@intutic/cli 2.2.0 → 2.4.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 (356) hide show
  1. package/README.md +8 -8
  2. package/dist/cli.d.ts +0 -1
  3. package/dist/cli.d.ts.map +1 -1
  4. package/dist/cli.js +582 -21
  5. package/dist/cli.js.map +1 -1
  6. package/dist/commands/apiCommand.d.ts +40 -0
  7. package/dist/commands/apiCommand.d.ts.map +1 -0
  8. package/dist/commands/apiCommand.js +88 -0
  9. package/dist/commands/apiCommand.js.map +1 -0
  10. package/dist/commands/budget.d.ts +4 -0
  11. package/dist/commands/budget.d.ts.map +1 -1
  12. package/dist/commands/budget.js +8 -0
  13. package/dist/commands/budget.js.map +1 -1
  14. package/dist/commands/budgetLimits.d.ts +70 -0
  15. package/dist/commands/budgetLimits.d.ts.map +1 -0
  16. package/dist/commands/budgetLimits.js +223 -0
  17. package/dist/commands/budgetLimits.js.map +1 -0
  18. package/dist/commands/compliance.d.ts +122 -0
  19. package/dist/commands/compliance.d.ts.map +1 -0
  20. package/dist/commands/compliance.js +278 -0
  21. package/dist/commands/compliance.js.map +1 -0
  22. package/dist/commands/connect.d.ts +26 -1
  23. package/dist/commands/connect.d.ts.map +1 -1
  24. package/dist/commands/connect.js +109 -51
  25. package/dist/commands/connect.js.map +1 -1
  26. package/dist/commands/credentials.d.ts +6 -2
  27. package/dist/commands/credentials.d.ts.map +1 -1
  28. package/dist/commands/credentials.js +99 -10
  29. package/dist/commands/credentials.js.map +1 -1
  30. package/dist/commands/decision.d.ts +4 -4
  31. package/dist/commands/decision.js +4 -4
  32. package/dist/commands/disconnect.d.ts +4 -0
  33. package/dist/commands/disconnect.d.ts.map +1 -1
  34. package/dist/commands/disconnect.js +17 -3
  35. package/dist/commands/disconnect.js.map +1 -1
  36. package/dist/commands/doctor.d.ts +0 -1
  37. package/dist/commands/doctor.d.ts.map +1 -1
  38. package/dist/commands/doctor.js +0 -1
  39. package/dist/commands/doctor.js.map +1 -1
  40. package/dist/commands/enforce.d.ts +1 -1
  41. package/dist/commands/enforce.js +1 -1
  42. package/dist/commands/enterprise.d.ts +3 -2
  43. package/dist/commands/enterprise.d.ts.map +1 -1
  44. package/dist/commands/enterprise.js +11 -3
  45. package/dist/commands/enterprise.js.map +1 -1
  46. package/dist/commands/env.js +1 -1
  47. package/dist/commands/exec.d.ts +2 -3
  48. package/dist/commands/exec.d.ts.map +1 -1
  49. package/dist/commands/exec.js +4 -5
  50. package/dist/commands/exec.js.map +1 -1
  51. package/dist/commands/gateLiveness.d.ts +15 -0
  52. package/dist/commands/gateLiveness.d.ts.map +1 -0
  53. package/dist/commands/gateLiveness.js +34 -0
  54. package/dist/commands/gateLiveness.js.map +1 -0
  55. package/dist/commands/gateway.d.ts +14 -4
  56. package/dist/commands/gateway.d.ts.map +1 -1
  57. package/dist/commands/gateway.js +34 -4
  58. package/dist/commands/gateway.js.map +1 -1
  59. package/dist/commands/github.d.ts +20 -0
  60. package/dist/commands/github.d.ts.map +1 -0
  61. package/dist/commands/github.js +41 -0
  62. package/dist/commands/github.js.map +1 -0
  63. package/dist/commands/guardrails.d.ts +28 -2
  64. package/dist/commands/guardrails.d.ts.map +1 -1
  65. package/dist/commands/guardrails.js +233 -24
  66. package/dist/commands/guardrails.js.map +1 -1
  67. package/dist/commands/incidents.d.ts +32 -0
  68. package/dist/commands/incidents.d.ts.map +1 -0
  69. package/dist/commands/incidents.js +94 -0
  70. package/dist/commands/incidents.js.map +1 -0
  71. package/dist/commands/init.d.ts +1 -2
  72. package/dist/commands/init.d.ts.map +1 -1
  73. package/dist/commands/init.js +1 -2
  74. package/dist/commands/init.js.map +1 -1
  75. package/dist/commands/install-daemon.d.ts +1 -1
  76. package/dist/commands/install-daemon.js +2 -2
  77. package/dist/commands/integrity.d.ts +10 -2
  78. package/dist/commands/integrity.d.ts.map +1 -1
  79. package/dist/commands/integrity.js +3 -3
  80. package/dist/commands/integrity.js.map +1 -1
  81. package/dist/commands/inventory.d.ts +60 -0
  82. package/dist/commands/inventory.d.ts.map +1 -0
  83. package/dist/commands/inventory.js +141 -0
  84. package/dist/commands/inventory.js.map +1 -0
  85. package/dist/commands/judge.d.ts +1 -1
  86. package/dist/commands/judge.js +2 -2
  87. package/dist/commands/judge.js.map +1 -1
  88. package/dist/commands/login.d.ts +0 -1
  89. package/dist/commands/login.d.ts.map +1 -1
  90. package/dist/commands/login.js +0 -1
  91. package/dist/commands/login.js.map +1 -1
  92. package/dist/commands/logout.d.ts +0 -1
  93. package/dist/commands/logout.d.ts.map +1 -1
  94. package/dist/commands/logout.js +0 -1
  95. package/dist/commands/logout.js.map +1 -1
  96. package/dist/commands/mcp.d.ts +31 -0
  97. package/dist/commands/mcp.d.ts.map +1 -0
  98. package/dist/commands/mcp.js +65 -0
  99. package/dist/commands/mcp.js.map +1 -0
  100. package/dist/commands/notifications.d.ts +54 -0
  101. package/dist/commands/notifications.d.ts.map +1 -0
  102. package/dist/commands/notifications.js +151 -0
  103. package/dist/commands/notifications.js.map +1 -0
  104. package/dist/commands/org.d.ts +3 -3
  105. package/dist/commands/org.js +3 -3
  106. package/dist/commands/policy.d.ts +6 -1
  107. package/dist/commands/policy.d.ts.map +1 -1
  108. package/dist/commands/policy.js +77 -37
  109. package/dist/commands/policy.js.map +1 -1
  110. package/dist/commands/rollback.d.ts.map +1 -1
  111. package/dist/commands/rollback.js +3 -1
  112. package/dist/commands/rollback.js.map +1 -1
  113. package/dist/commands/routing.d.ts +1 -1
  114. package/dist/commands/routing.js +1 -1
  115. package/dist/commands/rules.d.ts +74 -0
  116. package/dist/commands/rules.d.ts.map +1 -0
  117. package/dist/commands/rules.js +283 -0
  118. package/dist/commands/rules.js.map +1 -0
  119. package/dist/commands/settings.d.ts +44 -0
  120. package/dist/commands/settings.d.ts.map +1 -0
  121. package/dist/commands/settings.js +110 -0
  122. package/dist/commands/settings.js.map +1 -0
  123. package/dist/commands/setup.d.ts +1 -1
  124. package/dist/commands/setup.js +1 -1
  125. package/dist/commands/siem.d.ts +53 -0
  126. package/dist/commands/siem.d.ts.map +1 -0
  127. package/dist/commands/siem.js +132 -0
  128. package/dist/commands/siem.js.map +1 -0
  129. package/dist/commands/skill.d.ts +5 -5
  130. package/dist/commands/skill.js +7 -7
  131. package/dist/commands/skill.js.map +1 -1
  132. package/dist/commands/status.d.ts +0 -1
  133. package/dist/commands/status.d.ts.map +1 -1
  134. package/dist/commands/status.js +2 -3
  135. package/dist/commands/status.js.map +1 -1
  136. package/dist/commands/syncContext.d.ts +0 -1
  137. package/dist/commands/syncContext.d.ts.map +1 -1
  138. package/dist/commands/syncContext.js +0 -1
  139. package/dist/commands/syncContext.js.map +1 -1
  140. package/dist/commands/team.d.ts +2 -2
  141. package/dist/commands/team.js +2 -2
  142. package/dist/commands/traces.d.ts +0 -3
  143. package/dist/commands/traces.d.ts.map +1 -1
  144. package/dist/commands/traces.js +0 -3
  145. package/dist/commands/traces.js.map +1 -1
  146. package/dist/commands/usage.d.ts +41 -0
  147. package/dist/commands/usage.d.ts.map +1 -0
  148. package/dist/commands/usage.js +129 -0
  149. package/dist/commands/usage.js.map +1 -0
  150. package/dist/commands/whoami.d.ts +0 -1
  151. package/dist/commands/whoami.d.ts.map +1 -1
  152. package/dist/commands/whoami.js +0 -1
  153. package/dist/commands/whoami.js.map +1 -1
  154. package/dist/config/keychain.d.ts +0 -1
  155. package/dist/config/keychain.d.ts.map +1 -1
  156. package/dist/config/keychain.js +0 -1
  157. package/dist/config/keychain.js.map +1 -1
  158. package/dist/config/paths.d.ts +0 -1
  159. package/dist/config/paths.d.ts.map +1 -1
  160. package/dist/config/paths.js +0 -1
  161. package/dist/config/paths.js.map +1 -1
  162. package/dist/config/store.d.ts +0 -1
  163. package/dist/config/store.d.ts.map +1 -1
  164. package/dist/config/store.js +0 -1
  165. package/dist/config/store.js.map +1 -1
  166. package/dist/harness/ag2.d.ts +1 -1
  167. package/dist/harness/ag2.js +1 -1
  168. package/dist/harness/agentFramework.d.ts +1 -1
  169. package/dist/harness/agentFramework.js +1 -1
  170. package/dist/harness/agenticOrchestrator.d.ts +1 -1
  171. package/dist/harness/agenticOrchestrator.js +2 -2
  172. package/dist/harness/agenticOrchestrator.js.map +1 -1
  173. package/dist/harness/agentsMd.d.ts +24 -0
  174. package/dist/harness/agentsMd.d.ts.map +1 -0
  175. package/dist/harness/agentsMd.js +28 -0
  176. package/dist/harness/agentsMd.js.map +1 -0
  177. package/dist/harness/aider.d.ts +3 -1
  178. package/dist/harness/aider.d.ts.map +1 -1
  179. package/dist/harness/aider.js +9 -7
  180. package/dist/harness/aider.js.map +1 -1
  181. package/dist/harness/antigravity.d.ts +23 -5
  182. package/dist/harness/antigravity.d.ts.map +1 -1
  183. package/dist/harness/antigravity.js +45 -55
  184. package/dist/harness/antigravity.js.map +1 -1
  185. package/dist/harness/autogen.d.ts +1 -1
  186. package/dist/harness/autogen.js +2 -2
  187. package/dist/harness/autogen.js.map +1 -1
  188. package/dist/harness/claudeCode.d.ts +22 -1
  189. package/dist/harness/claudeCode.d.ts.map +1 -1
  190. package/dist/harness/claudeCode.js +47 -3
  191. package/dist/harness/claudeCode.js.map +1 -1
  192. package/dist/harness/cline.d.ts.map +1 -1
  193. package/dist/harness/cline.js +14 -23
  194. package/dist/harness/cline.js.map +1 -1
  195. package/dist/harness/codex.d.ts +6 -1
  196. package/dist/harness/codex.d.ts.map +1 -1
  197. package/dist/harness/codex.js +23 -21
  198. package/dist/harness/codex.js.map +1 -1
  199. package/dist/harness/continue.d.ts +10 -4
  200. package/dist/harness/continue.d.ts.map +1 -1
  201. package/dist/harness/continue.js +22 -22
  202. package/dist/harness/continue.js.map +1 -1
  203. package/dist/harness/cursor.d.ts +5 -2
  204. package/dist/harness/cursor.d.ts.map +1 -1
  205. package/dist/harness/cursor.js +19 -23
  206. package/dist/harness/cursor.js.map +1 -1
  207. package/dist/harness/detector.d.ts +0 -1
  208. package/dist/harness/detector.d.ts.map +1 -1
  209. package/dist/harness/detector.js +0 -1
  210. package/dist/harness/detector.js.map +1 -1
  211. package/dist/harness/dsh.d.ts +5 -3
  212. package/dist/harness/dsh.d.ts.map +1 -1
  213. package/dist/harness/dsh.js +17 -29
  214. package/dist/harness/dsh.js.map +1 -1
  215. package/dist/harness/eve.d.ts +3 -4
  216. package/dist/harness/eve.d.ts.map +1 -1
  217. package/dist/harness/eve.js +3 -4
  218. package/dist/harness/eve.js.map +1 -1
  219. package/dist/harness/githubCopilot.d.ts +4 -1
  220. package/dist/harness/githubCopilot.d.ts.map +1 -1
  221. package/dist/harness/githubCopilot.js +12 -20
  222. package/dist/harness/githubCopilot.js.map +1 -1
  223. package/dist/harness/goose.d.ts +7 -3
  224. package/dist/harness/goose.d.ts.map +1 -1
  225. package/dist/harness/goose.js +18 -21
  226. package/dist/harness/goose.js.map +1 -1
  227. package/dist/harness/grok.d.ts +2 -2
  228. package/dist/harness/grok.d.ts.map +1 -1
  229. package/dist/harness/grok.js +13 -24
  230. package/dist/harness/grok.js.map +1 -1
  231. package/dist/harness/hermes.d.ts +6 -0
  232. package/dist/harness/hermes.d.ts.map +1 -1
  233. package/dist/harness/hermes.js +17 -16
  234. package/dist/harness/hermes.js.map +1 -1
  235. package/dist/harness/muse.d.ts +3 -7
  236. package/dist/harness/muse.d.ts.map +1 -1
  237. package/dist/harness/muse.js +15 -35
  238. package/dist/harness/muse.js.map +1 -1
  239. package/dist/harness/n8n.d.ts +13 -14
  240. package/dist/harness/n8n.d.ts.map +1 -1
  241. package/dist/harness/n8n.js +21 -181
  242. package/dist/harness/n8n.js.map +1 -1
  243. package/dist/harness/openWebUI.d.ts.map +1 -1
  244. package/dist/harness/openWebUI.js +5 -4
  245. package/dist/harness/openWebUI.js.map +1 -1
  246. package/dist/harness/openaiAgents.d.ts +1 -1
  247. package/dist/harness/openaiAgents.js +2 -2
  248. package/dist/harness/openaiAgents.js.map +1 -1
  249. package/dist/harness/openclaw.d.ts +8 -0
  250. package/dist/harness/openclaw.d.ts.map +1 -1
  251. package/dist/harness/openclaw.js +27 -12
  252. package/dist/harness/openclaw.js.map +1 -1
  253. package/dist/harness/opencode.d.ts +4 -5
  254. package/dist/harness/opencode.d.ts.map +1 -1
  255. package/dist/harness/opencode.js +15 -30
  256. package/dist/harness/opencode.js.map +1 -1
  257. package/dist/harness/openhands.d.ts +9 -3
  258. package/dist/harness/openhands.d.ts.map +1 -1
  259. package/dist/harness/openhands.js +30 -24
  260. package/dist/harness/openhands.js.map +1 -1
  261. package/dist/harness/pi.d.ts +7 -2
  262. package/dist/harness/pi.d.ts.map +1 -1
  263. package/dist/harness/pi.js +19 -18
  264. package/dist/harness/pi.js.map +1 -1
  265. package/dist/harness/rooCode.d.ts +12 -5
  266. package/dist/harness/rooCode.d.ts.map +1 -1
  267. package/dist/harness/rooCode.js +23 -39
  268. package/dist/harness/rooCode.js.map +1 -1
  269. package/dist/harness/rulesFiles.d.ts +53 -0
  270. package/dist/harness/rulesFiles.d.ts.map +1 -0
  271. package/dist/harness/rulesFiles.js +106 -0
  272. package/dist/harness/rulesFiles.js.map +1 -0
  273. package/dist/harness/smolagents.d.ts +2 -2
  274. package/dist/harness/smolagents.js +3 -3
  275. package/dist/harness/smolagents.js.map +1 -1
  276. package/dist/harness/types.d.ts +20 -6
  277. package/dist/harness/types.d.ts.map +1 -1
  278. package/dist/harness/types.js +1 -95
  279. package/dist/harness/types.js.map +1 -1
  280. package/dist/harness/windsurf.d.ts +6 -1
  281. package/dist/harness/windsurf.d.ts.map +1 -1
  282. package/dist/harness/windsurf.js +21 -24
  283. package/dist/harness/windsurf.js.map +1 -1
  284. package/dist/harness/xirp.d.ts +1 -2
  285. package/dist/harness/xirp.d.ts.map +1 -1
  286. package/dist/harness/xirp.js +2 -3
  287. package/dist/harness/xirp.js.map +1 -1
  288. package/dist/lib/api.d.ts +27 -1
  289. package/dist/lib/api.d.ts.map +1 -1
  290. package/dist/lib/api.js +55 -18
  291. package/dist/lib/api.js.map +1 -1
  292. package/dist/lib/ciscoScanner.d.ts +1 -1
  293. package/dist/lib/ciscoScanner.js +1 -1
  294. package/dist/lib/decisionsLogRefresh.d.ts.map +1 -1
  295. package/dist/lib/decisionsLogRefresh.js +6 -7
  296. package/dist/lib/decisionsLogRefresh.js.map +1 -1
  297. package/dist/lib/deviceReport.d.ts +15 -0
  298. package/dist/lib/deviceReport.d.ts.map +1 -1
  299. package/dist/lib/deviceReport.js +30 -0
  300. package/dist/lib/deviceReport.js.map +1 -1
  301. package/dist/lib/gitHooks.d.ts +0 -1
  302. package/dist/lib/gitHooks.d.ts.map +1 -1
  303. package/dist/lib/gitHooks.js +8 -9
  304. package/dist/lib/gitHooks.js.map +1 -1
  305. package/dist/lib/gracefulShutdown.d.ts +1 -1
  306. package/dist/lib/gracefulShutdown.js +1 -1
  307. package/dist/lib/hash.d.ts +0 -1
  308. package/dist/lib/hash.d.ts.map +1 -1
  309. package/dist/lib/hash.js +0 -1
  310. package/dist/lib/hash.js.map +1 -1
  311. package/dist/lib/inventory.d.ts +47 -0
  312. package/dist/lib/inventory.d.ts.map +1 -0
  313. package/dist/lib/inventory.js +106 -0
  314. package/dist/lib/inventory.js.map +1 -0
  315. package/dist/lib/logger.d.ts +0 -1
  316. package/dist/lib/logger.d.ts.map +1 -1
  317. package/dist/lib/logger.js +0 -1
  318. package/dist/lib/logger.js.map +1 -1
  319. package/dist/lib/mdmManifest.d.ts +30 -2
  320. package/dist/lib/mdmManifest.d.ts.map +1 -1
  321. package/dist/lib/mdmManifest.js +50 -2
  322. package/dist/lib/mdmManifest.js.map +1 -1
  323. package/dist/lib/onboarding.d.ts +0 -1
  324. package/dist/lib/onboarding.d.ts.map +1 -1
  325. package/dist/lib/onboarding.js +1 -2
  326. package/dist/lib/onboarding.js.map +1 -1
  327. package/dist/lib/policySnapshot.d.ts +3 -2
  328. package/dist/lib/policySnapshot.d.ts.map +1 -1
  329. package/dist/lib/policySnapshot.js +6 -3
  330. package/dist/lib/policySnapshot.js.map +1 -1
  331. package/dist/lib/process.d.ts +5 -1
  332. package/dist/lib/process.d.ts.map +1 -1
  333. package/dist/lib/process.js +13 -4
  334. package/dist/lib/process.js.map +1 -1
  335. package/dist/lib/providerProbe.d.ts +3 -3
  336. package/dist/lib/providerProbe.d.ts.map +1 -1
  337. package/dist/lib/providerProbe.js +15 -7
  338. package/dist/lib/providerProbe.js.map +1 -1
  339. package/dist/lib/sandbox/firecracker.d.ts +3 -3
  340. package/dist/lib/sandbox/firecracker.js +3 -3
  341. package/dist/lib/sandbox/index.d.ts +2 -2
  342. package/dist/lib/sandbox/index.js +2 -2
  343. package/dist/lib/sandbox/oci.d.ts +1 -1
  344. package/dist/lib/sandbox/oci.js +1 -1
  345. package/dist/lib/sandbox/types.d.ts +1 -1
  346. package/dist/lib/sandbox/types.js +1 -1
  347. package/dist/lib/sopFrontMatter.d.ts +1 -1
  348. package/dist/lib/sopFrontMatter.js +1 -1
  349. package/package.json +6 -5
  350. package/resources/sandbox/Dockerfile +1 -1
  351. package/resources/sandbox/entrypoint.sh +4 -4
  352. package/resources/sandbox/firecracker-launch.sh +1 -1
  353. package/dist/harness/base.d.ts +0 -24
  354. package/dist/harness/base.d.ts.map +0 -1
  355. package/dist/harness/base.js +0 -74
  356. package/dist/harness/base.js.map +0 -1
@@ -0,0 +1,223 @@
1
+ /**
2
+ * `intutic budget set|keys|key|members|member` — the workspace caps, and the
3
+ * spend budgets and rate limits on virtual keys and members.
4
+ *
5
+ * Subcommands:
6
+ * - `intutic budget set [--daily <usd|default>] [--monthly <usd|default>] [--threshold <pct>]
7
+ * [--daily-enforcement hard|soft] [--monthly-enforcement hard|soft]`
8
+ * - `intutic budget keys [--json]`
9
+ * - `intutic budget key <keyId> [--daily <usd|none>] [--monthly <usd|none>]
10
+ * [--daily-enforcement hard|soft] [--monthly-enforcement hard|soft]
11
+ * [--rpm <n|none>] [--tpm <n|none>]`
12
+ * - `intutic budget members [--json]`
13
+ * - `intutic budget member <memberId|default> [--daily <usd|none>]
14
+ * [--monthly <usd|none>] [--daily-enforcement hard|soft]
15
+ * [--monthly-enforcement hard|soft]`
16
+ *
17
+ * Server side (services/control-plane/src/routes/budget.ts, routes/keys.ts):
18
+ * `PUT /api/v1/budget`, `GET /api/v1/budget/keys`, `PATCH /api/v1/keys/:id`,
19
+ * `GET /api/v1/budget/members`, `PUT /api/v1/budget/members/:memberId`. The
20
+ * writes need OWNER or ADMIN, and member budgets a plan with them; the
21
+ * server's refusal is printed as it says it.
22
+ *
23
+ * Every write here changes only what its flags name. `budget set` sends only
24
+ * those fields (the route is a partial update); the key and member writes
25
+ * read the subject's budgets first and send them back with the change,
26
+ * because those routes replace a subject's budgets whole.
27
+ *
28
+ * @module
29
+ */
30
+ import { log } from '../lib/logger.js';
31
+ import { fail, runApiCommand } from './apiCommand.js';
32
+ const usd = (n) => `$${n.toFixed(2)}`;
33
+ /** A dollar amount, or `none` (returned as `null`), or fails naming the flag. */
34
+ function amountOrNone(value, flag) {
35
+ if (value.trim().toLowerCase() === 'none')
36
+ return null;
37
+ const n = Number(value);
38
+ if (!Number.isFinite(n) || n <= 0)
39
+ fail(`${flag} must be a dollar amount above 0, or "none", got "${value}"`);
40
+ return n;
41
+ }
42
+ /** A whole number, or `none` (returned as `null`), or fails naming the flag. */
43
+ function countOrNone(value, flag) {
44
+ if (value.trim().toLowerCase() === 'none')
45
+ return null;
46
+ const n = Number(value);
47
+ if (!Number.isInteger(n) || n <= 0)
48
+ fail(`${flag} must be a positive whole number, or "none", got "${value}"`);
49
+ return n;
50
+ }
51
+ function enforcement(value, flag) {
52
+ if (value === undefined)
53
+ return undefined;
54
+ if (value !== 'hard' && value !== 'soft')
55
+ fail(`${flag} must be hard or soft, got "${value}"`);
56
+ return value;
57
+ }
58
+ /**
59
+ * `current` with the periods the flags name changed: `--daily none` removes
60
+ * the day budget, `--daily 5` sets it (keeping its enforcement unless
61
+ * `--daily-enforcement` says otherwise, hard for a new one), and an
62
+ * enforcement flag alone changes an existing budget's.
63
+ */
64
+ export function applyBudgetFlags(current, flags) {
65
+ const out = new Map(current.map((b) => [b.period, { ...b }]));
66
+ for (const [period, amount, mode, flag] of [
67
+ ['day', flags.daily, flags.dailyEnforcement, '--daily'],
68
+ ['month', flags.monthly, flags.monthlyEnforcement, '--monthly'],
69
+ ]) {
70
+ const how = enforcement(mode, `${flag}-enforcement`);
71
+ if (amount !== undefined) {
72
+ const limitUsd = amountOrNone(amount, flag);
73
+ if (limitUsd === null)
74
+ out.delete(period);
75
+ else
76
+ out.set(period, { period, limitUsd, enforcement: how ?? out.get(period)?.enforcement ?? 'hard' });
77
+ }
78
+ else if (how) {
79
+ const existing = out.get(period);
80
+ if (!existing)
81
+ fail(`${flag}-enforcement needs a ${period} budget: set one with ${flag} <usd>`);
82
+ existing.enforcement = how;
83
+ }
84
+ }
85
+ return [...out.values()].sort((a, b) => (a.period === b.period ? 0 : a.period === 'day' ? -1 : 1));
86
+ }
87
+ /** One budget list in words: `$5.00/day hard, $50.00/month soft`, or `none`. */
88
+ export function describeBudgets(budgets) {
89
+ if (budgets.length === 0)
90
+ return 'none';
91
+ return budgets.map((b) => `${usd(b.limitUsd)}/${b.period} ${b.enforcement}`).join(', ');
92
+ }
93
+ function describeRateLimit(r) {
94
+ const parts = [r.rpm ? `${r.rpm} requests/min` : '', r.tpm ? `${r.tpm} tokens/min` : ''].filter(Boolean);
95
+ return parts.length ? parts.join(', ') : 'none';
96
+ }
97
+ function hasBudgetFlags(flags) {
98
+ return [flags.daily, flags.monthly, flags.dailyEnforcement, flags.monthlyEnforcement].some((v) => v !== undefined);
99
+ }
100
+ /** A cap flag: a dollar amount (0 is no cap), or `default` (sent as `null`) to return to the default. */
101
+ function capOrDefault(value, flag) {
102
+ if (value === undefined)
103
+ return undefined;
104
+ if (value.trim().toLowerCase() === 'default')
105
+ return null;
106
+ const n = Number(value);
107
+ if (!(n >= 0))
108
+ fail(`${flag} must be a dollar amount (0 for no cap) or "default", got "${value}"`);
109
+ return n;
110
+ }
111
+ /**
112
+ * The `PUT /api/v1/budget` body for the flags given: only those, so a cap
113
+ * nobody named stays as it is — in particular a daily cap that was never
114
+ * saved stays on the default.
115
+ */
116
+ export function budgetSetBody(opts) {
117
+ const body = {};
118
+ const daily = capOrDefault(opts.daily, '--daily');
119
+ const monthly = capOrDefault(opts.monthly, '--monthly');
120
+ if (daily !== undefined)
121
+ body.daily_budget_usd = daily;
122
+ if (monthly !== undefined)
123
+ body.monthly_budget_usd = monthly;
124
+ if (opts.threshold !== undefined) {
125
+ const t = Number(opts.threshold);
126
+ if (!(Number.isInteger(t) && t >= 1 && t <= 100))
127
+ fail(`--threshold must be a whole percentage from 1 to 100, got "${opts.threshold}"`);
128
+ body.alert_threshold_pct = t;
129
+ }
130
+ const dailyEnforcement = enforcement(opts.dailyEnforcement, '--daily-enforcement');
131
+ const monthlyEnforcement = enforcement(opts.monthlyEnforcement, '--monthly-enforcement');
132
+ if (dailyEnforcement)
133
+ body.daily_enforcement = dailyEnforcement;
134
+ if (monthlyEnforcement)
135
+ body.monthly_enforcement = monthlyEnforcement;
136
+ if (Object.keys(body).length === 0) {
137
+ fail('Nothing to change: pass --daily, --monthly, --threshold, --daily-enforcement or --monthly-enforcement');
138
+ }
139
+ return body;
140
+ }
141
+ export async function runBudgetSet(opts) {
142
+ const body = budgetSetBody(opts);
143
+ await runApiCommand(opts, 'Failed to update the workspace budget', async (client) => {
144
+ await client.put('/api/v1/budget', body);
145
+ return client.get('/api/v1/budget');
146
+ }, (b) => {
147
+ log.success('Workspace budget updated');
148
+ log.field('Daily cap', `${usd(b.daily_budget_usd)} (${b.daily_enforcement})${b.daily_budget_is_default ? ', the default' : ''}`);
149
+ log.field('Monthly cap', `${usd(b.monthly_budget_usd)} (${b.monthly_enforcement})`);
150
+ log.field('Alert at', `${b.alert_threshold_pct}%`);
151
+ });
152
+ }
153
+ export async function runBudgetKeys(opts) {
154
+ await runApiCommand(opts, 'Failed to list key budgets', (client) => client.get('/api/v1/budget/keys'), (res) => {
155
+ log.header('Virtual key budgets');
156
+ if (res.keys.length === 0)
157
+ console.log(' No live keys.');
158
+ for (const k of res.keys) {
159
+ console.log(` ${k.keyPrefix}… ${k.label} (${k.keyId}, ${k.memberEmail})`);
160
+ console.log(` Spent today ${usd(k.spendTodayUsd)}, this month ${usd(k.spendThisMonthUsd)}`);
161
+ console.log(` Budgets: ${describeBudgets(k.budgets)} Rate limit: ${describeRateLimit(k.rateLimit)}`);
162
+ }
163
+ console.log(` Days reset at ${res.dayResetsAt}; months at ${res.monthResetsAt} (UTC).`);
164
+ });
165
+ }
166
+ export async function runBudgetKey(keyId, opts) {
167
+ const budgetsChange = hasBudgetFlags(opts);
168
+ if (!budgetsChange && opts.rpm === undefined && opts.tpm === undefined) {
169
+ fail('Nothing to change: pass --daily, --monthly, an enforcement flag, --rpm or --tpm');
170
+ }
171
+ const rateLimit = {};
172
+ if (opts.rpm !== undefined)
173
+ rateLimit.rpm = countOrNone(opts.rpm, '--rpm');
174
+ if (opts.tpm !== undefined)
175
+ rateLimit.tpm = countOrNone(opts.tpm, '--tpm');
176
+ await runApiCommand(opts, `Failed to update key ${keyId}`, async (client) => {
177
+ let budgets;
178
+ if (budgetsChange) {
179
+ const listed = await client.get('/api/v1/budget/keys');
180
+ const current = listed.keys.find((k) => k.keyId === keyId);
181
+ if (!current)
182
+ throw new Error(`no live key ${keyId} in this workspace`);
183
+ budgets = applyBudgetFlags(current.budgets, opts);
184
+ }
185
+ return client.patch(`/api/v1/keys/${keyId}`, {
186
+ ...(budgets ? { budgets } : {}),
187
+ ...(Object.keys(rateLimit).length ? { rateLimit } : {}),
188
+ });
189
+ }, (k) => {
190
+ log.success(`Key ${k.keyId} updated`);
191
+ log.field('Budgets', describeBudgets(k.budgets));
192
+ log.field('Rate limit', describeRateLimit(k.rateLimit));
193
+ });
194
+ }
195
+ export async function runBudgetMembers(opts) {
196
+ await runApiCommand(opts, 'Failed to list member budgets', (client) => client.get('/api/v1/budget/members'), (res) => {
197
+ log.header('Member budgets');
198
+ log.field('Default (every member without their own)', describeBudgets(res.defaultBudgets));
199
+ for (const m of res.members) {
200
+ console.log(` ${m.email} (${m.memberId})`);
201
+ console.log(` Spent today ${usd(m.spendTodayUsd)}, this month ${usd(m.spendThisMonthUsd)}`);
202
+ console.log(` Own: ${describeBudgets(m.budgets)} Applies: ${describeBudgets(m.effectiveBudgets)}`);
203
+ }
204
+ console.log(` Days reset at ${res.dayResetsAt}; months at ${res.monthResetsAt} (UTC).`);
205
+ });
206
+ }
207
+ export async function runBudgetMember(memberId, opts) {
208
+ if (!hasBudgetFlags(opts))
209
+ fail('Nothing to change: pass --daily, --monthly or an enforcement flag');
210
+ await runApiCommand(opts, `Failed to update the budgets of ${memberId}`, async (client) => {
211
+ const listed = await client.get('/api/v1/budget/members');
212
+ const current = memberId === 'default' ? listed.defaultBudgets : listed.members.find((m) => m.memberId === memberId)?.budgets;
213
+ if (!current)
214
+ throw new Error(`no active member ${memberId} in this workspace`);
215
+ return client.put(`/api/v1/budget/members/${encodeURIComponent(memberId)}`, {
216
+ budgets: applyBudgetFlags(current, opts),
217
+ });
218
+ }, (m) => {
219
+ log.success(m.memberId === 'default' ? 'Default member budget updated' : `Budgets of ${m.memberId} updated`);
220
+ log.field('Budgets', describeBudgets(m.budgets));
221
+ });
222
+ }
223
+ //# sourceMappingURL=budgetLimits.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"budgetLimits.js","sourceRoot":"","sources":["../../src/commands/budgetLimits.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AAGH,OAAO,EAAE,GAAG,EAAE,MAAM,kBAAkB,CAAA;AACtC,OAAO,EAAE,IAAI,EAAE,aAAa,EAAuB,MAAM,iBAAiB,CAAA;AAkE1E,MAAM,GAAG,GAAG,CAAC,CAAS,EAAE,EAAE,CAAC,IAAI,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,CAAA;AAE7C,iFAAiF;AACjF,SAAS,YAAY,CAAC,KAAa,EAAE,IAAY;IAC/C,IAAI,KAAK,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,KAAK,MAAM;QAAE,OAAO,IAAI,CAAA;IACtD,MAAM,CAAC,GAAG,MAAM,CAAC,KAAK,CAAC,CAAA;IACvB,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC;QAAE,IAAI,CAAC,GAAG,IAAI,qDAAqD,KAAK,GAAG,CAAC,CAAA;IAC7G,OAAO,CAAC,CAAA;AACV,CAAC;AAED,gFAAgF;AAChF,SAAS,WAAW,CAAC,KAAa,EAAE,IAAY;IAC9C,IAAI,KAAK,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,KAAK,MAAM;QAAE,OAAO,IAAI,CAAA;IACtD,MAAM,CAAC,GAAG,MAAM,CAAC,KAAK,CAAC,CAAA;IACvB,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC;QAAE,IAAI,CAAC,GAAG,IAAI,qDAAqD,KAAK,GAAG,CAAC,CAAA;IAC9G,OAAO,CAAC,CAAA;AACV,CAAC;AAED,SAAS,WAAW,CAAC,KAAyB,EAAE,IAAY;IAC1D,IAAI,KAAK,KAAK,SAAS;QAAE,OAAO,SAAS,CAAA;IACzC,IAAI,KAAK,KAAK,MAAM,IAAI,KAAK,KAAK,MAAM;QAAE,IAAI,CAAC,GAAG,IAAI,+BAA+B,KAAK,GAAG,CAAC,CAAA;IAC9F,OAAO,KAAK,CAAA;AACd,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,gBAAgB,CAAC,OAA+B,EAAE,KAAkB;IAClF,MAAM,GAAG,GAAG,IAAI,GAAG,CAAiC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,MAAM,EAAE,EAAE,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC,CAAA;IAC7F,KAAK,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI;QACzC,CAAC,KAAK,EAAE,KAAK,CAAC,KAAK,EAAE,KAAK,CAAC,gBAAgB,EAAE,SAAS,CAAC;QACvD,CAAC,OAAO,EAAE,KAAK,CAAC,OAAO,EAAE,KAAK,CAAC,kBAAkB,EAAE,WAAW,CAAC;KACvD,EAAE,CAAC;QACX,MAAM,GAAG,GAAG,WAAW,CAAC,IAAI,EAAE,GAAG,IAAI,cAAc,CAAC,CAAA;QACpD,IAAI,MAAM,KAAK,SAAS,EAAE,CAAC;YACzB,MAAM,QAAQ,GAAG,YAAY,CAAC,MAAM,EAAE,IAAI,CAAC,CAAA;YAC3C,IAAI,QAAQ,KAAK,IAAI;gBAAE,GAAG,CAAC,MAAM,CAAC,MAAM,CAAC,CAAA;;gBACpC,GAAG,CAAC,GAAG,CAAC,MAAM,EAAE,EAAE,MAAM,EAAE,QAAQ,EAAE,WAAW,EAAE,GAAG,IAAI,GAAG,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,WAAW,IAAI,MAAM,EAAE,CAAC,CAAA;QACxG,CAAC;aAAM,IAAI,GAAG,EAAE,CAAC;YACf,MAAM,QAAQ,GAAG,GAAG,CAAC,GAAG,CAAC,MAAM,CAAC,CAAA;YAChC,IAAI,CAAC,QAAQ;gBAAE,IAAI,CAAC,GAAG,IAAI,wBAAwB,MAAM,yBAAyB,IAAI,QAAQ,CAAC,CAAA;YAC/F,QAAQ,CAAC,WAAW,GAAG,GAAG,CAAA;QAC5B,CAAC;IACH,CAAC;IACD,OAAO,CAAC,GAAG,GAAG,CAAC,MAAM,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,MAAM,KAAK,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,KAAK,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAA;AACpG,CAAC;AAED,gFAAgF;AAChF,MAAM,UAAU,eAAe,CAAC,OAA+B;IAC7D,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,MAAM,CAAA;IACvC,OAAO,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,GAAG,GAAG,CAAC,CAAC,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,MAAM,IAAI,CAAC,CAAC,WAAW,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;AACzF,CAAC;AAED,SAAS,iBAAiB,CAAC,CAAe;IACxC,MAAM,KAAK,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,GAAG,eAAe,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,GAAG,aAAa,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAA;IACxG,OAAO,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,MAAM,CAAA;AACjD,CAAC;AAED,SAAS,cAAc,CAAC,KAAkB;IACxC,OAAO,CAAC,KAAK,CAAC,KAAK,EAAE,KAAK,CAAC,OAAO,EAAE,KAAK,CAAC,gBAAgB,EAAE,KAAK,CAAC,kBAAkB,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,KAAK,SAAS,CAAC,CAAA;AACpH,CAAC;AAED,yGAAyG;AACzG,SAAS,YAAY,CAAC,KAAyB,EAAE,IAAY;IAC3D,IAAI,KAAK,KAAK,SAAS;QAAE,OAAO,SAAS,CAAA;IACzC,IAAI,KAAK,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,KAAK,SAAS;QAAE,OAAO,IAAI,CAAA;IACzD,MAAM,CAAC,GAAG,MAAM,CAAC,KAAK,CAAC,CAAA;IACvB,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;QAAE,IAAI,CAAC,GAAG,IAAI,8DAA8D,KAAK,GAAG,CAAC,CAAA;IAClG,OAAO,CAAC,CAAA;AACV,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,aAAa,CAAC,IAAc;IAC1C,MAAM,IAAI,GAA4B,EAAE,CAAA;IACxC,MAAM,KAAK,GAAG,YAAY,CAAC,IAAI,CAAC,KAAK,EAAE,SAAS,CAAC,CAAA;IACjD,MAAM,OAAO,GAAG,YAAY,CAAC,IAAI,CAAC,OAAO,EAAE,WAAW,CAAC,CAAA;IACvD,IAAI,KAAK,KAAK,SAAS;QAAE,IAAI,CAAC,gBAAgB,GAAG,KAAK,CAAA;IACtD,IAAI,OAAO,KAAK,SAAS;QAAE,IAAI,CAAC,kBAAkB,GAAG,OAAO,CAAA;IAC5D,IAAI,IAAI,CAAC,SAAS,KAAK,SAAS,EAAE,CAAC;QACjC,MAAM,CAAC,GAAG,MAAM,CAAC,IAAI,CAAC,SAAS,CAAC,CAAA;QAChC,IAAI,CAAC,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,GAAG,CAAC;YAAE,IAAI,CAAC,8DAA8D,IAAI,CAAC,SAAS,GAAG,CAAC,CAAA;QACvI,IAAI,CAAC,mBAAmB,GAAG,CAAC,CAAA;IAC9B,CAAC;IACD,MAAM,gBAAgB,GAAG,WAAW,CAAC,IAAI,CAAC,gBAAgB,EAAE,qBAAqB,CAAC,CAAA;IAClF,MAAM,kBAAkB,GAAG,WAAW,CAAC,IAAI,CAAC,kBAAkB,EAAE,uBAAuB,CAAC,CAAA;IACxF,IAAI,gBAAgB;QAAE,IAAI,CAAC,iBAAiB,GAAG,gBAAgB,CAAA;IAC/D,IAAI,kBAAkB;QAAE,IAAI,CAAC,mBAAmB,GAAG,kBAAkB,CAAA;IACrE,IAAI,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACnC,IAAI,CAAC,uGAAuG,CAAC,CAAA;IAC/G,CAAC;IACD,OAAO,IAAI,CAAA;AACb,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,YAAY,CAAC,IAAc;IAC/C,MAAM,IAAI,GAAG,aAAa,CAAC,IAAI,CAAC,CAAA;IAChC,MAAM,aAAa,CACjB,IAAI,EACJ,uCAAuC,EACvC,KAAK,EAAE,MAAM,EAAE,EAAE;QACf,MAAM,MAAM,CAAC,GAAG,CAAC,gBAAgB,EAAE,IAAI,CAAC,CAAA;QACxC,OAAO,MAAM,CAAC,GAAG,CAAkB,gBAAgB,CAAC,CAAA;IACtD,CAAC,EACD,CAAC,CAAC,EAAE,EAAE;QACJ,GAAG,CAAC,OAAO,CAAC,0BAA0B,CAAC,CAAA;QACvC,GAAG,CAAC,KAAK,CAAC,WAAW,EAAE,GAAG,GAAG,CAAC,CAAC,CAAC,gBAAgB,CAAC,KAAK,CAAC,CAAC,iBAAiB,IAAI,CAAC,CAAC,uBAAuB,CAAC,CAAC,CAAC,eAAe,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAA;QAChI,GAAG,CAAC,KAAK,CAAC,aAAa,EAAE,GAAG,GAAG,CAAC,CAAC,CAAC,kBAAkB,CAAC,KAAK,CAAC,CAAC,mBAAmB,GAAG,CAAC,CAAA;QACnF,GAAG,CAAC,KAAK,CAAC,UAAU,EAAE,GAAG,CAAC,CAAC,mBAAmB,GAAG,CAAC,CAAA;IACpD,CAAC,CACF,CAAA;AACH,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,aAAa,CAAC,IAAoB;IACtD,MAAM,aAAa,CACjB,IAAI,EACJ,4BAA4B,EAC5B,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,CAAC,GAAG,CAAe,qBAAqB,CAAC,EAC3D,CAAC,GAAG,EAAE,EAAE;QACN,GAAG,CAAC,MAAM,CAAC,qBAAqB,CAAC,CAAA;QACjC,IAAI,GAAG,CAAC,IAAI,CAAC,MAAM,KAAK,CAAC;YAAE,OAAO,CAAC,GAAG,CAAC,iBAAiB,CAAC,CAAA;QACzD,KAAK,MAAM,CAAC,IAAI,GAAG,CAAC,IAAI,EAAE,CAAC;YACzB,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,SAAS,MAAM,CAAC,CAAC,KAAK,MAAM,CAAC,CAAC,KAAK,KAAK,CAAC,CAAC,WAAW,GAAG,CAAC,CAAA;YAC5E,OAAO,CAAC,GAAG,CAAC,mBAAmB,GAAG,CAAC,CAAC,CAAC,aAAa,CAAC,gBAAgB,GAAG,CAAC,CAAC,CAAC,iBAAiB,CAAC,EAAE,CAAC,CAAA;YAC9F,OAAO,CAAC,GAAG,CAAC,gBAAgB,eAAe,CAAC,CAAC,CAAC,OAAO,CAAC,kBAAkB,iBAAiB,CAAC,CAAC,CAAC,SAAS,CAAC,EAAE,CAAC,CAAA;QAC3G,CAAC;QACD,OAAO,CAAC,GAAG,CAAC,mBAAmB,GAAG,CAAC,WAAW,eAAe,GAAG,CAAC,aAAa,SAAS,CAAC,CAAA;IAC1F,CAAC,CACF,CAAA;AACH,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,YAAY,CAAC,KAAa,EAAE,IAAc;IAC9D,MAAM,aAAa,GAAG,cAAc,CAAC,IAAI,CAAC,CAAA;IAC1C,IAAI,CAAC,aAAa,IAAI,IAAI,CAAC,GAAG,KAAK,SAAS,IAAI,IAAI,CAAC,GAAG,KAAK,SAAS,EAAE,CAAC;QACvE,IAAI,CAAC,iFAAiF,CAAC,CAAA;IACzF,CAAC;IACD,MAAM,SAAS,GAA0B,EAAE,CAAA;IAC3C,IAAI,IAAI,CAAC,GAAG,KAAK,SAAS;QAAE,SAAS,CAAC,GAAG,GAAG,WAAW,CAAC,IAAI,CAAC,GAAG,EAAE,OAAO,CAAC,CAAA;IAC1E,IAAI,IAAI,CAAC,GAAG,KAAK,SAAS;QAAE,SAAS,CAAC,GAAG,GAAG,WAAW,CAAC,IAAI,CAAC,GAAG,EAAE,OAAO,CAAC,CAAA;IAE1E,MAAM,aAAa,CACjB,IAAI,EACJ,wBAAwB,KAAK,EAAE,EAC/B,KAAK,EAAE,MAAM,EAAE,EAAE;QACf,IAAI,OAAkC,CAAA;QACtC,IAAI,aAAa,EAAE,CAAC;YAClB,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,GAAG,CAAe,qBAAqB,CAAC,CAAA;YACpE,MAAM,OAAO,GAAG,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,KAAK,KAAK,CAAC,CAAA;YAC1D,IAAI,CAAC,OAAO;gBAAE,MAAM,IAAI,KAAK,CAAC,eAAe,KAAK,oBAAoB,CAAC,CAAA;YACvE,OAAO,GAAG,gBAAgB,CAAC,OAAO,CAAC,OAAO,EAAE,IAAI,CAAC,CAAA;QACnD,CAAC;QACD,OAAO,MAAM,CAAC,KAAK,CAAqE,gBAAgB,KAAK,EAAE,EAAE;YAC/G,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;YAC/B,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,SAAS,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;SACxD,CAAC,CAAA;IACJ,CAAC,EACD,CAAC,CAAC,EAAE,EAAE;QACJ,GAAG,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,KAAK,UAAU,CAAC,CAAA;QACrC,GAAG,CAAC,KAAK,CAAC,SAAS,EAAE,eAAe,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAA;QAChD,GAAG,CAAC,KAAK,CAAC,YAAY,EAAE,iBAAiB,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAA;IACzD,CAAC,CACF,CAAA;AACH,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,gBAAgB,CAAC,IAAoB;IACzD,MAAM,aAAa,CACjB,IAAI,EACJ,+BAA+B,EAC/B,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,CAAC,GAAG,CAAkB,wBAAwB,CAAC,EACjE,CAAC,GAAG,EAAE,EAAE;QACN,GAAG,CAAC,MAAM,CAAC,gBAAgB,CAAC,CAAA;QAC5B,GAAG,CAAC,KAAK,CAAC,0CAA0C,EAAE,eAAe,CAAC,GAAG,CAAC,cAAc,CAAC,CAAC,CAAA;QAC1F,KAAK,MAAM,CAAC,IAAI,GAAG,CAAC,OAAO,EAAE,CAAC;YAC5B,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,KAAK,MAAM,CAAC,CAAC,QAAQ,GAAG,CAAC,CAAA;YAC5C,OAAO,CAAC,GAAG,CAAC,mBAAmB,GAAG,CAAC,CAAC,CAAC,aAAa,CAAC,gBAAgB,GAAG,CAAC,CAAC,CAAC,iBAAiB,CAAC,EAAE,CAAC,CAAA;YAC9F,OAAO,CAAC,GAAG,CAAC,YAAY,eAAe,CAAC,CAAC,CAAC,OAAO,CAAC,eAAe,eAAe,CAAC,CAAC,CAAC,gBAAgB,CAAC,EAAE,CAAC,CAAA;QACzG,CAAC;QACD,OAAO,CAAC,GAAG,CAAC,mBAAmB,GAAG,CAAC,WAAW,eAAe,GAAG,CAAC,aAAa,SAAS,CAAC,CAAA;IAC1F,CAAC,CACF,CAAA;AACH,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,eAAe,CAAC,QAAgB,EAAE,IAAiB;IACvE,IAAI,CAAC,cAAc,CAAC,IAAI,CAAC;QAAE,IAAI,CAAC,mEAAmE,CAAC,CAAA;IACpG,MAAM,aAAa,CACjB,IAAI,EACJ,mCAAmC,QAAQ,EAAE,EAC7C,KAAK,EAAE,MAAM,EAAE,EAAE;QACf,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,GAAG,CAAkB,wBAAwB,CAAC,CAAA;QAC1E,MAAM,OAAO,GAAG,QAAQ,KAAK,SAAS,CAAC,CAAC,CAAC,MAAM,CAAC,cAAc,CAAC,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,KAAK,QAAQ,CAAC,EAAE,OAAO,CAAA;QAC7H,IAAI,CAAC,OAAO;YAAE,MAAM,IAAI,KAAK,CAAC,oBAAoB,QAAQ,oBAAoB,CAAC,CAAA;QAC/E,OAAO,MAAM,CAAC,GAAG,CAA+C,0BAA0B,kBAAkB,CAAC,QAAQ,CAAC,EAAE,EAAE;YACxH,OAAO,EAAE,gBAAgB,CAAC,OAAO,EAAE,IAAI,CAAC;SACzC,CAAC,CAAA;IACJ,CAAC,EACD,CAAC,CAAC,EAAE,EAAE;QACJ,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,QAAQ,KAAK,SAAS,CAAC,CAAC,CAAC,+BAA+B,CAAC,CAAC,CAAC,cAAc,CAAC,CAAC,QAAQ,UAAU,CAAC,CAAA;QAC5G,GAAG,CAAC,KAAK,CAAC,SAAS,EAAE,eAAe,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAA;IAClD,CAAC,CACF,CAAA;AACH,CAAC"}
@@ -0,0 +1,122 @@
1
+ /**
2
+ * `intutic compliance` — framework coverage, and the evidence archive an
3
+ * auditor receives.
4
+ *
5
+ * Subcommands:
6
+ * - `intutic compliance coverage <framework_id>` — how the latest compliance
7
+ * probe results cover one framework (EU AI Act, ISO/IEC 42001, NIST AI
8
+ * RMF, MITRE ATLAS), as a summary or as the report file.
9
+ * - `intutic compliance collect` — run a fresh evidence collection and seal
10
+ * it into an archive.
11
+ * - `intutic compliance download <run_id>` — a stored archive.
12
+ * - `intutic compliance verify <file>` — check an archive offline.
13
+ *
14
+ * Server side: services/control-plane/src/routes/compliance.ts —
15
+ * `GET .../frameworks/:frameworkId/coverage` (`?format=` json, markdown, csv
16
+ * or pdf; a live report is unsigned, the signed copies are the ones sealed in
17
+ * the evidence pack), `POST .../soc2-collect` and `GET .../soc2-export/:runId`
18
+ * (OWNER/ADMIN). The archive's rules are in
19
+ * services/control-plane/src/services/soc2EvidenceService.ts: a sha256
20
+ * manifest over canonical JSON, and an Ed25519 signature over
21
+ * `intutic-soc2-evidence-v1\n<archiveSha256>` only when the deployment has a
22
+ * signing key. `verify` applies the same rules with nothing but the file and
23
+ * the published key set, so its answer does not depend on trusting the server
24
+ * that produced the archive.
25
+ *
26
+ * @module
27
+ */
28
+ import { type ApiCommandOpts } from './apiCommand.js';
29
+ import { type SignatureState, type SigningJwks } from './integrity.js';
30
+ interface CoverageOpts extends ApiCommandOpts {
31
+ format?: string;
32
+ out?: string;
33
+ }
34
+ /** `intutic compliance coverage <framework_id>` */
35
+ export declare function runComplianceCoverage(frameworkId: string, opts: CoverageOpts): Promise<void>;
36
+ /** The domain the control plane signs evidence archives under (soc2EvidenceService.ts). */
37
+ export declare const EVIDENCE_SIGNING_DOMAIN = "intutic-soc2-evidence-v1";
38
+ interface DetachedSignature {
39
+ algorithm: string;
40
+ keyId: string;
41
+ preimageDomain: string;
42
+ value: string;
43
+ }
44
+ /** The parts of an evidence archive `verify` reads; everything else is covered by the archive hash. */
45
+ interface EvidenceArchive {
46
+ formatVersion?: number;
47
+ runId?: string;
48
+ collectedAt?: string;
49
+ categories?: Array<{
50
+ category: string;
51
+ }>;
52
+ frameworks?: Array<{
53
+ coverage: {
54
+ frameworkId: string;
55
+ };
56
+ csv?: string;
57
+ pdf?: string;
58
+ }>;
59
+ manifest?: {
60
+ algorithm?: string;
61
+ sections?: Record<string, string>;
62
+ archiveSha256?: string;
63
+ unsignedReason?: string;
64
+ };
65
+ signature?: DetachedSignature | null;
66
+ }
67
+ /**
68
+ * Object keys sorted recursively, arrays in order: the preimage the control
69
+ * plane hashes (`canonicalJson` in soc2EvidenceService.ts), because the
70
+ * archive round-trips through a JSONB column that does not keep key order.
71
+ */
72
+ export declare function canonicalJson(value: unknown): string;
73
+ export interface EvidenceVerification {
74
+ /** The recomputed whole-archive hash, and whether it equals `manifest.archiveSha256`. */
75
+ archiveSha256: string;
76
+ archiveHashMatches: boolean;
77
+ /** Manifest entries whose content does not hash to the recorded value, or that name nothing in the archive. */
78
+ sectionMismatches: string[];
79
+ sectionsChecked: number;
80
+ signature: SignatureState;
81
+ signingKeyId: string | null;
82
+ /** Why the archive is unsigned, as the archive states it (format 3 on). */
83
+ unsignedReason: string | null;
84
+ }
85
+ /**
86
+ * Check an evidence archive the way it was sealed: the manifest's hashes,
87
+ * then the signature over the archive hash against the published key its
88
+ * `keyId` names. A signature over a hash that does not match proves nothing
89
+ * about this file, so it is reported only once the hash matches.
90
+ */
91
+ export declare function verifyEvidenceArchive(archive: EvidenceArchive, jwks: SigningJwks | null): EvidenceVerification;
92
+ /**
93
+ * The exit code for a verification: 0 only for an archive whose hashes match
94
+ * and whose signature a published key accepts; 1 when the file or its
95
+ * signature contradicts the archive (it was changed); 2 when the hashes match
96
+ * but nothing establishes who produced it (unsigned, or the key could not be
97
+ * had), which a script must not read as a pass.
98
+ */
99
+ export declare function verificationExitCode(v: EvidenceVerification): 0 | 1 | 2;
100
+ /**
101
+ * `verify`'s exit status for an input it cannot read (a missing file, one
102
+ * that is not JSON, one that is not an archive or a JWKS): its own status, so
103
+ * a script never reads a wrong path as a changed archive (1) or an unsigned
104
+ * one (2).
105
+ */
106
+ export declare const UNREADABLE_INPUT_EXIT = 3;
107
+ /** `intutic compliance verify <file>` */
108
+ export declare function runComplianceVerify(file: string, opts: ApiCommandOpts & {
109
+ jwks?: string;
110
+ }): Promise<void>;
111
+ /** `intutic compliance collect` */
112
+ export declare function runComplianceCollect(opts: ApiCommandOpts & {
113
+ from?: string;
114
+ to?: string;
115
+ out?: string;
116
+ }): Promise<void>;
117
+ /** `intutic compliance download <run_id>` */
118
+ export declare function runComplianceDownload(runId: string, opts: ApiCommandOpts & {
119
+ out?: string;
120
+ }): Promise<void>;
121
+ export {};
122
+ //# sourceMappingURL=compliance.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"compliance.d.ts","sourceRoot":"","sources":["../../src/commands/compliance.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,278 @@
1
+ /**
2
+ * `intutic compliance` — framework coverage, and the evidence archive an
3
+ * auditor receives.
4
+ *
5
+ * Subcommands:
6
+ * - `intutic compliance coverage <framework_id>` — how the latest compliance
7
+ * probe results cover one framework (EU AI Act, ISO/IEC 42001, NIST AI
8
+ * RMF, MITRE ATLAS), as a summary or as the report file.
9
+ * - `intutic compliance collect` — run a fresh evidence collection and seal
10
+ * it into an archive.
11
+ * - `intutic compliance download <run_id>` — a stored archive.
12
+ * - `intutic compliance verify <file>` — check an archive offline.
13
+ *
14
+ * Server side: services/control-plane/src/routes/compliance.ts —
15
+ * `GET .../frameworks/:frameworkId/coverage` (`?format=` json, markdown, csv
16
+ * or pdf; a live report is unsigned, the signed copies are the ones sealed in
17
+ * the evidence pack), `POST .../soc2-collect` and `GET .../soc2-export/:runId`
18
+ * (OWNER/ADMIN). The archive's rules are in
19
+ * services/control-plane/src/services/soc2EvidenceService.ts: a sha256
20
+ * manifest over canonical JSON, and an Ed25519 signature over
21
+ * `intutic-soc2-evidence-v1\n<archiveSha256>` only when the deployment has a
22
+ * signing key. `verify` applies the same rules with nothing but the file and
23
+ * the published key set, so its answer does not depend on trusting the server
24
+ * that produced the archive.
25
+ *
26
+ * @module
27
+ */
28
+ import { createHash, createPublicKey, verify as nodeVerify } from 'node:crypto';
29
+ import { readFileSync, writeFileSync } from 'node:fs';
30
+ import pc from 'picocolors';
31
+ import { resolveControlPlaneUrl } from '../config/paths.js';
32
+ import { log } from '../lib/logger.js';
33
+ import { fail, runApiCommand, writeOutput } from './apiCommand.js';
34
+ import { fetchSigningKeys } from './integrity.js';
35
+ /** The CLI's format names, and the route's for each. */
36
+ const FORMATS = { json: 'json', md: 'markdown', csv: 'csv', pdf: 'pdf' };
37
+ /** `intutic compliance coverage <framework_id>` */
38
+ export async function runComplianceCoverage(frameworkId, opts) {
39
+ const format = opts.format ?? (opts.json ? 'json' : undefined);
40
+ const path = `/api/v1/compliance/frameworks/${encodeURIComponent(frameworkId)}/coverage`;
41
+ if (format === undefined) {
42
+ await runApiCommand(opts, `Failed to read ${frameworkId} coverage`, (client) => client.get(path), (c) => {
43
+ log.header(`Intutic — ${c.name} coverage`);
44
+ log.field('Controls', `${c.summary.controls} (${c.summary.mapped} mapped: ${c.summary.full} full, ${c.summary.partial} partial)`);
45
+ for (const [state, count] of Object.entries(c.summary.states))
46
+ log.field(state, String(count));
47
+ log.field('Mapping version', c.mappingVersion);
48
+ log.field('Generated', c.generatedAt);
49
+ log.dim(' The full report: --format md|csv|pdf --out <file>.');
50
+ });
51
+ return;
52
+ }
53
+ if (!Object.keys(FORMATS).includes(format))
54
+ fail(`--format must be one of ${Object.keys(FORMATS).join(', ')}, got "${format}"`);
55
+ // A PDF written to a terminal is noise and a lost report; a pipe or a file is fine.
56
+ if (format === 'pdf' && !opts.out && process.stdout.isTTY) {
57
+ fail('A PDF report needs --out <file>, or redirect the output to a file.');
58
+ }
59
+ await runApiCommand(
60
+ // The output is the report itself; `--json` only chose the format.
61
+ { dev: opts.dev }, `Failed to export ${frameworkId} coverage`, (client) => client.getFile(`${path}?format=${FORMATS[format]}`), (bytes) => writeOutput(bytes, opts.out, `${frameworkId} coverage (${format})`));
62
+ }
63
+ // ─── Evidence archive ────────────────────────────────────────────────
64
+ /** The domain the control plane signs evidence archives under (soc2EvidenceService.ts). */
65
+ export const EVIDENCE_SIGNING_DOMAIN = 'intutic-soc2-evidence-v1';
66
+ /**
67
+ * Object keys sorted recursively, arrays in order: the preimage the control
68
+ * plane hashes (`canonicalJson` in soc2EvidenceService.ts), because the
69
+ * archive round-trips through a JSONB column that does not keep key order.
70
+ */
71
+ export function canonicalJson(value) {
72
+ if (value === null || typeof value !== 'object')
73
+ return JSON.stringify(value);
74
+ if (Array.isArray(value))
75
+ return `[${value.map(canonicalJson).join(',')}]`;
76
+ const entries = Object.entries(value)
77
+ .filter(([, v]) => v !== undefined)
78
+ .sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0));
79
+ return `{${entries.map(([k, v]) => `${JSON.stringify(k)}:${canonicalJson(v)}`).join(',')}}`;
80
+ }
81
+ const sha256Hex = (data) => createHash('sha256').update(data).digest('hex');
82
+ /** What one manifest entry hashes, or null when the archive has nothing under that name. */
83
+ function sectionContent(archive, name) {
84
+ const category = archive.categories?.find((c) => c.category === name);
85
+ if (category)
86
+ return canonicalJson(category);
87
+ const m = /^framework:([^:]+)(?::(csv|pdf))?$/.exec(name);
88
+ const framework = m ? archive.frameworks?.find((f) => f.coverage?.frameworkId === m[1]) : undefined;
89
+ if (!m || !framework)
90
+ return null;
91
+ // The CSV as its UTF-8 bytes and the PDF decoded, so a file saved out of
92
+ // the archive checks with `sha256sum` alone.
93
+ if (m[2] === 'csv')
94
+ return framework.csv ?? null;
95
+ if (m[2] === 'pdf')
96
+ return framework.pdf === undefined ? null : Buffer.from(framework.pdf, 'base64');
97
+ return canonicalJson(framework);
98
+ }
99
+ /**
100
+ * Check an evidence archive the way it was sealed: the manifest's hashes,
101
+ * then the signature over the archive hash against the published key its
102
+ * `keyId` names. A signature over a hash that does not match proves nothing
103
+ * about this file, so it is reported only once the hash matches.
104
+ */
105
+ export function verifyEvidenceArchive(archive, jwks) {
106
+ const rest = { ...archive };
107
+ delete rest['manifest'];
108
+ delete rest['signature'];
109
+ const archiveSha256 = sha256Hex(canonicalJson(rest));
110
+ const manifest = archive.manifest ?? {};
111
+ const archiveHashMatches = archiveSha256 === manifest.archiveSha256;
112
+ const sections = Object.entries(manifest.sections ?? {});
113
+ const sectionMismatches = sections
114
+ .filter(([name, recorded]) => {
115
+ const content = sectionContent(archive, name);
116
+ return content === null || sha256Hex(content) !== recorded;
117
+ })
118
+ .map(([name]) => name);
119
+ const sig = archive.signature ?? null;
120
+ return {
121
+ archiveSha256,
122
+ archiveHashMatches,
123
+ sectionMismatches,
124
+ sectionsChecked: sections.length,
125
+ signature: sig ? signatureState(sig, manifest.archiveSha256 ?? '', jwks) : 'unsigned',
126
+ signingKeyId: sig?.keyId ?? null,
127
+ unsignedReason: sig ? null : (manifest.unsignedReason ?? null),
128
+ };
129
+ }
130
+ function signatureState(sig, archiveSha256, jwks) {
131
+ // A signature made under another domain (a human-oversight or SLA export)
132
+ // or algorithm is not this archive's signature, whatever its bytes.
133
+ if (sig.preimageDomain !== EVIDENCE_SIGNING_DOMAIN || sig.algorithm !== 'Ed25519')
134
+ return 'invalid';
135
+ if (!jwks)
136
+ return 'keys_unavailable';
137
+ const jwk = jwks.keys.find((k) => k['kid'] === sig.keyId);
138
+ if (!jwk)
139
+ return 'unverifiable';
140
+ try {
141
+ const ok = nodeVerify(null, Buffer.from(`${EVIDENCE_SIGNING_DOMAIN}\n${archiveSha256}`, 'utf8'), createPublicKey({ key: jwk, format: 'jwk' }), Buffer.from(sig.value, 'base64'));
142
+ return ok ? 'valid' : 'invalid';
143
+ }
144
+ catch {
145
+ // A published key this build cannot load is one we do not effectively hold.
146
+ return 'unverifiable';
147
+ }
148
+ }
149
+ /**
150
+ * The exit code for a verification: 0 only for an archive whose hashes match
151
+ * and whose signature a published key accepts; 1 when the file or its
152
+ * signature contradicts the archive (it was changed); 2 when the hashes match
153
+ * but nothing establishes who produced it (unsigned, or the key could not be
154
+ * had), which a script must not read as a pass.
155
+ */
156
+ export function verificationExitCode(v) {
157
+ if (!v.archiveHashMatches || v.sectionMismatches.length > 0 || v.signature === 'invalid')
158
+ return 1;
159
+ return v.signature === 'valid' ? 0 : 2;
160
+ }
161
+ const SIGNATURE_TEXT = {
162
+ valid: 'valid: the published key accepts the signature over this archive hash',
163
+ invalid: 'INVALID: the signature does not match this archive',
164
+ unsigned: 'unsigned: the deployment had no signing key when this archive was collected',
165
+ unverifiable: 'not verified: no published key has this key id',
166
+ keys_unavailable: 'not verified: the published keys could not be fetched; pass --jwks <file>',
167
+ };
168
+ /**
169
+ * `verify`'s exit status for an input it cannot read (a missing file, one
170
+ * that is not JSON, one that is not an archive or a JWKS): its own status, so
171
+ * a script never reads a wrong path as a changed archive (1) or an unsigned
172
+ * one (2).
173
+ */
174
+ export const UNREADABLE_INPUT_EXIT = 3;
175
+ function unreadable(message) {
176
+ log.error(message);
177
+ process.exit(UNREADABLE_INPUT_EXIT);
178
+ }
179
+ function readInput(path) {
180
+ let text;
181
+ try {
182
+ text = readFileSync(path, 'utf8');
183
+ }
184
+ catch (err) {
185
+ unreadable(`Cannot read ${path}: ${err instanceof Error ? err.message : String(err)}`);
186
+ }
187
+ try {
188
+ return JSON.parse(text);
189
+ }
190
+ catch (err) {
191
+ unreadable(`${path} is not valid JSON: ${err instanceof Error ? err.message : String(err)}`);
192
+ }
193
+ }
194
+ /** The published key set, from a file when given, else from the control plane's unauthenticated endpoint. */
195
+ function loadJwks(path) {
196
+ const parsed = readInput(path);
197
+ if (!Array.isArray(parsed?.keys))
198
+ unreadable(`${path} is not a JWKS: it has no "keys" array`);
199
+ return parsed;
200
+ }
201
+ /** `intutic compliance verify <file>` */
202
+ export async function runComplianceVerify(file, opts) {
203
+ const archive = readInput(file);
204
+ if (!archive?.manifest?.archiveSha256)
205
+ unreadable(`${file} is not an evidence archive: it has no manifest.archiveSha256`);
206
+ const jwks = opts.jwks
207
+ ? loadJwks(opts.jwks)
208
+ : archive.signature
209
+ ? await fetchSigningKeys(resolveControlPlaneUrl(opts.dev))
210
+ : null;
211
+ const v = verifyEvidenceArchive(archive, jwks);
212
+ const code = verificationExitCode(v);
213
+ if (opts.json) {
214
+ console.log(JSON.stringify({ file, ...v, verified: code === 0 }, null, 2));
215
+ }
216
+ else {
217
+ log.header('Intutic — Evidence Archive');
218
+ if (archive.runId)
219
+ log.field('Run', archive.runId);
220
+ if (archive.collectedAt)
221
+ log.field('Collected', archive.collectedAt);
222
+ log.field('Archive hash', v.archiveHashMatches ? pc.green(`matches (${v.archiveSha256})`) : pc.red(`DOES NOT MATCH the manifest (recomputed ${v.archiveSha256})`));
223
+ log.field('Sections', v.sectionMismatches.length === 0
224
+ ? pc.green(`${v.sectionsChecked} of ${v.sectionsChecked} match`)
225
+ : pc.red(`${v.sectionMismatches.length} of ${v.sectionsChecked} DO NOT MATCH: ${v.sectionMismatches.join(', ')}`));
226
+ const text = SIGNATURE_TEXT[v.signature];
227
+ log.field('Signature', v.signature === 'valid' ? pc.green(text) : v.signature === 'invalid' ? pc.red(text) : pc.yellow(text));
228
+ if (v.signingKeyId)
229
+ log.field('Key id', v.signingKeyId);
230
+ if (v.unsignedReason)
231
+ log.dim(` ${v.unsignedReason}`);
232
+ if (code === 2)
233
+ log.warn('Not verified: the hashes match, but nothing shows who produced this archive.');
234
+ }
235
+ if (code !== 0)
236
+ process.exit(code);
237
+ }
238
+ /** `intutic compliance collect` */
239
+ export async function runComplianceCollect(opts) {
240
+ for (const [flag, value] of [['--from', opts.from], ['--to', opts.to]]) {
241
+ if (value !== undefined && Number.isNaN(Date.parse(value)))
242
+ fail(`${flag} must be an ISO 8601 date or datetime, got "${value}"`);
243
+ }
244
+ await runApiCommand(
245
+ // Not `opts`: with --json the response is printed, and --out still writes the archive.
246
+ { dev: opts.dev }, 'Failed to collect evidence', (client) => client.post('/api/v1/compliance/soc2-collect', { periodStart: opts.from, periodEnd: opts.to }), (res) => {
247
+ if (opts.out)
248
+ writeFileSync(opts.out, JSON.stringify(res.archive, null, 2));
249
+ if (opts.json) {
250
+ console.log(JSON.stringify(res, null, 2));
251
+ return;
252
+ }
253
+ log.header('Intutic — Evidence Collected');
254
+ log.field('Run', res.runId);
255
+ log.field('Period', `${res.archive.periodStart} to ${res.archive.periodEnd}`);
256
+ log.field('Overall score', res.archive.overallScore === null ? '— (no scored category)' : String(res.archive.overallScore));
257
+ log.field('Signed', res.signed ? pc.green(`yes (key ${res.signingKeyId})`) : pc.yellow('no'));
258
+ if (!res.signed && res.unsignedReason)
259
+ log.dim(` ${res.unsignedReason}`);
260
+ if (res.artifactUrl)
261
+ log.field('Stored at', res.artifactUrl);
262
+ if (opts.out)
263
+ log.success(`Wrote the archive to ${opts.out}. Check it: intutic compliance verify ${opts.out}`);
264
+ else
265
+ log.dim(` Download it: intutic compliance download ${res.runId} --out <file>`);
266
+ });
267
+ }
268
+ /** `intutic compliance download <run_id>` */
269
+ export async function runComplianceDownload(runId, opts) {
270
+ await runApiCommand(
271
+ // The output is the archive itself.
272
+ { dev: opts.dev }, `Failed to download evidence run ${runId}`, (client) => client.getFile(`/api/v1/compliance/soc2-export/${encodeURIComponent(runId)}`), (bytes) => {
273
+ writeOutput(bytes, opts.out, `evidence run ${runId}`);
274
+ if (opts.out)
275
+ log.dim(` Check it: intutic compliance verify ${opts.out}`);
276
+ });
277
+ }
278
+ //# sourceMappingURL=compliance.js.map