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,445 @@
|
|
|
1
|
+
"""Alert definitions: alerts list / show / create / update / delete / test.
|
|
2
|
+
|
|
3
|
+
Alerts are referenced by their **name** (unique per org). An alert is a **trigger** (a condition,
|
|
4
|
+
shaped per ``trigger_kind`` — e.g. a metric threshold, a custom SQL count, an evaluation-score
|
|
5
|
+
rule) plus an **evaluation** cadence and a set of notification **channels**. The trigger body
|
|
6
|
+
(``trigger_spec``) + ``channels`` are opaque/union JSON, so create/update take them as JSON flags
|
|
7
|
+
or a full payload via ``--file``/stdin, with a few common scalar fields available as convenience
|
|
8
|
+
overrides layered on top.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
import json
|
|
14
|
+
from typing import Any, Dict, Optional
|
|
15
|
+
|
|
16
|
+
import typer
|
|
17
|
+
|
|
18
|
+
from .. import _click_compat as click # the Click Typer is running; see _click_compat
|
|
19
|
+
from .. import client as api
|
|
20
|
+
from .. import output
|
|
21
|
+
from .._context import GLOBALS_EPILOG, AppState, require_auth
|
|
22
|
+
from ..models import Alert
|
|
23
|
+
from . import _write
|
|
24
|
+
|
|
25
|
+
_TRIGGER_KINDS = ("metric_threshold", "custom_sql", "evaluation_score", "eval_compound", "per_event")
|
|
26
|
+
_SEVERITIES = ("info", "warning", "critical")
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def _load_file(file: Optional[str]) -> Dict[str, Any]:
|
|
30
|
+
if file is None:
|
|
31
|
+
return {}
|
|
32
|
+
raw = _write.read_text_arg(file)
|
|
33
|
+
try:
|
|
34
|
+
parsed = json.loads(raw)
|
|
35
|
+
except json.JSONDecodeError as exc:
|
|
36
|
+
raise typer.BadParameter(f"--file is not valid JSON: {exc}", param_hint="--file")
|
|
37
|
+
if not isinstance(parsed, dict):
|
|
38
|
+
raise typer.BadParameter("--file must contain a JSON object.", param_hint="--file")
|
|
39
|
+
return parsed
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def _apply_overrides(body: Dict[str, Any], **overrides: Any) -> Dict[str, Any]:
|
|
43
|
+
for key, value in overrides.items():
|
|
44
|
+
if value is not None:
|
|
45
|
+
body[key] = value
|
|
46
|
+
return body
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def _alert_to_body(alert: Alert) -> Dict[str, Any]:
|
|
50
|
+
"""The writable AlertBody fields of an existing alert, used as the merge base for
|
|
51
|
+
a flag-only update.
|
|
52
|
+
|
|
53
|
+
The server's ``PUT /api/alerts/{id}`` is a **full replace** (it overwrites every
|
|
54
|
+
column), so a single-field edit must resend the whole alert. We seed the body from
|
|
55
|
+
the current alert and let the override flags change just the named fields — the
|
|
56
|
+
same read-merge ``users update`` already does."""
|
|
57
|
+
return {
|
|
58
|
+
"name": alert.name,
|
|
59
|
+
"description": alert.description,
|
|
60
|
+
"enabled": alert.enabled,
|
|
61
|
+
"trigger_kind": alert.trigger_kind,
|
|
62
|
+
"trigger_spec": alert.trigger_spec,
|
|
63
|
+
"min_breaches": alert.min_breaches,
|
|
64
|
+
"eval_window": alert.eval_window,
|
|
65
|
+
"eval_interval_secs": alert.eval_interval_secs,
|
|
66
|
+
"severity": alert.severity,
|
|
67
|
+
"channels": alert.channels,
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def _validate_alert(body: Dict[str, Any], *, require_core: bool) -> None:
|
|
72
|
+
if require_core:
|
|
73
|
+
if not body.get("name"):
|
|
74
|
+
raise typer.BadParameter("alert 'name' is required (in --file or via --name).")
|
|
75
|
+
if not body.get("trigger_kind"):
|
|
76
|
+
raise typer.BadParameter("alert 'trigger_kind' is required (in --file or via --trigger-kind).")
|
|
77
|
+
if body.get("trigger_spec") is None:
|
|
78
|
+
raise typer.BadParameter("alert 'trigger_spec' is required (provide it in --file or --trigger-spec).")
|
|
79
|
+
if body.get("trigger_kind") and body["trigger_kind"] not in _TRIGGER_KINDS:
|
|
80
|
+
raise typer.BadParameter(f"trigger_kind must be one of: {', '.join(_TRIGGER_KINDS)}.")
|
|
81
|
+
if body.get("severity") and body["severity"] not in _SEVERITIES:
|
|
82
|
+
raise typer.BadParameter(f"severity must be one of: {', '.join(_SEVERITIES)}.")
|
|
83
|
+
mb, ew = body.get("min_breaches"), body.get("eval_window")
|
|
84
|
+
if isinstance(mb, int) and mb < 1:
|
|
85
|
+
raise typer.BadParameter("min_breaches must be >= 1.")
|
|
86
|
+
if isinstance(ew, int) and ew < 1:
|
|
87
|
+
raise typer.BadParameter("eval_window must be >= 1.")
|
|
88
|
+
if isinstance(mb, int) and isinstance(ew, int) and mb > ew:
|
|
89
|
+
raise typer.BadParameter("min_breaches cannot exceed eval_window.")
|
|
90
|
+
eis = body.get("eval_interval_secs")
|
|
91
|
+
if isinstance(eis, int) and not (30 <= eis <= 86400):
|
|
92
|
+
raise typer.BadParameter("eval_interval_secs must be between 30 and 86400.")
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def _parse_json_opt(value: Optional[str], hint: str) -> Any:
|
|
96
|
+
if value is None:
|
|
97
|
+
return None
|
|
98
|
+
try:
|
|
99
|
+
return json.loads(value)
|
|
100
|
+
except json.JSONDecodeError as exc:
|
|
101
|
+
raise typer.BadParameter(f"{hint} is not valid JSON: {exc}", param_hint=hint)
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def _refetch_alert(cctx, name: Optional[str]) -> Optional[Alert]:
|
|
105
|
+
"""Re-read the canonical stored alert by name after a create/update (the server busts the
|
|
106
|
+
list cache on write, so this reflects the saved state incl. server-applied defaults). Returns
|
|
107
|
+
``None`` on any miss so the caller can fall back to rendering from the request body."""
|
|
108
|
+
if not name:
|
|
109
|
+
return None
|
|
110
|
+
try:
|
|
111
|
+
for a in api.list_alerts(cctx):
|
|
112
|
+
if a.name == name:
|
|
113
|
+
return a
|
|
114
|
+
except Exception:
|
|
115
|
+
return None
|
|
116
|
+
return None
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def _alert_from_body(body: Dict[str, Any], base: Optional[Alert] = None) -> Alert:
|
|
120
|
+
"""Build an ``Alert`` view from a create/update request body (fallback when the canonical
|
|
121
|
+
re-fetch misses). ``base`` (the pre-update alert) supplies id/created/open-incidents context;
|
|
122
|
+
for a create there is none, so those default to empty/0."""
|
|
123
|
+
return Alert(
|
|
124
|
+
id=base.id if base else "",
|
|
125
|
+
name=body.get("name") or (base.name if base else ""),
|
|
126
|
+
description=body.get("description") if body.get("description") is not None else (base.description if base else None),
|
|
127
|
+
enabled=body.get("enabled", base.enabled if base else True),
|
|
128
|
+
trigger_kind=body.get("trigger_kind") or (base.trigger_kind if base else ""),
|
|
129
|
+
trigger_spec=body.get("trigger_spec") or (base.trigger_spec if base else {}),
|
|
130
|
+
min_breaches=body.get("min_breaches", base.min_breaches if base else 1),
|
|
131
|
+
eval_window=body.get("eval_window", base.eval_window if base else 1),
|
|
132
|
+
eval_interval_secs=body.get("eval_interval_secs", base.eval_interval_secs if base else 0),
|
|
133
|
+
severity=body.get("severity") or (base.severity if base else ""),
|
|
134
|
+
channels=body.get("channels") if body.get("channels") is not None else (base.channels if base else []),
|
|
135
|
+
created_by=base.created_by if base else "",
|
|
136
|
+
created_at=base.created_at if base else "",
|
|
137
|
+
updated_at="",
|
|
138
|
+
last_attempted_at=base.last_attempted_at if base else None,
|
|
139
|
+
open_incidents=base.open_incidents if base else 0,
|
|
140
|
+
)
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
def _resolve_alert_or_exit(state: AppState, alerts, handle: str) -> Alert:
|
|
144
|
+
"""Resolve an alert by **name** (primary), falling back to an exact id match, raising a typed
|
|
145
|
+
error the central chokepoint renders: none → exit 6, several → exit 2."""
|
|
146
|
+
return _write.resolve_one(alerts, handle, kind="alert", list_cmd="alerts list")
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
def alerts_list(
|
|
150
|
+
ctx: typer.Context,
|
|
151
|
+
show_id: bool = typer.Option(False, "--show-id", help="Prepend a short alert-id column (the full id is always in --json)."),
|
|
152
|
+
) -> None:
|
|
153
|
+
"""List alert definitions in a boxed table, newest first.
|
|
154
|
+
|
|
155
|
+
Shows `created · name · by · trigger · severity · last alert` — `by` the creator's email,
|
|
156
|
+
severity colour-coded, and `last alert` the humanized age of the last evaluation (`never` if it
|
|
157
|
+
has never run, e.g. a disabled alert). Disabled alerts are dimmed; the on/off split is in the
|
|
158
|
+
footer. `name` is the handle action commands take; the raw id is hidden unless `--show-id`.
|
|
159
|
+
Needs `alerts:read`. With `--json`: `{"alerts": [{id, name, created_by, trigger_kind, severity,
|
|
160
|
+
enabled, last_attempted_at, created_at, open_incidents, ...}]}`.
|
|
161
|
+
|
|
162
|
+
Example:
|
|
163
|
+
|
|
164
|
+
* `fp alerts list`
|
|
165
|
+
"""
|
|
166
|
+
state: AppState = ctx.obj
|
|
167
|
+
cctx = require_auth(state)
|
|
168
|
+
alerts = api.list_alerts(cctx)
|
|
169
|
+
if state.json:
|
|
170
|
+
output.emit_json({"alerts": alerts})
|
|
171
|
+
return
|
|
172
|
+
output.render_alerts(alerts, show_id=show_id)
|
|
173
|
+
output.alerts_footer(alerts)
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
def alerts_show(
|
|
177
|
+
ctx: typer.Context,
|
|
178
|
+
name: str = typer.Argument(..., metavar="NAME", help="Alert name (or id)."),
|
|
179
|
+
) -> None:
|
|
180
|
+
"""Show one alert as a stack of parsed cards — identity, trigger, evaluation, channels.
|
|
181
|
+
|
|
182
|
+
Referenced by alert **name** (a UUID-shaped id is also accepted). The `trigger_spec` is parsed
|
|
183
|
+
into a human sentence per `trigger_kind`, and `channels` shows default-vs-custom per channel.
|
|
184
|
+
Not-found → red `✗ no alert named "…"`, exit 6. Needs `alerts:read`. With `--json`: the full
|
|
185
|
+
raw `Alert` (untouched `trigger_spec` + `channels`).
|
|
186
|
+
|
|
187
|
+
Example:
|
|
188
|
+
|
|
189
|
+
* `fp alerts show metric-threshold-alert`
|
|
190
|
+
"""
|
|
191
|
+
state: AppState = ctx.obj
|
|
192
|
+
cctx = require_auth(state)
|
|
193
|
+
alert = _resolve_alert_or_exit(state, api.list_alerts(cctx), name)
|
|
194
|
+
if state.json:
|
|
195
|
+
output.emit_json(alert)
|
|
196
|
+
return
|
|
197
|
+
output.render_alert_show(alert)
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
def _common_overrides(name, description, severity, trigger_kind, eval_interval_secs, min_breaches, eval_window, trigger_spec, channels):
|
|
201
|
+
return dict(
|
|
202
|
+
name=name,
|
|
203
|
+
description=description,
|
|
204
|
+
severity=severity,
|
|
205
|
+
trigger_kind=trigger_kind,
|
|
206
|
+
eval_interval_secs=eval_interval_secs,
|
|
207
|
+
min_breaches=min_breaches,
|
|
208
|
+
eval_window=eval_window,
|
|
209
|
+
trigger_spec=_parse_json_opt(trigger_spec, "--trigger-spec"),
|
|
210
|
+
channels=_parse_json_opt(channels, "--channels"),
|
|
211
|
+
)
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
def alerts_create(
|
|
215
|
+
ctx: typer.Context,
|
|
216
|
+
name: str = typer.Argument(..., metavar="NAME", help="Alert name (unique per org)."),
|
|
217
|
+
file: Optional[str] = typer.Option(None, "--file", help="Full alert JSON (AlertInput) to base it on, or `-` for stdin."),
|
|
218
|
+
description: Optional[str] = typer.Option(None, "--description", help="Description (overrides --file)."),
|
|
219
|
+
severity: Optional[str] = typer.Option(None, "--severity", help=f"Severity: {', '.join(_SEVERITIES)} (overrides --file)."),
|
|
220
|
+
trigger_kind: Optional[str] = typer.Option(None, "--trigger-kind", help=f"What kind of trigger: {', '.join(_TRIGGER_KINDS)} (overrides --file)."),
|
|
221
|
+
trigger_spec: Optional[str] = typer.Option(None, "--trigger-spec", help="Trigger spec as JSON — the condition, shaped per trigger kind (overrides --file)."),
|
|
222
|
+
channels: Optional[str] = typer.Option(None, "--channels", help="Channels as a JSON array, e.g. `[{\"kind\":\"email\"}]` (overrides --file)."),
|
|
223
|
+
eval_interval_secs: Optional[int] = typer.Option(None, "--eval-interval-secs", help="How often to evaluate, in seconds (30–86400)."),
|
|
224
|
+
min_breaches: Optional[int] = typer.Option(None, "--min-breaches", help="Breaches within the window required to fire."),
|
|
225
|
+
eval_window: Optional[int] = typer.Option(None, "--eval-window", help="Evaluation window size (number of intervals)."),
|
|
226
|
+
) -> None:
|
|
227
|
+
"""Create an alert and show it rendered as parsed cards.
|
|
228
|
+
|
|
229
|
+
Give the alert a **name** (positional), then define it inline with the flags or base it on a
|
|
230
|
+
full JSON payload via `--file` (flags layer on top). The core pieces are `--trigger-kind` +
|
|
231
|
+
`--trigger-spec` (the condition) and `--severity`; the rest have sensible server defaults. On
|
|
232
|
+
success the new alert renders the same way `alerts show` does (identity + trigger + evaluation
|
|
233
|
+
+ channels) in a green "alert created" card. Creating isn't destructive, so there's no confirm.
|
|
234
|
+
A name collision is rejected up front. New alerts start **enabled**. Needs `alerts:write`. With
|
|
235
|
+
`--json`: `{id, created_at}`.
|
|
236
|
+
|
|
237
|
+
Examples:
|
|
238
|
+
|
|
239
|
+
* `fp alerts create high-errors --trigger-kind metric_threshold --severity warning \\
|
|
240
|
+
--trigger-spec '{"metric":"error_count","op":">","value":50,"window_secs":900}'`
|
|
241
|
+
* `fp alerts create high-errors --file alert.json` — base it on a saved AlertInput
|
|
242
|
+
* `fp alerts create high-errors --file alert.json --severity critical` — file + an override
|
|
243
|
+
"""
|
|
244
|
+
state: AppState = ctx.obj
|
|
245
|
+
body = _load_file(file)
|
|
246
|
+
_apply_overrides(body, **_common_overrides(name, description, severity, trigger_kind, eval_interval_secs, min_breaches, eval_window, trigger_spec, channels))
|
|
247
|
+
_validate_alert(body, require_core=True)
|
|
248
|
+
cctx = require_auth(state)
|
|
249
|
+
if any(a.name == body.get("name") for a in api.list_alerts(cctx)): # names are the handle → unique
|
|
250
|
+
raise click.UsageError(f'an alert named "{body.get("name")}" already exists')
|
|
251
|
+
result = api.create_alert(cctx, body)
|
|
252
|
+
_write.record_action("alert_created", resource="alert", success=True, trigger_kind=body.get("trigger_kind"))
|
|
253
|
+
if state.json:
|
|
254
|
+
output.emit_json(result)
|
|
255
|
+
return
|
|
256
|
+
# Re-read the canonical stored alert (server defaults applied) for the rendered cards.
|
|
257
|
+
alert = _refetch_alert(cctx, body.get("name")) or _alert_from_body(body)
|
|
258
|
+
output.render_alert_created(alert)
|
|
259
|
+
|
|
260
|
+
|
|
261
|
+
def alerts_update(
|
|
262
|
+
ctx: typer.Context,
|
|
263
|
+
name: str = typer.Argument(..., metavar="NAME", help="Alert name (or id) to update."),
|
|
264
|
+
new_name: Optional[str] = typer.Option(None, "--name", help="Rename the alert."),
|
|
265
|
+
file: Optional[str] = typer.Option(None, "--file", help="Full alert JSON (AlertInput) to replace it with, or `-` for stdin."),
|
|
266
|
+
description: Optional[str] = typer.Option(None, "--description", help="New description."),
|
|
267
|
+
severity: Optional[str] = typer.Option(None, "--severity", help=f"New severity: {', '.join(_SEVERITIES)}."),
|
|
268
|
+
trigger_kind: Optional[str] = typer.Option(None, "--trigger-kind", help=f"New trigger kind: {', '.join(_TRIGGER_KINDS)}."),
|
|
269
|
+
trigger_spec: Optional[str] = typer.Option(None, "--trigger-spec", help="New trigger spec as JSON."),
|
|
270
|
+
channels: Optional[str] = typer.Option(None, "--channels", help="New channels as a JSON array."),
|
|
271
|
+
eval_interval_secs: Optional[int] = typer.Option(None, "--eval-interval-secs", help="New evaluation interval in seconds (30–86400)."),
|
|
272
|
+
min_breaches: Optional[int] = typer.Option(None, "--min-breaches", help="New breaches-to-fire."),
|
|
273
|
+
eval_window: Optional[int] = typer.Option(None, "--eval-window", help="New evaluation window size."),
|
|
274
|
+
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."),
|
|
275
|
+
) -> None:
|
|
276
|
+
"""Update an alert, referenced by **name** (or a UUID-shaped id). Two ways to change it:
|
|
277
|
+
|
|
278
|
+
* **Tweak fields** with the override flags (e.g. `--severity critical`, `--name new-name`).
|
|
279
|
+
Because the server replaces the whole alert on update, the CLI re-sends the current alert
|
|
280
|
+
with your changes applied — so a flag-only edit needs `alerts:read` **and** `alerts:write`.
|
|
281
|
+
* **Replace wholesale** with `--file` (a complete AlertInput), optionally layering a few
|
|
282
|
+
override flags on top.
|
|
283
|
+
|
|
284
|
+
It confirms first (default no; `--yes` skips) and renders the new state as a green "alert
|
|
285
|
+
updated" card. With `--json`: `{id, updated_at}`.
|
|
286
|
+
|
|
287
|
+
Examples:
|
|
288
|
+
|
|
289
|
+
* `fp alerts update high-errors --severity critical` — change one field
|
|
290
|
+
* `fp alerts update high-errors --name critical-errors --yes` — rename, no prompt
|
|
291
|
+
* `fp alerts update high-errors --file alert.json --yes` — replace the whole definition
|
|
292
|
+
"""
|
|
293
|
+
state: AppState = ctx.obj
|
|
294
|
+
overrides = _common_overrides(new_name, description, severity, trigger_kind, eval_interval_secs, min_breaches, eval_window, trigger_spec, channels)
|
|
295
|
+
cctx = require_auth(state)
|
|
296
|
+
existing = api.list_alerts(cctx)
|
|
297
|
+
alert = _resolve_alert_or_exit(state, existing, name)
|
|
298
|
+
if file is not None:
|
|
299
|
+
# An explicit full body is a straight replace (existing behaviour).
|
|
300
|
+
body = _load_file(file)
|
|
301
|
+
_apply_overrides(body, **overrides)
|
|
302
|
+
_validate_alert(body, require_core=False)
|
|
303
|
+
else:
|
|
304
|
+
# Flag-only edit: the server's PUT is a full replace, so seed the body from the resolved
|
|
305
|
+
# alert and overlay just the changed fields (read-merge, like `users update`).
|
|
306
|
+
body = _alert_to_body(alert)
|
|
307
|
+
_apply_overrides(body, **overrides)
|
|
308
|
+
_validate_alert(body, require_core=True)
|
|
309
|
+
final_name = body.get("name") or alert.name
|
|
310
|
+
if final_name != alert.name and any(a.name == final_name and a.id != alert.id for a in existing):
|
|
311
|
+
raise click.UsageError(f'an alert named "{final_name}" already exists')
|
|
312
|
+
if _write.should_prompt(state, yes):
|
|
313
|
+
if not output.confirm_alert_update(alert.name):
|
|
314
|
+
if state.json:
|
|
315
|
+
output.emit_json({"cancelled": True})
|
|
316
|
+
else:
|
|
317
|
+
output.print_cancelled("nothing changed")
|
|
318
|
+
return
|
|
319
|
+
result = api.update_alert(cctx, alert.id, body)
|
|
320
|
+
_write.record_action("alert_updated", resource="alert", success=True)
|
|
321
|
+
if state.json:
|
|
322
|
+
output.emit_json(result)
|
|
323
|
+
return
|
|
324
|
+
final_name = body.get("name") or alert.name
|
|
325
|
+
updated = _refetch_alert(cctx, final_name) or _alert_from_body(body, base=alert)
|
|
326
|
+
output.render_alert_updated(updated, old_name=alert.name)
|
|
327
|
+
|
|
328
|
+
|
|
329
|
+
def alerts_delete(
|
|
330
|
+
ctx: typer.Context,
|
|
331
|
+
name: str = typer.Argument(..., metavar="NAME", help="Alert name (or id) to delete."),
|
|
332
|
+
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."),
|
|
333
|
+
) -> None:
|
|
334
|
+
"""Delete an alert, referenced by **name** (or a UUID-shaped id). This cannot be undone.
|
|
335
|
+
|
|
336
|
+
Shows an amber preview of the alert (incl. its open-incident count, which the delete orphans)
|
|
337
|
+
then confirms. Needs `alerts:write`. With `--json`: `{"deleted": true, "id", "name"}` (or
|
|
338
|
+
`{"cancelled": true}` on a declined prompt).
|
|
339
|
+
|
|
340
|
+
Example:
|
|
341
|
+
|
|
342
|
+
* `fp alerts delete old-test-alert`
|
|
343
|
+
"""
|
|
344
|
+
state: AppState = ctx.obj
|
|
345
|
+
cctx = require_auth(state)
|
|
346
|
+
alert = _resolve_alert_or_exit(state, api.list_alerts(cctx), name)
|
|
347
|
+
if _write.should_prompt(state, yes):
|
|
348
|
+
output.render_alert_delete_preview(alert) # amber preview of what's about to go
|
|
349
|
+
if not output.confirm_alert_delete(alert.open_incidents):
|
|
350
|
+
if state.json:
|
|
351
|
+
output.emit_json({"cancelled": True})
|
|
352
|
+
else:
|
|
353
|
+
output.print_cancelled("nothing deleted")
|
|
354
|
+
return
|
|
355
|
+
api.delete_alert(cctx, alert.id)
|
|
356
|
+
_write.record_action("alert_deleted", resource="alert", success=True, destructive=True)
|
|
357
|
+
if state.json:
|
|
358
|
+
output.emit_json({"deleted": True, "id": alert.id, "name": alert.name})
|
|
359
|
+
else:
|
|
360
|
+
output.alert_deleted(alert.name)
|
|
361
|
+
|
|
362
|
+
|
|
363
|
+
def _test_channel_kinds(alert: Alert, override: Any) -> list:
|
|
364
|
+
"""The channel kinds a test will dispatch to: the ``--channels`` override if given, else the
|
|
365
|
+
alert's saved channels, else (empty) the default set (slack/webhook/email)."""
|
|
366
|
+
chans = override if override is not None else (alert.channels or [])
|
|
367
|
+
if not chans:
|
|
368
|
+
return ["slack", "webhook", "email"]
|
|
369
|
+
kinds: list = []
|
|
370
|
+
for c in chans:
|
|
371
|
+
if isinstance(c, dict):
|
|
372
|
+
k = c.get("kind")
|
|
373
|
+
if k and k not in kinds:
|
|
374
|
+
kinds.append(k)
|
|
375
|
+
return kinds
|
|
376
|
+
|
|
377
|
+
|
|
378
|
+
def alerts_test(
|
|
379
|
+
ctx: typer.Context,
|
|
380
|
+
name: str = typer.Argument(..., metavar="NAME", help="Alert name (or id) to test."),
|
|
381
|
+
channels: Optional[str] = typer.Option(None, "--channels", help="Channels as a JSON array (else uses the alert's saved channels)."),
|
|
382
|
+
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."),
|
|
383
|
+
) -> None:
|
|
384
|
+
"""Fire a test notification for an alert — really sends to its email/Slack/webhook channels.
|
|
385
|
+
|
|
386
|
+
Referenced by alert **name** (a UUID-shaped id is also accepted). Confirms first (it delivers
|
|
387
|
+
real notifications; `--yes` skips), then reports which channels it dispatched to. Note: the
|
|
388
|
+
server reports success as soon as it dispatches — actual delivery isn't confirmed. Needs
|
|
389
|
+
`alerts:write`. With `--json`: `{ok, synthetic_incident_id}` (or `{cancelled: true}`).
|
|
390
|
+
|
|
391
|
+
Example:
|
|
392
|
+
|
|
393
|
+
* `fp alerts test metric-threshold-alert`
|
|
394
|
+
"""
|
|
395
|
+
state: AppState = ctx.obj
|
|
396
|
+
cctx = require_auth(state)
|
|
397
|
+
alert = _resolve_alert_or_exit(state, api.list_alerts(cctx), name)
|
|
398
|
+
override = _parse_json_opt(channels, "--channels")
|
|
399
|
+
if _write.should_prompt(state, yes):
|
|
400
|
+
if not output.confirm_alert_test(alert.name):
|
|
401
|
+
if state.json:
|
|
402
|
+
output.emit_json({"cancelled": True})
|
|
403
|
+
else:
|
|
404
|
+
output.print_cancelled("nothing sent")
|
|
405
|
+
return
|
|
406
|
+
result = api.test_alert(cctx, alert.id, override)
|
|
407
|
+
_write.record_action("alert_tested", resource="alert", success=True)
|
|
408
|
+
if state.json:
|
|
409
|
+
output.emit_json(result)
|
|
410
|
+
return
|
|
411
|
+
output.alert_test_sent(alert.name, _test_channel_kinds(alert, override))
|
|
412
|
+
|
|
413
|
+
|
|
414
|
+
_ALERTS_GROUP_HELP = """Manage alert definitions — list, inspect, create, edit, delete, and test-fire them.
|
|
415
|
+
|
|
416
|
+
Alerts are referenced by **name** (unique per org). Each is a **trigger** (a condition shaped per
|
|
417
|
+
`--trigger-kind`) + an evaluation cadence + notification **channels** (email / Slack / webhook).
|
|
418
|
+
|
|
419
|
+
**Subcommands:** `list` · `show` · `create` · `update` · `delete` · `test`
|
|
420
|
+
|
|
421
|
+
**Examples:**
|
|
422
|
+
|
|
423
|
+
* `fp alerts list` — all alerts, newest first
|
|
424
|
+
* `fp alerts show high-errors` — one alert's trigger / evaluation / channels
|
|
425
|
+
* `fp alerts create high-errors --trigger-kind metric_threshold --severity warning --trigger-spec '{"metric":"error_count","op":">","value":50,"window_secs":900}'`
|
|
426
|
+
* `fp alerts update high-errors --severity critical` — change a field
|
|
427
|
+
* `fp alerts test high-errors` — fire a sample notification to its channels
|
|
428
|
+
* `fp alerts delete high-errors` — remove it
|
|
429
|
+
"""
|
|
430
|
+
|
|
431
|
+
|
|
432
|
+
def register(app: typer.Typer) -> None:
|
|
433
|
+
alerts_app = typer.Typer(
|
|
434
|
+
no_args_is_help=True,
|
|
435
|
+
rich_markup_mode="markdown",
|
|
436
|
+
context_settings={"help_option_names": ["-h", "--help"]},
|
|
437
|
+
help=_ALERTS_GROUP_HELP,
|
|
438
|
+
)
|
|
439
|
+
alerts_app.command("list", epilog=GLOBALS_EPILOG)(alerts_list)
|
|
440
|
+
alerts_app.command("show", epilog=GLOBALS_EPILOG)(alerts_show)
|
|
441
|
+
alerts_app.command("create", epilog=GLOBALS_EPILOG)(alerts_create)
|
|
442
|
+
alerts_app.command("update", epilog=GLOBALS_EPILOG)(alerts_update)
|
|
443
|
+
alerts_app.command("delete", epilog=GLOBALS_EPILOG)(alerts_delete)
|
|
444
|
+
alerts_app.command("test", epilog=GLOBALS_EPILOG)(alerts_test)
|
|
445
|
+
app.add_typer(alerts_app, name="alerts")
|