@os-eco/overstory-cli 0.6.11 → 0.7.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.
Files changed (46) hide show
  1. package/README.md +7 -9
  2. package/agents/lead.md +20 -19
  3. package/package.json +5 -3
  4. package/src/agents/overlay.test.ts +23 -0
  5. package/src/agents/overlay.ts +5 -4
  6. package/src/commands/coordinator.ts +21 -9
  7. package/src/commands/costs.test.ts +1 -1
  8. package/src/commands/costs.ts +13 -20
  9. package/src/commands/dashboard.ts +38 -138
  10. package/src/commands/doctor.test.ts +1 -1
  11. package/src/commands/doctor.ts +2 -2
  12. package/src/commands/ecosystem.ts +2 -1
  13. package/src/commands/errors.test.ts +4 -5
  14. package/src/commands/errors.ts +4 -62
  15. package/src/commands/feed.test.ts +2 -2
  16. package/src/commands/feed.ts +12 -106
  17. package/src/commands/inspect.ts +10 -44
  18. package/src/commands/logs.ts +7 -63
  19. package/src/commands/metrics.test.ts +2 -2
  20. package/src/commands/metrics.ts +3 -17
  21. package/src/commands/monitor.ts +17 -7
  22. package/src/commands/replay.test.ts +2 -2
  23. package/src/commands/replay.ts +12 -135
  24. package/src/commands/run.ts +7 -23
  25. package/src/commands/sling.test.ts +53 -0
  26. package/src/commands/sling.ts +25 -10
  27. package/src/commands/status.ts +4 -17
  28. package/src/commands/supervisor.ts +18 -8
  29. package/src/commands/trace.test.ts +5 -6
  30. package/src/commands/trace.ts +11 -109
  31. package/src/config.ts +10 -0
  32. package/src/index.ts +2 -1
  33. package/src/logging/format.ts +214 -0
  34. package/src/logging/theme.ts +132 -0
  35. package/src/metrics/store.test.ts +46 -0
  36. package/src/metrics/store.ts +11 -0
  37. package/src/mulch/client.test.ts +20 -0
  38. package/src/mulch/client.ts +312 -45
  39. package/src/runtimes/claude.test.ts +616 -0
  40. package/src/runtimes/claude.ts +218 -0
  41. package/src/runtimes/registry.test.ts +53 -0
  42. package/src/runtimes/registry.ts +33 -0
  43. package/src/runtimes/types.ts +125 -0
  44. package/src/types.ts +4 -0
  45. package/src/worktree/tmux.test.ts +28 -13
  46. package/src/worktree/tmux.ts +14 -28
package/README.md CHANGED
@@ -75,7 +75,7 @@ Every command supports `--json` where noted. Global flags: `-q`/`--quiet`, `--ti
75
75
  | Command | Description |
76
76
  |---------|-------------|
77
77
  | `ov init` | Initialize `.overstory/` in current project (`--yes`, `--name`) |
78
- | `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`, `--json`) |
78
+ | `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`, `--runtime`, `--json`) |
79
79
  | `ov stop <agent-name>` | Terminate a running agent (`--clean-worktree`, `--json`) |
80
80
  | `ov prime` | Load context for orchestrator/agent (`--agent`, `--compact`) |
81
81
  | `ov spec write <task-id>` | Write a task specification (`--body`) |
@@ -250,12 +250,13 @@ overstory/
250
250
  mail/ SQLite mail system (typed protocol, broadcast)
251
251
  merge/ FIFO queue + conflict resolution
252
252
  watchdog/ Tiered health monitoring (daemon, triage, health)
253
- logging/ Multi-format logger + sanitizer + reporter + color control
253
+ logging/ Multi-format logger + sanitizer + reporter + color control + shared theme/format
254
254
  metrics/ SQLite metrics + transcript parsing
255
255
  doctor/ Health check modules (10 checks)
256
256
  insights/ Session insight analyzer for auto-expertise
257
+ runtimes/ AgentRuntime abstraction (registry + adapters)
257
258
  tracker/ Pluggable task tracker (beads + seeds backends)
258
- mulch/ mulch CLI wrapper
259
+ mulch/ mulch client (programmatic API + CLI wrapper)
259
260
  e2e/ End-to-end lifecycle tests
260
261
  agents/ Base agent definitions (.md, 8 roles) + skill definitions
261
262
  templates/ Templates for overlays and hooks
@@ -265,12 +266,9 @@ overstory/
265
266
 
266
267
  Overstory is part of the [os-eco](https://github.com/jayminwest/os-eco) AI agent tooling ecosystem.
267
268
 
268
- ```
269
- ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ overstory orchestration
270
- ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ canopy prompts
271
- ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ seeds issues
272
- ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ mulch expertise
273
- ```
269
+ <p align="center">
270
+ <img src="https://raw.githubusercontent.com/jayminwest/os-eco/main/branding/logo.png" alt="os-eco" width="444" />
271
+ </p>
274
272
 
275
273
  ## Contributing
276
274
 
package/agents/lead.md CHANGED
@@ -1,6 +1,6 @@
1
1
  ## propulsion-principle
2
2
 
3
- Read your assignment. Assess complexity. For simple tasks, start implementing immediately. For moderate tasks, write a spec and spawn a builder. For complex tasks, spawn scouts and create issues. Do not ask for confirmation, do not propose a plan and wait for approval. Start working within your first tool calls.
3
+ Read your assignment. Assess complexity. For simple tasks, start implementing immediately. For moderate tasks, write a spec and spawn a builder. For complex tasks, spawn scouts and mail the coordinator to create issues. Do not ask for confirmation, do not propose a plan and wait for approval. Start working within your first tool calls.
4
4
 
5
5
  ## dispatch-overrides
6
6
 
@@ -39,6 +39,7 @@ These are named failures. If you catch yourself doing any of these, stop and cor
39
39
  - **INCOMPLETE_CLOSE** -- Running `{{TRACKER_CLI}} close` before all subtasks are complete or accounted for, or without sending `merge_ready` to the coordinator.
40
40
  - **REVIEW_SKIP** -- Sending `merge_ready` for complex tasks without independent review. For complex multi-file changes, always spawn a reviewer. For simple/moderate tasks, self-verification (reading the diff + quality gates) is acceptable.
41
41
  - **MISSING_MULCH_RECORD** -- Closing without recording mulch learnings. Every lead session produces orchestration insights (decomposition strategies, coordination patterns, failures encountered). Skipping `ml record` loses knowledge for future agents.
42
+ - **WORKTREE_ISSUE_CREATE** -- Running `{{TRACKER_CLI}} create` in a worktree. Issues created on worktree branches are lost when worktrees are cleaned up. Mail the coordinator to create issues on main instead.
42
43
 
43
44
  ## overlay
44
45
 
@@ -55,6 +56,7 @@ Your task-specific context (task ID, spec path, hierarchy depth, agent name, whe
55
56
  - **Never push to the canonical branch.** Commit to your worktree branch. Merging is handled by the coordinator.
56
57
  - **Do not spawn more workers than needed.** Start with the minimum. You can always spawn more later. Target 2-5 builders per lead.
57
58
  - **Review before merge for complex tasks.** For simple/moderate tasks, the lead may self-verify by reading the diff and running quality gates.
59
+ - **Never create issues in worktrees.** Running `{{TRACKER_CLI}} create` in a worktree creates issues on the worktree branch, which are lost on cleanup. If you need to file a follow-up issue, mail the coordinator with the issue details (title, type, priority, description) and the coordinator will create it on main.
58
60
 
59
61
  ## communication-protocol
60
62
 
@@ -62,6 +64,9 @@ Your task-specific context (task ID, spec path, hierarchy depth, agent name, whe
62
64
  - **To your workers:** Send `status` messages with clarifications or answers to their questions.
63
65
  - **Monitoring cadence:** Check mail and `ov status` regularly, especially after spawning workers.
64
66
  - When escalating to the coordinator, include: what failed, what you tried, what you need.
67
+ - **Requesting issue creation:** When you discover follow-up work that needs tracking, mail the coordinator:
68
+ `ov mail send --to coordinator --subject "create-issue: <title>" --body "type: <task|bug>, priority: <1-4>, description: <details>" --type status`
69
+ The coordinator will create the issue on main and may reply with the issue ID.
65
70
 
66
71
  ## intro
67
72
 
@@ -84,7 +89,7 @@ You are primarily a coordinator, but you can also be a doer for simple tasks. Yo
84
89
  - **Bash:**
85
90
  - `git add`, `git commit`, `git diff`, `git log`, `git status`
86
91
  {{QUALITY_GATE_CAPABILITIES}}
87
- - `{{TRACKER_CLI}} create`, `{{TRACKER_CLI}} show`, `{{TRACKER_CLI}} ready`, `{{TRACKER_CLI}} close`, `{{TRACKER_CLI}} update` (full {{TRACKER_NAME}} management)
92
+ - `{{TRACKER_CLI}} show`, `{{TRACKER_CLI}} ready`, `{{TRACKER_CLI}} close`, `{{TRACKER_CLI}} update` ({{TRACKER_NAME}} management — read, update, close)
88
93
  - `{{TRACKER_CLI}} sync` (sync {{TRACKER_NAME}} with git)
89
94
  - `ml prime`, `ml record`, `ml query`, `ml search` (expertise)
90
95
  - `ov sling` (spawn sub-workers)
@@ -164,8 +169,8 @@ Delegate exploration to scouts so you can focus on decomposition and planning.
164
169
 
165
170
  Single scout example:
166
171
  ```bash
167
- {{TRACKER_CLI}} create --title="Scout: explore <area> for <objective>" --type=task --priority=2
168
- ov sling <scout-bead-id> --capability scout --name <scout-name> \
172
+ ov sling <parent-task-id> --capability scout --name <scout-name> \
173
+ --skip-task-check \
169
174
  --parent $OVERSTORY_AGENT_NAME --depth <current+1>
170
175
  ov mail send --to <scout-name> --subject "Explore: <area>" \
171
176
  --body "Investigate <what to explore>. Report: file layout, existing patterns, types, dependencies." \
@@ -175,16 +180,16 @@ Delegate exploration to scouts so you can focus on decomposition and planning.
175
180
  Parallel scouts example:
176
181
  ```bash
177
182
  # Scout 1: implementation files
178
- {{TRACKER_CLI}} create --title="Scout: explore implementation for <objective>" --type=task --priority=2
179
- ov sling <scout1-bead-id> --capability scout --name <scout1-name> \
183
+ ov sling <parent-task-id> --capability scout --name <scout1-name> \
184
+ --skip-task-check \
180
185
  --parent $OVERSTORY_AGENT_NAME --depth <current+1>
181
186
  ov mail send --to <scout1-name> --subject "Explore: implementation" \
182
187
  --body "Investigate implementation files: <files>. Report: patterns, types, dependencies." \
183
188
  --type dispatch
184
189
 
185
190
  # Scout 2: tests and interfaces
186
- {{TRACKER_CLI}} create --title="Scout: explore tests/types for <objective>" --type=task --priority=2
187
- ov sling <scout2-bead-id> --capability scout --name <scout2-name> \
191
+ ov sling <parent-task-id> --capability scout --name <scout2-name> \
192
+ --skip-task-check \
188
193
  --parent $OVERSTORY_AGENT_NAME --depth <current+1>
189
194
  ov mail send --to <scout2-name> --subject "Explore: tests and interfaces" \
190
195
  --body "Investigate test files and type definitions: <files>. Report: test patterns, type contracts." \
@@ -204,17 +209,14 @@ Write specs from scout findings and dispatch builders.
204
209
  - File scope (which files the builder owns -- non-overlapping)
205
210
  - Context (relevant types, interfaces, existing patterns from scout findings)
206
211
  - Dependencies (what must be true before this work starts)
207
- 7. **Create {{TRACKER_NAME}} issues** for each subtask:
212
+ 7. **Spawn builders** for parallel tasks:
208
213
  ```bash
209
- {{TRACKER_CLI}} create --title="<subtask title>" --priority=P1 --desc="<spec summary>"
210
- ```
211
- 8. **Spawn builders** for parallel tasks:
212
- ```bash
213
- ov sling <bead-id> --capability builder --name <builder-name> \
214
+ ov sling <parent-task-id> --capability builder --name <builder-name> \
214
215
  --spec .overstory/specs/<bead-id>.md --files <scoped-files> \
216
+ --skip-task-check \
215
217
  --parent $OVERSTORY_AGENT_NAME --depth <current+1>
216
218
  ```
217
- 9. **Send dispatch mail** to each builder:
219
+ 8. **Send dispatch mail** to each builder:
218
220
  ```bash
219
221
  ov mail send --to <builder-name> --subject "Build: <task>" \
220
222
  --body "Spec: .overstory/specs/<bead-id>.md. Begin immediately." --type dispatch
@@ -248,10 +250,9 @@ Review is a quality investment. For complex, multi-file changes, spawn a reviewe
248
250
 
249
251
  To spawn a reviewer:
250
252
  ```bash
251
- {{TRACKER_CLI}} create --title="Review: <builder-task-summary>" --type=task --priority=P1
252
- ov sling <review-bead-id> --capability reviewer --name review-<builder-name> \
253
- --spec .overstory/specs/<builder-bead-id>.md --parent $OVERSTORY_AGENT_NAME \
254
- --depth <current+1>
253
+ ov sling <parent-task-id> --capability reviewer --name review-<builder-name> \
254
+ --spec .overstory/specs/<builder-bead-id>.md --skip-task-check \
255
+ --parent $OVERSTORY_AGENT_NAME --depth <current+1>
255
256
  ov mail send --to review-<builder-name> \
256
257
  --subject "Review: <builder-task>" \
257
258
  --body "Review the changes on branch <builder-branch>. Spec: .overstory/specs/<builder-bead-id>.md. Run quality gates and report PASS or FAIL." \
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@os-eco/overstory-cli",
3
- "version": "0.6.11",
3
+ "version": "0.7.0",
4
4
  "description": "Multi-agent orchestration for Claude Code — spawn worker agents in git worktrees via tmux, coordinate through SQLite mail, merge with tiered conflict resolution",
5
5
  "author": "Jaymin West",
6
6
  "license": "MIT",
@@ -44,12 +44,14 @@
44
44
  "version:bump": "bun scripts/version-bump.ts"
45
45
  },
46
46
  "dependencies": {
47
+ "@os-eco/mulch-cli": "^0.6.2",
47
48
  "chalk": "^5.6.2",
48
49
  "commander": "^14.0.3"
49
50
  },
50
51
  "devDependencies": {
52
+ "@biomejs/biome": "^2.3.15",
51
53
  "@types/bun": "latest",
52
- "typescript": "^5.9.0",
53
- "@biomejs/biome": "^2.3.15"
54
+ "@types/js-yaml": "^4.0.9",
55
+ "typescript": "^5.9.0"
54
56
  }
55
57
  }
@@ -736,6 +736,29 @@ describe("writeOverlay", () => {
736
736
  const exists = await Bun.file(outputPath).exists();
737
737
  expect(exists).toBe(true);
738
738
  });
739
+
740
+ test("writes to custom instruction path when provided", async () => {
741
+ const worktreePath = join(tempDir, "worktree");
742
+ const config = makeConfig();
743
+ await writeOverlay(worktreePath, config, "/nonexistent-canonical-root", "AGENTS.md");
744
+ const outputPath = join(worktreePath, "AGENTS.md");
745
+ expect(await Bun.file(outputPath).exists()).toBe(true);
746
+ expect(await Bun.file(join(worktreePath, ".claude", "CLAUDE.md")).exists()).toBe(false);
747
+ });
748
+
749
+ test("custom instruction path creates necessary subdirectories", async () => {
750
+ const worktreePath = join(tempDir, "worktree");
751
+ const config = makeConfig();
752
+ await writeOverlay(
753
+ worktreePath,
754
+ config,
755
+ "/nonexistent-canonical-root",
756
+ ".pi/instructions/AGENT.md",
757
+ );
758
+ expect(await Bun.file(join(worktreePath, ".pi", "instructions", "AGENT.md")).exists()).toBe(
759
+ true,
760
+ );
761
+ });
739
762
  });
740
763
 
741
764
  describe("isCanonicalRoot", () => {
@@ -369,6 +369,7 @@ export async function writeOverlay(
369
369
  worktreePath: string,
370
370
  config: OverlayConfig,
371
371
  canonicalRoot: string,
372
+ instructionPath = ".claude/CLAUDE.md",
372
373
  ): Promise<void> {
373
374
  // Guard: never write agent overlays to the canonical project root.
374
375
  // The project root's .claude/CLAUDE.md belongs to the orchestrator/user.
@@ -383,13 +384,13 @@ export async function writeOverlay(
383
384
  }
384
385
 
385
386
  const content = await generateOverlay(config);
386
- const claudeDir = join(worktreePath, ".claude");
387
- const outputPath = join(claudeDir, "CLAUDE.md");
387
+ const outputPath = join(worktreePath, instructionPath);
388
+ const outputDir = dirname(outputPath);
388
389
 
389
390
  try {
390
- await mkdir(claudeDir, { recursive: true });
391
+ await mkdir(outputDir, { recursive: true });
391
392
  } catch (err) {
392
- throw new AgentError(`Failed to create .claude/ directory at: ${claudeDir}`, {
393
+ throw new AgentError(`Failed to create directory for instruction file at: ${outputDir}`, {
393
394
  agentName: config.agentName,
394
395
  cause: err instanceof Error ? err : undefined,
395
396
  });
@@ -22,6 +22,7 @@ import { loadConfig } from "../config.ts";
22
22
  import { AgentError, ValidationError } from "../errors.ts";
23
23
  import { jsonOutput } from "../json.ts";
24
24
  import { printHint, printSuccess, printWarning } from "../logging/color.ts";
25
+ import { getRuntime } from "../runtimes/registry.ts";
25
26
  import { openSessionStore } from "../sessions/compat.ts";
26
27
  import { createRunStore } from "../sessions/store.ts";
27
28
  import { resolveBackend, trackerCliName } from "../tracker/factory.ts";
@@ -62,6 +63,7 @@ export interface CoordinatorDeps {
62
63
  sendKeys: (name: string, keys: string) => Promise<void>;
63
64
  waitForTuiReady: (
64
65
  name: string,
66
+ detectReady: (paneContent: string) => import("../runtimes/types.ts").ReadyState,
65
67
  timeoutMs?: number,
66
68
  pollIntervalMs?: number,
67
69
  ) => Promise<boolean>;
@@ -347,7 +349,8 @@ async function startCoordinator(
347
349
  join(projectRoot, config.agents.baseDir),
348
350
  );
349
351
  const manifest = await manifestLoader.load();
350
- const { model, env } = resolveModel(config, manifest, "coordinator", "opus");
352
+ const resolvedModel = resolveModel(config, manifest, "coordinator", "opus");
353
+ const runtime = getRuntime(undefined, config);
351
354
 
352
355
  // Preflight: verify tmux is installed before attempting to spawn.
353
356
  // Without this check, a missing tmux leads to cryptic errors later.
@@ -359,15 +362,22 @@ async function startCoordinator(
359
362
  // (overstory-gaio, overstory-0kwf).
360
363
  const agentDefPath = join(projectRoot, ".overstory", "agent-defs", "coordinator.md");
361
364
  const agentDefFile = Bun.file(agentDefPath);
362
- let claudeCmd = `claude --model ${model} --permission-mode bypassPermissions`;
365
+ let appendSystemPrompt: string | undefined;
363
366
  if (await agentDefFile.exists()) {
364
- const agentDef = await agentDefFile.text();
365
- // Single-quote the content for safe shell expansion (only escape single quotes)
366
- const escaped = agentDef.replace(/'/g, "'\\''");
367
- claudeCmd += ` --append-system-prompt '${escaped}'`;
367
+ appendSystemPrompt = await agentDefFile.text();
368
368
  }
369
- const pid = await tmux.createSession(tmuxSession, projectRoot, claudeCmd, {
370
- ...env,
369
+ const spawnCmd = runtime.buildSpawnCommand({
370
+ model: resolvedModel.model,
371
+ permissionMode: "bypass",
372
+ cwd: projectRoot,
373
+ appendSystemPrompt,
374
+ env: {
375
+ ...runtime.buildEnv(resolvedModel),
376
+ OVERSTORY_AGENT_NAME: COORDINATOR_NAME,
377
+ },
378
+ });
379
+ const pid = await tmux.createSession(tmuxSession, projectRoot, spawnCmd, {
380
+ ...runtime.buildEnv(resolvedModel),
371
381
  OVERSTORY_AGENT_NAME: COORDINATOR_NAME,
372
382
  });
373
383
 
@@ -397,7 +407,9 @@ async function startCoordinator(
397
407
  store.upsert(session);
398
408
 
399
409
  // Wait for Claude Code TUI to render before sending input
400
- const tuiReady = await tmux.waitForTuiReady(tmuxSession);
410
+ const tuiReady = await tmux.waitForTuiReady(tmuxSession, (content) =>
411
+ runtime.detectReady(content),
412
+ );
401
413
  if (!tuiReady) {
402
414
  // Session may have died — check liveness before proceeding
403
415
  const alive = await tmux.isSessionAlive(tmuxSession);
@@ -303,7 +303,7 @@ describe("costsCommand", () => {
303
303
  await costsCommand([]);
304
304
  const out = output();
305
305
 
306
- expect(out).toContain("=".repeat(70));
306
+ expect(out).toContain("\u2500".repeat(70));
307
307
  });
308
308
 
309
309
  test("shows Total row", async () => {
@@ -13,6 +13,7 @@ import { loadConfig } from "../config.ts";
13
13
  import { ValidationError } from "../errors.ts";
14
14
  import { jsonError, jsonOutput } from "../json.ts";
15
15
  import { color } from "../logging/color.ts";
16
+ import { renderHeader, separator } from "../logging/theme.ts";
16
17
  import { createMetricsStore } from "../metrics/store.ts";
17
18
  import { estimateCost, parseTranscriptUsage } from "../metrics/transcript.ts";
18
19
  import { openSessionStore } from "../sessions/compat.ts";
@@ -141,10 +142,8 @@ function groupByCapability(sessions: SessionMetrics[]): CapabilityGroup[] {
141
142
  /** Print the standard per-agent cost summary table. */
142
143
  function printCostSummary(sessions: SessionMetrics[]): void {
143
144
  const w = process.stdout.write.bind(process.stdout);
144
- const separator = "\u2500".repeat(70);
145
145
 
146
- w(`${color.bold("Cost Summary")}\n`);
147
- w(`${"=".repeat(70)}\n`);
146
+ w(`${renderHeader("Cost Summary")}\n`);
148
147
 
149
148
  if (sessions.length === 0) {
150
149
  w(`${color.dim("No session data found.")}\n`);
@@ -156,7 +155,7 @@ function printCostSummary(sessions: SessionMetrics[]): void {
156
155
  `${padLeft("Input", 10)}${padLeft("Output", 10)}` +
157
156
  `${padLeft("Cache", 10)}${padLeft("Cost", 10)}\n`,
158
157
  );
159
- w(`${color.dim(separator)}\n`);
158
+ w(`${color.dim(separator())}\n`);
160
159
 
161
160
  for (const s of sessions) {
162
161
  const cacheTotal = s.cacheReadTokens + s.cacheCreationTokens;
@@ -170,7 +169,7 @@ function printCostSummary(sessions: SessionMetrics[]): void {
170
169
  }
171
170
 
172
171
  const totals = computeTotals(sessions);
173
- w(`${color.dim(separator)}\n`);
172
+ w(`${color.dim(separator())}\n`);
174
173
  w(
175
174
  `${color.green(
176
175
  color.bold(
@@ -187,10 +186,8 @@ function printCostSummary(sessions: SessionMetrics[]): void {
187
186
  /** Print the capability-grouped cost table. */
188
187
  function printByCapability(sessions: SessionMetrics[]): void {
189
188
  const w = process.stdout.write.bind(process.stdout);
190
- const separator = "\u2500".repeat(70);
191
189
 
192
- w(`${color.bold("Cost by Capability")}\n`);
193
- w(`${"=".repeat(70)}\n`);
190
+ w(`${renderHeader("Cost by Capability")}\n`);
194
191
 
195
192
  if (sessions.length === 0) {
196
193
  w(`${color.dim("No session data found.")}\n`);
@@ -202,7 +199,7 @@ function printByCapability(sessions: SessionMetrics[]): void {
202
199
  `${padLeft("Input", 10)}${padLeft("Output", 10)}` +
203
200
  `${padLeft("Cache", 10)}${padLeft("Cost", 10)}\n`,
204
201
  );
205
- w(`${color.dim(separator)}\n`);
202
+ w(`${color.dim(separator())}\n`);
206
203
 
207
204
  const groups = groupByCapability(sessions);
208
205
 
@@ -218,7 +215,7 @@ function printByCapability(sessions: SessionMetrics[]): void {
218
215
  }
219
216
 
220
217
  const totals = computeTotals(sessions);
221
- w(`${color.dim(separator)}\n`);
218
+ w(`${color.dim(separator())}\n`);
222
219
  w(
223
220
  `${color.green(
224
221
  color.bold(
@@ -299,17 +296,15 @@ async function executeCosts(opts: CostsOpts): Promise<void> {
299
296
  });
300
297
  } else {
301
298
  const w = process.stdout.write.bind(process.stdout);
302
- const separator = "\u2500".repeat(70);
303
299
 
304
- w(`${color.bold("Orchestrator Session Cost")}\n`);
305
- w(`${"=".repeat(70)}\n`);
300
+ w(`${renderHeader("Orchestrator Session Cost")}\n`);
306
301
  w(`${padRight("Model:", 12)}${usage.modelUsed ?? "unknown"}\n`);
307
302
  w(`${padRight("Transcript:", 12)}${transcriptPath}\n`);
308
- w(`${color.dim(separator)}\n`);
303
+ w(`${color.dim(separator())}\n`);
309
304
  w(`${padRight("Input tokens:", 22)}${padLeft(formatNumber(usage.inputTokens), 12)}\n`);
310
305
  w(`${padRight("Output tokens:", 22)}${padLeft(formatNumber(usage.outputTokens), 12)}\n`);
311
306
  w(`${padRight("Cache tokens:", 22)}${padLeft(formatNumber(cacheTotal), 12)}\n`);
312
- w(`${color.dim(separator)}\n`);
307
+ w(`${color.dim(separator())}\n`);
313
308
  w(
314
309
  `${color.green(color.bold(padRight("Estimated cost:", 22) + padLeft(formatCost(cost), 12)))}\n`,
315
310
  );
@@ -453,16 +448,14 @@ async function executeCosts(opts: CostsOpts): Promise<void> {
453
448
  });
454
449
  } else {
455
450
  const w = process.stdout.write.bind(process.stdout);
456
- const separator = "\u2500".repeat(70);
457
451
 
458
- w(`${color.bold(`Live Token Usage (${agentData.length} active agents)`)}\n`);
459
- w(`${"=".repeat(70)}\n`);
452
+ w(`${renderHeader(`Live Token Usage (${agentData.length} active agents)`)}\n`);
460
453
  w(
461
454
  `${padRight("Agent", 19)}${padRight("Capability", 12)}` +
462
455
  `${padLeft("Input", 10)}${padLeft("Output", 10)}` +
463
456
  `${padLeft("Cache", 10)}${padLeft("Cost", 10)}\n`,
464
457
  );
465
- w(`${color.dim(separator)}\n`);
458
+ w(`${color.dim(separator())}\n`);
466
459
 
467
460
  for (const agent of agentData) {
468
461
  const cacheTotal = agent.cacheReadTokens + agent.cacheCreationTokens;
@@ -475,7 +468,7 @@ async function executeCosts(opts: CostsOpts): Promise<void> {
475
468
  );
476
469
  }
477
470
 
478
- w(`${color.dim(separator)}\n`);
471
+ w(`${color.dim(separator())}\n`);
479
472
  w(
480
473
  `${color.green(
481
474
  color.bold(