failproofai 1.0.0-beta.0 → 1.0.0-beta.10

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 (357) hide show
  1. package/.next/standalone/.next/BUILD_ID +1 -1
  2. package/.next/standalone/.next/app-path-routes-manifest.json +2 -1
  3. package/.next/standalone/.next/build-manifest.json +6 -6
  4. package/.next/standalone/.next/prerender-manifest.json +3 -3
  5. package/.next/standalone/.next/required-server-files.json +1 -1
  6. package/.next/standalone/.next/routes-manifest.json +6 -0
  7. package/.next/standalone/.next/server/app/_global-error/page/build-manifest.json +3 -3
  8. package/.next/standalone/.next/server/app/_global-error/page/server-reference-manifest.json +1 -1
  9. package/.next/standalone/.next/server/app/_global-error/page.js +3 -1
  10. package/.next/standalone/.next/server/app/_global-error/page.js.nft.json +1 -1
  11. package/.next/standalone/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
  12. package/.next/standalone/.next/server/app/_global-error.html +1 -1
  13. package/.next/standalone/.next/server/app/_global-error.rsc +7 -7
  14. package/.next/standalone/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +2 -2
  15. package/.next/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +7 -7
  16. package/.next/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +3 -3
  17. package/.next/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +3 -3
  18. package/.next/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  19. package/.next/standalone/.next/server/app/_not-found/page/build-manifest.json +3 -3
  20. package/.next/standalone/.next/server/app/_not-found/page/server-reference-manifest.json +1 -1
  21. package/.next/standalone/.next/server/app/_not-found/page.js +3 -1
  22. package/.next/standalone/.next/server/app/_not-found/page.js.nft.json +1 -1
  23. package/.next/standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  24. package/.next/standalone/.next/server/app/_not-found.html +1 -1
  25. package/.next/standalone/.next/server/app/_not-found.rsc +15 -15
  26. package/.next/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +15 -15
  27. package/.next/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +4 -4
  28. package/.next/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +10 -10
  29. package/.next/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +2 -2
  30. package/.next/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +3 -3
  31. package/.next/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
  32. package/.next/standalone/.next/server/app/api/audit/invite/route.js +3 -2
  33. package/.next/standalone/.next/server/app/api/audit/invite/route.js.nft.json +1 -1
  34. package/.next/standalone/.next/server/app/api/audit/run/route.js +4 -3
  35. package/.next/standalone/.next/server/app/api/audit/run/route.js.nft.json +1 -1
  36. package/.next/standalone/.next/server/app/api/audit/status/route.js +1 -1
  37. package/.next/standalone/.next/server/app/api/audit/status/route.js.nft.json +1 -1
  38. package/.next/standalone/.next/server/app/api/auth/login-request/route.js +2 -1
  39. package/.next/standalone/.next/server/app/api/auth/login-request/route.js.nft.json +1 -1
  40. package/.next/standalone/.next/server/app/api/auth/login-verify/route.js +3 -2
  41. package/.next/standalone/.next/server/app/api/auth/login-verify/route.js.nft.json +1 -1
  42. package/.next/standalone/.next/server/app/api/auth/logout/route.js +3 -2
  43. package/.next/standalone/.next/server/app/api/auth/logout/route.js.nft.json +1 -1
  44. package/.next/standalone/.next/server/app/api/auth/reminder/route.js +3 -2
  45. package/.next/standalone/.next/server/app/api/auth/reminder/route.js.nft.json +1 -1
  46. package/.next/standalone/.next/server/app/api/auth/status/route.js +3 -2
  47. package/.next/standalone/.next/server/app/api/auth/status/route.js.nft.json +1 -1
  48. package/.next/standalone/.next/server/app/api/download/[project]/[session]/route.js +1 -1
  49. package/.next/standalone/.next/server/app/api/download/[project]/[session]/route.js.nft.json +1 -1
  50. package/.next/standalone/.next/server/app/audit/page/build-manifest.json +3 -3
  51. package/.next/standalone/.next/server/app/audit/page/server-reference-manifest.json +2 -2
  52. package/.next/standalone/.next/server/app/audit/page.js +5 -3
  53. package/.next/standalone/.next/server/app/audit/page.js.nft.json +1 -1
  54. package/.next/standalone/.next/server/app/audit/page_client-reference-manifest.js +1 -1
  55. package/.next/standalone/.next/server/app/index.html +1 -1
  56. package/.next/standalone/.next/server/app/index.rsc +15 -15
  57. package/.next/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +2 -2
  58. package/.next/standalone/.next/server/app/index.segments/_full.segment.rsc +15 -15
  59. package/.next/standalone/.next/server/app/index.segments/_head.segment.rsc +4 -4
  60. package/.next/standalone/.next/server/app/index.segments/_index.segment.rsc +10 -10
  61. package/.next/standalone/.next/server/app/index.segments/_tree.segment.rsc +2 -2
  62. package/.next/standalone/.next/server/app/page/build-manifest.json +3 -3
  63. package/.next/standalone/.next/server/app/page/server-reference-manifest.json +1 -1
  64. package/.next/standalone/.next/server/app/page.js +3 -1
  65. package/.next/standalone/.next/server/app/page.js.nft.json +1 -1
  66. package/.next/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
  67. package/.next/standalone/.next/server/app/policies/page/build-manifest.json +3 -3
  68. package/.next/standalone/.next/server/app/policies/page/server-reference-manifest.json +30 -18
  69. package/.next/standalone/.next/server/app/policies/page.js +5 -3
  70. package/.next/standalone/.next/server/app/policies/page.js.nft.json +1 -1
  71. package/.next/standalone/.next/server/app/policies/page_client-reference-manifest.js +1 -1
  72. package/.next/standalone/.next/server/app/project/[name]/page/build-manifest.json +3 -3
  73. package/.next/standalone/.next/server/app/project/[name]/page/server-reference-manifest.json +1 -1
  74. package/.next/standalone/.next/server/app/project/[name]/page.js +4 -2
  75. package/.next/standalone/.next/server/app/project/[name]/page.js.nft.json +1 -1
  76. package/.next/standalone/.next/server/app/project/[name]/page_client-reference-manifest.js +1 -1
  77. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page/build-manifest.json +3 -3
  78. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page/react-loadable-manifest.json +2 -2
  79. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page/server-reference-manifest.json +2 -2
  80. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page.js +6 -4
  81. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page.js.nft.json +1 -1
  82. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page_client-reference-manifest.js +1 -1
  83. package/.next/standalone/.next/server/app/projects/page/build-manifest.json +3 -3
  84. package/.next/standalone/.next/server/app/projects/page/server-reference-manifest.json +1 -1
  85. package/.next/standalone/.next/server/app/projects/page.js +4 -2
  86. package/.next/standalone/.next/server/app/projects/page.js.nft.json +1 -1
  87. package/.next/standalone/.next/server/app/projects/page_client-reference-manifest.js +1 -1
  88. package/.next/standalone/.next/server/app/settings/page/app-paths-manifest.json +3 -0
  89. package/.next/standalone/.next/server/app/settings/page/build-manifest.json +18 -0
  90. package/.next/standalone/.next/server/app/settings/page/next-font-manifest.json +6 -0
  91. package/.next/standalone/.next/server/app/settings/page/react-loadable-manifest.json +1 -0
  92. package/.next/standalone/.next/server/app/settings/page/server-reference-manifest.json +53 -0
  93. package/.next/standalone/.next/server/app/settings/page.js +18 -0
  94. package/.next/standalone/.next/server/app/settings/page.js.map +5 -0
  95. package/.next/standalone/.next/server/app/settings/page.js.nft.json +1 -0
  96. package/.next/standalone/.next/server/app/settings/page_client-reference-manifest.js +3 -0
  97. package/.next/standalone/.next/server/app-paths-manifest.json +2 -1
  98. package/.next/standalone/.next/server/chunks/[externals]__0h9_83k._.js +3 -0
  99. package/.next/standalone/.next/server/chunks/{[externals]__1nl3dvw._.js → [externals]__0r-zamh._.js} +2 -2
  100. package/.next/standalone/.next/server/chunks/[externals]__1ifpkx-._.js +3 -0
  101. package/.next/standalone/.next/server/chunks/[root-of-the-server]__06hexd0._.js +3 -0
  102. package/.next/standalone/.next/server/chunks/[root-of-the-server]__094p1f1._.js +3 -0
  103. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0_0xu5z._.js +1 -1
  104. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0ed01_d._.js +3 -0
  105. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0g0vcwt._.js +3 -0
  106. package/.next/standalone/.next/server/chunks/{[root-of-the-server]__0h3lfmp._.js → [root-of-the-server]__0ky_n0r._.js} +2 -2
  107. package/.next/standalone/.next/server/chunks/{[root-of-the-server]__18d-9oi._.js → [root-of-the-server]__0lk_935._.js} +2 -2
  108. package/.next/standalone/.next/server/chunks/{[root-of-the-server]__16le-kd._.js → [root-of-the-server]__0pbp9fe._.js} +2 -2
  109. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0q-v9z2._.js +1 -1
  110. package/.next/standalone/.next/server/chunks/{[root-of-the-server]__10oa-v9._.js → [root-of-the-server]__0v2fpxe._.js} +2 -2
  111. package/.next/standalone/.next/server/chunks/{[root-of-the-server]__0sb_5m8._.js → [root-of-the-server]__131ebum._.js} +3 -3
  112. package/.next/standalone/.next/server/chunks/{[root-of-the-server]__0-dh5jy._.js → [root-of-the-server]__1_ps0jw._.js} +2 -2
  113. package/.next/standalone/.next/server/chunks/{[root-of-the-server]__0l4vuqm._.js → [root-of-the-server]__1a7i2q4._.js} +2 -2
  114. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1ibjy5x._.js +3 -0
  115. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1iny24d._.js +3 -0
  116. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1tvn4wf._.js +1 -1
  117. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1tyedjt._.js +3 -0
  118. package/.next/standalone/.next/server/chunks/_0lxbzdq._.js +5 -0
  119. package/.next/standalone/.next/server/chunks/_1zuiiy3._.js +5 -0
  120. package/.next/standalone/.next/server/chunks/lib_telemetry_ts_0dctyyw._.js +5 -0
  121. package/.next/standalone/.next/server/chunks/node_modules_next_dist_esm_build_templates_app-route_17k9e3w.js +5 -5
  122. package/.next/standalone/.next/server/chunks/node_modules_posthog-node_dist_entrypoints_index_node_mjs_01r25oi._.js +2 -1
  123. package/.next/standalone/.next/server/chunks/node_modules_posthog-node_dist_entrypoints_index_node_mjs_09z9-p7._.js +2 -1
  124. package/.next/standalone/.next/server/chunks/package_json_[json]_cjs_1nxcc4v._.js +1 -1
  125. package/.next/standalone/.next/server/chunks/ssr/[externals]__1aq139v._.js +3 -0
  126. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__04pwh4t._.js +3 -0
  127. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__06bp_fg._.js +5 -0
  128. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__074qmkp._.js +3 -0
  129. package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__170799-._.js → [root-of-the-server]__0spkm68._.js} +11 -11
  130. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__10butu6._.js +3 -0
  131. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1a2hcb_._.js +3 -0
  132. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1qapotl._.js +1 -1
  133. package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__1apb7k_._.js → [root-of-the-server]__1rdruw4._.js} +2 -2
  134. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1rv-sc9._.js +3 -0
  135. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1tyf0tc._.js +5 -0
  136. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1u-wx42._.js +4 -0
  137. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1v185mo._.js +3 -0
  138. package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__01od0m6._.js → [root-of-the-server]__1yw368c._.js} +2 -2
  139. package/.next/standalone/.next/server/chunks/ssr/_0-yi74u._.js +3 -0
  140. package/.next/standalone/.next/server/chunks/ssr/_04mekdy._.js +3 -0
  141. package/.next/standalone/.next/server/chunks/ssr/_0r-dsre._.js +3 -0
  142. package/.next/standalone/.next/server/chunks/ssr/_0rjgfln._.js +3 -0
  143. package/.next/standalone/.next/server/chunks/ssr/_0se484m._.js +3 -0
  144. package/.next/standalone/.next/server/chunks/ssr/_0uz7o9y._.js +3 -0
  145. package/.next/standalone/.next/server/chunks/ssr/_11_p9y8._.js +1 -1
  146. package/.next/standalone/.next/server/chunks/ssr/_1e19atl._.js +1 -1
  147. package/.next/standalone/.next/server/chunks/ssr/_1fahg9e._.js +3 -0
  148. package/.next/standalone/.next/server/chunks/ssr/_1gmzkwj._.js +5 -0
  149. package/.next/standalone/.next/server/chunks/ssr/_1w8sola._.js +3 -0
  150. package/.next/standalone/.next/server/chunks/ssr/_1w_6p9g._.js +3 -0
  151. package/.next/standalone/.next/server/chunks/ssr/_1y4qr7u._.js +3 -0
  152. package/.next/standalone/.next/server/chunks/ssr/app_audit__components_audit-dashboard_tsx_0p9ud47._.js +2 -2
  153. package/.next/standalone/.next/server/chunks/ssr/app_global-error_tsx_1kp6l3x._.js +1 -1
  154. package/.next/standalone/.next/server/chunks/ssr/app_policies_hooks-client_tsx_19dqvpc._.js +2 -2
  155. package/.next/standalone/.next/server/chunks/ssr/node_modules_next_dist_esm_build_templates_app-page_0dwo94s.js +4 -0
  156. package/.next/standalone/.next/server/chunks/ssr/node_modules_posthog-node_dist_entrypoints_index_node_mjs_11bnuzn._.js +2 -1
  157. package/.next/standalone/.next/server/chunks/ssr/src_hooks_builtin-policies_ts_09j2ndl._.js +3 -0
  158. package/.next/standalone/.next/server/chunks/ssr/src_hooks_fp-config_ts_04t589g._.js +5 -0
  159. package/.next/standalone/.next/server/middleware-build-manifest.js +6 -6
  160. package/.next/standalone/.next/server/middleware.js +2 -2
  161. package/.next/standalone/.next/server/pages/404.html +1 -1
  162. package/.next/standalone/.next/server/pages/500.html +1 -1
  163. package/.next/standalone/.next/server/server-reference-manifest.js +1 -1
  164. package/.next/standalone/.next/server/server-reference-manifest.json +74 -20
  165. package/.next/standalone/.next/static/chunks/00adubtgov21s.js +1 -0
  166. package/.next/standalone/.next/static/chunks/{03fmihek9n986.js → 06zgzyh3wv1ig.js} +1 -1
  167. package/.next/standalone/.next/static/chunks/{1hdhteui932bq.js → 0qmo2t50kpb30.js} +1 -1
  168. package/.next/standalone/.next/static/chunks/{1grd7y94-igz9.js → 0yudogeqddpzr.js} +1 -1
  169. package/.next/standalone/.next/static/chunks/16p68cybetr31.js +1 -0
  170. package/.next/standalone/.next/static/chunks/1bm0q12hgs22l.css +2 -0
  171. package/.next/standalone/.next/static/chunks/{1d6ncspjagm53.js → 1fu90ch80qngv.js} +1 -1
  172. package/.next/standalone/.next/static/chunks/{2vr7aazw6k2-n.js → 1m416lie3qwsj.js} +1 -1
  173. package/.next/standalone/.next/static/chunks/1v1gqvpjk62jm.js +1 -0
  174. package/.next/standalone/.next/static/chunks/1ypqzvoal8j56.js +6 -0
  175. package/.next/standalone/.next/static/chunks/23kev26by1e4f.js +1 -0
  176. package/.next/standalone/.next/static/chunks/{29k6ef49zppxj.js → 2h4sbmp0ad78w.js} +1 -1
  177. package/.next/standalone/.next/static/chunks/2obwg4tterjpa.js +1 -0
  178. package/.next/standalone/.next/static/chunks/2uvhw57mf9lg_.css +2 -0
  179. package/.next/standalone/.next/static/chunks/{29ydr-8q0zis6.js → 30ufy4_e2n78e.js} +22 -22
  180. package/.next/standalone/.next/static/chunks/31coz11ia5vc4.js +1 -0
  181. package/.next/standalone/.next/static/chunks/{1s-_swlc80y43.js → 3p4j0l1o_p-nm.js} +1 -1
  182. package/.next/standalone/.next/static/chunks/{0ip1n8xmgxarv.js → 3uxnxwxcctyj0.js} +1 -1
  183. package/.next/standalone/.next/static/chunks/{turbopack-1lsv92x7klwrn.js → turbopack-3diub4ctvn1n0.js} +1 -1
  184. package/.next/standalone/app/actions/get-active-pauses.ts +16 -0
  185. package/.next/standalone/app/actions/get-hooks-config.ts +2 -1
  186. package/.next/standalone/app/actions/get-scheduled-audit.ts +70 -0
  187. package/.next/standalone/app/actions/update-scheduled-audit.ts +44 -0
  188. package/.next/standalone/app/api/audit/run/route.ts +28 -0
  189. package/.next/standalone/app/api/audit/status/route.ts +13 -1
  190. package/.next/standalone/app/api/auth/status/route.ts +1 -1
  191. package/.next/standalone/app/audit/_components/auth-dialog.css +159 -0
  192. package/.next/standalone/app/audit/_components/auth-dialog.tsx +3 -0
  193. package/.next/standalone/app/audit/audit-styles.css +6 -149
  194. package/.next/standalone/app/components/pause-notices.tsx +111 -0
  195. package/.next/standalone/app/policies/hooks-client.tsx +81 -6
  196. package/.next/standalone/app/settings/page.tsx +31 -0
  197. package/.next/standalone/app/settings/settings-client.tsx +488 -0
  198. package/.next/standalone/components/navbar.tsx +2 -0
  199. package/.next/standalone/lib/auth/api-server-client.ts +2 -3
  200. package/.next/standalone/lib/auth/auth-store.ts +4 -4
  201. package/.next/standalone/lib/codex-sessions.ts +2 -1
  202. package/.next/standalone/lib/dashboard-host.ts +101 -0
  203. package/.next/standalone/lib/install-check.ts +22 -7
  204. package/.next/standalone/lib/pi-sessions.ts +99 -14
  205. package/.next/standalone/lib/telemetry-enabled.ts +54 -0
  206. package/.next/standalone/lib/telemetry-id.ts +52 -1
  207. package/.next/standalone/lib/telemetry.ts +8 -4
  208. package/.next/standalone/node_modules/react/cjs/react.development.js +1 -1
  209. package/.next/standalone/node_modules/react/cjs/react.production.js +1 -1
  210. package/.next/standalone/node_modules/react/package.json +3 -3
  211. package/.next/standalone/node_modules/react-dom/cjs/react-dom-server-legacy.browser.production.js +1 -1
  212. package/.next/standalone/node_modules/react-dom/cjs/react-dom-server-legacy.node.production.js +1 -1
  213. package/.next/standalone/node_modules/react-dom/cjs/react-dom-server.browser.production.js +3 -3
  214. package/.next/standalone/node_modules/react-dom/cjs/react-dom-server.edge.production.js +3 -3
  215. package/.next/standalone/node_modules/react-dom/cjs/react-dom-server.node.production.js +3 -3
  216. package/.next/standalone/node_modules/react-dom/cjs/react-dom.production.js +1 -1
  217. package/.next/standalone/node_modules/react-dom/package.json +4 -4
  218. package/.next/standalone/package.json +17 -10
  219. package/.next/standalone/proxy.ts +89 -1
  220. package/.next/standalone/server.js +1 -1
  221. package/bin/failproofai.mjs +518 -79
  222. package/dist/cli.mjs +11484 -6046
  223. package/dist/worker.mjs +2019 -272
  224. package/lib/auth/api-server-client.ts +2 -3
  225. package/lib/auth/auth-store.ts +4 -4
  226. package/lib/codex-sessions.ts +2 -1
  227. package/lib/dashboard-host.ts +101 -0
  228. package/lib/install-check.ts +22 -7
  229. package/lib/pi-sessions.ts +99 -14
  230. package/lib/telemetry-enabled.ts +54 -0
  231. package/lib/telemetry-id.ts +52 -1
  232. package/lib/telemetry.ts +8 -4
  233. package/package.json +17 -10
  234. package/scripts/build-daemon-packages.mjs +210 -0
  235. package/scripts/daemon-platforms.mjs +42 -0
  236. package/scripts/install-telemetry.mjs +8 -1
  237. package/scripts/launch.ts +25 -3
  238. package/scripts/parse-script-args.ts +17 -1
  239. package/scripts/prune-standalone.mjs +26 -0
  240. package/scripts/publish-aliases.mjs +62 -41
  241. package/scripts/repro-npm-install.sh +155 -0
  242. package/scripts/translate-docs/readme-translator.ts +163 -4
  243. package/scripts/validate-mdx.ts +153 -13
  244. package/src/audit/audit-lock.ts +285 -0
  245. package/src/audit/audit-schedule.ts +111 -0
  246. package/src/audit/cache.ts +18 -4
  247. package/src/audit/cli-adapters/goose.ts +2 -1
  248. package/src/audit/cli-adapters/hermes.ts +28 -10
  249. package/src/audit/cli.ts +191 -35
  250. package/src/audit/dashboard-cache.ts +2 -1
  251. package/src/hooks/backfill-cli.ts +247 -0
  252. package/src/hooks/builtin-policies.ts +390 -2
  253. package/src/hooks/cloud-connection.ts +354 -0
  254. package/src/hooks/cloud-enrollment-cli.ts +325 -0
  255. package/src/hooks/cloud-enrollment.ts +243 -0
  256. package/src/hooks/cloud-introspect.ts +147 -0
  257. package/src/hooks/cloud-managed-policies.ts +169 -0
  258. package/src/hooks/collector-config.ts +234 -0
  259. package/src/hooks/configure-wizard.ts +915 -158
  260. package/src/hooks/custom-hooks-loader.ts +191 -33
  261. package/src/hooks/daemon-client.ts +136 -17
  262. package/src/hooks/daemon-download.ts +192 -30
  263. package/src/hooks/daemon-service.ts +1190 -92
  264. package/src/hooks/first-run-gate.ts +50 -0
  265. package/src/hooks/fp-config.ts +497 -0
  266. package/src/hooks/fp-home.ts +375 -0
  267. package/src/hooks/fp-reset.ts +464 -0
  268. package/src/hooks/handler.ts +185 -6
  269. package/src/hooks/hook-activity-store.ts +85 -20
  270. package/src/hooks/hook-logger.ts +2 -1
  271. package/src/hooks/hook-telemetry.ts +5 -1
  272. package/src/hooks/hooks-config.ts +4 -3
  273. package/src/hooks/loader-utils.ts +171 -8
  274. package/src/hooks/manager.ts +5 -4
  275. package/src/hooks/onboarding-attempt.ts +179 -0
  276. package/src/hooks/onboarding-lock.ts +119 -0
  277. package/src/hooks/policy-types.ts +28 -0
  278. package/src/hooks/session-pause-cli.ts +159 -0
  279. package/src/hooks/session-pause.ts +268 -0
  280. package/src/hooks/setup-state.ts +256 -0
  281. package/src/hooks/tui.ts +119 -4
  282. package/src/hooks/uninstall-cli.ts +345 -0
  283. package/src/hooks/worker-server.ts +65 -3
  284. package/.next/standalone/.next/server/chunks/[externals]__14odj07._.js +0 -3
  285. package/.next/standalone/.next/server/chunks/[externals]__1s61mel._.js +0 -3
  286. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0id_xf7._.js +0 -3
  287. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0longh-._.js +0 -3
  288. package/.next/standalone/.next/server/chunks/[root-of-the-server]__17g9wh7._.js +0 -3
  289. package/.next/standalone/.next/server/chunks/[root-of-the-server]__19120tr._.js +0 -3
  290. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1fwl2mz._.js +0 -3
  291. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1myjm-d._.js +0 -3
  292. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1rhmvod._.js +0 -3
  293. package/.next/standalone/.next/server/chunks/ssr/[externals]__1_g_b3t._.js +0 -3
  294. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0114ewg._.js +0 -4
  295. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0259h38._.js +0 -3
  296. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__02r5bgf._.js +0 -4
  297. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0e8sjqm._.js +0 -4
  298. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0lkzqax._.js +0 -4
  299. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0qucxyj._.js +0 -4
  300. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0u02miy._.js +0 -4
  301. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0uno39i._.js +0 -3
  302. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__14dd6h8._.js +0 -3
  303. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1ath6v_._.js +0 -4
  304. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1zj-87g._.js +0 -3
  305. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1zqz4v8._.js +0 -4
  306. package/.next/standalone/.next/server/chunks/ssr/_1m_p7pi._.js +0 -3
  307. package/.next/standalone/.next/server/chunks/ssr/node_modules_html-to-image_es_index_1-upb14.js +0 -3
  308. package/.next/standalone/.next/server/chunks/ssr/src_hooks_1ezd2jf._.js +0 -5
  309. package/.next/standalone/.next/server/chunks/ssr/src_hooks_1tnuifj._.js +0 -5
  310. package/.next/standalone/.next/static/chunks/0h7auy7hzjyhw.js +0 -1
  311. package/.next/standalone/.next/static/chunks/0u9hc6oxx-gpj.js +0 -6
  312. package/.next/standalone/.next/static/chunks/0zbxssxh53n-3.js +0 -1
  313. package/.next/standalone/.next/static/chunks/2mazwb1qmur1p.js +0 -1
  314. package/.next/standalone/.next/static/chunks/2pujpnfx4pws6.css +0 -2
  315. package/.next/standalone/.next/static/chunks/33u59vf_8xpd-.js +0 -1
  316. package/.next/standalone/.next/static/chunks/3i27c3hcriawq.css +0 -1
  317. package/.next/standalone/.next/static/chunks/3wrgbih-516uj.js +0 -1
  318. package/.next/standalone/Cargo.lock +0 -428
  319. package/.next/standalone/Cargo.toml +0 -9
  320. package/.next/standalone/crates/PROTOCOL.md +0 -122
  321. package/.next/standalone/crates/failproofaid/Cargo.toml +0 -18
  322. package/.next/standalone/crates/failproofaid/src/lock.rs +0 -81
  323. package/.next/standalone/crates/failproofaid/src/main.rs +0 -83
  324. package/.next/standalone/crates/failproofaid/src/paths.rs +0 -172
  325. package/.next/standalone/crates/failproofaid/src/server.rs +0 -502
  326. package/.next/standalone/crates/failproofaid/src/worker.rs +0 -400
  327. package/.next/standalone/crates/failproofaid/tests/daemon_e2e.rs +0 -241
  328. package/.next/standalone/crates/fpai-ipc/Cargo.toml +0 -16
  329. package/.next/standalone/crates/fpai-ipc/src/envelope.rs +0 -175
  330. package/.next/standalone/crates/fpai-ipc/src/framing.rs +0 -177
  331. package/.next/standalone/crates/fpai-ipc/src/lib.rs +0 -10
  332. package/.next/standalone/crates/fpai-ipc/src/peer.rs +0 -80
  333. package/.next/standalone/docker-hook-sync/Dockerfile +0 -79
  334. package/.next/standalone/docker-hook-sync/entrypoint.sh +0 -109
  335. package/.next/standalone/integration-suite/Dockerfile +0 -47
  336. package/.next/standalone/integration-suite/README.md +0 -129
  337. package/.next/standalone/integration-suite/canary-policies.mjs +0 -45
  338. package/.next/standalone/integration-suite/capture-tokens.sh +0 -48
  339. package/.next/standalone/integration-suite/ci-entrypoint.sh +0 -179
  340. package/.next/standalone/integration-suite/inject-tokens.sh +0 -44
  341. package/.next/standalone/integration-suite/install-clis.sh +0 -136
  342. package/.next/standalone/integration-suite/probe-cli.sh +0 -248
  343. package/.next/standalone/integration-suite/report.js +0 -120
  344. package/.next/standalone/integration-suite/run.sh +0 -159
  345. package/.next/standalone/openclaw-plugin/index.js +0 -223
  346. package/.next/standalone/openclaw-plugin/openclaw.plugin.json +0 -6
  347. package/.next/standalone/openclaw-plugin/package.json +0 -12
  348. package/.next/standalone/osv-scanner.toml +0 -17
  349. package/.next/standalone/pi-extension/index.ts +0 -524
  350. package/.next/standalone/pi-extension/package.json +0 -12
  351. package/.next/standalone/postcss.config.mjs +0 -8
  352. package/.next/standalone/readme-arch-hq.gif +0 -0
  353. package/.next/standalone/rust-toolchain.toml +0 -4
  354. package/src/auth/cli.ts +0 -371
  355. /package/.next/standalone/.next/static/{7Lxtd352rSaHB-pmm6G8M → pBBcTvE6_VueYGbacy1kg}/_buildManifest.js +0 -0
  356. /package/.next/standalone/.next/static/{7Lxtd352rSaHB-pmm6G8M → pBBcTvE6_VueYGbacy1kg}/_clientMiddlewareManifest.js +0 -0
  357. /package/.next/standalone/.next/static/{7Lxtd352rSaHB-pmm6G8M → pBBcTvE6_VueYGbacy1kg}/_ssgManifest.js +0 -0
@@ -124,13 +124,13 @@ if (hookIdx >= 0) {
124
124
  // machine until `failproofai config` has installed failproofaid AND
125
125
  // written the daemonConfigured marker (Stage 4). Until then this is
126
126
  // byte-for-byte the same handleHookEvent(...) call below.
127
- const { isDaemonConfigured, tryDaemonHook } = await import("../src/hooks/daemon-client");
127
+ const { isDaemonConfigured, attemptDaemonHook } = await import("../src/hooks/daemon-client");
128
128
  if (isDaemonConfigured()) {
129
129
  const { readStdinPayload } = await import("../src/hooks/read-stdin");
130
130
  const { evaluateHookEvent } = await import("../src/hooks/handler");
131
131
  const stdinRead = await readStdinPayload();
132
132
 
133
- const daemonResult = await tryDaemonHook({
133
+ const attempt = await attemptDaemonHook({
134
134
  hookEvent: eventType,
135
135
  cli,
136
136
  stdin: stdinRead.payload,
@@ -140,21 +140,40 @@ if (hookIdx >= 0) {
140
140
  cwd: process.cwd(),
141
141
  });
142
142
 
143
- // No silent fallback to in-process evaluation here: this machine was
144
- // explicitly configured to run through the daemon, so an unreachable
145
- // daemon fails closed instead — a loud, correctly-shaped deny (real
146
- // per-CLI response shaping, not a generic denial) rather than a
147
- // silent downgrade. See the plan's "Confirmed scope decisions".
148
- const result =
149
- daemonResult ??
150
- (await evaluateHookEvent(eventType, cli, stdinRead.payload, {
151
- forceDecision: {
152
- decision: "deny",
153
- reason:
154
- "failproofaid could not be reached. This machine is configured to run hooks through it " +
155
- "— check the daemon (see `failproofai config`) rather than retrying blindly.",
156
- },
157
- }));
143
+ // On a daemon-configured machine the daemon is the ONLY evaluator. Every
144
+ // way of not getting an answer from it denies, and in-process evaluation
145
+ // is never reached from this branch — that is what "all enforcement is
146
+ // routed through the daemon" means, and a fallback here would be a second
147
+ // policy engine reachable by breaking the first.
148
+ //
149
+ // The two failures still differ in what the USER has to do, so they are
150
+ // told apart in the message and nowhere else:
151
+ //
152
+ // protocol-mismatch: a daemon answered, so it is alive; the CLI and the
153
+ // daemon are different versions, which is what an `npm update` that
154
+ // has not been followed by `failproofai config` looks like. The remedy
155
+ // is an upgrade, and naming it is the difference between a one-command
156
+ // fix and a support ticket. `daemonVersionSkew()` has already been
157
+ // hinting this on every CLI command.
158
+ //
159
+ // unreachable: nothing answered. A stopped service, a deleted socket and
160
+ // deliberate tampering are indistinguishable from here — and a machine
161
+ // where stopping one service silently disables every guardrail is not
162
+ // a guarded machine.
163
+ let result;
164
+ if (attempt.ok) {
165
+ result = attempt.response;
166
+ } else {
167
+ const reason =
168
+ attempt.failure === "protocol-mismatch"
169
+ ? "failproofaid is running a different protocol version than this CLI, so it " +
170
+ "cannot evaluate this call. Run `failproofai config` to update the daemon."
171
+ : "failproofaid could not be reached. This machine is configured to run hooks through it " +
172
+ "— check the daemon (see `failproofai config`) rather than retrying blindly.";
173
+ result = await evaluateHookEvent(eventType, cli, stdinRead.payload, {
174
+ forceDecision: { decision: "deny", reason },
175
+ });
176
+ }
158
177
 
159
178
  if (result.stdout) process.stdout.write(result.stdout);
160
179
  if (result.stderr) process.stderr.write(result.stderr);
@@ -191,18 +210,8 @@ if (hookIdx >= 0) {
191
210
  * Error → unexpected; shows message only, exits 2
192
211
  */
193
212
  async function runCli() {
194
- // Report a fresh install / upgrade. Deliberately here rather than at module
195
- // scope: everything above this point is the --hook fast path, which runs on
196
- // every tool call. No-ops after the first run on a given version.
197
- try {
198
- const { maybeReportInstall } = await import("../lib/install-check");
199
- await maybeReportInstall(version);
200
- } catch {
201
- // never block a command on reporting
202
- }
203
-
204
213
  // --help / -h (only when not inside a subcommand that handles its own --help)
205
- const SUBCOMMANDS = ["policies", "policy", "auth", "audit", "config"];
214
+ const SUBCOMMANDS = ["policies", "policy", "audit", "config", "uninstall", "backfill"];
206
215
  if ((args.includes("--help") || args.includes("-h")) && !SUBCOMMANDS.includes(args[0])) {
207
216
  const extraArgs = args.filter((a) => a !== "--help" && a !== "-h");
208
217
  if (extraArgs.length > 0) {
@@ -243,16 +252,20 @@ COMMANDS
243
252
 
244
253
  policies --help, -h Show this help for the policies command
245
254
 
246
- auth Sign in / out of FailproofAI from the CLI.
247
- login Email + OTP flow; writes ~/.failproofai/auth.json
248
- logout Revoke this session and remove auth.json
249
- whoami Print the currently authenticated identity
250
- auth --help, -h Show this help for the auth command
251
-
252
255
  audit Audit your agent's behavior, then open the
253
256
  dashboard at http://localhost:8020/audit
254
257
  audit --help, -h Show this help for the audit command
255
258
 
259
+ uninstall Remove failproofai from this machine: hook
260
+ entries from every agent CLI, and the daemon
261
+ service. Run this BEFORE \`npm rm -g failproofai\`
262
+ — npm runs no uninstall script, so removing the
263
+ package alone leaves both behind.
264
+ --purge Also delete ~/.failproofai (settings,
265
+ credentials, audit history, daemon binary)
266
+ --dry-run Show what would be removed, change nothing
267
+ --yes, -y Skip the confirmation prompt
268
+
256
269
  --version, -v Print version and exit
257
270
  --help, -h Show this help message
258
271
 
@@ -301,6 +314,296 @@ LINKS
301
314
  process.exit(0);
302
315
  }
303
316
 
317
+ // First-run onboarding — before any subcommand runs its own work.
318
+ //
319
+ // On a machine that has never been set up, the first thing the user typed is
320
+ // almost never the thing they need first, so we run the wizard and then let
321
+ // their original command proceed. Exemptions matter more than the rule:
322
+ //
323
+ // --hook never reaches here (it exits above) — it runs on every
324
+ // tool call, and a wizard on that path would hang an agent
325
+ // --version/--help answering "what is this" must not require setup
326
+ // config IS the wizard
327
+ // policies/policy explicit configuration actions. Intercepting these would
328
+ // fight the intent the user just stated, and would break
329
+ // non-interactive scripts that call them to do setup.
330
+ //
331
+ // `maybeFirstRunConfigure` is itself a no-op on a configured machine, on a
332
+ // non-TTY, and under sudo — so this is a cheap check, not a second gate.
333
+ // Layout check, before onboarding decides anything. A home written by an
334
+ // older layout is reset here — visibly, in a real command the user typed —
335
+ // rather than from a hook, which runs unattended once per tool call. A home
336
+ // written by a NEWER layout stops the command instead: that data is fine and
337
+ // an upgrade would read it, so deleting it would destroy something
338
+ // recoverable.
339
+ //
340
+ // `audit --scheduled` is the exception, and it takes the HOOK's branch: it is
341
+ // spawned by failproofaid on a timer, so "visibly, in a real command the user
342
+ // typed" is exactly what it is not. `checkLayoutForCli()` deletes
343
+ // config.toml and credentials.toml (see `resettablePaths`), so letting a
344
+ // background process reach it would silently revoke a user's
345
+ // `[telemetry] enabled = false`, erase their cloud enrolment, and switch off
346
+ // `[audit] auto` — the setting that scheduled the run — with the explanation
347
+ // going only to the service journal. Reachable on every machine at the next
348
+ // LAYOUT_VERSION bump, when a home carrying `auto = true` is by definition
349
+ // stale. Verified live: one scheduled tick took a home's whole config.
350
+ //
351
+ // `--help` / `--version` take the same exemption `first-run-gate.ts` gives
352
+ // them, and for a stronger reason. Those two answer "what is this / how do I
353
+ // use it"; the subcommands that parse their own help (`policies --help`) fall
354
+ // past the help block above and reach here, so without this a user typing
355
+ // `failproofai policies --help` had their home reset by a question. The
356
+ // adjacent, far less destructive first-run gate exempted help from the start.
357
+ let layoutWasReset = false;
358
+ {
359
+ const isHelpOrVersion =
360
+ args.includes("--help") || args.includes("-h") || args.includes("--version") || args.includes("-v");
361
+ if (args[0] === "audit" && args.includes("--scheduled")) {
362
+ const { layoutWarningForHook } = await import("../src/hooks/fp-reset");
363
+ const warning = layoutWarningForHook();
364
+ if (warning) {
365
+ // Exit 1, not 75: 75 means "another audit holds the lock" and is retried
366
+ // in fifteen minutes, which here would just re-warn four times an hour
367
+ // forever. A stale home is a real failure that `failproofai config`
368
+ // fixes, so it is reported and retried at the ordinary cadence.
369
+ console.error(warning);
370
+ process.exit(1);
371
+ }
372
+ } else if (!isHelpOrVersion) {
373
+ const { checkLayoutForCli } = await import("../src/hooks/fp-reset");
374
+ const check = await checkLayoutForCli();
375
+ for (const line of check.lines) console.error(line);
376
+ if (check.fatal) process.exit(1);
377
+ layoutWasReset = check.didReset;
378
+ }
379
+ }
380
+
381
+ // Report a fresh install / upgrade. AFTER the layout check above, never
382
+ // before: this writes the `last-version` marker, and while that file lived at
383
+ // the root of the home it was one of the landmarks `detectLayout()` reads as
384
+ // "layout 1". Running first meant the CLI created the file and then read it
385
+ // back as evidence of an old layout, so every genuinely fresh machine had its
386
+ // very first command open with "failproofai reorganised … Removed 1 item(s)
387
+ // from the old layout." Nothing was lost — there was nothing there — but
388
+ // training every new user to ignore that banner is expensive given what it
389
+ // says on a real layout-1 home. The file has also moved under `state/`, so
390
+ // the two are independent now; the order is kept because it is the correct
391
+ // one regardless.
392
+ //
393
+ // Deliberately not at module scope: everything above the CLI entry is the
394
+ // --hook fast path, which runs on every tool call. No-ops after the first run
395
+ // on a given version.
396
+ try {
397
+ const { maybeReportInstall } = await import("../lib/install-check");
398
+ await maybeReportInstall(version);
399
+ } catch {
400
+ // never block a command on reporting
401
+ }
402
+
403
+ const { shouldOfferFirstRun } = await import("../src/hooks/first-run-gate");
404
+ if (shouldOfferFirstRun(args) || layoutWasReset) {
405
+ try {
406
+ const { maybeFirstRunConfigure } = await import("../src/hooks/configure-wizard");
407
+ // `audit` runs its own scan immediately after this returns; firing the
408
+ // post-setup audit too would scan the whole history twice in a row.
409
+ //
410
+ // `force` after a reset: the home's policy config is gone, but the agent
411
+ // CLIs' settings files were deliberately left alone, so `isConfigured()`
412
+ // still reads true off `hasGlobalHooks` and setup would be skipped —
413
+ // leaving hooks firing against no policies, silently and permanently.
414
+ await maybeFirstRunConfigure(
415
+ {},
416
+ { postSetupAudit: args[0] !== "audit", force: layoutWasReset },
417
+ );
418
+ } catch {
419
+ // Onboarding is never allowed to block the command the user actually typed.
420
+ }
421
+ }
422
+
423
+ // backfill [--since <YYYY-MM-DD|Nd>] [--dry-run]
424
+ //
425
+ // Hands off to the daemon rather than doing the work: the cursors it rewinds
426
+ // are held in memory by the RUNNING collector, which would write them back
427
+ // over. Every precondition a person can get wrong is still checked HERE,
428
+ // synchronously, because reporting success and leaving the real failure in the
429
+ // journal is what already cost twenty minutes on a live machine.
430
+ if (args[0] === "backfill") {
431
+ const subArgs = args.slice(1);
432
+ if (subArgs.includes("--help") || subArgs.includes("-h")) {
433
+ console.log(`
434
+ failproofai backfill — re-send history the collector has already read past
435
+
436
+ USAGE
437
+ failproofai backfill [--since <when>] [--dry-run]
438
+
439
+ WHY
440
+ The collector never re-reads a file it has a cursor for, which is right until
441
+ the dashboard's data is cleared, a machine is re-enrolled, or cursors advanced
442
+ before there was anywhere to send. Then the history exists on disk and nowhere
443
+ else, with no way to ask for it again.
444
+
445
+ Re-sending is safe: redaction is deterministic, so a re-sent event hashes
446
+ identically to its first send and collapses into the row already there.
447
+
448
+ OPTIONS
449
+ --since <when> How far back. \`30d\`, \`6m\`, or \`YYYY-MM-DD\`.
450
+ Default: 30 days.
451
+ --dry-run Report what would be re-read and change nothing.
452
+
453
+ Which streams are sent follows [collector] in ~/.failproofai/config.toml —
454
+ a backfill never sends something your config says you do not want.
455
+ `);
456
+ process.exit(0);
457
+ }
458
+
459
+ const KNOWN = new Set(["--since", "--dry-run"]);
460
+ const unknown = subArgs.find((a, i) => a.startsWith("-") && !KNOWN.has(a) && subArgs[i - 1] !== "--since");
461
+ if (unknown) {
462
+ throw new CliError(`Unexpected argument: ${unknown}\nRun \`failproofai backfill --help\` for usage.`);
463
+ }
464
+
465
+ let sinceMs;
466
+ const sinceIdx = subArgs.indexOf("--since");
467
+ if (sinceIdx >= 0) {
468
+ const raw = subArgs[sinceIdx + 1];
469
+ if (!raw || raw.startsWith("-")) throw new CliError("Missing value after --since.");
470
+ // `30d` / `6m` / an ISO date. Rejected rather than guessed at: silently
471
+ // reading an unparseable window as "the default" would send a different
472
+ // amount of history than was asked for, and nothing would say so.
473
+ const rel = /^(\d+)([dmy])$/.exec(raw);
474
+ if (rel) {
475
+ const n = Number(rel[1]);
476
+ const days = rel[2] === "d" ? n : rel[2] === "m" ? n * 30 : n * 365;
477
+ sinceMs = Date.now() - days * 24 * 60 * 60 * 1000;
478
+ } else {
479
+ const t = Date.parse(raw);
480
+ if (Number.isNaN(t)) {
481
+ throw new CliError(`Could not read --since ${raw}. Use 30d, 6m, or YYYY-MM-DD.`);
482
+ }
483
+ sinceMs = t;
484
+ }
485
+ }
486
+
487
+ lastSubcommand = "backfill";
488
+ const { runBackfillCommand } = await import("../src/hooks/backfill-cli");
489
+ const result = runBackfillCommand({ sinceMs, dryRun: subArgs.includes("--dry-run") });
490
+ for (const line of result.lines) {
491
+ if (result.exitCode === 0) console.log(line);
492
+ else console.error(line);
493
+ }
494
+ await track("cli_backfill", { ok: result.exitCode === 0, dry_run: subArgs.includes("--dry-run"), explicit_since: sinceIdx >= 0 });
495
+ lastSubcommand = null;
496
+ await exitAfterFlush(result.exitCode);
497
+ return;
498
+ }
499
+
500
+ // uninstall [--purge] [--dry-run] [--yes|-y]
501
+ //
502
+ // Top-level, and deliberately NOT a flag on `policies`. `policies --uninstall`
503
+ // disables policies; this removes the product — hook entries across every
504
+ // agent CLI plus the root-owned daemon service — and the two must not be one
505
+ // keystroke apart.
506
+ if (args[0] === "uninstall") {
507
+ const subArgs = args.slice(1);
508
+ if (subArgs.includes("--help") || subArgs.includes("-h")) {
509
+ console.log(`
510
+ failproofai uninstall — remove failproofai from this machine
511
+
512
+ USAGE
513
+ failproofai uninstall [--purge] [--dry-run] [--yes]
514
+
515
+ WHAT IT REMOVES
516
+ • failproofai hook entries from every agent CLI that has them
517
+ • the failproofaid daemon service (needs sudo)
518
+ • the "require the daemon" flag — cleared FIRST, so a partial uninstall can
519
+ never leave this machine denying every tool call
520
+
521
+ OPTIONS
522
+ --purge Also delete ~/.failproofai — settings, credentials, audit
523
+ history and the downloaded daemon binary. Off by default so a
524
+ reinstall keeps your history.
525
+ --dry-run Print what would be removed and change nothing.
526
+ --yes, -y Skip the confirmation prompt. Required when there is no TTY.
527
+
528
+ WHY THIS EXISTS
529
+ npm runs no uninstall script, so \`npm rm -g failproofai\` removes the package
530
+ and leaves the hook entries and the service behind. Run this first, then:
531
+ npm rm -g failproofai
532
+ `);
533
+ process.exit(0);
534
+ }
535
+
536
+ const KNOWN = new Set(["--purge", "--dry-run", "--yes", "-y"]);
537
+ const unknown = subArgs.find((a) => !KNOWN.has(a));
538
+ if (unknown) {
539
+ throw new CliError(
540
+ `Unexpected argument: ${unknown}\nRun \`failproofai uninstall --help\` for usage.`,
541
+ );
542
+ }
543
+
544
+ lastSubcommand = "uninstall_command";
545
+ const { runUninstallCommand } = await import("../src/hooks/uninstall-cli");
546
+ const purge = subArgs.includes("--purge");
547
+ const dryRun = subArgs.includes("--dry-run");
548
+ const yes = subArgs.includes("--yes") || subArgs.includes("-y");
549
+
550
+ // Set only when the prompt actually rendered the plan, which is the one
551
+ // case where printing it again would duplicate it.
552
+ let planWasShown = false;
553
+ const result = await runUninstallCommand({
554
+ purge,
555
+ dryRun,
556
+ yes,
557
+ cwd: process.cwd(),
558
+ // Only offered when a person can actually answer. Without a TTY the
559
+ // command requires --yes rather than assuming consent — see the module.
560
+ confirm: process.stdin.isTTY
561
+ ? async (planLines) => {
562
+ planWasShown = true;
563
+ const { selectOne } = await import("../src/hooks/tui");
564
+ // "No" first, so the default landing position on Enter is the
565
+ // non-destructive one.
566
+ const answer = await selectOne({
567
+ message: purge
568
+ ? "Remove failproofai and DELETE ~/.failproofai?"
569
+ : "Remove failproofai from this machine?",
570
+ body: planLines,
571
+ choices: [
572
+ { label: "No, cancel", value: false },
573
+ { label: purge ? "Yes, remove and purge" : "Yes, remove it", value: true },
574
+ ],
575
+ });
576
+ return answer === true;
577
+ }
578
+ : undefined,
579
+ });
580
+
581
+ // The prompt already rendered the plan as its body; re-printing it would
582
+ // show the same block twice. `planLines` is reported by the command rather
583
+ // than guessed from the text — see UninstallResult.
584
+ const skip = planWasShown ? result.planLines : 0;
585
+ for (const line of result.lines.slice(skip)) {
586
+ if (result.exitCode === 0) console.log(line);
587
+ else console.error(line);
588
+ }
589
+ // NOT after a purge. `track` resolves the instance id, and `getInstanceId()`
590
+ // lazily WRITES ~/.failproofai/state/telemetry-id — which re-created the
591
+ // whole directory seconds after the purge deleted it, leaving a machine the
592
+ // user had just wiped holding a brand-new tracking identifier and making
593
+ // the command's own "✓ deleted" line false. A purge means gone; nothing
594
+ // gets to touch the home afterwards, least of all telemetry.
595
+ if (!result.purged) {
596
+ await track("cli_uninstall_command", {
597
+ ok: result.exitCode === 0,
598
+ purge,
599
+ dry_run: dryRun,
600
+ });
601
+ }
602
+ lastSubcommand = null;
603
+ await exitAfterFlush(result.exitCode);
604
+ return;
605
+ }
606
+
304
607
  // policies [--install|-i|--uninstall|-u|--help|-h] [names...] [--scope] [--beta] [--custom|-c <path>]
305
608
  if (args[0] === "policies") {
306
609
  const subArgs = args.slice(1);
@@ -566,19 +869,6 @@ EXAMPLES
566
869
  process.exit(0);
567
870
  }
568
871
 
569
- // auth — email-OTP login flow against the FailproofAI api-server.
570
- if (args[0] === "auth") {
571
- lastSubcommand = "auth";
572
- const { runAuthCli } = await import("../src/auth/cli");
573
- await runAuthCli(args.slice(1));
574
- await track("cli_auth_invoked", {
575
- args_count: args.length - 1,
576
- subcommand: args[1] ?? "help",
577
- exit_code: process.exitCode ?? 0,
578
- });
579
- process.exit(process.exitCode ?? 0);
580
- }
581
-
582
872
  // audit — scan local agent-CLI history, then launch the dashboard at /audit.
583
873
  if (args[0] === "audit") {
584
874
  lastSubcommand = "audit";
@@ -769,19 +1059,183 @@ WHAT IT DOES
769
1059
  3. Policies — presets (combine any), Everything, or a custom pick
770
1060
  4. Review — confirms the exact files it will change, then applies
771
1061
 
1062
+ FAILPROOF CLOUD
1063
+ failproofai config --connect <url> --token <key> [--machine-id <id>]
1064
+ Connect this machine to Failproof Cloud
1065
+ [--no-transcripts] decisions only, no transcripts
1066
+ failproofai config --disconnect Stop pulling policy and sending activity
1067
+ failproofai config --status Show connection and pause state
1068
+
1069
+ One connection, two capabilities: this machine PULLS centrally-managed
1070
+ policies and SENDS what its hooks decided, so the dashboard shows the fleet
1071
+ it is enforcing on. Both are checked against the server before anything is
1072
+ written, and reported separately — a key carrying policies:pull but not
1073
+ events:add connects for policy and says exactly why the dashboard is empty.
1074
+
1075
+ Tokens are stored owner-only in ~/.failproofai/, never in the service unit —
1076
+ that file is world-readable. Connecting needs no sudo, and the machine id
1077
+ defaults to this host's name.
1078
+
1079
+ Connecting sends BOTH policy decisions and full session transcripts. A
1080
+ transcript carries prompts, file contents and whatever was pasted into a
1081
+ terminal — that is the point of connecting, and it is stated here rather than
1082
+ buried behind a flag nobody finds. Use --no-transcripts for decisions only.
1083
+
1084
+ PAUSING ENFORCEMENT (one session, always time-boxed)
1085
+ failproofai config --pause Pause this directory's newest agent session (30m)
1086
+ failproofai config --pause 10m Pause for a given time (max 8h; s/m/h, bare = minutes)
1087
+ failproofai config --resume End the pause early
1088
+ failproofai config --status Show what is paused and when it lifts
1089
+ --session <id> Target a specific session
1090
+ --all With --resume, end every active pause
1091
+
1092
+ A pause suspends builtin, custom and convention policies for that session
1093
+ only, and always expires on its own. Cloud-managed policies keep enforcing.
1094
+
772
1095
  Prefer flags? See \`failproofai policies --help\`.
773
1096
  `.trimStart());
774
1097
  process.exit(0);
775
1098
  }
776
1099
  lastSubcommand = "config";
1100
+
1101
+ // --pause / --resume / --status are non-interactive session actions that
1102
+ // share `config`'s surface but not the wizard. They write session state,
1103
+ // never the config file — a pause that reached policies-config.json would
1104
+ // be committed and outlive the session that asked for it.
1105
+ // Cloud enrolment. Deliberately writes a credential file the daemon reads
1106
+ // rather than an Environment= line in the service unit: that unit is
1107
+ // installed world-readable (0644, /etc/systemd/system), so a token there
1108
+ // would be readable by every local user. Keeping it out also means no
1109
+ // sudo, and lets an already-installed daemon be connected.
1110
+ const connectIdx = args.indexOf("--connect");
1111
+ const wantsDisconnect = args.includes("--disconnect");
1112
+ if (connectIdx >= 0 || wantsDisconnect) {
1113
+ if (connectIdx >= 0 && wantsDisconnect) {
1114
+ throw new CliError("--connect and --disconnect cannot be combined.");
1115
+ }
1116
+ const valueAfter = (flag) => {
1117
+ const i = args.indexOf(flag);
1118
+ if (i < 0) return undefined;
1119
+ const v = args[i + 1];
1120
+ if (!v || v.startsWith("-")) throw new CliError(`Missing value after ${flag}.`);
1121
+ return v;
1122
+ };
1123
+ let result;
1124
+ if (wantsDisconnect) {
1125
+ const { runDisconnectCommand } = await import("../src/hooks/cloud-enrollment-cli");
1126
+ result = runDisconnectCommand();
1127
+ } else {
1128
+ const { hostname } = await import("node:os");
1129
+ const { runConnectCommand } = await import("../src/hooks/cloud-enrollment-cli");
1130
+ result = await runConnectCommand({
1131
+ url: valueAfter("--connect"),
1132
+ token: valueAfter("--token"),
1133
+ machineId: valueAfter("--machine-id"),
1134
+ machineLabel: valueAfter("--machine-label"),
1135
+ defaultMachineId: hostname(),
1136
+ // Transcripts are what connecting is FOR, so they default on and the
1137
+ // disclosure is made at the point of connection rather than hidden
1138
+ // behind an opt-in flag most people never discover — a dashboard
1139
+ // showing only decisions is the empty-dashboard problem in a
1140
+ // different costume. --no-transcripts is the explicit way out, and
1141
+ // `failproofai config --status` always says which is in effect.
1142
+ sessions: !args.includes("--no-transcripts"),
1143
+ });
1144
+ }
1145
+ for (const line of result.lines) {
1146
+ if (result.exitCode === 0) console.log(line);
1147
+ else console.error(line);
1148
+ }
1149
+ await track("cli_cloud_enrollment", {
1150
+ action: wantsDisconnect ? "disconnect" : "connect",
1151
+ ok: result.exitCode === 0,
1152
+ });
1153
+ await exitAfterFlush(result.exitCode);
1154
+ return;
1155
+ }
1156
+
1157
+ const pauseIdx = args.indexOf("--pause");
1158
+ const wantsResume = args.includes("--resume");
1159
+ const wantsStatus = args.includes("--status");
1160
+ if (pauseIdx >= 0 || wantsResume || wantsStatus) {
1161
+ const chosen = [pauseIdx >= 0 && "--pause", wantsResume && "--resume", wantsStatus && "--status"].filter(Boolean);
1162
+ if (chosen.length > 1) {
1163
+ throw new CliError(`${chosen.join(" and ")} cannot be combined.`);
1164
+ }
1165
+ const sessionIdx = args.indexOf("--session");
1166
+ if (sessionIdx >= 0 && !args[sessionIdx + 1]) {
1167
+ throw new CliError("Missing session id after --session.");
1168
+ }
1169
+ // A bare `--pause` takes the default duration, so only treat the next
1170
+ // token as a duration when it isn't another flag.
1171
+ const next = pauseIdx >= 0 ? args[pauseIdx + 1] : undefined;
1172
+ const duration = next && !next.startsWith("-") ? next : undefined;
1173
+
1174
+ const { runPauseCommand } = await import("../src/hooks/session-pause-cli");
1175
+ const result = runPauseCommand({
1176
+ action: pauseIdx >= 0 ? "pause" : wantsResume ? "resume" : "status",
1177
+ duration,
1178
+ sessionId: sessionIdx >= 0 ? args[sessionIdx + 1] : undefined,
1179
+ all: args.includes("--all"),
1180
+ cwd: process.cwd(),
1181
+ });
1182
+ // `--status` answers "what is this machine's state?", which is both
1183
+ // halves: whether enforcement is paused AND whether cloud is connected.
1184
+ if (wantsStatus) {
1185
+ const { connectionStatusLines } = await import("../src/hooks/cloud-enrollment-cli");
1186
+ for (const line of connectionStatusLines()) console.log(line);
1187
+ // Always printed, including where reports can never work: "why am I
1188
+ // not getting them?" is the question --status exists to answer, and an
1189
+ // omitted line answers it with silence.
1190
+ console.log("");
1191
+ }
1192
+ for (const line of result.lines) {
1193
+ if (result.exitCode === 0) console.log(line);
1194
+ else console.error(line);
1195
+ }
1196
+ await track("cli_pause_invoked", {
1197
+ action: pauseIdx >= 0 ? "pause" : wantsResume ? "resume" : "status",
1198
+ ok: result.exitCode === 0,
1199
+ affected: result.affected,
1200
+ });
1201
+ await exitAfterFlush(result.exitCode);
1202
+ return;
1203
+ }
1204
+
777
1205
  const { runConfigureWizard } = await import("../src/hooks/configure-wizard");
778
1206
  const result = await runConfigureWizard();
779
1207
  await track("cli_configure_invoked", {
780
1208
  applied: result.applied,
781
- scope: result.scope ?? null,
1209
+ // `target` and `scopes`, not `scope`: the wizard rework replaced that
1210
+ // field and nothing caught it, because `.mjs` is outside the tsconfig
1211
+ // include so `tsc --noEmit` never type-checks this file. Every
1212
+ // `cli_configure_invoked` since has reported `scope: null`.
1213
+ target: result.target ?? null,
1214
+ scopes: result.scopes ?? [],
782
1215
  cli_count: result.clis?.length ?? 0,
1216
+ abort: result.abort ?? null,
783
1217
  });
784
- process.exit(0);
1218
+ // `abort` is the field `WizardAbort` exists to expose, and exiting 0
1219
+ // regardless discarded it: a fleet script could not tell "the user pressed
1220
+ // Esc" from "this machine could not install the required daemon and is
1221
+ // unconfigured". Cancelling is not a failure; the other two are.
1222
+ await exitAfterFlush(!result.applied && result.abort && result.abort !== "cancelled" ? 1 : 0);
1223
+ return;
1224
+ }
1225
+
1226
+ // Shared by both "unknown thing" guards below, so a mistyped SUBCOMMAND gets
1227
+ // the same nearest-match treatment a mistyped flag already got.
1228
+ function levenshtein(a, b) {
1229
+ const m = a.length, n = b.length;
1230
+ const dp = Array.from({ length: m + 1 }, (_, i) =>
1231
+ Array.from({ length: n + 1 }, (_, j) => (i === 0 ? j : j === 0 ? i : 0))
1232
+ );
1233
+ for (let i = 1; i <= m; i++)
1234
+ for (let j = 1; j <= n; j++)
1235
+ dp[i][j] = a[i - 1] === b[j - 1]
1236
+ ? dp[i - 1][j - 1]
1237
+ : 1 + Math.min(dp[i - 1][j], dp[i][j - 1], dp[i - 1][j - 1]);
1238
+ return dp[m][n];
785
1239
  }
786
1240
 
787
1241
  // Unknown flag guard — must appear after all known-flag branches
@@ -789,20 +1243,7 @@ WHAT IT DOES
789
1243
  const unknownFlag = args.find(a => a.startsWith("-") && !knownFlags.includes(a));
790
1244
 
791
1245
  if (unknownFlag) {
792
- function levenshtein(a, b) {
793
- const m = a.length, n = b.length;
794
- const dp = Array.from({ length: m + 1 }, (_, i) =>
795
- Array.from({ length: n + 1 }, (_, j) => (i === 0 ? j : j === 0 ? i : 0))
796
- );
797
- for (let i = 1; i <= m; i++)
798
- for (let j = 1; j <= n; j++)
799
- dp[i][j] = a[i - 1] === b[j - 1]
800
- ? dp[i - 1][j - 1]
801
- : 1 + Math.min(dp[i - 1][j], dp[i][j - 1], dp[i - 1][j - 1]);
802
- return dp[m][n];
803
- }
804
-
805
- const primary = ["--version", "--help", "--hook", "policies", "policy", "auth", "audit"];
1246
+ const primary = ["--version", "--help", "--hook", "policies", "policy", "audit"];
806
1247
  const closest = primary.reduce((best, flag) => {
807
1248
  const dist = levenshtein(unknownFlag, flag);
808
1249
  return dist < best.dist ? { flag, dist } : best;
@@ -818,29 +1259,27 @@ WHAT IT DOES
818
1259
  // Unknown subcommand guard (non-flag args that aren't a known subcommand)
819
1260
  const unknownSubcommand = args.find(a => !a.startsWith("-") && !SUBCOMMANDS.includes(a));
820
1261
  if (unknownSubcommand) {
1262
+ // Nearest match rather than a hardcoded "policies", which was wrong for
1263
+ // every input that was not a typo of it. `auth` made that concrete: it was
1264
+ // a real subcommand until this release, so an old script or plain muscle
1265
+ // memory lands here, and answering "did you mean policies?" sends someone
1266
+ // to the one command that has nothing to do with what they typed.
1267
+ const nearest = SUBCOMMANDS.reduce(
1268
+ (best, name) => {
1269
+ const dist = levenshtein(unknownSubcommand, name);
1270
+ return dist < best.dist ? { name, dist } : best;
1271
+ },
1272
+ { name: SUBCOMMANDS[0], dist: Infinity },
1273
+ );
821
1274
  throw new CliError(
822
1275
  `Unknown command: ${unknownSubcommand}\n` +
823
- `Did you mean: failproofai policies?\n` +
1276
+ `Did you mean: failproofai ${nearest.name}?\n` +
824
1277
  `Run \`failproofai --help\` for usage details.`
825
1278
  );
826
1279
  }
827
1280
 
828
- // First-run onboarding — on the first bare `failproofai` invocation, run the
829
- // configure wizard (which also fires the post-setup audit) BEFORE the
830
- // dashboard. Unlike before, we then fall through to launch the dashboard, so a
831
- // fresh user gets: setup → audit → dashboard, and every later `failproofai`
832
- // goes straight to the dashboard. Best-effort: any error must not block launch.
833
- if (args.length === 0) {
834
- try {
835
- const { maybeFirstRunConfigure } = await import("../src/hooks/configure-wizard");
836
- await maybeFirstRunConfigure();
837
- } catch {
838
- // First-run onboarding is non-critical; fall through to the dashboard.
839
- }
840
- }
841
-
842
1281
  // Dashboard launch — always production mode. Runs on every bare `failproofai`
843
- // (after first-run onboarding, if any).
1282
+ // (first-run onboarding, if any, already ran above).
844
1283
  const { launch } = await import("../scripts/launch");
845
1284
  launch("start");
846
1285
  }