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
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* failproofai policy bridge for OpenClaw (openclaw gateway).
|
|
3
|
+
*
|
|
4
|
+
* Loaded in-process by the gateway (registered via `plugins.load.paths` +
|
|
5
|
+
* `plugins.entries.failproofai` in ~/.openclaw/openclaw.json). It subscribes to
|
|
6
|
+
* OpenClaw's typed PLUGIN hooks — the only surface that can block; OpenClaw's
|
|
7
|
+
* file-based "internal hooks" are observation-only — and forwards each to the
|
|
8
|
+
* failproofai binary as `failproofai --hook <event> --cli openclaw`. failproofai
|
|
9
|
+
* prints a flat `{permission, reason}` verdict on stdout; this shim maps it to
|
|
10
|
+
* each hook's native return shape.
|
|
11
|
+
*
|
|
12
|
+
* Marker comment for failproofai's installer detection (do not remove):
|
|
13
|
+
* __failproofai_hook__: true
|
|
14
|
+
*
|
|
15
|
+
* Design notes (verified live against openclaw v2026.7.1):
|
|
16
|
+
* • ASYNC spawn, never spawnSync — the gateway is a long-running multi-channel
|
|
17
|
+
* process; a synchronous spawn on every hook would stall every channel.
|
|
18
|
+
* 30s guard timer; fail-open on any error (never block on infra failure).
|
|
19
|
+
* • Raw forwarding — we send the raw tool name / params and a Claude-shaped
|
|
20
|
+
* stdin (params→tool_input, toolName→tool_name, transcriptPath→
|
|
21
|
+
* transcript_path, stopHookActive→stop_hook_active, sessionKey→session_id).
|
|
22
|
+
* The binary canonicalizes via OPENCLAW_* maps (single source of truth); the
|
|
23
|
+
* shim ships NO inline maps (unlike the OpenCode/Pi shims).
|
|
24
|
+
* • No top-level `await` — OpenClaw's plugin loader rejects it; all async work
|
|
25
|
+
* happens inside register()'s handlers.
|
|
26
|
+
*
|
|
27
|
+
* Binary resolution mirrors pi-extension/index.ts: prefer the bundled
|
|
28
|
+
* dist/cli.mjs (node-compatible), fall back to bin/failproofai.mjs with bun for
|
|
29
|
+
* dev; honor FAILPROOFAI_BINARY_OVERRIDE. Paths resolve relative to this file
|
|
30
|
+
* via import.meta.url (the gateway spawns plugins with an undefined cwd).
|
|
31
|
+
*/
|
|
32
|
+
import { definePluginEntry } from "openclaw/plugin-sdk/plugin-entry";
|
|
33
|
+
import { spawn } from "node:child_process";
|
|
34
|
+
import { existsSync } from "node:fs";
|
|
35
|
+
import { resolve, dirname } from "node:path";
|
|
36
|
+
import { fileURLToPath } from "node:url";
|
|
37
|
+
|
|
38
|
+
const HERE = dirname(fileURLToPath(import.meta.url));
|
|
39
|
+
const DIST_BIN = resolve(HERE, "..", "dist", "cli.mjs");
|
|
40
|
+
const SRC_BIN = resolve(HERE, "..", "bin", "failproofai.mjs");
|
|
41
|
+
|
|
42
|
+
function resolveSpawn() {
|
|
43
|
+
if (process.env.FAILPROOFAI_BINARY_OVERRIDE) {
|
|
44
|
+
return { cmd: "node", args: [process.env.FAILPROOFAI_BINARY_OVERRIDE] };
|
|
45
|
+
}
|
|
46
|
+
if (existsSync(DIST_BIN)) {
|
|
47
|
+
return { cmd: "node", args: [DIST_BIN] };
|
|
48
|
+
}
|
|
49
|
+
return { cmd: "bun", args: [SRC_BIN] };
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function debug(msg) {
|
|
53
|
+
if (process.env.FAILPROOFAI_OPENCLAW_DEBUG === "1") {
|
|
54
|
+
process.stderr.write(`[failproofai-openclaw-shim] ${msg}\n`);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Spawn `failproofai --hook <rawEvent> --cli openclaw`, write the JSON payload
|
|
60
|
+
* to stdin, and resolve the parsed flat `{permission, reason}` verdict. Async +
|
|
61
|
+
* fail-open: any spawn/parse error or timeout resolves to `{permission:"allow"}`
|
|
62
|
+
* so a failproofai fault never blocks the gateway.
|
|
63
|
+
*/
|
|
64
|
+
function callPolicy(rawEvent, payload) {
|
|
65
|
+
return new Promise((done) => {
|
|
66
|
+
const { cmd, args } = resolveSpawn();
|
|
67
|
+
let child;
|
|
68
|
+
try {
|
|
69
|
+
child = spawn(cmd, [...args, "--hook", rawEvent, "--cli", "openclaw"], {
|
|
70
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
71
|
+
});
|
|
72
|
+
} catch (err) {
|
|
73
|
+
debug(`spawn threw: ${err && err.message ? err.message : String(err)}`);
|
|
74
|
+
done({ permission: "allow" });
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
let out = "";
|
|
78
|
+
let settled = false;
|
|
79
|
+
const finish = (verdict) => {
|
|
80
|
+
if (settled) return;
|
|
81
|
+
settled = true;
|
|
82
|
+
clearTimeout(timer);
|
|
83
|
+
done(verdict);
|
|
84
|
+
};
|
|
85
|
+
const timer = setTimeout(() => {
|
|
86
|
+
debug(`timeout on ${rawEvent}; killing child`);
|
|
87
|
+
try { child.kill(); } catch { /* ignore */ }
|
|
88
|
+
finish({ permission: "allow" });
|
|
89
|
+
}, 30_000);
|
|
90
|
+
child.stdout.on("data", (d) => { out += d; });
|
|
91
|
+
child.on("error", (err) => {
|
|
92
|
+
debug(`child error: ${err && err.message ? err.message : String(err)}`);
|
|
93
|
+
finish({ permission: "allow" });
|
|
94
|
+
});
|
|
95
|
+
child.on("close", () => {
|
|
96
|
+
const stdout = out.trim();
|
|
97
|
+
if (!stdout) { finish({ permission: "allow" }); return; }
|
|
98
|
+
try {
|
|
99
|
+
finish(JSON.parse(stdout));
|
|
100
|
+
} catch (err) {
|
|
101
|
+
debug(`parse error: ${err && err.message ? err.message : String(err)}`);
|
|
102
|
+
finish({ permission: "allow" });
|
|
103
|
+
}
|
|
104
|
+
});
|
|
105
|
+
try {
|
|
106
|
+
child.stdin.end(JSON.stringify(payload));
|
|
107
|
+
} catch (err) {
|
|
108
|
+
debug(`stdin write failed: ${err && err.message ? err.message : String(err)}`);
|
|
109
|
+
finish({ permission: "allow" });
|
|
110
|
+
}
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/** Claude-shaped stdin base. Extra keys are ignored by the binary's payload
|
|
115
|
+
* parser; they're forwarded for future use / activity attribution. */
|
|
116
|
+
function baseMeta(payload, ctx) {
|
|
117
|
+
const p = payload || {};
|
|
118
|
+
const c = ctx || {};
|
|
119
|
+
return {
|
|
120
|
+
session_id: c.sessionId ?? p.sessionId ?? c.sessionKey ?? p.sessionKey,
|
|
121
|
+
cwd: p.cwd ?? c.workspaceDir ?? process.cwd(),
|
|
122
|
+
transcript_path: p.transcriptPath,
|
|
123
|
+
stop_hook_active: p.stopHookActive === true,
|
|
124
|
+
openclaw: {
|
|
125
|
+
agentId: c.agentId,
|
|
126
|
+
sessionKey: c.sessionKey ?? p.sessionKey,
|
|
127
|
+
runId: c.runId ?? p.runId,
|
|
128
|
+
provider: p.provider,
|
|
129
|
+
model: p.model,
|
|
130
|
+
},
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
export default definePluginEntry({
|
|
135
|
+
id: "failproofai",
|
|
136
|
+
name: "failproofai",
|
|
137
|
+
description: "Real-time policy enforcement for OpenClaw by failproofai",
|
|
138
|
+
register(api) {
|
|
139
|
+
// before_tool_call → PreToolUse. Full deny: return {block:true, blockReason}.
|
|
140
|
+
api.on(
|
|
141
|
+
"before_tool_call",
|
|
142
|
+
async (payload, ctx) => {
|
|
143
|
+
const p = payload || {};
|
|
144
|
+
const verdict = await callPolicy("before_tool_call", {
|
|
145
|
+
...baseMeta(payload, ctx),
|
|
146
|
+
tool_name: p.toolName,
|
|
147
|
+
tool_input: p.params,
|
|
148
|
+
hook_event_name: "before_tool_call",
|
|
149
|
+
});
|
|
150
|
+
if (verdict.permission === "deny") {
|
|
151
|
+
return { block: true, blockReason: verdict.reason || "Blocked by failproofai" };
|
|
152
|
+
}
|
|
153
|
+
return undefined;
|
|
154
|
+
},
|
|
155
|
+
{ priority: 100, timeoutMs: 60_000 },
|
|
156
|
+
);
|
|
157
|
+
|
|
158
|
+
// before_agent_run → UserPromptSubmit. Deny: return {outcome:"block", reason}.
|
|
159
|
+
api.on(
|
|
160
|
+
"before_agent_run",
|
|
161
|
+
async (payload, ctx) => {
|
|
162
|
+
const p = payload || {};
|
|
163
|
+
const verdict = await callPolicy("before_agent_run", {
|
|
164
|
+
...baseMeta(payload, ctx),
|
|
165
|
+
prompt: p.prompt,
|
|
166
|
+
hook_event_name: "before_agent_run",
|
|
167
|
+
});
|
|
168
|
+
if (verdict.permission === "deny") {
|
|
169
|
+
return { outcome: "block", reason: verdict.reason || "Blocked by failproofai" };
|
|
170
|
+
}
|
|
171
|
+
return undefined;
|
|
172
|
+
},
|
|
173
|
+
{ priority: 100, timeoutMs: 60_000 },
|
|
174
|
+
);
|
|
175
|
+
|
|
176
|
+
// before_agent_finalize → Stop. The real turn-end gate: a deny becomes a
|
|
177
|
+
// revise so the agent runs another pass. The evaluator supplies the
|
|
178
|
+
// MANDATORY ACTION wording; the payload's stop_hook_active guards the
|
|
179
|
+
// require-*-before-stop builtins against infinite loops.
|
|
180
|
+
api.on(
|
|
181
|
+
"before_agent_finalize",
|
|
182
|
+
async (payload, ctx) => {
|
|
183
|
+
const verdict = await callPolicy("before_agent_finalize", {
|
|
184
|
+
...baseMeta(payload, ctx),
|
|
185
|
+
hook_event_name: "before_agent_finalize",
|
|
186
|
+
});
|
|
187
|
+
if (verdict.permission === "deny") {
|
|
188
|
+
return { action: "revise", reason: verdict.reason || "Action required by failproofai" };
|
|
189
|
+
}
|
|
190
|
+
return undefined;
|
|
191
|
+
},
|
|
192
|
+
{ priority: 100, timeoutMs: 60_000 },
|
|
193
|
+
);
|
|
194
|
+
|
|
195
|
+
// Observation hooks — await so activity ordering is stable, ignore verdict.
|
|
196
|
+
// after_tool_call → PostToolUse; session_start/end → SessionStart/End;
|
|
197
|
+
// subagent_ended → SubagentStop (obs only); before_compaction → PreCompact.
|
|
198
|
+
for (const ev of [
|
|
199
|
+
"after_tool_call",
|
|
200
|
+
"session_start",
|
|
201
|
+
"session_end",
|
|
202
|
+
"subagent_ended",
|
|
203
|
+
"before_compaction",
|
|
204
|
+
]) {
|
|
205
|
+
api.on(
|
|
206
|
+
ev,
|
|
207
|
+
async (payload, ctx) => {
|
|
208
|
+
const p = payload || {};
|
|
209
|
+
await callPolicy(ev, {
|
|
210
|
+
...baseMeta(payload, ctx),
|
|
211
|
+
tool_name: p.toolName,
|
|
212
|
+
tool_input: p.params,
|
|
213
|
+
tool_response: p.result,
|
|
214
|
+
reason: p.reason,
|
|
215
|
+
hook_event_name: ev,
|
|
216
|
+
});
|
|
217
|
+
return undefined;
|
|
218
|
+
},
|
|
219
|
+
{ priority: 100, timeoutMs: 60_000 },
|
|
220
|
+
);
|
|
221
|
+
}
|
|
222
|
+
},
|
|
223
|
+
});
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@failproofai/openclaw-plugin",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"description": "failproofai policy bridge for OpenClaw (openclaw gateway)",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./index.js",
|
|
7
|
+
"private": true,
|
|
8
|
+
"keywords": [
|
|
9
|
+
"openclaw-plugin",
|
|
10
|
+
"failproofai"
|
|
11
|
+
]
|
|
12
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "failproofai",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.14-beta.1",
|
|
4
4
|
"description": "The easiest way to manage policies that keep your AI agents reliable, on-task, and running autonomously — for Claude Code & the Agents SDK",
|
|
5
5
|
"bin": {
|
|
6
6
|
"failproofai": "./dist/cli.mjs"
|
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
"scripts/",
|
|
12
12
|
"lib/",
|
|
13
13
|
"pi-extension/",
|
|
14
|
+
"openclaw-plugin/",
|
|
14
15
|
".next/standalone/",
|
|
15
16
|
"dist/",
|
|
16
17
|
"README.md"
|
|
@@ -29,8 +30,6 @@
|
|
|
29
30
|
"test": "vitest",
|
|
30
31
|
"test:run": "vitest run",
|
|
31
32
|
"lint": "eslint . --config eslint.config.mjs",
|
|
32
|
-
"postinstall": "node scripts/postinstall.mjs",
|
|
33
|
-
"preuninstall": "node scripts/preuninstall.mjs",
|
|
34
33
|
"prepare": "bun run build",
|
|
35
34
|
"test:e2e": "vitest run --config vitest.config.e2e.mts",
|
|
36
35
|
"test:e2e:watch": "vitest --config vitest.config.e2e.mts",
|
|
@@ -0,0 +1,292 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* failproofai DEV-ONLY hook launcher — for THIS repo's dogfood configs only.
|
|
4
|
+
*
|
|
5
|
+
* End users never run this. They install `npx -y failproofai --hook <Event>`
|
|
6
|
+
* (see `src/hooks/integrations.ts`), which resolves the published package.
|
|
7
|
+
* This file exists because this repo dogfoods itself from source, and doing so
|
|
8
|
+
* has two prerequisites that silently rot:
|
|
9
|
+
*
|
|
10
|
+
* 1. `bun` on the hook's PATH. `bin/failproofai.mjs` cannot run under node —
|
|
11
|
+
* it does a bare `import { version } from "../package.json"` (node needs
|
|
12
|
+
* `with { type: "json" }`) and `await import("../src/hooks/handler")`,
|
|
13
|
+
* an extensionless TypeScript specifier node cannot resolve. But bun is
|
|
14
|
+
* routinely absent from a hook's PATH even when it's on yours: `npm i -g
|
|
15
|
+
* bun` installs into ONE nvm version's bin dir, so `nvm use <other>` drops
|
|
16
|
+
* it; a macOS GUI launch gets a launchd PATH built without ~/.zshrc.
|
|
17
|
+
* When that happens every hook exits 127 and the session runs with ZERO
|
|
18
|
+
* policy enforcement — silently.
|
|
19
|
+
*
|
|
20
|
+
* 2. `dist/index.js`. The committed policies in `.failproofai/policies/`
|
|
21
|
+
* `import from 'failproofai'`, which `findDistIndex()` resolves to that
|
|
22
|
+
* bundle. Without it all three fail-open and only builtins enforce.
|
|
23
|
+
*
|
|
24
|
+
* So: locate bun (installing it via npm if we must), make sure the bundle
|
|
25
|
+
* exists, then hand off to the real binary. If we cannot enforce, say so in
|
|
26
|
+
* one line rather than failing silently.
|
|
27
|
+
*
|
|
28
|
+
* INVARIANT — never write to stdout. Most CLIs' deny contracts are JSON on
|
|
29
|
+
* stdout (Copilot `{decision:"block"}`, Cursor `{followup_message}`, Devin,
|
|
30
|
+
* Factory-on-Stop, Antigravity). A single stray byte corrupts the parse and
|
|
31
|
+
* turns a denial into an allow. Diagnostics go to stderr; subprocesses we run
|
|
32
|
+
* for our own purposes get their stdout discarded, never inherited.
|
|
33
|
+
*/
|
|
34
|
+
import { spawnSync } from "node:child_process";
|
|
35
|
+
import { existsSync, mkdirSync, readdirSync, realpathSync, rmSync, statSync } from "node:fs";
|
|
36
|
+
import { constants, homedir } from "node:os";
|
|
37
|
+
import { delimiter, dirname, join, resolve } from "node:path";
|
|
38
|
+
import { fileURLToPath } from "node:url";
|
|
39
|
+
|
|
40
|
+
const HERE = dirname(fileURLToPath(import.meta.url));
|
|
41
|
+
const REPO_ROOT = resolve(HERE, "..");
|
|
42
|
+
const SRC_BIN = resolve(REPO_ROOT, "bin", "failproofai.mjs");
|
|
43
|
+
const BUNDLE = resolve(REPO_ROOT, "dist", "index.js");
|
|
44
|
+
const CACHE_DIR = resolve(REPO_ROOT, "node_modules", ".cache", "failproofai-dev");
|
|
45
|
+
const LOCK_DIR = join(CACHE_DIR, ".lock");
|
|
46
|
+
|
|
47
|
+
/** A lock older than this belonged to a process that died mid-install. */
|
|
48
|
+
const LOCK_STALE_MS = 5 * 60_000;
|
|
49
|
+
/** 120 × 250ms = 30s, under the tightest hook timeout (Factory/Antigravity). */
|
|
50
|
+
const LOCK_WAIT_TRIES = 120;
|
|
51
|
+
const LOCK_WAIT_MS = 250;
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* @typedef {object} Deps
|
|
55
|
+
* @property {Record<string, string | undefined>} [env]
|
|
56
|
+
* @property {string} [platform]
|
|
57
|
+
* @property {string} [execPath]
|
|
58
|
+
* @property {string} [home]
|
|
59
|
+
* @property {(p: string) => boolean} [exists]
|
|
60
|
+
* @property {(p: string) => string[]} [readdir]
|
|
61
|
+
*/
|
|
62
|
+
|
|
63
|
+
function warn(msg) {
|
|
64
|
+
process.stderr.write(`[failproofai-dev] ${msg}\n`);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function sleepSync(ms) {
|
|
68
|
+
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function readdirSafe(p) {
|
|
72
|
+
try {
|
|
73
|
+
return readdirSync(p);
|
|
74
|
+
} catch {
|
|
75
|
+
return [];
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/** Executable basenames to probe, most specific first. */
|
|
80
|
+
function exeNames(base, platform) {
|
|
81
|
+
return platform === "win32" ? [`${base}.exe`, `${base}.cmd`, base] : [base];
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/** First existing `<dir>/<name>` across dirs × names, or null. */
|
|
85
|
+
function probe(dirs, names, exists) {
|
|
86
|
+
for (const dir of dirs) {
|
|
87
|
+
for (const name of names) {
|
|
88
|
+
const candidate = join(dir, name);
|
|
89
|
+
if (exists(candidate)) return candidate;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
return null;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Absolute path to a bun executable, or null. NEVER spawns a subprocess —
|
|
97
|
+
* this runs on every hook, so it is an existsSync walk and nothing more.
|
|
98
|
+
* @param {Deps} deps
|
|
99
|
+
*/
|
|
100
|
+
export function findBun(deps = {}) {
|
|
101
|
+
const env = deps.env ?? process.env;
|
|
102
|
+
const platform = deps.platform ?? process.platform;
|
|
103
|
+
const execPath = deps.execPath ?? process.execPath;
|
|
104
|
+
const home = deps.home ?? homedir();
|
|
105
|
+
const exists = deps.exists ?? existsSync;
|
|
106
|
+
const readdir = deps.readdir ?? readdirSafe;
|
|
107
|
+
|
|
108
|
+
const dirs = [];
|
|
109
|
+
const push = (d) => {
|
|
110
|
+
if (d && !dirs.includes(d)) dirs.push(d);
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
// 1. PATH — the common case, and the only one that costs nothing.
|
|
114
|
+
for (const p of (env.PATH ?? "").split(delimiter)) push(p);
|
|
115
|
+
// 2. An explicit bun install root.
|
|
116
|
+
if (env.BUN_INSTALL) push(join(env.BUN_INSTALL, "bin"));
|
|
117
|
+
// 3. Where bun.sh's installer puts it. The macOS GUI-launch case: launchd's
|
|
118
|
+
// PATH has /usr/local/bin but never ~/.bun/bin.
|
|
119
|
+
push(join(home, ".bun", "bin"));
|
|
120
|
+
// 4. Package-manager prefixes.
|
|
121
|
+
if (platform !== "win32") {
|
|
122
|
+
push("/opt/homebrew/bin");
|
|
123
|
+
push("/usr/local/bin");
|
|
124
|
+
}
|
|
125
|
+
// 5. Every nvm-managed node version's bin dir. Load-bearing: `npm i -g bun`
|
|
126
|
+
// lands bun in exactly ONE version's bin, so `nvm use <another>` makes it
|
|
127
|
+
// vanish from PATH entirely while it's still perfectly usable on disk.
|
|
128
|
+
// Descending so the newest version wins deterministically.
|
|
129
|
+
const nvmRoot = join(home, ".nvm", "versions", "node");
|
|
130
|
+
for (const v of readdir(nvmRoot).sort().reverse()) push(join(nvmRoot, v, "bin"));
|
|
131
|
+
// 6. Next to the running node. For our own `node scripts/dev-hook.mjs` form
|
|
132
|
+
// this is a strict subset of (1) — node was resolved from PATH — so it is
|
|
133
|
+
// a no-op. Kept only for a caller that spawns node by absolute path.
|
|
134
|
+
push(dirname(execPath));
|
|
135
|
+
|
|
136
|
+
// Cache from a previous npm-install (see installBun). Last, so a real bun
|
|
137
|
+
// that reappears on PATH always wins over our vendored copy.
|
|
138
|
+
push(join(CACHE_DIR, "node_modules", ".bin"));
|
|
139
|
+
|
|
140
|
+
return probe(dirs, exeNames("bun", platform), exists);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/** Absolute path to npm, or null. PATH only — npm ships beside node. */
|
|
144
|
+
export function findNpm(deps = {}) {
|
|
145
|
+
const env = deps.env ?? process.env;
|
|
146
|
+
const platform = deps.platform ?? process.platform;
|
|
147
|
+
const exists = deps.exists ?? existsSync;
|
|
148
|
+
const dirs = (env.PATH ?? "").split(delimiter).filter(Boolean);
|
|
149
|
+
return probe(dirs, exeNames("npm", platform), exists);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* `{ cmd, args }` to run the real binary, or null when bun is unfindable.
|
|
154
|
+
* Shared with `.opencode/plugins/failproofai.mjs`, which spawns bun itself.
|
|
155
|
+
* @param {Deps} deps
|
|
156
|
+
*/
|
|
157
|
+
export function resolveDevSpawn(deps = {}) {
|
|
158
|
+
const bun = findBun(deps);
|
|
159
|
+
return bun ? { cmd: bun, args: [SRC_BIN] } : null;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/** One actionable line. Keep it to one — this can fire on 26 events. */
|
|
163
|
+
export function bunMissingMessage() {
|
|
164
|
+
return (
|
|
165
|
+
"bun not found and npm unavailable to install it — this repo's dogfood hooks are NOT " +
|
|
166
|
+
"enforcing. Install bun: curl -fsSL https://bun.sh/install | bash"
|
|
167
|
+
);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Run `doWork` at most once across concurrently-firing hooks. `mkdirSync` is
|
|
172
|
+
* atomic, so it doubles as the lock: EEXIST means another process got there.
|
|
173
|
+
* Losers wait for the artifact rather than duplicating the work.
|
|
174
|
+
*/
|
|
175
|
+
function ensureOnce(isDone, doWork) {
|
|
176
|
+
if (isDone()) return true;
|
|
177
|
+
mkdirSync(CACHE_DIR, { recursive: true });
|
|
178
|
+
|
|
179
|
+
for (let i = 0; i < LOCK_WAIT_TRIES; i++) {
|
|
180
|
+
if (isDone()) return true;
|
|
181
|
+
try {
|
|
182
|
+
mkdirSync(LOCK_DIR);
|
|
183
|
+
} catch (err) {
|
|
184
|
+
if (err?.code !== "EEXIST") throw err;
|
|
185
|
+
try {
|
|
186
|
+
if (Date.now() - statSync(LOCK_DIR).mtimeMs > LOCK_STALE_MS) {
|
|
187
|
+
rmSync(LOCK_DIR, { recursive: true, force: true });
|
|
188
|
+
} else {
|
|
189
|
+
sleepSync(LOCK_WAIT_MS);
|
|
190
|
+
}
|
|
191
|
+
} catch {
|
|
192
|
+
/* lock vanished under us — retry immediately */
|
|
193
|
+
}
|
|
194
|
+
continue;
|
|
195
|
+
}
|
|
196
|
+
try {
|
|
197
|
+
doWork();
|
|
198
|
+
} finally {
|
|
199
|
+
try {
|
|
200
|
+
rmSync(LOCK_DIR, { recursive: true, force: true });
|
|
201
|
+
} catch {
|
|
202
|
+
/* best effort; a leaked lock goes stale in LOCK_STALE_MS */
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
return isDone();
|
|
206
|
+
}
|
|
207
|
+
return isDone();
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* Vendor bun into node_modules/.cache via npm. bun ships as an npm package, so
|
|
212
|
+
* npm is the one bootstrap that works when bun is genuinely absent — building
|
|
213
|
+
* `dist/cli.mjs` to run under node can't, because `build:cli` is itself
|
|
214
|
+
* `bun build`.
|
|
215
|
+
*/
|
|
216
|
+
function installBun(deps = {}) {
|
|
217
|
+
const npm = findNpm(deps);
|
|
218
|
+
if (!npm) return null;
|
|
219
|
+
ensureOnce(
|
|
220
|
+
() => findBun(deps) !== null,
|
|
221
|
+
() => {
|
|
222
|
+
warn("bun not found — installing it into node_modules/.cache (one time, ~15s)");
|
|
223
|
+
const r = spawnSync(
|
|
224
|
+
npm,
|
|
225
|
+
["install", "bun", "--prefix", CACHE_DIR, "--no-save", "--no-audit", "--no-fund", "--loglevel=error"],
|
|
226
|
+
// NOT "inherit" on stdout: npm prints there, and our stdout is a deny
|
|
227
|
+
// channel. stderr passes through so a failed install is visible.
|
|
228
|
+
{ stdio: ["ignore", "ignore", "inherit"], cwd: REPO_ROOT },
|
|
229
|
+
);
|
|
230
|
+
if (r.status !== 0) warn("npm install bun failed — see the error above");
|
|
231
|
+
},
|
|
232
|
+
);
|
|
233
|
+
return findBun(deps);
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
/**
|
|
237
|
+
* Build dist/index.js if absent, so `.failproofai/policies/*.mjs` can resolve
|
|
238
|
+
* `import ... from 'failproofai'`. Non-fatal: builtins enforce without it.
|
|
239
|
+
*/
|
|
240
|
+
export function ensureBundle(bunPath) {
|
|
241
|
+
return ensureOnce(
|
|
242
|
+
() => existsSync(BUNDLE),
|
|
243
|
+
() => {
|
|
244
|
+
warn("dist/index.js missing — building it so .failproofai/policies/ can load (one time)");
|
|
245
|
+
const r = spawnSync(
|
|
246
|
+
bunPath,
|
|
247
|
+
["build", "--target=node", "--format=cjs", "--outfile=dist/index.js", "src/index.ts"],
|
|
248
|
+
{ stdio: ["ignore", "ignore", "inherit"], cwd: REPO_ROOT },
|
|
249
|
+
);
|
|
250
|
+
if (r.status !== 0) warn("build failed — policies in .failproofai/policies/ will NOT load");
|
|
251
|
+
},
|
|
252
|
+
);
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
function main() {
|
|
256
|
+
const bun = findBun() ?? installBun();
|
|
257
|
+
if (!bun) {
|
|
258
|
+
warn(bunMissingMessage());
|
|
259
|
+
process.exit(1);
|
|
260
|
+
}
|
|
261
|
+
ensureBundle(bun);
|
|
262
|
+
|
|
263
|
+
// stdio:"inherit" hands the child our actual fds: the payload on stdin and
|
|
264
|
+
// any decision JSON on stdout flow byte-exact, unbuffered, unparsed by us.
|
|
265
|
+
// No cwd override — the child must see whatever cwd the agent CLI chose,
|
|
266
|
+
// exactly as it did when the config spawned bun directly.
|
|
267
|
+
const r = spawnSync(bun, [SRC_BIN, ...process.argv.slice(2)], { stdio: "inherit" });
|
|
268
|
+
|
|
269
|
+
// Exit codes are a contract, not a status: 2 means DENY on Claude/Factory/
|
|
270
|
+
// Goose. Propagate verbatim. Never synthesize 2 (would deny) or 0 (would
|
|
271
|
+
// silently allow).
|
|
272
|
+
if (r.error) {
|
|
273
|
+
warn(`could not run bun (${r.error.code ?? r.error.message}) — hooks are NOT enforcing`);
|
|
274
|
+
process.exit(1);
|
|
275
|
+
}
|
|
276
|
+
if (r.status === null && r.signal) {
|
|
277
|
+
// Mirror what `sh` reports for a signalled child, which is what the agent
|
|
278
|
+
// CLI saw before this launcher sat in between.
|
|
279
|
+
process.exit(128 + (constants.signals[r.signal] ?? 15));
|
|
280
|
+
}
|
|
281
|
+
process.exit(r.status ?? 1);
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
// Only run when executed, not when `.opencode/plugins/failproofai.mjs` imports
|
|
285
|
+
// the resolver. realpath so a symlinked or relative argv[1] still matches.
|
|
286
|
+
let invokedDirectly = false;
|
|
287
|
+
try {
|
|
288
|
+
invokedDirectly = realpathSync(fileURLToPath(import.meta.url)) === realpathSync(process.argv[1]);
|
|
289
|
+
} catch {
|
|
290
|
+
invokedDirectly = false;
|
|
291
|
+
}
|
|
292
|
+
if (invokedDirectly) main();
|
|
@@ -4,11 +4,9 @@
|
|
|
4
4
|
* a `bun install -g failproofai` whose prefix sorts ahead of npm's on PATH.
|
|
5
5
|
*
|
|
6
6
|
* Used by:
|
|
7
|
-
* - scripts/
|
|
8
|
-
* the
|
|
9
|
-
*
|
|
10
|
-
* produce a shadow-shaped error if the cause is a shadow rather than a
|
|
11
|
-
* genuinely broken build.
|
|
7
|
+
* - scripts/launch.ts — when .next/standalone/server.js is missing, produce a
|
|
8
|
+
* shadow-shaped error if the cause is a shadow rather than a genuinely
|
|
9
|
+
* broken build.
|
|
12
10
|
*
|
|
13
11
|
* Pure Node.js built-ins, no external dependencies. Every probe is wrapped in
|
|
14
12
|
* try/catch — diagnoseShadow() is guaranteed not to throw.
|
|
@@ -125,9 +123,8 @@ function buildRecommendation(pathFirstBin) {
|
|
|
125
123
|
*
|
|
126
124
|
* @param {{ selfPackageRoot: string, selfVersion: string | null }} self
|
|
127
125
|
* The package root and version of the binary calling diagnoseShadow().
|
|
128
|
-
* Callers (
|
|
129
|
-
*
|
|
130
|
-
* import.meta.url assumptions.
|
|
126
|
+
* Callers (scripts/launch.ts) already have these values; passing them in keeps
|
|
127
|
+
* the helper deterministic and free of import.meta.url assumptions.
|
|
131
128
|
*/
|
|
132
129
|
export function diagnoseShadow(self) {
|
|
133
130
|
const selfPackageRoot = (() => {
|
|
@@ -80,12 +80,20 @@ function getInstanceId() {
|
|
|
80
80
|
|
|
81
81
|
/**
|
|
82
82
|
* Track a named event to PostHog. No-op when telemetry is disabled.
|
|
83
|
-
*
|
|
83
|
+
*
|
|
84
|
+
* `opts.version` must be supplied by callers outside an npm lifecycle script:
|
|
85
|
+
* npm only sets npm_package_version while running one, and these events now
|
|
86
|
+
* fire from the CLI (see lib/install-check.ts) because package managers block
|
|
87
|
+
* install scripts by default.
|
|
88
|
+
*
|
|
89
|
+
* @param {string} event
|
|
90
|
+
* @param {Record<string, unknown>} [properties]
|
|
91
|
+
* @param {{ version?: string, timeoutMs?: number }} [opts]
|
|
84
92
|
*/
|
|
85
|
-
export async function trackInstallEvent(event, properties = {}) {
|
|
93
|
+
export async function trackInstallEvent(event, properties = {}, opts = {}) {
|
|
86
94
|
if (process.env.FAILPROOFAI_TELEMETRY_DISABLED === "1") return;
|
|
87
95
|
|
|
88
|
-
const version = process.env.npm_package_version ?? "unknown";
|
|
96
|
+
const version = opts.version ?? process.env.npm_package_version ?? "unknown";
|
|
89
97
|
const body = JSON.stringify({
|
|
90
98
|
api_key: process.env.FAILPROOFAI_POSTHOG_KEY ?? API_KEY,
|
|
91
99
|
event,
|
|
@@ -106,7 +114,7 @@ export async function trackInstallEvent(event, properties = {}) {
|
|
|
106
114
|
method: "POST",
|
|
107
115
|
headers: { "Content-Type": "application/json" },
|
|
108
116
|
body,
|
|
109
|
-
signal: AbortSignal.timeout(5000),
|
|
117
|
+
signal: AbortSignal.timeout(opts.timeoutMs ?? 5000),
|
|
110
118
|
}
|
|
111
119
|
);
|
|
112
120
|
}
|
package/scripts/launch.ts
CHANGED
|
@@ -9,19 +9,15 @@ import { createInterface } from "node:readline";
|
|
|
9
9
|
import { parseScriptArgs } from "./parse-script-args";
|
|
10
10
|
import { diagnoseShadow } from "./install-diagnosis.mjs";
|
|
11
11
|
import { makeSkewLogFilter } from "./skew-log-filter";
|
|
12
|
+
import { renderLaunchBanner } from "../src/hooks/tui";
|
|
12
13
|
import { version } from "../package.json";
|
|
13
14
|
|
|
14
15
|
export function launch(mode: "dev" | "start"): void {
|
|
15
16
|
const { loggingLevel, disableTelemetry, allowedDevOrigins, remainingArgs } = parseScriptArgs(process.argv.slice(2));
|
|
16
17
|
|
|
17
|
-
//
|
|
18
|
-
//
|
|
19
|
-
|
|
20
|
-
console.log(`\n failproof ai\n`);
|
|
21
|
-
console.log(` 📦 Version: ${version}`);
|
|
22
|
-
console.log(` ⭐ Star us: https://github.com/failproofai/failproofai`);
|
|
23
|
-
console.log(` 📖 Docs: https://docs.befailproof.ai/introduction`);
|
|
24
|
-
console.log(` 💬 Discord: https://discord.gg/2zjBZP7yQJ\n`);
|
|
18
|
+
// Branded splash — the same logomark + palette as the `configure` wizard, then
|
|
19
|
+
// a tidy version/links column. Falls back to plain text off a TTY.
|
|
20
|
+
for (const line of renderLaunchBanner(version)) console.log(line);
|
|
25
21
|
|
|
26
22
|
let cmd: string;
|
|
27
23
|
let cmdArgs: string[];
|