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,99 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
import stat
|
|
5
|
+
from datetime import datetime, timedelta, timezone
|
|
6
|
+
|
|
7
|
+
from fp_cli import config
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def _iso(delta: timedelta) -> str:
|
|
11
|
+
return (datetime.now(timezone.utc) + delta).strftime("%Y-%m-%dT%H:%M:%SZ")
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def test_load_missing_returns_defaults(home):
|
|
15
|
+
cfg = config.load_config()
|
|
16
|
+
assert cfg.base_url is None # no default URL — it must be set explicitly
|
|
17
|
+
assert cfg.session_token is None
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def test_save_and_load_roundtrip(home):
|
|
21
|
+
cfg = config.CliConfig(
|
|
22
|
+
base_url="http://x.test",
|
|
23
|
+
session_token="t",
|
|
24
|
+
expires_at="2999-01-01T00:00:00Z",
|
|
25
|
+
email="e@test",
|
|
26
|
+
user_id="u",
|
|
27
|
+
)
|
|
28
|
+
config.save_config(cfg)
|
|
29
|
+
assert config.load_config() == cfg
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def test_save_uses_0600_permissions(home):
|
|
33
|
+
config.save_config(config.CliConfig())
|
|
34
|
+
mode = stat.S_IMODE(os.stat(config.config_path()).st_mode)
|
|
35
|
+
assert mode == 0o600
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def test_base_dir_respects_fp_home(home):
|
|
39
|
+
assert config.base_dir() == home
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def test_clear_token_wipes_session_identity_and_org(home):
|
|
43
|
+
# logout must leave NO stale identity: token, expiry, email, user id, and the
|
|
44
|
+
# active org are all cleared. base_url / insecure / anonymous_id are kept so the
|
|
45
|
+
# next login doesn't need them re-specified and telemetry stays stable.
|
|
46
|
+
config.save_config(
|
|
47
|
+
config.CliConfig(
|
|
48
|
+
base_url="http://x.test",
|
|
49
|
+
session_token="t",
|
|
50
|
+
expires_at="2999-01-01T00:00:00Z",
|
|
51
|
+
email="e@test",
|
|
52
|
+
user_id="u1",
|
|
53
|
+
insecure=True,
|
|
54
|
+
org="acme",
|
|
55
|
+
anonymous_id="anon-123",
|
|
56
|
+
)
|
|
57
|
+
)
|
|
58
|
+
config.clear_token(config.load_config())
|
|
59
|
+
reloaded = config.load_config()
|
|
60
|
+
# cleared — nothing about who/where remains
|
|
61
|
+
assert reloaded.session_token is None
|
|
62
|
+
assert reloaded.expires_at is None
|
|
63
|
+
assert reloaded.email is None
|
|
64
|
+
assert reloaded.user_id is None
|
|
65
|
+
assert reloaded.org is None
|
|
66
|
+
# kept — preferences + stable machine id
|
|
67
|
+
assert reloaded.base_url == "http://x.test"
|
|
68
|
+
assert reloaded.insecure is True
|
|
69
|
+
assert reloaded.anonymous_id == "anon-123"
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def test_corrupt_file_falls_back_to_defaults(home):
|
|
73
|
+
path = config.config_path()
|
|
74
|
+
path.parent.mkdir(parents=True, exist_ok=True)
|
|
75
|
+
path.write_text("{ not json")
|
|
76
|
+
assert config.load_config().base_url is None
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def test_is_expired():
|
|
80
|
+
assert config.is_expired(config.CliConfig()) is True # no token at all
|
|
81
|
+
assert config.is_expired(config.CliConfig(session_token="t")) is True # no expiry
|
|
82
|
+
assert config.is_expired(
|
|
83
|
+
config.CliConfig(session_token="t", expires_at=_iso(timedelta(hours=-1)))
|
|
84
|
+
) is True
|
|
85
|
+
assert config.is_expired(
|
|
86
|
+
config.CliConfig(session_token="t", expires_at=_iso(timedelta(hours=1)))
|
|
87
|
+
) is False
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def test_is_expired_handles_z_suffix_and_skew():
|
|
91
|
+
# 30s in the future is "expired" under the default 60s skew.
|
|
92
|
+
soon = _iso(timedelta(seconds=30))
|
|
93
|
+
assert config.is_expired(config.CliConfig(session_token="t", expires_at=soon)) is True
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def test_insecure_defaults_false_and_roundtrips(home):
|
|
97
|
+
assert config.load_config().insecure is False # secure by default
|
|
98
|
+
config.save_config(config.CliConfig(insecure=True))
|
|
99
|
+
assert config.load_config().insecure is True
|
|
@@ -0,0 +1,340 @@
|
|
|
1
|
+
"""The agent assistant (SSE)."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
|
|
7
|
+
import httpx
|
|
8
|
+
import respx
|
|
9
|
+
|
|
10
|
+
from fp_cli.app import app
|
|
11
|
+
|
|
12
|
+
BASE = "http://dash.test"
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def _sse(events) -> httpx.Response:
|
|
16
|
+
body = "".join(f"data: {json.dumps(e)}\n\n" for e in events)
|
|
17
|
+
return httpx.Response(200, text=body, headers={"content-type": "text/event-stream"})
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
# --- agent ------------------------------------------------------------------
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
@respx.mock
|
|
24
|
+
def test_agent_health(logged_in, runner):
|
|
25
|
+
respx.get(f"{BASE}/api/agent/health").mock(
|
|
26
|
+
return_value=httpx.Response(200, json={"enabled": True, "llm_configured": True, "default_model": "claude-x"})
|
|
27
|
+
)
|
|
28
|
+
result = runner.invoke(app, ["--json", "agent", "health"])
|
|
29
|
+
assert result.exit_code == 0, result.output
|
|
30
|
+
assert json.loads(result.stdout)["enabled"] is True
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
@respx.mock
|
|
34
|
+
def test_agent_models_lists_allowlist(logged_in, runner):
|
|
35
|
+
respx.get(f"{BASE}/api/agent/health").mock(
|
|
36
|
+
return_value=httpx.Response(200, json={"enabled": True, "models": ["m-default", "m-fast"], "defaultModel": "m-default"})
|
|
37
|
+
)
|
|
38
|
+
result = runner.invoke(app, ["--json", "agent", "models"])
|
|
39
|
+
assert result.exit_code == 0, result.output
|
|
40
|
+
data = json.loads(result.stdout)
|
|
41
|
+
assert data["models"] == ["m-default", "m-fast"]
|
|
42
|
+
assert data["default_model"] == "m-default"
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
@respx.mock
|
|
46
|
+
def test_agent_chats_list(logged_in, runner):
|
|
47
|
+
respx.get(f"{BASE}/api/agent/conversations").mock(
|
|
48
|
+
return_value=httpx.Response(200, json={"conversations": [{"id": "c1", "title": "hi", "message_count": 2, "updated_at": "t"}]})
|
|
49
|
+
)
|
|
50
|
+
result = runner.invoke(app, ["--json", "agent", "chats"])
|
|
51
|
+
assert result.exit_code == 0, result.output
|
|
52
|
+
assert json.loads(result.stdout)["chats"][0]["id"] == "c1"
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
@respx.mock
|
|
56
|
+
def test_agent_ask_new_chat_creates_persists_and_returns_id(logged_in, runner):
|
|
57
|
+
respx.post(f"{BASE}/api/agent/chat").mock(
|
|
58
|
+
return_value=_sse([
|
|
59
|
+
{"type": "text-delta", "text": "Hello "},
|
|
60
|
+
{"type": "tool-start", "tool": "run_query", "toolCallId": "1"},
|
|
61
|
+
{"type": "text-delta", "text": "world"},
|
|
62
|
+
{"type": "done"},
|
|
63
|
+
])
|
|
64
|
+
)
|
|
65
|
+
create = respx.post(f"{BASE}/api/agent/conversations").mock(
|
|
66
|
+
return_value=httpx.Response(200, json={"id": "c9", "title": "New conversation"})
|
|
67
|
+
)
|
|
68
|
+
put = respx.put(f"{BASE}/api/agent/conversations/c9/messages").mock(return_value=httpx.Response(200, json={}))
|
|
69
|
+
patch = respx.patch(f"{BASE}/api/agent/conversations/c9").mock(return_value=httpx.Response(200, json={}))
|
|
70
|
+
result = runner.invoke(app, ["--json", "agent", "ask", "hi there"]) # message is positional now
|
|
71
|
+
assert result.exit_code == 0, result.output
|
|
72
|
+
data = json.loads(result.stdout)
|
|
73
|
+
assert data["answer"] == "Hello world"
|
|
74
|
+
assert data["tools"] == ["run_query"]
|
|
75
|
+
assert data["chat_id"] == "c9" # the new chat id is surfaced
|
|
76
|
+
assert create.called # no --chat ⇒ a new chat was created
|
|
77
|
+
# persisted [user, assistant] in the shared {role, content:{text}} shape
|
|
78
|
+
assert json.loads(put.calls.last.request.content)["messages"] == [
|
|
79
|
+
{"role": "user", "content": {"text": "hi there"}},
|
|
80
|
+
{"role": "assistant", "content": {"text": "Hello world"}},
|
|
81
|
+
]
|
|
82
|
+
assert json.loads(create.calls.last.request.content)["title"] == "hi there" # titled at creation
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
@respx.mock
|
|
86
|
+
def test_agent_ask_interactive_aborts_without_creating_chat(logged_in, runner):
|
|
87
|
+
# No conversation routes are mocked — if a chat were created, respx would error.
|
|
88
|
+
respx.post(f"{BASE}/api/agent/chat").mock(
|
|
89
|
+
return_value=_sse([
|
|
90
|
+
{"type": "text-delta", "text": "let me check"},
|
|
91
|
+
{"type": "ask-user", "callId": "x", "kind": "approval", "question": "ok to run?", "allowText": True},
|
|
92
|
+
])
|
|
93
|
+
)
|
|
94
|
+
result = runner.invoke(app, ["agent", "ask", "do it"])
|
|
95
|
+
assert result.exit_code == 1 # clean abort, no orphan chat
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
@respx.mock
|
|
99
|
+
def test_agent_ask_not_configured_503(logged_in, runner):
|
|
100
|
+
respx.post(f"{BASE}/api/agent/chat").mock(return_value=httpx.Response(503, json={"error": "assistant not configured"}))
|
|
101
|
+
result = runner.invoke(app, ["agent", "ask", "hi"])
|
|
102
|
+
assert result.exit_code == 1 # ApiError surfaced; no chat created
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
@respx.mock
|
|
106
|
+
def test_agent_ask_chat_continues_existing_thread(logged_in, runner):
|
|
107
|
+
# --chat resolves via the chat list first, then loads the thread by full id.
|
|
108
|
+
respx.get(f"{BASE}/api/agent/conversations").mock(
|
|
109
|
+
return_value=httpx.Response(200, json={"conversations": [{"id": "c1", "title": "perf review", "message_count": 2}]})
|
|
110
|
+
)
|
|
111
|
+
respx.get(f"{BASE}/api/agent/conversations/c1").mock(
|
|
112
|
+
return_value=httpx.Response(200, json={"title": "perf review", "messages": [
|
|
113
|
+
{"role": "user", "content": {"text": "earlier q"}},
|
|
114
|
+
{"role": "assistant", "content": {"text": "earlier a"}},
|
|
115
|
+
]})
|
|
116
|
+
)
|
|
117
|
+
chat = respx.post(f"{BASE}/api/agent/chat").mock(
|
|
118
|
+
return_value=_sse([{"type": "text-delta", "text": "follow up"}, {"type": "done"}])
|
|
119
|
+
)
|
|
120
|
+
put = respx.put(f"{BASE}/api/agent/conversations/c1/messages").mock(return_value=httpx.Response(200, json={}))
|
|
121
|
+
patch = respx.patch(f"{BASE}/api/agent/conversations/c1").mock(return_value=httpx.Response(200, json={}))
|
|
122
|
+
create = respx.post(f"{BASE}/api/agent/conversations").mock(return_value=httpx.Response(200, json={"id": "cNEW"}))
|
|
123
|
+
result = runner.invoke(app, ["--json", "agent", "ask", "and now?", "--chat", "c1"])
|
|
124
|
+
assert result.exit_code == 0, result.output
|
|
125
|
+
# prior thread + new user turn sent to the agent for context
|
|
126
|
+
sent = json.loads(chat.calls.last.request.content)["messages"]
|
|
127
|
+
assert len(sent) == 3 and sent[-1] == {"role": "user", "content": {"text": "and now?"}}
|
|
128
|
+
# persisted = history + user + assistant; existing title NOT overwritten; no new chat made
|
|
129
|
+
saved = json.loads(put.calls.last.request.content)["messages"]
|
|
130
|
+
assert len(saved) == 4 and saved[-1] == {"role": "assistant", "content": {"text": "follow up"}}
|
|
131
|
+
assert not patch.called
|
|
132
|
+
assert not create.called
|
|
133
|
+
assert json.loads(result.stdout)["chat_id"] == "c1"
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
@respx.mock
|
|
137
|
+
def test_agent_ask_rejects_unknown_model(logged_in, runner):
|
|
138
|
+
respx.get(f"{BASE}/api/agent/health").mock(
|
|
139
|
+
return_value=httpx.Response(200, json={"enabled": True, "models": ["m-default", "m-fast"], "defaultModel": "m-default"})
|
|
140
|
+
)
|
|
141
|
+
chat = respx.post(f"{BASE}/api/agent/chat").mock(return_value=_sse([{"type": "done"}]))
|
|
142
|
+
result = runner.invoke(app, ["agent", "ask", "hi", "--model", "bogus-model"])
|
|
143
|
+
assert result.exit_code == 2 # BadParameter — validated against the allowlist before chatting
|
|
144
|
+
assert not chat.called
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
@respx.mock
|
|
148
|
+
def test_agent_ask_accepts_allowlisted_model_and_forwards_it(logged_in, runner):
|
|
149
|
+
respx.get(f"{BASE}/api/agent/health").mock(
|
|
150
|
+
return_value=httpx.Response(200, json={"enabled": True, "models": ["m-default", "m-fast"], "defaultModel": "m-default"})
|
|
151
|
+
)
|
|
152
|
+
respx.post(f"{BASE}/api/agent/chat").mock(return_value=_sse([{"type": "text-delta", "text": "ok"}, {"type": "done"}]))
|
|
153
|
+
respx.post(f"{BASE}/api/agent/conversations").mock(return_value=httpx.Response(200, json={"id": "c9"}))
|
|
154
|
+
respx.put(f"{BASE}/api/agent/conversations/c9/messages").mock(return_value=httpx.Response(200, json={}))
|
|
155
|
+
respx.patch(f"{BASE}/api/agent/conversations/c9").mock(return_value=httpx.Response(200, json={}))
|
|
156
|
+
result = runner.invoke(app, ["--json", "agent", "ask", "hi", "--model", "m-fast"])
|
|
157
|
+
assert result.exit_code == 0, result.output
|
|
158
|
+
chat_calls = [c for c in respx.calls if c.request.url.path == "/api/agent/chat"]
|
|
159
|
+
assert json.loads(chat_calls[-1].request.content)["model"] == "m-fast" # chosen model forwarded
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
# --- agent: redesigned UI (render assertions; the JSON contracts above are unchanged) ---
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
@respx.mock
|
|
166
|
+
def test_agent_health_renders_configured_card(logged_in, runner):
|
|
167
|
+
respx.get(f"{BASE}/api/agent/health").mock(
|
|
168
|
+
return_value=httpx.Response(200, json={"enabled": True, "llm_configured": True,
|
|
169
|
+
"default_model": "claude-x", "models": ["claude-x", "claude-fast"]})
|
|
170
|
+
)
|
|
171
|
+
result = runner.invoke(app, ["agent", "health"])
|
|
172
|
+
assert result.exit_code == 0, result.output
|
|
173
|
+
out = result.stdout
|
|
174
|
+
assert "assistant" in out and "configured" in out and "claude-x" in out
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
@respx.mock
|
|
178
|
+
def test_agent_health_renders_not_configured(logged_in, runner):
|
|
179
|
+
respx.get(f"{BASE}/api/agent/health").mock(
|
|
180
|
+
return_value=httpx.Response(200, json={"enabled": False, "llm_configured": False})
|
|
181
|
+
)
|
|
182
|
+
result = runner.invoke(app, ["agent", "health"])
|
|
183
|
+
assert result.exit_code == 0, result.output
|
|
184
|
+
assert "not configured" in result.stdout
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
@respx.mock
|
|
188
|
+
def test_agent_models_renders_default_marker(logged_in, runner):
|
|
189
|
+
respx.get(f"{BASE}/api/agent/health").mock(
|
|
190
|
+
return_value=httpx.Response(200, json={"enabled": True, "models": ["m-default", "m-fast"], "defaultModel": "m-default"})
|
|
191
|
+
)
|
|
192
|
+
result = runner.invoke(app, ["agent", "models"])
|
|
193
|
+
assert result.exit_code == 0, result.output
|
|
194
|
+
out = result.stdout
|
|
195
|
+
assert "models" in out and "m-default" in out and "m-fast" in out and "default" in out
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
@respx.mock
|
|
199
|
+
def test_agent_models_empty_is_calm_and_notes_unconfigured(logged_in, runner):
|
|
200
|
+
respx.get(f"{BASE}/api/agent/health").mock(return_value=httpx.Response(200, json={"enabled": False}))
|
|
201
|
+
result = runner.invoke(app, ["agent", "models"])
|
|
202
|
+
assert result.exit_code == 0, result.output
|
|
203
|
+
assert "no models reported" in result.stdout
|
|
204
|
+
assert "isn't configured" in (result.stderr or "")
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
@respx.mock
|
|
208
|
+
def test_agent_chats_renders_table(logged_in, runner):
|
|
209
|
+
respx.get(f"{BASE}/api/agent/conversations").mock(
|
|
210
|
+
return_value=httpx.Response(200, json={"conversations": [
|
|
211
|
+
{"id": "c1", "title": "perf review", "message_count": 4, "updated_at": "2026-06-27T10:00:00Z"},
|
|
212
|
+
]})
|
|
213
|
+
)
|
|
214
|
+
result = runner.invoke(app, ["agent", "chats"])
|
|
215
|
+
assert result.exit_code == 0, result.output
|
|
216
|
+
assert "chats" in result.stdout and "perf review" in result.stdout
|
|
217
|
+
assert "chat-id" in result.stdout and "c1" in result.stdout # full chat-id column by default
|
|
218
|
+
assert "open one with" not in (result.stderr or "") # footer removed
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
@respx.mock
|
|
222
|
+
def test_agent_chats_empty_is_calm(logged_in, runner):
|
|
223
|
+
respx.get(f"{BASE}/api/agent/conversations").mock(return_value=httpx.Response(200, json={"conversations": []}))
|
|
224
|
+
result = runner.invoke(app, ["agent", "chats"])
|
|
225
|
+
assert result.exit_code == 0, result.output
|
|
226
|
+
assert "no chats" in result.stdout
|
|
227
|
+
|
|
228
|
+
|
|
229
|
+
@respx.mock
|
|
230
|
+
def test_agent_show_renders_thread(logged_in, runner):
|
|
231
|
+
# show resolves the (short) id via the chat list, then loads the thread by full id.
|
|
232
|
+
respx.get(f"{BASE}/api/agent/conversations").mock(
|
|
233
|
+
return_value=httpx.Response(200, json={"conversations": [{"id": "c1", "title": "perf review", "message_count": 2}]})
|
|
234
|
+
)
|
|
235
|
+
respx.get(f"{BASE}/api/agent/conversations/c1").mock(
|
|
236
|
+
return_value=httpx.Response(200, json={"title": "perf review", "messages": [
|
|
237
|
+
{"role": "user", "content": {"text": "why slow"}},
|
|
238
|
+
{"role": "assistant", "content": "because cache"}, # str-or-{text}: both extracted
|
|
239
|
+
]})
|
|
240
|
+
)
|
|
241
|
+
result = runner.invoke(app, ["agent", "show", "c1"])
|
|
242
|
+
assert result.exit_code == 0, result.output
|
|
243
|
+
out = result.stdout
|
|
244
|
+
assert "perf review" in out and "you" in out and "assistant" in out
|
|
245
|
+
assert "why slow" in out and "because cache" in out
|
|
246
|
+
|
|
247
|
+
|
|
248
|
+
@respx.mock
|
|
249
|
+
def test_agent_show_not_found_exits_6(logged_in, runner):
|
|
250
|
+
respx.get(f"{BASE}/api/agent/conversations").mock(return_value=httpx.Response(200, json={"conversations": []}))
|
|
251
|
+
result = runner.invoke(app, ["agent", "show", "ghost"])
|
|
252
|
+
assert result.exit_code == 6
|
|
253
|
+
assert "chat not found" in (result.stderr or result.output)
|
|
254
|
+
|
|
255
|
+
|
|
256
|
+
@respx.mock
|
|
257
|
+
def test_agent_show_not_found_json(logged_in, runner):
|
|
258
|
+
respx.get(f"{BASE}/api/agent/conversations").mock(return_value=httpx.Response(200, json={"conversations": []}))
|
|
259
|
+
result = runner.invoke(app, ["--json", "agent", "show", "ghost"])
|
|
260
|
+
assert result.exit_code == 6
|
|
261
|
+
assert "chat not found" in json.loads(result.stdout)["error"]
|
|
262
|
+
|
|
263
|
+
|
|
264
|
+
@respx.mock
|
|
265
|
+
def test_agent_rename_renders_card_and_keeps_json(logged_in, runner):
|
|
266
|
+
respx.get(f"{BASE}/api/agent/conversations").mock(
|
|
267
|
+
return_value=httpx.Response(200, json={"conversations": [{"id": "c1", "title": "old name", "message_count": 0}]})
|
|
268
|
+
)
|
|
269
|
+
patch = respx.patch(f"{BASE}/api/agent/conversations/c1").mock(return_value=httpx.Response(200, json={}))
|
|
270
|
+
result = runner.invoke(app, ["agent", "rename", "c1", "--title", "new name"])
|
|
271
|
+
assert result.exit_code == 0, result.output
|
|
272
|
+
err = result.stderr or ""
|
|
273
|
+
assert "chat renamed" in err and "new name" in err and "was old name" in err
|
|
274
|
+
assert json.loads(patch.calls.last.request.content)["title"] == "new name"
|
|
275
|
+
# JSON contract unchanged (now the full resolved id)
|
|
276
|
+
result2 = runner.invoke(app, ["--json", "agent", "rename", "c1", "--title", "x"])
|
|
277
|
+
assert json.loads(result2.stdout) == {"id": "c1", "title": "x"}
|
|
278
|
+
|
|
279
|
+
|
|
280
|
+
@respx.mock
|
|
281
|
+
def test_agent_rename_not_found_exits_6(logged_in, runner):
|
|
282
|
+
respx.get(f"{BASE}/api/agent/conversations").mock(return_value=httpx.Response(200, json={"conversations": []}))
|
|
283
|
+
result = runner.invoke(app, ["agent", "rename", "ghost", "--title", "x"])
|
|
284
|
+
assert result.exit_code == 6
|
|
285
|
+
assert "chat not found" in (result.stderr or result.output)
|
|
286
|
+
|
|
287
|
+
|
|
288
|
+
@respx.mock
|
|
289
|
+
def test_agent_delete_yes_renders_and_keeps_json(logged_in, runner):
|
|
290
|
+
respx.get(f"{BASE}/api/agent/conversations").mock(
|
|
291
|
+
return_value=httpx.Response(200, json={"conversations": [{"id": "c1", "title": "perf review", "message_count": 1}]})
|
|
292
|
+
)
|
|
293
|
+
delete = respx.delete(f"{BASE}/api/agent/conversations/c1").mock(return_value=httpx.Response(200, json={}))
|
|
294
|
+
result = runner.invoke(app, ["agent", "delete", "c1", "--yes"])
|
|
295
|
+
assert result.exit_code == 0, result.output
|
|
296
|
+
err = result.stderr or ""
|
|
297
|
+
assert "deleted chat" in err and "perf review" in err
|
|
298
|
+
assert delete.called
|
|
299
|
+
# JSON contract (now also carries the title)
|
|
300
|
+
result2 = runner.invoke(app, ["--json", "agent", "delete", "c1", "--yes"])
|
|
301
|
+
body = json.loads(result2.stdout)
|
|
302
|
+
assert body["deleted"] is True and body["id"] == "c1" and body["title"] == "perf review"
|
|
303
|
+
|
|
304
|
+
|
|
305
|
+
@respx.mock
|
|
306
|
+
def test_agent_delete_not_found_exits_6(logged_in, runner):
|
|
307
|
+
respx.get(f"{BASE}/api/agent/conversations").mock(return_value=httpx.Response(200, json={"conversations": []}))
|
|
308
|
+
result = runner.invoke(app, ["agent", "delete", "ghost", "--yes"])
|
|
309
|
+
assert result.exit_code == 6
|
|
310
|
+
assert "chat not found" in (result.stderr or result.output)
|
|
311
|
+
|
|
312
|
+
|
|
313
|
+
@respx.mock
|
|
314
|
+
def test_agent_ask_answer_to_stdout_chrome_to_stderr(logged_in, runner):
|
|
315
|
+
respx.post(f"{BASE}/api/agent/chat").mock(
|
|
316
|
+
return_value=_sse([
|
|
317
|
+
{"type": "tool-start", "tool": "run_query"},
|
|
318
|
+
{"type": "text-delta", "text": "the answer"},
|
|
319
|
+
{"type": "done"},
|
|
320
|
+
])
|
|
321
|
+
)
|
|
322
|
+
respx.post(f"{BASE}/api/agent/conversations").mock(return_value=httpx.Response(200, json={"id": "c9"}))
|
|
323
|
+
respx.put(f"{BASE}/api/agent/conversations/c9/messages").mock(return_value=httpx.Response(200, json={}))
|
|
324
|
+
respx.patch(f"{BASE}/api/agent/conversations/c9").mock(return_value=httpx.Response(200, json={}))
|
|
325
|
+
result = runner.invoke(app, ["agent", "ask", "q"])
|
|
326
|
+
assert result.exit_code == 0, result.output
|
|
327
|
+
assert result.stdout.strip() == "the answer" # piped (non-tty) → ONLY the raw answer on stdout
|
|
328
|
+
err = result.stderr or ""
|
|
329
|
+
assert "used tool: run_query" in err
|
|
330
|
+
assert "new chat" in err and "fp agent ask --chat c9" in err
|
|
331
|
+
|
|
332
|
+
|
|
333
|
+
@respx.mock
|
|
334
|
+
def test_agent_ask_assistant_error_clean_exit_1(logged_in, runner):
|
|
335
|
+
respx.post(f"{BASE}/api/agent/chat").mock(
|
|
336
|
+
return_value=_sse([{"type": "error", "message": "model overloaded"}])
|
|
337
|
+
)
|
|
338
|
+
result = runner.invoke(app, ["agent", "ask", "q"])
|
|
339
|
+
assert result.exit_code == 1
|
|
340
|
+
assert "✗" in (result.stderr or "") and "model overloaded" in (result.stderr or "")
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from datetime import datetime, timezone
|
|
4
|
+
|
|
5
|
+
import pytest
|
|
6
|
+
|
|
7
|
+
from fp_cli import dates
|
|
8
|
+
|
|
9
|
+
NOW = datetime(2026, 5, 25, 12, 0, 0, tzinfo=timezone.utc)
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def test_all_and_none_have_no_bounds():
|
|
13
|
+
assert dates.resolve_range("all") == (None, None)
|
|
14
|
+
assert dates.resolve_range(None) == (None, None)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
@pytest.mark.parametrize(
|
|
18
|
+
"preset,expected_from",
|
|
19
|
+
[
|
|
20
|
+
("15m", "2026-05-25T11:45:00Z"),
|
|
21
|
+
("1h", "2026-05-25T11:00:00Z"),
|
|
22
|
+
("6h", "2026-05-25T06:00:00Z"),
|
|
23
|
+
("24h", "2026-05-24T12:00:00Z"),
|
|
24
|
+
("7d", "2026-05-18T12:00:00Z"),
|
|
25
|
+
],
|
|
26
|
+
)
|
|
27
|
+
def test_presets(preset, expected_from):
|
|
28
|
+
ts_from, ts_to = dates.resolve_range(preset, now=NOW)
|
|
29
|
+
assert ts_from == expected_from
|
|
30
|
+
assert ts_to is None
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def test_custom_from_to_overrides_since():
|
|
34
|
+
ts_from, ts_to = dates.resolve_range(
|
|
35
|
+
"24h", "2020-01-01T00:00:00Z", "2020-01-02T00:00:00Z", now=NOW
|
|
36
|
+
)
|
|
37
|
+
assert ts_from == "2020-01-01T00:00:00Z"
|
|
38
|
+
assert ts_to == "2020-01-02T00:00:00Z"
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def test_invalid_since_raises():
|
|
42
|
+
with pytest.raises(ValueError):
|
|
43
|
+
dates.resolve_range("bogus", now=NOW)
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
@pytest.mark.parametrize(
|
|
47
|
+
"ok_value",
|
|
48
|
+
["2026-05-01T00:00:00Z", "2026-05-01T00:00:00+00:00", "2026-05-01T12:30:00-05:00"],
|
|
49
|
+
)
|
|
50
|
+
def test_from_accepts_rfc3339_with_timezone(ok_value):
|
|
51
|
+
ts_from, _ = dates.resolve_range(None, ok_value, None, now=NOW)
|
|
52
|
+
assert ts_from == ok_value
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
@pytest.mark.parametrize(
|
|
56
|
+
"bad_value",
|
|
57
|
+
[
|
|
58
|
+
"2026-05-01", # date only
|
|
59
|
+
"2026-05-01T00:00:00", # no timezone offset → server 400
|
|
60
|
+
"2026-05-01 00:00:00Z", # space separator, not 'T'
|
|
61
|
+
"not-a-date",
|
|
62
|
+
],
|
|
63
|
+
)
|
|
64
|
+
def test_from_rejects_naive_or_malformed(bad_value):
|
|
65
|
+
# These deserialize to a server 400 (exit 1); validating client-side keeps them a
|
|
66
|
+
# clean usage error (exit 2). Applies to --from and --to alike.
|
|
67
|
+
with pytest.raises(ValueError):
|
|
68
|
+
dates.resolve_range(None, bad_value, None, now=NOW)
|
|
69
|
+
with pytest.raises(ValueError):
|
|
70
|
+
dates.resolve_range(None, None, bad_value, now=NOW)
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def test_since_choices_cover_presets():
|
|
74
|
+
for preset in dates.PRESETS:
|
|
75
|
+
assert preset in dates.SINCE_CHOICES
|
|
76
|
+
assert "all" in dates.SINCE_CHOICES
|