failproofai 1.0.1 → 1.0.2-beta.1

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 (390) 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]__0l4748x._.js +3 -0
  97. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0oa1lav._.js +1 -1
  98. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0p-5p8u._.js +4 -0
  99. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0s740oi._.js +6 -0
  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/_08x1r5t._.js +1 -1
  103. package/.next/standalone/.next/server/chunks/ssr/_0bwu160._.js +23 -0
  104. package/.next/standalone/.next/server/chunks/ssr/{_0u5zauv._.js → _0ns6k3a._.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/{3c808qrfcq0im.js → 01u1cf8js2yd6.js} +1 -1
  121. package/.next/standalone/.next/static/chunks/{0k5050ezjvv49.js → 0izgu7nqh2zp-.js} +1 -1
  122. package/.next/standalone/.next/static/chunks/0u4-org_e7zde.js +1 -0
  123. package/.next/standalone/.next/static/chunks/1mfzsja6ftim5.css +2 -0
  124. package/.next/standalone/.next/static/chunks/{2zpp1we2jv6tb.js → 1tynnrrh711lr.js} +1 -1
  125. package/.next/standalone/.next/static/chunks/{1ide1wl2f7zi1.js → 22rqvlr7p_1mv.js} +13 -13
  126. package/.next/standalone/.next/static/chunks/{31s1smp-g7gks.js → 2whxqyskpirbk.js} +1 -1
  127. package/.next/standalone/.next/static/chunks/31mwk8k0q3_3g.js +6 -0
  128. package/.next/standalone/.next/static/chunks/3ms6gxfm82t6s.js +1 -0
  129. package/.next/standalone/.next/static/chunks/{37fcpc36x9gpe.js → 3tbbs_d8xrvyp.js} +1 -1
  130. package/.next/standalone/.next/static/chunks/43nlc236oo898.js +1 -0
  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 +163 -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 -15
  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 +27 -21
  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 +579 -306
  319. package/dist/cli.mjs +6499 -3662
  320. package/dist/index.js +5 -5
  321. package/dist/worker.mjs +2390 -1978
  322. package/package.json +27 -21
  323. package/scripts/build-policy-pack.mjs +121 -0
  324. package/scripts/changelog-section.py +77 -0
  325. package/scripts/prune-standalone.mjs +3 -0
  326. package/scripts/python-version.py +232 -0
  327. package/scripts/translate-docs/cli.ts +21 -0
  328. package/scripts/translate-docs/mintlify-nav.ts +85 -13
  329. package/scripts/translate-docs/translator.ts +66 -11
  330. package/src/audit/cache.ts +40 -3
  331. package/src/audit/cli.ts +1 -1
  332. package/src/audit/index.ts +1 -1
  333. package/src/audit/replay.ts +89 -3
  334. package/src/audit/report.ts +5 -5
  335. package/src/audit/schedule-cli.ts +49 -53
  336. package/src/hooks/builtin-policies.ts +217 -579
  337. package/src/hooks/cloud-enrollment-cli.ts +66 -27
  338. package/src/hooks/cloud-managed-policies.ts +6 -1
  339. package/src/hooks/configure-wizard.ts +56 -369
  340. package/src/hooks/custom-hooks-loader.ts +89 -6
  341. package/src/hooks/fp-home.ts +92 -4
  342. package/src/hooks/fp-reset.ts +34 -2
  343. package/src/hooks/handler.ts +181 -11
  344. package/src/hooks/harness-cli.ts +79 -25
  345. package/src/hooks/hook-activity-store.ts +13 -3
  346. package/src/hooks/install-prompt.ts +25 -3
  347. package/src/hooks/manager.ts +424 -172
  348. package/src/hooks/migrations.ts +2 -2
  349. package/src/hooks/pack-cli.ts +885 -0
  350. package/src/hooks/pack-failclosed.ts +189 -0
  351. package/src/hooks/pack-manifest.ts +352 -0
  352. package/src/hooks/pack-store.ts +891 -0
  353. package/src/hooks/policy-catalog.ts +535 -0
  354. package/src/hooks/policy-evaluator.ts +24 -16
  355. package/src/hooks/policy-registry.ts +8 -2
  356. package/src/hooks/policy-types.ts +26 -0
  357. package/src/hooks/session-pause-cli.ts +33 -9
  358. package/src/hooks/tui.ts +695 -39
  359. package/src/hooks/types.ts +15 -1
  360. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0_usx0j._.js +0 -3
  361. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0_zk340._.js +0 -3
  362. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0agdw2a._.js +0 -3
  363. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0d1u_na._.js +0 -3
  364. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0oy4kxb._.js +0 -3
  365. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0zf034w._.js +0 -3
  366. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1tvn4wf._.js +0 -3
  367. package/.next/standalone/.next/server/chunks/_0lxbzdq._.js +0 -3
  368. package/.next/standalone/.next/server/chunks/_1ger0a2._.js +0 -3
  369. package/.next/standalone/.next/server/chunks/_1zuiiy3._.js +0 -3
  370. package/.next/standalone/.next/server/chunks/node_modules_posthog-node_dist_entrypoints_index_node_mjs_1iiwm5e._.js +0 -3
  371. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__07tbbf3._.js +0 -3
  372. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0kc8_v5._.js +0 -4
  373. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0kgy9da._.js +0 -4
  374. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0mhqk5a._.js +0 -4
  375. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0pltpn8._.js +0 -4
  376. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0qkcyf-._.js +0 -4
  377. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0w0x_ok._.js +0 -4
  378. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1_dfocn._.js +0 -4
  379. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1bfex62._.js +0 -5
  380. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1kzapg5._.js +0 -6
  381. package/.next/standalone/.next/server/chunks/ssr/_0lvyuv4._.js +0 -23
  382. package/.next/standalone/.next/static/chunks/2_2lhuojq2xop.js +0 -1
  383. package/.next/standalone/.next/static/chunks/36fw6yk1s6x_f.js +0 -1
  384. package/.next/standalone/.next/static/chunks/3k7mxbp9hiom0.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/src/hooks/policy-presets.ts +0 -135
  388. /package/.next/standalone/.next/static/{ZklXxyA5ygsWSQz2u_zii → I5kEb9kSgYS63j4iqfVLO}/_buildManifest.js +0 -0
  389. /package/.next/standalone/.next/static/{ZklXxyA5ygsWSQz2u_zii → I5kEb9kSgYS63j4iqfVLO}/_clientMiddlewareManifest.js +0 -0
  390. /package/.next/standalone/.next/static/{ZklXxyA5ygsWSQz2u_zii → I5kEb9kSgYS63j4iqfVLO}/_ssgManifest.js +0 -0
@@ -0,0 +1,214 @@
1
+ """Shared helpers for write/mutation commands (create / update / delete / …).
2
+
3
+ Centralising these guarantees every domain behaves the same way:
4
+ * request bodies come from discrete flags **or** ``--file``/stdin JSON,
5
+ * mutations confirm before acting but never block a scripted/`--json` run,
6
+ * created/updated resources render identically,
7
+ * each action emits a privacy-safe telemetry event.
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ import dataclasses
13
+ import json
14
+ import sys
15
+ from pathlib import Path
16
+ from typing import Any, Dict, Optional
17
+
18
+ import typer
19
+
20
+ from .. import _click_compat as click # the Click Typer is running; see _click_compat
21
+ from .. import analytics, output
22
+ from .._context import AppState
23
+ from ..errors import NotFoundError
24
+
25
+ # Telemetry properties a write action may carry. Names/enums/coarse counts only —
26
+ # never ids, emails, names, URLs, SQL, key plaintext, or any customer value.
27
+ # A count is named `<thing>_count`, the form the dashboard and the API's own fields
28
+ # use, so the same quantity queries as one series across surfaces. Bare `count` is the
29
+ # older spelling, kept only so anything still sending it lands rather than vanishing.
30
+ _SAFE_PROP_KEYS = frozenset(
31
+ {"success", "resource", "via", "mode", "destructive", "row_count_bucket",
32
+ "permission_count", "url_count", "trigger_kind", "severity_present", "count"}
33
+ )
34
+
35
+
36
+ def resolve_one(
37
+ items,
38
+ handle: str,
39
+ *,
40
+ kind: str,
41
+ list_cmd: str,
42
+ key: str = "name",
43
+ ref: str = "named",
44
+ match_id: bool = True,
45
+ plural: Optional[str] = None,
46
+ casefold: bool = False,
47
+ ):
48
+ """Find the single ``items`` entry whose ``key`` (or, as a fallback, ``id``) equals ``handle``.
49
+
50
+ The one place name/email→object resolution lives, so every command fails identically:
51
+ * exactly one match → return it,
52
+ * none → ``raise NotFoundError`` (exit 6) with a "run `fp <list_cmd>`" hint,
53
+ * several → ``raise click.UsageError`` (exit 2) telling the caller to use the id.
54
+
55
+ The single error chokepoint (app.py) renders the raised exception — JSON envelope under
56
+ ``--json`` (stdout), red box otherwise — so callers never branch on ``state.json`` themselves.
57
+
58
+ ``casefold`` compares case-insensitively. Opt-in rather than the default because it is only
59
+ correct where the server itself normalises: emails are lowercased on create, so
60
+ ``fp users create Alice.Chen@Example.com`` stores ``alice.chen@example.com`` and every
61
+ later ``fp users show Alice.Chen@Example.com`` answered "no user with email" — the CLI
62
+ denying a member it had just created, with the exact string the caller had just typed.
63
+ Key and query names are stored verbatim, so folding them would let ``PROD`` resolve
64
+ ``prod`` and silently act on the wrong object.
65
+ """
66
+
67
+ def norm(value):
68
+ return value.casefold() if casefold and isinstance(value, str) else value
69
+
70
+ target = norm(handle)
71
+ matches = [it for it in items if norm(getattr(it, key, None)) == target]
72
+ if not matches and match_id:
73
+ matches = [it for it in items if str(getattr(it, "id", "")) == handle]
74
+ if len(matches) == 1:
75
+ return matches[0]
76
+ if not matches:
77
+ raise NotFoundError(
78
+ f'no {kind} {ref} "{handle}"', hint=f"run `fp {list_cmd}` to list them"
79
+ )
80
+ raise click.UsageError(
81
+ f'"{handle}" matches {len(matches)} {plural or kind + "s"} — disambiguate with the id.'
82
+ )
83
+
84
+
85
+ def read_text_arg(path: str, *, flag: str = "--file") -> str:
86
+ """Read a file's text (or stdin when ``path == "-"``), mapping a missing/unreadable
87
+ path to a clean usage error (exit 2) instead of a raw OSError traceback. Shared by
88
+ every ``--file`` / ``@file`` reader so they all fail the same, predictable way."""
89
+ if path == "-":
90
+ return sys.stdin.read()
91
+ try:
92
+ return Path(path).read_text()
93
+ except OSError as exc:
94
+ raise click.BadParameter(
95
+ f"cannot read '{path}': {exc.strerror or exc}", param_hint=flag
96
+ )
97
+
98
+
99
+ def read_body(
100
+ state: AppState,
101
+ file: Optional[str],
102
+ inline: Optional[Dict[str, Any]] = None,
103
+ ) -> Dict[str, Any]:
104
+ """Resolve a request body.
105
+
106
+ ``--file PATH`` (or ``--file -`` for stdin) JSON wins and must be a JSON object;
107
+ otherwise the discrete-flag ``inline`` dict is used (``None`` values dropped, so a
108
+ partial PATCH/PUT body stays clean). Giving both a populated ``--file`` and discrete
109
+ flags is a usage error.
110
+ """
111
+ cleaned_inline = {k: v for k, v in (inline or {}).items() if v is not None}
112
+ if file is None:
113
+ return cleaned_inline
114
+ raw = read_text_arg(file)
115
+ try:
116
+ parsed = json.loads(raw)
117
+ except json.JSONDecodeError as exc:
118
+ raise click.BadParameter(f"--file is not valid JSON: {exc}", param_hint="--file")
119
+ if not isinstance(parsed, dict):
120
+ raise click.BadParameter("--file must contain a JSON object.", param_hint="--file")
121
+ if cleaned_inline:
122
+ raise click.UsageError("Pass either --file or the discrete flags, not both.")
123
+ return parsed
124
+
125
+
126
+ def confirm(state: AppState, action: str, *, assume_yes: bool, destructive: bool = False) -> None:
127
+ """Confirm a mutation, unless it is safe to skip.
128
+
129
+ Skips the prompt when ``--yes`` is given, ``--json`` is set, or stdin is not a TTY
130
+ (all mean a script/agent is driving and a blocking prompt would hang). Otherwise
131
+ prompts and aborts (exit 1) on "no".
132
+ """
133
+ if assume_yes or state.json or not sys.stdin.isatty():
134
+ return
135
+ if destructive:
136
+ output.warn(f"This cannot be undone: {action}.")
137
+ typer.confirm(f"{action}?", default=False, abort=True, err=True)
138
+
139
+
140
+ def should_prompt(state: AppState, assume_yes: bool) -> bool:
141
+ """Whether to actually show an interactive confirm. ``False`` (auto-proceed) when ``--yes``
142
+ is given, ``--json`` is set, or stdin is not a TTY — all mean a script/agent is driving and
143
+ a blocking prompt would hang. Lets a caller render a bespoke confirm (e.g. the users-update
144
+ diff prompt) behind the same guard the shared helpers use."""
145
+ return not (assume_yes or state.json or not sys.stdin.isatty())
146
+
147
+
148
+ def confirm_action(
149
+ state: AppState, action: str, target: str, *, consequence: str, assume_yes: bool,
150
+ glyph: str = "⚠", color: Optional[str] = None, title: str = "confirm",
151
+ ) -> bool:
152
+ """Confirm a named action. Returns ``True`` to proceed, ``False`` if the user declined.
153
+
154
+ Proceeds without prompting under :func:`should_prompt`'s conditions. Otherwise shows the
155
+ shared boxed prompt (default NO) via ``output.confirm_prompt`` — amber ⚠ by default
156
+ (destructive), or a caller-supplied glyph/colour (e.g. the calm ACCENT ``↑`` for
157
+ ``users enable``).
158
+ """
159
+ if not should_prompt(state, assume_yes):
160
+ return True
161
+ return output.confirm_prompt(action, target, consequence, glyph=glyph, color=color, title=title)
162
+
163
+
164
+ def confirm_destructive(
165
+ state: AppState, action: str, target: str, *, consequence: str, assume_yes: bool
166
+ ) -> bool:
167
+ """Confirm a destructive, named action (regenerate / disable / revoke a key) with the shared
168
+ amber ⚠ prompt. Thin wrapper over :func:`confirm_action` with the destructive defaults."""
169
+ return confirm_action(state, action, target, consequence=consequence, assume_yes=assume_yes)
170
+
171
+
172
+ def _to_dict(resource: Any) -> Any:
173
+ if dataclasses.is_dataclass(resource) and not isinstance(resource, type):
174
+ return dataclasses.asdict(resource)
175
+ return resource
176
+
177
+
178
+ def emit_resource(
179
+ state: AppState,
180
+ resource: Any,
181
+ *,
182
+ action: str,
183
+ title: str,
184
+ summary_fields=None,
185
+ ) -> None:
186
+ """Render the result of a mutation.
187
+
188
+ ``--json``: the resource verbatim. Human: a green success line plus a field/value
189
+ table of ``summary_fields`` (or all fields when omitted).
190
+ """
191
+ data = _to_dict(resource)
192
+ if state.json:
193
+ output.emit_json(data)
194
+ return
195
+ output.success(f"{title} {action}.")
196
+ if isinstance(data, dict):
197
+ keys = summary_fields if summary_fields is not None else list(data.keys())
198
+ rows = [[k, _cell(data.get(k))] for k in keys]
199
+ if rows:
200
+ output.print_table(["Field", "Value"], rows)
201
+
202
+
203
+ def _cell(value: Any) -> str:
204
+ if value is None:
205
+ return "-"
206
+ if isinstance(value, (dict, list)):
207
+ return json.dumps(value, ensure_ascii=False)
208
+ return str(value)
209
+
210
+
211
+ def record_action(event: str, **props: Any) -> None:
212
+ """Emit a privacy-safe per-action telemetry event (allowlisted properties only)."""
213
+ safe = {k: v for k, v in props.items() if k in _SAFE_PROP_KEYS}
214
+ analytics.capture(event, safe)
@@ -0,0 +1,407 @@
1
+ """FailproofAI Cloud assistant (Claude) from the CLI: agent health / models / chats / ask / show / rename / delete.
2
+
3
+ Every ``ask`` is saved to a **chat**: with ``--chat <id>`` it continues that chat, without it a
4
+ new chat is started and its short id printed (so you can continue it). ``chats`` lists them;
5
+ ``show`` / ``rename`` / ``delete`` manage one; ``models`` lists the models you can pass to
6
+ ``ask --model``.
7
+
8
+ Chats are referenced by a **short chat-id** — the first 8 chars (the segment before the UUID's
9
+ first ``-``) shown in ``agent chats``. The CLI resolves that prefix back to the full chat against
10
+ your chat list (so the server/dashboard keep the full id intact); a prefix that matches none → a
11
+ ``chat not found`` box, one that matches several → an ``ambiguous`` box. If the assistant asks for
12
+ interactive input mid-turn, ``ask`` aborts cleanly rather than hang.
13
+ """
14
+
15
+ from __future__ import annotations
16
+
17
+ import sys
18
+ from typing import Optional
19
+
20
+ import typer
21
+
22
+ from .. import _click_compat as click # the Click Typer is running; see _click_compat
23
+ from .. import client as api
24
+ from .. import output
25
+ from .._context import GLOBALS_EPILOG, AppState, deny_in_key_mode, require_auth
26
+ from ..errors import NotFoundError
27
+ from . import _write
28
+
29
+ # The assistant has NO server route at all: `/agent/chat` and `/agent/health` are
30
+ # implemented by the dashboard itself, and the conversation routes are per-human
31
+ # (keyed by actor) and deliberately excluded from the versioned API. So there is
32
+ # nothing for an API key to call here — refuse before opening a connection.
33
+ _KEY_MODE_REASON = (
34
+ "the assistant runs in the dashboard and its chats belong to a person — there is "
35
+ "no API route behind it for a key to call"
36
+ )
37
+
38
+ # The server's default title for a freshly-created chat; we auto-title from the first
39
+ # question only while the title is still this (so an explicit one is kept).
40
+ _DEFAULT_TITLE = "New conversation"
41
+
42
+
43
+ def _text_of(content: object) -> str:
44
+ """Extract the display text from a stored message ``content`` — mirrors the
45
+ dashboard's `textOf` so CLI and dashboard render the same thread identically."""
46
+ if isinstance(content, str):
47
+ return content
48
+ if isinstance(content, dict) and isinstance(content.get("text"), str):
49
+ return content["text"]
50
+ return ""
51
+
52
+
53
+ def _title_from_question(question: str) -> str:
54
+ """A short, single-line chat title derived from the first question."""
55
+ collapsed = " ".join(question.split())
56
+ if not collapsed:
57
+ return _DEFAULT_TITLE
58
+ return collapsed[:57] + "…" if len(collapsed) > 60 else collapsed
59
+
60
+
61
+ def _models_of(health: dict) -> list:
62
+ """The model allowlist from an agent-health payload (handles list or single shapes)."""
63
+ models = health.get("models")
64
+ if isinstance(models, list) and models:
65
+ return [str(m) for m in models]
66
+ one = health.get("default_model") or health.get("defaultModel") or health.get("model")
67
+ return [str(one)] if one else []
68
+
69
+
70
+ def _default_model_of(health: dict) -> Optional[str]:
71
+ return health.get("default_model") or health.get("defaultModel") or health.get("model")
72
+
73
+
74
+ def _is_configured(health: dict) -> bool:
75
+ """Whether the assistant is usable on this deployment — ``enabled`` AND (``llm_configured``
76
+ when the server reports it; absent → assume configured if enabled)."""
77
+ enabled = bool(health.get("enabled"))
78
+ llm = health.get("llm_configured")
79
+ return enabled and (llm if isinstance(llm, bool) else True)
80
+
81
+
82
+ def _validate_model(cctx, model: str) -> None:
83
+ """Reject a ``--model`` that isn't on the deployment's allowlist. Skips the check when
84
+ the deployment reports no models (lets the server decide / fall back)."""
85
+ models = _models_of(api.agent_health(cctx))
86
+ if models and model not in models:
87
+ raise typer.BadParameter(
88
+ f"model not found {model!r}; available: {', '.join(models)} (see `fp agent models`).",
89
+ param_hint="--model",
90
+ )
91
+
92
+
93
+ def _resolve_chat_or_exit(state: AppState, cctx, handle: str) -> dict:
94
+ """Resolve a chat by its short id PREFIX (the first-8 ``chat-id`` shown in ``agent chats``) or
95
+ a full id, matching against the chat list. Returns the matched chat summary (full ``id`` +
96
+ ``title`` + ``message_count``). None → boxed ``chat not found`` (exit 6); more than one → boxed
97
+ ``ambiguous`` (exit 2). Resolving via the list also avoids fetching a non-existent id directly
98
+ (which the server answers with a 500). Shared by show / rename / delete / ask --chat."""
99
+ chats = api.list_conversations(cctx)
100
+ exact = [c for c in chats if str(c.get("id")) == handle]
101
+ matches = exact or [c for c in chats if str(c.get("id", "")).startswith(handle)]
102
+ if len(matches) == 1:
103
+ return matches[0]
104
+ if not matches:
105
+ raise NotFoundError(
106
+ f"chat not found: {handle}", hint="run `fp agent chats` to list them"
107
+ )
108
+ ids = [str(c.get("id")) for c in matches]
109
+ raise click.UsageError(
110
+ f"ambiguous chat id: {handle} — matches {len(ids)}; use a longer prefix or the full id"
111
+ )
112
+
113
+
114
+ def agent_health(ctx: typer.Context) -> None:
115
+ """Show whether the assistant is configured on this deployment.
116
+
117
+ Needs `agent:use`. With `--json`: `{enabled, llm_configured?, model?, models?, default_model?}`.
118
+
119
+ Example:
120
+
121
+ * `fp --json agent health`
122
+ """
123
+ state: AppState = ctx.obj
124
+ deny_in_key_mode(state, "agent", _KEY_MODE_REASON)
125
+ cctx = require_auth(state)
126
+ data = api.agent_health(cctx)
127
+ if state.json:
128
+ output.emit_json(data)
129
+ return
130
+ output.render_agent_health(
131
+ configured=_is_configured(data),
132
+ default_model=_default_model_of(data),
133
+ model_count=len(_models_of(data)),
134
+ )
135
+
136
+
137
+ def agent_models(ctx: typer.Context) -> None:
138
+ """List the assistant models available on this deployment.
139
+
140
+ Needs `agent:use`. The choices (and the default) come from the agent service's
141
+ allowlist; pass any of them to `agent ask --model <name>`. With `--json`:
142
+ `{"models": [...], "default_model": "..."}`.
143
+
144
+ Example:
145
+
146
+ * `fp agent models`
147
+ """
148
+ state: AppState = ctx.obj
149
+ deny_in_key_mode(state, "agent", _KEY_MODE_REASON)
150
+ cctx = require_auth(state)
151
+ data = api.agent_health(cctx)
152
+ models = _models_of(data)
153
+ default = _default_model_of(data)
154
+ if state.json:
155
+ output.emit_json({"models": models, "default_model": default})
156
+ return
157
+ output.render_agent_models(models, default_model=default)
158
+ if not models:
159
+ output.agent_unconfigured_note()
160
+
161
+
162
+ def agent_chats(
163
+ ctx: typer.Context,
164
+ ) -> None:
165
+ """List your saved chats in a boxed table, newest first.
166
+
167
+ Shows `chat-id · title · messages · updated` — `chat-id` is the SHORT id (the first 8 chars; a
168
+ prefix the `agent show`/`rename`/`delete`/`ask --chat` commands resolve), and `updated` is the
169
+ chat's last-activity age. Needs `agent:use`. With `--json`: `{"chats": [{id, title, updated_at,
170
+ message_count}]}` (full id).
171
+
172
+ Example:
173
+
174
+ * `fp agent chats`
175
+ * `fp --json agent chats`
176
+ """
177
+ state: AppState = ctx.obj
178
+ deny_in_key_mode(state, "agent", _KEY_MODE_REASON)
179
+ cctx = require_auth(state)
180
+ chats = api.list_conversations(cctx)
181
+ if state.json:
182
+ output.emit_json({"chats": chats})
183
+ return
184
+ output.render_agent_chats(chats)
185
+
186
+
187
+ def agent_show(
188
+ ctx: typer.Context,
189
+ chat_id: str = typer.Argument(..., metavar="CHAT_ID", help="Chat id — the short one from `agent chats` (a prefix) or a full id."),
190
+ ) -> None:
191
+ """Show a chat as a readable thread — its title, then each turn (`you` / `assistant`).
192
+
193
+ Pass the **short chat-id** from `agent chats` (the first 8 chars; the CLI resolves the prefix
194
+ to the full chat) or a full id. Not-found → a `chat not found` error box, exit 6. Needs
195
+ `agent:use`. With `--json`: `{title, messages: [...]}`.
196
+
197
+ Example:
198
+
199
+ * `fp agent show 07854990`
200
+ """
201
+ state: AppState = ctx.obj
202
+ deny_in_key_mode(state, "agent", _KEY_MODE_REASON)
203
+ cctx = require_auth(state)
204
+ chat = _resolve_chat_or_exit(state, cctx, chat_id) # short prefix → the full chat
205
+ data = api.get_conversation(cctx, chat["id"])
206
+ if state.json:
207
+ output.emit_json(data)
208
+ return
209
+ output.render_agent_show(title=data.get("title"), messages=data.get("messages", []) or [], chat_id=chat["id"])
210
+
211
+
212
+ def agent_rename(
213
+ ctx: typer.Context,
214
+ chat_id: str = typer.Argument(..., metavar="CHAT_ID", help="Chat id (short prefix or full)."),
215
+ title: str = typer.Option(..., "--title", help="New title for the chat."),
216
+ ) -> None:
217
+ """Rename a chat, referenced by its **short chat-id** (a prefix) or full id.
218
+
219
+ Not-found → a `chat not found` error box, exit 6. Needs `agent:use`. With `--json`:
220
+ `{"id": "<full id>", "title": "<title>"}`.
221
+
222
+ Example:
223
+
224
+ * `fp agent rename 07854990 --title "page walkthrough"`
225
+ """
226
+ state: AppState = ctx.obj
227
+ deny_in_key_mode(state, "agent", _KEY_MODE_REASON)
228
+ cctx = require_auth(state)
229
+ chat = _resolve_chat_or_exit(state, cctx, chat_id) # validates + gives the full id + old title
230
+ old_title = chat.get("title")
231
+ api.rename_conversation(cctx, chat["id"], title)
232
+ _write.record_action("agent_chat_renamed", resource="conversation", success=True)
233
+ if state.json:
234
+ output.emit_json({"id": chat["id"], "title": title})
235
+ else:
236
+ output.render_agent_renamed(chat_id=chat["id"], title=title, old_title=old_title)
237
+
238
+
239
+ def agent_delete(
240
+ ctx: typer.Context,
241
+ chat_id: str = typer.Argument(..., metavar="CHAT_ID", help="Chat id to delete (short prefix or full)."),
242
+ yes: bool = typer.Option(False, "--yes", "-y", help="Skip the confirmation prompt. The prompt only appears on an interactive terminal: under --json, or with stdin redirected, this command proceeds without asking."),
243
+ ) -> None:
244
+ """Delete a chat, referenced by its **short chat-id** (a prefix) or full id. Cannot be undone.
245
+
246
+ Shows an amber preview, then confirms. Not-found → a `chat not found` error box, exit 6. Needs
247
+ `agent:use`. With `--json`: `{"deleted": true, "id": "<full id>", "title": "<title>"}` (or
248
+ `{"cancelled": true}` on a declined prompt).
249
+
250
+ Example:
251
+
252
+ * `fp agent delete 07854990`
253
+ """
254
+ state: AppState = ctx.obj
255
+ deny_in_key_mode(state, "agent", _KEY_MODE_REASON)
256
+ cctx = require_auth(state)
257
+ chat = _resolve_chat_or_exit(state, cctx, chat_id) # validates + powers the preview
258
+ title = chat.get("title") or chat["id"]
259
+ message_count = chat.get("message_count") or 0
260
+ if _write.should_prompt(state, yes):
261
+ output.render_agent_delete_preview(title=title, message_count=message_count, chat_id=chat["id"])
262
+ if not output.confirm_agent_delete():
263
+ if state.json:
264
+ output.emit_json({"cancelled": True})
265
+ else:
266
+ output.print_cancelled("nothing deleted")
267
+ return
268
+ api.delete_conversation(cctx, chat["id"])
269
+ _write.record_action("agent_chat_deleted", resource="conversation", success=True, destructive=True)
270
+ if state.json:
271
+ output.emit_json({"deleted": True, "id": chat["id"], "title": title})
272
+ else:
273
+ output.agent_deleted(title)
274
+
275
+
276
+ def agent_ask(
277
+ ctx: typer.Context,
278
+ message: Optional[str] = typer.Argument(None, metavar="MESSAGE", help="Your question (or pipe it on stdin)."),
279
+ chat: Optional[str] = typer.Option(None, "--chat", help="Continue this chat — its short id from `agent chats` (a prefix) or full id. Omit to start a NEW chat (its id is printed)."),
280
+ model: Optional[str] = typer.Option(None, "--model", help="Model to use (see `agent models`)."),
281
+ page_context: Optional[str] = typer.Option(None, "--page-context", help="Optional context string to ground the answer."),
282
+ ) -> None:
283
+ """Ask the assistant a question — every ask is saved to a chat.
284
+
285
+ Pass the question as the **positional MESSAGE** (or pipe it on stdin). Needs `agent:use`.
286
+
287
+ * **No `--chat`** → starts a **new chat**, answers, persists, and prints the new chat's short
288
+ id (continue it with `--chat <short id>`); the first question auto-titles the chat.
289
+ * **`--chat <id>`** → continues that chat (short prefix or full id): its prior thread is sent
290
+ for context and this turn is appended. Chats also show up in the dashboard assistant.
291
+
292
+ The answer renders in a boxed `assistant` card (interactively); when piped it's the raw answer
293
+ on **stdout** (a clean payload). A brand-new chat is created only once an answer lands — if the
294
+ assistant needs interactive input it aborts (exit 1) and leaves no empty chat. With `--json`:
295
+ `{answer, tools, interrupted, error, chat_id}`.
296
+
297
+ Examples:
298
+
299
+ * `fp agent ask "which agents errored most in the last day?"`
300
+ * `fp agent ask "and how about the last week?" --chat 07854990`
301
+ * `fp agent ask "summarize p95 latency" --model claude-opus-4-7`
302
+ """
303
+ state: AppState = ctx.obj
304
+ deny_in_key_mode(state, "agent", _KEY_MODE_REASON)
305
+ if not message:
306
+ message = sys.stdin.read().strip() if not sys.stdin.isatty() else typer.prompt("question", err=True)
307
+ if not message:
308
+ raise typer.BadParameter("Provide a question as the MESSAGE argument or on stdin.")
309
+ cctx = require_auth(state)
310
+ if model is not None:
311
+ _validate_model(cctx, model)
312
+
313
+ # Continuing a chat → resolve the (short) handle to a full id and load its prior thread;
314
+ # a new chat starts empty (and is created only on success, so a failed ask leaves no orphan).
315
+ full_chat_id: Optional[str] = None
316
+ prior = []
317
+ if chat:
318
+ full_chat_id = _resolve_chat_or_exit(state, cctx, chat)["id"]
319
+ prior = api.get_conversation(cctx, full_chat_id).get("messages", []) or []
320
+ thread = [
321
+ {"role": str(m.get("role", "user")), "content": {"text": _text_of(m.get("content"))}}
322
+ for m in prior
323
+ ]
324
+ thread.append({"role": "user", "content": {"text": message}})
325
+
326
+ # The reply streams server-side (an LLM turn + tool calls can take many seconds); show a
327
+ # themed spinner on stderr so the CLI doesn't look hung. No-op under --json / non-TTY.
328
+ with output.thinking("thinking…", enabled=not state.json):
329
+ result = api.agent_chat_oneshot(
330
+ cctx, messages=thread, conversation_id=full_chat_id, page_context=page_context, model=model
331
+ )
332
+
333
+ chat_id = full_chat_id
334
+ if not result.get("interrupted") and not result.get("error"):
335
+ if chat_id is None:
336
+ # New chat: set the question-derived title at creation (one POST) instead of
337
+ # create-with-empty-title then a follow-up rename PATCH.
338
+ chat_id = (api.create_conversation(cctx, title=_title_from_question(message)) or {}).get("id")
339
+ if chat_id:
340
+ full = thread + [{"role": "assistant", "content": {"text": result.get("answer", "")}}]
341
+ api.replace_messages(cctx, chat_id, full)
342
+ if full_chat_id is not None and not prior:
343
+ # Continuing a chat that had no messages yet → title it from this first question
344
+ # (a brand-new chat is already titled at create_conversation above).
345
+ api.rename_conversation(cctx, chat_id, _title_from_question(message))
346
+
347
+ _write.record_action(
348
+ "agent_chat", resource="conversation",
349
+ success=not result.get("interrupted"),
350
+ mode="continue" if chat else "new",
351
+ )
352
+ if state.json:
353
+ output.emit_json({**result, "chat_id": chat_id})
354
+ else:
355
+ for tool in result.get("tools", []):
356
+ output.agent_tool_used(tool)
357
+ answer = result.get("answer", "")
358
+ if sys.stdout.isatty():
359
+ output.render_agent_answer(answer, model=model) # boxed assistant card
360
+ else:
361
+ print(answer) # raw answer → stdout (pipeable)
362
+ if chat_id and not chat: # a fresh chat was created
363
+ output.render_agent_new_chat(chat_id)
364
+ if result.get("interrupted"):
365
+ output.agent_error(f"the assistant needs interactive input ({result.get('error')}); not supported in the CLI")
366
+ raise typer.Exit(code=1)
367
+ if result.get("error"):
368
+ output.agent_error(f"assistant error: {result.get('error')}")
369
+ raise typer.Exit(code=1)
370
+
371
+
372
+ _AGENT_GROUP_HELP = """Chat with the FailproofAI Cloud assistant (Claude) about your org's observability data.
373
+
374
+ `ask` a question — each ask is saved to a **chat**. Continue a chat with `--chat <short id>`
375
+ (the first 8 chars shown in `agent chats`). The assistant can read your sessions, events,
376
+ evaluations, alerts, keys, and saved queries — scoped to your org.
377
+
378
+ **Subcommands:** `health` · `models` · `chats` · `ask` · `show` · `rename` · `delete`
379
+
380
+ **Examples:**
381
+
382
+ * `fp agent health` — is the assistant configured on this deployment?
383
+ * `fp agent models` — which models can I pass to `ask --model`?
384
+ * `fp agent ask "which agents errored most in the last day?"` — ask (starts a new chat)
385
+ * `fp agent ask "and the last week?" --chat 07854990` — continue a chat by its short id
386
+ * `fp agent chats` — list your chats (with their short ids)
387
+ * `fp agent show 07854990` — read a chat's full thread
388
+ * `fp agent rename 07854990 --title "latency dig"` · `fp agent delete 07854990`
389
+ """
390
+
391
+
392
+ def register(app: typer.Typer) -> None:
393
+ agent = typer.Typer(
394
+ no_args_is_help=True,
395
+ rich_markup_mode="markdown",
396
+ context_settings={"help_option_names": ["-h", "--help"]},
397
+ help=_AGENT_GROUP_HELP,
398
+ )
399
+ # Order: discover (health/models) → list (chats) → use (ask) → manage one (show/rename/delete).
400
+ agent.command("health", epilog=GLOBALS_EPILOG)(agent_health)
401
+ agent.command("models", epilog=GLOBALS_EPILOG)(agent_models)
402
+ agent.command("chats", epilog=GLOBALS_EPILOG)(agent_chats)
403
+ agent.command("ask", epilog=GLOBALS_EPILOG)(agent_ask)
404
+ agent.command("show", epilog=GLOBALS_EPILOG)(agent_show)
405
+ agent.command("rename", epilog=GLOBALS_EPILOG)(agent_rename)
406
+ agent.command("delete", epilog=GLOBALS_EPILOG)(agent_delete)
407
+ app.add_typer(agent, name="agent")