context-carwash 0.1.6 → 0.1.7

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,10 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.1.7
4
+
5
+ - `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.
6
+ - Clearer wording on when Carwash goes online, and a reminder to look over `doctor` output before posting it in a public issue.
7
+
3
8
  ## 0.1.6
4
9
 
5
10
  - 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
@@ -5,7 +5,7 @@ A small, local-first utility for Claude Code.
5
5
  - **Meter:** a quiet estimate of the active context window in the status line.
6
6
  - **Wash:** when you ask, Claude writes a short handoff brief, and your next `/clear` starts a fresh session that begins with it.
7
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.
8
+ Nothing happens automatically, and nothing about you is ever sent. Carwash goes online only for these: once a day it reads its own public page on npm to see if a newer version is out (you can turn that off); `install` and `doctor` read the same page when you run them, and `install` downloads the newer version if there is one; and `carwash stats --share` sends anonymous totals if you run it and say yes.
9
9
 
10
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.
11
11
 
@@ -30,6 +30,8 @@ The installer copies itself to `~/.carwash/app` and makes three changes to Claud
30
30
 
31
31
  Start a new Claude Code session afterwards. The first one explains the meter and `/wash` in a short note, shown once.
32
32
 
33
+ The status line and hooks run with the Node you installed with. If you later remove that Node, for example an old version in nvm, they stop working until you run install again. `carwash doctor` points this out.
34
+
33
35
  ## The meter
34
36
 
35
37
  ```text
@@ -114,7 +116,7 @@ This shows every field it would send, and sends only if you say yes. It sends to
114
116
  npx context-carwash doctor
115
117
  ```
116
118
 
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).
119
+ This checks that the status line, hooks and `/wash` command are still in place and that the Node they run with is still there, 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). Issues are public, so look it over first: error details can include folder names.
118
120
 
119
121
  If a hook fails, the status line shows `Carwash hit an error` until you run `doctor`.
120
122
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "context-carwash",
3
- "version": "0.1.6",
3
+ "version": "0.1.7",
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;