failproofai 1.0.1 → 1.0.2-beta.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.next/standalone/.next/BUILD_ID +1 -1
- package/.next/standalone/.next/build-manifest.json +3 -3
- package/.next/standalone/.next/prerender-manifest.json +6 -6
- package/.next/standalone/.next/required-server-files.json +1 -1
- package/.next/standalone/.next/server/app/_global-error/page/server-reference-manifest.json +1 -1
- package/.next/standalone/.next/server/app/_global-error/page.js +2 -2
- package/.next/standalone/.next/server/app/_global-error/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/_global-error.html +1 -1
- package/.next/standalone/.next/server/app/_global-error.rsc +7 -7
- package/.next/standalone/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +6 -6
- package/.next/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +7 -7
- package/.next/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/_not-found/page/server-reference-manifest.json +1 -1
- package/.next/standalone/.next/server/app/_not-found/page.js +2 -2
- package/.next/standalone/.next/server/app/_not-found/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/_not-found.html +1 -1
- package/.next/standalone/.next/server/app/_not-found.rsc +15 -15
- package/.next/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +15 -15
- package/.next/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +14 -14
- package/.next/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/api/audit/invite/route.js +3 -2
- package/.next/standalone/.next/server/app/api/audit/invite/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/audit/run/route.js +5 -4
- package/.next/standalone/.next/server/app/api/audit/run/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/audit/status/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/auth/login-request/route.js +2 -2
- package/.next/standalone/.next/server/app/api/auth/login-request/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/auth/login-verify/route.js +4 -3
- package/.next/standalone/.next/server/app/api/auth/login-verify/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/auth/logout/route.js +4 -4
- package/.next/standalone/.next/server/app/api/auth/logout/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/auth/status/route.js +4 -4
- package/.next/standalone/.next/server/app/api/auth/status/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/download/[project]/[session]/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/audit/page/server-reference-manifest.json +2 -2
- package/.next/standalone/.next/server/app/audit/page.js +2 -2
- package/.next/standalone/.next/server/app/audit/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/audit/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/index.html +1 -1
- package/.next/standalone/.next/server/app/index.rsc +15 -15
- package/.next/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +14 -14
- package/.next/standalone/.next/server/app/index.segments/_full.segment.rsc +15 -15
- package/.next/standalone/.next/server/app/index.segments/_tree.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/page/server-reference-manifest.json +1 -1
- package/.next/standalone/.next/server/app/page.js +2 -2
- package/.next/standalone/.next/server/app/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/policies/page/server-reference-manifest.json +66 -22
- package/.next/standalone/.next/server/app/policies/page.js +5 -4
- package/.next/standalone/.next/server/app/policies/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/policies/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/project/[name]/page/server-reference-manifest.json +1 -1
- package/.next/standalone/.next/server/app/project/[name]/page.js +2 -2
- package/.next/standalone/.next/server/app/project/[name]/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/project/[name]/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page/react-loadable-manifest.json +2 -2
- package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page/server-reference-manifest.json +2 -2
- package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page.js +2 -2
- package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/projects/page/server-reference-manifest.json +1 -1
- package/.next/standalone/.next/server/app/projects/page.js +2 -2
- package/.next/standalone/.next/server/app/projects/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/projects/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/settings/page/server-reference-manifest.json +4 -4
- package/.next/standalone/.next/server/app/settings/page.js +1 -1
- package/.next/standalone/.next/server/app/settings/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/settings/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/chunks/{[externals]__1qzqcio._.js → [externals]__0e32-7c._.js} +1 -1
- package/.next/standalone/.next/server/chunks/[externals]__1s462yl._.js +3 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__01eqny5._.js +3 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0_0xu5z._.js +1 -1
- package/.next/standalone/.next/server/chunks/{[root-of-the-server]__1p8poxd._.js → [root-of-the-server]__0igxt32._.js} +2 -2
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0neqlu-._.js +3 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0o07qi9._.js +3 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0t9oqnn._.js +3 -0
- package/.next/standalone/.next/server/chunks/_0bs-v8p._.js +3 -0
- package/.next/standalone/.next/server/chunks/_0tovk6q._.js +3 -0
- package/.next/standalone/.next/server/chunks/_0trp3yc._.js +3 -0
- package/.next/standalone/.next/server/chunks/_185gyk9._.js +3 -0
- package/.next/standalone/.next/server/chunks/_1ek68ln._.js +5 -6
- package/.next/standalone/.next/server/chunks/lib_telemetry-id_ts_1k8gj9o._.js +3 -0
- package/.next/standalone/.next/server/chunks/{node_modules_posthog-node_dist_entrypoints_index_node_mjs_09d5c79._.js → node_modules_posthog-node_dist_entrypoints_index_node_mjs_04op_ou._.js} +1 -1
- package/.next/standalone/.next/server/chunks/{node_modules_posthog-node_dist_entrypoints_index_node_mjs_1d2g73o._.js → node_modules_posthog-node_dist_entrypoints_index_node_mjs_0dr20uz._.js} +1 -1
- package/.next/standalone/.next/server/chunks/package_json_[json]_cjs_1nxcc4v._.js +1 -1
- package/.next/standalone/.next/server/chunks/src_hooks_0xbw3bu._.js +12 -0
- package/.next/standalone/.next/server/chunks/src_hooks_fp-home_ts_09kv0bn._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__013jr2b._.js +4 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__01wy8d-._.js +4 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__02npjtd._.js +4 -0
- package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__0spkm68._.js → [root-of-the-server]__07gm3zl._.js} +21 -20
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0da85px._.js +4 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0ftmoxc._.js +4 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0l4748x._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0oa1lav._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0p-5p8u._.js +4 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0s740oi._.js +6 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__192nhvh._.js +5 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1p2otjt._.js +4 -0
- package/.next/standalone/.next/server/chunks/ssr/_08x1r5t._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/_0bwu160._.js +23 -0
- package/.next/standalone/.next/server/chunks/ssr/{_0u5zauv._.js → _0ns6k3a._.js} +1 -1
- package/.next/standalone/.next/server/chunks/ssr/_1u8-lu2._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/_1zopuov._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/app_audit__components_audit-dashboard_tsx_0p9ud47._.js +13 -13
- package/.next/standalone/.next/server/chunks/ssr/app_global-error_tsx_1kp6l3x._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/app_policies_hooks-client_tsx_19dqvpc._.js +2 -2
- package/.next/standalone/.next/server/chunks/ssr/app_settings_settings-client_tsx_20lq-mq._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/src_hooks_08o7bqo._.js +10 -0
- package/.next/standalone/.next/server/chunks/ssr/src_hooks_builtin-policies_ts_09j2ndl._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/src_hooks_fp-home_ts_0je3xkv._.js +1 -1
- package/.next/standalone/.next/server/middleware-build-manifest.js +3 -3
- package/.next/standalone/.next/server/middleware.js +1 -1
- package/.next/standalone/.next/server/pages/404.html +1 -1
- package/.next/standalone/.next/server/pages/500.html +1 -1
- package/.next/standalone/.next/server/server-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/server-reference-manifest.json +71 -27
- package/.next/standalone/.next/static/chunks/{3c808qrfcq0im.js → 01u1cf8js2yd6.js} +1 -1
- package/.next/standalone/.next/static/chunks/{0k5050ezjvv49.js → 0izgu7nqh2zp-.js} +1 -1
- package/.next/standalone/.next/static/chunks/0u4-org_e7zde.js +1 -0
- package/.next/standalone/.next/static/chunks/1mfzsja6ftim5.css +2 -0
- package/.next/standalone/.next/static/chunks/{2zpp1we2jv6tb.js → 1tynnrrh711lr.js} +1 -1
- package/.next/standalone/.next/static/chunks/{1ide1wl2f7zi1.js → 22rqvlr7p_1mv.js} +13 -13
- package/.next/standalone/.next/static/chunks/{31s1smp-g7gks.js → 2whxqyskpirbk.js} +1 -1
- package/.next/standalone/.next/static/chunks/31mwk8k0q3_3g.js +6 -0
- package/.next/standalone/.next/static/chunks/3ms6gxfm82t6s.js +1 -0
- package/.next/standalone/.next/static/chunks/{37fcpc36x9gpe.js → 3tbbs_d8xrvyp.js} +1 -1
- package/.next/standalone/.next/static/chunks/43nlc236oo898.js +1 -0
- package/.next/standalone/SECURITY.md +5 -4
- package/.next/standalone/app/actions/get-hooks-config.ts +108 -17
- package/.next/standalone/app/actions/pack-actions.ts +163 -0
- package/.next/standalone/app/audit/_components/run-progress.tsx +1 -1
- package/.next/standalone/app/audit/_components/share-templates.ts +13 -13
- package/.next/standalone/app/policies/hooks-client.tsx +320 -15
- package/.next/standalone/components/navbar.tsx +2 -28
- package/.next/standalone/components/reach-developers.tsx +8 -1
- package/.next/standalone/docs-old/agenteye/python-sdk-skill.mdx +1 -1
- package/.next/standalone/docs-old/agenteye/python-sdk.mdx +59 -35
- package/.next/standalone/docs-old/i18n/README.ar.md +1 -0
- package/.next/standalone/docs-old/i18n/README.de.md +1 -0
- package/.next/standalone/docs-old/i18n/README.es.md +1 -0
- package/.next/standalone/docs-old/i18n/README.fr.md +1 -0
- package/.next/standalone/docs-old/i18n/README.he.md +1 -0
- package/.next/standalone/docs-old/i18n/README.hi.md +1 -0
- package/.next/standalone/docs-old/i18n/README.it.md +1 -0
- package/.next/standalone/docs-old/i18n/README.ja.md +1 -0
- package/.next/standalone/docs-old/i18n/README.ko.md +1 -0
- package/.next/standalone/docs-old/i18n/README.pt-br.md +1 -0
- package/.next/standalone/docs-old/i18n/README.ru.md +1 -0
- package/.next/standalone/docs-old/i18n/README.tr.md +1 -0
- package/.next/standalone/docs-old/i18n/README.vi.md +1 -0
- package/.next/standalone/docs-old/i18n/README.zh.md +1 -0
- package/.next/standalone/fp-cloud-cli/CHANGELOG.md +431 -0
- package/.next/standalone/fp-cloud-cli/LICENSE +42 -0
- package/.next/standalone/fp-cloud-cli/MANIFEST.in +10 -0
- package/.next/standalone/fp-cloud-cli/README.md +259 -0
- package/.next/standalone/fp-cloud-cli/fp_cli/__init__.py +10 -0
- package/.next/standalone/fp-cloud-cli/fp_cli/__main__.py +4 -0
- package/.next/standalone/fp-cloud-cli/fp_cli/_click_compat.py +64 -0
- package/.next/standalone/fp-cloud-cli/fp_cli/_context.py +332 -0
- package/.next/standalone/fp-cloud-cli/fp_cli/_version.py +1 -0
- package/.next/standalone/fp-cloud-cli/fp_cli/analytics.py +432 -0
- package/.next/standalone/fp-cloud-cli/fp_cli/analytics_config.py +77 -0
- package/.next/standalone/fp-cloud-cli/fp_cli/analytics_registry.py +83 -0
- package/.next/standalone/fp-cloud-cli/fp_cli/app.py +492 -0
- package/.next/standalone/fp-cloud-cli/fp_cli/auth.py +160 -0
- package/.next/standalone/fp-cloud-cli/fp_cli/client.py +1694 -0
- package/.next/standalone/fp-cloud-cli/fp_cli/commands/__init__.py +0 -0
- package/.next/standalone/fp-cloud-cli/fp_cli/commands/_write.py +214 -0
- package/.next/standalone/fp-cloud-cli/fp_cli/commands/agent_cmds.py +407 -0
- package/.next/standalone/fp-cloud-cli/fp_cli/commands/alerts_cmds.py +445 -0
- package/.next/standalone/fp-cloud-cli/fp_cli/commands/audits_cmds.py +1054 -0
- package/.next/standalone/fp-cloud-cli/fp_cli/commands/auth_cmds.py +512 -0
- package/.next/standalone/fp-cloud-cli/fp_cli/commands/errors_cmds.py +190 -0
- package/.next/standalone/fp-cloud-cli/fp_cli/commands/evals_cmds.py +161 -0
- package/.next/standalone/fp-cloud-cli/fp_cli/commands/events_cmds.py +159 -0
- package/.next/standalone/fp-cloud-cli/fp_cli/commands/fleet_cmds.py +416 -0
- package/.next/standalone/fp-cloud-cli/fp_cli/commands/guardrails_cmds.py +148 -0
- package/.next/standalone/fp-cloud-cli/fp_cli/commands/incidents_cmds.py +472 -0
- package/.next/standalone/fp-cloud-cli/fp_cli/commands/keys_cmds.py +407 -0
- package/.next/standalone/fp-cloud-cli/fp_cli/commands/list_cmds.py +63 -0
- package/.next/standalone/fp-cloud-cli/fp_cli/commands/orgs_cmds.py +319 -0
- package/.next/standalone/fp-cloud-cli/fp_cli/commands/policies_cmds.py +499 -0
- package/.next/standalone/fp-cloud-cli/fp_cli/commands/queries_cmds.py +378 -0
- package/.next/standalone/fp-cloud-cli/fp_cli/commands/sessions_cmds.py +151 -0
- package/.next/standalone/fp-cloud-cli/fp_cli/commands/settings_cmds.py +150 -0
- package/.next/standalone/fp-cloud-cli/fp_cli/commands/usage_cmds.py +35 -0
- package/.next/standalone/fp-cloud-cli/fp_cli/commands/users_cmds.py +404 -0
- package/.next/standalone/fp-cloud-cli/fp_cli/config.py +330 -0
- package/.next/standalone/fp-cloud-cli/fp_cli/dates.py +78 -0
- package/.next/standalone/fp-cloud-cli/fp_cli/enforcement.py +345 -0
- package/.next/standalone/fp-cloud-cli/fp_cli/errors.py +98 -0
- package/.next/standalone/fp-cloud-cli/fp_cli/models.py +891 -0
- package/.next/standalone/fp-cloud-cli/fp_cli/orgs.py +30 -0
- package/.next/standalone/fp-cloud-cli/fp_cli/output.py +6593 -0
- package/.next/standalone/fp-cloud-cli/fp_cli/permissions.py +208 -0
- package/.next/standalone/fp-cloud-cli/fp_cli/policy_check.py +290 -0
- package/.next/standalone/fp-cloud-cli/fp_cli/py.typed +0 -0
- package/.next/standalone/fp-cloud-cli/fp_cli/select.py +322 -0
- package/.next/standalone/fp-cloud-cli/fp_cli/theme.py +53 -0
- package/.next/standalone/fp-cloud-cli/pyproject.toml +106 -0
- package/.next/standalone/fp-cloud-cli/skill/SKILL.md +302 -0
- package/.next/standalone/fp-cloud-cli/skill/agents/openai.yaml +8 -0
- package/.next/standalone/fp-cloud-cli/skill/references/commands.md +254 -0
- package/.next/standalone/fp-cloud-cli/tests/__init__.py +0 -0
- package/.next/standalone/fp-cloud-cli/tests/conftest.py +92 -0
- package/.next/standalone/fp-cloud-cli/tests/test_alerting.py +644 -0
- package/.next/standalone/fp-cloud-cli/tests/test_analytics.py +385 -0
- package/.next/standalone/fp-cloud-cli/tests/test_audits.py +950 -0
- package/.next/standalone/fp-cloud-cli/tests/test_auth.py +98 -0
- package/.next/standalone/fp-cloud-cli/tests/test_auth_mode.py +272 -0
- package/.next/standalone/fp-cloud-cli/tests/test_click_compat.py +118 -0
- package/.next/standalone/fp-cloud-cli/tests/test_client.py +299 -0
- package/.next/standalone/fp-cloud-cli/tests/test_commands.py +814 -0
- package/.next/standalone/fp-cloud-cli/tests/test_config.py +99 -0
- package/.next/standalone/fp-cloud-cli/tests/test_dashboards_agent.py +340 -0
- package/.next/standalone/fp-cloud-cli/tests/test_dates.py +76 -0
- package/.next/standalone/fp-cloud-cli/tests/test_enforcement_logic.py +481 -0
- package/.next/standalone/fp-cloud-cli/tests/test_facets.py +170 -0
- package/.next/standalone/fp-cloud-cli/tests/test_failproofai_home.py +722 -0
- package/.next/standalone/fp-cloud-cli/tests/test_fp_home_contract.py +198 -0
- package/.next/standalone/fp-cloud-cli/tests/test_hardening.py +186 -0
- package/.next/standalone/fp-cloud-cli/tests/test_help_table_coverage.py +127 -0
- package/.next/standalone/fp-cloud-cli/tests/test_keys_queries.py +496 -0
- package/.next/standalone/fp-cloud-cli/tests/test_list.py +59 -0
- package/.next/standalone/fp-cloud-cli/tests/test_multivalue.py +183 -0
- package/.next/standalone/fp-cloud-cli/tests/test_no_customer_identifiers.py +168 -0
- package/.next/standalone/fp-cloud-cli/tests/test_operator.py +492 -0
- package/.next/standalone/fp-cloud-cli/tests/test_orgs.py +692 -0
- package/.next/standalone/fp-cloud-cli/tests/test_output.py +1732 -0
- package/.next/standalone/fp-cloud-cli/tests/test_policy_check.py +191 -0
- package/.next/standalone/fp-cloud-cli/tests/test_readme_matches_reality.py +125 -0
- package/.next/standalone/fp-cloud-cli/tests/test_review_fixes.py +161 -0
- package/.next/standalone/fp-cloud-cli/tests/test_telemetry_completeness.py +90 -0
- package/.next/standalone/fp-cloud-cli/tests/test_usage.py +115 -0
- package/.next/standalone/fp-cloud-cli/tests/test_v1_origin_diagnostic.py +70 -0
- package/.next/standalone/fp-cloud-cli/tests/test_v1_routing.py +214 -0
- package/.next/standalone/fp-cloud-cli/tests/test_whoami.py +104 -0
- package/.next/standalone/fp-cloud-cli/uv.lock +552 -0
- package/.next/standalone/package.json +27 -21
- package/.next/standalone/sdk/python/CHANGELOG.md +70 -0
- package/.next/standalone/sdk/python/LICENSE +42 -0
- package/.next/standalone/sdk/python/MANIFEST.in +21 -0
- package/.next/standalone/sdk/python/README.md +466 -0
- package/.next/standalone/sdk/python/docs/README.md +249 -0
- package/.next/standalone/sdk/python/docs/_shared/README.md +43 -0
- package/.next/standalone/sdk/python/docs/_shared/__init__.py +195 -0
- package/.next/standalone/sdk/python/docs/crewai/README.md +328 -0
- package/.next/standalone/sdk/python/docs/crewai/examples/quickstart.py +61 -0
- package/.next/standalone/sdk/python/docs/crewai/examples/research_crew.py +92 -0
- package/.next/standalone/sdk/python/docs/langgraph/README.md +481 -0
- package/.next/standalone/sdk/python/docs/langgraph/examples/quickstart.py +51 -0
- package/.next/standalone/sdk/python/docs/langgraph/examples/research_agent.py +86 -0
- package/.next/standalone/sdk/python/docs/langgraph/examples/supervisor_handoff.py +83 -0
- package/.next/standalone/sdk/python/docs/llama_index/README.md +332 -0
- package/.next/standalone/sdk/python/docs/llama_index/examples/quickstart.py +62 -0
- package/.next/standalone/sdk/python/docs/llama_index/examples/research_agent.py +93 -0
- package/.next/standalone/sdk/python/docs/manual/README.md +491 -0
- package/.next/standalone/sdk/python/docs/manual/examples/quickstart.py +57 -0
- package/.next/standalone/sdk/python/docs/manual/examples/research_agent.py +133 -0
- package/.next/standalone/sdk/python/docs/pydantic_ai/README.md +323 -0
- package/.next/standalone/sdk/python/docs/pydantic_ai/examples/quickstart.py +48 -0
- package/.next/standalone/sdk/python/docs/pydantic_ai/examples/research_agent.py +94 -0
- package/.next/standalone/sdk/python/failproofai_sdk/__init__.py +131 -0
- package/.next/standalone/sdk/python/failproofai_sdk/_context.py +218 -0
- package/.next/standalone/sdk/python/failproofai_sdk/_environment.py +78 -0
- package/.next/standalone/sdk/python/failproofai_sdk/_events.py +752 -0
- package/.next/standalone/sdk/python/failproofai_sdk/_resolver.py +118 -0
- package/.next/standalone/sdk/python/failproofai_sdk/_runtime.py +21 -0
- package/.next/standalone/sdk/python/failproofai_sdk/_schema.py +317 -0
- package/.next/standalone/sdk/python/failproofai_sdk/_scopes.py +422 -0
- package/.next/standalone/sdk/python/failproofai_sdk/_version.py +1 -0
- package/.next/standalone/sdk/python/failproofai_sdk/_writer.py +750 -0
- package/.next/standalone/sdk/python/failproofai_sdk/integrations/__init__.py +274 -0
- package/.next/standalone/sdk/python/failproofai_sdk/integrations/_compat.py +247 -0
- package/.next/standalone/sdk/python/failproofai_sdk/integrations/_core.py +1076 -0
- package/.next/standalone/sdk/python/failproofai_sdk/integrations/crewai.py +1669 -0
- package/.next/standalone/sdk/python/failproofai_sdk/integrations/langchain.py +2151 -0
- package/.next/standalone/sdk/python/failproofai_sdk/integrations/llama_index.py +1588 -0
- package/.next/standalone/sdk/python/failproofai_sdk/integrations/pydantic_ai.py +977 -0
- package/.next/standalone/sdk/python/failproofai_sdk/py.typed +0 -0
- package/.next/standalone/sdk/python/pyproject.toml +92 -0
- package/.next/standalone/sdk/python/skill/SKILL.md +399 -0
- package/.next/standalone/sdk/python/skill/agents/openai.yaml +8 -0
- package/.next/standalone/sdk/python/skill/references/events.md +234 -0
- package/.next/standalone/sdk/python/skill/references/frameworks.md +314 -0
- package/.next/standalone/sdk/python/skill/references/install.md +100 -0
- package/.next/standalone/sdk/python/skill/references/integration.md +352 -0
- package/.next/standalone/sdk/python/tests/__init__.py +0 -0
- package/.next/standalone/sdk/python/tests/conftest.py +169 -0
- package/.next/standalone/sdk/python/tests/integrations/__init__.py +6 -0
- package/.next/standalone/sdk/python/tests/integrations/test_crewai.py +1299 -0
- package/.next/standalone/sdk/python/tests/integrations/test_langchain.py +2338 -0
- package/.next/standalone/sdk/python/tests/integrations/test_llama_index.py +1576 -0
- package/.next/standalone/sdk/python/tests/integrations/test_pydantic_ai.py +1102 -0
- package/.next/standalone/sdk/python/tests/test_context.py +410 -0
- package/.next/standalone/sdk/python/tests/test_docs.py +411 -0
- package/.next/standalone/sdk/python/tests/test_durability.py +1352 -0
- package/.next/standalone/sdk/python/tests/test_encoding.py +571 -0
- package/.next/standalone/sdk/python/tests/test_integrations.py +1127 -0
- package/.next/standalone/sdk/python/tests/test_no_customer_identifiers.py +214 -0
- package/.next/standalone/sdk/python/tests/test_packaging.py +185 -0
- package/.next/standalone/sdk/python/tests/test_resolver_umbrella.py +178 -0
- package/.next/standalone/sdk/python/tests/test_scopes.py +487 -0
- package/.next/standalone/sdk/python/tests/test_sdk.py +923 -0
- package/.next/standalone/sdk/python/tests/test_server_contract.py +650 -0
- package/.next/standalone/sdk/python/tests/test_site_docs.py +371 -0
- package/.next/standalone/sdk/python/tests/test_skill_snippets.py +165 -0
- package/.next/standalone/sdk/python/tests/test_spool_contract.py +287 -0
- package/.next/standalone/sdk/python/tests/test_spool_creation.py +360 -0
- package/.next/standalone/sdk/python/tests/test_wire_format.py +279 -0
- package/.next/standalone/sdk/python/tests/test_zero_dependencies.py +378 -0
- package/.next/standalone/sdk/python/uv.lock +6005 -0
- package/.next/standalone/server.js +1 -1
- package/README.md +77 -21
- package/bin/failproofai.mjs +579 -306
- package/dist/cli.mjs +6499 -3662
- package/dist/index.js +5 -5
- package/dist/worker.mjs +2390 -1978
- package/package.json +27 -21
- package/scripts/build-policy-pack.mjs +121 -0
- package/scripts/changelog-section.py +77 -0
- package/scripts/prune-standalone.mjs +3 -0
- package/scripts/python-version.py +232 -0
- package/scripts/translate-docs/cli.ts +21 -0
- package/scripts/translate-docs/mintlify-nav.ts +85 -13
- package/scripts/translate-docs/translator.ts +66 -11
- package/src/audit/cache.ts +40 -3
- package/src/audit/cli.ts +1 -1
- package/src/audit/index.ts +1 -1
- package/src/audit/replay.ts +89 -3
- package/src/audit/report.ts +5 -5
- package/src/audit/schedule-cli.ts +49 -53
- package/src/hooks/builtin-policies.ts +217 -579
- package/src/hooks/cloud-enrollment-cli.ts +66 -27
- package/src/hooks/cloud-managed-policies.ts +6 -1
- package/src/hooks/configure-wizard.ts +56 -369
- package/src/hooks/custom-hooks-loader.ts +89 -6
- package/src/hooks/fp-home.ts +92 -4
- package/src/hooks/fp-reset.ts +34 -2
- package/src/hooks/handler.ts +181 -11
- package/src/hooks/harness-cli.ts +79 -25
- package/src/hooks/hook-activity-store.ts +13 -3
- package/src/hooks/install-prompt.ts +25 -3
- package/src/hooks/manager.ts +424 -172
- package/src/hooks/migrations.ts +2 -2
- package/src/hooks/pack-cli.ts +885 -0
- package/src/hooks/pack-failclosed.ts +189 -0
- package/src/hooks/pack-manifest.ts +352 -0
- package/src/hooks/pack-store.ts +891 -0
- package/src/hooks/policy-catalog.ts +535 -0
- package/src/hooks/policy-evaluator.ts +24 -16
- package/src/hooks/policy-registry.ts +8 -2
- package/src/hooks/policy-types.ts +26 -0
- package/src/hooks/session-pause-cli.ts +33 -9
- package/src/hooks/tui.ts +695 -39
- package/src/hooks/types.ts +15 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0_usx0j._.js +0 -3
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0_zk340._.js +0 -3
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0agdw2a._.js +0 -3
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0d1u_na._.js +0 -3
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0oy4kxb._.js +0 -3
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0zf034w._.js +0 -3
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__1tvn4wf._.js +0 -3
- package/.next/standalone/.next/server/chunks/_0lxbzdq._.js +0 -3
- package/.next/standalone/.next/server/chunks/_1ger0a2._.js +0 -3
- package/.next/standalone/.next/server/chunks/_1zuiiy3._.js +0 -3
- package/.next/standalone/.next/server/chunks/node_modules_posthog-node_dist_entrypoints_index_node_mjs_1iiwm5e._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__07tbbf3._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0kc8_v5._.js +0 -4
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0kgy9da._.js +0 -4
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0mhqk5a._.js +0 -4
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0pltpn8._.js +0 -4
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0qkcyf-._.js +0 -4
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0w0x_ok._.js +0 -4
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1_dfocn._.js +0 -4
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1bfex62._.js +0 -5
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1kzapg5._.js +0 -6
- package/.next/standalone/.next/server/chunks/ssr/_0lvyuv4._.js +0 -23
- package/.next/standalone/.next/static/chunks/2_2lhuojq2xop.js +0 -1
- package/.next/standalone/.next/static/chunks/36fw6yk1s6x_f.js +0 -1
- package/.next/standalone/.next/static/chunks/3k7mxbp9hiom0.js +0 -1
- package/.next/standalone/.next/static/chunks/3tzq0260n17ae.js +0 -6
- package/.next/standalone/.next/static/chunks/3wq9x0c-7ld5d.css +0 -2
- package/src/hooks/policy-presets.ts +0 -135
- /package/.next/standalone/.next/static/{ZklXxyA5ygsWSQz2u_zii → I5kEb9kSgYS63j4iqfVLO}/_buildManifest.js +0 -0
- /package/.next/standalone/.next/static/{ZklXxyA5ygsWSQz2u_zii → I5kEb9kSgYS63j4iqfVLO}/_clientMiddlewareManifest.js +0 -0
- /package/.next/standalone/.next/static/{ZklXxyA5ygsWSQz2u_zii → I5kEb9kSgYS63j4iqfVLO}/_ssgManifest.js +0 -0
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
"""The CLI writes into a home another component owns, so both halves must agree.
|
|
2
|
+
|
|
3
|
+
`~/.failproofai/` is a governed layout. `src/hooks/fp-home.ts` is its register —
|
|
4
|
+
"nothing outside this file may join a path onto the failproofai home" — and
|
|
5
|
+
`resettablePaths()` is a *filter over* `HOME_CLASSES`, so what protects the
|
|
6
|
+
CLI's credential from a reset is its entry in that table, not the fact that the
|
|
7
|
+
file happens to exist.
|
|
8
|
+
|
|
9
|
+
Until this file existed, nothing checked that the two sides agreed. `config.py`
|
|
10
|
+
said so itself, in a comment above `FPCLI_SUBDIR`: *"Mirrors ``fpcliDir`` in
|
|
11
|
+
``src/hooks/fp-home.ts`` — change one, change the other; nothing checks."*
|
|
12
|
+
Verified by experiment: renaming `fpcliDir` to `fp-cloud-cli` in the TypeScript and
|
|
13
|
+
leaving Python alone left 53 TS tests and 59 Python tests all passing, with the
|
|
14
|
+
register describing a directory nothing writes and the real credential sitting
|
|
15
|
+
at a path the register had never heard of.
|
|
16
|
+
|
|
17
|
+
That is the same shape as the SDK's `tests/test_spool_contract.py` next door,
|
|
18
|
+
which reads the Rust and the TypeScript that define its spool root. This is the
|
|
19
|
+
CLI's version of it.
|
|
20
|
+
|
|
21
|
+
The assertions run against source text rather than a running Node, because
|
|
22
|
+
requiring a toolchain to test a Python package would mean this skips in every
|
|
23
|
+
environment that matters and guards nothing — the failure mode the SDK's own
|
|
24
|
+
contract test had for its whole life before it moved into this repo.
|
|
25
|
+
"""
|
|
26
|
+
from __future__ import annotations
|
|
27
|
+
|
|
28
|
+
import os
|
|
29
|
+
import re
|
|
30
|
+
from pathlib import Path
|
|
31
|
+
|
|
32
|
+
import pytest
|
|
33
|
+
|
|
34
|
+
from fp_cli import config as cfg
|
|
35
|
+
|
|
36
|
+
# tests/ -> fp-cloud-cli/ -> repo root.
|
|
37
|
+
#
|
|
38
|
+
# Guarded: `parents[2]` raises IndexError on a shallower tree, and a shallower
|
|
39
|
+
# tree is exactly the packaged-sdist case `_read_source` below handles. Raising
|
|
40
|
+
# here would be a COLLECTION error, which aborts the whole suite instead of
|
|
41
|
+
# skipping the one file that needs the repository.
|
|
42
|
+
_HERE = Path(__file__).resolve()
|
|
43
|
+
REPO_ROOT = _HERE.parents[2] if len(_HERE.parents) > 2 else _HERE.parent
|
|
44
|
+
FP_HOME_TS = REPO_ROOT / "src" / "hooks" / "fp-home.ts"
|
|
45
|
+
|
|
46
|
+
#: Set by CI. Turns "the source I read is missing" from a skip into a failure.
|
|
47
|
+
REQUIRE = os.environ.get("FP_CLI_REQUIRE_CONTRACT", "").strip().lower() in {
|
|
48
|
+
"1",
|
|
49
|
+
"true",
|
|
50
|
+
"yes",
|
|
51
|
+
"on",
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def _read_source() -> str:
|
|
56
|
+
"""`fp-home.ts`, or skip/fail depending on ``REQUIRE``.
|
|
57
|
+
|
|
58
|
+
Missing means one of two things: this is an installed sdist (fine — there is
|
|
59
|
+
no repo to read), or the file moved (not fine). ``REQUIRE`` distinguishes
|
|
60
|
+
them, because from in here they look identical.
|
|
61
|
+
"""
|
|
62
|
+
if FP_HOME_TS.is_file():
|
|
63
|
+
return FP_HOME_TS.read_text(encoding="utf-8")
|
|
64
|
+
message = (
|
|
65
|
+
f"{FP_HOME_TS} is missing. In a packaged sdist that is expected. In the "
|
|
66
|
+
"repository it means the layout register moved, and this contract is now "
|
|
67
|
+
"unguarded — re-point this test at the new location rather than deleting it."
|
|
68
|
+
)
|
|
69
|
+
if REQUIRE:
|
|
70
|
+
pytest.fail(message)
|
|
71
|
+
pytest.skip(message)
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def _declared(pattern: str, source: str, what: str) -> str:
|
|
75
|
+
"""The single capture of `pattern`, failing loudly if it matched 0 or 2+.
|
|
76
|
+
|
|
77
|
+
A regex over source that quietly matches nothing is worse than no test: it
|
|
78
|
+
passes forever while checking a file that has been rewritten around it.
|
|
79
|
+
"""
|
|
80
|
+
matches = re.findall(pattern, source)
|
|
81
|
+
assert len(matches) == 1, (
|
|
82
|
+
f"expected exactly one declaration of {what} in fp-home.ts, found "
|
|
83
|
+
f"{len(matches)}. The register was restructured; re-anchor this test "
|
|
84
|
+
f"rather than loosening the pattern."
|
|
85
|
+
)
|
|
86
|
+
return matches[0]
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
# ─────────────────────────────────────────────────────────────────────────────
|
|
90
|
+
# The paths themselves
|
|
91
|
+
# ─────────────────────────────────────────────────────────────────────────────
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def test_the_subdirectory_name_agrees():
|
|
95
|
+
"""`fpcliDir` in TypeScript vs `FPCLI_SUBDIR` here."""
|
|
96
|
+
source = _read_source()
|
|
97
|
+
declared = _declared(
|
|
98
|
+
r'export const fpcliDir = \(home\?: string\) => atHome\(home, "([^"]+)"\)',
|
|
99
|
+
source,
|
|
100
|
+
"fpcliDir",
|
|
101
|
+
)
|
|
102
|
+
assert declared == cfg.FPCLI_SUBDIR, (
|
|
103
|
+
f"fp-home.ts registers the CLI directory as {declared!r} and this package "
|
|
104
|
+
f"writes {cfg.FPCLI_SUBDIR!r}. The credential would sit at a path the "
|
|
105
|
+
f"layout register has never heard of, so nothing protects it from a reset "
|
|
106
|
+
f"the moment somebody classifies its parent."
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
def test_the_credential_filename_agrees():
|
|
111
|
+
source = _read_source()
|
|
112
|
+
declared = _declared(
|
|
113
|
+
r'export const fpcliAuthFile = \(home\?: string\) => resolve\(fpcliDir\(home\), "([^"]+)"\)',
|
|
114
|
+
source,
|
|
115
|
+
"fpcliAuthFile",
|
|
116
|
+
)
|
|
117
|
+
assert declared == cfg.config_path().name
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
def test_the_home_directory_name_agrees():
|
|
121
|
+
"""Both sides hardcode `.failproofai`; neither imports it from the other."""
|
|
122
|
+
source = _read_source()
|
|
123
|
+
declared = _declared(
|
|
124
|
+
r'return process\.env\.FAILPROOFAI_HOME \|\| resolve\(homedir\(\), "([^"]+)"\)',
|
|
125
|
+
source,
|
|
126
|
+
"the failproofai home",
|
|
127
|
+
)
|
|
128
|
+
home = cfg.base_dir().parent.name
|
|
129
|
+
assert declared == home, f"fp-home.ts says {declared!r}, this package writes {home!r}"
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
def test_the_shared_home_override_variable_agrees():
|
|
133
|
+
"""`FAILPROOFAI_HOME` relocates the whole layout; the CLI must follow it."""
|
|
134
|
+
source = _read_source()
|
|
135
|
+
assert "process.env.FAILPROOFAI_HOME" in source
|
|
136
|
+
assert "FAILPROOFAI_HOME" in Path(cfg.__file__).read_text(encoding="utf-8")
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
# ─────────────────────────────────────────────────────────────────────────────
|
|
140
|
+
# The classification — what actually keeps a reset off the credential
|
|
141
|
+
# ─────────────────────────────────────────────────────────────────────────────
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
def test_the_credential_is_registered_as_user_typed():
|
|
145
|
+
"""This entry, not the file's existence, is what survives `resettablePaths()`.
|
|
146
|
+
|
|
147
|
+
`resettablePaths()` filters `HOME_CLASSES`, so an unregistered path survives
|
|
148
|
+
only by accident — and only until someone lists its parent. `user-typed` is
|
|
149
|
+
the class that says "a person typed this; nothing regenerates it", which is
|
|
150
|
+
exactly a login.
|
|
151
|
+
"""
|
|
152
|
+
source = _read_source()
|
|
153
|
+
assert re.search(r"\{\s*path:\s*fpcliAuthFile,\s*class:\s*\"user-typed\"\s*\}", source), (
|
|
154
|
+
"fpcliAuthFile is no longer classified `user-typed` in HOME_CLASSES. A "
|
|
155
|
+
"reset walks that table; the CLI's session is not regenerable and must "
|
|
156
|
+
"never be on the delete list."
|
|
157
|
+
)
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
def test_the_directory_itself_is_deliberately_unclassified():
|
|
161
|
+
"""`auditDir`'s rule: classify the children, never the parent.
|
|
162
|
+
|
|
163
|
+
A `user-typed` parent would protect a cache added later; a `derived` parent
|
|
164
|
+
would delete the session beside it.
|
|
165
|
+
"""
|
|
166
|
+
source = _read_source()
|
|
167
|
+
assert not re.search(r"\{\s*path:\s*fpcliDir,\s*class:", source), (
|
|
168
|
+
"fpcliDir is now classified as a whole. If the directory has grown a "
|
|
169
|
+
"second file, classify that file — one class cannot be right for a "
|
|
170
|
+
"credential and a cache at once."
|
|
171
|
+
)
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
# ─────────────────────────────────────────────────────────────────────────────
|
|
175
|
+
# The anchors, so none of the above can pass vacuously
|
|
176
|
+
# ─────────────────────────────────────────────────────────────────────────────
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
def test_the_register_still_contains_what_these_patterns_anchor_on():
|
|
180
|
+
"""Every regex above reads source text, and source text gets rewritten.
|
|
181
|
+
|
|
182
|
+
If `fp-home.ts` is restructured so the patterns stop matching, the tests
|
|
183
|
+
above fail loudly via `_declared`. This one covers the rest of the file:
|
|
184
|
+
the names must still be exported and still be mentioned in the class table.
|
|
185
|
+
"""
|
|
186
|
+
source = _read_source()
|
|
187
|
+
for anchor in (
|
|
188
|
+
"export const fpcliDir",
|
|
189
|
+
"export const fpcliAuthFile",
|
|
190
|
+
"export const HOME_CLASSES",
|
|
191
|
+
"fpcliAuthFile, class:",
|
|
192
|
+
):
|
|
193
|
+
assert anchor in source, f"fp-home.ts no longer contains {anchor!r}"
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
def test_this_file_can_be_imported_outside_the_repository():
|
|
197
|
+
"""A collection error here would abort the whole CLI suite, not just this file."""
|
|
198
|
+
assert REPO_ROOT.is_absolute()
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
"""Regression tests for the cli-bug-fix hardening pass.
|
|
2
|
+
|
|
3
|
+
Covers the crash/robustness fixes: pagination cursor type-mix, guarded file reads,
|
|
4
|
+
the non-JSON read-body trap, 429 messaging, explicit ISO date validation, non-finite
|
|
5
|
+
score bounds, and the empty-token (no silent saved-session fallback) guard.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
from fp_cli import _click_compat as click # the Click Typer is running
|
|
11
|
+
import httpx
|
|
12
|
+
import pytest
|
|
13
|
+
import respx
|
|
14
|
+
|
|
15
|
+
from fp_cli import client, config, dates
|
|
16
|
+
from fp_cli._context import validate_score_filters
|
|
17
|
+
from fp_cli.app import app
|
|
18
|
+
from fp_cli.client import ClientContext
|
|
19
|
+
from fp_cli.commands._write import read_text_arg
|
|
20
|
+
from fp_cli.errors import ApiError
|
|
21
|
+
from fp_cli.models import Page
|
|
22
|
+
|
|
23
|
+
BASE = "http://dash.test"
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def _ctx() -> ClientContext:
|
|
27
|
+
return ClientContext(base_url=BASE, token="t", org="globex")
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
# --- paginate: int/str cursor mix must not crash (was a TypeError) ----------
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def test_paginate_mixed_cursor_types_no_crash():
|
|
34
|
+
# start_cursor is a str (from --cursor); pages return int next_cursor.
|
|
35
|
+
pages = [Page(items=[1, 2], next_cursor=100), Page(items=[3], next_cursor=None)]
|
|
36
|
+
seq = iter(pages)
|
|
37
|
+
|
|
38
|
+
def fetch(cursor, limit):
|
|
39
|
+
return next(seq)
|
|
40
|
+
|
|
41
|
+
got = list(client.paginate(fetch, limit=10, page_size=2, start_cursor="9999999999999999"))
|
|
42
|
+
assert got == [1, 2, 3]
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def test_paginate_stops_on_repeated_cursor():
|
|
46
|
+
# A server that returns the same cursor forever must not loop forever.
|
|
47
|
+
def fetch(cursor, limit):
|
|
48
|
+
return Page(items=["x"], next_cursor=42)
|
|
49
|
+
|
|
50
|
+
got = list(client.paginate(fetch, limit=1000, page_size=1, start_cursor=None))
|
|
51
|
+
assert 0 < len(got) < 1000 # bounded by the seen-cursor guard
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
# --- read_text_arg: missing file -> usage error, not a traceback -----------
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def test_read_text_arg_missing_file_is_usage_error():
|
|
58
|
+
with pytest.raises(click.BadParameter):
|
|
59
|
+
read_text_arg("/no/such/file.json")
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
# --- _get_json: non-JSON 2xx body -> ApiError, not raw JSONDecodeError ------
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
@respx.mock
|
|
66
|
+
def test_get_json_non_json_body_raises_clean_apierror():
|
|
67
|
+
respx.get(f"{BASE}/api/events").mock(
|
|
68
|
+
return_value=httpx.Response(200, text="<html>not json</html>")
|
|
69
|
+
)
|
|
70
|
+
with pytest.raises(ApiError):
|
|
71
|
+
client._get_json(_ctx(), "/api/events")
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
@respx.mock
|
|
75
|
+
def test_429_includes_retry_after():
|
|
76
|
+
respx.get(f"{BASE}/api/events").mock(
|
|
77
|
+
return_value=httpx.Response(429, headers={"retry-after": "5"}, json={})
|
|
78
|
+
)
|
|
79
|
+
with pytest.raises(ApiError) as ei:
|
|
80
|
+
client._get_json(_ctx(), "/api/events")
|
|
81
|
+
assert "Retry after 5" in str(ei.value)
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
# --- dates: explicit --from/--to validation --------------------------------
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def test_resolve_range_rejects_garbage_date():
|
|
88
|
+
with pytest.raises(ValueError):
|
|
89
|
+
dates.resolve_range(ts_from="not-a-date")
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def test_resolve_range_rejects_date_only():
|
|
93
|
+
with pytest.raises(ValueError):
|
|
94
|
+
dates.resolve_range(ts_from="2026-06-01")
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def test_resolve_range_accepts_full_iso():
|
|
98
|
+
frm, _ = dates.resolve_range(ts_from="2026-05-01T00:00:00Z")
|
|
99
|
+
assert frm == "2026-05-01T00:00:00Z"
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
# --- validate_score_filters: reject non-finite bounds ----------------------
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
@pytest.mark.parametrize("value", ["helpfulness:nan..", "x:0.0..inf", "y:-inf..1"])
|
|
106
|
+
def test_score_filter_rejects_non_finite(value):
|
|
107
|
+
with pytest.raises(click.BadParameter):
|
|
108
|
+
validate_score_filters([value])
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def test_score_filter_accepts_valid_ranges():
|
|
112
|
+
validate_score_filters(["helpfulness:0.5..0.8", "x:..0.3", "y:0.9.."]) # no raise
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
@pytest.mark.parametrize("value", ["helpfulness:..", "x:..", "metric:.."])
|
|
116
|
+
def test_score_filter_rejects_both_bounds_empty(value):
|
|
117
|
+
# `KEY:..` (no min, no max) is meaningless — the server silently drops it and
|
|
118
|
+
# returns the UNFILTERED set, so it must be a clean client-side usage error.
|
|
119
|
+
with pytest.raises(click.BadParameter):
|
|
120
|
+
validate_score_filters([value])
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
# --- empty --token must NOT silently use the saved session -----------------
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
def test_empty_token_does_not_fall_back_to_saved_session(home, runner):
|
|
127
|
+
config.save_config(config.CliConfig(base_url=BASE, session_token="saved-tok"))
|
|
128
|
+
# `--token ""` (e.g. an unset CI var) is an explicit "no auth", not a fallback.
|
|
129
|
+
result = runner.invoke(app, ["--base-url", BASE, "--token", "", "events"])
|
|
130
|
+
assert result.exit_code == 4, result.output # not logged in (was: used saved-tok)
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
def test_negative_timeout_is_usage_error(home, runner):
|
|
134
|
+
result = runner.invoke(app, ["--base-url", BASE, "--timeout", "-1", "events"])
|
|
135
|
+
assert result.exit_code == 2, result.output
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
def test_a_saved_insecure_does_not_follow_you_to_another_dashboard(logged_in, runner, monkeypatch):
|
|
139
|
+
"""`insecure` was stored per MACHINE and applied to every base URL.
|
|
140
|
+
|
|
141
|
+
One `fp --base-url https://dash.internal --insecure login` against a
|
|
142
|
+
self-signed dev box — the use its own help text names — left certificate
|
|
143
|
+
verification off for the next `fp --base-url https://app.befailproof.ai
|
|
144
|
+
login`, which is the request that carries the OTP and receives the session
|
|
145
|
+
token, and for `fp keys create`, which carries a freshly minted API key in
|
|
146
|
+
the body. `clear_token` deliberately preserves `insecure` across a logout, so
|
|
147
|
+
it outlived the session it was set for.
|
|
148
|
+
"""
|
|
149
|
+
cfg = config.load_config()
|
|
150
|
+
cfg.insecure = True
|
|
151
|
+
cfg.base_url = "https://dash.internal"
|
|
152
|
+
config.save_config(cfg)
|
|
153
|
+
|
|
154
|
+
seen = {}
|
|
155
|
+
|
|
156
|
+
class _Recorder(httpx.Client):
|
|
157
|
+
def __init__(self, *a, **kw):
|
|
158
|
+
seen["verify"] = kw.get("verify")
|
|
159
|
+
super().__init__(*a, **kw)
|
|
160
|
+
|
|
161
|
+
monkeypatch.setattr(httpx, "Client", _Recorder)
|
|
162
|
+
|
|
163
|
+
with respx.mock(assert_all_called=False) as m:
|
|
164
|
+
m.get(url__regex=r".*").mock(return_value=httpx.Response(200, json={"keys": []}))
|
|
165
|
+
runner.invoke(app, ["--base-url", "https://app.befailproof.ai", "keys", "list"])
|
|
166
|
+
|
|
167
|
+
assert seen.get("verify") is not False, (
|
|
168
|
+
"a preference granted for dash.internal was applied to a different dashboard"
|
|
169
|
+
)
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
def test_the_tls_warning_survives_quiet(logged_in, runner, monkeypatch):
|
|
173
|
+
"""`--quiet` shares one gate with cosmetic status chrome; this is not that.
|
|
174
|
+
|
|
175
|
+
`fp --quiet …` on a machine where someone once ran `login --insecure` ran
|
|
176
|
+
every request with verification off and printed nothing anywhere.
|
|
177
|
+
"""
|
|
178
|
+
cfg = config.load_config()
|
|
179
|
+
cfg.insecure = True
|
|
180
|
+
config.save_config(cfg)
|
|
181
|
+
|
|
182
|
+
with respx.mock(assert_all_called=False) as m:
|
|
183
|
+
m.get(url__regex=r".*").mock(return_value=httpx.Response(200, json={"keys": []}))
|
|
184
|
+
result = runner.invoke(app, ["--quiet", "keys", "list"])
|
|
185
|
+
|
|
186
|
+
assert "TLS verification disabled" in result.stderr, result.stderr
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
"""The top-level help screen is a HAND-MAINTAINED table, not Click's command tree.
|
|
2
|
+
|
|
3
|
+
`output.render_top_level_help` renders `_TOP_LEVEL_GROUPS`, a literal list. A command
|
|
4
|
+
registered on the Typer app but missing from that list works perfectly and is invisible
|
|
5
|
+
in `fp help` forever — there is no error, no warning, and no other test that looks.
|
|
6
|
+
|
|
7
|
+
These tests close that gap in both directions, and additionally assert the help text
|
|
8
|
+
carries the current command name, which is the thing a rename silently leaves stale
|
|
9
|
+
(Click derives its own `Usage:` line from argv[0], so the auto-generated half updates
|
|
10
|
+
itself and the hand-written half does not).
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from __future__ import annotations
|
|
14
|
+
|
|
15
|
+
import re
|
|
16
|
+
|
|
17
|
+
from typer.main import get_command
|
|
18
|
+
|
|
19
|
+
from fp_cli import output
|
|
20
|
+
from fp_cli.app import app
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def _registered_commands() -> set[str]:
|
|
24
|
+
"""Every command name Click actually knows about."""
|
|
25
|
+
cmd = get_command(app)
|
|
26
|
+
return set(cmd.commands) # type: ignore[attr-defined]
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def _help_table_commands() -> set[str]:
|
|
30
|
+
"""Every command name the hand-maintained help table advertises."""
|
|
31
|
+
names = set()
|
|
32
|
+
for _group, entries in output._TOP_LEVEL_GROUPS:
|
|
33
|
+
for entry in entries:
|
|
34
|
+
names.add(entry[0])
|
|
35
|
+
return names
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def test_every_registered_command_appears_in_the_help_table():
|
|
39
|
+
missing = _registered_commands() - _help_table_commands()
|
|
40
|
+
assert not missing, (
|
|
41
|
+
f"these commands are registered but absent from output._TOP_LEVEL_GROUPS, so "
|
|
42
|
+
f"`fp help` will never mention them: {sorted(missing)}"
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def test_the_help_table_never_advertises_a_command_that_does_not_exist():
|
|
47
|
+
extra = _help_table_commands() - _registered_commands()
|
|
48
|
+
assert not extra, (
|
|
49
|
+
f"output._TOP_LEVEL_GROUPS advertises commands that are not registered — "
|
|
50
|
+
f"`fp <name>` would be a usage error: {sorted(extra)}"
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def test_the_help_table_is_not_empty():
|
|
55
|
+
"""Guards the assertions above from passing vacuously if the structure changes."""
|
|
56
|
+
assert len(_help_table_commands()) >= 15
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def test_help_chrome_carries_no_retired_product_name():
|
|
60
|
+
"""A rename updates Click's generated usage for free and the prose not at all."""
|
|
61
|
+
rendered = "\n".join(
|
|
62
|
+
str(entry) for _group, entries in output._TOP_LEVEL_GROUPS for entry in entries
|
|
63
|
+
)
|
|
64
|
+
examples = "\n".join(f"{cmd} {why}" for cmd, why in output._TOP_LEVEL_EXAMPLES)
|
|
65
|
+
haystack = f"{rendered}\n{examples}".lower()
|
|
66
|
+
assert "agenteye" not in haystack, "retired product name still present in help chrome"
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def test_the_help_examples_use_the_current_command_name():
|
|
70
|
+
cmds = [cmd for cmd, _why in output._TOP_LEVEL_EXAMPLES]
|
|
71
|
+
assert cmds, "no examples to check"
|
|
72
|
+
assert all(c.startswith("fp ") for c in cmds), (
|
|
73
|
+
f"help examples must invoke `fp`: {[c for c in cmds if not c.startswith('fp ')]}"
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def test_no_module_advertises_the_retired_env_var_namespace():
|
|
78
|
+
"""`FP_*` is this CLI's namespace. `AGENTEYE_*` names that remain must be explicit
|
|
79
|
+
references to OTHER components (the collector's ingest key, the dashboard's admin
|
|
80
|
+
key, the SDK/collector spool dir) and never something this CLI reads."""
|
|
81
|
+
import pathlib
|
|
82
|
+
|
|
83
|
+
pkg = pathlib.Path(output.__file__).resolve().parent
|
|
84
|
+
allowed = {"AGENTEYE_KEY", "AGENTEYE_API_KEY", "AGENTEYE_HOME"}
|
|
85
|
+
found = set()
|
|
86
|
+
for mod in pkg.rglob("*.py"):
|
|
87
|
+
for m in re.finditer(r"AGENTEYE" + r"_[A-Z_]+", mod.read_text()):
|
|
88
|
+
found.add(m.group(0))
|
|
89
|
+
assert found <= allowed, (
|
|
90
|
+
f"these retired env vars are still referenced in the package: {sorted(found - allowed)}"
|
|
91
|
+
)
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def test_every_subcommand_the_hint_column_advertises_actually_exists():
|
|
95
|
+
"""The THIRD element of each row was never checked against anything.
|
|
96
|
+
|
|
97
|
+
`_help_table_commands()` reads `entry[0]` only, so the ten standalone leaves
|
|
98
|
+
and the thirteen group names were guarded in both directions and every
|
|
99
|
+
advertised SUBCOMMAND was not. `issues` listed a bare `comment`, which is not
|
|
100
|
+
a command — the group exposes `comment-add`, `comment-list` and
|
|
101
|
+
`comment-delete` — so `fp help` told users to run something that exits 2 with
|
|
102
|
+
"No such command 'comment'".
|
|
103
|
+
|
|
104
|
+
A `*` suffix is a deliberate wildcard (`comment-*`, `context-*`); a leading
|
|
105
|
+
`-` is a flag, not a subcommand. Everything else must be real.
|
|
106
|
+
"""
|
|
107
|
+
cmd = get_command(app)
|
|
108
|
+
bad = []
|
|
109
|
+
for _group, entries in output._TOP_LEVEL_GROUPS:
|
|
110
|
+
for entry in entries:
|
|
111
|
+
name, hint = entry[0], (entry[2] if len(entry) > 2 else "")
|
|
112
|
+
group = cmd.commands.get(name) # type: ignore[attr-defined]
|
|
113
|
+
subcommands = set(getattr(group, "commands", {}) or {})
|
|
114
|
+
if not subcommands:
|
|
115
|
+
continue # a standalone leaf; its hint is prose, not a command list
|
|
116
|
+
for token in hint.split():
|
|
117
|
+
if token.startswith("-"):
|
|
118
|
+
continue
|
|
119
|
+
if token.endswith("*"):
|
|
120
|
+
prefix = token[:-1]
|
|
121
|
+
if not any(sub.startswith(prefix) for sub in subcommands):
|
|
122
|
+
bad.append(f"{name}: '{token}' matches no subcommand")
|
|
123
|
+
continue
|
|
124
|
+
if token not in subcommands:
|
|
125
|
+
bad.append(f"{name}: '{token}' is not a subcommand of `fp {name}`")
|
|
126
|
+
|
|
127
|
+
assert not bad, "the help screen advertises commands that do not exist:\n " + "\n ".join(bad)
|