failproofai 1.0.10 → 1.0.11-beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (297) 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]__0oqrl71._.js → [root-of-the-server]__042u-jn._.js} +2 -2
  113. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__04usis8._.js +2 -2
  114. package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__1-bm5zf._.js → [root-of-the-server]__0502lat._.js} +1 -1
  115. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0_gn5iz._.js +4 -0
  116. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0f765xr._.js +4 -0
  117. package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__01bmjsj._.js → [root-of-the-server]__0h38gzz._.js} +1 -1
  118. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0hoaz1g._.js +4 -0
  119. package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__1ouobn4._.js → [root-of-the-server]__0mxmwtx._.js} +1 -1
  120. package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__19evfi8._.js → [root-of-the-server]__0uescqv._.js} +1 -1
  121. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__13d-wb6._.js +1 -1
  122. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1484zv2._.js +3 -0
  123. package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__1mf3zp6._.js → [root-of-the-server]__19dutr7._.js} +4 -4
  124. package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__1u6sn69._.js → [root-of-the-server]__1ab4ifb._.js} +1 -1
  125. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1fbrk56._.js +4 -0
  126. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1pprgri._.js +2 -2
  127. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1um4yxn._.js +4 -0
  128. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1wb4yt2._.js +4 -0
  129. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1y3n3cn._.js +4 -0
  130. package/.next/standalone/.next/server/chunks/ssr/_08x1r5t._.js +1 -1
  131. package/.next/standalone/.next/server/chunks/ssr/_0bqoto4._.js +1 -1
  132. package/.next/standalone/.next/server/chunks/ssr/{_0yq5_3a._.js → _1ffxwxz._.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/{_16pa9ht._.js → _1v1o2ya._.js} +2 -2
  137. package/.next/standalone/.next/server/chunks/ssr/_next-internal_server_app_policies_page_actions_1sp2-yo.js +12 -14
  138. package/.next/standalone/.next/server/chunks/ssr/app_actions_get-scheduled-audit_ts_0ei9sni._.js +1 -1
  139. package/.next/standalone/.next/server/chunks/ssr/app_audit__components_audit-dashboard_tsx_0p9ud47._.js +1 -1
  140. package/.next/standalone/.next/server/chunks/ssr/app_global-error_tsx_1kp6l3x._.js +1 -1
  141. package/.next/standalone/.next/server/chunks/ssr/app_policies_hooks-client_tsx_19dqvpc._.js +1 -1
  142. package/.next/standalone/.next/server/chunks/ssr/app_settings_02tf1h4._.js +1 -1
  143. package/.next/standalone/.next/server/chunks/ssr/node_modules_1hl81y4._.js +1 -1
  144. package/.next/standalone/.next/server/chunks/ssr/node_modules_next_dist_0nkkbfv._.js +6 -0
  145. package/.next/standalone/.next/server/chunks/ssr/src_hooks_fp-config_ts_04t589g._.js +1 -1
  146. package/.next/standalone/.next/server/chunks/ssr/src_hooks_fp-home_ts_0je3xkv._.js +1 -1
  147. package/.next/standalone/.next/server/middleware-build-manifest.js +6 -6
  148. package/.next/standalone/.next/server/middleware.js +1 -1
  149. package/.next/standalone/.next/server/pages/404.html +1 -1
  150. package/.next/standalone/.next/server/pages/500.html +1 -1
  151. package/.next/standalone/.next/server/server-reference-manifest.js +1 -1
  152. package/.next/standalone/.next/server/server-reference-manifest.json +23 -23
  153. package/.next/standalone/.next/static/chunks/08tmc_7n-rba1.js +1 -0
  154. package/.next/standalone/.next/static/chunks/{3mjfvng11xnoo.js → 09tlrmucn6oq3.js} +1 -1
  155. package/.next/standalone/.next/static/chunks/{078dofaxvjgpd.js → 0i69k917myh7b.js} +1 -1
  156. package/.next/standalone/.next/static/chunks/{03h3b-uomi7_d.js → 0u_nhsujkactp.js} +1 -1
  157. package/.next/standalone/.next/static/chunks/{2j92xul6y24_v.js → 0zp51hzx62i-u.js} +1 -1
  158. package/.next/standalone/.next/static/chunks/{3u1s8dsqiean5.js → 1fz7q0gtnmeod.js} +1 -1
  159. package/.next/standalone/.next/static/chunks/{3ob2161ap37ul.js → 1j75le8wln-26.js} +1 -1
  160. package/.next/standalone/.next/static/chunks/{2kj826h1w9r47.js → 1z4km7tw0_50h.js} +1 -1
  161. package/.next/standalone/.next/static/chunks/{1fqukzzowjflt.js → 239ay_80-xr8z.js} +1 -1
  162. package/.next/standalone/.next/static/chunks/{3nkc2jlgyygda.js → 2c9pc5xco6rnu.js} +1 -1
  163. package/.next/standalone/.next/static/chunks/{3i8rg2fv9nd6e.js → 3n46e45atic-m.js} +1 -1
  164. package/.next/standalone/.next/static/chunks/{0542cuk_va8g1.js → 3sg07wnn969xq.js} +1 -1
  165. package/.next/standalone/.next/static/chunks/{turbopack-11dn2pegmg9_9.js → turbopack-1opxka4dtjbc5.js} +1 -1
  166. package/.next/standalone/app/actions/install-hooks-web.ts +10 -2
  167. package/.next/standalone/hermes-plugin/plugin.yaml +1 -1
  168. package/.next/standalone/node_modules/@next/env/package.json +1 -1
  169. package/.next/standalone/node_modules/next/dist/build/swc/index.js +1 -1
  170. package/.next/standalone/node_modules/next/dist/compiled/hash.js/sha256/256.js +1 -0
  171. package/.next/standalone/node_modules/next/dist/compiled/hash.js/sha256/package.json +1 -0
  172. package/.next/standalone/node_modules/next/dist/compiled/next-server/app-page-turbo-experimental.runtime.prod.js +17 -17
  173. package/.next/standalone/node_modules/next/dist/compiled/next-server/app-page-turbo.runtime.prod.js +18 -18
  174. package/.next/standalone/node_modules/next/dist/compiled/next-server/app-route-turbo.runtime.prod.js +3 -3
  175. package/.next/standalone/node_modules/next/dist/compiled/next-server/pages-turbo.runtime.prod.js +13 -13
  176. package/.next/standalone/node_modules/next/dist/lib/patch-incorrect-lockfile.js +3 -3
  177. package/.next/standalone/node_modules/next/dist/server/base-server.js +1 -2
  178. package/.next/standalone/node_modules/next/dist/server/config.js +1 -1
  179. package/.next/standalone/node_modules/next/dist/server/dev/hot-reloader-turbopack.js +2 -2
  180. package/.next/standalone/node_modules/next/dist/server/dev/hot-reloader-webpack.js +1 -1
  181. package/.next/standalone/node_modules/next/dist/server/image-optimizer.js +265 -53
  182. package/.next/standalone/node_modules/next/dist/server/lib/app-info-log.js +1 -1
  183. package/.next/standalone/node_modules/next/dist/server/lib/incremental-cache/file-system-cache.js +177 -19
  184. package/.next/standalone/node_modules/next/dist/server/lib/incremental-cache/index.js +36 -17
  185. package/.next/standalone/node_modules/next/dist/server/lib/incremental-cache/shared-cache-controls.external.js +14 -6
  186. package/.next/standalone/node_modules/next/dist/server/lib/match-next-data-pathname.js +3 -1
  187. package/.next/standalone/node_modules/next/dist/server/lib/node-fs-methods.js +14 -0
  188. package/.next/standalone/node_modules/next/dist/server/lib/route-cache-key.js +77 -0
  189. package/.next/standalone/node_modules/next/dist/server/lib/router-utils/block-cross-site-dev.js +9 -11
  190. package/.next/standalone/node_modules/next/dist/server/lib/start-server.js +1 -1
  191. package/.next/standalone/node_modules/next/dist/server/mcp/get-mcp-middleware.js +4 -2
  192. package/.next/standalone/node_modules/next/dist/server/next-server.js +7 -3
  193. package/.next/standalone/node_modules/next/dist/server/response-cache/index.js +32 -16
  194. package/.next/standalone/node_modules/next/dist/server/route-matcher-providers/app-page-route-matcher-provider.js +2 -2
  195. package/.next/standalone/node_modules/next/dist/server/route-modules/pages/pages-handler.js +6 -1
  196. package/.next/standalone/node_modules/next/dist/server/route-modules/route-module.js +9 -2
  197. package/.next/standalone/node_modules/next/dist/server/use-cache/use-cache-wrapper.js +30 -29
  198. package/.next/standalone/node_modules/next/dist/shared/lib/router/utils/app-paths.js +18 -1
  199. package/.next/standalone/node_modules/next/dist/telemetry/anonymous-meta.js +1 -1
  200. package/.next/standalone/node_modules/next/dist/telemetry/events/swc-load-failure.js +1 -1
  201. package/.next/standalone/node_modules/next/dist/telemetry/events/version.js +2 -2
  202. package/.next/standalone/node_modules/next/package.json +10 -10
  203. package/.next/standalone/package.json +14 -11
  204. package/.next/standalone/sdk/python/README.md +41 -0
  205. package/.next/standalone/sdk/python/docs/README.md +1 -0
  206. package/.next/standalone/sdk/python/docs/otel/README.md +36 -0
  207. package/.next/standalone/sdk/python/docs/otel/examples/agent_trace.py +30 -0
  208. package/.next/standalone/sdk/python/docs/otel/examples/quickstart.py +22 -0
  209. package/.next/standalone/sdk/python/failproofai_sdk/__init__.py +8 -0
  210. package/.next/standalone/sdk/python/failproofai_sdk/integrations/__init__.py +3 -0
  211. package/.next/standalone/sdk/python/failproofai_sdk/integrations/otel.py +189 -0
  212. package/.next/standalone/sdk/python/pyproject.toml +2 -1
  213. package/.next/standalone/sdk/python/tests/test_docs.py +16 -3
  214. package/.next/standalone/sdk/python/tests/test_integrations.py +1 -1
  215. package/.next/standalone/sdk/python/tests/test_otel.py +147 -0
  216. package/.next/standalone/sdk/python/tests/test_zero_dependencies.py +4 -1
  217. package/.next/standalone/sdk/python/uv.lock +258 -147
  218. package/.next/standalone/sdk/typescript/README.md +43 -0
  219. package/.next/standalone/sdk/typescript/integration/ai.test.ts +9 -2
  220. package/.next/standalone/sdk/typescript/package-lock.json +57 -5
  221. package/.next/standalone/sdk/typescript/package.json +2 -0
  222. package/.next/standalone/sdk/typescript/src/edge/index.ts +9 -0
  223. package/.next/standalone/sdk/typescript/src/index.ts +2 -0
  224. package/.next/standalone/sdk/typescript/src/integrations/index.ts +4 -1
  225. package/.next/standalone/sdk/typescript/src/integrations/otel.ts +204 -0
  226. package/.next/standalone/sdk/typescript/test/integrations.test.ts +1 -1
  227. package/.next/standalone/sdk/typescript/test/otel.test.ts +125 -0
  228. package/.next/standalone/server.js +1 -1
  229. package/bin/failproofai.mjs +653 -1077
  230. package/dist/cli.mjs +8174 -3930
  231. package/dist/worker.mjs +170 -62
  232. package/hermes-plugin/plugin.yaml +1 -1
  233. package/package.json +14 -11
  234. package/scripts/launch-facts.ts +157 -0
  235. package/scripts/launch-ready.ts +228 -0
  236. package/scripts/launch-screen.ts +202 -0
  237. package/scripts/launch.ts +106 -49
  238. package/scripts/skew-log-filter.ts +42 -0
  239. package/src/audit/cli-login.ts +39 -59
  240. package/src/audit/cli.ts +308 -228
  241. package/src/audit/index.ts +45 -2
  242. package/src/audit/schedule-cli.ts +83 -120
  243. package/src/audit/types.ts +31 -0
  244. package/src/hooks/agent-selection.ts +155 -0
  245. package/src/hooks/backfill-cli.ts +151 -137
  246. package/src/hooks/backfill-request.ts +149 -0
  247. package/src/hooks/cloud-connection.ts +1 -1
  248. package/src/hooks/cloud-enrollment-cli.ts +78 -28
  249. package/src/hooks/cloud-introspect.ts +3 -0
  250. package/src/hooks/collector-config.ts +10 -1
  251. package/src/hooks/config-status.ts +606 -0
  252. package/src/hooks/configure-wizard.ts +915 -989
  253. package/src/hooks/daemon-service.ts +16 -6
  254. package/src/hooks/did-you-mean.ts +185 -0
  255. package/src/hooks/flush-cli.ts +33 -7
  256. package/src/hooks/fp-config.ts +96 -0
  257. package/src/hooks/fp-home.ts +11 -0
  258. package/src/hooks/fp-reset.ts +102 -95
  259. package/src/hooks/handler.ts +4 -0
  260. package/src/hooks/harness-cli.ts +87 -138
  261. package/src/hooks/install-prompt.ts +38 -477
  262. package/src/hooks/integrations.ts +1 -1
  263. package/src/hooks/jev-cli.ts +183 -196
  264. package/src/hooks/manager.ts +554 -358
  265. package/src/hooks/onboarding-attempt.ts +11 -9
  266. package/src/hooks/otel-cli.ts +344 -0
  267. package/src/hooks/otel-output.ts +24 -0
  268. package/src/hooks/otel-writers.ts +225 -0
  269. package/src/hooks/pack-cli.ts +219 -204
  270. package/src/hooks/report-body.ts +50 -0
  271. package/src/hooks/safe-config-write.ts +4 -2
  272. package/src/hooks/session-pause-cli.ts +93 -73
  273. package/src/hooks/tui.ts +849 -389
  274. package/src/hooks/uninstall-cli.ts +136 -149
  275. package/src/hooks/untraced-note.ts +52 -0
  276. package/.next/standalone/.next/server/chunks/[externals]__19_pzeq._.js +0 -3
  277. package/.next/standalone/.next/server/chunks/[externals]__20kzpkf._.js +0 -3
  278. package/.next/standalone/.next/server/chunks/[root-of-the-server]__01eqny5._.js +0 -3
  279. package/.next/standalone/.next/server/chunks/[root-of-the-server]__05ja_05._.js +0 -3
  280. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0igxt32._.js +0 -3
  281. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0zyc_nm._.js +0 -3
  282. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1ttrwnd._.js +0 -22
  283. package/.next/standalone/.next/server/chunks/_0bs-v8p._.js +0 -3
  284. package/.next/standalone/.next/server/chunks/_185gyk9._.js +0 -3
  285. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__056wjo4._.js +0 -4
  286. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0n0xg95._.js +0 -4
  287. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0rwtwpm._.js +0 -4
  288. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0yrsbd_._.js +0 -3
  289. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__11mayhe._.js +0 -4
  290. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__15578wp._.js +0 -4
  291. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1m_svbe._.js +0 -4
  292. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1q4p5b8._.js +0 -4
  293. package/.next/standalone/.next/server/chunks/ssr/node_modules_next_dist_1n3w9lb._.js +0 -6
  294. package/.next/standalone/.next/static/chunks/1lyg_puttohbc.js +0 -1
  295. /package/.next/standalone/.next/static/{ECs8__hcNabYV58WfDh-3 → UZKCiP4Q2TeU8zOB-h9Xj}/_buildManifest.js +0 -0
  296. /package/.next/standalone/.next/static/{ECs8__hcNabYV58WfDh-3 → UZKCiP4Q2TeU8zOB-h9Xj}/_clientMiddlewareManifest.js +0 -0
  297. /package/.next/standalone/.next/static/{ECs8__hcNabYV58WfDh-3 → UZKCiP4Q2TeU8zOB-h9Xj}/_ssgManifest.js +0 -0
package/src/audit/cli.ts CHANGED
@@ -8,11 +8,11 @@
8
8
  * failproofai audit --status What this machine is scheduled to do.
9
9
  * failproofai audit -h, --help Show usage.
10
10
  *
11
- * `runAudit()` is a pure local function (no network, no account). We run it,
12
- * render the same four progress stages the dashboard's RunProgress shows,
13
- * pre-warm the dashboard cache (~/.failproofai/audit-dashboard.json), then start
14
- * the bundled dashboard server and open the browser to /audit — which renders
15
- * instantly from that cache.
11
+ * `runAudit()` is a pure local function (no network, no account). We run it
12
+ * behind a progress screen — a bar per agent, fed by `runAudit`'s progress
13
+ * callback, and running counts of what was found — pre-warm the dashboard cache
14
+ * (~/.failproofai/audit-dashboard.json), then start the bundled dashboard server
15
+ * and open the browser to /audit, which renders instantly from that cache.
16
16
  *
17
17
  * A bare `failproofai audit` does a full scan (all CLIs, all history); the
18
18
  * scheduling flags above write config and never scan. Scan-shaping flags
@@ -25,6 +25,7 @@
25
25
  * an exit code. See `runScheduledAudit`.
26
26
  */
27
27
  import { runAudit } from "./index";
28
+ import { getDetectorByName } from "./detectors";
28
29
  import { hasInstalledPacks } from "../hooks/pack-manifest";
29
30
  import { CORE_SOURCE } from "../hooks/pack-store";
30
31
  import { acquireAuditLock, type AuditLockInfo } from "./audit-lock";
@@ -35,7 +36,13 @@ import { getInstanceId } from "../../lib/telemetry-id";
35
36
  import { sanitizeErrorMessage } from "../../lib/telemetry-sanitize";
36
37
  import { openWhenReady } from "./open-browser";
37
38
  import { describeOutcome, reportHarm } from "./report-harm";
38
- import { brandAnsi, ANSI_RESET, ANSI_BOLD, ANSI_DIM, helpScreen, helpOptsFor } from "../hooks/tui";
39
+ import {
40
+ INDENT,
41
+ optsFor,
42
+ screenKit,
43
+ type ScreenKitOpts,
44
+ type TTYOut,
45
+ } from "../hooks/tui";
39
46
  import { version } from "../../package.json";
40
47
 
41
48
  /** Port the bundled dashboard binds to. Matches `scripts/launch.ts`'s default
@@ -53,10 +60,17 @@ const DASHBOARD_PORT = 8020;
53
60
  export const EXIT_AUDIT_ALREADY_RUNNING = 75;
54
61
 
55
62
  /**
56
- * Mirror of `app/audit/_components/run-progress.tsx`'s `STAGES`. Kept identical
57
- * so the CLI and the dashboard's in-progress view tell the same story — the
58
- * dashboard even renders a mock `$ failproofai audit` terminal, and this is the
59
- * real thing. `audit-cli.test.ts` guards against drift between the two.
63
+ * Mirror of `app/audit/_components/run-progress.tsx`'s `STAGES`: the four
64
+ * time-driven stages the dashboard animates while its own run is in flight.
65
+ *
66
+ * The CLI no longer animates them. By decision D16 of the 2026-10 redesign it
67
+ * draws real progress instead — a bar per agent from `runAudit`'s progress
68
+ * callback, and running counts of what was found — so the screen moves when the
69
+ * scan does rather than on a timer. What the two still share is these words:
70
+ * the CLI labels the one phase it has no bars for, listing transcripts, with
71
+ * the first stage's label, and the per-agent bars stand for the middle two.
72
+ * `audit-cli.test.ts` keeps this list identical to the dashboard's, so a stage
73
+ * renamed there is renamed here too.
60
74
  */
61
75
  export const AUDIT_STAGES: ReadonlyArray<{ label: string; detail: string }> = [
62
76
  { label: "discovering transcripts", detail: "walking ~/.claude, ~/.codex, ~/.cursor, …" },
@@ -80,76 +94,50 @@ export const AUDIT_STAGES: ReadonlyArray<{ label: string; detail: string }> = [
80
94
  * one. It still works, and still refuses every argument it always refused.
81
95
  */
82
96
  export function helpText(): string {
83
- // Drawn by the same renderer as `config --help` and `policies --help`, from
84
- // the same kit `audit`'s output already uses for its palette. It used to
85
- // build its own rows against a hand-set HELP_DESC_COL, which is how `audit`
86
- // and `config` ended up with their descriptions in different columns.
87
- //
88
- // The `failproofai audit` prefix is dropped from the rows: the heading two
89
- // lines above already carries it, and repeating it cost 17 of the 80 columns
90
- // on every row — which is what forced the four-word-per-line descriptions.
91
- const lines = helpScreen(
92
- {
93
- command: "audit",
94
- version,
95
- tagline: "review your agent CLIs for risky and wasteful patterns",
96
- sections: [
97
- {
98
- label: "usage",
99
- entries: [
100
- ["(bare)", `Scan your session history, then open http://localhost:${DASHBOARD_PORT}/audit`],
101
- ["--schedule [days]", "Scan on a timer and email the findings. Default 7 days, range 1-90. Signs you in the first time."],
102
- // Its own row now, rather than a clause inside --schedule's. It
103
- // does not stand alone, which is why it used to be a clause — but
104
- // a clause wraps, and `--email <address>` landing with the flag at
105
- // the end of one line and its placeholder at the start of the next
106
- // is not a flag anybody can read or copy.
107
- ["--email <address>", "With --schedule, skips the sign-in prompt."],
108
- ["--no-schedule", "Stop the timer. Leaves you signed in."],
109
- ["--status", "Whether scheduling is on, where reports go, the daemon's state, and when the next scan is due."],
110
- ["-h, --help", "Show this help."],
111
- ],
112
- },
113
- ],
114
- footer: ["Everything runs on this machine; only a scheduled digest ever leaves it."],
115
- },
116
- helpOptsFor(process.stdout),
117
- );
97
+ // The same page every `<command> --help` is: usage, options and examples,
98
+ // built from the 2026-10 kit. Descriptions are prose and may be shortened to
99
+ // fit a terminal; nothing is ever cut in a pipe.
100
+ const kit = screenKit({ ...optsFor(process.stdout), fit: !!process.stdout.isTTY, version });
101
+ const lines = kit.helpPage({
102
+ name: "audit",
103
+ usage: [
104
+ ["failproofai audit", "Scan your agents' history and open the results"],
105
+ ["failproofai audit [options]", "Manage scheduled scans"],
106
+ ],
107
+ options: [
108
+ ["--schedule [days]", "Scan every N days (1-90, default 7) and email the findings"],
109
+ // Its own row rather than a clause inside --schedule's, so the flag and
110
+ // its placeholder can never be split across a wrap.
111
+ ["--email <address>", "With --schedule: sign in as this address; reports go there"],
112
+ ["--no-schedule", "Stop scheduled scans"],
113
+ ["--status", "Show the schedule and when the next scan runs"],
114
+ ["-h, --help", "Show this help"],
115
+ ],
116
+ examples: ["failproofai audit", "failproofai audit --schedule 7 --email you@example.com"],
117
+ optionsCol: 20,
118
+ });
118
119
  // The margins `printBlock` would add, spelled out because this one returns
119
120
  // its text for a caller to write rather than writing it.
120
121
  return ["", ...lines, ""].join("\n") + "\n";
121
122
  }
122
123
 
123
- // ── ANSI helpers ────────────────────────────────────────────────────────────
124
- // Colours come from the shared brand palette in hooks/tui.ts, so `audit` reads
125
- // as the same product as `config`. This file used to define its own 256-colour
126
- // set (a green and a blue that appear nowhere in the brand). The palette has
127
- // two accents rather than three, so success and command/URL text share the
128
- // teal — the glyph (✓) and the surrounding copy already distinguish them.
129
- const RESET = ANSI_RESET;
130
- const DIM = ANSI_DIM;
131
- const BOLD = ANSI_BOLD;
132
- const PINK = brandAnsi("pink");
133
- const GREEN = brandAnsi("guide"); // success — brand teal
134
- const CYAN = brandAnsi("guide"); // commands & URLs — brand teal
135
- const SPINNER = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
136
-
137
- function colorOn(): boolean {
138
- if (process.env.NO_COLOR && process.env.NO_COLOR !== "") return false;
139
- if (process.env.FORCE_COLOR === "0") return false;
140
- if (process.env.FORCE_COLOR) return true;
141
- return !!process.stdout.isTTY;
142
- }
143
-
144
- /** Wrap `s` in an ANSI code when color is enabled, else return it bare. */
145
- function c(code: string, s: string): string {
146
- return colorOn() ? `${code}${s}${RESET}` : s;
147
- }
124
+ // ── Colour ──────────────────────────────────────────────────────────────────
125
+ // Every line below is built by `screenKit()` from the shared palette in
126
+ // hooks/tui.ts, and coloured on exactly the terms every other screen is:
127
+ // `optsFor(stdout)` — a terminal, and no NO_COLOR. `FORCE_COLOR` is not read
128
+ // here, as it is read nowhere else; audit used to be the one screen that
129
+ // honoured it, so `FORCE_COLOR=1 failproofai audit | less -R` was the one
130
+ // place colour reached a pipe.
148
131
 
149
132
  function num(n: number): string {
150
133
  return n.toLocaleString("en-US");
151
134
  }
152
135
 
136
+ /** `1 session`, `1,284 sessions`. */
137
+ function counted(n: number, one: string, many = `${one}s`): string {
138
+ return `${num(n)} ${n === 1 ? one : many}`;
139
+ }
140
+
153
141
  /**
154
142
  * Print an error and exit 1. We exit directly rather than throwing a `CliError`
155
143
  * because, in the shipped single-file bundle (`dist/cli.mjs`), the entrypoint's
@@ -163,145 +151,253 @@ function die(message: string): never {
163
151
  process.exit(1);
164
152
  }
165
153
 
166
- // ── Progress animation ───────────────────────────────────────────────────────
154
+ // ── The audit screen ─────────────────────────────────────────────────────────
155
+
156
+ /** What the audit screen is drawn from: everything `runAudit` has reported so far. */
157
+ export interface AuditScreenState {
158
+ /**
159
+ * The agents with sessions to scan, in scan order, and how far each has got.
160
+ * `null` until every agent's transcripts have been listed.
161
+ */
162
+ agents: Array<{ cli: string; total: number; done: number }> | null;
163
+ /** How many policies each tool call is replayed against. */
164
+ policies: number;
165
+ /** Hits so far, by policy or detector name. */
166
+ hits: Record<string, number>;
167
+ }
167
168
 
168
- interface Progress {
169
- /** Mark every stage done and stop the timers. */
170
- finish(): void;
171
- /** Stop the timers without marking done (used on error). */
172
- fail(): void;
169
+ export interface AuditScreenOpts extends ScreenKitOpts {
170
+ /** The finished scan. With it the screen is the final one: every bar done, the summary in place of the key hint. */
171
+ result?: AuditResult;
172
+ /** The scan stopped without a result: the screen as far as it got, with no key hint. */
173
+ stopped?: boolean;
173
174
  }
174
175
 
176
+ /** The "found" rows, in order. */
177
+ const FOUND_LABELS = ["would block", "would warn", "audit only"] as const;
178
+
175
179
  /**
176
- * Render the four audit stages with a live spinner, redrawing in place. Like the
177
- * dashboard's RunProgress, the stages are time-driven (runAudit emits no phase
178
- * events) and the last stage is *held* until `finish()` is called, so it never
179
- * claims "done" before the real work resolves. TTY-only — the caller picks the
180
- * plain-text path when stdout isn't a terminal.
180
+ * Which "found" row a finding counts under, as an index into `FOUND_LABELS`:
181
+ * what the policy would do if it were on.
182
+ *
183
+ * Every `warn-*` builtin instructs, so it would warn. `sanitize-*` denies, but
184
+ * on PostToolUse, after the call has already run: it can tell the agent about a
185
+ * secret and stop nothing, so it would warn too — filing it under "would block"
186
+ * is the over-count #669 took out of the block numbers. Every other builtin
187
+ * (`block-*`, `protect-*`, `prefer-*`) denies before the call, so it would
188
+ * block. Deliberately not `severityForBuiltin`: that is the score's heuristic,
189
+ * and it files `protect-env-vars` and `prefer-package-manager` under warn
190
+ * although both deny. The audit-only detectors have no runtime policy at all,
191
+ * so they get a row of their own rather than a "would".
181
192
  */
182
- function startProgress(): Progress {
183
- const n = AUDIT_STAGES.length;
184
- let stage = 0;
185
- let tick = 0;
186
- let done = false;
187
- let printed = false;
188
-
189
- const lineFor = (i: number): string => {
190
- const s = AUDIT_STAGES[i];
191
- if (done || i < stage) return ` ${c(GREEN, "✓")} ${c(DIM, s.label)}`;
192
- if (i === stage) {
193
- return ` ${c(PINK, SPINNER[tick % SPINNER.length])} ${s.label} ${c(DIM, s.detail)}`;
194
- }
195
- return ` ${c(DIM, "○")} ${c(DIM, s.label)}`;
196
- };
197
-
198
- const render = (): void => {
199
- const lines = Array.from({ length: n }, (_, i) => lineFor(i));
200
- // Move the cursor back up over the previously-drawn block, then clear and
201
- // rewrite each line in place.
202
- if (printed) process.stdout.write(`\x1b[${n}A`);
203
- process.stdout.write(lines.map((l) => `\x1b[2K${l}`).join("\n") + "\n");
204
- printed = true;
205
- };
206
-
207
- render();
208
- const spinTimer = setInterval(() => {
209
- tick++;
210
- render();
211
- }, 90);
212
- // Advance through stages on a fixed cadence, holding on the last one until
213
- // finish() flips `done`.
214
- const stageTimer = setInterval(() => {
215
- if (stage < n - 1) {
216
- stage++;
217
- render();
218
- }
219
- }, 1100);
220
-
221
- const stop = (): void => {
222
- clearInterval(spinTimer);
223
- clearInterval(stageTimer);
224
- };
225
-
226
- return {
227
- finish() {
228
- stop();
229
- done = true;
230
- render();
231
- },
232
- fail() {
233
- stop();
234
- process.stdout.write("\n");
235
- },
236
- };
193
+ function foundRow(name: string): number {
194
+ if (getDetectorByName(name)) return 2;
195
+ const short = name.slice(name.indexOf("/") + 1);
196
+ return short.startsWith("warn-") || short.startsWith("sanitize-") ? 1 : 0;
237
197
  }
238
198
 
239
- /** Run the audit, showing animated progress on a TTY or a single line elsewhere. */
240
- async function runWithProgress(opts: RunAuditOptions): Promise<AuditResult> {
241
- if (!process.stdout.isTTY) {
242
- process.stdout.write(" scanning your agent session history — this can take a moment…\n");
243
- return runAudit(opts);
244
- }
245
- const progress = startProgress();
246
- try {
247
- const result = await runAudit(opts);
248
- progress.finish();
249
- return result;
250
- } catch (err) {
251
- progress.fail();
252
- throw err;
199
+ /**
200
+ * `7: block-env-files 4, block-sudo 2, …` — the total, then the names that
201
+ * make it up, most hits first.
202
+ *
203
+ * A name is whole or absent, never cut: when `room` runs out the list ends on
204
+ * `…`, and the first name is always shown, however narrow the terminal.
205
+ */
206
+ function foundValue(items: Array<[string, number]>, room: number): string {
207
+ if (items.length === 0) return num(0);
208
+ const total = items.reduce((sum, [, n]) => sum + n, 0);
209
+ items.sort((a, b) => b[1] - a[1] || a[0].localeCompare(b[0]));
210
+ let text = `${num(total)}: `;
211
+ for (let i = 0; i < items.length; i += 1) {
212
+ const piece = `${i > 0 ? ", " : ""}${items[i][0]} ${num(items[i][1])}`;
213
+ // Room for the `, …` this item would have to end on, unless it is the last.
214
+ const tail = i < items.length - 1 ? 3 : 0;
215
+ if (i > 0 && text.length + piece.length + tail > room) return `${text}, …`;
216
+ text += piece;
253
217
  }
218
+ return text;
254
219
  }
255
220
 
256
- // ── Output ───────────────────────────────────────────────────────────────────
221
+ /**
222
+ * The `failproofai audit` screen, as lines. Pure, so it is asserted at any
223
+ * width and colour tier without a terminal.
224
+ *
225
+ * Live, it is the design's progress screen: a bar per agent with sessions to
226
+ * scan, and what the scan has found so far. Given the result it finishes in
227
+ * place — every bar done, the headings in the past tense, and the summary where
228
+ * the key hint was — so the screen that stays in scrollback is the one the scan
229
+ * was drawn on.
230
+ */
231
+ export function buildAuditScreen(state: AuditScreenState, opts: AuditScreenOpts = {}): string[] {
232
+ const k = screenKit(opts);
233
+ const result = opts.result;
234
+ const out = [k.header("Audit"), ""];
235
+
236
+ if (state.agents === null) {
237
+ // Still listing transcripts: the one phase with no bars to draw, named in
238
+ // the dashboard's own words for it. A finished scan that never said what it
239
+ // found has no rows to draw, and is left to its summary.
240
+ if (!result) out.push(k.head("Scanning history", AUDIT_STAGES[0].label));
241
+ } else if (state.agents.length > 0) {
242
+ const agents = result ? state.agents.map((a) => ({ ...a, done: a.total })) : state.agents;
243
+ out.push(
244
+ k.head(
245
+ result ? "Scanned history" : "Scanning history",
246
+ `${counted(agents.length, "agent")}, ${counted(state.policies, "policy", "policies")}`,
247
+ ),
248
+ );
249
+ // The bar gives up width before the counts do: it is decoration, and the
250
+ // counts are the facts. Thirty cells where there is room, ten at the least.
251
+ const nameCol = Math.max(10, ...agents.map((a) => a.cli.length + 2));
252
+ const widest = Math.max(...agents.map((a) => `${num(a.total)} of ${counted(a.total, "session")}`.length));
253
+ const barWidth = Math.max(10, Math.min(30, k.cols - INDENT.length - nameCol - 2 - widest));
254
+ for (const a of agents) {
255
+ const tally =
256
+ a.done >= a.total ? k.ok(counted(a.total, "session")) : `${num(a.done)} of ${counted(a.total, "session")}`;
257
+ out.push(`${INDENT}${a.cli.padEnd(nameCol)}${k.bar(barWidth, a.total > 0 ? a.done / a.total : 1)} ${tally}`);
258
+ }
259
+
260
+ // The result's own counts once there is one — they are what the dashboard
261
+ // shows — and the running tally until then.
262
+ const hits = result ? Object.fromEntries(result.results.map((r) => [r.name, r.hits])) : state.hits;
263
+ const groups: Array<Array<[string, number]>> = FOUND_LABELS.map(() => []);
264
+ for (const [name, n] of Object.entries(hits)) {
265
+ if (n > 0) groups[foundRow(name)].push([name.slice(name.indexOf("/") + 1), n]);
266
+ }
267
+ // The column `kv` starts these values at: its widest label (or nine), plus two.
268
+ const valueCol = INDENT.length + Math.max(9, ...FOUND_LABELS.map((label) => label.length)) + 2;
269
+ const room = opts.fit ? k.cols - valueCol : Number.POSITIVE_INFINITY;
270
+ out.push(
271
+ "",
272
+ k.head(result ? "Found" : "Found so far"),
273
+ ...k.kv(FOUND_LABELS.map((label, i): [string, string] => [label, foundValue(groups[i], room)])),
274
+ );
275
+ }
257
276
 
258
- function printHeader(): void {
259
- process.stdout.write(`\n ${c(PINK, "🛡 failproofai audit")} ${c(DIM, "· beta")}\n\n`);
260
- process.stdout.write(` ${c(DIM, "starting audit…")}\n\n`);
277
+ // One blank line before the closing block, never two: with nothing to scan
278
+ // there are no blocks above it, only the header's own gap.
279
+ const closing = result ? buildSummary(result, opts) : opts.stopped ? [] : [k.keys(["ctrl+c stop"])];
280
+ if (out[out.length - 1] !== "") out.push("");
281
+ out.push(...closing);
282
+ while (out[out.length - 1] === "") out.pop();
283
+ return out;
261
284
  }
262
285
 
263
286
  /**
264
- * The post-run summary lines (no leading indent, no trailing newlines). Pure so
265
- * it's unit-testable; `printSummary` handles the indentation + stdout.
287
+ * The finished scan in at most two sentences: what was read, then how much of
288
+ * what it found your policies already cover. Pure; the audit screen ends on it.
289
+ *
290
+ * "Covered", not "blocked": a policy that is on may warn rather than block, and
291
+ * what is counted is patterns a policy you have switched on already answers.
266
292
  */
267
- export function buildSummary(result: AuditResult): string[] {
293
+ export function buildSummary(result: AuditResult, opts: ScreenKitOpts = {}): string[] {
294
+ const k = screenKit(opts);
268
295
  const sessions = result.transcripts.scanned;
269
296
  const events = result.eventsScanned;
270
297
  const projects = result.projectsScanned.length;
271
- const enabledRows = result.results.filter((r) => r.source === "builtin" && r.enabledInConfig);
272
- const slippingRows = result.results.filter((r) => !(r.source === "builtin" && r.enabledInConfig));
273
-
274
- const lines: string[] = [];
275
- lines.push(
276
- `${c(GREEN, "✓ audit complete")} ${c(DIM, "·")} ` +
277
- `${c(BOLD, num(events))} tool call${events === 1 ? "" : "s"} across ` +
278
- `${num(sessions)} session${sessions === 1 ? "" : "s"}` +
279
- (projects > 0 ? ` ${c(DIM, "·")} ${num(projects)} project${projects === 1 ? "" : "s"}` : ""),
280
- );
281
-
298
+ const covered = result.results.filter((r) => r.source === "builtin" && r.enabledInConfig).length;
299
+ const slipping = result.results.length - covered;
300
+
301
+ const lines = [
302
+ k.ok(
303
+ `Scanned ${counted(events, "tool call")} across ${counted(sessions, "session")}` +
304
+ (projects > 0 ? ` in ${counted(projects, "project")}` : "") +
305
+ ".",
306
+ ),
307
+ ];
282
308
  if (result.totals.hits === 0) {
283
- // Only call it a "clean run" when we actually scanned something — for zero
284
- // events the caller prints "no agent sessions found yet" guidance instead.
285
- if (events > 0) lines.push(c(DIM, "clean run — nothing flagged. nice."));
309
+ // Only when something was actually read. With no tool calls at all the
310
+ // caller says what to do instead, and "nothing was flagged" would read as
311
+ // a clean bill of health for a history that was never there.
312
+ if (events > 0) lines.push(`${INDENT}Nothing was flagged.`);
286
313
  return lines;
287
314
  }
288
-
289
- const parts: string[] = [];
290
- if (slippingRows.length > 0) {
291
- parts.push(
292
- `${c(PINK, String(slippingRows.length))} ${slippingRows.length === 1 ? "pattern" : "patterns"} slipping through`,
315
+ const patterns = (n: number): string => `${num(n)} ${n === 1 ? "pattern is" : "patterns are"}`;
316
+ if (slipping > 0 && covered > 0) {
317
+ lines.push(
318
+ `${INDENT}${patterns(slipping)} slipping through, and ${num(covered)} ${covered === 1 ? "is" : "are"} already covered by your policies.`,
293
319
  );
320
+ } else if (slipping > 0) {
321
+ lines.push(`${INDENT}${patterns(slipping)} slipping through.`);
322
+ } else if (covered > 0) {
323
+ lines.push(`${INDENT}${patterns(covered)} already covered by your policies.`);
294
324
  }
295
- if (enabledRows.length > 0) {
296
- parts.push(`${c(GREEN, String(enabledRows.length))} already blocked by your policies`);
297
- }
298
- if (parts.length > 0) lines.push(parts.join(` ${c(DIM, "·")} `));
299
325
  return lines;
300
326
  }
301
327
 
302
- function printSummary(result: AuditResult): void {
303
- process.stdout.write("\n");
304
- for (const line of buildSummary(result)) process.stdout.write(` ${line}\n`);
328
+ // ── Progress ─────────────────────────────────────────────────────────────────
329
+
330
+ /**
331
+ * Run the audit behind its screen, and return once the finished screen is on
332
+ * the terminal.
333
+ *
334
+ * On a terminal that takes colour the screen is live: it redraws in place as
335
+ * `runAudit` reports, at most every 100 ms and ONE write per frame, through the
336
+ * kit's `live()`. The spinner it replaces wrote its cursor-up and its lines as
337
+ * separate chunks, and advanced on a timer that knew nothing about the scan.
338
+ *
339
+ * Piped, or under NO_COLOR, nothing is drawn while the scan runs: the finished
340
+ * screen prints once, with no escapes and no frames.
341
+ */
342
+ async function runWithProgress(opts: RunAuditOptions): Promise<AuditResult> {
343
+ const out: TTYOut = process.stdout;
344
+ const render = optsFor(out);
345
+ const animate = !!out.isTTY && render.color;
346
+ // Prose may be shortened to fit a terminal, never a pipe (decision D9).
347
+ const view: AuditScreenOpts = { ...render, fit: !!out.isTTY };
348
+ const state: AuditScreenState = { agents: null, policies: 0, hits: {} };
349
+ const region = animate ? screenKit(view).live(out) : null;
350
+ // The width is read per frame, so a terminal resized mid-scan gets frames
351
+ // that fit it.
352
+ const frame = (extra: AuditScreenOpts = {}): string[] => [
353
+ "",
354
+ ...buildAuditScreen(state, { ...view, cols: out.columns || view.cols, ...extra }),
355
+ ];
356
+
357
+ region?.draw(frame);
358
+ let result: AuditResult;
359
+ try {
360
+ result = await runAudit({
361
+ ...opts,
362
+ onProgress: (progress) => {
363
+ if (progress.kind === "discovered") {
364
+ state.policies = progress.policies;
365
+ state.agents = progress.agents
366
+ .filter((a) => a.transcripts > 0)
367
+ .map((a) => ({ cli: a.cli, total: a.transcripts, done: 0 }));
368
+ } else {
369
+ const agent = state.agents?.find((a) => a.cli === progress.cli);
370
+ if (agent) agent.done += 1;
371
+ for (const [name, n] of Object.entries(progress.hitsByName)) {
372
+ state.hits[name] = (state.hits[name] ?? 0) + n;
373
+ }
374
+ }
375
+ region?.draw(frame);
376
+ },
377
+ });
378
+ } catch (err) {
379
+ // Left as far as it got, without the key hint: nothing is running now.
380
+ region?.done(frame({ stopped: true }));
381
+ throw err;
382
+ }
383
+ const finished = frame({ result });
384
+ if (region) region.done(finished);
385
+ else out.write(finished.join("\n") + "\n");
386
+ return result;
387
+ }
388
+
389
+ /** A block printed under the screen: a blank line above it and one below. */
390
+ function printAfter(lines: string[]): void {
391
+ process.stdout.write(`\n${lines.join("\n")}\n\n`);
392
+ }
393
+
394
+ /**
395
+ * The closing line when there was nothing to read. What changes that is using
396
+ * the agent: the audit reads each agent's own history, which exists with or
397
+ * without failproofai's hooks.
398
+ */
399
+ function nothingToScan(k: ReturnType<typeof screenKit>): string {
400
+ return `Run it again after using your agent: ${k.cmd("failproofai audit")}`;
305
401
  }
306
402
 
307
403
  // ── Audit telemetry ──────────────────────────────────────────────────────────
@@ -441,13 +537,15 @@ export async function runScheduledAudit(): Promise<number> {
441
537
  * `~/.failproofai/audit-dashboard.json` so the dashboard renders instantly, and
442
538
  * immediately shows the user what's slipping through.
443
539
  *
444
- * Shows the same animated stages as `failproofai audit`. The scan runs to
445
- * completion; Ctrl+C interrupts it the usual way (default SIGINT). Best-effort:
446
- * never throws, never exits the process; the caller boots the dashboard
447
- * afterward. Opt out with `FAILPROOFAI_NO_AUTO_AUDIT=1`.
540
+ * Shows the same screen as `failproofai audit`. The scan runs to completion;
541
+ * Ctrl+C interrupts it the usual way (the screen gives the cursor back and lets
542
+ * the SIGINT through). Best-effort: never throws, never exits the process; the
543
+ * caller boots the dashboard afterward. Opt out with
544
+ * `FAILPROOFAI_NO_AUTO_AUDIT=1`.
448
545
  */
449
546
  export async function runPostSetupAudit(): Promise<void> {
450
547
  if (process.env.FAILPROOFAI_NO_AUTO_AUDIT === "1") return;
548
+ const k = screenKit(optsFor(process.stdout));
451
549
 
452
550
  // Take the same cross-process cache lock the scheduled run, `failproofai
453
551
  // audit` and the dashboard re-run take. This onboarding scan writes the very
@@ -459,9 +557,7 @@ export async function runPostSetupAudit(): Promise<void> {
459
557
  // the telemetry source below and the lock source declared in audit-lock.ts.
460
558
  const attempt = acquireAuditLock("onboarding");
461
559
  if (!attempt.ok) {
462
- process.stdout.write(
463
- `\n ${c(DIM, "an audit is already running — the dashboard will show its result.")}\n\n`,
464
- );
560
+ printAfter(["Another audit is already running. The dashboard will show its result."]);
465
561
  return;
466
562
  }
467
563
 
@@ -472,10 +568,8 @@ export async function runPostSetupAudit(): Promise<void> {
472
568
  // that follows is awaited.
473
569
  void trackHookEvent(instanceId, "cli_audit_started", { source: "onboarding" });
474
570
 
475
- process.stdout.write(
476
- `\n ${c(PINK, "✦")} ${c(BOLD, "failproofai audit now running")} ${c(DIM, "· ctrl+c to stop")}\n\n`,
477
- );
478
-
571
+ // No line of its own before the scan: the screen opens with its own header
572
+ // and closes on its own key hint.
479
573
  let result: AuditResult;
480
574
  try {
481
575
  result = await runWithProgress({});
@@ -487,9 +581,7 @@ export async function runPostSetupAudit(): Promise<void> {
487
581
  error_type: err instanceof Error ? err.name : "unknown",
488
582
  error_message: sanitizeErrorMessage(err),
489
583
  });
490
- process.stdout.write(
491
- ` ${c(PINK, "!")} ${c(DIM, "audit couldn't finish — run")} ${c(CYAN, "failproofai audit")} ${c(DIM, "later.")}\n\n`,
492
- );
584
+ printAfter([k.fail("The audit could not finish.", "failproofai audit")]);
493
585
  return;
494
586
  }
495
587
 
@@ -499,23 +591,18 @@ export async function runPostSetupAudit(): Promise<void> {
499
591
  await trackHookEvent(instanceId, "cli_audit_completed", auditCompletedProps("onboarding", result));
500
592
 
501
593
  if (result.eventsScanned === 0) {
502
- process.stdout.write(
503
- `\n ${c(DIM, "no agent sessions to audit yet — come back after using your agent.")}\n\n`,
504
- );
594
+ printAfter([nothingToScan(k)]);
505
595
  return;
506
596
  }
507
597
  writeDashboardCache({}, result);
508
- printSummary(result);
509
- process.stdout.write("\n");
510
598
  // The audit says what ALREADY happened; nothing here says how to stop it
511
599
  // happening again. This is the first thing a new machine runs, and setup
512
600
  // installs no policies by design, so without this the whole first session
513
601
  // ends on a count of findings and no way to act on it.
514
602
  if (!hasInstalledPacks()) {
515
- process.stdout.write(
516
- ` ${c(DIM, "none of this is being enforced yet. take ours, or anyone's:")}\n` +
517
- ` ${c(CYAN, `failproofai policies add ${CORE_SOURCE}`)}\n\n`,
518
- );
603
+ printAfter([k.caution("None of this is enforced yet.", `failproofai policies add ${CORE_SOURCE}`)]);
604
+ } else {
605
+ process.stdout.write("\n");
519
606
  }
520
607
  } finally {
521
608
  attempt.lock.release();
@@ -641,9 +728,8 @@ export async function runAuditCli(args: string[]): Promise<void> {
641
728
  // stall on a flaky network for no reliability gain.
642
729
  void trackHookEvent(instanceId, "cli_audit_started", { source: "cli" });
643
730
 
644
- printHeader();
645
-
646
- // Full scan: all CLIs, all history, per-transcript cache on.
731
+ // Full scan: all CLIs, all history, per-transcript cache on. The screen opens
732
+ // with its own header, so nothing is printed ahead of it.
647
733
  const opts: RunAuditOptions = {};
648
734
 
649
735
  let result: AuditResult;
@@ -661,32 +747,23 @@ export async function runAuditCli(args: string[]): Promise<void> {
661
747
  die(`Audit failed: ${err instanceof Error ? err.message : String(err)}`);
662
748
  }
663
749
 
664
- printSummary(result);
665
-
666
750
  // Await before the empty-history branch below, which calls process.exit(0) and
667
751
  // would otherwise drop this event. On the dashboard path launch() keeps the
668
752
  // process alive, but awaiting makes delivery reliable on every exit path.
669
753
  // Bounded (5s) and never throws.
670
754
  await trackHookEvent(instanceId, "cli_audit_completed", auditCompletedProps("cli", result));
671
755
 
672
- // No sessions on disk — guide the user instead of opening an empty dashboard.
756
+ const k = screenKit(optsFor(process.stdout));
757
+
758
+ // Nothing to read — say what changes that instead of opening an empty dashboard.
673
759
  if (result.eventsScanned === 0) {
674
- process.stdout.write(
675
- `\n ${c(DIM, "no agent sessions found yet.")}\n` +
676
- ` install hooks with ${c(CYAN, "failproofai policies --install")} ` +
677
- `${c(DIM, "and come back after using your agent.")}\n\n`,
678
- );
760
+ printAfter([nothingToScan(k)]);
679
761
  process.exit(0);
680
762
  }
681
763
 
682
764
  // Pre-warm the dashboard cache — the /audit page reads this file directly, so
683
765
  // the page renders our result instantly with no in-browser re-run.
684
766
  const persisted = writeDashboardCache(opts, result);
685
- if (!persisted) {
686
- process.stdout.write(
687
- `\n ${c(PINK, "!")} ${c(DIM, "couldn't save the audit cache; the dashboard may show an empty state.")}\n`,
688
- );
689
- }
690
767
 
691
768
  // Released here, not in a `finally`: the lock covers the SCAN, and launch()
692
769
  // below keeps this process alive for as long as the user leaves the dashboard
@@ -695,19 +772,22 @@ export async function runAuditCli(args: string[]): Promise<void> {
695
772
  // empty-history exit) are covered by the handle's own process-exit hook.
696
773
  attempt.lock.release();
697
774
 
775
+ // The hand-off: where the audit is, and how to stop the server that shows it.
776
+ // No logomark here — the design keeps it to bare `failproofai` and `config`.
698
777
  const url = `http://localhost:${DASHBOARD_PORT}/audit`;
699
- process.stdout.write(
700
- `\n ${c(DIM, "starting the dashboard…")}\n` +
701
- ` ${c(PINK, "✦")} ${c(BOLD, "here's your audit")} ${c(DIM, "→")} ${c(CYAN, url)}\n` +
702
- ` ${c(DIM, "(opening in your browser — press Ctrl+C to stop the server)")}\n\n`,
703
- );
778
+ printAfter([
779
+ ...(persisted ? [] : [k.caution("Could not save the audit, so the dashboard may show nothing.")]),
780
+ k.ok(`The audit is ready: ${k.cmd(url)}`),
781
+ "",
782
+ k.keys(["ctrl+c stop the dashboard"]),
783
+ ]);
704
784
 
705
785
  // Open the page once the server answers (best-effort, detached), then start
706
786
  // the server. `launch("start")` blocks-by-keeping-alive — it spawns the
707
787
  // bundled standalone dashboard and the process stays up serving it.
708
788
  openWhenReady(DASHBOARD_PORT, "/audit");
709
789
  const { launch } = await import("../../scripts/launch");
710
- launch("start");
790
+ launch("start", { screen: false });
711
791
  // Intentionally no process.exit(): launch() keeps this process alive running
712
792
  // the dashboard until the user stops it.
713
793
  }