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
|
File without changes
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
"""Shared helpers for write/mutation commands (create / update / delete / …).
|
|
2
|
+
|
|
3
|
+
Centralising these guarantees every domain behaves the same way:
|
|
4
|
+
* request bodies come from discrete flags **or** ``--file``/stdin JSON,
|
|
5
|
+
* mutations confirm before acting but never block a scripted/`--json` run,
|
|
6
|
+
* created/updated resources render identically,
|
|
7
|
+
* each action emits a privacy-safe telemetry event.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
import dataclasses
|
|
13
|
+
import json
|
|
14
|
+
import sys
|
|
15
|
+
from pathlib import Path
|
|
16
|
+
from typing import Any, Dict, Optional
|
|
17
|
+
|
|
18
|
+
import typer
|
|
19
|
+
|
|
20
|
+
from .. import _click_compat as click # the Click Typer is running; see _click_compat
|
|
21
|
+
from .. import analytics, output
|
|
22
|
+
from .._context import AppState
|
|
23
|
+
from ..errors import NotFoundError
|
|
24
|
+
|
|
25
|
+
# Telemetry properties a write action may carry. Names/enums/coarse counts only —
|
|
26
|
+
# never ids, emails, names, URLs, SQL, key plaintext, or any customer value.
|
|
27
|
+
# A count is named `<thing>_count`, the form the dashboard and the API's own fields
|
|
28
|
+
# use, so the same quantity queries as one series across surfaces. Bare `count` is the
|
|
29
|
+
# older spelling, kept only so anything still sending it lands rather than vanishing.
|
|
30
|
+
_SAFE_PROP_KEYS = frozenset(
|
|
31
|
+
{"success", "resource", "via", "mode", "destructive", "row_count_bucket",
|
|
32
|
+
"permission_count", "url_count", "trigger_kind", "severity_present", "count"}
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def resolve_one(
|
|
37
|
+
items,
|
|
38
|
+
handle: str,
|
|
39
|
+
*,
|
|
40
|
+
kind: str,
|
|
41
|
+
list_cmd: str,
|
|
42
|
+
key: str = "name",
|
|
43
|
+
ref: str = "named",
|
|
44
|
+
match_id: bool = True,
|
|
45
|
+
plural: Optional[str] = None,
|
|
46
|
+
casefold: bool = False,
|
|
47
|
+
):
|
|
48
|
+
"""Find the single ``items`` entry whose ``key`` (or, as a fallback, ``id``) equals ``handle``.
|
|
49
|
+
|
|
50
|
+
The one place name/email→object resolution lives, so every command fails identically:
|
|
51
|
+
* exactly one match → return it,
|
|
52
|
+
* none → ``raise NotFoundError`` (exit 6) with a "run `fp <list_cmd>`" hint,
|
|
53
|
+
* several → ``raise click.UsageError`` (exit 2) telling the caller to use the id.
|
|
54
|
+
|
|
55
|
+
The single error chokepoint (app.py) renders the raised exception — JSON envelope under
|
|
56
|
+
``--json`` (stdout), red box otherwise — so callers never branch on ``state.json`` themselves.
|
|
57
|
+
|
|
58
|
+
``casefold`` compares case-insensitively. Opt-in rather than the default because it is only
|
|
59
|
+
correct where the server itself normalises: emails are lowercased on create, so
|
|
60
|
+
``fp users create Alice.Chen@Example.com`` stores ``alice.chen@example.com`` and every
|
|
61
|
+
later ``fp users show Alice.Chen@Example.com`` answered "no user with email" — the CLI
|
|
62
|
+
denying a member it had just created, with the exact string the caller had just typed.
|
|
63
|
+
Key and query names are stored verbatim, so folding them would let ``PROD`` resolve
|
|
64
|
+
``prod`` and silently act on the wrong object.
|
|
65
|
+
"""
|
|
66
|
+
|
|
67
|
+
def norm(value):
|
|
68
|
+
return value.casefold() if casefold and isinstance(value, str) else value
|
|
69
|
+
|
|
70
|
+
target = norm(handle)
|
|
71
|
+
matches = [it for it in items if norm(getattr(it, key, None)) == target]
|
|
72
|
+
if not matches and match_id:
|
|
73
|
+
matches = [it for it in items if str(getattr(it, "id", "")) == handle]
|
|
74
|
+
if len(matches) == 1:
|
|
75
|
+
return matches[0]
|
|
76
|
+
if not matches:
|
|
77
|
+
raise NotFoundError(
|
|
78
|
+
f'no {kind} {ref} "{handle}"', hint=f"run `fp {list_cmd}` to list them"
|
|
79
|
+
)
|
|
80
|
+
raise click.UsageError(
|
|
81
|
+
f'"{handle}" matches {len(matches)} {plural or kind + "s"} — disambiguate with the id.'
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def read_text_arg(path: str, *, flag: str = "--file") -> str:
|
|
86
|
+
"""Read a file's text (or stdin when ``path == "-"``), mapping a missing/unreadable
|
|
87
|
+
path to a clean usage error (exit 2) instead of a raw OSError traceback. Shared by
|
|
88
|
+
every ``--file`` / ``@file`` reader so they all fail the same, predictable way."""
|
|
89
|
+
if path == "-":
|
|
90
|
+
return sys.stdin.read()
|
|
91
|
+
try:
|
|
92
|
+
return Path(path).read_text()
|
|
93
|
+
except OSError as exc:
|
|
94
|
+
raise click.BadParameter(
|
|
95
|
+
f"cannot read '{path}': {exc.strerror or exc}", param_hint=flag
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def read_body(
|
|
100
|
+
state: AppState,
|
|
101
|
+
file: Optional[str],
|
|
102
|
+
inline: Optional[Dict[str, Any]] = None,
|
|
103
|
+
) -> Dict[str, Any]:
|
|
104
|
+
"""Resolve a request body.
|
|
105
|
+
|
|
106
|
+
``--file PATH`` (or ``--file -`` for stdin) JSON wins and must be a JSON object;
|
|
107
|
+
otherwise the discrete-flag ``inline`` dict is used (``None`` values dropped, so a
|
|
108
|
+
partial PATCH/PUT body stays clean). Giving both a populated ``--file`` and discrete
|
|
109
|
+
flags is a usage error.
|
|
110
|
+
"""
|
|
111
|
+
cleaned_inline = {k: v for k, v in (inline or {}).items() if v is not None}
|
|
112
|
+
if file is None:
|
|
113
|
+
return cleaned_inline
|
|
114
|
+
raw = read_text_arg(file)
|
|
115
|
+
try:
|
|
116
|
+
parsed = json.loads(raw)
|
|
117
|
+
except json.JSONDecodeError as exc:
|
|
118
|
+
raise click.BadParameter(f"--file is not valid JSON: {exc}", param_hint="--file")
|
|
119
|
+
if not isinstance(parsed, dict):
|
|
120
|
+
raise click.BadParameter("--file must contain a JSON object.", param_hint="--file")
|
|
121
|
+
if cleaned_inline:
|
|
122
|
+
raise click.UsageError("Pass either --file or the discrete flags, not both.")
|
|
123
|
+
return parsed
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
def confirm(state: AppState, action: str, *, assume_yes: bool, destructive: bool = False) -> None:
|
|
127
|
+
"""Confirm a mutation, unless it is safe to skip.
|
|
128
|
+
|
|
129
|
+
Skips the prompt when ``--yes`` is given, ``--json`` is set, or stdin is not a TTY
|
|
130
|
+
(all mean a script/agent is driving and a blocking prompt would hang). Otherwise
|
|
131
|
+
prompts and aborts (exit 1) on "no".
|
|
132
|
+
"""
|
|
133
|
+
if assume_yes or state.json or not sys.stdin.isatty():
|
|
134
|
+
return
|
|
135
|
+
if destructive:
|
|
136
|
+
output.warn(f"This cannot be undone: {action}.")
|
|
137
|
+
typer.confirm(f"{action}?", default=False, abort=True, err=True)
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
def should_prompt(state: AppState, assume_yes: bool) -> bool:
|
|
141
|
+
"""Whether to actually show an interactive confirm. ``False`` (auto-proceed) when ``--yes``
|
|
142
|
+
is given, ``--json`` is set, or stdin is not a TTY — all mean a script/agent is driving and
|
|
143
|
+
a blocking prompt would hang. Lets a caller render a bespoke confirm (e.g. the users-update
|
|
144
|
+
diff prompt) behind the same guard the shared helpers use."""
|
|
145
|
+
return not (assume_yes or state.json or not sys.stdin.isatty())
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
def confirm_action(
|
|
149
|
+
state: AppState, action: str, target: str, *, consequence: str, assume_yes: bool,
|
|
150
|
+
glyph: str = "⚠", color: Optional[str] = None, title: str = "confirm",
|
|
151
|
+
) -> bool:
|
|
152
|
+
"""Confirm a named action. Returns ``True`` to proceed, ``False`` if the user declined.
|
|
153
|
+
|
|
154
|
+
Proceeds without prompting under :func:`should_prompt`'s conditions. Otherwise shows the
|
|
155
|
+
shared boxed prompt (default NO) via ``output.confirm_prompt`` — amber ⚠ by default
|
|
156
|
+
(destructive), or a caller-supplied glyph/colour (e.g. the calm ACCENT ``↑`` for
|
|
157
|
+
``users enable``).
|
|
158
|
+
"""
|
|
159
|
+
if not should_prompt(state, assume_yes):
|
|
160
|
+
return True
|
|
161
|
+
return output.confirm_prompt(action, target, consequence, glyph=glyph, color=color, title=title)
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
def confirm_destructive(
|
|
165
|
+
state: AppState, action: str, target: str, *, consequence: str, assume_yes: bool
|
|
166
|
+
) -> bool:
|
|
167
|
+
"""Confirm a destructive, named action (regenerate / disable / revoke a key) with the shared
|
|
168
|
+
amber ⚠ prompt. Thin wrapper over :func:`confirm_action` with the destructive defaults."""
|
|
169
|
+
return confirm_action(state, action, target, consequence=consequence, assume_yes=assume_yes)
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
def _to_dict(resource: Any) -> Any:
|
|
173
|
+
if dataclasses.is_dataclass(resource) and not isinstance(resource, type):
|
|
174
|
+
return dataclasses.asdict(resource)
|
|
175
|
+
return resource
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
def emit_resource(
|
|
179
|
+
state: AppState,
|
|
180
|
+
resource: Any,
|
|
181
|
+
*,
|
|
182
|
+
action: str,
|
|
183
|
+
title: str,
|
|
184
|
+
summary_fields=None,
|
|
185
|
+
) -> None:
|
|
186
|
+
"""Render the result of a mutation.
|
|
187
|
+
|
|
188
|
+
``--json``: the resource verbatim. Human: a green success line plus a field/value
|
|
189
|
+
table of ``summary_fields`` (or all fields when omitted).
|
|
190
|
+
"""
|
|
191
|
+
data = _to_dict(resource)
|
|
192
|
+
if state.json:
|
|
193
|
+
output.emit_json(data)
|
|
194
|
+
return
|
|
195
|
+
output.success(f"{title} {action}.")
|
|
196
|
+
if isinstance(data, dict):
|
|
197
|
+
keys = summary_fields if summary_fields is not None else list(data.keys())
|
|
198
|
+
rows = [[k, _cell(data.get(k))] for k in keys]
|
|
199
|
+
if rows:
|
|
200
|
+
output.print_table(["Field", "Value"], rows)
|
|
201
|
+
|
|
202
|
+
|
|
203
|
+
def _cell(value: Any) -> str:
|
|
204
|
+
if value is None:
|
|
205
|
+
return "-"
|
|
206
|
+
if isinstance(value, (dict, list)):
|
|
207
|
+
return json.dumps(value, ensure_ascii=False)
|
|
208
|
+
return str(value)
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
def record_action(event: str, **props: Any) -> None:
|
|
212
|
+
"""Emit a privacy-safe per-action telemetry event (allowlisted properties only)."""
|
|
213
|
+
safe = {k: v for k, v in props.items() if k in _SAFE_PROP_KEYS}
|
|
214
|
+
analytics.capture(event, safe)
|
|
@@ -0,0 +1,407 @@
|
|
|
1
|
+
"""FailproofAI Cloud assistant (Claude) from the CLI: agent health / models / chats / ask / show / rename / delete.
|
|
2
|
+
|
|
3
|
+
Every ``ask`` is saved to a **chat**: with ``--chat <id>`` it continues that chat, without it a
|
|
4
|
+
new chat is started and its short id printed (so you can continue it). ``chats`` lists them;
|
|
5
|
+
``show`` / ``rename`` / ``delete`` manage one; ``models`` lists the models you can pass to
|
|
6
|
+
``ask --model``.
|
|
7
|
+
|
|
8
|
+
Chats are referenced by a **short chat-id** — the first 8 chars (the segment before the UUID's
|
|
9
|
+
first ``-``) shown in ``agent chats``. The CLI resolves that prefix back to the full chat against
|
|
10
|
+
your chat list (so the server/dashboard keep the full id intact); a prefix that matches none → a
|
|
11
|
+
``chat not found`` box, one that matches several → an ``ambiguous`` box. If the assistant asks for
|
|
12
|
+
interactive input mid-turn, ``ask`` aborts cleanly rather than hang.
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
from __future__ import annotations
|
|
16
|
+
|
|
17
|
+
import sys
|
|
18
|
+
from typing import Optional
|
|
19
|
+
|
|
20
|
+
import typer
|
|
21
|
+
|
|
22
|
+
from .. import _click_compat as click # the Click Typer is running; see _click_compat
|
|
23
|
+
from .. import client as api
|
|
24
|
+
from .. import output
|
|
25
|
+
from .._context import GLOBALS_EPILOG, AppState, deny_in_key_mode, require_auth
|
|
26
|
+
from ..errors import NotFoundError
|
|
27
|
+
from . import _write
|
|
28
|
+
|
|
29
|
+
# The assistant has NO server route at all: `/agent/chat` and `/agent/health` are
|
|
30
|
+
# implemented by the dashboard itself, and the conversation routes are per-human
|
|
31
|
+
# (keyed by actor) and deliberately excluded from the versioned API. So there is
|
|
32
|
+
# nothing for an API key to call here — refuse before opening a connection.
|
|
33
|
+
_KEY_MODE_REASON = (
|
|
34
|
+
"the assistant runs in the dashboard and its chats belong to a person — there is "
|
|
35
|
+
"no API route behind it for a key to call"
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
# The server's default title for a freshly-created chat; we auto-title from the first
|
|
39
|
+
# question only while the title is still this (so an explicit one is kept).
|
|
40
|
+
_DEFAULT_TITLE = "New conversation"
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def _text_of(content: object) -> str:
|
|
44
|
+
"""Extract the display text from a stored message ``content`` — mirrors the
|
|
45
|
+
dashboard's `textOf` so CLI and dashboard render the same thread identically."""
|
|
46
|
+
if isinstance(content, str):
|
|
47
|
+
return content
|
|
48
|
+
if isinstance(content, dict) and isinstance(content.get("text"), str):
|
|
49
|
+
return content["text"]
|
|
50
|
+
return ""
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def _title_from_question(question: str) -> str:
|
|
54
|
+
"""A short, single-line chat title derived from the first question."""
|
|
55
|
+
collapsed = " ".join(question.split())
|
|
56
|
+
if not collapsed:
|
|
57
|
+
return _DEFAULT_TITLE
|
|
58
|
+
return collapsed[:57] + "…" if len(collapsed) > 60 else collapsed
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def _models_of(health: dict) -> list:
|
|
62
|
+
"""The model allowlist from an agent-health payload (handles list or single shapes)."""
|
|
63
|
+
models = health.get("models")
|
|
64
|
+
if isinstance(models, list) and models:
|
|
65
|
+
return [str(m) for m in models]
|
|
66
|
+
one = health.get("default_model") or health.get("defaultModel") or health.get("model")
|
|
67
|
+
return [str(one)] if one else []
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def _default_model_of(health: dict) -> Optional[str]:
|
|
71
|
+
return health.get("default_model") or health.get("defaultModel") or health.get("model")
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def _is_configured(health: dict) -> bool:
|
|
75
|
+
"""Whether the assistant is usable on this deployment — ``enabled`` AND (``llm_configured``
|
|
76
|
+
when the server reports it; absent → assume configured if enabled)."""
|
|
77
|
+
enabled = bool(health.get("enabled"))
|
|
78
|
+
llm = health.get("llm_configured")
|
|
79
|
+
return enabled and (llm if isinstance(llm, bool) else True)
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def _validate_model(cctx, model: str) -> None:
|
|
83
|
+
"""Reject a ``--model`` that isn't on the deployment's allowlist. Skips the check when
|
|
84
|
+
the deployment reports no models (lets the server decide / fall back)."""
|
|
85
|
+
models = _models_of(api.agent_health(cctx))
|
|
86
|
+
if models and model not in models:
|
|
87
|
+
raise typer.BadParameter(
|
|
88
|
+
f"model not found {model!r}; available: {', '.join(models)} (see `fp agent models`).",
|
|
89
|
+
param_hint="--model",
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def _resolve_chat_or_exit(state: AppState, cctx, handle: str) -> dict:
|
|
94
|
+
"""Resolve a chat by its short id PREFIX (the first-8 ``chat-id`` shown in ``agent chats``) or
|
|
95
|
+
a full id, matching against the chat list. Returns the matched chat summary (full ``id`` +
|
|
96
|
+
``title`` + ``message_count``). None → boxed ``chat not found`` (exit 6); more than one → boxed
|
|
97
|
+
``ambiguous`` (exit 2). Resolving via the list also avoids fetching a non-existent id directly
|
|
98
|
+
(which the server answers with a 500). Shared by show / rename / delete / ask --chat."""
|
|
99
|
+
chats = api.list_conversations(cctx)
|
|
100
|
+
exact = [c for c in chats if str(c.get("id")) == handle]
|
|
101
|
+
matches = exact or [c for c in chats if str(c.get("id", "")).startswith(handle)]
|
|
102
|
+
if len(matches) == 1:
|
|
103
|
+
return matches[0]
|
|
104
|
+
if not matches:
|
|
105
|
+
raise NotFoundError(
|
|
106
|
+
f"chat not found: {handle}", hint="run `fp agent chats` to list them"
|
|
107
|
+
)
|
|
108
|
+
ids = [str(c.get("id")) for c in matches]
|
|
109
|
+
raise click.UsageError(
|
|
110
|
+
f"ambiguous chat id: {handle} — matches {len(ids)}; use a longer prefix or the full id"
|
|
111
|
+
)
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def agent_health(ctx: typer.Context) -> None:
|
|
115
|
+
"""Show whether the assistant is configured on this deployment.
|
|
116
|
+
|
|
117
|
+
Needs `agent:use`. With `--json`: `{enabled, llm_configured?, model?, models?, default_model?}`.
|
|
118
|
+
|
|
119
|
+
Example:
|
|
120
|
+
|
|
121
|
+
* `fp --json agent health`
|
|
122
|
+
"""
|
|
123
|
+
state: AppState = ctx.obj
|
|
124
|
+
deny_in_key_mode(state, "agent", _KEY_MODE_REASON)
|
|
125
|
+
cctx = require_auth(state)
|
|
126
|
+
data = api.agent_health(cctx)
|
|
127
|
+
if state.json:
|
|
128
|
+
output.emit_json(data)
|
|
129
|
+
return
|
|
130
|
+
output.render_agent_health(
|
|
131
|
+
configured=_is_configured(data),
|
|
132
|
+
default_model=_default_model_of(data),
|
|
133
|
+
model_count=len(_models_of(data)),
|
|
134
|
+
)
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
def agent_models(ctx: typer.Context) -> None:
|
|
138
|
+
"""List the assistant models available on this deployment.
|
|
139
|
+
|
|
140
|
+
Needs `agent:use`. The choices (and the default) come from the agent service's
|
|
141
|
+
allowlist; pass any of them to `agent ask --model <name>`. With `--json`:
|
|
142
|
+
`{"models": [...], "default_model": "..."}`.
|
|
143
|
+
|
|
144
|
+
Example:
|
|
145
|
+
|
|
146
|
+
* `fp agent models`
|
|
147
|
+
"""
|
|
148
|
+
state: AppState = ctx.obj
|
|
149
|
+
deny_in_key_mode(state, "agent", _KEY_MODE_REASON)
|
|
150
|
+
cctx = require_auth(state)
|
|
151
|
+
data = api.agent_health(cctx)
|
|
152
|
+
models = _models_of(data)
|
|
153
|
+
default = _default_model_of(data)
|
|
154
|
+
if state.json:
|
|
155
|
+
output.emit_json({"models": models, "default_model": default})
|
|
156
|
+
return
|
|
157
|
+
output.render_agent_models(models, default_model=default)
|
|
158
|
+
if not models:
|
|
159
|
+
output.agent_unconfigured_note()
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
def agent_chats(
|
|
163
|
+
ctx: typer.Context,
|
|
164
|
+
) -> None:
|
|
165
|
+
"""List your saved chats in a boxed table, newest first.
|
|
166
|
+
|
|
167
|
+
Shows `chat-id · title · messages · updated` — `chat-id` is the SHORT id (the first 8 chars; a
|
|
168
|
+
prefix the `agent show`/`rename`/`delete`/`ask --chat` commands resolve), and `updated` is the
|
|
169
|
+
chat's last-activity age. Needs `agent:use`. With `--json`: `{"chats": [{id, title, updated_at,
|
|
170
|
+
message_count}]}` (full id).
|
|
171
|
+
|
|
172
|
+
Example:
|
|
173
|
+
|
|
174
|
+
* `fp agent chats`
|
|
175
|
+
* `fp --json agent chats`
|
|
176
|
+
"""
|
|
177
|
+
state: AppState = ctx.obj
|
|
178
|
+
deny_in_key_mode(state, "agent", _KEY_MODE_REASON)
|
|
179
|
+
cctx = require_auth(state)
|
|
180
|
+
chats = api.list_conversations(cctx)
|
|
181
|
+
if state.json:
|
|
182
|
+
output.emit_json({"chats": chats})
|
|
183
|
+
return
|
|
184
|
+
output.render_agent_chats(chats)
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
def agent_show(
|
|
188
|
+
ctx: typer.Context,
|
|
189
|
+
chat_id: str = typer.Argument(..., metavar="CHAT_ID", help="Chat id — the short one from `agent chats` (a prefix) or a full id."),
|
|
190
|
+
) -> None:
|
|
191
|
+
"""Show a chat as a readable thread — its title, then each turn (`you` / `assistant`).
|
|
192
|
+
|
|
193
|
+
Pass the **short chat-id** from `agent chats` (the first 8 chars; the CLI resolves the prefix
|
|
194
|
+
to the full chat) or a full id. Not-found → a `chat not found` error box, exit 6. Needs
|
|
195
|
+
`agent:use`. With `--json`: `{title, messages: [...]}`.
|
|
196
|
+
|
|
197
|
+
Example:
|
|
198
|
+
|
|
199
|
+
* `fp agent show 07854990`
|
|
200
|
+
"""
|
|
201
|
+
state: AppState = ctx.obj
|
|
202
|
+
deny_in_key_mode(state, "agent", _KEY_MODE_REASON)
|
|
203
|
+
cctx = require_auth(state)
|
|
204
|
+
chat = _resolve_chat_or_exit(state, cctx, chat_id) # short prefix → the full chat
|
|
205
|
+
data = api.get_conversation(cctx, chat["id"])
|
|
206
|
+
if state.json:
|
|
207
|
+
output.emit_json(data)
|
|
208
|
+
return
|
|
209
|
+
output.render_agent_show(title=data.get("title"), messages=data.get("messages", []) or [], chat_id=chat["id"])
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
def agent_rename(
|
|
213
|
+
ctx: typer.Context,
|
|
214
|
+
chat_id: str = typer.Argument(..., metavar="CHAT_ID", help="Chat id (short prefix or full)."),
|
|
215
|
+
title: str = typer.Option(..., "--title", help="New title for the chat."),
|
|
216
|
+
) -> None:
|
|
217
|
+
"""Rename a chat, referenced by its **short chat-id** (a prefix) or full id.
|
|
218
|
+
|
|
219
|
+
Not-found → a `chat not found` error box, exit 6. Needs `agent:use`. With `--json`:
|
|
220
|
+
`{"id": "<full id>", "title": "<title>"}`.
|
|
221
|
+
|
|
222
|
+
Example:
|
|
223
|
+
|
|
224
|
+
* `fp agent rename 07854990 --title "page walkthrough"`
|
|
225
|
+
"""
|
|
226
|
+
state: AppState = ctx.obj
|
|
227
|
+
deny_in_key_mode(state, "agent", _KEY_MODE_REASON)
|
|
228
|
+
cctx = require_auth(state)
|
|
229
|
+
chat = _resolve_chat_or_exit(state, cctx, chat_id) # validates + gives the full id + old title
|
|
230
|
+
old_title = chat.get("title")
|
|
231
|
+
api.rename_conversation(cctx, chat["id"], title)
|
|
232
|
+
_write.record_action("agent_chat_renamed", resource="conversation", success=True)
|
|
233
|
+
if state.json:
|
|
234
|
+
output.emit_json({"id": chat["id"], "title": title})
|
|
235
|
+
else:
|
|
236
|
+
output.render_agent_renamed(chat_id=chat["id"], title=title, old_title=old_title)
|
|
237
|
+
|
|
238
|
+
|
|
239
|
+
def agent_delete(
|
|
240
|
+
ctx: typer.Context,
|
|
241
|
+
chat_id: str = typer.Argument(..., metavar="CHAT_ID", help="Chat id to delete (short prefix or full)."),
|
|
242
|
+
yes: bool = typer.Option(False, "--yes", "-y", help="Skip the confirmation prompt. The prompt only appears on an interactive terminal: under --json, or with stdin redirected, this command proceeds without asking."),
|
|
243
|
+
) -> None:
|
|
244
|
+
"""Delete a chat, referenced by its **short chat-id** (a prefix) or full id. Cannot be undone.
|
|
245
|
+
|
|
246
|
+
Shows an amber preview, then confirms. Not-found → a `chat not found` error box, exit 6. Needs
|
|
247
|
+
`agent:use`. With `--json`: `{"deleted": true, "id": "<full id>", "title": "<title>"}` (or
|
|
248
|
+
`{"cancelled": true}` on a declined prompt).
|
|
249
|
+
|
|
250
|
+
Example:
|
|
251
|
+
|
|
252
|
+
* `fp agent delete 07854990`
|
|
253
|
+
"""
|
|
254
|
+
state: AppState = ctx.obj
|
|
255
|
+
deny_in_key_mode(state, "agent", _KEY_MODE_REASON)
|
|
256
|
+
cctx = require_auth(state)
|
|
257
|
+
chat = _resolve_chat_or_exit(state, cctx, chat_id) # validates + powers the preview
|
|
258
|
+
title = chat.get("title") or chat["id"]
|
|
259
|
+
message_count = chat.get("message_count") or 0
|
|
260
|
+
if _write.should_prompt(state, yes):
|
|
261
|
+
output.render_agent_delete_preview(title=title, message_count=message_count, chat_id=chat["id"])
|
|
262
|
+
if not output.confirm_agent_delete():
|
|
263
|
+
if state.json:
|
|
264
|
+
output.emit_json({"cancelled": True})
|
|
265
|
+
else:
|
|
266
|
+
output.print_cancelled("nothing deleted")
|
|
267
|
+
return
|
|
268
|
+
api.delete_conversation(cctx, chat["id"])
|
|
269
|
+
_write.record_action("agent_chat_deleted", resource="conversation", success=True, destructive=True)
|
|
270
|
+
if state.json:
|
|
271
|
+
output.emit_json({"deleted": True, "id": chat["id"], "title": title})
|
|
272
|
+
else:
|
|
273
|
+
output.agent_deleted(title)
|
|
274
|
+
|
|
275
|
+
|
|
276
|
+
def agent_ask(
|
|
277
|
+
ctx: typer.Context,
|
|
278
|
+
message: Optional[str] = typer.Argument(None, metavar="MESSAGE", help="Your question (or pipe it on stdin)."),
|
|
279
|
+
chat: Optional[str] = typer.Option(None, "--chat", help="Continue this chat — its short id from `agent chats` (a prefix) or full id. Omit to start a NEW chat (its id is printed)."),
|
|
280
|
+
model: Optional[str] = typer.Option(None, "--model", help="Model to use (see `agent models`)."),
|
|
281
|
+
page_context: Optional[str] = typer.Option(None, "--page-context", help="Optional context string to ground the answer."),
|
|
282
|
+
) -> None:
|
|
283
|
+
"""Ask the assistant a question — every ask is saved to a chat.
|
|
284
|
+
|
|
285
|
+
Pass the question as the **positional MESSAGE** (or pipe it on stdin). Needs `agent:use`.
|
|
286
|
+
|
|
287
|
+
* **No `--chat`** → starts a **new chat**, answers, persists, and prints the new chat's short
|
|
288
|
+
id (continue it with `--chat <short id>`); the first question auto-titles the chat.
|
|
289
|
+
* **`--chat <id>`** → continues that chat (short prefix or full id): its prior thread is sent
|
|
290
|
+
for context and this turn is appended. Chats also show up in the dashboard assistant.
|
|
291
|
+
|
|
292
|
+
The answer renders in a boxed `assistant` card (interactively); when piped it's the raw answer
|
|
293
|
+
on **stdout** (a clean payload). A brand-new chat is created only once an answer lands — if the
|
|
294
|
+
assistant needs interactive input it aborts (exit 1) and leaves no empty chat. With `--json`:
|
|
295
|
+
`{answer, tools, interrupted, error, chat_id}`.
|
|
296
|
+
|
|
297
|
+
Examples:
|
|
298
|
+
|
|
299
|
+
* `fp agent ask "which agents errored most in the last day?"`
|
|
300
|
+
* `fp agent ask "and how about the last week?" --chat 07854990`
|
|
301
|
+
* `fp agent ask "summarize p95 latency" --model claude-opus-4-7`
|
|
302
|
+
"""
|
|
303
|
+
state: AppState = ctx.obj
|
|
304
|
+
deny_in_key_mode(state, "agent", _KEY_MODE_REASON)
|
|
305
|
+
if not message:
|
|
306
|
+
message = sys.stdin.read().strip() if not sys.stdin.isatty() else typer.prompt("question", err=True)
|
|
307
|
+
if not message:
|
|
308
|
+
raise typer.BadParameter("Provide a question as the MESSAGE argument or on stdin.")
|
|
309
|
+
cctx = require_auth(state)
|
|
310
|
+
if model is not None:
|
|
311
|
+
_validate_model(cctx, model)
|
|
312
|
+
|
|
313
|
+
# Continuing a chat → resolve the (short) handle to a full id and load its prior thread;
|
|
314
|
+
# a new chat starts empty (and is created only on success, so a failed ask leaves no orphan).
|
|
315
|
+
full_chat_id: Optional[str] = None
|
|
316
|
+
prior = []
|
|
317
|
+
if chat:
|
|
318
|
+
full_chat_id = _resolve_chat_or_exit(state, cctx, chat)["id"]
|
|
319
|
+
prior = api.get_conversation(cctx, full_chat_id).get("messages", []) or []
|
|
320
|
+
thread = [
|
|
321
|
+
{"role": str(m.get("role", "user")), "content": {"text": _text_of(m.get("content"))}}
|
|
322
|
+
for m in prior
|
|
323
|
+
]
|
|
324
|
+
thread.append({"role": "user", "content": {"text": message}})
|
|
325
|
+
|
|
326
|
+
# The reply streams server-side (an LLM turn + tool calls can take many seconds); show a
|
|
327
|
+
# themed spinner on stderr so the CLI doesn't look hung. No-op under --json / non-TTY.
|
|
328
|
+
with output.thinking("thinking…", enabled=not state.json):
|
|
329
|
+
result = api.agent_chat_oneshot(
|
|
330
|
+
cctx, messages=thread, conversation_id=full_chat_id, page_context=page_context, model=model
|
|
331
|
+
)
|
|
332
|
+
|
|
333
|
+
chat_id = full_chat_id
|
|
334
|
+
if not result.get("interrupted") and not result.get("error"):
|
|
335
|
+
if chat_id is None:
|
|
336
|
+
# New chat: set the question-derived title at creation (one POST) instead of
|
|
337
|
+
# create-with-empty-title then a follow-up rename PATCH.
|
|
338
|
+
chat_id = (api.create_conversation(cctx, title=_title_from_question(message)) or {}).get("id")
|
|
339
|
+
if chat_id:
|
|
340
|
+
full = thread + [{"role": "assistant", "content": {"text": result.get("answer", "")}}]
|
|
341
|
+
api.replace_messages(cctx, chat_id, full)
|
|
342
|
+
if full_chat_id is not None and not prior:
|
|
343
|
+
# Continuing a chat that had no messages yet → title it from this first question
|
|
344
|
+
# (a brand-new chat is already titled at create_conversation above).
|
|
345
|
+
api.rename_conversation(cctx, chat_id, _title_from_question(message))
|
|
346
|
+
|
|
347
|
+
_write.record_action(
|
|
348
|
+
"agent_chat", resource="conversation",
|
|
349
|
+
success=not result.get("interrupted"),
|
|
350
|
+
mode="continue" if chat else "new",
|
|
351
|
+
)
|
|
352
|
+
if state.json:
|
|
353
|
+
output.emit_json({**result, "chat_id": chat_id})
|
|
354
|
+
else:
|
|
355
|
+
for tool in result.get("tools", []):
|
|
356
|
+
output.agent_tool_used(tool)
|
|
357
|
+
answer = result.get("answer", "")
|
|
358
|
+
if sys.stdout.isatty():
|
|
359
|
+
output.render_agent_answer(answer, model=model) # boxed assistant card
|
|
360
|
+
else:
|
|
361
|
+
print(answer) # raw answer → stdout (pipeable)
|
|
362
|
+
if chat_id and not chat: # a fresh chat was created
|
|
363
|
+
output.render_agent_new_chat(chat_id)
|
|
364
|
+
if result.get("interrupted"):
|
|
365
|
+
output.agent_error(f"the assistant needs interactive input ({result.get('error')}); not supported in the CLI")
|
|
366
|
+
raise typer.Exit(code=1)
|
|
367
|
+
if result.get("error"):
|
|
368
|
+
output.agent_error(f"assistant error: {result.get('error')}")
|
|
369
|
+
raise typer.Exit(code=1)
|
|
370
|
+
|
|
371
|
+
|
|
372
|
+
_AGENT_GROUP_HELP = """Chat with the FailproofAI Cloud assistant (Claude) about your org's observability data.
|
|
373
|
+
|
|
374
|
+
`ask` a question — each ask is saved to a **chat**. Continue a chat with `--chat <short id>`
|
|
375
|
+
(the first 8 chars shown in `agent chats`). The assistant can read your sessions, events,
|
|
376
|
+
evaluations, alerts, keys, and saved queries — scoped to your org.
|
|
377
|
+
|
|
378
|
+
**Subcommands:** `health` · `models` · `chats` · `ask` · `show` · `rename` · `delete`
|
|
379
|
+
|
|
380
|
+
**Examples:**
|
|
381
|
+
|
|
382
|
+
* `fp agent health` — is the assistant configured on this deployment?
|
|
383
|
+
* `fp agent models` — which models can I pass to `ask --model`?
|
|
384
|
+
* `fp agent ask "which agents errored most in the last day?"` — ask (starts a new chat)
|
|
385
|
+
* `fp agent ask "and the last week?" --chat 07854990` — continue a chat by its short id
|
|
386
|
+
* `fp agent chats` — list your chats (with their short ids)
|
|
387
|
+
* `fp agent show 07854990` — read a chat's full thread
|
|
388
|
+
* `fp agent rename 07854990 --title "latency dig"` · `fp agent delete 07854990`
|
|
389
|
+
"""
|
|
390
|
+
|
|
391
|
+
|
|
392
|
+
def register(app: typer.Typer) -> None:
|
|
393
|
+
agent = typer.Typer(
|
|
394
|
+
no_args_is_help=True,
|
|
395
|
+
rich_markup_mode="markdown",
|
|
396
|
+
context_settings={"help_option_names": ["-h", "--help"]},
|
|
397
|
+
help=_AGENT_GROUP_HELP,
|
|
398
|
+
)
|
|
399
|
+
# Order: discover (health/models) → list (chats) → use (ask) → manage one (show/rename/delete).
|
|
400
|
+
agent.command("health", epilog=GLOBALS_EPILOG)(agent_health)
|
|
401
|
+
agent.command("models", epilog=GLOBALS_EPILOG)(agent_models)
|
|
402
|
+
agent.command("chats", epilog=GLOBALS_EPILOG)(agent_chats)
|
|
403
|
+
agent.command("ask", epilog=GLOBALS_EPILOG)(agent_ask)
|
|
404
|
+
agent.command("show", epilog=GLOBALS_EPILOG)(agent_show)
|
|
405
|
+
agent.command("rename", epilog=GLOBALS_EPILOG)(agent_rename)
|
|
406
|
+
agent.command("delete", epilog=GLOBALS_EPILOG)(agent_delete)
|
|
407
|
+
app.add_typer(agent, name="agent")
|