failproofai 1.0.1 → 1.0.2-beta.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.next/standalone/.next/BUILD_ID +1 -1
- package/.next/standalone/.next/build-manifest.json +3 -3
- package/.next/standalone/.next/prerender-manifest.json +6 -6
- package/.next/standalone/.next/required-server-files.json +1 -1
- package/.next/standalone/.next/server/app/_global-error/page/server-reference-manifest.json +1 -1
- package/.next/standalone/.next/server/app/_global-error/page.js +2 -2
- package/.next/standalone/.next/server/app/_global-error/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/_global-error.html +1 -1
- package/.next/standalone/.next/server/app/_global-error.rsc +7 -7
- package/.next/standalone/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +6 -6
- package/.next/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +7 -7
- package/.next/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/_not-found/page/server-reference-manifest.json +1 -1
- package/.next/standalone/.next/server/app/_not-found/page.js +2 -2
- package/.next/standalone/.next/server/app/_not-found/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/_not-found.html +1 -1
- package/.next/standalone/.next/server/app/_not-found.rsc +15 -15
- package/.next/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +15 -15
- package/.next/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +14 -14
- package/.next/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/api/audit/invite/route.js +3 -2
- package/.next/standalone/.next/server/app/api/audit/invite/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/audit/run/route.js +5 -4
- package/.next/standalone/.next/server/app/api/audit/run/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/audit/status/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/auth/login-request/route.js +2 -2
- package/.next/standalone/.next/server/app/api/auth/login-request/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/auth/login-verify/route.js +4 -3
- package/.next/standalone/.next/server/app/api/auth/login-verify/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/auth/logout/route.js +4 -4
- package/.next/standalone/.next/server/app/api/auth/logout/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/auth/status/route.js +4 -4
- package/.next/standalone/.next/server/app/api/auth/status/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/download/[project]/[session]/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/audit/page/server-reference-manifest.json +2 -2
- package/.next/standalone/.next/server/app/audit/page.js +2 -2
- package/.next/standalone/.next/server/app/audit/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/audit/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/index.html +1 -1
- package/.next/standalone/.next/server/app/index.rsc +15 -15
- package/.next/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +14 -14
- package/.next/standalone/.next/server/app/index.segments/_full.segment.rsc +15 -15
- package/.next/standalone/.next/server/app/index.segments/_tree.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/page/server-reference-manifest.json +1 -1
- package/.next/standalone/.next/server/app/page.js +2 -2
- package/.next/standalone/.next/server/app/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/policies/page/server-reference-manifest.json +66 -22
- package/.next/standalone/.next/server/app/policies/page.js +5 -4
- package/.next/standalone/.next/server/app/policies/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/policies/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/project/[name]/page/server-reference-manifest.json +1 -1
- package/.next/standalone/.next/server/app/project/[name]/page.js +2 -2
- package/.next/standalone/.next/server/app/project/[name]/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/project/[name]/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page/react-loadable-manifest.json +2 -2
- package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page/server-reference-manifest.json +2 -2
- package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page.js +2 -2
- package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/projects/page/server-reference-manifest.json +1 -1
- package/.next/standalone/.next/server/app/projects/page.js +2 -2
- package/.next/standalone/.next/server/app/projects/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/projects/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/settings/page/server-reference-manifest.json +4 -4
- package/.next/standalone/.next/server/app/settings/page.js +1 -1
- package/.next/standalone/.next/server/app/settings/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/settings/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/chunks/{[externals]__1qzqcio._.js → [externals]__0e32-7c._.js} +1 -1
- package/.next/standalone/.next/server/chunks/[externals]__1s462yl._.js +3 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__01eqny5._.js +3 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0_0xu5z._.js +1 -1
- package/.next/standalone/.next/server/chunks/{[root-of-the-server]__1p8poxd._.js → [root-of-the-server]__0igxt32._.js} +2 -2
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0neqlu-._.js +3 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0o07qi9._.js +3 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0t9oqnn._.js +3 -0
- package/.next/standalone/.next/server/chunks/_0bs-v8p._.js +3 -0
- package/.next/standalone/.next/server/chunks/_0tovk6q._.js +3 -0
- package/.next/standalone/.next/server/chunks/_0trp3yc._.js +3 -0
- package/.next/standalone/.next/server/chunks/_185gyk9._.js +3 -0
- package/.next/standalone/.next/server/chunks/_1ek68ln._.js +5 -6
- package/.next/standalone/.next/server/chunks/lib_telemetry-id_ts_1k8gj9o._.js +3 -0
- package/.next/standalone/.next/server/chunks/{node_modules_posthog-node_dist_entrypoints_index_node_mjs_09d5c79._.js → node_modules_posthog-node_dist_entrypoints_index_node_mjs_04op_ou._.js} +1 -1
- package/.next/standalone/.next/server/chunks/{node_modules_posthog-node_dist_entrypoints_index_node_mjs_1d2g73o._.js → node_modules_posthog-node_dist_entrypoints_index_node_mjs_0dr20uz._.js} +1 -1
- package/.next/standalone/.next/server/chunks/package_json_[json]_cjs_1nxcc4v._.js +1 -1
- package/.next/standalone/.next/server/chunks/src_hooks_0xbw3bu._.js +12 -0
- package/.next/standalone/.next/server/chunks/src_hooks_fp-home_ts_09kv0bn._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__013jr2b._.js +4 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__01wy8d-._.js +4 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__02npjtd._.js +4 -0
- package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__0spkm68._.js → [root-of-the-server]__07gm3zl._.js} +21 -20
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0da85px._.js +4 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0ftmoxc._.js +4 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0l4748x._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0oa1lav._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0p-5p8u._.js +4 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0s740oi._.js +6 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__192nhvh._.js +5 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1p2otjt._.js +4 -0
- package/.next/standalone/.next/server/chunks/ssr/_08x1r5t._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/_0bwu160._.js +23 -0
- package/.next/standalone/.next/server/chunks/ssr/{_0u5zauv._.js → _0ns6k3a._.js} +1 -1
- package/.next/standalone/.next/server/chunks/ssr/_1u8-lu2._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/_1zopuov._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/app_audit__components_audit-dashboard_tsx_0p9ud47._.js +13 -13
- package/.next/standalone/.next/server/chunks/ssr/app_global-error_tsx_1kp6l3x._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/app_policies_hooks-client_tsx_19dqvpc._.js +2 -2
- package/.next/standalone/.next/server/chunks/ssr/app_settings_settings-client_tsx_20lq-mq._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/src_hooks_08o7bqo._.js +10 -0
- package/.next/standalone/.next/server/chunks/ssr/src_hooks_builtin-policies_ts_09j2ndl._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/src_hooks_fp-home_ts_0je3xkv._.js +1 -1
- package/.next/standalone/.next/server/middleware-build-manifest.js +3 -3
- package/.next/standalone/.next/server/middleware.js +1 -1
- package/.next/standalone/.next/server/pages/404.html +1 -1
- package/.next/standalone/.next/server/pages/500.html +1 -1
- package/.next/standalone/.next/server/server-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/server-reference-manifest.json +71 -27
- package/.next/standalone/.next/static/chunks/{3c808qrfcq0im.js → 01u1cf8js2yd6.js} +1 -1
- package/.next/standalone/.next/static/chunks/{0k5050ezjvv49.js → 0izgu7nqh2zp-.js} +1 -1
- package/.next/standalone/.next/static/chunks/0u4-org_e7zde.js +1 -0
- package/.next/standalone/.next/static/chunks/1mfzsja6ftim5.css +2 -0
- package/.next/standalone/.next/static/chunks/{2zpp1we2jv6tb.js → 1tynnrrh711lr.js} +1 -1
- package/.next/standalone/.next/static/chunks/{1ide1wl2f7zi1.js → 22rqvlr7p_1mv.js} +13 -13
- package/.next/standalone/.next/static/chunks/{31s1smp-g7gks.js → 2whxqyskpirbk.js} +1 -1
- package/.next/standalone/.next/static/chunks/31mwk8k0q3_3g.js +6 -0
- package/.next/standalone/.next/static/chunks/3ms6gxfm82t6s.js +1 -0
- package/.next/standalone/.next/static/chunks/{37fcpc36x9gpe.js → 3tbbs_d8xrvyp.js} +1 -1
- package/.next/standalone/.next/static/chunks/43nlc236oo898.js +1 -0
- package/.next/standalone/SECURITY.md +5 -4
- package/.next/standalone/app/actions/get-hooks-config.ts +108 -17
- package/.next/standalone/app/actions/pack-actions.ts +163 -0
- package/.next/standalone/app/audit/_components/run-progress.tsx +1 -1
- package/.next/standalone/app/audit/_components/share-templates.ts +13 -13
- package/.next/standalone/app/policies/hooks-client.tsx +320 -15
- package/.next/standalone/components/navbar.tsx +2 -28
- package/.next/standalone/components/reach-developers.tsx +8 -1
- package/.next/standalone/docs-old/agenteye/python-sdk-skill.mdx +1 -1
- package/.next/standalone/docs-old/agenteye/python-sdk.mdx +59 -35
- package/.next/standalone/docs-old/i18n/README.ar.md +1 -0
- package/.next/standalone/docs-old/i18n/README.de.md +1 -0
- package/.next/standalone/docs-old/i18n/README.es.md +1 -0
- package/.next/standalone/docs-old/i18n/README.fr.md +1 -0
- package/.next/standalone/docs-old/i18n/README.he.md +1 -0
- package/.next/standalone/docs-old/i18n/README.hi.md +1 -0
- package/.next/standalone/docs-old/i18n/README.it.md +1 -0
- package/.next/standalone/docs-old/i18n/README.ja.md +1 -0
- package/.next/standalone/docs-old/i18n/README.ko.md +1 -0
- package/.next/standalone/docs-old/i18n/README.pt-br.md +1 -0
- package/.next/standalone/docs-old/i18n/README.ru.md +1 -0
- package/.next/standalone/docs-old/i18n/README.tr.md +1 -0
- package/.next/standalone/docs-old/i18n/README.vi.md +1 -0
- package/.next/standalone/docs-old/i18n/README.zh.md +1 -0
- package/.next/standalone/fp-cloud-cli/CHANGELOG.md +431 -0
- package/.next/standalone/fp-cloud-cli/LICENSE +42 -0
- package/.next/standalone/fp-cloud-cli/MANIFEST.in +10 -0
- package/.next/standalone/fp-cloud-cli/README.md +259 -0
- package/.next/standalone/fp-cloud-cli/fp_cli/__init__.py +10 -0
- package/.next/standalone/fp-cloud-cli/fp_cli/__main__.py +4 -0
- package/.next/standalone/fp-cloud-cli/fp_cli/_click_compat.py +64 -0
- package/.next/standalone/fp-cloud-cli/fp_cli/_context.py +332 -0
- package/.next/standalone/fp-cloud-cli/fp_cli/_version.py +1 -0
- package/.next/standalone/fp-cloud-cli/fp_cli/analytics.py +432 -0
- package/.next/standalone/fp-cloud-cli/fp_cli/analytics_config.py +77 -0
- package/.next/standalone/fp-cloud-cli/fp_cli/analytics_registry.py +83 -0
- package/.next/standalone/fp-cloud-cli/fp_cli/app.py +492 -0
- package/.next/standalone/fp-cloud-cli/fp_cli/auth.py +160 -0
- package/.next/standalone/fp-cloud-cli/fp_cli/client.py +1694 -0
- package/.next/standalone/fp-cloud-cli/fp_cli/commands/__init__.py +0 -0
- package/.next/standalone/fp-cloud-cli/fp_cli/commands/_write.py +214 -0
- package/.next/standalone/fp-cloud-cli/fp_cli/commands/agent_cmds.py +407 -0
- package/.next/standalone/fp-cloud-cli/fp_cli/commands/alerts_cmds.py +445 -0
- package/.next/standalone/fp-cloud-cli/fp_cli/commands/audits_cmds.py +1054 -0
- package/.next/standalone/fp-cloud-cli/fp_cli/commands/auth_cmds.py +512 -0
- package/.next/standalone/fp-cloud-cli/fp_cli/commands/errors_cmds.py +190 -0
- package/.next/standalone/fp-cloud-cli/fp_cli/commands/evals_cmds.py +161 -0
- package/.next/standalone/fp-cloud-cli/fp_cli/commands/events_cmds.py +159 -0
- package/.next/standalone/fp-cloud-cli/fp_cli/commands/fleet_cmds.py +416 -0
- package/.next/standalone/fp-cloud-cli/fp_cli/commands/guardrails_cmds.py +148 -0
- package/.next/standalone/fp-cloud-cli/fp_cli/commands/incidents_cmds.py +472 -0
- package/.next/standalone/fp-cloud-cli/fp_cli/commands/keys_cmds.py +407 -0
- package/.next/standalone/fp-cloud-cli/fp_cli/commands/list_cmds.py +63 -0
- package/.next/standalone/fp-cloud-cli/fp_cli/commands/orgs_cmds.py +319 -0
- package/.next/standalone/fp-cloud-cli/fp_cli/commands/policies_cmds.py +499 -0
- package/.next/standalone/fp-cloud-cli/fp_cli/commands/queries_cmds.py +378 -0
- package/.next/standalone/fp-cloud-cli/fp_cli/commands/sessions_cmds.py +151 -0
- package/.next/standalone/fp-cloud-cli/fp_cli/commands/settings_cmds.py +150 -0
- package/.next/standalone/fp-cloud-cli/fp_cli/commands/usage_cmds.py +35 -0
- package/.next/standalone/fp-cloud-cli/fp_cli/commands/users_cmds.py +404 -0
- package/.next/standalone/fp-cloud-cli/fp_cli/config.py +330 -0
- package/.next/standalone/fp-cloud-cli/fp_cli/dates.py +78 -0
- package/.next/standalone/fp-cloud-cli/fp_cli/enforcement.py +345 -0
- package/.next/standalone/fp-cloud-cli/fp_cli/errors.py +98 -0
- package/.next/standalone/fp-cloud-cli/fp_cli/models.py +891 -0
- package/.next/standalone/fp-cloud-cli/fp_cli/orgs.py +30 -0
- package/.next/standalone/fp-cloud-cli/fp_cli/output.py +6593 -0
- package/.next/standalone/fp-cloud-cli/fp_cli/permissions.py +208 -0
- package/.next/standalone/fp-cloud-cli/fp_cli/policy_check.py +290 -0
- package/.next/standalone/fp-cloud-cli/fp_cli/py.typed +0 -0
- package/.next/standalone/fp-cloud-cli/fp_cli/select.py +322 -0
- package/.next/standalone/fp-cloud-cli/fp_cli/theme.py +53 -0
- package/.next/standalone/fp-cloud-cli/pyproject.toml +106 -0
- package/.next/standalone/fp-cloud-cli/skill/SKILL.md +302 -0
- package/.next/standalone/fp-cloud-cli/skill/agents/openai.yaml +8 -0
- package/.next/standalone/fp-cloud-cli/skill/references/commands.md +254 -0
- package/.next/standalone/fp-cloud-cli/tests/__init__.py +0 -0
- package/.next/standalone/fp-cloud-cli/tests/conftest.py +92 -0
- package/.next/standalone/fp-cloud-cli/tests/test_alerting.py +644 -0
- package/.next/standalone/fp-cloud-cli/tests/test_analytics.py +385 -0
- package/.next/standalone/fp-cloud-cli/tests/test_audits.py +950 -0
- package/.next/standalone/fp-cloud-cli/tests/test_auth.py +98 -0
- package/.next/standalone/fp-cloud-cli/tests/test_auth_mode.py +272 -0
- package/.next/standalone/fp-cloud-cli/tests/test_click_compat.py +118 -0
- package/.next/standalone/fp-cloud-cli/tests/test_client.py +299 -0
- package/.next/standalone/fp-cloud-cli/tests/test_commands.py +814 -0
- package/.next/standalone/fp-cloud-cli/tests/test_config.py +99 -0
- package/.next/standalone/fp-cloud-cli/tests/test_dashboards_agent.py +340 -0
- package/.next/standalone/fp-cloud-cli/tests/test_dates.py +76 -0
- package/.next/standalone/fp-cloud-cli/tests/test_enforcement_logic.py +481 -0
- package/.next/standalone/fp-cloud-cli/tests/test_facets.py +170 -0
- package/.next/standalone/fp-cloud-cli/tests/test_failproofai_home.py +722 -0
- package/.next/standalone/fp-cloud-cli/tests/test_fp_home_contract.py +198 -0
- package/.next/standalone/fp-cloud-cli/tests/test_hardening.py +186 -0
- package/.next/standalone/fp-cloud-cli/tests/test_help_table_coverage.py +127 -0
- package/.next/standalone/fp-cloud-cli/tests/test_keys_queries.py +496 -0
- package/.next/standalone/fp-cloud-cli/tests/test_list.py +59 -0
- package/.next/standalone/fp-cloud-cli/tests/test_multivalue.py +183 -0
- package/.next/standalone/fp-cloud-cli/tests/test_no_customer_identifiers.py +168 -0
- package/.next/standalone/fp-cloud-cli/tests/test_operator.py +492 -0
- package/.next/standalone/fp-cloud-cli/tests/test_orgs.py +692 -0
- package/.next/standalone/fp-cloud-cli/tests/test_output.py +1732 -0
- package/.next/standalone/fp-cloud-cli/tests/test_policy_check.py +191 -0
- package/.next/standalone/fp-cloud-cli/tests/test_readme_matches_reality.py +125 -0
- package/.next/standalone/fp-cloud-cli/tests/test_review_fixes.py +161 -0
- package/.next/standalone/fp-cloud-cli/tests/test_telemetry_completeness.py +90 -0
- package/.next/standalone/fp-cloud-cli/tests/test_usage.py +115 -0
- package/.next/standalone/fp-cloud-cli/tests/test_v1_origin_diagnostic.py +70 -0
- package/.next/standalone/fp-cloud-cli/tests/test_v1_routing.py +214 -0
- package/.next/standalone/fp-cloud-cli/tests/test_whoami.py +104 -0
- package/.next/standalone/fp-cloud-cli/uv.lock +552 -0
- package/.next/standalone/package.json +27 -21
- package/.next/standalone/sdk/python/CHANGELOG.md +70 -0
- package/.next/standalone/sdk/python/LICENSE +42 -0
- package/.next/standalone/sdk/python/MANIFEST.in +21 -0
- package/.next/standalone/sdk/python/README.md +466 -0
- package/.next/standalone/sdk/python/docs/README.md +249 -0
- package/.next/standalone/sdk/python/docs/_shared/README.md +43 -0
- package/.next/standalone/sdk/python/docs/_shared/__init__.py +195 -0
- package/.next/standalone/sdk/python/docs/crewai/README.md +328 -0
- package/.next/standalone/sdk/python/docs/crewai/examples/quickstart.py +61 -0
- package/.next/standalone/sdk/python/docs/crewai/examples/research_crew.py +92 -0
- package/.next/standalone/sdk/python/docs/langgraph/README.md +481 -0
- package/.next/standalone/sdk/python/docs/langgraph/examples/quickstart.py +51 -0
- package/.next/standalone/sdk/python/docs/langgraph/examples/research_agent.py +86 -0
- package/.next/standalone/sdk/python/docs/langgraph/examples/supervisor_handoff.py +83 -0
- package/.next/standalone/sdk/python/docs/llama_index/README.md +332 -0
- package/.next/standalone/sdk/python/docs/llama_index/examples/quickstart.py +62 -0
- package/.next/standalone/sdk/python/docs/llama_index/examples/research_agent.py +93 -0
- package/.next/standalone/sdk/python/docs/manual/README.md +491 -0
- package/.next/standalone/sdk/python/docs/manual/examples/quickstart.py +57 -0
- package/.next/standalone/sdk/python/docs/manual/examples/research_agent.py +133 -0
- package/.next/standalone/sdk/python/docs/pydantic_ai/README.md +323 -0
- package/.next/standalone/sdk/python/docs/pydantic_ai/examples/quickstart.py +48 -0
- package/.next/standalone/sdk/python/docs/pydantic_ai/examples/research_agent.py +94 -0
- package/.next/standalone/sdk/python/failproofai_sdk/__init__.py +131 -0
- package/.next/standalone/sdk/python/failproofai_sdk/_context.py +218 -0
- package/.next/standalone/sdk/python/failproofai_sdk/_environment.py +78 -0
- package/.next/standalone/sdk/python/failproofai_sdk/_events.py +752 -0
- package/.next/standalone/sdk/python/failproofai_sdk/_resolver.py +118 -0
- package/.next/standalone/sdk/python/failproofai_sdk/_runtime.py +21 -0
- package/.next/standalone/sdk/python/failproofai_sdk/_schema.py +317 -0
- package/.next/standalone/sdk/python/failproofai_sdk/_scopes.py +422 -0
- package/.next/standalone/sdk/python/failproofai_sdk/_version.py +1 -0
- package/.next/standalone/sdk/python/failproofai_sdk/_writer.py +750 -0
- package/.next/standalone/sdk/python/failproofai_sdk/integrations/__init__.py +274 -0
- package/.next/standalone/sdk/python/failproofai_sdk/integrations/_compat.py +247 -0
- package/.next/standalone/sdk/python/failproofai_sdk/integrations/_core.py +1076 -0
- package/.next/standalone/sdk/python/failproofai_sdk/integrations/crewai.py +1669 -0
- package/.next/standalone/sdk/python/failproofai_sdk/integrations/langchain.py +2151 -0
- package/.next/standalone/sdk/python/failproofai_sdk/integrations/llama_index.py +1588 -0
- package/.next/standalone/sdk/python/failproofai_sdk/integrations/pydantic_ai.py +977 -0
- package/.next/standalone/sdk/python/failproofai_sdk/py.typed +0 -0
- package/.next/standalone/sdk/python/pyproject.toml +92 -0
- package/.next/standalone/sdk/python/skill/SKILL.md +399 -0
- package/.next/standalone/sdk/python/skill/agents/openai.yaml +8 -0
- package/.next/standalone/sdk/python/skill/references/events.md +234 -0
- package/.next/standalone/sdk/python/skill/references/frameworks.md +314 -0
- package/.next/standalone/sdk/python/skill/references/install.md +100 -0
- package/.next/standalone/sdk/python/skill/references/integration.md +352 -0
- package/.next/standalone/sdk/python/tests/__init__.py +0 -0
- package/.next/standalone/sdk/python/tests/conftest.py +169 -0
- package/.next/standalone/sdk/python/tests/integrations/__init__.py +6 -0
- package/.next/standalone/sdk/python/tests/integrations/test_crewai.py +1299 -0
- package/.next/standalone/sdk/python/tests/integrations/test_langchain.py +2338 -0
- package/.next/standalone/sdk/python/tests/integrations/test_llama_index.py +1576 -0
- package/.next/standalone/sdk/python/tests/integrations/test_pydantic_ai.py +1102 -0
- package/.next/standalone/sdk/python/tests/test_context.py +410 -0
- package/.next/standalone/sdk/python/tests/test_docs.py +411 -0
- package/.next/standalone/sdk/python/tests/test_durability.py +1352 -0
- package/.next/standalone/sdk/python/tests/test_encoding.py +571 -0
- package/.next/standalone/sdk/python/tests/test_integrations.py +1127 -0
- package/.next/standalone/sdk/python/tests/test_no_customer_identifiers.py +214 -0
- package/.next/standalone/sdk/python/tests/test_packaging.py +185 -0
- package/.next/standalone/sdk/python/tests/test_resolver_umbrella.py +178 -0
- package/.next/standalone/sdk/python/tests/test_scopes.py +487 -0
- package/.next/standalone/sdk/python/tests/test_sdk.py +923 -0
- package/.next/standalone/sdk/python/tests/test_server_contract.py +650 -0
- package/.next/standalone/sdk/python/tests/test_site_docs.py +371 -0
- package/.next/standalone/sdk/python/tests/test_skill_snippets.py +165 -0
- package/.next/standalone/sdk/python/tests/test_spool_contract.py +287 -0
- package/.next/standalone/sdk/python/tests/test_spool_creation.py +360 -0
- package/.next/standalone/sdk/python/tests/test_wire_format.py +279 -0
- package/.next/standalone/sdk/python/tests/test_zero_dependencies.py +378 -0
- package/.next/standalone/sdk/python/uv.lock +6005 -0
- package/.next/standalone/server.js +1 -1
- package/README.md +77 -21
- package/bin/failproofai.mjs +579 -306
- package/dist/cli.mjs +6499 -3662
- package/dist/index.js +5 -5
- package/dist/worker.mjs +2390 -1978
- package/package.json +27 -21
- package/scripts/build-policy-pack.mjs +121 -0
- package/scripts/changelog-section.py +77 -0
- package/scripts/prune-standalone.mjs +3 -0
- package/scripts/python-version.py +232 -0
- package/scripts/translate-docs/cli.ts +21 -0
- package/scripts/translate-docs/mintlify-nav.ts +85 -13
- package/scripts/translate-docs/translator.ts +66 -11
- package/src/audit/cache.ts +40 -3
- package/src/audit/cli.ts +1 -1
- package/src/audit/index.ts +1 -1
- package/src/audit/replay.ts +89 -3
- package/src/audit/report.ts +5 -5
- package/src/audit/schedule-cli.ts +49 -53
- package/src/hooks/builtin-policies.ts +217 -579
- package/src/hooks/cloud-enrollment-cli.ts +66 -27
- package/src/hooks/cloud-managed-policies.ts +6 -1
- package/src/hooks/configure-wizard.ts +56 -369
- package/src/hooks/custom-hooks-loader.ts +89 -6
- package/src/hooks/fp-home.ts +92 -4
- package/src/hooks/fp-reset.ts +34 -2
- package/src/hooks/handler.ts +181 -11
- package/src/hooks/harness-cli.ts +79 -25
- package/src/hooks/hook-activity-store.ts +13 -3
- package/src/hooks/install-prompt.ts +25 -3
- package/src/hooks/manager.ts +424 -172
- package/src/hooks/migrations.ts +2 -2
- package/src/hooks/pack-cli.ts +885 -0
- package/src/hooks/pack-failclosed.ts +189 -0
- package/src/hooks/pack-manifest.ts +352 -0
- package/src/hooks/pack-store.ts +891 -0
- package/src/hooks/policy-catalog.ts +535 -0
- package/src/hooks/policy-evaluator.ts +24 -16
- package/src/hooks/policy-registry.ts +8 -2
- package/src/hooks/policy-types.ts +26 -0
- package/src/hooks/session-pause-cli.ts +33 -9
- package/src/hooks/tui.ts +695 -39
- package/src/hooks/types.ts +15 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0_usx0j._.js +0 -3
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0_zk340._.js +0 -3
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0agdw2a._.js +0 -3
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0d1u_na._.js +0 -3
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0oy4kxb._.js +0 -3
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0zf034w._.js +0 -3
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__1tvn4wf._.js +0 -3
- package/.next/standalone/.next/server/chunks/_0lxbzdq._.js +0 -3
- package/.next/standalone/.next/server/chunks/_1ger0a2._.js +0 -3
- package/.next/standalone/.next/server/chunks/_1zuiiy3._.js +0 -3
- package/.next/standalone/.next/server/chunks/node_modules_posthog-node_dist_entrypoints_index_node_mjs_1iiwm5e._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__07tbbf3._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0kc8_v5._.js +0 -4
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0kgy9da._.js +0 -4
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0mhqk5a._.js +0 -4
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0pltpn8._.js +0 -4
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0qkcyf-._.js +0 -4
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0w0x_ok._.js +0 -4
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1_dfocn._.js +0 -4
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1bfex62._.js +0 -5
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1kzapg5._.js +0 -6
- package/.next/standalone/.next/server/chunks/ssr/_0lvyuv4._.js +0 -23
- package/.next/standalone/.next/static/chunks/2_2lhuojq2xop.js +0 -1
- package/.next/standalone/.next/static/chunks/36fw6yk1s6x_f.js +0 -1
- package/.next/standalone/.next/static/chunks/3k7mxbp9hiom0.js +0 -1
- package/.next/standalone/.next/static/chunks/3tzq0260n17ae.js +0 -6
- package/.next/standalone/.next/static/chunks/3wq9x0c-7ld5d.css +0 -2
- package/src/hooks/policy-presets.ts +0 -135
- /package/.next/standalone/.next/static/{ZklXxyA5ygsWSQz2u_zii → I5kEb9kSgYS63j4iqfVLO}/_buildManifest.js +0 -0
- /package/.next/standalone/.next/static/{ZklXxyA5ygsWSQz2u_zii → I5kEb9kSgYS63j4iqfVLO}/_clientMiddlewareManifest.js +0 -0
- /package/.next/standalone/.next/static/{ZklXxyA5ygsWSQz2u_zii → I5kEb9kSgYS63j4iqfVLO}/_ssgManifest.js +0 -0
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
"""usage — show the active organization's current billing-window usage."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import typer
|
|
6
|
+
|
|
7
|
+
from .. import client as api
|
|
8
|
+
from .. import output
|
|
9
|
+
from .._context import GLOBALS_EPILOG, AppState, require_auth
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def usage(ctx: typer.Context) -> None:
|
|
13
|
+
"""Show usage for the active org's current fixed 30-day metering window.
|
|
14
|
+
|
|
15
|
+
Reports telemetry, evaluations, workspace objects, audits, API keys, and members. This is
|
|
16
|
+
read-only and does not apply or display limits. Needs `usage:read`.
|
|
17
|
+
|
|
18
|
+
With `--json`, returns the dashboard response unchanged: `org_id`, `billing_anchor`,
|
|
19
|
+
`window`, `usage`, `calculated_at`, and `stale_after`.
|
|
20
|
+
|
|
21
|
+
Examples:
|
|
22
|
+
|
|
23
|
+
* `fp usage`
|
|
24
|
+
* `fp --json usage | jq '.usage.events_ingested'`
|
|
25
|
+
"""
|
|
26
|
+
state: AppState = ctx.obj
|
|
27
|
+
data = api.get_usage(require_auth(state))
|
|
28
|
+
if state.json:
|
|
29
|
+
output.emit_json(data)
|
|
30
|
+
return
|
|
31
|
+
output.render_usage(data)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def register(app: typer.Typer) -> None:
|
|
35
|
+
app.command("usage", epilog=GLOBALS_EPILOG)(usage)
|
|
@@ -0,0 +1,404 @@
|
|
|
1
|
+
"""Org member management: users list / show / create / update / disable / enable.
|
|
2
|
+
|
|
3
|
+
Members are referenced by their **email** (unique within the org) — not a raw id — across
|
|
4
|
+
``show`` / ``update`` / ``disable`` / ``enable`` (a UUID-shaped handle is also accepted). The
|
|
5
|
+
whole group converges on the shared boxed visual language: a boxed member list, identity cards,
|
|
6
|
+
the shared grouped permissions panel, and the shared confirm/cancel helpers. Effective grants are
|
|
7
|
+
``(set ∪ added) − removed``; ``--add`` / ``--remove`` use the compact ``slug:act.act`` token
|
|
8
|
+
format (the same parser as ``keys``). Protected members can't be edited/disabled.
|
|
9
|
+
|
|
10
|
+
A member's role comes from a **permission set** that lives **per org on the server** (managed in
|
|
11
|
+
the dashboard): the built-in ``read-only`` / ``standard`` / ``admin`` plus any custom sets the org
|
|
12
|
+
defines. The CLI hardcodes only those three built-ins — and only to *preview* the resulting grants
|
|
13
|
+
locally; the ``--permission-set`` value is resolved + expanded server-side, so custom org sets work
|
|
14
|
+
too (but a custom set can't be previewed, so ``update`` confirms it generically).
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
from __future__ import annotations
|
|
18
|
+
|
|
19
|
+
from typing import List, Optional
|
|
20
|
+
|
|
21
|
+
import typer
|
|
22
|
+
|
|
23
|
+
from .. import _click_compat as click # the Click Typer is running; see _click_compat
|
|
24
|
+
from .. import client as api
|
|
25
|
+
from .. import output, permissions, theme
|
|
26
|
+
from .._context import GLOBALS_EPILOG, AppState, require_auth
|
|
27
|
+
from ..errors import ForbiddenError
|
|
28
|
+
from . import _write
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def _resolve_user_or_exit(state: AppState, users, handle: str):
|
|
32
|
+
"""Resolve a member by **email** (primary), falling back to an exact id match, raising a
|
|
33
|
+
typed error the central chokepoint renders (JSON envelope under ``--json``, red box
|
|
34
|
+
otherwise): none → exit 6, several → exit 2."""
|
|
35
|
+
return _write.resolve_one(
|
|
36
|
+
users,
|
|
37
|
+
handle,
|
|
38
|
+
kind="user",
|
|
39
|
+
ref="with email",
|
|
40
|
+
key="email",
|
|
41
|
+
list_cmd="users list",
|
|
42
|
+
# The server lowercases on create, so the address the caller typed is not the address
|
|
43
|
+
# stored. Without this, `fp users create Alice.Chen@Example.com` succeeds and every
|
|
44
|
+
# subsequent show/update/disable/enable on that same string reports "no user with
|
|
45
|
+
# email" — the member is reachable only via a lowercased form nothing told them about.
|
|
46
|
+
casefold=True,
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def _parse_user_tokens_or_exit(state: AppState, tokens) -> List[str]:
|
|
51
|
+
"""Expand the compact ``slug:act1.act2`` ``--add`` / ``--remove`` tokens into a flat
|
|
52
|
+
assignable permission list (empty when no value was given). A malformed/unknown token → a clean
|
|
53
|
+
usage error (exit 2), before any read/write. Space-separated tokens (quoted) and repeated flags
|
|
54
|
+
both compose — e.g. ``--add "keys:create.regenerate users:create.update"`` or
|
|
55
|
+
``--add keys:create.regenerate --add users:create.update``."""
|
|
56
|
+
if not tokens:
|
|
57
|
+
return []
|
|
58
|
+
try:
|
|
59
|
+
return permissions.parse_permission_tokens(tokens)
|
|
60
|
+
except permissions.PermissionTokenError as exc:
|
|
61
|
+
raise click.UsageError(str(exc))
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def users_list(
|
|
65
|
+
ctx: typer.Context,
|
|
66
|
+
active_only: bool = typer.Option(False, "--active-only", help="Show only enabled members (hide disabled ones)."),
|
|
67
|
+
show_id: bool = typer.Option(False, "--show-id", help="Prepend a short user-id column (the full id is always in --json)."),
|
|
68
|
+
) -> None:
|
|
69
|
+
"""List org members in a boxed table — active members first, disabled ones dimmed at the bottom.
|
|
70
|
+
|
|
71
|
+
Shows `email · access · permissions · joined · status` with a leading 🔒 marker on protected
|
|
72
|
+
members; status is derived from the disable state (`● active` / `○ disabled`), and `joined`
|
|
73
|
+
comes from each member's join date. The raw id is hidden by default (use `--show-id` for a
|
|
74
|
+
short id, or `--json` for the full id). Needs `users:read`. With `--json`:
|
|
75
|
+
`{"users": [{id, email, permissions, permission_set, disabled_at, is_protected, created_at, …}]}`.
|
|
76
|
+
|
|
77
|
+
Examples:
|
|
78
|
+
|
|
79
|
+
* `fp users list`
|
|
80
|
+
* `fp users list --active-only`
|
|
81
|
+
"""
|
|
82
|
+
state: AppState = ctx.obj
|
|
83
|
+
cctx = require_auth(state)
|
|
84
|
+
users = api.list_users(cctx)
|
|
85
|
+
if active_only:
|
|
86
|
+
users = [u for u in users if not u.disabled_at]
|
|
87
|
+
users = sorted(users, key=lambda u: 1 if u.disabled_at else 0) # active first, disabled last
|
|
88
|
+
if state.json:
|
|
89
|
+
output.emit_json({"users": users})
|
|
90
|
+
return
|
|
91
|
+
output.render_users(users, show_id=show_id)
|
|
92
|
+
output.users_footer(users)
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def users_show(
|
|
96
|
+
ctx: typer.Context,
|
|
97
|
+
email: str = typer.Argument(..., metavar="EMAIL", help="User email (or id)."),
|
|
98
|
+
) -> None:
|
|
99
|
+
"""Show one member's identity and full effective permissions — like `whoami` for someone else.
|
|
100
|
+
|
|
101
|
+
Renders an identity card (email, role, status) then the shared grouped permissions panel with
|
|
102
|
+
**all** the member's grants (no truncation). Referenced by **email** (or a UUID-shaped id).
|
|
103
|
+
Needs `users:read`. With `--json`: the full member object.
|
|
104
|
+
|
|
105
|
+
Example:
|
|
106
|
+
|
|
107
|
+
* `fp users show dev@example.com`
|
|
108
|
+
"""
|
|
109
|
+
state: AppState = ctx.obj
|
|
110
|
+
cctx = require_auth(state)
|
|
111
|
+
user = _resolve_user_or_exit(state, api.list_users(cctx), email)
|
|
112
|
+
if state.json:
|
|
113
|
+
output.emit_json(user)
|
|
114
|
+
return
|
|
115
|
+
output.render_user_show(user)
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def _validate_permission_set_or_exit(state: AppState, cctx, set_name: Optional[str]) -> None:
|
|
119
|
+
"""Reject an unknown ``--permission-set`` client-side (clean exit 2) instead of a raw server
|
|
120
|
+
422 with a request-id. Valid = the org's sets ∪ the built-in presets (the server expands it).
|
|
121
|
+
Mirrors how ``keys create`` validates its set."""
|
|
122
|
+
presets = set(permissions.PRESETS) - {"clear"} # `clear` is an internal preset, not assignable
|
|
123
|
+
if not set_name or set_name in presets:
|
|
124
|
+
return # no set, or a built-in preset (always valid) → no extra round-trip
|
|
125
|
+
valid = set(api.list_permission_sets(cctx)) | presets
|
|
126
|
+
if set_name not in valid:
|
|
127
|
+
raise click.UsageError(
|
|
128
|
+
f'unknown permission set "{set_name}". available: {", ".join(sorted(valid))}'
|
|
129
|
+
)
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
def users_create(
|
|
133
|
+
ctx: typer.Context,
|
|
134
|
+
email: str = typer.Argument(..., metavar="EMAIL", help="Email of the member to invite (unique within the org)."),
|
|
135
|
+
permission_set: Optional[str] = typer.Option(None, "--permission-set", help="Role to start from — a permission set: `read-only`, `standard`, `admin`, or a custom set your org defines in the dashboard. Omit for no base role."),
|
|
136
|
+
add: Optional[List[str]] = typer.Option(None, "--add", help="Grant extra permissions on top of the set, as `slug:action.action` tokens (dotted actions expand: `keys:create.regenerate` → `keys:create`, `keys:regenerate`). Several via comma, repeated flag, or a quoted group: `--add keys:create,users:read` · `--add a --add b` · `--add \"a b\"`."),
|
|
137
|
+
remove: Optional[List[str]] = typer.Option(None, "--remove", help="Revoke permissions from the set, same `slug:action.action` token format as --add (comma / repeated / quoted)."),
|
|
138
|
+
) -> None:
|
|
139
|
+
"""Invite an org member and show their new identity + permissions.
|
|
140
|
+
|
|
141
|
+
Give the member's **email** (positional). Their role comes from `--permission-set` (a built-in
|
|
142
|
+
`read-only` / `standard` / `admin`, or a custom set your org defines in the dashboard), and you
|
|
143
|
+
can fine-tune it per member with `--add` / `--remove`. The effective grants are
|
|
144
|
+
`(set ∪ added) − removed`, expanded server-side; the permissions box shows the result.
|
|
145
|
+
|
|
146
|
+
`--add` / `--remove` take the compact `slug:action.action` token format — dotted actions expand
|
|
147
|
+
(`keys:create.regenerate` → two grants) and several compose by comma, repeated flag, or a quoted
|
|
148
|
+
group. Needs `users:create`. With `--json`: `{id, email, permission_set, permissions}`
|
|
149
|
+
(`permissions` = the expanded set).
|
|
150
|
+
|
|
151
|
+
Examples:
|
|
152
|
+
|
|
153
|
+
* `fp users create dev@example.com --permission-set standard` — invite with the standard role
|
|
154
|
+
* `fp users create ci@example.com --permission-set read-only --add keys:create.regenerate` — read-only plus key management
|
|
155
|
+
* `fp users create lead@example.com --permission-set admin --remove settings:write` — admin minus settings writes
|
|
156
|
+
"""
|
|
157
|
+
state: AppState = ctx.obj
|
|
158
|
+
parsed_add = _parse_user_tokens_or_exit(state, add)
|
|
159
|
+
parsed_remove = _parse_user_tokens_or_exit(state, remove)
|
|
160
|
+
cctx = require_auth(state)
|
|
161
|
+
_validate_permission_set_or_exit(state, cctx, permission_set)
|
|
162
|
+
# Casefolded, matching `_write.resolve_one`. The server lowercases an email
|
|
163
|
+
# on create, so the address the caller typed is not the address stored —
|
|
164
|
+
# 6372ecf2 fixed the RESOLVER for that and left this guard an exact
|
|
165
|
+
# comparison, which made the group internally inconsistent about case: `fp
|
|
166
|
+
# users show Alice.Chen@Example.com` resolved the member, while `fp users
|
|
167
|
+
# create Alice.Chen@Example.com` failed to see them, sent the POST, and
|
|
168
|
+
# surfaced the server's 409 as exit 1. The skill's own guidance branches on
|
|
169
|
+
# the code — exit 2 means "report it and ask", exit 1 means "unexpected
|
|
170
|
+
# server error" — so an agent took the wrong branch for the same logical
|
|
171
|
+
# outcome, decided purely by the capitalisation a human typed.
|
|
172
|
+
if any((u.email or "").casefold() == email.casefold() for u in api.list_users(cctx)):
|
|
173
|
+
raise click.UsageError(f'a user with email "{email}" already exists')
|
|
174
|
+
user = api.create_user(
|
|
175
|
+
cctx, email=email, permission_set=permission_set,
|
|
176
|
+
permission_added=parsed_add or None, permission_removed=parsed_remove or None,
|
|
177
|
+
)
|
|
178
|
+
_write.record_action("user_created", resource="user", success=True, permission_count=len(user.permissions))
|
|
179
|
+
if state.json:
|
|
180
|
+
output.emit_json({"id": user.id, "email": user.email,
|
|
181
|
+
"permission_set": user.permission_set, "permissions": user.permissions})
|
|
182
|
+
return
|
|
183
|
+
output.render_user_created(user)
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
def users_update(
|
|
187
|
+
ctx: typer.Context,
|
|
188
|
+
email: str = typer.Argument(..., metavar="EMAIL", help="Email (or id) of the member to update."),
|
|
189
|
+
permission_set: Optional[str] = typer.Option(None, "--permission-set", help="Reassign the member's role — a permission set: `read-only`, `standard`, `admin`, or a custom set your org defines in the dashboard. Replaces their per-member overrides (apply fresh ones with --add/--remove)."),
|
|
190
|
+
add: Optional[List[str]] = typer.Option(None, "--add", help="Grant permissions, same `slug:action.action` token format as `users create` (dotted actions expand; comma / repeated flag / quoted group compose). Incremental — merged into the member's CURRENT grants, unless --permission-set is also given."),
|
|
191
|
+
remove: Optional[List[str]] = typer.Option(None, "--remove", help="Revoke permissions, same `slug:action.action` token format as --add. Incremental — applied to the member's CURRENT grants, unless --permission-set is also given."),
|
|
192
|
+
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."),
|
|
193
|
+
) -> None:
|
|
194
|
+
"""Change a member's permissions and show the diff. Referenced by **email** (or a UUID id).
|
|
195
|
+
|
|
196
|
+
Two ways to use it (combine if you like):
|
|
197
|
+
|
|
198
|
+
* **Reassign a role** — `--permission-set <set>` swaps the member's base role (a built-in
|
|
199
|
+
`read-only` / `standard` / `admin`, or a custom org set); add `--add`/`--remove` to layer
|
|
200
|
+
fresh per-member overrides on top.
|
|
201
|
+
* **Tweak incrementally** — `--add` / `--remove` **alone** adjust the member's *current* grants
|
|
202
|
+
(their set is kept). `--add` grants, `--remove` revokes; both take the compact
|
|
203
|
+
`slug:action.action` token format (dotted actions expand; comma / repeated / quoted compose).
|
|
204
|
+
|
|
205
|
+
The CLI reads the current grants, computes the resulting set `(set ∪ added) − removed`, and
|
|
206
|
+
shows a git-style diff — added green, removed struck-through, unchanged dim. It confirms first
|
|
207
|
+
(default no; `--yes` skips) and a no-op exits without calling the server. Needs `users:update`
|
|
208
|
+
(+ `users:read`). With `--json`: `{id, email, permission_set, permissions, added, removed}`.
|
|
209
|
+
|
|
210
|
+
Examples:
|
|
211
|
+
|
|
212
|
+
* `fp users update dev@example.com --add keys:create.regenerate` — grant on top of current grants
|
|
213
|
+
* `fp users update dev@example.com --remove alerts:read,incidents:ack` — revoke a couple of grants
|
|
214
|
+
* `fp users update dev@example.com --add events:read --remove keys:delete` — add and remove at once
|
|
215
|
+
* `fp users update dev@example.com --permission-set admin --yes` — reassign the role (no prompt)
|
|
216
|
+
"""
|
|
217
|
+
state: AppState = ctx.obj
|
|
218
|
+
if permission_set is None and not add and not remove:
|
|
219
|
+
raise typer.BadParameter("nothing to update — pass --permission-set, --add, and/or --remove.")
|
|
220
|
+
parsed_add = _parse_user_tokens_or_exit(state, add)
|
|
221
|
+
parsed_remove = _parse_user_tokens_or_exit(state, remove)
|
|
222
|
+
both = sorted(set(parsed_add) & set(parsed_remove))
|
|
223
|
+
if both:
|
|
224
|
+
raise typer.BadParameter(f"{', '.join(both)} given to both --add and --remove.")
|
|
225
|
+
cctx = require_auth(state)
|
|
226
|
+
_validate_permission_set_or_exit(state, cctx, permission_set)
|
|
227
|
+
user = _resolve_user_or_exit(state, api.list_users(cctx), email)
|
|
228
|
+
before = set(user.permissions)
|
|
229
|
+
add_set, remove_set = set(parsed_add), set(parsed_remove)
|
|
230
|
+
|
|
231
|
+
# Build the body to send + predict the resulting effective set (for the confirm preview).
|
|
232
|
+
if permission_set is None:
|
|
233
|
+
# Incremental: merge the override deltas into the member's CURRENT overrides (keep the set).
|
|
234
|
+
new_set = user.permission_set
|
|
235
|
+
merged_add = (set(user.permission_added or []) | add_set) - remove_set
|
|
236
|
+
merged_remove = (set(user.permission_removed or []) | remove_set) - add_set
|
|
237
|
+
new_added, new_removed = sorted(merged_add), sorted(merged_remove)
|
|
238
|
+
# Applying the deltas to the current effective set is exact (base is unchanged).
|
|
239
|
+
predicted_after = (before | add_set) - remove_set
|
|
240
|
+
else:
|
|
241
|
+
# Assign a role: the set is the base; the flags are its fresh overrides.
|
|
242
|
+
new_set, new_added, new_removed = permission_set, sorted(add_set), sorted(remove_set)
|
|
243
|
+
if permission_set in permissions.PRESETS:
|
|
244
|
+
predicted_after = (set(permissions.PRESETS[permission_set]) | add_set) - remove_set
|
|
245
|
+
else:
|
|
246
|
+
predicted_after = None # a custom set we can't expand locally → confirm generically
|
|
247
|
+
|
|
248
|
+
if predicted_after is not None:
|
|
249
|
+
p_added = predicted_after - before
|
|
250
|
+
p_removed = before - predicted_after
|
|
251
|
+
if not p_added and not p_removed: # no-op: don't call the server, don't prompt
|
|
252
|
+
if state.json:
|
|
253
|
+
output.emit_json({"id": user.id, "email": user.email, "permission_set": new_set,
|
|
254
|
+
"permissions": sorted(before), "added": [], "removed": []})
|
|
255
|
+
else:
|
|
256
|
+
output.user_no_change()
|
|
257
|
+
return
|
|
258
|
+
proceed = (not _write.should_prompt(state, yes)) or output.confirm_user_update(
|
|
259
|
+
user.email, len(p_added), len(p_removed))
|
|
260
|
+
else:
|
|
261
|
+
proceed = _write.confirm_action(
|
|
262
|
+
state, "change permissions for", user.email,
|
|
263
|
+
consequence="this reassigns their role; the user keeps access", assume_yes=yes)
|
|
264
|
+
|
|
265
|
+
if not proceed:
|
|
266
|
+
if state.json:
|
|
267
|
+
output.emit_json({"cancelled": True})
|
|
268
|
+
else:
|
|
269
|
+
output.print_cancelled("permissions unchanged")
|
|
270
|
+
return
|
|
271
|
+
|
|
272
|
+
result = api.update_user(
|
|
273
|
+
cctx, user.id, permission_set=new_set, permission_added=new_added, permission_removed=new_removed)
|
|
274
|
+
after = set(result.permissions)
|
|
275
|
+
added, removed = sorted(after - before), sorted(before - after)
|
|
276
|
+
_write.record_action("user_updated", resource="user", success=True, permission_count=len(after))
|
|
277
|
+
if state.json:
|
|
278
|
+
output.emit_json({"id": result.id, "email": result.email, "permission_set": result.permission_set,
|
|
279
|
+
"permissions": sorted(after), "added": added, "removed": removed})
|
|
280
|
+
return
|
|
281
|
+
if not added and not removed: # custom-set assign that turned out to be a no-op
|
|
282
|
+
output.user_no_change()
|
|
283
|
+
return
|
|
284
|
+
output.render_user_updated(result, added=added, removed=removed, union=sorted(before | after))
|
|
285
|
+
|
|
286
|
+
|
|
287
|
+
def users_disable(
|
|
288
|
+
ctx: typer.Context,
|
|
289
|
+
email: str = typer.Argument(..., metavar="EMAIL", help="User email (or id) to disable."),
|
|
290
|
+
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."),
|
|
291
|
+
) -> None:
|
|
292
|
+
"""Disable a member by email — they can no longer sign in (reversible with `users enable`).
|
|
293
|
+
|
|
294
|
+
Confirms first (amber, default no; `--yes` skips). Refuses a protected member or your own
|
|
295
|
+
account. Already-disabled is a calm no-op. Needs `users:delete`. With `--json`:
|
|
296
|
+
`{id, email, status: "disabled"}` (or `{cancelled: true}` on a declined prompt).
|
|
297
|
+
|
|
298
|
+
Example:
|
|
299
|
+
|
|
300
|
+
* `fp users disable dev@example.com`
|
|
301
|
+
"""
|
|
302
|
+
state: AppState = ctx.obj
|
|
303
|
+
cctx = require_auth(state)
|
|
304
|
+
user = _resolve_user_or_exit(state, api.list_users(cctx), email)
|
|
305
|
+
if user.is_protected:
|
|
306
|
+
raise ForbiddenError(f'"{user.email}" is protected and can\'t be disabled')
|
|
307
|
+
if state.config.email and user.email == state.config.email:
|
|
308
|
+
raise ForbiddenError("you can't disable your own account")
|
|
309
|
+
if user.disabled_at: # already disabled — a no-op, not an error
|
|
310
|
+
if state.json:
|
|
311
|
+
output.emit_json({"id": user.id, "email": user.email, "status": "disabled"})
|
|
312
|
+
else:
|
|
313
|
+
output.user_already_disabled(user.email)
|
|
314
|
+
return
|
|
315
|
+
if not _write.confirm_action(
|
|
316
|
+
state, "disable user", user.email,
|
|
317
|
+
consequence="they can no longer sign in — you can re-enable them later", assume_yes=yes,
|
|
318
|
+
):
|
|
319
|
+
if state.json:
|
|
320
|
+
output.emit_json({"cancelled": True})
|
|
321
|
+
else:
|
|
322
|
+
output.print_cancelled("nothing changed")
|
|
323
|
+
return
|
|
324
|
+
api.disable_user(cctx, user.id)
|
|
325
|
+
_write.record_action("user_disabled", resource="user", success=True, destructive=True)
|
|
326
|
+
if state.json:
|
|
327
|
+
output.emit_json({"id": user.id, "email": user.email, "status": "disabled"})
|
|
328
|
+
else:
|
|
329
|
+
output.user_disabled(user.email)
|
|
330
|
+
|
|
331
|
+
|
|
332
|
+
def users_enable(
|
|
333
|
+
ctx: typer.Context,
|
|
334
|
+
email: str = typer.Argument(..., metavar="EMAIL", help="User email (or id) to re-enable."),
|
|
335
|
+
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."),
|
|
336
|
+
) -> None:
|
|
337
|
+
"""Re-enable a disabled member by email — they can sign in again.
|
|
338
|
+
|
|
339
|
+
Confirms first (a calm re-activation prompt, default no; `--yes` skips). Already-active is a
|
|
340
|
+
calm no-op. Needs `users:delete`. With `--json`: `{id, email, status: "active"}` (or
|
|
341
|
+
`{cancelled: true}` on a declined prompt).
|
|
342
|
+
|
|
343
|
+
Example:
|
|
344
|
+
|
|
345
|
+
* `fp users enable dev@example.com`
|
|
346
|
+
"""
|
|
347
|
+
state: AppState = ctx.obj
|
|
348
|
+
cctx = require_auth(state)
|
|
349
|
+
user = _resolve_user_or_exit(state, api.list_users(cctx), email)
|
|
350
|
+
if not user.disabled_at: # already active — a no-op, not an error
|
|
351
|
+
if state.json:
|
|
352
|
+
output.emit_json({"id": user.id, "email": user.email, "status": "active"})
|
|
353
|
+
else:
|
|
354
|
+
output.user_already_active(user.email)
|
|
355
|
+
return
|
|
356
|
+
if not _write.confirm_action(
|
|
357
|
+
state, "re-enable user", user.email,
|
|
358
|
+
consequence="they'll be able to sign in again", assume_yes=yes,
|
|
359
|
+
glyph="↑", color=theme.ACCENT,
|
|
360
|
+
):
|
|
361
|
+
if state.json:
|
|
362
|
+
output.emit_json({"cancelled": True})
|
|
363
|
+
else:
|
|
364
|
+
output.print_cancelled("nothing changed")
|
|
365
|
+
return
|
|
366
|
+
result = api.enable_user(cctx, user.id)
|
|
367
|
+
_write.record_action("user_enabled", resource="user", success=True)
|
|
368
|
+
if state.json:
|
|
369
|
+
output.emit_json({"id": result.id, "email": result.email, "status": "active"})
|
|
370
|
+
else:
|
|
371
|
+
output.user_enabled(user.email)
|
|
372
|
+
|
|
373
|
+
|
|
374
|
+
_USERS_GROUP_HELP = """Manage org members — list, inspect, invite, and adjust their permissions.
|
|
375
|
+
|
|
376
|
+
Members are referenced by **email** (unique in the org). A member's grants are a permission
|
|
377
|
+
set (role) plus optional per-member `--add` / `--remove` overrides.
|
|
378
|
+
|
|
379
|
+
**Subcommands:** `list` · `show` · `create` · `update` · `disable` · `enable`
|
|
380
|
+
|
|
381
|
+
**Examples:**
|
|
382
|
+
|
|
383
|
+
* `fp users list --active-only` — current members, active first
|
|
384
|
+
* `fp users show dev@example.com` — one member's identity + full grants
|
|
385
|
+
* `fp users create dev@example.com --permission-set standard` — invite with a role
|
|
386
|
+
* `fp users update dev@example.com --add keys:create --remove alerts:read` — tweak grants
|
|
387
|
+
* `fp users disable dev@example.com` / `fp users enable dev@example.com` — revoke / restore sign-in
|
|
388
|
+
"""
|
|
389
|
+
|
|
390
|
+
|
|
391
|
+
def register(app: typer.Typer) -> None:
|
|
392
|
+
users_app = typer.Typer(
|
|
393
|
+
no_args_is_help=True,
|
|
394
|
+
rich_markup_mode="markdown",
|
|
395
|
+
context_settings={"help_option_names": ["-h", "--help"]},
|
|
396
|
+
help=_USERS_GROUP_HELP,
|
|
397
|
+
)
|
|
398
|
+
users_app.command("list", epilog=GLOBALS_EPILOG)(users_list)
|
|
399
|
+
users_app.command("show", epilog=GLOBALS_EPILOG)(users_show)
|
|
400
|
+
users_app.command("create", epilog=GLOBALS_EPILOG)(users_create)
|
|
401
|
+
users_app.command("update", epilog=GLOBALS_EPILOG)(users_update)
|
|
402
|
+
users_app.command("disable", epilog=GLOBALS_EPILOG)(users_disable)
|
|
403
|
+
users_app.command("enable", epilog=GLOBALS_EPILOG)(users_enable)
|
|
404
|
+
app.add_typer(users_app, name="users")
|