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 with proxy / viewer subcommands:
context-tracker help # show usage
context-tracker viewer # start the viewer
context-tracker proxy anthropic # start the proxy with a profileWithout installing you can run the scripts directly:
node src/viewer.js,node src/proxy.js <profile>. Both forms are interchangeable below.
context-tracker viewer # or: node src/viewer.jsThe viewer prints each request live in the terminal (with a model / system / tools / messages summary) and serves a web UI at http://localhost:39877.
Set REDACT_AUTH=1 to automatically redact API keys:
REDACT_AUTH=1 context-tracker viewerRecommended: start with a profile (no need to type TARGET_URL)
profiles.json ships with common providers preconfigured — start one by name:
context-tracker proxy anthropic # loads the "anthropic" profile from profiles.json
context-tracker proxy openai
context-tracker proxy claude+codex # multi-upstream routing profile
context-tracker proxy # no argument: interactively pick a profileEnvironment variables always override profile values (e.g. PROXY_PORT=9000 context-tracker proxy anthropic).
profiles.json format:
Available profile fields: target, routes, source, port, mirrorPort, captureResponse, maxResponseBytes, redactAuth.
Manual mode (without a profile)
# single upstream
TARGET_URL=https://api.deepseek.com node src/proxy.js
# 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"}
]' node src/proxy.jsClaude 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 |
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 unit tests, and end-to-end tests (mock SSE upstream + proxy + viewer) verifying routing, streaming passthrough, mirror classification, redaction, response aggregation, and profile startup.
| 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.
{ "anthropic": { "target": "https://api.anthropic.com", // single upstream "source": "claude-code", // mirror source label (defaults to the profile name) "captureResponse": true // optional: capture responses too }, "claude+codex": { "routes": [ // multi-upstream routing by path prefix (instead of target) {"prefix":"/v1/messages","target":"https://api.anthropic.com","source":"claude-code"}, {"prefix":"/v1/responses","target":"https://api.openai.com","source":"codex"} ], "captureResponse": true } }