context-carwash 0.1.0 → 0.1.2

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
@@ -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 leaves your machine.
8
+ Nothing happens automatically, and nothing leaves your machine. The one exception: the installer asks npm whether a newer version is out.
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
 
@@ -18,7 +18,7 @@ npx context-carwash preview # sample meters in your terminal, nothing instal
18
18
  npx context-carwash install # lists what it will change, then asks
19
19
  ```
20
20
 
21
- To update later, run `npx context-carwash@latest install` again. Waiting briefs are kept.
21
+ To update later, run `npx context-carwash@latest install` again. Waiting briefs are kept. The `@latest` matters: without it npx may rerun the copy it downloaded last time. If it does, the installer says a newer version is out before asking to go ahead.
22
22
 
23
23
  The installer copies itself to `~/.carwash/app` and makes three changes to Claude Code, all listed before you confirm:
24
24
 
@@ -31,16 +31,16 @@ Start a new Claude Code session afterwards. The first one explains the meter and
31
31
  ## The meter
32
32
 
33
33
  ```text
34
- context ~136k [██████████░░░░░] /wash to compress & save context, then /clear
34
+ session context ~136k [██████████░░░░░] /wash to compress & save context, then /clear keeps compressed context
35
35
  ```
36
36
 
37
37
  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:
38
38
 
39
39
  ```text
40
- context ~150k [███████████░░░░] getting full · /wash to compress & save context, then /clear
40
+ session context ~150k [███████████░░░░] getting full · /wash to compress & save context, then /clear keeps compressed context
41
41
  ```
42
42
 
43
- Narrower windows show less: the sentence shortens first (`/wash to compress, then /clear` fits an 80-column window), then the bar goes.
43
+ 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.
44
44
 
45
45
  A full meter is information. It never triggers anything.
46
46
 
@@ -76,7 +76,7 @@ Things worth knowing:
76
76
 
77
77
  ## Where things are kept
78
78
 
79
- Briefs are stored in `~/.carwash`, readable only by you, and never inside your project. Nothing is uploaded, and there is no telemetry.
79
+ Briefs are stored in `~/.carwash`, readable only by you, and never inside your project. Nothing is uploaded, and there is no telemetry. The installer reads the newest version number from npm, and nothing else goes online; set `CARWASH_NO_UPDATE_CHECK=1` to skip even that.
80
80
 
81
81
  If a hook fails it stays silent and writes the error to `~/.carwash/errors.log`.
82
82
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "context-carwash",
3
- "version": "0.1.0",
3
+ "version": "0.1.2",
4
4
  "description": "A quiet context meter and deliberate fresh-session handoff for Claude Code.",
5
5
  "license": "MIT",
6
6
  "repository": {
package/src/cli.mjs CHANGED
@@ -2,6 +2,7 @@ import { appendFileSync, existsSync, statSync, writeFileSync } from 'node:fs';
2
2
  import { createInterface } from 'node:readline';
3
3
  import { applyInstall, applyUninstall, describeInstall, describeUninstall, packageVersion, planInstall, planUninstall } from './install/install.mjs';
4
4
  import { UserError } from './install/settings.mjs';
5
+ import { latestVersion, updateNotice } from './install/update.mjs';
5
6
  import { errorLogPath, welcomedPath } from './paths.mjs';
6
7
  import { render } from './statusline/render.mjs';
7
8
  import { statusline } from './statusline/run.mjs';
@@ -24,7 +25,7 @@ Inside Claude Code, once installed:
24
25
  /wash load [id] Make a waiting brief the one /clear will use
25
26
  /wash help Explain the meter and the options
26
27
 
27
- Nothing happens automatically, and nothing leaves this machine.`;
28
+ Nothing happens automatically. Only install goes online, to ask npm for the newest version.`;
28
29
 
29
30
  async function readAll(stream) {
30
31
  if (stream.isTTY) return '';
@@ -87,7 +88,7 @@ function logError(env, error) {
87
88
  }
88
89
  }
89
90
 
90
- export async function main(argv, { env = process.env, stdin = process.stdin, stdout = process.stdout, stderr = process.stderr } = {}) {
91
+ export async function main(argv, { env = process.env, stdin = process.stdin, stdout = process.stdout, stderr = process.stderr, latest = latestVersion } = {}) {
91
92
  const [command, ...rest] = argv;
92
93
  const yes = rest.includes('--yes') || rest.includes('-y');
93
94
  try {
@@ -108,6 +109,9 @@ export async function main(argv, { env = process.env, stdin = process.stdin, std
108
109
  case 'install': {
109
110
  const plan = planInstall({ env });
110
111
  stdout.write(describeInstall(plan) + '\n\n');
112
+ // Last, so it sits right above the question it changes the answer to.
113
+ const notice = updateNotice(packageVersion(), await latest({ env }));
114
+ if (notice) stdout.write(notice + '\n\n');
111
115
  if (!(await confirm('Proceed?', { yes, stdin, stdout }))) {
112
116
  stdout.write('Nothing was changed.\n');
113
117
  return 1;
@@ -0,0 +1,44 @@
1
+ // Whether a newer Context Carwash is on npm. Only the installer asks, since
2
+ // npx can quietly rerun a copy it downloaded earlier. The meter and /wash never
3
+ // use the network.
4
+
5
+ const LATEST_URL = 'https://registry.npmjs.org/context-carwash/latest';
6
+ const TIMEOUT_MS = 3000;
7
+
8
+ const parse = (version) => {
9
+ const found = /^(\d+)\.(\d+)\.(\d+)$/.exec(String(version ?? '').trim());
10
+ return found ? found.slice(1).map(Number) : null;
11
+ };
12
+
13
+ // True when `latest` is a later release than `current`. Anything unreadable is
14
+ // not newer.
15
+ export function isNewer(latest, current) {
16
+ const a = parse(latest);
17
+ const b = parse(current);
18
+ if (!a || !b) return false;
19
+ for (let i = 0; i < 3; i++) if (a[i] !== b[i]) return a[i] > b[i];
20
+ return false;
21
+ }
22
+
23
+ // The version npm calls latest, or null when it cannot say in time. Offline,
24
+ // slow or opted out (CARWASH_NO_UPDATE_CHECK) all mean no answer, never an error.
25
+ export async function latestVersion({ env = process.env, timeoutMs = TIMEOUT_MS } = {}) {
26
+ if (env.CARWASH_NO_UPDATE_CHECK) return null;
27
+ try {
28
+ const response = await fetch(LATEST_URL, { signal: AbortSignal.timeout(timeoutMs), headers: { accept: 'application/json' } });
29
+ if (!response.ok) return null;
30
+ const { version } = await response.json();
31
+ return parse(version) ? version : null;
32
+ } catch {
33
+ return null;
34
+ }
35
+ }
36
+
37
+ // What the installer says before asking, or null when this is the latest.
38
+ export function updateNotice(current, latest) {
39
+ if (!isNewer(latest, current)) return null;
40
+ return [
41
+ `A newer Context Carwash is out: ${latest}. This installer is ${current}.`,
42
+ 'To install the newer one, answer no and run: npx context-carwash@latest install',
43
+ ].join('\n');
44
+ }
@@ -12,10 +12,13 @@ const FULL = 'getting full';
12
12
 
13
13
  // What /wash does, said plainly. The levels and their caps are left to the
14
14
  // command menu and /wash help, which show them when someone is choosing. The
15
- // middle form keeps the meaning in an 80-column window, the Terminal.app default.
15
+ // full form needs a wide window, the long one fits about half a Mac screen, and
16
+ // the middle one keeps the /clear step in an 80-column window, the Terminal.app
17
+ // default.
18
+ const HINT_FULL = '/wash to compress & save context, then /clear keeps compressed context';
16
19
  const HINT_LONG = '/wash to compress & save context, then /clear';
17
20
  const HINT_MID = '/wash to compress, then /clear';
18
- const HINT_SHORT = '/wash, then /clear';
21
+ const HINT_SHORT = '/wash to compress';
19
22
 
20
23
  const finite = (v) => (typeof v === 'number' && Number.isFinite(v) ? v : null);
21
24
 
@@ -97,7 +100,7 @@ export function render(payload, options = {}) {
97
100
  // The reading is the tokens in use, and nothing else: a count is something
98
101
  // people can picture, and the bar already shows how much of the window that
99
102
  // is, so neither a percentage nor the window size is spelled out.
100
- const usedSeg = `context ${used ? `~${formatTokens(used)}` : '--'}`;
103
+ const usedSeg = `session context ${used ? `~${formatTokens(used)}` : '--'}`;
101
104
  const barSeg = bar(pct, depth, options.barStyle ?? 'ramp');
102
105
  const caps = capLabels(used);
103
106
  // Only while inviting a wash: once one is under way there is nothing to add.
@@ -112,8 +115,8 @@ export function render(payload, options = {}) {
112
115
  candidates = [
113
116
  [usedSeg, barSeg, `${ready}. Old session stays in /resume.`],
114
117
  [usedSeg, barSeg, ready],
115
- [usedSeg, barSeg, 'Brief ready ✓ · type /clear'],
116
- [usedSeg, 'Brief ready ✓ · type /clear'],
118
+ [usedSeg, barSeg, 'Brief ready ✓ · /clear to start fresh'],
119
+ [usedSeg, 'Brief ready ✓ · /clear to start fresh'],
117
120
  [usedSeg, 'Brief ready ✓'],
118
121
  ];
119
122
  } else if (options.state?.washing) {
@@ -130,6 +133,7 @@ export function render(payload, options = {}) {
130
133
  ];
131
134
  } else {
132
135
  candidates = [
136
+ [usedSeg, barSeg, say(HINT_FULL)],
133
137
  [usedSeg, barSeg, say(HINT_LONG)],
134
138
  [usedSeg, barSeg, say(HINT_MID)],
135
139
  [usedSeg, barSeg, say(HINT_SHORT)],
@@ -111,10 +111,10 @@ export function loadedMessage(record, now, stale) {
111
111
  // Shown once, at the first session after install.
112
112
  export function welcomeMessage() {
113
113
  return [
114
- 'Context Carwash is installed. This note is shown once.',
115
- 'The status line shows how many tokens this conversation is using, and the bar shows how close that is to the model\'s limit. It changes colour as it fills and says "getting full" from three quarters on.',
116
- 'To start fresh without losing your place: /wash to compress & save context into a short brief, then /clear. The new session begins with the brief, and the old one stays in /resume.',
117
- 'Nothing happens unless you ask. /wash help shows the options.',
114
+ 'Context Carwash installed ✓ (shown once)',
115
+ ' • Status line: tokens in use, and how full the context is',
116
+ ' • Getting full? /wash, then /clear. You start fresh, right where you left off',
117
+ ' • Nothing runs on its own · /wash help for options',
118
118
  ].join('\n');
119
119
  }
120
120