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
@@ -41,6 +41,9 @@ import { resolveTranscriptPath } from "./resolve-transcript-path";
41
41
  import { getInstanceId } from "../../lib/telemetry-id";
42
42
  import { hookLogInfo, hookLogWarn } from "./hook-logger";
43
43
  import { readStdinPayload } from "./read-stdin";
44
+ import { readActiveCloudManagedPolicies, type CloudManagedPolicyArtifact } from "./cloud-managed-policies";
45
+ import { readActivePause, type ActivePause } from "./session-pause";
46
+ import { layoutWarningForHook } from "./fp-reset";
44
47
 
45
48
  /**
46
49
  * Canonicalize an event name to PascalCase. Codex sends snake_case event names
@@ -134,6 +137,37 @@ export interface EvaluateHookEventOptions {
134
137
  fallbackCwd?: string;
135
138
  }
136
139
 
140
+ /**
141
+ * Runs an observe-mode policy under the same timeout and error handling as an
142
+ * enforcing one, so what gets measured is what would actually have happened —
143
+ * including a policy that times out, which in enforce mode is an allow and must
144
+ * be recorded as one rather than as a would-deny.
145
+ */
146
+ async function runObserved(
147
+ hook: CustomHook,
148
+ ctx: Parameters<CustomHook["fn"]>[0],
149
+ hookName: string,
150
+ eventType: string,
151
+ cli: IntegrationType,
152
+ ): Promise<PolicyResult> {
153
+ try {
154
+ return await Promise.race([
155
+ hook.fn(ctx),
156
+ new Promise<PolicyResult>((_, reject) => setTimeout(() => reject(new Error("timeout")), 10_000)),
157
+ ]);
158
+ } catch (err) {
159
+ const msg = err instanceof Error ? err.message : String(err);
160
+ void trackHookEvent(getInstanceId(), "custom_hook_error", {
161
+ hook_name: hookName,
162
+ error_type: msg === "timeout" ? "timeout" : "exception",
163
+ event_type: eventType,
164
+ cli,
165
+ is_observe_mode: true,
166
+ });
167
+ return { decision: "allow" };
168
+ }
169
+ }
170
+
137
171
  /**
138
172
  * The core hook-evaluation logic, decoupled from process stdin/stdout so it
139
173
  * can be called repeatedly inside a long-lived process (the daemon's warm
@@ -149,6 +183,13 @@ export async function evaluateHookEvent(
149
183
  opts?: EvaluateHookEventOptions,
150
184
  ): Promise<HookEventOutcome> {
151
185
  const startTime = performance.now();
186
+ // A home from another layout resolves to no global config, so every builtin
187
+ // silently stops firing — the machine looks protected and is not. Say so on
188
+ // every call rather than deleting anything (a hook is unattended) or denying
189
+ // (a blanket deny takes UserPromptSubmit with it and locks the user out of
190
+ // their agent entirely — demonstrated on a real machine during this work).
191
+ const layoutWarning = layoutWarningForHook();
192
+ if (layoutWarning) hookLogWarn(layoutWarning);
152
193
  try {
153
194
  let parsed: Record<string, unknown> = {};
154
195
  if (stdinPayload) {
@@ -209,6 +250,20 @@ export async function evaluateHookEvent(
209
250
  let config: HooksConfig;
210
251
  let customHooksList: CustomHook[] = [];
211
252
  let conventionHookNames = new Set<string>();
253
+ let activePause: ActivePause | null = null;
254
+ /** Registered policy name → where it came from. See the set() below. */
255
+ const policyAttribution = new Map<
256
+ string,
257
+ { source: "custom" | "convention" | "cloud"; cloudPolicyId?: string; cloudVersion?: number }
258
+ >();
259
+ let cloudDeployment: number | undefined;
260
+ /** What observe-mode policies WOULD have done, had they been enforcing. */
261
+ const observedResults: Array<{
262
+ policyId: string;
263
+ version: number;
264
+ decision: "deny" | "instruct";
265
+ reason: string | null;
266
+ }> = [];
212
267
 
213
268
  if (opts?.forceDecision) {
214
269
  // Fail-closed: no config/custom-hook loading at all — a daemon that
@@ -228,25 +283,113 @@ export async function evaluateHookEvent(
228
283
  // Load enabled policies (merge across project/local/global scopes)
229
284
  config = readMergedHooksConfig(session.cwd);
230
285
  clearPolicies();
231
- registerBuiltinPolicies(config.enabledPolicies);
286
+
287
+ // A session pause suspends LOCAL policy only, for a bounded time. Cloud
288
+ // assignments are exempt below for the same reason `disabledCustomPolicies`
289
+ // already exempts them: a locally-issued command that could switch off a
290
+ // centrally assigned policy would make cloud enforcement decorative.
291
+ activePause = readActivePause(session.sessionId);
292
+ registerBuiltinPolicies(activePause ? [] : config.enabledPolicies);
293
+
294
+ // Cloud-managed policies are daemon-reconciled artifacts, but they use
295
+ // the same public JS policy API as local custom policies. Verify and add
296
+ // only the paths referenced by the atomically active deployment.
297
+ //
298
+ // Wrapped, like `readConfigAt` and `readActivePause` above it. This call
299
+ // has fourteen throw sites — a malformed manifest, an unsafe id, an
300
+ // integrity mismatch, an unknown effect — and it sat bare inside a `try`
301
+ // whose only handler is a `finally`, so any of them aborted the entire
302
+ // evaluation. What that cost depended on where the hook ran, and none of
303
+ // the outcomes were the intended one: on a daemon machine the client
304
+ // fail-closed denies every tool call, and off the daemon path the throw
305
+ // reaches `bin/failproofai.mjs`'s outer catch, which exits 2 with nothing
306
+ // on stdout — a deny on Claude and Factory, but a logged warning followed
307
+ // by an ALLOW on Copilot, Cursor, Goose, Pi and Hermes, which read a
308
+ // decision off stdout and ignore the exit code. One corrupt byte was
309
+ // either a permanent machine-wide lockout or silent non-enforcement,
310
+ // depending on the CLI.
311
+ //
312
+ // Failing open here degrades ONE layer: builtins and local custom
313
+ // policies were already registered above and keep enforcing. That is the
314
+ // same trade `2ad735e5` made on the Rust side, and it is strictly better
315
+ // than the status quo, which already failed open on five of the twelve —
316
+ // just accidentally, and without saying so.
317
+ let cloudManagedPolicies: CloudManagedPolicyArtifact[] = [];
318
+ try {
319
+ cloudManagedPolicies = readActiveCloudManagedPolicies();
320
+ } catch (err) {
321
+ const msg = err instanceof Error ? err.message : String(err);
322
+ hookLogWarn(
323
+ `[failproofai] cloud-managed policies could NOT be loaded and are not being enforced: ${msg}. ` +
324
+ `Local policies are unaffected. Run \`failproofai config --status\` to check this machine's enrolment.`,
325
+ );
326
+ void trackHookEvent(getInstanceId(), "cloud_managed_load_error", {
327
+ event_type: eventType,
328
+ cli,
329
+ });
330
+ }
331
+ // Recorded on every row once a machine is managed, whether or not a cloud
332
+ // policy decided this event: "what was deployed here at the time" is a
333
+ // separate question from "what decided", and only the former can tell a
334
+ // rollout that changed nothing from one that never reached the machine.
335
+ cloudDeployment = cloudManagedPolicies[0]?.deployment;
336
+ const configuredCustomPaths = config.customPoliciesPaths ?? config.customPoliciesPath;
337
+ const allExplicitPaths =
338
+ cloudManagedPolicies.length === 0
339
+ ? configuredCustomPaths
340
+ : [
341
+ ...(typeof configuredCustomPaths === "string" ? [configuredCustomPaths] : configuredCustomPaths ?? []),
342
+ ...cloudManagedPolicies.map((policy) => policy.path),
343
+ ];
232
344
 
233
345
  // Load and register custom hooks (layer 2, after builtins)
234
- const loadResult = await loadAllCustomHooks(config.customPoliciesPaths ?? config.customPoliciesPath, {
346
+ const loadResult = await loadAllCustomHooks(allExplicitPaths, {
235
347
  sessionCwd: session.cwd,
236
348
  customPoliciesEnabled: config.customPoliciesEnabled,
349
+ ...(cloudManagedPolicies.length > 0 ? { cloudManagedPolicies } : {}),
237
350
  });
238
351
  customHooksList = loadResult.hooks;
239
352
  const disabledCustomPolicies = new Set(config.disabledCustomPolicies ?? []);
240
353
  conventionHookNames = new Set(loadResult.conventionSources.flatMap((s) => s.hookNames));
241
354
 
242
355
  for (const hook of customHooksList) {
243
- const policyId = (hook as CustomHook & { __policyId?: string }).__policyId;
244
- if (policyId && disabledCustomPolicies.has(policyId)) continue;
356
+ const taggedHook = hook as CustomHook & {
357
+ __policyId?: string;
358
+ __cloudManaged?: CloudManagedPolicyArtifact;
359
+ };
360
+ const policyId = taggedHook.__policyId;
361
+ const cloudManaged = taggedHook.__cloudManaged;
362
+ // Local config cannot disable a centrally assigned policy merely by
363
+ // copying its generated ID into disabledCustomPolicies.
364
+ if (!cloudManaged && policyId && disabledCustomPolicies.has(policyId)) continue;
365
+ // Same rule for a session pause — it suspends local policy, never cloud.
366
+ if (!cloudManaged && activePause) continue;
245
367
  const hookName = hook.name;
246
368
  const conventionScope = (hook as CustomHook & { __conventionScope?: string }).__conventionScope;
247
369
  const isConvention = !!conventionScope;
248
- const prefix = isConvention ? `.failproofai-${conventionScope}` : "custom";
370
+ const prefix = cloudManaged
371
+ ? `cloud/${cloudManaged.id}@${cloudManaged.version}`
372
+ : isConvention
373
+ ? `.failproofai-${conventionScope}`
374
+ : "custom";
375
+ // Observe mode: run it for real, record what it decided, then hand back
376
+ // an allow. Evaluating and discarding is the whole point — a rollout is
377
+ // measured against real traffic before it can break anyone's work, and
378
+ // a policy that did not actually run would measure nothing.
379
+ const observeOnly = cloudManaged?.effect === "observe";
249
380
  const fn: PolicyFunction = async (ctx): Promise<PolicyResult> => {
381
+ if (observeOnly) {
382
+ const shadow = await runObserved(hook, ctx, hookName, eventType, cli);
383
+ if (shadow.decision !== "allow") {
384
+ observedResults.push({
385
+ policyId: cloudManaged!.id,
386
+ version: cloudManaged!.version,
387
+ decision: shadow.decision,
388
+ reason: shadow.reason ?? null,
389
+ });
390
+ }
391
+ return { decision: "allow" };
392
+ }
250
393
  try {
251
394
  const result = await Promise.race([
252
395
  hook.fn(ctx),
@@ -268,8 +411,18 @@ export async function evaluateHookEvent(
268
411
  return { decision: "allow" };
269
412
  }
270
413
  };
414
+ const registeredName = `${prefix}/${hookName}`;
415
+ // Record where this policy came from as structured data, keyed by the
416
+ // exact name the evaluator will report back. The display name already
417
+ // encodes it ("cloud/org-guard@7/…"), but only as a string — so the one
418
+ // question cloud attribution has to answer, "which rollout produced
419
+ // this decision", could only be answered by re-parsing our own label.
420
+ policyAttribution.set(registeredName, {
421
+ source: cloudManaged ? "cloud" : isConvention ? "convention" : "custom",
422
+ ...(cloudManaged ? { cloudPolicyId: cloudManaged.id, cloudVersion: cloudManaged.version } : {}),
423
+ });
271
424
  registerPolicy(
272
- `${prefix}/${hookName}`,
425
+ registeredName,
273
426
  hook.description ?? "",
274
427
  fn,
275
428
  hook.match ?? {},
@@ -335,6 +488,32 @@ export async function evaluateHookEvent(
335
488
  cwd: session.cwd,
336
489
  permissionMode: session.permissionMode,
337
490
  hookEventName: session.hookEventName,
491
+ // Attribution. A builtin is anything registered that is not in the map,
492
+ // so its absence is meaningful rather than missing — but only when a
493
+ // policy actually decided; a plain allow names nobody.
494
+ ...(result.policyName
495
+ ? (() => {
496
+ const attribution = policyAttribution.get(result.policyName);
497
+ return {
498
+ policySource: attribution?.source ?? ("builtin" as const),
499
+ ...(attribution?.cloudPolicyId ? { cloudPolicyId: attribution.cloudPolicyId } : {}),
500
+ ...(attribution?.cloudVersion !== undefined
501
+ ? { cloudVersion: attribution.cloudVersion }
502
+ : {}),
503
+ };
504
+ })()
505
+ : {}),
506
+ ...(cloudDeployment !== undefined ? { cloudDeployment } : {}),
507
+ // The point of observe mode is this record. Without it the rollout is
508
+ // unmeasurable and the row is indistinguishable from one where the policy
509
+ // never matched at all.
510
+ ...(observedResults.length > 0 ? { observed: observedResults } : {}),
511
+ // Without these, a row logged during a pause is indistinguishable from
512
+ // one where every policy ran and allowed — the log would assert a clean
513
+ // window over exactly the window that was not enforced.
514
+ ...(activePause
515
+ ? { pausedBy: activePause.setBy, pauseExpiresAt: activePause.expiresAt }
516
+ : {}),
338
517
  };
339
518
  try {
340
519
  persistHookActivity(activityEntry);
@@ -0,0 +1,386 @@
1
+ /**
2
+ * `failproofai harness` — manage extra capture paths per harness.
3
+ *
4
+ * Each supported agent CLI has one location its own installer put it in, and
5
+ * the collector watches that. This command adds others: a second profile, a
6
+ * bind-mounted team share, a container's home mapped in beside the host's, an
7
+ * agent an operator relocated. Those hold real sessions and nothing collected
8
+ * them.
9
+ *
10
+ * # This file edits config; the daemon decides what the config means
11
+ *
12
+ * Entries are stored as written. The grammar (`label=path`, folder-name
13
+ * fallback), `~` expansion, overlap rejection and the `<label>-<agentId>`
14
+ * namespacing all live in `crates/fpai-collect/src/extra_paths.rs`, and the
15
+ * daemon reports what it rejected at startup. Re-implementing any of that here
16
+ * would give one product two parsers, and the way that fails is the CLI
17
+ * accepting a path the daemon then silently drops — which is indistinguishable
18
+ * from the feature working.
19
+ *
20
+ * The one thing validated here is the HARNESS NAME, because that failure has no
21
+ * other detector: `collector.sources.claud` is valid JSON, parses cleanly,
22
+ * captures nothing, and is mentioned nowhere else a user would look.
23
+ *
24
+ * # No restart, and no sudo
25
+ *
26
+ * This writes one file in the user's own home and nothing else. The daemon's
27
+ * collector manager re-reads `config.json` on an interval and cycles the
28
+ * collector whenever the resolved `CollectorConfig` changes — the same path
29
+ * that already picks up `--connect`, a stream being switched off, and a
30
+ * verbosity change. `extra_paths` rides it for free by living inside
31
+ * `Settings`, which is part of the value being compared. That is load-bearing
32
+ * rather than incidental, and `collector_config_change_cycles_the_collector`
33
+ * in `crates/fpai-collect/src/config.rs` pins it: move `sources` out of
34
+ * `CollectorConfig` and the CLI would keep reporting success for paths the
35
+ * running daemon never picks up.
36
+ */
37
+
38
+ import { homedir } from "node:os";
39
+ import { resolve } from "node:path";
40
+ import { readConfig, updateConfig } from "./fp-config";
41
+ import { configFile } from "./fp-home";
42
+
43
+ /**
44
+ * Harness keys `collector.sources.<key>` accepts.
45
+ *
46
+ * MUST stay in step with `HARNESS_KEYS` in `crates/failproofaid/src/main.rs`,
47
+ * which is the side that actually registers the tasks. A name here that the
48
+ * daemon does not know configures a table nothing reads; a name the daemon
49
+ * knows and this list omits is a path the user cannot add. `__tests__` asserts
50
+ * the two lists are identical by reading the Rust source, because nothing else
51
+ * connects them.
52
+ *
53
+ * Twelve keys, thirteen sources: `claude` covers both the main and the subagent
54
+ * transcript formats, which share a root — an extra path holding Claude
55
+ * transcripts holds their subagents too.
56
+ */
57
+ export const HARNESS_KEYS = [
58
+ "claude",
59
+ "codex",
60
+ "copilot",
61
+ "openclaw",
62
+ "pi",
63
+ "factory",
64
+ "antigravity",
65
+ "cursor",
66
+ "goose",
67
+ "opencode",
68
+ "devin",
69
+ "hermes",
70
+ ] as const;
71
+
72
+ export type HarnessKey = (typeof HARNESS_KEYS)[number];
73
+
74
+ export interface HarnessResult {
75
+ lines: string[];
76
+ exitCode: number;
77
+ }
78
+
79
+ const ok = (lines: string[]): HarnessResult => ({ lines, exitCode: 0 });
80
+ const fail = (lines: string[]): HarnessResult => ({ lines, exitCode: 1 });
81
+
82
+ function unknownHarness(name: string): HarnessResult {
83
+ return fail([
84
+ `Unknown harness: ${name}`,
85
+ "",
86
+ `Known harnesses: ${HARNESS_KEYS.join(", ")}`,
87
+ ]);
88
+ }
89
+
90
+ /** The path half of an entry, for comparing against what a user typed. */
91
+ function pathOf(entry: string): string {
92
+ const eq = entry.indexOf("=");
93
+ if (eq <= 0) return entry.trim();
94
+ const label = entry.slice(0, eq).trim();
95
+ return label === "" ? entry.trim() : entry.slice(eq + 1).trim();
96
+ }
97
+
98
+ /** The label half, or `null` when the entry did not name one. */
99
+ function labelOf(entry: string): string | null {
100
+ const eq = entry.indexOf("=");
101
+ if (eq <= 0) return null;
102
+ const label = entry.slice(0, eq).trim();
103
+ return label === "" ? null : label;
104
+ }
105
+
106
+ function currentPaths(harness: string): string[] {
107
+ return readConfig().collector.sources?.[harness]?.extraPaths ?? [];
108
+ }
109
+
110
+ function writePaths(harness: string, paths: string[]): void {
111
+ const cfg = readConfig();
112
+ const sources = { ...(cfg.collector.sources ?? {}) };
113
+ if (paths.length > 0) sources[harness] = { extraPaths: paths };
114
+ else delete sources[harness];
115
+ updateConfig({
116
+ collector: {
117
+ ...cfg.collector,
118
+ // `undefined` rather than `{}` when the last entry goes, so removing what
119
+ // you added leaves the file as it was rather than carrying an empty table
120
+ // forever.
121
+ sources: Object.keys(sources).length > 0 ? sources : undefined,
122
+ },
123
+ });
124
+ }
125
+
126
+ /**
127
+ * No restart, and deliberately no instruction to perform one.
128
+ *
129
+ * The daemon's collector manager re-reads `config.json` on an interval and
130
+ * cycles the collector whenever the resolved `CollectorConfig` changes — the
131
+ * same path that already picks up `--connect`, a stream being switched off and
132
+ * a verbosity change. `extra_paths` rides that for free because it lives inside
133
+ * `Settings`, which lives inside the value being compared.
134
+ *
135
+ * Verified live: adding a path to a running daemon logged "collector
136
+ * configuration changed; cycling the collector", took the task count from 21 to
137
+ * 23, and the first transcript written under the new path reached the server.
138
+ *
139
+ * This said "run `sudo systemctl restart failproofaid@$USER`" first. That was
140
+ * wrong twice over: the restart is unnecessary, and it asks for root from a
141
+ * command that writes one file in the user's own home and needs none.
142
+ */
143
+ const TAKES_EFFECT_HINT = [
144
+ "",
145
+ "The daemon picks this up on its own within a few seconds — no restart, no sudo.",
146
+ ];
147
+
148
+ /**
149
+ * The daemon's label rule, FOR COMPARISON ONLY.
150
+ *
151
+ * Mirrors `sanitize_label()` in `crates/fpai-collect/src/extra_paths.rs`:
152
+ * lowercase, every non-alphanumeric run collapsed to one `-`, leading and
153
+ * trailing dashes trimmed.
154
+ *
155
+ * Deliberately NOT used to rewrite what is stored. The daemon is the authority on
156
+ * the grammar, and this file's header is explicit that it must not re-implement
157
+ * the parser — "two parsers is how the CLI comes to accept a path the daemon then
158
+ * silently drops". Normalising for a duplicate CHECK is the narrow opposite of
159
+ * that: it makes the CLI's own pre-flight agree with the rule it exists to
160
+ * pre-empt, while the stored string stays exactly what the user typed and the
161
+ * daemon still derives the real label.
162
+ */
163
+ function comparableLabel(raw: string): string {
164
+ return raw
165
+ .toLowerCase()
166
+ .replace(/[^a-z0-9]+/g, "-")
167
+ .replace(/^-+|-+$/g, "");
168
+ }
169
+
170
+ /**
171
+ * The daemon's path rule, for comparison only. Mirrors `clean()`: `~/` expanded,
172
+ * trailing slashes trimmed.
173
+ */
174
+ function comparablePath(raw: string): string {
175
+ const home = homedir();
176
+ const expanded = raw === "~" ? home : raw.startsWith("~/") ? resolve(home, raw.slice(2)) : raw;
177
+ return expanded.replace(/\/+$/, "");
178
+ }
179
+
180
+ /**
181
+ * What the daemon will call this entry, label given or derived.
182
+ *
183
+ * Mirrors `derive_label()` for the unlabelled case: the folder name with a leading
184
+ * dot stripped, then sanitised. Without this an unlabelled
185
+ * `/mnt/team-a/.claude/projects` and `/mnt/team-b/.claude/projects` both derive
186
+ * "projects" — the CLI's label check was skipped entirely for unlabelled entries,
187
+ * so both were written and the daemon silently dropped the second.
188
+ */
189
+ function effectiveLabel(entry: string): string {
190
+ const given = labelOf(entry);
191
+ if (given) return comparableLabel(given);
192
+ const path = comparablePath(pathOf(entry));
193
+ const base = path.split("/").filter(Boolean).pop() ?? "";
194
+ return comparableLabel(base.replace(/^\.+/, ""));
195
+ }
196
+
197
+ export function addPath(harness: string, entry: string): HarnessResult {
198
+ if (!(HARNESS_KEYS as readonly string[]).includes(harness)) return unknownHarness(harness);
199
+ const trimmed = entry.trim();
200
+ if (trimmed === "") return fail(["A path is required."]);
201
+ if (pathOf(trimmed) === "") {
202
+ return fail([`Could not read a path out of ${JSON.stringify(entry)}.`, "", "Use `<path>` or `<label>=<path>`."]);
203
+ }
204
+
205
+ const existing = currentPaths(harness);
206
+
207
+ // Exact duplicate: succeed rather than error. `add-path` run twice from a
208
+ // provisioning script must not fail the second time.
209
+ if (existing.includes(trimmed)) {
210
+ return ok([`${harness}: ${trimmed} is already configured.`]);
211
+ }
212
+
213
+ // Same path under a different label, or the same label on a different path.
214
+ // Both are rejected HERE rather than left to the daemon, because the daemon
215
+ // resolves them at startup and drops one — so the CLI would report success
216
+ // for a path that is never captured.
217
+ // Compared NORMALISED, not raw. `add-path claude a=/srv/x/` then
218
+ // `add-path claude b=/srv/x` differ as strings and are the same path to the
219
+ // daemon, which resolves them at startup and drops one — so the CLI reported
220
+ // success for a path that is never captured.
221
+ const samePath = existing.find((e) => comparablePath(pathOf(e)) === comparablePath(pathOf(trimmed)));
222
+ if (samePath) {
223
+ return fail([
224
+ `${harness} already captures ${pathOf(trimmed)} as ${JSON.stringify(samePath)}.`,
225
+ "",
226
+ "Remove it first if you meant to relabel it:",
227
+ ` failproofai harness remove-path ${harness} ${pathOf(trimmed)}`,
228
+ ]);
229
+ }
230
+ const wanted = labelOf(trimmed);
231
+ // The EFFECTIVE label, normalised, and computed for unlabelled entries too.
232
+ // Three inputs slipped past the old exact-string check on the given label:
233
+ // `"Team Share"` vs `team-share` (the daemon lowercases and substitutes), and two
234
+ // unlabelled paths whose folder name derives the same label — for which the check
235
+ // did not run at all. In every case both entries were written, `harness list`
236
+ // showed both, and only one was captured.
237
+ const effective = effectiveLabel(trimmed);
238
+ const sameLabel = effective
239
+ ? existing.find((e) => effectiveLabel(e) === effective)
240
+ : undefined;
241
+ if (sameLabel) {
242
+ return fail([
243
+ `${harness} already uses the label ${JSON.stringify(effective)} for ${pathOf(sameLabel)}.`,
244
+ "",
245
+ "Labels namespace agent ids, so two paths cannot share one — and the daemon",
246
+ "lowercases them and collapses punctuation to `-`, so labels that differ only",
247
+ "in case or spacing collide. An unlabelled path takes its folder name.",
248
+ "",
249
+ "Give this one an explicit label:",
250
+ ` failproofai harness add-path ${harness} <label>=${pathOf(trimmed)}`,
251
+ ]);
252
+ }
253
+
254
+ writePaths(harness, [...existing, trimmed]);
255
+ return ok([
256
+ // "configured", NOT "now capturing". The checks above cover what this side can
257
+ // know — the entries already in the file — and there is one rejection it
258
+ // cannot: a path overlapping the harness's own DEFAULT capture root, which the
259
+ // daemon owns and refuses. Claiming capture there was a promise this command
260
+ // is not in a position to make, so it says what it did and where the answer
261
+ // is. Teaching the CLI all thirteen sources' default roots would be the second
262
+ // parser this file exists to avoid.
263
+ `${harness}: configured to also capture ${pathOf(trimmed)}`,
264
+ wanted
265
+ ? ` agent ids will be namespaced ${wanted}-*`
266
+ : " a label will be derived from the folder name; `failproofai harness list` shows it",
267
+ ` written to ${configFile()}`,
268
+ "",
269
+ " The daemon validates it on the next read and reports what it rejected —",
270
+ " `failproofai harness list` shows what is actually being captured.",
271
+ ...TAKES_EFFECT_HINT,
272
+ ]);
273
+ }
274
+
275
+ export function removePath(harness: string, target: string): HarnessResult {
276
+ if (!(HARNESS_KEYS as readonly string[]).includes(harness)) return unknownHarness(harness);
277
+ const existing = currentPaths(harness);
278
+ const t = target.trim();
279
+ // Match on the whole entry, on its path, or on its label — a user removing
280
+ // something is working from what `list` printed, which shows all three.
281
+ const kept = existing.filter((e) => e !== t && pathOf(e) !== t && labelOf(e) !== t);
282
+ if (kept.length === existing.length) {
283
+ return fail([
284
+ `${harness} has no extra path matching ${JSON.stringify(t)}.`,
285
+ "",
286
+ existing.length > 0
287
+ ? `Configured: ${existing.join(", ")}`
288
+ : `${harness} has no extra paths configured.`,
289
+ ]);
290
+ }
291
+ writePaths(harness, kept);
292
+ return ok([
293
+ `${harness}: no longer capturing ${t}`,
294
+ "",
295
+ "Already-collected sessions from that path are NOT removed — they are on the",
296
+ "server. This only stops new ones.",
297
+ ...TAKES_EFFECT_HINT,
298
+ ]);
299
+ }
300
+
301
+ export function listPaths(harness?: string): HarnessResult {
302
+ if (harness && !(HARNESS_KEYS as readonly string[]).includes(harness)) return unknownHarness(harness);
303
+ const sources = readConfig().collector.sources ?? {};
304
+ const names: string[] = harness
305
+ ? [harness]
306
+ : HARNESS_KEYS.filter((k) => sources[k]?.extraPaths?.length);
307
+
308
+ // Computed BEFORE the empty-state return, not after. A config whose only
309
+ // tables are typos has no known harness configured, so the early return fired
310
+ // and the warning below was unreachable in precisely the case it exists for —
311
+ // the user sees "No extra capture paths configured" while their file plainly
312
+ // contains one, which is the most confusing possible answer.
313
+ const unknown = Object.keys(sources).filter(
314
+ (k) => !(HARNESS_KEYS as readonly string[]).includes(k),
315
+ );
316
+
317
+ if (names.length === 0 && unknown.length === 0) {
318
+ return ok([
319
+ "No extra capture paths configured.",
320
+ "",
321
+ "Every harness is watching only its default location. Add one with:",
322
+ " failproofai harness add-path <harness> [<label>=]<path>",
323
+ "",
324
+ `Harnesses: ${HARNESS_KEYS.join(", ")}`,
325
+ ]);
326
+ }
327
+
328
+ const lines: string[] = [];
329
+ for (const name of names) {
330
+ const entries = sources[name]?.extraPaths ?? [];
331
+ if (entries.length === 0) {
332
+ if (harness) lines.push(`${name}: no extra paths configured (default location only)`);
333
+ continue;
334
+ }
335
+ lines.push(`${name}:`);
336
+ for (const e of entries) {
337
+ const label = labelOf(e);
338
+ lines.push(
339
+ label
340
+ ? ` ${pathOf(e)} → agent ids ${label}-*`
341
+ : ` ${pathOf(e)} → label derived from the folder name`,
342
+ );
343
+ }
344
+ }
345
+ // Unknown tables are surfaced here as well as by the daemon: a user who
346
+ // hand-edited config.json runs `list` to check it, and that is the moment the
347
+ // typo is cheapest to find.
348
+ if (unknown.length > 0) {
349
+ if (lines.length === 0) lines.push("No extra capture paths configured for any known harness.");
350
+ lines.push(
351
+ "",
352
+ `⚠ config.json configures ${unknown.length} unknown harness(es): ${unknown.join(", ")}`,
353
+ " Nothing is captured from them.",
354
+ ` Known: ${HARNESS_KEYS.join(", ")}`,
355
+ );
356
+ }
357
+ return ok(lines);
358
+ }
359
+
360
+ /** Dispatch for `failproofai harness <sub> ...`. */
361
+ export function runHarnessCommand(argv: string[]): HarnessResult {
362
+ const [sub, ...rest] = argv;
363
+ switch (sub) {
364
+ case "add-path":
365
+ if (rest.length < 2) {
366
+ return fail(["Usage: failproofai harness add-path <harness> [<label>=]<path>"]);
367
+ }
368
+ return addPath(rest[0], rest.slice(1).join(" "));
369
+ case "remove-path":
370
+ if (rest.length < 2) {
371
+ return fail(["Usage: failproofai harness remove-path <harness> <path|label>"]);
372
+ }
373
+ return removePath(rest[0], rest.slice(1).join(" "));
374
+ case "list":
375
+ return listPaths(rest[0]);
376
+ default:
377
+ return fail([
378
+ sub ? `Unknown subcommand: ${sub}` : "A subcommand is required.",
379
+ "",
380
+ "Usage:",
381
+ " failproofai harness list [<harness>]",
382
+ " failproofai harness add-path <harness> [<label>=]<path>",
383
+ " failproofai harness remove-path <harness> <path|label>",
384
+ ]);
385
+ }
386
+ }