failproofai 1.0.1 → 1.0.2-beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (390) hide show
  1. package/.next/standalone/.next/BUILD_ID +1 -1
  2. package/.next/standalone/.next/build-manifest.json +3 -3
  3. package/.next/standalone/.next/prerender-manifest.json +6 -6
  4. package/.next/standalone/.next/required-server-files.json +1 -1
  5. package/.next/standalone/.next/server/app/_global-error/page/server-reference-manifest.json +1 -1
  6. package/.next/standalone/.next/server/app/_global-error/page.js +2 -2
  7. package/.next/standalone/.next/server/app/_global-error/page.js.nft.json +1 -1
  8. package/.next/standalone/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
  9. package/.next/standalone/.next/server/app/_global-error.html +1 -1
  10. package/.next/standalone/.next/server/app/_global-error.rsc +7 -7
  11. package/.next/standalone/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +6 -6
  12. package/.next/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +7 -7
  13. package/.next/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  14. package/.next/standalone/.next/server/app/_not-found/page/server-reference-manifest.json +1 -1
  15. package/.next/standalone/.next/server/app/_not-found/page.js +2 -2
  16. package/.next/standalone/.next/server/app/_not-found/page.js.nft.json +1 -1
  17. package/.next/standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  18. package/.next/standalone/.next/server/app/_not-found.html +1 -1
  19. package/.next/standalone/.next/server/app/_not-found.rsc +15 -15
  20. package/.next/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +15 -15
  21. package/.next/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +14 -14
  22. package/.next/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
  23. package/.next/standalone/.next/server/app/api/audit/invite/route.js +3 -2
  24. package/.next/standalone/.next/server/app/api/audit/invite/route.js.nft.json +1 -1
  25. package/.next/standalone/.next/server/app/api/audit/run/route.js +5 -4
  26. package/.next/standalone/.next/server/app/api/audit/run/route.js.nft.json +1 -1
  27. package/.next/standalone/.next/server/app/api/audit/status/route.js.nft.json +1 -1
  28. package/.next/standalone/.next/server/app/api/auth/login-request/route.js +2 -2
  29. package/.next/standalone/.next/server/app/api/auth/login-request/route.js.nft.json +1 -1
  30. package/.next/standalone/.next/server/app/api/auth/login-verify/route.js +4 -3
  31. package/.next/standalone/.next/server/app/api/auth/login-verify/route.js.nft.json +1 -1
  32. package/.next/standalone/.next/server/app/api/auth/logout/route.js +4 -4
  33. package/.next/standalone/.next/server/app/api/auth/logout/route.js.nft.json +1 -1
  34. package/.next/standalone/.next/server/app/api/auth/status/route.js +4 -4
  35. package/.next/standalone/.next/server/app/api/auth/status/route.js.nft.json +1 -1
  36. package/.next/standalone/.next/server/app/api/download/[project]/[session]/route.js.nft.json +1 -1
  37. package/.next/standalone/.next/server/app/audit/page/server-reference-manifest.json +2 -2
  38. package/.next/standalone/.next/server/app/audit/page.js +2 -2
  39. package/.next/standalone/.next/server/app/audit/page.js.nft.json +1 -1
  40. package/.next/standalone/.next/server/app/audit/page_client-reference-manifest.js +1 -1
  41. package/.next/standalone/.next/server/app/index.html +1 -1
  42. package/.next/standalone/.next/server/app/index.rsc +15 -15
  43. package/.next/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +14 -14
  44. package/.next/standalone/.next/server/app/index.segments/_full.segment.rsc +15 -15
  45. package/.next/standalone/.next/server/app/index.segments/_tree.segment.rsc +2 -2
  46. package/.next/standalone/.next/server/app/page/server-reference-manifest.json +1 -1
  47. package/.next/standalone/.next/server/app/page.js +2 -2
  48. package/.next/standalone/.next/server/app/page.js.nft.json +1 -1
  49. package/.next/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
  50. package/.next/standalone/.next/server/app/policies/page/server-reference-manifest.json +66 -22
  51. package/.next/standalone/.next/server/app/policies/page.js +5 -4
  52. package/.next/standalone/.next/server/app/policies/page.js.nft.json +1 -1
  53. package/.next/standalone/.next/server/app/policies/page_client-reference-manifest.js +1 -1
  54. package/.next/standalone/.next/server/app/project/[name]/page/server-reference-manifest.json +1 -1
  55. package/.next/standalone/.next/server/app/project/[name]/page.js +2 -2
  56. package/.next/standalone/.next/server/app/project/[name]/page.js.nft.json +1 -1
  57. package/.next/standalone/.next/server/app/project/[name]/page_client-reference-manifest.js +1 -1
  58. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page/react-loadable-manifest.json +2 -2
  59. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page/server-reference-manifest.json +2 -2
  60. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page.js +2 -2
  61. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page.js.nft.json +1 -1
  62. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page_client-reference-manifest.js +1 -1
  63. package/.next/standalone/.next/server/app/projects/page/server-reference-manifest.json +1 -1
  64. package/.next/standalone/.next/server/app/projects/page.js +2 -2
  65. package/.next/standalone/.next/server/app/projects/page.js.nft.json +1 -1
  66. package/.next/standalone/.next/server/app/projects/page_client-reference-manifest.js +1 -1
  67. package/.next/standalone/.next/server/app/settings/page/server-reference-manifest.json +4 -4
  68. package/.next/standalone/.next/server/app/settings/page.js +1 -1
  69. package/.next/standalone/.next/server/app/settings/page.js.nft.json +1 -1
  70. package/.next/standalone/.next/server/app/settings/page_client-reference-manifest.js +1 -1
  71. package/.next/standalone/.next/server/chunks/{[externals]__1qzqcio._.js → [externals]__0e32-7c._.js} +1 -1
  72. package/.next/standalone/.next/server/chunks/[externals]__1s462yl._.js +3 -0
  73. package/.next/standalone/.next/server/chunks/[root-of-the-server]__01eqny5._.js +3 -0
  74. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0_0xu5z._.js +1 -1
  75. package/.next/standalone/.next/server/chunks/{[root-of-the-server]__1p8poxd._.js → [root-of-the-server]__0igxt32._.js} +2 -2
  76. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0neqlu-._.js +3 -0
  77. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0o07qi9._.js +3 -0
  78. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0t9oqnn._.js +3 -0
  79. package/.next/standalone/.next/server/chunks/_0bs-v8p._.js +3 -0
  80. package/.next/standalone/.next/server/chunks/_0tovk6q._.js +3 -0
  81. package/.next/standalone/.next/server/chunks/_0trp3yc._.js +3 -0
  82. package/.next/standalone/.next/server/chunks/_185gyk9._.js +3 -0
  83. package/.next/standalone/.next/server/chunks/_1ek68ln._.js +5 -6
  84. package/.next/standalone/.next/server/chunks/lib_telemetry-id_ts_1k8gj9o._.js +3 -0
  85. package/.next/standalone/.next/server/chunks/{node_modules_posthog-node_dist_entrypoints_index_node_mjs_09d5c79._.js → node_modules_posthog-node_dist_entrypoints_index_node_mjs_04op_ou._.js} +1 -1
  86. package/.next/standalone/.next/server/chunks/{node_modules_posthog-node_dist_entrypoints_index_node_mjs_1d2g73o._.js → node_modules_posthog-node_dist_entrypoints_index_node_mjs_0dr20uz._.js} +1 -1
  87. package/.next/standalone/.next/server/chunks/package_json_[json]_cjs_1nxcc4v._.js +1 -1
  88. package/.next/standalone/.next/server/chunks/src_hooks_0xbw3bu._.js +12 -0
  89. package/.next/standalone/.next/server/chunks/src_hooks_fp-home_ts_09kv0bn._.js +1 -1
  90. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__013jr2b._.js +4 -0
  91. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__01wy8d-._.js +4 -0
  92. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__02npjtd._.js +4 -0
  93. package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__0spkm68._.js → [root-of-the-server]__07gm3zl._.js} +21 -20
  94. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0da85px._.js +4 -0
  95. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0ftmoxc._.js +4 -0
  96. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0l4748x._.js +3 -0
  97. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0oa1lav._.js +1 -1
  98. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0p-5p8u._.js +4 -0
  99. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0s740oi._.js +6 -0
  100. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__192nhvh._.js +5 -0
  101. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1p2otjt._.js +4 -0
  102. package/.next/standalone/.next/server/chunks/ssr/_08x1r5t._.js +1 -1
  103. package/.next/standalone/.next/server/chunks/ssr/_0bwu160._.js +23 -0
  104. package/.next/standalone/.next/server/chunks/ssr/{_0u5zauv._.js → _0ns6k3a._.js} +1 -1
  105. package/.next/standalone/.next/server/chunks/ssr/_1u8-lu2._.js +1 -1
  106. package/.next/standalone/.next/server/chunks/ssr/_1zopuov._.js +1 -1
  107. package/.next/standalone/.next/server/chunks/ssr/app_audit__components_audit-dashboard_tsx_0p9ud47._.js +13 -13
  108. package/.next/standalone/.next/server/chunks/ssr/app_global-error_tsx_1kp6l3x._.js +1 -1
  109. package/.next/standalone/.next/server/chunks/ssr/app_policies_hooks-client_tsx_19dqvpc._.js +2 -2
  110. package/.next/standalone/.next/server/chunks/ssr/app_settings_settings-client_tsx_20lq-mq._.js +1 -1
  111. package/.next/standalone/.next/server/chunks/ssr/src_hooks_08o7bqo._.js +10 -0
  112. package/.next/standalone/.next/server/chunks/ssr/src_hooks_builtin-policies_ts_09j2ndl._.js +1 -1
  113. package/.next/standalone/.next/server/chunks/ssr/src_hooks_fp-home_ts_0je3xkv._.js +1 -1
  114. package/.next/standalone/.next/server/middleware-build-manifest.js +3 -3
  115. package/.next/standalone/.next/server/middleware.js +1 -1
  116. package/.next/standalone/.next/server/pages/404.html +1 -1
  117. package/.next/standalone/.next/server/pages/500.html +1 -1
  118. package/.next/standalone/.next/server/server-reference-manifest.js +1 -1
  119. package/.next/standalone/.next/server/server-reference-manifest.json +71 -27
  120. package/.next/standalone/.next/static/chunks/{3c808qrfcq0im.js → 01u1cf8js2yd6.js} +1 -1
  121. package/.next/standalone/.next/static/chunks/{0k5050ezjvv49.js → 0izgu7nqh2zp-.js} +1 -1
  122. package/.next/standalone/.next/static/chunks/0u4-org_e7zde.js +1 -0
  123. package/.next/standalone/.next/static/chunks/1mfzsja6ftim5.css +2 -0
  124. package/.next/standalone/.next/static/chunks/{2zpp1we2jv6tb.js → 1tynnrrh711lr.js} +1 -1
  125. package/.next/standalone/.next/static/chunks/{1ide1wl2f7zi1.js → 22rqvlr7p_1mv.js} +13 -13
  126. package/.next/standalone/.next/static/chunks/{31s1smp-g7gks.js → 2whxqyskpirbk.js} +1 -1
  127. package/.next/standalone/.next/static/chunks/31mwk8k0q3_3g.js +6 -0
  128. package/.next/standalone/.next/static/chunks/3ms6gxfm82t6s.js +1 -0
  129. package/.next/standalone/.next/static/chunks/{37fcpc36x9gpe.js → 3tbbs_d8xrvyp.js} +1 -1
  130. package/.next/standalone/.next/static/chunks/43nlc236oo898.js +1 -0
  131. package/.next/standalone/SECURITY.md +5 -4
  132. package/.next/standalone/app/actions/get-hooks-config.ts +108 -17
  133. package/.next/standalone/app/actions/pack-actions.ts +163 -0
  134. package/.next/standalone/app/audit/_components/run-progress.tsx +1 -1
  135. package/.next/standalone/app/audit/_components/share-templates.ts +13 -13
  136. package/.next/standalone/app/policies/hooks-client.tsx +320 -15
  137. package/.next/standalone/components/navbar.tsx +2 -28
  138. package/.next/standalone/components/reach-developers.tsx +8 -1
  139. package/.next/standalone/docs-old/agenteye/python-sdk-skill.mdx +1 -1
  140. package/.next/standalone/docs-old/agenteye/python-sdk.mdx +59 -35
  141. package/.next/standalone/docs-old/i18n/README.ar.md +1 -0
  142. package/.next/standalone/docs-old/i18n/README.de.md +1 -0
  143. package/.next/standalone/docs-old/i18n/README.es.md +1 -0
  144. package/.next/standalone/docs-old/i18n/README.fr.md +1 -0
  145. package/.next/standalone/docs-old/i18n/README.he.md +1 -0
  146. package/.next/standalone/docs-old/i18n/README.hi.md +1 -0
  147. package/.next/standalone/docs-old/i18n/README.it.md +1 -0
  148. package/.next/standalone/docs-old/i18n/README.ja.md +1 -0
  149. package/.next/standalone/docs-old/i18n/README.ko.md +1 -0
  150. package/.next/standalone/docs-old/i18n/README.pt-br.md +1 -0
  151. package/.next/standalone/docs-old/i18n/README.ru.md +1 -0
  152. package/.next/standalone/docs-old/i18n/README.tr.md +1 -0
  153. package/.next/standalone/docs-old/i18n/README.vi.md +1 -0
  154. package/.next/standalone/docs-old/i18n/README.zh.md +1 -0
  155. package/.next/standalone/fp-cloud-cli/CHANGELOG.md +431 -0
  156. package/.next/standalone/fp-cloud-cli/LICENSE +42 -0
  157. package/.next/standalone/fp-cloud-cli/MANIFEST.in +10 -0
  158. package/.next/standalone/fp-cloud-cli/README.md +259 -0
  159. package/.next/standalone/fp-cloud-cli/fp_cli/__init__.py +10 -0
  160. package/.next/standalone/fp-cloud-cli/fp_cli/__main__.py +4 -0
  161. package/.next/standalone/fp-cloud-cli/fp_cli/_click_compat.py +64 -0
  162. package/.next/standalone/fp-cloud-cli/fp_cli/_context.py +332 -0
  163. package/.next/standalone/fp-cloud-cli/fp_cli/_version.py +1 -0
  164. package/.next/standalone/fp-cloud-cli/fp_cli/analytics.py +432 -0
  165. package/.next/standalone/fp-cloud-cli/fp_cli/analytics_config.py +77 -0
  166. package/.next/standalone/fp-cloud-cli/fp_cli/analytics_registry.py +83 -0
  167. package/.next/standalone/fp-cloud-cli/fp_cli/app.py +492 -0
  168. package/.next/standalone/fp-cloud-cli/fp_cli/auth.py +160 -0
  169. package/.next/standalone/fp-cloud-cli/fp_cli/client.py +1694 -0
  170. package/.next/standalone/fp-cloud-cli/fp_cli/commands/__init__.py +0 -0
  171. package/.next/standalone/fp-cloud-cli/fp_cli/commands/_write.py +214 -0
  172. package/.next/standalone/fp-cloud-cli/fp_cli/commands/agent_cmds.py +407 -0
  173. package/.next/standalone/fp-cloud-cli/fp_cli/commands/alerts_cmds.py +445 -0
  174. package/.next/standalone/fp-cloud-cli/fp_cli/commands/audits_cmds.py +1054 -0
  175. package/.next/standalone/fp-cloud-cli/fp_cli/commands/auth_cmds.py +512 -0
  176. package/.next/standalone/fp-cloud-cli/fp_cli/commands/errors_cmds.py +190 -0
  177. package/.next/standalone/fp-cloud-cli/fp_cli/commands/evals_cmds.py +161 -0
  178. package/.next/standalone/fp-cloud-cli/fp_cli/commands/events_cmds.py +159 -0
  179. package/.next/standalone/fp-cloud-cli/fp_cli/commands/fleet_cmds.py +416 -0
  180. package/.next/standalone/fp-cloud-cli/fp_cli/commands/guardrails_cmds.py +148 -0
  181. package/.next/standalone/fp-cloud-cli/fp_cli/commands/incidents_cmds.py +472 -0
  182. package/.next/standalone/fp-cloud-cli/fp_cli/commands/keys_cmds.py +407 -0
  183. package/.next/standalone/fp-cloud-cli/fp_cli/commands/list_cmds.py +63 -0
  184. package/.next/standalone/fp-cloud-cli/fp_cli/commands/orgs_cmds.py +319 -0
  185. package/.next/standalone/fp-cloud-cli/fp_cli/commands/policies_cmds.py +499 -0
  186. package/.next/standalone/fp-cloud-cli/fp_cli/commands/queries_cmds.py +378 -0
  187. package/.next/standalone/fp-cloud-cli/fp_cli/commands/sessions_cmds.py +151 -0
  188. package/.next/standalone/fp-cloud-cli/fp_cli/commands/settings_cmds.py +150 -0
  189. package/.next/standalone/fp-cloud-cli/fp_cli/commands/usage_cmds.py +35 -0
  190. package/.next/standalone/fp-cloud-cli/fp_cli/commands/users_cmds.py +404 -0
  191. package/.next/standalone/fp-cloud-cli/fp_cli/config.py +330 -0
  192. package/.next/standalone/fp-cloud-cli/fp_cli/dates.py +78 -0
  193. package/.next/standalone/fp-cloud-cli/fp_cli/enforcement.py +345 -0
  194. package/.next/standalone/fp-cloud-cli/fp_cli/errors.py +98 -0
  195. package/.next/standalone/fp-cloud-cli/fp_cli/models.py +891 -0
  196. package/.next/standalone/fp-cloud-cli/fp_cli/orgs.py +30 -0
  197. package/.next/standalone/fp-cloud-cli/fp_cli/output.py +6593 -0
  198. package/.next/standalone/fp-cloud-cli/fp_cli/permissions.py +208 -0
  199. package/.next/standalone/fp-cloud-cli/fp_cli/policy_check.py +290 -0
  200. package/.next/standalone/fp-cloud-cli/fp_cli/py.typed +0 -0
  201. package/.next/standalone/fp-cloud-cli/fp_cli/select.py +322 -0
  202. package/.next/standalone/fp-cloud-cli/fp_cli/theme.py +53 -0
  203. package/.next/standalone/fp-cloud-cli/pyproject.toml +106 -0
  204. package/.next/standalone/fp-cloud-cli/skill/SKILL.md +302 -0
  205. package/.next/standalone/fp-cloud-cli/skill/agents/openai.yaml +8 -0
  206. package/.next/standalone/fp-cloud-cli/skill/references/commands.md +254 -0
  207. package/.next/standalone/fp-cloud-cli/tests/__init__.py +0 -0
  208. package/.next/standalone/fp-cloud-cli/tests/conftest.py +92 -0
  209. package/.next/standalone/fp-cloud-cli/tests/test_alerting.py +644 -0
  210. package/.next/standalone/fp-cloud-cli/tests/test_analytics.py +385 -0
  211. package/.next/standalone/fp-cloud-cli/tests/test_audits.py +950 -0
  212. package/.next/standalone/fp-cloud-cli/tests/test_auth.py +98 -0
  213. package/.next/standalone/fp-cloud-cli/tests/test_auth_mode.py +272 -0
  214. package/.next/standalone/fp-cloud-cli/tests/test_click_compat.py +118 -0
  215. package/.next/standalone/fp-cloud-cli/tests/test_client.py +299 -0
  216. package/.next/standalone/fp-cloud-cli/tests/test_commands.py +814 -0
  217. package/.next/standalone/fp-cloud-cli/tests/test_config.py +99 -0
  218. package/.next/standalone/fp-cloud-cli/tests/test_dashboards_agent.py +340 -0
  219. package/.next/standalone/fp-cloud-cli/tests/test_dates.py +76 -0
  220. package/.next/standalone/fp-cloud-cli/tests/test_enforcement_logic.py +481 -0
  221. package/.next/standalone/fp-cloud-cli/tests/test_facets.py +170 -0
  222. package/.next/standalone/fp-cloud-cli/tests/test_failproofai_home.py +722 -0
  223. package/.next/standalone/fp-cloud-cli/tests/test_fp_home_contract.py +198 -0
  224. package/.next/standalone/fp-cloud-cli/tests/test_hardening.py +186 -0
  225. package/.next/standalone/fp-cloud-cli/tests/test_help_table_coverage.py +127 -0
  226. package/.next/standalone/fp-cloud-cli/tests/test_keys_queries.py +496 -0
  227. package/.next/standalone/fp-cloud-cli/tests/test_list.py +59 -0
  228. package/.next/standalone/fp-cloud-cli/tests/test_multivalue.py +183 -0
  229. package/.next/standalone/fp-cloud-cli/tests/test_no_customer_identifiers.py +168 -0
  230. package/.next/standalone/fp-cloud-cli/tests/test_operator.py +492 -0
  231. package/.next/standalone/fp-cloud-cli/tests/test_orgs.py +692 -0
  232. package/.next/standalone/fp-cloud-cli/tests/test_output.py +1732 -0
  233. package/.next/standalone/fp-cloud-cli/tests/test_policy_check.py +191 -0
  234. package/.next/standalone/fp-cloud-cli/tests/test_readme_matches_reality.py +125 -0
  235. package/.next/standalone/fp-cloud-cli/tests/test_review_fixes.py +161 -0
  236. package/.next/standalone/fp-cloud-cli/tests/test_telemetry_completeness.py +90 -0
  237. package/.next/standalone/fp-cloud-cli/tests/test_usage.py +115 -0
  238. package/.next/standalone/fp-cloud-cli/tests/test_v1_origin_diagnostic.py +70 -0
  239. package/.next/standalone/fp-cloud-cli/tests/test_v1_routing.py +214 -0
  240. package/.next/standalone/fp-cloud-cli/tests/test_whoami.py +104 -0
  241. package/.next/standalone/fp-cloud-cli/uv.lock +552 -0
  242. package/.next/standalone/package.json +27 -21
  243. package/.next/standalone/sdk/python/CHANGELOG.md +70 -0
  244. package/.next/standalone/sdk/python/LICENSE +42 -0
  245. package/.next/standalone/sdk/python/MANIFEST.in +21 -0
  246. package/.next/standalone/sdk/python/README.md +466 -0
  247. package/.next/standalone/sdk/python/docs/README.md +249 -0
  248. package/.next/standalone/sdk/python/docs/_shared/README.md +43 -0
  249. package/.next/standalone/sdk/python/docs/_shared/__init__.py +195 -0
  250. package/.next/standalone/sdk/python/docs/crewai/README.md +328 -0
  251. package/.next/standalone/sdk/python/docs/crewai/examples/quickstart.py +61 -0
  252. package/.next/standalone/sdk/python/docs/crewai/examples/research_crew.py +92 -0
  253. package/.next/standalone/sdk/python/docs/langgraph/README.md +481 -0
  254. package/.next/standalone/sdk/python/docs/langgraph/examples/quickstart.py +51 -0
  255. package/.next/standalone/sdk/python/docs/langgraph/examples/research_agent.py +86 -0
  256. package/.next/standalone/sdk/python/docs/langgraph/examples/supervisor_handoff.py +83 -0
  257. package/.next/standalone/sdk/python/docs/llama_index/README.md +332 -0
  258. package/.next/standalone/sdk/python/docs/llama_index/examples/quickstart.py +62 -0
  259. package/.next/standalone/sdk/python/docs/llama_index/examples/research_agent.py +93 -0
  260. package/.next/standalone/sdk/python/docs/manual/README.md +491 -0
  261. package/.next/standalone/sdk/python/docs/manual/examples/quickstart.py +57 -0
  262. package/.next/standalone/sdk/python/docs/manual/examples/research_agent.py +133 -0
  263. package/.next/standalone/sdk/python/docs/pydantic_ai/README.md +323 -0
  264. package/.next/standalone/sdk/python/docs/pydantic_ai/examples/quickstart.py +48 -0
  265. package/.next/standalone/sdk/python/docs/pydantic_ai/examples/research_agent.py +94 -0
  266. package/.next/standalone/sdk/python/failproofai_sdk/__init__.py +131 -0
  267. package/.next/standalone/sdk/python/failproofai_sdk/_context.py +218 -0
  268. package/.next/standalone/sdk/python/failproofai_sdk/_environment.py +78 -0
  269. package/.next/standalone/sdk/python/failproofai_sdk/_events.py +752 -0
  270. package/.next/standalone/sdk/python/failproofai_sdk/_resolver.py +118 -0
  271. package/.next/standalone/sdk/python/failproofai_sdk/_runtime.py +21 -0
  272. package/.next/standalone/sdk/python/failproofai_sdk/_schema.py +317 -0
  273. package/.next/standalone/sdk/python/failproofai_sdk/_scopes.py +422 -0
  274. package/.next/standalone/sdk/python/failproofai_sdk/_version.py +1 -0
  275. package/.next/standalone/sdk/python/failproofai_sdk/_writer.py +750 -0
  276. package/.next/standalone/sdk/python/failproofai_sdk/integrations/__init__.py +274 -0
  277. package/.next/standalone/sdk/python/failproofai_sdk/integrations/_compat.py +247 -0
  278. package/.next/standalone/sdk/python/failproofai_sdk/integrations/_core.py +1076 -0
  279. package/.next/standalone/sdk/python/failproofai_sdk/integrations/crewai.py +1669 -0
  280. package/.next/standalone/sdk/python/failproofai_sdk/integrations/langchain.py +2151 -0
  281. package/.next/standalone/sdk/python/failproofai_sdk/integrations/llama_index.py +1588 -0
  282. package/.next/standalone/sdk/python/failproofai_sdk/integrations/pydantic_ai.py +977 -0
  283. package/.next/standalone/sdk/python/failproofai_sdk/py.typed +0 -0
  284. package/.next/standalone/sdk/python/pyproject.toml +92 -0
  285. package/.next/standalone/sdk/python/skill/SKILL.md +399 -0
  286. package/.next/standalone/sdk/python/skill/agents/openai.yaml +8 -0
  287. package/.next/standalone/sdk/python/skill/references/events.md +234 -0
  288. package/.next/standalone/sdk/python/skill/references/frameworks.md +314 -0
  289. package/.next/standalone/sdk/python/skill/references/install.md +100 -0
  290. package/.next/standalone/sdk/python/skill/references/integration.md +352 -0
  291. package/.next/standalone/sdk/python/tests/__init__.py +0 -0
  292. package/.next/standalone/sdk/python/tests/conftest.py +169 -0
  293. package/.next/standalone/sdk/python/tests/integrations/__init__.py +6 -0
  294. package/.next/standalone/sdk/python/tests/integrations/test_crewai.py +1299 -0
  295. package/.next/standalone/sdk/python/tests/integrations/test_langchain.py +2338 -0
  296. package/.next/standalone/sdk/python/tests/integrations/test_llama_index.py +1576 -0
  297. package/.next/standalone/sdk/python/tests/integrations/test_pydantic_ai.py +1102 -0
  298. package/.next/standalone/sdk/python/tests/test_context.py +410 -0
  299. package/.next/standalone/sdk/python/tests/test_docs.py +411 -0
  300. package/.next/standalone/sdk/python/tests/test_durability.py +1352 -0
  301. package/.next/standalone/sdk/python/tests/test_encoding.py +571 -0
  302. package/.next/standalone/sdk/python/tests/test_integrations.py +1127 -0
  303. package/.next/standalone/sdk/python/tests/test_no_customer_identifiers.py +214 -0
  304. package/.next/standalone/sdk/python/tests/test_packaging.py +185 -0
  305. package/.next/standalone/sdk/python/tests/test_resolver_umbrella.py +178 -0
  306. package/.next/standalone/sdk/python/tests/test_scopes.py +487 -0
  307. package/.next/standalone/sdk/python/tests/test_sdk.py +923 -0
  308. package/.next/standalone/sdk/python/tests/test_server_contract.py +650 -0
  309. package/.next/standalone/sdk/python/tests/test_site_docs.py +371 -0
  310. package/.next/standalone/sdk/python/tests/test_skill_snippets.py +165 -0
  311. package/.next/standalone/sdk/python/tests/test_spool_contract.py +287 -0
  312. package/.next/standalone/sdk/python/tests/test_spool_creation.py +360 -0
  313. package/.next/standalone/sdk/python/tests/test_wire_format.py +279 -0
  314. package/.next/standalone/sdk/python/tests/test_zero_dependencies.py +378 -0
  315. package/.next/standalone/sdk/python/uv.lock +6005 -0
  316. package/.next/standalone/server.js +1 -1
  317. package/README.md +77 -21
  318. package/bin/failproofai.mjs +579 -306
  319. package/dist/cli.mjs +6499 -3662
  320. package/dist/index.js +5 -5
  321. package/dist/worker.mjs +2390 -1978
  322. package/package.json +27 -21
  323. package/scripts/build-policy-pack.mjs +121 -0
  324. package/scripts/changelog-section.py +77 -0
  325. package/scripts/prune-standalone.mjs +3 -0
  326. package/scripts/python-version.py +232 -0
  327. package/scripts/translate-docs/cli.ts +21 -0
  328. package/scripts/translate-docs/mintlify-nav.ts +85 -13
  329. package/scripts/translate-docs/translator.ts +66 -11
  330. package/src/audit/cache.ts +40 -3
  331. package/src/audit/cli.ts +1 -1
  332. package/src/audit/index.ts +1 -1
  333. package/src/audit/replay.ts +89 -3
  334. package/src/audit/report.ts +5 -5
  335. package/src/audit/schedule-cli.ts +49 -53
  336. package/src/hooks/builtin-policies.ts +217 -579
  337. package/src/hooks/cloud-enrollment-cli.ts +66 -27
  338. package/src/hooks/cloud-managed-policies.ts +6 -1
  339. package/src/hooks/configure-wizard.ts +56 -369
  340. package/src/hooks/custom-hooks-loader.ts +89 -6
  341. package/src/hooks/fp-home.ts +92 -4
  342. package/src/hooks/fp-reset.ts +34 -2
  343. package/src/hooks/handler.ts +181 -11
  344. package/src/hooks/harness-cli.ts +79 -25
  345. package/src/hooks/hook-activity-store.ts +13 -3
  346. package/src/hooks/install-prompt.ts +25 -3
  347. package/src/hooks/manager.ts +424 -172
  348. package/src/hooks/migrations.ts +2 -2
  349. package/src/hooks/pack-cli.ts +885 -0
  350. package/src/hooks/pack-failclosed.ts +189 -0
  351. package/src/hooks/pack-manifest.ts +352 -0
  352. package/src/hooks/pack-store.ts +891 -0
  353. package/src/hooks/policy-catalog.ts +535 -0
  354. package/src/hooks/policy-evaluator.ts +24 -16
  355. package/src/hooks/policy-registry.ts +8 -2
  356. package/src/hooks/policy-types.ts +26 -0
  357. package/src/hooks/session-pause-cli.ts +33 -9
  358. package/src/hooks/tui.ts +695 -39
  359. package/src/hooks/types.ts +15 -1
  360. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0_usx0j._.js +0 -3
  361. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0_zk340._.js +0 -3
  362. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0agdw2a._.js +0 -3
  363. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0d1u_na._.js +0 -3
  364. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0oy4kxb._.js +0 -3
  365. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0zf034w._.js +0 -3
  366. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1tvn4wf._.js +0 -3
  367. package/.next/standalone/.next/server/chunks/_0lxbzdq._.js +0 -3
  368. package/.next/standalone/.next/server/chunks/_1ger0a2._.js +0 -3
  369. package/.next/standalone/.next/server/chunks/_1zuiiy3._.js +0 -3
  370. package/.next/standalone/.next/server/chunks/node_modules_posthog-node_dist_entrypoints_index_node_mjs_1iiwm5e._.js +0 -3
  371. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__07tbbf3._.js +0 -3
  372. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0kc8_v5._.js +0 -4
  373. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0kgy9da._.js +0 -4
  374. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0mhqk5a._.js +0 -4
  375. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0pltpn8._.js +0 -4
  376. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0qkcyf-._.js +0 -4
  377. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0w0x_ok._.js +0 -4
  378. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1_dfocn._.js +0 -4
  379. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1bfex62._.js +0 -5
  380. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1kzapg5._.js +0 -6
  381. package/.next/standalone/.next/server/chunks/ssr/_0lvyuv4._.js +0 -23
  382. package/.next/standalone/.next/static/chunks/2_2lhuojq2xop.js +0 -1
  383. package/.next/standalone/.next/static/chunks/36fw6yk1s6x_f.js +0 -1
  384. package/.next/standalone/.next/static/chunks/3k7mxbp9hiom0.js +0 -1
  385. package/.next/standalone/.next/static/chunks/3tzq0260n17ae.js +0 -6
  386. package/.next/standalone/.next/static/chunks/3wq9x0c-7ld5d.css +0 -2
  387. package/src/hooks/policy-presets.ts +0 -135
  388. /package/.next/standalone/.next/static/{ZklXxyA5ygsWSQz2u_zii → I5kEb9kSgYS63j4iqfVLO}/_buildManifest.js +0 -0
  389. /package/.next/standalone/.next/static/{ZklXxyA5ygsWSQz2u_zii → I5kEb9kSgYS63j4iqfVLO}/_clientMiddlewareManifest.js +0 -0
  390. /package/.next/standalone/.next/static/{ZklXxyA5ygsWSQz2u_zii → I5kEb9kSgYS63j4iqfVLO}/_ssgManifest.js +0 -0
@@ -0,0 +1,885 @@
1
+ /**
2
+ * `failproofai policies add | remove | list`.
3
+ *
4
+ * Presentation only — every rule about what a pack may be lives in
5
+ * `pack-manifest.ts` (what may load) and `pack-store.ts` (what may install).
6
+ * This layer decides nothing; it formats.
7
+ */
8
+ import { execFileSync } from "node:child_process";
9
+ import { createHash } from "node:crypto";
10
+ import { copyFileSync, existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
11
+ import { resolve } from "node:path";
12
+ import { parsePackIdentity, parsePackPolicy, readInstalledPacks } from "./pack-manifest";
13
+ import {
14
+ PACK_CHECKSUMS_ASSET,
15
+ PACK_ENTRY_ASSET,
16
+ PACK_MANIFEST_ASSET,
17
+ addPack,
18
+ checkPackArtifact,
19
+ CORE_ALIASES,
20
+ fetchPackPreview,
21
+ CORE_SOURCE,
22
+ packTagMatchesVersion,
23
+ removePack,
24
+ setPackPolicyEnabled,
25
+ slugifyCategory,
26
+ } from "./pack-store";
27
+ import type { PolicyEffect } from "./cloud-managed-policies";
28
+ import { loadCustomHooks } from "./custom-hooks-loader";
29
+ import type { MultiChoice, TTYIn, TTYOut } from "./tui";
30
+ import {
31
+ chip,
32
+ emptyState,
33
+ multiSelect,
34
+ note,
35
+ nextStep,
36
+ optsFor,
37
+ rows as kitRows,
38
+ rule,
39
+ stack,
40
+ table,
41
+ title,
42
+ warning,
43
+ } from "./tui";
44
+
45
+ export interface PackCliResult {
46
+ lines: string[];
47
+ exitCode: number;
48
+ }
49
+
50
+ const ok = (lines: string[]): PackCliResult => ({ lines, exitCode: 0 });
51
+ const fail = (lines: string[]): PackCliResult => ({ lines, exitCode: 1 });
52
+
53
+ function parseList(rest: string[], flag: string): string[] | undefined {
54
+ const idx = rest.findIndex((a) => a === flag || a.startsWith(`${flag}=`));
55
+ if (idx === -1) return undefined;
56
+ const raw = rest[idx].includes("=") ? rest[idx].split("=").slice(1).join("=") : rest[idx + 1];
57
+ if (!raw || raw.startsWith("--")) return [];
58
+ return raw.split(",").map((s) => s.trim()).filter(Boolean);
59
+ }
60
+
61
+ /** Flags that take a separate value, so it is never mistaken for the source. */
62
+ const VALUE_FLAGS = new Set(["--only", "--policy", "--category"]);
63
+
64
+ /** Find the positional source without mistaking a flag's separate value for it. */
65
+ export function packAddSource(rest: string[]): string | undefined {
66
+ const consumed = new Set<number>();
67
+ for (let i = 0; i < rest.length; i += 1) {
68
+ if (VALUE_FLAGS.has(rest[i])) consumed.add(i + 1);
69
+ }
70
+ return rest.find((arg, index) => !arg.startsWith("--") && !consumed.has(index));
71
+ }
72
+
73
+ /** Names taken by our own policies, so a selection can be checked before install. */
74
+ function selectionFrom(rest: string[]): { only?: string[]; categories?: string[]; all?: boolean } {
75
+ // `--policy` reads right for one ("give me this policy"), `--only` for a set.
76
+ // They are the same switch; taking both means neither is the wrong guess.
77
+ const only = parseList(rest, "--policy") ?? parseList(rest, "--only");
78
+ const categories = parseList(rest, "--category");
79
+ return {
80
+ ...(only ? { only } : {}),
81
+ ...(categories ? { categories } : {}),
82
+ ...(rest.includes("--all") ? { all: true } : {}),
83
+ };
84
+ }
85
+
86
+ /** Name a handful, then say how many more and where to see them. A 37-name wall
87
+ * is not information, and a count alone is not either. */
88
+ function summarise(names: string[], limit = 6): string {
89
+ if (names.length <= limit) return names.join(", ");
90
+ return `${names.slice(0, limit).join(", ")} +${names.length - limit} more`;
91
+ }
92
+
93
+
94
+ /**
95
+ * Turn a policy file into the three assets a GitHub release needs.
96
+ *
97
+ * The publishing side of the lane. Everything about what a pack IS was only
98
+ * discoverable by reading `pack-manifest.ts` and this repo's own build script —
99
+ * so a stranger who wanted to publish policies had to reverse-engineer a
100
+ * manifest, a checksum file and an asset naming convention, and would find out
101
+ * they got it wrong when somebody else's `pack add` refused it.
102
+ *
103
+ * It does NOT bundle. Only the ENTRY is content-addressed, so a pack whose entry
104
+ * imports local files could not honestly claim to be digest-pinned — the digest
105
+ * would cover one file out of several. A multi-file source must be bundled by
106
+ * its author first (esbuild, bun, rollup); this refuses it rather than shipping
107
+ * a promise it cannot keep. Not bundling here also keeps the command runnable on
108
+ * plain node, which is what the published CLI is.
109
+ *
110
+ * Every policy is validated with `parsePackPolicy` — the LOADER's own rules — so
111
+ * a pack that could never install fails here, where the author can fix it,
112
+ * rather than on a stranger's machine.
113
+ */
114
+ async function build(rest: string[]): Promise<PackCliResult> {
115
+ const flag = (name: string): string | undefined => {
116
+ const i = rest.findIndex((a) => a === `--${name}` || a.startsWith(`--${name}=`));
117
+ if (i === -1) return undefined;
118
+ return rest[i].includes("=") ? rest[i].split("=").slice(1).join("=") : rest[i + 1];
119
+ };
120
+ const entry = packAddSource(rest) ?? flag("entry");
121
+ const id = flag("id");
122
+ const version = flag("version");
123
+ const effect = flag("effect") ?? "enforce";
124
+ const outDir = resolve(flag("out") ?? "dist-pack");
125
+
126
+ if (!entry || !id || !version) {
127
+ return fail([
128
+ "Usage: failproofai publish <entry.mjs> --repo <owner>/<repo> --version <version>",
129
+ " [--out <dir>] [--effect enforce|observe]",
130
+ ]);
131
+ }
132
+ let identity: { id: string; version: string; effect: PolicyEffect };
133
+ try {
134
+ identity = parsePackIdentity({ id, version, effect });
135
+ } catch (err) {
136
+ return fail([err instanceof Error ? err.message : String(err)]);
137
+ }
138
+
139
+ const entryPath = resolve(entry);
140
+ if (!existsSync(entryPath)) return fail([`No such file: ${entryPath}`]);
141
+
142
+ // Refused rather than bundled: see the note above. A relative specifier is the
143
+ // only kind that would be rewritten by the loader and left outside the digest.
144
+ const source = readFileSync(entryPath, "utf8");
145
+ const localImport = /(?:^|\n)\s*(?:import|export)[^;\n]*from\s+["'](\.[^"']*)["']/.exec(source);
146
+ if (localImport) {
147
+ return fail([
148
+ `${entryPath} imports ${localImport[1]}, and only the entry file is digest-pinned.`,
149
+ "Bundle it to a single file first (esbuild, bun build, rollup), then build the pack from that.",
150
+ ]);
151
+ }
152
+
153
+ let hooks;
154
+ try {
155
+ hooks = await loadCustomHooks(entryPath, { strict: true });
156
+ } catch (err) {
157
+ return fail([`Could not load ${entryPath}: ${err instanceof Error ? err.message : String(err)}`]);
158
+ }
159
+ if (hooks.length === 0) {
160
+ return fail([
161
+ `${entryPath} registered no policies.`,
162
+ "A pack entry calls customPolicies.add({ name, description, match, fn }) for each one.",
163
+ ]);
164
+ }
165
+
166
+ const policies: unknown[] = [];
167
+ for (const [index, hook] of hooks.entries()) {
168
+ // `category` and `defaultEnabled` are pack-manifest fields a plain custom
169
+ // policy has no reason to carry, so they are read off the registration when
170
+ // the author set them and defaulted when not. `defaultEnabled` defaults to
171
+ // FALSE: a pack's declared defaults are what `pack add` switches on with no
172
+ // flags, and switching on a stranger's every policy unattended is the
173
+ // installer opinion this lane already refused once.
174
+ const extra = hook as unknown as { category?: unknown; defaultEnabled?: unknown };
175
+ const candidate = {
176
+ name: hook.name,
177
+ description: hook.description ?? "",
178
+ category: typeof extra.category === "string" && extra.category ? extra.category : "General",
179
+ defaultEnabled: extra.defaultEnabled === true,
180
+ match: hook.match ?? {},
181
+ };
182
+ try {
183
+ policies.push(parsePackPolicy(identity.id, candidate, index));
184
+ } catch (err) {
185
+ return fail([err instanceof Error ? err.message : String(err)]);
186
+ }
187
+ }
188
+
189
+ const manifest =
190
+ JSON.stringify(
191
+ { id: identity.id, version: identity.version, effect: identity.effect, policies },
192
+ null,
193
+ 2,
194
+ ) + "\n";
195
+ mkdirSync(outDir, { recursive: true });
196
+ const manifestPath = resolve(outDir, PACK_MANIFEST_ASSET);
197
+ const entryOut = resolve(outDir, PACK_ENTRY_ASSET);
198
+ writeFileSync(manifestPath, manifest, "utf8");
199
+ copyFileSync(entryPath, entryOut);
200
+ const sha = (bytes: Buffer | string) => createHash("sha256").update(bytes).digest("hex");
201
+ writeFileSync(
202
+ resolve(outDir, PACK_CHECKSUMS_ASSET),
203
+ `${sha(manifest)} ${PACK_MANIFEST_ASSET}\n${sha(readFileSync(entryOut))} ${PACK_ENTRY_ASSET}\n`,
204
+ "utf8",
205
+ );
206
+
207
+ const on = policies.filter((p) => (p as { defaultEnabled?: boolean }).defaultEnabled).length;
208
+ return ok([
209
+ `Built ${identity.id}@${identity.version} — ${policies.length} policies, ${on} on by default.`,
210
+ ` ${outDir}/${PACK_MANIFEST_ASSET}`,
211
+ ` ${outDir}/${PACK_ENTRY_ASSET}`,
212
+ ` ${outDir}/${PACK_CHECKSUMS_ASSET}`,
213
+ "",
214
+ `Publish: attach all three to a GitHub release tagged ${identity.version}, then anyone runs:`,
215
+ ` failproofai policies add <owner>/<repo>`,
216
+ ]);
217
+ }
218
+
219
+ /**
220
+ * The list, with what is already on ticked, for `failproofai policies add` with
221
+ * nothing after it.
222
+ *
223
+ * Naming nothing used to be an error — "Missing policy name", followed by an
224
+ * instruction to go and read a list somewhere else and come back. That is the
225
+ * command telling the user to do the work it is for. The same objection applies
226
+ * to `pack add` taking a publisher's defaults and only afterwards printing what
227
+ * it decided: a default is a suggestion, and a suggestion the user never saw is
228
+ * just a decision taken on their behalf.
229
+ *
230
+ * So this is one screen showing every policy on the machine, grouped by pack and
231
+ * category, with the current state pre-ticked. It edits the enabled SET — there
232
+ * is no separate "remove" screen, because choosing what is on and choosing what
233
+ * is off are the same act.
234
+ *
235
+ * Refuses rather than guesses when there is no terminal to draw on. `multiSelect`
236
+ * degrades by returning its pre-checked set, which here would mean "confirm
237
+ * exactly what is already true" — a silent no-op reported as a success. A script
238
+ * that lands in this branch asked the wrong question and should be told so.
239
+ */
240
+ export async function runPolicyPicker(
241
+ action: "add" | "remove",
242
+ io: { stdin?: TTYIn; stdout?: TTYOut } = {},
243
+ ): Promise<PackCliResult> {
244
+ const stdin = io.stdin ?? process.stdin;
245
+ const stdout = io.stdout ?? process.stdout;
246
+ const opts = optsFor(stdout as NodeJS.WriteStream);
247
+ const { packs } = readInstalledPacks();
248
+
249
+ if (!stdin.isTTY || !stdout.isTTY) {
250
+ return fail([
251
+ `\`policies ${action}\` with no name needs a terminal to show you the list.`,
252
+ "From a script, name what you mean:",
253
+ ` failproofai policies ${action} <policy-name>`,
254
+ ` failproofai policies ${action} <owner>/<repo> [--policy a,b] [--category x,y] [--all]`,
255
+ ]);
256
+ }
257
+
258
+ if (packs.length === 0) {
259
+ return ok(
260
+ stack(
261
+ emptyState(
262
+ {
263
+ what: "No policies are installed yet.",
264
+ hint: "Take ours, or anyone's:",
265
+ cmd: "failproofai policies add core",
266
+ },
267
+ opts,
268
+ ),
269
+ note("Someone else's: failproofai policies add <owner>/<repo>", opts),
270
+ note("Look first: failproofai policies show <owner>/<repo>", opts),
271
+ ),
272
+ );
273
+ }
274
+
275
+ // One row per policy across every pack. The pack id leads the section heading
276
+ // because two packs may legitimately ship a category of the same name, and a
277
+ // row that says only "Data" would not tell you whose Data it is.
278
+ const rows: MultiChoice<string>[] = [];
279
+ const owner = new Map<string, string>();
280
+ for (const pack of packs) {
281
+ const taken = new Set(pack.enabled ?? pack.policies.map((pol) => pol.name));
282
+ const categories = [...new Set(pack.policies.map((pol) => pol.category))];
283
+ for (const category of categories) {
284
+ for (const pol of pack.policies.filter((x) => x.category === category)) {
285
+ // Keyed by name, not by pack+name: a name collision across two packs is
286
+ // already impossible to enforce unambiguously, and silently editing the
287
+ // wrong pack's copy would be worse than the collision.
288
+ owner.set(pol.name, pack.id);
289
+ rows.push({
290
+ label: pol.name,
291
+ value: pol.name,
292
+ hint: pol.description,
293
+ checked: taken.has(pol.name),
294
+ section: `${pack.id} · ${category}`,
295
+ });
296
+ }
297
+ }
298
+ }
299
+
300
+ const before = new Set(rows.filter((r) => r.checked).map((r) => r.value));
301
+ const picked = await multiSelect<string>({
302
+ message: "Which policies should be on?",
303
+ choices: rows,
304
+ summaryNoun: "policies",
305
+ hint: "space toggles · ctrl+a all · ↵ confirm · what is on now is ticked",
306
+ stdin,
307
+ stdout,
308
+ });
309
+ if (picked === null) return ok(["Nothing changed."]);
310
+
311
+ const after = new Set(picked);
312
+ const turnedOn = [...after].filter((n) => !before.has(n));
313
+ const turnedOff = [...before].filter((n) => !after.has(n));
314
+ if (turnedOn.length === 0 && turnedOff.length === 0) {
315
+ return ok([`Nothing changed — ${after.size} of ${rows.length} still on.`]);
316
+ }
317
+
318
+ const failures: string[] = [];
319
+ for (const name of [...turnedOn, ...turnedOff]) {
320
+ const packId = owner.get(name);
321
+ if (!packId) continue;
322
+ const result = setPackPolicyEnabled(packId, name, after.has(name));
323
+ if (!result.ok) failures.push(`${name}: ${result.reason ?? "could not be written"}`);
324
+ }
325
+ if (failures.length > 0) {
326
+ return fail(["Some policies could not be changed:", ...failures.map((f) => ` ${f}`)]);
327
+ }
328
+
329
+ const lines = [`${after.size} of ${rows.length} policies on.`];
330
+ if (turnedOn.length > 0) lines.push(` turned on (${turnedOn.length}): ${summarise(turnedOn)}`);
331
+ if (turnedOff.length > 0) lines.push(` turned off (${turnedOff.length}): ${summarise(turnedOff)}`);
332
+ return ok(lines);
333
+ }
334
+
335
+
336
+ // ── publish ───────────────────────────────────────────────────────────────
337
+
338
+ const GITHUB_API = process.env.FAILPROOFAI_GITHUB_API ?? "https://api.github.com";
339
+ const GITHUB_UPLOADS = process.env.FAILPROOFAI_GITHUB_UPLOADS ?? "https://uploads.github.com";
340
+
341
+ /**
342
+ * The credential, from the places a developer already keeps one.
343
+ *
344
+ * `gh auth token` is READ-ONLY and is deliberately not one of the subcommands
345
+ * `block-gh-pipeline` matches — which is exactly why the release itself goes
346
+ * over REST rather than through `gh release create`: that one IS matched, so a
347
+ * user with our own policy switched on could not publish with a gh-shaped
348
+ * implementation. Shipping a publish path our own guardrail blocks is not a
349
+ * thing to do.
350
+ *
351
+ * Never returned in any message. A token that reaches stdout reaches CI logs.
352
+ */
353
+ function githubToken(): string | null {
354
+ const fromEnv = process.env.GITHUB_TOKEN || process.env.GH_TOKEN;
355
+ if (fromEnv) return fromEnv.trim();
356
+ try {
357
+ const out = execFileSync("gh", ["auth", "token"], {
358
+ encoding: "utf8",
359
+ stdio: ["ignore", "pipe", "ignore"],
360
+ timeout: 5_000,
361
+ });
362
+ return out.trim() || null;
363
+ } catch {
364
+ return null;
365
+ }
366
+ }
367
+
368
+ async function gh(
369
+ url: string,
370
+ token: string,
371
+ init: { method?: string; body?: BodyInit; contentType?: string } = {},
372
+ ): Promise<{ status: number; json: Record<string, unknown> | null; text: string }> {
373
+ const response = await fetch(url, {
374
+ method: init.method ?? "GET",
375
+ headers: {
376
+ Authorization: `Bearer ${token}`,
377
+ Accept: "application/vnd.github+json",
378
+ "X-GitHub-Api-Version": "2022-11-28",
379
+ "User-Agent": "failproofai",
380
+ ...(init.contentType ? { "Content-Type": init.contentType } : {}),
381
+ },
382
+ ...(init.body ? { body: init.body } : {}),
383
+ signal: AbortSignal.timeout(60_000),
384
+ });
385
+ const text = await response.text();
386
+ let json: Record<string, unknown> | null = null;
387
+ try {
388
+ json = text ? (JSON.parse(text) as Record<string, unknown>) : null;
389
+ } catch {
390
+ json = null;
391
+ }
392
+ return { status: response.status, json, text };
393
+ }
394
+
395
+ /** GitHub's error bodies are shaped `{message, errors:[{code,field}]}`; the bare
396
+ * `message` alone is frequently just "Validation Failed". */
397
+ function ghError(res: { status: number; json: Record<string, unknown> | null; text: string }): string {
398
+ const message = typeof res.json?.message === "string" ? res.json.message : res.text.slice(0, 200);
399
+ const errors = Array.isArray(res.json?.errors)
400
+ ? (res.json.errors as Array<Record<string, unknown>>)
401
+ .map((e) => [e.field, e.code].filter(Boolean).join(" "))
402
+ .filter(Boolean)
403
+ : [];
404
+ return errors.length > 0 ? `${message} (${errors.join("; ")})` : message;
405
+ }
406
+
407
+ /**
408
+ * Build the assets AND put them on a GitHub release, in one command.
409
+ *
410
+ * Publishing used to be four commands, and two of them did nothing: `git init`,
411
+ * `git add`, `git commit`, `gh repo create`, `gh release create`. Only the last
412
+ * one publishes — installs read `releases/download/<tag>/<asset>` and never
413
+ * touch the git tree, so pushing source is for humans reading it, not for the
414
+ * install to work. A publisher had to discover that by reading `pack-store.ts`.
415
+ *
416
+ * With no `--repo` this is exactly the old `pack build`: write the three assets
417
+ * and stop. That is what `pack build` now resolves to, so the old command keeps
418
+ * working and means the same thing.
419
+ *
420
+ * Two failures the manual flow let through silently, both refused here:
421
+ * - a tag that does not describe the manifest version, which installs and then
422
+ * reports a version matching no URL;
423
+ * - a private repository, which publishes to nobody, because installs are
424
+ * anonymous HTTPS with no credential to offer.
425
+ */
426
+ async function publish(rest: string[]): Promise<PackCliResult> {
427
+ const flag = (name: string): string | undefined => {
428
+ const i = rest.findIndex((a) => a === `--${name}` || a.startsWith(`--${name}=`));
429
+ if (i === -1) return undefined;
430
+ return rest[i].includes("=") ? rest[i].split("=").slice(1).join("=") : rest[i + 1];
431
+ };
432
+
433
+ const repo = flag("repo");
434
+ const version = flag("version");
435
+ const entry = packAddSource(rest.filter((a) => a !== "--dry-run")) ?? flag("entry");
436
+ // The id and the repo are usually the same words, and requiring both is asking
437
+ // the same question twice. Either one alone answers for the other.
438
+ const id = flag("id") ?? repo;
439
+ const tag = flag("tag") ?? version;
440
+ const dryRun = rest.includes("--dry-run") || !repo;
441
+
442
+ if (!entry || !version || !id) {
443
+ return fail([
444
+ "Usage: failproofai publish <entry.mjs> --repo <owner>/<repo> --version <version>",
445
+ " [--id <publisher/name>] [--tag <tag>] [--notes <text>]",
446
+ " [--out <dir>] [--effect enforce|observe] [--dry-run]",
447
+ "",
448
+ "With no --repo it writes the three release assets and stops.",
449
+ ]);
450
+ }
451
+
452
+ // Build first, always. There is no point authenticating against GitHub to
453
+ // discover the pack does not load.
454
+ const built = await build([entry, "--id", id, "--version", version, ...outFlagFrom(rest), ...effectFlagFrom(rest)]);
455
+ if (built.exitCode !== 0) return built;
456
+
457
+ const outDir = resolve(flag("out") ?? "dist-pack");
458
+ if (dryRun) {
459
+ return ok([
460
+ ...built.lines.slice(0, 4),
461
+ "",
462
+ ...(repo
463
+ ? ["Dry run — nothing was published.", `Drop --dry-run to release it on ${repo}.`]
464
+ : [
465
+ "Nothing was published: name a repository to release it on.",
466
+ ` failproofai publish ${entry} --repo <owner>/<repo> --version ${version}`,
467
+ ]),
468
+ ]);
469
+ }
470
+
471
+ if (!packTagMatchesVersion(tag!, version)) {
472
+ return fail([
473
+ `Tag ${tag} does not describe version ${version}, so nobody could install it.`,
474
+ `A pack is fetched from releases/download/${tag}/… and then reports ${version} —`,
475
+ `which names no release. Use --tag ${version} (a leading v is fine), or build`,
476
+ "the pack at the version the tag says.",
477
+ ]);
478
+ }
479
+
480
+ const [owner, name] = repo!.split("/");
481
+ if (!owner || !name || repo!.split("/").length !== 2) {
482
+ return fail([`--repo must be <owner>/<repo>, got ${JSON.stringify(repo)}`]);
483
+ }
484
+
485
+ const token = githubToken();
486
+ if (!token) {
487
+ return fail([
488
+ "No GitHub credential found.",
489
+ "Set GITHUB_TOKEN (or GH_TOKEN), or sign in once with `gh auth login`.",
490
+ "It needs write access to releases on that repository, and nothing else.",
491
+ ]);
492
+ }
493
+
494
+ const repoInfo = await gh(`${GITHUB_API}/repos/${owner}/${name}`, token);
495
+ if (repoInfo.status === 404) {
496
+ return fail([
497
+ `${repo} does not exist, or that credential cannot see it.`,
498
+ `Create it first: gh repo create ${repo} --public`,
499
+ ]);
500
+ }
501
+ if (repoInfo.status >= 400) return fail([`Could not read ${repo}: ${ghError(repoInfo)}`]);
502
+ // A private repo is not a smaller audience, it is no audience: `pack add`
503
+ // sends no Authorization header at all, by design, so every install 404s.
504
+ const isPrivate = repoInfo.json?.private === true;
505
+
506
+ // Reuse a release on this tag rather than failing on it. Re-publishing a
507
+ // corrected artifact under an existing tag is a normal thing to need, and the
508
+ // digest change is visible to anyone who reinstalls.
509
+ let releaseId: number | null = null;
510
+ const existing = await gh(`${GITHUB_API}/repos/${owner}/${name}/releases/tags/${encodeURIComponent(tag!)}`, token);
511
+ if (existing.status === 200 && typeof existing.json?.id === "number") {
512
+ releaseId = existing.json.id;
513
+ } else {
514
+ const created = await gh(`${GITHUB_API}/repos/${owner}/${name}/releases`, token, {
515
+ method: "POST",
516
+ contentType: "application/json",
517
+ body: JSON.stringify({
518
+ tag_name: tag,
519
+ name: `${id} ${version}`,
520
+ body: flag("notes") ?? `${id}@${version}`,
521
+ // A prerelease is invisible to `releases/latest`, which is how a tagless
522
+ // `policies add owner/repo` resolves a version — so publishing one would
523
+ // make the pack installable only by people who already knew its tag.
524
+ prerelease: false,
525
+ draft: false,
526
+ }),
527
+ });
528
+ if (created.status >= 400 || typeof created.json?.id !== "number") {
529
+ return fail([`Could not create the release on ${repo}: ${ghError(created)}`]);
530
+ }
531
+ releaseId = created.json.id;
532
+ }
533
+
534
+ const assets = [PACK_MANIFEST_ASSET, PACK_ENTRY_ASSET, PACK_CHECKSUMS_ASSET];
535
+ const uploaded: string[] = [];
536
+ const listed = await gh(`${GITHUB_API}/repos/${owner}/${name}/releases/${releaseId}/assets?per_page=100`, token);
537
+ const already = Array.isArray(listed.json)
538
+ ? (listed.json as unknown as Array<{ id: number; name: string }>)
539
+ : [];
540
+ for (const asset of assets) {
541
+ // Asset names are fixed and the URL is constructed from them, so a stale
542
+ // copy under the same name is what an installer would fetch. Replace, never
543
+ // append.
544
+ const prior = already.find((a) => a.name === asset);
545
+ if (prior) {
546
+ await gh(`${GITHUB_API}/repos/${owner}/${name}/releases/assets/${prior.id}`, token, { method: "DELETE" });
547
+ }
548
+ const bytes = readFileSync(resolve(outDir, asset));
549
+ const upload = await gh(
550
+ `${GITHUB_UPLOADS}/repos/${owner}/${name}/releases/${releaseId}/assets?name=${encodeURIComponent(asset)}`,
551
+ token,
552
+ {
553
+ method: "POST",
554
+ contentType: asset.endsWith(".json") ? "application/json" : "application/octet-stream",
555
+ body: new Uint8Array(bytes),
556
+ },
557
+ );
558
+ if (upload.status >= 400) {
559
+ return fail([
560
+ `Uploaded ${uploaded.length} of ${assets.length} assets, then ${asset} failed: ${ghError(upload)}`,
561
+ `The release exists but is INCOMPLETE — an install would fail on the missing asset.`,
562
+ `Re-run the same command; existing assets are replaced, not duplicated.`,
563
+ ]);
564
+ }
565
+ uploaded.push(asset);
566
+ }
567
+
568
+ const lines = [
569
+ `Published ${id}@${version} to ${repo} at tag ${tag}.`,
570
+ ` ${assets.length} assets attached`,
571
+ "",
572
+ "Anyone can now install it:",
573
+ ` failproofai policies add ${repo}`,
574
+ ` failproofai policies show ${repo} (look first, without running it)`,
575
+ ];
576
+ if (isPrivate) {
577
+ lines.push(
578
+ "",
579
+ `WARNING: ${repo} is PRIVATE, so nobody can install this.`,
580
+ "Installs are anonymous HTTPS with no credential to offer — every one will 404.",
581
+ `Make it public: gh repo edit ${repo} --visibility public`,
582
+ );
583
+ }
584
+ return ok(lines);
585
+ }
586
+
587
+ /** Pass --out through to build without re-parsing it. */
588
+ function outFlagFrom(rest: string[]): string[] {
589
+ const i = rest.findIndex((a) => a === "--out" || a.startsWith("--out="));
590
+ if (i === -1) return [];
591
+ return rest[i].includes("=") ? [rest[i]] : [rest[i], rest[i + 1]];
592
+ }
593
+ function effectFlagFrom(rest: string[]): string[] {
594
+ const i = rest.findIndex((a) => a === "--effect" || a.startsWith("--effect="));
595
+ if (i === -1) return [];
596
+ return rest[i].includes("=") ? [rest[i]] : [rest[i], rest[i + 1]];
597
+ }
598
+
599
+ /** `failproofai publish` — exported for bin/failproofai.mjs and the tests. */
600
+ export async function runPublishCommand(rest: string[]): Promise<PackCliResult> {
601
+ return publish(rest);
602
+ }
603
+
604
+ async function add(rest: string[]): Promise<PackCliResult> {
605
+ const source = packAddSource(rest);
606
+ const selection = selectionFrom(rest);
607
+
608
+ // Our own pack, by the short name. `core` is a SPELLING of a GitHub source,
609
+ // not a second delivery path: it resolves to CORE_SOURCE and is fetched,
610
+ // verified and pinned exactly like anybody else's.
611
+ //
612
+ // The package used to carry a copy so this worked with no network. It no
613
+ // longer does, on purpose. A pack that ships inside the binary is a policy set
614
+ // we picked for you and wrote to your disk before you asked for it, and it
615
+ // gave our own policies a route no third-party pack could use — which is the
616
+ // opposite of the thing this whole lane exists to make possible.
617
+ const resolvedSource =
618
+ source && CORE_ALIASES.has(source.toLowerCase()) ? CORE_SOURCE : source;
619
+
620
+ if (!resolvedSource) {
621
+ return fail(["Usage: failproofai policies add <source> [--policy a,b] [--category x,y] [--all]"]);
622
+ }
623
+ if (selection.only && selection.only.length === 0) {
624
+ return fail(["--policy needs at least one policy name, comma-separated"]);
625
+ }
626
+ if (selection.categories && selection.categories.length === 0) {
627
+ return fail(["--category needs at least one category, comma-separated"]);
628
+ }
629
+
630
+ try {
631
+ const result = await addPack(resolvedSource!, selection);
632
+ const lines = [
633
+ ...(result.replaced?.length
634
+ ? [`Replaced ${result.replaced.join(", ")} — same policies under a new name; your selection was kept.`]
635
+ : []),
636
+ result.resolvedFromLatest
637
+ ? `Installed ${result.id}@${result.version} from ${result.source} (newest release; pinned to ${result.tag})`
638
+ : `Installed ${result.id}@${result.version} from ${result.source}`,
639
+ ];
640
+ const skipped = result.available.filter((n) => !result.enabled.includes(n));
641
+
642
+ // Say WHY this set is on. Without it, "10 of 38 enabled" reads like
643
+ // something went wrong rather than like the pack's own recommendation.
644
+ const why = {
645
+ defaults: "the pack's defaults",
646
+ selected: "your selection",
647
+ carried: "your existing selection",
648
+ all: "everything in the pack",
649
+ }[result.selection];
650
+ lines.push(` enabled (${result.enabled.length}/${result.available.length}, ${why}): ${summarise(result.enabled)}`);
651
+
652
+ if (skipped.length > 0) {
653
+ lines.push(` not enabled (${skipped.length}): ${summarise(skipped)}`);
654
+ lines.push("");
655
+ lines.push(` see all: failproofai policies`);
656
+ // Naming --policy here as well as the coarser two: it is the flag people
657
+ // reach for first ("just give me that one"), and it was only ever
658
+ // suggested on the branch that installed our own pack from disk — so
659
+ // every third-party pack told you about categories and everything, and
660
+ // never about taking a single policy.
661
+ lines.push(` one policy: failproofai policies add ${source} --policy ${skipped[0]}`);
662
+ if (result.categories.length > 0) {
663
+ lines.push(` by category: failproofai policies add ${source} --category ${result.categories.slice(0, 3).join(",")}`);
664
+ }
665
+ lines.push(` everything: failproofai policies add ${source} --all`);
666
+ }
667
+ return ok(lines);
668
+ } catch (err) {
669
+ return fail([`Could not install pack: ${err instanceof Error ? err.message : String(err)}`]);
670
+ }
671
+ }
672
+
673
+ function remove(rest: string[]): PackCliResult {
674
+ const id = rest[0];
675
+ if (!id) return fail(["Usage: failproofai policies remove <publisher/name>"]);
676
+ if (!removePack(id)) return fail([`No installed pack with id ${id}`]);
677
+ return ok([
678
+ `Removed ${id}. Its policies stop enforcing now; re-add it any time with the same command.`,
679
+ ]);
680
+ }
681
+
682
+ /**
683
+ * What a pack CONTAINS, for a pack this machine has not installed.
684
+ *
685
+ * `failproofai policies` already lists everything installed, packs included, so
686
+ * the only thing missing was the pack you are still deciding about. Reading the
687
+ * manifest answers that without installing it — and deliberately without
688
+ * downloading or importing the entry artifact, so looking at a stranger's pack
689
+ * cannot run a stranger's code.
690
+ */
691
+ async function listRemote(source: string): Promise<PackCliResult> {
692
+ const opts = optsFor(process.stdout);
693
+ let preview;
694
+ try {
695
+ preview = await fetchPackPreview(source);
696
+ } catch (err) {
697
+ return fail([`Could not read ${source}: ${err instanceof Error ? err.message : String(err)}`]);
698
+ }
699
+
700
+ const defaults = preview.policies.filter((p) => p.defaultEnabled);
701
+ const categories = [...new Set(preview.policies.map((p) => p.category))];
702
+ const rows: Array<string[] | { section: string }> = [];
703
+ for (const category of categories) {
704
+ const inCategory = preview.policies.filter((p) => p.category === category);
705
+ const on = inCategory.filter((p) => p.defaultEnabled).length;
706
+ rows.push({
707
+ section: `${category} — ${on}/${inCategory.length} on by default · ${slugifyCategory(category)}`,
708
+ });
709
+ for (const policy of inCategory) {
710
+ // The publisher's DEFAULTS, not a machine's state — this pack is not
711
+ // installed, so a row claiming ON would be describing nothing.
712
+ rows.push([
713
+ policy.defaultEnabled ? "default" : "opt-in",
714
+ policy.name,
715
+ policy.description,
716
+ ]);
717
+ }
718
+ }
719
+
720
+ return ok(
721
+ stack(
722
+ title(
723
+ `${preview.id}@${preview.version}`,
724
+ `${preview.policies.length} policies · ${categories.length} categories`,
725
+ opts,
726
+ ),
727
+ note(
728
+ `Not installed. ${defaults.length} of ${preview.policies.length} are on by default; the rest are opt-in.` +
729
+ (preview.effect === "observe" ? " This pack OBSERVES — it records and blocks nothing." : ""),
730
+ opts,
731
+ ),
732
+ preview.resolvedFromLatest ? note(`Newest release: ${preview.source}`, opts) : null,
733
+ table({ head: ["", "", ""], rows }, opts),
734
+ nextStep(`failproofai policies add ${source}`, "Install the defaults with:", opts),
735
+ note("Or take part of it: --policy <a,b>, --category <x,y>, --all", opts),
736
+ ),
737
+ );
738
+ }
739
+
740
+ async function list(): Promise<PackCliResult> {
741
+ const { packs, errors } = readInstalledPacks();
742
+ const opts = optsFor(process.stdout);
743
+ const policyCount = packs.reduce((n, pack) => n + pack.policies.length, 0);
744
+ const head = title(
745
+ "failproofai policies",
746
+ packs.length === 0
747
+ ? undefined
748
+ : `${packs.length} pack${packs.length === 1 ? "" : "s"} · ${policyCount} policies`,
749
+ opts,
750
+ );
751
+
752
+ if (packs.length === 0 && errors.length === 0) {
753
+ return ok(
754
+ stack(
755
+ head,
756
+ emptyState(
757
+ {
758
+ what: "No packs installed.",
759
+ hint: "Install one with:",
760
+ cmd: "failproofai policies add github:owner/repo@tag",
761
+ },
762
+ opts,
763
+ ),
764
+ ),
765
+ );
766
+ }
767
+
768
+ const groups: Array<string[] | null> = [head];
769
+ const broken: string[] = [];
770
+ for (const pack of packs) {
771
+ const taken = pack.enabled ?? pack.policies.map((p) => p.name);
772
+ // Importing it is the only way to know it still loads. A listing that reads
773
+ // healthy while the machine is denying every tool call because of this pack
774
+ // is worse than no listing.
775
+ const failure = await checkPackArtifact(pack.path, pack.policies.map((p) => p.name));
776
+ if (failure) broken.push(`${pack.id}@${pack.version} ${failure}`);
777
+ groups.push(rule(`${pack.id}@${pack.version}`, opts));
778
+ groups.push(
779
+ kitRows(
780
+ [
781
+ ["source", pack.source],
782
+ ["digest", `${pack.sha256.slice(0, 16)}…`],
783
+ ["effect", pack.effect],
784
+ ["enabled", `${taken.length}/${pack.policies.length}`],
785
+ ...(failure ? ([["health", "WILL NOT LOAD"]] as Array<[string, string]>) : []),
786
+ ],
787
+ opts,
788
+ ),
789
+ );
790
+ // The category is a COLUMN rather than a set of sub-headings because it is
791
+ // what `--category` selects on: seeing the slug next to every policy is what
792
+ // tells someone the flag exists, and the suggestion below spells it out.
793
+ // Four columns leave a description about 24 characters wide on an
794
+ // 80-column terminal, which is not a description. Below 100 the category
795
+ // column goes and its slugs are named once underneath instead.
796
+ const wide = opts.cols >= 100;
797
+ groups.push(
798
+ table(
799
+ {
800
+ head: wide ? ["", "Policy", "Category", "Description"] : ["", "Policy", "Description"],
801
+ rows: pack.policies.map((policy) =>
802
+ wide
803
+ ? [
804
+ chip(taken.includes(policy.name) ? "on" : "off", opts),
805
+ policy.name,
806
+ policy.category,
807
+ policy.description,
808
+ ]
809
+ : [
810
+ chip(taken.includes(policy.name) ? "on" : "off", opts),
811
+ policy.name,
812
+ policy.description,
813
+ ],
814
+ ),
815
+ },
816
+ opts,
817
+ ),
818
+ );
819
+ const slugs = [...new Set(pack.policies.map((p) => slugifyCategory(p.category)))];
820
+ if (!wide && slugs.length > 0) {
821
+ groups.push(note(`Categories: ${slugs.join(", ")}`, opts));
822
+ }
823
+ // The RECORDED SOURCE, never the manifest id. `failproofai/builtins` is an
824
+ // id, not a repository, so suggesting `pack add failproofai/builtins` hands
825
+ // the user a command that resolves nothing. A `bundled:` pack came off the
826
+ // package rather than a release and has no `pack add` form at all.
827
+ const source = pack.source.startsWith("bundled:") ? null : pack.source;
828
+ if (source && taken.length < pack.policies.length && slugs.length > 0) {
829
+ groups.push(
830
+ nextStep(
831
+ `failproofai policies add ${source} --category ${slugs.slice(0, 3).join(",")}`,
832
+ "Take a whole category with:",
833
+ opts,
834
+ ),
835
+ );
836
+ }
837
+ }
838
+
839
+ // Never silent: a pack that was installed and now refuses to load is exactly
840
+ // the state someone needs told about, since the machine is enforcing less
841
+ // than its manifest says.
842
+ if (errors.length > 0) {
843
+ groups.push(
844
+ warning(
845
+ errors.map((err) => `NOT LOADED ${err.id ?? "(unnamed)"}: ${err.reason}`),
846
+ opts,
847
+ ),
848
+ );
849
+ }
850
+
851
+ if (broken.length > 0) {
852
+ groups.push(
853
+ warning(
854
+ [
855
+ ...broken,
856
+ "This machine denies the events those policies covered until it is fixed.",
857
+ ],
858
+ opts,
859
+ ),
860
+ );
861
+ }
862
+ const lines = stack(...groups);
863
+ return errors.length > 0 || broken.length > 0 ? fail(lines) : ok(lines);
864
+ }
865
+
866
+ export async function runPackCommand(argv: string[]): Promise<PackCliResult> {
867
+ const [sub, ...rest] = argv;
868
+ switch (sub) {
869
+ case "add":
870
+ return add(rest);
871
+ case "remove":
872
+ return remove(rest);
873
+ case "build":
874
+ return build(rest);
875
+ case "list":
876
+ case undefined: {
877
+ // `pack list` is what is installed here; `pack list <source>` is what a
878
+ // pack out there contains.
879
+ const source = packAddSource(rest);
880
+ return source ? listRemote(source) : list();
881
+ }
882
+ default:
883
+ return fail([`Unknown pack subcommand ${JSON.stringify(sub)}`, "Try: add, remove, list, build"]);
884
+ }
885
+ }