failproofai 1.0.0-beta.5 → 1.0.0-beta.7

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 (368) 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 +3 -3
  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/server-reference-manifest.json +1 -1
  8. package/.next/standalone/.next/server/app/_global-error/page.js +3 -1
  9. package/.next/standalone/.next/server/app/_global-error/page.js.nft.json +1 -1
  10. package/.next/standalone/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
  11. package/.next/standalone/.next/server/app/_global-error.html +1 -1
  12. package/.next/standalone/.next/server/app/_global-error.rsc +7 -7
  13. package/.next/standalone/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +2 -2
  14. package/.next/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +7 -7
  15. package/.next/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +3 -3
  16. package/.next/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +3 -3
  17. package/.next/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  18. package/.next/standalone/.next/server/app/_not-found/page/server-reference-manifest.json +1 -1
  19. package/.next/standalone/.next/server/app/_not-found/page.js +3 -1
  20. package/.next/standalone/.next/server/app/_not-found/page.js.nft.json +1 -1
  21. package/.next/standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  22. package/.next/standalone/.next/server/app/_not-found.html +1 -1
  23. package/.next/standalone/.next/server/app/_not-found.rsc +15 -15
  24. package/.next/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +15 -15
  25. package/.next/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +4 -4
  26. package/.next/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +10 -10
  27. package/.next/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +2 -2
  28. package/.next/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +3 -3
  29. package/.next/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
  30. package/.next/standalone/.next/server/app/api/audit/invite/route.js +3 -2
  31. package/.next/standalone/.next/server/app/api/audit/invite/route.js.nft.json +1 -1
  32. package/.next/standalone/.next/server/app/api/audit/run/route.js +4 -3
  33. package/.next/standalone/.next/server/app/api/audit/run/route.js.nft.json +1 -1
  34. package/.next/standalone/.next/server/app/api/audit/status/route.js +1 -1
  35. package/.next/standalone/.next/server/app/api/audit/status/route.js.nft.json +1 -1
  36. package/.next/standalone/.next/server/app/api/auth/login-request/route.js +2 -1
  37. package/.next/standalone/.next/server/app/api/auth/login-request/route.js.nft.json +1 -1
  38. package/.next/standalone/.next/server/app/api/auth/login-verify/route.js +3 -2
  39. package/.next/standalone/.next/server/app/api/auth/login-verify/route.js.nft.json +1 -1
  40. package/.next/standalone/.next/server/app/api/auth/logout/route.js +3 -2
  41. package/.next/standalone/.next/server/app/api/auth/logout/route.js.nft.json +1 -1
  42. package/.next/standalone/.next/server/app/api/auth/reminder/route.js +3 -2
  43. package/.next/standalone/.next/server/app/api/auth/reminder/route.js.nft.json +1 -1
  44. package/.next/standalone/.next/server/app/api/auth/status/route.js +3 -2
  45. package/.next/standalone/.next/server/app/api/auth/status/route.js.nft.json +1 -1
  46. package/.next/standalone/.next/server/app/api/download/[project]/[session]/route.js +1 -1
  47. package/.next/standalone/.next/server/app/api/download/[project]/[session]/route.js.nft.json +1 -1
  48. package/.next/standalone/.next/server/app/audit/page/server-reference-manifest.json +2 -2
  49. package/.next/standalone/.next/server/app/audit/page.js +5 -3
  50. package/.next/standalone/.next/server/app/audit/page.js.nft.json +1 -1
  51. package/.next/standalone/.next/server/app/audit/page_client-reference-manifest.js +1 -1
  52. package/.next/standalone/.next/server/app/index.html +1 -1
  53. package/.next/standalone/.next/server/app/index.rsc +15 -15
  54. package/.next/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +2 -2
  55. package/.next/standalone/.next/server/app/index.segments/_full.segment.rsc +15 -15
  56. package/.next/standalone/.next/server/app/index.segments/_head.segment.rsc +4 -4
  57. package/.next/standalone/.next/server/app/index.segments/_index.segment.rsc +10 -10
  58. package/.next/standalone/.next/server/app/index.segments/_tree.segment.rsc +2 -2
  59. package/.next/standalone/.next/server/app/page/server-reference-manifest.json +1 -1
  60. package/.next/standalone/.next/server/app/page.js +3 -1
  61. package/.next/standalone/.next/server/app/page.js.nft.json +1 -1
  62. package/.next/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
  63. package/.next/standalone/.next/server/app/policies/page/server-reference-manifest.json +10 -10
  64. package/.next/standalone/.next/server/app/policies/page.js +4 -2
  65. package/.next/standalone/.next/server/app/policies/page.js.nft.json +1 -1
  66. package/.next/standalone/.next/server/app/policies/page_client-reference-manifest.js +1 -1
  67. package/.next/standalone/.next/server/app/project/[name]/page/server-reference-manifest.json +1 -1
  68. package/.next/standalone/.next/server/app/project/[name]/page.js +4 -2
  69. package/.next/standalone/.next/server/app/project/[name]/page.js.nft.json +1 -1
  70. package/.next/standalone/.next/server/app/project/[name]/page_client-reference-manifest.js +1 -1
  71. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page/react-loadable-manifest.json +2 -2
  72. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page/server-reference-manifest.json +2 -2
  73. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page.js +6 -4
  74. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page.js.nft.json +1 -1
  75. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page_client-reference-manifest.js +1 -1
  76. package/.next/standalone/.next/server/app/projects/page/server-reference-manifest.json +1 -1
  77. package/.next/standalone/.next/server/app/projects/page.js +4 -2
  78. package/.next/standalone/.next/server/app/projects/page.js.nft.json +1 -1
  79. package/.next/standalone/.next/server/app/projects/page_client-reference-manifest.js +1 -1
  80. package/.next/standalone/.next/server/app/settings/page/app-paths-manifest.json +3 -0
  81. package/.next/standalone/.next/server/app/settings/page/build-manifest.json +18 -0
  82. package/.next/standalone/.next/server/app/settings/page/next-font-manifest.json +6 -0
  83. package/.next/standalone/.next/server/app/settings/page/react-loadable-manifest.json +1 -0
  84. package/.next/standalone/.next/server/app/settings/page/server-reference-manifest.json +53 -0
  85. package/.next/standalone/.next/server/app/settings/page.js +18 -0
  86. package/.next/standalone/.next/server/app/settings/page.js.map +5 -0
  87. package/.next/standalone/.next/server/app/settings/page.js.nft.json +1 -0
  88. package/.next/standalone/.next/server/app/settings/page_client-reference-manifest.js +3 -0
  89. package/.next/standalone/.next/server/app-paths-manifest.json +2 -1
  90. package/.next/standalone/.next/server/chunks/{[externals]__1nl3dvw._.js → [externals]__0r-zamh._.js} +2 -2
  91. package/.next/standalone/.next/server/chunks/[root-of-the-server]__06hexd0._.js +3 -0
  92. package/.next/standalone/.next/server/chunks/[root-of-the-server]__094p1f1._.js +3 -0
  93. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0_0xu5z._.js +1 -1
  94. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0ed01_d._.js +3 -0
  95. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0g0vcwt._.js +3 -0
  96. package/.next/standalone/.next/server/chunks/{[root-of-the-server]__0h3lfmp._.js → [root-of-the-server]__0ky_n0r._.js} +2 -2
  97. package/.next/standalone/.next/server/chunks/{[root-of-the-server]__18d-9oi._.js → [root-of-the-server]__0lk_935._.js} +2 -2
  98. package/.next/standalone/.next/server/chunks/{[root-of-the-server]__1p7s8-d._.js → [root-of-the-server]__0pbp9fe._.js} +2 -2
  99. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0q-v9z2._.js +1 -1
  100. package/.next/standalone/.next/server/chunks/{[root-of-the-server]__10oa-v9._.js → [root-of-the-server]__0v2fpxe._.js} +2 -2
  101. package/.next/standalone/.next/server/chunks/{[root-of-the-server]__0sb_5m8._.js → [root-of-the-server]__131ebum._.js} +3 -3
  102. package/.next/standalone/.next/server/chunks/{[root-of-the-server]__0-dh5jy._.js → [root-of-the-server]__1_ps0jw._.js} +2 -2
  103. package/.next/standalone/.next/server/chunks/{[root-of-the-server]__0l4vuqm._.js → [root-of-the-server]__1a7i2q4._.js} +1 -1
  104. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1ibjy5x._.js +3 -0
  105. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1iny24d._.js +3 -0
  106. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1tvn4wf._.js +1 -1
  107. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1tyedjt._.js +3 -0
  108. package/.next/standalone/.next/server/chunks/_0lxbzdq._.js +5 -0
  109. package/.next/standalone/.next/server/chunks/_1zuiiy3._.js +5 -0
  110. package/.next/standalone/.next/server/chunks/lib_telemetry_ts_0dctyyw._.js +5 -0
  111. package/.next/standalone/.next/server/chunks/node_modules_next_dist_esm_build_templates_app-route_17k9e3w.js +5 -5
  112. package/.next/standalone/.next/server/chunks/package_json_[json]_cjs_1nxcc4v._.js +1 -1
  113. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__06bp_fg._.js +5 -0
  114. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__074qmkp._.js +3 -0
  115. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0c_qcgp._.js +3 -0
  116. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0kbc9bs._.js +3 -0
  117. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0spkm68._.js +10 -10
  118. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1a2hcb_._.js +3 -0
  119. package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__0rtdfof._.js → [root-of-the-server]__1rdruw4._.js} +1 -1
  120. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1rv-sc9._.js +3 -0
  121. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1tyf0tc._.js +5 -0
  122. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1u-wx42._.js +4 -0
  123. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1v185mo._.js +3 -0
  124. package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__0rag5dg._.js → [root-of-the-server]__1yw368c._.js} +2 -2
  125. package/.next/standalone/.next/server/chunks/ssr/_0-yi74u._.js +3 -0
  126. package/.next/standalone/.next/server/chunks/ssr/_04mekdy._.js +3 -0
  127. package/.next/standalone/.next/server/chunks/ssr/_0r-dsre._.js +3 -0
  128. package/.next/standalone/.next/server/chunks/ssr/_0rjgfln._.js +3 -0
  129. package/.next/standalone/.next/server/chunks/ssr/_0se484m._.js +3 -0
  130. package/.next/standalone/.next/server/chunks/ssr/_0uz7o9y._.js +3 -0
  131. package/.next/standalone/.next/server/chunks/ssr/_11_p9y8._.js +1 -1
  132. package/.next/standalone/.next/server/chunks/ssr/_194yh9u._.js +3 -0
  133. package/.next/standalone/.next/server/chunks/ssr/_1fahg9e._.js +3 -0
  134. package/.next/standalone/.next/server/chunks/ssr/_1gmzkwj._.js +5 -0
  135. package/.next/standalone/.next/server/chunks/ssr/_1w8sola._.js +3 -0
  136. package/.next/standalone/.next/server/chunks/ssr/_1w_6p9g._.js +3 -0
  137. package/.next/standalone/.next/server/chunks/ssr/app_audit__components_audit-dashboard_tsx_0p9ud47._.js +2 -2
  138. package/.next/standalone/.next/server/chunks/ssr/app_global-error_tsx_1kp6l3x._.js +1 -1
  139. package/.next/standalone/.next/server/chunks/ssr/app_policies_hooks-client_tsx_19dqvpc._.js +2 -2
  140. package/.next/standalone/.next/server/chunks/ssr/node_modules_next_dist_esm_build_templates_app-page_0dwo94s.js +4 -0
  141. package/.next/standalone/.next/server/chunks/ssr/src_hooks_builtin-policies_ts_09j2ndl._.js +3 -0
  142. package/.next/standalone/.next/server/chunks/ssr/src_hooks_fp-config_ts_04t589g._.js +5 -0
  143. package/.next/standalone/.next/server/middleware-build-manifest.js +3 -3
  144. package/.next/standalone/.next/server/middleware.js +2 -2
  145. package/.next/standalone/.next/server/pages/404.html +1 -1
  146. package/.next/standalone/.next/server/pages/500.html +1 -1
  147. package/.next/standalone/.next/server/server-reference-manifest.js +1 -1
  148. package/.next/standalone/.next/server/server-reference-manifest.json +54 -12
  149. package/.next/standalone/.next/static/chunks/0945ybz3fsvba.js +1 -0
  150. package/.next/standalone/.next/static/chunks/{0cr85wuc9gc9k.js → 0gr76ps8_zm26.js} +1 -1
  151. package/.next/standalone/.next/static/chunks/{2cqk4a9s893oy.js → 0m-m7wgtxdoav.js} +1 -1
  152. package/.next/standalone/.next/static/chunks/{1tp9dex23zs8y.js → 11l6oh9cyb35a.js} +1 -1
  153. package/.next/standalone/.next/static/chunks/166f8qnh03jqc.js +6 -0
  154. package/.next/standalone/.next/static/chunks/1bm0q12hgs22l.css +2 -0
  155. package/.next/standalone/.next/static/chunks/1v1gqvpjk62jm.js +1 -0
  156. package/.next/standalone/.next/static/chunks/{2s9g1dsde_ts1.js → 2aej0vi1fm87m.js} +1 -1
  157. package/.next/standalone/.next/static/chunks/{1bkynffp3q1f7.js → 2bxld6-rym3ah.js} +22 -22
  158. package/.next/standalone/.next/static/chunks/{2v1rpzrh2i1wv.js → 2p2kz4aj1tooj.js} +1 -1
  159. package/.next/standalone/.next/static/chunks/2uvhw57mf9lg_.css +2 -0
  160. package/.next/standalone/.next/static/chunks/36pc3_8fpb6ic.js +1 -0
  161. package/.next/standalone/.next/static/chunks/{2k285z07qce47.js → 3t1wr_kx6brwc.js} +1 -1
  162. package/.next/standalone/app/actions/get-hooks-config.ts +2 -1
  163. package/.next/standalone/app/actions/get-scheduled-audit.ts +70 -0
  164. package/.next/standalone/app/actions/update-scheduled-audit.ts +44 -0
  165. package/.next/standalone/app/api/audit/run/route.ts +28 -0
  166. package/.next/standalone/app/api/audit/status/route.ts +13 -1
  167. package/.next/standalone/app/api/auth/status/route.ts +1 -1
  168. package/.next/standalone/app/audit/_components/auth-dialog.css +159 -0
  169. package/.next/standalone/app/audit/_components/auth-dialog.tsx +3 -0
  170. package/.next/standalone/app/audit/audit-styles.css +6 -149
  171. package/.next/standalone/app/settings/page.tsx +31 -0
  172. package/.next/standalone/app/settings/settings-client.tsx +488 -0
  173. package/.next/standalone/components/navbar.tsx +2 -0
  174. package/.next/standalone/lib/auth/api-server-client.ts +2 -3
  175. package/.next/standalone/lib/auth/auth-store.ts +4 -4
  176. package/.next/standalone/lib/codex-sessions.ts +2 -1
  177. package/.next/standalone/lib/dashboard-host.ts +101 -0
  178. package/.next/standalone/lib/install-check.ts +22 -7
  179. package/.next/standalone/lib/pi-sessions.ts +99 -14
  180. package/.next/standalone/lib/telemetry-enabled.ts +54 -0
  181. package/.next/standalone/lib/telemetry-id.ts +52 -1
  182. package/.next/standalone/lib/telemetry.ts +8 -4
  183. package/.next/standalone/node_modules/react/cjs/react.development.js +1 -1
  184. package/.next/standalone/node_modules/react/cjs/react.production.js +1 -1
  185. package/.next/standalone/node_modules/react/package.json +3 -3
  186. package/.next/standalone/node_modules/react-dom/cjs/react-dom-server-legacy.browser.production.js +1 -1
  187. package/.next/standalone/node_modules/react-dom/cjs/react-dom-server-legacy.node.production.js +1 -1
  188. package/.next/standalone/node_modules/react-dom/cjs/react-dom-server.browser.production.js +3 -3
  189. package/.next/standalone/node_modules/react-dom/cjs/react-dom-server.edge.production.js +3 -3
  190. package/.next/standalone/node_modules/react-dom/cjs/react-dom-server.node.production.js +3 -3
  191. package/.next/standalone/node_modules/react-dom/cjs/react-dom.production.js +1 -1
  192. package/.next/standalone/node_modules/react-dom/package.json +4 -4
  193. package/.next/standalone/package.json +7 -6
  194. package/.next/standalone/proxy.ts +89 -1
  195. package/.next/standalone/server.js +1 -1
  196. package/bin/failproofai.mjs +202 -86
  197. package/dist/cli.mjs +17803 -15346
  198. package/dist/worker.mjs +1620 -257
  199. package/lib/auth/api-server-client.ts +2 -3
  200. package/lib/auth/auth-store.ts +4 -4
  201. package/lib/codex-sessions.ts +2 -1
  202. package/lib/dashboard-host.ts +101 -0
  203. package/lib/install-check.ts +22 -7
  204. package/lib/pi-sessions.ts +99 -14
  205. package/lib/telemetry-enabled.ts +54 -0
  206. package/lib/telemetry-id.ts +52 -1
  207. package/lib/telemetry.ts +8 -4
  208. package/package.json +7 -6
  209. package/scripts/install-telemetry.mjs +8 -1
  210. package/scripts/launch.ts +25 -3
  211. package/scripts/parse-script-args.ts +17 -1
  212. package/scripts/prune-standalone.mjs +20 -0
  213. package/scripts/translate-docs/readme-translator.ts +163 -4
  214. package/scripts/validate-mdx.ts +153 -13
  215. package/src/audit/audit-lock.ts +285 -0
  216. package/src/audit/audit-schedule.ts +111 -0
  217. package/src/audit/cache.ts +18 -4
  218. package/src/audit/cli-adapters/goose.ts +2 -1
  219. package/src/audit/cli-adapters/hermes.ts +28 -10
  220. package/src/audit/cli.ts +191 -35
  221. package/src/audit/dashboard-cache.ts +2 -1
  222. package/src/hooks/builtin-policies.ts +100 -3
  223. package/src/hooks/cloud-connection.ts +160 -14
  224. package/src/hooks/cloud-enrollment-cli.ts +119 -21
  225. package/src/hooks/cloud-enrollment.ts +87 -17
  226. package/src/hooks/cloud-introspect.ts +147 -0
  227. package/src/hooks/cloud-managed-policies.ts +33 -2
  228. package/src/hooks/collector-config.ts +25 -43
  229. package/src/hooks/configure-wizard.ts +750 -318
  230. package/src/hooks/custom-hooks-loader.ts +125 -11
  231. package/src/hooks/daemon-client.ts +83 -17
  232. package/src/hooks/daemon-download.ts +54 -2
  233. package/src/hooks/daemon-service.ts +605 -29
  234. package/src/hooks/first-run-gate.ts +39 -0
  235. package/src/hooks/fp-config.ts +497 -0
  236. package/src/hooks/fp-home.ts +355 -0
  237. package/src/hooks/fp-reset.ts +332 -0
  238. package/src/hooks/handler.ts +42 -1
  239. package/src/hooks/hook-activity-store.ts +35 -20
  240. package/src/hooks/hook-logger.ts +2 -1
  241. package/src/hooks/hook-telemetry.ts +5 -1
  242. package/src/hooks/hooks-config.ts +4 -3
  243. package/src/hooks/loader-utils.ts +166 -5
  244. package/src/hooks/manager.ts +5 -4
  245. package/src/hooks/onboarding-attempt.ts +179 -0
  246. package/src/hooks/onboarding-lock.ts +119 -0
  247. package/src/hooks/policy-types.ts +0 -7
  248. package/src/hooks/session-pause-cli.ts +15 -13
  249. package/src/hooks/session-pause.ts +36 -4
  250. package/src/hooks/setup-state.ts +256 -0
  251. package/src/hooks/tui.ts +4 -4
  252. package/src/hooks/worker-server.ts +53 -1
  253. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0f8nu8d._.js +0 -3
  254. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0szqzl8._.js +0 -3
  255. package/.next/standalone/.next/server/chunks/[root-of-the-server]__17g9wh7._.js +0 -3
  256. package/.next/standalone/.next/server/chunks/[root-of-the-server]__19120tr._.js +0 -3
  257. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1fwl2mz._.js +0 -3
  258. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1myjm-d._.js +0 -3
  259. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1rhmvod._.js +0 -3
  260. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__02r5bgf._.js +0 -4
  261. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__094nop3._.js +0 -3
  262. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0e8sjqm._.js +0 -4
  263. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0lkzqax._.js +0 -4
  264. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0ls0gsr._.js +0 -4
  265. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0n_uvv0._.js +0 -3
  266. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0qucxyj._.js +0 -4
  267. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0u02miy._.js +0 -4
  268. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__14dd6h8._.js +0 -3
  269. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1gak4pd._.js +0 -3
  270. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1j4u3yd._.js +0 -4
  271. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1zqz4v8._.js +0 -4
  272. package/.next/standalone/.next/server/chunks/ssr/_1m_p7pi._.js +0 -3
  273. package/.next/standalone/.next/server/chunks/ssr/node_modules_html-to-image_es_index_12c_2cz.js +0 -3
  274. package/.next/standalone/.next/server/chunks/ssr/src_hooks_1ezd2jf._.js +0 -5
  275. package/.next/standalone/.next/server/chunks/ssr/src_hooks_1tnuifj._.js +0 -5
  276. package/.next/standalone/.next/static/chunks/0h7auy7hzjyhw.js +0 -1
  277. package/.next/standalone/.next/static/chunks/1bw4dfzflelcv.js +0 -1
  278. package/.next/standalone/.next/static/chunks/34sjdw8lv46e3.js +0 -6
  279. package/.next/standalone/.next/static/chunks/3i27c3hcriawq.css +0 -1
  280. package/.next/standalone/.next/static/chunks/3zdwnnthucs_1.css +0 -2
  281. package/.next/standalone/Cargo.lock +0 -2222
  282. package/.next/standalone/Cargo.toml +0 -9
  283. package/.next/standalone/crates/CLOUD_POLICIES.md +0 -136
  284. package/.next/standalone/crates/PROTOCOL.md +0 -122
  285. package/.next/standalone/crates/failproofaid/Cargo.toml +0 -25
  286. package/.next/standalone/crates/failproofaid/src/cloud_client.rs +0 -484
  287. package/.next/standalone/crates/failproofaid/src/cloud_policies.rs +0 -921
  288. package/.next/standalone/crates/failproofaid/src/lock.rs +0 -81
  289. package/.next/standalone/crates/failproofaid/src/main.rs +0 -639
  290. package/.next/standalone/crates/failproofaid/src/paths.rs +0 -201
  291. package/.next/standalone/crates/failproofaid/src/server.rs +0 -502
  292. package/.next/standalone/crates/failproofaid/src/worker.rs +0 -400
  293. package/.next/standalone/crates/failproofaid/tests/daemon_e2e.rs +0 -241
  294. package/.next/standalone/crates/fpai-collect/Cargo.toml +0 -43
  295. package/.next/standalone/crates/fpai-collect/src/config.rs +0 -420
  296. package/.next/standalone/crates/fpai-collect/src/cursor.rs +0 -520
  297. package/.next/standalone/crates/fpai-collect/src/delivery.rs +0 -472
  298. package/.next/standalone/crates/fpai-collect/src/filetail.rs +0 -487
  299. package/.next/standalone/crates/fpai-collect/src/health.rs +0 -369
  300. package/.next/standalone/crates/fpai-collect/src/lib.rs +0 -34
  301. package/.next/standalone/crates/fpai-collect/src/redact.rs +0 -516
  302. package/.next/standalone/crates/fpai-collect/src/sources/claude/mod.rs +0 -279
  303. package/.next/standalone/crates/fpai-collect/src/sources/claude/transform.rs +0 -619
  304. package/.next/standalone/crates/fpai-collect/src/sources/codex/mod.rs +0 -156
  305. package/.next/standalone/crates/fpai-collect/src/sources/codex/transform.rs +0 -715
  306. package/.next/standalone/crates/fpai-collect/src/sources/copilot/mod.rs +0 -158
  307. package/.next/standalone/crates/fpai-collect/src/sources/copilot/transform.rs +0 -512
  308. package/.next/standalone/crates/fpai-collect/src/sources/goose/mod.rs +0 -511
  309. package/.next/standalone/crates/fpai-collect/src/sources/goose/transform.rs +0 -586
  310. package/.next/standalone/crates/fpai-collect/src/sources/hermes/mod.rs +0 -318
  311. package/.next/standalone/crates/fpai-collect/src/sources/hermes/transform.rs +0 -579
  312. package/.next/standalone/crates/fpai-collect/src/sources/hooks/mod.rs +0 -314
  313. package/.next/standalone/crates/fpai-collect/src/sources/hooks/transform.rs +0 -448
  314. package/.next/standalone/crates/fpai-collect/src/sources/mod.rs +0 -23
  315. package/.next/standalone/crates/fpai-collect/src/sources/openclaw/mod.rs +0 -176
  316. package/.next/standalone/crates/fpai-collect/src/sources/openclaw/transform.rs +0 -500
  317. package/.next/standalone/crates/fpai-collect/src/sources/opencode/mod.rs +0 -315
  318. package/.next/standalone/crates/fpai-collect/src/sources/opencode/transform.rs +0 -493
  319. package/.next/standalone/crates/fpai-collect/src/sources/pi/mod.rs +0 -201
  320. package/.next/standalone/crates/fpai-collect/src/sources/pi/transform.rs +0 -533
  321. package/.next/standalone/crates/fpai-collect/src/spool.rs +0 -535
  322. package/.next/standalone/crates/fpai-collect/src/sqlitepoll.rs +0 -390
  323. package/.next/standalone/crates/fpai-collect/src/supervisor.rs +0 -423
  324. package/.next/standalone/crates/fpai-collect/src/uploader.rs +0 -600
  325. package/.next/standalone/crates/fpai-collect/tests/claude_source.rs +0 -739
  326. package/.next/standalone/crates/fpai-collect/tests/claude_subagents.rs +0 -394
  327. package/.next/standalone/crates/fpai-collect/tests/codex_source.rs +0 -976
  328. package/.next/standalone/crates/fpai-collect/tests/config.rs +0 -286
  329. package/.next/standalone/crates/fpai-collect/tests/copilot_source.rs +0 -1020
  330. package/.next/standalone/crates/fpai-collect/tests/delivery.rs +0 -281
  331. package/.next/standalone/crates/fpai-collect/tests/goose_source.rs +0 -1009
  332. package/.next/standalone/crates/fpai-collect/tests/hermes_source.rs +0 -1130
  333. package/.next/standalone/crates/fpai-collect/tests/hooks_source.rs +0 -622
  334. package/.next/standalone/crates/fpai-collect/tests/openclaw_source.rs +0 -855
  335. package/.next/standalone/crates/fpai-collect/tests/opencode_source.rs +0 -1478
  336. package/.next/standalone/crates/fpai-collect/tests/pi_source.rs +0 -1192
  337. package/.next/standalone/crates/fpai-collect/tests/supervisor.rs +0 -290
  338. package/.next/standalone/crates/fpai-collect/tests/uploader.rs +0 -506
  339. package/.next/standalone/crates/fpai-ipc/Cargo.toml +0 -16
  340. package/.next/standalone/crates/fpai-ipc/src/envelope.rs +0 -175
  341. package/.next/standalone/crates/fpai-ipc/src/framing.rs +0 -177
  342. package/.next/standalone/crates/fpai-ipc/src/lib.rs +0 -10
  343. package/.next/standalone/crates/fpai-ipc/src/peer.rs +0 -80
  344. package/.next/standalone/docker-hook-sync/Dockerfile +0 -79
  345. package/.next/standalone/docker-hook-sync/entrypoint.sh +0 -109
  346. package/.next/standalone/integration-suite/Dockerfile +0 -47
  347. package/.next/standalone/integration-suite/README.md +0 -129
  348. package/.next/standalone/integration-suite/canary-policies.mjs +0 -45
  349. package/.next/standalone/integration-suite/capture-tokens.sh +0 -48
  350. package/.next/standalone/integration-suite/ci-entrypoint.sh +0 -179
  351. package/.next/standalone/integration-suite/inject-tokens.sh +0 -44
  352. package/.next/standalone/integration-suite/install-clis.sh +0 -136
  353. package/.next/standalone/integration-suite/probe-cli.sh +0 -248
  354. package/.next/standalone/integration-suite/report.js +0 -120
  355. package/.next/standalone/integration-suite/run.sh +0 -159
  356. package/.next/standalone/openclaw-plugin/index.js +0 -223
  357. package/.next/standalone/openclaw-plugin/openclaw.plugin.json +0 -6
  358. package/.next/standalone/openclaw-plugin/package.json +0 -12
  359. package/.next/standalone/osv-scanner.toml +0 -17
  360. package/.next/standalone/pi-extension/index.ts +0 -524
  361. package/.next/standalone/pi-extension/package.json +0 -12
  362. package/.next/standalone/postcss.config.mjs +0 -8
  363. package/.next/standalone/readme-arch-hq.gif +0 -0
  364. package/.next/standalone/rust-toolchain.toml +0 -4
  365. package/src/auth/cli.ts +0 -371
  366. /package/.next/standalone/.next/static/{LOuwMmfomO6hQYfMQYs4K → LUaLkKg40nSkHrIajexIk}/_buildManifest.js +0 -0
  367. /package/.next/standalone/.next/static/{LOuwMmfomO6hQYfMQYs4K → LUaLkKg40nSkHrIajexIk}/_clientMiddlewareManifest.js +0 -0
  368. /package/.next/standalone/.next/static/{LOuwMmfomO6hQYfMQYs4K → LUaLkKg40nSkHrIajexIk}/_ssgManifest.js +0 -0
@@ -23,6 +23,9 @@ import { execFileSync } from "node:child_process";
23
23
  import { hookLogWarn } from "./hook-logger";
24
24
  import { getConfigPathForScope } from "./hooks-config";
25
25
  import { downloadFailproofaidBinary, installFromNpmPackage, installedBinaryPath } from "./daemon-download";
26
+ import { logsDir } from "./fp-home";
27
+ import { version } from "../../package.json";
28
+ import { readVersionFile, updateConfig, writeVersionFile } from "./fp-config";
26
29
 
27
30
  /**
28
31
  * Every `systemctl --user` / `launchctl` call is bounded. Both talk to a
@@ -76,19 +79,16 @@ const SUDO_PROMPT_TIMEOUT_MS = 120_000;
76
79
  * Global scope only: whether *this machine* runs a daemon is not a
77
80
  * per-project setting.
78
81
  */
79
- export function setDaemonConfigured(value: boolean): void {
80
- const path = getConfigPathForScope("user");
81
- let config: Record<string, unknown> = {};
82
+ export function setDaemonConfigured(value: boolean, installedVersion?: string): void {
82
83
  try {
83
- if (existsSync(path)) config = JSON.parse(readFileSync(path, "utf8")) as Record<string, unknown>;
84
- } catch {
85
- return; // a malformed global config is the install path's problem, not ours
86
- }
87
- if (value) config.daemonConfigured = true;
88
- else delete config.daemonConfigured;
89
- try {
90
- mkdirSync(dirname(path), { recursive: true });
91
- writeFileSync(path, JSON.stringify(config, null, 2) + "\n", "utf8");
84
+ updateConfig({ daemon: { configured: value } });
85
+ // The version lives in VERSION, not here. One file is ABOUT versions and
86
+ // one copy cannot disagree with itself; keeping a second in config.toml
87
+ // would be a field that drifts out of step with the file beside it —
88
+ // exactly the class of bug the single path module exists to prevent.
89
+ // Cleared on uninstall: a recorded binary version for a service that is no
90
+ // longer configured is a claim about this machine that is no longer true.
91
+ writeVersionFile({ daemon: value ? installedVersion : undefined, clearDaemon: !value });
92
92
  } catch {
93
93
  /* best-effort: never fail a completed setup (or uninstall) over this flag */
94
94
  }
@@ -225,12 +225,94 @@ export function resolveWorkerCommand(): string | null {
225
225
  return `${shellQuote(process.execPath)} ${shellQuote(workerScript)}`;
226
226
  }
227
227
 
228
+ /**
229
+ * Resolves the command the daemon should use to run a one-shot `failproofai`
230
+ * CLI task — today the scheduled audit — passed through as
231
+ * `FAILPROOFAI_CLI_CMD` in the service's own environment.
232
+ *
233
+ * Everything `resolveWorkerCommand` says about `process.execPath` and the
234
+ * shell quoting applies here verbatim, for the same reason: a system-scope
235
+ * unit gets no login environment, so its PATH is the system default, and the
236
+ * single most common Node install (nvm) is on no system PATH. The difference
237
+ * is the consequence of getting it wrong. A worker that cannot start makes the
238
+ * daemon visibly unhealthy; an audit that cannot start is SILENT — the config
239
+ * says the scan is on, nothing ever runs, and the only symptom is a dashboard
240
+ * that quietly stops moving.
241
+ *
242
+ * `dist/cli.mjs` (package.json's `bin.failproofai`), not `bin/failproofai.mjs`
243
+ * — the latter has a `#!/usr/bin/env bun` shebang and uses syntax node cannot
244
+ * load (a bare `import … from "../package.json"`, extensionless `.ts`
245
+ * specifiers). Only the bundle is node-runnable, and it sits beside the
246
+ * `dist/worker.mjs` the worker command already points at.
247
+ */
248
+ export function resolveCliCommand(): string | null {
249
+ if (process.env.FAILPROOFAI_CLI_CMD) return process.env.FAILPROOFAI_CLI_CMD;
250
+
251
+ const packageRoot = process.env.FAILPROOFAI_PACKAGE_ROOT;
252
+ if (!packageRoot) return null;
253
+ const cliScript = resolve(packageRoot, "dist", "cli.mjs");
254
+ if (!existsSync(cliScript)) return null;
255
+ return `${shellQuote(process.execPath)} ${shellQuote(cliScript)}`;
256
+ }
257
+
228
258
  /** POSIX single-quoting: everything is literal inside '…', and a literal
229
259
  * quote is closed, escaped and reopened. */
230
260
  function shellQuote(value: string): string {
231
261
  return `'${value.replace(/'/g, `'\\''`)}'`;
232
262
  }
233
263
 
264
+ /**
265
+ * The environment the service definition carries, built once so the systemd
266
+ * and launchd renderers cannot drift apart — a variable added to one and not
267
+ * the other is invisible until somebody runs the other platform.
268
+ *
269
+ * Order is fixed rather than incidental: `upgradedServiceDefinition` rebuilds
270
+ * a definition from these renderers, so a stable order is what makes a
271
+ * rewritten unit byte-identical to a freshly installed one rather than merely
272
+ * equivalent — which is the difference between a diff a human can read and one
273
+ * they stop reading.
274
+ */
275
+ /**
276
+ * Characters that cannot appear in a value interpolated into a systemd unit.
277
+ *
278
+ * A newline ENDS THE DIRECTIVE, so a path containing one injects arbitrary
279
+ * settings into a file that is installed root-owned at `/etc/systemd/system`
280
+ * and loaded at every boot. A double quote closes `Environment="..."` early and
281
+ * does the same; a backslash is systemd's own escape character inside a quoted
282
+ * value and makes the result unpredictable rather than merely wrong.
283
+ *
284
+ * The mechanism is demonstrated by this repo's own test, which sets
285
+ * `FAILPROOFAI_CLI_CMD` to `/usr/bin/true"\nUser=failproofai-no-such-user` and
286
+ * relies on systemd HONOURING the injected `User=` — it passes only because the
287
+ * injected user does not exist. A valid one (`User=root`, or an added
288
+ * `ExecStartPre=`) would have succeeded silently, undoing the
289
+ * "root-installed but never root-run" invariant the whole design rests on.
290
+ *
291
+ * These values are resolved paths and commands; none can legitimately contain
292
+ * any of these characters. So this REJECTS rather than escaping — a unit that
293
+ * refuses to be written is a clear failure, and inventing an escaping scheme
294
+ * for systemd's grammar is a larger surface than the problem.
295
+ */
296
+ const UNIT_UNSAFE_RE = /["\\\r\n]/;
297
+
298
+ /** Throws when a value cannot be safely interpolated into a unit file. */
299
+ function assertUnitSafe(value: string, field: string): string {
300
+ if (UNIT_UNSAFE_RE.test(value)) {
301
+ throw new Error(
302
+ `refusing to write a service definition: ${field} contains a quote, backslash or newline ` +
303
+ `(${JSON.stringify(value)}), which would inject directives into a root-owned unit file`,
304
+ );
305
+ }
306
+ return value;
307
+ }
308
+
309
+ function serviceEnvironment(workerCmd: string | null, cliCmd: string | null): [string, string][] {
310
+ const entries: [string, string][] = [];
311
+ if (workerCmd) entries.push(["FAILPROOFAI_WORKER_CMD", workerCmd]);
312
+ if (cliCmd) entries.push(["FAILPROOFAI_CLI_CMD", cliCmd]);
313
+ return entries;
314
+ }
315
+
234
316
  /**
235
317
  * The account the daemon runs as. The service is root-*installed* but never
236
318
  * root-*run*: everything it touches (the socket, the lock, the policy config)
@@ -321,6 +403,22 @@ export function daemonStatusCommand(): string | null {
321
403
  : `sudo launchctl print system/${launchdLabel()}`;
322
404
  }
323
405
 
406
+ /**
407
+ * The command that restarts the daemon, for the places where a config change
408
+ * only takes effect on the next start.
409
+ *
410
+ * `--disconnect` is the motivating case: the collector manager starts once for
411
+ * the daemon's lifetime and the uploader caches its bearer key at construction,
412
+ * so removing the credential file changes nothing about the process already
413
+ * running.
414
+ */
415
+ export function daemonRestartCommand(): string | null {
416
+ if (!isDaemonSupportedPlatform()) return null;
417
+ return process.platform === "linux"
418
+ ? `sudo systemctl restart ${systemdUnitName()}`
419
+ : `sudo launchctl kickstart -k system/${launchdLabel()}`;
420
+ }
421
+
324
422
  /**
325
423
  * Acquires sudo credentials up front, prompting in the terminal if needed.
326
424
  *
@@ -352,8 +450,16 @@ export function primeElevation(): boolean {
352
450
  }
353
451
  }
354
452
 
355
- /** True when privileged commands can run without prompting for a password. */
356
- function canElevate(): boolean {
453
+ /**
454
+ * True when privileged commands can run without prompting for a password.
455
+ *
456
+ * Exported because onboarding re-checks it: `needs_root` is the most common
457
+ * reason setup aborts, and it is the one most likely to stop being true (the
458
+ * user gets sudo rights, or primes their timestamp in another terminal). One
459
+ * `sudo -n true`, no prompt, milliseconds — and never on the hook path, which
460
+ * does not reach the first-run gate at all.
461
+ */
462
+ export function canElevate(): boolean {
357
463
  if (typeof process.getuid === "function" && process.getuid() === 0) return true;
358
464
  try {
359
465
  execFileSync("sudo", ["-n", "true"], { stdio: "ignore", timeout: SERVICE_CMD_TIMEOUT_MS });
@@ -457,11 +563,19 @@ function removeLegacyUserService(): void {
457
563
  }
458
564
  }
459
565
 
460
- export function systemdUnitContents(binaryPath: string, workerCmd: string | null): string {
461
- // Quoted because both values contain a space or a path — systemd's
566
+ export function systemdUnitContents(
567
+ binaryPath: string,
568
+ workerCmd: string | null,
569
+ cliCmd: string | null = null,
570
+ ): string {
571
+ // Quoted because every value here contains a space or a path — systemd's
462
572
  // Environment= requires quoting whenever the value does.
463
- const envLine = workerCmd ? `Environment="FAILPROOFAI_WORKER_CMD=${workerCmd}"\n` : "";
464
- const user = serviceUser();
573
+ const envLines = serviceEnvironment(workerCmd, cliCmd)
574
+ .map(([key, value]) => `Environment="${key}=${assertUnitSafe(value, key)}"\n`)
575
+ .join("");
576
+ const user = assertUnitSafe(serviceUser(), "User");
577
+ assertUnitSafe(binaryPath, "ExecStart");
578
+ assertUnitSafe(homedir(), "HOME");
465
579
  return `[Unit]
466
580
  Description=failproofai background daemon (failproofaid) for ${user}
467
581
  After=network.target
@@ -474,7 +588,7 @@ User=${user}
474
588
  # HOME ("HOME is not set; failproofaid is user-scope only"), so the one
475
589
  # variable it cannot do without is not left to a version-dependent default.
476
590
  Environment="HOME=${homedir()}"
477
- ${envLine}ExecStart=${binaryPath}
591
+ ${envLines}ExecStart=${binaryPath}
478
592
  Restart=on-failure
479
593
  RestartSec=2
480
594
 
@@ -490,12 +604,27 @@ function escapeXml(s: string): string {
490
604
  return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
491
605
  }
492
606
 
493
- function launchdPlistContents(binaryPath: string, logDir: string, workerCmd: string | null): string {
494
- const envBlock = workerCmd
607
+ function unescapeXml(s: string): string {
608
+ // &amp; last, or an escaped `&amp;lt;` would come back as `<`.
609
+ return s.replace(/&lt;/g, "<").replace(/&gt;/g, ">").replace(/&amp;/g, "&");
610
+ }
611
+
612
+ /**
613
+ * Exported for the same reason `systemdUnitContents` is: it is the only way to
614
+ * assert the plist's shape from a Linux CI runner, and this is the half of the
615
+ * pair no Linux test run can otherwise reach.
616
+ */
617
+ export function launchdPlistContents(
618
+ binaryPath: string,
619
+ logDir: string,
620
+ workerCmd: string | null,
621
+ cliCmd: string | null = null,
622
+ ): string {
623
+ const env = serviceEnvironment(workerCmd, cliCmd);
624
+ const envBlock = env.length
495
625
  ? ` <key>EnvironmentVariables</key>
496
626
  <dict>
497
- <key>FAILPROOFAI_WORKER_CMD</key>
498
- <string>${escapeXml(workerCmd)}</string>
627
+ ${env.map(([key, value]) => ` <key>${key}</key>\n <string>${escapeXml(value)}</string>`).join("\n")}
499
628
  </dict>
500
629
  `
501
630
  : "";
@@ -538,10 +667,14 @@ export interface DaemonInstallResult {
538
667
  * machine that cannot elevate can be told exactly what to run rather than
539
668
  * just that something failed.
540
669
  */
541
- function daemonInstallCommands(binaryPath: string, workerCmd: string | null): string[] {
670
+ function daemonInstallCommands(
671
+ binaryPath: string,
672
+ workerCmd: string | null,
673
+ cliCmd: string | null,
674
+ ): string[] {
542
675
  if (process.platform === "linux") {
543
676
  return [
544
- `sudo tee ${systemdUnitPath()} <<'EOF'\n${systemdUnitContents(binaryPath, workerCmd)}EOF`,
677
+ `sudo tee ${systemdUnitPath()} <<'EOF'\n${systemdUnitContents(binaryPath, workerCmd, cliCmd)}EOF`,
545
678
  "sudo systemctl daemon-reload",
546
679
  `sudo systemctl enable --now ${systemdUnitName()}`,
547
680
  ];
@@ -577,6 +710,11 @@ export async function installDaemonService(): Promise<DaemonInstallResult> {
577
710
  // environment is what makes a *service-managed* daemon actually find its
578
711
  // worker regardless of what cwd the service manager starts it from.
579
712
  const workerCmd = resolveWorkerCommand();
713
+ // Same best-effort footing as the worker command, and null is survivable in
714
+ // the same way — the daemon just has no way to run an audit. Unlike the
715
+ // worker, there is no relative-path fallback to degrade to, which is exactly
716
+ // why it is resolved here where FAILPROOFAI_PACKAGE_ROOT is reliably set.
717
+ const cliCmd = resolveCliCommand();
580
718
 
581
719
  // The service is installed system-wide, which needs root. Check before
582
720
  // writing anything, so a machine that cannot elevate gets the exact
@@ -588,7 +726,7 @@ export async function installDaemonService(): Promise<DaemonInstallResult> {
588
726
  "root privileges are required to install the failproofaid system service, and sudo credentials were not available. " +
589
727
  "Re-run `failproofai config` and approve the sudo prompt — do NOT run the CLI itself under sudo, which would " +
590
728
  "configure root's account instead of yours. To install by hand: " +
591
- daemonInstallCommands(binaryPath, workerCmd).join(" && "),
729
+ daemonInstallCommands(binaryPath, workerCmd, cliCmd).join(" && "),
592
730
  };
593
731
  }
594
732
 
@@ -600,12 +738,12 @@ export async function installDaemonService(): Promise<DaemonInstallResult> {
600
738
 
601
739
  try {
602
740
  if (process.platform === "linux") {
603
- writePrivilegedFile(systemdUnitPath(), systemdUnitContents(binaryPath, workerCmd));
741
+ writePrivilegedFile(systemdUnitPath(), systemdUnitContents(binaryPath, workerCmd, cliCmd));
604
742
  runPrivileged("systemctl", ["daemon-reload"]);
605
743
  runPrivileged("systemctl", ["enable", "--now", systemdUnitName()]);
606
744
  } else {
607
745
  const plistPath = launchdPlistPath();
608
- const logDir = resolve(homedir(), ".failproofai", "logs");
746
+ const logDir = logsDir();
609
747
  mkdirSync(logDir, { recursive: true });
610
748
  // Unload any previously-loaded copy first — reloading with a changed
611
749
  // binary path (e.g. after an upgrade) is a no-op under plain `load`
@@ -615,7 +753,7 @@ export async function installDaemonService(): Promise<DaemonInstallResult> {
615
753
  } catch {
616
754
  // Wasn't loaded — fine, this is the common case on a fresh install.
617
755
  }
618
- writePrivilegedFile(plistPath, launchdPlistContents(binaryPath, logDir, workerCmd));
756
+ writePrivilegedFile(plistPath, launchdPlistContents(binaryPath, logDir, workerCmd, cliCmd));
619
757
  runPrivileged("launchctl", ["load", "-w", plistPath]);
620
758
  }
621
759
  } catch (err) {
@@ -636,9 +774,70 @@ export async function installDaemonService(): Promise<DaemonInstallResult> {
636
774
  hookLogWarn(`daemon service install failed: ${reason}`);
637
775
  return { installed: false, reason };
638
776
  }
777
+
778
+ // NOTE: this reports that the SERVICE is installed and running, which is all
779
+ // it can honestly claim. Whether the daemon can actually evaluate a hook is a
780
+ // separate question, answered by `probeDaemonEndToEnd()` — and the caller
781
+ // must ask it before setting `daemonConfigured`, because that flag is what
782
+ // makes an unanswering daemon deny every tool call. `configure-wizard.ts`
783
+ // does exactly that. Keeping the two apart matters: "the unit was written and
784
+ // systemd started it" is testable against a stub binary, and folding the
785
+ // probe in here would mean this function could never be tested without a real
786
+ // failproofaid.
639
787
  return { installed: true };
640
788
  }
641
789
 
790
+ /**
791
+ * How long the health probe waits for the daemon to answer a real hook.
792
+ *
793
+ * Far below the hook path's 30s: this runs from an interactive command, and
794
+ * the cold start it is most likely to be waiting on (`worker.rs` pre-warms the
795
+ * worker, ~700ms of Node startup) is comfortably inside it. A daemon that
796
+ * cannot answer a trivial event in this long is not one to hand a machine's
797
+ * fail-closed enforcement to.
798
+ */
799
+ const DAEMON_PROBE_TIMEOUT_MS = 5_000;
800
+
801
+ /**
802
+ * Ask the daemon to evaluate a real hook, end to end.
803
+ *
804
+ * The thing this catches that nothing else did: a unit that is *running* while
805
+ * the worker behind it cannot start. `ExecStart` bakes in `process.execPath`
806
+ * and an absolute `dist/worker.mjs`, so an `nvm uninstall 20` leaves a service
807
+ * systemd reports as perfectly active and a worker that dies on every spawn.
808
+ * Every existing check passes that machine — `waitForDaemonRunning()` asks the
809
+ * service manager, `Ping` is answered in `server.rs` without ever touching the
810
+ * worker, a null `resolveWorkerCommand()` is treated as best-effort, and
811
+ * `Worker::warm()` swallows its own failure. `daemonConfigured` then gets set,
812
+ * and because that flag makes every hook fail closed, the machine denies every
813
+ * tool call across all 12 CLIs — including `UserPromptSubmit`, so the user
814
+ * cannot even talk to their agent to ask why.
815
+ *
816
+ * `SessionStart` rather than a tool event: it traverses the identical path
817
+ * (socket → daemon → worker → `evaluateHookEvent`) but describes nothing a
818
+ * policy would deny and records no tool decision, so probing cannot itself
819
+ * change what the machine does.
820
+ */
821
+ export async function probeDaemonEndToEnd(): Promise<boolean> {
822
+ try {
823
+ const { attemptDaemonHook } = await import("./daemon-client");
824
+ const attempt = await attemptDaemonHook(
825
+ {
826
+ hookEvent: "SessionStart",
827
+ cli: "claude",
828
+ stdin: JSON.stringify({ hook_event_name: "SessionStart", source: "failproofai-health-probe" }),
829
+ },
830
+ { responseTimeoutMs: DAEMON_PROBE_TIMEOUT_MS },
831
+ );
832
+ // A protocol mismatch is a REACHABLE daemon of the wrong vintage. The hook
833
+ // path already falls back rather than denying for that case, so it is not
834
+ // the lockout this probe exists to find.
835
+ return attempt.ok || attempt.failure === "protocol-mismatch";
836
+ } catch {
837
+ return false;
838
+ }
839
+ }
840
+
642
841
  /**
643
842
  * Waits for the service to report running, then re-checks after a settle
644
843
  * window (see `SERVICE_SETTLE_MS`) so a daemon that dies at startup doesn't
@@ -655,6 +854,353 @@ async function waitForDaemonRunning(): Promise<boolean> {
655
854
  return daemonServiceStatus() === "running";
656
855
  }
657
856
 
857
+ // ── Upgrading a service definition that predates a variable ──────────────────
858
+ //
859
+ // A machine that installed the daemon before `FAILPROOFAI_CLI_CMD` existed
860
+ // keeps its old unit forever: `npm i -g failproofai@latest` replaces the CLI
861
+ // and never touches /etc/systemd/system, and the wizard's own "already
862
+ // installed and running — leaving it alone" branch skips it too. The daemon
863
+ // then has no way to spawn an audit while `config.toml` says the scheduled
864
+ // scan is on, and nothing anywhere reports a fault. This is the single most
865
+ // likely way the whole feature ends up dead on real machines, so it gets an
866
+ // explicit detect-and-rewrite rather than an assumption that reinstalls happen.
867
+
868
+ /** Reads the installed unit / plist, or null when no service is installed. */
869
+ function readInstalledServiceDefinition(): string | null {
870
+ if (!isDaemonSupportedPlatform()) return null;
871
+ const path = daemonServiceFilePath();
872
+ if (!path || !existsSync(path)) return null;
873
+ try {
874
+ return readFileSync(path, "utf8");
875
+ } catch {
876
+ // Unreadable is not "absent": claiming no service is installed would let a
877
+ // caller reinstall over something it cannot see. Treated as "nothing to
878
+ // upgrade" by the one caller below, which is the inert direction.
879
+ return null;
880
+ }
881
+ }
882
+
883
+ /**
884
+ * Reads one variable back out of an installed service definition.
885
+ *
886
+ * Used to carry values FORWARD across a rewrite. A rewrite resolves each
887
+ * command from this process, and a resolution that comes back null (no
888
+ * `FAILPROOFAI_PACKAGE_ROOT`, a `dist/` that moved) must never be allowed to
889
+ * silently DELETE a working line from a working unit — the rewrite exists to
890
+ * add a variable, not to drop one.
891
+ */
892
+ function installedEnvValue(definition: string, name: string): string | null {
893
+ if (process.platform === "linux") {
894
+ const m = new RegExp(`^Environment="${name}=(.*)"$`, "m").exec(definition);
895
+ return m ? m[1] : null;
896
+ }
897
+ const m = new RegExp(`<key>${name}</key>\\s*<string>([^<]*)</string>`).exec(definition);
898
+ return m ? unescapeXml(m[1]) : null;
899
+ }
900
+
901
+ /**
902
+ * The binary an installed definition currently starts.
903
+ *
904
+ * Preserved across a rewrite rather than re-resolved, because the two disagree
905
+ * in exactly the upgrade case this is for: `installedBinaryPath()` is
906
+ * version-stamped (`failproofaid-<version>`), so right after a CLI upgrade the
907
+ * binary for the NEW version is not on disk yet and resolution returns null,
908
+ * while the unit still points at the older binary that is there and running.
909
+ * Re-resolving would either refuse to rewrite or repoint a live service at a
910
+ * file that does not exist.
911
+ */
912
+ function installedExecStart(definition: string): string | null {
913
+ if (process.platform === "linux") {
914
+ const m = /^ExecStart=(.+)$/m.exec(definition);
915
+ return m ? m[1].trim() : null;
916
+ }
917
+ const m = /<key>ProgramArguments<\/key>\s*<array>\s*<string>([^<]*)<\/string>/.exec(definition);
918
+ return m ? unescapeXml(m[1]) : null;
919
+ }
920
+
921
+ /**
922
+ * True when a service IS installed but its definition predates
923
+ * `FAILPROOFAI_CLI_CMD`.
924
+ *
925
+ * Deliberately a content check against the definition on disk, not a revision
926
+ * number mirrored into `config.toml`. The mirror is the tempting shape — it is
927
+ * how `daemon.installed_version` is modelled — but that field is declared,
928
+ * read and cleared and has never once been WRITTEN (both `setDaemonConfigured`
929
+ * call sites pass no version), which is precisely how a mirror fails: it
930
+ * reports "current" for a unit nobody updated. A false negative here is the
931
+ * permanently-inert audit lane this function exists to catch, and the unit
932
+ * itself cannot lie about what it contains.
933
+ */
934
+ export function daemonServiceNeedsUpgrade(): boolean {
935
+ const definition = readInstalledServiceDefinition();
936
+ if (definition === null) return false;
937
+ return !definition.includes("FAILPROOFAI_CLI_CMD");
938
+ }
939
+
940
+ /**
941
+ * The definition an installed one should become: regenerated by the same
942
+ * renderers a fresh install uses, carrying forward everything this process
943
+ * cannot re-resolve. `null` when the input has no readable start command,
944
+ * which means it is not a definition failproofai wrote and must be left alone.
945
+ *
946
+ * Split out from `ensureDaemonServiceCurrent` and kept pure — no privilege, no
947
+ * service manager, no writes — because the rewrite itself can only be
948
+ * exercised end-to-end on a machine with root AND the matching service
949
+ * manager. That is nowhere on a Linux CI runner for the launchd half, and
950
+ * nowhere at all on a developer box without passwordless sudo, so the one
951
+ * transformation that must not be wrong would otherwise be the one thing no
952
+ * ordinary test run ever executes.
953
+ */
954
+ export function upgradedServiceDefinition(
955
+ definition: string,
956
+ cliCmd: string,
957
+ logDir: string,
958
+ ): string | null {
959
+ const binaryPath = installedExecStart(definition);
960
+ if (!binaryPath) return null;
961
+ const workerCmd = resolveWorkerCommand() ?? installedEnvValue(definition, "FAILPROOFAI_WORKER_CMD");
962
+ try {
963
+ return process.platform === "linux"
964
+ ? systemdUnitContents(binaryPath, workerCmd, cliCmd)
965
+ : launchdPlistContents(binaryPath, logDir, workerCmd, cliCmd);
966
+ } catch (err) {
967
+ // A value that cannot be safely interpolated is reported as "no usable
968
+ // definition" rather than thrown: this runs against a HEALTHY, RUNNING
969
+ // daemon and the caller's contract is an outcome, not an exception. The
970
+ // refusal happens before anything is written or stopped, so the machine is
971
+ // left exactly as it was.
972
+ hookLogWarn(
973
+ `service definition not rewritten: ${err instanceof Error ? err.message : String(err)}`,
974
+ );
975
+ return null;
976
+ }
977
+ }
978
+
979
+ export type DaemonUpgradeOutcome =
980
+ /** No service installed, or its definition already carries the variable. */
981
+ | "current"
982
+ /** The definition was rewritten and the service came back running. */
983
+ | "rewritten"
984
+ /** It needed a rewrite and did not get one. `reason` says why. */
985
+ | "failed";
986
+
987
+ export interface DaemonUpgradeResult {
988
+ outcome: DaemonUpgradeOutcome;
989
+ reason?: string;
990
+ /**
991
+ * Whether failproofaid is running when this returns — probed, never
992
+ * inferred from what the commands returned.
993
+ *
994
+ * Set on every outcome that touched the service, because on a
995
+ * `daemonConfigured` machine a stopped daemon is not a lost feature: every
996
+ * hook event fails closed, across all 12 CLIs, until somebody hand-edits
997
+ * `policies-config.json`. The caller needs to know that specifically, not
998
+ * just that "the refresh failed".
999
+ */
1000
+ daemonRunning?: boolean;
1001
+ }
1002
+
1003
+ /**
1004
+ * Restart the systemd unit, clearing any start-limit latch first.
1005
+ *
1006
+ * The unit ships `Restart=on-failure` with `RestartSec=2`, so a definition
1007
+ * systemd accepts but cannot run (a poisoned `User=`, a missing binary) does
1008
+ * not fail once — it cycles, and within `DefaultStartLimitIntervalSec` (10s) it
1009
+ * trips `DefaultStartLimitBurst` (5) and latches into "start request repeated
1010
+ * too quickly". On systemd 255 — what ubuntu-24.04 and GitHub's runners ship —
1011
+ * that latch is sticky at the unit level: a later `systemctl restart` is refused
1012
+ * even after the definition on disk has been REPLACED with a good one. A
1013
+ * rollback that restores a perfectly runnable unit then still cannot start it,
1014
+ * and the machine stays fail-closed against a daemon that never comes back.
1015
+ * `reset-failed` clears the failure counter so the restart is deterministic; on
1016
+ * a healthy unit it is a no-op. Best-effort — a `reset-failed` that errors (the
1017
+ * unit was never failed) must never abort the restart that is the real step.
1018
+ */
1019
+ function restartSystemdUnit(): void {
1020
+ try {
1021
+ runPrivileged("systemctl", ["reset-failed", systemdUnitName()]);
1022
+ } catch {
1023
+ // The restart below is the operative step; do not let a reset-failed
1024
+ // that errored on an already-clean unit swallow it.
1025
+ }
1026
+ runPrivileged("systemctl", ["restart", systemdUnitName()]);
1027
+ }
1028
+
1029
+ /**
1030
+ * Puts a previous service definition back and starts the service again,
1031
+ * answering whether failproofaid is running once it has.
1032
+ *
1033
+ * `ensureDaemonServiceCurrent` is the only caller, and it runs exclusively
1034
+ * against a machine whose daemon is UP — which is what makes a half-applied
1035
+ * rewrite so much worse than no rewrite. Stopping a healthy daemon and failing
1036
+ * to start it again trades a dead audit lane for a machine that denies every
1037
+ * tool call, so the refresh must be able to undo itself.
1038
+ *
1039
+ * Best-effort and never throws: it only ever runs where something has already
1040
+ * gone wrong. The return value is `waitForDaemonRunning()` rather than "the
1041
+ * commands succeeded" on purpose — the common rollback failure is a lost sudo
1042
+ * credential, which means nothing was written and the daemon was never down.
1043
+ */
1044
+ async function restoreServiceDefinition(previous: string): Promise<boolean> {
1045
+ try {
1046
+ if (process.platform === "linux") {
1047
+ writePrivilegedFile(systemdUnitPath(), previous);
1048
+ runPrivileged("systemctl", ["daemon-reload"]);
1049
+ // reset-failed first: the failed rewrite we are undoing has been cycling
1050
+ // under Restart=on-failure and may have tripped systemd's start-limit,
1051
+ // which on systemd 255 refuses to start even this restored, good unit.
1052
+ restartSystemdUnit();
1053
+ } else {
1054
+ const plistPath = launchdPlistPath();
1055
+ try {
1056
+ runPrivileged("launchctl", ["unload", plistPath]);
1057
+ } catch {
1058
+ // Already unloaded — getting the old definition loaded is the point.
1059
+ }
1060
+ writePrivilegedFile(plistPath, previous);
1061
+ runPrivileged("launchctl", ["load", "-w", plistPath]);
1062
+ }
1063
+ } catch (err) {
1064
+ hookLogWarn(
1065
+ `daemon service definition rollback failed: ${err instanceof Error ? err.message : String(err)}`,
1066
+ );
1067
+ // Not a verdict on the daemon — fall through to the probe below, which is.
1068
+ }
1069
+ return waitForDaemonRunning();
1070
+ }
1071
+
1072
+ /**
1073
+ * The one exit for every way a refresh can fail after it started touching the
1074
+ * service. Rolls back when there is something to roll back to, then reports
1075
+ * the daemon's actual state.
1076
+ */
1077
+ async function refreshFailed(message: string, previous: string | null): Promise<DaemonUpgradeResult> {
1078
+ hookLogWarn(`daemon service unit refresh failed: ${message}`);
1079
+ const running =
1080
+ previous === null ? daemonServiceStatus() === "running" : await restoreServiceDefinition(previous);
1081
+ return {
1082
+ outcome: "failed",
1083
+ daemonRunning: running,
1084
+ reason: running
1085
+ ? message
1086
+ : `${message}. The previous service definition could not be restored either, so failproofaid is NOT running`,
1087
+ };
1088
+ }
1089
+
1090
+ /**
1091
+ * Brings an already-installed service definition up to date, in place.
1092
+ *
1093
+ * Narrower than `installDaemonService` on purpose, and it is the narrowness
1094
+ * that makes it safe to run against a healthy machine: it never downloads, it
1095
+ * never touches a machine that has no service, and it keeps the ExecStart and
1096
+ * any environment value it cannot re-resolve. Everything past the first
1097
+ * privileged write is undone by `restoreServiceDefinition` if the daemon does
1098
+ * not come back, so the worst outcome really is the machine it started with —
1099
+ * and when even the rollback cannot get the daemon up, that fact rides out on
1100
+ * `daemonRunning` rather than being folded into a generic failure, because the
1101
+ * caller has to stop asserting `daemonConfigured` over a daemon that is gone.
1102
+ *
1103
+ * It DOES restart the service, which `installDaemonService` does not do on
1104
+ * Linux (`enable --now` is a no-op against an already-active unit, so a
1105
+ * rewritten unit's environment reaches the running process only at the next
1106
+ * boot). Without the restart this "fix" would leave the audit lane inert for
1107
+ * however long the machine stays up — which is the bug, not a smaller version
1108
+ * of it. The restart costs a sub-second window in which a hook can find no
1109
+ * daemon and fail closed, so it is confined to the wizard: `failproofai
1110
+ * config` is an explicit, attended reconfiguration, and the same window
1111
+ * already exists on macOS, where install unloads and reloads on every run.
1112
+ */
1113
+ export async function ensureDaemonServiceCurrent(): Promise<DaemonUpgradeResult> {
1114
+ const definition = readInstalledServiceDefinition();
1115
+ if (definition === null || definition.includes("FAILPROOFAI_CLI_CMD")) {
1116
+ return { outcome: "current" };
1117
+ }
1118
+
1119
+ const cliCmd = resolveCliCommand();
1120
+ if (!cliCmd) {
1121
+ return {
1122
+ outcome: "failed",
1123
+ reason:
1124
+ "the failproofai CLI entry point (dist/cli.mjs) could not be located, so there is nothing to " +
1125
+ "write into the unit. Re-run `failproofai config` from a complete install.",
1126
+ };
1127
+ }
1128
+
1129
+ const logDir = logsDir();
1130
+ const upgraded = upgradedServiceDefinition(definition, cliCmd, logDir);
1131
+ if (!upgraded) {
1132
+ return {
1133
+ outcome: "failed",
1134
+ reason:
1135
+ `no safe replacement could be composed for the service definition at ${daemonServiceFilePath()} — ` +
1136
+ `either it has no readable start command (it looks hand-edited) or a value that would go into it ` +
1137
+ `contains a quote, backslash or newline. It was left alone.`,
1138
+ // Reported because nothing was touched: the refusal happens before any
1139
+ // write and before any stop, so whatever was running still is. The
1140
+ // wizard branches on this to decide whether to clear `daemonConfigured`,
1141
+ // and omitting it here would have read as "the daemon may be down" on a
1142
+ // machine whose daemon is perfectly healthy — which on a
1143
+ // `daemonConfigured` box is the difference between a lost audit lane and
1144
+ // every tool call denied.
1145
+ daemonRunning: daemonServiceStatus() === "running",
1146
+ };
1147
+ }
1148
+
1149
+ if (!canElevate()) {
1150
+ return {
1151
+ outcome: "failed",
1152
+ reason: `root privileges are required to rewrite ${daemonServiceFilePath()}, and sudo credentials were not available`,
1153
+ };
1154
+ }
1155
+
1156
+ // What to put back if this goes wrong, and from which point. Set BEFORE the
1157
+ // step that can leave the machine changed, not after it succeeds: on Linux
1158
+ // `writePrivilegedFile` is an `install` copy straight onto the unit path, so
1159
+ // a failure part-way through leaves a truncated unit behind, and on macOS
1160
+ // the unload has already stopped the daemon before anything is written.
1161
+ let previous: string | null = null;
1162
+
1163
+ try {
1164
+ if (process.platform === "linux") {
1165
+ previous = definition;
1166
+ writePrivilegedFile(systemdUnitPath(), upgraded);
1167
+ runPrivileged("systemctl", ["daemon-reload"]);
1168
+ // `restart`, not `enable --now`: the unit is already enabled and active,
1169
+ // so `--now` would return success having changed nothing and the daemon
1170
+ // would keep the environment it was started with. reset-failed first so a
1171
+ // machine that arrived here with an already-latched start-limit (a prior
1172
+ // failed refresh) can still be upgraded.
1173
+ restartSystemdUnit();
1174
+ } else {
1175
+ const plistPath = launchdPlistPath();
1176
+ mkdirSync(logDir, { recursive: true });
1177
+ previous = definition;
1178
+ try {
1179
+ runPrivileged("launchctl", ["unload", plistPath]);
1180
+ } catch {
1181
+ // Not loaded — the rewrite is still the point.
1182
+ }
1183
+ writePrivilegedFile(plistPath, upgraded);
1184
+ runPrivileged("launchctl", ["load", "-w", plistPath]);
1185
+ }
1186
+ } catch (err) {
1187
+ return refreshFailed(err instanceof Error ? err.message : String(err), previous);
1188
+ }
1189
+
1190
+ // The service was running when this started, so anything short of running
1191
+ // now is a regression this call caused — and on a daemon-configured machine
1192
+ // that is every tool call failing closed. A unit that parses but will not
1193
+ // start is exactly the case `systemctl restart`'s exit code does not catch,
1194
+ // so this is the check that hands it to the rollback.
1195
+ if (!(await waitForDaemonRunning())) {
1196
+ return refreshFailed(
1197
+ `the service definition was refreshed but failproofaid did not come back within ${SERVICE_START_TIMEOUT_MS}ms (status: ${daemonServiceStatus()})`,
1198
+ previous,
1199
+ );
1200
+ }
1201
+ return { outcome: "rewritten", daemonRunning: true };
1202
+ }
1203
+
658
1204
  /**
659
1205
  * Stops and removes the service, and clears the `daemonConfigured` marker
660
1206
  * so this machine goes back to in-process evaluation. Best-effort: never
@@ -715,6 +1261,36 @@ export async function uninstallDaemonService(): Promise<void> {
715
1261
  * file exists — a unit can be installed but crash-looped into a stopped
716
1262
  * state.
717
1263
  */
1264
+ /**
1265
+ * Whether the installed daemon is a different version than this CLI expects.
1266
+ *
1267
+ * The expected version is this CLI's OWN — no lookup, no registry call, no
1268
+ * network. `package.json` and `Cargo.toml` are held identical by CI, and every
1269
+ * downstream path (the download URL, the binary filename, the npm pin) is
1270
+ * derived from that one number, so a CLI can never want a daemon version that
1271
+ * was not published alongside it.
1272
+ *
1273
+ * Returns null when the question does not apply, and the exclusions are the
1274
+ * point:
1275
+ *
1276
+ * • FAILPROOFAI_DAEMON_BINARY — someone named a binary explicitly; its
1277
+ * version is their business, not ours to second-guess.
1278
+ * • a locally-built target/{release,debug} binary — the developer's own
1279
+ * build. Reporting it "stale" on every command would be noise about the
1280
+ * exact setup this repo tells contributors to use.
1281
+ *
1282
+ * Only the managed `~/.failproofai/bin/failproofaid-<version>` path is
1283
+ * checked, because it is the only one whose version we actually own.
1284
+ */
1285
+ export function daemonVersionSkew(): { installed: string; expected: string } | null {
1286
+ if (process.env.FAILPROOFAI_DAEMON_BINARY) return null;
1287
+ const recorded = readVersionFile()?.daemon;
1288
+ if (!recorded) return null;
1289
+ // Managed installs only — see the exclusions above.
1290
+ if (!existsSync(installedBinaryPath(recorded))) return null;
1291
+ return recorded === version ? null : { installed: recorded, expected: version };
1292
+ }
1293
+
718
1294
  export function daemonServiceStatus(): DaemonServiceStatus {
719
1295
  if (!isDaemonSupportedPlatform()) return "unsupported-platform";
720
1296