failproofai 1.0.10 → 1.0.11-beta.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.next/standalone/.next/BUILD_ID +1 -1
- package/.next/standalone/.next/build-manifest.json +6 -6
- package/.next/standalone/.next/prerender-manifest.json +4 -4
- package/.next/standalone/.next/required-server-files.json +1 -1
- 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 +4 -4
- 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.meta +8 -0
- package/.next/standalone/.next/server/app/_global-error.rsc +7 -7
- package/.next/standalone/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +6 -6
- package/.next/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +7 -7
- 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 +4 -4
- 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.meta +8 -0
- package/.next/standalone/.next/server/app/_not-found.rsc +14 -14
- package/.next/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +14 -14
- package/.next/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +13 -13
- package/.next/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/api/audit/invite/route.js +4 -4
- 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 -4
- 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 -2
- 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 +4 -4
- 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 -3
- package/.next/standalone/.next/server/app/api/auth/logout/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/auth/status/route.js +3 -3
- 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 +4 -4
- 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.meta +8 -0
- package/.next/standalone/.next/server/app/index.rsc +14 -14
- package/.next/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +13 -13
- package/.next/standalone/.next/server/app/index.segments/_full.segment.rsc +14 -14
- package/.next/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
- 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 +4 -4
- 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 +14 -14
- package/.next/standalone/.next/server/app/policies/page.js +8 -8
- 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 +5 -5
- 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 +5 -5
- 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 +5 -5
- 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/build-manifest.json +3 -3
- package/.next/standalone/.next/server/app/settings/page/server-reference-manifest.json +8 -8
- package/.next/standalone/.next/server/app/settings/page.js +9 -9
- package/.next/standalone/.next/server/app/settings/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/settings/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/chunks/{[externals]__0qxhecv._.js → [externals]__0d7i9ea._.js} +2 -2
- package/.next/standalone/.next/server/chunks/{[externals]__0sg9-3p._.js → [externals]__0hjaw70._.js} +1 -1
- package/.next/standalone/.next/server/chunks/[externals]__1_9ze9b._.js +3 -0
- package/.next/standalone/.next/server/chunks/[externals]__1oenfmn._.js +3 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__08dchfu._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0ga9hs7._.js +3 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0l3yhx4._.js +2 -2
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0moxohc._.js +22 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0o07qi9._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0z2ll-a._.js +3 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__1adacul._.js +11 -11
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__1autwno._.js +3 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__1v25n-6._.js +3 -0
- package/.next/standalone/.next/server/chunks/_09dz7xv._.js +1 -1
- package/.next/standalone/.next/server/chunks/_0tovk6q._.js +1 -1
- package/.next/standalone/.next/server/chunks/_0trp3yc._.js +1 -1
- package/.next/standalone/.next/server/chunks/_1ek68ln._.js +2 -2
- package/.next/standalone/.next/server/chunks/_1fz8m31._.js +1 -1
- package/.next/standalone/.next/server/chunks/_1jrv3hg._.js +3 -0
- package/.next/standalone/.next/server/chunks/_1nx8fim._.js +3 -0
- package/.next/standalone/.next/server/chunks/_1ogwigf._.js +1 -1
- package/.next/standalone/.next/server/chunks/{_1q5i8mb._.js → _1q27azx._.js} +2 -2
- package/.next/standalone/.next/server/chunks/lib_telemetry_ts_0dctyyw._.js +1 -1
- package/.next/standalone/.next/server/chunks/{node_modules_posthog-node_dist_entrypoints_index_node_mjs_0el6rg8._.js → node_modules_posthog-node_dist_entrypoints_index_node_mjs_0ol5pa3._.js} +2 -2
- package/.next/standalone/.next/server/chunks/{node_modules_posthog-node_dist_entrypoints_index_node_mjs_04op_ou._.js → node_modules_posthog-node_dist_entrypoints_index_node_mjs_1rqmk27._.js} +2 -2
- package/.next/standalone/.next/server/chunks/{node_modules_posthog-node_dist_entrypoints_index_node_mjs_0dr20uz._.js → node_modules_posthog-node_dist_entrypoints_index_node_mjs_1w7tiq4._.js} +2 -2
- 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]__0oqrl71._.js → [root-of-the-server]__042u-jn._.js} +2 -2
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__04usis8._.js +2 -2
- package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__1-bm5zf._.js → [root-of-the-server]__0502lat._.js} +1 -1
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0_gn5iz._.js +4 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0f765xr._.js +4 -0
- package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__01bmjsj._.js → [root-of-the-server]__0h38gzz._.js} +1 -1
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0hoaz1g._.js +4 -0
- package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__1ouobn4._.js → [root-of-the-server]__0mxmwtx._.js} +1 -1
- package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__19evfi8._.js → [root-of-the-server]__0uescqv._.js} +1 -1
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__13d-wb6._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1484zv2._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__1mf3zp6._.js → [root-of-the-server]__19dutr7._.js} +4 -4
- package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__1u6sn69._.js → [root-of-the-server]__1ab4ifb._.js} +1 -1
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1fbrk56._.js +4 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1pprgri._.js +2 -2
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1um4yxn._.js +4 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1wb4yt2._.js +4 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1y3n3cn._.js +4 -0
- package/.next/standalone/.next/server/chunks/ssr/_08x1r5t._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/_0bqoto4._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/{_0yq5_3a._.js → _1ffxwxz._.js} +1 -1
- package/.next/standalone/.next/server/chunks/ssr/_1gb0ifp._.js +2 -2
- package/.next/standalone/.next/server/chunks/ssr/{_042cgl1._.js → _1o95rf0._.js} +2 -2
- package/.next/standalone/.next/server/chunks/ssr/{_1zopuov._.js → _1thr5rf._.js} +2 -2
- package/.next/standalone/.next/server/chunks/ssr/{_16pa9ht._.js → _1v1o2ya._.js} +2 -2
- package/.next/standalone/.next/server/chunks/ssr/_next-internal_server_app_policies_page_actions_1sp2-yo.js +12 -14
- package/.next/standalone/.next/server/chunks/ssr/app_actions_get-scheduled-audit_ts_0ei9sni._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/app_audit__components_audit-dashboard_tsx_0p9ud47._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/app_global-error_tsx_1kp6l3x._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/app_policies_hooks-client_tsx_19dqvpc._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/app_settings_02tf1h4._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/node_modules_1hl81y4._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/node_modules_next_dist_0nkkbfv._.js +6 -0
- package/.next/standalone/.next/server/chunks/ssr/src_hooks_fp-config_ts_04t589g._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/src_hooks_fp-home_ts_0je3xkv._.js +1 -1
- package/.next/standalone/.next/server/middleware-build-manifest.js +6 -6
- package/.next/standalone/.next/server/middleware.js +1 -1
- 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 +23 -23
- package/.next/standalone/.next/static/chunks/08tmc_7n-rba1.js +1 -0
- package/.next/standalone/.next/static/chunks/{3mjfvng11xnoo.js → 09tlrmucn6oq3.js} +1 -1
- package/.next/standalone/.next/static/chunks/{078dofaxvjgpd.js → 0i69k917myh7b.js} +1 -1
- package/.next/standalone/.next/static/chunks/{03h3b-uomi7_d.js → 0u_nhsujkactp.js} +1 -1
- package/.next/standalone/.next/static/chunks/{2j92xul6y24_v.js → 0zp51hzx62i-u.js} +1 -1
- package/.next/standalone/.next/static/chunks/{3u1s8dsqiean5.js → 1fz7q0gtnmeod.js} +1 -1
- package/.next/standalone/.next/static/chunks/{3ob2161ap37ul.js → 1j75le8wln-26.js} +1 -1
- package/.next/standalone/.next/static/chunks/{2kj826h1w9r47.js → 1z4km7tw0_50h.js} +1 -1
- package/.next/standalone/.next/static/chunks/{1fqukzzowjflt.js → 239ay_80-xr8z.js} +1 -1
- package/.next/standalone/.next/static/chunks/{3nkc2jlgyygda.js → 2c9pc5xco6rnu.js} +1 -1
- package/.next/standalone/.next/static/chunks/{3i8rg2fv9nd6e.js → 3n46e45atic-m.js} +1 -1
- package/.next/standalone/.next/static/chunks/{0542cuk_va8g1.js → 3sg07wnn969xq.js} +1 -1
- package/.next/standalone/.next/static/chunks/{turbopack-11dn2pegmg9_9.js → turbopack-1opxka4dtjbc5.js} +1 -1
- package/.next/standalone/app/actions/install-hooks-web.ts +10 -2
- package/.next/standalone/hermes-plugin/plugin.yaml +1 -1
- package/.next/standalone/node_modules/@next/env/package.json +1 -1
- package/.next/standalone/node_modules/next/dist/build/swc/index.js +1 -1
- package/.next/standalone/node_modules/next/dist/compiled/hash.js/sha256/256.js +1 -0
- package/.next/standalone/node_modules/next/dist/compiled/hash.js/sha256/package.json +1 -0
- package/.next/standalone/node_modules/next/dist/compiled/next-server/app-page-turbo-experimental.runtime.prod.js +17 -17
- package/.next/standalone/node_modules/next/dist/compiled/next-server/app-page-turbo.runtime.prod.js +18 -18
- package/.next/standalone/node_modules/next/dist/compiled/next-server/app-route-turbo.runtime.prod.js +3 -3
- package/.next/standalone/node_modules/next/dist/compiled/next-server/pages-turbo.runtime.prod.js +13 -13
- package/.next/standalone/node_modules/next/dist/lib/patch-incorrect-lockfile.js +3 -3
- package/.next/standalone/node_modules/next/dist/server/base-server.js +1 -2
- package/.next/standalone/node_modules/next/dist/server/config.js +1 -1
- package/.next/standalone/node_modules/next/dist/server/dev/hot-reloader-turbopack.js +2 -2
- package/.next/standalone/node_modules/next/dist/server/dev/hot-reloader-webpack.js +1 -1
- package/.next/standalone/node_modules/next/dist/server/image-optimizer.js +265 -53
- package/.next/standalone/node_modules/next/dist/server/lib/app-info-log.js +1 -1
- package/.next/standalone/node_modules/next/dist/server/lib/incremental-cache/file-system-cache.js +177 -19
- package/.next/standalone/node_modules/next/dist/server/lib/incremental-cache/index.js +36 -17
- package/.next/standalone/node_modules/next/dist/server/lib/incremental-cache/shared-cache-controls.external.js +14 -6
- package/.next/standalone/node_modules/next/dist/server/lib/match-next-data-pathname.js +3 -1
- package/.next/standalone/node_modules/next/dist/server/lib/node-fs-methods.js +14 -0
- package/.next/standalone/node_modules/next/dist/server/lib/route-cache-key.js +77 -0
- package/.next/standalone/node_modules/next/dist/server/lib/router-utils/block-cross-site-dev.js +9 -11
- package/.next/standalone/node_modules/next/dist/server/lib/start-server.js +1 -1
- package/.next/standalone/node_modules/next/dist/server/mcp/get-mcp-middleware.js +4 -2
- package/.next/standalone/node_modules/next/dist/server/next-server.js +7 -3
- package/.next/standalone/node_modules/next/dist/server/response-cache/index.js +32 -16
- package/.next/standalone/node_modules/next/dist/server/route-matcher-providers/app-page-route-matcher-provider.js +2 -2
- package/.next/standalone/node_modules/next/dist/server/route-modules/pages/pages-handler.js +6 -1
- package/.next/standalone/node_modules/next/dist/server/route-modules/route-module.js +9 -2
- package/.next/standalone/node_modules/next/dist/server/use-cache/use-cache-wrapper.js +30 -29
- package/.next/standalone/node_modules/next/dist/shared/lib/router/utils/app-paths.js +18 -1
- package/.next/standalone/node_modules/next/dist/telemetry/anonymous-meta.js +1 -1
- package/.next/standalone/node_modules/next/dist/telemetry/events/swc-load-failure.js +1 -1
- package/.next/standalone/node_modules/next/dist/telemetry/events/version.js +2 -2
- package/.next/standalone/node_modules/next/package.json +10 -10
- package/.next/standalone/package.json +14 -11
- package/.next/standalone/sdk/python/README.md +41 -0
- package/.next/standalone/sdk/python/docs/README.md +1 -0
- package/.next/standalone/sdk/python/docs/otel/README.md +36 -0
- package/.next/standalone/sdk/python/docs/otel/examples/agent_trace.py +30 -0
- package/.next/standalone/sdk/python/docs/otel/examples/quickstart.py +22 -0
- package/.next/standalone/sdk/python/failproofai_sdk/__init__.py +8 -0
- package/.next/standalone/sdk/python/failproofai_sdk/integrations/__init__.py +3 -0
- package/.next/standalone/sdk/python/failproofai_sdk/integrations/otel.py +189 -0
- package/.next/standalone/sdk/python/pyproject.toml +2 -1
- package/.next/standalone/sdk/python/tests/test_docs.py +16 -3
- package/.next/standalone/sdk/python/tests/test_integrations.py +1 -1
- package/.next/standalone/sdk/python/tests/test_otel.py +147 -0
- package/.next/standalone/sdk/python/tests/test_zero_dependencies.py +4 -1
- package/.next/standalone/sdk/python/uv.lock +258 -147
- package/.next/standalone/sdk/typescript/README.md +43 -0
- package/.next/standalone/sdk/typescript/integration/ai.test.ts +9 -2
- package/.next/standalone/sdk/typescript/package-lock.json +57 -5
- package/.next/standalone/sdk/typescript/package.json +2 -0
- package/.next/standalone/sdk/typescript/src/edge/index.ts +9 -0
- package/.next/standalone/sdk/typescript/src/index.ts +2 -0
- package/.next/standalone/sdk/typescript/src/integrations/index.ts +4 -1
- package/.next/standalone/sdk/typescript/src/integrations/otel.ts +204 -0
- package/.next/standalone/sdk/typescript/test/integrations.test.ts +1 -1
- package/.next/standalone/sdk/typescript/test/otel.test.ts +125 -0
- package/.next/standalone/server.js +1 -1
- package/bin/failproofai.mjs +653 -1077
- package/dist/cli.mjs +8174 -3930
- package/dist/worker.mjs +170 -62
- package/hermes-plugin/plugin.yaml +1 -1
- package/package.json +14 -11
- package/scripts/launch-facts.ts +157 -0
- package/scripts/launch-ready.ts +228 -0
- package/scripts/launch-screen.ts +202 -0
- package/scripts/launch.ts +106 -49
- package/scripts/skew-log-filter.ts +42 -0
- package/src/audit/cli-login.ts +39 -59
- package/src/audit/cli.ts +308 -228
- package/src/audit/index.ts +45 -2
- package/src/audit/schedule-cli.ts +83 -120
- package/src/audit/types.ts +31 -0
- package/src/hooks/agent-selection.ts +155 -0
- package/src/hooks/backfill-cli.ts +151 -137
- package/src/hooks/backfill-request.ts +149 -0
- package/src/hooks/cloud-connection.ts +1 -1
- package/src/hooks/cloud-enrollment-cli.ts +78 -28
- package/src/hooks/cloud-introspect.ts +3 -0
- package/src/hooks/collector-config.ts +10 -1
- package/src/hooks/config-status.ts +606 -0
- package/src/hooks/configure-wizard.ts +915 -989
- package/src/hooks/daemon-service.ts +16 -6
- package/src/hooks/did-you-mean.ts +185 -0
- package/src/hooks/flush-cli.ts +33 -7
- package/src/hooks/fp-config.ts +96 -0
- package/src/hooks/fp-home.ts +11 -0
- package/src/hooks/fp-reset.ts +102 -95
- package/src/hooks/handler.ts +4 -0
- package/src/hooks/harness-cli.ts +87 -138
- package/src/hooks/install-prompt.ts +38 -477
- package/src/hooks/integrations.ts +1 -1
- package/src/hooks/jev-cli.ts +183 -196
- package/src/hooks/manager.ts +554 -358
- package/src/hooks/onboarding-attempt.ts +11 -9
- package/src/hooks/otel-cli.ts +344 -0
- package/src/hooks/otel-output.ts +24 -0
- package/src/hooks/otel-writers.ts +225 -0
- package/src/hooks/pack-cli.ts +219 -204
- package/src/hooks/report-body.ts +50 -0
- package/src/hooks/safe-config-write.ts +4 -2
- package/src/hooks/session-pause-cli.ts +93 -73
- package/src/hooks/tui.ts +849 -389
- package/src/hooks/uninstall-cli.ts +136 -149
- package/src/hooks/untraced-note.ts +52 -0
- package/.next/standalone/.next/server/chunks/[externals]__19_pzeq._.js +0 -3
- package/.next/standalone/.next/server/chunks/[externals]__20kzpkf._.js +0 -3
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__01eqny5._.js +0 -3
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__05ja_05._.js +0 -3
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0igxt32._.js +0 -3
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0zyc_nm._.js +0 -3
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__1ttrwnd._.js +0 -22
- package/.next/standalone/.next/server/chunks/_0bs-v8p._.js +0 -3
- package/.next/standalone/.next/server/chunks/_185gyk9._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__056wjo4._.js +0 -4
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0n0xg95._.js +0 -4
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0rwtwpm._.js +0 -4
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0yrsbd_._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__11mayhe._.js +0 -4
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__15578wp._.js +0 -4
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1m_svbe._.js +0 -4
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1q4p5b8._.js +0 -4
- package/.next/standalone/.next/server/chunks/ssr/node_modules_next_dist_1n3w9lb._.js +0 -6
- package/.next/standalone/.next/static/chunks/1lyg_puttohbc.js +0 -1
- /package/.next/standalone/.next/static/{ECs8__hcNabYV58WfDh-3 → UZKCiP4Q2TeU8zOB-h9Xj}/_buildManifest.js +0 -0
- /package/.next/standalone/.next/static/{ECs8__hcNabYV58WfDh-3 → UZKCiP4Q2TeU8zOB-h9Xj}/_clientMiddlewareManifest.js +0 -0
- /package/.next/standalone/.next/static/{ECs8__hcNabYV58WfDh-3 → UZKCiP4Q2TeU8zOB-h9Xj}/_ssgManifest.js +0 -0
package/src/audit/cli.ts
CHANGED
|
@@ -8,11 +8,11 @@
|
|
|
8
8
|
* failproofai audit --status What this machine is scheduled to do.
|
|
9
9
|
* failproofai audit -h, --help Show usage.
|
|
10
10
|
*
|
|
11
|
-
* `runAudit()` is a pure local function (no network, no account). We run it
|
|
12
|
-
*
|
|
13
|
-
* pre-warm the dashboard cache
|
|
14
|
-
*
|
|
15
|
-
* instantly from that cache.
|
|
11
|
+
* `runAudit()` is a pure local function (no network, no account). We run it
|
|
12
|
+
* behind a progress screen — a bar per agent, fed by `runAudit`'s progress
|
|
13
|
+
* callback, and running counts of what was found — pre-warm the dashboard cache
|
|
14
|
+
* (~/.failproofai/audit-dashboard.json), then start the bundled dashboard server
|
|
15
|
+
* and open the browser to /audit, which renders instantly from that cache.
|
|
16
16
|
*
|
|
17
17
|
* A bare `failproofai audit` does a full scan (all CLIs, all history); the
|
|
18
18
|
* scheduling flags above write config and never scan. Scan-shaping flags
|
|
@@ -25,6 +25,7 @@
|
|
|
25
25
|
* an exit code. See `runScheduledAudit`.
|
|
26
26
|
*/
|
|
27
27
|
import { runAudit } from "./index";
|
|
28
|
+
import { getDetectorByName } from "./detectors";
|
|
28
29
|
import { hasInstalledPacks } from "../hooks/pack-manifest";
|
|
29
30
|
import { CORE_SOURCE } from "../hooks/pack-store";
|
|
30
31
|
import { acquireAuditLock, type AuditLockInfo } from "./audit-lock";
|
|
@@ -35,7 +36,13 @@ import { getInstanceId } from "../../lib/telemetry-id";
|
|
|
35
36
|
import { sanitizeErrorMessage } from "../../lib/telemetry-sanitize";
|
|
36
37
|
import { openWhenReady } from "./open-browser";
|
|
37
38
|
import { describeOutcome, reportHarm } from "./report-harm";
|
|
38
|
-
import {
|
|
39
|
+
import {
|
|
40
|
+
INDENT,
|
|
41
|
+
optsFor,
|
|
42
|
+
screenKit,
|
|
43
|
+
type ScreenKitOpts,
|
|
44
|
+
type TTYOut,
|
|
45
|
+
} from "../hooks/tui";
|
|
39
46
|
import { version } from "../../package.json";
|
|
40
47
|
|
|
41
48
|
/** Port the bundled dashboard binds to. Matches `scripts/launch.ts`'s default
|
|
@@ -53,10 +60,17 @@ const DASHBOARD_PORT = 8020;
|
|
|
53
60
|
export const EXIT_AUDIT_ALREADY_RUNNING = 75;
|
|
54
61
|
|
|
55
62
|
/**
|
|
56
|
-
* Mirror of `app/audit/_components/run-progress.tsx`'s `STAGES
|
|
57
|
-
*
|
|
58
|
-
*
|
|
59
|
-
*
|
|
63
|
+
* Mirror of `app/audit/_components/run-progress.tsx`'s `STAGES`: the four
|
|
64
|
+
* time-driven stages the dashboard animates while its own run is in flight.
|
|
65
|
+
*
|
|
66
|
+
* The CLI no longer animates them. By decision D16 of the 2026-10 redesign it
|
|
67
|
+
* draws real progress instead — a bar per agent from `runAudit`'s progress
|
|
68
|
+
* callback, and running counts of what was found — so the screen moves when the
|
|
69
|
+
* scan does rather than on a timer. What the two still share is these words:
|
|
70
|
+
* the CLI labels the one phase it has no bars for, listing transcripts, with
|
|
71
|
+
* the first stage's label, and the per-agent bars stand for the middle two.
|
|
72
|
+
* `audit-cli.test.ts` keeps this list identical to the dashboard's, so a stage
|
|
73
|
+
* renamed there is renamed here too.
|
|
60
74
|
*/
|
|
61
75
|
export const AUDIT_STAGES: ReadonlyArray<{ label: string; detail: string }> = [
|
|
62
76
|
{ label: "discovering transcripts", detail: "walking ~/.claude, ~/.codex, ~/.cursor, …" },
|
|
@@ -80,76 +94,50 @@ export const AUDIT_STAGES: ReadonlyArray<{ label: string; detail: string }> = [
|
|
|
80
94
|
* one. It still works, and still refuses every argument it always refused.
|
|
81
95
|
*/
|
|
82
96
|
export function helpText(): string {
|
|
83
|
-
//
|
|
84
|
-
// the
|
|
85
|
-
//
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
// the end of one line and its placeholder at the start of the next
|
|
106
|
-
// is not a flag anybody can read or copy.
|
|
107
|
-
["--email <address>", "With --schedule, skips the sign-in prompt."],
|
|
108
|
-
["--no-schedule", "Stop the timer. Leaves you signed in."],
|
|
109
|
-
["--status", "Whether scheduling is on, where reports go, the daemon's state, and when the next scan is due."],
|
|
110
|
-
["-h, --help", "Show this help."],
|
|
111
|
-
],
|
|
112
|
-
},
|
|
113
|
-
],
|
|
114
|
-
footer: ["Everything runs on this machine; only a scheduled digest ever leaves it."],
|
|
115
|
-
},
|
|
116
|
-
helpOptsFor(process.stdout),
|
|
117
|
-
);
|
|
97
|
+
// The same page every `<command> --help` is: usage, options and examples,
|
|
98
|
+
// built from the 2026-10 kit. Descriptions are prose and may be shortened to
|
|
99
|
+
// fit a terminal; nothing is ever cut in a pipe.
|
|
100
|
+
const kit = screenKit({ ...optsFor(process.stdout), fit: !!process.stdout.isTTY, version });
|
|
101
|
+
const lines = kit.helpPage({
|
|
102
|
+
name: "audit",
|
|
103
|
+
usage: [
|
|
104
|
+
["failproofai audit", "Scan your agents' history and open the results"],
|
|
105
|
+
["failproofai audit [options]", "Manage scheduled scans"],
|
|
106
|
+
],
|
|
107
|
+
options: [
|
|
108
|
+
["--schedule [days]", "Scan every N days (1-90, default 7) and email the findings"],
|
|
109
|
+
// Its own row rather than a clause inside --schedule's, so the flag and
|
|
110
|
+
// its placeholder can never be split across a wrap.
|
|
111
|
+
["--email <address>", "With --schedule: sign in as this address; reports go there"],
|
|
112
|
+
["--no-schedule", "Stop scheduled scans"],
|
|
113
|
+
["--status", "Show the schedule and when the next scan runs"],
|
|
114
|
+
["-h, --help", "Show this help"],
|
|
115
|
+
],
|
|
116
|
+
examples: ["failproofai audit", "failproofai audit --schedule 7 --email you@example.com"],
|
|
117
|
+
optionsCol: 20,
|
|
118
|
+
});
|
|
118
119
|
// The margins `printBlock` would add, spelled out because this one returns
|
|
119
120
|
// its text for a caller to write rather than writing it.
|
|
120
121
|
return ["", ...lines, ""].join("\n") + "\n";
|
|
121
122
|
}
|
|
122
123
|
|
|
123
|
-
// ──
|
|
124
|
-
//
|
|
125
|
-
//
|
|
126
|
-
//
|
|
127
|
-
//
|
|
128
|
-
//
|
|
129
|
-
|
|
130
|
-
const DIM = ANSI_DIM;
|
|
131
|
-
const BOLD = ANSI_BOLD;
|
|
132
|
-
const PINK = brandAnsi("pink");
|
|
133
|
-
const GREEN = brandAnsi("guide"); // success — brand teal
|
|
134
|
-
const CYAN = brandAnsi("guide"); // commands & URLs — brand teal
|
|
135
|
-
const SPINNER = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
|
|
136
|
-
|
|
137
|
-
function colorOn(): boolean {
|
|
138
|
-
if (process.env.NO_COLOR && process.env.NO_COLOR !== "") return false;
|
|
139
|
-
if (process.env.FORCE_COLOR === "0") return false;
|
|
140
|
-
if (process.env.FORCE_COLOR) return true;
|
|
141
|
-
return !!process.stdout.isTTY;
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
/** Wrap `s` in an ANSI code when color is enabled, else return it bare. */
|
|
145
|
-
function c(code: string, s: string): string {
|
|
146
|
-
return colorOn() ? `${code}${s}${RESET}` : s;
|
|
147
|
-
}
|
|
124
|
+
// ── Colour ──────────────────────────────────────────────────────────────────
|
|
125
|
+
// Every line below is built by `screenKit()` from the shared palette in
|
|
126
|
+
// hooks/tui.ts, and coloured on exactly the terms every other screen is:
|
|
127
|
+
// `optsFor(stdout)` — a terminal, and no NO_COLOR. `FORCE_COLOR` is not read
|
|
128
|
+
// here, as it is read nowhere else; audit used to be the one screen that
|
|
129
|
+
// honoured it, so `FORCE_COLOR=1 failproofai audit | less -R` was the one
|
|
130
|
+
// place colour reached a pipe.
|
|
148
131
|
|
|
149
132
|
function num(n: number): string {
|
|
150
133
|
return n.toLocaleString("en-US");
|
|
151
134
|
}
|
|
152
135
|
|
|
136
|
+
/** `1 session`, `1,284 sessions`. */
|
|
137
|
+
function counted(n: number, one: string, many = `${one}s`): string {
|
|
138
|
+
return `${num(n)} ${n === 1 ? one : many}`;
|
|
139
|
+
}
|
|
140
|
+
|
|
153
141
|
/**
|
|
154
142
|
* Print an error and exit 1. We exit directly rather than throwing a `CliError`
|
|
155
143
|
* because, in the shipped single-file bundle (`dist/cli.mjs`), the entrypoint's
|
|
@@ -163,145 +151,253 @@ function die(message: string): never {
|
|
|
163
151
|
process.exit(1);
|
|
164
152
|
}
|
|
165
153
|
|
|
166
|
-
// ──
|
|
154
|
+
// ── The audit screen ─────────────────────────────────────────────────────────
|
|
155
|
+
|
|
156
|
+
/** What the audit screen is drawn from: everything `runAudit` has reported so far. */
|
|
157
|
+
export interface AuditScreenState {
|
|
158
|
+
/**
|
|
159
|
+
* The agents with sessions to scan, in scan order, and how far each has got.
|
|
160
|
+
* `null` until every agent's transcripts have been listed.
|
|
161
|
+
*/
|
|
162
|
+
agents: Array<{ cli: string; total: number; done: number }> | null;
|
|
163
|
+
/** How many policies each tool call is replayed against. */
|
|
164
|
+
policies: number;
|
|
165
|
+
/** Hits so far, by policy or detector name. */
|
|
166
|
+
hits: Record<string, number>;
|
|
167
|
+
}
|
|
167
168
|
|
|
168
|
-
interface
|
|
169
|
-
/**
|
|
170
|
-
|
|
171
|
-
/**
|
|
172
|
-
|
|
169
|
+
export interface AuditScreenOpts extends ScreenKitOpts {
|
|
170
|
+
/** The finished scan. With it the screen is the final one: every bar done, the summary in place of the key hint. */
|
|
171
|
+
result?: AuditResult;
|
|
172
|
+
/** The scan stopped without a result: the screen as far as it got, with no key hint. */
|
|
173
|
+
stopped?: boolean;
|
|
173
174
|
}
|
|
174
175
|
|
|
176
|
+
/** The "found" rows, in order. */
|
|
177
|
+
const FOUND_LABELS = ["would block", "would warn", "audit only"] as const;
|
|
178
|
+
|
|
175
179
|
/**
|
|
176
|
-
*
|
|
177
|
-
*
|
|
178
|
-
*
|
|
179
|
-
*
|
|
180
|
-
*
|
|
180
|
+
* Which "found" row a finding counts under, as an index into `FOUND_LABELS`:
|
|
181
|
+
* what the policy would do if it were on.
|
|
182
|
+
*
|
|
183
|
+
* Every `warn-*` builtin instructs, so it would warn. `sanitize-*` denies, but
|
|
184
|
+
* on PostToolUse, after the call has already run: it can tell the agent about a
|
|
185
|
+
* secret and stop nothing, so it would warn too — filing it under "would block"
|
|
186
|
+
* is the over-count #669 took out of the block numbers. Every other builtin
|
|
187
|
+
* (`block-*`, `protect-*`, `prefer-*`) denies before the call, so it would
|
|
188
|
+
* block. Deliberately not `severityForBuiltin`: that is the score's heuristic,
|
|
189
|
+
* and it files `protect-env-vars` and `prefer-package-manager` under warn
|
|
190
|
+
* although both deny. The audit-only detectors have no runtime policy at all,
|
|
191
|
+
* so they get a row of their own rather than a "would".
|
|
181
192
|
*/
|
|
182
|
-
function
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
let done = false;
|
|
187
|
-
let printed = false;
|
|
188
|
-
|
|
189
|
-
const lineFor = (i: number): string => {
|
|
190
|
-
const s = AUDIT_STAGES[i];
|
|
191
|
-
if (done || i < stage) return ` ${c(GREEN, "✓")} ${c(DIM, s.label)}`;
|
|
192
|
-
if (i === stage) {
|
|
193
|
-
return ` ${c(PINK, SPINNER[tick % SPINNER.length])} ${s.label} ${c(DIM, s.detail)}`;
|
|
194
|
-
}
|
|
195
|
-
return ` ${c(DIM, "○")} ${c(DIM, s.label)}`;
|
|
196
|
-
};
|
|
197
|
-
|
|
198
|
-
const render = (): void => {
|
|
199
|
-
const lines = Array.from({ length: n }, (_, i) => lineFor(i));
|
|
200
|
-
// Move the cursor back up over the previously-drawn block, then clear and
|
|
201
|
-
// rewrite each line in place.
|
|
202
|
-
if (printed) process.stdout.write(`\x1b[${n}A`);
|
|
203
|
-
process.stdout.write(lines.map((l) => `\x1b[2K${l}`).join("\n") + "\n");
|
|
204
|
-
printed = true;
|
|
205
|
-
};
|
|
206
|
-
|
|
207
|
-
render();
|
|
208
|
-
const spinTimer = setInterval(() => {
|
|
209
|
-
tick++;
|
|
210
|
-
render();
|
|
211
|
-
}, 90);
|
|
212
|
-
// Advance through stages on a fixed cadence, holding on the last one until
|
|
213
|
-
// finish() flips `done`.
|
|
214
|
-
const stageTimer = setInterval(() => {
|
|
215
|
-
if (stage < n - 1) {
|
|
216
|
-
stage++;
|
|
217
|
-
render();
|
|
218
|
-
}
|
|
219
|
-
}, 1100);
|
|
220
|
-
|
|
221
|
-
const stop = (): void => {
|
|
222
|
-
clearInterval(spinTimer);
|
|
223
|
-
clearInterval(stageTimer);
|
|
224
|
-
};
|
|
225
|
-
|
|
226
|
-
return {
|
|
227
|
-
finish() {
|
|
228
|
-
stop();
|
|
229
|
-
done = true;
|
|
230
|
-
render();
|
|
231
|
-
},
|
|
232
|
-
fail() {
|
|
233
|
-
stop();
|
|
234
|
-
process.stdout.write("\n");
|
|
235
|
-
},
|
|
236
|
-
};
|
|
193
|
+
function foundRow(name: string): number {
|
|
194
|
+
if (getDetectorByName(name)) return 2;
|
|
195
|
+
const short = name.slice(name.indexOf("/") + 1);
|
|
196
|
+
return short.startsWith("warn-") || short.startsWith("sanitize-") ? 1 : 0;
|
|
237
197
|
}
|
|
238
198
|
|
|
239
|
-
/**
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
199
|
+
/**
|
|
200
|
+
* `7: block-env-files 4, block-sudo 2, …` — the total, then the names that
|
|
201
|
+
* make it up, most hits first.
|
|
202
|
+
*
|
|
203
|
+
* A name is whole or absent, never cut: when `room` runs out the list ends on
|
|
204
|
+
* `…`, and the first name is always shown, however narrow the terminal.
|
|
205
|
+
*/
|
|
206
|
+
function foundValue(items: Array<[string, number]>, room: number): string {
|
|
207
|
+
if (items.length === 0) return num(0);
|
|
208
|
+
const total = items.reduce((sum, [, n]) => sum + n, 0);
|
|
209
|
+
items.sort((a, b) => b[1] - a[1] || a[0].localeCompare(b[0]));
|
|
210
|
+
let text = `${num(total)}: `;
|
|
211
|
+
for (let i = 0; i < items.length; i += 1) {
|
|
212
|
+
const piece = `${i > 0 ? ", " : ""}${items[i][0]} ${num(items[i][1])}`;
|
|
213
|
+
// Room for the `, …` this item would have to end on, unless it is the last.
|
|
214
|
+
const tail = i < items.length - 1 ? 3 : 0;
|
|
215
|
+
if (i > 0 && text.length + piece.length + tail > room) return `${text}, …`;
|
|
216
|
+
text += piece;
|
|
253
217
|
}
|
|
218
|
+
return text;
|
|
254
219
|
}
|
|
255
220
|
|
|
256
|
-
|
|
221
|
+
/**
|
|
222
|
+
* The `failproofai audit` screen, as lines. Pure, so it is asserted at any
|
|
223
|
+
* width and colour tier without a terminal.
|
|
224
|
+
*
|
|
225
|
+
* Live, it is the design's progress screen: a bar per agent with sessions to
|
|
226
|
+
* scan, and what the scan has found so far. Given the result it finishes in
|
|
227
|
+
* place — every bar done, the headings in the past tense, and the summary where
|
|
228
|
+
* the key hint was — so the screen that stays in scrollback is the one the scan
|
|
229
|
+
* was drawn on.
|
|
230
|
+
*/
|
|
231
|
+
export function buildAuditScreen(state: AuditScreenState, opts: AuditScreenOpts = {}): string[] {
|
|
232
|
+
const k = screenKit(opts);
|
|
233
|
+
const result = opts.result;
|
|
234
|
+
const out = [k.header("Audit"), ""];
|
|
235
|
+
|
|
236
|
+
if (state.agents === null) {
|
|
237
|
+
// Still listing transcripts: the one phase with no bars to draw, named in
|
|
238
|
+
// the dashboard's own words for it. A finished scan that never said what it
|
|
239
|
+
// found has no rows to draw, and is left to its summary.
|
|
240
|
+
if (!result) out.push(k.head("Scanning history", AUDIT_STAGES[0].label));
|
|
241
|
+
} else if (state.agents.length > 0) {
|
|
242
|
+
const agents = result ? state.agents.map((a) => ({ ...a, done: a.total })) : state.agents;
|
|
243
|
+
out.push(
|
|
244
|
+
k.head(
|
|
245
|
+
result ? "Scanned history" : "Scanning history",
|
|
246
|
+
`${counted(agents.length, "agent")}, ${counted(state.policies, "policy", "policies")}`,
|
|
247
|
+
),
|
|
248
|
+
);
|
|
249
|
+
// The bar gives up width before the counts do: it is decoration, and the
|
|
250
|
+
// counts are the facts. Thirty cells where there is room, ten at the least.
|
|
251
|
+
const nameCol = Math.max(10, ...agents.map((a) => a.cli.length + 2));
|
|
252
|
+
const widest = Math.max(...agents.map((a) => `${num(a.total)} of ${counted(a.total, "session")}`.length));
|
|
253
|
+
const barWidth = Math.max(10, Math.min(30, k.cols - INDENT.length - nameCol - 2 - widest));
|
|
254
|
+
for (const a of agents) {
|
|
255
|
+
const tally =
|
|
256
|
+
a.done >= a.total ? k.ok(counted(a.total, "session")) : `${num(a.done)} of ${counted(a.total, "session")}`;
|
|
257
|
+
out.push(`${INDENT}${a.cli.padEnd(nameCol)}${k.bar(barWidth, a.total > 0 ? a.done / a.total : 1)} ${tally}`);
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
// The result's own counts once there is one — they are what the dashboard
|
|
261
|
+
// shows — and the running tally until then.
|
|
262
|
+
const hits = result ? Object.fromEntries(result.results.map((r) => [r.name, r.hits])) : state.hits;
|
|
263
|
+
const groups: Array<Array<[string, number]>> = FOUND_LABELS.map(() => []);
|
|
264
|
+
for (const [name, n] of Object.entries(hits)) {
|
|
265
|
+
if (n > 0) groups[foundRow(name)].push([name.slice(name.indexOf("/") + 1), n]);
|
|
266
|
+
}
|
|
267
|
+
// The column `kv` starts these values at: its widest label (or nine), plus two.
|
|
268
|
+
const valueCol = INDENT.length + Math.max(9, ...FOUND_LABELS.map((label) => label.length)) + 2;
|
|
269
|
+
const room = opts.fit ? k.cols - valueCol : Number.POSITIVE_INFINITY;
|
|
270
|
+
out.push(
|
|
271
|
+
"",
|
|
272
|
+
k.head(result ? "Found" : "Found so far"),
|
|
273
|
+
...k.kv(FOUND_LABELS.map((label, i): [string, string] => [label, foundValue(groups[i], room)])),
|
|
274
|
+
);
|
|
275
|
+
}
|
|
257
276
|
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
277
|
+
// One blank line before the closing block, never two: with nothing to scan
|
|
278
|
+
// there are no blocks above it, only the header's own gap.
|
|
279
|
+
const closing = result ? buildSummary(result, opts) : opts.stopped ? [] : [k.keys(["ctrl+c stop"])];
|
|
280
|
+
if (out[out.length - 1] !== "") out.push("");
|
|
281
|
+
out.push(...closing);
|
|
282
|
+
while (out[out.length - 1] === "") out.pop();
|
|
283
|
+
return out;
|
|
261
284
|
}
|
|
262
285
|
|
|
263
286
|
/**
|
|
264
|
-
* The
|
|
265
|
-
* it
|
|
287
|
+
* The finished scan in at most two sentences: what was read, then how much of
|
|
288
|
+
* what it found your policies already cover. Pure; the audit screen ends on it.
|
|
289
|
+
*
|
|
290
|
+
* "Covered", not "blocked": a policy that is on may warn rather than block, and
|
|
291
|
+
* what is counted is patterns a policy you have switched on already answers.
|
|
266
292
|
*/
|
|
267
|
-
export function buildSummary(result: AuditResult): string[] {
|
|
293
|
+
export function buildSummary(result: AuditResult, opts: ScreenKitOpts = {}): string[] {
|
|
294
|
+
const k = screenKit(opts);
|
|
268
295
|
const sessions = result.transcripts.scanned;
|
|
269
296
|
const events = result.eventsScanned;
|
|
270
297
|
const projects = result.projectsScanned.length;
|
|
271
|
-
const
|
|
272
|
-
const
|
|
273
|
-
|
|
274
|
-
const lines
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
298
|
+
const covered = result.results.filter((r) => r.source === "builtin" && r.enabledInConfig).length;
|
|
299
|
+
const slipping = result.results.length - covered;
|
|
300
|
+
|
|
301
|
+
const lines = [
|
|
302
|
+
k.ok(
|
|
303
|
+
`Scanned ${counted(events, "tool call")} across ${counted(sessions, "session")}` +
|
|
304
|
+
(projects > 0 ? ` in ${counted(projects, "project")}` : "") +
|
|
305
|
+
".",
|
|
306
|
+
),
|
|
307
|
+
];
|
|
282
308
|
if (result.totals.hits === 0) {
|
|
283
|
-
// Only
|
|
284
|
-
//
|
|
285
|
-
|
|
309
|
+
// Only when something was actually read. With no tool calls at all the
|
|
310
|
+
// caller says what to do instead, and "nothing was flagged" would read as
|
|
311
|
+
// a clean bill of health for a history that was never there.
|
|
312
|
+
if (events > 0) lines.push(`${INDENT}Nothing was flagged.`);
|
|
286
313
|
return lines;
|
|
287
314
|
}
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
`${c(PINK, String(slippingRows.length))} ${slippingRows.length === 1 ? "pattern" : "patterns"} slipping through`,
|
|
315
|
+
const patterns = (n: number): string => `${num(n)} ${n === 1 ? "pattern is" : "patterns are"}`;
|
|
316
|
+
if (slipping > 0 && covered > 0) {
|
|
317
|
+
lines.push(
|
|
318
|
+
`${INDENT}${patterns(slipping)} slipping through, and ${num(covered)} ${covered === 1 ? "is" : "are"} already covered by your policies.`,
|
|
293
319
|
);
|
|
320
|
+
} else if (slipping > 0) {
|
|
321
|
+
lines.push(`${INDENT}${patterns(slipping)} slipping through.`);
|
|
322
|
+
} else if (covered > 0) {
|
|
323
|
+
lines.push(`${INDENT}${patterns(covered)} already covered by your policies.`);
|
|
294
324
|
}
|
|
295
|
-
if (enabledRows.length > 0) {
|
|
296
|
-
parts.push(`${c(GREEN, String(enabledRows.length))} already blocked by your policies`);
|
|
297
|
-
}
|
|
298
|
-
if (parts.length > 0) lines.push(parts.join(` ${c(DIM, "·")} `));
|
|
299
325
|
return lines;
|
|
300
326
|
}
|
|
301
327
|
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
328
|
+
// ── Progress ─────────────────────────────────────────────────────────────────
|
|
329
|
+
|
|
330
|
+
/**
|
|
331
|
+
* Run the audit behind its screen, and return once the finished screen is on
|
|
332
|
+
* the terminal.
|
|
333
|
+
*
|
|
334
|
+
* On a terminal that takes colour the screen is live: it redraws in place as
|
|
335
|
+
* `runAudit` reports, at most every 100 ms and ONE write per frame, through the
|
|
336
|
+
* kit's `live()`. The spinner it replaces wrote its cursor-up and its lines as
|
|
337
|
+
* separate chunks, and advanced on a timer that knew nothing about the scan.
|
|
338
|
+
*
|
|
339
|
+
* Piped, or under NO_COLOR, nothing is drawn while the scan runs: the finished
|
|
340
|
+
* screen prints once, with no escapes and no frames.
|
|
341
|
+
*/
|
|
342
|
+
async function runWithProgress(opts: RunAuditOptions): Promise<AuditResult> {
|
|
343
|
+
const out: TTYOut = process.stdout;
|
|
344
|
+
const render = optsFor(out);
|
|
345
|
+
const animate = !!out.isTTY && render.color;
|
|
346
|
+
// Prose may be shortened to fit a terminal, never a pipe (decision D9).
|
|
347
|
+
const view: AuditScreenOpts = { ...render, fit: !!out.isTTY };
|
|
348
|
+
const state: AuditScreenState = { agents: null, policies: 0, hits: {} };
|
|
349
|
+
const region = animate ? screenKit(view).live(out) : null;
|
|
350
|
+
// The width is read per frame, so a terminal resized mid-scan gets frames
|
|
351
|
+
// that fit it.
|
|
352
|
+
const frame = (extra: AuditScreenOpts = {}): string[] => [
|
|
353
|
+
"",
|
|
354
|
+
...buildAuditScreen(state, { ...view, cols: out.columns || view.cols, ...extra }),
|
|
355
|
+
];
|
|
356
|
+
|
|
357
|
+
region?.draw(frame);
|
|
358
|
+
let result: AuditResult;
|
|
359
|
+
try {
|
|
360
|
+
result = await runAudit({
|
|
361
|
+
...opts,
|
|
362
|
+
onProgress: (progress) => {
|
|
363
|
+
if (progress.kind === "discovered") {
|
|
364
|
+
state.policies = progress.policies;
|
|
365
|
+
state.agents = progress.agents
|
|
366
|
+
.filter((a) => a.transcripts > 0)
|
|
367
|
+
.map((a) => ({ cli: a.cli, total: a.transcripts, done: 0 }));
|
|
368
|
+
} else {
|
|
369
|
+
const agent = state.agents?.find((a) => a.cli === progress.cli);
|
|
370
|
+
if (agent) agent.done += 1;
|
|
371
|
+
for (const [name, n] of Object.entries(progress.hitsByName)) {
|
|
372
|
+
state.hits[name] = (state.hits[name] ?? 0) + n;
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
region?.draw(frame);
|
|
376
|
+
},
|
|
377
|
+
});
|
|
378
|
+
} catch (err) {
|
|
379
|
+
// Left as far as it got, without the key hint: nothing is running now.
|
|
380
|
+
region?.done(frame({ stopped: true }));
|
|
381
|
+
throw err;
|
|
382
|
+
}
|
|
383
|
+
const finished = frame({ result });
|
|
384
|
+
if (region) region.done(finished);
|
|
385
|
+
else out.write(finished.join("\n") + "\n");
|
|
386
|
+
return result;
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
/** A block printed under the screen: a blank line above it and one below. */
|
|
390
|
+
function printAfter(lines: string[]): void {
|
|
391
|
+
process.stdout.write(`\n${lines.join("\n")}\n\n`);
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
/**
|
|
395
|
+
* The closing line when there was nothing to read. What changes that is using
|
|
396
|
+
* the agent: the audit reads each agent's own history, which exists with or
|
|
397
|
+
* without failproofai's hooks.
|
|
398
|
+
*/
|
|
399
|
+
function nothingToScan(k: ReturnType<typeof screenKit>): string {
|
|
400
|
+
return `Run it again after using your agent: ${k.cmd("failproofai audit")}`;
|
|
305
401
|
}
|
|
306
402
|
|
|
307
403
|
// ── Audit telemetry ──────────────────────────────────────────────────────────
|
|
@@ -441,13 +537,15 @@ export async function runScheduledAudit(): Promise<number> {
|
|
|
441
537
|
* `~/.failproofai/audit-dashboard.json` so the dashboard renders instantly, and
|
|
442
538
|
* immediately shows the user what's slipping through.
|
|
443
539
|
*
|
|
444
|
-
* Shows the same
|
|
445
|
-
*
|
|
446
|
-
* never throws, never exits the process; the
|
|
447
|
-
* afterward. Opt out with
|
|
540
|
+
* Shows the same screen as `failproofai audit`. The scan runs to completion;
|
|
541
|
+
* Ctrl+C interrupts it the usual way (the screen gives the cursor back and lets
|
|
542
|
+
* the SIGINT through). Best-effort: never throws, never exits the process; the
|
|
543
|
+
* caller boots the dashboard afterward. Opt out with
|
|
544
|
+
* `FAILPROOFAI_NO_AUTO_AUDIT=1`.
|
|
448
545
|
*/
|
|
449
546
|
export async function runPostSetupAudit(): Promise<void> {
|
|
450
547
|
if (process.env.FAILPROOFAI_NO_AUTO_AUDIT === "1") return;
|
|
548
|
+
const k = screenKit(optsFor(process.stdout));
|
|
451
549
|
|
|
452
550
|
// Take the same cross-process cache lock the scheduled run, `failproofai
|
|
453
551
|
// audit` and the dashboard re-run take. This onboarding scan writes the very
|
|
@@ -459,9 +557,7 @@ export async function runPostSetupAudit(): Promise<void> {
|
|
|
459
557
|
// the telemetry source below and the lock source declared in audit-lock.ts.
|
|
460
558
|
const attempt = acquireAuditLock("onboarding");
|
|
461
559
|
if (!attempt.ok) {
|
|
462
|
-
|
|
463
|
-
`\n ${c(DIM, "an audit is already running — the dashboard will show its result.")}\n\n`,
|
|
464
|
-
);
|
|
560
|
+
printAfter(["Another audit is already running. The dashboard will show its result."]);
|
|
465
561
|
return;
|
|
466
562
|
}
|
|
467
563
|
|
|
@@ -472,10 +568,8 @@ export async function runPostSetupAudit(): Promise<void> {
|
|
|
472
568
|
// that follows is awaited.
|
|
473
569
|
void trackHookEvent(instanceId, "cli_audit_started", { source: "onboarding" });
|
|
474
570
|
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
);
|
|
478
|
-
|
|
571
|
+
// No line of its own before the scan: the screen opens with its own header
|
|
572
|
+
// and closes on its own key hint.
|
|
479
573
|
let result: AuditResult;
|
|
480
574
|
try {
|
|
481
575
|
result = await runWithProgress({});
|
|
@@ -487,9 +581,7 @@ export async function runPostSetupAudit(): Promise<void> {
|
|
|
487
581
|
error_type: err instanceof Error ? err.name : "unknown",
|
|
488
582
|
error_message: sanitizeErrorMessage(err),
|
|
489
583
|
});
|
|
490
|
-
|
|
491
|
-
` ${c(PINK, "!")} ${c(DIM, "audit couldn't finish — run")} ${c(CYAN, "failproofai audit")} ${c(DIM, "later.")}\n\n`,
|
|
492
|
-
);
|
|
584
|
+
printAfter([k.fail("The audit could not finish.", "failproofai audit")]);
|
|
493
585
|
return;
|
|
494
586
|
}
|
|
495
587
|
|
|
@@ -499,23 +591,18 @@ export async function runPostSetupAudit(): Promise<void> {
|
|
|
499
591
|
await trackHookEvent(instanceId, "cli_audit_completed", auditCompletedProps("onboarding", result));
|
|
500
592
|
|
|
501
593
|
if (result.eventsScanned === 0) {
|
|
502
|
-
|
|
503
|
-
`\n ${c(DIM, "no agent sessions to audit yet — come back after using your agent.")}\n\n`,
|
|
504
|
-
);
|
|
594
|
+
printAfter([nothingToScan(k)]);
|
|
505
595
|
return;
|
|
506
596
|
}
|
|
507
597
|
writeDashboardCache({}, result);
|
|
508
|
-
printSummary(result);
|
|
509
|
-
process.stdout.write("\n");
|
|
510
598
|
// The audit says what ALREADY happened; nothing here says how to stop it
|
|
511
599
|
// happening again. This is the first thing a new machine runs, and setup
|
|
512
600
|
// installs no policies by design, so without this the whole first session
|
|
513
601
|
// ends on a count of findings and no way to act on it.
|
|
514
602
|
if (!hasInstalledPacks()) {
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
);
|
|
603
|
+
printAfter([k.caution("None of this is enforced yet.", `failproofai policies add ${CORE_SOURCE}`)]);
|
|
604
|
+
} else {
|
|
605
|
+
process.stdout.write("\n");
|
|
519
606
|
}
|
|
520
607
|
} finally {
|
|
521
608
|
attempt.lock.release();
|
|
@@ -641,9 +728,8 @@ export async function runAuditCli(args: string[]): Promise<void> {
|
|
|
641
728
|
// stall on a flaky network for no reliability gain.
|
|
642
729
|
void trackHookEvent(instanceId, "cli_audit_started", { source: "cli" });
|
|
643
730
|
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
// Full scan: all CLIs, all history, per-transcript cache on.
|
|
731
|
+
// Full scan: all CLIs, all history, per-transcript cache on. The screen opens
|
|
732
|
+
// with its own header, so nothing is printed ahead of it.
|
|
647
733
|
const opts: RunAuditOptions = {};
|
|
648
734
|
|
|
649
735
|
let result: AuditResult;
|
|
@@ -661,32 +747,23 @@ export async function runAuditCli(args: string[]): Promise<void> {
|
|
|
661
747
|
die(`Audit failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
662
748
|
}
|
|
663
749
|
|
|
664
|
-
printSummary(result);
|
|
665
|
-
|
|
666
750
|
// Await before the empty-history branch below, which calls process.exit(0) and
|
|
667
751
|
// would otherwise drop this event. On the dashboard path launch() keeps the
|
|
668
752
|
// process alive, but awaiting makes delivery reliable on every exit path.
|
|
669
753
|
// Bounded (5s) and never throws.
|
|
670
754
|
await trackHookEvent(instanceId, "cli_audit_completed", auditCompletedProps("cli", result));
|
|
671
755
|
|
|
672
|
-
|
|
756
|
+
const k = screenKit(optsFor(process.stdout));
|
|
757
|
+
|
|
758
|
+
// Nothing to read — say what changes that instead of opening an empty dashboard.
|
|
673
759
|
if (result.eventsScanned === 0) {
|
|
674
|
-
|
|
675
|
-
`\n ${c(DIM, "no agent sessions found yet.")}\n` +
|
|
676
|
-
` install hooks with ${c(CYAN, "failproofai policies --install")} ` +
|
|
677
|
-
`${c(DIM, "and come back after using your agent.")}\n\n`,
|
|
678
|
-
);
|
|
760
|
+
printAfter([nothingToScan(k)]);
|
|
679
761
|
process.exit(0);
|
|
680
762
|
}
|
|
681
763
|
|
|
682
764
|
// Pre-warm the dashboard cache — the /audit page reads this file directly, so
|
|
683
765
|
// the page renders our result instantly with no in-browser re-run.
|
|
684
766
|
const persisted = writeDashboardCache(opts, result);
|
|
685
|
-
if (!persisted) {
|
|
686
|
-
process.stdout.write(
|
|
687
|
-
`\n ${c(PINK, "!")} ${c(DIM, "couldn't save the audit cache; the dashboard may show an empty state.")}\n`,
|
|
688
|
-
);
|
|
689
|
-
}
|
|
690
767
|
|
|
691
768
|
// Released here, not in a `finally`: the lock covers the SCAN, and launch()
|
|
692
769
|
// below keeps this process alive for as long as the user leaves the dashboard
|
|
@@ -695,19 +772,22 @@ export async function runAuditCli(args: string[]): Promise<void> {
|
|
|
695
772
|
// empty-history exit) are covered by the handle's own process-exit hook.
|
|
696
773
|
attempt.lock.release();
|
|
697
774
|
|
|
775
|
+
// The hand-off: where the audit is, and how to stop the server that shows it.
|
|
776
|
+
// No logomark here — the design keeps it to bare `failproofai` and `config`.
|
|
698
777
|
const url = `http://localhost:${DASHBOARD_PORT}/audit`;
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
778
|
+
printAfter([
|
|
779
|
+
...(persisted ? [] : [k.caution("Could not save the audit, so the dashboard may show nothing.")]),
|
|
780
|
+
k.ok(`The audit is ready: ${k.cmd(url)}`),
|
|
781
|
+
"",
|
|
782
|
+
k.keys(["ctrl+c stop the dashboard"]),
|
|
783
|
+
]);
|
|
704
784
|
|
|
705
785
|
// Open the page once the server answers (best-effort, detached), then start
|
|
706
786
|
// the server. `launch("start")` blocks-by-keeping-alive — it spawns the
|
|
707
787
|
// bundled standalone dashboard and the process stays up serving it.
|
|
708
788
|
openWhenReady(DASHBOARD_PORT, "/audit");
|
|
709
789
|
const { launch } = await import("../../scripts/launch");
|
|
710
|
-
launch("start");
|
|
790
|
+
launch("start", { screen: false });
|
|
711
791
|
// Intentionally no process.exit(): launch() keeps this process alive running
|
|
712
792
|
// the dashboard until the user stops it.
|
|
713
793
|
}
|