failproofai 0.0.6-beta.3 → 0.0.6-beta.4

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 (509) hide show
  1. package/.next/standalone/.next/BUILD_ID +1 -1
  2. package/.next/standalone/.next/build-manifest.json +3 -3
  3. package/.next/standalone/.next/prerender-manifest.json +3 -3
  4. package/.next/standalone/.next/required-server-files.json +7 -1
  5. package/.next/standalone/.next/server/app/_global-error/page/server-reference-manifest.json +1 -1
  6. package/.next/standalone/.next/server/app/_global-error/page.js.nft.json +1 -1
  7. package/.next/standalone/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
  8. package/.next/standalone/.next/server/app/_global-error.html +1 -1
  9. package/.next/standalone/.next/server/app/_global-error.rsc +7 -7
  10. package/.next/standalone/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +2 -2
  11. package/.next/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +7 -7
  12. package/.next/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +3 -3
  13. package/.next/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +3 -3
  14. package/.next/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  15. package/.next/standalone/.next/server/app/_not-found/page/server-reference-manifest.json +1 -1
  16. package/.next/standalone/.next/server/app/_not-found/page.js.nft.json +1 -1
  17. package/.next/standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  18. package/.next/standalone/.next/server/app/_not-found.html +2 -2
  19. package/.next/standalone/.next/server/app/_not-found.rsc +17 -17
  20. package/.next/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +17 -17
  21. package/.next/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +4 -4
  22. package/.next/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +11 -11
  23. package/.next/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +2 -2
  24. package/.next/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +3 -3
  25. package/.next/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
  26. package/.next/standalone/.next/server/app/index.html +1 -1
  27. package/.next/standalone/.next/server/app/index.rsc +16 -16
  28. package/.next/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +2 -2
  29. package/.next/standalone/.next/server/app/index.segments/_full.segment.rsc +16 -16
  30. package/.next/standalone/.next/server/app/index.segments/_head.segment.rsc +4 -4
  31. package/.next/standalone/.next/server/app/index.segments/_index.segment.rsc +11 -11
  32. package/.next/standalone/.next/server/app/index.segments/_tree.segment.rsc +2 -2
  33. package/.next/standalone/.next/server/app/page/server-reference-manifest.json +1 -1
  34. package/.next/standalone/.next/server/app/page.js.nft.json +1 -1
  35. package/.next/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
  36. package/.next/standalone/.next/server/app/policies/page/server-reference-manifest.json +8 -8
  37. package/.next/standalone/.next/server/app/policies/page.js.nft.json +1 -1
  38. package/.next/standalone/.next/server/app/policies/page_client-reference-manifest.js +1 -1
  39. package/.next/standalone/.next/server/app/project/[name]/page/server-reference-manifest.json +1 -1
  40. package/.next/standalone/.next/server/app/project/[name]/page.js.nft.json +1 -1
  41. package/.next/standalone/.next/server/app/project/[name]/page_client-reference-manifest.js +1 -1
  42. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page/react-loadable-manifest.json +2 -2
  43. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page/server-reference-manifest.json +2 -2
  44. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page.js.nft.json +1 -1
  45. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page_client-reference-manifest.js +1 -1
  46. package/.next/standalone/.next/server/app/projects/page/server-reference-manifest.json +1 -1
  47. package/.next/standalone/.next/server/app/projects/page.js.nft.json +1 -1
  48. package/.next/standalone/.next/server/app/projects/page_client-reference-manifest.js +1 -1
  49. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0g72weg._.js +1 -1
  50. package/.next/standalone/.next/server/chunks/package_json_[json]_cjs_0z7w.hh._.js +1 -1
  51. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__092s1ta._.js +2 -2
  52. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__09icjsf._.js +2 -2
  53. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0g.lg8b._.js +2 -2
  54. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0h..k-e._.js +2 -2
  55. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0okos0k._.js +2 -2
  56. package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__0kyh86x._.js → [root-of-the-server]__0om-5pe._.js} +2 -2
  57. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0w6l33k._.js +1 -1
  58. package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__096k.db._.js → [root-of-the-server]__111.vxi._.js} +2 -2
  59. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__11pa2ra._.js +2 -2
  60. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__12t-wym._.js +2 -2
  61. package/.next/standalone/.next/server/chunks/ssr/_10lm7or._.js +2 -2
  62. package/.next/standalone/.next/server/chunks/ssr/app_global-error_tsx_0xerkr6._.js +1 -1
  63. package/.next/standalone/.next/server/chunks/ssr/app_policies_hooks-client_tsx_0q-m0y-._.js +1 -1
  64. package/.next/standalone/.next/server/chunks/ssr/node_modules_posthog-node_dist_entrypoints_index_node_mjs_0mebn66._.js +1 -1
  65. package/.next/standalone/.next/server/middleware-build-manifest.js +3 -3
  66. package/.next/standalone/.next/server/pages/404.html +2 -2
  67. package/.next/standalone/.next/server/pages/500.html +1 -1
  68. package/.next/standalone/.next/server/server-reference-manifest.js +1 -1
  69. package/.next/standalone/.next/server/server-reference-manifest.json +9 -9
  70. package/.next/standalone/.next/static/chunks/{01pmw1-asbek~.js → 02dqjyv6_9mhq.js} +2 -2
  71. package/.next/standalone/.next/static/chunks/{051m32nx~n5yr.js → 070orfsl6.xal.js} +1 -1
  72. package/.next/standalone/.next/static/chunks/0mir9jdxn35~s.css +1 -0
  73. package/.next/standalone/.next/static/chunks/{0mazj-p-~2kc6.js → 0o547jv-k_k35.js} +1 -1
  74. package/.next/standalone/.next/static/chunks/0pk2h2.mjxy.m.js +6 -0
  75. package/.next/standalone/.next/static/chunks/{0l-mu4okl-cj1.js → 0rcwkbh24w38b.js} +1 -1
  76. package/.next/standalone/.next/static/chunks/{0-dm_9a6nsc2l.js → 140xx_tfr~lm_.js} +1 -1
  77. package/.next/standalone/.next/static/chunks/{156zca6aewyr-.js → 169_e4dq~1~b6.js} +1 -1
  78. package/.next/standalone/.next/static/chunks/{0a-yctdwn368y.js → 17ne4p.1sw1jy.js} +1 -1
  79. package/.next/standalone/next.config.ts +6 -0
  80. package/.next/standalone/package.json +2 -2
  81. package/.next/standalone/server.js +1 -1
  82. package/dist/cli.mjs +2 -2
  83. package/package.json +2 -2
  84. package/scripts/prune-standalone.mjs +128 -0
  85. package/.next/standalone/.claude/settings.json +0 -316
  86. package/.next/standalone/.failproofai/policies/review-policies.mjs +0 -113
  87. package/.next/standalone/.failproofai/policies/workflow-policies.mjs +0 -63
  88. package/.next/standalone/.failproofai/policies-config.json +0 -39
  89. package/.next/standalone/.next/static/chunks/0gu_a.a80ritd.css +0 -1
  90. package/.next/standalone/.next/static/chunks/0qakntsrpc~1j.js +0 -6
  91. package/.next/standalone/AGENTS.md +0 -80
  92. package/.next/standalone/CHANGELOG.md +0 -158
  93. package/.next/standalone/CLAUDE.md +0 -165
  94. package/.next/standalone/CONTRIBUTING.md +0 -76
  95. package/.next/standalone/Dockerfile.docs +0 -12
  96. package/.next/standalone/LICENSE +0 -42
  97. package/.next/standalone/README.md +0 -301
  98. package/.next/standalone/bin/failproofai.mjs +0 -439
  99. package/.next/standalone/bun.lock +0 -1119
  100. package/.next/standalone/components.json +0 -23
  101. package/.next/standalone/dist/cli.mjs +0 -4696
  102. package/.next/standalone/dist/index.js +0 -80
  103. package/.next/standalone/docs/ar/architecture.mdx +0 -334
  104. package/.next/standalone/docs/ar/built-in-policies.mdx +0 -574
  105. package/.next/standalone/docs/ar/cli/dashboard.mdx +0 -28
  106. package/.next/standalone/docs/ar/cli/environment-variables.mdx +0 -34
  107. package/.next/standalone/docs/ar/cli/hook.mdx +0 -31
  108. package/.next/standalone/docs/ar/cli/install-policies.mdx +0 -48
  109. package/.next/standalone/docs/ar/cli/list-policies.mdx +0 -31
  110. package/.next/standalone/docs/ar/cli/remove-policies.mdx +0 -43
  111. package/.next/standalone/docs/ar/cli/version.mdx +0 -13
  112. package/.next/standalone/docs/ar/configuration.mdx +0 -223
  113. package/.next/standalone/docs/ar/custom-policies.mdx +0 -354
  114. package/.next/standalone/docs/ar/dashboard.mdx +0 -142
  115. package/.next/standalone/docs/ar/examples.mdx +0 -307
  116. package/.next/standalone/docs/ar/for-agents.mdx +0 -39
  117. package/.next/standalone/docs/ar/getting-started.mdx +0 -187
  118. package/.next/standalone/docs/ar/introduction.mdx +0 -58
  119. package/.next/standalone/docs/ar/package-aliases.mdx +0 -82
  120. package/.next/standalone/docs/ar/testing.mdx +0 -261
  121. package/.next/standalone/docs/architecture.mdx +0 -332
  122. package/.next/standalone/docs/built-in-policies.mdx +0 -574
  123. package/.next/standalone/docs/cli/dashboard.mdx +0 -28
  124. package/.next/standalone/docs/cli/environment-variables.mdx +0 -34
  125. package/.next/standalone/docs/cli/hook.mdx +0 -30
  126. package/.next/standalone/docs/cli/install-policies.mdx +0 -47
  127. package/.next/standalone/docs/cli/list-policies.mdx +0 -31
  128. package/.next/standalone/docs/cli/remove-policies.mdx +0 -43
  129. package/.next/standalone/docs/cli/version.mdx +0 -12
  130. package/.next/standalone/docs/configuration.mdx +0 -222
  131. package/.next/standalone/docs/custom-policies.mdx +0 -353
  132. package/.next/standalone/docs/dashboard.mdx +0 -142
  133. package/.next/standalone/docs/de/architecture.mdx +0 -332
  134. package/.next/standalone/docs/de/built-in-policies.mdx +0 -574
  135. package/.next/standalone/docs/de/cli/dashboard.mdx +0 -28
  136. package/.next/standalone/docs/de/cli/environment-variables.mdx +0 -34
  137. package/.next/standalone/docs/de/cli/hook.mdx +0 -30
  138. package/.next/standalone/docs/de/cli/install-policies.mdx +0 -47
  139. package/.next/standalone/docs/de/cli/list-policies.mdx +0 -31
  140. package/.next/standalone/docs/de/cli/remove-policies.mdx +0 -43
  141. package/.next/standalone/docs/de/cli/version.mdx +0 -12
  142. package/.next/standalone/docs/de/configuration.mdx +0 -222
  143. package/.next/standalone/docs/de/custom-policies.mdx +0 -353
  144. package/.next/standalone/docs/de/dashboard.mdx +0 -142
  145. package/.next/standalone/docs/de/examples.mdx +0 -307
  146. package/.next/standalone/docs/de/for-agents.mdx +0 -38
  147. package/.next/standalone/docs/de/getting-started.mdx +0 -186
  148. package/.next/standalone/docs/de/introduction.mdx +0 -57
  149. package/.next/standalone/docs/de/package-aliases.mdx +0 -82
  150. package/.next/standalone/docs/de/testing.mdx +0 -260
  151. package/.next/standalone/docs/docs.json +0 -1002
  152. package/.next/standalone/docs/es/architecture.mdx +0 -332
  153. package/.next/standalone/docs/es/built-in-policies.mdx +0 -574
  154. package/.next/standalone/docs/es/cli/dashboard.mdx +0 -28
  155. package/.next/standalone/docs/es/cli/environment-variables.mdx +0 -34
  156. package/.next/standalone/docs/es/cli/hook.mdx +0 -30
  157. package/.next/standalone/docs/es/cli/install-policies.mdx +0 -47
  158. package/.next/standalone/docs/es/cli/list-policies.mdx +0 -31
  159. package/.next/standalone/docs/es/cli/remove-policies.mdx +0 -43
  160. package/.next/standalone/docs/es/cli/version.mdx +0 -12
  161. package/.next/standalone/docs/es/configuration.mdx +0 -222
  162. package/.next/standalone/docs/es/custom-policies.mdx +0 -353
  163. package/.next/standalone/docs/es/dashboard.mdx +0 -142
  164. package/.next/standalone/docs/es/examples.mdx +0 -307
  165. package/.next/standalone/docs/es/for-agents.mdx +0 -38
  166. package/.next/standalone/docs/es/getting-started.mdx +0 -186
  167. package/.next/standalone/docs/es/introduction.mdx +0 -57
  168. package/.next/standalone/docs/es/package-aliases.mdx +0 -82
  169. package/.next/standalone/docs/es/testing.mdx +0 -260
  170. package/.next/standalone/docs/examples.mdx +0 -307
  171. package/.next/standalone/docs/favicon.ico +0 -0
  172. package/.next/standalone/docs/for-agents.mdx +0 -38
  173. package/.next/standalone/docs/fr/architecture.mdx +0 -332
  174. package/.next/standalone/docs/fr/built-in-policies.mdx +0 -574
  175. package/.next/standalone/docs/fr/cli/dashboard.mdx +0 -28
  176. package/.next/standalone/docs/fr/cli/environment-variables.mdx +0 -34
  177. package/.next/standalone/docs/fr/cli/hook.mdx +0 -30
  178. package/.next/standalone/docs/fr/cli/install-policies.mdx +0 -47
  179. package/.next/standalone/docs/fr/cli/list-policies.mdx +0 -31
  180. package/.next/standalone/docs/fr/cli/remove-policies.mdx +0 -43
  181. package/.next/standalone/docs/fr/cli/version.mdx +0 -12
  182. package/.next/standalone/docs/fr/configuration.mdx +0 -222
  183. package/.next/standalone/docs/fr/custom-policies.mdx +0 -353
  184. package/.next/standalone/docs/fr/dashboard.mdx +0 -142
  185. package/.next/standalone/docs/fr/examples.mdx +0 -307
  186. package/.next/standalone/docs/fr/for-agents.mdx +0 -38
  187. package/.next/standalone/docs/fr/getting-started.mdx +0 -186
  188. package/.next/standalone/docs/fr/introduction.mdx +0 -57
  189. package/.next/standalone/docs/fr/package-aliases.mdx +0 -82
  190. package/.next/standalone/docs/fr/testing.mdx +0 -260
  191. package/.next/standalone/docs/getting-started.mdx +0 -186
  192. package/.next/standalone/docs/he/architecture.mdx +0 -333
  193. package/.next/standalone/docs/he/built-in-policies.mdx +0 -575
  194. package/.next/standalone/docs/he/cli/dashboard.mdx +0 -28
  195. package/.next/standalone/docs/he/cli/environment-variables.mdx +0 -34
  196. package/.next/standalone/docs/he/cli/hook.mdx +0 -30
  197. package/.next/standalone/docs/he/cli/install-policies.mdx +0 -47
  198. package/.next/standalone/docs/he/cli/list-policies.mdx +0 -32
  199. package/.next/standalone/docs/he/cli/remove-policies.mdx +0 -43
  200. package/.next/standalone/docs/he/cli/version.mdx +0 -12
  201. package/.next/standalone/docs/he/configuration.mdx +0 -223
  202. package/.next/standalone/docs/he/custom-policies.mdx +0 -353
  203. package/.next/standalone/docs/he/dashboard.mdx +0 -142
  204. package/.next/standalone/docs/he/examples.mdx +0 -307
  205. package/.next/standalone/docs/he/for-agents.mdx +0 -38
  206. package/.next/standalone/docs/he/getting-started.mdx +0 -186
  207. package/.next/standalone/docs/he/introduction.mdx +0 -57
  208. package/.next/standalone/docs/he/package-aliases.mdx +0 -82
  209. package/.next/standalone/docs/he/testing.mdx +0 -260
  210. package/.next/standalone/docs/hi/architecture.mdx +0 -334
  211. package/.next/standalone/docs/hi/built-in-policies.mdx +0 -572
  212. package/.next/standalone/docs/hi/cli/dashboard.mdx +0 -28
  213. package/.next/standalone/docs/hi/cli/environment-variables.mdx +0 -34
  214. package/.next/standalone/docs/hi/cli/hook.mdx +0 -30
  215. package/.next/standalone/docs/hi/cli/install-policies.mdx +0 -47
  216. package/.next/standalone/docs/hi/cli/list-policies.mdx +0 -31
  217. package/.next/standalone/docs/hi/cli/remove-policies.mdx +0 -43
  218. package/.next/standalone/docs/hi/cli/version.mdx +0 -12
  219. package/.next/standalone/docs/hi/configuration.mdx +0 -222
  220. package/.next/standalone/docs/hi/custom-policies.mdx +0 -354
  221. package/.next/standalone/docs/hi/dashboard.mdx +0 -142
  222. package/.next/standalone/docs/hi/examples.mdx +0 -309
  223. package/.next/standalone/docs/hi/for-agents.mdx +0 -38
  224. package/.next/standalone/docs/hi/getting-started.mdx +0 -187
  225. package/.next/standalone/docs/hi/introduction.mdx +0 -57
  226. package/.next/standalone/docs/hi/package-aliases.mdx +0 -82
  227. package/.next/standalone/docs/hi/testing.mdx +0 -260
  228. package/.next/standalone/docs/i18n/README.ar.md +0 -312
  229. package/.next/standalone/docs/i18n/README.de.md +0 -307
  230. package/.next/standalone/docs/i18n/README.es.md +0 -307
  231. package/.next/standalone/docs/i18n/README.fr.md +0 -307
  232. package/.next/standalone/docs/i18n/README.he.md +0 -312
  233. package/.next/standalone/docs/i18n/README.hi.md +0 -307
  234. package/.next/standalone/docs/i18n/README.it.md +0 -307
  235. package/.next/standalone/docs/i18n/README.ja.md +0 -307
  236. package/.next/standalone/docs/i18n/README.ko.md +0 -307
  237. package/.next/standalone/docs/i18n/README.pt-br.md +0 -307
  238. package/.next/standalone/docs/i18n/README.ru.md +0 -308
  239. package/.next/standalone/docs/i18n/README.tr.md +0 -307
  240. package/.next/standalone/docs/i18n/README.vi.md +0 -307
  241. package/.next/standalone/docs/i18n/README.zh.md +0 -307
  242. package/.next/standalone/docs/introduction.mdx +0 -57
  243. package/.next/standalone/docs/it/architecture.mdx +0 -334
  244. package/.next/standalone/docs/it/built-in-policies.mdx +0 -574
  245. package/.next/standalone/docs/it/cli/dashboard.mdx +0 -28
  246. package/.next/standalone/docs/it/cli/environment-variables.mdx +0 -34
  247. package/.next/standalone/docs/it/cli/hook.mdx +0 -30
  248. package/.next/standalone/docs/it/cli/install-policies.mdx +0 -47
  249. package/.next/standalone/docs/it/cli/list-policies.mdx +0 -31
  250. package/.next/standalone/docs/it/cli/remove-policies.mdx +0 -43
  251. package/.next/standalone/docs/it/cli/version.mdx +0 -12
  252. package/.next/standalone/docs/it/configuration.mdx +0 -222
  253. package/.next/standalone/docs/it/custom-policies.mdx +0 -353
  254. package/.next/standalone/docs/it/dashboard.mdx +0 -142
  255. package/.next/standalone/docs/it/examples.mdx +0 -307
  256. package/.next/standalone/docs/it/for-agents.mdx +0 -38
  257. package/.next/standalone/docs/it/getting-started.mdx +0 -186
  258. package/.next/standalone/docs/it/introduction.mdx +0 -57
  259. package/.next/standalone/docs/it/package-aliases.mdx +0 -82
  260. package/.next/standalone/docs/it/testing.mdx +0 -260
  261. package/.next/standalone/docs/ja/architecture.mdx +0 -332
  262. package/.next/standalone/docs/ja/built-in-policies.mdx +0 -572
  263. package/.next/standalone/docs/ja/cli/dashboard.mdx +0 -28
  264. package/.next/standalone/docs/ja/cli/environment-variables.mdx +0 -34
  265. package/.next/standalone/docs/ja/cli/hook.mdx +0 -30
  266. package/.next/standalone/docs/ja/cli/install-policies.mdx +0 -47
  267. package/.next/standalone/docs/ja/cli/list-policies.mdx +0 -31
  268. package/.next/standalone/docs/ja/cli/remove-policies.mdx +0 -43
  269. package/.next/standalone/docs/ja/cli/version.mdx +0 -12
  270. package/.next/standalone/docs/ja/configuration.mdx +0 -222
  271. package/.next/standalone/docs/ja/custom-policies.mdx +0 -353
  272. package/.next/standalone/docs/ja/dashboard.mdx +0 -142
  273. package/.next/standalone/docs/ja/examples.mdx +0 -307
  274. package/.next/standalone/docs/ja/for-agents.mdx +0 -38
  275. package/.next/standalone/docs/ja/getting-started.mdx +0 -186
  276. package/.next/standalone/docs/ja/introduction.mdx +0 -57
  277. package/.next/standalone/docs/ja/package-aliases.mdx +0 -82
  278. package/.next/standalone/docs/ja/testing.mdx +0 -260
  279. package/.next/standalone/docs/ko/architecture.mdx +0 -332
  280. package/.next/standalone/docs/ko/built-in-policies.mdx +0 -572
  281. package/.next/standalone/docs/ko/cli/dashboard.mdx +0 -28
  282. package/.next/standalone/docs/ko/cli/environment-variables.mdx +0 -34
  283. package/.next/standalone/docs/ko/cli/hook.mdx +0 -30
  284. package/.next/standalone/docs/ko/cli/install-policies.mdx +0 -47
  285. package/.next/standalone/docs/ko/cli/list-policies.mdx +0 -31
  286. package/.next/standalone/docs/ko/cli/remove-policies.mdx +0 -43
  287. package/.next/standalone/docs/ko/cli/version.mdx +0 -12
  288. package/.next/standalone/docs/ko/configuration.mdx +0 -222
  289. package/.next/standalone/docs/ko/custom-policies.mdx +0 -353
  290. package/.next/standalone/docs/ko/dashboard.mdx +0 -142
  291. package/.next/standalone/docs/ko/examples.mdx +0 -307
  292. package/.next/standalone/docs/ko/for-agents.mdx +0 -38
  293. package/.next/standalone/docs/ko/getting-started.mdx +0 -186
  294. package/.next/standalone/docs/ko/introduction.mdx +0 -57
  295. package/.next/standalone/docs/ko/package-aliases.mdx +0 -82
  296. package/.next/standalone/docs/ko/testing.mdx +0 -260
  297. package/.next/standalone/docs/logo/dark.svg +0 -21
  298. package/.next/standalone/docs/logo/exosphere-dark.png +0 -0
  299. package/.next/standalone/docs/logo/exosphere-light.png +0 -0
  300. package/.next/standalone/docs/logo/light.svg +0 -21
  301. package/.next/standalone/docs/package-aliases.mdx +0 -82
  302. package/.next/standalone/docs/pt-br/architecture.mdx +0 -332
  303. package/.next/standalone/docs/pt-br/built-in-policies.mdx +0 -574
  304. package/.next/standalone/docs/pt-br/cli/dashboard.mdx +0 -28
  305. package/.next/standalone/docs/pt-br/cli/environment-variables.mdx +0 -34
  306. package/.next/standalone/docs/pt-br/cli/hook.mdx +0 -30
  307. package/.next/standalone/docs/pt-br/cli/install-policies.mdx +0 -47
  308. package/.next/standalone/docs/pt-br/cli/list-policies.mdx +0 -31
  309. package/.next/standalone/docs/pt-br/cli/remove-policies.mdx +0 -43
  310. package/.next/standalone/docs/pt-br/cli/version.mdx +0 -12
  311. package/.next/standalone/docs/pt-br/configuration.mdx +0 -222
  312. package/.next/standalone/docs/pt-br/custom-policies.mdx +0 -353
  313. package/.next/standalone/docs/pt-br/dashboard.mdx +0 -142
  314. package/.next/standalone/docs/pt-br/examples.mdx +0 -307
  315. package/.next/standalone/docs/pt-br/for-agents.mdx +0 -38
  316. package/.next/standalone/docs/pt-br/getting-started.mdx +0 -186
  317. package/.next/standalone/docs/pt-br/introduction.mdx +0 -57
  318. package/.next/standalone/docs/pt-br/package-aliases.mdx +0 -82
  319. package/.next/standalone/docs/pt-br/testing.mdx +0 -260
  320. package/.next/standalone/docs/ru/architecture.mdx +0 -333
  321. package/.next/standalone/docs/ru/built-in-policies.mdx +0 -574
  322. package/.next/standalone/docs/ru/cli/dashboard.mdx +0 -28
  323. package/.next/standalone/docs/ru/cli/environment-variables.mdx +0 -34
  324. package/.next/standalone/docs/ru/cli/hook.mdx +0 -30
  325. package/.next/standalone/docs/ru/cli/install-policies.mdx +0 -48
  326. package/.next/standalone/docs/ru/cli/list-policies.mdx +0 -32
  327. package/.next/standalone/docs/ru/cli/remove-policies.mdx +0 -43
  328. package/.next/standalone/docs/ru/cli/version.mdx +0 -12
  329. package/.next/standalone/docs/ru/configuration.mdx +0 -222
  330. package/.next/standalone/docs/ru/custom-policies.mdx +0 -354
  331. package/.next/standalone/docs/ru/dashboard.mdx +0 -142
  332. package/.next/standalone/docs/ru/examples.mdx +0 -309
  333. package/.next/standalone/docs/ru/for-agents.mdx +0 -38
  334. package/.next/standalone/docs/ru/getting-started.mdx +0 -186
  335. package/.next/standalone/docs/ru/introduction.mdx +0 -57
  336. package/.next/standalone/docs/ru/package-aliases.mdx +0 -82
  337. package/.next/standalone/docs/ru/testing.mdx +0 -260
  338. package/.next/standalone/docs/testing.mdx +0 -260
  339. package/.next/standalone/docs/tr/architecture.mdx +0 -333
  340. package/.next/standalone/docs/tr/built-in-policies.mdx +0 -574
  341. package/.next/standalone/docs/tr/cli/dashboard.mdx +0 -28
  342. package/.next/standalone/docs/tr/cli/environment-variables.mdx +0 -34
  343. package/.next/standalone/docs/tr/cli/hook.mdx +0 -30
  344. package/.next/standalone/docs/tr/cli/install-policies.mdx +0 -47
  345. package/.next/standalone/docs/tr/cli/list-policies.mdx +0 -31
  346. package/.next/standalone/docs/tr/cli/remove-policies.mdx +0 -44
  347. package/.next/standalone/docs/tr/cli/version.mdx +0 -12
  348. package/.next/standalone/docs/tr/configuration.mdx +0 -222
  349. package/.next/standalone/docs/tr/custom-policies.mdx +0 -353
  350. package/.next/standalone/docs/tr/dashboard.mdx +0 -142
  351. package/.next/standalone/docs/tr/examples.mdx +0 -308
  352. package/.next/standalone/docs/tr/for-agents.mdx +0 -38
  353. package/.next/standalone/docs/tr/getting-started.mdx +0 -186
  354. package/.next/standalone/docs/tr/introduction.mdx +0 -57
  355. package/.next/standalone/docs/tr/package-aliases.mdx +0 -82
  356. package/.next/standalone/docs/tr/testing.mdx +0 -260
  357. package/.next/standalone/docs/vi/architecture.mdx +0 -334
  358. package/.next/standalone/docs/vi/built-in-policies.mdx +0 -572
  359. package/.next/standalone/docs/vi/cli/dashboard.mdx +0 -28
  360. package/.next/standalone/docs/vi/cli/environment-variables.mdx +0 -34
  361. package/.next/standalone/docs/vi/cli/hook.mdx +0 -30
  362. package/.next/standalone/docs/vi/cli/install-policies.mdx +0 -47
  363. package/.next/standalone/docs/vi/cli/list-policies.mdx +0 -31
  364. package/.next/standalone/docs/vi/cli/remove-policies.mdx +0 -43
  365. package/.next/standalone/docs/vi/cli/version.mdx +0 -13
  366. package/.next/standalone/docs/vi/configuration.mdx +0 -222
  367. package/.next/standalone/docs/vi/custom-policies.mdx +0 -353
  368. package/.next/standalone/docs/vi/dashboard.mdx +0 -142
  369. package/.next/standalone/docs/vi/examples.mdx +0 -308
  370. package/.next/standalone/docs/vi/for-agents.mdx +0 -38
  371. package/.next/standalone/docs/vi/getting-started.mdx +0 -186
  372. package/.next/standalone/docs/vi/introduction.mdx +0 -57
  373. package/.next/standalone/docs/vi/package-aliases.mdx +0 -82
  374. package/.next/standalone/docs/vi/testing.mdx +0 -260
  375. package/.next/standalone/docs/zh/architecture.mdx +0 -332
  376. package/.next/standalone/docs/zh/built-in-policies.mdx +0 -570
  377. package/.next/standalone/docs/zh/cli/dashboard.mdx +0 -28
  378. package/.next/standalone/docs/zh/cli/environment-variables.mdx +0 -34
  379. package/.next/standalone/docs/zh/cli/hook.mdx +0 -30
  380. package/.next/standalone/docs/zh/cli/install-policies.mdx +0 -47
  381. package/.next/standalone/docs/zh/cli/list-policies.mdx +0 -31
  382. package/.next/standalone/docs/zh/cli/remove-policies.mdx +0 -43
  383. package/.next/standalone/docs/zh/cli/version.mdx +0 -12
  384. package/.next/standalone/docs/zh/configuration.mdx +0 -222
  385. package/.next/standalone/docs/zh/custom-policies.mdx +0 -353
  386. package/.next/standalone/docs/zh/dashboard.mdx +0 -142
  387. package/.next/standalone/docs/zh/examples.mdx +0 -307
  388. package/.next/standalone/docs/zh/for-agents.mdx +0 -38
  389. package/.next/standalone/docs/zh/getting-started.mdx +0 -186
  390. package/.next/standalone/docs/zh/introduction.mdx +0 -57
  391. package/.next/standalone/docs/zh/package-aliases.mdx +0 -82
  392. package/.next/standalone/docs/zh/testing.mdx +0 -260
  393. package/.next/standalone/eslint.config.mjs +0 -15
  394. package/.next/standalone/examples/convention-policies/security-policies.mjs +0 -40
  395. package/.next/standalone/examples/convention-policies/workflow-policies.mjs +0 -41
  396. package/.next/standalone/examples/policies-advanced/index.js +0 -103
  397. package/.next/standalone/examples/policies-advanced/utils.js +0 -35
  398. package/.next/standalone/examples/policies-basic.js +0 -77
  399. package/.next/standalone/examples/policies-notification.js +0 -104
  400. package/.next/standalone/node_modules/@img/colour/color.cjs +0 -1594
  401. package/.next/standalone/node_modules/@img/colour/index.cjs +0 -1
  402. package/.next/standalone/node_modules/@img/colour/package.json +0 -45
  403. package/.next/standalone/node_modules/@img/sharp-libvips-linux-x64/README.md +0 -46
  404. package/.next/standalone/node_modules/@img/sharp-libvips-linux-x64/lib/glib-2.0/include/glibconfig.h +0 -221
  405. package/.next/standalone/node_modules/@img/sharp-libvips-linux-x64/lib/index.js +0 -1
  406. package/.next/standalone/node_modules/@img/sharp-libvips-linux-x64/lib/libvips-cpp.so.8.17.3 +0 -0
  407. package/.next/standalone/node_modules/@img/sharp-libvips-linux-x64/package.json +0 -42
  408. package/.next/standalone/node_modules/@img/sharp-libvips-linux-x64/versions.json +0 -30
  409. package/.next/standalone/node_modules/@img/sharp-libvips-linuxmusl-x64/README.md +0 -46
  410. package/.next/standalone/node_modules/@img/sharp-libvips-linuxmusl-x64/lib/glib-2.0/include/glibconfig.h +0 -221
  411. package/.next/standalone/node_modules/@img/sharp-libvips-linuxmusl-x64/lib/index.js +0 -1
  412. package/.next/standalone/node_modules/@img/sharp-libvips-linuxmusl-x64/lib/libvips-cpp.so.8.17.3 +0 -0
  413. package/.next/standalone/node_modules/@img/sharp-libvips-linuxmusl-x64/package.json +0 -42
  414. package/.next/standalone/node_modules/@img/sharp-libvips-linuxmusl-x64/versions.json +0 -30
  415. package/.next/standalone/node_modules/@img/sharp-linux-x64/lib/sharp-linux-x64.node +0 -0
  416. package/.next/standalone/node_modules/@img/sharp-linux-x64/package.json +0 -46
  417. package/.next/standalone/node_modules/@img/sharp-linuxmusl-x64/lib/sharp-linuxmusl-x64.node +0 -0
  418. package/.next/standalone/node_modules/@img/sharp-linuxmusl-x64/package.json +0 -46
  419. package/.next/standalone/node_modules/detect-libc/lib/detect-libc.js +0 -313
  420. package/.next/standalone/node_modules/detect-libc/lib/elf.js +0 -39
  421. package/.next/standalone/node_modules/detect-libc/lib/filesystem.js +0 -51
  422. package/.next/standalone/node_modules/detect-libc/lib/process.js +0 -24
  423. package/.next/standalone/node_modules/detect-libc/package.json +0 -44
  424. package/.next/standalone/node_modules/sharp/lib/channel.js +0 -177
  425. package/.next/standalone/node_modules/sharp/lib/colour.js +0 -195
  426. package/.next/standalone/node_modules/sharp/lib/composite.js +0 -212
  427. package/.next/standalone/node_modules/sharp/lib/constructor.js +0 -499
  428. package/.next/standalone/node_modules/sharp/lib/index.js +0 -16
  429. package/.next/standalone/node_modules/sharp/lib/input.js +0 -809
  430. package/.next/standalone/node_modules/sharp/lib/is.js +0 -143
  431. package/.next/standalone/node_modules/sharp/lib/libvips.js +0 -207
  432. package/.next/standalone/node_modules/sharp/lib/operation.js +0 -1016
  433. package/.next/standalone/node_modules/sharp/lib/output.js +0 -1666
  434. package/.next/standalone/node_modules/sharp/lib/resize.js +0 -595
  435. package/.next/standalone/node_modules/sharp/lib/sharp.js +0 -121
  436. package/.next/standalone/node_modules/sharp/lib/utility.js +0 -291
  437. package/.next/standalone/node_modules/sharp/node_modules/semver/classes/comparator.js +0 -143
  438. package/.next/standalone/node_modules/sharp/node_modules/semver/classes/range.js +0 -557
  439. package/.next/standalone/node_modules/sharp/node_modules/semver/classes/semver.js +0 -333
  440. package/.next/standalone/node_modules/sharp/node_modules/semver/functions/cmp.js +0 -54
  441. package/.next/standalone/node_modules/sharp/node_modules/semver/functions/coerce.js +0 -62
  442. package/.next/standalone/node_modules/sharp/node_modules/semver/functions/compare.js +0 -7
  443. package/.next/standalone/node_modules/sharp/node_modules/semver/functions/eq.js +0 -5
  444. package/.next/standalone/node_modules/sharp/node_modules/semver/functions/gt.js +0 -5
  445. package/.next/standalone/node_modules/sharp/node_modules/semver/functions/gte.js +0 -5
  446. package/.next/standalone/node_modules/sharp/node_modules/semver/functions/lt.js +0 -5
  447. package/.next/standalone/node_modules/sharp/node_modules/semver/functions/lte.js +0 -5
  448. package/.next/standalone/node_modules/sharp/node_modules/semver/functions/neq.js +0 -5
  449. package/.next/standalone/node_modules/sharp/node_modules/semver/functions/parse.js +0 -18
  450. package/.next/standalone/node_modules/sharp/node_modules/semver/functions/satisfies.js +0 -12
  451. package/.next/standalone/node_modules/sharp/node_modules/semver/internal/constants.js +0 -37
  452. package/.next/standalone/node_modules/sharp/node_modules/semver/internal/debug.js +0 -11
  453. package/.next/standalone/node_modules/sharp/node_modules/semver/internal/identifiers.js +0 -29
  454. package/.next/standalone/node_modules/sharp/node_modules/semver/internal/lrucache.js +0 -42
  455. package/.next/standalone/node_modules/sharp/node_modules/semver/internal/parse-options.js +0 -17
  456. package/.next/standalone/node_modules/sharp/node_modules/semver/internal/re.js +0 -223
  457. package/.next/standalone/node_modules/sharp/node_modules/semver/package.json +0 -78
  458. package/.next/standalone/node_modules/sharp/package.json +0 -202
  459. package/.next/standalone/scripts/alias-proxy.js +0 -18
  460. package/.next/standalone/scripts/dev.ts +0 -3
  461. package/.next/standalone/scripts/install-telemetry.mjs +0 -108
  462. package/.next/standalone/scripts/launch.ts +0 -83
  463. package/.next/standalone/scripts/parse-script-args.ts +0 -87
  464. package/.next/standalone/scripts/postinstall.mjs +0 -121
  465. package/.next/standalone/scripts/preuninstall.mjs +0 -131
  466. package/.next/standalone/scripts/publish-aliases.mjs +0 -87
  467. package/.next/standalone/scripts/start.ts +0 -3
  468. package/.next/standalone/scripts/sync-hook-events-prompt.md +0 -60
  469. package/.next/standalone/scripts/translate-docs/cache.ts +0 -62
  470. package/.next/standalone/scripts/translate-docs/cli.ts +0 -357
  471. package/.next/standalone/scripts/translate-docs/config.ts +0 -248
  472. package/.next/standalone/scripts/translate-docs/mdx-translator.ts +0 -153
  473. package/.next/standalone/scripts/translate-docs/mintlify-nav.ts +0 -107
  474. package/.next/standalone/scripts/translate-docs/readme-translator.ts +0 -154
  475. package/.next/standalone/scripts/translate-docs/translator.ts +0 -68
  476. package/.next/standalone/scripts/translate-docs/types.ts +0 -43
  477. package/.next/standalone/src/auth/login.ts +0 -104
  478. package/.next/standalone/src/auth/logout.ts +0 -50
  479. package/.next/standalone/src/auth/token-store.ts +0 -64
  480. package/.next/standalone/src/cli-error.ts +0 -18
  481. package/.next/standalone/src/hooks/builtin-policies.ts +0 -1615
  482. package/.next/standalone/src/hooks/custom-hooks-loader.ts +0 -205
  483. package/.next/standalone/src/hooks/custom-hooks-registry.ts +0 -30
  484. package/.next/standalone/src/hooks/handler.ts +0 -222
  485. package/.next/standalone/src/hooks/hook-activity-store.ts +0 -349
  486. package/.next/standalone/src/hooks/hook-logger.ts +0 -133
  487. package/.next/standalone/src/hooks/hook-telemetry.ts +0 -43
  488. package/.next/standalone/src/hooks/hooks-config.ts +0 -166
  489. package/.next/standalone/src/hooks/install-prompt.ts +0 -357
  490. package/.next/standalone/src/hooks/llm-client.ts +0 -90
  491. package/.next/standalone/src/hooks/loader-utils.ts +0 -178
  492. package/.next/standalone/src/hooks/manager.ts +0 -692
  493. package/.next/standalone/src/hooks/policy-evaluator.ts +0 -224
  494. package/.next/standalone/src/hooks/policy-helpers.ts +0 -16
  495. package/.next/standalone/src/hooks/policy-registry.ts +0 -90
  496. package/.next/standalone/src/hooks/policy-types.ts +0 -77
  497. package/.next/standalone/src/hooks/types.ts +0 -63
  498. package/.next/standalone/src/index.ts +0 -19
  499. package/.next/standalone/src/posthog-key.ts +0 -5
  500. package/.next/standalone/src/relay/daemon.ts +0 -362
  501. package/.next/standalone/src/relay/pid.ts +0 -76
  502. package/.next/standalone/src/relay/queue.ts +0 -225
  503. package/.next/standalone/tailwind.config.ts +0 -11
  504. package/.next/standalone/tsconfig.json +0 -42
  505. package/.next/standalone/vitest.config.e2e.mts +0 -24
  506. package/.next/standalone/vitest.config.mts +0 -23
  507. /package/.next/standalone/.next/static/{r-wX0MuAfCjbhJm3phQc8 → wOkJXoch1UmRAmyIuKZWc}/_buildManifest.js +0 -0
  508. /package/.next/standalone/.next/static/{r-wX0MuAfCjbhJm3phQc8 → wOkJXoch1UmRAmyIuKZWc}/_clientMiddlewareManifest.js +0 -0
  509. /package/.next/standalone/.next/static/{r-wX0MuAfCjbhJm3phQc8 → wOkJXoch1UmRAmyIuKZWc}/_ssgManifest.js +0 -0
@@ -1,4696 +0,0 @@
1
- #!/usr/bin/env node
2
- var __defProp = Object.defineProperty;
3
- var __returnValue = (v) => v;
4
- function __exportSetter(name, newValue) {
5
- this[name] = __returnValue.bind(null, newValue);
6
- }
7
- var __export = (target, all) => {
8
- for (var name in all)
9
- __defProp(target, name, {
10
- get: all[name],
11
- enumerable: true,
12
- configurable: true,
13
- set: __exportSetter.bind(all, name)
14
- });
15
- };
16
- var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
17
-
18
- // src/hooks/hook-logger.ts
19
- import {
20
- appendFileSync,
21
- renameSync,
22
- mkdirSync,
23
- existsSync,
24
- statSync
25
- } from "node:fs";
26
- import { join } from "node:path";
27
- import { homedir } from "node:os";
28
- function ensureResolved() {
29
- if (resolved)
30
- return;
31
- resolved = true;
32
- const rawLevel = (process.env.FAILPROOFAI_LOG_LEVEL ?? "").toLowerCase();
33
- if (rawLevel === "info" || rawLevel === "warn" || rawLevel === "error") {
34
- currentLevel = rawLevel;
35
- }
36
- const rawFile = (process.env.FAILPROOFAI_HOOK_LOG_FILE ?? "").trim();
37
- if (rawFile) {
38
- fileLoggingEnabled = true;
39
- if (rawFile !== "1" && rawFile !== "true") {
40
- logDir = rawFile;
41
- }
42
- }
43
- }
44
- function shouldEmit(level) {
45
- ensureResolved();
46
- return LEVEL_ORDER[level] >= LEVEL_ORDER[currentLevel];
47
- }
48
- function emitStderr(label, msg) {
49
- process.stderr.write(`[failproofai:hook] ${label} ${msg}
50
- `);
51
- }
52
- function ensureLogDir() {
53
- if (!existsSync(logDir)) {
54
- mkdirSync(logDir, { recursive: true });
55
- }
56
- }
57
- function rotateIfNeeded(filePath) {
58
- try {
59
- const stats = statSync(filePath);
60
- if (stats.size >= MAX_FILE_SIZE) {
61
- const archiveName = `hooks-${Date.now()}.log`;
62
- renameSync(filePath, join(logDir, archiveName));
63
- }
64
- } catch {}
65
- }
66
- function appendToFile(label, msg) {
67
- if (!fileLoggingEnabled)
68
- return;
69
- try {
70
- ensureLogDir();
71
- const filePath = join(logDir, LOG_FILENAME);
72
- rotateIfNeeded(filePath);
73
- const timestamp = new Date().toISOString();
74
- const line = `[${timestamp}] ${label} ${msg}
75
- `;
76
- appendFileSync(filePath, line, "utf-8");
77
- } catch {}
78
- }
79
- function hookLogInfo(msg) {
80
- if (!shouldEmit("info"))
81
- return;
82
- emitStderr("INFO", msg);
83
- appendToFile("INFO", msg);
84
- }
85
- function hookLogWarn(msg) {
86
- if (!shouldEmit("warn"))
87
- return;
88
- emitStderr("WARN", msg);
89
- appendToFile("WARN", msg);
90
- }
91
- function hookLogError(msg) {
92
- if (!shouldEmit("error"))
93
- return;
94
- emitStderr("ERROR", msg);
95
- appendToFile("ERROR", msg);
96
- }
97
- var LEVEL_ORDER, MAX_FILE_SIZE, LOG_FILENAME = "hooks.log", DEFAULT_LOG_DIR, resolved = false, currentLevel = "warn", fileLoggingEnabled = false, logDir;
98
- var init_hook_logger = __esm(() => {
99
- LEVEL_ORDER = { info: 0, warn: 1, error: 2 };
100
- MAX_FILE_SIZE = 512 * 1024;
101
- DEFAULT_LOG_DIR = join(homedir(), ".failproofai", "logs");
102
- logDir = DEFAULT_LOG_DIR;
103
- });
104
-
105
- // src/hooks/hooks-config.ts
106
- import { readFileSync, writeFileSync, existsSync as existsSync2, mkdirSync as mkdirSync2 } from "node:fs";
107
- import { resolve, dirname } from "node:path";
108
- import { homedir as homedir2 } from "node:os";
109
- function readConfigAt(path) {
110
- if (!existsSync2(path))
111
- return {};
112
- try {
113
- const raw = readFileSync(path, "utf8");
114
- return JSON.parse(raw);
115
- } catch (err) {
116
- hookLogWarn(`failed to parse config at ${path}: ${err instanceof Error ? err.message : String(err)}`);
117
- return {};
118
- }
119
- }
120
- function readMergedHooksConfig(cwd) {
121
- const base = cwd ? resolve(cwd) : process.cwd();
122
- const projectPath = resolve(base, ".failproofai", "policies-config.json");
123
- const localPath = resolve(base, ".failproofai", "policies-config.local.json");
124
- const globalPath = resolve(homedir2(), ".failproofai", "policies-config.json");
125
- const project = readConfigAt(projectPath);
126
- const local = readConfigAt(localPath);
127
- const global_ = readConfigAt(globalPath);
128
- const enabledSet = new Set([
129
- ...project.enabledPolicies ?? [],
130
- ...local.enabledPolicies ?? [],
131
- ...global_.enabledPolicies ?? []
132
- ]);
133
- const mergedParams = {};
134
- for (const scope of [project, local, global_]) {
135
- if (!scope.policyParams)
136
- continue;
137
- for (const [policyName, params] of Object.entries(scope.policyParams)) {
138
- if (!(policyName in mergedParams)) {
139
- mergedParams[policyName] = params;
140
- }
141
- }
142
- }
143
- const customPoliciesPath = project.customPoliciesPath ?? local.customPoliciesPath ?? global_.customPoliciesPath;
144
- const llm = project.llm ?? local.llm ?? global_.llm;
145
- return {
146
- enabledPolicies: [...enabledSet],
147
- ...Object.keys(mergedParams).length > 0 ? { policyParams: mergedParams } : {},
148
- ...customPoliciesPath !== undefined ? { customPoliciesPath } : {},
149
- ...llm !== undefined ? { llm } : {}
150
- };
151
- }
152
- function getConfigPathForScope(scope, cwd) {
153
- const base = cwd ? resolve(cwd) : process.cwd();
154
- switch (scope) {
155
- case "user":
156
- return resolve(homedir2(), ".failproofai", "policies-config.json");
157
- case "project":
158
- return resolve(base, ".failproofai", "policies-config.json");
159
- case "local":
160
- return resolve(base, ".failproofai", "policies-config.local.json");
161
- }
162
- }
163
- function readScopedHooksConfig(scope, cwd) {
164
- const configPath = getConfigPathForScope(scope, cwd);
165
- if (!existsSync2(configPath)) {
166
- return { enabledPolicies: [] };
167
- }
168
- try {
169
- const raw = readFileSync(configPath, "utf8");
170
- return JSON.parse(raw);
171
- } catch (err) {
172
- hookLogWarn(`failed to parse config at ${configPath}: ${err instanceof Error ? err.message : String(err)}`);
173
- return { enabledPolicies: [] };
174
- }
175
- }
176
- function writeScopedHooksConfig(config, scope, cwd) {
177
- const configPath = getConfigPathForScope(scope, cwd);
178
- const dir = dirname(configPath);
179
- if (!existsSync2(dir)) {
180
- mkdirSync2(dir, { recursive: true });
181
- }
182
- writeFileSync(configPath, JSON.stringify(config, null, 2) + `
183
- `, "utf8");
184
- }
185
- var init_hooks_config = __esm(() => {
186
- init_hook_logger();
187
- });
188
-
189
- // src/hooks/policy-helpers.ts
190
- function allow(reason) {
191
- return reason ? { decision: "allow", reason } : { decision: "allow" };
192
- }
193
- function deny(reason) {
194
- return { decision: "deny", reason };
195
- }
196
- function instruct(reason) {
197
- return { decision: "instruct", reason };
198
- }
199
-
200
- // src/hooks/policy-registry.ts
201
- function getIndexCache() {
202
- return globalThis[INDEX_CACHE_KEY];
203
- }
204
- function setIndexCache(cache) {
205
- globalThis[INDEX_CACHE_KEY] = cache;
206
- }
207
- function getRegistry() {
208
- const g = globalThis;
209
- if (!g[REGISTRY_KEY]) {
210
- g[REGISTRY_KEY] = [];
211
- }
212
- return g[REGISTRY_KEY];
213
- }
214
- function registerPolicy(name, description, fn, match, priority = 0) {
215
- const registry = getRegistry();
216
- const idx = registry.findIndex((p) => p.name === name);
217
- const entry = { name, description, fn, match, priority };
218
- if (idx >= 0) {
219
- registry[idx] = entry;
220
- } else {
221
- registry.push(entry);
222
- }
223
- setIndexCache(null);
224
- }
225
- function getPoliciesForEvent(eventType, toolName) {
226
- let cache = getIndexCache();
227
- if (!cache) {
228
- cache = new Map;
229
- setIndexCache(cache);
230
- }
231
- const key = `${eventType}:${toolName ?? ""}`;
232
- const cached = cache.get(key);
233
- if (cached)
234
- return cached;
235
- const result = getRegistry().filter((p) => {
236
- if (p.match.events && p.match.events.length > 0) {
237
- if (!p.match.events.includes(eventType))
238
- return false;
239
- }
240
- if (p.match.toolNames && p.match.toolNames.length > 0) {
241
- if (!toolName || !p.match.toolNames.includes(toolName))
242
- return false;
243
- }
244
- return true;
245
- }).sort((a, b) => b.priority - a.priority);
246
- cache.set(key, result);
247
- return result;
248
- }
249
- function clearPolicies() {
250
- const g = globalThis;
251
- g[REGISTRY_KEY] = [];
252
- setIndexCache(null);
253
- }
254
- var REGISTRY_KEY = "__FAILPROOFAI_POLICY_REGISTRY__", INDEX_CACHE_KEY = "__FAILPROOFAI_POLICY_INDEX_CACHE__";
255
-
256
- // src/hooks/builtin-policies.ts
257
- import { resolve as resolve2, join as join2 } from "node:path";
258
- import { readFile, writeFile } from "node:fs/promises";
259
- import { execSync, execFileSync } from "node:child_process";
260
- import { homedir as homedir3 } from "node:os";
261
- function isClaudeInternalPath(resolved2) {
262
- const claudeDir = join2(homedir3(), ".claude");
263
- return resolved2 === claudeDir || resolved2.startsWith(claudeDir + "/");
264
- }
265
- function isClaudeSettingsFile(resolved2) {
266
- return /[\\/]\.claude[\\/]settings(?:\.[^/\\]+)?\.json$/.test(resolved2);
267
- }
268
- function getCommand(ctx) {
269
- return ctx.toolInput?.command ?? "";
270
- }
271
- function getFilePath(ctx) {
272
- return ctx.toolInput?.file_path ?? "";
273
- }
274
- function parseArgvTokens(cmd) {
275
- return cmd.trim().split(/\s+/).map((t) => t.replace(/^['"]|['"]$/g, ""));
276
- }
277
- function getCurrentBranch(cwd) {
278
- try {
279
- let branch = gitBranchCache.get(cwd);
280
- if (branch === undefined) {
281
- branch = execSync("git rev-parse --abbrev-ref HEAD", {
282
- cwd,
283
- encoding: "utf8",
284
- stdio: ["pipe", "pipe", "pipe"],
285
- timeout: 3000
286
- }).trim();
287
- gitBranchCache.set(cwd, branch);
288
- }
289
- return branch || null;
290
- } catch {
291
- return null;
292
- }
293
- }
294
- function getHeadSha(cwd) {
295
- try {
296
- const sha = execSync("git rev-parse HEAD", {
297
- cwd,
298
- encoding: "utf8",
299
- stdio: ["pipe", "pipe", "pipe"],
300
- timeout: 3000
301
- }).trim();
302
- return sha || null;
303
- } catch {
304
- return null;
305
- }
306
- }
307
- function getThirdPartyCheckRuns(cwd, sha) {
308
- try {
309
- const json = execFileSync("gh", [
310
- "api",
311
- `repos/{owner}/{repo}/commits/${sha}/check-runs`,
312
- "--jq",
313
- '.check_runs | map(select(.app.slug != "github-actions")) | map({name: .name, status: .status, conclusion: (.conclusion // "")})'
314
- ], {
315
- cwd,
316
- encoding: "utf8",
317
- stdio: ["pipe", "pipe", "pipe"],
318
- timeout: 15000
319
- }).trim();
320
- if (!json || json === "[]")
321
- return [];
322
- return JSON.parse(json);
323
- } catch {
324
- return [];
325
- }
326
- }
327
- function getCommitStatuses(cwd, sha) {
328
- try {
329
- const json = execFileSync("gh", [
330
- "api",
331
- `repos/{owner}/{repo}/commits/${sha}/statuses`,
332
- "--jq",
333
- "map({name: .context, state: .state}) | unique_by(.name)"
334
- ], {
335
- cwd,
336
- encoding: "utf8",
337
- stdio: ["pipe", "pipe", "pipe"],
338
- timeout: 15000
339
- }).trim();
340
- if (!json || json === "[]")
341
- return [];
342
- const statuses = JSON.parse(json);
343
- return statuses.map((s) => ({
344
- name: s.name,
345
- status: s.state === "pending" ? "in_progress" : "completed",
346
- conclusion: s.state === "pending" ? "" : s.state === "success" ? "success" : "failure"
347
- }));
348
- } catch {
349
- return [];
350
- }
351
- }
352
- function matchesAllowedPattern(cmd, pattern) {
353
- const cmdTokens = parseArgvTokens(cmd);
354
- const patTokens = parseArgvTokens(pattern);
355
- if (cmdTokens.length < patTokens.length)
356
- return false;
357
- if (cmdTokens.some((tok) => SHELL_OPERATORS.has(tok)))
358
- return false;
359
- if (cmdTokens.some((tok) => SHELL_METACHAR_RE.test(tok)))
360
- return false;
361
- return patTokens.every((tok, i) => tok === "*" || tok === cmdTokens[i]);
362
- }
363
- function sanitizeJwt(ctx) {
364
- const output = JSON.stringify(ctx.payload);
365
- if (JWT_RE.test(output)) {
366
- return {
367
- decision: "deny",
368
- reason: "JWT token detected in tool output",
369
- message: "[REDACTED: JWT token removed by failproofai]"
370
- };
371
- }
372
- return allow();
373
- }
374
- function sanitizeApiKeys(ctx) {
375
- const output = JSON.stringify(ctx.payload);
376
- for (const [pattern, label] of API_KEY_PATTERNS) {
377
- if (pattern.test(output)) {
378
- return {
379
- decision: "deny",
380
- reason: `${label} detected in tool output`,
381
- message: `[REDACTED: ${label} removed by failproofai]`
382
- };
383
- }
384
- }
385
- const additional = ctx.params?.additionalPatterns ?? [];
386
- for (const { regex, label } of additional) {
387
- try {
388
- if (new RegExp(regex).test(output)) {
389
- return {
390
- decision: "deny",
391
- reason: `${label} detected in tool output`,
392
- message: `[REDACTED: ${label} removed by failproofai]`
393
- };
394
- }
395
- } catch {
396
- hookLogWarn(`additionalPatterns: invalid regex "${regex}", skipping`);
397
- }
398
- }
399
- return allow();
400
- }
401
- function sanitizeConnectionStrings(ctx) {
402
- const output = JSON.stringify(ctx.payload);
403
- if (CONNECTION_STRING_RE.test(output)) {
404
- return {
405
- decision: "deny",
406
- reason: "Database connection string with credentials detected in tool output",
407
- message: "[REDACTED: connection string removed by failproofai]"
408
- };
409
- }
410
- return allow();
411
- }
412
- function sanitizePrivateKeyContent(ctx) {
413
- const output = JSON.stringify(ctx.payload);
414
- if (PRIVATE_KEY_RE.test(output)) {
415
- return {
416
- decision: "deny",
417
- reason: "Private key content detected in tool output",
418
- message: "[REDACTED: private key content removed by failproofai]"
419
- };
420
- }
421
- return allow();
422
- }
423
- function sanitizeBearerTokens(ctx) {
424
- const output = JSON.stringify(ctx.payload);
425
- if (BEARER_TOKEN_RE.test(output)) {
426
- return {
427
- decision: "deny",
428
- reason: "Bearer token detected in tool output",
429
- message: "[REDACTED: Bearer token removed by failproofai]"
430
- };
431
- }
432
- return allow();
433
- }
434
- function warnDestructiveSql(ctx) {
435
- if (ctx.toolName !== "Bash")
436
- return allow();
437
- const cmd = getCommand(ctx);
438
- if (!SQL_TOOL_RE.test(cmd))
439
- return allow();
440
- if (DESTRUCTIVE_SQL_RE.test(cmd)) {
441
- return instruct("STOP: This command contains destructive SQL (DROP/TRUNCATE/DELETE). Confirm with the user before executing.");
442
- }
443
- if (DELETE_NO_WHERE_RE.test(cmd) && !SQL_WHERE_RE.test(cmd)) {
444
- return instruct("STOP: This command contains destructive SQL (DROP/TRUNCATE/DELETE). Confirm with the user before executing.");
445
- }
446
- return allow();
447
- }
448
- function warnLargeFileWrite(ctx) {
449
- if (ctx.toolName !== "Write")
450
- return allow();
451
- const content = ctx.toolInput?.content;
452
- if (typeof content !== "string")
453
- return allow();
454
- const thresholdKb = ctx.params?.thresholdKb ?? 1024;
455
- const thresholdBytes = thresholdKb * 1024;
456
- if (content.length > thresholdBytes) {
457
- return instruct(`STOP: You are writing a file larger than ${thresholdKb}KB (${Math.round(content.length / 1024)}KB). This is unusually large. Confirm this is intentional before proceeding.`);
458
- }
459
- return allow();
460
- }
461
- function warnPackagePublish(ctx) {
462
- if (ctx.toolName !== "Bash")
463
- return allow();
464
- const cmd = getCommand(ctx);
465
- if (PUBLISH_CMD_RE.test(cmd)) {
466
- return instruct("STOP: This command publishes a package to a public registry. Confirm with the user that this is intentional.");
467
- }
468
- return allow();
469
- }
470
- function protectEnvVars(ctx) {
471
- if (ctx.toolName !== "Bash")
472
- return allow();
473
- const cmd = getCommand(ctx);
474
- if (ENV_PRINTENV_RE.test(cmd)) {
475
- return deny("Command reads environment variables");
476
- }
477
- if (ECHO_ENV_RE.test(cmd)) {
478
- return deny("Command echoes environment variable");
479
- }
480
- if (EXPORT_RE.test(cmd)) {
481
- return deny("Command exports environment variable");
482
- }
483
- if (PS_ENV_VAR_RE.test(cmd)) {
484
- return deny("Command reads environment variable via PowerShell");
485
- }
486
- if (PS_CHILDITEM_ENV_RE.test(cmd)) {
487
- return deny("Command reads environment variables via PowerShell");
488
- }
489
- if (DOTNET_GETENV_RE.test(cmd)) {
490
- return deny("Command reads environment variable via .NET");
491
- }
492
- if (CMD_ECHO_ENV_RE.test(cmd)) {
493
- return deny("Command echoes environment variable via cmd");
494
- }
495
- return allow();
496
- }
497
- function blockEnvFiles(ctx) {
498
- const cmd = getCommand(ctx);
499
- const filePath = getFilePath(ctx);
500
- if (filePath && ENV_FILE_PATH_RE.test(filePath)) {
501
- return deny("Access to .env file blocked");
502
- }
503
- if (ctx.toolName === "Bash" && ENV_CMD_RE.test(cmd)) {
504
- return deny("Command references .env file");
505
- }
506
- return allow();
507
- }
508
- function blockSudo(ctx) {
509
- if (ctx.toolName !== "Bash")
510
- return allow();
511
- const cmd = getCommand(ctx).trimStart();
512
- if (SUDO_RE.test(cmd) || cmd.startsWith("sudo ")) {
513
- const allowPatterns = ctx.params?.allowPatterns ?? [];
514
- if (allowPatterns.some((p) => matchesAllowedPattern(cmd, p)))
515
- return allow();
516
- return deny("sudo commands are blocked");
517
- }
518
- if (PS_ELEVATION_RE.test(cmd)) {
519
- return deny("Elevated process launch is blocked");
520
- }
521
- if (RUNAS_RE.test(cmd)) {
522
- return deny("runas elevation is blocked");
523
- }
524
- return allow();
525
- }
526
- function blockCurlPipeSh(ctx) {
527
- if (ctx.toolName !== "Bash")
528
- return allow();
529
- const cmd = getCommand(ctx);
530
- if (CURL_PIPE_SH_RE.test(cmd)) {
531
- return deny("Piping downloads to shell is blocked");
532
- }
533
- if (PS_WEB_PIPE_RE.test(cmd)) {
534
- return deny("Piping downloads to Invoke-Expression is blocked");
535
- }
536
- return allow();
537
- }
538
- function extractGitPushArgs(cmd) {
539
- return cmd.split(/&&|\|\||[|;\n]/).map((s) => s.trim()).filter((s) => /^git\s+push\s/.test(s)).map((s) => s.replace(/^git\s+push\s+/, ""));
540
- }
541
- function blockPushMaster(ctx) {
542
- if (ctx.toolName !== "Bash")
543
- return allow();
544
- const protectedBranches = ctx.params?.protectedBranches ?? ["main", "master"];
545
- if (protectedBranches.length === 0)
546
- return allow();
547
- const args = extractGitPushArgs(getCommand(ctx));
548
- const branchPattern = new RegExp(`\\b(?:${protectedBranches.map((b) => b.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")).join("|")})\\b`);
549
- if (args.some((a) => branchPattern.test(a))) {
550
- return deny(`Pushing to ${protectedBranches.join("/")} is blocked`);
551
- }
552
- return allow();
553
- }
554
- function rmTargetIsAllowed(cmd, allowPaths) {
555
- if (allowPaths.length === 0)
556
- return false;
557
- const segments = cmd.split(/&&|\|\||[|;\n]/).map((s) => s.trim()).filter((s) => /\brm\b/.test(s));
558
- if (segments.length === 0)
559
- return false;
560
- for (const seg of segments) {
561
- const tokens = parseArgvTokens(seg);
562
- const rmIdx = tokens.findIndex((t) => t === "rm");
563
- if (rmIdx < 0)
564
- continue;
565
- const flagTokens = tokens.slice(rmIdx + 1).filter((t) => /^-[^-]/.test(t));
566
- const longFlagsInSeg = tokens.slice(rmIdx + 1).filter((t) => /^--/.test(t));
567
- if (!/r/i.test(flagTokens.join("")) && !longFlagsInSeg.some((f) => /^--recursive$/i.test(f)))
568
- continue;
569
- const pathArgs = tokens.slice(rmIdx + 1).filter((t) => !t.startsWith("-"));
570
- for (const target of pathArgs) {
571
- const normalized = target.replace(/\/\*$/, "").replace(/\/+$/, "") || "/";
572
- const covered = allowPaths.some((p) => {
573
- const np = p.replace(/\/+$/, "") || "/";
574
- return normalized === np || normalized.startsWith(np + "/");
575
- });
576
- if (!covered) {
577
- const segCovered = allowPaths.some((p) => {
578
- const escaped = p.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
579
- return new RegExp(`${escaped}(?:[/"'\\s/*]|$)`).test(seg);
580
- });
581
- if (!segCovered)
582
- return false;
583
- }
584
- }
585
- }
586
- return true;
587
- }
588
- function blockRmRf(ctx) {
589
- if (ctx.toolName !== "Bash")
590
- return allow();
591
- const cmd = getCommand(ctx);
592
- const hasDestructivePath = parseArgvTokens(cmd).some((token) => {
593
- const normalized = token.replace(/\/\*$/, "").replace(/\/+$/, "") || (token.startsWith("/") ? "/" : "");
594
- return normalized === "/" || normalized === "~" || /^\/[A-Za-z_][\w.-]*$/.test(normalized);
595
- });
596
- if (hasDestructivePath && (/rm\s+-[^\s]*r[^\s]*f[^\s]*/.test(cmd) || /rm\s+-[^\s]*f[^\s]*r[^\s]*/.test(cmd))) {
597
- const allowPaths = ctx.params?.allowPaths ?? [];
598
- if (rmTargetIsAllowed(cmd, allowPaths))
599
- return allow();
600
- return deny("Catastrophic deletion blocked");
601
- }
602
- if (hasDestructivePath && /\brm\b/.test(cmd)) {
603
- const tokens = parseArgvTokens(cmd);
604
- const shortFlags = tokens.filter((t) => /^-[^-]/.test(t)).join("");
605
- const longFlags = tokens.filter((t) => /^--/.test(t));
606
- const hasRecursive = /r/i.test(shortFlags) || longFlags.some((f) => /^--recursive$/i.test(f));
607
- const hasForce = /f/.test(shortFlags) || longFlags.some((f) => /^--force$/i.test(f));
608
- if (hasRecursive && hasForce) {
609
- const allowPaths = ctx.params?.allowPaths ?? [];
610
- if (rmTargetIsAllowed(cmd, allowPaths))
611
- return allow();
612
- return deny("Catastrophic deletion blocked");
613
- }
614
- }
615
- if (/Remove-Item\s+.*-Recurse.*-Force.*(?:[A-Z]:\\(?:\s|$)|\\\*)/i.test(cmd)) {
616
- return deny("Catastrophic deletion blocked");
617
- }
618
- if (/(?:rd|rmdir)\s+\/s\s+\/q\s+[A-Z]:\\/i.test(cmd)) {
619
- return deny("Catastrophic deletion blocked");
620
- }
621
- return allow();
622
- }
623
- function blockForcePush(ctx) {
624
- if (ctx.toolName !== "Bash")
625
- return allow();
626
- const args = extractGitPushArgs(getCommand(ctx));
627
- if (args.some((a) => FORCE_PUSH_RE.test(a))) {
628
- return deny("Force-pushing is blocked");
629
- }
630
- return allow();
631
- }
632
- function blockSecretsWrite(ctx) {
633
- if (ctx.toolName !== "Write")
634
- return allow();
635
- const filePath = getFilePath(ctx);
636
- if (SECRET_FILE_RE.test(filePath) || SECRET_FILE_ID_RSA_RE.test(filePath) || SECRET_FILE_CREDENTIALS_RE.test(filePath)) {
637
- return deny("Writing secret key files is blocked");
638
- }
639
- const additionalPatterns = ctx.params?.additionalPatterns ?? [];
640
- for (const pattern of additionalPatterns) {
641
- if (filePath.includes(pattern)) {
642
- return deny(`Writing blocked file pattern: ${pattern}`);
643
- }
644
- }
645
- return allow();
646
- }
647
- function extractAbsolutePaths(command) {
648
- const paths = [];
649
- const pathRe = /(?<![a-zA-Z0-9_.\-~\\])(?:~\/[^\s;|&"'()\[\]{}]*|~(?=\s|$|[;|&"'()\[\]{}])|\/[^\s;|&"'()\[\]{}]*)/g;
650
- function addPaths(s) {
651
- pathRe.lastIndex = 0;
652
- let m;
653
- while ((m = pathRe.exec(s)) !== null) {
654
- let p = m[0];
655
- if (p === "~")
656
- p = homedir3();
657
- else if (p.startsWith("~/"))
658
- p = join2(homedir3(), p.slice(2));
659
- paths.push(p);
660
- }
661
- }
662
- let firstBarePipe = command.length;
663
- let inDouble = false, inSingle = false;
664
- for (let i = 0;i < command.length; i++) {
665
- const c = command[i];
666
- if (c === '"' && !inSingle)
667
- inDouble = !inDouble;
668
- else if (c === "'" && !inDouble)
669
- inSingle = !inSingle;
670
- else if (c === "|" && !inDouble && !inSingle) {
671
- firstBarePipe = i;
672
- break;
673
- }
674
- }
675
- const firstSegment = command.slice(0, firstBarePipe);
676
- const quotedRe = /"([^"]*)"|'([^']*)'/g;
677
- let qm;
678
- while ((qm = quotedRe.exec(firstSegment)) !== null) {
679
- const content = qm[1] ?? qm[2] ?? "";
680
- if (/[*?\[\]^$+()\\]/.test(content))
681
- continue;
682
- addPaths(content);
683
- }
684
- const stripped = command.replace(/"[^"]*"/g, (m) => " ".repeat(m.length)).replace(/'[^']*'/g, (m) => " ".repeat(m.length));
685
- addPaths(stripped);
686
- return paths;
687
- }
688
- function blockReadOutsideCwd(ctx) {
689
- const cwd = process.env.CLAUDE_PROJECT_DIR || ctx.session?.cwd;
690
- if (!cwd)
691
- return allow();
692
- const allowPaths = ctx.params?.allowPaths ?? [];
693
- if (ctx.toolName === "Bash") {
694
- const cmd = getCommand(ctx);
695
- if (!READ_LIKE_CMDS.test(cmd))
696
- return allow();
697
- const paths = extractAbsolutePaths(cmd);
698
- const cwdWithSep2 = cwd.endsWith("/") ? cwd : cwd + "/";
699
- for (const p of paths) {
700
- const resolved3 = resolve2(cwd, p);
701
- if (isClaudeSettingsFile(resolved3)) {
702
- return deny(`Reading Claude settings file blocked: ${resolved3}`);
703
- }
704
- if (isClaudeInternalPath(resolved3))
705
- continue;
706
- if (resolved3 === "/dev/null")
707
- continue;
708
- if (resolved3 !== cwd && !resolved3.startsWith(cwdWithSep2)) {
709
- if (allowPaths.some((ap) => resolved3 === ap || resolved3.startsWith(ap.endsWith("/") ? ap : ap + "/")))
710
- continue;
711
- return deny(`Bash read outside project directory blocked: ${resolved3}`);
712
- }
713
- }
714
- return allow();
715
- }
716
- const filePath = getFilePath(ctx);
717
- const searchPath = ctx.toolInput?.path ?? "";
718
- const target = filePath || searchPath;
719
- if (!target)
720
- return allow();
721
- const resolved2 = resolve2(cwd, target);
722
- if (isClaudeSettingsFile(resolved2)) {
723
- return deny(`Reading Claude settings file blocked: ${resolved2}`);
724
- }
725
- if (isClaudeInternalPath(resolved2))
726
- return allow();
727
- if (resolved2 === "/dev/null")
728
- return allow();
729
- const cwdWithSep = cwd.endsWith("/") ? cwd : cwd + "/";
730
- if (resolved2 !== cwd && !resolved2.startsWith(cwdWithSep)) {
731
- if (allowPaths.some((ap) => resolved2 === ap || resolved2.startsWith(ap.endsWith("/") ? ap : ap + "/")))
732
- return allow();
733
- return deny(`Access outside project directory blocked: ${resolved2}`);
734
- }
735
- return allow();
736
- }
737
- function blockWorkOnMain(ctx) {
738
- if (ctx.toolName !== "Bash")
739
- return allow();
740
- const cmd = getCommand(ctx);
741
- if (!GIT_COMMIT_MERGE_RE.test(cmd))
742
- return allow();
743
- const cwd = ctx.session?.cwd;
744
- if (!cwd)
745
- return allow();
746
- const branch = getCurrentBranch(cwd);
747
- if (!branch)
748
- return allow();
749
- const protectedBranches = ctx.params?.protectedBranches ?? ["main", "master"];
750
- if (protectedBranches.includes(branch)) {
751
- return deny(`Git ${cmd.match(/git\s+(\S+)/)?.[1] ?? "operation"} on ${branch} is blocked. Create a feature branch first.`);
752
- }
753
- return allow();
754
- }
755
- function blockFailproofaiCommands(ctx) {
756
- if (ctx.toolName !== "Bash")
757
- return allow();
758
- const cmd = getCommand(ctx);
759
- if (FAILPROOFAI_CLI_RE.test(cmd)) {
760
- return deny("Running failproofai CLI commands is blocked");
761
- }
762
- if (FAILPROOFAI_UNINSTALL_RE.test(cmd)) {
763
- return deny("Uninstalling failproofai is blocked");
764
- }
765
- return allow();
766
- }
767
- async function warnRepeatedToolCalls(ctx) {
768
- const THRESHOLD = 3;
769
- const transcriptPath = ctx.session?.transcriptPath;
770
- if (!transcriptPath || !ctx.toolName || !ctx.toolInput)
771
- return allow();
772
- const trackerPath = `${transcriptPath}.tool-calls.json`;
773
- const fingerprint = JSON.stringify({ tool: ctx.toolName, input: ctx.toolInput });
774
- let counts = {};
775
- try {
776
- const raw = await readFile(trackerPath, "utf8");
777
- counts = JSON.parse(raw);
778
- } catch {}
779
- const prevCount = counts[fingerprint] ?? 0;
780
- if (prevCount >= THRESHOLD) {
781
- return instruct(`STOP: You have already called ${ctx.toolName} ${prevCount} times with identical parameters. This is wasteful and unproductive. Do NOT repeat this call — use a different approach or ask the user for clarification.`);
782
- }
783
- counts[fingerprint] = prevCount + 1;
784
- try {
785
- const serialized = JSON.stringify(counts);
786
- if (serialized.length <= TOOL_CALL_TRACKER_MAX_BYTES) {
787
- await writeFile(trackerPath, serialized, "utf8");
788
- }
789
- } catch {}
790
- return allow();
791
- }
792
- function warnGitAmend(ctx) {
793
- if (ctx.toolName !== "Bash")
794
- return allow();
795
- const cmd = getCommand(ctx);
796
- if (GIT_AMEND_RE.test(cmd)) {
797
- return instruct("STOP: This command amends the last commit, which rewrites git history. If this commit has already been pushed to a shared branch, this will cause divergence for other contributors. Confirm with the user before executing.");
798
- }
799
- return allow();
800
- }
801
- function warnGitStashDrop(ctx) {
802
- if (ctx.toolName !== "Bash")
803
- return allow();
804
- const cmd = getCommand(ctx);
805
- if (GIT_STASH_DROP_RE.test(cmd)) {
806
- return instruct("STOP: This command permanently deletes stashed changes (git stash drop/clear). Stash entries cannot be recovered after deletion. Confirm with the user before executing.");
807
- }
808
- return allow();
809
- }
810
- function warnAllFilesStaged(ctx) {
811
- if (ctx.toolName !== "Bash")
812
- return allow();
813
- const cmd = getCommand(ctx);
814
- if (GIT_ADD_ALL_RE.test(cmd)) {
815
- return instruct("STOP: This command stages all files in the working tree (git add -A / --all / .). This may inadvertently include build artifacts, generated files, or sensitive files not covered by .gitignore. Confirm with the user before executing.");
816
- }
817
- return allow();
818
- }
819
- function warnSchemaAlteration(ctx) {
820
- if (ctx.toolName !== "Bash")
821
- return allow();
822
- const cmd = getCommand(ctx);
823
- if (!SQL_TOOL_RE.test(cmd))
824
- return allow();
825
- if (SCHEMA_ALTER_RE.test(cmd)) {
826
- return instruct("STOP: This command contains a schema-altering SQL statement (ALTER TABLE with column or rename operation). Schema changes on production databases are irreversible or disruptive. Confirm with the user before executing.");
827
- }
828
- return allow();
829
- }
830
- function warnGlobalPackageInstall(ctx) {
831
- if (ctx.toolName !== "Bash")
832
- return allow();
833
- const cmd = getCommand(ctx);
834
- const isGlobal = NPM_GLOBAL_RE.test(cmd) || YARN_GLOBAL_RE.test(cmd) || PNPM_GLOBAL_RE.test(cmd) || BUN_GLOBAL_RE.test(cmd) || CARGO_INSTALL_RE.test(cmd) || PIP_SYSTEM_RE.test(cmd);
835
- if (isGlobal) {
836
- return instruct("STOP: This command installs a package globally, which modifies the system-wide environment outside the project. This can conflict with other projects or system tools. Confirm with the user before executing.");
837
- }
838
- return allow();
839
- }
840
- function preferPackageManager(ctx) {
841
- if (ctx.toolName !== "Bash")
842
- return allow();
843
- const cmd = getCommand(ctx);
844
- if (!cmd)
845
- return allow();
846
- const allowed = ctx.params?.allowed ?? [];
847
- if (allowed.length === 0)
848
- return allow();
849
- const allowedSet = new Set(allowed.map((a) => a.toLowerCase()));
850
- const blocked = ctx.params?.blocked ?? [];
851
- const allowedList = allowed.join(", ");
852
- const segments = cmd.split(SEGMENT_SPLIT_RE);
853
- for (const segment of segments) {
854
- const trimmed = segment.trim();
855
- if (!trimmed)
856
- continue;
857
- let segmentAllowed = false;
858
- for (const manager of allowedSet) {
859
- const patterns = PKG_MANAGER_DETECTORS[manager];
860
- if (!patterns)
861
- continue;
862
- for (const pattern of patterns) {
863
- if (pattern.test(trimmed)) {
864
- segmentAllowed = true;
865
- break;
866
- }
867
- }
868
- if (segmentAllowed)
869
- break;
870
- }
871
- if (segmentAllowed)
872
- continue;
873
- for (const [manager, patterns] of Object.entries(PKG_MANAGER_DETECTORS)) {
874
- if (allowedSet.has(manager))
875
- continue;
876
- for (const pattern of patterns) {
877
- if (pattern.test(trimmed)) {
878
- return deny(`"${manager}" is not an allowed package manager. ` + `Allowed package managers for this project: ${allowedList}. ` + `Rewrite this command using an allowed package manager.`);
879
- }
880
- }
881
- }
882
- for (const name of blocked) {
883
- const lower = name.toLowerCase();
884
- if (allowedSet.has(lower))
885
- continue;
886
- const re = new RegExp(`\\b${lower.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\\b`);
887
- if (re.test(trimmed)) {
888
- return deny(`"${lower}" is not an allowed package manager. ` + `Allowed package managers for this project: ${allowedList}. ` + `Rewrite this command using an allowed package manager.`);
889
- }
890
- }
891
- }
892
- return allow();
893
- }
894
- function warnBackgroundProcess(ctx) {
895
- if (ctx.toolName !== "Bash")
896
- return allow();
897
- const cmd = getCommand(ctx);
898
- const isBackground = NOHUP_RE.test(cmd) || SCREEN_DETACH_RE.test(cmd) || TMUX_DETACH_RE.test(cmd) || DISOWN_RE.test(cmd) || BACKGROUND_AMPERSAND_RE.test(cmd);
899
- if (isBackground) {
900
- return instruct("STOP: This command starts a background or detached process (nohup, screen -d, tmux -d, or trailing &). Background processes persist after Claude's session and may be difficult to track or stop. Confirm with the user before executing.");
901
- }
902
- return allow();
903
- }
904
- function requireCommitBeforeStop(ctx) {
905
- const cwd = ctx.session?.cwd;
906
- if (!cwd)
907
- return allow("No working directory available, skipping commit check.");
908
- try {
909
- const status = execSync("git status --porcelain", {
910
- cwd,
911
- encoding: "utf8",
912
- stdio: ["pipe", "pipe", "pipe"],
913
- timeout: 5000
914
- }).trim();
915
- if (status.length > 0) {
916
- return deny("You have uncommitted changes in the working directory. Commit all changes now.");
917
- }
918
- return allow("All changes are committed.");
919
- } catch {
920
- return allow("Not a git repository, skipping commit check.");
921
- }
922
- }
923
- function requirePushBeforeStop(ctx) {
924
- const cwd = ctx.session?.cwd;
925
- if (!cwd)
926
- return allow("No working directory available, skipping push check.");
927
- try {
928
- const remotes = execSync("git remote", {
929
- cwd,
930
- encoding: "utf8",
931
- stdio: ["pipe", "pipe", "pipe"],
932
- timeout: 3000
933
- }).trim();
934
- if (!remotes)
935
- return allow("No git remote configured, skipping push check.");
936
- const remote = ctx.params?.remote ?? "origin";
937
- const branch = getCurrentBranch(cwd);
938
- if (!branch || branch === "HEAD")
939
- return allow("Detached HEAD, skipping push check.");
940
- const baseBranch = ctx.params?.baseBranch ?? "main";
941
- if (branch === baseBranch) {
942
- return allow(`On base branch "${baseBranch}", skipping push check.`);
943
- }
944
- try {
945
- const ahead = execFileSync("git", ["log", `${remote}/${baseBranch}..HEAD`, "--oneline"], { cwd, encoding: "utf8", stdio: ["pipe", "pipe", "pipe"], timeout: 5000 }).trim();
946
- if (!ahead) {
947
- return allow(`No commits ahead of ${remote}/${baseBranch}, skipping push check.`);
948
- }
949
- const diff = execFileSync("git", ["diff", "--stat", `${remote}/${baseBranch}`, "HEAD"], { cwd, encoding: "utf8", stdio: ["pipe", "pipe", "pipe"], timeout: 5000 }).trim();
950
- if (!diff) {
951
- return allow(`No file changes compared to ${remote}/${baseBranch}, skipping push check.`);
952
- }
953
- } catch {}
954
- let hasTracking = false;
955
- try {
956
- execFileSync("git", ["rev-parse", "--verify", `${remote}/${branch}`], {
957
- cwd,
958
- encoding: "utf8",
959
- stdio: ["pipe", "pipe", "pipe"],
960
- timeout: 3000
961
- });
962
- hasTracking = true;
963
- } catch {}
964
- if (!hasTracking) {
965
- return deny(`Branch "${branch}" has not been pushed to remote "${remote}". ` + `Run now: git push -u ${remote} ${branch}`);
966
- }
967
- const unpushed = execFileSync("git", ["log", `${remote}/${branch}..HEAD`, "--oneline"], {
968
- cwd,
969
- encoding: "utf8",
970
- stdio: ["pipe", "pipe", "pipe"],
971
- timeout: 5000
972
- }).trim();
973
- if (unpushed.length > 0) {
974
- const commitCount = unpushed.split(`
975
- `).length;
976
- return deny(`You have ${commitCount} unpushed commit${commitCount > 1 ? "s" : ""} on branch "${branch}". ` + `Run now: git push`);
977
- }
978
- return allow(`All commits pushed to "${remote}".`);
979
- } catch {
980
- return allow("Could not check push status, skipping.");
981
- }
982
- }
983
- function requirePrBeforeStop(ctx) {
984
- const cwd = ctx.session?.cwd;
985
- if (!cwd)
986
- return allow("No working directory available, skipping PR check.");
987
- try {
988
- try {
989
- execSync("gh --version", { cwd, encoding: "utf8", stdio: ["pipe", "pipe", "pipe"], timeout: 3000 });
990
- } catch {
991
- return allow("GitHub CLI (gh) not installed, skipping PR check.");
992
- }
993
- const branch = getCurrentBranch(cwd);
994
- if (!branch || branch === "HEAD")
995
- return allow("Detached HEAD, skipping PR check.");
996
- const baseBranch = ctx.params?.baseBranch ?? "main";
997
- if (branch === baseBranch) {
998
- return allow(`On base branch "${baseBranch}", skipping PR check.`);
999
- }
1000
- try {
1001
- const ahead = execFileSync("git", ["log", `origin/${baseBranch}..HEAD`, "--oneline"], { cwd, encoding: "utf8", stdio: ["pipe", "pipe", "pipe"], timeout: 5000 }).trim();
1002
- if (!ahead) {
1003
- return allow(`No commits ahead of origin/${baseBranch}, skipping PR check.`);
1004
- }
1005
- const diff = execFileSync("git", ["diff", "--stat", `origin/${baseBranch}`, "HEAD"], { cwd, encoding: "utf8", stdio: ["pipe", "pipe", "pipe"], timeout: 5000 }).trim();
1006
- if (!diff) {
1007
- return allow(`No file changes compared to origin/${baseBranch}, skipping PR check.`);
1008
- }
1009
- } catch {}
1010
- let prJson;
1011
- try {
1012
- prJson = execSync("gh pr view --json number,url,state", {
1013
- cwd,
1014
- encoding: "utf8",
1015
- stdio: ["pipe", "pipe", "pipe"],
1016
- timeout: 15000
1017
- }).trim();
1018
- } catch {
1019
- return deny(`No pull request found for branch "${branch}". ` + `Run now: gh pr create`);
1020
- }
1021
- const pr = JSON.parse(prJson);
1022
- if (pr.state === "OPEN") {
1023
- return allow(`PR #${pr.number} exists: ${pr.url}`);
1024
- }
1025
- if (pr.state === "MERGED") {
1026
- try {
1027
- execFileSync("git", ["fetch", "origin", `+refs/heads/${baseBranch}:refs/remotes/origin/${baseBranch}`], {
1028
- cwd,
1029
- encoding: "utf8",
1030
- stdio: ["pipe", "pipe", "pipe"],
1031
- timeout: 1e4
1032
- });
1033
- const freshAhead = execFileSync("git", ["log", `origin/${baseBranch}..HEAD`, "--oneline"], { cwd, encoding: "utf8", stdio: ["pipe", "pipe", "pipe"], timeout: 5000 }).trim();
1034
- if (!freshAhead) {
1035
- return allow(`PR #${pr.number} was merged; branch is up to date with ${baseBranch}.`);
1036
- }
1037
- const freshDiff = execFileSync("git", ["diff", "--stat", `origin/${baseBranch}`, "HEAD"], { cwd, encoding: "utf8", stdio: ["pipe", "pipe", "pipe"], timeout: 5000 }).trim();
1038
- if (!freshDiff) {
1039
- return allow(`PR #${pr.number} was merged; no file changes vs ${baseBranch}.`);
1040
- }
1041
- } catch {}
1042
- }
1043
- return deny(`Pull request for branch "${branch}" is ${pr.state.toLowerCase()}. Run now: gh pr create`);
1044
- } catch {
1045
- return allow("Could not check PR status, skipping.");
1046
- }
1047
- }
1048
- function requireCiGreenBeforeStop(ctx) {
1049
- const cwd = ctx.session?.cwd;
1050
- if (!cwd)
1051
- return allow("No working directory available, skipping CI check.");
1052
- try {
1053
- try {
1054
- execSync("gh --version", { cwd, encoding: "utf8", stdio: ["pipe", "pipe", "pipe"], timeout: 3000 });
1055
- } catch {
1056
- return allow("GitHub CLI (gh) not installed, skipping CI check.");
1057
- }
1058
- const branch = getCurrentBranch(cwd);
1059
- if (!branch || branch === "HEAD")
1060
- return allow("Detached HEAD, skipping CI check.");
1061
- let workflowRuns = [];
1062
- try {
1063
- const runsJson = execFileSync("gh", ["run", "list", "--branch", branch, "--limit", "5", "--json", "status,conclusion,name"], { cwd, encoding: "utf8", stdio: ["pipe", "pipe", "pipe"], timeout: 15000 }).trim();
1064
- if (runsJson && runsJson !== "[]") {
1065
- workflowRuns = JSON.parse(runsJson);
1066
- }
1067
- } catch {}
1068
- let thirdPartyChecks = [];
1069
- let commitStatuses = [];
1070
- const sha = getHeadSha(cwd);
1071
- if (sha) {
1072
- thirdPartyChecks = getThirdPartyCheckRuns(cwd, sha);
1073
- commitStatuses = getCommitStatuses(cwd, sha);
1074
- }
1075
- const allChecks = [...workflowRuns, ...thirdPartyChecks, ...commitStatuses];
1076
- if (allChecks.length === 0)
1077
- return allow(`No CI runs found for branch "${branch}".`);
1078
- const failing = allChecks.filter((r) => r.status === "completed" && r.conclusion !== "success" && r.conclusion !== "skipped" && r.conclusion !== "cancelled");
1079
- if (failing.length > 0) {
1080
- const names = failing.map((r) => `"${r.name}"`).join(", ");
1081
- return deny(`CI checks are failing on branch "${branch}": ${names}. Fix the failing checks now.`);
1082
- }
1083
- const pending = allChecks.filter((r) => r.status === "in_progress" || r.status === "queued" || r.status === "waiting");
1084
- if (pending.length > 0) {
1085
- const names = pending.map((r) => `"${r.name}"`).join(", ");
1086
- return deny(`CI checks are still running on branch "${branch}": ${names}. Wait for all checks to complete, then verify they pass.`);
1087
- }
1088
- return allow(`All CI checks passed on branch "${branch}".`);
1089
- } catch {
1090
- return allow("Could not check CI status, skipping.");
1091
- }
1092
- }
1093
- function registerBuiltinPolicies(enabledNames) {
1094
- const enabledSet = new Set(enabledNames);
1095
- for (const policy of BUILTIN_POLICIES) {
1096
- if (enabledSet.has(policy.name)) {
1097
- registerPolicy(policy.name, policy.description, policy.fn, policy.match);
1098
- }
1099
- }
1100
- }
1101
- var SHELL_OPERATORS, SHELL_METACHAR_RE, JWT_RE, API_KEY_PATTERNS, CONNECTION_STRING_RE, PRIVATE_KEY_RE, BEARER_TOKEN_RE, SQL_TOOL_RE, DESTRUCTIVE_SQL_RE, DELETE_NO_WHERE_RE, SQL_WHERE_RE, SCHEMA_ALTER_RE, PUBLISH_CMD_RE, ENV_PRINTENV_RE, ECHO_ENV_RE, EXPORT_RE, PS_ENV_VAR_RE, PS_CHILDITEM_ENV_RE, DOTNET_GETENV_RE, CMD_ECHO_ENV_RE, ENV_FILE_PATH_RE, ENV_CMD_RE, SUDO_RE, PS_ELEVATION_RE, RUNAS_RE, CURL_PIPE_SH_RE, PS_WEB_PIPE_RE, FORCE_PUSH_RE, SECRET_FILE_RE, SECRET_FILE_ID_RSA_RE, SECRET_FILE_CREDENTIALS_RE, GIT_COMMIT_MERGE_RE, FAILPROOFAI_CLI_RE, FAILPROOFAI_UNINSTALL_RE, GIT_AMEND_RE, GIT_STASH_DROP_RE, GIT_ADD_ALL_RE, NPM_GLOBAL_RE, YARN_GLOBAL_RE, PNPM_GLOBAL_RE, BUN_GLOBAL_RE, CARGO_INSTALL_RE, PIP_SYSTEM_RE, PKG_MANAGER_DETECTORS, NOHUP_RE, SCREEN_DETACH_RE, TMUX_DETACH_RE, DISOWN_RE, BACKGROUND_AMPERSAND_RE, gitBranchCache, READ_LIKE_CMDS, TOOL_CALL_TRACKER_MAX_BYTES = 65536, SEGMENT_SPLIT_RE, BUILTIN_POLICIES;
1102
- var init_builtin_policies = __esm(() => {
1103
- init_hook_logger();
1104
- SHELL_OPERATORS = new Set(["&&", "||", "|", ";"]);
1105
- SHELL_METACHAR_RE = /[;&<>`$()\\]/;
1106
- JWT_RE = /eyJ[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}/;
1107
- API_KEY_PATTERNS = [
1108
- [/sk-ant-[A-Za-z0-9\-_]{20,}/, "Anthropic API key"],
1109
- [/sk-proj-[A-Za-z0-9\-_]{20,}/, "OpenAI project API key"],
1110
- [/sk-[A-Za-z0-9]{20,}/, "OpenAI API key"],
1111
- [/ghp_[A-Za-z0-9]{36}/, "GitHub personal access token"],
1112
- [/github_pat_[A-Za-z0-9_]{82}/, "GitHub fine-grained token"],
1113
- [/AKIA[A-Z0-9]{16}/, "AWS access key ID"],
1114
- [/sk_live_[A-Za-z0-9]{24,}/, "Stripe live secret key"],
1115
- [/sk_test_[A-Za-z0-9]{24,}/, "Stripe test secret key"],
1116
- [/AIza[0-9A-Za-z\-_]{35}/, "Google API key"]
1117
- ];
1118
- CONNECTION_STRING_RE = /(?:postgresql|postgres|mysql|mongodb(?:\+srv)?|redis|amqps?|smtps?):\/\/[^@\s]+@/;
1119
- PRIVATE_KEY_RE = /-----BEGIN (?:[A-Z]+ )?PRIVATE KEY-----/;
1120
- BEARER_TOKEN_RE = /Authorization:\s*Bearer\s+[A-Za-z0-9\-._~+/]{20,}/i;
1121
- SQL_TOOL_RE = /\b(?:psql|mysql|sqlite3|pgcli|clickhouse-client)\b/;
1122
- DESTRUCTIVE_SQL_RE = /\b(?:DROP\s+(?:TABLE|DATABASE|SCHEMA)|TRUNCATE\b)/i;
1123
- DELETE_NO_WHERE_RE = /\bDELETE\s+FROM\b/i;
1124
- SQL_WHERE_RE = /\bWHERE\b/i;
1125
- SCHEMA_ALTER_RE = /\bALTER\s+TABLE\b[\s\S]*\b(?:DROP\s+COLUMN|ADD\s+COLUMN|RENAME\s+(?:COLUMN|TO)|MODIFY\s+COLUMN)\b/i;
1126
- PUBLISH_CMD_RE = /(?:npm\s+publish|bun\s+publish|pnpm\s+publish|yarn\s+npm\s+publish|twine\s+upload|poetry\s+publish|cargo\s+publish|gem\s+push)\b/;
1127
- ENV_PRINTENV_RE = /(?:^|\s|;|&&|\|\|)(?:env|printenv)(?:\s|$|;|&&|\|)/;
1128
- ECHO_ENV_RE = /echo\s+.*\$\{?[A-Za-z_]/;
1129
- EXPORT_RE = /(?:^|\s|;|&&|\|\|)export\s+\w+/;
1130
- PS_ENV_VAR_RE = /\$env:[A-Za-z_]/i;
1131
- PS_CHILDITEM_ENV_RE = /(?:Get-ChildItem|dir|gci|ls)\s+Env:/i;
1132
- DOTNET_GETENV_RE = /\[Environment\]::GetEnvironment/i;
1133
- CMD_ECHO_ENV_RE = /echo\s+%[A-Za-z_]/i;
1134
- ENV_FILE_PATH_RE = /(?:^|[\\/])\.env(?:\.|$)/;
1135
- ENV_CMD_RE = /\.env(?:\b|\s|$|\.)/;
1136
- SUDO_RE = /(?:^|;|&&|\|\|)\s*sudo\s/;
1137
- PS_ELEVATION_RE = /Start-Process\s+.*-Verb\s+RunAs/i;
1138
- RUNAS_RE = /(?:^|;|&&|\|\|)\s*runas\s/i;
1139
- CURL_PIPE_SH_RE = /(?:curl|wget)\s.*\|\s*(?:sh|bash|zsh|dash|ksh|csh|tcsh|fish|ash)\b/;
1140
- PS_WEB_PIPE_RE = /(?:Invoke-WebRequest|iwr|Invoke-RestMethod|irm)\s+.*\|\s*(?:Invoke-Expression|iex)/i;
1141
- FORCE_PUSH_RE = /(?:--force|-f\b)/;
1142
- SECRET_FILE_RE = /\.(?:pem|key)$/;
1143
- SECRET_FILE_ID_RSA_RE = /id_rsa/;
1144
- SECRET_FILE_CREDENTIALS_RE = /credentials/;
1145
- GIT_COMMIT_MERGE_RE = /git\s+(?:commit|merge|rebase|cherry-pick)\b/;
1146
- FAILPROOFAI_CLI_RE = /(?:^|;|&&|\|\||\|)\s*failproofai(?:\s|$)/;
1147
- FAILPROOFAI_UNINSTALL_RE = /(?:npm\s+(?:uninstall|remove|un|r)\s.*failproofai|bun\s+remove\s.*failproofai|yarn\s+global\s+remove\s+failproofai|pnpm\s+(?:remove|uninstall|un)\s.*failproofai)/;
1148
- GIT_AMEND_RE = /\bgit\s+commit\b.*--amend\b/;
1149
- GIT_STASH_DROP_RE = /\bgit\s+stash\s+(?:drop|clear)\b/;
1150
- GIT_ADD_ALL_RE = /\bgit\s+add\s+(?:-A\b|--all\b|\.(?:\s|$|;|&&|\|\|))/;
1151
- NPM_GLOBAL_RE = /\bnpm\s+(?:install|i)\b(?=.*(?:\s-g\b|--global\b))/;
1152
- YARN_GLOBAL_RE = /\byarn\s+global\s+add\b/;
1153
- PNPM_GLOBAL_RE = /\bpnpm\s+(?:add|install|i)\b(?=.*(?:\s-g\b|--global\b))/;
1154
- BUN_GLOBAL_RE = /\bbun\s+(?:install|add)\b(?=.*(?:\s-g\b|--global\b))/;
1155
- CARGO_INSTALL_RE = /\bcargo\s+install\b/;
1156
- PIP_SYSTEM_RE = /\bpip(?:3)?\s+install\b(?=.*(?:--user\b|--break-system-packages\b))/;
1157
- PKG_MANAGER_DETECTORS = {
1158
- pip: [/\bpip\b/, /\bpip3\b/, /\bpython3?\s+-m\s+pip\b/],
1159
- npm: [/\bnpm\b/, /\bnpx\b/],
1160
- yarn: [/\byarn\b/],
1161
- pnpm: [/\bpnpm\b/, /\bpnpx\b/],
1162
- bun: [/\bbun\b/, /\bbunx\b/],
1163
- uv: [/\buv\b/],
1164
- poetry: [/\bpoetry\b/],
1165
- pipenv: [/\bpipenv\b/],
1166
- conda: [/\bconda\b/],
1167
- cargo: [/\bcargo\b/]
1168
- };
1169
- NOHUP_RE = /\bnohup\s+\S/;
1170
- SCREEN_DETACH_RE = /\bscreen\s+-[A-Za-z]*d[A-Za-z]*\b/;
1171
- TMUX_DETACH_RE = /\btmux\s+(?:new-session|new)\b[^|&;]*-d\b/;
1172
- DISOWN_RE = /\bdisown\b/;
1173
- BACKGROUND_AMPERSAND_RE = /(?<![&|])\s?&\s*(?:$|#|;)/;
1174
- gitBranchCache = new Map;
1175
- READ_LIKE_CMDS = /(?:^|;|&&|\|\||\|)\s*(?:ls|find|cat|head|tail|less|more|wc|file|stat|tree|du)\s/;
1176
- SEGMENT_SPLIT_RE = /\s*(?:&&|\|\||\||;)\s*/;
1177
- BUILTIN_POLICIES = [
1178
- {
1179
- name: "sanitize-jwt",
1180
- description: "Stop Claude from reading JWTs in tool responses",
1181
- fn: sanitizeJwt,
1182
- match: { events: ["PostToolUse"] },
1183
- defaultEnabled: true,
1184
- category: "Sanitize"
1185
- },
1186
- {
1187
- name: "sanitize-api-keys",
1188
- description: "Stop Claude from reading API keys (OpenAI, Anthropic, GitHub, AWS, Stripe, Google) in tool responses",
1189
- fn: sanitizeApiKeys,
1190
- match: { events: ["PostToolUse"] },
1191
- defaultEnabled: true,
1192
- category: "Sanitize",
1193
- params: {
1194
- additionalPatterns: {
1195
- type: "pattern[]",
1196
- description: "Additional API key patterns to scrub, each with { regex, label }",
1197
- default: []
1198
- }
1199
- }
1200
- },
1201
- {
1202
- name: "sanitize-connection-strings",
1203
- description: "Stop Claude from reading database connection strings with embedded credentials in tool responses",
1204
- fn: sanitizeConnectionStrings,
1205
- match: { events: ["PostToolUse"] },
1206
- defaultEnabled: true,
1207
- category: "Sanitize"
1208
- },
1209
- {
1210
- name: "sanitize-private-key-content",
1211
- description: "Stop Claude from reading PEM private key content in tool responses",
1212
- fn: sanitizePrivateKeyContent,
1213
- match: { events: ["PostToolUse"] },
1214
- defaultEnabled: true,
1215
- category: "Sanitize"
1216
- },
1217
- {
1218
- name: "sanitize-bearer-tokens",
1219
- description: "Stop Claude from reading Authorization Bearer tokens in tool responses",
1220
- fn: sanitizeBearerTokens,
1221
- match: { events: ["PostToolUse"] },
1222
- defaultEnabled: true,
1223
- category: "Sanitize"
1224
- },
1225
- {
1226
- name: "protect-env-vars",
1227
- description: "Prevent commands that read environment variables",
1228
- fn: protectEnvVars,
1229
- match: { events: ["PreToolUse"], toolNames: ["Bash"] },
1230
- defaultEnabled: true,
1231
- category: "Environment"
1232
- },
1233
- {
1234
- name: "block-env-files",
1235
- description: "Block reading/writing .env files",
1236
- fn: blockEnvFiles,
1237
- match: { events: ["PreToolUse"] },
1238
- defaultEnabled: true,
1239
- category: "Environment"
1240
- },
1241
- {
1242
- name: "block-read-outside-cwd",
1243
- description: "Block file reads outside the session working directory",
1244
- fn: blockReadOutsideCwd,
1245
- match: { events: ["PreToolUse"], toolNames: ["Read", "Glob", "Grep", "Bash"] },
1246
- defaultEnabled: false,
1247
- category: "Environment",
1248
- params: {
1249
- allowPaths: {
1250
- type: "string[]",
1251
- description: "Absolute paths outside cwd that are allowed to be read",
1252
- default: []
1253
- }
1254
- }
1255
- },
1256
- {
1257
- name: "block-sudo",
1258
- description: "Block sudo commands",
1259
- fn: blockSudo,
1260
- match: { events: ["PreToolUse"], toolNames: ["Bash"] },
1261
- defaultEnabled: true,
1262
- category: "Dangerous Commands",
1263
- params: {
1264
- allowPatterns: {
1265
- type: "string[]",
1266
- description: "Sudo command patterns to allow, matched token-by-token (e.g. 'sudo systemctl status')",
1267
- default: []
1268
- }
1269
- }
1270
- },
1271
- {
1272
- name: "block-curl-pipe-sh",
1273
- description: "Block piping downloads to shell",
1274
- fn: blockCurlPipeSh,
1275
- match: { events: ["PreToolUse"], toolNames: ["Bash"] },
1276
- defaultEnabled: true,
1277
- category: "Dangerous Commands"
1278
- },
1279
- {
1280
- name: "block-rm-rf",
1281
- description: "Prevent catastrophic deletions",
1282
- fn: blockRmRf,
1283
- match: { events: ["PreToolUse"], toolNames: ["Bash"] },
1284
- defaultEnabled: false,
1285
- category: "Dangerous Commands",
1286
- params: {
1287
- allowPaths: {
1288
- type: "string[]",
1289
- description: "Paths that are allowed to be recursively deleted",
1290
- default: []
1291
- }
1292
- }
1293
- },
1294
- {
1295
- name: "block-failproofai-commands",
1296
- description: "Block failproofai CLI commands and uninstallation",
1297
- fn: blockFailproofaiCommands,
1298
- match: { events: ["PreToolUse"], toolNames: ["Bash"] },
1299
- defaultEnabled: true,
1300
- category: "Dangerous Commands"
1301
- },
1302
- {
1303
- name: "block-secrets-write",
1304
- description: "Block writing secret key files",
1305
- fn: blockSecretsWrite,
1306
- match: { events: ["PreToolUse"], toolNames: ["Write"] },
1307
- defaultEnabled: false,
1308
- category: "Dangerous Commands",
1309
- params: {
1310
- additionalPatterns: {
1311
- type: "string[]",
1312
- description: "Additional filename patterns (substrings) to block",
1313
- default: []
1314
- }
1315
- }
1316
- },
1317
- {
1318
- name: "block-push-master",
1319
- description: "Block pushing to main/master",
1320
- fn: blockPushMaster,
1321
- match: { events: ["PreToolUse"], toolNames: ["Bash"] },
1322
- defaultEnabled: true,
1323
- category: "Git",
1324
- params: {
1325
- protectedBranches: {
1326
- type: "string[]",
1327
- description: "Branch names to protect from direct pushes",
1328
- default: ["main", "master"]
1329
- }
1330
- }
1331
- },
1332
- {
1333
- name: "block-force-push",
1334
- description: "Prevent force-pushing to any branch",
1335
- fn: blockForcePush,
1336
- match: { events: ["PreToolUse"], toolNames: ["Bash"] },
1337
- defaultEnabled: false,
1338
- category: "Git"
1339
- },
1340
- {
1341
- name: "block-work-on-main",
1342
- description: "Block git commits and merges on main/master branch",
1343
- fn: blockWorkOnMain,
1344
- match: { events: ["PreToolUse"], toolNames: ["Bash"] },
1345
- defaultEnabled: false,
1346
- category: "Git",
1347
- params: {
1348
- protectedBranches: {
1349
- type: "string[]",
1350
- description: "Branch names where commits/merges are blocked",
1351
- default: ["main", "master"]
1352
- }
1353
- }
1354
- },
1355
- {
1356
- name: "warn-git-amend",
1357
- description: "Warns before amending git commits, which rewrites history",
1358
- fn: warnGitAmend,
1359
- match: { events: ["PreToolUse"], toolNames: ["Bash"] },
1360
- defaultEnabled: false,
1361
- category: "Git"
1362
- },
1363
- {
1364
- name: "warn-git-stash-drop",
1365
- description: "Warns before permanently deleting stashed changes",
1366
- fn: warnGitStashDrop,
1367
- match: { events: ["PreToolUse"], toolNames: ["Bash"] },
1368
- defaultEnabled: false,
1369
- category: "Git"
1370
- },
1371
- {
1372
- name: "warn-all-files-staged",
1373
- description: "Warns before staging all working tree files with git add -A / . / --all",
1374
- fn: warnAllFilesStaged,
1375
- match: { events: ["PreToolUse"], toolNames: ["Bash"] },
1376
- defaultEnabled: false,
1377
- category: "Git"
1378
- },
1379
- {
1380
- name: "warn-destructive-sql",
1381
- description: "Warn before executing destructive SQL (DROP/TRUNCATE/DELETE without WHERE) via database clients",
1382
- fn: warnDestructiveSql,
1383
- match: { events: ["PreToolUse"], toolNames: ["Bash"] },
1384
- defaultEnabled: false,
1385
- category: "Database"
1386
- },
1387
- {
1388
- name: "warn-schema-alteration",
1389
- description: "Warns before SQL schema changes (ALTER TABLE with column or rename operations)",
1390
- fn: warnSchemaAlteration,
1391
- match: { events: ["PreToolUse"], toolNames: ["Bash"] },
1392
- defaultEnabled: false,
1393
- category: "Database"
1394
- },
1395
- {
1396
- name: "warn-package-publish",
1397
- description: "Warn before publishing packages to public registries (npm, PyPI, crates.io, RubyGems, etc.)",
1398
- fn: warnPackagePublish,
1399
- match: { events: ["PreToolUse"], toolNames: ["Bash"] },
1400
- defaultEnabled: false,
1401
- category: "Packages & System"
1402
- },
1403
- {
1404
- name: "warn-global-package-install",
1405
- description: "Warns before installing packages globally (npm -g, cargo install, etc.)",
1406
- fn: warnGlobalPackageInstall,
1407
- match: { events: ["PreToolUse"], toolNames: ["Bash"] },
1408
- defaultEnabled: false,
1409
- category: "Packages & System"
1410
- },
1411
- {
1412
- name: "prefer-package-manager",
1413
- description: "Blocks non-preferred package managers and tells Claude to use an allowed one (e.g., uv instead of pip)",
1414
- fn: preferPackageManager,
1415
- match: { events: ["PreToolUse"], toolNames: ["Bash"] },
1416
- defaultEnabled: false,
1417
- category: "Packages & System",
1418
- params: {
1419
- allowed: {
1420
- type: "string[]",
1421
- description: "Allowed package manager names (e.g. ['uv', 'bun']). Any detected manager not in this list is blocked.",
1422
- default: []
1423
- },
1424
- blocked: {
1425
- type: "string[]",
1426
- description: "Additional manager names to block beyond the built-in list (e.g. ['pdm', 'pipx']).",
1427
- default: []
1428
- }
1429
- }
1430
- },
1431
- {
1432
- name: "warn-large-file-write",
1433
- description: "Warn before writing files larger than 1MB (configurable via thresholdKb param)",
1434
- fn: warnLargeFileWrite,
1435
- match: { events: ["PreToolUse"], toolNames: ["Write"] },
1436
- defaultEnabled: false,
1437
- category: "Packages & System",
1438
- params: {
1439
- thresholdKb: {
1440
- type: "number",
1441
- description: "File size threshold in KB above which a warning is issued",
1442
- default: 1024
1443
- }
1444
- }
1445
- },
1446
- {
1447
- name: "warn-background-process",
1448
- description: "Warns before starting detached or background processes",
1449
- fn: warnBackgroundProcess,
1450
- match: { events: ["PreToolUse"], toolNames: ["Bash"] },
1451
- defaultEnabled: false,
1452
- category: "Packages & System"
1453
- },
1454
- {
1455
- name: "warn-repeated-tool-calls",
1456
- description: "Warn when the same tool is called 3+ times with identical parameters",
1457
- fn: warnRepeatedToolCalls,
1458
- match: { events: ["PreToolUse"] },
1459
- defaultEnabled: false,
1460
- category: "AI Behavior"
1461
- },
1462
- {
1463
- name: "require-commit-before-stop",
1464
- description: "Require all changes to be committed before Claude stops",
1465
- fn: requireCommitBeforeStop,
1466
- match: { events: ["Stop"] },
1467
- defaultEnabled: false,
1468
- category: "Workflow"
1469
- },
1470
- {
1471
- name: "require-push-before-stop",
1472
- description: "Require all commits to be pushed to remote before Claude stops",
1473
- fn: requirePushBeforeStop,
1474
- match: { events: ["Stop"] },
1475
- defaultEnabled: false,
1476
- category: "Workflow",
1477
- params: {
1478
- remote: {
1479
- type: "string",
1480
- description: "Remote name to push to (default: origin)",
1481
- default: "origin"
1482
- },
1483
- baseBranch: {
1484
- type: "string",
1485
- description: "Base branch to compare against (default: main)",
1486
- default: "main"
1487
- }
1488
- }
1489
- },
1490
- {
1491
- name: "require-pr-before-stop",
1492
- description: "Require a pull request to exist for the current branch before Claude stops",
1493
- fn: requirePrBeforeStop,
1494
- match: { events: ["Stop"] },
1495
- defaultEnabled: false,
1496
- category: "Workflow",
1497
- params: {
1498
- baseBranch: {
1499
- type: "string",
1500
- description: "Base branch to compare against (default: main)",
1501
- default: "main"
1502
- }
1503
- }
1504
- },
1505
- {
1506
- name: "require-ci-green-before-stop",
1507
- description: "Require CI checks to pass on the current branch before Claude stops",
1508
- fn: requireCiGreenBeforeStop,
1509
- match: { events: ["Stop"] },
1510
- defaultEnabled: false,
1511
- category: "Workflow"
1512
- }
1513
- ];
1514
- });
1515
-
1516
- // src/hooks/policy-evaluator.ts
1517
- function appendHint(baseReason, hint) {
1518
- const base = baseReason.trim();
1519
- const normalizedHint = typeof hint === "string" ? hint.trim() : "";
1520
- if (!normalizedHint)
1521
- return base;
1522
- if (!base)
1523
- return normalizedHint;
1524
- return `${base}. ${normalizedHint}`;
1525
- }
1526
- async function evaluatePolicies(eventType, payload, session, config) {
1527
- const toolName = payload.tool_name;
1528
- const toolInput = payload.tool_input;
1529
- const policies = getPoliciesForEvent(eventType, toolName);
1530
- hookLogInfo(`evaluating ${policies.length} policies for ${eventType}`);
1531
- if (policies.length === 0) {
1532
- return { exitCode: 0, stdout: "", stderr: "", policyName: null, reason: null, decision: "allow" };
1533
- }
1534
- const baseCtx = {
1535
- eventType,
1536
- payload,
1537
- toolName,
1538
- toolInput,
1539
- session
1540
- };
1541
- const instructEntries = [];
1542
- const allowEntries = [];
1543
- for (const policy of policies) {
1544
- const schema = POLICY_PARAMS_MAP.get(policy.name);
1545
- let ctx;
1546
- if (schema) {
1547
- const userParams = config?.policyParams?.[policy.name] ?? {};
1548
- const resolvedParams = {};
1549
- for (const [key, spec] of Object.entries(schema)) {
1550
- resolvedParams[key] = key in userParams ? userParams[key] : spec.default;
1551
- }
1552
- ctx = { ...baseCtx, params: resolvedParams };
1553
- } else {
1554
- ctx = { ...baseCtx, params: {} };
1555
- }
1556
- let result;
1557
- try {
1558
- result = await policy.fn(ctx);
1559
- } catch (err) {
1560
- hookLogWarn(`policy "${policy.name}" threw: ${err instanceof Error ? err.message : String(err)}`);
1561
- continue;
1562
- }
1563
- if (result.decision === "deny") {
1564
- const reason = appendHint(result.reason ?? `Blocked by policy: ${policy.name}`, config?.policyParams?.[policy.name]?.hint);
1565
- hookLogInfo(`deny by "${policy.name}": ${reason}`);
1566
- const displayTool = ctx.toolName ?? "unknown tool";
1567
- if (eventType === "PreToolUse") {
1568
- const response = {
1569
- hookSpecificOutput: {
1570
- hookEventName: eventType,
1571
- permissionDecision: "deny",
1572
- permissionDecisionReason: `Blocked ${displayTool} by failproofai because: ${reason}, as per the policy configured by the user`
1573
- }
1574
- };
1575
- return {
1576
- exitCode: 0,
1577
- stdout: JSON.stringify(response),
1578
- stderr: "",
1579
- policyName: policy.name,
1580
- reason,
1581
- decision: "deny"
1582
- };
1583
- }
1584
- if (eventType === "PostToolUse") {
1585
- const response = {
1586
- hookSpecificOutput: {
1587
- hookEventName: eventType,
1588
- additionalContext: `Blocked ${displayTool} by failproofai because: ${reason}, as per the policy configured by the user`
1589
- }
1590
- };
1591
- return {
1592
- exitCode: 0,
1593
- stdout: JSON.stringify(response),
1594
- stderr: "",
1595
- policyName: policy.name,
1596
- reason,
1597
- decision: "deny"
1598
- };
1599
- }
1600
- if (eventType === "Stop") {
1601
- return {
1602
- exitCode: 2,
1603
- stdout: "",
1604
- stderr: `MANDATORY ACTION REQUIRED from failproofai (policy: ${policy.name}): ${reason}
1605
-
1606
- You MUST complete the above action NOW. Do NOT ask the user for confirmation — execute the required action, then attempt to finish your task again.`,
1607
- policyName: policy.name,
1608
- reason,
1609
- decision: "deny"
1610
- };
1611
- }
1612
- return {
1613
- exitCode: 2,
1614
- stdout: "",
1615
- stderr: reason,
1616
- policyName: policy.name,
1617
- reason,
1618
- decision: "deny"
1619
- };
1620
- }
1621
- if (result.decision === "instruct") {
1622
- const reason = appendHint(result.reason ?? `Instruction from policy: ${policy.name}`, config?.policyParams?.[policy.name]?.hint);
1623
- instructEntries.push({ policyName: policy.name, reason });
1624
- hookLogInfo(`instruct by "${policy.name}": ${reason}`);
1625
- }
1626
- if (result.decision === "allow" && result.reason) {
1627
- allowEntries.push({ policyName: policy.name, reason: result.reason });
1628
- }
1629
- }
1630
- if (instructEntries.length > 0) {
1631
- const combined = instructEntries.map((e) => e.reason).join(`
1632
- `);
1633
- const policyNames = instructEntries.map((e) => e.policyName);
1634
- if (eventType === "Stop") {
1635
- const policyAttribution = policyNames.length === 1 ? `policy: ${policyNames[0]}` : `policies: ${policyNames.join(", ")}`;
1636
- return {
1637
- exitCode: 2,
1638
- stdout: "",
1639
- stderr: `MANDATORY ACTION REQUIRED from failproofai (${policyAttribution}): ${combined}
1640
-
1641
- You MUST complete the above action(s) NOW. Do NOT ask the user for confirmation — execute the required action(s), then attempt to finish your task again.`,
1642
- policyName: policyNames[0],
1643
- policyNames,
1644
- reason: combined,
1645
- decision: "instruct"
1646
- };
1647
- }
1648
- const response = {
1649
- hookSpecificOutput: {
1650
- hookEventName: eventType,
1651
- additionalContext: `Instruction from failproofai: ${combined}`
1652
- }
1653
- };
1654
- return {
1655
- exitCode: 0,
1656
- stdout: JSON.stringify(response),
1657
- stderr: "",
1658
- policyName: policyNames[0],
1659
- policyNames,
1660
- reason: combined,
1661
- decision: "instruct"
1662
- };
1663
- }
1664
- if (allowEntries.length > 0) {
1665
- const combined = allowEntries.map((e) => e.reason).join(`
1666
- `);
1667
- const policyNames = allowEntries.map((e) => e.policyName);
1668
- const supportsHookSpecificOutput = eventType === "PreToolUse" || eventType === "PostToolUse" || eventType === "UserPromptSubmit";
1669
- const response = supportsHookSpecificOutput ? { hookSpecificOutput: { hookEventName: eventType, additionalContext: `Note from failproofai: ${combined}` } } : { reason: combined };
1670
- const stderrMsg = allowEntries.map((e) => `[failproofai] ${e.policyName}: ${e.reason}`).join(`
1671
- `);
1672
- return { exitCode: 0, stdout: JSON.stringify(response), stderr: stderrMsg + `
1673
- `, policyName: policyNames[0], policyNames, reason: combined, decision: "allow" };
1674
- }
1675
- return { exitCode: 0, stdout: "", stderr: "", policyName: null, reason: null, decision: "allow" };
1676
- }
1677
- var POLICY_PARAMS_MAP;
1678
- var init_policy_evaluator = __esm(() => {
1679
- init_builtin_policies();
1680
- init_hook_logger();
1681
- POLICY_PARAMS_MAP = new Map(BUILTIN_POLICIES.filter((p) => p.params).map((p) => [p.name, p.params]));
1682
- });
1683
-
1684
- // src/hooks/custom-hooks-registry.ts
1685
- function getRegistry2() {
1686
- const g = globalThis;
1687
- if (!Array.isArray(g[REGISTRY_KEY2]))
1688
- g[REGISTRY_KEY2] = [];
1689
- return g[REGISTRY_KEY2];
1690
- }
1691
- function getCustomHooks() {
1692
- return getRegistry2();
1693
- }
1694
- function clearCustomHooks() {
1695
- const g = globalThis;
1696
- g[REGISTRY_KEY2] = [];
1697
- }
1698
- var REGISTRY_KEY2 = "__failproofai_custom_hooks__";
1699
- var init_custom_hooks_registry = () => {};
1700
-
1701
- // src/hooks/loader-utils.ts
1702
- import { readFile as readFile2, writeFile as writeFile2, unlink, access } from "fs/promises";
1703
- import { resolve as resolve3, dirname as dirname2, relative } from "path";
1704
- import { pathToFileURL } from "url";
1705
- async function fileExists(path) {
1706
- try {
1707
- await access(path);
1708
- return true;
1709
- } catch {
1710
- return false;
1711
- }
1712
- }
1713
- async function findDistIndex() {
1714
- const distPath = process.env.FAILPROOFAI_DIST_PATH;
1715
- if (distPath) {
1716
- const candidate = resolve3(distPath, "index.js");
1717
- if (await fileExists(candidate))
1718
- return candidate;
1719
- }
1720
- const candidates = [
1721
- resolve3(dirname2(process.execPath), "..", "assets", "dist", "index.js"),
1722
- resolve3(process.cwd(), "dist", "index.js"),
1723
- resolve3(process.cwd(), "node_modules", "failproofai", "dist", "index.js")
1724
- ];
1725
- for (const c of candidates) {
1726
- if (await fileExists(c))
1727
- return c;
1728
- }
1729
- return null;
1730
- }
1731
- async function resolveLocalImport(fromDir, specifier) {
1732
- const base = resolve3(fromDir, specifier);
1733
- const candidates = [base, `${base}.js`, `${base}.mjs`, `${base}.ts`, resolve3(base, "index.js")];
1734
- for (const c of candidates) {
1735
- if (await fileExists(c))
1736
- return c;
1737
- }
1738
- return null;
1739
- }
1740
- async function createEsmShim(distIndex, distUrl) {
1741
- const shimPath = distIndex + ".__failproofai_esm_shim__.mjs";
1742
- const shimCode = [
1743
- `import _cjs from '${distUrl}';`,
1744
- `export const customPolicies = _cjs.customPolicies;`,
1745
- `export const getCustomHooks = _cjs.getCustomHooks;`,
1746
- `export const clearCustomHooks = _cjs.clearCustomHooks;`,
1747
- `export const allow = _cjs.allow;`,
1748
- `export const deny = _cjs.deny;`,
1749
- `export const instruct = _cjs.instruct;`,
1750
- `export default _cjs;`
1751
- ].join(`
1752
- `);
1753
- await writeFile2(shimPath, shimCode, "utf-8");
1754
- return { shimPath, shimUrl: pathToFileURL(shimPath).href };
1755
- }
1756
- async function rewriteFileTree(entryPath, distUrl, distIndex) {
1757
- const queue = [entryPath];
1758
- const visited = new Set;
1759
- const tmpFiles = [];
1760
- let esmShimUrl = null;
1761
- if (distIndex && distUrl) {
1762
- const shim = await createEsmShim(distIndex, distUrl);
1763
- tmpFiles.push(shim.shimPath);
1764
- esmShimUrl = shim.shimUrl;
1765
- }
1766
- while (queue.length > 0) {
1767
- const filePath = queue.shift();
1768
- if (visited.has(filePath))
1769
- continue;
1770
- visited.add(filePath);
1771
- let code = await readFile2(filePath, "utf-8");
1772
- if (esmShimUrl) {
1773
- code = code.replace(/from\s+(['"])(?:claudeye|failproofai)\1/g, `from '${esmShimUrl}'`);
1774
- }
1775
- if (distIndex) {
1776
- code = code.replace(/require\s*\(\s*(['"])(?:claudeye|failproofai)\1\s*\)/g, `require('${distIndex.replace(/\\/g, "\\\\")}')`);
1777
- }
1778
- const dir = dirname2(filePath);
1779
- const rewrites = new Map;
1780
- for (const re of [LOCAL_IMPORT_RE, LOCAL_REQUIRE_RE]) {
1781
- const freshRe = new RegExp(re.source, re.flags);
1782
- let match;
1783
- while ((match = freshRe.exec(code)) !== null) {
1784
- const specifier = match[2];
1785
- if (rewrites.has(specifier))
1786
- continue;
1787
- const resolved2 = await resolveLocalImport(dir, specifier);
1788
- if (!resolved2)
1789
- continue;
1790
- if (!visited.has(resolved2) && !queue.includes(resolved2)) {
1791
- queue.push(resolved2);
1792
- }
1793
- let relPath = relative(dir, resolved2 + TMP_SUFFIX).split("\\").join("/");
1794
- if (!relPath.startsWith("."))
1795
- relPath = "./" + relPath;
1796
- rewrites.set(specifier, relPath);
1797
- }
1798
- }
1799
- const sortedSpecs = [...rewrites.keys()].sort((a, b) => b.length - a.length);
1800
- for (const specifier of sortedSpecs) {
1801
- const replacement = rewrites.get(specifier);
1802
- const escaped = specifier.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
1803
- code = code.replace(new RegExp(`'${escaped}'`, "g"), `'${replacement}'`);
1804
- code = code.replace(new RegExp(`"${escaped}"`, "g"), `"${replacement}"`);
1805
- }
1806
- const tmpPath = filePath + TMP_SUFFIX;
1807
- await writeFile2(tmpPath, code, "utf-8");
1808
- tmpFiles.push(tmpPath);
1809
- }
1810
- return tmpFiles;
1811
- }
1812
- async function cleanupTmpFiles(tmpFiles) {
1813
- for (const tmp of tmpFiles) {
1814
- try {
1815
- await unlink(tmp);
1816
- } catch {}
1817
- }
1818
- }
1819
- var TMP_SUFFIX = ".__failproofai_tmp__.mjs", LOCAL_IMPORT_RE, LOCAL_REQUIRE_RE;
1820
- var init_loader_utils = __esm(() => {
1821
- LOCAL_IMPORT_RE = /(?:import\s+(?:[\s\S]*?\s+from\s+)?|export\s+(?:[\s\S]*?\s+from\s+))(['"])(\.\.?\/[^'"]+)\1/g;
1822
- LOCAL_REQUIRE_RE = /require\s*\(\s*(['"])(\.\.?\/[^'"]+)\1\s*\)/g;
1823
- });
1824
-
1825
- // src/hooks/custom-hooks-loader.ts
1826
- import { resolve as resolve4, isAbsolute, basename } from "node:path";
1827
- import { existsSync as existsSync3, readdirSync } from "node:fs";
1828
- import { pathToFileURL as pathToFileURL2 } from "node:url";
1829
- import { homedir as homedir4 } from "node:os";
1830
- function discoverPolicyFiles(dir) {
1831
- if (!existsSync3(dir))
1832
- return [];
1833
- try {
1834
- const entries = readdirSync(dir, { withFileTypes: true });
1835
- return entries.filter((e) => e.isFile() && CONVENTION_FILE_RE.test(e.name)).sort((a, b) => a.name.localeCompare(b.name)).map((e) => resolve4(dir, e.name));
1836
- } catch {
1837
- return [];
1838
- }
1839
- }
1840
- async function loadSingleFile(absPath, opts) {
1841
- const g = globalThis;
1842
- g[LOADING_KEY] = true;
1843
- let tmpFiles = [];
1844
- try {
1845
- const distIndex = await findDistIndex();
1846
- const distUrl = distIndex ? pathToFileURL2(distIndex).href : null;
1847
- tmpFiles = await rewriteFileTree(absPath, distUrl, distIndex);
1848
- const entryTmp = absPath + TMP_SUFFIX;
1849
- const fileUrl = pathToFileURL2(entryTmp).href;
1850
- await import(fileUrl);
1851
- } catch (err) {
1852
- const msg = err instanceof Error ? err.message : String(err);
1853
- if (opts?.strict)
1854
- throw new Error(`Failed to load custom hooks from ${absPath}: ${msg}`);
1855
- hookLogError(`failed to load custom hooks from ${absPath}: ${msg}`);
1856
- } finally {
1857
- g[LOADING_KEY] = false;
1858
- await cleanupTmpFiles(tmpFiles);
1859
- }
1860
- }
1861
- async function loadCustomHooks(customPoliciesPath, opts) {
1862
- if (!customPoliciesPath)
1863
- return [];
1864
- const absPath = isAbsolute(customPoliciesPath) ? customPoliciesPath : resolve4(opts?.sessionCwd ?? process.cwd(), customPoliciesPath);
1865
- if (!existsSync3(absPath)) {
1866
- if (opts?.strict)
1867
- throw new Error(`Custom hooks file not found: ${absPath}`);
1868
- hookLogWarn(`customPoliciesPath not found: ${absPath}`);
1869
- return [];
1870
- }
1871
- clearCustomHooks();
1872
- await loadSingleFile(absPath, opts);
1873
- return getCustomHooks();
1874
- }
1875
- async function loadAllCustomHooks(customPoliciesPath, opts) {
1876
- clearCustomHooks();
1877
- const conventionSources = [];
1878
- if (customPoliciesPath) {
1879
- const absPath = isAbsolute(customPoliciesPath) ? customPoliciesPath : resolve4(opts?.sessionCwd ?? process.cwd(), customPoliciesPath);
1880
- if (existsSync3(absPath)) {
1881
- await loadSingleFile(absPath);
1882
- } else {
1883
- hookLogWarn(`customPoliciesPath not found: ${absPath}`);
1884
- }
1885
- }
1886
- const hooksBeforeConvention = getCustomHooks().length;
1887
- const projectDir = resolve4(opts?.sessionCwd ?? process.cwd(), ".failproofai", "policies");
1888
- const projectFiles = discoverPolicyFiles(projectDir);
1889
- for (const file of projectFiles) {
1890
- const hooksBefore = getCustomHooks().length;
1891
- await loadSingleFile(file);
1892
- const newHooks = getCustomHooks().slice(hooksBefore);
1893
- if (newHooks.length > 0) {
1894
- conventionSources.push({
1895
- scope: "project",
1896
- file: basename(file),
1897
- hookNames: newHooks.map((h) => h.name)
1898
- });
1899
- }
1900
- }
1901
- const userDir = resolve4(homedir4(), ".failproofai", "policies");
1902
- const userFiles = discoverPolicyFiles(userDir);
1903
- for (const file of userFiles) {
1904
- const hooksBefore = getCustomHooks().length;
1905
- await loadSingleFile(file);
1906
- const newHooks = getCustomHooks().slice(hooksBefore);
1907
- if (newHooks.length > 0) {
1908
- conventionSources.push({
1909
- scope: "user",
1910
- file: basename(file),
1911
- hookNames: newHooks.map((h) => h.name)
1912
- });
1913
- }
1914
- }
1915
- const allHooks = getCustomHooks();
1916
- const conventionCount = allHooks.length - hooksBeforeConvention;
1917
- if (projectFiles.length > 0 || userFiles.length > 0) {
1918
- hookLogInfo(`convention policies: ${projectFiles.length} project file(s), ${userFiles.length} user file(s), ${conventionCount} hook(s)`);
1919
- }
1920
- const hookNameToScope = new Map;
1921
- for (const source of conventionSources) {
1922
- for (const name of source.hookNames) {
1923
- hookNameToScope.set(name, source.scope);
1924
- }
1925
- }
1926
- const conventionHookRefs = new Set;
1927
- for (const hook of allHooks.slice(hooksBeforeConvention)) {
1928
- conventionHookRefs.add(hook);
1929
- }
1930
- for (const hook of allHooks) {
1931
- if (conventionHookRefs.has(hook)) {
1932
- hook.__conventionScope = hookNameToScope.get(hook.name) ?? "project";
1933
- }
1934
- }
1935
- return { hooks: allHooks, conventionSources };
1936
- }
1937
- var LOADING_KEY = "__FAILPROOFAI_LOADING_HOOKS__", CONVENTION_FILE_RE;
1938
- var init_custom_hooks_loader = __esm(() => {
1939
- init_hook_logger();
1940
- init_custom_hooks_registry();
1941
- init_loader_utils();
1942
- CONVENTION_FILE_RE = /policies\.(js|mjs|ts)$/;
1943
- });
1944
-
1945
- // src/hooks/hook-activity-store.ts
1946
- import {
1947
- readFileSync as readFileSync2,
1948
- writeFileSync as writeFileSync2,
1949
- appendFileSync as appendFileSync2,
1950
- renameSync as renameSync2,
1951
- readdirSync as readdirSync2,
1952
- mkdirSync as mkdirSync3,
1953
- existsSync as existsSync4,
1954
- statSync as statSync2,
1955
- unlinkSync
1956
- } from "node:fs";
1957
- import { join as join3 } from "node:path";
1958
- import { homedir as homedir5 } from "node:os";
1959
- function ensureDir() {
1960
- if (!existsSync4(storeDir)) {
1961
- mkdirSync3(storeDir, { recursive: true });
1962
- }
1963
- }
1964
- function acquireLock() {
1965
- ensureDir();
1966
- const lockPath = join3(storeDir, LOCK_FILE);
1967
- const deadline = Date.now() + LOCK_STALE_MS;
1968
- while (Date.now() < deadline) {
1969
- try {
1970
- writeFileSync2(lockPath, String(process.pid), { flag: "wx" });
1971
- return;
1972
- } catch (e) {
1973
- if (e.code !== "EEXIST")
1974
- return;
1975
- try {
1976
- const s = statSync2(lockPath);
1977
- if (Date.now() - s.mtimeMs > LOCK_STALE_MS) {
1978
- writeFileSync2(lockPath, String(process.pid), "utf-8");
1979
- return;
1980
- }
1981
- } catch {}
1982
- }
1983
- }
1984
- }
1985
- function releaseLock() {
1986
- try {
1987
- unlinkSync(join3(storeDir, LOCK_FILE));
1988
- } catch {}
1989
- }
1990
- function persistHookActivity(entry) {
1991
- ensureDir();
1992
- acquireLock();
1993
- try {
1994
- const currentPath = join3(storeDir, CURRENT_FILE);
1995
- const countPath = join3(storeDir, COUNT_FILE);
1996
- const lineCount = readCount(countPath);
1997
- if (lineCount >= PAGE_SIZE) {
1998
- try {
1999
- rotate(currentPath, countPath);
2000
- } catch (e) {
2001
- if (e.code !== "ENOENT")
2002
- throw e;
2003
- }
2004
- }
2005
- appendFileSync2(currentPath, JSON.stringify(entry) + `
2006
- `, "utf-8");
2007
- writeCount(countPath, lineCount >= PAGE_SIZE ? 1 : lineCount + 1);
2008
- updateStats(entry);
2009
- } finally {
2010
- releaseLock();
2011
- }
2012
- }
2013
- function rotate(currentPath, countPath) {
2014
- const archiveName = `page-${Date.now()}-${rotateSeq++}.jsonl`;
2015
- const archivePath = join3(storeDir, archiveName);
2016
- renameSync2(currentPath, archivePath);
2017
- writeCount(countPath, 0);
2018
- }
2019
- function readCount(countPath) {
2020
- try {
2021
- const n = parseInt(readFileSync2(countPath, "utf-8"), 10);
2022
- return isNaN(n) ? 0 : n;
2023
- } catch {
2024
- return 0;
2025
- }
2026
- }
2027
- function writeCount(countPath, n) {
2028
- try {
2029
- writeFileSync2(countPath, String(n), "utf-8");
2030
- } catch {}
2031
- }
2032
- function readStoredStats() {
2033
- try {
2034
- return JSON.parse(readFileSync2(join3(storeDir, STATS_FILE), "utf-8"));
2035
- } catch {
2036
- return { totalEvents: 0, denyCount: 0, policyMap: {} };
2037
- }
2038
- }
2039
- function updateStats(entry) {
2040
- const s = readStoredStats();
2041
- s.totalEvents += 1;
2042
- if (entry.decision === "deny")
2043
- s.denyCount += 1;
2044
- if (entry.policyNames && entry.policyNames.length > 0) {
2045
- for (const name of entry.policyNames) {
2046
- s.policyMap[name] = (s.policyMap[name] ?? 0) + 1;
2047
- }
2048
- } else if (entry.policyName) {
2049
- s.policyMap[entry.policyName] = (s.policyMap[entry.policyName] ?? 0) + 1;
2050
- }
2051
- const tmpPath = join3(storeDir, `stats.json.${process.pid}.tmp`);
2052
- try {
2053
- writeFileSync2(tmpPath, JSON.stringify(s), "utf-8");
2054
- renameSync2(tmpPath, join3(storeDir, STATS_FILE));
2055
- } catch {
2056
- try {
2057
- unlinkSync(tmpPath);
2058
- } catch {}
2059
- }
2060
- }
2061
- var PAGE_SIZE = 25, DEFAULT_STORE_DIR, CURRENT_FILE = "current.jsonl", COUNT_FILE = "current.count", STATS_FILE = "stats.json", LOCK_FILE = "current.lock", LOCK_STALE_MS = 2000, storeDir, rotateSeq = 0;
2062
- var init_hook_activity_store = __esm(() => {
2063
- DEFAULT_STORE_DIR = join3(homedir5(), ".failproofai", "cache", "hook-activity");
2064
- storeDir = DEFAULT_STORE_DIR;
2065
- });
2066
-
2067
- // package.json
2068
- var version2 = "0.0.6-beta.3";
2069
- var init_package = () => {};
2070
-
2071
- // src/posthog-key.ts
2072
- var POSTHOG_API_KEY = "phc_Ac1Ww1GqKc0z1SyrRWbmatEeQdlOQIsDEEdP8l8JRgX";
2073
-
2074
- // src/hooks/hook-telemetry.ts
2075
- async function trackHookEvent(distinctId, event, properties) {
2076
- if (process.env.FAILPROOFAI_TELEMETRY_DISABLED === "1")
2077
- return;
2078
- const body = JSON.stringify({
2079
- api_key: process.env.FAILPROOFAI_POSTHOG_KEY ?? API_KEY,
2080
- event,
2081
- distinct_id: distinctId,
2082
- properties: { ...properties, $lib: "failproofai-hooks", failproofai_version: version2 }
2083
- });
2084
- try {
2085
- await fetch(process.env.FAILPROOFAI_POSTHOG_HOST ? `${process.env.FAILPROOFAI_POSTHOG_HOST}/capture/` : CAPTURE_URL, {
2086
- method: "POST",
2087
- headers: { "Content-Type": "application/json" },
2088
- body,
2089
- signal: AbortSignal.timeout(5000)
2090
- });
2091
- } catch {}
2092
- }
2093
- var API_KEY, CAPTURE_URL = "https://us.i.posthog.com/capture/";
2094
- var init_hook_telemetry = __esm(() => {
2095
- init_package();
2096
- API_KEY = POSTHOG_API_KEY;
2097
- });
2098
-
2099
- // lib/telemetry-id.ts
2100
- import fs from "node:fs";
2101
- import path from "node:path";
2102
- import os from "node:os";
2103
- import crypto from "node:crypto";
2104
- import { execSync as execSync2 } from "node:child_process";
2105
- function hashToId(raw) {
2106
- return crypto.createHmac("sha256", NAMESPACE).update(raw).digest("hex");
2107
- }
2108
- function getPlatformMachineId() {
2109
- try {
2110
- const platform = os.platform();
2111
- if (platform === "linux") {
2112
- for (const p of ["/etc/machine-id", "/var/lib/dbus/machine-id"]) {
2113
- try {
2114
- const id = fs.readFileSync(p, "utf-8").trim();
2115
- if (id)
2116
- return id;
2117
- } catch {}
2118
- }
2119
- } else if (platform === "darwin") {
2120
- const out = execSync2("ioreg -rd1 -c IOPlatformExpertDevice", {
2121
- encoding: "utf-8",
2122
- timeout: 3000
2123
- });
2124
- const m = out.match(/"IOPlatformUUID"\s*=\s*"([^"]+)"/);
2125
- if (m?.[1])
2126
- return m[1];
2127
- } else if (platform === "win32") {
2128
- const out = execSync2('reg query "HKLM\\SOFTWARE\\Microsoft\\Cryptography" /v MachineGuid', { encoding: "utf-8", timeout: 3000 });
2129
- const m = out.match(/MachineGuid\s+REG_SZ\s+(\S+)/);
2130
- if (m?.[1])
2131
- return m[1];
2132
- }
2133
- } catch {}
2134
- return;
2135
- }
2136
- function getSystemPropertiesId() {
2137
- return [
2138
- os.hostname(),
2139
- os.homedir(),
2140
- os.platform(),
2141
- os.arch(),
2142
- os.cpus()[0]?.model ?? ""
2143
- ].join(":");
2144
- }
2145
- function getFileBasedId() {
2146
- try {
2147
- const existing = fs.readFileSync(ID_FILE, "utf-8").trim();
2148
- if (existing)
2149
- return existing;
2150
- } catch {}
2151
- const id = crypto.randomUUID();
2152
- try {
2153
- fs.mkdirSync(ID_DIR, { recursive: true });
2154
- fs.writeFileSync(ID_FILE, id, "utf-8");
2155
- } catch {}
2156
- return id;
2157
- }
2158
- function getInstanceId() {
2159
- if (cachedId)
2160
- return cachedId;
2161
- const machineId = getPlatformMachineId();
2162
- if (machineId) {
2163
- cachedId = hashToId(machineId);
2164
- return cachedId;
2165
- }
2166
- const sysProps = getSystemPropertiesId();
2167
- if (sysProps) {
2168
- cachedId = hashToId(sysProps);
2169
- return cachedId;
2170
- }
2171
- cachedId = getFileBasedId();
2172
- return cachedId;
2173
- }
2174
- var NAMESPACE = "failproofai-telemetry-v1", ID_DIR, ID_FILE, cachedId;
2175
- var init_telemetry_id = __esm(() => {
2176
- ID_DIR = path.join(os.homedir(), ".failproofai");
2177
- ID_FILE = path.join(ID_DIR, "instance-id");
2178
- });
2179
-
2180
- // src/auth/token-store.ts
2181
- import {
2182
- readFileSync as readFileSync3,
2183
- writeFileSync as writeFileSync3,
2184
- existsSync as existsSync5,
2185
- mkdirSync as mkdirSync4,
2186
- unlinkSync as unlinkSync2,
2187
- renameSync as renameSync3,
2188
- openSync,
2189
- closeSync
2190
- } from "node:fs";
2191
- import { join as join4 } from "node:path";
2192
- import { homedir as homedir6 } from "node:os";
2193
- function ensureAuthDir() {
2194
- if (!existsSync5(AUTH_DIR))
2195
- mkdirSync4(AUTH_DIR, { recursive: true, mode: 448 });
2196
- }
2197
- function readTokens() {
2198
- if (!existsSync5(AUTH_FILE))
2199
- return null;
2200
- try {
2201
- const raw = readFileSync3(AUTH_FILE, "utf8");
2202
- return JSON.parse(raw);
2203
- } catch {
2204
- return null;
2205
- }
2206
- }
2207
- function writeTokens(tokens) {
2208
- ensureAuthDir();
2209
- const tmpPath = `${AUTH_FILE}.tmp`;
2210
- const fd = openSync(tmpPath, "w", 384);
2211
- try {
2212
- writeFileSync3(fd, JSON.stringify(tokens, null, 2));
2213
- } finally {
2214
- closeSync(fd);
2215
- }
2216
- renameSync3(tmpPath, AUTH_FILE);
2217
- }
2218
- function clearTokens() {
2219
- if (existsSync5(AUTH_FILE))
2220
- unlinkSync2(AUTH_FILE);
2221
- }
2222
- function isLoggedIn() {
2223
- return existsSync5(AUTH_FILE);
2224
- }
2225
- var AUTH_DIR, AUTH_FILE;
2226
- var init_token_store = __esm(() => {
2227
- AUTH_DIR = join4(homedir6(), ".failproofai");
2228
- AUTH_FILE = join4(AUTH_DIR, "auth.json");
2229
- });
2230
-
2231
- // src/relay/queue.ts
2232
- var exports_queue = {};
2233
- __export(exports_queue, {
2234
- readProcessingFile: () => readProcessingFile,
2235
- queueSizeBytes: () => queueSizeBytes,
2236
- findOrphanProcessingFiles: () => findOrphanProcessingFiles,
2237
- deleteProcessingFile: () => deleteProcessingFile,
2238
- claimPendingBatch: () => claimPendingBatch,
2239
- appendToServerQueue: () => appendToServerQueue
2240
- });
2241
- import {
2242
- appendFileSync as appendFileSync3,
2243
- mkdirSync as mkdirSync5,
2244
- existsSync as existsSync6,
2245
- readFileSync as readFileSync4,
2246
- statSync as statSync3,
2247
- renameSync as renameSync4,
2248
- unlinkSync as unlinkSync3,
2249
- readdirSync as readdirSync3,
2250
- chmodSync
2251
- } from "node:fs";
2252
- import { join as join5 } from "node:path";
2253
- import { homedir as homedir7 } from "node:os";
2254
- import { createHash, randomUUID } from "node:crypto";
2255
- function hashCwd(cwd) {
2256
- if (!cwd)
2257
- return null;
2258
- return createHash("sha256").update(cwd).digest("hex");
2259
- }
2260
- function redactReason(reason) {
2261
- if (!reason)
2262
- return reason ?? null;
2263
- return reason.replace(/AKIA[0-9A-Z]{16}/g, "[REDACTED-AWS-KEY]").replace(/eyJ[A-Za-z0-9_=-]+\.[A-Za-z0-9_=-]+\.[A-Za-z0-9_=-]+/g, "[REDACTED-JWT]").replace(/ghp_[A-Za-z0-9]{36,}/g, "[REDACTED-GH-TOKEN]").replace(/sk-[A-Za-z0-9]{20,}/g, "[REDACTED-API-KEY]").replace(/Bearer\s+[A-Za-z0-9_.=+-]+/gi, "Bearer [REDACTED]");
2264
- }
2265
- function sanitize(entry) {
2266
- return {
2267
- client_event_id: randomUUID(),
2268
- timestamp: entry.timestamp,
2269
- event_type: entry.eventType,
2270
- tool_name: entry.toolName ?? null,
2271
- policy_name: entry.policyName ?? null,
2272
- policy_names: entry.policyNames ?? [],
2273
- decision: entry.decision,
2274
- reason: redactReason(entry.reason),
2275
- duration_ms: entry.durationMs,
2276
- session_id: entry.sessionId ?? null,
2277
- cwd_hash: hashCwd(entry.cwd),
2278
- permission_mode: entry.permissionMode ?? null,
2279
- hook_event_name: entry.hookEventName ?? null
2280
- };
2281
- }
2282
- function ensureDir2() {
2283
- if (!existsSync6(QUEUE_DIR)) {
2284
- mkdirSync5(QUEUE_DIR, { recursive: true, mode: 448 });
2285
- }
2286
- }
2287
- function appendToServerQueue(entry) {
2288
- if (!isLoggedIn())
2289
- return;
2290
- ensureDir2();
2291
- try {
2292
- if (existsSync6(PENDING_FILE) && statSync3(PENDING_FILE).size > MAX_QUEUE_BYTES) {
2293
- return;
2294
- }
2295
- } catch {}
2296
- const sanitized = sanitize(entry);
2297
- appendFileSync3(PENDING_FILE, JSON.stringify(sanitized) + `
2298
- `, { mode: 384 });
2299
- try {
2300
- chmodSync(PENDING_FILE, 384);
2301
- } catch {}
2302
- }
2303
- function queueSizeBytes() {
2304
- try {
2305
- return statSync3(PENDING_FILE).size;
2306
- } catch {
2307
- return 0;
2308
- }
2309
- }
2310
- function claimPendingBatch() {
2311
- if (!existsSync6(PENDING_FILE))
2312
- return null;
2313
- try {
2314
- const size = statSync3(PENDING_FILE).size;
2315
- if (size === 0)
2316
- return null;
2317
- } catch {
2318
- return null;
2319
- }
2320
- const seq = `${Date.now()}-${process.pid}`;
2321
- const processingFile = join5(QUEUE_DIR, `${PROCESSING_PREFIX}${seq}.jsonl`);
2322
- try {
2323
- renameSync4(PENDING_FILE, processingFile);
2324
- try {
2325
- chmodSync(processingFile, 384);
2326
- } catch {}
2327
- return processingFile;
2328
- } catch (err) {
2329
- const e = err;
2330
- if (e?.code === "ENOENT")
2331
- return null;
2332
- throw err;
2333
- }
2334
- }
2335
- function findOrphanProcessingFiles() {
2336
- ensureDir2();
2337
- try {
2338
- return readdirSync3(QUEUE_DIR).filter((n) => n.startsWith(PROCESSING_PREFIX) && n.endsWith(".jsonl")).map((n) => join5(QUEUE_DIR, n)).sort();
2339
- } catch {
2340
- return [];
2341
- }
2342
- }
2343
- function readProcessingFile(path2) {
2344
- if (!existsSync6(path2))
2345
- return [];
2346
- const content = readFileSync4(path2, "utf8");
2347
- const out = [];
2348
- for (const line of content.split(`
2349
- `)) {
2350
- const trimmed = line.trim();
2351
- if (!trimmed)
2352
- continue;
2353
- try {
2354
- out.push(JSON.parse(trimmed));
2355
- } catch {}
2356
- }
2357
- return out;
2358
- }
2359
- function deleteProcessingFile(path2) {
2360
- try {
2361
- unlinkSync3(path2);
2362
- } catch {}
2363
- }
2364
- var QUEUE_DIR, PENDING_FILE, PROCESSING_PREFIX = "processing-", MAX_QUEUE_BYTES;
2365
- var init_queue = __esm(() => {
2366
- init_token_store();
2367
- QUEUE_DIR = join5(homedir7(), ".failproofai", "cache", "server-queue");
2368
- PENDING_FILE = join5(QUEUE_DIR, "pending.jsonl");
2369
- MAX_QUEUE_BYTES = 50 * 1024 * 1024;
2370
- });
2371
-
2372
- // src/relay/pid.ts
2373
- import { readFileSync as readFileSync5, writeFileSync as writeFileSync4, existsSync as existsSync7, unlinkSync as unlinkSync4, mkdirSync as mkdirSync6 } from "node:fs";
2374
- import { join as join6, dirname as dirname3 } from "node:path";
2375
- import { homedir as homedir8 } from "node:os";
2376
- function readPid() {
2377
- if (!existsSync7(PID_FILE))
2378
- return null;
2379
- try {
2380
- const raw = readFileSync5(PID_FILE, "utf8").trim();
2381
- const pid = parseInt(raw, 10);
2382
- if (Number.isNaN(pid) || pid <= 0)
2383
- return null;
2384
- return pid;
2385
- } catch {
2386
- return null;
2387
- }
2388
- }
2389
- function writePid(pid) {
2390
- const dir = dirname3(PID_FILE);
2391
- if (!existsSync7(dir))
2392
- mkdirSync6(dir, { recursive: true, mode: 448 });
2393
- writeFileSync4(PID_FILE, String(pid));
2394
- }
2395
- function clearPid() {
2396
- if (existsSync7(PID_FILE))
2397
- unlinkSync4(PID_FILE);
2398
- }
2399
- function isProcessAlive(pid) {
2400
- try {
2401
- process.kill(pid, 0);
2402
- return true;
2403
- } catch (err) {
2404
- const e = err;
2405
- if (e?.code === "EPERM")
2406
- return true;
2407
- return false;
2408
- }
2409
- }
2410
- function stopRelay() {
2411
- const pid = readPid();
2412
- if (pid === null)
2413
- return false;
2414
- if (!isProcessAlive(pid)) {
2415
- clearPid();
2416
- return false;
2417
- }
2418
- try {
2419
- process.kill(pid, "SIGTERM");
2420
- clearPid();
2421
- return true;
2422
- } catch {
2423
- return false;
2424
- }
2425
- }
2426
- var PID_FILE;
2427
- var init_pid = __esm(() => {
2428
- PID_FILE = join6(homedir8(), ".failproofai", "relay.pid");
2429
- });
2430
-
2431
- // src/relay/daemon.ts
2432
- var exports_daemon = {};
2433
- __export(exports_daemon, {
2434
- waitForRelayAlive: () => waitForRelayAlive,
2435
- runOneShotSync: () => runOneShotSync,
2436
- runDaemon: () => runDaemon,
2437
- ensureRelayRunning: () => ensureRelayRunning
2438
- });
2439
- import { spawn } from "node:child_process";
2440
- import { existsSync as existsSync8 } from "node:fs";
2441
- import { join as join7 } from "node:path";
2442
- import { homedir as homedir9 } from "node:os";
2443
- import { randomUUID as randomUUID2 } from "node:crypto";
2444
- function ensureRelayRunning() {
2445
- if (!isLoggedIn())
2446
- return;
2447
- const pid = readPid();
2448
- if (pid !== null && isProcessAlive(pid))
2449
- return;
2450
- if (pid !== null)
2451
- clearPid();
2452
- spawnDaemon();
2453
- }
2454
- function spawnDaemon() {
2455
- const entrypoint = process.env.FAILPROOFAI_RELAY_ENTRYPOINT ?? process.argv[1];
2456
- if (!entrypoint)
2457
- return;
2458
- const child = spawn(process.execPath, [entrypoint, "--relay-daemon"], {
2459
- detached: true,
2460
- stdio: "ignore",
2461
- env: { ...process.env, FAILPROOFAI_DAEMON: "1" }
2462
- });
2463
- child.unref();
2464
- if (typeof child.pid === "number") {
2465
- writePid(child.pid);
2466
- }
2467
- }
2468
- async function waitForRelayAlive(timeoutMs = 2000) {
2469
- const deadline = Date.now() + timeoutMs;
2470
- while (Date.now() < deadline) {
2471
- const pid = readPid();
2472
- if (pid !== null && isProcessAlive(pid))
2473
- return true;
2474
- await new Promise((r) => setTimeout(r, 50));
2475
- }
2476
- return false;
2477
- }
2478
- async function refreshTokenIfNeeded() {
2479
- const tokens = readTokens();
2480
- if (!tokens)
2481
- return null;
2482
- const nowSec = Math.floor(Date.now() / 1000);
2483
- if (tokens.expires_at - nowSec > 300) {
2484
- return tokens.access_token;
2485
- }
2486
- try {
2487
- const resp = await fetch(`${tokens.server_url}/api/v1/auth/refresh`, {
2488
- method: "POST",
2489
- headers: { "Content-Type": "application/json" },
2490
- body: JSON.stringify({ refresh_token: tokens.refresh_token }),
2491
- signal: AbortSignal.timeout(HTTP_TIMEOUT_MS)
2492
- });
2493
- if (!resp.ok)
2494
- return tokens.access_token;
2495
- const refreshed = await resp.json();
2496
- writeTokens({
2497
- ...tokens,
2498
- access_token: refreshed.access_token,
2499
- refresh_token: refreshed.refresh_token,
2500
- expires_at: nowSec + refreshed.expires_in
2501
- });
2502
- return refreshed.access_token;
2503
- } catch {
2504
- return tokens.access_token;
2505
- }
2506
- }
2507
-
2508
- class Relay {
2509
- ws;
2510
- pendingAcks = new Map;
2511
- closed = false;
2512
- constructor(ws) {
2513
- this.ws = ws;
2514
- ws.onmessage = (ev) => this.handleMessage(ev.data);
2515
- ws.onclose = () => this.handleClose();
2516
- ws.onerror = () => this.handleClose();
2517
- }
2518
- handleMessage(data) {
2519
- try {
2520
- const msg = JSON.parse(data);
2521
- if (msg.ack && this.pendingAcks.has(msg.ack)) {
2522
- const resolve5 = this.pendingAcks.get(msg.ack);
2523
- this.pendingAcks.delete(msg.ack);
2524
- resolve5(true);
2525
- }
2526
- } catch {}
2527
- }
2528
- handleClose() {
2529
- this.closed = true;
2530
- for (const [, resolve5] of this.pendingAcks) {
2531
- resolve5(false);
2532
- }
2533
- this.pendingAcks.clear();
2534
- }
2535
- isClosed() {
2536
- return this.closed;
2537
- }
2538
- close() {
2539
- try {
2540
- this.ws.close();
2541
- } catch {}
2542
- }
2543
- async sendBatchAndWaitAck(events) {
2544
- if (this.closed)
2545
- return false;
2546
- const batchId = randomUUID2();
2547
- const ackPromise = new Promise((resolve5) => {
2548
- this.pendingAcks.set(batchId, resolve5);
2549
- setTimeout(() => {
2550
- if (this.pendingAcks.delete(batchId))
2551
- resolve5(false);
2552
- }, ACK_TIMEOUT_MS);
2553
- });
2554
- try {
2555
- this.ws.send(JSON.stringify({ batch_id: batchId, events }));
2556
- } catch {
2557
- this.pendingAcks.delete(batchId);
2558
- return false;
2559
- }
2560
- return ackPromise;
2561
- }
2562
- }
2563
- async function connect(wsUrl, token) {
2564
- const WSCtor = globalThis.WebSocket;
2565
- if (!WSCtor) {
2566
- throw new Error("WebSocket not available in this Node version. Requires Node 22+.");
2567
- }
2568
- const ws = new WSCtor(wsUrl);
2569
- await new Promise((resolve5, reject) => {
2570
- let settled = false;
2571
- const timeout = setTimeout(() => {
2572
- if (settled)
2573
- return;
2574
- settled = true;
2575
- try {
2576
- ws.close();
2577
- } catch {}
2578
- reject(new Error("WebSocket connect timeout"));
2579
- }, WS_CONNECT_TIMEOUT_MS);
2580
- ws.onopen = () => {
2581
- if (settled)
2582
- return;
2583
- settled = true;
2584
- clearTimeout(timeout);
2585
- try {
2586
- ws.send(token);
2587
- resolve5();
2588
- } catch (e) {
2589
- reject(e);
2590
- }
2591
- };
2592
- ws.onerror = (e) => {
2593
- if (settled)
2594
- return;
2595
- settled = true;
2596
- clearTimeout(timeout);
2597
- reject(e);
2598
- };
2599
- ws.onclose = () => {
2600
- if (settled)
2601
- return;
2602
- settled = true;
2603
- clearTimeout(timeout);
2604
- reject(new Error("WebSocket closed before opening"));
2605
- };
2606
- });
2607
- return ws;
2608
- }
2609
- async function sendProcessingFile(relay, path2) {
2610
- const events = readProcessingFile(path2);
2611
- if (events.length === 0)
2612
- return true;
2613
- for (let i = 0;i < events.length; i += BATCH_SIZE) {
2614
- const batch = events.slice(i, i + BATCH_SIZE);
2615
- const ok = await relay.sendBatchAndWaitAck(batch);
2616
- if (!ok)
2617
- return false;
2618
- }
2619
- return true;
2620
- }
2621
- async function runDaemon() {
2622
- let reconnectDelay = RECONNECT_BASE_MS;
2623
- while (true) {
2624
- const token = await refreshTokenIfNeeded();
2625
- const tokens = readTokens();
2626
- if (!token || !tokens) {
2627
- await new Promise((r) => setTimeout(r, 30000));
2628
- continue;
2629
- }
2630
- const wsUrl = `${tokens.server_url.replace(/^http/, "ws")}/ws/events/ingest`;
2631
- try {
2632
- const ws = await connect(wsUrl, token);
2633
- const relay = new Relay(ws);
2634
- reconnectDelay = RECONNECT_BASE_MS;
2635
- for (const orphan of findOrphanProcessingFiles()) {
2636
- if (relay.isClosed())
2637
- break;
2638
- const ok = await sendProcessingFile(relay, orphan);
2639
- if (ok)
2640
- deleteProcessingFile(orphan);
2641
- }
2642
- while (!relay.isClosed()) {
2643
- let processingFile = null;
2644
- try {
2645
- processingFile = claimPendingBatch();
2646
- } catch {}
2647
- if (processingFile) {
2648
- const ok = await sendProcessingFile(relay, processingFile);
2649
- if (ok) {
2650
- deleteProcessingFile(processingFile);
2651
- } else {
2652
- break;
2653
- }
2654
- }
2655
- await new Promise((r) => setTimeout(r, FLUSH_INTERVAL_MS));
2656
- }
2657
- relay.close();
2658
- } catch {}
2659
- if (existsSync8(QUEUE_DIR2)) {}
2660
- await new Promise((r) => setTimeout(r, reconnectDelay));
2661
- reconnectDelay = Math.min(reconnectDelay * 2, RECONNECT_MAX_MS);
2662
- }
2663
- }
2664
- async function runOneShotSync() {
2665
- const token = await refreshTokenIfNeeded();
2666
- const tokens = readTokens();
2667
- if (!token || !tokens) {
2668
- throw new Error("Not logged in. Run `failproofai login` first.");
2669
- }
2670
- let total = 0;
2671
- async function postBatch(events) {
2672
- const resp = await fetch(`${tokens.server_url}/api/v1/events/batch`, {
2673
- method: "POST",
2674
- headers: {
2675
- "Content-Type": "application/json",
2676
- Authorization: `Bearer ${token}`
2677
- },
2678
- body: JSON.stringify({ events }),
2679
- signal: AbortSignal.timeout(HTTP_TIMEOUT_MS)
2680
- });
2681
- if (!resp.ok) {
2682
- throw new Error(`Sync failed: ${resp.status} ${resp.statusText}`);
2683
- }
2684
- }
2685
- for (const orphan of findOrphanProcessingFiles()) {
2686
- const events = readProcessingFile(orphan);
2687
- if (events.length > 0) {
2688
- await postBatch(events);
2689
- total += events.length;
2690
- }
2691
- deleteProcessingFile(orphan);
2692
- }
2693
- const processingFile = claimPendingBatch();
2694
- if (processingFile) {
2695
- const events = readProcessingFile(processingFile);
2696
- if (events.length > 0) {
2697
- await postBatch(events);
2698
- total += events.length;
2699
- }
2700
- deleteProcessingFile(processingFile);
2701
- }
2702
- return total;
2703
- }
2704
- var QUEUE_DIR2, BATCH_SIZE = 100, FLUSH_INTERVAL_MS = 2000, RECONNECT_BASE_MS = 1000, RECONNECT_MAX_MS = 60000, HTTP_TIMEOUT_MS = 1e4, WS_CONNECT_TIMEOUT_MS = 15000, ACK_TIMEOUT_MS = 30000;
2705
- var init_daemon = __esm(() => {
2706
- init_token_store();
2707
- init_pid();
2708
- init_queue();
2709
- QUEUE_DIR2 = join7(homedir9(), ".failproofai", "cache", "server-queue");
2710
- });
2711
-
2712
- // src/hooks/handler.ts
2713
- var exports_handler = {};
2714
- __export(exports_handler, {
2715
- handleHookEvent: () => handleHookEvent
2716
- });
2717
- async function handleHookEvent(eventType) {
2718
- const startTime = performance.now();
2719
- const MAX_STDIN_BYTES = 1048576;
2720
- let payload = "";
2721
- try {
2722
- payload = await new Promise((resolve5, reject) => {
2723
- const chunks = [];
2724
- let totalBytes = 0;
2725
- process.stdin.setEncoding("utf8");
2726
- process.stdin.on("data", (chunk) => {
2727
- totalBytes += Buffer.byteLength(chunk);
2728
- if (totalBytes > MAX_STDIN_BYTES) {
2729
- hookLogWarn(`stdin payload exceeds 1 MB for ${eventType}, discarding`);
2730
- process.stdin.destroy();
2731
- resolve5("");
2732
- return;
2733
- }
2734
- chunks.push(chunk);
2735
- });
2736
- process.stdin.on("end", () => resolve5(chunks.join("")));
2737
- process.stdin.on("error", reject);
2738
- if (process.stdin.readableEnded)
2739
- resolve5("");
2740
- });
2741
- } catch {
2742
- hookLogWarn(`stdin read failed for ${eventType}`);
2743
- }
2744
- let parsed = {};
2745
- if (payload) {
2746
- try {
2747
- parsed = JSON.parse(payload);
2748
- } catch {
2749
- hookLogWarn(`payload parse failed for ${eventType} (${payload.length} bytes)`);
2750
- }
2751
- }
2752
- const session = {
2753
- sessionId: parsed.session_id,
2754
- transcriptPath: parsed.transcript_path,
2755
- cwd: parsed.cwd,
2756
- permissionMode: parsed.permission_mode,
2757
- hookEventName: parsed.hook_event_name
2758
- };
2759
- const config = readMergedHooksConfig(session.cwd);
2760
- clearPolicies();
2761
- registerBuiltinPolicies(config.enabledPolicies);
2762
- const loadResult = await loadAllCustomHooks(config.customPoliciesPath, { sessionCwd: session.cwd });
2763
- const customHooksList = loadResult.hooks;
2764
- const conventionHookNames = new Set(loadResult.conventionSources.flatMap((s) => s.hookNames));
2765
- for (const hook of customHooksList) {
2766
- const hookName = hook.name;
2767
- const conventionScope = hook.__conventionScope;
2768
- const isConvention = !!conventionScope;
2769
- const prefix = isConvention ? `.failproofai-${conventionScope}` : "custom";
2770
- const fn = async (ctx) => {
2771
- try {
2772
- const result2 = await Promise.race([
2773
- hook.fn(ctx),
2774
- new Promise((_, reject) => setTimeout(() => reject(new Error("timeout")), 1e4))
2775
- ]);
2776
- return result2;
2777
- } catch (err) {
2778
- const msg = err instanceof Error ? err.message : String(err);
2779
- const isTimeout = msg === "timeout";
2780
- hookLogWarn(`${prefix} hook "${hookName}" failed: ${msg}`);
2781
- trackHookEvent(getInstanceId(), "custom_hook_error", {
2782
- hook_name: hookName,
2783
- error_type: isTimeout ? "timeout" : "exception",
2784
- event_type: eventType,
2785
- is_convention_policy: isConvention,
2786
- convention_scope: conventionScope ?? null
2787
- });
2788
- return { decision: "allow" };
2789
- }
2790
- };
2791
- registerPolicy(`${prefix}/${hookName}`, hook.description ?? "", fn, hook.match ?? {}, -1);
2792
- }
2793
- if (customHooksList.length > 0) {
2794
- trackHookEvent(getInstanceId(), "custom_hooks_loaded", {
2795
- custom_hooks_count: customHooksList.length,
2796
- custom_hook_names: customHooksList.map((h) => h.name),
2797
- event_types_covered: [...new Set(customHooksList.flatMap((h) => h.match?.events ?? []))]
2798
- });
2799
- }
2800
- if (loadResult.conventionSources.length > 0) {
2801
- trackHookEvent(getInstanceId(), "convention_policies_loaded", {
2802
- event_type: eventType,
2803
- project_file_count: loadResult.conventionSources.filter((s) => s.scope === "project").length,
2804
- user_file_count: loadResult.conventionSources.filter((s) => s.scope === "user").length,
2805
- convention_hook_count: conventionHookNames.size,
2806
- convention_hook_names: [...conventionHookNames]
2807
- });
2808
- }
2809
- hookLogInfo(`event=${eventType} policies=${config.enabledPolicies.length} custom=${customHooksList.length} convention=${conventionHookNames.size}`);
2810
- const result = await evaluatePolicies(eventType, parsed, session, config);
2811
- const durationMs = Math.round(performance.now() - startTime);
2812
- hookLogInfo(`result=${result.decision} policy=${result.policyName ?? "none"} duration=${durationMs}ms`);
2813
- if (result.stdout) {
2814
- process.stdout.write(result.stdout);
2815
- }
2816
- if (result.stderr) {
2817
- process.stderr.write(result.stderr);
2818
- }
2819
- const activityEntry = {
2820
- timestamp: Date.now(),
2821
- eventType,
2822
- toolName: parsed.tool_name ?? null,
2823
- policyName: result.policyName,
2824
- policyNames: result.policyNames,
2825
- decision: result.decision,
2826
- reason: result.reason,
2827
- durationMs,
2828
- sessionId: session.sessionId,
2829
- transcriptPath: session.transcriptPath,
2830
- cwd: session.cwd,
2831
- permissionMode: session.permissionMode,
2832
- hookEventName: session.hookEventName
2833
- };
2834
- try {
2835
- persistHookActivity(activityEntry);
2836
- } catch {
2837
- hookLogWarn("activity persistence failed");
2838
- }
2839
- try {
2840
- const { appendToServerQueue: appendToServerQueue2 } = await Promise.resolve().then(() => (init_queue(), exports_queue));
2841
- appendToServerQueue2(activityEntry);
2842
- } catch {}
2843
- try {
2844
- const { ensureRelayRunning: ensureRelayRunning2 } = await Promise.resolve().then(() => (init_daemon(), exports_daemon));
2845
- ensureRelayRunning2();
2846
- } catch {}
2847
- if (result.decision === "deny" || result.decision === "instruct") {
2848
- try {
2849
- const isCustomHook = result.policyName?.startsWith("custom/") ?? false;
2850
- const isConventionPolicy = result.policyName?.startsWith(".failproofai-") ?? false;
2851
- const conventionScope = isConventionPolicy ? result.policyName.match(/^\.failproofai-(project|user)\//)?.[1] ?? null : null;
2852
- const hasCustomParams = !isCustomHook && !isConventionPolicy && !!(result.policyName && config.policyParams?.[result.policyName]);
2853
- const paramKeysOverridden = hasCustomParams ? Object.keys(config.policyParams[result.policyName]) : [];
2854
- const distinctId = getInstanceId();
2855
- await trackHookEvent(distinctId, "hook_policy_triggered", {
2856
- event_type: eventType,
2857
- tool_name: parsed.tool_name ?? null,
2858
- policy_name: result.policyName,
2859
- decision: result.decision,
2860
- is_custom_hook: isCustomHook,
2861
- is_convention_policy: isConventionPolicy,
2862
- convention_scope: conventionScope,
2863
- has_custom_params: hasCustomParams,
2864
- param_keys_overridden: paramKeysOverridden
2865
- });
2866
- } catch {}
2867
- }
2868
- return result.exitCode;
2869
- }
2870
- var init_handler = __esm(() => {
2871
- init_hooks_config();
2872
- init_builtin_policies();
2873
- init_policy_evaluator();
2874
- init_custom_hooks_loader();
2875
- init_hook_activity_store();
2876
- init_hook_telemetry();
2877
- init_telemetry_id();
2878
- init_hook_logger();
2879
- });
2880
-
2881
- // src/relay/daemon.ts
2882
- var exports_daemon2 = {};
2883
- __export(exports_daemon2, {
2884
- waitForRelayAlive: () => waitForRelayAlive2,
2885
- runOneShotSync: () => runOneShotSync2,
2886
- runDaemon: () => runDaemon2,
2887
- ensureRelayRunning: () => ensureRelayRunning2
2888
- });
2889
- import { spawn as spawn2 } from "node:child_process";
2890
- import { existsSync as existsSync9 } from "node:fs";
2891
- import { join as join8 } from "node:path";
2892
- import { homedir as homedir10 } from "node:os";
2893
- import { randomUUID as randomUUID3 } from "node:crypto";
2894
- function ensureRelayRunning2() {
2895
- if (!isLoggedIn())
2896
- return;
2897
- const pid = readPid();
2898
- if (pid !== null && isProcessAlive(pid))
2899
- return;
2900
- if (pid !== null)
2901
- clearPid();
2902
- spawnDaemon2();
2903
- }
2904
- function spawnDaemon2() {
2905
- const entrypoint = process.env.FAILPROOFAI_RELAY_ENTRYPOINT ?? process.argv[1];
2906
- if (!entrypoint)
2907
- return;
2908
- const child = spawn2(process.execPath, [entrypoint, "--relay-daemon"], {
2909
- detached: true,
2910
- stdio: "ignore",
2911
- env: { ...process.env, FAILPROOFAI_DAEMON: "1" }
2912
- });
2913
- child.unref();
2914
- if (typeof child.pid === "number") {
2915
- writePid(child.pid);
2916
- }
2917
- }
2918
- async function waitForRelayAlive2(timeoutMs = 2000) {
2919
- const deadline = Date.now() + timeoutMs;
2920
- while (Date.now() < deadline) {
2921
- const pid = readPid();
2922
- if (pid !== null && isProcessAlive(pid))
2923
- return true;
2924
- await new Promise((r) => setTimeout(r, 50));
2925
- }
2926
- return false;
2927
- }
2928
- async function refreshTokenIfNeeded2() {
2929
- const tokens = readTokens();
2930
- if (!tokens)
2931
- return null;
2932
- const nowSec = Math.floor(Date.now() / 1000);
2933
- if (tokens.expires_at - nowSec > 300) {
2934
- return tokens.access_token;
2935
- }
2936
- try {
2937
- const resp = await fetch(`${tokens.server_url}/api/v1/auth/refresh`, {
2938
- method: "POST",
2939
- headers: { "Content-Type": "application/json" },
2940
- body: JSON.stringify({ refresh_token: tokens.refresh_token }),
2941
- signal: AbortSignal.timeout(HTTP_TIMEOUT_MS2)
2942
- });
2943
- if (!resp.ok)
2944
- return tokens.access_token;
2945
- const refreshed = await resp.json();
2946
- writeTokens({
2947
- ...tokens,
2948
- access_token: refreshed.access_token,
2949
- refresh_token: refreshed.refresh_token,
2950
- expires_at: nowSec + refreshed.expires_in
2951
- });
2952
- return refreshed.access_token;
2953
- } catch {
2954
- return tokens.access_token;
2955
- }
2956
- }
2957
-
2958
- class Relay2 {
2959
- ws;
2960
- pendingAcks = new Map;
2961
- closed = false;
2962
- constructor(ws) {
2963
- this.ws = ws;
2964
- ws.onmessage = (ev) => this.handleMessage(ev.data);
2965
- ws.onclose = () => this.handleClose();
2966
- ws.onerror = () => this.handleClose();
2967
- }
2968
- handleMessage(data) {
2969
- try {
2970
- const msg = JSON.parse(data);
2971
- if (msg.ack && this.pendingAcks.has(msg.ack)) {
2972
- const resolve5 = this.pendingAcks.get(msg.ack);
2973
- this.pendingAcks.delete(msg.ack);
2974
- resolve5(true);
2975
- }
2976
- } catch {}
2977
- }
2978
- handleClose() {
2979
- this.closed = true;
2980
- for (const [, resolve5] of this.pendingAcks) {
2981
- resolve5(false);
2982
- }
2983
- this.pendingAcks.clear();
2984
- }
2985
- isClosed() {
2986
- return this.closed;
2987
- }
2988
- close() {
2989
- try {
2990
- this.ws.close();
2991
- } catch {}
2992
- }
2993
- async sendBatchAndWaitAck(events) {
2994
- if (this.closed)
2995
- return false;
2996
- const batchId = randomUUID3();
2997
- const ackPromise = new Promise((resolve5) => {
2998
- this.pendingAcks.set(batchId, resolve5);
2999
- setTimeout(() => {
3000
- if (this.pendingAcks.delete(batchId))
3001
- resolve5(false);
3002
- }, ACK_TIMEOUT_MS2);
3003
- });
3004
- try {
3005
- this.ws.send(JSON.stringify({ batch_id: batchId, events }));
3006
- } catch {
3007
- this.pendingAcks.delete(batchId);
3008
- return false;
3009
- }
3010
- return ackPromise;
3011
- }
3012
- }
3013
- async function connect2(wsUrl, token) {
3014
- const WSCtor = globalThis.WebSocket;
3015
- if (!WSCtor) {
3016
- throw new Error("WebSocket not available in this Node version. Requires Node 22+.");
3017
- }
3018
- const ws = new WSCtor(wsUrl);
3019
- await new Promise((resolve5, reject) => {
3020
- let settled = false;
3021
- const timeout = setTimeout(() => {
3022
- if (settled)
3023
- return;
3024
- settled = true;
3025
- try {
3026
- ws.close();
3027
- } catch {}
3028
- reject(new Error("WebSocket connect timeout"));
3029
- }, WS_CONNECT_TIMEOUT_MS2);
3030
- ws.onopen = () => {
3031
- if (settled)
3032
- return;
3033
- settled = true;
3034
- clearTimeout(timeout);
3035
- try {
3036
- ws.send(token);
3037
- resolve5();
3038
- } catch (e) {
3039
- reject(e);
3040
- }
3041
- };
3042
- ws.onerror = (e) => {
3043
- if (settled)
3044
- return;
3045
- settled = true;
3046
- clearTimeout(timeout);
3047
- reject(e);
3048
- };
3049
- ws.onclose = () => {
3050
- if (settled)
3051
- return;
3052
- settled = true;
3053
- clearTimeout(timeout);
3054
- reject(new Error("WebSocket closed before opening"));
3055
- };
3056
- });
3057
- return ws;
3058
- }
3059
- async function sendProcessingFile2(relay, path2) {
3060
- const events = readProcessingFile(path2);
3061
- if (events.length === 0)
3062
- return true;
3063
- for (let i = 0;i < events.length; i += BATCH_SIZE2) {
3064
- const batch = events.slice(i, i + BATCH_SIZE2);
3065
- const ok = await relay.sendBatchAndWaitAck(batch);
3066
- if (!ok)
3067
- return false;
3068
- }
3069
- return true;
3070
- }
3071
- async function runDaemon2() {
3072
- let reconnectDelay = RECONNECT_BASE_MS2;
3073
- while (true) {
3074
- const token = await refreshTokenIfNeeded2();
3075
- const tokens = readTokens();
3076
- if (!token || !tokens) {
3077
- await new Promise((r) => setTimeout(r, 30000));
3078
- continue;
3079
- }
3080
- const wsUrl = `${tokens.server_url.replace(/^http/, "ws")}/ws/events/ingest`;
3081
- try {
3082
- const ws = await connect2(wsUrl, token);
3083
- const relay = new Relay2(ws);
3084
- reconnectDelay = RECONNECT_BASE_MS2;
3085
- for (const orphan of findOrphanProcessingFiles()) {
3086
- if (relay.isClosed())
3087
- break;
3088
- const ok = await sendProcessingFile2(relay, orphan);
3089
- if (ok)
3090
- deleteProcessingFile(orphan);
3091
- }
3092
- while (!relay.isClosed()) {
3093
- let processingFile = null;
3094
- try {
3095
- processingFile = claimPendingBatch();
3096
- } catch {}
3097
- if (processingFile) {
3098
- const ok = await sendProcessingFile2(relay, processingFile);
3099
- if (ok) {
3100
- deleteProcessingFile(processingFile);
3101
- } else {
3102
- break;
3103
- }
3104
- }
3105
- await new Promise((r) => setTimeout(r, FLUSH_INTERVAL_MS2));
3106
- }
3107
- relay.close();
3108
- } catch {}
3109
- if (existsSync9(QUEUE_DIR3)) {}
3110
- await new Promise((r) => setTimeout(r, reconnectDelay));
3111
- reconnectDelay = Math.min(reconnectDelay * 2, RECONNECT_MAX_MS2);
3112
- }
3113
- }
3114
- async function runOneShotSync2() {
3115
- const token = await refreshTokenIfNeeded2();
3116
- const tokens = readTokens();
3117
- if (!token || !tokens) {
3118
- throw new Error("Not logged in. Run `failproofai login` first.");
3119
- }
3120
- let total = 0;
3121
- async function postBatch(events) {
3122
- const resp = await fetch(`${tokens.server_url}/api/v1/events/batch`, {
3123
- method: "POST",
3124
- headers: {
3125
- "Content-Type": "application/json",
3126
- Authorization: `Bearer ${token}`
3127
- },
3128
- body: JSON.stringify({ events }),
3129
- signal: AbortSignal.timeout(HTTP_TIMEOUT_MS2)
3130
- });
3131
- if (!resp.ok) {
3132
- throw new Error(`Sync failed: ${resp.status} ${resp.statusText}`);
3133
- }
3134
- }
3135
- for (const orphan of findOrphanProcessingFiles()) {
3136
- const events = readProcessingFile(orphan);
3137
- if (events.length > 0) {
3138
- await postBatch(events);
3139
- total += events.length;
3140
- }
3141
- deleteProcessingFile(orphan);
3142
- }
3143
- const processingFile = claimPendingBatch();
3144
- if (processingFile) {
3145
- const events = readProcessingFile(processingFile);
3146
- if (events.length > 0) {
3147
- await postBatch(events);
3148
- total += events.length;
3149
- }
3150
- deleteProcessingFile(processingFile);
3151
- }
3152
- return total;
3153
- }
3154
- var QUEUE_DIR3, BATCH_SIZE2 = 100, FLUSH_INTERVAL_MS2 = 2000, RECONNECT_BASE_MS2 = 1000, RECONNECT_MAX_MS2 = 60000, HTTP_TIMEOUT_MS2 = 1e4, WS_CONNECT_TIMEOUT_MS2 = 15000, ACK_TIMEOUT_MS2 = 30000;
3155
- var init_daemon2 = __esm(() => {
3156
- init_token_store();
3157
- init_pid();
3158
- init_queue();
3159
- QUEUE_DIR3 = join8(homedir10(), ".failproofai", "cache", "server-queue");
3160
- });
3161
-
3162
- // src/hooks/types.ts
3163
- var HOOK_SCOPES, HOOK_EVENT_TYPES, FAILPROOFAI_HOOK_MARKER = "__failproofai_hook__";
3164
- var init_types = __esm(() => {
3165
- HOOK_SCOPES = ["user", "project", "local"];
3166
- HOOK_EVENT_TYPES = [
3167
- "SessionStart",
3168
- "SessionEnd",
3169
- "UserPromptSubmit",
3170
- "PreToolUse",
3171
- "PermissionRequest",
3172
- "PermissionDenied",
3173
- "PostToolUse",
3174
- "PostToolUseFailure",
3175
- "Notification",
3176
- "SubagentStart",
3177
- "SubagentStop",
3178
- "TaskCreated",
3179
- "TaskCompleted",
3180
- "Stop",
3181
- "StopFailure",
3182
- "TeammateIdle",
3183
- "InstructionsLoaded",
3184
- "ConfigChange",
3185
- "CwdChanged",
3186
- "FileChanged",
3187
- "WorktreeCreate",
3188
- "WorktreeRemove",
3189
- "PreCompact",
3190
- "PostCompact",
3191
- "Elicitation",
3192
- "ElicitationResult"
3193
- ];
3194
- });
3195
-
3196
- // src/hooks/install-prompt.ts
3197
- import * as readline from "node:readline";
3198
- async function promptPolicySelection(preSelected, options = {}) {
3199
- const { includeBeta = false } = options;
3200
- if (!process.stdin.isTTY) {
3201
- const available = BUILTIN_POLICIES.filter((p) => includeBeta || !p.beta);
3202
- if (preSelected)
3203
- return preSelected.filter((name) => available.some((p) => p.name === name));
3204
- return available.filter((p) => p.defaultEnabled).map((p) => p.name);
3205
- }
3206
- const preSelectedSet = preSelected ? new Set(preSelected) : null;
3207
- const items = BUILTIN_POLICIES.filter((p) => includeBeta || !p.beta).map((p) => ({
3208
- name: p.name,
3209
- description: p.description,
3210
- category: p.category,
3211
- selected: preSelectedSet ? preSelectedSet.has(p.name) : p.defaultEnabled,
3212
- beta: !!p.beta
3213
- }));
3214
- const total = items.length;
3215
- const WINDOW_SIZE = 8;
3216
- let cursor = 0;
3217
- let search = "";
3218
- let lastLineCount = 0;
3219
- let cursorHidden = false;
3220
- function hideCursor() {
3221
- if (!cursorHidden) {
3222
- process.stdout.write("\x1B[?25l");
3223
- cursorHidden = true;
3224
- }
3225
- }
3226
- function showCursor() {
3227
- if (cursorHidden) {
3228
- process.stdout.write("\x1B[?25h");
3229
- cursorHidden = false;
3230
- }
3231
- }
3232
- function truncateLine(line, width) {
3233
- let visual = 0;
3234
- let result = "";
3235
- let i = 0;
3236
- while (i < line.length) {
3237
- if (line[i] === "\x1B" && line[i + 1] === "[") {
3238
- let j = i + 2;
3239
- while (j < line.length && !/[A-Za-z]/.test(line[j]))
3240
- j++;
3241
- j++;
3242
- result += line.slice(i, j);
3243
- i = j;
3244
- } else {
3245
- if (visual >= width)
3246
- break;
3247
- result += line[i];
3248
- visual++;
3249
- i++;
3250
- }
3251
- }
3252
- return result;
3253
- }
3254
- function getFiltered() {
3255
- if (!search)
3256
- return items;
3257
- const q = search.toLowerCase();
3258
- return items.filter((i) => i.name.toLowerCase().includes(q) || i.description.toLowerCase().includes(q));
3259
- }
3260
- function buildDisplayRows(filtered) {
3261
- const categoryOrder = [];
3262
- const categoryEnabledCount = new Map;
3263
- const categoryTotalCount = new Map;
3264
- for (const p of items) {
3265
- if (!categoryEnabledCount.has(p.category)) {
3266
- categoryOrder.push(p.category);
3267
- categoryEnabledCount.set(p.category, 0);
3268
- categoryTotalCount.set(p.category, 0);
3269
- }
3270
- categoryTotalCount.set(p.category, categoryTotalCount.get(p.category) + 1);
3271
- if (p.selected)
3272
- categoryEnabledCount.set(p.category, categoryEnabledCount.get(p.category) + 1);
3273
- }
3274
- const filteredByCategory = new Map;
3275
- for (const item of filtered) {
3276
- const bucket = filteredByCategory.get(item.category) ?? [];
3277
- bucket.push(item);
3278
- filteredByCategory.set(item.category, bucket);
3279
- }
3280
- const rows = [];
3281
- let idx = 0;
3282
- for (const cat of categoryOrder) {
3283
- const catFiltered = filteredByCategory.get(cat);
3284
- if (!catFiltered || catFiltered.length === 0)
3285
- continue;
3286
- rows.push({ kind: "header", category: cat, enabledCount: categoryEnabledCount.get(cat), totalCount: categoryTotalCount.get(cat) });
3287
- for (const item of catFiltered) {
3288
- rows.push({ kind: "item", item, filteredIndex: idx++ });
3289
- }
3290
- }
3291
- return rows;
3292
- }
3293
- function render() {
3294
- const cols = process.stdout.columns || 120;
3295
- hideCursor();
3296
- const filtered = getFiltered();
3297
- const shown = filtered.length;
3298
- if (shown > 0 && cursor >= shown)
3299
- cursor = shown - 1;
3300
- const lines = [];
3301
- lines.push(" Failproof AI — Policy Manager");
3302
- lines.push("");
3303
- const innerWidth = Math.max(20, cols - 6);
3304
- const topBorder = " ┌" + "─".repeat(innerWidth + 2) + "┐";
3305
- const botBorder = " └" + "─".repeat(innerWidth + 2) + "┘";
3306
- const cursorChar = "\x1B[7m \x1B[0m";
3307
- const countPart = search ? ` \x1B[2m(${shown}/${total})\x1B[0m` : ` \x1B[2m(${total} policies)\x1B[0m`;
3308
- const searchContent = `\x1B[1mSearch:\x1B[0m ${search}${cursorChar}${countPart}`;
3309
- lines.push(topBorder);
3310
- lines.push(` │ ${searchContent}`);
3311
- lines.push(botBorder);
3312
- lines.push("");
3313
- if (shown === 0) {
3314
- lines.push(" \x1B[2mNo policies match “" + search + "”\x1B[0m");
3315
- for (let i = 0;i < WINDOW_SIZE + 1; i++)
3316
- lines.push("");
3317
- } else {
3318
- const displayRows = buildDisplayRows(filtered);
3319
- let cursorDisplayRow = 0;
3320
- for (let i = 0;i < displayRows.length; i++) {
3321
- const row = displayRows[i];
3322
- if (row.kind === "item" && row.filteredIndex === cursor) {
3323
- cursorDisplayRow = i;
3324
- break;
3325
- }
3326
- }
3327
- let windowStart = cursorDisplayRow - Math.floor(WINDOW_SIZE / 2);
3328
- windowStart = Math.max(0, windowStart);
3329
- windowStart = Math.min(windowStart, Math.max(0, displayRows.length - WINDOW_SIZE));
3330
- const windowEnd = Math.min(displayRows.length, windowStart + WINDOW_SIZE);
3331
- const aboveItems = displayRows.slice(0, windowStart).filter((r) => r.kind === "item").length;
3332
- if (aboveItems > 0) {
3333
- lines.push(` \x1B[2m ↑ ${aboveItems} more above\x1B[0m`);
3334
- } else {
3335
- lines.push("");
3336
- }
3337
- for (let i = windowStart;i < windowEnd; i++) {
3338
- const row = displayRows[i];
3339
- if (row.kind === "header") {
3340
- const label = ` ${row.category.toUpperCase()} (${row.enabledCount}/${row.totalCount}) `;
3341
- const prefix = "── ";
3342
- const prefixLen = 3 + label.length;
3343
- const dashLen = Math.max(2, cols - 2 - prefixLen);
3344
- lines.push(` \x1B[2m${prefix}${label}${"─".repeat(dashLen)}\x1B[0m`);
3345
- } else {
3346
- const item = row.item;
3347
- const isActive = row.filteredIndex === cursor;
3348
- const pointer = isActive ? "\x1B[36m❯\x1B[0m" : " ";
3349
- const check = item.selected ? "\x1B[32m[✓]\x1B[0m" : "[ ]";
3350
- const namePart = isActive ? `\x1B[1;36m${item.name}\x1B[0m` : item.name;
3351
- const betaPart = item.beta ? " \x1B[35m[beta]\x1B[0m" : "";
3352
- const pad = " ".repeat(Math.max(1, 28 - item.name.length));
3353
- const desc = `\x1B[2m${item.description}\x1B[0m`;
3354
- lines.push(` ${pointer} ${check} ${namePart}${betaPart}${pad}${desc}`);
3355
- }
3356
- }
3357
- for (let i = windowEnd - windowStart;i < WINDOW_SIZE; i++) {
3358
- lines.push("");
3359
- }
3360
- const belowItems = displayRows.slice(windowEnd).filter((r) => r.kind === "item").length;
3361
- if (belowItems > 0) {
3362
- lines.push(` \x1B[2m ↓ ${belowItems} more below\x1B[0m`);
3363
- } else {
3364
- lines.push("");
3365
- }
3366
- }
3367
- lines.push("");
3368
- lines.push(" \x1B[2m" + "─".repeat(cols - 2) + "\x1B[0m");
3369
- lines.push(" [↑↓] Move [Space] Toggle [Ctrl+A] All [Ctrl+S] Save [Esc] Clear [^C] Quit");
3370
- lines.push("");
3371
- lines.push(" \x1B[2mTip: `policies` for a flat list · `policies --install <name…>` to skip prompt\x1B[0m");
3372
- if (!includeBeta) {
3373
- lines.push(" \x1B[2mTip: `policies --install --beta` to include beta policies\x1B[0m");
3374
- }
3375
- if (lastLineCount > 0) {
3376
- process.stdout.write(`\x1B[${lastLineCount}A\x1B[J`);
3377
- }
3378
- const output = lines.map((l) => l === "" ? l : truncateLine(l, cols)).join(`
3379
- `) + `
3380
- `;
3381
- process.stdout.write(output);
3382
- lastLineCount = lines.length;
3383
- }
3384
- return new Promise((resolve5) => {
3385
- render();
3386
- process.stdin.setRawMode(true);
3387
- process.stdin.resume();
3388
- readline.emitKeypressEvents(process.stdin);
3389
- function keypressHandler(_str, key) {
3390
- if (!key)
3391
- return;
3392
- if (key.ctrl && key.name === "c") {
3393
- cleanup();
3394
- process.exit(0);
3395
- }
3396
- const filtered = getFiltered();
3397
- if (key.name === "up") {
3398
- if (filtered.length > 0) {
3399
- cursor = cursor > 0 ? cursor - 1 : filtered.length - 1;
3400
- }
3401
- render();
3402
- } else if (key.name === "down") {
3403
- if (filtered.length > 0) {
3404
- cursor = cursor < filtered.length - 1 ? cursor + 1 : 0;
3405
- }
3406
- render();
3407
- } else if (key.name === "return" || key.name === "space") {
3408
- const item = filtered[cursor];
3409
- if (item)
3410
- item.selected = !item.selected;
3411
- render();
3412
- } else if (key.name === "escape") {
3413
- search = "";
3414
- cursor = 0;
3415
- render();
3416
- } else if (key.ctrl && key.name === "a") {
3417
- const allSelected = filtered.length > 0 && filtered.every((i) => i.selected);
3418
- for (const item of filtered)
3419
- item.selected = !allSelected;
3420
- render();
3421
- } else if (key.ctrl && key.name === "s") {
3422
- cleanup();
3423
- const selected = items.filter((i) => i.selected).map((i) => i.name);
3424
- process.stdout.write(`
3425
- `);
3426
- resolve5(selected);
3427
- } else if (key.name === "backspace" || key.name === "delete") {
3428
- if (search.length > 0) {
3429
- search = search.slice(0, -1);
3430
- cursor = 0;
3431
- render();
3432
- }
3433
- } else if (_str && _str.length === 1 && !key.ctrl && !key.meta) {
3434
- search += _str;
3435
- cursor = 0;
3436
- render();
3437
- }
3438
- }
3439
- function cleanup() {
3440
- showCursor();
3441
- process.stdin.removeListener("keypress", keypressHandler);
3442
- process.stdin.setRawMode(false);
3443
- process.stdin.pause();
3444
- }
3445
- process.stdin.on("keypress", keypressHandler);
3446
- });
3447
- }
3448
- var init_install_prompt = __esm(() => {
3449
- init_builtin_policies();
3450
- });
3451
-
3452
- // src/cli-error.ts
3453
- var CliError;
3454
- var init_cli_error = __esm(() => {
3455
- CliError = class CliError extends Error {
3456
- exitCode;
3457
- constructor(message, exitCode = 1) {
3458
- super(message);
3459
- this.name = "CliError";
3460
- this.exitCode = exitCode;
3461
- }
3462
- };
3463
- });
3464
-
3465
- // src/hooks/manager.ts
3466
- var exports_manager = {};
3467
- __export(exports_manager, {
3468
- removeHooks: () => removeHooks,
3469
- listHooks: () => listHooks,
3470
- installHooks: () => installHooks,
3471
- hooksInstalledInSettings: () => hooksInstalledInSettings,
3472
- getSettingsPath: () => getSettingsPath
3473
- });
3474
- import { execSync as execSync3 } from "node:child_process";
3475
- import { readFileSync as readFileSync6, writeFileSync as writeFileSync5, existsSync as existsSync10, mkdirSync as mkdirSync7 } from "node:fs";
3476
- import { resolve as resolve5, dirname as dirname4, basename as basename2 } from "node:path";
3477
- import { homedir as homedir11, platform, arch, release, hostname } from "node:os";
3478
- function getSettingsPath(scope, cwd) {
3479
- const base = cwd ? resolve5(cwd) : process.cwd();
3480
- switch (scope) {
3481
- case "user":
3482
- return resolve5(homedir11(), ".claude", "settings.json");
3483
- case "project":
3484
- return resolve5(base, ".claude", "settings.json");
3485
- case "local":
3486
- return resolve5(base, ".claude", "settings.local.json");
3487
- }
3488
- }
3489
- function scopeLabel(scope) {
3490
- switch (scope) {
3491
- case "user":
3492
- return `~/.claude/settings.json`;
3493
- case "project":
3494
- return `{cwd}/.claude/settings.json`;
3495
- case "local":
3496
- return `{cwd}/.claude/settings.local.json`;
3497
- }
3498
- }
3499
- function readSettings(settingsPath) {
3500
- if (!existsSync10(settingsPath)) {
3501
- return {};
3502
- }
3503
- const raw = readFileSync6(settingsPath, "utf8");
3504
- return JSON.parse(raw);
3505
- }
3506
- function writeSettings(settingsPath, settings) {
3507
- mkdirSync7(dirname4(settingsPath), { recursive: true });
3508
- writeFileSync5(settingsPath, JSON.stringify(settings, null, 2) + `
3509
- `, "utf8");
3510
- }
3511
- function resolveFailproofaiBinary() {
3512
- try {
3513
- const cmd = process.platform === "win32" ? "where failproofai" : "which failproofai";
3514
- const result = execSync3(cmd, { encoding: "utf8" }).trim();
3515
- return result.split(`
3516
- `)[0].trim();
3517
- } catch {
3518
- throw new CliError(`failproofai binary not found in PATH.
3519
- ` + "Install it globally first: npm install -g failproofai");
3520
- }
3521
- }
3522
- function isFailproofaiHook(hook) {
3523
- if (hook[FAILPROOFAI_HOOK_MARKER] === true)
3524
- return true;
3525
- const cmd = typeof hook.command === "string" ? hook.command : "";
3526
- return cmd.includes("failproofai") && cmd.includes("--hook");
3527
- }
3528
- function validatePolicyNames(names) {
3529
- const invalid = names.filter((n) => !VALID_POLICY_NAMES.has(n));
3530
- if (invalid.length > 0) {
3531
- const validList = [...VALID_POLICY_NAMES].join(", ");
3532
- throw new CliError(`Unknown policy name(s): ${invalid.join(", ")}
3533
- ` + `Valid policies: ${validList}`);
3534
- }
3535
- }
3536
- function deduplicateScopes(scopes, cwd) {
3537
- const seen = new Set;
3538
- return scopes.filter((s) => {
3539
- const p = getSettingsPath(s, cwd);
3540
- if (seen.has(p))
3541
- return false;
3542
- seen.add(p);
3543
- return true;
3544
- });
3545
- }
3546
- function hooksInstalledInSettings(scope, cwd) {
3547
- const settingsPath = getSettingsPath(scope, cwd);
3548
- if (!existsSync10(settingsPath))
3549
- return false;
3550
- try {
3551
- const settings = readSettings(settingsPath);
3552
- if (!settings.hooks)
3553
- return false;
3554
- for (const matchers of Object.values(settings.hooks)) {
3555
- if (!Array.isArray(matchers))
3556
- continue;
3557
- for (const matcher of matchers) {
3558
- if (!matcher.hooks)
3559
- continue;
3560
- if (matcher.hooks.some((h) => isFailproofaiHook(h))) {
3561
- return true;
3562
- }
3563
- }
3564
- }
3565
- } catch {}
3566
- return false;
3567
- }
3568
- function removeHooksFromSettingsFile(settingsPath) {
3569
- const settings = readSettings(settingsPath);
3570
- if (!settings.hooks)
3571
- return 0;
3572
- let removed = 0;
3573
- for (const eventType of Object.keys(settings.hooks)) {
3574
- const matchers = settings.hooks[eventType];
3575
- if (!Array.isArray(matchers))
3576
- continue;
3577
- for (let i = matchers.length - 1;i >= 0; i--) {
3578
- const matcher = matchers[i];
3579
- if (!matcher.hooks)
3580
- continue;
3581
- const before = matcher.hooks.length;
3582
- matcher.hooks = matcher.hooks.filter((h) => !isFailproofaiHook(h));
3583
- removed += before - matcher.hooks.length;
3584
- if (matcher.hooks.length === 0) {
3585
- matchers.splice(i, 1);
3586
- }
3587
- }
3588
- if (matchers.length === 0) {
3589
- delete settings.hooks[eventType];
3590
- }
3591
- }
3592
- if (Object.keys(settings.hooks).length === 0) {
3593
- delete settings.hooks;
3594
- }
3595
- writeSettings(settingsPath, settings);
3596
- return removed;
3597
- }
3598
- async function installHooks(policyNames, scope = "user", cwd, includeBeta = false, source, customPoliciesPath, removeCustomHooks = false) {
3599
- if (policyNames !== undefined && policyNames.length > 0) {
3600
- const nonAllNames = policyNames.filter((n) => n !== "all");
3601
- if (nonAllNames.length > 0)
3602
- validatePolicyNames(nonAllNames);
3603
- if (policyNames.includes("all") && nonAllNames.length > 0) {
3604
- throw new CliError(`"all" cannot be combined with specific policy names.
3605
- ` + `Use either: --install all or --install block-sudo sanitize-jwt ...`);
3606
- }
3607
- }
3608
- const binaryPath = resolveFailproofaiBinary();
3609
- const previousConfig = readScopedHooksConfig(scope, cwd);
3610
- const previousEnabled = new Set(previousConfig.enabledPolicies);
3611
- let selectedPolicies;
3612
- if (policyNames !== undefined) {
3613
- let incoming;
3614
- if (policyNames.length === 1 && policyNames[0] === "all") {
3615
- incoming = BUILTIN_POLICIES.filter((p) => includeBeta || !p.beta).map((p) => p.name);
3616
- } else {
3617
- incoming = policyNames;
3618
- }
3619
- selectedPolicies = [...new Set([...previousConfig.enabledPolicies, ...incoming])];
3620
- } else {
3621
- const preSelected = previousConfig.enabledPolicies.length > 0 ? previousConfig.enabledPolicies : undefined;
3622
- selectedPolicies = await promptPolicySelection(preSelected, { includeBeta });
3623
- }
3624
- const configToWrite = { ...previousConfig, enabledPolicies: selectedPolicies };
3625
- if (removeCustomHooks) {
3626
- delete configToWrite.customPoliciesPath;
3627
- } else if (customPoliciesPath) {
3628
- configToWrite.customPoliciesPath = resolve5(customPoliciesPath);
3629
- let validatedHooks = [];
3630
- try {
3631
- validatedHooks = await loadCustomHooks(configToWrite.customPoliciesPath, { strict: true });
3632
- } catch (err) {
3633
- console.error(`Error: ${err instanceof Error ? err.message : String(err)}`);
3634
- process.exit(1);
3635
- }
3636
- if (validatedHooks.length === 0) {
3637
- console.error(`Error: no hooks registered in ${customPoliciesPath}. ` + `Make sure your file calls customPolicies.add(...) at least once.`);
3638
- process.exit(1);
3639
- }
3640
- console.log(`
3641
- Validated ${validatedHooks.length} custom hook(s): ${validatedHooks.map((h) => h.name).join(", ")}`);
3642
- }
3643
- writeScopedHooksConfig(configToWrite, scope, cwd);
3644
- console.log(`
3645
- Enabled ${selectedPolicies.length} policy(ies): ${selectedPolicies.join(", ")}`);
3646
- if (removeCustomHooks) {
3647
- console.log("Custom hooks path cleared.");
3648
- } else if (configToWrite.customPoliciesPath) {
3649
- console.log(`Custom hooks path: ${configToWrite.customPoliciesPath}`);
3650
- }
3651
- const settingsPath = getSettingsPath(scope, cwd);
3652
- const settings = readSettings(settingsPath);
3653
- if (!settings.hooks) {
3654
- settings.hooks = {};
3655
- }
3656
- for (const eventType of HOOK_EVENT_TYPES) {
3657
- const command = scope === "project" ? `npx -y failproofai --hook ${eventType}` : `"${binaryPath}" --hook ${eventType}`;
3658
- const hookEntry = {
3659
- type: "command",
3660
- command,
3661
- timeout: 60000,
3662
- [FAILPROOFAI_HOOK_MARKER]: true
3663
- };
3664
- if (!settings.hooks[eventType]) {
3665
- settings.hooks[eventType] = [];
3666
- }
3667
- const matchers = settings.hooks[eventType];
3668
- let found = false;
3669
- for (const matcher of matchers) {
3670
- if (!matcher.hooks)
3671
- continue;
3672
- const failproofaiIdx = matcher.hooks.findIndex((h) => isFailproofaiHook(h));
3673
- if (failproofaiIdx >= 0) {
3674
- matcher.hooks[failproofaiIdx] = hookEntry;
3675
- found = true;
3676
- break;
3677
- }
3678
- }
3679
- if (!found) {
3680
- matchers.push({ hooks: [hookEntry] });
3681
- }
3682
- }
3683
- writeSettings(settingsPath, settings);
3684
- try {
3685
- const newSet = new Set(selectedPolicies);
3686
- const policiesAdded = selectedPolicies.filter((p) => !previousEnabled.has(p));
3687
- const policiesRemoved = [...previousEnabled].filter((p) => !newSet.has(p));
3688
- const distinctId = getInstanceId();
3689
- await trackHookEvent(distinctId, "hooks_installed", {
3690
- scope,
3691
- policies: selectedPolicies,
3692
- policy_count: selectedPolicies.length,
3693
- policies_added: policiesAdded,
3694
- policies_removed: policiesRemoved,
3695
- ...source ? { source } : {},
3696
- platform: platform(),
3697
- arch: arch(),
3698
- os_release: release(),
3699
- hostname_hash: hashToId(hostname()),
3700
- has_custom_hooks_path: !!configToWrite.customPoliciesPath,
3701
- has_policy_params: !!(configToWrite.policyParams && Object.keys(configToWrite.policyParams).length > 0),
3702
- param_policy_names: configToWrite.policyParams ? Object.keys(configToWrite.policyParams) : [],
3703
- command_format: scope === "project" ? "npx" : "absolute"
3704
- });
3705
- } catch {}
3706
- console.log(`Failproof AI hooks installed for all ${HOOK_EVENT_TYPES.length} event types (scope: ${scope}).`);
3707
- console.log(`Settings: ${settingsPath}`);
3708
- if (scope === "project") {
3709
- console.log(`Command: npx -y failproofai`);
3710
- console.log(`
3711
- This file can be committed to git — no machine-specific paths.`);
3712
- } else {
3713
- console.log(`Binary: ${binaryPath}`);
3714
- }
3715
- const otherScopes = deduplicateScopes(HOOK_SCOPES, cwd).filter((s) => s !== scope);
3716
- const duplicates = otherScopes.filter((s) => hooksInstalledInSettings(s, cwd));
3717
- if (duplicates.length > 0) {
3718
- const scopeList = duplicates.map((s) => `${s} (${scopeLabel(s)})`).join(", ");
3719
- console.log();
3720
- console.log(`\x1B[33mWarning: Failproof AI hooks are also installed at ${scopeList}.\x1B[0m`);
3721
- console.log(`Having hooks in multiple scopes may cause duplicate policy evaluation.`);
3722
- console.log(`Use \`failproofai policies --uninstall --scope ${duplicates[0]}\` to remove the other installation,`);
3723
- console.log(`or \`failproofai policies\` to see all scopes.`);
3724
- }
3725
- }
3726
- async function removeHooks(policyNames, scope = "user", cwd, opts) {
3727
- const configScope = scope === "all" ? "user" : scope;
3728
- if (opts?.removeCustomHooks) {
3729
- const config = readScopedHooksConfig(configScope, cwd);
3730
- delete config.customPoliciesPath;
3731
- writeScopedHooksConfig(config, configScope, cwd);
3732
- console.log("Custom hooks path cleared.");
3733
- }
3734
- if (policyNames && policyNames.length > 0 && !(policyNames.length === 1 && policyNames[0] === "all")) {
3735
- validatePolicyNames(policyNames);
3736
- const config = readScopedHooksConfig(configScope, cwd);
3737
- const removeSet = new Set(policyNames);
3738
- const remaining = config.enabledPolicies.filter((p) => !removeSet.has(p));
3739
- const notEnabled = policyNames.filter((p) => !config.enabledPolicies.includes(p));
3740
- if (notEnabled.length > 0) {
3741
- console.log(`Warning: policy(ies) not currently enabled: ${notEnabled.join(", ")}`);
3742
- }
3743
- const { policyParams: existingParams, ...baseConfig } = config;
3744
- const filteredParams = existingParams ? Object.fromEntries(Object.entries(existingParams).filter(([k]) => !removeSet.has(k))) : null;
3745
- const updatedConfig = {
3746
- ...baseConfig,
3747
- enabledPolicies: remaining,
3748
- ...filteredParams && Object.keys(filteredParams).length > 0 ? { policyParams: filteredParams } : {}
3749
- };
3750
- writeScopedHooksConfig(updatedConfig, configScope, cwd);
3751
- try {
3752
- const distinctId = getInstanceId();
3753
- const actuallyRemoved = policyNames.filter((p) => config.enabledPolicies.includes(p));
3754
- await trackHookEvent(distinctId, "hooks_removed", {
3755
- scope,
3756
- removal_mode: opts?.betaOnly ? "beta_policies" : "policies",
3757
- beta_only: opts?.betaOnly ?? false,
3758
- policies_removed: actuallyRemoved,
3759
- removed_count: actuallyRemoved.length,
3760
- ...opts?.source ? { source: opts.source } : {},
3761
- platform: platform(),
3762
- arch: arch(),
3763
- os_release: release(),
3764
- hostname_hash: hashToId(hostname())
3765
- });
3766
- } catch {}
3767
- console.log(`Disabled ${policyNames.length - notEnabled.length} policy(ies).`);
3768
- console.log(`Remaining: ${remaining.length > 0 ? remaining.join(", ") : "(none)"}`);
3769
- return;
3770
- }
3771
- const configBeforeRemoval = readScopedHooksConfig(configScope, cwd);
3772
- const scopesToRemove = scope === "all" ? [...HOOK_SCOPES] : [scope];
3773
- let totalRemoved = 0;
3774
- for (const s of scopesToRemove) {
3775
- const settingsPath = getSettingsPath(s, cwd);
3776
- if (!existsSync10(settingsPath)) {
3777
- if (scope !== "all") {
3778
- console.log("No settings file found. Nothing to remove.");
3779
- return;
3780
- }
3781
- continue;
3782
- }
3783
- const settings = readSettings(settingsPath);
3784
- if (!settings.hooks) {
3785
- if (scope !== "all") {
3786
- console.log("No hooks found in settings. Nothing to remove.");
3787
- return;
3788
- }
3789
- continue;
3790
- }
3791
- const removed = removeHooksFromSettingsFile(settingsPath);
3792
- totalRemoved += removed;
3793
- if (scope !== "all") {
3794
- console.log(`Removed ${removed} failproofai hook(s) from settings.`);
3795
- console.log(`Settings: ${settingsPath}`);
3796
- }
3797
- }
3798
- if (scope === "all") {
3799
- console.log(`Removed ${totalRemoved} failproofai hook(s) from all scopes.`);
3800
- for (const s of scopesToRemove) {
3801
- console.log(` ${s}: ${getSettingsPath(s, cwd)}`);
3802
- }
3803
- }
3804
- try {
3805
- const distinctId = getInstanceId();
3806
- await trackHookEvent(distinctId, "hooks_removed", {
3807
- scope,
3808
- removal_mode: "hooks",
3809
- policies_removed: configBeforeRemoval.enabledPolicies,
3810
- removed_count: totalRemoved,
3811
- ...opts?.source ? { source: opts.source } : {},
3812
- platform: platform(),
3813
- arch: arch(),
3814
- os_release: release(),
3815
- hostname_hash: hashToId(hostname())
3816
- });
3817
- } catch {}
3818
- if (scope === "all") {
3819
- for (const s of HOOK_SCOPES) {
3820
- const existing = readScopedHooksConfig(s, cwd);
3821
- if (existing.enabledPolicies.length > 0 || existing.customPoliciesPath || existing.policyParams) {
3822
- const { customPoliciesPath: _drop, policyParams: _dropParams, ...rest } = existing;
3823
- writeScopedHooksConfig({ ...rest, enabledPolicies: [] }, s, cwd);
3824
- }
3825
- }
3826
- } else if (!HOOK_SCOPES.some((s) => hooksInstalledInSettings(s, cwd))) {
3827
- const existing = readScopedHooksConfig(configScope, cwd);
3828
- const { customPoliciesPath: _drop, policyParams: _dropParams, ...rest } = existing;
3829
- writeScopedHooksConfig({ ...rest, enabledPolicies: [] }, configScope, cwd);
3830
- }
3831
- }
3832
- async function listHooks(cwd) {
3833
- const config = readMergedHooksConfig(cwd);
3834
- const enabledSet = new Set(config.enabledPolicies);
3835
- const uniqueScopes = deduplicateScopes(HOOK_SCOPES, cwd);
3836
- const installedScopes = uniqueScopes.filter((s) => hooksInstalledInSettings(s, cwd));
3837
- const regularPolicies = BUILTIN_POLICIES.filter((p) => !p.beta);
3838
- const betaPolicies = BUILTIN_POLICIES.filter((p) => p.beta);
3839
- const nameColWidth = Math.max(...BUILTIN_POLICIES.map((p) => p.name.length)) + 2;
3840
- const builtinPolicyNames = new Set(BUILTIN_POLICIES.map((p) => p.name));
3841
- const printParamsSummary = (policyName, indent) => {
3842
- const params = config.policyParams?.[policyName];
3843
- if (!params)
3844
- return;
3845
- for (const [key, val] of Object.entries(params)) {
3846
- console.log(`${indent} ${key}: ${JSON.stringify(val)}`);
3847
- }
3848
- };
3849
- const statusCol = 8;
3850
- const printSimpleRow = (policy) => {
3851
- const mark = enabledSet.has(policy.name) ? `\x1B[32m✓\x1B[0m` : " ";
3852
- console.log(` ${mark}${" ".repeat(statusCol - 1)}${policy.name.padEnd(nameColWidth)}${policy.description}`);
3853
- printParamsSummary(policy.name, ` ${" ".repeat(statusCol)}`);
3854
- };
3855
- const printBetaSection = (printRow) => {
3856
- if (betaPolicies.length > 0) {
3857
- console.log(`
3858
- \x1B[2m── Beta ──\x1B[0m`);
3859
- for (const policy of betaPolicies)
3860
- printRow(policy);
3861
- }
3862
- };
3863
- if (installedScopes.length === 0) {
3864
- console.log(`
3865
- Failproof AI Policies — not installed
3866
- `);
3867
- console.log(` ${"Status".padEnd(statusCol)}${"Name".padEnd(nameColWidth)}Description`);
3868
- console.log(` ${"─".repeat(6)} ${"─".repeat(nameColWidth - 2)} ${"─".repeat(38)}`);
3869
- for (const policy of regularPolicies)
3870
- printSimpleRow(policy);
3871
- printBetaSection(printSimpleRow);
3872
- if (config.enabledPolicies.length > 0) {
3873
- console.log("\n Policies not installed. Run `failproofai policies --install` to activate.");
3874
- } else {
3875
- console.log("\n Run `failproofai policies --install` to get started.");
3876
- }
3877
- console.log(` Config: ~/.failproofai/policies-config.json
3878
- `);
3879
- } else if (installedScopes.length === 1) {
3880
- const scope = installedScopes[0];
3881
- console.log(`
3882
- Failproof AI Hook Policies (${scope})
3883
- `);
3884
- console.log(` ${"Status".padEnd(statusCol)}${"Name".padEnd(nameColWidth)}Description`);
3885
- console.log(` ${"─".repeat(6)} ${"─".repeat(nameColWidth - 2)} ${"─".repeat(38)}`);
3886
- for (const policy of regularPolicies)
3887
- printSimpleRow(policy);
3888
- printBetaSection(printSimpleRow);
3889
- console.log(`
3890
- Config: ~/.failproofai/policies-config.json
3891
- `);
3892
- } else {
3893
- const COL = 9;
3894
- const scopeLabelMap = {
3895
- user: "User",
3896
- project: "Project",
3897
- local: "Local"
3898
- };
3899
- console.log(`
3900
- Failproof AI Hook Policies
3901
- `);
3902
- const buildScopePrefix = () => {
3903
- let s = " ";
3904
- for (const sc of installedScopes)
3905
- s += scopeLabelMap[sc].padEnd(COL);
3906
- return s;
3907
- };
3908
- const scopeHeaderWidth = installedScopes.length * COL;
3909
- console.log(`${buildScopePrefix()}${"Name".padEnd(nameColWidth)}Description`);
3910
- console.log(` ${"─".repeat(scopeHeaderWidth)}${"─".repeat(nameColWidth)}${"─".repeat(38)}`);
3911
- const printMultiScopeRow = (policy) => {
3912
- const enabled = enabledSet.has(policy.name);
3913
- let row = " ";
3914
- for (const _scope of installedScopes) {
3915
- if (enabled) {
3916
- row += `\x1B[32m✓ ON\x1B[0m` + " ".repeat(COL - 4);
3917
- } else {
3918
- row += " OFF" + " ".repeat(COL - 5);
3919
- }
3920
- }
3921
- row += policy.name.padEnd(nameColWidth) + policy.description;
3922
- console.log(row);
3923
- printParamsSummary(policy.name, ` ${" ".repeat(scopeHeaderWidth)}`);
3924
- };
3925
- for (const policy of regularPolicies)
3926
- printMultiScopeRow(policy);
3927
- if (betaPolicies.length > 0) {
3928
- console.log(`
3929
- \x1B[2m── Beta ──\x1B[0m`);
3930
- for (const policy of betaPolicies)
3931
- printMultiScopeRow(policy);
3932
- }
3933
- console.log(`
3934
- Config: ~/.failproofai/policies-config.json`);
3935
- const scopeNames = installedScopes.join(", ");
3936
- console.log();
3937
- console.log(`\x1B[33m⚠ Hooks in multiple scopes (${scopeNames}).\x1B[0m`);
3938
- console.log(` Consider keeping one. Remove with: failproofai policies --uninstall --scope <scope>
3939
- `);
3940
- }
3941
- if (config.policyParams) {
3942
- for (const key of Object.keys(config.policyParams)) {
3943
- if (!builtinPolicyNames.has(key)) {
3944
- console.log(` \x1B[33mWarning: unknown policyParams key "${key}" — possible typo\x1B[0m`);
3945
- }
3946
- }
3947
- }
3948
- if (config.customPoliciesPath) {
3949
- console.log(`
3950
- ── Custom Policies (${config.customPoliciesPath}) ───────────────────────`);
3951
- if (!existsSync10(config.customPoliciesPath)) {
3952
- console.log(` \x1B[31m✗ File not found: ${config.customPoliciesPath}\x1B[0m`);
3953
- } else {
3954
- const hooks = await loadCustomHooks(config.customPoliciesPath);
3955
- if (hooks.length === 0) {
3956
- console.log(` \x1B[31m✗ ERR failed to load (check ~/.failproofai/logs/hooks.log)\x1B[0m`);
3957
- } else {
3958
- const descColWidth = nameColWidth;
3959
- for (const hook of hooks) {
3960
- console.log(` \x1B[32m✓\x1B[0m ${hook.name.padEnd(descColWidth)}${hook.description ?? ""}`);
3961
- }
3962
- }
3963
- }
3964
- console.log();
3965
- }
3966
- const base = cwd ? resolve5(cwd) : process.cwd();
3967
- const conventionDirs = [
3968
- { label: "Project", dir: resolve5(base, ".failproofai", "policies") },
3969
- { label: "User", dir: resolve5(homedir11(), ".failproofai", "policies") }
3970
- ];
3971
- for (const { label, dir } of conventionDirs) {
3972
- const files = discoverPolicyFiles(dir);
3973
- if (files.length === 0)
3974
- continue;
3975
- console.log(`
3976
- ── Convention Policies — ${label} (${dir}) ──────────`);
3977
- for (const file of files) {
3978
- try {
3979
- const hooks = await loadCustomHooks(file);
3980
- if (hooks.length === 0) {
3981
- const filename = basename2(file);
3982
- console.log(` \x1B[31m✗\x1B[0m ${filename.padEnd(nameColWidth)}\x1B[31mfailed to load\x1B[0m`);
3983
- } else {
3984
- const filename = basename2(file);
3985
- const hookSummary = hooks.map((h) => h.name).join(", ");
3986
- console.log(` \x1B[32m✓\x1B[0m ${filename.padEnd(nameColWidth)}${hooks.length} hook(s): ${hookSummary}`);
3987
- }
3988
- } catch {
3989
- const filename = basename2(file);
3990
- console.log(` \x1B[31m✗\x1B[0m ${filename.padEnd(nameColWidth)}\x1B[31merror\x1B[0m`);
3991
- }
3992
- }
3993
- console.log();
3994
- }
3995
- }
3996
- var VALID_POLICY_NAMES;
3997
- var init_manager = __esm(() => {
3998
- init_types();
3999
- init_install_prompt();
4000
- init_hooks_config();
4001
- init_builtin_policies();
4002
- init_custom_hooks_loader();
4003
- init_hook_telemetry();
4004
- init_telemetry_id();
4005
- init_cli_error();
4006
- VALID_POLICY_NAMES = new Set(BUILTIN_POLICIES.map((p) => p.name));
4007
- });
4008
-
4009
- // src/auth/login.ts
4010
- var exports_login = {};
4011
- __export(exports_login, {
4012
- login: () => login
4013
- });
4014
- import { spawn as spawn3 } from "node:child_process";
4015
- import { platform as platform2 } from "node:os";
4016
- function openBrowser(url) {
4017
- const os2 = platform2();
4018
- try {
4019
- if (os2 === "darwin") {
4020
- spawn3("open", [url], { detached: true, stdio: "ignore" }).unref();
4021
- } else if (os2 === "win32") {
4022
- spawn3("cmd", ["/c", "start", "", url], { detached: true, stdio: "ignore" }).unref();
4023
- } else {
4024
- spawn3("xdg-open", [url], { detached: true, stdio: "ignore" }).unref();
4025
- }
4026
- } catch {}
4027
- }
4028
- async function postJson(url, body, timeoutMs = HTTP_TIMEOUT_MS3) {
4029
- const resp = await fetch(url, {
4030
- method: "POST",
4031
- headers: { "Content-Type": "application/json" },
4032
- body: JSON.stringify(body),
4033
- signal: AbortSignal.timeout(timeoutMs)
4034
- });
4035
- if (!resp.ok) {
4036
- throw new Error(`${url} → ${resp.status} ${resp.statusText}`);
4037
- }
4038
- return await resp.json();
4039
- }
4040
- async function login() {
4041
- const serverUrl = DEFAULT_SERVER_URL;
4042
- console.log("Requesting device code...");
4043
- const dc = await postJson(`${serverUrl}/api/v1/auth/device-code`, {});
4044
- console.log(`
4045
- Open this URL in your browser (will be opened automatically):`);
4046
- console.log(` ${dc.verification_url}
4047
- `);
4048
- console.log(` Your code: ${dc.user_code}
4049
- `);
4050
- openBrowser(dc.verification_url);
4051
- const deadline = Date.now() + dc.expires_in * 1000;
4052
- const intervalMs = dc.interval * 1000;
4053
- while (Date.now() < deadline) {
4054
- await new Promise((r) => setTimeout(r, intervalMs));
4055
- try {
4056
- const result = await postJson(`${serverUrl}/api/v1/auth/device-token`, { device_code: dc.device_code });
4057
- if ("access_token" in result) {
4058
- const tokens = {
4059
- access_token: result.access_token,
4060
- refresh_token: result.refresh_token,
4061
- expires_at: Math.floor(Date.now() / 1000) + result.expires_in,
4062
- user_email: result.user.email,
4063
- user_id: result.user.id,
4064
- server_url: serverUrl
4065
- };
4066
- writeTokens(tokens);
4067
- console.log(`Logged in as ${result.user.email}`);
4068
- try {
4069
- const { ensureRelayRunning: ensureRelayRunning3 } = await Promise.resolve().then(() => (init_daemon(), exports_daemon));
4070
- ensureRelayRunning3();
4071
- console.log("Relay daemon started.");
4072
- } catch (e) {
4073
- console.warn("Failed to auto-start relay daemon:", e);
4074
- }
4075
- return;
4076
- }
4077
- } catch {}
4078
- }
4079
- throw new Error("Login timed out. Run `failproofai login` again.");
4080
- }
4081
- var DEFAULT_SERVER_URL, HTTP_TIMEOUT_MS3 = 1e4;
4082
- var init_login = __esm(() => {
4083
- init_token_store();
4084
- DEFAULT_SERVER_URL = process.env.FAILPROOFAI_SERVER_URL ?? "https://api.befailproof.ai";
4085
- });
4086
-
4087
- // src/auth/logout.ts
4088
- var exports_logout = {};
4089
- __export(exports_logout, {
4090
- whoami: () => whoami,
4091
- logout: () => logout
4092
- });
4093
- async function logout() {
4094
- const tokens = readTokens();
4095
- if (!tokens) {
4096
- console.log("Not logged in.");
4097
- return;
4098
- }
4099
- try {
4100
- await fetch(`${tokens.server_url}/api/v1/auth/logout`, {
4101
- method: "POST",
4102
- headers: { "Content-Type": "application/json" },
4103
- body: JSON.stringify({ refresh_token: tokens.refresh_token }),
4104
- signal: AbortSignal.timeout(LOGOUT_TIMEOUT_MS)
4105
- });
4106
- } catch {}
4107
- try {
4108
- stopRelay();
4109
- } catch {}
4110
- clearTokens();
4111
- console.log("Logged out.");
4112
- }
4113
- function whoami() {
4114
- const tokens = readTokens();
4115
- if (!tokens) {
4116
- console.log("Not logged in. Run `failproofai login` to authenticate.");
4117
- process.exit(1);
4118
- }
4119
- console.log(`Logged in as ${tokens.user_email}`);
4120
- console.log(`Server: ${tokens.server_url}`);
4121
- const expiresIn = tokens.expires_at - Math.floor(Date.now() / 1000);
4122
- if (expiresIn > 0) {
4123
- console.log(`Access token expires in ${Math.floor(expiresIn / 60)} minutes`);
4124
- } else {
4125
- console.log(`Access token expired (will refresh on next use)`);
4126
- }
4127
- }
4128
- var LOGOUT_TIMEOUT_MS = 3000;
4129
- var init_logout = __esm(() => {
4130
- init_token_store();
4131
- init_pid();
4132
- });
4133
-
4134
- // src/relay/pid.ts
4135
- var exports_pid = {};
4136
- __export(exports_pid, {
4137
- writePid: () => writePid2,
4138
- stopRelay: () => stopRelay2,
4139
- relayStatus: () => relayStatus,
4140
- readPid: () => readPid2,
4141
- isProcessAlive: () => isProcessAlive2,
4142
- clearPid: () => clearPid2
4143
- });
4144
- import { readFileSync as readFileSync7, writeFileSync as writeFileSync6, existsSync as existsSync11, unlinkSync as unlinkSync5, mkdirSync as mkdirSync8 } from "node:fs";
4145
- import { join as join9, dirname as dirname5 } from "node:path";
4146
- import { homedir as homedir12 } from "node:os";
4147
- function readPid2() {
4148
- if (!existsSync11(PID_FILE2))
4149
- return null;
4150
- try {
4151
- const raw = readFileSync7(PID_FILE2, "utf8").trim();
4152
- const pid = parseInt(raw, 10);
4153
- if (Number.isNaN(pid) || pid <= 0)
4154
- return null;
4155
- return pid;
4156
- } catch {
4157
- return null;
4158
- }
4159
- }
4160
- function writePid2(pid) {
4161
- const dir = dirname5(PID_FILE2);
4162
- if (!existsSync11(dir))
4163
- mkdirSync8(dir, { recursive: true, mode: 448 });
4164
- writeFileSync6(PID_FILE2, String(pid));
4165
- }
4166
- function clearPid2() {
4167
- if (existsSync11(PID_FILE2))
4168
- unlinkSync5(PID_FILE2);
4169
- }
4170
- function isProcessAlive2(pid) {
4171
- try {
4172
- process.kill(pid, 0);
4173
- return true;
4174
- } catch (err) {
4175
- const e = err;
4176
- if (e?.code === "EPERM")
4177
- return true;
4178
- return false;
4179
- }
4180
- }
4181
- function stopRelay2() {
4182
- const pid = readPid2();
4183
- if (pid === null)
4184
- return false;
4185
- if (!isProcessAlive2(pid)) {
4186
- clearPid2();
4187
- return false;
4188
- }
4189
- try {
4190
- process.kill(pid, "SIGTERM");
4191
- clearPid2();
4192
- return true;
4193
- } catch {
4194
- return false;
4195
- }
4196
- }
4197
- function relayStatus() {
4198
- const pid = readPid2();
4199
- if (pid === null)
4200
- return { running: false, pid: null };
4201
- return { running: isProcessAlive2(pid), pid };
4202
- }
4203
- var PID_FILE2;
4204
- var init_pid2 = __esm(() => {
4205
- PID_FILE2 = join9(homedir12(), ".failproofai", "relay.pid");
4206
- });
4207
-
4208
- // lib/paths.ts
4209
- import { homedir as homedir13 } from "os";
4210
- import { join as join10 } from "path";
4211
- function getDefaultClaudeProjectsPath() {
4212
- return join10(homedir13(), ".claude", "projects");
4213
- }
4214
- var init_paths = () => {};
4215
-
4216
- // scripts/parse-script-args.ts
4217
- import { resolve as resolve6 } from "path";
4218
- function parseStringFlag(flagName, errorLabel, inlineValue, args, index, options) {
4219
- const raw = inlineValue ?? args[index + 1];
4220
- if (raw === undefined || inlineValue === null && raw.startsWith("-")) {
4221
- console.error(`Error: ${flagName} requires ${errorLabel}`);
4222
- process.exit(1);
4223
- }
4224
- const value = options?.resolve ? resolve6(raw) : raw;
4225
- return { value, spliceCount: inlineValue !== null ? 1 : 2 };
4226
- }
4227
- function parseScriptArgs(argv) {
4228
- const args = [...argv];
4229
- let claudeProjectsPath;
4230
- let loggingLevel;
4231
- let disableTelemetry = false;
4232
- let allowedDevOrigins;
4233
- for (let i = 0;i < args.length; i++) {
4234
- const arg = args[i];
4235
- const eqIdx = arg.indexOf("=");
4236
- const flag = eqIdx >= 0 ? arg.slice(0, eqIdx) : arg;
4237
- const inlineValue = eqIdx >= 0 ? arg.slice(eqIdx + 1) : null;
4238
- if (flag === "--projects-path" || flag === "-p") {
4239
- const { value, spliceCount } = parseStringFlag(flag, "a path argument", inlineValue, args, i);
4240
- claudeProjectsPath = value;
4241
- args.splice(i, spliceCount);
4242
- i--;
4243
- continue;
4244
- }
4245
- if (flag === "--logging") {
4246
- const raw = inlineValue ?? args[i + 1];
4247
- if (raw === undefined || inlineValue === null && raw.startsWith("-")) {
4248
- console.error("Error: --logging requires a level (info, warn, error)");
4249
- process.exit(1);
4250
- }
4251
- const val = raw.toLowerCase();
4252
- if (val !== "info" && val !== "warn" && val !== "error") {
4253
- console.error("Error: --logging must be one of: info, warn, error");
4254
- process.exit(1);
4255
- }
4256
- loggingLevel = val;
4257
- args.splice(i, inlineValue !== null ? 1 : 2);
4258
- i--;
4259
- continue;
4260
- }
4261
- if (flag === "--disable-telemetry") {
4262
- disableTelemetry = true;
4263
- args.splice(i, 1);
4264
- i--;
4265
- continue;
4266
- }
4267
- if (flag === "--allowed-origins") {
4268
- const { value, spliceCount } = parseStringFlag(flag, "a comma-separated list of origins", inlineValue, args, i);
4269
- allowedDevOrigins = value.split(",").map((s) => s.trim()).filter(Boolean);
4270
- args.splice(i, spliceCount);
4271
- i--;
4272
- continue;
4273
- }
4274
- }
4275
- return { claudeProjectsPath, loggingLevel, disableTelemetry, allowedDevOrigins, remainingArgs: args };
4276
- }
4277
- var init_parse_script_args = () => {};
4278
-
4279
- // scripts/launch.ts
4280
- var exports_launch = {};
4281
- __export(exports_launch, {
4282
- launch: () => launch
4283
- });
4284
- import { spawn as spawn4 } from "child_process";
4285
- import { realpathSync, existsSync as existsSync12 } from "node:fs";
4286
- import { resolve as resolve7, dirname as dirname6 } from "node:path";
4287
- import { fileURLToPath } from "node:url";
4288
- function launch(mode) {
4289
- const { claudeProjectsPath: parsedPath, loggingLevel, disableTelemetry, allowedDevOrigins, remainingArgs } = parseScriptArgs(process.argv.slice(2));
4290
- console.log(`
4291
- ______ _ __ ____ ___ ____
4292
- / ____/___ _(_) /___ _________ ____ / __/ / | / _/
4293
- / /_ / __ \`/ / / __ \\/ ___/ __ \\/ __ \\/ /_ / /| | / /
4294
- / __/ / /_/ / / / /_/ / / / /_/ / /_/ / __/ / ___ |_/ /
4295
- /_/ \\__,_/_/_/ .___/_/ \\____/\\____/_/ /_/ |_/___/
4296
- /_/ v${version2}
4297
- `);
4298
- console.log(` ⭐ Star us: https://github.com/exospherehost/failproofai`);
4299
- console.log(` \uD83D\uDCD6 Docs: https://befailproof.ai
4300
- `);
4301
- let claudeProjectsPath = parsedPath;
4302
- if (!claudeProjectsPath) {
4303
- claudeProjectsPath = getDefaultClaudeProjectsPath();
4304
- console.log(`Using default .claude projects path: ${claudeProjectsPath}`);
4305
- } else {
4306
- console.log(`Using custom .claude projects path: ${claudeProjectsPath}`);
4307
- }
4308
- process.env.CLAUDE_PROJECTS_PATH = claudeProjectsPath;
4309
- let cmd;
4310
- let cmdArgs;
4311
- if (mode === "start") {
4312
- const portIdx = remainingArgs.indexOf("--port");
4313
- const port = portIdx >= 0 ? remainingArgs[portIdx + 1] : "8020";
4314
- process.env.PORT = port;
4315
- process.env.HOSTNAME = "0.0.0.0";
4316
- cmd = "node";
4317
- const packageRoot = process.env.FAILPROOFAI_PACKAGE_ROOT ?? resolve7(dirname6(realpathSync(fileURLToPath(import.meta.url))), "..");
4318
- const serverJsPath = resolve7(packageRoot, ".next/standalone/server.js");
4319
- if (!existsSync12(serverJsPath)) {
4320
- console.error(`
4321
- Error: Cannot find server.js at:
4322
- ${serverJsPath}
4323
-
4324
- ` + `The package may be missing its build output.
4325
- ` + `Try reinstalling:
4326
- npm install -g failproofai@latest
4327
- `);
4328
- process.exit(1);
4329
- }
4330
- cmdArgs = [serverJsPath];
4331
- } else {
4332
- cmd = "bunx";
4333
- cmdArgs = ["--bun", "next", "dev", ...remainingArgs];
4334
- }
4335
- const nextProcess = spawn4(cmd, cmdArgs, {
4336
- stdio: "inherit",
4337
- env: {
4338
- ...process.env,
4339
- CLAUDE_PROJECTS_PATH: claudeProjectsPath,
4340
- ...loggingLevel ? { FAILPROOFAI_LOG_LEVEL: loggingLevel } : {},
4341
- ...disableTelemetry ? { FAILPROOFAI_TELEMETRY_DISABLED: "1" } : {},
4342
- ...allowedDevOrigins ? { FAILPROOFAI_ALLOWED_DEV_ORIGINS: allowedDevOrigins.join(",") } : {}
4343
- }
4344
- });
4345
- nextProcess.on("error", (error) => {
4346
- console.error("Error starting Next.js:", error);
4347
- process.exit(1);
4348
- });
4349
- nextProcess.on("exit", (code) => {
4350
- process.exit(code || 0);
4351
- });
4352
- }
4353
- var init_launch = __esm(() => {
4354
- init_paths();
4355
- init_parse_script_args();
4356
- init_package();
4357
- });
4358
-
4359
- // src/cli-error.ts
4360
- var exports_cli_error = {};
4361
- __export(exports_cli_error, {
4362
- CliError: () => CliError2
4363
- });
4364
- var CliError2;
4365
- var init_cli_error2 = __esm(() => {
4366
- CliError2 = class CliError2 extends Error {
4367
- exitCode;
4368
- constructor(message, exitCode = 1) {
4369
- super(message);
4370
- this.name = "CliError";
4371
- this.exitCode = exitCode;
4372
- }
4373
- };
4374
- });
4375
-
4376
- // bin/failproofai.mjs
4377
- import { realpathSync as realpathSync2 } from "fs";
4378
- import { dirname as dirname7, resolve as resolve8 } from "path";
4379
- import { fileURLToPath as fileURLToPath2 } from "url";
4380
- // package.json
4381
- var version = "0.0.6-beta.3";
4382
-
4383
- // bin/failproofai.mjs
4384
- if (!process.env.FAILPROOFAI_PACKAGE_ROOT) {
4385
- process.env.FAILPROOFAI_PACKAGE_ROOT = resolve8(dirname7(realpathSync2(fileURLToPath2(import.meta.url))), "..");
4386
- }
4387
- if (!process.env.FAILPROOFAI_DIST_PATH) {
4388
- process.env.FAILPROOFAI_DIST_PATH = resolve8(dirname7(realpathSync2(fileURLToPath2(import.meta.url))), "..", "dist");
4389
- }
4390
- var args = process.argv.slice(2);
4391
- if (args[0] === "p")
4392
- args[0] = "policies";
4393
- var hookIdx = args.indexOf("--hook");
4394
- if (hookIdx >= 0) {
4395
- if (!args[hookIdx + 1]) {
4396
- console.error("Error: Missing event type after --hook");
4397
- console.error("Usage: failproofai --hook <event> (e.g. PreToolUse, PostToolUse)");
4398
- process.exit(1);
4399
- }
4400
- try {
4401
- const { handleHookEvent: handleHookEvent2 } = await Promise.resolve().then(() => (init_handler(), exports_handler));
4402
- const exitCode = await handleHookEvent2(args[hookIdx + 1]);
4403
- process.exit(exitCode);
4404
- } catch (err) {
4405
- const msg = err instanceof Error ? err.message : String(err);
4406
- console.error(`Unexpected error: ${msg}`);
4407
- process.exit(2);
4408
- }
4409
- }
4410
- if (args.includes("--relay-daemon")) {
4411
- try {
4412
- const { runDaemon: runDaemon3 } = await Promise.resolve().then(() => (init_daemon2(), exports_daemon2));
4413
- await runDaemon3();
4414
- process.exit(0);
4415
- } catch (err) {
4416
- const msg = err instanceof Error ? err.message : String(err);
4417
- console.error(`Relay daemon error: ${msg}`);
4418
- process.exit(1);
4419
- }
4420
- }
4421
- async function runCli() {
4422
- const SUBCOMMANDS = ["policies", "login", "logout", "whoami", "relay", "sync"];
4423
- if ((args.includes("--help") || args.includes("-h")) && !SUBCOMMANDS.includes(args[0])) {
4424
- const extraArgs = args.filter((a) => a !== "--help" && a !== "-h");
4425
- if (extraArgs.length > 0) {
4426
- throw new CliError3(`Unexpected argument: ${extraArgs[0]}
4427
- Run \`failproofai --help\` for usage.`);
4428
- }
4429
- console.log(`
4430
- failproofai v${version}
4431
-
4432
- USAGE
4433
- failproofai [command] [options]
4434
-
4435
- COMMANDS
4436
- (no args) Launch the policy dashboard
4437
-
4438
- policies, p List all available policies and their status
4439
- policies --install, -i Enable policies in Claude Code settings
4440
- [names...] Specific policy names to enable
4441
- --scope user|project|local Config scope to write to (default: user)
4442
- --beta Include beta policies
4443
- --custom, -c <path> Path to a JS file of custom policies
4444
-
4445
- policies --uninstall, -u Disable policies or remove hooks
4446
- [names...] Specific policy names to disable
4447
- --scope user|project|local|all Config scope to remove from (default: user)
4448
- --beta Remove only beta policies
4449
- --custom, -c Clear the customPoliciesPath from config
4450
-
4451
- policies --help, -h Show this help for the policies command
4452
-
4453
- login Authenticate with the failproofai cloud (Google OAuth)
4454
- logout Clear local auth tokens and stop relay daemon
4455
- whoami Print current logged-in user
4456
- relay start|stop|status Manage the event relay daemon
4457
- sync One-shot flush of pending events to the server
4458
-
4459
- --version, -v Print version and exit
4460
- --help, -h Show this help message
4461
-
4462
- CONVENTION POLICIES
4463
- Drop *policies.{js,mjs,ts} files into .failproofai/policies/ for auto-loading.
4464
- Works at project level (.failproofai/policies/) and user level (~/.failproofai/policies/).
4465
- No --custom flag or config changes needed \u2014 just drop files and they're picked up.
4466
-
4467
- EXAMPLES
4468
- failproofai policies
4469
- failproofai policies --install
4470
- failproofai policies --install block-sudo sanitize-api-keys --scope project
4471
- failproofai policies --install --custom ./my-policies.js
4472
- failproofai policies -i -c ./my-policies.js
4473
- failproofai policies --uninstall block-sudo
4474
- failproofai policies --uninstall --custom
4475
-
4476
- LINKS
4477
- \u2B50 Star us: https://github.com/exospherehost/failproofai
4478
- \uD83D\uDCD6 Docs: https://befailproof.ai
4479
- `.trimStart());
4480
- process.exit(0);
4481
- }
4482
- if ((args.includes("--version") || args.includes("-v")) && !SUBCOMMANDS.includes(args[0])) {
4483
- const extraArgs = args.filter((a) => a !== "--version" && a !== "-v");
4484
- if (extraArgs.length > 0) {
4485
- throw new CliError3(`Unexpected argument: ${extraArgs[0]}
4486
- Run \`failproofai --help\` for usage.`);
4487
- }
4488
- console.log(version);
4489
- process.exit(0);
4490
- }
4491
- if (args[0] === "policies") {
4492
- const subArgs = args.slice(1);
4493
- const isInstall = subArgs.includes("--install") || subArgs.includes("-i");
4494
- const isUninstall = subArgs.includes("--uninstall") || subArgs.includes("-u");
4495
- const isHelp = subArgs.includes("--help") || subArgs.includes("-h");
4496
- if (isHelp) {
4497
- console.log(`
4498
- failproofai policies \u2014 manage Failproof AI policies
4499
-
4500
- USAGE
4501
- failproofai policies List all policies and their status
4502
- failproofai policies --install, -i Enable policies
4503
- failproofai policies --uninstall, -u Disable policies or remove hooks
4504
-
4505
- OPTIONS (install)
4506
- [names...] Specific policy names to enable (omit for interactive)
4507
- --scope user|project|local Config scope to write to (default: user)
4508
- --beta Include beta policies
4509
- --custom, -c <path> Path to a JS file of custom policies
4510
- (skips interactive prompt; validates file first)
4511
-
4512
- OPTIONS (uninstall)
4513
- [names...] Specific policy names to disable (omit to remove hooks)
4514
- --scope user|project|local|all Config scope to remove from (default: user)
4515
- --beta Remove only beta policies
4516
- --custom, -c Clear the customPoliciesPath from config
4517
-
4518
- EXAMPLES
4519
- failproofai policies
4520
- failproofai policies --install
4521
- failproofai policies --install block-sudo sanitize-api-keys
4522
- failproofai policies --install --custom ./my-policies.js
4523
- failproofai policies -i -c ./my-policies.js
4524
- failproofai policies --uninstall block-sudo
4525
- failproofai policies -u
4526
- failproofai policies --uninstall --custom
4527
- `.trimStart());
4528
- process.exit(0);
4529
- }
4530
- if (isInstall) {
4531
- const { installHooks: installHooks2 } = await Promise.resolve().then(() => (init_manager(), exports_manager));
4532
- const scopeIdx = subArgs.indexOf("--scope");
4533
- const scope = scopeIdx >= 0 ? subArgs[scopeIdx + 1] : "user";
4534
- if (scopeIdx >= 0 && (!scope || scope.startsWith("-"))) {
4535
- throw new CliError3("Missing value for --scope. Valid values: user, project, local");
4536
- }
4537
- if (scopeIdx >= 0 && !["user", "project", "local"].includes(scope)) {
4538
- throw new CliError3(`Invalid scope: ${scope}. Valid values: user, project, local`);
4539
- }
4540
- const customIdx = subArgs.includes("--custom") ? subArgs.indexOf("--custom") : subArgs.includes("-c") ? subArgs.indexOf("-c") : -1;
4541
- const customPoliciesPath = customIdx >= 0 ? subArgs[customIdx + 1] : undefined;
4542
- if (customIdx >= 0 && (!customPoliciesPath || customPoliciesPath.startsWith("-"))) {
4543
- throw new CliError3(`Missing path after --custom/-c
4544
- Usage: --custom <path> (e.g. --custom ./my-policies.js)`);
4545
- }
4546
- const includeBeta = subArgs.includes("--beta");
4547
- const consumedIdxs = new Set;
4548
- if (scopeIdx >= 0)
4549
- consumedIdxs.add(scopeIdx + 1);
4550
- if (customIdx >= 0)
4551
- consumedIdxs.add(customIdx + 1);
4552
- const flags = new Set(["--install", "-i", "--scope", "--beta", "--custom", "-c"]);
4553
- const unknownInstallFlag = subArgs.find((a) => a.startsWith("-") && !flags.has(a));
4554
- if (unknownInstallFlag) {
4555
- throw new CliError3(`Unknown flag: ${unknownInstallFlag}
4556
- Run \`failproofai policies --help\` for usage.`);
4557
- }
4558
- const explicitPolicyNames = subArgs.filter((a, idx) => !a.startsWith("-") && !consumedIdxs.has(idx));
4559
- const policyNames = explicitPolicyNames.length > 0 ? explicitPolicyNames : customPoliciesPath !== undefined ? [] : undefined;
4560
- await installHooks2(policyNames, scope, undefined, includeBeta, undefined, customPoliciesPath);
4561
- process.exit(0);
4562
- }
4563
- if (isUninstall) {
4564
- const { removeHooks: removeHooks2 } = await Promise.resolve().then(() => (init_manager(), exports_manager));
4565
- const scopeIdx = subArgs.indexOf("--scope");
4566
- const scope = scopeIdx >= 0 ? subArgs[scopeIdx + 1] : "user";
4567
- if (scopeIdx >= 0 && (!scope || scope.startsWith("-"))) {
4568
- throw new CliError3("Missing value for --scope. Valid values: user, project, local, all");
4569
- }
4570
- if (scopeIdx >= 0 && !["user", "project", "local", "all"].includes(scope)) {
4571
- throw new CliError3(`Invalid scope: ${scope}. Valid values: user, project, local, all`);
4572
- }
4573
- const betaOnly = subArgs.includes("--beta");
4574
- const removeCustomHooks = subArgs.includes("--custom") || subArgs.includes("-c");
4575
- const consumedIdxs = new Set;
4576
- if (scopeIdx >= 0)
4577
- consumedIdxs.add(scopeIdx + 1);
4578
- const flags = new Set(["--uninstall", "-u", "--scope", "--beta", "--custom", "-c"]);
4579
- const unknownUninstallFlag = subArgs.find((a) => a.startsWith("-") && !flags.has(a));
4580
- if (unknownUninstallFlag) {
4581
- throw new CliError3(`Unknown flag: ${unknownUninstallFlag}
4582
- Run \`failproofai policies --help\` for usage.`);
4583
- }
4584
- const policyNames = subArgs.filter((a, idx) => !a.startsWith("-") && !consumedIdxs.has(idx));
4585
- await removeHooks2(policyNames.length > 0 ? policyNames : undefined, scope, undefined, { betaOnly, removeCustomHooks });
4586
- process.exit(0);
4587
- }
4588
- const knownListFlags = new Set(["--install", "-i", "--uninstall", "-u", "--help", "-h", "--list"]);
4589
- const unknownListArg = subArgs.find((a) => a.startsWith("-") && !knownListFlags.has(a));
4590
- if (unknownListArg) {
4591
- throw new CliError3(`Unknown flag: ${unknownListArg}
4592
- Run \`failproofai policies --help\` for usage.`);
4593
- }
4594
- const positionalArgs = subArgs.filter((a) => !a.startsWith("-"));
4595
- if (positionalArgs.length > 0) {
4596
- throw new CliError3(`Unexpected argument: ${positionalArgs[0]}
4597
- Run \`failproofai policies --help\` for usage.`);
4598
- }
4599
- const { listHooks: listHooks2 } = await Promise.resolve().then(() => (init_manager(), exports_manager));
4600
- await listHooks2();
4601
- process.exit(0);
4602
- }
4603
- if (args[0] === "login") {
4604
- const { login: login2 } = await Promise.resolve().then(() => (init_login(), exports_login));
4605
- await login2();
4606
- process.exit(0);
4607
- }
4608
- if (args[0] === "logout") {
4609
- const { logout: logout2 } = await Promise.resolve().then(() => (init_logout(), exports_logout));
4610
- await logout2();
4611
- process.exit(0);
4612
- }
4613
- if (args[0] === "whoami") {
4614
- const { whoami: whoami2 } = await Promise.resolve().then(() => (init_logout(), exports_logout));
4615
- whoami2();
4616
- process.exit(0);
4617
- }
4618
- if (args[0] === "relay") {
4619
- const subcmd = args[1];
4620
- const { relayStatus: relayStatus2, stopRelay: stopRelay3 } = await Promise.resolve().then(() => (init_pid2(), exports_pid));
4621
- if (subcmd === "status") {
4622
- const s = relayStatus2();
4623
- if (s.running)
4624
- console.log(`Relay daemon running (pid ${s.pid})`);
4625
- else if (s.pid !== null)
4626
- console.log(`Stale PID file (${s.pid}); daemon not running`);
4627
- else
4628
- console.log("Relay daemon not running");
4629
- process.exit(0);
4630
- }
4631
- if (subcmd === "stop") {
4632
- const stopped = stopRelay3();
4633
- console.log(stopped ? "Relay daemon stopped" : "Relay daemon was not running");
4634
- process.exit(0);
4635
- }
4636
- if (subcmd === "start") {
4637
- const { ensureRelayRunning: ensureRelayRunning3, waitForRelayAlive: waitForRelayAlive3 } = await Promise.resolve().then(() => (init_daemon2(), exports_daemon2));
4638
- ensureRelayRunning3();
4639
- const alive = await waitForRelayAlive3();
4640
- const s = relayStatus2();
4641
- if (alive && s.running) {
4642
- console.log(`Relay daemon started (pid ${s.pid})`);
4643
- process.exit(0);
4644
- }
4645
- console.log("Failed to start daemon");
4646
- process.exit(1);
4647
- }
4648
- throw new CliError3(`Usage: failproofai relay <start|stop|status>`);
4649
- }
4650
- if (args[0] === "sync") {
4651
- const { runOneShotSync: runOneShotSync3 } = await Promise.resolve().then(() => (init_daemon2(), exports_daemon2));
4652
- const count = await runOneShotSync3();
4653
- console.log(`Synced ${count} event${count === 1 ? "" : "s"} to server`);
4654
- process.exit(0);
4655
- }
4656
- const knownFlags = ["--version", "-v", "--help", "-h", "--hook"];
4657
- const unknownFlag = args.find((a) => a.startsWith("-") && !knownFlags.includes(a));
4658
- if (unknownFlag) {
4659
- let levenshtein = function(a, b) {
4660
- const m = a.length, n = b.length;
4661
- const dp = Array.from({ length: m + 1 }, (_, i) => Array.from({ length: n + 1 }, (_2, j) => i === 0 ? j : j === 0 ? i : 0));
4662
- for (let i = 1;i <= m; i++)
4663
- for (let j = 1;j <= n; j++)
4664
- dp[i][j] = a[i - 1] === b[j - 1] ? dp[i - 1][j - 1] : 1 + Math.min(dp[i - 1][j], dp[i][j - 1], dp[i - 1][j - 1]);
4665
- return dp[m][n];
4666
- };
4667
- const primary = ["--version", "--help", "--hook", "policies", "login", "logout", "whoami", "relay", "sync"];
4668
- const closest = primary.reduce((best, flag) => {
4669
- const dist = levenshtein(unknownFlag, flag);
4670
- return dist < best.dist ? { flag, dist } : best;
4671
- }, { flag: primary[0], dist: Infinity });
4672
- throw new CliError3(`Unknown flag: ${unknownFlag}
4673
- Did you mean: ${closest.flag}?
4674
- Run \`failproofai --help\` for usage details.`);
4675
- }
4676
- const unknownSubcommand = args.find((a) => !a.startsWith("-") && !SUBCOMMANDS.includes(a));
4677
- if (unknownSubcommand) {
4678
- throw new CliError3(`Unknown command: ${unknownSubcommand}
4679
- Did you mean: failproofai policies?
4680
- Run \`failproofai --help\` for usage details.`);
4681
- }
4682
- const { launch: launch2 } = await Promise.resolve().then(() => (init_launch(), exports_launch));
4683
- launch2("start");
4684
- }
4685
- var { CliError: CliError3 } = await Promise.resolve().then(() => (init_cli_error2(), exports_cli_error));
4686
- try {
4687
- await runCli();
4688
- } catch (err) {
4689
- if (err instanceof CliError3) {
4690
- console.error(`Error: ${err.message}`);
4691
- process.exit(err.exitCode);
4692
- }
4693
- const msg = err instanceof Error ? err.message : String(err);
4694
- console.error(`Unexpected error: ${msg}`);
4695
- process.exit(2);
4696
- }