Skip to main content

argpeek

version python license deps

"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:

  1. Sprinkle print(sys.argv) everywhere. Adds noise to production code, doesn't capture environment or cwd, and lies across the subprocess boundary.
  2. Use a shell tracer (set -x). Couples the diagnostic to a specific shell, leaks secrets, and produces output that downstream tools can't parse.
  3. 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, or cmd
  • a plain newline-delimited representation for xargs and 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. Select bash only 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 schemaargv, cwd, executable, python, env in a fixed key order, suitable for diffing in tests and CI.
  • 🌐 Four shells supportedposix, bash, powershell, cmd, chosen at runtime via --shell or shell_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 → uses sys.argv[1:] (the program name is not included).
  • env_keys is intersected with the built-in allowlist before lookup.
  • include_env=True (with no env_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:

  • 0 on success.
  • 2 on invalid arguments (e.g. unknown shell, unknown flag) — standard argparse behaviour.

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 (not python -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.exe has 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 every cmd quirk 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 \x00 in its output, but on Linux/macOS the kernel execve call 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

argpeek-0.1.1.tar.gz (27.5 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

argpeek-0.1.1-py3-none-any.whl (15.5 kB view details)

Uploaded Python 3

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

Hashes for argpeek-0.1.1.tar.gz
Algorithm Hash digest
SHA256 46370dcaf62cac578123495b9a263dbbffc39a9690e4c191d01c2c96abe39f01
MD5 af59fced7494466da4d6a819634c2593
BLAKE2b-256 bb779ecb3ef98e41b80f923b704d31896a5b07a75d45fca7f55f1de4e1594811

See more details on using hashes here.

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

Hashes for argpeek-0.1.1-py3-none-any.whl
Algorithm Hash digest
SHA256 1bc5edc749a39b63bed36c7a86c292b6348b7d56252300542a4609acb81f5dcb
MD5 6e21595a0ce1792eb21f5c233a386425
BLAKE2b-256 77d26a469d9883850983d5be1ba09ec53d424570d2d3dccc0fca103ae0d03462

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page