failproofai 0.0.13 → 0.0.14-beta.1
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.
- package/.next/standalone/.codex/hooks.json +6 -6
- package/.next/standalone/.cursor/hooks.json +6 -6
- package/.next/standalone/.factory/hooks.json +112 -0
- package/.next/standalone/.next/BUILD_ID +1 -1
- package/.next/standalone/.next/build-manifest.json +5 -5
- package/.next/standalone/.next/prerender-manifest.json +3 -3
- package/.next/standalone/.next/required-server-files.json +1 -1
- package/.next/standalone/.next/server/app/_global-error/page/build-manifest.json +2 -2
- package/.next/standalone/.next/server/app/_global-error/page/server-reference-manifest.json +1 -1
- package/.next/standalone/.next/server/app/_global-error/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/_global-error.html +1 -1
- package/.next/standalone/.next/server/app/_global-error.rsc +7 -7
- package/.next/standalone/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +7 -7
- package/.next/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +3 -3
- package/.next/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +3 -3
- package/.next/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/_not-found/page/build-manifest.json +2 -2
- package/.next/standalone/.next/server/app/_not-found/page/server-reference-manifest.json +1 -1
- package/.next/standalone/.next/server/app/_not-found/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/_not-found.html +1 -1
- package/.next/standalone/.next/server/app/_not-found.rsc +15 -15
- package/.next/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +15 -15
- package/.next/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +4 -4
- package/.next/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +10 -10
- package/.next/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +3 -3
- package/.next/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/api/audit/invite/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/audit/run/route.js +3 -3
- package/.next/standalone/.next/server/app/api/audit/run/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/auth/login-request/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/auth/login-verify/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/auth/logout/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/auth/reminder/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/auth/status/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/download/[project]/[session]/route.js +2 -1
- package/.next/standalone/.next/server/app/api/download/[project]/[session]/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/audit/page/build-manifest.json +2 -2
- package/.next/standalone/.next/server/app/audit/page/server-reference-manifest.json +2 -2
- package/.next/standalone/.next/server/app/audit/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/audit/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/index.html +1 -1
- package/.next/standalone/.next/server/app/index.rsc +15 -15
- package/.next/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/index.segments/_full.segment.rsc +15 -15
- package/.next/standalone/.next/server/app/index.segments/_head.segment.rsc +4 -4
- package/.next/standalone/.next/server/app/index.segments/_index.segment.rsc +10 -10
- package/.next/standalone/.next/server/app/index.segments/_tree.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/page/build-manifest.json +2 -2
- package/.next/standalone/.next/server/app/page/server-reference-manifest.json +1 -1
- package/.next/standalone/.next/server/app/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/policies/page/build-manifest.json +2 -2
- package/.next/standalone/.next/server/app/policies/page/server-reference-manifest.json +8 -8
- package/.next/standalone/.next/server/app/policies/page.js +1 -1
- package/.next/standalone/.next/server/app/policies/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/policies/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/project/[name]/page/build-manifest.json +2 -2
- package/.next/standalone/.next/server/app/project/[name]/page/server-reference-manifest.json +1 -1
- package/.next/standalone/.next/server/app/project/[name]/page.js +3 -2
- package/.next/standalone/.next/server/app/project/[name]/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/project/[name]/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page/build-manifest.json +2 -2
- package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page/react-loadable-manifest.json +2 -2
- package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page/server-reference-manifest.json +2 -2
- package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page.js +5 -3
- package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/projects/page/build-manifest.json +2 -2
- package/.next/standalone/.next/server/app/projects/page/server-reference-manifest.json +1 -1
- package/.next/standalone/.next/server/app/projects/page.js +1 -1
- package/.next/standalone/.next/server/app/projects/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/projects/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0--lkk6._.js +3 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0-219ec._.js +3 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__04_h-a5._.js +3 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__06cuf1y._.js +3 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__08w4wmd._.js +3 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0g6bbuw._.js +3 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0jllheg._.js +3 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0jnjf2t._.js +3 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0q-v9z2._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0q0qzx1._.js +3 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0q2nbsl._.js +3 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0rv7m0k._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__16le-kd._.js +3 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__17g9wh7._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__1d9qen3._.js +3 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__1ffkmds._.js +3 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__1fwl2mz._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__1legmza._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__1mrihkj._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__1myjm-d._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__1qb590j._.js +3 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__1qxztj-._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__1shcqgr._.js +3 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__1xuedea._.js +3 -0
- package/.next/standalone/.next/server/chunks/{_1z062h5._.js → _1vslkoe._.js} +2 -2
- package/.next/standalone/.next/server/chunks/lib_factory-projects_ts_1eo_bk-._.js +3 -0
- package/.next/standalone/.next/server/chunks/lib_openclaw-projects_ts_0gc-p24._.js +3 -0
- package/.next/standalone/.next/server/chunks/node_modules_next_dist_esm_build_templates_app-route_17k9e3w.js +24 -12
- package/.next/standalone/.next/server/chunks/node_modules_posthog-node_dist_entrypoints_index_node_mjs_01r25oi._.js +1 -1
- package/.next/standalone/.next/server/chunks/node_modules_posthog-node_dist_entrypoints_index_node_mjs_09z9-p7._.js +1 -1
- package/.next/standalone/.next/server/chunks/package_json_[json]_cjs_1nxcc4v._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__00cz9as._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__015_i4t._.js +2 -2
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__030k0c6._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__03lv-pe._.js +2 -2
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__042d3cd._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__05r_17v._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__09q1168._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__108slqn._.js → [root-of-the-server]__0brjczq._.js} +2 -2
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0e2l3c1._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0hci7t3._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0hdsupo._.js +2 -2
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0p84xee._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0qeuy9c._.js +2 -2
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0s89hoe._.js +2 -2
- package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__07iom4f._.js → [root-of-the-server]__0wp9w6i._.js} +2 -2
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0ynf7tx._.js +2 -2
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__170799-._.js +12 -9
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__17ai7sy._.js +4 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1d8omgc._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1nq7ivq._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1tkjqka._.js +2 -2
- package/.next/standalone/.next/server/chunks/ssr/_05whahf._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/_0_9_ytf._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/_11_p9y8._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/{_0ul5rf_._.js → _1_ozgif._.js} +2 -2
- package/.next/standalone/.next/server/chunks/ssr/_1p0-leb._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/app_087bt9w._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/app_1fvisnp._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/app_209u41o._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/app_audit__components_audit-dashboard_tsx_0p9ud47._.js +2 -2
- package/.next/standalone/.next/server/chunks/ssr/app_global-error_tsx_1kp6l3x._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/app_policies_hooks-client_tsx_19dqvpc._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/lib_0xkhw_v._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/lib_antigravity-projects_ts_0_6zf11._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/lib_devin-projects_ts_0a64qdy._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/lib_factory-projects_ts_1uyaahq._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/lib_goose-projects_ts_0zkscri._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/lib_openclaw-projects_ts_0qxq3bp._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/{node_modules_html-to-image_es_index_13p7zob.js → node_modules_html-to-image_es_index_12rjfea.js} +1 -1
- package/.next/standalone/.next/server/chunks/ssr/node_modules_posthog-node_dist_entrypoints_index_node_mjs_11bnuzn._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/src_hooks_1ezd2jf._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/src_hooks_1tnuifj._.js +1 -1
- package/.next/standalone/.next/server/middleware-build-manifest.js +5 -5
- package/.next/standalone/.next/server/pages/404.html +1 -1
- package/.next/standalone/.next/server/pages/500.html +1 -1
- package/.next/standalone/.next/server/server-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/server-reference-manifest.json +10 -10
- package/.next/standalone/.next/static/chunks/{13f1kmjea-0md.js → 02jcotghcxrbu.js} +1 -1
- package/.next/standalone/.next/static/chunks/{30ysv-_8jj052.js → 0d5lypu1zs21r.js} +1 -1
- package/.next/standalone/.next/static/chunks/0emg30_6svycu.js +1 -0
- package/.next/standalone/.next/static/chunks/0o9wpnze1ey8y.js +6 -0
- package/.next/standalone/.next/static/chunks/{05apzuand0c22.js → 1359o5785rmve.js} +2 -2
- package/.next/standalone/.next/static/chunks/20tuctw38jbgw.js +1 -0
- package/.next/standalone/.next/static/chunks/{0vc0eak148vo6.js → 2ldhnaweu9ew3.js} +1 -1
- package/.next/standalone/.next/static/chunks/{23c7v2svx90dp.js → 2vav_x0_336ds.js} +1 -1
- package/.next/standalone/.next/static/chunks/3-ogr79xqbw0h.js +1 -0
- package/.next/standalone/.next/static/chunks/36j05kb2x323i.css +2 -0
- package/.next/standalone/.next/static/chunks/3cg7x0joi2b98.js +1 -0
- package/.next/standalone/.next/static/chunks/{36wdtkgdf0zrd.js → 3k87p-tijud-4.js} +1 -1
- package/.next/standalone/.next/static/chunks/{2cejn75gug3j8.js → 3kmaqcp7rldad.js} +1 -1
- package/.next/standalone/.next/static/chunks/{turbopack-0vavqo2d-tlt3.js → turbopack-10hj8t5pf33tu.js} +1 -1
- package/.next/standalone/.opencode/plugins/failproofai.mjs +30 -11
- package/.next/standalone/app/api/audit/run/route.ts +2 -0
- package/.next/standalone/app/audit/_components/empty-state.tsx +1 -1
- package/.next/standalone/app/components/global-error-listeners.tsx +27 -0
- package/.next/standalone/app/globals.css +25 -1
- package/.next/standalone/app/policies/hooks-client.tsx +2 -4
- package/.next/standalone/app/project/[name]/page.tsx +30 -10
- package/.next/standalone/app/project/[name]/session/[sessionId]/page.tsx +71 -21
- package/.next/standalone/assets/logos/antigravity.svg +1 -0
- package/.next/standalone/assets/logos/devin.svg +1 -0
- package/.next/standalone/assets/logos/factory-dark.png +0 -0
- package/.next/standalone/assets/logos/factory-light.png +0 -0
- package/.next/standalone/assets/logos/goose-dark.svg +1 -0
- package/.next/standalone/assets/logos/goose-light.svg +1 -0
- package/.next/standalone/assets/logos/openclaw.svg +22 -0
- package/.next/standalone/ci/cli-integration/Dockerfile +47 -0
- package/.next/standalone/ci/cli-integration/README.md +69 -0
- package/.next/standalone/ci/cli-integration/canary-policies.mjs +45 -0
- package/.next/standalone/ci/cli-integration/capture-tokens.sh +48 -0
- package/.next/standalone/ci/cli-integration/inject-tokens.sh +44 -0
- package/.next/standalone/ci/cli-integration/install-clis.sh +91 -0
- package/.next/standalone/ci/cli-integration/probe-cli.sh +208 -0
- package/.next/standalone/ci/cli-integration/report.js +74 -0
- package/.next/standalone/ci/cli-integration/run.sh +107 -0
- package/.next/standalone/components/navbar.tsx +5 -11
- package/.next/standalone/components/reach-developers.tsx +2 -2
- package/.next/standalone/docker-hook-sync/Dockerfile +8 -4
- package/.next/standalone/lib/antigravity-projects.ts +224 -0
- package/.next/standalone/lib/antigravity-sessions.ts +283 -0
- package/.next/standalone/lib/cli-registry.ts +26 -6
- package/.next/standalone/lib/devin-projects.ts +141 -0
- package/.next/standalone/lib/devin-sessions.ts +343 -0
- package/.next/standalone/lib/download-session.ts +56 -5
- package/.next/standalone/lib/error-origin.ts +67 -0
- package/.next/standalone/lib/factory-projects.ts +109 -0
- package/.next/standalone/lib/factory-sessions.ts +312 -0
- package/.next/standalone/lib/goose-projects.ts +148 -0
- package/.next/standalone/lib/goose-sessions.ts +296 -0
- package/.next/standalone/lib/install-check.ts +231 -0
- package/.next/standalone/lib/openclaw-projects.ts +182 -0
- package/.next/standalone/lib/openclaw-sessions.ts +284 -0
- package/.next/standalone/lib/projects.ts +33 -9
- package/.next/standalone/lib/telemetry-sanitize.ts +43 -0
- package/.next/standalone/openclaw-plugin/index.js +223 -0
- package/.next/standalone/openclaw-plugin/openclaw.plugin.json +6 -0
- package/.next/standalone/openclaw-plugin/package.json +12 -0
- package/.next/standalone/package.json +2 -3
- package/.next/standalone/server.js +1 -1
- package/README.md +94 -62
- package/bin/failproofai.mjs +98 -30
- package/dist/cli.mjs +12499 -9243
- package/lib/antigravity-projects.ts +224 -0
- package/lib/antigravity-sessions.ts +283 -0
- package/lib/cli-registry.ts +26 -6
- package/lib/devin-projects.ts +141 -0
- package/lib/devin-sessions.ts +343 -0
- package/lib/download-session.ts +56 -5
- package/lib/error-origin.ts +67 -0
- package/lib/factory-projects.ts +109 -0
- package/lib/factory-sessions.ts +312 -0
- package/lib/goose-projects.ts +148 -0
- package/lib/goose-sessions.ts +296 -0
- package/lib/install-check.ts +231 -0
- package/lib/openclaw-projects.ts +182 -0
- package/lib/openclaw-sessions.ts +284 -0
- package/lib/projects.ts +33 -9
- package/lib/telemetry-sanitize.ts +43 -0
- package/openclaw-plugin/index.js +223 -0
- package/openclaw-plugin/openclaw.plugin.json +6 -0
- package/openclaw-plugin/package.json +12 -0
- package/package.json +2 -3
- package/scripts/dev-hook.mjs +292 -0
- package/scripts/install-diagnosis.mjs +5 -8
- package/scripts/install-telemetry.mjs +12 -4
- package/scripts/launch.ts +4 -8
- package/scripts/sync-agent-cli-harnesses-prompt.md +7 -10
- package/scripts/translate-docs/cli.ts +120 -53
- package/scripts/translate-docs/config.ts +57 -13
- package/scripts/translate-docs/mdx-translator.ts +177 -28
- package/scripts/translate-docs/mintlify-nav.ts +91 -10
- package/scripts/translate-docs/readme-translator.ts +13 -5
- package/scripts/translate-docs/translator.ts +38 -2
- package/scripts/translate-docs/types.ts +3 -0
- package/scripts/validate-mdx.ts +12 -4
- package/src/audit/cli-adapters/antigravity.ts +87 -0
- package/src/audit/cli-adapters/devin.ts +53 -0
- package/src/audit/cli-adapters/factory.ts +75 -0
- package/src/audit/cli-adapters/goose.ts +53 -0
- package/src/audit/cli-adapters/index.ts +30 -6
- package/src/audit/cli-adapters/openclaw.ts +56 -0
- package/src/audit/cli.ts +83 -8
- package/src/audit/report.ts +1 -1
- package/src/audit/types.ts +1 -1
- package/src/hooks/builtin-policies.ts +5 -5
- package/src/hooks/configure-wizard.ts +356 -0
- package/src/hooks/handler.ts +283 -218
- package/src/hooks/hook-telemetry.ts +43 -3
- package/src/hooks/install-prompt.ts +92 -173
- package/src/hooks/integrations.ts +739 -138
- package/src/hooks/manager.ts +46 -2
- package/src/hooks/policy-evaluator.ts +301 -121
- package/src/hooks/policy-presets.ts +71 -0
- package/src/hooks/resolve-cwd.ts +1 -1
- package/src/hooks/resolve-permission-mode.ts +1 -5
- package/src/hooks/resolve-transcript-path.ts +25 -7
- package/src/hooks/tool-name-canonicalize.ts +36 -3
- package/src/hooks/tui.ts +578 -0
- package/src/hooks/types.ts +447 -109
- package/.next/standalone/.gemini/settings.json +0 -147
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0cag8qd._.js +0 -3
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0cefjd9._.js +0 -3
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0ekj92q._.js +0 -3
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__12v-lma._.js +0 -3
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__136y39g._.js +0 -3
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__13h8pzr._.js +0 -3
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__1hgv_75._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__04bqvos._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0dqgs96._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0q5rfxg._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0qukut7._.js +0 -4
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1ql-mch._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/_11v0g3z._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/lib_gemini-projects_ts_1c7bgx-._.js +0 -3
- package/.next/standalone/.next/static/chunks/0fa7j157kg9_d.js +0 -1
- package/.next/standalone/.next/static/chunks/0nrah-qv6smeg.js +0 -6
- package/.next/standalone/.next/static/chunks/0wtagd8a5gwq-.js +0 -1
- package/.next/standalone/.next/static/chunks/1c8i78p7t1i-5.js +0 -1
- package/.next/standalone/.next/static/chunks/25qr0e6ez0ei0.js +0 -1
- package/.next/standalone/.next/static/chunks/25s52un3k_wht.css +0 -2
- package/.next/standalone/assets/logos/gemini-dark.svg +0 -13
- package/.next/standalone/assets/logos/gemini-light.svg +0 -13
- package/.next/standalone/lib/gemini-projects.ts +0 -243
- package/.next/standalone/lib/gemini-sessions.ts +0 -365
- package/lib/gemini-projects.ts +0 -243
- package/lib/gemini-sessions.ts +0 -365
- package/scripts/postinstall.mjs +0 -243
- package/scripts/preuninstall.mjs +0 -131
- package/src/audit/cli-adapters/gemini.ts +0 -51
- package/src/hooks/first-run-nudge.ts +0 -146
- /package/.next/standalone/.next/static/{kmzdjTLVs6LqsfyOaUf69 → LkN-JjmhKGmL-WklH7WVi}/_buildManifest.js +0 -0
- /package/.next/standalone/.next/static/{kmzdjTLVs6LqsfyOaUf69 → LkN-JjmhKGmL-WklH7WVi}/_clientMiddlewareManifest.js +0 -0
- /package/.next/standalone/.next/static/{kmzdjTLVs6LqsfyOaUf69 → LkN-JjmhKGmL-WklH7WVi}/_ssgManifest.js +0 -0
|
@@ -26,10 +26,18 @@ import {
|
|
|
26
26
|
OPENCODE_HOOK_SCOPES,
|
|
27
27
|
PI_HOOK_EVENT_TYPES,
|
|
28
28
|
PI_HOOK_SCOPES,
|
|
29
|
-
GEMINI_HOOK_EVENT_TYPES,
|
|
30
|
-
GEMINI_HOOK_SCOPES,
|
|
31
29
|
HERMES_HOOK_EVENT_TYPES,
|
|
32
30
|
HERMES_HOOK_SCOPES,
|
|
31
|
+
OPENCLAW_HOOK_EVENT_TYPES,
|
|
32
|
+
OPENCLAW_HOOK_SCOPES,
|
|
33
|
+
FACTORY_HOOK_EVENT_TYPES,
|
|
34
|
+
FACTORY_HOOK_SCOPES,
|
|
35
|
+
DEVIN_HOOK_EVENT_TYPES,
|
|
36
|
+
DEVIN_HOOK_SCOPES,
|
|
37
|
+
ANTIGRAVITY_HOOK_EVENT_TYPES,
|
|
38
|
+
ANTIGRAVITY_HOOK_SCOPES,
|
|
39
|
+
GOOSE_HOOK_EVENT_TYPES,
|
|
40
|
+
GOOSE_HOOK_SCOPES,
|
|
33
41
|
FAILPROOFAI_HOOK_MARKER,
|
|
34
42
|
INTEGRATION_TYPES,
|
|
35
43
|
type IntegrationType,
|
|
@@ -310,8 +318,7 @@ export const codex: Integration = {
|
|
|
310
318
|
type: "command",
|
|
311
319
|
// Codex reads `timeout` in SECONDS (the field is literally `timeout`,
|
|
312
320
|
// default 600 per https://developers.openai.com/codex/hooks) — same unit as
|
|
313
|
-
// Claude/Cursor/Copilot
|
|
314
|
-
// milliseconds (default 60000). 60 = 60s, not 60000ms (~16.7h).
|
|
321
|
+
// Claude/Cursor/Copilot. 60 = 60s.
|
|
315
322
|
command,
|
|
316
323
|
timeout: 60,
|
|
317
324
|
[FAILPROOFAI_HOOK_MARKER]: true,
|
|
@@ -1286,61 +1293,456 @@ function makePiProjectRelativeEntry(extPath: string): string {
|
|
|
1286
1293
|
// works for the local user.
|
|
1287
1294
|
return extResolved;
|
|
1288
1295
|
}
|
|
1289
|
-
// ──
|
|
1290
|
-
//
|
|
1291
|
-
// Gemini's hook contract is the closest thing to a Claude Code clone we've
|
|
1292
|
-
// shipped: same `{matcher, hooks: [{type, command, timeout}]}` settings shape,
|
|
1293
|
-
// PascalCase event names, snake_case stdin payload field names (session_id,
|
|
1294
|
-
// tool_name, tool_input, hook_event_name, cwd, transcript_path), subprocess
|
|
1295
|
-
// execution model, and `$CLAUDE_PROJECT_DIR` env-var alias on top of its own
|
|
1296
|
-
// `$GEMINI_PROJECT_DIR`. The integration is structurally identical to
|
|
1297
|
-
// claudeCode below, with three deltas:
|
|
1298
|
-
//
|
|
1299
|
-
// • Settings paths: ~/.gemini/settings.json (user) / <cwd>/.gemini/settings.json (project).
|
|
1300
|
-
// System scope (/etc/gemini-cli/settings.json) is documented but not exposed.
|
|
1301
|
-
//
|
|
1302
|
-
// • Matcher field: each Gemini matcher entry carries an explicit `matcher`
|
|
1303
|
-
// regex (e.g. `"write_file|replace"`). We default to `"*"` so policies fire
|
|
1304
|
-
// on every tool call, mirroring the failproofai default of "every event,
|
|
1305
|
-
// every tool". Users can hand-edit settings.json to scope tighter; we
|
|
1306
|
-
// preserve their `matcher` field across re-installs by NOT replacing
|
|
1307
|
-
// entries that aren't failproofai-marked.
|
|
1308
|
-
//
|
|
1309
|
-
// • Tool name canonicalization happens in handler.ts (snake_case →
|
|
1310
|
-
// PascalCase via GEMINI_TOOL_MAP) so policies match unchanged; not the
|
|
1311
|
-
// install layer's concern.
|
|
1296
|
+
// ── Hermes (hermes-agent) — live hooks (Pillar 1) ────────────────────────────
|
|
1312
1297
|
//
|
|
1313
|
-
//
|
|
1298
|
+
// External-command CLI like codex/cursor, but its config is YAML
|
|
1299
|
+
// (`~/.hermes/config.yaml`) under a `hooks:` map, so the I/O layer uses the yaml
|
|
1300
|
+
// Document API (comment-preserving). Flat per-event arrays like cursor:
|
|
1301
|
+
// `hooks: { pre_tool_call: [ { command, timeout, <marker> } ], … }`. Hermes
|
|
1302
|
+
// reads a `{"decision":"block",…}` JSON response on stdout (see
|
|
1303
|
+
// policy-evaluator.ts); exit codes are ignored. User-scope only.
|
|
1304
|
+
|
|
1305
|
+
/** One hook entry as stored under a `hooks:` event key in config.yaml. */
|
|
1306
|
+
interface HermesHookEntry {
|
|
1307
|
+
command: string;
|
|
1308
|
+
timeout?: number;
|
|
1309
|
+
[key: string]: unknown;
|
|
1310
|
+
}
|
|
1311
|
+
|
|
1312
|
+
export const hermes: Integration = {
|
|
1313
|
+
id: "hermes",
|
|
1314
|
+
displayName: "Hermes",
|
|
1315
|
+
scopes: HERMES_HOOK_SCOPES,
|
|
1316
|
+
eventTypes: HERMES_HOOK_EVENT_TYPES,
|
|
1317
|
+
|
|
1318
|
+
// Hermes config is USER-scope only (`~/.hermes/config.yaml`); there is no
|
|
1319
|
+
// project/local file, so scope/cwd are irrelevant here.
|
|
1320
|
+
getSettingsPath() {
|
|
1321
|
+
return resolve(homedir(), ".hermes", "config.yaml");
|
|
1322
|
+
},
|
|
1323
|
+
|
|
1324
|
+
readSettings(settingsPath) {
|
|
1325
|
+
// Return the yaml Document (cast) so writeHookEntries/writeSettings can
|
|
1326
|
+
// round-trip it while preserving the user's other keys + comments.
|
|
1327
|
+
return readYamlDoc(settingsPath) as unknown as Record<string, unknown>;
|
|
1328
|
+
},
|
|
1329
|
+
|
|
1330
|
+
writeSettings(settingsPath, settings) {
|
|
1331
|
+
writeYamlDoc(settingsPath, settings as unknown as Document);
|
|
1332
|
+
},
|
|
1333
|
+
|
|
1334
|
+
buildHookEntry(binaryPath, eventType, scope) {
|
|
1335
|
+
// No matcher → fires for ALL tools / all platforms (slack/telegram/cli/cron)
|
|
1336
|
+
// and internal subagents. `timeout` is in seconds; Hermes runs the command
|
|
1337
|
+
// via shlex.split (shell=false).
|
|
1338
|
+
const command =
|
|
1339
|
+
scope === "project"
|
|
1340
|
+
? `npx -y failproofai --hook ${eventType} --cli hermes`
|
|
1341
|
+
: `"${binaryPath}" --hook ${eventType} --cli hermes`;
|
|
1342
|
+
return {
|
|
1343
|
+
command,
|
|
1344
|
+
timeout: 30,
|
|
1345
|
+
[FAILPROOFAI_HOOK_MARKER]: true,
|
|
1346
|
+
};
|
|
1347
|
+
},
|
|
1348
|
+
|
|
1349
|
+
isFailproofaiHook: isMarkedHook,
|
|
1350
|
+
|
|
1351
|
+
writeHookEntries(settings, binaryPath, scope) {
|
|
1352
|
+
const doc = settings as unknown as Document;
|
|
1353
|
+
// Read the current hooks map as plain JS, then re-set ONLY the `hooks` key —
|
|
1354
|
+
// preserving comments on every other part of config.yaml.
|
|
1355
|
+
const js = (doc.toJS() ?? {}) as { hooks?: Record<string, HermesHookEntry[]> };
|
|
1356
|
+
const hooks: Record<string, HermesHookEntry[]> =
|
|
1357
|
+
js.hooks && typeof js.hooks === "object" ? js.hooks : {};
|
|
1358
|
+
|
|
1359
|
+
for (const eventType of HERMES_HOOK_EVENT_TYPES) {
|
|
1360
|
+
const entry = this.buildHookEntry(binaryPath, eventType, scope) as unknown as HermesHookEntry;
|
|
1361
|
+
const arr = Array.isArray(hooks[eventType]) ? hooks[eventType] : [];
|
|
1362
|
+
const idx = arr.findIndex((h) => isMarkedHook(h));
|
|
1363
|
+
if (idx >= 0) arr[idx] = entry;
|
|
1364
|
+
else arr.push(entry);
|
|
1365
|
+
hooks[eventType] = arr;
|
|
1366
|
+
}
|
|
1367
|
+
doc.set("hooks", hooks);
|
|
1368
|
+
// The headless gateway has no TTY to answer Hermes's first-use hook-consent
|
|
1369
|
+
// prompt, so auto-accept declared hooks. Tradeoff: also auto-accepts any
|
|
1370
|
+
// other hook the operator adds; a targeted `shell-hooks-allowlist.json`
|
|
1371
|
+
// pre-seed is a future refinement.
|
|
1372
|
+
doc.set("hooks_auto_accept", true);
|
|
1373
|
+
},
|
|
1374
|
+
|
|
1375
|
+
removeHooksFromFile(settingsPath) {
|
|
1376
|
+
if (!existsSync(settingsPath)) return 0;
|
|
1377
|
+
const doc = readYamlDoc(settingsPath);
|
|
1378
|
+
const js = (doc.toJS() ?? {}) as { hooks?: Record<string, HermesHookEntry[]> };
|
|
1379
|
+
const hooks = js.hooks;
|
|
1380
|
+
|
|
1381
|
+
let removed = 0;
|
|
1382
|
+
if (hooks && typeof hooks === "object") {
|
|
1383
|
+
for (const eventType of Object.keys(hooks)) {
|
|
1384
|
+
const entries = hooks[eventType];
|
|
1385
|
+
if (!Array.isArray(entries)) continue;
|
|
1386
|
+
const before = entries.length;
|
|
1387
|
+
const filtered = entries.filter((h) => !isMarkedHook(h));
|
|
1388
|
+
removed += before - filtered.length;
|
|
1389
|
+
if (filtered.length === 0) delete hooks[eventType];
|
|
1390
|
+
else hooks[eventType] = filtered;
|
|
1391
|
+
}
|
|
1392
|
+
if (removed > 0) {
|
|
1393
|
+
if (Object.keys(hooks).length === 0) doc.delete("hooks");
|
|
1394
|
+
else doc.set("hooks", hooks);
|
|
1395
|
+
}
|
|
1396
|
+
}
|
|
1397
|
+
|
|
1398
|
+
// Always drop our headless-consent flag on uninstall — even if the hooks were
|
|
1399
|
+
// already removed manually — so it can't silently auto-accept future operator
|
|
1400
|
+
// hooks. `doc.delete` returns true iff the key was present.
|
|
1401
|
+
const droppedAutoAccept = doc.delete("hooks_auto_accept");
|
|
1402
|
+
if (removed > 0 || droppedAutoAccept) writeYamlDoc(settingsPath, doc);
|
|
1403
|
+
return removed;
|
|
1404
|
+
},
|
|
1405
|
+
|
|
1406
|
+
hooksInstalledInSettings(scope, cwd) {
|
|
1407
|
+
const settingsPath = this.getSettingsPath(scope, cwd);
|
|
1408
|
+
if (!existsSync(settingsPath)) return false;
|
|
1409
|
+
try {
|
|
1410
|
+
const doc = readYamlDoc(settingsPath);
|
|
1411
|
+
const js = (doc.toJS() ?? {}) as { hooks?: Record<string, HermesHookEntry[]> };
|
|
1412
|
+
const hooks = js.hooks;
|
|
1413
|
+
if (!hooks || typeof hooks !== "object") return false;
|
|
1414
|
+
for (const entries of Object.values(hooks)) {
|
|
1415
|
+
if (Array.isArray(entries) && entries.some((h) => isMarkedHook(h))) return true;
|
|
1416
|
+
}
|
|
1417
|
+
} catch {
|
|
1418
|
+
// Corrupt config — treat as not installed.
|
|
1419
|
+
}
|
|
1420
|
+
return false;
|
|
1421
|
+
},
|
|
1422
|
+
|
|
1423
|
+
detectInstalled() {
|
|
1424
|
+
return binaryExists("hermes");
|
|
1425
|
+
},
|
|
1426
|
+
};
|
|
1427
|
+
|
|
1428
|
+
// ── OpenClaw integration ────────────────────────────────────────────────────
|
|
1314
1429
|
//
|
|
1315
|
-
//
|
|
1430
|
+
// OpenClaw is a self-hosted assistant gateway. Enforcement is via its in-process
|
|
1431
|
+
// PLUGIN hooks (file-based "internal hooks" are observation-only), so failproofai
|
|
1432
|
+
// ships a static plugin package (`openclaw-plugin/`, like Pi's pi-extension) that
|
|
1433
|
+
// async-spawns the binary and maps verdicts back. Install registers the plugin in
|
|
1434
|
+
// `~/.openclaw/openclaw.json` (JSON):
|
|
1435
|
+
// • plugins.load.paths[] → the shipped openclaw-plugin dir (absolute path)
|
|
1436
|
+
// • plugins.entries.failproofai = { enabled: true, hooks: { allowConversationAccess: true } }
|
|
1437
|
+
// (allowConversationAccess is required for the raw-conversation hooks
|
|
1438
|
+
// before_agent_run / before_agent_finalize; verified live v2026.7.1).
|
|
1439
|
+
// USER scope only — OpenClaw has no project config (workspace plugins are
|
|
1440
|
+
// disabled by default). We NEVER delete the shipped plugin dir on uninstall
|
|
1441
|
+
// (unlike OpenCode's generated shim) — uninstall only edits openclaw.json.
|
|
1442
|
+
// Detected via the `openclaw` binary on PATH.
|
|
1443
|
+
|
|
1444
|
+
const OPENCLAW_PLUGIN_ID = "failproofai";
|
|
1445
|
+
|
|
1446
|
+
interface OpenClawPluginsSection {
|
|
1447
|
+
load?: { paths?: string[]; [k: string]: unknown };
|
|
1448
|
+
entries?: Record<string, Record<string, unknown>>;
|
|
1449
|
+
allow?: string[];
|
|
1450
|
+
deny?: string[];
|
|
1451
|
+
[k: string]: unknown;
|
|
1452
|
+
}
|
|
1453
|
+
interface OpenClawSettingsFile {
|
|
1454
|
+
plugins?: OpenClawPluginsSection;
|
|
1455
|
+
[k: string]: unknown;
|
|
1456
|
+
}
|
|
1316
1457
|
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1458
|
+
/** Absolute path to the failproofai-shipped OpenClaw plugin package. */
|
|
1459
|
+
function getOpenClawPluginPath(): string {
|
|
1460
|
+
const fromEnv = process.env.FAILPROOFAI_PACKAGE_ROOT;
|
|
1461
|
+
if (fromEnv) return resolve(fromEnv, "openclaw-plugin");
|
|
1462
|
+
return resolve(fileURLToPath(import.meta.url), "..", "..", "..", "openclaw-plugin");
|
|
1320
1463
|
}
|
|
1321
1464
|
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1465
|
+
/** True iff a plugins.load.paths entry was written by failproofai. */
|
|
1466
|
+
function isFailproofaiOpenClawPath(p: unknown): boolean {
|
|
1467
|
+
if (typeof p !== "string") return false;
|
|
1468
|
+
return /(?:^|\/)openclaw-plugin\/?$/.test(p) || (p.includes("openclaw-plugin") && p.includes("failproofai"));
|
|
1325
1469
|
}
|
|
1326
1470
|
|
|
1327
|
-
export const
|
|
1328
|
-
id: "
|
|
1329
|
-
displayName: "
|
|
1330
|
-
scopes:
|
|
1331
|
-
eventTypes:
|
|
1471
|
+
export const openclaw: Integration = {
|
|
1472
|
+
id: "openclaw",
|
|
1473
|
+
displayName: "OpenClaw",
|
|
1474
|
+
scopes: OPENCLAW_HOOK_SCOPES,
|
|
1475
|
+
eventTypes: OPENCLAW_HOOK_EVENT_TYPES,
|
|
1476
|
+
|
|
1477
|
+
// USER scope only (~/.openclaw/openclaw.json); OpenClaw has no project config.
|
|
1478
|
+
getSettingsPath() {
|
|
1479
|
+
return resolve(homedir(), ".openclaw", "openclaw.json");
|
|
1480
|
+
},
|
|
1481
|
+
|
|
1482
|
+
readSettings(settingsPath) {
|
|
1483
|
+
return readJsonFile(settingsPath);
|
|
1484
|
+
},
|
|
1485
|
+
|
|
1486
|
+
writeSettings(settingsPath, settings) {
|
|
1487
|
+
writeJsonFile(settingsPath, settings);
|
|
1488
|
+
},
|
|
1489
|
+
|
|
1490
|
+
buildHookEntry() {
|
|
1491
|
+
// OpenClaw registers plugins at the package level — one registration covers
|
|
1492
|
+
// all hooks (the plugin's index.js wires every api.on(...) handler). This
|
|
1493
|
+
// sentinel satisfies the interface; writeHookEntries does the real work.
|
|
1494
|
+
return {
|
|
1495
|
+
[FAILPROOFAI_HOOK_MARKER]: true,
|
|
1496
|
+
_openclawPluginPath: getOpenClawPluginPath(),
|
|
1497
|
+
};
|
|
1498
|
+
},
|
|
1499
|
+
|
|
1500
|
+
isFailproofaiHook(hook) {
|
|
1501
|
+
if (typeof hook === "string") return isFailproofaiOpenClawPath(hook);
|
|
1502
|
+
if (!hook || typeof hook !== "object") return false;
|
|
1503
|
+
const h = hook as Record<string, unknown>;
|
|
1504
|
+
if (h[FAILPROOFAI_HOOK_MARKER] === true) return true;
|
|
1505
|
+
if (typeof h.source === "string") return isFailproofaiOpenClawPath(h.source);
|
|
1506
|
+
return false;
|
|
1507
|
+
},
|
|
1508
|
+
|
|
1509
|
+
writeHookEntries(settings) {
|
|
1510
|
+
const s = settings as OpenClawSettingsFile;
|
|
1511
|
+
const plugins = (s.plugins ??= {});
|
|
1512
|
+
const load = (plugins.load ??= {});
|
|
1513
|
+
if (!Array.isArray(load.paths)) load.paths = [];
|
|
1514
|
+
const dir = getOpenClawPluginPath();
|
|
1515
|
+
// Idempotent: replace any existing failproofai path, otherwise append.
|
|
1516
|
+
const idx = load.paths.findIndex((p) => isFailproofaiOpenClawPath(p));
|
|
1517
|
+
if (idx >= 0) load.paths[idx] = dir;
|
|
1518
|
+
else load.paths.push(dir);
|
|
1519
|
+
|
|
1520
|
+
// Enable the plugin + grant conversation access (needed for before_agent_run
|
|
1521
|
+
// / before_agent_finalize). Only set our own keys — operator config on this
|
|
1522
|
+
// entry (e.g. hooks.timeouts.<hookName>) and other entries are preserved.
|
|
1523
|
+
const entries = (plugins.entries ??= {});
|
|
1524
|
+
const entry = (entries[OPENCLAW_PLUGIN_ID] ??= {});
|
|
1525
|
+
entry.enabled = true;
|
|
1526
|
+
const hooks = ((entry.hooks as Record<string, unknown>) ??= {});
|
|
1527
|
+
hooks.allowConversationAccess = true;
|
|
1528
|
+
},
|
|
1529
|
+
|
|
1530
|
+
removeHooksFromFile(settingsPath) {
|
|
1531
|
+
if (!existsSync(settingsPath)) return 0;
|
|
1532
|
+
const settings = this.readSettings(settingsPath) as OpenClawSettingsFile;
|
|
1533
|
+
const plugins = settings.plugins;
|
|
1534
|
+
if (!plugins) return 0;
|
|
1535
|
+
let removed = 0;
|
|
1536
|
+
|
|
1537
|
+
if (Array.isArray(plugins.load?.paths)) {
|
|
1538
|
+
const before = plugins.load.paths.length;
|
|
1539
|
+
plugins.load.paths = plugins.load.paths.filter((p) => !isFailproofaiOpenClawPath(p));
|
|
1540
|
+
removed += before - plugins.load.paths.length;
|
|
1541
|
+
if (plugins.load.paths.length === 0) delete plugins.load.paths;
|
|
1542
|
+
if (plugins.load && Object.keys(plugins.load).length === 0) delete plugins.load;
|
|
1543
|
+
}
|
|
1544
|
+
if (plugins.entries && OPENCLAW_PLUGIN_ID in plugins.entries) {
|
|
1545
|
+
delete plugins.entries[OPENCLAW_PLUGIN_ID];
|
|
1546
|
+
removed += 1;
|
|
1547
|
+
if (Object.keys(plugins.entries).length === 0) delete plugins.entries;
|
|
1548
|
+
}
|
|
1549
|
+
// Prune an empty plugins object so uninstall leaves openclaw.json clean.
|
|
1550
|
+
if (Object.keys(plugins).length === 0) delete settings.plugins;
|
|
1551
|
+
|
|
1552
|
+
this.writeSettings(settingsPath, settings as Record<string, unknown>);
|
|
1553
|
+
return removed;
|
|
1554
|
+
},
|
|
1555
|
+
|
|
1556
|
+
hooksInstalledInSettings(scope, cwd) {
|
|
1557
|
+
const settingsPath = this.getSettingsPath(scope, cwd);
|
|
1558
|
+
if (!existsSync(settingsPath)) return false;
|
|
1559
|
+
try {
|
|
1560
|
+
const settings = this.readSettings(settingsPath) as OpenClawSettingsFile;
|
|
1561
|
+
const paths = settings.plugins?.load?.paths;
|
|
1562
|
+
const pathPresent = Array.isArray(paths) && paths.some((p) => isFailproofaiOpenClawPath(p));
|
|
1563
|
+
const entryEnabled = settings.plugins?.entries?.[OPENCLAW_PLUGIN_ID]?.enabled === true;
|
|
1564
|
+
return pathPresent && entryEnabled;
|
|
1565
|
+
} catch {
|
|
1566
|
+
return false;
|
|
1567
|
+
}
|
|
1568
|
+
},
|
|
1569
|
+
|
|
1570
|
+
detectInstalled() {
|
|
1571
|
+
return binaryExists("openclaw");
|
|
1572
|
+
},
|
|
1573
|
+
};
|
|
1574
|
+
|
|
1575
|
+
// ── Factory Droid (droid) integration ───────────────────────────────────────
|
|
1576
|
+
//
|
|
1577
|
+
// Factory's droid CLI uses a Claude-compatible external-command hook system,
|
|
1578
|
+
// but its hooks.json puts event names at the TOP LEVEL — there is NO `"hooks"`
|
|
1579
|
+
// wrapper (the file IS the events object). Verified live against droid
|
|
1580
|
+
// v0.171.0: a `{"hooks":{…}}` wrapper is rejected with `WARN Ignoring unknown
|
|
1581
|
+
// hook event keys keys:["hooks"]`. Tool events (PreToolUse / PostToolUse) carry
|
|
1582
|
+
// `"matcher": "*"`; non-tool events omit the matcher. Deny is exit-2 + stderr
|
|
1583
|
+
// (see policy-evaluator.ts). Event names are already PascalCase, so no event
|
|
1584
|
+
// canonicalization is needed. Settings paths: ~/.factory/hooks.json (user) and
|
|
1585
|
+
// <cwd>/.factory/hooks.json (project); no "local" scope.
|
|
1586
|
+
|
|
1587
|
+
interface FactoryHookMatcher {
|
|
1588
|
+
matcher?: string;
|
|
1589
|
+
hooks: Array<ClaudeHookEntry | Record<string, unknown>>;
|
|
1590
|
+
}
|
|
1591
|
+
|
|
1592
|
+
/** The Factory settings file IS the top-level events map (no wrapper key). */
|
|
1593
|
+
type FactorySettingsFile = Record<string, FactoryHookMatcher[] | unknown>;
|
|
1594
|
+
|
|
1595
|
+
export const factory: Integration = {
|
|
1596
|
+
id: "factory",
|
|
1597
|
+
displayName: "Factory Droid",
|
|
1598
|
+
scopes: FACTORY_HOOK_SCOPES,
|
|
1599
|
+
eventTypes: FACTORY_HOOK_EVENT_TYPES,
|
|
1600
|
+
|
|
1601
|
+
getSettingsPath(scope, cwd) {
|
|
1602
|
+
const base = cwd ? resolve(cwd) : process.cwd();
|
|
1603
|
+
switch (scope) {
|
|
1604
|
+
case "user":
|
|
1605
|
+
return resolve(homedir(), ".factory", "hooks.json");
|
|
1606
|
+
case "project":
|
|
1607
|
+
return resolve(base, ".factory", "hooks.json");
|
|
1608
|
+
case "local":
|
|
1609
|
+
// Factory has no "local" scope; the CLI rejects --cli factory --scope
|
|
1610
|
+
// local before reaching here, but fall back to project so callers don't
|
|
1611
|
+
// crash.
|
|
1612
|
+
return resolve(base, ".factory", "hooks.json");
|
|
1613
|
+
}
|
|
1614
|
+
},
|
|
1615
|
+
|
|
1616
|
+
readSettings(settingsPath) {
|
|
1617
|
+
return readJsonFile(settingsPath);
|
|
1618
|
+
},
|
|
1619
|
+
|
|
1620
|
+
writeSettings(settingsPath, settings) {
|
|
1621
|
+
writeJsonFile(settingsPath, settings);
|
|
1622
|
+
},
|
|
1623
|
+
|
|
1624
|
+
buildHookEntry(binaryPath, eventType, scope) {
|
|
1625
|
+
const command =
|
|
1626
|
+
scope === "project"
|
|
1627
|
+
? `npx -y failproofai --hook ${eventType} --cli factory`
|
|
1628
|
+
: `"${binaryPath}" --hook ${eventType} --cli factory`;
|
|
1629
|
+
return {
|
|
1630
|
+
type: "command",
|
|
1631
|
+
command,
|
|
1632
|
+
// droid reads `timeout` in SECONDS (verified against droid v0.171.0). 30s.
|
|
1633
|
+
timeout: 30,
|
|
1634
|
+
[FAILPROOFAI_HOOK_MARKER]: true,
|
|
1635
|
+
};
|
|
1636
|
+
},
|
|
1637
|
+
|
|
1638
|
+
isFailproofaiHook: isMarkedHook,
|
|
1639
|
+
|
|
1640
|
+
writeHookEntries(settings, binaryPath, scope) {
|
|
1641
|
+
const s = settings as Record<string, FactoryHookMatcher[]>;
|
|
1642
|
+
|
|
1643
|
+
for (const eventType of FACTORY_HOOK_EVENT_TYPES) {
|
|
1644
|
+
const hookEntry = this.buildHookEntry(binaryPath, eventType, scope) as unknown as ClaudeHookEntry;
|
|
1645
|
+
if (!Array.isArray(s[eventType])) s[eventType] = [];
|
|
1646
|
+
const matchers: FactoryHookMatcher[] = s[eventType];
|
|
1647
|
+
|
|
1648
|
+
let found = false;
|
|
1649
|
+
for (const matcher of matchers) {
|
|
1650
|
+
if (!matcher.hooks) continue;
|
|
1651
|
+
const idx = matcher.hooks.findIndex((h) => isMarkedHook(h as Record<string, unknown>));
|
|
1652
|
+
if (idx >= 0) {
|
|
1653
|
+
matcher.hooks[idx] = hookEntry;
|
|
1654
|
+
found = true;
|
|
1655
|
+
break;
|
|
1656
|
+
}
|
|
1657
|
+
}
|
|
1658
|
+
if (!found) {
|
|
1659
|
+
// Tool events match all tools via `matcher: "*"`; non-tool events carry
|
|
1660
|
+
// no matcher (verified live against droid v0.171.0).
|
|
1661
|
+
const isToolEvent = eventType === "PreToolUse" || eventType === "PostToolUse";
|
|
1662
|
+
matchers.push(isToolEvent ? { matcher: "*", hooks: [hookEntry] } : { hooks: [hookEntry] });
|
|
1663
|
+
}
|
|
1664
|
+
}
|
|
1665
|
+
},
|
|
1666
|
+
|
|
1667
|
+
removeHooksFromFile(settingsPath) {
|
|
1668
|
+
const settings = this.readSettings(settingsPath) as FactorySettingsFile;
|
|
1669
|
+
|
|
1670
|
+
let removed = 0;
|
|
1671
|
+
for (const eventType of Object.keys(settings)) {
|
|
1672
|
+
const matchers = settings[eventType];
|
|
1673
|
+
if (!Array.isArray(matchers)) continue;
|
|
1674
|
+
for (let i = matchers.length - 1; i >= 0; i--) {
|
|
1675
|
+
const matcher = matchers[i] as FactoryHookMatcher;
|
|
1676
|
+
if (!matcher || !matcher.hooks) continue;
|
|
1677
|
+
const before = matcher.hooks.length;
|
|
1678
|
+
matcher.hooks = matcher.hooks.filter((h) => !isMarkedHook(h as Record<string, unknown>));
|
|
1679
|
+
removed += before - matcher.hooks.length;
|
|
1680
|
+
if (matcher.hooks.length === 0) matchers.splice(i, 1);
|
|
1681
|
+
}
|
|
1682
|
+
if (matchers.length === 0) delete settings[eventType];
|
|
1683
|
+
}
|
|
1684
|
+
|
|
1685
|
+
this.writeSettings(settingsPath, settings as Record<string, unknown>);
|
|
1686
|
+
return removed;
|
|
1687
|
+
},
|
|
1688
|
+
|
|
1689
|
+
hooksInstalledInSettings(scope, cwd) {
|
|
1690
|
+
const settingsPath = this.getSettingsPath(scope, cwd);
|
|
1691
|
+
if (!existsSync(settingsPath)) return false;
|
|
1692
|
+
try {
|
|
1693
|
+
const settings = this.readSettings(settingsPath) as FactorySettingsFile;
|
|
1694
|
+
for (const matchers of Object.values(settings)) {
|
|
1695
|
+
if (!Array.isArray(matchers)) continue;
|
|
1696
|
+
for (const matcher of matchers as FactoryHookMatcher[]) {
|
|
1697
|
+
if (!matcher || !matcher.hooks) continue;
|
|
1698
|
+
if (matcher.hooks.some((h) => isMarkedHook(h as Record<string, unknown>))) return true;
|
|
1699
|
+
}
|
|
1700
|
+
}
|
|
1701
|
+
} catch {
|
|
1702
|
+
// Corrupt settings — treat as not installed
|
|
1703
|
+
}
|
|
1704
|
+
return false;
|
|
1705
|
+
},
|
|
1706
|
+
|
|
1707
|
+
detectInstalled() {
|
|
1708
|
+
return binaryExists("droid");
|
|
1709
|
+
},
|
|
1710
|
+
};
|
|
1711
|
+
|
|
1712
|
+
// ── Devin CLI (devin) integration ────────────────────────────────────────────
|
|
1713
|
+
//
|
|
1714
|
+
// Devin's CLI (Cognition) is a **pure Claude-clone** external-command hook
|
|
1715
|
+
// system, verified live against devin v3000.1.27. It uses the standard Claude
|
|
1716
|
+
// `"hooks"`-wrapper schema, so this Integration mirrors `claudeCode` verbatim —
|
|
1717
|
+
// only the settings paths and the `--cli devin` command flag differ. The
|
|
1718
|
+
// config file also carries the operator's own keys (`org_id`, `theme_mode`,
|
|
1719
|
+
// …), so readSettings/writeSettings use the merge-preserving JSON helpers.
|
|
1720
|
+
//
|
|
1721
|
+
// Settings paths (verified against devin v3000.1.27):
|
|
1722
|
+
// user → ~/.config/devin/config.json (the `"hooks"` key)
|
|
1723
|
+
// project → <cwd>/.devin/config.json (the `"hooks"` key)
|
|
1724
|
+
// No "local" scope. Event names are already PascalCase (no event map / no
|
|
1725
|
+
// handler branch); the stdin payload is Claude snake_case (no normalization).
|
|
1726
|
+
// Deny/instruct semantics live in policy-evaluator.ts's `cli === "devin"`
|
|
1727
|
+
// branch (`{decision:"block", reason}` on stdout at exit 0).
|
|
1728
|
+
|
|
1729
|
+
export const devin: Integration = {
|
|
1730
|
+
id: "devin",
|
|
1731
|
+
displayName: "Devin CLI",
|
|
1732
|
+
scopes: DEVIN_HOOK_SCOPES,
|
|
1733
|
+
eventTypes: DEVIN_HOOK_EVENT_TYPES,
|
|
1332
1734
|
|
|
1333
1735
|
getSettingsPath(scope, cwd) {
|
|
1334
1736
|
const base = cwd ? resolve(cwd) : process.cwd();
|
|
1335
1737
|
switch (scope) {
|
|
1336
1738
|
case "user":
|
|
1337
|
-
return resolve(homedir(), ".
|
|
1739
|
+
return resolve(homedir(), ".config", "devin", "config.json");
|
|
1338
1740
|
case "project":
|
|
1339
|
-
return resolve(base, ".
|
|
1741
|
+
return resolve(base, ".devin", "config.json");
|
|
1340
1742
|
case "local":
|
|
1341
|
-
//
|
|
1743
|
+
// Devin has no "local" scope; the CLI rejects --cli devin --scope local
|
|
1342
1744
|
// before reaching here, but fall back to project so callers don't crash.
|
|
1343
|
-
return resolve(base, ".
|
|
1745
|
+
return resolve(base, ".devin", "config.json");
|
|
1344
1746
|
}
|
|
1345
1747
|
},
|
|
1346
1748
|
|
|
@@ -1355,12 +1757,13 @@ export const gemini: Integration = {
|
|
|
1355
1757
|
buildHookEntry(binaryPath, eventType, scope) {
|
|
1356
1758
|
const command =
|
|
1357
1759
|
scope === "project"
|
|
1358
|
-
? `npx -y failproofai --hook ${eventType} --cli
|
|
1359
|
-
: `"${binaryPath}" --hook ${eventType} --cli
|
|
1760
|
+
? `npx -y failproofai --hook ${eventType} --cli devin`
|
|
1761
|
+
: `"${binaryPath}" --hook ${eventType} --cli devin`;
|
|
1360
1762
|
return {
|
|
1361
1763
|
type: "command",
|
|
1362
1764
|
command,
|
|
1363
|
-
timeout
|
|
1765
|
+
// Devin reads `timeout` in SECONDS like Claude. 60 = 60s.
|
|
1766
|
+
timeout: 60,
|
|
1364
1767
|
[FAILPROOFAI_HOOK_MARKER]: true,
|
|
1365
1768
|
};
|
|
1366
1769
|
},
|
|
@@ -1368,19 +1771,14 @@ export const gemini: Integration = {
|
|
|
1368
1771
|
isFailproofaiHook: isMarkedHook,
|
|
1369
1772
|
|
|
1370
1773
|
writeHookEntries(settings, binaryPath, scope) {
|
|
1371
|
-
const s = settings as
|
|
1774
|
+
const s = settings as ClaudeSettings;
|
|
1372
1775
|
if (!s.hooks) s.hooks = {};
|
|
1373
1776
|
|
|
1374
|
-
for (const eventType of
|
|
1777
|
+
for (const eventType of DEVIN_HOOK_EVENT_TYPES) {
|
|
1375
1778
|
const hookEntry = this.buildHookEntry(binaryPath, eventType, scope) as unknown as ClaudeHookEntry;
|
|
1376
1779
|
if (!s.hooks[eventType]) s.hooks[eventType] = [];
|
|
1377
|
-
const matchers:
|
|
1780
|
+
const matchers: ClaudeHookMatcher[] = s.hooks[eventType];
|
|
1378
1781
|
|
|
1379
|
-
// Idempotent: replace an existing failproofai-marked entry inside our
|
|
1380
|
-
// own matcher; otherwise append a new `{matcher: "*", hooks: [...]}`.
|
|
1381
|
-
// Hand-written matchers (with their own `matcher` regex) are never
|
|
1382
|
-
// touched — we identify our matcher by checking whether ANY of its
|
|
1383
|
-
// inner hooks are failproofai-marked.
|
|
1384
1782
|
let found = false;
|
|
1385
1783
|
for (const matcher of matchers) {
|
|
1386
1784
|
if (!matcher.hooks) continue;
|
|
@@ -1391,12 +1789,12 @@ export const gemini: Integration = {
|
|
|
1391
1789
|
break;
|
|
1392
1790
|
}
|
|
1393
1791
|
}
|
|
1394
|
-
if (!found) matchers.push({
|
|
1792
|
+
if (!found) matchers.push({ hooks: [hookEntry] });
|
|
1395
1793
|
}
|
|
1396
1794
|
},
|
|
1397
1795
|
|
|
1398
1796
|
removeHooksFromFile(settingsPath) {
|
|
1399
|
-
const settings = this.readSettings(settingsPath) as
|
|
1797
|
+
const settings = this.readSettings(settingsPath) as ClaudeSettings;
|
|
1400
1798
|
if (!settings.hooks) return 0;
|
|
1401
1799
|
|
|
1402
1800
|
let removed = 0;
|
|
@@ -1423,7 +1821,7 @@ export const gemini: Integration = {
|
|
|
1423
1821
|
const settingsPath = this.getSettingsPath(scope, cwd);
|
|
1424
1822
|
if (!existsSync(settingsPath)) return false;
|
|
1425
1823
|
try {
|
|
1426
|
-
const settings = this.readSettings(settingsPath) as
|
|
1824
|
+
const settings = this.readSettings(settingsPath) as ClaudeSettings;
|
|
1427
1825
|
if (!settings.hooks) return false;
|
|
1428
1826
|
for (const matchers of Object.values(settings.hooks)) {
|
|
1429
1827
|
if (!Array.isArray(matchers)) continue;
|
|
@@ -1439,58 +1837,77 @@ export const gemini: Integration = {
|
|
|
1439
1837
|
},
|
|
1440
1838
|
|
|
1441
1839
|
detectInstalled() {
|
|
1442
|
-
return binaryExists("
|
|
1840
|
+
return binaryExists("devin");
|
|
1443
1841
|
},
|
|
1444
1842
|
};
|
|
1445
1843
|
|
|
1446
|
-
// ──
|
|
1844
|
+
// ── Antigravity CLI (antigravity) integration ────────────────────────────────
|
|
1447
1845
|
//
|
|
1448
|
-
//
|
|
1449
|
-
//
|
|
1450
|
-
//
|
|
1451
|
-
//
|
|
1452
|
-
//
|
|
1453
|
-
//
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1846
|
+
// Antigravity's `agy` CLI uses a NAMED-hook schema: the hooks.json top-level key
|
|
1847
|
+
// is a hook *name* ("failproofai"), whose value is an event→handlers map. Tool
|
|
1848
|
+
// events (PreToolUse / PostToolUse) wrap handlers in `{matcher, hooks:[…]}`;
|
|
1849
|
+
// non-tool events (PreInvocation / Stop) are FLAT arrays of handler objects.
|
|
1850
|
+
// Verified live against agy v1.1.2. Settings paths:
|
|
1851
|
+
// user → ~/.gemini/config/hooks.json
|
|
1852
|
+
// project → <cwd>/.agents/hooks.json
|
|
1853
|
+
// No "local" scope. Deny/instruct semantics live in policy-evaluator.ts's
|
|
1854
|
+
// `cli === "antigravity"` branch (Antigravity's OWN `{decision:"deny"}` /
|
|
1855
|
+
// `{decision:"continue"}` / `{injectSteps}` shapes — NOT Claude's).
|
|
1856
|
+
|
|
1857
|
+
/** The Antigravity settings file is `{ "<hookName>": { <event>: … } }`. Our
|
|
1858
|
+
* named hook is "failproofai". */
|
|
1859
|
+
const ANTIGRAVITY_HOOK_NAME = "failproofai";
|
|
1860
|
+
|
|
1861
|
+
interface AntigravityToolMatcher {
|
|
1862
|
+
matcher?: string;
|
|
1863
|
+
hooks: Array<ClaudeHookEntry | Record<string, unknown>>;
|
|
1460
1864
|
}
|
|
1865
|
+
/** An Antigravity named-hook body: tool events → `{matcher, hooks}[]`; flat
|
|
1866
|
+
* events (PreInvocation / Stop) → `handler[]`. */
|
|
1867
|
+
type AntigravityNamedHook = Record<
|
|
1868
|
+
string,
|
|
1869
|
+
AntigravityToolMatcher[] | Array<ClaudeHookEntry | Record<string, unknown>>
|
|
1870
|
+
>;
|
|
1461
1871
|
|
|
1462
|
-
|
|
1463
|
-
id: "hermes",
|
|
1464
|
-
displayName: "Hermes",
|
|
1465
|
-
scopes: HERMES_HOOK_SCOPES,
|
|
1466
|
-
eventTypes: HERMES_HOOK_EVENT_TYPES,
|
|
1872
|
+
const ANTIGRAVITY_TOOL_EVENTS = new Set(["PreToolUse", "PostToolUse"]);
|
|
1467
1873
|
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
|
|
1874
|
+
export const antigravity: Integration = {
|
|
1875
|
+
id: "antigravity",
|
|
1876
|
+
displayName: "Antigravity CLI",
|
|
1877
|
+
scopes: ANTIGRAVITY_HOOK_SCOPES,
|
|
1878
|
+
eventTypes: ANTIGRAVITY_HOOK_EVENT_TYPES,
|
|
1879
|
+
|
|
1880
|
+
getSettingsPath(scope, cwd) {
|
|
1881
|
+
const base = cwd ? resolve(cwd) : process.cwd();
|
|
1882
|
+
switch (scope) {
|
|
1883
|
+
case "user":
|
|
1884
|
+
return resolve(homedir(), ".gemini", "config", "hooks.json");
|
|
1885
|
+
case "project":
|
|
1886
|
+
return resolve(base, ".agents", "hooks.json");
|
|
1887
|
+
case "local":
|
|
1888
|
+
// Antigravity has no "local" scope; the CLI rejects it before reaching
|
|
1889
|
+
// here, but fall back to project so callers don't crash.
|
|
1890
|
+
return resolve(base, ".agents", "hooks.json");
|
|
1891
|
+
}
|
|
1472
1892
|
},
|
|
1473
1893
|
|
|
1474
1894
|
readSettings(settingsPath) {
|
|
1475
|
-
|
|
1476
|
-
// round-trip it while preserving the user's other keys + comments.
|
|
1477
|
-
return readYamlDoc(settingsPath) as unknown as Record<string, unknown>;
|
|
1895
|
+
return readJsonFile(settingsPath);
|
|
1478
1896
|
},
|
|
1479
1897
|
|
|
1480
1898
|
writeSettings(settingsPath, settings) {
|
|
1481
|
-
|
|
1899
|
+
writeJsonFile(settingsPath, settings);
|
|
1482
1900
|
},
|
|
1483
1901
|
|
|
1484
1902
|
buildHookEntry(binaryPath, eventType, scope) {
|
|
1485
|
-
// No matcher → fires for ALL tools / all platforms (slack/telegram/cli/cron)
|
|
1486
|
-
// and internal subagents. `timeout` is in seconds; Hermes runs the command
|
|
1487
|
-
// via shlex.split (shell=false).
|
|
1488
1903
|
const command =
|
|
1489
1904
|
scope === "project"
|
|
1490
|
-
? `npx -y failproofai --hook ${eventType} --cli
|
|
1491
|
-
: `"${binaryPath}" --hook ${eventType} --cli
|
|
1905
|
+
? `npx -y failproofai --hook ${eventType} --cli antigravity`
|
|
1906
|
+
: `"${binaryPath}" --hook ${eventType} --cli antigravity`;
|
|
1492
1907
|
return {
|
|
1908
|
+
type: "command",
|
|
1493
1909
|
command,
|
|
1910
|
+
// Antigravity reads `timeout` in SECONDS (verified agy v1.1.2). 30s.
|
|
1494
1911
|
timeout: 30,
|
|
1495
1912
|
[FAILPROOFAI_HOOK_MARKER]: true,
|
|
1496
1913
|
};
|
|
@@ -1499,57 +1916,235 @@ export const hermes: Integration = {
|
|
|
1499
1916
|
isFailproofaiHook: isMarkedHook,
|
|
1500
1917
|
|
|
1501
1918
|
writeHookEntries(settings, binaryPath, scope) {
|
|
1502
|
-
const
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
const
|
|
1507
|
-
js.hooks && typeof js.hooks === "object" ? js.hooks : {};
|
|
1919
|
+
const s = settings as Record<string, unknown>;
|
|
1920
|
+
if (!s[ANTIGRAVITY_HOOK_NAME] || typeof s[ANTIGRAVITY_HOOK_NAME] !== "object") {
|
|
1921
|
+
s[ANTIGRAVITY_HOOK_NAME] = {};
|
|
1922
|
+
}
|
|
1923
|
+
const named = s[ANTIGRAVITY_HOOK_NAME] as AntigravityNamedHook;
|
|
1508
1924
|
|
|
1509
|
-
for (const eventType of
|
|
1510
|
-
const
|
|
1511
|
-
const
|
|
1512
|
-
|
|
1513
|
-
if (
|
|
1514
|
-
|
|
1515
|
-
|
|
1925
|
+
for (const eventType of ANTIGRAVITY_HOOK_EVENT_TYPES) {
|
|
1926
|
+
const hookEntry = this.buildHookEntry(binaryPath, eventType, scope) as unknown as ClaudeHookEntry;
|
|
1927
|
+
const isToolEvent = ANTIGRAVITY_TOOL_EVENTS.has(eventType);
|
|
1928
|
+
|
|
1929
|
+
if (isToolEvent) {
|
|
1930
|
+
if (!Array.isArray(named[eventType])) named[eventType] = [] as AntigravityToolMatcher[];
|
|
1931
|
+
const matchers = named[eventType] as AntigravityToolMatcher[];
|
|
1932
|
+
let found = false;
|
|
1933
|
+
for (const matcher of matchers) {
|
|
1934
|
+
if (!matcher.hooks) continue;
|
|
1935
|
+
const idx = matcher.hooks.findIndex((h) => isMarkedHook(h as Record<string, unknown>));
|
|
1936
|
+
if (idx >= 0) {
|
|
1937
|
+
matcher.hooks[idx] = hookEntry;
|
|
1938
|
+
found = true;
|
|
1939
|
+
break;
|
|
1940
|
+
}
|
|
1941
|
+
}
|
|
1942
|
+
if (!found) matchers.push({ matcher: "*", hooks: [hookEntry] });
|
|
1943
|
+
} else {
|
|
1944
|
+
// Flat array of handler objects (PreInvocation / Stop).
|
|
1945
|
+
if (!Array.isArray(named[eventType])) named[eventType] = [] as Array<ClaudeHookEntry | Record<string, unknown>>;
|
|
1946
|
+
const handlers = named[eventType] as Array<ClaudeHookEntry | Record<string, unknown>>;
|
|
1947
|
+
const idx = handlers.findIndex((h) => isMarkedHook(h as Record<string, unknown>));
|
|
1948
|
+
if (idx >= 0) handlers[idx] = hookEntry;
|
|
1949
|
+
else handlers.push(hookEntry);
|
|
1950
|
+
}
|
|
1516
1951
|
}
|
|
1517
|
-
doc.set("hooks", hooks);
|
|
1518
|
-
// The headless gateway has no TTY to answer Hermes's first-use hook-consent
|
|
1519
|
-
// prompt, so auto-accept declared hooks. Tradeoff: also auto-accepts any
|
|
1520
|
-
// other hook the operator adds; a targeted `shell-hooks-allowlist.json`
|
|
1521
|
-
// pre-seed is a future refinement.
|
|
1522
|
-
doc.set("hooks_auto_accept", true);
|
|
1523
1952
|
},
|
|
1524
1953
|
|
|
1525
1954
|
removeHooksFromFile(settingsPath) {
|
|
1526
|
-
|
|
1527
|
-
const
|
|
1528
|
-
|
|
1529
|
-
|
|
1955
|
+
const settings = this.readSettings(settingsPath) as Record<string, unknown>;
|
|
1956
|
+
const named = settings[ANTIGRAVITY_HOOK_NAME];
|
|
1957
|
+
if (!named || typeof named !== "object") {
|
|
1958
|
+
this.writeSettings(settingsPath, settings);
|
|
1959
|
+
return 0;
|
|
1960
|
+
}
|
|
1961
|
+
const namedHook = named as AntigravityNamedHook;
|
|
1530
1962
|
|
|
1531
1963
|
let removed = 0;
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
const
|
|
1537
|
-
|
|
1964
|
+
for (const eventType of Object.keys(namedHook)) {
|
|
1965
|
+
const value = namedHook[eventType];
|
|
1966
|
+
if (!Array.isArray(value)) continue;
|
|
1967
|
+
if (ANTIGRAVITY_TOOL_EVENTS.has(eventType)) {
|
|
1968
|
+
const matchers = value as AntigravityToolMatcher[];
|
|
1969
|
+
for (let i = matchers.length - 1; i >= 0; i--) {
|
|
1970
|
+
const matcher = matchers[i];
|
|
1971
|
+
if (!matcher || !matcher.hooks) continue;
|
|
1972
|
+
const before = matcher.hooks.length;
|
|
1973
|
+
matcher.hooks = matcher.hooks.filter((h) => !isMarkedHook(h as Record<string, unknown>));
|
|
1974
|
+
removed += before - matcher.hooks.length;
|
|
1975
|
+
if (matcher.hooks.length === 0) matchers.splice(i, 1);
|
|
1976
|
+
}
|
|
1977
|
+
if (matchers.length === 0) delete namedHook[eventType];
|
|
1978
|
+
} else {
|
|
1979
|
+
const handlers = value as Array<Record<string, unknown>>;
|
|
1980
|
+
const before = handlers.length;
|
|
1981
|
+
const filtered = handlers.filter((h) => !isMarkedHook(h));
|
|
1538
1982
|
removed += before - filtered.length;
|
|
1539
|
-
if (filtered.length === 0) delete
|
|
1540
|
-
else
|
|
1983
|
+
if (filtered.length === 0) delete namedHook[eventType];
|
|
1984
|
+
else namedHook[eventType] = filtered as Array<ClaudeHookEntry | Record<string, unknown>>;
|
|
1541
1985
|
}
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1986
|
+
}
|
|
1987
|
+
// Drop the named hook entirely if it has no remaining events.
|
|
1988
|
+
if (Object.keys(namedHook).length === 0) delete settings[ANTIGRAVITY_HOOK_NAME];
|
|
1989
|
+
|
|
1990
|
+
this.writeSettings(settingsPath, settings);
|
|
1991
|
+
return removed;
|
|
1992
|
+
},
|
|
1993
|
+
|
|
1994
|
+
hooksInstalledInSettings(scope, cwd) {
|
|
1995
|
+
const settingsPath = this.getSettingsPath(scope, cwd);
|
|
1996
|
+
if (!existsSync(settingsPath)) return false;
|
|
1997
|
+
try {
|
|
1998
|
+
const settings = this.readSettings(settingsPath) as Record<string, unknown>;
|
|
1999
|
+
const named = settings[ANTIGRAVITY_HOOK_NAME];
|
|
2000
|
+
if (!named || typeof named !== "object") return false;
|
|
2001
|
+
for (const value of Object.values(named as AntigravityNamedHook)) {
|
|
2002
|
+
if (!Array.isArray(value)) continue;
|
|
2003
|
+
for (const item of value) {
|
|
2004
|
+
if (isMarkedHook(item as Record<string, unknown>)) return true;
|
|
2005
|
+
const matcher = item as AntigravityToolMatcher;
|
|
2006
|
+
if (matcher && Array.isArray(matcher.hooks) && matcher.hooks.some((h) => isMarkedHook(h as Record<string, unknown>))) {
|
|
2007
|
+
return true;
|
|
2008
|
+
}
|
|
2009
|
+
}
|
|
1545
2010
|
}
|
|
2011
|
+
} catch {
|
|
2012
|
+
// Corrupt settings — treat as not installed
|
|
1546
2013
|
}
|
|
2014
|
+
return false;
|
|
2015
|
+
},
|
|
1547
2016
|
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
2017
|
+
detectInstalled() {
|
|
2018
|
+
return binaryExists("agy");
|
|
2019
|
+
},
|
|
2020
|
+
};
|
|
2021
|
+
|
|
2022
|
+
// ── Goose (codename goose, Block) integration ────────────────────────────────
|
|
2023
|
+
//
|
|
2024
|
+
// Goose's "hooks" system follows the cross-agent Open Plugins spec: a plugin
|
|
2025
|
+
// directory whose hooks/hooks.json wires shell commands into agent events.
|
|
2026
|
+
// failproofai owns the entire `failproofai` plugin dir and writes an Open
|
|
2027
|
+
// Plugins hooks.json — `{"hooks": {<Event>: [{"hooks": [{type, command}]}]}}`.
|
|
2028
|
+
// Two differences from Factory (verified live against goose v1.43.0):
|
|
2029
|
+
// 1. the schema DOES carry the top-level "hooks" wrapper (like Claude), and
|
|
2030
|
+
// 2. the matcher is OMITTED on every event — a bare "*" is an invalid regex
|
|
2031
|
+
// that matches NOTHING (omitted = match all tools).
|
|
2032
|
+
// Entries are the clean `{type, command}` shape with NO marker field — Goose
|
|
2033
|
+
// parses this file, so we identify our hooks by the `--cli goose` command
|
|
2034
|
+
// substring instead of injecting `__failproofai_hook__`. Goose auto-discovers
|
|
2035
|
+
// the dir at startup (no config edit) and self-registers it into
|
|
2036
|
+
// ~/.config/goose/config.yaml; uninstall clears our entries (the emptied
|
|
2037
|
+
// hooks.json and the auto-added config `plugins:` entry are left for Goose to
|
|
2038
|
+
// reconcile on next start — it logs the empty plugin and continues, harmless).
|
|
2039
|
+
// Deny is PreToolUse-only JSON (see policy-evaluator.ts). Settings paths:
|
|
2040
|
+
// ~/.agents/plugins/failproofai/hooks/hooks.json (user) and
|
|
2041
|
+
// <cwd>/.agents/plugins/failproofai/hooks/hooks.json (project); no "local" scope.
|
|
2042
|
+
|
|
2043
|
+
interface GooseHookMatcher {
|
|
2044
|
+
matcher?: string;
|
|
2045
|
+
hooks: Array<Record<string, unknown>>;
|
|
2046
|
+
}
|
|
2047
|
+
/** The Open Plugins hooks file: { "hooks": { <Event>: GooseHookMatcher[] } }. */
|
|
2048
|
+
interface GooseHooksFile {
|
|
2049
|
+
hooks?: Record<string, GooseHookMatcher[]>;
|
|
2050
|
+
[key: string]: unknown;
|
|
2051
|
+
}
|
|
2052
|
+
|
|
2053
|
+
/** failproofai owns the `failproofai` Goose plugin dir; identify our hook
|
|
2054
|
+
* entries by the `--cli goose` command substring (the file is Goose-parsed, so
|
|
2055
|
+
* entries stay the clean {type, command} shape with no marker field). */
|
|
2056
|
+
function isGooseFailproofaiHook(hook: unknown): boolean {
|
|
2057
|
+
if (!hook || typeof hook !== "object") return false;
|
|
2058
|
+
const cmd = (hook as { command?: unknown }).command;
|
|
2059
|
+
return typeof cmd === "string" && cmd.includes("failproofai") && cmd.includes("--cli goose");
|
|
2060
|
+
}
|
|
2061
|
+
|
|
2062
|
+
export const goose: Integration = {
|
|
2063
|
+
id: "goose",
|
|
2064
|
+
displayName: "Goose",
|
|
2065
|
+
scopes: GOOSE_HOOK_SCOPES,
|
|
2066
|
+
eventTypes: GOOSE_HOOK_EVENT_TYPES,
|
|
2067
|
+
|
|
2068
|
+
getSettingsPath(scope, cwd) {
|
|
2069
|
+
const base = cwd ? resolve(cwd) : process.cwd();
|
|
2070
|
+
switch (scope) {
|
|
2071
|
+
case "user":
|
|
2072
|
+
return resolve(homedir(), ".agents", "plugins", "failproofai", "hooks", "hooks.json");
|
|
2073
|
+
case "project":
|
|
2074
|
+
return resolve(base, ".agents", "plugins", "failproofai", "hooks", "hooks.json");
|
|
2075
|
+
case "local":
|
|
2076
|
+
// Goose has no "local" scope; the CLI rejects --scope local before
|
|
2077
|
+
// reaching here, but fall back to project so callers don't crash.
|
|
2078
|
+
return resolve(base, ".agents", "plugins", "failproofai", "hooks", "hooks.json");
|
|
2079
|
+
}
|
|
2080
|
+
},
|
|
2081
|
+
|
|
2082
|
+
readSettings(settingsPath) {
|
|
2083
|
+
return readJsonFile(settingsPath);
|
|
2084
|
+
},
|
|
2085
|
+
|
|
2086
|
+
writeSettings(settingsPath, settings) {
|
|
2087
|
+
writeJsonFile(settingsPath, settings);
|
|
2088
|
+
},
|
|
2089
|
+
|
|
2090
|
+
buildHookEntry(binaryPath, eventType, scope) {
|
|
2091
|
+
const command =
|
|
2092
|
+
scope === "project"
|
|
2093
|
+
? `npx -y failproofai --hook ${eventType} --cli goose`
|
|
2094
|
+
: `"${binaryPath}" --hook ${eventType} --cli goose`;
|
|
2095
|
+
// Open Plugins command entry: { type, command } only (Goose applies its own
|
|
2096
|
+
// timeout; no marker field — see isGooseFailproofaiHook).
|
|
2097
|
+
return { type: "command", command };
|
|
2098
|
+
},
|
|
2099
|
+
|
|
2100
|
+
isFailproofaiHook: isGooseFailproofaiHook,
|
|
2101
|
+
|
|
2102
|
+
writeHookEntries(settings, binaryPath, scope) {
|
|
2103
|
+
const s = settings as GooseHooksFile;
|
|
2104
|
+
if (!s.hooks) s.hooks = {};
|
|
2105
|
+
|
|
2106
|
+
for (const eventType of GOOSE_HOOK_EVENT_TYPES) {
|
|
2107
|
+
const hookEntry = this.buildHookEntry(binaryPath, eventType, scope);
|
|
2108
|
+
if (!Array.isArray(s.hooks[eventType])) s.hooks[eventType] = [];
|
|
2109
|
+
const matchers: GooseHookMatcher[] = s.hooks[eventType];
|
|
2110
|
+
|
|
2111
|
+
let found = false;
|
|
2112
|
+
for (const matcher of matchers) {
|
|
2113
|
+
if (!matcher.hooks) continue;
|
|
2114
|
+
const idx = matcher.hooks.findIndex((h) => isGooseFailproofaiHook(h));
|
|
2115
|
+
if (idx >= 0) {
|
|
2116
|
+
matcher.hooks[idx] = hookEntry;
|
|
2117
|
+
found = true;
|
|
2118
|
+
break;
|
|
2119
|
+
}
|
|
2120
|
+
}
|
|
2121
|
+
// matcher OMITTED on every event (a bare "*" matches nothing; omitted =
|
|
2122
|
+
// match all tools — verified live against goose v1.43.0).
|
|
2123
|
+
if (!found) matchers.push({ hooks: [hookEntry] });
|
|
2124
|
+
}
|
|
2125
|
+
},
|
|
2126
|
+
|
|
2127
|
+
removeHooksFromFile(settingsPath) {
|
|
2128
|
+
const settings = this.readSettings(settingsPath) as GooseHooksFile;
|
|
2129
|
+
if (!settings.hooks) return 0;
|
|
2130
|
+
|
|
2131
|
+
let removed = 0;
|
|
2132
|
+
for (const eventType of Object.keys(settings.hooks)) {
|
|
2133
|
+
const matchers = settings.hooks[eventType];
|
|
2134
|
+
if (!Array.isArray(matchers)) continue;
|
|
2135
|
+
for (let i = matchers.length - 1; i >= 0; i--) {
|
|
2136
|
+
const matcher = matchers[i];
|
|
2137
|
+
if (!matcher || !matcher.hooks) continue;
|
|
2138
|
+
const before = matcher.hooks.length;
|
|
2139
|
+
matcher.hooks = matcher.hooks.filter((h) => !isGooseFailproofaiHook(h));
|
|
2140
|
+
removed += before - matcher.hooks.length;
|
|
2141
|
+
if (matcher.hooks.length === 0) matchers.splice(i, 1);
|
|
2142
|
+
}
|
|
2143
|
+
if (matchers.length === 0) delete settings.hooks[eventType];
|
|
2144
|
+
}
|
|
2145
|
+
if (Object.keys(settings.hooks).length === 0) delete settings.hooks;
|
|
2146
|
+
|
|
2147
|
+
this.writeSettings(settingsPath, settings as Record<string, unknown>);
|
|
1553
2148
|
return removed;
|
|
1554
2149
|
},
|
|
1555
2150
|
|
|
@@ -1557,21 +2152,23 @@ export const hermes: Integration = {
|
|
|
1557
2152
|
const settingsPath = this.getSettingsPath(scope, cwd);
|
|
1558
2153
|
if (!existsSync(settingsPath)) return false;
|
|
1559
2154
|
try {
|
|
1560
|
-
const
|
|
1561
|
-
|
|
1562
|
-
const
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
2155
|
+
const settings = this.readSettings(settingsPath) as GooseHooksFile;
|
|
2156
|
+
if (!settings.hooks) return false;
|
|
2157
|
+
for (const matchers of Object.values(settings.hooks)) {
|
|
2158
|
+
if (!Array.isArray(matchers)) continue;
|
|
2159
|
+
for (const matcher of matchers) {
|
|
2160
|
+
if (!matcher || !matcher.hooks) continue;
|
|
2161
|
+
if (matcher.hooks.some((h) => isGooseFailproofaiHook(h))) return true;
|
|
2162
|
+
}
|
|
1566
2163
|
}
|
|
1567
2164
|
} catch {
|
|
1568
|
-
// Corrupt
|
|
2165
|
+
// Corrupt settings — treat as not installed
|
|
1569
2166
|
}
|
|
1570
2167
|
return false;
|
|
1571
2168
|
},
|
|
1572
2169
|
|
|
1573
2170
|
detectInstalled() {
|
|
1574
|
-
return binaryExists("
|
|
2171
|
+
return binaryExists("goose");
|
|
1575
2172
|
},
|
|
1576
2173
|
};
|
|
1577
2174
|
|
|
@@ -1589,8 +2186,12 @@ const INTEGRATIONS: Partial<Record<IntegrationType, Integration>> = {
|
|
|
1589
2186
|
cursor,
|
|
1590
2187
|
opencode,
|
|
1591
2188
|
pi,
|
|
1592
|
-
gemini,
|
|
1593
2189
|
hermes,
|
|
2190
|
+
openclaw,
|
|
2191
|
+
factory,
|
|
2192
|
+
devin,
|
|
2193
|
+
antigravity,
|
|
2194
|
+
goose,
|
|
1594
2195
|
};
|
|
1595
2196
|
|
|
1596
2197
|
export function getIntegration(id: IntegrationType): Integration {
|