Skip to main content

Drive Claude Code's interactive session programmatically from scripts and pipelines

Project description

claudecmd

PyPI version Python versions CI License: MIT

pip install pyclacmd → the claudecmd command. Script Claude Code from the shell while keeping calls on your interactive (subscription) path, not separately-priced -p/headless usage.

A small, reliable command that drives your local, already-installed Claude Code interactive session programmatically — from scripts, shell pipelines, and editor integrations — and returns the assistant's reply as plain text or a stable JSON envelope.

It drives the interactive session (not claude -p) on purpose: Claude Code prices -p/headless usage separately from interactive sessions, so claudecmd keeps automated calls on your interactive (subscription) path. There is no network server and no API key handling of its own — it shells out to the claude binary you already use, under a pseudo-terminal.

The PyPI package is pyclacmd; the installed command is claudecmd.

claudecmd "say hello"
echo "summarize this" | claudecmd
git diff | claudecmd "Review this diff for risky changes"
claudecmd --json "explain this repo"
claudecmd --session 8f3c… "continue from previous context"

Demo

claudecmd demo


Requirements

  • macOS (primary target; also works on Linux).

  • Python 3.8+.

  • Claude Code installed and authenticated. claudecmd invokes the claude binary found on your PATH. Validate your setup:

    command -v claude
    claude --version
    claude "say hello"      # interactive session works for your login
    

    Authentication (claude /login or a subscription token) is a prerequisite — claudecmd does not manage it. Point claudecmd at a specific binary with the CLAUDECMD_CLAUDE_BIN environment variable.


Install

pip install pyclacmd

This provides the claudecmd console script. pexpect and pyte (used to drive and render the interactive TUI) are installed automatically.

macOS note: if the system pip is too old, use a virtualenv:

python3 -m venv .venv && source .venv/bin/activate
pip install pyclacmd

Usage

claudecmd [options] [prompt]

The prompt comes from (in priority order): the positional argument, then piped STDIN. If both are present they are combined:

<positional prompt>

--- STDIN ---
<stdin content>

Flags

Flag Description
--json Emit a stable JSON envelope (see below).
--raw Print the full rendered TUI screen (debugging aid).
--cwd <path> Working directory Claude runs in.
--session <id-or-name> Resume a session by UUID or local name.
--timeout <seconds> Abort (and clean up) after N seconds.
--model <model> Model alias (opus, sonnet, haiku) or full id.
--tools <tools> Built-in tools to allow (e.g. "Bash,Read"); "" disables all.
--permission-mode <mode> Claude permission mode (e.g. plan, acceptEdits).
--system-prompt <text> Replace the system prompt.
--append-system-prompt <text> Append to the system prompt.
--allowed-tools <patterns> Permission allow patterns, e.g. "Bash(git:*),Read".
--disallowed-tools <patterns> Permission deny patterns.
--add-dir <path> Extra allowed directory (repeatable).
--debug Emit redacted diagnostics to stderr; keep oversize-stdin temp files.
--dry-run Print the command plan and exit without calling Claude.
--version / --help Standard.

Examples

# Basic
claudecmd "explain this repo"

# Pipe input as JSON
cat task.md | claudecmd --json

# Review a git diff
git diff | claudecmd "Review for risky changes and return concise findings"

# Unattended: disable tools so no permission prompt can stall the run
claudecmd --tools "" "summarize the open questions in this file"

# See exactly what would run, without running it
claudecmd --dry-run "say hello"

Output contract

Default (human) mode

Prints only the assistant's final reply to stdout, extracted from the rendered session. No banners, no metadata.

--json mode

Emits one JSON object:

{
  "ok": true,
  "result": "assistant response text",
  "session_id": null,
  "duration_ms": 6824,
  "cost_usd": null,
  "raw": null,
  "mode": "interactive"
}

session_id, cost_usd, and raw are null — the interactive TUI does not expose them. On failure:

{ "ok": false, "error": "…", "kind": "claude_timeout", "exit_code": 124, "duration_ms": 1234 }

--raw mode

Prints the full rendered TUI screen unchanged — useful for debugging extraction.

--dry-run mode

Prints the exact command plan (run through the secret redactor) and exits:

{ "ok": true, "dry_run": true, "command": ["claude", "say hello", "--model", "haiku"] }

How it works

claudecmd spawns claude "<prompt>" (interactive, no -p) under a pseudo-terminal, renders the TUI with a real terminal emulator (pyte) so layout and whitespace survive, auto-answers the one-time workspace-trust dialog for --cwd, waits for the turn to settle, and extracts the assistant's reply from the rendered screen.

Caveats — it scrapes a human-facing TUI, so it is inherently less robust than a headless API:

  • No session_id or cost_usd is available (the TUI does not expose them).
  • Completion is detected heuristically (the reply settles and the input box returns). Give long replies a larger --timeout.
  • A tool-permission prompt will stall an unattended run — pass --tools "" to disable tools, or an appropriate --permission-mode.
  • Extraction is tuned to Claude Code's current TUI (v2.1.x) and may need updating if the interface changes.

Sessions

  • A --session value that is a UUID is resumed directly (--resume).
  • A friendly name is looked up in ~/.claudecmd/sessions.json (override the directory with CLAUDECMD_HOME) and resumed if present. Note: because the interactive TUI does not surface the session id, claudecmd cannot record a new name→id mapping on this path — pre-seed names or resume by UUID.

Large input

STDIN larger than the guard (default 9 MB) is not silently truncated. It is written to a restrictive (0600) temp file under the OS temp directory, and the prompt references that file path so Claude can read it. The temp file is deleted after the run unless --debug is set.


Error kinds

Every failure carries a stable kind (the kind field in --json mode; in the stderr message otherwise) and a non-zero exit code:

Kind Exit Meaning
claude_not_found 127 The claude binary was not found / not executable.
claude_auth_required 2 Claude reported an authentication problem.
claude_timeout 124 The run exceeded --timeout.
cwd_not_found 66 --cwd is not an existing directory.
session_store_error 74 The session mapping file could not be read/written.
pty_unavailable 69 pexpect/pyte could not be imported.
no_prompt 64 No prompt via argument or stdin.
unknown 1 Anything else (including no extractable reply).

Security notes

  • Prompts and flags are passed as a subprocess argument array — never interpolated into a shell string, so there is no shell-injection surface.
  • Prompts are not written to disk except for the oversize-stdin case above.
  • Temp files and the session store use restrictive permissions.
  • --debug output and the --dry-run command plan are run through a best-effort secret redactor (API keys, bearer tokens, Authorization: headers, .env-style assignments).
  • No permission-bypass flags are enabled by default. --permission-mode is a passthrough: explicitly passing bypassPermissions opts into Claude Code's auto-approve behavior — a deliberate choice, never a default.

Development

pip install -e ".[dev]"
pytest

The test suite mocks the claude binary / interactive runner, so CI never requires a real Claude login. GitHub Actions runs pytest on Python 3.12 / Ubuntu; the package itself supports Python 3.8+.

License

MIT — see LICENSE.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

pyclacmd-0.2.2.tar.gz (27.7 kB view details)

Uploaded Source

Built Distribution

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

pyclacmd-0.2.2-py3-none-any.whl (20.9 kB view details)

Uploaded Python 3

File details

Details for the file pyclacmd-0.2.2.tar.gz.

File metadata

  • Download URL: pyclacmd-0.2.2.tar.gz
  • Upload date:
  • Size: 27.7 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for pyclacmd-0.2.2.tar.gz
Algorithm Hash digest
SHA256 67319b37e401426aebdcc0ac55f534ac6bbcc085dda4e92a1d4913b57e88d45a
MD5 6586406cd5afc1b4c8f2bec113150304
BLAKE2b-256 d306c7e599d2298190e438fa82ea3af1d82a1ad6a64a48f3d1a4b7037a9cfcb7

See more details on using hashes here.

Provenance

The following attestation bundles were made for pyclacmd-0.2.2.tar.gz:

Publisher: publish.yml on kurok/pyclacmd

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file pyclacmd-0.2.2-py3-none-any.whl.

File metadata

  • Download URL: pyclacmd-0.2.2-py3-none-any.whl
  • Upload date:
  • Size: 20.9 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for pyclacmd-0.2.2-py3-none-any.whl
Algorithm Hash digest
SHA256 2e15f072ba69f5d2e583388e232e566a7addbe4a2c9a7c173e0b5c76f72a8d98
MD5 e9d49fcefbd511c70832624464a04ec0
BLAKE2b-256 935013a4c9bc494984de6beee8fec9a9986ebd0c3fcb0882e0c5154929c1fd73

See more details on using hashes here.

Provenance

The following attestation bundles were made for pyclacmd-0.2.2-py3-none-any.whl:

Publisher: publish.yml on kurok/pyclacmd

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

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