@os-eco/overstory-cli 0.7.4 → 0.7.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +10 -8
- package/package.json +1 -1
- package/src/commands/agents.ts +21 -3
- package/src/commands/completions.ts +7 -1
- package/src/commands/coordinator.test.ts +3 -1
- package/src/commands/coordinator.ts +6 -3
- package/src/commands/costs.test.ts +45 -2
- package/src/commands/costs.ts +42 -13
- package/src/commands/doctor.ts +3 -1
- package/src/commands/init.test.ts +366 -27
- package/src/commands/init.ts +194 -2
- package/src/commands/monitor.ts +4 -3
- package/src/commands/supervisor.ts +4 -3
- package/src/doctor/providers.test.ts +373 -0
- package/src/doctor/providers.ts +250 -0
- package/src/doctor/types.ts +2 -1
- package/src/e2e/init-sling-lifecycle.test.ts +12 -7
- package/src/index.ts +11 -2
- package/src/metrics/pricing.ts +57 -2
- package/src/metrics/store.test.ts +38 -0
- package/src/metrics/store.ts +10 -0
- package/src/metrics/transcript.test.ts +84 -2
- package/src/metrics/transcript.ts +1 -1
- package/src/runtimes/claude.test.ts +40 -0
- package/src/runtimes/claude.ts +8 -1
- package/src/runtimes/copilot.test.ts +507 -0
- package/src/runtimes/copilot.ts +226 -0
- package/src/runtimes/pi.test.ts +28 -0
- package/src/runtimes/pi.ts +5 -1
- package/src/runtimes/registry.test.ts +20 -0
- package/src/runtimes/registry.ts +2 -0
- package/src/runtimes/types.ts +2 -0
package/README.md
CHANGED
|
@@ -6,7 +6,7 @@ Multi-agent orchestration for AI coding agents.
|
|
|
6
6
|
[](https://github.com/jayminwest/overstory/actions/workflows/ci.yml)
|
|
7
7
|
[](LICENSE)
|
|
8
8
|
|
|
9
|
-
Overstory turns a single coding session into a multi-agent team by spawning worker agents in git worktrees via tmux, coordinating them through a custom SQLite mail system, and merging their work back with tiered conflict resolution. A pluggable `AgentRuntime` interface lets you swap between runtimes — Claude Code, [Pi](https://github.com/
|
|
9
|
+
Overstory turns a single coding session into a multi-agent team by spawning worker agents in git worktrees via tmux, coordinating them through a custom SQLite mail system, and merging their work back with tiered conflict resolution. A pluggable `AgentRuntime` interface lets you swap between runtimes — Claude Code, [Pi](https://github.com/badlogic/pi-mono/tree/main/packages/coding-agent), or your own adapter.
|
|
10
10
|
|
|
11
11
|
> **Warning: Agent swarms are not a universal solution.** Do not deploy Overstory without understanding the risks of multi-agent orchestration — compounding error rates, cost amplification, debugging complexity, and merge conflicts are the normal case, not edge cases. Read [STEELMAN.md](STEELMAN.md) for a full risk analysis and the [Agentic Engineering Book](https://github.com/jayminwest/agentic-engineering-book) ([web version](https://jayminwest.com/agentic-engineering-book)) before using this tool in production.
|
|
12
12
|
|
|
@@ -15,7 +15,8 @@ Overstory turns a single coding session into a multi-agent team by spawning work
|
|
|
15
15
|
Requires [Bun](https://bun.sh) v1.0+, git, and tmux. At least one supported agent runtime must be installed:
|
|
16
16
|
|
|
17
17
|
- [Claude Code](https://docs.anthropic.com/en/docs/claude-code) (`claude` CLI)
|
|
18
|
-
- [Pi](https://github.com/
|
|
18
|
+
- [Pi](https://github.com/badlogic/pi-mono/tree/main/packages/coding-agent) (`pi` CLI)
|
|
19
|
+
- [GitHub Copilot](https://github.com/features/copilot) (`copilot` CLI)
|
|
19
20
|
|
|
20
21
|
```bash
|
|
21
22
|
bun install -g @os-eco/overstory-cli
|
|
@@ -77,7 +78,7 @@ Every command supports `--json` where noted. Global flags: `-q`/`--quiet`, `--ti
|
|
|
77
78
|
|
|
78
79
|
| Command | Description |
|
|
79
80
|
|---------|-------------|
|
|
80
|
-
| `ov init` | Initialize `.overstory/`
|
|
81
|
+
| `ov init` | Initialize `.overstory/` and bootstrap os-eco tools (`--yes`, `--name`, `--tools`, `--skip-mulch`, `--skip-seeds`, `--skip-canopy`, `--skip-onboard`, `--json`) |
|
|
81
82
|
| `ov sling <task-id>` | Spawn a worker agent (`--capability`, `--name`, `--spec`, `--files`, `--parent`, `--depth`, `--skip-scout`, `--skip-review`, `--max-agents`, `--dispatch-max-agents`, `--skip-task-check`, `--no-scout-check`, `--runtime`, `--json`) |
|
|
82
83
|
| `ov stop <agent-name>` | Terminate a running agent (`--clean-worktree`, `--json`) |
|
|
83
84
|
| `ov prime` | Load context for orchestrator/agent (`--agent`, `--compact`) |
|
|
@@ -132,7 +133,7 @@ Every command supports `--json` where noted. Global flags: `-q`/`--quiet`, `--ti
|
|
|
132
133
|
| `ov replay` | Interleaved chronological replay (`--run`, `--agent`, `--since`, `--until`, `--limit`, `--json`) |
|
|
133
134
|
| `ov feed` | Unified real-time event stream (`--follow`, `--interval`, `--agent`, `--run`, `--json`) |
|
|
134
135
|
| `ov logs` | Query NDJSON logs across agents (`--agent`, `--level`, `--since`, `--until`, `--follow`, `--json`) |
|
|
135
|
-
| `ov costs` | Token/cost analysis and breakdown (`--live`, `--self`, `--agent`, `--run`, `--by-capability`, `--last`, `--json`) |
|
|
136
|
+
| `ov costs` | Token/cost analysis and breakdown (`--live`, `--self`, `--agent`, `--run`, `--bead`, `--by-capability`, `--last`, `--json`) |
|
|
136
137
|
| `ov metrics` | Show session metrics (`--last`, `--json`) |
|
|
137
138
|
| `ov run list` | List orchestration runs (`--last`, `--json`) |
|
|
138
139
|
| `ov run show <id>` | Show run details |
|
|
@@ -153,7 +154,7 @@ Every command supports `--json` where noted. Global flags: `-q`/`--quiet`, `--ti
|
|
|
153
154
|
| `ov monitor status` | Show monitor state |
|
|
154
155
|
| `ov log <event>` | Log a hook event (`--agent`) |
|
|
155
156
|
| `ov clean` | Clean up worktrees, sessions, artifacts (`--completed`, `--all`, `--run`) |
|
|
156
|
-
| `ov doctor` | Run health checks on overstory setup (`--category`, `--fix`, `--json`) |
|
|
157
|
+
| `ov doctor` | Run health checks on overstory setup — 11 categories (`--category`, `--fix`, `--json`) |
|
|
157
158
|
| `ov ecosystem` | Show os-eco tool versions and health (`--json`) |
|
|
158
159
|
| `ov upgrade` | Upgrade overstory to latest npm version (`--check`, `--all`, `--json`) |
|
|
159
160
|
| `ov agents discover` | Discover agents by capability/state/parent (`--capability`, `--state`, `--parent`, `--json`) |
|
|
@@ -171,6 +172,7 @@ Overstory is runtime-agnostic. The `AgentRuntime` interface (`src/runtimes/types
|
|
|
171
172
|
|---------|-----|-----------------|--------|
|
|
172
173
|
| Claude Code | `claude` | `settings.local.json` hooks | Stable |
|
|
173
174
|
| Pi | `pi` | `.pi/extensions/` guard extension | Active development |
|
|
175
|
+
| Copilot | `copilot` | (none — `--allow-all-tools`) | Active development |
|
|
174
176
|
|
|
175
177
|
## How It Works
|
|
176
178
|
|
|
@@ -240,7 +242,7 @@ overstory/
|
|
|
240
242
|
run.ts Orchestration run lifecycle
|
|
241
243
|
trace.ts Agent/task timeline viewing
|
|
242
244
|
clean.ts Worktree/session cleanup
|
|
243
|
-
doctor.ts Health check runner (
|
|
245
|
+
doctor.ts Health check runner (11 check modules)
|
|
244
246
|
inspect.ts Deep per-agent inspection
|
|
245
247
|
spec.ts Task spec management
|
|
246
248
|
errors.ts Aggregated error view
|
|
@@ -265,9 +267,9 @@ overstory/
|
|
|
265
267
|
watchdog/ Tiered health monitoring (daemon, triage, health)
|
|
266
268
|
logging/ Multi-format logger + sanitizer + reporter + color control + shared theme/format
|
|
267
269
|
metrics/ SQLite metrics + pricing + transcript parsing
|
|
268
|
-
doctor/ Health check modules (
|
|
270
|
+
doctor/ Health check modules (11 checks)
|
|
269
271
|
insights/ Session insight analyzer for auto-expertise
|
|
270
|
-
runtimes/ AgentRuntime abstraction (registry + adapters: Claude, Pi)
|
|
272
|
+
runtimes/ AgentRuntime abstraction (registry + adapters: Claude, Pi, Copilot)
|
|
271
273
|
tracker/ Pluggable task tracker (beads + seeds backends)
|
|
272
274
|
mulch/ mulch client (programmatic API + CLI wrapper)
|
|
273
275
|
e2e/ End-to-end lifecycle tests
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@os-eco/overstory-cli",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.6",
|
|
4
4
|
"description": "Multi-agent orchestration for AI coding agents — spawn workers in git worktrees via tmux, coordinate through SQLite mail, merge with tiered conflict resolution. Pluggable runtime adapters for Claude Code, Pi, and more.",
|
|
5
5
|
"author": "Jaymin West",
|
|
6
6
|
"license": "MIT",
|
package/src/commands/agents.ts
CHANGED
|
@@ -10,6 +10,7 @@ import { loadConfig } from "../config.ts";
|
|
|
10
10
|
import { ValidationError } from "../errors.ts";
|
|
11
11
|
import { jsonOutput } from "../json.ts";
|
|
12
12
|
import { accent, color } from "../logging/color.ts";
|
|
13
|
+
import { getRuntime } from "../runtimes/registry.ts";
|
|
13
14
|
import { openSessionStore } from "../sessions/compat.ts";
|
|
14
15
|
import { type AgentSession, SUPPORTED_CAPABILITIES } from "../types.ts";
|
|
15
16
|
|
|
@@ -41,12 +42,19 @@ const KNOWN_INSTRUCTION_PATHS = [
|
|
|
41
42
|
* or can't be read.
|
|
42
43
|
*
|
|
43
44
|
* @param worktreePath - Absolute path to the agent's worktree
|
|
45
|
+
* @param runtimeInstructionPath - Optional runtime-specific instruction path to try first
|
|
44
46
|
* @returns Array of file paths (relative to worktree root)
|
|
45
47
|
*/
|
|
46
|
-
export async function extractFileScope(
|
|
48
|
+
export async function extractFileScope(
|
|
49
|
+
worktreePath: string,
|
|
50
|
+
runtimeInstructionPath?: string,
|
|
51
|
+
): Promise<string[]> {
|
|
47
52
|
try {
|
|
48
53
|
let content: string | null = null;
|
|
49
|
-
|
|
54
|
+
const pathsToTry = runtimeInstructionPath
|
|
55
|
+
? [runtimeInstructionPath, ...KNOWN_INSTRUCTION_PATHS]
|
|
56
|
+
: KNOWN_INSTRUCTION_PATHS;
|
|
57
|
+
for (const relPath of pathsToTry) {
|
|
50
58
|
const overlayPath = join(worktreePath, relPath);
|
|
51
59
|
const overlayFile = Bun.file(overlayPath);
|
|
52
60
|
if (await overlayFile.exists()) {
|
|
@@ -112,6 +120,16 @@ export async function discoverAgents(
|
|
|
112
120
|
const overstoryDir = join(root, ".overstory");
|
|
113
121
|
const { store } = openSessionStore(overstoryDir);
|
|
114
122
|
|
|
123
|
+
// Resolve runtime instruction path from config; fall back gracefully if config is absent.
|
|
124
|
+
let runtimeInstructionPath: string | undefined;
|
|
125
|
+
try {
|
|
126
|
+
const config = await loadConfig(root);
|
|
127
|
+
const runtime = getRuntime(undefined, config);
|
|
128
|
+
runtimeInstructionPath = runtime.instructionPath;
|
|
129
|
+
} catch {
|
|
130
|
+
// Config may not exist in all contexts; KNOWN_INSTRUCTION_PATHS will be used as fallback.
|
|
131
|
+
}
|
|
132
|
+
|
|
115
133
|
try {
|
|
116
134
|
const sessions: AgentSession[] = opts?.includeAll ? store.getAll() : store.getActive();
|
|
117
135
|
|
|
@@ -124,7 +142,7 @@ export async function discoverAgents(
|
|
|
124
142
|
// Extract file scopes for each agent
|
|
125
143
|
const agents: DiscoveredAgent[] = await Promise.all(
|
|
126
144
|
filteredSessions.map(async (session) => {
|
|
127
|
-
const fileScope = await extractFileScope(session.worktreePath);
|
|
145
|
+
const fileScope = await extractFileScope(session.worktreePath, runtimeInstructionPath);
|
|
128
146
|
return {
|
|
129
147
|
agentName: session.agentName,
|
|
130
148
|
capability: session.capability,
|
|
@@ -55,12 +55,18 @@ export const COMMANDS: readonly CommandDef[] = [
|
|
|
55
55
|
},
|
|
56
56
|
{
|
|
57
57
|
name: "init",
|
|
58
|
-
desc: "Initialize .overstory/
|
|
58
|
+
desc: "Initialize .overstory/ and bootstrap os-eco ecosystem tools",
|
|
59
59
|
flags: [
|
|
60
60
|
{ name: "--force", desc: "Overwrite existing configuration" },
|
|
61
61
|
{ name: "--yes", desc: "Accept all defaults without prompting" },
|
|
62
62
|
{ name: "-y", desc: "Alias for --yes" },
|
|
63
63
|
{ name: "--name", desc: "Project name", takesValue: true },
|
|
64
|
+
{ name: "--tools", desc: "Comma-separated list of tools to bootstrap", takesValue: true },
|
|
65
|
+
{ name: "--skip-mulch", desc: "Skip mulch bootstrap" },
|
|
66
|
+
{ name: "--skip-seeds", desc: "Skip seeds bootstrap" },
|
|
67
|
+
{ name: "--skip-canopy", desc: "Skip canopy bootstrap" },
|
|
68
|
+
{ name: "--skip-onboard", desc: "Skip CLAUDE.md onboarding step" },
|
|
69
|
+
{ name: "--json", desc: "Output result as JSON" },
|
|
64
70
|
{ name: "--help", desc: "Show help" },
|
|
65
71
|
],
|
|
66
72
|
},
|
|
@@ -540,7 +540,9 @@ describe("startCoordinator", () => {
|
|
|
540
540
|
expect(calls.createSession).toHaveLength(1);
|
|
541
541
|
const cmd = calls.createSession[0]?.command ?? "";
|
|
542
542
|
expect(cmd).toContain("--append-system-prompt");
|
|
543
|
-
|
|
543
|
+
// File path is passed via $(cat ...) instead of inlining content (overstory#45)
|
|
544
|
+
expect(cmd).toContain("$(cat '");
|
|
545
|
+
expect(cmd).toContain("agent-defs/coordinator.md");
|
|
544
546
|
});
|
|
545
547
|
|
|
546
548
|
test("reads model from manifest instead of hardcoding", async () => {
|
|
@@ -363,17 +363,20 @@ async function startCoordinator(
|
|
|
363
363
|
// Inject the coordinator base definition via --append-system-prompt so the
|
|
364
364
|
// coordinator knows its role, hierarchy rules, and delegation patterns
|
|
365
365
|
// (overstory-gaio, overstory-0kwf).
|
|
366
|
+
// Pass the file path (not content) so the shell inside the tmux pane reads
|
|
367
|
+
// it via $(cat ...) — avoids tmux IPC "command too long" errors with large
|
|
368
|
+
// agent definitions (overstory#45).
|
|
366
369
|
const agentDefPath = join(projectRoot, ".overstory", "agent-defs", "coordinator.md");
|
|
367
370
|
const agentDefFile = Bun.file(agentDefPath);
|
|
368
|
-
let
|
|
371
|
+
let appendSystemPromptFile: string | undefined;
|
|
369
372
|
if (await agentDefFile.exists()) {
|
|
370
|
-
|
|
373
|
+
appendSystemPromptFile = agentDefPath;
|
|
371
374
|
}
|
|
372
375
|
const spawnCmd = runtime.buildSpawnCommand({
|
|
373
376
|
model: resolvedModel.model,
|
|
374
377
|
permissionMode: "bypass",
|
|
375
378
|
cwd: projectRoot,
|
|
376
|
-
|
|
379
|
+
appendSystemPromptFile,
|
|
377
380
|
env: {
|
|
378
381
|
...runtime.buildEnv(resolvedModel),
|
|
379
382
|
OVERSTORY_AGENT_NAME: COORDINATOR_NAME,
|
|
@@ -1023,6 +1023,48 @@ describe("costsCommand", () => {
|
|
|
1023
1023
|
});
|
|
1024
1024
|
});
|
|
1025
1025
|
|
|
1026
|
+
// === --bead filter ===
|
|
1027
|
+
|
|
1028
|
+
describe("--bead filter", () => {
|
|
1029
|
+
test("--bead filters by task ID (JSON)", async () => {
|
|
1030
|
+
const dbPath = join(tempDir, ".overstory", "metrics.db");
|
|
1031
|
+
const store = createMetricsStore(dbPath);
|
|
1032
|
+
store.recordSession(makeMetrics({ agentName: "builder-1", taskId: "task-A" }));
|
|
1033
|
+
store.recordSession(makeMetrics({ agentName: "builder-2", taskId: "task-A" }));
|
|
1034
|
+
store.recordSession(
|
|
1035
|
+
makeMetrics({ agentName: "scout-1", taskId: "task-B", capability: "scout" }),
|
|
1036
|
+
);
|
|
1037
|
+
store.close();
|
|
1038
|
+
|
|
1039
|
+
await costsCommand(["--json", "--bead", "task-A"]);
|
|
1040
|
+
const out = output();
|
|
1041
|
+
|
|
1042
|
+
const parsed = JSON.parse(out.trim()) as { sessions: Record<string, unknown>[] };
|
|
1043
|
+
expect(parsed.sessions).toHaveLength(2);
|
|
1044
|
+
expect(parsed.sessions.every((s) => s.taskId === "task-A")).toBe(true);
|
|
1045
|
+
});
|
|
1046
|
+
|
|
1047
|
+
test("--bead returns empty for unknown task", async () => {
|
|
1048
|
+
const dbPath = join(tempDir, ".overstory", "metrics.db");
|
|
1049
|
+
const store = createMetricsStore(dbPath);
|
|
1050
|
+
store.recordSession(makeMetrics({ agentName: "builder-1", taskId: "task-A" }));
|
|
1051
|
+
store.close();
|
|
1052
|
+
|
|
1053
|
+
await costsCommand(["--json", "--bead", "nonexistent"]);
|
|
1054
|
+
const out = output();
|
|
1055
|
+
|
|
1056
|
+
const parsed = JSON.parse(out.trim()) as { sessions: unknown[] };
|
|
1057
|
+
expect(parsed.sessions).toEqual([]);
|
|
1058
|
+
});
|
|
1059
|
+
|
|
1060
|
+
test("--bead appears in help text", async () => {
|
|
1061
|
+
await costsCommand(["--help"]);
|
|
1062
|
+
const out = output();
|
|
1063
|
+
|
|
1064
|
+
expect(out).toContain("--bead");
|
|
1065
|
+
});
|
|
1066
|
+
});
|
|
1067
|
+
|
|
1026
1068
|
// === --self flag ===
|
|
1027
1069
|
|
|
1028
1070
|
describe("--self flag", () => {
|
|
@@ -1111,7 +1153,7 @@ describe("costsCommand", () => {
|
|
|
1111
1153
|
await costsCommand(["--self"]);
|
|
1112
1154
|
const out = output();
|
|
1113
1155
|
|
|
1114
|
-
expect(out).toContain("No
|
|
1156
|
+
expect(out).toContain("No transcript found");
|
|
1115
1157
|
});
|
|
1116
1158
|
|
|
1117
1159
|
test("--self --json outputs error JSON when no transcript found", async () => {
|
|
@@ -1122,7 +1164,8 @@ describe("costsCommand", () => {
|
|
|
1122
1164
|
const out = output();
|
|
1123
1165
|
|
|
1124
1166
|
const parsed = JSON.parse(out.trim()) as Record<string, unknown>;
|
|
1125
|
-
expect(parsed.error).toBe("
|
|
1167
|
+
expect(typeof parsed.error).toBe("string");
|
|
1168
|
+
expect(parsed.error as string).toContain("No transcript found");
|
|
1126
1169
|
});
|
|
1127
1170
|
|
|
1128
1171
|
test("--self in help text", async () => {
|
package/src/commands/costs.ts
CHANGED
|
@@ -16,6 +16,7 @@ import { color } from "../logging/color.ts";
|
|
|
16
16
|
import { renderHeader, separator } from "../logging/theme.ts";
|
|
17
17
|
import { createMetricsStore } from "../metrics/store.ts";
|
|
18
18
|
import { estimateCost, parseTranscriptUsage } from "../metrics/transcript.ts";
|
|
19
|
+
import { getRuntime } from "../runtimes/registry.ts";
|
|
19
20
|
import { openSessionStore } from "../sessions/compat.ts";
|
|
20
21
|
import type { SessionMetrics } from "../types.ts";
|
|
21
22
|
|
|
@@ -43,24 +44,45 @@ function padLeft(str: string, width: number): string {
|
|
|
43
44
|
}
|
|
44
45
|
|
|
45
46
|
/**
|
|
46
|
-
*
|
|
47
|
-
*
|
|
48
|
-
* Scans ~/.claude/projects/{project-key}/ for JSONL files and returns
|
|
49
|
-
* the most recently modified one, corresponding to the current orchestrator session.
|
|
47
|
+
* Resolve the transcript directory for a given runtime and project root.
|
|
50
48
|
*
|
|
49
|
+
* @param runtimeId - The runtime identifier (e.g. "claude")
|
|
51
50
|
* @param projectRoot - Absolute path to the project root
|
|
52
|
-
* @returns Absolute path to the
|
|
51
|
+
* @returns Absolute path to the transcript directory, or null if not supported
|
|
53
52
|
*/
|
|
54
|
-
|
|
53
|
+
function getTranscriptDir(runtimeId: string, projectRoot: string): string | null {
|
|
55
54
|
const homeDir = process.env.HOME ?? "";
|
|
56
55
|
if (homeDir.length === 0) return null;
|
|
56
|
+
switch (runtimeId) {
|
|
57
|
+
case "claude": {
|
|
58
|
+
const projectKey = projectRoot.replace(/\//g, "-");
|
|
59
|
+
return join(homeDir, ".claude", "projects", projectKey);
|
|
60
|
+
}
|
|
61
|
+
default:
|
|
62
|
+
return null;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
57
65
|
|
|
58
|
-
|
|
59
|
-
|
|
66
|
+
/**
|
|
67
|
+
* Discover the orchestrator's transcript JSONL file for the given runtime.
|
|
68
|
+
*
|
|
69
|
+
* Scans the runtime-specific transcript directory for JSONL files and returns
|
|
70
|
+
* the most recently modified one, corresponding to the current orchestrator session.
|
|
71
|
+
*
|
|
72
|
+
* @param runtimeId - The runtime identifier (e.g. "claude")
|
|
73
|
+
* @param projectRoot - Absolute path to the project root
|
|
74
|
+
* @returns Absolute path to the most recent transcript, or null if none found
|
|
75
|
+
*/
|
|
76
|
+
async function discoverOrchestratorTranscript(
|
|
77
|
+
runtimeId: string,
|
|
78
|
+
projectRoot: string,
|
|
79
|
+
): Promise<string | null> {
|
|
80
|
+
const transcriptDir = getTranscriptDir(runtimeId, projectRoot);
|
|
81
|
+
if (transcriptDir === null) return null;
|
|
60
82
|
|
|
61
83
|
let entries: string[];
|
|
62
84
|
try {
|
|
63
|
-
entries = await readdir(
|
|
85
|
+
entries = await readdir(transcriptDir);
|
|
64
86
|
} catch {
|
|
65
87
|
return null;
|
|
66
88
|
}
|
|
@@ -72,7 +94,7 @@ async function discoverOrchestratorTranscript(projectRoot: string): Promise<stri
|
|
|
72
94
|
let bestMtime = 0;
|
|
73
95
|
|
|
74
96
|
for (const file of jsonlFiles) {
|
|
75
|
-
const filePath = join(
|
|
97
|
+
const filePath = join(transcriptDir, file);
|
|
76
98
|
try {
|
|
77
99
|
const fileStat = await stat(filePath);
|
|
78
100
|
if (fileStat.mtimeMs > bestMtime) {
|
|
@@ -236,6 +258,7 @@ interface CostsOpts {
|
|
|
236
258
|
byCapability?: boolean;
|
|
237
259
|
agent?: string;
|
|
238
260
|
run?: string;
|
|
261
|
+
bead?: string;
|
|
239
262
|
last?: string;
|
|
240
263
|
json?: boolean;
|
|
241
264
|
}
|
|
@@ -247,6 +270,7 @@ async function executeCosts(opts: CostsOpts): Promise<void> {
|
|
|
247
270
|
const byCapability = opts.byCapability ?? false;
|
|
248
271
|
const agentName = opts.agent;
|
|
249
272
|
const runId = opts.run;
|
|
273
|
+
const beadId = opts.bead;
|
|
250
274
|
const lastStr = opts.last;
|
|
251
275
|
|
|
252
276
|
if (lastStr !== undefined) {
|
|
@@ -267,13 +291,15 @@ async function executeCosts(opts: CostsOpts): Promise<void> {
|
|
|
267
291
|
|
|
268
292
|
// Handle --self flag (early return for self-scan)
|
|
269
293
|
if (self) {
|
|
270
|
-
const
|
|
294
|
+
const runtime = getRuntime(undefined, config);
|
|
295
|
+
const transcriptPath = await discoverOrchestratorTranscript(runtime.id, config.project.root);
|
|
271
296
|
if (!transcriptPath) {
|
|
272
297
|
if (json) {
|
|
273
|
-
jsonError("costs",
|
|
298
|
+
jsonError("costs", `No transcript found for runtime '${runtime.id}'`);
|
|
274
299
|
} else {
|
|
275
300
|
process.stdout.write(
|
|
276
|
-
|
|
301
|
+
`No transcript found for runtime '${runtime.id}'.\n` +
|
|
302
|
+
"Transcript discovery may not be supported for this runtime.\n",
|
|
277
303
|
);
|
|
278
304
|
}
|
|
279
305
|
return;
|
|
@@ -521,6 +547,8 @@ async function executeCosts(opts: CostsOpts): Promise<void> {
|
|
|
521
547
|
sessions = metricsStore.getSessionsByAgent(agentName);
|
|
522
548
|
} else if (runId !== undefined) {
|
|
523
549
|
sessions = metricsStore.getSessionsByRun(runId);
|
|
550
|
+
} else if (beadId !== undefined) {
|
|
551
|
+
sessions = metricsStore.getSessionsByTask(beadId);
|
|
524
552
|
} else {
|
|
525
553
|
sessions = metricsStore.getRecentSessions(last);
|
|
526
554
|
}
|
|
@@ -559,6 +587,7 @@ export function createCostsCommand(): Command {
|
|
|
559
587
|
.option("--self", "Show cost for the current orchestrator session")
|
|
560
588
|
.option("--agent <name>", "Filter by agent name")
|
|
561
589
|
.option("--run <id>", "Filter by run ID")
|
|
590
|
+
.option("--bead <id>", "Show cost breakdown for a specific task/bead")
|
|
562
591
|
.option("--by-capability", "Group results by capability with subtotals")
|
|
563
592
|
.option("--last <n>", "Number of recent sessions (default: 20)")
|
|
564
593
|
.option("--json", "Output as JSON")
|
package/src/commands/doctor.ts
CHANGED
|
@@ -15,6 +15,7 @@ import { checkDependencies } from "../doctor/dependencies.ts";
|
|
|
15
15
|
import { checkEcosystem } from "../doctor/ecosystem.ts";
|
|
16
16
|
import { checkLogs } from "../doctor/logs.ts";
|
|
17
17
|
import { checkMergeQueue } from "../doctor/merge-queue.ts";
|
|
18
|
+
import { checkProviders } from "../doctor/providers.ts";
|
|
18
19
|
import { checkStructure } from "../doctor/structure.ts";
|
|
19
20
|
import type { DoctorCategory, DoctorCheck, DoctorCheckFn } from "../doctor/types.ts";
|
|
20
21
|
import { checkVersion } from "../doctor/version.ts";
|
|
@@ -35,6 +36,7 @@ const ALL_CHECKS: Array<{ category: DoctorCategory; fn: DoctorCheckFn }> = [
|
|
|
35
36
|
{ category: "logs", fn: checkLogs },
|
|
36
37
|
{ category: "version", fn: checkVersion },
|
|
37
38
|
{ category: "ecosystem", fn: checkEcosystem },
|
|
39
|
+
{ category: "providers", fn: checkProviders },
|
|
38
40
|
];
|
|
39
41
|
|
|
40
42
|
/**
|
|
@@ -166,7 +168,7 @@ export function createDoctorCommand(options?: DoctorCommandOptions): Command {
|
|
|
166
168
|
.option("--fix", "Attempt to auto-fix issues")
|
|
167
169
|
.addHelpText(
|
|
168
170
|
"after",
|
|
169
|
-
"\nCategories: dependencies, structure, config, databases, consistency, agents, merge, logs, version, ecosystem",
|
|
171
|
+
"\nCategories: dependencies, structure, config, databases, consistency, agents, merge, logs, version, ecosystem, providers",
|
|
170
172
|
)
|
|
171
173
|
.action(
|
|
172
174
|
async (opts: { json?: boolean; verbose?: boolean; category?: string; fix?: boolean }) => {
|