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
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
// The boot's terminal: the palette, the glyphs, the cursor, the status rows,
|
|
2
|
+
// the wordmark and the spinner — everything bin/deck.js draws with.
|
|
3
|
+
//
|
|
4
|
+
// Lifted out of deck.js, where it was a block in the middle of the boot. The
|
|
5
|
+
// answers are the same ones asked of the same environment, only asked when this
|
|
6
|
+
// module loads instead of halfway down that file; nothing here has a side effect
|
|
7
|
+
// until the boot calls takeCursor, at the point where the block used to hide it.
|
|
8
|
+
import { pathToFileURL } from "node:url";
|
|
9
|
+
import { dieOfSignal } from "../../src/server/supervisor.mjs";
|
|
10
|
+
import {
|
|
11
|
+
CURSOR_HIDE, CURSOR_SHOW, colorProfile, elapsedSuffix, fit, glyphs, labelColumn, link, motionOK,
|
|
12
|
+
palette, spinnerFrames, statusLine, supportsHyperlinks, termColumns, unicodeOK, visibleWidth,
|
|
13
|
+
} from "../../src/server/term.mjs";
|
|
14
|
+
import { wordmark } from "../../src/server/wordmark.mjs";
|
|
15
|
+
import { PKG_VERSION } from "./package.js";
|
|
16
|
+
|
|
17
|
+
// ── the terminal we are printing into ─────────────────────────────────────────
|
|
18
|
+
// Asked once, degraded from there — see src/server/term.mjs, which is where all
|
|
19
|
+
// of this is decided and asserted. Below this point the deck writes no escape of
|
|
20
|
+
// its own: colour comes from `P`, glyphs from `G`, layout from statusLine. That
|
|
21
|
+
// is what makes NO_COLOR, a pipe, a CI log and a legacy Windows console one
|
|
22
|
+
// question rather than thirty separate ones nobody remembers to ask.
|
|
23
|
+
export const tty = Boolean(process.stdout.isTTY);
|
|
24
|
+
export const PROFILE = colorProfile({ isTTY: tty });
|
|
25
|
+
export const P = palette(PROFILE);
|
|
26
|
+
export const UNICODE = unicodeOK();
|
|
27
|
+
export const G = glyphs(UNICODE);
|
|
28
|
+
export const LINKS = supportsHyperlinks({ profile: PROFILE });
|
|
29
|
+
// The terminal's prefers-reduced-motion: nothing sleeps, spins or repaints in a
|
|
30
|
+
// pipe, under CI, or with NO_COLOR set.
|
|
31
|
+
export const MOTION = motionOK({ isTTY: tty, profile: PROFILE });
|
|
32
|
+
export const write = (s) => process.stdout.write(s);
|
|
33
|
+
// Read per line, never cached: a terminal can be resized while the deck runs,
|
|
34
|
+
// and the pulse below is still on screen hours later.
|
|
35
|
+
export const cols = () => termColumns(process.stdout);
|
|
36
|
+
export const sleep = ms => new Promise(r => setTimeout(r, ms));
|
|
37
|
+
export const fileLink = (path) => link(path, pathToFileURL(path).href, LINKS);
|
|
38
|
+
|
|
39
|
+
// ── the cursor ────────────────────────────────────────────────────────────────
|
|
40
|
+
// Hidden for as long as anything of ours is moving — the reveal, the spinner,
|
|
41
|
+
// the pulse — and put back on every way out of this process: the ordinary exit,
|
|
42
|
+
// all three signals, and an uncaught throw, which reaches 'exit' after Node has
|
|
43
|
+
// printed it. Half of this is worse than none: a deck that dies with the cursor
|
|
44
|
+
// hidden leaves the user's shell with no cursor and nothing to do about it but
|
|
45
|
+
// `reset`.
|
|
46
|
+
let cursorHidden = false;
|
|
47
|
+
export const showCursor = () => {
|
|
48
|
+
if (!cursorHidden) return;
|
|
49
|
+
cursorHidden = false;
|
|
50
|
+
try { write(CURSOR_SHOW); } catch { /* stdout is gone; nothing left to restore */ }
|
|
51
|
+
};
|
|
52
|
+
/**
|
|
53
|
+
* Hide the cursor, if anything is going to move, and arm every way of putting
|
|
54
|
+
* it back. Called once, by the boot, at the point it starts drawing.
|
|
55
|
+
*/
|
|
56
|
+
export function takeCursor() {
|
|
57
|
+
if (MOTION) { cursorHidden = true; write(CURSOR_HIDE); }
|
|
58
|
+
process.on("exit", showCursor);
|
|
59
|
+
// SIGHUP is the one signal this process does not otherwise handle, so its
|
|
60
|
+
// default action would end us before 'exit' could run. Handled only to put the
|
|
61
|
+
// cursor back and then die of it exactly as before — the supervisor reads the
|
|
62
|
+
// signal, not an exit code.
|
|
63
|
+
process.on("SIGHUP", () => { showCursor(); dieOfSignal("SIGHUP"); });
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// ── rows ──────────────────────────────────────────────────────────────────────
|
|
67
|
+
// The status column is computed from the longest label. It used to be counted
|
|
68
|
+
// into each string as trailing spaces, so any new row, or any label a character
|
|
69
|
+
// longer, silently broke the alignment of every other one.
|
|
70
|
+
const LABELS = [
|
|
71
|
+
"workspace", "Claude hooks", "Codex sessions", "Codex hooks", "claude-swap", "accounts",
|
|
72
|
+
"ccusage", "update", "name", "server ready", "log", "unknown option",
|
|
73
|
+
"missing value",
|
|
74
|
+
];
|
|
75
|
+
const LABEL_W = labelColumn(LABELS);
|
|
76
|
+
|
|
77
|
+
export function row({ mark = " ", tone = P.ok, label = "", detail = "", detailTone = P.muted, keep = false }) {
|
|
78
|
+
return statusLine({
|
|
79
|
+
mark, label, detail, keep, labelWidth: LABEL_W, columns: cols(), ellipsis: G.ellipsis,
|
|
80
|
+
paint: {
|
|
81
|
+
mark: (s) => `${tone}${s}${P.reset}`,
|
|
82
|
+
detail: (s) => `${detailTone}${s}${P.reset}`,
|
|
83
|
+
},
|
|
84
|
+
}) + "\n";
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// ── the wordmark ──────────────────────────────────────────────────────────────
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Hold anything else that wants to speak until the art is finished.
|
|
91
|
+
*
|
|
92
|
+
* #742, found while watching a first run through a pty: the startup jobs run
|
|
93
|
+
* UNDER the reveal on purpose, and one of them failing writes to console.error
|
|
94
|
+
* the moment it fails — which put
|
|
95
|
+
*
|
|
96
|
+
* ccdeck ccusage: install failed: npm install ccusage failed: spawn npm ENOENT
|
|
97
|
+
*
|
|
98
|
+
* between the second and third rows of the logo. A wordmark with a stack of
|
|
99
|
+
* someone else's bad news through the middle of it is the first thing a new
|
|
100
|
+
* user sees, and it reads as a crash rather than as a note.
|
|
101
|
+
*
|
|
102
|
+
* The window is the reveal and nothing else — about 180ms — so at worst a
|
|
103
|
+
* message arrives a fifth of a second later than it would have, on the one
|
|
104
|
+
* stretch of the boot where there is nowhere for it to go. Restored in a
|
|
105
|
+
* `finally`, so a throw inside the reveal cannot leave the process mute.
|
|
106
|
+
*/
|
|
107
|
+
function holdConsole() {
|
|
108
|
+
const held = [];
|
|
109
|
+
const real = { warn: console.warn, error: console.error, log: console.log };
|
|
110
|
+
for (const k of Object.keys(real)) console[k] = (...args) => { held.push([k, args]); };
|
|
111
|
+
return () => {
|
|
112
|
+
Object.assign(console, real);
|
|
113
|
+
for (const [k, args] of held) real[k](...args);
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
export async function printBanner() {
|
|
118
|
+
const { lines } = wordmark({ columns: cols(), version: PKG_VERSION, profile: PROFILE, unicode: UNICODE, pal: P });
|
|
119
|
+
const release = holdConsole();
|
|
120
|
+
try {
|
|
121
|
+
for (const line of lines) {
|
|
122
|
+
write(line + "\n");
|
|
123
|
+
// A reveal, not a wait. The once-per-session work is already running under
|
|
124
|
+
// it (see startupWork), so the art costs the boot nothing and the deck is
|
|
125
|
+
// ready about when the last row lands. What used to be here — 560ms of
|
|
126
|
+
// spinner at "loading…" before a single art line — was dead time in a tool
|
|
127
|
+
// whose documented entry point is `npx ccdeck`.
|
|
128
|
+
if (MOTION && line) await sleep(45);
|
|
129
|
+
}
|
|
130
|
+
} finally {
|
|
131
|
+
release();
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// ── a step, with a spinner only if it is slow enough to need one ──────────────
|
|
136
|
+
// The interval's first frame is 80ms away, so anything already settled when we
|
|
137
|
+
// get here paints nothing at all and the row below is the only trace of it.
|
|
138
|
+
|
|
139
|
+
export async function step(label, work) {
|
|
140
|
+
if (!MOTION) return work;
|
|
141
|
+
const frames = spinnerFrames(UNICODE);
|
|
142
|
+
// Kept inside the terminal: a label that wraps is a label the \r below can
|
|
143
|
+
// only half erase, and what is left of it stays under the row that follows.
|
|
144
|
+
// Six columns for the indent and the spinner, four more so the elapsed
|
|
145
|
+
// seconds have somewhere to go without pushing the label off the edge.
|
|
146
|
+
const text = fit(label, cols() - 10, G.ellipsis);
|
|
147
|
+
const started = Date.now();
|
|
148
|
+
let i = 0;
|
|
149
|
+
let widest = 0;
|
|
150
|
+
const iv = setInterval(() => {
|
|
151
|
+
const line = ` ${P.accent}${frames[i++ % frames.length]}${P.reset} ${P.muted}${text}${elapsedSuffix(Date.now() - started)}${P.reset}`;
|
|
152
|
+
widest = Math.max(widest, visibleWidth(line));
|
|
153
|
+
write(`\r${line}`);
|
|
154
|
+
}, 80);
|
|
155
|
+
try {
|
|
156
|
+
return await work;
|
|
157
|
+
} finally {
|
|
158
|
+
clearInterval(iv);
|
|
159
|
+
// Cleared rather than overwritten: the row that follows is a different
|
|
160
|
+
// length, and relying on it to be the longer of the two is how a spinner
|
|
161
|
+
// leaves its own tail on screen. Measured rather than computed, because the
|
|
162
|
+
// line grows when the elapsed seconds appear and again when they reach two
|
|
163
|
+
// digits. Nothing to clear if it never painted.
|
|
164
|
+
if (i) write("\r" + " ".repeat(widest) + "\r");
|
|
165
|
+
}
|
|
166
|
+
}
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
// A second start, settled: attach to the deck that is already up, yield to one
|
|
2
|
+
// that came up while this one was coming back, or stop what this start
|
|
3
|
+
// replaces and go on.
|
|
4
|
+
//
|
|
5
|
+
// A bare `ccdeck` typed beside a deck that is already running used to build a
|
|
6
|
+
// second everything on a random port, and neither half mentioned the other.
|
|
7
|
+
// Then it attached only to a deck of exactly its own shape and built a second
|
|
8
|
+
// one beside anything else — a different flag, an older version, or a login
|
|
9
|
+
// item whose environment decided `codex` or the log path differently from the
|
|
10
|
+
// shell's. src/server/running-deck.mjs carries the whole argument, the registry
|
|
11
|
+
// read, the handshake and the rule; this is where the answer is acted on, and
|
|
12
|
+
// bin/deck.js is where — and when — it is asked.
|
|
13
|
+
//
|
|
14
|
+
// A RESPAWN IS ASKED TOO, and answers differently. A restart is THIS deck
|
|
15
|
+
// coming back, so there is nothing to attach to — but in the gap a crash
|
|
16
|
+
// leaves, a `ccdeck` typed by hand finds no deck and starts one, and the
|
|
17
|
+
// respawn that followed used to take a random port beside it. Now it finds
|
|
18
|
+
// that deck and exits 0, and the supervisor, reading a clean exit, ends too.
|
|
19
|
+
import { join } from "node:path";
|
|
20
|
+
import { pathToFileURL } from "node:url";
|
|
21
|
+
import { isPortValue } from "../../src/server/args.mjs";
|
|
22
|
+
import { PRODUCT } from "../../src/server/brand.mjs";
|
|
23
|
+
import { liveDecks, olderVersion, secondStart, versionNote } from "../../src/server/running-deck.mjs";
|
|
24
|
+
import { link } from "../../src/server/term.mjs";
|
|
25
|
+
import { COMMAND, PKG_ROOT, PKG_VERSION } from "./package.js";
|
|
26
|
+
import { G, LINKS, P, sleep, write } from "./screen.js";
|
|
27
|
+
import { reportIncompleteFlags, reportUnknownFlags } from "./startup.js";
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Ask the rule, and do what it says. Resolves to the exit code when this start
|
|
31
|
+
* ends here — a yield, or an attach — having said why, and to null when it is
|
|
32
|
+
* to go on and boot. Nothing here exits the process: bin/deck.js does, so every
|
|
33
|
+
* way out is one its exit handlers already cover (#980).
|
|
34
|
+
*/
|
|
35
|
+
export async function settleSecondStart({ flags, workspace, persist, wantCodex, wantClaude, codexHome, openBrowser, RESPAWN }) {
|
|
36
|
+
// `--port` alone, not AGENT_DAG_PORT: the variable is how somebody RUNS a
|
|
37
|
+
// deck rather than which one they mean — the line `--stop` draws in
|
|
38
|
+
// bin/cli/one-shot.js.
|
|
39
|
+
const askedPort = flags.port != null && isPortValue(flags.port) ? Number(flags.port) : null;
|
|
40
|
+
const plan = secondStart({
|
|
41
|
+
live: await liveDecks().catch(() => []),
|
|
42
|
+
want: { workspace, persist, codex: wantCodex, claude: wantClaude, codexHome },
|
|
43
|
+
port: askedPort,
|
|
44
|
+
ours: PKG_VERSION,
|
|
45
|
+
fresh: flags.new === true,
|
|
46
|
+
respawn: RESPAWN,
|
|
47
|
+
});
|
|
48
|
+
if (plan.act === "yield") {
|
|
49
|
+
console.error(`${PRODUCT}: a deck started on ${plan.deck.port} while this one was coming back ${G.dash} leaving it to that one.`);
|
|
50
|
+
return 0;
|
|
51
|
+
}
|
|
52
|
+
// THE NEWEST START WINS. What is stopped here is either the deck this start
|
|
53
|
+
// replaces or, on an attach, a second deck left over from before this rule —
|
|
54
|
+
// the duplicate the rule exists to end. Each one is said, with why, because a
|
|
55
|
+
// deck that vanishes without a word is a mystery of its own.
|
|
56
|
+
if (plan.stop.length) {
|
|
57
|
+
const { stopDeck } = await import(pathToFileURL(join(PKG_ROOT, "src/server/stop-deck.mjs")).href);
|
|
58
|
+
for (const d of plan.stop) {
|
|
59
|
+
const why = plan.act === "replace" && flags.new === true
|
|
60
|
+
? "you asked for a fresh one"
|
|
61
|
+
: olderVersion(d.version, PKG_VERSION)
|
|
62
|
+
? (d.version ? `it was v${d.version}` : "it was an older version")
|
|
63
|
+
: plan.act === "attach" ? "it was a second deck" : "it was started with different settings";
|
|
64
|
+
const out = await stopDeck(d).catch(() => ({ ok: false, reason: "unreachable" }));
|
|
65
|
+
write(out.ok
|
|
66
|
+
? `\n ${P.ok}${G.ok}${P.reset} stopped the deck on ${d.port}${P.muted} ${G.bullet} pid ${d.pid} ${G.bullet} ${why}${P.reset}\n`
|
|
67
|
+
: `\n ${P.warn}${G.warn} could not stop the deck on ${d.port} (pid ${d.pid}) ${G.dash} ${out.reason ?? out.how}${P.reset}\n`);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
if (plan.act === "attach") {
|
|
71
|
+
const live = plan.deck;
|
|
72
|
+
const liveUrl = `http://127.0.0.1:${live.port}`;
|
|
73
|
+
const note = versionNote(live.version, PKG_VERSION);
|
|
74
|
+
// Not the startup report's rows. That report has a label column because it
|
|
75
|
+
// has twelve lines to align; this has two, and borrowing the column would
|
|
76
|
+
// indent a three-line message behind a gutter sized for "Codex sessions".
|
|
77
|
+
//
|
|
78
|
+
// The version chunk is dropped rather than printed as "v?" when the running
|
|
79
|
+
// deck is too old to report one — see versionNote, which says nothing in the
|
|
80
|
+
// same case for the same reason.
|
|
81
|
+
const ident = [live.version ? `v${live.version}` : "", `pid ${live.pid}`]
|
|
82
|
+
.filter(Boolean).join(` ${G.bullet} `);
|
|
83
|
+
write(`\n ${P.ok}${G.ok}${P.reset} deck already running${P.muted} ${G.bullet} ${ident}${P.reset}\n`);
|
|
84
|
+
// Its own line, always. The URL is the one detail an ellipsis would destroy
|
|
85
|
+
// — half an address is not a shorter address — and this message has no
|
|
86
|
+
// report to hand it to. Same rule as statusLine's `keep`.
|
|
87
|
+
write(` ${P.accent}${P.bold}${link(liveUrl, liveUrl, LINKS)}${P.reset}\n`);
|
|
88
|
+
if (note) write(` ${P.warn}${G.warn} ${note}${P.reset}\n`);
|
|
89
|
+
// THE TYPO'S WARNING, on the path that has no startup report to carry it.
|
|
90
|
+
//
|
|
91
|
+
// The gate used to answer `true` for these two so the report would
|
|
92
|
+
// run and print them, and that is how `ccdeck --stpo` — a misspelling of
|
|
93
|
+
// the flag that STOPS a deck — came to build a second one. The warning was
|
|
94
|
+
// the requirement; the extra process never was. Printed here, in the same
|
|
95
|
+
// rows the report uses, so nothing is lost and nothing is started.
|
|
96
|
+
reportUnknownFlags(flags.unknown);
|
|
97
|
+
reportIncompleteFlags(flags.incomplete);
|
|
98
|
+
// The line that says a second deck was NOT started. Without it the command
|
|
99
|
+
// looks like it did nothing at all, which is the other way to be confusing
|
|
100
|
+
// about this — and it names the flag for the person who wanted a fresh one.
|
|
101
|
+
write(`\n ${P.muted}${G.dash} no second deck was started ${G.dash} \`${COMMAND} --new\` replaces it with a fresh one${P.reset}\n`);
|
|
102
|
+
if (openBrowser) {
|
|
103
|
+
write(`\n ${P.ok}${P.bold}${G.play} opening browser${G.ellipsis}${P.reset}\n\n`);
|
|
104
|
+
try {
|
|
105
|
+
const { openUrl, LAUNCH_GRACE_MS } = await import(pathToFileURL(join(PKG_ROOT, "src/server/open-url.mjs")).href);
|
|
106
|
+
openUrl(liveUrl);
|
|
107
|
+
// Held for exactly as long as openUrl needs to fall through to its next
|
|
108
|
+
// launcher. Every child it spawns is unref'd, so an immediate exit ends
|
|
109
|
+
// this process before a missing xdg-open has been answered by gio — and
|
|
110
|
+
// then no browser opens and nothing says why. The boot path never had
|
|
111
|
+
// to think about this because it stays alive forever.
|
|
112
|
+
await sleep(LAUNCH_GRACE_MS);
|
|
113
|
+
} catch { /* the URL is on screen; it can be clicked or pasted */ }
|
|
114
|
+
} else {
|
|
115
|
+
write("\n");
|
|
116
|
+
}
|
|
117
|
+
return 0;
|
|
118
|
+
}
|
|
119
|
+
return null;
|
|
120
|
+
}
|