agent-dag 1.35.32 → 1.35.34
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/dist/web/assets/{index-Ca-CsYuj.js → index-BiZ-D_yB.js} +21 -21
- package/dist/web/index.html +1 -1
- package/package.json +1 -1
- package/src/server/claude-dir.mjs +8 -1
- package/src/server/codex-auth.mjs +14 -1
- package/src/server/cswap-admin.mjs +13 -1
- package/src/server/cswap-auto.mjs +19 -1
- package/src/server/cswap-install.mjs +25 -2
- package/src/server/installer.mjs +7 -1
- package/src/server/npx.mjs +6 -1
- package/src/server/self-update.mjs +9 -2
- package/src/server/term.mjs +42 -8
package/dist/web/index.html
CHANGED
|
@@ -40,7 +40,7 @@
|
|
|
40
40
|
document.documentElement.setAttribute("data-theme", stored === "light" ? "light" : "dark");
|
|
41
41
|
})();
|
|
42
42
|
</script>
|
|
43
|
-
<script type="module" crossorigin src="/assets/index-
|
|
43
|
+
<script type="module" crossorigin src="/assets/index-BiZ-D_yB.js"></script>
|
|
44
44
|
<link rel="stylesheet" crossorigin href="/assets/index-jWyLguyW.css">
|
|
45
45
|
</head>
|
|
46
46
|
<body>
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "agent-dag",
|
|
3
|
-
"version": "1.35.
|
|
3
|
+
"version": "1.35.34",
|
|
4
4
|
"description": "Live deck of Claude Code and Codex agents — watch tool calls, token spend and every Claude Code subagent on one calm canvas. Also available as npx ccdeck and npx agent-dag.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -113,8 +113,15 @@ const CLAUDE_USE_MARKERS = [
|
|
|
113
113
|
* PATH walk is done by hand, with PATHEXT applied on Windows exactly as cmd.exe
|
|
114
114
|
* would — a `claude.cmd` shim from npm and a bare `claude.exe` from the native
|
|
115
115
|
* installer both have to count, and neither is spelled `claude`.
|
|
116
|
+
*
|
|
117
|
+
* Not exported (#383). It is the first half of `hasClaudeInstalled` below, and
|
|
118
|
+
* that function takes the same four injected dependencies — platform, env, home
|
|
119
|
+
* and `exists` — so every branch of this walk is already driven from a test with
|
|
120
|
+
* no host state reaching it, including the Windows PATHEXT branch exercised from
|
|
121
|
+
* a Mac. Exporting it would offer a second, narrower answer to "is Claude Code
|
|
122
|
+
* here", and the point of #402 was that there is one.
|
|
116
123
|
*/
|
|
117
|
-
|
|
124
|
+
function claudeCliOnDisk({
|
|
118
125
|
platform = process.platform,
|
|
119
126
|
env = process.env,
|
|
120
127
|
home = homedir(),
|
|
@@ -100,7 +100,20 @@ const PERMANENT_CODES = new Set([
|
|
|
100
100
|
"invalid_grant",
|
|
101
101
|
]);
|
|
102
102
|
|
|
103
|
-
/**
|
|
103
|
+
/**
|
|
104
|
+
* Decode a JWT payload. Returns null for anything that isn't a 3-part JWT.
|
|
105
|
+
*
|
|
106
|
+
* Exported for its test and not for a caller (#383). Its two readers are
|
|
107
|
+
* `expiryMs` below — which decides whether the deck spends the single-use
|
|
108
|
+
* refresh token, the one mistake in this file that costs the user a `codex
|
|
109
|
+
* login` — and `identityFrom`, which reads the plan, the account id and the
|
|
110
|
+
* email out of the id_token. Both take the answer from a file the deck did not
|
|
111
|
+
* write and cannot validate, so every way this can be handed something that is
|
|
112
|
+
* not a JWT is a real input, and neither reader can be driven far enough to
|
|
113
|
+
* exercise them: `expiryMs` collapses the whole result to one number and
|
|
114
|
+
* `identityFrom` needs a full auth file plus a refresh round-trip to reach.
|
|
115
|
+
* See codex-jwt-decode.test.ts.
|
|
116
|
+
*/
|
|
104
117
|
export function decodeJwt(token) {
|
|
105
118
|
if (typeof token !== "string") return null;
|
|
106
119
|
const parts = token.split(".");
|
|
@@ -110,7 +110,19 @@ export function newSlot(before, after) {
|
|
|
110
110
|
return fresh.length === 1 ? fresh[0] : null;
|
|
111
111
|
}
|
|
112
112
|
|
|
113
|
-
/**
|
|
113
|
+
/**
|
|
114
|
+
* Anthropic's own view of who is signed in. Null when it cannot be read.
|
|
115
|
+
*
|
|
116
|
+
* Exported for its test rather than for a caller (#383). Both callers are inside
|
|
117
|
+
* the login flow and neither can show what was parsed: `spawnLogin` keeps only
|
|
118
|
+
* `identity?.email` as the address to restore to, and `submitLoginCode` turns
|
|
119
|
+
* the whole thing into a pass/fail — a null there is the difference between a
|
|
120
|
+
* sign-in the deck accepts and one it reports as "signed in, but the claude CLI
|
|
121
|
+
* still reports nobody logged in". The success path, where the email read here
|
|
122
|
+
* is what matches the new credential to a cswap slot, is reachable only with a
|
|
123
|
+
* real signed-in CLI on the machine running the suite. See
|
|
124
|
+
* cswap-identity.test.ts.
|
|
125
|
+
*/
|
|
114
126
|
export async function currentIdentity() {
|
|
115
127
|
const r = await run(await claudeBin(), ["auth", "status", "--json"], { timeout: 20_000 });
|
|
116
128
|
if (!r.ok) return null;
|
|
@@ -34,7 +34,18 @@ const SETTINGS = {
|
|
|
34
34
|
|
|
35
35
|
// ── settings ───────────────────────────────────────────────────────────────
|
|
36
36
|
|
|
37
|
-
/**
|
|
37
|
+
/**
|
|
38
|
+
* Parse `cswap config` — "key value (default)" per line.
|
|
39
|
+
*
|
|
40
|
+
* Exported for its test rather than for a caller (#383). The two callers here —
|
|
41
|
+
* `autoStatus`, which hands the map straight to the settings panel, and
|
|
42
|
+
* `tickInterval`, which takes the poll interval out of it — both reduce the
|
|
43
|
+
* parse to something a test cannot see through: the panel takes whatever shape
|
|
44
|
+
* it is given, and the interval collapses four fields to one number that is
|
|
45
|
+
* clamped anyway. The parse itself is a regex over human-formatted output from a
|
|
46
|
+
* separate Python tool, on both line-ending conventions. See
|
|
47
|
+
* cswap-auto-readers.test.ts.
|
|
48
|
+
*/
|
|
38
49
|
export async function readCswapConfig() {
|
|
39
50
|
const r = await run(await cswapBin(), ["config"]);
|
|
40
51
|
if (!r.ok) return null;
|
|
@@ -117,6 +128,13 @@ async function runAutoTick() {
|
|
|
117
128
|
* budget that is already the scarce resource here, and the user would have two
|
|
118
129
|
* things switching their account with no single place showing why. So the deck
|
|
119
130
|
* reports it and stays out of the way.
|
|
131
|
+
*
|
|
132
|
+
* Exported for its test rather than for a caller (#383). Its two callers reduce
|
|
133
|
+
* it to a boolean on a status object and to a skipped tick, so neither can show
|
|
134
|
+
* WHICH command line was matched — and the matching is the whole function. The
|
|
135
|
+
* two halves also run completely different commands, `ps` against
|
|
136
|
+
* `Get-CimInstance`, so on any one machine only half of it is ever exercised at
|
|
137
|
+
* all. See cswap-auto-readers.test.ts, which drives both from either host.
|
|
120
138
|
*/
|
|
121
139
|
export async function externalAutoRunning() {
|
|
122
140
|
// A line is the user's loop if it runs `cswap auto` without --once. Our own
|
|
@@ -87,7 +87,26 @@ export async function cswapBin() {
|
|
|
87
87
|
return "cswap"; // not found; leave the bare name so errors read sensibly
|
|
88
88
|
}
|
|
89
89
|
|
|
90
|
-
/**
|
|
90
|
+
/**
|
|
91
|
+
* Forget the cached resolution — call after installing.
|
|
92
|
+
*
|
|
93
|
+
* WHAT IS ACTUALLY STALE HERE (#383). Note that `cswapBin` above memoizes only
|
|
94
|
+
* SUCCESS: the "not found" answer is the bare name returned without ever being
|
|
95
|
+
* written to `_bin`, so a lookup that found nothing is already re-run on the
|
|
96
|
+
* next call and there is no negative result for this to clear. What it clears is
|
|
97
|
+
* a positive one — a path that resolved, was cached, and has since been
|
|
98
|
+
* superseded. That is not hypothetical on the install path: `cswapVersion` runs
|
|
99
|
+
* `--version` a SECOND time under the default timeout after `cswapBin` has
|
|
100
|
+
* already cached the copy its own 8s probe accepted, so a copy that is present
|
|
101
|
+
* but too slow, half-written or broken caches a path and still reports no
|
|
102
|
+
* version — and the install that follows lands a working cswap somewhere the
|
|
103
|
+
* cached path may not point at. Clearing is cheap; a deck driving the wrong
|
|
104
|
+
* binary for the life of the process is not.
|
|
105
|
+
*
|
|
106
|
+
* Exported for its test rather than for a caller (#383): the one caller is
|
|
107
|
+
* ensureCswap below, whose return value says nothing about which binary the
|
|
108
|
+
* following twenty account operations will be sent to. See cswap-bin-memo.test.ts.
|
|
109
|
+
*/
|
|
91
110
|
export function resetCswapBin() { _bin = null; }
|
|
92
111
|
|
|
93
112
|
/** Installed version string, or null when cswap cannot be found. */
|
|
@@ -327,7 +346,11 @@ export async function ensureCswap() {
|
|
|
327
346
|
|
|
328
347
|
const result = await installCswap();
|
|
329
348
|
if (!result.ok) return { state: "unavailable", ...result };
|
|
330
|
-
|
|
349
|
+
// Something was just installed, so any path resolved before it is a guess made
|
|
350
|
+
// against a different filesystem. Nothing is cached when the earlier lookup
|
|
351
|
+
// found nothing — see resetCswapBin — but a lookup that DID resolve, to a copy
|
|
352
|
+
// whose `--version` then failed, is exactly the case that got us here.
|
|
353
|
+
resetCswapBin();
|
|
331
354
|
|
|
332
355
|
// Freshly installed tools land in ~/.local/bin, which may not be on the PATH
|
|
333
356
|
// of the shell that launched us — cswapBin looks there directly, so this
|
package/src/server/installer.mjs
CHANGED
|
@@ -530,7 +530,13 @@ export function keepDiscovery({ port, workspace, token, persist = null, codex =
|
|
|
530
530
|
return { file: discoveryPath(), check, stop: () => clearInterval(timer) };
|
|
531
531
|
}
|
|
532
532
|
|
|
533
|
-
|
|
533
|
+
// CLAUDE_EVENTS used to ride along here (#383). It is the events list of the
|
|
534
|
+
// `claude` entry in PROVIDERS and has never had a reader outside this file; it
|
|
535
|
+
// was easy to miss because the three directories beside it ARE imported and
|
|
536
|
+
// because the long justification below belongs to the SECOND export, not this
|
|
537
|
+
// one. Which events the deck asks Claude Code for is answered by installHooks
|
|
538
|
+
// writing settings.json, which is what the tests read.
|
|
539
|
+
export { AGENT_DAG_DIR, CLAUDE_DIR, CODEX_DIR };
|
|
534
540
|
// Exported for the other modules that rewrite settings.json — the sound toggle
|
|
535
541
|
// today. Every one of them needs the same two guarantees: a file we cannot
|
|
536
542
|
// parse is never treated as an empty one, and the replacement is a single
|
package/src/server/npx.mjs
CHANGED
|
@@ -103,7 +103,12 @@ export function npxLaunch(args, {
|
|
|
103
103
|
// Long enough for a cold tarball on a slow line; short enough that a connection
|
|
104
104
|
// hanging on a dead proxy does not hold the deck's Update button for the three
|
|
105
105
|
// minutes the tab waits before giving up on its own.
|
|
106
|
-
|
|
106
|
+
//
|
|
107
|
+
// Not exported (#383): it is the default of `prefetch`'s `timeoutMs` parameter
|
|
108
|
+
// and nothing else. A caller that wants a different budget passes one, and a
|
|
109
|
+
// caller that wants this one passes nothing — there is no third caller for the
|
|
110
|
+
// number to be worth naming across the module boundary.
|
|
111
|
+
const PREFETCH_TIMEOUT_MS = 120_000;
|
|
107
112
|
|
|
108
113
|
/**
|
|
109
114
|
* What to hand npx to DOWNLOAD a spec without running it.
|
|
@@ -132,8 +132,15 @@ export function isNpxInstall(pkgRoot) {
|
|
|
132
132
|
|
|
133
133
|
/** A git checkout is the maintainer's own tree. Its version routinely sits
|
|
134
134
|
* ahead of npm, and telling someone to `npm i -g` over their working copy is
|
|
135
|
-
* actively wrong, so the registry side of the check is skipped there.
|
|
136
|
-
|
|
135
|
+
* actively wrong, so the registry side of the check is skipped there.
|
|
136
|
+
*
|
|
137
|
+
* Not exported (#383). It gates three answers and each of the three is already
|
|
138
|
+
* asserted against a directory with a real `.git` in it — `upgradeCommand`
|
|
139
|
+
* returning "git pull && npm run build", `upgradeName` refusing to move a
|
|
140
|
+
* checkout onto a published alias, and `startUpgrade` refusing with
|
|
141
|
+
* `git_checkout`. A test importing the predicate would restate what those three
|
|
142
|
+
* already prove, one level further from the behaviour a user can see. */
|
|
143
|
+
function isGitCheckout(pkgRoot) {
|
|
137
144
|
try { return existsSync(join(pkgRoot, ".git")); } catch { return false; }
|
|
138
145
|
}
|
|
139
146
|
|
package/src/server/term.mjs
CHANGED
|
@@ -411,22 +411,54 @@ function renderWord(word) {
|
|
|
411
411
|
return rows.map((r) => r.replace(/\s+$/, ""));
|
|
412
412
|
}
|
|
413
413
|
|
|
414
|
+
/** The drawn mark itself, three text rows of half-blocks.
|
|
415
|
+
*
|
|
416
|
+
* Exported for one reader, and it is a test rather than a caller (#383). This
|
|
417
|
+
* is the ONLY place the art's true width can be measured: `renderWord` trims
|
|
418
|
+
* each row's trailing blanks, so the rows are not all the same length, and the
|
|
419
|
+
* arithmetic in WORDMARK_WIDTH below is a hand-computed prediction of the
|
|
420
|
+
* widest of them. Nothing in the banner path compares the two — `wordmark()`
|
|
421
|
+
* gates on WORDMARK_WIDTH and then prints THESE lines — so an under-counting
|
|
422
|
+
* width lets the art render into a terminal too narrow to hold it and the
|
|
423
|
+
* three rows wrap into six. See wordmark-art-width.test.ts, which is the
|
|
424
|
+
* comparison this export exists to make possible. */
|
|
414
425
|
export const WORDMARK_LINES = renderWord("ccdeck");
|
|
415
426
|
/** 29 columns of art, plus the two-space indent every other line uses. */
|
|
416
427
|
export const WORDMARK_WIDTH = 2 + LETTERS.c[0].length * 6 + 5;
|
|
417
428
|
|
|
418
|
-
/**
|
|
419
|
-
*
|
|
420
|
-
|
|
429
|
+
/**
|
|
430
|
+
* The tagline, longest version that fits — the name and version are the part
|
|
431
|
+
* that must survive, the rest is context.
|
|
432
|
+
*
|
|
433
|
+
* `prefix` is how many columns are already spent on the line the tagline lands
|
|
434
|
+
* on, and it is a parameter because the three layouts below do not agree on it
|
|
435
|
+
* (#383). In the full banner the tagline gets a line of its own behind a
|
|
436
|
+
* two-space indent; in the compact and plain ones it shares the line with the
|
|
437
|
+
* product name, which costs eight or nine columns more. This budget was
|
|
438
|
+
* hard-coded at 2 for all three, so between 21 and 31 columns — the widths where
|
|
439
|
+
* the compact form is chosen and the medium tagline still looks affordable —
|
|
440
|
+
* the banner printed a 36-column line into a terminal that could not hold it and
|
|
441
|
+
* the first thing a user saw wrapped. `columns - 1` keeps the last cell empty,
|
|
442
|
+
* because a line ending exactly at the right margin makes some terminals wrap
|
|
443
|
+
* anyway.
|
|
444
|
+
*/
|
|
445
|
+
function tagline(version, columns, bullet, prefix) {
|
|
421
446
|
const v = `v${version}`;
|
|
422
447
|
const options = [
|
|
423
448
|
`${v} ${bullet} live agent DAG ${bullet} Claude Code + Codex`,
|
|
424
449
|
`${v} ${bullet} live agent DAG`,
|
|
425
450
|
v,
|
|
426
451
|
];
|
|
427
|
-
return options.find((o) => o.length +
|
|
452
|
+
return options.find((o) => o.length + prefix <= columns - 1) ?? v;
|
|
428
453
|
}
|
|
429
454
|
|
|
455
|
+
// What each layout spends before the tagline starts, counted off the strings
|
|
456
|
+
// built in `wordmark` below: " ccdeck " for plain, " ccdeck " for compact,
|
|
457
|
+
// and the bare two-space indent for the full banner's own tagline line.
|
|
458
|
+
const PLAIN_PREFIX_W = " ccdeck ".length;
|
|
459
|
+
const COMPACT_PREFIX_W = " ccdeck ".length;
|
|
460
|
+
const TAG_INDENT_W = " ".length;
|
|
461
|
+
|
|
430
462
|
/**
|
|
431
463
|
* The wordmark, in the largest form this terminal can hold.
|
|
432
464
|
*
|
|
@@ -442,11 +474,13 @@ export function wordmark({
|
|
|
442
474
|
columns = 80, version = "0.0.0", profile = "none", unicode = true, pal = palette(profile),
|
|
443
475
|
} = {}) {
|
|
444
476
|
const g = glyphs(unicode);
|
|
445
|
-
|
|
477
|
+
// Each layout asks for its own tagline, because each leaves it a different
|
|
478
|
+
// amount of room — see the prefix constants above.
|
|
479
|
+
const tag = (prefix) => tagline(version, columns, g.bullet, prefix);
|
|
446
480
|
|
|
447
|
-
if (profile === "none") return { kind: "plain", lines: ["", ` ccdeck ${tag}`, ""] };
|
|
481
|
+
if (profile === "none") return { kind: "plain", lines: ["", ` ccdeck ${tag(PLAIN_PREFIX_W)}`, ""] };
|
|
448
482
|
if (!unicode || columns < WORDMARK_WIDTH + 1) {
|
|
449
|
-
return { kind: "compact", lines: ["", ` ${pal.bold}${pal.accent}ccdeck${pal.reset} ${pal.muted}${tag}${pal.reset}`, ""] };
|
|
483
|
+
return { kind: "compact", lines: ["", ` ${pal.bold}${pal.accent}ccdeck${pal.reset} ${pal.muted}${tag(COMPACT_PREFIX_W)}${pal.reset}`, ""] };
|
|
450
484
|
}
|
|
451
485
|
|
|
452
486
|
const ramp = [pal.accentSoft, pal.accent, pal.accentDeep];
|
|
@@ -456,7 +490,7 @@ export function wordmark({
|
|
|
456
490
|
"",
|
|
457
491
|
...WORDMARK_LINES.map((l, i) => ` ${ramp[i]}${l}${pal.reset}`),
|
|
458
492
|
"",
|
|
459
|
-
` ${pal.muted}${tag}${pal.reset}`,
|
|
493
|
+
` ${pal.muted}${tag(TAG_INDENT_W)}${pal.reset}`,
|
|
460
494
|
"",
|
|
461
495
|
],
|
|
462
496
|
};
|