@os-eco/overstory-cli 0.6.1 → 0.6.4

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 (80) hide show
  1. package/README.md +7 -6
  2. package/package.json +12 -4
  3. package/src/agents/hooks-deployer.test.ts +94 -16
  4. package/src/agents/hooks-deployer.ts +18 -0
  5. package/src/agents/manifest.test.ts +86 -0
  6. package/src/commands/agents.test.ts +3 -3
  7. package/src/commands/agents.ts +59 -88
  8. package/src/commands/clean.test.ts +31 -46
  9. package/src/commands/clean.ts +28 -49
  10. package/src/commands/completions.ts +14 -0
  11. package/src/commands/coordinator.test.ts +131 -24
  12. package/src/commands/coordinator.ts +100 -63
  13. package/src/commands/costs.test.ts +2 -2
  14. package/src/commands/costs.ts +96 -75
  15. package/src/commands/dashboard.test.ts +2 -2
  16. package/src/commands/dashboard.ts +73 -93
  17. package/src/commands/doctor.test.ts +2 -2
  18. package/src/commands/doctor.ts +92 -79
  19. package/src/commands/errors.test.ts +2 -2
  20. package/src/commands/errors.ts +56 -50
  21. package/src/commands/feed.test.ts +2 -2
  22. package/src/commands/feed.ts +86 -83
  23. package/src/commands/group.ts +167 -177
  24. package/src/commands/hooks.test.ts +2 -2
  25. package/src/commands/hooks.ts +52 -42
  26. package/src/commands/init.test.ts +19 -19
  27. package/src/commands/init.ts +7 -16
  28. package/src/commands/inspect.test.ts +2 -2
  29. package/src/commands/inspect.ts +54 -57
  30. package/src/commands/log.test.ts +5 -10
  31. package/src/commands/log.ts +90 -84
  32. package/src/commands/logs.test.ts +1 -1
  33. package/src/commands/logs.ts +101 -104
  34. package/src/commands/mail.ts +157 -169
  35. package/src/commands/merge.test.ts +20 -58
  36. package/src/commands/merge.ts +13 -43
  37. package/src/commands/metrics.test.ts +2 -2
  38. package/src/commands/metrics.ts +33 -34
  39. package/src/commands/monitor.test.ts +3 -3
  40. package/src/commands/monitor.ts +56 -61
  41. package/src/commands/nudge.ts +41 -89
  42. package/src/commands/prime.test.ts +15 -47
  43. package/src/commands/prime.ts +7 -44
  44. package/src/commands/replay.test.ts +2 -2
  45. package/src/commands/replay.ts +79 -86
  46. package/src/commands/run.ts +97 -77
  47. package/src/commands/sling.test.ts +196 -0
  48. package/src/commands/sling.ts +24 -54
  49. package/src/commands/spec.test.ts +13 -39
  50. package/src/commands/spec.ts +30 -99
  51. package/src/commands/status.ts +46 -42
  52. package/src/commands/stop.test.ts +21 -39
  53. package/src/commands/stop.ts +18 -33
  54. package/src/commands/supervisor.test.ts +3 -5
  55. package/src/commands/supervisor.ts +136 -157
  56. package/src/commands/trace.test.ts +9 -9
  57. package/src/commands/trace.ts +54 -77
  58. package/src/commands/watch.test.ts +2 -2
  59. package/src/commands/watch.ts +38 -45
  60. package/src/commands/worktree.test.ts +8 -8
  61. package/src/commands/worktree.ts +63 -46
  62. package/src/config.test.ts +96 -0
  63. package/src/doctor/databases.test.ts +22 -2
  64. package/src/doctor/databases.ts +16 -0
  65. package/src/doctor/dependencies.test.ts +55 -1
  66. package/src/doctor/dependencies.ts +113 -18
  67. package/src/e2e/init-sling-lifecycle.test.ts +6 -6
  68. package/src/index.ts +223 -213
  69. package/src/logging/color.test.ts +74 -91
  70. package/src/logging/color.ts +52 -46
  71. package/src/logging/reporter.test.ts +10 -10
  72. package/src/logging/reporter.ts +6 -5
  73. package/src/merge/queue.test.ts +66 -0
  74. package/src/merge/queue.ts +15 -0
  75. package/src/schema-consistency.test.ts +239 -0
  76. package/src/sessions/compat.ts +1 -1
  77. package/src/sessions/store.test.ts +37 -0
  78. package/src/sessions/store.ts +11 -0
  79. package/src/worktree/tmux.test.ts +98 -9
  80. package/src/worktree/tmux.ts +18 -0
package/src/index.ts CHANGED
@@ -7,85 +7,42 @@
7
7
  * Usage: overstory <command> [args...]
8
8
  */
9
9
 
10
- import { agentsCommand } from "./commands/agents.ts";
10
+ import { Command } from "commander";
11
+ import { createAgentsCommand } from "./commands/agents.ts";
11
12
  import { cleanCommand } from "./commands/clean.ts";
12
- import { completionsCommand } from "./commands/completions.ts";
13
- import { coordinatorCommand } from "./commands/coordinator.ts";
14
- import { costsCommand } from "./commands/costs.ts";
15
- import { dashboardCommand } from "./commands/dashboard.ts";
16
- import { doctorCommand } from "./commands/doctor.ts";
17
- import { errorsCommand } from "./commands/errors.ts";
18
- import { feedCommand } from "./commands/feed.ts";
19
- import { groupCommand } from "./commands/group.ts";
20
- import { hooksCommand } from "./commands/hooks.ts";
13
+ import { createCompletionsCommand } from "./commands/completions.ts";
14
+ import { createCoordinatorCommand } from "./commands/coordinator.ts";
15
+ import { createCostsCommand } from "./commands/costs.ts";
16
+ import { createDashboardCommand } from "./commands/dashboard.ts";
17
+ import { createDoctorCommand } from "./commands/doctor.ts";
18
+ import { createErrorsCommand } from "./commands/errors.ts";
19
+ import { createFeedCommand } from "./commands/feed.ts";
20
+ import { createGroupCommand } from "./commands/group.ts";
21
+ import { createHooksCommand } from "./commands/hooks.ts";
21
22
  import { initCommand } from "./commands/init.ts";
22
- import { inspectCommand } from "./commands/inspect.ts";
23
- import { logCommand } from "./commands/log.ts";
23
+ import { createInspectCommand } from "./commands/inspect.ts";
24
+ import { createLogCommand } from "./commands/log.ts";
24
25
  import { logsCommand } from "./commands/logs.ts";
25
26
  import { mailCommand } from "./commands/mail.ts";
26
27
  import { mergeCommand } from "./commands/merge.ts";
27
- import { metricsCommand } from "./commands/metrics.ts";
28
- import { monitorCommand } from "./commands/monitor.ts";
28
+ import { createMetricsCommand } from "./commands/metrics.ts";
29
+ import { createMonitorCommand } from "./commands/monitor.ts";
29
30
  import { nudgeCommand } from "./commands/nudge.ts";
30
31
  import { primeCommand } from "./commands/prime.ts";
31
- import { replayCommand } from "./commands/replay.ts";
32
- import { runCommand } from "./commands/run.ts";
32
+ import { createReplayCommand } from "./commands/replay.ts";
33
+ import { createRunCommand } from "./commands/run.ts";
33
34
  import { slingCommand } from "./commands/sling.ts";
34
- import { specCommand } from "./commands/spec.ts";
35
- import { statusCommand } from "./commands/status.ts";
35
+ import { specWriteCommand } from "./commands/spec.ts";
36
+ import { createStatusCommand } from "./commands/status.ts";
36
37
  import { stopCommand } from "./commands/stop.ts";
37
- import { supervisorCommand } from "./commands/supervisor.ts";
38
+ import { createSupervisorCommand } from "./commands/supervisor.ts";
38
39
  import { traceCommand } from "./commands/trace.ts";
39
- import { watchCommand } from "./commands/watch.ts";
40
- import { worktreeCommand } from "./commands/worktree.ts";
40
+ import { createWatchCommand } from "./commands/watch.ts";
41
+ import { createWorktreeCommand } from "./commands/worktree.ts";
41
42
  import { OverstoryError, WorktreeError } from "./errors.ts";
42
43
  import { setQuiet } from "./logging/color.ts";
43
44
 
44
- const VERSION = "0.6.1";
45
-
46
- const HELP = `overstory v${VERSION} — Multi-agent orchestration for Claude Code
47
-
48
- Usage: overstory <command> [args...]
49
-
50
- Commands:
51
- agents <sub> Discover and query agents (discover)
52
- init Initialize .overstory/ in current project
53
- sling <task-id> Spawn a worker agent
54
- spec <sub> Manage task specs (write)
55
- prime Load context for orchestrator/agent
56
- stop <agent> Terminate a running agent
57
- status Show all active agents and project state
58
- dashboard Live TUI dashboard for agent monitoring
59
- inspect <agent> Deep inspection of a single agent
60
- coordinator <sub> Persistent coordinator agent (start/stop/status)
61
- supervisor <sub> Per-project supervisor agent (start/stop/status)
62
- hooks <sub> Manage orchestrator hooks (install/uninstall/status)
63
- mail <sub> Mail system (send/check/list/read/reply)
64
- monitor <sub> Tier 2 monitor agent (start/stop/status)
65
- merge Merge agent branches into canonical
66
- nudge <agent> [msg] Send a text nudge to an agent
67
- group <sub> Task groups (create/status/add/remove/list)
68
- clean Wipe runtime state (nuclear cleanup)
69
- doctor Run health checks on overstory setup
70
- worktree <sub> Manage worktrees (list/clean)
71
- log <event> Log a hook event
72
- logs [options] Query NDJSON logs across agents
73
- watch Start watchdog daemon
74
- feed [options] Unified real-time event stream across all agents
75
- trace <target> Chronological event timeline for agent/bead
76
- errors [options] Aggregated error view across agents
77
- run [sub] Manage runs (list/show/complete)
78
- replay [options] Interleaved chronological replay across agents
79
- costs [options] Token/cost analysis and breakdown
80
- metrics Show session metrics
81
-
82
- Options:
83
- --help, -h Show this help
84
- --version, -v Show version
85
- --quiet, -q Suppress non-error output
86
- --completions <shell> Generate shell completions (bash, zsh, fish)
87
-
88
- Run 'overstory <command> --help' for command-specific help.`;
45
+ const VERSION = "0.6.4";
89
46
 
90
47
  const COMMANDS = [
91
48
  "agents",
@@ -153,164 +110,217 @@ function suggestCommand(input: string): string | undefined {
153
110
  return bestMatch;
154
111
  }
155
112
 
156
- async function main(): Promise<void> {
157
- const args = process.argv.slice(2);
113
+ const program = new Command();
114
+
115
+ program
116
+ .name("overstory")
117
+ .description("Multi-agent orchestration for Claude Code")
118
+ .version(`overstory v${VERSION}`, "-v, --version")
119
+ .option("-q, --quiet", "Suppress non-error output")
120
+ .option("--json", "JSON output")
121
+ .option("--verbose", "Verbose output");
158
122
 
159
- // Parse global flags before command routing
160
- const quietIndex = args.indexOf("--quiet");
161
- const qIndex = args.indexOf("-q");
162
- if (quietIndex !== -1 || qIndex !== -1) {
123
+ // Apply global flags before any command action runs
124
+ program.hook("preAction", (thisCmd) => {
125
+ const opts = thisCmd.optsWithGlobals();
126
+ if (opts.quiet) {
163
127
  setQuiet(true);
164
- // Remove the flag from args so commands do not see it
165
- if (quietIndex !== -1) args.splice(quietIndex, 1);
166
- if (qIndex !== -1) {
167
- const idx = args.indexOf("-q");
168
- if (idx !== -1) args.splice(idx, 1);
169
- }
170
128
  }
129
+ });
171
130
 
172
- const command = args[0];
173
- const commandArgs = args.slice(1);
131
+ // Migrated commands — use addCommand() with createXCommand() factories
132
+ program.addCommand(createAgentsCommand());
133
+ program.addCommand(createDoctorCommand());
134
+ program.addCommand(createCoordinatorCommand());
135
+ program.addCommand(createSupervisorCommand());
136
+ program.addCommand(createHooksCommand());
137
+ program.addCommand(createMonitorCommand());
138
+ program.addCommand(createWorktreeCommand());
139
+ program.addCommand(createLogCommand());
140
+ program.addCommand(createWatchCommand());
141
+ program.addCommand(createGroupCommand());
142
+ program.addCommand(createCompletionsCommand());
174
143
 
175
- if (!command || command === "--help" || command === "-h") {
176
- process.stdout.write(`${HELP}\n`);
177
- return;
178
- }
144
+ // Unmigrated commands passthrough pattern
145
+ program
146
+ .command("init")
147
+ .description("Initialize .overstory/ in current project")
148
+ .option("--force", "Reinitialize even if .overstory/ already exists")
149
+ .action(async (opts) => {
150
+ await initCommand(opts);
151
+ });
179
152
 
180
- if (command === "--version" || command === "-v") {
181
- process.stdout.write(`overstory v${VERSION}\n`);
182
- return;
183
- }
153
+ program
154
+ .command("sling")
155
+ .description("Spawn a worker agent")
156
+ .argument("<task-id>", "Task ID to assign")
157
+ .option(
158
+ "--capability <type>",
159
+ "Agent type: builder | scout | reviewer | lead | merger",
160
+ "builder",
161
+ )
162
+ .option("--name <name>", "Unique agent name")
163
+ .option("--spec <path>", "Path to task spec file")
164
+ .option("--files <list>", "Exclusive file scope (comma-separated)")
165
+ .option("--parent <agent>", "Parent agent for hierarchy tracking")
166
+ .option("--depth <n>", "Current hierarchy depth", "0")
167
+ .option("--skip-scout", "Skip scout phase for lead agents")
168
+ .option("--skip-task-check", "Skip task existence validation")
169
+ .option("--force-hierarchy", "Bypass hierarchy validation")
170
+ .option("--json", "Output result as JSON")
171
+ .action(async (taskId, opts) => {
172
+ await slingCommand(taskId, opts);
173
+ });
174
+
175
+ const specCmd = program.command("spec").description("Manage task specifications");
176
+
177
+ specCmd
178
+ .command("write")
179
+ .description("Write a spec file to .overstory/specs/<bead-id>.md")
180
+ .argument("<bead-id>", "Task ID for the spec file")
181
+ .option("--body <content>", "Spec content (or pipe via stdin)")
182
+ .option("--agent <name>", "Agent writing the spec (for attribution)")
183
+ .action(async (beadId, opts) => {
184
+ await specWriteCommand(beadId, opts);
185
+ });
186
+
187
+ program
188
+ .command("prime")
189
+ .description("Load context for orchestrator/agent")
190
+ .option("--agent <name>", "Prime for a specific agent")
191
+ .option("--compact", "Output reduced context (for PreCompact hook)")
192
+ .action(async (opts) => {
193
+ await primeCommand(opts);
194
+ });
195
+
196
+ program
197
+ .command("stop")
198
+ .description("Terminate a running agent")
199
+ .argument("<agent-name>", "Name of the agent to stop")
200
+ .option("--force", "Force kill and force-delete branch")
201
+ .option("--clean-worktree", "Remove the agent's worktree after stopping")
202
+ .option("--json", "Output as JSON")
203
+ .action(async (agentName, opts) => {
204
+ await stopCommand(agentName, opts);
205
+ });
206
+
207
+ program.addCommand(createStatusCommand());
208
+
209
+ program.addCommand(createDashboardCommand());
210
+
211
+ program.addCommand(createInspectCommand());
212
+
213
+ program
214
+ .command("clean")
215
+ .description("Wipe runtime state (nuclear cleanup)")
216
+ .option("--all", "Wipe everything (nuclear option)")
217
+ .option("--mail", "Delete mail.db")
218
+ .option("--sessions", "Wipe sessions.db")
219
+ .option("--metrics", "Delete metrics.db")
220
+ .option("--logs", "Remove all agent logs")
221
+ .option("--worktrees", "Remove all worktrees + kill tmux sessions")
222
+ .option("--branches", "Delete all overstory/* branch refs")
223
+ .option("--agents", "Remove agent identity files")
224
+ .option("--specs", "Remove task spec files")
225
+ .option("--json", "Output as JSON")
226
+ .action(async (opts) => {
227
+ await cleanCommand(opts);
228
+ });
229
+
230
+ program
231
+ .command("mail")
232
+ .description("Mail system (send/check/list/read/reply)")
233
+ .allowUnknownOption()
234
+ .allowExcessArguments()
235
+ .action(async (_opts, cmd) => {
236
+ await mailCommand(cmd.args);
237
+ });
238
+
239
+ program
240
+ .command("merge")
241
+ .description("Merge agent branches into canonical")
242
+ .option("--branch <name>", "Merge a specific branch")
243
+ .option("--all", "Merge all pending branches in the queue")
244
+ .option("--into <branch>", "Target branch to merge into")
245
+ .option("--dry-run", "Check for conflicts without actually merging")
246
+ .option("--json", "Output results as JSON")
247
+ .action(async (opts) => {
248
+ await mergeCommand(opts);
249
+ });
250
+
251
+ program
252
+ .command("nudge")
253
+ .description("Send a text nudge to an agent")
254
+ .allowUnknownOption()
255
+ .allowExcessArguments()
256
+ .action(async (_opts, cmd) => {
257
+ await nudgeCommand(cmd.args);
258
+ });
259
+
260
+ program
261
+ .command("logs")
262
+ .description("Query NDJSON logs across agents")
263
+ .allowUnknownOption()
264
+ .allowExcessArguments()
265
+ .action(async (_opts, cmd) => {
266
+ await logsCommand(cmd.args);
267
+ });
268
+
269
+ program
270
+ .command("trace")
271
+ .description("Chronological event timeline for agent/bead")
272
+ .allowUnknownOption()
273
+ .allowExcessArguments()
274
+ .action(async (_opts, cmd) => {
275
+ await traceCommand(cmd.args);
276
+ });
277
+
278
+ program.addCommand(createFeedCommand());
279
+
280
+ program.addCommand(createErrorsCommand());
281
+
282
+ program.addCommand(createReplayCommand());
283
+
284
+ program.addCommand(createRunCommand());
184
285
 
185
- if (command === "--completions") {
186
- completionsCommand(commandArgs);
187
- return;
286
+ program.addCommand(createCostsCommand());
287
+
288
+ program.addCommand(createMetricsCommand());
289
+
290
+ // Handle unknown commands with Levenshtein fuzzy-match suggestions
291
+ program.on("command:*", (operands) => {
292
+ const unknown = operands[0] ?? "";
293
+ process.stderr.write(`Unknown command: ${unknown}\n`);
294
+ const suggestion = suggestCommand(unknown);
295
+ if (suggestion) {
296
+ process.stderr.write(`Did you mean '${suggestion}'?\n`);
188
297
  }
298
+ process.stderr.write("Run 'overstory --help' for usage.\n");
299
+ process.exit(1);
300
+ });
189
301
 
190
- switch (command) {
191
- case "agents":
192
- await agentsCommand(commandArgs);
193
- break;
194
- case "init":
195
- await initCommand(commandArgs);
196
- break;
197
- case "sling":
198
- await slingCommand(commandArgs);
199
- break;
200
- case "spec":
201
- await specCommand(commandArgs);
202
- break;
203
- case "prime":
204
- await primeCommand(commandArgs);
205
- break;
206
- case "stop":
207
- await stopCommand(commandArgs);
208
- break;
209
- case "status":
210
- await statusCommand(commandArgs);
211
- break;
212
- case "dashboard":
213
- await dashboardCommand(commandArgs);
214
- break;
215
- case "inspect":
216
- await inspectCommand(commandArgs);
217
- break;
218
- case "clean":
219
- await cleanCommand(commandArgs);
220
- break;
221
- case "doctor": {
222
- const exitCode = await doctorCommand(commandArgs);
223
- if (exitCode !== undefined) {
224
- process.exitCode = exitCode;
225
- }
226
- break;
302
+ async function main(): Promise<void> {
303
+ await program.parseAsync(process.argv);
304
+ }
305
+
306
+ if (import.meta.main)
307
+ main().catch((err: unknown) => {
308
+ // Friendly message when running outside a git repository
309
+ if (err instanceof WorktreeError && err.message.includes("not a git repository")) {
310
+ process.stderr.write("Not in an overstory project. Run 'overstory init' first.\n");
311
+ process.exit(1);
227
312
  }
228
- case "coordinator":
229
- await coordinatorCommand(commandArgs);
230
- break;
231
- case "supervisor":
232
- await supervisorCommand(commandArgs);
233
- break;
234
- case "hooks":
235
- await hooksCommand(commandArgs);
236
- break;
237
- case "monitor":
238
- await monitorCommand(commandArgs);
239
- break;
240
- case "mail":
241
- await mailCommand(commandArgs);
242
- break;
243
- case "merge":
244
- await mergeCommand(commandArgs);
245
- break;
246
- case "nudge":
247
- await nudgeCommand(commandArgs);
248
- break;
249
- case "group":
250
- await groupCommand(commandArgs);
251
- break;
252
- case "worktree":
253
- await worktreeCommand(commandArgs);
254
- break;
255
- case "log":
256
- await logCommand(commandArgs);
257
- break;
258
- case "logs":
259
- await logsCommand(commandArgs);
260
- break;
261
- case "watch":
262
- await watchCommand(commandArgs);
263
- break;
264
- case "trace":
265
- await traceCommand(commandArgs);
266
- break;
267
- case "feed":
268
- await feedCommand(commandArgs);
269
- break;
270
- case "errors":
271
- await errorsCommand(commandArgs);
272
- break;
273
- case "replay":
274
- await replayCommand(commandArgs);
275
- break;
276
- case "run":
277
- await runCommand(commandArgs);
278
- break;
279
- case "costs":
280
- await costsCommand(commandArgs);
281
- break;
282
- case "metrics":
283
- await metricsCommand(commandArgs);
284
- break;
285
- default: {
286
- process.stderr.write(`Unknown command: ${command}\n`);
287
- const suggestion = suggestCommand(command);
288
- if (suggestion) {
289
- process.stderr.write(`Did you mean '${suggestion}'?\n`);
290
- }
291
- process.stderr.write(`Run 'overstory --help' for usage.\n`);
313
+ if (err instanceof OverstoryError) {
314
+ process.stderr.write(`Error [${err.code}]: ${err.message}\n`);
292
315
  process.exit(1);
293
316
  }
294
- }
295
- }
296
-
297
- main().catch((err: unknown) => {
298
- // Friendly message when running outside a git repository
299
- if (err instanceof WorktreeError && err.message.includes("not a git repository")) {
300
- process.stderr.write("Not in an overstory project. Run 'overstory init' first.\n");
301
- process.exit(1);
302
- }
303
- if (err instanceof OverstoryError) {
304
- process.stderr.write(`Error [${err.code}]: ${err.message}\n`);
305
- process.exit(1);
306
- }
307
- if (err instanceof Error) {
308
- process.stderr.write(`Error: ${err.message}\n`);
309
- if (process.argv.includes("--verbose")) {
310
- process.stderr.write(`${err.stack}\n`);
317
+ if (err instanceof Error) {
318
+ process.stderr.write(`Error: ${err.message}\n`);
319
+ if (process.argv.includes("--verbose")) {
320
+ process.stderr.write(`${err.stack}\n`);
321
+ }
322
+ process.exit(1);
311
323
  }
324
+ process.stderr.write(`Unknown error: ${String(err)}\n`);
312
325
  process.exit(1);
313
- }
314
- process.stderr.write(`Unknown error: ${String(err)}\n`);
315
- process.exit(1);
316
- });
326
+ });