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.
- package/README.md +1 -1
- package/bin/agent-dag.js +67 -118
- package/bin/cli/help.js +67 -0
- package/bin/cli/login-item.js +182 -0
- package/bin/cli/one-shot.js +254 -0
- package/bin/cli/package.js +39 -0
- package/bin/cli/pulse.js +98 -0
- package/bin/cli/restart.js +179 -0
- package/bin/cli/screen.js +166 -0
- package/bin/cli/second-start.js +120 -0
- package/bin/cli/startup.js +459 -0
- package/bin/cli/uninstall.js +169 -0
- package/bin/deck.js +112 -1559
- package/dist/web/assets/BrowserWatchModal-DP3YMtEi.js +1 -0
- package/dist/web/assets/UsageHistoryModal-CcdPVXAw.js +1 -0
- package/dist/web/assets/index-3vS6A0vt.css +1 -0
- package/dist/web/assets/index-BVTWHDta.js +298 -0
- package/dist/web/index.html +2 -2
- package/hook/hook.js +15 -14
- package/package.json +1 -1
- package/release-notes.json +22 -0
- package/src/server/account-projects.mjs +25 -11
- package/src/server/account-routes.mjs +254 -0
- package/src/server/activity.mjs +3 -3
- package/src/server/append-budget.mjs +185 -0
- package/src/server/append-failures.mjs +147 -0
- package/src/server/args.mjs +37 -5
- package/src/server/atomic-write.mjs +300 -0
- package/src/server/block-notify.mjs +0 -5
- package/src/server/boot-lock.mjs +1 -1
- package/src/server/brand.mjs +7 -6
- package/src/server/browser-react.mjs +10 -2
- package/src/server/browser-watch-decks.mjs +146 -0
- package/src/server/browser-watch-feed.mjs +69 -0
- package/src/server/browser-watch-log.mjs +173 -0
- package/src/server/browser-watch-mtime.mjs +18 -0
- package/src/server/browser-watch-relay.mjs +112 -0
- package/src/server/browser-watch-routes.mjs +172 -0
- package/src/server/browser-watch-seen.mjs +127 -0
- package/src/server/browser-watch-store.mjs +14 -143
- package/src/server/browser-watch.mjs +278 -626
- package/src/server/canonical-path.mjs +150 -0
- package/src/server/ccusage-failure.mjs +64 -0
- package/src/server/ccusage-install.mjs +459 -0
- package/src/server/ccusage-runner.mjs +271 -0
- package/src/server/ccusage.mjs +21 -756
- package/src/server/claude-accounts.mjs +220 -464
- package/src/server/claude-collector.mjs +189 -0
- package/src/server/claude-dir.mjs +2 -2
- package/src/server/claude-identity.mjs +5 -4
- package/src/server/claude-reset-credits.mjs +130 -0
- package/src/server/claude-verdicts.mjs +174 -0
- package/src/server/clear-route.mjs +191 -0
- package/src/server/codex-auth.mjs +4 -4
- package/src/server/codex-dir.mjs +52 -8
- package/src/server/codex-enrichment.mjs +310 -0
- package/src/server/codex-quota.mjs +5 -3
- package/src/server/codex-translate.mjs +407 -0
- package/src/server/codex-usage.mjs +8 -5
- package/src/server/codex-watch.mjs +496 -0
- package/src/server/cswap-auto-loop.mjs +342 -0
- package/src/server/cswap-auto-readers.mjs +303 -0
- package/src/server/cswap-auto.mjs +51 -609
- package/src/server/cswap-install.mjs +69 -265
- package/src/server/cswap-layout.mjs +242 -0
- package/src/server/deck-home.mjs +1 -1
- package/src/server/deck-prefs.mjs +51 -184
- package/src/server/deck-scope.mjs +52 -0
- package/src/server/desktop-update-routes.mjs +75 -0
- package/src/server/detach.mjs +78 -0
- package/src/server/discovery.mjs +298 -0
- package/src/server/event-log.mjs +328 -0
- package/src/server/event-pipeline.mjs +235 -0
- package/src/server/event-ring.mjs +214 -0
- package/src/server/event-routes.mjs +352 -0
- package/src/server/event-sink.mjs +26 -0
- package/src/server/exec-children.mjs +128 -0
- package/src/server/exec-not-found.mjs +213 -0
- package/src/server/exec-output.mjs +23 -0
- package/src/server/exec-spec.mjs +341 -0
- package/src/server/exec.mjs +74 -661
- package/src/server/health-route.mjs +42 -0
- package/src/server/hook-challenge.mjs +37 -0
- package/src/server/http-io.mjs +104 -0
- package/src/server/index.mjs +172 -7620
- package/src/server/install-layout.mjs +506 -0
- package/src/server/installer.mjs +79 -608
- package/src/server/jsonl-chunks.mjs +139 -0
- package/src/server/lan-accounts.mjs +27 -0
- package/src/server/lan-beacon.mjs +457 -0
- package/src/server/lan-call.mjs +239 -0
- package/src/server/lan-copies.mjs +270 -0
- package/src/server/lan-deck.mjs +583 -0
- package/src/server/lan-dials.mjs +161 -0
- package/src/server/lan-engine.mjs +475 -1024
- package/src/server/lan-hearing.mjs +49 -0
- package/src/server/lan-inbound.mjs +100 -0
- package/src/server/lan-invite-offer.mjs +77 -0
- package/src/server/lan-invite.mjs +207 -0
- package/src/server/lan-lines.mjs +73 -0
- package/src/server/lan-manifest.mjs +74 -0
- package/src/server/lan-reach.mjs +82 -69
- package/src/server/lan-requests.mjs +249 -0
- package/src/server/lan-round-record.mjs +45 -0
- package/src/server/lan-round-timer.mjs +62 -0
- package/src/server/lan-routes.mjs +143 -0
- package/src/server/lan-serve.mjs +112 -0
- package/src/server/lan-socket.mjs +243 -925
- package/src/server/lan-sync.mjs +58 -1026
- package/src/server/lan-tailnet-poll.mjs +57 -0
- package/src/server/lan-turns.mjs +80 -0
- package/src/server/lan-wire.mjs +620 -0
- package/src/server/lifecycle.mjs +421 -0
- package/src/server/listen.mjs +110 -0
- package/src/server/live-decks.mjs +138 -0
- package/src/server/load-average.mjs +32 -0
- package/src/server/log-election.mjs +235 -0
- package/src/server/log-replay.mjs +374 -0
- package/src/server/log-writer.mjs +72 -557
- package/src/server/login-service.mjs +3 -2
- package/src/server/macmon.mjs +1 -1
- package/src/server/memory-files.mjs +163 -0
- package/src/server/memory-metrics.mjs +178 -0
- package/src/server/metrics-history.mjs +94 -0
- package/src/server/metrics-run.mjs +100 -0
- package/src/server/music-routes.mjs +134 -0
- package/src/server/network-metrics.mjs +5 -3
- package/src/server/network-sampler.mjs +256 -0
- package/src/server/npm-latest.mjs +385 -0
- package/src/server/npm-upgrade.mjs +352 -0
- package/src/server/npx.mjs +41 -7
- package/src/server/output-watch.mjs +5 -4
- package/src/server/pinned-build.mjs +108 -0
- package/src/server/prefs-path.mjs +14 -0
- package/src/server/prefs-refusal.mjs +77 -0
- package/src/server/prefs-routes.mjs +76 -0
- package/src/server/prefs-set-aside.mjs +86 -0
- package/src/server/prefs-state.mjs +51 -0
- package/src/server/process-command.mjs +104 -0
- package/src/server/process-list.mjs +547 -0
- package/src/server/pulse-line.mjs +132 -0
- package/src/server/purge-key.mjs +7 -7
- package/src/server/quota-cli.mjs +174 -0
- package/src/server/quota-oauth.mjs +190 -0
- package/src/server/quota-shape.mjs +229 -0
- package/src/server/quota-store-resets.mjs +206 -0
- package/src/server/quota.mjs +123 -509
- package/src/server/relay-guard.mjs +3 -3
- package/src/server/request-gates.mjs +441 -0
- package/src/server/restart-note.mjs +180 -0
- package/src/server/retire-sound-hook.mjs +3 -3
- package/src/server/ring-bounds.mjs +215 -0
- package/src/server/running-deck.mjs +1 -1
- package/src/server/running-version.mjs +24 -0
- package/src/server/self-update.mjs +39 -1371
- package/src/server/session-enrichment.mjs +622 -0
- package/src/server/session-read-gate.mjs +49 -0
- package/src/server/session-tracking.mjs +242 -0
- package/src/server/sse-clients.mjs +222 -0
- package/src/server/stale-discovery.mjs +30 -0
- package/src/server/static-serve.mjs +81 -0
- package/src/server/supervisor.mjs +73 -12
- package/src/server/system-metrics.mjs +169 -1935
- package/src/server/term.mjs +11 -249
- package/src/server/thermal-metrics.mjs +520 -0
- package/src/server/thermal-sampler.mjs +204 -0
- package/src/server/token-redact.mjs +123 -0
- package/src/server/transcript-gate.mjs +107 -0
- package/src/server/transcript-scan.mjs +563 -0
- package/src/server/usage-routes.mjs +93 -0
- package/src/server/uv-bootstrap.mjs +1 -1
- package/src/server/wordmark.mjs +127 -0
- package/dist/web/assets/BrowserWatchModal-CyAFTvAY.js +0 -1
- package/dist/web/assets/UsageHistoryModal-KJfli0oa.js +0 -1
- package/dist/web/assets/index-Ck2RAla3.css +0 -1
- package/dist/web/assets/index-jmrcEsCj.js +0 -298
package/src/server/term.mjs
CHANGED
|
@@ -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/
|
|
237
|
-
* and this is the one part of `step`
|
|
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
|
|
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";
|