argpeek
"Why did my subprocess receive that? argpeek shows the exact argv, process metadata, and a shell-safe replay — without executing a thing."
Quick Start
pip install git+https://github.com/prasad-a-abhishek/argpeek.git
This repository is not yet published on PyPI, so the GitHub URL is the supported install source.
from argpeek import capture, render_json, shell_quote
snap = capture(argv=["a b", "", "--x"], cwd="/tmp", env_keys=["PATH"])
print(render_json(snap))
print(shell_quote(snap.argv, shell="bash"))
python -m argpeek --json -- 'a b' '' --flag
python -m argpeek --shell bash -- '$(touch SHOULD_NOT_RUN)'
python -m argpeek --pretty --env PATH --env HOME -- foo bar
⚡ Performance & Benchmarks
50-iteration head-to-head benchmark (10 workload profiles × 5 runs each)
comparing argpeek against the closest existing tool — Python's
shlex.quote (stdlib) and
manual string concatenation. The benchmark is reproducible via
python3 benchmarks/run_benchmark.py.
| Workload (1000 args) | argpeek (median µs) | shlex.quote (median µs) | manual concat (median µs) |
|---|---|---|---|
| safe_alnum | 396.3 | 80.6 | 38.4 |
| spaces_in_values | 1172.4 | 166.2 | 49.4 |
| unicode_payload | 1089.0 | 164.8 | 53.2 |
| newlines_and_tabs | 1733.6 | 170.9 | 61.3 |
| shell_metacharacters | 750.1 | 152.2 | 43.7 |
| empty_and_short | 151.7 | 56.5 | 31.0 |
| single_quote_only | 423.2 | 165.5 | 34.5 |
| bash_ansi_c_quote_required | 821.9 | 148.3 | no concept³ |
| powershell_payload | 635.3 | 166.9 | no concept³ |
| cmd_trailing_backslash | 429.8 | no concept⁴ | 35.9 |
shlex.quote preserves embedded newlines, tabs, and empty strings for POSIX
shells; it is faster because it solves that narrower one-shell problem.
argpeek adds deterministic rendering for Bash control-character syntax,
PowerShell, and cmd.exe, plus argv/process snapshots. shlex.quote has no
notion of $'…' ANSI-C quoting for control characters or cmd.exe semantics.
Why argpeek is slower on the safe cases: it does strictly more work — it
supports four shells, deterministically quotes every argument, and produces a
JSON-stable result. shlex.quote is the faster, correct choice for quoting one
POSIX-shell token; manual concatenation is faster still but is not safe quoting.
The benchmarks above show argpeek is 3–10× slower than shlex.quote on the
easy cases. Argpeek's value is its broader cross-shell and snapshot contract,
not a speed advantage over the standard library.
Full results — including mean, p95, and per-shell sub-tables — live in
benchmarks/BENCHMARK.md. Run yourself:
python3 benchmarks/run_benchmark.py
Why argpeek?
CLI debugging usually degenerates into one of three workarounds, all unpleasant:
- Sprinkle
print(sys.argv)everywhere. Adds noise to production code, doesn't capture environment or cwd, and lies across the subprocess boundary. - Use a shell tracer (
set -x). Couples the diagnostic to a specific shell, leaks secrets, and produces output that downstream tools can't parse. - Build a one-off argparse shim. Pulls in a framework when all you wanted was a passive argv inspector — argparse actually consumes options rather than echoing them.
argpeek is the third thing you reach for but never had: a tiny
standard-library-only utility that treats every post--- token as opaque
data. It never parses, executes, or interprets the payload; it only echoes
it back, in three useful forms:
- a stable JSON snapshot with argv + cwd + executable + python + env
- a deterministic shell-escaped replay for
posix,bash,powershell, orcmd - a plain newline-delimited representation for
xargsand friends
The environment is captured only on explicit request, against a hard-coded
allowlist (PATH, HOME, USER, …) — secret-bearing keys like
AWS_SECRET_ACCESS_KEY and API_KEY are silently dropped so typos can't
leak credentials into logs or CI artifacts.
Trade-offs we made
- No shell parser. We re-emit a single, safe, shell-escaped line; we do
not parse an arbitrary shell command back into argv. That is a much
larger problem (see
fish's AST for a taste). Our output is always safe to copy/paste back into a shell, which is what humans and CI need. - No network, no plugins, no color. Reproducibility wins. Snapshots diff cleanly across machines.
- POSIX-by-default for bash. Bash shares most quoting rules with POSIX
shells; we use
$'…'(ANSI-C quotes) only when an argument contains control characters that must not be expanded. Selectbashonly when the replay target really is Bash;$'…'is not portable to every/bin/sh.
Key Features
- 🪶 Zero runtime dependencies — uses only Python's standard library.
- 🔒 Safe by default — payload is never executed, even if it contains
$(rm -rf /),`evil`, or; shutdown. - 📦 Stable JSON schema —
argv,cwd,executable,python,envin a fixed key order, suitable for diffing in tests and CI. - 🌐 Four shells supported —
posix,bash,powershell,cmd, chosen at runtime via--shellorshell_quote(..., shell=...). - 🎯 Allowlisted environment capture — sensitive keys are silently filtered; missing keys are omitted (never null).
- 🧬 Deterministic output — same input always produces byte-identical output across machines and Python versions.
- 🧪 129-test suite covering all 30 acceptance criteria + edge cases + subprocess/CLI parity + round-trip + malicious payload safety.
Library API
from argpeek import Snapshot, capture, render_json, shell_quote, RENDERERS
Snapshot
Immutable record of one observed command invocation.
| field | type | meaning |
|---|---|---|
argv |
tuple[str, ...] |
Argument vector exactly as observed. |
cwd |
str |
Working directory at capture time. |
executable |
str |
Resolved interpreter path (sys.executable). |
python |
str |
Python version string (sys.version). |
env |
tuple[tuple,…] |
Sorted, allowlisted env entries; empty if none captured. |
capture(argv=None, *, cwd=None, env_keys=None, include_env=False, executable=None, python=None)
Build a Snapshot.
argv=None→ usessys.argv[1:](the program name is not included).env_keysis intersected with the built-in allowlist before lookup.include_env=True(with noenv_keys) captures every set allowlisted key.- Returns a frozen
Snapshot; the input list is never mutated.
render_json(snapshot, *, pretty=False)
Return a deterministic JSON string with argv, cwd, executable,
python, and env in canonical key order. Unicode is not escaped.
shell_quote(argv, *, shell="posix")
Return a single-line, shell-safe representation. Supported shells:
| name | when to use |
|---|---|
posix |
Default; sh, dash, bash, zsh. |
bash |
Adds $'…' ANSI-C quoting for control chars. |
powershell |
Windows PowerShell 5.1+ and PowerShell Core 7+. |
cmd |
cmd.exe (handles trailing-backslash edge case). |
Raises ValueError for unknown shell names and TypeError for non-string
arguments.
CLI Reference
python -m argpeek [-h] [--json] [--pretty] [--shell {bash,cmd,posix,powershell}]
[--env KEY] [--cwd CWD] [payload ...]
| flag | purpose |
|---|---|
--json |
Emit a JSON snapshot on stdout. |
--pretty |
Pretty-print JSON (implies --json). |
--shell SHELL |
Emit a shell-escaped replay line. |
--env KEY |
Capture env var KEY (repeatable, allowlisted). |
--cwd CWD |
Record CWD instead of os.getcwd(). |
-- |
Begin the opaque payload (optional but recommended). |
payload … |
The arguments to capture; never re-interpreted as options. |
Exit codes:
0on success.2on invalid arguments (e.g. unknown shell, unknown flag) — standardargparsebehaviour.
Tip: payload arguments that start with
-must be passed after a--separator, otherwise argparse treats them as options and exits with code 2. Example:python -m argpeek -- --flag-like(notpython -m argpeek --flag-like).
Examples
# Snapshot a subprocess invocation as JSON, including PATH and HOME.
$ python -m argpeek --json --pretty --env PATH --env HOME -- 'arg with spaces' --flag
{
"argv": ["arg with spaces", "--flag"],
"cwd": "/work",
"executable": "/usr/bin/python",
"python": "3.11.15 (main, …)",
"env": {"HOME": "/home/me", "PATH": "/usr/bin:/bin"}
}
# Show what a shell would see after argv parsing.
$ python -m argpeek --shell bash -- '$(touch SHOULD_NOT_RUN)' 'a b' 'café'
'$(touch SHOULD_NOT_RUN)' 'a b' 'café'
# Round-trip safely back into bash (echoes the same payload, untouched).
$ cmd=$(python -m argpeek --shell bash -- "$@") && eval "$cmd" # only if YOU choose to eval
# Capture an argv from a generator (useful in pytest fixtures).
$ python -c "from argpeek import capture, render_json; print(render_json(capture(argv=['x','y'])))"
{"argv":["x","y"],…}
Limitations
- Quoting covers the deterministic subset.
cmd.exehas historic edge cases around caret-escaping and percent-expansion. We cover the documented rules for double-quoted strings (trailing backslashes, embedded double quotes) AND the active cmd metacharacters (& | < > ^ ( ) %), each of which is^-escaped so the rendered command is safe to replay. We do not claim correctness for everycmdquirk in every Windows version (e.g.%ERRORLEVEL%expansion in batch files,!VAR!delayed expansion,^|inside parenthesised blocks). If you need those, wrap the renderer output in an additional layer or use a dedicated Windows shell escaping library. - No shell parsing. We don't parse an arbitrary shell command line back into argv. Use a proper shell parser if that's what you need.
- NUL bytes are truncated by the OS, not the renderer. argpeek
faithfully quotes
\x00in its output, but on Linux/macOS the kernelexecvecall truncates the argv at the first NUL byte, so the receiving process will never see a NUL-containing argument regardless of how it is quoted. This is an OS limitation, not an argpeek bug. - No secrets by default. If you ask for
--env AWS_SECRET_ACCESS_KEY, the answer is empty (and silent) — by design.
Non-goals
- Executing, replaying, or spawning the captured command.
- Reverse-engineering a command line from a raw string.
- A general argument parser, subcommand framework, autocomplete, or config-file system.
- Capturing secrets without explicit per-key opt-in.
- Windows API-specific process inspection beyond deterministic quoting text.
- Runtime dependencies, telemetry, network access, plugins, or colorized output.
Development
The specification's 450-LOC estimate was a soft scope guideline, not a shipment
threshold. The implementation is currently 557 physical lines under src/
(480 nonblank lines): the variance preserves complete typing, docstrings, four
shell renderers, and the security fix rather than deleting required behavior.
The contract explicitly gives clean code and complete tests precedence over an
arbitrary line count.
git clone https://github.com/prasad-a-abhishek/argpeek
cd argpeek
pip install -e .
pytest -v # 129 tests, no external services
python -c "import argpeek; assert argpeek.__version__ == '0.1.1'"
python -m argpeek --help
argpeek.__version__ is the supported version smoke. The CLI intentionally has
no --version flag in v0.1.1; argparse therefore exits 2 for that unsupported
option.
License
MIT © 2026 Abhishek Prasad — see LICENSE.
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file argpeek-0.1.1.tar.gz.
File metadata
- Download URL: argpeek-0.1.1.tar.gz
- Upload date:
- Size: 27.5 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.11.15
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
46370dcaf62cac578123495b9a263dbbffc39a9690e4c191d01c2c96abe39f01
|
|
| MD5 |
af59fced7494466da4d6a819634c2593
|
|
| BLAKE2b-256 |
bb779ecb3ef98e41b80f923b704d31896a5b07a75d45fca7f55f1de4e1594811
|
File details
Details for the file argpeek-0.1.1-py3-none-any.whl.
File metadata
- Download URL: argpeek-0.1.1-py3-none-any.whl
- Upload date:
- Size: 15.5 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.11.15
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
1bc5edc749a39b63bed36c7a86c292b6348b7d56252300542a4609acb81f5dcb
|
|
| MD5 |
6e21595a0ce1792eb21f5c233a386425
|
|
| BLAKE2b-256 |
77d26a469d9883850983d5be1ba09ec53d424570d2d3dccc0fca103ae0d03462
|