failproofai 1.0.0-beta.2 → 1.0.0-beta.21

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 (381) 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 +3 -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]__0l4vuqm._.js → [root-of-the-server]__00ni9za._.js} +2 -2
  102. package/.next/standalone/.next/server/chunks/[root-of-the-server]__06hexd0._.js +3 -0
  103. package/.next/standalone/.next/server/chunks/[root-of-the-server]__08y94xv._.js +3 -0
  104. package/.next/standalone/.next/server/chunks/[root-of-the-server]__094p1f1._.js +3 -0
  105. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0_0xu5z._.js +1 -1
  106. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0ed01_d._.js +3 -0
  107. package/.next/standalone/.next/server/chunks/{[root-of-the-server]__0-dh5jy._.js → [root-of-the-server]__0effm9m._.js} +2 -2
  108. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0g0vcwt._.js +3 -0
  109. package/.next/standalone/.next/server/chunks/{[root-of-the-server]__18d-9oi._.js → [root-of-the-server]__0ix8gm7._.js} +2 -2
  110. package/.next/standalone/.next/server/chunks/{[root-of-the-server]__0h3lfmp._.js → [root-of-the-server]__0ji11a-._.js} +2 -2
  111. package/.next/standalone/.next/server/chunks/{[root-of-the-server]__10oa-v9._.js → [root-of-the-server]__0ojwjxl._.js} +2 -2
  112. package/.next/standalone/.next/server/chunks/{[root-of-the-server]__16le-kd._.js → [root-of-the-server]__0pbp9fe._.js} +2 -2
  113. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0q-v9z2._.js +1 -1
  114. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0vb5fl2._.js +3 -0
  115. package/.next/standalone/.next/server/chunks/{[root-of-the-server]__0sb_5m8._.js → [root-of-the-server]__131ebum._.js} +4 -4
  116. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1ibjy5x._.js +3 -0
  117. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1tvn4wf._.js +1 -1
  118. package/.next/standalone/.next/server/chunks/[root-of-the-server]__20d06rh._.js +3 -0
  119. package/.next/standalone/.next/server/chunks/_0lxbzdq._.js +3 -0
  120. package/.next/standalone/.next/server/chunks/_1zuiiy3._.js +3 -0
  121. package/.next/standalone/.next/server/chunks/lib_telemetry_ts_0dctyyw._.js +3 -0
  122. package/.next/standalone/.next/server/chunks/node_modules_next_dist_esm_build_templates_app-route_17k9e3w.js +5 -5
  123. package/.next/standalone/.next/server/chunks/node_modules_posthog-node_dist_entrypoints_index_node_mjs_01r25oi._.js +2 -1
  124. package/.next/standalone/.next/server/chunks/node_modules_posthog-node_dist_entrypoints_index_node_mjs_09z9-p7._.js +2 -1
  125. package/.next/standalone/.next/server/chunks/package_json_[json]_cjs_1nxcc4v._.js +1 -1
  126. package/.next/standalone/.next/server/chunks/ssr/[externals]__1aq139v._.js +3 -0
  127. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0-wfhrp._.js +3 -0
  128. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__06bp_fg._.js +5 -0
  129. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__074qmkp._.js +3 -0
  130. package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__1apb7k_._.js → [root-of-the-server]__0_1tiya._.js} +2 -2
  131. package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__170799-._.js → [root-of-the-server]__0spkm68._.js} +11 -11
  132. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0t281d4._.js +3 -0
  133. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__17itmx-._.js +5 -0
  134. package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__01od0m6._.js → [root-of-the-server]__1n2vcv4._.js} +2 -2
  135. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1qapotl._.js +1 -1
  136. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1rv-sc9._.js +3 -0
  137. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1tlvv9a._.js +3 -0
  138. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1u-wx42._.js +4 -0
  139. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1v185mo._.js +3 -0
  140. package/.next/standalone/.next/server/chunks/ssr/_0-yi74u._.js +3 -0
  141. package/.next/standalone/.next/server/chunks/ssr/_0rjgfln._.js +3 -0
  142. package/.next/standalone/.next/server/chunks/ssr/_0vo2qyt._.js +3 -0
  143. package/.next/standalone/.next/server/chunks/ssr/_0xej-0e._.js +3 -0
  144. package/.next/standalone/.next/server/chunks/ssr/_10399ge._.js +3 -0
  145. package/.next/standalone/.next/server/chunks/ssr/_11_p9y8._.js +1 -1
  146. package/.next/standalone/.next/server/chunks/ssr/_149k9oy._.js +3 -0
  147. package/.next/standalone/.next/server/chunks/ssr/_1_hwp06._.js +3 -0
  148. package/.next/standalone/.next/server/chunks/ssr/_1ctse6u._.js +3 -0
  149. package/.next/standalone/.next/server/chunks/ssr/_1e19atl._.js +1 -1
  150. package/.next/standalone/.next/server/chunks/ssr/_1mnhi_r._.js +3 -0
  151. package/.next/standalone/.next/server/chunks/ssr/_1ror7k9._.js +3 -0
  152. package/.next/standalone/.next/server/chunks/ssr/_1sdgprl._.js +5 -0
  153. package/.next/standalone/.next/server/chunks/ssr/app_audit__components_audit-dashboard_tsx_0p9ud47._.js +2 -2
  154. package/.next/standalone/.next/server/chunks/ssr/app_global-error_tsx_1kp6l3x._.js +1 -1
  155. package/.next/standalone/.next/server/chunks/ssr/app_policies_hooks-client_tsx_19dqvpc._.js +2 -2
  156. package/.next/standalone/.next/server/chunks/ssr/node_modules_next_dist_esm_build_templates_app-page_0dwo94s.js +4 -0
  157. package/.next/standalone/.next/server/chunks/ssr/node_modules_posthog-node_dist_entrypoints_index_node_mjs_11bnuzn._.js +2 -1
  158. package/.next/standalone/.next/server/chunks/ssr/src_hooks_builtin-policies_ts_09j2ndl._.js +3 -0
  159. package/.next/standalone/.next/server/chunks/ssr/src_hooks_fp-config_ts_04t589g._.js +4 -0
  160. package/.next/standalone/.next/server/middleware-build-manifest.js +6 -6
  161. package/.next/standalone/.next/server/middleware.js +2 -2
  162. package/.next/standalone/.next/server/pages/404.html +1 -1
  163. package/.next/standalone/.next/server/pages/500.html +1 -1
  164. package/.next/standalone/.next/server/server-reference-manifest.js +1 -1
  165. package/.next/standalone/.next/server/server-reference-manifest.json +74 -20
  166. package/.next/standalone/.next/static/chunks/{03fmihek9n986.js → 06zgzyh3wv1ig.js} +1 -1
  167. package/.next/standalone/.next/static/chunks/0km-jn9o5xyqs.css +2 -0
  168. package/.next/standalone/.next/static/chunks/{1hdhteui932bq.js → 0qmo2t50kpb30.js} +1 -1
  169. package/.next/standalone/.next/static/chunks/{2sbelanzvxvv0.js → 113g8wtal6_c4.js} +22 -22
  170. package/.next/standalone/.next/static/chunks/16p68cybetr31.js +1 -0
  171. package/.next/standalone/.next/static/chunks/18wqn0_3mzylf.js +1 -0
  172. package/.next/standalone/.next/static/chunks/1v1gqvpjk62jm.js +1 -0
  173. package/.next/standalone/.next/static/chunks/{0fi645si-uiiq.js → 1xq1m8h14tnlj.js} +1 -1
  174. package/.next/standalone/.next/static/chunks/{3hp8ulqknybil.js → 1y3vwogqb88dx.js} +1 -1
  175. package/.next/standalone/.next/static/chunks/{3vs4ay36tjds_.js → 1zmh-b0ulrqu7.js} +1 -1
  176. package/.next/standalone/.next/static/chunks/23kev26by1e4f.js +1 -0
  177. package/.next/standalone/.next/static/chunks/299lhw60e8gr6.js +1 -0
  178. package/.next/standalone/.next/static/chunks/{18rvdw5v72364.js → 2adbpdaxp5ygq.js} +1 -1
  179. package/.next/standalone/.next/static/chunks/{2j94uwkb4s3qr.js → 2kbqcw-6swmav.js} +1 -1
  180. package/.next/standalone/.next/static/chunks/2n9n5m0963pdg.js +6 -0
  181. package/.next/standalone/.next/static/chunks/2uvhw57mf9lg_.css +2 -0
  182. package/.next/standalone/.next/static/chunks/{31feojxhjyjpl.js → 30nrrpwymht5m.js} +1 -1
  183. package/.next/standalone/.next/static/chunks/31coz11ia5vc4.js +1 -0
  184. package/.next/standalone/.next/static/chunks/{turbopack-1lsv92x7klwrn.js → turbopack-3diub4ctvn1n0.js} +1 -1
  185. package/.next/standalone/SECURITY.md +8 -2
  186. package/.next/standalone/app/actions/get-active-pauses.ts +16 -0
  187. package/.next/standalone/app/actions/get-hooks-config.ts +2 -2
  188. package/.next/standalone/app/actions/get-scheduled-audit.ts +70 -0
  189. package/.next/standalone/app/actions/update-scheduled-audit.ts +44 -0
  190. package/.next/standalone/app/api/audit/run/route.ts +28 -0
  191. package/.next/standalone/app/api/audit/status/route.ts +13 -1
  192. package/.next/standalone/app/api/auth/status/route.ts +1 -1
  193. package/.next/standalone/app/audit/_components/audit-dashboard.tsx +1 -3
  194. package/.next/standalone/app/audit/_components/audit-progress-strip.tsx +1 -1
  195. package/.next/standalone/app/audit/_components/auth-dialog.css +159 -0
  196. package/.next/standalone/app/audit/_components/auth-dialog.tsx +3 -0
  197. package/.next/standalone/app/audit/_components/come-back-better-section.tsx +1 -1
  198. package/.next/standalone/app/audit/_components/empty-state.tsx +0 -1
  199. package/.next/standalone/app/audit/_components/how-to-improve-section.tsx +1 -1
  200. package/.next/standalone/app/audit/_components/quirks-section.tsx +0 -1
  201. package/.next/standalone/app/audit/_components/report-footer.tsx +0 -2
  202. package/.next/standalone/app/audit/_components/run-progress.tsx +1 -1
  203. package/.next/standalone/app/audit/_components/strengths-section.tsx +0 -1
  204. package/.next/standalone/app/audit/audit-styles.css +6 -149
  205. package/.next/standalone/app/components/pause-notices.tsx +111 -0
  206. package/.next/standalone/app/components/raw-log-viewer.tsx +1 -1
  207. package/.next/standalone/app/components/toast.tsx +1 -1
  208. package/.next/standalone/app/policies/hooks-client.tsx +84 -8
  209. package/.next/standalone/app/settings/page.tsx +31 -0
  210. package/.next/standalone/app/settings/settings-client.tsx +487 -0
  211. package/.next/standalone/components/navbar.tsx +2 -0
  212. package/.next/standalone/lib/auth/api-server-client.ts +2 -3
  213. package/.next/standalone/lib/auth/auth-store.ts +4 -5
  214. package/.next/standalone/lib/codex-sessions.ts +2 -1
  215. package/.next/standalone/lib/dashboard-host.ts +101 -0
  216. package/.next/standalone/lib/install-check.ts +22 -7
  217. package/.next/standalone/lib/pi-sessions.ts +99 -14
  218. package/.next/standalone/lib/telemetry-enabled.ts +54 -0
  219. package/.next/standalone/lib/telemetry-id.ts +52 -1
  220. package/.next/standalone/lib/telemetry.ts +8 -4
  221. package/.next/standalone/node_modules/react/cjs/react.development.js +1 -1
  222. package/.next/standalone/node_modules/react/cjs/react.production.js +1 -1
  223. package/.next/standalone/node_modules/react/package.json +3 -3
  224. package/.next/standalone/node_modules/react-dom/cjs/react-dom-server-legacy.browser.production.js +1 -1
  225. package/.next/standalone/node_modules/react-dom/cjs/react-dom-server-legacy.node.production.js +1 -1
  226. package/.next/standalone/node_modules/react-dom/cjs/react-dom-server.browser.production.js +3 -3
  227. package/.next/standalone/node_modules/react-dom/cjs/react-dom-server.edge.production.js +3 -3
  228. package/.next/standalone/node_modules/react-dom/cjs/react-dom-server.node.production.js +3 -3
  229. package/.next/standalone/node_modules/react-dom/cjs/react-dom.production.js +1 -1
  230. package/.next/standalone/node_modules/react-dom/package.json +4 -4
  231. package/.next/standalone/package.json +23 -12
  232. package/.next/standalone/proxy.ts +111 -1
  233. package/.next/standalone/server.js +1 -1
  234. package/bin/failproofai-worker.mjs +1 -1
  235. package/bin/failproofai.mjs +1041 -81
  236. package/bin/failproofaid-shim.mjs +4 -4
  237. package/dist/cli.mjs +18513 -10474
  238. package/dist/worker.mjs +1223 -299
  239. package/lib/auth/api-server-client.ts +2 -3
  240. package/lib/auth/auth-store.ts +4 -5
  241. package/lib/codex-sessions.ts +2 -1
  242. package/lib/dashboard-host.ts +101 -0
  243. package/lib/install-check.ts +22 -7
  244. package/lib/pi-sessions.ts +99 -14
  245. package/lib/telemetry-enabled.ts +54 -0
  246. package/lib/telemetry-id.ts +52 -1
  247. package/lib/telemetry.ts +8 -4
  248. package/package.json +23 -12
  249. package/scripts/build-daemon-packages.mjs +247 -0
  250. package/scripts/daemon-platforms.mjs +42 -0
  251. package/scripts/dev-hook.mjs +46 -5
  252. package/scripts/install-telemetry.mjs +8 -1
  253. package/scripts/launch.ts +25 -3
  254. package/scripts/parse-script-args.ts +27 -1
  255. package/scripts/prune-standalone.mjs +26 -0
  256. package/scripts/publish-aliases.mjs +62 -41
  257. package/scripts/repro-npm-install.sh +155 -0
  258. package/scripts/translate-docs/cache.ts +1 -1
  259. package/scripts/translate-docs/cli.ts +2 -2
  260. package/scripts/translate-docs/mintlify-nav.ts +1 -1
  261. package/scripts/translate-docs/readme-translator.ts +163 -4
  262. package/scripts/validate-mdx.ts +153 -13
  263. package/src/audit/audit-lock.ts +285 -0
  264. package/src/audit/audit-schedule.ts +111 -0
  265. package/src/audit/cache.ts +18 -5
  266. package/src/audit/cli-adapters/goose.ts +2 -1
  267. package/src/audit/cli-adapters/hermes.ts +28 -10
  268. package/src/audit/cli.ts +191 -35
  269. package/src/audit/dashboard-cache.ts +2 -3
  270. package/src/audit/report.ts +0 -1
  271. package/src/hooks/backfill-cli.ts +247 -0
  272. package/src/hooks/builtin-policies.ts +391 -3
  273. package/src/hooks/cloud-connection.ts +354 -0
  274. package/src/hooks/cloud-enrollment-cli.ts +439 -0
  275. package/src/hooks/cloud-enrollment.ts +284 -0
  276. package/src/hooks/cloud-introspect.ts +147 -0
  277. package/src/hooks/cloud-managed-policies.ts +206 -0
  278. package/src/hooks/collector-config.ts +218 -0
  279. package/src/hooks/configure-wizard.ts +1211 -233
  280. package/src/hooks/custom-hooks-loader.ts +243 -37
  281. package/src/hooks/daemon-client.ts +136 -33
  282. package/src/hooks/daemon-download.ts +264 -31
  283. package/src/hooks/daemon-service.ts +1124 -76
  284. package/src/hooks/first-run-gate.ts +50 -0
  285. package/src/hooks/flush-cli.ts +180 -0
  286. package/src/hooks/fp-config.ts +835 -0
  287. package/src/hooks/fp-home.ts +714 -0
  288. package/src/hooks/fp-reset.ts +1363 -0
  289. package/src/hooks/handler.ts +185 -6
  290. package/src/hooks/harness-cli.ts +386 -0
  291. package/src/hooks/hook-activity-store.ts +171 -24
  292. package/src/hooks/hook-logger.ts +2 -2
  293. package/src/hooks/hook-telemetry.ts +5 -1
  294. package/src/hooks/hooks-config.ts +4 -3
  295. package/src/hooks/integrations.ts +0 -1
  296. package/src/hooks/loader-utils.ts +172 -9
  297. package/src/hooks/manager.ts +41 -5
  298. package/src/hooks/migrations.ts +450 -0
  299. package/src/hooks/onboarding-attempt.ts +195 -0
  300. package/src/hooks/onboarding-lock.ts +118 -0
  301. package/src/hooks/policy-types.ts +28 -0
  302. package/src/hooks/session-pause-cli.ts +159 -0
  303. package/src/hooks/session-pause.ts +267 -0
  304. package/src/hooks/setup-state.ts +256 -0
  305. package/src/hooks/tui.ts +209 -9
  306. package/src/hooks/uninstall-cli.ts +418 -0
  307. package/src/hooks/worker-server.ts +134 -15
  308. package/.next/standalone/.next/server/chunks/[externals]__14odj07._.js +0 -3
  309. package/.next/standalone/.next/server/chunks/[externals]__1s61mel._.js +0 -3
  310. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0id_xf7._.js +0 -3
  311. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0longh-._.js +0 -3
  312. package/.next/standalone/.next/server/chunks/[root-of-the-server]__17g9wh7._.js +0 -3
  313. package/.next/standalone/.next/server/chunks/[root-of-the-server]__19120tr._.js +0 -3
  314. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1fwl2mz._.js +0 -3
  315. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1myjm-d._.js +0 -3
  316. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1rhmvod._.js +0 -3
  317. package/.next/standalone/.next/server/chunks/ssr/[externals]__1_g_b3t._.js +0 -3
  318. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0114ewg._.js +0 -4
  319. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__02r5bgf._.js +0 -4
  320. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0e8sjqm._.js +0 -4
  321. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0lkzqax._.js +0 -4
  322. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0nb0syg._.js +0 -3
  323. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0qucxyj._.js +0 -4
  324. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0u02miy._.js +0 -4
  325. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0uno39i._.js +0 -3
  326. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__14dd6h8._.js +0 -3
  327. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1ath6v_._.js +0 -4
  328. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1psjybo._.js +0 -3
  329. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1zqz4v8._.js +0 -4
  330. package/.next/standalone/.next/server/chunks/ssr/_1m_p7pi._.js +0 -3
  331. package/.next/standalone/.next/server/chunks/ssr/node_modules_html-to-image_es_index_0oy_-36.js +0 -3
  332. package/.next/standalone/.next/server/chunks/ssr/src_hooks_1ezd2jf._.js +0 -5
  333. package/.next/standalone/.next/server/chunks/ssr/src_hooks_1tnuifj._.js +0 -5
  334. package/.next/standalone/.next/static/chunks/0h7auy7hzjyhw.js +0 -1
  335. package/.next/standalone/.next/static/chunks/0zbxssxh53n-3.js +0 -1
  336. package/.next/standalone/.next/static/chunks/2mazwb1qmur1p.js +0 -1
  337. package/.next/standalone/.next/static/chunks/2pujpnfx4pws6.css +0 -2
  338. package/.next/standalone/.next/static/chunks/33u59vf_8xpd-.js +0 -1
  339. package/.next/standalone/.next/static/chunks/3i27c3hcriawq.css +0 -1
  340. package/.next/standalone/.next/static/chunks/3s7myk_co0gbh.js +0 -1
  341. package/.next/standalone/.next/static/chunks/40k1_v4os4cw6.js +0 -6
  342. package/.next/standalone/Cargo.lock +0 -428
  343. package/.next/standalone/Cargo.toml +0 -9
  344. package/.next/standalone/crates/PROTOCOL.md +0 -122
  345. package/.next/standalone/crates/failproofaid/Cargo.toml +0 -18
  346. package/.next/standalone/crates/failproofaid/src/lock.rs +0 -81
  347. package/.next/standalone/crates/failproofaid/src/main.rs +0 -83
  348. package/.next/standalone/crates/failproofaid/src/paths.rs +0 -172
  349. package/.next/standalone/crates/failproofaid/src/server.rs +0 -502
  350. package/.next/standalone/crates/failproofaid/src/worker.rs +0 -400
  351. package/.next/standalone/crates/failproofaid/tests/daemon_e2e.rs +0 -241
  352. package/.next/standalone/crates/fpai-ipc/Cargo.toml +0 -16
  353. package/.next/standalone/crates/fpai-ipc/src/envelope.rs +0 -175
  354. package/.next/standalone/crates/fpai-ipc/src/framing.rs +0 -177
  355. package/.next/standalone/crates/fpai-ipc/src/lib.rs +0 -10
  356. package/.next/standalone/crates/fpai-ipc/src/peer.rs +0 -80
  357. package/.next/standalone/docker-hook-sync/Dockerfile +0 -79
  358. package/.next/standalone/docker-hook-sync/entrypoint.sh +0 -109
  359. package/.next/standalone/integration-suite/Dockerfile +0 -47
  360. package/.next/standalone/integration-suite/README.md +0 -129
  361. package/.next/standalone/integration-suite/canary-policies.mjs +0 -45
  362. package/.next/standalone/integration-suite/capture-tokens.sh +0 -48
  363. package/.next/standalone/integration-suite/ci-entrypoint.sh +0 -179
  364. package/.next/standalone/integration-suite/inject-tokens.sh +0 -44
  365. package/.next/standalone/integration-suite/install-clis.sh +0 -136
  366. package/.next/standalone/integration-suite/probe-cli.sh +0 -248
  367. package/.next/standalone/integration-suite/report.js +0 -120
  368. package/.next/standalone/integration-suite/run.sh +0 -159
  369. package/.next/standalone/openclaw-plugin/index.js +0 -223
  370. package/.next/standalone/openclaw-plugin/openclaw.plugin.json +0 -6
  371. package/.next/standalone/openclaw-plugin/package.json +0 -12
  372. package/.next/standalone/osv-scanner.toml +0 -17
  373. package/.next/standalone/pi-extension/index.ts +0 -524
  374. package/.next/standalone/pi-extension/package.json +0 -12
  375. package/.next/standalone/postcss.config.mjs +0 -8
  376. package/.next/standalone/readme-arch-hq.gif +0 -0
  377. package/.next/standalone/rust-toolchain.toml +0 -4
  378. package/src/auth/cli.ts +0 -371
  379. /package/.next/standalone/.next/static/{NWAjs0tHFyzk97gRGG09r → VjEdIL6W5uBsvEzknXqV9}/_buildManifest.js +0 -0
  380. /package/.next/standalone/.next/static/{NWAjs0tHFyzk97gRGG09r → VjEdIL6W5uBsvEzknXqV9}/_clientMiddlewareManifest.js +0 -0
  381. /package/.next/standalone/.next/static/{NWAjs0tHFyzk97gRGG09r → VjEdIL6W5uBsvEzknXqV9}/_ssgManifest.js +0 -0
@@ -7,35 +7,53 @@
7
7
  * producing the same LogEntry[] shape the other adapters do — so
8
8
  * `logEntriesToEvents` handles the rest.
9
9
  *
10
- * Gateway sessions have no `cwd` (Slack/Telegram runs aren't in a repo), so they
11
- * group by (profile, `source`) instead of working directory a Hermes profile
12
- * is a whole separate home dir with its own state.db (lib/hermes-profiles.ts).
10
+ * Hermes sessions are NOT uniformly cwd-less. This file used to assert they were
11
+ * and returned nothing at all for `audit --project <cwd>`, so a Hermes session
12
+ * driven in a repo silently contributed zero findings to that project's audit.
13
+ * Verified against hermes-agent 0.19.0: `sessions` carries real `cwd`,
14
+ * `git_branch` and `git_repo_root` columns, and every `source='cli'` session
15
+ * populated them.
16
+ *
17
+ * Both shapes are real, so both are handled:
18
+ * • a session WITH a cwd groups by working directory like Claude/Goose/Devin,
19
+ * and takes part in cwd-scoped audits;
20
+ * • a session WITHOUT one — a Slack/Telegram gateway run, which genuinely
21
+ * isn't in a repo — keeps grouping by (profile, `source`), since a Hermes
22
+ * profile is a whole separate home dir with its own state.db
23
+ * (lib/hermes-profiles.ts), and is correctly excluded from a cwd filter.
13
24
  */
14
25
  import { getHermesSessions } from "../../../lib/hermes-projects";
15
26
  import { hermesProjectPath } from "../../../lib/hermes-profiles";
16
27
  import { getHermesSessionLog } from "../../../lib/hermes-sessions";
28
+ import { encodeFolderName } from "../../../lib/paths";
17
29
  import type { NormalizedToolEvent, TranscriptMetadata } from "../types";
18
30
  import type { ListOpts } from "./claude";
19
31
  import { logEntriesToEvents } from "./shared";
20
32
 
33
+ /** Grouping label for one session: its working directory when it has one,
34
+ * else the (profile, source) bucket gateway sessions have always used. */
35
+ function hermesProjectName(s: { profile: string; source?: string; cwd?: string }): string {
36
+ if (s.cwd) return encodeFolderName(s.cwd);
37
+ return s.source ? hermesProjectPath(s.profile, s.source) : `hermes:${s.profile}`;
38
+ }
39
+
21
40
  export async function listHermesTranscriptMetadata(
22
41
  opts: ListOpts = {},
23
42
  ): Promise<TranscriptMetadata[]> {
24
- // `audit --project <cwd>` filters on working directory; gateway sessions have
25
- // none, so Hermes contributes nothing to a cwd-scoped audit.
26
- if (opts.projects && opts.projects.length > 0) return [];
27
-
43
+ const projectFilter = opts.projects ? new Set(opts.projects) : null;
28
44
  const sinceMs = opts.sinceMs ?? 0;
29
45
  const sessions = await getHermesSessions();
30
46
  const out: TranscriptMetadata[] = [];
31
47
  for (const s of sessions) {
32
48
  if (s.mtimeMs < sinceMs) continue;
33
49
  if (s.messageCount <= 0 && !s.hasMessages) continue; // empty → no events (message_count can lag; trust real messages)
50
+ // `audit --project <cwd>` filters on the session's working directory, the
51
+ // same comparison Claude and Goose make. A session with no cwd is not in
52
+ // any project, so it drops out here rather than at the top of the function.
53
+ if (projectFilter && (!s.cwd || !projectFilter.has(s.cwd))) continue;
34
54
  out.push({
35
55
  cli: "hermes",
36
- // Group by (profile, channel); gateway sessions are cwd-less, and each
37
- // profile is a separate Hermes home with its own state.db.
38
- projectName: s.source ? hermesProjectPath(s.profile, s.source) : `hermes:${s.profile}`,
56
+ projectName: hermesProjectName(s),
39
57
  sessionId: s.sessionId,
40
58
  transcriptPath: `hermes://${s.sessionId}`,
41
59
  mtimeMs: s.mtimeMs,
package/src/audit/cli.ts CHANGED
@@ -14,8 +14,14 @@
14
14
  * No arguments yet — a bare `failproofai audit` does a full scan (all CLIs, all
15
15
  * history). Flags (--since, --cli, --project, --port, --no-open) are easy
16
16
  * follow-ups against `RunAuditOptions`.
17
+ *
18
+ * `--scheduled` is the one exception, and it is not a flag on the interactive
19
+ * command so much as a second entry point sharing its name: it runs the same
20
+ * scan with no TTY, no browser and no server, and reports its outcome through
21
+ * an exit code. See `runScheduledAudit`.
17
22
  */
18
23
  import { runAudit } from "./index";
24
+ import { acquireAuditLock, type AuditLockInfo } from "./audit-lock";
19
25
  import { writeDashboardCache } from "./dashboard-cache";
20
26
  import type { AuditResult, RunAuditOptions } from "./types";
21
27
  import { trackHookEvent } from "../hooks/hook-telemetry";
@@ -28,6 +34,16 @@ import { brandAnsi, ANSI_RESET, ANSI_BOLD, ANSI_DIM } from "../hooks/tui";
28
34
  * for `start` mode, which `failproofai` (bare) already uses. */
29
35
  const DASHBOARD_PORT = 8020;
30
36
 
37
+ /**
38
+ * `EX_TEMPFAIL` from sysexits.h — "another audit already holds the lock".
39
+ *
40
+ * A distinct code rather than 1, because the scheduler that spawns the headless
41
+ * run has to tell the two apart: a failure deserves a report, while losing the
42
+ * lock means only "come back in fifteen minutes". Collapsing them would make a
43
+ * healthy machine that simply ran two audits close together look broken.
44
+ */
45
+ export const EXIT_AUDIT_ALREADY_RUNNING = 75;
46
+
31
47
  /**
32
48
  * Mirror of `app/audit/_components/run-progress.tsx`'s `STAGES`. Kept identical
33
49
  * so the CLI and the dashboard's in-progress view tell the same story — the
@@ -49,6 +65,12 @@ USAGE
49
65
  wasteful patterns, then open the audit dashboard.
50
66
  failproofai audit --help Show this help.
51
67
 
68
+ failproofai audit --scheduled
69
+ Headless run: scan, refresh the dashboard's cached
70
+ result, print one line, exit. No browser, no
71
+ server. This is what a scheduled audit runs; exit
72
+ 75 means another audit already had the lock.
73
+
52
74
  WHAT IT DOES
53
75
  1. Scans past sessions from every installed agent CLI (Claude, Codex, Cursor,
54
76
  Copilot, OpenCode, Pi) — entirely on your machine.
@@ -247,10 +269,13 @@ function printSummary(result: AuditResult): void {
247
269
 
248
270
  /**
249
271
  * Which entry point ran the audit. `onboarding` is the automatic post-setup run;
250
- * `cli` is an explicit `failproofai audit`. Carried on every cli_audit_* event so
251
- * the first audit a user ever runs is distinguishable from later deliberate ones.
272
+ * `cli` is an explicit `failproofai audit`; `scheduled` is the headless run the
273
+ * daemon spawns. Carried on every cli_audit_* event so the first audit a user
274
+ * ever runs, a deliberate one, and one nobody was present for stay distinct —
275
+ * without which an opt-in scheduled scan would silently inflate the counts that
276
+ * describe what people actually do by hand.
252
277
  */
253
- type AuditSource = "cli" | "onboarding";
278
+ type AuditSource = "cli" | "onboarding" | "scheduled";
254
279
 
255
280
  /** Shared so both entry points report cli_audit_completed identically. */
256
281
  function auditCompletedProps(source: AuditSource, result: AuditResult) {
@@ -263,6 +288,89 @@ function auditCompletedProps(source: AuditSource, result: AuditResult) {
263
288
  };
264
289
  }
265
290
 
291
+ /** One line naming whoever holds the lock, for a refusal message. */
292
+ function heldByLine(held: AuditLockInfo | null): string {
293
+ if (!held) return "another audit is already running";
294
+ const ageS = Math.max(0, Math.round((Date.now() - held.startedAt) / 1000));
295
+ return `another audit is already running (pid ${held.pid}, started by ${held.source} ${ageS}s ago)`;
296
+ }
297
+
298
+ // ── Headless (scheduled) audit ───────────────────────────────────────────────
299
+
300
+ /**
301
+ * Run the audit with nobody watching: no TTY animation, no browser, no prompts,
302
+ * no dashboard server left behind.
303
+ *
304
+ * This is the entry point the scheduler spawns, and it must be a SEPARATE
305
+ * short-lived process. `src/hooks/worker-server.ts` serialises every request
306
+ * through one promise chain that `crates/failproofaid/src/worker.rs` caps at
307
+ * 30s, and `daemon-client.ts` turns that timeout into a DENY — so a ~104-second
308
+ * audit on the warm worker would be a machine-wide fail-closed denial across
309
+ * all 12 CLIs for as long as it ran.
310
+ *
311
+ * Returns the exit code instead of exiting, so the caller owns the exit and
312
+ * this stays callable from a test:
313
+ * 0 the scan completed (whether or not it found anything)
314
+ * 1 the scan failed, or its result could not be persisted
315
+ * 75 another audit holds the lock — not an error, come back later
316
+ */
317
+ export async function runScheduledAudit(): Promise<number> {
318
+ const attempt = acquireAuditLock("scheduled");
319
+ if (!attempt.ok) {
320
+ // Deliberately ahead of cli_audit_started: a run that never started must
321
+ // not be counted as one that did.
322
+ process.stderr.write(`failproofai: ${heldByLine(attempt.heldBy)}; skipping this scheduled run\n`);
323
+ return EXIT_AUDIT_ALREADY_RUNNING;
324
+ }
325
+
326
+ const instanceId = getInstanceId();
327
+ try {
328
+ // Every event here is awaited, unlike runAuditCli's fire-and-forget
329
+ // `started`. Nothing keeps this process alive once the audit settles — no
330
+ // dashboard server, no user — and there is nobody waiting on latency
331
+ // either, so the bounded (5s, never-throws) send costs nothing that matters.
332
+ await trackHookEvent(instanceId, "cli_audit_started", { source: "scheduled" });
333
+
334
+ let result: AuditResult;
335
+ try {
336
+ result = await runAudit({});
337
+ } catch (err) {
338
+ await trackHookEvent(instanceId, "cli_audit_failed", {
339
+ source: "scheduled",
340
+ error_type: err instanceof Error ? err.name : "unknown",
341
+ error_message: sanitizeErrorMessage(err),
342
+ });
343
+ process.stderr.write(
344
+ `failproofai: scheduled audit failed: ${err instanceof Error ? err.message : String(err)}\n`,
345
+ );
346
+ return 1;
347
+ }
348
+
349
+ await trackHookEvent(instanceId, "cli_audit_completed", auditCompletedProps("scheduled", result));
350
+
351
+ // No cache write for an empty scan, matching both interactive paths — and it
352
+ // matters more here. This run is unattended, so overwriting a real earlier
353
+ // audit's cached result with an empty one (after a history rotation, or on a
354
+ // service unit whose HOME resolves somewhere else) would blank the dashboard
355
+ // with nobody present to notice why.
356
+ if (result.eventsScanned > 0 && !writeDashboardCache({}, result)) {
357
+ // The cache is the only channel by which this run's result reaches
358
+ // anyone, so failing to write it is a failed run, not a footnote.
359
+ process.stderr.write("failproofai: scheduled audit ran but its result could not be saved\n");
360
+ return 1;
361
+ }
362
+
363
+ process.stdout.write(
364
+ `failproofai: audit complete — ${num(result.eventsScanned)} tool calls across ` +
365
+ `${num(result.transcripts.scanned)} sessions, ${num(result.totals.hits)} hits\n`,
366
+ );
367
+
368
+ return 0;
369
+ } finally {
370
+ attempt.lock.release();
371
+ }
372
+ }
373
+
266
374
  // ── Post-setup background audit ────────────────────────────────────────────────
267
375
 
268
376
  /**
@@ -279,47 +387,67 @@ function auditCompletedProps(source: AuditSource, result: AuditResult) {
279
387
  export async function runPostSetupAudit(): Promise<void> {
280
388
  if (process.env.FAILPROOFAI_NO_AUTO_AUDIT === "1") return;
281
389
 
282
- const instanceId = getInstanceId();
283
- // Fire-and-forget, as in runAuditCli: the multi-second scan below keeps the
284
- // process alive long enough for this to land, and the completed/failed event
285
- // that follows is awaited.
286
- void trackHookEvent(instanceId, "cli_audit_started", { source: "onboarding" });
287
-
288
- process.stdout.write(
289
- `\n ${c(PINK, "✦")} ${c(BOLD, "failproofai audit now running")} ${c(DIM, ctrl+c to stop")}\n\n`,
290
- );
291
-
292
- let result: AuditResult;
293
- try {
294
- result = await runWithProgress({});
295
- } catch (err) {
296
- // Awaited: this function returns straight into the dashboard boot, and a
297
- // fire-and-forget fetch would race it.
298
- await trackHookEvent(instanceId, "cli_audit_failed", {
299
- source: "onboarding",
300
- error_type: err instanceof Error ? err.name : "unknown",
301
- error_message: sanitizeErrorMessage(err),
302
- });
390
+ // Take the same cross-process cache lock the scheduled run, `failproofai
391
+ // audit` and the dashboard re-run take. This onboarding scan writes the very
392
+ // same sha1-keyed per-transcript cache and single-slot dashboard cache, so it
393
+ // is the fourth writer and must serialise with the other three — a scheduled
394
+ // daemon child can already be mid-scan when setup finishes. Held ⇒ skip, best
395
+ // effort: the dashboard boots on whatever the holder's run leaves behind, and
396
+ // this is pre-warming, not a result anyone is waiting on. `onboarding` matches
397
+ // the telemetry source below and the lock source declared in audit-lock.ts.
398
+ const attempt = acquireAuditLock("onboarding");
399
+ if (!attempt.ok) {
303
400
  process.stdout.write(
304
- ` ${c(PINK, "!")} ${c(DIM, "audit couldn't finishrun")} ${c(CYAN, "failproofai audit")} ${c(DIM, "later.")}\n\n`,
401
+ `\n ${c(DIM, "an audit is already running the dashboard will show its result.")}\n\n`,
305
402
  );
306
403
  return;
307
404
  }
308
405
 
309
- // Reported before the empty-history return below, so an onboarding audit that
310
- // finds nothing is still counted — matching runAuditCli, which reports
311
- // completed regardless of what the scan turned up.
312
- await trackHookEvent(instanceId, "cli_audit_completed", auditCompletedProps("onboarding", result));
406
+ try {
407
+ const instanceId = getInstanceId();
408
+ // Fire-and-forget, as in runAuditCli: the multi-second scan below keeps the
409
+ // process alive long enough for this to land, and the completed/failed event
410
+ // that follows is awaited.
411
+ void trackHookEvent(instanceId, "cli_audit_started", { source: "onboarding" });
313
412
 
314
- if (result.eventsScanned === 0) {
315
413
  process.stdout.write(
316
- `\n ${c(DIM, "no agent sessions to audit yet come back after using your agent.")}\n\n`,
414
+ `\n ${c(PINK, "✦")} ${c(BOLD, "failproofai audit now running")} ${c(DIM, ctrl+c to stop")}\n\n`,
317
415
  );
318
- return;
416
+
417
+ let result: AuditResult;
418
+ try {
419
+ result = await runWithProgress({});
420
+ } catch (err) {
421
+ // Awaited: this function returns straight into the dashboard boot, and a
422
+ // fire-and-forget fetch would race it.
423
+ await trackHookEvent(instanceId, "cli_audit_failed", {
424
+ source: "onboarding",
425
+ error_type: err instanceof Error ? err.name : "unknown",
426
+ error_message: sanitizeErrorMessage(err),
427
+ });
428
+ process.stdout.write(
429
+ ` ${c(PINK, "!")} ${c(DIM, "audit couldn't finish — run")} ${c(CYAN, "failproofai audit")} ${c(DIM, "later.")}\n\n`,
430
+ );
431
+ return;
432
+ }
433
+
434
+ // Reported before the empty-history return below, so an onboarding audit that
435
+ // finds nothing is still counted — matching runAuditCli, which reports
436
+ // completed regardless of what the scan turned up.
437
+ await trackHookEvent(instanceId, "cli_audit_completed", auditCompletedProps("onboarding", result));
438
+
439
+ if (result.eventsScanned === 0) {
440
+ process.stdout.write(
441
+ `\n ${c(DIM, "no agent sessions to audit yet — come back after using your agent.")}\n\n`,
442
+ );
443
+ return;
444
+ }
445
+ writeDashboardCache({}, result);
446
+ printSummary(result);
447
+ process.stdout.write("\n");
448
+ } finally {
449
+ attempt.lock.release();
319
450
  }
320
- writeDashboardCache({}, result);
321
- printSummary(result);
322
- process.stdout.write("\n");
323
451
  }
324
452
 
325
453
  // ── Entry point ──────────────────────────────────────────────────────────────
@@ -329,6 +457,15 @@ export async function runAuditCli(args: string[]): Promise<void> {
329
457
  process.stdout.write(HELP);
330
458
  process.exit(0);
331
459
  }
460
+ // The headless path, spawned rather than typed. Handled ahead of the
461
+ // rejection below so that adding it costs the interactive path nothing: it
462
+ // still refuses every argument it has always refused.
463
+ if (args.includes("--scheduled")) {
464
+ const extra = args.find((a) => a !== "--scheduled");
465
+ if (extra) die(`\`audit --scheduled\` takes no other arguments (got: ${extra}).`);
466
+ process.exit(await runScheduledAudit());
467
+ }
468
+
332
469
  // No arguments supported yet — reject typos rather than silently doing a bare
333
470
  // audit, so a future `failproofai audit --since 7d` doesn't quietly no-op.
334
471
  const stray = args.find((a) => a !== "--help" && a !== "-h");
@@ -339,6 +476,18 @@ export async function runAuditCli(args: string[]): Promise<void> {
339
476
  );
340
477
  }
341
478
 
479
+ // Taken before any telemetry so a refused run is not counted as a started
480
+ // one, and released the moment the SCAN is done — see below, well before
481
+ // launch() parks this process on the dashboard.
482
+ const attempt = acquireAuditLock("cli");
483
+ if (!attempt.ok) {
484
+ process.stderr.write(
485
+ `Error: ${heldByLine(attempt.heldBy)}.\n` +
486
+ `Two audits write the same cache files, so this one won't start. Try again shortly.\n`,
487
+ );
488
+ process.exit(EXIT_AUDIT_ALREADY_RUNNING);
489
+ }
490
+
342
491
  const instanceId = getInstanceId();
343
492
  // Fire-and-forget is safe for `started`: the multi-second audit below (and the
344
493
  // awaited cli_audit_completed / cli_audit_failed) keep the process alive long
@@ -393,6 +542,13 @@ export async function runAuditCli(args: string[]): Promise<void> {
393
542
  );
394
543
  }
395
544
 
545
+ // Released here, not in a `finally`: the lock covers the SCAN, and launch()
546
+ // below keeps this process alive for as long as the user leaves the dashboard
547
+ // open. Holding it that long would block every scheduled run until the
548
+ // one-hour stale ceiling expired. The exit paths above (die(), the
549
+ // empty-history exit) are covered by the handle's own process-exit hook.
550
+ attempt.lock.release();
551
+
396
552
  const url = `http://localhost:${DASHBOARD_PORT}/audit`;
397
553
  process.stdout.write(
398
554
  `\n ${c(DIM, "starting the dashboard…")}\n` +
@@ -11,10 +11,9 @@
11
11
  * makes navigating back to /audit instant without re-running.
12
12
  */
13
13
  import { existsSync, readFileSync } from "node:fs";
14
- import { join } from "node:path";
15
- import { homedir } from "node:os";
16
14
  import { writeJsonAtomically } from "../../lib/atomic-write";
17
15
  import type { AuditResult, RunAuditOptions } from "./types";
16
+ import { auditDashboardFile } from "../hooks/fp-home";
18
17
 
19
18
  const DEFAULT_MAX_AGE_MINUTES = 30;
20
19
 
@@ -50,7 +49,7 @@ export interface DashboardCacheEntry {
50
49
  }
51
50
 
52
51
  function getCachePath(): string {
53
- return join(homedir(), ".failproofai", "audit-dashboard.json");
52
+ return auditDashboardFile();
54
53
  }
55
54
 
56
55
  /** Read the cache file. Returns null on missing/corrupt/unreadable file —
@@ -31,7 +31,6 @@ function noColorEnabled(): boolean {
31
31
  }
32
32
 
33
33
  function stripAnsi(s: string): string {
34
- // eslint-disable-next-line no-control-regex
35
34
  return s.replace(/\x1B\[[0-9;]*m/g, "");
36
35
  }
37
36
 
@@ -0,0 +1,247 @@
1
+ /**
2
+ * `failproofai backfill` — re-ship history the collector has already read past.
3
+ *
4
+ * The collector never re-reads a file it has a cursor for, which is right for
5
+ * steady state and wrong exactly twice:
6
+ *
7
+ * • the dashboard's data was cleared, or a machine was re-enrolled into a
8
+ * different org, and the history on disk is now the only copy;
9
+ * • cursors advanced before there was anywhere to send to, so everything
10
+ * before the connection was never shipped at all.
11
+ *
12
+ * Both leave a machine whose transcripts exist locally and nowhere else, with no
13
+ * way to ask for them again short of deleting files by hand and hoping.
14
+ *
15
+ * SAFE BY CONSTRUCTION, not by luck. Re-reading is already the documented
16
+ * recovery path for a damaged cursor store — "starting over re-ships records the
17
+ * server already dedups" — and redaction is deterministic, so a re-shipped event
18
+ * hashes identically to its first send and collapses into the row that is
19
+ * already there rather than duplicating it.
20
+ *
21
+ * HANDS OFF rather than doing the work. The cursors it needs to rewind are held
22
+ * in memory by the RUNNING collector, which would write them straight back over;
23
+ * only the daemon can stop the collector first. So this writes a request the
24
+ * daemon drains on its next tick.
25
+ *
26
+ * What it does NOT hand off is the checking. Every precondition a person can get
27
+ * wrong — no daemon, no credential, collection switched off — is verified here,
28
+ * synchronously, before returning. The alternative is what already happened once
29
+ * on a real machine: the CLI reported success, and the actual failure sat in the
30
+ * journal for twenty minutes while batches parked.
31
+ */
32
+ import { existsSync, mkdirSync, writeFileSync, readdirSync, statSync } from "node:fs";
33
+ import { dirname, join } from "node:path";
34
+
35
+ import { failproofaiHome } from "./fp-home";
36
+ import { readConfig } from "./fp-config";
37
+ import { readIngestCredential } from "./collector-config";
38
+ import { daemonServiceStatus, isDaemonSupportedPlatform } from "./daemon-service";
39
+
40
+ /** Default window. `--since` widens it. */
41
+ export const DEFAULT_BACKFILL_DAYS = 30;
42
+
43
+ export interface BackfillOptions {
44
+ /** Epoch ms. Everything modified at or after this is re-read. */
45
+ sinceMs?: number;
46
+ /** Report what would be re-read and write nothing. */
47
+ dryRun?: boolean;
48
+ /** Injected for tests. */
49
+ now?: number;
50
+ }
51
+
52
+ export interface BackfillResult {
53
+ exitCode: number;
54
+ lines: string[];
55
+ }
56
+
57
+ /**
58
+ * `~/.failproofai/state/backfill-request.json` — mirrored from the daemon's
59
+ * `paths::backfill_request_path()`. Two processes, one path; the comment there
60
+ * says why that is written down twice rather than derived.
61
+ */
62
+ export function backfillRequestPath(home?: string): string {
63
+ return join(failproofaiHome(home), "state", "backfill-request.json");
64
+ }
65
+
66
+ /**
67
+ * Where each agent CLI keeps its transcripts, so the command can say what it is
68
+ * about to re-read rather than asking for trust.
69
+ *
70
+ * Deliberately a SURVEY, not the source of truth. The daemon decides what is
71
+ * actually collected from its own config; this list only produces the sentence
72
+ * a person reads before agreeing. A directory missing here means a quieter
73
+ * message, never a missed backfill.
74
+ */
75
+ const SESSION_DIRS: Array<{ cli: string; dir: string }> = [
76
+ { cli: "Claude Code", dir: ".claude/projects" },
77
+ { cli: "OpenAI Codex", dir: ".codex/sessions" },
78
+ { cli: "Cursor", dir: ".cursor/chats" },
79
+ { cli: "GitHub Copilot", dir: ".copilot/history-session-state" },
80
+ { cli: "OpenCode", dir: ".local/share/opencode/storage" },
81
+ { cli: "Factory Droid", dir: ".factory/sessions" },
82
+ { cli: "Antigravity", dir: ".gemini/antigravity-cli/brain" },
83
+ ];
84
+
85
+ interface Survey {
86
+ cli: string;
87
+ files: number;
88
+ newest: number;
89
+ }
90
+
91
+ /** What is on disk and inside the window, per CLI. */
92
+ function surveySessions(sinceMs: number, homeDir: string): Survey[] {
93
+ const out: Survey[] = [];
94
+ for (const { cli, dir } of SESSION_DIRS) {
95
+ const root = join(homeDir, dir);
96
+ if (!existsSync(root)) continue;
97
+ let files = 0;
98
+ let newest = 0;
99
+ const walk = (d: string, depth: number) => {
100
+ // Bounded: these trees are date-nested a few levels deep, and an
101
+ // unbounded walk on a symlinked home is a way to hang a CLI command.
102
+ if (depth > 6) return;
103
+ let entries;
104
+ try {
105
+ entries = readdirSync(d, { withFileTypes: true });
106
+ } catch {
107
+ return;
108
+ }
109
+ for (const e of entries) {
110
+ const p = join(d, e.name);
111
+ if (e.isDirectory()) {
112
+ walk(p, depth + 1);
113
+ continue;
114
+ }
115
+ try {
116
+ const st = statSync(p);
117
+ if (st.mtimeMs >= sinceMs) {
118
+ files += 1;
119
+ newest = Math.max(newest, st.mtimeMs);
120
+ }
121
+ } catch {
122
+ /* raced away mid-walk */
123
+ }
124
+ }
125
+ };
126
+ walk(root, 0);
127
+ if (files > 0) out.push({ cli, files, newest });
128
+ }
129
+ return out;
130
+ }
131
+
132
+ export function runBackfillCommand(opts: BackfillOptions = {}): BackfillResult {
133
+ const now = opts.now ?? Date.now();
134
+ const sinceMs = opts.sinceMs ?? now - DEFAULT_BACKFILL_DAYS * 24 * 60 * 60 * 1000;
135
+ const home = failproofaiHome();
136
+
137
+ if (!existsSync(home)) {
138
+ return {
139
+ exitCode: 1,
140
+ lines: [`No failproofai home at ${home}. Run \`failproofai config\` first.`],
141
+ };
142
+ }
143
+
144
+ // Checked HERE, not left to the daemon. Handing off an impossible request and
145
+ // reporting success is the failure this command is partly a response to.
146
+ const ingest = readIngestCredential();
147
+ if (!ingest) {
148
+ return {
149
+ exitCode: 1,
150
+ lines: [
151
+ "This machine is not connected, so there is nowhere to send history.",
152
+ "Connect first: `failproofai config --connect <url> --token <key>`.",
153
+ ],
154
+ };
155
+ }
156
+
157
+ let sessions = false;
158
+ let hooks = false;
159
+ try {
160
+ const cfg = readConfig();
161
+ sessions = cfg.collector?.sessions ?? false;
162
+ hooks = cfg.collector?.hooks ?? false;
163
+ } catch {
164
+ /* an unreadable config is reported as "nothing enabled" below */
165
+ }
166
+ if (!sessions && !hooks) {
167
+ return {
168
+ exitCode: 1,
169
+ lines: [
170
+ "Collection is switched off, so a backfill would re-read history and send",
171
+ 'none of it. Enable it in ~/.failproofai/config.json under "collector".',
172
+ ],
173
+ };
174
+ }
175
+
176
+ const found = surveySessions(sinceMs, dirname(home));
177
+ const totalFiles = found.reduce((n, f) => n + f.files, 0);
178
+ const sinceLabel = new Date(sinceMs).toISOString().slice(0, 10);
179
+
180
+ const lines: string[] = [
181
+ `Re-reading everything modified since ${sinceLabel}, and sending it again.`,
182
+ "",
183
+ ];
184
+ // Named honestly: only what the config actually enables, because that is all
185
+ // the daemon will ship. Promising transcripts on a machine with
186
+ // `sessions = false` would be a lie the user could only catch by waiting.
187
+ lines.push(
188
+ ` streams: ${[sessions && "session transcripts", hooks && "hook decisions"]
189
+ .filter(Boolean)
190
+ .join(" + ")}`,
191
+ );
192
+ if (sessions) {
193
+ if (found.length === 0) {
194
+ lines.push(" sessions: none found on disk in this window");
195
+ } else {
196
+ for (const f of found) {
197
+ lines.push(` ${f.cli}: ${f.files} file${f.files === 1 ? "" : "s"}`);
198
+ }
199
+ }
200
+ }
201
+
202
+ if (opts.dryRun) {
203
+ lines.push("", "--dry-run: nothing was requested.");
204
+ return { exitCode: 0, lines };
205
+ }
206
+
207
+ try {
208
+ const path = backfillRequestPath();
209
+ mkdirSync(dirname(path), { recursive: true });
210
+ // Whole-file write of a small JSON object: the daemon deletes it before
211
+ // acting, so a partially-written file can only ever be read once, and an
212
+ // unparseable one is discarded rather than retried.
213
+ writeFileSync(path, `${JSON.stringify({ sinceMs, requestedAtMs: now }, null, 2)}\n`, {
214
+ mode: 0o600,
215
+ });
216
+ } catch (err) {
217
+ return {
218
+ exitCode: 2,
219
+ lines: [
220
+ ...lines,
221
+ "",
222
+ `Could not write the request: ${err instanceof Error ? err.message : String(err)}`,
223
+ ],
224
+ };
225
+ }
226
+
227
+ lines.push("", "Requested. The daemon picks this up within a few seconds.");
228
+
229
+ // A daemon that is not running will act on the request whenever it next
230
+ // starts — the request is a file, deliberately, so it survives that. Saying so
231
+ // is the difference between "nothing happened" and "nothing happened yet".
232
+ if (isDaemonSupportedPlatform()) {
233
+ const status = daemonServiceStatus();
234
+ if (status !== "running") {
235
+ lines.push(
236
+ `failproofaid is ${status}, so nothing moves until it starts. The request is`,
237
+ "on disk and will be honoured then.",
238
+ );
239
+ }
240
+ }
241
+ lines.push(
242
+ "",
243
+ `Watch it land: ${totalFiles > 0 ? `~${totalFiles} files queued · ` : ""}` +
244
+ "`failproofai config --status`",
245
+ );
246
+ return { exitCode: 0, lines };
247
+ }