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,814 @@
1
+ from __future__ import annotations
2
+
3
+ import json
4
+
5
+ import httpx
6
+ import respx
7
+
8
+ from fp_cli import config
9
+ from fp_cli.app import app
10
+
11
+ BASE = "http://dash.test"
12
+
13
+
14
+ # --- auth commands ----------------------------------------------------------
15
+
16
+
17
+ @respx.mock
18
+ def test_login_flow_persists_token(home, runner):
19
+ respx.post(f"{BASE}/api/auth/otp/request").mock(return_value=httpx.Response(200, json={"ok": True}))
20
+ respx.post(f"{BASE}/api/auth/otp/verify").mock(
21
+ return_value=httpx.Response(
22
+ 200,
23
+ json={"user": {"id": "u1", "email": "me@test"}, "expires_in_secs": 3600},
24
+ headers={"set-cookie": "ae_session=tok-xyz; Path=/"},
25
+ )
26
+ )
27
+ result = runner.invoke(
28
+ app, ["--base-url", BASE, "login", "--email", "me@test"], input="123456\n"
29
+ )
30
+ assert result.exit_code == 0, result.output
31
+ assert config.load_config().session_token == "tok-xyz"
32
+
33
+
34
+ @respx.mock
35
+ def test_login_insecure_persists_flag(home, runner):
36
+ respx.post(f"{BASE}/api/auth/otp/request").mock(return_value=httpx.Response(200, json={"ok": True}))
37
+ respx.post(f"{BASE}/api/auth/otp/verify").mock(
38
+ return_value=httpx.Response(
39
+ 200,
40
+ json={"user": {"id": "u1", "email": "me@test"}, "expires_in_secs": 3600},
41
+ headers={"set-cookie": "ae_session=tok-xyz; Path=/"},
42
+ )
43
+ )
44
+ result = runner.invoke(
45
+ app, ["--base-url", BASE, "--insecure", "login", "--email", "me@test"], input="123456\n"
46
+ )
47
+ assert result.exit_code == 0, result.output
48
+ saved = config.load_config()
49
+ assert saved.session_token == "tok-xyz"
50
+ assert saved.insecure is True # remembered, so later commands skip TLS verification too
51
+
52
+
53
+ def _seed_valid_session() -> None:
54
+ config.save_config(
55
+ config.CliConfig(
56
+ base_url=BASE,
57
+ session_token="existing",
58
+ expires_at="2099-01-01T00:00:00Z",
59
+ email="me@test",
60
+ user_id="u1",
61
+ org="acme",
62
+ )
63
+ )
64
+
65
+
66
+ @respx.mock
67
+ def test_login_already_signed_in_short_circuits(home, runner):
68
+ _seed_valid_session()
69
+ req = respx.post(f"{BASE}/api/auth/otp/request").mock(
70
+ return_value=httpx.Response(200, json={"ok": True})
71
+ )
72
+ result = runner.invoke(app, ["--base-url", BASE, "login"])
73
+ assert result.exit_code == 0, result.output
74
+ assert "already signed in" in (result.stderr or "")
75
+ assert not req.called # short-circuited — no code requested
76
+ assert config.load_config().session_token == "existing" # session untouched
77
+
78
+
79
+ @respx.mock
80
+ def test_login_json_already_signed_in(home, runner):
81
+ _seed_valid_session()
82
+ result = runner.invoke(app, ["--base-url", BASE, "--json", "login"])
83
+ assert result.exit_code == 0, result.output
84
+ payload = json.loads(result.stdout)
85
+ assert payload == {
86
+ "logged_in": True,
87
+ "email": "me@test",
88
+ "org": "acme",
89
+ "already_signed_in": True,
90
+ }
91
+
92
+
93
+ @respx.mock
94
+ def test_login_force_reauthenticates_when_signed_in(home, runner):
95
+ _seed_valid_session()
96
+ req = respx.post(f"{BASE}/api/auth/otp/request").mock(
97
+ return_value=httpx.Response(200, json={"ok": True})
98
+ )
99
+ respx.post(f"{BASE}/api/auth/otp/verify").mock(
100
+ return_value=httpx.Response(
101
+ 200,
102
+ json={"user": {"id": "u1", "email": "me@test"}, "expires_in_secs": 3600},
103
+ headers={"set-cookie": "ae_session=newtok; Path=/"},
104
+ )
105
+ )
106
+ result = runner.invoke(
107
+ app, ["--base-url", BASE, "login", "--force", "--email", "me@test"], input="123456\n"
108
+ )
109
+ assert result.exit_code == 0, result.output
110
+ assert req.called # --force bypassed the short-circuit and re-authenticated
111
+ assert config.load_config().session_token == "newtok"
112
+
113
+
114
+ @respx.mock
115
+ def test_login_expired_session_proceeds(home, runner):
116
+ config.save_config(
117
+ config.CliConfig(
118
+ base_url=BASE,
119
+ session_token="oldtok",
120
+ expires_at="2020-01-01T00:00:00Z", # expired
121
+ email="me@test",
122
+ )
123
+ )
124
+ req = respx.post(f"{BASE}/api/auth/otp/request").mock(
125
+ return_value=httpx.Response(200, json={"ok": True})
126
+ )
127
+ respx.post(f"{BASE}/api/auth/otp/verify").mock(
128
+ return_value=httpx.Response(
129
+ 200,
130
+ json={"user": {"id": "u1", "email": "me@test"}, "expires_in_secs": 3600},
131
+ headers={"set-cookie": "ae_session=freshtok; Path=/"},
132
+ )
133
+ )
134
+ result = runner.invoke(app, ["--base-url", BASE, "login", "--email", "me@test"], input="123456\n")
135
+ assert result.exit_code == 0, result.output
136
+ assert req.called # expired → login proceeds normally
137
+ assert config.load_config().session_token == "freshtok"
138
+
139
+
140
+ @respx.mock
141
+ def test_secure_flag_overrides_saved_insecure(home, runner):
142
+ # Saved config has insecure=True; an explicit --secure must turn verification back on.
143
+ config.save_config(config.CliConfig(base_url=BASE, insecure=True))
144
+ respx.post(f"{BASE}/api/auth/otp/request").mock(return_value=httpx.Response(200, json={"ok": True}))
145
+ respx.post(f"{BASE}/api/auth/otp/verify").mock(
146
+ return_value=httpx.Response(
147
+ 200,
148
+ json={"user": {"id": "u1", "email": "me@test"}, "expires_in_secs": 3600},
149
+ headers={"set-cookie": "ae_session=tok; Path=/"},
150
+ )
151
+ )
152
+ result = runner.invoke(app, ["--secure", "login", "--email", "me@test"], input="123456\n")
153
+ assert result.exit_code == 0, result.output
154
+ assert config.load_config().insecure is False # explicit --secure beat the stored True
155
+
156
+
157
+ @respx.mock
158
+ def test_logout_clears_token(logged_in, runner):
159
+ respx.post(f"{BASE}/api/auth/logout").mock(return_value=httpx.Response(200, json={}))
160
+ result = runner.invoke(app, ["logout"])
161
+ assert result.exit_code == 0
162
+ assert config.load_config().session_token is None
163
+
164
+
165
+ @respx.mock
166
+ def test_whoami_json(logged_in, runner):
167
+ respx.get(f"{BASE}/api/auth/session").mock(
168
+ return_value=httpx.Response(200, json={"id": "u1", "email": "me@test", "permissions": ["events:read"]})
169
+ )
170
+ result = runner.invoke(app, ["--json", "whoami"])
171
+ assert result.exit_code == 0
172
+ assert json.loads(result.stdout)["email"] == "me@test"
173
+
174
+
175
+ def test_whoami_not_logged_in(home, runner):
176
+ result = runner.invoke(app, ["--json", "whoami"])
177
+ assert result.exit_code == 0
178
+ assert json.loads(result.stdout)["logged_in"] is False
179
+
180
+
181
+ # --- exit-code contract -----------------------------------------------------
182
+
183
+
184
+ def test_not_logged_in_exits_4(home, runner):
185
+ # URL is set (via flag); the missing token is what triggers exit 4.
186
+ result = runner.invoke(app, ["--base-url", BASE, "events"])
187
+ assert result.exit_code == 4
188
+
189
+
190
+ def test_no_base_url_defaults_to_hosted(home, runner):
191
+ # No --base-url, no env, no saved config -> the CLI defaults to the hosted
192
+ # dashboard (config.DEFAULT_BASE_URL) rather than erroring. With no token it
193
+ # then fails on auth (exit 4, "not signed in"), NOT a usage error (exit 2)
194
+ # about a missing URL. This pins that the default kicks in.
195
+ result = runner.invoke(app, ["sessions"])
196
+ assert result.exit_code == 4
197
+
198
+
199
+ def test_default_base_url_is_the_hosted_product():
200
+ # The default the CLI falls back to is the hosted dashboard.
201
+ from fp_cli import config
202
+ from fp_cli._context import AppState, resolved_base_url
203
+
204
+ assert config.DEFAULT_BASE_URL == "https://app.befailproof.ai"
205
+ # An AppState with no URL resolves to that default.
206
+ state = AppState(json=False, base_url=None, token=None, timeout=30.0, config=config.CliConfig())
207
+ assert resolved_base_url(state) == config.DEFAULT_BASE_URL
208
+
209
+
210
+ @respx.mock
211
+ def test_forbidden_exits_5(logged_in, runner):
212
+ respx.get(f"{BASE}/api/users").mock(
213
+ return_value=httpx.Response(403, json={"error": "forbidden"})
214
+ )
215
+ result = runner.invoke(app, ["users", "list"])
216
+ assert result.exit_code == 5
217
+
218
+
219
+ @respx.mock
220
+ def test_network_error_exits_3(logged_in, runner):
221
+ respx.get(f"{BASE}/api/sessions").mock(side_effect=httpx.ConnectError("down"))
222
+ result = runner.invoke(app, ["sessions"])
223
+ assert result.exit_code == 3
224
+
225
+
226
+ def test_bad_since_is_usage_error(logged_in, runner):
227
+ result = runner.invoke(app, ["events", "--since", "bogus"])
228
+ assert result.exit_code == 2 # click usage error
229
+
230
+
231
+ # --- query commands ---------------------------------------------------------
232
+
233
+
234
+ @respx.mock
235
+ def test_events_json(logged_in, runner):
236
+ # --session-id stays on the light feed (payload is a deliberate --full opt-in).
237
+ respx.get(f"{BASE}/api/events/summary").mock(
238
+ return_value=httpx.Response(
239
+ 200,
240
+ json={
241
+ "events": [
242
+ {
243
+ "id": 1,
244
+ "session_id": "s",
245
+ "agent_id": "a",
246
+ "event_type": "tool_use",
247
+ "ts": "t",
248
+ "summary": "slack.post",
249
+ "environment": "prod",
250
+ }
251
+ ],
252
+ "next_cursor": None,
253
+ },
254
+ )
255
+ )
256
+ result = runner.invoke(app, ["--json", "events", "--session-id", "s"])
257
+ assert result.exit_code == 0, result.output
258
+ assert json.loads(result.stdout)["events"][0]["event_type"] == "tool_use"
259
+
260
+
261
+ @respx.mock
262
+ def test_events_all_paginates(logged_in, runner):
263
+ respx.get(f"{BASE}/api/events/summary").mock(
264
+ side_effect=[
265
+ httpx.Response(
266
+ 200,
267
+ json={
268
+ "events": [
269
+ {"id": 3, "session_id": "s", "agent_id": "a", "event_type": "e", "ts": "t", "payload": {}, "environment": "p"},
270
+ {"id": 2, "session_id": "s", "agent_id": "a", "event_type": "e", "ts": "t", "payload": {}, "environment": "p"},
271
+ ],
272
+ "next_cursor": 2,
273
+ },
274
+ ),
275
+ httpx.Response(
276
+ 200,
277
+ json={
278
+ "events": [
279
+ {"id": 1, "session_id": "s", "agent_id": "a", "event_type": "e", "ts": "t", "payload": {}, "environment": "p"}
280
+ ],
281
+ "next_cursor": None,
282
+ },
283
+ ),
284
+ ]
285
+ )
286
+ result = runner.invoke(app, ["--json", "events", "--all", "--limit", "100"])
287
+ assert result.exit_code == 0, result.output
288
+ assert len(json.loads(result.stdout)["events"]) == 3
289
+
290
+
291
+ @respx.mock
292
+ def test_events_feed_routing(logged_in, runner):
293
+ """Default → light feed (payload-free); --full / --fields payload / --session-id → full feed."""
294
+ resp = httpx.Response(200, json={"events": [], "next_cursor": None})
295
+ light = respx.get(f"{BASE}/api/events/summary").mock(return_value=resp)
296
+ full = respx.get(f"{BASE}/api/events").mock(return_value=resp)
297
+
298
+ def counts():
299
+ return light.call_count, full.call_count
300
+
301
+ l, f = counts()
302
+ # bare / broad → light, never full
303
+ assert runner.invoke(app, ["--json", "events", "--env", "prod"]).exit_code == 0
304
+ assert counts() == (l + 1, f); l, f = counts()
305
+
306
+ # explicit --full → full
307
+ assert runner.invoke(app, ["--json", "events", "--full"]).exit_code == 0
308
+ assert counts() == (l, f + 1); l, f = counts()
309
+
310
+ # --fields payload (payload requested) → full
311
+ assert runner.invoke(app, ["--json", "events", "--fields", "id,payload"]).exit_code == 0
312
+ assert counts() == (l, f + 1); l, f = counts()
313
+
314
+ # --session-id stays on the LIGHT feed (fast session timeline; payload is a --full opt-in)
315
+ assert runner.invoke(app, ["--json", "events", "--session-id", "run-1"]).exit_code == 0
316
+ assert counts() == (l + 1, f); l, f = counts()
317
+
318
+ # --full + --session-id → full (bounded raw-payload read)
319
+ assert runner.invoke(app, ["--json", "events", "--full", "--session-id", "run-1"]).exit_code == 0
320
+ assert counts() == (l, f + 1); l, f = counts()
321
+
322
+ # --fields summary (light-only field) stays on the light feed
323
+ assert runner.invoke(app, ["--json", "events", "--fields", "id,summary"]).exit_code == 0
324
+ assert counts() == (l + 1, f)
325
+
326
+
327
+ @respx.mock
328
+ def test_events_empty_with_filter_shows_recheck_hint(logged_in, runner):
329
+ # A filtered run that matches nothing exits 0 (not an error) and nudges the user to
330
+ # re-check the value, pointing at the facet that lists valid values.
331
+ respx.get(f"{BASE}/api/events/summary").mock(
332
+ return_value=httpx.Response(200, json={"events": [], "next_cursor": None})
333
+ )
334
+ result = runner.invoke(app, ["events", "--env", "xyz"])
335
+ assert result.exit_code == 0, result.output
336
+ assert "no events match these filters" in result.output # the box (stdout)
337
+ assert "double-check" in result.stderr
338
+ assert "--env" in result.stderr
339
+ assert "fp list envs" in result.stderr # facet discovery hint
340
+
341
+
342
+ @respx.mock
343
+ def test_events_empty_no_filter_no_hint(logged_in, runner):
344
+ # A bare run with 0 rows is a genuinely empty window, not a typo — no recheck nudge.
345
+ respx.get(f"{BASE}/api/events/summary").mock(
346
+ return_value=httpx.Response(200, json={"events": [], "next_cursor": None})
347
+ )
348
+ result = runner.invoke(app, ["events"])
349
+ assert result.exit_code == 0, result.output
350
+ assert "no events in this window" in result.output
351
+ assert "double-check" not in result.stderr
352
+
353
+
354
+ @respx.mock
355
+ def test_events_empty_json_has_no_hint(logged_in, runner):
356
+ # --json stays clean: the recheck nudge is stderr chrome, never on the JSON stdout.
357
+ respx.get(f"{BASE}/api/events/summary").mock(
358
+ return_value=httpx.Response(200, json={"events": [], "next_cursor": None})
359
+ )
360
+ result = runner.invoke(app, ["--json", "events", "--env", "xyz"])
361
+ assert result.exit_code == 0, result.output
362
+ assert json.loads(result.stdout) == {"events": [], "next_cursor": None}
363
+ assert "double-check" not in result.stderr
364
+
365
+
366
+ @respx.mock
367
+ def test_sessions_empty_with_filter_shows_recheck_hint(logged_in, runner):
368
+ # Same 0-result nudge on sessions: filtered run matching nothing exits 0 + names filters.
369
+ respx.get(f"{BASE}/api/sessions").mock(
370
+ return_value=httpx.Response(200, json={"sessions": [], "next_cursor": None})
371
+ )
372
+ result = runner.invoke(app, ["sessions", "--env", "xyz"])
373
+ assert result.exit_code == 0, result.output
374
+ assert "no sessions match these filters" in result.output # the box (stdout)
375
+ assert "double-check" in result.stderr
376
+ assert "--env" in result.stderr
377
+ assert "fp list envs" in result.stderr # facet discovery hint
378
+
379
+
380
+ @respx.mock
381
+ def test_sessions_empty_no_filter_no_hint(logged_in, runner):
382
+ # A bare run with 0 rows is a genuinely empty result, not a typo — no recheck nudge.
383
+ respx.get(f"{BASE}/api/sessions").mock(
384
+ return_value=httpx.Response(200, json={"sessions": [], "next_cursor": None})
385
+ )
386
+ result = runner.invoke(app, ["sessions"])
387
+ assert result.exit_code == 0, result.output
388
+ assert "no sessions" in result.output
389
+ assert "double-check" not in result.stderr
390
+
391
+
392
+ @respx.mock
393
+ def test_evals_list_empty_with_filter_shows_recheck_hint(logged_in, runner):
394
+ # Same 0-result nudge on evals list mode.
395
+ respx.get(f"{BASE}/api/evaluations").mock(
396
+ return_value=httpx.Response(200, json={"evaluations": [], "next_cursor": None})
397
+ )
398
+ result = runner.invoke(app, ["evals", "--env", "xyz"])
399
+ assert result.exit_code == 0, result.output
400
+ assert "no evals match these filters" in result.output
401
+ assert "double-check" in result.stderr
402
+ assert "fp list envs" in result.stderr
403
+
404
+
405
+ @respx.mock
406
+ def test_evals_aggregate_empty_with_filter_shows_recheck_hint(logged_in, runner):
407
+ # The nudge also fires in aggregate mode when the slice is empty (total 0).
408
+ respx.get(f"{BASE}/api/evaluations/aggregate").mock(
409
+ return_value=httpx.Response(
410
+ 200,
411
+ json={"total": 0, "status_counts": {"done": 0, "error": 0, "timeout": 0},
412
+ "score_stats": [], "timeline": []},
413
+ )
414
+ )
415
+ result = runner.invoke(app, ["evals", "--aggregate", "--agent-id", "nope"])
416
+ assert result.exit_code == 0, result.output
417
+ assert "double-check" in result.stderr
418
+ assert "--agent-id" in result.stderr
419
+ assert "fp list agents" in result.stderr
420
+
421
+
422
+ @respx.mock
423
+ def test_evals_aggregate_nonempty_no_hint(logged_in, runner):
424
+ # A non-empty aggregate (total > 0) never shows the recheck nudge.
425
+ respx.get(f"{BASE}/api/evaluations/aggregate").mock(
426
+ return_value=httpx.Response(
427
+ 200,
428
+ json={"total": 5, "status_counts": {"done": 5, "error": 0, "timeout": 0},
429
+ "score_stats": [], "timeline": []},
430
+ )
431
+ )
432
+ result = runner.invoke(app, ["evals", "--aggregate", "--env", "prod"])
433
+ assert result.exit_code == 0, result.output
434
+ assert "double-check" not in result.stderr
435
+
436
+
437
+ @respx.mock
438
+ def test_errors_list_empty_with_filter_shows_recheck_hint(logged_in, runner):
439
+ # Same 0-result nudge on errors list mode; --error-type maps to its own facet.
440
+ respx.get(f"{BASE}/api/events/summary").mock(
441
+ return_value=httpx.Response(200, json={"events": [], "next_cursor": None})
442
+ )
443
+ result = runner.invoke(app, ["errors", "--error-type", "NopeError"])
444
+ assert result.exit_code == 0, result.output
445
+ assert "no errors match these filters" in result.output
446
+ assert "double-check" in result.stderr
447
+ assert "fp list error_types" in result.stderr
448
+
449
+
450
+ @respx.mock
451
+ def test_errors_aggregate_empty_with_filter_shows_recheck_hint(logged_in, runner):
452
+ # Aggregate 0-total with an active filter shows the nudge under the "no errors found" card.
453
+ respx.get(f"{BASE}/api/events/error_summary").mock(
454
+ return_value=httpx.Response(
455
+ 200, json={"total": 0, "sessions": 0, "agents": 0, "last_ts": None, "bins": []}
456
+ )
457
+ )
458
+ result = runner.invoke(app, ["errors", "--aggregate", "--env", "xyz"])
459
+ assert result.exit_code == 0, result.output
460
+ assert "no errors found" in result.output # the card stays
461
+ assert "double-check" in result.stderr
462
+ assert "fp list envs" in result.stderr
463
+
464
+
465
+ @respx.mock
466
+ def test_errors_aggregate_no_filter_no_hint(logged_in, runner):
467
+ # 0 errors with NO filter is genuinely clean — celebrate, don't nag.
468
+ respx.get(f"{BASE}/api/events/error_summary").mock(
469
+ return_value=httpx.Response(
470
+ 200, json={"total": 0, "sessions": 0, "agents": 0, "last_ts": None, "bins": []}
471
+ )
472
+ )
473
+ result = runner.invoke(app, ["errors", "--aggregate"])
474
+ assert result.exit_code == 0, result.output
475
+ assert "no errors found" in result.output
476
+ assert "double-check" not in result.stderr
477
+
478
+
479
+ @respx.mock
480
+ def test_evals_score_filters(logged_in, runner):
481
+ route = respx.get(f"{BASE}/api/evaluations").mock(
482
+ return_value=httpx.Response(200, json={"evaluations": [], "next_cursor": None})
483
+ )
484
+ result = runner.invoke(
485
+ app,
486
+ ["--json", "evals", "--score", "helpfulness:0.5..0.8", "--score", "x:..0.3"],
487
+ )
488
+ assert result.exit_code == 0, result.output
489
+ params = route.calls.last.request.url.params
490
+ assert params["score_filters"] == "helpfulness:0.5..0.8,x:..0.3"
491
+
492
+
493
+ def test_sessions_has_no_score_option(logged_in, runner):
494
+ # `--score` moved to `evals`; sessions must reject it (scores live on evals now).
495
+ assert runner.invoke(app, ["sessions", "--score", "helpfulness:..0.5"]).exit_code == 2
496
+
497
+
498
+ @respx.mock
499
+ def test_sessions_table(logged_in, runner):
500
+ respx.get(f"{BASE}/api/sessions").mock(
501
+ return_value=httpx.Response(
502
+ 200,
503
+ json={
504
+ "sessions": [
505
+ {
506
+ "session_id": "s",
507
+ "agent_id": "a",
508
+ "environment": "prod",
509
+ "event_count": 5,
510
+ "started_at": "2026-06-22T12:00:00Z",
511
+ "last_event_at": "2026-06-22T12:05:00Z",
512
+ "latest_evaluation": {
513
+ "evaluation_id": "e1",
514
+ "status": "done",
515
+ "scores": {"helpfulness": 0.9},
516
+ },
517
+ }
518
+ ],
519
+ "next_cursor": None,
520
+ },
521
+ )
522
+ )
523
+ result = runner.invoke(app, ["sessions"])
524
+ assert result.exit_code == 0, result.output
525
+ assert "sessions" in result.stdout # the boxed panel title
526
+ assert "done" in result.stdout # status flattened up from latest_evaluation
527
+
528
+
529
+ @respx.mock
530
+ def test_sessions_json_flattens_status_and_scores(logged_in, runner):
531
+ # Flattening: status/scores are lifted to the top level of each row for back-compat,
532
+ # while the full evaluation stays under `latest_evaluation`.
533
+ respx.get(f"{BASE}/api/sessions").mock(
534
+ return_value=httpx.Response(
535
+ 200,
536
+ json={
537
+ "sessions": [
538
+ {
539
+ "session_id": "s",
540
+ "agent_id": "a",
541
+ "environment": "prod",
542
+ "last_event_at": "2026-06-22T12:05:00Z",
543
+ "latest_evaluation": {"status": "error", "scores": {"x": 0.5}},
544
+ }
545
+ ],
546
+ "next_cursor": None,
547
+ },
548
+ )
549
+ )
550
+ result = runner.invoke(app, ["--json", "sessions"])
551
+ assert result.exit_code == 0, result.output
552
+ row = json.loads(result.stdout)["sessions"][0]
553
+ assert row["status"] == "error" # flattened to top level
554
+ assert row["scores"] == {"x": 0.5} # flattened to top level
555
+ assert row["latest_evaluation"]["status"] == "error" # nested source preserved
556
+
557
+
558
+ # ── multi-agent roster (agents column) ─────────────────────────────────────
559
+
560
+ _MULTI_AGENT_SESSION = {
561
+ "session_id": "s", "agent_id": "agent-codegen", "environment": "dev",
562
+ "last_event_at": "2026-07-16T12:05:00Z",
563
+ "agents": [
564
+ {"agent_id": "agent-codegen", "event_count": 52},
565
+ {"agent_id": "agent-linter", "event_count": 18},
566
+ {"agent_id": "agent-testgen", "event_count": 9},
567
+ ],
568
+ "latest_evaluation": None,
569
+ }
570
+
571
+
572
+ @respx.mock
573
+ def test_sessions_json_includes_agents_roster(logged_in, runner):
574
+ respx.get(f"{BASE}/api/sessions").mock(
575
+ return_value=httpx.Response(200, json={"sessions": [_MULTI_AGENT_SESSION], "next_cursor": None})
576
+ )
577
+ result = runner.invoke(app, ["--json", "sessions"])
578
+ assert result.exit_code == 0, result.output
579
+ row = json.loads(result.stdout)["sessions"][0]
580
+ # the full nested roster (agent_id + event_count) is carried through verbatim
581
+ assert row["agents"] == _MULTI_AGENT_SESSION["agents"]
582
+
583
+
584
+ @respx.mock
585
+ def test_sessions_table_badges_multi_agent(logged_in, runner):
586
+ respx.get(f"{BASE}/api/sessions").mock(
587
+ return_value=httpx.Response(200, json={"sessions": [_MULTI_AGENT_SESSION], "next_cursor": None})
588
+ )
589
+ result = runner.invoke(app, ["sessions"])
590
+ assert result.exit_code == 0, result.output
591
+ assert "+2" in result.stdout # 3 agents → +2 badge
592
+ assert "1 multi-agent" in result.stderr # footer count
593
+ assert "fp sessions --agents" in result.stderr # footer hint
594
+
595
+
596
+ @respx.mock
597
+ def test_sessions_agents_flag_expands_roster(logged_in, runner):
598
+ respx.get(f"{BASE}/api/sessions").mock(
599
+ return_value=httpx.Response(200, json={"sessions": [_MULTI_AGENT_SESSION], "next_cursor": None})
600
+ )
601
+ result = runner.invoke(app, ["sessions", "--agents"])
602
+ assert result.exit_code == 0, result.output
603
+ # the roster is expanded → the other agents' names + event counts are now visible
604
+ assert "agent-linter" in result.stdout and "agent-testgen" in result.stdout
605
+ assert "18 ev" in result.stdout
606
+
607
+
608
+ @respx.mock
609
+ def test_sessions_single_agent_no_badge(logged_in, runner):
610
+ respx.get(f"{BASE}/api/sessions").mock(
611
+ return_value=httpx.Response(200, json={
612
+ "sessions": [{
613
+ "session_id": "s", "agent_id": "solo", "environment": "dev",
614
+ "last_event_at": "2026-07-16T12:05:00Z",
615
+ "agents": [{"agent_id": "solo", "event_count": 5}],
616
+ "latest_evaluation": None,
617
+ }],
618
+ "next_cursor": None,
619
+ })
620
+ )
621
+ result = runner.invoke(app, ["sessions"])
622
+ assert result.exit_code == 0, result.output
623
+ assert "+" not in result.stdout # single agent → no badge
624
+ assert "multi-agent" not in result.stderr # footer omits the segment
625
+
626
+
627
+ @respx.mock
628
+ def test_sessions_unevaluated_row_blank_status(logged_in, runner):
629
+ # A session never evaluated → latest_evaluation null → blank status (no crash).
630
+ respx.get(f"{BASE}/api/sessions").mock(
631
+ return_value=httpx.Response(
632
+ 200,
633
+ json={
634
+ "sessions": [
635
+ {
636
+ "session_id": "s",
637
+ "agent_id": "a",
638
+ "environment": "prod",
639
+ "last_event_at": "2026-06-22T12:05:00Z",
640
+ "latest_evaluation": None,
641
+ }
642
+ ],
643
+ "next_cursor": None,
644
+ },
645
+ )
646
+ )
647
+ result = runner.invoke(app, ["--json", "sessions"])
648
+ assert result.exit_code == 0, result.output
649
+ row = json.loads(result.stdout)["sessions"][0]
650
+ assert row["status"] == ""
651
+ assert row["scores"] is None
652
+ assert row["latest_evaluation"] is None
653
+
654
+
655
+ def test_version_command(home, runner):
656
+ from fp_cli import __version__
657
+
658
+ result = runner.invoke(app, ["version"])
659
+ assert result.exit_code == 0
660
+ assert __version__ in result.stdout # shown in the branded box
661
+
662
+
663
+ def test_version_json_global_form(home, runner):
664
+ # JSON via the GLOBAL --json (before the command), per the global option format.
665
+ from fp_cli import __version__
666
+
667
+ result = runner.invoke(app, ["--json", "version"])
668
+ assert result.exit_code == 0, result.output
669
+ assert json.loads(result.stdout) == {"version": __version__}
670
+
671
+
672
+ def test_version_json_after_command_is_rejected(home, runner):
673
+ # `--json` AFTER the command is NOT accepted — globals come before (usage error).
674
+ result = runner.invoke(app, ["version", "--json"])
675
+ assert result.exit_code == 2
676
+
677
+
678
+ def test_help_command(home, runner):
679
+ result = runner.invoke(app, ["help"])
680
+ assert result.exit_code == 0
681
+ out = result.stdout
682
+ assert "Commands" in out # the grouped top-level panel
683
+ # the four purpose groups, in order
684
+ for g in ("ESSENTIALS", "OBSERVE", "MANAGE", "TOOLS"):
685
+ assert g in out
686
+ assert out.index("ESSENTIALS") < out.index("OBSERVE") < out.index("MANAGE") < out.index("TOOLS")
687
+ assert "sessions" in out and "EXAMPLES" in out
688
+
689
+
690
+ def test_insecure_threads_verify_into_client_context():
691
+ from fp_cli._context import AppState, build_context, require_auth
692
+
693
+ cfg = config.CliConfig(base_url=BASE, session_token="t", expires_at="2999-01-01T00:00:00Z")
694
+ insecure = AppState(json=False, base_url=BASE, token="t", timeout=30.0, config=cfg, insecure=True)
695
+ assert require_auth(insecure).verify is False
696
+ assert build_context(insecure).verify is False
697
+
698
+ secure = AppState(json=False, base_url=BASE, token="t", timeout=30.0, config=cfg, insecure=False)
699
+ assert require_auth(secure).verify is True
700
+
701
+
702
+ # --- new filters, --fields projection, and -h -------------------------------
703
+
704
+
705
+ @respx.mock
706
+ def test_evals_agent_id_and_score_filters(logged_in, runner):
707
+ route = respx.get(f"{BASE}/api/evaluations").mock(
708
+ return_value=httpx.Response(200, json={"evaluations": [], "next_cursor": None})
709
+ )
710
+ result = runner.invoke(
711
+ app, ["--json", "evals", "--agent-id", "bot-1", "--score", "helpfulness:..0.5"]
712
+ )
713
+ assert result.exit_code == 0, result.output
714
+ params = route.calls.last.request.url.params
715
+ assert params["agent_id"] == "bot-1"
716
+ assert params["score_filters"] == "helpfulness:..0.5"
717
+ assert "latest_per_session" not in params # not deduped
718
+
719
+
720
+ @respx.mock
721
+ def test_fields_projection_json(logged_in, runner):
722
+ # --fields without `payload` stays on the light feed.
723
+ respx.get(f"{BASE}/api/events/summary").mock(
724
+ return_value=httpx.Response(
725
+ 200,
726
+ json={
727
+ "events": [
728
+ {"id": 1, "session_id": "s", "agent_id": "a", "event_type": "tool_use", "ts": "t", "summary": "slack.post", "environment": "prod"}
729
+ ],
730
+ "next_cursor": None,
731
+ },
732
+ )
733
+ )
734
+ result = runner.invoke(app, ["--json", "events", "--fields", "id,event_type"])
735
+ assert result.exit_code == 0, result.output
736
+ assert json.loads(result.stdout)["events"][0] == {"id": 1, "event_type": "tool_use"}
737
+
738
+
739
+ def test_fields_unknown_is_usage_error(logged_in, runner):
740
+ result = runner.invoke(app, ["--json", "events", "--fields", "bogus"])
741
+ assert result.exit_code == 2 # BadParameter lists the valid fields
742
+
743
+
744
+ def test_dash_h_alias_shows_help(home, runner):
745
+ result = runner.invoke(app, ["-h"])
746
+ assert result.exit_code == 0
747
+ assert "Commands" in result.stdout and "sessions" in result.stdout
748
+ assert "ESSENTIALS" in result.stdout # the grouped top-level help, via the rich_format_help override
749
+
750
+
751
+ def test_subcommand_dash_h(home, runner):
752
+ result = runner.invoke(app, ["events", "-h"])
753
+ assert result.exit_code == 0
754
+ assert "--fields" in result.stdout
755
+
756
+
757
+ @respx.mock
758
+ def test_all_reports_a_resumable_cursor_when_it_stops_on_the_limit(logged_in, runner):
759
+ """`--all` truncated silently and then asserted it had not.
760
+
761
+ `--limit` defaults to 50, so `fp --json events --session-id X --all` — the
762
+ exact line the docs give for reading a whole session — made ONE request,
763
+ returned 50 rows out of however many exist, and emitted
764
+ `"next_cursor": null`, which positively states the feed is exhausted. A
765
+ script or an agent reading that JSON concludes the session had 50 events and
766
+ has nothing to resume from. The CLI had the live cursor in hand at that
767
+ moment and threw it away.
768
+ """
769
+ def handler(request):
770
+ limit = int(dict(request.url.params).get("limit", 50))
771
+ return httpx.Response(
772
+ 200,
773
+ json={
774
+ "events": [
775
+ {"id": i, "session_id": "s", "agent_id": "a", "event_type": "e",
776
+ "ts": "t", "environment": "p"}
777
+ for i in range(limit)
778
+ ],
779
+ "next_cursor": "more-to-come",
780
+ },
781
+ )
782
+
783
+ route = respx.get(f"{BASE}/api/events/summary").mock(side_effect=handler)
784
+ result = runner.invoke(app, ["--json", "events", "--session-id", "run-001", "--all"])
785
+
786
+ assert result.exit_code == 0, result.output
787
+ body = json.loads(result.stdout)
788
+ assert len(body["events"]) == 50
789
+ assert route.call_count == 1
790
+ assert body["next_cursor"] == "more-to-come", (
791
+ "a walk that stopped on --limit must hand back somewhere to resume, not null"
792
+ )
793
+
794
+
795
+ @respx.mock
796
+ def test_all_reports_no_cursor_when_the_feed_really_is_exhausted(logged_in, runner):
797
+ """The other half: a complete walk must still say so."""
798
+ respx.get(f"{BASE}/api/events/summary").mock(
799
+ return_value=httpx.Response(
800
+ 200,
801
+ json={
802
+ "events": [
803
+ {"id": 1, "session_id": "s", "agent_id": "a", "event_type": "e",
804
+ "ts": "t", "environment": "p"}
805
+ ],
806
+ "next_cursor": None,
807
+ },
808
+ )
809
+ )
810
+ result = runner.invoke(app, ["--json", "events", "--all", "--limit", "100"])
811
+ assert result.exit_code == 0, result.output
812
+ body = json.loads(result.stdout)
813
+ assert len(body["events"]) == 1
814
+ assert body["next_cursor"] is None