claudecmd
pip install pyclacmd→ theclaudecmdcommand. 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 isclaudecmd.
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
Requirements
-
macOS (primary target; also works on Linux).
-
Python 3.8+.
-
Claude Code installed and authenticated.
claudecmdinvokes theclaudebinary found on yourPATH. Validate your setup:command -v claude claude --version claude "say hello" # interactive session works for your login
Authentication (
claude /loginor a subscription token) is a prerequisite —claudecmddoes not manage it. Pointclaudecmdat a specific binary with theCLAUDECMD_CLAUDE_BINenvironment 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
pipis 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_idorcost_usdis 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
--sessionvalue that is a UUID is resumed directly (--resume). - A friendly name is looked up in
~/.claudecmd/sessions.json(override the directory withCLAUDECMD_HOME) and resumed if present. Note: because the interactive TUI does not surface the session id,claudecmdcannot 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.
--debugoutput and the--dry-runcommand 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-modeis a passthrough: explicitly passingbypassPermissionsopts 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.
Metadata
Release files for pyclacmd 0.2.2
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| pyclacmd-0.2.2.tar.gz | 27.7 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| pyclacmd-0.2.2-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 48.6 kB
Release files / pyclacmd-0.2.2.tar.gz
| Download URL | pyclacmd-0.2.2.tar.gz |
|---|---|
| Size | 27.7 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
67319b37e401426aebdcc0ac55f534ac6bbcc085dda4e92a1d4913b57e88d45a
|
|
BLAKE2b-256 checksum How to use checksums |
d306c7e599d2298190e438fa82ea3af1d82a1ad6a64a48f3d1a4b7037a9cfcb7
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.12
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Jun 9, 2026.
Transparency logRelease files / pyclacmd-0.2.2-py3-none-any.whl
| Download URL | pyclacmd-0.2.2-py3-none-any.whl |
|---|---|
| Size | 20.9 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
2e15f072ba69f5d2e583388e232e566a7addbe4a2c9a7c173e0b5c76f72a8d98
|
|
BLAKE2b-256 checksum How to use checksums |
935013a4c9bc494984de6beee8fec9a9986ebd0c3fcb0882e0c5154929c1fd73
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.12
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Jun 9, 2026.
Transparency log