Claude-bridge: A Drop-in Replacement for claude -p Available After June 15
claude-bridge is a bridge tool that replaces common claude -p automation by launching interactive Claude Code sessions inside tmux, sending prompts via tmux, capturing transcripts, formatting replies, and exiting at turn end. It supports print mode, streaming, JSON Schema validation, and aims to be a drop-in replacement for claude -p in shell scripts.
Notifications You must be signed in to change notification settings
Fork 0
Star 1
BranchesTags
Open more actions menu
Folders and files
NameName
Last commit message
Last commit date
Latest commit
History
17 Commits
17 Commits
.github/workflows
.github/workflows
docs
docs
scripts
scripts
src
src
.gitignore
.gitignore
AGENTS.md
AGENTS.md
CLAUDE.md
CLAUDE.md
CODE_OF_CONDUCT.md
CODE_OF_CONDUCT.md
CONTRIBUTING.md
CONTRIBUTING.md
LICENSE
LICENSE
README.md
README.md
SECURITY.md
SECURITY.md
bun.lock
bun.lock
package.json
package.json
tsconfig.json
tsconfig.json
Repository files navigation
claude-bridge is a bridge-owned replacement for common claude -p automation.
Instead of delegating to raw claude -p, it starts normal interactive Claude Code inside a detached tmux pane, sends your prompt through tmux, tails Claude's own on-disk transcript, formats the reply, and exits at turn end.
That keeps prompt dispatch, transcript capture, --output-format, JSON schema validation, and process exit behavior inside the bridge.
Raw Claude Code print mode
claude -p "say hi" --output-format json
Bridge-owned replacement
bunx @desplega.ai/claude-bridge -p "say hi" --output-format json
The transcript source is the same JSONL file Claude writes under ~/.claude/projects//.jsonl. Outside print mode, piped consumers get bridge envelopes and TTY users get a compact readable view. The transcript tailing follows the Shannon technique: snapshot the pre-existing *.jsonl set before launch, poll for a fresh file, and poll-and-reparse it every 100 ms.
The orchestrator also pre-clears the prompts that would otherwise block Claude's UI:
Claude's global config is edited so projects[].hasTrustDialogAccepted and hasCompletedProjectOnboarding are set. This is ~/.claude.json by default, or $CLAUDE_CONFIG_DIR/.claude.json when CLAUDE_CONFIG_DIR is set. The previous file is backed up alongside it as .claude.json.claude-bridge-backup.
A per-workdir .claude/settings.local.json sets defaultMode: "bypassPermissions" and skipDangerousModePermissionPrompt: true.
claude is launched with --dangerously-skip-permissions.
Theme/security startup prompts are auto-accepted by watching tmux capture-pane for marker text and sending Enter. With --desplega-local-auth, the custom API key confirmation prompt is also auto-accepted. Login-method selection is deliberately not auto-accepted.
+--------------------+ | claude-bridge | | - tmux paste | | - transcript tail | +----------+---------+ | | tmux paste-buffer + Enter v +------+-----------------------------+ | tmux session claude-bridge- | | pane 0: claude --dangerously-... | +------------------------------------+
Requirements
Bun (>= 1.1)
claude CLI on PATH, version >= 2.1.80
tmux on PATH.
Claude Code authenticated for the spawned claude process.
Use From npm
Run without installing:
Drop-in print-mode usage.
bunx @desplega.ai/claude-bridge -p "say hi" bunx @desplega.ai/claude-bridge -p "say hi" --output-format json bunx @desplega.ai/claude-bridge -p "say hi" --output-format stream-json
Opt in to Desplega/bridge envelopes for bridge-specific consumers.
bunx @desplega.ai/claude-bridge -p "say hi" --output-format stream-json --desplega-format
Install globally with Bun:
bun install -g @desplega.ai/claude-bridge claude-bridge -p "say hi" claude-bridge --help
Install globally with npm:
npm install -g @desplega.ai/claude-bridge claude-bridge -p "say hi"
The installed command is claude-bridge. Bun is still required at runtime because the published bin uses #!/usr/bin/env bun.
Print Mode
claude-bridge -p "say hi" claude-bridge -p "say hi" --model sonnet claude-bridge -p "say hi" --output-format json claude-bridge -p "say hi" --output-format stream-json printf 'say hi\n' | claude-bridge --print
Print mode is intended for shell automation that would otherwise call claude -p:
claude -p "say hi" --output-format json claude-bridge -p "say hi" --output-format json
claude -p "say hi" --output-format stream-json claude-bridge -p "say hi" --output-format stream-json
This is intended as a drop-in replacement for common claude -p automation. In print mode the wrapper starts an interactive Claude session in tmux, waits for the pane to become ready, sends the prompt through tmux, prints the requested format, then kills the tmux session.
By default, print-mode stdout is reserved for the requested Claude-compatible output. Bridge envelopes and bridge debug events are not written to stdout in json or stream-json mode unless you explicitly pass --desplega-format.
Auth
claude-bridge does not call the Anthropic API itself. It launches the local claude CLI and relies on whatever authentication that claude process can use.
For local interactive machines, first make sure claude works:
claude auth status claude -p "say hi"
Then run the bridge:
claude-bridge -p "say hi"
For headless CI, use the long-lived Claude Code OAuth token from:
claude setup-token
Set it exactly as printed:
export CLAUDE_CODE_OAUTH_TOKEN=sk-ant-oat01-... claude-bridge -p "say hi" --output-format json
By default, the spawned Claude process receives HOME, CLAUDE_CONFIG_DIR, and CLAUDE_CODE_OAUTH_TOKEN; Anthropic provider env vars such as ANTHROPIC_API_KEY and ANTHROPIC_AUTH_TOKEN are cleared so the bridge does not accidentally test a different auth path.
Use --desplega-local-auth when you intentionally want the spawned Claude process to receive local auth-related env vars. If Claude shows the custom API key confirmation prompt, this mode selects the API-key path:
ANTHROPIC_API_KEY=... claude-bridge --desplega-local-auth -p "say hi"
If Claude shows a browser login or login-method selector, the bridge will not auto-select it. Run claude auth status, run claude setup-token, or attach to the tmux pane shown in the banner and complete the prompt manually.
Output Formats
-p/--print requires a prompt argument or piped stdin. --output-format requires print mode and accepts text, json, or stream-json; the default is text. --json-schema is also print-only.
Compatibility mode is the default. If you are replacing claude -p in scripts, do not pass --desplega-format.
The final result comes from the transcript. When Claude writes a system turn_duration row, the wrapper uses the latest assistant text it saw in that turn.
text: prints only the final answer text plus a trailing newline. Wrapper errors go to stderr and exit non-zero.
json: prints one final Claude-compatible JSON result object with the answer in result, plus available transcript metadata such as session_id, duration_ms, and usage.
stream-json: streams raw Claude transcript JSONL rows as they are written. The bridge does not wrap them in custom envelopes.
Use --desplega-format when you want the older bridge-owned JSON envelopes in json or stream-json modes. This flag is for bridge-specific consumers, not drop-in claude -p replacement scripts:
claude-bridge -p "say hi" --output-format stream-json --desplega-format
With --desplega-format, json includes bridge debug metadata when --desplega-verbose is set, and stream-json prints newline-delimited bridge events as the run progresses, then a final result event. This is a custom claude-bridge event stream, not Claude's native stream-json schema.
Typical --desplega-format --output-format stream-json event types are:
These custom transcript events only exist with --desplega-format. In the default compatibility mode, the same Claude data is written as the top-level JSONL row.
Structured JSON
--json-schema is bridge-owned. It is not forwarded to raw claude -p; the wrapper keeps the normal tmux/transcript path, injects schema guidance with --append-system-prompt, extracts the last JSON value from the final assistant text, and validates it locally with Zod.
Existing user-provided --append-system-prompt values are preserved. When a schema is present, the wrapper merges those prompts with its schema instruction instead of replacing them.
Schema print mode also installs a global Claude Code Stop hook in ~/.claude/settings.json. The hook is inert outside claude-bridge schema runs; during a schema run it checks the final assistant text before Claude stops and blocks the stop if it does not validate. That gives Claude a bounded number of extra turns to answer with valid JSON before the wrapper exits.
Control that hook explicitly with:
claude-bridge --desplega-install claude-bridge --desplega-uninstall
Install is append-only and idempotent: unrelated hooks are preserved, and stale old claude-bridge hook commands are replaced with the current command.
The schema argument may be inline JSON or a path to a JSON file:
claude-bridge -p "Return the repo name" \ --json-schema '{"type":"object","required":["name"],"properties":{"name":{"type":"string"}}}' \ --output-format json
claude-bridge -p "Return the repo name" \ --json-schema ./schema.json \ --output-format text
Extraction is intentionally simple and deterministic:
Try the whole reply as JSON.
Otherwise use the last fenced json block.
Otherwise use the final balanced JSON object or array in the reply.
Validation uses Zod's z.fromJSONSchema() converter. That API is still marked experimental by Zod, but it keeps the bridge aligned with Zod's JSON Schema support instead of maintaining a handwritten validator here. If Zod cannot convert the schema, the wrapper treats that as a print-mode error.
With --output-format text, successful schema mode prints the extracted JSON value as compact JSON. With --output-format json, the final result includes structured_output alongside the original reply text in result. With --desplega-format, bridge JSON results also include structured_output_source.
If schema extraction or validation fails after Claude replies, json and stream-json error results include raw_response with the unmodified Claude reply. In text mode the same raw reply is printed to stderr under Raw Claude reply:.
The compact stringified schema is capped before Claude starts. The default cap is roughly 15000 tokens, estimated as ceil(chars / 4). Configure it with:
CLAUDE_BRIDGE_JSON_SCHEMA_MAX_TOKENS=30000 claude-bridge -p "..." --json-schema schema.json claude-bridge -p "..." --json-schema schema.json --desplega-json-schema-max-tokens=30000
Wrapper-owned vs forwarded
The wrapper owns these options and does not forward them to Claude:
-p/--print, --output-format, and --json-schema
--desplega-verbose, --desplega-local-auth, and other --desplega-[=] flags
--claude-help
-h/--help
-v/--version
Most interactive claude -h options pass through to the spawned Claude session, for example --model sonnet, --permission-mode acceptEdits, --append-system-prompt, or --allowed-tools. The wrapper always prepends its own launch flags: --dangerously-skip-permissions.
The initial prompt is wrapper-owned too. It is not passed to Claude as a CLI argument; once the pane is ready, the wrapper sends it through tmux. In non-print mode, stdin remains a small REPL that sends each entered line through the same tmux/transcript bridge.
Claude subcommands are intentionally blocked; run claude directly for commands such as doctor, mcp, plugin, update, agents, or auth. Claude modes that conflict with the bridge are also blocked: --tmux, --replay-user-messages/--replay*, and -w/--worktree.
Use --claude-help to see raw Claude help, with the caveat that wrapper-owned modes behave as described here. Use -v/--version to print the wrapper package version, the full claude path from which claude, and the claude -v output.
Use --desplega-verbose for extra wrapper debug output and raw transcript rows. Other --desplega-[=] flags are reserved for future wrapper features and are not forwarded to Claude.
Interactive Mode
The CLI prints a banner with the tmux session name and run state path:
tmux session : c
[truncated for AI cost control]