agent-control-plane 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 (317) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +589 -0
  3. package/SKILL.md +149 -0
  4. package/assets/workflow-catalog.json +57 -0
  5. package/bin/audit-issue-routing.sh +74 -0
  6. package/bin/issue-resource-class.sh +58 -0
  7. package/bin/label-follow-up-issues.sh +114 -0
  8. package/bin/pr-risk.sh +532 -0
  9. package/bin/sync-pr-labels.sh +112 -0
  10. package/hooks/heartbeat-hooks.sh +573 -0
  11. package/hooks/issue-reconcile-hooks.sh +217 -0
  12. package/hooks/pr-reconcile-hooks.sh +225 -0
  13. package/npm/bin/agent-control-plane.js +1984 -0
  14. package/npm/public-bin/agent-control-plane +3 -0
  15. package/package.json +61 -0
  16. package/tools/bin/agent-cleanup-worktree +247 -0
  17. package/tools/bin/agent-github-update-labels +66 -0
  18. package/tools/bin/agent-init-worktree +216 -0
  19. package/tools/bin/agent-project-archive-run +52 -0
  20. package/tools/bin/agent-project-capture-worker +46 -0
  21. package/tools/bin/agent-project-catch-up-merged-prs +137 -0
  22. package/tools/bin/agent-project-cleanup-session +244 -0
  23. package/tools/bin/agent-project-detached-launch +107 -0
  24. package/tools/bin/agent-project-heartbeat-loop +2347 -0
  25. package/tools/bin/agent-project-open-issue-worktree +89 -0
  26. package/tools/bin/agent-project-open-pr-worktree +80 -0
  27. package/tools/bin/agent-project-publish-issue-pr +349 -0
  28. package/tools/bin/agent-project-reconcile-issue-session +1128 -0
  29. package/tools/bin/agent-project-reconcile-pr-session +1005 -0
  30. package/tools/bin/agent-project-retry-state +147 -0
  31. package/tools/bin/agent-project-run-claude-session +657 -0
  32. package/tools/bin/agent-project-run-codex-resilient +718 -0
  33. package/tools/bin/agent-project-run-codex-session +316 -0
  34. package/tools/bin/agent-project-run-kilo-session +27 -0
  35. package/tools/bin/agent-project-run-openclaw-session +984 -0
  36. package/tools/bin/agent-project-run-opencode-session +27 -0
  37. package/tools/bin/agent-project-sync-anchor-repo +128 -0
  38. package/tools/bin/agent-project-worker-status +143 -0
  39. package/tools/bin/audit-agent-worktrees.sh +310 -0
  40. package/tools/bin/audit-issue-routing.sh +11 -0
  41. package/tools/bin/audit-retained-layout.sh +58 -0
  42. package/tools/bin/audit-retained-overlap.sh +135 -0
  43. package/tools/bin/audit-retained-worktrees.sh +228 -0
  44. package/tools/bin/branch-verification-guard.sh +351 -0
  45. package/tools/bin/capture-worker.sh +18 -0
  46. package/tools/bin/check-skill-contracts.sh +324 -0
  47. package/tools/bin/cleanup-worktree.sh +44 -0
  48. package/tools/bin/codex-quota +31 -0
  49. package/tools/bin/create-follow-up-issue.sh +114 -0
  50. package/tools/bin/dashboard-launchd-bootstrap.sh +38 -0
  51. package/tools/bin/flow-config-lib.sh +2127 -0
  52. package/tools/bin/flow-resident-worker-lib.sh +683 -0
  53. package/tools/bin/flow-runtime-doctor.sh +97 -0
  54. package/tools/bin/flow-shell-lib.sh +266 -0
  55. package/tools/bin/heartbeat-recovery-preflight.sh +106 -0
  56. package/tools/bin/heartbeat-safe-auto.sh +551 -0
  57. package/tools/bin/install-dashboard-launchd.sh +152 -0
  58. package/tools/bin/install-project-launchd.sh +219 -0
  59. package/tools/bin/issue-publish-scope-guard.sh +242 -0
  60. package/tools/bin/issue-requires-local-workspace-install.sh +31 -0
  61. package/tools/bin/issue-resource-class.sh +12 -0
  62. package/tools/bin/kick-scheduler.sh +75 -0
  63. package/tools/bin/label-follow-up-issues.sh +14 -0
  64. package/tools/bin/new-pr-worktree.sh +50 -0
  65. package/tools/bin/new-worktree.sh +49 -0
  66. package/tools/bin/pr-risk.sh +12 -0
  67. package/tools/bin/prepare-worktree.sh +140 -0
  68. package/tools/bin/profile-activate.sh +109 -0
  69. package/tools/bin/profile-adopt.sh +219 -0
  70. package/tools/bin/profile-smoke.sh +461 -0
  71. package/tools/bin/project-init.sh +189 -0
  72. package/tools/bin/project-launchd-bootstrap.sh +54 -0
  73. package/tools/bin/project-remove.sh +155 -0
  74. package/tools/bin/project-runtime-supervisor.sh +56 -0
  75. package/tools/bin/project-runtimectl.sh +586 -0
  76. package/tools/bin/provider-cooldown-state.sh +166 -0
  77. package/tools/bin/publish-issue-worker.sh +31 -0
  78. package/tools/bin/reconcile-issue-worker.sh +34 -0
  79. package/tools/bin/reconcile-pr-worker.sh +34 -0
  80. package/tools/bin/record-verification.sh +71 -0
  81. package/tools/bin/render-architecture-infographics.sh +110 -0
  82. package/tools/bin/render-dashboard-demo-media.sh +333 -0
  83. package/tools/bin/render-dashboard-snapshot.py +16 -0
  84. package/tools/bin/render-flow-config.sh +86 -0
  85. package/tools/bin/retry-state.sh +31 -0
  86. package/tools/bin/reuse-issue-worktree.sh +75 -0
  87. package/tools/bin/run-codex-bypass.sh +3 -0
  88. package/tools/bin/run-codex-safe.sh +3 -0
  89. package/tools/bin/run-codex-task.sh +231 -0
  90. package/tools/bin/scaffold-profile.sh +374 -0
  91. package/tools/bin/serve-dashboard.sh +5 -0
  92. package/tools/bin/split-retained-slice.sh +124 -0
  93. package/tools/bin/start-issue-worker.sh +796 -0
  94. package/tools/bin/start-pr-fix-worker.sh +458 -0
  95. package/tools/bin/start-pr-merge-repair-worker.sh +8 -0
  96. package/tools/bin/start-pr-review-worker.sh +227 -0
  97. package/tools/bin/start-resident-issue-loop.sh +908 -0
  98. package/tools/bin/sync-agent-repo.sh +52 -0
  99. package/tools/bin/sync-dependency-baseline.sh +247 -0
  100. package/tools/bin/sync-pr-labels.sh +12 -0
  101. package/tools/bin/sync-recurring-issue-checklist.sh +274 -0
  102. package/tools/bin/sync-shared-agent-home.sh +214 -0
  103. package/tools/bin/sync-vscode-workspace.sh +157 -0
  104. package/tools/bin/test-smoke.sh +63 -0
  105. package/tools/bin/uninstall-project-launchd.sh +55 -0
  106. package/tools/bin/update-github-labels.sh +14 -0
  107. package/tools/bin/worker-status.sh +19 -0
  108. package/tools/bin/workflow-catalog.sh +77 -0
  109. package/tools/dashboard/app.js +286 -0
  110. package/tools/dashboard/dashboard_snapshot.py +466 -0
  111. package/tools/dashboard/index.html +41 -0
  112. package/tools/dashboard/server.py +64 -0
  113. package/tools/dashboard/styles.css +351 -0
  114. package/tools/templates/issue-prompt-template.md +109 -0
  115. package/tools/templates/pr-fix-template.md +120 -0
  116. package/tools/templates/pr-merge-repair-template.md +91 -0
  117. package/tools/templates/pr-review-template.md +62 -0
  118. package/tools/templates/scheduled-issue-prompt-template.md +62 -0
  119. package/tools/tests/test-agent-control-plane-npm-cli.sh +279 -0
  120. package/tools/tests/test-agent-github-update-labels-falls-back-to-repository-id.sh +56 -0
  121. package/tools/tests/test-agent-project-claude-session-wrapper-clears-stale-sandbox-artifacts.sh +89 -0
  122. package/tools/tests/test-agent-project-claude-session-wrapper-does-not-retry-provider-quota.sh +82 -0
  123. package/tools/tests/test-agent-project-claude-session-wrapper-retries-transient-failures.sh +90 -0
  124. package/tools/tests/test-agent-project-claude-session-wrapper-times-out.sh +73 -0
  125. package/tools/tests/test-agent-project-claude-session-wrapper.sh +103 -0
  126. package/tools/tests/test-agent-project-cleanup-session-orphan-fallback.sh +90 -0
  127. package/tools/tests/test-agent-project-cleanup-session-skip-worktree-cleanup.sh +90 -0
  128. package/tools/tests/test-agent-project-codex-live-thread-persist.sh +76 -0
  129. package/tools/tests/test-agent-project-codex-recovery.sh +731 -0
  130. package/tools/tests/test-agent-project-codex-session-wrapper-clears-stale-sandbox-artifacts.sh +105 -0
  131. package/tools/tests/test-agent-project-codex-session-wrapper.sh +97 -0
  132. package/tools/tests/test-agent-project-open-pr-worktree-config-prefix.sh +81 -0
  133. package/tools/tests/test-agent-project-openclaw-session-wrapper-clears-stale-sandbox-artifacts.sh +109 -0
  134. package/tools/tests/test-agent-project-openclaw-session-wrapper-infers-blocked-result-contract.sh +89 -0
  135. package/tools/tests/test-agent-project-openclaw-session-wrapper-recovers-literal-env-artifacts.sh +113 -0
  136. package/tools/tests/test-agent-project-openclaw-session-wrapper-recovers-version-mismatch.sh +135 -0
  137. package/tools/tests/test-agent-project-openclaw-session-wrapper-resident.sh +179 -0
  138. package/tools/tests/test-agent-project-openclaw-session-wrapper-reuses-existing-agent-after-add-race.sh +119 -0
  139. package/tools/tests/test-agent-project-openclaw-session-wrapper-terminates-rate-limit-hang.sh +91 -0
  140. package/tools/tests/test-agent-project-openclaw-session-wrapper.sh +117 -0
  141. package/tools/tests/test-agent-project-publish-issue-pr-prunes-stale-worktree-entry.sh +148 -0
  142. package/tools/tests/test-agent-project-publish-issue-pr-reads-archived-session.sh +146 -0
  143. package/tools/tests/test-agent-project-publish-issue-pr-recovers-final-head.sh +145 -0
  144. package/tools/tests/test-agent-project-publish-issue-pr-reuses-existing-worktree.sh +147 -0
  145. package/tools/tests/test-agent-project-reconcile-failure-reason.sh +456 -0
  146. package/tools/tests/test-agent-project-reconcile-issue-archived-session-fallback.sh +96 -0
  147. package/tools/tests/test-agent-project-reconcile-issue-before-blocked.sh +90 -0
  148. package/tools/tests/test-agent-project-reconcile-issue-host-verification-recovery-uses-recovered-worktree.sh +212 -0
  149. package/tools/tests/test-agent-project-reconcile-issue-host-verification-recovery.sh +207 -0
  150. package/tools/tests/test-agent-project-reconcile-issue-provider-quota-schedules-provider-cooldown.sh +101 -0
  151. package/tools/tests/test-agent-project-reconcile-issue-session-backfills-lane-metadata-from-worker-key.sh +113 -0
  152. package/tools/tests/test-agent-project-reconcile-issue-session-clears-stale-failed-summary.sh +117 -0
  153. package/tools/tests/test-agent-project-reconcile-issue-session-initializes-shared-agent-home.sh +55 -0
  154. package/tools/tests/test-agent-project-reconcile-issue-session-normalizes-runner-state.sh +125 -0
  155. package/tools/tests/test-agent-project-reconcile-issue-session-records-invalid-contract-summary.sh +118 -0
  156. package/tools/tests/test-agent-project-reconcile-issue-session-skips-duplicate-blocked-comment.sh +144 -0
  157. package/tools/tests/test-agent-project-reconcile-issue-session-standardizes-no-commits-blocker.sh +145 -0
  158. package/tools/tests/test-agent-project-reconcile-issue-session-synthesizes-blocked-comment.sh +139 -0
  159. package/tools/tests/test-agent-project-reconcile-pr-blocked-host-recovery.sh +242 -0
  160. package/tools/tests/test-agent-project-reconcile-pr-guard-blocked-no-commit.sh +142 -0
  161. package/tools/tests/test-agent-project-reconcile-pr-provider-quota-schedules-provider-cooldown.sh +106 -0
  162. package/tools/tests/test-agent-project-reconcile-pr-session-initializes-shared-agent-home.sh +66 -0
  163. package/tools/tests/test-agent-project-reconcile-pr-updated-branch-noop.sh +129 -0
  164. package/tools/tests/test-audit-agent-worktrees-active-launch-skips-git-inspection.sh +69 -0
  165. package/tools/tests/test-audit-agent-worktrees-broken-worktree.sh +43 -0
  166. package/tools/tests/test-audit-agent-worktrees-pending-launch-owner.sh +46 -0
  167. package/tools/tests/test-audit-agent-worktrees-unreconciled-owner.sh +79 -0
  168. package/tools/tests/test-audit-issue-routing-managed-branch-globs.sh +56 -0
  169. package/tools/tests/test-branch-verification-guard-generated-artifacts.sh +72 -0
  170. package/tools/tests/test-branch-verification-guard-targeted-coverage.sh +125 -0
  171. package/tools/tests/test-codex-quota-manager-failure-driven-rotation.sh +178 -0
  172. package/tools/tests/test-codex-quota-wrapper.sh +37 -0
  173. package/tools/tests/test-contribution-docs.sh +18 -0
  174. package/tools/tests/test-control-plane-dashboard-runtime-smoke.sh +343 -0
  175. package/tools/tests/test-create-follow-up-issue.sh +73 -0
  176. package/tools/tests/test-dashboard-launchd-bootstrap.sh +55 -0
  177. package/tools/tests/test-flow-export-execution-env-exports-repo-id.sh +30 -0
  178. package/tools/tests/test-flow-export-github-cli-auth-env-prefers-git-credential.sh +48 -0
  179. package/tools/tests/test-flow-github-api-repo-fallback-preserves-input.sh +85 -0
  180. package/tools/tests/test-flow-github-api-repo-prefers-explicit-repository-id.sh +60 -0
  181. package/tools/tests/test-flow-github-issue-list-falls-back-to-repository-id.sh +64 -0
  182. package/tools/tests/test-flow-github-pr-list-falls-back-to-repository-id.sh +77 -0
  183. package/tools/tests/test-flow-resident-can-reuse-does-not-leak-metadata.sh +52 -0
  184. package/tools/tests/test-flow-resident-reap-stale-controllers.sh +63 -0
  185. package/tools/tests/test-flow-resolve-codex-quota-tools.sh +104 -0
  186. package/tools/tests/test-flow-runtime-doctor-profile-selection.sh +27 -0
  187. package/tools/tests/test-heartbeat-codex-pr-linked-issue-exclusion.sh +79 -0
  188. package/tools/tests/test-heartbeat-hooks-enqueue-resident-issue-for-idle-controller.sh +115 -0
  189. package/tools/tests/test-heartbeat-hooks-enqueue-resident-issue-for-live-lane-controller.sh +117 -0
  190. package/tools/tests/test-heartbeat-hooks-start-resident-issue-loop-claude.sh +96 -0
  191. package/tools/tests/test-heartbeat-hooks-start-resident-issue-loop-codex.sh +96 -0
  192. package/tools/tests/test-heartbeat-hooks-start-resident-issue-loop.sh +96 -0
  193. package/tools/tests/test-heartbeat-loop-auth-wait-does-not-consume-capacity.sh +170 -0
  194. package/tools/tests/test-heartbeat-loop-blocked-recovery-lane.sh +201 -0
  195. package/tools/tests/test-heartbeat-loop-blocked-recovery-vs-pr-reservation.sh +201 -0
  196. package/tools/tests/test-heartbeat-loop-idle-resident-controller-does-not-block-launches.sh +160 -0
  197. package/tools/tests/test-heartbeat-loop-pr-launch-dedup.sh +133 -0
  198. package/tools/tests/test-heartbeat-loop-provider-cooldown-suppresses-launches.sh +157 -0
  199. package/tools/tests/test-heartbeat-loop-reaps-stale-resident-controller.sh +181 -0
  200. package/tools/tests/test-heartbeat-loop-waiting-provider-resident-controller-does-not-block-launches.sh +160 -0
  201. package/tools/tests/test-heartbeat-ready-issues-blocked-recovery.sh +134 -0
  202. package/tools/tests/test-heartbeat-safe-auto-dynamic-concurrency.sh +162 -0
  203. package/tools/tests/test-heartbeat-safe-auto-no-tmux-sessions.sh +136 -0
  204. package/tools/tests/test-heartbeat-safe-auto-openclaw-skips-codex-quota.sh +139 -0
  205. package/tools/tests/test-heartbeat-safe-auto-quota-health-signal.sh +119 -0
  206. package/tools/tests/test-heartbeat-safe-auto-stale-shared-loop-pid-does-not-skip.sh +140 -0
  207. package/tools/tests/test-heartbeat-safe-auto-static-capacity-without-quota-cache.sh +142 -0
  208. package/tools/tests/test-heartbeat-safe-auto-zero-healthy-pools.sh +141 -0
  209. package/tools/tests/test-heartbeat-sync-issue-labels-empty-schedule.sh +65 -0
  210. package/tools/tests/test-heartbeat-sync-open-agent-prs-terminal-clears-running.sh +179 -0
  211. package/tools/tests/test-install-dashboard-launchd.sh +78 -0
  212. package/tools/tests/test-install-project-launchd-adds-tool-paths.sh +87 -0
  213. package/tools/tests/test-install-project-launchd.sh +110 -0
  214. package/tools/tests/test-issue-local-workspace-install-policy.sh +81 -0
  215. package/tools/tests/test-issue-publish-scope-guard-docs-signal.sh +70 -0
  216. package/tools/tests/test-issue-reconcile-hooks-success-clears-blocked.sh +36 -0
  217. package/tools/tests/test-kick-scheduler-requires-explicit-profile.sh +47 -0
  218. package/tools/tests/test-label-follow-up-issues-falls-back-to-repository-id.sh +132 -0
  219. package/tools/tests/test-manual-operator-entrypoints-require-explicit-profile.sh +64 -0
  220. package/tools/tests/test-package-funding-metadata.sh +21 -0
  221. package/tools/tests/test-package-public-metadata.sh +62 -0
  222. package/tools/tests/test-placeholder-worker-adapters.sh +38 -0
  223. package/tools/tests/test-pr-reconcile-hooks-refreshes-recurring-issue-checklist.sh +110 -0
  224. package/tools/tests/test-pr-risk-cohesive-mobile-locale-scope.sh +70 -0
  225. package/tools/tests/test-pr-risk-fix-label-semantics.sh +114 -0
  226. package/tools/tests/test-pr-risk-local-first-no-checks.sh +70 -0
  227. package/tools/tests/test-prepare-worktree-simple-repo-baseline.sh +67 -0
  228. package/tools/tests/test-profile-activate.sh +33 -0
  229. package/tools/tests/test-profile-adopt-allow-missing-repo.sh +68 -0
  230. package/tools/tests/test-profile-adopt-skip-workspace-sync-missing-file.sh +61 -0
  231. package/tools/tests/test-profile-adopt-syncs-anchor-and-workspace.sh +90 -0
  232. package/tools/tests/test-profile-smoke-collision.sh +44 -0
  233. package/tools/tests/test-profile-smoke-invalid-claude-config.sh +31 -0
  234. package/tools/tests/test-profile-smoke-invalid-provider-pool.sh +68 -0
  235. package/tools/tests/test-profile-smoke-repo-slug-mismatch.sh +36 -0
  236. package/tools/tests/test-profile-smoke.sh +45 -0
  237. package/tools/tests/test-project-init-force-and-skip-sync.sh +61 -0
  238. package/tools/tests/test-project-init-repo-slug-mismatch.sh +29 -0
  239. package/tools/tests/test-project-init.sh +66 -0
  240. package/tools/tests/test-project-launchd-bootstrap.sh +66 -0
  241. package/tools/tests/test-project-remove.sh +150 -0
  242. package/tools/tests/test-project-runtime-supervisor.sh +47 -0
  243. package/tools/tests/test-project-runtimectl-launchd.sh +115 -0
  244. package/tools/tests/test-project-runtimectl-missing-profile.sh +54 -0
  245. package/tools/tests/test-project-runtimectl-start-falls-back-to-bootstrap.sh +108 -0
  246. package/tools/tests/test-project-runtimectl-status-reports-supervisor-as-heartbeat-parent.sh +95 -0
  247. package/tools/tests/test-project-runtimectl-status-supervisor-running.sh +59 -0
  248. package/tools/tests/test-project-runtimectl-stop-cancels-pending-kick.sh +85 -0
  249. package/tools/tests/test-project-runtimectl-stop-clears-running-labels.sh +78 -0
  250. package/tools/tests/test-project-runtimectl.sh +212 -0
  251. package/tools/tests/test-provider-cooldown-state-prefers-runtime-worker-context.sh +39 -0
  252. package/tools/tests/test-provider-cooldown-state.sh +59 -0
  253. package/tools/tests/test-public-repo-docs.sh +159 -0
  254. package/tools/tests/test-reconcile-pr-worker-acp-config-routing.sh +75 -0
  255. package/tools/tests/test-render-dashboard-snapshot.sh +149 -0
  256. package/tools/tests/test-render-flow-config-demo-profile.sh +36 -0
  257. package/tools/tests/test-render-flow-config-provider-pool-fallback.sh +81 -0
  258. package/tools/tests/test-render-flow-config.sh +52 -0
  259. package/tools/tests/test-run-codex-task-claude-routing.sh +125 -0
  260. package/tools/tests/test-run-codex-task-codex-resident-routing.sh +108 -0
  261. package/tools/tests/test-run-codex-task-kilo-routing.sh +98 -0
  262. package/tools/tests/test-run-codex-task-openclaw-resident-routing.sh +117 -0
  263. package/tools/tests/test-run-codex-task-openclaw-routing.sh +113 -0
  264. package/tools/tests/test-run-codex-task-opencode-routing.sh +98 -0
  265. package/tools/tests/test-run-codex-task-provider-pool-fallback-routing.sh +146 -0
  266. package/tools/tests/test-scaffold-profile.sh +108 -0
  267. package/tools/tests/test-serve-dashboard.sh +93 -0
  268. package/tools/tests/test-start-issue-worker-blocked-context.sh +129 -0
  269. package/tools/tests/test-start-issue-worker-blocks-complete-recurring-checklist.sh +189 -0
  270. package/tools/tests/test-start-issue-worker-local-install-routing.sh +157 -0
  271. package/tools/tests/test-start-issue-worker-profile-template-routing.sh +149 -0
  272. package/tools/tests/test-start-issue-worker-recurring-resident-reuse-codex.sh +212 -0
  273. package/tools/tests/test-start-issue-worker-recurring-resident-reuse.sh +219 -0
  274. package/tools/tests/test-start-issue-worker-renders-verification-snippet.sh +155 -0
  275. package/tools/tests/test-start-issue-worker-resident-reuse-falls-back-to-new-worktree.sh +199 -0
  276. package/tools/tests/test-start-pr-fix-worker-host-blocker-context.sh +275 -0
  277. package/tools/tests/test-start-resident-issue-loop-adopts-next-recurring-issue.sh +185 -0
  278. package/tools/tests/test-start-resident-issue-loop-clears-pending-while-waiting-due.sh +152 -0
  279. package/tools/tests/test-start-resident-issue-loop-consumes-queued-lease.sh +186 -0
  280. package/tools/tests/test-start-resident-issue-loop-fails-over-provider-pool.sh +212 -0
  281. package/tools/tests/test-start-resident-issue-loop-immediate-cycles.sh +148 -0
  282. package/tools/tests/test-start-resident-issue-loop-waits-for-provider.sh +194 -0
  283. package/tools/tests/test-start-resident-issue-loop-waits-for-terminal-reconcile-status.sh +198 -0
  284. package/tools/tests/test-start-resident-issue-loop-yields-to-live-lane-controller.sh +145 -0
  285. package/tools/tests/test-sync-pr-labels-fix-lane-uses-repair-queued.sh +67 -0
  286. package/tools/tests/test-sync-recurring-issue-checklist-backfills-workflow-complete-blocker.sh +70 -0
  287. package/tools/tests/test-sync-recurring-issue-checklist.sh +95 -0
  288. package/tools/tests/test-sync-shared-agent-home-local-source-root.sh +66 -0
  289. package/tools/tests/test-sync-shared-agent-home-preserves-unrelated-workflow-catalog-skill.sh +47 -0
  290. package/tools/tests/test-test-smoke.sh +86 -0
  291. package/tools/tests/test-uninstall-project-launchd.sh +37 -0
  292. package/tools/tests/test-update-github-labels-prefers-sibling-helper.sh +49 -0
  293. package/tools/tests/test-workflow-catalog.sh +43 -0
  294. package/tools/vendor/codex-quota/LICENSE +21 -0
  295. package/tools/vendor/codex-quota/README.md +459 -0
  296. package/tools/vendor/codex-quota/codex-quota.js +261 -0
  297. package/tools/vendor/codex-quota/lib/claude-accounts.js +226 -0
  298. package/tools/vendor/codex-quota/lib/claude-oauth.js +174 -0
  299. package/tools/vendor/codex-quota/lib/claude-tokens.js +471 -0
  300. package/tools/vendor/codex-quota/lib/claude-usage.js +929 -0
  301. package/tools/vendor/codex-quota/lib/codex-accounts.js +205 -0
  302. package/tools/vendor/codex-quota/lib/codex-tokens.js +326 -0
  303. package/tools/vendor/codex-quota/lib/codex-usage.js +32 -0
  304. package/tools/vendor/codex-quota/lib/color.js +72 -0
  305. package/tools/vendor/codex-quota/lib/constants.js +57 -0
  306. package/tools/vendor/codex-quota/lib/container.js +143 -0
  307. package/tools/vendor/codex-quota/lib/display.js +1111 -0
  308. package/tools/vendor/codex-quota/lib/fs.js +63 -0
  309. package/tools/vendor/codex-quota/lib/handlers.js +2060 -0
  310. package/tools/vendor/codex-quota/lib/jwt.js +33 -0
  311. package/tools/vendor/codex-quota/lib/oauth.js +486 -0
  312. package/tools/vendor/codex-quota/lib/paths.js +34 -0
  313. package/tools/vendor/codex-quota/lib/prompts.js +44 -0
  314. package/tools/vendor/codex-quota/lib/sync.js +1438 -0
  315. package/tools/vendor/codex-quota/lib/token-match.js +96 -0
  316. package/tools/vendor/codex-quota-manager/scripts/auto-switch.sh +500 -0
  317. package/tools/vendor/codex-quota-manager/scripts/batch-add.sh +123 -0
@@ -0,0 +1,1984 @@
1
+ #!/usr/bin/env node
2
+
3
+ const fs = require("fs");
4
+ const os = require("os");
5
+ const path = require("path");
6
+ const readline = require("readline");
7
+ const util = require("util");
8
+ const { spawnSync } = require("child_process");
9
+
10
+ const packageRoot = path.resolve(__dirname, "..", "..");
11
+ const packageJson = require(path.join(packageRoot, "package.json"));
12
+ const skillName = "agent-control-plane";
13
+ let setupJsonOutputEnabled = false;
14
+
15
+ function printHelp() {
16
+ console.log(`agent-control-plane ${packageJson.version}
17
+
18
+ Usage:
19
+ agent-control-plane <command> [args...]
20
+
21
+ Commands:
22
+ help Show this help
23
+ version Print package version
24
+ setup Guided setup flow for one repo profile
25
+ sync Publish the packaged runtime into ~/.agent-runtime
26
+ install Alias for sync
27
+ init Scaffold and adopt a project profile
28
+ doctor Inspect runtime/source installation state
29
+ profile-smoke Validate installed profiles
30
+ dashboard Start the dashboard server
31
+ launchd-install Install a per-project LaunchAgent (macOS)
32
+ launchd-uninstall Remove a per-project LaunchAgent (macOS)
33
+ runtime Forward to tools/bin/project-runtimectl.sh
34
+ remove Remove one installed profile and runtime state
35
+ smoke Run the packaged smoke suite
36
+ `);
37
+ }
38
+
39
+ function printSetupHelp() {
40
+ console.log(`agent-control-plane setup
41
+
42
+ Usage:
43
+ agent-control-plane setup [options]
44
+
45
+ Guided install/bootstrap flow for one repo profile. It can detect the current
46
+ repo, suggest sane runtime paths, sync ACP into ~/.agent-runtime, scaffold one
47
+ profile, run doctor checks, and optionally start the runtime.
48
+
49
+ Options:
50
+ --profile-id <id> Profile id to create or refresh
51
+ --repo-slug <owner/repo> GitHub repo slug
52
+ --repo-root <path> Local checkout to manage (defaults to current git root)
53
+ --agent-root <path> ACP-managed runtime root for this profile
54
+ --agent-repo-root <path> Clean ACP-managed anchor repo root
55
+ --worktree-root <path> Parent root for ACP worktrees
56
+ --retained-repo-root <path> Manual checkout root to keep linked in the profile
57
+ --vscode-workspace-file <path>
58
+ Workspace file path ACP should generate/use
59
+ --coding-worker <backend> One of: codex, claude, openclaw
60
+ --force Overwrite an existing profile
61
+ --skip-anchor-sync Skip profile-adopt anchor repo sync
62
+ --skip-workspace-sync Skip profile-adopt workspace sync
63
+ --allow-missing-repo Allow setup even when the source repo is incomplete
64
+ --install-missing-deps Install missing core dependencies automatically when supported
65
+ --no-install-missing-deps Skip dependency installation prompts and auto install
66
+ --install-missing-backend Install the selected worker backend automatically when ACP knows how
67
+ --no-install-missing-backend Skip worker backend installation prompts and auto install
68
+ --gh-auth-login Run \`gh auth login\` automatically when GitHub auth is not ready
69
+ --no-gh-auth-login Skip the GitHub auth prompt during setup
70
+ --dry-run Render the setup plan without making changes
71
+ --plan Alias for --dry-run
72
+ --json Emit one JSON result object and send progress logs to stderr
73
+ --start-runtime Start the runtime after setup
74
+ --no-start-runtime Do not start the runtime after setup
75
+ --install-launchd Install macOS autostart after a successful runtime start
76
+ --no-install-launchd Do not install macOS autostart
77
+ --yes Accept detected defaults without prompting
78
+ --non-interactive Same as --yes
79
+ --help Show this help
80
+ `);
81
+ }
82
+
83
+ function copyTree(sourceDir, targetDir) {
84
+ if (!fs.existsSync(sourceDir)) {
85
+ return;
86
+ }
87
+
88
+ fs.cpSync(sourceDir, targetDir, {
89
+ recursive: true,
90
+ filter: (sourcePath) => {
91
+ const base = path.basename(sourcePath);
92
+ if (base === ".git" || base === "node_modules" || base === ".DS_Store") {
93
+ return false;
94
+ }
95
+ if (base === "__pycache__") {
96
+ return false;
97
+ }
98
+ if (base.endsWith(".bak") || base.endsWith(".bak2") || base.endsWith(".bak3")) {
99
+ return false;
100
+ }
101
+ return true;
102
+ }
103
+ });
104
+ }
105
+
106
+ function stageSharedHome() {
107
+ const stageRoot = fs.mkdtempSync(path.join(os.tmpdir(), "agent-control-plane-"));
108
+ const sharedHome = path.join(stageRoot, "shared-home");
109
+ const stagedSkillRoot = path.join(sharedHome, "skills", "openclaw", skillName);
110
+
111
+ fs.mkdirSync(path.dirname(stagedSkillRoot), { recursive: true });
112
+ copyTree(packageRoot, stagedSkillRoot);
113
+ copyTree(path.join(packageRoot, "tools"), path.join(sharedHome, "tools"));
114
+
115
+ return {
116
+ stageRoot: fs.realpathSync.native(stageRoot),
117
+ sharedHome: fs.realpathSync.native(sharedHome),
118
+ stagedSkillRoot: fs.realpathSync.native(stagedSkillRoot)
119
+ };
120
+ }
121
+
122
+ function resolvePlatformHome(env = process.env) {
123
+ const homeDir = env.HOME || os.homedir();
124
+ return env.AGENT_PLATFORM_HOME || path.join(homeDir, ".agent-runtime");
125
+ }
126
+
127
+ function createExecutionContext(stage) {
128
+ const homeDir = process.env.HOME || os.homedir();
129
+ const platformHome = resolvePlatformHome(process.env);
130
+ const runtimeHome = path.join(platformHome, "runtime-home");
131
+ const profileRegistryRoot = path.join(platformHome, "control-plane", "profiles");
132
+ const env = {
133
+ ...process.env,
134
+ SHARED_AGENT_HOME: stage.sharedHome,
135
+ AGENT_CONTROL_PLANE_ROOT: stage.stagedSkillRoot,
136
+ ACP_ROOT: stage.stagedSkillRoot,
137
+ AGENT_FLOW_SOURCE_ROOT: stage.stagedSkillRoot,
138
+ ACP_PROJECT_INIT_SOURCE_HOME: stage.sharedHome,
139
+ ACP_PROJECT_INIT_RUNTIME_HOME: runtimeHome,
140
+ ACP_PROJECT_RUNTIME_SOURCE_HOME: stage.sharedHome,
141
+ ACP_DASHBOARD_SOURCE_HOME: stage.sharedHome,
142
+ ACP_PROFILE_REGISTRY_ROOT: profileRegistryRoot,
143
+ ACP_PROJECT_RUNTIME_PROFILE_REGISTRY_ROOT: profileRegistryRoot,
144
+ ACP_DASHBOARD_PROFILE_REGISTRY_ROOT: profileRegistryRoot,
145
+ HOME: homeDir
146
+ };
147
+
148
+ return {
149
+ ...stage,
150
+ env,
151
+ homeDir,
152
+ platformHome,
153
+ runtimeHome,
154
+ profileRegistryRoot
155
+ };
156
+ }
157
+
158
+ function runScriptWithContext(context, scriptRelativePath, forwardedArgs, options = {}) {
159
+ const scriptPath = path.join(packageRoot, scriptRelativePath);
160
+ const stdio = options.stdio || "inherit";
161
+ const result = spawnSync("bash", [scriptPath, ...forwardedArgs], {
162
+ stdio,
163
+ encoding: stdio === "inherit" ? undefined : "utf8",
164
+ env: options.env || context.env,
165
+ cwd: options.cwd || process.cwd()
166
+ });
167
+
168
+ if (typeof result.status === "number") {
169
+ return result;
170
+ }
171
+
172
+ return {
173
+ ...result,
174
+ status: result.error ? 1 : 0
175
+ };
176
+ }
177
+
178
+ function runCommand(scriptRelativePath, forwardedArgs) {
179
+ const stage = stageSharedHome();
180
+ const context = createExecutionContext(stage);
181
+
182
+ try {
183
+ const result = runScriptWithContext(context, scriptRelativePath, forwardedArgs, { stdio: "inherit" });
184
+ return result.status;
185
+ } finally {
186
+ fs.rmSync(stage.stageRoot, { recursive: true, force: true });
187
+ }
188
+ }
189
+
190
+ function runCapture(command, args, options = {}) {
191
+ const result = spawnSync(command, args, {
192
+ encoding: "utf8",
193
+ env: options.env || process.env,
194
+ cwd: options.cwd || process.cwd()
195
+ });
196
+ return {
197
+ status: typeof result.status === "number" ? result.status : (result.error ? 1 : 0),
198
+ stdout: result.stdout || "",
199
+ stderr: result.stderr || ""
200
+ };
201
+ }
202
+
203
+ function commandExists(command) {
204
+ return runCapture("bash", ["-lc", `command -v ${JSON.stringify(command)} >/dev/null 2>&1`]).status === 0;
205
+ }
206
+
207
+ function detectRepoRoot(startDir) {
208
+ const probe = runCapture("git", ["rev-parse", "--show-toplevel"], { cwd: startDir });
209
+ if (probe.status !== 0) {
210
+ return "";
211
+ }
212
+ return probe.stdout.trim();
213
+ }
214
+
215
+ function parseGithubRepoSlug(remoteUrl) {
216
+ const trimmed = String(remoteUrl || "").trim();
217
+ const patterns = [
218
+ /^git@github\.com:([^/]+\/[^/]+?)(?:\.git)?$/,
219
+ /^https:\/\/github\.com\/([^/]+\/[^/]+?)(?:\.git)?$/,
220
+ /^ssh:\/\/git@github\.com\/([^/]+\/[^/]+?)(?:\.git)?$/
221
+ ];
222
+
223
+ for (const pattern of patterns) {
224
+ const match = trimmed.match(pattern);
225
+ if (match) {
226
+ return match[1];
227
+ }
228
+ }
229
+
230
+ return "";
231
+ }
232
+
233
+ function detectRepoSlug(repoRoot) {
234
+ if (!repoRoot) {
235
+ return "";
236
+ }
237
+ const remote = runCapture("git", ["remote", "get-url", "origin"], { cwd: repoRoot });
238
+ if (remote.status !== 0) {
239
+ return "";
240
+ }
241
+ return parseGithubRepoSlug(remote.stdout.trim());
242
+ }
243
+
244
+ function sanitizeProfileId(value) {
245
+ const lowered = String(value || "")
246
+ .trim()
247
+ .toLowerCase()
248
+ .replace(/[^a-z0-9]+/g, "-")
249
+ .replace(/^-+|-+$/g, "");
250
+ if (!lowered) {
251
+ return "repo";
252
+ }
253
+ if (/^[a-z0-9]/.test(lowered)) {
254
+ return lowered;
255
+ }
256
+ return `repo-${lowered}`;
257
+ }
258
+
259
+ function detectPreferredWorker() {
260
+ if (commandExists("codex")) {
261
+ return "codex";
262
+ }
263
+ if (commandExists("claude")) {
264
+ return "claude";
265
+ }
266
+ if (commandExists("openclaw")) {
267
+ return "openclaw";
268
+ }
269
+ return "openclaw";
270
+ }
271
+
272
+ function parseKvOutput(text) {
273
+ const records = {};
274
+ for (const rawLine of String(text || "").split(/\r?\n/)) {
275
+ const line = rawLine.trim();
276
+ if (!line || !line.includes("=")) {
277
+ continue;
278
+ }
279
+ const idx = line.indexOf("=");
280
+ const key = line.slice(0, idx);
281
+ const value = line.slice(idx + 1);
282
+ records[key] = value;
283
+ }
284
+ return records;
285
+ }
286
+
287
+ function printFailureDetails(result) {
288
+ const stdoutTarget = setupJsonOutputEnabled ? process.stderr : process.stdout;
289
+ const stderrTarget = process.stderr;
290
+ if (result.stdout) {
291
+ stdoutTarget.write(result.stdout);
292
+ if (!result.stdout.endsWith("\n")) {
293
+ stdoutTarget.write("\n");
294
+ }
295
+ }
296
+ if (result.stderr) {
297
+ stderrTarget.write(result.stderr);
298
+ if (!result.stderr.endsWith("\n")) {
299
+ stderrTarget.write("\n");
300
+ }
301
+ }
302
+ }
303
+
304
+ function parseSetupArgs(args) {
305
+ const options = {
306
+ profileId: "",
307
+ repoSlug: "",
308
+ repoRoot: "",
309
+ agentRoot: "",
310
+ agentRepoRoot: "",
311
+ worktreeRoot: "",
312
+ retainedRepoRoot: "",
313
+ vscodeWorkspaceFile: "",
314
+ codingWorker: "",
315
+ sourceRepoRoot: "",
316
+ force: false,
317
+ skipAnchorSync: false,
318
+ skipWorkspaceSync: false,
319
+ allowMissingRepo: false,
320
+ installMissingDeps: null,
321
+ installMissingBackend: null,
322
+ ghAuthLogin: null,
323
+ dryRun: false,
324
+ json: false,
325
+ startRuntime: null,
326
+ installLaunchd: null,
327
+ interactive: process.stdin.isTTY && process.stdout.isTTY,
328
+ help: false
329
+ };
330
+
331
+ for (let index = 0; index < args.length; index += 1) {
332
+ const current = args[index];
333
+ switch (current) {
334
+ case "--profile-id":
335
+ options.profileId = args[++index] || "";
336
+ break;
337
+ case "--repo-slug":
338
+ options.repoSlug = args[++index] || "";
339
+ break;
340
+ case "--repo-root":
341
+ options.repoRoot = args[++index] || "";
342
+ break;
343
+ case "--agent-root":
344
+ options.agentRoot = args[++index] || "";
345
+ break;
346
+ case "--agent-repo-root":
347
+ options.agentRepoRoot = args[++index] || "";
348
+ break;
349
+ case "--worktree-root":
350
+ options.worktreeRoot = args[++index] || "";
351
+ break;
352
+ case "--retained-repo-root":
353
+ options.retainedRepoRoot = args[++index] || "";
354
+ break;
355
+ case "--vscode-workspace-file":
356
+ options.vscodeWorkspaceFile = args[++index] || "";
357
+ break;
358
+ case "--coding-worker":
359
+ options.codingWorker = args[++index] || "";
360
+ break;
361
+ case "--source-repo-root":
362
+ options.sourceRepoRoot = args[++index] || "";
363
+ break;
364
+ case "--force":
365
+ options.force = true;
366
+ break;
367
+ case "--skip-anchor-sync":
368
+ options.skipAnchorSync = true;
369
+ break;
370
+ case "--skip-workspace-sync":
371
+ options.skipWorkspaceSync = true;
372
+ break;
373
+ case "--allow-missing-repo":
374
+ options.allowMissingRepo = true;
375
+ break;
376
+ case "--install-missing-deps":
377
+ options.installMissingDeps = true;
378
+ break;
379
+ case "--no-install-missing-deps":
380
+ options.installMissingDeps = false;
381
+ break;
382
+ case "--install-missing-backend":
383
+ options.installMissingBackend = true;
384
+ break;
385
+ case "--no-install-missing-backend":
386
+ options.installMissingBackend = false;
387
+ break;
388
+ case "--gh-auth-login":
389
+ options.ghAuthLogin = true;
390
+ break;
391
+ case "--no-gh-auth-login":
392
+ options.ghAuthLogin = false;
393
+ break;
394
+ case "--dry-run":
395
+ case "--plan":
396
+ options.dryRun = true;
397
+ break;
398
+ case "--json":
399
+ options.json = true;
400
+ options.interactive = false;
401
+ break;
402
+ case "--start-runtime":
403
+ options.startRuntime = true;
404
+ break;
405
+ case "--no-start-runtime":
406
+ options.startRuntime = false;
407
+ break;
408
+ case "--install-launchd":
409
+ options.installLaunchd = true;
410
+ break;
411
+ case "--no-install-launchd":
412
+ options.installLaunchd = false;
413
+ break;
414
+ case "--yes":
415
+ case "--non-interactive":
416
+ options.interactive = false;
417
+ break;
418
+ case "--help":
419
+ case "-h":
420
+ options.help = true;
421
+ break;
422
+ default:
423
+ throw new Error(`Unknown argument for setup: ${current}`);
424
+ }
425
+ }
426
+
427
+ return options;
428
+ }
429
+
430
+ function createPromptInterface() {
431
+ return readline.createInterface({
432
+ input: process.stdin,
433
+ output: process.stdout
434
+ });
435
+ }
436
+
437
+ function question(rl, prompt) {
438
+ return new Promise((resolve) => rl.question(prompt, resolve));
439
+ }
440
+
441
+ async function promptText(rl, label, defaultValue) {
442
+ const suffix = defaultValue ? ` [${defaultValue}]` : "";
443
+ const answer = (await question(rl, `${label}${suffix}: `)).trim();
444
+ return answer || defaultValue;
445
+ }
446
+
447
+ async function promptYesNo(rl, label, defaultValue) {
448
+ const suffix = defaultValue ? " [Y/n]" : " [y/N]";
449
+ const answer = (await question(rl, `${label}${suffix}: `)).trim().toLowerCase();
450
+ if (!answer) {
451
+ return defaultValue;
452
+ }
453
+ if (answer === "y" || answer === "yes") {
454
+ return true;
455
+ }
456
+ if (answer === "n" || answer === "no") {
457
+ return false;
458
+ }
459
+ return defaultValue;
460
+ }
461
+
462
+ function buildSetupPaths(platformHome, repoRoot, profileId, overrides) {
463
+ const agentRoot = path.resolve(overrides.agentRoot || path.join(platformHome, "projects", profileId));
464
+ const repoRootResolved = path.resolve(repoRoot);
465
+ return {
466
+ repoRoot: repoRootResolved,
467
+ agentRoot,
468
+ agentRepoRoot: path.resolve(overrides.agentRepoRoot || path.join(agentRoot, "repo")),
469
+ worktreeRoot: path.resolve(overrides.worktreeRoot || path.join(agentRoot, "worktrees")),
470
+ retainedRepoRoot: path.resolve(overrides.retainedRepoRoot || repoRootResolved),
471
+ vscodeWorkspaceFile: path.resolve(overrides.vscodeWorkspaceFile || path.join(agentRoot, `${profileId}-agents.code-workspace`)),
472
+ sourceRepoRoot: path.resolve(overrides.sourceRepoRoot || repoRootResolved)
473
+ };
474
+ }
475
+
476
+ function collectPrereqStatus(codingWorker) {
477
+ const requiredTools = ["bash", "git", "gh", "jq", "python3", "tmux"];
478
+ const missingRequired = requiredTools.filter((tool) => !commandExists(tool));
479
+ const workerCommand = codingWorker;
480
+ const workerAvailable = commandExists(workerCommand);
481
+ const ghAuthResult = commandExists("gh") ? runCapture("gh", ["auth", "status"]) : { status: 1, stdout: "", stderr: "" };
482
+
483
+ return {
484
+ missingRequired,
485
+ coreToolsOk: missingRequired.length === 0,
486
+ workerCommand,
487
+ workerAvailable,
488
+ ghAuthOk: ghAuthResult.status === 0,
489
+ ghAuthOutput: `${ghAuthResult.stdout}${ghAuthResult.stderr}`.trim()
490
+ };
491
+ }
492
+
493
+ function detectPackageManager() {
494
+ if (commandExists("brew")) {
495
+ return { name: "brew" };
496
+ }
497
+ if (commandExists("apt-get")) {
498
+ return { name: "apt-get" };
499
+ }
500
+ if (commandExists("dnf")) {
501
+ return { name: "dnf" };
502
+ }
503
+ if (commandExists("yum")) {
504
+ return { name: "yum" };
505
+ }
506
+ if (commandExists("pacman")) {
507
+ return { name: "pacman" };
508
+ }
509
+ return null;
510
+ }
511
+
512
+ function dependencyPackageMap(managerName) {
513
+ switch (managerName) {
514
+ case "brew":
515
+ return {
516
+ bash: "bash",
517
+ git: "git",
518
+ gh: "gh",
519
+ jq: "jq",
520
+ python3: "python",
521
+ tmux: "tmux"
522
+ };
523
+ case "apt-get":
524
+ return {
525
+ bash: "bash",
526
+ git: "git",
527
+ gh: "gh",
528
+ jq: "jq",
529
+ python3: "python3",
530
+ tmux: "tmux"
531
+ };
532
+ case "dnf":
533
+ case "yum":
534
+ return {
535
+ bash: "bash",
536
+ git: "git",
537
+ gh: "gh",
538
+ jq: "jq",
539
+ python3: "python3",
540
+ tmux: "tmux"
541
+ };
542
+ case "pacman":
543
+ return {
544
+ bash: "bash",
545
+ git: "git",
546
+ gh: "github-cli",
547
+ jq: "jq",
548
+ python3: "python",
549
+ tmux: "tmux"
550
+ };
551
+ default:
552
+ return {};
553
+ }
554
+ }
555
+
556
+ function sudoPrefix() {
557
+ if (typeof process.getuid === "function" && process.getuid() === 0) {
558
+ return [];
559
+ }
560
+ return commandExists("sudo") ? ["sudo"] : [];
561
+ }
562
+
563
+ function buildDependencyInstallPlan(missingTools) {
564
+ const manager = detectPackageManager();
565
+ if (!manager) {
566
+ return null;
567
+ }
568
+
569
+ const packageMap = dependencyPackageMap(manager.name);
570
+ const packages = [...new Set(
571
+ missingTools
572
+ .map((tool) => packageMap[tool])
573
+ .filter(Boolean)
574
+ )];
575
+
576
+ if (packages.length === 0) {
577
+ return null;
578
+ }
579
+
580
+ const prefix = sudoPrefix();
581
+ const commands = [];
582
+ switch (manager.name) {
583
+ case "brew":
584
+ commands.push(["brew", "install", ...packages]);
585
+ break;
586
+ case "apt-get":
587
+ commands.push([...prefix, "apt-get", "update"]);
588
+ commands.push([...prefix, "apt-get", "install", "-y", ...packages]);
589
+ break;
590
+ case "dnf":
591
+ commands.push([...prefix, "dnf", "install", "-y", ...packages]);
592
+ break;
593
+ case "yum":
594
+ commands.push([...prefix, "yum", "install", "-y", ...packages]);
595
+ break;
596
+ case "pacman":
597
+ commands.push([...prefix, "pacman", "-Sy", "--noconfirm", ...packages]);
598
+ break;
599
+ default:
600
+ return null;
601
+ }
602
+
603
+ return {
604
+ manager: manager.name,
605
+ packages,
606
+ commands
607
+ };
608
+ }
609
+
610
+ function shellQuote(value) {
611
+ return `'${String(value).replace(/'/g, `'\\''`)}'`;
612
+ }
613
+
614
+ function formatCommand(args) {
615
+ return args.map(shellQuote).join(" ");
616
+ }
617
+
618
+ function runInteractiveCommand(command, args) {
619
+ const result = spawnSync(command, args, {
620
+ stdio: "inherit",
621
+ env: process.env
622
+ });
623
+ return typeof result.status === "number" ? result.status : (result.error ? 1 : 0);
624
+ }
625
+
626
+ async function maybeInstallMissingDependencies(options, prereq) {
627
+ if (prereq.coreToolsOk) {
628
+ return {
629
+ status: "not-needed",
630
+ reason: "",
631
+ installer: "",
632
+ commands: []
633
+ };
634
+ }
635
+
636
+ const plan = buildDependencyInstallPlan(prereq.missingRequired);
637
+ if (!plan) {
638
+ console.log("\nACP found missing core dependencies but cannot install them automatically on this machine.");
639
+ console.log(`- missing tools: ${prereq.missingRequired.join(", ")}`);
640
+ console.log("- supported auto-install package managers today: brew, apt-get, dnf, yum, pacman");
641
+ return {
642
+ status: "unavailable",
643
+ reason: "no-supported-package-manager",
644
+ installer: "",
645
+ commands: []
646
+ };
647
+ }
648
+
649
+ if (options.installMissingDeps === false) {
650
+ console.log("\nSkipping dependency installation because the setup flags disabled it.");
651
+ return {
652
+ status: "skipped",
653
+ reason: "disabled",
654
+ installer: plan.manager,
655
+ commands: plan.commands
656
+ };
657
+ }
658
+
659
+ let shouldInstall = options.installMissingDeps === true;
660
+ if (options.installMissingDeps === null && options.interactive) {
661
+ console.log("\nACP can install the missing core dependencies for you.");
662
+ console.log(`- package manager: ${plan.manager}`);
663
+ console.log(`- packages: ${plan.packages.join(", ")}`);
664
+ console.log(`- command preview: ${plan.commands.map(formatCommand).join(" && ")}`);
665
+ const rl = createPromptInterface();
666
+ try {
667
+ shouldInstall = await promptYesNo(rl, "Install missing core dependencies now", true);
668
+ } finally {
669
+ rl.close();
670
+ }
671
+ }
672
+
673
+ if (!shouldInstall) {
674
+ console.log("\nDependency installation skipped.");
675
+ return {
676
+ status: "skipped",
677
+ reason: "not-confirmed",
678
+ installer: plan.manager,
679
+ commands: plan.commands
680
+ };
681
+ }
682
+
683
+ console.log("\nInstalling missing core dependencies...");
684
+ for (const commandArgs of plan.commands) {
685
+ const [command, ...rest] = commandArgs;
686
+ console.log(`> ${formatCommand(commandArgs)}`);
687
+ const status = runInteractiveCommand(command, rest);
688
+ if (status !== 0) {
689
+ return {
690
+ status: "failed",
691
+ reason: `command-failed:${command}`,
692
+ installer: plan.manager,
693
+ commands: plan.commands
694
+ };
695
+ }
696
+ }
697
+
698
+ return {
699
+ status: "ok",
700
+ reason: "",
701
+ installer: plan.manager,
702
+ commands: plan.commands
703
+ };
704
+ }
705
+
706
+ async function maybeRunGithubAuthLogin(options, prereq) {
707
+ if (prereq.ghAuthOk) {
708
+ return { status: "not-needed", reason: "" };
709
+ }
710
+ if (!commandExists("gh")) {
711
+ console.log("\nGitHub authentication cannot run yet because `gh` is not installed.");
712
+ return { status: "skipped", reason: "gh-missing" };
713
+ }
714
+ if (options.ghAuthLogin === false) {
715
+ console.log("\nSkipping `gh auth login` because the setup flags disabled it.");
716
+ return { status: "skipped", reason: "disabled" };
717
+ }
718
+
719
+ let shouldRun = options.ghAuthLogin === true;
720
+ if (options.ghAuthLogin === null && options.interactive) {
721
+ console.log("\nGitHub CLI is not authenticated for this OS user.");
722
+ const rl = createPromptInterface();
723
+ try {
724
+ shouldRun = await promptYesNo(rl, "Run `gh auth login` now", true);
725
+ } finally {
726
+ rl.close();
727
+ }
728
+ }
729
+
730
+ if (!shouldRun) {
731
+ console.log("\nGitHub authentication skipped.");
732
+ return { status: "skipped", reason: "not-confirmed" };
733
+ }
734
+
735
+ console.log("\nLaunching GitHub authentication...");
736
+ const status = runInteractiveCommand("gh", ["auth", "login"]);
737
+ if (status !== 0) {
738
+ return { status: "failed", reason: "gh-auth-login-failed" };
739
+ }
740
+ return { status: "ok", reason: "" };
741
+ }
742
+
743
+ function backendReadinessHint(workerCommand) {
744
+ switch (workerCommand) {
745
+ case "codex":
746
+ return "Make sure Codex is installed and authenticated for this OS user before you start background runs.";
747
+ case "claude":
748
+ return "Make sure Claude Code is installed and authenticated for this OS user before you start background runs.";
749
+ case "openclaw":
750
+ return "Make sure OpenClaw is installed and provider credentials are ready before you start background runs.";
751
+ default:
752
+ return "Make sure the selected worker backend is installed and authenticated before you start background runs.";
753
+ }
754
+ }
755
+
756
+ function backendSetupGuide(workerCommand) {
757
+ switch (workerCommand) {
758
+ case "codex":
759
+ return {
760
+ title: "Codex",
761
+ docsUrl: "https://platform.openai.com/docs/codex",
762
+ installExamples: [
763
+ "npm install -g @openai/codex"
764
+ ],
765
+ authExamples: [
766
+ "codex login"
767
+ ],
768
+ verifyExamples: [
769
+ "codex --version",
770
+ "codex login status"
771
+ ],
772
+ note: "ACP can install Codex through npm when npm is available. Authentication and account selection still stay with your local Codex setup."
773
+ };
774
+ case "claude":
775
+ return {
776
+ title: "Claude Code",
777
+ docsUrl: "https://docs.anthropic.com/en/docs/claude-code/setup",
778
+ installExamples: [
779
+ "npm install -g @anthropic-ai/claude-code"
780
+ ],
781
+ authExamples: [
782
+ "claude auth login"
783
+ ],
784
+ verifyExamples: [
785
+ "claude --version",
786
+ "claude doctor"
787
+ ],
788
+ note: "ACP uses the npm install path when available. Anthropic also documents a native installer if you prefer not to use npm."
789
+ };
790
+ case "openclaw":
791
+ return {
792
+ title: "OpenClaw",
793
+ docsUrl: "https://docs.openclaw.ai/start/getting-started",
794
+ installExamples: [
795
+ "npm install -g openclaw@latest"
796
+ ],
797
+ authExamples: [
798
+ "openclaw setup --wizard",
799
+ "openclaw onboard --install-daemon"
800
+ ],
801
+ verifyExamples: [
802
+ "openclaw doctor",
803
+ "openclaw status"
804
+ ],
805
+ note: "ACP uses the npm install path when npm is available. OpenClaw also documents a dedicated install script and onboarding flow."
806
+ };
807
+ default:
808
+ return {
809
+ title: workerCommand,
810
+ docsUrl: "",
811
+ installExamples: [],
812
+ authExamples: [],
813
+ verifyExamples: [],
814
+ note: "Install and authenticate the selected worker backend before you ask ACP to start background runs."
815
+ };
816
+ }
817
+ }
818
+
819
+ function buildWorkerBackendInstallPlan(workerCommand) {
820
+ if (!commandExists("npm")) {
821
+ return null;
822
+ }
823
+
824
+ switch (workerCommand) {
825
+ case "codex":
826
+ return {
827
+ installer: "npm",
828
+ commands: [["npm", "install", "-g", "@openai/codex"]]
829
+ };
830
+ case "claude":
831
+ return {
832
+ installer: "npm",
833
+ commands: [["npm", "install", "-g", "@anthropic-ai/claude-code"]]
834
+ };
835
+ case "openclaw":
836
+ return {
837
+ installer: "npm",
838
+ commands: [["npm", "install", "-g", "openclaw@latest"]]
839
+ };
840
+ default:
841
+ return null;
842
+ }
843
+ }
844
+
845
+ function detectUrlOpener() {
846
+ if (commandExists("open")) {
847
+ return "open";
848
+ }
849
+ if (commandExists("xdg-open")) {
850
+ return "xdg-open";
851
+ }
852
+ return "";
853
+ }
854
+
855
+ function printWorkerSetupGuide(guide) {
856
+ console.log(`\n${guide.title} backend setup`);
857
+ if (guide.installExamples.length > 0) {
858
+ console.log("- install");
859
+ for (const example of guide.installExamples) {
860
+ console.log(` ${example}`);
861
+ }
862
+ }
863
+ if (guide.authExamples.length > 0) {
864
+ console.log("- authenticate");
865
+ for (const example of guide.authExamples) {
866
+ console.log(` ${example}`);
867
+ }
868
+ }
869
+ if (guide.verifyExamples.length > 0) {
870
+ console.log("- verify");
871
+ for (const example of guide.verifyExamples) {
872
+ console.log(` ${example}`);
873
+ }
874
+ }
875
+ if (guide.docsUrl) {
876
+ console.log(`- docs: ${guide.docsUrl}`);
877
+ }
878
+ if (guide.note) {
879
+ console.log(`- note: ${guide.note}`);
880
+ }
881
+ }
882
+
883
+ async function maybeShowWorkerSetupGuide(options, prereq) {
884
+ const guide = backendSetupGuide(prereq.workerCommand);
885
+ if (prereq.workerAvailable) {
886
+ return {
887
+ status: "not-needed",
888
+ reason: "",
889
+ installStatus: "not-needed",
890
+ installReason: "",
891
+ installer: "",
892
+ commands: [],
893
+ docsOpened: "not-needed",
894
+ guide
895
+ };
896
+ }
897
+
898
+ printWorkerSetupGuide(guide);
899
+
900
+ const installPlan = buildWorkerBackendInstallPlan(prereq.workerCommand);
901
+ if (!installPlan) {
902
+ console.log("\nACP does not know a safe automated install command for this worker on the current machine.");
903
+ }
904
+
905
+ if (options.installMissingBackend === false) {
906
+ return {
907
+ status: "shown",
908
+ reason: "backend-missing",
909
+ installStatus: "skipped",
910
+ installReason: "disabled",
911
+ installer: installPlan ? installPlan.installer : "",
912
+ commands: installPlan ? installPlan.commands : [],
913
+ docsOpened: installPlan ? "skipped" : "unsupported",
914
+ guide
915
+ };
916
+ }
917
+
918
+ let shouldInstall = options.installMissingBackend === true;
919
+ if (!shouldInstall && installPlan && options.installMissingBackend === null && options.interactive) {
920
+ console.log("\nACP can try to install the selected worker backend for you.");
921
+ console.log(`- installer: ${installPlan.installer}`);
922
+ console.log(`- command preview: ${installPlan.commands.map(formatCommand).join(" && ")}`);
923
+ const rl = createPromptInterface();
924
+ try {
925
+ shouldInstall = await promptYesNo(rl, `Install ${guide.title} now`, true);
926
+ } finally {
927
+ rl.close();
928
+ }
929
+ }
930
+
931
+ let installStatus = "unavailable";
932
+ let installReason = installPlan ? "" : "no-supported-installer";
933
+ if (installPlan && shouldInstall) {
934
+ console.log(`\nInstalling ${guide.title}...`);
935
+ installStatus = "ok";
936
+ for (const commandArgs of installPlan.commands) {
937
+ const [command, ...rest] = commandArgs;
938
+ console.log(`> ${formatCommand(commandArgs)}`);
939
+ const status = runInteractiveCommand(command, rest);
940
+ if (status !== 0) {
941
+ installStatus = "failed";
942
+ installReason = `command-failed:${command}`;
943
+ break;
944
+ }
945
+ }
946
+ } else if (installPlan) {
947
+ installStatus = "skipped";
948
+ installReason = options.installMissingBackend === null ? "not-confirmed" : "disabled";
949
+ }
950
+
951
+ const refreshedPrereq = collectPrereqStatus(prereq.workerCommand);
952
+ if (refreshedPrereq.workerAvailable) {
953
+ return {
954
+ status: "shown",
955
+ reason: "backend-missing",
956
+ installStatus,
957
+ installReason,
958
+ installer: installPlan ? installPlan.installer : "",
959
+ commands: installPlan ? installPlan.commands : [],
960
+ docsOpened: "not-needed",
961
+ guide
962
+ };
963
+ }
964
+
965
+ if (!guide.docsUrl) {
966
+ return {
967
+ status: "shown",
968
+ reason: "backend-missing",
969
+ installStatus,
970
+ installReason,
971
+ installer: installPlan ? installPlan.installer : "",
972
+ commands: installPlan ? installPlan.commands : [],
973
+ docsOpened: "unavailable",
974
+ guide
975
+ };
976
+ }
977
+
978
+ const opener = detectUrlOpener();
979
+ if (!opener) {
980
+ return {
981
+ status: "shown",
982
+ reason: "backend-missing",
983
+ installStatus,
984
+ installReason,
985
+ installer: installPlan ? installPlan.installer : "",
986
+ commands: installPlan ? installPlan.commands : [],
987
+ docsOpened: "unsupported",
988
+ guide
989
+ };
990
+ }
991
+
992
+ if (!options.interactive) {
993
+ return {
994
+ status: "shown",
995
+ reason: "backend-missing",
996
+ installStatus,
997
+ installReason,
998
+ installer: installPlan ? installPlan.installer : "",
999
+ commands: installPlan ? installPlan.commands : [],
1000
+ docsOpened: "skipped",
1001
+ guide
1002
+ };
1003
+ }
1004
+
1005
+ const rl = createPromptInterface();
1006
+ let shouldOpen = false;
1007
+ try {
1008
+ shouldOpen = await promptYesNo(rl, `Open ${guide.title} setup docs in your browser now`, false);
1009
+ } finally {
1010
+ rl.close();
1011
+ }
1012
+
1013
+ if (!shouldOpen) {
1014
+ return {
1015
+ status: "shown",
1016
+ reason: "backend-missing",
1017
+ installStatus,
1018
+ installReason,
1019
+ installer: installPlan ? installPlan.installer : "",
1020
+ commands: installPlan ? installPlan.commands : [],
1021
+ docsOpened: "skipped",
1022
+ guide
1023
+ };
1024
+ }
1025
+
1026
+ const status = runInteractiveCommand(opener, [guide.docsUrl]);
1027
+ return {
1028
+ status: "shown",
1029
+ reason: "backend-missing",
1030
+ installStatus,
1031
+ installReason,
1032
+ installer: installPlan ? installPlan.installer : "",
1033
+ commands: installPlan ? installPlan.commands : [],
1034
+ docsOpened: status === 0 ? "yes" : "failed",
1035
+ guide
1036
+ };
1037
+ }
1038
+
1039
+ function printPrereqSummary(prereq) {
1040
+ console.log("\nPrerequisite check");
1041
+ console.log(`- core tools: ${prereq.coreToolsOk ? "ok" : `missing ${prereq.missingRequired.join(", ")}`}`);
1042
+ console.log(`- worker backend (${prereq.workerCommand}): ${prereq.workerAvailable ? "found" : "missing on PATH"}`);
1043
+ console.log(`- GitHub auth: ${prereq.ghAuthOk ? "ok" : "not ready"}`);
1044
+ console.log(`- backend note: ${backendReadinessHint(prereq.workerCommand)}`);
1045
+ }
1046
+
1047
+ function profileExists(profileRegistryRoot, profileId) {
1048
+ return fs.existsSync(path.join(profileRegistryRoot, profileId, "control-plane.yaml"));
1049
+ }
1050
+
1051
+ function buildScopedContext(context, profileId) {
1052
+ return {
1053
+ ...context,
1054
+ env: {
1055
+ ...context.env,
1056
+ ACP_PROJECT_ID: profileId,
1057
+ AGENT_PROJECT_ID: profileId
1058
+ }
1059
+ };
1060
+ }
1061
+
1062
+ function renderSetupSummary(config) {
1063
+ console.log("\nSetup plan");
1064
+ console.log(`- profile id: ${config.profileId}`);
1065
+ console.log(`- repo slug: ${config.repoSlug}`);
1066
+ console.log(`- repo root: ${config.paths.repoRoot}`);
1067
+ console.log(`- agent root: ${config.paths.agentRoot}`);
1068
+ console.log(`- agent repo root: ${config.paths.agentRepoRoot}`);
1069
+ console.log(`- worktree root: ${config.paths.worktreeRoot}`);
1070
+ console.log(`- coding worker: ${config.codingWorker}`);
1071
+ }
1072
+
1073
+ function planStatusWithReason(status, reason = "") {
1074
+ return {
1075
+ status,
1076
+ reason
1077
+ };
1078
+ }
1079
+
1080
+ function buildSetupDryRunPlan(options, context, config) {
1081
+ const prereq = config.prereq;
1082
+ const dependencyPlan = buildDependencyInstallPlan(prereq.missingRequired);
1083
+ const workerInstallPlan = buildWorkerBackendInstallPlan(prereq.workerCommand);
1084
+ const workerGuide = backendSetupGuide(prereq.workerCommand);
1085
+
1086
+ let dependencyAction = planStatusWithReason("not-needed");
1087
+ if (!prereq.coreToolsOk) {
1088
+ if (options.installMissingDeps === false) {
1089
+ dependencyAction = planStatusWithReason("skipped", "disabled");
1090
+ } else if (!dependencyPlan) {
1091
+ dependencyAction = planStatusWithReason("unavailable", "no-supported-package-manager");
1092
+ } else if (options.installMissingDeps === true) {
1093
+ dependencyAction = planStatusWithReason("will-run");
1094
+ } else {
1095
+ dependencyAction = planStatusWithReason("would-prompt");
1096
+ }
1097
+ }
1098
+
1099
+ let workerAction = planStatusWithReason("not-needed");
1100
+ if (!prereq.workerAvailable) {
1101
+ if (options.installMissingBackend === false) {
1102
+ workerAction = planStatusWithReason("skipped", "disabled");
1103
+ } else if (!workerInstallPlan) {
1104
+ workerAction = planStatusWithReason("unavailable", "no-supported-installer");
1105
+ } else if (options.installMissingBackend === true) {
1106
+ workerAction = planStatusWithReason("will-run");
1107
+ } else {
1108
+ workerAction = planStatusWithReason("would-prompt");
1109
+ }
1110
+ }
1111
+
1112
+ let githubAuthAction = planStatusWithReason("not-needed");
1113
+ if (!prereq.ghAuthOk) {
1114
+ if (!commandExists("gh")) {
1115
+ githubAuthAction = planStatusWithReason("blocked", "gh-missing");
1116
+ } else if (options.ghAuthLogin === false) {
1117
+ githubAuthAction = planStatusWithReason("skipped", "disabled");
1118
+ } else if (options.ghAuthLogin === true) {
1119
+ githubAuthAction = planStatusWithReason("will-run");
1120
+ } else {
1121
+ githubAuthAction = planStatusWithReason("would-prompt");
1122
+ }
1123
+ }
1124
+
1125
+ let runtimeStartAction = planStatusWithReason("skipped", "not-requested");
1126
+ if (config.startRuntime) {
1127
+ if (!prereq.coreToolsOk) {
1128
+ runtimeStartAction = planStatusWithReason("blocked", `missing-tools:${prereq.missingRequired.join(",")}`);
1129
+ } else if (!prereq.workerAvailable) {
1130
+ runtimeStartAction = planStatusWithReason("blocked", `missing-worker:${prereq.workerCommand}`);
1131
+ } else if (!prereq.ghAuthOk) {
1132
+ runtimeStartAction = planStatusWithReason("blocked", "gh-auth-not-ready");
1133
+ } else {
1134
+ runtimeStartAction = planStatusWithReason("would-run");
1135
+ }
1136
+ }
1137
+
1138
+ let launchdAction = planStatusWithReason(process.platform === "darwin" ? "skipped" : "unsupported", process.platform === "darwin" ? "not-requested" : "non-macos");
1139
+ if (config.installLaunchd) {
1140
+ if (process.platform !== "darwin") {
1141
+ launchdAction = planStatusWithReason("unsupported", "non-macos");
1142
+ } else if (!config.startRuntime) {
1143
+ launchdAction = planStatusWithReason("blocked", "runtime-not-requested");
1144
+ } else if (runtimeStartAction.status !== "would-run") {
1145
+ launchdAction = planStatusWithReason("blocked", "runtime-not-ready");
1146
+ } else {
1147
+ launchdAction = planStatusWithReason("would-run");
1148
+ }
1149
+ }
1150
+
1151
+ return {
1152
+ profileExists: profileExists(context.profileRegistryRoot, config.profileId),
1153
+ prereq,
1154
+ dependencyPlan,
1155
+ dependencyAction,
1156
+ workerInstallPlan,
1157
+ workerAction,
1158
+ workerGuide,
1159
+ githubAuthAction,
1160
+ runtimeStartAction,
1161
+ launchdAction
1162
+ };
1163
+ }
1164
+
1165
+ function printSetupDryRunPlan(context, config, plan) {
1166
+ console.log("\nDry run plan");
1167
+ console.log(`- mode: dry-run`);
1168
+ console.log(`- profile exists already: ${plan.profileExists ? "yes" : "no"}`);
1169
+ console.log(`- runtime home: ${context.runtimeHome}`);
1170
+ console.log(`- profile registry root: ${context.profileRegistryRoot}`);
1171
+ console.log(`- sync packaged runtime: would-run`);
1172
+ console.log(`- scaffold/adopt profile: would-run`);
1173
+ console.log(`- doctor: would-run`);
1174
+ console.log(`- dependency install: ${plan.dependencyAction.status}${plan.dependencyAction.reason ? ` (${plan.dependencyAction.reason})` : ""}`);
1175
+ if (plan.dependencyAction.status !== "not-needed" && plan.dependencyPlan && plan.dependencyPlan.commands.length > 0) {
1176
+ console.log(` command preview: ${plan.dependencyPlan.commands.map(formatCommand).join(" && ")}`);
1177
+ }
1178
+ console.log(`- worker backend install: ${plan.workerAction.status}${plan.workerAction.reason ? ` (${plan.workerAction.reason})` : ""}`);
1179
+ if (plan.workerAction.status !== "not-needed" && plan.workerInstallPlan && plan.workerInstallPlan.commands.length > 0) {
1180
+ console.log(` command preview: ${plan.workerInstallPlan.commands.map(formatCommand).join(" && ")}`);
1181
+ }
1182
+ console.log(`- GitHub auth step: ${plan.githubAuthAction.status}${plan.githubAuthAction.reason ? ` (${plan.githubAuthAction.reason})` : ""}`);
1183
+ console.log(`- runtime start: ${plan.runtimeStartAction.status}${plan.runtimeStartAction.reason ? ` (${plan.runtimeStartAction.reason})` : ""}`);
1184
+ console.log(`- launchd install: ${plan.launchdAction.status}${plan.launchdAction.reason ? ` (${plan.launchdAction.reason})` : ""}`);
1185
+ }
1186
+
1187
+ function buildSetupResultPayload(params) {
1188
+ return {
1189
+ setupStatus: params.setupStatus,
1190
+ setupMode: params.setupMode,
1191
+ profileId: params.profileId,
1192
+ repoSlug: params.repoSlug,
1193
+ codingWorker: params.codingWorker,
1194
+ profileExists: params.profileExists,
1195
+ paths: {
1196
+ repoRoot: params.repoRoot,
1197
+ agentRoot: params.agentRoot,
1198
+ agentRepoRoot: params.agentRepoRoot,
1199
+ worktreeRoot: params.worktreeRoot
1200
+ },
1201
+ coreTools: {
1202
+ status: params.coreToolsStatus,
1203
+ missingRequiredTools: params.missingRequiredTools
1204
+ },
1205
+ workerBackend: {
1206
+ command: params.workerBackendCommand,
1207
+ status: params.workerBackendStatus,
1208
+ setupGuideStatus: params.workerSetupGuideStatus,
1209
+ setupGuideReason: params.workerSetupGuideReason,
1210
+ installStatus: params.workerBackendInstallStatus,
1211
+ installReason: params.workerBackendInstallReason,
1212
+ installer: params.workerBackendInstaller,
1213
+ installCommand: params.workerBackendInstallCommand,
1214
+ docsOpened: params.workerSetupDocsOpened,
1215
+ docsUrl: params.workerBackendDocsUrl,
1216
+ installExample: params.workerBackendInstallExample,
1217
+ authExample: params.workerBackendAuthExample,
1218
+ verifyExample: params.workerBackendVerifyExample
1219
+ },
1220
+ githubAuth: {
1221
+ status: params.githubAuthStatus,
1222
+ stepStatus: params.githubAuthStepStatus,
1223
+ stepReason: params.githubAuthStepReason
1224
+ },
1225
+ dependencyInstall: {
1226
+ status: params.dependencyInstallStatus,
1227
+ reason: params.dependencyInstallReason,
1228
+ installer: params.dependencyInstaller,
1229
+ command: params.dependencyInstallCommand
1230
+ },
1231
+ projectInitStatus: params.projectInitStatus,
1232
+ doctorStatus: params.doctorStatus,
1233
+ runtime: {
1234
+ startStatus: params.runtimeStartStatus,
1235
+ startReason: params.runtimeStartReason,
1236
+ status: params.runtimeStatus
1237
+ },
1238
+ launchd: {
1239
+ status: params.launchdInstallStatus,
1240
+ reason: params.launchdInstallReason
1241
+ },
1242
+ finalFixup: {
1243
+ status: params.finalFixupStatus,
1244
+ actions: params.finalFixupActions
1245
+ }
1246
+ };
1247
+ }
1248
+
1249
+ function emitSetupJsonPayload(payload) {
1250
+ process.stdout.write(`${JSON.stringify(payload, null, 2)}\n`);
1251
+ }
1252
+
1253
+ function collectFinalSetupIssues(config, prereq, doctorKv, runtimeStartStatus) {
1254
+ const issues = [];
1255
+ if (!prereq.coreToolsOk) {
1256
+ issues.push(`missing core tools: ${prereq.missingRequired.join(", ")}`);
1257
+ }
1258
+ if (!prereq.workerAvailable) {
1259
+ issues.push(`missing worker backend on PATH: ${prereq.workerCommand}`);
1260
+ }
1261
+ if (!prereq.ghAuthOk) {
1262
+ issues.push("GitHub CLI is not authenticated");
1263
+ }
1264
+ if ((doctorKv.DOCTOR_STATUS || "") !== "ok") {
1265
+ issues.push(`doctor status is ${doctorKv.DOCTOR_STATUS || "unknown"}`);
1266
+ }
1267
+ if (config.startRuntime && runtimeStartStatus !== "ok") {
1268
+ issues.push(`runtime start is ${runtimeStartStatus}`);
1269
+ }
1270
+ return issues;
1271
+ }
1272
+
1273
+ async function maybeRunFinalSetupFixups(options, scopedContext, config, currentState) {
1274
+ const issues = collectFinalSetupIssues(config, currentState.prereq, currentState.doctorKv, currentState.runtimeStartStatus);
1275
+ if (issues.length === 0) {
1276
+ return {
1277
+ status: "not-needed",
1278
+ actions: [],
1279
+ ...currentState
1280
+ };
1281
+ }
1282
+
1283
+ console.log("\nRemaining setup items");
1284
+ for (const issue of issues) {
1285
+ console.log(`- ${issue}`);
1286
+ }
1287
+
1288
+ if (!options.interactive) {
1289
+ return {
1290
+ status: "skipped",
1291
+ actions: [],
1292
+ ...currentState
1293
+ };
1294
+ }
1295
+
1296
+ const rl = createPromptInterface();
1297
+ let shouldFix = false;
1298
+ try {
1299
+ shouldFix = await promptYesNo(rl, "Run a final fix-up pass for the remaining items", true);
1300
+ } finally {
1301
+ rl.close();
1302
+ }
1303
+
1304
+ if (!shouldFix) {
1305
+ return {
1306
+ status: "skipped",
1307
+ actions: [],
1308
+ ...currentState
1309
+ };
1310
+ }
1311
+
1312
+ const actions = [];
1313
+ let prereq = currentState.prereq;
1314
+ let dependencyInstall = currentState.dependencyInstall;
1315
+ let githubAuthStep = currentState.githubAuthStep;
1316
+ let workerSetupStep = currentState.workerSetupStep;
1317
+ let doctorKv = currentState.doctorKv;
1318
+ let runtimeStartStatus = currentState.runtimeStartStatus;
1319
+ let runtimeStartReason = currentState.runtimeStartReason;
1320
+ let runtimeStatusKv = currentState.runtimeStatusKv;
1321
+ let launchdInstallStatus = currentState.launchdInstallStatus;
1322
+ let launchdInstallReason = currentState.launchdInstallReason;
1323
+
1324
+ if (!prereq.coreToolsOk) {
1325
+ actions.push("install-core-tools");
1326
+ dependencyInstall = await maybeInstallMissingDependencies({ ...options, installMissingDeps: true, interactive: false }, prereq);
1327
+ prereq = collectPrereqStatus(config.codingWorker);
1328
+ }
1329
+
1330
+ if (!prereq.workerAvailable) {
1331
+ actions.push("install-worker-backend");
1332
+ workerSetupStep = await maybeShowWorkerSetupGuide({ ...options, installMissingBackend: true, interactive: options.interactive }, prereq);
1333
+ prereq = collectPrereqStatus(config.codingWorker);
1334
+ }
1335
+
1336
+ if (!prereq.ghAuthOk) {
1337
+ actions.push("github-auth-login");
1338
+ githubAuthStep = await maybeRunGithubAuthLogin({ ...options, ghAuthLogin: true, interactive: false }, prereq);
1339
+ prereq = collectPrereqStatus(config.codingWorker);
1340
+ }
1341
+
1342
+ if ((doctorKv.DOCTOR_STATUS || "") !== "ok") {
1343
+ actions.push("doctor-resync");
1344
+ runSetupStep(scopedContext, "Re-sync packaged runtime into ~/.agent-runtime", "tools/bin/sync-shared-agent-home.sh", []);
1345
+ const doctorOutput = runSetupStep(scopedContext, "Re-check runtime and profile health", "tools/bin/flow-runtime-doctor.sh", []);
1346
+ doctorKv = parseKvOutput(doctorOutput);
1347
+ }
1348
+
1349
+ if (config.startRuntime && runtimeStartStatus !== "ok") {
1350
+ if (!prereq.coreToolsOk) {
1351
+ runtimeStartStatus = "skipped";
1352
+ runtimeStartReason = `missing-tools:${prereq.missingRequired.join(",")}`;
1353
+ } else if (!prereq.workerAvailable) {
1354
+ runtimeStartStatus = "skipped";
1355
+ runtimeStartReason = `missing-worker:${prereq.workerCommand}`;
1356
+ } else if (!prereq.ghAuthOk) {
1357
+ runtimeStartStatus = "skipped";
1358
+ runtimeStartReason = "gh-auth-not-ready";
1359
+ } else if ((doctorKv.DOCTOR_STATUS || "") !== "ok") {
1360
+ runtimeStartStatus = "skipped";
1361
+ runtimeStartReason = `doctor-${doctorKv.DOCTOR_STATUS || "not-ok"}`;
1362
+ } else {
1363
+ actions.push("runtime-start");
1364
+ runSetupStep(scopedContext, "Start the runtime", "tools/bin/project-runtimectl.sh", ["start", "--profile-id", config.profileId]);
1365
+ const runtimeStatusOutput = runSetupStep(scopedContext, "Read back runtime status", "tools/bin/project-runtimectl.sh", ["status", "--profile-id", config.profileId]);
1366
+ runtimeStatusKv = parseKvOutput(runtimeStatusOutput);
1367
+ runtimeStartStatus = "ok";
1368
+ runtimeStartReason = "";
1369
+ }
1370
+ }
1371
+
1372
+ if (config.installLaunchd && process.platform === "darwin" && launchdInstallStatus !== "ok" && runtimeStartStatus === "ok") {
1373
+ actions.push("launchd-install");
1374
+ runSetupStep(scopedContext, "Install macOS autostart", "tools/bin/install-project-launchd.sh", ["--profile-id", config.profileId]);
1375
+ launchdInstallStatus = "ok";
1376
+ launchdInstallReason = "";
1377
+ }
1378
+
1379
+ const finalIssues = collectFinalSetupIssues(config, prereq, doctorKv, runtimeStartStatus);
1380
+ let status = "ok";
1381
+ if (dependencyInstall.status === "failed" || githubAuthStep.status === "failed" || workerSetupStep.installStatus === "failed") {
1382
+ status = "failed";
1383
+ } else if (finalIssues.length > 0) {
1384
+ status = "remaining";
1385
+ }
1386
+
1387
+ return {
1388
+ status,
1389
+ actions,
1390
+ prereq,
1391
+ dependencyInstall,
1392
+ githubAuthStep,
1393
+ workerSetupStep,
1394
+ doctorKv,
1395
+ runtimeStartStatus,
1396
+ runtimeStartReason,
1397
+ runtimeStatusKv,
1398
+ launchdInstallStatus,
1399
+ launchdInstallReason
1400
+ };
1401
+ }
1402
+
1403
+ async function collectSetupConfig(options, context) {
1404
+ const detectedRepoRoot = path.resolve(options.repoRoot || detectRepoRoot(process.cwd()) || process.cwd());
1405
+ const detectedRepoSlug = options.repoSlug || detectRepoSlug(detectedRepoRoot);
1406
+ const suggestedProfileId = options.profileId || sanitizeProfileId((detectedRepoSlug.split("/").pop() || path.basename(detectedRepoRoot)));
1407
+ const suggestedWorker = options.codingWorker || detectPreferredWorker();
1408
+
1409
+ let repoRoot = detectedRepoRoot;
1410
+ let repoSlug = detectedRepoSlug;
1411
+ let profileId = suggestedProfileId;
1412
+ let codingWorker = suggestedWorker;
1413
+
1414
+ if (!fs.existsSync(detectedRepoRoot)) {
1415
+ throw new Error(`setup repo root does not exist: ${detectedRepoRoot}`);
1416
+ }
1417
+
1418
+ if (!options.interactive) {
1419
+ if (!repoSlug) {
1420
+ throw new Error("setup could not detect --repo-slug automatically; pass --repo-slug <owner/repo> or run interactively inside a git checkout with origin set");
1421
+ }
1422
+ } else {
1423
+ const rl = createPromptInterface();
1424
+ try {
1425
+ console.log("ACP setup will guide one repo profile from install to operator-ready defaults.");
1426
+ console.log("Press Enter to accept the suggested value shown in brackets.\n");
1427
+
1428
+ repoRoot = path.resolve(await promptText(rl, "Local repo root", detectedRepoRoot));
1429
+ repoSlug = await promptText(rl, "GitHub repo slug", repoSlug || "");
1430
+ profileId = sanitizeProfileId(await promptText(rl, "Profile id", profileId));
1431
+ codingWorker = await promptText(rl, "Coding worker (codex|claude|openclaw)", codingWorker);
1432
+
1433
+ if (!["codex", "claude", "openclaw"].includes(codingWorker)) {
1434
+ throw new Error(`unsupported coding worker: ${codingWorker}`);
1435
+ }
1436
+ } finally {
1437
+ rl.close();
1438
+ }
1439
+ }
1440
+
1441
+ if (!["codex", "claude", "openclaw"].includes(codingWorker)) {
1442
+ throw new Error(`unsupported coding worker: ${codingWorker}`);
1443
+ }
1444
+
1445
+ const paths = buildSetupPaths(context.platformHome, repoRoot, profileId, options);
1446
+ const prereq = collectPrereqStatus(codingWorker);
1447
+ const config = {
1448
+ profileId,
1449
+ repoSlug,
1450
+ repoRoot,
1451
+ codingWorker,
1452
+ paths,
1453
+ prereq
1454
+ };
1455
+
1456
+ renderSetupSummary(config);
1457
+
1458
+ if (options.interactive) {
1459
+ printPrereqSummary(prereq);
1460
+ const rl = createPromptInterface();
1461
+ try {
1462
+ if (!prereq.coreToolsOk || !prereq.workerAvailable || !prereq.ghAuthOk) {
1463
+ console.log("\nACP can still scaffold the profile now, but runtime start may be skipped until these checks are green.");
1464
+ }
1465
+ const shouldContinue = await promptYesNo(rl, "Continue with these values", true);
1466
+ if (!shouldContinue) {
1467
+ throw new Error("setup cancelled");
1468
+ }
1469
+ if (options.startRuntime === null) {
1470
+ options.startRuntime = await promptYesNo(rl, "Start the runtime after setup", true);
1471
+ }
1472
+ if (process.platform === "darwin" && options.installLaunchd === null && options.startRuntime) {
1473
+ options.installLaunchd = await promptYesNo(rl, "Install macOS autostart for this profile", false);
1474
+ }
1475
+ } finally {
1476
+ rl.close();
1477
+ }
1478
+ }
1479
+
1480
+ if (options.startRuntime === null) {
1481
+ options.startRuntime = false;
1482
+ }
1483
+ if (options.installLaunchd === null) {
1484
+ options.installLaunchd = false;
1485
+ }
1486
+
1487
+ return {
1488
+ ...config,
1489
+ startRuntime: Boolean(options.startRuntime),
1490
+ installLaunchd: Boolean(options.installLaunchd)
1491
+ };
1492
+ }
1493
+
1494
+ function runSetupStep(context, title, scriptRelativePath, args, options = {}) {
1495
+ console.log(`\n== ${title} ==`);
1496
+ const result = runScriptWithContext(context, scriptRelativePath, args, { stdio: "pipe", env: options.env, cwd: options.cwd });
1497
+ if (result.status !== 0) {
1498
+ printFailureDetails(result);
1499
+ throw new Error(`${title} failed`);
1500
+ }
1501
+ return result.stdout || "";
1502
+ }
1503
+
1504
+ async function runSetupFlow(forwardedArgs) {
1505
+ const jsonRequested = forwardedArgs.includes("--json");
1506
+ let options;
1507
+ try {
1508
+ options = parseSetupArgs(forwardedArgs);
1509
+ } catch (error) {
1510
+ if (jsonRequested) {
1511
+ emitSetupJsonPayload({
1512
+ setupStatus: "error",
1513
+ setupMode: "unknown",
1514
+ error: error.message
1515
+ });
1516
+ } else {
1517
+ console.error(error.message);
1518
+ printSetupHelp();
1519
+ }
1520
+ return 64;
1521
+ }
1522
+
1523
+ if (options.help) {
1524
+ printSetupHelp();
1525
+ return 0;
1526
+ }
1527
+
1528
+ const originalConsoleLog = console.log;
1529
+ const originalConsoleError = console.error;
1530
+ if (options.json) {
1531
+ setupJsonOutputEnabled = true;
1532
+ console.log = (...args) => {
1533
+ process.stderr.write(`${util.format(...args)}\n`);
1534
+ };
1535
+ console.error = (...args) => {
1536
+ process.stderr.write(`${util.format(...args)}\n`);
1537
+ };
1538
+ }
1539
+
1540
+ const stage = stageSharedHome();
1541
+ const context = createExecutionContext(stage);
1542
+
1543
+ try {
1544
+ const config = await collectSetupConfig(options, context);
1545
+ if (options.dryRun) {
1546
+ const plan = buildSetupDryRunPlan(options, context, config);
1547
+ printSetupDryRunPlan(context, config, plan);
1548
+ const dryRunWorkerBackendInstallCommand = plan.workerAction.status !== "not-needed" && plan.workerInstallPlan && plan.workerInstallPlan.commands.length > 0
1549
+ ? plan.workerInstallPlan.commands.map(formatCommand).join(" && ")
1550
+ : "";
1551
+ const dryRunDependencyInstallCommand = plan.dependencyAction.status !== "not-needed" && plan.dependencyPlan && plan.dependencyPlan.commands.length > 0
1552
+ ? plan.dependencyPlan.commands.map(formatCommand).join(" && ")
1553
+ : "";
1554
+ const dryRunPayload = buildSetupResultPayload({
1555
+ setupStatus: "dry-run",
1556
+ setupMode: "dry-run",
1557
+ profileId: config.profileId,
1558
+ repoSlug: config.repoSlug,
1559
+ codingWorker: config.codingWorker,
1560
+ profileExists: plan.profileExists,
1561
+ repoRoot: config.paths.repoRoot,
1562
+ agentRoot: config.paths.agentRoot,
1563
+ agentRepoRoot: config.paths.agentRepoRoot,
1564
+ worktreeRoot: config.paths.worktreeRoot,
1565
+ coreToolsStatus: plan.prereq.coreToolsOk ? "ok" : "missing",
1566
+ missingRequiredTools: plan.prereq.missingRequired,
1567
+ workerBackendCommand: plan.prereq.workerCommand,
1568
+ workerBackendStatus: plan.prereq.workerAvailable ? "ok" : "missing",
1569
+ workerSetupGuideStatus: "planned",
1570
+ workerSetupGuideReason: plan.workerAction.reason || "",
1571
+ workerBackendInstallStatus: plan.workerAction.status,
1572
+ workerBackendInstallReason: plan.workerAction.reason || "",
1573
+ workerBackendInstaller: plan.workerInstallPlan ? plan.workerInstallPlan.installer : "",
1574
+ workerBackendInstallCommand: dryRunWorkerBackendInstallCommand,
1575
+ workerSetupDocsOpened: "planned",
1576
+ workerBackendDocsUrl: plan.workerGuide.docsUrl || "",
1577
+ workerBackendInstallExample: plan.workerGuide.installExamples[0] || "",
1578
+ workerBackendAuthExample: plan.workerGuide.authExamples[0] || "",
1579
+ workerBackendVerifyExample: plan.workerGuide.verifyExamples[0] || "",
1580
+ githubAuthStatus: plan.prereq.ghAuthOk ? "ok" : "not-ready",
1581
+ githubAuthStepStatus: plan.githubAuthAction.status,
1582
+ githubAuthStepReason: plan.githubAuthAction.reason || "",
1583
+ dependencyInstallStatus: plan.dependencyAction.status,
1584
+ dependencyInstallReason: plan.dependencyAction.reason || "",
1585
+ dependencyInstaller: plan.dependencyPlan ? plan.dependencyPlan.manager : "",
1586
+ dependencyInstallCommand: dryRunDependencyInstallCommand,
1587
+ projectInitStatus: "would-run",
1588
+ doctorStatus: "would-run",
1589
+ runtimeStartStatus: plan.runtimeStartAction.status,
1590
+ runtimeStartReason: plan.runtimeStartAction.reason || "",
1591
+ runtimeStatus: "",
1592
+ launchdInstallStatus: plan.launchdAction.status,
1593
+ launchdInstallReason: plan.launchdAction.reason || "",
1594
+ finalFixupStatus: "planned",
1595
+ finalFixupActions: ["review-plan"]
1596
+ });
1597
+
1598
+ if (options.json) {
1599
+ emitSetupJsonPayload(dryRunPayload);
1600
+ } else {
1601
+ console.log(`SETUP_STATUS=dry-run`);
1602
+ console.log(`SETUP_MODE=dry-run`);
1603
+ console.log(`PROFILE_ID=${config.profileId}`);
1604
+ console.log(`REPO_SLUG=${config.repoSlug}`);
1605
+ console.log(`REPO_ROOT=${config.paths.repoRoot}`);
1606
+ console.log(`AGENT_ROOT=${config.paths.agentRoot}`);
1607
+ console.log(`AGENT_REPO_ROOT=${config.paths.agentRepoRoot}`);
1608
+ console.log(`WORKTREE_ROOT=${config.paths.worktreeRoot}`);
1609
+ console.log(`CODING_WORKER=${config.codingWorker}`);
1610
+ console.log(`PROFILE_EXISTS=${plan.profileExists ? "yes" : "no"}`);
1611
+ console.log(`CORE_TOOLS_STATUS=${plan.prereq.coreToolsOk ? "ok" : "missing"}`);
1612
+ console.log(`MISSING_REQUIRED_TOOLS=${plan.prereq.missingRequired.join(",")}`);
1613
+ console.log(`WORKER_BACKEND_COMMAND=${plan.prereq.workerCommand}`);
1614
+ console.log(`WORKER_BACKEND_STATUS=${plan.prereq.workerAvailable ? "ok" : "missing"}`);
1615
+ console.log(`GITHUB_AUTH_STATUS=${plan.prereq.ghAuthOk ? "ok" : "not-ready"}`);
1616
+ console.log(`DEPENDENCY_INSTALL_STATUS=${plan.dependencyAction.status}`);
1617
+ if (plan.dependencyAction.reason) {
1618
+ console.log(`DEPENDENCY_INSTALL_REASON=${plan.dependencyAction.reason}`);
1619
+ }
1620
+ if (plan.dependencyPlan && plan.dependencyPlan.commands.length > 0) {
1621
+ console.log(`DEPENDENCY_INSTALL_COMMAND=${plan.dependencyPlan.commands.map(formatCommand).join(" && ")}`);
1622
+ }
1623
+ console.log(`WORKER_BACKEND_INSTALL_STATUS=${plan.workerAction.status}`);
1624
+ if (plan.workerAction.reason) {
1625
+ console.log(`WORKER_BACKEND_INSTALL_REASON=${plan.workerAction.reason}`);
1626
+ }
1627
+ if (plan.workerInstallPlan && plan.workerInstallPlan.installer) {
1628
+ console.log(`WORKER_BACKEND_INSTALLER=${plan.workerInstallPlan.installer}`);
1629
+ }
1630
+ if (plan.workerInstallPlan && plan.workerInstallPlan.commands.length > 0) {
1631
+ console.log(`WORKER_BACKEND_INSTALL_COMMAND=${plan.workerInstallPlan.commands.map(formatCommand).join(" && ")}`);
1632
+ }
1633
+ if (plan.workerGuide.docsUrl) {
1634
+ console.log(`WORKER_BACKEND_DOCS_URL=${plan.workerGuide.docsUrl}`);
1635
+ }
1636
+ if (plan.workerGuide.installExamples[0]) {
1637
+ console.log(`WORKER_BACKEND_INSTALL_EXAMPLE=${plan.workerGuide.installExamples[0]}`);
1638
+ }
1639
+ if (plan.workerGuide.authExamples[0]) {
1640
+ console.log(`WORKER_BACKEND_AUTH_EXAMPLE=${plan.workerGuide.authExamples[0]}`);
1641
+ }
1642
+ if (plan.workerGuide.verifyExamples[0]) {
1643
+ console.log(`WORKER_BACKEND_VERIFY_EXAMPLE=${plan.workerGuide.verifyExamples[0]}`);
1644
+ }
1645
+ console.log(`GITHUB_AUTH_STEP_STATUS=${plan.githubAuthAction.status}`);
1646
+ if (plan.githubAuthAction.reason) {
1647
+ console.log(`GITHUB_AUTH_STEP_REASON=${plan.githubAuthAction.reason}`);
1648
+ }
1649
+ console.log(`PROJECT_INIT_STATUS=would-run`);
1650
+ console.log(`DOCTOR_STATUS=would-run`);
1651
+ console.log(`RUNTIME_START_STATUS=${plan.runtimeStartAction.status}`);
1652
+ if (plan.runtimeStartAction.reason) {
1653
+ console.log(`RUNTIME_START_REASON=${plan.runtimeStartAction.reason}`);
1654
+ }
1655
+ console.log(`LAUNCHD_INSTALL_STATUS=${plan.launchdAction.status}`);
1656
+ if (plan.launchdAction.reason) {
1657
+ console.log(`LAUNCHD_INSTALL_REASON=${plan.launchdAction.reason}`);
1658
+ }
1659
+ console.log(`FINAL_FIXUP_STATUS=planned`);
1660
+ console.log(`FINAL_FIXUP_ACTIONS=review-plan`);
1661
+ }
1662
+ return 0;
1663
+ }
1664
+
1665
+ if (profileExists(context.profileRegistryRoot, config.profileId) && !options.force) {
1666
+ console.error(`setup found an existing profile at ${path.join(context.profileRegistryRoot, config.profileId)}.`);
1667
+ console.error("Re-run with --force if you want to overwrite it.");
1668
+ return 1;
1669
+ }
1670
+
1671
+ let prereq = config.prereq;
1672
+ let dependencyInstall = await maybeInstallMissingDependencies(options, prereq);
1673
+ if (dependencyInstall.status === "failed") {
1674
+ console.error("dependency installation failed");
1675
+ return 1;
1676
+ }
1677
+ prereq = collectPrereqStatus(config.codingWorker);
1678
+
1679
+ let githubAuthStep = await maybeRunGithubAuthLogin(options, prereq);
1680
+ if (githubAuthStep.status === "failed") {
1681
+ console.error("GitHub authentication failed");
1682
+ return 1;
1683
+ }
1684
+ prereq = collectPrereqStatus(config.codingWorker);
1685
+ let workerSetupStep = await maybeShowWorkerSetupGuide(options, prereq);
1686
+ prereq = collectPrereqStatus(config.codingWorker);
1687
+
1688
+ const scopedContext = buildScopedContext(context, config.profileId);
1689
+
1690
+ runSetupStep(scopedContext, "Sync packaged runtime into ~/.agent-runtime", "tools/bin/sync-shared-agent-home.sh", []);
1691
+
1692
+ const initArgs = [
1693
+ "--profile-id", config.profileId,
1694
+ "--repo-slug", config.repoSlug,
1695
+ "--repo-root", config.paths.repoRoot,
1696
+ "--agent-root", config.paths.agentRoot,
1697
+ "--agent-repo-root", config.paths.agentRepoRoot,
1698
+ "--worktree-root", config.paths.worktreeRoot,
1699
+ "--retained-repo-root", config.paths.retainedRepoRoot,
1700
+ "--vscode-workspace-file", config.paths.vscodeWorkspaceFile,
1701
+ "--source-repo-root", config.paths.sourceRepoRoot,
1702
+ "--coding-worker", config.codingWorker
1703
+ ];
1704
+ if (options.force) {
1705
+ initArgs.push("--force");
1706
+ }
1707
+ if (options.skipAnchorSync) {
1708
+ initArgs.push("--skip-anchor-sync");
1709
+ }
1710
+ if (options.skipWorkspaceSync) {
1711
+ initArgs.push("--skip-workspace-sync");
1712
+ }
1713
+ if (options.allowMissingRepo) {
1714
+ initArgs.push("--allow-missing-repo");
1715
+ }
1716
+
1717
+ const initOutput = runSetupStep(scopedContext, "Scaffold and adopt the project profile", "tools/bin/project-init.sh", initArgs);
1718
+ const initKv = parseKvOutput(initOutput);
1719
+
1720
+ const doctorOutput = runSetupStep(scopedContext, "Check runtime and profile health", "tools/bin/flow-runtime-doctor.sh", []);
1721
+ let doctorKv = parseKvOutput(doctorOutput);
1722
+
1723
+ let runtimeStartStatus = "skipped";
1724
+ let runtimeStartReason = "not-requested";
1725
+ let runtimeStatusKv = {};
1726
+
1727
+ if (config.startRuntime) {
1728
+ if (prereq.missingRequired.length > 0) {
1729
+ runtimeStartReason = `missing-tools:${prereq.missingRequired.join(",")}`;
1730
+ console.log(`runtime start skipped: missing required tools (${prereq.missingRequired.join(", ")})`);
1731
+ } else if (!prereq.workerAvailable) {
1732
+ runtimeStartReason = `missing-worker:${prereq.workerCommand}`;
1733
+ console.log(`runtime start skipped: ${prereq.workerCommand} is not available on PATH`);
1734
+ } else if (!prereq.ghAuthOk) {
1735
+ runtimeStartReason = "gh-auth-not-ready";
1736
+ console.log("runtime start skipped: GitHub CLI is not authenticated yet. Run `gh auth login` and start the runtime afterwards.");
1737
+ } else {
1738
+ runSetupStep(scopedContext, "Start the runtime", "tools/bin/project-runtimectl.sh", ["start", "--profile-id", config.profileId]);
1739
+ const runtimeStatusOutput = runSetupStep(scopedContext, "Read back runtime status", "tools/bin/project-runtimectl.sh", ["status", "--profile-id", config.profileId]);
1740
+ runtimeStatusKv = parseKvOutput(runtimeStatusOutput);
1741
+ runtimeStartStatus = "ok";
1742
+ runtimeStartReason = "";
1743
+ }
1744
+ }
1745
+
1746
+ let launchdInstallStatus = "skipped";
1747
+ let launchdInstallReason = process.platform === "darwin" ? "not-requested" : "non-macos";
1748
+
1749
+ if (config.installLaunchd) {
1750
+ if (process.platform !== "darwin") {
1751
+ console.log("launchd install skipped: this command is only relevant on macOS.");
1752
+ } else if (runtimeStartStatus !== "ok") {
1753
+ launchdInstallReason = "runtime-not-started";
1754
+ console.log("launchd install skipped: runtime was not started successfully in this setup run.");
1755
+ } else {
1756
+ runSetupStep(scopedContext, "Install macOS autostart", "tools/bin/install-project-launchd.sh", ["--profile-id", config.profileId]);
1757
+ launchdInstallStatus = "ok";
1758
+ launchdInstallReason = "";
1759
+ }
1760
+ }
1761
+
1762
+ const finalFixup = await maybeRunFinalSetupFixups(options, scopedContext, config, {
1763
+ prereq,
1764
+ dependencyInstall,
1765
+ githubAuthStep,
1766
+ workerSetupStep,
1767
+ doctorKv,
1768
+ runtimeStartStatus,
1769
+ runtimeStartReason,
1770
+ runtimeStatusKv,
1771
+ launchdInstallStatus,
1772
+ launchdInstallReason
1773
+ });
1774
+
1775
+ prereq = finalFixup.prereq;
1776
+ dependencyInstall = finalFixup.dependencyInstall;
1777
+ githubAuthStep = finalFixup.githubAuthStep;
1778
+ workerSetupStep = finalFixup.workerSetupStep;
1779
+ doctorKv = finalFixup.doctorKv;
1780
+ runtimeStartStatus = finalFixup.runtimeStartStatus;
1781
+ runtimeStartReason = finalFixup.runtimeStartReason;
1782
+ runtimeStatusKv = finalFixup.runtimeStatusKv;
1783
+ launchdInstallStatus = finalFixup.launchdInstallStatus;
1784
+ launchdInstallReason = finalFixup.launchdInstallReason;
1785
+
1786
+ const runPayload = buildSetupResultPayload({
1787
+ setupStatus: "ok",
1788
+ setupMode: "run",
1789
+ profileId: config.profileId,
1790
+ repoSlug: config.repoSlug,
1791
+ codingWorker: config.codingWorker,
1792
+ profileExists: true,
1793
+ repoRoot: config.paths.repoRoot,
1794
+ agentRoot: config.paths.agentRoot,
1795
+ agentRepoRoot: config.paths.agentRepoRoot,
1796
+ worktreeRoot: config.paths.worktreeRoot,
1797
+ coreToolsStatus: prereq.coreToolsOk ? "ok" : "missing",
1798
+ missingRequiredTools: prereq.missingRequired,
1799
+ workerBackendCommand: prereq.workerCommand,
1800
+ workerBackendStatus: prereq.workerAvailable ? "ok" : "missing",
1801
+ workerSetupGuideStatus: workerSetupStep.status,
1802
+ workerSetupGuideReason: workerSetupStep.reason || "",
1803
+ workerBackendInstallStatus: workerSetupStep.installStatus,
1804
+ workerBackendInstallReason: workerSetupStep.installReason || "",
1805
+ workerBackendInstaller: workerSetupStep.installer || "",
1806
+ workerBackendInstallCommand: workerSetupStep.commands.length > 0 ? workerSetupStep.commands.map(formatCommand).join(" && ") : "",
1807
+ workerSetupDocsOpened: workerSetupStep.docsOpened,
1808
+ workerBackendDocsUrl: workerSetupStep.guide.docsUrl || "",
1809
+ workerBackendInstallExample: workerSetupStep.guide.installExamples[0] || "",
1810
+ workerBackendAuthExample: workerSetupStep.guide.authExamples[0] || "",
1811
+ workerBackendVerifyExample: workerSetupStep.guide.verifyExamples[0] || "",
1812
+ githubAuthStatus: prereq.ghAuthOk ? "ok" : "not-ready",
1813
+ githubAuthStepStatus: githubAuthStep.status,
1814
+ githubAuthStepReason: githubAuthStep.reason || "",
1815
+ dependencyInstallStatus: dependencyInstall.status,
1816
+ dependencyInstallReason: dependencyInstall.reason || "",
1817
+ dependencyInstaller: dependencyInstall.installer || "",
1818
+ dependencyInstallCommand: dependencyInstall.commands.length > 0 ? dependencyInstall.commands.map(formatCommand).join(" && ") : "",
1819
+ projectInitStatus: initKv.PROJECT_INIT_STATUS || "ok",
1820
+ doctorStatus: doctorKv.DOCTOR_STATUS || "",
1821
+ runtimeStartStatus,
1822
+ runtimeStartReason: runtimeStartReason || "",
1823
+ runtimeStatus: runtimeStatusKv.RUNTIME_STATUS || "",
1824
+ launchdInstallStatus,
1825
+ launchdInstallReason: launchdInstallReason || "",
1826
+ finalFixupStatus: finalFixup.status,
1827
+ finalFixupActions: finalFixup.actions
1828
+ });
1829
+
1830
+ if (options.json) {
1831
+ emitSetupJsonPayload(runPayload);
1832
+ } else {
1833
+ console.log("\nSetup complete.");
1834
+ console.log(`- profile: ${config.profileId}`);
1835
+ console.log(`- repo: ${config.repoSlug}`);
1836
+ console.log(`- runtime home: ${context.runtimeHome}`);
1837
+ console.log(`- next status command: npx agent-control-plane@latest runtime status --profile-id ${config.profileId}`);
1838
+
1839
+ console.log(`SETUP_STATUS=ok`);
1840
+ console.log(`PROFILE_ID=${config.profileId}`);
1841
+ console.log(`REPO_SLUG=${config.repoSlug}`);
1842
+ console.log(`REPO_ROOT=${config.paths.repoRoot}`);
1843
+ console.log(`AGENT_ROOT=${config.paths.agentRoot}`);
1844
+ console.log(`AGENT_REPO_ROOT=${config.paths.agentRepoRoot}`);
1845
+ console.log(`WORKTREE_ROOT=${config.paths.worktreeRoot}`);
1846
+ console.log(`CODING_WORKER=${config.codingWorker}`);
1847
+ console.log(`CORE_TOOLS_STATUS=${prereq.coreToolsOk ? "ok" : "missing"}`);
1848
+ console.log(`MISSING_REQUIRED_TOOLS=${prereq.missingRequired.join(",")}`);
1849
+ console.log(`WORKER_BACKEND_COMMAND=${prereq.workerCommand}`);
1850
+ console.log(`WORKER_BACKEND_STATUS=${prereq.workerAvailable ? "ok" : "missing"}`);
1851
+ console.log(`WORKER_SETUP_GUIDE_STATUS=${workerSetupStep.status}`);
1852
+ if (workerSetupStep.reason) {
1853
+ console.log(`WORKER_SETUP_GUIDE_REASON=${workerSetupStep.reason}`);
1854
+ }
1855
+ console.log(`WORKER_BACKEND_INSTALL_STATUS=${workerSetupStep.installStatus}`);
1856
+ if (workerSetupStep.installReason) {
1857
+ console.log(`WORKER_BACKEND_INSTALL_REASON=${workerSetupStep.installReason}`);
1858
+ }
1859
+ if (workerSetupStep.installer) {
1860
+ console.log(`WORKER_BACKEND_INSTALLER=${workerSetupStep.installer}`);
1861
+ }
1862
+ if (workerSetupStep.commands.length > 0) {
1863
+ console.log(`WORKER_BACKEND_INSTALL_COMMAND=${workerSetupStep.commands.map(formatCommand).join(" && ")}`);
1864
+ }
1865
+ console.log(`WORKER_SETUP_DOCS_OPENED=${workerSetupStep.docsOpened}`);
1866
+ if (workerSetupStep.guide.docsUrl) {
1867
+ console.log(`WORKER_BACKEND_DOCS_URL=${workerSetupStep.guide.docsUrl}`);
1868
+ }
1869
+ if (workerSetupStep.guide.installExamples[0]) {
1870
+ console.log(`WORKER_BACKEND_INSTALL_EXAMPLE=${workerSetupStep.guide.installExamples[0]}`);
1871
+ }
1872
+ if (workerSetupStep.guide.authExamples[0]) {
1873
+ console.log(`WORKER_BACKEND_AUTH_EXAMPLE=${workerSetupStep.guide.authExamples[0]}`);
1874
+ }
1875
+ if (workerSetupStep.guide.verifyExamples[0]) {
1876
+ console.log(`WORKER_BACKEND_VERIFY_EXAMPLE=${workerSetupStep.guide.verifyExamples[0]}`);
1877
+ }
1878
+ console.log(`GITHUB_AUTH_STATUS=${prereq.ghAuthOk ? "ok" : "not-ready"}`);
1879
+ console.log(`FINAL_FIXUP_STATUS=${finalFixup.status}`);
1880
+ console.log(`FINAL_FIXUP_ACTIONS=${finalFixup.actions.join(",")}`);
1881
+ console.log(`DEPENDENCY_INSTALL_STATUS=${dependencyInstall.status}`);
1882
+ if (dependencyInstall.reason) {
1883
+ console.log(`DEPENDENCY_INSTALL_REASON=${dependencyInstall.reason}`);
1884
+ }
1885
+ if (dependencyInstall.installer) {
1886
+ console.log(`DEPENDENCY_INSTALLER=${dependencyInstall.installer}`);
1887
+ }
1888
+ if (dependencyInstall.commands.length > 0) {
1889
+ console.log(`DEPENDENCY_INSTALL_COMMAND=${dependencyInstall.commands.map(formatCommand).join(" && ")}`);
1890
+ }
1891
+ console.log(`GITHUB_AUTH_STEP_STATUS=${githubAuthStep.status}`);
1892
+ if (githubAuthStep.reason) {
1893
+ console.log(`GITHUB_AUTH_STEP_REASON=${githubAuthStep.reason}`);
1894
+ }
1895
+ console.log(`PROJECT_INIT_STATUS=${initKv.PROJECT_INIT_STATUS || "ok"}`);
1896
+ console.log(`DOCTOR_STATUS=${doctorKv.DOCTOR_STATUS || ""}`);
1897
+ console.log(`RUNTIME_START_STATUS=${runtimeStartStatus}`);
1898
+ if (runtimeStartReason) {
1899
+ console.log(`RUNTIME_START_REASON=${runtimeStartReason}`);
1900
+ }
1901
+ console.log(`LAUNCHD_INSTALL_STATUS=${launchdInstallStatus}`);
1902
+ if (launchdInstallReason) {
1903
+ console.log(`LAUNCHD_INSTALL_REASON=${launchdInstallReason}`);
1904
+ }
1905
+ if (runtimeStatusKv.RUNTIME_STATUS) {
1906
+ console.log(`RUNTIME_STATUS=${runtimeStatusKv.RUNTIME_STATUS}`);
1907
+ }
1908
+ }
1909
+
1910
+ return 0;
1911
+ } catch (error) {
1912
+ if (options.json) {
1913
+ emitSetupJsonPayload({
1914
+ setupStatus: "error",
1915
+ setupMode: options.dryRun ? "dry-run" : "run",
1916
+ error: error && error.message ? error.message : String(error)
1917
+ });
1918
+ } else if (error && error.message) {
1919
+ console.error(error.message);
1920
+ }
1921
+ return 1;
1922
+ } finally {
1923
+ if (options.json) {
1924
+ console.log = originalConsoleLog;
1925
+ console.error = originalConsoleError;
1926
+ setupJsonOutputEnabled = false;
1927
+ }
1928
+ fs.rmSync(stage.stageRoot, { recursive: true, force: true });
1929
+ }
1930
+ }
1931
+
1932
+ async function main() {
1933
+ const command = process.argv[2] || "help";
1934
+ const forwardedArgs = process.argv.slice(3);
1935
+
1936
+ switch (command) {
1937
+ case "help":
1938
+ case "--help":
1939
+ case "-h":
1940
+ printHelp();
1941
+ return 0;
1942
+ case "version":
1943
+ case "--version":
1944
+ case "-v":
1945
+ console.log(packageJson.version);
1946
+ return 0;
1947
+ case "setup":
1948
+ return runSetupFlow(forwardedArgs);
1949
+ case "sync":
1950
+ case "install":
1951
+ return runCommand("tools/bin/sync-shared-agent-home.sh", forwardedArgs);
1952
+ case "init":
1953
+ return runCommand("tools/bin/project-init.sh", forwardedArgs);
1954
+ case "doctor":
1955
+ return runCommand("tools/bin/flow-runtime-doctor.sh", forwardedArgs);
1956
+ case "profile-smoke":
1957
+ return runCommand("tools/bin/profile-smoke.sh", forwardedArgs);
1958
+ case "dashboard":
1959
+ return runCommand("tools/bin/serve-dashboard.sh", forwardedArgs);
1960
+ case "launchd-install":
1961
+ return runCommand("tools/bin/install-project-launchd.sh", forwardedArgs);
1962
+ case "launchd-uninstall":
1963
+ return runCommand("tools/bin/uninstall-project-launchd.sh", forwardedArgs);
1964
+ case "runtime":
1965
+ return runCommand("tools/bin/project-runtimectl.sh", forwardedArgs);
1966
+ case "remove":
1967
+ return runCommand("tools/bin/project-remove.sh", forwardedArgs);
1968
+ case "smoke":
1969
+ return runCommand("tools/bin/test-smoke.sh", forwardedArgs);
1970
+ default:
1971
+ console.error(`unknown command: ${command}`);
1972
+ console.error("run `agent-control-plane help` for usage");
1973
+ return 64;
1974
+ }
1975
+ }
1976
+
1977
+ main()
1978
+ .then((status) => {
1979
+ process.exit(status);
1980
+ })
1981
+ .catch((error) => {
1982
+ console.error(error && error.message ? error.message : String(error));
1983
+ process.exit(1);
1984
+ });