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
|
@@ -0,0 +1,606 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `failproofai config --status`: what this machine is doing, on one screen.
|
|
3
|
+
*
|
|
4
|
+
* THIS MACHINE the daemon and any paused sessions
|
|
5
|
+
* CLOUD where this machine's data goes, and whether it arrives
|
|
6
|
+
* ENFORCEMENT which agents are traced and which policies are on
|
|
7
|
+
*
|
|
8
|
+
* Then at most ONE line that needs attention, the most severe thing wrong with
|
|
9
|
+
* its fix. Everything a reader could need to act on also stays in a row, so a
|
|
10
|
+
* problem that loses the attention line to a worse one is still on screen.
|
|
11
|
+
*
|
|
12
|
+
* Split in two on purpose. `gatherStatusFacts` reads the machine, and is the
|
|
13
|
+
* only part that touches disk, the service manager or a socket.
|
|
14
|
+
* `renderStatusScreen` turns those facts into lines and is pure, so every row
|
|
15
|
+
* and every priority between problems can be pinned without a daemon, a
|
|
16
|
+
* terminal or a clock.
|
|
17
|
+
*
|
|
18
|
+
* The top-level names here are deliberately long. The CLI ships as one
|
|
19
|
+
* scope-hoisted bundle, and a short one (`cmd`, `row`, `state`, …) that matches
|
|
20
|
+
* a local inside a builtin policy or an audit detector makes the bundler rename
|
|
21
|
+
* that local. That changes the source the audit cache keys on, and every user's
|
|
22
|
+
* history is rescanned at upgrade.
|
|
23
|
+
*/
|
|
24
|
+
import { readFileSync } from "node:fs";
|
|
25
|
+
import { resolve } from "node:path";
|
|
26
|
+
import { readCloudCredentials } from "./cloud-enrollment";
|
|
27
|
+
import { describeMachine } from "./cloud-enrollment-cli";
|
|
28
|
+
import { cloudBaseFor } from "./cloud-connection";
|
|
29
|
+
import { readActiveCloudManagedPolicies } from "./cloud-managed-policies";
|
|
30
|
+
import { readIngestCredential } from "./collector-config";
|
|
31
|
+
import { discoverPolicyFiles } from "./custom-hooks-loader";
|
|
32
|
+
import { daemonSocketPresent, isDaemonConfigured } from "./daemon-client";
|
|
33
|
+
import {
|
|
34
|
+
daemonServiceStatus,
|
|
35
|
+
daemonStatusCommand,
|
|
36
|
+
daemonVersionSkew,
|
|
37
|
+
isDaemonSupportedPlatform,
|
|
38
|
+
type DaemonServiceStatus,
|
|
39
|
+
} from "./daemon-service";
|
|
40
|
+
import { deliveryHealth, type DeliveryHealth } from "./delivery-health";
|
|
41
|
+
import { spoolBacklog } from "./flush-cli";
|
|
42
|
+
import { isAgentTraced, readConfig, readCredentials, readVersionFile } from "./fp-config";
|
|
43
|
+
import { collectorHealthFile, customPoliciesDir } from "./fp-home";
|
|
44
|
+
import { configuredCustomPolicyPaths, findProjectConfigDir, readMergedHooksConfig } from "./hooks-config";
|
|
45
|
+
import { detectInstalledClis, listInstallableIds } from "./integrations";
|
|
46
|
+
import { notEnforcingReason, type NotEnforcingReason } from "./manager";
|
|
47
|
+
import { hasInstalledRegexPacks, readInstalledPacks } from "./pack-manifest";
|
|
48
|
+
import { CORE_SOURCE } from "./pack-store";
|
|
49
|
+
import { formatDuration, listActivePauses } from "./session-pause";
|
|
50
|
+
import { pauseClockTime, type PauseClock } from "./session-pause-cli";
|
|
51
|
+
import { optsFor, screenKit, type ScreenKitOpts, type TTYOut } from "./tui";
|
|
52
|
+
import type { IntegrationType } from "./types";
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* A health record older than this was left by a daemon that stopped writing it.
|
|
56
|
+
* The daemon rewrites it every 30 s and deletes it on a clean shutdown, so four
|
|
57
|
+
* missed beats is a daemon that died or hung, not one between writes.
|
|
58
|
+
*/
|
|
59
|
+
export const STATUS_HEALTH_STALE_MS = 2 * 60_000;
|
|
60
|
+
/**
|
|
61
|
+
* A queued batch this old, with nothing delivered for as long, is stuck. The
|
|
62
|
+
* collector sweeps a batch once it is two minutes old, every minute, so a
|
|
63
|
+
* quarter of an hour is several passes that delivered nothing.
|
|
64
|
+
*/
|
|
65
|
+
export const STATUS_QUEUE_STUCK_MS = 15 * 60_000;
|
|
66
|
+
|
|
67
|
+
/** What `collector-health.json` says, in the units this side uses. */
|
|
68
|
+
export interface StatusCollectorHealth {
|
|
69
|
+
/** When the daemon last rewrote the record, epoch ms. */
|
|
70
|
+
writtenAt: number;
|
|
71
|
+
/** The newest event any source produced since the daemon started, epoch ms. */
|
|
72
|
+
lastEventAt?: number;
|
|
73
|
+
/** The last upload the cloud accepted since the daemon started, epoch ms. */
|
|
74
|
+
lastOkAt?: number;
|
|
75
|
+
/** False when the record has no delivery block: the daemon holds no key. */
|
|
76
|
+
delivering: boolean;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export type StatusCloud =
|
|
80
|
+
| { kind: "none" }
|
|
81
|
+
/** FAILPROOFAI_CLOUD_URL is set; it wins over the credential file in the daemon. */
|
|
82
|
+
| { kind: "env"; url: string }
|
|
83
|
+
| {
|
|
84
|
+
kind: "file";
|
|
85
|
+
host: string;
|
|
86
|
+
/** The org as the server named it at connect time, when it did. */
|
|
87
|
+
org?: string;
|
|
88
|
+
machine?: string;
|
|
89
|
+
pullsPolicies: boolean;
|
|
90
|
+
sendsEvents: boolean;
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
/** Where the policies on this machine come from, counted. */
|
|
94
|
+
export interface StatusPolicies {
|
|
95
|
+
/** Policies switched on in enforcing packs. */
|
|
96
|
+
packOn: number;
|
|
97
|
+
/** `id@version` of the first enforcing pack with a policy on. */
|
|
98
|
+
firstPack?: string;
|
|
99
|
+
/** Other enforcing packs with a policy on. */
|
|
100
|
+
morePacks: number;
|
|
101
|
+
/** Enforcing cloud-managed policies. */
|
|
102
|
+
cloudOn: number;
|
|
103
|
+
/** Policies on in observe mode, packs and cloud alike: they log, never decide. */
|
|
104
|
+
observing: number;
|
|
105
|
+
/** Configured custom policy files plus convention files on disk. */
|
|
106
|
+
customFiles: number;
|
|
107
|
+
/** Builtins still run by the pre-pack migration shim. */
|
|
108
|
+
legacy: number;
|
|
109
|
+
/**
|
|
110
|
+
* Enforcing packs that will not load. They fail CLOSED — every tool call
|
|
111
|
+
* their policies cover is denied — so they are counted apart from "on", and
|
|
112
|
+
* a machine holding one is never called "not enforcing".
|
|
113
|
+
*/
|
|
114
|
+
refusedPacks: number;
|
|
115
|
+
packsInstalled: number;
|
|
116
|
+
/** `id@version` of the first installed pack, on or not. */
|
|
117
|
+
anyPack?: string;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
export interface StatusFacts {
|
|
121
|
+
now: number;
|
|
122
|
+
daemon: {
|
|
123
|
+
supported: boolean;
|
|
124
|
+
platform: string;
|
|
125
|
+
service: DaemonServiceStatus;
|
|
126
|
+
/** A daemon socket exists, so something answers hooks whatever the service manager says. */
|
|
127
|
+
answering: boolean;
|
|
128
|
+
/** `failproofai config` set this machine up to evaluate through the daemon only. */
|
|
129
|
+
configured: boolean;
|
|
130
|
+
/** The daemon version recorded at install. */
|
|
131
|
+
version?: string;
|
|
132
|
+
skew: { installed: string; expected: string } | null;
|
|
133
|
+
checkCommand: string | null;
|
|
134
|
+
};
|
|
135
|
+
pauses: Array<{ sessionId: string; expiresAt: number; cwd?: string }>;
|
|
136
|
+
cloud: StatusCloud;
|
|
137
|
+
/** Absent when nothing is connected. */
|
|
138
|
+
delivery?: {
|
|
139
|
+
health: StatusCollectorHealth | null;
|
|
140
|
+
refused: DeliveryHealth;
|
|
141
|
+
queued: number;
|
|
142
|
+
oldestQueuedMs?: number;
|
|
143
|
+
};
|
|
144
|
+
/** notTraced: found on this machine and left out of the selection. */
|
|
145
|
+
agents: { traced: number; notTraced?: number; notInstalled: number };
|
|
146
|
+
policies: StatusPolicies;
|
|
147
|
+
notEnforcing: NotEnforcingReason | null;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/** Seams for everything that would otherwise reach a service manager, PATH or the network. */
|
|
151
|
+
export interface StatusDeps {
|
|
152
|
+
now?: number;
|
|
153
|
+
cwd?: string;
|
|
154
|
+
daemonStatus?: () => DaemonServiceStatus;
|
|
155
|
+
daemonAnswering?: () => boolean;
|
|
156
|
+
detectInstalled?: () => IntegrationType[];
|
|
157
|
+
notEnforcing?: (cwd?: string) => NotEnforcingReason | null;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* `collector-health.json`, or null when there is none or it cannot be read.
|
|
162
|
+
*
|
|
163
|
+
* The daemon writes it (`crates/fpai-collect/src/health.rs`) and, until this,
|
|
164
|
+
* nothing on this side read it. Timestamps there are Unix SECONDS, and zero
|
|
165
|
+
* means "none since the daemon started", so zero is read as absent rather than
|
|
166
|
+
* as 1970. Never throws: a status command that crashes on a half-written file
|
|
167
|
+
* describes nothing.
|
|
168
|
+
*/
|
|
169
|
+
export function readCollectorHealth(file: string = collectorHealthFile()): StatusCollectorHealth | null {
|
|
170
|
+
let parsed: unknown;
|
|
171
|
+
try {
|
|
172
|
+
parsed = JSON.parse(readFileSync(file, "utf8"));
|
|
173
|
+
} catch {
|
|
174
|
+
return null;
|
|
175
|
+
}
|
|
176
|
+
if (!parsed || typeof parsed !== "object") return null;
|
|
177
|
+
const record = parsed as Record<string, unknown>;
|
|
178
|
+
const ms = (v: unknown): number | undefined =>
|
|
179
|
+
typeof v === "number" && Number.isFinite(v) && v > 0 ? v * 1000 : undefined;
|
|
180
|
+
const writtenAt = ms(record.ts);
|
|
181
|
+
if (writtenAt === undefined) return null;
|
|
182
|
+
|
|
183
|
+
let lastEventAt: number | undefined;
|
|
184
|
+
if (record.sources && typeof record.sources === "object") {
|
|
185
|
+
for (const source of Object.values(record.sources as Record<string, unknown>)) {
|
|
186
|
+
const at = source && typeof source === "object" ? ms((source as Record<string, unknown>).last_event_ts) : undefined;
|
|
187
|
+
if (at !== undefined && (lastEventAt === undefined || at > lastEventAt)) lastEventAt = at;
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
const delivery =
|
|
191
|
+
record.delivery && typeof record.delivery === "object" ? (record.delivery as Record<string, unknown>) : undefined;
|
|
192
|
+
const lastOkAt = delivery ? ms(delivery.last_ok_ts) : undefined;
|
|
193
|
+
return {
|
|
194
|
+
writtenAt,
|
|
195
|
+
delivering: delivery !== undefined,
|
|
196
|
+
...(lastEventAt !== undefined ? { lastEventAt } : {}),
|
|
197
|
+
...(lastOkAt !== undefined ? { lastOkAt } : {}),
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* Count where this machine's policies come from.
|
|
203
|
+
*
|
|
204
|
+
* The same sources, read the same way, as `notEnforcingReason` — installed
|
|
205
|
+
* packs minus what was switched off, cloud-managed policies, custom and
|
|
206
|
+
* convention files (counted, never imported), and the legacy shim — so the
|
|
207
|
+
* policies row and the "not enforcing" line can never disagree.
|
|
208
|
+
*/
|
|
209
|
+
export function summarizePolicySources(cwd?: string): StatusPolicies {
|
|
210
|
+
const summary: StatusPolicies = {
|
|
211
|
+
packOn: 0,
|
|
212
|
+
morePacks: 0,
|
|
213
|
+
cloudOn: 0,
|
|
214
|
+
observing: 0,
|
|
215
|
+
customFiles: 0,
|
|
216
|
+
legacy: 0,
|
|
217
|
+
refusedPacks: 0,
|
|
218
|
+
packsInstalled: 0,
|
|
219
|
+
};
|
|
220
|
+
let config: ReturnType<typeof readMergedHooksConfig>;
|
|
221
|
+
try {
|
|
222
|
+
config = readMergedHooksConfig(cwd);
|
|
223
|
+
} catch {
|
|
224
|
+
config = { enabledPolicies: [] };
|
|
225
|
+
}
|
|
226
|
+
const disabled = new Set(config.disabledCustomPolicies ?? []);
|
|
227
|
+
|
|
228
|
+
try {
|
|
229
|
+
const { packs, errors } = readInstalledPacks();
|
|
230
|
+
// The refusals that deny, as `failproofai policies` counts them: an observe
|
|
231
|
+
// pack or a Jev-checks-only one has nothing to fail closed on.
|
|
232
|
+
summary.refusedPacks = errors.filter((err) => err.effect !== "observe" && !err.semanticOnly).length;
|
|
233
|
+
summary.packsInstalled = packs.length;
|
|
234
|
+
if (packs[0]) summary.anyPack = `${packs[0].id}@${packs[0].version}`;
|
|
235
|
+
let enforcing = 0;
|
|
236
|
+
for (const pack of packs) {
|
|
237
|
+
const taken = pack.enabled ?? pack.policies.map((policy) => policy.name);
|
|
238
|
+
const on = taken.filter((name) => !disabled.has(`pack:${pack.id}@${pack.version}:${name}`)).length;
|
|
239
|
+
if (on === 0) continue;
|
|
240
|
+
if (pack.effect === "observe") {
|
|
241
|
+
summary.observing += on;
|
|
242
|
+
continue;
|
|
243
|
+
}
|
|
244
|
+
if (enforcing === 0) summary.firstPack = `${pack.id}@${pack.version}`;
|
|
245
|
+
summary.packOn += on;
|
|
246
|
+
enforcing++;
|
|
247
|
+
}
|
|
248
|
+
summary.morePacks = Math.max(0, enforcing - 1);
|
|
249
|
+
} catch {
|
|
250
|
+
// An unreadable manifest is reported by `failproofai policies`.
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
try {
|
|
254
|
+
for (const artifact of readActiveCloudManagedPolicies()) {
|
|
255
|
+
if (artifact.effect === "observe") summary.observing++;
|
|
256
|
+
else summary.cloudOn++;
|
|
257
|
+
}
|
|
258
|
+
} catch {
|
|
259
|
+
// No deployment, or an unreadable one: nothing comes from there.
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
// Deduplicated by path: run from the home directory, the project's convention
|
|
263
|
+
// folder IS the global one.
|
|
264
|
+
const projectDir = resolve(findProjectConfigDir(cwd ?? process.cwd()), ".failproofai", "policies");
|
|
265
|
+
const conventionFiles = new Set([...discoverPolicyFiles(projectDir), ...discoverPolicyFiles(customPoliciesDir())]);
|
|
266
|
+
summary.customFiles = configuredCustomPolicyPaths(config).length + conventionFiles.size;
|
|
267
|
+
|
|
268
|
+
if (config.enabledPolicies.length > 0 && !hasInstalledRegexPacks()) summary.legacy = config.enabledPolicies.length;
|
|
269
|
+
return summary;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
/** Read everything the status screen shows. The only part of it that touches the machine. */
|
|
273
|
+
export async function gatherStatusFacts(deps: StatusDeps = {}): Promise<StatusFacts> {
|
|
274
|
+
const now = deps.now ?? Date.now();
|
|
275
|
+
const cwd = deps.cwd ?? process.cwd();
|
|
276
|
+
|
|
277
|
+
const daemon: StatusFacts["daemon"] = {
|
|
278
|
+
supported: isDaemonSupportedPlatform(),
|
|
279
|
+
platform: process.platform,
|
|
280
|
+
service: (deps.daemonStatus ?? daemonServiceStatus)(),
|
|
281
|
+
answering: (deps.daemonAnswering ?? daemonSocketPresent)(),
|
|
282
|
+
configured: isDaemonConfigured(),
|
|
283
|
+
version: readVersionFile()?.daemon,
|
|
284
|
+
skew: daemonVersionSkew(),
|
|
285
|
+
checkCommand: daemonStatusCommand(),
|
|
286
|
+
};
|
|
287
|
+
|
|
288
|
+
let cloud: StatusCloud = { kind: "none" };
|
|
289
|
+
const envUrl = process.env.FAILPROOFAI_CLOUD_URL;
|
|
290
|
+
if (envUrl) {
|
|
291
|
+
// The daemon takes the environment over the file, so the file's org and
|
|
292
|
+
// machine may describe a connection that is not the one in effect.
|
|
293
|
+
cloud = { kind: "env", url: envUrl };
|
|
294
|
+
} else {
|
|
295
|
+
const creds = readCloudCredentials();
|
|
296
|
+
const ingest = readIngestCredential();
|
|
297
|
+
if (creds || ingest) {
|
|
298
|
+
const org = readCredentials().org;
|
|
299
|
+
const orgName = org
|
|
300
|
+
? org.name && org.slug && org.name !== org.slug
|
|
301
|
+
? `${org.name} (${org.slug})`
|
|
302
|
+
: (org.name ?? org.slug ?? org.id)
|
|
303
|
+
: undefined;
|
|
304
|
+
const base = creds?.url ?? cloudBaseFor(ingest!.url);
|
|
305
|
+
let host = base;
|
|
306
|
+
try {
|
|
307
|
+
host = new URL(base).host || base;
|
|
308
|
+
} catch {
|
|
309
|
+
// Not a URL we can parse: show it as written.
|
|
310
|
+
}
|
|
311
|
+
const machineId = creds?.machineId ?? readConfig().collector.machineId;
|
|
312
|
+
cloud = {
|
|
313
|
+
kind: "file",
|
|
314
|
+
host,
|
|
315
|
+
...(orgName ? { org: orgName } : {}),
|
|
316
|
+
...(machineId ? { machine: describeMachine(machineId, creds?.machineLabel) } : {}),
|
|
317
|
+
pullsPolicies: Boolean(creds),
|
|
318
|
+
sendsEvents: Boolean(ingest),
|
|
319
|
+
};
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
let delivery: StatusFacts["delivery"];
|
|
324
|
+
if (cloud.kind !== "none") {
|
|
325
|
+
const backlog = spoolBacklog(undefined, now);
|
|
326
|
+
delivery = {
|
|
327
|
+
health: readCollectorHealth(),
|
|
328
|
+
refused: deliveryHealth(undefined, now),
|
|
329
|
+
queued: backlog.count,
|
|
330
|
+
...(backlog.oldestAgeMs !== undefined ? { oldestQueuedMs: backlog.oldestAgeMs } : {}),
|
|
331
|
+
};
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
const config = readConfig();
|
|
335
|
+
const known = listInstallableIds();
|
|
336
|
+
let detected: Set<string>;
|
|
337
|
+
try {
|
|
338
|
+
detected = new Set((deps.detectInstalled ?? detectInstalledClis)());
|
|
339
|
+
} catch {
|
|
340
|
+
detected = new Set();
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
let notEnforcing: NotEnforcingReason | null = null;
|
|
344
|
+
try {
|
|
345
|
+
notEnforcing = (deps.notEnforcing ?? notEnforcingReason)(cwd);
|
|
346
|
+
} catch {
|
|
347
|
+
notEnforcing = null;
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
return {
|
|
351
|
+
now,
|
|
352
|
+
daemon,
|
|
353
|
+
pauses: listActivePauses(now).map((pause) => ({
|
|
354
|
+
sessionId: pause.sessionId,
|
|
355
|
+
expiresAt: pause.expiresAt,
|
|
356
|
+
...(pause.cwd ? { cwd: pause.cwd } : {}),
|
|
357
|
+
})),
|
|
358
|
+
cloud,
|
|
359
|
+
...(delivery ? { delivery } : {}),
|
|
360
|
+
agents: {
|
|
361
|
+
traced: known.filter((id) => isAgentTraced(id, config)).length,
|
|
362
|
+
notTraced: known.filter((id) => detected.has(id) && !isAgentTraced(id, config)).length,
|
|
363
|
+
notInstalled: known.filter((id) => !detected.has(id)).length,
|
|
364
|
+
},
|
|
365
|
+
policies: summarizePolicySources(cwd),
|
|
366
|
+
notEnforcing,
|
|
367
|
+
};
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
/** The one line that needs attention, before it is painted. */
|
|
371
|
+
export interface StatusAttention {
|
|
372
|
+
severity: "fail" | "caution";
|
|
373
|
+
text: string;
|
|
374
|
+
fix?: string;
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
/**
|
|
378
|
+
* The most severe thing wrong, or null.
|
|
379
|
+
*
|
|
380
|
+
* Ordered by what it costs the user: tool calls being denied (all of them, then
|
|
381
|
+
* the ones a refused pack covers), then data refused outright, then enforcement
|
|
382
|
+
* that is not happening (the whole machine, then one Hermes profile), then the
|
|
383
|
+
* cloud connection standing idle, then refusals that lose some batches, then a
|
|
384
|
+
* daemon older or newer than this CLI.
|
|
385
|
+
*/
|
|
386
|
+
export function pickStatusAttention(facts: StatusFacts): StatusAttention | null {
|
|
387
|
+
const { daemon, cloud, delivery, policies } = facts;
|
|
388
|
+
if (daemon.configured && !daemon.answering) {
|
|
389
|
+
return { severity: "fail", text: "failproofaid is not answering, so every tool call is denied.", fix: "failproofai config" };
|
|
390
|
+
}
|
|
391
|
+
if (policies.refusedPacks > 0) {
|
|
392
|
+
return {
|
|
393
|
+
severity: "fail",
|
|
394
|
+
text:
|
|
395
|
+
policies.refusedPacks === 1
|
|
396
|
+
? "A pack will not load, so the tool calls it covers are denied."
|
|
397
|
+
: `${policies.refusedPacks} packs will not load, so the tool calls they cover are denied.`,
|
|
398
|
+
fix: "failproofai policies",
|
|
399
|
+
};
|
|
400
|
+
}
|
|
401
|
+
if (delivery && delivery.refused.credentialRejected > 0) {
|
|
402
|
+
return {
|
|
403
|
+
severity: "fail",
|
|
404
|
+
text: "The cloud is refusing this machine's key, so nothing reaches the dashboard.",
|
|
405
|
+
fix: "failproofai config",
|
|
406
|
+
};
|
|
407
|
+
}
|
|
408
|
+
if (facts.notEnforcing) {
|
|
409
|
+
const fix =
|
|
410
|
+
facts.notEnforcing === "no-hooks"
|
|
411
|
+
? "failproofai config"
|
|
412
|
+
: facts.notEnforcing === "observe-only"
|
|
413
|
+
? "failproofai policies"
|
|
414
|
+
: policies.packsInstalled > 0
|
|
415
|
+
? "failproofai policies add"
|
|
416
|
+
: `failproofai policies add ${CORE_SOURCE}`;
|
|
417
|
+
return { severity: "caution", text: "Policies are not enforcing yet.", fix };
|
|
418
|
+
}
|
|
419
|
+
if (cloud.kind !== "none" && !daemon.answering && daemon.service !== "running") {
|
|
420
|
+
const idle =
|
|
421
|
+
cloud.kind === "file" && !cloud.sendsEvents
|
|
422
|
+
? "no cloud policy is pulled"
|
|
423
|
+
: cloud.kind === "file" && !cloud.pullsPolicies
|
|
424
|
+
? "nothing is delivered"
|
|
425
|
+
: "nothing is pulled or delivered";
|
|
426
|
+
if (!daemon.supported) return { severity: "caution", text: `failproofaid does not run on this platform, so ${idle}.` };
|
|
427
|
+
if (daemon.service === "unknown") {
|
|
428
|
+
return {
|
|
429
|
+
severity: "caution",
|
|
430
|
+
text: `No daemon is answering, so ${idle}.`,
|
|
431
|
+
...(daemon.checkCommand ? { fix: daemon.checkCommand } : {}),
|
|
432
|
+
};
|
|
433
|
+
}
|
|
434
|
+
return {
|
|
435
|
+
severity: "caution",
|
|
436
|
+
text: `failproofaid is ${daemon.service === "not-installed" ? "not installed" : "not running"}, so ${idle}.`,
|
|
437
|
+
fix: "failproofai config",
|
|
438
|
+
};
|
|
439
|
+
}
|
|
440
|
+
if (delivery && delivery.refused.rejected > 0) {
|
|
441
|
+
const n = delivery.refused.rejected;
|
|
442
|
+
const codes = Object.keys(delivery.refused.byStatus).map(Number).sort((a, b) => a - b).join("/");
|
|
443
|
+
return {
|
|
444
|
+
severity: "caution",
|
|
445
|
+
text: `The cloud refused ${n} batch${n === 1 ? "" : "es"} (${codes}); they are not retried.`,
|
|
446
|
+
};
|
|
447
|
+
}
|
|
448
|
+
if (daemon.skew) {
|
|
449
|
+
return {
|
|
450
|
+
severity: "caution",
|
|
451
|
+
text: `failproofaid ${daemon.skew.installed} does not match this CLI (${daemon.skew.expected}).`,
|
|
452
|
+
fix: "failproofai update",
|
|
453
|
+
};
|
|
454
|
+
}
|
|
455
|
+
return null;
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
/**
|
|
459
|
+
* The screen, as lines without outer margins (`printBlock` adds those).
|
|
460
|
+
*
|
|
461
|
+
* Every kv row of every section goes through ONE `kv` call, so the three
|
|
462
|
+
* sections share a value column derived from the widest label rather than each
|
|
463
|
+
* computing its own. Values are never cut: they are ids, URLs and counts.
|
|
464
|
+
*/
|
|
465
|
+
export function renderStatusScreen(facts: StatusFacts, opts: ScreenKitOpts & { clock?: PauseClock } = {}): string[] {
|
|
466
|
+
const kit = screenKit(opts);
|
|
467
|
+
const { now, daemon, cloud, delivery, policies, agents } = facts;
|
|
468
|
+
const ago = (at: number) => `${formatDuration(Math.max(0, now - at))} ago`;
|
|
469
|
+
const many = (n: number, one: string, more = `${one}s`) => `${n} ${n === 1 ? one : more}`;
|
|
470
|
+
|
|
471
|
+
const daemonValue = (): string => {
|
|
472
|
+
if (!daemon.supported) return `${kit.off} Not supported on ${daemon.platform}`;
|
|
473
|
+
if (daemon.service === "running") {
|
|
474
|
+
// The mismatch stays on the row, not only in the attention line: a worse
|
|
475
|
+
// problem can take that line, and this one still needs `failproofai update`.
|
|
476
|
+
const skew = daemon.skew ? `, this CLI is ${daemon.skew.expected}` : "";
|
|
477
|
+
return `${kit.on} Running failproofaid${daemon.version ? ` ${daemon.version}` : ""}${skew}`;
|
|
478
|
+
}
|
|
479
|
+
if (daemon.service === "unknown") {
|
|
480
|
+
return daemon.answering
|
|
481
|
+
? `${kit.on} Answering, its service state needs root to read`
|
|
482
|
+
: `${kit.off} Not answering, its service state needs root to read`;
|
|
483
|
+
}
|
|
484
|
+
if (daemon.answering) return `${kit.on} Running outside the service manager`;
|
|
485
|
+
if (daemon.service === "stopped") return `${kit.off} Installed, not running`;
|
|
486
|
+
if (daemon.service === "condition-failed") return `${kit.off} Installed, will not start: its binary or worker is missing`;
|
|
487
|
+
return `${kit.off} Not installed`;
|
|
488
|
+
};
|
|
489
|
+
|
|
490
|
+
const deliveryValue = (): string => {
|
|
491
|
+
if (!delivery) return `${kit.off} Not sending`;
|
|
492
|
+
if (cloud.kind === "file" && !cloud.sendsEvents) return `${kit.off} Not sending, connected for policy only`;
|
|
493
|
+
const { refused, health, queued, oldestQueuedMs } = delivery;
|
|
494
|
+
if (refused.rejected > 0) {
|
|
495
|
+
const codes = Object.keys(refused.byStatus).map(Number).sort((a, b) => a - b).join("/");
|
|
496
|
+
const oldest = refused.oldestAgeMs !== undefined ? `, oldest ${formatDuration(refused.oldestAgeMs)}` : "";
|
|
497
|
+
return kit.caution(`${many(refused.rejected, "batch", "batches")} refused (${codes})${oldest}`);
|
|
498
|
+
}
|
|
499
|
+
if (!health) return `${kit.off} No report from failproofaid yet`;
|
|
500
|
+
if (now - health.writtenAt > STATUS_HEALTH_STALE_MS) {
|
|
501
|
+
return kit.caution(`No report from failproofaid for ${formatDuration(now - health.writtenAt)}`);
|
|
502
|
+
}
|
|
503
|
+
if (!health.delivering) return `${kit.off} Not delivering, failproofaid has no key`;
|
|
504
|
+
const backlog = queued === 0 ? "nothing queued" : `${many(queued, "batch", "batches")} queued`;
|
|
505
|
+
const stuck =
|
|
506
|
+
queued > 0 &&
|
|
507
|
+
(oldestQueuedMs ?? 0) > STATUS_QUEUE_STUCK_MS &&
|
|
508
|
+
(health.lastOkAt === undefined || now - health.lastOkAt > STATUS_QUEUE_STUCK_MS);
|
|
509
|
+
if (stuck) {
|
|
510
|
+
const last = health.lastOkAt !== undefined ? `last delivered ${ago(health.lastOkAt)}` : "nothing delivered since failproofaid started";
|
|
511
|
+
return kit.caution(`${many(queued, "batch", "batches")} queued, oldest ${formatDuration(oldestQueuedMs ?? 0)}, ${last}`);
|
|
512
|
+
}
|
|
513
|
+
const lastEvent = health.lastEventAt !== undefined ? `last event ${ago(health.lastEventAt)}` : "no events since failproofaid started";
|
|
514
|
+
return `${kit.on} Healthy, ${lastEvent}, ${backlog}`;
|
|
515
|
+
};
|
|
516
|
+
|
|
517
|
+
const policiesValue = (): string => {
|
|
518
|
+
const clauses: string[] = [];
|
|
519
|
+
if (policies.packOn > 0) {
|
|
520
|
+
const more = policies.morePacks > 0 ? ` and ${many(policies.morePacks, "more pack")}` : "";
|
|
521
|
+
clauses.push(`${policies.packOn} on from ${policies.firstPack}${more}`);
|
|
522
|
+
}
|
|
523
|
+
if (policies.cloudOn > 0) clauses.push(`${policies.cloudOn} ${clauses.length > 0 ? "" : "on "}from cloud`);
|
|
524
|
+
if (policies.legacy > 0) clauses.push(`${policies.legacy} ${clauses.length > 0 ? "" : "on from "}legacy builtins`);
|
|
525
|
+
if (policies.customFiles > 0) {
|
|
526
|
+
clauses.push(`${clauses.length > 0 ? "custom" : "Custom"} policies from ${many(policies.customFiles, "file")}`);
|
|
527
|
+
}
|
|
528
|
+
if (policies.observing > 0) clauses.push(`${policies.observing} observing`);
|
|
529
|
+
if (policies.refusedPacks > 0) clauses.push(`${many(policies.refusedPacks, "pack")} will not load`);
|
|
530
|
+
if (clauses.length > 0) return clauses.join(", ");
|
|
531
|
+
return policies.anyPack ? `0 on from ${policies.anyPack}` : "None installed";
|
|
532
|
+
};
|
|
533
|
+
|
|
534
|
+
const machineRows: Array<[string, string]> = [
|
|
535
|
+
["daemon", daemonValue()],
|
|
536
|
+
["paused", facts.pauses.length === 0 ? "No" : many(facts.pauses.length, "session")],
|
|
537
|
+
// One row per pause, under the value column: the id is what
|
|
538
|
+
// `--resume --session <id>` needs, so it is never folded into a count.
|
|
539
|
+
...facts.pauses.map((pause): [string, string] => [
|
|
540
|
+
"",
|
|
541
|
+
`${pause.sessionId} ${formatDuration(Math.max(0, pause.expiresAt - now))} left, until ` +
|
|
542
|
+
`${pauseClockTime(pause.expiresAt, opts.clock)}${pause.cwd ? `${kit.sep}${pause.cwd}` : ""}`,
|
|
543
|
+
]),
|
|
544
|
+
// An enforcement fact, not fine print: a pause suspends local policy only.
|
|
545
|
+
...(facts.pauses.length > 0 ? [["", "Cloud-managed policies keep enforcing."] as [string, string]] : []),
|
|
546
|
+
];
|
|
547
|
+
|
|
548
|
+
const cloudRows: Array<[string, string]> =
|
|
549
|
+
cloud.kind === "none"
|
|
550
|
+
? [["account", `Not connected${kit.sep}${kit.cmd("failproofai config")}`]]
|
|
551
|
+
: cloud.kind === "env"
|
|
552
|
+
? [
|
|
553
|
+
["account", `Set by FAILPROOFAI_CLOUD_URL to ${cloud.url}`],
|
|
554
|
+
["delivery", deliveryValue()],
|
|
555
|
+
]
|
|
556
|
+
: [
|
|
557
|
+
[
|
|
558
|
+
"account",
|
|
559
|
+
`${cloud.org ? `${cloud.org} on ${cloud.host}` : `Connected to ${cloud.host}`}` +
|
|
560
|
+
`${cloud.pullsPolicies ? "" : ", for reporting only"}`,
|
|
561
|
+
],
|
|
562
|
+
...(cloud.machine ? [["machine", cloud.machine] as [string, string]] : []),
|
|
563
|
+
["delivery", deliveryValue()],
|
|
564
|
+
];
|
|
565
|
+
|
|
566
|
+
const enforcementRows: Array<[string, string]> = [
|
|
567
|
+
[
|
|
568
|
+
"agents",
|
|
569
|
+
`${agents.traced} traced` +
|
|
570
|
+
(agents.notTraced ? `, ${agents.notTraced} not traced` : "") +
|
|
571
|
+
(agents.notInstalled > 0 ? `, ${agents.notInstalled} not installed` : ""),
|
|
572
|
+
],
|
|
573
|
+
["policies", policiesValue()],
|
|
574
|
+
];
|
|
575
|
+
|
|
576
|
+
const sections: Array<[string, Array<[string, string]>]> = [
|
|
577
|
+
["This machine", machineRows],
|
|
578
|
+
["Cloud", cloudRows],
|
|
579
|
+
["Enforcement", enforcementRows],
|
|
580
|
+
];
|
|
581
|
+
const aligned = kit.kv(sections.flatMap(([, items]) => items));
|
|
582
|
+
const screen = [kit.header("Status")];
|
|
583
|
+
let taken = 0;
|
|
584
|
+
for (const [heading, items] of sections) {
|
|
585
|
+
screen.push("", kit.head(heading), ...aligned.slice(taken, taken + items.length));
|
|
586
|
+
taken += items.length;
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
const attention = pickStatusAttention(facts);
|
|
590
|
+
if (attention) {
|
|
591
|
+
screen.push("", attention.severity === "fail" ? kit.fail(attention.text, attention.fix) : kit.caution(attention.text, attention.fix));
|
|
592
|
+
}
|
|
593
|
+
if (facts.pauses.length > 0) {
|
|
594
|
+
const id = facts.pauses.length === 1 ? facts.pauses[0].sessionId : "<id>";
|
|
595
|
+
screen.push("", `Resume early: ${kit.cmd(`failproofai config --resume --session ${id}`)}`);
|
|
596
|
+
}
|
|
597
|
+
return screen;
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
/** `config --status`: read the machine, draw it for `stdout`. */
|
|
601
|
+
export async function runStatusCommand(
|
|
602
|
+
opts: { cwd?: string; stdout?: TTYOut; deps?: StatusDeps } = {},
|
|
603
|
+
): Promise<{ lines: string[]; paused: number }> {
|
|
604
|
+
const facts = await gatherStatusFacts({ ...opts.deps, cwd: opts.cwd ?? opts.deps?.cwd });
|
|
605
|
+
return { lines: renderStatusScreen(facts, optsFor(opts.stdout ?? process.stdout)), paused: facts.pauses.length };
|
|
606
|
+
}
|