@moretea-labs/forge 1.5.0-rc.1 → 1.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (264) hide show
  1. package/AGENTS.md +9 -11
  2. package/CHANGELOG.md +27 -1
  3. package/CLAUDE.md +8 -10
  4. package/DESIGN.md +278 -0
  5. package/README.md +70 -90
  6. package/README.zh-CN.md +52 -55
  7. package/SKILL.md +8 -11
  8. package/THIRD_PARTY_NOTICES.md +9 -3
  9. package/assets/branding/README.md +4 -2
  10. package/assets/hooks/lib/workflow-state.sh +19 -18
  11. package/assets/hooks/post-edit-guard.sh +6 -75
  12. package/assets/hooks/prompt-guard.sh +24 -12
  13. package/assets/hooks/session-start-context.sh +17 -17
  14. package/assets/hooks/stop-orchestrator.sh +2 -2
  15. package/assets/partials/04-project-structure.partial.md +6 -6
  16. package/assets/partials/05-workflow.partial.md +6 -6
  17. package/assets/partials/07-footer.partial.md +1 -1
  18. package/assets/partials/08-orchestration.partial.md +2 -2
  19. package/assets/partials-agents/02-operating-mode.partial.md +5 -5
  20. package/assets/partials-agents/03-orchestration.partial.md +3 -3
  21. package/assets/partials-agents/04-task-protocol.partial.md +3 -3
  22. package/assets/partials-agents/08-deep-docs.partial.md +1 -1
  23. package/assets/reference-configs/agentic-development-flow.md +49 -86
  24. package/assets/reference-configs/external-tooling.md +21 -61
  25. package/assets/reference-configs/handoff-protocol.md +25 -20
  26. package/assets/reference-configs/harness-overview.md +8 -8
  27. package/assets/skill-version.json +2 -2
  28. package/assets/templates/helpers/archive-workflow.sh +0 -1
  29. package/assets/templates/helpers/capture-plan.sh +3 -3
  30. package/assets/templates/helpers/check-task-workflow.sh +2 -2
  31. package/assets/templates/helpers/codex-handoff-resume.sh +5 -5
  32. package/assets/templates/helpers/ensure-task-workflow.sh +37 -35
  33. package/assets/templates/helpers/new-plan.sh +1 -1
  34. package/assets/templates/helpers/plan-to-todo.sh +2 -2
  35. package/assets/templates/helpers/prepare-codex-handoff.sh +2 -2
  36. package/assets/templates/helpers/prepare-handoff.sh +9 -9
  37. package/assets/templates/helpers/refresh-current-status.sh +2 -2
  38. package/assets/templates/helpers/ship-worktrees.sh +1 -1
  39. package/assets/templates/helpers/sprint-backlog.sh +1 -1
  40. package/assets/templates/helpers/verify-sprint.sh +7 -7
  41. package/assets/templates/helpers/workstream-sync.sh +1 -1
  42. package/assets/templates/plan.template.md +1 -1
  43. package/assets/workflow-contract.v1.json +6 -3
  44. package/docs/architecture/current/README.md +10 -9
  45. package/docs/architecture/current/approved-target-architecture.zh-CN.md +2 -2
  46. package/docs/architecture/current/architecture-invariants.md +2 -2
  47. package/docs/architecture/current/automation-and-schedule-engine.md +15 -15
  48. package/docs/architecture/current/codegraph-context-provider.md +2 -2
  49. package/docs/architecture/current/control-plane-authority-inventory.md +12 -0
  50. package/docs/architecture/current/dispatch-and-agent-strategy.md +11 -11
  51. package/docs/architecture/current/entity-model.md +0 -27
  52. package/docs/architecture/current/failure-recovery.md +0 -24
  53. package/docs/architecture/current/governance.md +1 -1
  54. package/docs/architecture/current/implementation-status.md +1 -1
  55. package/docs/architecture/current/legacy-governance.md +58 -0
  56. package/docs/architecture/current/multi-repository-execution.md +1 -1
  57. package/docs/architecture/current/peer-supercontroller-runtime.md +1 -1
  58. package/docs/architecture/current/personal-assistant-plugin-baseline.md +4 -5
  59. package/docs/architecture/current/scheduler-and-resource-claims.md +0 -10
  60. package/docs/architecture/current/target-requirements-traceability.md +2 -2
  61. package/docs/architecture/current/thin-harness-governance-policy.md +1 -1
  62. package/docs/architecture/current/thin-harness-v1.md +9 -11
  63. package/docs/architecture/domains/runtime-harness.md +1 -1
  64. package/docs/forge-autonomous-goal-loop.md +3 -1
  65. package/docs/forge-chatgpt-mcp-setup.md +67 -176
  66. package/docs/images/forge-banner-cn.svg +26 -1
  67. package/docs/images/forge-banner.svg +26 -1
  68. package/docs/operations/features.md +1 -1
  69. package/docs/operations/homebrew.md +3 -3
  70. package/docs/operations/mcp-tool-exposure.md +5 -5
  71. package/docs/operations/platform-support.md +45 -37
  72. package/docs/operations/platform-support.zh-CN.md +40 -38
  73. package/docs/operations/releasing.md +3 -3
  74. package/docs/operations/releasing.zh-CN.md +3 -3
  75. package/docs/operations/troubleshooting.md +11 -6
  76. package/docs/operations/troubleshooting.zh-CN.md +11 -6
  77. package/docs/public-usage-guide.md +28 -21
  78. package/docs/public-usage-guide.zh-CN.md +28 -21
  79. package/docs/tutorials/01-install-and-start.md +58 -35
  80. package/docs/tutorials/01-install-and-start.zh-CN.md +58 -35
  81. package/docs/tutorials/02-connect-chatgpt.md +118 -46
  82. package/docs/tutorials/02-connect-chatgpt.zh-CN.md +119 -45
  83. package/docs/wiki/Architecture.md +1 -1
  84. package/docs/wiki/Installation.md +2 -2
  85. package/docs/wiki/Quick-Start.md +1 -1
  86. package/docs/wiki/Work-Lifecycle.md +1 -1
  87. package/install.ps1 +4 -5
  88. package/install.sh +4 -6
  89. package/package.json +15 -5
  90. package/scripts/archive-workflow.sh +0 -1
  91. package/scripts/benchmark-thin-harness-mcp-e2e.ts +27 -14
  92. package/scripts/build-controller-ui.ts +28 -0
  93. package/scripts/capture-plan.sh +4 -4
  94. package/scripts/check-agent-tooling.sh +12 -1
  95. package/scripts/check-platform-support.mjs +2 -2
  96. package/scripts/check-public-docs.sh +16 -5
  97. package/scripts/check-release-readiness.sh +5 -11
  98. package/scripts/check-runtime-architecture.mjs +14 -22
  99. package/scripts/check-tarball-install-smoke.sh +3 -2
  100. package/scripts/check-task-workflow.sh +2 -2
  101. package/scripts/codex-handoff-resume.sh +5 -5
  102. package/scripts/create-project-dirs.sh +3 -3
  103. package/scripts/ensure-task-workflow.sh +45 -35
  104. package/scripts/init-project.sh +4 -4
  105. package/scripts/install-desktop-operator-registration.ts +2 -2
  106. package/scripts/lib/project-init-lib.sh +46 -44
  107. package/scripts/migrate-project-template.sh +7 -4
  108. package/scripts/new-plan.sh +1 -1
  109. package/scripts/plan-to-todo.sh +2 -2
  110. package/scripts/prepare-codex-handoff.sh +2 -2
  111. package/scripts/prepare-handoff.sh +9 -9
  112. package/scripts/refresh-current-status.sh +2 -2
  113. package/scripts/setup-plugins.sh +3 -3
  114. package/scripts/ship-worktrees.sh +1 -1
  115. package/scripts/sprint-backlog.sh +5 -5
  116. package/scripts/sync-codex-installed-copies.sh +2 -3
  117. package/scripts/verify-sprint.sh +7 -7
  118. package/scripts/workstream-sync.sh +1 -1
  119. package/src/cli/chatgpt-browser/bridge-extension.ts +1 -1
  120. package/src/cli/chatgpt-browser/bridge-provider.ts +29 -0
  121. package/src/cli/chatgpt-browser/file-policy.ts +3 -1
  122. package/src/cli/commands/chatgpt.ts +51 -0
  123. package/src/cli/commands/doctor.ts +1 -1
  124. package/src/cli/commands/init-hook.ts +213 -70
  125. package/src/cli/commands/init.ts +5 -6
  126. package/src/cli/commands/mcp.ts +31 -5
  127. package/src/cli/commands/runtime.ts +61 -8
  128. package/src/cli/commands/setup-profile.ts +472 -0
  129. package/src/cli/controller/check-runner.ts +20 -3
  130. package/src/cli/controller/codex-continuation.ts +4 -7
  131. package/src/cli/controller/context-pack.ts +118 -14
  132. package/src/cli/controller/operational-plan.ts +4 -4
  133. package/src/cli/controller/task-ledger.ts +10 -10
  134. package/src/cli/controller/work-mode.ts +6 -13
  135. package/src/cli/index.ts +14 -35
  136. package/src/cli/local-bridge/connector-freshness.ts +1 -1
  137. package/src/cli/local-bridge/console-assets.ts +17 -0
  138. package/src/cli/local-bridge/console-automations.ts +160 -0
  139. package/src/cli/local-bridge/dashboard.ts +8 -1303
  140. package/src/cli/local-bridge/facade-api.ts +16 -7
  141. package/src/cli/local-bridge/frontend/src/App.tsx +10 -0
  142. package/src/cli/local-bridge/frontend/src/components/Button.tsx +1 -0
  143. package/src/cli/local-bridge/frontend/src/components/DetailPane.tsx +3 -0
  144. package/src/cli/local-bridge/frontend/src/components/SearchField.tsx +1 -0
  145. package/src/cli/local-bridge/frontend/src/components/SectionHeader.tsx +1 -0
  146. package/src/cli/local-bridge/frontend/src/components/Segmented.tsx +1 -0
  147. package/src/cli/local-bridge/frontend/src/components/StatusText.tsx +3 -0
  148. package/src/cli/local-bridge/frontend/src/design/icons.tsx +11 -0
  149. package/src/cli/local-bridge/frontend/src/design/tokens.css +8 -0
  150. package/src/cli/local-bridge/frontend/src/features/automations/AutomationsView.tsx +3 -0
  151. package/src/cli/local-bridge/frontend/src/features/capabilities/CapabilitiesView.tsx +4 -0
  152. package/src/cli/local-bridge/frontend/src/features/overview/OverviewView.tsx +3 -0
  153. package/src/cli/local-bridge/frontend/src/features/repositories/RepositoriesView.tsx +2 -0
  154. package/src/cli/local-bridge/frontend/src/features/settings/SettingsView.tsx +2 -0
  155. package/src/cli/local-bridge/frontend/src/features/system/SystemView.tsx +2 -0
  156. package/src/cli/local-bridge/frontend/src/features/work/WorkView.tsx +3 -0
  157. package/src/cli/local-bridge/frontend/src/lib/api.ts +23 -0
  158. package/src/cli/local-bridge/frontend/src/lib/format.ts +4 -0
  159. package/src/cli/local-bridge/frontend/src/lib/routes.ts +7 -0
  160. package/src/cli/local-bridge/frontend/src/main.tsx +1 -0
  161. package/src/cli/local-bridge/frontend/src/shell/AppShell.tsx +1 -0
  162. package/src/cli/local-bridge/frontend/src/shell/CommandBar.tsx +2 -0
  163. package/src/cli/local-bridge/frontend/src/shell/Sidebar.tsx +3 -0
  164. package/src/cli/local-bridge/frontend/src/styles.css +12 -0
  165. package/src/cli/local-bridge/frontend/src/types.ts +14 -0
  166. package/src/cli/local-bridge/frontend/src/vite-env.d.ts +1 -0
  167. package/src/cli/local-bridge/frontend/vite.config.ts +27 -0
  168. package/src/cli/local-bridge/server.ts +45 -0
  169. package/src/cli/local-bridge/ui-dist/app.css +1 -0
  170. package/src/cli/local-bridge/ui-dist/app.js +11094 -0
  171. package/src/cli/mcp/auth.ts +2 -0
  172. package/src/cli/mcp/instructions.ts +3 -3
  173. package/src/cli/mcp/legacy-tool-service.ts +27 -23
  174. package/src/cli/mcp/multi-repository.ts +2 -0
  175. package/src/cli/mcp/policy.ts +7 -5
  176. package/src/cli/mcp/repository-tools.ts +53 -14
  177. package/src/cli/mcp/server.ts +5 -10
  178. package/src/cli/mcp/setup.ts +220 -276
  179. package/src/cli/mcp/tool-capability-cache.ts +1 -1
  180. package/src/cli/mcp/toolset-names.ts +1 -1
  181. package/src/cli/mcp/toolset.ts +0 -1
  182. package/src/cli/mcp/transports/http.ts +51 -4
  183. package/src/cli/repositories/branch-name-policy.ts +0 -7
  184. package/src/cli/repositories/controller-home.ts +110 -3
  185. package/src/cli/repositories/safe-patch.ts +1 -1
  186. package/src/cli/repositories/selected-path-actions.ts +26 -24
  187. package/src/core/adoption/gitignore-plan.ts +2 -3
  188. package/src/core/adoption/plan.ts +4 -1
  189. package/src/runtime/control-plane/execution/edit-validation-coordinator.ts +556 -0
  190. package/src/runtime/control-plane/execution/work-handle-store.ts +128 -1
  191. package/src/runtime/control-plane/execution/work-validation-reconciler.ts +277 -0
  192. package/src/runtime/control-plane/execution/work-verification-snapshot.ts +169 -0
  193. package/src/runtime/control-plane/facade/capability-registry.ts +0 -10
  194. package/src/runtime/control-plane/facade/handoff-inbox-store.ts +44 -25
  195. package/src/runtime/control-plane/facade/suggested-actions.ts +7 -1
  196. package/src/runtime/control-plane/facade/types.ts +0 -1
  197. package/src/runtime/control-plane/global-scheduler/scheduler.ts +30 -8
  198. package/src/runtime/control-plane/launcher/chatgpt-work-binding-store.ts +114 -0
  199. package/src/runtime/control-plane/launcher/chatgpt-work-continuation.ts +175 -0
  200. package/src/runtime/control-plane/launcher/launch-reservation-store.ts +161 -0
  201. package/src/runtime/control-plane/launcher/provider-mcp-bootstrap.ts +64 -0
  202. package/src/runtime/control-plane/launcher/thin-launcher.ts +117 -24
  203. package/src/runtime/control-plane/routing/route-policy.ts +14 -14
  204. package/src/runtime/control-plane/runtime-cleanup.ts +0 -1
  205. package/src/runtime/evidence/event-ledger.ts +1 -1
  206. package/src/runtime/{workflow/campaigns/workspace.ts → execution/managed-workspace.ts} +41 -43
  207. package/src/runtime/execution/process-runtime/check-facade.ts +6 -0
  208. package/src/runtime/execution/process-runtime/check-result.ts +45 -0
  209. package/src/runtime/execution/process-runtime/check-runner-sidecar.ts +42 -16
  210. package/src/runtime/execution/process-runtime/command-facade.ts +4 -2
  211. package/src/runtime/execution/process-runtime/gc.ts +1 -1
  212. package/src/runtime/execution/process-runtime/index.ts +3 -0
  213. package/src/runtime/execution/process-runtime/interactive-admission.ts +14 -5
  214. package/src/runtime/execution/process-runtime/persisted-check.ts +285 -0
  215. package/src/runtime/execution/process-runtime/resource-claims.ts +30 -0
  216. package/src/runtime/execution/process-runtime/runtime.ts +18 -1
  217. package/src/runtime/execution/process-runtime/store.ts +1 -19
  218. package/src/runtime/execution/process-runtime/types.ts +4 -0
  219. package/src/runtime/execution/thin-harness/execution-router.ts +0 -1
  220. package/src/runtime/execution/thin-harness/fast-executor.ts +1 -1
  221. package/src/runtime/execution/thin-harness/lightweight-lanes.ts +1 -2
  222. package/src/runtime/execution/thin-harness/types.ts +0 -1
  223. package/src/runtime/execution/workers/executor.ts +1 -1
  224. package/src/runtime/gateway/mcp/execution-tools.ts +1 -1
  225. package/src/runtime/gateway/mcp/persisted-check-process.ts +3 -255
  226. package/src/runtime/gateway/mcp/process-tools.ts +16 -3
  227. package/src/runtime/gateway/mcp/resource-policy.ts +0 -9
  228. package/src/runtime/gateway/mcp/router.ts +117 -182
  229. package/src/runtime/gateway/mcp/runtime-tools.ts +331 -409
  230. package/src/runtime/gateway/mcp/work-validation-reconciler.ts +1 -219
  231. package/src/runtime/plugins/browser-adapter.ts +5 -0
  232. package/src/runtime/plugins/desktop-operator-registration.ts +1 -1
  233. package/src/runtime/plugins/store.ts +14 -6
  234. package/src/runtime/plugins/types.ts +1 -1
  235. package/src/runtime/projections/materialized-view.ts +1 -23
  236. package/src/runtime/recovery/maintenance-executor.ts +9 -0
  237. package/src/runtime/resources/ownership.ts +0 -1
  238. package/src/runtime/root/gateway-adapter.ts +2 -0
  239. package/src/runtime/root/mcp-transport.ts +10 -2
  240. package/src/runtime/root/package-connector-service.ts +136 -0
  241. package/src/runtime/root/package-runtime-release.ts +161 -0
  242. package/src/runtime/root/package-runtime-service.ts +185 -0
  243. package/src/runtime/root/release-materialize.ts +19 -0
  244. package/src/runtime/root/runtime.ts +2 -1
  245. package/src/runtime/root/scheduler.ts +5 -0
  246. package/src/runtime/root/service.ts +18 -5
  247. package/src/runtime/standalone-recovery/core.ts +65 -16
  248. package/src/runtime/workflow/findings/store.ts +145 -106
  249. package/src/runtime/workflow/findings/types.ts +22 -0
  250. package/src/runtime/workflow/schedules/engine.ts +132 -2
  251. package/src/runtime/workflow/schedules/pending-activation.ts +139 -0
  252. package/src/runtime/workflow/schedules/work-continuation.ts +218 -0
  253. package/docs/CHANGELOG.md +0 -978
  254. package/docs/README.zh-CN.md +0 -58
  255. package/scripts/smoke-chatgpt-supervised-automation.ts +0 -208
  256. package/src/cli/commands/controller.ts +0 -680
  257. package/src/runtime/workflow/campaigns/cleanup.ts +0 -263
  258. package/src/runtime/workflow/campaigns/engine.ts +0 -112
  259. package/src/runtime/workflow/campaigns/normalize.ts +0 -100
  260. package/src/runtime/workflow/campaigns/review.ts +0 -315
  261. package/src/runtime/workflow/campaigns/store.ts +0 -526
  262. package/src/runtime/workflow/campaigns/supervisor.ts +0 -112
  263. package/src/runtime/workflow/campaigns/types.ts +0 -290
  264. /package/src/runtime/{workflow/campaigns → execution}/workspace-agent.ts +0 -0
@@ -1,5 +1,5 @@
1
1
  /**
2
- * `forge init-hook` -- read-only Agent bootstrap checklist.
2
+ * Readiness model behind `forge setup check` and resumable `forge setup`.
3
3
  *
4
4
  * This command intentionally does not install hooks, write user-owned markdown,
5
5
  * or mutate repo-local runtime files. It gathers the existing readiness probes
@@ -16,9 +16,23 @@ import { fileURLToPath } from 'url';
16
16
  import { Command } from 'commander';
17
17
  import { runDoctor, type CheckStatus, type DoctorReport } from './doctor';
18
18
  import { runStatus, type StatusReport } from './status';
19
- import type { InstallTargetSpec } from './install';
20
-
21
- export type InitHookTarget = InstallTargetSpec;
19
+ import { runMcpSetupChatgpt } from '../mcp/setup';
20
+ import { resolveRepoPreferredControllerHome } from '../repositories/controller-home';
21
+ import {
22
+ configureSetupProfile,
23
+ detectSetupPlatform,
24
+ formatSetupProfile,
25
+ readSetupProfile,
26
+ resolveControllerGuidance,
27
+ resolveRuntimeGuidance,
28
+ resolveTunnelGuidance,
29
+ setupHostTarget,
30
+ type SetupHostTarget,
31
+ type SetupPlatformSnapshot,
32
+ type SetupProfile,
33
+ } from './setup-profile';
34
+
35
+ export type InitHookTarget = SetupHostTarget;
22
36
  export type InitHookStatus = 'ok' | 'attention' | 'blocked';
23
37
  export type InitHookCheckStatus = CheckStatus | 'needs_agent';
24
38
  export type InitHookCheckSource =
@@ -108,7 +122,7 @@ const SCRIPT_DIR = dirname(fileURLToPath(import.meta.url));
108
122
  const REPO_ROOT = resolve(SCRIPT_DIR, '..', '..', '..');
109
123
  const UPDATE_CHECK_ENV = 'FORGE_CHECK_UPDATES';
110
124
  const GLOBAL_RULES_BEGIN = '<!-- BEGIN: forge global-working-rules -->';
111
- const VALID_TARGETS: readonly InitHookTarget[] = ['codex', 'claude', 'both'];
125
+ const VALID_TARGETS: readonly InitHookTarget[] = ['none', 'codex', 'claude', 'both'];
112
126
 
113
127
  function withProcessEnv<T>(env: NodeJS.ProcessEnv | undefined, fn: () => T): T {
114
128
  if (!env) return fn();
@@ -129,6 +143,7 @@ function withProcessEnv<T>(env: NodeJS.ProcessEnv | undefined, fn: () => T): T {
129
143
  }
130
144
 
131
145
  function selectedTargets(target: InitHookTarget): Array<'codex' | 'claude'> {
146
+ if (target === 'none') return [];
132
147
  if (target === 'both') return ['codex', 'claude'];
133
148
  return [target];
134
149
  }
@@ -230,6 +245,19 @@ function parseActionCommand(detail: string): string | undefined {
230
245
  return run?.[1]?.trim();
231
246
  }
232
247
 
248
+ function doctorEntryRelevant(id: string, target: InitHookTarget): boolean {
249
+ if (id === 'cli-on-path' || id === 'cli-version' || id === 'cli-update' || id === 'security-config') return true;
250
+ if (target === 'none') return false;
251
+ if (id === 'repo-hook-scripts' || id === 'codegraph-readiness' || id === 'codegraph-index') return true;
252
+ if (target === 'codex' || target === 'both') {
253
+ if (id === 'codex-adapter' || id === 'codex-trust-state' || id === 'codex-codegraph-mcp') return true;
254
+ }
255
+ if (target === 'claude' || target === 'both') {
256
+ if (id === 'claude-adapter' || id === 'claude-codegraph-mcp') return true;
257
+ }
258
+ return false;
259
+ }
260
+
233
261
  function doctorChecks(
234
262
  report: DoctorReport,
235
263
  target: InitHookTarget,
@@ -238,6 +266,7 @@ function doctorChecks(
238
266
  ): InitHookCheck[] {
239
267
  const checks: InitHookCheck[] = [];
240
268
  for (const entry of report.checks) {
269
+ if (!doctorEntryRelevant(entry.id, target)) continue;
241
270
  const source: InitHookCheckSource = entry.id === 'security-config' ? 'security' : 'doctor';
242
271
  let checkStatus: InitHookCheckStatus = entry.status;
243
272
 
@@ -425,7 +454,7 @@ function commandForToolGap(toolName: string, tool: ToolingTool, target: InitHook
425
454
  return normalizeToolCommand(tool.install_command, target);
426
455
  }
427
456
  if (toolName === 'codex_automation_profile') {
428
- return 'forge init --target codex --no-cli --no-hooks --no-codegraph';
457
+ return 'forge install --target codex --no-cli --no-hooks --no-codegraph';
429
458
  }
430
459
  return normalizeToolCommand(
431
460
  tool.sync_command ?? tool.ensure_command ?? tool.mcp_install_command ?? tool.install_command ?? tool.upgrade_command,
@@ -579,24 +608,26 @@ function overallStatus(summary: InitHookReport['summary']): InitHookStatus {
579
608
  export function runInitHook(opts: InitHookOptions = {}): InitHookReport {
580
609
  const cwd = resolve(opts.cwd ?? process.cwd());
581
610
  const sourceRoot = resolve(opts.sourceRoot ?? REPO_ROOT);
582
- const target = opts.target ?? 'both';
611
+ const target = opts.target ?? 'none';
583
612
  const checkUpdates = opts.checkUpdates === true;
584
613
  const actions: InitHookAction[] = [];
585
614
 
586
615
  const statusReport = opts.statusReport ?? withProcessEnv(opts.env, () => runStatus(cwd));
587
616
  const doctorEnv = { ...(opts.env ?? {}), [UPDATE_CHECK_ENV]: checkUpdates ? '1' : undefined };
588
617
  const doctorReport = opts.doctorReport ?? withProcessEnv(doctorEnv, () => runDoctor(cwd));
589
- const toolingProbe = opts.toolingReport
590
- ? { report: opts.toolingReport, error: undefined }
591
- : collectToolingReport(sourceRoot, cwd, target, checkUpdates, opts.env);
618
+ const toolingProbe = target === 'none'
619
+ ? { report: undefined, error: undefined }
620
+ : opts.toolingReport
621
+ ? { report: opts.toolingReport, error: undefined }
622
+ : collectToolingReport(sourceRoot, cwd, target, checkUpdates, opts.env);
592
623
 
593
624
  const checks: InitHookCheck[] = [
594
- ...statusChecks(statusReport, target, checkUpdates, actions),
625
+ ...(target === 'none' ? [] : statusChecks(statusReport, target, checkUpdates, actions)),
595
626
  ...doctorChecks(doctorReport, target, checkUpdates, actions),
596
- ...globalRulesChecks(target, opts.env, checkUpdates, actions),
597
- ...runtimeCapabilityChecks(toolingProbe.report, target, checkUpdates, actions),
598
- ...toolingChecks(toolingProbe.report, target, checkUpdates, actions, toolingProbe.error),
599
- ...legacyChecks(cwd, target, checkUpdates, actions),
627
+ ...(target === 'none' ? [] : globalRulesChecks(target, opts.env, checkUpdates, actions)),
628
+ ...(target === 'none' ? [] : runtimeCapabilityChecks(toolingProbe.report, target, checkUpdates, actions)),
629
+ ...(target === 'none' ? [] : toolingChecks(toolingProbe.report, target, checkUpdates, actions, toolingProbe.error)),
630
+ ...(target === 'none' ? [] : legacyChecks(cwd, target, checkUpdates, actions)),
600
631
  ];
601
632
 
602
633
  const summary = summarize(checks);
@@ -617,7 +648,7 @@ export function formatInitHook(report: InitHookReport, asJson = false): string {
617
648
  lines.push(`forge setup check: ${report.status}`);
618
649
  lines.push(`target=${report.target}; check-updates=${report.checkUpdates ? 'on' : 'off'}`);
619
650
  lines.push(
620
- `summary: ${report.summary.ok} ok, ${report.summary.warn} warn, ${report.summary.fail} fail, ${report.summary.na} n/a, ${report.summary.needs_agent} needs-agent`,
651
+ `summary: ${report.summary.ok} ok, ${report.summary.warn} warn, ${report.summary.fail} fail, ${report.summary.na} n/a, ${report.summary.needs_agent} needs-action`,
621
652
  );
622
653
  lines.push('');
623
654
  lines.push('Checks:');
@@ -625,7 +656,7 @@ export function formatInitHook(report: InitHookReport, asJson = false): string {
625
656
  lines.push(` [${check.status}] ${check.id}: ${check.detail}`);
626
657
  }
627
658
  lines.push('');
628
- lines.push('Agent actions:');
659
+ lines.push('Actions:');
629
660
  if (report.agent_actions.length === 0) {
630
661
  lines.push(' none');
631
662
  } else {
@@ -640,30 +671,6 @@ export function formatInitHook(report: InitHookReport, asJson = false): string {
640
671
  return lines.join('\n');
641
672
  }
642
673
 
643
- export function buildInitHookCommand(): Command {
644
- const command = new Command('init-hook');
645
- command
646
- .description('Run a read-only Agent bootstrap checklist for user-level Forge readiness')
647
- .option('--target <target>', `Host target to inspect: ${VALID_TARGETS.join('|')}`, 'both')
648
- .option('--check-updates', 'Include network-backed version update advisories')
649
- .option('--json', 'Output JSON instead of human-readable text')
650
- .action((rawOpts: { target: string; checkUpdates?: boolean; json?: boolean }) => {
651
- if (!VALID_TARGETS.includes(rawOpts.target as InitHookTarget)) {
652
- console.error(
653
- `forge init-hook: invalid --target "${rawOpts.target}" (expected: ${VALID_TARGETS.join(', ')})`,
654
- );
655
- process.exit(2);
656
- }
657
- const report = runInitHook({
658
- target: rawOpts.target as InitHookTarget,
659
- checkUpdates: rawOpts.checkUpdates === true,
660
- });
661
- console.log(formatInitHook(report, rawOpts.json === true));
662
- process.exit(report.status === 'blocked' ? 1 : 0);
663
- });
664
- return command;
665
- }
666
-
667
674
  export type SetupSessionStatus = 'open' | 'blocked' | 'ready' | 'closed';
668
675
 
669
676
  export interface SetupSession {
@@ -672,6 +679,8 @@ export interface SetupSession {
672
679
  status: SetupSessionStatus;
673
680
  target: InitHookTarget;
674
681
  checkUpdates: boolean;
682
+ profile?: SetupProfile;
683
+ platform?: SetupPlatformSnapshot;
675
684
  createdAt: string;
676
685
  updatedAt: string;
677
686
  closedAt?: string;
@@ -681,6 +690,10 @@ export interface SetupSession {
681
690
 
682
691
  export interface SetupSessionOptions extends InitHookOptions {
683
692
  setupRoot?: string;
693
+ profile?: SetupProfile;
694
+ platform?: SetupPlatformSnapshot;
695
+ controllerHome?: string;
696
+ accountHome?: string;
684
697
  report?: InitHookReport;
685
698
  now?: () => Date;
686
699
  uuid?: () => string;
@@ -714,28 +727,86 @@ export function readSetupSession(options: Pick<SetupSessionOptions, 'setupRoot'
714
727
  return readSetupSessionAt(setupSessionPath(options));
715
728
  }
716
729
 
717
- function setupStatus(report: InitHookReport): SetupSessionStatus {
730
+ function setupStatus(report: InitHookReport, nextAction?: InitHookAction): SetupSessionStatus {
718
731
  if (report.status === 'blocked') return 'blocked';
719
- if (report.status === 'ok' && report.agent_actions.length === 0) return 'ready';
732
+ if (report.status === 'ok' && report.agent_actions.length === 0 && !nextAction) return 'ready';
720
733
  return 'open';
721
734
  }
722
735
 
736
+ function profileNextAction(profile: SetupProfile | undefined, platform: SetupPlatformSnapshot, options: { controllerHome?: string; accountHome?: string } = {}): InitHookAction | undefined {
737
+ if (!profile) {
738
+ return {
739
+ id: 'controller.select',
740
+ status: 'needs_agent',
741
+ reason: 'Choose the external controller that will act as Forge’s brain. ChatGPT is recommended; Codex, Claude, or another MCP client can be primary instead.',
742
+ requires_agent: true,
743
+ risk: 'Writes only the local Forge setup profile. It does not install or authenticate third-party software.',
744
+ command: 'forge setup configure --controller chatgpt',
745
+ verification: 'forge setup next',
746
+ };
747
+ }
748
+ const controller = resolveControllerGuidance(profile, { controllerHome: options.controllerHome, home: options.accountHome });
749
+ if (controller && !controller.ready) {
750
+ return {
751
+ id: `controller.${controller.controller}.configure`,
752
+ status: 'needs_agent',
753
+ reason: `${controller.title}: ${controller.detail}`,
754
+ requires_agent: true,
755
+ risk: 'Configures only the explicitly selected external controller entry. Unselected Codex/Claude providers remain untouched.',
756
+ ...(controller.command ? { command: controller.command } : {}),
757
+ verification: 'forge setup next',
758
+ };
759
+ }
760
+ const runtime = resolveRuntimeGuidance(profile, { controllerHome: options.controllerHome });
761
+ if (runtime && !runtime.ready) {
762
+ return {
763
+ id: 'runtime.package.install',
764
+ status: 'needs_agent',
765
+ reason: `${runtime.title}: ${runtime.detail}`,
766
+ requires_agent: true,
767
+ risk: 'Installs or diagnoses the user-level Forge Runtime only. Standalone Recovery and source-release activation are not part of normal setup.',
768
+ ...(runtime.command ? { command: runtime.command } : {}),
769
+ verification: 'forge setup next',
770
+ };
771
+ }
772
+ const tunnel = resolveTunnelGuidance(profile, platform, { controllerHome: options.controllerHome });
773
+ if (!tunnel.ready) {
774
+ return {
775
+ id: `tunnel.${tunnel.provider}.configure`,
776
+ status: 'needs_agent',
777
+ reason: `${tunnel.title}: ${tunnel.detail}`,
778
+ requires_agent: true,
779
+ risk: 'Remote connectivity is provider-owned. Forge keeps its MCP listener on loopback; credentials, authentication, and account changes remain explicit.',
780
+ ...(tunnel.command ? { command: tunnel.command } : {}),
781
+ verification: 'forge setup next',
782
+ };
783
+ }
784
+ return undefined;
785
+ }
786
+
723
787
  export function openSetupSession(options: SetupSessionOptions = {}): SetupSession {
724
788
  const path = setupSessionPath(options);
725
789
  const previous = readSetupSessionAt(path);
726
- const report = options.report ?? runInitHook(options);
790
+ const profile = options.profile ?? readSetupProfile(options);
791
+ const platform = options.platform ?? detectSetupPlatform({ env: options.env });
792
+ const target = options.target ?? setupHostTarget(profile);
793
+ const report = options.report ?? runInitHook({ ...options, target });
794
+ const controllerHome = resolveRepoPreferredControllerHome(options.cwd ?? process.cwd(), options.controllerHome);
795
+ const nextAction = profileNextAction(profile, platform, { controllerHome, accountHome: options.accountHome }) ?? report.agent_actions[0];
727
796
  const now = (options.now ?? (() => new Date()))().toISOString();
728
797
  const reuse = previous && previous.status !== 'closed';
729
798
  return writeSetupSessionAt(path, {
730
799
  schemaVersion: 1,
731
800
  sessionId: reuse ? previous.sessionId : (options.uuid ?? randomUUID)(),
732
- status: setupStatus(report),
801
+ status: setupStatus(report, nextAction),
733
802
  target: report.target,
734
803
  checkUpdates: report.checkUpdates,
804
+ ...(profile ? { profile } : {}),
805
+ platform,
735
806
  createdAt: reuse ? previous.createdAt : now,
736
807
  updatedAt: now,
737
808
  lastReport: report,
738
- ...(report.agent_actions[0] ? { nextAction: report.agent_actions[0] } : {}),
809
+ ...(nextAction ? { nextAction } : {}),
739
810
  });
740
811
  }
741
812
 
@@ -756,8 +827,9 @@ export function formatSetupSession(session: SetupSession, asJson = false): strin
756
827
  if (asJson) return JSON.stringify(session, null, 2);
757
828
  const lines = [
758
829
  `forge setup: ${session.status}`,
759
- `session=${session.sessionId}; target=${session.target}; updated=${session.updatedAt}`,
760
- `summary: ${session.lastReport.summary.ok} ok, ${session.lastReport.summary.warn} warn, ${session.lastReport.summary.fail} fail, ${session.lastReport.summary.needs_agent} needs-agent`,
830
+ `session=${session.sessionId}; controller=${session.profile?.primaryController ?? 'not-selected'}; host-tooling=${session.target}; updated=${session.updatedAt}`,
831
+ `summary: ${session.lastReport.summary.ok} ok, ${session.lastReport.summary.warn} warn, ${session.lastReport.summary.fail} fail, ${session.lastReport.summary.needs_agent} needs-action`,
832
+ ...(session.profile ? ['', formatSetupProfile(session.profile, session.platform)] : []),
761
833
  ];
762
834
  if (session.nextAction) {
763
835
  lines.push('', `Next: ${session.nextAction.reason}`);
@@ -771,17 +843,50 @@ export function formatSetupSession(session: SetupSession, asJson = false): strin
771
843
  return lines.join('\n');
772
844
  }
773
845
 
774
- function setupTarget(value: string, surface: string): InitHookTarget {
846
+ function setupTarget(value: string | undefined, surface: string): InitHookTarget | undefined {
847
+ if (value === undefined) return undefined;
775
848
  if (!VALID_TARGETS.includes(value as InitHookTarget)) {
776
849
  throw new Error(`${surface}: invalid --target "${value}" (expected: ${VALID_TARGETS.join(', ')})`);
777
850
  }
778
851
  return value as InitHookTarget;
779
852
  }
780
853
 
781
- function setupCycle(rawOpts: { target: string; checkUpdates?: boolean; json?: boolean }, surface: string): void {
854
+ interface SetupCycleRawOptions {
855
+ target?: string;
856
+ controller?: string;
857
+ addController?: string[];
858
+ tunnel?: string;
859
+ endpoint?: string;
860
+ tunnelId?: string;
861
+ checkUpdates?: boolean;
862
+ json?: boolean;
863
+ }
864
+
865
+ function collectOption(value: string, previous: string[] = []): string[] {
866
+ return [...previous, value];
867
+ }
868
+
869
+ function syncExplicitRemoteControllerEndpoint(profile: SetupProfile | undefined, endpoint: string | undefined): void {
870
+ if (!profile || !endpoint || !profile.controllers.some((controller) => controller === 'chatgpt' || controller === 'mcp')) return;
871
+ runMcpSetupChatgpt({ userLevel: true, endpoint });
872
+ }
873
+
874
+ function setupCycle(rawOpts: SetupCycleRawOptions, surface: string): void {
782
875
  try {
876
+ let profile = readSetupProfile();
877
+ if (rawOpts.controller || rawOpts.addController?.length || rawOpts.tunnel || rawOpts.endpoint || rawOpts.tunnelId) {
878
+ profile = configureSetupProfile({
879
+ controller: rawOpts.controller,
880
+ addControllers: rawOpts.addController,
881
+ tunnel: rawOpts.tunnel,
882
+ endpoint: rawOpts.endpoint,
883
+ tunnelId: rawOpts.tunnelId,
884
+ });
885
+ syncExplicitRemoteControllerEndpoint(profile, rawOpts.endpoint);
886
+ }
783
887
  const session = openSetupSession({
784
- target: setupTarget(rawOpts.target, surface),
888
+ profile,
889
+ target: setupTarget(rawOpts.target, surface) ?? setupHostTarget(profile),
785
890
  checkUpdates: rawOpts.checkUpdates === true,
786
891
  });
787
892
  console.log(formatSetupSession(session, rawOpts.json === true));
@@ -792,33 +897,69 @@ function setupCycle(rawOpts: { target: string; checkUpdates?: boolean; json?: bo
792
897
  }
793
898
  }
794
899
 
900
+ function addSetupSelectionOptions(command: Command): Command {
901
+ return command
902
+ .option('--controller <controller>', 'Primary external controller: chatgpt|codex|claude|mcp')
903
+ .option('--add-controller <controller>', 'Also configure another external controller entry (repeatable)', collectOption, [])
904
+ .option('--tunnel <provider>', 'Remote connection: auto|openai|cloudflare|tailscale|existing|none')
905
+ .option('--tunnel-id <id>', 'OpenAI Secure MCP Tunnel id (non-secret tunnel_...)')
906
+ .option('--endpoint <url>', 'Existing/stable public HTTPS URL ending in /mcp')
907
+ .option('--target <target>', `Deprecated host-tooling override: ${VALID_TARGETS.join('|')}`)
908
+ .option('--check-updates', 'Include network-backed version update advisories')
909
+ .option('--json', 'Output JSON instead of human-readable text');
910
+ }
911
+
795
912
  export function buildSetupCommand(): Command {
796
913
  const command = new Command('setup');
797
914
  command
798
- .description('Open or continue one resumable Forge setup session')
799
- .option('--target <target>', `Host target to configure: ${VALID_TARGETS.join('|')}`, 'both')
915
+ .description('Configure Forge around one external controller brain and optional additional controller entries')
800
916
  .option('--check-updates', 'Include network-backed version update advisories')
801
917
  .option('--json', 'Output JSON instead of human-readable text')
802
- .action((rawOpts: { target: string; checkUpdates?: boolean; json?: boolean }) => setupCycle(rawOpts, 'forge setup'));
918
+ .action((rawOpts: SetupCycleRawOptions) => setupCycle(rawOpts, 'forge setup'));
803
919
 
804
920
  for (const name of ['open', 'next'] as const) {
805
- command
921
+ addSetupSelectionOptions(command
806
922
  .command(name)
807
- .description(name === 'open' ? 'Start or resume the verified setup session' : 'Re-check configuration and advance to the next action')
808
- .option('--target <target>', `Host target to configure: ${VALID_TARGETS.join('|')}`, 'both')
809
- .option('--check-updates', 'Include network-backed version update advisories')
810
- .option('--json', 'Output JSON instead of human-readable text')
811
- .action((rawOpts: { target: string; checkUpdates?: boolean; json?: boolean }) => setupCycle(rawOpts, `forge setup ${name}`));
923
+ .description(name === 'open' ? 'Start or resume the guided Forge setup session' : 'Re-check configuration and advance to the next action'))
924
+ .action((rawOpts: SetupCycleRawOptions) => setupCycle(rawOpts, `forge setup ${name}`));
812
925
  }
813
926
 
927
+ command
928
+ .command('configure')
929
+ .description('Choose the primary external controller, optional additional controllers, and public access provider')
930
+ .requiredOption('--controller <controller>', 'Primary external controller: chatgpt|codex|claude|mcp')
931
+ .option('--add-controller <controller>', 'Also configure another external controller entry (repeatable)', collectOption, [])
932
+ .option('--tunnel <provider>', 'Remote connection: auto|openai|cloudflare|tailscale|existing|none')
933
+ .option('--tunnel-id <id>', 'OpenAI Secure MCP Tunnel id (non-secret tunnel_...)')
934
+ .option('--endpoint <url>', 'Existing/stable public HTTPS URL ending in /mcp')
935
+ .option('--json', 'Output JSON instead of human-readable text')
936
+ .action((rawOpts: { controller: string; addController?: string[]; tunnel?: string; tunnelId?: string; endpoint?: string; json?: boolean }) => {
937
+ try {
938
+ const profile = configureSetupProfile({
939
+ controller: rawOpts.controller,
940
+ addControllers: rawOpts.addController,
941
+ tunnel: rawOpts.tunnel,
942
+ endpoint: rawOpts.endpoint,
943
+ tunnelId: rawOpts.tunnelId,
944
+ });
945
+ syncExplicitRemoteControllerEndpoint(profile, rawOpts.endpoint);
946
+ console.log(rawOpts.json ? JSON.stringify(profile, null, 2) : `${formatSetupProfile(profile)}\n\nNext: forge setup next`);
947
+ } catch (error) {
948
+ console.error(error instanceof Error ? error.message : String(error));
949
+ process.exitCode = 2;
950
+ }
951
+ });
952
+
814
953
  command
815
954
  .command('status')
816
- .description('Read the persisted setup session without changing host configuration')
955
+ .description('Read the persisted setup session and external-controller profile')
817
956
  .option('--json', 'Output JSON instead of human-readable text')
818
957
  .action((rawOpts: { json?: boolean }) => {
819
958
  const session = readSetupSession();
959
+ const profile = readSetupProfile();
820
960
  if (!session) {
821
- console.log(rawOpts.json ? JSON.stringify({ status: 'not_started' }) : 'forge setup: not started\nRun: forge setup open');
961
+ if (rawOpts.json) console.log(JSON.stringify({ status: 'not_started', profile }, null, 2));
962
+ else console.log(profile ? `forge setup: not started\n${formatSetupProfile(profile)}\nRun: forge setup open` : 'forge setup: not started\nRun: forge setup');
822
963
  return;
823
964
  }
824
965
  console.log(formatSetupSession(session, rawOpts.json === true));
@@ -826,15 +967,16 @@ export function buildSetupCommand(): Command {
826
967
 
827
968
  command
828
969
  .command('close')
829
- .description('Close setup after all checks pass')
830
- .option('--target <target>', `Host target to verify: ${VALID_TARGETS.join('|')}`, 'both')
970
+ .description('Close setup after the selected external controller path is ready')
831
971
  .option('--check-updates', 'Include network-backed version update advisories')
832
972
  .option('--force', 'Close even when checks still need attention')
833
973
  .option('--json', 'Output JSON instead of human-readable text')
834
- .action((rawOpts: { target: string; checkUpdates?: boolean; force?: boolean; json?: boolean }) => {
974
+ .action((rawOpts: { checkUpdates?: boolean; force?: boolean; json?: boolean }) => {
835
975
  try {
976
+ const profile = readSetupProfile();
836
977
  const session = closeSetupSession({
837
- target: setupTarget(rawOpts.target, 'forge setup close'),
978
+ profile,
979
+ target: setupHostTarget(profile),
838
980
  checkUpdates: rawOpts.checkUpdates === true,
839
981
  force: rawOpts.force === true,
840
982
  });
@@ -848,14 +990,15 @@ export function buildSetupCommand(): Command {
848
990
 
849
991
  command
850
992
  .command('check')
851
- .description('Run the complete read-only setup checklist without opening a session')
852
- .option('--target <target>', `Host target to inspect: ${VALID_TARGETS.join('|')}`, 'both')
993
+ .description('Run the read-only readiness checklist for the configured external controller set')
994
+ .option('--target <target>', `Host-tooling override: ${VALID_TARGETS.join('|')}`)
853
995
  .option('--check-updates', 'Include network-backed version update advisories')
854
996
  .option('--json', 'Output JSON instead of human-readable text')
855
- .action((rawOpts: { target: string; checkUpdates?: boolean; json?: boolean }) => {
997
+ .action((rawOpts: { target?: string; checkUpdates?: boolean; json?: boolean }) => {
856
998
  try {
999
+ const profile = readSetupProfile();
857
1000
  const report = runInitHook({
858
- target: setupTarget(rawOpts.target, 'forge setup check'),
1001
+ target: setupTarget(rawOpts.target, 'forge setup check') ?? setupHostTarget(profile),
859
1002
  checkUpdates: rawOpts.checkUpdates === true,
860
1003
  });
861
1004
  console.log(formatInitHook(report, rawOpts.json === true));
@@ -1,8 +1,7 @@
1
1
  /**
2
2
  * Existing-repository Forge bootstrap/update implementation.
3
3
  *
4
- * This backs the public `forge adopt` command and the legacy
5
- * `forge-init` skill facade: default the target repo to cwd,
4
+ * This backs the public `forge adopt` command and reusable bootstrap primitives: default the target repo to cwd,
6
5
  * install/refresh the machine runtime pieces, apply the repo-local workflow
7
6
  * migration, then verify the installed harness.
8
7
  */
@@ -382,7 +381,7 @@ export function runInit(opts: InitCommandOptions = {}): InitCommandResult {
382
381
  const verify = opts.verify !== false;
383
382
  const syncSkill = opts.syncSkill !== false;
384
383
  const hostAdapters = opts.hostAdapters !== false;
385
- const externalSkills = opts.externalSkills !== false;
384
+ const externalSkills = opts.externalSkills === true;
386
385
  const codegraph = opts.codegraph !== false;
387
386
  const configureCgMcp = opts.configureCodegraphMcp === true;
388
387
  const syncCodegraph = opts.syncCodegraph === true;
@@ -576,15 +575,15 @@ export function runInit(opts: InitCommandOptions = {}): InitCommandResult {
576
575
  steps.push(
577
576
  withStepName(
578
577
  handoff,
579
- "refresh handoff packet",
578
+ "refresh session continuation",
580
579
  "scripts/prepare-codex-handoff.sh --reason forge-adopt-verify",
581
580
  ),
582
581
  );
583
582
  } else {
584
583
  steps.push({
585
- step: "refresh handoff packet",
584
+ step: "refresh session continuation",
586
585
  status: "skipped",
587
- detail: "scripts/prepare-codex-handoff.sh missing",
586
+ detail: "session continuation helper missing (legacy filename scripts/prepare-codex-handoff.sh)",
588
587
  });
589
588
  }
590
589
  }
@@ -12,6 +12,7 @@ import {
12
12
  runMcpPrintGuide,
13
13
  runMcpSetupChatgpt,
14
14
  runMcpSetupCodex,
15
+ runMcpSetupClaude,
15
16
  } from '../mcp/setup';
16
17
 
17
18
  export interface McpServeOptions {
@@ -42,15 +43,20 @@ interface McpAccessOptions {
42
43
 
43
44
  interface McpSetupChatgptOptions {
44
45
  repo?: string;
46
+ controllerHome?: string;
47
+ userLevel?: boolean;
45
48
  host?: string;
46
49
  port?: string;
47
50
  endpoint?: string;
48
51
  serverName?: string;
52
+ localControllerPort?: string;
53
+ connectorPort?: string;
49
54
  }
50
55
 
51
- interface McpSetupCodexOptions {
56
+ interface McpSetupLocalControllerOptions {
52
57
  repo?: string;
53
58
  scope?: string;
59
+ profile?: string;
54
60
  dryRun?: boolean;
55
61
  }
56
62
 
@@ -221,14 +227,18 @@ export function buildMcpCommand(): Command {
221
227
  });
222
228
  });
223
229
 
224
- const setup = new Command('setup').description('Generate MCP setup files for ChatGPT or Codex');
230
+ const setup = new Command('setup').description('Configure an external controller connection to Forge');
225
231
 
226
232
  setup
227
233
  .command('chatgpt')
228
234
  .description('Generate ChatGPT Connector local config and manual setup guide')
229
235
  .option('--repo <path>', 'Repository root to configure', '.')
236
+ .option('--controller-home <path>', 'User Controller Home (used with --user-level)')
237
+ .option('--user-level', 'Configure the user-level Controller without writing repository guide/gitignore files')
230
238
  .option('--host <host>', 'Local MCP HTTP bind host', '127.0.0.1')
231
239
  .option('--port <port>', 'Local MCP HTTP bind port', '8765')
240
+ .option('--local-controller-port <port>', 'Local-only Utility Console port; defaults to MCP port + 1')
241
+ .option('--connector-port <port>', 'Loopback OAuth connector port for ChatGPT tunnels; defaults to MCP port + 2')
232
242
  .option('--endpoint <url>', 'Stable public HTTPS /mcp endpoint to store in ignored local config')
233
243
  .option('--server-name <name>', 'ChatGPT Connector/MCP server name to record in ignored local config')
234
244
  .action((rawOpts: McpSetupChatgptOptions) => {
@@ -240,17 +250,33 @@ export function buildMcpCommand(): Command {
240
250
 
241
251
  setup
242
252
  .command('codex')
243
- .description('Patch Codex MCP config for forge')
253
+ .description('Configure Codex as an explicit Forge controller or executor')
244
254
  .option('--repo <path>', 'Repository root to configure', '.')
245
255
  .option('--scope <scope>', 'Config scope: project|user', 'project')
256
+ .option('--profile <profile>', 'Forge MCP role: controller|executor', 'executor')
246
257
  .option('--dry-run', 'Print planned changes without writing files')
247
- .action((rawOpts: McpSetupCodexOptions) => {
258
+ .action((rawOpts: McpSetupLocalControllerOptions) => {
248
259
  void runMcpAction(() => {
249
260
  const result = runMcpSetupCodex(rawOpts);
250
261
  console.log(result.lines.join('\n'));
251
262
  });
252
263
  });
253
264
 
265
+
266
+ setup
267
+ .command('claude')
268
+ .description('Configure Claude Code as an explicit Forge controller or executor through the Claude CLI')
269
+ .option('--repo <path>', 'Repository root to expose initially', '.')
270
+ .option('--scope <scope>', 'Claude MCP scope: local|project|user', 'user')
271
+ .option('--profile <profile>', 'Forge MCP role: controller|executor', 'controller')
272
+ .option('--dry-run', 'Print the Claude MCP command without changing Claude config')
273
+ .action((rawOpts: McpSetupLocalControllerOptions) => {
274
+ void runMcpAction(() => {
275
+ const result = runMcpSetupClaude(rawOpts);
276
+ console.log(result.lines.join('\n'));
277
+ });
278
+ });
279
+
254
280
  mcp.addCommand(setup);
255
281
 
256
282
  const access = new Command('access').description('Read or change repository Request / Full Access permission levels');
@@ -284,7 +310,7 @@ export function buildMcpCommand(): Command {
284
310
 
285
311
  mcp
286
312
  .command('prepare-goal')
287
- .description('Prepare .ai/harness/handoff/codex-goal.md and print a host-native /goal prompt from a PRD and checklist Sprint')
313
+ .description('Prepare .ai/harness/controller/packets/codex-goal.md and print a host-native /goal prompt from a PRD and checklist Sprint')
288
314
  .option('--repo <path>', 'Repository root to configure', '.')
289
315
  .requiredOption('--prd <path>', 'PRD path to read')
290
316
  .requiredOption('--sprint <path>', 'Checklist Sprint path to execute')