failproofai 1.0.0-beta.5 → 1.0.0-beta.7
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 +3 -3
- 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/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/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 +2 -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/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/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/server-reference-manifest.json +10 -10
- package/.next/standalone/.next/server/app/policies/page.js +4 -2
- 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/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/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/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]__1nl3dvw._.js → [externals]__0r-zamh._.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]__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]__0g0vcwt._.js +3 -0
- package/.next/standalone/.next/server/chunks/{[root-of-the-server]__0h3lfmp._.js → [root-of-the-server]__0ky_n0r._.js} +2 -2
- package/.next/standalone/.next/server/chunks/{[root-of-the-server]__18d-9oi._.js → [root-of-the-server]__0lk_935._.js} +2 -2
- package/.next/standalone/.next/server/chunks/{[root-of-the-server]__1p7s8-d._.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]__10oa-v9._.js → [root-of-the-server]__0v2fpxe._.js} +2 -2
- package/.next/standalone/.next/server/chunks/{[root-of-the-server]__0sb_5m8._.js → [root-of-the-server]__131ebum._.js} +3 -3
- package/.next/standalone/.next/server/chunks/{[root-of-the-server]__0-dh5jy._.js → [root-of-the-server]__1_ps0jw._.js} +2 -2
- package/.next/standalone/.next/server/chunks/{[root-of-the-server]__0l4vuqm._.js → [root-of-the-server]__1a7i2q4._.js} +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__1ibjy5x._.js +3 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__1iny24d._.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]__1tyedjt._.js +3 -0
- package/.next/standalone/.next/server/chunks/_0lxbzdq._.js +5 -0
- package/.next/standalone/.next/server/chunks/_1zuiiy3._.js +5 -0
- package/.next/standalone/.next/server/chunks/lib_telemetry_ts_0dctyyw._.js +5 -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/package_json_[json]_cjs_1nxcc4v._.js +1 -1
- 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]__0c_qcgp._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0kbc9bs._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0spkm68._.js +10 -10
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1a2hcb_._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__0rtdfof._.js → [root-of-the-server]__1rdruw4._.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]__1tyf0tc._.js +5 -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/{[root-of-the-server]__0rag5dg._.js → [root-of-the-server]__1yw368c._.js} +2 -2
- package/.next/standalone/.next/server/chunks/ssr/_0-yi74u._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/_04mekdy._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/_0r-dsre._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/_0rjgfln._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/_0se484m._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/_0uz7o9y._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/_11_p9y8._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/_194yh9u._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/_1fahg9e._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/_1gmzkwj._.js +5 -0
- package/.next/standalone/.next/server/chunks/ssr/_1w8sola._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/_1w_6p9g._.js +3 -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/src_hooks_builtin-policies_ts_09j2ndl._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/src_hooks_fp-config_ts_04t589g._.js +5 -0
- package/.next/standalone/.next/server/middleware-build-manifest.js +3 -3
- 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 +54 -12
- package/.next/standalone/.next/static/chunks/0945ybz3fsvba.js +1 -0
- package/.next/standalone/.next/static/chunks/{0cr85wuc9gc9k.js → 0gr76ps8_zm26.js} +1 -1
- package/.next/standalone/.next/static/chunks/{2cqk4a9s893oy.js → 0m-m7wgtxdoav.js} +1 -1
- package/.next/standalone/.next/static/chunks/{1tp9dex23zs8y.js → 11l6oh9cyb35a.js} +1 -1
- package/.next/standalone/.next/static/chunks/166f8qnh03jqc.js +6 -0
- package/.next/standalone/.next/static/chunks/1bm0q12hgs22l.css +2 -0
- package/.next/standalone/.next/static/chunks/1v1gqvpjk62jm.js +1 -0
- package/.next/standalone/.next/static/chunks/{2s9g1dsde_ts1.js → 2aej0vi1fm87m.js} +1 -1
- package/.next/standalone/.next/static/chunks/{1bkynffp3q1f7.js → 2bxld6-rym3ah.js} +22 -22
- package/.next/standalone/.next/static/chunks/{2v1rpzrh2i1wv.js → 2p2kz4aj1tooj.js} +1 -1
- package/.next/standalone/.next/static/chunks/2uvhw57mf9lg_.css +2 -0
- package/.next/standalone/.next/static/chunks/36pc3_8fpb6ic.js +1 -0
- package/.next/standalone/.next/static/chunks/{2k285z07qce47.js → 3t1wr_kx6brwc.js} +1 -1
- package/.next/standalone/app/actions/get-hooks-config.ts +2 -1
- 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/auth-dialog.css +159 -0
- package/.next/standalone/app/audit/_components/auth-dialog.tsx +3 -0
- package/.next/standalone/app/audit/audit-styles.css +6 -149
- package/.next/standalone/app/settings/page.tsx +31 -0
- package/.next/standalone/app/settings/settings-client.tsx +488 -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 -4
- 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 +7 -6
- package/.next/standalone/proxy.ts +89 -1
- package/.next/standalone/server.js +1 -1
- package/bin/failproofai.mjs +202 -86
- package/dist/cli.mjs +17803 -15346
- package/dist/worker.mjs +1620 -257
- package/lib/auth/api-server-client.ts +2 -3
- package/lib/auth/auth-store.ts +4 -4
- 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 +7 -6
- package/scripts/install-telemetry.mjs +8 -1
- package/scripts/launch.ts +25 -3
- package/scripts/parse-script-args.ts +17 -1
- package/scripts/prune-standalone.mjs +20 -0
- 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 -4
- 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 -1
- package/src/hooks/builtin-policies.ts +100 -3
- package/src/hooks/cloud-connection.ts +160 -14
- package/src/hooks/cloud-enrollment-cli.ts +119 -21
- package/src/hooks/cloud-enrollment.ts +87 -17
- package/src/hooks/cloud-introspect.ts +147 -0
- package/src/hooks/cloud-managed-policies.ts +33 -2
- package/src/hooks/collector-config.ts +25 -43
- package/src/hooks/configure-wizard.ts +750 -318
- package/src/hooks/custom-hooks-loader.ts +125 -11
- package/src/hooks/daemon-client.ts +83 -17
- package/src/hooks/daemon-download.ts +54 -2
- package/src/hooks/daemon-service.ts +605 -29
- package/src/hooks/first-run-gate.ts +39 -0
- package/src/hooks/fp-config.ts +497 -0
- package/src/hooks/fp-home.ts +355 -0
- package/src/hooks/fp-reset.ts +332 -0
- package/src/hooks/handler.ts +42 -1
- package/src/hooks/hook-activity-store.ts +35 -20
- package/src/hooks/hook-logger.ts +2 -1
- package/src/hooks/hook-telemetry.ts +5 -1
- package/src/hooks/hooks-config.ts +4 -3
- package/src/hooks/loader-utils.ts +166 -5
- package/src/hooks/manager.ts +5 -4
- package/src/hooks/onboarding-attempt.ts +179 -0
- package/src/hooks/onboarding-lock.ts +119 -0
- package/src/hooks/policy-types.ts +0 -7
- package/src/hooks/session-pause-cli.ts +15 -13
- package/src/hooks/session-pause.ts +36 -4
- package/src/hooks/setup-state.ts +256 -0
- package/src/hooks/tui.ts +4 -4
- package/src/hooks/worker-server.ts +53 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0f8nu8d._.js +0 -3
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0szqzl8._.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/[root-of-the-server]__02r5bgf._.js +0 -4
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__094nop3._.js +0 -3
- 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]__0ls0gsr._.js +0 -4
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0n_uvv0._.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]__14dd6h8._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1gak4pd._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1j4u3yd._.js +0 -4
- 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_12c_2cz.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/1bw4dfzflelcv.js +0 -1
- package/.next/standalone/.next/static/chunks/34sjdw8lv46e3.js +0 -6
- package/.next/standalone/.next/static/chunks/3i27c3hcriawq.css +0 -1
- package/.next/standalone/.next/static/chunks/3zdwnnthucs_1.css +0 -2
- package/.next/standalone/Cargo.lock +0 -2222
- package/.next/standalone/Cargo.toml +0 -9
- package/.next/standalone/crates/CLOUD_POLICIES.md +0 -136
- package/.next/standalone/crates/PROTOCOL.md +0 -122
- package/.next/standalone/crates/failproofaid/Cargo.toml +0 -25
- package/.next/standalone/crates/failproofaid/src/cloud_client.rs +0 -484
- package/.next/standalone/crates/failproofaid/src/cloud_policies.rs +0 -921
- package/.next/standalone/crates/failproofaid/src/lock.rs +0 -81
- package/.next/standalone/crates/failproofaid/src/main.rs +0 -639
- package/.next/standalone/crates/failproofaid/src/paths.rs +0 -201
- 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-collect/Cargo.toml +0 -43
- package/.next/standalone/crates/fpai-collect/src/config.rs +0 -420
- package/.next/standalone/crates/fpai-collect/src/cursor.rs +0 -520
- package/.next/standalone/crates/fpai-collect/src/delivery.rs +0 -472
- package/.next/standalone/crates/fpai-collect/src/filetail.rs +0 -487
- package/.next/standalone/crates/fpai-collect/src/health.rs +0 -369
- package/.next/standalone/crates/fpai-collect/src/lib.rs +0 -34
- package/.next/standalone/crates/fpai-collect/src/redact.rs +0 -516
- package/.next/standalone/crates/fpai-collect/src/sources/claude/mod.rs +0 -279
- package/.next/standalone/crates/fpai-collect/src/sources/claude/transform.rs +0 -619
- package/.next/standalone/crates/fpai-collect/src/sources/codex/mod.rs +0 -156
- package/.next/standalone/crates/fpai-collect/src/sources/codex/transform.rs +0 -715
- package/.next/standalone/crates/fpai-collect/src/sources/copilot/mod.rs +0 -158
- package/.next/standalone/crates/fpai-collect/src/sources/copilot/transform.rs +0 -512
- package/.next/standalone/crates/fpai-collect/src/sources/goose/mod.rs +0 -511
- package/.next/standalone/crates/fpai-collect/src/sources/goose/transform.rs +0 -586
- package/.next/standalone/crates/fpai-collect/src/sources/hermes/mod.rs +0 -318
- package/.next/standalone/crates/fpai-collect/src/sources/hermes/transform.rs +0 -579
- package/.next/standalone/crates/fpai-collect/src/sources/hooks/mod.rs +0 -314
- package/.next/standalone/crates/fpai-collect/src/sources/hooks/transform.rs +0 -448
- package/.next/standalone/crates/fpai-collect/src/sources/mod.rs +0 -23
- package/.next/standalone/crates/fpai-collect/src/sources/openclaw/mod.rs +0 -176
- package/.next/standalone/crates/fpai-collect/src/sources/openclaw/transform.rs +0 -500
- package/.next/standalone/crates/fpai-collect/src/sources/opencode/mod.rs +0 -315
- package/.next/standalone/crates/fpai-collect/src/sources/opencode/transform.rs +0 -493
- package/.next/standalone/crates/fpai-collect/src/sources/pi/mod.rs +0 -201
- package/.next/standalone/crates/fpai-collect/src/sources/pi/transform.rs +0 -533
- package/.next/standalone/crates/fpai-collect/src/spool.rs +0 -535
- package/.next/standalone/crates/fpai-collect/src/sqlitepoll.rs +0 -390
- package/.next/standalone/crates/fpai-collect/src/supervisor.rs +0 -423
- package/.next/standalone/crates/fpai-collect/src/uploader.rs +0 -600
- package/.next/standalone/crates/fpai-collect/tests/claude_source.rs +0 -739
- package/.next/standalone/crates/fpai-collect/tests/claude_subagents.rs +0 -394
- package/.next/standalone/crates/fpai-collect/tests/codex_source.rs +0 -976
- package/.next/standalone/crates/fpai-collect/tests/config.rs +0 -286
- package/.next/standalone/crates/fpai-collect/tests/copilot_source.rs +0 -1020
- package/.next/standalone/crates/fpai-collect/tests/delivery.rs +0 -281
- package/.next/standalone/crates/fpai-collect/tests/goose_source.rs +0 -1009
- package/.next/standalone/crates/fpai-collect/tests/hermes_source.rs +0 -1130
- package/.next/standalone/crates/fpai-collect/tests/hooks_source.rs +0 -622
- package/.next/standalone/crates/fpai-collect/tests/openclaw_source.rs +0 -855
- package/.next/standalone/crates/fpai-collect/tests/opencode_source.rs +0 -1478
- package/.next/standalone/crates/fpai-collect/tests/pi_source.rs +0 -1192
- package/.next/standalone/crates/fpai-collect/tests/supervisor.rs +0 -290
- package/.next/standalone/crates/fpai-collect/tests/uploader.rs +0 -506
- 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/{LOuwMmfomO6hQYfMQYs4K → LUaLkKg40nSkHrIajexIk}/_buildManifest.js +0 -0
- /package/.next/standalone/.next/static/{LOuwMmfomO6hQYfMQYs4K → LUaLkKg40nSkHrIajexIk}/_clientMiddlewareManifest.js +0 -0
- /package/.next/standalone/.next/static/{LOuwMmfomO6hQYfMQYs4K → LUaLkKg40nSkHrIajexIk}/_ssgManifest.js +0 -0
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Whether a given `failproofai` invocation may be interrupted by the
|
|
3
|
+
* first-run wizard.
|
|
4
|
+
*
|
|
5
|
+
* Deliberately has NO imports. It is consulted from `bin/failproofai.mjs`,
|
|
6
|
+
* which keeps its module graph small so the `--hook` fast path (one process
|
|
7
|
+
* per tool call) does not pay for loading the world; and it lives outside that
|
|
8
|
+
* file so it can be unit-tested without executing the CLI's top-level body.
|
|
9
|
+
*
|
|
10
|
+
* This decides only the SHAPE of the command. Whether the machine is actually
|
|
11
|
+
* unconfigured, whether there is a TTY, and whether we are running under sudo
|
|
12
|
+
* are decided later, in `maybeFirstRunConfigure`.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Subcommands that must never be interrupted by onboarding.
|
|
17
|
+
*
|
|
18
|
+
* All three are configuration actions in their own right: `config` IS the
|
|
19
|
+
* wizard, and `policies` / `policy` are the non-interactive way to do setup.
|
|
20
|
+
* Putting a wizard in front of them would override an intent the user just
|
|
21
|
+
* stated, and would hang any script that calls them.
|
|
22
|
+
*/
|
|
23
|
+
export const FIRST_RUN_EXEMPT_SUBCOMMANDS: readonly string[] = [
|
|
24
|
+
"config",
|
|
25
|
+
"policies",
|
|
26
|
+
"policy",
|
|
27
|
+
];
|
|
28
|
+
|
|
29
|
+
export function shouldOfferFirstRun(args: readonly string[]): boolean {
|
|
30
|
+
// "What is this / how do I use it" must never require setup first.
|
|
31
|
+
if (args.includes("--help") || args.includes("-h")) return false;
|
|
32
|
+
if (args.includes("--version") || args.includes("-v")) return false;
|
|
33
|
+
// `--hook` never reaches this check (the entry point handles and exits
|
|
34
|
+
// above), but a stray one here would mean an agent's tool call sitting at an
|
|
35
|
+
// interactive prompt it cannot answer. Cheap to be explicit.
|
|
36
|
+
if (args.includes("--hook")) return false;
|
|
37
|
+
if (args.length > 0 && FIRST_RUN_EXEMPT_SUBCOMMANDS.includes(args[0])) return false;
|
|
38
|
+
return true;
|
|
39
|
+
}
|
|
@@ -0,0 +1,497 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `VERSION`, `config.toml` and `credentials.toml` — the three files a human
|
|
3
|
+
* might actually open in `~/.failproofai`.
|
|
4
|
+
*
|
|
5
|
+
* ## Why the split
|
|
6
|
+
*
|
|
7
|
+
* `config.toml` inherits the umask and lands at 0664 on a normal machine.
|
|
8
|
+
* `credentials.toml` is written 0600 and holds every token. That boundary is
|
|
9
|
+
* not tidiness: it is why `ingest.json` and `cloud.json` were separate files
|
|
10
|
+
* before this, and it means CI, tooling and a screen-shared `cat config.toml`
|
|
11
|
+
* can read a machine's configuration without ever seeing a live credential.
|
|
12
|
+
*
|
|
13
|
+
* ## Why TOML here and JSON elsewhere
|
|
14
|
+
*
|
|
15
|
+
* These three files are the ones a person edits by hand, so they get comments
|
|
16
|
+
* and a forgiving syntax. Everything the machine writes on a hot path — the
|
|
17
|
+
* builtin policy set, cursors, collector health, the spool — stays JSON,
|
|
18
|
+
* parsed by the runtime's own native parser with no dependency and no import
|
|
19
|
+
* cost. A TOML parser on the per-tool-call path would be paying a
|
|
20
|
+
* human-ergonomics tax on a latency budget that exists precisely because
|
|
21
|
+
* per-call work was too expensive.
|
|
22
|
+
*/
|
|
23
|
+
import { chmodSync, existsSync, mkdirSync, readFileSync, statSync, writeFileSync } from "node:fs";
|
|
24
|
+
import { dirname } from "node:path";
|
|
25
|
+
import { parse as parseToml } from "smol-toml";
|
|
26
|
+
import { version as cliVersion } from "../../package.json";
|
|
27
|
+
import {
|
|
28
|
+
LAYOUT_VERSION,
|
|
29
|
+
configFile,
|
|
30
|
+
credentialsFile,
|
|
31
|
+
failproofaiHome,
|
|
32
|
+
legacy,
|
|
33
|
+
versionFile,
|
|
34
|
+
} from "./fp-home";
|
|
35
|
+
|
|
36
|
+
// ── VERSION ──────────────────────────────────────────────────────────────────
|
|
37
|
+
|
|
38
|
+
export interface VersionFile {
|
|
39
|
+
/** The on-disk layout this home was written by. */
|
|
40
|
+
layout: number;
|
|
41
|
+
/** The CLI version that last wrote it — replaces the old `last-version`. */
|
|
42
|
+
cli: string;
|
|
43
|
+
/** The daemon binary version installed, if any. */
|
|
44
|
+
daemon?: string;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export type LayoutState =
|
|
48
|
+
/** No home yet, or an empty one. A fresh install. */
|
|
49
|
+
| { kind: "absent" }
|
|
50
|
+
/** A home written by this layout. */
|
|
51
|
+
| { kind: "current"; version: VersionFile }
|
|
52
|
+
/** A home from an older layout — must be reset, never half-read. */
|
|
53
|
+
| { kind: "stale"; found: number }
|
|
54
|
+
/** A home from a NEWER layout: a downgrade. Refuse rather than corrupt it. */
|
|
55
|
+
| { kind: "future"; found: number };
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* What layout the home on disk speaks.
|
|
59
|
+
*
|
|
60
|
+
* The `VERSION` file is the primary signal; its absence is only treated as
|
|
61
|
+
* "layout 1" when the home actually contains something, because an empty or
|
|
62
|
+
* missing directory is a fresh install and must not be reported as stale.
|
|
63
|
+
*
|
|
64
|
+
* A NEWER layout is called out separately from an older one. Both are
|
|
65
|
+
* unreadable, but only one of them is fixable by re-running setup — telling a
|
|
66
|
+
* user to reset a home written by a newer CLI would delete data that a simple
|
|
67
|
+
* upgrade would have read fine.
|
|
68
|
+
*/
|
|
69
|
+
export function detectLayout(): LayoutState {
|
|
70
|
+
const raw = readVersionFile();
|
|
71
|
+
if (raw) {
|
|
72
|
+
if (raw.layout === LAYOUT_VERSION) return { kind: "current", version: raw };
|
|
73
|
+
return raw.layout > LAYOUT_VERSION
|
|
74
|
+
? { kind: "future", found: raw.layout }
|
|
75
|
+
: { kind: "stale", found: raw.layout };
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// No VERSION file. Layout 1 if any of its landmarks are present, otherwise
|
|
79
|
+
// this is simply a home that has not been set up yet.
|
|
80
|
+
const layoutOneMarkers = [
|
|
81
|
+
legacy.policyConfig(),
|
|
82
|
+
legacy.cacheDir(),
|
|
83
|
+
legacy.launcherMarker(),
|
|
84
|
+
legacy.lastVersion(),
|
|
85
|
+
legacy.cloudCredentials(),
|
|
86
|
+
legacy.ingestCredentials(),
|
|
87
|
+
legacy.auditDashboard(),
|
|
88
|
+
];
|
|
89
|
+
return layoutOneMarkers.some((p) => existsSync(p)) ? { kind: "stale", found: 1 } : { kind: "absent" };
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export function readVersionFile(): VersionFile | null {
|
|
93
|
+
try {
|
|
94
|
+
const parsed = parseToml(readFileSync(versionFile(), "utf8")) as Record<string, unknown>;
|
|
95
|
+
const layout = Number(parsed.layout);
|
|
96
|
+
if (!Number.isFinite(layout)) return null;
|
|
97
|
+
return {
|
|
98
|
+
layout,
|
|
99
|
+
cli: typeof parsed.cli === "string" ? parsed.cli : "",
|
|
100
|
+
daemon: typeof parsed.daemon === "string" ? parsed.daemon : undefined,
|
|
101
|
+
};
|
|
102
|
+
} catch {
|
|
103
|
+
// Absent, unreadable, or malformed. All three mean "cannot prove a
|
|
104
|
+
// layout", and the landmark check above decides what that implies.
|
|
105
|
+
return null;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
export function writeVersionFile(
|
|
110
|
+
v: Partial<VersionFile> & { /** Erase the daemon version rather than keeping it. */ clearDaemon?: boolean } = {},
|
|
111
|
+
): void {
|
|
112
|
+
const existing = readVersionFile();
|
|
113
|
+
const next: VersionFile = {
|
|
114
|
+
layout: LAYOUT_VERSION,
|
|
115
|
+
cli: v.cli ?? cliVersion,
|
|
116
|
+
// `undefined` means "leave whatever is there" — a CLI-only rewrite must not
|
|
117
|
+
// drop a daemon version it never touched. Erasing it is therefore an
|
|
118
|
+
// explicit act, used on uninstall.
|
|
119
|
+
daemon: v.clearDaemon ? undefined : (v.daemon ?? existing?.daemon),
|
|
120
|
+
};
|
|
121
|
+
const lines = [
|
|
122
|
+
"# Written by failproofai. `layout` is what tells a newer CLI whether this",
|
|
123
|
+
"# directory can be read as-is; do not edit it by hand.",
|
|
124
|
+
`layout = ${next.layout}`,
|
|
125
|
+
`cli = ${JSON.stringify(next.cli)}`,
|
|
126
|
+
];
|
|
127
|
+
if (next.daemon) lines.push(`daemon = ${JSON.stringify(next.daemon)}`);
|
|
128
|
+
writeFileAt(versionFile(), lines.join("\n") + "\n");
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// ── config.toml ──────────────────────────────────────────────────────────────
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* How this machine relates to Failproof Cloud.
|
|
135
|
+
*
|
|
136
|
+
* `oss` is not merely "no credentials configured" — it is a hard gate. In OSS
|
|
137
|
+
* mode nothing constructs a cloud URL, polls for policy, or spools an event
|
|
138
|
+
* for upload. A machine that never opted in must be provably silent, not
|
|
139
|
+
* silent-by-happenstance because a token lookup returned undefined.
|
|
140
|
+
*/
|
|
141
|
+
export type Mode = "oss" | "cloud";
|
|
142
|
+
|
|
143
|
+
export interface FpConfig {
|
|
144
|
+
mode: Mode;
|
|
145
|
+
daemon: {
|
|
146
|
+
/**
|
|
147
|
+
* Route hooks through the daemon — and FAIL CLOSED when it is unreachable.
|
|
148
|
+
*
|
|
149
|
+
* The installed binary VERSION deliberately does not live here; it is in
|
|
150
|
+
* `VERSION`, which is the file about versions. Two copies could disagree,
|
|
151
|
+
* and a stale one would misreport which daemon a machine is running.
|
|
152
|
+
*/
|
|
153
|
+
configured: boolean;
|
|
154
|
+
};
|
|
155
|
+
collector: {
|
|
156
|
+
sessions: boolean;
|
|
157
|
+
hooks: boolean;
|
|
158
|
+
hooksVerbosity: "all" | "decisions" | "off";
|
|
159
|
+
redact: "minimal" | "off";
|
|
160
|
+
environment: string;
|
|
161
|
+
machineId?: string;
|
|
162
|
+
};
|
|
163
|
+
telemetry: {
|
|
164
|
+
/**
|
|
165
|
+
* Anonymous product telemetry. On unless switched off.
|
|
166
|
+
*
|
|
167
|
+
* This is the ONLY off-switch that can reach the daemon.
|
|
168
|
+
* `FAILPROOFAI_TELEMETRY_DISABLED=1` is read from `process.env`, and
|
|
169
|
+
* failproofaid runs as a system-scope service whose environment carries
|
|
170
|
+
* essentially nothing — so a shell export is structurally incapable of
|
|
171
|
+
* turning it off there. A file both sides read is the only thing that works.
|
|
172
|
+
*/
|
|
173
|
+
enabled: boolean;
|
|
174
|
+
};
|
|
175
|
+
audit: {
|
|
176
|
+
/**
|
|
177
|
+
* Scan this machine's agent history on a schedule.
|
|
178
|
+
*
|
|
179
|
+
* OFF by default, and the asymmetry with `telemetry.enabled` above is
|
|
180
|
+
* deliberate: the audit reads the CONTENTS of every session transcript on
|
|
181
|
+
* disk — prompts, file contents, command output — so nothing scans on a
|
|
182
|
+
* timer until somebody asks for it.
|
|
183
|
+
*/
|
|
184
|
+
auto: boolean;
|
|
185
|
+
/** Days between scheduled runs. Wall clock, so it survives suspend. */
|
|
186
|
+
intervalDays: number;
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* Days between scheduled audits when nothing says otherwise, and the bounds a
|
|
192
|
+
* hand-written value is held to.
|
|
193
|
+
*
|
|
194
|
+
* Both ends fail quietly, which is why they are clamped at all. A measured full
|
|
195
|
+
* scan is ~104 seconds over 3,277 transcripts and grows with history, so an
|
|
196
|
+
* interval under a day spends minutes of disk every day re-reading transcripts
|
|
197
|
+
* that have barely changed. At the other end a typo'd `interval_days = 3650`
|
|
198
|
+
* reads as "never", which on a status line is indistinguishable from the
|
|
199
|
+
* feature being broken — and "never" already has its own switch, `auto = false`.
|
|
200
|
+
*/
|
|
201
|
+
export const DEFAULT_AUDIT_INTERVAL_DAYS = 7;
|
|
202
|
+
const MIN_AUDIT_INTERVAL_DAYS = 1;
|
|
203
|
+
const MAX_AUDIT_INTERVAL_DAYS = 90;
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* Resolve `interval_days`, refusing anything that is not a whole number of days.
|
|
207
|
+
*
|
|
208
|
+
* 0, a negative, a fraction below a day, and any non-number all resolve to the
|
|
209
|
+
* DEFAULT rather than clamping up to the 1-day floor. A `0` almost certainly
|
|
210
|
+
* means "off", and reading it as a DAILY 104-second scan of every transcript on
|
|
211
|
+
* the machine is the loudest possible way to get that wrong. A too-large value
|
|
212
|
+
* is clamped DOWN to 90 instead, because that is the conservative direction of
|
|
213
|
+
* the two available: falling back to 7 there would scan an order of magnitude
|
|
214
|
+
* more often than was asked for.
|
|
215
|
+
*/
|
|
216
|
+
function readIntervalDays(raw: unknown): number {
|
|
217
|
+
if (typeof raw !== "number" || !Number.isFinite(raw)) return DEFAULT_AUDIT_INTERVAL_DAYS;
|
|
218
|
+
const days = Math.floor(raw);
|
|
219
|
+
if (days < MIN_AUDIT_INTERVAL_DAYS) return DEFAULT_AUDIT_INTERVAL_DAYS;
|
|
220
|
+
return Math.min(days, MAX_AUDIT_INTERVAL_DAYS);
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
export const DEFAULT_CONFIG: FpConfig = {
|
|
224
|
+
mode: "oss",
|
|
225
|
+
daemon: { configured: false },
|
|
226
|
+
collector: {
|
|
227
|
+
sessions: false,
|
|
228
|
+
hooks: true,
|
|
229
|
+
hooksVerbosity: "decisions",
|
|
230
|
+
redact: "minimal",
|
|
231
|
+
environment: "local",
|
|
232
|
+
},
|
|
233
|
+
telemetry: { enabled: true },
|
|
234
|
+
audit: { auto: false, intervalDays: DEFAULT_AUDIT_INTERVAL_DAYS },
|
|
235
|
+
};
|
|
236
|
+
|
|
237
|
+
export function readConfig(): FpConfig {
|
|
238
|
+
try {
|
|
239
|
+
const parsed = parseToml(readFileSync(configFile(), "utf8")) as Record<string, unknown>;
|
|
240
|
+
const mode = (parsed.mode as Record<string, unknown> | undefined)?.kind;
|
|
241
|
+
const daemon = (parsed.daemon ?? {}) as Record<string, unknown>;
|
|
242
|
+
const collector = (parsed.collector ?? {}) as Record<string, unknown>;
|
|
243
|
+
const telemetry = (parsed.telemetry ?? {}) as Record<string, unknown>;
|
|
244
|
+
const audit = (parsed.audit ?? {}) as Record<string, unknown>;
|
|
245
|
+
return {
|
|
246
|
+
// Anything unrecognised reads as `oss`. The failure direction matters:
|
|
247
|
+
// a corrupt config must not be able to turn cloud reporting ON.
|
|
248
|
+
mode: mode === "cloud" ? "cloud" : "oss",
|
|
249
|
+
daemon: { configured: daemon.configured === true },
|
|
250
|
+
collector: {
|
|
251
|
+
sessions: collector.sessions === true,
|
|
252
|
+
hooks: collector.hooks !== false,
|
|
253
|
+
hooksVerbosity:
|
|
254
|
+
collector.hooks_verbosity === "all" || collector.hooks_verbosity === "off"
|
|
255
|
+
? collector.hooks_verbosity
|
|
256
|
+
: "decisions",
|
|
257
|
+
redact: collector.redact === "off" ? "off" : "minimal",
|
|
258
|
+
environment:
|
|
259
|
+
typeof collector.environment === "string" ? collector.environment : "local",
|
|
260
|
+
machineId: typeof collector.machine_id === "string" ? collector.machine_id : undefined,
|
|
261
|
+
},
|
|
262
|
+
// Only an explicit `false` switches it off. Absent, or any other value,
|
|
263
|
+
// reads as on — the shipped default, and what a config with no
|
|
264
|
+
// [telemetry] block at all means.
|
|
265
|
+
telemetry: { enabled: telemetry.enabled !== false },
|
|
266
|
+
// Mirror image of telemetry above: only an explicit `true` switches the
|
|
267
|
+
// scheduled scan on. Absent, misspelled, or `"yes"` all read as off,
|
|
268
|
+
// because the failure direction here is a machine that starts reading
|
|
269
|
+
// every transcript it can find on a timer nobody set.
|
|
270
|
+
audit: { auto: audit.auto === true, intervalDays: readIntervalDays(audit.interval_days) },
|
|
271
|
+
// Same shape as `audit.auto` above and for the same reason: only an
|
|
272
|
+
// explicit `true` opts in. Anything else — absent, misspelled, `"yes"` —
|
|
273
|
+
// reads as off, because the failure direction is a machine that starts
|
|
274
|
+
// mailing a report nobody asked for to an org address it never sees.
|
|
275
|
+
};
|
|
276
|
+
} catch {
|
|
277
|
+
return structuredClone(DEFAULT_CONFIG);
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
export function writeConfig(config: FpConfig): void {
|
|
282
|
+
const c = config.collector;
|
|
283
|
+
const lines = [
|
|
284
|
+
"# failproofai configuration. Safe to edit by hand.",
|
|
285
|
+
"# Credentials are NOT here — see credentials.toml (owner-only).",
|
|
286
|
+
"",
|
|
287
|
+
"[mode]",
|
|
288
|
+
"# \"oss\" — fully local. No transcripts, hook activity or policy leave",
|
|
289
|
+
"# this machine.",
|
|
290
|
+
"# \"cloud\" — reports to, and receives policy from, Failproof Cloud.",
|
|
291
|
+
`kind = ${JSON.stringify(config.mode)}`,
|
|
292
|
+
"",
|
|
293
|
+
"[daemon]",
|
|
294
|
+
"# When true, hooks route through failproofaid AND FAIL CLOSED if it is",
|
|
295
|
+
"# unreachable. Never set this by hand: `failproofai config` sets it only",
|
|
296
|
+
"# after verifying the service is genuinely running.",
|
|
297
|
+
`configured = ${config.daemon.configured}`,
|
|
298
|
+
];
|
|
299
|
+
lines.push(
|
|
300
|
+
"",
|
|
301
|
+
"[collector]",
|
|
302
|
+
"# Session transcripts carry prompts, file contents and command output.",
|
|
303
|
+
`sessions = ${c.sessions}`,
|
|
304
|
+
"# Hook decisions: which policy fired and what it decided. No file contents.",
|
|
305
|
+
`hooks = ${c.hooks}`,
|
|
306
|
+
`hooks_verbosity = ${JSON.stringify(c.hooksVerbosity)}`,
|
|
307
|
+
`redact = ${JSON.stringify(c.redact)}`,
|
|
308
|
+
`environment = ${JSON.stringify(c.environment)}`,
|
|
309
|
+
);
|
|
310
|
+
if (c.machineId) lines.push(`machine_id = ${JSON.stringify(c.machineId)}`);
|
|
311
|
+
// Written ONLY when switched off. A default install therefore carries no
|
|
312
|
+
// [telemetry] block at all, but an operator who added one by hand keeps it:
|
|
313
|
+
// writeConfig regenerates this file wholesale, so emitting the key only when
|
|
314
|
+
// it is set is what stops a later rewrite from silently switching telemetry
|
|
315
|
+
// back on underneath them.
|
|
316
|
+
if (!config.telemetry.enabled) {
|
|
317
|
+
lines.push("", "[telemetry]", "enabled = false");
|
|
318
|
+
}
|
|
319
|
+
// Written ALWAYS, unlike [telemetry] directly above — the two are opposites
|
|
320
|
+
// on purpose. Telemetry ships on and is deliberately not advertised in the
|
|
321
|
+
// file; the scheduled audit ships off and is meant to be FOUND, and a switch
|
|
322
|
+
// nobody can see is the same as a switch that does not exist. Emitting both
|
|
323
|
+
// keys unconditionally also makes "a user's setting survives a rewrite" total
|
|
324
|
+
// rather than conditional: every field the struct carries is on disk, so
|
|
325
|
+
// there is no value of this block that a later regeneration can drop.
|
|
326
|
+
lines.push(
|
|
327
|
+
"",
|
|
328
|
+
"[audit]",
|
|
329
|
+
"# Scan this machine's agent history on a schedule and refresh the audit",
|
|
330
|
+
"# dashboard. OFF by default: the scan reads the CONTENTS of every session",
|
|
331
|
+
"# transcript on disk, so it waits to be asked. It runs as a separate",
|
|
332
|
+
"# short-lived process — never on the hook path.",
|
|
333
|
+
`auto = ${config.audit.auto}`,
|
|
334
|
+
`interval_days = ${config.audit.intervalDays}`,
|
|
335
|
+
);
|
|
336
|
+
writeFileAt(configFile(), lines.join("\n") + "\n");
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
/** Merge a partial update into the config on disk. */
|
|
340
|
+
export function updateConfig(patch: {
|
|
341
|
+
mode?: Mode;
|
|
342
|
+
daemon?: Partial<FpConfig["daemon"]>;
|
|
343
|
+
collector?: Partial<FpConfig["collector"]>;
|
|
344
|
+
telemetry?: Partial<FpConfig["telemetry"]>;
|
|
345
|
+
audit?: Partial<FpConfig["audit"]>;
|
|
346
|
+
}): FpConfig {
|
|
347
|
+
const current = readConfig();
|
|
348
|
+
const next: FpConfig = {
|
|
349
|
+
mode: patch.mode ?? current.mode,
|
|
350
|
+
daemon: { ...current.daemon, ...patch.daemon },
|
|
351
|
+
collector: { ...current.collector, ...patch.collector },
|
|
352
|
+
telemetry: { ...current.telemetry, ...patch.telemetry },
|
|
353
|
+
audit: { ...current.audit, ...patch.audit },
|
|
354
|
+
};
|
|
355
|
+
writeConfig(next);
|
|
356
|
+
return next;
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
// ── credentials.toml ─────────────────────────────────────────────────────────
|
|
360
|
+
|
|
361
|
+
export interface FpCredentials {
|
|
362
|
+
cloud?: { url: string; machineId: string; token: string; machineLabel?: string };
|
|
363
|
+
/**
|
|
364
|
+
* Which organisation this machine's credentials report into, as the server
|
|
365
|
+
* named it at connect time (`/v1/auth/introspect`).
|
|
366
|
+
*
|
|
367
|
+
* ONE table, not a field on `[cloud]` and `[ingest]` both: it describes the
|
|
368
|
+
* TOKEN, and connect uses the same token for both capabilities, so two copies
|
|
369
|
+
* could only ever drift or disagree. It also has to survive the case that
|
|
370
|
+
* motivated recording it at all — an `events:add`-only key, which configures
|
|
371
|
+
* ingest and never writes `[cloud]`.
|
|
372
|
+
*
|
|
373
|
+
* Recorded so `--status` can answer "where does this machine's data go?"
|
|
374
|
+
* with no network call: the question asked when a dashboard looks empty, and
|
|
375
|
+
* the one a key pasted from the wrong org gets silently wrong. Absent against
|
|
376
|
+
* a server predating introspect, and in files written by an older CLI.
|
|
377
|
+
*/
|
|
378
|
+
org?: { id?: string; slug?: string; name?: string };
|
|
379
|
+
ingest?: { url: string; key: string };
|
|
380
|
+
auth?: { baseUrl?: string; sessionToken?: string; expiresAt?: number; email?: string };
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
export function readCredentials(): FpCredentials {
|
|
384
|
+
try {
|
|
385
|
+
const parsed = parseToml(readFileSync(credentialsFile(), "utf8")) as Record<string, unknown>;
|
|
386
|
+
const cloud = parsed.cloud as Record<string, unknown> | undefined;
|
|
387
|
+
const ingest = parsed.ingest as Record<string, unknown> | undefined;
|
|
388
|
+
const auth = parsed.auth as Record<string, unknown> | undefined;
|
|
389
|
+
const out: FpCredentials = {};
|
|
390
|
+
if (
|
|
391
|
+
cloud &&
|
|
392
|
+
typeof cloud.url === "string" &&
|
|
393
|
+
typeof cloud.machine_id === "string" &&
|
|
394
|
+
typeof cloud.token === "string" &&
|
|
395
|
+
cloud.url &&
|
|
396
|
+
cloud.token
|
|
397
|
+
) {
|
|
398
|
+
const machineLabel =
|
|
399
|
+
typeof cloud.machine_label === "string" && cloud.machine_label ? cloud.machine_label : undefined;
|
|
400
|
+
out.cloud = { url: cloud.url, machineId: cloud.machine_id, token: cloud.token, machineLabel };
|
|
401
|
+
}
|
|
402
|
+
const org = parsed.org as Record<string, unknown> | undefined;
|
|
403
|
+
if (org) {
|
|
404
|
+
// Every field independently optional: a server may send fewer than three,
|
|
405
|
+
// and a partial org record is still better than none for `--status`.
|
|
406
|
+
const pick = (v: unknown) => (typeof v === "string" && v ? v : undefined);
|
|
407
|
+
const built = { id: pick(org.id), slug: pick(org.slug), name: pick(org.name) };
|
|
408
|
+
if (built.id || built.slug || built.name) out.org = built;
|
|
409
|
+
}
|
|
410
|
+
if (ingest && typeof ingest.url === "string" && typeof ingest.key === "string" && ingest.url && ingest.key) {
|
|
411
|
+
out.ingest = { url: ingest.url, key: ingest.key };
|
|
412
|
+
}
|
|
413
|
+
if (auth) {
|
|
414
|
+
out.auth = {
|
|
415
|
+
baseUrl: typeof auth.base_url === "string" ? auth.base_url : undefined,
|
|
416
|
+
sessionToken: typeof auth.session_token === "string" ? auth.session_token : undefined,
|
|
417
|
+
expiresAt: typeof auth.expires_at === "number" ? auth.expires_at : undefined,
|
|
418
|
+
email: typeof auth.email === "string" ? auth.email : undefined,
|
|
419
|
+
};
|
|
420
|
+
}
|
|
421
|
+
return out;
|
|
422
|
+
} catch {
|
|
423
|
+
return {};
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
/**
|
|
428
|
+
* Write credentials at 0600, tightening the home that holds them.
|
|
429
|
+
*
|
|
430
|
+
* A 0600 file inside a 0775 directory is still reachable by every local user,
|
|
431
|
+
* so the directory is tightened too. `mode` on `writeFileSync` applies only
|
|
432
|
+
* when the file is CREATED, so an existing over-permissive file keeps its mode
|
|
433
|
+
* without the explicit chmod.
|
|
434
|
+
*/
|
|
435
|
+
export function writeCredentials(creds: FpCredentials): void {
|
|
436
|
+
const lines: string[] = [
|
|
437
|
+
"# failproofai credentials — owner-only (0600). Do not commit.",
|
|
438
|
+
];
|
|
439
|
+
if (creds.cloud) {
|
|
440
|
+
lines.push(
|
|
441
|
+
"",
|
|
442
|
+
"[cloud]",
|
|
443
|
+
`url = ${JSON.stringify(creds.cloud.url)}`,
|
|
444
|
+
`machine_id = ${JSON.stringify(creds.cloud.machineId)}`,
|
|
445
|
+
...(creds.cloud.machineLabel
|
|
446
|
+
? [`machine_label = ${JSON.stringify(creds.cloud.machineLabel)}`]
|
|
447
|
+
: []),
|
|
448
|
+
`token = ${JSON.stringify(creds.cloud.token)}`,
|
|
449
|
+
);
|
|
450
|
+
}
|
|
451
|
+
if (creds.ingest) {
|
|
452
|
+
lines.push(
|
|
453
|
+
"",
|
|
454
|
+
"[ingest]",
|
|
455
|
+
`url = ${JSON.stringify(creds.ingest.url)}`,
|
|
456
|
+
`key = ${JSON.stringify(creds.ingest.key)}`,
|
|
457
|
+
);
|
|
458
|
+
}
|
|
459
|
+
if (creds.org && (creds.org.id || creds.org.slug || creds.org.name)) {
|
|
460
|
+
lines.push("", "[org]");
|
|
461
|
+
if (creds.org.id) lines.push(`id = ${JSON.stringify(creds.org.id)}`);
|
|
462
|
+
if (creds.org.slug) lines.push(`slug = ${JSON.stringify(creds.org.slug)}`);
|
|
463
|
+
if (creds.org.name) lines.push(`name = ${JSON.stringify(creds.org.name)}`);
|
|
464
|
+
}
|
|
465
|
+
if (creds.auth && (creds.auth.sessionToken || creds.auth.email)) {
|
|
466
|
+
lines.push("", "[auth]");
|
|
467
|
+
if (creds.auth.baseUrl) lines.push(`base_url = ${JSON.stringify(creds.auth.baseUrl)}`);
|
|
468
|
+
if (creds.auth.sessionToken) lines.push(`session_token = ${JSON.stringify(creds.auth.sessionToken)}`);
|
|
469
|
+
if (creds.auth.expiresAt) lines.push(`expires_at = ${creds.auth.expiresAt}`);
|
|
470
|
+
if (creds.auth.email) lines.push(`email = ${JSON.stringify(creds.auth.email)}`);
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
const home = failproofaiHome();
|
|
474
|
+
mkdirSync(home, { recursive: true });
|
|
475
|
+
try {
|
|
476
|
+
if (statSync(home).mode & 0o077) chmodSync(home, 0o700);
|
|
477
|
+
} catch {
|
|
478
|
+
// Not fatal: the file's own 0600 is the primary protection.
|
|
479
|
+
}
|
|
480
|
+
writeFileSync(credentialsFile(), lines.join("\n") + "\n", { mode: 0o600 });
|
|
481
|
+
try {
|
|
482
|
+
chmodSync(credentialsFile(), 0o600);
|
|
483
|
+
} catch {
|
|
484
|
+
/* best effort on filesystems without POSIX modes */
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
/** True when this machine has any cloud credential at all. */
|
|
489
|
+
export function hasCloudCredentials(): boolean {
|
|
490
|
+
const c = readCredentials();
|
|
491
|
+
return Boolean(c.cloud || c.ingest);
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
function writeFileAt(path: string, contents: string): void {
|
|
495
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
496
|
+
writeFileSync(path, contents, "utf8");
|
|
497
|
+
}
|