failproofai 1.0.1 → 1.0.2-beta.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.next/standalone/.next/BUILD_ID +1 -1
- package/.next/standalone/.next/build-manifest.json +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]__0l4748x._.js +3 -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]__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/_08x1r5t._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/_0bwu160._.js +23 -0
- package/.next/standalone/.next/server/chunks/ssr/{_0u5zauv._.js → _0ns6k3a._.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/{3c808qrfcq0im.js → 01u1cf8js2yd6.js} +1 -1
- package/.next/standalone/.next/static/chunks/{0k5050ezjvv49.js → 0izgu7nqh2zp-.js} +1 -1
- package/.next/standalone/.next/static/chunks/0u4-org_e7zde.js +1 -0
- package/.next/standalone/.next/static/chunks/1mfzsja6ftim5.css +2 -0
- package/.next/standalone/.next/static/chunks/{2zpp1we2jv6tb.js → 1tynnrrh711lr.js} +1 -1
- package/.next/standalone/.next/static/chunks/{1ide1wl2f7zi1.js → 22rqvlr7p_1mv.js} +13 -13
- package/.next/standalone/.next/static/chunks/{31s1smp-g7gks.js → 2whxqyskpirbk.js} +1 -1
- package/.next/standalone/.next/static/chunks/31mwk8k0q3_3g.js +6 -0
- package/.next/standalone/.next/static/chunks/3ms6gxfm82t6s.js +1 -0
- package/.next/standalone/.next/static/chunks/{37fcpc36x9gpe.js → 3tbbs_d8xrvyp.js} +1 -1
- package/.next/standalone/.next/static/chunks/43nlc236oo898.js +1 -0
- 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 +163 -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 -15
- 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 +27 -21
- 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 +579 -306
- package/dist/cli.mjs +6499 -3662
- package/dist/index.js +5 -5
- package/dist/worker.mjs +2390 -1978
- package/package.json +27 -21
- 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 +56 -369
- package/src/hooks/custom-hooks-loader.ts +89 -6
- package/src/hooks/fp-home.ts +92 -4
- package/src/hooks/fp-reset.ts +34 -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 +424 -172
- package/src/hooks/migrations.ts +2 -2
- package/src/hooks/pack-cli.ts +885 -0
- package/src/hooks/pack-failclosed.ts +189 -0
- package/src/hooks/pack-manifest.ts +352 -0
- package/src/hooks/pack-store.ts +891 -0
- package/src/hooks/policy-catalog.ts +535 -0
- package/src/hooks/policy-evaluator.ts +24 -16
- 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 +695 -39
- 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]__07tbbf3._.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/server/chunks/ssr/_0lvyuv4._.js +0 -23
- package/.next/standalone/.next/static/chunks/2_2lhuojq2xop.js +0 -1
- package/.next/standalone/.next/static/chunks/36fw6yk1s6x_f.js +0 -1
- package/.next/standalone/.next/static/chunks/3k7mxbp9hiom0.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/src/hooks/policy-presets.ts +0 -135
- /package/.next/standalone/.next/static/{ZklXxyA5ygsWSQz2u_zii → I5kEb9kSgYS63j4iqfVLO}/_buildManifest.js +0 -0
- /package/.next/standalone/.next/static/{ZklXxyA5ygsWSQz2u_zii → I5kEb9kSgYS63j4iqfVLO}/_clientMiddlewareManifest.js +0 -0
- /package/.next/standalone/.next/static/{ZklXxyA5ygsWSQz2u_zii → I5kEb9kSgYS63j4iqfVLO}/_ssgManifest.js +0 -0
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
"""A 404 in key mode has two very different causes; the CLI must tell them apart.
|
|
2
|
+
|
|
3
|
+
Pointing `--base-url` at a dashboard whose front door does not forward `/v1` is
|
|
4
|
+
the likeliest first-run mistake. It used to arrive as a 3xx to `/login`, which
|
|
5
|
+
`_raise_for_status` names explicitly — but a dashboard that correctly declines to
|
|
6
|
+
auth-gate `/v1` (see `dashboard/proxy.ts`) returns its own 404 instead, and on
|
|
7
|
+
the status code alone that is indistinguishable from "no such record".
|
|
8
|
+
|
|
9
|
+
The tell is the content type: the API only ever answers JSON.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
import httpx
|
|
13
|
+
import pytest
|
|
14
|
+
import respx
|
|
15
|
+
|
|
16
|
+
from fp_cli import client as api
|
|
17
|
+
from fp_cli.client import AuthMode, ClientContext
|
|
18
|
+
from fp_cli.errors import ApiError, NotFoundError
|
|
19
|
+
|
|
20
|
+
BASE = "http://server.test"
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def key_ctx() -> ClientContext:
|
|
24
|
+
return ClientContext(base_url=BASE, api_key="ak_test", auth_mode=AuthMode.API_KEY)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
@respx.mock
|
|
28
|
+
def test_html_404_names_the_routing_problem() -> None:
|
|
29
|
+
respx.get(f"{BASE}/v1/events").mock(
|
|
30
|
+
return_value=httpx.Response(
|
|
31
|
+
404,
|
|
32
|
+
text="<!DOCTYPE html><html>404</html>",
|
|
33
|
+
headers={"content-type": "text/html; charset=utf-8"},
|
|
34
|
+
)
|
|
35
|
+
)
|
|
36
|
+
with pytest.raises(ApiError) as excinfo:
|
|
37
|
+
api.list_events(key_ctx())
|
|
38
|
+
assert "not routed" in str(excinfo.value)
|
|
39
|
+
# Must NOT degrade to the ordinary not-found error, which would send the
|
|
40
|
+
# reader hunting for a missing record instead of a misconfigured base URL.
|
|
41
|
+
assert not isinstance(excinfo.value, NotFoundError)
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
@respx.mock
|
|
45
|
+
def test_json_404_is_still_an_ordinary_not_found() -> None:
|
|
46
|
+
"""The other half.
|
|
47
|
+
|
|
48
|
+
Without this, the guard above could 'pass' by relabelling every 404 as a
|
|
49
|
+
routing problem — worse than the bug it fixes, because then a genuinely
|
|
50
|
+
missing record would send people to check their base URL forever.
|
|
51
|
+
"""
|
|
52
|
+
respx.get(f"{BASE}/v1/events").mock(
|
|
53
|
+
return_value=httpx.Response(404, json={"error": "nope"})
|
|
54
|
+
)
|
|
55
|
+
with pytest.raises(NotFoundError):
|
|
56
|
+
api.list_events(key_ctx())
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
@respx.mock
|
|
60
|
+
def test_session_mode_html_404_is_untouched() -> None:
|
|
61
|
+
"""Cookie mode keeps its existing behaviour — this diagnostic is key-mode only."""
|
|
62
|
+
respx.get(f"{BASE}/api/events").mock(
|
|
63
|
+
return_value=httpx.Response(
|
|
64
|
+
404,
|
|
65
|
+
text="<!DOCTYPE html><html>404</html>",
|
|
66
|
+
headers={"content-type": "text/html; charset=utf-8"},
|
|
67
|
+
)
|
|
68
|
+
)
|
|
69
|
+
with pytest.raises(NotFoundError):
|
|
70
|
+
api.list_events(ClientContext(base_url=BASE, token="tok"))
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
"""Anti-drift guard for API-key mode's `/api/*` -> `/v1/*` translation.
|
|
2
|
+
|
|
3
|
+
Why this test is shaped the way it is
|
|
4
|
+
-------------------------------------
|
|
5
|
+
The CLI already had an anti-drift test that stayed green through an 85 -> 0 collapse
|
|
6
|
+
(`memory/2026-07-24-typer-026-vendored-click-breaks-cli-errors.md`): it walked the
|
|
7
|
+
command tree with the *same* broken predicate the production code used, so it
|
|
8
|
+
compared nothing to nothing and reported success. The lesson is not "write an
|
|
9
|
+
anti-drift test", it is "get the expectation from a source the production code does
|
|
10
|
+
not also consult".
|
|
11
|
+
|
|
12
|
+
So each leg below sources its input independently of `client.py`'s own logic:
|
|
13
|
+
|
|
14
|
+
1. The set of paths comes from an **AST scan of client.py's string literals**, not
|
|
15
|
+
from any registry the translator reads. Add a call site and it appears here
|
|
16
|
+
whether or not anyone remembered a list.
|
|
17
|
+
2. The unsupported commands are driven through a **real CliRunner**, asserting the
|
|
18
|
+
exit code AND that zero HTTP calls happened — the "fails before any network
|
|
19
|
+
call" half is the part a return-value assertion cannot see.
|
|
20
|
+
|
|
21
|
+
A third leg used to check every translated `/v1` path against the server router's
|
|
22
|
+
own `.route()` literals, read out of an AgentEye checkout. The server now lives in
|
|
23
|
+
a separate private repository, so that leg could only ever run when a checkout
|
|
24
|
+
happened to be on disk and skipped everywhere else — including all of CI, where a
|
|
25
|
+
skip reads as green. It was removed rather than left switched off. Nothing here
|
|
26
|
+
verifies that a translated path is a route the server actually registers; a rename
|
|
27
|
+
on the server side surfaces as a 404 at runtime.
|
|
28
|
+
"""
|
|
29
|
+
|
|
30
|
+
from __future__ import annotations
|
|
31
|
+
|
|
32
|
+
import ast
|
|
33
|
+
import pathlib
|
|
34
|
+
|
|
35
|
+
import httpx
|
|
36
|
+
import pytest
|
|
37
|
+
import respx
|
|
38
|
+
|
|
39
|
+
from fp_cli import client as api
|
|
40
|
+
from fp_cli.app import app
|
|
41
|
+
from fp_cli.errors import KeyModeUnsupportedError
|
|
42
|
+
|
|
43
|
+
CLIENT_PY = pathlib.Path(api.__file__)
|
|
44
|
+
BASE = "http://dash.test"
|
|
45
|
+
KEY = "ak_live_abc123"
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
# --- leg 1: every /api/ literal in client.py is classified -------------------
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def _api_path_literals(source: str) -> set:
|
|
52
|
+
"""Every `/api/...` path the CLI can build, read straight out of the source.
|
|
53
|
+
|
|
54
|
+
f-strings are reconstructed as templates (`f"/api/keys/{key_id}/disable"` ->
|
|
55
|
+
`/api/keys/{}/disable`) so an interpolated id becomes a wildcard segment rather
|
|
56
|
+
than a fragment. The constant *pieces* of an f-string are skipped for exactly that
|
|
57
|
+
reason: `"/api/issues/"` on its own is not a path anyone requests.
|
|
58
|
+
"""
|
|
59
|
+
tree = ast.parse(source)
|
|
60
|
+
inside_fstring = {
|
|
61
|
+
id(v)
|
|
62
|
+
for node in ast.walk(tree)
|
|
63
|
+
if isinstance(node, ast.JoinedStr)
|
|
64
|
+
for v in node.values
|
|
65
|
+
}
|
|
66
|
+
found = set()
|
|
67
|
+
for node in ast.walk(tree):
|
|
68
|
+
if isinstance(node, ast.JoinedStr):
|
|
69
|
+
text = "".join(
|
|
70
|
+
v.value if isinstance(v, ast.Constant) and isinstance(v.value, str) else "{}"
|
|
71
|
+
for v in node.values
|
|
72
|
+
)
|
|
73
|
+
elif (
|
|
74
|
+
isinstance(node, ast.Constant)
|
|
75
|
+
and isinstance(node.value, str)
|
|
76
|
+
and id(node) not in inside_fstring
|
|
77
|
+
):
|
|
78
|
+
text = node.value
|
|
79
|
+
else:
|
|
80
|
+
continue
|
|
81
|
+
# `> len("/api/")` skips the `_API_PREFIX` constant itself: it is the rule, not
|
|
82
|
+
# a path, and no real request is ever made to a bare "/api/".
|
|
83
|
+
if text.startswith("/api/") and len(text) > len("/api/"):
|
|
84
|
+
found.add(text)
|
|
85
|
+
return found
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def _buckets(path: str) -> list:
|
|
89
|
+
"""Which classification(s) `path` falls into, computed from the DATA in client.py
|
|
90
|
+
rather than by calling its classifier — a catch-all `else` in the translator must
|
|
91
|
+
not be able to make this test pass."""
|
|
92
|
+
kinds = []
|
|
93
|
+
if path in api._V1_RENAMED:
|
|
94
|
+
kinds.append("override")
|
|
95
|
+
else:
|
|
96
|
+
# The rename is an exact-match override of the family rule, so it is only
|
|
97
|
+
# checked when the path is not itself renamed; that keeps the three buckets
|
|
98
|
+
# genuinely disjoint instead of "exactly one, if you squint".
|
|
99
|
+
family = path[len("/api/") :].split("/", 1)[0]
|
|
100
|
+
if family in api._V1_NO_EQUIVALENT:
|
|
101
|
+
kinds.append("no-v1")
|
|
102
|
+
if family in api._V1_MECHANICAL_FAMILIES:
|
|
103
|
+
kinds.append("mechanical")
|
|
104
|
+
return kinds
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def test_every_api_literal_is_classified_exactly_once():
|
|
108
|
+
literals = _api_path_literals(CLIENT_PY.read_text())
|
|
109
|
+
assert literals, "the AST scan found no /api/ paths — it has stopped asking anything"
|
|
110
|
+
unclassified = sorted(p for p in literals if not _buckets(p))
|
|
111
|
+
assert not unclassified, (
|
|
112
|
+
"these /api/ paths have no /v1 classification — add the family to "
|
|
113
|
+
"_V1_MECHANICAL_FAMILIES, _V1_RENAMED or _V1_NO_EQUIVALENT in client.py "
|
|
114
|
+
f"(and check the server actually serves it): {unclassified}"
|
|
115
|
+
)
|
|
116
|
+
ambiguous = sorted(p for p in literals if len(_buckets(p)) > 1)
|
|
117
|
+
assert not ambiguous, f"/api paths matching more than one bucket: {ambiguous}"
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
def test_no_v1_paths_raise_instead_of_being_requested():
|
|
121
|
+
literals = _api_path_literals(CLIENT_PY.read_text())
|
|
122
|
+
excluded = [p for p in literals if _buckets(p) == ["no-v1"]]
|
|
123
|
+
assert excluded, "the excluded families vanished from client.py — verify on purpose"
|
|
124
|
+
for path in excluded:
|
|
125
|
+
with pytest.raises(KeyModeUnsupportedError):
|
|
126
|
+
api._v1_path(path)
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
def test_an_unknown_family_raises_loudly():
|
|
130
|
+
# The property the two lists above cannot prove about each other: a path in no
|
|
131
|
+
# bucket must fail, not pass through to a URL nobody chose.
|
|
132
|
+
with pytest.raises(Exception) as excinfo:
|
|
133
|
+
api._v1_path("/api/telepathy/read")
|
|
134
|
+
assert "/api/telepathy/read" in str(excinfo.value)
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
def test_the_score_keys_rename_is_applied():
|
|
138
|
+
# Hyphen -> underscore. It exists only in the dashboard proxy
|
|
139
|
+
# (dashboard/app/api/evaluations/score-keys/route.ts), so a blind s|^/api|/v1|
|
|
140
|
+
# 404s and the CLI reports a cheerful "Not found."
|
|
141
|
+
assert api._v1_path("/api/evaluations/score-keys") == "/v1/evaluations/score_keys"
|
|
142
|
+
assert api._FACET_PATHS["score_filters"] == "/api/evaluations/score-keys"
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
# --- leg 2: unsupported commands fail before any network call -----------------
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
UNSUPPORTED = [
|
|
149
|
+
["login"],
|
|
150
|
+
["logout"],
|
|
151
|
+
["orgs", "list"],
|
|
152
|
+
["orgs", "switch", "acme"],
|
|
153
|
+
["orgs", "current"],
|
|
154
|
+
["orgs", "perms"],
|
|
155
|
+
["agent", "health"],
|
|
156
|
+
["agent", "models"],
|
|
157
|
+
["agent", "chats"],
|
|
158
|
+
["agent", "ask", "hello"],
|
|
159
|
+
["agent", "show", "abc123"],
|
|
160
|
+
["agent", "rename", "abc123", "--title", "x"],
|
|
161
|
+
["agent", "delete", "abc123", "--yes"],
|
|
162
|
+
["keys", "update", "ci-bot", "--add", "keys:read"],
|
|
163
|
+
]
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
@pytest.mark.parametrize("argv", UNSUPPORTED, ids=lambda a: " ".join(a[:2]))
|
|
167
|
+
def test_unsupported_command_exits_2_with_zero_http_calls(logged_in, runner, argv):
|
|
168
|
+
# `logged_in` seeds a saved session deliberately: the failure must come from the
|
|
169
|
+
# key, not from having nothing else to fall back on.
|
|
170
|
+
with respx.mock(assert_all_called=False) as mock:
|
|
171
|
+
catch_all = mock.route().mock(return_value=httpx.Response(200, json={}))
|
|
172
|
+
result = runner.invoke(app, ["--base-url", BASE, "--api-key", KEY, *argv])
|
|
173
|
+
assert result.exit_code == 2, f"{argv} -> {result.exit_code}: {result.output}"
|
|
174
|
+
assert catch_all.call_count == 0, f"{argv} opened a connection before failing"
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
def test_unsupported_command_says_why_and_what_to_do(logged_in, runner):
|
|
178
|
+
result = runner.invoke(app, ["--base-url", BASE, "--api-key", KEY, "--json", "orgs", "list"])
|
|
179
|
+
assert result.exit_code == 2, result.output
|
|
180
|
+
assert "API key" in result.stdout
|
|
181
|
+
assert "fp login" in result.stdout # the hint rides in the JSON envelope
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
def test_whoami_is_the_exception(logged_in, runner):
|
|
185
|
+
# Contractually "never errors" (cli/skill/SKILL.md leans on it as the pre-flight).
|
|
186
|
+
result = runner.invoke(app, ["--base-url", BASE, "--api-key", KEY, "whoami"])
|
|
187
|
+
assert result.exit_code == 0, result.output
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
SUPPORTED = [
|
|
191
|
+
(["keys", "list"], "/v1/keys", []),
|
|
192
|
+
(["sessions"], "/v1/sessions", {"sessions": [], "next_cursor": None}),
|
|
193
|
+
(["events"], "/v1/events/summary", {"events": [], "next_cursor": None}),
|
|
194
|
+
(["issues", "list"], "/v1/issues", []),
|
|
195
|
+
(["list", "envs"], "/v1/events/environments", []),
|
|
196
|
+
(["list", "score_filters"], "/v1/evaluations/score_keys", []),
|
|
197
|
+
(["query", "list"], "/v1/queries", {"queries": []}),
|
|
198
|
+
(["users", "list"], "/v1/users", []),
|
|
199
|
+
(["settings", "list"], "/v1/settings", {"settings": []}),
|
|
200
|
+
(["alerts", "list"], "/v1/alerts", []),
|
|
201
|
+
(["audits", "list"], "/v1/audits", []),
|
|
202
|
+
]
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
@pytest.mark.parametrize("argv,path,body", SUPPORTED, ids=lambda a: " ".join(a[:2]) if isinstance(a, list) else "")
|
|
206
|
+
def test_supported_commands_reach_their_v1_route(logged_in, runner, argv, path, body):
|
|
207
|
+
"""The other half of leg 3: the guard must not have swallowed the working set, and
|
|
208
|
+
each command must land on the `/v1` route it is supposed to (only this exact URL is
|
|
209
|
+
mocked, so a wrong path is a connection error, not a quiet pass)."""
|
|
210
|
+
with respx.mock(assert_all_called=False) as mock:
|
|
211
|
+
route = mock.get(f"{BASE}{path}").mock(return_value=httpx.Response(200, json=body))
|
|
212
|
+
result = runner.invoke(app, ["--base-url", BASE, "--api-key", KEY, "--json", *argv])
|
|
213
|
+
assert result.exit_code == 0, f"{argv} -> {result.exit_code}: {result.output}"
|
|
214
|
+
assert route.called
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
"""`whoami` (logged in): the identity header + permissions/orgs panels, and the
|
|
2
|
+
permission grouping/coloring logic."""
|
|
3
|
+
|
|
4
|
+
from __future__ import annotations
|
|
5
|
+
|
|
6
|
+
import httpx
|
|
7
|
+
import respx
|
|
8
|
+
|
|
9
|
+
from fp_cli import theme
|
|
10
|
+
from fp_cli.app import app
|
|
11
|
+
from fp_cli.output import _group_permissions
|
|
12
|
+
|
|
13
|
+
BASE = "http://dash.test"
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
# --- permission grouping / coloring -----------------------------------------
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def test_group_permissions_orders_resources_and_actions_by_risk():
|
|
20
|
+
grouped = _group_permissions(
|
|
21
|
+
["keys:delete", "keys:read", "keys:create", "dashboards:read", "agent:use"]
|
|
22
|
+
)
|
|
23
|
+
# resources follow the fixed priority order (dashboards < keys < agent)
|
|
24
|
+
assert [r for r, _ in grouped] == ["dashboards", "keys", "agent"]
|
|
25
|
+
by_res = dict(grouped)
|
|
26
|
+
# actions within a row ordered by risk: read → create → delete
|
|
27
|
+
assert [a for a, _ in by_res["keys"]] == ["read", "create", "delete"]
|
|
28
|
+
colors = dict(by_res["keys"])
|
|
29
|
+
assert colors["read"] == theme.PERM_READ
|
|
30
|
+
assert colors["create"] == theme.PERM_WRITE
|
|
31
|
+
assert colors["delete"] == theme.PERM_DANGER
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def test_group_permissions_unknown_action_falls_back_neutral():
|
|
35
|
+
grouped = _group_permissions(["weird:frobnicate"])
|
|
36
|
+
assert grouped == [("weird", [("frobnicate", theme.DEFAULT_PERM_COLOR)])]
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
# --- human render ------------------------------------------------------------
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def _session(memberships):
|
|
43
|
+
return {
|
|
44
|
+
"id": "62230791-4811-4f04-b388-ae57bdcb422e",
|
|
45
|
+
"email": "admin@local.host",
|
|
46
|
+
"is_instance_admin": True,
|
|
47
|
+
"memberships": memberships,
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
@respx.mock
|
|
52
|
+
def test_whoami_human_renders_header_and_panels(logged_in, runner):
|
|
53
|
+
respx.get(f"{BASE}/api/auth/session").mock(
|
|
54
|
+
return_value=httpx.Response(200, json=_session([
|
|
55
|
+
{"org_id": "o1", "org_slug": "globex", "org_name": "Globex Corp", "permission_set": "admin",
|
|
56
|
+
"permissions": ["dashboards:read", "dashboards:write", "dashboards:delete", "keys:read", "agent:use"]},
|
|
57
|
+
{"org_id": "o2", "org_slug": "acme", "org_name": "Acme Corp", "permission_set": "admin",
|
|
58
|
+
"permissions": ["events:read"]},
|
|
59
|
+
]))
|
|
60
|
+
)
|
|
61
|
+
result = runner.invoke(app, ["--org", "globex", "whoami"])
|
|
62
|
+
assert result.exit_code == 0, result.output
|
|
63
|
+
out = result.stdout
|
|
64
|
+
# identity header: email, instance role, FULL user id, active org
|
|
65
|
+
assert "admin@local.host" in out
|
|
66
|
+
assert "62230791-4811-4f04-b388-ae57bdcb422e" in out
|
|
67
|
+
assert "instance admin" in out
|
|
68
|
+
# permissions panel
|
|
69
|
+
assert "permissions" in out
|
|
70
|
+
assert "dashboards" in out and "read" in out and "write" in out and "delete" in out
|
|
71
|
+
# orgs panel + switch hint to the non-active org
|
|
72
|
+
assert "your orgs" in out
|
|
73
|
+
assert "globex" in out and "acme" in out
|
|
74
|
+
assert "Globex Corp" in out # permissions title shows the active org NAME
|
|
75
|
+
assert "fp orgs switch <slug>" in out
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
@respx.mock
|
|
79
|
+
def test_whoami_single_org_has_no_switch_hint(logged_in, runner):
|
|
80
|
+
respx.get(f"{BASE}/api/auth/session").mock(
|
|
81
|
+
return_value=httpx.Response(200, json=_session([
|
|
82
|
+
{"org_id": "o", "org_slug": "acme", "org_name": "Acme", "permission_set": "admin",
|
|
83
|
+
"permissions": ["events:read"]},
|
|
84
|
+
]))
|
|
85
|
+
)
|
|
86
|
+
result = runner.invoke(app, ["whoami"])
|
|
87
|
+
assert result.exit_code == 0, result.output
|
|
88
|
+
assert "your orgs" in result.stdout
|
|
89
|
+
assert "orgs switch" not in result.stdout # only one org → no switch hint
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
@respx.mock
|
|
93
|
+
def test_whoami_no_color_marks_destructive_actions(logged_in, runner):
|
|
94
|
+
respx.get(f"{BASE}/api/auth/session").mock(
|
|
95
|
+
return_value=httpx.Response(200, json=_session([
|
|
96
|
+
{"org_id": "o", "org_slug": "acme", "org_name": "Acme", "permission_set": "admin",
|
|
97
|
+
"permissions": ["keys:read", "keys:delete", "keys:disable"]},
|
|
98
|
+
]))
|
|
99
|
+
)
|
|
100
|
+
result = runner.invoke(app, ["whoami"], env={"NO_COLOR": "1"})
|
|
101
|
+
assert result.exit_code == 0, result.output
|
|
102
|
+
out = result.stdout
|
|
103
|
+
assert "delete*" in out and "disable*" in out # destructive marked in plain text
|
|
104
|
+
assert "read" in out and "read*" not in out # non-destructive unmarked
|