@os-eco/overstory-cli 0.6.1 → 0.6.5

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 (110) hide show
  1. package/README.md +8 -7
  2. package/package.json +12 -4
  3. package/src/agents/checkpoint.test.ts +2 -2
  4. package/src/agents/hooks-deployer.test.ts +131 -16
  5. package/src/agents/hooks-deployer.ts +33 -1
  6. package/src/agents/identity.test.ts +27 -27
  7. package/src/agents/identity.ts +10 -10
  8. package/src/agents/lifecycle.test.ts +6 -6
  9. package/src/agents/lifecycle.ts +2 -2
  10. package/src/agents/manifest.test.ts +86 -0
  11. package/src/agents/overlay.test.ts +9 -9
  12. package/src/agents/overlay.ts +4 -4
  13. package/src/commands/agents.test.ts +8 -8
  14. package/src/commands/agents.ts +62 -91
  15. package/src/commands/clean.test.ts +36 -51
  16. package/src/commands/clean.ts +28 -49
  17. package/src/commands/completions.ts +14 -0
  18. package/src/commands/coordinator.test.ts +133 -26
  19. package/src/commands/coordinator.ts +101 -64
  20. package/src/commands/costs.test.ts +47 -47
  21. package/src/commands/costs.ts +96 -75
  22. package/src/commands/dashboard.test.ts +2 -2
  23. package/src/commands/dashboard.ts +75 -95
  24. package/src/commands/doctor.test.ts +2 -2
  25. package/src/commands/doctor.ts +92 -79
  26. package/src/commands/errors.test.ts +2 -2
  27. package/src/commands/errors.ts +56 -50
  28. package/src/commands/feed.test.ts +2 -2
  29. package/src/commands/feed.ts +86 -83
  30. package/src/commands/group.ts +167 -177
  31. package/src/commands/hooks.test.ts +2 -2
  32. package/src/commands/hooks.ts +52 -42
  33. package/src/commands/init.test.ts +19 -19
  34. package/src/commands/init.ts +7 -16
  35. package/src/commands/inspect.test.ts +18 -18
  36. package/src/commands/inspect.ts +55 -58
  37. package/src/commands/log.test.ts +26 -31
  38. package/src/commands/log.ts +97 -91
  39. package/src/commands/logs.test.ts +1 -1
  40. package/src/commands/logs.ts +101 -104
  41. package/src/commands/mail.test.ts +5 -5
  42. package/src/commands/mail.ts +157 -169
  43. package/src/commands/merge.test.ts +28 -66
  44. package/src/commands/merge.ts +21 -51
  45. package/src/commands/metrics.test.ts +8 -8
  46. package/src/commands/metrics.ts +34 -35
  47. package/src/commands/monitor.test.ts +3 -3
  48. package/src/commands/monitor.ts +57 -62
  49. package/src/commands/nudge.test.ts +1 -1
  50. package/src/commands/nudge.ts +41 -89
  51. package/src/commands/prime.test.ts +19 -51
  52. package/src/commands/prime.ts +13 -50
  53. package/src/commands/replay.test.ts +2 -2
  54. package/src/commands/replay.ts +79 -86
  55. package/src/commands/run.test.ts +1 -1
  56. package/src/commands/run.ts +97 -77
  57. package/src/commands/sling.test.ts +201 -5
  58. package/src/commands/sling.ts +37 -64
  59. package/src/commands/spec.test.ts +14 -40
  60. package/src/commands/spec.ts +32 -101
  61. package/src/commands/status.test.ts +97 -1
  62. package/src/commands/status.ts +63 -58
  63. package/src/commands/stop.test.ts +22 -40
  64. package/src/commands/stop.ts +18 -33
  65. package/src/commands/supervisor.test.ts +12 -14
  66. package/src/commands/supervisor.ts +144 -165
  67. package/src/commands/trace.test.ts +15 -15
  68. package/src/commands/trace.ts +59 -82
  69. package/src/commands/watch.test.ts +2 -2
  70. package/src/commands/watch.ts +38 -45
  71. package/src/commands/worktree.test.ts +213 -37
  72. package/src/commands/worktree.ts +110 -55
  73. package/src/config.test.ts +96 -0
  74. package/src/doctor/consistency.test.ts +14 -14
  75. package/src/doctor/databases.test.ts +22 -2
  76. package/src/doctor/databases.ts +16 -0
  77. package/src/doctor/dependencies.test.ts +55 -1
  78. package/src/doctor/dependencies.ts +113 -18
  79. package/src/doctor/merge-queue.test.ts +4 -4
  80. package/src/e2e/init-sling-lifecycle.test.ts +8 -8
  81. package/src/errors.ts +1 -1
  82. package/src/index.ts +223 -213
  83. package/src/logging/color.test.ts +74 -91
  84. package/src/logging/color.ts +52 -46
  85. package/src/logging/reporter.test.ts +10 -10
  86. package/src/logging/reporter.ts +6 -5
  87. package/src/mail/broadcast.test.ts +1 -1
  88. package/src/mail/client.test.ts +6 -6
  89. package/src/mail/store.test.ts +3 -3
  90. package/src/merge/queue.test.ts +73 -7
  91. package/src/merge/queue.ts +17 -2
  92. package/src/merge/resolver.test.ts +159 -7
  93. package/src/merge/resolver.ts +46 -2
  94. package/src/metrics/store.test.ts +44 -44
  95. package/src/metrics/store.ts +2 -2
  96. package/src/metrics/summary.test.ts +35 -35
  97. package/src/mulch/client.test.ts +1 -1
  98. package/src/schema-consistency.test.ts +239 -0
  99. package/src/sessions/compat.test.ts +3 -3
  100. package/src/sessions/compat.ts +2 -2
  101. package/src/sessions/store.test.ts +41 -4
  102. package/src/sessions/store.ts +13 -2
  103. package/src/types.ts +14 -14
  104. package/src/watchdog/daemon.test.ts +10 -10
  105. package/src/watchdog/daemon.ts +1 -1
  106. package/src/watchdog/health.test.ts +1 -1
  107. package/src/worktree/manager.test.ts +20 -20
  108. package/src/worktree/manager.ts +120 -4
  109. package/src/worktree/tmux.test.ts +98 -9
  110. package/src/worktree/tmux.ts +18 -0
@@ -47,7 +47,7 @@ describe("initCommand: agent-defs deployment", () => {
47
47
  });
48
48
 
49
49
  test("creates .overstory/agent-defs/ with all 8 agent definition files", async () => {
50
- await initCommand([]);
50
+ await initCommand({});
51
51
 
52
52
  const agentDefsDir = join(tempDir, ".overstory", "agent-defs");
53
53
  const files = await readdir(agentDefsDir);
@@ -57,7 +57,7 @@ describe("initCommand: agent-defs deployment", () => {
57
57
  });
58
58
 
59
59
  test("copied files match source content", async () => {
60
- await initCommand([]);
60
+ await initCommand({});
61
61
 
62
62
  for (const fileName of AGENT_DEF_FILES) {
63
63
  const sourcePath = join(SOURCE_AGENTS_DIR, fileName);
@@ -72,7 +72,7 @@ describe("initCommand: agent-defs deployment", () => {
72
72
 
73
73
  test("--force reinit overwrites existing agent def files", async () => {
74
74
  // First init
75
- await initCommand([]);
75
+ await initCommand({});
76
76
 
77
77
  // Tamper with one of the deployed files
78
78
  const tamperPath = join(tempDir, ".overstory", "agent-defs", "scout.md");
@@ -83,7 +83,7 @@ describe("initCommand: agent-defs deployment", () => {
83
83
  expect(tampered).toBe("# tampered content\n");
84
84
 
85
85
  // Reinit with --force
86
- await initCommand(["--force"]);
86
+ await initCommand({ force: true });
87
87
 
88
88
  // Verify the file was overwritten with the original source
89
89
  const sourceContent = await Bun.file(join(SOURCE_AGENTS_DIR, "scout.md")).text();
@@ -92,7 +92,7 @@ describe("initCommand: agent-defs deployment", () => {
92
92
  });
93
93
 
94
94
  test("Stop hook includes mulch learn command", async () => {
95
- await initCommand([]);
95
+ await initCommand({});
96
96
 
97
97
  const hooksPath = join(tempDir, ".overstory", "hooks.json");
98
98
  const content = await Bun.file(hooksPath).text();
@@ -105,7 +105,7 @@ describe("initCommand: agent-defs deployment", () => {
105
105
  });
106
106
 
107
107
  test("PostToolUse hooks include Bash-matched mulch diff hook", async () => {
108
- await initCommand([]);
108
+ await initCommand({});
109
109
 
110
110
  const hooksPath = join(tempDir, ".overstory", "hooks.json");
111
111
  const content = await Bun.file(hooksPath).text();
@@ -147,7 +147,7 @@ describe("initCommand: .overstory/.gitignore", () => {
147
147
  });
148
148
 
149
149
  test("creates .overstory/.gitignore with wildcard+whitelist model", async () => {
150
- await initCommand([]);
150
+ await initCommand({});
151
151
 
152
152
  const gitignorePath = join(tempDir, ".overstory", ".gitignore");
153
153
  const content = await Bun.file(gitignorePath).text();
@@ -167,7 +167,7 @@ describe("initCommand: .overstory/.gitignore", () => {
167
167
 
168
168
  test("gitignore is always written when init completes", async () => {
169
169
  // Init should write gitignore
170
- await initCommand([]);
170
+ await initCommand({});
171
171
 
172
172
  const gitignorePath = join(tempDir, ".overstory", ".gitignore");
173
173
  const content = await Bun.file(gitignorePath).text();
@@ -182,7 +182,7 @@ describe("initCommand: .overstory/.gitignore", () => {
182
182
 
183
183
  test("--force reinit overwrites stale .overstory/.gitignore", async () => {
184
184
  // First init
185
- await initCommand([]);
185
+ await initCommand({});
186
186
 
187
187
  const gitignorePath = join(tempDir, ".overstory", ".gitignore");
188
188
 
@@ -195,7 +195,7 @@ describe("initCommand: .overstory/.gitignore", () => {
195
195
  expect(tampered).not.toContain("!.gitignore\n");
196
196
 
197
197
  // Reinit with --force
198
- await initCommand(["--force"]);
198
+ await initCommand({ force: true });
199
199
 
200
200
  // Verify the file was overwritten with the new wildcard+whitelist format
201
201
  const restored = await Bun.file(gitignorePath).text();
@@ -206,7 +206,7 @@ describe("initCommand: .overstory/.gitignore", () => {
206
206
 
207
207
  test("subsequent init without --force does not overwrite gitignore", async () => {
208
208
  // First init
209
- await initCommand([]);
209
+ await initCommand({});
210
210
 
211
211
  const gitignorePath = join(tempDir, ".overstory", ".gitignore");
212
212
 
@@ -218,7 +218,7 @@ describe("initCommand: .overstory/.gitignore", () => {
218
218
  expect(tampered).toBe("# custom content\n");
219
219
 
220
220
  // Second init without --force should return early (not overwrite)
221
- await initCommand([]);
221
+ await initCommand({});
222
222
 
223
223
  // Verify the file was NOT overwritten (early return prevented it)
224
224
  const afterSecondInit = await Bun.file(gitignorePath).text();
@@ -248,7 +248,7 @@ describe("initCommand: .overstory/README.md", () => {
248
248
  });
249
249
 
250
250
  test("creates .overstory/README.md with expected content", async () => {
251
- await initCommand([]);
251
+ await initCommand({});
252
252
 
253
253
  const readmePath = join(tempDir, ".overstory", "README.md");
254
254
  const exists = await Bun.file(readmePath).exists();
@@ -264,7 +264,7 @@ describe("initCommand: .overstory/README.md", () => {
264
264
 
265
265
  test("--force reinit overwrites README.md", async () => {
266
266
  // First init
267
- await initCommand([]);
267
+ await initCommand({});
268
268
 
269
269
  const readmePath = join(tempDir, ".overstory", "README.md");
270
270
 
@@ -274,7 +274,7 @@ describe("initCommand: .overstory/README.md", () => {
274
274
  expect(tampered).toBe("# tampered\n");
275
275
 
276
276
  // Reinit with --force
277
- await initCommand(["--force"]);
277
+ await initCommand({ force: true });
278
278
 
279
279
  // Verify restored to canonical content
280
280
  const restored = await Bun.file(readmePath).text();
@@ -283,7 +283,7 @@ describe("initCommand: .overstory/README.md", () => {
283
283
 
284
284
  test("subsequent init without --force does not overwrite README.md", async () => {
285
285
  // First init
286
- await initCommand([]);
286
+ await initCommand({});
287
287
 
288
288
  const readmePath = join(tempDir, ".overstory", "README.md");
289
289
 
@@ -293,7 +293,7 @@ describe("initCommand: .overstory/README.md", () => {
293
293
  expect(tampered).toBe("# custom content\n");
294
294
 
295
295
  // Second init without --force returns early
296
- await initCommand([]);
296
+ await initCommand({});
297
297
 
298
298
  // Verify tampered content preserved (early return)
299
299
  const afterSecondInit = await Bun.file(readmePath).text();
@@ -329,7 +329,7 @@ describe("initCommand: canonical branch detection", () => {
329
329
  // Switch to a non-standard branch name
330
330
  await runGitInDir(tempDir, ["switch", "-c", "trunk"]);
331
331
 
332
- await initCommand([]);
332
+ await initCommand({});
333
333
 
334
334
  const configPath = join(tempDir, ".overstory", "config.yaml");
335
335
  const content = await Bun.file(configPath).text();
@@ -338,7 +338,7 @@ describe("initCommand: canonical branch detection", () => {
338
338
 
339
339
  test("standard branch names (main) still work as canonicalBranch", async () => {
340
340
  // createTempGitRepo defaults to main branch
341
- await initCommand([]);
341
+ await initCommand({});
342
342
 
343
343
  const configPath = join(tempDir, ".overstory", "config.yaml");
344
344
  const content = await Bun.file(configPath).text();
@@ -514,6 +514,10 @@ export async function writeOverstoryReadme(overstoryPath: string): Promise<void>
514
514
  await Bun.write(readmePath, OVERSTORY_README);
515
515
  }
516
516
 
517
+ export interface InitOptions {
518
+ force?: boolean;
519
+ }
520
+
517
521
  /**
518
522
  * Print a success status line.
519
523
  */
@@ -526,23 +530,10 @@ function printCreated(relativePath: string): void {
526
530
  *
527
531
  * Scaffolds the .overstory/ directory structure in the current working directory.
528
532
  *
529
- * @param args - CLI arguments after "init" subcommand
533
+ * @param opts - Command options
530
534
  */
531
- const INIT_HELP = `overstory init Initialize .overstory/ in current project
532
-
533
- Usage: overstory init [--force]
534
-
535
- Options:
536
- --force Reinitialize even if .overstory/ already exists
537
- --help, -h Show this help`;
538
-
539
- export async function initCommand(args: string[]): Promise<void> {
540
- if (args.includes("--help") || args.includes("-h")) {
541
- process.stdout.write(`${INIT_HELP}\n`);
542
- return;
543
- }
544
-
545
- const force = args.includes("--force");
535
+ export async function initCommand(opts: InitOptions): Promise<void> {
536
+ const force = opts.force ?? false;
546
537
  const projectRoot = process.cwd();
547
538
  const overstoryPath = join(projectRoot, OVERSTORY_DIR);
548
539
 
@@ -39,7 +39,7 @@ function makeEvent(overrides: Partial<InsertEvent> = {}): InsertEvent {
39
39
  function makeMetrics(overrides: Partial<SessionMetrics> = {}): SessionMetrics {
40
40
  return {
41
41
  agentName: "builder-1",
42
- beadId: "overstory-001",
42
+ taskId: "overstory-001",
43
43
  capability: "builder",
44
44
  startedAt: new Date().toISOString(),
45
45
  completedAt: null,
@@ -102,7 +102,7 @@ describe("inspectCommand", () => {
102
102
  test("--help shows help text", async () => {
103
103
  await inspectCommand(["--help"]);
104
104
  const out = output();
105
- expect(out).toContain("overstory inspect");
105
+ expect(out).toContain("inspect");
106
106
  expect(out).toContain("--json");
107
107
  expect(out).toContain("--follow");
108
108
  expect(out).toContain("--limit");
@@ -112,7 +112,7 @@ describe("inspectCommand", () => {
112
112
  test("-h shows help text", async () => {
113
113
  await inspectCommand(["-h"]);
114
114
  const out = output();
115
- expect(out).toContain("overstory inspect");
115
+ expect(out).toContain("inspect");
116
116
  });
117
117
  });
118
118
 
@@ -142,7 +142,7 @@ describe("inspectCommand", () => {
142
142
  capability: "builder",
143
143
  worktreePath: "/tmp/wt",
144
144
  branchName: "overstory/builder-1/test",
145
- beadId: "overstory-001",
145
+ taskId: "overstory-001",
146
146
  tmuxSession: "overstory-test-builder-1",
147
147
  state: "working",
148
148
  pid: 12345,
@@ -174,7 +174,7 @@ describe("inspectCommand", () => {
174
174
  capability: "builder",
175
175
  worktreePath: "/tmp/wt",
176
176
  branchName: "overstory/builder-1/test",
177
- beadId: "overstory-001",
177
+ taskId: "overstory-001",
178
178
  tmuxSession: "overstory-test-builder-1",
179
179
  state: "working",
180
180
  pid: 12345,
@@ -210,7 +210,7 @@ describe("inspectCommand", () => {
210
210
  capability: "builder",
211
211
  worktreePath: "/tmp/wt",
212
212
  branchName: "overstory/builder-1/test",
213
- beadId: "overstory-001",
213
+ taskId: "overstory-001",
214
214
  tmuxSession: "overstory-test-builder-1",
215
215
  state: "working",
216
216
  pid: 12345,
@@ -229,7 +229,7 @@ describe("inspectCommand", () => {
229
229
  expect(data.session.agentName).toBe("builder-1");
230
230
  expect(data.session.capability).toBe("builder");
231
231
  expect(data.session.state).toBe("working");
232
- expect(data.session.beadId).toBe("overstory-001");
232
+ expect(data.session.taskId).toBe("overstory-001");
233
233
  expect(data.timeSinceLastActivity).toBeGreaterThan(4000);
234
234
  expect(data.timeSinceLastActivity).toBeLessThan(10000);
235
235
  });
@@ -246,7 +246,7 @@ describe("inspectCommand", () => {
246
246
  capability: "builder",
247
247
  worktreePath: "/tmp/wt",
248
248
  branchName: "overstory/builder-1/test",
249
- beadId: "overstory-001",
249
+ taskId: "overstory-001",
250
250
  tmuxSession: "overstory-test-builder-1",
251
251
  state: "working",
252
252
  pid: 12345,
@@ -285,7 +285,7 @@ describe("inspectCommand", () => {
285
285
  capability: "builder",
286
286
  worktreePath: "/tmp/wt",
287
287
  branchName: "overstory/builder-1/test",
288
- beadId: "overstory-001",
288
+ taskId: "overstory-001",
289
289
  tmuxSession: "overstory-test-builder-1",
290
290
  state: "working",
291
291
  pid: 12345,
@@ -320,7 +320,7 @@ describe("inspectCommand", () => {
320
320
  capability: "builder",
321
321
  worktreePath: "/tmp/wt",
322
322
  branchName: "overstory/builder-1/test",
323
- beadId: "overstory-001",
323
+ taskId: "overstory-001",
324
324
  tmuxSession: "overstory-test-builder-1",
325
325
  state: "working",
326
326
  pid: 12345,
@@ -355,7 +355,7 @@ describe("inspectCommand", () => {
355
355
  capability: "builder",
356
356
  worktreePath: "/tmp/wt",
357
357
  branchName: "overstory/builder-1/test",
358
- beadId: "overstory-001",
358
+ taskId: "overstory-001",
359
359
  tmuxSession: "overstory-test-builder-1",
360
360
  state: "working",
361
361
  pid: 12345,
@@ -399,7 +399,7 @@ describe("inspectCommand", () => {
399
399
  capability: "builder",
400
400
  worktreePath: "/tmp/wt",
401
401
  branchName: "overstory/builder-1/test",
402
- beadId: "overstory-001",
402
+ taskId: "overstory-001",
403
403
  tmuxSession: "overstory-test-builder-1",
404
404
  state: "working",
405
405
  pid: 12345,
@@ -442,7 +442,7 @@ describe("inspectCommand", () => {
442
442
  capability: "builder",
443
443
  worktreePath: "/tmp/wt",
444
444
  branchName: "overstory/builder-1/test",
445
- beadId: "overstory-001",
445
+ taskId: "overstory-001",
446
446
  tmuxSession: "overstory-test-builder-1",
447
447
  state: "working",
448
448
  pid: 12345,
@@ -491,7 +491,7 @@ describe("inspectCommand", () => {
491
491
  capability: "builder",
492
492
  worktreePath: "/tmp/wt",
493
493
  branchName: "overstory/builder-1/test",
494
- beadId: "overstory-001",
494
+ taskId: "overstory-001",
495
495
  tmuxSession: "overstory-test-builder-1",
496
496
  state: "working",
497
497
  pid: 12345,
@@ -528,7 +528,7 @@ describe("inspectCommand", () => {
528
528
  capability: "builder",
529
529
  worktreePath: "/tmp/wt",
530
530
  branchName: "overstory/builder-1/test",
531
- beadId: "overstory-001",
531
+ taskId: "overstory-001",
532
532
  tmuxSession: "overstory-test-builder-1",
533
533
  state: "working",
534
534
  pid: 12345,
@@ -564,7 +564,7 @@ describe("inspectCommand", () => {
564
564
  capability: "builder",
565
565
  worktreePath: "/tmp/wt",
566
566
  branchName: "overstory/builder-1/test",
567
- beadId: "overstory-001",
567
+ taskId: "overstory-001",
568
568
  tmuxSession: "overstory-test-builder-1",
569
569
  state: "working",
570
570
  pid: 12345,
@@ -601,7 +601,7 @@ describe("inspectCommand", () => {
601
601
  capability: "builder",
602
602
  worktreePath: "/tmp/wt",
603
603
  branchName: "overstory/builder-1/test",
604
- beadId: "overstory-001",
604
+ taskId: "overstory-001",
605
605
  tmuxSession: "overstory-test-builder-1",
606
606
  state: "working",
607
607
  pid: 12345,
@@ -639,7 +639,7 @@ describe("inspectCommand", () => {
639
639
  capability: "builder",
640
640
  worktreePath: "/tmp/wt",
641
641
  branchName: "overstory/builder-1/test",
642
- beadId: "overstory-001",
642
+ taskId: "overstory-001",
643
643
  tmuxSession: "overstory-test-builder-1",
644
644
  state: "working",
645
645
  pid: 12345,
@@ -6,6 +6,7 @@
6
6
  */
7
7
 
8
8
  import { join } from "node:path";
9
+ import { Command } from "commander";
9
10
  import { loadConfig } from "../config.ts";
10
11
  import { ValidationError } from "../errors.ts";
11
12
  import { createEventStore } from "../events/store.ts";
@@ -14,21 +15,6 @@ import { createMetricsStore } from "../metrics/store.ts";
14
15
  import { openSessionStore } from "../sessions/compat.ts";
15
16
  import type { AgentSession, StoredEvent, ToolStats } from "../types.ts";
16
17
 
17
- /**
18
- * Parse a named flag value from args.
19
- */
20
- function getFlag(args: string[], flag: string): string | undefined {
21
- const idx = args.indexOf(flag);
22
- if (idx === -1 || idx + 1 >= args.length) {
23
- return undefined;
24
- }
25
- return args[idx + 1];
26
- }
27
-
28
- function hasFlag(args: string[], flag: string): boolean {
29
- return args.includes(flag);
30
- }
31
-
32
18
  /**
33
19
  * Format a duration in ms to a human-readable string.
34
20
  */
@@ -49,15 +35,15 @@ function formatDuration(ms: number): string {
49
35
  function getStateIcon(state: AgentSession["state"]): string {
50
36
  switch (state) {
51
37
  case "booting":
52
- return `${color.yellow}⏳${color.reset}`; // Yellow hourglass
38
+ return `${color.yellow("⏳")}`; // Yellow hourglass
53
39
  case "working":
54
- return `${color.green}●${color.reset}`; // Green circle
40
+ return `${color.green("●")}`; // Green circle
55
41
  case "stalled":
56
- return `${color.yellow}⚠${color.reset}`; // Yellow warning
42
+ return `${color.yellow("⚠")}`; // Yellow warning
57
43
  case "completed":
58
- return `${color.blue}✓${color.reset}`; // Blue checkmark
44
+ return `${color.blue("✓")}`; // Blue checkmark
59
45
  case "zombie":
60
- return `${color.red}☠${color.reset}`; // Red skull
46
+ return `${color.red("☠")}`; // Red skull
61
47
  default:
62
48
  return "?";
63
49
  }
@@ -273,7 +259,7 @@ export function printInspectData(data: InspectData): void {
273
259
  const stateIcon = getStateIcon(session.state);
274
260
  w(`${stateIcon} State: ${session.state}\n`);
275
261
  w(`⏱ Last activity: ${formatDuration(data.timeSinceLastActivity)} ago\n`);
276
- w(`🎯 Task: ${session.beadId}\n`);
262
+ w(`🎯 Task: ${session.taskId}\n`);
277
263
  w(`🔧 Capability: ${session.capability}\n`);
278
264
  w(`🌿 Branch: ${session.branchName}\n`);
279
265
  if (session.parentAgent) {
@@ -343,44 +329,21 @@ export function printInspectData(data: InspectData): void {
343
329
  }
344
330
  }
345
331
 
346
- const INSPECT_HELP = `overstory inspect <agent-name> — Deep inspection of a single agent
347
-
348
- Usage: overstory inspect <agent-name> [options]
349
-
350
- Options:
351
- --json Output as JSON
352
- --follow Poll and refresh (clears screen, re-gathers, re-prints)
353
- --interval <ms> Polling interval for --follow in milliseconds (default: 3000, min: 500)
354
- --limit <n> Number of recent tool calls to show (default: 20)
355
- --no-tmux Skip tmux capture-pane
356
- --help, -h Show this help
357
-
358
- Examples:
359
- overstory inspect builder-1
360
- overstory inspect scout-alpha --json
361
- overstory inspect builder-1 --follow --interval 2000`;
362
-
363
- /**
364
- * Entry point for `overstory inspect <agent-name>`.
365
- */
366
- export async function inspectCommand(args: string[]): Promise<void> {
367
- if (args.includes("--help") || args.includes("-h")) {
368
- process.stdout.write(`${INSPECT_HELP}\n`);
369
- return;
370
- }
371
-
372
- const agentName = args[0];
373
- if (!agentName) {
374
- throw new ValidationError("Agent name is required", {
375
- field: "agent-name",
376
- });
377
- }
332
+ interface InspectOpts {
333
+ json?: boolean;
334
+ follow?: boolean;
335
+ interval?: string;
336
+ limit?: string;
337
+ tmux?: boolean; // Commander: --no-tmux sets tmux=false
338
+ }
378
339
 
379
- const json = hasFlag(args, "--json");
380
- const follow = hasFlag(args, "--follow");
381
- const noTmux = hasFlag(args, "--no-tmux");
340
+ async function executeInspect(agentName: string, opts: InspectOpts): Promise<void> {
341
+ const json = opts.json ?? false;
342
+ const follow = opts.follow ?? false;
343
+ // Commander --no-tmux sets opts.tmux = false
344
+ const noTmux = opts.tmux === false;
382
345
 
383
- const intervalStr = getFlag(args, "--interval");
346
+ const intervalStr = opts.interval;
384
347
  const interval = intervalStr ? Number.parseInt(intervalStr, 10) : 3000;
385
348
  if (Number.isNaN(interval) || interval < 500) {
386
349
  throw new ValidationError("--interval must be a number >= 500 (milliseconds)", {
@@ -389,7 +352,7 @@ export async function inspectCommand(args: string[]): Promise<void> {
389
352
  });
390
353
  }
391
354
 
392
- const limitStr = getFlag(args, "--limit");
355
+ const limitStr = opts.limit;
393
356
  const limit = limitStr ? Number.parseInt(limitStr, 10) : 20;
394
357
  if (Number.isNaN(limit) || limit < 1) {
395
358
  throw new ValidationError("--limit must be a number >= 1", {
@@ -429,3 +392,37 @@ export async function inspectCommand(args: string[]): Promise<void> {
429
392
  }
430
393
  }
431
394
  }
395
+
396
+ export function createInspectCommand(): Command {
397
+ return new Command("inspect")
398
+ .description("Deep inspection of a single agent")
399
+ .argument("<agent-name>", "Agent name to inspect")
400
+ .option("--json", "Output as JSON")
401
+ .option("--follow", "Poll and refresh continuously")
402
+ .option("--interval <ms>", "Polling interval for --follow in milliseconds (default: 3000)")
403
+ .option("--limit <n>", "Number of recent tool calls to show (default: 20)")
404
+ .option("--no-tmux", "Skip tmux capture-pane")
405
+ .action(async (agentName: string, opts: InspectOpts) => {
406
+ await executeInspect(agentName, opts);
407
+ });
408
+ }
409
+
410
+ export async function inspectCommand(args: string[]): Promise<void> {
411
+ const cmd = createInspectCommand();
412
+ cmd.exitOverride();
413
+ try {
414
+ await cmd.parseAsync(args, { from: "user" });
415
+ } catch (err: unknown) {
416
+ if (err && typeof err === "object" && "code" in err) {
417
+ const code = (err as { code: string }).code;
418
+ if (code === "commander.helpDisplayed" || code === "commander.version") {
419
+ return;
420
+ }
421
+ if (code.startsWith("commander.")) {
422
+ const message = err instanceof Error ? err.message : String(err);
423
+ throw new ValidationError(message, { field: "args" });
424
+ }
425
+ }
426
+ throw err;
427
+ }
428
+ }