failproofai 0.0.6-beta.2 → 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 (511) 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]__0i5kvry._.js → [root-of-the-server]__0om-5pe._.js} +2 -2
  57. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0w6l33k._.js +2 -2
  58. package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__05akje6._.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/{05j1px0r8yzh6.js → 02dqjyv6_9mhq.js} +2 -2
  71. package/.next/standalone/.next/static/chunks/{14cl9poem30dq.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/{00j0rr7rh8ef8.js → 0o547jv-k_k35.js} +1 -1
  74. package/.next/standalone/.next/static/chunks/{0ijk_kek9_wyx.js → 0pk2h2.mjxy.m.js} +1 -1
  75. package/.next/standalone/.next/static/chunks/{0xpl.oscrakvx.js → 0rcwkbh24w38b.js} +1 -1
  76. package/.next/standalone/.next/static/chunks/{1052sguyd-.ka.js → 140xx_tfr~lm_.js} +1 -1
  77. package/.next/standalone/.next/static/chunks/{0npb~873.wvg3.js → 169_e4dq~1~b6.js} +1 -1
  78. package/.next/standalone/.next/static/chunks/{0badv41uxa56..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/bin/failproofai.mjs +91 -4
  83. package/dist/cli.mjs +1155 -54
  84. package/package.json +2 -2
  85. package/scripts/prune-standalone.mjs +128 -0
  86. package/src/auth/login.ts +104 -0
  87. package/src/auth/logout.ts +50 -0
  88. package/src/auth/token-store.ts +64 -0
  89. package/src/hooks/builtin-policies.ts +22 -20
  90. package/src/hooks/handler.ts +35 -15
  91. package/src/relay/daemon.ts +362 -0
  92. package/src/relay/pid.ts +76 -0
  93. package/src/relay/queue.ts +225 -0
  94. package/.next/standalone/.claude/settings.json +0 -316
  95. package/.next/standalone/.failproofai/policies/review-policies.mjs +0 -113
  96. package/.next/standalone/.failproofai/policies/workflow-policies.mjs +0 -63
  97. package/.next/standalone/.failproofai/policies-config.json +0 -39
  98. package/.next/standalone/.next/static/chunks/0gu_a.a80ritd.css +0 -1
  99. package/.next/standalone/AGENTS.md +0 -80
  100. package/.next/standalone/CHANGELOG.md +0 -151
  101. package/.next/standalone/CLAUDE.md +0 -165
  102. package/.next/standalone/CONTRIBUTING.md +0 -76
  103. package/.next/standalone/Dockerfile.docs +0 -12
  104. package/.next/standalone/LICENSE +0 -42
  105. package/.next/standalone/README.md +0 -301
  106. package/.next/standalone/bin/failproofai.mjs +0 -352
  107. package/.next/standalone/bun.lock +0 -1119
  108. package/.next/standalone/components.json +0 -23
  109. package/.next/standalone/dist/cli.mjs +0 -3595
  110. package/.next/standalone/dist/index.js +0 -80
  111. package/.next/standalone/docs/ar/architecture.mdx +0 -334
  112. package/.next/standalone/docs/ar/built-in-policies.mdx +0 -574
  113. package/.next/standalone/docs/ar/cli/dashboard.mdx +0 -28
  114. package/.next/standalone/docs/ar/cli/environment-variables.mdx +0 -34
  115. package/.next/standalone/docs/ar/cli/hook.mdx +0 -31
  116. package/.next/standalone/docs/ar/cli/install-policies.mdx +0 -48
  117. package/.next/standalone/docs/ar/cli/list-policies.mdx +0 -31
  118. package/.next/standalone/docs/ar/cli/remove-policies.mdx +0 -43
  119. package/.next/standalone/docs/ar/cli/version.mdx +0 -13
  120. package/.next/standalone/docs/ar/configuration.mdx +0 -223
  121. package/.next/standalone/docs/ar/custom-policies.mdx +0 -354
  122. package/.next/standalone/docs/ar/dashboard.mdx +0 -142
  123. package/.next/standalone/docs/ar/examples.mdx +0 -307
  124. package/.next/standalone/docs/ar/for-agents.mdx +0 -39
  125. package/.next/standalone/docs/ar/getting-started.mdx +0 -187
  126. package/.next/standalone/docs/ar/introduction.mdx +0 -58
  127. package/.next/standalone/docs/ar/package-aliases.mdx +0 -82
  128. package/.next/standalone/docs/ar/testing.mdx +0 -261
  129. package/.next/standalone/docs/architecture.mdx +0 -332
  130. package/.next/standalone/docs/built-in-policies.mdx +0 -574
  131. package/.next/standalone/docs/cli/dashboard.mdx +0 -28
  132. package/.next/standalone/docs/cli/environment-variables.mdx +0 -34
  133. package/.next/standalone/docs/cli/hook.mdx +0 -30
  134. package/.next/standalone/docs/cli/install-policies.mdx +0 -47
  135. package/.next/standalone/docs/cli/list-policies.mdx +0 -31
  136. package/.next/standalone/docs/cli/remove-policies.mdx +0 -43
  137. package/.next/standalone/docs/cli/version.mdx +0 -12
  138. package/.next/standalone/docs/configuration.mdx +0 -222
  139. package/.next/standalone/docs/custom-policies.mdx +0 -353
  140. package/.next/standalone/docs/dashboard.mdx +0 -142
  141. package/.next/standalone/docs/de/architecture.mdx +0 -332
  142. package/.next/standalone/docs/de/built-in-policies.mdx +0 -574
  143. package/.next/standalone/docs/de/cli/dashboard.mdx +0 -28
  144. package/.next/standalone/docs/de/cli/environment-variables.mdx +0 -34
  145. package/.next/standalone/docs/de/cli/hook.mdx +0 -30
  146. package/.next/standalone/docs/de/cli/install-policies.mdx +0 -47
  147. package/.next/standalone/docs/de/cli/list-policies.mdx +0 -31
  148. package/.next/standalone/docs/de/cli/remove-policies.mdx +0 -43
  149. package/.next/standalone/docs/de/cli/version.mdx +0 -12
  150. package/.next/standalone/docs/de/configuration.mdx +0 -222
  151. package/.next/standalone/docs/de/custom-policies.mdx +0 -353
  152. package/.next/standalone/docs/de/dashboard.mdx +0 -142
  153. package/.next/standalone/docs/de/examples.mdx +0 -307
  154. package/.next/standalone/docs/de/for-agents.mdx +0 -38
  155. package/.next/standalone/docs/de/getting-started.mdx +0 -186
  156. package/.next/standalone/docs/de/introduction.mdx +0 -57
  157. package/.next/standalone/docs/de/package-aliases.mdx +0 -82
  158. package/.next/standalone/docs/de/testing.mdx +0 -260
  159. package/.next/standalone/docs/docs.json +0 -1002
  160. package/.next/standalone/docs/es/architecture.mdx +0 -332
  161. package/.next/standalone/docs/es/built-in-policies.mdx +0 -574
  162. package/.next/standalone/docs/es/cli/dashboard.mdx +0 -28
  163. package/.next/standalone/docs/es/cli/environment-variables.mdx +0 -34
  164. package/.next/standalone/docs/es/cli/hook.mdx +0 -30
  165. package/.next/standalone/docs/es/cli/install-policies.mdx +0 -47
  166. package/.next/standalone/docs/es/cli/list-policies.mdx +0 -31
  167. package/.next/standalone/docs/es/cli/remove-policies.mdx +0 -43
  168. package/.next/standalone/docs/es/cli/version.mdx +0 -12
  169. package/.next/standalone/docs/es/configuration.mdx +0 -222
  170. package/.next/standalone/docs/es/custom-policies.mdx +0 -353
  171. package/.next/standalone/docs/es/dashboard.mdx +0 -142
  172. package/.next/standalone/docs/es/examples.mdx +0 -307
  173. package/.next/standalone/docs/es/for-agents.mdx +0 -38
  174. package/.next/standalone/docs/es/getting-started.mdx +0 -186
  175. package/.next/standalone/docs/es/introduction.mdx +0 -57
  176. package/.next/standalone/docs/es/package-aliases.mdx +0 -82
  177. package/.next/standalone/docs/es/testing.mdx +0 -260
  178. package/.next/standalone/docs/examples.mdx +0 -307
  179. package/.next/standalone/docs/favicon.ico +0 -0
  180. package/.next/standalone/docs/for-agents.mdx +0 -38
  181. package/.next/standalone/docs/fr/architecture.mdx +0 -332
  182. package/.next/standalone/docs/fr/built-in-policies.mdx +0 -574
  183. package/.next/standalone/docs/fr/cli/dashboard.mdx +0 -28
  184. package/.next/standalone/docs/fr/cli/environment-variables.mdx +0 -34
  185. package/.next/standalone/docs/fr/cli/hook.mdx +0 -30
  186. package/.next/standalone/docs/fr/cli/install-policies.mdx +0 -47
  187. package/.next/standalone/docs/fr/cli/list-policies.mdx +0 -31
  188. package/.next/standalone/docs/fr/cli/remove-policies.mdx +0 -43
  189. package/.next/standalone/docs/fr/cli/version.mdx +0 -12
  190. package/.next/standalone/docs/fr/configuration.mdx +0 -222
  191. package/.next/standalone/docs/fr/custom-policies.mdx +0 -353
  192. package/.next/standalone/docs/fr/dashboard.mdx +0 -142
  193. package/.next/standalone/docs/fr/examples.mdx +0 -307
  194. package/.next/standalone/docs/fr/for-agents.mdx +0 -38
  195. package/.next/standalone/docs/fr/getting-started.mdx +0 -186
  196. package/.next/standalone/docs/fr/introduction.mdx +0 -57
  197. package/.next/standalone/docs/fr/package-aliases.mdx +0 -82
  198. package/.next/standalone/docs/fr/testing.mdx +0 -260
  199. package/.next/standalone/docs/getting-started.mdx +0 -186
  200. package/.next/standalone/docs/he/architecture.mdx +0 -333
  201. package/.next/standalone/docs/he/built-in-policies.mdx +0 -574
  202. package/.next/standalone/docs/he/cli/dashboard.mdx +0 -28
  203. package/.next/standalone/docs/he/cli/environment-variables.mdx +0 -34
  204. package/.next/standalone/docs/he/cli/hook.mdx +0 -30
  205. package/.next/standalone/docs/he/cli/install-policies.mdx +0 -47
  206. package/.next/standalone/docs/he/cli/list-policies.mdx +0 -32
  207. package/.next/standalone/docs/he/cli/remove-policies.mdx +0 -43
  208. package/.next/standalone/docs/he/cli/version.mdx +0 -12
  209. package/.next/standalone/docs/he/configuration.mdx +0 -223
  210. package/.next/standalone/docs/he/custom-policies.mdx +0 -353
  211. package/.next/standalone/docs/he/dashboard.mdx +0 -142
  212. package/.next/standalone/docs/he/examples.mdx +0 -307
  213. package/.next/standalone/docs/he/for-agents.mdx +0 -38
  214. package/.next/standalone/docs/he/getting-started.mdx +0 -186
  215. package/.next/standalone/docs/he/introduction.mdx +0 -57
  216. package/.next/standalone/docs/he/package-aliases.mdx +0 -82
  217. package/.next/standalone/docs/he/testing.mdx +0 -260
  218. package/.next/standalone/docs/hi/architecture.mdx +0 -334
  219. package/.next/standalone/docs/hi/built-in-policies.mdx +0 -576
  220. package/.next/standalone/docs/hi/cli/dashboard.mdx +0 -28
  221. package/.next/standalone/docs/hi/cli/environment-variables.mdx +0 -34
  222. package/.next/standalone/docs/hi/cli/hook.mdx +0 -30
  223. package/.next/standalone/docs/hi/cli/install-policies.mdx +0 -47
  224. package/.next/standalone/docs/hi/cli/list-policies.mdx +0 -31
  225. package/.next/standalone/docs/hi/cli/remove-policies.mdx +0 -43
  226. package/.next/standalone/docs/hi/cli/version.mdx +0 -12
  227. package/.next/standalone/docs/hi/configuration.mdx +0 -222
  228. package/.next/standalone/docs/hi/custom-policies.mdx +0 -354
  229. package/.next/standalone/docs/hi/dashboard.mdx +0 -142
  230. package/.next/standalone/docs/hi/examples.mdx +0 -309
  231. package/.next/standalone/docs/hi/for-agents.mdx +0 -38
  232. package/.next/standalone/docs/hi/getting-started.mdx +0 -187
  233. package/.next/standalone/docs/hi/introduction.mdx +0 -57
  234. package/.next/standalone/docs/hi/package-aliases.mdx +0 -82
  235. package/.next/standalone/docs/hi/testing.mdx +0 -260
  236. package/.next/standalone/docs/i18n/README.ar.md +0 -312
  237. package/.next/standalone/docs/i18n/README.de.md +0 -307
  238. package/.next/standalone/docs/i18n/README.es.md +0 -307
  239. package/.next/standalone/docs/i18n/README.fr.md +0 -307
  240. package/.next/standalone/docs/i18n/README.he.md +0 -312
  241. package/.next/standalone/docs/i18n/README.hi.md +0 -307
  242. package/.next/standalone/docs/i18n/README.it.md +0 -307
  243. package/.next/standalone/docs/i18n/README.ja.md +0 -307
  244. package/.next/standalone/docs/i18n/README.ko.md +0 -307
  245. package/.next/standalone/docs/i18n/README.pt-br.md +0 -307
  246. package/.next/standalone/docs/i18n/README.ru.md +0 -308
  247. package/.next/standalone/docs/i18n/README.tr.md +0 -307
  248. package/.next/standalone/docs/i18n/README.vi.md +0 -307
  249. package/.next/standalone/docs/i18n/README.zh.md +0 -307
  250. package/.next/standalone/docs/introduction.mdx +0 -57
  251. package/.next/standalone/docs/it/architecture.mdx +0 -334
  252. package/.next/standalone/docs/it/built-in-policies.mdx +0 -574
  253. package/.next/standalone/docs/it/cli/dashboard.mdx +0 -28
  254. package/.next/standalone/docs/it/cli/environment-variables.mdx +0 -34
  255. package/.next/standalone/docs/it/cli/hook.mdx +0 -30
  256. package/.next/standalone/docs/it/cli/install-policies.mdx +0 -47
  257. package/.next/standalone/docs/it/cli/list-policies.mdx +0 -31
  258. package/.next/standalone/docs/it/cli/remove-policies.mdx +0 -43
  259. package/.next/standalone/docs/it/cli/version.mdx +0 -12
  260. package/.next/standalone/docs/it/configuration.mdx +0 -222
  261. package/.next/standalone/docs/it/custom-policies.mdx +0 -353
  262. package/.next/standalone/docs/it/dashboard.mdx +0 -142
  263. package/.next/standalone/docs/it/examples.mdx +0 -307
  264. package/.next/standalone/docs/it/for-agents.mdx +0 -38
  265. package/.next/standalone/docs/it/getting-started.mdx +0 -186
  266. package/.next/standalone/docs/it/introduction.mdx +0 -57
  267. package/.next/standalone/docs/it/package-aliases.mdx +0 -82
  268. package/.next/standalone/docs/it/testing.mdx +0 -260
  269. package/.next/standalone/docs/ja/architecture.mdx +0 -332
  270. package/.next/standalone/docs/ja/built-in-policies.mdx +0 -572
  271. package/.next/standalone/docs/ja/cli/dashboard.mdx +0 -28
  272. package/.next/standalone/docs/ja/cli/environment-variables.mdx +0 -34
  273. package/.next/standalone/docs/ja/cli/hook.mdx +0 -30
  274. package/.next/standalone/docs/ja/cli/install-policies.mdx +0 -47
  275. package/.next/standalone/docs/ja/cli/list-policies.mdx +0 -31
  276. package/.next/standalone/docs/ja/cli/remove-policies.mdx +0 -43
  277. package/.next/standalone/docs/ja/cli/version.mdx +0 -12
  278. package/.next/standalone/docs/ja/configuration.mdx +0 -222
  279. package/.next/standalone/docs/ja/custom-policies.mdx +0 -353
  280. package/.next/standalone/docs/ja/dashboard.mdx +0 -142
  281. package/.next/standalone/docs/ja/examples.mdx +0 -307
  282. package/.next/standalone/docs/ja/for-agents.mdx +0 -38
  283. package/.next/standalone/docs/ja/getting-started.mdx +0 -186
  284. package/.next/standalone/docs/ja/introduction.mdx +0 -57
  285. package/.next/standalone/docs/ja/package-aliases.mdx +0 -82
  286. package/.next/standalone/docs/ja/testing.mdx +0 -260
  287. package/.next/standalone/docs/ko/architecture.mdx +0 -332
  288. package/.next/standalone/docs/ko/built-in-policies.mdx +0 -572
  289. package/.next/standalone/docs/ko/cli/dashboard.mdx +0 -28
  290. package/.next/standalone/docs/ko/cli/environment-variables.mdx +0 -34
  291. package/.next/standalone/docs/ko/cli/hook.mdx +0 -30
  292. package/.next/standalone/docs/ko/cli/install-policies.mdx +0 -47
  293. package/.next/standalone/docs/ko/cli/list-policies.mdx +0 -31
  294. package/.next/standalone/docs/ko/cli/remove-policies.mdx +0 -43
  295. package/.next/standalone/docs/ko/cli/version.mdx +0 -12
  296. package/.next/standalone/docs/ko/configuration.mdx +0 -222
  297. package/.next/standalone/docs/ko/custom-policies.mdx +0 -353
  298. package/.next/standalone/docs/ko/dashboard.mdx +0 -142
  299. package/.next/standalone/docs/ko/examples.mdx +0 -307
  300. package/.next/standalone/docs/ko/for-agents.mdx +0 -38
  301. package/.next/standalone/docs/ko/getting-started.mdx +0 -186
  302. package/.next/standalone/docs/ko/introduction.mdx +0 -57
  303. package/.next/standalone/docs/ko/package-aliases.mdx +0 -82
  304. package/.next/standalone/docs/ko/testing.mdx +0 -260
  305. package/.next/standalone/docs/logo/dark.svg +0 -21
  306. package/.next/standalone/docs/logo/exosphere-dark.png +0 -0
  307. package/.next/standalone/docs/logo/exosphere-light.png +0 -0
  308. package/.next/standalone/docs/logo/light.svg +0 -21
  309. package/.next/standalone/docs/package-aliases.mdx +0 -82
  310. package/.next/standalone/docs/pt-br/architecture.mdx +0 -332
  311. package/.next/standalone/docs/pt-br/built-in-policies.mdx +0 -574
  312. package/.next/standalone/docs/pt-br/cli/dashboard.mdx +0 -28
  313. package/.next/standalone/docs/pt-br/cli/environment-variables.mdx +0 -34
  314. package/.next/standalone/docs/pt-br/cli/hook.mdx +0 -30
  315. package/.next/standalone/docs/pt-br/cli/install-policies.mdx +0 -47
  316. package/.next/standalone/docs/pt-br/cli/list-policies.mdx +0 -31
  317. package/.next/standalone/docs/pt-br/cli/remove-policies.mdx +0 -43
  318. package/.next/standalone/docs/pt-br/cli/version.mdx +0 -12
  319. package/.next/standalone/docs/pt-br/configuration.mdx +0 -222
  320. package/.next/standalone/docs/pt-br/custom-policies.mdx +0 -353
  321. package/.next/standalone/docs/pt-br/dashboard.mdx +0 -142
  322. package/.next/standalone/docs/pt-br/examples.mdx +0 -307
  323. package/.next/standalone/docs/pt-br/for-agents.mdx +0 -38
  324. package/.next/standalone/docs/pt-br/getting-started.mdx +0 -186
  325. package/.next/standalone/docs/pt-br/introduction.mdx +0 -57
  326. package/.next/standalone/docs/pt-br/package-aliases.mdx +0 -82
  327. package/.next/standalone/docs/pt-br/testing.mdx +0 -260
  328. package/.next/standalone/docs/ru/architecture.mdx +0 -333
  329. package/.next/standalone/docs/ru/built-in-policies.mdx +0 -574
  330. package/.next/standalone/docs/ru/cli/dashboard.mdx +0 -28
  331. package/.next/standalone/docs/ru/cli/environment-variables.mdx +0 -34
  332. package/.next/standalone/docs/ru/cli/hook.mdx +0 -30
  333. package/.next/standalone/docs/ru/cli/install-policies.mdx +0 -48
  334. package/.next/standalone/docs/ru/cli/list-policies.mdx +0 -32
  335. package/.next/standalone/docs/ru/cli/remove-policies.mdx +0 -43
  336. package/.next/standalone/docs/ru/cli/version.mdx +0 -12
  337. package/.next/standalone/docs/ru/configuration.mdx +0 -222
  338. package/.next/standalone/docs/ru/custom-policies.mdx +0 -354
  339. package/.next/standalone/docs/ru/dashboard.mdx +0 -142
  340. package/.next/standalone/docs/ru/examples.mdx +0 -309
  341. package/.next/standalone/docs/ru/for-agents.mdx +0 -38
  342. package/.next/standalone/docs/ru/getting-started.mdx +0 -186
  343. package/.next/standalone/docs/ru/introduction.mdx +0 -57
  344. package/.next/standalone/docs/ru/package-aliases.mdx +0 -82
  345. package/.next/standalone/docs/ru/testing.mdx +0 -260
  346. package/.next/standalone/docs/testing.mdx +0 -260
  347. package/.next/standalone/docs/tr/architecture.mdx +0 -333
  348. package/.next/standalone/docs/tr/built-in-policies.mdx +0 -574
  349. package/.next/standalone/docs/tr/cli/dashboard.mdx +0 -28
  350. package/.next/standalone/docs/tr/cli/environment-variables.mdx +0 -34
  351. package/.next/standalone/docs/tr/cli/hook.mdx +0 -30
  352. package/.next/standalone/docs/tr/cli/install-policies.mdx +0 -47
  353. package/.next/standalone/docs/tr/cli/list-policies.mdx +0 -31
  354. package/.next/standalone/docs/tr/cli/remove-policies.mdx +0 -44
  355. package/.next/standalone/docs/tr/cli/version.mdx +0 -12
  356. package/.next/standalone/docs/tr/configuration.mdx +0 -222
  357. package/.next/standalone/docs/tr/custom-policies.mdx +0 -353
  358. package/.next/standalone/docs/tr/dashboard.mdx +0 -142
  359. package/.next/standalone/docs/tr/examples.mdx +0 -308
  360. package/.next/standalone/docs/tr/for-agents.mdx +0 -38
  361. package/.next/standalone/docs/tr/getting-started.mdx +0 -186
  362. package/.next/standalone/docs/tr/introduction.mdx +0 -57
  363. package/.next/standalone/docs/tr/package-aliases.mdx +0 -82
  364. package/.next/standalone/docs/tr/testing.mdx +0 -260
  365. package/.next/standalone/docs/vi/architecture.mdx +0 -334
  366. package/.next/standalone/docs/vi/built-in-policies.mdx +0 -575
  367. package/.next/standalone/docs/vi/cli/dashboard.mdx +0 -28
  368. package/.next/standalone/docs/vi/cli/environment-variables.mdx +0 -34
  369. package/.next/standalone/docs/vi/cli/hook.mdx +0 -30
  370. package/.next/standalone/docs/vi/cli/install-policies.mdx +0 -47
  371. package/.next/standalone/docs/vi/cli/list-policies.mdx +0 -31
  372. package/.next/standalone/docs/vi/cli/remove-policies.mdx +0 -43
  373. package/.next/standalone/docs/vi/cli/version.mdx +0 -13
  374. package/.next/standalone/docs/vi/configuration.mdx +0 -222
  375. package/.next/standalone/docs/vi/custom-policies.mdx +0 -353
  376. package/.next/standalone/docs/vi/dashboard.mdx +0 -142
  377. package/.next/standalone/docs/vi/examples.mdx +0 -308
  378. package/.next/standalone/docs/vi/for-agents.mdx +0 -38
  379. package/.next/standalone/docs/vi/getting-started.mdx +0 -186
  380. package/.next/standalone/docs/vi/introduction.mdx +0 -57
  381. package/.next/standalone/docs/vi/package-aliases.mdx +0 -82
  382. package/.next/standalone/docs/vi/testing.mdx +0 -260
  383. package/.next/standalone/docs/zh/architecture.mdx +0 -332
  384. package/.next/standalone/docs/zh/built-in-policies.mdx +0 -572
  385. package/.next/standalone/docs/zh/cli/dashboard.mdx +0 -28
  386. package/.next/standalone/docs/zh/cli/environment-variables.mdx +0 -34
  387. package/.next/standalone/docs/zh/cli/hook.mdx +0 -30
  388. package/.next/standalone/docs/zh/cli/install-policies.mdx +0 -47
  389. package/.next/standalone/docs/zh/cli/list-policies.mdx +0 -31
  390. package/.next/standalone/docs/zh/cli/remove-policies.mdx +0 -43
  391. package/.next/standalone/docs/zh/cli/version.mdx +0 -12
  392. package/.next/standalone/docs/zh/configuration.mdx +0 -222
  393. package/.next/standalone/docs/zh/custom-policies.mdx +0 -353
  394. package/.next/standalone/docs/zh/dashboard.mdx +0 -142
  395. package/.next/standalone/docs/zh/examples.mdx +0 -307
  396. package/.next/standalone/docs/zh/for-agents.mdx +0 -38
  397. package/.next/standalone/docs/zh/getting-started.mdx +0 -186
  398. package/.next/standalone/docs/zh/introduction.mdx +0 -57
  399. package/.next/standalone/docs/zh/package-aliases.mdx +0 -82
  400. package/.next/standalone/docs/zh/testing.mdx +0 -260
  401. package/.next/standalone/eslint.config.mjs +0 -15
  402. package/.next/standalone/examples/convention-policies/security-policies.mjs +0 -40
  403. package/.next/standalone/examples/convention-policies/workflow-policies.mjs +0 -41
  404. package/.next/standalone/examples/policies-advanced/index.js +0 -103
  405. package/.next/standalone/examples/policies-advanced/utils.js +0 -35
  406. package/.next/standalone/examples/policies-basic.js +0 -77
  407. package/.next/standalone/examples/policies-notification.js +0 -104
  408. package/.next/standalone/node_modules/@img/colour/color.cjs +0 -1594
  409. package/.next/standalone/node_modules/@img/colour/index.cjs +0 -1
  410. package/.next/standalone/node_modules/@img/colour/package.json +0 -45
  411. package/.next/standalone/node_modules/@img/sharp-libvips-linux-x64/README.md +0 -46
  412. package/.next/standalone/node_modules/@img/sharp-libvips-linux-x64/lib/glib-2.0/include/glibconfig.h +0 -221
  413. package/.next/standalone/node_modules/@img/sharp-libvips-linux-x64/lib/index.js +0 -1
  414. package/.next/standalone/node_modules/@img/sharp-libvips-linux-x64/lib/libvips-cpp.so.8.17.3 +0 -0
  415. package/.next/standalone/node_modules/@img/sharp-libvips-linux-x64/package.json +0 -42
  416. package/.next/standalone/node_modules/@img/sharp-libvips-linux-x64/versions.json +0 -30
  417. package/.next/standalone/node_modules/@img/sharp-libvips-linuxmusl-x64/README.md +0 -46
  418. package/.next/standalone/node_modules/@img/sharp-libvips-linuxmusl-x64/lib/glib-2.0/include/glibconfig.h +0 -221
  419. package/.next/standalone/node_modules/@img/sharp-libvips-linuxmusl-x64/lib/index.js +0 -1
  420. package/.next/standalone/node_modules/@img/sharp-libvips-linuxmusl-x64/lib/libvips-cpp.so.8.17.3 +0 -0
  421. package/.next/standalone/node_modules/@img/sharp-libvips-linuxmusl-x64/package.json +0 -42
  422. package/.next/standalone/node_modules/@img/sharp-libvips-linuxmusl-x64/versions.json +0 -30
  423. package/.next/standalone/node_modules/@img/sharp-linux-x64/lib/sharp-linux-x64.node +0 -0
  424. package/.next/standalone/node_modules/@img/sharp-linux-x64/package.json +0 -46
  425. package/.next/standalone/node_modules/@img/sharp-linuxmusl-x64/lib/sharp-linuxmusl-x64.node +0 -0
  426. package/.next/standalone/node_modules/@img/sharp-linuxmusl-x64/package.json +0 -46
  427. package/.next/standalone/node_modules/detect-libc/lib/detect-libc.js +0 -313
  428. package/.next/standalone/node_modules/detect-libc/lib/elf.js +0 -39
  429. package/.next/standalone/node_modules/detect-libc/lib/filesystem.js +0 -51
  430. package/.next/standalone/node_modules/detect-libc/lib/process.js +0 -24
  431. package/.next/standalone/node_modules/detect-libc/package.json +0 -44
  432. package/.next/standalone/node_modules/sharp/lib/channel.js +0 -177
  433. package/.next/standalone/node_modules/sharp/lib/colour.js +0 -195
  434. package/.next/standalone/node_modules/sharp/lib/composite.js +0 -212
  435. package/.next/standalone/node_modules/sharp/lib/constructor.js +0 -499
  436. package/.next/standalone/node_modules/sharp/lib/index.js +0 -16
  437. package/.next/standalone/node_modules/sharp/lib/input.js +0 -809
  438. package/.next/standalone/node_modules/sharp/lib/is.js +0 -143
  439. package/.next/standalone/node_modules/sharp/lib/libvips.js +0 -207
  440. package/.next/standalone/node_modules/sharp/lib/operation.js +0 -1016
  441. package/.next/standalone/node_modules/sharp/lib/output.js +0 -1666
  442. package/.next/standalone/node_modules/sharp/lib/resize.js +0 -595
  443. package/.next/standalone/node_modules/sharp/lib/sharp.js +0 -121
  444. package/.next/standalone/node_modules/sharp/lib/utility.js +0 -291
  445. package/.next/standalone/node_modules/sharp/node_modules/semver/classes/comparator.js +0 -143
  446. package/.next/standalone/node_modules/sharp/node_modules/semver/classes/range.js +0 -557
  447. package/.next/standalone/node_modules/sharp/node_modules/semver/classes/semver.js +0 -333
  448. package/.next/standalone/node_modules/sharp/node_modules/semver/functions/cmp.js +0 -54
  449. package/.next/standalone/node_modules/sharp/node_modules/semver/functions/coerce.js +0 -62
  450. package/.next/standalone/node_modules/sharp/node_modules/semver/functions/compare.js +0 -7
  451. package/.next/standalone/node_modules/sharp/node_modules/semver/functions/eq.js +0 -5
  452. package/.next/standalone/node_modules/sharp/node_modules/semver/functions/gt.js +0 -5
  453. package/.next/standalone/node_modules/sharp/node_modules/semver/functions/gte.js +0 -5
  454. package/.next/standalone/node_modules/sharp/node_modules/semver/functions/lt.js +0 -5
  455. package/.next/standalone/node_modules/sharp/node_modules/semver/functions/lte.js +0 -5
  456. package/.next/standalone/node_modules/sharp/node_modules/semver/functions/neq.js +0 -5
  457. package/.next/standalone/node_modules/sharp/node_modules/semver/functions/parse.js +0 -18
  458. package/.next/standalone/node_modules/sharp/node_modules/semver/functions/satisfies.js +0 -12
  459. package/.next/standalone/node_modules/sharp/node_modules/semver/internal/constants.js +0 -37
  460. package/.next/standalone/node_modules/sharp/node_modules/semver/internal/debug.js +0 -11
  461. package/.next/standalone/node_modules/sharp/node_modules/semver/internal/identifiers.js +0 -29
  462. package/.next/standalone/node_modules/sharp/node_modules/semver/internal/lrucache.js +0 -42
  463. package/.next/standalone/node_modules/sharp/node_modules/semver/internal/parse-options.js +0 -17
  464. package/.next/standalone/node_modules/sharp/node_modules/semver/internal/re.js +0 -223
  465. package/.next/standalone/node_modules/sharp/node_modules/semver/package.json +0 -78
  466. package/.next/standalone/node_modules/sharp/package.json +0 -202
  467. package/.next/standalone/scripts/alias-proxy.js +0 -18
  468. package/.next/standalone/scripts/dev.ts +0 -3
  469. package/.next/standalone/scripts/install-telemetry.mjs +0 -108
  470. package/.next/standalone/scripts/launch.ts +0 -83
  471. package/.next/standalone/scripts/parse-script-args.ts +0 -87
  472. package/.next/standalone/scripts/postinstall.mjs +0 -121
  473. package/.next/standalone/scripts/preuninstall.mjs +0 -131
  474. package/.next/standalone/scripts/publish-aliases.mjs +0 -87
  475. package/.next/standalone/scripts/start.ts +0 -3
  476. package/.next/standalone/scripts/sync-hook-events-prompt.md +0 -60
  477. package/.next/standalone/scripts/translate-docs/cache.ts +0 -62
  478. package/.next/standalone/scripts/translate-docs/cli.ts +0 -357
  479. package/.next/standalone/scripts/translate-docs/config.ts +0 -248
  480. package/.next/standalone/scripts/translate-docs/mdx-translator.ts +0 -153
  481. package/.next/standalone/scripts/translate-docs/mintlify-nav.ts +0 -107
  482. package/.next/standalone/scripts/translate-docs/readme-translator.ts +0 -154
  483. package/.next/standalone/scripts/translate-docs/translator.ts +0 -68
  484. package/.next/standalone/scripts/translate-docs/types.ts +0 -43
  485. package/.next/standalone/src/cli-error.ts +0 -18
  486. package/.next/standalone/src/hooks/builtin-policies.ts +0 -1613
  487. package/.next/standalone/src/hooks/custom-hooks-loader.ts +0 -205
  488. package/.next/standalone/src/hooks/custom-hooks-registry.ts +0 -30
  489. package/.next/standalone/src/hooks/handler.ts +0 -202
  490. package/.next/standalone/src/hooks/hook-activity-store.ts +0 -349
  491. package/.next/standalone/src/hooks/hook-logger.ts +0 -133
  492. package/.next/standalone/src/hooks/hook-telemetry.ts +0 -43
  493. package/.next/standalone/src/hooks/hooks-config.ts +0 -166
  494. package/.next/standalone/src/hooks/install-prompt.ts +0 -357
  495. package/.next/standalone/src/hooks/llm-client.ts +0 -90
  496. package/.next/standalone/src/hooks/loader-utils.ts +0 -178
  497. package/.next/standalone/src/hooks/manager.ts +0 -692
  498. package/.next/standalone/src/hooks/policy-evaluator.ts +0 -224
  499. package/.next/standalone/src/hooks/policy-helpers.ts +0 -16
  500. package/.next/standalone/src/hooks/policy-registry.ts +0 -90
  501. package/.next/standalone/src/hooks/policy-types.ts +0 -77
  502. package/.next/standalone/src/hooks/types.ts +0 -63
  503. package/.next/standalone/src/index.ts +0 -19
  504. package/.next/standalone/src/posthog-key.ts +0 -5
  505. package/.next/standalone/tailwind.config.ts +0 -11
  506. package/.next/standalone/tsconfig.json +0 -42
  507. package/.next/standalone/vitest.config.e2e.mts +0 -24
  508. package/.next/standalone/vitest.config.mts +0 -23
  509. /package/.next/standalone/.next/static/{A9pNTZdoYJTVyPAYwQMx5 → wOkJXoch1UmRAmyIuKZWc}/_buildManifest.js +0 -0
  510. /package/.next/standalone/.next/static/{A9pNTZdoYJTVyPAYwQMx5 → wOkJXoch1UmRAmyIuKZWc}/_clientMiddlewareManifest.js +0 -0
  511. /package/.next/standalone/.next/static/{A9pNTZdoYJTVyPAYwQMx5 → wOkJXoch1UmRAmyIuKZWc}/_ssgManifest.js +0 -0
@@ -1,3595 +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
- timeout: 3000
285
- }).trim();
286
- gitBranchCache.set(cwd, branch);
287
- }
288
- return branch || null;
289
- } catch {
290
- return null;
291
- }
292
- }
293
- function getHeadSha(cwd) {
294
- try {
295
- const sha = execSync("git rev-parse HEAD", {
296
- cwd,
297
- encoding: "utf8",
298
- timeout: 3000
299
- }).trim();
300
- return sha || null;
301
- } catch {
302
- return null;
303
- }
304
- }
305
- function getThirdPartyCheckRuns(cwd, sha) {
306
- try {
307
- const json = execFileSync("gh", [
308
- "api",
309
- `repos/{owner}/{repo}/commits/${sha}/check-runs`,
310
- "--jq",
311
- '.check_runs | map(select(.app.slug != "github-actions")) | map({name: .name, status: .status, conclusion: (.conclusion // "")})'
312
- ], {
313
- cwd,
314
- encoding: "utf8",
315
- timeout: 15000
316
- }).trim();
317
- if (!json || json === "[]")
318
- return [];
319
- return JSON.parse(json);
320
- } catch {
321
- return [];
322
- }
323
- }
324
- function getCommitStatuses(cwd, sha) {
325
- try {
326
- const json = execFileSync("gh", [
327
- "api",
328
- `repos/{owner}/{repo}/commits/${sha}/statuses`,
329
- "--jq",
330
- "map({name: .context, state: .state}) | unique_by(.name)"
331
- ], {
332
- cwd,
333
- encoding: "utf8",
334
- timeout: 15000
335
- }).trim();
336
- if (!json || json === "[]")
337
- return [];
338
- const statuses = JSON.parse(json);
339
- return statuses.map((s) => ({
340
- name: s.name,
341
- status: s.state === "pending" ? "in_progress" : "completed",
342
- conclusion: s.state === "pending" ? "" : s.state === "success" ? "success" : "failure"
343
- }));
344
- } catch {
345
- return [];
346
- }
347
- }
348
- function matchesAllowedPattern(cmd, pattern) {
349
- const cmdTokens = parseArgvTokens(cmd);
350
- const patTokens = parseArgvTokens(pattern);
351
- if (cmdTokens.length < patTokens.length)
352
- return false;
353
- if (cmdTokens.some((tok) => SHELL_OPERATORS.has(tok)))
354
- return false;
355
- if (cmdTokens.some((tok) => SHELL_METACHAR_RE.test(tok)))
356
- return false;
357
- return patTokens.every((tok, i) => tok === "*" || tok === cmdTokens[i]);
358
- }
359
- function sanitizeJwt(ctx) {
360
- const output = JSON.stringify(ctx.payload);
361
- if (JWT_RE.test(output)) {
362
- return {
363
- decision: "deny",
364
- reason: "JWT token detected in tool output",
365
- message: "[REDACTED: JWT token removed by failproofai]"
366
- };
367
- }
368
- return allow();
369
- }
370
- function sanitizeApiKeys(ctx) {
371
- const output = JSON.stringify(ctx.payload);
372
- for (const [pattern, label] of API_KEY_PATTERNS) {
373
- if (pattern.test(output)) {
374
- return {
375
- decision: "deny",
376
- reason: `${label} detected in tool output`,
377
- message: `[REDACTED: ${label} removed by failproofai]`
378
- };
379
- }
380
- }
381
- const additional = ctx.params?.additionalPatterns ?? [];
382
- for (const { regex, label } of additional) {
383
- try {
384
- if (new RegExp(regex).test(output)) {
385
- return {
386
- decision: "deny",
387
- reason: `${label} detected in tool output`,
388
- message: `[REDACTED: ${label} removed by failproofai]`
389
- };
390
- }
391
- } catch {
392
- hookLogWarn(`additionalPatterns: invalid regex "${regex}", skipping`);
393
- }
394
- }
395
- return allow();
396
- }
397
- function sanitizeConnectionStrings(ctx) {
398
- const output = JSON.stringify(ctx.payload);
399
- if (CONNECTION_STRING_RE.test(output)) {
400
- return {
401
- decision: "deny",
402
- reason: "Database connection string with credentials detected in tool output",
403
- message: "[REDACTED: connection string removed by failproofai]"
404
- };
405
- }
406
- return allow();
407
- }
408
- function sanitizePrivateKeyContent(ctx) {
409
- const output = JSON.stringify(ctx.payload);
410
- if (PRIVATE_KEY_RE.test(output)) {
411
- return {
412
- decision: "deny",
413
- reason: "Private key content detected in tool output",
414
- message: "[REDACTED: private key content removed by failproofai]"
415
- };
416
- }
417
- return allow();
418
- }
419
- function sanitizeBearerTokens(ctx) {
420
- const output = JSON.stringify(ctx.payload);
421
- if (BEARER_TOKEN_RE.test(output)) {
422
- return {
423
- decision: "deny",
424
- reason: "Bearer token detected in tool output",
425
- message: "[REDACTED: Bearer token removed by failproofai]"
426
- };
427
- }
428
- return allow();
429
- }
430
- function warnDestructiveSql(ctx) {
431
- if (ctx.toolName !== "Bash")
432
- return allow();
433
- const cmd = getCommand(ctx);
434
- if (!SQL_TOOL_RE.test(cmd))
435
- return allow();
436
- if (DESTRUCTIVE_SQL_RE.test(cmd)) {
437
- return instruct("STOP: This command contains destructive SQL (DROP/TRUNCATE/DELETE). Confirm with the user before executing.");
438
- }
439
- if (DELETE_NO_WHERE_RE.test(cmd) && !SQL_WHERE_RE.test(cmd)) {
440
- return instruct("STOP: This command contains destructive SQL (DROP/TRUNCATE/DELETE). Confirm with the user before executing.");
441
- }
442
- return allow();
443
- }
444
- function warnLargeFileWrite(ctx) {
445
- if (ctx.toolName !== "Write")
446
- return allow();
447
- const content = ctx.toolInput?.content;
448
- if (typeof content !== "string")
449
- return allow();
450
- const thresholdKb = ctx.params?.thresholdKb ?? 1024;
451
- const thresholdBytes = thresholdKb * 1024;
452
- if (content.length > thresholdBytes) {
453
- 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.`);
454
- }
455
- return allow();
456
- }
457
- function warnPackagePublish(ctx) {
458
- if (ctx.toolName !== "Bash")
459
- return allow();
460
- const cmd = getCommand(ctx);
461
- if (PUBLISH_CMD_RE.test(cmd)) {
462
- return instruct("STOP: This command publishes a package to a public registry. Confirm with the user that this is intentional.");
463
- }
464
- return allow();
465
- }
466
- function protectEnvVars(ctx) {
467
- if (ctx.toolName !== "Bash")
468
- return allow();
469
- const cmd = getCommand(ctx);
470
- if (ENV_PRINTENV_RE.test(cmd)) {
471
- return deny("Command reads environment variables");
472
- }
473
- if (ECHO_ENV_RE.test(cmd)) {
474
- return deny("Command echoes environment variable");
475
- }
476
- if (EXPORT_RE.test(cmd)) {
477
- return deny("Command exports environment variable");
478
- }
479
- if (PS_ENV_VAR_RE.test(cmd)) {
480
- return deny("Command reads environment variable via PowerShell");
481
- }
482
- if (PS_CHILDITEM_ENV_RE.test(cmd)) {
483
- return deny("Command reads environment variables via PowerShell");
484
- }
485
- if (DOTNET_GETENV_RE.test(cmd)) {
486
- return deny("Command reads environment variable via .NET");
487
- }
488
- if (CMD_ECHO_ENV_RE.test(cmd)) {
489
- return deny("Command echoes environment variable via cmd");
490
- }
491
- return allow();
492
- }
493
- function blockEnvFiles(ctx) {
494
- const cmd = getCommand(ctx);
495
- const filePath = getFilePath(ctx);
496
- if (filePath && ENV_FILE_PATH_RE.test(filePath)) {
497
- return deny("Access to .env file blocked");
498
- }
499
- if (ctx.toolName === "Bash" && ENV_CMD_RE.test(cmd)) {
500
- return deny("Command references .env file");
501
- }
502
- return allow();
503
- }
504
- function blockSudo(ctx) {
505
- if (ctx.toolName !== "Bash")
506
- return allow();
507
- const cmd = getCommand(ctx).trimStart();
508
- if (SUDO_RE.test(cmd) || cmd.startsWith("sudo ")) {
509
- const allowPatterns = ctx.params?.allowPatterns ?? [];
510
- if (allowPatterns.some((p) => matchesAllowedPattern(cmd, p)))
511
- return allow();
512
- return deny("sudo commands are blocked");
513
- }
514
- if (PS_ELEVATION_RE.test(cmd)) {
515
- return deny("Elevated process launch is blocked");
516
- }
517
- if (RUNAS_RE.test(cmd)) {
518
- return deny("runas elevation is blocked");
519
- }
520
- return allow();
521
- }
522
- function blockCurlPipeSh(ctx) {
523
- if (ctx.toolName !== "Bash")
524
- return allow();
525
- const cmd = getCommand(ctx);
526
- if (CURL_PIPE_SH_RE.test(cmd)) {
527
- return deny("Piping downloads to shell is blocked");
528
- }
529
- if (PS_WEB_PIPE_RE.test(cmd)) {
530
- return deny("Piping downloads to Invoke-Expression is blocked");
531
- }
532
- return allow();
533
- }
534
- function extractGitPushArgs(cmd) {
535
- return cmd.split(/&&|\|\||[|;\n]/).map((s) => s.trim()).filter((s) => /^git\s+push\s/.test(s)).map((s) => s.replace(/^git\s+push\s+/, ""));
536
- }
537
- function blockPushMaster(ctx) {
538
- if (ctx.toolName !== "Bash")
539
- return allow();
540
- const protectedBranches = ctx.params?.protectedBranches ?? ["main", "master"];
541
- if (protectedBranches.length === 0)
542
- return allow();
543
- const args = extractGitPushArgs(getCommand(ctx));
544
- const branchPattern = new RegExp(`\\b(?:${protectedBranches.map((b) => b.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")).join("|")})\\b`);
545
- if (args.some((a) => branchPattern.test(a))) {
546
- return deny(`Pushing to ${protectedBranches.join("/")} is blocked`);
547
- }
548
- return allow();
549
- }
550
- function rmTargetIsAllowed(cmd, allowPaths) {
551
- if (allowPaths.length === 0)
552
- return false;
553
- const segments = cmd.split(/&&|\|\||[|;\n]/).map((s) => s.trim()).filter((s) => /\brm\b/.test(s));
554
- if (segments.length === 0)
555
- return false;
556
- for (const seg of segments) {
557
- const tokens = parseArgvTokens(seg);
558
- const rmIdx = tokens.findIndex((t) => t === "rm");
559
- if (rmIdx < 0)
560
- continue;
561
- const flagTokens = tokens.slice(rmIdx + 1).filter((t) => /^-[^-]/.test(t));
562
- const longFlagsInSeg = tokens.slice(rmIdx + 1).filter((t) => /^--/.test(t));
563
- if (!/r/i.test(flagTokens.join("")) && !longFlagsInSeg.some((f) => /^--recursive$/i.test(f)))
564
- continue;
565
- const pathArgs = tokens.slice(rmIdx + 1).filter((t) => !t.startsWith("-"));
566
- for (const target of pathArgs) {
567
- const normalized = target.replace(/\/\*$/, "").replace(/\/+$/, "") || "/";
568
- const covered = allowPaths.some((p) => {
569
- const np = p.replace(/\/+$/, "") || "/";
570
- return normalized === np || normalized.startsWith(np + "/");
571
- });
572
- if (!covered) {
573
- const segCovered = allowPaths.some((p) => {
574
- const escaped = p.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
575
- return new RegExp(`${escaped}(?:[/"'\\s/*]|$)`).test(seg);
576
- });
577
- if (!segCovered)
578
- return false;
579
- }
580
- }
581
- }
582
- return true;
583
- }
584
- function blockRmRf(ctx) {
585
- if (ctx.toolName !== "Bash")
586
- return allow();
587
- const cmd = getCommand(ctx);
588
- const hasDestructivePath = parseArgvTokens(cmd).some((token) => {
589
- const normalized = token.replace(/\/\*$/, "").replace(/\/+$/, "") || (token.startsWith("/") ? "/" : "");
590
- return normalized === "/" || normalized === "~" || /^\/[A-Za-z_][\w.-]*$/.test(normalized);
591
- });
592
- if (hasDestructivePath && (/rm\s+-[^\s]*r[^\s]*f[^\s]*/.test(cmd) || /rm\s+-[^\s]*f[^\s]*r[^\s]*/.test(cmd))) {
593
- const allowPaths = ctx.params?.allowPaths ?? [];
594
- if (rmTargetIsAllowed(cmd, allowPaths))
595
- return allow();
596
- return deny("Catastrophic deletion blocked");
597
- }
598
- if (hasDestructivePath && /\brm\b/.test(cmd)) {
599
- const tokens = parseArgvTokens(cmd);
600
- const shortFlags = tokens.filter((t) => /^-[^-]/.test(t)).join("");
601
- const longFlags = tokens.filter((t) => /^--/.test(t));
602
- const hasRecursive = /r/i.test(shortFlags) || longFlags.some((f) => /^--recursive$/i.test(f));
603
- const hasForce = /f/.test(shortFlags) || longFlags.some((f) => /^--force$/i.test(f));
604
- if (hasRecursive && hasForce) {
605
- const allowPaths = ctx.params?.allowPaths ?? [];
606
- if (rmTargetIsAllowed(cmd, allowPaths))
607
- return allow();
608
- return deny("Catastrophic deletion blocked");
609
- }
610
- }
611
- if (/Remove-Item\s+.*-Recurse.*-Force.*(?:[A-Z]:\\(?:\s|$)|\\\*)/i.test(cmd)) {
612
- return deny("Catastrophic deletion blocked");
613
- }
614
- if (/(?:rd|rmdir)\s+\/s\s+\/q\s+[A-Z]:\\/i.test(cmd)) {
615
- return deny("Catastrophic deletion blocked");
616
- }
617
- return allow();
618
- }
619
- function blockForcePush(ctx) {
620
- if (ctx.toolName !== "Bash")
621
- return allow();
622
- const args = extractGitPushArgs(getCommand(ctx));
623
- if (args.some((a) => FORCE_PUSH_RE.test(a))) {
624
- return deny("Force-pushing is blocked");
625
- }
626
- return allow();
627
- }
628
- function blockSecretsWrite(ctx) {
629
- if (ctx.toolName !== "Write")
630
- return allow();
631
- const filePath = getFilePath(ctx);
632
- if (SECRET_FILE_RE.test(filePath) || SECRET_FILE_ID_RSA_RE.test(filePath) || SECRET_FILE_CREDENTIALS_RE.test(filePath)) {
633
- return deny("Writing secret key files is blocked");
634
- }
635
- const additionalPatterns = ctx.params?.additionalPatterns ?? [];
636
- for (const pattern of additionalPatterns) {
637
- if (filePath.includes(pattern)) {
638
- return deny(`Writing blocked file pattern: ${pattern}`);
639
- }
640
- }
641
- return allow();
642
- }
643
- function extractAbsolutePaths(command) {
644
- const paths = [];
645
- const pathRe = /(?<![a-zA-Z0-9_.\-~\\])(?:~\/[^\s;|&"'()\[\]{}]*|~(?=\s|$|[;|&"'()\[\]{}])|\/[^\s;|&"'()\[\]{}]*)/g;
646
- function addPaths(s) {
647
- pathRe.lastIndex = 0;
648
- let m;
649
- while ((m = pathRe.exec(s)) !== null) {
650
- let p = m[0];
651
- if (p === "~")
652
- p = homedir3();
653
- else if (p.startsWith("~/"))
654
- p = join2(homedir3(), p.slice(2));
655
- paths.push(p);
656
- }
657
- }
658
- let firstBarePipe = command.length;
659
- let inDouble = false, inSingle = false;
660
- for (let i = 0;i < command.length; i++) {
661
- const c = command[i];
662
- if (c === '"' && !inSingle)
663
- inDouble = !inDouble;
664
- else if (c === "'" && !inDouble)
665
- inSingle = !inSingle;
666
- else if (c === "|" && !inDouble && !inSingle) {
667
- firstBarePipe = i;
668
- break;
669
- }
670
- }
671
- const firstSegment = command.slice(0, firstBarePipe);
672
- const quotedRe = /"([^"]*)"|'([^']*)'/g;
673
- let qm;
674
- while ((qm = quotedRe.exec(firstSegment)) !== null) {
675
- const content = qm[1] ?? qm[2] ?? "";
676
- if (/[*?\[\]^$+()\\]/.test(content))
677
- continue;
678
- addPaths(content);
679
- }
680
- const stripped = command.replace(/"[^"]*"/g, (m) => " ".repeat(m.length)).replace(/'[^']*'/g, (m) => " ".repeat(m.length));
681
- addPaths(stripped);
682
- return paths;
683
- }
684
- function blockReadOutsideCwd(ctx) {
685
- const cwd = ctx.session?.cwd;
686
- if (!cwd)
687
- return allow();
688
- const allowPaths = ctx.params?.allowPaths ?? [];
689
- if (ctx.toolName === "Bash") {
690
- const cmd = getCommand(ctx);
691
- if (!READ_LIKE_CMDS.test(cmd))
692
- return allow();
693
- const paths = extractAbsolutePaths(cmd);
694
- const cwdWithSep2 = cwd.endsWith("/") ? cwd : cwd + "/";
695
- for (const p of paths) {
696
- const resolved3 = resolve2(cwd, p);
697
- if (isClaudeSettingsFile(resolved3)) {
698
- return deny(`Reading Claude settings file blocked: ${resolved3}`);
699
- }
700
- if (isClaudeInternalPath(resolved3))
701
- continue;
702
- if (resolved3 === "/dev/null")
703
- continue;
704
- if (resolved3 !== cwd && !resolved3.startsWith(cwdWithSep2)) {
705
- if (allowPaths.some((ap) => resolved3 === ap || resolved3.startsWith(ap.endsWith("/") ? ap : ap + "/")))
706
- continue;
707
- return deny(`Bash read outside project directory blocked: ${resolved3}`);
708
- }
709
- }
710
- return allow();
711
- }
712
- const filePath = getFilePath(ctx);
713
- const searchPath = ctx.toolInput?.path ?? "";
714
- const target = filePath || searchPath;
715
- if (!target)
716
- return allow();
717
- const resolved2 = resolve2(cwd, target);
718
- if (isClaudeSettingsFile(resolved2)) {
719
- return deny(`Reading Claude settings file blocked: ${resolved2}`);
720
- }
721
- if (isClaudeInternalPath(resolved2))
722
- return allow();
723
- if (resolved2 === "/dev/null")
724
- return allow();
725
- const cwdWithSep = cwd.endsWith("/") ? cwd : cwd + "/";
726
- if (resolved2 !== cwd && !resolved2.startsWith(cwdWithSep)) {
727
- if (allowPaths.some((ap) => resolved2 === ap || resolved2.startsWith(ap.endsWith("/") ? ap : ap + "/")))
728
- return allow();
729
- return deny(`Access outside project directory blocked: ${resolved2}`);
730
- }
731
- return allow();
732
- }
733
- function blockWorkOnMain(ctx) {
734
- if (ctx.toolName !== "Bash")
735
- return allow();
736
- const cmd = getCommand(ctx);
737
- if (!GIT_COMMIT_MERGE_RE.test(cmd))
738
- return allow();
739
- const cwd = ctx.session?.cwd;
740
- if (!cwd)
741
- return allow();
742
- const branch = getCurrentBranch(cwd);
743
- if (!branch)
744
- return allow();
745
- const protectedBranches = ctx.params?.protectedBranches ?? ["main", "master"];
746
- if (protectedBranches.includes(branch)) {
747
- return deny(`Git ${cmd.match(/git\s+(\S+)/)?.[1] ?? "operation"} on ${branch} is blocked. Create a feature branch first.`);
748
- }
749
- return allow();
750
- }
751
- function blockFailproofaiCommands(ctx) {
752
- if (ctx.toolName !== "Bash")
753
- return allow();
754
- const cmd = getCommand(ctx);
755
- if (FAILPROOFAI_CLI_RE.test(cmd)) {
756
- return deny("Running failproofai CLI commands is blocked");
757
- }
758
- if (FAILPROOFAI_UNINSTALL_RE.test(cmd)) {
759
- return deny("Uninstalling failproofai is blocked");
760
- }
761
- return allow();
762
- }
763
- async function warnRepeatedToolCalls(ctx) {
764
- const THRESHOLD = 3;
765
- const transcriptPath = ctx.session?.transcriptPath;
766
- if (!transcriptPath || !ctx.toolName || !ctx.toolInput)
767
- return allow();
768
- const trackerPath = `${transcriptPath}.tool-calls.json`;
769
- const fingerprint = JSON.stringify({ tool: ctx.toolName, input: ctx.toolInput });
770
- let counts = {};
771
- try {
772
- const raw = await readFile(trackerPath, "utf8");
773
- counts = JSON.parse(raw);
774
- } catch {}
775
- const prevCount = counts[fingerprint] ?? 0;
776
- if (prevCount >= THRESHOLD) {
777
- 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.`);
778
- }
779
- counts[fingerprint] = prevCount + 1;
780
- try {
781
- const serialized = JSON.stringify(counts);
782
- if (serialized.length <= TOOL_CALL_TRACKER_MAX_BYTES) {
783
- await writeFile(trackerPath, serialized, "utf8");
784
- }
785
- } catch {}
786
- return allow();
787
- }
788
- function warnGitAmend(ctx) {
789
- if (ctx.toolName !== "Bash")
790
- return allow();
791
- const cmd = getCommand(ctx);
792
- if (GIT_AMEND_RE.test(cmd)) {
793
- 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.");
794
- }
795
- return allow();
796
- }
797
- function warnGitStashDrop(ctx) {
798
- if (ctx.toolName !== "Bash")
799
- return allow();
800
- const cmd = getCommand(ctx);
801
- if (GIT_STASH_DROP_RE.test(cmd)) {
802
- 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.");
803
- }
804
- return allow();
805
- }
806
- function warnAllFilesStaged(ctx) {
807
- if (ctx.toolName !== "Bash")
808
- return allow();
809
- const cmd = getCommand(ctx);
810
- if (GIT_ADD_ALL_RE.test(cmd)) {
811
- 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.");
812
- }
813
- return allow();
814
- }
815
- function warnSchemaAlteration(ctx) {
816
- if (ctx.toolName !== "Bash")
817
- return allow();
818
- const cmd = getCommand(ctx);
819
- if (!SQL_TOOL_RE.test(cmd))
820
- return allow();
821
- if (SCHEMA_ALTER_RE.test(cmd)) {
822
- 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.");
823
- }
824
- return allow();
825
- }
826
- function warnGlobalPackageInstall(ctx) {
827
- if (ctx.toolName !== "Bash")
828
- return allow();
829
- const cmd = getCommand(ctx);
830
- 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);
831
- if (isGlobal) {
832
- 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.");
833
- }
834
- return allow();
835
- }
836
- function preferPackageManager(ctx) {
837
- if (ctx.toolName !== "Bash")
838
- return allow();
839
- const cmd = getCommand(ctx);
840
- if (!cmd)
841
- return allow();
842
- const allowed = ctx.params?.allowed ?? [];
843
- if (allowed.length === 0)
844
- return allow();
845
- const allowedSet = new Set(allowed.map((a) => a.toLowerCase()));
846
- const blocked = ctx.params?.blocked ?? [];
847
- const allowedList = allowed.join(", ");
848
- const segments = cmd.split(SEGMENT_SPLIT_RE);
849
- for (const segment of segments) {
850
- const trimmed = segment.trim();
851
- if (!trimmed)
852
- continue;
853
- let segmentAllowed = false;
854
- for (const manager of allowedSet) {
855
- const patterns = PKG_MANAGER_DETECTORS[manager];
856
- if (!patterns)
857
- continue;
858
- for (const pattern of patterns) {
859
- if (pattern.test(trimmed)) {
860
- segmentAllowed = true;
861
- break;
862
- }
863
- }
864
- if (segmentAllowed)
865
- break;
866
- }
867
- if (segmentAllowed)
868
- continue;
869
- for (const [manager, patterns] of Object.entries(PKG_MANAGER_DETECTORS)) {
870
- if (allowedSet.has(manager))
871
- continue;
872
- for (const pattern of patterns) {
873
- if (pattern.test(trimmed)) {
874
- return deny(`"${manager}" is not an allowed package manager. ` + `Allowed package managers for this project: ${allowedList}. ` + `Rewrite this command using an allowed package manager.`);
875
- }
876
- }
877
- }
878
- for (const name of blocked) {
879
- const lower = name.toLowerCase();
880
- if (allowedSet.has(lower))
881
- continue;
882
- const re = new RegExp(`\\b${lower.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\\b`);
883
- if (re.test(trimmed)) {
884
- return deny(`"${lower}" is not an allowed package manager. ` + `Allowed package managers for this project: ${allowedList}. ` + `Rewrite this command using an allowed package manager.`);
885
- }
886
- }
887
- }
888
- return allow();
889
- }
890
- function warnBackgroundProcess(ctx) {
891
- if (ctx.toolName !== "Bash")
892
- return allow();
893
- const cmd = getCommand(ctx);
894
- 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);
895
- if (isBackground) {
896
- 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.");
897
- }
898
- return allow();
899
- }
900
- function requireCommitBeforeStop(ctx) {
901
- const cwd = ctx.session?.cwd;
902
- if (!cwd)
903
- return allow("No working directory available, skipping commit check.");
904
- try {
905
- const status = execSync("git status --porcelain", {
906
- cwd,
907
- encoding: "utf8",
908
- timeout: 5000
909
- }).trim();
910
- if (status.length > 0) {
911
- return deny("You have uncommitted changes in the working directory. Commit all changes now.");
912
- }
913
- return allow("All changes are committed.");
914
- } catch {
915
- return allow("Not a git repository, skipping commit check.");
916
- }
917
- }
918
- function requirePushBeforeStop(ctx) {
919
- const cwd = ctx.session?.cwd;
920
- if (!cwd)
921
- return allow("No working directory available, skipping push check.");
922
- try {
923
- const remotes = execSync("git remote", {
924
- cwd,
925
- encoding: "utf8",
926
- timeout: 3000
927
- }).trim();
928
- if (!remotes)
929
- return allow("No git remote configured, skipping push check.");
930
- const remote = ctx.params?.remote ?? "origin";
931
- const branch = getCurrentBranch(cwd);
932
- if (!branch || branch === "HEAD")
933
- return allow("Detached HEAD, skipping push check.");
934
- const baseBranch = ctx.params?.baseBranch ?? "main";
935
- if (branch === baseBranch) {
936
- return allow(`On base branch "${baseBranch}", skipping push check.`);
937
- }
938
- try {
939
- const ahead = execFileSync("git", ["log", `${remote}/${baseBranch}..HEAD`, "--oneline"], { cwd, encoding: "utf8", timeout: 5000 }).trim();
940
- if (!ahead) {
941
- return allow(`No commits ahead of ${remote}/${baseBranch}, skipping push check.`);
942
- }
943
- const diff = execFileSync("git", ["diff", "--stat", `${remote}/${baseBranch}`, "HEAD"], { cwd, encoding: "utf8", timeout: 5000 }).trim();
944
- if (!diff) {
945
- return allow(`No file changes compared to ${remote}/${baseBranch}, skipping push check.`);
946
- }
947
- } catch {}
948
- let hasTracking = false;
949
- try {
950
- execFileSync("git", ["rev-parse", "--verify", `${remote}/${branch}`], {
951
- cwd,
952
- encoding: "utf8",
953
- timeout: 3000
954
- });
955
- hasTracking = true;
956
- } catch {}
957
- if (!hasTracking) {
958
- return deny(`Branch "${branch}" has not been pushed to remote "${remote}". ` + `Run now: git push -u ${remote} ${branch}`);
959
- }
960
- const unpushed = execFileSync("git", ["log", `${remote}/${branch}..HEAD`, "--oneline"], {
961
- cwd,
962
- encoding: "utf8",
963
- timeout: 5000
964
- }).trim();
965
- if (unpushed.length > 0) {
966
- const commitCount = unpushed.split(`
967
- `).length;
968
- return deny(`You have ${commitCount} unpushed commit${commitCount > 1 ? "s" : ""} on branch "${branch}". ` + `Run now: git push`);
969
- }
970
- return allow(`All commits pushed to "${remote}".`);
971
- } catch {
972
- return allow("Could not check push status, skipping.");
973
- }
974
- }
975
- function requirePrBeforeStop(ctx) {
976
- const cwd = ctx.session?.cwd;
977
- if (!cwd)
978
- return allow("No working directory available, skipping PR check.");
979
- try {
980
- try {
981
- execSync("gh --version", { cwd, encoding: "utf8", timeout: 3000 });
982
- } catch {
983
- return allow("GitHub CLI (gh) not installed, skipping PR check.");
984
- }
985
- const branch = getCurrentBranch(cwd);
986
- if (!branch || branch === "HEAD")
987
- return allow("Detached HEAD, skipping PR check.");
988
- const baseBranch = ctx.params?.baseBranch ?? "main";
989
- if (branch === baseBranch) {
990
- return allow(`On base branch "${baseBranch}", skipping PR check.`);
991
- }
992
- try {
993
- const ahead = execFileSync("git", ["log", `origin/${baseBranch}..HEAD`, "--oneline"], { cwd, encoding: "utf8", timeout: 5000 }).trim();
994
- if (!ahead) {
995
- return allow(`No commits ahead of origin/${baseBranch}, skipping PR check.`);
996
- }
997
- const diff = execFileSync("git", ["diff", "--stat", `origin/${baseBranch}`, "HEAD"], { cwd, encoding: "utf8", timeout: 5000 }).trim();
998
- if (!diff) {
999
- return allow(`No file changes compared to origin/${baseBranch}, skipping PR check.`);
1000
- }
1001
- } catch {}
1002
- let prJson;
1003
- try {
1004
- prJson = execSync("gh pr view --json number,url,state", {
1005
- cwd,
1006
- encoding: "utf8",
1007
- timeout: 15000
1008
- }).trim();
1009
- } catch {
1010
- return deny(`No pull request found for branch "${branch}". ` + `Run now: gh pr create`);
1011
- }
1012
- const pr = JSON.parse(prJson);
1013
- if (pr.state === "OPEN") {
1014
- return allow(`PR #${pr.number} exists: ${pr.url}`);
1015
- }
1016
- if (pr.state === "MERGED") {
1017
- try {
1018
- execFileSync("git", ["fetch", "origin", `+refs/heads/${baseBranch}:refs/remotes/origin/${baseBranch}`], {
1019
- cwd,
1020
- encoding: "utf8",
1021
- timeout: 1e4
1022
- });
1023
- const freshAhead = execFileSync("git", ["log", `origin/${baseBranch}..HEAD`, "--oneline"], { cwd, encoding: "utf8", timeout: 5000 }).trim();
1024
- if (!freshAhead) {
1025
- return allow(`PR #${pr.number} was merged; branch is up to date with ${baseBranch}.`);
1026
- }
1027
- const freshDiff = execFileSync("git", ["diff", "--stat", `origin/${baseBranch}`, "HEAD"], { cwd, encoding: "utf8", timeout: 5000 }).trim();
1028
- if (!freshDiff) {
1029
- return allow(`PR #${pr.number} was merged; no file changes vs ${baseBranch}.`);
1030
- }
1031
- } catch {}
1032
- }
1033
- return deny(`Pull request for branch "${branch}" is ${pr.state.toLowerCase()}. Run now: gh pr create`);
1034
- } catch {
1035
- return allow("Could not check PR status, skipping.");
1036
- }
1037
- }
1038
- function requireCiGreenBeforeStop(ctx) {
1039
- const cwd = ctx.session?.cwd;
1040
- if (!cwd)
1041
- return allow("No working directory available, skipping CI check.");
1042
- try {
1043
- try {
1044
- execSync("gh --version", { cwd, encoding: "utf8", timeout: 3000 });
1045
- } catch {
1046
- return allow("GitHub CLI (gh) not installed, skipping CI check.");
1047
- }
1048
- const branch = getCurrentBranch(cwd);
1049
- if (!branch || branch === "HEAD")
1050
- return allow("Detached HEAD, skipping CI check.");
1051
- let workflowRuns = [];
1052
- try {
1053
- const runsJson = execFileSync("gh", ["run", "list", "--branch", branch, "--limit", "5", "--json", "status,conclusion,name"], { cwd, encoding: "utf8", timeout: 15000 }).trim();
1054
- if (runsJson && runsJson !== "[]") {
1055
- workflowRuns = JSON.parse(runsJson);
1056
- }
1057
- } catch {}
1058
- let thirdPartyChecks = [];
1059
- let commitStatuses = [];
1060
- const sha = getHeadSha(cwd);
1061
- if (sha) {
1062
- thirdPartyChecks = getThirdPartyCheckRuns(cwd, sha);
1063
- commitStatuses = getCommitStatuses(cwd, sha);
1064
- }
1065
- const allChecks = [...workflowRuns, ...thirdPartyChecks, ...commitStatuses];
1066
- if (allChecks.length === 0)
1067
- return allow(`No CI runs found for branch "${branch}".`);
1068
- const failing = allChecks.filter((r) => r.status === "completed" && r.conclusion !== "success" && r.conclusion !== "skipped" && r.conclusion !== "cancelled");
1069
- if (failing.length > 0) {
1070
- const names = failing.map((r) => `"${r.name}"`).join(", ");
1071
- return deny(`CI checks are failing on branch "${branch}": ${names}. Fix the failing checks now.`);
1072
- }
1073
- const pending = allChecks.filter((r) => r.status === "in_progress" || r.status === "queued" || r.status === "waiting");
1074
- if (pending.length > 0) {
1075
- const names = pending.map((r) => `"${r.name}"`).join(", ");
1076
- return deny(`CI checks are still running on branch "${branch}": ${names}. Wait for all checks to complete, then verify they pass.`);
1077
- }
1078
- return allow(`All CI checks passed on branch "${branch}".`);
1079
- } catch {
1080
- return allow("Could not check CI status, skipping.");
1081
- }
1082
- }
1083
- function registerBuiltinPolicies(enabledNames) {
1084
- const enabledSet = new Set(enabledNames);
1085
- for (const policy of BUILTIN_POLICIES) {
1086
- if (enabledSet.has(policy.name)) {
1087
- registerPolicy(policy.name, policy.description, policy.fn, policy.match);
1088
- }
1089
- }
1090
- }
1091
- 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;
1092
- var init_builtin_policies = __esm(() => {
1093
- init_hook_logger();
1094
- SHELL_OPERATORS = new Set(["&&", "||", "|", ";"]);
1095
- SHELL_METACHAR_RE = /[;&<>`$()\\]/;
1096
- JWT_RE = /eyJ[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}/;
1097
- API_KEY_PATTERNS = [
1098
- [/sk-ant-[A-Za-z0-9\-_]{20,}/, "Anthropic API key"],
1099
- [/sk-proj-[A-Za-z0-9\-_]{20,}/, "OpenAI project API key"],
1100
- [/sk-[A-Za-z0-9]{20,}/, "OpenAI API key"],
1101
- [/ghp_[A-Za-z0-9]{36}/, "GitHub personal access token"],
1102
- [/github_pat_[A-Za-z0-9_]{82}/, "GitHub fine-grained token"],
1103
- [/AKIA[A-Z0-9]{16}/, "AWS access key ID"],
1104
- [/sk_live_[A-Za-z0-9]{24,}/, "Stripe live secret key"],
1105
- [/sk_test_[A-Za-z0-9]{24,}/, "Stripe test secret key"],
1106
- [/AIza[0-9A-Za-z\-_]{35}/, "Google API key"]
1107
- ];
1108
- CONNECTION_STRING_RE = /(?:postgresql|postgres|mysql|mongodb(?:\+srv)?|redis|amqps?|smtps?):\/\/[^@\s]+@/;
1109
- PRIVATE_KEY_RE = /-----BEGIN (?:[A-Z]+ )?PRIVATE KEY-----/;
1110
- BEARER_TOKEN_RE = /Authorization:\s*Bearer\s+[A-Za-z0-9\-._~+/]{20,}/i;
1111
- SQL_TOOL_RE = /\b(?:psql|mysql|sqlite3|pgcli|clickhouse-client)\b/;
1112
- DESTRUCTIVE_SQL_RE = /\b(?:DROP\s+(?:TABLE|DATABASE|SCHEMA)|TRUNCATE\b)/i;
1113
- DELETE_NO_WHERE_RE = /\bDELETE\s+FROM\b/i;
1114
- SQL_WHERE_RE = /\bWHERE\b/i;
1115
- 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;
1116
- 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/;
1117
- ENV_PRINTENV_RE = /(?:^|\s|;|&&|\|\|)(?:env|printenv)(?:\s|$|;|&&|\|)/;
1118
- ECHO_ENV_RE = /echo\s+.*\$\{?[A-Za-z_]/;
1119
- EXPORT_RE = /(?:^|\s|;|&&|\|\|)export\s+\w+/;
1120
- PS_ENV_VAR_RE = /\$env:[A-Za-z_]/i;
1121
- PS_CHILDITEM_ENV_RE = /(?:Get-ChildItem|dir|gci|ls)\s+Env:/i;
1122
- DOTNET_GETENV_RE = /\[Environment\]::GetEnvironment/i;
1123
- CMD_ECHO_ENV_RE = /echo\s+%[A-Za-z_]/i;
1124
- ENV_FILE_PATH_RE = /(?:^|[\\/])\.env(?:\.|$)/;
1125
- ENV_CMD_RE = /\.env(?:\b|\s|$|\.)/;
1126
- SUDO_RE = /(?:^|;|&&|\|\|)\s*sudo\s/;
1127
- PS_ELEVATION_RE = /Start-Process\s+.*-Verb\s+RunAs/i;
1128
- RUNAS_RE = /(?:^|;|&&|\|\|)\s*runas\s/i;
1129
- CURL_PIPE_SH_RE = /(?:curl|wget)\s.*\|\s*(?:sh|bash|zsh|dash|ksh|csh|tcsh|fish|ash)\b/;
1130
- PS_WEB_PIPE_RE = /(?:Invoke-WebRequest|iwr|Invoke-RestMethod|irm)\s+.*\|\s*(?:Invoke-Expression|iex)/i;
1131
- FORCE_PUSH_RE = /(?:--force|-f\b)/;
1132
- SECRET_FILE_RE = /\.(?:pem|key)$/;
1133
- SECRET_FILE_ID_RSA_RE = /id_rsa/;
1134
- SECRET_FILE_CREDENTIALS_RE = /credentials/;
1135
- GIT_COMMIT_MERGE_RE = /git\s+(?:commit|merge|rebase|cherry-pick)\b/;
1136
- FAILPROOFAI_CLI_RE = /(?:^|;|&&|\|\||\|)\s*failproofai(?:\s|$)/;
1137
- 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)/;
1138
- GIT_AMEND_RE = /\bgit\s+commit\b.*--amend\b/;
1139
- GIT_STASH_DROP_RE = /\bgit\s+stash\s+(?:drop|clear)\b/;
1140
- GIT_ADD_ALL_RE = /\bgit\s+add\s+(?:-A\b|--all\b|\.(?:\s|$|;|&&|\|\|))/;
1141
- NPM_GLOBAL_RE = /\bnpm\s+(?:install|i)\b(?=.*(?:\s-g\b|--global\b))/;
1142
- YARN_GLOBAL_RE = /\byarn\s+global\s+add\b/;
1143
- PNPM_GLOBAL_RE = /\bpnpm\s+(?:add|install|i)\b(?=.*(?:\s-g\b|--global\b))/;
1144
- BUN_GLOBAL_RE = /\bbun\s+(?:install|add)\b(?=.*(?:\s-g\b|--global\b))/;
1145
- CARGO_INSTALL_RE = /\bcargo\s+install\b/;
1146
- PIP_SYSTEM_RE = /\bpip(?:3)?\s+install\b(?=.*(?:--user\b|--break-system-packages\b))/;
1147
- PKG_MANAGER_DETECTORS = {
1148
- pip: [/\bpip\b/, /\bpip3\b/, /\bpython3?\s+-m\s+pip\b/],
1149
- npm: [/\bnpm\b/, /\bnpx\b/],
1150
- yarn: [/\byarn\b/],
1151
- pnpm: [/\bpnpm\b/, /\bpnpx\b/],
1152
- bun: [/\bbun\b/, /\bbunx\b/],
1153
- uv: [/\buv\b/],
1154
- poetry: [/\bpoetry\b/],
1155
- pipenv: [/\bpipenv\b/],
1156
- conda: [/\bconda\b/],
1157
- cargo: [/\bcargo\b/]
1158
- };
1159
- NOHUP_RE = /\bnohup\s+\S/;
1160
- SCREEN_DETACH_RE = /\bscreen\s+-[A-Za-z]*d[A-Za-z]*\b/;
1161
- TMUX_DETACH_RE = /\btmux\s+(?:new-session|new)\b[^|&;]*-d\b/;
1162
- DISOWN_RE = /\bdisown\b/;
1163
- BACKGROUND_AMPERSAND_RE = /(?<![&|])\s?&\s*(?:$|#|;)/;
1164
- gitBranchCache = new Map;
1165
- READ_LIKE_CMDS = /(?:^|;|&&|\|\||\|)\s*(?:ls|find|cat|head|tail|less|more|wc|file|stat|tree|du)\s/;
1166
- SEGMENT_SPLIT_RE = /\s*(?:&&|\|\||\||;)\s*/;
1167
- BUILTIN_POLICIES = [
1168
- {
1169
- name: "sanitize-jwt",
1170
- description: "Stop Claude from reading JWTs in tool responses",
1171
- fn: sanitizeJwt,
1172
- match: { events: ["PostToolUse"] },
1173
- defaultEnabled: true,
1174
- category: "Sanitize"
1175
- },
1176
- {
1177
- name: "sanitize-api-keys",
1178
- description: "Stop Claude from reading API keys (OpenAI, Anthropic, GitHub, AWS, Stripe, Google) in tool responses",
1179
- fn: sanitizeApiKeys,
1180
- match: { events: ["PostToolUse"] },
1181
- defaultEnabled: true,
1182
- category: "Sanitize",
1183
- params: {
1184
- additionalPatterns: {
1185
- type: "pattern[]",
1186
- description: "Additional API key patterns to scrub, each with { regex, label }",
1187
- default: []
1188
- }
1189
- }
1190
- },
1191
- {
1192
- name: "sanitize-connection-strings",
1193
- description: "Stop Claude from reading database connection strings with embedded credentials in tool responses",
1194
- fn: sanitizeConnectionStrings,
1195
- match: { events: ["PostToolUse"] },
1196
- defaultEnabled: true,
1197
- category: "Sanitize"
1198
- },
1199
- {
1200
- name: "sanitize-private-key-content",
1201
- description: "Stop Claude from reading PEM private key content in tool responses",
1202
- fn: sanitizePrivateKeyContent,
1203
- match: { events: ["PostToolUse"] },
1204
- defaultEnabled: true,
1205
- category: "Sanitize"
1206
- },
1207
- {
1208
- name: "sanitize-bearer-tokens",
1209
- description: "Stop Claude from reading Authorization Bearer tokens in tool responses",
1210
- fn: sanitizeBearerTokens,
1211
- match: { events: ["PostToolUse"] },
1212
- defaultEnabled: true,
1213
- category: "Sanitize"
1214
- },
1215
- {
1216
- name: "protect-env-vars",
1217
- description: "Prevent commands that read environment variables",
1218
- fn: protectEnvVars,
1219
- match: { events: ["PreToolUse"], toolNames: ["Bash"] },
1220
- defaultEnabled: true,
1221
- category: "Environment"
1222
- },
1223
- {
1224
- name: "block-env-files",
1225
- description: "Block reading/writing .env files",
1226
- fn: blockEnvFiles,
1227
- match: { events: ["PreToolUse"] },
1228
- defaultEnabled: true,
1229
- category: "Environment"
1230
- },
1231
- {
1232
- name: "block-read-outside-cwd",
1233
- description: "Block file reads outside the session working directory",
1234
- fn: blockReadOutsideCwd,
1235
- match: { events: ["PreToolUse"], toolNames: ["Read", "Glob", "Grep", "Bash"] },
1236
- defaultEnabled: false,
1237
- category: "Environment",
1238
- params: {
1239
- allowPaths: {
1240
- type: "string[]",
1241
- description: "Absolute paths outside cwd that are allowed to be read",
1242
- default: []
1243
- }
1244
- }
1245
- },
1246
- {
1247
- name: "block-sudo",
1248
- description: "Block sudo commands",
1249
- fn: blockSudo,
1250
- match: { events: ["PreToolUse"], toolNames: ["Bash"] },
1251
- defaultEnabled: true,
1252
- category: "Dangerous Commands",
1253
- params: {
1254
- allowPatterns: {
1255
- type: "string[]",
1256
- description: "Sudo command patterns to allow, matched token-by-token (e.g. 'sudo systemctl status')",
1257
- default: []
1258
- }
1259
- }
1260
- },
1261
- {
1262
- name: "block-curl-pipe-sh",
1263
- description: "Block piping downloads to shell",
1264
- fn: blockCurlPipeSh,
1265
- match: { events: ["PreToolUse"], toolNames: ["Bash"] },
1266
- defaultEnabled: true,
1267
- category: "Dangerous Commands"
1268
- },
1269
- {
1270
- name: "block-rm-rf",
1271
- description: "Prevent catastrophic deletions",
1272
- fn: blockRmRf,
1273
- match: { events: ["PreToolUse"], toolNames: ["Bash"] },
1274
- defaultEnabled: false,
1275
- category: "Dangerous Commands",
1276
- params: {
1277
- allowPaths: {
1278
- type: "string[]",
1279
- description: "Paths that are allowed to be recursively deleted",
1280
- default: []
1281
- }
1282
- }
1283
- },
1284
- {
1285
- name: "block-failproofai-commands",
1286
- description: "Block failproofai CLI commands and uninstallation",
1287
- fn: blockFailproofaiCommands,
1288
- match: { events: ["PreToolUse"], toolNames: ["Bash"] },
1289
- defaultEnabled: true,
1290
- category: "Dangerous Commands"
1291
- },
1292
- {
1293
- name: "block-secrets-write",
1294
- description: "Block writing secret key files",
1295
- fn: blockSecretsWrite,
1296
- match: { events: ["PreToolUse"], toolNames: ["Write"] },
1297
- defaultEnabled: false,
1298
- category: "Dangerous Commands",
1299
- params: {
1300
- additionalPatterns: {
1301
- type: "string[]",
1302
- description: "Additional filename patterns (substrings) to block",
1303
- default: []
1304
- }
1305
- }
1306
- },
1307
- {
1308
- name: "block-push-master",
1309
- description: "Block pushing to main/master",
1310
- fn: blockPushMaster,
1311
- match: { events: ["PreToolUse"], toolNames: ["Bash"] },
1312
- defaultEnabled: true,
1313
- category: "Git",
1314
- params: {
1315
- protectedBranches: {
1316
- type: "string[]",
1317
- description: "Branch names to protect from direct pushes",
1318
- default: ["main", "master"]
1319
- }
1320
- }
1321
- },
1322
- {
1323
- name: "block-force-push",
1324
- description: "Prevent force-pushing to any branch",
1325
- fn: blockForcePush,
1326
- match: { events: ["PreToolUse"], toolNames: ["Bash"] },
1327
- defaultEnabled: false,
1328
- category: "Git"
1329
- },
1330
- {
1331
- name: "block-work-on-main",
1332
- description: "Block git commits and merges on main/master branch",
1333
- fn: blockWorkOnMain,
1334
- match: { events: ["PreToolUse"], toolNames: ["Bash"] },
1335
- defaultEnabled: false,
1336
- category: "Git",
1337
- params: {
1338
- protectedBranches: {
1339
- type: "string[]",
1340
- description: "Branch names where commits/merges are blocked",
1341
- default: ["main", "master"]
1342
- }
1343
- }
1344
- },
1345
- {
1346
- name: "warn-git-amend",
1347
- description: "Warns before amending git commits, which rewrites history",
1348
- fn: warnGitAmend,
1349
- match: { events: ["PreToolUse"], toolNames: ["Bash"] },
1350
- defaultEnabled: false,
1351
- category: "Git"
1352
- },
1353
- {
1354
- name: "warn-git-stash-drop",
1355
- description: "Warns before permanently deleting stashed changes",
1356
- fn: warnGitStashDrop,
1357
- match: { events: ["PreToolUse"], toolNames: ["Bash"] },
1358
- defaultEnabled: false,
1359
- category: "Git"
1360
- },
1361
- {
1362
- name: "warn-all-files-staged",
1363
- description: "Warns before staging all working tree files with git add -A / . / --all",
1364
- fn: warnAllFilesStaged,
1365
- match: { events: ["PreToolUse"], toolNames: ["Bash"] },
1366
- defaultEnabled: false,
1367
- category: "Git"
1368
- },
1369
- {
1370
- name: "warn-destructive-sql",
1371
- description: "Warn before executing destructive SQL (DROP/TRUNCATE/DELETE without WHERE) via database clients",
1372
- fn: warnDestructiveSql,
1373
- match: { events: ["PreToolUse"], toolNames: ["Bash"] },
1374
- defaultEnabled: false,
1375
- category: "Database"
1376
- },
1377
- {
1378
- name: "warn-schema-alteration",
1379
- description: "Warns before SQL schema changes (ALTER TABLE with column or rename operations)",
1380
- fn: warnSchemaAlteration,
1381
- match: { events: ["PreToolUse"], toolNames: ["Bash"] },
1382
- defaultEnabled: false,
1383
- category: "Database"
1384
- },
1385
- {
1386
- name: "warn-package-publish",
1387
- description: "Warn before publishing packages to public registries (npm, PyPI, crates.io, RubyGems, etc.)",
1388
- fn: warnPackagePublish,
1389
- match: { events: ["PreToolUse"], toolNames: ["Bash"] },
1390
- defaultEnabled: false,
1391
- category: "Packages & System"
1392
- },
1393
- {
1394
- name: "warn-global-package-install",
1395
- description: "Warns before installing packages globally (npm -g, cargo install, etc.)",
1396
- fn: warnGlobalPackageInstall,
1397
- match: { events: ["PreToolUse"], toolNames: ["Bash"] },
1398
- defaultEnabled: false,
1399
- category: "Packages & System"
1400
- },
1401
- {
1402
- name: "prefer-package-manager",
1403
- description: "Blocks non-preferred package managers and tells Claude to use an allowed one (e.g., uv instead of pip)",
1404
- fn: preferPackageManager,
1405
- match: { events: ["PreToolUse"], toolNames: ["Bash"] },
1406
- defaultEnabled: false,
1407
- category: "Packages & System",
1408
- params: {
1409
- allowed: {
1410
- type: "string[]",
1411
- description: "Allowed package manager names (e.g. ['uv', 'bun']). Any detected manager not in this list is blocked.",
1412
- default: []
1413
- },
1414
- blocked: {
1415
- type: "string[]",
1416
- description: "Additional manager names to block beyond the built-in list (e.g. ['pdm', 'pipx']).",
1417
- default: []
1418
- }
1419
- }
1420
- },
1421
- {
1422
- name: "warn-large-file-write",
1423
- description: "Warn before writing files larger than 1MB (configurable via thresholdKb param)",
1424
- fn: warnLargeFileWrite,
1425
- match: { events: ["PreToolUse"], toolNames: ["Write"] },
1426
- defaultEnabled: false,
1427
- category: "Packages & System",
1428
- params: {
1429
- thresholdKb: {
1430
- type: "number",
1431
- description: "File size threshold in KB above which a warning is issued",
1432
- default: 1024
1433
- }
1434
- }
1435
- },
1436
- {
1437
- name: "warn-background-process",
1438
- description: "Warns before starting detached or background processes",
1439
- fn: warnBackgroundProcess,
1440
- match: { events: ["PreToolUse"], toolNames: ["Bash"] },
1441
- defaultEnabled: false,
1442
- category: "Packages & System"
1443
- },
1444
- {
1445
- name: "warn-repeated-tool-calls",
1446
- description: "Warn when the same tool is called 3+ times with identical parameters",
1447
- fn: warnRepeatedToolCalls,
1448
- match: { events: ["PreToolUse"] },
1449
- defaultEnabled: false,
1450
- category: "AI Behavior"
1451
- },
1452
- {
1453
- name: "require-commit-before-stop",
1454
- description: "Require all changes to be committed before Claude stops",
1455
- fn: requireCommitBeforeStop,
1456
- match: { events: ["Stop"] },
1457
- defaultEnabled: false,
1458
- category: "Workflow"
1459
- },
1460
- {
1461
- name: "require-push-before-stop",
1462
- description: "Require all commits to be pushed to remote before Claude stops",
1463
- fn: requirePushBeforeStop,
1464
- match: { events: ["Stop"] },
1465
- defaultEnabled: false,
1466
- category: "Workflow",
1467
- params: {
1468
- remote: {
1469
- type: "string",
1470
- description: "Remote name to push to (default: origin)",
1471
- default: "origin"
1472
- },
1473
- baseBranch: {
1474
- type: "string",
1475
- description: "Base branch to compare against (default: main)",
1476
- default: "main"
1477
- }
1478
- }
1479
- },
1480
- {
1481
- name: "require-pr-before-stop",
1482
- description: "Require a pull request to exist for the current branch before Claude stops",
1483
- fn: requirePrBeforeStop,
1484
- match: { events: ["Stop"] },
1485
- defaultEnabled: false,
1486
- category: "Workflow",
1487
- params: {
1488
- baseBranch: {
1489
- type: "string",
1490
- description: "Base branch to compare against (default: main)",
1491
- default: "main"
1492
- }
1493
- }
1494
- },
1495
- {
1496
- name: "require-ci-green-before-stop",
1497
- description: "Require CI checks to pass on the current branch before Claude stops",
1498
- fn: requireCiGreenBeforeStop,
1499
- match: { events: ["Stop"] },
1500
- defaultEnabled: false,
1501
- category: "Workflow"
1502
- }
1503
- ];
1504
- });
1505
-
1506
- // src/hooks/policy-evaluator.ts
1507
- function appendHint(baseReason, hint) {
1508
- const base = baseReason.trim();
1509
- const normalizedHint = typeof hint === "string" ? hint.trim() : "";
1510
- if (!normalizedHint)
1511
- return base;
1512
- if (!base)
1513
- return normalizedHint;
1514
- return `${base}. ${normalizedHint}`;
1515
- }
1516
- async function evaluatePolicies(eventType, payload, session, config) {
1517
- const toolName = payload.tool_name;
1518
- const toolInput = payload.tool_input;
1519
- const policies = getPoliciesForEvent(eventType, toolName);
1520
- hookLogInfo(`evaluating ${policies.length} policies for ${eventType}`);
1521
- if (policies.length === 0) {
1522
- return { exitCode: 0, stdout: "", stderr: "", policyName: null, reason: null, decision: "allow" };
1523
- }
1524
- const baseCtx = {
1525
- eventType,
1526
- payload,
1527
- toolName,
1528
- toolInput,
1529
- session
1530
- };
1531
- const instructEntries = [];
1532
- const allowEntries = [];
1533
- for (const policy of policies) {
1534
- const schema = POLICY_PARAMS_MAP.get(policy.name);
1535
- let ctx;
1536
- if (schema) {
1537
- const userParams = config?.policyParams?.[policy.name] ?? {};
1538
- const resolvedParams = {};
1539
- for (const [key, spec] of Object.entries(schema)) {
1540
- resolvedParams[key] = key in userParams ? userParams[key] : spec.default;
1541
- }
1542
- ctx = { ...baseCtx, params: resolvedParams };
1543
- } else {
1544
- ctx = { ...baseCtx, params: {} };
1545
- }
1546
- let result;
1547
- try {
1548
- result = await policy.fn(ctx);
1549
- } catch (err) {
1550
- hookLogWarn(`policy "${policy.name}" threw: ${err instanceof Error ? err.message : String(err)}`);
1551
- continue;
1552
- }
1553
- if (result.decision === "deny") {
1554
- const reason = appendHint(result.reason ?? `Blocked by policy: ${policy.name}`, config?.policyParams?.[policy.name]?.hint);
1555
- hookLogInfo(`deny by "${policy.name}": ${reason}`);
1556
- const displayTool = ctx.toolName ?? "unknown tool";
1557
- if (eventType === "PreToolUse") {
1558
- const response = {
1559
- hookSpecificOutput: {
1560
- hookEventName: eventType,
1561
- permissionDecision: "deny",
1562
- permissionDecisionReason: `Blocked ${displayTool} by failproofai because: ${reason}, as per the policy configured by the user`
1563
- }
1564
- };
1565
- return {
1566
- exitCode: 0,
1567
- stdout: JSON.stringify(response),
1568
- stderr: "",
1569
- policyName: policy.name,
1570
- reason,
1571
- decision: "deny"
1572
- };
1573
- }
1574
- if (eventType === "PostToolUse") {
1575
- const response = {
1576
- hookSpecificOutput: {
1577
- hookEventName: eventType,
1578
- additionalContext: `Blocked ${displayTool} by failproofai because: ${reason}, as per the policy configured by the user`
1579
- }
1580
- };
1581
- return {
1582
- exitCode: 0,
1583
- stdout: JSON.stringify(response),
1584
- stderr: "",
1585
- policyName: policy.name,
1586
- reason,
1587
- decision: "deny"
1588
- };
1589
- }
1590
- if (eventType === "Stop") {
1591
- return {
1592
- exitCode: 2,
1593
- stdout: "",
1594
- stderr: `MANDATORY ACTION REQUIRED from failproofai (policy: ${policy.name}): ${reason}
1595
-
1596
- 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.`,
1597
- policyName: policy.name,
1598
- reason,
1599
- decision: "deny"
1600
- };
1601
- }
1602
- return {
1603
- exitCode: 2,
1604
- stdout: "",
1605
- stderr: reason,
1606
- policyName: policy.name,
1607
- reason,
1608
- decision: "deny"
1609
- };
1610
- }
1611
- if (result.decision === "instruct") {
1612
- const reason = appendHint(result.reason ?? `Instruction from policy: ${policy.name}`, config?.policyParams?.[policy.name]?.hint);
1613
- instructEntries.push({ policyName: policy.name, reason });
1614
- hookLogInfo(`instruct by "${policy.name}": ${reason}`);
1615
- }
1616
- if (result.decision === "allow" && result.reason) {
1617
- allowEntries.push({ policyName: policy.name, reason: result.reason });
1618
- }
1619
- }
1620
- if (instructEntries.length > 0) {
1621
- const combined = instructEntries.map((e) => e.reason).join(`
1622
- `);
1623
- const policyNames = instructEntries.map((e) => e.policyName);
1624
- if (eventType === "Stop") {
1625
- const policyAttribution = policyNames.length === 1 ? `policy: ${policyNames[0]}` : `policies: ${policyNames.join(", ")}`;
1626
- return {
1627
- exitCode: 2,
1628
- stdout: "",
1629
- stderr: `MANDATORY ACTION REQUIRED from failproofai (${policyAttribution}): ${combined}
1630
-
1631
- 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.`,
1632
- policyName: policyNames[0],
1633
- policyNames,
1634
- reason: combined,
1635
- decision: "instruct"
1636
- };
1637
- }
1638
- const response = {
1639
- hookSpecificOutput: {
1640
- hookEventName: eventType,
1641
- additionalContext: `Instruction from failproofai: ${combined}`
1642
- }
1643
- };
1644
- return {
1645
- exitCode: 0,
1646
- stdout: JSON.stringify(response),
1647
- stderr: "",
1648
- policyName: policyNames[0],
1649
- policyNames,
1650
- reason: combined,
1651
- decision: "instruct"
1652
- };
1653
- }
1654
- if (allowEntries.length > 0) {
1655
- const combined = allowEntries.map((e) => e.reason).join(`
1656
- `);
1657
- const policyNames = allowEntries.map((e) => e.policyName);
1658
- const supportsHookSpecificOutput = eventType === "PreToolUse" || eventType === "PostToolUse" || eventType === "UserPromptSubmit";
1659
- const response = supportsHookSpecificOutput ? { hookSpecificOutput: { hookEventName: eventType, additionalContext: `Note from failproofai: ${combined}` } } : { reason: combined };
1660
- const stderrMsg = allowEntries.map((e) => `[failproofai] ${e.policyName}: ${e.reason}`).join(`
1661
- `);
1662
- return { exitCode: 0, stdout: JSON.stringify(response), stderr: stderrMsg + `
1663
- `, policyName: policyNames[0], policyNames, reason: combined, decision: "allow" };
1664
- }
1665
- return { exitCode: 0, stdout: "", stderr: "", policyName: null, reason: null, decision: "allow" };
1666
- }
1667
- var POLICY_PARAMS_MAP;
1668
- var init_policy_evaluator = __esm(() => {
1669
- init_builtin_policies();
1670
- init_hook_logger();
1671
- POLICY_PARAMS_MAP = new Map(BUILTIN_POLICIES.filter((p) => p.params).map((p) => [p.name, p.params]));
1672
- });
1673
-
1674
- // src/hooks/custom-hooks-registry.ts
1675
- function getRegistry2() {
1676
- const g = globalThis;
1677
- if (!Array.isArray(g[REGISTRY_KEY2]))
1678
- g[REGISTRY_KEY2] = [];
1679
- return g[REGISTRY_KEY2];
1680
- }
1681
- function getCustomHooks() {
1682
- return getRegistry2();
1683
- }
1684
- function clearCustomHooks() {
1685
- const g = globalThis;
1686
- g[REGISTRY_KEY2] = [];
1687
- }
1688
- var REGISTRY_KEY2 = "__failproofai_custom_hooks__";
1689
- var init_custom_hooks_registry = () => {};
1690
-
1691
- // src/hooks/loader-utils.ts
1692
- import { readFile as readFile2, writeFile as writeFile2, unlink, access } from "fs/promises";
1693
- import { resolve as resolve3, dirname as dirname2, relative } from "path";
1694
- import { pathToFileURL } from "url";
1695
- async function fileExists(path) {
1696
- try {
1697
- await access(path);
1698
- return true;
1699
- } catch {
1700
- return false;
1701
- }
1702
- }
1703
- async function findDistIndex() {
1704
- const distPath = process.env.FAILPROOFAI_DIST_PATH;
1705
- if (distPath) {
1706
- const candidate = resolve3(distPath, "index.js");
1707
- if (await fileExists(candidate))
1708
- return candidate;
1709
- }
1710
- const candidates = [
1711
- resolve3(dirname2(process.execPath), "..", "assets", "dist", "index.js"),
1712
- resolve3(process.cwd(), "dist", "index.js"),
1713
- resolve3(process.cwd(), "node_modules", "failproofai", "dist", "index.js")
1714
- ];
1715
- for (const c of candidates) {
1716
- if (await fileExists(c))
1717
- return c;
1718
- }
1719
- return null;
1720
- }
1721
- async function resolveLocalImport(fromDir, specifier) {
1722
- const base = resolve3(fromDir, specifier);
1723
- const candidates = [base, `${base}.js`, `${base}.mjs`, `${base}.ts`, resolve3(base, "index.js")];
1724
- for (const c of candidates) {
1725
- if (await fileExists(c))
1726
- return c;
1727
- }
1728
- return null;
1729
- }
1730
- async function createEsmShim(distIndex, distUrl) {
1731
- const shimPath = distIndex + ".__failproofai_esm_shim__.mjs";
1732
- const shimCode = [
1733
- `import _cjs from '${distUrl}';`,
1734
- `export const customPolicies = _cjs.customPolicies;`,
1735
- `export const getCustomHooks = _cjs.getCustomHooks;`,
1736
- `export const clearCustomHooks = _cjs.clearCustomHooks;`,
1737
- `export const allow = _cjs.allow;`,
1738
- `export const deny = _cjs.deny;`,
1739
- `export const instruct = _cjs.instruct;`,
1740
- `export default _cjs;`
1741
- ].join(`
1742
- `);
1743
- await writeFile2(shimPath, shimCode, "utf-8");
1744
- return { shimPath, shimUrl: pathToFileURL(shimPath).href };
1745
- }
1746
- async function rewriteFileTree(entryPath, distUrl, distIndex) {
1747
- const queue = [entryPath];
1748
- const visited = new Set;
1749
- const tmpFiles = [];
1750
- let esmShimUrl = null;
1751
- if (distIndex && distUrl) {
1752
- const shim = await createEsmShim(distIndex, distUrl);
1753
- tmpFiles.push(shim.shimPath);
1754
- esmShimUrl = shim.shimUrl;
1755
- }
1756
- while (queue.length > 0) {
1757
- const filePath = queue.shift();
1758
- if (visited.has(filePath))
1759
- continue;
1760
- visited.add(filePath);
1761
- let code = await readFile2(filePath, "utf-8");
1762
- if (esmShimUrl) {
1763
- code = code.replace(/from\s+(['"])(?:claudeye|failproofai)\1/g, `from '${esmShimUrl}'`);
1764
- }
1765
- if (distIndex) {
1766
- code = code.replace(/require\s*\(\s*(['"])(?:claudeye|failproofai)\1\s*\)/g, `require('${distIndex.replace(/\\/g, "\\\\")}')`);
1767
- }
1768
- const dir = dirname2(filePath);
1769
- const rewrites = new Map;
1770
- for (const re of [LOCAL_IMPORT_RE, LOCAL_REQUIRE_RE]) {
1771
- const freshRe = new RegExp(re.source, re.flags);
1772
- let match;
1773
- while ((match = freshRe.exec(code)) !== null) {
1774
- const specifier = match[2];
1775
- if (rewrites.has(specifier))
1776
- continue;
1777
- const resolved2 = await resolveLocalImport(dir, specifier);
1778
- if (!resolved2)
1779
- continue;
1780
- if (!visited.has(resolved2) && !queue.includes(resolved2)) {
1781
- queue.push(resolved2);
1782
- }
1783
- let relPath = relative(dir, resolved2 + TMP_SUFFIX).split("\\").join("/");
1784
- if (!relPath.startsWith("."))
1785
- relPath = "./" + relPath;
1786
- rewrites.set(specifier, relPath);
1787
- }
1788
- }
1789
- const sortedSpecs = [...rewrites.keys()].sort((a, b) => b.length - a.length);
1790
- for (const specifier of sortedSpecs) {
1791
- const replacement = rewrites.get(specifier);
1792
- const escaped = specifier.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
1793
- code = code.replace(new RegExp(`'${escaped}'`, "g"), `'${replacement}'`);
1794
- code = code.replace(new RegExp(`"${escaped}"`, "g"), `"${replacement}"`);
1795
- }
1796
- const tmpPath = filePath + TMP_SUFFIX;
1797
- await writeFile2(tmpPath, code, "utf-8");
1798
- tmpFiles.push(tmpPath);
1799
- }
1800
- return tmpFiles;
1801
- }
1802
- async function cleanupTmpFiles(tmpFiles) {
1803
- for (const tmp of tmpFiles) {
1804
- try {
1805
- await unlink(tmp);
1806
- } catch {}
1807
- }
1808
- }
1809
- var TMP_SUFFIX = ".__failproofai_tmp__.mjs", LOCAL_IMPORT_RE, LOCAL_REQUIRE_RE;
1810
- var init_loader_utils = __esm(() => {
1811
- LOCAL_IMPORT_RE = /(?:import\s+(?:[\s\S]*?\s+from\s+)?|export\s+(?:[\s\S]*?\s+from\s+))(['"])(\.\.?\/[^'"]+)\1/g;
1812
- LOCAL_REQUIRE_RE = /require\s*\(\s*(['"])(\.\.?\/[^'"]+)\1\s*\)/g;
1813
- });
1814
-
1815
- // src/hooks/custom-hooks-loader.ts
1816
- import { resolve as resolve4, isAbsolute, basename } from "node:path";
1817
- import { existsSync as existsSync3, readdirSync } from "node:fs";
1818
- import { pathToFileURL as pathToFileURL2 } from "node:url";
1819
- import { homedir as homedir4 } from "node:os";
1820
- function discoverPolicyFiles(dir) {
1821
- if (!existsSync3(dir))
1822
- return [];
1823
- try {
1824
- const entries = readdirSync(dir, { withFileTypes: true });
1825
- 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));
1826
- } catch {
1827
- return [];
1828
- }
1829
- }
1830
- async function loadSingleFile(absPath, opts) {
1831
- const g = globalThis;
1832
- g[LOADING_KEY] = true;
1833
- let tmpFiles = [];
1834
- try {
1835
- const distIndex = await findDistIndex();
1836
- const distUrl = distIndex ? pathToFileURL2(distIndex).href : null;
1837
- tmpFiles = await rewriteFileTree(absPath, distUrl, distIndex);
1838
- const entryTmp = absPath + TMP_SUFFIX;
1839
- const fileUrl = pathToFileURL2(entryTmp).href;
1840
- await import(fileUrl);
1841
- } catch (err) {
1842
- const msg = err instanceof Error ? err.message : String(err);
1843
- if (opts?.strict)
1844
- throw new Error(`Failed to load custom hooks from ${absPath}: ${msg}`);
1845
- hookLogError(`failed to load custom hooks from ${absPath}: ${msg}`);
1846
- } finally {
1847
- g[LOADING_KEY] = false;
1848
- await cleanupTmpFiles(tmpFiles);
1849
- }
1850
- }
1851
- async function loadCustomHooks(customPoliciesPath, opts) {
1852
- if (!customPoliciesPath)
1853
- return [];
1854
- const absPath = isAbsolute(customPoliciesPath) ? customPoliciesPath : resolve4(opts?.sessionCwd ?? process.cwd(), customPoliciesPath);
1855
- if (!existsSync3(absPath)) {
1856
- if (opts?.strict)
1857
- throw new Error(`Custom hooks file not found: ${absPath}`);
1858
- hookLogWarn(`customPoliciesPath not found: ${absPath}`);
1859
- return [];
1860
- }
1861
- clearCustomHooks();
1862
- await loadSingleFile(absPath, opts);
1863
- return getCustomHooks();
1864
- }
1865
- async function loadAllCustomHooks(customPoliciesPath, opts) {
1866
- clearCustomHooks();
1867
- const conventionSources = [];
1868
- if (customPoliciesPath) {
1869
- const absPath = isAbsolute(customPoliciesPath) ? customPoliciesPath : resolve4(opts?.sessionCwd ?? process.cwd(), customPoliciesPath);
1870
- if (existsSync3(absPath)) {
1871
- await loadSingleFile(absPath);
1872
- } else {
1873
- hookLogWarn(`customPoliciesPath not found: ${absPath}`);
1874
- }
1875
- }
1876
- const hooksBeforeConvention = getCustomHooks().length;
1877
- const projectDir = resolve4(opts?.sessionCwd ?? process.cwd(), ".failproofai", "policies");
1878
- const projectFiles = discoverPolicyFiles(projectDir);
1879
- for (const file of projectFiles) {
1880
- const hooksBefore = getCustomHooks().length;
1881
- await loadSingleFile(file);
1882
- const newHooks = getCustomHooks().slice(hooksBefore);
1883
- if (newHooks.length > 0) {
1884
- conventionSources.push({
1885
- scope: "project",
1886
- file: basename(file),
1887
- hookNames: newHooks.map((h) => h.name)
1888
- });
1889
- }
1890
- }
1891
- const userDir = resolve4(homedir4(), ".failproofai", "policies");
1892
- const userFiles = discoverPolicyFiles(userDir);
1893
- for (const file of userFiles) {
1894
- const hooksBefore = getCustomHooks().length;
1895
- await loadSingleFile(file);
1896
- const newHooks = getCustomHooks().slice(hooksBefore);
1897
- if (newHooks.length > 0) {
1898
- conventionSources.push({
1899
- scope: "user",
1900
- file: basename(file),
1901
- hookNames: newHooks.map((h) => h.name)
1902
- });
1903
- }
1904
- }
1905
- const allHooks = getCustomHooks();
1906
- const conventionCount = allHooks.length - hooksBeforeConvention;
1907
- if (projectFiles.length > 0 || userFiles.length > 0) {
1908
- hookLogInfo(`convention policies: ${projectFiles.length} project file(s), ${userFiles.length} user file(s), ${conventionCount} hook(s)`);
1909
- }
1910
- const hookNameToScope = new Map;
1911
- for (const source of conventionSources) {
1912
- for (const name of source.hookNames) {
1913
- hookNameToScope.set(name, source.scope);
1914
- }
1915
- }
1916
- const conventionHookRefs = new Set;
1917
- for (const hook of allHooks.slice(hooksBeforeConvention)) {
1918
- conventionHookRefs.add(hook);
1919
- }
1920
- for (const hook of allHooks) {
1921
- if (conventionHookRefs.has(hook)) {
1922
- hook.__conventionScope = hookNameToScope.get(hook.name) ?? "project";
1923
- }
1924
- }
1925
- return { hooks: allHooks, conventionSources };
1926
- }
1927
- var LOADING_KEY = "__FAILPROOFAI_LOADING_HOOKS__", CONVENTION_FILE_RE;
1928
- var init_custom_hooks_loader = __esm(() => {
1929
- init_hook_logger();
1930
- init_custom_hooks_registry();
1931
- init_loader_utils();
1932
- CONVENTION_FILE_RE = /policies\.(js|mjs|ts)$/;
1933
- });
1934
-
1935
- // src/hooks/hook-activity-store.ts
1936
- import {
1937
- readFileSync as readFileSync2,
1938
- writeFileSync as writeFileSync2,
1939
- appendFileSync as appendFileSync2,
1940
- renameSync as renameSync2,
1941
- readdirSync as readdirSync2,
1942
- mkdirSync as mkdirSync3,
1943
- existsSync as existsSync4,
1944
- statSync as statSync2,
1945
- unlinkSync
1946
- } from "node:fs";
1947
- import { join as join3 } from "node:path";
1948
- import { homedir as homedir5 } from "node:os";
1949
- function ensureDir() {
1950
- if (!existsSync4(storeDir)) {
1951
- mkdirSync3(storeDir, { recursive: true });
1952
- }
1953
- }
1954
- function acquireLock() {
1955
- ensureDir();
1956
- const lockPath = join3(storeDir, LOCK_FILE);
1957
- const deadline = Date.now() + LOCK_STALE_MS;
1958
- while (Date.now() < deadline) {
1959
- try {
1960
- writeFileSync2(lockPath, String(process.pid), { flag: "wx" });
1961
- return;
1962
- } catch (e) {
1963
- if (e.code !== "EEXIST")
1964
- return;
1965
- try {
1966
- const s = statSync2(lockPath);
1967
- if (Date.now() - s.mtimeMs > LOCK_STALE_MS) {
1968
- writeFileSync2(lockPath, String(process.pid), "utf-8");
1969
- return;
1970
- }
1971
- } catch {}
1972
- }
1973
- }
1974
- }
1975
- function releaseLock() {
1976
- try {
1977
- unlinkSync(join3(storeDir, LOCK_FILE));
1978
- } catch {}
1979
- }
1980
- function persistHookActivity(entry) {
1981
- ensureDir();
1982
- acquireLock();
1983
- try {
1984
- const currentPath = join3(storeDir, CURRENT_FILE);
1985
- const countPath = join3(storeDir, COUNT_FILE);
1986
- const lineCount = readCount(countPath);
1987
- if (lineCount >= PAGE_SIZE) {
1988
- try {
1989
- rotate(currentPath, countPath);
1990
- } catch (e) {
1991
- if (e.code !== "ENOENT")
1992
- throw e;
1993
- }
1994
- }
1995
- appendFileSync2(currentPath, JSON.stringify(entry) + `
1996
- `, "utf-8");
1997
- writeCount(countPath, lineCount >= PAGE_SIZE ? 1 : lineCount + 1);
1998
- updateStats(entry);
1999
- } finally {
2000
- releaseLock();
2001
- }
2002
- }
2003
- function rotate(currentPath, countPath) {
2004
- const archiveName = `page-${Date.now()}-${rotateSeq++}.jsonl`;
2005
- const archivePath = join3(storeDir, archiveName);
2006
- renameSync2(currentPath, archivePath);
2007
- writeCount(countPath, 0);
2008
- }
2009
- function readCount(countPath) {
2010
- try {
2011
- const n = parseInt(readFileSync2(countPath, "utf-8"), 10);
2012
- return isNaN(n) ? 0 : n;
2013
- } catch {
2014
- return 0;
2015
- }
2016
- }
2017
- function writeCount(countPath, n) {
2018
- try {
2019
- writeFileSync2(countPath, String(n), "utf-8");
2020
- } catch {}
2021
- }
2022
- function readStoredStats() {
2023
- try {
2024
- return JSON.parse(readFileSync2(join3(storeDir, STATS_FILE), "utf-8"));
2025
- } catch {
2026
- return { totalEvents: 0, denyCount: 0, policyMap: {} };
2027
- }
2028
- }
2029
- function updateStats(entry) {
2030
- const s = readStoredStats();
2031
- s.totalEvents += 1;
2032
- if (entry.decision === "deny")
2033
- s.denyCount += 1;
2034
- if (entry.policyNames && entry.policyNames.length > 0) {
2035
- for (const name of entry.policyNames) {
2036
- s.policyMap[name] = (s.policyMap[name] ?? 0) + 1;
2037
- }
2038
- } else if (entry.policyName) {
2039
- s.policyMap[entry.policyName] = (s.policyMap[entry.policyName] ?? 0) + 1;
2040
- }
2041
- const tmpPath = join3(storeDir, `stats.json.${process.pid}.tmp`);
2042
- try {
2043
- writeFileSync2(tmpPath, JSON.stringify(s), "utf-8");
2044
- renameSync2(tmpPath, join3(storeDir, STATS_FILE));
2045
- } catch {
2046
- try {
2047
- unlinkSync(tmpPath);
2048
- } catch {}
2049
- }
2050
- }
2051
- 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;
2052
- var init_hook_activity_store = __esm(() => {
2053
- DEFAULT_STORE_DIR = join3(homedir5(), ".failproofai", "cache", "hook-activity");
2054
- storeDir = DEFAULT_STORE_DIR;
2055
- });
2056
-
2057
- // package.json
2058
- var version2 = "0.0.6-beta.2";
2059
- var init_package = () => {};
2060
-
2061
- // src/posthog-key.ts
2062
- var POSTHOG_API_KEY = "phc_Ac1Ww1GqKc0z1SyrRWbmatEeQdlOQIsDEEdP8l8JRgX";
2063
-
2064
- // src/hooks/hook-telemetry.ts
2065
- async function trackHookEvent(distinctId, event, properties) {
2066
- if (process.env.FAILPROOFAI_TELEMETRY_DISABLED === "1")
2067
- return;
2068
- const body = JSON.stringify({
2069
- api_key: process.env.FAILPROOFAI_POSTHOG_KEY ?? API_KEY,
2070
- event,
2071
- distinct_id: distinctId,
2072
- properties: { ...properties, $lib: "failproofai-hooks", failproofai_version: version2 }
2073
- });
2074
- try {
2075
- await fetch(process.env.FAILPROOFAI_POSTHOG_HOST ? `${process.env.FAILPROOFAI_POSTHOG_HOST}/capture/` : CAPTURE_URL, {
2076
- method: "POST",
2077
- headers: { "Content-Type": "application/json" },
2078
- body,
2079
- signal: AbortSignal.timeout(5000)
2080
- });
2081
- } catch {}
2082
- }
2083
- var API_KEY, CAPTURE_URL = "https://us.i.posthog.com/capture/";
2084
- var init_hook_telemetry = __esm(() => {
2085
- init_package();
2086
- API_KEY = POSTHOG_API_KEY;
2087
- });
2088
-
2089
- // lib/telemetry-id.ts
2090
- import fs from "node:fs";
2091
- import path from "node:path";
2092
- import os from "node:os";
2093
- import crypto from "node:crypto";
2094
- import { execSync as execSync2 } from "node:child_process";
2095
- function hashToId(raw) {
2096
- return crypto.createHmac("sha256", NAMESPACE).update(raw).digest("hex");
2097
- }
2098
- function getPlatformMachineId() {
2099
- try {
2100
- const platform = os.platform();
2101
- if (platform === "linux") {
2102
- for (const p of ["/etc/machine-id", "/var/lib/dbus/machine-id"]) {
2103
- try {
2104
- const id = fs.readFileSync(p, "utf-8").trim();
2105
- if (id)
2106
- return id;
2107
- } catch {}
2108
- }
2109
- } else if (platform === "darwin") {
2110
- const out = execSync2("ioreg -rd1 -c IOPlatformExpertDevice", {
2111
- encoding: "utf-8",
2112
- timeout: 3000
2113
- });
2114
- const m = out.match(/"IOPlatformUUID"\s*=\s*"([^"]+)"/);
2115
- if (m?.[1])
2116
- return m[1];
2117
- } else if (platform === "win32") {
2118
- const out = execSync2('reg query "HKLM\\SOFTWARE\\Microsoft\\Cryptography" /v MachineGuid', { encoding: "utf-8", timeout: 3000 });
2119
- const m = out.match(/MachineGuid\s+REG_SZ\s+(\S+)/);
2120
- if (m?.[1])
2121
- return m[1];
2122
- }
2123
- } catch {}
2124
- return;
2125
- }
2126
- function getSystemPropertiesId() {
2127
- return [
2128
- os.hostname(),
2129
- os.homedir(),
2130
- os.platform(),
2131
- os.arch(),
2132
- os.cpus()[0]?.model ?? ""
2133
- ].join(":");
2134
- }
2135
- function getFileBasedId() {
2136
- try {
2137
- const existing = fs.readFileSync(ID_FILE, "utf-8").trim();
2138
- if (existing)
2139
- return existing;
2140
- } catch {}
2141
- const id = crypto.randomUUID();
2142
- try {
2143
- fs.mkdirSync(ID_DIR, { recursive: true });
2144
- fs.writeFileSync(ID_FILE, id, "utf-8");
2145
- } catch {}
2146
- return id;
2147
- }
2148
- function getInstanceId() {
2149
- if (cachedId)
2150
- return cachedId;
2151
- const machineId = getPlatformMachineId();
2152
- if (machineId) {
2153
- cachedId = hashToId(machineId);
2154
- return cachedId;
2155
- }
2156
- const sysProps = getSystemPropertiesId();
2157
- if (sysProps) {
2158
- cachedId = hashToId(sysProps);
2159
- return cachedId;
2160
- }
2161
- cachedId = getFileBasedId();
2162
- return cachedId;
2163
- }
2164
- var NAMESPACE = "failproofai-telemetry-v1", ID_DIR, ID_FILE, cachedId;
2165
- var init_telemetry_id = __esm(() => {
2166
- ID_DIR = path.join(os.homedir(), ".failproofai");
2167
- ID_FILE = path.join(ID_DIR, "instance-id");
2168
- });
2169
-
2170
- // src/hooks/handler.ts
2171
- var exports_handler = {};
2172
- __export(exports_handler, {
2173
- handleHookEvent: () => handleHookEvent
2174
- });
2175
- async function handleHookEvent(eventType) {
2176
- const startTime = performance.now();
2177
- const MAX_STDIN_BYTES = 1048576;
2178
- let payload = "";
2179
- try {
2180
- payload = await new Promise((resolve5, reject) => {
2181
- const chunks = [];
2182
- let totalBytes = 0;
2183
- process.stdin.setEncoding("utf8");
2184
- process.stdin.on("data", (chunk) => {
2185
- totalBytes += Buffer.byteLength(chunk);
2186
- if (totalBytes > MAX_STDIN_BYTES) {
2187
- hookLogWarn(`stdin payload exceeds 1 MB for ${eventType}, discarding`);
2188
- process.stdin.destroy();
2189
- resolve5("");
2190
- return;
2191
- }
2192
- chunks.push(chunk);
2193
- });
2194
- process.stdin.on("end", () => resolve5(chunks.join("")));
2195
- process.stdin.on("error", reject);
2196
- if (process.stdin.readableEnded)
2197
- resolve5("");
2198
- });
2199
- } catch {
2200
- hookLogWarn(`stdin read failed for ${eventType}`);
2201
- }
2202
- let parsed = {};
2203
- if (payload) {
2204
- try {
2205
- parsed = JSON.parse(payload);
2206
- } catch {
2207
- hookLogWarn(`payload parse failed for ${eventType} (${payload.length} bytes)`);
2208
- }
2209
- }
2210
- const session = {
2211
- sessionId: parsed.session_id,
2212
- transcriptPath: parsed.transcript_path,
2213
- cwd: parsed.cwd,
2214
- permissionMode: parsed.permission_mode,
2215
- hookEventName: parsed.hook_event_name
2216
- };
2217
- const config = readMergedHooksConfig(session.cwd);
2218
- clearPolicies();
2219
- registerBuiltinPolicies(config.enabledPolicies);
2220
- const loadResult = await loadAllCustomHooks(config.customPoliciesPath, { sessionCwd: session.cwd });
2221
- const customHooksList = loadResult.hooks;
2222
- const conventionHookNames = new Set(loadResult.conventionSources.flatMap((s) => s.hookNames));
2223
- for (const hook of customHooksList) {
2224
- const hookName = hook.name;
2225
- const conventionScope = hook.__conventionScope;
2226
- const isConvention = !!conventionScope;
2227
- const prefix = isConvention ? `.failproofai-${conventionScope}` : "custom";
2228
- const fn = async (ctx) => {
2229
- try {
2230
- const result2 = await Promise.race([
2231
- hook.fn(ctx),
2232
- new Promise((_, reject) => setTimeout(() => reject(new Error("timeout")), 1e4))
2233
- ]);
2234
- return result2;
2235
- } catch (err) {
2236
- const msg = err instanceof Error ? err.message : String(err);
2237
- const isTimeout = msg === "timeout";
2238
- hookLogWarn(`${prefix} hook "${hookName}" failed: ${msg}`);
2239
- trackHookEvent(getInstanceId(), "custom_hook_error", {
2240
- hook_name: hookName,
2241
- error_type: isTimeout ? "timeout" : "exception",
2242
- event_type: eventType,
2243
- is_convention_policy: isConvention,
2244
- convention_scope: conventionScope ?? null
2245
- });
2246
- return { decision: "allow" };
2247
- }
2248
- };
2249
- registerPolicy(`${prefix}/${hookName}`, hook.description ?? "", fn, hook.match ?? {}, -1);
2250
- }
2251
- if (customHooksList.length > 0) {
2252
- trackHookEvent(getInstanceId(), "custom_hooks_loaded", {
2253
- custom_hooks_count: customHooksList.length,
2254
- custom_hook_names: customHooksList.map((h) => h.name),
2255
- event_types_covered: [...new Set(customHooksList.flatMap((h) => h.match?.events ?? []))]
2256
- });
2257
- }
2258
- if (loadResult.conventionSources.length > 0) {
2259
- trackHookEvent(getInstanceId(), "convention_policies_loaded", {
2260
- event_type: eventType,
2261
- project_file_count: loadResult.conventionSources.filter((s) => s.scope === "project").length,
2262
- user_file_count: loadResult.conventionSources.filter((s) => s.scope === "user").length,
2263
- convention_hook_count: conventionHookNames.size,
2264
- convention_hook_names: [...conventionHookNames]
2265
- });
2266
- }
2267
- hookLogInfo(`event=${eventType} policies=${config.enabledPolicies.length} custom=${customHooksList.length} convention=${conventionHookNames.size}`);
2268
- const result = await evaluatePolicies(eventType, parsed, session, config);
2269
- const durationMs = Math.round(performance.now() - startTime);
2270
- hookLogInfo(`result=${result.decision} policy=${result.policyName ?? "none"} duration=${durationMs}ms`);
2271
- if (result.stdout) {
2272
- process.stdout.write(result.stdout);
2273
- }
2274
- if (result.stderr) {
2275
- process.stderr.write(result.stderr);
2276
- }
2277
- try {
2278
- persistHookActivity({
2279
- timestamp: Date.now(),
2280
- eventType,
2281
- toolName: parsed.tool_name ?? null,
2282
- policyName: result.policyName,
2283
- policyNames: result.policyNames,
2284
- decision: result.decision,
2285
- reason: result.reason,
2286
- durationMs,
2287
- sessionId: session.sessionId,
2288
- transcriptPath: session.transcriptPath,
2289
- cwd: session.cwd,
2290
- permissionMode: session.permissionMode,
2291
- hookEventName: session.hookEventName
2292
- });
2293
- } catch {
2294
- hookLogWarn("activity persistence failed");
2295
- }
2296
- if (result.decision === "deny" || result.decision === "instruct") {
2297
- try {
2298
- const isCustomHook = result.policyName?.startsWith("custom/") ?? false;
2299
- const isConventionPolicy = result.policyName?.startsWith(".failproofai-") ?? false;
2300
- const conventionScope = isConventionPolicy ? result.policyName.match(/^\.failproofai-(project|user)\//)?.[1] ?? null : null;
2301
- const hasCustomParams = !isCustomHook && !isConventionPolicy && !!(result.policyName && config.policyParams?.[result.policyName]);
2302
- const paramKeysOverridden = hasCustomParams ? Object.keys(config.policyParams[result.policyName]) : [];
2303
- const distinctId = getInstanceId();
2304
- await trackHookEvent(distinctId, "hook_policy_triggered", {
2305
- event_type: eventType,
2306
- tool_name: parsed.tool_name ?? null,
2307
- policy_name: result.policyName,
2308
- decision: result.decision,
2309
- is_custom_hook: isCustomHook,
2310
- is_convention_policy: isConventionPolicy,
2311
- convention_scope: conventionScope,
2312
- has_custom_params: hasCustomParams,
2313
- param_keys_overridden: paramKeysOverridden
2314
- });
2315
- } catch {}
2316
- }
2317
- return result.exitCode;
2318
- }
2319
- var init_handler = __esm(() => {
2320
- init_hooks_config();
2321
- init_builtin_policies();
2322
- init_policy_evaluator();
2323
- init_custom_hooks_loader();
2324
- init_hook_activity_store();
2325
- init_hook_telemetry();
2326
- init_telemetry_id();
2327
- init_hook_logger();
2328
- });
2329
-
2330
- // src/hooks/types.ts
2331
- var HOOK_SCOPES, HOOK_EVENT_TYPES, FAILPROOFAI_HOOK_MARKER = "__failproofai_hook__";
2332
- var init_types = __esm(() => {
2333
- HOOK_SCOPES = ["user", "project", "local"];
2334
- HOOK_EVENT_TYPES = [
2335
- "SessionStart",
2336
- "SessionEnd",
2337
- "UserPromptSubmit",
2338
- "PreToolUse",
2339
- "PermissionRequest",
2340
- "PermissionDenied",
2341
- "PostToolUse",
2342
- "PostToolUseFailure",
2343
- "Notification",
2344
- "SubagentStart",
2345
- "SubagentStop",
2346
- "TaskCreated",
2347
- "TaskCompleted",
2348
- "Stop",
2349
- "StopFailure",
2350
- "TeammateIdle",
2351
- "InstructionsLoaded",
2352
- "ConfigChange",
2353
- "CwdChanged",
2354
- "FileChanged",
2355
- "WorktreeCreate",
2356
- "WorktreeRemove",
2357
- "PreCompact",
2358
- "PostCompact",
2359
- "Elicitation",
2360
- "ElicitationResult"
2361
- ];
2362
- });
2363
-
2364
- // src/hooks/install-prompt.ts
2365
- import * as readline from "node:readline";
2366
- async function promptPolicySelection(preSelected, options = {}) {
2367
- const { includeBeta = false } = options;
2368
- if (!process.stdin.isTTY) {
2369
- const available = BUILTIN_POLICIES.filter((p) => includeBeta || !p.beta);
2370
- if (preSelected)
2371
- return preSelected.filter((name) => available.some((p) => p.name === name));
2372
- return available.filter((p) => p.defaultEnabled).map((p) => p.name);
2373
- }
2374
- const preSelectedSet = preSelected ? new Set(preSelected) : null;
2375
- const items = BUILTIN_POLICIES.filter((p) => includeBeta || !p.beta).map((p) => ({
2376
- name: p.name,
2377
- description: p.description,
2378
- category: p.category,
2379
- selected: preSelectedSet ? preSelectedSet.has(p.name) : p.defaultEnabled,
2380
- beta: !!p.beta
2381
- }));
2382
- const total = items.length;
2383
- const WINDOW_SIZE = 8;
2384
- let cursor = 0;
2385
- let search = "";
2386
- let lastLineCount = 0;
2387
- let cursorHidden = false;
2388
- function hideCursor() {
2389
- if (!cursorHidden) {
2390
- process.stdout.write("\x1B[?25l");
2391
- cursorHidden = true;
2392
- }
2393
- }
2394
- function showCursor() {
2395
- if (cursorHidden) {
2396
- process.stdout.write("\x1B[?25h");
2397
- cursorHidden = false;
2398
- }
2399
- }
2400
- function truncateLine(line, width) {
2401
- let visual = 0;
2402
- let result = "";
2403
- let i = 0;
2404
- while (i < line.length) {
2405
- if (line[i] === "\x1B" && line[i + 1] === "[") {
2406
- let j = i + 2;
2407
- while (j < line.length && !/[A-Za-z]/.test(line[j]))
2408
- j++;
2409
- j++;
2410
- result += line.slice(i, j);
2411
- i = j;
2412
- } else {
2413
- if (visual >= width)
2414
- break;
2415
- result += line[i];
2416
- visual++;
2417
- i++;
2418
- }
2419
- }
2420
- return result;
2421
- }
2422
- function getFiltered() {
2423
- if (!search)
2424
- return items;
2425
- const q = search.toLowerCase();
2426
- return items.filter((i) => i.name.toLowerCase().includes(q) || i.description.toLowerCase().includes(q));
2427
- }
2428
- function buildDisplayRows(filtered) {
2429
- const categoryOrder = [];
2430
- const categoryEnabledCount = new Map;
2431
- const categoryTotalCount = new Map;
2432
- for (const p of items) {
2433
- if (!categoryEnabledCount.has(p.category)) {
2434
- categoryOrder.push(p.category);
2435
- categoryEnabledCount.set(p.category, 0);
2436
- categoryTotalCount.set(p.category, 0);
2437
- }
2438
- categoryTotalCount.set(p.category, categoryTotalCount.get(p.category) + 1);
2439
- if (p.selected)
2440
- categoryEnabledCount.set(p.category, categoryEnabledCount.get(p.category) + 1);
2441
- }
2442
- const filteredByCategory = new Map;
2443
- for (const item of filtered) {
2444
- const bucket = filteredByCategory.get(item.category) ?? [];
2445
- bucket.push(item);
2446
- filteredByCategory.set(item.category, bucket);
2447
- }
2448
- const rows = [];
2449
- let idx = 0;
2450
- for (const cat of categoryOrder) {
2451
- const catFiltered = filteredByCategory.get(cat);
2452
- if (!catFiltered || catFiltered.length === 0)
2453
- continue;
2454
- rows.push({ kind: "header", category: cat, enabledCount: categoryEnabledCount.get(cat), totalCount: categoryTotalCount.get(cat) });
2455
- for (const item of catFiltered) {
2456
- rows.push({ kind: "item", item, filteredIndex: idx++ });
2457
- }
2458
- }
2459
- return rows;
2460
- }
2461
- function render() {
2462
- const cols = process.stdout.columns || 120;
2463
- hideCursor();
2464
- const filtered = getFiltered();
2465
- const shown = filtered.length;
2466
- if (shown > 0 && cursor >= shown)
2467
- cursor = shown - 1;
2468
- const lines = [];
2469
- lines.push(" Failproof AI — Policy Manager");
2470
- lines.push("");
2471
- const innerWidth = Math.max(20, cols - 6);
2472
- const topBorder = " ┌" + "─".repeat(innerWidth + 2) + "┐";
2473
- const botBorder = " └" + "─".repeat(innerWidth + 2) + "┘";
2474
- const cursorChar = "\x1B[7m \x1B[0m";
2475
- const countPart = search ? ` \x1B[2m(${shown}/${total})\x1B[0m` : ` \x1B[2m(${total} policies)\x1B[0m`;
2476
- const searchContent = `\x1B[1mSearch:\x1B[0m ${search}${cursorChar}${countPart}`;
2477
- lines.push(topBorder);
2478
- lines.push(` │ ${searchContent}`);
2479
- lines.push(botBorder);
2480
- lines.push("");
2481
- if (shown === 0) {
2482
- lines.push(" \x1B[2mNo policies match “" + search + "”\x1B[0m");
2483
- for (let i = 0;i < WINDOW_SIZE + 1; i++)
2484
- lines.push("");
2485
- } else {
2486
- const displayRows = buildDisplayRows(filtered);
2487
- let cursorDisplayRow = 0;
2488
- for (let i = 0;i < displayRows.length; i++) {
2489
- const row = displayRows[i];
2490
- if (row.kind === "item" && row.filteredIndex === cursor) {
2491
- cursorDisplayRow = i;
2492
- break;
2493
- }
2494
- }
2495
- let windowStart = cursorDisplayRow - Math.floor(WINDOW_SIZE / 2);
2496
- windowStart = Math.max(0, windowStart);
2497
- windowStart = Math.min(windowStart, Math.max(0, displayRows.length - WINDOW_SIZE));
2498
- const windowEnd = Math.min(displayRows.length, windowStart + WINDOW_SIZE);
2499
- const aboveItems = displayRows.slice(0, windowStart).filter((r) => r.kind === "item").length;
2500
- if (aboveItems > 0) {
2501
- lines.push(` \x1B[2m ↑ ${aboveItems} more above\x1B[0m`);
2502
- } else {
2503
- lines.push("");
2504
- }
2505
- for (let i = windowStart;i < windowEnd; i++) {
2506
- const row = displayRows[i];
2507
- if (row.kind === "header") {
2508
- const label = ` ${row.category.toUpperCase()} (${row.enabledCount}/${row.totalCount}) `;
2509
- const prefix = "── ";
2510
- const prefixLen = 3 + label.length;
2511
- const dashLen = Math.max(2, cols - 2 - prefixLen);
2512
- lines.push(` \x1B[2m${prefix}${label}${"─".repeat(dashLen)}\x1B[0m`);
2513
- } else {
2514
- const item = row.item;
2515
- const isActive = row.filteredIndex === cursor;
2516
- const pointer = isActive ? "\x1B[36m❯\x1B[0m" : " ";
2517
- const check = item.selected ? "\x1B[32m[✓]\x1B[0m" : "[ ]";
2518
- const namePart = isActive ? `\x1B[1;36m${item.name}\x1B[0m` : item.name;
2519
- const betaPart = item.beta ? " \x1B[35m[beta]\x1B[0m" : "";
2520
- const pad = " ".repeat(Math.max(1, 28 - item.name.length));
2521
- const desc = `\x1B[2m${item.description}\x1B[0m`;
2522
- lines.push(` ${pointer} ${check} ${namePart}${betaPart}${pad}${desc}`);
2523
- }
2524
- }
2525
- for (let i = windowEnd - windowStart;i < WINDOW_SIZE; i++) {
2526
- lines.push("");
2527
- }
2528
- const belowItems = displayRows.slice(windowEnd).filter((r) => r.kind === "item").length;
2529
- if (belowItems > 0) {
2530
- lines.push(` \x1B[2m ↓ ${belowItems} more below\x1B[0m`);
2531
- } else {
2532
- lines.push("");
2533
- }
2534
- }
2535
- lines.push("");
2536
- lines.push(" \x1B[2m" + "─".repeat(cols - 2) + "\x1B[0m");
2537
- lines.push(" [↑↓] Move [Space] Toggle [Ctrl+A] All [Ctrl+S] Save [Esc] Clear [^C] Quit");
2538
- lines.push("");
2539
- lines.push(" \x1B[2mTip: `policies` for a flat list · `policies --install <name…>` to skip prompt\x1B[0m");
2540
- if (!includeBeta) {
2541
- lines.push(" \x1B[2mTip: `policies --install --beta` to include beta policies\x1B[0m");
2542
- }
2543
- if (lastLineCount > 0) {
2544
- process.stdout.write(`\x1B[${lastLineCount}A\x1B[J`);
2545
- }
2546
- const output = lines.map((l) => l === "" ? l : truncateLine(l, cols)).join(`
2547
- `) + `
2548
- `;
2549
- process.stdout.write(output);
2550
- lastLineCount = lines.length;
2551
- }
2552
- return new Promise((resolve5) => {
2553
- render();
2554
- process.stdin.setRawMode(true);
2555
- process.stdin.resume();
2556
- readline.emitKeypressEvents(process.stdin);
2557
- function keypressHandler(_str, key) {
2558
- if (!key)
2559
- return;
2560
- if (key.ctrl && key.name === "c") {
2561
- cleanup();
2562
- process.exit(0);
2563
- }
2564
- const filtered = getFiltered();
2565
- if (key.name === "up") {
2566
- if (filtered.length > 0) {
2567
- cursor = cursor > 0 ? cursor - 1 : filtered.length - 1;
2568
- }
2569
- render();
2570
- } else if (key.name === "down") {
2571
- if (filtered.length > 0) {
2572
- cursor = cursor < filtered.length - 1 ? cursor + 1 : 0;
2573
- }
2574
- render();
2575
- } else if (key.name === "return" || key.name === "space") {
2576
- const item = filtered[cursor];
2577
- if (item)
2578
- item.selected = !item.selected;
2579
- render();
2580
- } else if (key.name === "escape") {
2581
- search = "";
2582
- cursor = 0;
2583
- render();
2584
- } else if (key.ctrl && key.name === "a") {
2585
- const allSelected = filtered.length > 0 && filtered.every((i) => i.selected);
2586
- for (const item of filtered)
2587
- item.selected = !allSelected;
2588
- render();
2589
- } else if (key.ctrl && key.name === "s") {
2590
- cleanup();
2591
- const selected = items.filter((i) => i.selected).map((i) => i.name);
2592
- process.stdout.write(`
2593
- `);
2594
- resolve5(selected);
2595
- } else if (key.name === "backspace" || key.name === "delete") {
2596
- if (search.length > 0) {
2597
- search = search.slice(0, -1);
2598
- cursor = 0;
2599
- render();
2600
- }
2601
- } else if (_str && _str.length === 1 && !key.ctrl && !key.meta) {
2602
- search += _str;
2603
- cursor = 0;
2604
- render();
2605
- }
2606
- }
2607
- function cleanup() {
2608
- showCursor();
2609
- process.stdin.removeListener("keypress", keypressHandler);
2610
- process.stdin.setRawMode(false);
2611
- process.stdin.pause();
2612
- }
2613
- process.stdin.on("keypress", keypressHandler);
2614
- });
2615
- }
2616
- var init_install_prompt = __esm(() => {
2617
- init_builtin_policies();
2618
- });
2619
-
2620
- // src/cli-error.ts
2621
- var CliError;
2622
- var init_cli_error = __esm(() => {
2623
- CliError = class CliError extends Error {
2624
- exitCode;
2625
- constructor(message, exitCode = 1) {
2626
- super(message);
2627
- this.name = "CliError";
2628
- this.exitCode = exitCode;
2629
- }
2630
- };
2631
- });
2632
-
2633
- // src/hooks/manager.ts
2634
- var exports_manager = {};
2635
- __export(exports_manager, {
2636
- removeHooks: () => removeHooks,
2637
- listHooks: () => listHooks,
2638
- installHooks: () => installHooks,
2639
- hooksInstalledInSettings: () => hooksInstalledInSettings,
2640
- getSettingsPath: () => getSettingsPath
2641
- });
2642
- import { execSync as execSync3 } from "node:child_process";
2643
- import { readFileSync as readFileSync3, writeFileSync as writeFileSync3, existsSync as existsSync5, mkdirSync as mkdirSync4 } from "node:fs";
2644
- import { resolve as resolve5, dirname as dirname3, basename as basename2 } from "node:path";
2645
- import { homedir as homedir6, platform, arch, release, hostname } from "node:os";
2646
- function getSettingsPath(scope, cwd) {
2647
- const base = cwd ? resolve5(cwd) : process.cwd();
2648
- switch (scope) {
2649
- case "user":
2650
- return resolve5(homedir6(), ".claude", "settings.json");
2651
- case "project":
2652
- return resolve5(base, ".claude", "settings.json");
2653
- case "local":
2654
- return resolve5(base, ".claude", "settings.local.json");
2655
- }
2656
- }
2657
- function scopeLabel(scope) {
2658
- switch (scope) {
2659
- case "user":
2660
- return `~/.claude/settings.json`;
2661
- case "project":
2662
- return `{cwd}/.claude/settings.json`;
2663
- case "local":
2664
- return `{cwd}/.claude/settings.local.json`;
2665
- }
2666
- }
2667
- function readSettings(settingsPath) {
2668
- if (!existsSync5(settingsPath)) {
2669
- return {};
2670
- }
2671
- const raw = readFileSync3(settingsPath, "utf8");
2672
- return JSON.parse(raw);
2673
- }
2674
- function writeSettings(settingsPath, settings) {
2675
- mkdirSync4(dirname3(settingsPath), { recursive: true });
2676
- writeFileSync3(settingsPath, JSON.stringify(settings, null, 2) + `
2677
- `, "utf8");
2678
- }
2679
- function resolveFailproofaiBinary() {
2680
- try {
2681
- const cmd = process.platform === "win32" ? "where failproofai" : "which failproofai";
2682
- const result = execSync3(cmd, { encoding: "utf8" }).trim();
2683
- return result.split(`
2684
- `)[0].trim();
2685
- } catch {
2686
- throw new CliError(`failproofai binary not found in PATH.
2687
- ` + "Install it globally first: npm install -g failproofai");
2688
- }
2689
- }
2690
- function isFailproofaiHook(hook) {
2691
- if (hook[FAILPROOFAI_HOOK_MARKER] === true)
2692
- return true;
2693
- const cmd = typeof hook.command === "string" ? hook.command : "";
2694
- return cmd.includes("failproofai") && cmd.includes("--hook");
2695
- }
2696
- function validatePolicyNames(names) {
2697
- const invalid = names.filter((n) => !VALID_POLICY_NAMES.has(n));
2698
- if (invalid.length > 0) {
2699
- const validList = [...VALID_POLICY_NAMES].join(", ");
2700
- throw new CliError(`Unknown policy name(s): ${invalid.join(", ")}
2701
- ` + `Valid policies: ${validList}`);
2702
- }
2703
- }
2704
- function deduplicateScopes(scopes, cwd) {
2705
- const seen = new Set;
2706
- return scopes.filter((s) => {
2707
- const p = getSettingsPath(s, cwd);
2708
- if (seen.has(p))
2709
- return false;
2710
- seen.add(p);
2711
- return true;
2712
- });
2713
- }
2714
- function hooksInstalledInSettings(scope, cwd) {
2715
- const settingsPath = getSettingsPath(scope, cwd);
2716
- if (!existsSync5(settingsPath))
2717
- return false;
2718
- try {
2719
- const settings = readSettings(settingsPath);
2720
- if (!settings.hooks)
2721
- return false;
2722
- for (const matchers of Object.values(settings.hooks)) {
2723
- if (!Array.isArray(matchers))
2724
- continue;
2725
- for (const matcher of matchers) {
2726
- if (!matcher.hooks)
2727
- continue;
2728
- if (matcher.hooks.some((h) => isFailproofaiHook(h))) {
2729
- return true;
2730
- }
2731
- }
2732
- }
2733
- } catch {}
2734
- return false;
2735
- }
2736
- function removeHooksFromSettingsFile(settingsPath) {
2737
- const settings = readSettings(settingsPath);
2738
- if (!settings.hooks)
2739
- return 0;
2740
- let removed = 0;
2741
- for (const eventType of Object.keys(settings.hooks)) {
2742
- const matchers = settings.hooks[eventType];
2743
- if (!Array.isArray(matchers))
2744
- continue;
2745
- for (let i = matchers.length - 1;i >= 0; i--) {
2746
- const matcher = matchers[i];
2747
- if (!matcher.hooks)
2748
- continue;
2749
- const before = matcher.hooks.length;
2750
- matcher.hooks = matcher.hooks.filter((h) => !isFailproofaiHook(h));
2751
- removed += before - matcher.hooks.length;
2752
- if (matcher.hooks.length === 0) {
2753
- matchers.splice(i, 1);
2754
- }
2755
- }
2756
- if (matchers.length === 0) {
2757
- delete settings.hooks[eventType];
2758
- }
2759
- }
2760
- if (Object.keys(settings.hooks).length === 0) {
2761
- delete settings.hooks;
2762
- }
2763
- writeSettings(settingsPath, settings);
2764
- return removed;
2765
- }
2766
- async function installHooks(policyNames, scope = "user", cwd, includeBeta = false, source, customPoliciesPath, removeCustomHooks = false) {
2767
- if (policyNames !== undefined && policyNames.length > 0) {
2768
- const nonAllNames = policyNames.filter((n) => n !== "all");
2769
- if (nonAllNames.length > 0)
2770
- validatePolicyNames(nonAllNames);
2771
- if (policyNames.includes("all") && nonAllNames.length > 0) {
2772
- throw new CliError(`"all" cannot be combined with specific policy names.
2773
- ` + `Use either: --install all or --install block-sudo sanitize-jwt ...`);
2774
- }
2775
- }
2776
- const binaryPath = resolveFailproofaiBinary();
2777
- const previousConfig = readScopedHooksConfig(scope, cwd);
2778
- const previousEnabled = new Set(previousConfig.enabledPolicies);
2779
- let selectedPolicies;
2780
- if (policyNames !== undefined) {
2781
- let incoming;
2782
- if (policyNames.length === 1 && policyNames[0] === "all") {
2783
- incoming = BUILTIN_POLICIES.filter((p) => includeBeta || !p.beta).map((p) => p.name);
2784
- } else {
2785
- incoming = policyNames;
2786
- }
2787
- selectedPolicies = [...new Set([...previousConfig.enabledPolicies, ...incoming])];
2788
- } else {
2789
- const preSelected = previousConfig.enabledPolicies.length > 0 ? previousConfig.enabledPolicies : undefined;
2790
- selectedPolicies = await promptPolicySelection(preSelected, { includeBeta });
2791
- }
2792
- const configToWrite = { ...previousConfig, enabledPolicies: selectedPolicies };
2793
- if (removeCustomHooks) {
2794
- delete configToWrite.customPoliciesPath;
2795
- } else if (customPoliciesPath) {
2796
- configToWrite.customPoliciesPath = resolve5(customPoliciesPath);
2797
- let validatedHooks = [];
2798
- try {
2799
- validatedHooks = await loadCustomHooks(configToWrite.customPoliciesPath, { strict: true });
2800
- } catch (err) {
2801
- console.error(`Error: ${err instanceof Error ? err.message : String(err)}`);
2802
- process.exit(1);
2803
- }
2804
- if (validatedHooks.length === 0) {
2805
- console.error(`Error: no hooks registered in ${customPoliciesPath}. ` + `Make sure your file calls customPolicies.add(...) at least once.`);
2806
- process.exit(1);
2807
- }
2808
- console.log(`
2809
- Validated ${validatedHooks.length} custom hook(s): ${validatedHooks.map((h) => h.name).join(", ")}`);
2810
- }
2811
- writeScopedHooksConfig(configToWrite, scope, cwd);
2812
- console.log(`
2813
- Enabled ${selectedPolicies.length} policy(ies): ${selectedPolicies.join(", ")}`);
2814
- if (removeCustomHooks) {
2815
- console.log("Custom hooks path cleared.");
2816
- } else if (configToWrite.customPoliciesPath) {
2817
- console.log(`Custom hooks path: ${configToWrite.customPoliciesPath}`);
2818
- }
2819
- const settingsPath = getSettingsPath(scope, cwd);
2820
- const settings = readSettings(settingsPath);
2821
- if (!settings.hooks) {
2822
- settings.hooks = {};
2823
- }
2824
- for (const eventType of HOOK_EVENT_TYPES) {
2825
- const command = scope === "project" ? `npx -y failproofai --hook ${eventType}` : `"${binaryPath}" --hook ${eventType}`;
2826
- const hookEntry = {
2827
- type: "command",
2828
- command,
2829
- timeout: 60000,
2830
- [FAILPROOFAI_HOOK_MARKER]: true
2831
- };
2832
- if (!settings.hooks[eventType]) {
2833
- settings.hooks[eventType] = [];
2834
- }
2835
- const matchers = settings.hooks[eventType];
2836
- let found = false;
2837
- for (const matcher of matchers) {
2838
- if (!matcher.hooks)
2839
- continue;
2840
- const failproofaiIdx = matcher.hooks.findIndex((h) => isFailproofaiHook(h));
2841
- if (failproofaiIdx >= 0) {
2842
- matcher.hooks[failproofaiIdx] = hookEntry;
2843
- found = true;
2844
- break;
2845
- }
2846
- }
2847
- if (!found) {
2848
- matchers.push({ hooks: [hookEntry] });
2849
- }
2850
- }
2851
- writeSettings(settingsPath, settings);
2852
- try {
2853
- const newSet = new Set(selectedPolicies);
2854
- const policiesAdded = selectedPolicies.filter((p) => !previousEnabled.has(p));
2855
- const policiesRemoved = [...previousEnabled].filter((p) => !newSet.has(p));
2856
- const distinctId = getInstanceId();
2857
- await trackHookEvent(distinctId, "hooks_installed", {
2858
- scope,
2859
- policies: selectedPolicies,
2860
- policy_count: selectedPolicies.length,
2861
- policies_added: policiesAdded,
2862
- policies_removed: policiesRemoved,
2863
- ...source ? { source } : {},
2864
- platform: platform(),
2865
- arch: arch(),
2866
- os_release: release(),
2867
- hostname_hash: hashToId(hostname()),
2868
- has_custom_hooks_path: !!configToWrite.customPoliciesPath,
2869
- has_policy_params: !!(configToWrite.policyParams && Object.keys(configToWrite.policyParams).length > 0),
2870
- param_policy_names: configToWrite.policyParams ? Object.keys(configToWrite.policyParams) : [],
2871
- command_format: scope === "project" ? "npx" : "absolute"
2872
- });
2873
- } catch {}
2874
- console.log(`Failproof AI hooks installed for all ${HOOK_EVENT_TYPES.length} event types (scope: ${scope}).`);
2875
- console.log(`Settings: ${settingsPath}`);
2876
- if (scope === "project") {
2877
- console.log(`Command: npx -y failproofai`);
2878
- console.log(`
2879
- This file can be committed to git — no machine-specific paths.`);
2880
- } else {
2881
- console.log(`Binary: ${binaryPath}`);
2882
- }
2883
- const otherScopes = deduplicateScopes(HOOK_SCOPES, cwd).filter((s) => s !== scope);
2884
- const duplicates = otherScopes.filter((s) => hooksInstalledInSettings(s, cwd));
2885
- if (duplicates.length > 0) {
2886
- const scopeList = duplicates.map((s) => `${s} (${scopeLabel(s)})`).join(", ");
2887
- console.log();
2888
- console.log(`\x1B[33mWarning: Failproof AI hooks are also installed at ${scopeList}.\x1B[0m`);
2889
- console.log(`Having hooks in multiple scopes may cause duplicate policy evaluation.`);
2890
- console.log(`Use \`failproofai policies --uninstall --scope ${duplicates[0]}\` to remove the other installation,`);
2891
- console.log(`or \`failproofai policies\` to see all scopes.`);
2892
- }
2893
- }
2894
- async function removeHooks(policyNames, scope = "user", cwd, opts) {
2895
- const configScope = scope === "all" ? "user" : scope;
2896
- if (opts?.removeCustomHooks) {
2897
- const config = readScopedHooksConfig(configScope, cwd);
2898
- delete config.customPoliciesPath;
2899
- writeScopedHooksConfig(config, configScope, cwd);
2900
- console.log("Custom hooks path cleared.");
2901
- }
2902
- if (policyNames && policyNames.length > 0 && !(policyNames.length === 1 && policyNames[0] === "all")) {
2903
- validatePolicyNames(policyNames);
2904
- const config = readScopedHooksConfig(configScope, cwd);
2905
- const removeSet = new Set(policyNames);
2906
- const remaining = config.enabledPolicies.filter((p) => !removeSet.has(p));
2907
- const notEnabled = policyNames.filter((p) => !config.enabledPolicies.includes(p));
2908
- if (notEnabled.length > 0) {
2909
- console.log(`Warning: policy(ies) not currently enabled: ${notEnabled.join(", ")}`);
2910
- }
2911
- const { policyParams: existingParams, ...baseConfig } = config;
2912
- const filteredParams = existingParams ? Object.fromEntries(Object.entries(existingParams).filter(([k]) => !removeSet.has(k))) : null;
2913
- const updatedConfig = {
2914
- ...baseConfig,
2915
- enabledPolicies: remaining,
2916
- ...filteredParams && Object.keys(filteredParams).length > 0 ? { policyParams: filteredParams } : {}
2917
- };
2918
- writeScopedHooksConfig(updatedConfig, configScope, cwd);
2919
- try {
2920
- const distinctId = getInstanceId();
2921
- const actuallyRemoved = policyNames.filter((p) => config.enabledPolicies.includes(p));
2922
- await trackHookEvent(distinctId, "hooks_removed", {
2923
- scope,
2924
- removal_mode: opts?.betaOnly ? "beta_policies" : "policies",
2925
- beta_only: opts?.betaOnly ?? false,
2926
- policies_removed: actuallyRemoved,
2927
- removed_count: actuallyRemoved.length,
2928
- ...opts?.source ? { source: opts.source } : {},
2929
- platform: platform(),
2930
- arch: arch(),
2931
- os_release: release(),
2932
- hostname_hash: hashToId(hostname())
2933
- });
2934
- } catch {}
2935
- console.log(`Disabled ${policyNames.length - notEnabled.length} policy(ies).`);
2936
- console.log(`Remaining: ${remaining.length > 0 ? remaining.join(", ") : "(none)"}`);
2937
- return;
2938
- }
2939
- const configBeforeRemoval = readScopedHooksConfig(configScope, cwd);
2940
- const scopesToRemove = scope === "all" ? [...HOOK_SCOPES] : [scope];
2941
- let totalRemoved = 0;
2942
- for (const s of scopesToRemove) {
2943
- const settingsPath = getSettingsPath(s, cwd);
2944
- if (!existsSync5(settingsPath)) {
2945
- if (scope !== "all") {
2946
- console.log("No settings file found. Nothing to remove.");
2947
- return;
2948
- }
2949
- continue;
2950
- }
2951
- const settings = readSettings(settingsPath);
2952
- if (!settings.hooks) {
2953
- if (scope !== "all") {
2954
- console.log("No hooks found in settings. Nothing to remove.");
2955
- return;
2956
- }
2957
- continue;
2958
- }
2959
- const removed = removeHooksFromSettingsFile(settingsPath);
2960
- totalRemoved += removed;
2961
- if (scope !== "all") {
2962
- console.log(`Removed ${removed} failproofai hook(s) from settings.`);
2963
- console.log(`Settings: ${settingsPath}`);
2964
- }
2965
- }
2966
- if (scope === "all") {
2967
- console.log(`Removed ${totalRemoved} failproofai hook(s) from all scopes.`);
2968
- for (const s of scopesToRemove) {
2969
- console.log(` ${s}: ${getSettingsPath(s, cwd)}`);
2970
- }
2971
- }
2972
- try {
2973
- const distinctId = getInstanceId();
2974
- await trackHookEvent(distinctId, "hooks_removed", {
2975
- scope,
2976
- removal_mode: "hooks",
2977
- policies_removed: configBeforeRemoval.enabledPolicies,
2978
- removed_count: totalRemoved,
2979
- ...opts?.source ? { source: opts.source } : {},
2980
- platform: platform(),
2981
- arch: arch(),
2982
- os_release: release(),
2983
- hostname_hash: hashToId(hostname())
2984
- });
2985
- } catch {}
2986
- if (scope === "all") {
2987
- for (const s of HOOK_SCOPES) {
2988
- const existing = readScopedHooksConfig(s, cwd);
2989
- if (existing.enabledPolicies.length > 0 || existing.customPoliciesPath || existing.policyParams) {
2990
- const { customPoliciesPath: _drop, policyParams: _dropParams, ...rest } = existing;
2991
- writeScopedHooksConfig({ ...rest, enabledPolicies: [] }, s, cwd);
2992
- }
2993
- }
2994
- } else if (!HOOK_SCOPES.some((s) => hooksInstalledInSettings(s, cwd))) {
2995
- const existing = readScopedHooksConfig(configScope, cwd);
2996
- const { customPoliciesPath: _drop, policyParams: _dropParams, ...rest } = existing;
2997
- writeScopedHooksConfig({ ...rest, enabledPolicies: [] }, configScope, cwd);
2998
- }
2999
- }
3000
- async function listHooks(cwd) {
3001
- const config = readMergedHooksConfig(cwd);
3002
- const enabledSet = new Set(config.enabledPolicies);
3003
- const uniqueScopes = deduplicateScopes(HOOK_SCOPES, cwd);
3004
- const installedScopes = uniqueScopes.filter((s) => hooksInstalledInSettings(s, cwd));
3005
- const regularPolicies = BUILTIN_POLICIES.filter((p) => !p.beta);
3006
- const betaPolicies = BUILTIN_POLICIES.filter((p) => p.beta);
3007
- const nameColWidth = Math.max(...BUILTIN_POLICIES.map((p) => p.name.length)) + 2;
3008
- const builtinPolicyNames = new Set(BUILTIN_POLICIES.map((p) => p.name));
3009
- const printParamsSummary = (policyName, indent) => {
3010
- const params = config.policyParams?.[policyName];
3011
- if (!params)
3012
- return;
3013
- for (const [key, val] of Object.entries(params)) {
3014
- console.log(`${indent} ${key}: ${JSON.stringify(val)}`);
3015
- }
3016
- };
3017
- const statusCol = 8;
3018
- const printSimpleRow = (policy) => {
3019
- const mark = enabledSet.has(policy.name) ? `\x1B[32m✓\x1B[0m` : " ";
3020
- console.log(` ${mark}${" ".repeat(statusCol - 1)}${policy.name.padEnd(nameColWidth)}${policy.description}`);
3021
- printParamsSummary(policy.name, ` ${" ".repeat(statusCol)}`);
3022
- };
3023
- const printBetaSection = (printRow) => {
3024
- if (betaPolicies.length > 0) {
3025
- console.log(`
3026
- \x1B[2m── Beta ──\x1B[0m`);
3027
- for (const policy of betaPolicies)
3028
- printRow(policy);
3029
- }
3030
- };
3031
- if (installedScopes.length === 0) {
3032
- console.log(`
3033
- Failproof AI Policies — not installed
3034
- `);
3035
- console.log(` ${"Status".padEnd(statusCol)}${"Name".padEnd(nameColWidth)}Description`);
3036
- console.log(` ${"─".repeat(6)} ${"─".repeat(nameColWidth - 2)} ${"─".repeat(38)}`);
3037
- for (const policy of regularPolicies)
3038
- printSimpleRow(policy);
3039
- printBetaSection(printSimpleRow);
3040
- if (config.enabledPolicies.length > 0) {
3041
- console.log("\n Policies not installed. Run `failproofai policies --install` to activate.");
3042
- } else {
3043
- console.log("\n Run `failproofai policies --install` to get started.");
3044
- }
3045
- console.log(` Config: ~/.failproofai/policies-config.json
3046
- `);
3047
- } else if (installedScopes.length === 1) {
3048
- const scope = installedScopes[0];
3049
- console.log(`
3050
- Failproof AI Hook Policies (${scope})
3051
- `);
3052
- console.log(` ${"Status".padEnd(statusCol)}${"Name".padEnd(nameColWidth)}Description`);
3053
- console.log(` ${"─".repeat(6)} ${"─".repeat(nameColWidth - 2)} ${"─".repeat(38)}`);
3054
- for (const policy of regularPolicies)
3055
- printSimpleRow(policy);
3056
- printBetaSection(printSimpleRow);
3057
- console.log(`
3058
- Config: ~/.failproofai/policies-config.json
3059
- `);
3060
- } else {
3061
- const COL = 9;
3062
- const scopeLabelMap = {
3063
- user: "User",
3064
- project: "Project",
3065
- local: "Local"
3066
- };
3067
- console.log(`
3068
- Failproof AI Hook Policies
3069
- `);
3070
- const buildScopePrefix = () => {
3071
- let s = " ";
3072
- for (const sc of installedScopes)
3073
- s += scopeLabelMap[sc].padEnd(COL);
3074
- return s;
3075
- };
3076
- const scopeHeaderWidth = installedScopes.length * COL;
3077
- console.log(`${buildScopePrefix()}${"Name".padEnd(nameColWidth)}Description`);
3078
- console.log(` ${"─".repeat(scopeHeaderWidth)}${"─".repeat(nameColWidth)}${"─".repeat(38)}`);
3079
- const printMultiScopeRow = (policy) => {
3080
- const enabled = enabledSet.has(policy.name);
3081
- let row = " ";
3082
- for (const _scope of installedScopes) {
3083
- if (enabled) {
3084
- row += `\x1B[32m✓ ON\x1B[0m` + " ".repeat(COL - 4);
3085
- } else {
3086
- row += " OFF" + " ".repeat(COL - 5);
3087
- }
3088
- }
3089
- row += policy.name.padEnd(nameColWidth) + policy.description;
3090
- console.log(row);
3091
- printParamsSummary(policy.name, ` ${" ".repeat(scopeHeaderWidth)}`);
3092
- };
3093
- for (const policy of regularPolicies)
3094
- printMultiScopeRow(policy);
3095
- if (betaPolicies.length > 0) {
3096
- console.log(`
3097
- \x1B[2m── Beta ──\x1B[0m`);
3098
- for (const policy of betaPolicies)
3099
- printMultiScopeRow(policy);
3100
- }
3101
- console.log(`
3102
- Config: ~/.failproofai/policies-config.json`);
3103
- const scopeNames = installedScopes.join(", ");
3104
- console.log();
3105
- console.log(`\x1B[33m⚠ Hooks in multiple scopes (${scopeNames}).\x1B[0m`);
3106
- console.log(` Consider keeping one. Remove with: failproofai policies --uninstall --scope <scope>
3107
- `);
3108
- }
3109
- if (config.policyParams) {
3110
- for (const key of Object.keys(config.policyParams)) {
3111
- if (!builtinPolicyNames.has(key)) {
3112
- console.log(` \x1B[33mWarning: unknown policyParams key "${key}" — possible typo\x1B[0m`);
3113
- }
3114
- }
3115
- }
3116
- if (config.customPoliciesPath) {
3117
- console.log(`
3118
- ── Custom Policies (${config.customPoliciesPath}) ───────────────────────`);
3119
- if (!existsSync5(config.customPoliciesPath)) {
3120
- console.log(` \x1B[31m✗ File not found: ${config.customPoliciesPath}\x1B[0m`);
3121
- } else {
3122
- const hooks = await loadCustomHooks(config.customPoliciesPath);
3123
- if (hooks.length === 0) {
3124
- console.log(` \x1B[31m✗ ERR failed to load (check ~/.failproofai/logs/hooks.log)\x1B[0m`);
3125
- } else {
3126
- const descColWidth = nameColWidth;
3127
- for (const hook of hooks) {
3128
- console.log(` \x1B[32m✓\x1B[0m ${hook.name.padEnd(descColWidth)}${hook.description ?? ""}`);
3129
- }
3130
- }
3131
- }
3132
- console.log();
3133
- }
3134
- const base = cwd ? resolve5(cwd) : process.cwd();
3135
- const conventionDirs = [
3136
- { label: "Project", dir: resolve5(base, ".failproofai", "policies") },
3137
- { label: "User", dir: resolve5(homedir6(), ".failproofai", "policies") }
3138
- ];
3139
- for (const { label, dir } of conventionDirs) {
3140
- const files = discoverPolicyFiles(dir);
3141
- if (files.length === 0)
3142
- continue;
3143
- console.log(`
3144
- ── Convention Policies — ${label} (${dir}) ──────────`);
3145
- for (const file of files) {
3146
- try {
3147
- const hooks = await loadCustomHooks(file);
3148
- if (hooks.length === 0) {
3149
- const filename = basename2(file);
3150
- console.log(` \x1B[31m✗\x1B[0m ${filename.padEnd(nameColWidth)}\x1B[31mfailed to load\x1B[0m`);
3151
- } else {
3152
- const filename = basename2(file);
3153
- const hookSummary = hooks.map((h) => h.name).join(", ");
3154
- console.log(` \x1B[32m✓\x1B[0m ${filename.padEnd(nameColWidth)}${hooks.length} hook(s): ${hookSummary}`);
3155
- }
3156
- } catch {
3157
- const filename = basename2(file);
3158
- console.log(` \x1B[31m✗\x1B[0m ${filename.padEnd(nameColWidth)}\x1B[31merror\x1B[0m`);
3159
- }
3160
- }
3161
- console.log();
3162
- }
3163
- }
3164
- var VALID_POLICY_NAMES;
3165
- var init_manager = __esm(() => {
3166
- init_types();
3167
- init_install_prompt();
3168
- init_hooks_config();
3169
- init_builtin_policies();
3170
- init_custom_hooks_loader();
3171
- init_hook_telemetry();
3172
- init_telemetry_id();
3173
- init_cli_error();
3174
- VALID_POLICY_NAMES = new Set(BUILTIN_POLICIES.map((p) => p.name));
3175
- });
3176
-
3177
- // lib/paths.ts
3178
- import { homedir as homedir7 } from "os";
3179
- import { join as join4 } from "path";
3180
- function getDefaultClaudeProjectsPath() {
3181
- return join4(homedir7(), ".claude", "projects");
3182
- }
3183
- var init_paths = () => {};
3184
-
3185
- // scripts/parse-script-args.ts
3186
- import { resolve as resolve6 } from "path";
3187
- function parseStringFlag(flagName, errorLabel, inlineValue, args, index, options) {
3188
- const raw = inlineValue ?? args[index + 1];
3189
- if (raw === undefined || inlineValue === null && raw.startsWith("-")) {
3190
- console.error(`Error: ${flagName} requires ${errorLabel}`);
3191
- process.exit(1);
3192
- }
3193
- const value = options?.resolve ? resolve6(raw) : raw;
3194
- return { value, spliceCount: inlineValue !== null ? 1 : 2 };
3195
- }
3196
- function parseScriptArgs(argv) {
3197
- const args = [...argv];
3198
- let claudeProjectsPath;
3199
- let loggingLevel;
3200
- let disableTelemetry = false;
3201
- let allowedDevOrigins;
3202
- for (let i = 0;i < args.length; i++) {
3203
- const arg = args[i];
3204
- const eqIdx = arg.indexOf("=");
3205
- const flag = eqIdx >= 0 ? arg.slice(0, eqIdx) : arg;
3206
- const inlineValue = eqIdx >= 0 ? arg.slice(eqIdx + 1) : null;
3207
- if (flag === "--projects-path" || flag === "-p") {
3208
- const { value, spliceCount } = parseStringFlag(flag, "a path argument", inlineValue, args, i);
3209
- claudeProjectsPath = value;
3210
- args.splice(i, spliceCount);
3211
- i--;
3212
- continue;
3213
- }
3214
- if (flag === "--logging") {
3215
- const raw = inlineValue ?? args[i + 1];
3216
- if (raw === undefined || inlineValue === null && raw.startsWith("-")) {
3217
- console.error("Error: --logging requires a level (info, warn, error)");
3218
- process.exit(1);
3219
- }
3220
- const val = raw.toLowerCase();
3221
- if (val !== "info" && val !== "warn" && val !== "error") {
3222
- console.error("Error: --logging must be one of: info, warn, error");
3223
- process.exit(1);
3224
- }
3225
- loggingLevel = val;
3226
- args.splice(i, inlineValue !== null ? 1 : 2);
3227
- i--;
3228
- continue;
3229
- }
3230
- if (flag === "--disable-telemetry") {
3231
- disableTelemetry = true;
3232
- args.splice(i, 1);
3233
- i--;
3234
- continue;
3235
- }
3236
- if (flag === "--allowed-origins") {
3237
- const { value, spliceCount } = parseStringFlag(flag, "a comma-separated list of origins", inlineValue, args, i);
3238
- allowedDevOrigins = value.split(",").map((s) => s.trim()).filter(Boolean);
3239
- args.splice(i, spliceCount);
3240
- i--;
3241
- continue;
3242
- }
3243
- }
3244
- return { claudeProjectsPath, loggingLevel, disableTelemetry, allowedDevOrigins, remainingArgs: args };
3245
- }
3246
- var init_parse_script_args = () => {};
3247
-
3248
- // scripts/launch.ts
3249
- var exports_launch = {};
3250
- __export(exports_launch, {
3251
- launch: () => launch
3252
- });
3253
- import { spawn } from "child_process";
3254
- import { realpathSync, existsSync as existsSync6 } from "node:fs";
3255
- import { resolve as resolve7, dirname as dirname4 } from "node:path";
3256
- import { fileURLToPath } from "node:url";
3257
- function launch(mode) {
3258
- const { claudeProjectsPath: parsedPath, loggingLevel, disableTelemetry, allowedDevOrigins, remainingArgs } = parseScriptArgs(process.argv.slice(2));
3259
- console.log(`
3260
- ______ _ __ ____ ___ ____
3261
- / ____/___ _(_) /___ _________ ____ / __/ / | / _/
3262
- / /_ / __ \`/ / / __ \\/ ___/ __ \\/ __ \\/ /_ / /| | / /
3263
- / __/ / /_/ / / / /_/ / / / /_/ / /_/ / __/ / ___ |_/ /
3264
- /_/ \\__,_/_/_/ .___/_/ \\____/\\____/_/ /_/ |_/___/
3265
- /_/ v${version2}
3266
- `);
3267
- console.log(` ⭐ Star us: https://github.com/exospherehost/failproofai`);
3268
- console.log(` \uD83D\uDCD6 Docs: https://befailproof.ai
3269
- `);
3270
- let claudeProjectsPath = parsedPath;
3271
- if (!claudeProjectsPath) {
3272
- claudeProjectsPath = getDefaultClaudeProjectsPath();
3273
- console.log(`Using default .claude projects path: ${claudeProjectsPath}`);
3274
- } else {
3275
- console.log(`Using custom .claude projects path: ${claudeProjectsPath}`);
3276
- }
3277
- process.env.CLAUDE_PROJECTS_PATH = claudeProjectsPath;
3278
- let cmd;
3279
- let cmdArgs;
3280
- if (mode === "start") {
3281
- const portIdx = remainingArgs.indexOf("--port");
3282
- const port = portIdx >= 0 ? remainingArgs[portIdx + 1] : "8020";
3283
- process.env.PORT = port;
3284
- process.env.HOSTNAME = "0.0.0.0";
3285
- cmd = "node";
3286
- const packageRoot = process.env.FAILPROOFAI_PACKAGE_ROOT ?? resolve7(dirname4(realpathSync(fileURLToPath(import.meta.url))), "..");
3287
- const serverJsPath = resolve7(packageRoot, ".next/standalone/server.js");
3288
- if (!existsSync6(serverJsPath)) {
3289
- console.error(`
3290
- Error: Cannot find server.js at:
3291
- ${serverJsPath}
3292
-
3293
- ` + `The package may be missing its build output.
3294
- ` + `Try reinstalling:
3295
- npm install -g failproofai@latest
3296
- `);
3297
- process.exit(1);
3298
- }
3299
- cmdArgs = [serverJsPath];
3300
- } else {
3301
- cmd = "bunx";
3302
- cmdArgs = ["--bun", "next", "dev", ...remainingArgs];
3303
- }
3304
- const nextProcess = spawn(cmd, cmdArgs, {
3305
- stdio: "inherit",
3306
- env: {
3307
- ...process.env,
3308
- CLAUDE_PROJECTS_PATH: claudeProjectsPath,
3309
- ...loggingLevel ? { FAILPROOFAI_LOG_LEVEL: loggingLevel } : {},
3310
- ...disableTelemetry ? { FAILPROOFAI_TELEMETRY_DISABLED: "1" } : {},
3311
- ...allowedDevOrigins ? { FAILPROOFAI_ALLOWED_DEV_ORIGINS: allowedDevOrigins.join(",") } : {}
3312
- }
3313
- });
3314
- nextProcess.on("error", (error) => {
3315
- console.error("Error starting Next.js:", error);
3316
- process.exit(1);
3317
- });
3318
- nextProcess.on("exit", (code) => {
3319
- process.exit(code || 0);
3320
- });
3321
- }
3322
- var init_launch = __esm(() => {
3323
- init_paths();
3324
- init_parse_script_args();
3325
- init_package();
3326
- });
3327
-
3328
- // src/cli-error.ts
3329
- var exports_cli_error = {};
3330
- __export(exports_cli_error, {
3331
- CliError: () => CliError2
3332
- });
3333
- var CliError2;
3334
- var init_cli_error2 = __esm(() => {
3335
- CliError2 = class CliError2 extends Error {
3336
- exitCode;
3337
- constructor(message, exitCode = 1) {
3338
- super(message);
3339
- this.name = "CliError";
3340
- this.exitCode = exitCode;
3341
- }
3342
- };
3343
- });
3344
-
3345
- // bin/failproofai.mjs
3346
- import { realpathSync as realpathSync2 } from "fs";
3347
- import { dirname as dirname5, resolve as resolve8 } from "path";
3348
- import { fileURLToPath as fileURLToPath2 } from "url";
3349
- // package.json
3350
- var version = "0.0.6-beta.2";
3351
-
3352
- // bin/failproofai.mjs
3353
- if (!process.env.FAILPROOFAI_PACKAGE_ROOT) {
3354
- process.env.FAILPROOFAI_PACKAGE_ROOT = resolve8(dirname5(realpathSync2(fileURLToPath2(import.meta.url))), "..");
3355
- }
3356
- if (!process.env.FAILPROOFAI_DIST_PATH) {
3357
- process.env.FAILPROOFAI_DIST_PATH = resolve8(dirname5(realpathSync2(fileURLToPath2(import.meta.url))), "..", "dist");
3358
- }
3359
- var args = process.argv.slice(2);
3360
- if (args[0] === "p")
3361
- args[0] = "policies";
3362
- var hookIdx = args.indexOf("--hook");
3363
- if (hookIdx >= 0) {
3364
- if (!args[hookIdx + 1]) {
3365
- console.error("Error: Missing event type after --hook");
3366
- console.error("Usage: failproofai --hook <event> (e.g. PreToolUse, PostToolUse)");
3367
- process.exit(1);
3368
- }
3369
- try {
3370
- const { handleHookEvent: handleHookEvent2 } = await Promise.resolve().then(() => (init_handler(), exports_handler));
3371
- const exitCode = await handleHookEvent2(args[hookIdx + 1]);
3372
- process.exit(exitCode);
3373
- } catch (err) {
3374
- const msg = err instanceof Error ? err.message : String(err);
3375
- console.error(`Unexpected error: ${msg}`);
3376
- process.exit(2);
3377
- }
3378
- }
3379
- async function runCli() {
3380
- const SUBCOMMANDS = ["policies"];
3381
- if ((args.includes("--help") || args.includes("-h")) && !SUBCOMMANDS.includes(args[0])) {
3382
- const extraArgs = args.filter((a) => a !== "--help" && a !== "-h");
3383
- if (extraArgs.length > 0) {
3384
- throw new CliError3(`Unexpected argument: ${extraArgs[0]}
3385
- Run \`failproofai --help\` for usage.`);
3386
- }
3387
- console.log(`
3388
- failproofai v${version}
3389
-
3390
- USAGE
3391
- failproofai [command] [options]
3392
-
3393
- COMMANDS
3394
- (no args) Launch the policy dashboard
3395
-
3396
- policies, p List all available policies and their status
3397
- policies --install, -i Enable policies in Claude Code settings
3398
- [names...] Specific policy names to enable
3399
- --scope user|project|local Config scope to write to (default: user)
3400
- --beta Include beta policies
3401
- --custom, -c <path> Path to a JS file of custom policies
3402
-
3403
- policies --uninstall, -u Disable policies or remove hooks
3404
- [names...] Specific policy names to disable
3405
- --scope user|project|local|all Config scope to remove from (default: user)
3406
- --beta Remove only beta policies
3407
- --custom, -c Clear the customPoliciesPath from config
3408
-
3409
- policies --help, -h Show this help for the policies command
3410
-
3411
- --version, -v Print version and exit
3412
- --help, -h Show this help message
3413
-
3414
- CONVENTION POLICIES
3415
- Drop *policies.{js,mjs,ts} files into .failproofai/policies/ for auto-loading.
3416
- Works at project level (.failproofai/policies/) and user level (~/.failproofai/policies/).
3417
- No --custom flag or config changes needed \u2014 just drop files and they're picked up.
3418
-
3419
- EXAMPLES
3420
- failproofai policies
3421
- failproofai policies --install
3422
- failproofai policies --install block-sudo sanitize-api-keys --scope project
3423
- failproofai policies --install --custom ./my-policies.js
3424
- failproofai policies -i -c ./my-policies.js
3425
- failproofai policies --uninstall block-sudo
3426
- failproofai policies --uninstall --custom
3427
-
3428
- LINKS
3429
- \u2B50 Star us: https://github.com/exospherehost/failproofai
3430
- \uD83D\uDCD6 Docs: https://befailproof.ai
3431
- `.trimStart());
3432
- process.exit(0);
3433
- }
3434
- if ((args.includes("--version") || args.includes("-v")) && !SUBCOMMANDS.includes(args[0])) {
3435
- const extraArgs = args.filter((a) => a !== "--version" && a !== "-v");
3436
- if (extraArgs.length > 0) {
3437
- throw new CliError3(`Unexpected argument: ${extraArgs[0]}
3438
- Run \`failproofai --help\` for usage.`);
3439
- }
3440
- console.log(version);
3441
- process.exit(0);
3442
- }
3443
- if (args[0] === "policies") {
3444
- const subArgs = args.slice(1);
3445
- const isInstall = subArgs.includes("--install") || subArgs.includes("-i");
3446
- const isUninstall = subArgs.includes("--uninstall") || subArgs.includes("-u");
3447
- const isHelp = subArgs.includes("--help") || subArgs.includes("-h");
3448
- if (isHelp) {
3449
- console.log(`
3450
- failproofai policies \u2014 manage Failproof AI policies
3451
-
3452
- USAGE
3453
- failproofai policies List all policies and their status
3454
- failproofai policies --install, -i Enable policies
3455
- failproofai policies --uninstall, -u Disable policies or remove hooks
3456
-
3457
- OPTIONS (install)
3458
- [names...] Specific policy names to enable (omit for interactive)
3459
- --scope user|project|local Config scope to write to (default: user)
3460
- --beta Include beta policies
3461
- --custom, -c <path> Path to a JS file of custom policies
3462
- (skips interactive prompt; validates file first)
3463
-
3464
- OPTIONS (uninstall)
3465
- [names...] Specific policy names to disable (omit to remove hooks)
3466
- --scope user|project|local|all Config scope to remove from (default: user)
3467
- --beta Remove only beta policies
3468
- --custom, -c Clear the customPoliciesPath from config
3469
-
3470
- EXAMPLES
3471
- failproofai policies
3472
- failproofai policies --install
3473
- failproofai policies --install block-sudo sanitize-api-keys
3474
- failproofai policies --install --custom ./my-policies.js
3475
- failproofai policies -i -c ./my-policies.js
3476
- failproofai policies --uninstall block-sudo
3477
- failproofai policies -u
3478
- failproofai policies --uninstall --custom
3479
- `.trimStart());
3480
- process.exit(0);
3481
- }
3482
- if (isInstall) {
3483
- const { installHooks: installHooks2 } = await Promise.resolve().then(() => (init_manager(), exports_manager));
3484
- const scopeIdx = subArgs.indexOf("--scope");
3485
- const scope = scopeIdx >= 0 ? subArgs[scopeIdx + 1] : "user";
3486
- if (scopeIdx >= 0 && (!scope || scope.startsWith("-"))) {
3487
- throw new CliError3("Missing value for --scope. Valid values: user, project, local");
3488
- }
3489
- if (scopeIdx >= 0 && !["user", "project", "local"].includes(scope)) {
3490
- throw new CliError3(`Invalid scope: ${scope}. Valid values: user, project, local`);
3491
- }
3492
- const customIdx = subArgs.includes("--custom") ? subArgs.indexOf("--custom") : subArgs.includes("-c") ? subArgs.indexOf("-c") : -1;
3493
- const customPoliciesPath = customIdx >= 0 ? subArgs[customIdx + 1] : undefined;
3494
- if (customIdx >= 0 && (!customPoliciesPath || customPoliciesPath.startsWith("-"))) {
3495
- throw new CliError3(`Missing path after --custom/-c
3496
- Usage: --custom <path> (e.g. --custom ./my-policies.js)`);
3497
- }
3498
- const includeBeta = subArgs.includes("--beta");
3499
- const consumedIdxs = new Set;
3500
- if (scopeIdx >= 0)
3501
- consumedIdxs.add(scopeIdx + 1);
3502
- if (customIdx >= 0)
3503
- consumedIdxs.add(customIdx + 1);
3504
- const flags = new Set(["--install", "-i", "--scope", "--beta", "--custom", "-c"]);
3505
- const unknownInstallFlag = subArgs.find((a) => a.startsWith("-") && !flags.has(a));
3506
- if (unknownInstallFlag) {
3507
- throw new CliError3(`Unknown flag: ${unknownInstallFlag}
3508
- Run \`failproofai policies --help\` for usage.`);
3509
- }
3510
- const explicitPolicyNames = subArgs.filter((a, idx) => !a.startsWith("-") && !consumedIdxs.has(idx));
3511
- const policyNames = explicitPolicyNames.length > 0 ? explicitPolicyNames : customPoliciesPath !== undefined ? [] : undefined;
3512
- await installHooks2(policyNames, scope, undefined, includeBeta, undefined, customPoliciesPath);
3513
- process.exit(0);
3514
- }
3515
- if (isUninstall) {
3516
- const { removeHooks: removeHooks2 } = await Promise.resolve().then(() => (init_manager(), exports_manager));
3517
- const scopeIdx = subArgs.indexOf("--scope");
3518
- const scope = scopeIdx >= 0 ? subArgs[scopeIdx + 1] : "user";
3519
- if (scopeIdx >= 0 && (!scope || scope.startsWith("-"))) {
3520
- throw new CliError3("Missing value for --scope. Valid values: user, project, local, all");
3521
- }
3522
- if (scopeIdx >= 0 && !["user", "project", "local", "all"].includes(scope)) {
3523
- throw new CliError3(`Invalid scope: ${scope}. Valid values: user, project, local, all`);
3524
- }
3525
- const betaOnly = subArgs.includes("--beta");
3526
- const removeCustomHooks = subArgs.includes("--custom") || subArgs.includes("-c");
3527
- const consumedIdxs = new Set;
3528
- if (scopeIdx >= 0)
3529
- consumedIdxs.add(scopeIdx + 1);
3530
- const flags = new Set(["--uninstall", "-u", "--scope", "--beta", "--custom", "-c"]);
3531
- const unknownUninstallFlag = subArgs.find((a) => a.startsWith("-") && !flags.has(a));
3532
- if (unknownUninstallFlag) {
3533
- throw new CliError3(`Unknown flag: ${unknownUninstallFlag}
3534
- Run \`failproofai policies --help\` for usage.`);
3535
- }
3536
- const policyNames = subArgs.filter((a, idx) => !a.startsWith("-") && !consumedIdxs.has(idx));
3537
- await removeHooks2(policyNames.length > 0 ? policyNames : undefined, scope, undefined, { betaOnly, removeCustomHooks });
3538
- process.exit(0);
3539
- }
3540
- const knownListFlags = new Set(["--install", "-i", "--uninstall", "-u", "--help", "-h", "--list"]);
3541
- const unknownListArg = subArgs.find((a) => a.startsWith("-") && !knownListFlags.has(a));
3542
- if (unknownListArg) {
3543
- throw new CliError3(`Unknown flag: ${unknownListArg}
3544
- Run \`failproofai policies --help\` for usage.`);
3545
- }
3546
- const positionalArgs = subArgs.filter((a) => !a.startsWith("-"));
3547
- if (positionalArgs.length > 0) {
3548
- throw new CliError3(`Unexpected argument: ${positionalArgs[0]}
3549
- Run \`failproofai policies --help\` for usage.`);
3550
- }
3551
- const { listHooks: listHooks2 } = await Promise.resolve().then(() => (init_manager(), exports_manager));
3552
- await listHooks2();
3553
- process.exit(0);
3554
- }
3555
- const knownFlags = ["--version", "-v", "--help", "-h", "--hook"];
3556
- const unknownFlag = args.find((a) => a.startsWith("-") && !knownFlags.includes(a));
3557
- if (unknownFlag) {
3558
- let levenshtein = function(a, b) {
3559
- const m = a.length, n = b.length;
3560
- const dp = Array.from({ length: m + 1 }, (_, i) => Array.from({ length: n + 1 }, (_2, j) => i === 0 ? j : j === 0 ? i : 0));
3561
- for (let i = 1;i <= m; i++)
3562
- for (let j = 1;j <= n; j++)
3563
- 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]);
3564
- return dp[m][n];
3565
- };
3566
- const primary = ["--version", "--help", "--hook", "policies"];
3567
- const closest = primary.reduce((best, flag) => {
3568
- const dist = levenshtein(unknownFlag, flag);
3569
- return dist < best.dist ? { flag, dist } : best;
3570
- }, { flag: primary[0], dist: Infinity });
3571
- throw new CliError3(`Unknown flag: ${unknownFlag}
3572
- Did you mean: ${closest.flag}?
3573
- Run \`failproofai --help\` for usage details.`);
3574
- }
3575
- const unknownSubcommand = args.find((a) => !a.startsWith("-") && a !== "policies");
3576
- if (unknownSubcommand) {
3577
- throw new CliError3(`Unknown command: ${unknownSubcommand}
3578
- Did you mean: failproofai policies?
3579
- Run \`failproofai --help\` for usage details.`);
3580
- }
3581
- const { launch: launch2 } = await Promise.resolve().then(() => (init_launch(), exports_launch));
3582
- launch2("start");
3583
- }
3584
- var { CliError: CliError3 } = await Promise.resolve().then(() => (init_cli_error2(), exports_cli_error));
3585
- try {
3586
- await runCli();
3587
- } catch (err) {
3588
- if (err instanceof CliError3) {
3589
- console.error(`Error: ${err.message}`);
3590
- process.exit(err.exitCode);
3591
- }
3592
- const msg = err instanceof Error ? err.message : String(err);
3593
- console.error(`Unexpected error: ${msg}`);
3594
- process.exit(2);
3595
- }