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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (357) hide show
  1. package/.next/standalone/.next/BUILD_ID +1 -1
  2. package/.next/standalone/.next/app-path-routes-manifest.json +2 -1
  3. package/.next/standalone/.next/build-manifest.json +6 -6
  4. package/.next/standalone/.next/prerender-manifest.json +3 -3
  5. package/.next/standalone/.next/required-server-files.json +1 -1
  6. package/.next/standalone/.next/routes-manifest.json +6 -0
  7. package/.next/standalone/.next/server/app/_global-error/page/build-manifest.json +3 -3
  8. package/.next/standalone/.next/server/app/_global-error/page/server-reference-manifest.json +1 -1
  9. package/.next/standalone/.next/server/app/_global-error/page.js +3 -1
  10. package/.next/standalone/.next/server/app/_global-error/page.js.nft.json +1 -1
  11. package/.next/standalone/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
  12. package/.next/standalone/.next/server/app/_global-error.html +1 -1
  13. package/.next/standalone/.next/server/app/_global-error.rsc +7 -7
  14. package/.next/standalone/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +2 -2
  15. package/.next/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +7 -7
  16. package/.next/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +3 -3
  17. package/.next/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +3 -3
  18. package/.next/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  19. package/.next/standalone/.next/server/app/_not-found/page/build-manifest.json +3 -3
  20. package/.next/standalone/.next/server/app/_not-found/page/server-reference-manifest.json +1 -1
  21. package/.next/standalone/.next/server/app/_not-found/page.js +3 -1
  22. package/.next/standalone/.next/server/app/_not-found/page.js.nft.json +1 -1
  23. package/.next/standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  24. package/.next/standalone/.next/server/app/_not-found.html +1 -1
  25. package/.next/standalone/.next/server/app/_not-found.rsc +15 -15
  26. package/.next/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +15 -15
  27. package/.next/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +4 -4
  28. package/.next/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +10 -10
  29. package/.next/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +2 -2
  30. package/.next/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +3 -3
  31. package/.next/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
  32. package/.next/standalone/.next/server/app/api/audit/invite/route.js +3 -2
  33. package/.next/standalone/.next/server/app/api/audit/invite/route.js.nft.json +1 -1
  34. package/.next/standalone/.next/server/app/api/audit/run/route.js +4 -3
  35. package/.next/standalone/.next/server/app/api/audit/run/route.js.nft.json +1 -1
  36. package/.next/standalone/.next/server/app/api/audit/status/route.js +1 -1
  37. package/.next/standalone/.next/server/app/api/audit/status/route.js.nft.json +1 -1
  38. package/.next/standalone/.next/server/app/api/auth/login-request/route.js +2 -1
  39. package/.next/standalone/.next/server/app/api/auth/login-request/route.js.nft.json +1 -1
  40. package/.next/standalone/.next/server/app/api/auth/login-verify/route.js +3 -2
  41. package/.next/standalone/.next/server/app/api/auth/login-verify/route.js.nft.json +1 -1
  42. package/.next/standalone/.next/server/app/api/auth/logout/route.js +3 -2
  43. package/.next/standalone/.next/server/app/api/auth/logout/route.js.nft.json +1 -1
  44. package/.next/standalone/.next/server/app/api/auth/reminder/route.js +3 -2
  45. package/.next/standalone/.next/server/app/api/auth/reminder/route.js.nft.json +1 -1
  46. package/.next/standalone/.next/server/app/api/auth/status/route.js +3 -2
  47. package/.next/standalone/.next/server/app/api/auth/status/route.js.nft.json +1 -1
  48. package/.next/standalone/.next/server/app/api/download/[project]/[session]/route.js +1 -1
  49. package/.next/standalone/.next/server/app/api/download/[project]/[session]/route.js.nft.json +1 -1
  50. package/.next/standalone/.next/server/app/audit/page/build-manifest.json +3 -3
  51. package/.next/standalone/.next/server/app/audit/page/server-reference-manifest.json +2 -2
  52. package/.next/standalone/.next/server/app/audit/page.js +5 -3
  53. package/.next/standalone/.next/server/app/audit/page.js.nft.json +1 -1
  54. package/.next/standalone/.next/server/app/audit/page_client-reference-manifest.js +1 -1
  55. package/.next/standalone/.next/server/app/index.html +1 -1
  56. package/.next/standalone/.next/server/app/index.rsc +15 -15
  57. package/.next/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +2 -2
  58. package/.next/standalone/.next/server/app/index.segments/_full.segment.rsc +15 -15
  59. package/.next/standalone/.next/server/app/index.segments/_head.segment.rsc +4 -4
  60. package/.next/standalone/.next/server/app/index.segments/_index.segment.rsc +10 -10
  61. package/.next/standalone/.next/server/app/index.segments/_tree.segment.rsc +2 -2
  62. package/.next/standalone/.next/server/app/page/build-manifest.json +3 -3
  63. package/.next/standalone/.next/server/app/page/server-reference-manifest.json +1 -1
  64. package/.next/standalone/.next/server/app/page.js +3 -1
  65. package/.next/standalone/.next/server/app/page.js.nft.json +1 -1
  66. package/.next/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
  67. package/.next/standalone/.next/server/app/policies/page/build-manifest.json +3 -3
  68. package/.next/standalone/.next/server/app/policies/page/server-reference-manifest.json +30 -18
  69. package/.next/standalone/.next/server/app/policies/page.js +5 -3
  70. package/.next/standalone/.next/server/app/policies/page.js.nft.json +1 -1
  71. package/.next/standalone/.next/server/app/policies/page_client-reference-manifest.js +1 -1
  72. package/.next/standalone/.next/server/app/project/[name]/page/build-manifest.json +3 -3
  73. package/.next/standalone/.next/server/app/project/[name]/page/server-reference-manifest.json +1 -1
  74. package/.next/standalone/.next/server/app/project/[name]/page.js +4 -2
  75. package/.next/standalone/.next/server/app/project/[name]/page.js.nft.json +1 -1
  76. package/.next/standalone/.next/server/app/project/[name]/page_client-reference-manifest.js +1 -1
  77. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page/build-manifest.json +3 -3
  78. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page/react-loadable-manifest.json +2 -2
  79. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page/server-reference-manifest.json +2 -2
  80. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page.js +6 -4
  81. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page.js.nft.json +1 -1
  82. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page_client-reference-manifest.js +1 -1
  83. package/.next/standalone/.next/server/app/projects/page/build-manifest.json +3 -3
  84. package/.next/standalone/.next/server/app/projects/page/server-reference-manifest.json +1 -1
  85. package/.next/standalone/.next/server/app/projects/page.js +4 -2
  86. package/.next/standalone/.next/server/app/projects/page.js.nft.json +1 -1
  87. package/.next/standalone/.next/server/app/projects/page_client-reference-manifest.js +1 -1
  88. package/.next/standalone/.next/server/app/settings/page/app-paths-manifest.json +3 -0
  89. package/.next/standalone/.next/server/app/settings/page/build-manifest.json +18 -0
  90. package/.next/standalone/.next/server/app/settings/page/next-font-manifest.json +6 -0
  91. package/.next/standalone/.next/server/app/settings/page/react-loadable-manifest.json +1 -0
  92. package/.next/standalone/.next/server/app/settings/page/server-reference-manifest.json +53 -0
  93. package/.next/standalone/.next/server/app/settings/page.js +18 -0
  94. package/.next/standalone/.next/server/app/settings/page.js.map +5 -0
  95. package/.next/standalone/.next/server/app/settings/page.js.nft.json +1 -0
  96. package/.next/standalone/.next/server/app/settings/page_client-reference-manifest.js +3 -0
  97. package/.next/standalone/.next/server/app-paths-manifest.json +2 -1
  98. package/.next/standalone/.next/server/chunks/[externals]__0h9_83k._.js +3 -0
  99. package/.next/standalone/.next/server/chunks/{[externals]__1nl3dvw._.js → [externals]__0r-zamh._.js} +2 -2
  100. package/.next/standalone/.next/server/chunks/[externals]__1ifpkx-._.js +3 -0
  101. package/.next/standalone/.next/server/chunks/[root-of-the-server]__06hexd0._.js +3 -0
  102. package/.next/standalone/.next/server/chunks/[root-of-the-server]__094p1f1._.js +3 -0
  103. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0_0xu5z._.js +1 -1
  104. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0ed01_d._.js +3 -0
  105. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0g0vcwt._.js +3 -0
  106. package/.next/standalone/.next/server/chunks/{[root-of-the-server]__0h3lfmp._.js → [root-of-the-server]__0ky_n0r._.js} +2 -2
  107. package/.next/standalone/.next/server/chunks/{[root-of-the-server]__18d-9oi._.js → [root-of-the-server]__0lk_935._.js} +2 -2
  108. package/.next/standalone/.next/server/chunks/{[root-of-the-server]__16le-kd._.js → [root-of-the-server]__0pbp9fe._.js} +2 -2
  109. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0q-v9z2._.js +1 -1
  110. package/.next/standalone/.next/server/chunks/{[root-of-the-server]__10oa-v9._.js → [root-of-the-server]__0v2fpxe._.js} +2 -2
  111. package/.next/standalone/.next/server/chunks/{[root-of-the-server]__0sb_5m8._.js → [root-of-the-server]__131ebum._.js} +3 -3
  112. package/.next/standalone/.next/server/chunks/{[root-of-the-server]__0-dh5jy._.js → [root-of-the-server]__1_ps0jw._.js} +2 -2
  113. package/.next/standalone/.next/server/chunks/{[root-of-the-server]__0l4vuqm._.js → [root-of-the-server]__1a7i2q4._.js} +2 -2
  114. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1ibjy5x._.js +3 -0
  115. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1iny24d._.js +3 -0
  116. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1tvn4wf._.js +1 -1
  117. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1tyedjt._.js +3 -0
  118. package/.next/standalone/.next/server/chunks/_0lxbzdq._.js +5 -0
  119. package/.next/standalone/.next/server/chunks/_1zuiiy3._.js +5 -0
  120. package/.next/standalone/.next/server/chunks/lib_telemetry_ts_0dctyyw._.js +5 -0
  121. package/.next/standalone/.next/server/chunks/node_modules_next_dist_esm_build_templates_app-route_17k9e3w.js +5 -5
  122. package/.next/standalone/.next/server/chunks/node_modules_posthog-node_dist_entrypoints_index_node_mjs_01r25oi._.js +2 -1
  123. package/.next/standalone/.next/server/chunks/node_modules_posthog-node_dist_entrypoints_index_node_mjs_09z9-p7._.js +2 -1
  124. package/.next/standalone/.next/server/chunks/package_json_[json]_cjs_1nxcc4v._.js +1 -1
  125. package/.next/standalone/.next/server/chunks/ssr/[externals]__1aq139v._.js +3 -0
  126. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__04pwh4t._.js +3 -0
  127. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__06bp_fg._.js +5 -0
  128. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__074qmkp._.js +3 -0
  129. package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__170799-._.js → [root-of-the-server]__0spkm68._.js} +11 -11
  130. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__10butu6._.js +3 -0
  131. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1a2hcb_._.js +3 -0
  132. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1qapotl._.js +1 -1
  133. package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__1apb7k_._.js → [root-of-the-server]__1rdruw4._.js} +2 -2
  134. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1rv-sc9._.js +3 -0
  135. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1tyf0tc._.js +5 -0
  136. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1u-wx42._.js +4 -0
  137. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1v185mo._.js +3 -0
  138. package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__01od0m6._.js → [root-of-the-server]__1yw368c._.js} +2 -2
  139. package/.next/standalone/.next/server/chunks/ssr/_0-yi74u._.js +3 -0
  140. package/.next/standalone/.next/server/chunks/ssr/_04mekdy._.js +3 -0
  141. package/.next/standalone/.next/server/chunks/ssr/_0r-dsre._.js +3 -0
  142. package/.next/standalone/.next/server/chunks/ssr/_0rjgfln._.js +3 -0
  143. package/.next/standalone/.next/server/chunks/ssr/_0se484m._.js +3 -0
  144. package/.next/standalone/.next/server/chunks/ssr/_0uz7o9y._.js +3 -0
  145. package/.next/standalone/.next/server/chunks/ssr/_11_p9y8._.js +1 -1
  146. package/.next/standalone/.next/server/chunks/ssr/_1e19atl._.js +1 -1
  147. package/.next/standalone/.next/server/chunks/ssr/_1fahg9e._.js +3 -0
  148. package/.next/standalone/.next/server/chunks/ssr/_1gmzkwj._.js +5 -0
  149. package/.next/standalone/.next/server/chunks/ssr/_1w8sola._.js +3 -0
  150. package/.next/standalone/.next/server/chunks/ssr/_1w_6p9g._.js +3 -0
  151. package/.next/standalone/.next/server/chunks/ssr/_1y4qr7u._.js +3 -0
  152. package/.next/standalone/.next/server/chunks/ssr/app_audit__components_audit-dashboard_tsx_0p9ud47._.js +2 -2
  153. package/.next/standalone/.next/server/chunks/ssr/app_global-error_tsx_1kp6l3x._.js +1 -1
  154. package/.next/standalone/.next/server/chunks/ssr/app_policies_hooks-client_tsx_19dqvpc._.js +2 -2
  155. package/.next/standalone/.next/server/chunks/ssr/node_modules_next_dist_esm_build_templates_app-page_0dwo94s.js +4 -0
  156. package/.next/standalone/.next/server/chunks/ssr/node_modules_posthog-node_dist_entrypoints_index_node_mjs_11bnuzn._.js +2 -1
  157. package/.next/standalone/.next/server/chunks/ssr/src_hooks_builtin-policies_ts_09j2ndl._.js +3 -0
  158. package/.next/standalone/.next/server/chunks/ssr/src_hooks_fp-config_ts_04t589g._.js +5 -0
  159. package/.next/standalone/.next/server/middleware-build-manifest.js +6 -6
  160. package/.next/standalone/.next/server/middleware.js +2 -2
  161. package/.next/standalone/.next/server/pages/404.html +1 -1
  162. package/.next/standalone/.next/server/pages/500.html +1 -1
  163. package/.next/standalone/.next/server/server-reference-manifest.js +1 -1
  164. package/.next/standalone/.next/server/server-reference-manifest.json +74 -20
  165. package/.next/standalone/.next/static/chunks/00adubtgov21s.js +1 -0
  166. package/.next/standalone/.next/static/chunks/{03fmihek9n986.js → 06zgzyh3wv1ig.js} +1 -1
  167. package/.next/standalone/.next/static/chunks/{1hdhteui932bq.js → 0qmo2t50kpb30.js} +1 -1
  168. package/.next/standalone/.next/static/chunks/{1grd7y94-igz9.js → 0yudogeqddpzr.js} +1 -1
  169. package/.next/standalone/.next/static/chunks/16p68cybetr31.js +1 -0
  170. package/.next/standalone/.next/static/chunks/1bm0q12hgs22l.css +2 -0
  171. package/.next/standalone/.next/static/chunks/{1d6ncspjagm53.js → 1fu90ch80qngv.js} +1 -1
  172. package/.next/standalone/.next/static/chunks/{2vr7aazw6k2-n.js → 1m416lie3qwsj.js} +1 -1
  173. package/.next/standalone/.next/static/chunks/1v1gqvpjk62jm.js +1 -0
  174. package/.next/standalone/.next/static/chunks/1ypqzvoal8j56.js +6 -0
  175. package/.next/standalone/.next/static/chunks/23kev26by1e4f.js +1 -0
  176. package/.next/standalone/.next/static/chunks/{29k6ef49zppxj.js → 2h4sbmp0ad78w.js} +1 -1
  177. package/.next/standalone/.next/static/chunks/2obwg4tterjpa.js +1 -0
  178. package/.next/standalone/.next/static/chunks/2uvhw57mf9lg_.css +2 -0
  179. package/.next/standalone/.next/static/chunks/{29ydr-8q0zis6.js → 30ufy4_e2n78e.js} +22 -22
  180. package/.next/standalone/.next/static/chunks/31coz11ia5vc4.js +1 -0
  181. package/.next/standalone/.next/static/chunks/{1s-_swlc80y43.js → 3p4j0l1o_p-nm.js} +1 -1
  182. package/.next/standalone/.next/static/chunks/{0ip1n8xmgxarv.js → 3uxnxwxcctyj0.js} +1 -1
  183. package/.next/standalone/.next/static/chunks/{turbopack-1lsv92x7klwrn.js → turbopack-3diub4ctvn1n0.js} +1 -1
  184. package/.next/standalone/app/actions/get-active-pauses.ts +16 -0
  185. package/.next/standalone/app/actions/get-hooks-config.ts +2 -1
  186. package/.next/standalone/app/actions/get-scheduled-audit.ts +70 -0
  187. package/.next/standalone/app/actions/update-scheduled-audit.ts +44 -0
  188. package/.next/standalone/app/api/audit/run/route.ts +28 -0
  189. package/.next/standalone/app/api/audit/status/route.ts +13 -1
  190. package/.next/standalone/app/api/auth/status/route.ts +1 -1
  191. package/.next/standalone/app/audit/_components/auth-dialog.css +159 -0
  192. package/.next/standalone/app/audit/_components/auth-dialog.tsx +3 -0
  193. package/.next/standalone/app/audit/audit-styles.css +6 -149
  194. package/.next/standalone/app/components/pause-notices.tsx +111 -0
  195. package/.next/standalone/app/policies/hooks-client.tsx +81 -6
  196. package/.next/standalone/app/settings/page.tsx +31 -0
  197. package/.next/standalone/app/settings/settings-client.tsx +488 -0
  198. package/.next/standalone/components/navbar.tsx +2 -0
  199. package/.next/standalone/lib/auth/api-server-client.ts +2 -3
  200. package/.next/standalone/lib/auth/auth-store.ts +4 -4
  201. package/.next/standalone/lib/codex-sessions.ts +2 -1
  202. package/.next/standalone/lib/dashboard-host.ts +101 -0
  203. package/.next/standalone/lib/install-check.ts +22 -7
  204. package/.next/standalone/lib/pi-sessions.ts +99 -14
  205. package/.next/standalone/lib/telemetry-enabled.ts +54 -0
  206. package/.next/standalone/lib/telemetry-id.ts +52 -1
  207. package/.next/standalone/lib/telemetry.ts +8 -4
  208. package/.next/standalone/node_modules/react/cjs/react.development.js +1 -1
  209. package/.next/standalone/node_modules/react/cjs/react.production.js +1 -1
  210. package/.next/standalone/node_modules/react/package.json +3 -3
  211. package/.next/standalone/node_modules/react-dom/cjs/react-dom-server-legacy.browser.production.js +1 -1
  212. package/.next/standalone/node_modules/react-dom/cjs/react-dom-server-legacy.node.production.js +1 -1
  213. package/.next/standalone/node_modules/react-dom/cjs/react-dom-server.browser.production.js +3 -3
  214. package/.next/standalone/node_modules/react-dom/cjs/react-dom-server.edge.production.js +3 -3
  215. package/.next/standalone/node_modules/react-dom/cjs/react-dom-server.node.production.js +3 -3
  216. package/.next/standalone/node_modules/react-dom/cjs/react-dom.production.js +1 -1
  217. package/.next/standalone/node_modules/react-dom/package.json +4 -4
  218. package/.next/standalone/package.json +17 -10
  219. package/.next/standalone/proxy.ts +89 -1
  220. package/.next/standalone/server.js +1 -1
  221. package/bin/failproofai.mjs +518 -79
  222. package/dist/cli.mjs +11484 -6046
  223. package/dist/worker.mjs +2019 -272
  224. package/lib/auth/api-server-client.ts +2 -3
  225. package/lib/auth/auth-store.ts +4 -4
  226. package/lib/codex-sessions.ts +2 -1
  227. package/lib/dashboard-host.ts +101 -0
  228. package/lib/install-check.ts +22 -7
  229. package/lib/pi-sessions.ts +99 -14
  230. package/lib/telemetry-enabled.ts +54 -0
  231. package/lib/telemetry-id.ts +52 -1
  232. package/lib/telemetry.ts +8 -4
  233. package/package.json +17 -10
  234. package/scripts/build-daemon-packages.mjs +210 -0
  235. package/scripts/daemon-platforms.mjs +42 -0
  236. package/scripts/install-telemetry.mjs +8 -1
  237. package/scripts/launch.ts +25 -3
  238. package/scripts/parse-script-args.ts +17 -1
  239. package/scripts/prune-standalone.mjs +26 -0
  240. package/scripts/publish-aliases.mjs +62 -41
  241. package/scripts/repro-npm-install.sh +155 -0
  242. package/scripts/translate-docs/readme-translator.ts +163 -4
  243. package/scripts/validate-mdx.ts +153 -13
  244. package/src/audit/audit-lock.ts +285 -0
  245. package/src/audit/audit-schedule.ts +111 -0
  246. package/src/audit/cache.ts +18 -4
  247. package/src/audit/cli-adapters/goose.ts +2 -1
  248. package/src/audit/cli-adapters/hermes.ts +28 -10
  249. package/src/audit/cli.ts +191 -35
  250. package/src/audit/dashboard-cache.ts +2 -1
  251. package/src/hooks/backfill-cli.ts +247 -0
  252. package/src/hooks/builtin-policies.ts +390 -2
  253. package/src/hooks/cloud-connection.ts +354 -0
  254. package/src/hooks/cloud-enrollment-cli.ts +325 -0
  255. package/src/hooks/cloud-enrollment.ts +243 -0
  256. package/src/hooks/cloud-introspect.ts +147 -0
  257. package/src/hooks/cloud-managed-policies.ts +169 -0
  258. package/src/hooks/collector-config.ts +234 -0
  259. package/src/hooks/configure-wizard.ts +915 -158
  260. package/src/hooks/custom-hooks-loader.ts +191 -33
  261. package/src/hooks/daemon-client.ts +136 -17
  262. package/src/hooks/daemon-download.ts +192 -30
  263. package/src/hooks/daemon-service.ts +1190 -92
  264. package/src/hooks/first-run-gate.ts +50 -0
  265. package/src/hooks/fp-config.ts +497 -0
  266. package/src/hooks/fp-home.ts +375 -0
  267. package/src/hooks/fp-reset.ts +464 -0
  268. package/src/hooks/handler.ts +185 -6
  269. package/src/hooks/hook-activity-store.ts +85 -20
  270. package/src/hooks/hook-logger.ts +2 -1
  271. package/src/hooks/hook-telemetry.ts +5 -1
  272. package/src/hooks/hooks-config.ts +4 -3
  273. package/src/hooks/loader-utils.ts +171 -8
  274. package/src/hooks/manager.ts +5 -4
  275. package/src/hooks/onboarding-attempt.ts +179 -0
  276. package/src/hooks/onboarding-lock.ts +119 -0
  277. package/src/hooks/policy-types.ts +28 -0
  278. package/src/hooks/session-pause-cli.ts +159 -0
  279. package/src/hooks/session-pause.ts +268 -0
  280. package/src/hooks/setup-state.ts +256 -0
  281. package/src/hooks/tui.ts +119 -4
  282. package/src/hooks/uninstall-cli.ts +345 -0
  283. package/src/hooks/worker-server.ts +65 -3
  284. package/.next/standalone/.next/server/chunks/[externals]__14odj07._.js +0 -3
  285. package/.next/standalone/.next/server/chunks/[externals]__1s61mel._.js +0 -3
  286. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0id_xf7._.js +0 -3
  287. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0longh-._.js +0 -3
  288. package/.next/standalone/.next/server/chunks/[root-of-the-server]__17g9wh7._.js +0 -3
  289. package/.next/standalone/.next/server/chunks/[root-of-the-server]__19120tr._.js +0 -3
  290. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1fwl2mz._.js +0 -3
  291. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1myjm-d._.js +0 -3
  292. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1rhmvod._.js +0 -3
  293. package/.next/standalone/.next/server/chunks/ssr/[externals]__1_g_b3t._.js +0 -3
  294. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0114ewg._.js +0 -4
  295. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0259h38._.js +0 -3
  296. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__02r5bgf._.js +0 -4
  297. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0e8sjqm._.js +0 -4
  298. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0lkzqax._.js +0 -4
  299. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0qucxyj._.js +0 -4
  300. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0u02miy._.js +0 -4
  301. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0uno39i._.js +0 -3
  302. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__14dd6h8._.js +0 -3
  303. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1ath6v_._.js +0 -4
  304. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1zj-87g._.js +0 -3
  305. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1zqz4v8._.js +0 -4
  306. package/.next/standalone/.next/server/chunks/ssr/_1m_p7pi._.js +0 -3
  307. package/.next/standalone/.next/server/chunks/ssr/node_modules_html-to-image_es_index_1-upb14.js +0 -3
  308. package/.next/standalone/.next/server/chunks/ssr/src_hooks_1ezd2jf._.js +0 -5
  309. package/.next/standalone/.next/server/chunks/ssr/src_hooks_1tnuifj._.js +0 -5
  310. package/.next/standalone/.next/static/chunks/0h7auy7hzjyhw.js +0 -1
  311. package/.next/standalone/.next/static/chunks/0u9hc6oxx-gpj.js +0 -6
  312. package/.next/standalone/.next/static/chunks/0zbxssxh53n-3.js +0 -1
  313. package/.next/standalone/.next/static/chunks/2mazwb1qmur1p.js +0 -1
  314. package/.next/standalone/.next/static/chunks/2pujpnfx4pws6.css +0 -2
  315. package/.next/standalone/.next/static/chunks/33u59vf_8xpd-.js +0 -1
  316. package/.next/standalone/.next/static/chunks/3i27c3hcriawq.css +0 -1
  317. package/.next/standalone/.next/static/chunks/3wrgbih-516uj.js +0 -1
  318. package/.next/standalone/Cargo.lock +0 -428
  319. package/.next/standalone/Cargo.toml +0 -9
  320. package/.next/standalone/crates/PROTOCOL.md +0 -122
  321. package/.next/standalone/crates/failproofaid/Cargo.toml +0 -18
  322. package/.next/standalone/crates/failproofaid/src/lock.rs +0 -81
  323. package/.next/standalone/crates/failproofaid/src/main.rs +0 -83
  324. package/.next/standalone/crates/failproofaid/src/paths.rs +0 -172
  325. package/.next/standalone/crates/failproofaid/src/server.rs +0 -502
  326. package/.next/standalone/crates/failproofaid/src/worker.rs +0 -400
  327. package/.next/standalone/crates/failproofaid/tests/daemon_e2e.rs +0 -241
  328. package/.next/standalone/crates/fpai-ipc/Cargo.toml +0 -16
  329. package/.next/standalone/crates/fpai-ipc/src/envelope.rs +0 -175
  330. package/.next/standalone/crates/fpai-ipc/src/framing.rs +0 -177
  331. package/.next/standalone/crates/fpai-ipc/src/lib.rs +0 -10
  332. package/.next/standalone/crates/fpai-ipc/src/peer.rs +0 -80
  333. package/.next/standalone/docker-hook-sync/Dockerfile +0 -79
  334. package/.next/standalone/docker-hook-sync/entrypoint.sh +0 -109
  335. package/.next/standalone/integration-suite/Dockerfile +0 -47
  336. package/.next/standalone/integration-suite/README.md +0 -129
  337. package/.next/standalone/integration-suite/canary-policies.mjs +0 -45
  338. package/.next/standalone/integration-suite/capture-tokens.sh +0 -48
  339. package/.next/standalone/integration-suite/ci-entrypoint.sh +0 -179
  340. package/.next/standalone/integration-suite/inject-tokens.sh +0 -44
  341. package/.next/standalone/integration-suite/install-clis.sh +0 -136
  342. package/.next/standalone/integration-suite/probe-cli.sh +0 -248
  343. package/.next/standalone/integration-suite/report.js +0 -120
  344. package/.next/standalone/integration-suite/run.sh +0 -159
  345. package/.next/standalone/openclaw-plugin/index.js +0 -223
  346. package/.next/standalone/openclaw-plugin/openclaw.plugin.json +0 -6
  347. package/.next/standalone/openclaw-plugin/package.json +0 -12
  348. package/.next/standalone/osv-scanner.toml +0 -17
  349. package/.next/standalone/pi-extension/index.ts +0 -524
  350. package/.next/standalone/pi-extension/package.json +0 -12
  351. package/.next/standalone/postcss.config.mjs +0 -8
  352. package/.next/standalone/readme-arch-hq.gif +0 -0
  353. package/.next/standalone/rust-toolchain.toml +0 -4
  354. package/src/auth/cli.ts +0 -371
  355. /package/.next/standalone/.next/static/{7Lxtd352rSaHB-pmm6G8M → pBBcTvE6_VueYGbacy1kg}/_buildManifest.js +0 -0
  356. /package/.next/standalone/.next/static/{7Lxtd352rSaHB-pmm6G8M → pBBcTvE6_VueYGbacy1kg}/_clientMiddlewareManifest.js +0 -0
  357. /package/.next/standalone/.next/static/{7Lxtd352rSaHB-pmm6G8M → pBBcTvE6_VueYGbacy1kg}/_ssgManifest.js +0 -0
@@ -0,0 +1,155 @@
1
+ #!/usr/bin/env bash
2
+ # ─────────────────────────────────────────────────────────────────────────────
3
+ # Reproduce a REAL user install, in the shape that actually breaks.
4
+ #
5
+ # sudo npm i -g failproofai (global prefix owned by root)
6
+ # then run the CLI as a NORMAL user
7
+ #
8
+ # That split is the whole point. A single-user laptop where npm's prefix is
9
+ # owned by the person running the hooks hides an entire class of bug — it is
10
+ # how the root-owned policy-shim fail-open shipped: every hook run by a non-root
11
+ # user hit EACCES, the policy never loaded, and the hook exited 0 ALLOWING the
12
+ # call, while builtins kept firing so the machine looked protected.
13
+ #
14
+ # systemd is real here (jrei/systemd-ubuntu), so `failproofai config` installs
15
+ # and starts the actual service rather than skipping that half.
16
+ #
17
+ # Usage:
18
+ # scripts/repro-npm-install.sh # build, pack, drop into a shell
19
+ # scripts/repro-npm-install.sh --published # install the last RELEASE instead
20
+ # ─────────────────────────────────────────────────────────────────────────────
21
+ set -uo pipefail
22
+
23
+ REPO="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
24
+ cd "$REPO"
25
+
26
+ IMAGE=jrei/systemd-ubuntu:24.04
27
+ NAME=fpai-repro
28
+ USER_IN_BOX=dev
29
+ PUBLISHED=0
30
+ [ "${1:-}" = "--published" ] && PUBLISHED=1
31
+
32
+ VERSION=$(node -p "require('$REPO/package.json').version")
33
+ say() { printf '\n\033[1m── %s\033[0m\n' "$1"; }
34
+
35
+ # ── 1. Build what a tarball would carry ──────────────────────────────────────
36
+ if [ "$PUBLISHED" -eq 0 ]; then
37
+ say "Building dist + daemon"
38
+ [ -f dist/cli.mjs ] || bun run build
39
+ [ -f target/release/failproofaid ] || cargo build --release --bin failproofaid
40
+
41
+ # `dist/cli.mjs` INLINES the version at build time. `npm pack --ignore-scripts`
42
+ # skips the `prepare` script that would rebuild it, so bumping package.json
43
+ # without rebuilding packs a bundle that reports the OLD version — and you
44
+ # spend an afternoon testing code you did not write. A real publish never hits
45
+ # this (publish.yml builds, and `prepare` runs on a normal pack); this is a
46
+ # trap of the fast path only, which is exactly why it is checked here.
47
+ if ! grep -q "$VERSION" dist/cli.mjs 2>/dev/null; then
48
+ say "dist is stale (built for $(grep -o '[0-9]\+\.[0-9]\+\.[0-9]\+-beta\.[0-9]\+' dist/cli.mjs | head -1)) — rebuilding"
49
+ bun run build
50
+ fi
51
+
52
+ say "Packing $VERSION"
53
+ rm -f failproofai-*.tgz
54
+ npm pack --ignore-scripts >/dev/null 2>&1
55
+ TARBALL="$REPO/failproofai-$VERSION.tgz"
56
+ [ -f "$TARBALL" ] || { echo "pack failed"; exit 1; }
57
+ ls -la "$TARBALL" | awk '{printf " %s bytes %s\n", $5, $9}'
58
+
59
+ # Belt and braces: assert what is actually IN the tarball, not what we meant
60
+ # to put there.
61
+ PACKED=$(tar -xzOf "$TARBALL" package/dist/cli.mjs 2>/dev/null \
62
+ | grep -o '[0-9]\+\.[0-9]\+\.[0-9]\+-beta\.[0-9]\+' | head -1)
63
+ if [ "$PACKED" != "$VERSION" ]; then
64
+ echo " REFUSING: tarball reports '$PACKED' but package.json says '$VERSION'"
65
+ exit 1
66
+ fi
67
+ echo " bundled version verified: $PACKED"
68
+ fi
69
+
70
+ # ── 2. A container with a real init ──────────────────────────────────────────
71
+ say "Starting $IMAGE (real systemd)"
72
+ docker rm -f "$NAME" >/dev/null 2>&1
73
+ # --cgroupns=host + the two tmpfs mounts are REQUIRED on a cgroup v2 host
74
+ # (anything modern). Without them systemd cannot set up its own hierarchy and
75
+ # the container exits 255 immediately, with an empty `docker logs` — which is
76
+ # a genuinely awful thing to debug, hence the comment.
77
+ docker run -d --name "$NAME" --privileged --cgroupns=host \
78
+ --network=host \
79
+ -v /sys/fs/cgroup:/sys/fs/cgroup:rw --tmpfs /run --tmpfs /run/lock \
80
+ ${TARBALL:+-v "$TARBALL":/pkg.tgz:ro} \
81
+ -v "$REPO/target/release/failproofaid":/opt/failproofaid:ro \
82
+ "$IMAGE" >/dev/null
83
+ # Wait for systemd to finish booting, or `systemctl` answers nothing useful.
84
+ until docker exec "$NAME" systemctl is-system-running 2>/dev/null | grep -qE 'running|degraded'; do
85
+ if ! docker ps --filter "name=$NAME" --format '{{.Names}}' | grep -q "$NAME"; then
86
+ echo " container died during boot — cgroup v2 flags wrong for this host?"
87
+ docker logs "$NAME" 2>&1 | tail -5
88
+ exit 1
89
+ fi
90
+ sleep 1
91
+ done
92
+ echo " systemd: $(docker exec "$NAME" systemctl is-system-running 2>/dev/null)"
93
+
94
+ # ── 3. Node, and a NON-ROOT user to run as ───────────────────────────────────
95
+ say "Installing Node + creating an unprivileged user"
96
+ docker exec "$NAME" bash -lc '
97
+ apt-get update -qq >/dev/null 2>&1
98
+ apt-get install -y -qq curl ca-certificates sudo >/dev/null 2>&1
99
+ curl -fsSL https://deb.nodesource.com/setup_22.x -o /tmp/n.sh 2>/dev/null && bash /tmp/n.sh >/dev/null 2>&1
100
+ apt-get install -y -qq nodejs >/dev/null 2>&1
101
+ node --version
102
+ ' 2>&1 | sed 's/^/ node /'
103
+
104
+ docker exec "$NAME" bash -lc "
105
+ id $USER_IN_BOX >/dev/null 2>&1 || useradd -m -s /bin/bash $USER_IN_BOX
106
+ # Passwordless sudo: the daemon install needs root, and we are testing the
107
+ # INSTALL, not whether a human can type a password.
108
+ echo '$USER_IN_BOX ALL=(ALL) NOPASSWD:ALL' > /etc/sudoers.d/$USER_IN_BOX
109
+ chmod 440 /etc/sudoers.d/$USER_IN_BOX
110
+ "
111
+
112
+ # ── 4. Install globally AS ROOT — the shape that exposes ownership bugs ──────
113
+ say "npm install -g (as root)"
114
+ if [ "$PUBLISHED" -eq 1 ]; then
115
+ docker exec "$NAME" bash -lc "npm install -g failproofai@$VERSION 2>&1 | tail -4"
116
+ else
117
+ docker exec "$NAME" bash -lc "npm install -g /pkg.tgz 2>&1 | tail -4"
118
+ fi
119
+
120
+ say "Who owns the global install?"
121
+ docker exec "$NAME" bash -lc '
122
+ P=$(npm root -g)/failproofai
123
+ stat -c " %U:%G %a %n" "$P" 2>/dev/null
124
+ stat -c " %U:%G %a %n" "$P/dist" 2>/dev/null
125
+ '
126
+
127
+ # ── 5. Hand over, running as the unprivileged user ───────────────────────────
128
+ say "Ready"
129
+ cat <<EOF
130
+
131
+ The box is up as container "$NAME". Everything below runs as the
132
+ UNPRIVILEGED user, against a root-owned global install — the real shape.
133
+
134
+ Shell in:
135
+ docker exec -it -u $USER_IN_BOX $NAME bash -l
136
+
137
+ Then, inside:
138
+ failproofai --version
139
+ failproofai config # real systemd service install
140
+ systemctl status failproofaid@$USER_IN_BOX
141
+
142
+ The daemon binary is mounted at /opt/failproofaid. There is no GitHub
143
+ release for $VERSION, so point the CLI at it rather than letting it try
144
+ to download one:
145
+ export FAILPROOFAI_DAEMON_BINARY=/opt/failproofaid
146
+
147
+ Fire a hook exactly as an agent CLI would:
148
+ printf '{"session_id":"s","cwd":"'"\$PWD"'","hook_event_name":"PreToolUse",
149
+ "tool_name":"Bash","tool_input":{"command":"sudo rm -rf /"}}' \\
150
+ | failproofai --hook PreToolUse --cli claude
151
+
152
+ Tear down:
153
+ docker rm -f $NAME
154
+
155
+ EOF
@@ -17,6 +17,158 @@ const ROOT_DIR = join(__dirname, "..", "..");
17
17
  const README_PATH = join(ROOT_DIR, "README.md");
18
18
  const I18N_DIR = join(ROOT_DIR, "docs", "i18n");
19
19
 
20
+ /**
21
+ * Prefix that walks from `docs/i18n/` back to the repo root. Keep in sync with
22
+ * I18N_DIR — the disclaimer and language selector already hard-code the same
23
+ * two levels in their `../../README.md` links.
24
+ */
25
+ const TO_REPO_ROOT = "../../";
26
+
27
+ /** Raw-content base for repo files that must resolve off-GitHub too. */
28
+ const RAW_BASE = "https://raw.githubusercontent.com/FailproofAI/failproofai/main/";
29
+
30
+ const ASSET_RE = /\.(?:png|jpe?g|gif|svg|webp|ico|mp4|webm)$/i;
31
+
32
+ /**
33
+ * Re-point the root README's repo-root-relative paths so they still resolve
34
+ * from `docs/i18n/README.<lang>.md`, two directories deeper.
35
+ *
36
+ * The root README lives AT the repo root, so it writes `readme-arch-hq.gif` and
37
+ * `assets/logos/claude.svg` — correct there. The translator is prompt-forbidden
38
+ * from touching paths ("Preserve all URLs and paths", translator.ts), and
39
+ * rightly so, but that means every translated copy inherited those paths
40
+ * verbatim into a directory two levels down, where they resolve against
41
+ * `docs/i18n/`. Both consumers 404'd: GitHub asked for
42
+ * `docs/i18n/assets/logos/claude.svg` and Mintlify (which also serves these
43
+ * pages, unlisted, at `/i18n/README.<lang>`) asked S3 for `exosphere/i18n/...`.
44
+ * Every logo and the architecture GIF were broken in all 14 languages.
45
+ *
46
+ * The two ref kinds get different treatment because they resolve in different
47
+ * places:
48
+ * - IMAGES become absolute raw.githubusercontent URLs. A `../../` relative
49
+ * path would fix GitHub but not Mintlify, whose copy of the page has no
50
+ * `assets/` tree above it to walk into — an absolute URL is the only form
51
+ * that renders on BOTH surfaces.
52
+ * - DOCUMENT links (`./LICENSE`, `./CONTRIBUTING.md`, the translations row's
53
+ * `./docs/i18n/README.*.md`) get the `../../` prefix instead. GitHub is the
54
+ * only surface where a link to a repo file resolves at all, and a raw URL
55
+ * there would serve unrendered plaintext.
56
+ *
57
+ * Left alone: absolute URLs, anchors, `mailto:`, protocol-relative `//`,
58
+ * site-absolute `/`, anything already starting with `../`, and everything
59
+ * inside a fenced code block (where a path is literal sample text, not a ref).
60
+ */
61
+ export function rebaseReadmePaths(content: string): string {
62
+ // Recomputed before each pass. `String.replace` reports offsets into the
63
+ // string it is scanning, so one map is valid for a whole pass — but a pass
64
+ // that rewrites a path AHEAD of a fence lengthens the text and shifts that
65
+ // fence, leaving offsets from the previous string pointing short. The next
66
+ // pass would then read a literal `<img src=…>` inside a fence as ordinary
67
+ // markup and rewrite the sample path this guard exists to protect.
68
+ let fenceRanges = findFenceRanges(content);
69
+ const insideFence = (offset: number): boolean =>
70
+ fenceRanges.some(([start, end]) => offset >= start && offset < end);
71
+
72
+ const rebase = (path: string): string | null => {
73
+ if (path === "" || /^[a-z][a-z0-9+.-]*:/i.test(path)) return null; // scheme
74
+ if (path.startsWith("#") || path.startsWith("/")) return null; // anchor, site-absolute
75
+ if (path.startsWith("../")) return null; // already rebased
76
+ const bare = path.replace(/^\.\//, "");
77
+ if (bare === "" || bare.startsWith("../")) return null;
78
+ return ASSET_RE.test(bare.split(/[?#]/)[0])
79
+ ? `${RAW_BASE}${bare}`
80
+ : `${TO_REPO_ROOT}${bare}`;
81
+ };
82
+
83
+ // Markdown links and images: `](path)` / `](path "title")`.
84
+ let out = content.replace(
85
+ /(\]\()([^)\s]+)/g,
86
+ (match, prefix: string, path: string, offset: number) => {
87
+ if (insideFence(offset)) return match;
88
+ const next = rebase(path);
89
+ return next === null ? match : `${prefix}${next}`;
90
+ },
91
+ );
92
+
93
+ // HTML/JSX attributes: the README's logo table is a raw <table> of <img>.
94
+ fenceRanges = findFenceRanges(out);
95
+ out = out.replace(
96
+ /((?:src|href)=(["']))(.*?)\2/g,
97
+ (match, prefix: string, quote: string, path: string, offset: number) => {
98
+ if (insideFence(offset)) return match;
99
+ const next = rebase(path);
100
+ return next === null ? match : `${prefix}${next}${quote}`;
101
+ },
102
+ );
103
+
104
+ // `srcset` needs its own pass: each logo cell is a <picture> whose dark-mode
105
+ // <source srcset="assets/logos/*-dark.svg"> sits beside the <img src>. Miss
106
+ // it and half the table stays broken for dark-theme readers only — the half
107
+ // least likely to be noticed in review.
108
+ fenceRanges = findFenceRanges(out);
109
+ out = out.replace(
110
+ /(srcset=(["']))(.*?)\2/g,
111
+ (match, prefix: string, quote: string, value: string, offset: number) => {
112
+ if (insideFence(offset)) return match;
113
+ return `${prefix}${rebaseSrcset(value)}${quote}`;
114
+ },
115
+ );
116
+
117
+ return out;
118
+
119
+ /**
120
+ * Rebase every candidate in a `srcset` value, preserving each one's optional
121
+ * density/width descriptor (`logo.svg 2x`, `wide.png 800w`) and the original
122
+ * comma spacing.
123
+ */
124
+ function rebaseSrcset(value: string): string {
125
+ return value
126
+ .split(",")
127
+ .map((candidate) => {
128
+ const [, lead, url, descriptor] =
129
+ /^(\s*)(\S+)(.*)$/.exec(candidate) ?? [];
130
+ if (url === undefined) return candidate; // whitespace-only candidate
131
+ const next = rebase(url);
132
+ return `${lead}${next ?? url}${descriptor}`;
133
+ })
134
+ .join(",");
135
+ }
136
+ }
137
+
138
+ /**
139
+ * Byte ranges covered by fenced code blocks, per CommonMark: a fence opens with
140
+ * ≥3 backticks or tildes and closes only on a later line using the SAME
141
+ * character at ≥ the same length.
142
+ *
143
+ * Follows the scanner in `mdx-translator.convertHtmlComments`, with one
144
+ * correction: a CLOSING fence may carry only trailing whitespace, never an info
145
+ * string. Accepting ```` ```ts ```` as a close would end the block at the first
146
+ * *nested* opener inside it and leave the real code that follows looking like
147
+ * prose — whose sample paths this function exists to protect.
148
+ */
149
+ function findFenceRanges(content: string): Array<[number, number]> {
150
+ const ranges: Array<[number, number]> = [];
151
+ const fenceRe = /^[ \t]*(`{3,}|~{3,})([^\n]*)$/gm;
152
+ let match: RegExpExecArray | null;
153
+ let open: { char: string; length: number; start: number } | null = null;
154
+ while ((match = fenceRe.exec(content)) !== null) {
155
+ const [, marker, rest] = match;
156
+ if (!open) {
157
+ open = { char: marker[0], length: marker.length, start: match.index };
158
+ } else if (
159
+ marker[0] === open.char &&
160
+ marker.length >= open.length &&
161
+ rest.trim() === ""
162
+ ) {
163
+ const lineEnd = content.indexOf("\n", fenceRe.lastIndex);
164
+ ranges.push([open.start, lineEnd === -1 ? content.length : lineEnd]);
165
+ open = null;
166
+ }
167
+ }
168
+ if (open) ranges.push([open.start, content.length]);
169
+ return ranges;
170
+ }
171
+
20
172
  function buildLanguageSelector(currentLang: string): string {
21
173
  const flags: Record<string, string> = {
22
174
  en: "\ud83c\uddfa\ud83c\uddf8",
@@ -117,10 +269,17 @@ export async function translateReadme(
117
269
  // Same MDX sanitizers as translateMdxPage — the README emits JSX (the
118
270
  // logo table), so strip stray attribute quotes, drop any unmatched
119
271
  // trailing code fence (which would swallow the RTL `</div>`), and
120
- // convert HTML comments to MDX — then wrap in disclaimer + selector +
121
- // RTL div.
122
- const cleaned = convertHtmlComments(
123
- stripStrayTrailingFence(sanitizeJsxAttributes(raw)),
272
+ // convert HTML comments to MDX — then re-point the root README's
273
+ // repo-root-relative paths for this file's depth, and wrap in
274
+ // disclaimer + selector + RTL div.
275
+ //
276
+ // The rebase runs on the model output ONLY, never on the wrapper: the
277
+ // disclaimer's `../../README.md` and the selector's sibling
278
+ // `README.<lang>.md` links are already written for `docs/i18n/`.
279
+ const cleaned = rebaseReadmePaths(
280
+ convertHtmlComments(
281
+ stripStrayTrailingFence(sanitizeJsxAttributes(raw)),
282
+ ),
124
283
  );
125
284
  return `${disclaimer}\n\n${langSelector}\n\n---\n${rtlOpen}\n${cleaned}\n${rtlClose}`;
126
285
  },
@@ -25,15 +25,22 @@
25
25
  * `findPageError`), so this net is a strict superset of `mintlify validate`:
26
26
  * it catches both the frontmatter YAML class that fails `mintlify validate`
27
27
  * and the body-MDX class that `mintlify validate` lets through to deploy.
28
+ *
29
+ * It also covers a third class neither tool sees: image references whose target
30
+ * does not exist (`findBrokenAssetRefs`). Those are valid MDX and valid YAML —
31
+ * nothing errors, the reader just gets a broken image — which is how all 14
32
+ * translated READMEs shipped with every logo and the architecture GIF broken.
33
+ * See that function for the mechanism.
28
34
  */
29
- import { readdirSync, statSync, readFileSync } from "node:fs";
30
- import { dirname, join, relative } from "node:path";
35
+ import { readdirSync, statSync, readFileSync, existsSync } from "node:fs";
36
+ import { dirname, join, relative, resolve, posix } from "node:path";
31
37
  import { fileURLToPath } from "node:url";
32
38
  import { compile } from "@mdx-js/mdx";
33
39
  import YAML from "yaml";
34
40
 
35
41
  const __dirname = dirname(fileURLToPath(import.meta.url));
36
- const DOCS_DIR = join(__dirname, "..", "docs");
42
+ const ROOT_DIR = join(__dirname, "..");
43
+ const DOCS_DIR = join(ROOT_DIR, "docs");
37
44
 
38
45
  export interface MdxParseError {
39
46
  message: string;
@@ -178,34 +185,167 @@ export function collectMdxFiles(dir: string): string[] {
178
185
  return out;
179
186
  }
180
187
 
188
+ /** File extensions treated as bundled media rather than a link to a page. */
189
+ const ASSET_RE = /\.(?:png|jpe?g|gif|svg|webp|ico|mp4|webm)$/i;
190
+
191
+ export interface BrokenAssetRef {
192
+ /** The reference exactly as written in the page. */
193
+ ref: string;
194
+ /** Repo-relative path the reference resolves to, which does not exist. */
195
+ resolved: string;
196
+ /** 1-based line the reference appears on. */
197
+ line: number;
198
+ }
199
+
200
+ /**
201
+ * Report every local image reference on a page whose target does not exist on
202
+ * disk.
203
+ *
204
+ * Why this is a separate net from the MDX parse above: a broken image path is
205
+ * perfectly valid MDX and perfectly valid YAML, so `mintlify validate` and
206
+ * `findPageError` both pass it — it only surfaces as a missing image in a
207
+ * reader's browser, which nothing in CI was watching. That is exactly how all
208
+ * 14 translated READMEs shipped with every logo and the architecture GIF
209
+ * broken: the translator faithfully copies the root README's repo-root-relative
210
+ * `assets/logos/*.svg` into `docs/i18n/`, two directories deeper, where they
211
+ * resolve to nothing (GitHub 404, Mintlify S3 403). The auto-translation
212
+ * workflow regenerates these pages unattended, so only a deterministic check
213
+ * keeps that class from coming back.
214
+ *
215
+ * Resolution follows the two conventions in this repo:
216
+ * - A leading `/` is Mintlify site-absolute → resolve against `docs/`
217
+ * (`/agenteye/images/x.png` → `docs/agenteye/images/x.png`).
218
+ * - Anything else is relative to the page's own directory, the way GitHub and
219
+ * Mintlify both resolve it.
220
+ *
221
+ * Skipped: absolute URLs and any other scheme (`https:`, `mailto:`, `data:`),
222
+ * protocol-relative `//`, bare anchors, and non-asset references — a link to a
223
+ * `.md`/`.mdx` page or a bare doc slug is nav, already covered by
224
+ * `mintlify validate`, and would false-positive on extensionless Mintlify
225
+ * routes.
226
+ */
227
+ export function findBrokenAssetRefs(
228
+ file: string,
229
+ source: string,
230
+ ): BrokenAssetRef[] {
231
+ const pageDir = dirname(file);
232
+ const out: BrokenAssetRef[] = [];
233
+ const seen = new Set<string>();
234
+
235
+ const lines = source.split("\n");
236
+ for (let i = 0; i < lines.length; i++) {
237
+ const refs = [
238
+ ...lines[i].matchAll(/\]\(([^)\s]+)/g),
239
+ ...lines[i].matchAll(/(?:src|href)=["']([^"']+)["']/g),
240
+ ].map((m) => m[1]);
241
+
242
+ // `srcset` carries a comma-separated candidate list, each optionally
243
+ // followed by a density/width descriptor (`logo.svg 2x`). The README's
244
+ // logo table pairs every <img src> with a dark-mode <source srcset>, so
245
+ // extracting only `src` would pass a table half-broken — and only for
246
+ // dark-theme readers, the half least likely to be caught by eye.
247
+ for (const m of lines[i].matchAll(/srcset=["']([^"']+)["']/g)) {
248
+ for (const candidate of m[1].split(",")) {
249
+ const url = candidate.trim().split(/\s+/)[0];
250
+ if (url) refs.push(url);
251
+ }
252
+ }
253
+
254
+ for (const ref of refs) {
255
+ if (/^[a-z][a-z0-9+.-]*:/i.test(ref)) continue; // https:, mailto:, data:
256
+ if (ref.startsWith("//") || ref.startsWith("#")) continue;
257
+ // Strip the query/fragment before testing the extension so a versioned
258
+ // `x.png?v=2` is still recognised as an asset.
259
+ const path = ref.split(/[?#]/)[0];
260
+ if (!ASSET_RE.test(path)) continue;
261
+
262
+ const target = path.startsWith("/")
263
+ ? join(DOCS_DIR, path.slice(1))
264
+ : resolve(pageDir, path);
265
+ if (existsSync(target)) continue;
266
+
267
+ const key = `${i}:${ref}`;
268
+ if (seen.has(key)) continue;
269
+ seen.add(key);
270
+ out.push({
271
+ ref,
272
+ resolved: relative(ROOT_DIR, target).split(/[\\/]/).join(posix.sep),
273
+ line: i + 1,
274
+ });
275
+ }
276
+ }
277
+ return out;
278
+ }
279
+
181
280
  async function main(): Promise<void> {
182
281
  const files = collectMdxFiles(DOCS_DIR).sort();
183
- const failures: Array<{ file: string; error: MdxParseError }> = [];
282
+ const failures: Array<{
283
+ file: string;
284
+ error: MdxParseError;
285
+ kind: "MDX parse error" | "Broken image";
286
+ }> = [];
287
+
288
+ const collectAssetFailures = (file: string, source: string): void => {
289
+ for (const broken of findBrokenAssetRefs(file, source)) {
290
+ failures.push({
291
+ file: relative(process.cwd(), file),
292
+ kind: "Broken image",
293
+ error: {
294
+ message:
295
+ `Image \`${broken.ref}\` does not exist — it resolves to ` +
296
+ `\`${broken.resolved}\`. Paths are relative to the page's own ` +
297
+ "directory (a leading `/` is relative to `docs/`), so a path " +
298
+ "copied from a file at a different depth has to be re-pointed.",
299
+ line: broken.line,
300
+ },
301
+ });
302
+ }
303
+ };
184
304
 
185
305
  for (const file of files) {
186
- const error = await findPageError(readFileSync(file, "utf-8"));
187
- if (error) failures.push({ file: relative(process.cwd(), file), error });
306
+ const source = readFileSync(file, "utf-8");
307
+ const error = await findPageError(source);
308
+ if (error)
309
+ failures.push({
310
+ file: relative(process.cwd(), file),
311
+ error,
312
+ kind: "MDX parse error",
313
+ });
314
+
315
+ // A page can carry a broken image and still parse, so collect these
316
+ // independently rather than only when the parse succeeded.
317
+ collectAssetFailures(file, source);
318
+ }
319
+
320
+ // The root README is not a docs page (it is GitHub-only, and its HTML
321
+ // comments are illegal MDX, so findPageError would reject it) — but it IS
322
+ // the source every docs/i18n/README.<lang>.md is translated from. A bad
323
+ // image path here propagates into 14 files as an absolute raw URL, which
324
+ // findBrokenAssetRefs deliberately does not follow. Check it at the source.
325
+ const rootReadme = join(ROOT_DIR, "README.md");
326
+ if (existsSync(rootReadme)) {
327
+ collectAssetFailures(rootReadme, readFileSync(rootReadme, "utf-8"));
188
328
  }
189
329
 
190
330
  if (failures.length === 0) {
191
- console.log(`✓ ${files.length} MDX page(s) parsed cleanly`);
331
+ console.log(
332
+ `✓ ${files.length} MDX page(s) parsed cleanly with no broken images`,
333
+ );
192
334
  return;
193
335
  }
194
336
 
195
- console.error(
196
- `✗ ${failures.length} of ${files.length} MDX page(s) failed to parse:\n`,
197
- );
198
- for (const { file, error } of failures) {
337
+ console.error(`✗ ${failures.length} problem(s) in ${files.length} page(s):\n`);
338
+ for (const { file, error, kind } of failures) {
199
339
  const pos = error.line
200
340
  ? `:${error.line}${error.column ? `:${error.column}` : ""}`
201
341
  : "";
202
- console.error(` ${file}${pos}\n ${error.message}\n`);
342
+ console.error(` ${file}${pos}\n ${kind}: ${error.message}\n`);
203
343
  // GitHub Actions inline annotation.
204
344
  const loc =
205
345
  (error.line ? `,line=${error.line}` : "") +
206
346
  (error.column ? `,col=${error.column}` : "");
207
347
  console.log(
208
- `::error file=${encodeAnnotation(file)}${loc}::MDX parse error: ${encodeAnnotation(error.message)}`,
348
+ `::error file=${encodeAnnotation(file)}${loc}::${kind}: ${encodeAnnotation(error.message)}`,
209
349
  );
210
350
  }
211
351
  process.exitCode = 1;