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,512 @@
|
|
|
1
|
+
"""login / logout / whoami."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from dataclasses import asdict
|
|
6
|
+
from typing import List, Optional, Tuple
|
|
7
|
+
|
|
8
|
+
import typer
|
|
9
|
+
|
|
10
|
+
from .. import _click_compat as click # the Click Typer is running; see _click_compat
|
|
11
|
+
from .. import analytics
|
|
12
|
+
from .. import auth
|
|
13
|
+
from .. import config as cfgmod
|
|
14
|
+
from .. import orgs as orgsmod
|
|
15
|
+
from .. import output
|
|
16
|
+
from .. import select as selectmod
|
|
17
|
+
from .._context import (
|
|
18
|
+
GLOBALS_EPILOG,
|
|
19
|
+
AppState,
|
|
20
|
+
AuthMode,
|
|
21
|
+
build_context,
|
|
22
|
+
deny_in_key_mode,
|
|
23
|
+
resolved_base_url,
|
|
24
|
+
)
|
|
25
|
+
from ..client import ClientContext, get_session_user, org_is_accessible
|
|
26
|
+
from ..errors import ApiError, AuthError
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def _resolve_login_org(
|
|
30
|
+
state: AppState,
|
|
31
|
+
requested: Optional[str],
|
|
32
|
+
slugs: List[str],
|
|
33
|
+
is_admin: bool,
|
|
34
|
+
saved: Optional[str] = None,
|
|
35
|
+
probe_ctx: Optional[ClientContext] = None,
|
|
36
|
+
discovery_failed: bool = False,
|
|
37
|
+
) -> Tuple[Optional[str], bool]:
|
|
38
|
+
"""Pick the active org at login. Returns ``(slug_or_None, needs_selection)``.
|
|
39
|
+
|
|
40
|
+
``requested`` is the **explicit** tenant only (login ``--org`` / global
|
|
41
|
+
``--org`` / ``FP_ORG``) — NOT a previously-saved tenant. ``saved`` is the
|
|
42
|
+
last-used tenant from the config; it never bypasses the picker, it is only the
|
|
43
|
+
interactive default (Enter-to-keep) and the non-interactive fallback.
|
|
44
|
+
|
|
45
|
+
Selection rules for a multi-org user with no explicit ``--org``:
|
|
46
|
+
* interactive TTY → always show the picker (defaulting to ``saved``), so the
|
|
47
|
+
user re-chooses every login rather than silently re-entering a stale tenant.
|
|
48
|
+
* non-interactive (``--json`` / piped stdin) → reuse a still-valid ``saved``
|
|
49
|
+
tenant if present, else return ``needs_selection`` so the caller persists
|
|
50
|
+
the token, lists the orgs and exits non-zero for a script to handle.
|
|
51
|
+
"""
|
|
52
|
+
if requested:
|
|
53
|
+
if not orgsmod.is_valid_org_slug(requested):
|
|
54
|
+
raise click.BadParameter(
|
|
55
|
+
f"'{requested}' is not a valid org slug.", param_hint="--org"
|
|
56
|
+
)
|
|
57
|
+
# Fast path: an org you're a member of is always fine (no server round-trip).
|
|
58
|
+
if requested in slugs:
|
|
59
|
+
return requested, False
|
|
60
|
+
# Not a membership — but only say so if we actually LOOKED. When the
|
|
61
|
+
# membership fetch failed, `slugs` is empty because nothing answered,
|
|
62
|
+
# not because the user belongs to nothing, and "You are not a member of
|
|
63
|
+
# org 'X'. Your orgs: (none)." is then a confident lie that exits 2 on a
|
|
64
|
+
# tenant they may well have. Fall through to the server probe instead,
|
|
65
|
+
# which is the only thing that can still give a real answer.
|
|
66
|
+
if not is_admin and not discovery_failed:
|
|
67
|
+
raise click.BadParameter(
|
|
68
|
+
f"You are not a member of org '{requested}'. "
|
|
69
|
+
f"Your orgs: {', '.join(slugs) or '(none)'}.",
|
|
70
|
+
param_hint="--org",
|
|
71
|
+
)
|
|
72
|
+
# Instance admin requesting a non-member org: allow ONLY if it actually
|
|
73
|
+
# EXISTS and is accessible — verified against the server. This closes the
|
|
74
|
+
# old `or is_admin` hole that accepted (and saved) any typo'd/nonexistent
|
|
75
|
+
# slug. Same check covers --org and FP_ORG (both feed `requested`).
|
|
76
|
+
if probe_ctx is not None and org_is_accessible(probe_ctx, requested):
|
|
77
|
+
return requested, False
|
|
78
|
+
if discovery_failed:
|
|
79
|
+
raise click.BadParameter(
|
|
80
|
+
f"Could not verify access to org '{requested}' — reading your "
|
|
81
|
+
"memberships failed, and the direct check did not succeed either. "
|
|
82
|
+
"You are still signed in; retry once the dashboard is reachable.",
|
|
83
|
+
param_hint="--org",
|
|
84
|
+
)
|
|
85
|
+
raise click.BadParameter(
|
|
86
|
+
f"Org '{requested}' does not exist or you do not have access to it.",
|
|
87
|
+
param_hint="--org",
|
|
88
|
+
)
|
|
89
|
+
if len(slugs) == 1:
|
|
90
|
+
return slugs[0], False
|
|
91
|
+
if not slugs:
|
|
92
|
+
return None, False # no memberships (e.g. instance admin) — pick per command with --org
|
|
93
|
+
# Multi-org, no explicit tenant requested.
|
|
94
|
+
if state.json or not selectmod.stdin_is_tty():
|
|
95
|
+
# Can't prompt: reuse a still-valid saved tenant, else ask the caller to choose.
|
|
96
|
+
if saved and saved in slugs:
|
|
97
|
+
return saved, False
|
|
98
|
+
return None, True
|
|
99
|
+
return selectmod.choose_org(slugs, default=saved), False
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def _looks_like_email(value: str) -> bool:
|
|
103
|
+
"""A light shape check for the interactive email step — `x@y.z`, no spaces (the server is the
|
|
104
|
+
real authority; this just catches an obvious typo before sending a code)."""
|
|
105
|
+
s = (value or "").strip()
|
|
106
|
+
return "@" in s and " " not in s and "." in s.rsplit("@", 1)[-1] and len(s) >= 5
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def _login_interactive(state: AppState, base: str, email_opt: Optional[str], org_opt: Optional[str]) -> None:
|
|
110
|
+
"""The single-box interactive login (real TTY only): one redrawn-in-place panel — email → code →
|
|
111
|
+
(org picker) → signed-in. Mirrors the non-interactive orchestration below; only the UI differs.
|
|
112
|
+
Network/auth errors propagate (terminal restored on the way out) to the standard red error box."""
|
|
113
|
+
saved = state.config.org
|
|
114
|
+
persisted = False
|
|
115
|
+
with selectmod.LoginBox() as box:
|
|
116
|
+
try:
|
|
117
|
+
# email
|
|
118
|
+
if email_opt:
|
|
119
|
+
email = email_opt
|
|
120
|
+
box.note("email", email)
|
|
121
|
+
else:
|
|
122
|
+
email = box.text_step("email", validate=_looks_like_email,
|
|
123
|
+
error_msg="that doesn't look like an email")
|
|
124
|
+
# send the code
|
|
125
|
+
box.working("sending a code…")
|
|
126
|
+
auth.request_otp(base, email, timeout=state.timeout, verify=not state.insecure)
|
|
127
|
+
box.note("code sent")
|
|
128
|
+
# code → verify. A wrong/expired code is reported cleanly INSIDE the box (not a raw
|
|
129
|
+
# HTTP 500) with a relogin hint, then we exit — re-run `fp login` for a fresh code.
|
|
130
|
+
code = box.text_step("code", helper="enter the 6-digit code", slots=6, collapse=False)
|
|
131
|
+
box.working("verifying…")
|
|
132
|
+
try:
|
|
133
|
+
token, expires_in, user = auth.verify_otp(
|
|
134
|
+
base, email, code.strip(), timeout=state.timeout, verify=not state.insecure
|
|
135
|
+
)
|
|
136
|
+
except AuthError:
|
|
137
|
+
box.fail("that code didn't match or has expired", "sign in again with fp login")
|
|
138
|
+
raise typer.Exit(code=4)
|
|
139
|
+
except ApiError as exc: # e.g. too many attempts (429)
|
|
140
|
+
box.fail(exc.message, "try fp login again in a bit")
|
|
141
|
+
raise typer.Exit(code=exc.exit_code)
|
|
142
|
+
box.note("code") # collapse to `✓ code` — never echo the value
|
|
143
|
+
# persist first (so we can read authoritative memberships), then resolve the org
|
|
144
|
+
auth.persist_session(state.config, base, token, expires_in, user, insecure=state.insecure)
|
|
145
|
+
persisted = True
|
|
146
|
+
analytics.identify(state.config.user_id)
|
|
147
|
+
sess_ctx = ClientContext(base_url=base, token=token, timeout=state.timeout, verify=not state.insecure)
|
|
148
|
+
slugs: List[str] = []
|
|
149
|
+
is_admin = False
|
|
150
|
+
discovery_failed: Optional[str] = None
|
|
151
|
+
# The token is already persisted, so a discovery failure must NOT
|
|
152
|
+
# undo the login — but it must not be reported as "no memberships"
|
|
153
|
+
# either. A timeout, a 500, a malformed body or a permissions
|
|
154
|
+
# problem all used to collapse into an empty list, which then reads
|
|
155
|
+
# as a single-org user: the active org is silently cleared, a
|
|
156
|
+
# previously chosen tenant is dropped, and an explicit `--org` is
|
|
157
|
+
# rejected as inaccessible when it was never actually checked.
|
|
158
|
+
# Keeping the session and SAYING so is the honest recovery.
|
|
159
|
+
try:
|
|
160
|
+
su = get_session_user(sess_ctx)
|
|
161
|
+
slugs = su.org_slugs
|
|
162
|
+
is_admin = su.is_instance_admin
|
|
163
|
+
except Exception as exc:
|
|
164
|
+
discovery_failed = str(exc) or exc.__class__.__name__
|
|
165
|
+
output.cli_warn(
|
|
166
|
+
"⚠ signed in, but could not read your organisation memberships "
|
|
167
|
+
f"({discovery_failed}). The active org was left unchanged — "
|
|
168
|
+
"run `fp orgs list` once the dashboard is reachable."
|
|
169
|
+
)
|
|
170
|
+
requested = org_opt or state.org_explicit
|
|
171
|
+
if requested:
|
|
172
|
+
chosen, _ = _resolve_login_org(
|
|
173
|
+
state, requested, slugs, is_admin, saved=saved, probe_ctx=sess_ctx,
|
|
174
|
+
discovery_failed=discovery_failed is not None,
|
|
175
|
+
)
|
|
176
|
+
if chosen:
|
|
177
|
+
box.note("org", chosen)
|
|
178
|
+
elif len(slugs) > 1:
|
|
179
|
+
chosen = box.pick(slugs, default=saved if saved in slugs else None)
|
|
180
|
+
else:
|
|
181
|
+
chosen = slugs[0] if slugs else None # single/none → named only in the final block
|
|
182
|
+
state.config.org = chosen
|
|
183
|
+
cfgmod.save_config(state.config)
|
|
184
|
+
analytics.capture("logged_in") # also count the interactive (real-TTY) login path
|
|
185
|
+
who = user.get("email") or email
|
|
186
|
+
box.finish(who, chosen)
|
|
187
|
+
except selectmod.LoginCancelled:
|
|
188
|
+
box.cancel(persisted) # calm close inside the same frame
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
def login(
|
|
192
|
+
ctx: typer.Context,
|
|
193
|
+
email: Optional[str] = typer.Option(None, "--email", "-e", help="Email to send the login code to (prompted if omitted)."),
|
|
194
|
+
org: Optional[str] = typer.Option(None, "--org", help="Org/tenant slug to sign in to — skips the picker. Must be one you can access."),
|
|
195
|
+
force: bool = typer.Option(False, "--force", help="Re-authenticate even if you already have a valid session."),
|
|
196
|
+
) -> None:
|
|
197
|
+
"""Sign in to the dashboard with an emailed one-time code.
|
|
198
|
+
|
|
199
|
+
You enter your email (or pass `--email`), the dashboard emails a 6-digit code, and you paste
|
|
200
|
+
it back. The session is saved to `~/.failproofai/fpcli/cli-auth.json` (mode 0600) and lasts ~24h — just
|
|
201
|
+
re-run `login` when it expires. Already signed in? `login` shows who you are and exits 0
|
|
202
|
+
without prompting; pass `--force` to re-authenticate anyway.
|
|
203
|
+
|
|
204
|
+
The active org is chosen here and saved for later commands. With one org it's picked
|
|
205
|
+
automatically; with several you choose from a list once your email is verified (your last-used
|
|
206
|
+
org is the Enter-to-keep default), or pass `--org <slug>` to skip the picker. Switch it later
|
|
207
|
+
with `fp orgs switch <slug>`.
|
|
208
|
+
|
|
209
|
+
Both prompts are skippable: `--email` / `--org` provide the values up front, otherwise you're
|
|
210
|
+
asked interactively. First time, set the dashboard URL with the global `--base-url` (saved for
|
|
211
|
+
next time); add the global `--insecure` for a self-signed/internal dashboard.
|
|
212
|
+
|
|
213
|
+
With `--json`: `{"logged_in": true, "email": "...", "org": "<slug>", "expires_in_secs": <n>}`.
|
|
214
|
+
Exit `0` on success; `2` if sign-in worked but the org is still unresolved (multi-org, no
|
|
215
|
+
`--org`, non-interactive) — the token is saved, so re-run with `--org <slug>`.
|
|
216
|
+
|
|
217
|
+
Examples:
|
|
218
|
+
|
|
219
|
+
* `fp login` — fully interactive: prompts for your email, then the org picker
|
|
220
|
+
* `fp --base-url https://fp.example.com login` — first time: set the dashboard URL, then sign in interactively
|
|
221
|
+
* `fp login --email you@example.com --org acme` — skip both prompts (email + org up front)
|
|
222
|
+
* `fp --base-url https://dash.internal --insecure login` — self-signed / internal dashboard
|
|
223
|
+
"""
|
|
224
|
+
state: AppState = ctx.obj
|
|
225
|
+
deny_in_key_mode(
|
|
226
|
+
state,
|
|
227
|
+
"login",
|
|
228
|
+
"it signs a human in and saves the session, and an API key already IS the "
|
|
229
|
+
"credential — there is nothing to sign in as",
|
|
230
|
+
)
|
|
231
|
+
# Already signed in with a still-valid session? Don't silently start a second login —
|
|
232
|
+
# but DO honor an explicit selector instead of dropping it: a different `--email` means
|
|
233
|
+
# "sign in as someone else" (fall through to a full re-auth); an explicit `--org` that
|
|
234
|
+
# differs from the active one switches the tenant on the existing session (no re-auth).
|
|
235
|
+
# `--force` overrides everything. Otherwise, just report who you are.
|
|
236
|
+
if not force and not cfgmod.is_expired(state.config):
|
|
237
|
+
requested = org or state.org_explicit
|
|
238
|
+
switching_account = bool(email) and email != state.config.email
|
|
239
|
+
if not switching_account:
|
|
240
|
+
if requested and requested != state.config.org:
|
|
241
|
+
base = resolved_base_url(state)
|
|
242
|
+
sess_ctx = ClientContext(
|
|
243
|
+
base_url=base, token=state.config.session_token,
|
|
244
|
+
timeout=state.timeout, verify=not state.insecure,
|
|
245
|
+
)
|
|
246
|
+
# Read memberships to validate the requested org. Unlike the post-OTP flow (where
|
|
247
|
+
# memberships are best-effort), here a failed read must surface — otherwise an
|
|
248
|
+
# empty membership set would mis-reject a valid org as "not a member". A dead/
|
|
249
|
+
# expired session (AuthError) or outage (NetworkError) propagates to the chokepoint.
|
|
250
|
+
su = get_session_user(sess_ctx)
|
|
251
|
+
slugs, is_admin = su.org_slugs, su.is_instance_admin
|
|
252
|
+
# Validates membership / admin-accessibility (raises a clean usage error on a
|
|
253
|
+
# bad slug) exactly like a fresh login, then persists the new active tenant.
|
|
254
|
+
chosen, _ = _resolve_login_org(
|
|
255
|
+
state, requested, slugs, is_admin, saved=state.config.org, probe_ctx=sess_ctx
|
|
256
|
+
)
|
|
257
|
+
state.config.org = chosen
|
|
258
|
+
cfgmod.save_config(state.config)
|
|
259
|
+
analytics.capture("org_selected") # name-only (no slug value)
|
|
260
|
+
if state.json:
|
|
261
|
+
output.emit_json({
|
|
262
|
+
"logged_in": True, "email": state.config.email, "org": chosen,
|
|
263
|
+
"already_signed_in": True, "switched_org": True,
|
|
264
|
+
})
|
|
265
|
+
else:
|
|
266
|
+
output.signed_in(state.config.email, chosen)
|
|
267
|
+
return
|
|
268
|
+
if state.json:
|
|
269
|
+
output.emit_json(
|
|
270
|
+
{
|
|
271
|
+
"logged_in": True,
|
|
272
|
+
"email": state.config.email,
|
|
273
|
+
"org": state.config.org,
|
|
274
|
+
"already_signed_in": True,
|
|
275
|
+
}
|
|
276
|
+
)
|
|
277
|
+
else:
|
|
278
|
+
output.already_signed_in(state.config.email, state.config.org)
|
|
279
|
+
return
|
|
280
|
+
base = resolved_base_url(state)
|
|
281
|
+
# On a real TTY (not --json / pipes / CI) run the single-box interactive flow; everything
|
|
282
|
+
# else keeps the plain prompt flow below (which the test runner + scripts rely on).
|
|
283
|
+
if not state.json and selectmod.login_box_supported():
|
|
284
|
+
_login_interactive(state, base, email, org)
|
|
285
|
+
return
|
|
286
|
+
output.auth_header()
|
|
287
|
+
if not email:
|
|
288
|
+
email = output.prompt("email")
|
|
289
|
+
auth.request_otp(base, email, timeout=state.timeout, verify=not state.insecure)
|
|
290
|
+
output.code_sent(email)
|
|
291
|
+
code = output.prompt("code")
|
|
292
|
+
token, expires_in, user = auth.verify_otp(
|
|
293
|
+
base, email, str(code).strip(), timeout=state.timeout, verify=not state.insecure
|
|
294
|
+
)
|
|
295
|
+
# Persist the token first so we can query the session for authoritative memberships:
|
|
296
|
+
# the OTP-verify payload is slim (no memberships / is_instance_admin) — only
|
|
297
|
+
# GET /api/auth/session returns them.
|
|
298
|
+
auth.persist_session(state.config, base, token, expires_in, user, insecure=state.insecure)
|
|
299
|
+
analytics.identify(state.config.user_id)
|
|
300
|
+
# One context, reused to read memberships AND to validate an explicit --org.
|
|
301
|
+
sess_ctx = ClientContext(
|
|
302
|
+
base_url=base, token=token, timeout=state.timeout, verify=not state.insecure
|
|
303
|
+
)
|
|
304
|
+
slugs: List[str] = []
|
|
305
|
+
is_admin = False
|
|
306
|
+
discovery_failed: Optional[str] = None
|
|
307
|
+
# Same reasoning as the interactive flow above: keep the session, but never
|
|
308
|
+
# let a discovery failure masquerade as "this user belongs to no orgs".
|
|
309
|
+
try:
|
|
310
|
+
su = get_session_user(sess_ctx)
|
|
311
|
+
slugs = su.org_slugs
|
|
312
|
+
is_admin = su.is_instance_admin
|
|
313
|
+
except Exception as exc:
|
|
314
|
+
discovery_failed = str(exc) or exc.__class__.__name__
|
|
315
|
+
output.cli_warn(
|
|
316
|
+
"⚠ signed in, but could not read your organisation memberships "
|
|
317
|
+
f"({discovery_failed}). The active org was left unchanged — "
|
|
318
|
+
"run `fp orgs list` once the dashboard is reachable."
|
|
319
|
+
)
|
|
320
|
+
# The active tenant at login is an EXPLICIT choice only: the `login --org`
|
|
321
|
+
# flag, the global `--org`, or FP_ORG. A previously *saved* tenant must
|
|
322
|
+
# not silently bypass the picker — a multi-org user re-running `login` is shown
|
|
323
|
+
# their orgs and chooses (the saved one is just the default). `state.org`
|
|
324
|
+
# (flag > env > config) is NOT used here precisely because it folds in config.
|
|
325
|
+
requested = org or state.org_explicit
|
|
326
|
+
saved = state.config.org
|
|
327
|
+
chosen, needs_selection = _resolve_login_org(
|
|
328
|
+
state, requested, slugs, is_admin, saved=saved, probe_ctx=sess_ctx,
|
|
329
|
+
discovery_failed=discovery_failed is not None,
|
|
330
|
+
)
|
|
331
|
+
state.config.org = chosen # persist the active tenant (or clear it if unresolved)
|
|
332
|
+
cfgmod.save_config(state.config)
|
|
333
|
+
who = user.get("email") or email
|
|
334
|
+
|
|
335
|
+
if needs_selection:
|
|
336
|
+
# Multi-org, non-interactive, no --org: token is saved but no tenant is active.
|
|
337
|
+
if state.json:
|
|
338
|
+
output.emit_json(
|
|
339
|
+
{
|
|
340
|
+
"logged_in": True,
|
|
341
|
+
"email": who,
|
|
342
|
+
"org": None,
|
|
343
|
+
"expires_in_secs": expires_in,
|
|
344
|
+
"needs_org_selection": True,
|
|
345
|
+
"orgs": slugs,
|
|
346
|
+
}
|
|
347
|
+
)
|
|
348
|
+
else:
|
|
349
|
+
output.warn(
|
|
350
|
+
"Logged in, but you belong to multiple orgs and none was selected."
|
|
351
|
+
)
|
|
352
|
+
output.info("Your orgs: " + ", ".join(slugs))
|
|
353
|
+
output.hint("Re-run with --org <slug> (e.g. `fp login --org "
|
|
354
|
+
f"{slugs[0]}`) or `fp orgs switch <slug>`.")
|
|
355
|
+
raise typer.Exit(code=2)
|
|
356
|
+
|
|
357
|
+
analytics.capture("logged_in") # name-only business event (no email/id/org value)
|
|
358
|
+
if state.json:
|
|
359
|
+
output.emit_json(
|
|
360
|
+
{"logged_in": True, "email": who, "org": chosen, "expires_in_secs": expires_in}
|
|
361
|
+
)
|
|
362
|
+
else:
|
|
363
|
+
output.signed_in(who, chosen)
|
|
364
|
+
|
|
365
|
+
|
|
366
|
+
def logout(ctx: typer.Context) -> None:
|
|
367
|
+
"""Sign out and clear the saved session from this machine.
|
|
368
|
+
|
|
369
|
+
Best-effort server-side revocation, then wipes the session from
|
|
370
|
+
`~/.failproofai/fpcli/cli-auth.json` —
|
|
371
|
+
the token, your email and user id, and the active org — so nothing about who you were or
|
|
372
|
+
which tenant you used is left behind. Your `base_url` and `--insecure` preference are kept,
|
|
373
|
+
so the next `login` is quick. If you are not signed in it is a no-op that reports
|
|
374
|
+
"already signed out".
|
|
375
|
+
|
|
376
|
+
With `--json`: `{"logged_out": true}` (plus `"already_signed_out": true` when there was no
|
|
377
|
+
session to clear).
|
|
378
|
+
|
|
379
|
+
Example:
|
|
380
|
+
|
|
381
|
+
* `fp logout`
|
|
382
|
+
"""
|
|
383
|
+
state: AppState = ctx.obj
|
|
384
|
+
deny_in_key_mode(
|
|
385
|
+
state,
|
|
386
|
+
"logout",
|
|
387
|
+
"an API key is never saved to disk, so there is nothing here to clear — and "
|
|
388
|
+
"revoking one takes `keys disable`, a permission a scoped key does not hold",
|
|
389
|
+
)
|
|
390
|
+
# No active session → nothing to revoke. Don't falsely claim a sign-out happened.
|
|
391
|
+
if not state.token:
|
|
392
|
+
if state.json:
|
|
393
|
+
output.emit_json({"logged_out": True, "already_signed_out": True})
|
|
394
|
+
else:
|
|
395
|
+
output.already_signed_out()
|
|
396
|
+
return
|
|
397
|
+
if state.base_url:
|
|
398
|
+
auth.logout(state.base_url, state.token, timeout=state.timeout, verify=not state.insecure)
|
|
399
|
+
analytics.capture("logged_out") # name-only; fire before reset() rotates the anon id
|
|
400
|
+
cfgmod.clear_token(state.config)
|
|
401
|
+
analytics.reset()
|
|
402
|
+
if state.json:
|
|
403
|
+
output.emit_json({"logged_out": True})
|
|
404
|
+
else:
|
|
405
|
+
output.signed_out()
|
|
406
|
+
|
|
407
|
+
|
|
408
|
+
def whoami(ctx: typer.Context) -> None:
|
|
409
|
+
"""Show the **current user**, the active org, and that org's permissions.
|
|
410
|
+
|
|
411
|
+
Never errors on a missing/expired session — it reports "not logged in" instead, so
|
|
412
|
+
it is safe for agents to probe auth state. Permissions are **per org**: the
|
|
413
|
+
`permissions` shown are for the active org. With `--json`, logged in:
|
|
414
|
+
`{"logged_in": true, "auth_mode": "session", "id": "...", "email": "...",
|
|
415
|
+
"is_instance_admin": <bool>, "active_org": "<slug|null>", "permissions": [...],
|
|
416
|
+
"memberships": [...]}`; otherwise `{"logged_in": false, "auth_mode": "none"}`.
|
|
417
|
+
|
|
418
|
+
With an API key it is the one command that still works, and it reports an honest
|
|
419
|
+
different shape — `{"logged_in": false, "auth_mode": "api_key", "active_org":
|
|
420
|
+
"<slug|null>"}`. A `null` there is worth reading: an instance-scoped key with no
|
|
421
|
+
`--org` resolves server-side to the DEFAULT org, so you would get *an* org's data,
|
|
422
|
+
just not necessarily the one you meant, with no error anywhere.
|
|
423
|
+
|
|
424
|
+
Example:
|
|
425
|
+
|
|
426
|
+
* `fp whoami`
|
|
427
|
+
* `fp --json whoami`
|
|
428
|
+
"""
|
|
429
|
+
state: AppState = ctx.obj
|
|
430
|
+
if state.auth_mode is AuthMode.API_KEY:
|
|
431
|
+
# `whoami` is contractually "never errors" (cli/skill/SKILL.md leans on it as
|
|
432
|
+
# the pre-flight probe), so key mode reports rather than refuses — but it does
|
|
433
|
+
# NOT invent a user: a key has no identity the CLI can read, and no /v1
|
|
434
|
+
# endpoint would tell us. Report exactly what we know, locally, exit 0.
|
|
435
|
+
if state.json:
|
|
436
|
+
output.emit_json(
|
|
437
|
+
{
|
|
438
|
+
"logged_in": False,
|
|
439
|
+
"auth_mode": AuthMode.API_KEY.value,
|
|
440
|
+
"active_org": state.org_explicit,
|
|
441
|
+
}
|
|
442
|
+
)
|
|
443
|
+
else:
|
|
444
|
+
output.render_key_mode_whoami(state.org_explicit)
|
|
445
|
+
return
|
|
446
|
+
if not state.token:
|
|
447
|
+
if state.json:
|
|
448
|
+
output.emit_json({"logged_in": False, "auth_mode": AuthMode.NONE.value})
|
|
449
|
+
else:
|
|
450
|
+
output.not_signed_in()
|
|
451
|
+
return
|
|
452
|
+
try:
|
|
453
|
+
user = get_session_user(build_context(state))
|
|
454
|
+
except AuthError:
|
|
455
|
+
if state.json:
|
|
456
|
+
output.emit_json({"logged_in": False, "auth_mode": AuthMode.NONE.value})
|
|
457
|
+
else:
|
|
458
|
+
output.not_signed_in()
|
|
459
|
+
return
|
|
460
|
+
|
|
461
|
+
# Effective active org: explicit/global/saved, else the sole membership.
|
|
462
|
+
active = state.org
|
|
463
|
+
if not active and len(user.memberships) == 1:
|
|
464
|
+
active = user.memberships[0].org_slug
|
|
465
|
+
active_perms = user.permissions_for(active)
|
|
466
|
+
|
|
467
|
+
if state.json:
|
|
468
|
+
output.emit_json(
|
|
469
|
+
{
|
|
470
|
+
"logged_in": True,
|
|
471
|
+
"auth_mode": AuthMode.SESSION.value,
|
|
472
|
+
"id": user.id,
|
|
473
|
+
"email": user.email,
|
|
474
|
+
"is_instance_admin": user.is_instance_admin,
|
|
475
|
+
"active_org": active,
|
|
476
|
+
"permissions": active_perms,
|
|
477
|
+
"memberships": [asdict(m) for m in user.memberships],
|
|
478
|
+
}
|
|
479
|
+
)
|
|
480
|
+
return
|
|
481
|
+
|
|
482
|
+
# Scannable identity view: a small header, then a permissions panel + an orgs panel.
|
|
483
|
+
orgs = [
|
|
484
|
+
{
|
|
485
|
+
"slug": m.org_slug,
|
|
486
|
+
"name": m.org_name,
|
|
487
|
+
"role": m.permission_set or "custom",
|
|
488
|
+
"perms": len(m.permissions),
|
|
489
|
+
"is_active": m.org_slug == active,
|
|
490
|
+
}
|
|
491
|
+
for m in user.memberships
|
|
492
|
+
]
|
|
493
|
+
active_role = next((o["role"] for o in orgs if o["is_active"]), None)
|
|
494
|
+
output.render_whoami(
|
|
495
|
+
email=user.email,
|
|
496
|
+
is_instance_admin=user.is_instance_admin,
|
|
497
|
+
user_id=user.id,
|
|
498
|
+
active_org=active,
|
|
499
|
+
active_role=active_role,
|
|
500
|
+
permissions=active_perms,
|
|
501
|
+
orgs=orgs,
|
|
502
|
+
)
|
|
503
|
+
|
|
504
|
+
|
|
505
|
+
# whoami's relevant global flag is `--json`; the full global-options list is omitted here.
|
|
506
|
+
_WHOAMI_EPILOG = "The global `--json` option goes **before** the command: `fp --json whoami`."
|
|
507
|
+
|
|
508
|
+
|
|
509
|
+
def register(app: typer.Typer) -> None:
|
|
510
|
+
app.command("login", epilog=GLOBALS_EPILOG)(login)
|
|
511
|
+
app.command("logout", epilog=GLOBALS_EPILOG)(logout)
|
|
512
|
+
app.command("whoami", epilog=_WHOAMI_EPILOG)(whoami)
|