failproofai 1.0.0-beta.2 → 1.0.0-beta.21
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/.next/BUILD_ID +1 -1
- package/.next/standalone/.next/app-path-routes-manifest.json +2 -1
- package/.next/standalone/.next/build-manifest.json +6 -6
- package/.next/standalone/.next/prerender-manifest.json +3 -3
- package/.next/standalone/.next/required-server-files.json +1 -1
- package/.next/standalone/.next/routes-manifest.json +6 -0
- package/.next/standalone/.next/server/app/_global-error/page/build-manifest.json +3 -3
- 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 +3 -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 +3 -3
- 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 +3 -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 +3 -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 +4 -3
- package/.next/standalone/.next/server/app/api/audit/run/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/audit/status/route.js +1 -1
- package/.next/standalone/.next/server/app/api/audit/status/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/auth/login-request/route.js +3 -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 +3 -2
- 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 +3 -2
- 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 +3 -2
- 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 +3 -2
- 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 +1 -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 +3 -3
- package/.next/standalone/.next/server/app/audit/page/server-reference-manifest.json +2 -2
- package/.next/standalone/.next/server/app/audit/page.js +5 -3
- 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 +3 -3
- package/.next/standalone/.next/server/app/page/server-reference-manifest.json +1 -1
- package/.next/standalone/.next/server/app/page.js +3 -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 +3 -3
- package/.next/standalone/.next/server/app/policies/page/server-reference-manifest.json +30 -18
- package/.next/standalone/.next/server/app/policies/page.js +5 -3
- 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 +3 -3
- 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 +4 -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 +3 -3
- 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 +6 -4
- 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 +3 -3
- package/.next/standalone/.next/server/app/projects/page/server-reference-manifest.json +1 -1
- package/.next/standalone/.next/server/app/projects/page.js +4 -2
- 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/app/settings/page/app-paths-manifest.json +3 -0
- package/.next/standalone/.next/server/app/settings/page/build-manifest.json +18 -0
- package/.next/standalone/.next/server/app/settings/page/next-font-manifest.json +6 -0
- package/.next/standalone/.next/server/app/settings/page/react-loadable-manifest.json +1 -0
- package/.next/standalone/.next/server/app/settings/page/server-reference-manifest.json +53 -0
- package/.next/standalone/.next/server/app/settings/page.js +18 -0
- package/.next/standalone/.next/server/app/settings/page.js.map +5 -0
- package/.next/standalone/.next/server/app/settings/page.js.nft.json +1 -0
- package/.next/standalone/.next/server/app/settings/page_client-reference-manifest.js +3 -0
- package/.next/standalone/.next/server/app-paths-manifest.json +2 -1
- package/.next/standalone/.next/server/chunks/[externals]__0h9_83k._.js +3 -0
- package/.next/standalone/.next/server/chunks/{[externals]__1nl3dvw._.js → [externals]__0r-zamh._.js} +2 -2
- package/.next/standalone/.next/server/chunks/[externals]__1ifpkx-._.js +3 -0
- package/.next/standalone/.next/server/chunks/{[root-of-the-server]__0l4vuqm._.js → [root-of-the-server]__00ni9za._.js} +2 -2
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__06hexd0._.js +3 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__08y94xv._.js +3 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__094p1f1._.js +3 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0_0xu5z._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0ed01_d._.js +3 -0
- package/.next/standalone/.next/server/chunks/{[root-of-the-server]__0-dh5jy._.js → [root-of-the-server]__0effm9m._.js} +2 -2
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0g0vcwt._.js +3 -0
- package/.next/standalone/.next/server/chunks/{[root-of-the-server]__18d-9oi._.js → [root-of-the-server]__0ix8gm7._.js} +2 -2
- package/.next/standalone/.next/server/chunks/{[root-of-the-server]__0h3lfmp._.js → [root-of-the-server]__0ji11a-._.js} +2 -2
- package/.next/standalone/.next/server/chunks/{[root-of-the-server]__10oa-v9._.js → [root-of-the-server]__0ojwjxl._.js} +2 -2
- package/.next/standalone/.next/server/chunks/{[root-of-the-server]__16le-kd._.js → [root-of-the-server]__0pbp9fe._.js} +2 -2
- 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]__0vb5fl2._.js +3 -0
- package/.next/standalone/.next/server/chunks/{[root-of-the-server]__0sb_5m8._.js → [root-of-the-server]__131ebum._.js} +4 -4
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__1ibjy5x._.js +3 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__1tvn4wf._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__20d06rh._.js +3 -0
- package/.next/standalone/.next/server/chunks/_0lxbzdq._.js +3 -0
- package/.next/standalone/.next/server/chunks/_1zuiiy3._.js +3 -0
- package/.next/standalone/.next/server/chunks/lib_telemetry_ts_0dctyyw._.js +3 -0
- package/.next/standalone/.next/server/chunks/node_modules_next_dist_esm_build_templates_app-route_17k9e3w.js +5 -5
- package/.next/standalone/.next/server/chunks/node_modules_posthog-node_dist_entrypoints_index_node_mjs_01r25oi._.js +2 -1
- package/.next/standalone/.next/server/chunks/node_modules_posthog-node_dist_entrypoints_index_node_mjs_09z9-p7._.js +2 -1
- package/.next/standalone/.next/server/chunks/package_json_[json]_cjs_1nxcc4v._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/[externals]__1aq139v._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0-wfhrp._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__06bp_fg._.js +5 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__074qmkp._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__1apb7k_._.js → [root-of-the-server]__0_1tiya._.js} +2 -2
- package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__170799-._.js → [root-of-the-server]__0spkm68._.js} +11 -11
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0t281d4._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__17itmx-._.js +5 -0
- package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__01od0m6._.js → [root-of-the-server]__1n2vcv4._.js} +2 -2
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1qapotl._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1rv-sc9._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1tlvv9a._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1u-wx42._.js +4 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1v185mo._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/_0-yi74u._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/_0rjgfln._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/_0vo2qyt._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/_0xej-0e._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/_10399ge._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/_11_p9y8._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/_149k9oy._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/_1_hwp06._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/_1ctse6u._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/_1e19atl._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/_1mnhi_r._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/_1ror7k9._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/_1sdgprl._.js +5 -0
- 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 +2 -2
- package/.next/standalone/.next/server/chunks/ssr/node_modules_next_dist_esm_build_templates_app-page_0dwo94s.js +4 -0
- package/.next/standalone/.next/server/chunks/ssr/node_modules_posthog-node_dist_entrypoints_index_node_mjs_11bnuzn._.js +2 -1
- package/.next/standalone/.next/server/chunks/ssr/src_hooks_builtin-policies_ts_09j2ndl._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/src_hooks_fp-config_ts_04t589g._.js +4 -0
- package/.next/standalone/.next/server/middleware-build-manifest.js +6 -6
- package/.next/standalone/.next/server/middleware.js +2 -2
- 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 +74 -20
- package/.next/standalone/.next/static/chunks/{03fmihek9n986.js → 06zgzyh3wv1ig.js} +1 -1
- package/.next/standalone/.next/static/chunks/0km-jn9o5xyqs.css +2 -0
- package/.next/standalone/.next/static/chunks/{1hdhteui932bq.js → 0qmo2t50kpb30.js} +1 -1
- package/.next/standalone/.next/static/chunks/{2sbelanzvxvv0.js → 113g8wtal6_c4.js} +22 -22
- package/.next/standalone/.next/static/chunks/16p68cybetr31.js +1 -0
- package/.next/standalone/.next/static/chunks/18wqn0_3mzylf.js +1 -0
- package/.next/standalone/.next/static/chunks/1v1gqvpjk62jm.js +1 -0
- package/.next/standalone/.next/static/chunks/{0fi645si-uiiq.js → 1xq1m8h14tnlj.js} +1 -1
- package/.next/standalone/.next/static/chunks/{3hp8ulqknybil.js → 1y3vwogqb88dx.js} +1 -1
- package/.next/standalone/.next/static/chunks/{3vs4ay36tjds_.js → 1zmh-b0ulrqu7.js} +1 -1
- package/.next/standalone/.next/static/chunks/23kev26by1e4f.js +1 -0
- package/.next/standalone/.next/static/chunks/299lhw60e8gr6.js +1 -0
- package/.next/standalone/.next/static/chunks/{18rvdw5v72364.js → 2adbpdaxp5ygq.js} +1 -1
- package/.next/standalone/.next/static/chunks/{2j94uwkb4s3qr.js → 2kbqcw-6swmav.js} +1 -1
- package/.next/standalone/.next/static/chunks/2n9n5m0963pdg.js +6 -0
- package/.next/standalone/.next/static/chunks/2uvhw57mf9lg_.css +2 -0
- package/.next/standalone/.next/static/chunks/{31feojxhjyjpl.js → 30nrrpwymht5m.js} +1 -1
- package/.next/standalone/.next/static/chunks/31coz11ia5vc4.js +1 -0
- package/.next/standalone/.next/static/chunks/{turbopack-1lsv92x7klwrn.js → turbopack-3diub4ctvn1n0.js} +1 -1
- package/.next/standalone/SECURITY.md +8 -2
- package/.next/standalone/app/actions/get-active-pauses.ts +16 -0
- package/.next/standalone/app/actions/get-hooks-config.ts +2 -2
- package/.next/standalone/app/actions/get-scheduled-audit.ts +70 -0
- package/.next/standalone/app/actions/update-scheduled-audit.ts +44 -0
- package/.next/standalone/app/api/audit/run/route.ts +28 -0
- package/.next/standalone/app/api/audit/status/route.ts +13 -1
- package/.next/standalone/app/api/auth/status/route.ts +1 -1
- package/.next/standalone/app/audit/_components/audit-dashboard.tsx +1 -3
- package/.next/standalone/app/audit/_components/audit-progress-strip.tsx +1 -1
- package/.next/standalone/app/audit/_components/auth-dialog.css +159 -0
- package/.next/standalone/app/audit/_components/auth-dialog.tsx +3 -0
- package/.next/standalone/app/audit/_components/come-back-better-section.tsx +1 -1
- package/.next/standalone/app/audit/_components/empty-state.tsx +0 -1
- package/.next/standalone/app/audit/_components/how-to-improve-section.tsx +1 -1
- package/.next/standalone/app/audit/_components/quirks-section.tsx +0 -1
- package/.next/standalone/app/audit/_components/report-footer.tsx +0 -2
- package/.next/standalone/app/audit/_components/run-progress.tsx +1 -1
- package/.next/standalone/app/audit/_components/strengths-section.tsx +0 -1
- package/.next/standalone/app/audit/audit-styles.css +6 -149
- package/.next/standalone/app/components/pause-notices.tsx +111 -0
- package/.next/standalone/app/components/raw-log-viewer.tsx +1 -1
- package/.next/standalone/app/components/toast.tsx +1 -1
- package/.next/standalone/app/policies/hooks-client.tsx +84 -8
- package/.next/standalone/app/settings/page.tsx +31 -0
- package/.next/standalone/app/settings/settings-client.tsx +487 -0
- package/.next/standalone/components/navbar.tsx +2 -0
- package/.next/standalone/lib/auth/api-server-client.ts +2 -3
- package/.next/standalone/lib/auth/auth-store.ts +4 -5
- package/.next/standalone/lib/codex-sessions.ts +2 -1
- package/.next/standalone/lib/dashboard-host.ts +101 -0
- package/.next/standalone/lib/install-check.ts +22 -7
- package/.next/standalone/lib/pi-sessions.ts +99 -14
- package/.next/standalone/lib/telemetry-enabled.ts +54 -0
- package/.next/standalone/lib/telemetry-id.ts +52 -1
- package/.next/standalone/lib/telemetry.ts +8 -4
- package/.next/standalone/node_modules/react/cjs/react.development.js +1 -1
- package/.next/standalone/node_modules/react/cjs/react.production.js +1 -1
- package/.next/standalone/node_modules/react/package.json +3 -3
- package/.next/standalone/node_modules/react-dom/cjs/react-dom-server-legacy.browser.production.js +1 -1
- package/.next/standalone/node_modules/react-dom/cjs/react-dom-server-legacy.node.production.js +1 -1
- package/.next/standalone/node_modules/react-dom/cjs/react-dom-server.browser.production.js +3 -3
- package/.next/standalone/node_modules/react-dom/cjs/react-dom-server.edge.production.js +3 -3
- package/.next/standalone/node_modules/react-dom/cjs/react-dom-server.node.production.js +3 -3
- package/.next/standalone/node_modules/react-dom/cjs/react-dom.production.js +1 -1
- package/.next/standalone/node_modules/react-dom/package.json +4 -4
- package/.next/standalone/package.json +23 -12
- package/.next/standalone/proxy.ts +111 -1
- package/.next/standalone/server.js +1 -1
- package/bin/failproofai-worker.mjs +1 -1
- package/bin/failproofai.mjs +1041 -81
- package/bin/failproofaid-shim.mjs +4 -4
- package/dist/cli.mjs +18513 -10474
- package/dist/worker.mjs +1223 -299
- package/lib/auth/api-server-client.ts +2 -3
- package/lib/auth/auth-store.ts +4 -5
- package/lib/codex-sessions.ts +2 -1
- package/lib/dashboard-host.ts +101 -0
- package/lib/install-check.ts +22 -7
- package/lib/pi-sessions.ts +99 -14
- package/lib/telemetry-enabled.ts +54 -0
- package/lib/telemetry-id.ts +52 -1
- package/lib/telemetry.ts +8 -4
- package/package.json +23 -12
- package/scripts/build-daemon-packages.mjs +247 -0
- package/scripts/daemon-platforms.mjs +42 -0
- package/scripts/dev-hook.mjs +46 -5
- package/scripts/install-telemetry.mjs +8 -1
- package/scripts/launch.ts +25 -3
- package/scripts/parse-script-args.ts +27 -1
- package/scripts/prune-standalone.mjs +26 -0
- package/scripts/publish-aliases.mjs +62 -41
- package/scripts/repro-npm-install.sh +155 -0
- package/scripts/translate-docs/cache.ts +1 -1
- package/scripts/translate-docs/cli.ts +2 -2
- package/scripts/translate-docs/mintlify-nav.ts +1 -1
- package/scripts/translate-docs/readme-translator.ts +163 -4
- package/scripts/validate-mdx.ts +153 -13
- package/src/audit/audit-lock.ts +285 -0
- package/src/audit/audit-schedule.ts +111 -0
- package/src/audit/cache.ts +18 -5
- package/src/audit/cli-adapters/goose.ts +2 -1
- package/src/audit/cli-adapters/hermes.ts +28 -10
- package/src/audit/cli.ts +191 -35
- package/src/audit/dashboard-cache.ts +2 -3
- package/src/audit/report.ts +0 -1
- package/src/hooks/backfill-cli.ts +247 -0
- package/src/hooks/builtin-policies.ts +391 -3
- package/src/hooks/cloud-connection.ts +354 -0
- package/src/hooks/cloud-enrollment-cli.ts +439 -0
- package/src/hooks/cloud-enrollment.ts +284 -0
- package/src/hooks/cloud-introspect.ts +147 -0
- package/src/hooks/cloud-managed-policies.ts +206 -0
- package/src/hooks/collector-config.ts +218 -0
- package/src/hooks/configure-wizard.ts +1211 -233
- package/src/hooks/custom-hooks-loader.ts +243 -37
- package/src/hooks/daemon-client.ts +136 -33
- package/src/hooks/daemon-download.ts +264 -31
- package/src/hooks/daemon-service.ts +1124 -76
- package/src/hooks/first-run-gate.ts +50 -0
- package/src/hooks/flush-cli.ts +180 -0
- package/src/hooks/fp-config.ts +835 -0
- package/src/hooks/fp-home.ts +714 -0
- package/src/hooks/fp-reset.ts +1363 -0
- package/src/hooks/handler.ts +185 -6
- package/src/hooks/harness-cli.ts +386 -0
- package/src/hooks/hook-activity-store.ts +171 -24
- package/src/hooks/hook-logger.ts +2 -2
- package/src/hooks/hook-telemetry.ts +5 -1
- package/src/hooks/hooks-config.ts +4 -3
- package/src/hooks/integrations.ts +0 -1
- package/src/hooks/loader-utils.ts +172 -9
- package/src/hooks/manager.ts +41 -5
- package/src/hooks/migrations.ts +450 -0
- package/src/hooks/onboarding-attempt.ts +195 -0
- package/src/hooks/onboarding-lock.ts +118 -0
- package/src/hooks/policy-types.ts +28 -0
- package/src/hooks/session-pause-cli.ts +159 -0
- package/src/hooks/session-pause.ts +267 -0
- package/src/hooks/setup-state.ts +256 -0
- package/src/hooks/tui.ts +209 -9
- package/src/hooks/uninstall-cli.ts +418 -0
- package/src/hooks/worker-server.ts +134 -15
- package/.next/standalone/.next/server/chunks/[externals]__14odj07._.js +0 -3
- package/.next/standalone/.next/server/chunks/[externals]__1s61mel._.js +0 -3
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0id_xf7._.js +0 -3
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0longh-._.js +0 -3
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__17g9wh7._.js +0 -3
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__19120tr._.js +0 -3
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__1fwl2mz._.js +0 -3
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__1myjm-d._.js +0 -3
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__1rhmvod._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/[externals]__1_g_b3t._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0114ewg._.js +0 -4
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__02r5bgf._.js +0 -4
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0e8sjqm._.js +0 -4
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0lkzqax._.js +0 -4
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0nb0syg._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0qucxyj._.js +0 -4
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0u02miy._.js +0 -4
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0uno39i._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__14dd6h8._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1ath6v_._.js +0 -4
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1psjybo._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1zqz4v8._.js +0 -4
- package/.next/standalone/.next/server/chunks/ssr/_1m_p7pi._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/node_modules_html-to-image_es_index_0oy_-36.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/src_hooks_1ezd2jf._.js +0 -5
- package/.next/standalone/.next/server/chunks/ssr/src_hooks_1tnuifj._.js +0 -5
- package/.next/standalone/.next/static/chunks/0h7auy7hzjyhw.js +0 -1
- package/.next/standalone/.next/static/chunks/0zbxssxh53n-3.js +0 -1
- package/.next/standalone/.next/static/chunks/2mazwb1qmur1p.js +0 -1
- package/.next/standalone/.next/static/chunks/2pujpnfx4pws6.css +0 -2
- package/.next/standalone/.next/static/chunks/33u59vf_8xpd-.js +0 -1
- package/.next/standalone/.next/static/chunks/3i27c3hcriawq.css +0 -1
- package/.next/standalone/.next/static/chunks/3s7myk_co0gbh.js +0 -1
- package/.next/standalone/.next/static/chunks/40k1_v4os4cw6.js +0 -6
- package/.next/standalone/Cargo.lock +0 -428
- package/.next/standalone/Cargo.toml +0 -9
- package/.next/standalone/crates/PROTOCOL.md +0 -122
- package/.next/standalone/crates/failproofaid/Cargo.toml +0 -18
- package/.next/standalone/crates/failproofaid/src/lock.rs +0 -81
- package/.next/standalone/crates/failproofaid/src/main.rs +0 -83
- package/.next/standalone/crates/failproofaid/src/paths.rs +0 -172
- package/.next/standalone/crates/failproofaid/src/server.rs +0 -502
- package/.next/standalone/crates/failproofaid/src/worker.rs +0 -400
- package/.next/standalone/crates/failproofaid/tests/daemon_e2e.rs +0 -241
- package/.next/standalone/crates/fpai-ipc/Cargo.toml +0 -16
- package/.next/standalone/crates/fpai-ipc/src/envelope.rs +0 -175
- package/.next/standalone/crates/fpai-ipc/src/framing.rs +0 -177
- package/.next/standalone/crates/fpai-ipc/src/lib.rs +0 -10
- package/.next/standalone/crates/fpai-ipc/src/peer.rs +0 -80
- package/.next/standalone/docker-hook-sync/Dockerfile +0 -79
- package/.next/standalone/docker-hook-sync/entrypoint.sh +0 -109
- package/.next/standalone/integration-suite/Dockerfile +0 -47
- package/.next/standalone/integration-suite/README.md +0 -129
- package/.next/standalone/integration-suite/canary-policies.mjs +0 -45
- package/.next/standalone/integration-suite/capture-tokens.sh +0 -48
- package/.next/standalone/integration-suite/ci-entrypoint.sh +0 -179
- package/.next/standalone/integration-suite/inject-tokens.sh +0 -44
- package/.next/standalone/integration-suite/install-clis.sh +0 -136
- package/.next/standalone/integration-suite/probe-cli.sh +0 -248
- package/.next/standalone/integration-suite/report.js +0 -120
- package/.next/standalone/integration-suite/run.sh +0 -159
- package/.next/standalone/openclaw-plugin/index.js +0 -223
- package/.next/standalone/openclaw-plugin/openclaw.plugin.json +0 -6
- package/.next/standalone/openclaw-plugin/package.json +0 -12
- package/.next/standalone/osv-scanner.toml +0 -17
- package/.next/standalone/pi-extension/index.ts +0 -524
- package/.next/standalone/pi-extension/package.json +0 -12
- package/.next/standalone/postcss.config.mjs +0 -8
- package/.next/standalone/readme-arch-hq.gif +0 -0
- package/.next/standalone/rust-toolchain.toml +0 -4
- package/src/auth/cli.ts +0 -371
- /package/.next/standalone/.next/static/{NWAjs0tHFyzk97gRGG09r → VjEdIL6W5uBsvEzknXqV9}/_buildManifest.js +0 -0
- /package/.next/standalone/.next/static/{NWAjs0tHFyzk97gRGG09r → VjEdIL6W5uBsvEzknXqV9}/_clientMiddlewareManifest.js +0 -0
- /package/.next/standalone/.next/static/{NWAjs0tHFyzk97gRGG09r → VjEdIL6W5uBsvEzknXqV9}/_ssgManifest.js +0 -0
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Per-session enforcement pause — the state behind `failproofai config --pause`.
|
|
3
|
+
*
|
|
4
|
+
* A pause suspends LOCAL policy (builtins, explicit custom, convention) for one
|
|
5
|
+
* agent session, for a bounded time. It is deliberately NOT configuration:
|
|
6
|
+
*
|
|
7
|
+
* - Config is persistent, merged across project/local/global, and routinely
|
|
8
|
+
* committed to git. A paused enforcement state written there would outlive
|
|
9
|
+
* the session that asked for it and travel to everyone who checks out the
|
|
10
|
+
* branch — enforcement silently off for a whole team.
|
|
11
|
+
* - So pause state lives here, under `~/.failproofai/state/sessions/`, owner-
|
|
12
|
+
* only, keyed by session, and carrying an absolute expiry.
|
|
13
|
+
*
|
|
14
|
+
* Disk is the source of truth rather than the daemon: most machines have no
|
|
15
|
+
* daemon at all, and even where one exists the CLI that writes a pause is a
|
|
16
|
+
* different process from the hook that reads it. A daemon may cache this; it
|
|
17
|
+
* must not own it.
|
|
18
|
+
*
|
|
19
|
+
* Expiry is evaluated at READ time against the clock, never by a sweeper. A
|
|
20
|
+
* stale file is therefore inert rather than dangerous — the failure mode worth
|
|
21
|
+
* engineering against is not "the pause didn't work", it is "the pause silently
|
|
22
|
+
* never ended".
|
|
23
|
+
*/
|
|
24
|
+
import { existsSync, mkdirSync, readdirSync, readFileSync, rmSync } from "node:fs";
|
|
25
|
+
import { createHash } from "node:crypto";
|
|
26
|
+
import { join, resolve } from "node:path";
|
|
27
|
+
import { writeJsonAtomically } from "../../lib/atomic-write";
|
|
28
|
+
import { getAllHookActivityEntries } from "./hook-activity-store";
|
|
29
|
+
import { sessionPauseDir } from "./fp-home";
|
|
30
|
+
|
|
31
|
+
const SCHEMA_VERSION = 1;
|
|
32
|
+
|
|
33
|
+
/** Default when `--pause` is given no duration. */
|
|
34
|
+
export const PAUSE_DEFAULT_MS = 30 * 60_000;
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Hard ceiling. Config may lower this, never raise it — a ceiling a project
|
|
38
|
+
* could raise is not a ceiling, and the whole point is that a forgotten pause
|
|
39
|
+
* costs little.
|
|
40
|
+
*/
|
|
41
|
+
export const PAUSE_CEILING_MS = 8 * 60 * 60_000;
|
|
42
|
+
|
|
43
|
+
export interface ActivePause {
|
|
44
|
+
sessionId: string;
|
|
45
|
+
/** Epoch ms. */
|
|
46
|
+
pausedAt: number;
|
|
47
|
+
/**
|
|
48
|
+
* Epoch ms of the FIRST pause in this unbroken run, which the ceiling is
|
|
49
|
+
* measured from.
|
|
50
|
+
*
|
|
51
|
+
* `pausedAt` moves to now on every renewal, so a ceiling measured from it is
|
|
52
|
+
* not a ceiling at all: `--pause 8h` every seven hours suspends enforcement
|
|
53
|
+
* indefinitely, one legal command at a time, and each individual pause passes
|
|
54
|
+
* every check. This field is what makes 8h mean 8h. Carried forward by
|
|
55
|
+
* `writePause` while a pause is still live, and reset once one has expired —
|
|
56
|
+
* the limit is on how long enforcement can stay off in one stretch, not a
|
|
57
|
+
* daily quota.
|
|
58
|
+
*/
|
|
59
|
+
firstPausedAt: number;
|
|
60
|
+
/** Epoch ms. Always finite — there is no unbounded pause. */
|
|
61
|
+
expiresAt: number;
|
|
62
|
+
/** Free-form provenance, e.g. "cli". Recorded so the activity log can say who. */
|
|
63
|
+
setBy: string;
|
|
64
|
+
/** Where it was issued from, for `--status` and for cwd-based resolution. */
|
|
65
|
+
cwd?: string;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
interface StoredPause extends ActivePause {
|
|
69
|
+
schemaVersion: number;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export function pauseStateDir(): string {
|
|
73
|
+
const override = process.env.FAILPROOFAI_STATE_DIR;
|
|
74
|
+
if (override) return resolve(override, "sessions");
|
|
75
|
+
return sessionPauseDir();
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Filename is a digest of the session id, not the id itself.
|
|
80
|
+
*
|
|
81
|
+
* Session ids come from twelve different agent CLIs and are not a format we
|
|
82
|
+
* control — Hermes and OpenClaw mint their own, and nothing stops one
|
|
83
|
+
* containing a path separator or `..`. Hashing sidesteps the entire class
|
|
84
|
+
* rather than trying to enumerate what is safe; the real id is stored inside
|
|
85
|
+
* the file, so `--status` and `--all` still report it.
|
|
86
|
+
*/
|
|
87
|
+
function pauseFilePath(sessionId: string): string {
|
|
88
|
+
const digest = createHash("sha256").update(sessionId).digest("hex").slice(0, 32);
|
|
89
|
+
return join(pauseStateDir(), `${digest}.json`);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Parse `10m` / `2h` / `90s`, or a bare integer read as minutes.
|
|
94
|
+
*
|
|
95
|
+
* Refuses anything over the ceiling rather than silently clamping: someone
|
|
96
|
+
* typing `--pause 12h` has a expectation about how long enforcement is off,
|
|
97
|
+
* and quietly giving them 8h is worse than telling them no.
|
|
98
|
+
*/
|
|
99
|
+
export function parsePauseDuration(input: string | undefined, ceilingMs = PAUSE_CEILING_MS): number {
|
|
100
|
+
if (input === undefined || input === "") return Math.min(PAUSE_DEFAULT_MS, ceilingMs);
|
|
101
|
+
|
|
102
|
+
const match = /^(\d+)\s*(s|m|h)?$/i.exec(input.trim());
|
|
103
|
+
if (!match) {
|
|
104
|
+
throw new Error(
|
|
105
|
+
`Invalid duration ${JSON.stringify(input)}. Use e.g. 30m, 2h, 90s (a bare number means minutes).`,
|
|
106
|
+
);
|
|
107
|
+
}
|
|
108
|
+
const value = Number(match[1]);
|
|
109
|
+
const unit = (match[2] ?? "m").toLowerCase();
|
|
110
|
+
const ms = unit === "s" ? value * 1000 : unit === "h" ? value * 3_600_000 : value * 60_000;
|
|
111
|
+
|
|
112
|
+
if (ms <= 0) throw new Error("Duration must be greater than zero.");
|
|
113
|
+
if (ms > ceilingMs) {
|
|
114
|
+
throw new Error(`Duration ${input} exceeds the maximum pause of ${formatDuration(ceilingMs)}.`);
|
|
115
|
+
}
|
|
116
|
+
return ms;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/** Human-readable, for CLI output and log reasons. */
|
|
120
|
+
export function formatDuration(ms: number): string {
|
|
121
|
+
if (ms < 60_000) return `${Math.round(ms / 1000)}s`;
|
|
122
|
+
const minutes = Math.round(ms / 60_000);
|
|
123
|
+
if (minutes < 60) return `${minutes}m`;
|
|
124
|
+
const hours = Math.floor(minutes / 60);
|
|
125
|
+
const rest = minutes % 60;
|
|
126
|
+
return rest === 0 ? `${hours}h` : `${hours}h${rest}m`;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function parseStored(raw: unknown): StoredPause | null {
|
|
130
|
+
if (!raw || typeof raw !== "object") return null;
|
|
131
|
+
const p = raw as Partial<StoredPause>;
|
|
132
|
+
if (p.schemaVersion !== SCHEMA_VERSION) return null;
|
|
133
|
+
if (typeof p.sessionId !== "string" || p.sessionId.length === 0) return null;
|
|
134
|
+
if (!Number.isFinite(p.pausedAt) || !Number.isFinite(p.expiresAt)) return null;
|
|
135
|
+
const pausedAt = p.pausedAt as number;
|
|
136
|
+
// A file written before this field existed measures its ceiling from the
|
|
137
|
+
// only start it recorded. Same meaning for a pause that was never renewed.
|
|
138
|
+
const firstPausedAt = Number.isFinite(p.firstPausedAt) ? (p.firstPausedAt as number) : pausedAt;
|
|
139
|
+
return {
|
|
140
|
+
schemaVersion: SCHEMA_VERSION,
|
|
141
|
+
sessionId: p.sessionId,
|
|
142
|
+
pausedAt,
|
|
143
|
+
firstPausedAt,
|
|
144
|
+
// Clamped at READ time, not only at write. The ceiling has to hold against
|
|
145
|
+
// a file this process did not write: `~/.failproofai/state/sessions/` is
|
|
146
|
+
// owner-writable by design, and an `expiresAt` a year out would otherwise
|
|
147
|
+
// be honoured on every subsequent tool call forever. Enforcing it here
|
|
148
|
+
// means the bound is a property of the pause, not of the path that created
|
|
149
|
+
// it.
|
|
150
|
+
expiresAt: Math.min(p.expiresAt as number, firstPausedAt + PAUSE_CEILING_MS),
|
|
151
|
+
setBy: typeof p.setBy === "string" ? p.setBy : "unknown",
|
|
152
|
+
...(typeof p.cwd === "string" ? { cwd: p.cwd } : {}),
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* The active pause for a session, or null.
|
|
158
|
+
*
|
|
159
|
+
* Never throws: this is called on the hook path, and a malformed or unreadable
|
|
160
|
+
* state file must mean "enforcement is ON" rather than crashing the evaluation.
|
|
161
|
+
* Failing toward enforcement is the only safe direction here.
|
|
162
|
+
*/
|
|
163
|
+
export function readActivePause(sessionId: string | undefined, now = Date.now()): ActivePause | null {
|
|
164
|
+
if (!sessionId) return null;
|
|
165
|
+
try {
|
|
166
|
+
const path = pauseFilePath(sessionId);
|
|
167
|
+
if (!existsSync(path)) return null;
|
|
168
|
+
const stored = parseStored(JSON.parse(readFileSync(path, "utf8")));
|
|
169
|
+
if (!stored) return null;
|
|
170
|
+
if (stored.expiresAt <= now) return null; // expired — inert, not dangerous
|
|
171
|
+
const { schemaVersion: _ignored, ...pause } = stored;
|
|
172
|
+
return pause;
|
|
173
|
+
} catch {
|
|
174
|
+
return null;
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
export function writePause(opts: {
|
|
179
|
+
sessionId: string;
|
|
180
|
+
durationMs: number;
|
|
181
|
+
cwd?: string;
|
|
182
|
+
setBy?: string;
|
|
183
|
+
now?: number;
|
|
184
|
+
}): ActivePause {
|
|
185
|
+
const now = opts.now ?? Date.now();
|
|
186
|
+
// Carry the run's original start forward while a pause is still live, so a
|
|
187
|
+
// renewal extends the same stretch rather than starting a fresh ceiling.
|
|
188
|
+
const existing = readActivePause(opts.sessionId, now);
|
|
189
|
+
const firstPausedAt = existing?.firstPausedAt ?? now;
|
|
190
|
+
const pause: ActivePause = {
|
|
191
|
+
sessionId: opts.sessionId,
|
|
192
|
+
pausedAt: now,
|
|
193
|
+
firstPausedAt,
|
|
194
|
+
// The requested duration, but never past the ceiling measured from the
|
|
195
|
+
// start of the run. A renewal can shorten a pause; it cannot outrun 8h.
|
|
196
|
+
expiresAt: Math.min(now + opts.durationMs, firstPausedAt + PAUSE_CEILING_MS),
|
|
197
|
+
setBy: opts.setBy ?? "cli",
|
|
198
|
+
...(opts.cwd ? { cwd: opts.cwd } : {}),
|
|
199
|
+
};
|
|
200
|
+
mkdirSync(pauseStateDir(), { recursive: true, mode: 0o700 });
|
|
201
|
+
writeJsonAtomically(pauseFilePath(opts.sessionId), { schemaVersion: SCHEMA_VERSION, ...pause });
|
|
202
|
+
return pause;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/** Returns true if a pause file was removed. */
|
|
206
|
+
export function clearPause(sessionId: string): boolean {
|
|
207
|
+
const path = pauseFilePath(sessionId);
|
|
208
|
+
if (!existsSync(path)) return false;
|
|
209
|
+
try {
|
|
210
|
+
rmSync(path);
|
|
211
|
+
return true;
|
|
212
|
+
} catch {
|
|
213
|
+
return false;
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/** Every pause that has not yet expired, newest first. Expired files are ignored. */
|
|
218
|
+
export function listActivePauses(now = Date.now()): ActivePause[] {
|
|
219
|
+
const dir = pauseStateDir();
|
|
220
|
+
if (!existsSync(dir)) return [];
|
|
221
|
+
const out: ActivePause[] = [];
|
|
222
|
+
let names: string[];
|
|
223
|
+
try {
|
|
224
|
+
names = readdirSync(dir);
|
|
225
|
+
} catch {
|
|
226
|
+
return [];
|
|
227
|
+
}
|
|
228
|
+
for (const name of names) {
|
|
229
|
+
if (!name.endsWith(".json")) continue;
|
|
230
|
+
try {
|
|
231
|
+
const stored = parseStored(JSON.parse(readFileSync(join(dir, name), "utf8")));
|
|
232
|
+
if (!stored || stored.expiresAt <= now) continue;
|
|
233
|
+
const { schemaVersion: _ignored, ...pause } = stored;
|
|
234
|
+
out.push(pause);
|
|
235
|
+
} catch {
|
|
236
|
+
// A single unreadable file must not hide every other pause from --status.
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
return out.sort((a, b) => b.pausedAt - a.pausedAt);
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
/**
|
|
243
|
+
* The session to act on when the user names none.
|
|
244
|
+
*
|
|
245
|
+
* Whoever types the command has no session id — it exists only inside the hook
|
|
246
|
+
* payload. But every hook event already records `sessionId` alongside `cwd` and
|
|
247
|
+
* a timestamp, so the most recent session seen in this directory is derivable
|
|
248
|
+
* from data we already write. Returns null when nothing recent matches, so the
|
|
249
|
+
* caller can say so instead of guessing at a stranger's session.
|
|
250
|
+
*/
|
|
251
|
+
export function resolveSessionForCwd(cwd: string, maxAgeMs = 12 * 3_600_000, now = Date.now()): string | null {
|
|
252
|
+
let best: { sessionId: string; timestamp: number } | null = null;
|
|
253
|
+
let entries;
|
|
254
|
+
try {
|
|
255
|
+
entries = getAllHookActivityEntries();
|
|
256
|
+
} catch {
|
|
257
|
+
return null;
|
|
258
|
+
}
|
|
259
|
+
for (const entry of entries) {
|
|
260
|
+
if (!entry.sessionId || entry.cwd !== cwd) continue;
|
|
261
|
+
if (now - entry.timestamp > maxAgeMs) continue;
|
|
262
|
+
if (!best || entry.timestamp > best.timestamp) {
|
|
263
|
+
best = { sessionId: entry.sessionId, timestamp: entry.timestamp };
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
return best?.sessionId ?? null;
|
|
267
|
+
}
|
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What is already set up on this machine, and where.
|
|
3
|
+
*
|
|
4
|
+
* Two questions live here, both of which used to be answered ad hoc in two
|
|
5
|
+
* different places:
|
|
6
|
+
*
|
|
7
|
+
* 1. "Has this user ever completed setup?" — drives first-run onboarding.
|
|
8
|
+
* 2. "Is this cwd a project, a home directory, or both?" — drives which
|
|
9
|
+
* scope the wizard offers to configure.
|
|
10
|
+
*
|
|
11
|
+
* Both are pure functions over the filesystem so they can be unit-tested
|
|
12
|
+
* without a TTY, a wizard, or a real home directory. Every path is derived
|
|
13
|
+
* from an injectable `home`/`cwd` rather than read from the environment at
|
|
14
|
+
* call time, because the wizard, the CLI entry point and the tests each have
|
|
15
|
+
* a different idea of where "home" is.
|
|
16
|
+
*/
|
|
17
|
+
import { existsSync, realpathSync, statSync } from "node:fs";
|
|
18
|
+
import { dirname, resolve } from "node:path";
|
|
19
|
+
import { homedir } from "node:os";
|
|
20
|
+
import { getIntegration } from "./integrations";
|
|
21
|
+
import { INTEGRATION_TYPES } from "./types";
|
|
22
|
+
import { globalPolicyConfigFile, launcherMarker as layoutLauncherMarker } from "./fp-home";
|
|
23
|
+
|
|
24
|
+
/** Which config a wizard run is going to write. */
|
|
25
|
+
export type SetupTarget = "user" | "project" | "both";
|
|
26
|
+
|
|
27
|
+
export interface SetupState {
|
|
28
|
+
/** Absolute cwd this was resolved against. */
|
|
29
|
+
cwd: string;
|
|
30
|
+
/** Absolute home directory this was resolved against. */
|
|
31
|
+
home: string;
|
|
32
|
+
/**
|
|
33
|
+
* The directory a project-scope write would land in — the nearest ancestor
|
|
34
|
+
* holding a `.failproofai/`, else `cwd` itself. Never the home directory:
|
|
35
|
+
* `~/.failproofai` is the GLOBAL config, and treating it as a project root
|
|
36
|
+
* would make a wizard run from `~` offer to "configure this project" and
|
|
37
|
+
* then write the global file under a project label.
|
|
38
|
+
*/
|
|
39
|
+
projectRoot: string;
|
|
40
|
+
/** `~/.failproofai/policies-config.json` exists. */
|
|
41
|
+
hasGlobalConfig: boolean;
|
|
42
|
+
/** `<projectRoot>/.failproofai/policies-config.json` exists. */
|
|
43
|
+
hasProjectConfig: boolean;
|
|
44
|
+
/** Any agent CLI has failproofai hooks installed at user scope. */
|
|
45
|
+
hasGlobalHooks: boolean;
|
|
46
|
+
/** The legacy first-run marker written by earlier versions. */
|
|
47
|
+
hasLegacyMarker: boolean;
|
|
48
|
+
/** cwd resolves to the home directory itself. */
|
|
49
|
+
inHomeDir: boolean;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export function globalConfigPath(home: string = homedir()): string {
|
|
53
|
+
return globalPolicyConfigFile(home);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export function projectConfigPath(projectRoot: string): string {
|
|
57
|
+
return resolve(projectRoot, ".failproofai", "policies-config.json");
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function legacyMarkerPath(home: string): string {
|
|
61
|
+
return layoutLauncherMarker(home);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function isDir(path: string): boolean {
|
|
65
|
+
try {
|
|
66
|
+
return statSync(path).isDirectory();
|
|
67
|
+
} catch {
|
|
68
|
+
return false;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* The nearest ancestor of `start` holding a `.failproofai/` directory, or
|
|
74
|
+
* `start` itself when there is none.
|
|
75
|
+
*
|
|
76
|
+
* Mirrors `hooks-config.findProjectConfigDir`, but takes `home` as a
|
|
77
|
+
* parameter instead of calling `homedir()` — the wizard needs to resolve this
|
|
78
|
+
* against a caller-supplied home in tests, and the two must not disagree
|
|
79
|
+
* about where the walk stops or the wizard would offer to write a project
|
|
80
|
+
* config at a path the policy loader never reads.
|
|
81
|
+
*/
|
|
82
|
+
export function findProjectRoot(start: string, home: string = homedir()): string {
|
|
83
|
+
// Both sides real-resolved for the same reason `detectSetupState` does it:
|
|
84
|
+
// a symlinked home would never equal the walked path, so the walk would run
|
|
85
|
+
// straight past home and could claim `~` — whose `.failproofai/` is the
|
|
86
|
+
// global config — as a project root.
|
|
87
|
+
const resolvedHome = realResolve(home);
|
|
88
|
+
let dir = realResolve(start);
|
|
89
|
+
while (dir !== resolvedHome) {
|
|
90
|
+
if (isDir(resolve(dir, ".failproofai"))) return dir;
|
|
91
|
+
const parent = dirname(dir);
|
|
92
|
+
if (parent === dir) break;
|
|
93
|
+
dir = parent;
|
|
94
|
+
}
|
|
95
|
+
return resolve(start);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Whether any agent CLI carries failproofai hooks at user scope.
|
|
100
|
+
*
|
|
101
|
+
* Deliberately ignores project scope: a checkout that happens to contain
|
|
102
|
+
* committed project hooks (this repo does) must not convince the wizard that
|
|
103
|
+
* the MACHINE has been set up.
|
|
104
|
+
*/
|
|
105
|
+
export function hasGlobalHooksInstalled(): boolean {
|
|
106
|
+
for (const id of INTEGRATION_TYPES) {
|
|
107
|
+
try {
|
|
108
|
+
if (getIntegration(id).hooksInstalledInSettings("user")) return true;
|
|
109
|
+
} catch {
|
|
110
|
+
// A malformed settings file is not evidence of an install.
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
return false;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* `resolve()` plus symlink resolution, falling back to the lexical path.
|
|
118
|
+
*
|
|
119
|
+
* `$HOME` is very often a symlink (`/home/x` → `/mnt/data/x`, macOS
|
|
120
|
+
* `/tmp` → `/private/tmp`) while a shell's cwd reports the real path. Compared
|
|
121
|
+
* lexically, the two never match: `inHomeDir` reads false while standing in
|
|
122
|
+
* home, and the wizard offers to "configure this project" pointing at the home
|
|
123
|
+
* directory — whose `.failproofai/` is the GLOBAL config. Comparing real paths
|
|
124
|
+
* is what makes "am I in my home directory" answerable.
|
|
125
|
+
*
|
|
126
|
+
* Falls back to the lexical path when the target does not exist (a cwd that
|
|
127
|
+
* has been deleted, a home that has not been created yet), because a missing
|
|
128
|
+
* path is not a reason to fail detection.
|
|
129
|
+
*/
|
|
130
|
+
function realResolve(path: string): string {
|
|
131
|
+
const lexical = resolve(path);
|
|
132
|
+
try {
|
|
133
|
+
return realpathSync(lexical);
|
|
134
|
+
} catch {
|
|
135
|
+
return lexical;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
export function detectSetupState(
|
|
140
|
+
cwd: string = process.cwd(),
|
|
141
|
+
home: string = homedir(),
|
|
142
|
+
): SetupState {
|
|
143
|
+
const resolvedCwd = realResolve(cwd);
|
|
144
|
+
const resolvedHome = realResolve(home);
|
|
145
|
+
const projectRoot = findProjectRoot(resolvedCwd, resolvedHome);
|
|
146
|
+
|
|
147
|
+
return {
|
|
148
|
+
cwd: resolvedCwd,
|
|
149
|
+
home: resolvedHome,
|
|
150
|
+
projectRoot,
|
|
151
|
+
hasGlobalConfig: existsSync(globalConfigPath(resolvedHome)),
|
|
152
|
+
// When cwd IS home, `projectRoot` is home and its `.failproofai` is the
|
|
153
|
+
// global config — reporting that as a project config would double-count
|
|
154
|
+
// one file as two scopes and offer "Both" for a single target.
|
|
155
|
+
hasProjectConfig:
|
|
156
|
+
projectRoot !== resolvedHome && existsSync(projectConfigPath(projectRoot)),
|
|
157
|
+
hasGlobalHooks: hasGlobalHooksInstalled(),
|
|
158
|
+
hasLegacyMarker: existsSync(legacyMarkerPath(resolvedHome)),
|
|
159
|
+
inHomeDir: resolvedCwd === resolvedHome,
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Whether this machine has ever completed setup.
|
|
165
|
+
*
|
|
166
|
+
* Deliberately NOT just the marker file. The marker is one of three
|
|
167
|
+
* independent signals, and any of them means "configured":
|
|
168
|
+
*
|
|
169
|
+
* • a global policies-config.json — the wizard, or `policies --install`,
|
|
170
|
+
* has written real configuration
|
|
171
|
+
* • user-scope hooks in any agent CLI — enforcement is live even if the
|
|
172
|
+
* config file was hand-deleted
|
|
173
|
+
* • the legacy marker — so users onboarded by an earlier version are never
|
|
174
|
+
* shown the wizard a second time
|
|
175
|
+
*
|
|
176
|
+
* Relying on the marker alone made deleting one file re-run onboarding on a
|
|
177
|
+
* fully configured machine; relying on hooks alone re-ran it for anyone who
|
|
178
|
+
* had uninstalled hooks but kept their policy config. Requiring all three
|
|
179
|
+
* would never fire for a genuinely fresh install. The union is the only
|
|
180
|
+
* combination that is right in each case.
|
|
181
|
+
*/
|
|
182
|
+
export function isConfigured(state: SetupState = detectSetupState()): boolean {
|
|
183
|
+
return state.hasGlobalConfig || state.hasGlobalHooks || state.hasLegacyMarker;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
export interface TargetChoice {
|
|
187
|
+
label: string;
|
|
188
|
+
value: SetupTarget;
|
|
189
|
+
hint: string;
|
|
190
|
+
/** True when this target would UPDATE existing config rather than create it. */
|
|
191
|
+
existing: boolean;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* The scope choices to offer, ordered so the most likely intent is first
|
|
196
|
+
* (and therefore pre-selected).
|
|
197
|
+
*
|
|
198
|
+
* The ordering rule is "what did the user point us at": run from inside a
|
|
199
|
+
* project, the project comes first; run from home — where there is no project
|
|
200
|
+
* to configure — only the global row is offered, because a "Just this
|
|
201
|
+
* project" row pointing at `~` would write `~/.failproofai/` and call the
|
|
202
|
+
* machine-wide config a project.
|
|
203
|
+
*/
|
|
204
|
+
export function buildTargetChoices(state: SetupState): TargetChoice[] {
|
|
205
|
+
const globalRow: TargetChoice = {
|
|
206
|
+
label: state.hasGlobalConfig ? "Update my global policies" : "Set up global policies",
|
|
207
|
+
value: "user",
|
|
208
|
+
hint: "applies in every project on this machine",
|
|
209
|
+
existing: state.hasGlobalConfig,
|
|
210
|
+
};
|
|
211
|
+
|
|
212
|
+
// From home (or anywhere with no project marker above it) there is no
|
|
213
|
+
// second target to offer.
|
|
214
|
+
if (state.inHomeDir || state.projectRoot === state.home) {
|
|
215
|
+
return [globalRow];
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
const projectRow: TargetChoice = {
|
|
219
|
+
label: state.hasProjectConfig
|
|
220
|
+
? "Update this project's policies"
|
|
221
|
+
: "Set up this project's policies",
|
|
222
|
+
value: "project",
|
|
223
|
+
hint: homeify(state.projectRoot, state.home),
|
|
224
|
+
existing: state.hasProjectConfig,
|
|
225
|
+
};
|
|
226
|
+
|
|
227
|
+
const bothRow: TargetChoice = {
|
|
228
|
+
label: "Both",
|
|
229
|
+
value: "both",
|
|
230
|
+
hint: "global defaults, plus overrides for this project",
|
|
231
|
+
existing: state.hasGlobalConfig && state.hasProjectConfig,
|
|
232
|
+
};
|
|
233
|
+
|
|
234
|
+
// Inside a project, the project is the likelier intent — unless nothing
|
|
235
|
+
// global exists yet, in which case the machine has never been set up and
|
|
236
|
+
// configuring only this one project would leave every other project
|
|
237
|
+
// unprotected without saying so.
|
|
238
|
+
return state.hasGlobalConfig
|
|
239
|
+
? [projectRow, globalRow, bothRow]
|
|
240
|
+
: [globalRow, projectRow, bothRow];
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
/** `/home/x/y` → `~/y`, for display only. */
|
|
244
|
+
export function homeify(path: string, home: string = homedir()): string {
|
|
245
|
+
const resolvedHome = resolve(home);
|
|
246
|
+
const resolvedPath = resolve(path);
|
|
247
|
+
if (resolvedPath === resolvedHome) return "~";
|
|
248
|
+
return resolvedPath.startsWith(resolvedHome + "/")
|
|
249
|
+
? `~${resolvedPath.slice(resolvedHome.length)}`
|
|
250
|
+
: resolvedPath;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
/** The scopes a `SetupTarget` expands to. */
|
|
254
|
+
export function scopesFor(target: SetupTarget): Array<"user" | "project"> {
|
|
255
|
+
return target === "both" ? ["user", "project"] : [target];
|
|
256
|
+
}
|