claude-code-infinite 1.0.25 → 1.0.27

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.
Files changed (71) hide show
  1. package/README.md +72 -20
  2. package/dist/auto-update-runner.d.ts +13 -0
  3. package/dist/auto-update-runner.d.ts.map +1 -1
  4. package/dist/auto-update-runner.js +51 -7
  5. package/dist/auto-update-runner.js.map +1 -1
  6. package/dist/auto-update-setup.d.ts +19 -3
  7. package/dist/auto-update-setup.d.ts.map +1 -1
  8. package/dist/auto-update-setup.js +42 -16
  9. package/dist/auto-update-setup.js.map +1 -1
  10. package/dist/auto-update.d.ts +59 -6
  11. package/dist/auto-update.d.ts.map +1 -1
  12. package/dist/auto-update.js +207 -40
  13. package/dist/auto-update.js.map +1 -1
  14. package/dist/claude-auth.d.ts +55 -0
  15. package/dist/claude-auth.d.ts.map +1 -0
  16. package/dist/claude-auth.js +193 -0
  17. package/dist/claude-auth.js.map +1 -0
  18. package/dist/claude-env.d.ts +11 -4
  19. package/dist/claude-env.d.ts.map +1 -1
  20. package/dist/claude-env.js +11 -4
  21. package/dist/claude-env.js.map +1 -1
  22. package/dist/claude-locate.d.ts +80 -0
  23. package/dist/claude-locate.d.ts.map +1 -0
  24. package/dist/claude-locate.js +303 -0
  25. package/dist/claude-locate.js.map +1 -0
  26. package/dist/claude-plan-warning.d.ts +55 -0
  27. package/dist/claude-plan-warning.d.ts.map +1 -0
  28. package/dist/claude-plan-warning.js +95 -0
  29. package/dist/claude-plan-warning.js.map +1 -0
  30. package/dist/cli-lifecycle.d.ts +5 -0
  31. package/dist/cli-lifecycle.d.ts.map +1 -1
  32. package/dist/cli-lifecycle.js +13 -0
  33. package/dist/cli-lifecycle.js.map +1 -1
  34. package/dist/cli-main.js +124 -18
  35. package/dist/cli-main.js.map +1 -1
  36. package/dist/cli.js +7 -1
  37. package/dist/cli.js.map +1 -1
  38. package/dist/diagnostics-schema.d.ts +11 -1
  39. package/dist/diagnostics-schema.d.ts.map +1 -1
  40. package/dist/diagnostics-schema.js +38 -2
  41. package/dist/diagnostics-schema.js.map +1 -1
  42. package/dist/diagnostics.d.ts +10 -1
  43. package/dist/diagnostics.d.ts.map +1 -1
  44. package/dist/diagnostics.js +26 -1
  45. package/dist/diagnostics.js.map +1 -1
  46. package/dist/memtree-mcp.d.ts.map +1 -1
  47. package/dist/memtree-mcp.js +8 -5
  48. package/dist/memtree-mcp.js.map +1 -1
  49. package/dist/notices.d.ts.map +1 -1
  50. package/dist/notices.js +3 -2
  51. package/dist/notices.js.map +1 -1
  52. package/dist/proxy.d.ts.map +1 -1
  53. package/dist/proxy.js +7 -0
  54. package/dist/proxy.js.map +1 -1
  55. package/dist/reqlog.d.ts +7 -0
  56. package/dist/reqlog.d.ts.map +1 -1
  57. package/dist/reqlog.js.map +1 -1
  58. package/dist/resume-repair.d.ts +2 -0
  59. package/dist/resume-repair.d.ts.map +1 -1
  60. package/dist/resume-repair.js +7 -5
  61. package/dist/resume-repair.js.map +1 -1
  62. package/dist/update-check.d.ts +2 -0
  63. package/dist/update-check.d.ts.map +1 -1
  64. package/dist/update-check.js.map +1 -1
  65. package/dist/update-lock.js +3 -1
  66. package/dist/update-lock.js.map +1 -1
  67. package/dist/update-wait.d.ts +25 -0
  68. package/dist/update-wait.d.ts.map +1 -0
  69. package/dist/update-wait.js +63 -0
  70. package/dist/update-wait.js.map +1 -0
  71. package/package.json +1 -1
package/README.md CHANGED
@@ -4,7 +4,14 @@
4
4
 
5
5
  # Claude Code Infinite
6
6
 
7
- Memory for long-running agents. Claude Code Infinite indexes your Claude Code sessions with
7
+ <p align="center">
8
+ <a href="#long-session-benchmark"><img width="640" alt="Long-session benchmark, Claude Opus 5.5, graded blind: MemTree 69% at 39M tokens per run, full context 69% at 113M, /compact 49% at 17M" src="https://raw.githubusercontent.com/crizCraig/claude-code-infinite/main/docs/images/benchmark-chart.png"></a>
9
+ </p>
10
+ <p align="center"><sub>Long-session benchmark on a 775k-token Claude Code session (Claude Opus 5.5, graded blind):
11
+ MemTree matched full context at 69% with a third of the tokens; <code>/compact</code> scored 49%.
12
+ <a href="#long-session-benchmark">Full results</a></sub></p>
13
+
14
+ Infinite context for Claude Code: memory for long-running agents. Claude Code Infinite indexes your Claude Code sessions with
8
15
  [MemTree](https://memtree.dev) so their history can be easily searched, navigated, and understood,
9
16
  by you and by Claude, within a session and across all of them.
10
17
 
@@ -30,14 +37,8 @@ work: your priorities, the nuances of your projects, your company and your envir
30
37
  orchestrate dozens of subagents on your behalf.
31
38
 
32
39
  > [!IMPORTANT]
33
- > Requires a Claude Pro or Max subscription.
34
-
35
- <p align="center">
36
- <picture>
37
- <source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/crizCraig/claude-code-infinite/main/docs/images/benchmark-dark.svg">
38
- <img width="600" alt="Long-session benchmark, judged score out of 100: Claude Code Infinite 69, full context 69, /compact 49" src="https://raw.githubusercontent.com/crizCraig/claude-code-infinite/main/docs/images/benchmark-light.svg">
39
- </picture>
40
- </p>
40
+ > Install [Claude Code in your terminal](#setup) and sign in with Claude Pro or Max
41
+ > before running `ccc`. The Claude desktop app isn't enough.
41
42
 
42
43
  ## Search across sessions
43
44
 
@@ -82,19 +83,32 @@ tokens; `/compact`'s exclude the one-time `/compact` step. This is one task fami
82
83
 
83
84
  ## Requirements
84
85
 
86
+ * **[Claude Code in your terminal](https://code.claude.com/docs/en/quickstart), signed in
87
+ with Claude Pro or Max.** The Claude desktop app isn't enough: `ccc` needs the terminal
88
+ version.
85
89
  * [node.js 20.3 or newer](https://nodejs.org/en/download/)
86
- * [Claude Code (the terminal version)](https://code.claude.com/docs/en/quickstart)
87
- * **A Claude Pro or Max subscription**
88
90
 
89
- <sub>Also works with an Anthropic API key, but that's far costlier than Pro or Max.</sub>
91
+ <sub>Also works with an Anthropic API key that has credit, but that's far costlier than Pro or Max.</sub>
90
92
 
91
93
  ## Setup
92
94
 
93
- 1. Install (Node ≥ 20.3; no git needed)
95
+ 1. **Install Claude Code first** and sign in with Claude Pro or Max. The Claude desktop app
96
+ isn't enough: `ccc` needs the terminal version.
97
+ ```powershell
98
+ # Windows PowerShell
99
+ irm https://claude.ai/install.ps1 | iex
100
+ ```
101
+ ```bash
102
+ # macOS / Linux
103
+ curl -fsSL https://claude.ai/install.sh | bash
104
+ ```
105
+ Open a new terminal and run `claude` once to sign in
106
+ ([help](#claude-code-isnt-installed)).
107
+ 2. Install Claude Code Infinite (Node ≥ 20.3; no git needed)
94
108
  ```bash
95
109
  npm install -g claude-code-infinite
96
110
  ```
97
- 2. Run Claude Code Infinite with
111
+ 3. Run it from your project folder
98
112
  ```bash
99
113
  ccc
100
114
  ```
@@ -136,7 +150,7 @@ Environment variables, set when launching `ccc`:
136
150
  | `CCC_COMPACT_TARGET=500k` | Compaction target for every session (useful for headless `-p` runs); `off` starts every session with compaction off. |
137
151
  | `CCC_MEMTREE_MCP=0` | Turns off the `memtree` MCP tools ([details](#memory-tools)), in interactive and headless (`-p`) runs. |
138
152
  | `CCC_MEMTREE_LINK` | Where the MemTree link appears: `turn` (default, end of a turn with a new page), `message` (under every reply), `stop` (end of every turn), `success` (only after an optimized turn), or `off`. |
139
- | `CCC_AUTO_COMPACT=1` | Restores Claude Code's own auto-compaction setting ([details](#claude-code-auto-compaction)). |
153
+ | `CCC_AUTO_COMPACT=1` | Restores Claude Code's own auto-compaction and idle compaction settings ([details](#claude-code-auto-compaction)). |
140
154
  | `CCC_NO_UPDATE_CHECK=1` | Turns off the update check, and with it automatic updates ([details](#upgrading)). |
141
155
  | `CCC_DISABLE_AUTOUPDATE=1` | Keeps the update notice but never installs ([details](#automatic-updates)). |
142
156
  | `CCC_NO_DIAGNOSTICS=1` | Turns off diagnostics ([details](#diagnostics)). So do `DO_NOT_TRACK=1`, Claude Code's `DISABLE_TELEMETRY=1` or `CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC=1`, and `"diagnostics": false` in `~/.claude-code-infinite/config.json`. |
@@ -148,6 +162,12 @@ kitty, VS Code, JetBrains, Windows Terminal); elsewhere you get the plain URL.
148
162
 
149
163
  `ccc` disables Claude Code's automatic conversation compaction for the Claude process it launches. MemTree manages the context sent to the model, so letting Claude Code independently summarize the full local transcript can compact a conversation MemTree already reduced. Manual `/compact` remains available.
150
164
 
165
+ That includes idle compaction (Claude Code 2.1.286 and later), which summarizes a
166
+ long conversation while you're away, just before the prompt cache expires
167
+ ("Compacted while idle, before the prompt cache expired"). The
168
+ `DISABLE_AUTO_COMPACT=1` that `ccc` sets turns it off too, so you don't need
169
+ `"idleCompaction": false` in your settings.
170
+
151
171
  Claude Code 2.1.219 also recommends “Resume from summary” for any old session
152
172
  above a fixed 100k-token threshold, even when the active model has a 1M window
153
173
  and auto-compaction is disabled. `ccc` suppresses that resume-time
@@ -168,7 +188,8 @@ On `ccc --resume <id>`, `ccc` backs the transcript up to
168
188
  recorded usage to half the window, so the next message is sent and MemTree
169
189
  compresses it.
170
190
 
171
- To restore Claude Code's native auto-compaction setting for one invocation, use:
191
+ To restore Claude Code's native auto-compaction and idle compaction settings for one
192
+ invocation, use:
172
193
 
173
194
  ```bash
174
195
  CCC_AUTO_COMPACT=1 ccc
@@ -202,6 +223,10 @@ Claude Code ──▶ localhost proxy (ccc)
202
223
 
203
224
  - versions (ccc, Claude Code, Node), how ccc was installed (npm, npx, linked), OS and CPU
204
225
  architecture, terminal type and locale;
226
+ - how Claude Code is signed in, as a type only (Claude.ai subscription, API key, Bedrock,
227
+ Vertex, Foundry), the plan's tier name when Claude Code records one (Pro, Max, Team), the main
228
+ model's context window ccc uses (200k or 1M), and where Claude Code was found (on PATH,
229
+ its installer's folder, npm, winget);
205
230
  - lifecycle events (first run, sign-in prompt shown, key accepted or rejected, session start,
206
231
  the MemTree-paused notice shown, update hint or background update result);
207
232
  - errors as codes, status numbers, sizes and timings (an Anthropic 401, a MemTree 402, a
@@ -371,6 +396,24 @@ publish.yml still tests that tag but skips publishing, since the version is alre
371
396
 
372
397
  `ccc` never touches your Anthropic credentials — Claude Code manages its own login exactly as it does without `ccc`. If you see auth errors, fix them the vanilla way: run `/login` inside Claude Code (or `claude` directly) and re-authenticate.
373
398
 
399
+ ### "Claude Code isn't installed"
400
+
401
+ `ccc` runs Claude Code, so it has to be installed and signed in first. `ccc` finds it on
402
+ PATH and, failing that, where its installers put it (`%USERPROFILE%\.local\bin`,
403
+ `%APPDATA%\npm`, winget's folder and your saved user PATH on Windows; `~/.local/bin`,
404
+ `~/.claude/local` and Homebrew elsewhere), so a terminal opened before the install works
405
+ too. If it is not there, install it with the official command (Windows PowerShell:
406
+ `irm https://claude.ai/install.ps1 | iex`; macOS/Linux:
407
+ `curl -fsSL https://claude.ai/install.sh | bash`), open a new terminal, run `claude` once
408
+ to sign in, then run
409
+ `ccc` again, from your project folder.
410
+
411
+ ### "This API key has no credit"
412
+
413
+ `ccc` isn't free Claude: it runs your own Claude Code with your own Anthropic login. When
414
+ Anthropic refuses an API key for having no credit, `ccc` says so as it exits. Sign in with
415
+ a Claude Pro or Max plan (`claude`, then `/login`) or add credit to the key.
416
+
374
417
  ### MemTree degraded / passthrough mode
375
418
 
376
419
  If you see an inline "⚠ MemTree degraded — this turn ran uncompressed" notice, the compression API is unreachable or your MemTree key is invalid/expired. Your session keeps working uncompressed. Check your key at [polychat.co](https://polychat.co/auth?memtree=true), or delete it from `~/.claude-code-infinite/config.json` and re-run `ccc` to re-enter it.
@@ -423,15 +466,24 @@ at once; after an hour `ccc` shows the update line instead, and deleting
423
466
 
424
467
  **When it doesn't.** `ccc` updates itself only when it is a plain global npm install
425
468
  (`npm i -g`) whose files are in `npm root -g` and writable by you. It never uses `sudo`.
426
- In every other case it only shows the update line:
469
+ In every other case it only shows the update line, with the command that fits the install:
427
470
 
428
- - `npm link`ed or git checkouts (a development copy is never touched),
471
+ - `npm link`ed or git checkouts: a development copy is never touched, and the line says to
472
+ `git pull && npm run build` in a git checkout, or to update and rebuild the linked folder
473
+ otherwise (`npm i -g` would replace the link with the published package),
429
474
  - `npx` and project-local installs,
430
- - pnpm, yarn, bun and Volta global installs (update with your tool's own command),
475
+ - pnpm, yarn, bun and Volta global installs (the line gives your tool's own command),
431
476
  - a global prefix that needs `sudo` to write, or `ccc` running as root,
432
- - Windows,
433
477
  - print (`-p`) and non-TTY runs, which never install anything.
434
478
 
479
+ **Windows.** The install starts as `ccc` exits rather than during the session: the
480
+ background installer waits for that `ccc` to be gone before npm runs, so npm does not
481
+ rewrite files under the session that started it. The banner says so (`• ccc X is
482
+ available … · installs when you exit ccc`). A `ccc` started while it installs says
483
+ "Finishing a ccc update…" and waits for it (usually seconds, at most two minutes), then runs the new
484
+ version. Closing the terminal window usually counts as exiting too. By hand:
485
+ `npm i -g claude-code-infinite@latest`.
486
+
435
487
  `ccc --debug` prints which install method was detected and what it decided.
436
488
 
437
489
  **Sessions running during an update.** npm replaces `ccc`'s files while other `ccc`
@@ -19,6 +19,13 @@
19
19
  * --no-fund --no-audit` (argv, never a shell); confirm the package.json now
20
20
  * says that version. Any failure is recorded with a short reason.
21
21
  *
22
+ * Windows (waitForPid): the runner first waits for the ccc that started it to
23
+ * exit, so npm does not rewrite files under that session; npm
24
+ * runs as `node npm-cli.js` (npmArgs), and its "group" is npm's own pid,
25
+ * stopped with `taskkill /T /F` (its whole process tree) instead of signals
26
+ * to a POSIX process group. Unlike a POSIX group, a descendant that outlives
27
+ * npm is not tracked there (ccc and its dependency have no install scripts).
28
+ *
22
29
  * Bounded: each npm command runs in its own process group, recorded in the
23
30
  * lock entry. When it ends (or times out) the whole group is reaped: SIGTERM,
24
31
  * then SIGKILL, until no member is left, so a descendant that outlives npm
@@ -31,6 +38,12 @@
31
38
  /** What ccc passes the runner, as JSON in argv[1]. */
32
39
  export interface RunnerConfig {
33
40
  npm: string;
41
+ /** Arguments before npm's own: Windows runs `node npm-cli.js`. */
42
+ npmArgs?: string[];
43
+ /** Windows: wait for this pid (the launching ccc) to exit before installing. */
44
+ waitForPid?: number;
45
+ /** Test-only: how long to wait for waitForPid (default 60 s). */
46
+ waitForPidMs?: number;
34
47
  packageName: string;
35
48
  version: string;
36
49
  packageDir: string;
@@ -1 +1 @@
1
- {"version":3,"file":"auto-update-runner.d.ts","sourceRoot":"","sources":["../src/auto-update-runner.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AAEH,sDAAsD;AACtD,MAAM,WAAW,YAAY;IAC3B,GAAG,EAAE,MAAM,CAAC;IACZ,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,MAAM,CAAC;IAClB,2EAA2E;IAC3E,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,MAAM,CAAC;IAClB,6EAA6E;IAC7E,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,gFAAgF;IAChF,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,eAAO,MAAM,aAAa,QAmRzB,CAAC"}
1
+ {"version":3,"file":"auto-update-runner.d.ts","sourceRoot":"","sources":["../src/auto-update-runner.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoCG;AAEH,sDAAsD;AACtD,MAAM,WAAW,YAAY;IAC3B,GAAG,EAAE,MAAM,CAAC;IACZ,kEAAkE;IAClE,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;IACnB,gFAAgF;IAChF,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,iEAAiE;IACjE,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,MAAM,CAAC;IAClB,2EAA2E;IAC3E,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,MAAM,CAAC;IAClB,6EAA6E;IAC7E,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,gFAAgF;IAChF,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,eAAO,MAAM,aAAa,QAwTzB,CAAC"}
@@ -19,6 +19,13 @@
19
19
  * --no-fund --no-audit` (argv, never a shell); confirm the package.json now
20
20
  * says that version. Any failure is recorded with a short reason.
21
21
  *
22
+ * Windows (waitForPid): the runner first waits for the ccc that started it to
23
+ * exit, so npm does not rewrite files under that session; npm
24
+ * runs as `node npm-cli.js` (npmArgs), and its "group" is npm's own pid,
25
+ * stopped with `taskkill /T /F` (its whole process tree) instead of signals
26
+ * to a POSIX process group. Unlike a POSIX group, a descendant that outlives
27
+ * npm is not tracked there (ccc and its dependency have no install scripts).
28
+ *
22
29
  * Bounded: each npm command runs in its own process group, recorded in the
23
30
  * lock entry. When it ends (or times out) the whole group is reaped: SIGTERM,
24
31
  * then SIGKILL, until no member is left, so a descendant that outlives npm
@@ -36,7 +43,9 @@ const path = require("node:path");
36
43
 
37
44
  const VERSION_RE = /^(0|[1-9]\d{0,8})\.(0|[1-9]\d{0,8})\.(0|[1-9]\d{0,8})$/;
38
45
  const ROOT_TIMEOUT_MS = 60000;
46
+ const IS_WINDOWS = process.platform === "win32";
39
47
  const cfg = JSON.parse(process.argv[1]);
48
+ const WAIT_FOR_PID_MS = cfg.waitForPidMs || 60000;
40
49
  const KILL_GRACE_MS = cfg.killGraceMs || 10000;
41
50
  const PIPE_GRACE_MS = cfg.pipeGraceMs || 5000;
42
51
  const ENTRY = path.join(cfg.lockDir, cfg.token + ".json");
@@ -49,7 +58,8 @@ let stopping = null;
49
58
 
50
59
  // Hard deadline: whatever npm does, this process kills its group, records a
51
60
  // result and exits, so a live holder never outlasts the lock's stale age.
52
- const DEADLINE_MS = ROOT_TIMEOUT_MS + cfg.timeoutMs + 2 * (2 * KILL_GRACE_MS + 2 * PIPE_GRACE_MS) + 30000;
61
+ const DEADLINE_MS = ROOT_TIMEOUT_MS + cfg.timeoutMs + 2 * (2 * KILL_GRACE_MS + 2 * PIPE_GRACE_MS) +
62
+ (cfg.waitForPid ? WAIT_FOR_PID_MS : 0) + 30000;
53
63
  setTimeout(() => abort("runner deadline passed"), DEADLINE_MS);
54
64
  for (const signal of ["SIGTERM", "SIGINT", "SIGHUP"]) {
55
65
  process.on(signal, () => abort("runner stopped by " + signal));
@@ -65,19 +75,51 @@ function sleep(ms) {
65
75
  return new Promise((resolve) => setTimeout(resolve, ms));
66
76
  }
67
77
 
68
- function groupAlive(pgid) {
78
+ function pidAlive(pid) {
69
79
  try {
70
- process.kill(-pgid, 0);
80
+ process.kill(pid, 0);
71
81
  return true;
72
82
  } catch (err) {
73
83
  return err.code === "EPERM";
74
84
  }
75
85
  }
76
86
 
87
+ // Windows has no process groups: the "group" is npm's pid and its tree.
88
+ function groupAlive(pgid) {
89
+ return pidAlive(IS_WINDOWS ? pgid : -pgid);
90
+ }
91
+
77
92
  function signalGroup(pgid, signal) {
93
+ if (IS_WINDOWS) {
94
+ try {
95
+ const taskkill = path.join(process.env.SystemRoot || "C:\\Windows", "System32", "taskkill.exe");
96
+ require("node:child_process").spawnSync(taskkill, ["/PID", String(pgid), "/T", "/F"], {
97
+ stdio: "ignore", windowsHide: true, timeout: 10000,
98
+ });
99
+ } catch {}
100
+ return;
101
+ }
78
102
  try { process.kill(-pgid, signal); } catch {}
79
103
  }
80
104
 
105
+ async function awaitLauncherExit() {
106
+ if (!cfg.waitForPid) return true;
107
+ const until = Date.now() + WAIT_FOR_PID_MS;
108
+ while (pidAlive(cfg.waitForPid)) {
109
+ if (Date.now() >= until || stopping) return false;
110
+ await sleep(100);
111
+ }
112
+ return true;
113
+ }
114
+
115
+ function npmArgs(args) {
116
+ return (cfg.npmArgs || []).concat(args);
117
+ }
118
+
119
+ function samePath(a, b) {
120
+ return a !== null && b !== null && (IS_WINDOWS ? a.toLowerCase() === b.toLowerCase() : a === b);
121
+ }
122
+
81
123
  async function goneWithin(pgid, ms) {
82
124
  const until = Date.now() + ms;
83
125
  while (groupAlive(pgid)) {
@@ -168,6 +210,7 @@ function run(command, args, timeoutMs, capture) {
168
210
  env: process.env,
169
211
  stdio: ["ignore", "pipe", "pipe"],
170
212
  detached: true,
213
+ windowsHide: true,
171
214
  });
172
215
  } catch (err) {
173
216
  recordGroup(null, false);
@@ -225,20 +268,21 @@ function describe(result, what) {
225
268
 
226
269
  async function install() {
227
270
  if (!VERSION_RE.test(cfg.version)) throw new Error("refusing version " + JSON.stringify(cfg.version));
228
- const rootRun = await runReaped(cfg.npm, ["root", "--global"], ROOT_TIMEOUT_MS, true);
271
+ if (!(await awaitLauncherExit())) throw new Error(stopping || "ccc did not exit");
272
+ const rootRun = await runReaped(cfg.npm, npmArgs(["root", "--global"]), ROOT_TIMEOUT_MS, true);
229
273
  const rootFailure = describe(rootRun, "npm root -g");
230
274
  if (rootFailure) throw new Error(rootFailure);
231
275
  const npmRoot = realpath(rootRun.out.trim());
232
- if (npmRoot === null || npmRoot !== realpath(cfg.npmRoot)) {
276
+ if (npmRoot === null || !samePath(npmRoot, realpath(cfg.npmRoot))) {
233
277
  throw new Error("npm root -g is " + rootRun.out.trim() + ", not " + cfg.npmRoot + ": wrong prefix");
234
278
  }
235
279
  const entry = path.join(npmRoot, cfg.packageName);
236
280
  if (fs.lstatSync(entry).isSymbolicLink()) throw new Error(entry + " is a symlink (npm link)");
237
- if (realpath(entry) !== realpath(cfg.packageDir)) throw new Error(entry + " is not this ccc");
281
+ if (!samePath(realpath(entry), realpath(cfg.packageDir))) throw new Error(entry + " is not this ccc");
238
282
  const spec = cfg.packageName + "@" + cfg.version;
239
283
  const args = ["install", "--global", "--prefix=" + cfg.prefix, spec, "--no-fund", "--no-audit"];
240
284
  note("npm " + args.join(" "));
241
- const failure = describe(await runReaped(cfg.npm, args, cfg.timeoutMs, false), "npm install");
285
+ const failure = describe(await runReaped(cfg.npm, npmArgs(args), cfg.timeoutMs, false), "npm install");
242
286
  if (failure) throw new Error(failure);
243
287
  const pkg = JSON.parse(fs.readFileSync(path.join(cfg.packageDir, "package.json"), "utf8"));
244
288
  if (pkg.version !== cfg.version) throw new Error("installed version is " + pkg.version);
@@ -1 +1 @@
1
- {"version":3,"file":"auto-update-runner.js","sourceRoot":"","sources":["../src/auto-update-runner.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AAuBH,MAAM,CAAC,MAAM,aAAa,GAAG,MAAM,CAAC,GAAG,CAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAmRtC,CAAC"}
1
+ {"version":3,"file":"auto-update-runner.js","sourceRoot":"","sources":["../src/auto-update-runner.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoCG;AA6BH,MAAM,CAAC,MAAM,aAAa,GAAG,MAAM,CAAC,GAAG,CAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAwTtC,CAAC"}
@@ -2,10 +2,10 @@
2
2
  * Launch-time glue for auto-update.ts: decide what this interactive launch
3
3
  * does about an available update, what the SessionStart banner says now, and
4
4
  * when the background install starts (after Claude Code is up, never on the
5
- * startup path).
5
+ * startup path; on Windows only as ccc exits, see auto-update.ts).
6
6
  */
7
7
  import { AutoUpdateNotices, type AutoUpdateConfig, type AutoUpdateDecision, type InstallMethod, type SpawnLike, type StartInstallResult } from "./auto-update.js";
8
- import type { UpdateAvailable } from "./update-check.js";
8
+ import { type UpdateAvailable } from "./update-check.js";
9
9
  export interface AutoUpdateSetupOptions {
10
10
  update: UpdateAvailable | null;
11
11
  currentVersion: string;
@@ -18,6 +18,12 @@ export interface AutoUpdateSetupOptions {
18
18
  spawnImpl?: SpawnLike;
19
19
  platform?: NodeJS.Platform;
20
20
  uid?: number;
21
+ /** process.argv[1], to see an `npm link` through the bin (detectInstallMethod). */
22
+ scriptPath?: string;
23
+ /** This process (Windows: the runner waits for it to exit). */
24
+ pid?: number;
25
+ /** Test-only: the Node (and so npm) the runner uses. */
26
+ execPath?: string;
21
27
  }
22
28
  export interface AutoUpdateSetup {
23
29
  decision: AutoUpdateDecision;
@@ -29,10 +35,20 @@ export interface AutoUpdateSetup {
29
35
  bannerInstalled: string | null;
30
36
  /** The SessionStart line for later hooks in this process (proxy option). */
31
37
  notices: AutoUpdateNotices;
32
- /** Start the planned install after `delayMs`; a no-op unless planned. */
38
+ /**
39
+ * Start the planned install after `delayMs`; a no-op unless planned, and on
40
+ * Windows, where it starts in installOnExit instead.
41
+ */
33
42
  scheduleInstall(delayMs?: number, onResult?: (result: StartInstallResult) => void): void;
43
+ /**
44
+ * Windows: start the planned install now, as ccc exits; the runner waits for
45
+ * this process to be gone. A no-op elsewhere or unless planned.
46
+ */
47
+ installOnExit(onResult?: (result: StartInstallResult) => void): void;
34
48
  /** One line naming the install method and the decision, for --debug. */
35
49
  debugLine: string;
36
50
  }
51
+ /** The banner's ending for a Windows update that installs as ccc exits. */
52
+ export declare const WINDOWS_ON_EXIT_TEXT = "installs when you exit ccc";
37
53
  export declare function setupAutoUpdate(opts: AutoUpdateSetupOptions): AutoUpdateSetup;
38
54
  //# sourceMappingURL=auto-update-setup.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"auto-update-setup.d.ts","sourceRoot":"","sources":["../src/auto-update-setup.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAGH,OAAO,EAEL,iBAAiB,EASjB,KAAK,gBAAgB,EACrB,KAAK,kBAAkB,EACvB,KAAK,aAAa,EAClB,KAAK,SAAS,EACd,KAAK,kBAAkB,EACxB,MAAM,kBAAkB,CAAC;AAE1B,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AAEzD,MAAM,WAAW,sBAAsB;IACrC,MAAM,EAAE,eAAe,GAAG,IAAI,CAAC;IAC/B,cAAc,EAAE,MAAM,CAAC;IACvB,MAAM,EAAE,gBAAgB,CAAC;IACzB,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,OAAO,CAAC;IACf,GAAG,CAAC,EAAE,MAAM,CAAC,UAAU,CAAC;IACxB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,GAAG,CAAC,EAAE,MAAM,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,SAAS,CAAC;IACtB,QAAQ,CAAC,EAAE,MAAM,CAAC,QAAQ,CAAC;IAC3B,GAAG,CAAC,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,eAAe;IAC9B,QAAQ,EAAE,kBAAkB,CAAC;IAC7B,uDAAuD;IACvD,MAAM,EAAE,aAAa,GAAG,IAAI,CAAC;IAC7B,uEAAuE;IACvE,YAAY,EAAE,eAAe,GAAG,IAAI,CAAC;IACrC,+EAA+E;IAC/E,eAAe,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B,4EAA4E;IAC5E,OAAO,EAAE,iBAAiB,CAAC;IAC3B,yEAAyE;IACzE,eAAe,CAAC,OAAO,CAAC,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,CAAC,MAAM,EAAE,kBAAkB,KAAK,IAAI,GAAG,IAAI,CAAC;IACzF,wEAAwE;IACxE,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,wBAAgB,eAAe,CAAC,IAAI,EAAE,sBAAsB,GAAG,eAAe,CA4C7E"}
1
+ {"version":3,"file":"auto-update-setup.d.ts","sourceRoot":"","sources":["../src/auto-update-setup.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAGH,OAAO,EAEL,iBAAiB,EAUjB,KAAK,gBAAgB,EACrB,KAAK,kBAAkB,EACvB,KAAK,aAAa,EAClB,KAAK,SAAS,EACd,KAAK,kBAAkB,EACxB,MAAM,kBAAkB,CAAC;AAE1B,OAAO,EAAmB,KAAK,eAAe,EAAE,MAAM,mBAAmB,CAAC;AAE1E,MAAM,WAAW,sBAAsB;IACrC,MAAM,EAAE,eAAe,GAAG,IAAI,CAAC;IAC/B,cAAc,EAAE,MAAM,CAAC;IACvB,MAAM,EAAE,gBAAgB,CAAC;IACzB,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,OAAO,CAAC;IACf,GAAG,CAAC,EAAE,MAAM,CAAC,UAAU,CAAC;IACxB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,GAAG,CAAC,EAAE,MAAM,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,SAAS,CAAC;IACtB,QAAQ,CAAC,EAAE,MAAM,CAAC,QAAQ,CAAC;IAC3B,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,mFAAmF;IACnF,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,+DAA+D;IAC/D,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,wDAAwD;IACxD,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,eAAe;IAC9B,QAAQ,EAAE,kBAAkB,CAAC;IAC7B,uDAAuD;IACvD,MAAM,EAAE,aAAa,GAAG,IAAI,CAAC;IAC7B,uEAAuE;IACvE,YAAY,EAAE,eAAe,GAAG,IAAI,CAAC;IACrC,+EAA+E;IAC/E,eAAe,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B,4EAA4E;IAC5E,OAAO,EAAE,iBAAiB,CAAC;IAC3B;;;OAGG;IACH,eAAe,CAAC,OAAO,CAAC,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,CAAC,MAAM,EAAE,kBAAkB,KAAK,IAAI,GAAG,IAAI,CAAC;IACzF;;;OAGG;IACH,aAAa,CAAC,QAAQ,CAAC,EAAE,CAAC,MAAM,EAAE,kBAAkB,KAAK,IAAI,GAAG,IAAI,CAAC;IACrE,wEAAwE;IACxE,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,2EAA2E;AAC3E,eAAO,MAAM,oBAAoB,+BAA+B,CAAC;AAEjE,wBAAgB,eAAe,CAAC,IAAI,EAAE,sBAAsB,GAAG,eAAe,CA8D7E"}
@@ -2,11 +2,14 @@
2
2
  * Launch-time glue for auto-update.ts: decide what this interactive launch
3
3
  * does about an available update, what the SessionStart banner says now, and
4
4
  * when the background install starts (after Claude Code is up, never on the
5
- * startup path).
5
+ * startup path; on Windows only as ccc exits, see auto-update.ts).
6
6
  */
7
7
  import { spawn } from "node:child_process";
8
- import { AUTO_UPDATE_DELAY_MS, AutoUpdateNotices, PACKAGE_DIR, detectInstallMethod, liveLockSince, planAutoUpdate, readInstalledVersion, readUpdateState, startAutoUpdateInstall, updateFiles, } from "./auto-update.js";
8
+ import { AUTO_UPDATE_DELAY_MS, AutoUpdateNotices, PACKAGE_DIR, detectInstallMethod, manualUpdateCommand, liveLockSince, planAutoUpdate, readInstalledVersion, readUpdateState, startAutoUpdateInstall, updateFiles, } from "./auto-update.js";
9
9
  import { updateInstalledNoticeText, updateNoticeText } from "./notices.js";
10
+ import { UPGRADE_COMMAND } from "./update-check.js";
11
+ /** The banner's ending for a Windows update that installs as ccc exits. */
12
+ export const WINDOWS_ON_EXIT_TEXT = "installs when you exit ccc";
10
13
  export function setupAutoUpdate(opts) {
11
14
  const env = opts.env ?? process.env;
12
15
  const packageDir = opts.packageDir ?? PACKAGE_DIR;
@@ -24,34 +27,53 @@ export function setupAutoUpdate(opts) {
24
27
  installedVersion: readInstalledVersion(method.packageDir),
25
28
  lockHeldSince: liveLockSince(files.lockDir, now()),
26
29
  })
27
- : { action: "skip", reason: "no update available" };
30
+ : { action: "skip", reason: "no update available", code: "no-update" };
31
+ const platform = opts.platform ?? process.platform;
32
+ const windows = platform === "win32";
33
+ const command = method ? manualUpdateCommand(method.kind) : undefined;
34
+ const withCommand = (update) => command && command !== UPGRADE_COMMAND ? { ...update, command } : update;
35
+ // Windows installs as ccc exits: say so now, since nothing installs during the session.
36
+ const onExitBanner = windows && decision.action === "install" && opts.update
37
+ ? { ...opts.update, command: WINDOWS_ON_EXIT_TEXT }
38
+ : null;
39
+ let started = false;
40
+ const start = (onResult) => {
41
+ if (decision.action !== "install" || started)
42
+ return;
43
+ started = true;
44
+ const spawnImpl = opts.spawnImpl ?? spawn;
45
+ const result = startAutoUpdateInstall({
46
+ decision, files, spawnImpl, env, now, platform, execPath: opts.execPath,
47
+ ...(windows ? { waitForPid: opts.pid ?? process.pid } : {}),
48
+ });
49
+ onResult?.(result);
50
+ };
28
51
  const notices = new AutoUpdateNotices({
29
52
  currentVersion: opts.currentVersion,
30
53
  packageDir: method?.packageDir ?? packageDir,
31
54
  stateFile: files.stateFile,
32
55
  startedAt: now(),
33
56
  installedText: (version) => updateInstalledNoticeText(version, opts.color),
34
- hintText: (update) => updateNoticeText(update, opts.color),
57
+ hintText: (update) => updateNoticeText(withCommand(update), opts.color),
35
58
  });
36
59
  if (decision.action === "installed")
37
60
  notices.markShown();
38
61
  return {
39
62
  decision,
40
63
  method,
41
- bannerUpdate: decision.action === "skip" ? opts.update : null,
64
+ bannerUpdate: decision.action === "skip" && opts.update ? withCommand(opts.update) : onExitBanner,
42
65
  bannerInstalled: decision.action === "installed" ? decision.version : null,
43
66
  notices,
44
67
  scheduleInstall(delayMs = AUTO_UPDATE_DELAY_MS, onResult) {
45
- if (decision.action !== "install")
68
+ if (decision.action !== "install" || windows)
46
69
  return;
47
- const spawnImpl = opts.spawnImpl ?? spawn;
48
- const timer = setTimeout(() => {
49
- const result = startAutoUpdateInstall({ decision, files, spawnImpl, env, now });
50
- onResult?.(result);
51
- }, delayMs);
52
- timer.unref();
70
+ setTimeout(() => start(onResult), delayMs).unref();
71
+ },
72
+ installOnExit(onResult) {
73
+ if (windows)
74
+ start(onResult);
53
75
  },
54
- debugLine: debugLine(method, decision),
76
+ debugLine: debugLine(method, decision, windows),
55
77
  };
56
78
  }
57
79
  function detectMethod(opts, env, packageDir) {
@@ -60,13 +82,17 @@ function detectMethod(opts, env, packageDir) {
60
82
  env,
61
83
  platform: opts.platform,
62
84
  uid: opts.uid ?? process.getuid?.(),
85
+ scriptPath: opts.scriptPath,
63
86
  });
64
87
  }
65
- function debugLine(method, decision) {
88
+ function debugLine(method, decision, windows) {
66
89
  const how = method ? `install method ${method.kind} (${method.detail})` : "no update to install";
67
- const what = decision.action === "install" ? `installing ${decision.version} in the background`
90
+ const what = decision.action === "install"
91
+ ? (windows ? `installs ${decision.version} when ccc exits`
92
+ : `installing ${decision.version} in the background`)
68
93
  : decision.action === "installed" ? `${decision.version} already installed, restart to use it`
69
- : `not installing: ${decision.reason}`;
94
+ : decision.code === "linked-install" ? "skipped: linked install"
95
+ : `not installing: ${decision.reason}`;
70
96
  return `Auto-update: ${how}; ${what}`;
71
97
  }
72
98
  //# sourceMappingURL=auto-update-setup.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"auto-update-setup.js","sourceRoot":"","sources":["../src/auto-update-setup.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"auto-update-setup.js","sourceRoot":"","sources":["../src/auto-update-setup.ts"],"names":[],"mappings":"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"}
@@ -4,12 +4,22 @@
4
4
  * Code does ("✔ Update installed · Restart to update").
5
5
  *
6
6
  * Only when self-updating is safe: a plain global npm install whose package
7
- * directory is physically inside `<prefix>/lib/node_modules` and writable by
8
- * this user (no sudo, ever). npm-linked and git checkouts, npx and local
9
- * installs, pnpm/yarn/bun/volta globals, root/sudo runs and Windows get the
10
- * hint line only. The install runs in a detached runner (auto-update-runner.ts)
11
- * that loads no file from the package, so it survives this ccc exiting and
12
- * npm replacing the package under it.
7
+ * directory is physically inside `<prefix>/lib/node_modules` (on Windows
8
+ * `<prefix>\node_modules`) and writable by this user (no sudo, ever).
9
+ * npm-linked and git checkouts (detected through the bin link too, so an
10
+ * `npm link` is never replaced), npx and local installs, pnpm/yarn/bun/volta
11
+ * globals and root/sudo runs get the hint line only, with the command that
12
+ * fits how they were installed. The install runs in a detached runner
13
+ * (auto-update-runner.ts) that loads no file from the package, so it survives
14
+ * this ccc exiting and npm replacing the package under it.
15
+ *
16
+ * Windows: the runner starts only as ccc exits (also when the window is
17
+ * closed) and waits for this process to be gone before npm runs, so npm does
18
+ * not rewrite files under the session that started it (other running
19
+ * sessions are as on POSIX: they already loaded their code; the shim npm
20
+ * rewrites is identical between versions). npm is run as `node npm-cli.js` (no
21
+ * shell: Node refuses to spawn a .cmd without one), and the runner kills
22
+ * npm's process tree with taskkill instead of a POSIX process group.
13
23
  *
14
24
  * One install at a time across sessions (a lock file with a stale timeout).
15
25
  * Every attempt is recorded in update-state.json: a failure backs off for a
@@ -60,12 +70,17 @@ export interface UpdateAttempt {
60
70
  at: number;
61
71
  packageDir: string;
62
72
  error?: string;
73
+ /** A launch has sent this result to diagnostics (takeUnreportedOutcome). */
74
+ reported?: boolean;
63
75
  }
64
76
  export interface UpdateState {
65
77
  /** Install attempts per version (newest few kept). */
66
78
  attempts: Record<string, number>;
67
79
  last?: UpdateAttempt;
68
80
  }
81
+ /** Why a launch with an update available did not install it, for diagnostics. */
82
+ export declare const SKIP_CODES: readonly ["disabled", "not-release", "linked-install", "install-method", "backoff", "max-attempts", "lock-held"];
83
+ export type SkipCode = (typeof SKIP_CODES)[number] | "no-update";
69
84
  export type AutoUpdateDecision = {
70
85
  action: "install";
71
86
  version: string;
@@ -76,9 +91,21 @@ export type AutoUpdateDecision = {
76
91
  } | {
77
92
  action: "skip";
78
93
  reason: string;
94
+ code: SkipCode;
79
95
  };
80
96
  export interface DetectInstallOptions {
81
97
  packageDir: string;
98
+ /**
99
+ * The script as started (process.argv[1]), before symlinks are resolved:
100
+ * through it an `npm link` shows even though Node reports the checkout as
101
+ * the package directory.
102
+ */
103
+ scriptPath?: string;
104
+ /**
105
+ * Windows: test writability by creating a file (default true). Diagnostics
106
+ * pass false, so a launch with no update writes nothing into the package.
107
+ */
108
+ probeWrites?: boolean;
82
109
  platform?: NodeJS.Platform;
83
110
  /** process.getuid(); undefined where there is none. */
84
111
  uid?: number;
@@ -105,6 +132,7 @@ export type SpawnLike = (command: string, args: string[], options: {
105
132
  detached: boolean;
106
133
  stdio: "ignore";
107
134
  env: NodeJS.ProcessEnv;
135
+ windowsHide: boolean;
108
136
  }) => {
109
137
  pid?: number;
110
138
  unref(): void;
@@ -119,6 +147,11 @@ export interface StartInstallOptions {
119
147
  execPath?: string;
120
148
  /** The npm to run; default the one beside this Node, else `npm` on PATH. */
121
149
  npmPath?: string;
150
+ /** Arguments before npm's own (Windows: npm-cli.js, run by this Node). */
151
+ npmArgs?: string[];
152
+ platform?: NodeJS.Platform;
153
+ /** Windows: the ccc whose exit the runner waits for before installing. */
154
+ waitForPid?: number;
122
155
  }
123
156
  export type StartInstallResult = "started" | "locked" | "not-planned" | "spawn-failed"
124
157
  /** Rechecked under the lock: backed off, tried enough, or already installed. */
@@ -132,6 +165,14 @@ export declare function strictReleaseVersion(version: unknown): string | null;
132
165
  * runner re-checks the root against `npm root -g` before installing.
133
166
  */
134
167
  export declare function detectInstallMethod(opts: DetectInstallOptions): InstallMethod;
168
+ /** True for the installs whose files are the user's own checkout. */
169
+ export declare function isLinkedInstall(kind: InstallMethodKind): boolean;
170
+ /**
171
+ * How to update this install by hand: never `npm i -g` over a linked checkout
172
+ * (that replaces the link with the published package), and each package
173
+ * manager's own command for its globals.
174
+ */
175
+ export declare function manualUpdateCommand(kind: InstallMethodKind): string;
135
176
  /** Decide what this launch does about an available update. */
136
177
  export declare function planAutoUpdate(opts: PlanOptions): AutoUpdateDecision;
137
178
  /**
@@ -161,10 +202,22 @@ export declare class AutoUpdateNotices {
161
202
  sessionStartLine(): string | undefined;
162
203
  private pendingLine;
163
204
  }
205
+ /**
206
+ * The result of the last background install, once: "ok" or "failed" the
207
+ * first time a launch sees it finished, then null (marked reported in the
208
+ * state file). For diagnostics, which the detached runner cannot send.
209
+ */
210
+ export declare function takeUnreportedOutcome(stateFile: string, lockDir?: string, now?: number): "ok" | "failed" | null;
164
211
  /** The version in `<packageDir>/package.json`, or null. */
165
212
  export declare function readInstalledVersion(packageDir: string): string | null;
166
213
  export declare function updateFiles(configDir: string): UpdateFiles;
167
214
  export declare function readUpdateState(file: string): UpdateState;
168
215
  /** Best effort and atomic: a failed write only loses bookkeeping. */
169
216
  export declare function writeUpdateState(file: string, state: UpdateState): void;
217
+ /**
218
+ * The `node_modules/claude-code-infinite` entry the script was started
219
+ * through, when that entry is a symlink or junction (an `npm link`). The bin
220
+ * itself may be a symlink (POSIX `<prefix>/bin/ccc`): one hop is followed.
221
+ */
222
+ export declare function linkedPackageEntry(scriptPath: string): string | null;
170
223
  //# sourceMappingURL=auto-update.d.ts.map