Skip to content

Latest commit

 

History

3 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

claude-bridge

Run the Claude Code CLI as a headless AI backend from any language. No API key required. Works with any Claude subscription.

Claude Bridge Architecture


Overview

Claude Bridge is a pattern for calling the Claude Code CLI as a subprocess from any program — Python, Node.js, Bash, Go — without a human at the terminal.

Instead of managing API keys, HTTP clients, or rate limit headers, you spawn claude as a child process, send your prompt via stdin, and read the response. Claude handles authentication, model selection, tool execution, and multi-turn reasoning.

echo "Analyze this codebase and list the main improvement areas." | claude \
  --dangerously-skip-permissions \
  --allowedTools "Read Glob Grep" \
  --max-turns 10

Claude reads files, edits code, runs shell commands, and calls web APIs depending on which tools you allow. The only requirement is a working Claude Code installation.


Two modes

Mode A - Metered Mode B - Subscription
Flag --print - (omit --print)
Output format JSON-Lines stream Raw text (ANSI cleanup needed)
Cost per call total_cost_usd returned Not tracked
Token counts input_tokens / output_tokens Not tracked
Session resume --resume <session_id> Not available
Best for Cost attribution per call High-volume automated pipelines

Quick start

Python - Mode B

import os, re, subprocess

def claude_invoke(prompt, cwd=".", tools=None, max_turns=10, timeout=900):
    env = {k: v for k, v in os.environ.items()
           if k not in {"CLAUDECODE", "CLAUDE_CODE_SESSION",
                        "CLAUDE_CODE_ENTRYPOINT", "CLAUDE_CODE_PARENT_SESSION"}}
    result = subprocess.run(
        ["claude", "--dangerously-skip-permissions",
         "--allowedTools", " ".join(tools or ["Read", "Glob", "Grep"]),
         "--max-turns", str(max_turns)],
        input=prompt, capture_output=True, text=True,
        cwd=cwd, env=env, timeout=timeout,
    )
    raw = result.stdout + result.stderr
    return re.sub(r'\x1b\[[0-9;]*[a-zA-Z]', '', raw).strip()

response = claude_invoke("Summarize the main purpose of this project.")
print(response)

Python - Mode A (with cost tracking)

import json, os, subprocess

def claude_invoke_metered(prompt, cwd=".", tools=None, max_turns=10):
    env = {k: v for k, v in os.environ.items()
           if k not in {"CLAUDECODE", "CLAUDE_CODE_SESSION",
                        "CLAUDE_CODE_ENTRYPOINT", "CLAUDE_CODE_PARENT_SESSION"}}
    result = subprocess.run(
        ["claude", "--print", "-", "--output-format", "stream-json", "--verbose",
         "--dangerously-skip-permissions",
         "--allowedTools", " ".join(tools or ["Read", "Glob", "Grep"]),
         "--max-turns", str(max_turns)],
        input=prompt, capture_output=True, text=True,
        cwd=cwd, env=env, timeout=300,
    )
    texts, cost = [], 0.0
    for line in result.stdout.splitlines():
        try:
            e = json.loads(line)
            if e.get("type") == "assistant":
                for b in e.get("message", {}).get("content", []):
                    if b.get("type") == "text":
                        texts.append(b["text"])
            elif e.get("type") == "result":
                cost = e.get("total_cost_usd", 0.0)
        except json.JSONDecodeError:
            pass
    return {"content": "\n\n".join(texts), "cost_usd": cost}

Node.js

const { invokeModeA, invokeModeB } = require('./examples/node/bridge.js');

// Mode B
const text = await invokeModeB('Explain what this file does.', {
  allowedTools: ['Read'],
  maxTurns: 5,
});

// Mode A with cost tracking
const { content, costUsd } = await invokeModeA('Write unit tests for auth.py', {
  allowedTools: ['Read', 'Glob', 'Edit', 'Write', 'Bash'],
  maxTurns: 20,
});

Bash

# Mode B (default)
./examples/bash/bridge.sh "List all Python files and their purpose"

# Mode A
./examples/bash/bridge.sh "Generate a test suite for this module" --mode-a

Rules

Strip nesting guards

When your program runs inside a Claude session, certain environment variables are set. Strip them before spawning the child process, otherwise the child claude process may refuse to start:

GUARDS = [
    "CLAUDECODE", "CLAUDE_CODE_ENTRYPOINT",
    "CLAUDE_CODE_SESSION", "CLAUDE_CODE_PARENT_SESSION",
]
env = {k: v for k, v in os.environ.items() if k not in GUARDS}

Restrict tool surfaces

Never give an agent more capability than it needs for the task:

Role --allowedTools
Read-only analysis Read Glob Grep
Code writer Edit Write Read Glob Grep Bash MultiEdit
Test runner Bash Read
Full agent Edit Write Read Glob Grep Bash MultiEdit WebFetch

Set --max-turns

Prevents runaway agent loops. Use 5-10 for simple tasks, 20-40 for complex multi-file work.

Set cwd to the project root

So Edit, Write, and Bash tools affect the right files:

subprocess.run([...], cwd="/path/to/target-project", ...)

Mode B timeout

Mode B has 10-30 seconds of startup overhead from the welcome banner. Use at least 2x the Mode A timeout as a baseline:

# Mode A: timeout=300
# Mode B: timeout=900 minimum

CLAUDE.md context

Both modes automatically load CLAUDE.md from the working directory during process initialization, before the first prompt is read. Place a CLAUDE.md at the root of your target project to give Claude persistent context:

# MyProject

FastAPI service. Test command: uv run pytest tests/.
Database: PostgreSQL at localhost:5432. Never commit .env files.

Token reduction

Reducing input tokens produces faster responses and lower cost in Mode A. Three techniques that compose cleanly:

RTK

RTK intercepts command output before it enters Claude's context window. A 200-test pytest failure produces around 15,000 tokens of raw output. RTK filters it to the failed tests and tracebacks only, around 800 tokens.

Instruct agents via system prompt:

Use rtk to filter verbose command output:
  rtk pytest tests/ -v
  rtk git diff main..HEAD

RTK preserves failures and tracebacks. It removes passing test lines and progress bars.

Install: npm i -g @rtk-ai/rtk

TOON

TOON (Token-Oriented Object Notation) encodes the same information as JSON in approximately 40% fewer tokens, losslessly. Claude reads it natively.

Apply to structured data sent in prompts (task lists, configuration, metadata):

# JSON
[{"task": "Write tests", "agent": "coder", "priority": "high"}]

# TOON (~33% fewer tokens)
tasks
  - task: Write tests
    agent: coder
    priority: high

Context truncation

For agents that write content rather than reading it, trim the input context:

BODY_LIMIT = 2500
issue_body = issue_body[:BODY_LIMIT]

Use as a Claude Code skill

Add to your project's CLAUDE.md:

## Agent backend
Use the Claude Bridge pattern from github.com/Dixter999/claude-bridge
when building any headless AI agent in this project.

Or copy CLAUDE.md from this repository into your project's .claude/commands/ folder.


Requirements

  • Claude Code installed and authenticated
  • --dangerously-skip-permissions available on the target machine
  • Python 3.8+ / Node.js 18+ / Bash 4+

Repository structure

claude-bridge/
├── examples/
│   ├── python/
│   │   ├── bridge_mode_a.py   Mode A - stream-json output with cost tracking
│   │   └── bridge_mode_b.py   Mode B - stdin-pipe, no cost tracking
│   ├── node/
│   │   └── bridge.js          Node.js implementation, both modes
│   └── bash/
│       └── bridge.sh          Bash wrapper
├── docs/
│   ├── mode-a.md              Mode A reference
│   ├── mode-b.md              Mode B reference
│   └── token-reduction.md     RTK, TOON, truncation details
├── assets/
│   └── diagram.svg            Architecture diagram
└── CLAUDE.md                  Skill instructions for Claude Code

License

MIT

About

Run the Claude Code CLI as a headless AI backend from any language. No API key required. Works with any Claude subscription.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors