Skip to content

Latest commit

 

History

5 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

pty-claude

pty-claude is a CLI compatibility wrapper for common claude -p subprocess usage.

It accepts -p / --print at your call site, but it does not forward print mode to Claude Code. Instead it starts the normal interactive Claude Code CLI inside a pseudo-terminal, sends one prompt, reads Claude's local transcript JSONL, and emits claude -p-shaped output.

This is useful when existing automation expects a claude -p-style command, but you want the request to go through interactive Claude Code behavior.

pty-claude is not official Anthropic software and is not an Agent SDK.

Requirements

  • Local claude CLI installed and authenticated.
  • Go 1.26+ to build from source.
  • A Unix-like PTY environment. Linux and macOS are the intended targets.
  • Claude Code must be able to write its normal transcript files under ~/.claude/projects.

Windows is not verified.

Install

go install github.com/cunninghamcard-bit/pty-claude/cmd/pty-claude@latest

Or build from a checkout:

git clone https://github.com/cunninghamcard-bit/pty-claude.git
cd pty-claude
go build -o ./bin/pty-claude ./cmd/pty-claude

If your real Claude binary is not named claude, set it explicitly:

PTY_CLAUDE_REAL_BIN=/usr/local/bin/claude pty-claude -p "hello"

You can also pass it per invocation:

pty-claude --claude-bin /usr/local/bin/claude -p "hello"

Usage

Text:

pty-claude -p "only output OK" --output-format text

JSON:

pty-claude -p "only output OK" --output-format json

Stream JSON:

pty-claude -p "only output OK" --output-format stream-json

Stdin:

printf "only output OK\n" | pty-claude -p --output-format json

Common Claude Code flags are forwarded to the real interactive CLI:

pty-claude -p "fix this bug" \
  --cwd /path/to/repo \
  --model sonnet \
  --system-prompt "Be concise" \
  --permission-mode acceptEdits \
  --output-format json \
  --timeout 120000

--input-format stream-json is accepted for simple single-shot callers. User messages are extracted from stdin and sent as one interactive prompt:

printf '%s\n' \
  '{"type":"user","message":{"role":"user","content":"only output OK"}}' \
  | pty-claude -p --input-format stream-json --output-format stream-json

Help:

pty-claude --help
pty-claude --version

Subprocess Examples

Node:

import { spawnSync } from "node:child_process";

const child = spawnSync(
  "pty-claude",
  ["-p", "only output OK", "--output-format", "json"],
  { encoding: "utf8" },
);

if (child.status !== 0) {
  throw new Error(child.stderr || child.stdout);
}

const result = JSON.parse(child.stdout);
console.log(result.result);

Python:

import json
import subprocess

completed = subprocess.run(
    ["pty-claude", "-p", "only output OK", "--output-format", "json"],
    text=True,
    capture_output=True,
    check=True,
)

result = json.loads(completed.stdout)
print(result["result"])

Output

Text output prints only the assistant text.

JSON output is shaped like the common claude -p --output-format json result:

{
  "type": "result",
  "subtype": "success",
  "is_error": false,
  "api_error_status": null,
  "duration_ms": 1234,
  "duration_api_ms": null,
  "num_turns": 1,
  "result": "OK",
  "stop_reason": "end_turn",
  "session_id": "...",
  "total_cost_usd": null,
  "usage": {},
  "modelUsage": null,
  "permission_denials": null,
  "terminal_reason": "completed",
  "fast_mode_state": null,
  "uuid": "..."
}

stream-json prints assistant transcript events as JSONL, followed by the final result event. Field names are normalized toward print-mode style, for example session_id, parent_tool_use_id, and final result.

Field Truthfulness

pty-claude does not invent values just to make the JSON look complete.

Fields that are available from the interactive transcript are copied or derived from that transcript. Fields that are native to print mode but unavailable from interactive transcript data are emitted as null.

Common null fields include:

  • duration_api_ms
  • total_cost_usd
  • modelUsage
  • permission_denials
  • fast_mode_state
  • uuid, when the transcript event does not contain one

By default, pty-claude also does not pass --session-id to Claude. Claude generates the session id, and pty-claude discovers the matching transcript by looking for the user prompt in newly written transcript files.

If you explicitly pass --session-id, it is forwarded to Claude:

pty-claude -p "hello" --session-id 11111111-2222-4333-8444-555555555555

How It Works

caller
  -> pty-claude -p "prompt"
  -> real claude interactive CLI in a PTY
  -> prompt is written as keyboard input
  -> ~/.claude/projects/<cwd-encoded>/*.jsonl is discovered and tailed
  -> text/json/stream-json output is emitted

For a working directory /tmp/example, Claude usually writes transcripts under:

~/.claude/projects/-tmp-example/*.jsonl

The -p / --print flag is consumed only for compatibility. It is never sent to the real Claude binary.

Print-only flags such as --max-budget-usd, --json-schema, --no-session-persistence, and --include-partial-messages are also consumed instead of being forwarded.

Compatibility Target

The target is common subprocess usage:

claude -p "prompt" --output-format json
printf "prompt" | claude -p --output-format json
printf '{"type":"user","message":{"role":"user","content":"prompt"}}\n' \
  | claude -p --input-format stream-json --output-format stream-json

The replacement is explicit:

pty-claude -p "prompt" --output-format json

This project aims to replace the most common claude -p process boundary, not to clone every Agent SDK protocol detail.

Limits

  • Transcript JSONL is a local Claude Code implementation detail.
  • stream-json is transcript-based, not the official print-mode stream.
  • Event timing, token accounting, and some print-only fields can differ.
  • Tool permission prompts can still block unless your Claude Code flags allow the operation.
  • Claude UI changes can affect prompt detection.

Development

go test ./...
go build -o ./bin/pty-claude ./cmd/pty-claude

Optional live smoke test:

./bin/pty-claude -p "only output OK" \
  --cwd /tmp/pty-claude-live \
  --model sonnet \
  --tools "" \
  --system-prompt "Reply exactly with the requested token and no extra text." \
  --output-format json

License

MIT

About

Run Claude Code interactively through a PTY and expose claude -p-shaped CLI output

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages