thehood 0.1.0-preview.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 (274) hide show
  1. package/CODE_OF_CONDUCT.md +21 -0
  2. package/CONTRIBUTING.md +58 -0
  3. package/LICENSE +21 -0
  4. package/PRIVACY.md +49 -0
  5. package/README.md +264 -0
  6. package/SECURITY.md +31 -0
  7. package/dist/bridges/chatgptWebBridge.d.ts +2 -0
  8. package/dist/bridges/chatgptWebBridge.js +981 -0
  9. package/dist/bridges/chatgptWebBridge.js.map +1 -0
  10. package/dist/cli/args.d.ts +9 -0
  11. package/dist/cli/args.js +82 -0
  12. package/dist/cli/args.js.map +1 -0
  13. package/dist/cli/format.d.ts +56 -0
  14. package/dist/cli/format.js +752 -0
  15. package/dist/cli/format.js.map +1 -0
  16. package/dist/cli/main.d.ts +2 -0
  17. package/dist/cli/main.js +996 -0
  18. package/dist/cli/main.js.map +1 -0
  19. package/dist/cli/mcpConfig.d.ts +36 -0
  20. package/dist/cli/mcpConfig.js +98 -0
  21. package/dist/cli/mcpConfig.js.map +1 -0
  22. package/dist/index.d.ts +37 -0
  23. package/dist/index.js +38 -0
  24. package/dist/index.js.map +1 -0
  25. package/dist/mcp/protocol.d.ts +44 -0
  26. package/dist/mcp/protocol.js +33 -0
  27. package/dist/mcp/protocol.js.map +1 -0
  28. package/dist/mcp/server.d.ts +1 -0
  29. package/dist/mcp/server.js +106 -0
  30. package/dist/mcp/server.js.map +1 -0
  31. package/dist/mcp/tools.d.ts +10 -0
  32. package/dist/mcp/tools.js +2200 -0
  33. package/dist/mcp/tools.js.map +1 -0
  34. package/dist/mcp/validation.d.ts +8 -0
  35. package/dist/mcp/validation.js +67 -0
  36. package/dist/mcp/validation.js.map +1 -0
  37. package/dist/providers/chatgptWeb.d.ts +2 -0
  38. package/dist/providers/chatgptWeb.js +26 -0
  39. package/dist/providers/chatgptWeb.js.map +1 -0
  40. package/dist/providers/claudeCode.d.ts +4 -0
  41. package/dist/providers/claudeCode.js +32 -0
  42. package/dist/providers/claudeCode.js.map +1 -0
  43. package/dist/providers/codexCli.d.ts +6 -0
  44. package/dist/providers/codexCli.js +25 -0
  45. package/dist/providers/codexCli.js.map +1 -0
  46. package/dist/providers/codexCliModels.d.ts +23 -0
  47. package/dist/providers/codexCliModels.js +147 -0
  48. package/dist/providers/codexCliModels.js.map +1 -0
  49. package/dist/providers/localCommand.d.ts +26 -0
  50. package/dist/providers/localCommand.js +614 -0
  51. package/dist/providers/localCommand.js.map +1 -0
  52. package/dist/providers/markdownPayload.d.ts +7 -0
  53. package/dist/providers/markdownPayload.js +29 -0
  54. package/dist/providers/markdownPayload.js.map +1 -0
  55. package/dist/providers/responseSchema.d.ts +3 -0
  56. package/dist/providers/responseSchema.js +187 -0
  57. package/dist/providers/responseSchema.js.map +1 -0
  58. package/dist/providers/router.d.ts +3 -0
  59. package/dist/providers/router.js +21 -0
  60. package/dist/providers/router.js.map +1 -0
  61. package/dist/providers/stub.d.ts +2 -0
  62. package/dist/providers/stub.js +177 -0
  63. package/dist/providers/stub.js.map +1 -0
  64. package/dist/providers/types.d.ts +37 -0
  65. package/dist/providers/types.js +2 -0
  66. package/dist/providers/types.js.map +1 -0
  67. package/dist/runtime/agentBoard.d.ts +79 -0
  68. package/dist/runtime/agentBoard.js +166 -0
  69. package/dist/runtime/agentBoard.js.map +1 -0
  70. package/dist/runtime/agentBoardArtifact.d.ts +9 -0
  71. package/dist/runtime/agentBoardArtifact.js +171 -0
  72. package/dist/runtime/agentBoardArtifact.js.map +1 -0
  73. package/dist/runtime/agentRunner.d.ts +17 -0
  74. package/dist/runtime/agentRunner.js +92 -0
  75. package/dist/runtime/agentRunner.js.map +1 -0
  76. package/dist/runtime/approvalInbox.d.ts +54 -0
  77. package/dist/runtime/approvalInbox.js +143 -0
  78. package/dist/runtime/approvalInbox.js.map +1 -0
  79. package/dist/runtime/approvalPolicy.d.ts +11 -0
  80. package/dist/runtime/approvalPolicy.js +58 -0
  81. package/dist/runtime/approvalPolicy.js.map +1 -0
  82. package/dist/runtime/artifacts.d.ts +23 -0
  83. package/dist/runtime/artifacts.js +48 -0
  84. package/dist/runtime/artifacts.js.map +1 -0
  85. package/dist/runtime/browserManager.d.ts +37 -0
  86. package/dist/runtime/browserManager.js +356 -0
  87. package/dist/runtime/browserManager.js.map +1 -0
  88. package/dist/runtime/canonicalMemory.d.ts +23 -0
  89. package/dist/runtime/canonicalMemory.js +134 -0
  90. package/dist/runtime/canonicalMemory.js.map +1 -0
  91. package/dist/runtime/chatGptPageReadiness.d.ts +16 -0
  92. package/dist/runtime/chatGptPageReadiness.js +74 -0
  93. package/dist/runtime/chatGptPageReadiness.js.map +1 -0
  94. package/dist/runtime/commandRunner.d.ts +18 -0
  95. package/dist/runtime/commandRunner.js +115 -0
  96. package/dist/runtime/commandRunner.js.map +1 -0
  97. package/dist/runtime/commandSafety.d.ts +7 -0
  98. package/dist/runtime/commandSafety.js +61 -0
  99. package/dist/runtime/commandSafety.js.map +1 -0
  100. package/dist/runtime/config.d.ts +10 -0
  101. package/dist/runtime/config.js +107 -0
  102. package/dist/runtime/config.js.map +1 -0
  103. package/dist/runtime/crewLanes.d.ts +2 -0
  104. package/dist/runtime/crewLanes.js +123 -0
  105. package/dist/runtime/crewLanes.js.map +1 -0
  106. package/dist/runtime/criticPolicy.d.ts +17 -0
  107. package/dist/runtime/criticPolicy.js +50 -0
  108. package/dist/runtime/criticPolicy.js.map +1 -0
  109. package/dist/runtime/defaults.d.ts +5 -0
  110. package/dist/runtime/defaults.js +100 -0
  111. package/dist/runtime/defaults.js.map +1 -0
  112. package/dist/runtime/directives.d.ts +3 -0
  113. package/dist/runtime/directives.js +218 -0
  114. package/dist/runtime/directives.js.map +1 -0
  115. package/dist/runtime/doctor.d.ts +36 -0
  116. package/dist/runtime/doctor.js +185 -0
  117. package/dist/runtime/doctor.js.map +1 -0
  118. package/dist/runtime/errors.d.ts +20 -0
  119. package/dist/runtime/errors.js +41 -0
  120. package/dist/runtime/errors.js.map +1 -0
  121. package/dist/runtime/externalTransfer.d.ts +20 -0
  122. package/dist/runtime/externalTransfer.js +156 -0
  123. package/dist/runtime/externalTransfer.js.map +1 -0
  124. package/dist/runtime/fanout.d.ts +64 -0
  125. package/dist/runtime/fanout.js +263 -0
  126. package/dist/runtime/fanout.js.map +1 -0
  127. package/dist/runtime/gitEvidence.d.ts +10 -0
  128. package/dist/runtime/gitEvidence.js +80 -0
  129. package/dist/runtime/gitEvidence.js.map +1 -0
  130. package/dist/runtime/handoffs.d.ts +32 -0
  131. package/dist/runtime/handoffs.js +100 -0
  132. package/dist/runtime/handoffs.js.map +1 -0
  133. package/dist/runtime/ids.d.ts +2 -0
  134. package/dist/runtime/ids.js +4 -0
  135. package/dist/runtime/ids.js.map +1 -0
  136. package/dist/runtime/localStateIgnore.d.ts +9 -0
  137. package/dist/runtime/localStateIgnore.js +98 -0
  138. package/dist/runtime/localStateIgnore.js.map +1 -0
  139. package/dist/runtime/loop.d.ts +14 -0
  140. package/dist/runtime/loop.js +1863 -0
  141. package/dist/runtime/loop.js.map +1 -0
  142. package/dist/runtime/loopRecommendation.d.ts +109 -0
  143. package/dist/runtime/loopRecommendation.js +566 -0
  144. package/dist/runtime/loopRecommendation.js.map +1 -0
  145. package/dist/runtime/loopResponsibilities.d.ts +2 -0
  146. package/dist/runtime/loopResponsibilities.js +395 -0
  147. package/dist/runtime/loopResponsibilities.js.map +1 -0
  148. package/dist/runtime/loopRunner.d.ts +28 -0
  149. package/dist/runtime/loopRunner.js +81 -0
  150. package/dist/runtime/loopRunner.js.map +1 -0
  151. package/dist/runtime/operatorNextActions.d.ts +2 -0
  152. package/dist/runtime/operatorNextActions.js +344 -0
  153. package/dist/runtime/operatorNextActions.js.map +1 -0
  154. package/dist/runtime/paths.d.ts +9 -0
  155. package/dist/runtime/paths.js +14 -0
  156. package/dist/runtime/paths.js.map +1 -0
  157. package/dist/runtime/permissions.d.ts +9 -0
  158. package/dist/runtime/permissions.js +73 -0
  159. package/dist/runtime/permissions.js.map +1 -0
  160. package/dist/runtime/progressPacket.d.ts +12 -0
  161. package/dist/runtime/progressPacket.js +512 -0
  162. package/dist/runtime/progressPacket.js.map +1 -0
  163. package/dist/runtime/protectedPaths.d.ts +6 -0
  164. package/dist/runtime/protectedPaths.js +48 -0
  165. package/dist/runtime/protectedPaths.js.map +1 -0
  166. package/dist/runtime/providers.d.ts +13 -0
  167. package/dist/runtime/providers.js +60 -0
  168. package/dist/runtime/providers.js.map +1 -0
  169. package/dist/runtime/reconciliation.d.ts +17 -0
  170. package/dist/runtime/reconciliation.js +283 -0
  171. package/dist/runtime/reconciliation.js.map +1 -0
  172. package/dist/runtime/redaction.d.ts +1 -0
  173. package/dist/runtime/redaction.js +5 -0
  174. package/dist/runtime/redaction.js.map +1 -0
  175. package/dist/runtime/remoteRepoContext.d.ts +77 -0
  176. package/dist/runtime/remoteRepoContext.js +316 -0
  177. package/dist/runtime/remoteRepoContext.js.map +1 -0
  178. package/dist/runtime/repoContext.d.ts +50 -0
  179. package/dist/runtime/repoContext.js +399 -0
  180. package/dist/runtime/repoContext.js.map +1 -0
  181. package/dist/runtime/repoGateway.d.ts +64 -0
  182. package/dist/runtime/repoGateway.js +308 -0
  183. package/dist/runtime/repoGateway.js.map +1 -0
  184. package/dist/runtime/responseContracts.d.ts +3 -0
  185. package/dist/runtime/responseContracts.js +86 -0
  186. package/dist/runtime/responseContracts.js.map +1 -0
  187. package/dist/runtime/reviewLanes.d.ts +2 -0
  188. package/dist/runtime/reviewLanes.js +343 -0
  189. package/dist/runtime/reviewLanes.js.map +1 -0
  190. package/dist/runtime/reviewRouting.d.ts +51 -0
  191. package/dist/runtime/reviewRouting.js +152 -0
  192. package/dist/runtime/reviewRouting.js.map +1 -0
  193. package/dist/runtime/revisionPacket.d.ts +38 -0
  194. package/dist/runtime/revisionPacket.js +144 -0
  195. package/dist/runtime/revisionPacket.js.map +1 -0
  196. package/dist/runtime/revisionTrail.d.ts +2 -0
  197. package/dist/runtime/revisionTrail.js +162 -0
  198. package/dist/runtime/revisionTrail.js.map +1 -0
  199. package/dist/runtime/role-assignment.d.ts +4 -0
  200. package/dist/runtime/role-assignment.js +21 -0
  201. package/dist/runtime/role-assignment.js.map +1 -0
  202. package/dist/runtime/roleRoster.d.ts +28 -0
  203. package/dist/runtime/roleRoster.js +96 -0
  204. package/dist/runtime/roleRoster.js.map +1 -0
  205. package/dist/runtime/runInsights.d.ts +121 -0
  206. package/dist/runtime/runInsights.js +305 -0
  207. package/dist/runtime/runInsights.js.map +1 -0
  208. package/dist/runtime/runMonitor.d.ts +33 -0
  209. package/dist/runtime/runMonitor.js +143 -0
  210. package/dist/runtime/runMonitor.js.map +1 -0
  211. package/dist/runtime/runtime.d.ts +15 -0
  212. package/dist/runtime/runtime.js +199 -0
  213. package/dist/runtime/runtime.js.map +1 -0
  214. package/dist/runtime/runtimeInfo.d.ts +9 -0
  215. package/dist/runtime/runtimeInfo.js +76 -0
  216. package/dist/runtime/runtimeInfo.js.map +1 -0
  217. package/dist/runtime/store.d.ts +4 -0
  218. package/dist/runtime/store.js +48 -0
  219. package/dist/runtime/store.js.map +1 -0
  220. package/dist/runtime/summons.d.ts +25 -0
  221. package/dist/runtime/summons.js +403 -0
  222. package/dist/runtime/summons.js.map +1 -0
  223. package/dist/runtime/teamPresets.d.ts +14 -0
  224. package/dist/runtime/teamPresets.js +153 -0
  225. package/dist/runtime/teamPresets.js.map +1 -0
  226. package/dist/runtime/types.d.ts +505 -0
  227. package/dist/runtime/types.js +28 -0
  228. package/dist/runtime/types.js.map +1 -0
  229. package/dist/runtime/validationCommands.d.ts +18 -0
  230. package/dist/runtime/validationCommands.js +106 -0
  231. package/dist/runtime/validationCommands.js.map +1 -0
  232. package/dist/tui/dashboard.d.ts +41 -0
  233. package/dist/tui/dashboard.js +1115 -0
  234. package/dist/tui/dashboard.js.map +1 -0
  235. package/docs/ARCHITECTURE.md +277 -0
  236. package/docs/CLI_SPEC.md +396 -0
  237. package/docs/CODEX_SETUP.md +288 -0
  238. package/docs/COMPLETION_CONTRACT.md +52 -0
  239. package/docs/CONTRIBUTOR_GUIDE.md +70 -0
  240. package/docs/DEMO.md +62 -0
  241. package/docs/GLOSSARY.md +46 -0
  242. package/docs/GOAL_LOOP_SCHEDULE.md +50 -0
  243. package/docs/KNOWN_LIMITATIONS.md +29 -0
  244. package/docs/LICENSING.md +21 -0
  245. package/docs/LOOP_RECIPES.md +290 -0
  246. package/docs/LOOP_SELECTION_UX.md +118 -0
  247. package/docs/MCP_SPEC.md +689 -0
  248. package/docs/MEMORY_AND_RECONCILIATION.md +222 -0
  249. package/docs/NPM_PUBLISHING.md +51 -0
  250. package/docs/OPEN_DECISIONS.md +81 -0
  251. package/docs/PROMPT_SCHEMAS.md +411 -0
  252. package/docs/PROVIDER_ADAPTERS.md +323 -0
  253. package/docs/PROVIDER_MATRIX.md +21 -0
  254. package/docs/PUBLIC_REPO_READINESS.md +49 -0
  255. package/docs/RESEARCH_NOTES.md +92 -0
  256. package/docs/ROADMAP.md +94 -0
  257. package/docs/ROLE_CONTRACTS.md +252 -0
  258. package/docs/RUNTIME_LOOP.md +240 -0
  259. package/docs/SECURITY_AND_PRIVACY.md +161 -0
  260. package/docs/TESTING_AND_VERIFICATION.md +180 -0
  261. package/docs/TRUST_MODEL.md +65 -0
  262. package/docs/decisions/0001-runtime-first-cli-and-mcp.md +23 -0
  263. package/docs/decisions/0002-provider-neutral-role-mapping.md +43 -0
  264. package/docs/decisions/0003-separate-implementation-and-verification.md +27 -0
  265. package/docs/product/README.md +14 -0
  266. package/docs/product/model-selection.md +88 -0
  267. package/docs/product/positioning.md +37 -0
  268. package/docs/product/pro-usage-modes.md +70 -0
  269. package/docs/product/roadmap.md +57 -0
  270. package/docs/product/role-policy.md +89 -0
  271. package/docs/product/runtime-invariants.md +44 -0
  272. package/docs/release/v0.1.0-preview.0.md +48 -0
  273. package/examples/stub-demo/README.md +25 -0
  274. package/package.json +55 -0
@@ -0,0 +1,996 @@
1
+ #!/usr/bin/env node
2
+ import path from "node:path";
3
+ import { initConfig, loadConfig, writeConfig } from "../runtime/config.js";
4
+ import { inspectBrowser, startBrowser, stopBrowser } from "../runtime/browserManager.js";
5
+ import { runRuntimeCommand } from "../runtime/commandRunner.js";
6
+ import { buildAgentBoard } from "../runtime/agentBoard.js";
7
+ import { buildAgentBoardArtifact } from "../runtime/agentBoardArtifact.js";
8
+ import { inspectRuntimeHealth } from "../runtime/doctor.js";
9
+ import { InputError, TheHoodError } from "../runtime/errors.js";
10
+ import { readLatestExternalTransferManifest } from "../runtime/externalTransfer.js";
11
+ import { captureGitEvidence } from "../runtime/gitEvidence.js";
12
+ import { recommendLoop } from "../runtime/loopRecommendation.js";
13
+ import { advanceRun } from "../runtime/loop.js";
14
+ import { runAutopilotLoop } from "../runtime/loopRunner.js";
15
+ import { startMcpServer } from "../mcp/server.js";
16
+ import { readRunArtifact } from "../runtime/artifacts.js";
17
+ import { fanoutAgents } from "../runtime/fanout.js";
18
+ import { listProvidersWithRuntimeModels } from "../runtime/providers.js";
19
+ import { assertRoleInvariants } from "../runtime/permissions.js";
20
+ import { reconcileRun } from "../runtime/reconciliation.js";
21
+ import { buildRoleRoster } from "../runtime/roleRoster.js";
22
+ import { parseRole, parseRoleAssignment } from "../runtime/role-assignment.js";
23
+ import { getRunInsights } from "../runtime/runInsights.js";
24
+ import { runMonitorFromRuns } from "../runtime/runMonitor.js";
25
+ import { getProjectPaths } from "../runtime/paths.js";
26
+ import { summonAgent } from "../runtime/summons.js";
27
+ import { applyTeamPreset, getTeamPreset, listTeamPresets, teamPresetIds } from "../runtime/teamPresets.js";
28
+ import { abortRun, createRun, getRun, listRuns, recordApproval } from "../runtime/runtime.js";
29
+ import { approvalInboxViewFromRuns, approvalMessageHint } from "../runtime/approvalInbox.js";
30
+ import { approvalDecisions, runModes } from "../runtime/types.js";
31
+ import { getBooleanOption, getStringListOption, getStringOption, parseArgs } from "./args.js";
32
+ import { getMcpConfigReport, getMcpTunnelConfigReport } from "./mcpConfig.js";
33
+ import { formatBrowserStartResult, formatBrowserStatus, formatBrowserStopResult, formatCliSetupReport, formatConfig, formatAdvanceRunResult, formatAgentBoard, formatCommandResult, formatDoctorReport, formatGitEvidence, formatLoopRecommendation, formatMcpConfigReport, formatMcpTunnelConfigReport, formatExternalTransferPreview, formatFanoutAgentsResult, formatRunLoopResult, formatProviders, formatReconcileRunResult, formatRoleRoster, formatRoles, formatRunEvents, formatRunList, formatRunSummary, formatSummonAgentResult, printJson } from "./format.js";
34
+ import { renderApprovalInbox, renderDashboard, renderSettingsCockpit, settingsPageIds } from "../tui/dashboard.js";
35
+ const helpText = `TheHood local agent runtime
36
+
37
+ Usage:
38
+ thehood init [--repo <path>]
39
+ thehood setup [--repo <path>] [--json]
40
+ thehood config show [--repo <path>] [--json]
41
+ thehood config set max-iterations|fanout-max-items <n> [--repo <path>] [--json]
42
+ thehood providers [--repo <path>] [--json]
43
+ thehood doctor [--repo <path>] [--json]
44
+ thehood models [--repo <path>] [--json]
45
+ thehood roster [--repo <path>] [--json]
46
+ thehood agent-board [run-id] [--repo <path>] [--artifact] [--json]
47
+ thehood teams [apply <preset>] [--repo <path>] [--json]
48
+ thehood roles [--repo <path>] [--json]
49
+ thehood roles set <role> <provider:model> [--repo <path>]
50
+ thehood recommend-loop <goal> [--repo <path>] [--constraint <text>] [--acceptance <text>] [--validation <command>] [--allowed-path <path>] [--forbidden-change <text>] [--max-iterations <n>] [--json]
51
+ thehood goal <goal> [--repo <path>] [--max-iterations <n>] [--max-cycles <n>] [--max-steps <n>] [--json]
52
+ thehood plan <goal> [--repo <path>] [--loop] [--max-cycles <n>] [--max-steps <n>] [--json]
53
+ thehood run <goal> [--repo <path>] [--mode <mode>] [--loop] [--max-cycles <n>] [--max-steps <n>] [--json]
54
+ thehood status [run-id] [--repo <path>] [--json]
55
+ thehood logs <run-id> [--repo <path>] [--json]
56
+ thehood artifact <run-id> <artifact-ref> [--repo <path>] [--max-bytes <n>] [--json]
57
+ thehood evidence <run-id> [--repo <path>] [--json]
58
+ thehood diff <run-id> [--repo <path>] [--max-bytes <n>] [--json]
59
+ thehood exec <run-id> [--repo <path>] [--cwd <path>] [--allow-risky] -- <command> [args...]
60
+ thehood approve <run-id> [--repo <path>] [--reason <text>]
61
+ thehood reject <run-id> [--repo <path>] [--reason <text>]
62
+ thehood revise <run-id> [--repo <path>] [--reason <text>]
63
+ thehood approvals policy [show|set mode manual|auto-low-risk|autopilot|set external-transfers manual|auto-low-risk] [--repo <path>] [--json]
64
+ thehood continue <run-id> [--repo <path>] [--json]
65
+ thehood loop <run-id> [--repo <path>] [--max-cycles <n>] [--max-steps <n>] [--json]
66
+ thehood reconcile <run-id> [--repo <path>] [--role planner|orchestrator] [--json]
67
+ thehood summon <run-id> --role <role> --brief <text> [--agent <provider:model>] [--kind <kind>] [--json]
68
+ thehood fanout <run-id> --items-json <json-array> [--max-items <n>] [--repo <path>] [--json]
69
+ thehood transfer preview <run-id> [--repo <path>] [--json]
70
+ thehood abort <run-id> [--repo <path>] [--reason <text>]
71
+ thehood browser start [--port <n>] [--profile <name>] [--profile-path <path>] [--chrome-path <path>]
72
+ thehood browser status [--port <n>] [--cdp-url <url>] [--profile <name>] [--profile-path <path>] [--json]
73
+ thehood browser stop [--port <n>] [--profile <name>] [--profile-path <path>] [--json]
74
+ thehood ui [approvals|settings [overview|crew|providers|budgets|safety|browser|commands|all]] [--repo <path>] [--port <n>] [--cdp-url <url>] [--approve <run-id>] [--reject <run-id>] [--revise <run-id>] [--json]
75
+ thehood mcp
76
+ thehood mcp config [--json] [--chatgpt-web] [--cdp-url <url>]
77
+ thehood mcp tunnel [--profile <name>] [--tunnel-id <id>] [--json]
78
+
79
+ Role override options for plan/run:
80
+ --orchestrator provider:model
81
+ --planner provider:model
82
+ --researcher provider:model
83
+ --implementer provider:model
84
+ --qa provider:model
85
+ --verifier provider:model
86
+ --critic provider:model
87
+ --constraint "text"
88
+
89
+ Summon roles:
90
+ orchestrator | planner | researcher | qa | verifier | critic
91
+
92
+ Fan-out item JSON:
93
+ [{"role":"qa","agent":"stub:qa","brief":"QA sidecar"},{"role":"critic","agent":"stub:critic","brief":"Critique sidecar"}]
94
+
95
+ Common team presets:
96
+ codex-default | pro-orchestrator | claude-critic | claude-second-judge
97
+ spark-plus-sonnet | claude-builder | pro-claude-high-assurance
98
+
99
+ Model examples:
100
+ codex-cli:spark | chatgpt-web:chatgpt-pro | chatgpt-web:configured
101
+ claude-code:sonnet | claude-code:fable | claude-code:mythos
102
+ `;
103
+ const repoFromOptions = (options) => getStringOption(options, "repo") ?? process.cwd();
104
+ const shouldPrintJson = (options) => getBooleanOption(options, "json");
105
+ const shellQuote = (value) => /^[A-Za-z0-9_./:@=-]+$/.test(value) ? value : `'${value.replace(/'/g, "'\\''")}'`;
106
+ const currentCliEntryPath = () => {
107
+ const entry = process.argv[1];
108
+ return entry ? path.resolve(entry) : path.resolve("dist/cli/main.js");
109
+ };
110
+ const buildCliSetupReport = (options) => {
111
+ const entryPath = currentCliEntryPath();
112
+ const packageRoot = path.resolve(path.dirname(entryPath), "..", "..");
113
+ const repoPath = repoFromOptions(options);
114
+ const localBuildCommand = `node ${shellQuote(entryPath)}`;
115
+ const installedCommand = "thehood";
116
+ const repoArg = `--repo ${shellQuote(repoPath)}`;
117
+ return {
118
+ commandName: installedCommand,
119
+ repoPath,
120
+ localBuildCommand,
121
+ installedCommand,
122
+ oneSessionAlias: `alias thehood=${shellQuote(`node ${entryPath}`)}`,
123
+ npmLinkCommand: `cd ${shellQuote(packageRoot)} && npm link`,
124
+ npmInstallCommand: "npm install -g thehood",
125
+ localMcpConfigCommand: `${localBuildCommand} mcp config`,
126
+ installedMcpConfigCommand: `${installedCommand} mcp config`,
127
+ localUiCommand: `${localBuildCommand} ui ${repoArg}`,
128
+ installedUiCommand: `${installedCommand} ui ${repoArg}`,
129
+ notes: [
130
+ "Use the local build command while developing this checkout.",
131
+ "Run npm run build after source edits so dist/cli/main.js reflects the current code.",
132
+ "npm link is optional and creates a global shell command pointing at this checkout.",
133
+ "npm install -g is for a published or otherwise available package."
134
+ ]
135
+ };
136
+ };
137
+ const parseSettingsPage = (value) => {
138
+ if (value === undefined) {
139
+ return "overview";
140
+ }
141
+ if (settingsPageIds.includes(value)) {
142
+ return value;
143
+ }
144
+ throw new InputError(`Unknown settings page "${value}". Expected one of: ${settingsPageIds.join(", ")}.`);
145
+ };
146
+ const isSettingsPage = (value) => Boolean(value && settingsPageIds.includes(value));
147
+ const parseMode = (value, fallback) => {
148
+ if (value === undefined) {
149
+ return fallback;
150
+ }
151
+ if (runModes.includes(value)) {
152
+ return value;
153
+ }
154
+ throw new InputError(`Invalid mode "${value}". Expected one of: ${runModes.join(", ")}.`);
155
+ };
156
+ const parseRoleOverrides = (options) => {
157
+ const overrides = {};
158
+ for (const role of ["orchestrator", "planner", "researcher", "implementer", "qa", "verifier", "critic"]) {
159
+ const value = getStringOption(options, role);
160
+ if (value) {
161
+ overrides[role] = parseRoleAssignment(value);
162
+ }
163
+ }
164
+ return overrides;
165
+ };
166
+ const parseDecision = (value) => {
167
+ if (approvalDecisions.includes(value)) {
168
+ return value;
169
+ }
170
+ throw new InputError(`Invalid approval decision "${value}".`);
171
+ };
172
+ const parseExternalTransferApprovalMode = (value) => {
173
+ const normalized = value.replace(/-/g, "_");
174
+ if (normalized === "manual" || normalized === "auto_low_risk") {
175
+ return normalized;
176
+ }
177
+ throw new InputError("External transfer policy must be manual or auto-low-risk.");
178
+ };
179
+ const parseApprovalPolicyMode = (value) => {
180
+ const normalized = value.replace(/-/g, "_");
181
+ if (normalized === "manual" || normalized === "auto_low_risk" || normalized === "autopilot") {
182
+ return normalized;
183
+ }
184
+ throw new InputError("Approval policy mode must be manual, auto-low-risk, or autopilot.");
185
+ };
186
+ const ensureRunId = (value) => {
187
+ if (!value) {
188
+ throw new InputError("Run id is required.");
189
+ }
190
+ return value;
191
+ };
192
+ const parsePositiveIntegerOption = (options, key) => {
193
+ const raw = getStringOption(options, key);
194
+ if (raw === undefined) {
195
+ return undefined;
196
+ }
197
+ const parsed = Number.parseInt(raw, 10);
198
+ if (!Number.isSafeInteger(parsed) || parsed < 1 || String(parsed) !== raw.trim()) {
199
+ throw new InputError(`Option --${key} must be a positive integer.`);
200
+ }
201
+ return parsed;
202
+ };
203
+ const parsePositiveIntegerValue = (value, label) => {
204
+ if (value === undefined) {
205
+ throw new InputError(`${label} is required.`);
206
+ }
207
+ const parsed = Number.parseInt(value, 10);
208
+ if (!Number.isSafeInteger(parsed) || parsed < 1 || String(parsed) !== value.trim()) {
209
+ throw new InputError(`${label} must be a positive integer.`);
210
+ }
211
+ return parsed;
212
+ };
213
+ const isPlainObject = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
214
+ const requiredStringField = (value, key, label) => {
215
+ const raw = value[key];
216
+ if (typeof raw === "string" && raw.trim().length > 0) {
217
+ return raw;
218
+ }
219
+ throw new InputError(`${label}.${key} must be a non-empty string.`);
220
+ };
221
+ const optionalStringField = (value, key, label) => {
222
+ const raw = value[key];
223
+ if (raw === undefined) {
224
+ return undefined;
225
+ }
226
+ if (typeof raw === "string" && raw.trim().length > 0) {
227
+ return raw;
228
+ }
229
+ throw new InputError(`${label}.${key} must be a non-empty string when provided.`);
230
+ };
231
+ const optionalStringArrayField = (value, key, label) => {
232
+ const raw = value[key];
233
+ if (raw === undefined) {
234
+ return [];
235
+ }
236
+ if (Array.isArray(raw) && raw.every((item) => typeof item === "string")) {
237
+ return raw;
238
+ }
239
+ throw new InputError(`${label}.${key} must be an array of strings when provided.`);
240
+ };
241
+ const parseFanoutItem = (value, index) => {
242
+ if (!isPlainObject(value)) {
243
+ throw new InputError(`Fan-out item ${index + 1} must be an object.`);
244
+ }
245
+ const label = `itemsJson[${index}]`;
246
+ const agent = optionalStringField(value, "agent", label);
247
+ const summonKind = optionalStringField(value, "kind", label) ?? optionalStringField(value, "summonKind", label);
248
+ const persona = optionalStringField(value, "persona", label);
249
+ const evidenceRefs = [
250
+ ...optionalStringArrayField(value, "evidenceRefs", label),
251
+ ...optionalStringArrayField(value, "evidence_refs", label)
252
+ ];
253
+ return {
254
+ role: parseRole(requiredStringField(value, "role", label)),
255
+ brief: requiredStringField(value, "brief", label),
256
+ ...(summonKind ? { summonKind } : {}),
257
+ ...(persona ? { persona } : {}),
258
+ ...(agent ? { agent: parseRoleAssignment(agent) } : {}),
259
+ constraints: optionalStringArrayField(value, "constraints", label),
260
+ evidenceRefs
261
+ };
262
+ };
263
+ const parseFanoutItems = (options) => {
264
+ const raw = getStringOption(options, "itemsJson");
265
+ if (!raw) {
266
+ throw new InputError("Option --items-json is required for fanout.");
267
+ }
268
+ try {
269
+ const parsed = JSON.parse(raw);
270
+ if (!Array.isArray(parsed)) {
271
+ throw new InputError("Option --items-json must be a JSON array.");
272
+ }
273
+ return parsed.map(parseFanoutItem);
274
+ }
275
+ catch (error) {
276
+ if (error instanceof InputError) {
277
+ throw error;
278
+ }
279
+ const message = error instanceof Error ? error.message : String(error);
280
+ throw new InputError(`Invalid --items-json: ${message}`);
281
+ }
282
+ };
283
+ const artifactReadOptions = (options) => {
284
+ const maxBytes = parsePositiveIntegerOption(options, "maxBytes");
285
+ return maxBytes === undefined ? {} : { maxBytes };
286
+ };
287
+ const browserOptionsFromCli = (options) => {
288
+ const port = parsePositiveIntegerOption(options, "port");
289
+ const cdpUrl = getStringOption(options, "cdpUrl");
290
+ const profile = getStringOption(options, "profile");
291
+ const profilePath = getStringOption(options, "profilePath");
292
+ const url = getStringOption(options, "url");
293
+ const chromePath = getStringOption(options, "chromePath");
294
+ return {
295
+ ...(port === undefined ? {} : { port }),
296
+ ...(cdpUrl ? { cdpUrl } : {}),
297
+ ...(profile ? { profile } : {}),
298
+ ...(profilePath ? { profilePath } : {}),
299
+ ...(url ? { url } : {}),
300
+ ...(chromePath ? { chromePath } : {})
301
+ };
302
+ };
303
+ const writeArtifactReadResult = (result, options) => {
304
+ if (shouldPrintJson(options)) {
305
+ printJson(result);
306
+ return;
307
+ }
308
+ process.stdout.write(result.content);
309
+ if (result.truncated) {
310
+ const prefix = result.content.endsWith("\n") ? "" : "\n";
311
+ process.stderr.write(`${prefix}thehood: artifact truncated from ${result.byteLength} byte(s). Use --max-bytes to read more.\n`);
312
+ }
313
+ };
314
+ const latestDiffArtifact = (artifacts) => {
315
+ const artifact = artifacts.filter((candidate) => candidate.kind === "diff").at(-1);
316
+ if (!artifact) {
317
+ throw new InputError("Run does not have a diff artifact.");
318
+ }
319
+ return artifact;
320
+ };
321
+ const formatLocalStateIgnoreResult = (result) => {
322
+ const entries = result.ignoredEntries.join(", ");
323
+ switch (result.status) {
324
+ case "updated":
325
+ return `Protected local state in git exclude: ${result.excludePath} (${entries})`;
326
+ case "already_ignored":
327
+ return `Local state already protected by git exclude: ${result.excludePath} (${entries})`;
328
+ case "not_git_repo":
329
+ return `Local state ignore not updated because this path is not a git checkout (${entries})`;
330
+ }
331
+ };
332
+ const handleInit = async (options) => {
333
+ const result = await initConfig(repoFromOptions(options));
334
+ if (shouldPrintJson(options)) {
335
+ printJson(result);
336
+ return;
337
+ }
338
+ process.stdout.write([
339
+ `${result.created ? "Created" : "Found existing"} config: ${result.configPath}`,
340
+ formatLocalStateIgnoreResult(result.localStateIgnore)
341
+ ].join("\n") + "\n");
342
+ };
343
+ const handleSetup = async (options) => {
344
+ const report = buildCliSetupReport(options);
345
+ shouldPrintJson(options) ? printJson(report) : process.stdout.write(`${formatCliSetupReport(report)}\n`);
346
+ };
347
+ const handleConfig = async (args, options) => {
348
+ const subcommand = args[0] ?? "show";
349
+ if (subcommand === "show") {
350
+ const config = await loadConfig(repoFromOptions(options));
351
+ shouldPrintJson(options) ? printJson(config) : process.stdout.write(`${formatConfig(config)}\n`);
352
+ return;
353
+ }
354
+ if (subcommand !== "set") {
355
+ throw new InputError(`Unknown config subcommand "${subcommand}".`);
356
+ }
357
+ const repoPath = repoFromOptions(options);
358
+ const config = await loadConfig(repoPath);
359
+ const key = args[1];
360
+ const value = args[2];
361
+ let updated = config;
362
+ if (key === "max-iterations") {
363
+ updated = {
364
+ ...config,
365
+ defaults: {
366
+ ...config.defaults,
367
+ maxIterations: parsePositiveIntegerValue(value, "max-iterations")
368
+ }
369
+ };
370
+ }
371
+ else if (key === "fanout-max-items") {
372
+ const fanoutMaxItems = parsePositiveIntegerValue(value, "fanout-max-items");
373
+ if (fanoutMaxItems > 8) {
374
+ throw new InputError("fanout-max-items cannot exceed 8.");
375
+ }
376
+ updated = {
377
+ ...config,
378
+ defaults: {
379
+ ...config.defaults,
380
+ fanoutMaxItems
381
+ }
382
+ };
383
+ }
384
+ else {
385
+ throw new InputError("Use: thehood config set max-iterations|fanout-max-items <positive-integer>");
386
+ }
387
+ await writeConfig(repoPath, updated);
388
+ shouldPrintJson(options) ? printJson(updated) : process.stdout.write(`${formatConfig(updated)}\n`);
389
+ };
390
+ const handleProviders = async (options) => {
391
+ const config = await loadConfig(repoFromOptions(options));
392
+ const providers = listProvidersWithRuntimeModels(config);
393
+ shouldPrintJson(options) ? printJson(providers) : process.stdout.write(`${formatProviders(providers)}\n`);
394
+ };
395
+ const handleDoctor = async (options) => {
396
+ const config = await loadConfig(repoFromOptions(options));
397
+ const report = await inspectRuntimeHealth(config);
398
+ shouldPrintJson(options) ? printJson(report) : process.stdout.write(`${formatDoctorReport(report)}\n`);
399
+ };
400
+ const handleRoles = async (args, options) => {
401
+ const repoPath = repoFromOptions(options);
402
+ const config = await loadConfig(repoPath);
403
+ if (args[0] === "set") {
404
+ const role = parseRole(args[1] ?? "");
405
+ const assignment = parseRoleAssignment(args[2] ?? "");
406
+ const updated = {
407
+ ...config,
408
+ roles: {
409
+ ...config.roles,
410
+ [role]: assignment
411
+ }
412
+ };
413
+ assertRoleInvariants(updated.roles);
414
+ await writeConfig(repoPath, updated);
415
+ if (shouldPrintJson(options)) {
416
+ printJson({ role, assignment, roles: updated.roles });
417
+ return;
418
+ }
419
+ process.stdout.write(`${role}: ${assignment.provider}:${assignment.model}\n`);
420
+ return;
421
+ }
422
+ shouldPrintJson(options) ? printJson(config.roles) : process.stdout.write(`${formatRoles(config.roles)}\n`);
423
+ };
424
+ const handleRoster = async (options) => {
425
+ const repoPath = repoFromOptions(options);
426
+ const config = await loadConfig(repoPath);
427
+ const health = await inspectRuntimeHealth(config);
428
+ const roster = buildRoleRoster(config, health);
429
+ shouldPrintJson(options)
430
+ ? printJson({ repoPath, roster })
431
+ : process.stdout.write(`${formatRoleRoster(roster, repoPath)}\n`);
432
+ };
433
+ const buildAgentBoardForCli = async (repoPath, runId, existingRun, existingInsights) => {
434
+ const config = await loadConfig(repoPath);
435
+ const health = await inspectRuntimeHealth(config);
436
+ const roster = buildRoleRoster(config, health);
437
+ if (!runId) {
438
+ return buildAgentBoard({ repoPath, roster });
439
+ }
440
+ const run = existingRun ?? await getRun(repoPath, runId);
441
+ const insights = existingInsights ?? await getRunInsights(run);
442
+ return buildAgentBoard({ repoPath, roster, run, insights });
443
+ };
444
+ const handleAgentBoard = async (args, options) => {
445
+ const board = await buildAgentBoardForCli(repoFromOptions(options), args[0]);
446
+ const output = getBooleanOption(options, "artifact")
447
+ ? {
448
+ board,
449
+ artifact: buildAgentBoardArtifact(board)
450
+ }
451
+ : board;
452
+ shouldPrintJson(options)
453
+ ? printJson(output)
454
+ : process.stdout.write(`${formatAgentBoard(board)}\n`);
455
+ };
456
+ const handleTeams = async (args, options) => {
457
+ const subcommand = args[0] ?? "list";
458
+ if (subcommand === "list") {
459
+ const presets = listTeamPresets();
460
+ if (shouldPrintJson(options)) {
461
+ printJson({ presets });
462
+ return;
463
+ }
464
+ process.stdout.write(`${presets.map((preset) => `${preset.id}: ${preset.summary}`).join("\n")}\n`);
465
+ return;
466
+ }
467
+ if (subcommand !== "apply") {
468
+ throw new InputError(`Unknown teams subcommand "${subcommand}".`);
469
+ }
470
+ const presetId = args[1] ?? "";
471
+ const preset = getTeamPreset(presetId);
472
+ if (!preset) {
473
+ throw new InputError(`Unknown team preset "${presetId}". Expected one of: ${teamPresetIds().join(", ")}.`);
474
+ }
475
+ const repoPath = repoFromOptions(options);
476
+ const config = await loadConfig(repoPath);
477
+ const updated = applyTeamPreset(config, preset);
478
+ assertRoleInvariants(updated.roles);
479
+ await writeConfig(repoPath, updated);
480
+ if (shouldPrintJson(options)) {
481
+ printJson({ preset, config: updated });
482
+ return;
483
+ }
484
+ process.stdout.write(`Applied team preset ${preset.id}: ${preset.summary}\n${formatRoles(updated.roles)}\n`);
485
+ };
486
+ const handleCreateRun = async (command, args, options) => {
487
+ const goal = args.join(" ").trim();
488
+ const mode = command === "plan" ? "plan" : parseMode(getStringOption(options, "mode"), "implement");
489
+ const shouldLoop = command === "goal" || getBooleanOption(options, "loop");
490
+ const maxIterations = parsePositiveIntegerOption(options, "maxIterations");
491
+ const run = await createRun({
492
+ repoPath: repoFromOptions(options),
493
+ goal,
494
+ mode,
495
+ roleOverrides: parseRoleOverrides(options),
496
+ constraints: getStringListOption(options, "constraint"),
497
+ ...(maxIterations === undefined ? {} : { maxIterations })
498
+ });
499
+ if (shouldLoop) {
500
+ const maxCycles = parsePositiveIntegerOption(options, "maxCycles");
501
+ const maxStepsPerCycle = parsePositiveIntegerOption(options, "maxSteps");
502
+ const result = await runAutopilotLoop({
503
+ repoPath: run.repoPath,
504
+ runId: run.runId,
505
+ ...(maxCycles === undefined ? {} : { maxCycles }),
506
+ ...(maxStepsPerCycle === undefined ? {} : { maxStepsPerCycle })
507
+ });
508
+ shouldPrintJson(options) ? printJson(result) : process.stdout.write(`${formatRunLoopResult(result)}\n`);
509
+ return;
510
+ }
511
+ shouldPrintJson(options) ? printJson(run) : process.stdout.write(`${formatRunSummary(run)}\n`);
512
+ };
513
+ const handleRecommendLoop = async (args, options) => {
514
+ const goal = args.join(" ").trim();
515
+ const maxIterations = parsePositiveIntegerOption(options, "maxIterations");
516
+ const recommendation = await recommendLoop({
517
+ repoPath: repoFromOptions(options),
518
+ goal,
519
+ constraints: getStringListOption(options, "constraint"),
520
+ acceptanceCriteria: getStringListOption(options, "acceptance"),
521
+ validationCommands: getStringListOption(options, "validation"),
522
+ allowedPaths: getStringListOption(options, "allowedPath"),
523
+ forbiddenChanges: getStringListOption(options, "forbiddenChange"),
524
+ ...(maxIterations === undefined ? {} : { maxIterations })
525
+ });
526
+ shouldPrintJson(options)
527
+ ? printJson(recommendation)
528
+ : process.stdout.write(`${formatLoopRecommendation(recommendation)}\n`);
529
+ };
530
+ const handleStatus = async (args, options) => {
531
+ const repoPath = repoFromOptions(options);
532
+ if (!args[0]) {
533
+ const runs = await listRuns(repoPath);
534
+ shouldPrintJson(options) ? printJson(runs) : process.stdout.write(`${formatRunList(runs)}\n`);
535
+ return;
536
+ }
537
+ const run = await getRun(repoPath, args[0]);
538
+ const insights = await getRunInsights(run);
539
+ const agentBoard = await buildAgentBoardForCli(repoPath, run.runId, run, insights);
540
+ shouldPrintJson(options)
541
+ ? printJson({ ...run, insights, agentBoard })
542
+ : process.stdout.write(`${formatRunSummary(run, insights)}\n`);
543
+ };
544
+ const handleLogs = async (args, options) => {
545
+ const run = await getRun(repoFromOptions(options), ensureRunId(args[0]));
546
+ shouldPrintJson(options) ? printJson(run.events) : process.stdout.write(`${formatRunEvents(run)}\n`);
547
+ };
548
+ const handleArtifact = async (args, options) => {
549
+ const runId = ensureRunId(args[0]);
550
+ const ref = args[1];
551
+ if (!ref) {
552
+ throw new InputError("Artifact ref is required. Use: thehood artifact <run-id> <artifact-ref>");
553
+ }
554
+ const result = await readRunArtifact({
555
+ repoPath: repoFromOptions(options),
556
+ runId,
557
+ ref,
558
+ ...artifactReadOptions(options)
559
+ });
560
+ writeArtifactReadResult(result, options);
561
+ };
562
+ const handleDiff = async (args, options) => {
563
+ const repoPath = repoFromOptions(options);
564
+ const runId = ensureRunId(args[0]);
565
+ const run = await getRun(repoPath, runId);
566
+ const artifact = latestDiffArtifact(run.artifacts);
567
+ const result = await readRunArtifact({
568
+ repoPath,
569
+ runId,
570
+ ref: artifact.ref,
571
+ ...artifactReadOptions(options)
572
+ });
573
+ writeArtifactReadResult(result, options);
574
+ };
575
+ const handleEvidence = async (args, options) => {
576
+ const result = await captureGitEvidence(repoFromOptions(options), ensureRunId(args[0]));
577
+ shouldPrintJson(options) ? printJson(result) : process.stdout.write(`${formatGitEvidence(result)}\n`);
578
+ };
579
+ const handleExec = async (args, options) => {
580
+ const runId = ensureRunId(args[0]);
581
+ const command = args[1];
582
+ if (!command) {
583
+ throw new InputError("Command is required after run id. Use: thehood exec <run-id> -- <command> [args...]");
584
+ }
585
+ const cwd = getStringOption(options, "cwd");
586
+ const result = await runRuntimeCommand({
587
+ repoPath: repoFromOptions(options),
588
+ runId,
589
+ command,
590
+ args: args.slice(2),
591
+ ...(cwd ? { cwd } : {}),
592
+ allowRisky: getBooleanOption(options, "allowRisky")
593
+ });
594
+ shouldPrintJson(options) ? printJson(result) : process.stdout.write(`${formatCommandResult(result)}\n`);
595
+ };
596
+ const handleApprovalCommand = async (command, args, options) => {
597
+ const decision = parseDecision(command);
598
+ const reason = getStringOption(options, "reason") ?? `${command} requested from CLI`;
599
+ const run = await recordApproval(repoFromOptions(options), ensureRunId(args[0]), decision, reason);
600
+ shouldPrintJson(options) ? printJson(run) : process.stdout.write(`${formatRunSummary(run)}\n`);
601
+ };
602
+ const handleApprovals = async (args, options) => {
603
+ const subcommand = args[0] ?? "policy";
604
+ if (subcommand !== "policy") {
605
+ throw new InputError(`Unknown approvals subcommand "${subcommand}".`);
606
+ }
607
+ const action = args[1] ?? "show";
608
+ const repoPath = repoFromOptions(options);
609
+ const config = await loadConfig(repoPath);
610
+ if (action === "show") {
611
+ shouldPrintJson(options) ? printJson(config.approvalPolicy) : process.stdout.write(`${formatConfig(config)}\n`);
612
+ return;
613
+ }
614
+ if (action !== "set") {
615
+ throw new InputError("Use: thehood approvals policy set mode manual|auto-low-risk|autopilot");
616
+ }
617
+ if (args[2] === "mode") {
618
+ const mode = parseApprovalPolicyMode(args[3] ?? "");
619
+ const externalTransferMode = mode === "manual" ? "manual" : "auto_low_risk";
620
+ const updated = {
621
+ ...config,
622
+ approvalPolicy: {
623
+ ...config.approvalPolicy,
624
+ mode,
625
+ externalTransfers: {
626
+ ...config.approvalPolicy.externalTransfers,
627
+ mode: externalTransferMode
628
+ }
629
+ }
630
+ };
631
+ await writeConfig(repoPath, updated);
632
+ shouldPrintJson(options) ? printJson(updated.approvalPolicy) : process.stdout.write(`${formatConfig(updated)}\n`);
633
+ return;
634
+ }
635
+ if (args[2] === "external-transfers") {
636
+ const mode = parseExternalTransferApprovalMode(args[3] ?? "");
637
+ const updated = {
638
+ ...config,
639
+ approvalPolicy: {
640
+ ...config.approvalPolicy,
641
+ externalTransfers: {
642
+ ...config.approvalPolicy.externalTransfers,
643
+ mode
644
+ }
645
+ }
646
+ };
647
+ await writeConfig(repoPath, updated);
648
+ shouldPrintJson(options) ? printJson(updated.approvalPolicy) : process.stdout.write(`${formatConfig(updated)}\n`);
649
+ return;
650
+ }
651
+ throw new InputError("Use: thehood approvals policy set mode manual|auto-low-risk|autopilot");
652
+ };
653
+ const handleContinue = async (args, options) => {
654
+ const result = await advanceRun({
655
+ repoPath: repoFromOptions(options),
656
+ runId: ensureRunId(args[0])
657
+ });
658
+ shouldPrintJson(options) ? printJson(result) : process.stdout.write(`${formatAdvanceRunResult(result)}\n`);
659
+ };
660
+ const handleLoop = async (args, options) => {
661
+ const maxCycles = parsePositiveIntegerOption(options, "maxCycles");
662
+ const maxStepsPerCycle = parsePositiveIntegerOption(options, "maxSteps");
663
+ const result = await runAutopilotLoop({
664
+ repoPath: repoFromOptions(options),
665
+ runId: ensureRunId(args[0]),
666
+ ...(maxCycles === undefined ? {} : { maxCycles }),
667
+ ...(maxStepsPerCycle === undefined ? {} : { maxStepsPerCycle })
668
+ });
669
+ shouldPrintJson(options) ? printJson(result) : process.stdout.write(`${formatRunLoopResult(result)}\n`);
670
+ };
671
+ const handleReconcile = async (args, options) => {
672
+ const roleValue = getStringOption(options, "role");
673
+ const role = roleValue ? parseRole(roleValue) : undefined;
674
+ const result = await reconcileRun({
675
+ repoPath: repoFromOptions(options),
676
+ runId: ensureRunId(args[0]),
677
+ ...(role ? { role } : {})
678
+ });
679
+ shouldPrintJson(options) ? printJson(result) : process.stdout.write(`${formatReconcileRunResult(result)}\n`);
680
+ };
681
+ const handleSummon = async (args, options) => {
682
+ const roleValue = getStringOption(options, "role");
683
+ if (!roleValue) {
684
+ throw new InputError("Option --role is required for summon.");
685
+ }
686
+ const brief = getStringOption(options, "brief") ?? args.slice(1).join(" ").trim();
687
+ const agent = getStringOption(options, "agent");
688
+ const persona = getStringOption(options, "persona");
689
+ const summonKind = getStringOption(options, "kind");
690
+ const result = await summonAgent({
691
+ repoPath: repoFromOptions(options),
692
+ runId: ensureRunId(args[0]),
693
+ role: parseRole(roleValue),
694
+ brief,
695
+ ...(summonKind ? { summonKind } : {}),
696
+ ...(persona ? { persona } : {}),
697
+ ...(agent ? { agent: parseRoleAssignment(agent) } : {}),
698
+ constraints: getStringListOption(options, "constraint"),
699
+ evidenceRefs: getStringListOption(options, "evidence")
700
+ });
701
+ shouldPrintJson(options) ? printJson(result) : process.stdout.write(`${formatSummonAgentResult(result)}\n`);
702
+ };
703
+ const handleFanout = async (args, options) => {
704
+ const maxItems = parsePositiveIntegerOption(options, "maxItems");
705
+ const result = await fanoutAgents({
706
+ repoPath: repoFromOptions(options),
707
+ runId: ensureRunId(args[0]),
708
+ items: parseFanoutItems(options),
709
+ ...(maxItems === undefined ? {} : { maxItems })
710
+ });
711
+ shouldPrintJson(options) ? printJson(result) : process.stdout.write(`${formatFanoutAgentsResult(result)}\n`);
712
+ };
713
+ const handleTransfer = async (args, options) => {
714
+ const subcommand = args[0] ?? "preview";
715
+ if (subcommand !== "preview") {
716
+ throw new InputError(`Unknown transfer subcommand "${subcommand}".`);
717
+ }
718
+ const repoPath = repoFromOptions(options);
719
+ const run = await getRun(repoPath, ensureRunId(args[1]));
720
+ const preview = await readLatestExternalTransferManifest(run);
721
+ shouldPrintJson(options) ? printJson(preview) : process.stdout.write(`${formatExternalTransferPreview(preview)}\n`);
722
+ };
723
+ const handleAbort = async (args, options) => {
724
+ const reason = getStringOption(options, "reason") ?? "Aborted from CLI.";
725
+ const run = await abortRun(repoFromOptions(options), ensureRunId(args[0]), reason);
726
+ shouldPrintJson(options) ? printJson(run) : process.stdout.write(`${formatRunSummary(run)}\n`);
727
+ };
728
+ const handleMcp = async (args, options) => {
729
+ if (args[0] === "config") {
730
+ const cdpUrl = getStringOption(options, "cdpUrl");
731
+ const report = getMcpConfigReport(process.argv[1], {
732
+ includeChatGptWeb: getBooleanOption(options, "chatgptWeb"),
733
+ ...(cdpUrl ? { cdpUrl } : {})
734
+ });
735
+ shouldPrintJson(options) ? printJson(report) : process.stdout.write(`${formatMcpConfigReport(report)}\n`);
736
+ return;
737
+ }
738
+ if (args[0] === "tunnel" || args[0] === "tunnel-config") {
739
+ const profile = getStringOption(options, "profile");
740
+ const tunnelId = getStringOption(options, "tunnelId");
741
+ const report = getMcpTunnelConfigReport(process.argv[1], {
742
+ ...(profile ? { profile } : {}),
743
+ ...(tunnelId ? { tunnelId } : {})
744
+ });
745
+ shouldPrintJson(options) ? printJson(report) : process.stdout.write(`${formatMcpTunnelConfigReport(report)}\n`);
746
+ return;
747
+ }
748
+ if (args.length > 0) {
749
+ throw new InputError(`Unknown mcp subcommand "${args[0]}".`);
750
+ }
751
+ await startMcpServer();
752
+ };
753
+ const handleBrowser = async (args, options) => {
754
+ const subcommand = args[0] ?? "status";
755
+ const browserOptions = browserOptionsFromCli(options);
756
+ if (subcommand === "status") {
757
+ const status = await inspectBrowser(browserOptions);
758
+ shouldPrintJson(options) ? printJson(status) : process.stdout.write(`${formatBrowserStatus(status)}\n`);
759
+ return;
760
+ }
761
+ if (subcommand === "start") {
762
+ const result = await startBrowser(browserOptions);
763
+ shouldPrintJson(options) ? printJson(result) : process.stdout.write(`${formatBrowserStartResult(result)}\n`);
764
+ return;
765
+ }
766
+ if (subcommand === "stop") {
767
+ const result = await stopBrowser(browserOptions);
768
+ shouldPrintJson(options) ? printJson(result) : process.stdout.write(`${formatBrowserStopResult(result)}\n`);
769
+ return;
770
+ }
771
+ throw new InputError(`Unknown browser subcommand "${subcommand}".`);
772
+ };
773
+ const handleUi = async (args, options) => {
774
+ const subcommand = args[0];
775
+ if (subcommand === "set") {
776
+ const key = args[1];
777
+ const value = args[2];
778
+ if (!key || !value || args.length > 3) {
779
+ throw new InputError("Usage: thehood ui set approval-mode|external-transfers|max-iterations|fanout-max-items <value>.");
780
+ }
781
+ if (key === "approval-mode") {
782
+ await handleApprovals(["policy", "set", "mode", value], options);
783
+ return;
784
+ }
785
+ if (key === "external-transfers") {
786
+ await handleApprovals(["policy", "set", "external-transfers", value], options);
787
+ return;
788
+ }
789
+ if (key === "max-iterations" || key === "fanout-max-items") {
790
+ await handleConfig(["set", key, value], options);
791
+ return;
792
+ }
793
+ throw new InputError("Usage: thehood ui set approval-mode|external-transfers|max-iterations|fanout-max-items <value>.");
794
+ }
795
+ if (subcommand === "team") {
796
+ if (!args[1] || args.length > 2) {
797
+ throw new InputError("Usage: thehood ui team <preset>.");
798
+ }
799
+ await handleTeams(["apply", args[1]], options);
800
+ return;
801
+ }
802
+ if (subcommand === "role") {
803
+ if (!args[1] || !args[2] || args.length > 3) {
804
+ throw new InputError("Usage: thehood ui role <role> <provider:model>.");
805
+ }
806
+ await handleRoles(["set", args[1], args[2]], options);
807
+ return;
808
+ }
809
+ const settingsPage = subcommand === "settings"
810
+ ? parseSettingsPage(args[1])
811
+ : isSettingsPage(subcommand)
812
+ ? subcommand
813
+ : undefined;
814
+ if (subcommand && subcommand !== "approvals" && subcommand !== "settings" && !settingsPage) {
815
+ throw new InputError(`Unknown ui subcommand "${subcommand}".`);
816
+ }
817
+ if (subcommand === "settings" && args.length > 2) {
818
+ throw new InputError("Usage: thehood ui settings [overview|crew|providers|budgets|safety|browser|commands|all].");
819
+ }
820
+ if (settingsPage && subcommand !== "settings" && args.length > 1) {
821
+ throw new InputError("Usage: thehood ui settings [overview|crew|providers|budgets|safety|browser|commands|all].");
822
+ }
823
+ const repoPath = repoFromOptions(options);
824
+ const approvalAction = [
825
+ ["approve", getStringOption(options, "approve")],
826
+ ["reject", getStringOption(options, "reject")],
827
+ ["revise", getStringOption(options, "revise")]
828
+ ].find(([, runId]) => runId !== undefined);
829
+ if (approvalAction) {
830
+ const [decision, runId] = approvalAction;
831
+ if (!runId) {
832
+ throw new InputError(`Run id is required for --${decision}.`);
833
+ }
834
+ const run = await getRun(repoPath, runId);
835
+ const reason = getStringOption(options, "reason") ?? (decision === "approve"
836
+ ? approvalMessageHint(run)
837
+ : decision === "reject"
838
+ ? `Rejected from TheHood UI for run ${run.runId}.`
839
+ : `Revision requested from TheHood UI for run ${run.runId}.`);
840
+ const updated = await recordApproval(repoPath, run.runId, decision, reason);
841
+ shouldPrintJson(options) ? printJson(updated) : process.stdout.write(`${formatRunSummary(updated)}\n`);
842
+ return;
843
+ }
844
+ const config = await loadConfig(repoPath);
845
+ const health = await inspectRuntimeHealth(config);
846
+ const roleRoster = buildRoleRoster(config, health);
847
+ const browser = await inspectBrowser(browserOptionsFromCli(options));
848
+ if (settingsPage) {
849
+ const settings = {
850
+ page: settingsPage,
851
+ repoPath,
852
+ configPath: getProjectPaths(repoPath).configPath,
853
+ config,
854
+ health,
855
+ roleRoster,
856
+ providers: listProvidersWithRuntimeModels(config),
857
+ teamPresets: listTeamPresets(),
858
+ browser
859
+ };
860
+ shouldPrintJson(options)
861
+ ? printJson(settings)
862
+ : process.stdout.write(`${renderSettingsCockpit(settings, { page: settingsPage })}\n`);
863
+ return;
864
+ }
865
+ const runs = await listRuns(repoPath);
866
+ const approvalInbox = approvalInboxViewFromRuns(runs);
867
+ const runMonitor = runMonitorFromRuns(runs);
868
+ const dashboard = {
869
+ repoPath,
870
+ health,
871
+ roleRoster,
872
+ browser,
873
+ approvalPolicy: config.approvalPolicy,
874
+ runMonitor,
875
+ approvalInbox
876
+ };
877
+ if (shouldPrintJson(options)) {
878
+ printJson(subcommand === "approvals" ? approvalInbox : dashboard);
879
+ return;
880
+ }
881
+ process.stdout.write(`${subcommand === "approvals" ? renderApprovalInbox(approvalInbox) : renderDashboard(dashboard)}\n`);
882
+ };
883
+ const runCli = async (argv) => {
884
+ const parsed = parseArgs(argv);
885
+ const [command, ...args] = parsed.positionals;
886
+ if (!command || command === "help" || command === "--help") {
887
+ process.stdout.write(helpText);
888
+ return;
889
+ }
890
+ switch (command) {
891
+ case "init":
892
+ await handleInit(parsed.options);
893
+ return;
894
+ case "setup":
895
+ await handleSetup(parsed.options);
896
+ return;
897
+ case "config":
898
+ await handleConfig(args, parsed.options);
899
+ return;
900
+ case "providers":
901
+ case "models":
902
+ await handleProviders(parsed.options);
903
+ return;
904
+ case "doctor":
905
+ await handleDoctor(parsed.options);
906
+ return;
907
+ case "roster":
908
+ await handleRoster(parsed.options);
909
+ return;
910
+ case "agent-board":
911
+ await handleAgentBoard(args, parsed.options);
912
+ return;
913
+ case "teams":
914
+ await handleTeams(args, parsed.options);
915
+ return;
916
+ case "roles":
917
+ await handleRoles(args, parsed.options);
918
+ return;
919
+ case "recommend-loop":
920
+ await handleRecommendLoop(args, parsed.options);
921
+ return;
922
+ case "goal":
923
+ case "plan":
924
+ case "run":
925
+ await handleCreateRun(command, args, parsed.options);
926
+ return;
927
+ case "status":
928
+ await handleStatus(args, parsed.options);
929
+ return;
930
+ case "logs":
931
+ await handleLogs(args, parsed.options);
932
+ return;
933
+ case "artifact":
934
+ await handleArtifact(args, parsed.options);
935
+ return;
936
+ case "evidence":
937
+ await handleEvidence(args, parsed.options);
938
+ return;
939
+ case "diff":
940
+ await handleDiff(args, parsed.options);
941
+ return;
942
+ case "exec":
943
+ await handleExec(args, parsed.options);
944
+ return;
945
+ case "approve":
946
+ case "reject":
947
+ case "revise":
948
+ await handleApprovalCommand(command, args, parsed.options);
949
+ return;
950
+ case "approvals":
951
+ await handleApprovals(args, parsed.options);
952
+ return;
953
+ case "continue":
954
+ await handleContinue(args, parsed.options);
955
+ return;
956
+ case "loop":
957
+ await handleLoop(args, parsed.options);
958
+ return;
959
+ case "reconcile":
960
+ await handleReconcile(args, parsed.options);
961
+ return;
962
+ case "summon":
963
+ await handleSummon(args, parsed.options);
964
+ return;
965
+ case "fanout":
966
+ await handleFanout(args, parsed.options);
967
+ return;
968
+ case "transfer":
969
+ await handleTransfer(args, parsed.options);
970
+ return;
971
+ case "abort":
972
+ await handleAbort(args, parsed.options);
973
+ return;
974
+ case "browser":
975
+ await handleBrowser(args, parsed.options);
976
+ return;
977
+ case "ui":
978
+ await handleUi(args, parsed.options);
979
+ return;
980
+ case "mcp":
981
+ await handleMcp(args, parsed.options);
982
+ return;
983
+ default:
984
+ throw new InputError(`Unknown command "${command}". Run "thehood help".`);
985
+ }
986
+ };
987
+ runCli(process.argv.slice(2)).catch((error) => {
988
+ if (error instanceof TheHoodError) {
989
+ process.stderr.write(`thehood: ${error.message}\n`);
990
+ process.exit(error.exitCode);
991
+ }
992
+ const message = error instanceof Error ? error.stack ?? error.message : String(error);
993
+ process.stderr.write(`thehood: unexpected error\n${message}\n`);
994
+ process.exit(1);
995
+ });
996
+ //# sourceMappingURL=main.js.map