failproofai 1.0.1 → 1.0.2-beta.0
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 +3 -3
- package/.next/standalone/.next/prerender-manifest.json +6 -6
- package/.next/standalone/.next/required-server-files.json +1 -1
- 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 +2 -2
- package/.next/standalone/.next/server/app/_global-error/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/_global-error.html +1 -1
- package/.next/standalone/.next/server/app/_global-error.rsc +7 -7
- package/.next/standalone/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +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/server-reference-manifest.json +1 -1
- package/.next/standalone/.next/server/app/_not-found/page.js +2 -2
- package/.next/standalone/.next/server/app/_not-found/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/_not-found.html +1 -1
- package/.next/standalone/.next/server/app/_not-found.rsc +15 -15
- package/.next/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +15 -15
- package/.next/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +14 -14
- package/.next/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/api/audit/invite/route.js +3 -2
- package/.next/standalone/.next/server/app/api/audit/invite/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/audit/run/route.js +5 -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.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 -3
- 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 +4 -4
- 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 +4 -4
- 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.nft.json +1 -1
- package/.next/standalone/.next/server/app/audit/page/server-reference-manifest.json +2 -2
- package/.next/standalone/.next/server/app/audit/page.js +2 -2
- package/.next/standalone/.next/server/app/audit/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/audit/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/index.html +1 -1
- package/.next/standalone/.next/server/app/index.rsc +15 -15
- package/.next/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +14 -14
- package/.next/standalone/.next/server/app/index.segments/_full.segment.rsc +15 -15
- package/.next/standalone/.next/server/app/index.segments/_tree.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/page/server-reference-manifest.json +1 -1
- package/.next/standalone/.next/server/app/page.js +2 -2
- package/.next/standalone/.next/server/app/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/policies/page/server-reference-manifest.json +66 -22
- package/.next/standalone/.next/server/app/policies/page.js +5 -4
- package/.next/standalone/.next/server/app/policies/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/policies/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/project/[name]/page/server-reference-manifest.json +1 -1
- package/.next/standalone/.next/server/app/project/[name]/page.js +2 -2
- package/.next/standalone/.next/server/app/project/[name]/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/project/[name]/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page/react-loadable-manifest.json +2 -2
- package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page/server-reference-manifest.json +2 -2
- package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page.js +2 -2
- package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/projects/page/server-reference-manifest.json +1 -1
- package/.next/standalone/.next/server/app/projects/page.js +2 -2
- package/.next/standalone/.next/server/app/projects/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/projects/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/settings/page/server-reference-manifest.json +4 -4
- package/.next/standalone/.next/server/app/settings/page.js +1 -1
- 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]__1qzqcio._.js → [externals]__0e32-7c._.js} +1 -1
- package/.next/standalone/.next/server/chunks/[externals]__1s462yl._.js +3 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__01eqny5._.js +3 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0_0xu5z._.js +1 -1
- package/.next/standalone/.next/server/chunks/{[root-of-the-server]__1p8poxd._.js → [root-of-the-server]__0igxt32._.js} +2 -2
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0neqlu-._.js +3 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0o07qi9._.js +3 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0t9oqnn._.js +3 -0
- package/.next/standalone/.next/server/chunks/_0bs-v8p._.js +3 -0
- package/.next/standalone/.next/server/chunks/_0tovk6q._.js +3 -0
- package/.next/standalone/.next/server/chunks/_0trp3yc._.js +3 -0
- package/.next/standalone/.next/server/chunks/_185gyk9._.js +3 -0
- package/.next/standalone/.next/server/chunks/_1ek68ln._.js +5 -6
- package/.next/standalone/.next/server/chunks/lib_telemetry-id_ts_1k8gj9o._.js +3 -0
- package/.next/standalone/.next/server/chunks/{node_modules_posthog-node_dist_entrypoints_index_node_mjs_09d5c79._.js → node_modules_posthog-node_dist_entrypoints_index_node_mjs_04op_ou._.js} +1 -1
- package/.next/standalone/.next/server/chunks/{node_modules_posthog-node_dist_entrypoints_index_node_mjs_1d2g73o._.js → node_modules_posthog-node_dist_entrypoints_index_node_mjs_0dr20uz._.js} +1 -1
- package/.next/standalone/.next/server/chunks/package_json_[json]_cjs_1nxcc4v._.js +1 -1
- package/.next/standalone/.next/server/chunks/src_hooks_0xbw3bu._.js +12 -0
- package/.next/standalone/.next/server/chunks/src_hooks_fp-home_ts_09kv0bn._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__013jr2b._.js +4 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__01wy8d-._.js +4 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__02npjtd._.js +4 -0
- package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__0spkm68._.js → [root-of-the-server]__07gm3zl._.js} +21 -20
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0da85px._.js +4 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0ftmoxc._.js +4 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0oa1lav._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0p-5p8u._.js +4 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0s740oi._.js +6 -0
- package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__07tbbf3._.js → [root-of-the-server]__0zig5sv._.js} +2 -2
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__192nhvh._.js +5 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1p2otjt._.js +4 -0
- package/.next/standalone/.next/server/chunks/ssr/{_0lvyuv4._.js → _00e9tnf._.js} +2 -2
- package/.next/standalone/.next/server/chunks/ssr/_08x1r5t._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/{_0u5zauv._.js → _12l4a_k._.js} +1 -1
- package/.next/standalone/.next/server/chunks/ssr/_1u8-lu2._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/_1zopuov._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/app_audit__components_audit-dashboard_tsx_0p9ud47._.js +13 -13
- package/.next/standalone/.next/server/chunks/ssr/app_global-error_tsx_1kp6l3x._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/app_policies_hooks-client_tsx_19dqvpc._.js +2 -2
- package/.next/standalone/.next/server/chunks/ssr/app_settings_settings-client_tsx_20lq-mq._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/src_hooks_08o7bqo._.js +10 -0
- package/.next/standalone/.next/server/chunks/ssr/src_hooks_builtin-policies_ts_09j2ndl._.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 +3 -3
- 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 +71 -27
- package/.next/standalone/.next/static/chunks/{37fcpc36x9gpe.js → 0eqky-7n7dgdm.js} +1 -1
- package/.next/standalone/.next/static/chunks/{1ide1wl2f7zi1.js → 0sa9v5mhc8861.js} +13 -13
- package/.next/standalone/.next/static/chunks/1mfzsja6ftim5.css +2 -0
- package/.next/standalone/.next/static/chunks/{3k7mxbp9hiom0.js → 1nuqytbta44af.js} +1 -1
- package/.next/standalone/.next/static/chunks/1sl2xmn9inx3y.js +6 -0
- package/.next/standalone/.next/static/chunks/{2_2lhuojq2xop.js → 2aawsjld9ied_.js} +1 -1
- package/.next/standalone/.next/static/chunks/2gn7ut4svofm2.js +1 -0
- package/.next/standalone/.next/static/chunks/{31s1smp-g7gks.js → 2hs7ycvsav7v2.js} +1 -1
- package/.next/standalone/.next/static/chunks/{0k5050ezjvv49.js → 2i2yt3hzjredm.js} +1 -1
- package/.next/standalone/.next/static/chunks/{3c808qrfcq0im.js → 35xg6retpe6rp.js} +1 -1
- package/.next/standalone/.next/static/chunks/{2zpp1we2jv6tb.js → 3y54nw0i7dsjl.js} +1 -1
- package/.next/standalone/SECURITY.md +5 -4
- package/.next/standalone/app/actions/get-hooks-config.ts +108 -17
- package/.next/standalone/app/actions/pack-actions.ts +153 -0
- package/.next/standalone/app/audit/_components/run-progress.tsx +1 -1
- package/.next/standalone/app/audit/_components/share-templates.ts +13 -13
- package/.next/standalone/app/policies/hooks-client.tsx +320 -14
- package/.next/standalone/components/navbar.tsx +2 -28
- package/.next/standalone/components/reach-developers.tsx +8 -1
- package/.next/standalone/docs-old/agenteye/python-sdk-skill.mdx +1 -1
- package/.next/standalone/docs-old/agenteye/python-sdk.mdx +59 -35
- package/.next/standalone/docs-old/i18n/README.ar.md +1 -0
- package/.next/standalone/docs-old/i18n/README.de.md +1 -0
- package/.next/standalone/docs-old/i18n/README.es.md +1 -0
- package/.next/standalone/docs-old/i18n/README.fr.md +1 -0
- package/.next/standalone/docs-old/i18n/README.he.md +1 -0
- package/.next/standalone/docs-old/i18n/README.hi.md +1 -0
- package/.next/standalone/docs-old/i18n/README.it.md +1 -0
- package/.next/standalone/docs-old/i18n/README.ja.md +1 -0
- package/.next/standalone/docs-old/i18n/README.ko.md +1 -0
- package/.next/standalone/docs-old/i18n/README.pt-br.md +1 -0
- package/.next/standalone/docs-old/i18n/README.ru.md +1 -0
- package/.next/standalone/docs-old/i18n/README.tr.md +1 -0
- package/.next/standalone/docs-old/i18n/README.vi.md +1 -0
- package/.next/standalone/docs-old/i18n/README.zh.md +1 -0
- package/.next/standalone/fp-cloud-cli/CHANGELOG.md +431 -0
- package/.next/standalone/fp-cloud-cli/LICENSE +42 -0
- package/.next/standalone/fp-cloud-cli/MANIFEST.in +10 -0
- package/.next/standalone/fp-cloud-cli/README.md +259 -0
- package/.next/standalone/fp-cloud-cli/fp_cli/__init__.py +10 -0
- package/.next/standalone/fp-cloud-cli/fp_cli/__main__.py +4 -0
- package/.next/standalone/fp-cloud-cli/fp_cli/_click_compat.py +64 -0
- package/.next/standalone/fp-cloud-cli/fp_cli/_context.py +332 -0
- package/.next/standalone/fp-cloud-cli/fp_cli/_version.py +1 -0
- package/.next/standalone/fp-cloud-cli/fp_cli/analytics.py +432 -0
- package/.next/standalone/fp-cloud-cli/fp_cli/analytics_config.py +77 -0
- package/.next/standalone/fp-cloud-cli/fp_cli/analytics_registry.py +83 -0
- package/.next/standalone/fp-cloud-cli/fp_cli/app.py +492 -0
- package/.next/standalone/fp-cloud-cli/fp_cli/auth.py +160 -0
- package/.next/standalone/fp-cloud-cli/fp_cli/client.py +1694 -0
- package/.next/standalone/fp-cloud-cli/fp_cli/commands/__init__.py +0 -0
- package/.next/standalone/fp-cloud-cli/fp_cli/commands/_write.py +214 -0
- package/.next/standalone/fp-cloud-cli/fp_cli/commands/agent_cmds.py +407 -0
- package/.next/standalone/fp-cloud-cli/fp_cli/commands/alerts_cmds.py +445 -0
- package/.next/standalone/fp-cloud-cli/fp_cli/commands/audits_cmds.py +1054 -0
- package/.next/standalone/fp-cloud-cli/fp_cli/commands/auth_cmds.py +512 -0
- package/.next/standalone/fp-cloud-cli/fp_cli/commands/errors_cmds.py +190 -0
- package/.next/standalone/fp-cloud-cli/fp_cli/commands/evals_cmds.py +161 -0
- package/.next/standalone/fp-cloud-cli/fp_cli/commands/events_cmds.py +159 -0
- package/.next/standalone/fp-cloud-cli/fp_cli/commands/fleet_cmds.py +416 -0
- package/.next/standalone/fp-cloud-cli/fp_cli/commands/guardrails_cmds.py +148 -0
- package/.next/standalone/fp-cloud-cli/fp_cli/commands/incidents_cmds.py +472 -0
- package/.next/standalone/fp-cloud-cli/fp_cli/commands/keys_cmds.py +407 -0
- package/.next/standalone/fp-cloud-cli/fp_cli/commands/list_cmds.py +63 -0
- package/.next/standalone/fp-cloud-cli/fp_cli/commands/orgs_cmds.py +319 -0
- package/.next/standalone/fp-cloud-cli/fp_cli/commands/policies_cmds.py +499 -0
- package/.next/standalone/fp-cloud-cli/fp_cli/commands/queries_cmds.py +378 -0
- package/.next/standalone/fp-cloud-cli/fp_cli/commands/sessions_cmds.py +151 -0
- package/.next/standalone/fp-cloud-cli/fp_cli/commands/settings_cmds.py +150 -0
- package/.next/standalone/fp-cloud-cli/fp_cli/commands/usage_cmds.py +35 -0
- package/.next/standalone/fp-cloud-cli/fp_cli/commands/users_cmds.py +404 -0
- package/.next/standalone/fp-cloud-cli/fp_cli/config.py +330 -0
- package/.next/standalone/fp-cloud-cli/fp_cli/dates.py +78 -0
- package/.next/standalone/fp-cloud-cli/fp_cli/enforcement.py +345 -0
- package/.next/standalone/fp-cloud-cli/fp_cli/errors.py +98 -0
- package/.next/standalone/fp-cloud-cli/fp_cli/models.py +891 -0
- package/.next/standalone/fp-cloud-cli/fp_cli/orgs.py +30 -0
- package/.next/standalone/fp-cloud-cli/fp_cli/output.py +6593 -0
- package/.next/standalone/fp-cloud-cli/fp_cli/permissions.py +208 -0
- package/.next/standalone/fp-cloud-cli/fp_cli/policy_check.py +290 -0
- package/.next/standalone/fp-cloud-cli/fp_cli/py.typed +0 -0
- package/.next/standalone/fp-cloud-cli/fp_cli/select.py +322 -0
- package/.next/standalone/fp-cloud-cli/fp_cli/theme.py +53 -0
- package/.next/standalone/fp-cloud-cli/pyproject.toml +106 -0
- package/.next/standalone/fp-cloud-cli/skill/SKILL.md +302 -0
- package/.next/standalone/fp-cloud-cli/skill/agents/openai.yaml +8 -0
- package/.next/standalone/fp-cloud-cli/skill/references/commands.md +254 -0
- package/.next/standalone/fp-cloud-cli/tests/__init__.py +0 -0
- package/.next/standalone/fp-cloud-cli/tests/conftest.py +92 -0
- package/.next/standalone/fp-cloud-cli/tests/test_alerting.py +644 -0
- package/.next/standalone/fp-cloud-cli/tests/test_analytics.py +385 -0
- package/.next/standalone/fp-cloud-cli/tests/test_audits.py +950 -0
- package/.next/standalone/fp-cloud-cli/tests/test_auth.py +98 -0
- package/.next/standalone/fp-cloud-cli/tests/test_auth_mode.py +272 -0
- package/.next/standalone/fp-cloud-cli/tests/test_click_compat.py +118 -0
- package/.next/standalone/fp-cloud-cli/tests/test_client.py +299 -0
- package/.next/standalone/fp-cloud-cli/tests/test_commands.py +814 -0
- package/.next/standalone/fp-cloud-cli/tests/test_config.py +99 -0
- package/.next/standalone/fp-cloud-cli/tests/test_dashboards_agent.py +340 -0
- package/.next/standalone/fp-cloud-cli/tests/test_dates.py +76 -0
- package/.next/standalone/fp-cloud-cli/tests/test_enforcement_logic.py +481 -0
- package/.next/standalone/fp-cloud-cli/tests/test_facets.py +170 -0
- package/.next/standalone/fp-cloud-cli/tests/test_failproofai_home.py +722 -0
- package/.next/standalone/fp-cloud-cli/tests/test_fp_home_contract.py +198 -0
- package/.next/standalone/fp-cloud-cli/tests/test_hardening.py +186 -0
- package/.next/standalone/fp-cloud-cli/tests/test_help_table_coverage.py +127 -0
- package/.next/standalone/fp-cloud-cli/tests/test_keys_queries.py +496 -0
- package/.next/standalone/fp-cloud-cli/tests/test_list.py +59 -0
- package/.next/standalone/fp-cloud-cli/tests/test_multivalue.py +183 -0
- package/.next/standalone/fp-cloud-cli/tests/test_no_customer_identifiers.py +168 -0
- package/.next/standalone/fp-cloud-cli/tests/test_operator.py +492 -0
- package/.next/standalone/fp-cloud-cli/tests/test_orgs.py +692 -0
- package/.next/standalone/fp-cloud-cli/tests/test_output.py +1732 -0
- package/.next/standalone/fp-cloud-cli/tests/test_policy_check.py +191 -0
- package/.next/standalone/fp-cloud-cli/tests/test_readme_matches_reality.py +125 -0
- package/.next/standalone/fp-cloud-cli/tests/test_review_fixes.py +161 -0
- package/.next/standalone/fp-cloud-cli/tests/test_telemetry_completeness.py +90 -0
- package/.next/standalone/fp-cloud-cli/tests/test_usage.py +115 -0
- package/.next/standalone/fp-cloud-cli/tests/test_v1_origin_diagnostic.py +70 -0
- package/.next/standalone/fp-cloud-cli/tests/test_v1_routing.py +214 -0
- package/.next/standalone/fp-cloud-cli/tests/test_whoami.py +104 -0
- package/.next/standalone/fp-cloud-cli/uv.lock +552 -0
- package/.next/standalone/package.json +29 -22
- package/.next/standalone/sdk/python/CHANGELOG.md +70 -0
- package/.next/standalone/sdk/python/LICENSE +42 -0
- package/.next/standalone/sdk/python/MANIFEST.in +21 -0
- package/.next/standalone/sdk/python/README.md +466 -0
- package/.next/standalone/sdk/python/docs/README.md +249 -0
- package/.next/standalone/sdk/python/docs/_shared/README.md +43 -0
- package/.next/standalone/sdk/python/docs/_shared/__init__.py +195 -0
- package/.next/standalone/sdk/python/docs/crewai/README.md +328 -0
- package/.next/standalone/sdk/python/docs/crewai/examples/quickstart.py +61 -0
- package/.next/standalone/sdk/python/docs/crewai/examples/research_crew.py +92 -0
- package/.next/standalone/sdk/python/docs/langgraph/README.md +481 -0
- package/.next/standalone/sdk/python/docs/langgraph/examples/quickstart.py +51 -0
- package/.next/standalone/sdk/python/docs/langgraph/examples/research_agent.py +86 -0
- package/.next/standalone/sdk/python/docs/langgraph/examples/supervisor_handoff.py +83 -0
- package/.next/standalone/sdk/python/docs/llama_index/README.md +332 -0
- package/.next/standalone/sdk/python/docs/llama_index/examples/quickstart.py +62 -0
- package/.next/standalone/sdk/python/docs/llama_index/examples/research_agent.py +93 -0
- package/.next/standalone/sdk/python/docs/manual/README.md +491 -0
- package/.next/standalone/sdk/python/docs/manual/examples/quickstart.py +57 -0
- package/.next/standalone/sdk/python/docs/manual/examples/research_agent.py +133 -0
- package/.next/standalone/sdk/python/docs/pydantic_ai/README.md +323 -0
- package/.next/standalone/sdk/python/docs/pydantic_ai/examples/quickstart.py +48 -0
- package/.next/standalone/sdk/python/docs/pydantic_ai/examples/research_agent.py +94 -0
- package/.next/standalone/sdk/python/failproofai_sdk/__init__.py +131 -0
- package/.next/standalone/sdk/python/failproofai_sdk/_context.py +218 -0
- package/.next/standalone/sdk/python/failproofai_sdk/_environment.py +78 -0
- package/.next/standalone/sdk/python/failproofai_sdk/_events.py +752 -0
- package/.next/standalone/sdk/python/failproofai_sdk/_resolver.py +118 -0
- package/.next/standalone/sdk/python/failproofai_sdk/_runtime.py +21 -0
- package/.next/standalone/sdk/python/failproofai_sdk/_schema.py +317 -0
- package/.next/standalone/sdk/python/failproofai_sdk/_scopes.py +422 -0
- package/.next/standalone/sdk/python/failproofai_sdk/_version.py +1 -0
- package/.next/standalone/sdk/python/failproofai_sdk/_writer.py +750 -0
- package/.next/standalone/sdk/python/failproofai_sdk/integrations/__init__.py +274 -0
- package/.next/standalone/sdk/python/failproofai_sdk/integrations/_compat.py +247 -0
- package/.next/standalone/sdk/python/failproofai_sdk/integrations/_core.py +1076 -0
- package/.next/standalone/sdk/python/failproofai_sdk/integrations/crewai.py +1669 -0
- package/.next/standalone/sdk/python/failproofai_sdk/integrations/langchain.py +2151 -0
- package/.next/standalone/sdk/python/failproofai_sdk/integrations/llama_index.py +1588 -0
- package/.next/standalone/sdk/python/failproofai_sdk/integrations/pydantic_ai.py +977 -0
- package/.next/standalone/sdk/python/failproofai_sdk/py.typed +0 -0
- package/.next/standalone/sdk/python/pyproject.toml +92 -0
- package/.next/standalone/sdk/python/skill/SKILL.md +399 -0
- package/.next/standalone/sdk/python/skill/agents/openai.yaml +8 -0
- package/.next/standalone/sdk/python/skill/references/events.md +234 -0
- package/.next/standalone/sdk/python/skill/references/frameworks.md +314 -0
- package/.next/standalone/sdk/python/skill/references/install.md +100 -0
- package/.next/standalone/sdk/python/skill/references/integration.md +352 -0
- package/.next/standalone/sdk/python/tests/__init__.py +0 -0
- package/.next/standalone/sdk/python/tests/conftest.py +169 -0
- package/.next/standalone/sdk/python/tests/integrations/__init__.py +6 -0
- package/.next/standalone/sdk/python/tests/integrations/test_crewai.py +1299 -0
- package/.next/standalone/sdk/python/tests/integrations/test_langchain.py +2338 -0
- package/.next/standalone/sdk/python/tests/integrations/test_llama_index.py +1576 -0
- package/.next/standalone/sdk/python/tests/integrations/test_pydantic_ai.py +1102 -0
- package/.next/standalone/sdk/python/tests/test_context.py +410 -0
- package/.next/standalone/sdk/python/tests/test_docs.py +411 -0
- package/.next/standalone/sdk/python/tests/test_durability.py +1352 -0
- package/.next/standalone/sdk/python/tests/test_encoding.py +571 -0
- package/.next/standalone/sdk/python/tests/test_integrations.py +1127 -0
- package/.next/standalone/sdk/python/tests/test_no_customer_identifiers.py +214 -0
- package/.next/standalone/sdk/python/tests/test_packaging.py +185 -0
- package/.next/standalone/sdk/python/tests/test_resolver_umbrella.py +178 -0
- package/.next/standalone/sdk/python/tests/test_scopes.py +487 -0
- package/.next/standalone/sdk/python/tests/test_sdk.py +923 -0
- package/.next/standalone/sdk/python/tests/test_server_contract.py +650 -0
- package/.next/standalone/sdk/python/tests/test_site_docs.py +371 -0
- package/.next/standalone/sdk/python/tests/test_skill_snippets.py +165 -0
- package/.next/standalone/sdk/python/tests/test_spool_contract.py +287 -0
- package/.next/standalone/sdk/python/tests/test_spool_creation.py +360 -0
- package/.next/standalone/sdk/python/tests/test_wire_format.py +279 -0
- package/.next/standalone/sdk/python/tests/test_zero_dependencies.py +378 -0
- package/.next/standalone/sdk/python/uv.lock +6005 -0
- package/.next/standalone/server.js +1 -1
- package/README.md +77 -21
- package/bin/failproofai.mjs +145 -11
- package/dist/cli.mjs +4482 -2205
- package/dist/index.js +5 -5
- package/dist/worker.mjs +2399 -1978
- package/package.json +29 -22
- package/policy-pack/SHA256SUMS +2 -0
- package/policy-pack/failproofai-pack.json +732 -0
- package/policy-pack/failproofai-pack.mjs +2017 -0
- package/scripts/build-policy-pack.mjs +121 -0
- package/scripts/changelog-section.py +77 -0
- package/scripts/prune-standalone.mjs +3 -0
- package/scripts/python-version.py +232 -0
- package/scripts/translate-docs/cli.ts +21 -0
- package/scripts/translate-docs/mintlify-nav.ts +85 -13
- package/scripts/translate-docs/translator.ts +66 -11
- package/src/audit/cache.ts +40 -3
- package/src/audit/cli.ts +1 -1
- package/src/audit/index.ts +1 -1
- package/src/audit/replay.ts +89 -3
- package/src/audit/report.ts +5 -5
- package/src/audit/schedule-cli.ts +49 -53
- package/src/hooks/builtin-policies.ts +217 -579
- package/src/hooks/cloud-enrollment-cli.ts +66 -27
- package/src/hooks/cloud-managed-policies.ts +6 -1
- package/src/hooks/configure-wizard.ts +1 -1
- package/src/hooks/custom-hooks-loader.ts +89 -6
- package/src/hooks/fp-home.ts +92 -4
- package/src/hooks/fp-reset.ts +26 -2
- package/src/hooks/handler.ts +181 -11
- package/src/hooks/harness-cli.ts +79 -25
- package/src/hooks/hook-activity-store.ts +13 -3
- package/src/hooks/install-prompt.ts +25 -3
- package/src/hooks/manager.ts +418 -172
- package/src/hooks/migrations.ts +2 -2
- package/src/hooks/pack-cli.ts +505 -0
- package/src/hooks/pack-failclosed.ts +189 -0
- package/src/hooks/pack-manifest.ts +344 -0
- package/src/hooks/pack-store.ts +930 -0
- package/src/hooks/policy-catalog.ts +535 -0
- package/src/hooks/policy-evaluator.ts +24 -16
- package/src/hooks/policy-presets.ts +4 -2
- package/src/hooks/policy-registry.ts +8 -2
- package/src/hooks/policy-types.ts +26 -0
- package/src/hooks/session-pause-cli.ts +33 -9
- package/src/hooks/tui.ts +570 -0
- package/src/hooks/types.ts +15 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0_usx0j._.js +0 -3
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0_zk340._.js +0 -3
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0agdw2a._.js +0 -3
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0d1u_na._.js +0 -3
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0oy4kxb._.js +0 -3
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0zf034w._.js +0 -3
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__1tvn4wf._.js +0 -3
- package/.next/standalone/.next/server/chunks/_0lxbzdq._.js +0 -3
- package/.next/standalone/.next/server/chunks/_1ger0a2._.js +0 -3
- package/.next/standalone/.next/server/chunks/_1zuiiy3._.js +0 -3
- package/.next/standalone/.next/server/chunks/node_modules_posthog-node_dist_entrypoints_index_node_mjs_1iiwm5e._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0kc8_v5._.js +0 -4
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0kgy9da._.js +0 -4
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0mhqk5a._.js +0 -4
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0pltpn8._.js +0 -4
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0qkcyf-._.js +0 -4
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0w0x_ok._.js +0 -4
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1_dfocn._.js +0 -4
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1bfex62._.js +0 -5
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1kzapg5._.js +0 -6
- package/.next/standalone/.next/static/chunks/36fw6yk1s6x_f.js +0 -1
- package/.next/standalone/.next/static/chunks/3tzq0260n17ae.js +0 -6
- package/.next/standalone/.next/static/chunks/3wq9x0c-7ld5d.css +0 -2
- /package/.next/standalone/.next/static/{ZklXxyA5ygsWSQz2u_zii → nwbyWT06Ud0XdXOrVJyvS}/_buildManifest.js +0 -0
- /package/.next/standalone/.next/static/{ZklXxyA5ygsWSQz2u_zii → nwbyWT06Ud0XdXOrVJyvS}/_clientMiddlewareManifest.js +0 -0
- /package/.next/standalone/.next/static/{ZklXxyA5ygsWSQz2u_zii → nwbyWT06Ud0XdXOrVJyvS}/_ssgManifest.js +0 -0
|
@@ -0,0 +1,432 @@
|
|
|
1
|
+
"""PostHog telemetry for the CLI — a thin, fail-safe wrapper.
|
|
2
|
+
|
|
3
|
+
Mirrors the dashboard's ``lib/analytics.ts``: import-safe, a no-op until
|
|
4
|
+
``init_analytics`` runs, identifies operators by an opaque id only (never email),
|
|
5
|
+
and tags every event with ``product`` super-properties. Adapted for a short-lived
|
|
6
|
+
process: events are flushed on exit within a hard time bound, and **every** public
|
|
7
|
+
function swallows all exceptions so telemetry can never slow or break a command.
|
|
8
|
+
|
|
9
|
+
Privacy: the generic ``command_executed`` event records only static names (command,
|
|
10
|
+
subcommand, flag NAMES) plus coarse booleans/counts and one closed enum
|
|
11
|
+
(``auth_mode``). Argument *values* — dashboard URL, session token, API key, email,
|
|
12
|
+
session ids, score expressions, file paths — are never sent, in any form: not the
|
|
13
|
+
value, not its length, not a prefix.
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
from __future__ import annotations
|
|
17
|
+
|
|
18
|
+
import logging
|
|
19
|
+
import platform
|
|
20
|
+
import threading
|
|
21
|
+
import uuid
|
|
22
|
+
from typing import Any, Dict, FrozenSet, List, Optional, Tuple
|
|
23
|
+
|
|
24
|
+
from . import analytics_config as acfg
|
|
25
|
+
from . import config as cfgmod
|
|
26
|
+
from ._version import __version__
|
|
27
|
+
|
|
28
|
+
# Single client + per-invocation context (the dashboard keeps one posthog-js instance).
|
|
29
|
+
# The client is built LAZILY (see _ensure_client) on the first actual send, so a normal
|
|
30
|
+
# command never pays the posthog import + client construction on its startup path.
|
|
31
|
+
_client: Any = None
|
|
32
|
+
_distinct_id: Optional[str] = None
|
|
33
|
+
_command: Optional[str] = None
|
|
34
|
+
_json_output: bool = False
|
|
35
|
+
_auth_mode: str = "none"
|
|
36
|
+
_force_anonymous: bool = False
|
|
37
|
+
_pending_conf: Any = None
|
|
38
|
+
_init_done: bool = False
|
|
39
|
+
|
|
40
|
+
# Hard cap on how long shutdown may block the CLI waiting on the network.
|
|
41
|
+
_FLUSH_TIMEOUT_SECS = 1.5
|
|
42
|
+
|
|
43
|
+
# Recognised command / flag names. These are the STATIC FALLBACK used only if the
|
|
44
|
+
# derived registry (analytics_registry, introspected from the live Typer app) cannot
|
|
45
|
+
# be built. The generic event emits ONLY known static strings, so customer data (urls,
|
|
46
|
+
# tokens, ids, queries, paths) can never leak through it. The anti-drift test asserts
|
|
47
|
+
# the *derived* catalog stays exhaustive, which is the real guarantee of completeness.
|
|
48
|
+
_KNOWN_COMMANDS = frozenset(
|
|
49
|
+
{
|
|
50
|
+
"login", "logout", "whoami", "orgs", "events",
|
|
51
|
+
"sessions", "evals", "version", "help",
|
|
52
|
+
"list", "errors", "keys", "query",
|
|
53
|
+
"users", "settings", "alerts", "audits", "issues", "agent",
|
|
54
|
+
}
|
|
55
|
+
)
|
|
56
|
+
# group -> its leaf subcommand names (static fallback).
|
|
57
|
+
_STATIC_LEAVES: Dict[str, FrozenSet[str]] = {
|
|
58
|
+
"list": frozenset({"envs", "agents", "event_types", "score_filters", "models",
|
|
59
|
+
"hooks", "tools", "error_types"}),
|
|
60
|
+
"orgs": frozenset({"list", "switch", "current", "perms"}),
|
|
61
|
+
"keys": frozenset({"list", "show", "create", "update", "disable", "regenerate"}),
|
|
62
|
+
"query": frozenset({"list", "show", "create", "update", "delete", "run", "schema"}),
|
|
63
|
+
"users": frozenset({"list", "show", "create", "update", "disable", "enable"}),
|
|
64
|
+
"settings": frozenset({"list", "schema", "set"}),
|
|
65
|
+
"alerts": frozenset({"list", "show", "create", "update", "delete", "test"}),
|
|
66
|
+
"audits": frozenset({"list", "show", "create", "edit", "delete", "run", "runs", "findings",
|
|
67
|
+
"finding", "ack", "mute", "dismiss", "resolve", "reopen", "assign"}),
|
|
68
|
+
"issues": frozenset({"list", "count", "show", "ack", "assign", "resolve", "comment-list",
|
|
69
|
+
"comment-add", "comment-delete", "subscribers", "subscribe", "unsubscribe", "open"}),
|
|
70
|
+
"agent": frozenset({"health", "models", "chats", "show", "rename", "delete", "ask"}),
|
|
71
|
+
}
|
|
72
|
+
# Global options that consume a following token (static fallback for value-skipping).
|
|
73
|
+
_STATIC_VALUE_FLAGS = frozenset(
|
|
74
|
+
{"--base-url", "--org", "--token", "--api-key", "--timeout", "--email", "-e"}
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
# Any flag token (long or short) -> its canonical long name (static fallback).
|
|
78
|
+
_FLAG_ALIASES: Dict[str, str] = {
|
|
79
|
+
"--json": "--json",
|
|
80
|
+
"--base-url": "--base-url",
|
|
81
|
+
"--org": "--org",
|
|
82
|
+
"--token": "--token",
|
|
83
|
+
"--api-key": "--api-key",
|
|
84
|
+
"--timeout": "--timeout",
|
|
85
|
+
"--no-color": "--no-color",
|
|
86
|
+
"--quiet": "--quiet", "-q": "--quiet",
|
|
87
|
+
"--insecure": "--insecure", "--secure": "--secure",
|
|
88
|
+
"--version": "--version",
|
|
89
|
+
"--email": "--email", "-e": "--email",
|
|
90
|
+
"--environment": "--environment", "--env": "--environment",
|
|
91
|
+
"--agent-id": "--agent-id",
|
|
92
|
+
"--status": "--status",
|
|
93
|
+
"--score": "--score",
|
|
94
|
+
"--since": "--since", "--from": "--from", "--to": "--to",
|
|
95
|
+
"--limit": "--limit", "-n": "--limit",
|
|
96
|
+
"--cursor": "--cursor",
|
|
97
|
+
"--all": "--all",
|
|
98
|
+
"--page-size": "--page-size",
|
|
99
|
+
"--fields": "--fields",
|
|
100
|
+
"--arg": "--arg", "--param": "--param", "--force": "--force",
|
|
101
|
+
"--session-id": "--session-id",
|
|
102
|
+
"--event-type": "--event-type",
|
|
103
|
+
"--audit": "--audit", "--run-id": "--run-id", "--offset": "--offset",
|
|
104
|
+
"--reason": "--reason", "--to": "--to", "--show-id": "--show-id",
|
|
105
|
+
"--scope": "--scope", "--sensitivity": "--sensitivity", "--top-k": "--top-k",
|
|
106
|
+
"--window-mode": "--window-mode", "--channels": "--channels",
|
|
107
|
+
"--latest-per-session": "--latest-per-session",
|
|
108
|
+
"--events-limit": "--events-limit",
|
|
109
|
+
"--no-events": "--no-events", "--no-eval": "--no-eval",
|
|
110
|
+
"--output": "--output", "-o": "--output",
|
|
111
|
+
"--source": "--source",
|
|
112
|
+
"--help": "--help", "-h": "--help",
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
def _catalog() -> "tuple":
|
|
117
|
+
"""``(known_commands, leaf_registry, flag_aliases, value_flags)``.
|
|
118
|
+
|
|
119
|
+
Prefers the live derived catalog; falls back to the static tables if introspection
|
|
120
|
+
fails (telemetry is best-effort and must never raise into a command).
|
|
121
|
+
"""
|
|
122
|
+
try:
|
|
123
|
+
from . import analytics_registry as reg
|
|
124
|
+
|
|
125
|
+
known, leaves, flags, value_flags = reg.build()
|
|
126
|
+
if known and flags:
|
|
127
|
+
return known, leaves, flags, value_flags
|
|
128
|
+
except Exception:
|
|
129
|
+
pass
|
|
130
|
+
return _KNOWN_COMMANDS, _STATIC_LEAVES, _FLAG_ALIASES, _STATIC_VALUE_FLAGS
|
|
131
|
+
|
|
132
|
+
# The CLOSED set of auth modes we will emit. Mirrors `client.AuthMode`, but stated as
|
|
133
|
+
# a literal allowlist so an unexpected value degrades to "none" rather than being
|
|
134
|
+
# forwarded verbatim — this property must never become a channel for anything but
|
|
135
|
+
# these three words.
|
|
136
|
+
_AUTH_MODES: Dict[str, str] = {"session": "session", "api_key": "api_key", "none": "none"}
|
|
137
|
+
|
|
138
|
+
# Exit code -> coarse error class (derived from errors.py / Click conventions).
|
|
139
|
+
_EXIT_CATEGORY: Dict[int, Optional[str]] = {
|
|
140
|
+
0: None, # success
|
|
141
|
+
2: "usage",
|
|
142
|
+
3: "network",
|
|
143
|
+
4: "auth",
|
|
144
|
+
5: "forbidden",
|
|
145
|
+
6: "not_found",
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
def init_analytics(conf: cfgmod.CliConfig, *, force_anonymous: bool = False) -> None:
|
|
150
|
+
"""Record config for a LAZILY-constructed PostHog client (resolve + build on first send).
|
|
151
|
+
|
|
152
|
+
Deferring the posthog import + client construction keeps them off every command's startup
|
|
153
|
+
path; an invocation that never emits an event (the common case, and every opted-out user)
|
|
154
|
+
pays nothing. :func:`_ensure_client` does the real work the first time a send is attempted.
|
|
155
|
+
|
|
156
|
+
``force_anonymous`` is set in API-key mode. :func:`_resolve_distinct_id` reads the
|
|
157
|
+
SAVED config, so a machine where a human is logged in would attribute a CI key's
|
|
158
|
+
commands to that person — a wrong identity, silently, forever.
|
|
159
|
+
"""
|
|
160
|
+
global _pending_conf, _init_done, _client, _distinct_id, _force_anonymous
|
|
161
|
+
_pending_conf = conf
|
|
162
|
+
_init_done = False
|
|
163
|
+
_client = None
|
|
164
|
+
_distinct_id = None
|
|
165
|
+
_force_anonymous = bool(force_anonymous)
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
def _ensure_client() -> None:
|
|
169
|
+
"""Build the PostHog client on first use (idempotent). Any failure leaves telemetry off."""
|
|
170
|
+
global _client, _distinct_id, _init_done
|
|
171
|
+
if _init_done:
|
|
172
|
+
return
|
|
173
|
+
_init_done = True
|
|
174
|
+
conf = _pending_conf
|
|
175
|
+
if conf is None:
|
|
176
|
+
return
|
|
177
|
+
settings = acfg.resolve_config()
|
|
178
|
+
if not settings.enabled:
|
|
179
|
+
return
|
|
180
|
+
try:
|
|
181
|
+
from posthog import Posthog
|
|
182
|
+
|
|
183
|
+
_distinct_id = (
|
|
184
|
+
_ensure_anonymous_id(conf) if _force_anonymous else _resolve_distinct_id(conf)
|
|
185
|
+
)
|
|
186
|
+
_client = Posthog(
|
|
187
|
+
settings.api_key,
|
|
188
|
+
host=settings.host,
|
|
189
|
+
flush_at=1, # a CLI fires few events then exits — send promptly
|
|
190
|
+
max_retries=1, # don't retry-storm a blocked/slow network
|
|
191
|
+
timeout=3, # short per-request timeout
|
|
192
|
+
disable_geoip=True, # don't resolve the caller's IP to a location
|
|
193
|
+
super_properties=_super_properties(),
|
|
194
|
+
)
|
|
195
|
+
# The CLI owns stderr; keep posthog's own logger from printing onto it.
|
|
196
|
+
logging.getLogger("posthog").setLevel(logging.CRITICAL)
|
|
197
|
+
except Exception:
|
|
198
|
+
_client = None
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
def note_command(
|
|
202
|
+
command: Optional[str], json_output: bool, auth_mode: Any = None
|
|
203
|
+
) -> None:
|
|
204
|
+
"""Record (from the main callback) which command ran, whether ``--json`` is set, and
|
|
205
|
+
which credential it used.
|
|
206
|
+
|
|
207
|
+
Authoritative — taken from the parsed Typer context, not re-parsed from argv. Uses the
|
|
208
|
+
cheap STATIC command set (no Typer→Click tree build on the startup path). Safe to call
|
|
209
|
+
when telemetry is disabled (it only sets module state).
|
|
210
|
+
|
|
211
|
+
``auth_mode`` is an ``AuthMode`` (a ``str`` enum we authored, so the property is a
|
|
212
|
+
closed set: ``session`` | ``api_key`` | ``none``). Never the key, its length, or a
|
|
213
|
+
prefix — the mode is the whole signal, and it is what makes "is anyone actually
|
|
214
|
+
running this in CI?" answerable.
|
|
215
|
+
"""
|
|
216
|
+
global _command, _json_output, _auth_mode
|
|
217
|
+
_command = command if command in _KNOWN_COMMANDS else None
|
|
218
|
+
_json_output = bool(json_output)
|
|
219
|
+
_auth_mode = _AUTH_MODES.get(str(getattr(auth_mode, "value", auth_mode)), "none")
|
|
220
|
+
|
|
221
|
+
|
|
222
|
+
def capture(event: str, properties: Optional[Dict[str, Any]] = None) -> None:
|
|
223
|
+
"""Send one event. No-op until initialised; never raises."""
|
|
224
|
+
_ensure_client()
|
|
225
|
+
if _client is None:
|
|
226
|
+
return
|
|
227
|
+
try:
|
|
228
|
+
# Keyword form. `posthog>=7` is `capture(self, event, **kwargs)` —
|
|
229
|
+
# everything after `event` is keyword-only — so the posthog-3 positional
|
|
230
|
+
# order `(distinct_id, event, …)` raised `TypeError: Client.capture()
|
|
231
|
+
# takes 2 positional arguments but 3 were given`, and the failure was
|
|
232
|
+
# invisible from three directions at once: posthog's own `@no_throw`
|
|
233
|
+
# swallowed it, `_ensure_client` sets the posthog logger to CRITICAL so
|
|
234
|
+
# the log it wrote was suppressed, and the `except Exception: pass` below
|
|
235
|
+
# would have swallowed it too. Nothing sent, nothing logged, nothing
|
|
236
|
+
# raised. (Dead today — `TELEMETRY_DISABLED` is True — which is precisely
|
|
237
|
+
# why it could sit here unnoticed until someone flipped that flag.)
|
|
238
|
+
_client.capture(event, distinct_id=_distinct_id, properties=properties or {})
|
|
239
|
+
except Exception:
|
|
240
|
+
pass
|
|
241
|
+
|
|
242
|
+
|
|
243
|
+
def capture_command(exit_code: int, duration_ms: int, argv: List[str]) -> None:
|
|
244
|
+
"""Emit the generic ``command_executed`` event with an allowlisted payload.
|
|
245
|
+
|
|
246
|
+
``command``/``subcommand`` come from resolving argv against the known command
|
|
247
|
+
tree, so every group's leaf (``keys create``, ``orgs switch``, …) is distinguished —
|
|
248
|
+
not just the top-level command. Both are static names from the catalog; values never leak.
|
|
249
|
+
"""
|
|
250
|
+
_ensure_client()
|
|
251
|
+
if _client is None:
|
|
252
|
+
return
|
|
253
|
+
group, leaf = _resolve_command_path(argv)
|
|
254
|
+
command = _command if _command is not None else group
|
|
255
|
+
subcommand = leaf if (command is not None and command == group) else None
|
|
256
|
+
capture(
|
|
257
|
+
"command_executed",
|
|
258
|
+
{
|
|
259
|
+
"command": command,
|
|
260
|
+
"subcommand": subcommand,
|
|
261
|
+
"success": exit_code == 0,
|
|
262
|
+
"exit_code": exit_code,
|
|
263
|
+
"error_category": _EXIT_CATEGORY.get(exit_code, "error"),
|
|
264
|
+
"duration_ms": duration_ms,
|
|
265
|
+
"flags": _sanitize_flags(argv),
|
|
266
|
+
"json_output": _json_output,
|
|
267
|
+
"auth_mode": _auth_mode,
|
|
268
|
+
},
|
|
269
|
+
)
|
|
270
|
+
|
|
271
|
+
|
|
272
|
+
def identify(user_id: Optional[str]) -> None:
|
|
273
|
+
"""Link this machine's prior anonymous activity to the operator id (called on login).
|
|
274
|
+
|
|
275
|
+
Mirrors the dashboard's ``posthog.identify(id)`` by the opaque operator id only.
|
|
276
|
+
No-op when telemetry is off or there is nothing to link.
|
|
277
|
+
"""
|
|
278
|
+
_ensure_client()
|
|
279
|
+
if _client is None or not user_id:
|
|
280
|
+
return
|
|
281
|
+
try:
|
|
282
|
+
conf = cfgmod.load_config()
|
|
283
|
+
if conf.anonymous_id and conf.anonymous_id != user_id:
|
|
284
|
+
_client.alias(previous_id=conf.anonymous_id, distinct_id=user_id)
|
|
285
|
+
except Exception:
|
|
286
|
+
pass
|
|
287
|
+
|
|
288
|
+
|
|
289
|
+
def reset() -> None:
|
|
290
|
+
"""Rotate the local anonymous id on logout so later anon events aren't tied to the user.
|
|
291
|
+
|
|
292
|
+
posthog-python has no client-side reset; this is local state only (the analog of
|
|
293
|
+
the dashboard's ``posthog.reset()``). Runs **regardless** of whether telemetry is
|
|
294
|
+
currently enabled: ``anonymous_id`` is persistent config state and the opt-out
|
|
295
|
+
flag can be toggled between runs, so the unlink-on-logout invariant must hold even
|
|
296
|
+
when this invocation has telemetry off (otherwise a later re-enabled run could
|
|
297
|
+
reuse an id still aliased to the operator who just logged out).
|
|
298
|
+
"""
|
|
299
|
+
try:
|
|
300
|
+
conf = cfgmod.load_config()
|
|
301
|
+
conf.anonymous_id = uuid.uuid4().hex
|
|
302
|
+
cfgmod.save_config(conf)
|
|
303
|
+
except Exception:
|
|
304
|
+
pass
|
|
305
|
+
|
|
306
|
+
|
|
307
|
+
def shutdown() -> None:
|
|
308
|
+
"""Flush pending events, bounded so telemetry never stalls the CLI on exit.
|
|
309
|
+
|
|
310
|
+
Idempotent. The flush runs on a daemon thread joined for at most
|
|
311
|
+
``_FLUSH_TIMEOUT_SECS``; if the network is slow/blocked the thread is abandoned
|
|
312
|
+
(it dies with the process) and the event is dropped — telemetry is best-effort.
|
|
313
|
+
|
|
314
|
+
TODO(telemetry-nonblocking): this bounds only the *final* flush. The client build
|
|
315
|
+
and first ``capture()`` connect (in ``_ensure_client`` / ``capture_command``) are
|
|
316
|
+
NOT bounded, so a blocked PostHog host stalls the CLI ~5s/command before this runs.
|
|
317
|
+
Telemetry is currently disabled via ``analytics_config.TELEMETRY_DISABLED``; before
|
|
318
|
+
re-enabling, move the whole capture+flush onto this bounded daemon thread (or set an
|
|
319
|
+
aggressive connect timeout on the client) so no command can ever block on it.
|
|
320
|
+
"""
|
|
321
|
+
global _client
|
|
322
|
+
client = _client
|
|
323
|
+
_client = None # a second call is a no-op
|
|
324
|
+
if client is None:
|
|
325
|
+
return
|
|
326
|
+
|
|
327
|
+
def _drain() -> None:
|
|
328
|
+
try:
|
|
329
|
+
client.flush()
|
|
330
|
+
except Exception:
|
|
331
|
+
pass
|
|
332
|
+
try:
|
|
333
|
+
client.shutdown()
|
|
334
|
+
except Exception:
|
|
335
|
+
pass
|
|
336
|
+
|
|
337
|
+
thread = threading.Thread(target=_drain, name="posthog-flush", daemon=True)
|
|
338
|
+
thread.start()
|
|
339
|
+
thread.join(_FLUSH_TIMEOUT_SECS)
|
|
340
|
+
|
|
341
|
+
|
|
342
|
+
# --- internals -------------------------------------------------------------------
|
|
343
|
+
|
|
344
|
+
|
|
345
|
+
def _super_properties() -> Dict[str, str]:
|
|
346
|
+
"""Tags merged into every event (the analog of ``posthog.register``)."""
|
|
347
|
+
return {
|
|
348
|
+
"product": acfg.PRODUCT,
|
|
349
|
+
"cli_version": __version__,
|
|
350
|
+
"os": platform.system(), # coarse: "Linux" / "Darwin" / "Windows"
|
|
351
|
+
"python_version": platform.python_version(),
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
|
|
355
|
+
def _resolve_distinct_id(conf: cfgmod.CliConfig) -> str:
|
|
356
|
+
"""Opaque operator id when logged in, else a stable per-machine anonymous id.
|
|
357
|
+
|
|
358
|
+
``user_id`` is used only alongside a stored session token, so a logged-out machine
|
|
359
|
+
falls back to its anonymous id (which :func:`reset` rotates on logout).
|
|
360
|
+
"""
|
|
361
|
+
if conf.user_id and conf.session_token:
|
|
362
|
+
return conf.user_id
|
|
363
|
+
return _ensure_anonymous_id(conf)
|
|
364
|
+
|
|
365
|
+
|
|
366
|
+
def _ensure_anonymous_id(conf: cfgmod.CliConfig) -> str:
|
|
367
|
+
"""Return the persisted anonymous id, generating and saving one on first use."""
|
|
368
|
+
if conf.anonymous_id:
|
|
369
|
+
return conf.anonymous_id
|
|
370
|
+
anon = uuid.uuid4().hex
|
|
371
|
+
conf.anonymous_id = anon
|
|
372
|
+
try:
|
|
373
|
+
cfgmod.save_config(conf)
|
|
374
|
+
except Exception:
|
|
375
|
+
pass # fall back to an in-memory id for this run
|
|
376
|
+
return anon
|
|
377
|
+
|
|
378
|
+
|
|
379
|
+
def _resolve_command_path(argv: List[str]) -> Tuple[Optional[str], Optional[str]]:
|
|
380
|
+
"""Resolve ``(group, leaf)`` from argv against the known command tree.
|
|
381
|
+
|
|
382
|
+
Walks tokens, skipping global/option flags (and the value of a value-taking option),
|
|
383
|
+
to find the first known top-level command (``group``) and then, if that group has
|
|
384
|
+
subcommands, the first matching leaf. Only ever returns static names from the
|
|
385
|
+
catalog, so argument values can never be emitted. Generalises the old
|
|
386
|
+
``session``-only detection to every group.
|
|
387
|
+
"""
|
|
388
|
+
known, leaves, _flags, value_flags = _catalog()
|
|
389
|
+
group: Optional[str] = None
|
|
390
|
+
leaf: Optional[str] = None
|
|
391
|
+
i = 0
|
|
392
|
+
while i < len(argv):
|
|
393
|
+
tok = argv[i]
|
|
394
|
+
if tok.startswith("-"):
|
|
395
|
+
name = tok.split("=", 1)[0]
|
|
396
|
+
if "=" not in tok and name in value_flags:
|
|
397
|
+
i += 2 # skip the option's value token
|
|
398
|
+
continue
|
|
399
|
+
i += 1
|
|
400
|
+
continue
|
|
401
|
+
if group is None:
|
|
402
|
+
if tok in known:
|
|
403
|
+
group = tok
|
|
404
|
+
i += 1
|
|
405
|
+
continue
|
|
406
|
+
if leaf is None and tok in leaves.get(group, frozenset()):
|
|
407
|
+
leaf = tok
|
|
408
|
+
break
|
|
409
|
+
i += 1
|
|
410
|
+
return group, leaf
|
|
411
|
+
|
|
412
|
+
|
|
413
|
+
def _detect_subcommand(argv: List[str]) -> Optional[str]:
|
|
414
|
+
"""Back-compat shim: the leaf subcommand for the resolved command (or None)."""
|
|
415
|
+
return _resolve_command_path(argv)[1]
|
|
416
|
+
|
|
417
|
+
|
|
418
|
+
def _sanitize_flags(argv: List[str]) -> List[str]:
|
|
419
|
+
"""Flag NAMES present in argv, intersected with the known set — never values.
|
|
420
|
+
|
|
421
|
+
``--opt=value`` is split so the value is dropped, and any unknown token (including
|
|
422
|
+
a value that happens to start with ``-``) is discarded.
|
|
423
|
+
"""
|
|
424
|
+
aliases = _catalog()[2]
|
|
425
|
+
seen: List[str] = []
|
|
426
|
+
for tok in argv:
|
|
427
|
+
if not tok.startswith("-"):
|
|
428
|
+
continue
|
|
429
|
+
canonical = aliases.get(tok.split("=", 1)[0])
|
|
430
|
+
if canonical and canonical not in seen:
|
|
431
|
+
seen.append(canonical)
|
|
432
|
+
return seen
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
"""Resolve whether CLI telemetry runs, and with what PostHog credentials.
|
|
2
|
+
|
|
3
|
+
Mirrors the dashboard's ``lib/posthog-config.ts``: a public, write-only project key
|
|
4
|
+
shipped in the package, an opt-out env var, and a dev/prod gate — resolved at
|
|
5
|
+
invocation time. This module is intentionally pure (no ``posthog`` import) so it is
|
|
6
|
+
cheap and safe to import and easy to unit-test.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
import os
|
|
12
|
+
from dataclasses import dataclass
|
|
13
|
+
|
|
14
|
+
# Public, write-only project key — the SAME PostHog project the dashboard uses
|
|
15
|
+
# (dashboard/lib/posthog-config.ts). Safe to ship in the package: it can only
|
|
16
|
+
# ingest events, not read them.
|
|
17
|
+
POSTHOG_KEY = "phc_Ac1Ww1GqKc0z1SyrRWbmatEeQdlOQIsDEEdP8l8JRgX"
|
|
18
|
+
|
|
19
|
+
# Direct ingest host. The dashboard posts to its own ``/ingest`` path and
|
|
20
|
+
# reverse-proxies to this; a CLI has no first-party origin to proxy through, so it
|
|
21
|
+
# talks to PostHog directly.
|
|
22
|
+
POSTHOG_HOST = "https://us.i.posthog.com"
|
|
23
|
+
|
|
24
|
+
# Tags every event so Cloud CLI data separates from the co-tenant "failproofai"
|
|
25
|
+
# (Enforcement) CLI in the shared Failproof AI project. REQUIRED — the analog of
|
|
26
|
+
# ``posthog.register({ product })``.
|
|
27
|
+
#
|
|
28
|
+
# This was ``"agenteye"`` before the rename. The value change splits the series in
|
|
29
|
+
# PostHog: saved insights filtered on ``product = 'agenteye'`` keep the historical
|
|
30
|
+
# rows and see nothing new. That is deliberate and the discontinuity is harmless
|
|
31
|
+
# here because ``TELEMETRY_DISABLED`` has been ``True`` since well before the
|
|
32
|
+
# rename, so no events were flowing across the boundary in either direction.
|
|
33
|
+
PRODUCT = "fp-cloud-cli"
|
|
34
|
+
|
|
35
|
+
# Master kill switch — telemetry is DISABLED for now (kept in the codebase, not removed).
|
|
36
|
+
# When the PostHog host is unreachable the send path blocks the CLI ~5s/command: the
|
|
37
|
+
# ``analytics.shutdown`` flush is bounded to 1.5s, but the client-build + first ``capture``
|
|
38
|
+
# connect attempt is not, so a blocked network stalls every command (even offline ones like
|
|
39
|
+
# ``version``). Until that path is made fully non-blocking (see the TODO in
|
|
40
|
+
# ``analytics.shutdown``), telemetry stays off. Flip this to ``False`` to re-enable; nothing
|
|
41
|
+
# else was removed, so re-enabling needs no other change.
|
|
42
|
+
TELEMETRY_DISABLED = True
|
|
43
|
+
|
|
44
|
+
_TRUTHY = {"1", "true", "yes"}
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def _truthy_env(name: str) -> bool:
|
|
48
|
+
"""Match the dashboard's ``isDisabled()`` parse: trim + lowercase, 1/true/yes."""
|
|
49
|
+
return (os.environ.get(name) or "").strip().lower() in _TRUTHY
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
@dataclass
|
|
53
|
+
class AnalyticsConfig:
|
|
54
|
+
enabled: bool
|
|
55
|
+
api_key: str
|
|
56
|
+
host: str
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def is_disabled() -> bool:
|
|
60
|
+
"""Opt-out via our own ``FP_ANALYTICS_DISABLED`` or the cross-tool ``DO_NOT_TRACK``."""
|
|
61
|
+
return _truthy_env("FP_ANALYTICS_DISABLED") or _truthy_env("DO_NOT_TRACK")
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def is_dev_or_test() -> bool:
|
|
65
|
+
"""The CLI analog of the dashboard's ``NODE_ENV === 'production'`` gate.
|
|
66
|
+
|
|
67
|
+
Keeps our own test runs and source checkouts out of the shared project. Customer
|
|
68
|
+
CI usage is intentionally **not** excluded here — that signal is wanted; CI users
|
|
69
|
+
who want out can set ``DO_NOT_TRACK``.
|
|
70
|
+
"""
|
|
71
|
+
return "PYTEST_CURRENT_TEST" in os.environ or _truthy_env("FP_CLI_DEV")
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def resolve_config() -> AnalyticsConfig:
|
|
75
|
+
"""Resolve telemetry settings now (like the dashboard's request-time check)."""
|
|
76
|
+
enabled = not TELEMETRY_DISABLED and not is_disabled() and not is_dev_or_test()
|
|
77
|
+
return AnalyticsConfig(enabled=enabled, api_key=POSTHOG_KEY, host=POSTHOG_HOST)
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
"""Derive the telemetry command/flag catalog from the assembled Typer app.
|
|
2
|
+
|
|
3
|
+
Hand-maintained allowlists drift: a new command or flag silently goes untracked
|
|
4
|
+
(lost signal) or, worse, leaks a value. Instead we walk the real Click command
|
|
5
|
+
tree once and build the catalog from it, so coverage is automatic. The anti-drift
|
|
6
|
+
test (`tests/test_telemetry_completeness.py`) asserts this stays exhaustive.
|
|
7
|
+
|
|
8
|
+
Built lazily and cached — the app module imports ``analytics``, so we cannot import
|
|
9
|
+
the app at module load without a cycle. Everything here is read-only introspection.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from __future__ import annotations
|
|
13
|
+
|
|
14
|
+
from functools import lru_cache
|
|
15
|
+
from typing import Dict, FrozenSet, List, Set, Tuple
|
|
16
|
+
|
|
17
|
+
from . import _click_compat as click # the Click Typer is running; see _click_compat
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def _option_tokens(opt: click.Parameter) -> List[str]:
|
|
21
|
+
return list(opt.opts) + list(opt.secondary_opts)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def _canonical(tokens: List[str]) -> str:
|
|
25
|
+
longs = [t for t in tokens if t.startswith("--")]
|
|
26
|
+
return longs[0] if longs else tokens[0]
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def _takes_value(opt: click.Parameter) -> bool:
|
|
30
|
+
"""True for options that consume a following token (so we can skip the value)."""
|
|
31
|
+
return not getattr(opt, "is_flag", False) and not getattr(opt, "count", False)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def _walk(
|
|
35
|
+
cmd: click.Command,
|
|
36
|
+
prefix: Tuple[str, ...],
|
|
37
|
+
known: Set[str],
|
|
38
|
+
leaves: Dict[str, Set[str]],
|
|
39
|
+
flags: Dict[str, str],
|
|
40
|
+
value_flags: Set[str],
|
|
41
|
+
) -> None:
|
|
42
|
+
for param in getattr(cmd, "params", []):
|
|
43
|
+
# `is_option`, not `isinstance(param, Option)`: Typer's vendored Click has no
|
|
44
|
+
# Option class, so an identity test silently matches nothing and this catalog
|
|
45
|
+
# comes back empty. See `_click_compat`.
|
|
46
|
+
if click.is_option(param):
|
|
47
|
+
tokens = _option_tokens(param)
|
|
48
|
+
if not tokens:
|
|
49
|
+
continue
|
|
50
|
+
canon = _canonical(tokens)
|
|
51
|
+
for tok in tokens:
|
|
52
|
+
flags[tok] = canon
|
|
53
|
+
if _takes_value(param):
|
|
54
|
+
value_flags.update(tokens)
|
|
55
|
+
subcommands = getattr(cmd, "commands", None)
|
|
56
|
+
if subcommands:
|
|
57
|
+
for name, sub in subcommands.items():
|
|
58
|
+
if not prefix:
|
|
59
|
+
known.add(name) # top-level command or group name
|
|
60
|
+
else:
|
|
61
|
+
leaves.setdefault(prefix[0], set()).add(name) # nested leaf
|
|
62
|
+
_walk(sub, prefix + (name,), known, leaves, flags, value_flags)
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
@lru_cache(maxsize=1)
|
|
66
|
+
def build() -> Tuple[FrozenSet[str], Dict[str, FrozenSet[str]], Dict[str, str], FrozenSet[str]]:
|
|
67
|
+
"""Return ``(known_commands, leaf_registry, flag_aliases, value_flags)``."""
|
|
68
|
+
from typer.main import get_command
|
|
69
|
+
|
|
70
|
+
from .app import app # lazy: avoids the app <-> analytics import cycle
|
|
71
|
+
|
|
72
|
+
cli = get_command(app)
|
|
73
|
+
known: Set[str] = set()
|
|
74
|
+
leaves: Dict[str, Set[str]] = {}
|
|
75
|
+
flags: Dict[str, str] = {}
|
|
76
|
+
value_flags: Set[str] = set()
|
|
77
|
+
_walk(cli, (), known, leaves, flags, value_flags)
|
|
78
|
+
return (
|
|
79
|
+
frozenset(known),
|
|
80
|
+
{group: frozenset(subs) for group, subs in leaves.items()},
|
|
81
|
+
dict(flags),
|
|
82
|
+
frozenset(value_flags),
|
|
83
|
+
)
|