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
@@ -11,7 +11,7 @@ import { batchAll } from "../../lib/concurrency";
11
11
  import { BUILTIN_POLICIES } from "../hooks/builtin-policies";
12
12
  import { readMergedHooksConfig } from "../hooks/hooks-config";
13
13
  import { readInstalledPacks } from "../hooks/pack-manifest";
14
- import { normalizePolicyName } from "../hooks/policy-registry";
14
+ import { getAllPolicies, normalizePolicyName } from "../hooks/policy-registry";
15
15
  import { INTEGRATION_TYPES, type IntegrationType } from "../hooks/types";
16
16
  import { ADAPTERS } from "./cli-adapters";
17
17
  import { AUDIT_DETECTORS } from "./detectors";
@@ -22,6 +22,7 @@ import {
22
22
  AUDIT_EXAMPLE_MAX_CHARS,
23
23
  AUDIT_MAX_EXAMPLES_PER_NAME,
24
24
  type AuditCount,
25
+ type AuditProgress,
25
26
  type AuditResult,
26
27
  type DetectorSessionState,
27
28
  type NormalizedToolEvent,
@@ -443,6 +444,32 @@ async function runAuditInner(opts: RunAuditOptions, startedAt: number): Promise<
443
444
  allTranscripts.push(...list);
444
445
  }
445
446
 
447
+ // Progress is reported through this and nothing else, so a callback that
448
+ // throws can never reach the scan below — where a throw inside a task would
449
+ // count as a scan error and change the result.
450
+ const report = (progress: AuditProgress): void => {
451
+ try {
452
+ opts.onProgress?.(progress);
453
+ } catch {
454
+ // A progress display must never change what an audit finds.
455
+ }
456
+ };
457
+ if (opts.onProgress) {
458
+ const perAgent = new Map<IntegrationType, number>();
459
+ for (const meta of allTranscripts) perAgent.set(meta.cli, (perAgent.get(meta.cli) ?? 0) + 1);
460
+ report({
461
+ kind: "discovered",
462
+ agents: clis.map((cli) => ({ cli, transcripts: perAgent.get(cli) ?? 0 })),
463
+ // The builtins `initReplay` registered that can fire on a tool event. The
464
+ // require-*-before-stop policies match only Stop, which a replay never
465
+ // sends, so counting them would claim checks that never run.
466
+ policies: getAllPolicies().filter((p) => {
467
+ const events = p.match.events;
468
+ return !events || events.length === 0 || events.includes("PreToolUse") || events.includes("PostToolUse");
469
+ }).length,
470
+ });
471
+ }
472
+
446
473
  // 2. Scan each transcript (cache-aware), 8 in parallel.
447
474
  let skipped = 0;
448
475
  let errors = 0;
@@ -503,7 +530,23 @@ async function runAuditInner(opts: RunAuditOptions, startedAt: number): Promise<
503
530
  }
504
531
  });
505
532
 
506
- const settled = await batchAll(tasks, TRANSCRIPT_CONCURRENCY);
533
+ // With a listener, each task reports when it finishes, however it finishes:
534
+ // a cache hit, a scan, a failed scan, or a task that threw and is counted as
535
+ // skipped below. Every transcript counts toward its agent's total, so a bar
536
+ // can always reach the end. Without one, the tasks run exactly as they are.
537
+ const run = opts.onProgress
538
+ ? tasks.map((task, i) => async (): Promise<TranscriptAuditResult> => {
539
+ let hitsByName: Record<string, number> = {};
540
+ try {
541
+ const done = await task();
542
+ hitsByName = done.hitsByName;
543
+ return done;
544
+ } finally {
545
+ report({ kind: "transcript", cli: allTranscripts[i].cli, hitsByName });
546
+ }
547
+ })
548
+ : tasks;
549
+ const settled = await batchAll(run, TRANSCRIPT_CONCURRENCY);
507
550
  const perTranscript: TranscriptAuditResult[] = [];
508
551
  for (const s of settled) {
509
552
  if (s.status === "fulfilled") perTranscript.push(s.value);
@@ -31,20 +31,7 @@ import { readAuditSchedule } from "./audit-schedule";
31
31
  import { readDashboardCacheMeta } from "./dashboard-cache";
32
32
  import { readMachineIdentity } from "./machine-store";
33
33
  import { ensureSignedIn, invalidEmail, LoginError } from "./cli-login";
34
- import {
35
- ANSI_BOLD,
36
- ANSI_DIM,
37
- ANSI_RESET,
38
- brandAnsi,
39
- colorsEnabled,
40
- optsFor,
41
- outro,
42
- printBlock,
43
- rows as kitRows,
44
- stack,
45
- step,
46
- title,
47
- } from "../hooks/tui";
34
+ import { INDENT, optsFor, paint, printBlock, screenKit, stack, type TTYOut } from "../hooks/tui";
48
35
 
49
36
  /** Mirrors `fp-config`'s own bounds so the error can name them before writing. */
50
37
  const MIN_DAYS = 1;
@@ -53,19 +40,17 @@ const MAX_DAYS = 90;
53
40
  export class ScheduleCliError extends Error {}
54
41
 
55
42
  /**
56
- * Colour and the spine answer to ONE gate, checked at call time.
57
- *
58
- * They used to disagree: these helpers emitted ANSI unconditionally while the
59
- * frame asked `colorsEnabled`, so a piped `--status` came out as escape codes
60
- * with no structure — the worst of both. `colorsEnabled` is false off a TTY and
61
- * under `NO_COLOR`, which is exactly when a readout should be plain text.
43
+ * The 2026-10 kit for one stream, read at call time: colour only on a terminal
44
+ * and never under NO_COLOR, so a piped `--status` is plain text. The daemon
45
+ * warning goes to stderr, which can be a terminal when stdout is not, so it is
46
+ * asked about separately.
62
47
  */
63
- const styled = () => colorsEnabled(process.stdout);
64
- const wrap = (open: string, s: string) => (styled() ? `${open}${s}${ANSI_RESET}` : s);
65
- const pink = (s: string) => wrap(brandAnsi("pink"), s);
66
- const green = (s: string) => wrap(brandAnsi("guide"), s);
67
- const dim = (s: string) => wrap(ANSI_DIM, s);
68
- const bold = (s: string) => wrap(ANSI_BOLD, s);
48
+ const kitFor = (stream: TTYOut = process.stdout) => screenKit(optsFor(stream));
49
+
50
+ /** `every 7 days`, `every day`. */
51
+ function everyPhrase(days: number): string {
52
+ return days === 1 ? "every day" : `every ${days} days`;
53
+ }
69
54
 
70
55
  /**
71
56
  * Turn scheduled audits on, signing in first if needed.
@@ -123,64 +108,48 @@ export async function runScheduleOn(
123
108
  },
124
109
  });
125
110
  const interval = next.audit.intervalDays;
111
+ const k = kitFor();
126
112
 
127
- // Two rows rather than one long one: at 80 columns the combined sentence
128
- // wrapped, and a wrapped summary loses the spine on its second row.
129
- // The third row enumerates what leaves the machine, and it is not optional.
130
- // This is the ONLY opt-in path on the headless boxes the whole feature was
131
- // built for — the settings panel says "sends: counts, redacted examples, and
132
- // this machine's name" and argues in its own comment that a checkable list
133
- // beats a stronger claim, and that reasoning applies here at least as much.
134
- // The list is the real payload from `report-harm.ts`: machine id, hostname,
135
- // platform, the window bounds and the redacted examples.
136
- const summary = [
137
- `every ${interval} day${interval === 1 ? "" : "s"} · reports to ${user.email}`,
138
- "you only hear from it when a scan finds something harmful",
139
- "each report sends: finding counts, redacted example commands, this machine's name",
113
+ // The second line enumerates what leaves the machine, and it is not optional.
114
+ // `reportsConsentedAt`, stamped above, records that a person READ it before
115
+ // anything is sent, so it is part of the consent rather than fine print — the
116
+ // one explanatory line the redesign keeps (decision D6). This is the ONLY
117
+ // opt-in path on the headless boxes the whole feature was built for, and the
118
+ // settings panel argues in its own comment that a checkable list beats a
119
+ // stronger claim. The list is the real payload from `report-harm.ts`: machine
120
+ // id, hostname, platform, the window bounds and the redacted examples.
121
+ const result = [
122
+ k.ok(`Scheduled audits to report to ${user.email} ${everyPhrase(interval)}.`),
123
+ `${INDENT}${paint(optsFor(process.stdout).color).ink3("Each report sends finding counts, redacted example commands and this machine's name.")}`,
140
124
  ];
141
-
142
- if (prompted) {
143
- // A sign-in just drew the frame, so the result continues it and the `└`
144
- // closes both at once — rather than the frame ending and a loose line
145
- // appearing underneath.
146
- step("scheduled audits are on", summary);
147
- } else {
148
- // Nothing was asked, so nothing was a flow: a spine here would open a frame
149
- // that has no beginning.
150
- process.stdout.write(
151
- `\n${green("✓")} ${bold("scheduled audits are on")}\n` +
152
- summary.map((r) => ` ${dim(r)}\n`).join(""),
153
- );
154
- }
125
+ // A sign-in just drew this screen's header, so the result continues it;
126
+ // otherwise this is the screen, and it opens with its own.
127
+ process.stdout.write(`\n${(prompted ? result : [k.header("Audit"), "", ...result]).join("\n")}\n`);
155
128
 
156
129
  // The switch is config; whether anything RUNS is the daemon. Saying "on"
157
130
  // without checking would be the same "on but silent" state the settings panel
158
131
  // exists to make visible.
159
132
  warnIfDaemonWontRun();
160
133
 
161
- if (prompted) {
162
- outro("failproofai audit --status · when the next scan is due");
163
- } else {
164
- process.stdout.write(dim(`\n failproofai audit --status when the next scan is due\n\n`));
165
- }
134
+ process.stdout.write(`\nSee when the next scan runs: ${k.cmd("failproofai audit --status")}\n\n`);
166
135
  }
167
136
 
168
137
  export function runScheduleOff(): void {
169
138
  const before = readConfig().audit.auto;
170
139
  const next = updateConfig({ audit: { auto: false } });
140
+ const k = kitFor();
141
+ const say = (line: string) => printBlock(process.stdout, [k.header("Audit"), "", line]);
171
142
  if (next.audit.auto) {
172
- process.stdout.write("Could not turn scheduled audits off.\n");
143
+ say(k.fail("Could not turn scheduled audits off."));
173
144
  return;
174
145
  }
175
146
  if (!before) {
176
- process.stdout.write(dim("\nScheduled audits were already off.\n\n"));
147
+ say("Scheduled audits were already off.");
177
148
  return;
178
149
  }
179
- process.stdout.write(
180
- `\n${green("✓")} ${bold("scheduled audits are off")}\n` +
181
- ` ${dim("nothing runs on a timer and nothing is sent")}\n` +
182
- ` ${dim("your session is untouched — sign out from the dashboard for that")}\n\n`,
183
- );
150
+ // Nothing scans on a timer once this is off, so nothing is sent either. The
151
+ // sign-in is kept, so turning them back on costs no second code.
152
+ say(k.ok("Turned off scheduled audits."));
184
153
  }
185
154
 
186
155
  /**
@@ -200,93 +169,89 @@ export function runScheduleStatus(): void {
200
169
  const daemon = daemonServiceStatus();
201
170
 
202
171
  const on = config.audit.auto;
203
- const opts = optsFor(process.stdout);
172
+ const k = kitFor();
204
173
  const detail: Array<[string, string]> = [];
174
+ // One ▲ per distinct problem, the one that stops scans before the one that
175
+ // stops digests.
176
+ const problems: string[] = [];
205
177
 
206
178
  // The state first, because everything below is detail about a machine that is
207
179
  // either doing this or not, and reading the detail first answers a question
208
- // nobody has asked yet. It is a ROW like the rest, though: the headline used
209
- // to be hand-padded to a different column than `row()` used, which is why this
210
- // readout printed its first value at column 21 and every other at 18.
211
- detail.push([
212
- "scheduled audit",
213
- on
214
- ? `${green("on")}${dim(` every ${config.audit.intervalDays} days`)}`
215
- : dim("off"),
216
- ]);
180
+ // nobody has asked yet. It is a ROW like the rest, on the one column every
181
+ // row shares.
182
+ const every = everyPhrase(config.audit.intervalDays);
183
+ detail.push(["scans", on ? `${k.on} ${every[0].toUpperCase()}${every.slice(1)}` : `${k.off} Off`]);
217
184
 
218
185
  // A session whose refresh window has closed cannot mint another access token,
219
186
  // so it is a destination in name only. Showing the address for one would tell
220
187
  // somebody their digests are going somewhere they are not.
221
188
  const live = auth && auth.refresh_expires_at * 1000 > Date.now() ? auth : null;
222
- detail.push(["reports to", live ? live.user.email : dim("— signed out")]);
189
+ detail.push(["reports to", live ? live.user.email : "Signed out"]);
190
+
191
+ const daemonRow = describeDaemon(daemon, k);
192
+ detail.push(["daemon", daemonRow.value]);
193
+ if (daemonRow.problem) problems.push(daemonRow.problem);
194
+
223
195
  if (on && !live) {
224
196
  // The state the reporter surfaces as "signed-out". Named here for the same
225
197
  // reason the settings panel names it: the scans keep running, so silence
226
198
  // about the digests would look like the feature failing.
227
- detail.push(["", pink("scans continue; digests are paused until you sign in")]);
199
+ problems.push(k.caution("Scans continue, but digests are paused until you sign in.", "failproofai audit --schedule"));
228
200
  } else if (on && config.audit.reportsConsentedAt === undefined) {
229
201
  // Signed in, scheduled, and still not sending: this machine set `audit.auto`
230
202
  // when it only meant "scan locally", so nothing has consented to the digest
231
- // leaving the box. Without this row the status screen would show a healthy
203
+ // leaving the box. Without this line the status screen would show a healthy
232
204
  // schedule and a live address and still mail nothing, with no explanation
233
205
  // anywhere the user can see.
234
- detail.push([
235
- "",
236
- pink("scans continue; digests need a fresh opt-in — run `--schedule` to turn them on"),
237
- ]);
206
+ problems.push(k.caution("Scans continue, but digests need a fresh opt-in.", "failproofai audit --schedule"));
238
207
  }
239
208
 
240
- detail.push(["daemon", describeDaemon(daemon)]);
241
-
242
209
  if (sched?.nextDueAtMs != null && on) {
243
210
  detail.push(["next scan", untilPhrase(sched.nextDueAtMs)]);
244
211
  }
245
212
  if (sched?.lastRunAtMs != null) {
246
213
  const exit = sched.lastExitCode;
247
214
  const failed = exit != null && exit !== 0 && exit !== 75;
248
- detail.push([
249
- "last scheduled",
250
- agoPhrase(sched.lastRunAtMs) + (failed ? ` ${pink(`exit ${exit}`)}` : ""),
251
- ]);
215
+ detail.push(["last scheduled", agoPhrase(sched.lastRunAtMs) + (failed ? `, failed with exit ${exit}` : "")]);
252
216
  }
253
- detail.push([
254
- "last result",
255
- meta?.cachedAt ? agoPhrase(Date.parse(meta.cachedAt)) : dim("none yet"),
256
- ]);
217
+ detail.push(["last result", meta?.cachedAt ? agoPhrase(Date.parse(meta.cachedAt)) : "None yet"]);
257
218
  if (machine?.last_reported_at) {
258
219
  detail.push(["last reported", agoPhrase(Date.parse(machine.last_reported_at))]);
259
220
  }
260
221
 
261
- printBlock(
262
- process.stdout,
263
- stack(
264
- // The meta carries a fact, not the word "status" — a heading that repeats
265
- // the command name tells the reader nothing they did not just type.
266
- title(
267
- "failproofai audit",
268
- on ? `scheduled · every ${config.audit.intervalDays} days` : "not scheduled",
269
- opts,
270
- ),
271
- kitRows(detail, opts),
272
- ),
273
- );
222
+ printBlock(process.stdout, stack([k.header("Audit")], [k.head("Schedule"), ...k.kv(detail)], problems));
274
223
  }
275
224
 
276
- function describeDaemon(status: ReturnType<typeof daemonServiceStatus>): string {
225
+ /**
226
+ * The daemon's row, and the ▲ that goes with it when it needs fixing. The fix
227
+ * is named whether or not scheduling is on: the daemon is what every scheduled
228
+ * scan would run under, and this is the screen people open to ask about it.
229
+ */
230
+ function describeDaemon(
231
+ status: ReturnType<typeof daemonServiceStatus>,
232
+ k: ReturnType<typeof kitFor>,
233
+ ): { value: string; problem?: string } {
277
234
  switch (status) {
278
235
  case "running":
279
- return green("running");
236
+ return { value: `${k.on} Running` };
280
237
  case "stopped":
281
- return pink("stopped — run `failproofai config` to repair it");
238
+ return { value: `${k.off} Stopped`, problem: k.caution("The daemon is stopped.", "failproofai config") };
282
239
  case "not-installed":
283
- return pink("not installed — run `failproofai config`");
240
+ return {
241
+ value: `${k.off} Not installed`,
242
+ problem: k.caution("The daemon is not installed.", "failproofai config"),
243
+ };
284
244
  case "condition-failed":
285
- return pink("installed but its binary is missing — run `failproofai config`");
245
+ return {
246
+ value: `${k.off} Binary missing`,
247
+ problem: k.caution("The daemon is installed but its binary is missing.", "failproofai config"),
248
+ };
249
+ case "unknown":
250
+ // Only macOS without a cached sudo credential reads this, about a daemon
251
+ // that is usually running perfectly — not a fault to fix.
252
+ return { value: "Unknown" };
286
253
  default:
287
- return isDaemonSupportedPlatform()
288
- ? dim(String(status))
289
- : dim("unavailable on this platform");
254
+ return { value: isDaemonSupportedPlatform() ? String(status) : "Unavailable on this platform" };
290
255
  }
291
256
  }
292
257
 
@@ -302,17 +267,15 @@ function warnIfDaemonWontRun(): void {
302
267
  // timer yet" in the same breath as confirming their schedule was on. The
303
268
  // dashboard already special-cases it; this is the same call.
304
269
  if (status === "unknown") return;
270
+ const k = kitFor(process.stderr);
305
271
  if (!isDaemonSupportedPlatform()) {
306
- process.stderr.write(
307
- `\n ${pink("!")} The background service is not available on this platform,\n` +
308
- ` so nothing will run on the timer here. \`failproofai audit\` still works.\n`,
309
- );
272
+ process.stderr.write(`\n${k.caution("The daemon is not available on this platform, so nothing will run on the timer.")}\n`);
310
273
  return;
311
274
  }
275
+ const state =
276
+ status === "not-installed" ? "not installed" : status === "condition-failed" ? "missing its binary" : status.replace(/-/g, " ");
312
277
  process.stderr.write(
313
- `\n ${pink("!")} The background service is ${status.replace("-", " ")}, ` +
314
- `so nothing will run on the timer yet.\n` +
315
- ` Run \`failproofai config\` to install or repair it.\n`,
278
+ `\n${k.caution(`The daemon is ${state}, so nothing will run on the timer yet.`, "failproofai config")}\n`,
316
279
  );
317
280
  }
318
281
 
@@ -181,8 +181,39 @@ export interface AuditResult {
181
181
  enabledBuiltinNames: string[];
182
182
  }
183
183
 
184
+ /**
185
+ * What `runAudit()` reports while it runs, for a caller drawing live progress.
186
+ *
187
+ * Two kinds, in this order: ONE `discovered` once every agent's transcripts
188
+ * are listed, then one `transcript` per transcript as each finishes — a cache
189
+ * hit, a scan, a scan that failed and a task that was skipped alike, so the
190
+ * per-agent counts always reach their totals. Transcripts finish in whatever
191
+ * order the scan pool completes them, not in discovery order.
192
+ */
193
+ export type AuditProgress =
194
+ | {
195
+ kind: "discovered";
196
+ /** Every agent the audit covers, in scan order, with how many transcripts it has to scan (0 included). */
197
+ agents: Array<{ cli: IntegrationType; transcripts: number }>;
198
+ /** How many policies each tool call is replayed against: the builtins that can fire on a tool event. */
199
+ policies: number;
200
+ }
201
+ | {
202
+ kind: "transcript";
203
+ cli: IntegrationType;
204
+ /** This transcript's hits, by policy or detector name. Empty when it found nothing or could not be scanned. */
205
+ hitsByName: Record<string, number>;
206
+ };
207
+
184
208
  /** CLI-supplied options for `runAudit()`. Set by `bin/failproofai.mjs`. */
185
209
  export interface RunAuditOptions {
210
+ /**
211
+ * Called as the audit progresses. Optional, and only `failproofai audit`'s
212
+ * own screen passes it: the dashboard and the scheduled run do not, and the
213
+ * result is the same either way. A callback that throws is ignored — a
214
+ * progress display must never change what an audit finds.
215
+ */
216
+ onProgress?: (progress: AuditProgress) => void;
186
217
  /** Restrict to one or more CLIs. Default: all 7. */
187
218
  clis?: IntegrationType[];
188
219
  /** Restrict to sessions whose cwd matches one of these paths. */
@@ -0,0 +1,155 @@
1
+ /**
2
+ * Decision D2: a command that installs or removes an agent's hooks keeps the
3
+ * agent selection true.
4
+ *
5
+ * `agents.selected` in `config.json` is one list with three readers: which
6
+ * agents `failproofai config` hooks, which harnesses the daemon collects, and
7
+ * which ones `backfill` re-sends. Only `config` used to write it, so any other
8
+ * command that changed an agent's hooks left the list saying the opposite:
9
+ *
10
+ * - `policies --install --cli goose`, on a machine whose selection leaves Goose
11
+ * out, wired Goose — and the next `failproofai config`, which takes out the
12
+ * hooks of every agent it does not trace, silently removed them again.
13
+ * - `policies --uninstall --cli goose` took the hooks out and left Goose
14
+ * traced, so the daemon kept collecting its sessions and the next `config`
15
+ * put the hooks back.
16
+ *
17
+ * An install traces the agent at ANY scope: whichever file the hooks went into,
18
+ * somebody asked for that agent to be guarded. A removal untraces it only at
19
+ * user scope or every scope. The selection is per machine, and taking one
20
+ * repository's project hooks out says nothing about whether this machine should
21
+ * trace the agent — so that decision is the caller's, which knows the scope.
22
+ *
23
+ * Absent means every agent is traced, so an install has nothing to add. A
24
+ * removal cannot be recorded without a list, so it writes one: the agents on
25
+ * this machine — detected, or still hooked at user scope — but the ones
26
+ * removed, and `seen` as detected now. Not "every agent but the ones removed":
27
+ * `failproofai config` restores a saved list as it stands, so that list would
28
+ * have it hook every agent this build knows, installed or not.
29
+ *
30
+ * An agent traced again starts fresh (`requestFreshStart`), exactly as one
31
+ * re-ticked in `failproofai config` does.
32
+ *
33
+ * Only the commands that ask for it (`syncAgentSelection`) come here.
34
+ * `failproofai config` writes the selection itself, and `failproofai uninstall`
35
+ * leaves it alone.
36
+ */
37
+ import { existsSync } from "node:fs";
38
+
39
+ import { machineCollectsSessions, requestFreshStart } from "./backfill-request";
40
+ import { readConfig, updateConfig } from "./fp-config";
41
+ import { detectInstalledClis, getIntegration, hermesProfileHealth, settingsPathsFor } from "./integrations";
42
+ import { optsFor, screenKit } from "./tui";
43
+ import { INTEGRATION_TYPES, type IntegrationType } from "./types";
44
+
45
+ /**
46
+ * Whether an agent has failproofai hooks in its USER settings — the seed for an
47
+ * upgrade in `failproofai config`, and what a removal on a machine with no
48
+ * saved list keeps traced. User scope only: project hooks depend on the
49
+ * directory a command happens to run from. A gateway with several profiles
50
+ * counts when ANY profile is hooked; requiring all of them would drop a partly
51
+ * hooked Hermes from the seed, and setup would then unhook every profile it has.
52
+ *
53
+ * Hooked means routed to failproofai, not healthy: a Hermes profile still on the
54
+ * legacy shell hooks sends every tool call here today. Requiring the healthy
55
+ * native plugin left such a gateway unticked, so setup stopped collecting it and
56
+ * never ran the install that moves it onto the plugin.
57
+ */
58
+ export function hookedAtUserScope(id: IntegrationType, cwd: string): boolean {
59
+ const integration = getIntegration(id);
60
+ try {
61
+ if (integration.hooksInstalledInSettings("user", cwd)) return true;
62
+ if (id === "hermes") {
63
+ return hermesProfileHealth().some(
64
+ (p) => existsSync(p.home) && ((p.pluginInstalled && p.pluginEnabled) || p.legacyShellHookPresent),
65
+ );
66
+ }
67
+ const paths = settingsPathsFor(integration, "user", cwd);
68
+ if (paths.length < 2) return false;
69
+ return paths.some((one) =>
70
+ integration.hooksInstalledInSettings.call({ ...integration, getSettingsPaths: () => [one] }, "user", cwd),
71
+ );
72
+ } catch {
73
+ return false;
74
+ }
75
+ }
76
+
77
+ /**
78
+ * Bring the selection in line with hooks just installed for, or removed from,
79
+ * `clis`, and return the one line that says so: a ✓ note, a ▲ when the change
80
+ * could not be written, or null when the selection already agreed.
81
+ *
82
+ * The caller prints it after its own output, and only where the scope counts —
83
+ * see the module comment.
84
+ */
85
+ export function keepAgentSelectionTrue(
86
+ change: "installed" | "removed",
87
+ clis: readonly IntegrationType[],
88
+ ): string | null {
89
+ const touched = INTEGRATION_TYPES.filter((id) => clis.includes(id));
90
+ if (touched.length === 0) return null;
91
+ const saved = readConfig().agents;
92
+
93
+ let moved: IntegrationType[];
94
+ let next: { selected: string[]; seen: string[] };
95
+ if (change === "installed") {
96
+ if (!saved) return null;
97
+ moved = touched.filter((id) => !saved.selected.includes(id));
98
+ if (moved.length === 0) return null;
99
+ // INTEGRATION_TYPES order. An id this build does not know is a newer
100
+ // build's agent, so it is kept after them as written, never dropped.
101
+ const inOrder = (ids: readonly string[]): string[] => [
102
+ ...INTEGRATION_TYPES.filter((id) => ids.includes(id)),
103
+ ...ids.filter((id) => !(INTEGRATION_TYPES as readonly string[]).includes(id)),
104
+ ];
105
+ const detected = detectInstalledClis();
106
+ const nowSeen = moved.filter((id) => detected.includes(id) && !saved.seen.includes(id));
107
+ next = {
108
+ selected: inOrder([...saved.selected, ...moved]),
109
+ seen: nowSeen.length > 0 ? inOrder([...saved.seen, ...nowSeen]) : saved.seen,
110
+ };
111
+ } else if (saved) {
112
+ moved = touched.filter((id) => saved.selected.includes(id));
113
+ if (moved.length === 0) return null;
114
+ const gone = new Set<string>(moved);
115
+ // Possibly empty, after every agent's hooks came out: an empty selection
116
+ // traces nothing, which is exactly what was asked for.
117
+ next = { selected: saved.selected.filter((id) => !gone.has(id)), seen: saved.seen };
118
+ } else {
119
+ moved = touched;
120
+ const detected = detectInstalledClis();
121
+ const cwd = process.cwd();
122
+ next = {
123
+ selected: INTEGRATION_TYPES.filter(
124
+ (id) => !touched.includes(id) && (detected.includes(id) || hookedAtUserScope(id, cwd)),
125
+ ),
126
+ seen: detected,
127
+ };
128
+ }
129
+
130
+ const kit = screenKit(optsFor(process.stdout));
131
+ const names = moved.map((id) => getIntegration(id).displayName);
132
+ const who = names.length === 1 ? names[0] : `${names.slice(0, -1).join(", ")} and ${names[names.length - 1]}`;
133
+ const one = names.length === 1;
134
+ const setup = kit.cmd("failproofai config");
135
+ // Before the selection that traces them, in the same order setup uses.
136
+ if (change === "installed") requestFreshStart(moved, machineCollectsSessions());
137
+ try {
138
+ updateConfig({ agents: next });
139
+ } catch (err) {
140
+ // The hooks themselves are in place, so the command is not failed for
141
+ // this. Said instead, because it is the exact drift this module exists to
142
+ // stop, and nothing else would mention it.
143
+ const why = err instanceof Error ? err.message : String(err);
144
+ return kit.caution(
145
+ change === "installed"
146
+ ? `Couldn't record ${who} as traced (${why}), so ${setup} may remove ${one ? "its" : "their"} hooks again.`
147
+ : `Couldn't record ${who} as no longer traced (${why}), so ${setup} may add ${one ? "its" : "their"} hooks back.`,
148
+ );
149
+ }
150
+ return kit.ok(
151
+ change === "installed"
152
+ ? `${who} ${one ? "is" : "are"} traced again, so ${setup} keeps ${one ? "its" : "their"} hooks.`
153
+ : `${who} ${one ? "is" : "are"} no longer traced, so ${setup} won't add ${one ? "its" : "their"} hooks back.`,
154
+ );
155
+ }