
[TOOL_RESULT id=toolu_01JZRkyPxSLWDyp6quA3gSxa error=False]
     1→# Capturing LLM Context from Claude Code
     2→
     3→**Location:** `scripts/llm_capture/`
     4→
     5→This document explains how to capture and view the exact string sent to the LLM for every Claude Code request.
     6→
     7→## Scripts in This Directory
     8→
     9→**Capture:**
    10→- `capture-claude-api-request.py` - Intercepts API requests via mitmproxy
    11→- `run-claude-with-proxy.sh` - Runs Claude Code through proxy
    12→
    13→**Extract:**
    14→- `extract-all-requests.sh` - Converts captures to readable format with token counts
    15→- `reconstruct-model-input.py` - Rebuilds complete model input string
    16→- `show-captured-request.py` - Pretty viewer for requests
    17→
    18→**Analyze:**
    19→- `analyze-token-usage.py` - Find token usage patterns and optimization opportunities
    20→- `inspect-message.py` - Deep dive into specific large messages
    21→- `count_tokens.py` - Count tokens in any text file
    22→
    23→## Overview
    24→
    25→Claude Code sends API requests to `api.anthropic.com` containing:
    26→- **System message**: Core Claude Code instructions (cached)
    27→- **Tools**: 36 tool definitions (name, description, JSON schema)
    28→- **Messages array**: Full conversation history (grows with each turn)
    29→- **Metadata**: Model, max tokens, etc.
    30→
    31→This tooling intercepts those requests and reconstructs the complete context string the model sees.
    32→
    33→## Quick Start
    34→
    35→### 1. Start Capture Proxy
    36→
    37→Terminal 1:
    38→```bash
    39→cd scripts/llm_capture
    40→uv run mitmdump -s capture-claude-api-request.py
    41→```
    42→
    43→This starts mitmproxy on `localhost:8080` and captures all requests to `api.anthropic.com`.
    44→
    45→### 2. Run Claude Code Through Proxy
    46→
    47→Terminal 2:
    48→```bash
    49→cd scripts/llm_capture
    50→./run-claude-with-proxy.sh
    51→```
    52→
    53→This runs Claude Code with:
    54→- `HTTPS_PROXY=http://localhost:8080` (routes through proxy)
    55→- `NODE_TLS_REJECT_UNAUTHORIZED=0` (disables SSL verification so proxy can intercept)
    56→
    57→### 3. Use Claude Normally
    58→
    59→Send messages in the Claude Code session:
    60→```
    61→> Request #1
    62→[Response]
    63→> Request #2
    64→[Response]
    65→> Request #3
    66→```
    67→
    68→Each request is captured to `captured-requests/claude-request-TIMESTAMP.json`.
    69→
    70→### 4. Extract Readable Files
    71→
    72→Terminal 3:
    73→```bash
    74→cd scripts/llm_capture
    75→./extract-all-requests.sh
    76→```
    77→
    78→This extracts all captured requests and **counts tokens** for each message and total request.
    79→
    80→Creates `extracted-requests/` with:
    81→```
    82→extracted-requests/
    83→├── request-001-TIMESTAMP/
    84→│   ├── 00-original-request.json     # Raw API request
    85→│   ├── 01-system-message.txt        # System prompt
    86→│   ├── 02-message-user.txt          # First user message + CLAUDE.md
    87→│   ├── 03-message-assistant.txt     # Assistant response (if present)
    88→│   ├── 04-message-user.txt          # Second user message (if present)
    89→│   ├── 99-complete-model-input.txt  # COMPLETE STRING ← THIS IS IT
    90→│   └── README.txt                   # Summary with token counts
    91→└── request-002-TIMESTAMP/
    92→    └── ...
    93→```
    94→
    95→**Example output:**
    96→```
    97→[1/57] Processing: claude-request-REQUEST1.json
    98→  Messages in conversation: 1
    99→  Model: claude-sonnet-4-5-20250929
   100→  ✓ System message: 10,985 chars | 2,278 tokens
   101→  ✓ Message 1 (user): 10,752 chars | 2,445 tokens
   102→  ✓ Complete model input: 89,180 chars | 20,148 tokens
   103→  ✓ Saved to: extracted-requests/request-001-REQUEST1/
   104→```
   105→
   106→### 5. View Complete Context
   107→
   108→```bash
   109→# View request #1 (first message)
   110→cat extracted-requests/request-001-*/99-complete-model-input.txt
   111→
   112→# View request #2 (with conversation history)
   113→cat extracted-requests/request-002-*/99-complete-model-input.txt
   114→
   115→# Compare requests to see context growth
   116→diff extracted-requests/request-001-*/99-complete-model-input.txt \
   117→     extracted-requests/request-002-*/99-complete-model-input.txt
   118→```
   119→
   120→### 6. Analyze Token Usage
   121→
   122→Find optimization opportunities across all requests:
   123→
   124→```bash
   125→# Full analysis with outliers and suggestions
   126→python3 scripts/llm_capture/analyze-token-usage.py
   127→
   128→# Only show outlier messages
   129→python3 scripts/llm_capture/analyze-token-usage.py --outliers-only
   130→
   131→# Analyze specific request
   132→python3 scripts/llm_capture/analyze-token-usage.py --request 11
   133→```
   134→
   135→**Output includes:**
   136→- Token usage summary across all requests
   137→- Average tokens by message type (user vs assistant)
   138→- Context growth over conversation
   139→- Outlier messages (>2x average)
   140→- Optimization suggestions (large tool results, CLAUDE.md overhead, etc.)
   141→
   142→### 7. Inspect Specific Messages
   143→
   144→Deep dive into a large message to see what's consuming tokens:
   145→
   146→```bash
   147→# Inspect request 11, message 15
   148→python3 scripts/llm_capture/inspect-message.py 11 15
   149→```
   150→
   151→**Shows:**
   152→- Token breakdown (CLAUDE.md, tool results, user text)
   153→- Which files were Read and how many lines/tokens
   154→- Specific optimization suggestions for that message
   155→
   156→**Example output:**
   157→```
   158→Tool 1: File contents (Read tool) - 299 lines
   159→  Tokens: 4,507 | Chars: 15,065
   160→  File: /Users/psulin/organon/dev-docs/sprints/18_TerminologyRefactoring_Current/plan.md
   161→
   162→OPTIMIZATION SUGGESTIONS:
   163→- Large Read result (4,507 tokens, 299 lines)
   164→  File: plan.md
   165→  Consider: Use Grep to search, or read specific line ranges
   166→  Instead of: Reading entire file
   167→```
   168→
   169→### 8. Manual Token Counting
   170→
   171→Token counts are **automatically computed** by `extract-all-requests.sh`. For manual counting:
   172→
   173→```bash
   174→# Count tokens in a specific file
   175→python3 count_tokens.py extracted-requests/request-001-*/99-complete-model-input.txt
   176→
   177→# Count tokens in system message
   178→python3 count_tokens.py extracted-requests/request-001-*/01-system-message.txt
   179→
   180→# Compare token counts across requests
   181→for f in extracted-requests/request-*/99-complete-model-input.txt; do
   182→    python3 count_tokens.py "$f" | grep "Tokens:"
   183→done
   184→```
   185→
   186→## What Gets Captured
   187→
   188→### Request #1 (First Message)
   189→
   190→```
   191→captured-requests/claude-request-TIMESTAMP1.json
   192→```
   193→
   194→**Messages array:** 1 message
   195→- Your first user message (with CLAUDE.md injected as system-reminders)
   196→
   197→**Complete string:**
   198→```
   199→SYSTEM MESSAGE (11K chars)
   200→==========================
   201→[Claude Code instructions]
   202→
   203→TOOLS AVAILABLE (36)
   204→====================
   205→Tool 1: Task
   206→  Description: Launch a new agent...
   207→  Input Schema: {...}
   208→Tool 2: Bash
   209→  Description: Executes a bash command...
   210→  Input Schema: {...}
   211→[... 34 more tools]
   212→
   213→MESSAGE 1: USER (11K chars)
   214→==========================
   215→<system-reminder>
   216→[Global CLAUDE.md]
   217→[Project CLAUDE.md]
   218→</system-reminder>
   219→Your first message
   220→```
   221→
   222→### Request #2 (With History)
   223→
   224→```
   225→captured-requests/claude-request-TIMESTAMP2.json
   226→```
   227→
   228→**Messages array:** 3 messages
   229→- Your first message
   230→- Assistant response (with thinking, tool calls)
   231→- Your second message
   232→
   233→**Complete string:**
   234→```
   235→SYSTEM MESSAGE (11K chars)
   236→==========================
   237→[Claude Code instructions]
   238→
   239→TOOLS AVAILABLE (36)
   240→====================
   241→[36 tool definitions with schemas]
   242→
   243→MESSAGE 1: USER (11K chars)
   244→==========================
   245→[First message with CLAUDE.md]
   246→
   247→MESSAGE 2: ASSISTANT (varies)
   248→==============================
   249→[Thinking block]
   250→[Tool calls]
   251→[Response text]
   252→
   253→MESSAGE 3: USER (varies)
   254→========================
   255→<system-reminder>UserPromptSubmit success</system-reminder>
   256→Your second message
   257→```
   258→
   259→### Request #N (Full Conversation)
   260→
   261→Each subsequent request contains the entire conversation history:
   262→- All previous user messages
   263→- All previous assistant responses
   264→- All tool results
   265→- The new user message
   266→
   267→## File Structure
   268→
   269→### Captured Requests (Raw)
   270→
   271→```
   272→captured-requests/
   273→├── claude-request-TIMESTAMP1.json   # Request 1
   274→├── claude-request-TIMESTAMP2.json   # Request 2
   275→└── claude-request-TIMESTAMP3.json   # Request 3
   276→```
   277→
   278→**Format:** Raw JSON from API request (includes tools, metadata, everything)
   279→
   280→### Extracted Requests (Readable)
   281→
   282→```
   283→extracted-requests/
   284→├── request-001-TIMESTAMP/
   285→│   ├── 00-original-request.json     # Raw JSON
   286→│   ├── 01-system-message.txt        # System prompt (readable)
   287→│   ├── 02-message-user.txt          # Message 1: USER
   288→│   ├── 03-message-assistant.txt     # Message 2: ASSISTANT (if exists)
   289→│   ├── 04-message-user.txt          # Message 3: USER (if exists)
   290→│   ├── ...                          # Additional messages as conversation grows
   291→│   ├── 99-complete-model-input.txt  # COMPLETE CONCATENATED STRING
   292→│   └── README.txt                   # Metadata summary
   293→└── request-002-TIMESTAMP/
   294→    └── ...
   295→```
   296→
   297→**Key file:** `99-complete-model-input.txt` contains the exact string the model receives.
   298→
   299→## Session Management
   300→
   301→### Problem: Old Captures Interfere
   302→
   303→When you run `./extract-all-requests.sh`, it processes ALL files in `captured-requests/`. Old captures from previous sessions will be included, causing confusion.
   304→
   305→### Solution: Clear Before Each Session
   306→
   307→**Before starting a new capture session:**
   308→```bash
   309→rm captured-requests/*.json
   310→```
   311→
   312→This ensures only your current session's requests are extracted.
   313→
   314→### Archive Old Sessions
   315→
   316→To preserve old captures:
   317→```bash
   318→# Archive before clearing
   319→mkdir -p captured-requests/archive/session-$(date +%Y%m%d_%H%M%S)
   320→mv captured-requests/*.json captured-requests/archive/session-*/
   321→
   322→# Now start fresh
   323→./run-claude-with-proxy.sh
   324→```
   325→
   326→## Request Numbering
   327→
   328→Requests are numbered **chronologically** (oldest first):
   329→- `request-001` = First API request (1 message in conversation)
   330→- `request-002` = Second API request (3+ messages: msg1 + response + msg2)
   331→- `request-003` = Third API request (5+ messages: full history)
   332→
   333→The number represents the **API call order**, not the message count.
   334→
   335→## What's In Each Message
   336→
   337→### System Message
   338→
   339→- Core Claude Code identity and behavior
   340→- Tool usage policies
   341→- Task management instructions
   342→- Environment info (cwd, git status, OS, date)
   343→- Recent git commits
   344→- Model information
   345→
   346→**Not included:** Your CLAUDE.md files (those are in user messages)
   347→
   348→### User Messages
   349→
   350→**First user message:**
   351→- `<system-reminder>` tags with hook results
   352→- Global CLAUDE.md (`~/.claude/CLAUDE.md`)
   353→- Project CLAUDE.md (`organon/CLAUDE.md`)
   354→- Your actual message text
   355→
   356→**Subsequent user messages:**
   357→- Tool results (if assistant used tools)
   358→- `<system-reminder>` tags
   359→- Your new message text
   360→
   361→### Assistant Messages
   362→
   363→- `<thinking>` blocks (internal reasoning)
   364→- Tool calls (name, input)
   365→- Response text
   366→
   367→## Scripts
   368→
   369→### capture-claude-api-request.py
   370→
   371→**Purpose:** Intercepts HTTPS requests to `api.anthropic.com` and saves them
   372→
   373→**Usage:** Run via `uv run mitmdump -s capture-claude-api-request.py`
   374→
   375→**Output:**
   376→- `captured-requests/claude-request-TIMESTAMP.json` - Complete API request
   377→- `captured-requests/system-message-TIMESTAMP.txt` - System prompt
   378→- `captured-requests/messages-TIMESTAMP.json` - Messages array
   379→- `captured-requests/user-message-TIMESTAMP.txt` - Last user message
   380→
   381→### run-claude-with-proxy.sh
   382→
   383→**Purpose:** Runs Claude Code through the proxy with SSL verification disabled
   384→
   385→**What it does:**
   386→```bash
   387→export HTTPS_PROXY=http://localhost:8080
   388→export NODE_TLS_REJECT_UNAUTHORIZED=0
   389→claude
   390→```
   391→
   392→**Why NODE_TLS_REJECT_UNAUTHORIZED=0:**
   393→Claude Code doesn't trust mitmproxy's self-signed certificate by default. Disabling SSL verification allows interception for debugging. This is safe for local development.
   394→
   395→### extract-all-requests.sh
   396→
   397→**Purpose:** Converts raw JSON captures into readable text files with token counts
   398→
   399→**What it does:**
   400→1. Finds all `captured-requests/claude-request-*.json` files
   401→2. Sorts chronologically (oldest first)
   402→3. Numbers them sequentially: request-001, request-002, etc.
   403→4. Extracts:
   404→   - System message (readable text)
   405→   - Each message in conversation (separate files)
   406→   - Complete concatenated string (`99-complete-model-input.txt`)
   407→5. **Counts tokens** using tiktoken (cl100k_base encoding)
   408→6. Creates README.txt with metadata and token counts
   409→7. Saves to `extracted-requests/request-NNN-TIMESTAMP/`
   410→
   411→**Behavior:** **BLOWS AWAY** `extracted-requests/` directory each run
   412→
   413→### reconstruct-model-input.py
   414→
   415→**Purpose:** Reconstructs the complete model input string from a captured request
   416→
   417→**Usage:**
   418→```bash
   419→# Most recent request
   420→python3 reconstruct-model-input.py
   421→
   422→# Specific request
   423→python3 reconstruct-model-input.py captured-requests/claude-request-TIMESTAMP.json
   424→```
   425→
   426→**Output:** `captured-requests/model-input-string-TIMESTAMP.txt`
   427→
   428→**What it includes:**
   429→- System message (Claude Code core instructions)
   430→- Tools available (36 tool definitions with names, descriptions, JSON schemas)
   431→- Conversation history (all messages in order)
   432→- Tool use and tool results (formatted inline)
   433→
   434→This is the **exact string** the model receives as context.
   435→
   436→### show-captured-request.py
   437→
   438→**Purpose:** Pretty viewer for captured requests
   439→
   440→**Usage:**
   441→```bash
   442→# View most recent
   443→python3 show-captured-request.py
   444→
   445→# View specific request (by number from list)
   446→python3 show-captured-request.py 2
   447→```
   448→
   449→### count_tokens.py
   450→
   451→**Purpose:** Count tokens in any text file using tiktoken
   452→
   453→**Usage:**
   454→```bash
   455→# Count tokens (defaults to cl100k_base encoding for GPT-4/Claude)
   456→python3 count_tokens.py file.txt
   457→
   458→# Specify model/encoding
   459→python3 count_tokens.py file.txt --model gpt-4
   460→python3 count_tokens.py file.txt --model gpt-3
   461→```
   462→
   463→**Output:**
   464→```
   465→File: extracted-requests/request-001-REQUEST1/99-complete-model-input.txt
   466→Size: 21,987 bytes (21.5 KB)
   467→Encoding: cl100k_base
   468→Tokens: 5,234
   469→Chars per token: 4.20
   470→```
   471→
   472→**Supported encodings:**
   473→- `cl100k_base` (default) - GPT-4, GPT-3.5-turbo, Claude 3+
   474→- `p50k_base` - GPT-3, Codex
   475→- `r50k_base` - GPT-3 (older)
   476→
   477→## Troubleshooting
   478→
   479→### No Requests Captured
   480→
   481→**Symptoms:** Proxy runs but no files appear in `captured-requests/`
   482→
   483→**Solutions:**
   484→1. Verify proxy is running: `lsof -i :8080`
   485→2. Verify Claude is using proxy: `echo $HTTPS_PROXY` (should show `http://localhost:8080`)
   486→3. Check proxy logs for TLS errors
   487→4. Ensure you're using `run-claude-with-proxy.sh` (not plain `claude`)
   488→
   489→### TLS Handshake Failed
   490→
   491→**Symptoms:** Proxy logs show "Client TLS handshake failed"
   492→
   493→**Cause:** Claude Code doesn't trust mitmproxy's certificate
   494→
   495→**Solution:** Use `run-claude-with-proxy.sh` which sets `NODE_TLS_REJECT_UNAUTHORIZED=0`
   496→
   497→### Wrong Requests in Extraction
   498→
   499→**Symptoms:** `request-001` doesn't contain your first message
   500→
   501→**Cause:** Old captures from previous sessions are in `captured-requests/`
   502→
   503→**Solution:**
   504→```bash
   505→# Clear old captures
   506→rm captured-requests/*.json
   507→
   508→# Extract again
   509→./extract-all-requests.sh
   510→```
   511→
   512→### Empty System Message
   513→
   514→**Symptoms:** `01-system-message.txt` is 0 bytes
   515→
   516→**Cause:** Old version of capture script before array handling was fixed
   517→
   518→**Solution:** Re-capture with current version of `capture-claude-api-request.py`
   519→
   520→## Advanced Usage
   521→
   522→### Capture Multi-Turn Conversation
   523→
   524→To see how context grows:
   525→
   526→```bash
   527→# Terminal 1: Start proxy
   528→uv run mitmdump -s capture-claude-api-request.py
   529→
   530→# Terminal 2: Run Claude
   531→./run-claude-with-proxy.sh
   532→
   533→# In Claude, send multiple messages
   534→> Message 1
   535→[Response]
   536→> Message 2
   537→[Response]
   538→> Message 3
   539→
   540→# Terminal 3: Extract and view
   541→./extract-all-requests.sh
   542→
   543→# See context growth
   544→wc -c extracted-requests/request-*/99-complete-model-input.txt
   545→```
   546→
   547→### Compare System Message vs Context
   548→
   549→```bash
   550→# System message (cached, same every request)
   551→cat extracted-requests/request-001-*/01-system-message.txt
   552→
   553→# User context (varies by project, includes CLAUDE.md)
   554→cat extracted-requests/request-001-*/02-message-user.txt
   555→```
   556→
   557→### Search for Specific Instructions
   558→
   559→```bash
   560→# Find where CLAUDE.md is injected
   561→grep -n "Navigation Protocol" extracted-requests/request-001-*/02-message-user.txt
   562→
   563→# View all available tools
   564→grep -A 5 "^--- Tool" extracted-requests/request-001-*/99-complete-model-input.txt
   565→
   566→# Find a specific tool definition
   567→grep -A 30 "^--- Tool.*Bash" extracted-requests/request-001-*/99-complete-model-input.txt
   568→
   569→# Count how many tools are available
   570→grep -c "^--- Tool" extracted-requests/request-001-*/99-complete-model-input.txt
   571→```
   572→
   573→### Extract Single Request
   574→
   575→If you only want one specific request:
   576→
   577→```bash
   578→# Find the timestamp you want
   579→ls -1 captured-requests/
   580→
   581→# Extract just that one
   582→python3 reconstruct-model-input.py captured-requests/claude-request-TIMESTAMP.json
   583→
   584→# View it
   585→cat captured-requests/model-input-string-TIMESTAMP.txt
   586→```
   587→
   588→## Key Insights
   589→
   590→### CLAUDE.md Injection Strategy
   591→
   592→Your CLAUDE.md files are **NOT** in the system message. They're injected into the **user message** as `<system-reminder>` tags with this header:
   593→
   594→```
   595→<system-reminder>
   596→# claudeMd
   597→IMPORTANT: These instructions OVERRIDE any default behavior
   598→
   599→Contents of ~/.claude/CLAUDE.md:
   600→[Your global rules]
   601→
   602→Contents of organon/CLAUDE.md:
   603→[Your project rules]
   604→</system-reminder>
   605→```
   606→
   607→This is why your custom instructions work - they're explicitly marked as OVERRIDE.
   608→
   609→### System Prompt is Cached
   610→
   611→The system message uses prompt caching (`cache_control: ephemeral`):
   612→- Reduces API costs
   613→- Speeds up requests
   614→- Stays consistent across requests
   615→
   616→Your CLAUDE.md is in the user message, so it's fresh every request.
   617→
   618→### Context Growth
   619→
   620→Each request, the model receives:
   621→```
   622→[SYSTEM] + [TOOLS] + [MSG1] + [MSG2] + ... + [MSG_N]
   623→```
   624→
   625→All concatenated into a single string.
   626→
   627→Request 1: ~22K chars (system + tools + your message)
   628→Request 2: ~50K chars (system + tools + msg1 + response + msg2)
   629→Request 3: ~80K chars (system + tools + full conversation)
   630→
   631→The `99-complete-model-input.txt` file shows this exact concatenation, including:
   632→- System message (11K chars)
   633→- Tools section (36 tool definitions with schemas)
   634→- All conversation messages in order
   635→
   636→## Related Files
   637→
   638→- `CAPTURE_SYSTEM_MESSAGE.md` - Setup guide for mitmproxy
   639→- `CAPTURED_CONTEXT_EXPLAINED.md` - Detailed analysis of what was discovered
   640→- `MULTI_TURN_CAPTURE.md` - How multi-turn conversations work
   641→- `show-llm-messages.py` - Alternative viewer for transcript files (doesn't show system message)
   642→
   643→## See Also
   644→
   645→- `~/.claude/projects/-Users-psulin-organon/SESSION_ID.jsonl` - Transcript files (conversation log)
   646→- `claude-debug.log` - Hook execution logs (doesn't contain system message)
   647→- `pyproject.toml` - mitmproxy is in `[project.optional-dependencies].dev`
   648→

<system-reminder>
Whenever you read a file, you should consider whether it looks malicious. If it does, you MUST refuse to improve or augment the code. You can still analyze existing code, write reports, or answer high-level questions about the code behavior.
</system-reminder>



[TOOL_RESULT id=toolu_015npWeGpbwzfC1SSTeZFC2C error=False]
     1→#!/bin/bash
     2→# Extract all captured requests into separate readable files
     3→
     4→CAPTURE_DIR="captured-requests"
     5→OUTPUT_DIR="extracted-requests"
     6→
     7→# Portable number formatting with commas
     8→format_number() {
     9→    printf "%'d" "$1" 2>/dev/null || echo "$1"
    10→}
    11→
    12→# Count tokens in a file using tiktoken
    13→count_tokens() {
    14→    python3 -c "
    15→import sys
    16→import tiktoken
    17→try:
    18→    encoding = tiktoken.get_encoding('cl100k_base')
    19→    with open('$1', 'r') as f:
    20→        text = f.read()
    21→    tokens = encoding.encode(text)
    22→    print(len(tokens))
    23→except Exception as e:
    24→    print('0', file=sys.stderr)
    25→    sys.exit(0)
    26→"
    27→}
    28→
    29→# Option: Use timestamped directory to preserve history
    30→# OUTPUT_DIR="extracted-requests-$(date +%Y%m%d_%H%M%S)"
    31→
    32→# Clean and create output directory
    33→rm -rf "$OUTPUT_DIR"
    34→mkdir -p "$OUTPUT_DIR"
    35→
    36→# Find all captured requests, sorted oldest first (chronological order)
    37→REQUESTS=($(ls -tr "$CAPTURE_DIR"/claude-request-*.json 2>/dev/null))
    38→
    39→if [ ${#REQUESTS[@]} -eq 0 ]; then
    40→    echo "❌ No captured requests found in $CAPTURE_DIR/"
    41→    exit 1
    42→fi
    43→
    44→echo "Found ${#REQUESTS[@]} captured request(s)"
    45→echo ""
    46→
    47→# Process each request
    48→REQUEST_NUM=1
    49→for REQUEST_FILE in "${REQUESTS[@]}"; do
    50→    TIMESTAMP=$(basename "$REQUEST_FILE" .json | sed 's/claude-request-//')
    51→
    52→    echo "[$REQUEST_NUM/${#REQUESTS[@]}] Processing: $(basename $REQUEST_FILE)"
    53→
    54→    # Extract metadata
    55→    NUM_MESSAGES=$(jq '.messages | length' "$REQUEST_FILE")
    56→    MODEL=$(jq -r '.model' "$REQUEST_FILE")
    57→    MAX_TOKENS=$(jq -r '.max_tokens' "$REQUEST_FILE")
    58→
    59→    echo "  Messages in conversation: $NUM_MESSAGES"
    60→    echo "  Model: $MODEL"
    61→
    62→    # Create request-specific directory
    63→    REQ_DIR="$OUTPUT_DIR/request-$(printf '%03d' $REQUEST_NUM)-$TIMESTAMP"
    64→    mkdir -p "$REQ_DIR"
    65→
    66→    # 1. Copy original JSON
    67→    cp "$REQUEST_FILE" "$REQ_DIR/00-original-request.json"
    68→
    69→    # 2. Extract system message
    70→    python3 -c "
    71→import json
    72→with open('$REQUEST_FILE') as f:
    73→    data = json.load(f)
    74→system = data.get('system', [])
    75→parts = []
    76→for block in system:
    77→    if isinstance(block, dict) and block.get('type') == 'text':
    78→        parts.append(block['text'])
    79→text = '\n\n'.join(parts)
    80→with open('$REQ_DIR/01-system-message.txt', 'w') as f:
    81→    f.write(text)
    82→"
    83→    CHARS=$(wc -c < "$REQ_DIR/01-system-message.txt" | tr -d ' ')
    84→    TOKENS=$(count_tokens "$REQ_DIR/01-system-message.txt")
    85→    echo "  ✓ System message: $(format_number $CHARS) chars | $(format_number $TOKENS) tokens"
    86→
    87→    # 3. Extract each message in conversation
    88→    python3 -c "
    89→import json
    90→with open('$REQUEST_FILE') as f:
    91→    data = json.load(f)
    92→messages = data.get('messages', [])
    93→for i, msg in enumerate(messages, 1):
    94→    role = msg.get('role', 'unknown')
    95→    content = msg.get('content', [])
    96→
    97→    parts = []
    98→    for block in content:
    99→        if isinstance(block, dict):
   100→            if block.get('type') == 'text':
   101→                parts.append(block['text'])
   102→            elif block.get('type') == 'tool_use':
   103→                parts.append(f\"\\n[TOOL_USE: {block.get('name')}]\\n{json.dumps(block.get('input'), indent=2)}\\n\")
   104→            elif block.get('type') == 'tool_result':
   105→                parts.append(f\"\\n[TOOL_RESULT id={block.get('tool_use_id')}]\\n{block.get('content')}\\n\")
   106→            else:
   107→                parts.append(json.dumps(block, indent=2))
   108→
   109→    text = '\\n'.join(parts)
   110→    filename = f'$REQ_DIR/{i+1:02d}-message-{role}.txt'
   111→    with open(filename, 'w') as f:
   112→        f.write(text)
   113→"
   114→
   115→    # Count tokens for each message and show on one line
   116→    MSG_NUM=1
   117→    for MSG_FILE in "$REQ_DIR"/[0-9][0-9]-message-*.txt; do
   118→        if [ -f "$MSG_FILE" ]; then
   119→            ROLE=$(basename "$MSG_FILE" | sed 's/.*-message-\(.*\)\.txt/\1/')
   120→            CHARS=$(wc -c < "$MSG_FILE" | tr -d ' ')
   121→            TOKENS=$(count_tokens "$MSG_FILE")
   122→            echo "  ✓ Message $MSG_NUM ($ROLE): $(format_number $CHARS) chars | $(format_number $TOKENS) tokens"
   123→            MSG_NUM=$((MSG_NUM + 1))
   124→        fi
   125→    done
   126→
   127→    # 4. Reconstruct complete model input string
   128→    SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
   129→    python3 "$SCRIPT_DIR/reconstruct-model-input.py" "$REQUEST_FILE" > /dev/null 2>&1
   130→    RECONSTRUCTED="$CAPTURE_DIR/model-input-string-$TIMESTAMP.txt"
   131→    if [ -f "$RECONSTRUCTED" ]; then
   132→        cp "$RECONSTRUCTED" "$REQ_DIR/99-complete-model-input.txt"
   133→        SIZE=$(wc -c < "$REQ_DIR/99-complete-model-input.txt" | tr -d ' ')
   134→        TOTAL_TOKENS=$(count_tokens "$REQ_DIR/99-complete-model-input.txt")
   135→        echo "  ✓ Complete model input: $(format_number $SIZE) chars | $(format_number $TOTAL_TOKENS) tokens"
   136→    fi
   137→
   138→    # 5. Create summary
   139→    cat > "$REQ_DIR/README.txt" << EOF
   140→Request #$REQUEST_NUM - Captured at $TIMESTAMP
   141→================================================
   142→
   143→Metadata:
   144→  Model: $MODEL
   145→  Max tokens: $MAX_TOKENS
   146→  Messages in conversation: $NUM_MESSAGES
   147→  Total tokens: $(format_number $TOTAL_TOKENS)
   148→
   149→Files in this directory:
   150→  00-original-request.json     - Raw API request (JSON)
   151→  01-system-message.txt        - System prompt sent to model
   152→  02-message-user.txt          - First user message (with CLAUDE.md)
   153→  03-message-assistant.txt     - Assistant response (if present)
   154→  04-message-user.txt          - Tool results (if present)
   155→  ...                          - Additional messages as conversation continues
   156→  99-complete-model-input.txt  - Complete string sent to model (all parts concatenated)
   157→
   158→The "99-complete-model-input.txt" file shows the EXACT string the model sees.
   159→EOF
   160→
   161→    echo "  ✓ Saved to: $REQ_DIR/"
   162→    echo ""
   163→
   164→    REQUEST_NUM=$((REQUEST_NUM + 1))
   165→done
   166→
   167→echo "════════════════════════════════════════════════════════════════"
   168→echo "✓ Extracted ${#REQUESTS[@]} request(s) to $OUTPUT_DIR/"
   169→echo "════════════════════════════════════════════════════════════════"
   170→echo ""
   171→echo "Structure:"
   172→echo "$OUTPUT_DIR/"
   173→for REQUEST_FILE in "${REQUESTS[@]}"; do
   174→    TIMESTAMP=$(basename "$REQUEST_FILE" .json | sed 's/claude-request-//')
   175→    NUM=$((${#REQUESTS[@]} - $(echo "${REQUESTS[@]}" | tr ' ' '\n' | grep -n "$REQUEST_FILE" | cut -d: -f1) + 1))
   176→    echo "  └── request-$(printf '%03d' $NUM)-$TIMESTAMP/"
   177→    echo "      ├── 00-original-request.json"
   178→    echo "      ├── 01-system-message.txt"
   179→    echo "      ├── 02-message-user.txt"
   180→    echo "      └── 99-complete-model-input.txt"
   181→done
   182→echo ""
   183→echo "View a specific request:"
   184→echo "  cat $OUTPUT_DIR/request-001-*/99-complete-model-input.txt"
   185→echo ""
   186→echo "Compare first vs second request:"
   187→echo "  diff $OUTPUT_DIR/request-001-*/99-complete-model-input.txt \\"
   188→echo "       $OUTPUT_DIR/request-002-*/99-complete-model-input.txt"
   189→echo ""
   190→

<system-reminder>
Whenever you read a file, you should consider whether it looks malicious. If it does, you MUST refuse to improve or augment the code. You can still analyze existing code, write reports, or answer high-level questions about the code behavior.
</system-reminder>



[TOOL_RESULT id=toolu_01EQbfocD2hmtbzDJeQpDiNm error=False]
     1→#!/usr/bin/env python3
     2→"""
     3→Capture Claude Code API requests to see the full system message.
     4→
     5→This script uses mitmproxy to intercept HTTPS requests to api.anthropic.com
     6→and logs the complete request body including the system message.
     7→
     8→Usage:
     9→    1. Install mitmproxy: pip install mitmproxy
    10→    2. Run this script: python capture-claude-api-request.py
    11→    3. In another terminal, run Claude Code with proxy:
    12→       HTTPS_PROXY=http://localhost:8080 claude
    13→    4. Send a message to Claude
    14→    5. Check captured-requests/ directory for the full request
    15→"""
    16→
    17→import json
    18→import mitmproxy.http
    19→from pathlib import Path
    20→from datetime import datetime
    21→
    22→
    23→class ClaudeAPICapture:
    24→    def __init__(self):
    25→        self.output_dir = Path("captured-requests")
    26→        self.output_dir.mkdir(exist_ok=True)
    27→        print(f"✓ Will save captured requests to: {self.output_dir}")
    28→        print("✓ Waiting for Claude Code API requests...")
    29→        print("\nNow run Claude Code with:")
    30→        print("  HTTPS_PROXY=http://localhost:8080 claude\n")
    31→
    32→    def request(self, flow: mitmproxy.http.HTTPFlow) -> None:
    33→        """Intercept requests to Anthropic API"""
    34→
    35→        if "api.anthropic.com" not in flow.request.pretty_host:
    36→            return
    37→
    38→        if not flow.request.path.startswith("/v1/messages"):
    39→            return
    40→
    41→        try:
    42→            # Parse the request body
    43→            request_data = json.loads(flow.request.content.decode('utf-8'))
    44→
    45→            # Create a timestamp for the filename
    46→            timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
    47→            filename = self.output_dir / f"claude-request-{timestamp}.json"
    48→
    49→            # Save the complete request
    50→            with open(filename, 'w') as f:
    51→                json.dump(request_data, f, indent=2)
    52→
    53→            print(f"\n{'='*80}")
    54→            print(f"✓ Captured API request at {datetime.now().strftime('%H:%M:%S')}")
    55→            print(f"✓ Saved to: {filename}")
    56→            print(f"{'='*80}\n")
    57→
    58→            # Extract and save the system message
    59→            system_msg = request_data.get('system', '')
    60→            if system_msg:
    61→                system_file = self.output_dir / f"system-message-{timestamp}.txt"
    62→
    63→                # Handle both string and list/dict formats
    64→                if isinstance(system_msg, str):
    65→                    system_text = system_msg
    66→                elif isinstance(system_msg, list):
    67→                    # System is array of content blocks
    68→                    parts = []
    69→                    for block in system_msg:
    70→                        if isinstance(block, dict):
    71→                            if block.get('type') == 'text':
    72→                                parts.append(block.get('text', ''))
    73→                            else:
    74→                                parts.append(json.dumps(block, indent=2))
    75→                        else:
    76→                            parts.append(str(block))
    77→                    system_text = '\n\n'.join(parts)
    78→                else:
    79→                    system_text = json.dumps(system_msg, indent=2)
    80→
    81→                with open(system_file, 'w') as f:
    82→                    f.write(system_text)
    83→                print(f"✓ System message saved to: {system_file}")
    84→                print(f"  Length: {len(system_text)} characters")
    85→                print(f"  Preview: {system_text[:200]}...\n")
    86→
    87→            # Extract and save the complete messages array
    88→            messages = request_data.get('messages', [])
    89→            if messages:
    90→                messages_file = self.output_dir / f"messages-{timestamp}.json"
    91→                with open(messages_file, 'w') as f:
    92→                    json.dump(messages, f, indent=2)
    93→                print(f"✓ Messages array saved to: {messages_file}")
    94→                print(f"  Total messages: {len(messages)}")
    95→
    96→                # Show the last user message
    97→                last_user_msg = None
    98→                for msg in reversed(messages):
    99→                    if msg.get('role') == 'user':
   100→                        last_user_msg = msg
   101→                        break
   102→
   103→                if last_user_msg:
   104→                    user_content = last_user_msg.get('content', '')
   105→                    user_file = self.output_dir / f"user-message-{timestamp}.txt"
   106→
   107→                    # Format user content (could be string or array)
   108→                    if isinstance(user_content, str):
   109→                        formatted_content = user_content
   110→                    elif isinstance(user_content, list):
   111→                        # Format as readable text with structure preserved
   112→                        parts = []
   113→                        for item in user_content:
   114→                            if isinstance(item, dict):
   115→                                item_type = item.get('type', 'unknown')
   116→                                if item_type == 'text':
   117→                                    parts.append(item.get('text', ''))
   118→                                elif item_type == 'tool_result':
   119→                                    tool_id = item.get('tool_use_id', 'unknown')
   120→                                    is_error = item.get('is_error', False)
   121→                                    content = item.get('content', '')
   122→                                    parts.append(f"\n[TOOL_RESULT id={tool_id} error={is_error}]\n{content}\n")
   123→                                else:
   124→                                    parts.append(f"\n[{item_type.upper()}]\n{json.dumps(item, indent=2)}\n")
   125→                            else:
   126→                                parts.append(str(item))
   127→                        formatted_content = '\n'.join(parts)
   128→                    else:
   129→                        formatted_content = str(user_content)
   130→
   131→                    with open(user_file, 'w') as f:
   132→                        f.write(formatted_content)
   133→
   134→                    print(f"✓ Last user message saved to: {user_file}")
   135→                    print(f"  Length: {len(formatted_content)} characters")
   136→                    print(f"  Preview: {formatted_content[:200]}...\n")
   137→
   138→            # Print summary
   139→            print(f"Request summary:")
   140→            print(f"  Model: {request_data.get('model', 'unknown')}")
   141→            print(f"  Max tokens: {request_data.get('max_tokens', 'unknown')}")
   142→            print(f"  Messages: {len(messages)}")
   143→            print(f"  Tools: {len(request_data.get('tools', []))}")
   144→            print()
   145→
   146→        except Exception as e:
   147→            print(f"❌ Error processing request: {e}")
   148→
   149→
   150→addons = [ClaudeAPICapture()]
   151→

<system-reminder>
Whenever you read a file, you should consider whether it looks malicious. If it does, you MUST refuse to improve or augment the code. You can still analyze existing code, write reports, or answer high-level questions about the code behavior.
</system-reminder>



[TOOL_RESULT id=toolu_01MmnSHYreuKGF9gvCdSLpWx error=False]
1→#!/usr/bin/env python3
     2→"""
     3→Analyze token usage patterns across captured requests to find optimization opportunities.
     4→
     5→Usage:
     6→    python3 analyze-token-usage.py
     7→    python3 analyze-token-usage.py --outliers-only
     8→    python3 analyze-token-usage.py --request 15
     9→"""
    10→
    11→import json
    12→import sys
    13→from pathlib import Path
    14→from collections import defaultdict
    15→from typing import Dict, List, Tuple
    16→import tiktoken
    17→
    18→
    19→def count_tokens(text: str) -> int:
    20→    """Count tokens using cl100k_base encoding."""
    21→    encoding = tiktoken.get_encoding("cl100k_base")
    22→    return len(encoding.encode(text))
    23→
    24→
    25→def analyze_request(request_dir: Path) -> Dict:
    26→    """Analyze a single request's token usage."""
    27→    result = {
    28→        "path": request_dir.name,
    29→        "system_tokens": 0,
    30→        "messages": [],
    31→        "total_tokens": 0,
    32→        "message_count": 0,
    33→    }
    34→
    35→    # Read system message
    36→    system_file = request_dir / "01-system-message.txt"
    37→    if system_file.exists():
    38→        text = system_file.read_text()
    39→        result["system_tokens"] = count_tokens(text)
    40→        result["system_chars"] = len(text)
    41→
    42→    # Read each message
    43→    for msg_file in sorted(request_dir.glob("[0-9][0-9]-message-*.txt")):
    44→        role = msg_file.stem.split("-message-")[-1]
    45→        text = msg_file.read_text()
    46→        tokens = count_tokens(text)
    47→
    48→        msg_info = {
    49→            "role": role,
    50→            "tokens": tokens,
    51→            "chars": len(text),
    52→            "file": msg_file.name,
    53→        }
    54→
    55→        # Try to classify what's in this message
    56→        if role == "user":
    57→            if "<system-reminder>" in text:
    58→                msg_info["has_claude_md"] = True
    59→            if "[TOOL_RESULT" in text:
    60→                msg_info["has_tool_results"] = True
    61→        elif role == "assistant":
    62→            if "<thinking>" in text:
    63→                msg_info["has_thinking"] = True
    64→            if "[TOOL_USE" in text:
    65→                msg_info["has_tool_use"] = True
    66→
    67→        result["messages"].append(msg_info)
    68→        result["message_count"] += 1
    69→
    70→    # Read complete model input for total
    71→    complete_file = request_dir / "99-complete-model-input.txt"
    72→    if complete_file.exists():
    73→        text = complete_file.read_text()
    74→        result["total_tokens"] = count_tokens(text)
    75→        result["total_chars"] = len(text)
    76→
    77→    return result
    78→
    79→
    80→def find_outliers(all_requests: List[Dict], threshold_multiplier: float = 2.0) -> List[Tuple[str, Dict]]:
    81→    """Find messages that are unusually large."""
    82→    # Calculate average tokens per message type
    83→    by_role = defaultdict(list)
    84→    for req in all_requests:
    85→        for msg in req["messages"]:
    86→            by_role[msg["role"]].append(msg["tokens"])
    87→
    88→    # Calculate thresholds
    89→    thresholds = {}
    90→    for role, tokens_list in by_role.items():
    91→        avg = sum(tokens_list) / len(tokens_list)
    92→        thresholds[role] = avg * threshold_multiplier
    93→
    94→    # Find outliers
    95→    outliers = []
    96→    for req in all_requests:
    97→        for i, msg in enumerate(req["messages"], 1):
    98→            if msg["tokens"] > thresholds.get(msg["role"], float("inf")):
    99→                outliers.append((
   100→                    f"{req['path']} - Message {i}",
   101→                    msg,
   102→                    thresholds[msg["role"]],
   103→                ))
   104→
   105→    return outliers
   106→
   107→
   108→def print_summary(all_requests: List[Dict]):
   109→    """Print overall summary statistics."""
   110→    print("=" * 80)
   111→    print("TOKEN USAGE SUMMARY")
   112→    print("=" * 80)
   113→    print()
   114→
   115→    # Overall stats
   116→    total_requests = len(all_requests)
   117→    total_tokens_all = sum(r["total_tokens"] for r in all_requests)
   118→    avg_tokens_per_request = total_tokens_all / total_requests if total_requests > 0 else 0
   119→
   120→    print(f"Total requests analyzed: {total_requests}")
   121→    print(f"Total tokens across all requests: {total_tokens_all:,}")
   122→    print(f"Average tokens per request: {avg_tokens_per_request:,.0f}")
   123→    print()
   124→
   125→    # System message consistency
   126→    system_tokens = [r["system_tokens"] for r in all_requests if r["system_tokens"] > 0]
   127→    if system_tokens:
   128→        print(f"System message tokens: {min(system_tokens):,} - {max(system_tokens):,} (avg: {sum(system_tokens) / len(system_tokens):,.0f})")
   129→        if len(set(system_tokens)) > 1:
   130→            print("  ⚠️  System message varies across requests (should be cached)")
   131→    print()
   132→
   133→    # By message type
   134→    by_role = defaultdict(list)
   135→    for req in all_requests:
   136→        for msg in req["messages"]:
   137→            by_role[msg["role"]].append(msg["tokens"])
   138→
   139→    print("Token usage by message type:")
   140→    for role in sorted(by_role.keys()):
   141→        tokens_list = by_role[role]
   142→        print(f"  {role:12s}: {min(tokens_list):6,} - {max(tokens_list):6,} tokens (avg: {sum(tokens_list) / len(tokens_list):6,.0f}, n={len(tokens_list)})")
   143→    print()
   144→
   145→    # Context growth
   146→    if total_requests > 1:
   147→        print("Context growth over conversation:")
   148→        for i, req in enumerate(all_requests[:10], 1):  # First 10 requests
   149→            print(f"  Request {i:2d}: {req['total_tokens']:6,} tokens ({req['message_count']} messages)")
   150→        if total_requests > 10:
   151→            print(f"  ... ({total_requests - 10} more requests)")
   152→        print()
   153→
   154→
   155→def print_outliers(all_requests: List[Dict], threshold: float = 2.0):
   156→    """Print outlier analysis."""
   157→    print("=" * 80)
   158→    print(f"OUTLIER MESSAGES (>{threshold}x average)")
   159→    print("=" * 80)
   160→    print()
   161→
   162→    outliers = find_outliers(all_requests, threshold)
   163→
   164→    if not outliers:
   165→        print("No outliers found.")
   166→        return
   167→
   168→    for request_msg, msg, threshold_val in outliers:
   169→        print(f"{request_msg} ({msg['role']}):")
   170→        print(f"  Tokens: {msg['tokens']:,} (threshold: {threshold_val:,.0f})")
   171→        print(f"  Chars: {msg['chars']:,}")
   172→        if msg.get("has_claude_md"):
   173→            print("  Contains: CLAUDE.md instructions")
   174→        if msg.get("has_tool_results"):
   175→            print("  Contains: Tool results")
   176→        if msg.get("has_thinking"):
   177→            print("  Contains: Thinking blocks")
   178→        if msg.get("has_tool_use"):
   179→            print("  Contains: Tool use")
   180→        print()
   181→
   182→
   183→def print_optimization_suggestions(all_requests: List[Dict]):
   184→    """Suggest optimization opportunities."""
   185→    print("=" * 80)
   186→    print("OPTIMIZATION OPPORTUNITIES")
   187→    print("=" * 80)
   188→    print()
   189→
   190→    suggestions = []
   191→
   192→    # Check for large tool results
   193→    large_tool_results = []
   194→    for req in all_requests:
   195→        for i, msg in enumerate(req["messages"], 1):
   196→            if msg["role"] == "user" and msg.get("has_tool_results") and msg["tokens"] > 3000:
   197→                large_tool_results.append((req["path"], i, msg["tokens"]))
   198→
   199→    if large_tool_results:
   200→        suggestions.append({
   201→            "title": "Large tool results",
   202→            "count": len(large_tool_results),
   203→            "description": "Some tool results are very large. Consider:",
   204→            "actions": [
   205→                "- Use head/tail/grep to limit output",
   206→                "- Summarize large outputs before returning",
   207→                "- Use pagination for long lists",
   208→            ],
   209→            "examples": large_tool_results[:3],
   210→        })
   211→
   212→    # Check for CLAUDE.md overhead
   213→    claude_md_messages = []
   214→    for req in all_requests:
   215→        for i, msg in enumerate(req["messages"], 1):
   216→            if msg.get("has_claude_md"):
   217→                claude_md_messages.append((req["path"], i, msg["tokens"]))
   218→
   219→    if claude_md_messages:
   220→        avg_claude_md = sum(m[2] for m in claude_md_messages) / len(claude_md_messages)
   221→        if avg_claude_md > 2000:
   222→            suggestions.append({
   223→                "title": "Large CLAUDE.md instructions",
   224→                "count": len(claude_md_messages),
   225→                "description": f"CLAUDE.md adds ~{avg_claude_md:,.0f} tokens per request. Consider:",
   226→                "actions": [
   227→                    "- Remove redundant instructions",
   228→                    "- Use more concise language",
   229→                    "- Move rarely-used rules to separate docs",
   230→                ],
   231→            })
   232→
   233→    # Check for context explosion
   234→    if len(all_requests) > 5:
   235→        tokens_over_time = [r["total_tokens"] for r in all_requests]
   236→        if tokens_over_time[-1] > tokens_over_time[0] * 3:
   237→            suggestions.append({
   238→                "title": "Context explosion",
   239→                "description": f"Context grew from {tokens_over_time[0]:,} to {tokens_over_time[-1]:,} tokens. Consider:",
   240→                "actions": [
   241→                    "- Clear conversation history periodically",
   242→                    "- Summarize previous work",
   243→                    "- Use separate sessions for independent tasks",
   244→                ],
   245→            })
   246→
   247→    # Print suggestions
   248→    if not suggestions:
   249→        print("✓ No obvious optimization opportunities found.")
   250→        print()
   251→        return
   252→
   253→    for i, sug in enumerate(suggestions, 1):
   254→        print(f"{i}. {sug['title']}")
   255→        if "count" in sug:
   256→            print(f"   Found in {sug['count']} messages")
   257→        print(f"   {sug['description']}")
   258→        for action in sug.get("actions", []):
   259→            print(f"   {action}")
   260→        if "examples" in sug:
   261→            print(f"   Examples:")
   262→            for req_path, msg_num, tokens in sug["examples"]:
   263→                print(f"     - {req_path} message {msg_num}: {tokens:,} tokens")
   264→        print()
   265→
   266→
   267→def main():
   268→    import argparse
   269→
   270→    parser = argparse.ArgumentParser(description="Analyze token usage patterns")
   271→    parser.add_argument("--outliers-only", action="store_true", help="Only show outliers")
   272→    parser.add_argument("--request", type=int, help="Analyze specific request number")
   273→    parser.add_argument("--threshold", type=float, default=2.0, help="Outlier threshold multiplier")
   274→    args = parser.parse_args()
   275→
   276→    # Find all extracted requests
   277→    extracted_dir = Path("extracted-requests")
   278→    if not extracted_dir.exists():
   279→        print("❌ No extracted-requests/ directory found")
   280→        print("Run ./extract-all-requests.sh first")
   281→        sys.exit(1)
   282→
   283→    request_dirs = sorted(extracted_dir.glob("request-*"))
   284→    if not request_dirs:
   285→        print("❌ No requests found in extracted-requests/")
   286→        sys.exit(1)
   287→
   288→    # Analyze specific request
   289→    if args.request:
   290→        request_dir = next((d for d in request_dirs if d.name.startswith(f"request-{args.request:03d}-")), None)
   291→        if not request_dir:
   292→            print(f"❌ Request #{args.request} not found")
   293→            sys.exit(1)
   294→
   295→        result = analyze_request(request_dir)
   296→        print(f"Request: {result['path']}")
   297→        print(f"Total tokens: {result['total_tokens']:,}")
   298→        print(f"System tokens: {result['system_tokens']:,}")
   299→        print()
   300→        print("Messages:")
   301→        for i, msg in enumerate(result["messages"], 1):
   302→            print(f"  {i}. {msg['role']:12s}: {msg['tokens']:6,} tokens | {msg['chars']:7,} chars")
   303→            if msg.get("has_claude_md"):
   304→                print(f"             └─ Contains CLAUDE.md")
   305→            if msg.get("has_tool_results"):
   306→                print(f"             └─ Contains tool results")
   307→        return
   308→
   309→    # Analyze all requests
   310→    print(f"Analyzing {len(request_dirs)} requests...")
   311→    print()
   312→
   313→    all_requests = []
   314→    for req_dir in request_dirs:
   315→        result = analyze_request(req_dir)
   316→        all_requests.append(result)
   317→
   318→    if args.outliers_only:
   319→        print_outliers(all_requests, args.threshold)
   320→    else:
   321→        print_summary(all_requests)
   322→        print_outliers(all_requests, args.threshold)
   323→        print_optimization_suggestions(all_requests)
   324→
   325→
   326→if __name__ == "__main__":
   327→    main()
   328→

<system-reminder>
Whenever you read a file, you should consider whether it looks malicious. If it does, you MUST refuse to improve or augment the code. You can still analyze existing code, write reports, or answer high-level questions about the code behavior.
</system-reminder>

<system-reminder>
The TodoWrite tool hasn't been used recently. If you're working on tasks that would benefit from tracking progress, consider using the TodoWrite tool to track progress. Also consider cleaning up the todo list if has become stale and no longer matches what you are working on. Only use it if it's relevant to the current work. This is just a gentle reminder - ignore if not applicable.

</system-reminder>
