context-carwash 0.1.6 → 0.1.8

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/CHANGELOG.md CHANGED
@@ -1,5 +1,14 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.1.8
4
+
5
+ - Shorter, clearer README.
6
+
7
+ ## 0.1.7
8
+
9
+ - `carwash doctor` checks that the Node Carwash runs with is still there, and says how to fix it if not. Removing that Node, for example an old version in nvm, stops the meter and `/wash` until install is run again. The installer now says which Node that is.
10
+ - Clearer wording on when Carwash goes online, and a reminder to look over `doctor` output before posting it in a public issue.
11
+
3
12
  ## 0.1.6
4
13
 
5
14
  - Update notices: once a day, a new session checks npm in the background. A line under the meter says when a newer version is out, or when the version you have carries a warning. `CARWASH_NO_UPDATE_CHECK=1` turns it off.
package/README.md CHANGED
@@ -1,34 +1,22 @@
1
1
  # Context Carwash
2
2
 
3
- A small, local-first utility for Claude Code.
3
+ Context Carwash adds a line to Claude Code in your terminal that:
4
4
 
5
- - **Meter:** a quiet estimate of the active context window in the status line.
6
- - **Wash:** when you ask, Claude writes a short handoff brief, and your next `/clear` starts a fresh session that begins with it.
7
-
8
- Nothing happens automatically, and nothing about you is ever sent. Carwash goes online in two ways: once a day it reads its own public page on npm to see if a newer version is out (you can turn that off), and `carwash stats --share` sends anonymous totals if you run it and say yes.
9
-
10
- **Status:** pre-release. The meter and the typed `/wash` command work today. Clickable links in the status line and the one-step restart are not built yet. See [plan-3.md](plan-3.md) for the design.
5
+ 1. Shows the current conversation's context size.
6
+ 2. Lets you compress your context and start a new session without losing the important details.
11
7
 
12
8
  ## Install
13
9
 
14
10
  Requires Node 18 or later.
15
11
 
16
12
  ```sh
17
- npx context-carwash preview # sample meters in your terminal, nothing installed
13
+ npx context-carwash preview # see sample meters, nothing installed
18
14
  npx context-carwash install # lists what it will change, then asks
19
15
  ```
20
16
 
21
- To update later, run `npx context-carwash@latest install`. Waiting briefs are kept. If npx reruns an older copy it downloaded before, the installer notices that npm has a newer version and installs that one instead.
22
-
23
- You do not have to watch for new versions. Once a day, when a session starts, Carwash checks npm in the background. If a newer version is out, or the version you have has a known problem, a line under the meter says so, with the command to update. `/wash help` shows the version you have.
17
+ Then start a new Claude Code session. To update later, run `npx context-carwash@latest install`. You don't need to watch for updates: a line under the meter tells you when there's a new version.
24
18
 
25
- The installer copies itself to `~/.carwash/app` and makes three changes to Claude Code, all listed before you confirm:
26
-
27
- - **Status line** in `~/.claude/settings.json`. If you already have one, yours keeps running and the meter is added after it.
28
- - **Three hooks** in the same file, added after any hooks you already have. They are how `/wash` saves a brief and how a fresh session receives it.
29
- - **The `/wash` command** at `~/.claude/skills/wash`.
30
-
31
- Start a new Claude Code session afterwards. The first one explains the meter and `/wash` in a short note, shown once.
19
+ The installer adds a status line and three hooks to `~/.claude/settings.json`, after anything you already have there, and adds the `/wash` command at `~/.claude/skills/wash`.
32
20
 
33
21
  ## The meter
34
22
 
@@ -36,22 +24,14 @@ Start a new Claude Code session afterwards. The first one explains the meter and
36
24
  session context ~136k [██████████░░░░░] /wash to compress & save context, then /clear keeps compressed context
37
25
  ```
38
26
 
39
- The number is how many tokens the conversation is using. It comes from Claude Code and is an estimate. The bar shows how close that is to the most the model's context window holds, and changes colour as it fills. From three quarters on, the meter also says so in words:
40
-
41
- ```text
42
- session context ~150k [███████████░░░░] getting full · /wash to compress & save context, then /clear keeps compressed context
43
- ```
44
-
45
- Narrower windows show less. The sentence shortens first: `/wash to compress & save context, then /clear` fits about half a Mac screen, `/wash to compress, then /clear` fits an 80-column window, and the narrowest windows show `/wash to compress`. Then the bar goes.
27
+ The number is how many tokens the conversation is using, as estimated by Claude Code. The bar shows how full the context window is and changes colour as it fills. In narrow windows the text gets shorter. The meter never does anything on its own.
46
28
 
47
- A full meter is information. It never triggers anything.
29
+ ## /wash
48
30
 
49
- ## Starting fresh with a brief
31
+ 1. Type `/wash`. Claude writes a short brief: what you're doing, where things stand, and what to do next. The status line changes to `Brief ready ✓`.
32
+ 2. Type `/clear`. The new session starts with that brief already loaded.
50
33
 
51
- 1. Type `/wash` (or `/wash light`, `/wash detailed`). Claude replies with a brief: what you are doing, where things stand, and what to do next. The status line changes to `Brief ready ✓`.
52
- 2. Type `/clear`. The new session starts with that brief already loaded, and says so: `Loaded your Carwash brief (mid ~1.1k, 2m ago), down from ~136k in the old session.`
53
-
54
- Your old conversation is not deleted. It stays in `/resume`.
34
+ Your old conversation stays in `/resume`.
55
35
 
56
36
  | Command | What it does |
57
37
  |---|---|
@@ -61,21 +41,15 @@ Your old conversation is not deleted. It stays in `/resume`.
61
41
  | `/wash detailed` | Adds rationale, history, test results, risks, a step checklist |
62
42
  | `/wash discard` | Drop the brief. `/clear` is a normal clear again |
63
43
  | `/wash load [id]` | Make a waiting brief the one your next `/clear` uses |
64
- | `/wash stats` | How much context your washes have saved (see below) |
65
- | `/wash help` | Explain the meter and these options, with this session's size limits |
66
-
67
- Each level has a size limit: the most tokens its brief may take, worked out from what the conversation is using (a hundredth for `light`, three hundredths for `mid`, a tenth for `detailed`). `/wash help` shows the limits for the session you are in.
68
-
69
- Words after the level are passed to Claude as a note on what to emphasise: `/wash mid focus on the failing test`.
44
+ | `/wash stats` | How much context your washes have saved |
45
+ | `/wash help` | Explain the meter and these options |
70
46
 
71
- Things worth knowing:
72
-
73
- - **A `/clear` with no brief ready is a normal clear.** Carwash only acts when you have asked for a brief in that terminal.
74
- - **Writing a brief costs tokens.** It is an ordinary Claude reply. `help`, `discard` and `load` cost nothing: they never reach Claude.
75
- - **The cap is a ceiling, not a target.** A brief is as short as it can be. If one comes out over its cap, Claude is asked to shorten it, up to twice. If it is still over, it is kept and the message says so.
76
- - **Sizes are estimates.** The brief's size is worked out from its length. It covers the brief alone, so the meter in the new session reads higher: it also counts Claude Code's own baseline.
77
- - **Closed the terminal before clearing?** Start `claude` in the same folder. If exactly one brief is waiting for that folder, it is under 12 hours old, and the session that wrote it has ended, it is loaded. If there is any doubt about which brief is meant, nothing is loaded and you are shown what is waiting, with the `/wash load <id>` to use.
78
- - **Briefs expire after 48 hours.** A delivered brief is kept for 24 hours in case you want it again (`/wash load <id>`), then deleted.
47
+ - Words after the level tell Claude what to focus on: `/wash mid focus on the failing test`.
48
+ - A `/clear` with no brief ready is a normal clear.
49
+ - Writing a brief is an ordinary Claude reply, so it uses tokens. `help`, `discard` and `load` don't.
50
+ - Each level has a size limit based on how big the conversation is. `/wash help` shows them.
51
+ - Closed the terminal before `/clear`? Start `claude` in the same folder and the brief loads. If more than one is waiting, you're shown which ones, with the `/wash load <id>` to pick one.
52
+ - Briefs expire after 48 hours.
79
53
 
80
54
  ## Stats
81
55
 
@@ -94,19 +68,9 @@ Carwash stats, since 3 Oct 2026 (12 days)
94
68
  Net saved ~20M tokens
95
69
  ```
96
70
 
97
- `/wash stats` shows the same inside Claude Code, at no cost.
98
-
99
- How it is worked out: every request to Claude carries the whole conversation, and a single prompt can make many requests while Claude uses tools. After a wash, each request in the fresh session starts from the brief instead of the old conversation. Claude Code's transcript of that session records the size of every request, so Carwash counts the requests and multiplies by the gap. Nothing is assumed about how long you would have kept going: only requests you actually made count. Counting stops where the old session would have compacted on its own.
100
-
101
- Claude caches repeated context and charges about a tenth for it, so in money terms the saving is roughly a tenth of these numbers. The brief cost counts every brief written, used or not.
71
+ Every request to Claude re-sends the whole conversation. After a wash, each request starts from the small brief instead, so Carwash counts the requests you actually made and adds up the difference. Claude charges about a tenth for repeated context, so the money saved is roughly a tenth of these numbers. `/wash stats` shows the same inside Claude Code.
102
72
 
103
- ### Sharing your stats
104
-
105
- ```sh
106
- npx context-carwash stats --share
107
- ```
108
-
109
- This shows every field it would send, and sends only if you say yes. It sends totals to a Google Form: a random share id made for sharing only (so sharing again replaces your last share), the Carwash version, your operating system, days since your first brief, and the numbers above. No names, folders, projects, prompts or briefs. You are not signed in to send it.
73
+ `npx context-carwash stats --share` sends your anonymous totals, but only after showing you exactly what it will send and you say yes.
110
74
 
111
75
  ## Something not working?
112
76
 
@@ -114,15 +78,11 @@ This shows every field it would send, and sends only if you say yes. It sends to
114
78
  npx context-carwash doctor
115
79
  ```
116
80
 
117
- This checks that the status line, hooks and `/wash` command are still in place, and shows your Carwash, Node, Claude Code and operating system versions, the newest version on npm, and the last few errors. Paste it into a [new issue](https://github.com/Spobi/context-carwash/issues/new).
118
-
119
- If a hook fails, the status line shows `Carwash hit an error` until you run `doctor`.
81
+ This checks that everything is still installed and working, and shows your versions and recent errors. Paste the output into a [new issue](https://github.com/Spobi/context-carwash/issues/new). Issues are public, and errors can include folder names, so look it over first.
120
82
 
121
83
  ## Where things are kept
122
84
 
123
- Briefs are stored in `~/.carwash`, readable only by you, and never inside your project. So is the stats ledger (`~/.carwash/stats`): one small file per brief with its size, its level, and which fresh sessions it went to, but never the brief or your prompts. Nothing about you is uploaded, and there is no telemetry. Once a day, a new session reads Carwash's public page on npm: the newest version, and any warning about the one you have. That is a download, like opening a web page, and it sends nothing about you, your projects or your use. The answer is kept in `~/.carwash/notice.json`. `install` and `doctor` read the same page when you run them. Set `CARWASH_NO_UPDATE_CHECK=1` to turn all of this off. `stats --share` sends only when you run it and say yes.
124
-
125
- If a hook fails it stays silent, writes the error to `~/.carwash/errors.log`, and the status line says so.
85
+ Briefs, stats and error logs are kept in `~/.carwash`, readable only by you. Nothing about you is uploaded. Once a day Carwash checks npm for a newer version. Set `CARWASH_NO_UPDATE_CHECK=1` to turn that off.
126
86
 
127
87
  ## Uninstall
128
88
 
@@ -132,12 +92,6 @@ npx context-carwash uninstall
132
92
 
133
93
  This puts `settings.json` back exactly as it was. If you have edited the file since installing, it removes only Carwash's own status line and hooks and keeps your edits. It then removes the `/wash` command and deletes `~/.carwash`, including any briefs still waiting and your stats.
134
94
 
135
- ## Limitations
136
-
137
- - Terminal.app cannot click links. When clickable links arrive for other terminals, Terminal.app will keep showing the command to type.
138
- - The status line re-runs every 5 seconds while a session is open, so it stays correct after `/wash discard` and `/wash load`. Each run reads a few small local files. If your own status line sets a refresh interval, yours is kept.
139
- - Claude Code shows `help`, `discard`, `load`, and mistyped arguments under the heading "operation blocked by hook". That is Carwash answering the command itself instead of sending it to Claude.
140
-
141
95
  ## Platforms
142
96
 
143
97
  Developed and tested on macOS. Automated tests also run on Linux and Windows; neither has been tried by hand yet.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "context-carwash",
3
- "version": "0.1.6",
3
+ "version": "0.1.8",
4
4
  "description": "A quiet context meter and deliberate fresh-session handoff for Claude Code.",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -6,7 +6,7 @@ import { readJson } from '../files.mjs';
6
6
  import { errorLogPath, errorsSeenPath, installRecordPath, settingsPath, skillDir } from '../paths.mjs';
7
7
  import { refreshLedger, totals } from '../stats/ledger.mjs';
8
8
  import { readyRecords } from '../wash/store.mjs';
9
- import { isCarwashCommand, isCarwashHook } from './install.mjs';
9
+ import { commandProgram, isCarwashCommand, isCarwashHook } from './install.mjs';
10
10
  import { UPDATE_COMMAND } from './notice.mjs';
11
11
  import { readSettings, resolveSettingsFile } from './settings.mjs';
12
12
  import { isNewer } from './update.mjs';
@@ -59,7 +59,19 @@ function settingsCheck(env) {
59
59
  }
60
60
  const groups = (event) => (Array.isArray(data?.hooks?.[event]) ? data.hooks[event] : []);
61
61
  const hooks = Object.fromEntries(HOOK_EVENTS.map((event) => [event, groups(event).some((group) => Array.isArray(group?.hooks) && group.hooks.some((hook) => isCarwashHook(hook?.command)))]));
62
- return { readable: true, statusLine: isCarwashCommand(data?.statusLine?.command), hooks };
62
+ const hookCommands = HOOK_EVENTS.flatMap((event) => groups(event).flatMap((group) => (Array.isArray(group?.hooks) ? group.hooks.map((hook) => hook?.command) : [])));
63
+ const ours = [data?.statusLine?.command, ...hookCommands].filter((command) => isCarwashCommand(command) || isCarwashHook(command));
64
+ return { readable: true, statusLine: isCarwashCommand(data?.statusLine?.command), hooks, nodes: [...new Set(ours.map(commandProgram).filter(Boolean))] };
65
+ }
66
+
67
+ // The Node that Carwash's commands start. Install writes its path into the
68
+ // settings, so if that Node is removed later (version managers such as nvm
69
+ // keep one folder per version), nothing of Carwash runs, not even the part
70
+ // that would log an error. Plain `node` (Windows) is looked up on PATH.
71
+ function nodeRow(nodes) {
72
+ const missing = nodes.find((node) => /[\\/]/.test(node) && !existsSync(node));
73
+ if (missing) return `MISSING: ${tilde(missing)} · to fix: ${UPDATE_COMMAND}`;
74
+ return nodes.length ? `ok (${nodes.map(tilde).join(', ')})` : null;
63
75
  }
64
76
 
65
77
  const date = (iso) => {
@@ -100,6 +112,8 @@ export function doctorReport({ env = process.env, now = Date.now(), running, new
100
112
  } else {
101
113
  out.push(row('Status line', yes(check.statusLine)));
102
114
  out.push(row('Hooks', HOOK_EVENTS.map((event) => `${event} ${yes(check.hooks[event])}`).join(' · ')));
115
+ const node = nodeRow(check.nodes);
116
+ if (node) out.push(row('Node for Carwash', node));
103
117
  }
104
118
  out.push(row('/wash command', yes(existsSync(join(skillDir(env), 'SKILL.md')))));
105
119
  let waiting = '?';
@@ -123,6 +137,7 @@ export function doctorReport({ env = process.env, now = Date.now(), running, new
123
137
  if (!errors.length) out.push(`Errors: none logged.`);
124
138
  else out.push(`Last ${errors.length === 1 ? 'error' : `${errors.length} errors`} (from ${tilde(errorLogPath(env))}):`, '', ...errors.map((entry) => entry.replace(/^/gm, ' ')));
125
139
  out.push('', `Something wrong? Open an issue and paste everything above: ${ISSUES_URL}`);
140
+ out.push('Issues are public, so look it over first: error details can include folder names.');
126
141
  return out.join('\n');
127
142
  }
128
143
 
@@ -69,6 +69,16 @@ export function commandFor(verb, { nodePath, scriptPath, platform = process.plat
69
69
 
70
70
  export const statusLineCommand = (options) => commandFor('statusline', options);
71
71
 
72
+ // The program a command from commandFor starts, with its quoting undone: the
73
+ // Node it was installed with, or plain `node` on Windows.
74
+ export function commandProgram(command) {
75
+ const word = /^\s*('(?:[^']|'\\'')*'|"[^"]*"|[^\s'"]+)/.exec(String(command ?? ''))?.[1];
76
+ if (!word) return null;
77
+ if (word.startsWith("'")) return word.slice(1, -1).replace(/'\\''/g, "'");
78
+ if (word.startsWith('"')) return word.slice(1, -1);
79
+ return word;
80
+ }
81
+
72
82
  const runsCarwash = (verb) => (command) => typeof command === 'string' && new RegExp(`carwash\\.mjs['"]?\\s+${verb}\\s*$`).test(command);
73
83
  export const isCarwashCommand = runsCarwash('statusline');
74
84
  export const isCarwashHook = runsCarwash('hook');
@@ -178,7 +188,10 @@ export function planInstall({ env = process.env, platform = process.platform } =
178
188
  };
179
189
  const skill = planSkill(env);
180
190
 
181
- return { home, appDir, file, settings, record, original, alreadyInstalled, next, skill, keepsHooks: isPlainObject(theirHooks) && Object.keys(theirHooks).length > 0 };
191
+ // The Node the commands will run with. Windows finds `node` on PATH instead.
192
+ const node = platform === 'win32' ? null : script.nodePath;
193
+
194
+ return { home, appDir, file, settings, record, original, alreadyInstalled, next, skill, node, keepsHooks: isPlainObject(theirHooks) && Object.keys(theirHooks).length > 0 };
182
195
  }
183
196
 
184
197
  export function describeInstall(plan) {
@@ -202,6 +215,9 @@ export function describeInstall(plan) {
202
215
  }
203
216
  lines.push(` 3. Add the /wash command at ${tilde(plan.skill.dir)}`);
204
217
  lines.push('', 'Writing a brief is an ordinary Claude reply, so it uses tokens like any other.');
218
+ // Version managers such as nvm keep a folder per Node version, so removing
219
+ // an old version takes this path with it, and nothing of Carwash can start.
220
+ if (plan.node) lines.push(`Carwash runs with the Node at ${tilde(plan.node)}. If you remove that Node later, for example an old version in nvm, run install again.`);
205
221
  lines.push('Nothing else is touched. To undo everything: carwash uninstall');
206
222
  return lines.join('\n');
207
223
  }
@@ -1,9 +1,9 @@
1
1
  import { spawnSync } from 'node:child_process';
2
2
 
3
- // Whether a newer Context Carwash is on npm, and handing over to it. Only the
4
- // installer asks, since npx can quietly rerun a copy it downloaded earlier, and
5
- // people should not have to know to type @latest. The meter and /wash never
6
- // use the network.
3
+ // Whether a newer Context Carwash is on npm, and handing over to it, for the
4
+ // installer: npx can quietly rerun a copy it downloaded earlier, and people
5
+ // should not have to know to type @latest. The daily check behind the update
6
+ // notice is in notice.mjs. The meter and /wash never go online themselves.
7
7
 
8
8
  const LATEST_URL = 'https://registry.npmjs.org/context-carwash/latest';
9
9
  const TIMEOUT_MS = 3000;