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,99 @@
1
+ from __future__ import annotations
2
+
3
+ import os
4
+ import stat
5
+ from datetime import datetime, timedelta, timezone
6
+
7
+ from fp_cli import config
8
+
9
+
10
+ def _iso(delta: timedelta) -> str:
11
+ return (datetime.now(timezone.utc) + delta).strftime("%Y-%m-%dT%H:%M:%SZ")
12
+
13
+
14
+ def test_load_missing_returns_defaults(home):
15
+ cfg = config.load_config()
16
+ assert cfg.base_url is None # no default URL — it must be set explicitly
17
+ assert cfg.session_token is None
18
+
19
+
20
+ def test_save_and_load_roundtrip(home):
21
+ cfg = config.CliConfig(
22
+ base_url="http://x.test",
23
+ session_token="t",
24
+ expires_at="2999-01-01T00:00:00Z",
25
+ email="e@test",
26
+ user_id="u",
27
+ )
28
+ config.save_config(cfg)
29
+ assert config.load_config() == cfg
30
+
31
+
32
+ def test_save_uses_0600_permissions(home):
33
+ config.save_config(config.CliConfig())
34
+ mode = stat.S_IMODE(os.stat(config.config_path()).st_mode)
35
+ assert mode == 0o600
36
+
37
+
38
+ def test_base_dir_respects_fp_home(home):
39
+ assert config.base_dir() == home
40
+
41
+
42
+ def test_clear_token_wipes_session_identity_and_org(home):
43
+ # logout must leave NO stale identity: token, expiry, email, user id, and the
44
+ # active org are all cleared. base_url / insecure / anonymous_id are kept so the
45
+ # next login doesn't need them re-specified and telemetry stays stable.
46
+ config.save_config(
47
+ config.CliConfig(
48
+ base_url="http://x.test",
49
+ session_token="t",
50
+ expires_at="2999-01-01T00:00:00Z",
51
+ email="e@test",
52
+ user_id="u1",
53
+ insecure=True,
54
+ org="acme",
55
+ anonymous_id="anon-123",
56
+ )
57
+ )
58
+ config.clear_token(config.load_config())
59
+ reloaded = config.load_config()
60
+ # cleared — nothing about who/where remains
61
+ assert reloaded.session_token is None
62
+ assert reloaded.expires_at is None
63
+ assert reloaded.email is None
64
+ assert reloaded.user_id is None
65
+ assert reloaded.org is None
66
+ # kept — preferences + stable machine id
67
+ assert reloaded.base_url == "http://x.test"
68
+ assert reloaded.insecure is True
69
+ assert reloaded.anonymous_id == "anon-123"
70
+
71
+
72
+ def test_corrupt_file_falls_back_to_defaults(home):
73
+ path = config.config_path()
74
+ path.parent.mkdir(parents=True, exist_ok=True)
75
+ path.write_text("{ not json")
76
+ assert config.load_config().base_url is None
77
+
78
+
79
+ def test_is_expired():
80
+ assert config.is_expired(config.CliConfig()) is True # no token at all
81
+ assert config.is_expired(config.CliConfig(session_token="t")) is True # no expiry
82
+ assert config.is_expired(
83
+ config.CliConfig(session_token="t", expires_at=_iso(timedelta(hours=-1)))
84
+ ) is True
85
+ assert config.is_expired(
86
+ config.CliConfig(session_token="t", expires_at=_iso(timedelta(hours=1)))
87
+ ) is False
88
+
89
+
90
+ def test_is_expired_handles_z_suffix_and_skew():
91
+ # 30s in the future is "expired" under the default 60s skew.
92
+ soon = _iso(timedelta(seconds=30))
93
+ assert config.is_expired(config.CliConfig(session_token="t", expires_at=soon)) is True
94
+
95
+
96
+ def test_insecure_defaults_false_and_roundtrips(home):
97
+ assert config.load_config().insecure is False # secure by default
98
+ config.save_config(config.CliConfig(insecure=True))
99
+ assert config.load_config().insecure is True
@@ -0,0 +1,340 @@
1
+ """The agent assistant (SSE)."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json
6
+
7
+ import httpx
8
+ import respx
9
+
10
+ from fp_cli.app import app
11
+
12
+ BASE = "http://dash.test"
13
+
14
+
15
+ def _sse(events) -> httpx.Response:
16
+ body = "".join(f"data: {json.dumps(e)}\n\n" for e in events)
17
+ return httpx.Response(200, text=body, headers={"content-type": "text/event-stream"})
18
+
19
+
20
+ # --- agent ------------------------------------------------------------------
21
+
22
+
23
+ @respx.mock
24
+ def test_agent_health(logged_in, runner):
25
+ respx.get(f"{BASE}/api/agent/health").mock(
26
+ return_value=httpx.Response(200, json={"enabled": True, "llm_configured": True, "default_model": "claude-x"})
27
+ )
28
+ result = runner.invoke(app, ["--json", "agent", "health"])
29
+ assert result.exit_code == 0, result.output
30
+ assert json.loads(result.stdout)["enabled"] is True
31
+
32
+
33
+ @respx.mock
34
+ def test_agent_models_lists_allowlist(logged_in, runner):
35
+ respx.get(f"{BASE}/api/agent/health").mock(
36
+ return_value=httpx.Response(200, json={"enabled": True, "models": ["m-default", "m-fast"], "defaultModel": "m-default"})
37
+ )
38
+ result = runner.invoke(app, ["--json", "agent", "models"])
39
+ assert result.exit_code == 0, result.output
40
+ data = json.loads(result.stdout)
41
+ assert data["models"] == ["m-default", "m-fast"]
42
+ assert data["default_model"] == "m-default"
43
+
44
+
45
+ @respx.mock
46
+ def test_agent_chats_list(logged_in, runner):
47
+ respx.get(f"{BASE}/api/agent/conversations").mock(
48
+ return_value=httpx.Response(200, json={"conversations": [{"id": "c1", "title": "hi", "message_count": 2, "updated_at": "t"}]})
49
+ )
50
+ result = runner.invoke(app, ["--json", "agent", "chats"])
51
+ assert result.exit_code == 0, result.output
52
+ assert json.loads(result.stdout)["chats"][0]["id"] == "c1"
53
+
54
+
55
+ @respx.mock
56
+ def test_agent_ask_new_chat_creates_persists_and_returns_id(logged_in, runner):
57
+ respx.post(f"{BASE}/api/agent/chat").mock(
58
+ return_value=_sse([
59
+ {"type": "text-delta", "text": "Hello "},
60
+ {"type": "tool-start", "tool": "run_query", "toolCallId": "1"},
61
+ {"type": "text-delta", "text": "world"},
62
+ {"type": "done"},
63
+ ])
64
+ )
65
+ create = respx.post(f"{BASE}/api/agent/conversations").mock(
66
+ return_value=httpx.Response(200, json={"id": "c9", "title": "New conversation"})
67
+ )
68
+ put = respx.put(f"{BASE}/api/agent/conversations/c9/messages").mock(return_value=httpx.Response(200, json={}))
69
+ patch = respx.patch(f"{BASE}/api/agent/conversations/c9").mock(return_value=httpx.Response(200, json={}))
70
+ result = runner.invoke(app, ["--json", "agent", "ask", "hi there"]) # message is positional now
71
+ assert result.exit_code == 0, result.output
72
+ data = json.loads(result.stdout)
73
+ assert data["answer"] == "Hello world"
74
+ assert data["tools"] == ["run_query"]
75
+ assert data["chat_id"] == "c9" # the new chat id is surfaced
76
+ assert create.called # no --chat ⇒ a new chat was created
77
+ # persisted [user, assistant] in the shared {role, content:{text}} shape
78
+ assert json.loads(put.calls.last.request.content)["messages"] == [
79
+ {"role": "user", "content": {"text": "hi there"}},
80
+ {"role": "assistant", "content": {"text": "Hello world"}},
81
+ ]
82
+ assert json.loads(create.calls.last.request.content)["title"] == "hi there" # titled at creation
83
+
84
+
85
+ @respx.mock
86
+ def test_agent_ask_interactive_aborts_without_creating_chat(logged_in, runner):
87
+ # No conversation routes are mocked — if a chat were created, respx would error.
88
+ respx.post(f"{BASE}/api/agent/chat").mock(
89
+ return_value=_sse([
90
+ {"type": "text-delta", "text": "let me check"},
91
+ {"type": "ask-user", "callId": "x", "kind": "approval", "question": "ok to run?", "allowText": True},
92
+ ])
93
+ )
94
+ result = runner.invoke(app, ["agent", "ask", "do it"])
95
+ assert result.exit_code == 1 # clean abort, no orphan chat
96
+
97
+
98
+ @respx.mock
99
+ def test_agent_ask_not_configured_503(logged_in, runner):
100
+ respx.post(f"{BASE}/api/agent/chat").mock(return_value=httpx.Response(503, json={"error": "assistant not configured"}))
101
+ result = runner.invoke(app, ["agent", "ask", "hi"])
102
+ assert result.exit_code == 1 # ApiError surfaced; no chat created
103
+
104
+
105
+ @respx.mock
106
+ def test_agent_ask_chat_continues_existing_thread(logged_in, runner):
107
+ # --chat resolves via the chat list first, then loads the thread by full id.
108
+ respx.get(f"{BASE}/api/agent/conversations").mock(
109
+ return_value=httpx.Response(200, json={"conversations": [{"id": "c1", "title": "perf review", "message_count": 2}]})
110
+ )
111
+ respx.get(f"{BASE}/api/agent/conversations/c1").mock(
112
+ return_value=httpx.Response(200, json={"title": "perf review", "messages": [
113
+ {"role": "user", "content": {"text": "earlier q"}},
114
+ {"role": "assistant", "content": {"text": "earlier a"}},
115
+ ]})
116
+ )
117
+ chat = respx.post(f"{BASE}/api/agent/chat").mock(
118
+ return_value=_sse([{"type": "text-delta", "text": "follow up"}, {"type": "done"}])
119
+ )
120
+ put = respx.put(f"{BASE}/api/agent/conversations/c1/messages").mock(return_value=httpx.Response(200, json={}))
121
+ patch = respx.patch(f"{BASE}/api/agent/conversations/c1").mock(return_value=httpx.Response(200, json={}))
122
+ create = respx.post(f"{BASE}/api/agent/conversations").mock(return_value=httpx.Response(200, json={"id": "cNEW"}))
123
+ result = runner.invoke(app, ["--json", "agent", "ask", "and now?", "--chat", "c1"])
124
+ assert result.exit_code == 0, result.output
125
+ # prior thread + new user turn sent to the agent for context
126
+ sent = json.loads(chat.calls.last.request.content)["messages"]
127
+ assert len(sent) == 3 and sent[-1] == {"role": "user", "content": {"text": "and now?"}}
128
+ # persisted = history + user + assistant; existing title NOT overwritten; no new chat made
129
+ saved = json.loads(put.calls.last.request.content)["messages"]
130
+ assert len(saved) == 4 and saved[-1] == {"role": "assistant", "content": {"text": "follow up"}}
131
+ assert not patch.called
132
+ assert not create.called
133
+ assert json.loads(result.stdout)["chat_id"] == "c1"
134
+
135
+
136
+ @respx.mock
137
+ def test_agent_ask_rejects_unknown_model(logged_in, runner):
138
+ respx.get(f"{BASE}/api/agent/health").mock(
139
+ return_value=httpx.Response(200, json={"enabled": True, "models": ["m-default", "m-fast"], "defaultModel": "m-default"})
140
+ )
141
+ chat = respx.post(f"{BASE}/api/agent/chat").mock(return_value=_sse([{"type": "done"}]))
142
+ result = runner.invoke(app, ["agent", "ask", "hi", "--model", "bogus-model"])
143
+ assert result.exit_code == 2 # BadParameter — validated against the allowlist before chatting
144
+ assert not chat.called
145
+
146
+
147
+ @respx.mock
148
+ def test_agent_ask_accepts_allowlisted_model_and_forwards_it(logged_in, runner):
149
+ respx.get(f"{BASE}/api/agent/health").mock(
150
+ return_value=httpx.Response(200, json={"enabled": True, "models": ["m-default", "m-fast"], "defaultModel": "m-default"})
151
+ )
152
+ respx.post(f"{BASE}/api/agent/chat").mock(return_value=_sse([{"type": "text-delta", "text": "ok"}, {"type": "done"}]))
153
+ respx.post(f"{BASE}/api/agent/conversations").mock(return_value=httpx.Response(200, json={"id": "c9"}))
154
+ respx.put(f"{BASE}/api/agent/conversations/c9/messages").mock(return_value=httpx.Response(200, json={}))
155
+ respx.patch(f"{BASE}/api/agent/conversations/c9").mock(return_value=httpx.Response(200, json={}))
156
+ result = runner.invoke(app, ["--json", "agent", "ask", "hi", "--model", "m-fast"])
157
+ assert result.exit_code == 0, result.output
158
+ chat_calls = [c for c in respx.calls if c.request.url.path == "/api/agent/chat"]
159
+ assert json.loads(chat_calls[-1].request.content)["model"] == "m-fast" # chosen model forwarded
160
+
161
+
162
+ # --- agent: redesigned UI (render assertions; the JSON contracts above are unchanged) ---
163
+
164
+
165
+ @respx.mock
166
+ def test_agent_health_renders_configured_card(logged_in, runner):
167
+ respx.get(f"{BASE}/api/agent/health").mock(
168
+ return_value=httpx.Response(200, json={"enabled": True, "llm_configured": True,
169
+ "default_model": "claude-x", "models": ["claude-x", "claude-fast"]})
170
+ )
171
+ result = runner.invoke(app, ["agent", "health"])
172
+ assert result.exit_code == 0, result.output
173
+ out = result.stdout
174
+ assert "assistant" in out and "configured" in out and "claude-x" in out
175
+
176
+
177
+ @respx.mock
178
+ def test_agent_health_renders_not_configured(logged_in, runner):
179
+ respx.get(f"{BASE}/api/agent/health").mock(
180
+ return_value=httpx.Response(200, json={"enabled": False, "llm_configured": False})
181
+ )
182
+ result = runner.invoke(app, ["agent", "health"])
183
+ assert result.exit_code == 0, result.output
184
+ assert "not configured" in result.stdout
185
+
186
+
187
+ @respx.mock
188
+ def test_agent_models_renders_default_marker(logged_in, runner):
189
+ respx.get(f"{BASE}/api/agent/health").mock(
190
+ return_value=httpx.Response(200, json={"enabled": True, "models": ["m-default", "m-fast"], "defaultModel": "m-default"})
191
+ )
192
+ result = runner.invoke(app, ["agent", "models"])
193
+ assert result.exit_code == 0, result.output
194
+ out = result.stdout
195
+ assert "models" in out and "m-default" in out and "m-fast" in out and "default" in out
196
+
197
+
198
+ @respx.mock
199
+ def test_agent_models_empty_is_calm_and_notes_unconfigured(logged_in, runner):
200
+ respx.get(f"{BASE}/api/agent/health").mock(return_value=httpx.Response(200, json={"enabled": False}))
201
+ result = runner.invoke(app, ["agent", "models"])
202
+ assert result.exit_code == 0, result.output
203
+ assert "no models reported" in result.stdout
204
+ assert "isn't configured" in (result.stderr or "")
205
+
206
+
207
+ @respx.mock
208
+ def test_agent_chats_renders_table(logged_in, runner):
209
+ respx.get(f"{BASE}/api/agent/conversations").mock(
210
+ return_value=httpx.Response(200, json={"conversations": [
211
+ {"id": "c1", "title": "perf review", "message_count": 4, "updated_at": "2026-06-27T10:00:00Z"},
212
+ ]})
213
+ )
214
+ result = runner.invoke(app, ["agent", "chats"])
215
+ assert result.exit_code == 0, result.output
216
+ assert "chats" in result.stdout and "perf review" in result.stdout
217
+ assert "chat-id" in result.stdout and "c1" in result.stdout # full chat-id column by default
218
+ assert "open one with" not in (result.stderr or "") # footer removed
219
+
220
+
221
+ @respx.mock
222
+ def test_agent_chats_empty_is_calm(logged_in, runner):
223
+ respx.get(f"{BASE}/api/agent/conversations").mock(return_value=httpx.Response(200, json={"conversations": []}))
224
+ result = runner.invoke(app, ["agent", "chats"])
225
+ assert result.exit_code == 0, result.output
226
+ assert "no chats" in result.stdout
227
+
228
+
229
+ @respx.mock
230
+ def test_agent_show_renders_thread(logged_in, runner):
231
+ # show resolves the (short) id via the chat list, then loads the thread by full id.
232
+ respx.get(f"{BASE}/api/agent/conversations").mock(
233
+ return_value=httpx.Response(200, json={"conversations": [{"id": "c1", "title": "perf review", "message_count": 2}]})
234
+ )
235
+ respx.get(f"{BASE}/api/agent/conversations/c1").mock(
236
+ return_value=httpx.Response(200, json={"title": "perf review", "messages": [
237
+ {"role": "user", "content": {"text": "why slow"}},
238
+ {"role": "assistant", "content": "because cache"}, # str-or-{text}: both extracted
239
+ ]})
240
+ )
241
+ result = runner.invoke(app, ["agent", "show", "c1"])
242
+ assert result.exit_code == 0, result.output
243
+ out = result.stdout
244
+ assert "perf review" in out and "you" in out and "assistant" in out
245
+ assert "why slow" in out and "because cache" in out
246
+
247
+
248
+ @respx.mock
249
+ def test_agent_show_not_found_exits_6(logged_in, runner):
250
+ respx.get(f"{BASE}/api/agent/conversations").mock(return_value=httpx.Response(200, json={"conversations": []}))
251
+ result = runner.invoke(app, ["agent", "show", "ghost"])
252
+ assert result.exit_code == 6
253
+ assert "chat not found" in (result.stderr or result.output)
254
+
255
+
256
+ @respx.mock
257
+ def test_agent_show_not_found_json(logged_in, runner):
258
+ respx.get(f"{BASE}/api/agent/conversations").mock(return_value=httpx.Response(200, json={"conversations": []}))
259
+ result = runner.invoke(app, ["--json", "agent", "show", "ghost"])
260
+ assert result.exit_code == 6
261
+ assert "chat not found" in json.loads(result.stdout)["error"]
262
+
263
+
264
+ @respx.mock
265
+ def test_agent_rename_renders_card_and_keeps_json(logged_in, runner):
266
+ respx.get(f"{BASE}/api/agent/conversations").mock(
267
+ return_value=httpx.Response(200, json={"conversations": [{"id": "c1", "title": "old name", "message_count": 0}]})
268
+ )
269
+ patch = respx.patch(f"{BASE}/api/agent/conversations/c1").mock(return_value=httpx.Response(200, json={}))
270
+ result = runner.invoke(app, ["agent", "rename", "c1", "--title", "new name"])
271
+ assert result.exit_code == 0, result.output
272
+ err = result.stderr or ""
273
+ assert "chat renamed" in err and "new name" in err and "was old name" in err
274
+ assert json.loads(patch.calls.last.request.content)["title"] == "new name"
275
+ # JSON contract unchanged (now the full resolved id)
276
+ result2 = runner.invoke(app, ["--json", "agent", "rename", "c1", "--title", "x"])
277
+ assert json.loads(result2.stdout) == {"id": "c1", "title": "x"}
278
+
279
+
280
+ @respx.mock
281
+ def test_agent_rename_not_found_exits_6(logged_in, runner):
282
+ respx.get(f"{BASE}/api/agent/conversations").mock(return_value=httpx.Response(200, json={"conversations": []}))
283
+ result = runner.invoke(app, ["agent", "rename", "ghost", "--title", "x"])
284
+ assert result.exit_code == 6
285
+ assert "chat not found" in (result.stderr or result.output)
286
+
287
+
288
+ @respx.mock
289
+ def test_agent_delete_yes_renders_and_keeps_json(logged_in, runner):
290
+ respx.get(f"{BASE}/api/agent/conversations").mock(
291
+ return_value=httpx.Response(200, json={"conversations": [{"id": "c1", "title": "perf review", "message_count": 1}]})
292
+ )
293
+ delete = respx.delete(f"{BASE}/api/agent/conversations/c1").mock(return_value=httpx.Response(200, json={}))
294
+ result = runner.invoke(app, ["agent", "delete", "c1", "--yes"])
295
+ assert result.exit_code == 0, result.output
296
+ err = result.stderr or ""
297
+ assert "deleted chat" in err and "perf review" in err
298
+ assert delete.called
299
+ # JSON contract (now also carries the title)
300
+ result2 = runner.invoke(app, ["--json", "agent", "delete", "c1", "--yes"])
301
+ body = json.loads(result2.stdout)
302
+ assert body["deleted"] is True and body["id"] == "c1" and body["title"] == "perf review"
303
+
304
+
305
+ @respx.mock
306
+ def test_agent_delete_not_found_exits_6(logged_in, runner):
307
+ respx.get(f"{BASE}/api/agent/conversations").mock(return_value=httpx.Response(200, json={"conversations": []}))
308
+ result = runner.invoke(app, ["agent", "delete", "ghost", "--yes"])
309
+ assert result.exit_code == 6
310
+ assert "chat not found" in (result.stderr or result.output)
311
+
312
+
313
+ @respx.mock
314
+ def test_agent_ask_answer_to_stdout_chrome_to_stderr(logged_in, runner):
315
+ respx.post(f"{BASE}/api/agent/chat").mock(
316
+ return_value=_sse([
317
+ {"type": "tool-start", "tool": "run_query"},
318
+ {"type": "text-delta", "text": "the answer"},
319
+ {"type": "done"},
320
+ ])
321
+ )
322
+ respx.post(f"{BASE}/api/agent/conversations").mock(return_value=httpx.Response(200, json={"id": "c9"}))
323
+ respx.put(f"{BASE}/api/agent/conversations/c9/messages").mock(return_value=httpx.Response(200, json={}))
324
+ respx.patch(f"{BASE}/api/agent/conversations/c9").mock(return_value=httpx.Response(200, json={}))
325
+ result = runner.invoke(app, ["agent", "ask", "q"])
326
+ assert result.exit_code == 0, result.output
327
+ assert result.stdout.strip() == "the answer" # piped (non-tty) → ONLY the raw answer on stdout
328
+ err = result.stderr or ""
329
+ assert "used tool: run_query" in err
330
+ assert "new chat" in err and "fp agent ask --chat c9" in err
331
+
332
+
333
+ @respx.mock
334
+ def test_agent_ask_assistant_error_clean_exit_1(logged_in, runner):
335
+ respx.post(f"{BASE}/api/agent/chat").mock(
336
+ return_value=_sse([{"type": "error", "message": "model overloaded"}])
337
+ )
338
+ result = runner.invoke(app, ["agent", "ask", "q"])
339
+ assert result.exit_code == 1
340
+ assert "✗" in (result.stderr or "") and "model overloaded" in (result.stderr or "")
@@ -0,0 +1,76 @@
1
+ from __future__ import annotations
2
+
3
+ from datetime import datetime, timezone
4
+
5
+ import pytest
6
+
7
+ from fp_cli import dates
8
+
9
+ NOW = datetime(2026, 5, 25, 12, 0, 0, tzinfo=timezone.utc)
10
+
11
+
12
+ def test_all_and_none_have_no_bounds():
13
+ assert dates.resolve_range("all") == (None, None)
14
+ assert dates.resolve_range(None) == (None, None)
15
+
16
+
17
+ @pytest.mark.parametrize(
18
+ "preset,expected_from",
19
+ [
20
+ ("15m", "2026-05-25T11:45:00Z"),
21
+ ("1h", "2026-05-25T11:00:00Z"),
22
+ ("6h", "2026-05-25T06:00:00Z"),
23
+ ("24h", "2026-05-24T12:00:00Z"),
24
+ ("7d", "2026-05-18T12:00:00Z"),
25
+ ],
26
+ )
27
+ def test_presets(preset, expected_from):
28
+ ts_from, ts_to = dates.resolve_range(preset, now=NOW)
29
+ assert ts_from == expected_from
30
+ assert ts_to is None
31
+
32
+
33
+ def test_custom_from_to_overrides_since():
34
+ ts_from, ts_to = dates.resolve_range(
35
+ "24h", "2020-01-01T00:00:00Z", "2020-01-02T00:00:00Z", now=NOW
36
+ )
37
+ assert ts_from == "2020-01-01T00:00:00Z"
38
+ assert ts_to == "2020-01-02T00:00:00Z"
39
+
40
+
41
+ def test_invalid_since_raises():
42
+ with pytest.raises(ValueError):
43
+ dates.resolve_range("bogus", now=NOW)
44
+
45
+
46
+ @pytest.mark.parametrize(
47
+ "ok_value",
48
+ ["2026-05-01T00:00:00Z", "2026-05-01T00:00:00+00:00", "2026-05-01T12:30:00-05:00"],
49
+ )
50
+ def test_from_accepts_rfc3339_with_timezone(ok_value):
51
+ ts_from, _ = dates.resolve_range(None, ok_value, None, now=NOW)
52
+ assert ts_from == ok_value
53
+
54
+
55
+ @pytest.mark.parametrize(
56
+ "bad_value",
57
+ [
58
+ "2026-05-01", # date only
59
+ "2026-05-01T00:00:00", # no timezone offset → server 400
60
+ "2026-05-01 00:00:00Z", # space separator, not 'T'
61
+ "not-a-date",
62
+ ],
63
+ )
64
+ def test_from_rejects_naive_or_malformed(bad_value):
65
+ # These deserialize to a server 400 (exit 1); validating client-side keeps them a
66
+ # clean usage error (exit 2). Applies to --from and --to alike.
67
+ with pytest.raises(ValueError):
68
+ dates.resolve_range(None, bad_value, None, now=NOW)
69
+ with pytest.raises(ValueError):
70
+ dates.resolve_range(None, None, bad_value, now=NOW)
71
+
72
+
73
+ def test_since_choices_cover_presets():
74
+ for preset in dates.PRESETS:
75
+ assert preset in dates.SINCE_CHOICES
76
+ assert "all" in dates.SINCE_CHOICES