@mmerterden/multi-agent-pipeline 20.35.0 → 20.37.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/CHANGELOG.md +26 -0
- package/README.md +6 -5
- package/README.tr.md +6 -5
- package/docs/architecture.md +3 -3
- package/docs/ecosystem.md +5 -5
- package/docs/facts.json +4 -4
- package/install/catalog-history.json +1 -1
- package/manifest.json +210 -136
- package/package.json +5 -1
- package/pipeline/commands/multi-agent/SKILL.md +33 -154
- package/pipeline/commands/multi-agent/analysis/SKILL.md +14 -68
- package/pipeline/commands/multi-agent/analysis-jira/SKILL.md +5 -8
- package/pipeline/commands/multi-agent/autopilot-on/SKILL.md +38 -109
- package/pipeline/commands/multi-agent/channels/SKILL.md +38 -167
- package/pipeline/commands/multi-agent/complaint-analysis/SKILL.md +37 -144
- package/pipeline/commands/multi-agent/design-check/SKILL.md +27 -131
- package/pipeline/commands/multi-agent/garbage-collect/SKILL.md +22 -48
- package/pipeline/commands/multi-agent/ios-coding-standard/SKILL.md +37 -180
- package/pipeline/commands/multi-agent/jira/SKILL.md +16 -46
- package/pipeline/commands/multi-agent/office-pair/SKILL.md +66 -0
- package/pipeline/commands/multi-agent/office-status/SKILL.md +1 -1
- package/pipeline/commands/multi-agent/refactor/SKILL.md +23 -184
- package/pipeline/commands/multi-agent/resume/SKILL.md +30 -136
- package/pipeline/commands/multi-agent/review/SKILL.md +24 -199
- package/pipeline/commands/multi-agent/setup/SKILL.md +25 -181
- package/pipeline/commands/multi-agent/stack/SKILL.md +9 -53
- package/pipeline/commands/multi-agent/status/SKILL.md +13 -2
- package/pipeline/commands/multi-agent/store-ready/SKILL.md +27 -226
- package/pipeline/commands/multi-agent/sync/SKILL.md +38 -167
- package/pipeline/commands/multi-agent/test-gen/SKILL.md +2 -4
- package/pipeline/commands/multi-agent/update/SKILL.md +18 -60
- package/pipeline/contract/CHANGELOG.md +9 -0
- package/pipeline/contract/fixtures/error-invalid-request.json +1 -1
- package/pipeline/contract/fixtures/error-unauthorized.json +1 -1
- package/pipeline/contract/fixtures/error-unsigned.json +1 -1
- package/pipeline/contract/fixtures/runs-awaiting-question.json +1 -1
- package/pipeline/contract/fixtures/runs-empty.json +1 -1
- package/pipeline/contract/fixtures/runs-failed.json +1 -1
- package/pipeline/contract/fixtures/runs-pr-opened-redacted.json +1 -1
- package/pipeline/contract/fixtures/runs-pr-opened.json +1 -1
- package/pipeline/contract/fixtures/runs-running.json +1 -1
- package/pipeline/contract/manifest.json +5 -5
- package/pipeline/contract/types/index.d.ts +3 -2
- package/pipeline/lib/qr-code.mjs +26 -0
- package/pipeline/multi-agent-refs/autopilot-on/details.md +148 -0
- package/pipeline/multi-agent-refs/channels/generator.md +14 -0
- package/pipeline/multi-agent-refs/channels/preview.md +13 -0
- package/pipeline/multi-agent-refs/channels/reference.md +46 -0
- package/pipeline/multi-agent-refs/complaint-analysis/phases.md +159 -0
- package/pipeline/multi-agent-refs/cross-cli-contract.md +4 -4
- package/pipeline/multi-agent-refs/design-check/export.md +1 -0
- package/pipeline/multi-agent-refs/design-check/init-inventory.md +1 -1
- package/pipeline/multi-agent-refs/design-check/scope-and-ledger.md +64 -0
- package/pipeline/multi-agent-refs/features/analysis-command-notes.md +53 -0
- package/pipeline/multi-agent-refs/garbage-collect/phases.md +60 -0
- package/pipeline/multi-agent-refs/help/en.md +1 -0
- package/pipeline/multi-agent-refs/help/tr.md +1 -0
- package/pipeline/multi-agent-refs/ios-coding-standard/plan.md +71 -0
- package/pipeline/multi-agent-refs/ios-coding-standard/resolve-and-scan.md +135 -0
- package/pipeline/multi-agent-refs/jira/picker-detail.md +71 -0
- package/pipeline/multi-agent-refs/orchestrator/copilot-pipeline.md +88 -0
- package/pipeline/multi-agent-refs/orchestrator/dispatcher.md +96 -1
- package/pipeline/multi-agent-refs/refactor/sweep.md +190 -0
- package/pipeline/multi-agent-refs/resume/details.md +115 -2
- package/pipeline/multi-agent-refs/review/inputs.md +33 -1
- package/pipeline/multi-agent-refs/review/module-guides.md +9 -1
- package/pipeline/multi-agent-refs/review/panel.md +75 -0
- package/pipeline/multi-agent-refs/review/valven.md +5 -1
- package/pipeline/multi-agent-refs/setup/hooks-and-plugins.md +4 -0
- package/pipeline/multi-agent-refs/setup/token-save-flow.md +13 -0
- package/pipeline/multi-agent-refs/stack/background.md +72 -0
- package/pipeline/multi-agent-refs/store-ready/gates.md +139 -0
- package/pipeline/multi-agent-refs/store-ready/report.md +40 -2
- package/pipeline/multi-agent-refs/sync/office.md +10 -0
- package/pipeline/multi-agent-refs/sync/overview.md +61 -0
- package/pipeline/multi-agent-refs/sync/repo.md +91 -0
- package/pipeline/multi-agent-refs/update/details.md +83 -0
- package/pipeline/schemas/runs-index.schema.json +2 -1
- package/pipeline/scripts/contract-server.mjs +1 -1
- package/pipeline/scripts/office-pair.mjs +40 -9
- package/pipeline/scripts/runs-index.mjs +34 -7
- package/pipeline/scripts/status-overview.mjs +401 -0
- package/pipeline/skills/.skill-manifest.json +69 -65
- package/pipeline/skills/.skills-index.json +15 -4
- package/pipeline/skills/shared/README.md +4 -3
- package/pipeline/skills/shared/core/apple-archive-compliance/SKILL.md +8 -71
- package/pipeline/skills/shared/core/apple-archive-compliance/references/examples.md +67 -0
- package/pipeline/skills/shared/core/apple-archive-compliance/references/version-history.md +8 -0
- package/pipeline/skills/shared/core/google-play-compliance/SKILL.md +11 -73
- package/pipeline/skills/shared/core/google-play-compliance/references/examples.md +69 -0
- package/pipeline/skills/shared/core/google-play-compliance/references/notes.md +7 -0
- package/pipeline/skills/shared/core/google-play-compliance/references/version-history.md +5 -0
- package/pipeline/skills/shared/core/multi-agent/SKILL.md +39 -262
- package/pipeline/skills/shared/core/multi-agent-autopilot-on/SKILL.md +35 -73
- package/pipeline/skills/shared/core/multi-agent-channels/SKILL.md +37 -337
- package/pipeline/skills/shared/core/multi-agent-design-check/SKILL.md +27 -122
- package/pipeline/skills/shared/core/multi-agent-help/SKILL.md +8 -340
- package/pipeline/skills/shared/core/multi-agent-ios-coding-standard/SKILL.md +37 -180
- package/pipeline/skills/shared/core/multi-agent-office-pair/SKILL.md +19 -0
- package/pipeline/skills/shared/core/multi-agent-office-status/SKILL.md +1 -1
- package/pipeline/skills/shared/core/multi-agent-refactor/SKILL.md +23 -184
- package/pipeline/skills/shared/core/multi-agent-review/SKILL.md +14 -82
- package/pipeline/skills/shared/core/multi-agent-setup/SKILL.md +25 -277
- package/pipeline/skills/shared/core/multi-agent-status/SKILL.md +13 -2
- package/pipeline/skills/shared/core/multi-agent-sync/SKILL.md +75 -219
- package/pipeline/skills/shared/external/alarmkit/SKILL.md +11 -162
- package/pipeline/skills/shared/external/alarmkit/references/alarmkit-code.md +257 -0
- package/pipeline/skills/shared/external/android-architecture/SKILL.md +7 -58
- package/pipeline/skills/shared/external/android-architecture/references/code-examples.md +68 -0
- package/pipeline/skills/shared/external/app-intents/SKILL.md +3 -45
- package/pipeline/skills/shared/external/app-intents/references/code-examples.md +54 -0
- package/pipeline/skills/shared/external/app-store-optimization/SKILL.md +5 -81
- package/pipeline/skills/shared/external/app-store-optimization/references/engagement-and-localization.md +96 -0
- package/pipeline/skills/shared/external/app-store-review/SKILL.md +6 -112
- package/pipeline/skills/shared/external/app-store-review/references/monetization-and-submission.md +138 -0
- package/pipeline/skills/shared/external/apple-on-device-ai/SKILL.md +10 -117
- package/pipeline/skills/shared/external/apple-on-device-ai/references/foundation-models-code.md +186 -0
- package/pipeline/skills/shared/external/authentication/SKILL.md +3 -50
- package/pipeline/skills/shared/external/authentication/references/code-examples.md +59 -0
- package/pipeline/skills/shared/external/callkit-voip/SKILL.md +3 -84
- package/pipeline/skills/shared/external/callkit-voip/references/code-examples.md +93 -0
- package/pipeline/skills/shared/external/ci-cd-pipelines/SKILL.md +7 -263
- package/pipeline/skills/shared/external/ci-cd-pipelines/references/pipeline-examples.md +281 -0
- package/pipeline/skills/shared/external/clean-code/SKILL.md +8 -41
- package/pipeline/skills/shared/external/clean-code/references/code-examples.md +56 -0
- package/pipeline/skills/shared/external/cloudkit-sync/SKILL.md +8 -146
- package/pipeline/skills/shared/external/cloudkit-sync/references/cloudkit-code.md +214 -0
- package/pipeline/skills/shared/external/compose-components/SKILL.md +6 -80
- package/pipeline/skills/shared/external/compose-components/references/code-examples.md +89 -0
- package/pipeline/skills/shared/external/compose-navigation/SKILL.md +5 -64
- package/pipeline/skills/shared/external/compose-navigation/references/code-examples.md +70 -0
- package/pipeline/skills/shared/external/core-data/SKILL.md +9 -157
- package/pipeline/skills/shared/external/core-data/references/core-data-code.md +221 -0
- package/pipeline/skills/shared/external/core-nfc/SKILL.md +3 -89
- package/pipeline/skills/shared/external/core-nfc/references/code-examples.md +98 -0
- package/pipeline/skills/shared/external/coreml/SKILL.md +17 -133
- package/pipeline/skills/shared/external/coreml/references/coreml-code.md +208 -0
- package/pipeline/skills/shared/external/css-modern/SKILL.md +8 -131
- package/pipeline/skills/shared/external/css-modern/references/code-examples.md +142 -0
- package/pipeline/skills/shared/external/database-patterns/SKILL.md +6 -103
- package/pipeline/skills/shared/external/database-patterns/references/orm-examples.md +112 -0
- package/pipeline/skills/shared/external/html-semantic/SKILL.md +7 -48
- package/pipeline/skills/shared/external/html-semantic/references/code-examples.md +54 -0
- package/pipeline/skills/shared/external/humanizer/SKILL.md +6 -19
- package/pipeline/skills/shared/external/humanizer/references/voice.md +24 -0
- package/pipeline/skills/shared/external/ios-accessibility/SKILL.md +13 -170
- package/pipeline/skills/shared/external/ios-accessibility/references/assistive-tech-review.md +224 -0
- package/pipeline/skills/shared/external/ios-localization/SKILL.md +2 -24
- package/pipeline/skills/shared/external/ios-localization/references/code-examples.md +30 -0
- package/pipeline/skills/shared/external/ios-networking/SKILL.md +3 -58
- package/pipeline/skills/shared/external/ios-networking/references/code-examples.md +67 -0
- package/pipeline/skills/shared/external/ios-security/SKILL.md +4 -64
- package/pipeline/skills/shared/external/ios-security/references/code-examples.md +74 -0
- package/pipeline/skills/shared/external/live-activities/SKILL.md +4 -90
- package/pipeline/skills/shared/external/live-activities/references/code-examples.md +108 -0
- package/pipeline/skills/shared/external/localization-reuse-map/SKILL.md +9 -23
- package/pipeline/skills/shared/external/localization-reuse-map/references/project-settings.md +22 -0
- package/pipeline/skills/shared/external/mapkit-location/SKILL.md +5 -95
- package/pipeline/skills/shared/external/mapkit-location/references/code-examples.md +117 -0
- package/pipeline/skills/shared/external/nextjs-app-router/SKILL.md +7 -106
- package/pipeline/skills/shared/external/nextjs-app-router/references/app-router-examples.md +124 -0
- package/pipeline/skills/shared/external/permissionkit/SKILL.md +2 -31
- package/pipeline/skills/shared/external/permissionkit/references/code-examples.md +37 -0
- package/pipeline/skills/shared/external/play-store-review/SKILL.md +6 -74
- package/pipeline/skills/shared/external/play-store-review/references/signing-and-policies.md +83 -0
- package/pipeline/skills/shared/external/push-notifications/SKILL.md +8 -125
- package/pipeline/skills/shared/external/push-notifications/references/notification-code.md +176 -0
- package/pipeline/skills/shared/external/python-patterns/SKILL.md +7 -158
- package/pipeline/skills/shared/external/python-patterns/references/python-examples.md +176 -0
- package/pipeline/skills/shared/external/rest-api-design/SKILL.md +7 -157
- package/pipeline/skills/shared/external/rest-api-design/references/api-examples.md +175 -0
- package/pipeline/skills/shared/external/swift-architecture/SKILL.md +11 -230
- package/pipeline/skills/shared/external/swift-architecture/references/pattern-examples.md +295 -0
- package/pipeline/skills/shared/external/swift-codable/SKILL.md +17 -149
- package/pipeline/skills/shared/external/swift-codable/references/codable-recipes.md +207 -0
- package/pipeline/skills/shared/external/swift-concurrency/SKILL.md +12 -99
- package/pipeline/skills/shared/external/swift-concurrency/references/code-examples.md +100 -0
- package/pipeline/skills/shared/external/swift-concurrency/references/swift-6-2-additions.md +18 -0
- package/pipeline/skills/shared/external/swift-formatstyle/SKILL.md +9 -78
- package/pipeline/skills/shared/external/swift-formatstyle/references/formatstyle-examples.md +113 -0
- package/pipeline/skills/shared/external/swift-language/SKILL.md +3 -56
- package/pipeline/skills/shared/external/swift-language/references/code-examples.md +65 -0
- package/pipeline/skills/shared/external/swift-testing-pro/SKILL.md +1 -0
- package/pipeline/skills/shared/external/swiftdata/SKILL.md +6 -122
- package/pipeline/skills/shared/external/swiftdata/references/code-examples.md +140 -0
- package/pipeline/skills/shared/external/swiftui-uikit-interop/SKILL.md +12 -139
- package/pipeline/skills/shared/external/swiftui-uikit-interop/references/interop-code.md +198 -0
- package/pipeline/skills/shared/external/tailwind-css/SKILL.md +9 -81
- package/pipeline/skills/shared/external/tailwind-css/references/code-examples.md +91 -0
- package/pipeline/skills/shared/external/testing-backend/SKILL.md +7 -207
- package/pipeline/skills/shared/external/testing-backend/references/testing-examples.md +225 -0
- package/pipeline/skills/shared/external/typescript-patterns/SKILL.md +8 -89
- package/pipeline/skills/shared/external/typescript-patterns/references/code-examples.md +100 -0
- package/pipeline/skills/shared/external/vision-framework/SKILL.md +2 -20
- package/pipeline/skills/shared/external/vision-framework/references/code-examples.md +26 -0
- package/pipeline/skills/shared/external/vue-composition/SKILL.md +7 -169
- package/pipeline/skills/shared/external/vue-composition/references/vue-examples.md +187 -0
- package/pipeline/skills/shared/external/web-accessibility/SKILL.md +7 -174
- package/pipeline/skills/shared/external/web-accessibility/references/a11y-examples.md +192 -0
- package/pipeline/skills/shared/external/web-performance/SKILL.md +8 -66
- package/pipeline/skills/shared/external/web-performance/references/code-examples.md +77 -0
- package/pipeline/skills/shared/external/web-testing/SKILL.md +11 -186
- package/pipeline/skills/shared/external/web-testing/references/testing-examples.md +213 -0
- package/pipeline/skills/skills-index.md +2 -1
- /package/pipeline/skills/shared/external/localization-reuse-map/{reference → references}/format-and-output.md +0 -0
- /package/pipeline/skills/shared/external/localization-reuse-map/{reference → references}/publish-and-snapshot.md +0 -0
- /package/pipeline/skills/shared/external/localization-reuse-map/{reference → references}/sources-and-recipes.md +0 -0
|
@@ -0,0 +1,401 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* @file status-overview.mjs - what is happening on this machine right now,
|
|
4
|
+
* in one answer: the live Claude Code sessions and the run each one drives,
|
|
5
|
+
* the tokens they used, which accounts and credentials are set up, and the
|
|
6
|
+
* run list.
|
|
7
|
+
*
|
|
8
|
+
* Sources, all read on every call:
|
|
9
|
+
*
|
|
10
|
+
* 1. sessions: `claude agents --json` (lib/claude-sessions.mjs), matched to
|
|
11
|
+
* a run by the session id it recorded, else by a working directory inside
|
|
12
|
+
* the run's worktree (activity-snapshot.mjs matchRun);
|
|
13
|
+
* 2. tokens: the `usage` block of every assistant message in the session's
|
|
14
|
+
* transcript and its subagent transcripts, counted once per message id
|
|
15
|
+
* (a message split into several lines repeats its usage). The last-24h
|
|
16
|
+
* figure covers every transcript written in that window. Prices come from
|
|
17
|
+
* cost-table.json; cache writes are priced at the input rate, so the
|
|
18
|
+
* estimate errs low;
|
|
19
|
+
* 3. accounts: the github.com logins gh holds (usage-identity.mjs) and the
|
|
20
|
+
* author git would write in the current directory;
|
|
21
|
+
* 4. credentials: each logical name in prefs.global.keychainMapping is
|
|
22
|
+
* "registered" when its service name is in `credential-store.sh list`.
|
|
23
|
+
* Only service names are read: the store is never asked for a value, and
|
|
24
|
+
* nothing about a value (length, prefix) is printed;
|
|
25
|
+
* 5. runs: runs-index.mjs, the same records /multi-agent:status always used.
|
|
26
|
+
*
|
|
27
|
+
* Usage:
|
|
28
|
+
* status-overview.mjs [--json] [--all]
|
|
29
|
+
*
|
|
30
|
+
* Env: MA_CREDENTIAL_STORE (the store script), MA_PREFS (the preferences
|
|
31
|
+
* file), CLAUDE_PROJECTS_ROOT (the transcript root), MA_AP_AGENTS_JSON (the
|
|
32
|
+
* session list, see claude-sessions.mjs).
|
|
33
|
+
*
|
|
34
|
+
* Exit: 0 printed, 2 usage.
|
|
35
|
+
*
|
|
36
|
+
* @module pipeline/scripts/status-overview
|
|
37
|
+
*/
|
|
38
|
+
|
|
39
|
+
import { createReadStream, existsSync, readdirSync, readFileSync, statSync } from "node:fs";
|
|
40
|
+
import { spawnSync } from "node:child_process";
|
|
41
|
+
import { homedir } from "node:os";
|
|
42
|
+
import { basename, join } from "node:path";
|
|
43
|
+
import { createInterface } from "node:readline";
|
|
44
|
+
|
|
45
|
+
import { listSessions } from "../lib/claude-sessions.mjs";
|
|
46
|
+
import { runMain } from "../lib/fatal.mjs";
|
|
47
|
+
import { invokedDirectly } from "../lib/invoked-directly.mjs";
|
|
48
|
+
import { costUsd } from "./_cost.mjs";
|
|
49
|
+
import { matchRun } from "./activity-snapshot.mjs";
|
|
50
|
+
import { buildIndex, phaseCell } from "./runs-index.mjs";
|
|
51
|
+
import { listAccounts } from "./usage-identity.mjs";
|
|
52
|
+
|
|
53
|
+
export const CONTRACT_VERSION = "1.0.0";
|
|
54
|
+
export const WINDOW_MS = 24 * 3_600_000;
|
|
55
|
+
const SAFE_SESSION_ID = /^[A-Za-z0-9-]{8,64}$/;
|
|
56
|
+
/**
|
|
57
|
+
* States of a session that is still open. Wider than claude-sessions.mjs
|
|
58
|
+
* LIVE_STATUSES, which asks whether a session is working: an idle or blocked
|
|
59
|
+
* session is not working, but it is open and it is what this overview counts.
|
|
60
|
+
*/
|
|
61
|
+
const OPEN = new Set(["busy", "running", "waiting", "idle", "blocked"]);
|
|
62
|
+
|
|
63
|
+
const projectsRoot = (env) => env.CLAUDE_PROJECTS_ROOT || join(homedir(), ".claude", "projects");
|
|
64
|
+
const prefsPath = (env) =>
|
|
65
|
+
env.MA_PREFS || join(homedir(), ".claude", "multi-agent-preferences.json");
|
|
66
|
+
const storePath = (env) =>
|
|
67
|
+
env.MA_CREDENTIAL_STORE || new URL("../lib/credential-store.sh", import.meta.url).pathname;
|
|
68
|
+
|
|
69
|
+
function listDir(dir) {
|
|
70
|
+
try {
|
|
71
|
+
return readdirSync(dir);
|
|
72
|
+
} catch {
|
|
73
|
+
return [];
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
let PRICES = null;
|
|
78
|
+
/** The cost-table rate for a wire model id, by its `modelId`, else by rung name in the id. */
|
|
79
|
+
export function rateFor(model) {
|
|
80
|
+
if (!PRICES) {
|
|
81
|
+
try {
|
|
82
|
+
PRICES = JSON.parse(
|
|
83
|
+
readFileSync(new URL("./cost-table.json", import.meta.url), "utf8"),
|
|
84
|
+
).prices;
|
|
85
|
+
} catch {
|
|
86
|
+
PRICES = {};
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
if (typeof model !== "string" || !model) return null;
|
|
90
|
+
for (const rate of Object.values(PRICES)) if (rate?.modelId === model) return rate;
|
|
91
|
+
for (const [rung, rate] of Object.entries(PRICES)) {
|
|
92
|
+
if (/^[a-z]+$/.test(rung) && model.includes(`-${rung}-`)) return rate;
|
|
93
|
+
}
|
|
94
|
+
return null;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const emptyUsage = () => ({ in: 0, out: 0, cacheRead: 0, cacheWrite: 0, usd: 0, unpriced: 0 });
|
|
98
|
+
|
|
99
|
+
function addUsage(acc, model, u) {
|
|
100
|
+
const tin = Number(u.input_tokens) || 0;
|
|
101
|
+
const tout = Number(u.output_tokens) || 0;
|
|
102
|
+
const read = Number(u.cache_read_input_tokens) || 0;
|
|
103
|
+
const write = Number(u.cache_creation_input_tokens) || 0;
|
|
104
|
+
acc.in += tin;
|
|
105
|
+
acc.out += tout;
|
|
106
|
+
acc.cacheRead += read;
|
|
107
|
+
acc.cacheWrite += write;
|
|
108
|
+
const usd = costUsd(rateFor(model), tin + write, tout, read);
|
|
109
|
+
if (usd === null) acc.unpriced++;
|
|
110
|
+
else acc.usd += usd;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Token usage in one transcript: the whole file, and the part at or after
|
|
115
|
+
* `since`. Each message id counts once in the file; `windowSeen`, shared
|
|
116
|
+
* across files, keeps a message a resumed or forked session copied into a
|
|
117
|
+
* second transcript from counting twice in the window. A file that cannot be
|
|
118
|
+
* read adds nothing and comes back `readable: false`.
|
|
119
|
+
*
|
|
120
|
+
* @returns {Promise<{total: object, window: object, readable: boolean}>}
|
|
121
|
+
*/
|
|
122
|
+
export async function transcriptUsage(path, since = 0, windowSeen = new Set()) {
|
|
123
|
+
const total = emptyUsage();
|
|
124
|
+
const window = emptyUsage();
|
|
125
|
+
const seen = new Set();
|
|
126
|
+
try {
|
|
127
|
+
await scan(path, (rec, msg, id) => {
|
|
128
|
+
if (id) {
|
|
129
|
+
if (seen.has(id)) return;
|
|
130
|
+
seen.add(id);
|
|
131
|
+
}
|
|
132
|
+
addUsage(total, msg.model, msg.usage);
|
|
133
|
+
const t = Date.parse(rec.timestamp);
|
|
134
|
+
if (!Number.isFinite(t) || t < since) return;
|
|
135
|
+
if (id) {
|
|
136
|
+
if (windowSeen.has(id)) return;
|
|
137
|
+
windowSeen.add(id);
|
|
138
|
+
}
|
|
139
|
+
addUsage(window, msg.model, msg.usage);
|
|
140
|
+
});
|
|
141
|
+
} catch {
|
|
142
|
+
return { total: emptyUsage(), window: emptyUsage(), readable: false };
|
|
143
|
+
}
|
|
144
|
+
return { total, window, readable: true };
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
async function scan(path, visit) {
|
|
148
|
+
const lines = createInterface({ input: createReadStream(path), crlfDelay: Infinity });
|
|
149
|
+
for await (const line of lines) {
|
|
150
|
+
if (!line.includes('"usage"')) continue;
|
|
151
|
+
let rec;
|
|
152
|
+
try {
|
|
153
|
+
rec = JSON.parse(line);
|
|
154
|
+
} catch {
|
|
155
|
+
continue;
|
|
156
|
+
}
|
|
157
|
+
const msg = rec?.message;
|
|
158
|
+
if (rec?.type !== "assistant" || !msg?.usage || typeof msg.usage !== "object") continue;
|
|
159
|
+
// The host writes its own notices as "<synthetic>" messages; no model ran.
|
|
160
|
+
if (msg.model === "<synthetic>") continue;
|
|
161
|
+
visit(rec, msg, typeof msg.id === "string" ? msg.id : null);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
function merge(a, b) {
|
|
166
|
+
for (const k of Object.keys(a)) a[k] += b[k];
|
|
167
|
+
return a;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/** The session transcript and its subagent transcripts, or [] when the id is unsafe or absent. */
|
|
171
|
+
export function sessionFiles(root, sessionId) {
|
|
172
|
+
if (typeof sessionId !== "string" || !SAFE_SESSION_ID.test(sessionId)) return [];
|
|
173
|
+
for (const slug of listDir(root)) {
|
|
174
|
+
const main = join(root, slug, `${sessionId}.jsonl`);
|
|
175
|
+
if (!existsSync(main)) continue;
|
|
176
|
+
const sub = join(root, slug, sessionId, "subagents");
|
|
177
|
+
return [
|
|
178
|
+
main,
|
|
179
|
+
...listDir(sub)
|
|
180
|
+
.filter((f) => f.endsWith(".jsonl"))
|
|
181
|
+
.map((f) => join(sub, f)),
|
|
182
|
+
];
|
|
183
|
+
}
|
|
184
|
+
return [];
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/** Every transcript under the root written at or after `since`. */
|
|
188
|
+
function recentFiles(root, since) {
|
|
189
|
+
const out = [];
|
|
190
|
+
const visit = (dir, depth) => {
|
|
191
|
+
for (const name of listDir(dir)) {
|
|
192
|
+
const p = join(dir, name);
|
|
193
|
+
let st;
|
|
194
|
+
try {
|
|
195
|
+
st = statSync(p);
|
|
196
|
+
} catch {
|
|
197
|
+
continue;
|
|
198
|
+
}
|
|
199
|
+
if (st.isDirectory() && depth < 3) visit(p, depth + 1);
|
|
200
|
+
else if (st.isFile() && name.endsWith(".jsonl") && st.mtimeMs >= since) out.push(p);
|
|
201
|
+
}
|
|
202
|
+
};
|
|
203
|
+
visit(root, 0);
|
|
204
|
+
return out;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/** Logical credential names and whether each is registered; values are never read. */
|
|
208
|
+
export function credentialStatus({ env = process.env } = {}) {
|
|
209
|
+
let mapping;
|
|
210
|
+
try {
|
|
211
|
+
mapping = JSON.parse(readFileSync(prefsPath(env), "utf8"))?.global?.keychainMapping ?? {};
|
|
212
|
+
} catch {
|
|
213
|
+
return { readable: false, items: [] };
|
|
214
|
+
}
|
|
215
|
+
const listed = spawnSync("bash", [storePath(env), "list"], {
|
|
216
|
+
encoding: "utf8",
|
|
217
|
+
timeout: 15000,
|
|
218
|
+
env,
|
|
219
|
+
});
|
|
220
|
+
if (listed.status !== 0) return { readable: false, items: [] };
|
|
221
|
+
const services = new Set(String(listed.stdout).split("\n").filter(Boolean));
|
|
222
|
+
const items = Object.entries(mapping)
|
|
223
|
+
.filter(([k, v]) => typeof k === "string" && typeof v === "string" && v)
|
|
224
|
+
.map(([name, service]) => ({ name, registered: services.has(service) }))
|
|
225
|
+
.sort((a, b) => a.name.localeCompare(b.name));
|
|
226
|
+
return { readable: true, items };
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
/** The author git would write in the current directory. */
|
|
230
|
+
function gitIdentity(env) {
|
|
231
|
+
const read = (key) => {
|
|
232
|
+
const r = spawnSync("git", ["config", "--get", key], { encoding: "utf8", timeout: 5000, env });
|
|
233
|
+
return r.status === 0 ? r.stdout.trim() || null : null;
|
|
234
|
+
};
|
|
235
|
+
const name = read("user.name");
|
|
236
|
+
return name ? { name, email: read("user.email") } : null;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
/**
|
|
240
|
+
* The whole overview as one document.
|
|
241
|
+
*
|
|
242
|
+
* @param {{env?: object, now?: number, all?: boolean}} [opts]
|
|
243
|
+
*/
|
|
244
|
+
export async function statusOverview({ env = process.env, now = Date.now(), all = false } = {}) {
|
|
245
|
+
const root = projectsRoot(env);
|
|
246
|
+
const since = now - WINDOW_MS;
|
|
247
|
+
const runs = buildIndex();
|
|
248
|
+
const rows = listSessions(env);
|
|
249
|
+
|
|
250
|
+
// One read per transcript: a live session's file is usually also one of the
|
|
251
|
+
// files written in the window.
|
|
252
|
+
const open = (Array.isArray(rows) ? rows : []).filter((s) => {
|
|
253
|
+
const state = s?.status ?? s?.state ?? null;
|
|
254
|
+
return !state || OPEN.has(state);
|
|
255
|
+
});
|
|
256
|
+
const files = new Map();
|
|
257
|
+
for (const s of open) for (const f of sessionFiles(root, s?.sessionId)) files.set(f, null);
|
|
258
|
+
for (const f of recentFiles(root, since)) files.set(f, null);
|
|
259
|
+
const windowSeen = new Set();
|
|
260
|
+
const last24h = emptyUsage();
|
|
261
|
+
let unreadable = 0;
|
|
262
|
+
for (const f of files.keys()) {
|
|
263
|
+
const u = await transcriptUsage(f, since, windowSeen);
|
|
264
|
+
if (!u.readable) unreadable++;
|
|
265
|
+
files.set(f, u);
|
|
266
|
+
merge(last24h, u.window);
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
const sessions = [];
|
|
270
|
+
for (const s of open) {
|
|
271
|
+
const state = s?.status ?? s?.state ?? null;
|
|
272
|
+
const { run, matchedBy } = matchRun(s?.sessionId, s?.cwd ?? null, null, runs);
|
|
273
|
+
const usage = emptyUsage();
|
|
274
|
+
for (const f of sessionFiles(root, s?.sessionId))
|
|
275
|
+
merge(usage, files.get(f)?.total ?? emptyUsage());
|
|
276
|
+
sessions.push({
|
|
277
|
+
sessionId: typeof s?.sessionId === "string" ? s.sessionId : null,
|
|
278
|
+
kind: typeof s?.kind === "string" ? s.kind : null,
|
|
279
|
+
state,
|
|
280
|
+
project: s?.cwd ? basename(s.cwd) : null,
|
|
281
|
+
startedAt: s?.startedAt ?? null,
|
|
282
|
+
run: run ? { taskId: run.taskId, currentPhase: run.currentPhase, status: run.status } : null,
|
|
283
|
+
matchedBy,
|
|
284
|
+
usage,
|
|
285
|
+
});
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
const accounts = listAccounts({ env });
|
|
289
|
+
return {
|
|
290
|
+
contractVersion: CONTRACT_VERSION,
|
|
291
|
+
generatedAt: new Date(now).toISOString(),
|
|
292
|
+
sessions,
|
|
293
|
+
sessionListReadable: Array.isArray(rows),
|
|
294
|
+
usage: {
|
|
295
|
+
liveSessions: sessions.reduce((acc, s) => merge(acc, { ...s.usage }), emptyUsage()),
|
|
296
|
+
last24h,
|
|
297
|
+
unreadableTranscripts: unreadable,
|
|
298
|
+
},
|
|
299
|
+
accounts: {
|
|
300
|
+
github: accounts.map((a) => ({ login: a.login, active: a.active })),
|
|
301
|
+
git: gitIdentity(env),
|
|
302
|
+
},
|
|
303
|
+
credentials: credentialStatus({ env }),
|
|
304
|
+
runs: all ? runs : runs.filter((r) => r.group !== "done" && r.group !== "unknown"),
|
|
305
|
+
runCounts: runs.reduce((acc, r) => ({ ...acc, [r.group]: (acc[r.group] ?? 0) + 1 }), {}),
|
|
306
|
+
};
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
const n = (x) =>
|
|
310
|
+
x >= 1e9
|
|
311
|
+
? `${(x / 1e9).toFixed(1)}B`
|
|
312
|
+
: x >= 1e6
|
|
313
|
+
? `${(x / 1e6).toFixed(1)}M`
|
|
314
|
+
: x >= 1e3
|
|
315
|
+
? `${(x / 1e3).toFixed(1)}k`
|
|
316
|
+
: String(x);
|
|
317
|
+
const usageLine = (u) =>
|
|
318
|
+
`in ${n(u.in)} out ${n(u.out)} cache read ${n(u.cacheRead)} cache write ${n(u.cacheWrite)} ~$${u.usd.toFixed(2)}` +
|
|
319
|
+
(u.unpriced ? ` (${u.unpriced} unpriced)` : "");
|
|
320
|
+
|
|
321
|
+
export function renderHuman(doc) {
|
|
322
|
+
const out = [];
|
|
323
|
+
const busy = doc.sessions.filter((s) => s.state === "busy" || s.state === "running").length;
|
|
324
|
+
out.push(`Live sessions (${doc.sessions.length}, ${busy} busy)`);
|
|
325
|
+
if (!doc.sessionListReadable)
|
|
326
|
+
out.push(" the session list could not be read (claude agents --json)");
|
|
327
|
+
for (const s of doc.sessions) {
|
|
328
|
+
const run = s.run ? `${s.run.taskId} ${phaseCell(s.run.currentPhase)}` : "no run";
|
|
329
|
+
out.push(
|
|
330
|
+
` ${(s.sessionId ?? "-").slice(0, 8)} ${(s.kind ?? "-").padEnd(11)} ${(s.state ?? "-").padEnd(7)} ` +
|
|
331
|
+
`${(s.project ?? "-").padEnd(24).slice(0, 24)} ${run.padEnd(22)} ~$${s.usage.usd.toFixed(2)}`,
|
|
332
|
+
);
|
|
333
|
+
}
|
|
334
|
+
out.push("");
|
|
335
|
+
out.push("Tokens");
|
|
336
|
+
out.push(` live sessions ${usageLine(doc.usage.liveSessions)}`);
|
|
337
|
+
out.push(` last 24h ${usageLine(doc.usage.last24h)}`);
|
|
338
|
+
if (doc.usage.unreadableTranscripts)
|
|
339
|
+
out.push(
|
|
340
|
+
` ${doc.usage.unreadableTranscripts} transcript(s) could not be read; the totals leave them out`,
|
|
341
|
+
);
|
|
342
|
+
out.push("");
|
|
343
|
+
out.push("Accounts");
|
|
344
|
+
const gh = doc.accounts.github;
|
|
345
|
+
out.push(
|
|
346
|
+
` github ${gh.length ? gh.map((a) => (a.active ? `${a.login} (active)` : a.login)).join(", ") : "none logged in"}`,
|
|
347
|
+
);
|
|
348
|
+
if (doc.accounts.git)
|
|
349
|
+
out.push(` git ${doc.accounts.git.name} <${doc.accounts.git.email ?? "-"}>`);
|
|
350
|
+
out.push("");
|
|
351
|
+
const c = doc.credentials;
|
|
352
|
+
if (!c.readable) out.push("Credentials could not be read");
|
|
353
|
+
else {
|
|
354
|
+
const missing = c.items.filter((i) => !i.registered).map((i) => i.name);
|
|
355
|
+
out.push(`Credentials ${c.items.length - missing.length}/${c.items.length} registered`);
|
|
356
|
+
out.push(
|
|
357
|
+
` ${
|
|
358
|
+
c.items
|
|
359
|
+
.filter((i) => i.registered)
|
|
360
|
+
.map((i) => i.name)
|
|
361
|
+
.join(", ") || "-"
|
|
362
|
+
}`,
|
|
363
|
+
);
|
|
364
|
+
if (missing.length) out.push(` missing: ${missing.join(", ")}`);
|
|
365
|
+
}
|
|
366
|
+
out.push("");
|
|
367
|
+
const counts = Object.entries(doc.runCounts)
|
|
368
|
+
.map(([g, k]) => `${g} ${k}`)
|
|
369
|
+
.join(", ");
|
|
370
|
+
out.push(`Runs (${counts || "none"}) - runs-index.mjs lists them`);
|
|
371
|
+
const driven = new Set(doc.sessions.map((x) => x.run?.taskId).filter(Boolean));
|
|
372
|
+
for (const r of doc.runs.filter((x) => x.group === "waiting" || driven.has(x.taskId))) {
|
|
373
|
+
out.push(
|
|
374
|
+
` ${r.taskId.padEnd(30).slice(0, 30)} ${phaseCell(r.currentPhase).padStart(5)} ${r.group}`,
|
|
375
|
+
);
|
|
376
|
+
}
|
|
377
|
+
return out.join("\n");
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
async function main() {
|
|
381
|
+
const args = process.argv.slice(2);
|
|
382
|
+
const flags = { json: false, all: false };
|
|
383
|
+
for (const a of args) {
|
|
384
|
+
if (a === "--json") flags.json = true;
|
|
385
|
+
else if (a === "--all") flags.all = true;
|
|
386
|
+
else if (a === "-h" || a === "--help") {
|
|
387
|
+
process.stdout.write("Usage: status-overview.mjs [--json] [--all]\n");
|
|
388
|
+
return 0;
|
|
389
|
+
} else {
|
|
390
|
+
process.stderr.write(`status-overview: unknown argument ${a}\n`);
|
|
391
|
+
return 2;
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
const doc = await statusOverview({ all: flags.all });
|
|
395
|
+
process.stdout.write((flags.json ? JSON.stringify(doc, null, 2) : renderHuman(doc)) + "\n");
|
|
396
|
+
return 0;
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
if (invokedDirectly(import.meta.url)) {
|
|
400
|
+
runMain("status-overview", main);
|
|
401
|
+
}
|