@b00dea/cockpit 0.0.0-bootstrap.0 → 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 (363) hide show
  1. package/CHANGELOG.md +375 -0
  2. package/LICENSE +21 -0
  3. package/README.md +21 -1
  4. package/bin/cockpit.mjs +33 -0
  5. package/cockpit.schema.json +2574 -0
  6. package/dist/autonomy.d.ts +44 -0
  7. package/dist/autonomy.js +156 -0
  8. package/dist/bounded-read.d.ts +14 -0
  9. package/dist/bounded-read.js +42 -0
  10. package/dist/browser/commands.d.ts +62 -0
  11. package/dist/browser/commands.js +349 -0
  12. package/dist/browser/package-paths.d.ts +1 -0
  13. package/dist/browser/package-paths.js +6 -0
  14. package/dist/browser/proxy.d.ts +22 -0
  15. package/dist/browser/proxy.js +37 -0
  16. package/dist/browser/runtime.d.ts +117 -0
  17. package/dist/browser/runtime.js +183 -0
  18. package/dist/browser/surfaces.d.ts +141 -0
  19. package/dist/browser/surfaces.js +556 -0
  20. package/dist/browser/ui-gate.d.ts +15 -0
  21. package/dist/browser/ui-gate.js +18 -0
  22. package/dist/cli-guard.d.ts +5 -0
  23. package/dist/cli-guard.js +71 -0
  24. package/dist/cli-main.d.ts +139 -0
  25. package/dist/cli-main.js +1405 -0
  26. package/dist/cli.d.ts +3 -0
  27. package/dist/cli.js +16 -0
  28. package/dist/command-table.d.ts +113 -0
  29. package/dist/command-table.js +512 -0
  30. package/dist/config/load.d.ts +171 -0
  31. package/dist/config/load.js +345 -0
  32. package/dist/config/sibling-toml.d.ts +48 -0
  33. package/dist/config/sibling-toml.js +160 -0
  34. package/dist/config/surface-tools-toml.d.ts +9 -0
  35. package/dist/config/surface-tools-toml.js +37 -0
  36. package/dist/config/verified-write.d.ts +43 -0
  37. package/dist/config/verified-write.js +89 -0
  38. package/dist/diagnostics.d.ts +26 -0
  39. package/dist/diagnostics.js +335 -0
  40. package/dist/emit.d.ts +22 -0
  41. package/dist/emit.js +43 -0
  42. package/dist/entrypoint-kinds.d.ts +3 -0
  43. package/dist/entrypoint-kinds.js +11 -0
  44. package/dist/environment/application/destructive.d.ts +6 -0
  45. package/dist/environment/application/destructive.js +425 -0
  46. package/dist/environment/application/doctor.d.ts +89 -0
  47. package/dist/environment/application/doctor.js +235 -0
  48. package/dist/environment/application/guard.d.ts +43 -0
  49. package/dist/environment/application/guard.js +622 -0
  50. package/dist/environment/application/health.d.ts +134 -0
  51. package/dist/environment/application/health.js +379 -0
  52. package/dist/environment/application/lifecycle-context.d.ts +228 -0
  53. package/dist/environment/application/lifecycle-context.js +154 -0
  54. package/dist/environment/application/lifecycle-state.d.ts +25 -0
  55. package/dist/environment/application/lifecycle-state.js +201 -0
  56. package/dist/environment/application/lifecycle.d.ts +17 -0
  57. package/dist/environment/application/lifecycle.js +729 -0
  58. package/dist/environment/application/logs.d.ts +45 -0
  59. package/dist/environment/application/logs.js +127 -0
  60. package/dist/environment/application/setup.d.ts +129 -0
  61. package/dist/environment/application/setup.js +741 -0
  62. package/dist/environment/application/shared.d.ts +11 -0
  63. package/dist/environment/application/shared.js +61 -0
  64. package/dist/environment/claims.d.ts +389 -0
  65. package/dist/environment/claims.js +1709 -0
  66. package/dist/environment/codex-hook-trust.d.ts +11 -0
  67. package/dist/environment/codex-hook-trust.js +185 -0
  68. package/dist/environment/compose-environment.d.ts +1 -0
  69. package/dist/environment/compose-environment.js +6 -0
  70. package/dist/environment/compose-ownership.d.ts +21 -0
  71. package/dist/environment/compose-ownership.js +117 -0
  72. package/dist/environment/compose-topology.d.ts +117 -0
  73. package/dist/environment/compose-topology.js +413 -0
  74. package/dist/environment/contract.d.ts +489 -0
  75. package/dist/environment/contract.js +761 -0
  76. package/dist/environment/declaration.d.ts +14 -0
  77. package/dist/environment/declaration.js +39 -0
  78. package/dist/environment/destructive-composition.d.ts +29 -0
  79. package/dist/environment/destructive-composition.js +333 -0
  80. package/dist/environment/docker-daemon.d.ts +30 -0
  81. package/dist/environment/docker-daemon.js +78 -0
  82. package/dist/environment/docker-endpoint.d.ts +10 -0
  83. package/dist/environment/docker-endpoint.js +19 -0
  84. package/dist/environment/env-schema-observation.d.ts +46 -0
  85. package/dist/environment/env-schema-observation.js +241 -0
  86. package/dist/environment/evidence.d.ts +136 -0
  87. package/dist/environment/evidence.js +437 -0
  88. package/dist/environment/executable.d.ts +26 -0
  89. package/dist/environment/executable.js +167 -0
  90. package/dist/environment/generated-application/authorities.d.ts +5 -0
  91. package/dist/environment/generated-application/authorities.js +61 -0
  92. package/dist/environment/generated-application/bootstrap-skill.d.ts +16 -0
  93. package/dist/environment/generated-application/bootstrap-skill.js +328 -0
  94. package/dist/environment/generated-application/check.d.ts +13 -0
  95. package/dist/environment/generated-application/check.js +189 -0
  96. package/dist/environment/generated-application/custom-surface.d.ts +26 -0
  97. package/dist/environment/generated-application/custom-surface.js +289 -0
  98. package/dist/environment/generated-application/prepare.d.ts +29 -0
  99. package/dist/environment/generated-application/prepare.js +276 -0
  100. package/dist/environment/generated-application/publication.d.ts +33 -0
  101. package/dist/environment/generated-application/publication.js +547 -0
  102. package/dist/environment/generated-application/shared.d.ts +484 -0
  103. package/dist/environment/generated-application/shared.js +349 -0
  104. package/dist/environment/generated-application/surface-observation.d.ts +4 -0
  105. package/dist/environment/generated-application/surface-observation.js +69 -0
  106. package/dist/environment/generated-application/surface-outputs.d.ts +71 -0
  107. package/dist/environment/generated-application/surface-outputs.js +389 -0
  108. package/dist/environment/generated-application/surfaces.d.ts +3 -0
  109. package/dist/environment/generated-application/surfaces.js +699 -0
  110. package/dist/environment/generated-application/sync.d.ts +6 -0
  111. package/dist/environment/generated-application/sync.js +109 -0
  112. package/dist/environment/git-observation.d.ts +183 -0
  113. package/dist/environment/git-observation.js +544 -0
  114. package/dist/environment/health-composition.d.ts +11 -0
  115. package/dist/environment/health-composition.js +628 -0
  116. package/dist/environment/host-observation.d.ts +56 -0
  117. package/dist/environment/host-observation.js +52 -0
  118. package/dist/environment/identity.d.ts +48 -0
  119. package/dist/environment/identity.js +85 -0
  120. package/dist/environment/init-application.d.ts +308 -0
  121. package/dist/environment/init-application.js +697 -0
  122. package/dist/environment/install-policy-observation.d.ts +71 -0
  123. package/dist/environment/install-policy-observation.js +284 -0
  124. package/dist/environment/install-policy.d.ts +48 -0
  125. package/dist/environment/install-policy.js +96 -0
  126. package/dist/environment/instance.d.ts +33 -0
  127. package/dist/environment/instance.js +122 -0
  128. package/dist/environment/instruction-pointer.d.ts +19 -0
  129. package/dist/environment/instruction-pointer.js +144 -0
  130. package/dist/environment/lifecycle-composition.d.ts +29 -0
  131. package/dist/environment/lifecycle-composition.js +675 -0
  132. package/dist/environment/lifecycle-stage.d.ts +115 -0
  133. package/dist/environment/lifecycle-stage.js +398 -0
  134. package/dist/environment/lifecycle.d.ts +75 -0
  135. package/dist/environment/lifecycle.js +354 -0
  136. package/dist/environment/logs.d.ts +132 -0
  137. package/dist/environment/logs.js +519 -0
  138. package/dist/environment/observations.d.ts +46 -0
  139. package/dist/environment/observations.js +344 -0
  140. package/dist/environment/operator-application.d.ts +50 -0
  141. package/dist/environment/operator-application.js +155 -0
  142. package/dist/environment/ports.d.ts +182 -0
  143. package/dist/environment/ports.js +1205 -0
  144. package/dist/environment/posture-observation.d.ts +68 -0
  145. package/dist/environment/posture-observation.js +253 -0
  146. package/dist/environment/read-composition.d.ts +27 -0
  147. package/dist/environment/read-composition.js +352 -0
  148. package/dist/environment/readiness-composition.d.ts +38 -0
  149. package/dist/environment/readiness-composition.js +130 -0
  150. package/dist/environment/reclaim.d.ts +333 -0
  151. package/dist/environment/reclaim.js +1835 -0
  152. package/dist/environment/repository-identity.d.ts +13 -0
  153. package/dist/environment/repository-identity.js +108 -0
  154. package/dist/environment/runtime-measurement-record.d.ts +38 -0
  155. package/dist/environment/runtime-measurement-record.js +111 -0
  156. package/dist/environment/runtime-measurement.d.ts +27 -0
  157. package/dist/environment/runtime-measurement.js +224 -0
  158. package/dist/environment/runtime-probe-residue.d.ts +43 -0
  159. package/dist/environment/runtime-probe-residue.js +148 -0
  160. package/dist/environment/runtime-probe.d.ts +72 -0
  161. package/dist/environment/runtime-probe.js +725 -0
  162. package/dist/environment/runtime.d.ts +505 -0
  163. package/dist/environment/runtime.js +2335 -0
  164. package/dist/environment/seed.d.ts +16 -0
  165. package/dist/environment/seed.js +43 -0
  166. package/dist/environment/selftest-application.d.ts +86 -0
  167. package/dist/environment/selftest-application.js +713 -0
  168. package/dist/environment/sensitive.d.ts +3 -0
  169. package/dist/environment/sensitive.js +48 -0
  170. package/dist/environment/setup-observation.d.ts +62 -0
  171. package/dist/environment/setup-observation.js +1473 -0
  172. package/dist/environment/setup.d.ts +43 -0
  173. package/dist/environment/setup.js +176 -0
  174. package/dist/environment/sibling-instructions.d.ts +33 -0
  175. package/dist/environment/sibling-instructions.js +224 -0
  176. package/dist/environment/sibling-registration.d.ts +45 -0
  177. package/dist/environment/sibling-registration.js +538 -0
  178. package/dist/environment/state.d.ts +371 -0
  179. package/dist/environment/state.js +2013 -0
  180. package/dist/environment/templates.d.ts +140 -0
  181. package/dist/environment/templates.js +685 -0
  182. package/dist/environment/tool-registration.d.ts +9 -0
  183. package/dist/environment/tool-registration.js +96 -0
  184. package/dist/environment/upgrade-application.d.ts +19 -0
  185. package/dist/environment/upgrade-application.js +175 -0
  186. package/dist/environment-exec/execute.d.ts +102 -0
  187. package/dist/environment-exec/execute.js +1175 -0
  188. package/dist/environment-exec/libexec-program.d.ts +6 -0
  189. package/dist/environment-exec/libexec-program.js +22 -0
  190. package/dist/environment-exec/verified-digest.d.ts +4 -0
  191. package/dist/environment-exec/verified-digest.js +16 -0
  192. package/dist/exact-keys.d.ts +7 -0
  193. package/dist/exact-keys.js +15 -0
  194. package/dist/fix-catalogue.d.ts +32 -0
  195. package/dist/fix-catalogue.js +60 -0
  196. package/dist/freshness.d.ts +29 -0
  197. package/dist/freshness.js +75 -0
  198. package/dist/generated-environment/adopt.d.ts +16 -0
  199. package/dist/generated-environment/adopt.js +275 -0
  200. package/dist/generated-environment/command.d.ts +47 -0
  201. package/dist/generated-environment/command.js +75 -0
  202. package/dist/generated-environment/transaction.d.ts +186 -0
  203. package/dist/generated-environment/transaction.js +1770 -0
  204. package/dist/grading/evaluate.d.ts +71 -0
  205. package/dist/grading/evaluate.js +403 -0
  206. package/dist/grading/grade.d.ts +33 -0
  207. package/dist/grading/grade.js +57 -0
  208. package/dist/guards/evaluator.d.ts +113 -0
  209. package/dist/guards/evaluator.js +1226 -0
  210. package/dist/guards/harness-adapter.d.ts +17 -0
  211. package/dist/guards/harness-adapter.js +57 -0
  212. package/dist/guards/host.d.ts +8 -0
  213. package/dist/guards/host.js +10 -0
  214. package/dist/guards/invocation-parser.d.ts +66 -0
  215. package/dist/guards/invocation-parser.js +595 -0
  216. package/dist/guards/local-identity.d.ts +40 -0
  217. package/dist/guards/local-identity.js +123 -0
  218. package/dist/guards/policy-pin.d.ts +13 -0
  219. package/dist/guards/policy-pin.js +175 -0
  220. package/dist/guards/protected-locations.d.ts +62 -0
  221. package/dist/guards/protected-locations.js +461 -0
  222. package/dist/guards/read-only.d.ts +37 -0
  223. package/dist/guards/read-only.js +315 -0
  224. package/dist/init/command.d.ts +40 -0
  225. package/dist/init/command.js +310 -0
  226. package/dist/init/derive.d.ts +51 -0
  227. package/dist/init/derive.js +715 -0
  228. package/dist/init/draft.d.ts +138 -0
  229. package/dist/init/draft.js +1205 -0
  230. package/dist/init/render.d.ts +35 -0
  231. package/dist/init/render.js +82 -0
  232. package/dist/init/scaffold.d.ts +53 -0
  233. package/dist/init/scaffold.js +121 -0
  234. package/dist/invocation.d.ts +13 -0
  235. package/dist/invocation.js +20 -0
  236. package/dist/package-metadata.d.ts +9 -0
  237. package/dist/package-metadata.js +19 -0
  238. package/dist/readiness.d.ts +53 -0
  239. package/dist/readiness.js +88 -0
  240. package/dist/references/named.d.ts +24 -0
  241. package/dist/references/named.js +27 -0
  242. package/dist/refusal.d.ts +15 -0
  243. package/dist/refusal.js +7 -0
  244. package/dist/registry-derivation.d.ts +31 -0
  245. package/dist/registry-derivation.js +32 -0
  246. package/dist/registry-resolve/command.d.ts +47 -0
  247. package/dist/registry-resolve/command.js +226 -0
  248. package/dist/registry-resolve/registry.d.ts +68 -0
  249. package/dist/registry-resolve/registry.js +292 -0
  250. package/dist/registry-resolve/render-shell.d.ts +18 -0
  251. package/dist/registry-resolve/render-shell.js +36 -0
  252. package/dist/repair.d.ts +29 -0
  253. package/dist/repair.js +8 -0
  254. package/dist/schema-json.d.ts +1 -0
  255. package/dist/schema-json.js +177 -0
  256. package/dist/schema.d.ts +1322 -0
  257. package/dist/schema.js +934 -0
  258. package/dist/setup/command.d.ts +4 -0
  259. package/dist/setup/command.js +4 -0
  260. package/dist/sha256.d.ts +1 -0
  261. package/dist/sha256.js +4 -0
  262. package/dist/surfaces/adoption.d.ts +41 -0
  263. package/dist/surfaces/adoption.js +217 -0
  264. package/dist/surfaces/consumer-instructions.d.ts +20 -0
  265. package/dist/surfaces/consumer-instructions.js +43 -0
  266. package/dist/surfaces/contract.d.ts +42 -0
  267. package/dist/surfaces/contract.js +169 -0
  268. package/dist/surfaces/core-declarations.d.ts +24 -0
  269. package/dist/surfaces/core-declarations.js +79 -0
  270. package/dist/surfaces/development-markers.d.ts +1 -0
  271. package/dist/surfaces/development-markers.js +18 -0
  272. package/dist/surfaces/generator-adapter.d.ts +71 -0
  273. package/dist/surfaces/generator-adapter.js +809 -0
  274. package/dist/surfaces/harness-registry.d.ts +180 -0
  275. package/dist/surfaces/harness-registry.js +117 -0
  276. package/dist/surfaces/mcp.d.ts +95 -0
  277. package/dist/surfaces/mcp.js +186 -0
  278. package/dist/surfaces/merge.d.ts +30 -0
  279. package/dist/surfaces/merge.js +105 -0
  280. package/dist/surfaces/ownership.d.ts +190 -0
  281. package/dist/surfaces/ownership.js +795 -0
  282. package/dist/surfaces/projection.d.ts +136 -0
  283. package/dist/surfaces/projection.js +681 -0
  284. package/dist/surfaces/render.d.ts +118 -0
  285. package/dist/surfaces/render.js +633 -0
  286. package/dist/surfaces/restrictions.d.ts +130 -0
  287. package/dist/surfaces/restrictions.js +447 -0
  288. package/dist/surfaces/selftest.d.ts +162 -0
  289. package/dist/surfaces/selftest.js +393 -0
  290. package/dist/surfaces/skill-contract.d.ts +85 -0
  291. package/dist/surfaces/skill-contract.js +449 -0
  292. package/dist/surfaces/sync.d.ts +57 -0
  293. package/dist/surfaces/sync.js +164 -0
  294. package/dist/team-metadata.d.ts +41 -0
  295. package/dist/team-metadata.js +512 -0
  296. package/dist/testing/gates.d.ts +91 -0
  297. package/dist/testing/gates.js +182 -0
  298. package/dist/testing/hooks.d.ts +59 -0
  299. package/dist/testing/hooks.js +139 -0
  300. package/dist/testing/verdict.d.ts +29 -0
  301. package/dist/testing/verdict.js +62 -0
  302. package/dist/work-orders/application.d.ts +99 -0
  303. package/dist/work-orders/application.js +383 -0
  304. package/dist/work-orders/composition.d.ts +124 -0
  305. package/dist/work-orders/composition.js +1010 -0
  306. package/dist/work-orders/contract.d.ts +61 -0
  307. package/dist/work-orders/contract.js +122 -0
  308. package/dist/work-orders/store.d.ts +158 -0
  309. package/dist/work-orders/store.js +197 -0
  310. package/docs/README.md +65 -0
  311. package/docs/architecture.md +136 -0
  312. package/docs/capability-matrix.md +50 -0
  313. package/docs/cli-reference.md +599 -0
  314. package/docs/command-list.md +51 -0
  315. package/docs/compose-patterns.md +99 -0
  316. package/docs/concepts.md +171 -0
  317. package/docs/configuration-essentials.md +172 -0
  318. package/docs/configuration-reference.md +621 -0
  319. package/docs/environment.md +760 -0
  320. package/docs/extending.md +43 -0
  321. package/docs/faq.md +111 -0
  322. package/docs/getting-started.md +608 -0
  323. package/docs/harness-surfaces.md +433 -0
  324. package/docs/joining-a-team.md +76 -0
  325. package/docs/release-policy.md +49 -0
  326. package/docs/safety-and-autonomy.md +419 -0
  327. package/docs/troubleshooting.md +361 -0
  328. package/docs/tutorial.md +170 -0
  329. package/docs/work-and-browser.md +254 -0
  330. package/libexec/browser-operation.cjs +186 -0
  331. package/libexec/fence-marker.cjs +101 -0
  332. package/libexec/fence-marker.d.cts +24 -0
  333. package/libexec/fenced-group-runner.cjs +77 -0
  334. package/libexec/fenced-supervisor.cjs +137 -0
  335. package/libexec/generated-filesystem.cjs +731 -0
  336. package/libexec/install-filesystem.cjs +225 -0
  337. package/libexec/rulesync-generator.NOTICE.md +1112 -0
  338. package/libexec/rulesync-generator.cjs +743 -0
  339. package/libexec/surface-generator-worker.cjs +266 -0
  340. package/libexec/surface-output-reader.cjs +99 -0
  341. package/libexec/surface-scratch-cleanup.cjs +14 -0
  342. package/libexec/validate-harness-evidence.d.mts +183 -0
  343. package/libexec/validate-harness-evidence.mjs +1312 -0
  344. package/package.json +76 -1
  345. package/surfaces/README.md +33 -0
  346. package/surfaces/agents/evaluator.agent.md +76 -0
  347. package/surfaces/guards/bash-guard.sh +48 -0
  348. package/surfaces/guards/claude.adapter.json +25 -0
  349. package/surfaces/guards/codex.adapter.json +24 -0
  350. package/surfaces/guards/opencode.adapter.json +25 -0
  351. package/surfaces/references/browser.md +63 -0
  352. package/surfaces/references/test-data-and-personas.md +84 -0
  353. package/surfaces/references/testing-tiers.md +11 -0
  354. package/surfaces/skills/cockpit-browser.skill.md +54 -0
  355. package/surfaces/skills/cockpit-explain.skill.md +32 -0
  356. package/surfaces/skills/cockpit-fix.skill.md +37 -0
  357. package/surfaces/skills/cockpit-setup.skill.md +39 -0
  358. package/surfaces/skills/cockpit-work.skill.md +34 -0
  359. package/surfaces/skills/collection.json +5 -0
  360. package/surfaces/skills/procedures/explain.md +61 -0
  361. package/surfaces/skills/procedures/fix.md +81 -0
  362. package/surfaces/skills/procedures/setup.md +379 -0
  363. package/surfaces/skills/procedures/work.md +105 -0
@@ -0,0 +1,1405 @@
1
+ import { randomUUID } from 'node:crypto';
2
+ import { EVALUATOR_COMMANDS, runningEvaluatorTool } from './schema.js';
3
+ import { installedPackageMetadata } from './package-metadata.js';
4
+ import { validateTeamMetadata } from './team-metadata.js';
5
+ import { renderComposeScaffold, SCAFFOLD_BACKING_SERVICES, writeComposeScaffold } from './init/scaffold.js';
6
+ import { discoverReleaseFreshness } from './freshness.js';
7
+ import { lstat, mkdir, realpath, writeFile } from 'node:fs/promises';
8
+ import { parse as parseToml, TomlError } from 'smol-toml';
9
+ import { basename, dirname, isAbsolute, join, relative, resolve } from 'node:path';
10
+ import { createInterface } from 'node:readline/promises';
11
+ import { parseArgs, types as utilTypes } from 'node:util';
12
+ import { createInvocationRecord, EXIT_CODES } from './invocation.js';
13
+ import { commandGroups, commandSpelling, commandStages, commands, describePublicOption, internalCommands, isWorkHandler, isRecordedAnswerField, resolveOrdinaryInvocation } from './command-table.js';
14
+ import { SURFACE_TARGETS } from './surfaces/contract.js';
15
+ import { DEFAULT_SURFACE_TOOLS, GUARD_SURFACE_TARGETS, isGuardSurfaceTarget } from './surfaces/harness-registry.js';
16
+ import { renderHumanDiagnostics, renderHumanValue, renderJsonResponse } from './diagnostics.js';
17
+ import { classifyEstateObservation, draftFromEstate, observeCockpitRoot, observePublicEstate } from './init/draft.js';
18
+ import { runInitOperatorCommand } from './init/command.js';
19
+ import { composeFindings, deriveInferredFacts } from './init/derive.js';
20
+ import { runInitApplication } from './environment/init-application.js';
21
+ import { nodeAuthority, readAnchoredBatches } from './generated-environment/transaction.js';
22
+ import { registryResolveCommand, } from './registry-resolve/command.js';
23
+ import { setupCommand } from './setup/command.js';
24
+ import { beginOperatorReset as beginOperatorResetApplication, runDown as runDownApplication, runReset as runResetApplication, } from './environment/application/destructive.js';
25
+ import { runDoctor as runDoctorApplication } from './environment/application/doctor.js';
26
+ import { runGuardCommand } from './cli-guard.js';
27
+ import { runPreflight as runPreflightApplication } from './environment/application/health.js';
28
+ import { runSession as runSessionApplication, runSleep as runSleepApplication, runUp as runUpApplication, } from './environment/application/lifecycle.js';
29
+ import {} from './environment/application/lifecycle-context.js';
30
+ import { runLogs as runLogsApplication } from './environment/application/logs.js';
31
+ import { executeForegroundProcess, executePlan, executeProbe } from './environment-exec/execute.js';
32
+ import { createDestructiveCommandAdapters, selectDestructiveDaemon } from './environment/destructive-composition.js';
33
+ import { createHealthCommandAdapters } from './environment/health-composition.js';
34
+ import { inspectInitReadiness } from './environment/readiness-composition.js';
35
+ import { createLifecycleCommandAdapters } from './environment/lifecycle-composition.js';
36
+ import { isProcessStart, observeProcessStart, processHost } from './environment/claims.js';
37
+ import { createReadCommandAdapters } from './environment/read-composition.js';
38
+ import { probeInstalledRuntime, runRuntimeMeasure } from './environment/runtime-measurement.js';
39
+ import { initializeLocalGitRepository, runSiblingAdd, runSiblingRemove } from './environment/sibling-registration.js';
40
+ import { runToolChange } from './environment/tool-registration.js';
41
+ import { issueSetupRunContext, setupEnvironment } from './environment/application/setup.js';
42
+ import { consumeSetupCheckoutProjection, observeEnvironmentIgnore, observeSetupCheckout, observeSetupPath, withSetupCheckoutProjection, } from './environment/setup-observation.js';
43
+ import { RefusalError, ENVIRONMENT_CODES, errorCause, hasErrorCode, INHERITED_TOOL_ENVIRONMENT, isCanonicalTimestamp, } from './environment/contract.js';
44
+ import { refusalOutcome } from './refusal.js';
45
+ import { loadAutonomyPolicy, loadCockpitConfig, observeConfigSource, readBoundedInput } from './config/load.js';
46
+ import { policyWeakening, policyWeakeningDiagnostic } from './autonomy.js';
47
+ import { gitEnvironment, planSetup } from './environment/setup.js';
48
+ import { synchronizeInstructionPointer } from './environment/instruction-pointer.js';
49
+ import { synchronizeSiblingInstructionFiles } from './environment/sibling-instructions.js';
50
+ import { resolvedGitExecutable, resolveExecutable, resolveGitExecutable } from './environment/executable.js';
51
+ import { checkoutId, holderId, stackId } from './environment/identity.js';
52
+ import { selectStateRoot } from './environment/state.js';
53
+ import { recordLifecycleStarted } from './environment/lifecycle-stage.js';
54
+ import { planEnvironmentSeed, seedEnvironmentInstanceSync } from './environment/templates.js';
55
+ import { observeInstallPolicyLockfiles, withInstallPolicyProjection, } from './environment/install-policy-observation.js';
56
+ import { resolveInstallPolicies } from './environment/install-policy.js';
57
+ import { prepareSelectedBootstrapSurface, resumeSelectedBootstrapSurface, applySelectedBootstrapSurface, checkGeneratedEnvironment, syncTeamGeneratedEnvironment, adoptAndSyncGeneratedEnvironment, runSyncOperatorCommand, } from './generated-environment/command.js';
58
+ import { runUpgrade } from './environment/upgrade-application.js';
59
+ import { inspectCodexHookTrust } from './environment/codex-hook-trust.js';
60
+ import { runConfiguredSurfaceSelftestOutcome, runSelftestOperatorCommand, } from './environment/selftest-application.js';
61
+ import { loadOperatorPolicy, runResetOperatorApplication } from './environment/operator-application.js';
62
+ function setupDiagnostic(code, message, target) {
63
+ return { severity: 'error', code, message, target };
64
+ }
65
+ function setupRefusal(code, message, target) {
66
+ return { kind: 'refusal', status: 2, diagnostics: [setupDiagnostic(code, message, target)], result: null };
67
+ }
68
+ async function loadSetupContext(input) {
69
+ let configPath;
70
+ let searchEnvironment;
71
+ let suppliedExecutable;
72
+ try {
73
+ configPath = input.configPath;
74
+ const suppliedEnvironment = input.env;
75
+ searchEnvironment = suppliedEnvironment === undefined ? process.env : suppliedEnvironment;
76
+ suppliedExecutable = input.gitExecutable;
77
+ }
78
+ catch { // A throwing accessor on caller-supplied input makes it unreadable, which is the refusal's whole cause.
79
+ return setupRefusal(ENVIRONMENT_CODES.SETUP_PATH_TYPE_INVALID, 'setup input is not readable', 'setup');
80
+ }
81
+ const ambient = setupEnvironment(searchEnvironment);
82
+ if (!ambient) {
83
+ return setupRefusal(ENVIRONMENT_CODES.SETUP_PATH_TYPE_INVALID, 'setup environment is not valid bounded data', 'setup-environment');
84
+ }
85
+ const loaded = await loadCockpitConfig(configPath);
86
+ if (loaded.kind === 'refusal')
87
+ return loaded;
88
+ const supplied = suppliedExecutable === undefined ? undefined : resolvedGitExecutable(suppliedExecutable);
89
+ const executable = suppliedExecutable === undefined
90
+ ? await resolveGitExecutable(searchEnvironment)
91
+ : supplied
92
+ ? { state: 'available', value: supplied }
93
+ : { state: 'unavailable', reason: 'Git executable identity is invalid' };
94
+ if (executable.state === 'unavailable') {
95
+ return setupRefusal(ENVIRONMENT_CODES.SETUP_PATH_TYPE_INVALID, executable.reason, loaded.result.source.path);
96
+ }
97
+ const observedAt = new Date().toISOString();
98
+ const setupCheckout = await observeSetupCheckout({
99
+ source: loaded.result.source,
100
+ cockpitName: loaded.result.config.cockpit.name,
101
+ observedAt,
102
+ env: ambient,
103
+ gitExecutable: executable.value,
104
+ });
105
+ const checkout = await withSetupCheckoutProjection(setupCheckout, {
106
+ source: loaded.result.source,
107
+ cockpitName: loaded.result.config.cockpit.name,
108
+ evaluatedAt: Date.parse(observedAt),
109
+ }, consumeSetupCheckoutProjection);
110
+ if (!checkout) {
111
+ const reason = setupCheckout.state === 'failed'
112
+ ? setupCheckout.reason
113
+ : 'setup checkout evidence is unavailable';
114
+ return setupRefusal(ENVIRONMENT_CODES.SETUP_PATH_TYPE_INVALID, reason, loaded.result.source.path);
115
+ }
116
+ if (process.platform !== 'darwin' && process.platform !== 'linux') {
117
+ return setupRefusal(ENVIRONMENT_CODES.STATE_ROOT_UNAVAILABLE, 'state root is unavailable on this platform', process.platform);
118
+ }
119
+ const selected = await selectStateRoot({
120
+ platform: process.platform,
121
+ home: ambient.HOME,
122
+ xdgStateHome: ambient.XDG_STATE_HOME,
123
+ checkoutRoots: [checkout.currentCheckoutPath, checkout.mainCheckoutPath],
124
+ cockpitId: checkout.cockpitId,
125
+ });
126
+ if (selected.kind !== 'complete') {
127
+ return setupRefusal(selected.kind === 'refusal' ? selected.code : ENVIRONMENT_CODES.STATE_ROOT_UNAVAILABLE, selected.kind === 'refusal' ? selected.message : 'state root is unavailable', selected.kind === 'refusal' ? selected.target : loaded.result.source.path);
128
+ }
129
+ const currentStackId = stackId({
130
+ cockpitId: checkout.cockpitId,
131
+ checkoutId: checkout.checkoutId,
132
+ variant: { kind: 'shared' },
133
+ });
134
+ const sessionId = randomUUID();
135
+ const confirmedSetupCheckout = await observeSetupCheckout({
136
+ source: loaded.result.source,
137
+ cockpitName: loaded.result.config.cockpit.name,
138
+ observedAt,
139
+ env: ambient,
140
+ gitExecutable: executable.value,
141
+ });
142
+ const issued = await withSetupCheckoutProjection(confirmedSetupCheckout, {
143
+ source: loaded.result.source,
144
+ cockpitName: loaded.result.config.cockpit.name,
145
+ evaluatedAt: Date.parse(observedAt),
146
+ }, (projection) => issueSetupRunContext({
147
+ checkout: projection,
148
+ configuration: loaded.result,
149
+ env: gitEnvironment(ambient),
150
+ gitExecutable: executable.value,
151
+ namespace: selected.value,
152
+ observedAt,
153
+ currentCheckoutPath: checkout.currentCheckoutPath,
154
+ mainCheckoutPath: checkout.mainCheckoutPath,
155
+ cockpitId: checkout.cockpitId,
156
+ checkoutId: checkout.checkoutId,
157
+ stackId: currentStackId,
158
+ holderId: holderId({ stackId: currentStackId, checkoutId: checkout.checkoutId, sessionId }),
159
+ sessionId,
160
+ writeInstructionPointers: input.writeInstructionPointers === true,
161
+ writeInstructionFiles: input.writeInstructionFiles === true,
162
+ }));
163
+ if (!issued) {
164
+ const reason = confirmedSetupCheckout.state === 'failed'
165
+ ? confirmedSetupCheckout.reason
166
+ : 'confirmed setup checkout evidence is unavailable';
167
+ return setupRefusal(ENVIRONMENT_CODES.SETUP_PATH_TYPE_INVALID, reason, loaded.result.source.path);
168
+ }
169
+ return issued.kind === 'complete' ? { ...issued, diagnostics: loaded.diagnostics } : issued;
170
+ }
171
+ const defaultSetupAdapters = {
172
+ load: loadSetupContext,
173
+ observedAt: () => new Date().toISOString(),
174
+ observeGit: ({ context, sibling }) => observeSetupPath(resolve(context.siblingsRoot, sibling.dir), context.siblingsRoot, context.env, context.gitExecutable),
175
+ planGit: ({ context, sibling, observation }) => planSetup({
176
+ siblingsRoot: context.siblingsRoot,
177
+ sibling,
178
+ observation,
179
+ env: context.env,
180
+ gitExecutable: context.gitExecutable,
181
+ }),
182
+ syncInstructionPointer: synchronizeInstructionPointer,
183
+ syncInstructionFiles: synchronizeSiblingInstructionFiles,
184
+ execute: executePlan,
185
+ observeSource: observeConfigSource,
186
+ recordStarted: recordLifecycleStarted,
187
+ withIgnore: async ({ context, siblingIndex, sibling, environmentFileIndex, environmentFile, observedAt }, use) => {
188
+ const siblingRoot = resolve(context.siblingsRoot, sibling.dir);
189
+ const siblingCheckoutId = checkoutId({ checkoutPath: siblingRoot });
190
+ return observeEnvironmentIgnore({
191
+ source: context.source,
192
+ siblingIndex,
193
+ siblingSlug: sibling.slug,
194
+ mainCheckoutPath: context.mainCheckoutPath,
195
+ siblingsRoot: context.siblingsRoot,
196
+ siblingRoot,
197
+ checkoutId: siblingCheckoutId,
198
+ environmentFileIndex,
199
+ environmentFile,
200
+ observedAt,
201
+ env: context.env,
202
+ gitExecutable: context.gitExecutable,
203
+ }, use);
204
+ },
205
+ planSeed: ({ context, sibling, environmentFile }) => planEnvironmentSeed({
206
+ siblingRoot: resolve(context.siblingsRoot, sibling.dir),
207
+ environmentFile,
208
+ }),
209
+ seed: seedEnvironmentInstanceSync,
210
+ observePolicy: ({ context, siblingIndex, sibling, checkoutRoot, checkoutId, observedAt }) => observeInstallPolicyLockfiles({
211
+ source: context.source,
212
+ policies: context.policyCatalog,
213
+ siblingIndex,
214
+ siblingSlug: sibling.slug,
215
+ checkoutRoot,
216
+ checkoutId,
217
+ observedAt,
218
+ }),
219
+ withPolicy: (observation, { context, siblingIndex, sibling, checkoutRoot, checkoutId, observedAt }, use) => withInstallPolicyProjection(observation, {
220
+ source: context.source,
221
+ policies: context.policyCatalog,
222
+ siblingIndex,
223
+ siblingSlug: sibling.slug,
224
+ checkoutRoot,
225
+ checkoutId,
226
+ evaluatedAt: Date.parse(observedAt),
227
+ }, use),
228
+ resolvePolicy: ({ context, siblingIndex, sibling, checkoutRoot, checkoutId, projection }) => resolveInstallPolicies({
229
+ source: context.source,
230
+ policies: context.policyCatalog,
231
+ siblingIndex,
232
+ sibling,
233
+ checkoutRoot,
234
+ checkoutId,
235
+ projection,
236
+ }),
237
+ };
238
+ // The budget for one runtime query or command step (a Docker listing, an inspection, a Compose
239
+ // config read). Scoped to the stack's own resources, those finish in well under a second even on
240
+ // a daemon running a hundred other containers, so ten seconds is ample headroom while a hung
241
+ // daemon still fails the step closed quickly. Service starts are bounded by cold_start_seconds instead.
242
+ const RUNTIME_STEP_TIMEOUT_MS = 10_000;
243
+ // down and reset delete, so the daemon is fixed once, before any adapter exists, and every step of
244
+ // the command addresses that one daemon.
245
+ async function destructiveAdapters(configPath, runtime, sessionId) {
246
+ const env = runtime.env ?? process.env;
247
+ const daemon = await selectDestructiveDaemon(env, RUNTIME_STEP_TIMEOUT_MS, executeProbe);
248
+ return daemon.kind === 'refusal' ? daemon.adapters : createDestructiveCommandAdapters({
249
+ configPath,
250
+ env,
251
+ sessionId,
252
+ timeoutMs: RUNTIME_STEP_TIMEOUT_MS,
253
+ dockerContext: daemon.dockerContext,
254
+ }, executeProbe);
255
+ }
256
+ function readAdapters(configPath, runtime, sessionId) {
257
+ return createReadCommandAdapters({
258
+ configPath,
259
+ env: runtime.env ?? process.env,
260
+ sessionId,
261
+ timeoutMs: RUNTIME_STEP_TIMEOUT_MS,
262
+ ...(runtime.signal ? { signal: runtime.signal } : {}),
263
+ }, executeProbe);
264
+ }
265
+ // Each return is a literal, so the diagnostic inventory captures the fix beside its code.
266
+ function unsatisfiedWorkDiagnostic(operation, message) {
267
+ if (operation === 'test-run')
268
+ return { severity: 'error', message,
269
+ code: 'TEST_GATE_UNSATISFIED',
270
+ fix: 'the message gives the verdict and its reasons. FAIL: a check a stage ran failed, so fix the product. DEGRADED: start the stack with npx --no cockpit up --config <your cockpit.toml> for a stage that needs it, make a stage command that could not start runnable from the cockpit root, raise testing.stages[].timeout_seconds for a stage that timed out, or declare testing.stages[].select.<kind> for a --select a stage lacks. VOID: stop changing the checkouts and run the gate again. PARTIAL: only the complete gate can PASS, so run it without --select or --mode staged' };
271
+ if (operation === 'work-check')
272
+ return { severity: 'error', message,
273
+ code: 'WORK_CHECK_FAILED',
274
+ fix: 'read result.results: fix the product for a check that failed, make one that did not run runnable from the cockpit root, then rerun npx --no cockpit work check --config <your cockpit.toml> --work-order <id>' };
275
+ if (operation === 'work-verify')
276
+ return { severity: 'error', message,
277
+ code: 'WORK_VERIFY_FAILED',
278
+ fix: 'for an order changed since acceptance, run npx --no cockpit work accept --config <your cockpit.toml> --work-order <id> or revert the change; for a done order without a passing grade, regrade it with work grade' };
279
+ if (operation === 'grade-criteria')
280
+ return { severity: 'error', message,
281
+ code: 'WORK_GRADE_UNSATISFIED',
282
+ fix: 'read result.criteria, result.checks and result.outOfScope: fix the product for an unmet or unclear criterion, make a failed check pass, move or revert a change outside scope, then grade again with npx --no cockpit grade --config <your cockpit.toml> --criteria <file>' };
283
+ return { severity: 'error', message,
284
+ code: 'WORK_GRADE_UNSATISFIED',
285
+ fix: 'read result.criteria, result.checks and result.outOfScope: fix the product for an unmet or unclear criterion, make a failed check pass, move or revert a change outside scope, then grade again with npx --no cockpit work grade --config <your cockpit.toml> --work-order <id>' };
286
+ }
287
+ // A person gate binds what the person reads to the digest they type, so it never completes for an agent.
288
+ function personRequired(handler, prompt) {
289
+ return { artifact: null, destination: null, result: { prompt }, status: 2, diagnostics: [{ severity: 'error', message: `${handler.slice('work-'.length)} is a person's step under [work.gates] in cockpit.toml, and this run has no person at a terminal`,
290
+ code: 'WORK_PERSON_REQUIRED',
291
+ fix: 'stop and ask a person to run the same command with --interactive in a terminal outside CI, read what it shows, and type the digest; never type it for them' }] };
292
+ }
293
+ // A grade by the same tool that did the work is weaker than one by another tool, so it is never silent.
294
+ function sameToolGradeWarnings(value, env) {
295
+ const evaluator = value?.evaluator;
296
+ if (!evaluator?.fallbacks?.length || evaluator.tool !== runningEvaluatorTool(env))
297
+ return [];
298
+ return [{ severity: 'warning', code: 'EVALUATOR_FELL_BACK_TO_RUNNING_TOOL',
299
+ message: `graded by ${EVALUATOR_COMMANDS[evaluator.tool]}, the tool running this command, because the others were passed over: ${evaluator.fallbacks.map(row => `${row.tool}: ${row.message}`).join('; ')}`,
300
+ fix: 'install and sign in to the passed-over tool so another tool grades the work, or name the grader you accept with --evaluator <tool>' }];
301
+ }
302
+ function workCommandOutcome(outcome, env) {
303
+ if (outcome.kind === 'complete')
304
+ return { status: 0, result: outcome.value, diagnostics: sameToolGradeWarnings(outcome.value, env) };
305
+ if (outcome.kind === 'unsatisfied')
306
+ return { status: 2, result: outcome.value, diagnostics: [unsatisfiedWorkDiagnostic(outcome.operation, outcome.reason), ...sameToolGradeWarnings(outcome.value, env)] };
307
+ return usageOutcome('WORK_APPLICATION_REFUSED', outcome.kind === 'refusal' ? outcome.reason : 'this step needs a person at a terminal');
308
+ }
309
+ async function configuredWorkApplication(input, runtime) {
310
+ const { createConfiguredWorkApplicationComposition } = await import('./work-orders/composition.js');
311
+ const { runWorkApplication } = await import('./work-orders/application.js');
312
+ const configured = await createConfiguredWorkApplicationComposition({ configPath: resolve(input.configPath), env: runtime.env ?? process.env }, input.evaluator ? { evaluator: input.evaluator } : {});
313
+ return configured.kind === 'refusal' ? { kind: 'refusal', operation: input.request.operation, reason: configured.reason }
314
+ : runWorkApplication(input.request, configured.composition);
315
+ }
316
+ const SESSION_ID = /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/;
317
+ function inheritedSessionId(env) {
318
+ const value = env.COCKPIT_SESSION_ID;
319
+ return value === undefined ? undefined : SESSION_ID.test(value) ? value : null;
320
+ }
321
+ const SESSION_PROCESS = /^([1-9][0-9]{0,9})@(\S+)$/;
322
+ // A `cockpit session` wrapper exports its own pid and start identity so that an `up` run inside it,
323
+ // after a `sleep` there dropped the session's hold, ties the hold it re-adds to the wrapper and
324
+ // not to itself. The variable only names a process: the child reads that process's start identity
325
+ // again and records the hold only when it matches, so a stale or invented value cannot tie a hold
326
+ // to a process the wrapper is not. A hold carries only its own holder's process, and dead-holder
327
+ // release removes only the holder whose recorded process is gone, so the most a forged value can
328
+ // do is end the forger's own hold early; reaching another session's hold needs its session ID,
329
+ // which already allows `sleep` on it.
330
+ async function sessionChildHolderProcess(env) {
331
+ const value = env.COCKPIT_SESSION_PROCESS;
332
+ if (value === undefined)
333
+ return {};
334
+ if (env.COCKPIT_SESSION_ID === undefined)
335
+ return { refusal: usageOutcome('CLI_USAGE_INVALID', 'COCKPIT_SESSION_PROCESS is set without COCKPIT_SESSION_ID; only a cockpit session wrapper sets it', 'COCKPIT_SESSION_PROCESS') };
336
+ const match = SESSION_PROCESS.exec(value);
337
+ const pid = Number(match?.[1]);
338
+ if (!match || pid > 2_147_483_647 || !isProcessStart(match[2]))
339
+ return { refusal: usageOutcome('CLI_USAGE_INVALID', 'COCKPIT_SESSION_PROCESS must be <pid>@<start identity> as a cockpit session wrapper sets it', 'COCKPIT_SESSION_PROCESS') };
340
+ const observed = await observeProcessStart(pid, processHost(executeProbe));
341
+ // As for the wrapper's own hold, a start that cannot be confirmed leaves a pid-only hold, which
342
+ // is released only once that pid is gone.
343
+ if (observed.state === 'unknown')
344
+ return { holderProcess: { pid } };
345
+ if (observed.state === 'absent' || observed.start !== match[2])
346
+ return { refusal: usageOutcome('CLI_USAGE_INVALID', `the cockpit session that set COCKPIT_SESSION_ID has ended (its process ${pid} is gone); start a new cockpit session or run this outside it`, 'COCKPIT_SESSION_PROCESS') };
347
+ return { holderProcess: { pid, processStart: match[2] } };
348
+ }
349
+ function concreteEnvironment(env) {
350
+ return Object.fromEntries(Object.entries(env).filter((entry) => entry[1] !== undefined));
351
+ }
352
+ function teamMetadataOutcome(result) {
353
+ if (result.kind === 'complete')
354
+ return undefined;
355
+ return refusalOutcome(result.code, result.message, result.target);
356
+ }
357
+ function defaultHandlers(runtime) {
358
+ const env = runtime.env ?? process.env;
359
+ const inherited = inheritedSessionId(env);
360
+ const ordinarySessionId = inherited === undefined ? 'cockpit-cli' : inherited;
361
+ const lifecycleAdapters = (configPath, sessionId, holderProcess) => createLifecycleCommandAdapters({
362
+ configPath,
363
+ env,
364
+ sessionId,
365
+ timeoutMs: RUNTIME_STEP_TIMEOUT_MS,
366
+ ...(holderProcess === undefined ? {} : { holderProcess }),
367
+ }, executeProbe);
368
+ const healthAdapters = (configPath) => createHealthCommandAdapters({
369
+ configPath,
370
+ env,
371
+ timeoutMs: RUNTIME_STEP_TIMEOUT_MS,
372
+ }, executeProbe);
373
+ const workHandlers = {};
374
+ for (const command of commands)
375
+ if (isWorkHandler(command.handler)) {
376
+ workHandlers[command.handler] = async (input) => workCommandOutcome(await configuredWorkApplication(input, runtime), runtime.env ?? process.env);
377
+ }
378
+ return {
379
+ ...workHandlers,
380
+ init: input => runInitApplication(input, {
381
+ now: () => new Date().toISOString(),
382
+ prepareBootstrap: prepareSelectedBootstrapSurface,
383
+ resumeBootstrap: resumeSelectedBootstrapSurface,
384
+ applyBootstrap: applySelectedBootstrapSurface,
385
+ document: draftFromEstate,
386
+ classifyEstate: input => classifyEstateObservation(input, { observe: observePublicEstate }),
387
+ observeRoot: observeCockpitRoot,
388
+ infer: deriveInferredFacts,
389
+ composeFindings,
390
+ inspectReadiness: input => inspectInitReadiness(input, { env, timeoutMs: RUNTIME_STEP_TIMEOUT_MS, execute: executeProbe }),
391
+ observeDestination: async ({ path, byteLimit }) => {
392
+ const node = await nodeAuthority();
393
+ const read = node ? await readAnchoredBatches(dirname(path), [
394
+ { path: basename(path), action: 'read-entry', maxBytes: byteLimit },
395
+ ], node, { execute: executeProbe }) : undefined;
396
+ const entry = read?.kind === 'complete' ? read.results[0] : undefined;
397
+ if (entry?.kind === 'missing')
398
+ return { kind: 'absent' };
399
+ if (entry?.kind === 'file')
400
+ return { kind: 'available', sha256: entry.sha256 };
401
+ return { kind: 'refusal', diagnostics: [{ severity: 'error', code: 'INIT_DESTINATION_UNAVAILABLE',
402
+ message: 'cockpit.toml could not be authenticated as an absent or bounded regular file', target: path }] };
403
+ },
404
+ validateConfig: loadCockpitConfig,
405
+ synchronize: syncTeamGeneratedEnvironment,
406
+ check: async (input) => {
407
+ const metadata = teamMetadataOutcome(await validateTeamMetadata(input.configPath));
408
+ return metadata ?? checkGeneratedEnvironment(input);
409
+ },
410
+ preflight: ({ configPath }) => runPreflightApplication({ configPath }, healthAdapters(configPath)),
411
+ }),
412
+ 'registry-resolve': registryResolveCommand,
413
+ setup: (input) => setupCommand({ ...input, env }, defaultSetupAdapters),
414
+ sync: async ({ authorizeStaleRemoval, adopt, ...input }) => {
415
+ if (adopt)
416
+ return adoptAndSyncGeneratedEnvironment({ configPath: input.configPath, adopt });
417
+ return syncTeamGeneratedEnvironment(input, authorizeStaleRemoval ? { authorizeStaleRemoval } : {});
418
+ },
419
+ check: async (input) => {
420
+ const loaded = await loadCockpitConfig(input.configPath);
421
+ if (loaded.kind !== 'complete')
422
+ return loaded;
423
+ const metadata = teamMetadataOutcome(await validateTeamMetadata(input.configPath));
424
+ if (metadata)
425
+ return metadata;
426
+ // A weakening sync would refuse is a failing check, so an agent's edit cannot sit unreported.
427
+ const policy = await loadAutonomyPolicy(input.configPath);
428
+ const weakened = 'recorded' in policy && policy.recorded ? policyWeakening(policy.recorded, policy.current) : [];
429
+ // Codex hook trust concerns a Codex user only; a leftover hooks.json of an unlisted Codex is stale, which check reports.
430
+ const [outcome, hookTrust] = await Promise.all([checkGeneratedEnvironment(input),
431
+ loaded.result.config.surfaces.tools.includes('codexcli')
432
+ ? inspectCodexHookTrust({ root: dirname(resolve(input.configPath)), env }) : Promise.resolve([])]);
433
+ if (weakened.length)
434
+ return { kind: 'refusal', status: 2, result: null,
435
+ diagnostics: [policyWeakeningDiagnostic(weakened), ...outcome.diagnostics, ...hookTrust] };
436
+ return { ...outcome, diagnostics: [...outcome.diagnostics, ...hookTrust], result: outcome.report
437
+ ? outcome.kind === 'complete' ? { ...outcome.result, currentness: outcome.report } : outcome.report
438
+ : outcome.result };
439
+ },
440
+ selftest: runConfiguredSurfaceSelftestOutcome,
441
+ upgrade: input => runUpgrade({ ...input, env: concreteEnvironment(env) }, { execute: executeProbe, executeForeground: executeForegroundProcess }),
442
+ up: async (input) => {
443
+ const sessionId = ordinarySessionId;
444
+ if (sessionId === null)
445
+ return usageOutcome('CLI_USAGE_INVALID', 'COCKPIT_SESSION_ID must be a canonical UUID', 'COCKPIT_SESSION_ID');
446
+ const wrapper = await sessionChildHolderProcess(env);
447
+ if (wrapper.refusal)
448
+ return wrapper.refusal;
449
+ const configPath = resolve(input.configPath);
450
+ return runUpApplication({ configPath }, lifecycleAdapters(configPath, sessionId, wrapper.holderProcess));
451
+ },
452
+ sleep: (input) => {
453
+ const sessionId = ordinarySessionId;
454
+ if (sessionId === null)
455
+ return Promise.resolve(usageOutcome('CLI_USAGE_INVALID', 'COCKPIT_SESSION_ID must be a canonical UUID', 'COCKPIT_SESSION_ID'));
456
+ const configPath = resolve(input.configPath);
457
+ return runSleepApplication({ configPath }, lifecycleAdapters(configPath, sessionId));
458
+ },
459
+ session: async (input) => {
460
+ if (inherited === null)
461
+ return usageOutcome('CLI_USAGE_INVALID', 'COCKPIT_SESSION_ID must be a canonical UUID', 'COCKPIT_SESSION_ID');
462
+ const configPath = resolve(input.configPath);
463
+ const sessionId = input.sessionId;
464
+ // Without its start identity the hold is released only once the pid is gone: while the
465
+ // pid runs it may since name another process, so the session runs with that weaker hold.
466
+ const started = await observeProcessStart(process.pid, processHost(executeProbe));
467
+ const sessionEnvironment = { ...env, COCKPIT_SESSION_ID: sessionId,
468
+ COCKPIT_SESSION_PROCESS: started.state === 'present' ? `${process.pid}@${started.start}` : undefined };
469
+ const childEnv = concreteEnvironment(sessionEnvironment);
470
+ const adapters = createLifecycleCommandAdapters({
471
+ configPath,
472
+ env: sessionEnvironment,
473
+ sessionId,
474
+ timeoutMs: RUNTIME_STEP_TIMEOUT_MS,
475
+ holderProcess: { pid: process.pid, ...(started.state === 'present' ? { processStart: started.start } : {}) },
476
+ }, executeProbe);
477
+ return runSessionApplication({ ...input, configPath, cwd: process.cwd(), env: childEnv }, {
478
+ up: (request) => runUpApplication(request, adapters),
479
+ sleep: (request) => runSleepApplication(request, adapters),
480
+ resolveExecutable: (name) => resolveExecutable(name, sessionEnvironment),
481
+ executeForeground: executeForegroundProcess,
482
+ ...(runtime.signal ? { signal: runtime.signal } : {}),
483
+ });
484
+ },
485
+ down: async (input) => {
486
+ if (ordinarySessionId === null)
487
+ return usageOutcome('CLI_USAGE_INVALID', 'COCKPIT_SESSION_ID must be a canonical UUID', 'COCKPIT_SESSION_ID');
488
+ const configPath = resolve(input.configPath);
489
+ return runDownApplication({ configPath }, await destructiveAdapters(configPath, runtime, ordinarySessionId));
490
+ },
491
+ reset: async (input) => {
492
+ if (ordinarySessionId === null)
493
+ return usageOutcome('CLI_USAGE_INVALID', 'COCKPIT_SESSION_ID must be a canonical UUID', 'COCKPIT_SESSION_ID');
494
+ const configPath = resolve(input.configPath);
495
+ return runResetApplication({ configPath }, await destructiveAdapters(configPath, runtime, ordinarySessionId));
496
+ },
497
+ preflight: (input) => {
498
+ const configPath = resolve(input.configPath);
499
+ return runPreflightApplication({ configPath }, healthAdapters(configPath));
500
+ },
501
+ doctor: (input) => {
502
+ const configPath = resolve(input.configPath);
503
+ return runDoctorApplication({ configPath }, {
504
+ ...healthAdapters(configPath),
505
+ discoverReleaseFreshness: () => discoverReleaseFreshness({ verb: 'doctor', env,
506
+ ...(runtime.createReleaseMetadataTransport ? { createTransport: runtime.createReleaseMetadataTransport } : {}) }),
507
+ });
508
+ },
509
+ runtime: (input) => runRuntimeMeasure({ ...input, env }, { execute: executeProbe, probe: probeInstalledRuntime }),
510
+ sibling: (request) => request.operation === 'add'
511
+ ? runSiblingAdd({ ...request.input, env }, { execute: executeProbe })
512
+ : runSiblingRemove({ ...request.input, env }),
513
+ tool: runToolChange,
514
+ logs: (input) => {
515
+ if (ordinarySessionId === null)
516
+ return Promise.resolve(usageOutcome('CLI_USAGE_INVALID', 'COCKPIT_SESSION_ID must be a canonical UUID', 'COCKPIT_SESSION_ID'));
517
+ const configPath = resolve(input.configPath);
518
+ return runLogsApplication({ ...input, configPath }, readAdapters(configPath, runtime, ordinarySessionId).logs);
519
+ },
520
+ };
521
+ }
522
+ function commandLines(rows, width = Math.max(...rows.map(row => row.name.length))) {
523
+ return rows.map(row => ` ${row.name.padEnd(width)} ${row.summary}`);
524
+ }
525
+ function groupMembers(group) {
526
+ return commands.filter(command => command.name.startsWith(`${group} `));
527
+ }
528
+ // Grouped by stage, with each group as one entry, so a newcomer reads the entry points rather than
529
+ // every subcommand; `cockpit <group> --help` lists the rest.
530
+ function renderHelp() {
531
+ const entries = commands.flatMap(command => {
532
+ const group = commandGroups.find(row => command.name.startsWith(`${row.name} `));
533
+ if (!group)
534
+ return [{ stage: command.stage, name: command.name, summary: command.summary }];
535
+ if (groupMembers(group.name)[0] !== command)
536
+ return [];
537
+ return [{ stage: command.stage, name: group.name, summary: group.summary }];
538
+ });
539
+ const width = Math.max(...entries.map(entry => entry.name.length));
540
+ const sections = commandStages.map(stage => [`${stage.title}:`,
541
+ ...commandLines(entries.filter(entry => entry.stage === stage.id), width)].join('\n'));
542
+ return ['Usage: cockpit <command> [options]', '', sections.join('\n\n'), '',
543
+ `Run 'cockpit <command> --help' for its options, and ${commandGroups.map(group => `'cockpit ${group.name} --help'`).join(' or ')} for its subcommands.`, ''].join('\n');
544
+ }
545
+ function renderGroupHelp(group) {
546
+ const members = groupMembers(group.name);
547
+ return [`Usage: cockpit ${group.name} <subcommand> [options]`, '', group.summary,
548
+ '', 'Subcommands:', ...commandLines(members.map(member => ({ name: member.name.slice(group.name.length + 1), summary: member.summary }))), '',
549
+ `Run 'cockpit ${group.name} <subcommand> --help' for its options.`, ''].join('\n');
550
+ }
551
+ function renderCommandHelp(definition) {
552
+ const optionLines = Object.entries(definition.options).map(([name, option]) => {
553
+ const usage = option.type === 'string' ? `--${name} <value>` : `--${name}`;
554
+ const fallback = option.default === undefined || option.default === false ? '' : ` (default: ${String(option.default)})`;
555
+ return [usage, `${describePublicOption(name)}${fallback}`];
556
+ });
557
+ const width = Math.max(...optionLines.map(([usage]) => usage.length), '-h, --help'.length);
558
+ return [`Usage: cockpit ${definition.name} ${definition.usage ?? '[options]'}`, '', definition.summary,
559
+ '', 'Options:',
560
+ ...optionLines.map(([usage, note]) => ` ${usage.padEnd(width)} ${note}`.trimEnd()),
561
+ ` ${'-h, --help'.padEnd(width)} Show this help`, ''].join('\n');
562
+ }
563
+ function renderVersion() {
564
+ return `${installedPackageMetadata.name} ${installedPackageMetadata.version} (Node ${installedPackageMetadata.engines.node}; source ${installedPackageMetadata.cockpitSourceRevision})\n`;
565
+ }
566
+ const missingConfig = () => usageOutcome('CLI_USAGE_INVALID', 'provide --config <file>', '--config');
567
+ function usageOutcome(code, message, target) {
568
+ return {
569
+ artifact: null,
570
+ destination: null,
571
+ diagnostics: [{ severity: 'error', code, message, target }],
572
+ result: null,
573
+ status: 2,
574
+ };
575
+ }
576
+ async function executeOutcome(outcome) {
577
+ if (!outcome.artifact || !outcome.destination)
578
+ return outcome;
579
+ try {
580
+ await writeFile(outcome.destination, outcome.artifact, { encoding: 'utf8', flag: 'wx' });
581
+ return outcome;
582
+ }
583
+ catch (error) {
584
+ const code = error.code;
585
+ const diagnostic = code === 'EEXIST'
586
+ ? {
587
+ severity: 'error',
588
+ code: 'OUTPUT_EXISTS',
589
+ message: 'the output path already exists and was not changed',
590
+ target: outcome.destination,
591
+ fix: 'choose a new path; replacement requires a future explicit option',
592
+ }
593
+ : {
594
+ severity: 'error',
595
+ code: 'OUTPUT_WRITE_FAILED',
596
+ message: `the resolved output could not be created (${code ?? 'unknown I/O error'})`,
597
+ target: outcome.destination,
598
+ };
599
+ return {
600
+ artifact: null,
601
+ destination: null,
602
+ diagnostics: [...outcome.diagnostics, diagnostic],
603
+ result: null,
604
+ status: 2,
605
+ };
606
+ }
607
+ }
608
+ function optionCount(args, name) {
609
+ const option = `--${name}`;
610
+ return args.filter((arg) => arg === option || arg.startsWith(`${option}=`)).length;
611
+ }
612
+ const ANSWERS_FILE_BYTE_LIMIT = 262_144;
613
+ const PARSE_ARGS_CODES = ['ERR_PARSE_ARGS_UNKNOWN_OPTION', 'ERR_PARSE_ARGS_INVALID_OPTION_VALUE', 'ERR_PARSE_ARGS_UNEXPECTED_POSITIONAL'];
614
+ /** The only path init creates outside its root: the previewed dedicated repository, never its parents. */
615
+ async function createDedicatedCockpitRoot(path, env) {
616
+ try {
617
+ await mkdir(path, { mode: 0o755 });
618
+ }
619
+ catch (error) {
620
+ if (!hasErrorCode(error, 'EEXIST'))
621
+ throw error;
622
+ }
623
+ const status = await lstat(path);
624
+ if (!status.isDirectory() || status.isSymbolicLink() || await realpath(path) !== path) {
625
+ throw new RefusalError(`${path} exists and is not a plain directory`);
626
+ }
627
+ const initialised = await initializeLocalGitRepository({ path, env }, { execute: executeProbe });
628
+ if (initialised.kind === 'refusal')
629
+ throw new RefusalError(initialised.reason);
630
+ }
631
+ /** Reads the consent digests and answers file of an unattended init; absent flags mean a terminal run. */
632
+ async function decodeUnattendedInit(values) {
633
+ const { answers: answersPath, 'accept-preview': previewSha256, 'accept-defaults': defaultsSha256 } = values;
634
+ if (answersPath === undefined && previewSha256 === undefined && defaultsSha256 === undefined)
635
+ return {};
636
+ if (values.interactive)
637
+ return usageOutcome('CLI_USAGE_INVALID', 'init --interactive asks a person; --answers, --accept-preview and --accept-defaults are for a caller without a terminal', '--interactive');
638
+ if (previewSha256 === undefined)
639
+ return usageOutcome('CLI_USAGE_INVALID', '--answers and --accept-defaults require --accept-preview: run init --json first and pass the preview digest it prints', '--accept-preview');
640
+ for (const [name, digest] of [['--accept-preview', previewSha256], ['--accept-defaults', defaultsSha256]]) {
641
+ if (digest !== undefined && !/^[a-f0-9]{64}$/.test(digest))
642
+ return usageOutcome('CLI_USAGE_INVALID', `${name} must be a lowercase sha256 digest printed by a pending init run`, name);
643
+ }
644
+ let answers = {};
645
+ if (answersPath !== undefined) {
646
+ const path = resolve(answersPath);
647
+ try {
648
+ const read = await readBoundedInput(path);
649
+ if (read.kind === 'too-large' || read.bytes.byteLength > ANSWERS_FILE_BYTE_LIMIT)
650
+ return usageOutcome('CLI_USAGE_INVALID', `--answers file exceeds ${ANSWERS_FILE_BYTE_LIMIT} bytes; replace it with a plain TOML file within that limit and retry`, '--answers');
651
+ let text;
652
+ try {
653
+ text = new TextDecoder('utf-8', { fatal: true }).decode(read.bytes);
654
+ }
655
+ catch { // Fatal decoding throws only for invalid UTF-8, which the message names.
656
+ return usageOutcome('CLI_USAGE_INVALID', `--answers ${path} is not valid UTF-8; replace it with a plain UTF-8 TOML file and retry`, '--answers');
657
+ }
658
+ answers = parseToml(text);
659
+ }
660
+ catch (error) {
661
+ const cause = error instanceof TomlError ? `invalid TOML at line ${error.line}, column ${error.column}` : errorCause(error);
662
+ return usageOutcome('CLI_USAGE_INVALID', `--answers ${path} could not be read as one bounded regular TOML file (${cause}); replace it with a plain TOML file and retry`, '--answers');
663
+ }
664
+ }
665
+ return { value: { previewSha256, answers, ...(defaultsSha256 === undefined ? {} : { defaultsSha256 }) } };
666
+ }
667
+ // A one-time write of a plain compose.yml the user then owns; it is not part of the init interview.
668
+ async function decodeComposeScaffold(root, extraRoots, values) {
669
+ const allowed = new Set(['scaffold-compose', 'command', 'port', 'with', 'json']);
670
+ const stray = Object.keys(values).find(name => !allowed.has(name) && values[name] !== undefined && values[name] !== false);
671
+ if (!root || extraRoots.length || stray)
672
+ return usageOutcome('CLI_USAGE_INVALID', `init --scaffold-compose takes one root and only --command, --port, --with and --json${stray ? `, not --${stray}` : ''}`);
673
+ const port = Number(values.port);
674
+ if (typeof values.port !== 'string' || !/^\d{1,5}$/.test(values.port))
675
+ return usageOutcome('CLI_USAGE_INVALID', 'init --scaffold-compose needs --port, the port the app listens on inside its container', '--port');
676
+ const backing = typeof values.with === 'string' ? values.with.split(',').map(name => name.trim()).filter(Boolean) : [];
677
+ const unknown = backing.find(name => !Object.hasOwn(SCAFFOLD_BACKING_SERVICES, name));
678
+ if (unknown)
679
+ return usageOutcome('CLI_USAGE_INVALID', `--with names ${JSON.stringify(unknown)}; the scaffold adds only ${Object.keys(SCAFFOLD_BACKING_SERVICES).join(' and ')}`, '--with');
680
+ const command = typeof values.command === 'string' ? values.command.trim().split(/\s+/).filter(Boolean) : undefined;
681
+ let absoluteRoot;
682
+ try {
683
+ absoluteRoot = await realpath(resolve(root));
684
+ }
685
+ catch (error) {
686
+ return usageOutcome('CLI_USAGE_INVALID', `init root must be an existing directory: ${errorCause(error)}`);
687
+ }
688
+ return { scaffold: { root: absoluteRoot, dir: String(values['scaffold-compose']), port,
689
+ ...(command?.length ? { command: command } : {}), with: backing } };
690
+ }
691
+ async function runComposeScaffold(request) {
692
+ const { root: absoluteRoot, dir, port, command } = request;
693
+ const present = async (name) => {
694
+ try {
695
+ return (await lstat(join(absoluteRoot, dir, name))).isFile();
696
+ }
697
+ catch (error) {
698
+ if (hasErrorCode(error, 'ENOENT') || hasErrorCode(error, 'ENOTDIR'))
699
+ return false;
700
+ throw error;
701
+ }
702
+ };
703
+ const rendered = renderComposeScaffold({ dir, dockerfile: await present('Dockerfile'), packageJson: await present('package.json'),
704
+ ...(command ? { command } : {}), port, with: request.with });
705
+ if (rendered.kind === 'refusal')
706
+ return { artifact: null, destination: null, result: null, status: 2,
707
+ diagnostics: [{ severity: 'error', code: 'INIT_SCAFFOLD_INVALID', message: rendered.message, target: '--scaffold-compose', fix: rendered.fix }] };
708
+ const written = await writeComposeScaffold(absoluteRoot, rendered.bytes);
709
+ if (written.kind === 'exists')
710
+ return { artifact: null, destination: null, result: null, status: 2,
711
+ diagnostics: [{ severity: 'error', code: 'INIT_SCAFFOLD_COMPOSE_EXISTS', target: written.path,
712
+ message: `${written.path} already exists, and the scaffold never replaces a Compose file`,
713
+ fix: 'run npx --no cockpit init <cockpit root> --target <tool>, which proposes the existing Compose file for environment.compose_files' }] };
714
+ const next = `declare ${rendered.portVariable} under [ports.bases]${rendered.volumes.length ? ` and resettable_volumes = ${JSON.stringify(rendered.volumes)} under [environment]` : ''}; npx --no cockpit init ${absoluteRoot} --target <tool> proposes compose_files = ["compose.yml"]`;
715
+ return { artifact: `wrote ${written.path}; it is yours to edit, and cockpit never rewrites it. Next: ${next}.\n`, destination: null,
716
+ diagnostics: [], status: 0,
717
+ result: { action: 'scaffolded', path: written.path, service: rendered.service, portVariable: rendered.portVariable, volumes: rendered.volumes } };
718
+ }
719
+ async function decodeRequest(handler, parsed, runtimeEnv) {
720
+ if (isWorkHandler(handler)) {
721
+ const values = parsed.values;
722
+ if (parsed.positionals.length || typeof values.config !== 'string' || !values.config) {
723
+ return usageOutcome('CLI_USAGE_INVALID', 'work, browser and testing commands require --config and named selectors only');
724
+ }
725
+ const text = (key) => typeof values[key] === 'string' ? values[key] : undefined;
726
+ const workOrderId = text('work-order');
727
+ let request;
728
+ if (handler.startsWith('work-') && handler !== 'work-status' && handler !== 'work-verify' && !workOrderId) {
729
+ return usageOutcome('CLI_USAGE_INVALID', 'provide --work-order <id>', '--work-order');
730
+ }
731
+ if (handler === 'work-status') {
732
+ if (values['work-order'] !== undefined && !workOrderId)
733
+ return usageOutcome('CLI_USAGE_INVALID', '--work-order requires a bounded identifier');
734
+ request = { operation: handler, ...(workOrderId ? { workOrderId } : {}) };
735
+ }
736
+ else if (handler === 'work-accept' || handler === 'work-check')
737
+ request = { operation: handler, workOrderId: workOrderId };
738
+ else if (handler === 'work-verify')
739
+ request = { operation: handler };
740
+ else if (handler === 'work-recover')
741
+ request = { operation: handler, workOrderId: workOrderId, apply: values.apply === true };
742
+ else if (handler === 'work-note') {
743
+ const note = text('text');
744
+ if (!note)
745
+ return usageOutcome('CLI_USAGE_INVALID', 'provide --text <note>', '--text');
746
+ request = { operation: handler, workOrderId: workOrderId, text: note, question: values.ask === true };
747
+ }
748
+ else if (handler === 'work-answer') {
749
+ const answer = text('text'), question = Number(text('question'));
750
+ if (!answer || !Number.isSafeInteger(question) || question < 1)
751
+ return usageOutcome('CLI_USAGE_INVALID', 'provide --question <number> and --text <answer>');
752
+ request = { operation: handler, workOrderId: workOrderId, question, text: answer };
753
+ }
754
+ else if (handler === 'work-grade') {
755
+ const verdict = text('verdict'), note = text('note');
756
+ if (verdict !== undefined && verdict !== 'met' && verdict !== 'unmet')
757
+ return usageOutcome('CLI_USAGE_INVALID', '--verdict is met or unmet', '--verdict');
758
+ request = { operation: handler, workOrderId: workOrderId, ...(verdict ? { verdict } : {}), ...(note ? { note } : {}) };
759
+ }
760
+ else if (handler === 'work-signoff') {
761
+ if (values.partial === true && values.abandon === true)
762
+ return usageOutcome('CLI_USAGE_INVALID', 'pass at most one of --partial and --abandon');
763
+ const note = text('note');
764
+ request = { operation: handler, workOrderId: workOrderId, close: values.partial === true ? 'partial' : values.abandon === true ? 'abandoned' : 'done', ...(note ? { note } : {}) };
765
+ }
766
+ else if (handler === 'grade-criteria') {
767
+ const criteriaFile = text('criteria');
768
+ if (!criteriaFile)
769
+ return usageOutcome('CLI_USAGE_INVALID', 'provide --criteria <file inside the repository>', '--criteria');
770
+ // A path is read from the cockpit root; an absolute path inside it is the same file spelled differently.
771
+ const inside = relative(dirname(resolve(values.config)), resolve(dirname(resolve(values.config)), criteriaFile));
772
+ if (inside === '' || inside === '..' || inside.startsWith('../') || isAbsolute(inside))
773
+ return usageOutcome('CLI_USAGE_INVALID', `--criteria ${JSON.stringify(criteriaFile)} must be a file inside the repository; pass a path under ${dirname(resolve(values.config))}`, '--criteria');
774
+ request = { operation: handler, criteriaFile: inside };
775
+ }
776
+ else if (handler === 'test-run') {
777
+ const gate = text('gate'), mode = values.mode;
778
+ if (!gate || mode !== 'canonical' && mode !== 'full' && mode !== 'staged')
779
+ return usageOutcome('CLI_USAGE_INVALID', 'provide --gate and --mode canonical|full|staged');
780
+ let select;
781
+ if (values.select !== undefined) {
782
+ const selector = text('select'), separator = selector?.indexOf(':') ?? -1;
783
+ const kind = selector?.slice(0, separator), value = selector?.slice(separator + 1);
784
+ if (!value || kind !== 'spec' && kind !== 'file' && kind !== 'test' && kind !== 'route')
785
+ return usageOutcome('CLI_USAGE_INVALID', '--select requires spec|file|test|route:<value>');
786
+ select = { kind, value };
787
+ }
788
+ request = { operation: handler, gate, mode, ...(select ? { select } : {}) };
789
+ }
790
+ else {
791
+ const role = text('role');
792
+ if (values.role !== undefined && !role)
793
+ return usageOutcome('CLI_USAGE_INVALID', '--role requires a bounded identifier');
794
+ request = { operation: handler, ...(role ? { role } : {}) };
795
+ }
796
+ const { captureWorkApplicationRequest } = await import('./work-orders/application.js');
797
+ const captured = captureWorkApplicationRequest(request);
798
+ if (!captured)
799
+ return usageOutcome('CLI_USAGE_INVALID', 'work, browser and testing commands require bounded selectors of the declared kind');
800
+ const evaluator = text('evaluator');
801
+ if (evaluator !== undefined && !Object.hasOwn(EVALUATOR_COMMANDS, evaluator)) {
802
+ return usageOutcome('CLI_USAGE_INVALID', `--evaluator is one of ${Object.keys(EVALUATOR_COMMANDS).join(', ')}`, '--evaluator');
803
+ }
804
+ return { handler, input: { configPath: values.config, request: captured, ...(evaluator ? { evaluator: evaluator } : {}) } };
805
+ }
806
+ if (handler === 'init') {
807
+ const [explicitRoot, ...extraRoots] = parsed.positionals;
808
+ if (parsed.values['scaffold-compose'] !== undefined)
809
+ return decodeComposeScaffold(explicitRoot, extraRoots, parsed.values);
810
+ // A bare `cockpit init` sets up the current directory for the default tools.
811
+ const root = explicitRoot ?? '.';
812
+ const scaffoldOnly = ['command', 'port', 'with'].find(name => parsed.values[name] !== undefined);
813
+ if (scaffoldOnly)
814
+ return usageOutcome('CLI_USAGE_INVALID', `--${scaffoldOnly} belongs to --scaffold-compose`, `--${scaffoldOnly}`);
815
+ const registry = parsed.values.registry;
816
+ // `--target a,b` chooses several tools; the first is the one whose setup skill init publishes
817
+ // before the interview. One comma list rather than a repeated flag keeps the guard's
818
+ // single-value grammar for --target.
819
+ const tools = typeof parsed.values.target === 'string' ? parsed.values.target.split(',')
820
+ : parsed.values.target === undefined ? [...DEFAULT_SURFACE_TOOLS] : [];
821
+ if (extraRoots.length || tools.length === 0 || !tools.every(tool => SURFACE_TARGETS.some(value => value === tool))
822
+ || new Set(tools).size !== tools.length
823
+ || registry !== undefined && (typeof registry !== 'string' || registry.length === 0)) {
824
+ return usageOutcome('CLI_USAGE_INVALID', `init takes at most one root (default: the current directory) and --target with one or more distinct tools, comma-separated: ${SURFACE_TARGETS.join('|')} (default: ${DEFAULT_SURFACE_TOOLS.join(',')})`);
825
+ }
826
+ if (!tools.some(tool => isGuardSurfaceTarget(tool))) {
827
+ return usageOutcome('CLI_USAGE_INVALID', `--target must include a tool cockpit has a guard for (${GUARD_SURFACE_TARGETS.join(', ')})`, '--target');
828
+ }
829
+ const target = tools[0];
830
+ const chosen = tools;
831
+ const absoluteRoot = resolve(root);
832
+ try {
833
+ if (await realpath(absoluteRoot) !== absoluteRoot)
834
+ return usageOutcome('CLI_USAGE_INVALID', 'init root must name its canonical directory');
835
+ }
836
+ catch (error) {
837
+ return usageOutcome('CLI_USAGE_INVALID', `init root must be an existing canonical directory: ${errorCause(error)}`);
838
+ }
839
+ if (process.platform !== 'darwin' && process.platform !== 'linux') {
840
+ return usageOutcome('CLI_USAGE_INVALID', 'init supports macOS and Linux state ownership');
841
+ }
842
+ const resetField = parsed.values['reset-answer'];
843
+ const acceptedReset = parsed.values['accept-reset'];
844
+ if (resetField !== undefined || acceptedReset !== undefined) {
845
+ if (typeof resetField !== 'string' || !isRecordedAnswerField(resetField) || acceptedReset !== undefined
846
+ && (typeof acceptedReset !== 'string' || !/^[a-f0-9]{64}$/.test(acceptedReset))) {
847
+ return usageOutcome('CLI_USAGE_INVALID', '--reset-answer requires one recorded field; --accept-reset requires its printed sha256 digest');
848
+ }
849
+ if (parsed.values.interactive || parsed.values.answers !== undefined || parsed.values['accept-preview'] !== undefined
850
+ || parsed.values['accept-defaults'] !== undefined) {
851
+ return usageOutcome('CLI_USAGE_INVALID', '--reset-answer is a standalone preview or exact acceptance operation');
852
+ }
853
+ return { handler, input: { root: absoluteRoot, target, tools: chosen,
854
+ ...(typeof registry === 'string' ? { registry: resolve(absoluteRoot, registry) } : {}),
855
+ accepted: false, sessionId: randomUUID(), platform: process.platform, home: runtimeEnv.HOME,
856
+ ...(runtimeEnv.XDG_STATE_HOME ? { xdgStateHome: runtimeEnv.XDG_STATE_HOME } : {}),
857
+ resetAnswer: { field: resetField, ...(typeof acceptedReset === 'string'
858
+ ? { acceptedPreviewSha256: acceptedReset } : {}) } } };
859
+ }
860
+ const unattended = await decodeUnattendedInit(parsed.values);
861
+ if ('status' in unattended)
862
+ return unattended;
863
+ return { handler, input: { root: absoluteRoot, target, tools: chosen,
864
+ ...(typeof registry === 'string' ? { registry: resolve(absoluteRoot, registry) } : {}),
865
+ accepted: false, sessionId: randomUUID(), platform: process.platform, home: runtimeEnv.HOME,
866
+ ...(runtimeEnv.XDG_STATE_HOME ? { xdgStateHome: runtimeEnv.XDG_STATE_HOME } : {}),
867
+ ...(unattended.value ? { unattended: unattended.value } : {}) } };
868
+ }
869
+ if (handler === 'setup') {
870
+ const values = parsed.values;
871
+ if (parsed.positionals.length > 0) {
872
+ return usageOutcome(ENVIRONMENT_CODES.SETUP_POSITIONAL_UNSUPPORTED, 'setup accepts named inputs only');
873
+ }
874
+ if (!values.config)
875
+ return usageOutcome(ENVIRONMENT_CODES.SETUP_CONFIG_REQUIRED, 'provide --config <file>', '--config');
876
+ return { handler, input: { configPath: values.config,
877
+ ...(values['write-instruction-pointers'] === true ? { writeInstructionPointers: true } : {}),
878
+ ...(values['write-instruction-files'] === true ? { writeInstructionFiles: true } : {}) } };
879
+ }
880
+ if (handler === 'sync') {
881
+ const values = parsed.values;
882
+ if (parsed.positionals.length > 0)
883
+ return usageOutcome('CLI_USAGE_INVALID', 'sync accepts named inputs only');
884
+ if (!values.config)
885
+ return missingConfig();
886
+ if (values['dry-run'] && values.interactive) {
887
+ return usageOutcome('CLI_USAGE_INVALID', 'sync --dry-run and --interactive are mutually exclusive', '--interactive');
888
+ }
889
+ if (values.adopt && (values['dry-run'] || values.interactive)) {
890
+ return usageOutcome('CLI_USAGE_INVALID', 'sync --adopt moves files and records them in cockpit.toml, so it cannot be a dry run or interactive; a refused adoption is rolled back', '--adopt');
891
+ }
892
+ return { handler, input: { configPath: values.config, ...(values['dry-run'] === true ? { dryRun: true } : {}),
893
+ ...(values.adopt ? { adopt: [...values.adopt] } : {}) } };
894
+ }
895
+ if (handler === 'upgrade') {
896
+ const values = parsed.values;
897
+ if (parsed.positionals.length > 1)
898
+ return usageOutcome('CLI_USAGE_INVALID', 'upgrade accepts at most one version');
899
+ if (!values.config)
900
+ return missingConfig();
901
+ return { handler, input: { configPath: values.config, dryRun: values['dry-run'] === true,
902
+ ...(parsed.positionals[0] === undefined ? {} : { version: parsed.positionals[0] }) } };
903
+ }
904
+ if (handler === 'selftest') {
905
+ const values = parsed.values;
906
+ if (parsed.positionals.length > 0)
907
+ return usageOutcome('CLI_USAGE_INVALID', 'selftest accepts named inputs only');
908
+ if (!values.config)
909
+ return missingConfig();
910
+ const draftPath = values['write-probe-specifications'];
911
+ if (draftPath !== undefined) {
912
+ if (draftPath.length === 0)
913
+ return usageOutcome('CLI_USAGE_INVALID', '--write-probe-specifications requires a non-empty path', '--write-probe-specifications');
914
+ // Writing the file runs nothing, and --json would print the envelope without creating it.
915
+ const conflict = values.interactive ? '--interactive' : values.json ? '--json' : undefined;
916
+ if (conflict)
917
+ return usageOutcome('CLI_USAGE_INVALID', `--write-probe-specifications only writes the file; drop ${conflict}`, conflict);
918
+ const previous = values['probe-specifications'];
919
+ if (previous !== undefined && previous.length === 0) {
920
+ return usageOutcome('CLI_USAGE_INVALID', '--probe-specifications requires a non-empty path', '--probe-specifications');
921
+ }
922
+ return { handler, input: { configPath: values.config, writeProbeSpecificationsPath: draftPath,
923
+ ...(previous === undefined ? {} : { previousProbeSpecificationsPath: previous }) } };
924
+ }
925
+ if (!values['probe-specifications'])
926
+ return usageOutcome('CLI_USAGE_INVALID', 'provide --probe-specifications <file>; write one with --write-probe-specifications <new file> first', '--probe-specifications');
927
+ const invalidEnvironment = () => usageOutcome('CLI_USAGE_INVALID', `selftest probe environment inputs must be bounded own string or undefined values; supply ${['PATH', ...INHERITED_TOOL_ENVIRONMENT].join(', ')} without accessors and run npx --no cockpit selftest --help to retry`);
928
+ const env = {};
929
+ try {
930
+ if (runtimeEnv === null || typeof runtimeEnv !== 'object' || Array.isArray(runtimeEnv)
931
+ || utilTypes.isProxy(runtimeEnv))
932
+ return invalidEnvironment();
933
+ const keys = Reflect.ownKeys(runtimeEnv);
934
+ if (keys.length > 4_096)
935
+ return invalidEnvironment();
936
+ const inherited = new Set(['PATH', ...INHERITED_TOOL_ENVIRONMENT]);
937
+ for (const key of keys) {
938
+ if (typeof key !== 'string' || !inherited.has(key))
939
+ continue;
940
+ const descriptor = Object.getOwnPropertyDescriptor(runtimeEnv, key);
941
+ if (!descriptor?.enumerable || !Object.hasOwn(descriptor, 'value')
942
+ || descriptor.value !== undefined && (typeof descriptor.value !== 'string'
943
+ || Buffer.byteLength(descriptor.value) > 65_536))
944
+ return invalidEnvironment();
945
+ env[key] = descriptor.value;
946
+ }
947
+ }
948
+ catch {
949
+ return invalidEnvironment();
950
+ } // A throwing accessor or proxy trap makes the environment invalid data.
951
+ return { handler, input: { configPath: values.config, probeSpecificationsPath: values['probe-specifications'], env } };
952
+ }
953
+ if (handler === 'session') {
954
+ const values = parsed.values;
955
+ const [command, ...argv] = parsed.positionals;
956
+ if (!values.config)
957
+ return missingConfig();
958
+ if (!command)
959
+ return usageOutcome('CLI_USAGE_INVALID', 'session requires a command after --', 'session');
960
+ const inherited = inheritedSessionId(runtimeEnv);
961
+ if (inherited === null)
962
+ return usageOutcome('CLI_USAGE_INVALID', 'COCKPIT_SESSION_ID must be a canonical UUID', 'COCKPIT_SESSION_ID');
963
+ if (inherited !== undefined)
964
+ return usageOutcome('CLI_USAGE_INVALID', 'session cannot be nested inside another supervised session', 'session');
965
+ return { handler, input: { configPath: values.config, argv: [command, ...argv], sessionId: randomUUID() } };
966
+ }
967
+ if (handler === 'check' || handler === 'up' || handler === 'sleep' || handler === 'down' || handler === 'reset'
968
+ || handler === 'preflight' || handler === 'doctor') {
969
+ const values = parsed.values;
970
+ if (parsed.positionals.length > 0) {
971
+ return usageOutcome('CLI_USAGE_INVALID', `${handler} accepts named inputs only`);
972
+ }
973
+ if (!values.config)
974
+ return missingConfig();
975
+ return { handler, input: { configPath: values.config } };
976
+ }
977
+ if (handler === 'logs') {
978
+ const values = parsed.values;
979
+ if (parsed.positionals.length > 0)
980
+ return usageOutcome('CLI_USAGE_INVALID', 'logs accepts named inputs only');
981
+ if (!values.config)
982
+ return missingConfig();
983
+ if (!values.service)
984
+ return usageOutcome('CLI_USAGE_INVALID', 'provide --service <name>', '--service');
985
+ const tail = Number(values.tail);
986
+ if (!Number.isSafeInteger(tail) || tail < 1 || tail > 10_000) {
987
+ return usageOutcome('CLI_USAGE_INVALID', '--tail must be an integer from 1 through 10000', '--tail');
988
+ }
989
+ if (!values.since)
990
+ return usageOutcome('CLI_USAGE_INVALID', '--since requires a non-empty value', '--since');
991
+ return {
992
+ handler,
993
+ input: {
994
+ configPath: values.config,
995
+ service: values.service,
996
+ tail,
997
+ since: values.since,
998
+ follow: values.follow === true,
999
+ json: values.json === true,
1000
+ },
1001
+ };
1002
+ }
1003
+ if (handler === 'runtime') {
1004
+ const values = parsed.values;
1005
+ const [operation, ...extra] = parsed.positionals;
1006
+ if (operation !== 'measure' || extra.length > 0) {
1007
+ return usageOutcome('CLI_USAGE_INVALID', 'runtime requires the measure operation: npx --no cockpit runtime measure --config <your cockpit.toml> --image <local-image>');
1008
+ }
1009
+ if (!values.config)
1010
+ return missingConfig();
1011
+ if (!values.image)
1012
+ return usageOutcome('CLI_USAGE_INVALID', 'provide --image <local-image>; the probe never pulls', '--image');
1013
+ return { handler, input: { configPath: values.config, image: values.image } };
1014
+ }
1015
+ if (handler === 'sibling') {
1016
+ const values = parsed.values;
1017
+ const [operation, slug, ...extra] = parsed.positionals;
1018
+ const shape = 'npx --no cockpit sibling add <slug> --config <your cockpit.toml> [--create | --from <path> | --remote <url>] [--dir <relative dir>] [--var <NAME>] [--purpose "<text>"] [--mount <always|none|profile:NAME>] [--instruction-pointer], or npx --no cockpit sibling remove <slug> --config <your cockpit.toml>';
1019
+ if ((operation !== 'add' && operation !== 'remove') || !slug || extra.length > 0) {
1020
+ return usageOutcome('CLI_USAGE_INVALID', `sibling requires an operation and one slug: ${shape}`);
1021
+ }
1022
+ if (!values.config)
1023
+ return missingConfig();
1024
+ if (operation === 'remove') {
1025
+ const stray = ['create', 'from', 'remote', 'dir', 'var', 'purpose', 'mount', 'instruction-pointer'].find((name) => values[name] !== undefined && values[name] !== false);
1026
+ if (stray)
1027
+ return usageOutcome('CLI_USAGE_INVALID', `sibling remove takes only --config and --json; --${stray} belongs to sibling add`, `--${stray}`);
1028
+ return { handler, input: { operation, input: { configPath: values.config, slug } } };
1029
+ }
1030
+ return { handler, input: { operation, input: {
1031
+ configPath: values.config, slug, create: values.create === true,
1032
+ ...(values.from === undefined ? {} : { from: values.from }), ...(values.remote === undefined ? {} : { remote: values.remote }),
1033
+ ...(values.dir === undefined ? {} : { dir: values.dir }), ...(values.var === undefined ? {} : { varName: values.var }),
1034
+ ...(values.purpose === undefined ? {} : { purpose: values.purpose }),
1035
+ ...(values.mount === undefined ? {} : { mount: values.mount }),
1036
+ ...(values['instruction-pointer'] === true ? { instructionPointer: true } : {}),
1037
+ } } };
1038
+ }
1039
+ if (handler === 'tool') {
1040
+ const values = parsed.values;
1041
+ const [operation, tool, ...extra] = parsed.positionals;
1042
+ if ((operation !== 'add' && operation !== 'remove') || !tool || extra.length > 0) {
1043
+ return usageOutcome('CLI_USAGE_INVALID', `tool requires an operation and one tool: npx --no cockpit tool add|remove <${SURFACE_TARGETS.join('|')}> --config <your cockpit.toml> [--dry-run]`);
1044
+ }
1045
+ if (!values.config)
1046
+ return missingConfig();
1047
+ return { handler, input: { operation, configPath: values.config, tool, dryRun: values['dry-run'] === true } };
1048
+ }
1049
+ const values = parsed.values;
1050
+ if (parsed.positionals.length > 0) {
1051
+ return usageOutcome('REGISTRY_POSITIONAL_UNSUPPORTED', 'registry-resolve accepts named inputs only');
1052
+ }
1053
+ if (!values.registry) {
1054
+ return usageOutcome('REGISTRY_SOURCE_REQUIRED', 'provide --registry <file>', '--registry');
1055
+ }
1056
+ if (!values['siblings-root']) {
1057
+ return usageOutcome('REGISTRY_SIBLINGS_ROOT_REQUIRED', 'provide --siblings-root <path>', '--siblings-root');
1058
+ }
1059
+ if (!values.adoption) {
1060
+ return usageOutcome('REGISTRY_ADOPTION_REQUIRED', 'provide --adoption <file>: five lines registry=, environment=, surfaces=, guards=, grading=, each migrated or documented-only (see the adoption guide)', '--adoption');
1061
+ }
1062
+ return {
1063
+ handler,
1064
+ input: {
1065
+ registry: values.registry,
1066
+ siblingsRoot: values['siblings-root'],
1067
+ adoption: values.adoption,
1068
+ },
1069
+ };
1070
+ }
1071
+ // `sibling` nests its arguments under the operation it runs; every other configured command carries
1072
+ // configPath at the top of its input.
1073
+ function requestConfigPath(request) {
1074
+ const input = request.handler === 'sibling' ? request.input.input : request.input;
1075
+ return input !== null && typeof input === 'object' && 'configPath' in input && typeof input.configPath === 'string' ? input.configPath : undefined;
1076
+ }
1077
+ // Printed remedies name a configuration placeholder; every outcome of a configured command binds it
1078
+ // to the path this invocation was given, not only the outcomes whose handler built a fix context.
1079
+ async function invokeHandler(request, handlers, operators, options, io) {
1080
+ const outcome = await dispatchHandler(request, handlers, operators, options, io);
1081
+ const configured = requestConfigPath(request);
1082
+ if (configured === undefined)
1083
+ return outcome;
1084
+ const configPath = resolve(configured);
1085
+ return { ...outcome, fixContext: outcome.fixContext
1086
+ ? { ...outcome.fixContext, resolvedConfigPath: outcome.fixContext.resolvedConfigPath ?? configPath }
1087
+ : { resolvedConfigPath: configPath, generatedTargets: [] } };
1088
+ }
1089
+ async function dispatchHandler(request, handlers, operators, options, io) {
1090
+ const unavailable = () => usageOutcome('CLI_INTERNAL_ERROR', `the "${request.handler}" command is unavailable`, request.handler);
1091
+ if (isWorkHandler(request.handler) && 'request' in request.input) {
1092
+ if (request.handler === 'work-accept' || request.handler === 'work-answer' || request.handler === 'work-signoff') {
1093
+ const opened = await operators.beginOperatorWorkApplication?.(request.input);
1094
+ if (!opened)
1095
+ return unavailable();
1096
+ if (!('decide' in opened))
1097
+ return opened;
1098
+ const terminal = io.confirmWorkOrder !== undefined || io.stdin?.isTTY === true && 'isTTY' in io.stdout && io.stdout.isTTY === true;
1099
+ if (!options.interactive || options.json || options.ci && options.ci.length > 0 || !terminal)
1100
+ return personRequired(request.handler, opened.prompt);
1101
+ let answer;
1102
+ if (io.confirmWorkOrder)
1103
+ answer = await io.confirmWorkOrder({ digest: opened.digest, prompt: opened.prompt });
1104
+ else if (io.stdin) {
1105
+ const prompt = createInterface({ input: io.stdin, output: io.stderr, terminal: true });
1106
+ try {
1107
+ answer = await prompt.question(`${opened.prompt}\n`);
1108
+ }
1109
+ finally {
1110
+ prompt.close();
1111
+ }
1112
+ }
1113
+ return opened.decide(answer ?? '');
1114
+ }
1115
+ const handler = handlers[request.handler];
1116
+ return handler?.(request.input) ?? unavailable();
1117
+ }
1118
+ if (request.handler === 'init')
1119
+ return runInitOperatorCommand(request.input, { ...options, unattended: request.input.unattended }, {
1120
+ terminal: io.confirmInit !== undefined || io.answerInitQuestion !== undefined
1121
+ || io.stdin?.isTTY === true && 'isTTY' in io.stdout && io.stdout.isTTY === true,
1122
+ confirm: async (preview) => {
1123
+ if (io.confirmInit)
1124
+ return io.confirmInit(preview);
1125
+ if (!io.stdin)
1126
+ return undefined;
1127
+ io.stderr.write(renderHumanValue(preview));
1128
+ const prompt = createInterface({ input: io.stdin, output: io.stderr, terminal: true });
1129
+ try {
1130
+ return await prompt.question('Type the exact bootstrap preview digest to accept: ');
1131
+ }
1132
+ finally {
1133
+ prompt.close();
1134
+ }
1135
+ },
1136
+ writeArtifact: (destination, artifact) => writeFile(destination, artifact, { flag: 'wx', mode: 0o600 }),
1137
+ createCockpitRoot: path => createDedicatedCockpitRoot(path, options.env),
1138
+ chooseCockpitRoot: async (question) => {
1139
+ if (!io.stdin)
1140
+ return undefined;
1141
+ io.stderr.write(renderHumanValue(question));
1142
+ const prompt = createInterface({ input: io.stdin, output: io.stderr, terminal: true });
1143
+ try {
1144
+ return (await prompt.question(`Where does the cockpit live? Type ${question.options.map(row => row.answer === question.recommended ? `${row.answer} (recommended)` : row.answer).join(' or ')}: `)).trim().replace(/ \(recommended\)$/, '');
1145
+ }
1146
+ finally {
1147
+ prompt.close();
1148
+ }
1149
+ },
1150
+ answerQuestion: async (question) => {
1151
+ if (io.answerInitQuestion)
1152
+ return io.answerInitQuestion(question);
1153
+ if (!io.stdin)
1154
+ return undefined;
1155
+ io.stderr.write(renderHumanValue(question));
1156
+ const prompt = createInterface({ input: io.stdin, output: io.stderr, terminal: true });
1157
+ try {
1158
+ return await prompt.question(`Enter a JSON value for ${question.field} (strings need quotes): `);
1159
+ }
1160
+ finally {
1161
+ prompt.close();
1162
+ }
1163
+ },
1164
+ }, handlers.init ?? (async () => refusalOutcome('INIT_UNAVAILABLE', 'init application is unavailable')));
1165
+ if (request.handler === 'registry-resolve')
1166
+ return handlers['registry-resolve']?.(request.input) ?? unavailable();
1167
+ if (request.handler === 'setup')
1168
+ return handlers.setup?.(request.input) ?? unavailable();
1169
+ if (request.handler === 'sync')
1170
+ return runSyncOperatorCommand(request.input, options, io, handlers.sync ?? (async () => unavailable()), operators.loadPolicy ?? loadOperatorPolicy);
1171
+ if (request.handler === 'check')
1172
+ return handlers.check?.(request.input) ?? unavailable();
1173
+ if (request.handler === 'selftest') {
1174
+ const selftest = handlers.selftest ?? (async () => unavailable());
1175
+ return 'writeProbeSpecificationsPath' in request.input ? selftest(request.input)
1176
+ : runSelftestOperatorCommand(request.input, options, io, selftest);
1177
+ }
1178
+ if (request.handler === 'upgrade')
1179
+ return handlers.upgrade?.(request.input) ?? unavailable();
1180
+ if (request.handler === 'up')
1181
+ return handlers.up?.(request.input) ?? unavailable();
1182
+ if (request.handler === 'sleep')
1183
+ return handlers.sleep?.(request.input) ?? unavailable();
1184
+ if (request.handler === 'session')
1185
+ return handlers.session?.(request.input) ?? unavailable();
1186
+ if (request.handler === 'down')
1187
+ return handlers.down?.(request.input) ?? unavailable();
1188
+ if (request.handler === 'reset')
1189
+ return runResetOperatorApplication(request.input, {
1190
+ ...options,
1191
+ terminal: io.confirmReset !== undefined || io.stdin?.isTTY === true,
1192
+ }, {
1193
+ policy: operators.loadPolicy ?? loadOperatorPolicy,
1194
+ preview: handlers.reset ?? (async () => unavailable()),
1195
+ begin: operators.beginOperatorReset ?? (async () => operatorRefusal('interactive reset is unavailable')),
1196
+ confirm: (preview) => confirmOperatorReset(io, preview),
1197
+ });
1198
+ if (request.handler === 'preflight')
1199
+ return handlers.preflight?.(request.input) ?? unavailable();
1200
+ if (request.handler === 'doctor')
1201
+ return handlers.doctor?.(request.input) ?? unavailable();
1202
+ if (request.handler === 'logs')
1203
+ return handlers.logs?.(request.input) ?? unavailable();
1204
+ if (request.handler === 'runtime')
1205
+ return handlers.runtime?.(request.input) ?? unavailable();
1206
+ if (request.handler === 'sibling')
1207
+ return handlers.sibling?.(request.input) ?? unavailable();
1208
+ if (request.handler === 'tool')
1209
+ return handlers.tool?.(request.input) ?? unavailable();
1210
+ return unavailable();
1211
+ }
1212
+ function operatorRefusal(message) {
1213
+ return usageOutcome(ENVIRONMENT_CODES.RESET_CONFIRMATION_MISMATCH, message, 'reset');
1214
+ }
1215
+ async function confirmOperatorReset(io, preview) {
1216
+ if (io.confirmReset)
1217
+ return io.confirmReset(preview);
1218
+ if (!io.stdin)
1219
+ return false;
1220
+ io.stderr.write(renderHumanValue(preview.result));
1221
+ const prompt = createInterface({ input: io.stdin, output: io.stderr, terminal: true });
1222
+ try {
1223
+ return (await prompt.question('Approve this exact reset? [y/N] ')).trim().toLowerCase() === 'y';
1224
+ }
1225
+ finally {
1226
+ prompt.close();
1227
+ }
1228
+ }
1229
+ function defaultOperatorHandlers(runtime) {
1230
+ const inherited = inheritedSessionId(runtime.env ?? process.env);
1231
+ const sessionId = inherited === undefined ? 'cockpit-cli' : inherited;
1232
+ return {
1233
+ beginOperatorWorkApplication: async (input) => {
1234
+ const outcome = await configuredWorkApplication(input, runtime);
1235
+ if (outcome.kind !== 'human-needed')
1236
+ return workCommandOutcome(outcome, runtime.env ?? process.env);
1237
+ return { digest: outcome.continuation.digest, prompt: outcome.continuation.prompt,
1238
+ decide: async (answer) => workCommandOutcome(await outcome.continuation.decide(answer), runtime.env ?? process.env) };
1239
+ },
1240
+ beginOperatorReset: async (input) => {
1241
+ if (sessionId === null)
1242
+ return operatorRefusal('COCKPIT_SESSION_ID must be a canonical UUID');
1243
+ const configPath = resolve(input.configPath);
1244
+ return beginOperatorResetApplication({ configPath }, await destructiveAdapters(configPath, runtime, sessionId));
1245
+ },
1246
+ };
1247
+ }
1248
+ export async function runCli(args, io = process, handlerOverrides = {}, runtime = {}) {
1249
+ const startedAt = performance.now();
1250
+ const { name: commandName, group, rest: commandArgs } = commandSpelling(args);
1251
+ const internal = internalCommands.find(command => command.name === commandName);
1252
+ const definition = commands.find(command => command.name === commandName);
1253
+ const verb = internal?.name ?? definition?.name ?? (commandName === undefined ? group : undefined)
1254
+ ?? (commandArgs.length === 0 && commandName === '--help' ? 'help'
1255
+ : commandArgs.length === 0 && commandName === '--version' ? 'version' : 'unknown');
1256
+ let resolvedConfigPath = null;
1257
+ // The invocation record is part of the --json envelope on stdout; human-mode runs publish no record.
1258
+ // session has no JSON mode and exits with its supervised command's own status, which scripts branch on.
1259
+ const finish = (status, outcome) => {
1260
+ const exitCode = status === EXIT_CODES.success ? EXIT_CODES.success
1261
+ : status === EXIT_CODES.cancelled ? EXIT_CODES.cancelled : EXIT_CODES.failure;
1262
+ if (outcome) {
1263
+ const invocation = createInvocationRecord({ verb, durationMs: Math.floor(performance.now() - startedAt),
1264
+ exitCode, resolvedConfigPath });
1265
+ io.stdout.write(renderJsonResponse({ ...outcome, invocation }, outcome.fixContext));
1266
+ }
1267
+ return verb === 'session' && Number.isInteger(status) && status > 0 && status < 256 ? status : exitCode;
1268
+ };
1269
+ // One refusal here instead of per-command checks: process ownership, filesystem publication and
1270
+ // the shell guards assume POSIX semantics, so a native Windows run could fail part-way through a
1271
+ // mutation rather than up front.
1272
+ if (process.platform === 'win32') {
1273
+ const outcome = usageOutcome('CLI_PLATFORM_UNSUPPORTED', 'cockpit does not run on native Windows because its process, filesystem and guard controls assume macOS or Linux; fix: run cockpit on a macOS or Linux host');
1274
+ if (args.includes('--json'))
1275
+ return finish(outcome.status, outcome);
1276
+ io.stderr.write(renderHumanDiagnostics(outcome.diagnostics));
1277
+ return finish(outcome.status);
1278
+ }
1279
+ if (internal)
1280
+ return runGuardCommand(commandArgs, io, runtime.env ?? process.env);
1281
+ if (commandName === '--help' && commandArgs.length === 0) {
1282
+ io.stdout.write(renderHelp());
1283
+ return finish(EXIT_CODES.success);
1284
+ }
1285
+ if (commandName === '--version' && commandArgs.length === 0) {
1286
+ io.stdout.write(renderVersion());
1287
+ return finish(EXIT_CODES.success);
1288
+ }
1289
+ const handlers = { ...defaultHandlers(runtime), ...handlerOverrides };
1290
+ const operators = { ...defaultOperatorHandlers(runtime), ...handlerOverrides };
1291
+ const optionTerminator = commandArgs.indexOf('--');
1292
+ const optionArgs = commandArgs.slice(0, optionTerminator === -1 ? commandArgs.length : optionTerminator);
1293
+ const helpRequested = optionArgs.some(arg => arg === '--help' || arg === '-h');
1294
+ if (definition && helpRequested) {
1295
+ io.stdout.write(renderCommandHelp(definition));
1296
+ return finish(EXIT_CODES.success);
1297
+ }
1298
+ const groupDefinition = commandName === undefined ? commandGroups.find(row => row.name === group) : undefined;
1299
+ if (groupDefinition && helpRequested) {
1300
+ io.stdout.write(renderGroupHelp(groupDefinition));
1301
+ return finish(EXIT_CODES.success);
1302
+ }
1303
+ // A hyphen where a subcommand's space belongs is the likeliest slip, so name the spelling that runs.
1304
+ const respelled = definition || !commandName ? undefined : commands.find(command => command.name === commandName.replace('-', ' '));
1305
+ // Resolved before dispatch: the publication tail below may only forward outcomes, not decide anything.
1306
+ const helpTarget = definition ?? respelled;
1307
+ const usageHint = helpTarget ? `Run 'npx --no cockpit ${helpTarget.name} --help' for usage.\n`
1308
+ : group ? `Run 'npx --no cockpit ${group} --help' for its subcommands.\n`
1309
+ : commandName ? `Run 'npx --no -- cockpit --help' for the command list.\n` : renderHelp();
1310
+ let jsonRequested = optionArgs.includes('--json');
1311
+ let outcome;
1312
+ // parseArgs keeps the last of a repeated option, so a repeat is refused before it can be dropped.
1313
+ const duplicateOption = definition && Object.entries(definition.options).find(([name, option]) => name !== 'json' && !('multiple' in option && option.multiple === true) && optionCount(optionArgs, name) > 1)?.[0];
1314
+ if (!commandName) {
1315
+ outcome = usageOutcome('CLI_COMMAND_REQUIRED', group === undefined ? 'a command is required'
1316
+ : `${group} requires a subcommand: ${groupMembers(group).map(member => member.name.slice(group.length + 1)).join(', ')}`);
1317
+ }
1318
+ else if (!definition) {
1319
+ outcome = usageOutcome('CLI_UNKNOWN_COMMAND', `unknown command "${commandName}"${respelled ? `; did you mean "${respelled.name}"?` : ''}`);
1320
+ }
1321
+ else if (definition.handler === 'registry-resolve' && optionCount(optionArgs, 'output') > 0) {
1322
+ outcome = usageOutcome('REGISTRY_OUTPUT_UNSUPPORTED', 'registry-resolve writes artifact bytes to stdout', '--output');
1323
+ }
1324
+ else if (duplicateOption !== undefined) {
1325
+ // Each code is a literal in its own arm so the diagnostic inventory lists it under its command only.
1326
+ const message = `each ${definition.name} option may be supplied only once`;
1327
+ outcome = definition.handler === 'registry-resolve' ? usageOutcome('REGISTRY_OPTION_DUPLICATE', message, `--${duplicateOption}`)
1328
+ : definition.handler === 'setup' ? usageOutcome(ENVIRONMENT_CODES.SETUP_OPTION_DUPLICATE, message, `--${duplicateOption}`)
1329
+ : usageOutcome('CLI_USAGE_INVALID', message, `--${duplicateOption}`);
1330
+ }
1331
+ else {
1332
+ let parsed;
1333
+ try {
1334
+ parsed = parseArgs({
1335
+ args: commandArgs,
1336
+ strict: true,
1337
+ allowPositionals: true,
1338
+ options: definition.options,
1339
+ });
1340
+ }
1341
+ catch (error) {
1342
+ // parseArgs words its refusal from the command line the person typed; anything else is unexpected.
1343
+ const message = hasErrorCode(error, ...PARSE_ARGS_CODES) ? error.message : errorCause(error);
1344
+ outcome = usageOutcome('CLI_USAGE_INVALID', message);
1345
+ }
1346
+ if (parsed) {
1347
+ jsonRequested = parsed.values.json === true;
1348
+ let decoded = await decodeRequest(definition.handler, parsed, runtime.env ?? process.env);
1349
+ const configured = 'handler' in decoded ? requestConfigPath(decoded) : undefined;
1350
+ if (configured !== undefined) {
1351
+ try {
1352
+ resolvedConfigPath = createInvocationRecord({ verb, durationMs: 0, exitCode: EXIT_CODES.success,
1353
+ resolvedConfigPath: resolve(configured) }).resolvedConfigPath;
1354
+ }
1355
+ catch { // createInvocationRecord throws only for control characters, which the message names.
1356
+ decoded = usageOutcome('CLI_USAGE_INVALID', 'config path must be recordable without control characters', '--config');
1357
+ }
1358
+ }
1359
+ if ('scaffold' in decoded) {
1360
+ try {
1361
+ outcome = await (handlerOverrides.scaffoldCompose ?? runComposeScaffold)(decoded.scaffold);
1362
+ }
1363
+ catch (error) {
1364
+ outcome = usageOutcome('CLI_INTERNAL_ERROR', `the scaffold failed unexpectedly (${errorCause(error)})`, '--scaffold-compose');
1365
+ }
1366
+ }
1367
+ else if (!('handler' in decoded)) {
1368
+ outcome = decoded;
1369
+ }
1370
+ else if (decoded.handler === 'logs' && decoded.input.follow && !runtime.signal) {
1371
+ outcome = usageOutcome('CLI_USAGE_INVALID', '--follow requires a cancellation signal', '--follow');
1372
+ }
1373
+ else {
1374
+ try {
1375
+ outcome = await invokeHandler(decoded, handlers, operators, {
1376
+ interactive: parsed.values.interactive === true,
1377
+ apply: parsed.values.apply === true,
1378
+ json: parsed.values.json === true,
1379
+ ci: (runtime.env ?? process.env).CI,
1380
+ env: runtime.env ?? process.env,
1381
+ }, io);
1382
+ }
1383
+ catch (error) {
1384
+ outcome = usageOutcome('CLI_INTERNAL_ERROR', `the "${definition.handler}" command failed unexpectedly (${errorCause(error)})`, definition.handler);
1385
+ }
1386
+ }
1387
+ if (!parsed.values.json) {
1388
+ try {
1389
+ outcome = await executeOutcome(outcome);
1390
+ }
1391
+ catch (error) {
1392
+ outcome = usageOutcome('CLI_INTERNAL_ERROR', `the "${definition.handler}:output" operation failed unexpectedly (${errorCause(error)})`, `${definition.handler}:output`);
1393
+ }
1394
+ }
1395
+ }
1396
+ }
1397
+ if (jsonRequested) {
1398
+ return finish(outcome.status, outcome);
1399
+ }
1400
+ if (outcome.artifact && !outcome.destination)
1401
+ io.stdout.write(outcome.artifact);
1402
+ io.stdout.write(renderHumanDiagnostics([], { command: commandName, outcome: outcome }, outcome.fixContext));
1403
+ io.stderr.write(renderHumanDiagnostics(outcome.diagnostics, undefined, outcome.fixContext, usageHint));
1404
+ return finish(outcome.status);
1405
+ }