Language: English · 简体中文
Mirror the HTTP requests that AI coding CLIs (OpenCode / Claude Code / Codex) send to model providers into a local viewer, so you can inspect the full request context — headers, system prompt, tool definitions, messages — and (optionally) the model's response.
Claude Code ──ANTHROPIC_BASE_URL──▶ ┐
Codex CLI ──config.toml base_url─▶ ├─▶ proxy ──HTTPS──▶ AI API
OpenCode ──opencode.json baseURL▶ ┘ │
└──POST──▶ viewer (:39877)
The proxy forwards traffic untouched to the upstream API and mirrors a copy of each request to the viewer — a fire-and-forget side channel that never blocks or alters the main request/response path. One proxy port can route to multiple upstreams by path prefix; the viewer classifies entries by source and API protocol.
Requires Node.js >= 18. Zero runtime dependencies.
Global install (recommended):
npm install -g context-tracker
# or from source: git clone https://github.com/conrad621/context-tracker.git && cd context-tracker && npm linkThis provides a single context-tracker command:
context-tracker start # start the viewer AND the proxy together (one process)
context-tracker help # show usageWithout installing you can run the scripts directly:
node src/start.js. The viewer and proxy still live atsrc/viewer.js/src/proxy.jsfor advanced use, but the CLI exposes onlystart.
Quick start — viewer + proxy together in one process:
context-tracker start # global install
npm start # from the repo (local)This starts the viewer (web UI + terminal) and the forwarding proxy as a single process — one Ctrl+C stops both.
Startup is quiet: only the listen URLs are printed. Pass --verbose (-v) to also see the hint lines — discovery source, route table, mirror URL, capture status, and the viewer's terminal-output note: context-tracker start --verbose (or npm start -- --verbose from the repo).
When Claude Code and Codex are both configured, start captures both (one port routes /v1/messages to Claude's upstream and /v1/responses to Codex's). To capture just one, pass --agent <claude|codex>: discovery then reads only that client's config and the proxy forwards only that agent's path prefix — other traffic gets a no route 404 instead of being forwarded to the wrong target:
context-tracker start --agent codex # capture Codex only
context-tracker start --agent claude # capture Claude Code onlystart runs the viewer (web UI at http://localhost:39877, live terminal output) and the forwarding proxy in one process. The proxy picks its upstream in this order:
-
Environment (highest freedom) —
TARGET_URL/ROUTESalways win over discovery; use them when you want a specific upstream, custom ports, or no config files at all:# single upstream TARGET_URL=https://api.deepseek.com context-tracker start # multi-upstream routing (capture Claude + Codex on one port) ROUTES='[ {"prefix":"/v1/messages","target":"https://api.anthropic.com","source":"claude-code"}, {"prefix":"/v1/responses","target":"https://api.openai.com","source":"codex"} ]' context-tracker start
-
Auto-discovery (default, no config needed) — if you use Claude Code and/or Codex CLI, the proxy derives its upstreams from the clients' own config files, so you don't need to maintain
profiles.jsonor setTARGET_URLat all:context-tracker start # no env: auto-discover upstreams
Discovery reads, following each tool's official posture and merge order:
- Claude Code —
~/.claude/settings.local.jsonthen~/.claude/settings.json(local overrides user) →env.ANTHROPIC_BASE_URL; falls back to theANTHROPIC_BASE_URLenvironment variable. Claude traffic always lands on/v1/messages. - Codex CLI —
~/.codex/config.toml→ the active[model_providers.*]base_url+wire_api; falls back toOPENAI_BASE_URL(wire viaCODEX_WIRE_API || "chat"). Codex traffic lands on/responses(or/chat/completionsfor chat wire).
When both are found, the proxy routes one port to both upstreams by prefix (--verbose shows where it found them). To point discovery at a non-default path (or to override in CI/tests): CT_CLAUDE_SETTINGS, CT_CLAUDE_SETTINGS_LOCAL, CT_CODEX_CONFIG. Set AUTO_DISCOVER=0 to disable.
A discovered upstream that points back at the proxy itself (a loopback host on the proxy/mirror port) is ignored — so a stale
ANTHROPIC_BASE_URL=http://localhost:39876capture setting won't create a loop. Legitimate local upstreams on other ports (e.g. Ollama on:11434) pass through. If every candidate is filtered, the startup error lists which source pointed back at the proxy so the fix is obvious. Auto-discovery only covers Claude Code and Codex configs; other tools (OpenCode, DeepSeek, …) still need env vars.
profiles.json(schema:target, orrouteswith per-prefixtarget/source; plusport/mirrorPort/captureResponse/maxResponseBytes/redactAuth) is retained for programmatic consumers vialoadProfiles()/resolveConfig()insrc/config.js(PROFILESoverrides its path); the CLI does not use it.
Claude Code (calls POST /v1/messages):
export ANTHROPIC_BASE_URL=http://localhost:39876
export ANTHROPIC_API_KEY=sk-ant-... # or ANTHROPIC_AUTH_TOKEN
claudeCodex CLI (calls POST /v1/responses) — edit ~/.codex/config.toml:
model_provider = "capture"
[model_providers.capture]
name = "Capture Proxy"
base_url = "http://localhost:39876/v1" # client sends /v1/responses
env_key = "OPENAI_API_KEY"
wire_api = "responses"export OPENAI_API_KEY=sk-...
codexCodex defaults to ChatGPT subscription login; to capture traffic you must use API-key auth (the
env_keyabove).
OpenCode — set the provider's baseURL to http://localhost:39876 in opencode.json.
⚠️ The/v1path prefix: Claude Code and Codex send the full path including/v1themselves, soTARGET_URL/ routetargetmust be the host root (e.g.https://api.anthropic.com, without/v1) — otherwise you get a doubled/v1/v1/...prefix. If OpenCode only sends/messages, thenTARGET_URLneeds to include/v1.
Request context appears live in both the viewer terminal and the web UI. Each entry offers structured / raw / headers / (optional) response views.
Web UI features:
- Search — the top search box filters by path / model / content in real time, with match highlighting (
/to focus,Escto clear) - Filter — source / api chips, multi-select and stackable
- Live metrics — total requests, currently shown, requests per minute
- Pause / follow / clear —
pausefreezes live updates,followauto-scrolls to newest,clearempties the view - Text selection — hover any content box for select-all / copy; double-click selects the whole block
- Incremental sync — the client only fetches new/updated entries (
/__history?since=), so poll cost stays flat regardless of history size - Virtual scrolling — only viewport rows are rendered; thousands of requests still mean only a few dozen DOM nodes
- Stable expansion — an expanded entry stays open and keeps its active tab across live refreshes
- Response usage — when response capture is on, the response pane parses the SSE stream into a token-usage / stop-reason / tool-call summary
- Request diff — a "diff" view compares each request against the previous one from the same source: system-prompt changes, added/removed/changed tools, added/removed message turns
- Token accounting & composition — each request shows a token count (real usage when captured, else estimated) and a stacked bar breaking it into system / tools / history / current, with per-tool cost
- Cost stats — a Stats panel aggregates captured traffic by model and source (requests, tokens, estimated USD cost); prices are configurable in
config/pricing.json - Waste detection — requests are flagged for oversized system prompts, large/duplicated tool schemas, over-long history, and repeated messages, with a suggestions list
- Session filter — requests carrying a session id (from headers or body) can be isolated to view one agent task's turns as a timeline
- Export — export the current view, a single request, or a session to JSON / Markdown / HAR (via export links or the
/__exportendpoint)
| Variable | Default | Description |
|---|---|---|
PROFILES |
./config/profiles.json |
path to the profile config file |
AUTO_DISCOVER |
1 |
when 1 (default), proxy with no profile/env derives upstreams from Claude Code / Codex config files; set 0 to disable |
CT_CLAUDE_SETTINGS |
~/.claude/settings.json |
auto-discovery: Claude Code settings file path |
CT_CLAUDE_SETTINGS_LOCAL |
~/.claude/settings.local.json |
auto-discovery: Claude Code local settings file path (overrides user settings) |
CT_CODEX_CONFIG |
~/.codex/config.toml |
auto-discovery: Codex CLI config file path |
PROXY_PORT |
39876 |
proxy listen port |
MIRROR_PORT |
39877 |
viewer listen port |
TARGET_URL |
- | single upstream base URL; used with or as the fallback for ROUTES |
ROUTES |
- | JSON array routing by prefix to different targets, with an optional source label |
SOURCE |
- | manual source label in single-upstream mode |
CAPTURE_RESPONSE |
- | when 1, tee and aggregate the SSE response and associate it with its request |
MAX_RESPONSE_BYTES |
2097152 |
response aggregation cap (truncated beyond this) |
MAX_HISTORY |
2000 |
max number of requests the viewer retains |
REDACT_AUTH |
- | when 1, redact API keys (applied at viewer render time) |
npm test # runs: node --testIncludes protocol-extraction unit tests (Anthropic Messages / OpenAI Chat / OpenAI Responses), config/profile-loading and auto-discovery unit tests, and end-to-end tests (mock SSE upstream + proxy + viewer) verifying routing, streaming passthrough, mirror classification, redaction, response aggregation, profile startup, and auto-discovery from injected client configs.
| API | Path | Source tool |
|---|---|---|
| Anthropic Messages | /v1/messages |
Claude Code |
| OpenAI Responses | /v1/responses |
Codex |
| OpenAI Chat Completions | /v1/chat/completions |
OpenCode / DeepSeek / etc. |
- Only works with header-authenticated providers (Anthropic, OpenAI, DeepSeek, OpenRouter, etc.). Signature-based auth such as AWS Bedrock (SigV4) or Google Vertex is not supported.
- Codex subscription login (ChatGPT) cannot be captured; switch to API-key auth.
- Response capture has a memory cost, bounded by
MAX_RESPONSE_BYTES; oversized responses keep only a truncated copy.
Where the project is headed — see ROADMAP.md.