context-doctor 0.18.0 → 0.20.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,15 +1,30 @@
1
1
  # context-doctor 🩺
2
2
 
3
- [![CI](https://github.com/KushalP1/context-doctor/actions/workflows/ci.yml/badge.svg)](https://github.com/KushalP1/context-doctor/actions) [![npm](https://img.shields.io/npm/v/context-doctor)](https://www.npmjs.com/package/context-doctor)
3
+ [![CI](https://github.com/KushalP1/context-doctor/actions/workflows/ci.yml/badge.svg)](https://github.com/KushalP1/context-doctor/actions) [![npm](https://img.shields.io/npm/v/context-doctor)](https://www.npmjs.com/package/context-doctor) [![npm downloads](https://img.shields.io/npm/dm/context-doctor)](https://www.npmjs.com/package/context-doctor) [![license: MIT](https://img.shields.io/badge/license-MIT-blue)](./LICENSE) ![macOS | Linux | Windows](https://img.shields.io/badge/platform-macOS%20%7C%20Linux%20%7C%20Windows-lightgrey)
4
4
 
5
- **See what's eating your LLM context window — and fix it.**
5
+ **Keep every AI session's context lean, automatically, without ever making it more expensive.**
6
6
 
7
- Every long-running LLM conversation slowly fills up with junk: duplicated documents, 10k-token tool outputs nobody reads again, base64 blobs, stale history. You pay for those tokens on **every single call**, and model quality drops as the window fills.
7
+ Long agent sessions fill up with tool output nobody reads again: file dumps, shell logs, search results, screenshots. You pay for all of it on every request, the model gets slower, and it drifts as the window fills. `context-doctor` measures that, and with **autopilot** it removes it from every Claude Code (and GPT API) request on your machine, only at moments when doing so costs nothing extra.
8
8
 
9
- `context-doctor` is a zero-config profiler + optimizer for LLM contexts. It works with **Claude, GPT, Gemini** message formats, and plugs into **Claude Desktop, ChatGPT (developer mode), Cursor, Claude Code** — any MCP-capable app — or runs standalone from the terminal.
9
+ - **9.8% less input cost, no session worse.** Replaying 130 days of the author's real Claude Code use (43 sessions) through the shipped code: **3.8 billion input tokens not sent, $4,694 saved at API list price, about $1,080 a month**, and not one session more expensive. Up to 37.7% on a long session. [What it saves →](#what-it-saves)
10
+ - **Counts Claude correctly.** Current Claude models pack 2.75 characters per token, not the 4 most tools assume; estimates built on 4 undercount Claude by about 40%. The ratios here were measured from the API's own counts, and `context-doctor accuracy` re-checks them on yours. [How →](#why-token-counts-are--and-where-they-are-exact)
11
+ - **Works where you work:** Claude Code, Cursor, Codex, Claude Desktop, any Anthropic or OpenAI API app, VS Code, CI. macOS, Linux and Windows, Node 20+.
12
+ - **Local and keyless.** No account, no telemetry, no API key. Your own login passes through untouched. MIT.
10
13
 
11
14
  Built and maintained by [gAI Ventures](https://gai.ventures).
12
15
 
16
+ ## Quick start
17
+
18
+ ```bash
19
+ npm install -g context-doctor
20
+ context-doctor install # hooks, MCP server and skill in every AI app it finds
21
+ context-doctor autopilot on # every new Claude Code session keeps its context lean
22
+ ```
23
+
24
+ Then start a new Claude Code session and work as usual. `context-doctor autopilot status` shows what it did; `context-doctor doctor` checks the whole setup. Everything is reversible: `context-doctor autopilot off`, `context-doctor uninstall`.
25
+
26
+ Just want a look first? `npx context-doctor session` profiles your latest Claude Code or Codex session in place, no install.
27
+
13
28
  ```
14
29
  Where the tokens go
15
30
  ────────────────────────────────────────────────────────
@@ -26,35 +41,66 @@ Findings (4)
26
41
  → Truncate or summarize large tool outputs before they enter history.
27
42
  ```
28
43
 
29
- ## Quick start (30 seconds)
44
+ ## What it saves
30
45
 
31
- **One command sets up everything** — detects Claude Desktop, Claude Code, and Cursor on your machine, wires in the MCP server, installs the Agent Skill, and registers the Claude Code every-prompt hook:
46
+ Measured, not modelled: every Claude Code session on the author's machine from 18 May to 25 September 2026 (43 sessions, 130 days, mostly Opus 5 and Fable 5 with the 1M window) was replayed request by request through the shipped autopilot code, with the real timestamps, and priced the way the prompt cache bills it (cached reads 0.1x, writes 1.25x). Run it on your own history with `node scripts/replay-autopilot.mjs`.
32
47
 
33
- ```bash
34
- npx context-doctor install
35
- ```
48
+ | | Without autopilot | With autopilot | Saved |
49
+ |---|---|---|---|
50
+ | Input tokens sent | 41.4 billion | 37.6 billion | **3.8 billion (9.2%)** |
51
+ | Input cost at API list price | $48,962 | $44,268 | **$4,694 (9.8%)** |
52
+ | Per 30 days | | | **~875 million tokens, ~$1,080** |
53
+ | Sessions made more expensive | | | **0 of 43** |
36
54
 
37
- `install` configures every app it detects and does not stop at the first problem: a corrupt Claude Desktop config still gets you Claude Code and Cursor. It does not pretend either. Any target that failed is named with a ✗ line, the summary reads "Done with N problem(s)" instead of "Done.", and the **exit code is 1**, so dotfiles and onboarding scripts can react. A broken config file is never overwritten; fix it and re-run.
55
+ How it spreads: the median session saves 1.9%, the best 37.7%. Short sessions barely change, because they rarely pile up 20k tokens of stale tool output before they end. Long sessions are where the money is: on this machine 94% of input cost came from requests above 200k tokens, and those are the requests autopilot makes smaller. Savings scale with how long your sessions run and how much they read, so a lighter user saves proportionally less, and never pays more.
38
56
 
39
- That single command is also all it takes to **set up context-doctor on anyone else's machine**. Prefer a global install, or want the unreleased `main`? Both work (Node 20+):
57
+ On a Claude subscription you do not pay list price; the same tokens come out of your usage limit instead. Anthropic does not publish how limits weight cached tokens, so read the dollar column as the size of the effect, not as your bill. The token column holds either way, and every request that is 9% smaller is also faster to first token and further from auto-compaction.
40
58
 
41
- ```bash
42
- npm install -g context-doctor && context-doctor install
43
- ```
59
+ What is not counted here: the proxy's full optimizer for your own API apps, the hook's guidance to the model, and fixes you make from `session` findings. Those save more on top, but they depend on what the model or you do with the advice, so they are not in this table.
60
+
61
+ ## What happens on each platform
62
+
63
+ | Where you work | Automatic, every request | What you get on top |
64
+ |---|---|---|
65
+ | **Claude Code** (terminal, VS Code, JetBrains, desktop app's Code tab) | **Autopilot** clears stale tool output (cold cache only, never more expensive). **Hook** on every prompt warns the model with the real context size and its largest waste | Status bar context meter, `/context-doctor` skill, `session`, `watch`, `report`, dashboard |
66
+ | **Cursor** (agent) | Cursor runs Claude Code's hooks, so the same every-prompt check fires inside Cursor | MCP tools, editor status bar extension, `cursor` profiler. With your own OpenAI key, autopilot too via a tokened tunnel ([how](#putting-the-proxy-on-a-public-url-cursor-with-your-own-openai-key-remote-apps)) |
67
+ | **Codex** (ChatGPT app's Codex tab, IDE extension, CLI) | Every-prompt hook with the API's own token counts | MCP tools, skill, `session` reads Codex rollouts. On an API key, autopilot too (`OPENAI_BASE_URL`) |
68
+ | **Your own apps on the Anthropic or OpenAI API** | Autopilot on `/v1/messages`, `/v1/chat/completions` and `/v1/responses` (`ANTHROPIC_BASE_URL` / `OPENAI_BASE_URL`), or the full optimizing proxy | Exact usage and cache hit rates in `/stats`, prompt-cache placement advice |
69
+ | **Claude Desktop chat** | Standing context rules in every chat; one cheap `profile_context` call the model makes past ~30 turns or on any cost question | One-click `.mcpb` install, `context_checkup` prompt |
70
+ | **claude.ai, ChatGPT, the phone apps** | Your account's standing preferences (`context-doctor instructions --copy`) | Profile an exported chat with `analyze` |
71
+ | **CI** | `analyze --fail-over-budget` fails a build whose prompts outgrow a budget | `.contextdoctorrc` budgets and presets |
72
+
73
+ Not claimed, because no process on your machine sends those requests: trimming inside Claude Desktop chat, claude.ai, ChatGPT, Cursor's own subscription models, or Codex signed in with ChatGPT. Those get the rules and the measurements above, not autopilot.
74
+
75
+ ## What's new
76
+
77
+ - **0.20 Autopilot**: stale tool output cleared from every Claude Code request, only when the prompt cache is cold, so it cannot cost more (measured: 9.8% less input cost, ~$1,080 a month on the author's usage, no session worse); runs as a login service on macOS, Linux and Windows; now also for GPT via OpenAI's Chat Completions and Responses APIs.
78
+ - **0.19 Measured Claude tokenizer**: estimates were 40% low for Claude; fixed from the API's own counts, with a per-model check in `accuracy`.
79
+ - **0.18** `proxy --token` for putting the proxy on a public URL safely. **0.17** Claude Desktop: a `profile_context` the model can afford to call from chat, `.mcpb` bundle, standing preferences for web and mobile. **0.16** Codex. **0.15** Cursor.
80
+
81
+ Full history with the measurements behind each change: [ROADMAP.md](./ROADMAP.md).
82
+
83
+ ## Setup details
84
+
85
+ `install` configures every app it detects and does not stop at the first problem: a corrupt Claude Desktop config still gets you Claude Code and Cursor. It does not pretend either. Any target that failed is named with a ✗ line, the summary reads "Done with N problem(s)" instead of "Done.", and the **exit code is 1**, so dotfiles and onboarding scripts can react. A broken config file is never overwritten; fix it and re-run.
44
86
 
45
- Restart your apps, then just ask Claude: *"what's eating my context?"* (`npx context-doctor uninstall` reverses it.)
87
+ `npx context-doctor install` works too, but autopilot needs the global install: a background service cannot point into npx's cache, which npm deletes at will.
46
88
 
47
89
  **No API keys, ever.** Everything is deterministic local code; when an LLM is needed (summarizing pruned history), the model already running in your app does it. The proxy forwards *your app's* credentials untouched — context-doctor itself holds nothing.
48
90
 
49
91
  ## What `install` actually does — and what happens in every session after
50
92
 
51
- One run of `npx context-doctor install` writes five things (each config edit makes a `.backup` first; `uninstall` reverses all of it):
93
+ One run of `context-doctor install` writes these (each config edit makes a `.backup` first; `uninstall` reverses all of it):
52
94
 
53
95
  1. **Claude Desktop config** (`claude_desktop_config.json`) — registers the MCP server
54
96
  2. **Claude Code config** (`~/.claude.json`) — registers the MCP server
55
97
  3. **Cursor config** (`~/.cursor/mcp.json`) — registers the MCP server
56
98
  4. **Agent Skill** → `~/.claude/skills/context-doctor/` — context-hygiene playbook for Claude Code
57
- 5. **Every-prompt hook** → `~/.claude/settings.json` — the per-query context check for Claude Code
99
+ 5. **Every-prompt hook** → `~/.claude/settings.json` — the per-query context check for Claude Code (Cursor runs it too)
100
+ 6. **Codex**, when present: MCP server in `~/.codex/config.toml`, the hook in `~/.codex/hooks.json`, the skill in `~/.codex/skills/`
101
+ 7. With `--statusline`: live context size, cache share and cost in Claude Code's status bar
102
+
103
+ `context-doctor autopilot on` is separate and opt-in: it adds the background proxy service and one line (`env.ANTHROPIC_BASE_URL`) to `~/.claude/settings.json`, after the proxy has answered a health check.
58
104
 
59
105
  **In every chat afterward (Claude Desktop, Cursor):** when the conversation starts, the app launches the MCP server, which hands the model standing instructions that stay in force for the whole chat:
60
106
 
@@ -72,11 +118,12 @@ One run of `npx context-doctor install` writes five things (each config edit mak
72
118
 
73
119
  ## Do you need MCP? Only sometimes — all the ways to use context-doctor
74
120
 
75
- MCP is just one of six delivery mechanisms. It's only required when you want the AI **inside a chat app** to run the tools itself. Everything else works without it:
121
+ MCP is just one of seven delivery mechanisms. It's only required when you want the AI **inside a chat app** to run the tools itself. Everything else works without it:
76
122
 
77
123
  | How you use it | MCP needed? | What it requires |
78
124
  |---|---|---|
79
125
  | **CLI** — `analyze`, `optimize`, `session` on files/transcripts | ❌ No | Nothing but `npx` — works in any terminal, scripts, CI |
126
+ | **Autopilot** — every Claude Code session, and GPT API apps | ❌ No | `context-doctor autopilot on` (a login service + one line in `~/.claude/settings.json`) |
80
127
  | **Proxy** — always-on optimization of your API apps | ❌ No | `context-doctor proxy` + one env var in your app |
81
128
  | **Claude Code every-prompt hook** | ❌ No | Written by `install`; Claude Code invokes it directly |
82
129
  | **Agent Skill** — hygiene behavior in Claude Code / claude.ai | ❌ No | A markdown file; `install` places it (or upload to claude.ai) |
@@ -90,14 +137,15 @@ Practical upshot: a developer who only wants cheaper, faster API calls never tou
90
137
  | Command | What it does |
91
138
  |---|---|
92
139
  | `context-doctor install` / `uninstall` | Wire (or remove) everything: MCP for Claude Desktop/Code/Cursor/Codex, the Agent Skill, the every-prompt hook |
93
- | `context-doctor instructions [--copy]` | The ~90-token standing rules for claude.ai / ChatGPT preferences, for web and phones where no server runs |
140
+ | `context-doctor autopilot on\|off\|pause\|resume\|status` | Every new Claude Code session goes through the local proxy, which clears stale tool output only when the prompt cache is cold: measured 9.8% less input cost, no session worse |
141
+ | `context-doctor instructions [--copy]` | The ~180-token standing rules (~120 on GPT) for claude.ai / ChatGPT preferences, for web and phones where no server runs |
94
142
  | `context-doctor analyze <file>` | Profile a conversation: token breakdown, findings, cost + latency estimates. `--fail-over-budget` exits 1 on a breach, for CI |
95
143
  | `context-doctor optimize <file>` | Apply the safe fixes; add `--strategy trim-tool-calls` for big inline file writes, `--strategy prune-history` for consented lossy compaction |
96
144
  | `context-doctor session [file]` | Profile a Claude Code session: live context, findings, **measured tokens and prompt-cache economics**, **where the wall clock went** per tool, and **what its subagents cost** (their own windows, your bill; never in the parent's profile). Also reads ChatGPT data exports (`conversations.json`) |
97
145
  | `context-doctor init [preset]` | Write a `.contextdoctorrc` from a preset (`chat`, `agent`, `batch`) — a budget you can adopt in one command and tune later |
98
146
  | `context-doctor experiment --task "…"` | Run one task twice from the same commit, in a fresh session and forked from an `--existing` one, same model and tools; compare bill, cache split, wall clock, and whether `--check` passed. The only command here that spends money, so it caps spend per arm and refuses a dirty tree |
99
147
  | `context-doctor diff <before> <after>` | Compare two profiles: what moved by category, which findings were resolved or introduced, and what it saves in money and latency |
100
- | `context-doctor accuracy` | How much of what you are billed for is visible in your transcript — the fixed harness baseline and the per-turn injected content neither you nor the profiler can see |
148
+ | `context-doctor accuracy` | How much of what you are billed for is visible in your transcript (the fixed harness baseline, per-turn injected content), plus a tokenizer check: real chars/token per model from the API's own counts, next to the ratio the estimator uses |
101
149
  | `context-doctor cursor [--list]` | Profile a chat from Cursor's local history (both storage formats) |
102
150
  | `context-doctor report` | Machine-wide impact report (proxy savings persist across restarts): exact proxy savings, hook activity, recoverable waste in recent sessions |
103
151
  | `context-doctor proxy` | Always-on local proxy that optimizes every Anthropic/OpenAI API request in flight (`/stats` for cumulative savings) |
@@ -112,14 +160,13 @@ Practical upshot: a developer who only wants cheaper, faster API calls never tou
112
160
 
113
161
  | Where you run LLMs | Mechanism | Guarantee |
114
162
  |---|---|---|
115
- | Your own apps/agents (API) | `context-doctor proxy` rewrites every request in flight | **Every call, automatic** |
163
+ | Claude Code sessions, with autopilot on | The login-service proxy clears stale tool output from every request, only when the cache is cold | **Every request; never more expensive (measured)** |
164
+ | Your own apps/agents (API) | `context-doctor proxy` rewrites every request in flight, or `proxy --autopilot` for the cache-safe mode | **Every call, automatic** |
116
165
  | Claude Code / Cowork sessions | `install` registers a **UserPromptSubmit hook**: every query measures the session; heavy sessions get injected hygiene guidance (silent when lean, rate-limited, never blocks a prompt) | **Every query checked** |
117
166
  | Claude Desktop chat / Cursor | **MCP server instructions** — standing hygiene directives injected into every conversation where the server is enabled, plus prescriptive tool triggers | **Every conversation carries the rules** |
118
- | claude.ai (web) / ChatGPT app | Upload `skills/context-doctor/SKILL.md` in the app's Skills settings for the same standing behavior | Manual one-time upload |
119
-
120
- Nothing runs in the background for the Claude apps — the hook, skill, MCP server, and its instructions are all delivered by the app itself at the right moment. The proxy is the only long-running piece, and only your API-calling apps need it.
167
+ | claude.ai (web) / ChatGPT / phone apps | `context-doctor instructions --copy`, pasted once into the account's preferences (or upload `skills/context-doctor/SKILL.md` as a skill) | Every chat on that account carries the rules |
121
168
 
122
- Optional belt-and-braces for any chat app: add one line to your profile preferences — *"Practice context hygiene: summarize large content instead of re-quoting it, and use context-doctor's tools when conversations get heavy."*
169
+ Without autopilot nothing runs in the background: the hook, skill, MCP server and its instructions are delivered by the apps themselves at the right moment. With autopilot, one small proxy runs as a login service (launchd / systemd user service / logon task) and is restarted by the service or by the next prompt's hook if it ever stops.
123
170
 
124
171
  Or use the CLI directly, no install needed:
125
172
 
@@ -150,6 +197,35 @@ npx context-doctor session --list # browse sessions
150
197
 
151
198
  Parses the transcripts Claude Code writes locally and answers "where did my tokens go today?" — it will happily tell you that one giant skill load is 67% of your context.
152
199
 
200
+ ## Autopilot: every Claude Code session keeps its own context lean
201
+
202
+ ```bash
203
+ context-doctor autopilot on # once; survives reboots
204
+ context-doctor autopilot status # what it has done
205
+ context-doctor autopilot pause # instant passthrough, nothing restarts
206
+ context-doctor autopilot off # remove it
207
+ ```
208
+
209
+ `autopilot on` runs the local proxy as a background service (launchd on macOS, a systemd user service on Linux, a logon task on Windows), waits until it answers, and only then points Claude Code at it through `env.ANTHROPIC_BASE_URL` in `~/.claude/settings.json`. Every Claude Code session started afterwards, in the terminal, an IDE, or the desktop app's Code tab, sends its requests through it. Your login (subscription or API key) passes through untouched.
210
+
211
+ **What it does to each request:** once old tool output adds up to 20k+ tokens (file reads, shell output, search and web results, screenshots inside them), it replaces that output with a one-line note, keeping the 3 most recent results. The tool call stays in the history, so the model can simply run it again if it needs the output. Your messages, its answers, answers you gave to its questions, subagent reports and MCP results are never touched.
212
+
213
+ **Why it cannot make a session more expensive.** A prompt cache matches a byte-identical prefix, and changing old history re-bills everything after the change at the write rate (1.25x instead of 0.1x). So autopilot changes history only when the cache is cold anyway: after an idle gap longer than the cache lifetime the request itself declares (1 hour for Claude Code on a subscription, 5 minutes otherwise), when the whole prompt is re-written regardless. Once cleared, an output stays cleared on every later request, so the prefix is identical between clearings and the cache keeps hitting.
214
+
215
+ **Measured before it shipped**, by replaying every Claude Code session on the author's machine (43 sessions, 130 days) request by request through the shipped code, priced as the cache bills it, with real timestamps: 3.8 billion input tokens not sent and $4,694 at list price, ~$1,080 a month ([details](#what-it-saves)). `scripts/replay-autopilot.mjs` does this on yours:
216
+
217
+ | Policy | Input cost saved | Worst session |
218
+ |---|---|---|
219
+ | **Autopilot: clear only when the cache is cold (default)** | **9.8%** (9.2% of raw input tokens; best session 37.7%) | **0.00%, no session worse** |
220
+ | Also clear on a warm cache when the saving "should" repay the rewrite | 9.9% | −0.13% (one session worse) |
221
+ | The proxy's general strategies (dedupe, trim, strip-base64) | 6.7% | −13% (one session worse) |
222
+
223
+ 94% of input cost on that machine came from requests above 200k tokens, which is where the clearing lands. Smaller requests also mean later auto-compaction and a faster first token.
224
+
225
+ **Never in the way:** anything it cannot parse is forwarded unchanged; only Anthropic `/v1/messages` requests are touched. It adds about 7 ms to a 2.9 MB (~1M token) request. If the proxy dies, the service restarts it within seconds, and the every-prompt hook checks it before each prompt and starts it if needed (measured: 0.6 s once, when it had to). `pause` turns it into a passthrough without restarting anything; `off` removes the setting before stopping the service, and sessions started while it was on need a restart.
226
+
227
+ **What it cannot reach** (no process on your machine sends those requests): Claude Desktop's chat tab and claude.ai, which use the standing instructions and the `profile_context` sketch instead; Cursor's own models (Cursor's servers call the model; with your own OpenAI key, see the tunnel section below); and Codex signed in with ChatGPT, where the every-prompt hook still reports context size.
228
+
153
229
  ## Always-on: optimize every request automatically
154
230
 
155
231
  Run the proxy and every Anthropic/OpenAI API call your apps make gets optimized in flight — no code changes:
@@ -203,13 +279,13 @@ Your API key still rides in the request headers, as before. The token protects t
203
279
  | **Claude Code** | Yes: hook on every prompt, status line on every refresh | Past ~80k tokens the model receives hygiene guidance naming the largest waste; compaction is offered. Measured: 115 automatic checks, 48 warnings, across 32 sessions on one machine |
204
280
  | **Cursor** | **Yes**, since 0.15: Cursor loads Claude Code's hook config (`~/.claude/settings.json`) and runs the same hook on every agent prompt, passing its own transcript. Output is accepted through Cursor's Claude-compat layer | Same guidance as Claude Code, inside Cursor's agent, for everyone who ran `install`. Before 0.15 the hook fired but could not read Cursor's transcript format, so it said nothing |
205
281
  | **API traffic through the proxy** | Yes: every request rewritten in flight | Fewer tokens, guaranteed, model not consulted |
206
- | **Claude Desktop** | The standing instruction in every chat (we confirmed in the app bundle that Desktop's `LocalMcpServerManager` reads it), a one-click `context_checkup` prompt, and since 0.17 a `profile_context` the model can actually afford to call from chat | Until 0.17 the tool wanted the whole conversation as its argument, so calling it from chat meant re-typing 50k tokens; nobody did, and Desktop's log showed zero calls in a month. Now the model passes a ~100-token **sketch** (turn count, the large or repeated blocks) and gets a sized estimate, findings and the fix to apply. Still a nudge, not a hook: Desktop chat has no hook API and no transcript on disk |
282
+ | **Claude Desktop** | The standing instruction in every chat (we confirmed in the app bundle that Desktop's `LocalMcpServerManager` reads it), a one-click `context_checkup` prompt, and since 0.17 a `profile_context` the model can actually afford to call from chat | Until 0.17 the tool wanted the whole conversation as its argument, so calling it from chat meant re-typing 50k tokens; nobody did, and Desktop's log showed zero calls in a month. Now the model passes a ~120-token **sketch** (turn count, the large or repeated blocks) and gets a sized estimate, findings and the fix to apply. Still a nudge, not a hook: Desktop chat has no hook API and no transcript on disk |
207
283
  | **Codex (OpenAI): ChatGPT.app's Codex tab, the Codex IDE extension, the `codex` CLI** | **Yes**, since 0.16: `install` writes the hook to `~/.codex/hooks.json`, the MCP server to `~/.codex/config.toml`, and the skill to `~/.codex/skills/`. Codex uses Claude Code's hook contract almost verbatim and passes its own rollout transcript, which carries the API's real usage figures | Same guidance as Claude Code, from measured tokens. One extra step, Codex's rule not ours: a new hook runs only after you trust it once (type `/hooks` in Codex). `session` and `session --list` read Codex rollouts too |
208
284
  | **ChatGPT chat UI** | No | No MCP, no hooks, no data path in the chat product itself. Use Codex, or a developer-mode connector at a URL you host |
209
285
 
210
286
  So "every chat inherently better" is true for Claude Code, Cursor, Codex and the proxy; for Claude Desktop it is "the rules ride in every chat and the checkup is one cheap tool call away"; and not a claim we make for the ChatGPT chat UI.
211
287
 
212
- **Where there is no hook and no MCP at all** (claude.ai on the web, the Claude and ChatGPT phone apps, plain ChatGPT): the app's per-account preferences are read on every turn, which is the closest those surfaces have to a hook. `context-doctor instructions --copy` puts the ~90-token rules on your clipboard and tells you where to paste them (claude.ai Settings > Profile; ChatGPT Settings > Personalization > Custom instructions).
288
+ **Where there is no hook and no MCP at all** (claude.ai on the web, the Claude and ChatGPT phone apps, plain ChatGPT): the app's per-account preferences are read on every turn, which is the closest those surfaces have to a hook. `context-doctor instructions --copy` puts the ~180-token rules on your clipboard and tells you where to paste them (claude.ai Settings > Profile; ChatGPT Settings > Personalization > Custom instructions).
213
289
 
214
290
  **Do you need to configure anything by hand? Usually no:**
215
291
 
@@ -238,7 +314,7 @@ For any other MCP client, the server entry is:
238
314
 
239
315
  1. Run `npx context-doctor install` (writes the config above for you) and restart Claude Desktop. Or open the `.mcpb` from the latest release: same server, no npm, installs as an Extension.
240
316
  2. From then on, **every conversation carries context-doctor's standing instructions**. The MCP server hands them to Desktop on connect and Desktop puts them in front of Claude: summarize big pastes instead of re-quoting them, refer to earlier content by name, never inline base64, and past ~30 turns or on any question about tokens, cost, speed or limits, call `profile_context` before answering.
241
- 3. That call is cheap on purpose. Claude cannot export a Desktop chat, so it passes a **sketch**: how many turns, which blocks are large, repeated, stale or images, with one size hint each (~100 tokens). The server sizes it (±30%, and it says so), prices the per-turn re-read (on a subscription that is what spends your usage limit), and returns ranked findings with the action for each: "summarize *the nginx config* into the points still needed", "refer to *test output* by name", "offer a 300-token handoff summary for a fresh chat". The reply ends with an instruction to apply the top one, not just suggest it.
317
+ 3. That call is cheap on purpose. Claude cannot export a Desktop chat, so it passes a **sketch**: how many turns, which blocks are large, repeated, stale or images, with one size hint each (~120 tokens). The server sizes it (usually within ±20%, measured; see "Why token counts are ~"), prices the per-turn re-read (on a subscription that is what spends your usage limit), and returns ranked findings with the action for each: "summarize *the nginx config* into the points still needed", "refer to *test output* by name", "offer a 300-token handoff summary for a fresh chat". The reply ends with an instruction to apply the top one, not just suggest it.
242
318
  4. One click instead of asking: the `context_checkup` prompt in the **+** menu sends that request for you.
243
319
  5. Say *"optimize it"* on an exported conversation and Claude applies the safe fixes; if you agree to pruning old history, **Claude itself writes the replacement summary** (that's the no-API-key summarization).
244
320
  6. For the same rules on your phone and on claude.ai, where no MCP server runs: `context-doctor instructions --copy`, then paste into Settings > Profile > personal preferences.
@@ -359,9 +435,9 @@ The verdict line is the point: cheaper only counts if it also passed. Because th
359
435
 
360
436
  ## Exact counts, and what they teach the estimator
361
437
 
362
- The default token count is a chars-per-token heuristic so everything runs with no key and no tokenizer. Its error is content-dependent, and there is no honest way to fix that from transcripts alone (the billed number includes content the transcript never sees). `analyze --exact` fetches a true count for the exact bytes just estimated (Anthropic's count-tokens API with `ANTHROPIC_API_KEY`; tiktoken for GPT if installed) and prints the drift.
438
+ The default token count is a chars-per-token heuristic so everything runs with no key and no tokenizer, with ratios per provider (see "Why token counts are ~" below). `analyze --exact` fetches a true count for the exact bytes just estimated (Anthropic's count-tokens API with `ANTHROPIC_API_KEY`; tiktoken for GPT if installed) and prints the drift.
363
439
 
364
- Since 0.13.9 it also **remembers the comparison**, per model family, on this machine, and later estimates for that family are scaled by it. Nothing about this is silent: the profile header says `estimates calibrated +12% from 3 exact count(s) you ran on this machine`. No exact count ever run means no calibration and unchanged numbers; out-of-range samples are ignored; `CONTEXT_DOCTOR_NO_CALIBRATION=1` returns to the raw heuristic.
440
+ Since 0.13.9 it also **remembers the comparison**, per model family, on this machine, and later estimates for that family are scaled by it. Nothing about this is silent: the profile header says `estimates calibrated +12% from 3 exact count(s) you ran on this machine`. No exact count ever run means no calibration and unchanged numbers; out-of-range samples are ignored; `CONTEXT_DOCTOR_NO_CALIBRATION=1` returns to the raw heuristic. Samples are tied to the heuristic they were taken against: after 0.19 changed Claude's ratios, older samples are ignored and learning restarts, rather than stacking an old correction on a fixed estimator.
365
441
 
366
442
  ## What it detects
367
443
 
@@ -466,7 +542,7 @@ A tool that promises speed must be near-free. Measured overhead per touchpoint:
466
542
  | Touchpoint | When it runs | Overhead |
467
543
  |---|---|---|
468
544
  | Every-prompt hook (Claude Code) | Every prompt | **~80ms** (Node startup; logic ~1ms). Lean sessions exit on a single `stat()` — the transcript is never read. Full profiling (~200ms on a 4MB session) happens only when the transcript has grown ~40% since last checked |
469
- | MCP server | Spawned once per app session | Tools run only when called; standing instructions cost **~110 tokens per conversation** — deliberately terse |
545
+ | MCP server | Spawned once per app session | Tools run only when called; standing instructions cost **~250 tokens per conversation on Claude, ~170 on GPT** — deliberately terse |
470
546
  | Proxy | Per API request | ~1–3ms of CPU (parse → optimize → re-serialize) against typical model latencies of hundreds of ms; responses stream through chunk-by-chunk, never buffered |
471
547
  | Skill | Loads only when relevant | ~1k tokens while active; its always-present description is ~60 tokens |
472
548
  | Profiling a session | On demand, and on hook growth events | ~160ms for an 8.5MB / 1,855-message transcript (near-duplicate pairs that cannot clear the similarity bar are skipped without comparison) |
@@ -476,18 +552,25 @@ Net effect is strongly negative overhead: the tokens these touchpoints save on e
476
552
 
477
553
  ## Why token counts are "~" (and where they are exact)
478
554
 
479
- Counting exactly needs each provider's tokenizer, so the default is a calibrated chars-per-token heuristic (denser for code and JSON). It is good enough to rank what is heavy and to measure the effect of a fix, and it keeps the tool offline and zero-config.
555
+ Counting exactly needs each provider's tokenizer, so the default is a chars-per-token heuristic, with ratios per provider and denser ones for code and JSON. It keeps the tool offline and zero-config.
480
556
 
481
- Two ways to get real numbers instead:
557
+ | Model | Prose | Code / tool output | Source |
558
+ |---|---|---|---|
559
+ | Claude (Opus 4.7 to 5.x, Fable 5.x, Sonnet 5) | 2.75 chars/token | 2.4 | Measured from the API's own counts, below |
560
+ | GPT, Gemini, unknown | 4.0 | 3.2 | Usual figures for o200k-class tokenizers; not re-measured here |
482
561
 
483
- - **`analyze --exact`** uses the Anthropic count-tokens API for Claude models (set `ANTHROPIC_API_KEY`; opt-in network call, key never stored) or tiktoken for GPT models (install it alongside), and reports how far the heuristic drifted.
484
- - **Sessions report measured tokens automatically.** Claude Code transcripts record what the API actually charged, so `session`, the hook and the reports use that figure when it is present — no key, no estimate.
562
+ Which row applies: the model you pass, else the request's own `model` field, else the request's shape (Anthropic's `system` field or `tool_use` blocks mean Claude). Cursor transcripts record no model and use Anthropic-style blocks, so Cursor sessions are counted at Claude density; for a GPT model in Cursor that reads about 40% high.
563
+
564
+ **How the Claude figures were measured, with no key.** Claude Code transcripts record what the API billed, and two things in them are exact. A reply with no thinking block is billed as exactly its `output_tokens`, and all of it is visible text: 504 replies gave a median of 2.75 chars/token (p10 2.4, p90 3.0). Between two consecutive API calls the prompt grows by exactly what was appended; when that is one large block, its size is the growth minus the previous reply: 474 blocks of code and tool output gave 2.4 (p10 2.1, p90 2.8). The ratios this tool used until 0.19 (4.0 / 3.2 for everything) **undercounted current Claude models by about 40%**: hook warnings came late, savings and costs read low, and the proxy stayed silent on cacheable prefixes between 1,024 and ~1,670 tokens. `context-doctor accuracy` re-runs both measurements on your own sessions and prints them per model beside the ratio in use, so the next tokenizer change shows up as a number, not a surprise. On this machine every model lands within ±9%.
485
565
 
486
- One honest caveat worth knowing: a transcript stores the conversation, **not** the harness's system prompt, tool schemas or skills. Measured against the API's own numbers here, a message-only estimate undercounts the true context by roughly 60%. That is why sessions prefer the reported figure, and why the message breakdown is labelled as covering messages only.
566
+ Two ways to get real numbers instead of estimates:
487
567
 
568
+ - **`analyze --exact`** uses the Anthropic count-tokens API for Claude models (set `ANTHROPIC_API_KEY`; opt-in network call, key never stored) or tiktoken for GPT models (install it alongside), and reports how far the heuristic drifted.
569
+ - **Sessions report measured tokens automatically.** Claude Code transcripts record what the API actually charged, so `session`, the hook and the reports use that figure when it is present — no key, no estimate.
488
570
 
571
+ One honest caveat worth knowing: a transcript stores the conversation, **not** the harness's system prompt, tool schemas or skills (about 54k tokens before the first turn in Claude Code here), nor the reminders it injects each turn. With the corrected ratios the transcript accounts for a median 56% of each turn's billed growth; before 0.19 this read 39%, and about a third of that "invisible" gap was the estimator. That is why sessions prefer the reported figure, and why the message breakdown is labelled as covering messages only.
489
572
 
490
- Exact counts require each provider's private tokenizer. `context-doctor` uses a calibrated chars-per-token heuristic (denser for code/JSON) that lands within ~10% — plenty accurate for finding what's heavy and measuring savings, and it keeps the tool fully offline with zero configuration.
573
+ **The chat-app sketch** (Claude Desktop, see above) is coarser by design, because the model describes the chat instead of sending it. Its sizes are measured, not assumed: a plain exchange is 2,060 chars (median of 1,283), a code line 42 chars (719 source files), a log line 56 (2,095 tool outputs), a word 6.3, all converted with the model's own ratio. Measured error: the total for a chat of 30+ exchanges from its turn count alone is within -20% to +9% (p10 to p90); a code block sized by lines is within about ±25%, by chars about ±15%. Logs vary from 38 to 100 chars a line, so the tool asks for their size in chars.
491
574
 
492
575
  ## Roadmap
493
576
 
@@ -11,10 +11,12 @@
11
11
  * measures the distance between them on your own sessions, so "why is my bill
12
12
  * bigger than the profile?" has an answer with evidence behind it.
13
13
  *
14
- * WHAT THIS IS NOT: a tokenizer benchmark. It cannot be — the content behind
15
- * the gap is unavailable to us, so the gap cannot be attributed to estimator
16
- * drift. To measure the estimator itself, use `analyze --exact`, which counts
17
- * the same bytes with the provider's own tokenizer.
14
+ * The coverage figure uses the model-aware estimator. Estimator drift itself is
15
+ * measured separately (tokenizer-measure.ts) on content the transcript DOES
16
+ * hold: replies whose output_tokens are exact, and single large blocks whose
17
+ * size is the exact prompt growth. Until 0.19 this command attributed the whole
18
+ * gap to invisible content; about a third of it was the estimator undercounting
19
+ * Claude's tokenizer.
18
20
  */
19
21
  export interface AccuracyReport {
20
22
  sessionsScanned: number;
package/dist/accuracy.js CHANGED
@@ -11,10 +11,12 @@
11
11
  * measures the distance between them on your own sessions, so "why is my bill
12
12
  * bigger than the profile?" has an answer with evidence behind it.
13
13
  *
14
- * WHAT THIS IS NOT: a tokenizer benchmark. It cannot be — the content behind
15
- * the gap is unavailable to us, so the gap cannot be attributed to estimator
16
- * drift. To measure the estimator itself, use `analyze --exact`, which counts
17
- * the same bytes with the provider's own tokenizer.
14
+ * The coverage figure uses the model-aware estimator. Estimator drift itself is
15
+ * measured separately (tokenizer-measure.ts) on content the transcript DOES
16
+ * hold: replies whose output_tokens are exact, and single large blocks whose
17
+ * size is the exact prompt growth. Until 0.19 this command attributed the whole
18
+ * gap to invisible content; about a third of it was the estimator undercounting
19
+ * Claude's tokenizer.
18
20
  */
19
21
  import { estimateTokens, formatTokens, MESSAGE_OVERHEAD_TOKENS } from "./tokens.js";
20
22
  import { parseConversation } from "./parse.js";
@@ -57,7 +59,7 @@ export function measureAccuracy(limit = 20, paths) {
57
59
  const normalized = parseConversation(parsed.conversationJson).messages;
58
60
  const estimateAt = (i) => {
59
61
  const m = normalized[i];
60
- return m ? estimateTokens(m.text) + MESSAGE_OVERHEAD_TOKENS : 0;
62
+ return m ? estimateTokens(m.text, parsed.model) + MESSAGE_OVERHEAD_TOKENS : 0;
61
63
  };
62
64
  let baseline = usage[0].input;
63
65
  for (let i = 0; i < usage[0].index; i++)
@@ -118,7 +120,7 @@ export function renderAccuracy(report) {
118
120
  lines.push("still saves real money — it just starts from a higher floor than the profile");
119
121
  lines.push("implies.");
120
122
  lines.push("");
121
- lines.push("This does NOT measure tokenizer drift: the missing content is not available");
122
- lines.push("to compare against. For that, use `analyze --exact` (provider tokenizer).");
123
+ lines.push("The tokenizer check below measures the estimator itself, on content the");
124
+ lines.push("transcript does hold; `analyze --exact` checks it against the provider's tokenizer.");
123
125
  return lines.join("\n");
124
126
  }
@@ -0,0 +1,128 @@
1
+ /**
2
+ * Autopilot: clear stale tool output from agent requests in flight, without
3
+ * ever costing more than it saves.
4
+ *
5
+ * Why this and not the proxy's general strategies: replayed over 33 real
6
+ * Claude Code sessions with prompt-cache pricing, the general trim saved 6.7%
7
+ * overall but made one session 13% MORE expensive, because every change to
8
+ * history the cache already holds re-bills everything after it at the write
9
+ * rate. Most of the bill sits in long sessions (94% of input cost came from
10
+ * requests above 200k tokens), so the win is real, but only if each change
11
+ * pays for its cache rewrite.
12
+ *
13
+ * The rules, modelled on the microcompact logic Claude Code ships (and keeps
14
+ * switched off by a server flag), tightened for "never worse":
15
+ *
16
+ * 1. Only output of read-only, re-runnable tools is eligible (file reads,
17
+ * shell, search, web fetch). Answers the user gave, subagent reports and
18
+ * MCP results are never touched.
19
+ * 2. The most recent `keepRecent` tool results always stay.
20
+ * 3. Clearing happens in batches of at least `minClearTokens`, so each cache
21
+ * rewrite buys a lot.
22
+ * 4. A cleared result stays cleared on every later request (ids are
23
+ * remembered, across restarts), so the prefix is byte-stable between
24
+ * batches and the cache keeps hitting.
25
+ * 5. By default a batch is taken ONLY when the cache is cold anyway (idle
26
+ * longer than the request's own cache TTL), because the whole prompt is
27
+ * re-written on that request regardless, so clearing costs nothing and
28
+ * every later request is smaller. That makes it never-worse by
29
+ * construction, and measured: replaying every Claude Code session on the
30
+ * author's machine through this class, with real timestamps and cache
31
+ * pricing, it saved 9.8% of cache-weighted input cost (9.2% of raw input
32
+ * tokens, up to 37.7% in one long session) and made no session worse.
33
+ * Clearing on a warm cache when the saving "should" repay the rewrite
34
+ * (`paybackSafety` > 0) added 0.1% and made one session 0.13% worse: the
35
+ * future is not knowable, so it is off unless asked for.
36
+ *
37
+ * The model sees a one-line note in place of the output and can simply re-run
38
+ * the tool; the file, command or URL is still in the tool call.
39
+ */
40
+ /** Tools whose output can be regenerated by calling them again (read-only or re-runnable). */
41
+ export declare const CLEARABLE_TOOLS: Set<string>;
42
+ export interface AutoClearOptions {
43
+ /** Most recent tool results that always stay. Default 3. */
44
+ keepRecent?: number;
45
+ /** Smallest batch worth a cache rewrite. Default 20,000 tokens. */
46
+ minClearTokens?: number;
47
+ /**
48
+ * Warm-cache rule: clear only if the saving over the requests still to come
49
+ * repays the rewrite this many times over. Requests still to come are
50
+ * estimated as the number already made in this conversation. Default 0:
51
+ * warm clearing off, cold cache only (the never-worse setting).
52
+ */
53
+ paybackSafety?: number;
54
+ /** Where cleared ids persist so a restart does not un-clear (and re-bill) them. */
55
+ statePath?: string;
56
+ }
57
+ export interface AutoClearResult {
58
+ changed: boolean;
59
+ /** Results cleared by this request's new batch (0 when none was taken). */
60
+ newlyCleared: number;
61
+ /** Tokens removed from this request, old batches included. */
62
+ tokensRemoved: number;
63
+ cold: boolean;
64
+ reason: string;
65
+ /** Index of the first message rewritten on this request (the cache breaks from here), or -1. */
66
+ firstChanged: number;
67
+ }
68
+ interface Found {
69
+ /** Index of the message / input item that holds the result (cache breaks from here). */
70
+ unit: number;
71
+ id: string;
72
+ tokens: number;
73
+ content: unknown;
74
+ set: (note: string) => void;
75
+ }
76
+ /**
77
+ * One request, whatever its API: the units that make up history, tool names
78
+ * by call id, the tool results in order, and the cache lifetime to assume.
79
+ * Anthropic Messages (Claude Code, Anthropic SDKs), OpenAI Chat Completions
80
+ * (role "tool" messages) and OpenAI Responses (function_call_output items,
81
+ * as Codex with an API key sends).
82
+ */
83
+ interface View {
84
+ format: "anthropic" | "openai-chat" | "openai-responses";
85
+ units: Array<Record<string, unknown>>;
86
+ toolName: Map<string, string>;
87
+ results: Array<Omit<Found, "tokens">>;
88
+ firstUser: unknown;
89
+ firstToolId: string;
90
+ ttlMs: number;
91
+ }
92
+ export declare function clearedNote(tokens: number): string;
93
+ /**
94
+ * How long the provider may keep this prompt cached, taking the LONGER
95
+ * possibility when unsure: calling a warm cache cold would pay a rewrite,
96
+ * calling a cold one warm only delays a free clearing.
97
+ * - Anthropic: the request's own cache_control, "1h" or the 5-minute default.
98
+ * - OpenAI: caches live up to an hour (in-memory retention), or 24 hours when
99
+ * the request sets prompt_cache_retention "24h".
100
+ */
101
+ export declare function requestTtlMs(body: Record<string, unknown>): number;
102
+ export declare function viewOf(body: Record<string, unknown>): View | undefined;
103
+ export declare class AutoClearer {
104
+ private cleared;
105
+ /** Tool results never change once sent, so their size is computed once per id. */
106
+ private sizes;
107
+ private convs;
108
+ private opts;
109
+ private dirty;
110
+ constructor(options?: AutoClearOptions);
111
+ /**
112
+ * Rewrite `body` in place: an Anthropic Messages, OpenAI Chat Completions or
113
+ * OpenAI Responses request. Never throws; anything unrecognised is left alone.
114
+ */
115
+ apply(body: Record<string, unknown>, now?: number): AutoClearResult;
116
+ /**
117
+ * Warm cache: clearing rewrites everything after the first cleared result
118
+ * at the write rate (1.25x instead of 0.1x: 1.15x extra, once), and saves
119
+ * the cleared tokens at the read rate (0.1x) on every later request.
120
+ * Later requests are estimated as the number made so far (a conversation's
121
+ * age is the best predictor of how long it keeps going), and the saving must
122
+ * cover the rewrite `paybackSafety` times over.
123
+ */
124
+ private shouldClearWarm;
125
+ private load;
126
+ private save;
127
+ }
128
+ export {};