ccdeck 3.29.11 → 3.31.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 (178) hide show
  1. package/README.md +4 -3
  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-n63GjLhv.js +1 -0
  15. package/dist/web/assets/UsageHistoryModal-DEwBX4Fk.js +1 -0
  16. package/dist/web/assets/index-CZJf1Cpp.js +299 -0
  17. package/dist/web/assets/index-D4q1ElXU.css +1 -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 +40 -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 +7 -3
  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 +2 -2
  67. package/src/server/deck-prefs.mjs +58 -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 +174 -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 +110 -0
  134. package/src/server/prefs-give-back.mjs +206 -0
  135. package/src/server/prefs-path.mjs +14 -0
  136. package/src/server/prefs-refusal.mjs +86 -0
  137. package/src/server/prefs-routes.mjs +99 -0
  138. package/src/server/prefs-set-aside.mjs +86 -0
  139. package/src/server/prefs-state.mjs +70 -0
  140. package/src/server/process-command.mjs +104 -0
  141. package/src/server/process-list.mjs +547 -0
  142. package/src/server/provider-status.mjs +307 -0
  143. package/src/server/pulse-line.mjs +132 -0
  144. package/src/server/purge-key.mjs +7 -7
  145. package/src/server/quota-cli.mjs +174 -0
  146. package/src/server/quota-oauth.mjs +190 -0
  147. package/src/server/quota-shape.mjs +229 -0
  148. package/src/server/quota-store-resets.mjs +206 -0
  149. package/src/server/quota.mjs +123 -509
  150. package/src/server/relay-guard.mjs +3 -3
  151. package/src/server/request-gates.mjs +441 -0
  152. package/src/server/restart-note.mjs +180 -0
  153. package/src/server/retire-sound-hook.mjs +3 -3
  154. package/src/server/ring-bounds.mjs +215 -0
  155. package/src/server/running-deck.mjs +1 -1
  156. package/src/server/running-version.mjs +24 -0
  157. package/src/server/self-update.mjs +39 -1371
  158. package/src/server/session-enrichment.mjs +622 -0
  159. package/src/server/session-read-gate.mjs +49 -0
  160. package/src/server/session-tracking.mjs +242 -0
  161. package/src/server/sse-clients.mjs +222 -0
  162. package/src/server/stale-discovery.mjs +30 -0
  163. package/src/server/static-serve.mjs +81 -0
  164. package/src/server/supervisor.mjs +73 -12
  165. package/src/server/system-metrics.mjs +169 -1935
  166. package/src/server/term.mjs +11 -249
  167. package/src/server/thermal-metrics.mjs +520 -0
  168. package/src/server/thermal-sampler.mjs +204 -0
  169. package/src/server/token-redact.mjs +123 -0
  170. package/src/server/transcript-gate.mjs +107 -0
  171. package/src/server/transcript-scan.mjs +563 -0
  172. package/src/server/usage-routes.mjs +108 -0
  173. package/src/server/uv-bootstrap.mjs +1 -1
  174. package/src/server/wordmark.mjs +127 -0
  175. package/dist/web/assets/BrowserWatchModal-CyAFTvAY.js +0 -1
  176. package/dist/web/assets/UsageHistoryModal-KJfli0oa.js +0 -1
  177. package/dist/web/assets/index-Ck2RAla3.css +0 -1
  178. package/dist/web/assets/index-jmrcEsCj.js +0 -298
@@ -0,0 +1,150 @@
1
+ // The one spelling of a directory the deck compares against: the
2
+ // `--workspace` flag, and the cwd a Codex rollout says it ran in.
3
+ //
4
+ // These lived in src/server/index.mjs, between the health payload's workspace
5
+ // and the Clear route. Neither touches the server: canonicalWorkspace runs once
6
+ // in bin/deck.js before a server exists, and canonicalCwd is a realpath the
7
+ // rollout watcher asks for once per file. So they moved to a leaf, unchanged,
8
+ // and index.mjs imports the one it calls and re-exports both under the names
9
+ // bin/deck.js and the tests have always imported.
10
+ import { realpath as realpathCb, realpathSync } from "node:fs";
11
+ import { resolve } from "node:path";
12
+ import { promisify } from "node:util";
13
+
14
+ /**
15
+ * The one spelling of `--workspace` everything downstream compares against.
16
+ * Empty — including a value that is nothing but spaces — stays empty, which is
17
+ * how every reader of it says "machine-wide".
18
+ *
19
+ * Called once, in bin/deck.js, where the flag arrives: that process's cwd is the
20
+ * shell the user typed the command in, and it is the only process on either
21
+ * capture path whose cwd is the one a relative `--workspace ./sub` was written
22
+ * against. The raw string used to go straight into the discovery file, and
23
+ * hook.js resolved it inside its own process — which the host CLI runs with the
24
+ * AGENT's cwd — so `--workspace ./sub` scoped Claude sessions to a `sub`
25
+ * directory under whatever the agent happened to be working in, a different
26
+ * directory per agent and none of them the one asked for. The Codex path
27
+ * resolved the same string in the server process and was right. Resolving here
28
+ * makes both of them right, and for the same reason bin/deck.js already resolves
29
+ * the events log before publishing it: what goes in the discovery file is read
30
+ * by other processes that cannot reconstruct the context it was written in.
31
+ *
32
+ * Symlinks are resolved too, so that `--workspace /tmp/proj` on a Mac is not
33
+ * scoped to /tmp/proj while every session inside it reports /private/tmp/proj,
34
+ * leaving the deck empty. A path that does not exist yet keeps its resolved form
35
+ * rather than failing: scoping a deck to a directory you are about to create is
36
+ * not an error.
37
+ *
38
+ * That resolution is only half the rule. Canonicalising the flag means nothing
39
+ * unless the cwd it is compared against is canonicalised the same way, and that
40
+ * is a separate job on each capture path: hook.js does it with normPath, and the
41
+ * rollout watcher does it with canonicalCwd below. This comment used to claim
42
+ * the second half came for free — that "a process's cwd comes from getcwd() and
43
+ * has none left in it" — which is true on POSIX and false on Windows. See
44
+ * canonicalCwd.
45
+ *
46
+ * WHICH REALPATH, AND WHY IT IS `.native` AT ALL THREE SITES. Node ships two
47
+ * implementations with different Windows behaviour, and picking one per site is
48
+ * how the flag and the two cwd paths end up meaning different things again:
49
+ *
50
+ * fs.realpathSync / fs.realpath a JavaScript lstat-and-readlink walk. It
51
+ * resolves symlinks and junctions and NOTHING
52
+ * else — a DOS 8.3 short component survives it
53
+ * untouched.
54
+ * …Sync.native / …native / the uv_fs_realpath, which on Windows is
55
+ * fs/promises realpath GetFinalPathNameByHandleW: symlinks and
56
+ * junctions, the UNC form of a mapped drive,
57
+ * the LONG form of an 8.3 short name, and the
58
+ * on-disk case of every component.
59
+ *
60
+ * The long form is the canonical one, and not by preference: it is the only
61
+ * spelling that is a fixed point. Short names are an alias for the same
62
+ * directory exactly as a junction is, they cannot be derived back from the long
63
+ * form (8.3 generation can be disabled per volume), and they are not a corner
64
+ * case a test invented — `%TEMP%` under a shortened profile directory answers
65
+ * with one, which is what every GitHub Windows runner has. It is also already
66
+ * this codebase's answer: persistAuth in src/server/codex-auth.mjs resolves
67
+ * through the fs/promises realpath, i.e. this one.
68
+ *
69
+ * So all three sites name `.native` explicitly rather than one of them reaching
70
+ * for whichever realpath came to hand. Being explicit is the point — the reason
71
+ * this had to be said twice is that fs/promises' realpath IS the native one
72
+ * while its sync namesake is not, an equivalence nothing documents and no reader
73
+ * should have to know.
74
+ */
75
+ export function canonicalWorkspace(raw) {
76
+ if (typeof raw !== "string" || raw.trim() === "") return "";
77
+ const abs = resolve(raw);
78
+ try { return realpathSync.native(abs); } catch { return abs; }
79
+ }
80
+
81
+ // The async half of the rule canonicalWorkspace states above, named the same way
82
+ // it is: fs.realpath.native is the documented callback form of uv_fs_realpath,
83
+ // and promisifying it says which realpath this is at the point of use. The
84
+ // fs/promises realpath is the same call and would have read as if it were the
85
+ // JavaScript one.
86
+ const realpathNative = promisify(realpathCb.native);
87
+
88
+ /**
89
+ * The one spelling of the directory a Codex session says it is running in —
90
+ * hook.js's normPath, for the capture path that never goes through the hook.
91
+ *
92
+ * `--workspace` is canonicalised above before anything compares against it, and
93
+ * the Claude side canonicalises the session's cwd to match: hook.js runs every
94
+ * incoming cwd through normPath (resolve + realpath) before asking
95
+ * capturesSession. The Codex side compared the rollout header's `cwd` raw, and
96
+ * the comment above said that was safe because a cwd comes from getcwd(), which
97
+ * has already resolved every link.
98
+ *
99
+ * That is true of getcwd(3) and not true of Windows. There the current directory
100
+ * is stored as the string it was set with, and GetCurrentDirectoryW — what
101
+ * Rust's std::env::current_dir() behind Codex calls — hands that string back
102
+ * without resolving a junction, a `subst` drive or a mapped network drive.
103
+ * realpath does resolve them, and on a mapped drive goes further and returns the
104
+ * UNC form, because libuv asks GetFinalPathNameByHandleW. So a deck started as
105
+ * `--workspace Z:\proj` scoped itself to `\\server\share\proj`, the Claude
106
+ * session in that tree reported `Z:\proj` and was realpath'd into the workspace
107
+ * and drawn, and the Codex session beside it reported `Z:\proj` in its rollout
108
+ * header, was compared raw, and silently never appeared — no error printed
109
+ * anywhere, the banner still claiming the rollout watcher was running. The same
110
+ * asymmetry runs in reverse for a user who passes the resolved path and works
111
+ * through the junction. The log election went wrong with it: writesCodexLog
112
+ * models the OTHER decks' capture with this same predicate against their
113
+ * published (canonical) workspaces, so it was picking the wrong group.
114
+ *
115
+ * Called at the one read of the header (readCodexHeader, in codex-watch.mjs)
116
+ * rather than inside codexCwdInWorkspace: the result is cached in codexFileState for
117
+ * the life of the file, so it costs one realpath per rollout instead of one per
118
+ * tick, and it leaves the predicate the pure string function that lets it be
119
+ * pinned against hook.js's copy in a test.
120
+ *
121
+ * `.native`, for the reason canonicalWorkspace sets out at length: three sites
122
+ * canonicalise a path against each other and all three must fold the same way,
123
+ * 8.3 short names included. Case IS canonicalised as a side effect — that is
124
+ * what GetFinalPathNameByHandleW and realpath(3) on a case-insensitive volume
125
+ * return — but nothing here DEPENDS on it: the two predicates still fold case
126
+ * per-platform themselves, which is the only place that decision can stay
127
+ * correct on Linux, where /srv/Proj and /srv/proj are two real directories and
128
+ * realpath quite rightly keeps them apart.
129
+ *
130
+ * Async, unlike canonicalWorkspace, because of where each one runs.
131
+ * canonicalWorkspace runs once in bin/deck.js before the server exists, so
132
+ * blocking there costs nothing. This runs inside the watcher's 1.5s tick, in the
133
+ * live event loop, against a path a rollout recorded some time ago — which on
134
+ * the very platform this exists for is quite likely to name a mapped drive that
135
+ * is no longer connected, and a synchronous realpath on one of those blocks the
136
+ * whole dashboard until SMB times out.
137
+ *
138
+ * A cwd that no longer resolves keeps its resolved form, exactly as the flag
139
+ * does: a deleted directory or a disconnected drive is not a reason to drop a
140
+ * session the deck can still draw, and the resolved string is the best answer
141
+ * available — for a rollout recorded in the tree the deck is scoped to and never
142
+ * moved, it is also the right one. Anything that is not a non-empty string is
143
+ * null, which is what readCodexHeader returned before and what both copies of
144
+ * the predicate read as "this session never said where it runs".
145
+ */
146
+ export async function canonicalCwd(raw) {
147
+ if (typeof raw !== "string" || raw.trim() === "") return null;
148
+ const abs = resolve(raw);
149
+ try { return await realpathNative(abs); } catch { return abs; }
150
+ }
@@ -0,0 +1,64 @@
1
+ // How a failed ccusage read is told, to the two audiences it has.
2
+ //
3
+ // The usage-history modal gets a `reason`, the `stage` that failed and, when it
4
+ // is a file of the user's, the `bin` — so it can say which path went wrong in
5
+ // its own words and keep the raw text one hover away. The terminal gets one
6
+ // line. Every module that runs or installs ccusage says what went wrong
7
+ // through these four, so the words stay the same whichever of them it was.
8
+ import { oneLine, termColumns } from "./term.mjs";
9
+ import { PRODUCT } from "./brand.mjs";
10
+
11
+ // A run can end four ways that mean four different things to whoever opened the
12
+ // modal — installs are forbidden, the deadline expired, the output was not usage
13
+ // data, the CLI exited on its own — and all four used to arrive as one `error`
14
+ // string, leaving the modal nothing to say but whatever `err.message` happened
15
+ // to be. The code rides on the error and comes back out as `reason`; `error`
16
+ // keeps the raw text, which the modal shows only on hover.
17
+ export function tagged(reason, message) {
18
+ return Object.assign(new Error(message), { reason });
19
+ }
20
+
21
+ // Everything the ccusage modules say out loud goes through here, and it says one
22
+ // line.
23
+ //
24
+ // Both failures it reports, a failed install and a failed run, carry a
25
+ // subprocess's entire stderr as their `message`, and on a machine whose npm
26
+ // shim is broken that is a fifteen-line Node stack trace. Handed to
27
+ // console.error whole, it landed across the deck's own status rows and its
28
+ // `\r`-repainted pulse line — and because primeCcusage runs at boot, it was
29
+ // the first thing such a machine ever showed (#432). The evidence
30
+ // is not lost by shortening this: a failed RUN carries its full text back to
31
+ // the browser in `error`, which the usage-history modal keeps on the status
32
+ // line's title, one hover away — the same division of labour admin-failure.ts
33
+ // states for claude-swap's output. What this line is for is the operator
34
+ // watching the terminal, who needs to know which of the two things failed and
35
+ // why, not to read a stack.
36
+ //
37
+ // The width is read per call: a terminal can be resized while the deck runs,
38
+ // and this can fire hours in.
39
+ export function note(what, err) {
40
+ const head = `${PRODUCT} ccusage: ${what}: `;
41
+ console.error(head + oneLine(err?.message ?? err, termColumns(process.stderr) - head.length));
42
+ }
43
+
44
+ /** Which of ccusage's three paths a failure came from, in the deck's own words.
45
+ * `stage` travels to the browser; the modal leads with it rather than making
46
+ * the reader guess from a stack trace which path they are looking at. */
47
+ const STAGE = { node: "managed", path: "path", npx: "npx" };
48
+
49
+ /** Mark a failure with the path that produced it, and with the managed
50
+ * install's own account when that is why this path was taken at all. Set once:
51
+ * runCcusage's retry re-stamps with the runner that actually failed, and an
52
+ * error that already knows where it came from is not overwritten. */
53
+ export function stamp(err, runner) {
54
+ if (err && typeof err === "object") {
55
+ if (!err.stage) err.stage = STAGE[runner?.kind] ?? "npx";
56
+ // Which file, when the answer is a file the deck did not put there. The
57
+ // stage alone says "your own copy failed" and leaves the reader to work out
58
+ // WHICH copy, and on a machine with an override, a PATH entry and a managed
59
+ // install that is exactly the question they cannot answer from here.
60
+ if (runner?.kind === "path" && err.bin === undefined) err.bin = runner.file;
61
+ if (runner?.installError && err.install === undefined) err.install = runner.installError;
62
+ }
63
+ return err;
64
+ }