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,492 @@
|
|
|
1
|
+
"""Typer application: global options + command registration."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import sys
|
|
6
|
+
import time
|
|
7
|
+
from typing import Optional
|
|
8
|
+
|
|
9
|
+
import typer
|
|
10
|
+
|
|
11
|
+
from . import _click_compat as click # the Click Typer is running; see _click_compat
|
|
12
|
+
from . import _context
|
|
13
|
+
from . import analytics
|
|
14
|
+
from . import config as cfgmod
|
|
15
|
+
from . import output
|
|
16
|
+
from . import orgs as orgsmod
|
|
17
|
+
from ._context import AppState
|
|
18
|
+
from ._version import __version__
|
|
19
|
+
from .commands import (
|
|
20
|
+
agent_cmds,
|
|
21
|
+
alerts_cmds,
|
|
22
|
+
audits_cmds,
|
|
23
|
+
auth_cmds,
|
|
24
|
+
errors_cmds,
|
|
25
|
+
evals_cmds,
|
|
26
|
+
events_cmds,
|
|
27
|
+
fleet_cmds,
|
|
28
|
+
guardrails_cmds,
|
|
29
|
+
incidents_cmds,
|
|
30
|
+
keys_cmds,
|
|
31
|
+
list_cmds,
|
|
32
|
+
orgs_cmds,
|
|
33
|
+
policies_cmds,
|
|
34
|
+
queries_cmds,
|
|
35
|
+
sessions_cmds,
|
|
36
|
+
settings_cmds,
|
|
37
|
+
usage_cmds,
|
|
38
|
+
users_cmds,
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
_HELP = """\
|
|
42
|
+
Query FailproofAI Cloud — **sessions, events, and evaluation results** — from your terminal.
|
|
43
|
+
|
|
44
|
+
Every data command accepts `--json` for stable, machine-readable output: data on **stdout**, and
|
|
45
|
+
on failure a `{"error": …, "exit_code": …}` object on **stdout** too; human status/progress goes to
|
|
46
|
+
**stderr**. So it is safe to script and easy for AI agents to parse.
|
|
47
|
+
|
|
48
|
+
**Getting started**
|
|
49
|
+
|
|
50
|
+
* `fp --base-url https://cloud.example.com login` — email a one-time code, then paste it
|
|
51
|
+
* `fp whoami` — confirm who you are and your permissions
|
|
52
|
+
* `fp --json sessions --since 24h` — run a query (results are newest-first)
|
|
53
|
+
|
|
54
|
+
**Global options come before the command.** `fp --json --base-url URL events` is correct;
|
|
55
|
+
`fp events --json` is not. The globals are `--json`, `--base-url`, `--org`, `--token`,
|
|
56
|
+
`--api-key`, `--insecure`/`--secure`, `--timeout`, `--quiet`, and `--no-color`.
|
|
57
|
+
|
|
58
|
+
**In CI, authenticate with an API key** — `--api-key <key>` or `FP_API_KEY` — instead of
|
|
59
|
+
a session. The key is never written to disk, and the commands that need a *human* session
|
|
60
|
+
(`login`, `logout`, `orgs`, `agent`, `keys update`, and the whole `policies`/`fleet`/`guardrails`
|
|
61
|
+
group) exit `2` rather than half-run. Pass `--org
|
|
62
|
+
<slug>` if the key can act for more than one org; nothing is inherited from a saved login.
|
|
63
|
+
|
|
64
|
+
**Destructive commands confirm only on a terminal.** Anything that creates, updates or
|
|
65
|
+
deletes prompts before acting when it is attached to a TTY — but under `--json`, or with
|
|
66
|
+
stdin redirected, it proceeds **without asking**, so scripts and agents never hang on a
|
|
67
|
+
prompt nobody can answer. Pass `--yes`/`-y` to say so explicitly, and prefer it in
|
|
68
|
+
automation: it makes the intent visible at the call site instead of resting on how stdin
|
|
69
|
+
happened to be wired.
|
|
70
|
+
|
|
71
|
+
**Multi-tenant:** if you belong to more than one org, pick the active tenant at login
|
|
72
|
+
(`fp login --org <slug>`) or per command with `--org <slug>` (or `FP_ORG`).
|
|
73
|
+
`fp orgs` lists the orgs you can access; `fp orgs switch <slug>` sets the default.
|
|
74
|
+
|
|
75
|
+
**Exit codes** (stable, for scripting): `0` success · `1` unexpected error (e.g. an unhandled
|
|
76
|
+
server status) · `2` usage error · `3` cannot reach the dashboard · `4` not signed in or session
|
|
77
|
+
expired (run `fp login`) · `5` missing the required permission · `6` resource not found.
|
|
78
|
+
|
|
79
|
+
Run `fp COMMAND -h` (or `--help`) for each command's filters, value formats, and JSON shape.
|
|
80
|
+
"""
|
|
81
|
+
|
|
82
|
+
# Each example is its own paragraph (blank line between) on purpose: Typer collapses single
|
|
83
|
+
# newlines within an epilog paragraph into spaces, which would mash a normal Markdown list onto
|
|
84
|
+
# one line. Paragraph breaks survive as the single newlines Markdown needs for a clean list.
|
|
85
|
+
_EPILOG = """\
|
|
86
|
+
**Examples**
|
|
87
|
+
|
|
88
|
+
* `fp --json sessions --since 7d --status error` — runs that errored in the past week
|
|
89
|
+
|
|
90
|
+
* `fp --json evals --score helpfulness:..0.5` — evaluations scoring ≤ 0.5 on helpfulness
|
|
91
|
+
|
|
92
|
+
* `fp evals --aggregate --since 7d --env prod` — rolled-up eval health for the last week in prod
|
|
93
|
+
|
|
94
|
+
* `fp --json errors --since 24h --env prod` — error summary for the last day in prod
|
|
95
|
+
|
|
96
|
+
* `fp --json events --session-id run-001 --all | jq '.events[].payload'` — every event payload for one session, ready to pipe into a script
|
|
97
|
+
"""
|
|
98
|
+
|
|
99
|
+
# -h is an alias for --help everywhere (Click reads this from the context).
|
|
100
|
+
_CTX = {"help_option_names": ["-h", "--help"]}
|
|
101
|
+
|
|
102
|
+
app = typer.Typer(
|
|
103
|
+
no_args_is_help=True,
|
|
104
|
+
add_completion=False,
|
|
105
|
+
rich_markup_mode="markdown",
|
|
106
|
+
context_settings=_CTX,
|
|
107
|
+
help=_HELP,
|
|
108
|
+
epilog=_EPILOG,
|
|
109
|
+
# Typer renders local variables into its pretty traceback, and the frames on
|
|
110
|
+
# any escaped exception hold `ClientContext(token=…, api_key=…)` and
|
|
111
|
+
# `CliConfig(session_token=…)`. The default is True for every typer from
|
|
112
|
+
# 0.13.0 to 0.23.x — the whole declared range below 0.24 — so an ordinary
|
|
113
|
+
# unhandled server-shape error dumped the session token onto stderr, into
|
|
114
|
+
# terminal scrollback, CI job logs and pasted bug reports. Verified on
|
|
115
|
+
# 0.13.0: `fp whoami` against a malformed `/api/keys` body printed the token
|
|
116
|
+
# three times. This package goes to real lengths to protect that credential
|
|
117
|
+
# (0600 file, atomic rename, symlink refusal, an allowlisted telemetry
|
|
118
|
+
# payload) and this bypassed all of it. Set explicitly rather than relying on
|
|
119
|
+
# a library default for a security property, since `uv.lock` happening to
|
|
120
|
+
# pin 0.27.1 is the only reason CI never saw it.
|
|
121
|
+
pretty_exceptions_show_locals=False,
|
|
122
|
+
)
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
# Render every ClickException as a clean one-line error (`✗ message`) instead of
|
|
126
|
+
# Typer's heavy red panel, so failures match the rest of the CLI's line aesthetic
|
|
127
|
+
# (`✓` / `○` / `›`). Usage errors keep a `try '… -h'` nudge. Patches the single
|
|
128
|
+
# function Typer calls for this; falls back silently if its internals change.
|
|
129
|
+
try: # pragma: no cover - exercised via real command output, not unit assertions
|
|
130
|
+
import typer.rich_utils as _rich_utils
|
|
131
|
+
|
|
132
|
+
import re as _re
|
|
133
|
+
|
|
134
|
+
from .errors import FpCliError as _FpCliError
|
|
135
|
+
|
|
136
|
+
# Global options live BEFORE the command; putting one after it ("sessions --json")
|
|
137
|
+
# is the single most common mistake, so detect it and nudge toward the right form.
|
|
138
|
+
_GLOBAL_FLAGS = frozenset({
|
|
139
|
+
"--json", "--base-url", "--org", "--token", "--api-key", "--timeout",
|
|
140
|
+
"--quiet", "--no-color", "--insecure", "--secure",
|
|
141
|
+
})
|
|
142
|
+
|
|
143
|
+
def _flag_placement_hint(message: str) -> "Optional[str]":
|
|
144
|
+
m = _re.search(r"No such option:? '?(--[a-z-]+)'?", message)
|
|
145
|
+
if m and m.group(1) in _GLOBAL_FLAGS:
|
|
146
|
+
return "global options go before the command, e.g. 'fp --json <command>'"
|
|
147
|
+
return None
|
|
148
|
+
|
|
149
|
+
def _wants_json() -> bool:
|
|
150
|
+
# output.is_json() is set by the group callback. Errors raised BEFORE it runs (an unknown
|
|
151
|
+
# command, a bad global option) never reached configure(), so fall back to the raw flag/env
|
|
152
|
+
# — otherwise `fp --json <typo>` would print a human box instead of a JSON envelope.
|
|
153
|
+
if output.is_json():
|
|
154
|
+
return True
|
|
155
|
+
import os
|
|
156
|
+
if os.environ.get("FP_JSON", "").strip().lower() in ("1", "true", "yes", "on"):
|
|
157
|
+
return True
|
|
158
|
+
return "--json" in sys.argv
|
|
159
|
+
|
|
160
|
+
def _format_error_as_line(error: click.ClickException) -> None:
|
|
161
|
+
# Our typed errors carry a clean message; the HTTP status (for ApiError) rides in the JSON
|
|
162
|
+
# envelope's `status` field instead of being appended to the human text. Other Click
|
|
163
|
+
# exceptions (usage etc.) use their normal rendering.
|
|
164
|
+
if isinstance(error, _FpCliError):
|
|
165
|
+
message = error.message
|
|
166
|
+
else:
|
|
167
|
+
message = error.format_message() if hasattr(error, "format_message") else str(error)
|
|
168
|
+
# Click/Typer sometimes doubles the suggestion ("Did you mean 'x'? Did you
|
|
169
|
+
# mean 'x'?"); collapse the repeat so the one-line error stays clean.
|
|
170
|
+
message = _re.sub(r"(Did you mean [^?]+\?)(\s*\1)+", r"\1", message)
|
|
171
|
+
# A bare group invocation (`fp`, `fp keys`) already printed its help via
|
|
172
|
+
# no_args_is_help; Click then raises a message-less UsageError. Don't render an empty
|
|
173
|
+
# "✗" box (or an `{"error": ""}` envelope) on top of the help — just let Click exit.
|
|
174
|
+
if not message.strip():
|
|
175
|
+
return
|
|
176
|
+
# Hint precedence: an explicit hint on the exception > the global-flag-placement
|
|
177
|
+
# nudge (more useful than the generic -h line) > the usage "try '… -h'" fallback.
|
|
178
|
+
hint = getattr(error, "hint", None)
|
|
179
|
+
if hint is None:
|
|
180
|
+
hint = _flag_placement_hint(message)
|
|
181
|
+
ctx = getattr(error, "ctx", None)
|
|
182
|
+
if hint is None and isinstance(error, click.UsageError) and ctx is not None:
|
|
183
|
+
hint = f"try '{ctx.command_path} -h' for help"
|
|
184
|
+
# Under --json, every failure is a machine-readable object on stdout (data channel),
|
|
185
|
+
# mirroring the success contract — never a Rich box. Click then exits with the same code.
|
|
186
|
+
if _wants_json():
|
|
187
|
+
code = getattr(error, "exit_code", None)
|
|
188
|
+
if not isinstance(code, int):
|
|
189
|
+
code = 2 if isinstance(error, click.UsageError) else 1
|
|
190
|
+
payload = {"error": message, "exit_code": code}
|
|
191
|
+
status = getattr(error, "status", None)
|
|
192
|
+
if isinstance(status, int):
|
|
193
|
+
payload["status"] = status
|
|
194
|
+
# Keep the server's request-id (set on ApiError) so an agent can correlate a failure
|
|
195
|
+
# with server logs — it's no longer appended to the human message.
|
|
196
|
+
request_id = getattr(error, "request_id", None)
|
|
197
|
+
if request_id:
|
|
198
|
+
payload["request_id"] = request_id
|
|
199
|
+
if hint:
|
|
200
|
+
payload["hint"] = hint
|
|
201
|
+
output.emit_json(payload)
|
|
202
|
+
else:
|
|
203
|
+
output.cli_error(message, hint=hint)
|
|
204
|
+
|
|
205
|
+
_rich_utils.rich_format_error = _format_error_as_line
|
|
206
|
+
|
|
207
|
+
# Replace the TOP-LEVEL help (bare `fp`, `fp --help`/`-h`) with the grouped
|
|
208
|
+
# commands screen; every subcommand's `--help` keeps Typer's default rendering. All three
|
|
209
|
+
# top-level paths flow through `rich_format_help`, so one override covers them.
|
|
210
|
+
_orig_rich_format_help = _rich_utils.rich_format_help
|
|
211
|
+
|
|
212
|
+
def _format_help(*, obj, ctx, markup_mode): # noqa: ANN001
|
|
213
|
+
if getattr(ctx, "parent", None) is None: # the root `fp` command
|
|
214
|
+
output.render_top_level_help()
|
|
215
|
+
else:
|
|
216
|
+
_orig_rich_format_help(obj=obj, ctx=ctx, markup_mode=markup_mode)
|
|
217
|
+
|
|
218
|
+
_rich_utils.rich_format_help = _format_help
|
|
219
|
+
except Exception:
|
|
220
|
+
pass
|
|
221
|
+
|
|
222
|
+
|
|
223
|
+
def _version_callback(value: bool) -> None:
|
|
224
|
+
if value:
|
|
225
|
+
print(__version__)
|
|
226
|
+
raise typer.Exit()
|
|
227
|
+
|
|
228
|
+
|
|
229
|
+
def _same_origin(a: str, b: str) -> bool:
|
|
230
|
+
"""Same scheme+host+port, ignoring path and trailing slashes.
|
|
231
|
+
|
|
232
|
+
Used to decide whether a saved `--insecure` applies to the URL in front of us.
|
|
233
|
+
A string compare would treat `https://dash.internal` and
|
|
234
|
+
`https://dash.internal/` as different hosts and re-prompt for no reason.
|
|
235
|
+
"""
|
|
236
|
+
from urllib.parse import urlsplit
|
|
237
|
+
|
|
238
|
+
try:
|
|
239
|
+
pa, pb = urlsplit((a or "").strip()), urlsplit((b or "").strip())
|
|
240
|
+
except ValueError:
|
|
241
|
+
return False
|
|
242
|
+
return bool(pa.scheme) and (pa.scheme, pa.netloc) == (pb.scheme, pb.netloc)
|
|
243
|
+
|
|
244
|
+
|
|
245
|
+
def _from_command_line(ctx: typer.Context, param: str) -> bool:
|
|
246
|
+
"""True when ``param`` was typed on the command line (not read from its env var).
|
|
247
|
+
|
|
248
|
+
`--api-key` and `--token` have DIFFERENT precedence as flags than as env vars, and
|
|
249
|
+
by the time Typer hands us the value the two are indistinguishable. Click records
|
|
250
|
+
the source; ask it. Defaults to False if a Click without the API is ever in play,
|
|
251
|
+
which degrades to "treat it as env" — the conservative direction, since the only
|
|
252
|
+
thing it can cost is the both-flags usage error, never a wrong credential.
|
|
253
|
+
"""
|
|
254
|
+
try:
|
|
255
|
+
source = ctx.get_parameter_source(param)
|
|
256
|
+
except Exception: # pragma: no cover - only if Click drops the API
|
|
257
|
+
return False
|
|
258
|
+
return getattr(source, "name", None) == "COMMANDLINE"
|
|
259
|
+
|
|
260
|
+
|
|
261
|
+
@app.callback()
|
|
262
|
+
def main(
|
|
263
|
+
ctx: typer.Context,
|
|
264
|
+
json_output: bool = typer.Option(
|
|
265
|
+
False, "--json", envvar="FP_JSON", help="Emit JSON to stdout instead of a table."
|
|
266
|
+
),
|
|
267
|
+
base_url: Optional[str] = typer.Option(
|
|
268
|
+
None,
|
|
269
|
+
"--base-url",
|
|
270
|
+
envvar="FP_DASHBOARD_URL",
|
|
271
|
+
metavar="URL",
|
|
272
|
+
help="Dashboard base URL. Defaults to https://app.befailproof.ai; "
|
|
273
|
+
"override for a self-hosted or dev instance (or set FP_DASHBOARD_URL).",
|
|
274
|
+
),
|
|
275
|
+
org: Optional[str] = typer.Option(
|
|
276
|
+
None,
|
|
277
|
+
"--org",
|
|
278
|
+
envvar="FP_ORG",
|
|
279
|
+
metavar="SLUG",
|
|
280
|
+
help="Active org/tenant slug. Required if you belong to more than one org; "
|
|
281
|
+
"set it once at login (`login --org <slug>`) or here per command.",
|
|
282
|
+
),
|
|
283
|
+
token: Optional[str] = typer.Option(
|
|
284
|
+
None, "--token", envvar="FP_TOKEN", help="Session token override (for CI/agents)."
|
|
285
|
+
),
|
|
286
|
+
# `FP_API_KEY`, deliberately NOT the two names that already exist:
|
|
287
|
+
# * `AGENTEYE_KEY` is the collector's INGEST key, normally `events:add` only —
|
|
288
|
+
# picking it up here would make every read command 403 for no visible reason.
|
|
289
|
+
# * `AGENTEYE_API_KEY` is the dashboard service's own admin-grade key. Silently
|
|
290
|
+
# promoting an operator credential to "the CLI's identity" is a privilege
|
|
291
|
+
# surprise, and on a dashboard host both variables are typically already set.
|
|
292
|
+
# It also keeps the CLI's own `FP_TOKEN` / `FP_JSON` namespace.
|
|
293
|
+
api_key: Optional[str] = typer.Option(
|
|
294
|
+
None,
|
|
295
|
+
"--api-key",
|
|
296
|
+
envvar="FP_API_KEY",
|
|
297
|
+
metavar="KEY",
|
|
298
|
+
help="Authenticate as an API key against the versioned API instead of a user "
|
|
299
|
+
"session (for CI). Wins over --token's env var; passing both flags is an error. "
|
|
300
|
+
"Never saved to disk.",
|
|
301
|
+
),
|
|
302
|
+
timeout: float = typer.Option(30.0, "--timeout", help="HTTP timeout in seconds."),
|
|
303
|
+
no_color: bool = typer.Option(
|
|
304
|
+
False, "--no-color", envvar="NO_COLOR", help="Disable coloured output."
|
|
305
|
+
),
|
|
306
|
+
quiet: bool = typer.Option(False, "--quiet", "-q", help="Suppress status messages on stderr."),
|
|
307
|
+
insecure: Optional[bool] = typer.Option(
|
|
308
|
+
None,
|
|
309
|
+
"--insecure/--secure",
|
|
310
|
+
envvar="FP_INSECURE",
|
|
311
|
+
help="Skip TLS certificate verification (for self-signed/internal dashboards). "
|
|
312
|
+
"Saved at login and honoured only for the base URL it was saved for; "
|
|
313
|
+
"--secure disables it for this invocation (pass it to `login` to save that).",
|
|
314
|
+
),
|
|
315
|
+
version: bool = typer.Option(
|
|
316
|
+
False, "--version", callback=_version_callback, is_eager=True, hidden=True,
|
|
317
|
+
help="Show version and exit.",
|
|
318
|
+
),
|
|
319
|
+
) -> None:
|
|
320
|
+
"""Resolve global options into the per-invocation AppState (flag > env > config)."""
|
|
321
|
+
if timeout <= 0:
|
|
322
|
+
raise click.BadParameter(
|
|
323
|
+
"must be a positive number of seconds.", param_hint="--timeout"
|
|
324
|
+
)
|
|
325
|
+
cfg = cfgmod.load_config()
|
|
326
|
+
output.configure(no_color=no_color, quiet=quiet, json=json_output)
|
|
327
|
+
# Resolve the credential BEFORE telemetry starts: the distinct-id depends on it.
|
|
328
|
+
# `get_parameter_source` is what separates a flag from its env var — the values
|
|
329
|
+
# alone cannot, and the precedence rules differ between the two (see resolve_auth).
|
|
330
|
+
auth_mode, resolved_api_key, resolved_token = _context.resolve_auth(
|
|
331
|
+
api_key=api_key,
|
|
332
|
+
api_key_on_cli=_from_command_line(ctx, "api_key"),
|
|
333
|
+
token=token,
|
|
334
|
+
token_on_cli=_from_command_line(ctx, "token"),
|
|
335
|
+
saved_token=cfg.session_token,
|
|
336
|
+
)
|
|
337
|
+
# In key mode force the ANONYMOUS distinct id. `_resolve_distinct_id` reads the
|
|
338
|
+
# SAVED config, so a CI box (or a laptop) where a human happens to be logged in
|
|
339
|
+
# would otherwise attribute every key-mode command to that person — an identity
|
|
340
|
+
# the key has nothing to do with.
|
|
341
|
+
analytics.init_analytics(cfg, force_anonymous=auth_mode is _context.AuthMode.API_KEY)
|
|
342
|
+
analytics.note_command(ctx.invoked_subcommand, json_output, auth_mode=auth_mode)
|
|
343
|
+
# Active tenant: flag > FP_ORG env > saved config. Validate the shape early
|
|
344
|
+
# so a typo is a clean usage error rather than a confusing server rejection.
|
|
345
|
+
resolved_org = org or cfg.org
|
|
346
|
+
if resolved_org and not orgsmod.is_valid_org_slug(resolved_org):
|
|
347
|
+
raise click.BadParameter(
|
|
348
|
+
f"'{resolved_org}' is not a valid org slug "
|
|
349
|
+
"(lowercase letters, digits and single hyphens).",
|
|
350
|
+
param_hint="--org",
|
|
351
|
+
)
|
|
352
|
+
# Base-URL resolution: explicit flag/env > saved config > the public default
|
|
353
|
+
# (`config.DEFAULT_BASE_URL`). An explicit empty string (`--base-url ""`, e.g.
|
|
354
|
+
# an unset CI var) is treated as "unset" and falls through, so a script never
|
|
355
|
+
# errors for lack of a URL — it lands on the hosted product. `or` (not
|
|
356
|
+
# `is not None`) is deliberate here: unlike `--token`, an empty base-url is a
|
|
357
|
+
# public endpoint choice, not an identity, so collapsing "" to the default is
|
|
358
|
+
# safe.
|
|
359
|
+
resolved_base = (base_url or None) or cfg.base_url or cfgmod.DEFAULT_BASE_URL
|
|
360
|
+
# A base-url without a scheme breaks httpx's cookie handling with a raw urllib
|
|
361
|
+
# ValueError (an uncaught traceback) — reject it up front as a clean usage error,
|
|
362
|
+
# mirroring the --org shape check above. (The default always has a scheme, so
|
|
363
|
+
# this only ever fires on a user-supplied value.)
|
|
364
|
+
if not resolved_base.lower().startswith(("http://", "https://")):
|
|
365
|
+
raise click.BadParameter(
|
|
366
|
+
f"'{resolved_base}' must start with http:// or https://.",
|
|
367
|
+
param_hint="--base-url",
|
|
368
|
+
)
|
|
369
|
+
# TLS verification, resolved AFTER the base URL because the saved preference
|
|
370
|
+
# is only honoured for the URL it was granted for.
|
|
371
|
+
#
|
|
372
|
+
# `insecure` is stored per MACHINE and was applied to every base URL, so one
|
|
373
|
+
# `fp --base-url https://dash.internal --insecure login` against a self-signed
|
|
374
|
+
# dev box — the use its own help text names — left verification off for the
|
|
375
|
+
# next `fp --base-url https://app.befailproof.ai login`, which is the request
|
|
376
|
+
# that carries the OTP and receives the session token, and for `fp keys
|
|
377
|
+
# create`, which carries a freshly minted API key in the body. `clear_token`
|
|
378
|
+
# deliberately preserves `insecure` across a logout, so it outlived the
|
|
379
|
+
# session it was set for. An explicit flag still wins, for both values.
|
|
380
|
+
if insecure is not None:
|
|
381
|
+
resolved_insecure = insecure
|
|
382
|
+
elif cfg.insecure and _same_origin(resolved_base, cfg.base_url):
|
|
383
|
+
resolved_insecure = True
|
|
384
|
+
else:
|
|
385
|
+
resolved_insecure = False
|
|
386
|
+
if cfg.insecure:
|
|
387
|
+
output.cli_warn(
|
|
388
|
+
f"⚠ ignoring the saved --insecure preference: it was granted for "
|
|
389
|
+
f"{cfg.base_url}, not {resolved_base}. Pass --insecure explicitly "
|
|
390
|
+
f"if you mean it here too."
|
|
391
|
+
)
|
|
392
|
+
if resolved_insecure and ctx.invoked_subcommand not in ("version", "help", None):
|
|
393
|
+
# NOT `output.warn`, which `--quiet` suppresses along with cosmetic status
|
|
394
|
+
# chrome. This one is a security property: `fp --quiet …` on a machine
|
|
395
|
+
# where someone once ran `login --insecure` performed every request with
|
|
396
|
+
# verification off and said nothing at all.
|
|
397
|
+
output.cli_warn("⚠ TLS verification disabled (--insecure).")
|
|
398
|
+
# `resolve_auth` above already applied the whole precedence ladder, including the
|
|
399
|
+
# rule that an EXPLICIT empty `--token ""` / `--api-key ""` (e.g. an unset CI var)
|
|
400
|
+
# means "no override" and must NOT silently fall back to the saved session — so a
|
|
401
|
+
# script can't unknowingly act as the stored identity. `base_url` intentionally
|
|
402
|
+
# differs (see `resolved_base` above): an empty URL is a public endpoint, not an
|
|
403
|
+
# identity, so it defaults.
|
|
404
|
+
#
|
|
405
|
+
# `api_key` is carried on the in-memory AppState ONLY. `CliConfig` has no field for
|
|
406
|
+
# it and must not grow one: a session token expires in ~24h, which bounds a leaked
|
|
407
|
+
# `cli.json`; an API key is valid until revoked, and nothing here could revoke it
|
|
408
|
+
# (`keys disable` needs `keys:disable`, which a scoped CI key will not hold).
|
|
409
|
+
ctx.obj = AppState(
|
|
410
|
+
json=json_output,
|
|
411
|
+
base_url=resolved_base,
|
|
412
|
+
token=resolved_token,
|
|
413
|
+
timeout=timeout,
|
|
414
|
+
config=cfg,
|
|
415
|
+
insecure=resolved_insecure,
|
|
416
|
+
org=resolved_org,
|
|
417
|
+
# `org` here is the global --org flag or FP_ORG env (None if neither) —
|
|
418
|
+
# the explicit choice, distinct from the saved-config fallback in `resolved_org`.
|
|
419
|
+
# Key mode sends ONLY this one (see `_context._org_header`).
|
|
420
|
+
org_explicit=org,
|
|
421
|
+
api_key=resolved_api_key,
|
|
422
|
+
auth_mode=auth_mode,
|
|
423
|
+
)
|
|
424
|
+
|
|
425
|
+
|
|
426
|
+
@app.command()
|
|
427
|
+
def version(ctx: typer.Context) -> None:
|
|
428
|
+
"""Show the CLI version in a small branded box.
|
|
429
|
+
|
|
430
|
+
Follows the global option format — the **global** `--json` (before the command) makes it
|
|
431
|
+
emit `{"version": "<x.y.z>"}` and nothing else: `fp --json version`. For a bare,
|
|
432
|
+
unboxed string use `fp --version`.
|
|
433
|
+
"""
|
|
434
|
+
if getattr(ctx.obj, "json", False):
|
|
435
|
+
output.emit_json({"version": __version__})
|
|
436
|
+
else:
|
|
437
|
+
output.version_banner(__version__)
|
|
438
|
+
|
|
439
|
+
|
|
440
|
+
@app.command("help")
|
|
441
|
+
def help_cmd(ctx: typer.Context) -> None:
|
|
442
|
+
"""Show this help and the available commands."""
|
|
443
|
+
output.render_top_level_help()
|
|
444
|
+
|
|
445
|
+
|
|
446
|
+
auth_cmds.register(app)
|
|
447
|
+
orgs_cmds.register(app)
|
|
448
|
+
events_cmds.register(app)
|
|
449
|
+
sessions_cmds.register(app)
|
|
450
|
+
evals_cmds.register(app)
|
|
451
|
+
errors_cmds.register(app)
|
|
452
|
+
usage_cmds.register(app)
|
|
453
|
+
list_cmds.register(app)
|
|
454
|
+
keys_cmds.register(app)
|
|
455
|
+
queries_cmds.register(app)
|
|
456
|
+
users_cmds.register(app)
|
|
457
|
+
settings_cmds.register(app)
|
|
458
|
+
alerts_cmds.register(app)
|
|
459
|
+
audits_cmds.register(app)
|
|
460
|
+
incidents_cmds.register(app)
|
|
461
|
+
agent_cmds.register(app)
|
|
462
|
+
policies_cmds.register(app)
|
|
463
|
+
fleet_cmds.register(app)
|
|
464
|
+
guardrails_cmds.register(app)
|
|
465
|
+
|
|
466
|
+
|
|
467
|
+
def _elapsed_ms(start: float) -> int:
|
|
468
|
+
return int((time.monotonic() - start) * 1000)
|
|
469
|
+
|
|
470
|
+
|
|
471
|
+
def main_entry() -> None:
|
|
472
|
+
"""Console-script entry point: run the CLI, then emit one telemetry event.
|
|
473
|
+
|
|
474
|
+
Wraps ``app()`` so a single ``command_executed`` event (carrying the exit code and
|
|
475
|
+
duration) is captured however the command ends, then flushed. The original exit
|
|
476
|
+
code is preserved exactly — standalone Click has already printed any error and
|
|
477
|
+
raised ``SystemExit`` with the right status (see ``errors.py``). Tests invoke
|
|
478
|
+
``app`` directly via ``CliRunner``, so they bypass this wrapper entirely.
|
|
479
|
+
"""
|
|
480
|
+
start = time.monotonic()
|
|
481
|
+
code = 0
|
|
482
|
+
try:
|
|
483
|
+
app()
|
|
484
|
+
except SystemExit as exc: # normal path: Click exits with its status code
|
|
485
|
+
code = exc.code if isinstance(exc.code, int) else (0 if exc.code is None else 1)
|
|
486
|
+
except BaseException: # escaped Click (e.g. KeyboardInterrupt): record, then re-raise unchanged
|
|
487
|
+
analytics.capture_command(1, _elapsed_ms(start), sys.argv[1:])
|
|
488
|
+
analytics.shutdown()
|
|
489
|
+
raise
|
|
490
|
+
analytics.capture_command(code, _elapsed_ms(start), sys.argv[1:])
|
|
491
|
+
analytics.shutdown()
|
|
492
|
+
sys.exit(code)
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
"""Email-OTP device login against the dashboard.
|
|
2
|
+
|
|
3
|
+
Flow: ``/api/auth/otp/request`` (sends a code) then ``/api/auth/otp/verify``.
|
|
4
|
+
The verify response carries the session token in the ``ae_session`` **Set-Cookie**
|
|
5
|
+
header (not the JSON body), so we read it from ``response.cookies``.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
from datetime import datetime, timedelta, timezone
|
|
11
|
+
from typing import Any, Dict, Optional, Tuple
|
|
12
|
+
|
|
13
|
+
import httpx
|
|
14
|
+
|
|
15
|
+
from .config import CliConfig, save_config
|
|
16
|
+
from .errors import ApiError, AuthError, NetworkError
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def _iso(dt: datetime) -> str:
|
|
20
|
+
return dt.astimezone(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ")
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def _network_error(base_url: str, exc: Exception) -> NetworkError:
|
|
24
|
+
return NetworkError(f"Cannot reach FailproofAI Cloud at {base_url}: {exc}")
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def request_otp(
|
|
28
|
+
base_url: str,
|
|
29
|
+
email: str,
|
|
30
|
+
*,
|
|
31
|
+
timeout: float = 30.0,
|
|
32
|
+
transport: Optional[httpx.BaseTransport] = None,
|
|
33
|
+
verify: bool = True,
|
|
34
|
+
) -> None:
|
|
35
|
+
"""Ask the dashboard to email a login code. Always succeeds quietly for a
|
|
36
|
+
valid request (the server returns 200 even for unknown emails)."""
|
|
37
|
+
try:
|
|
38
|
+
with httpx.Client(
|
|
39
|
+
base_url=base_url.rstrip("/"), timeout=timeout, transport=transport, verify=verify
|
|
40
|
+
) as client:
|
|
41
|
+
# Mark this as a CLI login so the server emails the paste-into-terminal
|
|
42
|
+
# OTP template (with no "open the dashboard" button) instead of the
|
|
43
|
+
# browser one.
|
|
44
|
+
response = client.post(
|
|
45
|
+
"/api/auth/otp/request",
|
|
46
|
+
json={"email": email},
|
|
47
|
+
headers={"X-AgentEye-Client": "cli"},
|
|
48
|
+
)
|
|
49
|
+
except httpx.RequestError as exc:
|
|
50
|
+
raise _network_error(base_url, exc)
|
|
51
|
+
if response.status_code >= 400:
|
|
52
|
+
raise ApiError(
|
|
53
|
+
f"Failed to request a login code (HTTP {response.status_code}).",
|
|
54
|
+
status=response.status_code,
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def verify_otp(
|
|
59
|
+
base_url: str,
|
|
60
|
+
email: str,
|
|
61
|
+
code: str,
|
|
62
|
+
*,
|
|
63
|
+
timeout: float = 30.0,
|
|
64
|
+
transport: Optional[httpx.BaseTransport] = None,
|
|
65
|
+
verify: bool = True,
|
|
66
|
+
) -> Tuple[str, int, Dict[str, Any]]:
|
|
67
|
+
"""Exchange the code for a session token. Returns ``(token, expires_in_secs, user)``."""
|
|
68
|
+
try:
|
|
69
|
+
with httpx.Client(
|
|
70
|
+
base_url=base_url.rstrip("/"), timeout=timeout, transport=transport, verify=verify
|
|
71
|
+
) as client:
|
|
72
|
+
response = client.post(
|
|
73
|
+
"/api/auth/otp/verify", json={"email": email, "code": code}
|
|
74
|
+
)
|
|
75
|
+
except httpx.RequestError as exc:
|
|
76
|
+
raise _network_error(base_url, exc)
|
|
77
|
+
|
|
78
|
+
if response.status_code == 401:
|
|
79
|
+
raise AuthError("That code didn't match or has expired. Sign in again with fp login.")
|
|
80
|
+
if response.status_code == 429:
|
|
81
|
+
raise ApiError(
|
|
82
|
+
"Too many attempts — wait a bit, then run fp login again.",
|
|
83
|
+
status=429,
|
|
84
|
+
)
|
|
85
|
+
if response.status_code >= 400:
|
|
86
|
+
# The dashboard proxy collapses the server's wrong/expired-code 401 into a 500 (its
|
|
87
|
+
# `await res.json()` throws on the server's empty 401 body). So a non-401 4xx/5xx at the
|
|
88
|
+
# verify step is, in practice, a bad/expired code — surface it as a clean auth failure,
|
|
89
|
+
# not a raw "HTTP 500". (Real unreachability is a NetworkError, handled above.)
|
|
90
|
+
raise AuthError("That code didn't match or has expired. Sign in again with fp login.")
|
|
91
|
+
|
|
92
|
+
token = response.cookies.get("ae_session")
|
|
93
|
+
if not token:
|
|
94
|
+
raise AuthError("The dashboard did not return a session token.")
|
|
95
|
+
|
|
96
|
+
try:
|
|
97
|
+
body = response.json()
|
|
98
|
+
except Exception:
|
|
99
|
+
body = {}
|
|
100
|
+
if not isinstance(body, dict):
|
|
101
|
+
body = {}
|
|
102
|
+
|
|
103
|
+
try:
|
|
104
|
+
expires_in = int(body.get("expires_in_secs"))
|
|
105
|
+
except (TypeError, ValueError):
|
|
106
|
+
expires_in = 86400
|
|
107
|
+
|
|
108
|
+
user = body.get("user") or {}
|
|
109
|
+
if not isinstance(user, dict):
|
|
110
|
+
user = {}
|
|
111
|
+
|
|
112
|
+
return token, expires_in, user
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
def persist_session(
|
|
116
|
+
cfg: CliConfig,
|
|
117
|
+
base_url: str,
|
|
118
|
+
token: str,
|
|
119
|
+
expires_in_secs: int,
|
|
120
|
+
user: Dict[str, Any],
|
|
121
|
+
*,
|
|
122
|
+
insecure: bool = False,
|
|
123
|
+
org: Optional[str] = None,
|
|
124
|
+
now: Optional[datetime] = None,
|
|
125
|
+
) -> CliConfig:
|
|
126
|
+
now = now or datetime.now(timezone.utc)
|
|
127
|
+
cfg.base_url = base_url
|
|
128
|
+
cfg.session_token = token
|
|
129
|
+
cfg.expires_at = _iso(now + timedelta(seconds=expires_in_secs))
|
|
130
|
+
cfg.email = (user or {}).get("email") or cfg.email
|
|
131
|
+
cfg.user_id = (user or {}).get("id") or cfg.user_id
|
|
132
|
+
cfg.insecure = insecure
|
|
133
|
+
if org is not None:
|
|
134
|
+
cfg.org = org # active tenant chosen at login
|
|
135
|
+
save_config(cfg)
|
|
136
|
+
return cfg
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
def logout(
|
|
140
|
+
base_url: str,
|
|
141
|
+
token: Optional[str],
|
|
142
|
+
*,
|
|
143
|
+
timeout: float = 30.0,
|
|
144
|
+
transport: Optional[httpx.BaseTransport] = None,
|
|
145
|
+
verify: bool = True,
|
|
146
|
+
) -> None:
|
|
147
|
+
"""Best-effort server-side session revocation; never raises."""
|
|
148
|
+
if not token:
|
|
149
|
+
return
|
|
150
|
+
try:
|
|
151
|
+
with httpx.Client(
|
|
152
|
+
base_url=base_url.rstrip("/"),
|
|
153
|
+
cookies={"ae_session": token},
|
|
154
|
+
timeout=timeout,
|
|
155
|
+
transport=transport,
|
|
156
|
+
verify=verify,
|
|
157
|
+
) as client:
|
|
158
|
+
client.post("/api/auth/logout")
|
|
159
|
+
except httpx.RequestError:
|
|
160
|
+
pass
|