failproofai 1.0.12-beta.1 → 1.0.12

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 (236) 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 +4 -4
  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.nft.json +1 -1
  7. package/.next/standalone/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
  8. package/.next/standalone/.next/server/app/_global-error.html +1 -1
  9. package/.next/standalone/.next/server/app/_global-error.rsc +2 -2
  10. package/.next/standalone/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +2 -2
  11. package/.next/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +2 -2
  12. package/.next/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  13. package/.next/standalone/.next/server/app/_not-found/page/server-reference-manifest.json +1 -1
  14. package/.next/standalone/.next/server/app/_not-found/page.js.nft.json +1 -1
  15. package/.next/standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  16. package/.next/standalone/.next/server/app/_not-found.html +1 -1
  17. package/.next/standalone/.next/server/app/_not-found.rsc +3 -3
  18. package/.next/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +3 -3
  19. package/.next/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +2 -2
  20. package/.next/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  21. package/.next/standalone/.next/server/app/api/audit/invite/route.js.nft.json +1 -1
  22. package/.next/standalone/.next/server/app/api/audit/run/route.js.nft.json +1 -1
  23. package/.next/standalone/.next/server/app/api/audit/status/route.js.nft.json +1 -1
  24. package/.next/standalone/.next/server/app/api/auth/login-request/route.js.nft.json +1 -1
  25. package/.next/standalone/.next/server/app/api/auth/login-verify/route.js.nft.json +1 -1
  26. package/.next/standalone/.next/server/app/api/auth/logout/route.js.nft.json +1 -1
  27. package/.next/standalone/.next/server/app/api/auth/status/route.js.nft.json +1 -1
  28. package/.next/standalone/.next/server/app/api/download/[project]/[session]/route.js.nft.json +1 -1
  29. package/.next/standalone/.next/server/app/audit/page/server-reference-manifest.json +2 -2
  30. package/.next/standalone/.next/server/app/audit/page.js.nft.json +1 -1
  31. package/.next/standalone/.next/server/app/audit/page_client-reference-manifest.js +1 -1
  32. package/.next/standalone/.next/server/app/index.html +1 -1
  33. package/.next/standalone/.next/server/app/index.rsc +3 -3
  34. package/.next/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +2 -2
  35. package/.next/standalone/.next/server/app/index.segments/_full.segment.rsc +3 -3
  36. package/.next/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  37. package/.next/standalone/.next/server/app/page/server-reference-manifest.json +1 -1
  38. package/.next/standalone/.next/server/app/page.js.nft.json +1 -1
  39. package/.next/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
  40. package/.next/standalone/.next/server/app/policies/page/server-reference-manifest.json +14 -14
  41. package/.next/standalone/.next/server/app/policies/page.js +2 -2
  42. package/.next/standalone/.next/server/app/policies/page.js.nft.json +1 -1
  43. package/.next/standalone/.next/server/app/policies/page_client-reference-manifest.js +1 -1
  44. package/.next/standalone/.next/server/app/project/[name]/page/server-reference-manifest.json +1 -1
  45. package/.next/standalone/.next/server/app/project/[name]/page.js.nft.json +1 -1
  46. package/.next/standalone/.next/server/app/project/[name]/page_client-reference-manifest.js +1 -1
  47. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page/react-loadable-manifest.json +2 -2
  48. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page/server-reference-manifest.json +2 -2
  49. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page.js.nft.json +1 -1
  50. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page_client-reference-manifest.js +1 -1
  51. package/.next/standalone/.next/server/app/projects/page/server-reference-manifest.json +1 -1
  52. package/.next/standalone/.next/server/app/projects/page.js.nft.json +1 -1
  53. package/.next/standalone/.next/server/app/projects/page_client-reference-manifest.js +1 -1
  54. package/.next/standalone/.next/server/app/settings/page/server-reference-manifest.json +8 -8
  55. package/.next/standalone/.next/server/app/settings/page.js.nft.json +1 -1
  56. package/.next/standalone/.next/server/app/settings/page_client-reference-manifest.js +1 -1
  57. package/.next/standalone/.next/server/chunks/_0tovk6q0miud6._.js +1 -1
  58. package/.next/standalone/.next/server/chunks/_0trp3ycqv17cl._.js +1 -1
  59. package/.next/standalone/.next/server/chunks/_1ek68lnh2daur._.js +2 -2
  60. package/.next/standalone/.next/server/chunks/package_json_[json]_cjs_1nxcc4vkpxxu7._.js +1 -1
  61. package/.next/standalone/.next/server/chunks/src_hooks_0_em82s16zfym._.js +1 -1
  62. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__08ch3staq3ejw._.js +1 -1
  63. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0bigvy9xmv2ka._.js +1 -1
  64. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0h86bl2qf0k_o._.js +1 -1
  65. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0pia5h3p8re4c._.js +1 -1
  66. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1-ey9fgesfbc0._.js +1 -1
  67. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__15usvs-ms-0hf._.js +1 -1
  68. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__167mipqmjprq5._.js +3 -0
  69. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__16gb3_92zvk5x._.js +1 -1
  70. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1avi2kypusyb_._.js +1 -1
  71. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1r7drlqli-ado._.js +1 -1
  72. package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__0b78b0j8iyv6l._.js → [root-of-the-server]__1vofkb2oq7p7e._.js} +1 -1
  73. package/.next/standalone/.next/server/chunks/ssr/_0eoura3_9gjb-._.js +1 -1
  74. package/.next/standalone/.next/server/chunks/ssr/{_1a3tubds7s7ow._.js → _0ouu886szh6yj._.js} +4 -4
  75. package/.next/standalone/.next/server/chunks/ssr/_0pedwblqdoh58._.js +1 -1
  76. package/.next/standalone/.next/server/chunks/ssr/_1d98fyfrcaqhy._.js +3 -0
  77. package/.next/standalone/.next/server/chunks/ssr/_1djqs9i27e4kg._.js +1 -1
  78. package/.next/standalone/.next/server/chunks/ssr/_next-internal_server_app_policies_page_actions_1sp2-yo5_vm5f.js +2 -2
  79. package/.next/standalone/.next/server/chunks/ssr/app_actions_get-scheduled-audit_ts_0ei9snixb5fd-._.js +1 -1
  80. package/.next/standalone/.next/server/chunks/ssr/app_audit__components_audit-dashboard_tsx_0p9ud47q0xq5w._.js +1 -1
  81. package/.next/standalone/.next/server/chunks/ssr/app_global-error_tsx_1kp6l3x33jqb6._.js +1 -1
  82. package/.next/standalone/.next/server/chunks/ssr/app_policies_hooks-client_tsx_19dqvpcz0uxur._.js +2 -2
  83. package/.next/standalone/.next/server/chunks/ssr/app_settings_02tf1h47jmhhd._.js +1 -1
  84. package/.next/standalone/.next/server/chunks/ssr/{node_modules_html-to-image_es_index_1i8oid1gc7s1f.js → node_modules_html-to-image_es_index_0tqc05n832v7a.js} +1 -1
  85. package/.next/standalone/.next/server/chunks/ssr/package_json_[json]_cjs_013tntikdft59._.js +1 -1
  86. package/.next/standalone/.next/server/chunks/ssr/src_hooks_0agdmptetetdt._.js +1 -1
  87. package/.next/standalone/.next/server/chunks/ssr/src_hooks_0mdwgjisv4fub._.js +1 -1
  88. package/.next/standalone/.next/server/chunks/ssr/src_hooks_fp-config_ts_04t589gp4p60j._.js +1 -1
  89. package/.next/standalone/.next/server/chunks/ssr/src_hooks_pack-cli_ts_0t7me658zqmh_._.js +1 -1
  90. package/.next/standalone/.next/server/middleware-build-manifest.js +3 -3
  91. package/.next/standalone/.next/server/pages/404.html +1 -1
  92. package/.next/standalone/.next/server/pages/500.html +1 -1
  93. package/.next/standalone/.next/server/preview-props.json +3 -3
  94. package/.next/standalone/.next/server/server-reference-manifest.js +1 -1
  95. package/.next/standalone/.next/server/server-reference-manifest.json +23 -23
  96. package/.next/standalone/.next/static/chunks/{1b32-afqacaen.js → 0el9n3h9_zp5m.js} +1 -1
  97. package/.next/standalone/.next/static/chunks/0k7g_8x3o6gd-.js +1 -0
  98. package/.next/standalone/.next/static/chunks/{42e8zab314654.js → 0umzct31duk5g.js} +1 -1
  99. package/.next/standalone/.next/static/chunks/{24w3x2da9sk49.js → 0wobqgosirs8p.js} +1 -1
  100. package/.next/standalone/.next/static/chunks/12rug3jilwqzs.js +1 -0
  101. package/.next/standalone/.next/static/chunks/{2-tf6_7u623kd.js → 1kduj74-715jh.js} +1 -1
  102. package/.next/standalone/.next/static/chunks/2701amhrbsbot.js +1 -0
  103. package/.next/standalone/.next/static/chunks/{18433qz-8njv3.js → 27cavoc8paelg.js} +1 -1
  104. package/.next/standalone/.next/static/chunks/{0_zfad4co-t2z.js → 2ve23a7keupjw.js} +1 -1
  105. package/.next/standalone/.next/static/chunks/3bv6xaoitjpnh.js +6 -0
  106. package/.next/standalone/app/actions/update-jev-config.ts +5 -3
  107. package/.next/standalone/app/settings/jev-panel.tsx +1 -1
  108. package/.next/standalone/fp-cloud-cli/CHANGELOG.md +28 -18
  109. package/.next/standalone/fp-cloud-cli/README.md +127 -9
  110. package/.next/standalone/fp-cloud-cli/fp_cli/app.py +15 -1
  111. package/.next/standalone/fp-cloud-cli/fp_cli/client.py +199 -34
  112. package/.next/standalone/fp-cloud-cli/fp_cli/cloud_output.py +523 -0
  113. package/.next/standalone/fp-cloud-cli/fp_cli/commands/_cloud.py +45 -0
  114. package/.next/standalone/fp-cloud-cli/fp_cli/commands/_write.py +3 -1
  115. package/.next/standalone/fp-cloud-cli/fp_cli/commands/account_cmds.py +171 -0
  116. package/.next/standalone/fp-cloud-cli/fp_cli/commands/agent_cmds.py +80 -7
  117. package/.next/standalone/fp-cloud-cli/fp_cli/commands/alerts_cmds.py +23 -0
  118. package/.next/standalone/fp-cloud-cli/fp_cli/commands/audits_cmds.py +2 -0
  119. package/.next/standalone/fp-cloud-cli/fp_cli/commands/contracts_cmds.py +67 -0
  120. package/.next/standalone/fp-cloud-cli/fp_cli/commands/dashboards_cmds.py +159 -0
  121. package/.next/standalone/fp-cloud-cli/fp_cli/commands/evaluators_cmds.py +380 -0
  122. package/.next/standalone/fp-cloud-cli/fp_cli/commands/events_cmds.py +14 -1
  123. package/.next/standalone/fp-cloud-cli/fp_cli/commands/fleet_cmds.py +12 -216
  124. package/.next/standalone/fp-cloud-cli/fp_cli/commands/incidents_cmds.py +82 -6
  125. package/.next/standalone/fp-cloud-cli/fp_cli/commands/list_cmds.py +4 -1
  126. package/.next/standalone/fp-cloud-cli/fp_cli/commands/orgs_cmds.py +17 -0
  127. package/.next/standalone/fp-cloud-cli/fp_cli/commands/policies_cmds.py +143 -1
  128. package/.next/standalone/fp-cloud-cli/fp_cli/commands/queries_cmds.py +70 -0
  129. package/.next/standalone/fp-cloud-cli/fp_cli/commands/sentiment_cmds.py +49 -0
  130. package/.next/standalone/fp-cloud-cli/fp_cli/commands/sessions_cmds.py +95 -1
  131. package/.next/standalone/fp-cloud-cli/fp_cli/commands/settings_cmds.py +2 -0
  132. package/.next/standalone/fp-cloud-cli/fp_cli/commands/usage_cmds.py +21 -1
  133. package/.next/standalone/fp-cloud-cli/fp_cli/commands/users_cmds.py +14 -0
  134. package/.next/standalone/fp-cloud-cli/fp_cli/enforcement.py +7 -53
  135. package/.next/standalone/fp-cloud-cli/fp_cli/errors.py +3 -0
  136. package/.next/standalone/fp-cloud-cli/fp_cli/evaluator_spec.py +266 -0
  137. package/.next/standalone/fp-cloud-cli/fp_cli/output.py +189 -152
  138. package/.next/standalone/fp-cloud-cli/fp_cli/theme.py +21 -24
  139. package/.next/standalone/fp-cloud-cli/skill/SKILL.md +124 -23
  140. package/.next/standalone/fp-cloud-cli/skill/agents/openai.yaml +2 -1
  141. package/.next/standalone/fp-cloud-cli/skill/references/commands.md +173 -22
  142. package/.next/standalone/fp-cloud-cli/tests/fake_cloud_smoke.py +312 -0
  143. package/.next/standalone/fp-cloud-cli/tests/test_cloud_account.py +125 -0
  144. package/.next/standalone/fp-cloud-cli/tests/test_cloud_agent.py +253 -0
  145. package/.next/standalone/fp-cloud-cli/tests/test_cloud_human_views.py +287 -0
  146. package/.next/standalone/fp-cloud-cli/tests/test_cloud_jev.py +32 -197
  147. package/.next/standalone/fp-cloud-cli/tests/test_cloud_observability.py +116 -0
  148. package/.next/standalone/fp-cloud-cli/tests/test_cloud_transport.py +141 -0
  149. package/.next/standalone/fp-cloud-cli/tests/test_commands.py +3 -3
  150. package/.next/standalone/fp-cloud-cli/tests/test_dashboards_agent.py +3 -3
  151. package/.next/standalone/fp-cloud-cli/tests/test_evaluator_authoring.py +449 -0
  152. package/.next/standalone/fp-cloud-cli/tests/test_facets.py +1 -1
  153. package/.next/standalone/fp-cloud-cli/tests/test_help_table_coverage.py +15 -0
  154. package/.next/standalone/fp-cloud-cli/tests/test_operator.py +1 -1
  155. package/.next/standalone/fp-cloud-cli/tests/test_orgs.py +1 -1
  156. package/.next/standalone/fp-cloud-cli/tests/test_output.py +37 -37
  157. package/.next/standalone/fp-cloud-cli/tests/test_round4_behaviour.py +144 -0
  158. package/.next/standalone/fp-cloud-cli/tests/test_screen_language.py +80 -0
  159. package/.next/standalone/fp-cloud-cli/tests/test_session_reevaluate.py +33 -0
  160. package/.next/standalone/fp-cloud-cli/tests/test_staging_parity.py +198 -0
  161. package/.next/standalone/fp-cloud-cli/tests/test_whoami.py +1 -1
  162. package/.next/standalone/hermes-plugin/__init__.py +11 -0
  163. package/.next/standalone/hermes-plugin/plugin.yaml +1 -1
  164. package/.next/standalone/package.json +11 -10
  165. package/.next/standalone/sdk/python/uv.lock +31 -8
  166. package/.next/standalone/sdk/typescript/integration/fixtures/ai-4/package-lock.json +68 -176
  167. package/.next/standalone/sdk/typescript/integration/fixtures/ai-4/package.json +2 -2
  168. package/.next/standalone/sdk/typescript/integration/fixtures/ai-5/package-lock.json +4 -4
  169. package/.next/standalone/sdk/typescript/integration/fixtures/ai-5/package.json +1 -1
  170. package/.next/standalone/sdk/typescript/integration/fixtures/ai-6/package-lock.json +7 -7
  171. package/.next/standalone/sdk/typescript/integration/fixtures/ai-6/package.json +1 -1
  172. package/.next/standalone/sdk/typescript/integration/fixtures/ai-7/package-lock.json +7 -7
  173. package/.next/standalone/sdk/typescript/integration/fixtures/ai-7/package.json +1 -1
  174. package/.next/standalone/sdk/typescript/integration/fixtures/langchain-0.3/package-lock.json +98 -115
  175. package/.next/standalone/sdk/typescript/integration/fixtures/langchain-0.3/package.json +3 -3
  176. package/.next/standalone/sdk/typescript/integration/fixtures/langchain-1/package-lock.json +4 -4
  177. package/.next/standalone/sdk/typescript/integration/fixtures/langchain-1/package.json +1 -1
  178. package/.next/standalone/sdk/typescript/integration/fixtures/langchain-dup-core/package-lock.json +4 -4
  179. package/.next/standalone/sdk/typescript/integration/fixtures/langchain-dup-core/package.json +1 -1
  180. package/.next/standalone/sdk/typescript/integration/fixtures/llamaindex-0.11/package-lock.json +149 -171
  181. package/.next/standalone/sdk/typescript/integration/fixtures/llamaindex-0.11/package.json +2 -2
  182. package/.next/standalone/sdk/typescript/integration/fixtures/llamaindex-0.12/package-lock.json +4 -4
  183. package/.next/standalone/sdk/typescript/integration/fixtures/llamaindex-0.12/package.json +1 -1
  184. package/.next/standalone/sdk/typescript/integration/fixtures/mastra-0/package-lock.json +25 -25
  185. package/.next/standalone/sdk/typescript/integration/fixtures/mastra-0/package.json +1 -1
  186. package/.next/standalone/sdk/typescript/integration/fixtures/mastra-1/package-lock.json +7 -7
  187. package/.next/standalone/sdk/typescript/integration/fixtures/mastra-1/package.json +1 -1
  188. package/.next/standalone/sdk/typescript/integration/fixtures/nextjs/package-lock.json +10 -10
  189. package/.next/standalone/sdk/typescript/integration/fixtures/nextjs/package.json +1 -1
  190. package/.next/standalone/sdk/typescript/integration/fixtures/runtimes/package-lock.json +7 -7
  191. package/.next/standalone/sdk/typescript/integration/fixtures/runtimes/package.json +1 -1
  192. package/.next/standalone/server.js +1 -1
  193. package/dist/cli.mjs +1760 -638
  194. package/dist/worker.mjs +426 -76
  195. package/hermes-plugin/__init__.py +11 -0
  196. package/hermes-plugin/plugin.yaml +1 -1
  197. package/package.json +11 -10
  198. package/src/audit/open-browser.ts +24 -7
  199. package/src/hooks/agent-roster.ts +9 -1
  200. package/src/hooks/agent-skill.ts +206 -0
  201. package/src/hooks/cloud-connection.ts +27 -51
  202. package/src/hooks/cloud-introspect.ts +5 -2
  203. package/src/hooks/cloud-login.ts +248 -0
  204. package/src/hooks/cloud-managed-policies.ts +54 -4
  205. package/src/hooks/codex-system-hooks.ts +10 -4
  206. package/src/hooks/config-status.ts +9 -3
  207. package/src/hooks/configure-wizard.ts +356 -32
  208. package/src/hooks/delivery-health.ts +47 -2
  209. package/src/hooks/effective-reviewers.ts +14 -0
  210. package/src/hooks/fp-config.ts +19 -0
  211. package/src/hooks/fp-home.ts +1 -1
  212. package/src/hooks/handler.ts +43 -5
  213. package/src/hooks/hook-activity-store.ts +7 -1
  214. package/src/hooks/integrations.ts +18 -3
  215. package/src/hooks/jev-activity.ts +117 -1
  216. package/src/hooks/jev-cli.ts +7 -9
  217. package/src/hooks/manager.ts +80 -46
  218. package/src/hooks/policy-evaluator.ts +17 -0
  219. package/src/hooks/policy-reviewability.ts +4 -2
  220. package/src/hooks/semantic/cloud-jev.ts +219 -28
  221. package/src/hooks/semantic/combine.ts +26 -1
  222. package/src/hooks/semantic/intent.ts +127 -24
  223. package/src/hooks/semantic/jev-client.ts +1 -1
  224. package/src/hooks/semantic/jev-config.ts +6 -27
  225. package/src/hooks/semantic/jev-review.ts +111 -4
  226. package/src/hooks/tui.ts +280 -0
  227. package/src/hooks/uninstall-cli.ts +12 -1
  228. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0588st-tpiuia._.js +0 -3
  229. package/.next/standalone/.next/server/chunks/ssr/_128ebxr83nk6g._.js +0 -3
  230. package/.next/standalone/.next/static/chunks/0jutamwqz8pg5.js +0 -1
  231. package/.next/standalone/.next/static/chunks/114hbolvrv94e.js +0 -1
  232. package/.next/standalone/.next/static/chunks/1p-hsi58i-6h0.js +0 -6
  233. package/.next/standalone/.next/static/chunks/3-pku9qwv1yfu.js +0 -1
  234. /package/.next/standalone/.next/static/{xq8UgF5yFxty1VWxY2hyn → D7LY1YSrw96ZxKSpxxrFw}/_buildManifest.js +0 -0
  235. /package/.next/standalone/.next/static/{xq8UgF5yFxty1VWxY2hyn → D7LY1YSrw96ZxKSpxxrFw}/_clientMiddlewareManifest.js +0 -0
  236. /package/.next/standalone/.next/static/{xq8UgF5yFxty1VWxY2hyn → D7LY1YSrw96ZxKSpxxrFw}/_ssgManifest.js +0 -0
@@ -1,4 +1,4 @@
1
- """The fleet: fleet list / show / deploy / jev-mode / diff / history / rollback / rename.
1
+ """The fleet: fleet list / show / deploy / diff / history / rollback / rename.
2
2
 
3
3
  What each machine is TOLD to enforce. Authoring the policies is `fp policies`;
4
4
  what they actually did is `fp guardrails`.
@@ -26,12 +26,10 @@ from .. import output
26
26
  from .._context import GLOBALS_EPILOG, AppState, deny_in_key_mode, require_auth
27
27
  from .. import _click_compat as click # the Click Typer is running; see _click_compat
28
28
  from ..enforcement import (
29
- JEV_MODES,
30
29
  RefError,
31
30
  RefUsageError,
32
31
  check_race,
33
32
  disabled_ids,
34
- jev_mode_after,
35
33
  latest_versions,
36
34
  plan_deploy,
37
35
  version_kinds,
@@ -157,7 +155,7 @@ def fleet_deploy(
157
155
  add: Optional[List[str]] = typer.Option(
158
156
  None, "--add",
159
157
  help="Add or update a policy: `id`, `id@version`, `id:effect` or `id@version:effect`. "
160
- "`observe` on a `both` policy observes its JavaScript and withholds its Jev checks.",
158
+ "`observe` records what the policy (its JavaScript and its Jev checks) would decide, and decides nothing.",
161
159
  ),
162
160
  remove: Optional[List[str]] = typer.Option(None, "--remove", help="Remove a policy by id."),
163
161
  replace: Optional[List[str]] = typer.Option(
@@ -176,14 +174,6 @@ def fleet_deploy(
176
174
  False, "--create",
177
175
  help="Allow deploying to a machine id that has not checked in yet (pre-staging).",
178
176
  ),
179
- jev_mode: Optional[str] = typer.Option(
180
- None, "--jev-mode",
181
- help="Set the machine's Jev mode from FailproofAI Cloud: off, observe, enforce (Jev's "
182
- "checks BLOCK calls as well as clearing what they review) — or local to stop "
183
- "overriding the machine's own. Jev checks run on FailproofAI Cloud; nothing is "
184
- "installed on the machine. Machine-wide: installed packs' checks too. "
185
- "Omitted: unchanged.",
186
- ),
187
177
  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."),
188
178
  ) -> None:
189
179
  """Change what a machine enforces, showing the full resulting set first.
@@ -198,20 +188,10 @@ def fleet_deploy(
198
188
  Any kind deploys the same way. Jev checks run on FailproofAI Cloud; nothing
199
189
  is installed on the machine: a `both` policy sends the machine its
200
190
  JavaScript, and the machine asks FailproofAI Cloud about each checked tool
201
- call while its Jev mode is `observe` or `enforce`. An effect applies to
202
- JavaScript only — an `observe` `both` policy observes its JavaScript and its
203
- Jev checks are not asked — and a `jev` policy is watched through the Jev
204
- MODE instead: `--jev-mode observe` asks Jev and logs what it says while the
205
- regex policies decide; `enforce` lets Jev's checks BLOCK calls (every `jev`
206
- policy's deny checks) as well as clear the regex verdicts they review; `off`
207
- stops Jev on the machine; `local` hands the choice back to the machine. The
208
- mode is the MACHINE's: it also governs the checks of any pack installed
209
- there, and it overrides the machine's own mode and `jev.json`, a local
210
- `off` included. Without `--jev-mode` it is left as it is.
211
-
212
- `--jev-mode` with no change to the set changes ONLY the mode (the same as
213
- `fp fleet jev-mode`): the policy set — disabled policies' assignments
214
- included — is not read back and rewritten.
191
+ call. An `observe` effect applies to every kind: the policy's JavaScript
192
+ and its Jev checks are evaluated and recorded, and decide nothing. There is
193
+ no Jev mode to set: FailproofAI Cloud turns Jev on for a machine while any
194
+ `jev` or `both` policy is deployed to it, and off when none is.
215
195
 
216
196
  **Concurrency.** The write is a full replace with no server-side lock, so the
217
197
  CLI records the generation it read and refuses if the result is not exactly
@@ -224,19 +204,18 @@ def fleet_deploy(
224
204
  * `fp fleet deploy ci-runner-01 --add no-force-push`
225
205
  * `fp fleet deploy ci-runner-01 --add prod-guard@1:observe --remove old-rule`
226
206
  * `fp fleet deploy ci-runner-01 --set no-force-push --set no-secret-echo`
227
- * `fp fleet deploy ci-runner-01 --add prod-db-intent --jev-mode observe`
228
- * `fp fleet deploy ci-runner-01 --jev-mode local`
207
+ * `fp fleet deploy ci-runner-01 --add prod-db-intent:observe`
229
208
  """
230
209
  state: AppState = ctx.obj
231
210
  deny_in_key_mode(state, "fleet deploy", _KEY_MODE_REASON)
232
211
  cctx = require_auth(state)
233
212
 
234
- if not add and not remove and replace is None and jev_mode is None and not targets and not all_agents:
213
+ if not add and not remove and replace is None and not targets and not all_agents:
235
214
  # Exit 2 for the same reason `--set` with `--add` is: no flag
236
215
  # combination was given that this command can act on. Both are the
237
216
  # caller's command line, not the server's answer.
238
217
  raise click.UsageError(
239
- "nothing to do — pass --add, --remove, --set, --target, --all-agents, or --jev-mode. "
218
+ "nothing to do — pass --add, --remove, --set, --target, or --all-agents. "
240
219
  "`fp fleet show <machine>` prints the current set."
241
220
  )
242
221
 
@@ -268,8 +247,6 @@ def fleet_deploy(
268
247
  replace=replace,
269
248
  latest=latest,
270
249
  disabled=disabled_ids(published),
271
- jev_mode=jev_mode,
272
- current_jev_mode=current.jev_mode if current else None,
273
250
  kinds=version_kinds(published),
274
251
  targets=targets or (),
275
252
  all_agents=all_agents or (),
@@ -298,16 +275,6 @@ def fleet_deploy(
298
275
  output.deployment_unchanged(machine_id)
299
276
  return
300
277
 
301
- # The mode alone: its own route, which never touches the policy set. A
302
- # deploy is a full replace of the set the CLI read, and that set leaves out
303
- # disabled policies' assignments — so a mode change sent as a deploy deleted
304
- # them, and reverted anything changed since the read (CONTRACT C9.1). A
305
- # machine with no deployment yet has nothing to lose and no mode to change:
306
- # its first deploy, carrying the mode, is how it gets one.
307
- if plan.jev_mode is not None and not plan.set_changes and current is not None:
308
- _apply_jev_modes(state, cctx, [(machine_id, current.jev_mode)], plan.jev_mode, yes=yes, plan=plan)
309
- return
310
-
311
278
  if not output.is_json():
312
279
  output.render_deploy_plan(plan)
313
280
  dropped = len(plan.removed)
@@ -323,7 +290,7 @@ def fleet_deploy(
323
290
  output.print_cancelled()
324
291
  return
325
292
 
326
- result = api.deploy_policies(cctx, machine_id, plan.result, jev_mode=jev_mode)
293
+ result = api.deploy_policies(cctx, machine_id, plan.result)
327
294
  check_race(plan.base, result.deployment)
328
295
 
329
296
  if output.is_json():
@@ -336,176 +303,6 @@ def fleet_deploy(
336
303
  output.deployment_applied(machine_id, result.deployment, len(result.policies))
337
304
 
338
305
 
339
- def _jev_mode_consequence(mode: str, count: int) -> str:
340
- what = f"{count} machine{'s' if count != 1 else ''}"
341
- tail = {
342
- "enforce": "; in enforce Jev's deny checks BLOCK calls, and it covers installed packs' checks too",
343
- "observe": "; in observe Jev only records what it would have decided",
344
- "off": "; Jev stops on them, a local jev.json included",
345
- "local": "; each machine's own jev.json decides again",
346
- }.get(mode, "")
347
- return f"this sets the Jev mode to {mode} on {what} and leaves their policy sets untouched{tail}"
348
-
349
-
350
- def _apply_jev_modes(
351
- state: AppState,
352
- cctx,
353
- targets: List[tuple],
354
- mode: str,
355
- *,
356
- yes: bool,
357
- plan=None,
358
- ) -> None:
359
- """Set `mode` on each `(machine_id, current_mode)`, through the mode-only route.
360
-
361
- Shared by `fleet jev-mode` and `fleet deploy --jev-mode` with no set change.
362
- A machine already on `mode` is left alone; one that fails is reported and
363
- the rest still go ahead (exit 1 at the end, naming them).
364
- """
365
- after = jev_mode_after(mode)
366
- rows = [
367
- {"machineId": mid, "from": cur or "local", "to": after or "local", "changed": (cur or None) != after}
368
- for mid, cur in targets
369
- ]
370
- todo = [r for r in rows if r["changed"]]
371
- if not todo:
372
- if output.is_json():
373
- out = {"mode": mode, "machines": rows, "applied": False}
374
- if plan is not None:
375
- out = {"plan": plan.to_dict(), "deployment": None, "applied": False}
376
- output.emit_json(out)
377
- return
378
- output.jev_mode_unchanged([r["machineId"] for r in rows], mode)
379
- return
380
-
381
- if not output.is_json():
382
- output.render_jev_mode_plan(rows)
383
- if not _write.confirm_destructive(
384
- state, "change the Jev mode on",
385
- todo[0]["machineId"] if len(todo) == 1 else f"{len(todo)} machines",
386
- consequence=_jev_mode_consequence(mode, len(todo)),
387
- assume_yes=yes,
388
- ):
389
- if output.is_json():
390
- output.emit_json({"cancelled": True, "applied": False, "machines": rows})
391
- else:
392
- output.print_cancelled()
393
- return
394
-
395
- failed = []
396
- last = None
397
- for row in todo:
398
- try:
399
- result, changed = api.set_jev_mode(cctx, row["machineId"], mode)
400
- except AuthError:
401
- raise
402
- except FpCliError as exc:
403
- # One machine's refusal (a 404, a 403, a 422) is that machine's;
404
- # the rest still go ahead, and the exit code says it was not all.
405
- row["error"] = exc.format_message() if hasattr(exc, "format_message") else str(exc)
406
- failed.append(row["machineId"])
407
- continue
408
- last = result
409
- row["deployment"] = result.deployment
410
- row["changed"] = changed
411
-
412
- if output.is_json():
413
- if plan is not None:
414
- output.emit_json({
415
- "plan": plan.to_dict(),
416
- "deployment": last.to_dict() if last is not None else None,
417
- "applied": last is not None,
418
- "jevModeOnly": True,
419
- })
420
- else:
421
- output.emit_json({"mode": mode, "machines": rows, "applied": not failed})
422
- else:
423
- output.jev_mode_applied(rows)
424
- if failed:
425
- raise ApiError(
426
- f"the Jev mode could not be changed on {', '.join(failed)}"
427
- + ("" if len(failed) == len(todo) else " — the other machines were changed"),
428
- )
429
-
430
-
431
- def fleet_jev_mode(
432
- ctx: typer.Context,
433
- args: Optional[List[str]] = typer.Argument(
434
- None, metavar="[MACHINE]... MODE",
435
- help="The machines, then the mode: off, observe, enforce or local. With --all, just the mode.",
436
- ),
437
- all_machines: bool = typer.Option(
438
- False, "--all", help="Every machine that has a deployment (machines with none are skipped).",
439
- ),
440
- 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."),
441
- ) -> None:
442
- """Set the Jev mode FailproofAI Cloud gives machines, and nothing else.
443
-
444
- `off`, `observe` or `enforce` override each machine's own mode and
445
- `jev.json`, a local `off` included; `local` hands the choice back to the
446
- machine. Jev checks run on FailproofAI Cloud; nothing is installed on the
447
- machine: under `observe` or `enforce` it sends each checked tool call to
448
- FailproofAI Cloud. `observe` asks Jev and records what it says while the
449
- regex policies decide. `enforce` lets Jev's checks BLOCK calls — every `jev`
450
- policy's deny checks — as well as clear the regex verdicts they review. The
451
- mode is the machine's: it also governs the checks of every pack installed
452
- there. A machine connected with `--no-transcripts` never asks, and reports
453
- `transcripts_disabled`.
454
-
455
- Only the mode changes: each machine's policy set, disabled policies'
456
- assignments included, is left exactly as it is, and a machine already on
457
- the mode is skipped. Each change is a new generation in `fp fleet history`.
458
- A machine with no deployment has no mode to change — `fp fleet deploy
459
- <machine> --jev-mode <mode>` gives it its first deployment with one.
460
-
461
- Needs `policies:write`. With `--json`: `{mode, applied, machines:[{machineId,
462
- from, to, changed, deployment?, error?}]}`.
463
-
464
- Examples:
465
-
466
- * `fp fleet jev-mode ci-runner-01 observe`
467
- * `fp fleet jev-mode ci-runner-01 ci-runner-02 enforce`
468
- * `fp fleet jev-mode --all local`
469
- """
470
- state: AppState = ctx.obj
471
- deny_in_key_mode(state, "fleet jev-mode", _KEY_MODE_REASON)
472
- words = list(args or [])
473
- if not words:
474
- raise click.UsageError("pass the mode: off, observe, enforce or local")
475
- mode, machine_ids = words[-1], list(dict.fromkeys(words[:-1]))
476
- if mode not in JEV_MODES:
477
- raise click.UsageError(f"{mode!r} is not a Jev mode — one of {', '.join(JEV_MODES)}")
478
- if all_machines and machine_ids:
479
- raise click.UsageError("--all sets every machine; do not name machines as well")
480
- if not all_machines and not machine_ids:
481
- raise click.UsageError("name at least one machine, or pass --all")
482
- cctx = require_auth(state)
483
-
484
- deployments = {d.machine_id: d for d in api.list_deployments(cctx)}
485
- if all_machines:
486
- targets = [(mid, deployments[mid].jev_mode) for mid in sorted(deployments)]
487
- else:
488
- known = {m.machine_id for m in api.list_machines(cctx)} | set(deployments)
489
- unknown = [mid for mid in machine_ids if mid not in known]
490
- if unknown:
491
- raise NotFoundError(f"no machine {', '.join(repr(u) for u in unknown)} has checked in")
492
- bare = [mid for mid in machine_ids if mid not in deployments]
493
- if bare:
494
- raise ApiError(
495
- f"{', '.join(bare)} {'has' if len(bare) == 1 else 'have'} no deployment, so there is no Jev "
496
- "mode to change",
497
- hint=f"`fp fleet deploy <machine> --jev-mode {mode}` gives a machine its first deployment with the mode",
498
- )
499
- targets = [(mid, deployments[mid].jev_mode) for mid in machine_ids]
500
- if not targets:
501
- if output.is_json():
502
- output.emit_json({"mode": mode, "machines": [], "applied": False})
503
- else:
504
- output.info("no machine has a deployment yet — nothing to set")
505
- return
506
- _apply_jev_modes(state, cctx, targets, mode, yes=yes)
507
-
508
-
509
306
  def fleet_diff(
510
307
  ctx: typer.Context,
511
308
  machine_id: Optional[str] = typer.Argument(None, help="Machine id. Omit for the whole fleet."),
@@ -606,7 +403,7 @@ def fleet_rollback(
606
403
  _require_machine(cctx, machine_id)
607
404
  current = api.get_deployment(cctx, machine_id)
608
405
  # The mode that generation carried comes back with it, NULL (`local`)
609
- # included — a rollback past an `--jev-mode off` turns enforcement back on.
406
+ # included, as the server reports it.
610
407
  # Read it first, so the prompt says so rather than the fleet finding out.
611
408
  target = next(
612
409
  (e for e in api.deployment_history(cctx, machine_id) if e.get("deployment") == deployment), None)
@@ -669,12 +466,11 @@ def register(app: typer.Typer) -> None:
669
466
  no_args_is_help=True,
670
467
  rich_markup_mode="markdown",
671
468
  context_settings={"help_option_names": ["-h", "--help"]},
672
- help="The fleet and what each machine enforces (list / show / deploy / jev-mode / diff / history / rollback / rename).",
469
+ help="The fleet and what each machine enforces (list / show / deploy / diff / history / rollback / rename).",
673
470
  )
674
471
  fleet_app.command("list", epilog=GLOBALS_EPILOG)(fleet_list)
675
472
  fleet_app.command("show", epilog=GLOBALS_EPILOG)(fleet_show)
676
473
  fleet_app.command("deploy", epilog=GLOBALS_EPILOG)(fleet_deploy)
677
- fleet_app.command("jev-mode", epilog=GLOBALS_EPILOG)(fleet_jev_mode)
678
474
  fleet_app.command("diff", epilog=GLOBALS_EPILOG)(fleet_diff)
679
475
  fleet_app.command("history", epilog=GLOBALS_EPILOG)(fleet_history)
680
476
  fleet_app.command("rollback", epilog=GLOBALS_EPILOG)(fleet_rollback)
@@ -14,7 +14,8 @@ import typer
14
14
 
15
15
  from .. import client as api
16
16
  from .. import output
17
- from .._context import GLOBALS_EPILOG, AppState, require_auth, validate_limit
17
+ from .. import _click_compat as click
18
+ from .._context import GLOBALS_EPILOG, AppState, require_auth, resolve_dates, validate_limit
18
19
  from ..errors import ApiError, ForbiddenError, NotFoundError
19
20
  from . import _write
20
21
 
@@ -68,12 +69,40 @@ def _fail(state: AppState, exc: Exception, *, incident_id: str = "") -> None:
68
69
  raise exc
69
70
 
70
71
 
72
+ def _issue_filters(
73
+ agent_id, env, machine, source, audit_id, assignee, archived, search, since,
74
+ ) -> dict:
75
+ if archived is not None and archived not in ("false", "true", "only", "all"):
76
+ raise click.UsageError("--archived must be false, only or all")
77
+ if source is not None:
78
+ bad = [s for s in source.split(",") if s.strip() and s.strip() not in ("alert", "audit", "manual")]
79
+ if bad:
80
+ raise click.UsageError("--source must be alert, audit or manual (CSV)")
81
+ opened_from = None
82
+ if since is not None:
83
+ opened_from, _ = resolve_dates(since, None, None)
84
+ return {
85
+ "agent": agent_id, "environment": env, "machine": machine, "source": source,
86
+ "audit_id": audit_id, "assignee": assignee, "archived": archived, "q": search,
87
+ "opened_from": opened_from,
88
+ }
89
+
90
+
71
91
  def incidents_list(
72
92
  ctx: typer.Context,
73
93
  state_filter: Optional[str] = typer.Option(None, "--state", help="Filter by state(s): firing, acknowledged, resolved, closed (CSV)."),
74
94
  alert_id: Optional[str] = typer.Option(None, "--alert-id", help="Only incidents for this alert."),
75
95
  limit: int = typer.Option(50, "--limit", "-n", help="Max incidents to return."),
76
96
  show_id: bool = typer.Option(False, "--show-id", help="Show the full incident id instead of the short form (always full in --json)."),
97
+ agent_id: Optional[str] = typer.Option(None, "--agent-id", help="Only issues touching these agent ids (CSV)."),
98
+ env: Optional[str] = typer.Option(None, "--env", help="Only these environments (CSV)."),
99
+ machine: Optional[str] = typer.Option(None, "--machine", help="Only these machine ids (CSV)."),
100
+ source: Optional[str] = typer.Option(None, "--source", help="alert, audit and/or manual (CSV)."),
101
+ audit_id: Optional[str] = typer.Option(None, "--audit-id", help="Only issues from these audits (CSV of ids)."),
102
+ assignee: Optional[str] = typer.Option(None, "--assignee", help="Emails, `me` or `unassigned` (CSV)."),
103
+ archived: Optional[str] = typer.Option(None, "--archived", help="false (default), only or all."),
104
+ search: Optional[str] = typer.Option(None, "--search", help="Case-insensitive text in the title or summary."),
105
+ since: Optional[str] = typer.Option(None, "--since", help="Opened within: 15m 1h 6h 24h 7d."),
77
106
  ) -> None:
78
107
  """List incidents in a boxed table (newest-opened first).
79
108
 
@@ -94,7 +123,8 @@ def incidents_list(
94
123
  _validate_states(state_filter)
95
124
  validate_limit(limit)
96
125
  cctx = require_auth(state)
97
- incidents = api.list_incidents(cctx, state=state_filter, alert_id=alert_id, limit=limit)
126
+ extra = _issue_filters(agent_id, env, machine, source, audit_id, assignee, archived, search, since)
127
+ incidents = api.list_incidents(cctx, state=state_filter, alert_id=alert_id, limit=limit, filters=extra)
98
128
  if state.json:
99
129
  output.emit_json({"issues": incidents})
100
130
  return
@@ -105,6 +135,16 @@ def incidents_list(
105
135
  def incidents_count(
106
136
  ctx: typer.Context,
107
137
  state_filter: Optional[str] = typer.Option(None, "--state", help="Filter by state(s) (CSV)."),
138
+ alert_id: Optional[str] = typer.Option(None, "--alert-id", help="Only issues from these alerts (CSV of ids)."),
139
+ agent_id: Optional[str] = typer.Option(None, "--agent-id", help="Only issues touching these agent ids (CSV)."),
140
+ env: Optional[str] = typer.Option(None, "--env", help="Only these environments (CSV)."),
141
+ machine: Optional[str] = typer.Option(None, "--machine", help="Only these machine ids (CSV)."),
142
+ source: Optional[str] = typer.Option(None, "--source", help="alert, audit and/or manual (CSV)."),
143
+ audit_id: Optional[str] = typer.Option(None, "--audit-id", help="Only issues from these audits (CSV of ids)."),
144
+ assignee: Optional[str] = typer.Option(None, "--assignee", help="Emails, `me` or `unassigned` (CSV)."),
145
+ archived: Optional[str] = typer.Option(None, "--archived", help="false (default), only or all."),
146
+ search: Optional[str] = typer.Option(None, "--search", help="Case-insensitive text in the title or summary."),
147
+ since: Optional[str] = typer.Option(None, "--since", help="Opened within: 15m 1h 6h 24h 7d."),
108
148
  ) -> None:
109
149
  """Count incidents (optionally by state) as a compact stat card.
110
150
 
@@ -118,7 +158,10 @@ def incidents_count(
118
158
  state: AppState = ctx.obj
119
159
  _validate_states(state_filter)
120
160
  cctx = require_auth(state)
121
- count = api.count_incidents(cctx, state=state_filter)
161
+ extra = _issue_filters(agent_id, env, machine, source, audit_id, assignee, archived, search, since)
162
+ if alert_id:
163
+ extra["alert_id"] = alert_id
164
+ count = api.count_incidents(cctx, state=state_filter, filters=extra)
122
165
  if state.json:
123
166
  output.emit_json({"count": count})
124
167
  else:
@@ -261,10 +304,11 @@ def incidents_close(
261
304
 
262
305
  For an issue that came from an audit, this also marks the finding `dismissed`. It does
263
306
  NOT write the finding's org-wide suppression: closing one issue never hides that pattern
264
- in your other audits. Use `fp audits finding-status <id> --action dismiss` for that.
307
+ in your other audits. Use `fp audits dismiss <finding-id>` for that.
265
308
 
266
- Needs `issues:close`. Exits 9 (conflict) if the issue already ended — an issue ends once,
267
- and closing must not overwrite the record that someone believed they had fixed it.
309
+ Needs `issues:close`. Exits 1 (the server answers 409 conflict) if the issue already
310
+ ended — an issue ends once, and closing must not overwrite the record that someone
311
+ believed they had fixed it.
268
312
 
269
313
  With `--json`: `{"closed": true, "id": "<id>"}` (or `{cancelled: true}` on a declined
270
314
  prompt).
@@ -666,6 +710,37 @@ def incidents_open(
666
710
  output.render_incident_opened(summary=summary, severity=sev, state=st, title=hero)
667
711
 
668
712
 
713
+ def incidents_facets(
714
+ ctx: typer.Context,
715
+ state_filter: Optional[str] = typer.Option(None, "--state", help="Filter by state(s) (CSV)."),
716
+ alert_id: Optional[str] = typer.Option(None, "--alert-id", help="Only issues from these alerts (CSV of ids)."),
717
+ agent_id: Optional[str] = typer.Option(None, "--agent-id", help="Only issues touching these agent ids (CSV)."),
718
+ env: Optional[str] = typer.Option(None, "--env", help="Only these environments (CSV)."),
719
+ machine: Optional[str] = typer.Option(None, "--machine", help="Only these machine ids (CSV)."),
720
+ source: Optional[str] = typer.Option(None, "--source", help="alert, audit and/or manual (CSV)."),
721
+ audit_id: Optional[str] = typer.Option(None, "--audit-id", help="Only issues from these audits (CSV of ids)."),
722
+ assignee: Optional[str] = typer.Option(None, "--assignee", help="Emails, `me` or `unassigned` (CSV)."),
723
+ archived: Optional[str] = typer.Option(None, "--archived", help="false (default), only or all."),
724
+ search: Optional[str] = typer.Option(None, "--search", help="Case-insensitive text in the title or summary."),
725
+ since: Optional[str] = typer.Option(None, "--since", help="Opened within: 15m 1h 6h 24h 7d."),
726
+ ) -> None:
727
+ """The values the issue filters can take right now (agents, environments, machines,
728
+ sources, assignees), with counts — the dashboard inbox's filter bar. Needs `issues:read`.
729
+
730
+ Example:
731
+
732
+ * `fp issues facets --state firing,acknowledged`
733
+ """
734
+ from . import _cloud
735
+ state: AppState = ctx.obj
736
+ _validate_states(state_filter)
737
+ extra = _issue_filters(agent_id, env, machine, source, audit_id, assignee, archived, search, since)
738
+ params = {"state": state_filter, "alert_id": alert_id, **extra}
739
+ data = api.cloud_request(require_auth(state), "GET", "/api/issues/facets",
740
+ params={k: v for k, v in params.items() if v is not None})
741
+ _cloud.emit(state, data, "Issue facets")
742
+
743
+
669
744
  def register(app: typer.Typer) -> None:
670
745
  inc = typer.Typer(
671
746
  no_args_is_help=True,
@@ -675,6 +750,7 @@ def register(app: typer.Typer) -> None:
675
750
  )
676
751
  inc.command("list", epilog=GLOBALS_EPILOG)(incidents_list)
677
752
  inc.command("count", epilog=GLOBALS_EPILOG)(incidents_count)
753
+ inc.command("facets", epilog=GLOBALS_EPILOG)(incidents_facets)
678
754
  inc.command("show", epilog=GLOBALS_EPILOG)(incidents_show)
679
755
  inc.command("ack", epilog=GLOBALS_EPILOG)(incidents_ack)
680
756
  inc.command("assign", epilog=GLOBALS_EPILOG)(incidents_assign)
@@ -24,6 +24,9 @@ _LIST_KINDS = {
24
24
  "hooks": ("hook_names", "events:read", "Hook names seen across events.", "seen across events"),
25
25
  "tools": ("tool_names", "events:read", "Tool names seen across events.", "seen across events"),
26
26
  "error_types": ("error_types", "events:read", "Error types seen across events.", "seen across events"),
27
+ "trigger_events": ("trigger_events", "events:read", "Hook trigger events seen across events.", "seen across events"),
28
+ "eval_envs": ("eval_environments", "evaluations:read", "Environments that have evaluation results.", "with evaluation results"),
29
+ "metric_keys": ("metric_keys", "evaluations:read", "Evaluation metric keys (metrics, not scores).", "evaluation metric keys"),
27
30
  }
28
31
 
29
32
  def _make_list_cmd(name: str, facet_key: str, perm: str, summary: str, description: str):
@@ -56,7 +59,7 @@ def register(app: typer.Typer) -> None:
56
59
  context_settings={"help_option_names": ["-h", "--help"]},
57
60
  help="List the distinct values behind the dashboard's filter dropdowns. Subcommands: "
58
61
  "**envs**, **agents**, **event_types**, **score_filters**, **models**, **hooks**, "
59
- "**tools**, **error_types**.",
62
+ "**tools**, **error_types**, **trigger_events**, **eval_envs**, **metric_keys**.",
60
63
  )
61
64
  for name, (facet_key, perm, summary, description) in _LIST_KINDS.items():
62
65
  list_app.command(name, epilog=GLOBALS_EPILOG)(_make_list_cmd(name, facet_key, perm, summary, description))
@@ -316,4 +316,21 @@ def register(app: typer.Typer) -> None:
316
316
  orgs_app.command("switch", epilog=GLOBALS_EPILOG)(org_switch)
317
317
  orgs_app.command("current", epilog=GLOBALS_EPILOG)(org_current)
318
318
  orgs_app.command("perms", epilog=GLOBALS_EPILOG)(org_perms)
319
+ orgs_app.command("create", epilog=GLOBALS_EPILOG)(org_create)
319
320
  app.add_typer(orgs_app, name="orgs")
321
+
322
+
323
+ def org_create(ctx: typer.Context, name: str = typer.Option(..., "--name"),
324
+ slug: Optional[str] = typer.Option(None, "--slug")) -> None:
325
+ """Create an organization (session only; does not switch the saved org)."""
326
+ from . import _cloud
327
+ from .account_cmds import request
328
+ from .. import _click_compat as click
329
+ _cloud.session_only(ctx.obj, "orgs create")
330
+ if not name.strip():
331
+ raise click.UsageError("--name cannot be empty")
332
+ import re
333
+ slug = slug or re.sub(r"[^a-z0-9]+", "-", name.lower()).strip("-")
334
+ if not re.fullmatch(r"[a-z0-9]+(?:-[a-z0-9]+)*", slug):
335
+ raise click.UsageError("--slug must be lowercase letters/digits separated by hyphens")
336
+ request(ctx, "POST", "/api/orgs", {"name": name, "slug": slug})