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,496 @@
1
+ """Dev provisioning: API keys (one-time secret) and saved queries / SQL runner."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json
6
+ import re
7
+
8
+ import httpx
9
+ import pytest
10
+ import respx
11
+
12
+ from fp_cli.app import app
13
+ from fp_cli.commands.keys_cmds import PermissionTokenError, _parse_permissions
14
+
15
+
16
+ def test_audit_permissions_are_assignable_and_in_presets():
17
+ from fp_cli.permissions import ALL_PERMISSIONS, PRESETS
18
+
19
+ assert "audits:read" in ALL_PERMISSIONS
20
+ assert "audits:write" in ALL_PERMISSIONS
21
+ assert "audits:read" in PRESETS["read-only"]
22
+ assert "usage:read" in ALL_PERMISSIONS
23
+ assert "usage:read" in PRESETS["read-only"]
24
+ assert "audits:write" in PRESETS["admin"]
25
+
26
+
27
+ def test_policy_permissions_are_assignable_and_in_presets():
28
+ from fp_cli.permissions import ALL_PERMISSIONS, PRESETS
29
+
30
+ assert "policies:read" in ALL_PERMISSIONS
31
+ assert "policies:write" in ALL_PERMISSIONS
32
+ assert "policies:pull" in ALL_PERMISSIONS
33
+ assert "policies:read" in PRESETS["read-only"]
34
+ assert "policies:read" in PRESETS["standard"]
35
+ assert "policies:pull" in PRESETS["admin"]
36
+
37
+ BASE = "http://dash.test"
38
+
39
+
40
+ def test_parse_permissions_expands_and_dedupes():
41
+ # dotted actions expand to flat slug:action, across tokens, de-duplicated, order preserved
42
+ assert _parse_permissions(["events:read.add", "keys:read.create"]) == [
43
+ "events:read", "events:add", "keys:read", "keys:create"]
44
+ assert _parse_permissions(["events:read", "events:read.add"]) == ["events:read", "events:add"]
45
+ assert _parse_permissions(["events:read.add keys:read"]) == ["events:read", "events:add", "keys:read"] # whitespace in one token
46
+
47
+
48
+ @pytest.mark.parametrize("bad", [["events"], ["events:"], ["events:frobnicate"], []])
49
+ def test_parse_permissions_rejects(bad):
50
+ with pytest.raises(PermissionTokenError):
51
+ _parse_permissions(bad)
52
+
53
+
54
+ def test_parse_permission_tokens_require_nonempty_flag():
55
+ # The shared parser (in permissions.py) lets users --add/--remove be empty when asked.
56
+ from fp_cli.permissions import PermissionTokenError as PTErr
57
+ from fp_cli.permissions import parse_permission_tokens
58
+ assert parse_permission_tokens([], require_nonempty=False) == []
59
+ assert parse_permission_tokens(None, require_nonempty=False) == []
60
+ with pytest.raises(PTErr):
61
+ parse_permission_tokens([]) # default requires at least one
62
+
63
+
64
+ # --- keys -------------------------------------------------------------------
65
+
66
+
67
+ @respx.mock
68
+ def test_keys_list_json(logged_in, runner):
69
+ respx.get(f"{BASE}/api/keys").mock(
70
+ return_value=httpx.Response(
71
+ 200,
72
+ json=[{"id": "k1", "name": "ci", "permissions": ["events:add"], "created_at": "t", "revoked_at": None}],
73
+ )
74
+ )
75
+ result = runner.invoke(app, ["--json", "keys", "list"])
76
+ assert result.exit_code == 0, result.output
77
+ assert json.loads(result.stdout)["keys"][0]["id"] == "k1"
78
+
79
+
80
+ @respx.mock
81
+ def test_keys_create_generates_secret_and_posts_it(logged_in, runner):
82
+ respx.get(f"{BASE}/api/keys").mock(return_value=httpx.Response(200, json=[])) # no name collision
83
+ route = respx.post(f"{BASE}/api/keys").mock(
84
+ return_value=httpx.Response(201, json={"id": "k9", "name": "ci-bot",
85
+ "permissions": ["events:read", "events:add", "keys:read"], "created_at": "t"})
86
+ )
87
+ # compact --add tokens: `events:read.add` expands to events:read + events:add; `keys:read` too
88
+ result = runner.invoke(app, ["--json", "keys", "create", "ci-bot", "--add", "events:read.add,keys:read"])
89
+ assert result.exit_code == 0, result.output
90
+ body = json.loads(route.calls.last.request.content)
91
+ assert body["name"] == "ci-bot"
92
+ assert body["permissions"] == ["events:add", "events:read", "keys:read"] # expanded + de-duped + sorted
93
+ assert re.fullmatch(r"[0-9a-f]{64}", body["key"]) # CLI-generated 64-hex secret
94
+ assert json.loads(result.stdout)["key"] == body["key"] # secret only in the JSON `key` field
95
+
96
+
97
+ def test_keys_create_rejects_unknown_permission(logged_in, runner):
98
+ result = runner.invoke(app, ["keys", "create", "k", "--add", "orgs:admin"])
99
+ assert result.exit_code == 2 # orgs:admin is not assignable
100
+
101
+
102
+ def test_keys_create_rejects_human_only_permission(logged_in, runner):
103
+ # keys:update is human-only — rejected client-side for a key (exit 2), not a server 422.
104
+ result = runner.invoke(app, ["keys", "create", "k", "--add", "keys:update"])
105
+ assert result.exit_code == 2
106
+
107
+
108
+ def test_keys_create_rejects_malformed_token(logged_in, runner):
109
+ # missing colon / empty action → red error box, exit 2, before any mutation
110
+ assert runner.invoke(app, ["keys", "create", "k", "--add", "events"]).exit_code == 2
111
+ assert runner.invoke(app, ["keys", "create", "k", "--add", "events:"]).exit_code == 2
112
+
113
+
114
+ @respx.mock
115
+ def test_keys_create_name_collision(logged_in, runner):
116
+ respx.get(f"{BASE}/api/keys").mock(return_value=httpx.Response(200, json=[
117
+ {"id": "k1", "name": "ci-bot", "permissions": [], "created_at": "t", "revoked_at": None}]))
118
+ result = runner.invoke(app, ["--json", "keys", "create", "ci-bot", "--add", "events:read"])
119
+ assert result.exit_code == 2
120
+ assert "already exists" in json.loads(result.stdout)["error"]
121
+
122
+
123
+ def _one_key(name="ci-bot", key_id="k1", revoked=False):
124
+ return [{"id": key_id, "name": name, "permissions": ["events:add"], "created_at": "t",
125
+ "revoked_at": ("t" if revoked else None)}]
126
+
127
+
128
+ @respx.mock
129
+ def test_keys_disable_by_name_json(logged_in, runner):
130
+ respx.get(f"{BASE}/api/keys").mock(return_value=httpx.Response(200, json=_one_key()))
131
+ route = respx.post(f"{BASE}/api/keys/k1/disable").mock(return_value=httpx.Response(200, json={}))
132
+ # name is resolved to the key id; --json non-tty + --yes proceeds (auto-skip rule).
133
+ result = runner.invoke(app, ["--json", "keys", "disable", "ci-bot", "--yes"])
134
+ assert result.exit_code == 0, result.output
135
+ assert route.called # resolved "ci-bot" -> /keys/k1/disable
136
+ assert json.loads(result.stdout) == {"name": "ci-bot", "status": "disabled"}
137
+
138
+
139
+ @respx.mock
140
+ def test_keys_disable_already_disabled_is_noop(logged_in, runner):
141
+ respx.get(f"{BASE}/api/keys").mock(return_value=httpx.Response(200, json=_one_key(revoked=True)))
142
+ route = respx.post(f"{BASE}/api/keys/k1/disable").mock(return_value=httpx.Response(200, json={}))
143
+ result = runner.invoke(app, ["--json", "keys", "disable", "ci-bot"])
144
+ assert result.exit_code == 0, result.output
145
+ assert not route.called # already disabled → no destructive call
146
+ assert json.loads(result.stdout) == {"name": "ci-bot", "status": "disabled"}
147
+
148
+
149
+ @respx.mock
150
+ def test_keys_disable_not_found_exits_6(logged_in, runner):
151
+ respx.get(f"{BASE}/api/keys").mock(return_value=httpx.Response(200, json=_one_key()))
152
+ result = runner.invoke(app, ["keys", "disable", "nope", "--yes"])
153
+ assert result.exit_code == 6
154
+ assert "no key named" in (result.stderr or result.output)
155
+
156
+
157
+ @respx.mock
158
+ def test_keys_disable_forbidden_exits_5(logged_in, runner):
159
+ respx.get(f"{BASE}/api/keys").mock(return_value=httpx.Response(200, json=_one_key()))
160
+ respx.post(f"{BASE}/api/keys/k1/disable").mock(
161
+ return_value=httpx.Response(403, json={"error": "key is part of the configuration"})
162
+ )
163
+ result = runner.invoke(app, ["keys", "disable", "ci-bot", "--yes"])
164
+ assert result.exit_code == 5
165
+
166
+
167
+ @respx.mock
168
+ def test_keys_regenerate_shows_new_secret(logged_in, runner):
169
+ respx.get(f"{BASE}/api/keys").mock(return_value=httpx.Response(200, json=_one_key()))
170
+ respx.post(f"{BASE}/api/keys/k1/regenerate").mock(
171
+ return_value=httpx.Response(200, json={"key": "a" * 64})
172
+ )
173
+ result = runner.invoke(app, ["--json", "keys", "regenerate", "ci-bot", "--yes"])
174
+ assert result.exit_code == 0, result.output
175
+ assert json.loads(result.stdout) == {"name": "ci-bot", "key": "a" * 64}
176
+
177
+
178
+ @respx.mock
179
+ def test_keys_list_box(logged_in, runner):
180
+ respx.get(f"{BASE}/api/keys").mock(return_value=httpx.Response(200, json=[
181
+ {"id": "1f58376d", "name": "admin", "permissions": ["a", "b"], "created_at": "2026-06-18T05:14:00Z", "revoked_at": None},
182
+ {"id": "9c22aa01", "name": "old-key", "permissions": ["x"], "created_at": "2026-06-10T00:00:00Z", "revoked_at": "2026-06-12T00:00:00Z"},
183
+ ]))
184
+ result = runner.invoke(app, ["keys", "list"])
185
+ assert result.exit_code == 0, result.output
186
+ assert "api keys" in result.stdout and "active first" in result.stdout
187
+ assert "admin" in result.stdout and "active" in result.stdout and "revoked" in result.stdout
188
+ assert result.stdout.index("admin") < result.stdout.index("old-key") # active sorts above revoked
189
+ # footer summary (stderr) counts by status
190
+ assert "2 keys" in (result.stderr or "") and "1 active" in (result.stderr or "") and "1 revoked" in (result.stderr or "")
191
+
192
+
193
+ @respx.mock
194
+ def test_keys_update_incremental_add(logged_in, runner):
195
+ # --add alone is INCREMENTAL — merged into the key's current grants (like users update).
196
+ respx.get(f"{BASE}/api/keys").mock(return_value=httpx.Response(200, json=[
197
+ {"id": "k1", "name": "ci", "permissions": ["events:add"], "created_at": "t", "revoked_at": None}]))
198
+ route = respx.patch(f"{BASE}/api/keys/k1").mock(
199
+ return_value=httpx.Response(200, json={"id": "k1", "name": "ci",
200
+ "permissions": ["events:add", "users:read"], "created_at": "t", "revoked_at": None})
201
+ )
202
+ result = runner.invoke(app, ["--json", "keys", "update", "ci", "--add", "users:read", "--yes"])
203
+ assert result.exit_code == 0, result.output
204
+ # current {events:add} ∪ {users:read} = sorted ["events:add", "users:read"]
205
+ assert json.loads(route.calls.last.request.content) == {"permissions": ["events:add", "users:read"]}
206
+ body = json.loads(result.stdout)
207
+ assert body["added"] == ["users:read"] and body["removed"] == []
208
+
209
+
210
+ @respx.mock
211
+ def test_keys_update_incremental_remove(logged_in, runner):
212
+ respx.get(f"{BASE}/api/keys").mock(return_value=httpx.Response(200, json=[
213
+ {"id": "k1", "name": "ci", "permissions": ["events:add", "events:read"], "created_at": "t", "revoked_at": None}]))
214
+ route = respx.patch(f"{BASE}/api/keys/k1").mock(
215
+ return_value=httpx.Response(200, json={"id": "k1", "name": "ci",
216
+ "permissions": ["events:read"], "created_at": "t", "revoked_at": None})
217
+ )
218
+ result = runner.invoke(app, ["--json", "keys", "update", "ci", "--remove", "events:add", "--yes"])
219
+ assert result.exit_code == 0, result.output
220
+ assert json.loads(route.calls.last.request.content) == {"permissions": ["events:read"]}
221
+
222
+
223
+ @respx.mock
224
+ def test_keys_update_noop_skips_server(logged_in, runner):
225
+ # adding a grant the key already has → no-op: no PATCH, exit 0.
226
+ respx.get(f"{BASE}/api/keys").mock(return_value=httpx.Response(200, json=[
227
+ {"id": "k1", "name": "ci", "permissions": ["events:add"], "created_at": "t", "revoked_at": None}]))
228
+ route = respx.patch(f"{BASE}/api/keys/k1").mock(return_value=httpx.Response(200, json={}))
229
+ result = runner.invoke(app, ["--json", "keys", "update", "ci", "--add", "events:add", "--yes"])
230
+ assert result.exit_code == 0, result.output
231
+ assert not route.called
232
+ assert json.loads(result.stdout)["added"] == [] and json.loads(result.stdout)["removed"] == []
233
+
234
+
235
+ def test_keys_update_requires_a_change_flag(logged_in, runner):
236
+ result = runner.invoke(app, ["keys", "update", "ci"])
237
+ assert result.exit_code == 2 # nothing to update
238
+
239
+
240
+ @respx.mock
241
+ def test_keys_update_not_found_exits_6(logged_in, runner):
242
+ respx.get(f"{BASE}/api/keys").mock(return_value=httpx.Response(200, json=[
243
+ {"id": "k1", "name": "ci", "permissions": [], "created_at": "t", "revoked_at": None}]))
244
+ result = runner.invoke(app, ["keys", "update", "nope", "--add", "events:read", "--yes"])
245
+ assert result.exit_code == 6
246
+
247
+
248
+ # --- queries ----------------------------------------------------------------
249
+
250
+
251
+ @respx.mock
252
+ def test_query_list_unwraps_queries_key(logged_in, runner):
253
+ respx.get(f"{BASE}/api/queries").mock(
254
+ return_value=httpx.Response(200, json={"queries": [{"id": "q1", "name": "errs", "sql_text": "select 1"}]})
255
+ )
256
+ result = runner.invoke(app, ["--json", "query", "list"])
257
+ assert result.exit_code == 0, result.output
258
+ assert json.loads(result.stdout)["queries"][0]["id"] == "q1"
259
+
260
+
261
+ @respx.mock
262
+ def test_query_create_posts_body(logged_in, runner):
263
+ respx.get(f"{BASE}/api/queries").mock(return_value=httpx.Response(200, json={"queries": []})) # no collision
264
+ route = respx.post(f"{BASE}/api/queries").mock(
265
+ return_value=httpx.Response(201, json={"id": "q9", "name": "errs", "sql_text": "select 1", "params": []})
266
+ )
267
+ result = runner.invoke(
268
+ app, ["--json", "query", "create", "errs", "--sql", "select 1"] # name is positional now
269
+ )
270
+ assert result.exit_code == 0, result.output
271
+ body = json.loads(route.calls.last.request.content)
272
+ assert body["name"] == "errs"
273
+ assert body["sql_text"] == "select 1"
274
+ assert body["params"] == [] # --param was removed; created queries carry no params
275
+
276
+
277
+ @respx.mock
278
+ def test_query_create_sql_from_file(logged_in, runner, tmp_path):
279
+ sql_file = tmp_path / "q.sql"
280
+ sql_file.write_text("select count(*) from analytics.events")
281
+ respx.get(f"{BASE}/api/queries").mock(return_value=httpx.Response(200, json={"queries": []}))
282
+ route = respx.post(f"{BASE}/api/queries").mock(
283
+ return_value=httpx.Response(201, json={"id": "q1", "name": "n", "sql_text": "x", "params": []})
284
+ )
285
+ result = runner.invoke(app, ["query", "create", "n", "--sql", f"@{sql_file}"])
286
+ assert result.exit_code == 0, result.output
287
+ assert json.loads(route.calls.last.request.content)["sql_text"] == "select count(*) from analytics.events"
288
+
289
+
290
+ @respx.mock
291
+ def test_query_create_name_collision_exits_2(logged_in, runner):
292
+ respx.get(f"{BASE}/api/queries").mock(return_value=httpx.Response(200, json={"queries": [{"id": "q1", "name": "errs"}]}))
293
+ result = runner.invoke(app, ["--json", "query", "create", "errs", "--sql", "select 1"])
294
+ assert result.exit_code == 2
295
+ assert "already exists" in json.loads(result.stdout)["error"]
296
+
297
+
298
+ @respx.mock
299
+ def test_query_run_inline_renders_rows(logged_in, runner):
300
+ respx.post(f"{BASE}/api/queries/run").mock(
301
+ return_value=httpx.Response(200, json={"columns": [{"name": "n", "type": "int"}], "rows": [[5]], "truncated": False, "elapsed_ms": 3})
302
+ )
303
+ result = runner.invoke(app, ["--json", "query", "run", "--sql", "select 5 as n"])
304
+ assert result.exit_code == 0, result.output
305
+ data = json.loads(result.stdout)
306
+ assert data["rows"] == [[5]]
307
+ assert data["elapsed_ms"] == 3
308
+
309
+
310
+ def test_query_run_requires_sql_or_saved(logged_in, runner):
311
+ result = runner.invoke(app, ["query", "run"])
312
+ assert result.exit_code == 2
313
+
314
+
315
+ @respx.mock
316
+ def test_query_run_coerces_param_values(logged_in, runner):
317
+ route = respx.post(f"{BASE}/api/queries/run").mock(
318
+ return_value=httpx.Response(200, json={"columns": [], "rows": [], "truncated": False, "elapsed_ms": 1})
319
+ )
320
+ result = runner.invoke(app, ["--json", "query", "run", "--sql", "select $1,$2,$3", "--param", "5", "--param", "true", "--param", "hi"])
321
+ assert result.exit_code == 0, result.output
322
+ assert json.loads(route.calls.last.request.content)["params"] == [5, True, "hi"]
323
+
324
+
325
+ @respx.mock
326
+ def test_query_run_readonly_violation_403(logged_in, runner):
327
+ respx.post(f"{BASE}/api/queries/run").mock(
328
+ return_value=httpx.Response(403, json={"error": "permission denied — only analytics.* views are queryable"})
329
+ )
330
+ result = runner.invoke(app, ["query", "run", "--sql", "select * from pg_user"])
331
+ assert result.exit_code == 5
332
+
333
+
334
+ @respx.mock
335
+ def test_query_schema_json_splits_nullable(logged_in, runner):
336
+ respx.get(f"{BASE}/api/queries/schema").mock(
337
+ return_value=httpx.Response(200, json={"schema": "analytics", "tables": [
338
+ {"name": "events", "columns": [{"name": "id", "type": "int"}, {"name": "tool_name", "type": "string?"}]}]})
339
+ )
340
+ result = runner.invoke(app, ["--json", "query", "schema"])
341
+ assert result.exit_code == 0, result.output
342
+ doc = json.loads(result.stdout)
343
+ assert doc["schema"] == "analytics"
344
+ cols = {c["column"]: c for c in doc["columns"]}
345
+ assert cols["id"] == {"table": "events", "column": "id", "type": "int", "nullable": False}
346
+ assert cols["tool_name"]["type"] == "string" and cols["tool_name"]["nullable"] is True # ? split off
347
+
348
+
349
+ @respx.mock
350
+ def test_query_schema_table_filter(logged_in, runner):
351
+ respx.get(f"{BASE}/api/queries/schema").mock(
352
+ return_value=httpx.Response(200, json={"schema": "analytics", "tables": [
353
+ {"name": "events", "columns": [{"name": "id", "type": "int"}]},
354
+ {"name": "evaluations", "columns": [{"name": "status", "type": "string"}]}]})
355
+ )
356
+ result = runner.invoke(app, ["--json", "query", "schema", "evaluations"])
357
+ assert result.exit_code == 0, result.output
358
+ cols = json.loads(result.stdout)["columns"]
359
+ assert {c["table"] for c in cols} == {"evaluations"} # filtered to one table
360
+
361
+
362
+ @respx.mock
363
+ def test_query_schema_table_not_found_exits_6(logged_in, runner):
364
+ respx.get(f"{BASE}/api/queries/schema").mock(
365
+ return_value=httpx.Response(200, json={"schema": "analytics", "tables": [{"name": "events", "columns": []}]}))
366
+ result = runner.invoke(app, ["query", "schema", "nope"])
367
+ assert result.exit_code == 6
368
+
369
+
370
+ # --- queries: name-referenced show / run / update / delete ------------------
371
+
372
+
373
+ @respx.mock
374
+ def test_query_show_by_name_json(logged_in, runner):
375
+ respx.get(f"{BASE}/api/queries").mock(return_value=httpx.Response(200, json={"queries": [
376
+ {"id": "q1", "name": "errs", "description": "d", "sql_text": "select 1", "created_by": "system", "created_at": "t"}]}))
377
+ result = runner.invoke(app, ["--json", "query", "show", "errs"]) # by NAME, not id
378
+ assert result.exit_code == 0, result.output
379
+ assert json.loads(result.stdout)["sql_text"] == "select 1"
380
+
381
+
382
+ @respx.mock
383
+ def test_query_show_not_found_exits_6(logged_in, runner):
384
+ respx.get(f"{BASE}/api/queries").mock(return_value=httpx.Response(200, json={"queries": [{"id": "q1", "name": "errs"}]}))
385
+ result = runner.invoke(app, ["query", "show", "nope"])
386
+ assert result.exit_code == 6
387
+
388
+
389
+ @respx.mock
390
+ def test_query_run_saved_by_positional_name(logged_in, runner):
391
+ respx.get(f"{BASE}/api/queries").mock(return_value=httpx.Response(200, json={"queries": [
392
+ {"id": "q1", "name": "errs", "sql_text": "select 1"}]}))
393
+ route = respx.post(f"{BASE}/api/queries/run").mock(
394
+ return_value=httpx.Response(200, json={"columns": [], "rows": [], "truncated": False, "elapsed_ms": 1}))
395
+ result = runner.invoke(app, ["--json", "query", "run", "errs", "--arg", "prod"]) # positional name, no --saved
396
+ assert result.exit_code == 0, result.output
397
+ body = json.loads(route.calls.last.request.content)
398
+ assert body["query_id"] == "q1" # name resolved to id
399
+ assert body["params"] == ["prod"]
400
+
401
+
402
+ def test_query_run_requires_name_or_sql(logged_in, runner):
403
+ assert runner.invoke(app, ["query", "run"]).exit_code == 2 # neither
404
+ assert runner.invoke(app, ["query", "run", "errs", "--sql", "select 1"]).exit_code == 2 # both
405
+
406
+
407
+ @respx.mock
408
+ def test_query_run_saved_not_found_exits_6(logged_in, runner):
409
+ respx.get(f"{BASE}/api/queries").mock(return_value=httpx.Response(200, json={"queries": [{"id": "q1", "name": "errs"}]}))
410
+ result = runner.invoke(app, ["query", "run", "ghost"])
411
+ assert result.exit_code == 6
412
+
413
+
414
+ @respx.mock
415
+ def test_query_run_exec_error_is_clean(logged_in, runner):
416
+ respx.post(f"{BASE}/api/queries/run").mock(
417
+ return_value=httpx.Response(400, json={"error": "Syntax error: unexpected token near 'FORM'"}))
418
+ result = runner.invoke(app, ["query", "run", "--sql", "SELECT * FORM events"])
419
+ assert result.exit_code == 1
420
+ # The underlying DB error is surfaced directly (no opaque "query failed" wrapper).
421
+ assert "Syntax error" in result.stderr
422
+
423
+
424
+ @respx.mock
425
+ def test_query_update_rename_collision_exits_2(logged_in, runner):
426
+ respx.get(f"{BASE}/api/queries").mock(return_value=httpx.Response(200, json={"queries": [
427
+ {"id": "q1", "name": "errs", "sql_text": "select 1"}, {"id": "q2", "name": "taken", "sql_text": "select 2"}]}))
428
+ result = runner.invoke(app, ["--json", "query", "update", "errs", "--name", "taken", "--yes"])
429
+ assert result.exit_code == 2
430
+ assert "already exists" in json.loads(result.stdout)["error"]
431
+
432
+
433
+ @respx.mock
434
+ def test_query_update_noop_makes_no_put(logged_in, runner):
435
+ # --description equal to current → nothing actually changes → no PUT, exit 0.
436
+ respx.get(f"{BASE}/api/queries").mock(return_value=httpx.Response(200, json={"queries": [
437
+ {"id": "q1", "name": "errs", "description": "same", "sql_text": "select 1", "params": []}]}))
438
+ put = respx.put(f"{BASE}/api/queries/q1").mock(return_value=httpx.Response(200, json={"id": "q1"}))
439
+ result = runner.invoke(app, ["--json", "query", "update", "errs", "--description", "same", "--yes"])
440
+ assert result.exit_code == 0, result.output
441
+ assert not put.called
442
+
443
+
444
+ @respx.mock
445
+ def test_query_update_by_name_keeps_omitted_fields(logged_in, runner):
446
+ respx.get(f"{BASE}/api/queries").mock(return_value=httpx.Response(200, json={"queries": [
447
+ {"id": "q1", "name": "errs", "description": "old desc", "sql_text": "select 1",
448
+ "params": [{"name": "e", "type": "text"}]}]}))
449
+ route = respx.put(f"{BASE}/api/queries/q1").mock(
450
+ return_value=httpx.Response(200, json={"id": "q1", "name": "errs", "sql_text": "select 2"}))
451
+ result = runner.invoke(app, ["--json", "query", "update", "errs", "--sql", "select 2", "--yes"])
452
+ assert result.exit_code == 0, result.output
453
+ sent = json.loads(route.calls.last.request.content)
454
+ assert sent["sql_text"] == "select 2" # changed
455
+ assert sent["name"] == "errs" # kept
456
+ assert sent["description"] == "old desc" # kept
457
+ assert sent["params"] == [{"name": "e", "type": "text"}] # kept
458
+
459
+
460
+ def test_query_update_noop_rejected(logged_in, runner):
461
+ # No fields → usage error before any read/write (no routes mocked).
462
+ result = runner.invoke(app, ["query", "update", "errs", "--yes"])
463
+ assert result.exit_code == 2
464
+
465
+
466
+ @respx.mock
467
+ def test_query_update_sql_from_stdin_reads_it_once(logged_in, runner):
468
+ # `--sql @-` is stdin, which drains on the first read. Reading it a second time for
469
+ # the request body would send "" — a query silently emptied at exit 0, while the
470
+ # change detection had compared the real text.
471
+ respx.get(f"{BASE}/api/queries").mock(return_value=httpx.Response(200, json={"queries": [
472
+ {"id": "q1", "name": "errs", "description": "d", "sql_text": "select 1", "params": []}]}))
473
+ route = respx.put(f"{BASE}/api/queries/q1").mock(
474
+ return_value=httpx.Response(200, json={"id": "q1", "name": "errs", "sql_text": "select 2"}))
475
+ result = runner.invoke(app, ["--json", "query", "update", "errs", "--sql", "@-", "--yes"],
476
+ input="select 2 from analytics.events")
477
+ assert result.exit_code == 0, result.output
478
+ assert json.loads(route.calls.last.request.content)["sql_text"] == "select 2 from analytics.events"
479
+
480
+
481
+ @respx.mock
482
+ def test_query_delete_by_name(logged_in, runner):
483
+ respx.get(f"{BASE}/api/queries").mock(return_value=httpx.Response(200, json={"queries": [{"id": "q1", "name": "errs"}]}))
484
+ route = respx.delete(f"{BASE}/api/queries/q1").mock(return_value=httpx.Response(200, json={"deleted": True}))
485
+ result = runner.invoke(app, ["--json", "query", "delete", "errs", "--yes"])
486
+ assert result.exit_code == 0, result.output
487
+ assert route.called
488
+ body = json.loads(result.stdout)
489
+ assert body["deleted"] is True and body["name"] == "errs"
490
+
491
+
492
+ @respx.mock
493
+ def test_query_delete_not_found_exits_6(logged_in, runner):
494
+ respx.get(f"{BASE}/api/queries").mock(return_value=httpx.Response(200, json={"queries": [{"id": "q1", "name": "errs"}]}))
495
+ result = runner.invoke(app, ["query", "delete", "ghost", "--yes"])
496
+ assert result.exit_code == 6
@@ -0,0 +1,59 @@
1
+ """`fp list <thing>` — value discovery behind the dashboard's filter dropdowns."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json
6
+
7
+ import httpx
8
+ import pytest
9
+ import respx
10
+
11
+ from fp_cli.app import app
12
+
13
+ BASE = "http://dash.test"
14
+
15
+ # friendly `list` name -> the endpoint it must hit
16
+ _CASES = {
17
+ "envs": "/api/events/environments",
18
+ "agents": "/api/events/agent_ids",
19
+ "event_types": "/api/events/event_types",
20
+ "score_filters": "/api/evaluations/score-keys",
21
+ "models": "/api/events/models",
22
+ "hooks": "/api/events/hook_names",
23
+ "tools": "/api/events/tool_names",
24
+ "error_types": "/api/events/error_types",
25
+ }
26
+
27
+
28
+ @pytest.mark.parametrize("name,path", list(_CASES.items()))
29
+ @respx.mock
30
+ def test_list_hits_right_endpoint_and_wraps(logged_in, runner, name, path):
31
+ route = respx.get(f"{BASE}{path}").mock(return_value=httpx.Response(200, json=["a", "b"]))
32
+ result = runner.invoke(app, ["--json", "list", name])
33
+ assert result.exit_code == 0, result.output
34
+ assert route.called # the friendly name resolved to the correct endpoint
35
+ assert json.loads(result.stdout) == {"kind": name, "values": ["a", "b"]}
36
+
37
+
38
+ @respx.mock
39
+ def test_list_empty_is_clean(logged_in, runner):
40
+ respx.get(f"{BASE}/api/events/models").mock(return_value=httpx.Response(200, json=[]))
41
+ result = runner.invoke(app, ["--json", "list", "models"])
42
+ assert result.exit_code == 0, result.output
43
+ assert json.loads(result.stdout)["values"] == []
44
+
45
+
46
+ def test_list_unknown_kind_is_usage_error(logged_in, runner):
47
+ # An undefined subcommand is a clean usage error, not a crash.
48
+ result = runner.invoke(app, ["list", "bogus"])
49
+ assert result.exit_code == 2
50
+
51
+
52
+ @respx.mock
53
+ def test_list_forbidden_exits_5(logged_in, runner):
54
+ # score_filters needs evaluations:read; a 403 maps to the forbidden exit code.
55
+ respx.get(f"{BASE}/api/evaluations/score-keys").mock(
56
+ return_value=httpx.Response(403, json={"error": "forbidden"})
57
+ )
58
+ result = runner.invoke(app, ["list", "score_filters"])
59
+ assert result.exit_code == 5