@intutic/cli 1.10.0 → 2.0.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 (357) hide show
  1. package/README.md +1 -0
  2. package/dist/cli.js +868 -30
  3. package/dist/cli.js.map +1 -1
  4. package/dist/commands/attenuate.d.ts +39 -0
  5. package/dist/commands/attenuate.d.ts.map +1 -0
  6. package/dist/commands/attenuate.js +98 -0
  7. package/dist/commands/attenuate.js.map +1 -0
  8. package/dist/commands/budget.d.ts +80 -0
  9. package/dist/commands/budget.d.ts.map +1 -1
  10. package/dist/commands/budget.js +119 -3
  11. package/dist/commands/budget.js.map +1 -1
  12. package/dist/commands/connect.d.ts +10 -0
  13. package/dist/commands/connect.d.ts.map +1 -1
  14. package/dist/commands/connect.js +358 -48
  15. package/dist/commands/connect.js.map +1 -1
  16. package/dist/commands/credentials.d.ts +31 -0
  17. package/dist/commands/credentials.d.ts.map +1 -0
  18. package/dist/commands/credentials.js +134 -0
  19. package/dist/commands/credentials.js.map +1 -0
  20. package/dist/commands/decision.d.ts +37 -0
  21. package/dist/commands/decision.d.ts.map +1 -0
  22. package/dist/commands/decision.js +56 -0
  23. package/dist/commands/decision.js.map +1 -0
  24. package/dist/commands/doctor.d.ts +38 -1
  25. package/dist/commands/doctor.d.ts.map +1 -1
  26. package/dist/commands/doctor.js +228 -18
  27. package/dist/commands/doctor.js.map +1 -1
  28. package/dist/commands/enforce.d.ts +33 -0
  29. package/dist/commands/enforce.d.ts.map +1 -0
  30. package/dist/commands/enforce.js +150 -0
  31. package/dist/commands/enforce.js.map +1 -0
  32. package/dist/commands/enterprise.d.ts +29 -0
  33. package/dist/commands/enterprise.d.ts.map +1 -0
  34. package/dist/commands/enterprise.js +141 -0
  35. package/dist/commands/enterprise.js.map +1 -0
  36. package/dist/commands/env.d.ts +7 -0
  37. package/dist/commands/env.d.ts.map +1 -0
  38. package/dist/commands/env.js +55 -0
  39. package/dist/commands/env.js.map +1 -0
  40. package/dist/commands/exec.d.ts +38 -2
  41. package/dist/commands/exec.d.ts.map +1 -1
  42. package/dist/commands/exec.js +247 -10
  43. package/dist/commands/exec.js.map +1 -1
  44. package/dist/commands/findings.d.ts +84 -0
  45. package/dist/commands/findings.d.ts.map +1 -0
  46. package/dist/commands/findings.js +329 -0
  47. package/dist/commands/findings.js.map +1 -0
  48. package/dist/commands/gateway.d.ts +62 -0
  49. package/dist/commands/gateway.d.ts.map +1 -0
  50. package/dist/commands/gateway.js +298 -0
  51. package/dist/commands/gateway.js.map +1 -0
  52. package/dist/commands/guardrails.d.ts +81 -0
  53. package/dist/commands/guardrails.d.ts.map +1 -0
  54. package/dist/commands/guardrails.js +735 -0
  55. package/dist/commands/guardrails.js.map +1 -0
  56. package/dist/commands/init.d.ts +8 -0
  57. package/dist/commands/init.d.ts.map +1 -1
  58. package/dist/commands/init.js +14 -2
  59. package/dist/commands/init.js.map +1 -1
  60. package/dist/commands/install-daemon.d.ts +70 -3
  61. package/dist/commands/install-daemon.d.ts.map +1 -1
  62. package/dist/commands/install-daemon.js +482 -47
  63. package/dist/commands/install-daemon.js.map +1 -1
  64. package/dist/commands/integrity.d.ts +205 -0
  65. package/dist/commands/integrity.d.ts.map +1 -0
  66. package/dist/commands/integrity.js +620 -0
  67. package/dist/commands/integrity.js.map +1 -0
  68. package/dist/commands/judge.d.ts +31 -0
  69. package/dist/commands/judge.d.ts.map +1 -0
  70. package/dist/commands/judge.js +118 -0
  71. package/dist/commands/judge.js.map +1 -0
  72. package/dist/commands/org.d.ts +31 -0
  73. package/dist/commands/org.d.ts.map +1 -0
  74. package/dist/commands/org.js +159 -0
  75. package/dist/commands/org.js.map +1 -0
  76. package/dist/commands/policy.d.ts +138 -4
  77. package/dist/commands/policy.d.ts.map +1 -1
  78. package/dist/commands/policy.js +538 -97
  79. package/dist/commands/policy.js.map +1 -1
  80. package/dist/commands/predict-cost.d.ts +45 -0
  81. package/dist/commands/predict-cost.d.ts.map +1 -0
  82. package/dist/commands/predict-cost.js +123 -0
  83. package/dist/commands/predict-cost.js.map +1 -0
  84. package/dist/commands/rollback.d.ts +5 -0
  85. package/dist/commands/rollback.d.ts.map +1 -0
  86. package/dist/commands/rollback.js +142 -0
  87. package/dist/commands/rollback.js.map +1 -0
  88. package/dist/commands/routing.d.ts +62 -0
  89. package/dist/commands/routing.d.ts.map +1 -0
  90. package/dist/commands/routing.js +121 -0
  91. package/dist/commands/routing.js.map +1 -0
  92. package/dist/commands/setup.d.ts +62 -0
  93. package/dist/commands/setup.d.ts.map +1 -0
  94. package/dist/commands/setup.js +238 -0
  95. package/dist/commands/setup.js.map +1 -0
  96. package/dist/commands/skill.d.ts +268 -1
  97. package/dist/commands/skill.d.ts.map +1 -1
  98. package/dist/commands/skill.js +744 -39
  99. package/dist/commands/skill.js.map +1 -1
  100. package/dist/commands/sops.d.ts +95 -0
  101. package/dist/commands/sops.d.ts.map +1 -1
  102. package/dist/commands/sops.js +334 -34
  103. package/dist/commands/sops.js.map +1 -1
  104. package/dist/commands/start.d.ts.map +1 -1
  105. package/dist/commands/start.js +1 -18
  106. package/dist/commands/start.js.map +1 -1
  107. package/dist/commands/status.d.ts.map +1 -1
  108. package/dist/commands/status.js +44 -1
  109. package/dist/commands/status.js.map +1 -1
  110. package/dist/commands/team.d.ts +38 -0
  111. package/dist/commands/team.d.ts.map +1 -0
  112. package/dist/commands/team.js +140 -0
  113. package/dist/commands/team.js.map +1 -0
  114. package/dist/commands/traces.d.ts +8 -1
  115. package/dist/commands/traces.d.ts.map +1 -1
  116. package/dist/commands/traces.js +114 -15
  117. package/dist/commands/traces.js.map +1 -1
  118. package/dist/commands/whoami.d.ts.map +1 -1
  119. package/dist/commands/whoami.js +2 -1
  120. package/dist/commands/whoami.js.map +1 -1
  121. package/dist/config/keychain.d.ts +10 -2
  122. package/dist/config/keychain.d.ts.map +1 -1
  123. package/dist/config/keychain.js +83 -14
  124. package/dist/config/keychain.js.map +1 -1
  125. package/dist/config/paths.d.ts +27 -0
  126. package/dist/config/paths.d.ts.map +1 -1
  127. package/dist/config/paths.js +37 -6
  128. package/dist/config/paths.js.map +1 -1
  129. package/dist/config/store.d.ts.map +1 -1
  130. package/dist/config/store.js +19 -3
  131. package/dist/config/store.js.map +1 -1
  132. package/dist/harness/ag2.d.ts +22 -0
  133. package/dist/harness/ag2.d.ts.map +1 -0
  134. package/dist/harness/ag2.js +34 -0
  135. package/dist/harness/ag2.js.map +1 -0
  136. package/dist/harness/agentcore.d.ts +55 -0
  137. package/dist/harness/agentcore.d.ts.map +1 -0
  138. package/dist/harness/agentcore.js +119 -0
  139. package/dist/harness/agentcore.js.map +1 -0
  140. package/dist/harness/agenticOrchestrator.d.ts +52 -0
  141. package/dist/harness/agenticOrchestrator.d.ts.map +1 -0
  142. package/dist/harness/agenticOrchestrator.js +115 -0
  143. package/dist/harness/agenticOrchestrator.js.map +1 -0
  144. package/dist/harness/aiSdkHarness.d.ts +30 -0
  145. package/dist/harness/aiSdkHarness.d.ts.map +1 -0
  146. package/dist/harness/aiSdkHarness.js +51 -0
  147. package/dist/harness/aiSdkHarness.js.map +1 -0
  148. package/dist/harness/aiSdkWorkflow.d.ts +26 -0
  149. package/dist/harness/aiSdkWorkflow.d.ts.map +1 -0
  150. package/dist/harness/aiSdkWorkflow.js +39 -0
  151. package/dist/harness/aiSdkWorkflow.js.map +1 -0
  152. package/dist/harness/autogen.d.ts +24 -0
  153. package/dist/harness/autogen.d.ts.map +1 -0
  154. package/dist/harness/autogen.js +36 -0
  155. package/dist/harness/autogen.js.map +1 -0
  156. package/dist/harness/base.js.map +1 -1
  157. package/dist/harness/continue.d.ts.map +1 -1
  158. package/dist/harness/continue.js +2 -1
  159. package/dist/harness/continue.js.map +1 -1
  160. package/dist/harness/crewai.d.ts +15 -0
  161. package/dist/harness/crewai.d.ts.map +1 -0
  162. package/dist/harness/crewai.js +26 -0
  163. package/dist/harness/crewai.js.map +1 -0
  164. package/dist/harness/detector.d.ts +1 -1
  165. package/dist/harness/detector.d.ts.map +1 -1
  166. package/dist/harness/detector.js +47 -1
  167. package/dist/harness/detector.js.map +1 -1
  168. package/dist/harness/dsh.d.ts +19 -0
  169. package/dist/harness/dsh.d.ts.map +1 -0
  170. package/dist/harness/dsh.js +93 -0
  171. package/dist/harness/dsh.js.map +1 -0
  172. package/dist/harness/eve.d.ts +40 -0
  173. package/dist/harness/eve.d.ts.map +1 -0
  174. package/dist/harness/eve.js +67 -0
  175. package/dist/harness/eve.js.map +1 -0
  176. package/dist/harness/googleAdk.d.ts +18 -0
  177. package/dist/harness/googleAdk.d.ts.map +1 -0
  178. package/dist/harness/googleAdk.js +29 -0
  179. package/dist/harness/googleAdk.js.map +1 -0
  180. package/dist/harness/goose.d.ts.map +1 -1
  181. package/dist/harness/goose.js +5 -1
  182. package/dist/harness/goose.js.map +1 -1
  183. package/dist/harness/grok.d.ts +16 -0
  184. package/dist/harness/grok.d.ts.map +1 -0
  185. package/dist/harness/grok.js +78 -0
  186. package/dist/harness/grok.js.map +1 -0
  187. package/dist/harness/jsSdkGatedAdapter.d.ts +102 -0
  188. package/dist/harness/jsSdkGatedAdapter.d.ts.map +1 -0
  189. package/dist/harness/jsSdkGatedAdapter.js +139 -0
  190. package/dist/harness/jsSdkGatedAdapter.js.map +1 -0
  191. package/dist/harness/langchain.d.ts +22 -0
  192. package/dist/harness/langchain.d.ts.map +1 -0
  193. package/dist/harness/langchain.js +33 -0
  194. package/dist/harness/langchain.js.map +1 -0
  195. package/dist/harness/langgraph.d.ts +22 -0
  196. package/dist/harness/langgraph.d.ts.map +1 -0
  197. package/dist/harness/langgraph.js +80 -0
  198. package/dist/harness/langgraph.js.map +1 -0
  199. package/dist/harness/mastra.d.ts +21 -0
  200. package/dist/harness/mastra.d.ts.map +1 -0
  201. package/dist/harness/mastra.js +33 -0
  202. package/dist/harness/mastra.js.map +1 -0
  203. package/dist/harness/muse.d.ts +22 -0
  204. package/dist/harness/muse.d.ts.map +1 -0
  205. package/dist/harness/muse.js +92 -0
  206. package/dist/harness/muse.js.map +1 -0
  207. package/dist/harness/n8n.d.ts.map +1 -1
  208. package/dist/harness/n8n.js +36 -11
  209. package/dist/harness/n8n.js.map +1 -1
  210. package/dist/harness/openaiAgents.d.ts +40 -0
  211. package/dist/harness/openaiAgents.d.ts.map +1 -0
  212. package/dist/harness/openaiAgents.js +107 -0
  213. package/dist/harness/openaiAgents.js.map +1 -0
  214. package/dist/harness/opencode.d.ts +24 -0
  215. package/dist/harness/opencode.d.ts.map +1 -0
  216. package/dist/harness/opencode.js +97 -0
  217. package/dist/harness/opencode.js.map +1 -0
  218. package/dist/harness/pydanticAi.d.ts +19 -0
  219. package/dist/harness/pydanticAi.d.ts.map +1 -0
  220. package/dist/harness/pydanticAi.js +30 -0
  221. package/dist/harness/pydanticAi.js.map +1 -0
  222. package/dist/harness/sdkGatedAdapter.d.ts +55 -0
  223. package/dist/harness/sdkGatedAdapter.d.ts.map +1 -0
  224. package/dist/harness/sdkGatedAdapter.js +85 -0
  225. package/dist/harness/sdkGatedAdapter.js.map +1 -0
  226. package/dist/harness/smolagents.d.ts +22 -0
  227. package/dist/harness/smolagents.d.ts.map +1 -0
  228. package/dist/harness/smolagents.js +33 -0
  229. package/dist/harness/smolagents.js.map +1 -0
  230. package/dist/harness/strands.d.ts +22 -0
  231. package/dist/harness/strands.d.ts.map +1 -0
  232. package/dist/harness/strands.js +35 -0
  233. package/dist/harness/strands.js.map +1 -0
  234. package/dist/harness/trueforge.d.ts +25 -0
  235. package/dist/harness/trueforge.d.ts.map +1 -0
  236. package/dist/harness/trueforge.js +40 -0
  237. package/dist/harness/trueforge.js.map +1 -0
  238. package/dist/harness/types.d.ts.map +1 -1
  239. package/dist/harness/types.js +70 -0
  240. package/dist/harness/types.js.map +1 -1
  241. package/dist/harness/vercelAiSdk.d.ts +31 -0
  242. package/dist/harness/vercelAiSdk.d.ts.map +1 -0
  243. package/dist/harness/vercelAiSdk.js +44 -0
  244. package/dist/harness/vercelAiSdk.js.map +1 -0
  245. package/dist/harness/xirp.d.ts +43 -0
  246. package/dist/harness/xirp.d.ts.map +1 -0
  247. package/dist/harness/xirp.js +95 -0
  248. package/dist/harness/xirp.js.map +1 -0
  249. package/dist/lib/api.d.ts +68 -0
  250. package/dist/lib/api.d.ts.map +1 -1
  251. package/dist/lib/api.js +108 -67
  252. package/dist/lib/api.js.map +1 -1
  253. package/dist/lib/authMessages.d.ts +19 -0
  254. package/dist/lib/authMessages.d.ts.map +1 -0
  255. package/dist/lib/authMessages.js +20 -0
  256. package/dist/lib/authMessages.js.map +1 -0
  257. package/dist/lib/binaryChecksum.d.ts +12 -0
  258. package/dist/lib/binaryChecksum.d.ts.map +1 -0
  259. package/dist/lib/binaryChecksum.js +45 -0
  260. package/dist/lib/binaryChecksum.js.map +1 -0
  261. package/dist/lib/binaryOnPath.d.ts +24 -0
  262. package/dist/lib/binaryOnPath.d.ts.map +1 -0
  263. package/dist/lib/binaryOnPath.js +31 -0
  264. package/dist/lib/binaryOnPath.js.map +1 -0
  265. package/dist/lib/caTrust.d.ts +69 -0
  266. package/dist/lib/caTrust.d.ts.map +1 -0
  267. package/dist/lib/caTrust.js +169 -0
  268. package/dist/lib/caTrust.js.map +1 -0
  269. package/dist/lib/ciscoScanner.d.ts +178 -0
  270. package/dist/lib/ciscoScanner.d.ts.map +1 -0
  271. package/dist/lib/ciscoScanner.js +292 -0
  272. package/dist/lib/ciscoScanner.js.map +1 -0
  273. package/dist/lib/decisionsLogRefresh.d.ts +28 -0
  274. package/dist/lib/decisionsLogRefresh.d.ts.map +1 -0
  275. package/dist/lib/decisionsLogRefresh.js +54 -0
  276. package/dist/lib/decisionsLogRefresh.js.map +1 -0
  277. package/dist/lib/deviceReport.d.ts +26 -0
  278. package/dist/lib/deviceReport.d.ts.map +1 -0
  279. package/dist/lib/deviceReport.js +55 -0
  280. package/dist/lib/deviceReport.js.map +1 -0
  281. package/dist/lib/elevation.d.ts +58 -0
  282. package/dist/lib/elevation.d.ts.map +1 -0
  283. package/dist/lib/elevation.js +83 -0
  284. package/dist/lib/elevation.js.map +1 -0
  285. package/dist/lib/enforcementState.d.ts +69 -0
  286. package/dist/lib/enforcementState.d.ts.map +1 -0
  287. package/dist/lib/enforcementState.js +113 -0
  288. package/dist/lib/enforcementState.js.map +1 -0
  289. package/dist/lib/ensureValkey.d.ts.map +1 -1
  290. package/dist/lib/ensureValkey.js +2 -2
  291. package/dist/lib/ensureValkey.js.map +1 -1
  292. package/dist/lib/envInjector.d.ts.map +1 -1
  293. package/dist/lib/envInjector.js +22 -4
  294. package/dist/lib/envInjector.js.map +1 -1
  295. package/dist/lib/gitHooks.d.ts +7 -2
  296. package/dist/lib/gitHooks.d.ts.map +1 -1
  297. package/dist/lib/gitHooks.js +180 -13
  298. package/dist/lib/gitHooks.js.map +1 -1
  299. package/dist/lib/gracefulShutdown.d.ts +37 -0
  300. package/dist/lib/gracefulShutdown.d.ts.map +1 -0
  301. package/dist/lib/gracefulShutdown.js +57 -0
  302. package/dist/lib/gracefulShutdown.js.map +1 -0
  303. package/dist/lib/graphIdentity.d.ts +80 -0
  304. package/dist/lib/graphIdentity.d.ts.map +1 -0
  305. package/dist/lib/graphIdentity.js +120 -0
  306. package/dist/lib/graphIdentity.js.map +1 -0
  307. package/dist/lib/localTraces.d.ts +61 -0
  308. package/dist/lib/localTraces.d.ts.map +1 -0
  309. package/dist/lib/localTraces.js +173 -0
  310. package/dist/lib/localTraces.js.map +1 -0
  311. package/dist/lib/mdmManifest.d.ts +73 -0
  312. package/dist/lib/mdmManifest.d.ts.map +1 -0
  313. package/dist/lib/mdmManifest.js +190 -0
  314. package/dist/lib/mdmManifest.js.map +1 -0
  315. package/dist/lib/onboarding.d.ts.map +1 -1
  316. package/dist/lib/onboarding.js +60 -1
  317. package/dist/lib/onboarding.js.map +1 -1
  318. package/dist/lib/otelPropagation.d.ts +26 -0
  319. package/dist/lib/otelPropagation.d.ts.map +1 -0
  320. package/dist/lib/otelPropagation.js +40 -0
  321. package/dist/lib/otelPropagation.js.map +1 -0
  322. package/dist/lib/policySnapshot.d.ts +92 -0
  323. package/dist/lib/policySnapshot.d.ts.map +1 -0
  324. package/dist/lib/policySnapshot.js +173 -0
  325. package/dist/lib/policySnapshot.js.map +1 -0
  326. package/dist/lib/process.d.ts.map +1 -1
  327. package/dist/lib/process.js +60 -0
  328. package/dist/lib/process.js.map +1 -1
  329. package/dist/lib/providerProbe.d.ts +28 -0
  330. package/dist/lib/providerProbe.d.ts.map +1 -0
  331. package/dist/lib/providerProbe.js +57 -0
  332. package/dist/lib/providerProbe.js.map +1 -0
  333. package/dist/lib/proxyBinary.d.ts +19 -0
  334. package/dist/lib/proxyBinary.d.ts.map +1 -0
  335. package/dist/lib/proxyBinary.js +30 -0
  336. package/dist/lib/proxyBinary.js.map +1 -0
  337. package/dist/lib/sandbox/firecracker.d.ts +89 -0
  338. package/dist/lib/sandbox/firecracker.d.ts.map +1 -0
  339. package/dist/lib/sandbox/firecracker.js +135 -0
  340. package/dist/lib/sandbox/firecracker.js.map +1 -0
  341. package/dist/lib/sandbox/index.d.ts +34 -0
  342. package/dist/lib/sandbox/index.d.ts.map +1 -0
  343. package/dist/lib/sandbox/index.js +54 -0
  344. package/dist/lib/sandbox/index.js.map +1 -0
  345. package/dist/lib/sandbox/oci.d.ts +38 -0
  346. package/dist/lib/sandbox/oci.d.ts.map +1 -0
  347. package/dist/lib/sandbox/oci.js +108 -0
  348. package/dist/lib/sandbox/oci.js.map +1 -0
  349. package/dist/lib/sandbox/types.d.ts +48 -0
  350. package/dist/lib/sandbox/types.d.ts.map +1 -0
  351. package/dist/lib/sandbox/types.js +12 -0
  352. package/dist/lib/sandbox/types.js.map +1 -0
  353. package/dist/lib/sopFrontMatter.d.ts +50 -0
  354. package/dist/lib/sopFrontMatter.d.ts.map +1 -0
  355. package/dist/lib/sopFrontMatter.js +129 -0
  356. package/dist/lib/sopFrontMatter.js.map +1 -0
  357. package/package.json +11 -6
package/dist/cli.js CHANGED
@@ -32,6 +32,26 @@ program
32
32
  const { runInit } = await import('./commands/init.js');
33
33
  await runInit(opts);
34
34
  });
35
+ // ── Cohort setup wizard (LLD #70) ────────────────────────────────────────
36
+ program
37
+ .command('setup')
38
+ .description('Guided setup: detect harnesses, configure a provider credential, verify it, and (optionally) a judge model')
39
+ .option('--dev', 'Use local control plane (http://localhost:3001)')
40
+ .action(async (opts) => {
41
+ const { runSetup } = await import('./commands/setup.js');
42
+ await runSetup(opts);
43
+ });
44
+ const judgeCmd = program
45
+ .command('judge')
46
+ .description('Configure the LLM-as-judge path');
47
+ judgeCmd
48
+ .command('configure')
49
+ .description('Generate local artifacts (litellm_config.yaml, env, Helm values) for an on-prem judge — never a remote API call')
50
+ .option('--out <path>', 'Where to write litellm_config.yaml', './litellm_config.yaml')
51
+ .action(async (opts) => {
52
+ const { runJudgeConfigure } = await import('./commands/judge.js');
53
+ await runJudgeConfigure(opts);
54
+ });
35
55
  program
36
56
  .command('login')
37
57
  .description('Authenticate with the Intutic control plane')
@@ -62,14 +82,45 @@ program
62
82
  const { runDoctor } = await import('./commands/doctor.js');
63
83
  await runDoctor();
64
84
  });
85
+ program
86
+ .command('rollback')
87
+ .description('List or restore pre-images captured when a guard flagged a file-writing call')
88
+ .option('--list', 'List captured pre-images (the default with no --id)')
89
+ .option('--id <id>', 'Restore the named pre-image')
90
+ .action(async (opts) => {
91
+ const { runRollback } = await import('./commands/rollback.js');
92
+ await runRollback(opts);
93
+ });
65
94
  program
66
95
  .command('budget')
67
96
  .description('Check remaining daily/monthly budget and list active loops')
68
97
  .option('--dev', 'Use local control plane (http://localhost:3001)')
98
+ .option('--watch', 'Continuously print machine-local and workspace spend, one line per tick')
99
+ .option('--interval <seconds>', 'Tick interval in seconds for --watch', String(5))
69
100
  .action(async (opts) => {
101
+ if (opts.watch) {
102
+ const { runBudgetWatch } = await import('./commands/budget.js');
103
+ await runBudgetWatch(opts);
104
+ return;
105
+ }
70
106
  const { runBudget } = await import('./commands/budget.js');
71
107
  await runBudget(opts);
72
108
  });
109
+ program
110
+ .command('predict-cost')
111
+ .description('Pre-flight cost estimate for a prompt/task before it runs (POST /api/v1/predict-cost). ' +
112
+ 'Estimates output tokens from the workspace baseline and prices with the same rate table ' +
113
+ 'every other cost figure in the control plane uses.')
114
+ .requiredOption('--model <model>', 'Model to estimate against (e.g. claude-sonnet-4-5)')
115
+ .option('--task-type <type>', 'Task type used to pick the baseline bucket', 'coding')
116
+ .option('--tokens <n>', 'Input token count (mutually exclusive with --file)')
117
+ .option('--file <path>', 'File whose contents size the input (mutually exclusive with --tokens)')
118
+ .option('--json', 'Output as JSON instead of a report')
119
+ .option('--dev', 'Use local control plane (http://localhost:3001)')
120
+ .action(async (opts) => {
121
+ const { runPredictCost } = await import('./commands/predict-cost.js');
122
+ await runPredictCost(opts);
123
+ });
73
124
  const sopsCmd = program
74
125
  .command('sops')
75
126
  .description('Manage local and global SOP rules');
@@ -77,10 +128,44 @@ sopsCmd
77
128
  .command('push <name>')
78
129
  .description('Push a local offline SOP folder to the central workspace')
79
130
  .option('--dev', 'Use local control plane (http://localhost:3001)')
131
+ .option('--org', "Push as an org-wide floor (LLD #65 follow-up) instead of a workspace SOP -- applies to every workspace under the caller's org, in addition to that workspace's own SOPs")
80
132
  .action(async (name, opts) => {
81
133
  const { runSopsPush } = await import('./commands/sops.js');
82
134
  await runSopsPush(name, opts);
83
135
  });
136
+ sopsCmd
137
+ .command('pull')
138
+ .description('Pull every workspace SOP from the control plane into .intutic/sops/<slug>.md')
139
+ .option('--dev', 'Use local control plane (http://localhost:3001)')
140
+ .option('--force', 'Overwrite locally-modified files instead of refusing them')
141
+ .action(async (opts) => {
142
+ const { runSopsPull } = await import('./commands/sops.js');
143
+ await runSopsPull(opts);
144
+ });
145
+ sopsCmd
146
+ .command('status')
147
+ .description('Show drift between .intutic/sops/*.md and the control plane, read-only')
148
+ .option('--dev', 'Use local control plane (http://localhost:3001)')
149
+ .action(async (opts) => {
150
+ const { runSopsStatus } = await import('./commands/sops.js');
151
+ await runSopsStatus(opts);
152
+ });
153
+ sopsCmd
154
+ .command('org-list')
155
+ .description("List org-wide SOP floors for the caller's own org (LLD #65 follow-up)")
156
+ .option('--dev', 'Use local control plane (http://localhost:3001)')
157
+ .action(async (opts) => {
158
+ const { runSopsOrgList } = await import('./commands/sops.js');
159
+ await runSopsOrgList(opts);
160
+ });
161
+ sopsCmd
162
+ .command('org-rm <orgSopId>')
163
+ .description('Remove (soft-delete) an org-wide SOP floor')
164
+ .option('--dev', 'Use local control plane (http://localhost:3001)')
165
+ .action(async (orgSopId, opts) => {
166
+ const { runSopsOrgRm } = await import('./commands/sops.js');
167
+ await runSopsOrgRm(orgSopId, opts);
168
+ });
84
169
  const policyCmd = program
85
170
  .command('policy')
86
171
  .description('Manage compliance and safety policies');
@@ -109,6 +194,17 @@ policyCmd
109
194
  const { runPolicyRollback } = await import('./commands/policy.js');
110
195
  await runPolicyRollback(policyId, opts);
111
196
  });
197
+ policyCmd
198
+ .command('snapshot')
199
+ .description('Compile workspace policy to ~/.intutic/hooks/policy-snapshot.rules — the file every gate reads.\n' +
200
+ '\n' +
201
+ ' Does only that. \'intutic connect\' writes it too, but as one step of\n' +
202
+ ' starting the full sync daemon; this arms the gates without one.')
203
+ .option('--dev', 'Use local control plane (http://localhost:3001)')
204
+ .action(async (opts) => {
205
+ const { runPolicySnapshot } = await import('./commands/policy.js');
206
+ await runPolicySnapshot(opts);
207
+ });
112
208
  policyCmd
113
209
  .command('export')
114
210
  .description('Export compliance policies to stdout')
@@ -130,9 +226,12 @@ policyCmd
130
226
  policyCmd
131
227
  .command('compile')
132
228
  .description('Compile an AssemblyScript rule to WASM (wraps asc)')
133
- .option('--src <path>', 'Rule source entry file', 'assembly/index.ts')
134
- .option('--out <path>', 'Output .wasm path', 'build/rule.wasm')
229
+ .option('--src <path>', 'Rule source entry file (default: assembly/index.ts)')
230
+ .option('--out <path>', 'Output .wasm path (default: build/rule.wasm, or build/<candidate>.wasm with --candidate)')
135
231
  .option('--debug', 'Include debug info and source maps')
232
+ .option('--candidate <id>', 'Compile a rule candidate from its source of record on the control plane (fetched and hash-verified; not with --src)')
233
+ .option('--upload', 'With --candidate: upload the bundle to the candidate with its source hash and print the gate results')
234
+ .option('--dev', 'Use local control plane (http://localhost:3001)')
136
235
  .action(async (opts) => {
137
236
  const { runPolicyCompile } = await import('./commands/policy.js');
138
237
  await runPolicyCompile(opts);
@@ -154,6 +253,217 @@ policyCmd
154
253
  const { runPolicyListLocal } = await import('./commands/policy.js');
155
254
  await runPolicyListLocal();
156
255
  });
256
+ policyCmd
257
+ .command('replay <ruleId>')
258
+ .description("Replay a rule (installed or not) against this workspace's own recent traffic and report " +
259
+ 'what it would have done, without touching enforcement.')
260
+ .option('--limit <n>', 'Max sampled contexts to replay against (default 500, capped at 5000)')
261
+ .option('--since <duration>', 'Only contexts at or after this time — relative ("7d", "24h") or ISO 8601')
262
+ .option('--dev', 'Use local control plane (http://localhost:3001)')
263
+ .action(async (ruleId, opts) => {
264
+ const { runPolicyReplay } = await import('./commands/policy.js');
265
+ await runPolicyReplay(ruleId, opts);
266
+ });
267
+ // Policy Clause Ledger (LLD #71). `intutic policy` is the WASM loop and
268
+ // `intutic sops` is files on disk; this namespace is the cited guardrails
269
+ // extracted from policy documents and their shadow -> promote lifecycle.
270
+ const guardrailsCmd = program
271
+ .command('guardrails')
272
+ .description('Policy documents, cited clauses and the guardrails compiled from them');
273
+ const guardrailsSourcesCmd = guardrailsCmd
274
+ .command('sources')
275
+ .description('Policy sources (Notion, Confluence, GitHub, Google Docs) feeding the ledger');
276
+ guardrailsSourcesCmd
277
+ .command('list')
278
+ .description('List configured policy sources and their last sync')
279
+ .option('--json', 'Output as JSON')
280
+ .option('--dev', 'Use local control plane (http://localhost:3001)')
281
+ .action(async (opts) => {
282
+ const { runGuardrailsSourcesList } = await import('./commands/guardrails.js');
283
+ await runGuardrailsSourcesList(opts);
284
+ });
285
+ guardrailsSourcesCmd
286
+ .command('add <provider>')
287
+ .description('Connect a policy source; the credential is read from --token or --token-file, never echoed')
288
+ .requiredOption('--name <name>', 'Display name for the source')
289
+ .option('--token <token>', 'Provider credential (integration token or API key)')
290
+ .option('--token-file <path>', 'Read the credential from a file (a Google service-account key JSON)')
291
+ .option('--config <json>', 'Provider-specific settings as a JSON object (gdrive: {"folder_id": "…"})')
292
+ .option('--json', 'Output as JSON')
293
+ .option('--dev', 'Use local control plane (http://localhost:3001)')
294
+ .action(async (provider, opts) => {
295
+ const { runGuardrailsSourcesAdd } = await import('./commands/guardrails.js');
296
+ await runGuardrailsSourcesAdd(provider, opts);
297
+ });
298
+ guardrailsSourcesCmd
299
+ .command('sync <connectorId>')
300
+ .description('Pull the source now instead of waiting for the next cron pass')
301
+ .option('--json', 'Output as JSON')
302
+ .option('--dev', 'Use local control plane (http://localhost:3001)')
303
+ .action(async (connectorId, opts) => {
304
+ const { runGuardrailsSourcesSync } = await import('./commands/guardrails.js');
305
+ await runGuardrailsSourcesSync(connectorId, opts);
306
+ });
307
+ const guardrailsDocsCmd = guardrailsCmd
308
+ .command('docs')
309
+ .description('Ingested policy documents and their citable passages');
310
+ guardrailsDocsCmd
311
+ .command('list')
312
+ .description('List ingested policy documents')
313
+ .option('--json', 'Output as JSON')
314
+ .option('--dev', 'Use local control plane (http://localhost:3001)')
315
+ .action(async (opts) => {
316
+ const { runGuardrailsDocsList } = await import('./commands/guardrails.js');
317
+ await runGuardrailsDocsList(opts);
318
+ });
319
+ guardrailsDocsCmd
320
+ .command('show <docId>')
321
+ .description('Show a document, its passages and the clauses extracted from them')
322
+ .option('--json', 'Output as JSON')
323
+ .option('--dev', 'Use local control plane (http://localhost:3001)')
324
+ .action(async (docId, opts) => {
325
+ const { runGuardrailsDocsShow } = await import('./commands/guardrails.js');
326
+ await runGuardrailsDocsShow(docId, opts);
327
+ });
328
+ guardrailsDocsCmd
329
+ .command('extract <docId>')
330
+ .description('Propose cited guardrails from a document (every proposal is re-validated deterministically)')
331
+ .option('--no-llm', 'Only lift existing SOP front matter; do not call the extraction model')
332
+ .option('--json', 'Output as JSON')
333
+ .option('--dev', 'Use local control plane (http://localhost:3001)')
334
+ .action(async (docId, opts) => {
335
+ const { runGuardrailsDocsExtract } = await import('./commands/guardrails.js');
336
+ await runGuardrailsDocsExtract(docId, { ...opts, noLlm: opts.llm === false });
337
+ });
338
+ guardrailsCmd
339
+ .command('search <token>')
340
+ .description('Which passages and guardrails mention a tool or action token (e.g. Bash, terraform); with --text, which passages use these words')
341
+ .option('--text', 'Full-text search over the words of every live passage instead of an exact token')
342
+ .option('--json', 'Output as JSON')
343
+ .option('--dev', 'Use local control plane (http://localhost:3001)')
344
+ .action(async (token, opts) => {
345
+ const { runGuardrailsSearch } = await import('./commands/guardrails.js');
346
+ await runGuardrailsSearch(token, opts);
347
+ });
348
+ guardrailsCmd
349
+ .command('list')
350
+ .description('List guardrails with their status and shadow evidence')
351
+ .option('--status <status>', 'PROPOSED | SHADOW | ENFORCING | REJECTED | RETIRED')
352
+ .option('--target <target>', 'hook_rule | sop_front_matter | wasm_rule')
353
+ .option('--doc <docId>', 'Only guardrails cited from this document')
354
+ .option('--limit <n>', 'Max rows (default 50, capped at 200)')
355
+ .option('--json', 'Output as JSON')
356
+ .option('--dev', 'Use local control plane (http://localhost:3001)')
357
+ .action(async (opts) => {
358
+ const { runGuardrailsList } = await import('./commands/guardrails.js');
359
+ await runGuardrailsList(opts);
360
+ });
361
+ guardrailsCmd
362
+ .command('show <guardrailId>')
363
+ .description('Show a guardrail: cited passage, rendered artifact, validation checks and readiness')
364
+ .option('--json', 'Output as JSON')
365
+ .option('--dev', 'Use local control plane (http://localhost:3001)')
366
+ .action(async (guardrailId, opts) => {
367
+ const { runGuardrailsShow } = await import('./commands/guardrails.js');
368
+ await runGuardrailsShow(guardrailId, opts);
369
+ });
370
+ guardrailsCmd
371
+ .command('approve-shadow <guardrailId>')
372
+ .description('Ship a proposed guardrail in shadow: it reports, never blocks')
373
+ .option('--json', 'Output as JSON')
374
+ .option('--dev', 'Use local control plane (http://localhost:3001)')
375
+ .action(async (guardrailId, opts) => {
376
+ const { runGuardrailsApproveShadow } = await import('./commands/guardrails.js');
377
+ await runGuardrailsApproveShadow(guardrailId, opts);
378
+ });
379
+ guardrailsCmd
380
+ .command('promote <guardrailId>')
381
+ .description('Promote a shadow guardrail to enforcing once the server says it is ready')
382
+ .option('--acknowledge-no-traffic', 'Promote a rule that no observed traffic exercised')
383
+ .option('--json', 'Output as JSON')
384
+ .option('--dev', 'Use local control plane (http://localhost:3001)')
385
+ .action(async (guardrailId, opts) => {
386
+ const { runGuardrailsPromote } = await import('./commands/guardrails.js');
387
+ await runGuardrailsPromote(guardrailId, opts);
388
+ });
389
+ guardrailsCmd
390
+ .command('reject <guardrailId>')
391
+ .description('Reject a guardrail; the reason is recorded on its authority chain')
392
+ .requiredOption('--reason <reason>', 'Why it is rejected')
393
+ .option('--json', 'Output as JSON')
394
+ .option('--dev', 'Use local control plane (http://localhost:3001)')
395
+ .action(async (guardrailId, opts) => {
396
+ const { runGuardrailsReject } = await import('./commands/guardrails.js');
397
+ await runGuardrailsReject(guardrailId, opts);
398
+ });
399
+ guardrailsCmd
400
+ .command('retire <guardrailId>')
401
+ .description('Retire a shadow or enforcing guardrail; it stops being projected')
402
+ .option('--json', 'Output as JSON')
403
+ .option('--dev', 'Use local control plane (http://localhost:3001)')
404
+ .action(async (guardrailId, opts) => {
405
+ const { runGuardrailsRetire } = await import('./commands/guardrails.js');
406
+ await runGuardrailsRetire(guardrailId, opts);
407
+ });
408
+ guardrailsCmd
409
+ .command('reconfirm <guardrailId>')
410
+ .description('Confirm a guardrail whose cited passage changed upstream still holds')
411
+ .option('--json', 'Output as JSON')
412
+ .option('--dev', 'Use local control plane (http://localhost:3001)')
413
+ .action(async (guardrailId, opts) => {
414
+ const { runGuardrailsReconfirm } = await import('./commands/guardrails.js');
415
+ await runGuardrailsReconfirm(guardrailId, opts);
416
+ });
417
+ guardrailsCmd
418
+ .command('replay <guardrailId>')
419
+ .description('Run a guardrail over captured calls and report how many it would have fired on')
420
+ .option('--json', 'Output as JSON')
421
+ .option('--dev', 'Use local control plane (http://localhost:3001)')
422
+ .action(async (guardrailId, opts) => {
423
+ const { runGuardrailsReplay } = await import('./commands/guardrails.js');
424
+ await runGuardrailsReplay(guardrailId, opts);
425
+ });
426
+ guardrailsCmd
427
+ .command('conflicts')
428
+ .description('List guardrails that contradict each other, with both quotes')
429
+ .option('--json', 'Output as JSON')
430
+ .option('--dev', 'Use local control plane (http://localhost:3001)')
431
+ .action(async (opts) => {
432
+ const { runGuardrailsConflicts } = await import('./commands/guardrails.js');
433
+ await runGuardrailsConflicts(opts);
434
+ });
435
+ guardrailsCmd
436
+ .command('impact')
437
+ .description('What a change to a document or passage reaches: passages within five computed edges, the clauses citing them, their guardrails')
438
+ .option('--doc <docId>', 'Start from every live passage of this document')
439
+ .option('--passage <passageId>', 'Start from one passage')
440
+ .option('--json', 'Output as JSON')
441
+ .option('--dev', 'Use local control plane (http://localhost:3001)')
442
+ .action(async (opts) => {
443
+ const { runGuardrailsImpact } = await import('./commands/guardrails.js');
444
+ await runGuardrailsImpact(opts);
445
+ });
446
+ guardrailsCmd
447
+ .command('duplicates')
448
+ .description('Overlapping passage pairs (with their Jaccard arithmetic) and the same rule cited from more than one passage')
449
+ .option('--min-jaccard <n>', 'Only pairs at or above this Jaccard (never below the recorded 0.70)')
450
+ .option('--json', 'Output as JSON')
451
+ .option('--dev', 'Use local control plane (http://localhost:3001)')
452
+ .action(async (opts) => {
453
+ const { runGuardrailsDuplicates } = await import('./commands/guardrails.js');
454
+ await runGuardrailsDuplicates(opts);
455
+ });
456
+ guardrailsCmd
457
+ .command('pull')
458
+ .description('Write the SHADOW and ENFORCING front-matter guardrails to .intutic/sops/guardrail-<id>.md, for a proxy that reads SOPs from disk')
459
+ .option('--force', 'Overwrite a locally-modified guardrail file instead of refusing it')
460
+ .option('--prune', 'Remove guardrail files that are no longer served (unmodified ones only)')
461
+ .option('--json', 'Output as JSON')
462
+ .option('--dev', 'Use local control plane (http://localhost:3001)')
463
+ .action(async (opts) => {
464
+ const { runGuardrailsPull } = await import('./commands/guardrails.js');
465
+ await runGuardrailsPull(opts);
466
+ });
157
467
  program
158
468
  .command('whoami')
159
469
  .description('Show current authenticated identity')
@@ -172,6 +482,83 @@ program
172
482
  const { runStart } = await import('./commands/start.js');
173
483
  await runStart(opts);
174
484
  });
485
+ // L2 mandatory egress firewall (LLD #63 §5). Makes the governing proxy
486
+ // non-optional: default-deny host egress to everything except the proxy, DNS,
487
+ // and declared infra. apply/remove need root; generate/status do not.
488
+ const enforceCmd = program
489
+ .command('enforce')
490
+ .description('Manage the mandatory default-deny egress firewall (forces all traffic through the proxy)');
491
+ for (const [action, desc] of [
492
+ ['apply', 'Apply the default-deny egress firewall (root). All egress except the proxy, DNS and --allow infra is dropped.'],
493
+ ['remove', 'Remove the Intutic egress firewall (root).'],
494
+ ['status', 'Report whether the egress firewall is currently applied.'],
495
+ ['generate', 'Print the platform ruleset without applying it (no privilege).'],
496
+ ]) {
497
+ enforceCmd
498
+ .command(action)
499
+ .description(desc)
500
+ .option('--port <port>', "The proxy's listener port to permit", '4000')
501
+ .option('--uid <uid>', 'uid the proxy runs as, exempted from the deny (defaults to current user)')
502
+ .option('--allow <cidrs>', 'Comma-separated extra destination CIDRs to permit (control plane, registries, …)')
503
+ .option('--no-dns', 'Also deny outbound DNS (only if a local resolver serves the host)')
504
+ .option('--platform <os>', 'Target ruleset platform: linux | macos | windows (defaults to current OS)')
505
+ .action(async (opts) => {
506
+ const { runEnforce } = await import('./commands/enforce.js');
507
+ await runEnforce(action, opts);
508
+ });
509
+ }
510
+ enforceCmd
511
+ .command('report')
512
+ .description('Report locally recorded enforcement state (firewall/CA-trust/system-hooks) to the control plane. ' +
513
+ 'No privilege needed — for when apply/remove ran elevated and could not reach stored credentials.')
514
+ .action(async () => {
515
+ const { runEnforce } = await import('./commands/enforce.js');
516
+ await runEnforce('report', {});
517
+ });
518
+ // CA-trust rollout, Cursor system hooks, and Jamf/Intune MDM manifest
519
+ // generation for a managed fleet. Deliberately named `enterprise install`,
520
+ // not the old hyphenated `enterprise-install` — that name collides with
521
+ // tools/scripts/intutic-enterprise-install.sh, an unrelated air-gapped
522
+ // docker-compose installer. Does not touch the host firewall; see `enforce`.
523
+ const enterpriseCmd = program
524
+ .command('enterprise')
525
+ .description('Enterprise fleet rollout: CA trust, Cursor system hooks, MDM manifests');
526
+ enterpriseCmd
527
+ .command('install')
528
+ .description('Generate MDM manifests, and (if privileged) install system CA trust + Cursor system hooks')
529
+ .option('--proxy-url <url>', 'Proxy base URL embedded in generated hooks (default: $INTUTIC_PROXY_URL or http://localhost:4000)')
530
+ .option('--generate-mdm-only', 'Only write the MDM manifests; skip CA trust and system hooks (no privilege needed)')
531
+ .option('--mdm-output-dir <dir>', 'Directory to write generated MDM manifests to', './intutic-mdm')
532
+ .option('--skip-ca', 'Skip system-wide CA trust installation')
533
+ .option('--skip-hooks', 'Skip system-level Cursor hooks installation')
534
+ .option('--dev', 'Use local control plane (http://localhost:3001) when resolving workspace context')
535
+ .option('--cli-binary-path <path>', 'Absolute path to the intutic CLI binary on TARGET machines, embedded in the firewall-deployment manifests (default: /usr/local/bin/intutic — override if your fleet installs elsewhere)')
536
+ .action(async (opts) => {
537
+ const { runEnterpriseInstall } = await import('./commands/enterprise.js');
538
+ await runEnterpriseInstall(opts);
539
+ });
540
+ const envCmd = program
541
+ .command('env')
542
+ .description('Persist or clear the proxy base-URL environment variables');
543
+ envCmd
544
+ .command('persist')
545
+ .description('Write ANTHROPIC_BASE_URL / OPENAI_BASE_URL at the OS level so they survive a restart.\n' +
546
+ ' macOS: launchctl setenv Linux: ~/.bashrc Windows: setx\n' +
547
+ '\n' +
548
+ ' Opt-in on purpose: on macOS this reaches every GUI app you launch afterwards.\n' +
549
+ ' For a single command instead, use \'intutic exec\'.')
550
+ .option('--proxy-url <url>', 'Proxy base URL to point the variables at', 'http://localhost:4000')
551
+ .action(async (opts) => {
552
+ const { runEnvPersist } = await import('./commands/env.js');
553
+ await runEnvPersist({ proxyUrl: opts.proxyUrl });
554
+ });
555
+ envCmd
556
+ .command('clear')
557
+ .description('Remove the variables written by \'intutic env persist\'')
558
+ .action(async () => {
559
+ const { runEnvClear } = await import('./commands/env.js');
560
+ await runEnvClear();
561
+ });
175
562
  program
176
563
  .command('connect')
177
564
  .description('Start sync daemon — bidirectional config sync with control plane (requires an account)')
@@ -194,13 +581,49 @@ program
194
581
  const { runSyncContext } = await import('./commands/syncContext.js');
195
582
  await runSyncContext(opts);
196
583
  });
584
+ program
585
+ .command('decisions-log-refresh')
586
+ .description('One-shot refresh of the governed decisions log (.intutic/DECISIONS.md + the claude-code ' +
587
+ 'harness config section) — no-ops if decisionsLogEnabled is off. Invoked by the optional ' +
588
+ 'post-merge hook (see gitHooks.ts); safe to run manually.')
589
+ .action(async () => {
590
+ const { refreshDecisionsLog } = await import('./lib/decisionsLogRefresh.js');
591
+ const result = await refreshDecisionsLog(process.cwd());
592
+ if (!result.refreshed && result.reason) {
593
+ const { log } = await import('./lib/logger.js');
594
+ log.dim(`Decisions log not refreshed: ${result.reason}`);
595
+ }
596
+ });
197
597
  program
198
598
  .command('exec')
199
599
  .description('Execute a command wrapped with Intutic proxy environment variables')
200
600
  .argument('[command...]', 'Command and arguments to execute (e.g. -- claude)')
201
- .action(async (commandAndArgs) => {
601
+ // Run the agent inside an isolated sandbox whose ONLY egress is the proxy
602
+ // (LLD #63 §6): cap-drop, no-new-privileges, read-only rootfs, resource caps,
603
+ // and a default-deny egress firewall the agent cannot undo.
604
+ .option('--sandbox [kind]', 'Run the agent in an isolated sandbox (kind: oci | firecracker; default oci)')
605
+ .option('--sandbox-image <image>', 'Sandbox image (must contain the agent + nftables + capsh)', 'intutic/sandbox:latest')
606
+ .option('--sandbox-memory <size>', 'Sandbox memory cap (e.g. 2g)', '2g')
607
+ .option('--sandbox-cpus <n>', 'Sandbox CPU cap', '2')
608
+ .option('--sandbox-pids <n>', 'Sandbox max process count', '512')
609
+ .option('--sandbox-allow <cidrs>', 'Comma-separated extra destination CIDRs the sandbox may reach')
610
+ .action(async (commandAndArgs, opts) => {
202
611
  const { runExec } = await import('./commands/exec.js');
203
- await runExec(commandAndArgs);
612
+ if (opts.sandbox) {
613
+ await runExec(commandAndArgs, {
614
+ kind: typeof opts.sandbox === 'string' ? opts.sandbox : 'oci',
615
+ image: opts.sandboxImage,
616
+ memory: opts.sandboxMemory,
617
+ cpus: opts.sandboxCpus,
618
+ pidsLimit: Number.parseInt(opts.sandboxPids, 10),
619
+ allow: opts.sandboxAllow
620
+ ? String(opts.sandboxAllow).split(',').map((s) => s.trim()).filter(Boolean)
621
+ : [],
622
+ });
623
+ }
624
+ else {
625
+ await runExec(commandAndArgs);
626
+ }
204
627
  });
205
628
  const traces = program
206
629
  .command('traces')
@@ -211,7 +634,8 @@ traces
211
634
  .description('List execution traces for the workspace')
212
635
  .option('--limit <n>', 'Number of traces to show (default: 20, max: 100)')
213
636
  .option('--since <duration>', 'Time window, e.g. "24h", "7d", "30m" (default: "24h")')
214
- .option('--action <type>', 'Filter by enforcement action (BYPASS|ENHANCE|HIJACK|KILL)')
637
+ .option('--action <type>', 'Filter by enforcement action (BYPASS|ENHANCE|HIJACK|KILL) — connected mode only')
638
+ .option('--verdict <type>', 'Filter by verdict (allowed|killed|upstream_error|reasked|hijacked) — local mode only')
215
639
  .option('--model <name>', 'Filter by model name')
216
640
  .option('--json', 'Output as JSON instead of table')
217
641
  .option('--dev', 'Use local control plane (http://localhost:3001)')
@@ -227,10 +651,115 @@ traces
227
651
  const { runTracesInspect } = await import('./commands/traces.js');
228
652
  await runTracesInspect(traceId, opts);
229
653
  });
654
+ // ── Findings (detector_findings adjudication) ───────────────────────────────
655
+ const findings = program
656
+ .command('findings')
657
+ .description('Query and adjudicate detector findings — the TP/FP labeling surface');
658
+ findings
659
+ .command('list')
660
+ .description('List detector findings for the workspace')
661
+ .option('--unadjudicated', "Only show findings nobody has ruled on yet (the route's own default is to show all)")
662
+ .option('--detector <id>', 'Filter by detector_id (e.g. response_injection:override-instructions)')
663
+ .option('--limit <n>', 'Max rows to return (default: 100, max: 500)')
664
+ .option('--json', 'Output as JSON instead of table')
665
+ .option('--dev', 'Use local control plane (http://localhost:3001)')
666
+ .action(async (opts) => {
667
+ const { runFindingsList } = await import('./commands/findings.js');
668
+ await runFindingsList(opts);
669
+ });
670
+ findings
671
+ .command('adjudicate <findingId>')
672
+ .description('Record a human ruling (true/false positive) on one finding')
673
+ .option('--true-positive', 'Mark this finding a true positive')
674
+ .option('--false-positive', 'Mark this finding a false positive')
675
+ .option('--note <text>', 'Optional note recorded with the ruling')
676
+ .option('--dev', 'Use local control plane (http://localhost:3001)')
677
+ .action(async (findingId, opts) => {
678
+ const { runFindingsAdjudicate } = await import('./commands/findings.js');
679
+ await runFindingsAdjudicate(findingId, opts);
680
+ });
681
+ findings
682
+ .command('stats')
683
+ .description('Per-detector false-positive rate, computed over adjudicated findings only')
684
+ .option('--json', 'Output as JSON instead of table')
685
+ .option('--dev', 'Use local control plane (http://localhost:3001)')
686
+ .action(async (opts) => {
687
+ const { runFindingsStats } = await import('./commands/findings.js');
688
+ await runFindingsStats(opts);
689
+ });
690
+ findings
691
+ .command('echo-report')
692
+ .description('Response-injection echo measurement report — per-pattern false-positive rate')
693
+ .option('--since <date>', 'ISO date — window start (route default: trailing 7 days)')
694
+ .option('--until <date>', 'ISO date — window end (route default: now)')
695
+ .option('--json', 'Output as JSON instead of table')
696
+ .option('--dev', 'Use local control plane (http://localhost:3001)')
697
+ .action(async (opts) => {
698
+ const { runFindingsEchoReport } = await import('./commands/findings.js');
699
+ await runFindingsEchoReport(opts);
700
+ });
701
+ // ── Trace integrity ────────────────────────────────────────────────────────
702
+ const integrity = program
703
+ .command('integrity')
704
+ .description('Verify sealed trace roots — list, re-derive, walk the root chain, and walk the config snapshot chain');
705
+ integrity
706
+ .command('roots')
707
+ .description('List sealed Merkle roots for the workspace, newest first')
708
+ .option('--loop-run <id>', 'Only roots sealed for this loop run')
709
+ .option('--json', 'Output as JSON instead of table')
710
+ .option('--dev', 'Use local control plane (http://localhost:3001)')
711
+ .action(async (opts) => {
712
+ const { runIntegrityRoots } = await import('./commands/integrity.js');
713
+ await runIntegrityRoots(opts);
714
+ });
715
+ integrity
716
+ .command('verify <root_id>')
717
+ .description('Re-derive a root from the live traces and check its signature (exit 1 on mismatch)')
718
+ .option('--json', 'Output as JSON instead of a report')
719
+ .option('--dev', 'Use local control plane (http://localhost:3001)')
720
+ .action(async (rootId, opts) => {
721
+ const { runIntegrityVerify } = await import('./commands/integrity.js');
722
+ await runIntegrityVerify(rootId, opts);
723
+ });
724
+ integrity
725
+ .command('chain')
726
+ .description('Walk the previous_root chain and report deleted roots (exit 1 on a break)')
727
+ .option('--json', 'Output as JSON instead of a report')
728
+ .option('--dev', 'Use local control plane (http://localhost:3001)')
729
+ .action(async (opts) => {
730
+ const { runIntegrityChain } = await import('./commands/integrity.js');
731
+ await runIntegrityChain(opts);
732
+ });
733
+ integrity
734
+ .command('config-chain')
735
+ .description('Walk the harness config snapshot chain and re-hash each stored body ' +
736
+ '(exit 1 on a break or a content mismatch)')
737
+ .option('--json', 'Output as JSON instead of a report')
738
+ .option('--dev', 'Use local control plane (http://localhost:3001)')
739
+ .action(async (opts) => {
740
+ const { runIntegrityConfigChain } = await import('./commands/integrity.js');
741
+ await runIntegrityConfigChain(opts);
742
+ });
743
+ // ── Routing reports ─────────────────────────────────────────────────────────
744
+ const routing = program
745
+ .command('routing')
746
+ .description('Read-only reports for the routing subsystem');
747
+ routing
748
+ .command('adoption-report')
749
+ .description('Mirror-test adoption report for one candidate model — win/loss/tie, fault-rate delta, ' +
750
+ 'cost delta, latency delta (Phase 7b: GET /api/v1/routing/mirror-adoption-report). ' +
751
+ 'A reported signal for human review, not an automatic gate.')
752
+ .requiredOption('--candidate-model <model>', 'The mirror-tested candidate model to report on')
753
+ .option('--json', 'Output as JSON instead of a report')
754
+ .option('--dev', 'Use local control plane (http://localhost:3001)')
755
+ .action(async (opts) => {
756
+ const { runRoutingAdoptionReport } = await import('./commands/routing.js');
757
+ await runRoutingAdoptionReport(opts);
758
+ });
230
759
  // ── Daemon persistence (WS-5 — Q3 Layer 4) ─────────────────────────────────
231
760
  const daemon = program
232
761
  .command('daemon')
233
- .description('Manage the Intutic sync-daemon system service (LaunchAgent / systemd)');
762
+ .description('Manage the Intutic sync-daemon, MCP daemon and standalone proxy system services (LaunchAgent / systemd)');
234
763
  daemon
235
764
  .command('install')
236
765
  .alias('install-daemon') // top-level alias for discoverability
@@ -238,17 +767,52 @@ daemon
238
767
  ' macOS: ~/Library/LaunchAgents/ai.intutic.sync-daemon.plist (KeepAlive: true)\n' +
239
768
  ' Linux: ~/.config/systemd/user/intutic-sync-daemon.service (Restart=always)\n' +
240
769
  '\n' +
770
+ ' --proxy installs the standalone intutic-proxy binary instead (no workspace or key needed):\n' +
771
+ ' macOS: ~/Library/LaunchAgents/ai.intutic.proxy.plist\n' +
772
+ ' Linux: ~/.config/systemd/user/intutic-proxy.service\n' +
773
+ '\n' +
241
774
  ' NOTE: To stop the daemon you MUST use \'intutic daemon uninstall\' or \'launchctl unload\'.\n' +
242
775
  ' \'intutic disconnect\' alone will NOT stop a daemon-installed service. (TD-154)')
243
- .requiredOption('--workspace-id <id>', 'Workspace ID (e.g. wk_xxxx)')
244
- .requiredOption('--api-key <key>', 'Workspace API key (e.g. vk_xxxx)')
776
+ .option('--workspace-id <id>', 'Workspace ID (e.g. wk_xxxx) — required unless --proxy')
777
+ .option('--api-key <key>', 'Workspace API key (e.g. vk_xxxx) — required unless --proxy')
245
778
  .option('--control-plane-url <url>', 'Control plane URL', 'https://api.intutic.ai')
246
- .option('--binary-path <path>', 'Path to intutic CLI binary (defaults to current process)')
779
+ .option('--binary-path <path>', 'Path to intutic CLI binary (defaults to current process); with --proxy, absolute path to intutic-proxy')
247
780
  .option('--dry-run', 'Print what would be done without writing files')
248
781
  .option('--system', 'Install as a system-level service (LaunchDaemon on macOS, systemd system unit on Linux)')
249
- .action(async (opts) => {
250
- const { installDaemon } = await import('./commands/install-daemon.js');
251
- await installDaemon({
782
+ .option('--mcp', 'Install the MCP proxy daemon instead of the sync-daemon')
783
+ .option('--proxy', 'Install the standalone intutic-proxy binary as a service (TD-465)')
784
+ .option('--port <port>', 'With --proxy: proxy listen port', '4000')
785
+ .option('--valkey-url <url>', 'With --proxy: Valkey URL to attach to; omit to run standalone (INTUTIC_STANDALONE=1)')
786
+ .option('--upstream-url <url>', 'With --proxy: upstream LLM provider base URL')
787
+ .action(async (opts, cmd) => {
788
+ if (opts.proxy) {
789
+ // The standalone proxy has no control plane, so no workspace or key.
790
+ // Its unit carries the same environment `intutic start` sets, fixed at
791
+ // install time rather than probed at launch (TD-465).
792
+ const { installProxyService } = await import('./commands/install-daemon.js');
793
+ await installProxyService({
794
+ port: opts.port,
795
+ valkeyUrl: opts.valkeyUrl,
796
+ upstreamUrl: opts.upstreamUrl,
797
+ binaryPath: opts.binaryPath,
798
+ dryRun: opts.dryRun,
799
+ system: opts.system,
800
+ });
801
+ return;
802
+ }
803
+ // Required for the two daemons; commander's `requiredOption` cannot express
804
+ // "unless --proxy", so the check lives here with the same wording.
805
+ if (!opts.workspaceId)
806
+ cmd.error("error: required option '--workspace-id <id>' not specified");
807
+ if (!opts.apiKey)
808
+ cmd.error("error: required option '--api-key <key>' not specified");
809
+ // `installMcpDaemon` and `buildMcpPlist` were written, tested and exported,
810
+ // and then nothing called them: every route into install-daemon.ts landed
811
+ // on `installDaemon`, so the MCP proxy daemon could not be installed by any
812
+ // command the CLI offered (TD-153). This flag is that missing route.
813
+ const { installDaemon, installMcpDaemon } = await import('./commands/install-daemon.js');
814
+ const install = opts.mcp ? installMcpDaemon : installDaemon;
815
+ await install({
252
816
  workspaceId: opts.workspaceId,
253
817
  apiKey: opts.apiKey,
254
818
  controlPlaneUrl: opts.controlPlaneUrl,
@@ -263,30 +827,48 @@ daemon
263
827
  .description('Remove the sync-daemon system service and stop it permanently.')
264
828
  .option('--dry-run', 'Print what would be done without writing files')
265
829
  .option('--system', 'Uninstall the system-level service')
830
+ .option('--mcp', 'Uninstall the MCP proxy daemon instead of the sync-daemon')
831
+ .option('--proxy', 'Uninstall the standalone intutic-proxy service instead of the sync-daemon')
266
832
  .action(async (opts) => {
267
- const { uninstallDaemon } = await import('./commands/install-daemon.js');
268
- await uninstallDaemon({ dryRun: opts.dryRun, system: opts.system });
833
+ const { uninstallDaemon, uninstallMcpDaemon, uninstallProxyService } = await import('./commands/install-daemon.js');
834
+ const uninstall = opts.proxy ? uninstallProxyService : opts.mcp ? uninstallMcpDaemon : uninstallDaemon;
835
+ await uninstall({ dryRun: opts.dryRun, system: opts.system });
269
836
  });
270
837
  daemon
271
838
  .command('status')
272
- .description('Show sync-daemon system service status.')
273
- .action(async () => {
274
- const { daemonStatus } = await import('./commands/install-daemon.js');
275
- await daemonStatus();
839
+ .description('Show sync-daemon system service status (or the standalone proxy service with --proxy).')
840
+ .option('--proxy', 'Show the standalone intutic-proxy service instead of the sync-daemon')
841
+ .option('--mcp', 'Show the MCP proxy daemon instead of the sync-daemon')
842
+ .action(async (opts) => {
843
+ // `mcpDaemonStatus` was exported and never called by any command until the
844
+ // reachability test widened to the status/stop/start verbs (TD-153's
845
+ // shape, one more time).
846
+ const { daemonStatus, proxyServiceStatus, mcpDaemonStatus } = await import('./commands/install-daemon.js');
847
+ await (opts.proxy ? proxyServiceStatus() : opts.mcp ? mcpDaemonStatus() : daemonStatus());
276
848
  });
277
849
  daemon
278
850
  .command('stop')
279
- .description('Stop and unload the sync-daemon system service.')
280
- .action(async () => {
281
- const { daemonStop } = await import('./commands/install-daemon.js');
282
- await daemonStop();
851
+ .description('Stop and unload the sync-daemon system service (or the standalone proxy service with --proxy).')
852
+ .option('--proxy', 'Stop the standalone intutic-proxy service instead of the sync-daemon')
853
+ .option('--mcp', 'Stop the MCP proxy daemon instead of the sync-daemon')
854
+ .action(async (opts) => {
855
+ // `mcpDaemonStop` was exported and never called by any command until the
856
+ // reachability test widened to the status/stop/start verbs (TD-153's
857
+ // shape, one more time).
858
+ const { daemonStop, proxyServiceStop, mcpDaemonStop } = await import('./commands/install-daemon.js');
859
+ await (opts.proxy ? proxyServiceStop() : opts.mcp ? mcpDaemonStop() : daemonStop());
283
860
  });
284
861
  daemon
285
862
  .command('start')
286
- .description('Start and load the sync-daemon system service.')
287
- .action(async () => {
288
- const { daemonStart } = await import('./commands/install-daemon.js');
289
- await daemonStart();
863
+ .description('Start and load the sync-daemon system service (or the standalone proxy service with --proxy).')
864
+ .option('--proxy', 'Start the standalone intutic-proxy service instead of the sync-daemon')
865
+ .option('--mcp', 'Start the MCP proxy daemon instead of the sync-daemon')
866
+ .action(async (opts) => {
867
+ // `mcpDaemonStart` was exported and never called by any command until the
868
+ // reachability test widened to the status/stop/start verbs (TD-153's
869
+ // shape, one more time).
870
+ const { daemonStart, proxyServiceStart, mcpDaemonStart } = await import('./commands/install-daemon.js');
871
+ await (opts.proxy ? proxyServiceStart() : opts.mcp ? mcpDaemonStart() : daemonStart());
290
872
  });
291
873
  // Top-level shortcuts (for discoverability)
292
874
  program
@@ -314,9 +896,12 @@ program
314
896
  .description('Shortcut for \'intutic daemon uninstall\'')
315
897
  .option('--dry-run', 'Print what would be done without writing files')
316
898
  .option('--system', 'Uninstall the system-level service')
899
+ .option('--mcp', 'Uninstall the MCP proxy daemon instead of the sync-daemon')
900
+ .option('--proxy', 'Uninstall the standalone intutic-proxy service instead of the sync-daemon')
317
901
  .action(async (opts) => {
318
- const { uninstallDaemon } = await import('./commands/install-daemon.js');
319
- await uninstallDaemon({ dryRun: opts.dryRun, system: opts.system });
902
+ const { uninstallDaemon, uninstallMcpDaemon, uninstallProxyService } = await import('./commands/install-daemon.js');
903
+ const uninstall = opts.proxy ? uninstallProxyService : opts.mcp ? uninstallMcpDaemon : uninstallDaemon;
904
+ await uninstall({ dryRun: opts.dryRun, system: opts.system });
320
905
  });
321
906
  // ── Skill commands ─────────────────────────────────────────────────────────
322
907
  const skillCmd = program
@@ -332,9 +917,19 @@ skillCmd
332
917
  skillCmd
333
918
  .command('audit')
334
919
  .description('Audit local rules/skills for security leakage or unsafe command patterns')
335
- .action(async () => {
920
+ .option('--sarif', 'Output findings as a SARIF 2.1.0 document (for GitHub Code Scanning / CI interop) instead of the human-readable report')
921
+ .option('--engine <engine>', "Scanning engine: 'native' (default, always runs) or 'cisco' to also run the opt-in Cisco " +
922
+ "skill-scanner integration (requires the 'skill-scanner' binary on PATH — pipx install cisco-ai-skill-scanner)", 'native')
923
+ .action(async (opts) => {
336
924
  const { runSkillAudit } = await import('./commands/skill.js');
337
- await runSkillAudit();
925
+ await runSkillAudit({ sarif: opts.sarif, engine: opts.engine });
926
+ });
927
+ skillCmd
928
+ .command('scan-staged')
929
+ .description('Warn-only content scan of staged skill-surface additions (.agents/skills, .claude/skills) — used by the pre-commit hook, never blocks the commit')
930
+ .action(async () => {
931
+ const { runSkillScanStaged } = await import('./commands/skill.js');
932
+ await runSkillScanStaged();
338
933
  });
339
934
  // ── Loop commands ──────────────────────────────────────────────────────────
340
935
  const loopCmd = program
@@ -368,6 +963,17 @@ loopCmd
368
963
  const { runLoopKill } = await import('./commands/skill.js');
369
964
  await runLoopKill(loopRunId, opts);
370
965
  });
966
+ loopCmd
967
+ .command('review <loopRunId>')
968
+ .description('Approve or reject a loop run held for human review')
969
+ .option('--approve', 'Release the hold; the run resumes')
970
+ .option('--reject', 'Refuse the hold; the run is killed')
971
+ .option('--note <note>', 'Why, recorded against the run')
972
+ .option('--dev', 'Target the local control plane')
973
+ .action(async (loopRunId, opts) => {
974
+ const { runLoopReview } = await import('./commands/skill.js');
975
+ await runLoopReview(loopRunId, opts);
976
+ });
371
977
  loopCmd
372
978
  .command('list')
373
979
  .description('List loop runs and cost accounting details for the workspace')
@@ -389,5 +995,237 @@ loopCmd
389
995
  const { runLoopExec } = await import('./commands/skill.js');
390
996
  await runLoopExec(commandAndArgs, opts);
391
997
  });
998
+ // ── Decision commands ────────────────────────────────────────────────────
999
+ //
1000
+ // The real front door for a review_before hold. See commands/decision.ts's
1001
+ // module doc for why `intutic loop review` was never the right command for
1002
+ // this — it addresses a different id space (Loop Runs, not decisions).
1003
+ const decisionCmd = program
1004
+ .command('decision')
1005
+ .description('Approve or reject a decision (e.g. a review_before hold) held for human review');
1006
+ decisionCmd
1007
+ .command('approve <holdId>')
1008
+ .description('Approve a held decision; may also unblock the exact retried call, if the workspace opted in')
1009
+ .option('--reason <reason>', 'Why, recorded against the decision')
1010
+ .option('--dev', 'Use local control plane (http://localhost:3001)')
1011
+ .action(async (holdId, opts) => {
1012
+ const { runDecisionApprove } = await import('./commands/decision.js');
1013
+ await runDecisionApprove(holdId, opts);
1014
+ });
1015
+ decisionCmd
1016
+ .command('reject <holdId>')
1017
+ .description('Reject a held decision')
1018
+ .option('--reason <reason>', 'Why, recorded against the decision')
1019
+ .option('--dev', 'Use local control plane (http://localhost:3001)')
1020
+ .action(async (holdId, opts) => {
1021
+ const { runDecisionReject } = await import('./commands/decision.js');
1022
+ await runDecisionReject(holdId, opts);
1023
+ });
1024
+ // ── Gateway commands (LLD #66) ───────────────────────────────────────────
1025
+ //
1026
+ // Self-hosted gateway registration/management. Previously reachable only by
1027
+ // hand-written curl against services/control-plane/src/routes/gateways.ts —
1028
+ // see commands/gateway.ts's module doc.
1029
+ const gatewayCmd = program
1030
+ .command('gateway')
1031
+ .description('Manage self-hosted gateway registrations (Docker / Kubernetes / bare-metal)');
1032
+ gatewayCmd
1033
+ .command('register')
1034
+ .description('Register a new self-hosted gateway and print its one-time gwk_ token')
1035
+ .requiredOption('--name <name>', 'Display name for this gateway')
1036
+ .requiredOption('--target <docker|kubernetes|bare_metal>', 'Deployment target')
1037
+ .option('--json', 'Output as JSON')
1038
+ .option('--dev', 'Use local control plane (http://localhost:3001)')
1039
+ .action(async (opts) => {
1040
+ const { runGatewayRegister } = await import('./commands/gateway.js');
1041
+ await runGatewayRegister(opts);
1042
+ });
1043
+ gatewayCmd
1044
+ .command('list')
1045
+ .description("List the org's registered gateways")
1046
+ .option('--json', 'Output as JSON')
1047
+ .option('--dev', 'Use local control plane (http://localhost:3001)')
1048
+ .action(async (opts) => {
1049
+ const { runGatewayList } = await import('./commands/gateway.js');
1050
+ await runGatewayList(opts);
1051
+ });
1052
+ gatewayCmd
1053
+ .command('status <gateway_id>')
1054
+ .description('Live heartbeat-derived status for one gateway')
1055
+ .option('--json', 'Output as JSON')
1056
+ .option('--dev', 'Use local control plane (http://localhost:3001)')
1057
+ .action(async (gatewayId, opts) => {
1058
+ const { runGatewayStatus } = await import('./commands/gateway.js');
1059
+ await runGatewayStatus(gatewayId, opts);
1060
+ });
1061
+ gatewayCmd
1062
+ .command('rotate <gateway_id>')
1063
+ .description('Issue a new gwk_ token; the old one keeps working during the rotation grace period')
1064
+ .option('--json', 'Output as JSON')
1065
+ .option('--dev', 'Use local control plane (http://localhost:3001)')
1066
+ .action(async (gatewayId, opts) => {
1067
+ const { runGatewayRotate } = await import('./commands/gateway.js');
1068
+ await runGatewayRotate(gatewayId, opts);
1069
+ });
1070
+ gatewayCmd
1071
+ .command('revoke <gateway_id>')
1072
+ .description('Revoke a gateway immediately (kills any active rotation grace period too)')
1073
+ .option('--reason <text>', 'Recorded in the audit log')
1074
+ .option('--dev', 'Use local control plane (http://localhost:3001)')
1075
+ .action(async (gatewayId, opts) => {
1076
+ const { runGatewayRevoke } = await import('./commands/gateway.js');
1077
+ await runGatewayRevoke(gatewayId, opts);
1078
+ });
1079
+ const gatewayConfigCmd = gatewayCmd
1080
+ .command('config')
1081
+ .description('Manage a gateway\'s remote config (requireVk, requireProvisionedKey)');
1082
+ gatewayConfigCmd
1083
+ .command('set <gateway_id>')
1084
+ .description('Update one or both config flags on a gateway')
1085
+ .option('--require-vk <true|false>', 'Refuse non-vk_ bearer tokens at this gateway')
1086
+ .option('--require-provisioned-key <true|false>', 'Refuse workspaces with no provisioned upstream key')
1087
+ .option('--json', 'Output as JSON')
1088
+ .option('--dev', 'Use local control plane (http://localhost:3001)')
1089
+ .action(async (gatewayId, opts) => {
1090
+ const { runGatewayConfigSet } = await import('./commands/gateway.js');
1091
+ await runGatewayConfigSet(gatewayId, opts);
1092
+ });
1093
+ gatewayCmd
1094
+ .command('assign')
1095
+ .description('Assign (or clear) the gateway this workspace or org defaults to')
1096
+ .option('--gateway <gateway_id>', 'Gateway to assign')
1097
+ .option('--clear', 'Clear the current assignment instead of setting one')
1098
+ .option('--org <org_id>', 'Set the ORG default instead of this workspace\'s own override')
1099
+ .option('--json', 'Output as JSON')
1100
+ .option('--dev', 'Use local control plane (http://localhost:3001)')
1101
+ .action(async (opts) => {
1102
+ const { runGatewayAssign } = await import('./commands/gateway.js');
1103
+ await runGatewayAssign(opts);
1104
+ });
1105
+ gatewayCmd
1106
+ .command('resolve')
1107
+ .description('Show which gateway this workspace currently resolves to (own override, org default, or shared)')
1108
+ .option('--json', 'Output as JSON')
1109
+ .option('--dev', 'Use local control plane (http://localhost:3001)')
1110
+ .action(async (opts) => {
1111
+ const { runGatewayResolve } = await import('./commands/gateway.js');
1112
+ await runGatewayResolve(opts);
1113
+ });
1114
+ // ── Provider credentials (LLD #64 §4, LLD #67) ───────────────────────────
1115
+ //
1116
+ // Provision a workspace's own upstream provider keys — the BYO-key wizard's
1117
+ // API, previously dashboard-only. See commands/credentials.ts's module doc.
1118
+ const credentialsCmd = program
1119
+ .command('credentials')
1120
+ .description('Manage this workspace\'s own upstream provider API keys (BYO-key)');
1121
+ credentialsCmd
1122
+ .command('list')
1123
+ .description('Provisioning status for every registry provider')
1124
+ .option('--json', 'Output as JSON')
1125
+ .option('--dev', 'Use local control plane (http://localhost:3001)')
1126
+ .action(async (opts) => {
1127
+ const { runCredentialsList } = await import('./commands/credentials.js');
1128
+ await runCredentialsList(opts);
1129
+ });
1130
+ credentialsCmd
1131
+ .command('set <provider>')
1132
+ .description('Provision or rotate a provider credential (e.g. --field apiKey=sk-ant-...)')
1133
+ .option('--field <key=value>', 'A credential field; repeat for multi-field providers', (v, prev) => [...prev, v], [])
1134
+ .option('--json', 'Output as JSON')
1135
+ .option('--dev', 'Use local control plane (http://localhost:3001)')
1136
+ .action(async (provider, opts) => {
1137
+ const { runCredentialsSet } = await import('./commands/credentials.js');
1138
+ await runCredentialsSet(provider, opts);
1139
+ });
1140
+ credentialsCmd
1141
+ .command('unset <provider>')
1142
+ .description('Remove a provisioned provider credential')
1143
+ .option('--dev', 'Use local control plane (http://localhost:3001)')
1144
+ .action(async (provider, opts) => {
1145
+ const { runCredentialsUnset } = await import('./commands/credentials.js');
1146
+ await runCredentialsUnset(provider, opts);
1147
+ });
1148
+ // ── DCT Token Attenuation (LLD #19 §2.1, HLD §5.6 Patent Family A) ───────
1149
+ //
1150
+ // "CLI mints, dashboard audits": this is the mint/inspect half. See
1151
+ // commands/attenuate.ts's module doc.
1152
+ const attenuateCmd = program
1153
+ .command('attenuate')
1154
+ .description('Attenuate an API key to a narrower child key (capability subset + optional TTL)')
1155
+ .option('--parent-key <keyId>', 'Parent API key ID to attenuate')
1156
+ .option('--caps <caps>', 'Comma-separated capability subset to grant the child key')
1157
+ .option('--ttl <seconds>', 'Child key TTL in seconds (server default 14400, max 86400)')
1158
+ .option('--dev', 'Use local control plane (http://localhost:3001)')
1159
+ .action(async (opts) => {
1160
+ const { runAttenuate } = await import('./commands/attenuate.js');
1161
+ await runAttenuate(opts);
1162
+ });
1163
+ attenuateCmd
1164
+ .command('chain <chainId>')
1165
+ .description('Resolve the full delegation lineage for an attenuation chain')
1166
+ .option('--dev', 'Use local control plane (http://localhost:3001)')
1167
+ .action(async (chainId, opts) => {
1168
+ const { runAttenuateChain } = await import('./commands/attenuate.js');
1169
+ await runAttenuateChain(chainId, opts);
1170
+ });
1171
+ // ── Org signup + team management (LLD #65) ───────────────────────────────
1172
+ const orgCmd = program
1173
+ .command('org')
1174
+ .description('Create and manage orgs');
1175
+ orgCmd
1176
+ .command('create')
1177
+ .description('Create a real org (paid tier, 30-day trial) with a default team and workspace. ' +
1178
+ 'Requires `intutic login` first and DNS domain-ownership verification.')
1179
+ .option('--org-name <orgName>', 'Organization name (prompted if omitted)')
1180
+ .option('--domain <domain>', 'Domain to verify ownership of (prompted if omitted)')
1181
+ .option('--region <region>', "Gateway cell region (e.g. 'us', 'eu'); server-validated, defaults to the deployment's home region")
1182
+ .option('--dev', 'Use local control plane (http://localhost:3001)')
1183
+ .action(async (opts) => {
1184
+ const { runOrgCreate } = await import('./commands/org.js');
1185
+ await runOrgCreate(opts);
1186
+ });
1187
+ const teamCmd = program
1188
+ .command('team')
1189
+ .description('Manage teams and workspaces under an org');
1190
+ teamCmd
1191
+ .command('list')
1192
+ .description("List an org's teams")
1193
+ .requiredOption('--org <org_id>', 'Org ID')
1194
+ .option('--json', 'Output as JSON')
1195
+ .option('--dev', 'Use local control plane (http://localhost:3001)')
1196
+ .action(async (opts) => {
1197
+ const { runTeamList } = await import('./commands/team.js');
1198
+ await runTeamList(opts);
1199
+ });
1200
+ teamCmd
1201
+ .command('create')
1202
+ .description('Create a new team under an org')
1203
+ .requiredOption('--org <org_id>', 'Org ID')
1204
+ .requiredOption('--name <name>', 'Team name')
1205
+ .option('--json', 'Output as JSON')
1206
+ .option('--dev', 'Use local control plane (http://localhost:3001)')
1207
+ .action(async (opts) => {
1208
+ const { runTeamCreate } = await import('./commands/team.js');
1209
+ await runTeamCreate(opts);
1210
+ });
1211
+ teamCmd
1212
+ .command('workspaces <team_id>')
1213
+ .description('List the workspaces under a team')
1214
+ .option('--json', 'Output as JSON')
1215
+ .option('--dev', 'Use local control plane (http://localhost:3001)')
1216
+ .action(async (teamId, opts) => {
1217
+ const { runTeamWorkspaces } = await import('./commands/team.js');
1218
+ await runTeamWorkspaces(teamId, opts);
1219
+ });
1220
+ teamCmd
1221
+ .command('create-workspace <team_id>')
1222
+ .description('Create a new workspace under a team; you become its OWNER')
1223
+ .requiredOption('--name <name>', 'Workspace name')
1224
+ .option('--json', 'Output as JSON')
1225
+ .option('--dev', 'Use local control plane (http://localhost:3001)')
1226
+ .action(async (teamId, opts) => {
1227
+ const { runTeamCreateWorkspace } = await import('./commands/team.js');
1228
+ await runTeamCreateWorkspace(teamId, opts);
1229
+ });
392
1230
  program.parse();
393
1231
  //# sourceMappingURL=cli.js.map