agent-dag 1.35.0 → 1.35.2
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 +19 -0
- package/bin/deck.js +61 -7
- package/dist/web/assets/index-B4-a3WNi.js +72 -0
- package/dist/web/assets/index-FKxgHN0p.css +1 -0
- package/dist/web/index.html +2 -2
- package/package.json +1 -1
- package/src/server/claude-dir.mjs +191 -7
- package/src/server/codex-usage.mjs +29 -10
- package/src/server/index.mjs +20 -1
- package/src/server/quota.mjs +7 -39
- package/dist/web/assets/index-CMZYhML1.js +0 -70
- package/dist/web/assets/index-CRPobBZf.css +0 -1
package/README.md
CHANGED
|
@@ -117,10 +117,21 @@ ccdeck [options]
|
|
|
117
117
|
--no-persist RAM-only mode — don't write or replay the log
|
|
118
118
|
--codex Force Codex capture even if ~/.codex/ is missing
|
|
119
119
|
--no-codex Skip Codex capture (Claude only)
|
|
120
|
+
--claude Force Claude capture even if Claude Code wasn't found
|
|
121
|
+
--no-claude Skip Claude entirely — no hooks, no claude-swap,
|
|
122
|
+
no Accounts panel (Codex only)
|
|
120
123
|
--uninstall Remove ccdeck's hooks from settings files
|
|
121
124
|
-h, --help Show this help
|
|
122
125
|
```
|
|
123
126
|
|
|
127
|
+
ccdeck looks for each CLI before it does anything on that CLI's behalf. Claude
|
|
128
|
+
Code counts as present when its binary is on `PATH` (or in one of the places its
|
|
129
|
+
installers put it), or when its config dir carries traces of having been used;
|
|
130
|
+
Codex counts as present when `~/.codex/` exists. On a machine with only one of
|
|
131
|
+
them, the other one's hooks, installs and panels are skipped rather than shown
|
|
132
|
+
empty — the boot banner says which way it went, and `--claude` / `--codex`
|
|
133
|
+
override it if the guess is wrong.
|
|
134
|
+
|
|
124
135
|
`--workspace` is a filter this deck applies to itself, not a claim on the sessions it matches: **every** running deck whose workspace contains a session's directory draws that session, so a machine-wide deck and one scoped to `~/proj` both show the agents working inside `~/proj`. It reads the same way on both capture paths — Claude Code's hook and Codex's rollout files — and the events log still gets exactly one copy of each event, whichever decks are up. A relative path is resolved against the directory you start the deck in, and once, so both paths scope to the same tree.
|
|
125
136
|
|
|
126
137
|
Environment:
|
|
@@ -147,6 +158,14 @@ npx ccdeck --uninstall
|
|
|
147
158
|
|
|
148
159
|
Removes every hook entry ccdeck injected from `~/.claude/settings.json`, and `~/.codex/hooks.json` if present.
|
|
149
160
|
|
|
161
|
+
It removes the hook entries and nothing else. The forwarder script
|
|
162
|
+
(`~/.claude/agent-dag/hook.js`), the discovery directory around it, the events
|
|
163
|
+
log, and the tools ccdeck installed for you — claude-swap, ccusage, and a `uv`
|
|
164
|
+
binary if it had to fetch one — are all left in place, and each has its own
|
|
165
|
+
uninstaller. Deleting `~/.claude/agent-dag/` and `~/.agents-deck/` clears
|
|
166
|
+
ccdeck's own files; `uv tool uninstall claude-swap` (or `pipx uninstall
|
|
167
|
+
claude-swap`) removes the account switcher.
|
|
168
|
+
|
|
150
169
|
## Design
|
|
151
170
|
|
|
152
171
|
One canvas. No tabs. No kanban.
|
package/bin/deck.js
CHANGED
|
@@ -83,7 +83,7 @@ const rawWorkspace = flags.workspace != null
|
|
|
83
83
|
const openBrowser = flags.noOpen !== true;
|
|
84
84
|
// The events log lives beside the discovery files, so it follows the Claude
|
|
85
85
|
// config dir rather than assuming ~/.claude — see src/server/claude-dir.mjs.
|
|
86
|
-
const { claudeConfigDir } =
|
|
86
|
+
const { claudeConfigDir, hasClaudeInstalled } =
|
|
87
87
|
await import(pathToFileURL(join(PKG_ROOT, "src/server/claude-dir.mjs")).href);
|
|
88
88
|
// Resolved here rather than left as typed: the discovery file publishes this
|
|
89
89
|
// path so the hook can tell which decks share one log and elect a single
|
|
@@ -120,6 +120,25 @@ const wantCodex = flags.noCodex
|
|
|
120
120
|
? false
|
|
121
121
|
: (flags.codex === true || hasCodexInstalled());
|
|
122
122
|
|
|
123
|
+
// The same question for the other CLI, and the one nobody was asking. README
|
|
124
|
+
// offers "Claude Code CLI or OpenAI Codex CLI (or both)"; a Codex-only machine
|
|
125
|
+
// nonetheless got a Python account-switcher installed for a CLI it does not
|
|
126
|
+
// have, an accounts panel open on first run, and a banner line telling it to
|
|
127
|
+
// sign into that CLI (#402). Everything the deck installs or opens on the
|
|
128
|
+
// Claude side now hangs off this one answer, and it is stated in the banner so
|
|
129
|
+
// a wrong answer is visible rather than mysterious.
|
|
130
|
+
//
|
|
131
|
+
// `--claude` is the escape hatch for a false negative, which is the failure
|
|
132
|
+
// that matters: hasClaudeInstalled looks for the binary and for traces of use,
|
|
133
|
+
// and a machine that hides Claude Code from both would otherwise lose its hooks
|
|
134
|
+
// with no way to ask for them back. `--no-claude` is the opt-out the Claude side
|
|
135
|
+
// never had — the mirror of --no-codex — and it is also what a Codex-only user
|
|
136
|
+
// with a settings.json the installer refuses to rewrite needs, since that
|
|
137
|
+
// refusal is fatal at boot on a component they do not use.
|
|
138
|
+
const wantClaude = flags.noClaude
|
|
139
|
+
? false
|
|
140
|
+
: (flags.claude === true || hasClaudeInstalled());
|
|
141
|
+
|
|
123
142
|
const WEB_DIST = join(PKG_ROOT, "dist", "web", "index.html");
|
|
124
143
|
if (!existsSync(WEB_DIST)) {
|
|
125
144
|
console.error(`${PRODUCT}: ui not built. run \`npm run build\` (or \`pnpm build\`) first.`);
|
|
@@ -239,16 +258,28 @@ async function step(label, work) {
|
|
|
239
258
|
* rejection.
|
|
240
259
|
*/
|
|
241
260
|
function startupWork() {
|
|
261
|
+
// Every job below this line serves Claude Code and only Claude Code: the
|
|
262
|
+
// hooks go in Claude Code's settings.json, claude-swap switches Claude
|
|
263
|
+
// accounts, and ccusage reads Claude Code's own session logs. On a machine
|
|
264
|
+
// without Claude Code all three are work done for a CLI that is not there —
|
|
265
|
+
// two of them installs the user did not ask for — so they are not started at
|
|
266
|
+
// all rather than started and then reported as failures. `null` is how each
|
|
267
|
+
// one says "not attempted", which reportStartup tells apart from "tried and
|
|
268
|
+
// could not".
|
|
269
|
+
|
|
242
270
|
// Settings the installer cannot parse are settings it cannot rewrite without
|
|
243
271
|
// losing them, so it refuses — and that refusal is reported rather than
|
|
244
272
|
// thrown, because it is the only thing the user can act on.
|
|
245
|
-
const hooks =
|
|
273
|
+
const hooks = wantClaude
|
|
274
|
+
? installHooks({ provider: "claude" }).then(v => ({ ok: true, v }), err => ({ ok: false, err }))
|
|
275
|
+
: Promise.resolve(null);
|
|
246
276
|
|
|
247
277
|
// claude-swap backs the multi-account panel, and an empty store leaves that
|
|
248
278
|
// panel useless even when the tool is there — so the account already signed
|
|
249
279
|
// in is registered once. Bounded inside seedFirstAccount: empty store only,
|
|
250
280
|
// once ever, never with NO_INSTALL set.
|
|
251
281
|
const cswap = (async () => {
|
|
282
|
+
if (!wantClaude) return null;
|
|
252
283
|
const { ensureCswap } = await import(pathToFileURL(join(PKG_ROOT, "src/server/cswap-install.mjs")).href);
|
|
253
284
|
const cs = await ensureCswap();
|
|
254
285
|
const usable = cs.state === "present" || cs.state === "installed" || cs.state === "upgrading";
|
|
@@ -259,7 +290,10 @@ function startupWork() {
|
|
|
259
290
|
|
|
260
291
|
// ccusage backs the usage-history modal. Primed at boot rather than on first
|
|
261
292
|
// open so a cold machine pays the install while the deck is still starting.
|
|
293
|
+
// Nothing is lost by skipping the prime: runCcusage falls back to npx, so the
|
|
294
|
+
// modal still answers if it is ever opened — it just pays the wait itself.
|
|
262
295
|
const ccusage = (async () => {
|
|
296
|
+
if (!wantClaude) return null;
|
|
263
297
|
if (process.env.AGENTS_DECK_NO_INSTALL === "1") return null;
|
|
264
298
|
const { primeCcusage } = await import(pathToFileURL(join(PKG_ROOT, "src/server/ccusage.mjs")).href);
|
|
265
299
|
return primeCcusage();
|
|
@@ -290,14 +324,22 @@ async function reportStartup(jobs) {
|
|
|
290
324
|
}));
|
|
291
325
|
|
|
292
326
|
const hooks = await step(`installing Claude hooks${G.ellipsis}`, jobs.hooks);
|
|
293
|
-
if (
|
|
327
|
+
if (hooks === null) {
|
|
328
|
+
// Said in the same shape as the Codex row below, because it is the same
|
|
329
|
+
// sentence: this deck is not watching that CLI, and here is why. It also
|
|
330
|
+
// retires the one boot failure a Codex-only machine could hit — an
|
|
331
|
+
// unparseable or unwritable settings.json used to exit(1) below, killing a
|
|
332
|
+
// deck over a file belonging to a CLI the user does not run.
|
|
333
|
+
write(row({ label: "Claude hooks", detail: `skipped ${G.dash} no Claude Code found, or --no-claude` }));
|
|
334
|
+
} else if (!hooks.ok) {
|
|
294
335
|
// The file it names is one only the user can repair, and every Claude Code
|
|
295
336
|
// session on this machine is reading it too.
|
|
296
337
|
write(row({ mark: G.fail, tone: P.err, label: "Claude hooks", detail: "not installed" }));
|
|
297
338
|
console.error(`\n ${PRODUCT}: ${hooks.err.message}\n`);
|
|
298
339
|
process.exit(1);
|
|
340
|
+
} else {
|
|
341
|
+
write(row({ mark: G.ok, label: "Claude hooks", detail: fileLink(hooks.v.hookPath) }));
|
|
299
342
|
}
|
|
300
|
-
write(row({ mark: G.ok, label: "Claude hooks", detail: fileLink(hooks.v.hookPath) }));
|
|
301
343
|
|
|
302
344
|
// Codex CLI hooks never fire on Windows (sandbox refuses to spawn the hook
|
|
303
345
|
// command). Instead the server tails Codex's rollout JSONL files directly, so
|
|
@@ -316,7 +358,13 @@ async function reportStartup(jobs) {
|
|
|
316
358
|
|
|
317
359
|
const swap = await step(`checking claude-swap${G.ellipsis}`, jobs.cswap);
|
|
318
360
|
const cs = swap?.cs;
|
|
319
|
-
if (
|
|
361
|
+
if (!wantClaude) {
|
|
362
|
+
// claude-swap is a Python tool that switches Claude Code accounts, and the
|
|
363
|
+
// deck used to fetch a uv binary to install it on machines with no Claude
|
|
364
|
+
// Code at all. Saying so is the point of the row: it is the one place a
|
|
365
|
+
// user can learn that the accounts panel is missing on purpose.
|
|
366
|
+
write(row({ label: "claude-swap", detail: `skipped ${G.dash} accounts are Claude-only` }));
|
|
367
|
+
} else if (cs?.state === "present") {
|
|
320
368
|
write(row({ mark: G.ok, label: "claude-swap", detail: `v${cs.version} (accounts panel enabled)` }));
|
|
321
369
|
} else if (cs?.state === "installed") {
|
|
322
370
|
write(row({ mark: G.ok, label: "claude-swap", detail: `installed v${cs.version} via ${cs.via}` }));
|
|
@@ -460,7 +508,7 @@ function restartTarget() {
|
|
|
460
508
|
}
|
|
461
509
|
|
|
462
510
|
const starting = startServer({
|
|
463
|
-
port, persist, workspace, codex: wantCodex,
|
|
511
|
+
port, persist, workspace, codex: wantCodex, claude: wantClaude,
|
|
464
512
|
// Withheld when nothing is supervising us: without a parent, exiting is just
|
|
465
513
|
// exiting, and /api/restart answers 501 so the UI hides the control.
|
|
466
514
|
onRestart: SUPERVISED ? requestRestart : null,
|
|
@@ -619,6 +667,8 @@ function parseArgs(args) {
|
|
|
619
667
|
else if (a === "--history") out.history = args[++i];
|
|
620
668
|
else if (a === "--codex") out.codex = true;
|
|
621
669
|
else if (a === "--no-codex") out.noCodex = true;
|
|
670
|
+
else if (a === "--claude") out.claude = true;
|
|
671
|
+
else if (a === "--no-claude") out.noClaude = true;
|
|
622
672
|
}
|
|
623
673
|
return out;
|
|
624
674
|
}
|
|
@@ -639,8 +689,12 @@ Options:
|
|
|
639
689
|
--no-persist Don't write or replay events log (RAM-only)
|
|
640
690
|
--codex Force-enable Codex capture even if ~/.codex/ missing
|
|
641
691
|
--no-codex Skip Codex capture (Claude only)
|
|
692
|
+
--claude Force-enable Claude capture even if Claude Code wasn't found
|
|
693
|
+
--no-claude Skip Claude entirely: no hooks, no claude-swap, no accounts panel
|
|
642
694
|
--uninstall Remove ${PRODUCT}'s hooks from ~/.claude/settings.json and
|
|
643
|
-
~/.codex/hooks.json, and restore any sound hooks of yours it parked
|
|
695
|
+
~/.codex/hooks.json, and restore any sound hooks of yours it parked.
|
|
696
|
+
Hook entries only: the forwarder script, ~/.claude/agent-dag/,
|
|
697
|
+
the events log, ~/.agents-deck/ and claude-swap all stay
|
|
644
698
|
-h, --help Show this help
|
|
645
699
|
`);
|
|
646
700
|
}
|