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,42 @@
1
+ // GET /api/health: which deck this is, what it has accepted, who is watching
2
+ // it, what it captures, and whether what it draws is being kept.
3
+ //
4
+ // This lived in src/server/index.mjs, after Clear. It reads the newest seq
5
+ // through event-ring.mjs, the pages and the desktop app's trays through
6
+ // sse-clients.mjs, the deck's scope through deck-scope.mjs and the log's state
7
+ // through event-log.mjs and log-writer.mjs. The body is unchanged but for the
8
+ // two scope fields, which it now reads through their getters.
9
+ import { send } from "./http-io.mjs";
10
+ import { lastSeq } from "./event-ring.mjs";
11
+ import { pageCount, trayClients } from "./sse-clients.mjs";
12
+ import { deckProviders, deckWorkspace } from "./deck-scope.mjs";
13
+ import { eventLogPath, logWritableNow } from "./event-log.mjs";
14
+ import { appendFailureStats } from "./log-writer.mjs";
15
+
16
+ export function handleHealth(_req, res) {
17
+ send(res, 200, {
18
+ ok: true,
19
+ name: "agent-dag",
20
+ seq: lastSeq(),
21
+ clients: pageCount(),
22
+ // The desktop app's tray connections, which are not pages (#1160).
23
+ trays: trayClients.size,
24
+ uptimeMs: Math.round(process.uptime() * 1000),
25
+ workspace: deckWorkspace(),
26
+ providers: deckProviders(),
27
+ // WHETHER THE EVENTS BEING DRAWN ARE BEING KEPT. `seq` above counts what
28
+ // the deck accepted, and it counted a deck whose every append was failing
29
+ // exactly the same as one whose every append landed. `log` is the other
30
+ // half of that sentence: `writable` is the newest evidence about the log —
31
+ // the boot probe's answer until a line lands after it and the appender's
32
+ // from then on, the same answer the Restart gate reads (#1130) — `failing`
33
+ // says the appender is inside a failure episode right now, and
34
+ // `failedLines` / `failedChars` are what has been attempted and lost since
35
+ // this deck started.
36
+ //
37
+ // No path. The health probe is a deliberately open route and this is the
38
+ // smallest set of facts that answers the question; the path is already in
39
+ // the banner for anyone standing at the terminal.
40
+ log: eventLogPath() ? { writable: logWritableNow(), ...appendFailureStats(eventLogPath()) } : null,
41
+ });
42
+ }
@@ -0,0 +1,37 @@
1
+ // GET /api/hook-challenge, where a hook asks whether the process on this port
2
+ // is the deck its discovery record describes, and the token that record
3
+ // carries for it to be asked about.
4
+ //
5
+ // These lived in src/server/index.mjs, after health. The token is
6
+ // request-gates.mjs's and the proof deck-probe.mjs's; this answers with one.
7
+ // The bodies are unchanged.
8
+ import { HOOK_TOKEN } from "./request-gates.mjs";
9
+ import { challengeProof } from "./deck-probe.mjs";
10
+ import { send } from "./http-io.mjs";
11
+
12
+ /** The token this deck expects to be challenged on. Written by writeDiscovery. */
13
+ export function hookToken() { return HOOK_TOKEN; }
14
+
15
+ // GET /api/hook-challenge?nonce=… — answer a hook's challenge.
16
+ //
17
+ // The nonce is the caller's, so the answer proves knowledge of the token
18
+ // without disclosing it, and proves it for this exchange only. Answering
19
+ // freely is what the exchange requires: the hook is asking whether the process
20
+ // on this port is the deck that wrote the discovery file, and it asks precisely
21
+ // because it does not yet know — a deck that demanded credentials before
22
+ // answering could not be told apart from a stranger that refuses.
23
+ //
24
+ // So this route is an oracle, and it must stay one. That makes its answer
25
+ // useless as a credential FOR this server, and nothing here may ever accept it
26
+ // as one: a caller who can GET this can obtain a valid proof for any nonce, so
27
+ // a gate honouring proofs is a gate honouring anybody. See presentsDeckToken,
28
+ // which takes the token itself and refuses the hashed form for this reason.
29
+ //
30
+ // What the free answer does NOT give away is the token: the response is a
31
+ // one-way hash of it, and behind isTrustedRead, the read gate in front of the
32
+ // route table, a rebound page cannot see even that.
33
+ export function handleHookChallenge(_req, res, url) {
34
+ const nonce = url.searchParams.get("nonce") ?? "";
35
+ if (!nonce || nonce.length > 256) return send(res, 400, { error: "bad nonce" });
36
+ send(res, 200, { proof: challengeProof(HOOK_TOKEN, nonce) });
37
+ }
@@ -0,0 +1,104 @@
1
+ // The two ends of a JSON route's exchange with its caller: reading the body it
2
+ // posted, and answering it.
3
+ //
4
+ // These lived in src/server/index.mjs beside the static file server, and every
5
+ // route handler there calls one or both. They hold no state and know nothing
6
+ // about the deck — send writes one JSON reply at most, readBody collects a
7
+ // capped body and answers 413 itself when it is too big — so they sit in a leaf
8
+ // that a module of route handlers can import without importing the server. The
9
+ // 413 and the drain after it are one rule with two callers: handleEventIngest
10
+ // keeps its own copy of the shape for a larger cap and imports the drain's
11
+ // deadline from here.
12
+ //
13
+ // sendInternalError joined them from index.mjs when the event routes left it:
14
+ // it is the answer to a handler that threw, the route table's `guard` and its
15
+ // last line of defence both call it, and so does the ingest route.
16
+ import { PRODUCT } from "./brand.mjs";
17
+
18
+ export function send(res, status, body, headers = {}) {
19
+ // ALREADY ANSWERED. A handler that replies after something upstream has
20
+ // already written a status would otherwise throw ERR_HTTP_HEADERS_SENT out
21
+ // of the route and become a 500 — or, on a streamed answer, corrupt it. The
22
+ // oversize path below relies on this: readBody answers 413 itself and then
23
+ // rejects into a caller whose own `send(res, 400, ...)` must be a no-op
24
+ // rather than a second reply.
25
+ if (res.headersSent) return;
26
+ res.writeHead(status, {
27
+ "Content-Type": "application/json; charset=utf-8",
28
+ "Cache-Control": "no-store",
29
+ ...headers,
30
+ });
31
+ res.end(typeof body === "string" ? body : JSON.stringify(body));
32
+ }
33
+
34
+ // How long an oversized POST is drained after it has been refused, so the 413
35
+ // reaches a poster that is still uploading. Generous next to hook.js's own
36
+ // one-second budget, and finite so nothing can sit on the socket indefinitely.
37
+ export const OVERSIZE_DRAIN_MS = 10_000;
38
+
39
+ /**
40
+ * Collect a request body as a string, capped so a bad client can't fill memory.
41
+ *
42
+ * IT ANSWERS BEFORE IT HANGS UP, which it did not. `req.destroy()` ran ahead of
43
+ * the reject, so the caller's own `send(res, 400, ...)` had no socket left to
44
+ * write to: every one of the eleven routes through here replied to an oversized
45
+ * body with a bare connection reset. Measured with a 70 KB body — `curl` exit
46
+ * 56, "failure receiving network data", no status line, and nothing in the log.
47
+ * `POST /api/lan/sync` is the one that matters, because its caller is another
48
+ * deck rather than a person: it saw a reset it could not tell from a deck that
49
+ * had died, and reported a network error rather than "too big".
50
+ *
51
+ * handleEventIngest was fixed for exactly this and its comment asserts this
52
+ * function already got it right. It did not; this is that fix, in the shape
53
+ * that one worked out and documents at length:
54
+ *
55
+ * • answer 413 here, so there is a status on the wire;
56
+ * • keep reading and throw it away, because the poster is still mid-upload
57
+ * and hanging up now lands its next write on a dead socket — it aborts with
58
+ * EPIPE and discards the answer already sitting in its receive buffer;
59
+ * • bound the drain, because draining forever is its own denial of service.
60
+ *
61
+ * The reject still fires, so the callers' `.catch(() => null)` and their
62
+ * `send(res, 400, ...)` are unchanged — `send` is a no-op once the headers have
63
+ * gone out.
64
+ */
65
+ export function readBody(req, res = null, limit = 64_000) {
66
+ return new Promise((resolve, reject) => {
67
+ let body = "";
68
+ let refused = false;
69
+ req.setEncoding("utf8");
70
+ req.on("data", c => {
71
+ if (refused) return;
72
+ body += c;
73
+ if (body.length > limit) {
74
+ refused = true;
75
+ body = "";
76
+ if (res) send(res, 413, { error: "body too large" });
77
+ req.resume();
78
+ const grace = setTimeout(() => req.destroy(), OVERSIZE_DRAIN_MS);
79
+ grace.unref?.();
80
+ req.on("close", () => clearTimeout(grace));
81
+ reject(new Error("body too large"));
82
+ }
83
+ });
84
+ req.on("end", () => { if (!refused) resolve(body); });
85
+ req.on("error", reject);
86
+ });
87
+ }
88
+
89
+ // A request handler rejected. Two audiences, two different amounts of detail:
90
+ // the operator, who needs the whole error — stack included — to find the bug,
91
+ // and the HTTP client, which needs to know only that the request failed.
92
+ //
93
+ // They used to get the same string, on the theory that this server binds
94
+ // 127.0.0.1 and its only client is the user's own tab. A DNS-rebound page
95
+ // reaches a loopback server as same-origin and can read the body, and the
96
+ // errors that land here carry absolute paths out of the user's home directory
97
+ // — a failed rename of ~/.claude/settings.json, an ENOENT from an import. So
98
+ // stderr keeps every byte and the response body carries none of it; nothing is
99
+ // swallowed, it just stops travelling over the wire.
100
+ export function sendInternalError(res, err, log = console.error) {
101
+ log(`${PRODUCT}: request handler failed:`, err);
102
+ if (!res.headersSent) send(res, 500, { error: "internal error" });
103
+ else res.end();
104
+ }