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 +9 -0
- package/README.md +23 -69
- package/package.json +1 -1
- package/src/install/doctor.mjs +17 -2
- package/src/install/install.mjs +17 -1
- package/src/install/update.mjs +4 -4
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
|
-
|
|
3
|
+
Context Carwash adds a line to Claude Code in your terminal that:
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
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
|
|
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`.
|
|
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
|
|
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
|
|
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
|
-
|
|
29
|
+
## /wash
|
|
48
30
|
|
|
49
|
-
|
|
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
|
-
|
|
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
|
|
65
|
-
| `/wash help` | Explain the meter and these options
|
|
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
|
-
|
|
72
|
-
|
|
73
|
-
-
|
|
74
|
-
-
|
|
75
|
-
-
|
|
76
|
-
-
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
|
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
package/src/install/doctor.mjs
CHANGED
|
@@ -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
|
-
|
|
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
|
|
package/src/install/install.mjs
CHANGED
|
@@ -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
|
-
|
|
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
|
}
|
package/src/install/update.mjs
CHANGED
|
@@ -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
|
|
4
|
-
// installer
|
|
5
|
-
//
|
|
6
|
-
//
|
|
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;
|