ccdeck 3.29.10 → 3.30.0

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 (176) hide show
  1. package/README.md +1 -1
  2. package/bin/agent-dag.js +67 -118
  3. package/bin/cli/help.js +67 -0
  4. package/bin/cli/login-item.js +182 -0
  5. package/bin/cli/one-shot.js +254 -0
  6. package/bin/cli/package.js +39 -0
  7. package/bin/cli/pulse.js +98 -0
  8. package/bin/cli/restart.js +179 -0
  9. package/bin/cli/screen.js +166 -0
  10. package/bin/cli/second-start.js +120 -0
  11. package/bin/cli/startup.js +459 -0
  12. package/bin/cli/uninstall.js +169 -0
  13. package/bin/deck.js +112 -1559
  14. package/dist/web/assets/BrowserWatchModal-DP3YMtEi.js +1 -0
  15. package/dist/web/assets/UsageHistoryModal-CcdPVXAw.js +1 -0
  16. package/dist/web/assets/index-3vS6A0vt.css +1 -0
  17. package/dist/web/assets/index-BVTWHDta.js +298 -0
  18. package/dist/web/index.html +2 -2
  19. package/hook/hook.js +15 -14
  20. package/package.json +1 -1
  21. package/release-notes.json +28 -0
  22. package/src/server/account-projects.mjs +25 -11
  23. package/src/server/account-routes.mjs +254 -0
  24. package/src/server/activity.mjs +3 -3
  25. package/src/server/append-budget.mjs +185 -0
  26. package/src/server/append-failures.mjs +147 -0
  27. package/src/server/args.mjs +37 -5
  28. package/src/server/atomic-write.mjs +300 -0
  29. package/src/server/block-notify.mjs +0 -5
  30. package/src/server/boot-lock.mjs +1 -1
  31. package/src/server/brand.mjs +7 -6
  32. package/src/server/browser-react.mjs +10 -2
  33. package/src/server/browser-watch-decks.mjs +146 -0
  34. package/src/server/browser-watch-feed.mjs +69 -0
  35. package/src/server/browser-watch-log.mjs +173 -0
  36. package/src/server/browser-watch-mtime.mjs +18 -0
  37. package/src/server/browser-watch-relay.mjs +112 -0
  38. package/src/server/browser-watch-routes.mjs +172 -0
  39. package/src/server/browser-watch-seen.mjs +127 -0
  40. package/src/server/browser-watch-store.mjs +14 -143
  41. package/src/server/browser-watch.mjs +278 -626
  42. package/src/server/canonical-path.mjs +150 -0
  43. package/src/server/ccusage-failure.mjs +64 -0
  44. package/src/server/ccusage-install.mjs +459 -0
  45. package/src/server/ccusage-runner.mjs +271 -0
  46. package/src/server/ccusage.mjs +21 -756
  47. package/src/server/claude-accounts.mjs +220 -464
  48. package/src/server/claude-collector.mjs +189 -0
  49. package/src/server/claude-dir.mjs +2 -2
  50. package/src/server/claude-identity.mjs +5 -4
  51. package/src/server/claude-reset-credits.mjs +130 -0
  52. package/src/server/claude-verdicts.mjs +174 -0
  53. package/src/server/clear-route.mjs +191 -0
  54. package/src/server/codex-auth.mjs +4 -4
  55. package/src/server/codex-dir.mjs +52 -8
  56. package/src/server/codex-enrichment.mjs +310 -0
  57. package/src/server/codex-quota.mjs +5 -3
  58. package/src/server/codex-translate.mjs +407 -0
  59. package/src/server/codex-usage.mjs +8 -5
  60. package/src/server/codex-watch.mjs +496 -0
  61. package/src/server/cswap-auto-loop.mjs +342 -0
  62. package/src/server/cswap-auto-readers.mjs +303 -0
  63. package/src/server/cswap-auto.mjs +51 -609
  64. package/src/server/cswap-install.mjs +69 -265
  65. package/src/server/cswap-layout.mjs +242 -0
  66. package/src/server/deck-home.mjs +1 -1
  67. package/src/server/deck-prefs.mjs +51 -184
  68. package/src/server/deck-scope.mjs +52 -0
  69. package/src/server/desktop-update-routes.mjs +75 -0
  70. package/src/server/detach.mjs +78 -0
  71. package/src/server/discovery.mjs +298 -0
  72. package/src/server/event-log.mjs +328 -0
  73. package/src/server/event-pipeline.mjs +235 -0
  74. package/src/server/event-ring.mjs +214 -0
  75. package/src/server/event-routes.mjs +352 -0
  76. package/src/server/event-sink.mjs +26 -0
  77. package/src/server/exec-children.mjs +128 -0
  78. package/src/server/exec-not-found.mjs +213 -0
  79. package/src/server/exec-output.mjs +23 -0
  80. package/src/server/exec-spec.mjs +341 -0
  81. package/src/server/exec.mjs +74 -661
  82. package/src/server/health-route.mjs +42 -0
  83. package/src/server/hook-challenge.mjs +37 -0
  84. package/src/server/http-io.mjs +104 -0
  85. package/src/server/index.mjs +172 -7620
  86. package/src/server/install-layout.mjs +506 -0
  87. package/src/server/installer.mjs +79 -608
  88. package/src/server/jsonl-chunks.mjs +139 -0
  89. package/src/server/lan-accounts.mjs +27 -0
  90. package/src/server/lan-beacon.mjs +457 -0
  91. package/src/server/lan-call.mjs +239 -0
  92. package/src/server/lan-copies.mjs +270 -0
  93. package/src/server/lan-deck.mjs +583 -0
  94. package/src/server/lan-dials.mjs +161 -0
  95. package/src/server/lan-engine.mjs +475 -1024
  96. package/src/server/lan-hearing.mjs +49 -0
  97. package/src/server/lan-inbound.mjs +100 -0
  98. package/src/server/lan-invite-offer.mjs +77 -0
  99. package/src/server/lan-invite.mjs +207 -0
  100. package/src/server/lan-lines.mjs +73 -0
  101. package/src/server/lan-manifest.mjs +74 -0
  102. package/src/server/lan-reach.mjs +82 -69
  103. package/src/server/lan-requests.mjs +249 -0
  104. package/src/server/lan-round-record.mjs +45 -0
  105. package/src/server/lan-round-timer.mjs +62 -0
  106. package/src/server/lan-routes.mjs +143 -0
  107. package/src/server/lan-serve.mjs +112 -0
  108. package/src/server/lan-socket.mjs +243 -925
  109. package/src/server/lan-sync.mjs +58 -1026
  110. package/src/server/lan-tailnet-poll.mjs +57 -0
  111. package/src/server/lan-turns.mjs +80 -0
  112. package/src/server/lan-wire.mjs +620 -0
  113. package/src/server/lifecycle.mjs +421 -0
  114. package/src/server/listen.mjs +110 -0
  115. package/src/server/live-decks.mjs +138 -0
  116. package/src/server/load-average.mjs +32 -0
  117. package/src/server/log-election.mjs +235 -0
  118. package/src/server/log-replay.mjs +374 -0
  119. package/src/server/log-writer.mjs +72 -557
  120. package/src/server/login-service.mjs +3 -2
  121. package/src/server/macmon.mjs +1 -1
  122. package/src/server/memory-files.mjs +163 -0
  123. package/src/server/memory-metrics.mjs +178 -0
  124. package/src/server/metrics-history.mjs +94 -0
  125. package/src/server/metrics-run.mjs +100 -0
  126. package/src/server/music-routes.mjs +134 -0
  127. package/src/server/network-metrics.mjs +5 -3
  128. package/src/server/network-sampler.mjs +256 -0
  129. package/src/server/npm-latest.mjs +385 -0
  130. package/src/server/npm-upgrade.mjs +352 -0
  131. package/src/server/npx.mjs +41 -7
  132. package/src/server/output-watch.mjs +5 -4
  133. package/src/server/pinned-build.mjs +108 -0
  134. package/src/server/prefs-path.mjs +14 -0
  135. package/src/server/prefs-refusal.mjs +77 -0
  136. package/src/server/prefs-routes.mjs +76 -0
  137. package/src/server/prefs-set-aside.mjs +86 -0
  138. package/src/server/prefs-state.mjs +51 -0
  139. package/src/server/process-command.mjs +104 -0
  140. package/src/server/process-list.mjs +547 -0
  141. package/src/server/pulse-line.mjs +132 -0
  142. package/src/server/purge-key.mjs +7 -7
  143. package/src/server/quota-cli.mjs +174 -0
  144. package/src/server/quota-oauth.mjs +190 -0
  145. package/src/server/quota-shape.mjs +229 -0
  146. package/src/server/quota-store-resets.mjs +206 -0
  147. package/src/server/quota.mjs +123 -509
  148. package/src/server/relay-guard.mjs +3 -3
  149. package/src/server/request-gates.mjs +441 -0
  150. package/src/server/restart-note.mjs +180 -0
  151. package/src/server/retire-sound-hook.mjs +3 -3
  152. package/src/server/ring-bounds.mjs +215 -0
  153. package/src/server/running-deck.mjs +1 -1
  154. package/src/server/running-version.mjs +24 -0
  155. package/src/server/self-update.mjs +39 -1371
  156. package/src/server/session-enrichment.mjs +622 -0
  157. package/src/server/session-read-gate.mjs +49 -0
  158. package/src/server/session-tracking.mjs +242 -0
  159. package/src/server/sse-clients.mjs +222 -0
  160. package/src/server/stale-discovery.mjs +30 -0
  161. package/src/server/static-serve.mjs +81 -0
  162. package/src/server/supervisor.mjs +73 -12
  163. package/src/server/system-metrics.mjs +169 -1935
  164. package/src/server/term.mjs +11 -249
  165. package/src/server/thermal-metrics.mjs +520 -0
  166. package/src/server/thermal-sampler.mjs +204 -0
  167. package/src/server/token-redact.mjs +123 -0
  168. package/src/server/transcript-gate.mjs +107 -0
  169. package/src/server/transcript-scan.mjs +563 -0
  170. package/src/server/usage-routes.mjs +93 -0
  171. package/src/server/uv-bootstrap.mjs +1 -1
  172. package/src/server/wordmark.mjs +127 -0
  173. package/dist/web/assets/BrowserWatchModal-CMn28FHb.js +0 -1
  174. package/dist/web/assets/UsageHistoryModal-C-sPhsKT.js +0 -1
  175. package/dist/web/assets/index-C1YFI9f4.js +0 -298
  176. package/dist/web/assets/index-Ck2RAla3.css +0 -1
@@ -0,0 +1,271 @@
1
+ // Which ccusage runs, and running it once.
2
+ //
3
+ // Three paths answer, in an order getRunner explains below: a copy the user
4
+ // named or has on PATH, the deck's own managed install (ccusage-install.mjs),
5
+ // and `npx -y ccusage@latest` when neither is there. None of them is handed
6
+ // to a shell. What to ask ccusage and what to make of its answer are
7
+ // ccusage.mjs's; this module only turns an argument vector into a child and
8
+ // its stdout, and retries the one failure a rebuilt install can fix.
9
+ import { createRequire } from "node:module";
10
+ import { spawn } from "node:child_process";
11
+ import { existsSync } from "node:fs";
12
+ import { killTree, pathLookup, spawnSpec } from "./exec.mjs";
13
+ import { note, stamp, tagged } from "./ccusage-failure.mjs";
14
+ import {
15
+ discardDamagedInstall, installsDisabled, lastInstallError, maybeBackgroundUpdate, resolveEntry,
16
+ startInstall, touchMarker, winShim,
17
+ } from "./ccusage-install.mjs";
18
+
19
+ const TIMEOUT_MS = 90_000;
20
+
21
+ // npx is the same kind of shim as npm, and the fallback run needs the same
22
+ // treatment for a second, sharper reason: its argument vector carries a
23
+ // user-supplied `--since`. `shell: true` handed `[file, ...args].join(" ")` to
24
+ // /bin/sh -c, so `GET /api/ccusage?since=1;id;` ran `id` — and because that
25
+ // route is a GET, any page open in the user's browser could aim it at the
26
+ // loopback port with no CORS, no preflight and no need to read the answer.
27
+ // spawnSpec quotes each argument into the cmd.exe line on Windows and spawns
28
+ // the vector untouched everywhere else, so nothing in it is ever parsed as
29
+ // syntax. Naming the file `npx.cmd` on Windows is what makes that work: only a
30
+ // .cmd/.bat file routes through cmd.exe, and a bare `npx` there is not a file
31
+ // spawn can launch at all (PATHEXT is a shell's job), so asking for the
32
+ // extensionless name would trade a shell injection for an ENOENT. What it must
33
+ // NOT be is the bare `npx.cmd` this said until #456 — see winShim.
34
+ function npxSpec(args, platform = process.platform, deps) {
35
+ return spawnSpec(platform === "win32" ? winShim("npx.cmd", deps) : "npx", args, platform);
36
+ }
37
+
38
+ /**
39
+ * What `spawn` gets for the portable `npx -y ccusage@latest <args>` fallback.
40
+ * Exported for tests: the platform is a parameter so the Windows command line
41
+ * can be checked from any OS, and `deps` stands in for the Windows filesystem
42
+ * the shim lookup asks about.
43
+ */
44
+ export const fallbackSpec = (args = [], platform = process.platform, deps) =>
45
+ npxSpec(["-y", "ccusage@latest", ...args], platform, deps);
46
+
47
+ // ── the user's own copy ─────────────────────────────────────────────────────
48
+
49
+ /**
50
+ * A ccusage the USER put somewhere, either by naming it outright or by having
51
+ * it on PATH — or null when there is no such thing.
52
+ *
53
+ * This is #433, and the case for it is not that a PATH search is nice to have.
54
+ * The deck has been telling people "put ccusage on PATH yourself" in
55
+ * admin-failure.ts for as long as that sentence has existed, and nothing
56
+ * anywhere ever looked: a user who did exactly what they were told, and could
57
+ * prove it with `ccusage --version` in their own shell, got the identical
58
+ * failure on the next click. Two ways out of that, and the other one is to
59
+ * delete the promise. It is kept because there is a configuration that needs it
60
+ * and has no other:
61
+ *
62
+ * - `AGENTS_DECK_NO_INSTALL=1` is documented as "never install or update
63
+ * claude-swap / ccusage". Someone who sets it AND installs ccusage
64
+ * themselves has done the only thing that flag can sensibly mean, and until
65
+ * now the deck answered "ccusage is not installed" while it was installed
66
+ * and on their PATH. There was NO combination of settings that made a
67
+ * self-managed ccusage work.
68
+ * - The #432 machine — npm and npx both unusable on disk — cannot create the
69
+ * managed install and cannot run the npx fallback. A copy the user already
70
+ * has is the only route left, and it was the one route the deck refused to
71
+ * look down.
72
+ *
73
+ * The override wins over PATH the way AGENTS_DECK_CSWAP does over cswap's own
74
+ * search, and is never remembered, because somebody debugging a bad resolution
75
+ * needs a change to it to take effect on the next click rather than the next
76
+ * restart. Nothing here is cached for the same reason, and it is affordable:
77
+ * a lookup is a handful of stats, once per uncached fetch, against a process
78
+ * spawn that follows it.
79
+ *
80
+ * `platform` and `deps` are parameters, and this is exported, for the reason
81
+ * every other Windows answer in this module is: the PATHEXT walk and the
82
+ * `.cmd` spelling have to be checkable from a machine that cannot run Windows.
83
+ */
84
+ export function userCcusage(platform = process.platform, deps, env = process.env) {
85
+ const named = env.AGENTS_DECK_CCUSAGE;
86
+ if (named) {
87
+ // A path the user typed is used as typed — pathLookup would refuse it
88
+ // anyway, since re-rooting a name that carries a directory is a way to run
89
+ // something other than what was asked for. Whether it EXISTS is the
90
+ // caller's question, because "the path you gave me is not there" is a
91
+ // different sentence from "you have no ccusage".
92
+ return { file: String(named), named: true };
93
+ }
94
+ const found = pathLookup("ccusage", platform, deps);
95
+ return found ? { file: found, named: false } : null;
96
+ }
97
+
98
+ /** Does the file an explicit override names actually exist? Split out so the
99
+ * existence check is injectable alongside the lookup above. */
100
+ export const overrideIsThere = (file, { exists = existsSync } = {}) => {
101
+ try {
102
+ return exists(file);
103
+ } catch {
104
+ return false;
105
+ }
106
+ };
107
+
108
+ // Ensure a runnable ccusage. Returns { kind:"node", entry } for the managed
109
+ // install, { kind:"path", file } for a copy the user provided, or { kind:"npx" }
110
+ // as the portable fallback.
111
+ //
112
+ // The order is the whole design decision, so it is stated rather than implied.
113
+ //
114
+ // An explicit AGENTS_DECK_CCUSAGE is first and is never fallen through: a user
115
+ // who pointed the deck at a file and got silently ignored has been given a
116
+ // setting that does nothing, which is worse than not having one. If it does not
117
+ // resolve, the failure names THAT — "npx is not on this deck's PATH" would be
118
+ // both true and completely beside the point.
119
+ //
120
+ // The managed install then comes BEFORE the PATH copy, which is the opposite of
121
+ // what #433 proposed, and deliberately. Preferring PATH would silently change
122
+ // which ccusage runs on every machine that already has both — a deck that works
123
+ // today would start running a copy it has never run, and an old global ccusage
124
+ // without `daily --json` would turn a working panel into a broken one for no
125
+ // reason the user asked for. The PATH copy is an ESCAPE ROUTE, and it wants to
126
+ // be reached exactly when the managed install is not there; someone who wants
127
+ // their own copy to win over the deck's says so with AGENTS_DECK_CCUSAGE, which
128
+ // is unambiguous in a way a precedence rule never is.
129
+ //
130
+ // PATH comes before INSTALLING, though. Downloading a second copy of a tool
131
+ // that is already on the machine is not something to do to somebody, and it is
132
+ // what makes the order identical with and without AGENTS_DECK_NO_INSTALL=1 —
133
+ // that flag now removes a step rather than changing the sequence.
134
+ async function getRunner() {
135
+ const mine = userCcusage();
136
+ if (mine?.named) {
137
+ if (!overrideIsThere(mine.file)) {
138
+ throw tagged("bad_override",
139
+ `AGENTS_DECK_CCUSAGE points at ${mine.file}, and there is no such file`);
140
+ }
141
+ return { kind: "path", file: mine.file, named: true };
142
+ }
143
+ let resolved = resolveEntry();
144
+ if (resolved) {
145
+ maybeBackgroundUpdate(resolved.version);
146
+ return { kind: "node", entry: resolved.entry };
147
+ }
148
+ if (mine) return { kind: "path", file: mine.file, named: false };
149
+ // Nothing installed, nothing of the user's to run, and installs are
150
+ // forbidden. The npx fallback is not an escape hatch — `npx -y ccusage@latest`
151
+ // downloads and runs the same package — so there is no runner to hand back.
152
+ // Fail with the reason, which the usage-history modal shows, instead of
153
+ // quietly installing.
154
+ if (installsDisabled()) {
155
+ throw tagged("no_install", "ccusage is not installed, and installs are off (AGENTS_DECK_NO_INSTALL=1)");
156
+ }
157
+ // Cold: install once (deduped across concurrent callers).
158
+ await startInstall("latest");
159
+ resolved = resolveEntry();
160
+ if (resolved) { touchMarker(); return { kind: "node", entry: resolved.entry }; }
161
+ // npm unavailable / offline → fall back to npx, carrying WHY the managed
162
+ // install is not here. Without that the modal can only describe the fallback,
163
+ // and the fallback is the second thing that failed.
164
+ return { kind: "npx", installError: lastInstallError() };
165
+ }
166
+
167
+ // ── invocation ──────────────────────────────────────────────────────────────
168
+
169
+ /**
170
+ * What `spawn` gets for a ccusage the user provided.
171
+ *
172
+ * `file` is always an absolute path by the time it reaches here — pathLookup
173
+ * resolves the directory and an override is a path the user typed — and that is
174
+ * load-bearing rather than tidy. On Windows the thing on PATH is `ccusage.cmd`,
175
+ * a batch file, so spawnSpec routes it through cmd.exe; a batch file launched
176
+ * by BARE name computes `%~dp0` from the deck's working directory and goes
177
+ * looking for its payload there, which is #456 exactly. Resolving first and
178
+ * quoting second is the same order the npm and npx shims go through.
179
+ *
180
+ * Exported for tests: the platform is a parameter so the Windows command line
181
+ * can be checked from any OS.
182
+ */
183
+ export const userSpec = (file, args = [], platform = process.platform) =>
184
+ spawnSpec(file, args, platform);
185
+
186
+ /**
187
+ * The platform binary ccusage's own cli.js would run, resolved the way cli.js
188
+ * resolves it (`@ccusage/ccusage-<platform>-<arch>/bin/ccusage[.exe]`, from
189
+ * the package's own directory), or null when there is none.
190
+ *
191
+ * WINDOWS ONLY, and for one reason: cli.js starts that binary WITHOUT hiding
192
+ * its console. The deck runs with no console of its own (detached, or inside
193
+ * the desktop app), so the binary is given a new one — and with Windows
194
+ * Terminal as the default terminal that is a window flashing up on every
195
+ * usage read, measured on a Windows 10 box as a CASCADIA_HOSTING_WINDOW_CLASS
196
+ * hosting ccusage. Running the binary directly, with windowsHide, is what
197
+ * cli.js does minus the window. Elsewhere nothing flashes, and the wrapper
198
+ * stays in the path it has always been in.
199
+ *
200
+ * Exported for tests; `resolve` is injectable.
201
+ */
202
+ export function nativeCcusage(entry, platform = process.platform, arch = process.arch, resolve = null) {
203
+ if (platform !== "win32" || typeof entry !== "string" || !entry) return null;
204
+ const pkg = arch === "arm64" ? "@ccusage/ccusage-win32-arm64" : arch === "x64" ? "@ccusage/ccusage-win32-x64" : null;
205
+ if (!pkg) return null;
206
+ try {
207
+ const req = resolve ?? createRequire(entry).resolve;
208
+ return req(`${pkg}/bin/ccusage.exe`);
209
+ } catch {
210
+ return null;
211
+ }
212
+ }
213
+
214
+ // One attempt with one runner. No branch gets a shell. The managed install is
215
+ // `node <entry> …`, which never needed one; the user's own copy and the npx
216
+ // fallback are routed through spawnSpec instead — see npxSpec and userSpec, and
217
+ // note that `args` here ends in whatever /api/ccusage was asked for.
218
+ function runOnce(runner, args) {
219
+ const native = runner.kind === "node" ? nativeCcusage(runner.entry) : null;
220
+ const { file, args: full, opts } = native
221
+ ? { file: native, args, opts: {} }
222
+ : runner.kind === "node"
223
+ ? { file: process.execPath, args: [runner.entry, ...args], opts: {} }
224
+ : runner.kind === "path"
225
+ ? userSpec(runner.file, args)
226
+ : fallbackSpec(args);
227
+ return new Promise((resolve, reject) => {
228
+ const child = spawn(file, full, { windowsHide: true, ...opts });
229
+ let out = "", err = "";
230
+ const timer = setTimeout(() => {
231
+ // The npx fallback goes through cmd.exe on Windows, so `child` is the
232
+ // wrapper there and npx is a grandchild that a plain kill would leave
233
+ // downloading.
234
+ killTree(child);
235
+ reject(tagged("timeout", "ccusage timed out"));
236
+ }, TIMEOUT_MS);
237
+ child.stdout.on("data", d => { out += d; });
238
+ child.stderr.on("data", d => { err += d; });
239
+ child.on("error", e => { clearTimeout(timer); reject(e); });
240
+ child.on("close", code => {
241
+ clearTimeout(timer);
242
+ if (code === 0) resolve(out);
243
+ else reject(new Error(err.trim() || `ccusage exited ${code}`));
244
+ });
245
+ });
246
+ }
247
+
248
+ // Run ccusage with the given args, resolve raw stdout AND the runner that
249
+ // produced it. One retry, and only for the one failure that is otherwise
250
+ // permanent — see discardDamagedInstall. The second getRunner() is what rebuilds
251
+ // the install, or falls through to npx when npm cannot.
252
+ //
253
+ // The runner comes back with the output because the caller has to say which
254
+ // path answered: `extractJson` in ccusage.mjs can fail on a run that started
255
+ // perfectly well, and "ccusage ran but printed no usage data" reads
256
+ // differently depending on which ccusage that was.
257
+ export async function runCcusage(args) {
258
+ const runner = await getRunner();
259
+ try {
260
+ return { out: await runOnce(runner, args), runner };
261
+ } catch (err) {
262
+ if (!discardDamagedInstall(runner, err)) throw stamp(err, runner);
263
+ note("managed install was unusable, rebuilding it", err);
264
+ const rebuilt = await getRunner();
265
+ try {
266
+ return { out: await runOnce(rebuilt, args), runner: rebuilt };
267
+ } catch (again) {
268
+ throw stamp(again, rebuilt);
269
+ }
270
+ }
271
+ }