failproofai 1.0.10 → 1.0.11-beta.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 (296) hide show
  1. package/.next/standalone/.next/BUILD_ID +1 -1
  2. package/.next/standalone/.next/build-manifest.json +6 -6
  3. package/.next/standalone/.next/prerender-manifest.json +4 -4
  4. package/.next/standalone/.next/required-server-files.json +1 -1
  5. package/.next/standalone/.next/server/app/_global-error/page/build-manifest.json +3 -3
  6. package/.next/standalone/.next/server/app/_global-error/page/server-reference-manifest.json +1 -1
  7. package/.next/standalone/.next/server/app/_global-error/page.js +4 -4
  8. package/.next/standalone/.next/server/app/_global-error/page.js.nft.json +1 -1
  9. package/.next/standalone/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
  10. package/.next/standalone/.next/server/app/_global-error.html +1 -1
  11. package/.next/standalone/.next/server/app/_global-error.meta +8 -0
  12. package/.next/standalone/.next/server/app/_global-error.rsc +7 -7
  13. package/.next/standalone/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +6 -6
  14. package/.next/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +7 -7
  15. package/.next/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  16. package/.next/standalone/.next/server/app/_not-found/page/build-manifest.json +3 -3
  17. package/.next/standalone/.next/server/app/_not-found/page/server-reference-manifest.json +1 -1
  18. package/.next/standalone/.next/server/app/_not-found/page.js +4 -4
  19. package/.next/standalone/.next/server/app/_not-found/page.js.nft.json +1 -1
  20. package/.next/standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  21. package/.next/standalone/.next/server/app/_not-found.html +1 -1
  22. package/.next/standalone/.next/server/app/_not-found.meta +8 -0
  23. package/.next/standalone/.next/server/app/_not-found.rsc +14 -14
  24. package/.next/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +14 -14
  25. package/.next/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +13 -13
  26. package/.next/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  27. package/.next/standalone/.next/server/app/api/audit/invite/route.js +4 -4
  28. package/.next/standalone/.next/server/app/api/audit/invite/route.js.nft.json +1 -1
  29. package/.next/standalone/.next/server/app/api/audit/run/route.js +4 -4
  30. package/.next/standalone/.next/server/app/api/audit/run/route.js.nft.json +1 -1
  31. package/.next/standalone/.next/server/app/api/audit/status/route.js +1 -1
  32. package/.next/standalone/.next/server/app/api/audit/status/route.js.nft.json +1 -1
  33. package/.next/standalone/.next/server/app/api/auth/login-request/route.js +2 -2
  34. package/.next/standalone/.next/server/app/api/auth/login-request/route.js.nft.json +1 -1
  35. package/.next/standalone/.next/server/app/api/auth/login-verify/route.js +4 -4
  36. package/.next/standalone/.next/server/app/api/auth/login-verify/route.js.nft.json +1 -1
  37. package/.next/standalone/.next/server/app/api/auth/logout/route.js +3 -3
  38. package/.next/standalone/.next/server/app/api/auth/logout/route.js.nft.json +1 -1
  39. package/.next/standalone/.next/server/app/api/auth/status/route.js +3 -3
  40. package/.next/standalone/.next/server/app/api/auth/status/route.js.nft.json +1 -1
  41. package/.next/standalone/.next/server/app/api/download/[project]/[session]/route.js +1 -1
  42. package/.next/standalone/.next/server/app/api/download/[project]/[session]/route.js.nft.json +1 -1
  43. package/.next/standalone/.next/server/app/audit/page/build-manifest.json +3 -3
  44. package/.next/standalone/.next/server/app/audit/page/server-reference-manifest.json +2 -2
  45. package/.next/standalone/.next/server/app/audit/page.js +4 -4
  46. package/.next/standalone/.next/server/app/audit/page.js.nft.json +1 -1
  47. package/.next/standalone/.next/server/app/audit/page_client-reference-manifest.js +1 -1
  48. package/.next/standalone/.next/server/app/index.html +1 -1
  49. package/.next/standalone/.next/server/app/index.meta +8 -0
  50. package/.next/standalone/.next/server/app/index.rsc +14 -14
  51. package/.next/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +13 -13
  52. package/.next/standalone/.next/server/app/index.segments/_full.segment.rsc +14 -14
  53. package/.next/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  54. package/.next/standalone/.next/server/app/page/build-manifest.json +3 -3
  55. package/.next/standalone/.next/server/app/page/server-reference-manifest.json +1 -1
  56. package/.next/standalone/.next/server/app/page.js +4 -4
  57. package/.next/standalone/.next/server/app/page.js.nft.json +1 -1
  58. package/.next/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
  59. package/.next/standalone/.next/server/app/policies/page/build-manifest.json +3 -3
  60. package/.next/standalone/.next/server/app/policies/page/server-reference-manifest.json +14 -14
  61. package/.next/standalone/.next/server/app/policies/page.js +8 -8
  62. package/.next/standalone/.next/server/app/policies/page.js.nft.json +1 -1
  63. package/.next/standalone/.next/server/app/policies/page_client-reference-manifest.js +1 -1
  64. package/.next/standalone/.next/server/app/project/[name]/page/build-manifest.json +3 -3
  65. package/.next/standalone/.next/server/app/project/[name]/page/server-reference-manifest.json +1 -1
  66. package/.next/standalone/.next/server/app/project/[name]/page.js +5 -5
  67. package/.next/standalone/.next/server/app/project/[name]/page.js.nft.json +1 -1
  68. package/.next/standalone/.next/server/app/project/[name]/page_client-reference-manifest.js +1 -1
  69. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page/build-manifest.json +3 -3
  70. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page/react-loadable-manifest.json +2 -2
  71. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page/server-reference-manifest.json +2 -2
  72. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page.js +5 -5
  73. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page.js.nft.json +1 -1
  74. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page_client-reference-manifest.js +1 -1
  75. package/.next/standalone/.next/server/app/projects/page/build-manifest.json +3 -3
  76. package/.next/standalone/.next/server/app/projects/page/server-reference-manifest.json +1 -1
  77. package/.next/standalone/.next/server/app/projects/page.js +5 -5
  78. package/.next/standalone/.next/server/app/projects/page.js.nft.json +1 -1
  79. package/.next/standalone/.next/server/app/projects/page_client-reference-manifest.js +1 -1
  80. package/.next/standalone/.next/server/app/settings/page/build-manifest.json +3 -3
  81. package/.next/standalone/.next/server/app/settings/page/server-reference-manifest.json +8 -8
  82. package/.next/standalone/.next/server/app/settings/page.js +9 -9
  83. package/.next/standalone/.next/server/app/settings/page.js.nft.json +1 -1
  84. package/.next/standalone/.next/server/app/settings/page_client-reference-manifest.js +1 -1
  85. package/.next/standalone/.next/server/chunks/{[externals]__0qxhecv._.js → [externals]__0d7i9ea._.js} +2 -2
  86. package/.next/standalone/.next/server/chunks/{[externals]__0sg9-3p._.js → [externals]__0hjaw70._.js} +1 -1
  87. package/.next/standalone/.next/server/chunks/[externals]__1_9ze9b._.js +3 -0
  88. package/.next/standalone/.next/server/chunks/[externals]__1oenfmn._.js +3 -0
  89. package/.next/standalone/.next/server/chunks/[root-of-the-server]__08dchfu._.js +1 -1
  90. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0ga9hs7._.js +3 -0
  91. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0l3yhx4._.js +2 -2
  92. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0moxohc._.js +22 -0
  93. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0o07qi9._.js +1 -1
  94. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0z2ll-a._.js +3 -0
  95. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1adacul._.js +11 -11
  96. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1autwno._.js +3 -0
  97. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1v25n-6._.js +3 -0
  98. package/.next/standalone/.next/server/chunks/_09dz7xv._.js +1 -1
  99. package/.next/standalone/.next/server/chunks/_0tovk6q._.js +1 -1
  100. package/.next/standalone/.next/server/chunks/_0trp3yc._.js +1 -1
  101. package/.next/standalone/.next/server/chunks/_1ek68ln._.js +2 -2
  102. package/.next/standalone/.next/server/chunks/_1fz8m31._.js +1 -1
  103. package/.next/standalone/.next/server/chunks/_1jrv3hg._.js +3 -0
  104. package/.next/standalone/.next/server/chunks/_1nx8fim._.js +3 -0
  105. package/.next/standalone/.next/server/chunks/_1ogwigf._.js +1 -1
  106. package/.next/standalone/.next/server/chunks/{_1q5i8mb._.js → _1q27azx._.js} +2 -2
  107. package/.next/standalone/.next/server/chunks/lib_telemetry_ts_0dctyyw._.js +1 -1
  108. package/.next/standalone/.next/server/chunks/{node_modules_posthog-node_dist_entrypoints_index_node_mjs_0el6rg8._.js → node_modules_posthog-node_dist_entrypoints_index_node_mjs_0ol5pa3._.js} +2 -2
  109. package/.next/standalone/.next/server/chunks/{node_modules_posthog-node_dist_entrypoints_index_node_mjs_04op_ou._.js → node_modules_posthog-node_dist_entrypoints_index_node_mjs_1rqmk27._.js} +2 -2
  110. package/.next/standalone/.next/server/chunks/{node_modules_posthog-node_dist_entrypoints_index_node_mjs_0dr20uz._.js → node_modules_posthog-node_dist_entrypoints_index_node_mjs_1w7tiq4._.js} +2 -2
  111. package/.next/standalone/.next/server/chunks/package_json_[json]_cjs_1nxcc4v._.js +1 -1
  112. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__04usis8._.js +2 -2
  113. package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__1-bm5zf._.js → [root-of-the-server]__0502lat._.js} +1 -1
  114. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0_gn5iz._.js +4 -0
  115. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0f765xr._.js +4 -0
  116. package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__01bmjsj._.js → [root-of-the-server]__0h38gzz._.js} +1 -1
  117. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0hoaz1g._.js +4 -0
  118. package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__1ouobn4._.js → [root-of-the-server]__0mxmwtx._.js} +1 -1
  119. package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__19evfi8._.js → [root-of-the-server]__0uescqv._.js} +1 -1
  120. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1484zv2._.js +3 -0
  121. package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__1mf3zp6._.js → [root-of-the-server]__19dutr7._.js} +4 -4
  122. package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__0oqrl71._.js → [root-of-the-server]__1_qv8oh._.js} +2 -2
  123. package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__1u6sn69._.js → [root-of-the-server]__1ab4ifb._.js} +1 -1
  124. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1fbrk56._.js +4 -0
  125. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1pprgri._.js +2 -2
  126. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1um4yxn._.js +4 -0
  127. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1wb4yt2._.js +4 -0
  128. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1y3n3cn._.js +4 -0
  129. package/.next/standalone/.next/server/chunks/ssr/_08x1r5t._.js +1 -1
  130. package/.next/standalone/.next/server/chunks/ssr/_0bqoto4._.js +1 -1
  131. package/.next/standalone/.next/server/chunks/ssr/{_16pa9ht._.js → _0lnpwk8._.js} +2 -2
  132. package/.next/standalone/.next/server/chunks/ssr/{_0yq5_3a._.js → _15y8myg._.js} +1 -1
  133. package/.next/standalone/.next/server/chunks/ssr/_1gb0ifp._.js +2 -2
  134. package/.next/standalone/.next/server/chunks/ssr/{_042cgl1._.js → _1o95rf0._.js} +2 -2
  135. package/.next/standalone/.next/server/chunks/ssr/{_1zopuov._.js → _1thr5rf._.js} +2 -2
  136. package/.next/standalone/.next/server/chunks/ssr/_next-internal_server_app_policies_page_actions_1sp2-yo.js +12 -14
  137. package/.next/standalone/.next/server/chunks/ssr/app_actions_get-scheduled-audit_ts_0ei9sni._.js +1 -1
  138. package/.next/standalone/.next/server/chunks/ssr/app_audit__components_audit-dashboard_tsx_0p9ud47._.js +1 -1
  139. package/.next/standalone/.next/server/chunks/ssr/app_global-error_tsx_1kp6l3x._.js +1 -1
  140. package/.next/standalone/.next/server/chunks/ssr/app_policies_hooks-client_tsx_19dqvpc._.js +1 -1
  141. package/.next/standalone/.next/server/chunks/ssr/app_settings_02tf1h4._.js +1 -1
  142. package/.next/standalone/.next/server/chunks/ssr/node_modules_1hl81y4._.js +1 -1
  143. package/.next/standalone/.next/server/chunks/ssr/node_modules_next_dist_0nkkbfv._.js +6 -0
  144. package/.next/standalone/.next/server/chunks/ssr/src_hooks_fp-config_ts_04t589g._.js +1 -1
  145. package/.next/standalone/.next/server/chunks/ssr/src_hooks_fp-home_ts_0je3xkv._.js +1 -1
  146. package/.next/standalone/.next/server/middleware-build-manifest.js +6 -6
  147. package/.next/standalone/.next/server/middleware.js +1 -1
  148. package/.next/standalone/.next/server/pages/404.html +1 -1
  149. package/.next/standalone/.next/server/pages/500.html +1 -1
  150. package/.next/standalone/.next/server/server-reference-manifest.js +1 -1
  151. package/.next/standalone/.next/server/server-reference-manifest.json +23 -23
  152. package/.next/standalone/.next/static/chunks/{03h3b-uomi7_d.js → 0pra9i676pym1.js} +1 -1
  153. package/.next/standalone/.next/static/chunks/{2kj826h1w9r47.js → 1-lpi0m-hc1kb.js} +1 -1
  154. package/.next/standalone/.next/static/chunks/{3ob2161ap37ul.js → 17yvyyblr_eb5.js} +1 -1
  155. package/.next/standalone/.next/static/chunks/{3nkc2jlgyygda.js → 1mylcr99gccyz.js} +1 -1
  156. package/.next/standalone/.next/static/chunks/{2j92xul6y24_v.js → 1wozchtn2etvh.js} +1 -1
  157. package/.next/standalone/.next/static/chunks/{3mjfvng11xnoo.js → 23thtelz1a-ur.js} +2 -2
  158. package/.next/standalone/.next/static/chunks/{1fqukzzowjflt.js → 2bc17tohgs4bs.js} +1 -1
  159. package/.next/standalone/.next/static/chunks/{3u1s8dsqiean5.js → 2xq-ejo8kti18.js} +1 -1
  160. package/.next/standalone/.next/static/chunks/3-_pth_c9n938.js +1 -0
  161. package/.next/standalone/.next/static/chunks/{3i8rg2fv9nd6e.js → 3n46e45atic-m.js} +1 -1
  162. package/.next/standalone/.next/static/chunks/{078dofaxvjgpd.js → 3q7sndqftqhw9.js} +1 -1
  163. package/.next/standalone/.next/static/chunks/{0542cuk_va8g1.js → 3sg07wnn969xq.js} +1 -1
  164. package/.next/standalone/.next/static/chunks/{turbopack-11dn2pegmg9_9.js → turbopack-1opxka4dtjbc5.js} +1 -1
  165. package/.next/standalone/app/actions/install-hooks-web.ts +10 -2
  166. package/.next/standalone/hermes-plugin/plugin.yaml +1 -1
  167. package/.next/standalone/node_modules/@next/env/package.json +1 -1
  168. package/.next/standalone/node_modules/next/dist/build/swc/index.js +1 -1
  169. package/.next/standalone/node_modules/next/dist/compiled/hash.js/sha256/256.js +1 -0
  170. package/.next/standalone/node_modules/next/dist/compiled/hash.js/sha256/package.json +1 -0
  171. package/.next/standalone/node_modules/next/dist/compiled/next-server/app-page-turbo-experimental.runtime.prod.js +17 -17
  172. package/.next/standalone/node_modules/next/dist/compiled/next-server/app-page-turbo.runtime.prod.js +18 -18
  173. package/.next/standalone/node_modules/next/dist/compiled/next-server/app-route-turbo.runtime.prod.js +3 -3
  174. package/.next/standalone/node_modules/next/dist/compiled/next-server/pages-turbo.runtime.prod.js +13 -13
  175. package/.next/standalone/node_modules/next/dist/lib/patch-incorrect-lockfile.js +3 -3
  176. package/.next/standalone/node_modules/next/dist/server/base-server.js +1 -2
  177. package/.next/standalone/node_modules/next/dist/server/config.js +1 -1
  178. package/.next/standalone/node_modules/next/dist/server/dev/hot-reloader-turbopack.js +2 -2
  179. package/.next/standalone/node_modules/next/dist/server/dev/hot-reloader-webpack.js +1 -1
  180. package/.next/standalone/node_modules/next/dist/server/image-optimizer.js +265 -53
  181. package/.next/standalone/node_modules/next/dist/server/lib/app-info-log.js +1 -1
  182. package/.next/standalone/node_modules/next/dist/server/lib/incremental-cache/file-system-cache.js +177 -19
  183. package/.next/standalone/node_modules/next/dist/server/lib/incremental-cache/index.js +36 -17
  184. package/.next/standalone/node_modules/next/dist/server/lib/incremental-cache/shared-cache-controls.external.js +14 -6
  185. package/.next/standalone/node_modules/next/dist/server/lib/match-next-data-pathname.js +3 -1
  186. package/.next/standalone/node_modules/next/dist/server/lib/node-fs-methods.js +14 -0
  187. package/.next/standalone/node_modules/next/dist/server/lib/route-cache-key.js +77 -0
  188. package/.next/standalone/node_modules/next/dist/server/lib/router-utils/block-cross-site-dev.js +9 -11
  189. package/.next/standalone/node_modules/next/dist/server/lib/start-server.js +1 -1
  190. package/.next/standalone/node_modules/next/dist/server/mcp/get-mcp-middleware.js +4 -2
  191. package/.next/standalone/node_modules/next/dist/server/next-server.js +7 -3
  192. package/.next/standalone/node_modules/next/dist/server/response-cache/index.js +32 -16
  193. package/.next/standalone/node_modules/next/dist/server/route-matcher-providers/app-page-route-matcher-provider.js +2 -2
  194. package/.next/standalone/node_modules/next/dist/server/route-modules/pages/pages-handler.js +6 -1
  195. package/.next/standalone/node_modules/next/dist/server/route-modules/route-module.js +9 -2
  196. package/.next/standalone/node_modules/next/dist/server/use-cache/use-cache-wrapper.js +30 -29
  197. package/.next/standalone/node_modules/next/dist/shared/lib/router/utils/app-paths.js +18 -1
  198. package/.next/standalone/node_modules/next/dist/telemetry/anonymous-meta.js +1 -1
  199. package/.next/standalone/node_modules/next/dist/telemetry/events/swc-load-failure.js +1 -1
  200. package/.next/standalone/node_modules/next/dist/telemetry/events/version.js +2 -2
  201. package/.next/standalone/node_modules/next/package.json +10 -10
  202. package/.next/standalone/package.json +14 -11
  203. package/.next/standalone/sdk/python/README.md +41 -0
  204. package/.next/standalone/sdk/python/docs/README.md +1 -0
  205. package/.next/standalone/sdk/python/docs/otel/README.md +36 -0
  206. package/.next/standalone/sdk/python/docs/otel/examples/agent_trace.py +30 -0
  207. package/.next/standalone/sdk/python/docs/otel/examples/quickstart.py +22 -0
  208. package/.next/standalone/sdk/python/failproofai_sdk/__init__.py +8 -0
  209. package/.next/standalone/sdk/python/failproofai_sdk/integrations/__init__.py +3 -0
  210. package/.next/standalone/sdk/python/failproofai_sdk/integrations/otel.py +189 -0
  211. package/.next/standalone/sdk/python/pyproject.toml +2 -1
  212. package/.next/standalone/sdk/python/tests/test_docs.py +16 -3
  213. package/.next/standalone/sdk/python/tests/test_integrations.py +1 -1
  214. package/.next/standalone/sdk/python/tests/test_otel.py +147 -0
  215. package/.next/standalone/sdk/python/tests/test_zero_dependencies.py +4 -1
  216. package/.next/standalone/sdk/python/uv.lock +258 -147
  217. package/.next/standalone/sdk/typescript/README.md +43 -0
  218. package/.next/standalone/sdk/typescript/integration/ai.test.ts +9 -2
  219. package/.next/standalone/sdk/typescript/package-lock.json +57 -5
  220. package/.next/standalone/sdk/typescript/package.json +2 -0
  221. package/.next/standalone/sdk/typescript/src/edge/index.ts +9 -0
  222. package/.next/standalone/sdk/typescript/src/index.ts +2 -0
  223. package/.next/standalone/sdk/typescript/src/integrations/index.ts +4 -1
  224. package/.next/standalone/sdk/typescript/src/integrations/otel.ts +204 -0
  225. package/.next/standalone/sdk/typescript/test/integrations.test.ts +1 -1
  226. package/.next/standalone/sdk/typescript/test/otel.test.ts +125 -0
  227. package/.next/standalone/server.js +1 -1
  228. package/bin/failproofai.mjs +653 -1077
  229. package/dist/cli.mjs +8781 -4050
  230. package/dist/worker.mjs +170 -62
  231. package/hermes-plugin/plugin.yaml +1 -1
  232. package/package.json +14 -11
  233. package/scripts/launch-facts.ts +157 -0
  234. package/scripts/launch-ready.ts +228 -0
  235. package/scripts/launch-screen.ts +202 -0
  236. package/scripts/launch.ts +106 -49
  237. package/scripts/skew-log-filter.ts +38 -0
  238. package/src/audit/cli-login.ts +39 -59
  239. package/src/audit/cli.ts +308 -228
  240. package/src/audit/index.ts +45 -2
  241. package/src/audit/schedule-cli.ts +83 -120
  242. package/src/audit/types.ts +31 -0
  243. package/src/hooks/agent-selection.ts +155 -0
  244. package/src/hooks/backfill-cli.ts +151 -137
  245. package/src/hooks/backfill-request.ts +149 -0
  246. package/src/hooks/cloud-connection.ts +1 -1
  247. package/src/hooks/cloud-enrollment-cli.ts +78 -28
  248. package/src/hooks/cloud-introspect.ts +3 -0
  249. package/src/hooks/collector-config.ts +10 -1
  250. package/src/hooks/config-status.ts +717 -0
  251. package/src/hooks/configure-wizard.ts +967 -988
  252. package/src/hooks/daemon-service.ts +16 -6
  253. package/src/hooks/did-you-mean.ts +185 -0
  254. package/src/hooks/flush-cli.ts +33 -7
  255. package/src/hooks/fp-config.ts +96 -0
  256. package/src/hooks/fp-home.ts +11 -0
  257. package/src/hooks/fp-reset.ts +102 -95
  258. package/src/hooks/handler.ts +4 -0
  259. package/src/hooks/harness-cli.ts +87 -138
  260. package/src/hooks/install-prompt.ts +38 -477
  261. package/src/hooks/integrations.ts +1 -1
  262. package/src/hooks/jev-cli.ts +183 -196
  263. package/src/hooks/manager.ts +554 -358
  264. package/src/hooks/onboarding-attempt.ts +11 -9
  265. package/src/hooks/otel-cli.ts +350 -0
  266. package/src/hooks/otel-output.ts +24 -0
  267. package/src/hooks/otel-writers.ts +215 -0
  268. package/src/hooks/pack-cli.ts +219 -204
  269. package/src/hooks/report-body.ts +50 -0
  270. package/src/hooks/safe-config-write.ts +4 -2
  271. package/src/hooks/session-pause-cli.ts +93 -73
  272. package/src/hooks/tui.ts +849 -389
  273. package/src/hooks/uninstall-cli.ts +136 -149
  274. package/src/hooks/untraced-note.ts +52 -0
  275. package/.next/standalone/.next/server/chunks/[externals]__19_pzeq._.js +0 -3
  276. package/.next/standalone/.next/server/chunks/[externals]__20kzpkf._.js +0 -3
  277. package/.next/standalone/.next/server/chunks/[root-of-the-server]__01eqny5._.js +0 -3
  278. package/.next/standalone/.next/server/chunks/[root-of-the-server]__05ja_05._.js +0 -3
  279. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0igxt32._.js +0 -3
  280. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0zyc_nm._.js +0 -3
  281. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1ttrwnd._.js +0 -22
  282. package/.next/standalone/.next/server/chunks/_0bs-v8p._.js +0 -3
  283. package/.next/standalone/.next/server/chunks/_185gyk9._.js +0 -3
  284. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__056wjo4._.js +0 -4
  285. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0n0xg95._.js +0 -4
  286. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0rwtwpm._.js +0 -4
  287. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0yrsbd_._.js +0 -3
  288. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__11mayhe._.js +0 -4
  289. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__15578wp._.js +0 -4
  290. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1m_svbe._.js +0 -4
  291. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1q4p5b8._.js +0 -4
  292. package/.next/standalone/.next/server/chunks/ssr/node_modules_next_dist_1n3w9lb._.js +0 -6
  293. package/.next/standalone/.next/static/chunks/1lyg_puttohbc.js +0 -1
  294. /package/.next/standalone/.next/static/{ECs8__hcNabYV58WfDh-3 → xtcpqBNBdjQVerPMkI2HI}/_buildManifest.js +0 -0
  295. /package/.next/standalone/.next/static/{ECs8__hcNabYV58WfDh-3 → xtcpqBNBdjQVerPMkI2HI}/_clientMiddlewareManifest.js +0 -0
  296. /package/.next/standalone/.next/static/{ECs8__hcNabYV58WfDh-3 → xtcpqBNBdjQVerPMkI2HI}/_ssgManifest.js +0 -0
@@ -12,7 +12,7 @@ import { copyFileSync, existsSync, mkdirSync, mkdtempSync, readFileSync, readdir
12
12
  import { basename, dirname, isAbsolute, join, relative, resolve, sep } from "node:path";
13
13
  import { INTEGRATION_TYPES } from "./types";
14
14
  import { PACK_COMMIT_RE, PACK_VERSION_RE } from "./pack-manifest";
15
- import { detectInstalledClis } from "./integrations";
15
+ import { detectInstalledClis, getIntegration } from "./integrations";
16
16
  import {
17
17
  packParamsProblem,
18
18
  parsePackIdentity,
@@ -57,8 +57,10 @@ import {
57
57
  note,
58
58
  nextStep,
59
59
  optsFor,
60
+ paint,
60
61
  rows as kitRows,
61
62
  rule,
63
+ screenKit,
62
64
  stack,
63
65
  table,
64
66
  title,
@@ -729,25 +731,23 @@ export async function runPolicyPicker(
729
731
  `\`policies ${action}\` with no name needs a terminal to show you the list.`,
730
732
  "From a script, name what you mean:",
731
733
  ` failproofai policies ${action} <policy-name>`,
732
- ` failproofai policies ${action} <owner>/<repo> [--policy a,b] [--category x,y] [--all]`,
734
+ // Pack-lane remove takes an id and no flags: it uninstalls the whole pack,
735
+ // so advertising add's selection flags here invited exactly that.
736
+ action === "remove"
737
+ ? " failproofai policies remove <pack-id>"
738
+ : " failproofai policies add <owner>/<repo> [--policy a,b] [--category x,y] [--all]",
733
739
  ]);
734
740
  }
735
741
 
736
742
  if (packs.length === 0) {
737
- return ok(
738
- stack(
739
- emptyState(
740
- {
741
- what: "No policies are installed yet.",
742
- hint: "Take ours, or anyone's:",
743
- cmd: "failproofai policies add FailproofAI/policies",
744
- },
745
- opts,
746
- ),
747
- note("Someone else's: failproofai policies add <owner>/<repo>", opts),
748
- note("Look first: failproofai policies show <owner>/<repo>", opts),
749
- ),
750
- );
743
+ // The same three doors the policies screen's empty state offers.
744
+ const kit = screenKit(opts);
745
+ return ok([
746
+ "No policies are installed yet.",
747
+ ` Turn on ours: ${kit.cmd("failproofai policies add FailproofAI/policies")}`,
748
+ ` Or anyone's: ${kit.cmd("failproofai policies add <owner>/<repo>")}`,
749
+ ` Look first: ${kit.cmd("failproofai policies show <owner>/<repo>")}`,
750
+ ]);
751
751
  }
752
752
 
753
753
  // One row per policy across every pack. The pack id leads the section heading
@@ -777,10 +777,9 @@ export async function runPolicyPicker(
777
777
 
778
778
  const before = new Set(rows.filter((r) => r.checked).map((r) => r.value));
779
779
  const picked = await multiSelect<string>({
780
- message: "Which policies should be on?",
780
+ message: "Choose policies",
781
781
  choices: rows,
782
782
  summaryNoun: "policies",
783
- hint: "space toggles · ctrl+a all · ↵ confirm · what is on now is ticked",
784
783
  stdin,
785
784
  stdout,
786
785
  });
@@ -2244,7 +2243,6 @@ async function publish(rest: string[]): Promise<PackCliResult> {
2244
2243
  );
2245
2244
  let version =
2246
2245
  flag("version") ?? (entry ? inferTaggedVersion(entry, bundledSources) : null) ?? undefined;
2247
- let versionFromSha = false;
2248
2246
  // The id and the repo are usually the same words, and requiring both is asking
2249
2247
  // the same question twice. Either one alone answers for the other.
2250
2248
  const dryRun = rest.includes("--dry-run") || !repo;
@@ -2319,7 +2317,6 @@ async function publish(rest: string[]): Promise<PackCliResult> {
2319
2317
  const resolved = versionForPublish(provenance);
2320
2318
  if ("error" in resolved) return fail(resolved.error);
2321
2319
  version = resolved.version;
2322
- versionFromSha = true;
2323
2320
  }
2324
2321
 
2325
2322
  // Check an EXPLICIT tag against the version before anything reaches the
@@ -2365,16 +2362,18 @@ async function publish(rest: string[]): Promise<PackCliResult> {
2365
2362
  // A repository this command creates is made public, so this only ever
2366
2363
  // fires on reuse of one that was already private.
2367
2364
  if (repoInfo.json?.private === true && !allowPrivate) {
2368
- return fail([
2369
- `${repo} is PRIVATE, so nothing was published.`,
2370
- "Installing a pack fetches its release assets over anonymous HTTPS with no",
2371
- "credential to offer, so every install would 404 — including your own from",
2372
- "another machine. Publishing would only have attached three assets",
2373
- "advertising a route nobody can take.",
2374
- `Make it public and re-run: gh repo edit ${repo} --visibility public`,
2375
- "Or, to keep it private and hand the pack over some other way, publish it",
2376
- "with --allow-private.",
2377
- ]);
2365
+ // The reason is the install, not the privacy: it fetches over anonymous
2366
+ // HTTPS with no credential to offer, so it 404s even for the author on
2367
+ // another machine, and publishing would only attach three assets
2368
+ // advertising a route nobody can take.
2369
+ return fail(
2370
+ screenKit(optsFor(process.stderr)).notice(
2371
+ "fail",
2372
+ `${repo} is private, so nothing was published: installs fetch over anonymous HTTPS, so every one would 404.\n` +
2373
+ `Make it public and publish again: \`gh repo edit ${repo} --visibility public\`\n` +
2374
+ "Or keep it private and hand the pack over yourself: `--allow-private`",
2375
+ ),
2376
+ );
2378
2377
  }
2379
2378
  }
2380
2379
  const tag = flag("tag") ?? version;
@@ -2528,36 +2527,46 @@ async function publish(rest: string[]): Promise<PackCliResult> {
2528
2527
  uploaded.push(asset);
2529
2528
  }
2530
2529
 
2530
+ // The design's done screen: one `✓` per thing that happened, then how to
2531
+ // install it. The policy count comes from the build; a pack of Jev checks
2532
+ // alone has no regex policies to count, and says so in its reminder.
2533
+ const kit = screenKit(optsFor(process.stdout));
2534
+ const policies = built.meta?.policies ?? 0;
2535
+ const counted = policies > 0 ? ` with ${policies} ${policies === 1 ? "policy" : "policies"}` : "";
2536
+ // Where it went, only when the id and version do not already say it: `--id`
2537
+ // can differ from the repository, and `--tag v1.2.0` from version 1.2.0.
2538
+ const where = repo === id && tag === version ? "" : ` to ${repo} at tag ${tag}`;
2531
2539
  const lines = [
2540
+ kit.header("Publish"),
2541
+ "",
2532
2542
  // What was done to the user's own directory, before what was done to
2533
2543
  // GitHub. A side effect on somebody's working tree is never silent, even
2534
2544
  // when it is the side effect they wanted.
2535
- ...gitLines,
2536
- ...(gitLines.length ? [""] : []),
2537
- ...bundleNote,
2538
- ...(created ? [`Created ${repo} (public).`] : []),
2539
- `Published ${id}@${version} to ${repo} at tag ${tag}.` +
2540
- (versionFromSha ? " That names the commit it was built from." : ""),
2541
- ` ${assets.length} assets attached`,
2542
- ...(built.meta?.semanticOnly ? JEV_ONLY_ROLLBACK_REMINDER : []),
2543
- "",
2544
- // The install lines are the whole point of the success message, and on a
2545
- // private repository they are a lie — `policies add` 404s there. Print the
2546
- // warning INSTEAD of them, never underneath them: a reader who copies the
2547
- // first command they see must not be copying one that cannot work.
2548
- ...(isPrivate
2545
+ ...gitLines.map((line) => kit.ok(line)),
2546
+ ...(discovered.length > 1
2549
2547
  ? [
2550
- `WARNING: ${repo} is PRIVATE, so nobody can install this — you asked for`,
2551
- "that with --allow-private. Installs are anonymous HTTPS with no credential",
2552
- "to offer, so every `failproofai policies add` will 404. Distribute the",
2553
- "three assets yourself, or make it public:",
2554
- ` gh repo edit ${repo} --visibility public`,
2548
+ kit.ok(
2549
+ `Bundled ${discovered.length} files into one artifact: ` +
2550
+ discovered.map((f) => f.replace(process.cwd() + "/", "")).join(", "),
2551
+ ),
2555
2552
  ]
2556
- : [
2557
- "Anyone can now install it:",
2558
- ` failproofai policies add ${repo}`,
2559
- ` failproofai policies show ${repo} (look first, without running it)`,
2560
- ]),
2553
+ : []),
2554
+ ...(created ? [kit.ok(`Created ${repo} (public).`)] : []),
2555
+ kit.ok(`Published ${id}@${version}${counted}${where}.`),
2556
+ // The install line is the whole point of the success message, and on a
2557
+ // private repository it is a lie — `policies add` 404s there. Print the
2558
+ // warning INSTEAD of it, never underneath it: a reader who copies the
2559
+ // first command they see must not be copying one that cannot work.
2560
+ ...(isPrivate
2561
+ ? kit.notice(
2562
+ "caution",
2563
+ `${repo} is private, and installs fetch over anonymous HTTPS, so every install will fail.\n` +
2564
+ `Share the three assets yourself, or make it public: \`gh repo edit ${repo} --visibility public\``,
2565
+ )
2566
+ : [` Anyone can install it: ${kit.cmd(`failproofai policies add ${repo}`)}`]),
2567
+ ...(built.meta?.semanticOnly
2568
+ ? kit.notice("caution", JEV_ONLY_ROLLBACK_REMINDER.map((line) => line.trim()).join("\n"))
2569
+ : []),
2561
2570
  ];
2562
2571
  return ok(lines);
2563
2572
  }
@@ -2612,7 +2621,7 @@ async function pickClis(
2612
2621
  ): Promise<string[] | null> {
2613
2622
  const detected = new Set(detectInstalledClis());
2614
2623
  const choices: MultiChoice<string>[] = INTEGRATION_TYPES.map((id) => ({
2615
- label: id,
2624
+ label: getIntegration(id).displayName,
2616
2625
  value: id,
2617
2626
  checked: true,
2618
2627
  hint: detected.has(id) ? "installed here" : "",
@@ -2621,8 +2630,9 @@ async function pickClis(
2621
2630
  message: "Which agents should this pack guard?",
2622
2631
  choices,
2623
2632
  minSelected: 1,
2633
+ minMessage: "Pick at least one agent.",
2624
2634
  summaryNoun: "agents",
2625
- hint: `space toggles · ctrl+a all · ↵ confirm · ${detected.size} detected on this machine`,
2635
+ meta: (checked) => `${checked.length} of ${choices.length} selected · ${detected.size} found on this machine`,
2626
2636
  stdin: io.stdin,
2627
2637
  stdout: io.stdout,
2628
2638
  });
@@ -2710,21 +2720,18 @@ async function pickFromSource(
2710
2720
  });
2711
2721
  }
2712
2722
  }
2713
- const on = preview.policies.filter((p) => p.defaultEnabled).length;
2714
- // Said BEFORE the list, because the list cannot say it: a pack's Jev checks are
2715
- // not rows here, and they arrive whole. Somebody ticking boxes should know
2716
- // that is part of what they are agreeing to.
2723
+ const kit = screenKit(optsFor(io.stdout));
2724
+ const lead = ["", kit.header(`Add ${preview.id}@${preview.version}`), ""];
2725
+ // Said BEFORE the list because the list cannot say it: Jev checks are not
2726
+ // rows here, and they arrive whole with the pack.
2717
2727
  if (preview.semantic.length > 0) {
2718
- io.stdout.write(
2719
- `\n This pack also brings ${semanticPhrase(preview.semantic.length)} (not selectable), ` +
2720
- `${howJevAsksChecks(preview)}.\n See: failproofai policies show ${source}\n\n`,
2721
- );
2728
+ lead.push(`Also brings ${semanticPhrase(preview.semantic.length)}, ${howJevAsksChecks(preview)}.`, "");
2722
2729
  }
2730
+ io.stdout.write(lead.join("\n") + "\n");
2723
2731
  const picked = await multiSelect<string>({
2724
- message: `${preview.id}@${preview.version} — which of these should be on?`,
2732
+ message: "Choose policies",
2725
2733
  choices: rows,
2726
2734
  summaryNoun: "policies",
2727
- hint: `space toggles · ctrl+a all · ↵ confirm · ${on} of ${rows.length} are the publisher's defaults`,
2728
2735
  stdin: io.stdin,
2729
2736
  stdout: io.stdout,
2730
2737
  });
@@ -2798,29 +2805,15 @@ async function add(rest: string[]): Promise<PackCliResult> {
2798
2805
 
2799
2806
  try {
2800
2807
  const result = await addPack(resolvedSource!, selection);
2801
- const lines = [
2802
- ...(result.replaced?.length
2803
- ? [
2804
- // What was OBSERVED, and then the inference — not the inference
2805
- // stated as fact. Identical artifact bytes are the only evidence
2806
- // there is, and they cannot tell a renamed pack from a second pack
2807
- // one repository builds from the same source. Absorbing is the
2808
- // right default (a publisher renaming a set must not reset everyone
2809
- // who narrowed it), but the record that goes away is one the user
2810
- // installed on purpose, so the line has to say what to do when the
2811
- // guess was wrong instead of asserting it was right.
2812
- `Replaced ${result.replaced.join(", ")} — same artifact, so it was taken as this pack renamed, and your selection was carried over.`,
2813
- `If ${result.replaced.length > 1 ? "those are separate packs" : "that is a separate pack"}, re-add ${result.replaced.join(", ")} to restore ${result.replaced.length > 1 ? "them" : "it"}.`,
2814
- ]
2815
- : []),
2816
- result.resolvedFromLatest
2817
- ? `Installed ${result.id}@${result.version} from ${result.source} (newest release; pinned to ${result.tag})`
2818
- : `Installed ${result.id}@${result.version} from ${result.source}`,
2819
- ];
2808
+ const kit = screenKit(optsFor(process.stdout));
2809
+ const ref = `${result.id}@${result.version}`;
2820
2810
  const skipped = result.available.filter((n) => !result.enabled.includes(n));
2811
+ const policies = (n: number): string => `${n} ${n === 1 ? "policy" : "policies"}`;
2821
2812
 
2822
2813
  // Say WHY this set is on. Without it, "10 of 39 enabled" reads like
2823
- // something went wrong rather than like the pack's own recommendation.
2814
+ // something went wrong rather than like the pack's own recommendation. Five
2815
+ // distinct reasons, because a reader who cannot tell them apart cannot tell
2816
+ // whose decision put these policies on their machine.
2824
2817
  const why = {
2825
2818
  defaults: "the pack's defaults",
2826
2819
  selected: "your selection",
@@ -2831,54 +2824,85 @@ async function add(rest: string[]): Promise<PackCliResult> {
2831
2824
  added: "what you added, plus what was already on",
2832
2825
  all: "everything in the pack",
2833
2826
  }[result.selection];
2834
- // `summarise([])` is the empty string, which would print a line ending in a
2835
- // colon and nothing else — the one outcome that most needs saying out loud,
2836
- // rendered as if something had gone missing.
2837
- lines.push(
2838
- result.enabled.length === 0
2839
- ? // "enforcing nothing" is only true when the pack brought nothing else.
2840
- // A pack of Jev checks alone enforces through the semantic tier with no
2841
- // regex policy enabled at all, and telling that user their install does
2842
- // nothing is worse than saying nothing.
2843
- ` enabled (0/${result.available.length}, ${why}): none` +
2844
- (result.semantic > 0 ? "" : " — the pack is installed and enforcing nothing")
2845
- : ` enabled (${result.enabled.length}/${result.available.length}, ${why}): ${summarise(result.enabled)}`,
2846
- );
2847
- // Counted separately, and without an on/off ratio, because there is none:
2848
- // a pack's Jev checks arrive whole, and a FailproofAI pack's REPLACE the ones
2849
- // this build ships (anyone else's are added to them).
2850
- // Silence here was the shape of the original bug — the manifest's semantic
2851
- // half was fetched, verified and then never written to `installed.json`, so
2852
- // the half somebody installed did nothing and nothing said so.
2853
- if (result.semantic > 0) {
2854
- lines.push(
2855
- ` ${semanticPhrase(result.semantic)}, ${howJevAsksChecks(result)}. ` +
2856
- "They apply only where you configured Jev (`failproofai jev status`).",
2827
+ // "Turned on N" only when this run turned them on. A re-add that carried
2828
+ // the machine's selection turned nothing on, a merge counts what was already
2829
+ // on, and a pack with nothing on — or nothing to turn on, a pack of Jev
2830
+ // checks alone — gets a sentence that says so instead of "Turned on 0".
2831
+ const headline =
2832
+ result.available.length === 0
2833
+ ? `Installed ${ref}.`
2834
+ : result.enabled.length === 0
2835
+ ? // "enforces nothing" is only true when the pack brought nothing else:
2836
+ // a pack's Jev checks enforce through the semantic tier with no regex
2837
+ // policy on at all.
2838
+ `Installed ${ref} with no policies on (${why})` +
2839
+ (result.semantic > 0 ? "." : ", so it enforces nothing.")
2840
+ : result.selection === "carried"
2841
+ ? `Kept ${policies(result.enabled.length)} on from ${ref}: ${why}.`
2842
+ : result.selection === "added"
2843
+ ? `${ref} has ${policies(result.enabled.length)} on: ${why}.`
2844
+ : `Turned on ${policies(result.enabled.length)} from ${ref}: ${why}.`;
2845
+
2846
+ // How many of each category are on, read back from what was just recorded:
2847
+ // the result carries category slugs, not which policy is in which.
2848
+ let perCategory = "";
2849
+ try {
2850
+ const recorded = readInstalledPacks().packs.find((p) => p.id === result.id);
2851
+ const on = new Set(result.enabled);
2852
+ const counts = new Map<string, number>();
2853
+ for (const policy of recorded?.policies ?? []) {
2854
+ if (on.has(policy.name)) counts.set(policy.category, (counts.get(policy.category) ?? 0) + 1);
2855
+ }
2856
+ perCategory = [...counts].map(([category, n]) => `${category.toLowerCase()} ${n}`).join(", ");
2857
+ } catch {
2858
+ // A count that cannot be read is left out, never guessed.
2859
+ }
2860
+
2861
+ // The facts only this outcome has, one line each.
2862
+ const facts: string[] = [];
2863
+ if (result.replaced?.length) {
2864
+ // What was OBSERVED, and then the inference — not the inference stated as
2865
+ // fact. Identical artifact bytes are the only evidence there is, and they
2866
+ // cannot tell a renamed pack from a second pack one repository builds from
2867
+ // the same source. Absorbing is the right default (a publisher renaming a
2868
+ // set must not reset everyone who narrowed it), but the record that goes
2869
+ // away is one the user installed on purpose, so the line says what to do
2870
+ // when the guess was wrong instead of asserting it was right.
2871
+ const many = result.replaced.length > 1;
2872
+ facts.push(
2873
+ `Replaced ${result.replaced.join(", ")}, the same artifact under another name, and kept your selection. ` +
2874
+ `If ${many ? "they are separate packs, re-add them" : "it is a separate pack, re-add it"}.`,
2857
2875
  );
2858
2876
  }
2877
+ // The pin made visible: re-running the same command later can install
2878
+ // something different, and that should not be a silent surprise.
2879
+ if (result.resolvedFromLatest) facts.push(`Pinned to ${result.source}, the newest release.`);
2880
+ // Counted separately, and without an on/off ratio, because there is none: a
2881
+ // pack's Jev checks arrive whole. Silence here was the shape of the original
2882
+ // bug — the manifest's semantic half was fetched, verified and then never
2883
+ // written to `installed.json`, so the half somebody installed did nothing
2884
+ // and nothing said so.
2885
+ if (result.semantic > 0) facts.push(`${semanticPhrase(result.semantic)}, ${howJevAsksChecks(result)}.`);
2859
2886
  // Nothing after this screen says it: the record keeps no unreadable minimum.
2860
- if (result.minCliVersionNote) lines.push(` ${result.minCliVersionNote}`);
2887
+ if (result.minCliVersionNote) facts.push(result.minCliVersionNote);
2861
2888
  // Which of its checks the resolver will leave out beside what is already
2862
2889
  // installed (the shared question budget, a reserved or contested name).
2863
2890
  // Otherwise said only in the hook log, on the first call that asks Jev.
2864
2891
  if (result.semantic > 0) {
2865
- for (const e of jevCheckWarnings(result.id)) lines.push(` ▲ ${e}`);
2892
+ for (const e of jevCheckWarnings(result.id)) facts.push(kit.caution(e));
2866
2893
  }
2867
2894
 
2895
+ const lines = [
2896
+ kit.header("Policies"),
2897
+ "",
2898
+ kit.ok(headline),
2899
+ ...(perCategory ? [` ${perCategory}`] : []),
2900
+ ...facts.map((fact) => ` ${fact}`),
2901
+ ];
2902
+ // The source as TYPED, never the manifest's id: the id is self-declared and
2903
+ // can name a repository the pack did not come from.
2868
2904
  if (skipped.length > 0) {
2869
- lines.push(` not enabled (${skipped.length}): ${summarise(skipped)}`);
2870
- lines.push("");
2871
- lines.push(` see all: failproofai policies`);
2872
- // Naming --policy here as well as the coarser two: it is the flag people
2873
- // reach for first ("just give me that one"), and it was only ever
2874
- // suggested on the branch that installed our own pack from disk — so
2875
- // every third-party pack told you about categories and everything, and
2876
- // never about taking a single policy.
2877
- lines.push(` one policy: failproofai policies add ${source} --policy ${skipped[0]}`);
2878
- if (result.categories.length > 0) {
2879
- lines.push(` by category: failproofai policies add ${source} --category ${result.categories.slice(0, 3).join(",")}`);
2880
- }
2881
- lines.push(` everything: failproofai policies add ${source} --all`);
2905
+ lines.push("", `Turn on the other ${skipped.length} with ${kit.cmd(`failproofai policies add ${source} --all`)}`);
2882
2906
  }
2883
2907
  return ok(lines);
2884
2908
  } catch (err) {
@@ -3001,20 +3025,15 @@ async function listReleases(source: string): Promise<PackCliResult> {
3001
3025
  return Number.NEGATIVE_INFINITY;
3002
3026
  };
3003
3027
  const releases = [...raw].sort((a, b) => when(b) - when(a));
3028
+ const kit = screenKit(opts);
3004
3029
  if (releases.length === 0) {
3005
- return ok(
3006
- stack(
3007
- title(`${spec.owner}/${spec.repo}`, "no releases", opts),
3008
- emptyState(
3009
- {
3010
- what: "This repository has published no releases, so there is nothing to install.",
3011
- hint: "Its author publishes one with:",
3012
- cmd: "failproofai publish",
3013
- },
3014
- opts,
3015
- ),
3016
- ),
3017
- );
3030
+ return ok([
3031
+ kit.header(`${spec.owner}/${spec.repo}`),
3032
+ "",
3033
+ kit.head("Releases", "no releases"),
3034
+ "This repository has published no releases, so there is nothing to install.",
3035
+ ` Its author publishes one with: ${kit.cmd("failproofai publish")}`,
3036
+ ]);
3018
3037
  }
3019
3038
 
3020
3039
  // Which of them is on THIS machine — the question somebody runs this to
@@ -3073,26 +3092,22 @@ async function listReleases(source: string): Promise<PackCliResult> {
3073
3092
  flags,
3074
3093
  ]);
3075
3094
  }
3076
- return ok(
3077
- stack(
3078
- title(`${spec.owner}/${spec.repo}`, `${rows.length} release${rows.length === 1 ? "" : "s"}`, opts),
3079
- table(
3080
- { head: ["version", "published", "commit", "policies", "default", ""], rows },
3081
- opts,
3082
- ),
3083
- // `—` is load-bearing: it means "this release did not say", which is what
3084
- // a release published before this format did, and what anybody else's
3085
- // hand-made release does. Filling those in would cost a manifest download
3086
- // each, and a listing that silently costs a hundred downloads is worse
3087
- // than one with gaps in it.
3088
- note("— means the release did not record it.", opts),
3089
- nextStep(
3090
- `failproofai policies add ${spec.owner}/${spec.repo}@${newest ?? "<tag>"}`,
3091
- "Install a particular one with:",
3092
- opts,
3093
- ),
3094
- ),
3095
- );
3095
+ return ok([
3096
+ kit.header(`${spec.owner}/${spec.repo}`),
3097
+ "",
3098
+ // The id stays in the header: `head` uppercases, and an id is case-sensitive.
3099
+ kit.head("Releases", `${rows.length} release${rows.length === 1 ? "" : "s"}`),
3100
+ // Columns, so it stays a table: the kit's rows are two-column.
3101
+ ...table({ head: ["version", "published", "commit", "policies", "default", ""], rows }, opts),
3102
+ // `—` is load-bearing: it means "this release did not say", which is what
3103
+ // a release published before this format did, and what anybody else's
3104
+ // hand-made release does. Filling those in would cost a manifest download
3105
+ // each, and a listing that silently costs a hundred downloads is worse than
3106
+ // one with gaps in it.
3107
+ ` ${paint(opts.color).ink3("— means the release did not record it.")}`,
3108
+ "",
3109
+ `Install a particular one with: ${kit.cmd(`failproofai policies add ${spec.owner}/${spec.repo}@${newest ?? "<tag>"}`)}`,
3110
+ ]);
3096
3111
  }
3097
3112
 
3098
3113
  /** `2 hours ago`, `6 days ago`. Coarse on purpose — the question is "how stale",
@@ -3237,10 +3252,7 @@ export function jevChecksSection(
3237
3252
  return [
3238
3253
  // The count is on the heading rather than in a row, the way every other
3239
3254
  // section on this screen carries its own total.
3240
- ...rule(
3241
- `Jev checks — ${pack.semantic.length} · not selectable · only where Jev is configured`,
3242
- opts,
3243
- ),
3255
+ screenKit(opts).head("Jev checks", `${pack.semantic.length} · not selectable · only where Jev is configured`),
3244
3256
  // The reviewers column is the only one allowed to shrink: a mode or a check
3245
3257
  // name cut in half is a fact nobody can act on, while a cut reviewer list is
3246
3258
  // one that was already abbreviated.
@@ -3274,52 +3286,55 @@ async function listRemote(source: string): Promise<PackCliResult> {
3274
3286
  return fail([`Could not read ${source}: ${err instanceof Error ? err.message : String(err)}`]);
3275
3287
  }
3276
3288
 
3277
- const defaults = preview.policies.filter((p) => p.defaultEnabled);
3289
+ const kit = screenKit({ ...opts, fit: Boolean(process.stdout.isTTY) });
3290
+ const count = (n: number, one: string, many: string): string => `${n} ${n === 1 ? one : many}`;
3278
3291
  const categories = [...new Set(preview.policies.map((p) => p.category))];
3279
- const rows: Array<string[] | { section: string }> = [];
3280
- for (const category of categories) {
3281
- const inCategory = preview.policies.filter((p) => p.category === category);
3282
- const on = inCategory.filter((p) => p.defaultEnabled).length;
3283
- rows.push({
3284
- section: `${category} — ${on}/${inCategory.length} on by default · ${slugifyCategory(category)}`,
3285
- });
3286
- for (const policy of inCategory) {
3287
- // The publisher's DEFAULTS, not a machine's state — this pack is not
3288
- // installed, so a row claiming ON would be describing nothing.
3289
- rows.push([
3290
- policy.defaultEnabled ? "default" : "opt-in",
3291
- policy.name,
3292
- policy.description,
3293
- ]);
3294
- }
3295
- }
3296
3292
 
3297
- return ok(
3298
- stack(
3299
- title(
3300
- `${preview.id}@${preview.version}`,
3301
- `${preview.policies.length} policies · ${categories.length} categories` +
3302
- (preview.semantic.length > 0 ? ` · ${semanticPhrase(preview.semantic.length)}` : ""),
3303
- opts,
3304
- ),
3305
- note(
3306
- `Not installed. ${defaults.length} of ${preview.policies.length} are on by default; the rest are opt-in.` +
3307
- (preview.effect === "observe" ? " This pack OBSERVES — it records and blocks nothing." : ""),
3308
- opts,
3293
+ // The PUBLISHER is the account the release was actually fetched from — the
3294
+ // owner in `github:<owner>/<repo>@<tag>` — never the id's owner segment,
3295
+ // which is whatever the manifest claims. No release date: knowing one would
3296
+ // take a GitHub API call this path does not make (decision D16).
3297
+ const kvRows: Array<[string, string]> = [["publisher", preview.source.replace(/^github:/i, "").split("/")[0]]];
3298
+ // Optional by design: a pack published from outside a git checkout has none.
3299
+ if (preview.commit) kvRows.push(["commit", preview.commit]);
3300
+ kvRows.push([
3301
+ "policies",
3302
+ preview.policies.length === 0
3303
+ ? "none"
3304
+ : `${preview.policies.length} in ${count(categories.length, "category", "categories")} ${kit.selected} on by default`,
3305
+ ]);
3306
+ if (preview.effect === "observe") kvRows.push(["effect", "observe, blocks nothing"]);
3307
+ if (preview.minCliVersion) kvRows.push(["requires", `failproofai ${preview.minCliVersion} or newer`]);
3308
+ if (preview.minCliVersionNote) kvRows.push(["requires", preview.minCliVersionNote]);
3309
+ if (preview.semantic.length > 0) kvRows.push(["jev checks", String(preview.semantic.length)]);
3310
+
3311
+ const lines = [kit.header(`${preview.id}@${preview.version}`), "", ...kit.kv(kvRows)];
3312
+ // ■ is the publisher's DEFAULT, not a machine's state: this pack is not
3313
+ // installed, so a row claiming ● on would be describing nothing.
3314
+ const column = Math.max(24, ...preview.policies.map((p) => p.name.length + 4));
3315
+ for (const category of categories) {
3316
+ lines.push(
3317
+ "",
3318
+ kit.head(category),
3319
+ ...kit.rows(
3320
+ preview.policies
3321
+ .filter((p) => p.category === category)
3322
+ .map((p): [string, string] => [`${p.defaultEnabled ? kit.selected : kit.unselected} ${p.name}`, p.description]),
3323
+ column,
3309
3324
  ),
3310
- preview.minCliVersion ? note(`Requires failproofai ${preview.minCliVersion} or newer.`, opts) : null,
3311
- preview.minCliVersionNote ? note(preview.minCliVersionNote, opts) : null,
3312
- preview.resolvedFromLatest ? note(`Newest release: ${preview.source}`, opts) : null,
3313
- table({ head: ["", "", ""], rows }, opts),
3314
- // The half the table above cannot hold: its own section, under the
3315
- // policies rather than above them, because a check is read against the
3316
- // policies it can clear and those are the rows just passed.
3317
- jevChecksSection(preview, opts),
3318
- preview.semantic.length > 0 ? jevCheckWarnings(preview.id, preview).flatMap((e) => warning([e], opts)) : null,
3319
- nextStep(`failproofai policies add ${source}`, "Install the defaults with:", opts),
3320
- note("Or take part of it: --policy <a,b>, --category <x,y>, --all", opts),
3321
- ),
3322
- );
3325
+ );
3326
+ }
3327
+ // The half the rows above cannot hold: its own section, under the policies
3328
+ // rather than above them, because a check is read against the policies it can
3329
+ // clear and those are the rows just passed.
3330
+ const jev = jevChecksSection(preview, opts);
3331
+ if (jev) lines.push("", ...jev);
3332
+ if (preview.semantic.length > 0) {
3333
+ const warnings = jevCheckWarnings(preview.id, preview);
3334
+ if (warnings.length > 0) lines.push("", ...warnings.map((e) => kit.caution(e)));
3335
+ }
3336
+ lines.push("", `Install with ${kit.cmd(`failproofai policies add ${source}`)}`);
3337
+ return ok(lines);
3323
3338
  }
3324
3339
 
3325
3340
  async function list(): Promise<PackCliResult> {
@@ -0,0 +1,50 @@
1
+ /**
2
+ * The body of an action report (`update`, `migrate`, `flush`, `connect`, …),
3
+ * laid out before colour: which line carries the failure's ✕, and how a
4
+ * wrapped line continues. bin/failproofai.mjs's `printReport` paints it.
5
+ *
6
+ * Kept apart from `tui.ts` so it can be tested without driving the binary.
7
+ */
8
+ import { wrap } from "./tui";
9
+
10
+ /** A line that opens on a state glyph says its own state. */
11
+ const STATE_GLYPH = /^\s*[✓▲✕] /;
12
+
13
+ /**
14
+ * Lay out `lines` for a report `width` columns wide.
15
+ *
16
+ * A failed run whose lines carry no state glyph of their own opens with ✕ on
17
+ * the first top-level line, and the top-level lines after it are the fix,
18
+ * indented under it. That guess is only right when the failure comes first.
19
+ * A report that marks its own lines — `update` opening on "no migration was
20
+ * needed" before a daemon's ✕, `migrate` listing its backup before the step
21
+ * that failed — gets no guess at all: a ✕ beside the line that succeeded
22
+ * reads as that step failing, and the real error indented beneath it reads as
23
+ * the remedy.
24
+ *
25
+ * Lines that already carry their own leading space are passed through: those
26
+ * are sub-items a module laid out on purpose.
27
+ */
28
+ export function reportBody(lines: readonly string[], opts: { ok: boolean; width: number }): string[] {
29
+ const autoLead = !opts.ok && !lines.some((line) => STATE_GLYPH.test(line));
30
+ const width = Math.max(20, opts.width);
31
+ const body: string[] = [];
32
+ let first = true;
33
+ for (const line of lines) {
34
+ if (line.trim() === "") {
35
+ body.push("");
36
+ continue;
37
+ }
38
+ if (line.startsWith(" ")) {
39
+ body.push(line);
40
+ continue;
41
+ }
42
+ const glyphed = STATE_GLYPH.test(line);
43
+ const lead = autoLead ? (first ? "✕ " : " ") : "";
44
+ // A glyphed line's continuation sits under its words, not under the glyph.
45
+ const hang = lead || glyphed ? " " : "";
46
+ wrap(line, width - lead.length).forEach((w, i) => body.push(i === 0 ? `${lead}${w}` : `${hang}${w}`));
47
+ first = false;
48
+ }
49
+ return body;
50
+ }