ccdeck 3.43.4 → 3.45.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 +79 -18
- package/THIRD_PARTY_NOTICES.md +70 -7
- package/bin/cli/commands.js +613 -0
- package/bin/cli/help.js +26 -3
- package/bin/cli/one-shot.js +2 -2
- package/bin/cli/picker.js +86 -0
- package/bin/cli/startup.js +13 -0
- package/bin/deck.js +16 -1
- package/dist/web/assets/BrowserWatchModal-Cbzf3TWa.js +1 -0
- package/dist/web/assets/IntegrationModal-BnnBr0a3.js +5 -0
- package/dist/web/assets/RadioPanel-BIqrMLQN.js +1 -0
- package/dist/web/assets/TrafficRadar-D1gZCIwX.js +8 -0
- package/dist/web/assets/UsageHistoryModal-DAhplVju.js +1 -0
- package/dist/web/assets/app-BW3NYUYB.js +318 -0
- package/dist/web/assets/app-C5RY_wZ4.css +1 -0
- package/dist/web/assets/{index-Ch4ks76J.js → index-r8Ymr47Q.js} +1 -1
- package/dist/web/index.html +3 -3
- package/package.json +2 -1
- package/release-notes.json +52 -0
- package/src/server/account-notify.mjs +268 -9
- package/src/server/account-pick.mjs +160 -0
- package/src/server/account-projects.mjs +55 -15
- package/src/server/account-routes.mjs +47 -4
- package/src/server/account-watch.mjs +107 -4
- package/src/server/activity.mjs +3 -2
- package/src/server/args.mjs +112 -2
- package/src/server/autoswitch-failover.mjs +138 -0
- package/src/server/claude-accounts.mjs +127 -16
- package/src/server/claude-dir.mjs +142 -5
- package/src/server/claude-identity.mjs +23 -0
- package/src/server/cli-lines.mjs +224 -0
- package/src/server/codex-cost-usage.mjs +67 -0
- package/src/server/codex-enrichment.mjs +53 -9
- package/src/server/codex-review-policy.d.mts +4 -0
- package/src/server/codex-review-policy.mjs +11 -0
- package/src/server/codex-skills.mjs +146 -0
- package/src/server/codex-watch.mjs +5 -2
- package/src/server/cswap-auto-choice.mjs +53 -0
- package/src/server/cswap-auto-loop.mjs +100 -9
- package/src/server/deck-client.mjs +112 -0
- package/src/server/deck-prefs.mjs +26 -5
- package/src/server/event-pipeline.mjs +19 -3
- package/src/server/event-routes.mjs +15 -0
- package/src/server/exec.mjs +6 -2
- package/src/server/feature-use.mjs +11 -1
- package/src/server/forge-http.mjs +514 -0
- package/src/server/forge-public-fetch.mjs +228 -0
- package/src/server/forge-routes.mjs +154 -0
- package/src/server/github-api.mjs +1369 -0
- package/src/server/gitlab-api.mjs +1271 -0
- package/src/server/index.mjs +23 -3
- package/src/server/installer.mjs +149 -8
- package/src/server/integrations/cache.mjs +42 -0
- package/src/server/integrations/errors.mjs +167 -0
- package/src/server/integrations/forge.mjs +76 -0
- package/src/server/integrations/jira.mjs +1026 -0
- package/src/server/integrations/providers.mjs +11 -0
- package/src/server/integrations/proxy.mjs +314 -0
- package/src/server/integrations/registry.mjs +142 -0
- package/src/server/integrations/routes.mjs +498 -0
- package/src/server/integrations/store.mjs +226 -0
- package/src/server/lan-deck.mjs +7 -0
- package/src/server/lan-elsewhere.d.mts +30 -0
- package/src/server/lan-elsewhere.mjs +137 -0
- package/src/server/lan-engine.mjs +2 -2
- package/src/server/lan-sync.mjs +3 -3
- package/src/server/lan-wire.mjs +3 -3
- package/src/server/login-guard.mjs +162 -0
- package/src/server/login-service.mjs +3 -0
- package/src/server/org-telemetry.mjs +173 -0
- package/src/server/pinned-build.mjs +8 -0
- package/src/server/prefs-routes.mjs +4 -0
- package/src/server/request-gates.mjs +26 -0
- package/src/server/session-read-gate.mjs +5 -3
- package/src/server/session-summary.mjs +281 -0
- package/src/server/skill-hook.mjs +162 -0
- package/src/server/stop-continue-gate.d.mts +8 -0
- package/src/server/stop-continue-gate.mjs +77 -0
- package/src/server/stop-continue.mjs +544 -0
- package/src/server/stop-failure.d.mts +10 -0
- package/src/server/stop-failure.mjs +97 -0
- package/src/server/swap-log.mjs +19 -10
- package/src/server/tool-audit-report.mjs +5 -3
- package/src/server/tool-audit-routes.mjs +5 -3
- package/src/server/tool-audit-skills.mjs +82 -0
- package/src/server/tool-audit.mjs +53 -11
- package/src/server/traffic-radar-capture.mjs +10 -1
- package/src/server/traffic-radar-monitor.mjs +4 -2
- package/src/server/traffic-radar-routes.mjs +13 -0
- package/src/server/traffic-radar-service.mjs +133 -0
- package/src/server/usage-day.mjs +3 -1
- package/dist/web/assets/BrowserWatchModal-nOxhGOO4.js +0 -1
- package/dist/web/assets/RadioPanel-BLfpQ1Iw.js +0 -1
- package/dist/web/assets/TrafficRadar-BFrObpjW.js +0 -1
- package/dist/web/assets/UsageHistoryModal-DCnFf1vi.js +0 -1
- package/dist/web/assets/app-BiJ8TfUl.css +0 -1
- package/dist/web/assets/app-DBsNB-2y.js +0 -312
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
// Whether the deck may add the UserPromptExpansion hook that Skill activity
|
|
2
|
+
// counts typed slash commands with (#2181) — and, until somebody asks for it,
|
|
3
|
+
// the answer is no.
|
|
4
|
+
//
|
|
5
|
+
// WHY THIS ENTRY IS GATED HARDER THAN STOPFAILURE. A hook event name Claude
|
|
6
|
+
// Code does not know is not skipped by every version. Up to 2.1.100 it made
|
|
7
|
+
// Claude Code ignore the WHOLE settings.json — the user's permissions, env,
|
|
8
|
+
// model and every hook in it, ours included — without a word on screen. 2.1.101
|
|
9
|
+
// changed that ("an unrecognized hook event name in settings.json no longer
|
|
10
|
+
// causes the entire file to be ignored", Claude Code's CHANGELOG.md, 2.1.101).
|
|
11
|
+
// UserPromptExpansion itself arrived later still: the first npm release whose
|
|
12
|
+
// CLI carries the name, and whose Agent SDK lists it in HOOK_EVENTS, is 2.1.116
|
|
13
|
+
// (2.1.114 has neither, and there is no 2.1.115). Checked offline against the
|
|
14
|
+
// published packages, with a scratch config holding the deck's own entry shape:
|
|
15
|
+
//
|
|
16
|
+
// 2.1.100 the whole file ignored — no hook fired, no env applied
|
|
17
|
+
// 2.1.101 the file read, the entry ignored
|
|
18
|
+
// 2.1.114 the file read, the entry ignored
|
|
19
|
+
// 2.1.116 the file read, the entry fired on a typed custom command
|
|
20
|
+
//
|
|
21
|
+
// So the gate is the later of the two: 2.1.116, which is where the entry is
|
|
22
|
+
// both harmless and useful. Below it the entry buys nothing, and below 2.1.101
|
|
23
|
+
// it costs the user every setting they have.
|
|
24
|
+
//
|
|
25
|
+
// WHAT HAS TO BE TRUE before the entry is written:
|
|
26
|
+
// - the deck was started with AGENTS_DECK_SKILL_HOOK=1 — never by default;
|
|
27
|
+
// - at least one Claude Code was found, the way the deck finds `claude`: the
|
|
28
|
+
// candidate list (claude-dir.mjs) and AGENTS_DECK_CLAUDE;
|
|
29
|
+
// - EVERY one found says which version it is, and every one is 2.1.116 or
|
|
30
|
+
// newer. An old CLI anywhere on the machine still reads the same file, and
|
|
31
|
+
// one that will not say its version is treated as an old one.
|
|
32
|
+
// A version is read off disk first (claude-dir.mjs, no child process). Only
|
|
33
|
+
// for an install whose layout does not name one — a wrapper script, an unusual
|
|
34
|
+
// prefix — and only once the user has opted in, is that binary asked
|
|
35
|
+
// `--version`, by its full path and as argv, and believed only when it answers
|
|
36
|
+
// the way Claude Code does: `<x.y.z> (Claude Code)`.
|
|
37
|
+
//
|
|
38
|
+
// Every boot re-decides. A machine that stops passing — the opt-in removed, a
|
|
39
|
+
// Claude Code rolled back, a second, older one installed — has the entry taken
|
|
40
|
+
// back out by the next start (installer.mjs), and the user's own
|
|
41
|
+
// UserPromptExpansion hooks are left where they are.
|
|
42
|
+
import { claudeCodeInstalls, claudeVersionAtLeast } from "./claude-dir.mjs";
|
|
43
|
+
import { run } from "./exec.mjs";
|
|
44
|
+
|
|
45
|
+
export const USER_PROMPT_EXPANSION = "UserPromptExpansion";
|
|
46
|
+
|
|
47
|
+
/** The first Claude Code that knows the event: 2.1.116 (npm). */
|
|
48
|
+
export const USER_PROMPT_EXPANSION_SINCE = "2.1.116";
|
|
49
|
+
|
|
50
|
+
/** The first Claude Code that skips an unknown hook event instead of ignoring
|
|
51
|
+
* the whole settings.json: 2.1.101 (CHANGELOG.md). */
|
|
52
|
+
export const UNKNOWN_EVENT_TOLERATED_SINCE = "2.1.101";
|
|
53
|
+
|
|
54
|
+
/** The gate: a version that is past both of the above. */
|
|
55
|
+
export const SKILL_HOOK_SINCE = claudeVersionAtLeast(USER_PROMPT_EXPANSION_SINCE, UNKNOWN_EVENT_TOLERATED_SINCE)
|
|
56
|
+
? USER_PROMPT_EXPANSION_SINCE
|
|
57
|
+
: UNKNOWN_EVENT_TOLERATED_SINCE;
|
|
58
|
+
|
|
59
|
+
/** Set to "1" to ask for the entry. Anything else, or nothing, is no. */
|
|
60
|
+
export const SKILL_HOOK_ENV = "AGENTS_DECK_SKILL_HOOK";
|
|
61
|
+
|
|
62
|
+
/** Whether the person starting the deck asked for the entry. Only the exact
|
|
63
|
+
* string "1": a typo or a "true" does not turn it on. */
|
|
64
|
+
export function skillHookOptedIn(env = process.env) {
|
|
65
|
+
return env?.[SKILL_HOOK_ENV] === "1";
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/** A `--version` that is not quick is not Claude Code's fast path. */
|
|
69
|
+
const PROBE_TIMEOUT_MS = 5_000;
|
|
70
|
+
|
|
71
|
+
/** What Claude Code prints for `--version`, e.g. `2.1.116 (Claude Code)`. A
|
|
72
|
+
* first line in any other shape — a wrapper's own version, an error, an
|
|
73
|
+
* update notice — is no answer. */
|
|
74
|
+
const VERSION_LINE_RE = /^(\d+\.\d+\.\d+(?:[-+][0-9A-Za-z.+-]*)?) \(Claude Code\)$/;
|
|
75
|
+
|
|
76
|
+
/** The version in Claude Code's own `--version` output, or undefined. */
|
|
77
|
+
export function parseClaudeVersionOutput(text) {
|
|
78
|
+
const first = String(text ?? "").split(/\r?\n/).map(l => l.trim()).find(l => l !== "");
|
|
79
|
+
const m = first === undefined ? null : VERSION_LINE_RE.exec(first);
|
|
80
|
+
return m ? m[1] : undefined;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
async function askVersion(runImpl, bin, timeout) {
|
|
84
|
+
try {
|
|
85
|
+
const r = await runImpl(bin, ["--version"], { timeout });
|
|
86
|
+
return r?.ok ? parseClaudeVersionOutput(r.stdout) : undefined;
|
|
87
|
+
} catch {
|
|
88
|
+
return undefined;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* The decision, and why:
|
|
94
|
+
*
|
|
95
|
+
* { install: false, state: "off", reason: "not-enabled" }
|
|
96
|
+
* { install: false, state: "skipped", reason: "no-claude" | "version-unknown" | "too-old", versions }
|
|
97
|
+
* { install: true, state: "on", versions }
|
|
98
|
+
*
|
|
99
|
+
* `versions` is what was found, in the order found, with `null` for an install
|
|
100
|
+
* that did not say. `since` is the gate. Never throws: a probe that fails is a
|
|
101
|
+
* version that is unknown, which is a refusal.
|
|
102
|
+
*
|
|
103
|
+
* Every dependency is injected so the suite can lay out any machine: `installs`
|
|
104
|
+
* is claudeCodeInstalls()'s answer, `runImpl` is exec.mjs's run.
|
|
105
|
+
*/
|
|
106
|
+
export async function skillHookDecision({
|
|
107
|
+
env = process.env,
|
|
108
|
+
optedIn = skillHookOptedIn(env),
|
|
109
|
+
installs,
|
|
110
|
+
runImpl = run,
|
|
111
|
+
timeout = PROBE_TIMEOUT_MS,
|
|
112
|
+
} = {}) {
|
|
113
|
+
const since = SKILL_HOOK_SINCE;
|
|
114
|
+
// Off is decided before anything is looked at: a deck that was not asked
|
|
115
|
+
// spends nothing on the question, and spawns nothing.
|
|
116
|
+
if (!optedIn) return { install: false, state: "off", reason: "not-enabled", since, versions: [] };
|
|
117
|
+
let found;
|
|
118
|
+
try {
|
|
119
|
+
found = Array.isArray(installs) ? [...installs] : claudeCodeInstalls();
|
|
120
|
+
} catch {
|
|
121
|
+
found = [];
|
|
122
|
+
}
|
|
123
|
+
// AGENTS_DECK_CLAUDE is the other way the deck finds `claude` — the sign-in
|
|
124
|
+
// and the account panel run it ahead of the candidate list
|
|
125
|
+
// (claude-identity.mjs) — so it is one more Claude Code that reads this
|
|
126
|
+
// settings.json. Its layout is not read: it is asked.
|
|
127
|
+
const configured = typeof env?.AGENTS_DECK_CLAUDE === "string" ? env.AGENTS_DECK_CLAUDE.trim() : "";
|
|
128
|
+
if (configured && !found.some(i => i.bin === configured)) found.push({ bin: configured, version: undefined });
|
|
129
|
+
if (found.length === 0) return { install: false, state: "skipped", reason: "no-claude", since, versions: [] };
|
|
130
|
+
const versions = [];
|
|
131
|
+
for (const { bin, version } of found) {
|
|
132
|
+
const v = version ?? (typeof bin === "string" && bin ? await askVersion(runImpl, bin, timeout) : undefined);
|
|
133
|
+
versions.push(v ?? null);
|
|
134
|
+
}
|
|
135
|
+
if (versions.some(v => v === null)) return { install: false, state: "skipped", reason: "version-unknown", since, versions };
|
|
136
|
+
if (!versions.every(v => claudeVersionAtLeast(v, since))) {
|
|
137
|
+
return { install: false, state: "skipped", reason: "too-old", since, versions };
|
|
138
|
+
}
|
|
139
|
+
return { install: true, state: "on", since, versions };
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* The boot report's row for the entry — `{ ok, detail }` — or null when there
|
|
144
|
+
* is nothing to say, which is the default: nobody asked for it, and a deck
|
|
145
|
+
* that was not asked prints exactly what it printed before. `dash` is the
|
|
146
|
+
* screen's own (bin/cli/screen.js), so the line carries no punctuation a
|
|
147
|
+
* legacy console cannot draw.
|
|
148
|
+
*/
|
|
149
|
+
export function skillHookStatus(decision, dash = "-") {
|
|
150
|
+
if (!decision || decision.state === "off") return null;
|
|
151
|
+
const found = (decision.versions ?? []).map(v => v ?? "unknown").join(", ");
|
|
152
|
+
if (decision.state === "on") return { ok: true, detail: `on ${dash} counts typed slash commands (Claude Code ${found})` };
|
|
153
|
+
const why = {
|
|
154
|
+
"no-claude": "no Claude Code found",
|
|
155
|
+
"version-unknown": `a Claude Code here does not say its version (found: ${found})`,
|
|
156
|
+
"too-old": `needs Claude Code ${decision.since} or newer everywhere (found: ${found})`,
|
|
157
|
+
"settings-shape": "the hooks in settings.json are not in a shape the deck can add to",
|
|
158
|
+
"backup-failed": "settings.json could not be backed up first",
|
|
159
|
+
raced: "settings.json changed while installing; the next start tries again",
|
|
160
|
+
}[decision.reason] ?? "not allowed";
|
|
161
|
+
return { ok: false, detail: `not installed ${dash} ${why}` };
|
|
162
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { StopFacts } from "./stop-failure.mjs";
|
|
2
|
+
export interface LoginEvidence { ok: boolean; at: number; room: boolean | null }
|
|
3
|
+
export type GateReason = "no-login" | "before-stop" | "no-room";
|
|
4
|
+
export interface Gate { ready: boolean; reason: GateReason | null }
|
|
5
|
+
export const CONTINUE_PROMPT: string;
|
|
6
|
+
export const REPEAT_HOLD_MS: number;
|
|
7
|
+
export function loginEvidence(reading: unknown): LoginEvidence | null;
|
|
8
|
+
export function continueGate(stop: Pick<StopFacts, "kind" | "at"> | null | undefined, login: LoginEvidence | null | undefined): Gate;
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
// When a turn that stopped on an expired login or a usage limit may be run
|
|
2
|
+
// again (#2193), read once for both halves of the deck: the page draws the
|
|
3
|
+
// Continue button from it, and the server refuses a press — and holds back the
|
|
4
|
+
// automatic continue — on the same answer. The page imports this file the way
|
|
5
|
+
// it imports stop-failure.mjs, so the two cannot come to disagree about one
|
|
6
|
+
// stop.
|
|
7
|
+
//
|
|
8
|
+
// WHAT COUNTS AS "A WORKING LOGIN IS ACTIVE AGAIN". The deck's newest reading
|
|
9
|
+
// of the active account's usage (quota.mjs), taken AFTER the stop and without
|
|
10
|
+
// an error. That reading is made with the very credentials a session uses —
|
|
11
|
+
// Claude Code's own token, `claude --print /usage`, or claude-swap's copy of
|
|
12
|
+
// the active account — so a login that is still dead cannot produce one, and
|
|
13
|
+
// one from before the stop says nothing about the login now. A switch of
|
|
14
|
+
// account invalidates the reading, so the next one is about the new account.
|
|
15
|
+
//
|
|
16
|
+
// And the account has to have room: a working login on an account at 100% of
|
|
17
|
+
// its 5-hour or 7-day window would stop the turn again on a usage limit at its
|
|
18
|
+
// first request.
|
|
19
|
+
//
|
|
20
|
+
// Pure. Nothing here reads a clock, a file or a process.
|
|
21
|
+
|
|
22
|
+
/** What the deck sends a background session to continue the turn that
|
|
23
|
+
* failed: Claude Code's own words for it, the default of
|
|
24
|
+
* CLAUDE_CODE_RESUME_PROMPT (code.claude.com/docs/en/env-vars), which is what
|
|
25
|
+
* Claude Code sends when it continues an interrupted turn by itself. */
|
|
26
|
+
export const CONTINUE_PROMPT = "Continue from where you left off.";
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* A session the deck continued automatically, which then stops again on a
|
|
30
|
+
* login or a limit inside this window, is not continued automatically again
|
|
31
|
+
* until somebody acts on it — a prompt typed into it, or a press of Continue.
|
|
32
|
+
* That is the loop the setting must never become: a login that works for one
|
|
33
|
+
* request and dies on the next would otherwise be continued, stop, and be
|
|
34
|
+
* continued again for as long as the deck runs.
|
|
35
|
+
*/
|
|
36
|
+
export const REPEAT_HOLD_MS = 60 * 60_000;
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* The quota reading as the gate reads it: `{ ok, at, room }`, or null when
|
|
40
|
+
* there is no reading at all.
|
|
41
|
+
*
|
|
42
|
+
* ok the reading is a real one, not a failure to read
|
|
43
|
+
* at when its numbers were collected — the reading's own clock, which is
|
|
44
|
+
* the one that can be compared with a stop's (a reading the deck is
|
|
45
|
+
* only holding over is still as old as it says)
|
|
46
|
+
* room both of the account's windows are under 100%, or null when the
|
|
47
|
+
* reading names neither
|
|
48
|
+
*/
|
|
49
|
+
export function loginEvidence(reading) {
|
|
50
|
+
if (!reading || typeof reading !== "object") return null;
|
|
51
|
+
const at = Number(reading.fetchedAt);
|
|
52
|
+
const when = Number.isFinite(at) && at > 0 ? at : 0;
|
|
53
|
+
if (reading.ok !== true || !when) return { ok: false, at: when, room: null };
|
|
54
|
+
const pcts = [reading.session5hPct, reading.week7dPct].filter(v => typeof v === "number" && Number.isFinite(v));
|
|
55
|
+
return { ok: true, at: when, room: pcts.length ? pcts.every(p => p < 100) : null };
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Whether the turn `stop` ended may be continued now, and when not, why:
|
|
60
|
+
*
|
|
61
|
+
* "no-login" there is no working reading of the active account at all
|
|
62
|
+
* "before-stop" the newest one was taken before the stop, so it cannot say
|
|
63
|
+
* the login works now
|
|
64
|
+
* "no-room" it works, and its 5-hour or 7-day window is full
|
|
65
|
+
*
|
|
66
|
+
* An API error that is neither — an overloaded server, a refused request — is
|
|
67
|
+
* not about the login, so nothing here holds it back. The automatic continue
|
|
68
|
+
* never acts on one (stop-continue.mjs); a press may.
|
|
69
|
+
*/
|
|
70
|
+
export function continueGate(stop, login) {
|
|
71
|
+
if (!stop || typeof stop !== "object") return { ready: false, reason: "no-login" };
|
|
72
|
+
if (stop.kind !== "login" && stop.kind !== "usage-limit") return { ready: true, reason: null };
|
|
73
|
+
if (!login || !login.ok) return { ready: false, reason: "no-login" };
|
|
74
|
+
if (!(login.at > stop.at)) return { ready: false, reason: "before-stop" };
|
|
75
|
+
if (login.room === false) return { ready: false, reason: "no-room" };
|
|
76
|
+
return { ready: true, reason: null };
|
|
77
|
+
}
|