agent-dag 1.30.9 → 1.32.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 +39 -4
- package/bin/agent-dag.js +94 -298
- package/bin/deck.js +368 -0
- package/dist/web/assets/index-Bj4ALC2V.js +66 -0
- package/dist/web/assets/index-CAnz1LW4.css +1 -0
- package/dist/web/index.html +2 -2
- package/package.json +1 -1
- package/src/server/index.mjs +49 -2
- package/src/server/self-update.mjs +126 -5
- package/dist/web/assets/index-CEAcuttN.css +0 -1
- package/dist/web/assets/index-bPVurIAI.js +0 -66
package/bin/deck.js
ADDED
|
@@ -0,0 +1,368 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// The deck itself: registers hooks, starts the server, opens the browser.
|
|
3
|
+
// Launched by the supervisor in bin/agent-dag.js, which restarts it when it
|
|
4
|
+
// exits with RESTART_CODE. On a respawn (AGENTS_DECK_RESPAWN=1) everything that
|
|
5
|
+
// was already done once this session is skipped — that is what makes a restart
|
|
6
|
+
// take about a second instead of the better part of ten.
|
|
7
|
+
import { resolve, dirname, join } from "node:path";
|
|
8
|
+
import { homedir } from "node:os";
|
|
9
|
+
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
10
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
11
|
+
|
|
12
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
13
|
+
const PKG_ROOT = resolve(__dirname, "..");
|
|
14
|
+
const PKG_VERSION = (() => {
|
|
15
|
+
try { return JSON.parse(readFileSync(join(PKG_ROOT, "package.json"), "utf8")).version ?? "0.0.0"; }
|
|
16
|
+
catch { return "0.0.0"; }
|
|
17
|
+
})();
|
|
18
|
+
|
|
19
|
+
const argv = process.argv.slice(2);
|
|
20
|
+
const flags = parseArgs(argv);
|
|
21
|
+
|
|
22
|
+
// Exit code the supervisor reads as "bring me back". Anything else it forwards.
|
|
23
|
+
const RESTART_CODE = 75;
|
|
24
|
+
const RESPAWN = process.env.AGENTS_DECK_RESPAWN === "1";
|
|
25
|
+
const SUPERVISED = typeof process.send === "function";
|
|
26
|
+
|
|
27
|
+
if (flags.help) {
|
|
28
|
+
printHelp();
|
|
29
|
+
process.exit(0);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
if (flags.uninstall) {
|
|
33
|
+
const { uninstallHooks, hasCodexInstalled } = await import(pathToFileURL(join(PKG_ROOT, "src/server/installer.mjs")).href);
|
|
34
|
+
const claude = await uninstallHooks({ provider: "claude" });
|
|
35
|
+
console.log(claude.changed
|
|
36
|
+
? `agents-deck: hooks removed from ${claude.settingsPath}`
|
|
37
|
+
: "agents-deck: no Claude hooks to remove");
|
|
38
|
+
if (hasCodexInstalled()) {
|
|
39
|
+
const codex = await uninstallHooks({ provider: "codex" });
|
|
40
|
+
console.log(codex.changed
|
|
41
|
+
? `agents-deck: hooks removed from ${codex.settingsPath}`
|
|
42
|
+
: "agents-deck: no Codex hooks to remove");
|
|
43
|
+
}
|
|
44
|
+
process.exit(0);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const port = Number(flags.port ?? process.env.AGENT_DAG_PORT ?? 4317);
|
|
48
|
+
// Default = machine-wide (capture every CC session on this box). Pass
|
|
49
|
+
// `--workspace <path>` (or `--scope`) to restrict to a single tree.
|
|
50
|
+
const workspace = flags.workspace != null
|
|
51
|
+
? flags.workspace
|
|
52
|
+
: (flags.scope ? process.cwd() : "");
|
|
53
|
+
const openBrowser = flags.noOpen !== true;
|
|
54
|
+
const persist = flags.noPersist
|
|
55
|
+
? null
|
|
56
|
+
: (flags.history ?? join(homedir(), ".claude", "agent-dag", "events.jsonl"));
|
|
57
|
+
|
|
58
|
+
const { installHooks, writeDiscovery, removeDiscovery, hasCodexInstalled } =
|
|
59
|
+
await import(pathToFileURL(join(PKG_ROOT, "src/server/installer.mjs")).href);
|
|
60
|
+
const { startServer } =
|
|
61
|
+
await import(pathToFileURL(join(PKG_ROOT, "src/server/index.mjs")).href);
|
|
62
|
+
|
|
63
|
+
// Codex hooks install when ~/.codex/ exists, unless --no-codex was passed.
|
|
64
|
+
// --codex forces install even if the dir is missing (creates it).
|
|
65
|
+
const wantCodex = flags.noCodex
|
|
66
|
+
? false
|
|
67
|
+
: (flags.codex === true || hasCodexInstalled());
|
|
68
|
+
|
|
69
|
+
const WEB_DIST = join(PKG_ROOT, "dist", "web", "index.html");
|
|
70
|
+
if (!existsSync(WEB_DIST)) {
|
|
71
|
+
console.error("agents-deck: ui not built. run `npm run build` (or `pnpm build`) first.");
|
|
72
|
+
process.exit(1);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// ── ANSI helpers ──────────────────────────────────────────────────────────────
|
|
76
|
+
const tty = process.stdout.isTTY;
|
|
77
|
+
const C = {
|
|
78
|
+
reset: tty ? "\x1b[0m" : "",
|
|
79
|
+
bold: tty ? "\x1b[1m" : "",
|
|
80
|
+
dim: tty ? "\x1b[2m" : "",
|
|
81
|
+
cyan: tty ? "\x1b[36m" : "",
|
|
82
|
+
blue: tty ? "\x1b[34m" : "",
|
|
83
|
+
magenta: tty ? "\x1b[35m" : "",
|
|
84
|
+
yellow: tty ? "\x1b[33m" : "",
|
|
85
|
+
green: tty ? "\x1b[32m" : "",
|
|
86
|
+
white: tty ? "\x1b[97m" : "",
|
|
87
|
+
bCyan: tty ? "\x1b[96m" : "",
|
|
88
|
+
bMag: tty ? "\x1b[95m" : "",
|
|
89
|
+
};
|
|
90
|
+
const sleep = ms => new Promise(r => setTimeout(r, ms));
|
|
91
|
+
|
|
92
|
+
// ── Animated banner ───────────────────────────────────────────────────────────
|
|
93
|
+
async function printBanner() {
|
|
94
|
+
// figlet slant font — hardcoded, no runtime dep
|
|
95
|
+
const ART = [
|
|
96
|
+
' __ __ __ ',
|
|
97
|
+
' ____ _____ ____ ____ / /______ ____/ /__ _____/ /__',
|
|
98
|
+
' / __ `/ __ `/ _ \\/ __ \\/ __/ ___/_____/ __ / _ \\/ ___/ //_/',
|
|
99
|
+
'/ /_/ / /_/ / __/ / / / /_(__ )_____/ /_/ / __/ /__/ ,< ',
|
|
100
|
+
'\\__,_/\\__, /\\___/_/ /_/\\__/____/ \\__,_/\\___/\\___/_/|_| ',
|
|
101
|
+
' /____/ ',
|
|
102
|
+
];
|
|
103
|
+
const COLORS = [C.dim, C.blue, C.cyan, C.bCyan, C.magenta, C.dim];
|
|
104
|
+
|
|
105
|
+
process.stdout.write('\n');
|
|
106
|
+
|
|
107
|
+
if (tty) {
|
|
108
|
+
const frames = ['⠋','⠙','⠹','⠸','⠼','⠴','⠦','⠧','⠇','⠏'];
|
|
109
|
+
for (let i = 0; i < 8; i++) {
|
|
110
|
+
process.stdout.write(`\r ${C.bCyan}${frames[i % frames.length]}${C.reset} ${C.dim}loading…${C.reset}`);
|
|
111
|
+
await sleep(70);
|
|
112
|
+
}
|
|
113
|
+
process.stdout.write('\r' + ' '.repeat(28) + '\n');
|
|
114
|
+
await sleep(40);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
for (let i = 0; i < ART.length; i++) {
|
|
118
|
+
process.stdout.write(` ${COLORS[i]}${ART[i]}${C.reset}\n`);
|
|
119
|
+
if (tty) await sleep(38);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
process.stdout.write(`\n ${C.dim}v${PKG_VERSION} · live agent DAG · Claude Code + Codex${C.reset}\n\n`);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// ── Spinner ───────────────────────────────────────────────────────────────────
|
|
126
|
+
function spinner(label) {
|
|
127
|
+
if (!tty) { process.stdout.write(` … ${label}\n`); return { stop: (ok, msg) => process.stdout.write(` ${ok ? "✓" : "✗"} ${msg}\n`) }; }
|
|
128
|
+
const frames = ["⠋","⠙","⠹","⠸","⠼","⠴","⠦","⠧","⠇","⠏"];
|
|
129
|
+
let i = 0;
|
|
130
|
+
const iv = setInterval(() => {
|
|
131
|
+
process.stdout.write(`\r ${C.cyan}${frames[i++ % frames.length]}${C.reset} ${label}`);
|
|
132
|
+
}, 80);
|
|
133
|
+
return {
|
|
134
|
+
stop(ok, msg) {
|
|
135
|
+
clearInterval(iv);
|
|
136
|
+
const icon = ok ? `${C.green}✓${C.reset}` : `${C.yellow}✗${C.reset}`;
|
|
137
|
+
process.stdout.write(`\r ${icon} ${msg}\n`);
|
|
138
|
+
}
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
let sp;
|
|
143
|
+
|
|
144
|
+
// Everything in here is once-per-session setup — hook install, tool probes,
|
|
145
|
+
// registry lookups, and about 600ms of deliberate banner animation. A respawn
|
|
146
|
+
// is the same session continuing, so it skips the lot and prints one line
|
|
147
|
+
// instead. This is the difference between a restart that feels instant and one
|
|
148
|
+
// that makes you wonder whether it worked.
|
|
149
|
+
if (!RESPAWN) {
|
|
150
|
+
await printBanner();
|
|
151
|
+
|
|
152
|
+
// ── Startup steps ─────────────────────────────────────────────────────────────
|
|
153
|
+
process.stdout.write(` ${C.dim}workspace :${C.reset} ${workspace === "" ? C.yellow + "(all)" + C.reset : workspace}\n`);
|
|
154
|
+
|
|
155
|
+
sp = spinner("installing Claude hooks…");
|
|
156
|
+
const claudeInstall = await installHooks({ provider: "claude" });
|
|
157
|
+
sp.stop(true, `Claude hooks ${C.dim}→ ${claudeInstall.hookPath}${C.reset}`);
|
|
158
|
+
|
|
159
|
+
// Codex CLI hooks never fire on Windows (sandbox refuses to spawn the hook
|
|
160
|
+
// command). Instead the server tails Codex's rollout JSONL files directly, so
|
|
161
|
+
// there's nothing to install and no /hooks trust step. We just confirm Codex
|
|
162
|
+
// is present and let the watcher pick up sessions.
|
|
163
|
+
if (wantCodex) {
|
|
164
|
+
process.stdout.write(` ${C.green}✓${C.reset} Codex sessions ${C.dim}→ watching ${join(homedir(), ".codex", "sessions")}${C.reset}\n`);
|
|
165
|
+
} else {
|
|
166
|
+
process.stdout.write(` ${C.dim}Codex watch skipped (no ~/.codex/, or --no-codex)${C.reset}\n`);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// claude-swap backs the multi-account panel. Installing it touches the user's
|
|
170
|
+
// global tool path, so unlike the ccusage install this one announces itself.
|
|
171
|
+
{
|
|
172
|
+
const { ensureCswap } = await import(pathToFileURL(join(PKG_ROOT, "src/server/cswap-install.mjs")).href);
|
|
173
|
+
const csp = spinner("checking claude-swap…");
|
|
174
|
+
const cs = await ensureCswap();
|
|
175
|
+
if (cs.state === "present") {
|
|
176
|
+
csp.stop(true, `claude-swap ${C.dim}→ v${cs.version} (accounts panel enabled)${C.reset}`);
|
|
177
|
+
} else if (cs.state === "installed") {
|
|
178
|
+
csp.stop(true, `claude-swap ${C.dim}→ installed v${cs.version} via ${cs.via}${C.reset}`);
|
|
179
|
+
} else if (cs.state === "upgrading") {
|
|
180
|
+
csp.stop(true, `claude-swap ${C.dim}→ v${cs.version}, upgrading to v${cs.latest} in background${C.reset}`);
|
|
181
|
+
} else if (cs.state === "skipped") {
|
|
182
|
+
csp.stop(true, `claude-swap ${C.dim}not installed (AGENTS_DECK_NO_INSTALL=1)${C.reset}`);
|
|
183
|
+
} else {
|
|
184
|
+
const how = cs.reason === "no_installer"
|
|
185
|
+
? "not installed — the accounts panel needs it"
|
|
186
|
+
: cs.reason === "not_on_path"
|
|
187
|
+
? `installed via ${cs.via} but not on PATH — add ${
|
|
188
|
+
process.platform === "win32" ? "%USERPROFILE%\\.local\\bin" : "~/.local/bin"
|
|
189
|
+
}`
|
|
190
|
+
: `install failed via ${cs.via}`;
|
|
191
|
+
csp.stop(false, `claude-swap ${C.dim}${how}${C.reset}`);
|
|
192
|
+
// A URL is not an answer when someone just wants the panel to work. Print
|
|
193
|
+
// the command for THIS machine, picked from what is already on it.
|
|
194
|
+
if (cs.hint) process.stdout.write(` ${C.dim}${cs.hint}${C.reset}\n`);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// A working claude-swap with an empty store still leaves the panel useless,
|
|
198
|
+
// so the account already signed in is registered once. Bounded inside
|
|
199
|
+
// seedFirstAccount: empty store only, once ever, never with NO_INSTALL set.
|
|
200
|
+
if (cs.state === "present" || cs.state === "installed" || cs.state === "upgrading") {
|
|
201
|
+
const { seedFirstAccount } = await import(pathToFileURL(join(PKG_ROOT, "src/server/claude-accounts.mjs")).href);
|
|
202
|
+
const seed = await seedFirstAccount().catch(() => ({ state: "failed" }));
|
|
203
|
+
if (seed.state === "added") {
|
|
204
|
+
process.stdout.write(` ${C.green}✓${C.reset} accounts ${C.dim}registered the signed-in account (cswap add)${C.reset}\n`);
|
|
205
|
+
} else if (seed.state === "failed" || seed.state === "nothing-to-add") {
|
|
206
|
+
process.stdout.write(` ${C.dim} accounts panel empty — sign in to Claude Code, then run cswap add${C.reset}\n`);
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// ccusage backs the usage-history modal. Primed here rather than on first
|
|
212
|
+
// open so a cold machine pays the install while the deck is still booting.
|
|
213
|
+
if (process.env.AGENTS_DECK_NO_INSTALL !== "1") {
|
|
214
|
+
const { primeCcusage } = await import(pathToFileURL(join(PKG_ROOT, "src/server/ccusage.mjs")).href);
|
|
215
|
+
const cu = primeCcusage();
|
|
216
|
+
if (cu.state === "present") process.stdout.write(` ${C.green}✓${C.reset} ccusage ${C.dim}→ v${cu.version}${C.reset}\n`);
|
|
217
|
+
else if (cu.state === "updating") process.stdout.write(` ${C.green}✓${C.reset} ccusage ${C.dim}→ v${cu.version}, checking for update${C.reset}\n`);
|
|
218
|
+
else if (cu.state === "installing") process.stdout.write(` ${C.green}✓${C.reset} ccusage ${C.dim}installing in background${C.reset}\n`);
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
// A newer release on npm, said once, in the place the upgrade gets typed.
|
|
222
|
+
// Started here and collected below so the lookup overlaps the rest of boot, and
|
|
223
|
+
// hard-capped so a slow registry cannot delay the server — the answer is
|
|
224
|
+
// usually already cached in ~/.agents-deck/.self-update-check anyway. It has to
|
|
225
|
+
// resolve BEFORE the pulse indicator starts writing over the last line.
|
|
226
|
+
const selfCheck = import(pathToFileURL(join(PKG_ROOT, "src/server/self-update.mjs")).href)
|
|
227
|
+
.then(m => m.versionReport({ running: PKG_VERSION, pkgRoot: PKG_ROOT }))
|
|
228
|
+
.catch(() => null);
|
|
229
|
+
const upgrade = await Promise.race([
|
|
230
|
+
selfCheck.then(r => r?.notice?.kind === "upgrade" ? r : null),
|
|
231
|
+
new Promise(r => setTimeout(() => r(null), 1200)),
|
|
232
|
+
]);
|
|
233
|
+
if (upgrade) {
|
|
234
|
+
process.stdout.write(
|
|
235
|
+
` ${C.yellow}↑${C.reset} update ${C.dim}v${upgrade.notice.to} available — ${C.reset}${C.yellow}${upgrade.command}${C.reset}\n`,
|
|
236
|
+
);
|
|
237
|
+
}
|
|
238
|
+
} // end !RESPAWN
|
|
239
|
+
|
|
240
|
+
// Asking the supervisor to bring us back. It is the only party that can, and
|
|
241
|
+
// only after this process is gone — which is precisely what keeps the
|
|
242
|
+
// replacement from racing this listener onto a random fallback port.
|
|
243
|
+
let restarting = false;
|
|
244
|
+
const requestRestart = () => {
|
|
245
|
+
if (restarting) return;
|
|
246
|
+
restarting = true;
|
|
247
|
+
const to = restartTarget();
|
|
248
|
+
process.stdout.write(
|
|
249
|
+
`\n ${C.yellow}↻${C.reset} ${C.dim}restarting${to ? ` → v${to}` : ""}…${C.reset}\n`,
|
|
250
|
+
);
|
|
251
|
+
shutdown(RESTART_CODE);
|
|
252
|
+
};
|
|
253
|
+
// What a restart would land on. Read from disk now rather than remembered from
|
|
254
|
+
// boot, because the whole point is that the two differ.
|
|
255
|
+
function restartTarget() {
|
|
256
|
+
try { return JSON.parse(readFileSync(join(PKG_ROOT, "package.json"), "utf8")).version ?? null; }
|
|
257
|
+
catch { return null; }
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
if (!RESPAWN) sp = spinner("starting server…");
|
|
261
|
+
const server = await startServer({
|
|
262
|
+
port, persist, workspace, codex: wantCodex,
|
|
263
|
+
// Withheld when nothing is supervising us: without a parent, exiting is just
|
|
264
|
+
// exiting, and /api/restart answers 501 so the UI hides the control.
|
|
265
|
+
onRestart: SUPERVISED ? requestRestart : null,
|
|
266
|
+
}).catch(err => {
|
|
267
|
+
if (sp) sp.stop(false, `server failed: ${err.message}`);
|
|
268
|
+
else console.error(`agents-deck: server failed: ${err.message}`);
|
|
269
|
+
process.exit(1);
|
|
270
|
+
});
|
|
271
|
+
const addr = server.address();
|
|
272
|
+
const realPort = typeof addr === "object" && addr ? addr.port : port;
|
|
273
|
+
const url = `http://127.0.0.1:${realPort}`;
|
|
274
|
+
|
|
275
|
+
// The supervisor re-launches with this on --port. It has to be the port we
|
|
276
|
+
// actually got, not the one we asked for: those differ whenever the first
|
|
277
|
+
// launch found 4317 taken, and re-launching on the requested port would move
|
|
278
|
+
// the deck out from under every open tab.
|
|
279
|
+
try { process.send?.({ type: "listening", port: realPort }); } catch { /* not supervised */ }
|
|
280
|
+
|
|
281
|
+
if (RESPAWN) {
|
|
282
|
+
process.stdout.write(` ${C.green}↻${C.reset} ${C.dim}restarted → ${C.reset}v${PKG_VERSION}${C.dim} · ${url}${C.reset}\n`);
|
|
283
|
+
} else {
|
|
284
|
+
sp.stop(true, `server ready ${C.dim}→ ${C.reset}${C.bCyan}${C.bold}${url}${C.reset}`);
|
|
285
|
+
if (persist) process.stdout.write(` ${C.dim}log : ${persist}${C.reset}\n`);
|
|
286
|
+
process.stdout.write(`\n ${C.green}${C.bold}▶ opening browser…${C.reset}\n\n`);
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
const discoveryFile = await writeDiscovery({ port: realPort, workspace });
|
|
290
|
+
|
|
291
|
+
// Never on a respawn: the tab that asked for the restart is still open and
|
|
292
|
+
// reconnecting on its own. A second one would be the deck talking over itself.
|
|
293
|
+
if (openBrowser && !RESPAWN) {
|
|
294
|
+
try {
|
|
295
|
+
const { default: open } = await import("open");
|
|
296
|
+
await open(url);
|
|
297
|
+
} catch {}
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
// ── Pulse indicator ───────────────────────────────────────────────────────────
|
|
301
|
+
if (tty) {
|
|
302
|
+
const pulseFrames = [`${C.green}●${C.reset}`, `${C.dim}●${C.reset}`];
|
|
303
|
+
let pi = 0;
|
|
304
|
+
setInterval(() => {
|
|
305
|
+
process.stdout.write(`\r ${pulseFrames[pi++ % 2]} ${C.dim}listening — Ctrl+C to stop${C.reset} `);
|
|
306
|
+
}, 800).unref();
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
const shutdown = async (code = 0) => {
|
|
310
|
+
// Also set as exitCode, not only passed to exit(): if the event loop empties
|
|
311
|
+
// on its own before either timer runs, Node would otherwise exit 0 and the
|
|
312
|
+
// supervisor would take that as "done" instead of "bring me back".
|
|
313
|
+
process.exitCode = code;
|
|
314
|
+
if (tty && code !== RESTART_CODE) process.stdout.write(`\n\n ${C.yellow}◉ shutting down…${C.reset}\n`);
|
|
315
|
+
await removeDiscovery(discoveryFile);
|
|
316
|
+
server.close(() => process.exit(code));
|
|
317
|
+
// SSE connections never end by themselves, so close() alone would sit out the
|
|
318
|
+
// full 1500ms fallback on every restart. Hanging them up is safe — the stream
|
|
319
|
+
// sets retry: 1500 and replays from Last-Event-ID, so each tab reconnects and
|
|
320
|
+
// catches up without being told anything.
|
|
321
|
+
try { server.closeAllConnections?.(); } catch { /* Node < 18.2 */ }
|
|
322
|
+
setTimeout(() => process.exit(code), 1500).unref();
|
|
323
|
+
};
|
|
324
|
+
process.on("SIGINT", () => shutdown(0));
|
|
325
|
+
process.on("SIGTERM", () => shutdown(0));
|
|
326
|
+
process.on("beforeExit", () => removeDiscovery(discoveryFile));
|
|
327
|
+
|
|
328
|
+
// ── helpers ───────────────────────────────────────────────────────────────────
|
|
329
|
+
|
|
330
|
+
function parseArgs(args) {
|
|
331
|
+
const out = {};
|
|
332
|
+
for (let i = 0; i < args.length; i++) {
|
|
333
|
+
const a = args[i];
|
|
334
|
+
if (a === "-h" || a === "--help") out.help = true;
|
|
335
|
+
else if (a === "-p" || a === "--port") out.port = args[++i];
|
|
336
|
+
else if (a === "--no-open") out.noOpen = true;
|
|
337
|
+
else if (a === "--uninstall") out.uninstall = true;
|
|
338
|
+
else if (a === "--workspace") out.workspace = args[++i];
|
|
339
|
+
else if (a === "--scope") out.scope = true;
|
|
340
|
+
else if (a === "--all") out.all = true; // legacy no-op (now default)
|
|
341
|
+
else if (a === "--no-persist") out.noPersist = true;
|
|
342
|
+
else if (a === "--history") out.history = args[++i];
|
|
343
|
+
else if (a === "--codex") out.codex = true;
|
|
344
|
+
else if (a === "--no-codex") out.noCodex = true;
|
|
345
|
+
}
|
|
346
|
+
return out;
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
function printHelp() {
|
|
350
|
+
process.stdout.write(`agents-deck — live deck of Claude Code + Codex agents
|
|
351
|
+
|
|
352
|
+
Usage:
|
|
353
|
+
agents-deck [options]
|
|
354
|
+
|
|
355
|
+
Options:
|
|
356
|
+
-p, --port <number> Preferred port (default: 4317; falls back to random 4318–4400)
|
|
357
|
+
--no-open Don't open the browser automatically
|
|
358
|
+
--workspace <path> Only capture sessions whose cwd is inside <path>
|
|
359
|
+
--scope Restrict to current working directory
|
|
360
|
+
--all Capture every session (default)
|
|
361
|
+
--history <path> Override events log file (default: ~/.claude/agent-dag/events.jsonl)
|
|
362
|
+
--no-persist Don't write or replay events log (RAM-only)
|
|
363
|
+
--codex Force-enable Codex capture even if ~/.codex/ missing
|
|
364
|
+
--no-codex Skip Codex capture (Claude only)
|
|
365
|
+
--uninstall Remove agents-deck Claude hook entries
|
|
366
|
+
-h, --help Show this help
|
|
367
|
+
`);
|
|
368
|
+
}
|