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,814 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
|
|
5
|
+
import httpx
|
|
6
|
+
import respx
|
|
7
|
+
|
|
8
|
+
from fp_cli import config
|
|
9
|
+
from fp_cli.app import app
|
|
10
|
+
|
|
11
|
+
BASE = "http://dash.test"
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
# --- auth commands ----------------------------------------------------------
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
@respx.mock
|
|
18
|
+
def test_login_flow_persists_token(home, runner):
|
|
19
|
+
respx.post(f"{BASE}/api/auth/otp/request").mock(return_value=httpx.Response(200, json={"ok": True}))
|
|
20
|
+
respx.post(f"{BASE}/api/auth/otp/verify").mock(
|
|
21
|
+
return_value=httpx.Response(
|
|
22
|
+
200,
|
|
23
|
+
json={"user": {"id": "u1", "email": "me@test"}, "expires_in_secs": 3600},
|
|
24
|
+
headers={"set-cookie": "ae_session=tok-xyz; Path=/"},
|
|
25
|
+
)
|
|
26
|
+
)
|
|
27
|
+
result = runner.invoke(
|
|
28
|
+
app, ["--base-url", BASE, "login", "--email", "me@test"], input="123456\n"
|
|
29
|
+
)
|
|
30
|
+
assert result.exit_code == 0, result.output
|
|
31
|
+
assert config.load_config().session_token == "tok-xyz"
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
@respx.mock
|
|
35
|
+
def test_login_insecure_persists_flag(home, runner):
|
|
36
|
+
respx.post(f"{BASE}/api/auth/otp/request").mock(return_value=httpx.Response(200, json={"ok": True}))
|
|
37
|
+
respx.post(f"{BASE}/api/auth/otp/verify").mock(
|
|
38
|
+
return_value=httpx.Response(
|
|
39
|
+
200,
|
|
40
|
+
json={"user": {"id": "u1", "email": "me@test"}, "expires_in_secs": 3600},
|
|
41
|
+
headers={"set-cookie": "ae_session=tok-xyz; Path=/"},
|
|
42
|
+
)
|
|
43
|
+
)
|
|
44
|
+
result = runner.invoke(
|
|
45
|
+
app, ["--base-url", BASE, "--insecure", "login", "--email", "me@test"], input="123456\n"
|
|
46
|
+
)
|
|
47
|
+
assert result.exit_code == 0, result.output
|
|
48
|
+
saved = config.load_config()
|
|
49
|
+
assert saved.session_token == "tok-xyz"
|
|
50
|
+
assert saved.insecure is True # remembered, so later commands skip TLS verification too
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def _seed_valid_session() -> None:
|
|
54
|
+
config.save_config(
|
|
55
|
+
config.CliConfig(
|
|
56
|
+
base_url=BASE,
|
|
57
|
+
session_token="existing",
|
|
58
|
+
expires_at="2099-01-01T00:00:00Z",
|
|
59
|
+
email="me@test",
|
|
60
|
+
user_id="u1",
|
|
61
|
+
org="acme",
|
|
62
|
+
)
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
@respx.mock
|
|
67
|
+
def test_login_already_signed_in_short_circuits(home, runner):
|
|
68
|
+
_seed_valid_session()
|
|
69
|
+
req = respx.post(f"{BASE}/api/auth/otp/request").mock(
|
|
70
|
+
return_value=httpx.Response(200, json={"ok": True})
|
|
71
|
+
)
|
|
72
|
+
result = runner.invoke(app, ["--base-url", BASE, "login"])
|
|
73
|
+
assert result.exit_code == 0, result.output
|
|
74
|
+
assert "already signed in" in (result.stderr or "")
|
|
75
|
+
assert not req.called # short-circuited — no code requested
|
|
76
|
+
assert config.load_config().session_token == "existing" # session untouched
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
@respx.mock
|
|
80
|
+
def test_login_json_already_signed_in(home, runner):
|
|
81
|
+
_seed_valid_session()
|
|
82
|
+
result = runner.invoke(app, ["--base-url", BASE, "--json", "login"])
|
|
83
|
+
assert result.exit_code == 0, result.output
|
|
84
|
+
payload = json.loads(result.stdout)
|
|
85
|
+
assert payload == {
|
|
86
|
+
"logged_in": True,
|
|
87
|
+
"email": "me@test",
|
|
88
|
+
"org": "acme",
|
|
89
|
+
"already_signed_in": True,
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
@respx.mock
|
|
94
|
+
def test_login_force_reauthenticates_when_signed_in(home, runner):
|
|
95
|
+
_seed_valid_session()
|
|
96
|
+
req = respx.post(f"{BASE}/api/auth/otp/request").mock(
|
|
97
|
+
return_value=httpx.Response(200, json={"ok": True})
|
|
98
|
+
)
|
|
99
|
+
respx.post(f"{BASE}/api/auth/otp/verify").mock(
|
|
100
|
+
return_value=httpx.Response(
|
|
101
|
+
200,
|
|
102
|
+
json={"user": {"id": "u1", "email": "me@test"}, "expires_in_secs": 3600},
|
|
103
|
+
headers={"set-cookie": "ae_session=newtok; Path=/"},
|
|
104
|
+
)
|
|
105
|
+
)
|
|
106
|
+
result = runner.invoke(
|
|
107
|
+
app, ["--base-url", BASE, "login", "--force", "--email", "me@test"], input="123456\n"
|
|
108
|
+
)
|
|
109
|
+
assert result.exit_code == 0, result.output
|
|
110
|
+
assert req.called # --force bypassed the short-circuit and re-authenticated
|
|
111
|
+
assert config.load_config().session_token == "newtok"
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
@respx.mock
|
|
115
|
+
def test_login_expired_session_proceeds(home, runner):
|
|
116
|
+
config.save_config(
|
|
117
|
+
config.CliConfig(
|
|
118
|
+
base_url=BASE,
|
|
119
|
+
session_token="oldtok",
|
|
120
|
+
expires_at="2020-01-01T00:00:00Z", # expired
|
|
121
|
+
email="me@test",
|
|
122
|
+
)
|
|
123
|
+
)
|
|
124
|
+
req = respx.post(f"{BASE}/api/auth/otp/request").mock(
|
|
125
|
+
return_value=httpx.Response(200, json={"ok": True})
|
|
126
|
+
)
|
|
127
|
+
respx.post(f"{BASE}/api/auth/otp/verify").mock(
|
|
128
|
+
return_value=httpx.Response(
|
|
129
|
+
200,
|
|
130
|
+
json={"user": {"id": "u1", "email": "me@test"}, "expires_in_secs": 3600},
|
|
131
|
+
headers={"set-cookie": "ae_session=freshtok; Path=/"},
|
|
132
|
+
)
|
|
133
|
+
)
|
|
134
|
+
result = runner.invoke(app, ["--base-url", BASE, "login", "--email", "me@test"], input="123456\n")
|
|
135
|
+
assert result.exit_code == 0, result.output
|
|
136
|
+
assert req.called # expired → login proceeds normally
|
|
137
|
+
assert config.load_config().session_token == "freshtok"
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
@respx.mock
|
|
141
|
+
def test_secure_flag_overrides_saved_insecure(home, runner):
|
|
142
|
+
# Saved config has insecure=True; an explicit --secure must turn verification back on.
|
|
143
|
+
config.save_config(config.CliConfig(base_url=BASE, insecure=True))
|
|
144
|
+
respx.post(f"{BASE}/api/auth/otp/request").mock(return_value=httpx.Response(200, json={"ok": True}))
|
|
145
|
+
respx.post(f"{BASE}/api/auth/otp/verify").mock(
|
|
146
|
+
return_value=httpx.Response(
|
|
147
|
+
200,
|
|
148
|
+
json={"user": {"id": "u1", "email": "me@test"}, "expires_in_secs": 3600},
|
|
149
|
+
headers={"set-cookie": "ae_session=tok; Path=/"},
|
|
150
|
+
)
|
|
151
|
+
)
|
|
152
|
+
result = runner.invoke(app, ["--secure", "login", "--email", "me@test"], input="123456\n")
|
|
153
|
+
assert result.exit_code == 0, result.output
|
|
154
|
+
assert config.load_config().insecure is False # explicit --secure beat the stored True
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
@respx.mock
|
|
158
|
+
def test_logout_clears_token(logged_in, runner):
|
|
159
|
+
respx.post(f"{BASE}/api/auth/logout").mock(return_value=httpx.Response(200, json={}))
|
|
160
|
+
result = runner.invoke(app, ["logout"])
|
|
161
|
+
assert result.exit_code == 0
|
|
162
|
+
assert config.load_config().session_token is None
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
@respx.mock
|
|
166
|
+
def test_whoami_json(logged_in, runner):
|
|
167
|
+
respx.get(f"{BASE}/api/auth/session").mock(
|
|
168
|
+
return_value=httpx.Response(200, json={"id": "u1", "email": "me@test", "permissions": ["events:read"]})
|
|
169
|
+
)
|
|
170
|
+
result = runner.invoke(app, ["--json", "whoami"])
|
|
171
|
+
assert result.exit_code == 0
|
|
172
|
+
assert json.loads(result.stdout)["email"] == "me@test"
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
def test_whoami_not_logged_in(home, runner):
|
|
176
|
+
result = runner.invoke(app, ["--json", "whoami"])
|
|
177
|
+
assert result.exit_code == 0
|
|
178
|
+
assert json.loads(result.stdout)["logged_in"] is False
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
# --- exit-code contract -----------------------------------------------------
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
def test_not_logged_in_exits_4(home, runner):
|
|
185
|
+
# URL is set (via flag); the missing token is what triggers exit 4.
|
|
186
|
+
result = runner.invoke(app, ["--base-url", BASE, "events"])
|
|
187
|
+
assert result.exit_code == 4
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
def test_no_base_url_defaults_to_hosted(home, runner):
|
|
191
|
+
# No --base-url, no env, no saved config -> the CLI defaults to the hosted
|
|
192
|
+
# dashboard (config.DEFAULT_BASE_URL) rather than erroring. With no token it
|
|
193
|
+
# then fails on auth (exit 4, "not signed in"), NOT a usage error (exit 2)
|
|
194
|
+
# about a missing URL. This pins that the default kicks in.
|
|
195
|
+
result = runner.invoke(app, ["sessions"])
|
|
196
|
+
assert result.exit_code == 4
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
def test_default_base_url_is_the_hosted_product():
|
|
200
|
+
# The default the CLI falls back to is the hosted dashboard.
|
|
201
|
+
from fp_cli import config
|
|
202
|
+
from fp_cli._context import AppState, resolved_base_url
|
|
203
|
+
|
|
204
|
+
assert config.DEFAULT_BASE_URL == "https://app.befailproof.ai"
|
|
205
|
+
# An AppState with no URL resolves to that default.
|
|
206
|
+
state = AppState(json=False, base_url=None, token=None, timeout=30.0, config=config.CliConfig())
|
|
207
|
+
assert resolved_base_url(state) == config.DEFAULT_BASE_URL
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
@respx.mock
|
|
211
|
+
def test_forbidden_exits_5(logged_in, runner):
|
|
212
|
+
respx.get(f"{BASE}/api/users").mock(
|
|
213
|
+
return_value=httpx.Response(403, json={"error": "forbidden"})
|
|
214
|
+
)
|
|
215
|
+
result = runner.invoke(app, ["users", "list"])
|
|
216
|
+
assert result.exit_code == 5
|
|
217
|
+
|
|
218
|
+
|
|
219
|
+
@respx.mock
|
|
220
|
+
def test_network_error_exits_3(logged_in, runner):
|
|
221
|
+
respx.get(f"{BASE}/api/sessions").mock(side_effect=httpx.ConnectError("down"))
|
|
222
|
+
result = runner.invoke(app, ["sessions"])
|
|
223
|
+
assert result.exit_code == 3
|
|
224
|
+
|
|
225
|
+
|
|
226
|
+
def test_bad_since_is_usage_error(logged_in, runner):
|
|
227
|
+
result = runner.invoke(app, ["events", "--since", "bogus"])
|
|
228
|
+
assert result.exit_code == 2 # click usage error
|
|
229
|
+
|
|
230
|
+
|
|
231
|
+
# --- query commands ---------------------------------------------------------
|
|
232
|
+
|
|
233
|
+
|
|
234
|
+
@respx.mock
|
|
235
|
+
def test_events_json(logged_in, runner):
|
|
236
|
+
# --session-id stays on the light feed (payload is a deliberate --full opt-in).
|
|
237
|
+
respx.get(f"{BASE}/api/events/summary").mock(
|
|
238
|
+
return_value=httpx.Response(
|
|
239
|
+
200,
|
|
240
|
+
json={
|
|
241
|
+
"events": [
|
|
242
|
+
{
|
|
243
|
+
"id": 1,
|
|
244
|
+
"session_id": "s",
|
|
245
|
+
"agent_id": "a",
|
|
246
|
+
"event_type": "tool_use",
|
|
247
|
+
"ts": "t",
|
|
248
|
+
"summary": "slack.post",
|
|
249
|
+
"environment": "prod",
|
|
250
|
+
}
|
|
251
|
+
],
|
|
252
|
+
"next_cursor": None,
|
|
253
|
+
},
|
|
254
|
+
)
|
|
255
|
+
)
|
|
256
|
+
result = runner.invoke(app, ["--json", "events", "--session-id", "s"])
|
|
257
|
+
assert result.exit_code == 0, result.output
|
|
258
|
+
assert json.loads(result.stdout)["events"][0]["event_type"] == "tool_use"
|
|
259
|
+
|
|
260
|
+
|
|
261
|
+
@respx.mock
|
|
262
|
+
def test_events_all_paginates(logged_in, runner):
|
|
263
|
+
respx.get(f"{BASE}/api/events/summary").mock(
|
|
264
|
+
side_effect=[
|
|
265
|
+
httpx.Response(
|
|
266
|
+
200,
|
|
267
|
+
json={
|
|
268
|
+
"events": [
|
|
269
|
+
{"id": 3, "session_id": "s", "agent_id": "a", "event_type": "e", "ts": "t", "payload": {}, "environment": "p"},
|
|
270
|
+
{"id": 2, "session_id": "s", "agent_id": "a", "event_type": "e", "ts": "t", "payload": {}, "environment": "p"},
|
|
271
|
+
],
|
|
272
|
+
"next_cursor": 2,
|
|
273
|
+
},
|
|
274
|
+
),
|
|
275
|
+
httpx.Response(
|
|
276
|
+
200,
|
|
277
|
+
json={
|
|
278
|
+
"events": [
|
|
279
|
+
{"id": 1, "session_id": "s", "agent_id": "a", "event_type": "e", "ts": "t", "payload": {}, "environment": "p"}
|
|
280
|
+
],
|
|
281
|
+
"next_cursor": None,
|
|
282
|
+
},
|
|
283
|
+
),
|
|
284
|
+
]
|
|
285
|
+
)
|
|
286
|
+
result = runner.invoke(app, ["--json", "events", "--all", "--limit", "100"])
|
|
287
|
+
assert result.exit_code == 0, result.output
|
|
288
|
+
assert len(json.loads(result.stdout)["events"]) == 3
|
|
289
|
+
|
|
290
|
+
|
|
291
|
+
@respx.mock
|
|
292
|
+
def test_events_feed_routing(logged_in, runner):
|
|
293
|
+
"""Default → light feed (payload-free); --full / --fields payload / --session-id → full feed."""
|
|
294
|
+
resp = httpx.Response(200, json={"events": [], "next_cursor": None})
|
|
295
|
+
light = respx.get(f"{BASE}/api/events/summary").mock(return_value=resp)
|
|
296
|
+
full = respx.get(f"{BASE}/api/events").mock(return_value=resp)
|
|
297
|
+
|
|
298
|
+
def counts():
|
|
299
|
+
return light.call_count, full.call_count
|
|
300
|
+
|
|
301
|
+
l, f = counts()
|
|
302
|
+
# bare / broad → light, never full
|
|
303
|
+
assert runner.invoke(app, ["--json", "events", "--env", "prod"]).exit_code == 0
|
|
304
|
+
assert counts() == (l + 1, f); l, f = counts()
|
|
305
|
+
|
|
306
|
+
# explicit --full → full
|
|
307
|
+
assert runner.invoke(app, ["--json", "events", "--full"]).exit_code == 0
|
|
308
|
+
assert counts() == (l, f + 1); l, f = counts()
|
|
309
|
+
|
|
310
|
+
# --fields payload (payload requested) → full
|
|
311
|
+
assert runner.invoke(app, ["--json", "events", "--fields", "id,payload"]).exit_code == 0
|
|
312
|
+
assert counts() == (l, f + 1); l, f = counts()
|
|
313
|
+
|
|
314
|
+
# --session-id stays on the LIGHT feed (fast session timeline; payload is a --full opt-in)
|
|
315
|
+
assert runner.invoke(app, ["--json", "events", "--session-id", "run-1"]).exit_code == 0
|
|
316
|
+
assert counts() == (l + 1, f); l, f = counts()
|
|
317
|
+
|
|
318
|
+
# --full + --session-id → full (bounded raw-payload read)
|
|
319
|
+
assert runner.invoke(app, ["--json", "events", "--full", "--session-id", "run-1"]).exit_code == 0
|
|
320
|
+
assert counts() == (l, f + 1); l, f = counts()
|
|
321
|
+
|
|
322
|
+
# --fields summary (light-only field) stays on the light feed
|
|
323
|
+
assert runner.invoke(app, ["--json", "events", "--fields", "id,summary"]).exit_code == 0
|
|
324
|
+
assert counts() == (l + 1, f)
|
|
325
|
+
|
|
326
|
+
|
|
327
|
+
@respx.mock
|
|
328
|
+
def test_events_empty_with_filter_shows_recheck_hint(logged_in, runner):
|
|
329
|
+
# A filtered run that matches nothing exits 0 (not an error) and nudges the user to
|
|
330
|
+
# re-check the value, pointing at the facet that lists valid values.
|
|
331
|
+
respx.get(f"{BASE}/api/events/summary").mock(
|
|
332
|
+
return_value=httpx.Response(200, json={"events": [], "next_cursor": None})
|
|
333
|
+
)
|
|
334
|
+
result = runner.invoke(app, ["events", "--env", "xyz"])
|
|
335
|
+
assert result.exit_code == 0, result.output
|
|
336
|
+
assert "no events match these filters" in result.output # the box (stdout)
|
|
337
|
+
assert "double-check" in result.stderr
|
|
338
|
+
assert "--env" in result.stderr
|
|
339
|
+
assert "fp list envs" in result.stderr # facet discovery hint
|
|
340
|
+
|
|
341
|
+
|
|
342
|
+
@respx.mock
|
|
343
|
+
def test_events_empty_no_filter_no_hint(logged_in, runner):
|
|
344
|
+
# A bare run with 0 rows is a genuinely empty window, not a typo — no recheck nudge.
|
|
345
|
+
respx.get(f"{BASE}/api/events/summary").mock(
|
|
346
|
+
return_value=httpx.Response(200, json={"events": [], "next_cursor": None})
|
|
347
|
+
)
|
|
348
|
+
result = runner.invoke(app, ["events"])
|
|
349
|
+
assert result.exit_code == 0, result.output
|
|
350
|
+
assert "no events in this window" in result.output
|
|
351
|
+
assert "double-check" not in result.stderr
|
|
352
|
+
|
|
353
|
+
|
|
354
|
+
@respx.mock
|
|
355
|
+
def test_events_empty_json_has_no_hint(logged_in, runner):
|
|
356
|
+
# --json stays clean: the recheck nudge is stderr chrome, never on the JSON stdout.
|
|
357
|
+
respx.get(f"{BASE}/api/events/summary").mock(
|
|
358
|
+
return_value=httpx.Response(200, json={"events": [], "next_cursor": None})
|
|
359
|
+
)
|
|
360
|
+
result = runner.invoke(app, ["--json", "events", "--env", "xyz"])
|
|
361
|
+
assert result.exit_code == 0, result.output
|
|
362
|
+
assert json.loads(result.stdout) == {"events": [], "next_cursor": None}
|
|
363
|
+
assert "double-check" not in result.stderr
|
|
364
|
+
|
|
365
|
+
|
|
366
|
+
@respx.mock
|
|
367
|
+
def test_sessions_empty_with_filter_shows_recheck_hint(logged_in, runner):
|
|
368
|
+
# Same 0-result nudge on sessions: filtered run matching nothing exits 0 + names filters.
|
|
369
|
+
respx.get(f"{BASE}/api/sessions").mock(
|
|
370
|
+
return_value=httpx.Response(200, json={"sessions": [], "next_cursor": None})
|
|
371
|
+
)
|
|
372
|
+
result = runner.invoke(app, ["sessions", "--env", "xyz"])
|
|
373
|
+
assert result.exit_code == 0, result.output
|
|
374
|
+
assert "no sessions match these filters" in result.output # the box (stdout)
|
|
375
|
+
assert "double-check" in result.stderr
|
|
376
|
+
assert "--env" in result.stderr
|
|
377
|
+
assert "fp list envs" in result.stderr # facet discovery hint
|
|
378
|
+
|
|
379
|
+
|
|
380
|
+
@respx.mock
|
|
381
|
+
def test_sessions_empty_no_filter_no_hint(logged_in, runner):
|
|
382
|
+
# A bare run with 0 rows is a genuinely empty result, not a typo — no recheck nudge.
|
|
383
|
+
respx.get(f"{BASE}/api/sessions").mock(
|
|
384
|
+
return_value=httpx.Response(200, json={"sessions": [], "next_cursor": None})
|
|
385
|
+
)
|
|
386
|
+
result = runner.invoke(app, ["sessions"])
|
|
387
|
+
assert result.exit_code == 0, result.output
|
|
388
|
+
assert "no sessions" in result.output
|
|
389
|
+
assert "double-check" not in result.stderr
|
|
390
|
+
|
|
391
|
+
|
|
392
|
+
@respx.mock
|
|
393
|
+
def test_evals_list_empty_with_filter_shows_recheck_hint(logged_in, runner):
|
|
394
|
+
# Same 0-result nudge on evals list mode.
|
|
395
|
+
respx.get(f"{BASE}/api/evaluations").mock(
|
|
396
|
+
return_value=httpx.Response(200, json={"evaluations": [], "next_cursor": None})
|
|
397
|
+
)
|
|
398
|
+
result = runner.invoke(app, ["evals", "--env", "xyz"])
|
|
399
|
+
assert result.exit_code == 0, result.output
|
|
400
|
+
assert "no evals match these filters" in result.output
|
|
401
|
+
assert "double-check" in result.stderr
|
|
402
|
+
assert "fp list envs" in result.stderr
|
|
403
|
+
|
|
404
|
+
|
|
405
|
+
@respx.mock
|
|
406
|
+
def test_evals_aggregate_empty_with_filter_shows_recheck_hint(logged_in, runner):
|
|
407
|
+
# The nudge also fires in aggregate mode when the slice is empty (total 0).
|
|
408
|
+
respx.get(f"{BASE}/api/evaluations/aggregate").mock(
|
|
409
|
+
return_value=httpx.Response(
|
|
410
|
+
200,
|
|
411
|
+
json={"total": 0, "status_counts": {"done": 0, "error": 0, "timeout": 0},
|
|
412
|
+
"score_stats": [], "timeline": []},
|
|
413
|
+
)
|
|
414
|
+
)
|
|
415
|
+
result = runner.invoke(app, ["evals", "--aggregate", "--agent-id", "nope"])
|
|
416
|
+
assert result.exit_code == 0, result.output
|
|
417
|
+
assert "double-check" in result.stderr
|
|
418
|
+
assert "--agent-id" in result.stderr
|
|
419
|
+
assert "fp list agents" in result.stderr
|
|
420
|
+
|
|
421
|
+
|
|
422
|
+
@respx.mock
|
|
423
|
+
def test_evals_aggregate_nonempty_no_hint(logged_in, runner):
|
|
424
|
+
# A non-empty aggregate (total > 0) never shows the recheck nudge.
|
|
425
|
+
respx.get(f"{BASE}/api/evaluations/aggregate").mock(
|
|
426
|
+
return_value=httpx.Response(
|
|
427
|
+
200,
|
|
428
|
+
json={"total": 5, "status_counts": {"done": 5, "error": 0, "timeout": 0},
|
|
429
|
+
"score_stats": [], "timeline": []},
|
|
430
|
+
)
|
|
431
|
+
)
|
|
432
|
+
result = runner.invoke(app, ["evals", "--aggregate", "--env", "prod"])
|
|
433
|
+
assert result.exit_code == 0, result.output
|
|
434
|
+
assert "double-check" not in result.stderr
|
|
435
|
+
|
|
436
|
+
|
|
437
|
+
@respx.mock
|
|
438
|
+
def test_errors_list_empty_with_filter_shows_recheck_hint(logged_in, runner):
|
|
439
|
+
# Same 0-result nudge on errors list mode; --error-type maps to its own facet.
|
|
440
|
+
respx.get(f"{BASE}/api/events/summary").mock(
|
|
441
|
+
return_value=httpx.Response(200, json={"events": [], "next_cursor": None})
|
|
442
|
+
)
|
|
443
|
+
result = runner.invoke(app, ["errors", "--error-type", "NopeError"])
|
|
444
|
+
assert result.exit_code == 0, result.output
|
|
445
|
+
assert "no errors match these filters" in result.output
|
|
446
|
+
assert "double-check" in result.stderr
|
|
447
|
+
assert "fp list error_types" in result.stderr
|
|
448
|
+
|
|
449
|
+
|
|
450
|
+
@respx.mock
|
|
451
|
+
def test_errors_aggregate_empty_with_filter_shows_recheck_hint(logged_in, runner):
|
|
452
|
+
# Aggregate 0-total with an active filter shows the nudge under the "no errors found" card.
|
|
453
|
+
respx.get(f"{BASE}/api/events/error_summary").mock(
|
|
454
|
+
return_value=httpx.Response(
|
|
455
|
+
200, json={"total": 0, "sessions": 0, "agents": 0, "last_ts": None, "bins": []}
|
|
456
|
+
)
|
|
457
|
+
)
|
|
458
|
+
result = runner.invoke(app, ["errors", "--aggregate", "--env", "xyz"])
|
|
459
|
+
assert result.exit_code == 0, result.output
|
|
460
|
+
assert "no errors found" in result.output # the card stays
|
|
461
|
+
assert "double-check" in result.stderr
|
|
462
|
+
assert "fp list envs" in result.stderr
|
|
463
|
+
|
|
464
|
+
|
|
465
|
+
@respx.mock
|
|
466
|
+
def test_errors_aggregate_no_filter_no_hint(logged_in, runner):
|
|
467
|
+
# 0 errors with NO filter is genuinely clean — celebrate, don't nag.
|
|
468
|
+
respx.get(f"{BASE}/api/events/error_summary").mock(
|
|
469
|
+
return_value=httpx.Response(
|
|
470
|
+
200, json={"total": 0, "sessions": 0, "agents": 0, "last_ts": None, "bins": []}
|
|
471
|
+
)
|
|
472
|
+
)
|
|
473
|
+
result = runner.invoke(app, ["errors", "--aggregate"])
|
|
474
|
+
assert result.exit_code == 0, result.output
|
|
475
|
+
assert "no errors found" in result.output
|
|
476
|
+
assert "double-check" not in result.stderr
|
|
477
|
+
|
|
478
|
+
|
|
479
|
+
@respx.mock
|
|
480
|
+
def test_evals_score_filters(logged_in, runner):
|
|
481
|
+
route = respx.get(f"{BASE}/api/evaluations").mock(
|
|
482
|
+
return_value=httpx.Response(200, json={"evaluations": [], "next_cursor": None})
|
|
483
|
+
)
|
|
484
|
+
result = runner.invoke(
|
|
485
|
+
app,
|
|
486
|
+
["--json", "evals", "--score", "helpfulness:0.5..0.8", "--score", "x:..0.3"],
|
|
487
|
+
)
|
|
488
|
+
assert result.exit_code == 0, result.output
|
|
489
|
+
params = route.calls.last.request.url.params
|
|
490
|
+
assert params["score_filters"] == "helpfulness:0.5..0.8,x:..0.3"
|
|
491
|
+
|
|
492
|
+
|
|
493
|
+
def test_sessions_has_no_score_option(logged_in, runner):
|
|
494
|
+
# `--score` moved to `evals`; sessions must reject it (scores live on evals now).
|
|
495
|
+
assert runner.invoke(app, ["sessions", "--score", "helpfulness:..0.5"]).exit_code == 2
|
|
496
|
+
|
|
497
|
+
|
|
498
|
+
@respx.mock
|
|
499
|
+
def test_sessions_table(logged_in, runner):
|
|
500
|
+
respx.get(f"{BASE}/api/sessions").mock(
|
|
501
|
+
return_value=httpx.Response(
|
|
502
|
+
200,
|
|
503
|
+
json={
|
|
504
|
+
"sessions": [
|
|
505
|
+
{
|
|
506
|
+
"session_id": "s",
|
|
507
|
+
"agent_id": "a",
|
|
508
|
+
"environment": "prod",
|
|
509
|
+
"event_count": 5,
|
|
510
|
+
"started_at": "2026-06-22T12:00:00Z",
|
|
511
|
+
"last_event_at": "2026-06-22T12:05:00Z",
|
|
512
|
+
"latest_evaluation": {
|
|
513
|
+
"evaluation_id": "e1",
|
|
514
|
+
"status": "done",
|
|
515
|
+
"scores": {"helpfulness": 0.9},
|
|
516
|
+
},
|
|
517
|
+
}
|
|
518
|
+
],
|
|
519
|
+
"next_cursor": None,
|
|
520
|
+
},
|
|
521
|
+
)
|
|
522
|
+
)
|
|
523
|
+
result = runner.invoke(app, ["sessions"])
|
|
524
|
+
assert result.exit_code == 0, result.output
|
|
525
|
+
assert "sessions" in result.stdout # the boxed panel title
|
|
526
|
+
assert "done" in result.stdout # status flattened up from latest_evaluation
|
|
527
|
+
|
|
528
|
+
|
|
529
|
+
@respx.mock
|
|
530
|
+
def test_sessions_json_flattens_status_and_scores(logged_in, runner):
|
|
531
|
+
# Flattening: status/scores are lifted to the top level of each row for back-compat,
|
|
532
|
+
# while the full evaluation stays under `latest_evaluation`.
|
|
533
|
+
respx.get(f"{BASE}/api/sessions").mock(
|
|
534
|
+
return_value=httpx.Response(
|
|
535
|
+
200,
|
|
536
|
+
json={
|
|
537
|
+
"sessions": [
|
|
538
|
+
{
|
|
539
|
+
"session_id": "s",
|
|
540
|
+
"agent_id": "a",
|
|
541
|
+
"environment": "prod",
|
|
542
|
+
"last_event_at": "2026-06-22T12:05:00Z",
|
|
543
|
+
"latest_evaluation": {"status": "error", "scores": {"x": 0.5}},
|
|
544
|
+
}
|
|
545
|
+
],
|
|
546
|
+
"next_cursor": None,
|
|
547
|
+
},
|
|
548
|
+
)
|
|
549
|
+
)
|
|
550
|
+
result = runner.invoke(app, ["--json", "sessions"])
|
|
551
|
+
assert result.exit_code == 0, result.output
|
|
552
|
+
row = json.loads(result.stdout)["sessions"][0]
|
|
553
|
+
assert row["status"] == "error" # flattened to top level
|
|
554
|
+
assert row["scores"] == {"x": 0.5} # flattened to top level
|
|
555
|
+
assert row["latest_evaluation"]["status"] == "error" # nested source preserved
|
|
556
|
+
|
|
557
|
+
|
|
558
|
+
# ── multi-agent roster (agents column) ─────────────────────────────────────
|
|
559
|
+
|
|
560
|
+
_MULTI_AGENT_SESSION = {
|
|
561
|
+
"session_id": "s", "agent_id": "agent-codegen", "environment": "dev",
|
|
562
|
+
"last_event_at": "2026-07-16T12:05:00Z",
|
|
563
|
+
"agents": [
|
|
564
|
+
{"agent_id": "agent-codegen", "event_count": 52},
|
|
565
|
+
{"agent_id": "agent-linter", "event_count": 18},
|
|
566
|
+
{"agent_id": "agent-testgen", "event_count": 9},
|
|
567
|
+
],
|
|
568
|
+
"latest_evaluation": None,
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
|
|
572
|
+
@respx.mock
|
|
573
|
+
def test_sessions_json_includes_agents_roster(logged_in, runner):
|
|
574
|
+
respx.get(f"{BASE}/api/sessions").mock(
|
|
575
|
+
return_value=httpx.Response(200, json={"sessions": [_MULTI_AGENT_SESSION], "next_cursor": None})
|
|
576
|
+
)
|
|
577
|
+
result = runner.invoke(app, ["--json", "sessions"])
|
|
578
|
+
assert result.exit_code == 0, result.output
|
|
579
|
+
row = json.loads(result.stdout)["sessions"][0]
|
|
580
|
+
# the full nested roster (agent_id + event_count) is carried through verbatim
|
|
581
|
+
assert row["agents"] == _MULTI_AGENT_SESSION["agents"]
|
|
582
|
+
|
|
583
|
+
|
|
584
|
+
@respx.mock
|
|
585
|
+
def test_sessions_table_badges_multi_agent(logged_in, runner):
|
|
586
|
+
respx.get(f"{BASE}/api/sessions").mock(
|
|
587
|
+
return_value=httpx.Response(200, json={"sessions": [_MULTI_AGENT_SESSION], "next_cursor": None})
|
|
588
|
+
)
|
|
589
|
+
result = runner.invoke(app, ["sessions"])
|
|
590
|
+
assert result.exit_code == 0, result.output
|
|
591
|
+
assert "+2" in result.stdout # 3 agents → +2 badge
|
|
592
|
+
assert "1 multi-agent" in result.stderr # footer count
|
|
593
|
+
assert "fp sessions --agents" in result.stderr # footer hint
|
|
594
|
+
|
|
595
|
+
|
|
596
|
+
@respx.mock
|
|
597
|
+
def test_sessions_agents_flag_expands_roster(logged_in, runner):
|
|
598
|
+
respx.get(f"{BASE}/api/sessions").mock(
|
|
599
|
+
return_value=httpx.Response(200, json={"sessions": [_MULTI_AGENT_SESSION], "next_cursor": None})
|
|
600
|
+
)
|
|
601
|
+
result = runner.invoke(app, ["sessions", "--agents"])
|
|
602
|
+
assert result.exit_code == 0, result.output
|
|
603
|
+
# the roster is expanded → the other agents' names + event counts are now visible
|
|
604
|
+
assert "agent-linter" in result.stdout and "agent-testgen" in result.stdout
|
|
605
|
+
assert "18 ev" in result.stdout
|
|
606
|
+
|
|
607
|
+
|
|
608
|
+
@respx.mock
|
|
609
|
+
def test_sessions_single_agent_no_badge(logged_in, runner):
|
|
610
|
+
respx.get(f"{BASE}/api/sessions").mock(
|
|
611
|
+
return_value=httpx.Response(200, json={
|
|
612
|
+
"sessions": [{
|
|
613
|
+
"session_id": "s", "agent_id": "solo", "environment": "dev",
|
|
614
|
+
"last_event_at": "2026-07-16T12:05:00Z",
|
|
615
|
+
"agents": [{"agent_id": "solo", "event_count": 5}],
|
|
616
|
+
"latest_evaluation": None,
|
|
617
|
+
}],
|
|
618
|
+
"next_cursor": None,
|
|
619
|
+
})
|
|
620
|
+
)
|
|
621
|
+
result = runner.invoke(app, ["sessions"])
|
|
622
|
+
assert result.exit_code == 0, result.output
|
|
623
|
+
assert "+" not in result.stdout # single agent → no badge
|
|
624
|
+
assert "multi-agent" not in result.stderr # footer omits the segment
|
|
625
|
+
|
|
626
|
+
|
|
627
|
+
@respx.mock
|
|
628
|
+
def test_sessions_unevaluated_row_blank_status(logged_in, runner):
|
|
629
|
+
# A session never evaluated → latest_evaluation null → blank status (no crash).
|
|
630
|
+
respx.get(f"{BASE}/api/sessions").mock(
|
|
631
|
+
return_value=httpx.Response(
|
|
632
|
+
200,
|
|
633
|
+
json={
|
|
634
|
+
"sessions": [
|
|
635
|
+
{
|
|
636
|
+
"session_id": "s",
|
|
637
|
+
"agent_id": "a",
|
|
638
|
+
"environment": "prod",
|
|
639
|
+
"last_event_at": "2026-06-22T12:05:00Z",
|
|
640
|
+
"latest_evaluation": None,
|
|
641
|
+
}
|
|
642
|
+
],
|
|
643
|
+
"next_cursor": None,
|
|
644
|
+
},
|
|
645
|
+
)
|
|
646
|
+
)
|
|
647
|
+
result = runner.invoke(app, ["--json", "sessions"])
|
|
648
|
+
assert result.exit_code == 0, result.output
|
|
649
|
+
row = json.loads(result.stdout)["sessions"][0]
|
|
650
|
+
assert row["status"] == ""
|
|
651
|
+
assert row["scores"] is None
|
|
652
|
+
assert row["latest_evaluation"] is None
|
|
653
|
+
|
|
654
|
+
|
|
655
|
+
def test_version_command(home, runner):
|
|
656
|
+
from fp_cli import __version__
|
|
657
|
+
|
|
658
|
+
result = runner.invoke(app, ["version"])
|
|
659
|
+
assert result.exit_code == 0
|
|
660
|
+
assert __version__ in result.stdout # shown in the branded box
|
|
661
|
+
|
|
662
|
+
|
|
663
|
+
def test_version_json_global_form(home, runner):
|
|
664
|
+
# JSON via the GLOBAL --json (before the command), per the global option format.
|
|
665
|
+
from fp_cli import __version__
|
|
666
|
+
|
|
667
|
+
result = runner.invoke(app, ["--json", "version"])
|
|
668
|
+
assert result.exit_code == 0, result.output
|
|
669
|
+
assert json.loads(result.stdout) == {"version": __version__}
|
|
670
|
+
|
|
671
|
+
|
|
672
|
+
def test_version_json_after_command_is_rejected(home, runner):
|
|
673
|
+
# `--json` AFTER the command is NOT accepted — globals come before (usage error).
|
|
674
|
+
result = runner.invoke(app, ["version", "--json"])
|
|
675
|
+
assert result.exit_code == 2
|
|
676
|
+
|
|
677
|
+
|
|
678
|
+
def test_help_command(home, runner):
|
|
679
|
+
result = runner.invoke(app, ["help"])
|
|
680
|
+
assert result.exit_code == 0
|
|
681
|
+
out = result.stdout
|
|
682
|
+
assert "Commands" in out # the grouped top-level panel
|
|
683
|
+
# the four purpose groups, in order
|
|
684
|
+
for g in ("ESSENTIALS", "OBSERVE", "MANAGE", "TOOLS"):
|
|
685
|
+
assert g in out
|
|
686
|
+
assert out.index("ESSENTIALS") < out.index("OBSERVE") < out.index("MANAGE") < out.index("TOOLS")
|
|
687
|
+
assert "sessions" in out and "EXAMPLES" in out
|
|
688
|
+
|
|
689
|
+
|
|
690
|
+
def test_insecure_threads_verify_into_client_context():
|
|
691
|
+
from fp_cli._context import AppState, build_context, require_auth
|
|
692
|
+
|
|
693
|
+
cfg = config.CliConfig(base_url=BASE, session_token="t", expires_at="2999-01-01T00:00:00Z")
|
|
694
|
+
insecure = AppState(json=False, base_url=BASE, token="t", timeout=30.0, config=cfg, insecure=True)
|
|
695
|
+
assert require_auth(insecure).verify is False
|
|
696
|
+
assert build_context(insecure).verify is False
|
|
697
|
+
|
|
698
|
+
secure = AppState(json=False, base_url=BASE, token="t", timeout=30.0, config=cfg, insecure=False)
|
|
699
|
+
assert require_auth(secure).verify is True
|
|
700
|
+
|
|
701
|
+
|
|
702
|
+
# --- new filters, --fields projection, and -h -------------------------------
|
|
703
|
+
|
|
704
|
+
|
|
705
|
+
@respx.mock
|
|
706
|
+
def test_evals_agent_id_and_score_filters(logged_in, runner):
|
|
707
|
+
route = respx.get(f"{BASE}/api/evaluations").mock(
|
|
708
|
+
return_value=httpx.Response(200, json={"evaluations": [], "next_cursor": None})
|
|
709
|
+
)
|
|
710
|
+
result = runner.invoke(
|
|
711
|
+
app, ["--json", "evals", "--agent-id", "bot-1", "--score", "helpfulness:..0.5"]
|
|
712
|
+
)
|
|
713
|
+
assert result.exit_code == 0, result.output
|
|
714
|
+
params = route.calls.last.request.url.params
|
|
715
|
+
assert params["agent_id"] == "bot-1"
|
|
716
|
+
assert params["score_filters"] == "helpfulness:..0.5"
|
|
717
|
+
assert "latest_per_session" not in params # not deduped
|
|
718
|
+
|
|
719
|
+
|
|
720
|
+
@respx.mock
|
|
721
|
+
def test_fields_projection_json(logged_in, runner):
|
|
722
|
+
# --fields without `payload` stays on the light feed.
|
|
723
|
+
respx.get(f"{BASE}/api/events/summary").mock(
|
|
724
|
+
return_value=httpx.Response(
|
|
725
|
+
200,
|
|
726
|
+
json={
|
|
727
|
+
"events": [
|
|
728
|
+
{"id": 1, "session_id": "s", "agent_id": "a", "event_type": "tool_use", "ts": "t", "summary": "slack.post", "environment": "prod"}
|
|
729
|
+
],
|
|
730
|
+
"next_cursor": None,
|
|
731
|
+
},
|
|
732
|
+
)
|
|
733
|
+
)
|
|
734
|
+
result = runner.invoke(app, ["--json", "events", "--fields", "id,event_type"])
|
|
735
|
+
assert result.exit_code == 0, result.output
|
|
736
|
+
assert json.loads(result.stdout)["events"][0] == {"id": 1, "event_type": "tool_use"}
|
|
737
|
+
|
|
738
|
+
|
|
739
|
+
def test_fields_unknown_is_usage_error(logged_in, runner):
|
|
740
|
+
result = runner.invoke(app, ["--json", "events", "--fields", "bogus"])
|
|
741
|
+
assert result.exit_code == 2 # BadParameter lists the valid fields
|
|
742
|
+
|
|
743
|
+
|
|
744
|
+
def test_dash_h_alias_shows_help(home, runner):
|
|
745
|
+
result = runner.invoke(app, ["-h"])
|
|
746
|
+
assert result.exit_code == 0
|
|
747
|
+
assert "Commands" in result.stdout and "sessions" in result.stdout
|
|
748
|
+
assert "ESSENTIALS" in result.stdout # the grouped top-level help, via the rich_format_help override
|
|
749
|
+
|
|
750
|
+
|
|
751
|
+
def test_subcommand_dash_h(home, runner):
|
|
752
|
+
result = runner.invoke(app, ["events", "-h"])
|
|
753
|
+
assert result.exit_code == 0
|
|
754
|
+
assert "--fields" in result.stdout
|
|
755
|
+
|
|
756
|
+
|
|
757
|
+
@respx.mock
|
|
758
|
+
def test_all_reports_a_resumable_cursor_when_it_stops_on_the_limit(logged_in, runner):
|
|
759
|
+
"""`--all` truncated silently and then asserted it had not.
|
|
760
|
+
|
|
761
|
+
`--limit` defaults to 50, so `fp --json events --session-id X --all` — the
|
|
762
|
+
exact line the docs give for reading a whole session — made ONE request,
|
|
763
|
+
returned 50 rows out of however many exist, and emitted
|
|
764
|
+
`"next_cursor": null`, which positively states the feed is exhausted. A
|
|
765
|
+
script or an agent reading that JSON concludes the session had 50 events and
|
|
766
|
+
has nothing to resume from. The CLI had the live cursor in hand at that
|
|
767
|
+
moment and threw it away.
|
|
768
|
+
"""
|
|
769
|
+
def handler(request):
|
|
770
|
+
limit = int(dict(request.url.params).get("limit", 50))
|
|
771
|
+
return httpx.Response(
|
|
772
|
+
200,
|
|
773
|
+
json={
|
|
774
|
+
"events": [
|
|
775
|
+
{"id": i, "session_id": "s", "agent_id": "a", "event_type": "e",
|
|
776
|
+
"ts": "t", "environment": "p"}
|
|
777
|
+
for i in range(limit)
|
|
778
|
+
],
|
|
779
|
+
"next_cursor": "more-to-come",
|
|
780
|
+
},
|
|
781
|
+
)
|
|
782
|
+
|
|
783
|
+
route = respx.get(f"{BASE}/api/events/summary").mock(side_effect=handler)
|
|
784
|
+
result = runner.invoke(app, ["--json", "events", "--session-id", "run-001", "--all"])
|
|
785
|
+
|
|
786
|
+
assert result.exit_code == 0, result.output
|
|
787
|
+
body = json.loads(result.stdout)
|
|
788
|
+
assert len(body["events"]) == 50
|
|
789
|
+
assert route.call_count == 1
|
|
790
|
+
assert body["next_cursor"] == "more-to-come", (
|
|
791
|
+
"a walk that stopped on --limit must hand back somewhere to resume, not null"
|
|
792
|
+
)
|
|
793
|
+
|
|
794
|
+
|
|
795
|
+
@respx.mock
|
|
796
|
+
def test_all_reports_no_cursor_when_the_feed_really_is_exhausted(logged_in, runner):
|
|
797
|
+
"""The other half: a complete walk must still say so."""
|
|
798
|
+
respx.get(f"{BASE}/api/events/summary").mock(
|
|
799
|
+
return_value=httpx.Response(
|
|
800
|
+
200,
|
|
801
|
+
json={
|
|
802
|
+
"events": [
|
|
803
|
+
{"id": 1, "session_id": "s", "agent_id": "a", "event_type": "e",
|
|
804
|
+
"ts": "t", "environment": "p"}
|
|
805
|
+
],
|
|
806
|
+
"next_cursor": None,
|
|
807
|
+
},
|
|
808
|
+
)
|
|
809
|
+
)
|
|
810
|
+
result = runner.invoke(app, ["--json", "events", "--all", "--limit", "100"])
|
|
811
|
+
assert result.exit_code == 0, result.output
|
|
812
|
+
body = json.loads(result.stdout)
|
|
813
|
+
assert len(body["events"]) == 1
|
|
814
|
+
assert body["next_cursor"] is None
|