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,378 @@
|
|
|
1
|
+
"""Saved SQL queries + ad-hoc runner: query list/show/create/update/delete/run/schema.
|
|
2
|
+
|
|
3
|
+
Runs against the server's read-only analytics pool. SQL can be inline or read from a
|
|
4
|
+
file with ``--sql @path.sql``; run parameters are positional ``$1..$N`` values.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
from typing import Any, List, Optional
|
|
10
|
+
|
|
11
|
+
import typer
|
|
12
|
+
|
|
13
|
+
from .. import _click_compat as click # the Click Typer is running; see _click_compat
|
|
14
|
+
from .. import client as api
|
|
15
|
+
from .. import output
|
|
16
|
+
from .._context import GLOBALS_EPILOG, AppState, require_auth, resolve_fields, validate_limit
|
|
17
|
+
from ..errors import NotFoundError
|
|
18
|
+
from ..models import SavedQuery
|
|
19
|
+
from . import _write
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def _humanize_list(items: List[str]) -> str:
|
|
23
|
+
"""``["name","sql","description"]`` → ``name, sql, and description``; one item → itself."""
|
|
24
|
+
if not items:
|
|
25
|
+
return ""
|
|
26
|
+
if len(items) == 1:
|
|
27
|
+
return items[0]
|
|
28
|
+
if len(items) == 2:
|
|
29
|
+
return f"{items[0]} and {items[1]}"
|
|
30
|
+
return ", ".join(items[:-1]) + f", and {items[-1]}"
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def _read_sql(value: str) -> str:
|
|
34
|
+
"""Inline SQL, or the contents of a file when given as ``@path.sql``."""
|
|
35
|
+
if value.startswith("@"):
|
|
36
|
+
return _write.read_text_arg(value[1:], flag="--sql")
|
|
37
|
+
return value
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def _coerce_value(raw: str) -> Any:
|
|
41
|
+
"""Best-effort coercion of a run parameter value to int/float/bool/null/str."""
|
|
42
|
+
low = raw.lower()
|
|
43
|
+
if low in ("true", "false"):
|
|
44
|
+
return low == "true"
|
|
45
|
+
if low in ("null", "none"):
|
|
46
|
+
return None
|
|
47
|
+
try:
|
|
48
|
+
return int(raw)
|
|
49
|
+
except ValueError:
|
|
50
|
+
pass
|
|
51
|
+
try:
|
|
52
|
+
return float(raw)
|
|
53
|
+
except ValueError:
|
|
54
|
+
pass
|
|
55
|
+
return raw
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def _resolve_query_or_exit(state: AppState, queries, handle: str) -> SavedQuery:
|
|
59
|
+
"""Resolve a saved query by **name** (primary), falling back to an exact id match, raising a
|
|
60
|
+
typed error the central chokepoint renders: none → exit 6, several → exit 2."""
|
|
61
|
+
return _write.resolve_one(queries, handle, kind="query", plural="queries", list_cmd="query list")
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def query_list(
|
|
65
|
+
ctx: typer.Context,
|
|
66
|
+
show_id: bool = typer.Option(False, "--show-id", help="Prepend a short query-id column (the full id is always in --json)."),
|
|
67
|
+
fields: Optional[str] = typer.Option(None, "--fields", help="Comma-separated subset of raw fields (falls back to a plain table)."),
|
|
68
|
+
) -> None:
|
|
69
|
+
"""List the org's saved queries in a boxed table, newest first.
|
|
70
|
+
|
|
71
|
+
Shows `name · description · created by · created` — the long description is truncated to one
|
|
72
|
+
line (full text in `query show` / `--json`), and `created` is when the query was made (not
|
|
73
|
+
`updated_at`). `name` is the handle `query run`/`query show` take; the raw id is hidden unless
|
|
74
|
+
`--show-id`. Needs `queries:read`. With `--json`: `{"queries": [{id, name, description,
|
|
75
|
+
sql_text, params, created_by, created_at, ...}]}`.
|
|
76
|
+
|
|
77
|
+
Example:
|
|
78
|
+
|
|
79
|
+
* `fp query list`
|
|
80
|
+
"""
|
|
81
|
+
state: AppState = ctx.obj
|
|
82
|
+
cctx = require_auth(state)
|
|
83
|
+
queries = api.list_saved_queries(cctx)
|
|
84
|
+
cols = resolve_fields(fields, SavedQuery)
|
|
85
|
+
if state.json:
|
|
86
|
+
payload = output.project_dicts(queries, cols) if cols else queries
|
|
87
|
+
output.emit_json({"queries": payload})
|
|
88
|
+
return
|
|
89
|
+
if cols:
|
|
90
|
+
# `--fields` asks for specific raw columns → the generic table (no bespoke styling).
|
|
91
|
+
output.print_table(list(cols), output.project_rows(queries, cols), title=f"Saved queries ({len(queries)})")
|
|
92
|
+
return
|
|
93
|
+
output.render_queries(queries, show_id=show_id)
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def query_show(
|
|
97
|
+
ctx: typer.Context,
|
|
98
|
+
name: str = typer.Argument(..., metavar="NAME", help="Saved query name (or id)."),
|
|
99
|
+
) -> None:
|
|
100
|
+
"""Show one saved query — a metadata card + its full, syntax-highlighted SQL.
|
|
101
|
+
|
|
102
|
+
Referenced by query **name** (a UUID-shaped id is also accepted). The SQL is shown in full with
|
|
103
|
+
line numbers. Not-found → red `✗ no query named "…"`, exit 6. Needs `queries:read`. With
|
|
104
|
+
`--json`: the full `SavedQuery` (raw `sql_text` for piping).
|
|
105
|
+
|
|
106
|
+
Example:
|
|
107
|
+
|
|
108
|
+
* `fp query show q_eval_score_avg`
|
|
109
|
+
"""
|
|
110
|
+
state: AppState = ctx.obj
|
|
111
|
+
cctx = require_auth(state)
|
|
112
|
+
q = _resolve_query_or_exit(state, api.list_saved_queries(cctx), name)
|
|
113
|
+
if state.json:
|
|
114
|
+
output.emit_json(q)
|
|
115
|
+
return
|
|
116
|
+
output.render_query_show(q)
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def query_create(
|
|
120
|
+
ctx: typer.Context,
|
|
121
|
+
name: str = typer.Argument(..., metavar="NAME", help="Saved query name (unique per org)."),
|
|
122
|
+
sql: str = typer.Option(..., "--sql", help="SQL text, or `@file.sql` to read from a file."),
|
|
123
|
+
description: str = typer.Option("", "--description", help="Optional one-line description."),
|
|
124
|
+
) -> None:
|
|
125
|
+
"""Create a saved query — give it a **name**, the SQL, and an optional description.
|
|
126
|
+
|
|
127
|
+
The query is saved to the org's analytics library; run it later with `fp query run
|
|
128
|
+
<name>`. The SQL can be inline or read from a file with `--sql @file.sql`. A name collision is
|
|
129
|
+
rejected up front. Needs `queries:write`. With `--json`: the created `SavedQuery`
|
|
130
|
+
(`{id, name, description, created_at, sql_text}`).
|
|
131
|
+
|
|
132
|
+
Examples:
|
|
133
|
+
|
|
134
|
+
* `fp query create top-agents --sql "SELECT agent_id, count() FROM analytics.events GROUP BY agent_id"`
|
|
135
|
+
* `fp query create errors-by-env --sql @errs.sql --description "errored events per environment"`
|
|
136
|
+
"""
|
|
137
|
+
state: AppState = ctx.obj
|
|
138
|
+
cctx = require_auth(state)
|
|
139
|
+
if any(q.name == name for q in api.list_saved_queries(cctx)): # names are unique per org
|
|
140
|
+
raise click.UsageError(f'a query named "{name}" already exists')
|
|
141
|
+
result = api.create_saved_query(
|
|
142
|
+
cctx, name=name, sql_text=_read_sql(sql), description=description, params=[]
|
|
143
|
+
)
|
|
144
|
+
_write.record_action("saved_query_created", resource="query", success=True)
|
|
145
|
+
if state.json:
|
|
146
|
+
output.emit_json({"id": result.id, "name": result.name, "description": result.description,
|
|
147
|
+
"created_at": result.created_at, "sql_text": result.sql_text})
|
|
148
|
+
return
|
|
149
|
+
output.render_query_created(result)
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
def query_update(
|
|
153
|
+
ctx: typer.Context,
|
|
154
|
+
name: str = typer.Argument(..., metavar="NAME", help="Saved query name (or id) to update."),
|
|
155
|
+
new_name: Optional[str] = typer.Option(None, "--name", help="Rename the query (defaults to the current name)."),
|
|
156
|
+
sql: Optional[str] = typer.Option(None, "--sql", help="New SQL text, or `@file.sql` (defaults to current)."),
|
|
157
|
+
description: Optional[str] = typer.Option(None, "--description", help="New description (defaults to current)."),
|
|
158
|
+
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."),
|
|
159
|
+
) -> None:
|
|
160
|
+
"""Update a saved query, referenced by **name** (or a UUID-shaped id).
|
|
161
|
+
|
|
162
|
+
Change any of `--name`, `--sql`, `--description` (pass at least one); fields you omit keep
|
|
163
|
+
their current value (the CLI reads the query first, then saves the merged result). It confirms
|
|
164
|
+
first (default no; `--yes` skips) and shows the updated query as a green card + numbered SQL
|
|
165
|
+
box. A no-op exits without saving. Needs `queries:write`. With `--json`: the updated
|
|
166
|
+
`SavedQuery`.
|
|
167
|
+
|
|
168
|
+
Examples:
|
|
169
|
+
|
|
170
|
+
* `fp query update top-agents --sql @top.sql` — replace the SQL
|
|
171
|
+
* `fp query update top-agents --name agent-totals --description "rows per agent"` — rename + redescribe
|
|
172
|
+
"""
|
|
173
|
+
state: AppState = ctx.obj
|
|
174
|
+
if new_name is None and sql is None and description is None:
|
|
175
|
+
raise typer.BadParameter("nothing to update — pass --name, --sql, and/or --description.")
|
|
176
|
+
cctx = require_auth(state)
|
|
177
|
+
queries = api.list_saved_queries(cctx)
|
|
178
|
+
q = _resolve_query_or_exit(state, queries, name)
|
|
179
|
+
|
|
180
|
+
# A rename to an existing name collides (like create's 409).
|
|
181
|
+
if new_name is not None and new_name != q.name and any(o.name == new_name for o in queries):
|
|
182
|
+
raise click.UsageError(f'a query named "{new_name}" already exists')
|
|
183
|
+
|
|
184
|
+
# Read `--sql` exactly ONCE. `@-` is stdin, and stdin can only be drained once: a
|
|
185
|
+
# second `_read_sql(sql)` for the request body returns "", so change detection would
|
|
186
|
+
# compare the real text while the save wrote an empty query — silently, exit 0.
|
|
187
|
+
new_sql = _read_sql(sql) if sql is not None else None
|
|
188
|
+
|
|
189
|
+
# Which fields actually change → the confirm consequence (and no-op detection).
|
|
190
|
+
changed: List[str] = []
|
|
191
|
+
if new_name is not None and new_name != q.name:
|
|
192
|
+
changed.append("name")
|
|
193
|
+
if new_sql is not None and new_sql != q.sql_text:
|
|
194
|
+
changed.append("sql")
|
|
195
|
+
if description is not None and description != q.description:
|
|
196
|
+
changed.append("description")
|
|
197
|
+
if not changed:
|
|
198
|
+
if state.json:
|
|
199
|
+
output.emit_json({"id": q.id, "name": q.name, "description": q.description,
|
|
200
|
+
"created_at": q.created_at, "sql_text": q.sql_text})
|
|
201
|
+
else:
|
|
202
|
+
output.query_no_change()
|
|
203
|
+
return
|
|
204
|
+
|
|
205
|
+
if _write.should_prompt(state, yes):
|
|
206
|
+
if not output.confirm_query_update(q.name, _humanize_list(changed)):
|
|
207
|
+
if state.json:
|
|
208
|
+
output.emit_json({"cancelled": True})
|
|
209
|
+
else:
|
|
210
|
+
output.query_cancelled("nothing changed")
|
|
211
|
+
return
|
|
212
|
+
|
|
213
|
+
result = api.update_saved_query(
|
|
214
|
+
cctx, q.id,
|
|
215
|
+
name=new_name if new_name is not None else q.name,
|
|
216
|
+
sql_text=new_sql if new_sql is not None else q.sql_text,
|
|
217
|
+
description=description if description is not None else q.description,
|
|
218
|
+
params=q.params,
|
|
219
|
+
)
|
|
220
|
+
_write.record_action("saved_query_updated", resource="query", success=True)
|
|
221
|
+
if state.json:
|
|
222
|
+
output.emit_json({"id": result.id, "name": result.name, "description": result.description,
|
|
223
|
+
"created_at": result.created_at, "sql_text": result.sql_text})
|
|
224
|
+
return
|
|
225
|
+
output.render_query_updated(result, old_name=q.name)
|
|
226
|
+
|
|
227
|
+
|
|
228
|
+
def query_delete(
|
|
229
|
+
ctx: typer.Context,
|
|
230
|
+
name: str = typer.Argument(..., metavar="NAME", help="Saved query name (or id) to delete."),
|
|
231
|
+
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."),
|
|
232
|
+
) -> None:
|
|
233
|
+
"""Delete a saved query, referenced by **name** (or a UUID-shaped id). This cannot be undone.
|
|
234
|
+
|
|
235
|
+
Needs `queries:delete`. With `--json`: `{"deleted": true, "id", "name"}`.
|
|
236
|
+
|
|
237
|
+
Example:
|
|
238
|
+
|
|
239
|
+
* `fp query delete errs --yes`
|
|
240
|
+
"""
|
|
241
|
+
state: AppState = ctx.obj
|
|
242
|
+
cctx = require_auth(state)
|
|
243
|
+
q = _resolve_query_or_exit(state, api.list_saved_queries(cctx), name)
|
|
244
|
+
if _write.should_prompt(state, yes):
|
|
245
|
+
output.render_query_delete_preview(q) # amber preview of what's about to go
|
|
246
|
+
if not output.confirm_query_delete():
|
|
247
|
+
if state.json:
|
|
248
|
+
output.emit_json({"cancelled": True})
|
|
249
|
+
else:
|
|
250
|
+
output.query_cancelled("nothing deleted")
|
|
251
|
+
return
|
|
252
|
+
api.delete_saved_query(cctx, q.id)
|
|
253
|
+
_write.record_action("saved_query_deleted", resource="query", success=True, destructive=True)
|
|
254
|
+
if state.json:
|
|
255
|
+
output.emit_json({"deleted": True, "id": q.id, "name": q.name})
|
|
256
|
+
else:
|
|
257
|
+
output.query_deleted(q.name)
|
|
258
|
+
|
|
259
|
+
|
|
260
|
+
def query_run(
|
|
261
|
+
ctx: typer.Context,
|
|
262
|
+
name: Optional[str] = typer.Argument(None, metavar="[NAME]", help="Saved query name to run (or a UUID-shaped id)."),
|
|
263
|
+
sql: Optional[str] = typer.Option(None, "--sql", help="Run ad-hoc SQL instead of a saved query, or `@file.sql`."),
|
|
264
|
+
limit: Optional[int] = typer.Option(None, "--limit", help=f"Max rows to show in the table view (default {output.QUERY_RUN_ROW_CAP}; --json always returns all)."),
|
|
265
|
+
all_: bool = typer.Option(False, "--all", help="Show every returned row (override the table row cap)."),
|
|
266
|
+
param: Optional[List[str]] = typer.Option(None, "--arg", "--param", help="Positional argument value bound to $1..$N, in order (repeatable). Alias: --param."),
|
|
267
|
+
) -> None:
|
|
268
|
+
"""Run a saved query by **name**, or ad-hoc `--sql`, against the read-only analytics pool.
|
|
269
|
+
|
|
270
|
+
The result is rendered adaptively from its shape — a scalar stat card, a single record, or a
|
|
271
|
+
boxed table (capped to a preview; `--limit`/`--all` adjust it, `--json` returns everything).
|
|
272
|
+
Pass a saved query's positional parameters with `--arg` (one per `$1..$N`, in order). Needs
|
|
273
|
+
`queries:run`. Not-found → red `✗ no query named "…"`; a SQL/exec error → `✗ query failed — …`.
|
|
274
|
+
With `--json`: `{columns: [{name, type}], rows: [[...]], truncated, elapsed_ms}` (all rows).
|
|
275
|
+
|
|
276
|
+
Examples:
|
|
277
|
+
|
|
278
|
+
* `fp query run q_eval_total`
|
|
279
|
+
* `fp query run q_eval_score_avg --arg agent-codegen`
|
|
280
|
+
* `fp --json query run --sql "select count(*) from analytics.events"`
|
|
281
|
+
"""
|
|
282
|
+
state: AppState = ctx.obj
|
|
283
|
+
if (name is None) == (sql is None):
|
|
284
|
+
raise typer.BadParameter("pass a saved query NAME or --sql (exactly one).")
|
|
285
|
+
validate_limit(limit)
|
|
286
|
+
cctx = require_auth(state)
|
|
287
|
+
query_id: Optional[str] = None
|
|
288
|
+
display_name = "query"
|
|
289
|
+
if name is not None:
|
|
290
|
+
q = _resolve_query_or_exit(state, api.list_saved_queries(cctx), name)
|
|
291
|
+
query_id, display_name = q.id, q.name
|
|
292
|
+
values = [_coerce_value(v) for v in (param or [])]
|
|
293
|
+
# A SQL/exec/permission failure propagates as a typed error to the central chokepoint
|
|
294
|
+
# (JSON envelope under --json, red box otherwise). The DB detail is surfaced because
|
|
295
|
+
# client._extract_error folds the server's `detail` into the message.
|
|
296
|
+
result = api.run_query(
|
|
297
|
+
cctx, sql=_read_sql(sql) if sql is not None else None, query_id=query_id, params=values
|
|
298
|
+
)
|
|
299
|
+
_write.record_action(
|
|
300
|
+
"query_run", success=True, via="saved" if query_id else "ad_hoc", row_count_bucket=_bucket(len(result.rows))
|
|
301
|
+
)
|
|
302
|
+
if state.json:
|
|
303
|
+
output.emit_json(result)
|
|
304
|
+
return
|
|
305
|
+
row_cap = limit if limit is not None else output.QUERY_RUN_ROW_CAP
|
|
306
|
+
output.render_query_result(display_name, result, row_cap=row_cap, show_all=all_)
|
|
307
|
+
|
|
308
|
+
|
|
309
|
+
def _bucket(n: int) -> str:
|
|
310
|
+
if n == 0:
|
|
311
|
+
return "0"
|
|
312
|
+
if n <= 10:
|
|
313
|
+
return "1-10"
|
|
314
|
+
if n <= 100:
|
|
315
|
+
return "11-100"
|
|
316
|
+
return "100+"
|
|
317
|
+
|
|
318
|
+
|
|
319
|
+
def query_schema(
|
|
320
|
+
ctx: typer.Context,
|
|
321
|
+
table: Optional[str] = typer.Argument(None, metavar="[TABLE]", help="Filter to one table's columns."),
|
|
322
|
+
) -> None:
|
|
323
|
+
"""Show the queryable analytics schema in a boxed, table-grouped view.
|
|
324
|
+
|
|
325
|
+
Each table's name prints once (then blanks down its columns); types are coloured by category
|
|
326
|
+
(numeric / string / uuid+timestamp / bool) with a dim `?` for nullable. Pass a `TABLE` to
|
|
327
|
+
filter to one. Needs `queries:read`. With `--json`: `{schema, columns: [{table, column, type,
|
|
328
|
+
nullable}]}` (the `?` split into a boolean).
|
|
329
|
+
|
|
330
|
+
Examples:
|
|
331
|
+
|
|
332
|
+
* `fp query schema`
|
|
333
|
+
* `fp query schema events`
|
|
334
|
+
"""
|
|
335
|
+
state: AppState = ctx.obj
|
|
336
|
+
cctx = require_auth(state)
|
|
337
|
+
data = api.query_schema(cctx)
|
|
338
|
+
tables = list(data.get("tables", []) or [])
|
|
339
|
+
if table is not None:
|
|
340
|
+
match = [t for t in tables if t.get("name") == table]
|
|
341
|
+
if not match:
|
|
342
|
+
names = [str(t.get("name", "")) for t in tables]
|
|
343
|
+
raise NotFoundError(
|
|
344
|
+
f'no table named "{table}"', hint=f"available: {', '.join(names)}" if names else None
|
|
345
|
+
)
|
|
346
|
+
tables = match
|
|
347
|
+
data = {**data, "tables": tables}
|
|
348
|
+
|
|
349
|
+
if state.json:
|
|
350
|
+
flat = []
|
|
351
|
+
for t in tables:
|
|
352
|
+
for col in t.get("columns", []) or []:
|
|
353
|
+
ty = str(col.get("type", ""))
|
|
354
|
+
nullable = ty.endswith("?")
|
|
355
|
+
flat.append({"table": t.get("name", ""), "column": col.get("name", ""),
|
|
356
|
+
"type": ty[:-1] if nullable else ty, "nullable": nullable})
|
|
357
|
+
output.emit_json({"schema": data.get("schema", ""), "columns": flat})
|
|
358
|
+
return
|
|
359
|
+
output.render_query_schema(data)
|
|
360
|
+
total_cols = sum(len(t.get("columns", []) or []) for t in tables)
|
|
361
|
+
output.schema_footer(len(tables), total_cols)
|
|
362
|
+
|
|
363
|
+
|
|
364
|
+
def register(app: typer.Typer) -> None:
|
|
365
|
+
query_app = typer.Typer(
|
|
366
|
+
no_args_is_help=True,
|
|
367
|
+
rich_markup_mode="markdown",
|
|
368
|
+
context_settings={"help_option_names": ["-h", "--help"]},
|
|
369
|
+
help="Run and manage saved SQL queries (list / show / create / update / delete / run / schema).",
|
|
370
|
+
)
|
|
371
|
+
query_app.command("list", epilog=GLOBALS_EPILOG)(query_list)
|
|
372
|
+
query_app.command("show", epilog=GLOBALS_EPILOG)(query_show)
|
|
373
|
+
query_app.command("create", epilog=GLOBALS_EPILOG)(query_create)
|
|
374
|
+
query_app.command("update", epilog=GLOBALS_EPILOG)(query_update)
|
|
375
|
+
query_app.command("delete", epilog=GLOBALS_EPILOG)(query_delete)
|
|
376
|
+
query_app.command("run", epilog=GLOBALS_EPILOG)(query_run)
|
|
377
|
+
query_app.command("schema", epilog=GLOBALS_EPILOG)(query_schema)
|
|
378
|
+
app.add_typer(query_app, name="query")
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
"""sessions — list agent sessions (newest first) with their run status."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import List, Optional
|
|
6
|
+
|
|
7
|
+
import typer
|
|
8
|
+
|
|
9
|
+
from .. import client as api
|
|
10
|
+
from .. import dates, output
|
|
11
|
+
from .._context import (
|
|
12
|
+
GLOBALS_EPILOG,
|
|
13
|
+
AppState,
|
|
14
|
+
collect_multi,
|
|
15
|
+
require_auth,
|
|
16
|
+
resolve_dates,
|
|
17
|
+
resolve_fields,
|
|
18
|
+
validate_choice,
|
|
19
|
+
validate_limit,
|
|
20
|
+
)
|
|
21
|
+
from ..models import Session
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def sessions(
|
|
25
|
+
ctx: typer.Context,
|
|
26
|
+
limit: int = typer.Option(50, "--limit", "-n", help="Max rows in total. Use --all to auto-paginate beyond the server's single-request cap."),
|
|
27
|
+
since: Optional[str] = typer.Option(None, "--since", help=f"Relative window from now (dashboard presets): {', '.join(dates.SINCE_CHOICES)}."),
|
|
28
|
+
ts_from: Optional[str] = typer.Option(None, "--from", help="Custom-range start, ISO-8601 UTC (e.g. 2026-05-01T00:00:00Z). Overrides --since."),
|
|
29
|
+
ts_to: Optional[str] = typer.Option(None, "--to", help="Custom-range end, ISO-8601 UTC. Overrides --since."),
|
|
30
|
+
environment: Optional[List[str]] = typer.Option(None, "--env", help="Filter by environment. Accepts multiple — repeat the flag or comma-separate: `--env prod --env staging` or `--env prod,staging` (matches any)."),
|
|
31
|
+
status: Optional[List[str]] = typer.Option(None, "--status", help="Filter by run status (`done`/`error`/`timeout`), matched against each session's latest evaluation. Accepts multiple — repeat the flag or comma-separate: `--status error,timeout` (matches any)."),
|
|
32
|
+
agent_id: Optional[List[str]] = typer.Option(None, "--agent-id", help="Filter by agent id. Accepts multiple — repeat the flag or comma-separate: `--agent-id a,b` (matches any)."),
|
|
33
|
+
session_id: Optional[List[str]] = typer.Option(None, "--session-id", help="Filter by session id. Accepts multiple — repeat the flag or comma-separate: `--session-id a,b` (matches any)."),
|
|
34
|
+
fetch_all: bool = typer.Option(False, "--all", help="Auto-paginate through all pages, up to --limit."),
|
|
35
|
+
cursor: Optional[str] = typer.Option(None, "--cursor", help="Resume after this cursor (a prior next_cursor; opaque token)."),
|
|
36
|
+
page_size: Optional[int] = typer.Option(None, "--page-size", help="Rows per request when --all (max 200)."),
|
|
37
|
+
fields: Optional[str] = typer.Option(None, "--fields", help="Comma-separated subset of fields to output (applies to --json and the table). e.g. `session_id,status,last_event_at`."),
|
|
38
|
+
full_ids: bool = typer.Option(False, "--full-ids", help="Show full session ids in the table instead of truncating them (--json always has the full id)."),
|
|
39
|
+
agents_expand: bool = typer.Option(False, "--agents", help="Expand every multi-agent session into an indented roster of its agents (name + event count). Rendered view only — `--json` always carries the full `agents` list."),
|
|
40
|
+
) -> None:
|
|
41
|
+
"""List **sessions** — one row per agent run, newest first.
|
|
42
|
+
|
|
43
|
+
Shows what ran and how it ended: `time · env · agent · session · status`. `status` is the
|
|
44
|
+
session's latest evaluation outcome (`done`/`error`/`timeout`, blank if it was never
|
|
45
|
+
evaluated). For the per-evaluation **scores** behind a session — and rolled-up score stats —
|
|
46
|
+
use `fp evals`.
|
|
47
|
+
|
|
48
|
+
The filters `--env`, `--status`, `--agent-id`, and `--session-id` each accept **multiple
|
|
49
|
+
values** — repeat the flag or comma-separate (`--status error,timeout` is the same as
|
|
50
|
+
`--status error --status timeout`). Values within one filter match **any** of them; different
|
|
51
|
+
filters are combined (a session must satisfy all of them). Scope by time with `--since` (a
|
|
52
|
+
preset window) or `--from`/`--to` (a custom UTC range), and page with `--all` or `--cursor`.
|
|
53
|
+
|
|
54
|
+
A session can involve **more than one agent**. The `agent` column shows the root agent (the
|
|
55
|
+
first to start) plus a `+N` badge counting the others; `--agents` expands every multi-agent
|
|
56
|
+
session into an indented roster (each agent + its event count). `--agent-id` matches a session
|
|
57
|
+
if **any** of its agents is one you named (not just the root).
|
|
58
|
+
|
|
59
|
+
Needs `evaluations:read`. With `--json`: `{"sessions": [...], "next_cursor": <cursor or null>}`.
|
|
60
|
+
Each row has `session_id, agent_id, agents, environment, status, scores, event_count,
|
|
61
|
+
started_at, last_event_at, first_event_id, last_event_id, latest_evaluation` (also the valid
|
|
62
|
+
`--fields` names) — `agent_id` is the root agent and `agents` is the full roster (each
|
|
63
|
+
`{agent_id, event_count}`, sorted by event count); `status` and `scores` are flattened up from
|
|
64
|
+
the latest evaluation for convenience, and the full evaluation is kept under `latest_evaluation`.
|
|
65
|
+
|
|
66
|
+
Examples:
|
|
67
|
+
|
|
68
|
+
* `fp sessions --env prod,staging --status error,timeout` — recent prod/staging runs that errored or timed out
|
|
69
|
+
* `fp sessions --agent-id agent-orderbot --since 24h` — every session agent-orderbot took part in over the last 24h
|
|
70
|
+
* `fp sessions --agents` — expand each multi-agent session to its full agent roster
|
|
71
|
+
* `fp --json sessions --all --fields session_id,agents | jq '.sessions'` — every session's full agent roster as JSON
|
|
72
|
+
"""
|
|
73
|
+
state: AppState = ctx.obj
|
|
74
|
+
cctx = require_auth(state)
|
|
75
|
+
validate_limit(limit)
|
|
76
|
+
frm, to = resolve_dates(since, ts_from, ts_to)
|
|
77
|
+
cols = resolve_fields(fields, Session)
|
|
78
|
+
|
|
79
|
+
# Multi-value filters: merge repeated flags + comma-separated values into one flat,
|
|
80
|
+
# de-duplicated list each (the server UNIONs within a filter via `IN`, ANDs across them).
|
|
81
|
+
environment = collect_multi(environment)
|
|
82
|
+
status = collect_multi(status)
|
|
83
|
+
agent_id = collect_multi(agent_id)
|
|
84
|
+
session_id = collect_multi(session_id)
|
|
85
|
+
# Validate each status value client-side (clean exit 2) rather than a server 400.
|
|
86
|
+
for s in status or []:
|
|
87
|
+
validate_choice(s, ("done", "error", "timeout"), flag="--status")
|
|
88
|
+
|
|
89
|
+
def fetch(cursor: Optional[str], limit: Optional[int]):
|
|
90
|
+
return api.list_sessions(
|
|
91
|
+
cctx,
|
|
92
|
+
session_id=session_id,
|
|
93
|
+
agent_id=agent_id,
|
|
94
|
+
environment=environment,
|
|
95
|
+
status=status,
|
|
96
|
+
ts_from=frm,
|
|
97
|
+
ts_to=to,
|
|
98
|
+
cursor=cursor,
|
|
99
|
+
limit=limit,
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
if fetch_all:
|
|
103
|
+
walk = api.Walk()
|
|
104
|
+
items = list(
|
|
105
|
+
api.paginate(fetch, limit=limit, page_size=page_size, start_cursor=cursor, walk=walk)
|
|
106
|
+
)
|
|
107
|
+
# NOT `None`. `--limit` defaults to 50, so `--all` without an explicit
|
|
108
|
+
# limit stops at 50 rows — and hard-coding the cursor to null told the
|
|
109
|
+
# caller the feed was exhausted, on the one output a script or an agent
|
|
110
|
+
# reads. `walk` carries the cursor the walk actually stopped on.
|
|
111
|
+
next_cursor = walk.next_cursor
|
|
112
|
+
else:
|
|
113
|
+
page = fetch(cursor, limit)
|
|
114
|
+
items = page.items
|
|
115
|
+
next_cursor = page.next_cursor
|
|
116
|
+
|
|
117
|
+
if state.json:
|
|
118
|
+
payload = output.project_dicts(items, cols) if cols else items
|
|
119
|
+
output.emit_json({"sessions": payload, "next_cursor": next_cursor})
|
|
120
|
+
return
|
|
121
|
+
|
|
122
|
+
# Which narrowing filters did the user set? Used to word the empty-box message and to
|
|
123
|
+
# nudge them to re-check those values when 0 rows come back (a value matching nothing
|
|
124
|
+
# returns empty, not an error — so a typo looks like "no sessions").
|
|
125
|
+
active_filters = [
|
|
126
|
+
flag for flag, val in (
|
|
127
|
+
("--env", environment),
|
|
128
|
+
("--status", status),
|
|
129
|
+
("--agent-id", agent_id),
|
|
130
|
+
("--session-id", session_id),
|
|
131
|
+
("--since/--from/--to", since or ts_from or ts_to),
|
|
132
|
+
) if val
|
|
133
|
+
]
|
|
134
|
+
|
|
135
|
+
if cols:
|
|
136
|
+
# `--fields` asks for specific columns → the generic table (no bespoke styling).
|
|
137
|
+
output.print_table(list(cols), output.project_rows(items, cols), title=f"Sessions ({len(items)})")
|
|
138
|
+
else:
|
|
139
|
+
empty_message = "no sessions match these filters" if active_filters else "no sessions"
|
|
140
|
+
if agents_expand:
|
|
141
|
+
output.render_sessions_expanded(items, full_ids=full_ids, empty_message=empty_message)
|
|
142
|
+
else:
|
|
143
|
+
output.render_sessions(items, full_ids=full_ids, empty_message=empty_message)
|
|
144
|
+
multi_agent = sum(1 for e in items if output.is_multi_agent(e))
|
|
145
|
+
output.sessions_footer(len(items), more=next_cursor is not None, multi_agent=multi_agent)
|
|
146
|
+
if not items:
|
|
147
|
+
output.recheck_filters_hint(active_filters)
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
def register(app: typer.Typer) -> None:
|
|
151
|
+
app.command("sessions", epilog=GLOBALS_EPILOG)(sessions)
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
"""Org settings: settings list / schema / set.
|
|
2
|
+
|
|
3
|
+
Settings are a FIXED registry — you can read them (``list``), inspect what each accepts
|
|
4
|
+
(``schema``), and change an existing key's value (``set``). You can't create new keys.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import json
|
|
10
|
+
from typing import Any, Optional
|
|
11
|
+
|
|
12
|
+
import typer
|
|
13
|
+
|
|
14
|
+
from .. import client as api
|
|
15
|
+
from .. import output
|
|
16
|
+
from .._context import GLOBALS_EPILOG, AppState, require_auth
|
|
17
|
+
from ..errors import NotFoundError
|
|
18
|
+
from . import _write
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def settings_list(ctx: typer.Context) -> None:
|
|
22
|
+
"""List the org's settings and their current values in a boxed table.
|
|
23
|
+
|
|
24
|
+
Shows `key · value · type · updated` — the value rendered type-aware (lists comma-joined,
|
|
25
|
+
numbers pink, secrets masked), truncated to one line (full values in `--json`). `key` is the
|
|
26
|
+
handle `settings set` takes. Needs `settings:read`. With `--json`: `{"settings": [{key, value,
|
|
27
|
+
updated_at, updated_by, scope, schema}]}`.
|
|
28
|
+
|
|
29
|
+
Example:
|
|
30
|
+
|
|
31
|
+
* `fp settings list`
|
|
32
|
+
"""
|
|
33
|
+
state: AppState = ctx.obj
|
|
34
|
+
cctx = require_auth(state)
|
|
35
|
+
rows = api.list_settings(cctx)
|
|
36
|
+
if state.json:
|
|
37
|
+
output.emit_json({"settings": rows})
|
|
38
|
+
return
|
|
39
|
+
output.render_settings(rows, current_email=state.config.email)
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def settings_schema(ctx: typer.Context) -> None:
|
|
43
|
+
"""Show the settings registry — what each key is and what it accepts.
|
|
44
|
+
|
|
45
|
+
A boxed `key · type · accepts · description` table derived from each setting's `schema` blob
|
|
46
|
+
(there is no separate schema endpoint). `accepts` summarizes the constraints (int range + unit,
|
|
47
|
+
channel options, …). Needs `settings:read`. With `--json`: `{"settings": [<schema entries>]}`.
|
|
48
|
+
|
|
49
|
+
Example:
|
|
50
|
+
|
|
51
|
+
* `fp settings schema`
|
|
52
|
+
"""
|
|
53
|
+
state: AppState = ctx.obj
|
|
54
|
+
cctx = require_auth(state)
|
|
55
|
+
schema = api.get_settings_schema(cctx)
|
|
56
|
+
if state.json:
|
|
57
|
+
output.emit_json({"settings": schema})
|
|
58
|
+
return
|
|
59
|
+
output.render_settings_schema(schema)
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def settings_set(
|
|
63
|
+
ctx: typer.Context,
|
|
64
|
+
key: str = typer.Argument(..., help="Setting key to update (must be an existing key — see `settings list`)."),
|
|
65
|
+
value: Optional[str] = typer.Option(None, "--value", help="Scalar value (string; a digit-only value is sent as an integer)."),
|
|
66
|
+
json_value: Optional[str] = typer.Option(None, "--json-value", help="Raw JSON value, for arrays/objects (e.g. `[\"a@b.com\"]`)."),
|
|
67
|
+
file: Optional[str] = typer.Option(None, "--file", help="Read a JSON value from a file, or `-` for stdin."),
|
|
68
|
+
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."),
|
|
69
|
+
) -> None:
|
|
70
|
+
"""Change one org setting's value, showing the before → after.
|
|
71
|
+
|
|
72
|
+
Provide the value exactly one way: `--value` (scalar), `--json-value` (raw JSON for
|
|
73
|
+
arrays/objects), or `--file`/stdin (JSON). The CLI checks the key exists, shows the change and
|
|
74
|
+
confirms (a no-op is skipped), then renders the updated value. Needs `settings:write`. Unknown
|
|
75
|
+
key → exit 6; an invalid value → the server's clean message, exit non-zero. With `--json`: the
|
|
76
|
+
updated `SettingRow` (or `{cancelled: true}`).
|
|
77
|
+
|
|
78
|
+
Examples:
|
|
79
|
+
|
|
80
|
+
* `fp settings set session_ttl_secs --value 86400`
|
|
81
|
+
* `fp settings set alerts.email_default_recipients --json-value '["a@b.com"]'`
|
|
82
|
+
"""
|
|
83
|
+
state: AppState = ctx.obj
|
|
84
|
+
provided = [v is not None for v in (value, json_value, file)]
|
|
85
|
+
if sum(provided) != 1:
|
|
86
|
+
raise typer.BadParameter("Provide exactly one of --value, --json-value, or --file.")
|
|
87
|
+
|
|
88
|
+
if value is not None:
|
|
89
|
+
# `str.isdigit()` accepts characters int() rejects (superscripts, "--5"); just try the
|
|
90
|
+
# conversion and fall back to the string, so a non-int value is never an uncaught traceback.
|
|
91
|
+
try:
|
|
92
|
+
parsed: Any = int(value)
|
|
93
|
+
except ValueError:
|
|
94
|
+
parsed = value
|
|
95
|
+
elif json_value is not None:
|
|
96
|
+
try:
|
|
97
|
+
parsed = json.loads(json_value)
|
|
98
|
+
except json.JSONDecodeError as exc:
|
|
99
|
+
raise typer.BadParameter(f"--json-value is not valid JSON: {exc}", param_hint="--json-value")
|
|
100
|
+
else:
|
|
101
|
+
raw = _write.read_text_arg(file)
|
|
102
|
+
try:
|
|
103
|
+
parsed = json.loads(raw)
|
|
104
|
+
except json.JSONDecodeError as exc:
|
|
105
|
+
raise typer.BadParameter(f"--file is not valid JSON: {exc}", param_hint="--file")
|
|
106
|
+
|
|
107
|
+
cctx = require_auth(state)
|
|
108
|
+
current = next((s for s in api.list_settings(cctx) if s.key == key), None)
|
|
109
|
+
if current is None: # settings are a fixed registry — an unknown key can't be created
|
|
110
|
+
raise NotFoundError(
|
|
111
|
+
f'no setting named "{key}"', hint="run `fp settings list` to see them"
|
|
112
|
+
)
|
|
113
|
+
kind = (current.schema or {}).get("kind", "") if isinstance(current.schema, dict) else ""
|
|
114
|
+
|
|
115
|
+
if parsed == current.value: # no-op — don't call the server or prompt (secrets always blank)
|
|
116
|
+
if state.json:
|
|
117
|
+
output.emit_json(current)
|
|
118
|
+
else:
|
|
119
|
+
output.setting_no_change(key, current.value, kind)
|
|
120
|
+
return
|
|
121
|
+
|
|
122
|
+
if _write.should_prompt(state, yes):
|
|
123
|
+
if not output.confirm_setting_change(key, current.value, parsed, kind):
|
|
124
|
+
if state.json:
|
|
125
|
+
output.emit_json({"cancelled": True})
|
|
126
|
+
else:
|
|
127
|
+
output.print_cancelled("nothing changed")
|
|
128
|
+
return
|
|
129
|
+
|
|
130
|
+
# An invalid value (422) / unknown key (404) propagates as a typed error to the central
|
|
131
|
+
# chokepoint (JSON envelope under --json, red box otherwise).
|
|
132
|
+
row = api.put_setting(cctx, key, parsed)
|
|
133
|
+
_write.record_action("setting_updated", resource="setting", success=True)
|
|
134
|
+
if state.json:
|
|
135
|
+
output.emit_json(row)
|
|
136
|
+
return
|
|
137
|
+
output.render_setting_updated(row, kind)
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
def register(app: typer.Typer) -> None:
|
|
141
|
+
settings_app = typer.Typer(
|
|
142
|
+
no_args_is_help=True,
|
|
143
|
+
rich_markup_mode="markdown",
|
|
144
|
+
context_settings={"help_option_names": ["-h", "--help"]},
|
|
145
|
+
help="View and update org settings (list / schema / set).",
|
|
146
|
+
)
|
|
147
|
+
settings_app.command("list", epilog=GLOBALS_EPILOG)(settings_list)
|
|
148
|
+
settings_app.command("schema", epilog=GLOBALS_EPILOG)(settings_schema)
|
|
149
|
+
settings_app.command("set", epilog=GLOBALS_EPILOG)(settings_set)
|
|
150
|
+
app.add_typer(settings_app, name="settings")
|