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,330 @@
|
|
|
1
|
+
"""Persistent CLI configuration at ``~/.failproofai/fpcli/cli-auth.json`` (mode 0600).
|
|
2
|
+
|
|
3
|
+
This used to be ``~/.fp/cli.json`` — a third top-level dotfile beside
|
|
4
|
+
``~/.failproofai`` (the Enforcement CLI) and ``~/.agenteye`` (the SDK and
|
|
5
|
+
collector's event spool). One product owning three home directories is one more
|
|
6
|
+
than anybody can keep track of, so the CLI moved under the failproofai home.
|
|
7
|
+
``~/.agenteye`` stays where it is: it is a wire contract with the collector, not
|
|
8
|
+
a preference (see the SDK's ``test_server_contract.py``).
|
|
9
|
+
|
|
10
|
+
The failproofai home is a governed layout, NOT a free directory. Its shape is
|
|
11
|
+
declared in one place — ``src/hooks/fp-home.ts`` in this repo, mirrored for the
|
|
12
|
+
daemon in ``crates/failproofaid/src/paths.rs`` — and that file's rule is that
|
|
13
|
+
nothing outside it may join a path onto the home. So ``fpcli/cli-auth.json`` is
|
|
14
|
+
registered there too, classified ``user-typed`` in ``HOME_CLASSES``. That
|
|
15
|
+
classification is what keeps it: a layout migration deletes only ``derived`` and
|
|
16
|
+
``refetchable`` paths, and ``resettablePaths()`` is a filter over that table
|
|
17
|
+
rather than a hand-written list. It follows ``audit/session.json``, which is the
|
|
18
|
+
same thing for the audit tool and is likewise TS-side only — the daemon never
|
|
19
|
+
opens a human credential.
|
|
20
|
+
|
|
21
|
+
We only ever create. ``mkdir(parents=True, exist_ok=True)`` will bring
|
|
22
|
+
``~/.failproofai`` into existence on a machine that has never run the
|
|
23
|
+
Enforcement CLI, and leaves a populated one exactly as it found it. Nothing here
|
|
24
|
+
removes or rewrites a path it does not own.
|
|
25
|
+
|
|
26
|
+
The move is invisible to the user: a session at the old path is adopted on the
|
|
27
|
+
next command, so nobody is signed out by an upgrade. The old file is copied, not
|
|
28
|
+
moved, which keeps a downgrade working — an older `fp` still finds its session
|
|
29
|
+
where it left it.
|
|
30
|
+
"""
|
|
31
|
+
|
|
32
|
+
from __future__ import annotations
|
|
33
|
+
|
|
34
|
+
import errno
|
|
35
|
+
import json
|
|
36
|
+
import os
|
|
37
|
+
import stat
|
|
38
|
+
import tempfile
|
|
39
|
+
from dataclasses import asdict, dataclass
|
|
40
|
+
from datetime import datetime, timedelta, timezone
|
|
41
|
+
from pathlib import Path
|
|
42
|
+
from typing import Optional
|
|
43
|
+
|
|
44
|
+
# The dashboard the CLI talks to when nothing else says otherwise. Resolution is
|
|
45
|
+
# always explicit flag/env (`--base-url` / `FP_DASHBOARD_URL`) > saved config
|
|
46
|
+
# (`~/.failproofai/fpcli/cli-auth.json`) > this default, so a fresh install points at the hosted
|
|
47
|
+
# product with zero configuration, while a self-hosted or dev user overrides it
|
|
48
|
+
# once (at login, or per command) and never thinks about it again.
|
|
49
|
+
# NOT stored in `CliConfig` — a saved config with no `base_url` still reads back
|
|
50
|
+
# as `None`; the default is applied only when resolving the effective URL.
|
|
51
|
+
DEFAULT_BASE_URL = "https://app.befailproof.ai"
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
#: The CLI's own directory inside the failproofai home. Mirrors ``fpcliDir`` in
|
|
55
|
+
#: ``src/hooks/fp-home.ts``. Something checks now:
|
|
56
|
+
#: ``tests/test_fp_home_contract.py`` reads that file and fails if the two names
|
|
57
|
+
#: drift. Before it existed, renaming one side left 53 TS tests and 59 Python
|
|
58
|
+
#: tests passing while the credential sat at a path the layout register had
|
|
59
|
+
#: never heard of.
|
|
60
|
+
FPCLI_SUBDIR = "fpcli"
|
|
61
|
+
|
|
62
|
+
#: The pre-move location. READ once, to adopt a session that would otherwise be
|
|
63
|
+
#: lost, and never written or deleted — see :func:`load_config`, which copies it
|
|
64
|
+
#: to the new path and leaves the original so a downgrade still finds it.
|
|
65
|
+
#:
|
|
66
|
+
#: This said "never read" until it was checked against the code. Adoption
|
|
67
|
+
#: arrived after the move and the comment did not follow it.
|
|
68
|
+
LEGACY_DIR_NAME = ".fp"
|
|
69
|
+
LEGACY_FILE_NAME = "cli.json"
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def base_dir() -> Path:
|
|
73
|
+
"""Where ``cli-auth.json`` lives.
|
|
74
|
+
|
|
75
|
+
``$FP_HOME`` (the CLI's own variable, and the one the docs have always
|
|
76
|
+
named) wins, so an existing override keeps working untouched. Otherwise
|
|
77
|
+
``$FAILPROOFAI_HOME`` — the variable the rest of the failproofai home
|
|
78
|
+
already honours, which containers and tests routinely set — and finally
|
|
79
|
+
``~/.failproofai/fpcli``.
|
|
80
|
+
|
|
81
|
+
Both env vars name a DIRECTORY that the config file sits directly in.
|
|
82
|
+
``FAILPROOFAI_HOME`` points at the home root, so the subdirectory is
|
|
83
|
+
appended; ``FP_HOME`` points at the CLI's own directory and is used as-is,
|
|
84
|
+
which is what it meant before the move.
|
|
85
|
+
"""
|
|
86
|
+
override = os.environ.get("FP_HOME")
|
|
87
|
+
if override:
|
|
88
|
+
return Path(override)
|
|
89
|
+
fp_home = os.environ.get("FAILPROOFAI_HOME")
|
|
90
|
+
if fp_home:
|
|
91
|
+
return Path(fp_home) / FPCLI_SUBDIR
|
|
92
|
+
return Path.home() / ".failproofai" / FPCLI_SUBDIR
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def config_path() -> Path:
|
|
96
|
+
return base_dir() / "cli-auth.json"
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def legacy_config_paths() -> list[Path]:
|
|
100
|
+
"""Every place a pre-move session could still be sitting.
|
|
101
|
+
|
|
102
|
+
Two, not one, and the second is the one that is easy to miss. The move
|
|
103
|
+
changed the FILENAME as well as the directory, so somebody who exported
|
|
104
|
+
``FP_HOME`` — the documented way to relocate this config, and the shape CI
|
|
105
|
+
images use — has their old session at ``$FP_HOME/cli.json`` and will never
|
|
106
|
+
own a ``~/.fp`` at all. Checking only the default would hand exactly those
|
|
107
|
+
users an unexplained logout, which is the group least able to shrug at one.
|
|
108
|
+
|
|
109
|
+
A session found here is COPIED to the new location on the next command and
|
|
110
|
+
the original is left alone — see :func:`load_config`. Copying rather than
|
|
111
|
+
moving keeps a downgrade working: an older `fp` still finds its session.
|
|
112
|
+
|
|
113
|
+
The relocated path is checked FIRST. When both exist, the one in the
|
|
114
|
+
directory this invocation actually resolved is the one that explains this
|
|
115
|
+
user's logout; naming the default instead sends somebody with ``FP_HOME``
|
|
116
|
+
set to delete an unrelated file on a machine they may share.
|
|
117
|
+
"""
|
|
118
|
+
candidates = [base_dir() / LEGACY_FILE_NAME]
|
|
119
|
+
# `~/.fp` is only a candidate when the user has NOT redirected the config.
|
|
120
|
+
# Someone who exported `FP_HOME` said where their config lives; reaching past
|
|
121
|
+
# that into the home directory would adopt a session from a different context
|
|
122
|
+
# — a different tenant, or another user's leftovers on a shared box — and is
|
|
123
|
+
# also how this fallback quietly picked up the developer's own login when the
|
|
124
|
+
# suite ran.
|
|
125
|
+
if not os.environ.get("FP_HOME"):
|
|
126
|
+
default = Path.home() / LEGACY_DIR_NAME / LEGACY_FILE_NAME
|
|
127
|
+
if default not in candidates:
|
|
128
|
+
candidates.append(default)
|
|
129
|
+
return candidates
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
def legacy_config_path() -> Optional[Path]:
|
|
133
|
+
"""The first pre-move session file that actually exists, if any."""
|
|
134
|
+
for path in legacy_config_paths():
|
|
135
|
+
try:
|
|
136
|
+
if path.is_file():
|
|
137
|
+
return path
|
|
138
|
+
except OSError:
|
|
139
|
+
continue
|
|
140
|
+
return None
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
def legacy_install_detected() -> bool:
|
|
144
|
+
"""True when a pre-move session is on disk and the new one is not.
|
|
145
|
+
|
|
146
|
+
False once the new config exists, so the notice stops after the first
|
|
147
|
+
successful `fp login` rather than nagging forever.
|
|
148
|
+
"""
|
|
149
|
+
try:
|
|
150
|
+
if config_path().is_file():
|
|
151
|
+
return False
|
|
152
|
+
except OSError:
|
|
153
|
+
return False
|
|
154
|
+
return legacy_config_path() is not None
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
@dataclass
|
|
158
|
+
class CliConfig:
|
|
159
|
+
base_url: Optional[str] = None
|
|
160
|
+
session_token: Optional[str] = None
|
|
161
|
+
expires_at: Optional[str] = None # ISO 8601, e.g. 2026-05-26T12:00:00Z
|
|
162
|
+
email: Optional[str] = None
|
|
163
|
+
user_id: Optional[str] = None
|
|
164
|
+
insecure: bool = False
|
|
165
|
+
org: Optional[str] = None # active tenant slug, chosen at login (multi-tenant)
|
|
166
|
+
anonymous_id: Optional[str] = None # stable per-machine id for anonymous telemetry
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
def _parse(path: Path) -> Optional[CliConfig]:
|
|
170
|
+
"""Read one config file, or ``None`` if it is missing/unreadable/not ours."""
|
|
171
|
+
try:
|
|
172
|
+
data = json.loads(path.read_text())
|
|
173
|
+
except (FileNotFoundError, json.JSONDecodeError, OSError, UnicodeDecodeError):
|
|
174
|
+
return None
|
|
175
|
+
if not isinstance(data, dict):
|
|
176
|
+
return None
|
|
177
|
+
return _from_dict(data)
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
def load_config() -> CliConfig:
|
|
181
|
+
"""Load the session, adopting a pre-move one the first time we see it.
|
|
182
|
+
|
|
183
|
+
The move is silent for the user: nobody is signed out, no command changes
|
|
184
|
+
behaviour, and CI that authenticates by env var never enters this path at
|
|
185
|
+
all. The adoption is a copy — the old file is left exactly where it is, so
|
|
186
|
+
downgrading to a previous `fp` finds its session intact and this is
|
|
187
|
+
reversible on the machine as well as in the release.
|
|
188
|
+
|
|
189
|
+
Adoption is best-effort by design. If the new location cannot be written
|
|
190
|
+
(read-only home, a symlink we refuse, a full disk) the caller still gets the
|
|
191
|
+
session that was found, so a machine that cannot be migrated keeps working
|
|
192
|
+
rather than being logged out by our own housekeeping.
|
|
193
|
+
"""
|
|
194
|
+
current = _parse(config_path())
|
|
195
|
+
if current is not None:
|
|
196
|
+
return current
|
|
197
|
+
|
|
198
|
+
for legacy in legacy_config_paths():
|
|
199
|
+
adopted = _parse(legacy)
|
|
200
|
+
if adopted is None:
|
|
201
|
+
continue
|
|
202
|
+
try:
|
|
203
|
+
save_config(adopted)
|
|
204
|
+
except OSError:
|
|
205
|
+
pass # unwritable target: still hand back the session we found
|
|
206
|
+
return adopted
|
|
207
|
+
|
|
208
|
+
return CliConfig()
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
def _from_dict(data: dict) -> CliConfig:
|
|
212
|
+
return CliConfig(
|
|
213
|
+
base_url=data.get("base_url"),
|
|
214
|
+
session_token=data.get("session_token"),
|
|
215
|
+
expires_at=data.get("expires_at"),
|
|
216
|
+
email=data.get("email"),
|
|
217
|
+
user_id=data.get("user_id"),
|
|
218
|
+
insecure=bool(data.get("insecure", False)),
|
|
219
|
+
org=data.get("org"),
|
|
220
|
+
anonymous_id=data.get("anonymous_id"),
|
|
221
|
+
)
|
|
222
|
+
|
|
223
|
+
|
|
224
|
+
def save_config(cfg: CliConfig) -> None:
|
|
225
|
+
"""Write the session atomically, owner-only, without following any link.
|
|
226
|
+
|
|
227
|
+
Both halves of this matter only because the file now sits in a directory
|
|
228
|
+
another product's secrets live in. Writing in place was fine when the CLI
|
|
229
|
+
owned ``~/.fp`` outright; beside ``credentials.json`` it is a way to destroy
|
|
230
|
+
someone else's token.
|
|
231
|
+
|
|
232
|
+
* A **symlink** at ``cli-auth.json`` is refused outright. It is not removed:
|
|
233
|
+
a link is something a person put there, and replacing it silently is the
|
|
234
|
+
behaviour this function exists to avoid.
|
|
235
|
+
* A **hard link** cannot be detected the same way — it is not a link, it is a
|
|
236
|
+
second name for one inode, so ``O_NOFOLLOW`` says nothing about it. Writing
|
|
237
|
+
to a temporary file and ``os.replace``-ing it into position is what
|
|
238
|
+
actually answers this: rename swaps the DIRECTORY ENTRY, so the other name
|
|
239
|
+
keeps the old inode and the neighbour's file is untouched.
|
|
240
|
+
|
|
241
|
+
The same rename gives two things worth having on their own: a reader never
|
|
242
|
+
observes a half-written credential, and two ``fp`` processes racing end with
|
|
243
|
+
one of the two sessions rather than a splice of both.
|
|
244
|
+
"""
|
|
245
|
+
path = config_path()
|
|
246
|
+
# `mode` applies to the LEAF only, which is exactly the split we want: our
|
|
247
|
+
# own directory is created `0700` because it holds a credential, while a
|
|
248
|
+
# `~/.failproofai` we happen to be the first to create is left to the user's
|
|
249
|
+
# umask — the same shape the Enforcement CLI would have made it. An existing
|
|
250
|
+
# directory keeps its mode either way: `exist_ok=True` does not chmod, and
|
|
251
|
+
# re-permissioning a home another product owns is not ours to do.
|
|
252
|
+
path.parent.mkdir(mode=0o700, parents=True, exist_ok=True)
|
|
253
|
+
|
|
254
|
+
# `lstat`, not `exists`: the question is what the NAME is, not what it leads
|
|
255
|
+
# to. A dangling link must be refused too, or the refusal depends on whether
|
|
256
|
+
# the attacker's target happens to exist yet.
|
|
257
|
+
try:
|
|
258
|
+
if stat.S_ISLNK(os.lstat(path).st_mode):
|
|
259
|
+
raise OSError(
|
|
260
|
+
errno.ELOOP,
|
|
261
|
+
f"{path} is a symbolic link. Refusing to write the session "
|
|
262
|
+
"through it — that would overwrite whatever it points at, and "
|
|
263
|
+
"this directory is shared with the Enforcement CLI. Remove the "
|
|
264
|
+
"link and run the command again.",
|
|
265
|
+
)
|
|
266
|
+
except FileNotFoundError:
|
|
267
|
+
pass # the ordinary first-login case
|
|
268
|
+
|
|
269
|
+
payload = json.dumps(asdict(cfg), indent=2) + "\n"
|
|
270
|
+
# Same directory, so the rename is on one filesystem and therefore atomic.
|
|
271
|
+
#
|
|
272
|
+
# `mkstemp` rather than a name built from the pid: two THREADS share a pid,
|
|
273
|
+
# so a pid-derived name collided under `O_EXCL` and turned a concurrent
|
|
274
|
+
# `save_config` into a crash. It also creates `0600` and `O_EXCL` itself,
|
|
275
|
+
# which is the same guarantee hand-rolled with fewer ways to get it wrong.
|
|
276
|
+
fd, tmp_name = tempfile.mkstemp(dir=str(path.parent), prefix=f".{path.name}.", suffix=".tmp")
|
|
277
|
+
tmp = Path(tmp_name)
|
|
278
|
+
try:
|
|
279
|
+
with os.fdopen(fd, "w") as fh:
|
|
280
|
+
fh.write(payload)
|
|
281
|
+
fh.flush()
|
|
282
|
+
os.fsync(fh.fileno()) # survive a crash between write and rename
|
|
283
|
+
os.chmod(tmp, 0o600)
|
|
284
|
+
os.replace(tmp, path)
|
|
285
|
+
except BaseException:
|
|
286
|
+
# Never leave a stray credential behind on the failure paths.
|
|
287
|
+
try:
|
|
288
|
+
os.unlink(tmp)
|
|
289
|
+
except OSError:
|
|
290
|
+
pass
|
|
291
|
+
raise
|
|
292
|
+
|
|
293
|
+
|
|
294
|
+
def clear_token(cfg: CliConfig) -> CliConfig:
|
|
295
|
+
"""Clear the whole logged-in session and persist.
|
|
296
|
+
|
|
297
|
+
Drops everything tied to *who* was signed in — token, expiry, email, user id,
|
|
298
|
+
and the active org/tenant — so a logout leaves no stale identity (and the next
|
|
299
|
+
`login` starts the org picker fresh, with no remembered tenant). Kept on
|
|
300
|
+
purpose: `base_url` and the `insecure` TLS preference (so the next login
|
|
301
|
+
doesn't need them re-specified) and the machine-stable `anonymous_id`.
|
|
302
|
+
"""
|
|
303
|
+
cfg.session_token = None
|
|
304
|
+
cfg.expires_at = None
|
|
305
|
+
cfg.email = None
|
|
306
|
+
cfg.user_id = None
|
|
307
|
+
cfg.org = None
|
|
308
|
+
save_config(cfg)
|
|
309
|
+
return cfg
|
|
310
|
+
|
|
311
|
+
|
|
312
|
+
def _parse_iso(value: str) -> datetime:
|
|
313
|
+
# Python 3.10's fromisoformat does not accept a trailing 'Z'.
|
|
314
|
+
normalized = value.strip().replace("Z", "+00:00")
|
|
315
|
+
dt = datetime.fromisoformat(normalized)
|
|
316
|
+
if dt.tzinfo is None:
|
|
317
|
+
dt = dt.replace(tzinfo=timezone.utc)
|
|
318
|
+
return dt
|
|
319
|
+
|
|
320
|
+
|
|
321
|
+
def is_expired(cfg: CliConfig, *, skew_secs: int = 60, now: Optional[datetime] = None) -> bool:
|
|
322
|
+
"""True if there is no usable, unexpired token (with a safety skew)."""
|
|
323
|
+
if not cfg.session_token or not cfg.expires_at:
|
|
324
|
+
return True
|
|
325
|
+
try:
|
|
326
|
+
expires = _parse_iso(cfg.expires_at)
|
|
327
|
+
except ValueError:
|
|
328
|
+
return True
|
|
329
|
+
now = now or datetime.now(timezone.utc)
|
|
330
|
+
return now >= expires - timedelta(seconds=skew_secs)
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
"""Relative date-range presets, mirroring the dashboard's ``resolveDateRange``.
|
|
2
|
+
|
|
3
|
+
``--since`` accepts one of ``15m|1h|6h|24h|7d|all`` and is converted to a
|
|
4
|
+
``ts_from`` lower bound (open-ended to "now"). ``--from``/``--to`` provide an
|
|
5
|
+
explicit custom range and take precedence over ``--since``.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
from datetime import datetime, timedelta, timezone
|
|
11
|
+
from typing import Optional, Tuple
|
|
12
|
+
|
|
13
|
+
PRESETS = {
|
|
14
|
+
"15m": timedelta(minutes=15),
|
|
15
|
+
"1h": timedelta(hours=1),
|
|
16
|
+
"6h": timedelta(hours=6),
|
|
17
|
+
"24h": timedelta(hours=24),
|
|
18
|
+
"7d": timedelta(days=7),
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
# Accepted values for --since (including the no-op "all").
|
|
22
|
+
SINCE_CHOICES = ["all", "15m", "1h", "6h", "24h", "7d"]
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def _iso(dt: datetime) -> str:
|
|
26
|
+
return dt.astimezone(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ")
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def _validate_iso(value: str, flag: str) -> str:
|
|
30
|
+
"""Validate an explicit ``--from``/``--to`` is a full RFC3339 UTC datetime.
|
|
31
|
+
|
|
32
|
+
The server deserializes ``ts_from``/``ts_to`` as ``chrono::DateTime<Utc>``, which
|
|
33
|
+
requires a ``T`` separator AND an explicit timezone (``Z`` or ``±HH:MM``). A
|
|
34
|
+
date-only, timezone-less (``2026-05-01T00:00:00``), or space-separated value is
|
|
35
|
+
sent verbatim and rejected by the server with a 400; validating here turns that
|
|
36
|
+
into a clean usage error (exit 2) instead.
|
|
37
|
+
"""
|
|
38
|
+
v = value.strip()
|
|
39
|
+
try:
|
|
40
|
+
parsed = datetime.fromisoformat(v.replace("Z", "+00:00"))
|
|
41
|
+
except ValueError:
|
|
42
|
+
parsed = None
|
|
43
|
+
if parsed is None or "T" not in v or parsed.tzinfo is None:
|
|
44
|
+
raise ValueError(
|
|
45
|
+
f"invalid {flag} value {value!r}; expected an ISO-8601 UTC timestamp, "
|
|
46
|
+
"e.g. 2026-05-01T00:00:00Z"
|
|
47
|
+
)
|
|
48
|
+
return v
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def resolve_range(
|
|
52
|
+
since: Optional[str] = None,
|
|
53
|
+
ts_from: Optional[str] = None,
|
|
54
|
+
ts_to: Optional[str] = None,
|
|
55
|
+
*,
|
|
56
|
+
now: Optional[datetime] = None,
|
|
57
|
+
) -> Tuple[Optional[str], Optional[str]]:
|
|
58
|
+
"""Return ``(ts_from, ts_to)`` for the given inputs.
|
|
59
|
+
|
|
60
|
+
Explicit ``ts_from``/``ts_to`` win. Otherwise a ``--since`` preset sets the
|
|
61
|
+
lower bound. ``since="all"`` (or ``None``) means no bounds.
|
|
62
|
+
"""
|
|
63
|
+
if ts_from is not None or ts_to is not None:
|
|
64
|
+
return (
|
|
65
|
+
_validate_iso(ts_from, "--from") if ts_from is not None else None,
|
|
66
|
+
_validate_iso(ts_to, "--to") if ts_to is not None else None,
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
if since is None or since == "all":
|
|
70
|
+
return None, None
|
|
71
|
+
|
|
72
|
+
if since not in PRESETS:
|
|
73
|
+
raise ValueError(
|
|
74
|
+
f"invalid --since value {since!r}; choose one of {', '.join(SINCE_CHOICES)}"
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
now = now or datetime.now(timezone.utc)
|
|
78
|
+
return _iso(now - PRESETS[since]), None
|