@os-eco/overstory-cli 0.6.1

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 (170) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +381 -0
  3. package/agents/builder.md +137 -0
  4. package/agents/coordinator.md +263 -0
  5. package/agents/lead.md +301 -0
  6. package/agents/merger.md +160 -0
  7. package/agents/monitor.md +214 -0
  8. package/agents/reviewer.md +140 -0
  9. package/agents/scout.md +119 -0
  10. package/agents/supervisor.md +423 -0
  11. package/package.json +47 -0
  12. package/src/agents/checkpoint.test.ts +88 -0
  13. package/src/agents/checkpoint.ts +101 -0
  14. package/src/agents/hooks-deployer.test.ts +2040 -0
  15. package/src/agents/hooks-deployer.ts +607 -0
  16. package/src/agents/identity.test.ts +603 -0
  17. package/src/agents/identity.ts +384 -0
  18. package/src/agents/lifecycle.test.ts +196 -0
  19. package/src/agents/lifecycle.ts +183 -0
  20. package/src/agents/manifest.test.ts +746 -0
  21. package/src/agents/manifest.ts +354 -0
  22. package/src/agents/overlay.test.ts +676 -0
  23. package/src/agents/overlay.ts +308 -0
  24. package/src/beads/client.test.ts +217 -0
  25. package/src/beads/client.ts +202 -0
  26. package/src/beads/molecules.test.ts +338 -0
  27. package/src/beads/molecules.ts +198 -0
  28. package/src/commands/agents.test.ts +322 -0
  29. package/src/commands/agents.ts +287 -0
  30. package/src/commands/clean.test.ts +670 -0
  31. package/src/commands/clean.ts +618 -0
  32. package/src/commands/completions.test.ts +342 -0
  33. package/src/commands/completions.ts +887 -0
  34. package/src/commands/coordinator.test.ts +1530 -0
  35. package/src/commands/coordinator.ts +733 -0
  36. package/src/commands/costs.test.ts +1119 -0
  37. package/src/commands/costs.ts +564 -0
  38. package/src/commands/dashboard.test.ts +308 -0
  39. package/src/commands/dashboard.ts +838 -0
  40. package/src/commands/doctor.test.ts +294 -0
  41. package/src/commands/doctor.ts +213 -0
  42. package/src/commands/errors.test.ts +647 -0
  43. package/src/commands/errors.ts +248 -0
  44. package/src/commands/feed.test.ts +578 -0
  45. package/src/commands/feed.ts +361 -0
  46. package/src/commands/group.test.ts +262 -0
  47. package/src/commands/group.ts +511 -0
  48. package/src/commands/hooks.test.ts +458 -0
  49. package/src/commands/hooks.ts +253 -0
  50. package/src/commands/init.test.ts +347 -0
  51. package/src/commands/init.ts +650 -0
  52. package/src/commands/inspect.test.ts +670 -0
  53. package/src/commands/inspect.ts +431 -0
  54. package/src/commands/log.test.ts +1454 -0
  55. package/src/commands/log.ts +724 -0
  56. package/src/commands/logs.test.ts +379 -0
  57. package/src/commands/logs.ts +546 -0
  58. package/src/commands/mail.test.ts +1270 -0
  59. package/src/commands/mail.ts +771 -0
  60. package/src/commands/merge.test.ts +670 -0
  61. package/src/commands/merge.ts +355 -0
  62. package/src/commands/metrics.test.ts +444 -0
  63. package/src/commands/metrics.ts +143 -0
  64. package/src/commands/monitor.test.ts +191 -0
  65. package/src/commands/monitor.ts +390 -0
  66. package/src/commands/nudge.test.ts +230 -0
  67. package/src/commands/nudge.ts +372 -0
  68. package/src/commands/prime.test.ts +470 -0
  69. package/src/commands/prime.ts +381 -0
  70. package/src/commands/replay.test.ts +741 -0
  71. package/src/commands/replay.ts +360 -0
  72. package/src/commands/run.test.ts +431 -0
  73. package/src/commands/run.ts +351 -0
  74. package/src/commands/sling.test.ts +657 -0
  75. package/src/commands/sling.ts +661 -0
  76. package/src/commands/spec.test.ts +203 -0
  77. package/src/commands/spec.ts +168 -0
  78. package/src/commands/status.test.ts +430 -0
  79. package/src/commands/status.ts +398 -0
  80. package/src/commands/stop.test.ts +420 -0
  81. package/src/commands/stop.ts +151 -0
  82. package/src/commands/supervisor.test.ts +187 -0
  83. package/src/commands/supervisor.ts +535 -0
  84. package/src/commands/trace.test.ts +745 -0
  85. package/src/commands/trace.ts +325 -0
  86. package/src/commands/watch.test.ts +145 -0
  87. package/src/commands/watch.ts +247 -0
  88. package/src/commands/worktree.test.ts +786 -0
  89. package/src/commands/worktree.ts +311 -0
  90. package/src/config.test.ts +822 -0
  91. package/src/config.ts +829 -0
  92. package/src/doctor/agents.test.ts +454 -0
  93. package/src/doctor/agents.ts +396 -0
  94. package/src/doctor/config-check.test.ts +190 -0
  95. package/src/doctor/config-check.ts +183 -0
  96. package/src/doctor/consistency.test.ts +651 -0
  97. package/src/doctor/consistency.ts +294 -0
  98. package/src/doctor/databases.test.ts +290 -0
  99. package/src/doctor/databases.ts +218 -0
  100. package/src/doctor/dependencies.test.ts +184 -0
  101. package/src/doctor/dependencies.ts +175 -0
  102. package/src/doctor/logs.test.ts +251 -0
  103. package/src/doctor/logs.ts +295 -0
  104. package/src/doctor/merge-queue.test.ts +216 -0
  105. package/src/doctor/merge-queue.ts +144 -0
  106. package/src/doctor/structure.test.ts +291 -0
  107. package/src/doctor/structure.ts +198 -0
  108. package/src/doctor/types.ts +37 -0
  109. package/src/doctor/version.test.ts +136 -0
  110. package/src/doctor/version.ts +129 -0
  111. package/src/e2e/init-sling-lifecycle.test.ts +277 -0
  112. package/src/errors.ts +217 -0
  113. package/src/events/store.test.ts +660 -0
  114. package/src/events/store.ts +369 -0
  115. package/src/events/tool-filter.test.ts +330 -0
  116. package/src/events/tool-filter.ts +126 -0
  117. package/src/index.ts +316 -0
  118. package/src/insights/analyzer.test.ts +466 -0
  119. package/src/insights/analyzer.ts +203 -0
  120. package/src/logging/color.test.ts +142 -0
  121. package/src/logging/color.ts +71 -0
  122. package/src/logging/logger.test.ts +813 -0
  123. package/src/logging/logger.ts +266 -0
  124. package/src/logging/reporter.test.ts +259 -0
  125. package/src/logging/reporter.ts +109 -0
  126. package/src/logging/sanitizer.test.ts +190 -0
  127. package/src/logging/sanitizer.ts +57 -0
  128. package/src/mail/broadcast.test.ts +203 -0
  129. package/src/mail/broadcast.ts +92 -0
  130. package/src/mail/client.test.ts +773 -0
  131. package/src/mail/client.ts +223 -0
  132. package/src/mail/store.test.ts +705 -0
  133. package/src/mail/store.ts +387 -0
  134. package/src/merge/queue.test.ts +359 -0
  135. package/src/merge/queue.ts +231 -0
  136. package/src/merge/resolver.test.ts +1345 -0
  137. package/src/merge/resolver.ts +645 -0
  138. package/src/metrics/store.test.ts +667 -0
  139. package/src/metrics/store.ts +445 -0
  140. package/src/metrics/summary.test.ts +398 -0
  141. package/src/metrics/summary.ts +178 -0
  142. package/src/metrics/transcript.test.ts +356 -0
  143. package/src/metrics/transcript.ts +175 -0
  144. package/src/mulch/client.test.ts +671 -0
  145. package/src/mulch/client.ts +332 -0
  146. package/src/sessions/compat.test.ts +280 -0
  147. package/src/sessions/compat.ts +104 -0
  148. package/src/sessions/store.test.ts +873 -0
  149. package/src/sessions/store.ts +494 -0
  150. package/src/test-helpers.test.ts +124 -0
  151. package/src/test-helpers.ts +126 -0
  152. package/src/tracker/beads.ts +56 -0
  153. package/src/tracker/factory.test.ts +80 -0
  154. package/src/tracker/factory.ts +64 -0
  155. package/src/tracker/seeds.ts +182 -0
  156. package/src/tracker/types.ts +52 -0
  157. package/src/types.ts +724 -0
  158. package/src/watchdog/daemon.test.ts +1975 -0
  159. package/src/watchdog/daemon.ts +671 -0
  160. package/src/watchdog/health.test.ts +431 -0
  161. package/src/watchdog/health.ts +264 -0
  162. package/src/watchdog/triage.test.ts +164 -0
  163. package/src/watchdog/triage.ts +179 -0
  164. package/src/worktree/manager.test.ts +439 -0
  165. package/src/worktree/manager.ts +198 -0
  166. package/src/worktree/tmux.test.ts +1009 -0
  167. package/src/worktree/tmux.ts +509 -0
  168. package/templates/CLAUDE.md.tmpl +89 -0
  169. package/templates/hooks.json.tmpl +105 -0
  170. package/templates/overlay.md.tmpl +81 -0
@@ -0,0 +1,126 @@
1
+ /**
2
+ * Smart tool argument filter for observability events.
3
+ *
4
+ * Reduces tool invocation payloads from ~20KB to ~200 bytes by keeping only
5
+ * the fields useful for post-mortem analysis. Each tool type has a custom
6
+ * filter that preserves identifying information while dropping bulky content.
7
+ */
8
+
9
+ export interface FilteredToolArgs {
10
+ args: Record<string, unknown>;
11
+ summary: string;
12
+ }
13
+
14
+ /**
15
+ * Filter tool arguments down to what matters for observability.
16
+ *
17
+ * Keeps identifying fields (paths, patterns, commands) and drops bulk content
18
+ * (file bodies, old/new strings, timeouts). Returns a compact summary string
19
+ * suitable for log lines.
20
+ */
21
+ export function filterToolArgs(
22
+ toolName: string,
23
+ toolInput: Record<string, unknown>,
24
+ ): FilteredToolArgs {
25
+ const handler = TOOL_FILTERS[toolName];
26
+ if (handler) {
27
+ return handler(toolInput);
28
+ }
29
+ return { args: {}, summary: toolName };
30
+ }
31
+
32
+ type ToolFilter = (input: Record<string, unknown>) => FilteredToolArgs;
33
+
34
+ function pickDefined(input: Record<string, unknown>, keys: string[]): Record<string, unknown> {
35
+ const result: Record<string, unknown> = {};
36
+ for (const key of keys) {
37
+ if (key in input && input[key] !== undefined) {
38
+ result[key] = input[key];
39
+ }
40
+ }
41
+ return result;
42
+ }
43
+
44
+ function truncate(value: unknown, maxLen: number): string {
45
+ const str = typeof value === "string" ? value : String(value ?? "");
46
+ if (str.length <= maxLen) {
47
+ return str;
48
+ }
49
+ return `${str.slice(0, maxLen)}...`;
50
+ }
51
+
52
+ const TOOL_FILTERS: Record<string, ToolFilter> = {
53
+ Bash: (input) => {
54
+ const args = pickDefined(input, ["command", "description"]);
55
+ const cmd = typeof input.command === "string" ? input.command : "";
56
+ return { args, summary: `bash: ${truncate(cmd, 80)}` };
57
+ },
58
+
59
+ Read: (input) => {
60
+ const args = pickDefined(input, ["file_path", "offset", "limit"]);
61
+ const filePath = typeof input.file_path === "string" ? input.file_path : "";
62
+ const offset = typeof input.offset === "number" ? input.offset : undefined;
63
+ const limit = typeof input.limit === "number" ? input.limit : undefined;
64
+ let summary: string;
65
+ if (offset !== undefined && limit !== undefined) {
66
+ summary = `read: ${filePath} (lines ${offset}-${offset + limit})`;
67
+ } else if (offset !== undefined) {
68
+ summary = `read: ${filePath} (from line ${offset})`;
69
+ } else if (limit !== undefined) {
70
+ summary = `read: ${filePath} (first ${limit} lines)`;
71
+ } else {
72
+ summary = `read: ${filePath}`;
73
+ }
74
+ return { args, summary };
75
+ },
76
+
77
+ Write: (input) => {
78
+ const args = pickDefined(input, ["file_path"]);
79
+ const filePath = typeof input.file_path === "string" ? input.file_path : "";
80
+ return { args, summary: `write: ${filePath}` };
81
+ },
82
+
83
+ Edit: (input) => {
84
+ const args = pickDefined(input, ["file_path"]);
85
+ const filePath = typeof input.file_path === "string" ? input.file_path : "";
86
+ return { args, summary: `edit: ${filePath}` };
87
+ },
88
+
89
+ Glob: (input) => {
90
+ const args = pickDefined(input, ["pattern", "path"]);
91
+ const pattern = typeof input.pattern === "string" ? input.pattern : "";
92
+ const path = typeof input.path === "string" ? input.path : "";
93
+ const summary = path ? `glob: ${pattern} in ${path}` : `glob: ${pattern}`;
94
+ return { args, summary };
95
+ },
96
+
97
+ Grep: (input) => {
98
+ const args = pickDefined(input, ["pattern", "path", "glob", "output_mode"]);
99
+ const pattern = typeof input.pattern === "string" ? input.pattern : "";
100
+ const path = typeof input.path === "string" ? input.path : "";
101
+ const summary = path ? `grep: "${pattern}" in ${path}` : `grep: "${pattern}"`;
102
+ return { args, summary };
103
+ },
104
+
105
+ WebFetch: (input) => {
106
+ const args = pickDefined(input, ["url"]);
107
+ const url = typeof input.url === "string" ? input.url : "";
108
+ return { args, summary: `fetch: ${url}` };
109
+ },
110
+
111
+ WebSearch: (input) => {
112
+ const args = pickDefined(input, ["query"]);
113
+ const query = typeof input.query === "string" ? input.query : "";
114
+ return { args, summary: `search: ${query}` };
115
+ },
116
+
117
+ Task: (input) => {
118
+ const args = pickDefined(input, ["description", "subagent_type"]);
119
+ const description = typeof input.description === "string" ? input.description : "";
120
+ const subagentType = typeof input.subagent_type === "string" ? input.subagent_type : "";
121
+ const summary = subagentType
122
+ ? `task: ${description} (${subagentType})`
123
+ : `task: ${description}`;
124
+ return { args, summary };
125
+ },
126
+ };
package/src/index.ts ADDED
@@ -0,0 +1,316 @@
1
+ #!/usr/bin/env bun
2
+
3
+ /**
4
+ * Overstory CLI — main entry point and command router.
5
+ *
6
+ * Routes subcommands to their respective handlers in src/commands/.
7
+ * Usage: overstory <command> [args...]
8
+ */
9
+
10
+ import { agentsCommand } from "./commands/agents.ts";
11
+ 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";
21
+ import { initCommand } from "./commands/init.ts";
22
+ import { inspectCommand } from "./commands/inspect.ts";
23
+ import { logCommand } from "./commands/log.ts";
24
+ import { logsCommand } from "./commands/logs.ts";
25
+ import { mailCommand } from "./commands/mail.ts";
26
+ import { mergeCommand } from "./commands/merge.ts";
27
+ import { metricsCommand } from "./commands/metrics.ts";
28
+ import { monitorCommand } from "./commands/monitor.ts";
29
+ import { nudgeCommand } from "./commands/nudge.ts";
30
+ import { primeCommand } from "./commands/prime.ts";
31
+ import { replayCommand } from "./commands/replay.ts";
32
+ import { runCommand } from "./commands/run.ts";
33
+ import { slingCommand } from "./commands/sling.ts";
34
+ import { specCommand } from "./commands/spec.ts";
35
+ import { statusCommand } from "./commands/status.ts";
36
+ import { stopCommand } from "./commands/stop.ts";
37
+ import { supervisorCommand } from "./commands/supervisor.ts";
38
+ import { traceCommand } from "./commands/trace.ts";
39
+ import { watchCommand } from "./commands/watch.ts";
40
+ import { worktreeCommand } from "./commands/worktree.ts";
41
+ import { OverstoryError, WorktreeError } from "./errors.ts";
42
+ import { setQuiet } from "./logging/color.ts";
43
+
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.`;
89
+
90
+ const COMMANDS = [
91
+ "agents",
92
+ "init",
93
+ "sling",
94
+ "spec",
95
+ "prime",
96
+ "stop",
97
+ "status",
98
+ "dashboard",
99
+ "inspect",
100
+ "clean",
101
+ "doctor",
102
+ "coordinator",
103
+ "supervisor",
104
+ "hooks",
105
+ "monitor",
106
+ "mail",
107
+ "merge",
108
+ "nudge",
109
+ "group",
110
+ "worktree",
111
+ "log",
112
+ "logs",
113
+ "watch",
114
+ "trace",
115
+ "feed",
116
+ "errors",
117
+ "replay",
118
+ "run",
119
+ "costs",
120
+ "metrics",
121
+ ];
122
+
123
+ function editDistance(a: string, b: string): number {
124
+ const m = a.length;
125
+ const n = b.length;
126
+ // Use a flat 1D array to avoid nested indexing warnings
127
+ const dp = new Array<number>((m + 1) * (n + 1)).fill(0);
128
+ const idx = (i: number, j: number) => i * (n + 1) + j;
129
+ for (let i = 0; i <= m; i++) dp[idx(i, 0)] = i;
130
+ for (let j = 0; j <= n; j++) dp[idx(0, j)] = j;
131
+ for (let i = 1; i <= m; i++) {
132
+ for (let j = 1; j <= n; j++) {
133
+ const cost = a[i - 1] === b[j - 1] ? 0 : 1;
134
+ const del = (dp[idx(i - 1, j)] ?? 0) + 1;
135
+ const ins = (dp[idx(i, j - 1)] ?? 0) + 1;
136
+ const sub = (dp[idx(i - 1, j - 1)] ?? 0) + cost;
137
+ dp[idx(i, j)] = Math.min(del, ins, sub);
138
+ }
139
+ }
140
+ return dp[idx(m, n)] ?? 0;
141
+ }
142
+
143
+ function suggestCommand(input: string): string | undefined {
144
+ let bestMatch: string | undefined;
145
+ let bestDist = 3; // Only suggest if distance <= 2
146
+ for (const cmd of COMMANDS) {
147
+ const dist = editDistance(input, cmd);
148
+ if (dist < bestDist) {
149
+ bestDist = dist;
150
+ bestMatch = cmd;
151
+ }
152
+ }
153
+ return bestMatch;
154
+ }
155
+
156
+ async function main(): Promise<void> {
157
+ const args = process.argv.slice(2);
158
+
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) {
163
+ 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
+ }
171
+
172
+ const command = args[0];
173
+ const commandArgs = args.slice(1);
174
+
175
+ if (!command || command === "--help" || command === "-h") {
176
+ process.stdout.write(`${HELP}\n`);
177
+ return;
178
+ }
179
+
180
+ if (command === "--version" || command === "-v") {
181
+ process.stdout.write(`overstory v${VERSION}\n`);
182
+ return;
183
+ }
184
+
185
+ if (command === "--completions") {
186
+ completionsCommand(commandArgs);
187
+ return;
188
+ }
189
+
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;
227
+ }
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`);
292
+ process.exit(1);
293
+ }
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`);
311
+ }
312
+ process.exit(1);
313
+ }
314
+ process.stderr.write(`Unknown error: ${String(err)}\n`);
315
+ process.exit(1);
316
+ });