ccdeck 3.29.11 → 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 +22 -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-CyAFTvAY.js +0 -1
  174. package/dist/web/assets/UsageHistoryModal-KJfli0oa.js +0 -1
  175. package/dist/web/assets/index-Ck2RAla3.css +0 -1
  176. package/dist/web/assets/index-jmrcEsCj.js +0 -298
@@ -17,6 +17,10 @@
17
17
  // The two exceptions are the cursor constants, which are strings the caller
18
18
  // writes, because who restores the cursor and when is a lifecycle question and
19
19
  // belongs with the lifecycle.
20
+ //
21
+ // What the deck says with these answers lives beside them: the drawn wordmark
22
+ // the boot opens with in wordmark.mjs, and the pulse line it leaves on screen
23
+ // in pulse-line.mjs.
20
24
 
21
25
  // ── colour profile ───────────────────────────────────────────────────────────
22
26
  //
@@ -104,8 +108,8 @@ export function fg([r, g, b], profile, fallback) {
104
108
  }
105
109
 
106
110
  // The canvas' own tokens (src/web/styles.css), so the terminal and the page are
107
- // one product rather than two. The ramp is the wordmark's vertical gradient:
108
- // lit at the top, settling into the deeper tone at the baseline.
111
+ // one product rather than two. The ramp is the wordmark's vertical gradient
112
+ // (wordmark.mjs): lit at the top, settling into the deeper tone at the baseline.
109
113
  const BRAND = {
110
114
  accentSoft: [[186, 230, 253], "\x1b[96m"],
111
115
  accent: [[125, 211, 252], "\x1b[96m"],
@@ -233,8 +237,9 @@ export const SPINNER_ELAPSED_AFTER_MS = 3_000;
233
237
  *
234
238
  * Whole seconds, floored, and never a tenth: a number that changes ten times a
235
239
  * second is a second spinner rather than an answer about the first one. Here
236
- * rather than in bin/deck.js because that file runs a deck when it is imported,
237
- * and this is the one part of `step` worth holding still in a test.
240
+ * rather than beside `step` in bin/cli/screen.js, which asks the real terminal
241
+ * its questions the moment it is imported, and this is the one part of `step`
242
+ * worth holding still in a test.
238
243
  */
239
244
  export function elapsedSuffix(ms, after = SPINNER_ELAPSED_AFTER_MS) {
240
245
  return ms < after ? "" : ` ${Math.floor(ms / 1000)}s`;
@@ -432,255 +437,12 @@ export function statusLine({
432
437
  return `${pad}${paintMark(mark)} ${paintLabel(padded)} ${paintDetail(fitted)}`;
433
438
  }
434
439
 
435
- // ── wordmark ─────────────────────────────────────────────────────────────────
436
- //
437
- // Drawn, not pasted out of a figlet font — six letters on a 4×6 pixel grid,
438
- // printed two pixel rows to a text row with the half-block characters, which is
439
- // what gives it a lowercase with real ascenders in three lines instead of six.
440
- // The old banner said `agents-deck`, a name the repo, the command, the README
441
- // and the docs stopped using; this one says what the product is called.
442
-
443
- const LETTERS = {
444
- c: ["....", "....", ".###", "#...", "#...", ".###"],
445
- d: ["...#", "...#", ".###", "#..#", "#..#", ".###"],
446
- e: ["....", "....", ".###", "####", "#...", ".###"],
447
- k: ["#...", "#...", "#.##", "##..", "#.#.", "#..#"],
448
- };
449
-
450
- /** Two pixel rows into one text row: ▀ upper only, ▄ lower only, █ both. */
451
- function squash(top, bottom) {
452
- let out = "";
453
- for (let i = 0; i < top.length; i++) {
454
- const t = top[i] === "#";
455
- const b = bottom[i] === "#";
456
- out += t && b ? "█" : t ? "▀" : b ? "▄" : " ";
457
- }
458
- return out;
459
- }
460
-
461
- function renderWord(word) {
462
- const rows = ["", "", ""];
463
- for (const [n, ch] of [...word].entries()) {
464
- const px = LETTERS[ch];
465
- for (let r = 0; r < 3; r++) rows[r] += (n ? " " : "") + squash(px[r * 2], px[r * 2 + 1]);
466
- }
467
- return rows.map((r) => r.replace(/\s+$/, ""));
468
- }
469
-
470
- /** The drawn mark itself, three text rows of half-blocks.
471
- *
472
- * Exported for one reader, and it is a test rather than a caller (#383). This
473
- * is the ONLY place the art's true width can be measured: `renderWord` trims
474
- * each row's trailing blanks, so the rows are not all the same length, and the
475
- * arithmetic in WORDMARK_WIDTH below is a hand-computed prediction of the
476
- * widest of them. Nothing in the banner path compares the two — `wordmark()`
477
- * gates on WORDMARK_WIDTH and then prints THESE lines — so an under-counting
478
- * width lets the art render into a terminal too narrow to hold it and the
479
- * three rows wrap into six. See wordmark-art-width.test.ts, which is the
480
- * comparison this export exists to make possible. */
481
- export const WORDMARK_LINES = renderWord("ccdeck");
482
- /** 29 columns of art, plus the two-space indent every other line uses. */
483
- export const WORDMARK_WIDTH = 2 + LETTERS.c[0].length * 6 + 5;
484
-
485
- /**
486
- * The tagline, longest version that fits — the name and version are the part
487
- * that must survive, the rest is context.
488
- *
489
- * `prefix` is how many columns are already spent on the line the tagline lands
490
- * on, and it is a parameter because the three layouts below do not agree on it
491
- * (#383). In the full banner the tagline gets a line of its own behind a
492
- * two-space indent; in the compact and plain ones it shares the line with the
493
- * product name, which costs eight or nine columns more. This budget was
494
- * hard-coded at 2 for all three, so between 21 and 31 columns — the widths where
495
- * the compact form is chosen and the medium tagline still looks affordable —
496
- * the banner printed a 36-column line into a terminal that could not hold it and
497
- * the first thing a user saw wrapped. `columns - 1` keeps the last cell empty,
498
- * because a line ending exactly at the right margin makes some terminals wrap
499
- * anyway.
500
- */
501
- function tagline(version, columns, bullet, prefix) {
502
- const v = `v${version}`;
503
- const options = [
504
- `${v} ${bullet} live agent DAG ${bullet} Claude Code + Codex`,
505
- `${v} ${bullet} live agent DAG`,
506
- v,
507
- ];
508
- return options.find((o) => o.length + prefix <= columns - 1) ?? v;
509
- }
510
-
511
- // What each layout spends before the tagline starts, counted off the strings
512
- // built in `wordmark` below: " ccdeck " for plain, " ccdeck " for compact,
513
- // and the bare two-space indent for the full banner's own tagline line.
514
- const PLAIN_PREFIX_W = " ccdeck ".length;
515
- const COMPACT_PREFIX_W = " ccdeck ".length;
516
- const TAG_INDENT_W = " ".length;
517
-
518
- /**
519
- * The wordmark, in the largest form this terminal can hold.
520
- *
521
- * "full" — the drawn mark, three rows, on a vertical gradient
522
- * "compact" — the name in the accent, for a terminal too narrow for the art
523
- * or a console without the half-block glyphs
524
- * "plain" — no escapes at all: a pipe, a CI log, NO_COLOR
525
- *
526
- * Returns the lines to print, blank lines included, so the caller writes them
527
- * and nothing else decides how much air the banner gets.
528
- */
529
- export function wordmark({
530
- columns = 80, version = "0.0.0", profile = "none", unicode = true, pal = palette(profile),
531
- } = {}) {
532
- const g = glyphs(unicode);
533
- // Each layout asks for its own tagline, because each leaves it a different
534
- // amount of room — see the prefix constants above.
535
- const tag = (prefix) => tagline(version, columns, g.bullet, prefix);
536
-
537
- if (profile === "none") return { kind: "plain", lines: ["", ` ccdeck ${tag(PLAIN_PREFIX_W)}`, ""] };
538
- if (!unicode || columns < WORDMARK_WIDTH + 1) {
539
- return { kind: "compact", lines: ["", ` ${pal.bold}${pal.accent}ccdeck${pal.reset} ${pal.muted}${tag(COMPACT_PREFIX_W)}${pal.reset}`, ""] };
540
- }
541
-
542
- const ramp = [pal.accentSoft, pal.accent, pal.accentDeep];
543
- return {
544
- kind: "full",
545
- lines: [
546
- "",
547
- ...WORDMARK_LINES.map((l, i) => ` ${ramp[i]}${l}${pal.reset}`),
548
- "",
549
- ` ${pal.muted}${tag(TAG_INDENT_W)}${pal.reset}`,
550
- "",
551
- ],
552
- };
553
- }
554
-
555
- // ── the pulse line ───────────────────────────────────────────────────────────
556
-
557
- /**
558
- * The one line that stays on screen for hours, sized to the terminal it is on.
559
- *
560
- * It is redrawn over itself with `\r`, so both messages are padded to one width
561
- * — the shorter has to cover the longer — and that width is clamped to the real
562
- * terminal: at 40 columns the old 58-character message wrapped, after which the
563
- * `\r` only ever reached the second row and every repaint left the first one
564
- * behind. A deck no hook can find is not listening in any sense the user cares
565
- * about, which is why that state has a message here at all.
566
- *
567
- * `claude` is what makes that last sentence conditional. A deck watching only
568
- * Codex is not waiting on a hook: startCodexWatcher is started by startServer
569
- * and never consults the discovery file, and writesCodexLog keeps a deck with no
570
- * record on disk writing its log. Capture and persistence both work perfectly
571
- * there, while this line used to repaint "hooks cannot find this deck" every
572
- * 800ms, forever (#404) — a permanent alarm about a mechanism that deck does not
573
- * use. The one-time report at boot still says what IS lost; see
574
- * unregisteredDetail.
575
- */
576
- export function pulseText({
577
- registered = true, claude = true, columns = 80, unicode = true, indent = 2, busy = null,
578
- } = {}) {
579
- const g = glyphs(unicode);
580
- const room = Math.max(4, columns - indent - 3 - 1);
581
- const pick = (options) => options.find((o) => o.length <= room) ?? options[options.length - 1].slice(0, room);
582
-
583
- const rest = pick([`listening ${g.dash} Ctrl+C to stop`, "listening"]);
584
- const bad = pick([
585
- `listening, but not registered ${g.dash} hooks cannot find this deck`,
586
- `not registered ${g.dash} hooks cannot find this deck`,
587
- "not registered",
588
- ]);
589
- // Both branches are still measured, registered or not, because the line is
590
- // redrawn over itself and the shorter message has to cover the longer one on
591
- // the beat after a deck loses its registration.
592
- //
593
- // The width is deliberately computed from the two FIXED messages only. `busy`
594
- // comes and goes on a single boot — an install starts, the line names it, the
595
- // install ends and the line goes back to Ctrl+C — so a width that grew to fit
596
- // the label would have to shrink again afterwards, and the shorter line would
597
- // leave the tail of the longer one on screen. Instead the label is shown only
598
- // where it already fits — 60 columns and wider, measured — and below that the
599
- // line says the true thing it has always said.
600
- const width = Math.min(room, Math.max(rest.length, bad.length));
601
- // What is still happening, rather than what is always true. `Ctrl+C to stop`
602
- // is the right thing to say to somebody with nothing left to wait for, and
603
- // the wrong thing to say to somebody watching an install.
604
- const label = typeof busy === "string" && busy.trim() ? busy.trim() : null;
605
- const working = label ? `listening ${g.bullet} ${label}` : null;
606
- const ok = working && working.length <= width ? working : rest;
607
- return (registered || !claude ? ok : bad).padEnd(width);
608
- }
609
-
610
- /**
611
- * Whether the line has anything left to say by moving.
612
- *
613
- * #742. The dot alternated green and grey every 800ms for as long as the deck
614
- * ran, and a blinking indicator beside a status line is the vocabulary of
615
- * "working on it" — so a boot that had finished in a second read as one that
616
- * never finished, and people said so. The deck's own web UI already retired
617
- * this once: the pill goes quiet at rest (#720). The terminal did not.
618
- *
619
- * Motion is now spent on the two states where something is genuinely
620
- * outstanding — a deck no hook can find, and a background job still running —
621
- * and nowhere else. At rest the dot is painted once, in the healthy colour, and
622
- * left alone. Movement then means something changed, which is the only thing
623
- * movement should ever mean on a line somebody leaves open for hours.
624
- */
625
- export function pulseMoves({ registered = true, claude = true, busy = null } = {}) {
626
- if (!registered && claude) return true;
627
- return typeof busy === "string" && busy.trim() !== "";
628
- }
629
-
630
- /**
631
- * Whether the dot is lit on this beat.
632
- *
633
- * `"on"` on every beat of a deck at rest, which is what makes the line still:
634
- * bin/deck.js paints a beat only when the frame differs from the one already on
635
- * screen, so a dot that is always lit is a line written once and then left
636
- * alone. Alternating is reserved for the states pulseMoves admits.
637
- *
638
- * The beat is a parameter rather than counted here so this is a function of its
639
- * inputs and nothing else — and so a test can ask what the twentieth beat of an
640
- * idle deck looks like without waiting sixteen seconds for it.
641
- */
642
- export function pulseDot(beat, { registered = true, claude = true, busy = null } = {}) {
643
- if (!pulseMoves({ registered, claude, busy })) return "on";
644
- return beat % 2 === 0 ? "on" : "off";
645
- }
646
-
647
- /**
648
- * The second line of the "not registered" report, which bin/deck.js prints once
649
- * per change of state rather than every beat.
650
- *
651
- * What a missing discovery file costs is not the same on the two capture paths,
652
- * and the old sentence — "hooks find this deck through <file>, so until that
653
- * file exists no events arrive" — stated the Claude Code answer as if it were
654
- * both. On a Codex-only deck it is false twice over: no hook is looking for this
655
- * deck, and events do keep arriving, because the rollout watcher reads the files
656
- * directly. What that deck really loses is the writer election in
657
- * log-writer.mjs, which is how several decks tailing one rollout agree on which
658
- * of them appends it to a shared events log. A deck with no record on disk is
659
- * assumed to be writing, so nothing is dropped — the log can gain the same line
660
- * twice instead.
661
- *
662
- * @param file the discovery file that could not be written.
663
- * @param claude whether this deck is watching Claude Code at all.
664
- * @param dash the em dash, or the ASCII stand-in on a console that cannot draw
665
- * one (#797). A parameter for the same reason `renameNotice` takes one: this
666
- * module's own header says "every glyph the deck prints — punctuation
667
- * included, since an em dash is as absent from CP437 as a check mark — comes
668
- * from one of them", and this sentence had it hardcoded. On a legacy cmd.exe
669
- * or the Linux virtual console the reader got a box in the middle of the one
670
- * line explaining why no events are arriving.
671
- */
672
- export function unregisteredDetail({ file, claude = true, dash = "—" }) {
673
- if (claude) {
674
- return `Claude Code hooks find this deck through ${file}, so until that file exists no Claude Code events arrive.`;
675
- }
676
- return `Codex capture does not use ${file} ${dash} the deck tails the rollout files itself ${dash} so events still arrive. Only decks sharing one events log need it, to agree on which of them records.`;
677
- }
678
-
679
440
  // ── cursor ───────────────────────────────────────────────────────────────────
680
441
  //
681
442
  // Strings rather than writers: hiding the cursor is trivial and restoring it is
682
443
  // not — it has to happen on the normal exit, on SIGINT/SIGTERM/SIGHUP and after
683
444
  // an uncaught throw, and getting that half-right leaves the user's shell with
684
- // no cursor after the deck is gone. The lifecycle owns it; see bin/deck.js.
445
+ // no cursor after the deck is gone. The lifecycle owns it; see takeCursor in
446
+ // bin/cli/screen.js.
685
447
  export const CURSOR_HIDE = "\x1b[?25l";
686
448
  export const CURSOR_SHOW = "\x1b[?25h";