claude-code-session-manager 0.121.0 → 0.122.1
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/assets/{AgentLibrary-FO2ei2wB.js → AgentLibrary-C2qUX9WU.js} +1 -1
- package/dist/assets/{Button-CeIOXUPU.js → Button-ssBzvFha.js} +1 -1
- package/dist/assets/{DataModel-D0p__Hil.js → DataModel-CENoTvc2.js} +1 -1
- package/dist/assets/History-BRdRp1J_.js +2 -0
- package/dist/assets/{Hooks-DkTnRHyT.js → Hooks--hiGPBQy.js} +1 -1
- package/dist/assets/{Library-l7KS6pkE.js → Library-0XYvkhh2.js} +1 -1
- package/dist/assets/{ListDetail-DDw8fuRT.js → ListDetail-CkKXAM59.js} +1 -1
- package/dist/assets/{McpServers-UUNIxk_3.js → McpServers-CCjHSwFm.js} +1 -1
- package/dist/assets/{Memory-CNYUoQ8C.js → Memory-C7RXoVO4.js} +1 -1
- package/dist/assets/{Panel-Bb54soc2.js → Panel-C92O_aAH.js} +1 -1
- package/dist/assets/{Permissions-D-dNdV3f.js → Permissions-DuTJ1FUb.js} +1 -1
- package/dist/assets/Plugins-DSXsP4hM.js +2 -0
- package/dist/assets/{ProvenanceBadge-DEAkDNjA.js → ProvenanceBadge-CWdDkU1m.js} +1 -1
- package/dist/assets/{RemoteMachines-xCEgvTGU.js → RemoteMachines-BFmbIGpW.js} +1 -1
- package/dist/assets/{SaveBar-BSEs7ecA.js → SaveBar-ob2dJJjH.js} +1 -1
- package/dist/assets/Scheduler-Du4HgPKY.js +16 -0
- package/dist/assets/{ScopeSwitcher-DcaFyAV-.js → ScopeSwitcher-Dv_6M_Ci.js} +1 -1
- package/dist/assets/{Settings-vcdWy_Jx.js → Settings-DMoI0cqB.js} +1 -1
- package/dist/assets/{SkillReferenceGraph-CLrJWTPd.js → SkillReferenceGraph-Dx1Qs8Y0.js} +1 -1
- package/dist/assets/{Skills-CYM4rBEC.js → Skills-DYWQTQB1.js} +1 -1
- package/dist/assets/{SystemPrompt-BWNIu-iY.js → SystemPrompt-Df0IfOuI.js} +1 -1
- package/dist/assets/{TagLibrary-CYdwHK6m.js → TagLibrary-BuMsGx-2.js} +1 -1
- package/dist/assets/{TiptapBody-Kt90lHrD.js → TiptapBody-Cihe5er3.js} +1 -1
- package/dist/assets/{Toggle-D1OXWYOz.js → Toggle-CpYVC8bF.js} +1 -1
- package/dist/assets/{ViewTabs-BeQR3CKh.js → ViewTabs-C3FN6P_C.js} +1 -1
- package/dist/assets/{historyTotals-B0qafrRo.js → historyTotals-C4Rs37Cd.js} +1 -1
- package/dist/assets/{index-Bq9uH08R.js → index-S-F5jp_h.js} +396 -396
- package/dist/assets/{settingsSchema-0ZksSAPB.js → settingsSchema-BZcdzHTQ.js} +1 -1
- package/dist/assets/{skillFrontmatter-C0nh8yfa.js → skillFrontmatter-EfNERt4b.js} +1 -1
- package/dist/assets/{useAgentsDirWatch-DPkoBybS.js → useAgentsDirWatch-BfbrFvB3.js} +1 -1
- package/dist/assets/{useRemotePeers-BDJMUihy.js → useRemotePeers-CKVakQPc.js} +1 -1
- package/dist/index.html +1 -1
- package/package.json +1 -1
- package/plugins/session-manager-dev/skills/develop/SKILL.md +8 -7
- package/plugins/session-manager-dev/skills/develop/standards.md +4 -10
- package/scripts/scheduler-mcp-server.cjs +20 -6
- package/src/main/build-info.json +4 -4
- package/src/main/chatRunner.cjs +13 -37
- package/src/main/docEdit.cjs +1 -0
- package/src/main/index.cjs +12 -13
- package/src/main/ipcSchemas.cjs +2 -2
- package/src/main/lib/classifyPromptTicket.cjs +1 -0
- package/src/main/lib/claude/effectiveModelInfo.cjs +9 -21
- package/src/main/lib/claude/personaToolScope.cjs +7 -1
- package/src/main/lib/claude/runClaudeP.cjs +1 -0
- package/src/main/lib/epic/delegationReadiness.cjs +75 -8
- package/src/main/lib/mcpToolCatalog.cjs +5 -8
- package/src/main/lib/persona-macro/shippedPersonaSeeds.cjs +11 -0
- package/src/main/lib/prd/prdCreate.cjs +48 -1
- package/src/main/lib/prd/prdSizing.cjs +1 -8
- package/src/main/lib/runSeeders.cjs +21 -0
- package/src/main/lib/scheduler/loopEfficiency.cjs +109 -0
- package/src/main/lib/scheduler/queueHistory.cjs +44 -1
- package/src/main/lib/scheduler/queueStore.cjs +4 -72
- package/src/main/lib/scheduler/rcaReport.cjs +1 -1
- package/src/main/lib/scheduler/refireCap.cjs +40 -0
- package/src/main/lib/scheduler/reviewNotice.cjs +1 -1
- package/src/main/lib/scheduler/schedulerConfig.cjs +1 -2
- package/src/main/lib/scheduler/schedulerPaths.cjs +0 -2
- package/src/main/lib/scheduler/sessionSlots.cjs +1 -1
- package/src/main/lib/telemetry/auditLog.cjs +65 -14
- package/src/main/loopEfficiencyHandlers.cjs +160 -0
- package/src/main/queueOps.cjs +4 -9
- package/src/main/runVerify.cjs +22 -454
- package/src/main/scheduler/reviewLadder.cjs +12 -2
- package/src/main/scheduler.cjs +116 -181
- package/src/main/seedDevPlugin.cjs +49 -22
- package/src/main/seedStatus.cjs +3 -2
- package/src/main/supervisor.cjs +1 -0
- package/src/main/templates/PRD_AUTHORING.md +16 -31
- package/src/preload/api.d.ts +26 -6
- package/src/preload/index.cjs +1 -0
- package/src/seed/agents/dev-lead.md +2 -9
- package/dist/assets/History-DVqQ5azN.js +0 -2
- package/dist/assets/Plugins-DhcIXRhQ.js +0 -2
- package/dist/assets/Scheduler-BIKJIU-C.js +0 -16
|
@@ -29,21 +29,21 @@
|
|
|
29
29
|
const fs = require('node:fs');
|
|
30
30
|
const path = require('node:path');
|
|
31
31
|
const os = require('node:os');
|
|
32
|
-
const { install, update } = require('./pluginInstall.cjs');
|
|
32
|
+
const { install, update, bundledMarketplaceDir } = require('./pluginInstall.cjs');
|
|
33
33
|
|
|
34
34
|
const PLUGIN_SLUG = 'session-manager-dev';
|
|
35
35
|
const MARKETPLACE = { add: 'bundled', name: 'session-manager' };
|
|
36
36
|
const ENABLED_KEY = `${PLUGIN_SLUG}@${MARKETPLACE.name}`;
|
|
37
37
|
const MAX_ATTEMPTS = 3; // give a transient first-boot failure a few chances
|
|
38
38
|
|
|
39
|
-
function markerPath() {
|
|
40
|
-
return path.join(
|
|
39
|
+
function markerPath(home = os.homedir()) {
|
|
40
|
+
return path.join(home, '.claude', 'session-manager', '.dev-plugin-seeded');
|
|
41
41
|
}
|
|
42
42
|
|
|
43
43
|
/** Read the marker → { done:boolean, attempts:number }. Absent = fresh. */
|
|
44
|
-
function readMarker() {
|
|
44
|
+
function readMarker(home) {
|
|
45
45
|
try {
|
|
46
|
-
const raw = fs.readFileSync(markerPath(), 'utf8').trim();
|
|
46
|
+
const raw = fs.readFileSync(markerPath(home), 'utf8').trim();
|
|
47
47
|
const m = JSON.parse(raw);
|
|
48
48
|
return { done: !!m.done, attempts: Number(m.attempts) || 0 };
|
|
49
49
|
} catch {
|
|
@@ -51,9 +51,9 @@ function readMarker() {
|
|
|
51
51
|
}
|
|
52
52
|
}
|
|
53
53
|
|
|
54
|
-
function alreadyEnabled() {
|
|
54
|
+
function alreadyEnabled(home = os.homedir()) {
|
|
55
55
|
try {
|
|
56
|
-
const raw = fs.readFileSync(path.join(
|
|
56
|
+
const raw = fs.readFileSync(path.join(home, '.claude', 'settings.json'), 'utf8');
|
|
57
57
|
const enabled = JSON.parse(raw)?.enabledPlugins;
|
|
58
58
|
return !!(enabled && enabled[ENABLED_KEY]);
|
|
59
59
|
} catch {
|
|
@@ -61,9 +61,9 @@ function alreadyEnabled() {
|
|
|
61
61
|
}
|
|
62
62
|
}
|
|
63
63
|
|
|
64
|
-
function writeMarker(state) {
|
|
64
|
+
function writeMarker(state, home) {
|
|
65
65
|
try {
|
|
66
|
-
const p = markerPath();
|
|
66
|
+
const p = markerPath(home);
|
|
67
67
|
fs.mkdirSync(path.dirname(p), { recursive: true });
|
|
68
68
|
fs.writeFileSync(p, JSON.stringify({ ...state, ts: new Date().toISOString() }) + '\n');
|
|
69
69
|
} catch (err) {
|
|
@@ -87,6 +87,24 @@ function readJson(p) {
|
|
|
87
87
|
try { return JSON.parse(fs.readFileSync(p, 'utf8')); } catch { return null; }
|
|
88
88
|
}
|
|
89
89
|
|
|
90
|
+
/**
|
|
91
|
+
* Pure check: is the registered 'session-manager' marketplace source missing on disk or
|
|
92
|
+
* different from `expectedDir`? Not registered at all → false (a deliberate uninstall stays).
|
|
93
|
+
* (Own reader: delegationReadiness's readDevMarketplacePath is not exported.)
|
|
94
|
+
*/
|
|
95
|
+
function marketplaceIsStale({ homeDir = os.homedir(), expectedDir = bundledMarketplaceDir() } = {}) {
|
|
96
|
+
const m = readJson(path.join(homeDir, '.claude', 'plugins', 'known_marketplaces.json'))?.[MARKETPLACE.name];
|
|
97
|
+
const p = m?.source?.path ?? m?.installLocation;
|
|
98
|
+
if (typeof p !== 'string' || !p) return false;
|
|
99
|
+
if (path.resolve(p) !== path.resolve(expectedDir)) return true;
|
|
100
|
+
return !fs.existsSync(p);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/** True only when settings.json explicitly sets the plugin to false. */
|
|
104
|
+
function deliberatelyDisabled(home) {
|
|
105
|
+
return readJson(path.join(home, '.claude', 'settings.json'))?.enabledPlugins?.[ENABLED_KEY] === false;
|
|
106
|
+
}
|
|
107
|
+
|
|
90
108
|
function bundledVersion() {
|
|
91
109
|
const m = readJson(path.join(__dirname, '..', '..', 'plugins', PLUGIN_SLUG, '.claude-plugin', 'plugin.json'));
|
|
92
110
|
return typeof m?.version === 'string' ? m.version : null;
|
|
@@ -131,23 +149,32 @@ async function upgradeIfStale({
|
|
|
131
149
|
}
|
|
132
150
|
}
|
|
133
151
|
|
|
134
|
-
async function seedDevPlugin({
|
|
152
|
+
async function seedDevPlugin({
|
|
153
|
+
logger = console,
|
|
154
|
+
writeLog = () => {},
|
|
155
|
+
home = os.homedir(),
|
|
156
|
+
expectedDir = bundledMarketplaceDir(),
|
|
157
|
+
runInstall = install,
|
|
158
|
+
runUpdate = update,
|
|
159
|
+
} = {}) {
|
|
135
160
|
if (process.env.SM_SEED_DEV_PLUGIN_DISABLE === '1') return;
|
|
136
|
-
await upgradeIfStale({ logger, writeLog });
|
|
137
|
-
const marker = readMarker();
|
|
138
|
-
|
|
139
|
-
|
|
161
|
+
await upgradeIfStale({ logger, writeLog, home, runUpdate });
|
|
162
|
+
const marker = readMarker(home);
|
|
163
|
+
// Repair: the registered marketplace dir vanished or points at an older package dir.
|
|
164
|
+
const repair = marker.done && !deliberatelyDisabled(home) && marketplaceIsStale({ homeDir: home, expectedDir });
|
|
165
|
+
if (marker.done && !repair) return; // succeeded before — leave it alone.
|
|
166
|
+
if (!marker.done && marker.attempts >= MAX_ATTEMPTS) return; // gave up — manual install only.
|
|
140
167
|
|
|
141
168
|
try {
|
|
142
|
-
if (alreadyEnabled()) {
|
|
143
|
-
writeMarker({ done: true, attempts: marker.attempts });
|
|
169
|
+
if (!repair && alreadyEnabled(home)) {
|
|
170
|
+
writeMarker({ done: true, attempts: marker.attempts }, home);
|
|
144
171
|
return;
|
|
145
172
|
}
|
|
146
|
-
logger.log?.(
|
|
147
|
-
const r = await
|
|
173
|
+
logger.log?.(`[seedDevPlugin] ${repair ? 're-registering' : 'installing'} bundled session-manager-dev plugin…`);
|
|
174
|
+
const r = await runInstall({ slug: PLUGIN_SLUG, marketplace: MARKETPLACE });
|
|
148
175
|
if (r.ok) {
|
|
149
176
|
logger.log?.('[seedDevPlugin] installed session-manager-dev@session-manager');
|
|
150
|
-
writeMarker({ done: true, attempts: marker.attempts });
|
|
177
|
+
writeMarker({ done: true, attempts: marker.attempts }, home);
|
|
151
178
|
} else {
|
|
152
179
|
// Failure: bump the attempt counter so the next boot can retry (bounded).
|
|
153
180
|
logger.warn?.(`[seedDevPlugin] install failed (exit ${r.exitCode})${r.error ? ` — ${r.error}` : ''}; attempt ${marker.attempts + 1}/${MAX_ATTEMPTS}`);
|
|
@@ -157,7 +184,7 @@ async function seedDevPlugin({ logger = console, writeLog = () => {} } = {}) {
|
|
|
157
184
|
message: 'install failed',
|
|
158
185
|
meta: { exitCode: r.exitCode, error: r.error, attempt: marker.attempts + 1, maxAttempts: MAX_ATTEMPTS },
|
|
159
186
|
});
|
|
160
|
-
writeMarker({ done: false, attempts: marker.attempts + 1 });
|
|
187
|
+
writeMarker({ done: false, attempts: marker.attempts + 1 }, home);
|
|
161
188
|
}
|
|
162
189
|
} catch (err) {
|
|
163
190
|
logger.warn?.('[seedDevPlugin] error:', err?.message ?? err);
|
|
@@ -167,8 +194,8 @@ async function seedDevPlugin({ logger = console, writeLog = () => {} } = {}) {
|
|
|
167
194
|
message: 'seed error',
|
|
168
195
|
meta: { error: err?.message ?? String(err), attempt: marker.attempts + 1, maxAttempts: MAX_ATTEMPTS },
|
|
169
196
|
});
|
|
170
|
-
writeMarker({ done: false, attempts: marker.attempts + 1 });
|
|
197
|
+
writeMarker({ done: false, attempts: marker.attempts + 1 }, home);
|
|
171
198
|
}
|
|
172
199
|
}
|
|
173
200
|
|
|
174
|
-
module.exports = { seedDevPlugin, upgradeIfStale, compareSemver, markerPath, MAX_ATTEMPTS };
|
|
201
|
+
module.exports = { seedDevPlugin, upgradeIfStale, marketplaceIsStale, compareSemver, markerPath, MAX_ATTEMPTS };
|
package/src/main/seedStatus.cjs
CHANGED
|
@@ -22,14 +22,15 @@ const SEEDERS = {
|
|
|
22
22
|
'agent-personas': seedAgentPersonas,
|
|
23
23
|
};
|
|
24
24
|
|
|
25
|
-
/** One seeder's marker → 'done' | 'pending' | 'exhausted'. */
|
|
25
|
+
/** One seeder's marker → 'done' | 'pending' | 'failed' | 'exhausted'. */
|
|
26
26
|
function statusFromMarker(mod) {
|
|
27
27
|
try {
|
|
28
28
|
const raw = fs.readFileSync(mod.markerPath(), 'utf8').trim();
|
|
29
29
|
const m = JSON.parse(raw);
|
|
30
30
|
if (m && m.done) return 'done';
|
|
31
31
|
const attempts = Number(m && m.attempts) || 0;
|
|
32
|
-
|
|
32
|
+
if (attempts >= mod.MAX_ATTEMPTS) return 'exhausted';
|
|
33
|
+
return attempts > 0 ? 'failed' : 'pending';
|
|
33
34
|
} catch {
|
|
34
35
|
return 'pending'; // absent or unparseable — never surfaced as an error.
|
|
35
36
|
}
|
package/src/main/supervisor.cjs
CHANGED
|
@@ -254,6 +254,7 @@ async function runProbe(claudeBin, prompt) {
|
|
|
254
254
|
'--max-budget-usd', '0.50',
|
|
255
255
|
'--dangerously-skip-permissions',
|
|
256
256
|
...headlessPermissionArgs(),
|
|
257
|
+
'--strict-mcp-config',
|
|
257
258
|
'--allowedTools', 'Bash',
|
|
258
259
|
], { env: withProcRole(process.env, 'aux'), stdio: ['ignore', 'pipe', 'pipe'], ...(target.argv0 ? { argv0: target.argv0 } : {}) });
|
|
259
260
|
} catch (e) {
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
<!-- PRD_AUTHORING.md
|
|
1
|
+
<!-- PRD_AUTHORING.md v9 -->
|
|
2
2
|
# PRD Authoring Guide — Scheduler Safety Rules
|
|
3
3
|
|
|
4
4
|
Rule: every PRD you queue follows the rules below.
|
|
5
5
|
Why: two real stuck jobs (fizzpop poll-hang, etch-engine post-AC overrun) cost hours of wall-clock time and real money.
|
|
6
6
|
|
|
7
|
-
Before queueing, run the §
|
|
7
|
+
Before queueing, run the §14 checklist at the bottom. It is last on purpose — check it last.
|
|
8
8
|
|
|
9
9
|
---
|
|
10
10
|
|
|
@@ -69,7 +69,7 @@ if (!found) {
|
|
|
69
69
|
Rule: every PRD needs these fields.
|
|
70
70
|
1. `title` — one line, plain English.
|
|
71
71
|
2. `cwd` — the Epic's project root, as an absolute path or `~/…`. The API always sets it to the Epic's project, whatever you pass. To work in another project, open an Epic in that project. The folder must exist when you queue.
|
|
72
|
-
3. `estimateMinutes` —
|
|
72
|
+
3. `estimateMinutes` — optional; omit it. A run without one gets the 45-minute kill-budget floor. Pass one only if the run legitimately needs longer. Most PRDs take 5–10 minutes (§8).
|
|
73
73
|
|
|
74
74
|
`parallelGroup` is deprecated and ignored. `dependsOn: [<slug>, ...]` is the only ordering primitive.
|
|
75
75
|
|
|
@@ -125,7 +125,7 @@ A short Epic-context digest is prepended automatically for orientation only —
|
|
|
125
125
|
|
|
126
126
|
The executor can't ask you anything: no `ScheduleWakeup`, Cron, Monitor, `AskUserQuestion`, plan mode, or worktree tools; background tasks are off (a `timeout` command is always on PATH — the app installs a shim on macOS). It must never stop to ask a question — it makes the safest reasonable call and reports it, so write the PRD so that call is obvious.
|
|
127
127
|
|
|
128
|
-
Worktree reach: every PRD runs in an isolated git worktree holding tracked files plus `.venv`/`venv` (and `node_modules` for non-pnpm projects) symlinked from the main checkout; pnpm projects (pnpm-lock.yaml) get a worktree-local `node_modules` installed offline from the store. Why: a pnpm install through the symlink purged the main checkout's node_modules (Bilko-Game-Academy 2026-10-18). A step that needs gitignored runtime state (databases, `downloads/`, browser profiles, logs) or a live side effect must address the main checkout by absolute path (e.g. `$HOME/Projects/<repo>/...`), be artifact-only (a deliverable artifact + `artifactPaths`, no tracked edits), and `dependsOn` the PRD that lands its tracked edits. Host crontab installs stay human-only (see §
|
|
128
|
+
Worktree reach: every PRD runs in an isolated git worktree holding tracked files plus `.venv`/`venv` (and `node_modules` for non-pnpm projects) symlinked from the main checkout; pnpm projects (pnpm-lock.yaml) get a worktree-local `node_modules` installed offline from the store. Why: a pnpm install through the symlink purged the main checkout's node_modules (Bilko-Game-Academy 2026-10-18). A step that needs gitignored runtime state (databases, `downloads/`, browser profiles, logs) or a live side effect must address the main checkout by absolute path (e.g. `$HOME/Projects/<repo>/...`), be artifact-only (a deliverable artifact + `artifactPaths`, no tracked edits), and `dependsOn` the PRD that lands its tracked edits. Host crontab installs stay human-only (see §14).
|
|
129
129
|
Why: burrow PRD 1516 halted because its relative `.venv` python against `downloads/fb-tracker.db`, `scripts/install-cron.sh` and `pipeline-cron.sh --now` resolved inside the worktree (no runtime state; install-cron would bake a temporary worktree path into crontab), while sibling 1494 succeeded the same day using an absolute main-checkout path and artifact-only output. `scheduler_create_prd` warns on relative gitignored paths.
|
|
130
130
|
|
|
131
131
|
---
|
|
@@ -155,7 +155,7 @@ Note: a `rateLimited` exit-1 is the scheduler's own benign auto-pause (it resume
|
|
|
155
155
|
## §10 Negative-assertion checks must exit 0 on the clean case
|
|
156
156
|
|
|
157
157
|
Rule: a check that asserts something is ABSENT must exit 0 when it's absent. Write it as `if <detector>; then echo HALT...; exit 1; fi` — never leave the no-match path carrying the non-zero exit.
|
|
158
|
-
Why: `grep` exits 1 when it finds nothing. PRD `62-x-trader-doctrine` cleaned up correctly, but its sanity `grep` for a banned phrase found nothing, exited 1, and
|
|
158
|
+
Why: `grep` exits 1 when it finds nothing. PRD `62-x-trader-doctrine` cleaned up correctly, but its sanity `grep` for a banned phrase found nothing, exited 1, and the gate command itself failed on a clean tree.
|
|
159
159
|
|
|
160
160
|
```bash
|
|
161
161
|
if grep -rniE "banned phrase" path/; then
|
|
@@ -168,10 +168,10 @@ Applies to `grep`, `rg`, `diff` (exits 1 on any difference), and any custom dete
|
|
|
168
168
|
|
|
169
169
|
---
|
|
170
170
|
|
|
171
|
-
## §11 End green
|
|
171
|
+
## §11 End green
|
|
172
172
|
|
|
173
|
-
Rule: order the run so the acceptance/test gate is the LAST command. Run any intentionally-failing step (a TDD red test, an expected-nonzero probe) EARLY, and capture its output (`2>&1 | tail`
|
|
174
|
-
Why: the
|
|
173
|
+
Rule: order the run so the acceptance/test gate is the LAST command. Run any intentionally-failing step (a TDD red test, an expected-nonzero probe) EARLY, and capture its output (`2>&1 | tail`).
|
|
174
|
+
Why: the last command's result is the run's real outcome; a red step left at the end hides whether the work is green.
|
|
175
175
|
|
|
176
176
|
```bash
|
|
177
177
|
# RIGHT — red demo first and captured, green gate last
|
|
@@ -180,38 +180,24 @@ timeout 120 python -m pytest tests/test_repro.py::test_bug 2>&1 | tail -3 || tru
|
|
|
180
180
|
timeout 300 pytest -q # LAST thing the run does
|
|
181
181
|
```
|
|
182
182
|
|
|
183
|
-
The scheduler appends a finish protocol after your PRD's own steps — you never write it: review, then the `# Gate` commands (§6), then commit only the exact paths you created or changed for this PRD, then the verdict line — `SCHEDULER_VERDICT: PASS` once the gate is green and the commit landed, else `SCHEDULER_VERDICT: FAIL <one-line reason>`.
|
|
183
|
+
The scheduler appends a finish protocol after your PRD's own steps — you never write it: review, then the `# Gate` commands (§6), then commit only the exact paths you created or changed for this PRD, then the verdict line — `SCHEDULER_VERDICT: PASS` once the gate is green and the commit landed, else `SCHEDULER_VERDICT: FAIL <one-line reason>`. Never print `PASS` on a red gate.
|
|
184
184
|
|
|
185
185
|
---
|
|
186
186
|
|
|
187
|
-
## §12
|
|
188
|
-
|
|
189
|
-
Rule: a throwaway probe that errors (a bad quote, a wrong kwarg) must be re-run corrected right after, or annotated `# expected/handled: <why>` on the next line — never left stranded. Prefer a temp `.py` file over a fragile inline `python -c` one-liner.
|
|
190
|
-
|
|
191
|
-
Rule: an *expected* `timeout` cap (a long ingest/scan you expect to hit it) is success-with-note, not a bare `Exit code 124` — branch on it explicitly:
|
|
192
|
-
```bash
|
|
193
|
-
timeout 120 python -m project.ingest --all || { rc=$?
|
|
194
|
-
[ $rc -eq 124 ] && echo "hit time cap — partial, rows persist; OK" \
|
|
195
|
-
|| { echo "HALT: ingest failed rc=$rc"; exit 1; }; }
|
|
196
|
-
```
|
|
197
|
-
Why: both a stranded probe traceback and a bare `Exit code 124` read as failure to the verifier even when the committed work is correct and green (PRDs 77, 80 — 2026-06-13).
|
|
198
|
-
|
|
199
|
-
---
|
|
200
|
-
|
|
201
|
-
## §13 Queueing PRDs from external automation
|
|
187
|
+
## §12 Queueing PRDs from external automation
|
|
202
188
|
|
|
203
189
|
Decision: is the session-manager app running on this machine right now?
|
|
204
|
-
- Yes → call the `scheduler_create_prd` MCP tool, with the usual fields (`title`, `cwd`, `
|
|
190
|
+
- Yes → call the `scheduler_create_prd` MCP tool, with the usual fields (`title`, `cwd`, `sourcePromptId`, `goal`, `acceptanceCriteria`, `implementationNotes`) plus `gate` and `files` (§6).
|
|
205
191
|
- No → stop. Ask the human to start the app, then call the tool again. Never write the PRD file by hand: the scheduler quarantines a file the API did not write, and it never runs.
|
|
206
192
|
|
|
207
193
|
It only works while the app is running — closing it removes the admin port/token, and the call fails with `session-manager app is not running (admin API unreachable)`.
|
|
208
194
|
|
|
209
195
|
---
|
|
210
196
|
|
|
211
|
-
## §
|
|
197
|
+
## §13 A parked job may resolve itself now
|
|
212
198
|
|
|
213
|
-
Rule: if a job parks `needs_review` with verdict `
|
|
214
|
-
Why: those
|
|
199
|
+
Rule: if a job parks `needs_review` with verdict `no_verdict_sentinel` or `abandoned_background_task`, do nothing first. The scheduler re-runs the gate on its own and completes the job once the gate is green, the commit is on HEAD, and the tracked tree is clean.
|
|
200
|
+
Why: those verdicts mean the run ended without a clean verdict line, not that the work was wrong.
|
|
215
201
|
|
|
216
202
|
Rule: a park caused by a DIFFERENT actor already finishing the same objective (a sibling PRD, a human) does not self-heal this way. Why: the job has no landed commit of its own. Confirm in the tree that the work is really done, then call `scheduler_archive_prd` with the slug. Archiving marks the job completed and frees the PRDs that depend on it. Never hand-edit `queue.json`: the scheduler and the watchdog both write it. Archiving clears a stale label; it does not fix a real bug.
|
|
217
203
|
|
|
@@ -219,7 +205,7 @@ Known gap: `pass_no_commit` ("already correct, nothing to commit") isn't yet in
|
|
|
219
205
|
|
|
220
206
|
---
|
|
221
207
|
|
|
222
|
-
## §
|
|
208
|
+
## §14 Pre-queue checklist (the litany)
|
|
223
209
|
|
|
224
210
|
Before queueing a new PRD, verify each of these:
|
|
225
211
|
|
|
@@ -228,7 +214,7 @@ Before queueing a new PRD, verify each of these:
|
|
|
228
214
|
- [ ] **§2 No bonus work:** the AC list is the only source of work.
|
|
229
215
|
- [ ] **§3 Verify, don't poll:** every deploy/migration step is followed by a test command that exits 1 on failure; the AC test command ran green once before you declare done.
|
|
230
216
|
- [ ] **§4 Bounded generators:** any search/seed loop has an explicit max and surfaces failure on exhaustion.
|
|
231
|
-
- [ ] **§5 Frontmatter:** `title`, `cwd` (exists on this machine)
|
|
217
|
+
- [ ] **§5 Frontmatter:** `title`, `cwd` (exists on this machine); `estimateMinutes` omitted (optional); `dependsOn` used for ordering, not `parallelGroup`.
|
|
232
218
|
- [ ] **§6 Gate and files:** `gate` follows every rule in §6 (or is `["none"]` alone); `files` is 1–50 repo-relative paths, no overlap with a concurrent PRD.
|
|
233
219
|
- [ ] **§7 Self-contained:** no reference to "the conversation" or outside context; paths/identifiers inline; clean UTF-8, no NUL bytes (`grep -qP '\x00' file && echo BAD`).
|
|
234
220
|
- [ ] **§7 Worktree reach:** no relative path to a gitignored file; live-run steps use absolute main-checkout paths in an artifact-only PRD. Why: the job worktree has no gitignored runtime state, so relative paths fail or bake a temporary path into host config (burrow 1516).
|
|
@@ -236,6 +222,5 @@ Before queueing a new PRD, verify each of these:
|
|
|
236
222
|
- [ ] **§9 Failure surfacing:** errors exit 1 with a diagnostic line; no silent `|| true`.
|
|
237
223
|
- [ ] **§10 Negative assertions:** every "should find nothing" check is inverted to exit 0 on the clean case.
|
|
238
224
|
- [ ] **§11 End green:** the gate runs last; any intentional failure runs early and is captured.
|
|
239
|
-
- [ ] **§12 No stranded probes:** a probe error is corrected or annotated; an expected `timeout` cap branches on exit 124.
|
|
240
225
|
- [ ] **Gate baseline:** every gate command ran green once at HEAD in the main checkout; a red suite is scoped to the PRD's own test files and the PRD notes say why. Why: a gate already red on base parks as `sentinel_fail`.
|
|
241
226
|
- [ ] **No human-only gates:** no gate or criterion needs a human or the passage of time (cron install, live post wait, live payment, dashboard action); those end `SCHEDULER_VERDICT: NEEDS_HUMAN <question>`. Why: no executor can pass them.
|
package/src/preload/api.d.ts
CHANGED
|
@@ -489,10 +489,10 @@ export interface DelegationReadiness {
|
|
|
489
489
|
}
|
|
490
490
|
|
|
491
491
|
/** First-boot seeder outcome, one per seeder id, derived from its marker file.
|
|
492
|
-
* `fix` is the manual remedy shown by the Plugins banner when `status` is 'exhausted'. */
|
|
492
|
+
* `fix` is the manual remedy shown by the Plugins banner when `status` is 'failed' or 'exhausted'. */
|
|
493
493
|
export type SeedStatus = Record<
|
|
494
494
|
'dev-plugin' | 'scheduler-mcp' | 'agent-personas',
|
|
495
|
-
{ status: 'done' | 'pending' | 'exhausted'; fix: string }
|
|
495
|
+
{ status: 'done' | 'pending' | 'failed' | 'exhausted'; fix: string }
|
|
496
496
|
>;
|
|
497
497
|
|
|
498
498
|
export interface OtelConfig {
|
|
@@ -622,14 +622,14 @@ export interface TelemetryFlushResult {
|
|
|
622
622
|
reason: string;
|
|
623
623
|
}
|
|
624
624
|
|
|
625
|
-
export type ScheduleFirePolicy = 'manual' | '
|
|
625
|
+
export type ScheduleFirePolicy = 'manual' | 'when-available';
|
|
626
626
|
|
|
627
627
|
export interface ScheduleConfig {
|
|
628
628
|
/** Legacy on/off — kept for backwards compat with v0.4 queue.json. New
|
|
629
629
|
* installs derive enablement from firePolicy ('manual' === disabled). */
|
|
630
630
|
enabled: boolean;
|
|
631
631
|
/** Minutes to wait after the 5h reset before firing pending jobs.
|
|
632
|
-
*
|
|
632
|
+
* Currently unused by the fire policies ('when-available', 'manual'). */
|
|
633
633
|
offsetMinutes: number;
|
|
634
634
|
defaultCwd: string;
|
|
635
635
|
/** Auto-fire policy. Default 'when-available'. */
|
|
@@ -977,7 +977,6 @@ export interface ScheduleStateSnapshot {
|
|
|
977
977
|
/** Why the last tick fired (or didn't). Null before the first tick. */
|
|
978
978
|
lastTick?: ScheduleLastTick | null;
|
|
979
979
|
jobs: ScheduleJob[];
|
|
980
|
-
scheduledFor: string | null;
|
|
981
980
|
lastRunAt: string | null;
|
|
982
981
|
/** Stamped every time tickQueue reaches the picker at all, whether or not
|
|
983
982
|
* that pass ends in a launch — distinct from lastRunAt (only stamped on an
|
|
@@ -1781,7 +1780,7 @@ export interface SessionManagerAPI {
|
|
|
1781
1780
|
* on macOS symlinked-/Users mismatch and blocks all session spawns. */
|
|
1782
1781
|
homeSelfCheck: () => Promise<{ ok: boolean; error?: string; realCwd?: string }>;
|
|
1783
1782
|
/** First-boot seeder outcomes, read live from each seeder's own marker
|
|
1784
|
-
* file: 'done' (succeeded), 'pending' (not yet run
|
|
1783
|
+
* file: 'done' (succeeded), 'pending' (not yet run), 'failed' (an attempt failed; retries next launch),
|
|
1785
1784
|
* 'exhausted' (gave up after MAX_ATTEMPTS — manual fix needed). */
|
|
1786
1785
|
seedStatus: () => Promise<SeedStatus>;
|
|
1787
1786
|
/** "Can this project actually delegate?" — the 4 preconditions for
|
|
@@ -2057,6 +2056,8 @@ export interface SessionManagerAPI {
|
|
|
2057
2056
|
aggregate: (req?: HistoryAggregateRequest) => Promise<HistoryAggregateResult>;
|
|
2058
2057
|
scanProjects: () => Promise<SessionScanResult>;
|
|
2059
2058
|
dashboard: (req: HistoryDashboardRequest) => Promise<HistoryDashboardResult>;
|
|
2059
|
+
/** This usage week's validated-PRDs-per-usage efficiency, per project (loopEfficiency.cjs). */
|
|
2060
|
+
loopEfficiency: () => Promise<LoopEfficiencyResult>;
|
|
2060
2061
|
/** Fires (throttled to 1/5 s) when main appends days to the rollup. Returns unsubscribe. */
|
|
2061
2062
|
onUpdated: (handler: () => void) => () => void;
|
|
2062
2063
|
};
|
|
@@ -2514,3 +2515,22 @@ declare global {
|
|
|
2514
2515
|
}
|
|
2515
2516
|
|
|
2516
2517
|
export {};
|
|
2518
|
+
|
|
2519
|
+
interface LoopEfficiencyRow {
|
|
2520
|
+
cwd: string;
|
|
2521
|
+
project: string;
|
|
2522
|
+
runs: number;
|
|
2523
|
+
wastedRuns: number;
|
|
2524
|
+
verified: number;
|
|
2525
|
+
refuted: number;
|
|
2526
|
+
executorUsd: number;
|
|
2527
|
+
usdPerVerified: number | null;
|
|
2528
|
+
}
|
|
2529
|
+
interface LoopEfficiencyResult {
|
|
2530
|
+
fromMs: number;
|
|
2531
|
+
toMs: number;
|
|
2532
|
+
rows: LoopEfficiencyRow[];
|
|
2533
|
+
totals: { runs: number; wastedRuns: number; verified: number; refuted: number; executorUsd: number; wastedRunRate: number };
|
|
2534
|
+
weeklyUtilizationPct: number | null;
|
|
2535
|
+
verifiedPerUsagePct: number | null;
|
|
2536
|
+
}
|
package/src/preload/index.cjs
CHANGED
|
@@ -244,6 +244,7 @@ contextBridge.exposeInMainWorld('api', {
|
|
|
244
244
|
aggregate: (req) => ipcRenderer.invoke('history:aggregate', req),
|
|
245
245
|
scanProjects: () => ipcRenderer.invoke('history:scan-projects'),
|
|
246
246
|
dashboard: (req) => ipcRenderer.invoke('history:dashboard', req),
|
|
247
|
+
loopEfficiency: () => ipcRenderer.invoke('history:loop-efficiency'),
|
|
247
248
|
// Fired (throttled) after main appends new days to the rollup.
|
|
248
249
|
onUpdated: (handler) => {
|
|
249
250
|
const listener = () => handler();
|
|
@@ -5,7 +5,7 @@ tools: Read, Grep, Glob, Bash, Edit, Write
|
|
|
5
5
|
model: sonnet
|
|
6
6
|
effort: medium
|
|
7
7
|
title: Engineering — Software Engineer
|
|
8
|
-
seedVersion:
|
|
8
|
+
seedVersion: 4
|
|
9
9
|
---
|
|
10
10
|
|
|
11
11
|
You are dev-lead. You execute one PRD — nothing above it, nothing beyond it. You don't decide
|
|
@@ -32,14 +32,7 @@ points you at), and the project's own files.
|
|
|
32
32
|
2. BUILD. Change only the files listed in `# Files`. If you need another file, change it and say why in your report. Why: an edit outside the list collides with another PRD's own edit to that file.
|
|
33
33
|
3. FINISH. Follow the SCHEDULER FINISH PROTOCOL at the end of your prompt — do not run the gate, commit, or print a verdict yourself outside it. That protocol runs the `# Gate` commands in order, commits with exact paths, and prints the scheduler's own last line, `SCHEDULER_VERDICT: PASS`, only when the gate came back green and the commit landed.
|
|
34
34
|
|
|
35
|
-
|
|
36
|
-
- A bare absence check reads as an error. Write it `if grep …; then echo HALT; exit 1; fi; echo clean` so a clean result exits 0.
|
|
37
|
-
- Run an expected failure (a red test, a probe) early and capture its output. Nothing may error after the gate runs.
|
|
38
|
-
- When a probe errors, re-run it corrected at once, or print `# expected/handled: <why>` on the next line. Never leave an unexplained error in the transcript.
|
|
39
|
-
- A `timeout` that exits 124 as expected still needs a branch: print OK on 124, never leave a bare 124 in the output.
|
|
40
|
-
- Never call ScheduleWakeup or Monitor, never start background work, never invoke /develop or queue work. The finish protocol's review steps are the only exception: run them in the foreground.
|
|
41
|
-
- `gh pr checks` exits 8 while a run is still pending. Wrap it, annotate the exit code, and poll with the identical command each time.
|
|
42
|
-
- Retry a transient failure with the exact same command text, so the verifier can pair the recovery with the failure it recovered from.
|
|
35
|
+
Never call ScheduleWakeup or Monitor, never start background work, never invoke /develop or queue work. The finish protocol's review steps run in the foreground.
|
|
43
36
|
|
|
44
37
|
Out of contract: anything past the Acceptance Criteria list. If the PRD looks wrong, do the AC as written anyway and say why in your report — don't expand or redesign it yourself.
|
|
45
38
|
`standards.md` (the PRD names its path) carries the reasoning behind every line above. Open the one section you need; don't re-read the whole file every run.
|
|
@@ -1,2 +0,0 @@
|
|
|
1
|
-
import{az as xe,aI as ze,bm as Ke,ap as e,aF as f,aX as q,aA as A,aB as O,b6 as Ge,be as ye,au as Ye,b5 as Ve,n as Xe}from"./index-Bq9uH08R.js";import{P as qe}from"./Panel-Bb54soc2.js";import{p as De,i as L,m as $e,t as I,u as $,f as Qe}from"./historyTotals-B0qafrRo.js";import"./monaco-editor-BsIlgG7c.js";function re(){return{promptCount:0,inputTokens:0,outputTokens:0,cacheReadTokens:0,cacheCreationTokens:0,toolCallCount:0,sessionCount:0,errorCount:0,activeMinutes:0,estimatedCostUsd:0}}function Ce(t,s){t.promptCount+=s.promptCount,t.inputTokens+=s.inputTokens,t.outputTokens+=s.outputTokens,t.cacheReadTokens+=s.cacheReadTokens,t.cacheCreationTokens+=s.cacheCreationTokens,t.toolCallCount+=s.toolCallCount,t.sessionCount+=s.sessionCount,t.errorCount+=s.errorCount,t.activeMinutes+=s.activeMinutes,t.estimatedCostUsd+=s.estimatedCostUsd}function Ze(t){return t.promptCount>0||t.sessionCount>0||t.toolCallCount>0||t.inputTokens>0||t.outputTokens>0}function Je(t,s){const n=s===null,c=[],o=re(),r={};let a=0;for(const l of t.days){const u=re();let d=!1;for(const[i,x]of Object.entries(l.byProject))if(!(!n&&!s.has(i))){Ce(u,x),Ce(o,x),Ze(x)&&(d=!0);for(const[m,k]of Object.entries(x.byModel)){const v=r[m]??(r[m]={inputTokens:0,outputTokens:0,cacheReadTokens:0,cacheCreationTokens:0,costUsd:0});v.inputTokens+=k.inputTokens,v.outputTokens+=k.outputTokens,v.cacheReadTokens+=k.cacheReadTokens,v.cacheCreationTokens+=k.cacheCreationTokens,v.costUsd+=k.costUsd,k.estimated&&(v.estimated=!0)}}d&&(a+=1),c.push({date:l.date,totals:u})}return{days:c,totals:o,prevTotals:n?t.prevTotals:re(),prevAvailable:n,byModelTotals:r,activeDays:a}}function Ee(t,s){if(!s)return{pct:null,direction:"flat"};const n=(t-s)/s*100,c=n>0?"up":n<0?"down":"flat";return{pct:n,direction:c}}function et(t,s,n){return s<=0?0:t/s*n}const tt=["promptCount","inputTokens","outputTokens","cacheReadTokens","cacheCreationTokens","toolCallCount","sessionCount","errorCount","activeMinutes","estimatedCostUsd"],st=["inputTokens","outputTokens","cacheReadTokens","cacheCreationTokens","costUsd"];function Te(t){const s={promptCount:0,inputTokens:0,outputTokens:0,cacheReadTokens:0,cacheCreationTokens:0,toolCallCount:0,sessionCount:0,errorCount:0,activeMinutes:0,estimatedCostUsd:0,byModel:{}},n=c=>typeof c=="number"&&Number.isFinite(c)?c:0;for(const c of t){for(const o of tt)s[o]+=n(c[o]);for(const[o,r]of Object.entries(c.byModel??{})){const a=s.byModel[o]??(s.byModel[o]={inputTokens:0,outputTokens:0,cacheReadTokens:0,cacheCreationTokens:0,costUsd:0});for(const l of st)a[l]+=n(r==null?void 0:r[l])}}return s}const Ue={in:"In tokens",out:"Out tokens",total:"Total tokens",prompts:"Prompts",sessions:"Sessions",time:"Time",spend:"Spend"};function S(t,s){const n=t.inputTokens+t.cacheReadTokens+t.cacheCreationTokens;switch(s){case"in":return n;case"out":return t.outputTokens;case"total":return n+t.outputTokens;case"prompts":return t.promptCount;case"sessions":return t.sessionCount;case"time":return t.activeMinutes;case"spend":return t.estimatedCostUsd}}function nt(t,s){const n=S(t.totals,s),c=t.prevAvailable?Ee(n,S(t.prevTotals,s)):null,o=t.days.map(r=>S(r.totals,s));return{value:n,delta:c,series:o}}function Ne(t,s){const n=new Map,c=Math.ceil(t.length/2);t.forEach((a,l)=>{for(const[u,d]of Object.entries(a.byProject)){let i=n.get(u);i||(i={totals:{promptCount:0,inputTokens:0,outputTokens:0,cacheReadTokens:0,cacheCreationTokens:0,toolCallCount:0,sessionCount:0,errorCount:0,activeMinutes:0,estimatedCostUsd:0},toolBreakdown:{},firstHalf:0,secondHalf:0},n.set(u,i)),i.totals.promptCount+=d.promptCount,i.totals.inputTokens+=d.inputTokens,i.totals.outputTokens+=d.outputTokens,i.totals.cacheReadTokens+=d.cacheReadTokens,i.totals.cacheCreationTokens+=d.cacheCreationTokens,i.totals.toolCallCount+=d.toolCallCount,i.totals.sessionCount+=d.sessionCount,i.totals.errorCount+=d.errorCount,i.totals.activeMinutes+=d.activeMinutes,i.totals.estimatedCostUsd+=d.estimatedCostUsd;for(const[m,k]of Object.entries(d.toolBreakdown))i.toolBreakdown[m]=(i.toolBreakdown[m]??0)+k;const x=S(d,s);l<c?i.firstHalf+=x:i.secondHalf+=x}});const o=Array.from(n.entries()).map(([a,l])=>{let u="",d=-1;for(const[i,x]of Object.entries(l.toolBreakdown))x>d&&(d=x,u=i);return{projectDir:a,value:S(l.totals,s),sharePct:0,trend:Ee(l.secondHalf,l.firstHalf),sessions:l.totals.sessionCount,topTool:u}}),r=o.reduce((a,l)=>a+l.value,0);for(const a of o)a.sharePct=r>0?a.value/r*100:0;return o.sort((a,l)=>l.value-a.value)}const ot=["date","cost_usd","input_tokens","output_tokens","cache_write_tokens","cache_read_tokens","total_tokens","cache_hit_pct","num_models","top_model"];function at(t){return/[",\n]/.test(t)?`"${t.replace(/"/g,'""')}"`:t}function we(t){return t.inputTokens+t.outputTokens+t.cacheReadTokens+t.cacheCreationTokens}function ct(t){const s=t.map(n=>{const c=n.inputTokens+n.outputTokens+n.cacheCreationTokens+n.cacheReadTokens,o=n.inputTokens+n.cacheReadTokens+n.cacheCreationTokens,r=o>0?n.cacheReadTokens/o*100:0,a=Object.entries(n.byModel).filter(([,i])=>we(i)>0);let l="",u=-1;for(const[i,x]of a){const m=we(x);m>u&&(u=m,l=i)}return[n.date,n.costUsd.toFixed(4),String(n.inputTokens),String(n.outputTokens),String(n.cacheCreationTokens),String(n.cacheReadTokens),String(c),r.toFixed(1),String(a.length),l?xe(l):""].map(at).join(",")});return[ot.join(","),...s].join(`
|
|
2
|
-
`)}const rt={folderCount:0,sessionCount:0,promptCount:0,estimatedCostUsd:0,totalTokens:0};function it(t,s){t.promptCount+=s.promptCount,t.inputTokens+=s.inputTokens,t.outputTokens+=s.outputTokens,t.cacheReadTokens+=s.cacheReadTokens,t.cacheCreationTokens+=s.cacheCreationTokens,t.toolCallCount+=s.toolCallCount,t.sessionCount+=s.sessionCount,t.errorCount+=s.errorCount,t.activeMinutes+=s.activeMinutes,t.estimatedCostUsd+=s.estimatedCostUsd;for(const[n,c]of Object.entries(s.toolBreakdown??{}))t.toolBreakdown[n]=(t.toolBreakdown[n]??0)+c;for(const[n,c]of Object.entries(s.byModel??{})){const o=t.byModel[n]??(t.byModel[n]={inputTokens:0,outputTokens:0,cacheReadTokens:0,cacheCreationTokens:0,costUsd:0});o.inputTokens+=c.inputTokens,o.outputTokens+=c.outputTokens,o.cacheReadTokens+=c.cacheReadTokens,o.cacheCreationTokens+=c.cacheCreationTokens,o.costUsd+=c.costUsd,c.estimated&&(o.estimated=!0)}}function lt(t,s){const n={};for(const[c,o]of Object.entries(t.byModel??{}))n[c]={...o};return{...t,projectDir:s,toolBreakdown:{...t.toolBreakdown??{}},byModel:n}}function dt(t,s){const n={...rt},c=new Set,o=t.map(r=>{const a={};for(const[l,u]of Object.entries(r.byProject)){const d=s[l];if(!d){c.add(l),n.sessionCount+=u.sessionCount,n.promptCount+=u.promptCount,n.estimatedCostUsd+=u.estimatedCostUsd,n.totalTokens+=u.inputTokens+u.outputTokens;continue}const i=a[d];i?it(i,u):a[d]=lt(u,d)}return{...r,byProject:a}});return n.folderCount=c.size,{days:o,excluded:n}}function ut(t){return t.folderCount>0}function pt(t){const s=[`${t.folderCount} transcript folder${t.folderCount===1?"":"s"} with no resolvable working directory`];return t.sessionCount&&s.push(`${t.sessionCount.toLocaleString()} sessions`),t.promptCount&&s.push(`${t.promptCount.toLocaleString()} prompts`),t.estimatedCostUsd>=.005&&s.push(`$${t.estimatedCostUsd.toFixed(2)}`),`Excluded: ${s.join(" · ")}`}async function fe(){return(await ze()).history??{}}let Me=Promise.resolve();function ue(t){const s=Me.then(async()=>{const n=await fe();await Ke({history:{...n,...t}})});return Me=s.catch(()=>{}),s}const xt=["in","out","total","prompts","sessions","time","spend"],ft=[{value:30,label:"30d"},{value:60,label:"60d"},{value:90,label:"90d"},{value:0,label:"All time"}];function Re({value:t,options:s,onChange:n}){return e.jsx("div",{className:"flex gap-0.5 p-0.5 rounded bg-bg-elev shadow-[inset_0_0_0_1px_rgba(0,0,0,0.06)] text-[11px]",children:s.map(c=>e.jsx("button",{onClick:()=>n(c.value),className:`px-2 py-1 rounded whitespace-nowrap font-mono ${t===c.value?"bg-bg-hi text-fg shadow-sm":"text-fg-faint hover:text-fg"}`,children:c.label},String(c.value)))})}function ht({measure:t,onMeasure:s,range:n,onRange:c,projectsShown:o,projectsTotal:r,fromDate:a,toDate:l,onRefresh:u,onExport:d,exportDisabled:i}){return e.jsxs("div",{className:"sticky top-0 z-10 border-b border-line px-4 py-2.5 flex flex-wrap items-center gap-3 backdrop-blur-sm",style:{backgroundColor:"rgba(246,239,225,0.93)"},children:[e.jsx(Re,{value:t,options:xt.map(x=>({value:x,label:Ue[x]})),onChange:s}),e.jsx(Re,{value:n,options:ft,onChange:c}),e.jsxs("span",{className:"text-[11px] text-fg-faint font-mono",children:[o," / ",r," projects"]}),e.jsxs("span",{className:"text-[11px] text-fg-faint font-mono",children:[a," → ",l]}),e.jsx("div",{className:"flex-1"}),e.jsx("button",{onClick:u,className:"px-2 py-1 text-[11px] border border-line rounded hover:text-fg hover:bg-bg-hi text-fg-faint",title:"Re-fetch the rollup (no scan)",children:"↻ Refresh"}),e.jsx("button",{onClick:d,disabled:i,className:"px-2 py-1 text-[11px] border border-line rounded hover:text-fg hover:bg-bg-hi text-fg-faint disabled:opacity-40 disabled:pointer-events-none",children:"Export CSV"})]})}const w="rounded-card bg-bg-hi border border-line",mt="#ded2bd",jt="#6f7d52";function D({kicker:t,title:s,right:n}){return e.jsxs("div",{className:"flex items-baseline justify-between gap-3 mb-3",children:[e.jsxs("div",{children:[e.jsx("div",{className:"text-[10.5px] uppercase tracking-wider text-accent font-mono",children:t}),e.jsx("h2",{className:"font-serif text-[25px] leading-tight tracking-tight text-fg",children:s})]}),n]})}function Q(t,s){switch(t){case"spend":return $(s);case"in":case"out":case"total":return I(s);case"time":return $e(s);default:return L(s)}}function gt({measure:t,headline:s,activeDays:n,projectsTouched:c,totalSessions:o}){const r=n>0?s.value/n:0,a=o>0?s.value/o:0,{delta:l}=s,u=l&&(t==="spend"?l.direction==="down":l.direction==="up"),d=l&&l.direction!=="flat"&&(t==="spend"?l.direction==="up":l.direction==="down"),i=u?"text-delta-good":d?"text-delta-bad":"text-fg-faint";return e.jsxs("div",{className:`${w} p-4`,children:[e.jsx(D,{kicker:"01 · overview",title:Ue[t]}),e.jsxs("div",{className:"flex items-start justify-between gap-4",children:[e.jsxs("div",{children:[e.jsx("div",{className:"text-[52px] leading-none font-mono tracking-tight text-fg",children:Q(t,s.value)}),l?e.jsxs("div",{className:`text-xs font-mono mt-2 ${i}`,children:[l.direction==="up"?"▲":l.direction==="down"?"▼":"·"," ",De(Math.abs(l.pct??0))," vs prior period"]}):e.jsx("div",{className:"text-xs text-fg-faint mt-2",children:"no prior window to compare"})]}),e.jsx("div",{className:"flex-1 min-w-0 self-stretch",children:e.jsx(bt,{series:s.series})})]}),e.jsxs("div",{className:"grid grid-cols-4 gap-3 mt-4 pt-3 border-t border-line",children:[e.jsx(X,{label:"per active day",value:Q(t,r)}),e.jsx(X,{label:"per session",value:Q(t,a)}),e.jsx(X,{label:"active days",value:L(n)}),e.jsx(X,{label:"projects touched",value:L(c)})]})]})}function X({label:t,value:s}){return e.jsxs("div",{children:[e.jsx("div",{className:"text-[10px] text-fg-faint uppercase tracking-wider",children:t}),e.jsx("div",{className:"text-[17px] font-mono tracking-tight text-fg-dim",children:s})]})}function bt({series:t}){if(t.length<2)return null;const c=Math.max(...t,0),o=Math.min(...t,0),r=c-o||1,a=160/(t.length-1),l=t.map((d,i)=>`${i*a},${48-(d-o)/r*48}`).join(" "),u=`0,48 ${l} 160,48`;return e.jsxs("svg",{viewBox:"0 0 160 48",preserveAspectRatio:"none",className:"w-full h-full min-h-[48px]",children:[e.jsx("polygon",{points:u,fill:"#b85c34",opacity:.15}),e.jsx("polyline",{points:l,fill:"none",stroke:"#b85c34",strokeWidth:1.5,vectorEffect:"non-scaling-stroke"})]})}const kt=50;function vt({days:t}){const[s,n]=f.useState(kt),c=f.useRef(!1);f.useEffect(()=>{let d=!1;return fe().then(i=>{d||c.current||typeof i.budgetCapUsd=="number"&&Number.isFinite(i.budgetCapUsd)&&i.budgetCapUsd>0&&n(i.budgetCapUsd)}),()=>{d=!0}},[]);const o=d=>{c.current=!0;const i=s;n(d),ue({budgetCapUsd:d}).catch(()=>{n(i),q.error("Couldn't save the budget cap — reverted.")})},{mtdSpend:r,projectedSpend:a}=f.useMemo(()=>{const d=new Date,i=d.toLocaleDateString("en-CA").slice(0,7),x=new Date(d.getFullYear(),d.getMonth()+1,0).getDate(),m=d.getDate();let k=0;for(const v of t)if(v.date.startsWith(i))for(const M of Object.values(v.byProject))k+=M.estimatedCostUsd;return{mtdSpend:k,projectedSpend:et(k,m,x)}},[t]),l=a>s,u=s>0?Math.min(100,r/s*100):0;return e.jsxs("div",{className:`${w} p-3`,children:[e.jsxs("div",{className:"flex items-center justify-between mb-2",children:[e.jsx("h3",{className:"text-[10.5px] uppercase tracking-wider text-fg-faint font-mono",children:"Monthly budget"}),e.jsxs("label",{className:"flex items-center gap-1.5 text-[10px] text-fg-faint",children:["cap",e.jsx("span",{children:"$"}),e.jsx("input",{type:"number",min:1,step:1,value:s,onChange:d=>{const i=Number(d.target.value);Number.isFinite(i)&&i>0&&o(i)},className:"w-16 bg-bg border border-line rounded px-1.5 py-0.5 text-xs text-fg font-mono"})]})]}),e.jsxs("div",{className:"flex items-center gap-4 text-xs font-mono mb-1.5",children:[e.jsxs("span",{className:"text-fg",children:[$(r)," ",e.jsx("span",{className:"text-fg-faint",children:"month-to-date"})]}),e.jsxs("span",{className:l?"text-delta-bad":"text-fg-dim",children:[$(a)," ",e.jsx("span",{className:"text-fg-faint",children:"projected"})]})]}),e.jsx("div",{className:"h-1.5 rounded bg-bg-hi overflow-hidden",children:e.jsx("div",{className:`h-full ${l?"bg-delta-bad":"bg-sage"}`,style:{width:`${Math.max(u,u>0?2:0)}%`}})}),e.jsx("div",{className:`text-[10px] mt-1 font-mono ${l?"text-delta-bad":"text-fg-faint"}`,children:l?`projected to exceed $${s.toFixed(0)} cap`:`${u.toFixed(0)}% of $${s.toFixed(0)} cap`})]})}function yt({chips:t,keep:s,onToggle:n,onIsolate:c,onShowAll:o}){const r=s===null;return e.jsxs("div",{className:"flex flex-wrap items-center gap-1.5",children:[e.jsxs("button",{onClick:o,className:`px-2 py-1 rounded text-[11px] font-mono border ${r?"bg-accent text-white border-accent":"border-line text-fg-faint hover:text-fg hover:bg-bg-hi"}`,children:["show all (",t.length,")"]}),t.map(a=>{const l=r||s.has(a.projectDir);return e.jsxs("button",{onClick:u=>u.shiftKey?c(a.projectDir):n(a.projectDir),title:a.projectDir,className:`inline-flex items-center gap-1.5 px-2 py-1 rounded text-[11px] font-mono border ${l?"border-line text-fg-dim bg-bg-hi":"border-line text-fg-faint opacity-40 hover:opacity-70"}`,children:[e.jsx("span",{className:"w-1.5 h-1.5 rounded-full shrink-0",style:{backgroundColor:A(a.projectDir)}}),O(a.projectDir)]},a.projectDir)})]})}function Ct(t,s){const n=new Map;for(const o of t)for(const[r,a]of Object.entries(o.values))n.set(r,(n.get(r)??0)+a);const c=Array.from(n.entries()).sort((o,r)=>r[1]-o[1]).map(([o])=>o);return t.map(o=>{const r=c.reduce((u,d)=>u+(o.values[d]??0),0);let a=0;const l=c.map(u=>{const d=o.values[u]??0;if(r<=0)return{projectDir:u,value:d,y0:0,y1:0};const i=s==="share"?d/r:d,x=a,m=a+i;return a=m,{projectDir:u,value:d,y0:x,y1:m}});return{date:o.date,bands:l,total:r}})}const ie=720,le=220,de=44,Tt=20;function Nt(t,s){return t==="spend"?$(s):I(s)}function wt({days:t,measure:s,selected:n,onSelect:c}){const[o,r]=f.useState("absolute"),a=f.useMemo(()=>Ct(t,o),[t,o]),l=f.useMemo(()=>{if(o==="share")return 1;let h=0;for(const g of a)h=Math.max(h,g.total);return h||1},[a,o]),u=ie-de,d=le-Tt,i=a.length>1?u/(a.length-1):u,x=h=>de+h*i,m=h=>d-h/l*d,k=Math.max(1,Math.ceil(a.length/8)),v=4,M=Array.from({length:v+1},(h,g)=>l/v*g);return e.jsxs("div",{className:`${w} p-4`,children:[e.jsx(D,{kicker:"02 · over time",title:"Trend by project",right:e.jsx("div",{className:"flex border border-line rounded overflow-hidden text-[10px] font-mono",children:["absolute","share"].map(h=>e.jsx("button",{onClick:()=>r(h),className:`px-2 py-0.5 ${o===h?"bg-accent text-white":"text-fg-faint hover:text-fg"}`,children:h},h))})}),e.jsxs("svg",{width:"100%",viewBox:`0 0 ${ie} ${le}`,className:"overflow-visible",children:[M.map((h,g)=>e.jsxs("g",{children:[e.jsx("line",{x1:de,x2:ie,y1:m(h),y2:m(h),stroke:"currentColor",className:"text-line",strokeWidth:1}),e.jsx("text",{x:0,y:m(h)+3,fontSize:9,className:"fill-fg-faint",children:o==="share"?`${Math.round(h*100)}%`:Nt(s,h)})]},g)),a.map((h,g)=>{if(g===0)return null;const E=a[g-1];return h.bands.map((j,J)=>{const z=E.bands[J],W=n!==null&&n!==j.projectDir,ee=W?mt:A(j.projectDir),te=[`${x(g-1)},${m(z.y0)}`,`${x(g)},${m(j.y0)}`,`${x(g)},${m(j.y1)}`,`${x(g-1)},${m(z.y1)}`].join(" ");return e.jsx("polygon",{points:te,fill:ee,opacity:W?.6:.75,className:"cursor-pointer",onClick:()=>c(n===j.projectDir?null:j.projectDir),children:e.jsx("title",{children:O(j.projectDir)})},`${j.projectDir}-${g}`)})}),a.map((h,g)=>g%k!==0&&g!==a.length-1?null:e.jsx("text",{x:x(g),y:le-4,fontSize:9,textAnchor:"middle",className:"fill-fg-faint",children:h.date.slice(5)},h.date))]})]})}function Mt({rows:t,measure:s,selected:n,onSelect:c}){return t.length?e.jsxs("div",{className:`${w} overflow-hidden`,children:[e.jsx("div",{className:"p-4 pb-0",children:e.jsx(D,{kicker:"03 · by project",title:"Ranking"})}),e.jsxs("table",{className:"w-full text-xs",children:[e.jsx("thead",{className:"bg-bg-elev",children:e.jsx("tr",{children:["project","share","value","share %","trend","sessions","top tool"].map(o=>e.jsx("th",{className:"text-left px-3 py-2 text-fg-faint uppercase tracking-wider whitespace-nowrap",children:o},o))})}),e.jsx("tbody",{children:t.map(o=>{const r=A(o.projectDir),a=n===o.projectDir;return e.jsxs("tr",{onClick:()=>c(o.projectDir),className:`border-t border-line hover:bg-bg-elev cursor-pointer ${a?"bg-bg-elev":""}`,children:[e.jsx("td",{className:"px-3 py-2 font-mono text-fg-dim",children:e.jsxs("span",{className:"inline-flex items-center gap-1.5",children:[e.jsx("span",{className:"w-1.5 h-1.5 rounded-full shrink-0",style:{backgroundColor:r}}),O(o.projectDir)]})}),e.jsx("td",{className:"px-3 py-2 w-32",children:e.jsx("div",{className:"h-1.5 rounded bg-bg-hi overflow-hidden",children:e.jsx("div",{className:"h-full",style:{width:`${Math.max(o.sharePct,o.sharePct>0?2:0)}%`,backgroundColor:r}})})}),e.jsx("td",{className:"px-3 py-2 text-fg-faint font-mono",children:Q(s,o.value)}),e.jsxs("td",{className:"px-3 py-2 text-fg-faint font-mono",children:[o.sharePct.toFixed(1),"%"]}),e.jsx("td",{className:"px-3 py-2 font-mono",children:o.trend.pct===null?e.jsx("span",{className:"text-fg-faint",children:"—"}):e.jsxs("span",{className:o.trend.direction==="up"?"text-delta-good":o.trend.direction==="down"?"text-delta-bad":"text-fg-faint",children:[o.trend.direction==="up"?"▲":o.trend.direction==="down"?"▼":"·"," ",De(Math.abs(o.trend.pct))]})}),e.jsx("td",{className:"px-3 py-2 text-fg-faint font-mono",children:L(o.sessions)}),e.jsx("td",{className:"px-3 py-2 text-fg-faint font-mono",children:o.topTool||"—"})]},o.projectDir)})})]})]}):e.jsxs("div",{className:`${w} p-4`,children:[e.jsx(D,{kicker:"03 · by project",title:"Ranking"}),e.jsx("div",{className:"text-xs text-fg-faint",children:"no projects in range"})]})}function Rt({projectDir:t,totals:s,byModel:n,toolBreakdown:c,onClear:o}){var i;const r=S(s,"in"),a=S(s,"out"),l=Object.entries(n).sort((x,m)=>m[1].costUsd-x[1].costUsd),u=Object.entries(c).sort((x,m)=>m[1]-x[1]),d=((i=u[0])==null?void 0:i[1])??0;return e.jsxs("div",{className:`${w} p-4`,children:[e.jsx(D,{kicker:"04 · drill in",title:O(t),right:e.jsx("button",{onClick:o,className:"text-[11px] text-fg-faint hover:text-fg font-mono",children:"clear ✕"})}),e.jsx("div",{className:"flex items-center gap-1.5 -mt-2 mb-3",children:e.jsx("span",{className:"w-1.5 h-1.5 rounded-full shrink-0",style:{backgroundColor:A(t)}})}),e.jsxs("div",{className:"grid grid-cols-6 gap-3 mb-4",children:[e.jsx(H,{label:"prompts",value:L(s.promptCount)}),e.jsx(H,{label:"sessions",value:L(s.sessionCount)}),e.jsx(H,{label:"in tokens",value:I(r)}),e.jsx(H,{label:"out tokens",value:I(a)}),e.jsx(H,{label:"spend",value:$(s.estimatedCostUsd)}),e.jsx(H,{label:"time",value:$e(s.activeMinutes)})]}),e.jsxs("div",{className:"grid grid-cols-2 gap-4",children:[e.jsxs("div",{children:[e.jsx("div",{className:"text-[10px] text-fg-faint uppercase tracking-wider mb-1.5",children:"model mix"}),l.length===0?e.jsx("div",{className:"text-xs text-fg-faint",children:"no model data"}):e.jsx("div",{className:"space-y-1",children:l.map(([x,m])=>e.jsxs("div",{className:"flex items-center justify-between text-[11px] font-mono text-fg-dim",children:[e.jsx("span",{children:xe(x)}),e.jsx("span",{className:"text-fg-faint",children:$(m.costUsd)})]},x))})]}),e.jsxs("div",{children:[e.jsx("div",{className:"text-[10px] text-fg-faint uppercase tracking-wider mb-1.5",children:"tool mix"}),u.length===0?e.jsx("div",{className:"text-xs text-fg-faint",children:"no tool data"}):e.jsx("div",{className:"space-y-1",children:u.slice(0,6).map(([x,m])=>e.jsxs("div",{className:"flex items-center gap-2 text-[11px]",children:[e.jsx("span",{className:"w-20 shrink-0 truncate text-fg-dim font-mono",children:x}),e.jsx("div",{className:"flex-1 h-1.5 rounded bg-bg-hi overflow-hidden",children:e.jsx("div",{className:"h-full bg-hive-teal",style:{width:`${d>0?m/d*100:0}%`}})}),e.jsx("span",{className:"text-fg-faint font-mono w-8 text-right",children:m})]},x))})]})]})]})}function H({label:t,value:s}){return e.jsxs("div",{children:[e.jsx("div",{className:"text-[10px] text-fg-faint uppercase tracking-wider",children:t}),e.jsx("div",{className:"text-[17px] font-mono tracking-tight text-fg",children:s})]})}function St({byModelTotals:t}){const s=Object.entries(t).sort((c,o)=>o[1].costUsd-c[1].costUsd),n=s.reduce((c,[,o])=>c+o.costUsd,0);return e.jsxs("div",{className:`${w} p-4`,children:[e.jsx(D,{kicker:"05 · composition",title:"Model mix",right:e.jsxs("span",{className:"text-xs font-mono text-fg-faint",children:[$(n)," total"]})}),s.length===0?e.jsx("div",{className:"text-xs text-fg-faint",children:"no model data in range"}):e.jsx("div",{className:"space-y-2",children:s.map(([c,o])=>{const r=n>0?o.costUsd/n*100:0,a=A(c);return e.jsxs("div",{children:[e.jsxs("div",{className:"flex items-center justify-between text-xs mb-0.5",children:[e.jsxs("span",{className:"flex items-center gap-1.5 font-mono text-fg-dim truncate",children:[e.jsx("span",{className:"w-2 h-2 rounded-sm shrink-0",style:{backgroundColor:a}}),xe(c),o.estimated&&e.jsx("span",{className:"text-fg-faint",children:"(est.)"})]}),e.jsxs("span",{className:"text-fg-faint whitespace-nowrap ml-2",children:[$(o.costUsd)," · ",r.toFixed(0),"%"]})]}),e.jsx("div",{className:"h-1.5 rounded bg-bg-hi overflow-hidden",children:e.jsx("div",{className:"h-full",style:{width:`${Math.max(r,r>0?2:0)}%`,backgroundColor:a}})})]},c)})})]})}function Dt({rows:t}){const s=t.slice(0,3),n=s.reduce((r,a)=>r+a.sharePct,0),c=t.slice(3),o=c.reduce((r,a)=>r+a.sharePct,0);return e.jsxs("div",{className:`${w} p-4`,children:[e.jsx(D,{kicker:"05 · composition",title:"Concentration",right:e.jsxs("span",{className:"text-xs font-mono text-fg-faint",children:["top 3 = ",n.toFixed(0),"%"]})}),t.length===0?e.jsx("div",{className:"text-xs text-fg-faint",children:"no projects in range"}):e.jsxs(e.Fragment,{children:[e.jsx("div",{className:"flex h-3 rounded overflow-hidden mb-2",children:t.map(r=>e.jsx("div",{style:{width:`${Math.max(r.sharePct,.5)}%`,backgroundColor:A(r.projectDir)},title:`${O(r.projectDir)} · ${r.sharePct.toFixed(1)}%`},r.projectDir))}),e.jsx("div",{className:"space-y-1 mb-2",children:s.map(r=>e.jsxs("div",{className:"flex items-center justify-between text-[11px] font-mono",children:[e.jsxs("span",{className:"flex items-center gap-1.5 text-fg-dim truncate",children:[e.jsx("span",{className:"w-1.5 h-1.5 rounded-full shrink-0",style:{backgroundColor:A(r.projectDir)}}),O(r.projectDir)]}),e.jsxs("span",{className:"text-fg-faint",children:[r.sharePct.toFixed(1),"%"]})]},r.projectDir))}),c.length>0&&e.jsxs("p",{className:"text-[10px] text-fg-faint",children:[c.length," more project",c.length===1?"":"s"," account for ",o.toFixed(1),"% (long tail)."]})]})]})}const Z=30,pe=9,P=(Z+pe)*2;function $t({totals:t}){const s=t.inputTokens+t.cacheReadTokens+t.cacheCreationTokens,n=s>0?t.cacheReadTokens/s*100:0,c=2*Math.PI*Z,o=P/2;return e.jsxs("div",{className:`${w} p-4`,children:[e.jsx(D,{kicker:"05 · composition",title:"Cache"}),e.jsxs("div",{className:"flex items-center gap-4",children:[e.jsxs("div",{className:"relative shrink-0",style:{width:P,height:P},children:[e.jsxs("svg",{width:P,height:P,viewBox:`0 0 ${P} ${P}`,className:"-rotate-90",children:[e.jsx("circle",{cx:o,cy:o,r:Z,fill:"none",stroke:"currentColor",className:"text-bg-hi",strokeWidth:pe}),e.jsx("circle",{cx:o,cy:o,r:Z,fill:"none",stroke:jt,strokeWidth:pe,strokeDasharray:c,strokeDashoffset:c*(1-n/100),strokeLinecap:"round"})]}),e.jsxs("div",{className:"absolute inset-0 flex items-center justify-center text-[13px] font-mono text-fg",children:[n.toFixed(0),"%"]})]}),e.jsxs("div",{className:"text-[11px] space-y-1 font-mono",children:[e.jsxs("div",{className:"flex items-center justify-between gap-3",children:[e.jsx("span",{className:"text-fg-faint",children:"reused (cache read)"}),e.jsx("span",{className:"text-fg-dim",children:I(t.cacheReadTokens)})]}),e.jsxs("div",{className:"flex items-center justify-between gap-3",children:[e.jsx("span",{className:"text-fg-faint",children:"written to cache"}),e.jsx("span",{className:"text-fg-dim",children:I(t.cacheCreationTokens)})]})]})]})]})}const Et=["Sun","Mon","Tue","Wed","Thu","Fri","Sat"];function Ut({days:t,measure:s}){const{buckets:n,weekendShare:c,peakIdx:o}=f.useMemo(()=>{const a=new Array(7).fill(0);for(const i of t){const[x,m,k]=i.date.split("-").map(Number),v=new Date(x,m-1,k).getDay();a[v]+=S(i.totals,s)}const l=a.reduce((i,x)=>i+x,0),u=a[0]+a[6];let d=0;for(let i=1;i<7;i++)a[i]>a[d]&&(d=i);return{buckets:a,weekendShare:l>0?u/l*100:0,peakIdx:d}},[t,s]),r=Math.max(...n,1);return e.jsxs("div",{className:`${w} p-4`,children:[e.jsx(D,{kicker:"05 · composition",title:"Rhythm",right:e.jsxs("span",{className:"text-xs font-mono text-fg-faint",children:["weekend ",c.toFixed(0),"%"]})}),e.jsx("div",{className:"flex items-end gap-2 h-24",children:n.map((a,l)=>e.jsxs("div",{className:"flex-1 flex flex-col items-center justify-end gap-1 h-full",children:[e.jsx("div",{className:`w-full rounded-t ${l===o?"bg-accent":"bg-rhythm"}`,style:{height:`${r>0?a/r*100:0}%`,minHeight:a>0?2:0}}),e.jsx("span",{className:"text-[9px] text-fg-faint font-mono",children:Et[l]})]},l))})]})}const Pt=["in","out","total","prompts","sessions","time","spend"],At=[30,60,90,0],Ft="spend",_t=30,Bt=3e4,Ht=[];function Ot(){var ve;const[t,s]=f.useState(Ft),[n,c]=f.useState(_t),o=f.useRef(!1),r=f.useRef(!1),a=p=>{o.current=!0;const b=t;s(p),ue({measure:p}).catch(()=>{s(b),q.error("Couldn't save the measure preference — reverted.")})},l=p=>{r.current=!0;const b=n;c(p),ue({range:p}).catch(()=>{c(b),q.error("Couldn't save the range preference — reverted.")})};f.useEffect(()=>{let p=!1;return fe().then(b=>{p||(!o.current&&b.measure&&Pt.includes(b.measure)&&s(b.measure),!r.current&&b.range!==void 0&&At.includes(b.range)&&c(b.range))}),()=>{p=!0}},[]);const u=Ge(p=>p.navFace),d=ye(p=>p.tabs),i=ye(p=>p.activeTabId),x=((ve=d.find(p=>p.id===i))==null?void 0:ve.cwd)??null,m=f.useRef(u),k=x?Ye(x):null,[v,M]=f.useState(null),h=f.useMemo(()=>u==="project"&&k?new Set([k]):v,[u,k,v]),[g,E]=f.useState(null),[j,J]=f.useState(null),[z,W]=f.useState(!0),[ee,te]=f.useState(0),se=f.useRef(n);se.current=n;const K=f.useRef(!0),ne=f.useRef(0),he=f.useRef(0),F=f.useRef(null),G=f.useCallback(p=>{const b=se.current;ne.current+=1,he.current=Date.now(),Qe(b).then(y=>{if(ne.current-=1,!(!K.current||b!==se.current)){if(W(!1),y.ok){J(y.data);return}p||q.error(`History analytics failed to load: ${y.error}`)}})},[]);f.useEffect(()=>{K.current=!0,W(!0),G(!1)},[n,ee,G]),f.useEffect(()=>{const p=()=>{if(!K.current)return;const T=he.current+Bt-Date.now();if(ne.current===0&&T<=0){G(!0);return}F.current||(F.current=setTimeout(()=>{F.current=null,p()},Math.max(T,1e3)))},b=()=>{document.visibilityState==="visible"&&p()},y=window.api.history.onUpdated(p);return window.addEventListener("focus",p),document.addEventListener("visibilitychange",b),()=>{K.current=!1,y(),window.removeEventListener("focus",p),document.removeEventListener("visibilitychange",b),F.current&&(clearTimeout(F.current),F.current=null)}},[G]);const me=f.useRef(!1);f.useEffect(()=>{if(!me.current){me.current=!0;return}M(null),E(null)},[n]),f.useEffect(()=>{m.current!==u&&(m.current=u,u==="home"&&M(null))},[u]);const{projects:je,resolving:ge}=Ve(),be=f.useMemo(()=>{const p={};for(const b of je)for(const y of b.encodedIds)p[y]=b.cwd;return p},[je]),_=f.useMemo(()=>j&&!ge?dt(j.days,be):null,[j,be,ge]),N=(_==null?void 0:_.days)??(j==null?void 0:j.days)??Ht,oe=(_==null?void 0:_.excluded)??null,R=f.useMemo(()=>j?Je({days:N,totals:j.totals,prevTotals:j.prevTotals},h):null,[j,N,h]),B=f.useMemo(()=>h===null?N:N.map(p=>({date:p.date,byProject:Object.fromEntries(Object.entries(p.byProject).filter(([b])=>h.has(b)))})),[N,h]),Y=f.useMemo(()=>Ne(N,t),[N,t]),ae=f.useMemo(()=>Ne(B,t),[B,t]),Pe=f.useMemo(()=>B.map(p=>({date:p.date,values:Object.fromEntries(Object.entries(p.byProject).map(([b,y])=>[b,S(y,t)]))})),[B,t]),ke=f.useMemo(()=>R?nt(R,t):null,[R,t]),V=f.useMemo(()=>{if(!j||!g)return null;const p=N.flatMap(C=>C.byProject[g]?[C.byProject[g]]:[]),{byModel:b,...y}=Te(p),T={};for(const C of p)for(const[U,ce]of Object.entries(C.toolBreakdown))T[U]=(T[U]??0)+ce;return{totals:y,byModel:b,toolBreakdown:T}},[j,N,g]);f.useEffect(()=>{g&&h!==null&&!h.has(g)&&E(null)},[g,h]);function Ae(p){M(b=>{const y=new Set(Y.map(U=>U.projectDir)),T=b??new Set(y),C=new Set(T);return C.has(p)?C.delete(p):C.add(p),C.size>=y.size?null:C})}function Fe(p){M(new Set([p]))}function _e(){M(null)}function Be(){if(!j)return;const p=B.map(U=>{const{byModel:ce,estimatedCostUsd:He,inputTokens:Oe,outputTokens:Le,cacheCreationTokens:Ie,cacheReadTokens:We}=Te(Object.values(U.byProject));return{date:U.date,costUsd:He,inputTokens:Oe,outputTokens:Le,cacheCreationTokens:Ie,cacheReadTokens:We,byModel:ce}}),b=ct(p),y=new Blob([b],{type:"text/csv"}),T=URL.createObjectURL(y),C=document.createElement("a");C.href=T,C.download=`usage-history-${j.from}-${j.to}.csv`,C.click(),URL.revokeObjectURL(T)}return z&&!j?e.jsx(Se,{}):j&&j.days.length===0?e.jsx(Xe,{title:"no sessions in range",hint:"Widen the range or check back after your next session."}):!j||!R||!ke?e.jsx(Se,{}):e.jsxs("div",{"data-testid":"history-dashboard",className:"flex flex-col h-full",children:[e.jsxs("h1",{className:"font-serif text-[42px] leading-tight tracking-tight text-fg px-4 pt-4",children:["Where your ",e.jsx("em",{className:"italic text-accent",children:"Claude time"})," goes"]}),e.jsx(ht,{measure:t,onMeasure:a,range:n,onRange:l,projectsShown:h===null?Y.length:h.size,projectsTotal:Y.length,fromDate:j.from,toDate:j.to,onRefresh:()=>te(p=>p+1),onExport:Be,exportDisabled:B.length===0}),e.jsxs("div",{className:"flex-1 min-h-0 overflow-auto p-4 space-y-4",children:[e.jsx(gt,{measure:t,headline:ke,activeDays:R.activeDays,projectsTouched:ae.length,totalSessions:R.totals.sessionCount}),e.jsx(vt,{days:N}),oe&&ut(oe)&&e.jsx("div",{"data-testid":"history-excluded-note",className:"rounded-[10px] border border-line bg-bg-hi px-3 py-2 text-[11.5px] font-mono text-fg-faint",title:"A project is a working directory. ~/.claude/projects holds one folder per path the CLI was ever launched from — throwaway /tmp dirs and test fixtures included — and a folder whose cwd can't be recovered from its transcripts is not a project. Their totals are stated here rather than silently folded into the charts.",children:pt(oe)}),u==="home"&&e.jsx(yt,{chips:Y.map(p=>({projectDir:p.projectDir,value:p.value})),keep:h,onToggle:Ae,onIsolate:Fe,onShowAll:_e}),e.jsx(wt,{days:Pe,measure:t,selected:g,onSelect:E}),e.jsx(Mt,{rows:ae,measure:t,selected:g,onSelect:E}),g&&V&&e.jsx(Rt,{projectDir:g,totals:V.totals,byModel:V.byModel,toolBreakdown:V.toolBreakdown,onClear:()=>E(null)}),e.jsxs("div",{className:"grid grid-cols-2 lg:grid-cols-4 gap-4",children:[e.jsx(St,{byModelTotals:R.byModelTotals}),e.jsx(Dt,{rows:ae}),e.jsx($t,{totals:R.totals}),e.jsx(Ut,{days:R.days,measure:t})]})]})]})}function Se(){return e.jsxs("div",{className:"p-4 space-y-4 animate-pulse",children:[e.jsx("div",{className:"h-24 bg-bg-elev rounded"}),e.jsx("div",{className:"h-16 bg-bg-elev rounded"}),e.jsx("div",{className:"h-8 bg-bg-elev rounded"}),e.jsx("div",{className:"h-56 bg-bg-elev rounded"}),e.jsx("div",{className:"h-40 bg-bg-elev rounded"})]})}function Lt(){return e.jsx(qe,{children:e.jsx(Ot,{})})}const Gt=f.memo(Lt);export{Gt as History};
|
|
@@ -1,2 +0,0 @@
|
|
|
1
|
-
const __vite__mapDeps=(i,m=__vite__mapDeps,d=(m.f||(m.f=["./SkillReferenceGraph-CLrJWTPd.js","./index-Bq9uH08R.js","./monaco-editor-BsIlgG7c.js","./monaco-editor-Cu5D__1H.css","./index-Dd7UQabV.css"])))=>i.map(i=>d[i]);
|
|
2
|
-
import{ap as t,aF as c,n as S,b4 as pe,aX as q,a$ as ae,a as xe}from"./index-Bq9uH08R.js";import{P as ee}from"./Panel-Bb54soc2.js";import{P as re}from"./ProvenanceBadge-DEAkDNjA.js";import{e as ge}from"./Library-l7KS6pkE.js";import{p as le,M as je}from"./skillFrontmatter-C0nh8yfa.js";import{l as be}from"./listSkills-C1aWKf0T.js";import{_ as ye}from"./monaco-editor-BsIlgG7c.js";import{L as ve}from"./ListDetail-DDw8fuRT.js";import"./Button-CeIOXUPU.js";function ke({columns:e,rows:n,getKey:i,onRowClick:s,activeKey:a,empty:d}){return n.length===0?t.jsx("div",{className:"p-6 text-fg-faint text-xs text-center",children:d??"no entries"}):t.jsxs("table",{className:"w-full text-xs",children:[t.jsx("thead",{className:"sticky top-0 bg-bg-elev text-fg-faint uppercase tracking-wider",children:t.jsx("tr",{className:"border-b border-line",children:e.map(l=>t.jsx("th",{className:`text-left font-normal px-3 py-1.5 ${l.className??""}`,style:l.width?{width:l.width}:void 0,children:l.header},l.key))})}),t.jsx("tbody",{children:n.map(l=>{const h=i(l),o=a===h;return t.jsx("tr",{"data-row-key":h,onClick:()=>s==null?void 0:s(l),className:`border-b border-line/50 transition-colors ${s?"cursor-pointer":""} ${o?"bg-bg-hi text-fg":"text-fg-dim hover:bg-bg-hi/50"}`,children:e.map(u=>t.jsx("td",{className:`px-3 py-1 ${u.className??""}`,children:u.render(l)},u.key))},h)})})]})}async function oe(e){const n=`${e}/skills`;return await window.api.config.exists(n)?n:null}async function ce(e){const{entries:n}=await be(e,{nested:!1});return n.map(i=>{const s=le(i.text);return{id:i.name,name:s.name,description:s.description,dir:i.dir,path:i.path,body:s.body}})}function Ne(e){const n=[],i=new Set;for(const s of e)for(const a of e){if(s.id===a.id)continue;const d=a.id.replace(/[.*+?^${}()|[\]\\]/g,"\\$&"),l=[`/${d}\\b`,`\`${d}\``];if(a.id.length>=4&&l.push(`\\b${d}\\b`),!l.some(u=>new RegExp(u,"i").test(s.body)))continue;const o=`${s.id}->${a.id}`;i.has(o)||(i.add(o),n.push({from:s.id,to:a.id}))}return n}const we=c.lazy(()=>ye(()=>import("./SkillReferenceGraph-CLrJWTPd.js"),__vite__mapDeps([0,1,2,3,4]),import.meta.url).then(e=>({default:e.SkillReferenceGraph})));function Se({pluginName:e,skills:n,onBack:i,initialSelectedId:s}){var N;const[a,d]=c.useState(s??((N=n[0])==null?void 0:N.id)??null),l=a?n.find(m=>m.id===a)??null:null,h=c.useMemo(()=>Ne(n),[n]),[o,u]=c.useState("list");return t.jsxs("div",{className:"flex flex-col h-full min-h-0 overflow-hidden",children:[t.jsxs("div",{className:"shrink-0 h-11 flex items-baseline gap-3.5 px-3 border-b border-line bg-bg",children:[t.jsxs("button",{type:"button",onClick:i,className:"font-mono text-accent hover:underline shrink-0 text-xs",children:["← ",e]}),t.jsx("span",{className:"font-serif text-[22px] text-fg shrink-0 truncate max-w-[16rem]",children:(l==null?void 0:l.name)??(l==null?void 0:l.id)??"skill"}),t.jsx("span",{className:"flex-1"}),t.jsx("div",{className:"flex items-center gap-1.5 shrink-0",children:t.jsxs("div",{className:"flex rounded border border-line overflow-hidden",children:[t.jsx("button",{onClick:()=>u("list"),className:`px-2 py-0.5 text-xs ${o==="list"?"bg-bg-hi text-fg":"text-fg-dim hover:text-fg hover:bg-bg-hi"}`,children:"list"}),t.jsx("button",{onClick:()=>u("graph"),className:`px-2 py-0.5 text-xs ${o==="graph"?"bg-bg-hi text-fg":"text-fg-dim hover:text-fg hover:bg-bg-hi"}`,children:"graph"})]})})]}),t.jsxs("div",{className:"shrink-0 h-[26px] flex items-center gap-[18px] px-3 bg-bg-elev border-b border-line text-[9px] font-mono uppercase tracking-wide overflow-hidden",children:[l!=null&&l.description?t.jsx("span",{className:"text-fg-dim truncate normal-case",children:l.description}):null,t.jsxs("span",{className:"ml-auto text-fg-faint truncate normal-case",children:[n.length," ",n.length===1?"skill":"skills"," · ",h.length," ",h.length===1?"reference":"references"]})]}),t.jsx("div",{className:"flex-1 min-h-0 overflow-hidden",children:n.length===0?t.jsx(S,{title:"no skills in this plugin"}):o==="graph"?t.jsx(c.Suspense,{fallback:t.jsx("div",{className:"p-6 text-xs text-fg-faint",children:"Loading graph…"}),children:t.jsx(we,{skills:n,edges:h,selectedId:a,onSelect:d})}):t.jsx(ve,{sidebar:t.jsx("div",{className:"py-1",children:n.map(m=>t.jsxs("button",{onClick:()=>d(m.id),className:`w-full px-3 py-1 text-xs text-left ${a===m.id?"bg-bg-hi text-fg":"text-fg-dim hover:text-fg hover:bg-bg-hi"}`,children:[t.jsx("div",{className:"truncate",children:m.name??m.id}),m.description?t.jsx("div",{className:"truncate text-fg-faint",children:m.description}):null]},m.id))}),detail:l?t.jsx("div",{className:"h-full min-h-0",children:t.jsx(je,{path:l.path,value:l.body,onChange:()=>{},readOnly:!0})}):t.jsx(S,{title:"select a skill"})})})]})}const de=e=>{var n,i;return{type:"plugin",name:e.name,repository:(n=e.manifest)==null?void 0:n.repository,homepage:(i=e.manifest)==null?void 0:i.homepage}};function $e(){const e=pe(),[n,i]=c.useState([]),[s,a]=c.useState(!0),[d,l]=c.useState(null),[h,o]=c.useState(null),[u,N]=c.useState("installed"),[m,I]=c.useState(null),[D,F]=c.useState(new Set);c.useEffect(()=>{var g,k;let r=!1;return(k=(g=window.api.app).seedStatus)==null||k.call(g).then($=>{r||I($)}).catch(()=>{}),()=>{r=!0}},[]);const E=c.useRef(0),C=c.useRef(!1),b=c.useCallback(async()=>{const r=++E.current;if(!e)return;C.current||a(!0);const g=`${e}/.claude/plugins/installed_plugins.json`;let k=[],$=null;try{const x=await window.api.config.readJson(g);if(x.exists&&x.parseError)$=`could not parse installed_plugins.json${x.error?`: ${x.error}`:""}`;else if(x.exists&&x.data&&typeof x.data=="object"){const T=x.data,J=[];for(const[W,R]of Object.entries(T.plugins??{})){if(!Array.isArray(R))continue;const[_,O]=W.split("@");for(const j of R)j!=null&&j.installPath&&J.push({name:_,marketplace:O,entry:j})}k=await De(J,Le,async({name:W,marketplace:R,entry:_})=>{var H;const O={name:W,path:_.installPath,isDirectory:!0,isFile:!1,mtimeMs:0,size:0};let j;try{j=await _e(O)}catch(B){j=Re(O,B instanceof Error?B.message:String(B))}return(H=j.manifest)!=null&&H.version||(j.manifest={...j.manifest??{},version:_.version}),j.marketplace=R,j.scope=_.scope,j})}}catch(x){$=`could not read installed_plugins.json: ${x instanceof Error?x.message:String(x)}`}E.current===r&&(l($),$?q.error(`Plugins: ${$}`):(k.sort((x,T)=>x.name.localeCompare(T.name)),C.current=k.length>0,i(k)),a(!1))},[e]);c.useEffect(()=>{b()},[b]),c.useEffect(()=>()=>{E.current+=1},[]),ae(e?[`${e}/.claude/plugins/installed_plugins.json`]:[],b);const f=[{key:"name",header:"name",render:r=>{var g;return t.jsxs("div",{style:{maxWidth:"16rem"},children:[t.jsx("div",{className:"truncate",children:r.name}),(g=r.manifest)!=null&&g.description?t.jsx("div",{className:"truncate text-fg-faint",title:r.manifest.description,children:r.manifest.description}):null]})},width:"16rem"},{key:"origin",header:"origin",render:r=>t.jsx(re,{interactive:!1,input:de(r)}),width:"7rem"},{key:"scope",header:"scope",render:r=>r.scope?t.jsx("span",{"data-testid":"plugin-scope",children:r.scope}):t.jsx("span",{className:"text-fg-faint",children:"—"}),width:"5rem"},{key:"version",header:"version",render:r=>{var g;return((g=r.manifest)==null?void 0:g.version)??t.jsx("span",{className:"text-fg-faint",children:"—"})},width:"6rem"},{key:"manifest",header:"manifest",render:r=>r.hasManifest?"plugin.json":t.jsx("span",{className:"text-fg-faint",children:"—"}),width:"8rem"},{key:"contents",header:"contents",render:r=>t.jsx("span",{className:"font-mono text-fg-faint text-xs",children:[r.agents>0?`${r.agents} agents`:null,r.commands>0?`${r.commands} commands`:null,r.skills>0?`${r.skills} skills`:null,r.hooks>0?`${r.hooks} hooks`:null,r.monitors>0?`${r.monitors} monitors`:null,r.binCount>0?`${r.binCount} bin`:null,r.hasLsp?"lsp":null,r.hasMcp?"mcp":null,r.error?"inspection failed":null].filter(Boolean).join(" · ")||"—"})},{key:"path",header:"path",render:r=>t.jsx("span",{className:"font-mono text-fg-faint truncate",children:r.path})}],p=c.useMemo(()=>m?Object.keys(m).filter(r=>m[r].status==="exhausted"&&!D.has(r)):[],[m,D]),w=p.length>0?t.jsx(Pe,{seeders:p,seedStatus:m,onDismiss:r=>F(g=>new Set(g).add(r)),onOpenLibrary:()=>N("library")}):null;if(u==="library")return t.jsxs(ee,{toolbar:t.jsx(ne,{active:u,onChange:N}),children:[w,t.jsx(ge,{})]});const y=h?n.find(r=>r.path===h)??null:null;return t.jsxs(ee,{toolbar:t.jsxs(t.Fragment,{children:[t.jsx(ne,{active:u,onChange:N}),t.jsx("span",{className:"mx-2 text-fg-faint",children:"·"}),t.jsxs("span",{className:"text-fg-faint",children:[n.length," plugins"]}),t.jsx("div",{className:"flex-1"}),t.jsx("span",{className:"text-fg-faint font-mono truncate",children:"~/.claude/plugins/installed_plugins.json"})]}),children:[w,d?t.jsxs("div",{role:"alert",className:"shrink-0 px-3 py-2 border-b border-line bg-red-950/20 text-xs text-red-300",children:[d,n.length>0?" — showing the last known list":null]}):null,s&&n.length===0?t.jsx(S,{title:"scanning plugins…"}):d&&n.length===0?null:y?t.jsx(Ae,{row:y,home:e,onBack:()=>o(null)},y.path):t.jsx("div",{className:"flex flex-col h-full",children:t.jsx("div",{className:"flex-1 min-h-0 overflow-auto",children:t.jsx(ke,{columns:f,rows:n,getKey:r=>r.path,activeKey:h,onRowClick:r=>o(r.path),empty:t.jsxs(t.Fragment,{children:["no plugins installed",t.jsxs("div",{className:"mt-2 text-fg-faint",children:["install one from the Library view or run ",t.jsx("code",{children:"claude plugin install"})]})]})})})})]})}const Ge=c.memo($e),te={"dev-plugin":"session-manager-dev plugin","scheduler-mcp":"Scheduler MCP server","agent-personas":"Agent personas (Architect, Dev Lead)"};function Pe({seeders:e,seedStatus:n,onDismiss:i,onOpenLibrary:s}){return t.jsx("div",{className:"shrink-0 flex flex-col gap-1.5 px-3 py-2 border-b border-line bg-yellow-950/20",children:e.map(a=>t.jsxs("div",{className:"flex items-start gap-2 text-xs text-yellow-300",children:[t.jsxs("div",{className:"flex-1 min-w-0",children:[t.jsxs("div",{className:"font-medium",children:[te[a]," setup failed after retrying — manual fix needed"]}),a==="dev-plugin"?t.jsxs("div",{className:"text-yellow-400/80 mt-0.5",children:["Click ",t.jsx("button",{onClick:s,className:"underline hover:no-underline",children:"here"}),' to open the Library tab, then click Install next to "session-manager-dev".']}):t.jsx("div",{className:"text-yellow-400/80 mt-0.5 font-mono",children:n[a].fix})]}),t.jsx("button",{onClick:()=>i(a),className:"shrink-0 text-yellow-400/70 hover:text-yellow-300","aria-label":`Dismiss ${te[a]} warning`,children:"✕"})]},a))})}function ne({active:e,onChange:n}){const i=[["installed","Installed"],["library","Library"]];return t.jsx("div",{className:"flex rounded border border-line overflow-hidden",role:"tablist","aria-label":"Plugins view",children:i.map(([s,a])=>t.jsx("button",{onClick:()=>n(s),role:"tab","aria-selected":e===s,className:`px-2 py-0.5 text-xs ${e===s?"bg-bg-hi text-fg":"text-fg-dim hover:text-fg hover:bg-bg-hi"}`,children:a},s))})}const Ee=250,Ce=["hooks/hooks.json",".mcp.json",".lsp.json","agents","skills","commands",".claude-plugin/plugin.json"];function Ae({row:e,home:n,onBack:i}){var O,j,H,B,U;const[s,a]=c.useState([]),[d,l]=c.useState([]),[h,o]=c.useState([]),[u,N]=c.useState([]),[m,I]=c.useState([]),[D,F]=c.useState("skills"),[E,C]=c.useState(null),[b,f]=c.useState(!0),[p,w]=c.useState([]),y=c.useRef(0),r=c.useRef(!1),g=c.useCallback(async()=>{if(!n)return;const v=++y.current;try{const{manifest:M}=await ue(e.path),[z,V,he]=await Promise.all([oe(e.path),me(e.path,M),K(`${e.path}/bin`,{filesOnly:!0})]),G=z?await ce(z):[],[X,Y]=await Promise.all([ie(V.agentFiles),ie(V.commandFiles)]),Q=V.mcpServers,fe=await Promise.all([...new Set([...Ce.map(A=>`${e.path}/${A}`),...V.sourcePaths])].map(async A=>await window.api.config.exists(A)?A:null));if(v!==y.current)return;a(G),l(X),o(Y),N(Q),I(he);const Z=fe.filter(A=>A!==null);w(A=>A.join("\0")===Z.join("\0")?A:Z),r.current||(r.current=!0,F(G.length>0?"skills":X.length>0?"agents":Y.length>0?"commands":Q.length>0?"mcp":e.hooks>0?"hooks":e.monitors>0?"monitors":e.hasLsp?"lsp":"files"))}catch(M){if(v!==y.current)return;q.error(`Failed to load plugin contents: ${M instanceof Error?M.message:String(M)}`)}finally{v===y.current&&f(!1)}},[e.path,e.name,e.hooks,e.monitors,e.hasLsp,n]);c.useEffect(()=>(f(!0),g(),()=>{y.current+=1}),[g]);const k=c.useRef(null),$=c.useRef(!1),x=c.useRef(!1),T=c.useCallback(async()=>{if($.current){x.current=!0;return}$.current=!0;try{do x.current=!1,await g();while(x.current)}finally{$.current=!1}},[g]),J=c.useCallback(()=>{k.current&&clearTimeout(k.current),k.current=setTimeout(()=>{k.current=null,T()},Ee)},[T]);c.useEffect(()=>()=>{k.current&&clearTimeout(k.current)},[]),ae(p,J,{depth:0});const W=[{key:"skills",label:"Skills",count:s.length},{key:"agents",label:"Agents",count:d.length},{key:"commands",label:"Commands",count:h.length},{key:"mcp",label:"MCP server",count:u.length},{key:"hooks",label:"Hooks",count:e.hooks},{key:"monitors",label:"Monitors",count:e.monitors},{key:"lsp",label:"LSP",count:e.hasLsp?1:0},{key:"files",label:"Bin",count:m.length}],R=(O=e.manifest)!=null&&O.author?typeof e.manifest.author=="string"?e.manifest.author:e.manifest.author.name??null:null,_=async v=>{const M=await window.api.shell.open({as:"external",url:v});M.ok||q.error(M.error??`Failed to open ${v}`)};return E?t.jsx(Se,{pluginName:((j=e.manifest)==null?void 0:j.name)??e.name,skills:s,initialSelectedId:E.id,onBack:()=>C(null)},E.id):t.jsxs("div",{className:"flex flex-col h-full min-h-0 overflow-hidden",children:[t.jsxs("div",{className:"shrink-0 h-11 flex items-baseline gap-3.5 px-3 border-b border-line bg-bg",children:[t.jsx("button",{type:"button",onClick:i,className:"font-mono text-accent hover:underline shrink-0 text-xs",children:"← Plugins"}),t.jsx("span",{className:"font-serif text-[22px] text-fg shrink-0 truncate max-w-[16rem]",children:((H=e.manifest)==null?void 0:H.name)??e.name}),(B=e.manifest)!=null&&B.version?t.jsxs("span",{className:"font-mono text-fg-faint text-xs shrink-0",children:["v",e.manifest.version]}):null,(U=e.manifest)!=null&&U.description?t.jsx("span",{className:"text-fg-dim text-xs min-w-0 flex-1 overflow-hidden text-ellipsis whitespace-nowrap",children:e.manifest.description}):t.jsx("span",{className:"flex-1"}),t.jsxs("div",{className:"flex items-center gap-1.5 shrink-0",children:[t.jsx(re,{input:de(e)}),e.hasMcp?t.jsx(xe,{tone:"accent",children:"MCP"}):null]})]}),e.manifest?t.jsxs("div",{className:"shrink-0 h-[26px] flex items-center gap-[18px] px-3 bg-bg-elev border-b border-line text-[9px] font-mono uppercase tracking-wide overflow-hidden",children:[R?t.jsxs("span",{className:"text-fg-dim truncate",children:[t.jsx("span",{className:"text-fg-faint",children:"Author "}),R]}):null,e.manifest.license?t.jsxs("span",{className:"text-fg-dim truncate",children:[t.jsx("span",{className:"text-fg-faint",children:"License "}),e.manifest.license]}):null,e.manifest.homepage?t.jsxs("button",{onClick:()=>_(e.manifest.homepage),className:"text-accent hover:underline truncate normal-case",title:e.manifest.homepage,children:[t.jsx("span",{className:"text-fg-faint uppercase",children:"Homepage "}),e.manifest.homepage]}):null,t.jsx("span",{className:"ml-auto text-fg-faint truncate normal-case",children:e.path})]}):t.jsx("div",{className:"shrink-0 h-[26px] flex items-center px-3 bg-bg-elev border-b border-line text-[9px] font-mono uppercase tracking-wide overflow-hidden",children:t.jsx("span",{className:"ml-auto text-fg-faint truncate normal-case",children:e.path})}),t.jsxs("div",{className:"flex-1 min-h-0 grid overflow-hidden",style:{gridTemplateColumns:"170px 1fr"},children:[t.jsx("div",{className:"min-h-0 overflow-auto bg-bg-elev border-r border-line py-1",children:W.map(v=>t.jsxs("button",{type:"button",disabled:v.count===0,onClick:()=>F(v.key),className:`w-full text-left px-3 py-1.5 text-xs flex items-center justify-between ${v.count===0?"text-fg-faint opacity-50 cursor-default":D===v.key?"bg-bg-hi text-fg":"text-fg-dim hover:text-fg hover:bg-bg-hi"}`,children:[t.jsx("span",{children:v.label}),v.count>0?t.jsx("span",{className:"font-mono text-fg-faint",children:v.count}):null]},v.key))}),t.jsx("div",{className:"min-h-0 overflow-auto",children:b?t.jsx(S,{title:"scanning…"}):t.jsx(Me,{active:D,row:e,skills:s,agents:d,commands:h,mcpServers:u,files:m,onOpenSkill:C})})]})]})}function Me({active:e,row:n,skills:i,agents:s,commands:a,mcpServers:d,files:l,onOpenSkill:h}){return e==="skills"?i.length===0?t.jsx(S,{title:"no components"}):t.jsx("div",{children:i.map((o,u)=>t.jsx(L,{n:u+1,name:o.name??o.id,description:o.description,onClick:()=>h(o)},o.id))}):e==="agents"?s.length===0?t.jsx(S,{title:"no components"}):t.jsx("div",{children:s.map((o,u)=>t.jsx(L,{n:u+1,name:o.name??o.id,description:o.description},o.id))}):e==="commands"?a.length===0?t.jsx(S,{title:"no components"}):t.jsx("div",{children:a.map((o,u)=>t.jsx(L,{n:u+1,name:o.name??o.id,description:o.description},o.id))}):e==="mcp"?d.length===0?t.jsx(S,{title:"no components"}):t.jsx("div",{children:d.map((o,u)=>t.jsx(L,{n:u+1,name:o.name,description:o.description},o.id))}):e==="hooks"?n.hooks===0?t.jsx(S,{title:"no components"}):t.jsx("div",{children:t.jsx(L,{n:1,name:`${n.hooks} ${n.hooks===1?"hook":"hooks"} configured`,description:`${n.path}/hooks/hooks.json`})}):e==="monitors"?n.monitors===0?t.jsx(S,{title:"no components"}):t.jsx("div",{children:t.jsx(L,{n:1,name:`${n.monitors} ${n.monitors===1?"monitor":"monitors"} configured`,description:`${n.path}/monitors/monitors.json`})}):e==="lsp"?n.hasLsp?t.jsx("div",{children:t.jsx(L,{n:1,name:"LSP server enabled",description:`${n.path}/.lsp.json`})}):t.jsx(S,{title:"no components"}):l.length===0?t.jsx(S,{title:"no components"}):t.jsx("div",{children:l.map((o,u)=>t.jsx(L,{n:u+1,name:o,description:null},o))})}function L({n:e,name:n,description:i,onClick:s}){const a=s?"button":"div";return t.jsxs(a,{onClick:s,className:`w-full flex items-baseline gap-2.5 py-[9px] px-3 border-b border-line text-left min-w-0 ${s?"hover:bg-bg-hi cursor-pointer":""}`,children:[t.jsx("span",{className:"font-serif text-fg-faint text-xs shrink-0 w-6 text-right",children:String(e).padStart(2,"0")}),t.jsxs("div",{className:"min-w-0 flex-1",children:[t.jsx("div",{className:"font-mono font-semibold text-accent truncate",children:n}),i?t.jsx("div",{className:"text-fg-dim text-xs truncate",children:i}):null]})]})}const Le=4;async function De(e,n,i){const s=new Array(e.length);let a=0;const d=Array.from({length:Math.min(n,e.length)},async()=>{for(;a<e.length;){const l=a++;s[l]=await i(e[l])}});return await Promise.all(d),s}function Re(e,n){return{name:e.name,path:e.path,mtimeMs:e.mtimeMs,hasManifest:!1,manifest:null,hasLsp:!1,monitors:0,hooks:0,agents:0,commands:0,skills:0,binCount:0,hasMcp:!1,error:n}}async function _e(e){const{hasManifest:n,manifest:i}=await ue(e.path),[s,a]=await Promise.all([window.api.config.exists(`${e.path}/.lsp.json`),window.api.config.exists(`${e.path}/monitors/monitors.json`)]);let d=0;if(a){const m=await window.api.config.readJson(`${e.path}/monitors/monitors.json`);Array.isArray(m.data)?d=m.data.length:m.data&&typeof m.data=="object"&&Array.isArray(m.data.monitors)&&(d=m.data.monitors.length)}const[l,h,o]=await Promise.all([me(e.path,i),K(`${e.path}/bin`,{filesOnly:!0}),oe(e.path)]),u=h.length,N=o?(await ce(o)).length:0;return{name:e.name,path:e.path,mtimeMs:e.mtimeMs,hasManifest:n,manifest:i,hasLsp:s,monitors:d,hooks:l.hooks,agents:l.agentFiles.length,commands:l.commandFiles.length,skills:N,binCount:u,hasMcp:l.mcpServers.length>0}}async function ue(e){let n=`${e}/.claude-plugin/plugin.json`,i=await window.api.config.exists(n);if(i||(n=`${e}/plugin.json`,i=await window.api.config.exists(n)),!i)return{hasManifest:i,manifest:null};const s=await window.api.config.readJson(n),a=s.exists&&!s.parseError&&s.data&&typeof s.data=="object"&&!Array.isArray(s.data);return{hasManifest:i,manifest:a?s.data:null}}const P=e=>!!e&&typeof e=="object"&&!Array.isArray(e);function Oe(e,n){const i=n.startsWith("/")?n.startsWith(`${e}/`)?n.slice(e.length+1):null:n;if(i===null)return se(n);const s=[];for(const a of i.split("/"))if(!(a===""||a===".")){if(a!=="..")s.push(a);else if(s.pop()===void 0)return se(n)}return s.length>0?`${e}/${s.join("/")}`:e}function se(e){return console.warn(`[plugins] ignoring manifest path outside the plugin root: ${e}`),null}function Fe(e){return typeof e=="string"?[e]:Array.isArray(e)?e.filter(n=>typeof n=="string"):[]}function Te(e){if(!P(e))return null;if(P(e.mcpServers))return e.mcpServers;const n=Object.values(e);return n.length>0&&n.every(s=>P(s)&&(typeof s.command=="string"||typeof s.url=="string"))?e:null}function Be(e){if(!P(e))return 0;const n=P(e.hooks)?e.hooks:e;return Object.values(n).reduce((i,s)=>Array.isArray(s)?i+s.reduce((a,d)=>a+(P(d)&&Array.isArray(d.hooks)?d.hooks.length:0),0):i,0)}async function me(e,n){const i=(b,f)=>{const p=new Set;for(const w of Fe(b)){const y=Oe(e,w);y&&p.add(y)}return p.add(`${e}/${f}`),[...p]},s=async b=>{const f=new Set;for(const p of b)if(p.endsWith(".md"))f.add(p);else for(const w of await K(p,{filesOnly:!0}))w.endsWith(".md")&&f.add(`${p}/${w}`);return[...f]},a=async b=>{try{const f=await window.api.config.readJson(b);return f.exists&&!f.parseError?f.data:null}catch{return null}},d=i(n==null?void 0:n.commands,"commands"),l=i(n==null?void 0:n.agents,"agents"),h=n==null?void 0:n.hooks,o=i(typeof h=="string"||Array.isArray(h)?h:void 0,"hooks/hooks.json"),u=n==null?void 0:n.mcpServers,N=i(typeof u=="string"||Array.isArray(u)?u:void 0,".mcp.json"),[m,I,D,F]=await Promise.all([s(d),s(l),Promise.all(o.map(a)),Promise.all(N.map(a))]),E=[...P(h)?[h]:[],...D].reduce((b,f)=>b+Be(f),0),C=new Map;for(const b of[...P(u)?[u]:[],...F])for(const[f,p]of Object.entries(Te(b)??{})){if(C.has(f))continue;const w=P(p)?p.command:void 0,y=P(p)?p.url:void 0;C.set(f,{id:f,name:f,description:typeof w=="string"?w:typeof y=="string"?y:null})}return{commandFiles:m,agentFiles:I,hooks:E,mcpServers:[...C.values()],sourcePaths:[...d,...l,...o,...N]}}async function ie(e){const n=await Promise.all(e.map(s=>window.api.config.readText(s))),i=[];return e.forEach((s,a)=>{if(!n[a].exists)return;const d=s.slice(s.lastIndexOf("/")+1),l=le(n[a].text);i.push({id:s,name:l.name??d.replace(/\.md$/,""),description:l.description})}),i}async function K(e,n){try{return(await window.api.config.listDir(e,n)).entries.map(s=>s.name)}catch{return[]}}export{Ge as Plugins};
|