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,259 @@
|
|
|
1
|
+
# FailproofAI Cloud CLI (`fp`)
|
|
2
|
+
|
|
3
|
+
A command-line client for the **FailproofAI Cloud** API. It lets a developer —
|
|
4
|
+
or the coding agent working alongside them — authenticate and query agent
|
|
5
|
+
sessions, event logs, and evaluations from the terminal, with a `--json` flag on
|
|
6
|
+
every command for scripting.
|
|
7
|
+
|
|
8
|
+
> **The package is `fp-cloud-cli`; the command is `fp`.** They differ because `fp` was
|
|
9
|
+
> already taken on PyPI. This is also distinct from `failproofai` on npm, which is
|
|
10
|
+
> the Enforcement CLI — that one runs inside the agent loop and decides what an
|
|
11
|
+
> agent may do; this one reads back what it did.
|
|
12
|
+
|
|
13
|
+
## Install
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
pipx install fp-cloud-cli # recommended (isolated)
|
|
17
|
+
# or: uv tool install fp-cloud-cli / pip install fp-cloud-cli
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
Then the command is `fp`:
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
fp --version
|
|
24
|
+
fp help
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
For development in this repo:
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
cd fp-cloud-cli
|
|
31
|
+
uv sync --extra dev
|
|
32
|
+
uv run fp --help
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## Authentication
|
|
36
|
+
|
|
37
|
+
The CLI talks to your dashboard (set `--base-url` or `FP_DASHBOARD_URL`) and logs in with
|
|
38
|
+
an emailed one-time code:
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
fp login --email you@example.com
|
|
42
|
+
# enter the 6-digit code; the session is stored in
|
|
43
|
+
# ~/.failproofai/fpcli/cli-auth.json (mode 0600)
|
|
44
|
+
fp whoami
|
|
45
|
+
fp logout
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
Sessions expire (24h by default); re-run `fp login` when prompted.
|
|
49
|
+
|
|
50
|
+
## Working across orgs (multi-tenant)
|
|
51
|
+
|
|
52
|
+
Your account can belong to more than one org. The active org is chosen **at login** and saved:
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
fp login --org acme # pick the tenant at login (saved for later commands)
|
|
56
|
+
fp orgs list # your orgs + your role in each (active one marked)
|
|
57
|
+
fp orgs switch globex # switch the active org (pass a slug…)
|
|
58
|
+
fp orgs switch # …or omit it to pick from a list (in a terminal)
|
|
59
|
+
fp orgs current # which org you're acting as right now (identity card)
|
|
60
|
+
fp orgs perms # your permissions in the active org (grouped by resource)
|
|
61
|
+
fp --org globex sessions # override for a single command
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
If you belong to exactly one org it's selected automatically. If you belong to several, you
|
|
65
|
+
pick at login (or with `orgs switch`); pass `--org` to choose non-interactively. A slug you
|
|
66
|
+
can't access (non-existent, or not yours) is rejected rather than saved. The active org is
|
|
67
|
+
sent as the `X-AgentEye-Org` header on every request, and your permissions are resolved per
|
|
68
|
+
org. (All tenant commands live under one group: `orgs list` / `orgs switch` / `orgs current` /
|
|
69
|
+
`orgs perms`.)
|
|
70
|
+
|
|
71
|
+
## Commands
|
|
72
|
+
|
|
73
|
+
**Query & observability**
|
|
74
|
+
|
|
75
|
+
```text
|
|
76
|
+
fp sessions [--since 24h] [--status error] [--agent-id <id>] [--all] # runs: time/env/agent/session/status
|
|
77
|
+
fp evals [--score helpfulness:..0.5] [--all] # eval results + scores
|
|
78
|
+
fp evals --aggregate [--since 7d] # rolled-up eval health (status mix + per-metric score stats)
|
|
79
|
+
fp events --session-id <id> [--event-type tool_use,tool_result] [--env prod] [--all]
|
|
80
|
+
fp errors [--since 24h] [--error-type TimeoutError] [--all] # list errored events
|
|
81
|
+
fp errors --aggregate [--since 7d] # error summary (count + sessions/agents/last seen)
|
|
82
|
+
fp list <thing> # discover dropdown values to filter on:
|
|
83
|
+
# envs · agents · event_types · score_filters · models · hooks · tools · error_types
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
**Manage your org** (each gated by the matching permission)
|
|
87
|
+
|
|
88
|
+
```text
|
|
89
|
+
fp keys list|show|create|update|disable|regenerate # API keys (secret shown once)
|
|
90
|
+
fp users list|show|create|update|disable|enable
|
|
91
|
+
fp settings list|schema|set
|
|
92
|
+
fp alerts list|show|create|update|delete|test
|
|
93
|
+
fp issues list|count|show|ack|assign|resolve|comment-add|comment-list|comment-delete|subscribe|subscribers|unsubscribe|open
|
|
94
|
+
fp audits list|show|create|edit|delete|run|runs|findings|finding # scheduled audits
|
|
95
|
+
fp audits ack|assign|resolve|dismiss|mute|reopen # triage a finding
|
|
96
|
+
fp audits context-show|context-set|context-refresh # reference context
|
|
97
|
+
fp usage # org usage for the metering window
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
**Analytics & assistant**
|
|
101
|
+
|
|
102
|
+
```text
|
|
103
|
+
fp query list|show|create|update|delete|run|schema # saved SQL + ad-hoc runner
|
|
104
|
+
fp agent health|models|chats|show|rename|delete|ask
|
|
105
|
+
# agent models → models available for --model (with the default marked)
|
|
106
|
+
# agent ask "…" → starts a NEW chat, answers + persists, prints the chat id
|
|
107
|
+
# agent ask --chat <id> "…" → continue that chat (shows in the dashboard; 1st Q auto-titles)
|
|
108
|
+
# agent chats|show <id>|rename <id> --title …|delete <id> → manage saved chats
|
|
109
|
+
fp version | help
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
**Mutations are non-interactive-safe.** Create/update/delete prompt for confirmation in a
|
|
113
|
+
terminal, but auto-skip the prompt under `--json` or when stdin isn't a TTY (so scripts/agents
|
|
114
|
+
never hang). Pass `--yes`/`-y` to skip it explicitly. Request bodies can be supplied with
|
|
115
|
+
`--file payload.json` (or `--file -` for stdin) on `alerts` and `settings` — mutually
|
|
116
|
+
exclusive with the discrete flags. `users create`/`update` take only the discrete
|
|
117
|
+
flags (`--permission-set`, `--add`, `--remove`). (Saved-query SQL uses `--sql @file.sql`.)
|
|
118
|
+
|
|
119
|
+
Every command and subcommand has `--help` / `-h`; `fp -h` documents auth, exit codes, and
|
|
120
|
+
the global options. **Global options go before the command** (`fp --json events`, not
|
|
121
|
+
`fp events --json`).
|
|
122
|
+
|
|
123
|
+
Add `--json` for machine-readable output, and `--fields` to project just the keys you need:
|
|
124
|
+
|
|
125
|
+
```bash
|
|
126
|
+
fp --json events --session-id run-001 --all | jq '.events[].payload'
|
|
127
|
+
fp --json sessions --since 7d --fields session_id,status,scores
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
### Cloud-managed policies
|
|
131
|
+
|
|
132
|
+
Three commands for the three jobs the dashboard splits across three pages —
|
|
133
|
+
`fp policies` writes a policy version, `fp fleet` decides which machines run it,
|
|
134
|
+
`fp guardrails summary` reports what it actually blocked.
|
|
135
|
+
|
|
136
|
+
```bash
|
|
137
|
+
fp policies publish no-force-push ./rule.mjs # path, @path, a pipe, - or a paste
|
|
138
|
+
fp fleet deploy ci-runner-01 --add no-force-push
|
|
139
|
+
fp guardrails summary --since 24h
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
**A deploy REPLACES a machine's whole policy set.** The server takes the full
|
|
143
|
+
list and does not merge, so `fleet deploy` reads what the machine currently runs,
|
|
144
|
+
applies your `--add`/`--remove`, prints the complete resulting set, and writes
|
|
145
|
+
that. Use `--set` only when you mean "exactly these, drop the rest".
|
|
146
|
+
|
|
147
|
+
```bash
|
|
148
|
+
fp fleet deploy ci-runner-01 \
|
|
149
|
+
--add no-force-push \ # keeps its pinned version if already deployed
|
|
150
|
+
--add prod-guard@1:observe \ # id@version:effect
|
|
151
|
+
--remove old-rule
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
Three things worth knowing before you script it:
|
|
155
|
+
|
|
156
|
+
* A bare `--add` of a policy the machine already runs keeps its **pinned
|
|
157
|
+
version**. Pass `id@version` to move it — a pin is usually deliberate.
|
|
158
|
+
* The endpoint has no lock. The CLI records the deployment generation it read and
|
|
159
|
+
**refuses** if the write does not land at exactly one higher, because that means
|
|
160
|
+
somebody deployed in between and a replace does not merge.
|
|
161
|
+
* The exit code separates your mistake from the server's answer. A malformed ref,
|
|
162
|
+
`--set` alongside `--add`/`--remove`, or no flags at all is **2**; a ref that
|
|
163
|
+
parses but names a policy that does not exist is **1**; an unknown machine is
|
|
164
|
+
**6**. Branch on those rather than on the message.
|
|
165
|
+
|
|
166
|
+
`fp fleet diff` shows intent versus delivery: a machine can be deployed-to and
|
|
167
|
+
still enforcing an older set until it next polls. It refuses a machine id nobody
|
|
168
|
+
has reported under rather than rendering it as an empty fleet.
|
|
169
|
+
|
|
170
|
+
These commands are **session-only** (`fp login`). They are absent from the
|
|
171
|
+
versioned API an API key authenticates against, so `--api-key` exits 2 with the
|
|
172
|
+
reason rather than failing at the request.
|
|
173
|
+
|
|
174
|
+
## Configuration
|
|
175
|
+
|
|
176
|
+
| Setting | Flag | Env var | Default |
|
|
177
|
+
|---|---|---|---|
|
|
178
|
+
| Dashboard URL | `--base-url` | `FP_DASHBOARD_URL` | `https://app.befailproof.ai` |
|
|
179
|
+
| Active org/tenant | `--org` | `FP_ORG` | chosen at login; saved in `~/.failproofai/fpcli/cli-auth.json` |
|
|
180
|
+
| Session token | `--token` | `FP_TOKEN` | from `~/.failproofai/fpcli/cli-auth.json` |
|
|
181
|
+
| API key (CI) | `--api-key` | `FP_API_KEY` | none; never written to disk |
|
|
182
|
+
| JSON output | `--json` | `FP_JSON` | off |
|
|
183
|
+
| Skip TLS verification | `--insecure` / `--secure` | `FP_INSECURE` | off (saved at login) |
|
|
184
|
+
| Disable usage telemetry | _(none)_ | `FP_ANALYTICS_DISABLED` (or `DO_NOT_TRACK`) | off — [this build sends nothing](#telemetry) |
|
|
185
|
+
|
|
186
|
+
Precedence is **flag > environment variable > config file > built-in default**. A fresh
|
|
187
|
+
install points at the hosted product with no configuration; set `--base-url` or
|
|
188
|
+
`FP_DASHBOARD_URL` for a self-hosted or dev instance and it is saved after `login`.
|
|
189
|
+
|
|
190
|
+
The session lives at `~/.failproofai/fpcli/cli-auth.json` (mode `0600`). The directory
|
|
191
|
+
is resolved as `FP_HOME` > `$FAILPROOFAI_HOME/fpcli` > `~/.failproofai/fpcli` — `FP_HOME`
|
|
192
|
+
names the CLI's own directory and is used as-is, so an existing export keeps addressing
|
|
193
|
+
the same place; `FAILPROOFAI_HOME` names the shared home root, so `fpcli/` is appended.
|
|
194
|
+
|
|
195
|
+
The CLI only ever creates. It will bring `~/.failproofai` into existence on a machine
|
|
196
|
+
that has never run the Enforcement CLI, and leaves a populated one exactly as it found
|
|
197
|
+
it — nothing here removes or rewrites a path it does not own. The file is registered in
|
|
198
|
+
that home's layout (`src/hooks/fp-home.ts`) and classified `user-typed`, which is what
|
|
199
|
+
keeps a layout migration from dropping it.
|
|
200
|
+
|
|
201
|
+
The Python SDK writes its spool under `$FAILPROOFAI_HOME/custom-agents/events`,
|
|
202
|
+
otherwise `~/.failproofai/custom-agents/events` — it moved onto the shared umbrella in
|
|
203
|
+
the same release that renamed this CLI. `FAILPROOFAI_HOME` relocates it the same way it
|
|
204
|
+
relocates this CLI's own directory; what no longer works is `AGENTEYE_HOME`, and nothing
|
|
205
|
+
moves the spool *off* the umbrella except `configure(base_dir=...)` in the application.
|
|
206
|
+
`failproofaid` still drains `~/.agenteye/events` as well, for SDKs old enough to write
|
|
207
|
+
there.
|
|
208
|
+
|
|
209
|
+
> **Upgrading from a version that used `~/.fp/cli.json`?** Nothing to do — the old
|
|
210
|
+
> session is adopted on your next command, so you are not signed out. It is copied, not
|
|
211
|
+
> moved, so rolling back to an older `fp` still works; remove `~/.fp/` yourself once you
|
|
212
|
+
> are happy.
|
|
213
|
+
|
|
214
|
+
For a dashboard with a self-signed or internal TLS certificate, add `--insecure` to skip
|
|
215
|
+
certificate verification (saved at login, so you set it once). This disables protection
|
|
216
|
+
against man-in-the-middle attacks — prefer a valid certificate outside internal/testing use.
|
|
217
|
+
|
|
218
|
+
## Telemetry
|
|
219
|
+
|
|
220
|
+
**Telemetry is currently disabled and this build sends nothing.**
|
|
221
|
+
`TELEMETRY_DISABLED` is `True` in `fp_cli/analytics_config.py`: when the analytics
|
|
222
|
+
host is unreachable the send path stalls every command for ~5s — the shutdown flush
|
|
223
|
+
is bounded, but the client build and first connect attempt are not — so it stays off
|
|
224
|
+
until that path is fully non-blocking. Nothing was removed; re-enabling is one
|
|
225
|
+
constant.
|
|
226
|
+
|
|
227
|
+
The rest of this section describes what it collects **if** it is re-enabled, so you
|
|
228
|
+
can review it in advance rather than discover it in a release note:
|
|
229
|
+
|
|
230
|
+
- which command ran (including its subcommand, e.g. `keys create`), success/exit
|
|
231
|
+
status and duration, the **names** of flags used, and a per-action event for
|
|
232
|
+
mutations (e.g. `api_key_created`, `query_run`) carrying only static names/enums
|
|
233
|
+
and coarse counts;
|
|
234
|
+
- **no data, URLs, tokens, emails, ids, SQL, key secrets, or query values are ever
|
|
235
|
+
sent**, and operators are identified only by an opaque id.
|
|
236
|
+
|
|
237
|
+
`FP_ANALYTICS_DISABLED=1` (or the cross-tool `DO_NOT_TRACK=1`) opts out, and keeps
|
|
238
|
+
working as an opt-out if it is ever switched back on. See
|
|
239
|
+
<https://docs.befailproof.ai/reference/cloud-cli> for the full privacy details.
|
|
240
|
+
|
|
241
|
+
## Exit codes
|
|
242
|
+
|
|
243
|
+
| Code | Meaning |
|
|
244
|
+
|---|---|
|
|
245
|
+
| 0 | Success |
|
|
246
|
+
| 1 | Unexpected error (e.g. an unhandled server status) |
|
|
247
|
+
| 2 | Usage error (bad arguments) |
|
|
248
|
+
| 3 | Cannot reach the dashboard |
|
|
249
|
+
| 4 | Not logged in / session expired |
|
|
250
|
+
| 5 | Authenticated but missing permission |
|
|
251
|
+
| 6 | Resource not found |
|
|
252
|
+
|
|
253
|
+
## Tests
|
|
254
|
+
|
|
255
|
+
```bash
|
|
256
|
+
cd fp-cloud-cli
|
|
257
|
+
uv run --extra dev pytest
|
|
258
|
+
```
|
|
259
|
+
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
"""FailproofAI Cloud CLI — a command-line client for the FailproofAI Cloud API.
|
|
2
|
+
|
|
3
|
+
The query layer lives in :mod:`fp_cli.client` as pure functions that take a
|
|
4
|
+
``ClientContext`` and return plain dataclasses. They never print and never import
|
|
5
|
+
Typer/Rich, so a future MCP server can wrap them with zero duplication.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from ._version import __version__
|
|
9
|
+
|
|
10
|
+
__all__ = ["__version__"]
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
"""The Click that Typer is actually running. Import Click through here, never directly.
|
|
2
|
+
|
|
3
|
+
Typer 0.26 vendored Click into ``typer._click`` and dropped its dependency on the pip
|
|
4
|
+
``click`` distribution. The vendored classes are *different class objects* from pip
|
|
5
|
+
Click's, so every place we hand Click an object, or ask Click about one, breaks when
|
|
6
|
+
the two disagree — and each break is silent, because the code still imports, still
|
|
7
|
+
compiles, and every happy path still passes:
|
|
8
|
+
|
|
9
|
+
* ``ClickException`` — Typer's command runner catches only *its* Click's exception
|
|
10
|
+
class. Errors subclassing pip Click's escape **uncaught**: a typed failure that
|
|
11
|
+
should print ``✗ …`` and exit 5 exits **1 with an empty stderr**, and the
|
|
12
|
+
``rich_format_error`` hook in ``app.py`` (reached only *after* that catch) never
|
|
13
|
+
runs. Same for the ``UsageError``/``BadParameter`` we raise by hand.
|
|
14
|
+
* ``Abort`` — ``typer.prompt`` raises its own Click's ``Abort`` on closed stdin, so a
|
|
15
|
+
pip-Click ``except click.Abort`` stops matching and the clean "no TTY, pass a slug"
|
|
16
|
+
usage error becomes a bare abort.
|
|
17
|
+
* Options — Typer 0.26+ has no ``Option`` class in its vendored Click *at all*: every
|
|
18
|
+
option in a Typer-built tree is a ``typer.core.TyperOption``, subclassing
|
|
19
|
+
``Parameter`` directly. ``isinstance(param, click.Option)`` is then quietly always
|
|
20
|
+
False, which emptied the telemetry flag catalog (``analytics_registry``) while its
|
|
21
|
+
own anti-drift test stayed green — the test asked the same broken question.
|
|
22
|
+
|
|
23
|
+
So: resolve the Click that Typer imported, and speak that one.
|
|
24
|
+
``tests/test_click_compat.py`` asserts the package never imports ``click`` directly
|
|
25
|
+
again, and that our errors are still the class Typer catches.
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
from __future__ import annotations
|
|
29
|
+
|
|
30
|
+
try:
|
|
31
|
+
# typer >= 0.26. pip `click` may well still be installed — it is simply not the
|
|
32
|
+
# Click in play, so binding to it here would reintroduce the whole class of bug.
|
|
33
|
+
from typer._click import ClickException, Command, Parameter
|
|
34
|
+
from typer._click.exceptions import Abort, BadParameter, UsageError
|
|
35
|
+
except ImportError: # typer < 0.26 drives the pip `click` distribution directly
|
|
36
|
+
from click import ( # type: ignore[assignment]
|
|
37
|
+
Abort,
|
|
38
|
+
BadParameter,
|
|
39
|
+
ClickException,
|
|
40
|
+
Command,
|
|
41
|
+
Parameter,
|
|
42
|
+
UsageError,
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
__all__ = [
|
|
46
|
+
"Abort",
|
|
47
|
+
"BadParameter",
|
|
48
|
+
"ClickException",
|
|
49
|
+
"Command",
|
|
50
|
+
"Parameter",
|
|
51
|
+
"UsageError",
|
|
52
|
+
"is_option",
|
|
53
|
+
]
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def is_option(param: Parameter) -> bool:
|
|
57
|
+
"""True if ``param`` is an option (``--flag``), not a positional argument.
|
|
58
|
+
|
|
59
|
+
Class identity cannot answer this across both Clicks — the vendored one has no
|
|
60
|
+
``Option`` class to test against — so read the ``param_type_name`` that Click sets
|
|
61
|
+
on every parameter (``"option"`` / ``"argument"``). That holds for pip Click's
|
|
62
|
+
``Option`` and for ``TyperOption`` alike.
|
|
63
|
+
"""
|
|
64
|
+
return getattr(param, "param_type_name", None) == "option"
|
|
@@ -0,0 +1,332 @@
|
|
|
1
|
+
"""Shared command-layer state and helpers.
|
|
2
|
+
|
|
3
|
+
Kept separate from ``app.py`` so command modules can import these without a
|
|
4
|
+
circular dependency (``app.py`` imports the command modules).
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import dataclasses
|
|
10
|
+
import math
|
|
11
|
+
import re
|
|
12
|
+
from dataclasses import dataclass
|
|
13
|
+
from typing import List, Optional, Sequence, Tuple
|
|
14
|
+
|
|
15
|
+
from . import _click_compat as click # the Click Typer is running; see _click_compat
|
|
16
|
+
from . import config as cfgmod
|
|
17
|
+
from . import dates as _dates
|
|
18
|
+
|
|
19
|
+
# `AuthMode` is DEFINED in `client.py`, not here, even though it is this layer that
|
|
20
|
+
# resolves it: the import runs `_context` -> `client` (below), and `client` needs the
|
|
21
|
+
# enum at runtime for its bearer-vs-cookie branch — defining it here would make that
|
|
22
|
+
# a cycle. Re-exported so `from ._context import AuthMode` reads naturally beside
|
|
23
|
+
# AppState, which is where most callers want it.
|
|
24
|
+
from .client import AuthMode, ClientContext
|
|
25
|
+
from .errors import AuthError, KeyModeUnsupportedError
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
@dataclass
|
|
29
|
+
class AppState:
|
|
30
|
+
json: bool
|
|
31
|
+
base_url: Optional[str]
|
|
32
|
+
token: Optional[str]
|
|
33
|
+
timeout: float
|
|
34
|
+
config: cfgmod.CliConfig
|
|
35
|
+
insecure: bool = False
|
|
36
|
+
org: Optional[str] = None # active tenant slug (flag > env > config)
|
|
37
|
+
# The EXPLICITLY-supplied tenant (global `--org` or `FP_ORG`), before
|
|
38
|
+
# the saved-config fallback. `login` uses this so a *saved* tenant never
|
|
39
|
+
# silently bypasses the interactive org picker — only an explicit choice does.
|
|
40
|
+
# Key mode reuses it as the ONLY org it will ever send (see `build_context`).
|
|
41
|
+
org_explicit: Optional[str] = None
|
|
42
|
+
# The bearer credential in key mode. NEVER written to `cli.json` — see
|
|
43
|
+
# `resolve_auth`.
|
|
44
|
+
api_key: Optional[str] = None
|
|
45
|
+
# Which credential this invocation carries. Defaults to NONE so an AppState
|
|
46
|
+
# built by another path (tests, embedders) is never silently treated as key
|
|
47
|
+
# mode; the transport reads it directly, and everything that is not API_KEY
|
|
48
|
+
# takes the cookie path.
|
|
49
|
+
auth_mode: AuthMode = AuthMode.NONE
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def resolve_auth(
|
|
53
|
+
*,
|
|
54
|
+
api_key: Optional[str],
|
|
55
|
+
api_key_on_cli: bool,
|
|
56
|
+
token: Optional[str],
|
|
57
|
+
token_on_cli: bool,
|
|
58
|
+
saved_token: Optional[str],
|
|
59
|
+
) -> Tuple[AuthMode, Optional[str], Optional[str]]:
|
|
60
|
+
"""Resolve the one credential this invocation uses → ``(mode, api_key, token)``.
|
|
61
|
+
|
|
62
|
+
Precedence, highest first::
|
|
63
|
+
|
|
64
|
+
--api-key AND --token -> usage error, exit 2 (never guess)
|
|
65
|
+
--api-key -> key mode
|
|
66
|
+
--token -> session mode
|
|
67
|
+
FP_API_KEY -> key mode (a key env var beats a token env var)
|
|
68
|
+
FP_TOKEN -> session mode
|
|
69
|
+
cli.json session_token -> session mode
|
|
70
|
+
nothing -> AuthMode.NONE (require_auth then exits 4)
|
|
71
|
+
|
|
72
|
+
``*_on_cli`` distinguishes a flag from its env var, which the *values* cannot:
|
|
73
|
+
an explicit ``--token`` has to beat ``FP_API_KEY`` while
|
|
74
|
+
``FP_API_KEY`` beats ``FP_TOKEN``. Click's
|
|
75
|
+
``ctx.get_parameter_source`` is the only thing that knows the difference.
|
|
76
|
+
|
|
77
|
+
``--api-key ""`` (an unset CI variable spelled out) means "no override": the mode
|
|
78
|
+
stays KEY with an empty credential, so `require_auth` raises instead of quietly
|
|
79
|
+
acting as whichever human is logged in on this machine. That mirrors the
|
|
80
|
+
established ``--token ""`` rule exactly.
|
|
81
|
+
|
|
82
|
+
The key is returned for this process only and is never persisted: a session token
|
|
83
|
+
expires in ~24h, which bounds the blast radius of a leaked `cli.json`; an API key
|
|
84
|
+
is valid until someone revokes it. There is also no honest revocation path from
|
|
85
|
+
here — `keys disable` needs `keys:disable`, which a scoped CI key will not hold —
|
|
86
|
+
so a "clear the saved key" command could not actually revoke anything.
|
|
87
|
+
"""
|
|
88
|
+
if api_key_on_cli and token_on_cli:
|
|
89
|
+
raise click.UsageError(
|
|
90
|
+
"--api-key and --token are mutually exclusive: --api-key authenticates as an "
|
|
91
|
+
"API key against /v1, --token as a signed-in user session. Pass exactly one."
|
|
92
|
+
)
|
|
93
|
+
if api_key_on_cli:
|
|
94
|
+
return AuthMode.API_KEY, api_key, None
|
|
95
|
+
if token_on_cli:
|
|
96
|
+
return AuthMode.SESSION, None, token
|
|
97
|
+
# Neither flag was given: whatever Click resolved came from the environment.
|
|
98
|
+
# (Click treats an empty env var as unset, so `FP_API_KEY=""` falls
|
|
99
|
+
# through to the next rung rather than becoming an empty-credential key mode.)
|
|
100
|
+
if api_key is not None:
|
|
101
|
+
return AuthMode.API_KEY, api_key, None
|
|
102
|
+
if token is not None:
|
|
103
|
+
return AuthMode.SESSION, None, token
|
|
104
|
+
if saved_token:
|
|
105
|
+
return AuthMode.SESSION, None, saved_token
|
|
106
|
+
return AuthMode.NONE, None, None
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def deny_in_key_mode(state: "AppState", command: str, reason: str) -> None:
|
|
110
|
+
"""Fail ``command`` with exit 2 when this invocation carries an API key.
|
|
111
|
+
|
|
112
|
+
Called as the FIRST statement of every command an API key cannot perform, so the
|
|
113
|
+
CLI never opens a connection it already knows will fail — a 401/403 from the
|
|
114
|
+
server would be a much worse explanation than the real one.
|
|
115
|
+
"""
|
|
116
|
+
if state.auth_mode is AuthMode.API_KEY:
|
|
117
|
+
raise KeyModeUnsupportedError(
|
|
118
|
+
f"`fp {command}` does not work with an API key — {reason}.",
|
|
119
|
+
hint="drop --api-key / FP_API_KEY and sign in with fp login",
|
|
120
|
+
)
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
def resolved_base_url(state: AppState) -> str:
|
|
124
|
+
"""The effective dashboard URL.
|
|
125
|
+
|
|
126
|
+
The app callback already resolves flag/env > saved config > the public
|
|
127
|
+
default (`config.DEFAULT_BASE_URL`), so `state.base_url` is normally set.
|
|
128
|
+
This falls back to the same default for any AppState built by another path
|
|
129
|
+
(tests, embedders), so the CLI always has a URL to talk to.
|
|
130
|
+
"""
|
|
131
|
+
return state.base_url or cfgmod.DEFAULT_BASE_URL
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
def _org_header(state: AppState) -> Optional[str]:
|
|
135
|
+
"""The tenant slug to send as ``X-AgentEye-Org``.
|
|
136
|
+
|
|
137
|
+
In KEY mode only an EXPLICIT ``--org`` / ``FP_ORG`` is ever sent. The saved
|
|
138
|
+
`cli.json` org belongs to whichever human logged in on this machine and has no
|
|
139
|
+
bearing on which org a CI key was minted for; sending it would silently ask for
|
|
140
|
+
another tenant's data and get a 403 that reads like a missing permission.
|
|
141
|
+
|
|
142
|
+
(The reverse trap is real too and `whoami` is the pre-flight for it: an
|
|
143
|
+
instance-scoped key with no `--org` resolves server-side to the DEFAULT org and
|
|
144
|
+
answers with that org's data, no error anywhere.)
|
|
145
|
+
"""
|
|
146
|
+
if state.auth_mode is AuthMode.API_KEY:
|
|
147
|
+
return state.org_explicit
|
|
148
|
+
return state.org
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
def build_context(state: AppState) -> ClientContext:
|
|
152
|
+
return ClientContext(
|
|
153
|
+
base_url=resolved_base_url(state),
|
|
154
|
+
token=state.token,
|
|
155
|
+
timeout=state.timeout,
|
|
156
|
+
verify=not state.insecure,
|
|
157
|
+
org=_org_header(state),
|
|
158
|
+
api_key=state.api_key,
|
|
159
|
+
auth_mode=state.auth_mode,
|
|
160
|
+
)
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
def require_auth(state: AppState) -> ClientContext:
|
|
164
|
+
"""Return a client context, or raise if no URL is set / not authenticated."""
|
|
165
|
+
base = resolved_base_url(state) # the URL is the prerequisite — check it first
|
|
166
|
+
if state.auth_mode is AuthMode.API_KEY:
|
|
167
|
+
# `--api-key ""` lands here: key mode, no credential. It must NOT fall back
|
|
168
|
+
# to a saved cookie session (see `resolve_auth`), so this is an auth failure.
|
|
169
|
+
if not state.api_key:
|
|
170
|
+
raise AuthError(
|
|
171
|
+
"No API key supplied. Pass --api-key <key> or set FP_API_KEY."
|
|
172
|
+
)
|
|
173
|
+
# No local expiry check: an API key carries no expiry the CLI can see, and the
|
|
174
|
+
# server is the only authority on whether it is still live.
|
|
175
|
+
return ClientContext(
|
|
176
|
+
base_url=base,
|
|
177
|
+
api_key=state.api_key,
|
|
178
|
+
auth_mode=AuthMode.API_KEY,
|
|
179
|
+
timeout=state.timeout,
|
|
180
|
+
verify=not state.insecure,
|
|
181
|
+
org=_org_header(state),
|
|
182
|
+
)
|
|
183
|
+
if not state.token:
|
|
184
|
+
# A pre-move file with no usable session in it (expired, or only ever
|
|
185
|
+
# held a base_url) reaches here after adoption declined to carry
|
|
186
|
+
# anything. Name it, so the upgrade is not blamed for the logout.
|
|
187
|
+
if cfgmod.legacy_install_detected():
|
|
188
|
+
raise AuthError(
|
|
189
|
+
"Not logged in. Run fp login.\n"
|
|
190
|
+
f"The config moved to {cfgmod.config_path()}; "
|
|
191
|
+
f"{cfgmod.legacy_config_path()} held no usable session to carry "
|
|
192
|
+
"over. It is left in place — remove it when convenient."
|
|
193
|
+
)
|
|
194
|
+
raise AuthError("Not logged in. Run fp login.")
|
|
195
|
+
# Only enforce local expiry when the token came from the stored config; an
|
|
196
|
+
# explicit --token / env override has no known expiry, so trust it.
|
|
197
|
+
if state.token == state.config.session_token and cfgmod.is_expired(state.config):
|
|
198
|
+
raise AuthError("Session expired. Run fp login.")
|
|
199
|
+
return ClientContext(
|
|
200
|
+
base_url=base,
|
|
201
|
+
token=state.token,
|
|
202
|
+
timeout=state.timeout,
|
|
203
|
+
verify=not state.insecure,
|
|
204
|
+
org=_org_header(state),
|
|
205
|
+
)
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
def resolve_dates(
|
|
209
|
+
since: Optional[str], ts_from: Optional[str], ts_to: Optional[str]
|
|
210
|
+
) -> Tuple[Optional[str], Optional[str]]:
|
|
211
|
+
try:
|
|
212
|
+
return _dates.resolve_range(since, ts_from, ts_to)
|
|
213
|
+
except ValueError as exc:
|
|
214
|
+
raise click.BadParameter(str(exc))
|
|
215
|
+
|
|
216
|
+
|
|
217
|
+
def resolve_fields(raw: Optional[str], model_cls: type) -> Optional[List[str]]:
|
|
218
|
+
"""Parse a ``--fields`` CSV into a validated list of model field names (or None).
|
|
219
|
+
|
|
220
|
+
Field names must match the dataclass (and thus the ``--json``) keys of the
|
|
221
|
+
model; unknown names raise a usage error listing the valid set.
|
|
222
|
+
"""
|
|
223
|
+
if not raw:
|
|
224
|
+
return None
|
|
225
|
+
valid = [f.name for f in dataclasses.fields(model_cls)]
|
|
226
|
+
chosen = [f.strip() for f in raw.split(",") if f.strip()]
|
|
227
|
+
unknown = [f for f in chosen if f not in valid]
|
|
228
|
+
if unknown:
|
|
229
|
+
raise click.BadParameter(
|
|
230
|
+
f"unknown field(s): {', '.join(unknown)}. Valid fields: {', '.join(valid)}"
|
|
231
|
+
)
|
|
232
|
+
return chosen
|
|
233
|
+
|
|
234
|
+
|
|
235
|
+
def collect_multi(values: Optional[Sequence[str]]) -> Optional[List[str]]:
|
|
236
|
+
"""Normalize a repeatable + comma-separated CLI option into one flat, de-duplicated list.
|
|
237
|
+
|
|
238
|
+
The single reusable helper behind every multi-value filter. Typer hands us a list with
|
|
239
|
+
one entry per repeated flag (``--env prod --env staging`` → ``["prod", "staging"]``), and
|
|
240
|
+
each entry may itself be a comma-separated group (``--env prod,staging`` → ``["prod,staging"]``).
|
|
241
|
+
This splits every entry on commas, trims surrounding whitespace (so ``--env "prod, staging"``
|
|
242
|
+
works), drops empties (so a trailing comma ``--env prod,`` adds nothing), and de-duplicates
|
|
243
|
+
while preserving first-seen order. Returns ``None`` when nothing usable remains, so an unset
|
|
244
|
+
or blank option stays ``None`` and the client drops the param (unchanged single-value path).
|
|
245
|
+
|
|
246
|
+
A single value still yields the obvious one-item list (``--env prod`` → ``["prod"]``), which
|
|
247
|
+
the client serializes back to a bare ``environment=prod`` — fully backward compatible.
|
|
248
|
+
"""
|
|
249
|
+
if not values:
|
|
250
|
+
return None
|
|
251
|
+
out: List[str] = []
|
|
252
|
+
seen: set = set()
|
|
253
|
+
for raw in values:
|
|
254
|
+
for part in str(raw).split(","):
|
|
255
|
+
v = part.strip()
|
|
256
|
+
if v and v not in seen:
|
|
257
|
+
seen.add(v)
|
|
258
|
+
out.append(v)
|
|
259
|
+
return out or None
|
|
260
|
+
|
|
261
|
+
|
|
262
|
+
def validate_choice(
|
|
263
|
+
value: Optional[str], allowed: Sequence[str], *, flag: str
|
|
264
|
+
) -> Optional[str]:
|
|
265
|
+
"""Return ``value`` if it's None or one of ``allowed``; else a usage error (exit 2).
|
|
266
|
+
|
|
267
|
+
Keeps enum-style flags (``--status``) failing fast client-side with a clear
|
|
268
|
+
message, consistent with ``--order``/``--source``/``--kind`` — rather than the
|
|
269
|
+
server's opaque HTTP 400 (exit 1).
|
|
270
|
+
"""
|
|
271
|
+
if value is None or value in allowed:
|
|
272
|
+
return value
|
|
273
|
+
raise click.BadParameter(
|
|
274
|
+
f"'{value}' is not valid for {flag}. Choose one of: {', '.join(allowed)}.",
|
|
275
|
+
param_hint=flag,
|
|
276
|
+
)
|
|
277
|
+
|
|
278
|
+
|
|
279
|
+
def validate_limit(limit: Optional[int], *, flag: str = "--limit") -> None:
|
|
280
|
+
"""Reject a non-positive row limit up front with a clean usage error (exit 2), rather
|
|
281
|
+
than passing ``0``/negative through to the server, which silently defaults/clamps it to
|
|
282
|
+
a confusing result. Mirrors ``validate_choice`` / ``validate_score_filters``."""
|
|
283
|
+
if limit is not None and limit <= 0:
|
|
284
|
+
raise click.BadParameter("must be a positive integer (at least 1).", param_hint=flag)
|
|
285
|
+
|
|
286
|
+
|
|
287
|
+
def validate_score_filters(values: Optional[Sequence[str]]) -> None:
|
|
288
|
+
"""Validate ``--score`` values are ``KEY:MIN..MAX`` (either bound optional).
|
|
289
|
+
|
|
290
|
+
Without this a malformed value (no ``:`` or no ``..``) is silently sent and
|
|
291
|
+
dropped server-side, returning the UNFILTERED set — a silent footgun. Raises a
|
|
292
|
+
usage error (exit 2) on a bad value. Accepts e.g. ``helpfulness:0.5..0.8``,
|
|
293
|
+
``x:..0.5``, ``y:0.9..``.
|
|
294
|
+
"""
|
|
295
|
+
for v in values or []:
|
|
296
|
+
key, sep, rng = v.partition(":")
|
|
297
|
+
ok = bool(sep) and bool(key.strip()) and ".." in rng
|
|
298
|
+
if ok:
|
|
299
|
+
lo, _, hi = rng.partition("..")
|
|
300
|
+
# Both bounds empty (`helpfulness:..`) is meaningless: the server silently
|
|
301
|
+
# drops it and returns the UNFILTERED set, so reject it client-side too.
|
|
302
|
+
if lo == "" and hi == "":
|
|
303
|
+
ok = False
|
|
304
|
+
for bound in (lo, hi):
|
|
305
|
+
if bound != "":
|
|
306
|
+
try:
|
|
307
|
+
# `float("nan")`/`float("inf")` succeed but the server silently
|
|
308
|
+
# drops the filter — reject non-finite bounds too.
|
|
309
|
+
if not math.isfinite(float(bound)):
|
|
310
|
+
ok = False
|
|
311
|
+
break
|
|
312
|
+
except ValueError:
|
|
313
|
+
ok = False
|
|
314
|
+
break
|
|
315
|
+
if not ok:
|
|
316
|
+
raise click.BadParameter(
|
|
317
|
+
f"'{v}' is not a valid score filter. Use KEY:MIN..MAX (either bound "
|
|
318
|
+
"optional), e.g. helpfulness:0.5..0.8, tool_efficiency:..0.3, factuality:0.9..",
|
|
319
|
+
param_hint="--score",
|
|
320
|
+
)
|
|
321
|
+
|
|
322
|
+
|
|
323
|
+
# Appended to every subcommand's --help so an agent that jumps straight to
|
|
324
|
+
# `fp <command> -h` still learns the argument order + where the global options go.
|
|
325
|
+
GLOBALS_EPILOG = (
|
|
326
|
+
"Argument order: `fp [GLOBAL OPTIONS] <command> [<subcommand>] [ARGS] [OPTIONS]`. "
|
|
327
|
+
"**Global** options come *before* the command — `--json`, `--base-url`, `--token`, "
|
|
328
|
+
"`--api-key`, `--insecure`/`--secure`, `--timeout`, `--quiet`, `--no-color`. A command's (or "
|
|
329
|
+
"subcommand's) own options come *after* it. "
|
|
330
|
+
"e.g. `fp --json keys create ci-bot --permission-set read-only` — `--json` is global, "
|
|
331
|
+
"`keys` the command, `create` the subcommand, `--permission-set` its option."
|
|
332
|
+
)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.0.1b1"
|