@katyella/legio 0.1.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 (219) hide show
  1. package/CHANGELOG.md +422 -0
  2. package/LICENSE +21 -0
  3. package/README.md +555 -0
  4. package/agents/builder.md +141 -0
  5. package/agents/coordinator.md +351 -0
  6. package/agents/cto.md +196 -0
  7. package/agents/gateway.md +276 -0
  8. package/agents/lead.md +281 -0
  9. package/agents/merger.md +156 -0
  10. package/agents/monitor.md +212 -0
  11. package/agents/reviewer.md +142 -0
  12. package/agents/scout.md +131 -0
  13. package/agents/supervisor.md +416 -0
  14. package/bin/legio.mjs +38 -0
  15. package/package.json +77 -0
  16. package/src/agents/checkpoint.test.ts +88 -0
  17. package/src/agents/checkpoint.ts +102 -0
  18. package/src/agents/hooks-deployer.test.ts +1820 -0
  19. package/src/agents/hooks-deployer.ts +574 -0
  20. package/src/agents/identity.test.ts +614 -0
  21. package/src/agents/identity.ts +385 -0
  22. package/src/agents/lifecycle.test.ts +202 -0
  23. package/src/agents/lifecycle.ts +184 -0
  24. package/src/agents/manifest.test.ts +558 -0
  25. package/src/agents/manifest.ts +297 -0
  26. package/src/agents/overlay.test.ts +592 -0
  27. package/src/agents/overlay.ts +316 -0
  28. package/src/beads/client.test.ts +210 -0
  29. package/src/beads/client.ts +227 -0
  30. package/src/beads/molecules.test.ts +320 -0
  31. package/src/beads/molecules.ts +209 -0
  32. package/src/commands/agents.test.ts +325 -0
  33. package/src/commands/agents.ts +286 -0
  34. package/src/commands/clean.test.ts +730 -0
  35. package/src/commands/clean.ts +653 -0
  36. package/src/commands/completions.test.ts +346 -0
  37. package/src/commands/completions.ts +950 -0
  38. package/src/commands/coordinator.test.ts +1524 -0
  39. package/src/commands/coordinator.ts +880 -0
  40. package/src/commands/costs.test.ts +1015 -0
  41. package/src/commands/costs.ts +473 -0
  42. package/src/commands/dashboard.test.ts +94 -0
  43. package/src/commands/dashboard.ts +607 -0
  44. package/src/commands/doctor.test.ts +295 -0
  45. package/src/commands/doctor.ts +213 -0
  46. package/src/commands/down.test.ts +308 -0
  47. package/src/commands/down.ts +124 -0
  48. package/src/commands/errors.test.ts +648 -0
  49. package/src/commands/errors.ts +255 -0
  50. package/src/commands/feed.test.ts +579 -0
  51. package/src/commands/feed.ts +368 -0
  52. package/src/commands/gateway.test.ts +698 -0
  53. package/src/commands/gateway.ts +419 -0
  54. package/src/commands/group.test.ts +262 -0
  55. package/src/commands/group.ts +539 -0
  56. package/src/commands/hooks.test.ts +292 -0
  57. package/src/commands/hooks.ts +210 -0
  58. package/src/commands/init.test.ts +211 -0
  59. package/src/commands/init.ts +622 -0
  60. package/src/commands/inspect.test.ts +670 -0
  61. package/src/commands/inspect.ts +455 -0
  62. package/src/commands/log.test.ts +1556 -0
  63. package/src/commands/log.ts +752 -0
  64. package/src/commands/logs.test.ts +379 -0
  65. package/src/commands/logs.ts +544 -0
  66. package/src/commands/mail.test.ts +1726 -0
  67. package/src/commands/mail.ts +926 -0
  68. package/src/commands/merge.test.ts +676 -0
  69. package/src/commands/merge.ts +374 -0
  70. package/src/commands/metrics.test.ts +444 -0
  71. package/src/commands/metrics.ts +150 -0
  72. package/src/commands/monitor.test.ts +151 -0
  73. package/src/commands/monitor.ts +394 -0
  74. package/src/commands/nudge.test.ts +230 -0
  75. package/src/commands/nudge.ts +373 -0
  76. package/src/commands/prime.test.ts +467 -0
  77. package/src/commands/prime.ts +386 -0
  78. package/src/commands/replay.test.ts +742 -0
  79. package/src/commands/replay.ts +367 -0
  80. package/src/commands/run.test.ts +443 -0
  81. package/src/commands/run.ts +365 -0
  82. package/src/commands/server.test.ts +626 -0
  83. package/src/commands/server.ts +298 -0
  84. package/src/commands/sling.test.ts +810 -0
  85. package/src/commands/sling.ts +700 -0
  86. package/src/commands/spec.test.ts +206 -0
  87. package/src/commands/spec.ts +171 -0
  88. package/src/commands/status.test.ts +276 -0
  89. package/src/commands/status.ts +339 -0
  90. package/src/commands/stop.test.ts +357 -0
  91. package/src/commands/stop.ts +119 -0
  92. package/src/commands/supervisor.test.ts +186 -0
  93. package/src/commands/supervisor.ts +544 -0
  94. package/src/commands/trace.test.ts +746 -0
  95. package/src/commands/trace.ts +332 -0
  96. package/src/commands/up.test.ts +597 -0
  97. package/src/commands/up.ts +275 -0
  98. package/src/commands/watch.test.ts +152 -0
  99. package/src/commands/watch.ts +238 -0
  100. package/src/commands/worktree.test.ts +648 -0
  101. package/src/commands/worktree.ts +266 -0
  102. package/src/config.test.ts +496 -0
  103. package/src/config.ts +616 -0
  104. package/src/doctor/agents.test.ts +448 -0
  105. package/src/doctor/agents.ts +396 -0
  106. package/src/doctor/config-check.test.ts +184 -0
  107. package/src/doctor/config-check.ts +185 -0
  108. package/src/doctor/consistency.test.ts +645 -0
  109. package/src/doctor/consistency.ts +294 -0
  110. package/src/doctor/databases.test.ts +284 -0
  111. package/src/doctor/databases.ts +211 -0
  112. package/src/doctor/dependencies.test.ts +150 -0
  113. package/src/doctor/dependencies.ts +179 -0
  114. package/src/doctor/logs.test.ts +244 -0
  115. package/src/doctor/logs.ts +295 -0
  116. package/src/doctor/merge-queue.test.ts +210 -0
  117. package/src/doctor/merge-queue.ts +144 -0
  118. package/src/doctor/structure.test.ts +285 -0
  119. package/src/doctor/structure.ts +195 -0
  120. package/src/doctor/types.ts +37 -0
  121. package/src/doctor/version.test.ts +130 -0
  122. package/src/doctor/version.ts +131 -0
  123. package/src/e2e/chat-flow.test.ts +346 -0
  124. package/src/e2e/init-sling-lifecycle.test.ts +288 -0
  125. package/src/errors.test.ts +21 -0
  126. package/src/errors.ts +246 -0
  127. package/src/events/store.test.ts +660 -0
  128. package/src/events/store.ts +344 -0
  129. package/src/events/tool-filter.test.ts +330 -0
  130. package/src/events/tool-filter.ts +126 -0
  131. package/src/global-setup.ts +14 -0
  132. package/src/index.ts +339 -0
  133. package/src/insights/analyzer.test.ts +466 -0
  134. package/src/insights/analyzer.ts +203 -0
  135. package/src/logging/color.test.ts +118 -0
  136. package/src/logging/color.ts +71 -0
  137. package/src/logging/logger.test.ts +812 -0
  138. package/src/logging/logger.ts +266 -0
  139. package/src/logging/reporter.test.ts +258 -0
  140. package/src/logging/reporter.ts +109 -0
  141. package/src/logging/sanitizer.test.ts +190 -0
  142. package/src/logging/sanitizer.ts +57 -0
  143. package/src/mail/broadcast.test.ts +203 -0
  144. package/src/mail/broadcast.ts +92 -0
  145. package/src/mail/client.test.ts +873 -0
  146. package/src/mail/client.ts +236 -0
  147. package/src/mail/store.test.ts +815 -0
  148. package/src/mail/store.ts +402 -0
  149. package/src/merge/queue.test.ts +449 -0
  150. package/src/merge/queue.ts +262 -0
  151. package/src/merge/resolver.test.ts +1453 -0
  152. package/src/merge/resolver.ts +759 -0
  153. package/src/metrics/store.test.ts +1167 -0
  154. package/src/metrics/store.ts +511 -0
  155. package/src/metrics/summary.test.ts +397 -0
  156. package/src/metrics/summary.ts +178 -0
  157. package/src/metrics/transcript.test.ts +643 -0
  158. package/src/metrics/transcript.ts +351 -0
  159. package/src/mulch/client.test.ts +547 -0
  160. package/src/mulch/client.ts +416 -0
  161. package/src/server/audit-store.test.ts +384 -0
  162. package/src/server/audit-store.ts +257 -0
  163. package/src/server/headless.test.ts +180 -0
  164. package/src/server/headless.ts +151 -0
  165. package/src/server/index.test.ts +241 -0
  166. package/src/server/index.ts +317 -0
  167. package/src/server/public/app.js +187 -0
  168. package/src/server/public/apple-touch-icon.png +0 -0
  169. package/src/server/public/components/agent-badge.js +37 -0
  170. package/src/server/public/components/data-table.js +114 -0
  171. package/src/server/public/components/gateway-chat.js +256 -0
  172. package/src/server/public/components/issue-card.js +96 -0
  173. package/src/server/public/components/layout.js +88 -0
  174. package/src/server/public/components/message-bubble.js +120 -0
  175. package/src/server/public/components/stat-card.js +26 -0
  176. package/src/server/public/components/terminal-panel.js +140 -0
  177. package/src/server/public/favicon-16.png +0 -0
  178. package/src/server/public/favicon-32.png +0 -0
  179. package/src/server/public/favicon.ico +0 -0
  180. package/src/server/public/favicon.png +0 -0
  181. package/src/server/public/index.html +64 -0
  182. package/src/server/public/lib/api.js +35 -0
  183. package/src/server/public/lib/markdown.js +8 -0
  184. package/src/server/public/lib/preact-setup.js +8 -0
  185. package/src/server/public/lib/state.js +99 -0
  186. package/src/server/public/lib/utils.js +309 -0
  187. package/src/server/public/lib/ws.js +79 -0
  188. package/src/server/public/views/chat.js +983 -0
  189. package/src/server/public/views/costs.js +692 -0
  190. package/src/server/public/views/dashboard.js +781 -0
  191. package/src/server/public/views/gateway-chat.js +622 -0
  192. package/src/server/public/views/inspect.js +399 -0
  193. package/src/server/public/views/issues.js +470 -0
  194. package/src/server/public/views/setup.js +94 -0
  195. package/src/server/public/views/task-detail.js +422 -0
  196. package/src/server/routes.test.ts +3816 -0
  197. package/src/server/routes.ts +1964 -0
  198. package/src/server/websocket.test.ts +288 -0
  199. package/src/server/websocket.ts +196 -0
  200. package/src/sessions/compat.test.ts +109 -0
  201. package/src/sessions/compat.ts +17 -0
  202. package/src/sessions/store.test.ts +969 -0
  203. package/src/sessions/store.ts +480 -0
  204. package/src/test-helpers.test.ts +97 -0
  205. package/src/test-helpers.ts +143 -0
  206. package/src/types.ts +708 -0
  207. package/src/watchdog/daemon.test.ts +1233 -0
  208. package/src/watchdog/daemon.ts +533 -0
  209. package/src/watchdog/health.test.ts +371 -0
  210. package/src/watchdog/health.ts +248 -0
  211. package/src/watchdog/triage.test.ts +162 -0
  212. package/src/watchdog/triage.ts +193 -0
  213. package/src/worktree/manager.test.ts +444 -0
  214. package/src/worktree/manager.ts +224 -0
  215. package/src/worktree/tmux.test.ts +1238 -0
  216. package/src/worktree/tmux.ts +644 -0
  217. package/templates/CLAUDE.md.tmpl +89 -0
  218. package/templates/hooks.json.tmpl +132 -0
  219. package/templates/overlay.md.tmpl +79 -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
+ };
@@ -0,0 +1,14 @@
1
+ import { cleanupTempDir, createTempGitRepo } from "./test-helpers.js";
2
+
3
+ let fixtureRepoPath: string | undefined;
4
+
5
+ export async function setup() {
6
+ fixtureRepoPath = await createTempGitRepo();
7
+ process.env.LEGIO_TEST_FIXTURE_REPO = fixtureRepoPath;
8
+ }
9
+
10
+ export async function teardown() {
11
+ if (fixtureRepoPath) {
12
+ await cleanupTempDir(fixtureRepoPath);
13
+ }
14
+ }
package/src/index.ts ADDED
@@ -0,0 +1,339 @@
1
+ #!/usr/bin/env tsx
2
+
3
+ /**
4
+ * Legio CLI — main entry point and command router.
5
+ *
6
+ * Routes subcommands to their respective handlers in src/commands/.
7
+ * Usage: legio <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 { downCommand } from "./commands/down.ts";
18
+ import { errorsCommand } from "./commands/errors.ts";
19
+ import { feedCommand } from "./commands/feed.ts";
20
+ import { gatewayCommand } from "./commands/gateway.ts";
21
+ import { groupCommand } from "./commands/group.ts";
22
+ import { hooksCommand } from "./commands/hooks.ts";
23
+ import { initCommand } from "./commands/init.ts";
24
+ import { inspectCommand } from "./commands/inspect.ts";
25
+ import { logCommand } from "./commands/log.ts";
26
+ import { logsCommand } from "./commands/logs.ts";
27
+ import { mailCommand } from "./commands/mail.ts";
28
+ import { mergeCommand } from "./commands/merge.ts";
29
+ import { metricsCommand } from "./commands/metrics.ts";
30
+ import { monitorCommand } from "./commands/monitor.ts";
31
+ import { nudgeCommand } from "./commands/nudge.ts";
32
+ import { primeCommand } from "./commands/prime.ts";
33
+ import { replayCommand } from "./commands/replay.ts";
34
+ import { runCommand } from "./commands/run.ts";
35
+ import { serverCommand } from "./commands/server.ts";
36
+ import { slingCommand } from "./commands/sling.ts";
37
+ import { specCommand } from "./commands/spec.ts";
38
+ import { statusCommand } from "./commands/status.ts";
39
+ import { stopCommand } from "./commands/stop.ts";
40
+ import { supervisorCommand } from "./commands/supervisor.ts";
41
+ import { traceCommand } from "./commands/trace.ts";
42
+ import { upCommand } from "./commands/up.ts";
43
+ import { watchCommand } from "./commands/watch.ts";
44
+ import { worktreeCommand } from "./commands/worktree.ts";
45
+ import { LegioError, WorktreeError } from "./errors.ts";
46
+ import { setQuiet } from "./logging/color.ts";
47
+
48
+ const VERSION = "0.1.0";
49
+
50
+ const HELP = `legio v${VERSION} — Multi-agent orchestration for Claude Code
51
+
52
+ Usage: legio <command> [args...]
53
+
54
+ Commands:
55
+ agents <sub> Discover and query agents (discover)
56
+ init Initialize .legio/ in current project
57
+ sling <task-id> Spawn a worker agent
58
+ spec <sub> Manage task specs (write)
59
+ prime Load context for orchestrator/agent
60
+ status Show all active agents and project state
61
+ dashboard Live TUI dashboard for agent monitoring
62
+ inspect <agent> Deep inspection of a single agent
63
+ coordinator <sub> Persistent coordinator agent (start/stop/status)
64
+ gateway <sub> Gateway planning agent (start/stop/status)
65
+ supervisor <sub> Per-project supervisor agent (start/stop/status)
66
+ hooks <sub> Manage orchestrator hooks (install/uninstall/status)
67
+ mail <sub> Mail system (send/check/list/read/reply)
68
+ monitor <sub> Tier 2 monitor agent (start/stop/status)
69
+ merge Merge agent branches into canonical
70
+ nudge <agent> [msg] Send a text nudge to an agent
71
+ group <sub> Task groups (create/status/add/remove/list)
72
+ clean Wipe runtime state (nuclear cleanup)
73
+ doctor Run health checks on legio setup
74
+ worktree <sub> Manage worktrees (list/clean)
75
+ log <event> Log a hook event
76
+ logs [options] Query NDJSON logs across agents
77
+ watch Start watchdog daemon
78
+ feed [options] Unified real-time event stream across all agents
79
+ trace <target> Chronological event timeline for agent/bead
80
+ errors [options] Aggregated error view across agents
81
+ run [sub] Manage runs (list/show/complete)
82
+ replay [options] Interleaved chronological replay across agents
83
+ costs [options] Token/cost analysis and breakdown
84
+ metrics Show session metrics
85
+ server <sub> Local web UI (start)
86
+ up Start everything (init + server + coordinator)
87
+ down Stop everything (coordinator + server)
88
+ stop Stop active agent sessions (deepest-first)
89
+ Options:
90
+ --help, -h Show this help
91
+ --version, -v Show version
92
+ --quiet, -q Suppress non-error output
93
+ --completions <shell> Generate shell completions (bash, zsh, fish)
94
+
95
+ Run 'legio <command> --help' for command-specific help.`;
96
+
97
+ const COMMANDS = [
98
+ "agents",
99
+ "init",
100
+ "sling",
101
+ "spec",
102
+ "prime",
103
+ "status",
104
+ "dashboard",
105
+ "inspect",
106
+ "clean",
107
+ "doctor",
108
+ "coordinator",
109
+ "gateway",
110
+ "supervisor",
111
+ "hooks",
112
+ "monitor",
113
+ "mail",
114
+ "merge",
115
+ "nudge",
116
+ "group",
117
+ "worktree",
118
+ "log",
119
+ "logs",
120
+ "watch",
121
+ "trace",
122
+ "feed",
123
+ "errors",
124
+ "replay",
125
+ "run",
126
+ "costs",
127
+ "metrics",
128
+ "server",
129
+ "up",
130
+ "down",
131
+ "stop",
132
+ ];
133
+
134
+ function editDistance(a: string, b: string): number {
135
+ const m = a.length;
136
+ const n = b.length;
137
+ // Use a flat 1D array to avoid nested indexing warnings
138
+ const dp = new Array<number>((m + 1) * (n + 1)).fill(0);
139
+ const idx = (i: number, j: number) => i * (n + 1) + j;
140
+ for (let i = 0; i <= m; i++) dp[idx(i, 0)] = i;
141
+ for (let j = 0; j <= n; j++) dp[idx(0, j)] = j;
142
+ for (let i = 1; i <= m; i++) {
143
+ for (let j = 1; j <= n; j++) {
144
+ const cost = a[i - 1] === b[j - 1] ? 0 : 1;
145
+ const del = (dp[idx(i - 1, j)] ?? 0) + 1;
146
+ const ins = (dp[idx(i, j - 1)] ?? 0) + 1;
147
+ const sub = (dp[idx(i - 1, j - 1)] ?? 0) + cost;
148
+ dp[idx(i, j)] = Math.min(del, ins, sub);
149
+ }
150
+ }
151
+ return dp[idx(m, n)] ?? 0;
152
+ }
153
+
154
+ function suggestCommand(input: string): string | undefined {
155
+ let bestMatch: string | undefined;
156
+ let bestDist = 3; // Only suggest if distance <= 2
157
+ for (const cmd of COMMANDS) {
158
+ const dist = editDistance(input, cmd);
159
+ if (dist < bestDist) {
160
+ bestDist = dist;
161
+ bestMatch = cmd;
162
+ }
163
+ }
164
+ return bestMatch;
165
+ }
166
+
167
+ async function main(): Promise<void> {
168
+ const args = process.argv.slice(2);
169
+
170
+ // Parse global flags before command routing
171
+ const quietIndex = args.indexOf("--quiet");
172
+ const qIndex = args.indexOf("-q");
173
+ if (quietIndex !== -1 || qIndex !== -1) {
174
+ setQuiet(true);
175
+ // Remove the flag from args so commands do not see it
176
+ if (quietIndex !== -1) args.splice(quietIndex, 1);
177
+ if (qIndex !== -1) {
178
+ const idx = args.indexOf("-q");
179
+ if (idx !== -1) args.splice(idx, 1);
180
+ }
181
+ }
182
+
183
+ const command = args[0];
184
+ const commandArgs = args.slice(1);
185
+
186
+ if (!command || command === "--help" || command === "-h") {
187
+ process.stdout.write(`${HELP}\n`);
188
+ return;
189
+ }
190
+
191
+ if (command === "--version" || command === "-v") {
192
+ process.stdout.write(`legio v${VERSION}\n`);
193
+ return;
194
+ }
195
+
196
+ if (command === "--completions") {
197
+ completionsCommand(commandArgs);
198
+ return;
199
+ }
200
+
201
+ switch (command) {
202
+ case "agents":
203
+ await agentsCommand(commandArgs);
204
+ break;
205
+ case "init":
206
+ await initCommand(commandArgs);
207
+ break;
208
+ case "sling":
209
+ await slingCommand(commandArgs);
210
+ break;
211
+ case "spec":
212
+ await specCommand(commandArgs);
213
+ break;
214
+ case "prime":
215
+ await primeCommand(commandArgs);
216
+ break;
217
+ case "status":
218
+ await statusCommand(commandArgs);
219
+ break;
220
+ case "dashboard":
221
+ await dashboardCommand(commandArgs);
222
+ break;
223
+ case "inspect":
224
+ await inspectCommand(commandArgs);
225
+ break;
226
+ case "clean":
227
+ await cleanCommand(commandArgs);
228
+ break;
229
+ case "doctor": {
230
+ const exitCode = await doctorCommand(commandArgs);
231
+ if (exitCode !== undefined) {
232
+ process.exitCode = exitCode;
233
+ }
234
+ break;
235
+ }
236
+ case "coordinator":
237
+ await coordinatorCommand(commandArgs);
238
+ break;
239
+ case "gateway":
240
+ await gatewayCommand(commandArgs);
241
+ break;
242
+ case "supervisor":
243
+ await supervisorCommand(commandArgs);
244
+ break;
245
+ case "hooks":
246
+ await hooksCommand(commandArgs);
247
+ break;
248
+ case "monitor":
249
+ await monitorCommand(commandArgs);
250
+ break;
251
+ case "mail":
252
+ await mailCommand(commandArgs);
253
+ break;
254
+ case "merge":
255
+ await mergeCommand(commandArgs);
256
+ break;
257
+ case "nudge":
258
+ await nudgeCommand(commandArgs);
259
+ break;
260
+ case "group":
261
+ await groupCommand(commandArgs);
262
+ break;
263
+ case "worktree":
264
+ await worktreeCommand(commandArgs);
265
+ break;
266
+ case "log":
267
+ await logCommand(commandArgs);
268
+ break;
269
+ case "logs":
270
+ await logsCommand(commandArgs);
271
+ break;
272
+ case "watch":
273
+ await watchCommand(commandArgs);
274
+ break;
275
+ case "trace":
276
+ await traceCommand(commandArgs);
277
+ break;
278
+ case "feed":
279
+ await feedCommand(commandArgs);
280
+ break;
281
+ case "errors":
282
+ await errorsCommand(commandArgs);
283
+ break;
284
+ case "replay":
285
+ await replayCommand(commandArgs);
286
+ break;
287
+ case "run":
288
+ await runCommand(commandArgs);
289
+ break;
290
+ case "costs":
291
+ await costsCommand(commandArgs);
292
+ break;
293
+ case "metrics":
294
+ await metricsCommand(commandArgs);
295
+ break;
296
+ case "server":
297
+ await serverCommand(commandArgs);
298
+ break;
299
+ case "up":
300
+ await upCommand(commandArgs);
301
+ break;
302
+ case "down":
303
+ await downCommand(commandArgs);
304
+ break;
305
+ case "stop":
306
+ await stopCommand(commandArgs);
307
+ break;
308
+ default: {
309
+ process.stderr.write(`Unknown command: ${command}\n`);
310
+ const suggestion = suggestCommand(command);
311
+ if (suggestion) {
312
+ process.stderr.write(`Did you mean '${suggestion}'?\n`);
313
+ }
314
+ process.stderr.write(`Run 'legio --help' for usage.\n`);
315
+ process.exit(1);
316
+ }
317
+ }
318
+ }
319
+
320
+ main().catch((err: unknown) => {
321
+ // Friendly message when running outside a git repository
322
+ if (err instanceof WorktreeError && err.message.includes("not a git repository")) {
323
+ process.stderr.write("Not in an legio project. Run 'legio init' first.\n");
324
+ process.exit(1);
325
+ }
326
+ if (err instanceof LegioError) {
327
+ process.stderr.write(`Error [${err.code}]: ${err.message}\n`);
328
+ process.exit(1);
329
+ }
330
+ if (err instanceof Error) {
331
+ process.stderr.write(`Error: ${err.message}\n`);
332
+ if (process.argv.includes("--verbose")) {
333
+ process.stderr.write(`${err.stack}\n`);
334
+ }
335
+ process.exit(1);
336
+ }
337
+ process.stderr.write(`Unknown error: ${String(err)}\n`);
338
+ process.exit(1);
339
+ });