🔍 Proxy-layer microscope for LLM traffic analysis
A cross-platform command-line tool that intercepts, analyzes, and logs communications between AI coding tools/agents (Claude Code, Cursor, Codex, OpenCode, etc.) and their backend LLM APIs.

lli run) - Wrap any command (claude -p, codex exec, ...) with fully automated, non-interactive capture — ideal for scripted agent experimentsuv tool install llm-interceptor
pip install llm-interceptor
git clone https://github.com/chouzz/llm-interceptor.git
cd llm-interceptor
uv sync --dev
uv run lli-dev-setup
Git does not copy hooks from .git/hooks when you clone a repository, so each
new clone must install the project's pre-commit hook once:
uv sync --dev
uv run lli-dev-setup
If you prefer pip:
pip install -e .[dev]
lli-dev-setup
Scripting or agent experiments? You can skip this interactive workflow entirely — see Automated Capture:
lli runto captureclaude -p "..."-style commands with zero interaction.
If you're only capturing HTTP traffic, you can skip this step. Only install the certificate if you need to capture HTTPS requests.
# Generate certificate
lli watch &
sleep 2
kill %1
Then install the certificate:
macOS:
open ~/.mitmproxy/mitmproxy-ca-cert.pem
# Double-click to add to Keychain
# In Keychain Access, find "mitmproxy" → Double-click → Trust → "Always Trust"
Linux (Ubuntu/Debian):
sudo cp ~/.mitmproxy/mitmproxy-ca-cert.pem /usr/local/share/ca-certificates/mitmproxy.crt
sudo update-ca-certificates
Windows:
Navigate to %USERPROFILE%\.mitmproxy\. Double-click mitmproxy-ca-cert.p12 (or mitmproxy-ca-cert.cer) to open the certificate import wizard → Install Certificate → Local Machine → place in Trusted Root Certification Authorities → Finish.
lli watch
If you need to capture traffic to a custom or self-hosted API , use --include with a glob pattern, for example:
lli watch --include "*api.example.com*"
In watch mode:
export HTTP_PROXY=http://127.0.0.1:9090
export HTTPS_PROXY=http://127.0.0.1:9090
export NODE_EXTRA_CA_CERTS=~/.mitmproxy/mitmproxy-ca-cert.pem
# Optional: bypass proxy for some hosts (e.g. localhost). Configure in lli.toml as no_proxy or run: lli config --proxy-help
# Run Claude and start your conversation
claude
# Now start your dialogue - all prompts and responses will be captured
If you capture traffic behind a corporate proxy or on a network where upstream servers use a company-signed certificate, you may see TLS errors because the proxy only trusts the system CAs, not your company’s CA. Configure the upstream trust CA so LLI (mitmproxy) can verify connections to the corporate proxy or target hosts:
~/.mitmproxy/mitmproxy-ca-cert.pem as in step 1).# Option A: CLI
lli watch --upstream-ca-cert /path/to/corporate-ca.pem
Use lli config --show to confirm the upstream CA path. If the file does not exist at startup, LLI will exit with an error.
The web interface should be launched in http://127.0.0.1:48080 to analyze captured conversations:
In the UI, you can:
Tip: prefer a permanent UI? Run
lli serveonce and keep it running — it shows sessions from bothlli watchandlli runand picks up new ones automatically (see Automated Capture).
lli runFor scripting and agent experiments you can skip watch mode entirely. lli run
wraps a single command with a private ephemeral proxy and captures its LLM
traffic end-to-end — no keypresses, no env setup, no manual merge:
lli run -- claude -p "fix the failing test"
lli run --label codex -- codex exec "review src/"
How it works:
HTTP(S)_PROXY and CA trust (NODE_EXTRA_CA_CERTS,
SSL_CERT_FILE, REQUESTS_CA_BUNDLE) already injected — Claude Code,
Codex, and curl work out of the boxrun_meta.jsonEach run's session lands directly in the traces directory alongside watch-mode sessions (microsecond IDs keep concurrent runs collision-free), so it shows up in the web UI automatically:
traces/
├── all_captured_20260101_120000_123456.jsonl # raw records for this run (replayable)
└── session_20260101_120000_123456/ # this run's session
├── session_meta.json
├── run_meta.json # command, label, exit code, duration, ...
├── 001_request_2026-01-01_12-00-00.json
├── 001_response_2026-01-01_12-00-00.json
└── ...
--label is recorded in run_meta.json (not in directory names), so run
sessions are easy to find programmatically:
ls traces/session_*/run_meta.json
This makes it easy to batch-analyze how different agents (Claude Code, Codex, ...) solve the same tasks — every exchange, tool call, and token count lands in plain JSON files ready for scripting:
for task in "fix the login bug" "add dark mode" "write tests for api.py"; do
lli run --label "claude-$task" -- claude -p "$task"
done
# Then inspect traces/session_*/00N_{request,response}_*.json
# (run sessions are the ones containing run_meta.json)
Note: the mitmproxy CA file (
~/.mitmproxy/mitmproxy-ca-cert.pem) must exist — it is generated the first time any LLI proxy starts. System-wide certificate installation (Quick Start step 1) is not required forlli run, since CA trust is injected into the child process directly.
See lli run CLI reference for all options (--label,
--output-dir, --include, --exclude, --drain-timeout).
Watch mode uses a state machine with three states:
| State | Description |
|---|---|
| IDLE | Monitoring traffic, waiting for you to start a session |
| RECORDING | Capturing traffic with session ID injection |
| PROCESSING | Auto-extracting, merging, and splitting session data |
$ lli watch
╭─────────────────────────╮
│ LLI Watch Mode │
│ Continuous Capture │
╰─────────────────────────╯
Proxy Port: 9090
Output Dir: ./traces (or OS-specific logs directory)
Global Log: traces/all_captured_20251203_220000.jsonl
Configure your application:
export HTTP_PROXY=http://127.0.0.1:9090
export HTTPS_PROXY=http://127.0.0.1:9090
export NODE_EXTRA_CA_CERTS=~/.mitmproxy/mitmproxy-ca-cert.pem
● [IDLE] Monitoring on :9090... Logging to all_captured_20251203_220000.jsonl
Press [Enter] to START Session 1
<Enter>
◉ [REC] Session 01_session_20251203_223010 is recording...
Press [Enter] to STOP & PROCESS, [Esc] to CANCEL
<Enter>
⏳ [BUSY] Processing Session 01_session_20251203_223010...
✔ Saved to traces/01_session_20251203_223010/
● [IDLE] Monitoring on :9090... Logging to all_captured_20251203_220000.jsonl
Press [Enter] to START Session 2
./traces/ # Root output directory
├── all_captured_20251203_220000.jsonl # Global log (all traffic)
│
├── 01_session_20251203_223010/ # Session 01 folder
│ ├── raw.jsonl # Clean session data
│ ├── merged.jsonl # Merged conversations
│ └── split_output/ # Individual files
│ ├── 001_request_2025-12-03_22-30-10.json
│ └── 001_response_2025-12-03_22-30-10.json
│
├── 02_session_20251203_224500/ # Session 02 folder
└── ...
lli watchStart watch mode for continuous session capture (recommended).
lli watch [OPTIONS]
Options:
-p, --port INTEGER Proxy server port (default: 9090)
-o, --output-dir, --log-dir PATH Root output directory (default: ./traces or OS log dir)
-i, --include TEXT Additional URL patterns to include (glob pattern)
--upstream-ca-cert PATH Path to PEM or CA bundle for trusting upstream (e.g. corporate proxy) certificates
--debug Enable debug mode with verbose logging
Examples:
# Basic watch mode
lli watch
# Custom port and output directory
lli watch --port 8888 --output-dir ./my_traces
# Include custom API endpoint (glob pattern)
lli watch --include "*my-custom-api.com*"
# Corporate network: trust company CA so upstream TLS (proxy/target) is verified
lli watch --upstream-ca-cert /path/to/corporate-ca.pem
# Match all subdomains of a domain
lli watch --include "*api.example.com*"
Glob Pattern Syntax:
| Pattern | Description |
|---|---|
* | Matches any characters |
? | Matches a single character |
[seq] | Matches any character in seq |
[!seq] | Matches any character not in seq |
lli configDisplay configuration and setup help.
lli config --cert-help # Certificate installation instructions
lli config --proxy-help # Proxy configuration instructions
lli config --show # Show current configuration
lli runRun a command with automatic, fully non-interactive LLM traffic capture —
ideal for scripted agent experiments (claude -p, codex exec, ...).
lli run starts a private ephemeral proxy, spawns the command with
HTTP(S)_PROXY / CA environment variables injected, and when the command
exits it drains in-flight responses, processes the session (merge + split
into per-exchange request/response JSON files), and writes run metadata.
lli exits with the command's exit code, so it can be composed in scripts
and CI.
lli run -- claude -p "fix the failing test"
lli run --label codex -- codex exec "review src/"
# Custom provider + output location
lli run --include "*my-llm.example.com*" --output-dir ./experiments -- curl -s https://api.example.com/health
Each run's session is created directly in the traces directory alongside
watch-mode sessions (microsecond IDs keep concurrent runs collision-free),
and run_meta.json inside it records the command, label, exit code, and
duration:
traces/session_20260101_120000_123456/
├── session_meta.json
├── run_meta.json # command, label, exit code, duration, ...
├── all_captured_*.jsonl # raw captured records (replayable, at traces root)
├── 001_request_2026-01-01_12-00-00.json
├── 001_response_2026-01-01_12-00-00.json
└── ...
Notes:
~/.mitmproxy/mitmproxy-ca-cert.pem) is
exported to the child via NODE_EXTRA_CA_CERTS / SSL_CERT_FILE /
REQUESTS_CA_BUNDLE, so Node- and OpenSSL-based agents work out of the box.--drain-timeout (default 15s)
for in-flight streaming responses to complete before finalizing.lli serveStart the web UI as a standalone, long-running server. Serves all captured
sessions (from lli watch and lli run) and picks up new ones
automatically — keep it running in one terminal and capture from any other:
lli serve # http://127.0.0.1:48080, default traces dir
lli serve --port 48080 --output-dir ./traces
lli watch still embeds its own UI; when its port is already taken by a
standalone lli serve instance, it reuses that server.
lli statsDisplay statistics for a captured trace file.
lli stats traces/01_session_xxx/raw.jsonl
LLI is pre-configured to capture traffic from:
| Provider | API Domain |
|---|---|
| Anthropic | api.anthropic.com |
| OpenAI (Chat Completions & Responses API) | api.openai.com |
generativelanguage.googleapis.com | |
| Together | api.together.xyz |
| Groq | api.groq.com |
| Mistral | api.mistral.ai |
| Cohere | api.cohere.ai |
| DeepSeek | api.deepseek.com |
Add custom providers with --include (using glob patterns):
lli watch --include "*my-custom-api.com*"
Problem: SSL: CERTIFICATE_VERIFY_FAILED
Solution: Install the mitmproxy CA certificate. Run lli config --cert-help for instructions.
Problem: Requests hang or timeout when using Claude Code, Cursor, etc.
Solution: Set the NODE_EXTRA_CA_CERTS environment variable:
export NODE_EXTRA_CA_CERTS=~/.mitmproxy/mitmproxy-ca-cert.pem
Problem: Upstream TLS handshake failures when capturing company URLs (e.g. traffic goes through a corporate proxy that uses a company CA).
Solution: Configure the upstream trust CA so LLI can verify the corporate proxy or target server certificate. Use --upstream-ca-cert, or set proxy.upstream_ca_cert in lli.toml, or LLI_UPSTREAM_CA_CERT. See the "Corporate network: upstream CA certificate" section above.
Problem: Watch mode is running but no requests are logged
Solution:
--include)lli config --show to see current filter patternsMIT License
Contributions are welcome! Please feel free to submit a Pull Request.
Before committing from a fresh clone, run lli-dev-setup once to install the
repository's pre-commit hook locally.
126 commits
1 commits
Python
66.6%
TypeScript
32.8%
🔍 Proxy-layer microscope for LLM traffic analysis
A cross-platform command-line tool that intercepts, analyzes, and logs communications between AI coding tools/agents (Claude Code, Cursor, Codex, OpenCode, etc.) and their backend LLM APIs.

lli run) - Wrap any command (claude -p, codex exec, ...) with fully automated, non-interactive capture — ideal for scripted agent experimentsuv tool install llm-interceptor
pip install llm-interceptor
git clone https://github.com/chouzz/llm-interceptor.git
cd llm-interceptor
uv sync --dev
uv run lli-dev-setup
Git does not copy hooks from .git/hooks when you clone a repository, so each
new clone must install the project's pre-commit hook once:
uv sync --dev
uv run lli-dev-setup
If you prefer pip:
pip install -e .[dev]
lli-dev-setup
Scripting or agent experiments? You can skip this interactive workflow entirely — see Automated Capture:
lli runto captureclaude -p "..."-style commands with zero interaction.
If you're only capturing HTTP traffic, you can skip this step. Only install the certificate if you need to capture HTTPS requests.
# Generate certificate
lli watch &
sleep 2
kill %1
Then install the certificate:
macOS:
open ~/.mitmproxy/mitmproxy-ca-cert.pem
# Double-click to add to Keychain
# In Keychain Access, find "mitmproxy" → Double-click → Trust → "Always Trust"
Linux (Ubuntu/Debian):
sudo cp ~/.mitmproxy/mitmproxy-ca-cert.pem /usr/local/share/ca-certificates/mitmproxy.crt
sudo update-ca-certificates
Windows:
Navigate to %USERPROFILE%\.mitmproxy\. Double-click mitmproxy-ca-cert.p12 (or mitmproxy-ca-cert.cer) to open the certificate import wizard → Install Certificate → Local Machine → place in Trusted Root Certification Authorities → Finish.
lli watch
If you need to capture traffic to a custom or self-hosted API , use --include with a glob pattern, for example:
lli watch --include "*api.example.com*"
In watch mode:
export HTTP_PROXY=http://127.0.0.1:9090
export HTTPS_PROXY=http://127.0.0.1:9090
export NODE_EXTRA_CA_CERTS=~/.mitmproxy/mitmproxy-ca-cert.pem
# Optional: bypass proxy for some hosts (e.g. localhost). Configure in lli.toml as no_proxy or run: lli config --proxy-help
# Run Claude and start your conversation
claude
# Now start your dialogue - all prompts and responses will be captured
If you capture traffic behind a corporate proxy or on a network where upstream servers use a company-signed certificate, you may see TLS errors because the proxy only trusts the system CAs, not your company’s CA. Configure the upstream trust CA so LLI (mitmproxy) can verify connections to the corporate proxy or target hosts:
~/.mitmproxy/mitmproxy-ca-cert.pem as in step 1).# Option A: CLI
lli watch --upstream-ca-cert /path/to/corporate-ca.pem
Use lli config --show to confirm the upstream CA path. If the file does not exist at startup, LLI will exit with an error.
The web interface should be launched in http://127.0.0.1:48080 to analyze captured conversations:
In the UI, you can:
Tip: prefer a permanent UI? Run
lli serveonce and keep it running — it shows sessions from bothlli watchandlli runand picks up new ones automatically (see Automated Capture).
lli runFor scripting and agent experiments you can skip watch mode entirely. lli run
wraps a single command with a private ephemeral proxy and captures its LLM
traffic end-to-end — no keypresses, no env setup, no manual merge:
lli run -- claude -p "fix the failing test"
lli run --label codex -- codex exec "review src/"
How it works:
HTTP(S)_PROXY and CA trust (NODE_EXTRA_CA_CERTS,
SSL_CERT_FILE, REQUESTS_CA_BUNDLE) already injected — Claude Code,
Codex, and curl work out of the boxrun_meta.jsonEach run's session lands directly in the traces directory alongside watch-mode sessions (microsecond IDs keep concurrent runs collision-free), so it shows up in the web UI automatically:
traces/
├── all_captured_20260101_120000_123456.jsonl # raw records for this run (replayable)
└── session_20260101_120000_123456/ # this run's session
├── session_meta.json
├── run_meta.json # command, label, exit code, duration, ...
├── 001_request_2026-01-01_12-00-00.json
├── 001_response_2026-01-01_12-00-00.json
└── ...
--label is recorded in run_meta.json (not in directory names), so run
sessions are easy to find programmatically:
ls traces/session_*/run_meta.json
This makes it easy to batch-analyze how different agents (Claude Code, Codex, ...) solve the same tasks — every exchange, tool call, and token count lands in plain JSON files ready for scripting:
for task in "fix the login bug" "add dark mode" "write tests for api.py"; do
lli run --label "claude-$task" -- claude -p "$task"
done
# Then inspect traces/session_*/00N_{request,response}_*.json
# (run sessions are the ones containing run_meta.json)
Note: the mitmproxy CA file (
~/.mitmproxy/mitmproxy-ca-cert.pem) must exist — it is generated the first time any LLI proxy starts. System-wide certificate installation (Quick Start step 1) is not required forlli run, since CA trust is injected into the child process directly.
See lli run CLI reference for all options (--label,
--output-dir, --include, --exclude, --drain-timeout).
Watch mode uses a state machine with three states:
| State | Description |
|---|---|
| IDLE | Monitoring traffic, waiting for you to start a session |
| RECORDING | Capturing traffic with session ID injection |
| PROCESSING | Auto-extracting, merging, and splitting session data |
$ lli watch
╭─────────────────────────╮
│ LLI Watch Mode │
│ Continuous Capture │
╰─────────────────────────╯
Proxy Port: 9090
Output Dir: ./traces (or OS-specific logs directory)
Global Log: traces/all_captured_20251203_220000.jsonl
Configure your application:
export HTTP_PROXY=http://127.0.0.1:9090
export HTTPS_PROXY=http://127.0.0.1:9090
export NODE_EXTRA_CA_CERTS=~/.mitmproxy/mitmproxy-ca-cert.pem
● [IDLE] Monitoring on :9090... Logging to all_captured_20251203_220000.jsonl
Press [Enter] to START Session 1
<Enter>
◉ [REC] Session 01_session_20251203_223010 is recording...
Press [Enter] to STOP & PROCESS, [Esc] to CANCEL
<Enter>
⏳ [BUSY] Processing Session 01_session_20251203_223010...
✔ Saved to traces/01_session_20251203_223010/
● [IDLE] Monitoring on :9090... Logging to all_captured_20251203_220000.jsonl
Press [Enter] to START Session 2
./traces/ # Root output directory
├── all_captured_20251203_220000.jsonl # Global log (all traffic)
│
├── 01_session_20251203_223010/ # Session 01 folder
│ ├── raw.jsonl # Clean session data
│ ├── merged.jsonl # Merged conversations
│ └── split_output/ # Individual files
│ ├── 001_request_2025-12-03_22-30-10.json
│ └── 001_response_2025-12-03_22-30-10.json
│
├── 02_session_20251203_224500/ # Session 02 folder
└── ...
lli watchStart watch mode for continuous session capture (recommended).
lli watch [OPTIONS]
Options:
-p, --port INTEGER Proxy server port (default: 9090)
-o, --output-dir, --log-dir PATH Root output directory (default: ./traces or OS log dir)
-i, --include TEXT Additional URL patterns to include (glob pattern)
--upstream-ca-cert PATH Path to PEM or CA bundle for trusting upstream (e.g. corporate proxy) certificates
--debug Enable debug mode with verbose logging
Examples:
# Basic watch mode
lli watch
# Custom port and output directory
lli watch --port 8888 --output-dir ./my_traces
# Include custom API endpoint (glob pattern)
lli watch --include "*my-custom-api.com*"
# Corporate network: trust company CA so upstream TLS (proxy/target) is verified
lli watch --upstream-ca-cert /path/to/corporate-ca.pem
# Match all subdomains of a domain
lli watch --include "*api.example.com*"
Glob Pattern Syntax:
| Pattern | Description |
|---|---|
* | Matches any characters |
? | Matches a single character |
[seq] | Matches any character in seq |
[!seq] | Matches any character not in seq |
lli configDisplay configuration and setup help.
lli config --cert-help # Certificate installation instructions
lli config --proxy-help # Proxy configuration instructions
lli config --show # Show current configuration
lli runRun a command with automatic, fully non-interactive LLM traffic capture —
ideal for scripted agent experiments (claude -p, codex exec, ...).
lli run starts a private ephemeral proxy, spawns the command with
HTTP(S)_PROXY / CA environment variables injected, and when the command
exits it drains in-flight responses, processes the session (merge + split
into per-exchange request/response JSON files), and writes run metadata.
lli exits with the command's exit code, so it can be composed in scripts
and CI.
lli run -- claude -p "fix the failing test"
lli run --label codex -- codex exec "review src/"
# Custom provider + output location
lli run --include "*my-llm.example.com*" --output-dir ./experiments -- curl -s https://api.example.com/health
Each run's session is created directly in the traces directory alongside
watch-mode sessions (microsecond IDs keep concurrent runs collision-free),
and run_meta.json inside it records the command, label, exit code, and
duration:
traces/session_20260101_120000_123456/
├── session_meta.json
├── run_meta.json # command, label, exit code, duration, ...
├── all_captured_*.jsonl # raw captured records (replayable, at traces root)
├── 001_request_2026-01-01_12-00-00.json
├── 001_response_2026-01-01_12-00-00.json
└── ...
Notes:
~/.mitmproxy/mitmproxy-ca-cert.pem) is
exported to the child via NODE_EXTRA_CA_CERTS / SSL_CERT_FILE /
REQUESTS_CA_BUNDLE, so Node- and OpenSSL-based agents work out of the box.--drain-timeout (default 15s)
for in-flight streaming responses to complete before finalizing.lli serveStart the web UI as a standalone, long-running server. Serves all captured
sessions (from lli watch and lli run) and picks up new ones
automatically — keep it running in one terminal and capture from any other:
lli serve # http://127.0.0.1:48080, default traces dir
lli serve --port 48080 --output-dir ./traces
lli watch still embeds its own UI; when its port is already taken by a
standalone lli serve instance, it reuses that server.
lli statsDisplay statistics for a captured trace file.
lli stats traces/01_session_xxx/raw.jsonl
LLI is pre-configured to capture traffic from:
| Provider | API Domain |
|---|---|
| Anthropic | api.anthropic.com |
| OpenAI (Chat Completions & Responses API) | api.openai.com |
generativelanguage.googleapis.com | |
| Together | api.together.xyz |
| Groq | api.groq.com |
| Mistral | api.mistral.ai |
| Cohere | api.cohere.ai |
| DeepSeek | api.deepseek.com |
Add custom providers with --include (using glob patterns):
lli watch --include "*my-custom-api.com*"
Problem: SSL: CERTIFICATE_VERIFY_FAILED
Solution: Install the mitmproxy CA certificate. Run lli config --cert-help for instructions.
Problem: Requests hang or timeout when using Claude Code, Cursor, etc.
Solution: Set the NODE_EXTRA_CA_CERTS environment variable:
export NODE_EXTRA_CA_CERTS=~/.mitmproxy/mitmproxy-ca-cert.pem
Problem: Upstream TLS handshake failures when capturing company URLs (e.g. traffic goes through a corporate proxy that uses a company CA).
Solution: Configure the upstream trust CA so LLI can verify the corporate proxy or target server certificate. Use --upstream-ca-cert, or set proxy.upstream_ca_cert in lli.toml, or LLI_UPSTREAM_CA_CERT. See the "Corporate network: upstream CA certificate" section above.
Problem: Watch mode is running but no requests are logged
Solution:
--include)lli config --show to see current filter patternsMIT License
Contributions are welcome! Please feel free to submit a Pull Request.
Before committing from a fresh clone, run lli-dev-setup once to install the
repository's pre-commit hook locally.
126 commits
1 commits
Python
66.6%
TypeScript
32.8%