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,98 @@
1
+ from __future__ import annotations
2
+
3
+ import json
4
+ from datetime import datetime, timezone
5
+
6
+ import httpx
7
+ import pytest
8
+ import respx
9
+
10
+ from fp_cli import auth, config
11
+ from fp_cli.errors import ApiError, AuthError, NetworkError
12
+
13
+ BASE = "http://dash.test"
14
+
15
+
16
+ @respx.mock
17
+ def test_request_otp_ok():
18
+ route = respx.post(f"{BASE}/api/auth/otp/request").mock(
19
+ return_value=httpx.Response(200, json={"ok": True})
20
+ )
21
+ auth.request_otp(BASE, "me@test")
22
+ assert route.called
23
+ assert json.loads(route.calls.last.request.read()) == {"email": "me@test"}
24
+ # Marks a CLI login so the server emails the paste-into-terminal OTP variant.
25
+ assert route.calls.last.request.headers["X-AgentEye-Client"] == "cli"
26
+
27
+
28
+ @respx.mock
29
+ def test_request_otp_server_error_raises():
30
+ respx.post(f"{BASE}/api/auth/otp/request").mock(return_value=httpx.Response(502))
31
+ with pytest.raises(ApiError):
32
+ auth.request_otp(BASE, "me@test")
33
+
34
+
35
+ @respx.mock
36
+ def test_verify_otp_reads_token_from_set_cookie_not_body():
37
+ # The dashboard returns user + expires_in_secs in the body and the token only
38
+ # in the Set-Cookie header — verify we read it from the cookie.
39
+ respx.post(f"{BASE}/api/auth/otp/verify").mock(
40
+ return_value=httpx.Response(
41
+ 200,
42
+ json={"user": {"id": "u1", "email": "me@test"}, "expires_in_secs": 3600},
43
+ headers={"set-cookie": "ae_session=tok-xyz; HttpOnly; Path=/; Max-Age=3600"},
44
+ )
45
+ )
46
+ token, expires_in, user = auth.verify_otp(BASE, "me@test", "123456")
47
+ assert token == "tok-xyz"
48
+ assert expires_in == 3600
49
+ assert user["email"] == "me@test"
50
+
51
+
52
+ @respx.mock
53
+ def test_verify_otp_wrong_code_is_auth_error():
54
+ respx.post(f"{BASE}/api/auth/otp/verify").mock(return_value=httpx.Response(401, json={}))
55
+ with pytest.raises(AuthError):
56
+ auth.verify_otp(BASE, "me@test", "000000")
57
+
58
+
59
+ @respx.mock
60
+ def test_verify_otp_without_cookie_is_auth_error():
61
+ respx.post(f"{BASE}/api/auth/otp/verify").mock(
62
+ return_value=httpx.Response(200, json={"user": {}, "expires_in_secs": 3600})
63
+ )
64
+ with pytest.raises(AuthError):
65
+ auth.verify_otp(BASE, "me@test", "123456")
66
+
67
+
68
+ @respx.mock
69
+ def test_verify_otp_network_error():
70
+ respx.post(f"{BASE}/api/auth/otp/verify").mock(side_effect=httpx.ConnectError("down"))
71
+ with pytest.raises(NetworkError):
72
+ auth.verify_otp(BASE, "me@test", "123456")
73
+
74
+
75
+ def test_persist_session_computes_expiry_and_writes_0600(home):
76
+ cfg = config.CliConfig()
77
+ now = datetime(2026, 5, 25, 12, 0, 0, tzinfo=timezone.utc)
78
+ auth.persist_session(cfg, BASE, "tok", 3600, {"email": "me@test", "id": "u1"}, now=now)
79
+
80
+ reloaded = config.load_config()
81
+ assert reloaded.session_token == "tok"
82
+ assert reloaded.expires_at == "2026-05-25T13:00:00Z"
83
+ assert reloaded.email == "me@test"
84
+ assert reloaded.user_id == "u1"
85
+ assert reloaded.base_url == BASE
86
+ assert not config.is_expired(reloaded, now=now)
87
+
88
+
89
+ @respx.mock
90
+ def test_logout_is_best_effort_on_network_error():
91
+ respx.post(f"{BASE}/api/auth/logout").mock(side_effect=httpx.ConnectError("down"))
92
+ # Must not raise even though the server is unreachable.
93
+ auth.logout(BASE, "tok")
94
+
95
+
96
+ def test_logout_noop_without_token():
97
+ # No registered routes — if it tried to call out, respx would complain.
98
+ auth.logout(BASE, None)
@@ -0,0 +1,272 @@
1
+ """API-key auth mode: precedence, the empty-string rule, what goes on the wire, and
2
+ what never touches disk.
3
+
4
+ The wire assertions here are deliberately NEGATIVE as well as positive. "The bearer
5
+ header is set" passes just as happily when the CLI *also* attaches the operator's
6
+ `ae_session` cookie — which would hand a human's session to `/v1` from a CI box, and
7
+ no positive-only test would ever notice.
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ import json
13
+
14
+ import httpx
15
+ import pytest
16
+ import respx
17
+
18
+ from fp_cli import config
19
+ from fp_cli._context import AppState, AuthMode, resolve_auth
20
+ from fp_cli.app import app
21
+
22
+ BASE = "http://dash.test"
23
+ KEY = "ak_live_abc123"
24
+
25
+
26
+ def _mock_keys(mock):
27
+ """Mock BOTH surfaces so a wrong-mode request is a wrong URL, not a network error."""
28
+ return (
29
+ mock.get(f"{BASE}/api/keys").mock(return_value=httpx.Response(200, json=[])),
30
+ mock.get(f"{BASE}/v1/keys").mock(return_value=httpx.Response(200, json=[])),
31
+ )
32
+
33
+
34
+ def _run_keys_list(runner, argv, env=None):
35
+ """Run `keys list` with both surfaces mocked → (result, api_route, v1_route)."""
36
+ with respx.mock(assert_all_called=False) as mock:
37
+ api_route, v1_route = _mock_keys(mock)
38
+ result = runner.invoke(app, [*argv, "--json", "keys", "list"], env=env or {})
39
+ return result, api_route, v1_route
40
+
41
+
42
+ # --- precedence ladder ------------------------------------------------------
43
+
44
+
45
+ def test_api_key_flag_selects_key_mode(home, runner):
46
+ result, api_route, v1_route = _run_keys_list(runner, ["--base-url", BASE, "--api-key", KEY])
47
+ assert result.exit_code == 0, result.output
48
+ assert v1_route.called and not api_route.called
49
+
50
+
51
+ def test_token_flag_beats_api_key_env(home, runner):
52
+ # An explicit flag outranks any env var, including the key's.
53
+ result, api_route, v1_route = _run_keys_list(
54
+ runner, ["--base-url", BASE, "--token", "tok"], env={"FP_API_KEY": KEY}
55
+ )
56
+ assert result.exit_code == 0, result.output
57
+ assert api_route.called and not v1_route.called
58
+
59
+
60
+ def test_api_key_env_beats_token_env(home, runner):
61
+ # Between two env vars the KEY wins — the documented rung.
62
+ result, api_route, v1_route = _run_keys_list(
63
+ runner,
64
+ ["--base-url", BASE],
65
+ env={"FP_API_KEY": KEY, "FP_TOKEN": "tok"},
66
+ )
67
+ assert result.exit_code == 0, result.output
68
+ assert v1_route.called and not api_route.called
69
+
70
+
71
+ def test_token_env_beats_saved_session(home, runner):
72
+ config.save_config(config.CliConfig(base_url=BASE, session_token="saved-tok",
73
+ expires_at="2999-01-01T00:00:00Z"))
74
+ with respx.mock(assert_all_called=False) as mock:
75
+ api_route, _v1 = _mock_keys(mock)
76
+ result = runner.invoke(app, ["--json", "keys", "list"], env={"FP_TOKEN": "env-tok"})
77
+ assert result.exit_code == 0, result.output
78
+ assert "ae_session=env-tok" in api_route.calls.last.request.headers.get("cookie", "")
79
+
80
+
81
+ def test_api_key_env_beats_saved_session(home, runner):
82
+ config.save_config(config.CliConfig(base_url=BASE, session_token="saved-tok",
83
+ expires_at="2999-01-01T00:00:00Z"))
84
+ result, api_route, v1_route = _run_keys_list(
85
+ runner, [], env={"FP_API_KEY": KEY}
86
+ )
87
+ assert result.exit_code == 0, result.output
88
+ assert v1_route.called and not api_route.called
89
+
90
+
91
+ def test_saved_session_when_nothing_else(logged_in, runner):
92
+ result, api_route, v1_route = _run_keys_list(runner, [])
93
+ assert result.exit_code == 0, result.output
94
+ assert api_route.called and not v1_route.called
95
+
96
+
97
+ def test_no_credential_at_all_exits_4(home, runner):
98
+ with respx.mock(assert_all_called=False) as mock:
99
+ catch_all = mock.route().mock(return_value=httpx.Response(200, json=[]))
100
+ result = runner.invoke(app, ["--base-url", BASE, "keys", "list"])
101
+ assert result.exit_code == 4, result.output
102
+ assert catch_all.call_count == 0 # never opened a connection
103
+
104
+
105
+ # --- resolve_auth, unit ------------------------------------------------------
106
+
107
+
108
+ @pytest.mark.parametrize(
109
+ "kwargs,expected",
110
+ [
111
+ # flag key, flag token, env key, env token, saved token
112
+ (dict(api_key=KEY, api_key_on_cli=True, token=None, token_on_cli=False, saved_token=None),
113
+ (AuthMode.API_KEY, KEY, None)),
114
+ (dict(api_key=None, api_key_on_cli=False, token="t", token_on_cli=True, saved_token=None),
115
+ (AuthMode.SESSION, None, "t")),
116
+ (dict(api_key=KEY, api_key_on_cli=False, token="t", token_on_cli=False, saved_token=None),
117
+ (AuthMode.API_KEY, KEY, None)),
118
+ (dict(api_key=None, api_key_on_cli=False, token="t", token_on_cli=False, saved_token="s"),
119
+ (AuthMode.SESSION, None, "t")),
120
+ (dict(api_key=None, api_key_on_cli=False, token=None, token_on_cli=False, saved_token="s"),
121
+ (AuthMode.SESSION, None, "s")),
122
+ (dict(api_key=None, api_key_on_cli=False, token=None, token_on_cli=False, saved_token=None),
123
+ (AuthMode.NONE, None, None)),
124
+ # `--api-key ""` is key mode with NO credential — never a fallback to the
125
+ # saved session (mirrors the established `--token ""` rule).
126
+ (dict(api_key="", api_key_on_cli=True, token=None, token_on_cli=False, saved_token="s"),
127
+ (AuthMode.API_KEY, "", None)),
128
+ ],
129
+ )
130
+ def test_resolve_auth_precedence(kwargs, expected):
131
+ assert resolve_auth(**kwargs) == expected
132
+
133
+
134
+ def test_both_flags_is_a_usage_error():
135
+ from fp_cli import _click_compat as click
136
+
137
+ with pytest.raises(click.UsageError):
138
+ resolve_auth(api_key=KEY, api_key_on_cli=True, token="t", token_on_cli=True,
139
+ saved_token=None)
140
+
141
+
142
+ def test_both_flags_exits_2_with_zero_http_calls(logged_in, runner):
143
+ with respx.mock(assert_all_called=False) as mock:
144
+ catch_all = mock.route().mock(return_value=httpx.Response(200, json=[]))
145
+ result = runner.invoke(
146
+ app, ["--base-url", BASE, "--api-key", KEY, "--token", "tok", "keys", "list"]
147
+ )
148
+ assert result.exit_code == 2, result.output
149
+ assert catch_all.call_count == 0 # never guessed which one you meant
150
+
151
+
152
+ # --- the empty-string rule ---------------------------------------------------
153
+
154
+
155
+ def test_empty_api_key_does_not_fall_back_to_saved_session(logged_in, runner):
156
+ """`--api-key ""` (an unset CI var) must not silently act as the logged-in human."""
157
+ with respx.mock(assert_all_called=False) as mock:
158
+ catch_all = mock.route().mock(return_value=httpx.Response(200, json=[]))
159
+ result = runner.invoke(app, ["--base-url", BASE, "--api-key", "", "keys", "list"])
160
+ assert result.exit_code == 4, result.output # key mode, no credential
161
+ assert catch_all.call_count == 0
162
+
163
+
164
+ # --- what goes on the wire (both directions) ---------------------------------
165
+
166
+
167
+ def test_key_mode_sends_bearer_and_no_cookie(logged_in, runner):
168
+ # `logged_in` seeds a saved session on purpose: the cookie is available, and must
169
+ # still not be sent.
170
+ result, _api, v1_route = _run_keys_list(runner, ["--base-url", BASE, "--api-key", KEY])
171
+ assert result.exit_code == 0, result.output
172
+ headers = v1_route.calls.last.request.headers
173
+ assert headers["authorization"] == f"Bearer {KEY}"
174
+ assert "cookie" not in headers, "an API-key request must never carry ae_session"
175
+
176
+
177
+ def test_session_mode_sends_cookie_and_no_bearer(logged_in, runner):
178
+ result, api_route, _v1 = _run_keys_list(runner, [])
179
+ assert result.exit_code == 0, result.output
180
+ headers = api_route.calls.last.request.headers
181
+ assert "ae_session=tok" in headers.get("cookie", "")
182
+ assert "authorization" not in headers
183
+
184
+
185
+ # --- the org header ----------------------------------------------------------
186
+
187
+
188
+ def test_key_mode_never_sends_the_saved_org(home, runner):
189
+ config.save_config(config.CliConfig(base_url=BASE, org="human-org"))
190
+ result, _api, v1_route = _run_keys_list(runner, ["--api-key", KEY])
191
+ assert result.exit_code == 0, result.output
192
+ assert "x-agenteye-org" not in v1_route.calls.last.request.headers
193
+
194
+
195
+ def test_key_mode_sends_an_explicit_org(home, runner):
196
+ config.save_config(config.CliConfig(base_url=BASE, org="human-org"))
197
+ result, _api, v1_route = _run_keys_list(runner, ["--api-key", KEY, "--org", "acme"])
198
+ assert result.exit_code == 0, result.output
199
+ assert v1_route.calls.last.request.headers["x-agenteye-org"] == "acme"
200
+
201
+
202
+ def test_session_mode_still_sends_the_saved_org(home, runner):
203
+ config.save_config(config.CliConfig(base_url=BASE, session_token="tok",
204
+ expires_at="2999-01-01T00:00:00Z", org="human-org"))
205
+ result, api_route, _v1 = _run_keys_list(runner, [])
206
+ assert result.exit_code == 0, result.output
207
+ assert api_route.calls.last.request.headers["x-agenteye-org"] == "human-org"
208
+
209
+
210
+ # --- the key never reaches disk ---------------------------------------------
211
+
212
+
213
+ def test_api_key_is_never_persisted(home, runner):
214
+ result, _api, _v1 = _run_keys_list(runner, ["--base-url", BASE, "--api-key", KEY])
215
+ assert result.exit_code == 0, result.output
216
+ path = config.config_path()
217
+ on_disk = path.read_text() if path.exists() else ""
218
+ assert KEY not in on_disk
219
+ # And no field quietly holds it either (a new CliConfig field would be the way
220
+ # this regresses).
221
+ assert KEY not in json.dumps(config.load_config().__dict__)
222
+
223
+
224
+ def test_api_key_is_not_persisted_by_whoami(home, runner):
225
+ result = runner.invoke(app, ["--base-url", BASE, "--api-key", KEY, "--json", "whoami"])
226
+ assert result.exit_code == 0, result.output
227
+ path = config.config_path()
228
+ assert KEY not in (path.read_text() if path.exists() else "")
229
+
230
+
231
+ # --- whoami in key mode is the documented exception --------------------------
232
+
233
+
234
+ def test_whoami_key_mode_exits_0_with_honest_shape(logged_in, runner):
235
+ with respx.mock(assert_all_called=False) as mock:
236
+ catch_all = mock.route().mock(return_value=httpx.Response(200, json={}))
237
+ result = runner.invoke(
238
+ app, ["--base-url", BASE, "--api-key", KEY, "--org", "acme", "--json", "whoami"]
239
+ )
240
+ assert result.exit_code == 0, result.output
241
+ assert json.loads(result.stdout) == {
242
+ "logged_in": False,
243
+ "auth_mode": "api_key",
244
+ "active_org": "acme",
245
+ }
246
+ assert catch_all.call_count == 0 # a key has no identity to look up
247
+
248
+
249
+ def test_whoami_key_mode_reports_no_org_when_none_given(home, runner):
250
+ config.save_config(config.CliConfig(base_url=BASE, org="human-org"))
251
+ result = runner.invoke(app, ["--api-key", KEY, "--json", "whoami"])
252
+ assert result.exit_code == 0, result.output
253
+ # The SAVED org is not the key's org, so it must not be reported as active.
254
+ assert json.loads(result.stdout)["active_org"] is None
255
+
256
+
257
+ def test_whoami_human_shapes_carry_auth_mode(home, runner):
258
+ result = runner.invoke(app, ["--base-url", BASE, "--json", "whoami"])
259
+ assert result.exit_code == 0, result.output
260
+ assert json.loads(result.stdout) == {"logged_in": False, "auth_mode": "none"}
261
+
262
+
263
+ # --- AppState default --------------------------------------------------------
264
+
265
+
266
+ def test_appstate_defaults_to_none_mode():
267
+ # An AppState built by another path (tests, embedders) must never be silently
268
+ # treated as key mode.
269
+ state = AppState(json=False, base_url=None, token=None, timeout=30.0,
270
+ config=config.CliConfig())
271
+ assert state.auth_mode is AuthMode.NONE
272
+ assert state.api_key is None
@@ -0,0 +1,118 @@
1
+ """Guard the contract that ``fp_cli/_click_compat.py`` exists to hold.
2
+
3
+ Typer 0.26 vendored Click and now catches only *its* Click's exceptions. Every way
4
+ that coupling breaks is silent — the CLI imports, compiles, and passes every happy
5
+ path while typed errors exit 1 with an empty stderr and the telemetry flag catalog
6
+ quietly empties. These are the alarms.
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ import ast
12
+ import pathlib
13
+
14
+ import typer
15
+ from typer.testing import CliRunner
16
+
17
+ from fp_cli import _click_compat, analytics_registry
18
+ from fp_cli.errors import ForbiddenError, KeyModeUnsupportedError, NotFoundError
19
+
20
+ _PACKAGE = pathlib.Path(__file__).resolve().parent.parent / "fp_cli"
21
+
22
+
23
+ def test_the_package_scan_actually_has_something_to_scan():
24
+ """`_PACKAGE` is a path literal, so a package rename makes it point at nothing.
25
+
26
+ Every scanning test below then walks an empty directory, finds zero violations and
27
+ passes — the guard silently stops guarding. This is the tripwire for that: it caught
28
+ nothing during the agenteye -> fp_cli rename only because the literal was updated in
29
+ the same pass, which is exactly the kind of thing that is remembered once.
30
+ """
31
+ assert _PACKAGE.is_dir(), f"{_PACKAGE} does not exist — the scanning tests are vacuous"
32
+ modules = list(_PACKAGE.rglob("*.py"))
33
+ assert len(modules) > 20, (
34
+ f"only {len(modules)} modules found under {_PACKAGE}; the scan is not seeing the package"
35
+ )
36
+
37
+
38
+ def _click_imports(path: pathlib.Path) -> list[str]:
39
+ """Every direct `import click` / `from click import …` in one module."""
40
+ tree = ast.parse(path.read_text(encoding="utf-8"), filename=str(path))
41
+ hits = []
42
+ for node in ast.walk(tree):
43
+ if isinstance(node, ast.Import):
44
+ hits += [a.name for a in node.names if a.name == "click" or a.name.startswith("click.")]
45
+ elif isinstance(node, ast.ImportFrom):
46
+ if node.module and (node.module == "click" or node.module.startswith("click.")):
47
+ hits.append(node.module)
48
+ return hits
49
+
50
+
51
+ def test_package_never_imports_click_directly():
52
+ """The pip `click` distribution is not necessarily the Click Typer is running.
53
+
54
+ Binding to it directly is the whole bug: `raise click.UsageError(...)` from the wrong
55
+ Click is not caught by Typer's runner, so a clean exit-2 usage error becomes exit 1 with
56
+ nothing on stderr. `_click_compat` is the only module allowed to name `click`.
57
+ """
58
+ offenders = {
59
+ str(path.relative_to(_PACKAGE)): names
60
+ for path in sorted(_PACKAGE.rglob("*.py"))
61
+ if path.name != "_click_compat.py" and (names := _click_imports(path))
62
+ }
63
+ assert not offenders, (
64
+ f"import Click via `from . import _click_compat as click`, not directly: {offenders}"
65
+ )
66
+
67
+
68
+ def test_typed_errors_reach_typers_handler():
69
+ """A typed error must still be *caught* by Typer: its message on stderr, its exit code.
70
+
71
+ Asserted through a real command run rather than an isinstance check, because what matters
72
+ is the runner's behaviour — which is what silently changed under typer 0.26.
73
+ """
74
+ app = typer.Typer()
75
+
76
+ @app.command()
77
+ def boom() -> None:
78
+ raise ForbiddenError("nope", hint="ask an admin")
79
+
80
+ result = CliRunner().invoke(app, [])
81
+ assert result.exit_code == 5, result.output # not 1: uncaught would collapse to 1
82
+ assert "nope" in result.output
83
+
84
+
85
+ def test_error_exit_codes_are_distinct_per_class():
86
+ """The exit-code contract is documented in `--help` and scripted against."""
87
+ assert ForbiddenError("x").exit_code == 5
88
+ assert NotFoundError("x").exit_code == 6
89
+ # Key mode reuses exit 2 rather than adding a seventh code — the table is a scripted
90
+ # contract restated in `app.py`, `cli/skill/SKILL.md` and `enterprise-docs/cli.md`.
91
+ assert KeyModeUnsupportedError("x").exit_code == 2
92
+ assert issubclass(ForbiddenError, _click_compat.ClickException)
93
+ # Through the shim, not pip Click: bind it to the wrong Click and every key-mode
94
+ # refusal escapes uncaught as exit 1 with an empty stderr.
95
+ assert issubclass(KeyModeUnsupportedError, _click_compat.ClickException)
96
+
97
+
98
+ def test_is_option_recognises_typer_options():
99
+ """Typer's vendored Click has no `Option` class, so `isinstance` cannot answer this.
100
+
101
+ If a future Click stops setting `param_type_name`, this fails loudly instead of quietly
102
+ dropping every flag from the telemetry catalog.
103
+ """
104
+ from typer.main import get_command
105
+
106
+ from fp_cli.app import app as real_app
107
+
108
+ params = get_command(real_app).params
109
+ assert params, "the root command should declare the global options"
110
+ assert any(_click_compat.is_option(p) for p in params)
111
+ assert all(not _click_compat.is_option(p) for p in params if p.param_type_name == "argument")
112
+
113
+
114
+ def test_flag_catalog_is_populated():
115
+ """The catalog is derived by walking the real command tree; empty means the walk broke."""
116
+ _known, _leaves, flags, value_flags = analytics_registry.build()
117
+ assert "--json" in flags and "--base-url" in value_flags
118
+ assert len(flags) > 50, f"flag catalog collapsed to {len(flags)} entries"