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
|
@@ -7,35 +7,53 @@
|
|
|
7
7
|
* producing the same LogEntry[] shape the other adapters do — so
|
|
8
8
|
* `logEntriesToEvents` handles the rest.
|
|
9
9
|
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
10
|
+
* Hermes sessions are NOT uniformly cwd-less. This file used to assert they were
|
|
11
|
+
* and returned nothing at all for `audit --project <cwd>`, so a Hermes session
|
|
12
|
+
* driven in a repo silently contributed zero findings to that project's audit.
|
|
13
|
+
* Verified against hermes-agent 0.19.0: `sessions` carries real `cwd`,
|
|
14
|
+
* `git_branch` and `git_repo_root` columns, and every `source='cli'` session
|
|
15
|
+
* populated them.
|
|
16
|
+
*
|
|
17
|
+
* Both shapes are real, so both are handled:
|
|
18
|
+
* • a session WITH a cwd groups by working directory like Claude/Goose/Devin,
|
|
19
|
+
* and takes part in cwd-scoped audits;
|
|
20
|
+
* • a session WITHOUT one — a Slack/Telegram gateway run, which genuinely
|
|
21
|
+
* isn't in a repo — keeps grouping by (profile, `source`), since a Hermes
|
|
22
|
+
* profile is a whole separate home dir with its own state.db
|
|
23
|
+
* (lib/hermes-profiles.ts), and is correctly excluded from a cwd filter.
|
|
13
24
|
*/
|
|
14
25
|
import { getHermesSessions } from "../../../lib/hermes-projects";
|
|
15
26
|
import { hermesProjectPath } from "../../../lib/hermes-profiles";
|
|
16
27
|
import { getHermesSessionLog } from "../../../lib/hermes-sessions";
|
|
28
|
+
import { encodeFolderName } from "../../../lib/paths";
|
|
17
29
|
import type { NormalizedToolEvent, TranscriptMetadata } from "../types";
|
|
18
30
|
import type { ListOpts } from "./claude";
|
|
19
31
|
import { logEntriesToEvents } from "./shared";
|
|
20
32
|
|
|
33
|
+
/** Grouping label for one session: its working directory when it has one,
|
|
34
|
+
* else the (profile, source) bucket gateway sessions have always used. */
|
|
35
|
+
function hermesProjectName(s: { profile: string; source?: string; cwd?: string }): string {
|
|
36
|
+
if (s.cwd) return encodeFolderName(s.cwd);
|
|
37
|
+
return s.source ? hermesProjectPath(s.profile, s.source) : `hermes:${s.profile}`;
|
|
38
|
+
}
|
|
39
|
+
|
|
21
40
|
export async function listHermesTranscriptMetadata(
|
|
22
41
|
opts: ListOpts = {},
|
|
23
42
|
): Promise<TranscriptMetadata[]> {
|
|
24
|
-
|
|
25
|
-
// none, so Hermes contributes nothing to a cwd-scoped audit.
|
|
26
|
-
if (opts.projects && opts.projects.length > 0) return [];
|
|
27
|
-
|
|
43
|
+
const projectFilter = opts.projects ? new Set(opts.projects) : null;
|
|
28
44
|
const sinceMs = opts.sinceMs ?? 0;
|
|
29
45
|
const sessions = await getHermesSessions();
|
|
30
46
|
const out: TranscriptMetadata[] = [];
|
|
31
47
|
for (const s of sessions) {
|
|
32
48
|
if (s.mtimeMs < sinceMs) continue;
|
|
33
49
|
if (s.messageCount <= 0 && !s.hasMessages) continue; // empty → no events (message_count can lag; trust real messages)
|
|
50
|
+
// `audit --project <cwd>` filters on the session's working directory, the
|
|
51
|
+
// same comparison Claude and Goose make. A session with no cwd is not in
|
|
52
|
+
// any project, so it drops out here rather than at the top of the function.
|
|
53
|
+
if (projectFilter && (!s.cwd || !projectFilter.has(s.cwd))) continue;
|
|
34
54
|
out.push({
|
|
35
55
|
cli: "hermes",
|
|
36
|
-
|
|
37
|
-
// profile is a separate Hermes home with its own state.db.
|
|
38
|
-
projectName: s.source ? hermesProjectPath(s.profile, s.source) : `hermes:${s.profile}`,
|
|
56
|
+
projectName: hermesProjectName(s),
|
|
39
57
|
sessionId: s.sessionId,
|
|
40
58
|
transcriptPath: `hermes://${s.sessionId}`,
|
|
41
59
|
mtimeMs: s.mtimeMs,
|
package/src/audit/cli.ts
CHANGED
|
@@ -14,8 +14,14 @@
|
|
|
14
14
|
* No arguments yet — a bare `failproofai audit` does a full scan (all CLIs, all
|
|
15
15
|
* history). Flags (--since, --cli, --project, --port, --no-open) are easy
|
|
16
16
|
* follow-ups against `RunAuditOptions`.
|
|
17
|
+
*
|
|
18
|
+
* `--scheduled` is the one exception, and it is not a flag on the interactive
|
|
19
|
+
* command so much as a second entry point sharing its name: it runs the same
|
|
20
|
+
* scan with no TTY, no browser and no server, and reports its outcome through
|
|
21
|
+
* an exit code. See `runScheduledAudit`.
|
|
17
22
|
*/
|
|
18
23
|
import { runAudit } from "./index";
|
|
24
|
+
import { acquireAuditLock, type AuditLockInfo } from "./audit-lock";
|
|
19
25
|
import { writeDashboardCache } from "./dashboard-cache";
|
|
20
26
|
import type { AuditResult, RunAuditOptions } from "./types";
|
|
21
27
|
import { trackHookEvent } from "../hooks/hook-telemetry";
|
|
@@ -28,6 +34,16 @@ import { brandAnsi, ANSI_RESET, ANSI_BOLD, ANSI_DIM } from "../hooks/tui";
|
|
|
28
34
|
* for `start` mode, which `failproofai` (bare) already uses. */
|
|
29
35
|
const DASHBOARD_PORT = 8020;
|
|
30
36
|
|
|
37
|
+
/**
|
|
38
|
+
* `EX_TEMPFAIL` from sysexits.h — "another audit already holds the lock".
|
|
39
|
+
*
|
|
40
|
+
* A distinct code rather than 1, because the scheduler that spawns the headless
|
|
41
|
+
* run has to tell the two apart: a failure deserves a report, while losing the
|
|
42
|
+
* lock means only "come back in fifteen minutes". Collapsing them would make a
|
|
43
|
+
* healthy machine that simply ran two audits close together look broken.
|
|
44
|
+
*/
|
|
45
|
+
export const EXIT_AUDIT_ALREADY_RUNNING = 75;
|
|
46
|
+
|
|
31
47
|
/**
|
|
32
48
|
* Mirror of `app/audit/_components/run-progress.tsx`'s `STAGES`. Kept identical
|
|
33
49
|
* so the CLI and the dashboard's in-progress view tell the same story — the
|
|
@@ -49,6 +65,12 @@ USAGE
|
|
|
49
65
|
wasteful patterns, then open the audit dashboard.
|
|
50
66
|
failproofai audit --help Show this help.
|
|
51
67
|
|
|
68
|
+
failproofai audit --scheduled
|
|
69
|
+
Headless run: scan, refresh the dashboard's cached
|
|
70
|
+
result, print one line, exit. No browser, no
|
|
71
|
+
server. This is what a scheduled audit runs; exit
|
|
72
|
+
75 means another audit already had the lock.
|
|
73
|
+
|
|
52
74
|
WHAT IT DOES
|
|
53
75
|
1. Scans past sessions from every installed agent CLI (Claude, Codex, Cursor,
|
|
54
76
|
Copilot, OpenCode, Pi) — entirely on your machine.
|
|
@@ -247,10 +269,13 @@ function printSummary(result: AuditResult): void {
|
|
|
247
269
|
|
|
248
270
|
/**
|
|
249
271
|
* Which entry point ran the audit. `onboarding` is the automatic post-setup run;
|
|
250
|
-
* `cli` is an explicit `failproofai audit
|
|
251
|
-
*
|
|
272
|
+
* `cli` is an explicit `failproofai audit`; `scheduled` is the headless run the
|
|
273
|
+
* daemon spawns. Carried on every cli_audit_* event so the first audit a user
|
|
274
|
+
* ever runs, a deliberate one, and one nobody was present for stay distinct —
|
|
275
|
+
* without which an opt-in scheduled scan would silently inflate the counts that
|
|
276
|
+
* describe what people actually do by hand.
|
|
252
277
|
*/
|
|
253
|
-
type AuditSource = "cli" | "onboarding";
|
|
278
|
+
type AuditSource = "cli" | "onboarding" | "scheduled";
|
|
254
279
|
|
|
255
280
|
/** Shared so both entry points report cli_audit_completed identically. */
|
|
256
281
|
function auditCompletedProps(source: AuditSource, result: AuditResult) {
|
|
@@ -263,6 +288,89 @@ function auditCompletedProps(source: AuditSource, result: AuditResult) {
|
|
|
263
288
|
};
|
|
264
289
|
}
|
|
265
290
|
|
|
291
|
+
/** One line naming whoever holds the lock, for a refusal message. */
|
|
292
|
+
function heldByLine(held: AuditLockInfo | null): string {
|
|
293
|
+
if (!held) return "another audit is already running";
|
|
294
|
+
const ageS = Math.max(0, Math.round((Date.now() - held.startedAt) / 1000));
|
|
295
|
+
return `another audit is already running (pid ${held.pid}, started by ${held.source} ${ageS}s ago)`;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
// ── Headless (scheduled) audit ───────────────────────────────────────────────
|
|
299
|
+
|
|
300
|
+
/**
|
|
301
|
+
* Run the audit with nobody watching: no TTY animation, no browser, no prompts,
|
|
302
|
+
* no dashboard server left behind.
|
|
303
|
+
*
|
|
304
|
+
* This is the entry point the scheduler spawns, and it must be a SEPARATE
|
|
305
|
+
* short-lived process. `src/hooks/worker-server.ts` serialises every request
|
|
306
|
+
* through one promise chain that `crates/failproofaid/src/worker.rs` caps at
|
|
307
|
+
* 30s, and `daemon-client.ts` turns that timeout into a DENY — so a ~104-second
|
|
308
|
+
* audit on the warm worker would be a machine-wide fail-closed denial across
|
|
309
|
+
* all 12 CLIs for as long as it ran.
|
|
310
|
+
*
|
|
311
|
+
* Returns the exit code instead of exiting, so the caller owns the exit and
|
|
312
|
+
* this stays callable from a test:
|
|
313
|
+
* 0 the scan completed (whether or not it found anything)
|
|
314
|
+
* 1 the scan failed, or its result could not be persisted
|
|
315
|
+
* 75 another audit holds the lock — not an error, come back later
|
|
316
|
+
*/
|
|
317
|
+
export async function runScheduledAudit(): Promise<number> {
|
|
318
|
+
const attempt = acquireAuditLock("scheduled");
|
|
319
|
+
if (!attempt.ok) {
|
|
320
|
+
// Deliberately ahead of cli_audit_started: a run that never started must
|
|
321
|
+
// not be counted as one that did.
|
|
322
|
+
process.stderr.write(`failproofai: ${heldByLine(attempt.heldBy)}; skipping this scheduled run\n`);
|
|
323
|
+
return EXIT_AUDIT_ALREADY_RUNNING;
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
const instanceId = getInstanceId();
|
|
327
|
+
try {
|
|
328
|
+
// Every event here is awaited, unlike runAuditCli's fire-and-forget
|
|
329
|
+
// `started`. Nothing keeps this process alive once the audit settles — no
|
|
330
|
+
// dashboard server, no user — and there is nobody waiting on latency
|
|
331
|
+
// either, so the bounded (5s, never-throws) send costs nothing that matters.
|
|
332
|
+
await trackHookEvent(instanceId, "cli_audit_started", { source: "scheduled" });
|
|
333
|
+
|
|
334
|
+
let result: AuditResult;
|
|
335
|
+
try {
|
|
336
|
+
result = await runAudit({});
|
|
337
|
+
} catch (err) {
|
|
338
|
+
await trackHookEvent(instanceId, "cli_audit_failed", {
|
|
339
|
+
source: "scheduled",
|
|
340
|
+
error_type: err instanceof Error ? err.name : "unknown",
|
|
341
|
+
error_message: sanitizeErrorMessage(err),
|
|
342
|
+
});
|
|
343
|
+
process.stderr.write(
|
|
344
|
+
`failproofai: scheduled audit failed: ${err instanceof Error ? err.message : String(err)}\n`,
|
|
345
|
+
);
|
|
346
|
+
return 1;
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
await trackHookEvent(instanceId, "cli_audit_completed", auditCompletedProps("scheduled", result));
|
|
350
|
+
|
|
351
|
+
// No cache write for an empty scan, matching both interactive paths — and it
|
|
352
|
+
// matters more here. This run is unattended, so overwriting a real earlier
|
|
353
|
+
// audit's cached result with an empty one (after a history rotation, or on a
|
|
354
|
+
// service unit whose HOME resolves somewhere else) would blank the dashboard
|
|
355
|
+
// with nobody present to notice why.
|
|
356
|
+
if (result.eventsScanned > 0 && !writeDashboardCache({}, result)) {
|
|
357
|
+
// The cache is the only channel by which this run's result reaches
|
|
358
|
+
// anyone, so failing to write it is a failed run, not a footnote.
|
|
359
|
+
process.stderr.write("failproofai: scheduled audit ran but its result could not be saved\n");
|
|
360
|
+
return 1;
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
process.stdout.write(
|
|
364
|
+
`failproofai: audit complete — ${num(result.eventsScanned)} tool calls across ` +
|
|
365
|
+
`${num(result.transcripts.scanned)} sessions, ${num(result.totals.hits)} hits\n`,
|
|
366
|
+
);
|
|
367
|
+
|
|
368
|
+
return 0;
|
|
369
|
+
} finally {
|
|
370
|
+
attempt.lock.release();
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
|
|
266
374
|
// ── Post-setup background audit ────────────────────────────────────────────────
|
|
267
375
|
|
|
268
376
|
/**
|
|
@@ -279,47 +387,67 @@ function auditCompletedProps(source: AuditSource, result: AuditResult) {
|
|
|
279
387
|
export async function runPostSetupAudit(): Promise<void> {
|
|
280
388
|
if (process.env.FAILPROOFAI_NO_AUTO_AUDIT === "1") return;
|
|
281
389
|
|
|
282
|
-
|
|
283
|
-
//
|
|
284
|
-
//
|
|
285
|
-
//
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
);
|
|
291
|
-
|
|
292
|
-
let result: AuditResult;
|
|
293
|
-
try {
|
|
294
|
-
result = await runWithProgress({});
|
|
295
|
-
} catch (err) {
|
|
296
|
-
// Awaited: this function returns straight into the dashboard boot, and a
|
|
297
|
-
// fire-and-forget fetch would race it.
|
|
298
|
-
await trackHookEvent(instanceId, "cli_audit_failed", {
|
|
299
|
-
source: "onboarding",
|
|
300
|
-
error_type: err instanceof Error ? err.name : "unknown",
|
|
301
|
-
error_message: sanitizeErrorMessage(err),
|
|
302
|
-
});
|
|
390
|
+
// Take the same cross-process cache lock the scheduled run, `failproofai
|
|
391
|
+
// audit` and the dashboard re-run take. This onboarding scan writes the very
|
|
392
|
+
// same sha1-keyed per-transcript cache and single-slot dashboard cache, so it
|
|
393
|
+
// is the fourth writer and must serialise with the other three — a scheduled
|
|
394
|
+
// daemon child can already be mid-scan when setup finishes. Held ⇒ skip, best
|
|
395
|
+
// effort: the dashboard boots on whatever the holder's run leaves behind, and
|
|
396
|
+
// this is pre-warming, not a result anyone is waiting on. `onboarding` matches
|
|
397
|
+
// the telemetry source below and the lock source declared in audit-lock.ts.
|
|
398
|
+
const attempt = acquireAuditLock("onboarding");
|
|
399
|
+
if (!attempt.ok) {
|
|
303
400
|
process.stdout.write(
|
|
304
|
-
|
|
401
|
+
`\n ${c(DIM, "an audit is already running — the dashboard will show its result.")}\n\n`,
|
|
305
402
|
);
|
|
306
403
|
return;
|
|
307
404
|
}
|
|
308
405
|
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
406
|
+
try {
|
|
407
|
+
const instanceId = getInstanceId();
|
|
408
|
+
// Fire-and-forget, as in runAuditCli: the multi-second scan below keeps the
|
|
409
|
+
// process alive long enough for this to land, and the completed/failed event
|
|
410
|
+
// that follows is awaited.
|
|
411
|
+
void trackHookEvent(instanceId, "cli_audit_started", { source: "onboarding" });
|
|
313
412
|
|
|
314
|
-
if (result.eventsScanned === 0) {
|
|
315
413
|
process.stdout.write(
|
|
316
|
-
`\n ${c(
|
|
414
|
+
`\n ${c(PINK, "✦")} ${c(BOLD, "failproofai audit now running")} ${c(DIM, "· ctrl+c to stop")}\n\n`,
|
|
317
415
|
);
|
|
318
|
-
|
|
416
|
+
|
|
417
|
+
let result: AuditResult;
|
|
418
|
+
try {
|
|
419
|
+
result = await runWithProgress({});
|
|
420
|
+
} catch (err) {
|
|
421
|
+
// Awaited: this function returns straight into the dashboard boot, and a
|
|
422
|
+
// fire-and-forget fetch would race it.
|
|
423
|
+
await trackHookEvent(instanceId, "cli_audit_failed", {
|
|
424
|
+
source: "onboarding",
|
|
425
|
+
error_type: err instanceof Error ? err.name : "unknown",
|
|
426
|
+
error_message: sanitizeErrorMessage(err),
|
|
427
|
+
});
|
|
428
|
+
process.stdout.write(
|
|
429
|
+
` ${c(PINK, "!")} ${c(DIM, "audit couldn't finish — run")} ${c(CYAN, "failproofai audit")} ${c(DIM, "later.")}\n\n`,
|
|
430
|
+
);
|
|
431
|
+
return;
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
// Reported before the empty-history return below, so an onboarding audit that
|
|
435
|
+
// finds nothing is still counted — matching runAuditCli, which reports
|
|
436
|
+
// completed regardless of what the scan turned up.
|
|
437
|
+
await trackHookEvent(instanceId, "cli_audit_completed", auditCompletedProps("onboarding", result));
|
|
438
|
+
|
|
439
|
+
if (result.eventsScanned === 0) {
|
|
440
|
+
process.stdout.write(
|
|
441
|
+
`\n ${c(DIM, "no agent sessions to audit yet — come back after using your agent.")}\n\n`,
|
|
442
|
+
);
|
|
443
|
+
return;
|
|
444
|
+
}
|
|
445
|
+
writeDashboardCache({}, result);
|
|
446
|
+
printSummary(result);
|
|
447
|
+
process.stdout.write("\n");
|
|
448
|
+
} finally {
|
|
449
|
+
attempt.lock.release();
|
|
319
450
|
}
|
|
320
|
-
writeDashboardCache({}, result);
|
|
321
|
-
printSummary(result);
|
|
322
|
-
process.stdout.write("\n");
|
|
323
451
|
}
|
|
324
452
|
|
|
325
453
|
// ── Entry point ──────────────────────────────────────────────────────────────
|
|
@@ -329,6 +457,15 @@ export async function runAuditCli(args: string[]): Promise<void> {
|
|
|
329
457
|
process.stdout.write(HELP);
|
|
330
458
|
process.exit(0);
|
|
331
459
|
}
|
|
460
|
+
// The headless path, spawned rather than typed. Handled ahead of the
|
|
461
|
+
// rejection below so that adding it costs the interactive path nothing: it
|
|
462
|
+
// still refuses every argument it has always refused.
|
|
463
|
+
if (args.includes("--scheduled")) {
|
|
464
|
+
const extra = args.find((a) => a !== "--scheduled");
|
|
465
|
+
if (extra) die(`\`audit --scheduled\` takes no other arguments (got: ${extra}).`);
|
|
466
|
+
process.exit(await runScheduledAudit());
|
|
467
|
+
}
|
|
468
|
+
|
|
332
469
|
// No arguments supported yet — reject typos rather than silently doing a bare
|
|
333
470
|
// audit, so a future `failproofai audit --since 7d` doesn't quietly no-op.
|
|
334
471
|
const stray = args.find((a) => a !== "--help" && a !== "-h");
|
|
@@ -339,6 +476,18 @@ export async function runAuditCli(args: string[]): Promise<void> {
|
|
|
339
476
|
);
|
|
340
477
|
}
|
|
341
478
|
|
|
479
|
+
// Taken before any telemetry so a refused run is not counted as a started
|
|
480
|
+
// one, and released the moment the SCAN is done — see below, well before
|
|
481
|
+
// launch() parks this process on the dashboard.
|
|
482
|
+
const attempt = acquireAuditLock("cli");
|
|
483
|
+
if (!attempt.ok) {
|
|
484
|
+
process.stderr.write(
|
|
485
|
+
`Error: ${heldByLine(attempt.heldBy)}.\n` +
|
|
486
|
+
`Two audits write the same cache files, so this one won't start. Try again shortly.\n`,
|
|
487
|
+
);
|
|
488
|
+
process.exit(EXIT_AUDIT_ALREADY_RUNNING);
|
|
489
|
+
}
|
|
490
|
+
|
|
342
491
|
const instanceId = getInstanceId();
|
|
343
492
|
// Fire-and-forget is safe for `started`: the multi-second audit below (and the
|
|
344
493
|
// awaited cli_audit_completed / cli_audit_failed) keep the process alive long
|
|
@@ -393,6 +542,13 @@ export async function runAuditCli(args: string[]): Promise<void> {
|
|
|
393
542
|
);
|
|
394
543
|
}
|
|
395
544
|
|
|
545
|
+
// Released here, not in a `finally`: the lock covers the SCAN, and launch()
|
|
546
|
+
// below keeps this process alive for as long as the user leaves the dashboard
|
|
547
|
+
// open. Holding it that long would block every scheduled run until the
|
|
548
|
+
// one-hour stale ceiling expired. The exit paths above (die(), the
|
|
549
|
+
// empty-history exit) are covered by the handle's own process-exit hook.
|
|
550
|
+
attempt.lock.release();
|
|
551
|
+
|
|
396
552
|
const url = `http://localhost:${DASHBOARD_PORT}/audit`;
|
|
397
553
|
process.stdout.write(
|
|
398
554
|
`\n ${c(DIM, "starting the dashboard…")}\n` +
|
|
@@ -11,10 +11,9 @@
|
|
|
11
11
|
* makes navigating back to /audit instant without re-running.
|
|
12
12
|
*/
|
|
13
13
|
import { existsSync, readFileSync } from "node:fs";
|
|
14
|
-
import { join } from "node:path";
|
|
15
|
-
import { homedir } from "node:os";
|
|
16
14
|
import { writeJsonAtomically } from "../../lib/atomic-write";
|
|
17
15
|
import type { AuditResult, RunAuditOptions } from "./types";
|
|
16
|
+
import { auditDashboardFile } from "../hooks/fp-home";
|
|
18
17
|
|
|
19
18
|
const DEFAULT_MAX_AGE_MINUTES = 30;
|
|
20
19
|
|
|
@@ -50,7 +49,7 @@ export interface DashboardCacheEntry {
|
|
|
50
49
|
}
|
|
51
50
|
|
|
52
51
|
function getCachePath(): string {
|
|
53
|
-
return
|
|
52
|
+
return auditDashboardFile();
|
|
54
53
|
}
|
|
55
54
|
|
|
56
55
|
/** Read the cache file. Returns null on missing/corrupt/unreadable file —
|
package/src/audit/report.ts
CHANGED
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `failproofai backfill` — re-ship history the collector has already read past.
|
|
3
|
+
*
|
|
4
|
+
* The collector never re-reads a file it has a cursor for, which is right for
|
|
5
|
+
* steady state and wrong exactly twice:
|
|
6
|
+
*
|
|
7
|
+
* • the dashboard's data was cleared, or a machine was re-enrolled into a
|
|
8
|
+
* different org, and the history on disk is now the only copy;
|
|
9
|
+
* • cursors advanced before there was anywhere to send to, so everything
|
|
10
|
+
* before the connection was never shipped at all.
|
|
11
|
+
*
|
|
12
|
+
* Both leave a machine whose transcripts exist locally and nowhere else, with no
|
|
13
|
+
* way to ask for them again short of deleting files by hand and hoping.
|
|
14
|
+
*
|
|
15
|
+
* SAFE BY CONSTRUCTION, not by luck. Re-reading is already the documented
|
|
16
|
+
* recovery path for a damaged cursor store — "starting over re-ships records the
|
|
17
|
+
* server already dedups" — and redaction is deterministic, so a re-shipped event
|
|
18
|
+
* hashes identically to its first send and collapses into the row that is
|
|
19
|
+
* already there rather than duplicating it.
|
|
20
|
+
*
|
|
21
|
+
* HANDS OFF rather than doing the work. The cursors it needs to rewind are held
|
|
22
|
+
* in memory by the RUNNING collector, which would write them straight back over;
|
|
23
|
+
* only the daemon can stop the collector first. So this writes a request the
|
|
24
|
+
* daemon drains on its next tick.
|
|
25
|
+
*
|
|
26
|
+
* What it does NOT hand off is the checking. Every precondition a person can get
|
|
27
|
+
* wrong — no daemon, no credential, collection switched off — is verified here,
|
|
28
|
+
* synchronously, before returning. The alternative is what already happened once
|
|
29
|
+
* on a real machine: the CLI reported success, and the actual failure sat in the
|
|
30
|
+
* journal for twenty minutes while batches parked.
|
|
31
|
+
*/
|
|
32
|
+
import { existsSync, mkdirSync, writeFileSync, readdirSync, statSync } from "node:fs";
|
|
33
|
+
import { dirname, join } from "node:path";
|
|
34
|
+
|
|
35
|
+
import { failproofaiHome } from "./fp-home";
|
|
36
|
+
import { readConfig } from "./fp-config";
|
|
37
|
+
import { readIngestCredential } from "./collector-config";
|
|
38
|
+
import { daemonServiceStatus, isDaemonSupportedPlatform } from "./daemon-service";
|
|
39
|
+
|
|
40
|
+
/** Default window. `--since` widens it. */
|
|
41
|
+
export const DEFAULT_BACKFILL_DAYS = 30;
|
|
42
|
+
|
|
43
|
+
export interface BackfillOptions {
|
|
44
|
+
/** Epoch ms. Everything modified at or after this is re-read. */
|
|
45
|
+
sinceMs?: number;
|
|
46
|
+
/** Report what would be re-read and write nothing. */
|
|
47
|
+
dryRun?: boolean;
|
|
48
|
+
/** Injected for tests. */
|
|
49
|
+
now?: number;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export interface BackfillResult {
|
|
53
|
+
exitCode: number;
|
|
54
|
+
lines: string[];
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* `~/.failproofai/state/backfill-request.json` — mirrored from the daemon's
|
|
59
|
+
* `paths::backfill_request_path()`. Two processes, one path; the comment there
|
|
60
|
+
* says why that is written down twice rather than derived.
|
|
61
|
+
*/
|
|
62
|
+
export function backfillRequestPath(home?: string): string {
|
|
63
|
+
return join(failproofaiHome(home), "state", "backfill-request.json");
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Where each agent CLI keeps its transcripts, so the command can say what it is
|
|
68
|
+
* about to re-read rather than asking for trust.
|
|
69
|
+
*
|
|
70
|
+
* Deliberately a SURVEY, not the source of truth. The daemon decides what is
|
|
71
|
+
* actually collected from its own config; this list only produces the sentence
|
|
72
|
+
* a person reads before agreeing. A directory missing here means a quieter
|
|
73
|
+
* message, never a missed backfill.
|
|
74
|
+
*/
|
|
75
|
+
const SESSION_DIRS: Array<{ cli: string; dir: string }> = [
|
|
76
|
+
{ cli: "Claude Code", dir: ".claude/projects" },
|
|
77
|
+
{ cli: "OpenAI Codex", dir: ".codex/sessions" },
|
|
78
|
+
{ cli: "Cursor", dir: ".cursor/chats" },
|
|
79
|
+
{ cli: "GitHub Copilot", dir: ".copilot/history-session-state" },
|
|
80
|
+
{ cli: "OpenCode", dir: ".local/share/opencode/storage" },
|
|
81
|
+
{ cli: "Factory Droid", dir: ".factory/sessions" },
|
|
82
|
+
{ cli: "Antigravity", dir: ".gemini/antigravity-cli/brain" },
|
|
83
|
+
];
|
|
84
|
+
|
|
85
|
+
interface Survey {
|
|
86
|
+
cli: string;
|
|
87
|
+
files: number;
|
|
88
|
+
newest: number;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/** What is on disk and inside the window, per CLI. */
|
|
92
|
+
function surveySessions(sinceMs: number, homeDir: string): Survey[] {
|
|
93
|
+
const out: Survey[] = [];
|
|
94
|
+
for (const { cli, dir } of SESSION_DIRS) {
|
|
95
|
+
const root = join(homeDir, dir);
|
|
96
|
+
if (!existsSync(root)) continue;
|
|
97
|
+
let files = 0;
|
|
98
|
+
let newest = 0;
|
|
99
|
+
const walk = (d: string, depth: number) => {
|
|
100
|
+
// Bounded: these trees are date-nested a few levels deep, and an
|
|
101
|
+
// unbounded walk on a symlinked home is a way to hang a CLI command.
|
|
102
|
+
if (depth > 6) return;
|
|
103
|
+
let entries;
|
|
104
|
+
try {
|
|
105
|
+
entries = readdirSync(d, { withFileTypes: true });
|
|
106
|
+
} catch {
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
for (const e of entries) {
|
|
110
|
+
const p = join(d, e.name);
|
|
111
|
+
if (e.isDirectory()) {
|
|
112
|
+
walk(p, depth + 1);
|
|
113
|
+
continue;
|
|
114
|
+
}
|
|
115
|
+
try {
|
|
116
|
+
const st = statSync(p);
|
|
117
|
+
if (st.mtimeMs >= sinceMs) {
|
|
118
|
+
files += 1;
|
|
119
|
+
newest = Math.max(newest, st.mtimeMs);
|
|
120
|
+
}
|
|
121
|
+
} catch {
|
|
122
|
+
/* raced away mid-walk */
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
};
|
|
126
|
+
walk(root, 0);
|
|
127
|
+
if (files > 0) out.push({ cli, files, newest });
|
|
128
|
+
}
|
|
129
|
+
return out;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
export function runBackfillCommand(opts: BackfillOptions = {}): BackfillResult {
|
|
133
|
+
const now = opts.now ?? Date.now();
|
|
134
|
+
const sinceMs = opts.sinceMs ?? now - DEFAULT_BACKFILL_DAYS * 24 * 60 * 60 * 1000;
|
|
135
|
+
const home = failproofaiHome();
|
|
136
|
+
|
|
137
|
+
if (!existsSync(home)) {
|
|
138
|
+
return {
|
|
139
|
+
exitCode: 1,
|
|
140
|
+
lines: [`No failproofai home at ${home}. Run \`failproofai config\` first.`],
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// Checked HERE, not left to the daemon. Handing off an impossible request and
|
|
145
|
+
// reporting success is the failure this command is partly a response to.
|
|
146
|
+
const ingest = readIngestCredential();
|
|
147
|
+
if (!ingest) {
|
|
148
|
+
return {
|
|
149
|
+
exitCode: 1,
|
|
150
|
+
lines: [
|
|
151
|
+
"This machine is not connected, so there is nowhere to send history.",
|
|
152
|
+
"Connect first: `failproofai config --connect <url> --token <key>`.",
|
|
153
|
+
],
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
let sessions = false;
|
|
158
|
+
let hooks = false;
|
|
159
|
+
try {
|
|
160
|
+
const cfg = readConfig();
|
|
161
|
+
sessions = cfg.collector?.sessions ?? false;
|
|
162
|
+
hooks = cfg.collector?.hooks ?? false;
|
|
163
|
+
} catch {
|
|
164
|
+
/* an unreadable config is reported as "nothing enabled" below */
|
|
165
|
+
}
|
|
166
|
+
if (!sessions && !hooks) {
|
|
167
|
+
return {
|
|
168
|
+
exitCode: 1,
|
|
169
|
+
lines: [
|
|
170
|
+
"Collection is switched off, so a backfill would re-read history and send",
|
|
171
|
+
'none of it. Enable it in ~/.failproofai/config.json under "collector".',
|
|
172
|
+
],
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
const found = surveySessions(sinceMs, dirname(home));
|
|
177
|
+
const totalFiles = found.reduce((n, f) => n + f.files, 0);
|
|
178
|
+
const sinceLabel = new Date(sinceMs).toISOString().slice(0, 10);
|
|
179
|
+
|
|
180
|
+
const lines: string[] = [
|
|
181
|
+
`Re-reading everything modified since ${sinceLabel}, and sending it again.`,
|
|
182
|
+
"",
|
|
183
|
+
];
|
|
184
|
+
// Named honestly: only what the config actually enables, because that is all
|
|
185
|
+
// the daemon will ship. Promising transcripts on a machine with
|
|
186
|
+
// `sessions = false` would be a lie the user could only catch by waiting.
|
|
187
|
+
lines.push(
|
|
188
|
+
` streams: ${[sessions && "session transcripts", hooks && "hook decisions"]
|
|
189
|
+
.filter(Boolean)
|
|
190
|
+
.join(" + ")}`,
|
|
191
|
+
);
|
|
192
|
+
if (sessions) {
|
|
193
|
+
if (found.length === 0) {
|
|
194
|
+
lines.push(" sessions: none found on disk in this window");
|
|
195
|
+
} else {
|
|
196
|
+
for (const f of found) {
|
|
197
|
+
lines.push(` ${f.cli}: ${f.files} file${f.files === 1 ? "" : "s"}`);
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
if (opts.dryRun) {
|
|
203
|
+
lines.push("", "--dry-run: nothing was requested.");
|
|
204
|
+
return { exitCode: 0, lines };
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
try {
|
|
208
|
+
const path = backfillRequestPath();
|
|
209
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
210
|
+
// Whole-file write of a small JSON object: the daemon deletes it before
|
|
211
|
+
// acting, so a partially-written file can only ever be read once, and an
|
|
212
|
+
// unparseable one is discarded rather than retried.
|
|
213
|
+
writeFileSync(path, `${JSON.stringify({ sinceMs, requestedAtMs: now }, null, 2)}\n`, {
|
|
214
|
+
mode: 0o600,
|
|
215
|
+
});
|
|
216
|
+
} catch (err) {
|
|
217
|
+
return {
|
|
218
|
+
exitCode: 2,
|
|
219
|
+
lines: [
|
|
220
|
+
...lines,
|
|
221
|
+
"",
|
|
222
|
+
`Could not write the request: ${err instanceof Error ? err.message : String(err)}`,
|
|
223
|
+
],
|
|
224
|
+
};
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
lines.push("", "Requested. The daemon picks this up within a few seconds.");
|
|
228
|
+
|
|
229
|
+
// A daemon that is not running will act on the request whenever it next
|
|
230
|
+
// starts — the request is a file, deliberately, so it survives that. Saying so
|
|
231
|
+
// is the difference between "nothing happened" and "nothing happened yet".
|
|
232
|
+
if (isDaemonSupportedPlatform()) {
|
|
233
|
+
const status = daemonServiceStatus();
|
|
234
|
+
if (status !== "running") {
|
|
235
|
+
lines.push(
|
|
236
|
+
`failproofaid is ${status}, so nothing moves until it starts. The request is`,
|
|
237
|
+
"on disk and will be honoured then.",
|
|
238
|
+
);
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
lines.push(
|
|
242
|
+
"",
|
|
243
|
+
`Watch it land: ${totalFiles > 0 ? `~${totalFiles} files queued · ` : ""}` +
|
|
244
|
+
"`failproofai config --status`",
|
|
245
|
+
);
|
|
246
|
+
return { exitCode: 0, lines };
|
|
247
|
+
}
|