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,98 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
from datetime import datetime, timezone
|
|
5
|
+
|
|
6
|
+
import httpx
|
|
7
|
+
import pytest
|
|
8
|
+
import respx
|
|
9
|
+
|
|
10
|
+
from fp_cli import auth, config
|
|
11
|
+
from fp_cli.errors import ApiError, AuthError, NetworkError
|
|
12
|
+
|
|
13
|
+
BASE = "http://dash.test"
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@respx.mock
|
|
17
|
+
def test_request_otp_ok():
|
|
18
|
+
route = respx.post(f"{BASE}/api/auth/otp/request").mock(
|
|
19
|
+
return_value=httpx.Response(200, json={"ok": True})
|
|
20
|
+
)
|
|
21
|
+
auth.request_otp(BASE, "me@test")
|
|
22
|
+
assert route.called
|
|
23
|
+
assert json.loads(route.calls.last.request.read()) == {"email": "me@test"}
|
|
24
|
+
# Marks a CLI login so the server emails the paste-into-terminal OTP variant.
|
|
25
|
+
assert route.calls.last.request.headers["X-AgentEye-Client"] == "cli"
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
@respx.mock
|
|
29
|
+
def test_request_otp_server_error_raises():
|
|
30
|
+
respx.post(f"{BASE}/api/auth/otp/request").mock(return_value=httpx.Response(502))
|
|
31
|
+
with pytest.raises(ApiError):
|
|
32
|
+
auth.request_otp(BASE, "me@test")
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
@respx.mock
|
|
36
|
+
def test_verify_otp_reads_token_from_set_cookie_not_body():
|
|
37
|
+
# The dashboard returns user + expires_in_secs in the body and the token only
|
|
38
|
+
# in the Set-Cookie header — verify we read it from the cookie.
|
|
39
|
+
respx.post(f"{BASE}/api/auth/otp/verify").mock(
|
|
40
|
+
return_value=httpx.Response(
|
|
41
|
+
200,
|
|
42
|
+
json={"user": {"id": "u1", "email": "me@test"}, "expires_in_secs": 3600},
|
|
43
|
+
headers={"set-cookie": "ae_session=tok-xyz; HttpOnly; Path=/; Max-Age=3600"},
|
|
44
|
+
)
|
|
45
|
+
)
|
|
46
|
+
token, expires_in, user = auth.verify_otp(BASE, "me@test", "123456")
|
|
47
|
+
assert token == "tok-xyz"
|
|
48
|
+
assert expires_in == 3600
|
|
49
|
+
assert user["email"] == "me@test"
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
@respx.mock
|
|
53
|
+
def test_verify_otp_wrong_code_is_auth_error():
|
|
54
|
+
respx.post(f"{BASE}/api/auth/otp/verify").mock(return_value=httpx.Response(401, json={}))
|
|
55
|
+
with pytest.raises(AuthError):
|
|
56
|
+
auth.verify_otp(BASE, "me@test", "000000")
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
@respx.mock
|
|
60
|
+
def test_verify_otp_without_cookie_is_auth_error():
|
|
61
|
+
respx.post(f"{BASE}/api/auth/otp/verify").mock(
|
|
62
|
+
return_value=httpx.Response(200, json={"user": {}, "expires_in_secs": 3600})
|
|
63
|
+
)
|
|
64
|
+
with pytest.raises(AuthError):
|
|
65
|
+
auth.verify_otp(BASE, "me@test", "123456")
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
@respx.mock
|
|
69
|
+
def test_verify_otp_network_error():
|
|
70
|
+
respx.post(f"{BASE}/api/auth/otp/verify").mock(side_effect=httpx.ConnectError("down"))
|
|
71
|
+
with pytest.raises(NetworkError):
|
|
72
|
+
auth.verify_otp(BASE, "me@test", "123456")
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def test_persist_session_computes_expiry_and_writes_0600(home):
|
|
76
|
+
cfg = config.CliConfig()
|
|
77
|
+
now = datetime(2026, 5, 25, 12, 0, 0, tzinfo=timezone.utc)
|
|
78
|
+
auth.persist_session(cfg, BASE, "tok", 3600, {"email": "me@test", "id": "u1"}, now=now)
|
|
79
|
+
|
|
80
|
+
reloaded = config.load_config()
|
|
81
|
+
assert reloaded.session_token == "tok"
|
|
82
|
+
assert reloaded.expires_at == "2026-05-25T13:00:00Z"
|
|
83
|
+
assert reloaded.email == "me@test"
|
|
84
|
+
assert reloaded.user_id == "u1"
|
|
85
|
+
assert reloaded.base_url == BASE
|
|
86
|
+
assert not config.is_expired(reloaded, now=now)
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
@respx.mock
|
|
90
|
+
def test_logout_is_best_effort_on_network_error():
|
|
91
|
+
respx.post(f"{BASE}/api/auth/logout").mock(side_effect=httpx.ConnectError("down"))
|
|
92
|
+
# Must not raise even though the server is unreachable.
|
|
93
|
+
auth.logout(BASE, "tok")
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def test_logout_noop_without_token():
|
|
97
|
+
# No registered routes — if it tried to call out, respx would complain.
|
|
98
|
+
auth.logout(BASE, None)
|
|
@@ -0,0 +1,272 @@
|
|
|
1
|
+
"""API-key auth mode: precedence, the empty-string rule, what goes on the wire, and
|
|
2
|
+
what never touches disk.
|
|
3
|
+
|
|
4
|
+
The wire assertions here are deliberately NEGATIVE as well as positive. "The bearer
|
|
5
|
+
header is set" passes just as happily when the CLI *also* attaches the operator's
|
|
6
|
+
`ae_session` cookie — which would hand a human's session to `/v1` from a CI box, and
|
|
7
|
+
no positive-only test would ever notice.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
import json
|
|
13
|
+
|
|
14
|
+
import httpx
|
|
15
|
+
import pytest
|
|
16
|
+
import respx
|
|
17
|
+
|
|
18
|
+
from fp_cli import config
|
|
19
|
+
from fp_cli._context import AppState, AuthMode, resolve_auth
|
|
20
|
+
from fp_cli.app import app
|
|
21
|
+
|
|
22
|
+
BASE = "http://dash.test"
|
|
23
|
+
KEY = "ak_live_abc123"
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def _mock_keys(mock):
|
|
27
|
+
"""Mock BOTH surfaces so a wrong-mode request is a wrong URL, not a network error."""
|
|
28
|
+
return (
|
|
29
|
+
mock.get(f"{BASE}/api/keys").mock(return_value=httpx.Response(200, json=[])),
|
|
30
|
+
mock.get(f"{BASE}/v1/keys").mock(return_value=httpx.Response(200, json=[])),
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def _run_keys_list(runner, argv, env=None):
|
|
35
|
+
"""Run `keys list` with both surfaces mocked → (result, api_route, v1_route)."""
|
|
36
|
+
with respx.mock(assert_all_called=False) as mock:
|
|
37
|
+
api_route, v1_route = _mock_keys(mock)
|
|
38
|
+
result = runner.invoke(app, [*argv, "--json", "keys", "list"], env=env or {})
|
|
39
|
+
return result, api_route, v1_route
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
# --- precedence ladder ------------------------------------------------------
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def test_api_key_flag_selects_key_mode(home, runner):
|
|
46
|
+
result, api_route, v1_route = _run_keys_list(runner, ["--base-url", BASE, "--api-key", KEY])
|
|
47
|
+
assert result.exit_code == 0, result.output
|
|
48
|
+
assert v1_route.called and not api_route.called
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def test_token_flag_beats_api_key_env(home, runner):
|
|
52
|
+
# An explicit flag outranks any env var, including the key's.
|
|
53
|
+
result, api_route, v1_route = _run_keys_list(
|
|
54
|
+
runner, ["--base-url", BASE, "--token", "tok"], env={"FP_API_KEY": KEY}
|
|
55
|
+
)
|
|
56
|
+
assert result.exit_code == 0, result.output
|
|
57
|
+
assert api_route.called and not v1_route.called
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def test_api_key_env_beats_token_env(home, runner):
|
|
61
|
+
# Between two env vars the KEY wins — the documented rung.
|
|
62
|
+
result, api_route, v1_route = _run_keys_list(
|
|
63
|
+
runner,
|
|
64
|
+
["--base-url", BASE],
|
|
65
|
+
env={"FP_API_KEY": KEY, "FP_TOKEN": "tok"},
|
|
66
|
+
)
|
|
67
|
+
assert result.exit_code == 0, result.output
|
|
68
|
+
assert v1_route.called and not api_route.called
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def test_token_env_beats_saved_session(home, runner):
|
|
72
|
+
config.save_config(config.CliConfig(base_url=BASE, session_token="saved-tok",
|
|
73
|
+
expires_at="2999-01-01T00:00:00Z"))
|
|
74
|
+
with respx.mock(assert_all_called=False) as mock:
|
|
75
|
+
api_route, _v1 = _mock_keys(mock)
|
|
76
|
+
result = runner.invoke(app, ["--json", "keys", "list"], env={"FP_TOKEN": "env-tok"})
|
|
77
|
+
assert result.exit_code == 0, result.output
|
|
78
|
+
assert "ae_session=env-tok" in api_route.calls.last.request.headers.get("cookie", "")
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def test_api_key_env_beats_saved_session(home, runner):
|
|
82
|
+
config.save_config(config.CliConfig(base_url=BASE, session_token="saved-tok",
|
|
83
|
+
expires_at="2999-01-01T00:00:00Z"))
|
|
84
|
+
result, api_route, v1_route = _run_keys_list(
|
|
85
|
+
runner, [], env={"FP_API_KEY": KEY}
|
|
86
|
+
)
|
|
87
|
+
assert result.exit_code == 0, result.output
|
|
88
|
+
assert v1_route.called and not api_route.called
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def test_saved_session_when_nothing_else(logged_in, runner):
|
|
92
|
+
result, api_route, v1_route = _run_keys_list(runner, [])
|
|
93
|
+
assert result.exit_code == 0, result.output
|
|
94
|
+
assert api_route.called and not v1_route.called
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def test_no_credential_at_all_exits_4(home, runner):
|
|
98
|
+
with respx.mock(assert_all_called=False) as mock:
|
|
99
|
+
catch_all = mock.route().mock(return_value=httpx.Response(200, json=[]))
|
|
100
|
+
result = runner.invoke(app, ["--base-url", BASE, "keys", "list"])
|
|
101
|
+
assert result.exit_code == 4, result.output
|
|
102
|
+
assert catch_all.call_count == 0 # never opened a connection
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
# --- resolve_auth, unit ------------------------------------------------------
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
@pytest.mark.parametrize(
|
|
109
|
+
"kwargs,expected",
|
|
110
|
+
[
|
|
111
|
+
# flag key, flag token, env key, env token, saved token
|
|
112
|
+
(dict(api_key=KEY, api_key_on_cli=True, token=None, token_on_cli=False, saved_token=None),
|
|
113
|
+
(AuthMode.API_KEY, KEY, None)),
|
|
114
|
+
(dict(api_key=None, api_key_on_cli=False, token="t", token_on_cli=True, saved_token=None),
|
|
115
|
+
(AuthMode.SESSION, None, "t")),
|
|
116
|
+
(dict(api_key=KEY, api_key_on_cli=False, token="t", token_on_cli=False, saved_token=None),
|
|
117
|
+
(AuthMode.API_KEY, KEY, None)),
|
|
118
|
+
(dict(api_key=None, api_key_on_cli=False, token="t", token_on_cli=False, saved_token="s"),
|
|
119
|
+
(AuthMode.SESSION, None, "t")),
|
|
120
|
+
(dict(api_key=None, api_key_on_cli=False, token=None, token_on_cli=False, saved_token="s"),
|
|
121
|
+
(AuthMode.SESSION, None, "s")),
|
|
122
|
+
(dict(api_key=None, api_key_on_cli=False, token=None, token_on_cli=False, saved_token=None),
|
|
123
|
+
(AuthMode.NONE, None, None)),
|
|
124
|
+
# `--api-key ""` is key mode with NO credential — never a fallback to the
|
|
125
|
+
# saved session (mirrors the established `--token ""` rule).
|
|
126
|
+
(dict(api_key="", api_key_on_cli=True, token=None, token_on_cli=False, saved_token="s"),
|
|
127
|
+
(AuthMode.API_KEY, "", None)),
|
|
128
|
+
],
|
|
129
|
+
)
|
|
130
|
+
def test_resolve_auth_precedence(kwargs, expected):
|
|
131
|
+
assert resolve_auth(**kwargs) == expected
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
def test_both_flags_is_a_usage_error():
|
|
135
|
+
from fp_cli import _click_compat as click
|
|
136
|
+
|
|
137
|
+
with pytest.raises(click.UsageError):
|
|
138
|
+
resolve_auth(api_key=KEY, api_key_on_cli=True, token="t", token_on_cli=True,
|
|
139
|
+
saved_token=None)
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
def test_both_flags_exits_2_with_zero_http_calls(logged_in, runner):
|
|
143
|
+
with respx.mock(assert_all_called=False) as mock:
|
|
144
|
+
catch_all = mock.route().mock(return_value=httpx.Response(200, json=[]))
|
|
145
|
+
result = runner.invoke(
|
|
146
|
+
app, ["--base-url", BASE, "--api-key", KEY, "--token", "tok", "keys", "list"]
|
|
147
|
+
)
|
|
148
|
+
assert result.exit_code == 2, result.output
|
|
149
|
+
assert catch_all.call_count == 0 # never guessed which one you meant
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
# --- the empty-string rule ---------------------------------------------------
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
def test_empty_api_key_does_not_fall_back_to_saved_session(logged_in, runner):
|
|
156
|
+
"""`--api-key ""` (an unset CI var) must not silently act as the logged-in human."""
|
|
157
|
+
with respx.mock(assert_all_called=False) as mock:
|
|
158
|
+
catch_all = mock.route().mock(return_value=httpx.Response(200, json=[]))
|
|
159
|
+
result = runner.invoke(app, ["--base-url", BASE, "--api-key", "", "keys", "list"])
|
|
160
|
+
assert result.exit_code == 4, result.output # key mode, no credential
|
|
161
|
+
assert catch_all.call_count == 0
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
# --- what goes on the wire (both directions) ---------------------------------
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
def test_key_mode_sends_bearer_and_no_cookie(logged_in, runner):
|
|
168
|
+
# `logged_in` seeds a saved session on purpose: the cookie is available, and must
|
|
169
|
+
# still not be sent.
|
|
170
|
+
result, _api, v1_route = _run_keys_list(runner, ["--base-url", BASE, "--api-key", KEY])
|
|
171
|
+
assert result.exit_code == 0, result.output
|
|
172
|
+
headers = v1_route.calls.last.request.headers
|
|
173
|
+
assert headers["authorization"] == f"Bearer {KEY}"
|
|
174
|
+
assert "cookie" not in headers, "an API-key request must never carry ae_session"
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
def test_session_mode_sends_cookie_and_no_bearer(logged_in, runner):
|
|
178
|
+
result, api_route, _v1 = _run_keys_list(runner, [])
|
|
179
|
+
assert result.exit_code == 0, result.output
|
|
180
|
+
headers = api_route.calls.last.request.headers
|
|
181
|
+
assert "ae_session=tok" in headers.get("cookie", "")
|
|
182
|
+
assert "authorization" not in headers
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
# --- the org header ----------------------------------------------------------
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
def test_key_mode_never_sends_the_saved_org(home, runner):
|
|
189
|
+
config.save_config(config.CliConfig(base_url=BASE, org="human-org"))
|
|
190
|
+
result, _api, v1_route = _run_keys_list(runner, ["--api-key", KEY])
|
|
191
|
+
assert result.exit_code == 0, result.output
|
|
192
|
+
assert "x-agenteye-org" not in v1_route.calls.last.request.headers
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
def test_key_mode_sends_an_explicit_org(home, runner):
|
|
196
|
+
config.save_config(config.CliConfig(base_url=BASE, org="human-org"))
|
|
197
|
+
result, _api, v1_route = _run_keys_list(runner, ["--api-key", KEY, "--org", "acme"])
|
|
198
|
+
assert result.exit_code == 0, result.output
|
|
199
|
+
assert v1_route.calls.last.request.headers["x-agenteye-org"] == "acme"
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
def test_session_mode_still_sends_the_saved_org(home, runner):
|
|
203
|
+
config.save_config(config.CliConfig(base_url=BASE, session_token="tok",
|
|
204
|
+
expires_at="2999-01-01T00:00:00Z", org="human-org"))
|
|
205
|
+
result, api_route, _v1 = _run_keys_list(runner, [])
|
|
206
|
+
assert result.exit_code == 0, result.output
|
|
207
|
+
assert api_route.calls.last.request.headers["x-agenteye-org"] == "human-org"
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
# --- the key never reaches disk ---------------------------------------------
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
def test_api_key_is_never_persisted(home, runner):
|
|
214
|
+
result, _api, _v1 = _run_keys_list(runner, ["--base-url", BASE, "--api-key", KEY])
|
|
215
|
+
assert result.exit_code == 0, result.output
|
|
216
|
+
path = config.config_path()
|
|
217
|
+
on_disk = path.read_text() if path.exists() else ""
|
|
218
|
+
assert KEY not in on_disk
|
|
219
|
+
# And no field quietly holds it either (a new CliConfig field would be the way
|
|
220
|
+
# this regresses).
|
|
221
|
+
assert KEY not in json.dumps(config.load_config().__dict__)
|
|
222
|
+
|
|
223
|
+
|
|
224
|
+
def test_api_key_is_not_persisted_by_whoami(home, runner):
|
|
225
|
+
result = runner.invoke(app, ["--base-url", BASE, "--api-key", KEY, "--json", "whoami"])
|
|
226
|
+
assert result.exit_code == 0, result.output
|
|
227
|
+
path = config.config_path()
|
|
228
|
+
assert KEY not in (path.read_text() if path.exists() else "")
|
|
229
|
+
|
|
230
|
+
|
|
231
|
+
# --- whoami in key mode is the documented exception --------------------------
|
|
232
|
+
|
|
233
|
+
|
|
234
|
+
def test_whoami_key_mode_exits_0_with_honest_shape(logged_in, runner):
|
|
235
|
+
with respx.mock(assert_all_called=False) as mock:
|
|
236
|
+
catch_all = mock.route().mock(return_value=httpx.Response(200, json={}))
|
|
237
|
+
result = runner.invoke(
|
|
238
|
+
app, ["--base-url", BASE, "--api-key", KEY, "--org", "acme", "--json", "whoami"]
|
|
239
|
+
)
|
|
240
|
+
assert result.exit_code == 0, result.output
|
|
241
|
+
assert json.loads(result.stdout) == {
|
|
242
|
+
"logged_in": False,
|
|
243
|
+
"auth_mode": "api_key",
|
|
244
|
+
"active_org": "acme",
|
|
245
|
+
}
|
|
246
|
+
assert catch_all.call_count == 0 # a key has no identity to look up
|
|
247
|
+
|
|
248
|
+
|
|
249
|
+
def test_whoami_key_mode_reports_no_org_when_none_given(home, runner):
|
|
250
|
+
config.save_config(config.CliConfig(base_url=BASE, org="human-org"))
|
|
251
|
+
result = runner.invoke(app, ["--api-key", KEY, "--json", "whoami"])
|
|
252
|
+
assert result.exit_code == 0, result.output
|
|
253
|
+
# The SAVED org is not the key's org, so it must not be reported as active.
|
|
254
|
+
assert json.loads(result.stdout)["active_org"] is None
|
|
255
|
+
|
|
256
|
+
|
|
257
|
+
def test_whoami_human_shapes_carry_auth_mode(home, runner):
|
|
258
|
+
result = runner.invoke(app, ["--base-url", BASE, "--json", "whoami"])
|
|
259
|
+
assert result.exit_code == 0, result.output
|
|
260
|
+
assert json.loads(result.stdout) == {"logged_in": False, "auth_mode": "none"}
|
|
261
|
+
|
|
262
|
+
|
|
263
|
+
# --- AppState default --------------------------------------------------------
|
|
264
|
+
|
|
265
|
+
|
|
266
|
+
def test_appstate_defaults_to_none_mode():
|
|
267
|
+
# An AppState built by another path (tests, embedders) must never be silently
|
|
268
|
+
# treated as key mode.
|
|
269
|
+
state = AppState(json=False, base_url=None, token=None, timeout=30.0,
|
|
270
|
+
config=config.CliConfig())
|
|
271
|
+
assert state.auth_mode is AuthMode.NONE
|
|
272
|
+
assert state.api_key is None
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
"""Guard the contract that ``fp_cli/_click_compat.py`` exists to hold.
|
|
2
|
+
|
|
3
|
+
Typer 0.26 vendored Click and now catches only *its* Click's exceptions. Every way
|
|
4
|
+
that coupling breaks is silent — the CLI imports, compiles, and passes every happy
|
|
5
|
+
path while typed errors exit 1 with an empty stderr and the telemetry flag catalog
|
|
6
|
+
quietly empties. These are the alarms.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
import ast
|
|
12
|
+
import pathlib
|
|
13
|
+
|
|
14
|
+
import typer
|
|
15
|
+
from typer.testing import CliRunner
|
|
16
|
+
|
|
17
|
+
from fp_cli import _click_compat, analytics_registry
|
|
18
|
+
from fp_cli.errors import ForbiddenError, KeyModeUnsupportedError, NotFoundError
|
|
19
|
+
|
|
20
|
+
_PACKAGE = pathlib.Path(__file__).resolve().parent.parent / "fp_cli"
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def test_the_package_scan_actually_has_something_to_scan():
|
|
24
|
+
"""`_PACKAGE` is a path literal, so a package rename makes it point at nothing.
|
|
25
|
+
|
|
26
|
+
Every scanning test below then walks an empty directory, finds zero violations and
|
|
27
|
+
passes — the guard silently stops guarding. This is the tripwire for that: it caught
|
|
28
|
+
nothing during the agenteye -> fp_cli rename only because the literal was updated in
|
|
29
|
+
the same pass, which is exactly the kind of thing that is remembered once.
|
|
30
|
+
"""
|
|
31
|
+
assert _PACKAGE.is_dir(), f"{_PACKAGE} does not exist — the scanning tests are vacuous"
|
|
32
|
+
modules = list(_PACKAGE.rglob("*.py"))
|
|
33
|
+
assert len(modules) > 20, (
|
|
34
|
+
f"only {len(modules)} modules found under {_PACKAGE}; the scan is not seeing the package"
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def _click_imports(path: pathlib.Path) -> list[str]:
|
|
39
|
+
"""Every direct `import click` / `from click import …` in one module."""
|
|
40
|
+
tree = ast.parse(path.read_text(encoding="utf-8"), filename=str(path))
|
|
41
|
+
hits = []
|
|
42
|
+
for node in ast.walk(tree):
|
|
43
|
+
if isinstance(node, ast.Import):
|
|
44
|
+
hits += [a.name for a in node.names if a.name == "click" or a.name.startswith("click.")]
|
|
45
|
+
elif isinstance(node, ast.ImportFrom):
|
|
46
|
+
if node.module and (node.module == "click" or node.module.startswith("click.")):
|
|
47
|
+
hits.append(node.module)
|
|
48
|
+
return hits
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def test_package_never_imports_click_directly():
|
|
52
|
+
"""The pip `click` distribution is not necessarily the Click Typer is running.
|
|
53
|
+
|
|
54
|
+
Binding to it directly is the whole bug: `raise click.UsageError(...)` from the wrong
|
|
55
|
+
Click is not caught by Typer's runner, so a clean exit-2 usage error becomes exit 1 with
|
|
56
|
+
nothing on stderr. `_click_compat` is the only module allowed to name `click`.
|
|
57
|
+
"""
|
|
58
|
+
offenders = {
|
|
59
|
+
str(path.relative_to(_PACKAGE)): names
|
|
60
|
+
for path in sorted(_PACKAGE.rglob("*.py"))
|
|
61
|
+
if path.name != "_click_compat.py" and (names := _click_imports(path))
|
|
62
|
+
}
|
|
63
|
+
assert not offenders, (
|
|
64
|
+
f"import Click via `from . import _click_compat as click`, not directly: {offenders}"
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def test_typed_errors_reach_typers_handler():
|
|
69
|
+
"""A typed error must still be *caught* by Typer: its message on stderr, its exit code.
|
|
70
|
+
|
|
71
|
+
Asserted through a real command run rather than an isinstance check, because what matters
|
|
72
|
+
is the runner's behaviour — which is what silently changed under typer 0.26.
|
|
73
|
+
"""
|
|
74
|
+
app = typer.Typer()
|
|
75
|
+
|
|
76
|
+
@app.command()
|
|
77
|
+
def boom() -> None:
|
|
78
|
+
raise ForbiddenError("nope", hint="ask an admin")
|
|
79
|
+
|
|
80
|
+
result = CliRunner().invoke(app, [])
|
|
81
|
+
assert result.exit_code == 5, result.output # not 1: uncaught would collapse to 1
|
|
82
|
+
assert "nope" in result.output
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def test_error_exit_codes_are_distinct_per_class():
|
|
86
|
+
"""The exit-code contract is documented in `--help` and scripted against."""
|
|
87
|
+
assert ForbiddenError("x").exit_code == 5
|
|
88
|
+
assert NotFoundError("x").exit_code == 6
|
|
89
|
+
# Key mode reuses exit 2 rather than adding a seventh code — the table is a scripted
|
|
90
|
+
# contract restated in `app.py`, `cli/skill/SKILL.md` and `enterprise-docs/cli.md`.
|
|
91
|
+
assert KeyModeUnsupportedError("x").exit_code == 2
|
|
92
|
+
assert issubclass(ForbiddenError, _click_compat.ClickException)
|
|
93
|
+
# Through the shim, not pip Click: bind it to the wrong Click and every key-mode
|
|
94
|
+
# refusal escapes uncaught as exit 1 with an empty stderr.
|
|
95
|
+
assert issubclass(KeyModeUnsupportedError, _click_compat.ClickException)
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def test_is_option_recognises_typer_options():
|
|
99
|
+
"""Typer's vendored Click has no `Option` class, so `isinstance` cannot answer this.
|
|
100
|
+
|
|
101
|
+
If a future Click stops setting `param_type_name`, this fails loudly instead of quietly
|
|
102
|
+
dropping every flag from the telemetry catalog.
|
|
103
|
+
"""
|
|
104
|
+
from typer.main import get_command
|
|
105
|
+
|
|
106
|
+
from fp_cli.app import app as real_app
|
|
107
|
+
|
|
108
|
+
params = get_command(real_app).params
|
|
109
|
+
assert params, "the root command should declare the global options"
|
|
110
|
+
assert any(_click_compat.is_option(p) for p in params)
|
|
111
|
+
assert all(not _click_compat.is_option(p) for p in params if p.param_type_name == "argument")
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def test_flag_catalog_is_populated():
|
|
115
|
+
"""The catalog is derived by walking the real command tree; empty means the walk broke."""
|
|
116
|
+
_known, _leaves, flags, value_flags = analytics_registry.build()
|
|
117
|
+
assert "--json" in flags and "--base-url" in value_flags
|
|
118
|
+
assert len(flags) > 50, f"flag catalog collapsed to {len(flags)} entries"
|