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.
Files changed (389) hide show
  1. package/.next/standalone/.next/BUILD_ID +1 -1
  2. package/.next/standalone/.next/build-manifest.json +3 -3
  3. package/.next/standalone/.next/prerender-manifest.json +6 -6
  4. package/.next/standalone/.next/required-server-files.json +1 -1
  5. package/.next/standalone/.next/server/app/_global-error/page/server-reference-manifest.json +1 -1
  6. package/.next/standalone/.next/server/app/_global-error/page.js +2 -2
  7. package/.next/standalone/.next/server/app/_global-error/page.js.nft.json +1 -1
  8. package/.next/standalone/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
  9. package/.next/standalone/.next/server/app/_global-error.html +1 -1
  10. package/.next/standalone/.next/server/app/_global-error.rsc +7 -7
  11. package/.next/standalone/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +6 -6
  12. package/.next/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +7 -7
  13. package/.next/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  14. package/.next/standalone/.next/server/app/_not-found/page/server-reference-manifest.json +1 -1
  15. package/.next/standalone/.next/server/app/_not-found/page.js +2 -2
  16. package/.next/standalone/.next/server/app/_not-found/page.js.nft.json +1 -1
  17. package/.next/standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  18. package/.next/standalone/.next/server/app/_not-found.html +1 -1
  19. package/.next/standalone/.next/server/app/_not-found.rsc +15 -15
  20. package/.next/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +15 -15
  21. package/.next/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +14 -14
  22. package/.next/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
  23. package/.next/standalone/.next/server/app/api/audit/invite/route.js +3 -2
  24. package/.next/standalone/.next/server/app/api/audit/invite/route.js.nft.json +1 -1
  25. package/.next/standalone/.next/server/app/api/audit/run/route.js +5 -4
  26. package/.next/standalone/.next/server/app/api/audit/run/route.js.nft.json +1 -1
  27. package/.next/standalone/.next/server/app/api/audit/status/route.js.nft.json +1 -1
  28. package/.next/standalone/.next/server/app/api/auth/login-request/route.js +2 -2
  29. package/.next/standalone/.next/server/app/api/auth/login-request/route.js.nft.json +1 -1
  30. package/.next/standalone/.next/server/app/api/auth/login-verify/route.js +4 -3
  31. package/.next/standalone/.next/server/app/api/auth/login-verify/route.js.nft.json +1 -1
  32. package/.next/standalone/.next/server/app/api/auth/logout/route.js +4 -4
  33. package/.next/standalone/.next/server/app/api/auth/logout/route.js.nft.json +1 -1
  34. package/.next/standalone/.next/server/app/api/auth/status/route.js +4 -4
  35. package/.next/standalone/.next/server/app/api/auth/status/route.js.nft.json +1 -1
  36. package/.next/standalone/.next/server/app/api/download/[project]/[session]/route.js.nft.json +1 -1
  37. package/.next/standalone/.next/server/app/audit/page/server-reference-manifest.json +2 -2
  38. package/.next/standalone/.next/server/app/audit/page.js +2 -2
  39. package/.next/standalone/.next/server/app/audit/page.js.nft.json +1 -1
  40. package/.next/standalone/.next/server/app/audit/page_client-reference-manifest.js +1 -1
  41. package/.next/standalone/.next/server/app/index.html +1 -1
  42. package/.next/standalone/.next/server/app/index.rsc +15 -15
  43. package/.next/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +14 -14
  44. package/.next/standalone/.next/server/app/index.segments/_full.segment.rsc +15 -15
  45. package/.next/standalone/.next/server/app/index.segments/_tree.segment.rsc +2 -2
  46. package/.next/standalone/.next/server/app/page/server-reference-manifest.json +1 -1
  47. package/.next/standalone/.next/server/app/page.js +2 -2
  48. package/.next/standalone/.next/server/app/page.js.nft.json +1 -1
  49. package/.next/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
  50. package/.next/standalone/.next/server/app/policies/page/server-reference-manifest.json +66 -22
  51. package/.next/standalone/.next/server/app/policies/page.js +5 -4
  52. package/.next/standalone/.next/server/app/policies/page.js.nft.json +1 -1
  53. package/.next/standalone/.next/server/app/policies/page_client-reference-manifest.js +1 -1
  54. package/.next/standalone/.next/server/app/project/[name]/page/server-reference-manifest.json +1 -1
  55. package/.next/standalone/.next/server/app/project/[name]/page.js +2 -2
  56. package/.next/standalone/.next/server/app/project/[name]/page.js.nft.json +1 -1
  57. package/.next/standalone/.next/server/app/project/[name]/page_client-reference-manifest.js +1 -1
  58. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page/react-loadable-manifest.json +2 -2
  59. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page/server-reference-manifest.json +2 -2
  60. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page.js +2 -2
  61. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page.js.nft.json +1 -1
  62. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page_client-reference-manifest.js +1 -1
  63. package/.next/standalone/.next/server/app/projects/page/server-reference-manifest.json +1 -1
  64. package/.next/standalone/.next/server/app/projects/page.js +2 -2
  65. package/.next/standalone/.next/server/app/projects/page.js.nft.json +1 -1
  66. package/.next/standalone/.next/server/app/projects/page_client-reference-manifest.js +1 -1
  67. package/.next/standalone/.next/server/app/settings/page/server-reference-manifest.json +4 -4
  68. package/.next/standalone/.next/server/app/settings/page.js +1 -1
  69. package/.next/standalone/.next/server/app/settings/page.js.nft.json +1 -1
  70. package/.next/standalone/.next/server/app/settings/page_client-reference-manifest.js +1 -1
  71. package/.next/standalone/.next/server/chunks/{[externals]__1qzqcio._.js → [externals]__0e32-7c._.js} +1 -1
  72. package/.next/standalone/.next/server/chunks/[externals]__1s462yl._.js +3 -0
  73. package/.next/standalone/.next/server/chunks/[root-of-the-server]__01eqny5._.js +3 -0
  74. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0_0xu5z._.js +1 -1
  75. package/.next/standalone/.next/server/chunks/{[root-of-the-server]__1p8poxd._.js → [root-of-the-server]__0igxt32._.js} +2 -2
  76. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0neqlu-._.js +3 -0
  77. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0o07qi9._.js +3 -0
  78. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0t9oqnn._.js +3 -0
  79. package/.next/standalone/.next/server/chunks/_0bs-v8p._.js +3 -0
  80. package/.next/standalone/.next/server/chunks/_0tovk6q._.js +3 -0
  81. package/.next/standalone/.next/server/chunks/_0trp3yc._.js +3 -0
  82. package/.next/standalone/.next/server/chunks/_185gyk9._.js +3 -0
  83. package/.next/standalone/.next/server/chunks/_1ek68ln._.js +5 -6
  84. package/.next/standalone/.next/server/chunks/lib_telemetry-id_ts_1k8gj9o._.js +3 -0
  85. 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
  86. 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
  87. package/.next/standalone/.next/server/chunks/package_json_[json]_cjs_1nxcc4v._.js +1 -1
  88. package/.next/standalone/.next/server/chunks/src_hooks_0xbw3bu._.js +12 -0
  89. package/.next/standalone/.next/server/chunks/src_hooks_fp-home_ts_09kv0bn._.js +1 -1
  90. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__013jr2b._.js +4 -0
  91. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__01wy8d-._.js +4 -0
  92. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__02npjtd._.js +4 -0
  93. package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__0spkm68._.js → [root-of-the-server]__07gm3zl._.js} +21 -20
  94. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0da85px._.js +4 -0
  95. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0ftmoxc._.js +4 -0
  96. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0oa1lav._.js +1 -1
  97. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0p-5p8u._.js +4 -0
  98. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0s740oi._.js +6 -0
  99. package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__07tbbf3._.js → [root-of-the-server]__0zig5sv._.js} +2 -2
  100. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__192nhvh._.js +5 -0
  101. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1p2otjt._.js +4 -0
  102. package/.next/standalone/.next/server/chunks/ssr/{_0lvyuv4._.js → _00e9tnf._.js} +2 -2
  103. package/.next/standalone/.next/server/chunks/ssr/_08x1r5t._.js +1 -1
  104. package/.next/standalone/.next/server/chunks/ssr/{_0u5zauv._.js → _12l4a_k._.js} +1 -1
  105. package/.next/standalone/.next/server/chunks/ssr/_1u8-lu2._.js +1 -1
  106. package/.next/standalone/.next/server/chunks/ssr/_1zopuov._.js +1 -1
  107. package/.next/standalone/.next/server/chunks/ssr/app_audit__components_audit-dashboard_tsx_0p9ud47._.js +13 -13
  108. package/.next/standalone/.next/server/chunks/ssr/app_global-error_tsx_1kp6l3x._.js +1 -1
  109. package/.next/standalone/.next/server/chunks/ssr/app_policies_hooks-client_tsx_19dqvpc._.js +2 -2
  110. package/.next/standalone/.next/server/chunks/ssr/app_settings_settings-client_tsx_20lq-mq._.js +1 -1
  111. package/.next/standalone/.next/server/chunks/ssr/src_hooks_08o7bqo._.js +10 -0
  112. package/.next/standalone/.next/server/chunks/ssr/src_hooks_builtin-policies_ts_09j2ndl._.js +1 -1
  113. package/.next/standalone/.next/server/chunks/ssr/src_hooks_fp-home_ts_0je3xkv._.js +1 -1
  114. package/.next/standalone/.next/server/middleware-build-manifest.js +3 -3
  115. package/.next/standalone/.next/server/middleware.js +1 -1
  116. package/.next/standalone/.next/server/pages/404.html +1 -1
  117. package/.next/standalone/.next/server/pages/500.html +1 -1
  118. package/.next/standalone/.next/server/server-reference-manifest.js +1 -1
  119. package/.next/standalone/.next/server/server-reference-manifest.json +71 -27
  120. package/.next/standalone/.next/static/chunks/{37fcpc36x9gpe.js → 0eqky-7n7dgdm.js} +1 -1
  121. package/.next/standalone/.next/static/chunks/{1ide1wl2f7zi1.js → 0sa9v5mhc8861.js} +13 -13
  122. package/.next/standalone/.next/static/chunks/1mfzsja6ftim5.css +2 -0
  123. package/.next/standalone/.next/static/chunks/{3k7mxbp9hiom0.js → 1nuqytbta44af.js} +1 -1
  124. package/.next/standalone/.next/static/chunks/1sl2xmn9inx3y.js +6 -0
  125. package/.next/standalone/.next/static/chunks/{2_2lhuojq2xop.js → 2aawsjld9ied_.js} +1 -1
  126. package/.next/standalone/.next/static/chunks/2gn7ut4svofm2.js +1 -0
  127. package/.next/standalone/.next/static/chunks/{31s1smp-g7gks.js → 2hs7ycvsav7v2.js} +1 -1
  128. package/.next/standalone/.next/static/chunks/{0k5050ezjvv49.js → 2i2yt3hzjredm.js} +1 -1
  129. package/.next/standalone/.next/static/chunks/{3c808qrfcq0im.js → 35xg6retpe6rp.js} +1 -1
  130. package/.next/standalone/.next/static/chunks/{2zpp1we2jv6tb.js → 3y54nw0i7dsjl.js} +1 -1
  131. package/.next/standalone/SECURITY.md +5 -4
  132. package/.next/standalone/app/actions/get-hooks-config.ts +108 -17
  133. package/.next/standalone/app/actions/pack-actions.ts +153 -0
  134. package/.next/standalone/app/audit/_components/run-progress.tsx +1 -1
  135. package/.next/standalone/app/audit/_components/share-templates.ts +13 -13
  136. package/.next/standalone/app/policies/hooks-client.tsx +320 -14
  137. package/.next/standalone/components/navbar.tsx +2 -28
  138. package/.next/standalone/components/reach-developers.tsx +8 -1
  139. package/.next/standalone/docs-old/agenteye/python-sdk-skill.mdx +1 -1
  140. package/.next/standalone/docs-old/agenteye/python-sdk.mdx +59 -35
  141. package/.next/standalone/docs-old/i18n/README.ar.md +1 -0
  142. package/.next/standalone/docs-old/i18n/README.de.md +1 -0
  143. package/.next/standalone/docs-old/i18n/README.es.md +1 -0
  144. package/.next/standalone/docs-old/i18n/README.fr.md +1 -0
  145. package/.next/standalone/docs-old/i18n/README.he.md +1 -0
  146. package/.next/standalone/docs-old/i18n/README.hi.md +1 -0
  147. package/.next/standalone/docs-old/i18n/README.it.md +1 -0
  148. package/.next/standalone/docs-old/i18n/README.ja.md +1 -0
  149. package/.next/standalone/docs-old/i18n/README.ko.md +1 -0
  150. package/.next/standalone/docs-old/i18n/README.pt-br.md +1 -0
  151. package/.next/standalone/docs-old/i18n/README.ru.md +1 -0
  152. package/.next/standalone/docs-old/i18n/README.tr.md +1 -0
  153. package/.next/standalone/docs-old/i18n/README.vi.md +1 -0
  154. package/.next/standalone/docs-old/i18n/README.zh.md +1 -0
  155. package/.next/standalone/fp-cloud-cli/CHANGELOG.md +431 -0
  156. package/.next/standalone/fp-cloud-cli/LICENSE +42 -0
  157. package/.next/standalone/fp-cloud-cli/MANIFEST.in +10 -0
  158. package/.next/standalone/fp-cloud-cli/README.md +259 -0
  159. package/.next/standalone/fp-cloud-cli/fp_cli/__init__.py +10 -0
  160. package/.next/standalone/fp-cloud-cli/fp_cli/__main__.py +4 -0
  161. package/.next/standalone/fp-cloud-cli/fp_cli/_click_compat.py +64 -0
  162. package/.next/standalone/fp-cloud-cli/fp_cli/_context.py +332 -0
  163. package/.next/standalone/fp-cloud-cli/fp_cli/_version.py +1 -0
  164. package/.next/standalone/fp-cloud-cli/fp_cli/analytics.py +432 -0
  165. package/.next/standalone/fp-cloud-cli/fp_cli/analytics_config.py +77 -0
  166. package/.next/standalone/fp-cloud-cli/fp_cli/analytics_registry.py +83 -0
  167. package/.next/standalone/fp-cloud-cli/fp_cli/app.py +492 -0
  168. package/.next/standalone/fp-cloud-cli/fp_cli/auth.py +160 -0
  169. package/.next/standalone/fp-cloud-cli/fp_cli/client.py +1694 -0
  170. package/.next/standalone/fp-cloud-cli/fp_cli/commands/__init__.py +0 -0
  171. package/.next/standalone/fp-cloud-cli/fp_cli/commands/_write.py +214 -0
  172. package/.next/standalone/fp-cloud-cli/fp_cli/commands/agent_cmds.py +407 -0
  173. package/.next/standalone/fp-cloud-cli/fp_cli/commands/alerts_cmds.py +445 -0
  174. package/.next/standalone/fp-cloud-cli/fp_cli/commands/audits_cmds.py +1054 -0
  175. package/.next/standalone/fp-cloud-cli/fp_cli/commands/auth_cmds.py +512 -0
  176. package/.next/standalone/fp-cloud-cli/fp_cli/commands/errors_cmds.py +190 -0
  177. package/.next/standalone/fp-cloud-cli/fp_cli/commands/evals_cmds.py +161 -0
  178. package/.next/standalone/fp-cloud-cli/fp_cli/commands/events_cmds.py +159 -0
  179. package/.next/standalone/fp-cloud-cli/fp_cli/commands/fleet_cmds.py +416 -0
  180. package/.next/standalone/fp-cloud-cli/fp_cli/commands/guardrails_cmds.py +148 -0
  181. package/.next/standalone/fp-cloud-cli/fp_cli/commands/incidents_cmds.py +472 -0
  182. package/.next/standalone/fp-cloud-cli/fp_cli/commands/keys_cmds.py +407 -0
  183. package/.next/standalone/fp-cloud-cli/fp_cli/commands/list_cmds.py +63 -0
  184. package/.next/standalone/fp-cloud-cli/fp_cli/commands/orgs_cmds.py +319 -0
  185. package/.next/standalone/fp-cloud-cli/fp_cli/commands/policies_cmds.py +499 -0
  186. package/.next/standalone/fp-cloud-cli/fp_cli/commands/queries_cmds.py +378 -0
  187. package/.next/standalone/fp-cloud-cli/fp_cli/commands/sessions_cmds.py +151 -0
  188. package/.next/standalone/fp-cloud-cli/fp_cli/commands/settings_cmds.py +150 -0
  189. package/.next/standalone/fp-cloud-cli/fp_cli/commands/usage_cmds.py +35 -0
  190. package/.next/standalone/fp-cloud-cli/fp_cli/commands/users_cmds.py +404 -0
  191. package/.next/standalone/fp-cloud-cli/fp_cli/config.py +330 -0
  192. package/.next/standalone/fp-cloud-cli/fp_cli/dates.py +78 -0
  193. package/.next/standalone/fp-cloud-cli/fp_cli/enforcement.py +345 -0
  194. package/.next/standalone/fp-cloud-cli/fp_cli/errors.py +98 -0
  195. package/.next/standalone/fp-cloud-cli/fp_cli/models.py +891 -0
  196. package/.next/standalone/fp-cloud-cli/fp_cli/orgs.py +30 -0
  197. package/.next/standalone/fp-cloud-cli/fp_cli/output.py +6593 -0
  198. package/.next/standalone/fp-cloud-cli/fp_cli/permissions.py +208 -0
  199. package/.next/standalone/fp-cloud-cli/fp_cli/policy_check.py +290 -0
  200. package/.next/standalone/fp-cloud-cli/fp_cli/py.typed +0 -0
  201. package/.next/standalone/fp-cloud-cli/fp_cli/select.py +322 -0
  202. package/.next/standalone/fp-cloud-cli/fp_cli/theme.py +53 -0
  203. package/.next/standalone/fp-cloud-cli/pyproject.toml +106 -0
  204. package/.next/standalone/fp-cloud-cli/skill/SKILL.md +302 -0
  205. package/.next/standalone/fp-cloud-cli/skill/agents/openai.yaml +8 -0
  206. package/.next/standalone/fp-cloud-cli/skill/references/commands.md +254 -0
  207. package/.next/standalone/fp-cloud-cli/tests/__init__.py +0 -0
  208. package/.next/standalone/fp-cloud-cli/tests/conftest.py +92 -0
  209. package/.next/standalone/fp-cloud-cli/tests/test_alerting.py +644 -0
  210. package/.next/standalone/fp-cloud-cli/tests/test_analytics.py +385 -0
  211. package/.next/standalone/fp-cloud-cli/tests/test_audits.py +950 -0
  212. package/.next/standalone/fp-cloud-cli/tests/test_auth.py +98 -0
  213. package/.next/standalone/fp-cloud-cli/tests/test_auth_mode.py +272 -0
  214. package/.next/standalone/fp-cloud-cli/tests/test_click_compat.py +118 -0
  215. package/.next/standalone/fp-cloud-cli/tests/test_client.py +299 -0
  216. package/.next/standalone/fp-cloud-cli/tests/test_commands.py +814 -0
  217. package/.next/standalone/fp-cloud-cli/tests/test_config.py +99 -0
  218. package/.next/standalone/fp-cloud-cli/tests/test_dashboards_agent.py +340 -0
  219. package/.next/standalone/fp-cloud-cli/tests/test_dates.py +76 -0
  220. package/.next/standalone/fp-cloud-cli/tests/test_enforcement_logic.py +481 -0
  221. package/.next/standalone/fp-cloud-cli/tests/test_facets.py +170 -0
  222. package/.next/standalone/fp-cloud-cli/tests/test_failproofai_home.py +722 -0
  223. package/.next/standalone/fp-cloud-cli/tests/test_fp_home_contract.py +198 -0
  224. package/.next/standalone/fp-cloud-cli/tests/test_hardening.py +186 -0
  225. package/.next/standalone/fp-cloud-cli/tests/test_help_table_coverage.py +127 -0
  226. package/.next/standalone/fp-cloud-cli/tests/test_keys_queries.py +496 -0
  227. package/.next/standalone/fp-cloud-cli/tests/test_list.py +59 -0
  228. package/.next/standalone/fp-cloud-cli/tests/test_multivalue.py +183 -0
  229. package/.next/standalone/fp-cloud-cli/tests/test_no_customer_identifiers.py +168 -0
  230. package/.next/standalone/fp-cloud-cli/tests/test_operator.py +492 -0
  231. package/.next/standalone/fp-cloud-cli/tests/test_orgs.py +692 -0
  232. package/.next/standalone/fp-cloud-cli/tests/test_output.py +1732 -0
  233. package/.next/standalone/fp-cloud-cli/tests/test_policy_check.py +191 -0
  234. package/.next/standalone/fp-cloud-cli/tests/test_readme_matches_reality.py +125 -0
  235. package/.next/standalone/fp-cloud-cli/tests/test_review_fixes.py +161 -0
  236. package/.next/standalone/fp-cloud-cli/tests/test_telemetry_completeness.py +90 -0
  237. package/.next/standalone/fp-cloud-cli/tests/test_usage.py +115 -0
  238. package/.next/standalone/fp-cloud-cli/tests/test_v1_origin_diagnostic.py +70 -0
  239. package/.next/standalone/fp-cloud-cli/tests/test_v1_routing.py +214 -0
  240. package/.next/standalone/fp-cloud-cli/tests/test_whoami.py +104 -0
  241. package/.next/standalone/fp-cloud-cli/uv.lock +552 -0
  242. package/.next/standalone/package.json +29 -22
  243. package/.next/standalone/sdk/python/CHANGELOG.md +70 -0
  244. package/.next/standalone/sdk/python/LICENSE +42 -0
  245. package/.next/standalone/sdk/python/MANIFEST.in +21 -0
  246. package/.next/standalone/sdk/python/README.md +466 -0
  247. package/.next/standalone/sdk/python/docs/README.md +249 -0
  248. package/.next/standalone/sdk/python/docs/_shared/README.md +43 -0
  249. package/.next/standalone/sdk/python/docs/_shared/__init__.py +195 -0
  250. package/.next/standalone/sdk/python/docs/crewai/README.md +328 -0
  251. package/.next/standalone/sdk/python/docs/crewai/examples/quickstart.py +61 -0
  252. package/.next/standalone/sdk/python/docs/crewai/examples/research_crew.py +92 -0
  253. package/.next/standalone/sdk/python/docs/langgraph/README.md +481 -0
  254. package/.next/standalone/sdk/python/docs/langgraph/examples/quickstart.py +51 -0
  255. package/.next/standalone/sdk/python/docs/langgraph/examples/research_agent.py +86 -0
  256. package/.next/standalone/sdk/python/docs/langgraph/examples/supervisor_handoff.py +83 -0
  257. package/.next/standalone/sdk/python/docs/llama_index/README.md +332 -0
  258. package/.next/standalone/sdk/python/docs/llama_index/examples/quickstart.py +62 -0
  259. package/.next/standalone/sdk/python/docs/llama_index/examples/research_agent.py +93 -0
  260. package/.next/standalone/sdk/python/docs/manual/README.md +491 -0
  261. package/.next/standalone/sdk/python/docs/manual/examples/quickstart.py +57 -0
  262. package/.next/standalone/sdk/python/docs/manual/examples/research_agent.py +133 -0
  263. package/.next/standalone/sdk/python/docs/pydantic_ai/README.md +323 -0
  264. package/.next/standalone/sdk/python/docs/pydantic_ai/examples/quickstart.py +48 -0
  265. package/.next/standalone/sdk/python/docs/pydantic_ai/examples/research_agent.py +94 -0
  266. package/.next/standalone/sdk/python/failproofai_sdk/__init__.py +131 -0
  267. package/.next/standalone/sdk/python/failproofai_sdk/_context.py +218 -0
  268. package/.next/standalone/sdk/python/failproofai_sdk/_environment.py +78 -0
  269. package/.next/standalone/sdk/python/failproofai_sdk/_events.py +752 -0
  270. package/.next/standalone/sdk/python/failproofai_sdk/_resolver.py +118 -0
  271. package/.next/standalone/sdk/python/failproofai_sdk/_runtime.py +21 -0
  272. package/.next/standalone/sdk/python/failproofai_sdk/_schema.py +317 -0
  273. package/.next/standalone/sdk/python/failproofai_sdk/_scopes.py +422 -0
  274. package/.next/standalone/sdk/python/failproofai_sdk/_version.py +1 -0
  275. package/.next/standalone/sdk/python/failproofai_sdk/_writer.py +750 -0
  276. package/.next/standalone/sdk/python/failproofai_sdk/integrations/__init__.py +274 -0
  277. package/.next/standalone/sdk/python/failproofai_sdk/integrations/_compat.py +247 -0
  278. package/.next/standalone/sdk/python/failproofai_sdk/integrations/_core.py +1076 -0
  279. package/.next/standalone/sdk/python/failproofai_sdk/integrations/crewai.py +1669 -0
  280. package/.next/standalone/sdk/python/failproofai_sdk/integrations/langchain.py +2151 -0
  281. package/.next/standalone/sdk/python/failproofai_sdk/integrations/llama_index.py +1588 -0
  282. package/.next/standalone/sdk/python/failproofai_sdk/integrations/pydantic_ai.py +977 -0
  283. package/.next/standalone/sdk/python/failproofai_sdk/py.typed +0 -0
  284. package/.next/standalone/sdk/python/pyproject.toml +92 -0
  285. package/.next/standalone/sdk/python/skill/SKILL.md +399 -0
  286. package/.next/standalone/sdk/python/skill/agents/openai.yaml +8 -0
  287. package/.next/standalone/sdk/python/skill/references/events.md +234 -0
  288. package/.next/standalone/sdk/python/skill/references/frameworks.md +314 -0
  289. package/.next/standalone/sdk/python/skill/references/install.md +100 -0
  290. package/.next/standalone/sdk/python/skill/references/integration.md +352 -0
  291. package/.next/standalone/sdk/python/tests/__init__.py +0 -0
  292. package/.next/standalone/sdk/python/tests/conftest.py +169 -0
  293. package/.next/standalone/sdk/python/tests/integrations/__init__.py +6 -0
  294. package/.next/standalone/sdk/python/tests/integrations/test_crewai.py +1299 -0
  295. package/.next/standalone/sdk/python/tests/integrations/test_langchain.py +2338 -0
  296. package/.next/standalone/sdk/python/tests/integrations/test_llama_index.py +1576 -0
  297. package/.next/standalone/sdk/python/tests/integrations/test_pydantic_ai.py +1102 -0
  298. package/.next/standalone/sdk/python/tests/test_context.py +410 -0
  299. package/.next/standalone/sdk/python/tests/test_docs.py +411 -0
  300. package/.next/standalone/sdk/python/tests/test_durability.py +1352 -0
  301. package/.next/standalone/sdk/python/tests/test_encoding.py +571 -0
  302. package/.next/standalone/sdk/python/tests/test_integrations.py +1127 -0
  303. package/.next/standalone/sdk/python/tests/test_no_customer_identifiers.py +214 -0
  304. package/.next/standalone/sdk/python/tests/test_packaging.py +185 -0
  305. package/.next/standalone/sdk/python/tests/test_resolver_umbrella.py +178 -0
  306. package/.next/standalone/sdk/python/tests/test_scopes.py +487 -0
  307. package/.next/standalone/sdk/python/tests/test_sdk.py +923 -0
  308. package/.next/standalone/sdk/python/tests/test_server_contract.py +650 -0
  309. package/.next/standalone/sdk/python/tests/test_site_docs.py +371 -0
  310. package/.next/standalone/sdk/python/tests/test_skill_snippets.py +165 -0
  311. package/.next/standalone/sdk/python/tests/test_spool_contract.py +287 -0
  312. package/.next/standalone/sdk/python/tests/test_spool_creation.py +360 -0
  313. package/.next/standalone/sdk/python/tests/test_wire_format.py +279 -0
  314. package/.next/standalone/sdk/python/tests/test_zero_dependencies.py +378 -0
  315. package/.next/standalone/sdk/python/uv.lock +6005 -0
  316. package/.next/standalone/server.js +1 -1
  317. package/README.md +77 -21
  318. package/bin/failproofai.mjs +145 -11
  319. package/dist/cli.mjs +4482 -2205
  320. package/dist/index.js +5 -5
  321. package/dist/worker.mjs +2399 -1978
  322. package/package.json +29 -22
  323. package/policy-pack/SHA256SUMS +2 -0
  324. package/policy-pack/failproofai-pack.json +732 -0
  325. package/policy-pack/failproofai-pack.mjs +2017 -0
  326. package/scripts/build-policy-pack.mjs +121 -0
  327. package/scripts/changelog-section.py +77 -0
  328. package/scripts/prune-standalone.mjs +3 -0
  329. package/scripts/python-version.py +232 -0
  330. package/scripts/translate-docs/cli.ts +21 -0
  331. package/scripts/translate-docs/mintlify-nav.ts +85 -13
  332. package/scripts/translate-docs/translator.ts +66 -11
  333. package/src/audit/cache.ts +40 -3
  334. package/src/audit/cli.ts +1 -1
  335. package/src/audit/index.ts +1 -1
  336. package/src/audit/replay.ts +89 -3
  337. package/src/audit/report.ts +5 -5
  338. package/src/audit/schedule-cli.ts +49 -53
  339. package/src/hooks/builtin-policies.ts +217 -579
  340. package/src/hooks/cloud-enrollment-cli.ts +66 -27
  341. package/src/hooks/cloud-managed-policies.ts +6 -1
  342. package/src/hooks/configure-wizard.ts +1 -1
  343. package/src/hooks/custom-hooks-loader.ts +89 -6
  344. package/src/hooks/fp-home.ts +92 -4
  345. package/src/hooks/fp-reset.ts +26 -2
  346. package/src/hooks/handler.ts +181 -11
  347. package/src/hooks/harness-cli.ts +79 -25
  348. package/src/hooks/hook-activity-store.ts +13 -3
  349. package/src/hooks/install-prompt.ts +25 -3
  350. package/src/hooks/manager.ts +418 -172
  351. package/src/hooks/migrations.ts +2 -2
  352. package/src/hooks/pack-cli.ts +505 -0
  353. package/src/hooks/pack-failclosed.ts +189 -0
  354. package/src/hooks/pack-manifest.ts +344 -0
  355. package/src/hooks/pack-store.ts +930 -0
  356. package/src/hooks/policy-catalog.ts +535 -0
  357. package/src/hooks/policy-evaluator.ts +24 -16
  358. package/src/hooks/policy-presets.ts +4 -2
  359. package/src/hooks/policy-registry.ts +8 -2
  360. package/src/hooks/policy-types.ts +26 -0
  361. package/src/hooks/session-pause-cli.ts +33 -9
  362. package/src/hooks/tui.ts +570 -0
  363. package/src/hooks/types.ts +15 -1
  364. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0_usx0j._.js +0 -3
  365. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0_zk340._.js +0 -3
  366. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0agdw2a._.js +0 -3
  367. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0d1u_na._.js +0 -3
  368. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0oy4kxb._.js +0 -3
  369. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0zf034w._.js +0 -3
  370. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1tvn4wf._.js +0 -3
  371. package/.next/standalone/.next/server/chunks/_0lxbzdq._.js +0 -3
  372. package/.next/standalone/.next/server/chunks/_1ger0a2._.js +0 -3
  373. package/.next/standalone/.next/server/chunks/_1zuiiy3._.js +0 -3
  374. package/.next/standalone/.next/server/chunks/node_modules_posthog-node_dist_entrypoints_index_node_mjs_1iiwm5e._.js +0 -3
  375. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0kc8_v5._.js +0 -4
  376. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0kgy9da._.js +0 -4
  377. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0mhqk5a._.js +0 -4
  378. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0pltpn8._.js +0 -4
  379. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0qkcyf-._.js +0 -4
  380. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0w0x_ok._.js +0 -4
  381. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1_dfocn._.js +0 -4
  382. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1bfex62._.js +0 -5
  383. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1kzapg5._.js +0 -6
  384. package/.next/standalone/.next/static/chunks/36fw6yk1s6x_f.js +0 -1
  385. package/.next/standalone/.next/static/chunks/3tzq0260n17ae.js +0 -6
  386. package/.next/standalone/.next/static/chunks/3wq9x0c-7ld5d.css +0 -2
  387. /package/.next/standalone/.next/static/{ZklXxyA5ygsWSQz2u_zii → nwbyWT06Ud0XdXOrVJyvS}/_buildManifest.js +0 -0
  388. /package/.next/standalone/.next/static/{ZklXxyA5ygsWSQz2u_zii → nwbyWT06Ud0XdXOrVJyvS}/_clientMiddlewareManifest.js +0 -0
  389. /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)