catherd-cli 1.4.0 → 2.0.0-beta.20261022113309

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 (320) hide show
  1. package/README.md +188 -169
  2. package/THIRD_PARTY_NOTICES.md +217 -35
  3. package/catalog/jev.json +27 -1
  4. package/catalog/models.json +56 -239
  5. package/package.json +6 -8
  6. package/plugin/.claude-plugin/plugin.json +1 -1
  7. package/plugin/.codex-plugin/plugin.json +1 -1
  8. package/plugin/bin/catherd-mcp +1 -1
  9. package/plugin/commands/catherd.md +2 -2
  10. package/plugin/skills/catherd/SKILL.md +97 -356
  11. package/plugin/skills/catherd-setup/SKILL.md +21 -20
  12. package/src/adapters/all.ts +0 -4
  13. package/src/adapters/backend.ts +19 -4
  14. package/src/adapters/claude-code/index.ts +126 -14
  15. package/src/adapters/codex/events.ts +10 -0
  16. package/src/adapters/codex/index.ts +17 -5
  17. package/src/adapters/cursor/home.ts +1 -1
  18. package/src/adapters/cursor/index.ts +10 -4
  19. package/src/adapters/opencode/index.ts +38 -1
  20. package/src/cli.ts +27 -27
  21. package/src/domain/agents.ts +3 -1
  22. package/src/domain/bars.ts +33 -107
  23. package/src/domain/blocked.ts +72 -0
  24. package/src/domain/branch.ts +51 -0
  25. package/src/domain/brief.ts +106 -0
  26. package/src/domain/catalog.ts +69 -313
  27. package/src/domain/changes.ts +23 -1
  28. package/src/domain/cost.ts +19 -29
  29. package/src/domain/errors.ts +6 -2
  30. package/src/domain/fail-lines.ts +26 -0
  31. package/src/domain/failover.ts +37 -75
  32. package/src/domain/findings.ts +47 -0
  33. package/src/domain/gate-brief.ts +53 -0
  34. package/src/domain/hints.ts +13 -2
  35. package/src/domain/ids.ts +13 -2
  36. package/src/domain/jev.ts +47 -2
  37. package/src/domain/judge.ts +124 -0
  38. package/src/domain/lane.ts +118 -3
  39. package/src/domain/milestone-memory.ts +53 -0
  40. package/src/domain/notice.ts +32 -8
  41. package/src/domain/notify.ts +32 -0
  42. package/src/domain/plan-doc.ts +198 -0
  43. package/src/domain/plan-lint.ts +141 -0
  44. package/src/domain/profile-rules.ts +175 -138
  45. package/src/domain/profile.ts +140 -63
  46. package/src/domain/record.ts +68 -3
  47. package/src/domain/reply.ts +29 -0
  48. package/src/domain/role-prompts.ts +182 -56
  49. package/src/domain/role-scope.ts +76 -0
  50. package/src/domain/role-tools.ts +34 -0
  51. package/src/domain/roles.ts +2 -0
  52. package/src/domain/route.ts +90 -0
  53. package/src/domain/rules.ts +33 -0
  54. package/src/domain/run-events.ts +298 -0
  55. package/src/domain/run-machine.ts +668 -0
  56. package/src/domain/run-state.ts +1310 -0
  57. package/src/domain/scorecard.ts +197 -0
  58. package/src/domain/select.ts +229 -72
  59. package/src/domain/slopalytics.ts +72 -0
  60. package/src/domain/state.ts +5 -0
  61. package/src/domain/util.ts +22 -0
  62. package/src/domain/workspace.ts +130 -0
  63. package/src/entry/answer-command.ts +39 -0
  64. package/src/entry/catalog-command.ts +24 -165
  65. package/src/entry/check-bin.ts +17 -0
  66. package/src/entry/cli-kit.ts +18 -2
  67. package/src/entry/daemon-bin.ts +22 -0
  68. package/src/entry/deps.ts +2 -0
  69. package/src/entry/doctor-command.ts +20 -2
  70. package/src/entry/glyphs.ts +15 -2
  71. package/src/entry/host-arg.ts +28 -0
  72. package/src/entry/init-command.ts +20 -69
  73. package/src/entry/knowledge-command.ts +121 -5
  74. package/src/entry/lock-command.ts +103 -8
  75. package/src/entry/mcp/handshake.ts +6 -2
  76. package/src/entry/mcp/role-bin.ts +20 -0
  77. package/src/entry/mcp/role-server.ts +134 -0
  78. package/src/entry/mcp/server.ts +85 -21
  79. package/src/entry/mcp/session-tools.ts +149 -0
  80. package/src/entry/panel/openapi.ts +189 -0
  81. package/src/entry/panel/server.ts +410 -0
  82. package/src/entry/panel-command.ts +68 -0
  83. package/src/entry/pause-command.ts +67 -0
  84. package/src/entry/profile-command.ts +33 -18
  85. package/src/entry/role-cli-command.ts +159 -0
  86. package/src/entry/runs-command.ts +81 -27
  87. package/src/entry/scorecard-command.ts +187 -0
  88. package/src/entry/supervise-bin.ts +1 -1
  89. package/src/entry/supervise-command.ts +30 -3
  90. package/src/entry/workspace-command.ts +44 -0
  91. package/src/infra/boot-lock.ts +25 -0
  92. package/src/infra/delivery.ts +3 -0
  93. package/src/infra/dispatch-dir.ts +2 -0
  94. package/src/infra/env.ts +11 -3
  95. package/src/infra/git.ts +70 -0
  96. package/src/infra/heavy-lock.ts +133 -6
  97. package/src/infra/host-probe.ts +16 -0
  98. package/src/infra/launch.ts +63 -7
  99. package/src/infra/lock-activity.ts +88 -0
  100. package/src/infra/login-env.ts +48 -0
  101. package/src/infra/memory.ts +45 -0
  102. package/src/infra/ntfy.ts +40 -0
  103. package/src/infra/open-url.ts +42 -0
  104. package/src/infra/paths.ts +37 -2
  105. package/src/infra/role-mcp.ts +69 -0
  106. package/src/infra/slopalytics.ts +30 -0
  107. package/src/infra/store.ts +6 -1
  108. package/src/infra/supervisor.ts +107 -16
  109. package/src/services/admission.ts +133 -31
  110. package/src/services/backends.ts +4 -6
  111. package/src/services/budget.ts +26 -6
  112. package/src/services/capture.ts +1 -22
  113. package/src/services/catalog-service.ts +155 -254
  114. package/src/services/credentials.ts +15 -26
  115. package/src/services/daemon.ts +181 -0
  116. package/src/services/dispatch-service.ts +209 -17
  117. package/src/services/dispatches.ts +30 -2
  118. package/src/services/doctor-access.ts +8 -3
  119. package/src/services/doctor-backends.ts +1 -1
  120. package/src/services/doctor-checks.ts +47 -0
  121. package/src/services/doctor-docker.ts +221 -0
  122. package/src/services/doctor-role-mcp.ts +65 -0
  123. package/src/services/doctor.ts +72 -15
  124. package/src/services/end-push.ts +157 -0
  125. package/src/services/failover-rungs.ts +109 -0
  126. package/src/services/finalize.ts +24 -2
  127. package/src/services/gate-env.ts +217 -0
  128. package/src/services/gate-service.ts +231 -45
  129. package/src/services/global-install.ts +46 -13
  130. package/src/services/jev-service.ts +2 -4
  131. package/src/services/lane-edit.ts +154 -0
  132. package/src/services/lane-service.ts +291 -55
  133. package/src/services/milestone-check.ts +433 -0
  134. package/src/services/milestone-tree.ts +170 -0
  135. package/src/services/milestones.ts +167 -29
  136. package/src/services/notifier.ts +124 -4
  137. package/src/services/notify-service.ts +26 -0
  138. package/src/services/orchestrator-wait.ts +82 -0
  139. package/src/services/panel-setup.ts +116 -0
  140. package/src/services/pause.ts +161 -0
  141. package/src/services/peek.ts +79 -17
  142. package/src/services/plan-input.ts +151 -0
  143. package/src/services/ports.ts +60 -5
  144. package/src/services/preflight.ts +232 -24
  145. package/src/services/profile-service.ts +31 -3
  146. package/src/services/profile-store.ts +18 -6
  147. package/src/services/protocol.ts +123 -25
  148. package/src/services/provenance.ts +34 -89
  149. package/src/services/questions.ts +24 -0
  150. package/src/services/reconcile.ts +33 -1
  151. package/src/services/reentry.ts +7 -6
  152. package/src/services/role-access.ts +27 -0
  153. package/src/services/routing-service.ts +161 -42
  154. package/src/services/run-briefs.ts +477 -0
  155. package/src/services/run-client.ts +217 -0
  156. package/src/services/run-effects.ts +837 -0
  157. package/src/services/run-events-store.ts +174 -0
  158. package/src/services/run-evidence.ts +1 -1
  159. package/src/services/run-pin.ts +125 -0
  160. package/src/services/run-read.ts +221 -0
  161. package/src/services/run-report.ts +176 -0
  162. package/src/services/run-service.ts +204 -23
  163. package/src/services/run-store.ts +159 -18
  164. package/src/services/scorecard-metrics.ts +86 -0
  165. package/src/services/scorecard.ts +382 -0
  166. package/src/services/scratch.ts +60 -0
  167. package/src/services/sessions.ts +3 -1
  168. package/src/services/state.ts +21 -0
  169. package/src/services/summary.ts +76 -12
  170. package/src/services/tree-path.ts +27 -0
  171. package/src/services/verifier-brief.ts +23 -0
  172. package/src/services/verifier-step.ts +48 -0
  173. package/src/services/workspace-admission.ts +259 -0
  174. package/src/services/workspace-service.ts +259 -0
  175. package/src/services/workspace-store.ts +95 -0
  176. package/web/dist/assets/Brand-PKlUX5TE.js +1 -0
  177. package/web/dist/assets/Brand-PKlUX5TE.js.br +0 -0
  178. package/web/dist/assets/Brand-PKlUX5TE.js.gz +0 -0
  179. package/web/dist/assets/Modal-Dxc5so53.js +1 -0
  180. package/web/dist/assets/Modal-Dxc5so53.js.br +0 -0
  181. package/web/dist/assets/Modal-Dxc5so53.js.gz +0 -0
  182. package/web/dist/assets/Pressable-C8-T6Ed_.js +1 -0
  183. package/web/dist/assets/StatusChip-B5xfI_Mn.js +153 -0
  184. package/web/dist/assets/StatusChip-B5xfI_Mn.js.br +0 -0
  185. package/web/dist/assets/StatusChip-B5xfI_Mn.js.gz +0 -0
  186. package/web/dist/assets/_app-Ea5cpyKe.js +1 -0
  187. package/web/dist/assets/_app-Ea5cpyKe.js.br +0 -0
  188. package/web/dist/assets/_app-Ea5cpyKe.js.gz +0 -0
  189. package/web/dist/assets/_runId-pTG0fXVo.js +1 -0
  190. package/web/dist/assets/_runId-pTG0fXVo.js.br +0 -0
  191. package/web/dist/assets/_runId-pTG0fXVo.js.gz +0 -0
  192. package/web/dist/assets/drawer-Du3cdlok.js +1 -0
  193. package/web/dist/assets/drawer-Du3cdlok.js.br +0 -0
  194. package/web/dist/assets/drawer-Du3cdlok.js.gz +0 -0
  195. package/web/dist/assets/geist-cyrillic-ext-wght-normal-DjL33-gN.woff2 +0 -0
  196. package/web/dist/assets/geist-cyrillic-wght-normal-BEAKL7Jp.woff2 +0 -0
  197. package/web/dist/assets/geist-latin-ext-wght-normal-DC-KSUi6.woff2 +0 -0
  198. package/web/dist/assets/geist-latin-wght-normal-BgDaEnEv.woff2 +0 -0
  199. package/web/dist/assets/geist-mono-cyrillic-ext-wght-normal-X_5orZeX.woff2 +0 -0
  200. package/web/dist/assets/geist-mono-cyrillic-wght-normal-DiZS0aHC.woff2 +0 -0
  201. package/web/dist/assets/geist-mono-latin-ext-wght-normal-Bwz-egvJ.woff2 +0 -0
  202. package/web/dist/assets/geist-mono-latin-wght-normal-XN7g48iV.woff2 +0 -0
  203. package/web/dist/assets/geist-mono-symbols2-wght-normal-CO5SzqOn.woff2 +0 -0
  204. package/web/dist/assets/geist-mono-vietnamese-wght-normal-DadHysG0.woff2 +0 -0
  205. package/web/dist/assets/geist-vietnamese-wght-normal-6IgcOCM7.woff2 +0 -0
  206. package/web/dist/assets/index-B6GiwDkh.js +11 -0
  207. package/web/dist/assets/index-B6GiwDkh.js.br +0 -0
  208. package/web/dist/assets/index-B6GiwDkh.js.gz +0 -0
  209. package/web/dist/assets/index-C5IbylvM.css +2 -0
  210. package/web/dist/assets/index-C5IbylvM.css.br +0 -0
  211. package/web/dist/assets/index-C5IbylvM.css.gz +0 -0
  212. package/web/dist/assets/label-DFSYBc62.js +1 -0
  213. package/web/dist/assets/label-DFSYBc62.js.br +0 -0
  214. package/web/dist/assets/label-DFSYBc62.js.gz +0 -0
  215. package/web/dist/assets/link-D8EcKZUt.js +1 -0
  216. package/web/dist/assets/link-D8EcKZUt.js.br +0 -0
  217. package/web/dist/assets/link-D8EcKZUt.js.gz +0 -0
  218. package/web/dist/assets/login-DaN5HPPW.js +1 -0
  219. package/web/dist/assets/login-DaN5HPPW.js.br +0 -0
  220. package/web/dist/assets/login-DaN5HPPW.js.gz +0 -0
  221. package/web/dist/assets/query-Cu--ccT3.js +1 -0
  222. package/web/dist/assets/questions-shoO9OVj.js +1 -0
  223. package/web/dist/assets/questions-shoO9OVj.js.br +0 -0
  224. package/web/dist/assets/questions-shoO9OVj.js.gz +0 -0
  225. package/web/dist/assets/react-query.gen-Bu2yV-qJ.js +12 -0
  226. package/web/dist/assets/react-query.gen-Bu2yV-qJ.js.br +0 -0
  227. package/web/dist/assets/react-query.gen-Bu2yV-qJ.js.gz +0 -0
  228. package/web/dist/assets/rolldown-runtime-XKyaHtl9.js +1 -0
  229. package/web/dist/assets/run-status-C0DRuV7U.js +1 -0
  230. package/web/dist/assets/runs-C3rg2zZc.js +1 -0
  231. package/web/dist/assets/runs-C3rg2zZc.js.br +0 -0
  232. package/web/dist/assets/runs-C3rg2zZc.js.gz +0 -0
  233. package/web/dist/assets/setup-5I4Vl9fR.js +1 -0
  234. package/web/dist/assets/setup-5I4Vl9fR.js.br +0 -0
  235. package/web/dist/assets/setup-5I4Vl9fR.js.gz +0 -0
  236. package/web/dist/assets/table-BOxXhqS4.js +1 -0
  237. package/web/dist/assets/table-BOxXhqS4.js.br +0 -0
  238. package/web/dist/assets/table-BOxXhqS4.js.gz +0 -0
  239. package/web/dist/assets/textfield-BzRFkxBF.js +1 -0
  240. package/web/dist/assets/textfield-BzRFkxBF.js.br +0 -0
  241. package/web/dist/assets/textfield-BzRFkxBF.js.gz +0 -0
  242. package/web/dist/assets/time--9kSQbbp.js +1 -0
  243. package/web/dist/assets/use-css-variable-CpnN9WtA.js +5 -0
  244. package/web/dist/assets/use-css-variable-CpnN9WtA.js.br +0 -0
  245. package/web/dist/assets/use-css-variable-CpnN9WtA.js.gz +0 -0
  246. package/web/dist/assets/useMutation-CVPKlR34.js +1 -0
  247. package/web/dist/assets/useMutation-CVPKlR34.js.br +0 -0
  248. package/web/dist/assets/useMutation-CVPKlR34.js.gz +0 -0
  249. package/web/dist/index.html +15 -0
  250. package/catalog/ATTRIBUTION.md +0 -36
  251. package/catalog/scores.json +0 -3202
  252. package/catalog/sources.json +0 -97
  253. package/src/adapters/antigravity/events.ts +0 -131
  254. package/src/adapters/antigravity/home.ts +0 -46
  255. package/src/adapters/antigravity/index.ts +0 -320
  256. package/src/adapters/antigravity/models.ts +0 -37
  257. package/src/adapters/grok/events.ts +0 -112
  258. package/src/adapters/grok/home.ts +0 -132
  259. package/src/adapters/grok/index.ts +0 -339
  260. package/src/adapters/grok/models.ts +0 -48
  261. package/src/domain/calibration.ts +0 -125
  262. package/src/domain/sources.ts +0 -144
  263. package/src/entry/mcp/dispatch-tools.ts +0 -52
  264. package/src/entry/mcp/lane-tools.ts +0 -84
  265. package/src/entry/mcp/protocol-tools.ts +0 -57
  266. package/src/entry/mcp/run-tools.ts +0 -148
  267. package/src/entry/mcp/setup-tools.ts +0 -108
  268. package/src/entry/tui/commands.ts +0 -563
  269. package/src/entry/tui/effects.ts +0 -381
  270. package/src/entry/tui/fixtures.ts +0 -637
  271. package/src/entry/tui/profile-edits.ts +0 -146
  272. package/src/entry/tui/profile-tree.ts +0 -429
  273. package/src/entry/tui/providers/app.tsx +0 -162
  274. package/src/entry/tui/providers/boundary.tsx +0 -57
  275. package/src/entry/tui/providers/data.tsx +0 -160
  276. package/src/entry/tui/providers/keymap.tsx +0 -166
  277. package/src/entry/tui/providers/root.tsx +0 -45
  278. package/src/entry/tui/providers/theme.tsx +0 -17
  279. package/src/entry/tui/providers/toast.tsx +0 -77
  280. package/src/entry/tui/run.tsx +0 -114
  281. package/src/entry/tui/state.ts +0 -373
  282. package/src/entry/tui/stories.ts +0 -114
  283. package/src/entry/tui/text.ts +0 -130
  284. package/src/entry/tui/theme.ts +0 -140
  285. package/src/entry/tui/views/app.tsx +0 -208
  286. package/src/entry/tui/views/dialogs.tsx +0 -41
  287. package/src/entry/tui/views/profile-actions.ts +0 -331
  288. package/src/entry/tui/views/profiles.tsx +0 -520
  289. package/src/entry/tui/views/runs.tsx +0 -573
  290. package/src/entry/tui/views/save-dialog.tsx +0 -340
  291. package/src/entry/tui/views/status.tsx +0 -209
  292. package/src/entry/tui/widgets/chrome.tsx +0 -114
  293. package/src/entry/tui/widgets/dialog-confirm.tsx +0 -58
  294. package/src/entry/tui/widgets/dialog-prompt.tsx +0 -52
  295. package/src/entry/tui/widgets/dialog-select.tsx +0 -212
  296. package/src/entry/tui/widgets/dialog.tsx +0 -68
  297. package/src/entry/tui/widgets/line.tsx +0 -92
  298. package/src/entry/tui/widgets/list.tsx +0 -197
  299. package/src/entry/tui/widgets/spinner.tsx +0 -49
  300. package/src/entry/tui/widgets/toast.tsx +0 -62
  301. package/src/infra/sources/arena.ts +0 -87
  302. package/src/infra/sources/artificial-analysis.ts +0 -112
  303. package/src/infra/sources/cache.ts +0 -124
  304. package/src/infra/sources/csv.ts +0 -38
  305. package/src/infra/sources/epoch.ts +0 -82
  306. package/src/infra/sources/http.ts +0 -75
  307. package/src/infra/sources/litellm.ts +0 -58
  308. package/src/infra/sources/models-dev.ts +0 -59
  309. package/src/infra/sources/openrouter-endpoints.ts +0 -52
  310. package/src/infra/sources/openrouter-models.ts +0 -60
  311. package/src/infra/sources/rows.ts +0 -38
  312. package/src/infra/sources/vectara.ts +0 -69
  313. package/src/infra/sources/zip.ts +0 -48
  314. package/src/services/catalog-refresh.ts +0 -201
  315. package/src/services/doctor-sources.ts +0 -76
  316. package/src/services/runs-page.ts +0 -255
  317. package/src/services/source-derive.ts +0 -374
  318. package/src/services/source-sync.ts +0 -305
  319. package/src/services/standins.ts +0 -204
  320. package/src/services/treat-likes.ts +0 -97
@@ -1,97 +0,0 @@
1
- {
2
- "schema": 1,
3
- "version": "2026-09-28",
4
- "sources": [
5
- {
6
- "id": "models-dev",
7
- "name": "models.dev",
8
- "url": "https://models.dev/api.json",
9
- "license": "MIT",
10
- "attribution": "Model facts from models.dev (MIT License), https://models.dev",
11
- "keyed": false
12
- },
13
- {
14
- "id": "openrouter-models",
15
- "name": "OpenRouter models",
16
- "url": "https://openrouter.ai/api/v1/models",
17
- "license": "public API",
18
- "attribution": "Model listings from the OpenRouter API, https://openrouter.ai",
19
- "keyed": false
20
- },
21
- {
22
- "id": "openrouter-endpoints",
23
- "name": "OpenRouter endpoints",
24
- "url": "https://openrouter.ai/api/v1/models",
25
- "license": "public API",
26
- "attribution": "Endpoint latency and throughput from the OpenRouter API, https://openrouter.ai",
27
- "keyed": false
28
- },
29
- {
30
- "id": "litellm",
31
- "name": "LiteLLM",
32
- "url": "https://raw.githubusercontent.com/BerriAI/litellm/main/model_prices_and_context_window.json",
33
- "license": "MIT",
34
- "attribution": "Prices from LiteLLM's model_prices_and_context_window.json (MIT License), https://github.com/BerriAI/litellm",
35
- "keyed": false
36
- },
37
- {
38
- "id": "arena",
39
- "name": "Arena (LMArena)",
40
- "url": "https://huggingface.co/datasets/lmarena-ai/leaderboard-dataset",
41
- "license": "CC-BY-4.0",
42
- "attribution": "Leaderboard data by LMArena, CC BY 4.0, https://huggingface.co/datasets/lmarena-ai/leaderboard-dataset",
43
- "keyed": false
44
- },
45
- {
46
- "id": "vectara",
47
- "name": "Vectara hallucination leaderboard",
48
- "url": "https://github.com/vectara/hallucination-leaderboard",
49
- "license": "Apache-2.0",
50
- "attribution": "Hallucination Leaderboard by Vectara (Apache License 2.0), https://github.com/vectara/hallucination-leaderboard",
51
- "keyed": false
52
- },
53
- {
54
- "id": "epoch",
55
- "name": "Epoch AI benchmarks",
56
- "url": "https://epoch.ai/data/benchmark_data.zip",
57
- "license": "CC-BY-4.0; external tables keep their own license",
58
- "attribution": "Epoch AI, 'Capabilities & benchmarking'. Published online at epoch.ai. Retrieved from 'https://epoch.ai/benchmarks' (CC BY 4.0)",
59
- "keyed": false
60
- },
61
- {
62
- "id": "artificial-analysis",
63
- "name": "Artificial Analysis",
64
- "url": "https://artificialanalysis.ai/api/v2/data/llms/models",
65
- "license": "free key, internal use, attribution; read with the user's own key and never shipped",
66
- "attribution": "Data from Artificial Analysis, https://artificialanalysis.ai",
67
- "keyed": true
68
- }
69
- ],
70
- "aliases": {
71
- "claude-4-5-haiku": "claude-haiku-4-5",
72
- "claude-haiku-4-5-20251001": "claude-haiku-4-5",
73
- "gemini-3.1-pro-preview": "gemini-3-1-pro"
74
- },
75
- "defaultEffort": {
76
- "gpt-6-astra": "medium",
77
- "gpt-6.1-sol": "medium",
78
- "gpt-6-sol": "medium",
79
- "gpt-6-luna": "medium",
80
- "gpt-5.6-sol": "medium",
81
- "gpt-5.6-terra": "medium",
82
- "gpt-5.6-luna": "medium",
83
- "claude-fable-5-1": "high",
84
- "claude-opus-5-5": "high",
85
- "claude-sonnet-5-5": "high",
86
- "claude-sonnet-5": "high",
87
- "claude-haiku-4-5": "default",
88
- "grok-4-7": "high",
89
- "grok-4-6": "high",
90
- "grok-4-5": "high",
91
- "composer-2-5": "default",
92
- "gemini-3-8-flash": "high",
93
- "gemini-3-7-flash": "high",
94
- "gemini-3-6-flash": "high",
95
- "gemini-3-1-pro": "high"
96
- }
97
- }
@@ -1,131 +0,0 @@
1
- import { type Tokens, ZERO_TOKENS } from "../../domain/record.ts";
2
-
3
- export interface AgyResult {
4
- /** SUCCESS, ERROR, CANCELED, INTERRUPTED, INVALID, WAITING or RUNNING (research §4.4) */
5
- status: string;
6
- /** the reply: the final message only (spec 1.3 §6.5; the live kit confirms it) */
7
- response: string;
8
- error: string;
9
- tokens: Tokens;
10
- }
11
-
12
- export interface AgyFold {
13
- thread: string | null;
14
- result: AgyResult | null;
15
- lastEvent: string | null;
16
- }
17
-
18
- /** A quota, spend cap or credits stop (spec 1.3 §6.5; research §4.4, §4.8: the texts are the changelog's). */
19
- export const AGY_LIMIT = [/RESOURCE_EXHAUSTED/, /quota/i, /spend cap/i, /credits/i];
20
- /** What an older agy prints for a flag catherd passes (Go `flag`, exit 2; research §4.4 [run]). */
21
- export const AGY_TOO_OLD = [/flags? provided but not defined/i];
22
- /** research §4.4 [run] (the logged-out result's error) and §4.5 [run] (`agy models` logged out) */
23
- const AGY_AUTH = [/authentication failed/i, /please sign in/i];
24
-
25
- type Event = Record<string, any>;
26
-
27
- /** One stream line: an object keyed by `event` (research §4.4: `event`, not `type`); null for anything else. */
28
- export function parseAgyLine(line: string): Event | null {
29
- if (!line.trim().startsWith("{")) return null;
30
- try {
31
- const e = JSON.parse(line) as unknown;
32
- return e && typeof e === "object" && !Array.isArray(e) && typeof (e as Event).event === "string"
33
- ? (e as Event)
34
- : null;
35
- } catch {
36
- return null;
37
- }
38
- }
39
-
40
- /**
41
- * An event's payload: under the key its `event` names, as the logged-out `result` line has it (research §4.4
42
- * [run]), else the event itself (the headless doc lists the fields flat).
43
- */
44
- export const bodyOf = (e: Event): Event => {
45
- const inner = e[e.event as string];
46
- return inner && typeof inner === "object" && !Array.isArray(inner) ? (inner as Event) : e;
47
- };
48
-
49
- /**
50
- * Spec 1.3 §6.5: input with the cache reads in it, cached = cache reads, output = output + thinking. Whether
51
- * `input_tokens` already includes the reads is unverified (research §4.4): `total_tokens` says so when it adds up
52
- * one way; without it, reads that exceed the input cannot be in it, so they add back.
53
- */
54
- export function agyTokens(u: Event | undefined): Tokens {
55
- if (!u) return { ...ZERO_TOKENS };
56
- const n = (k: string) => (typeof u[k] === "number" ? (u[k] as number) : 0);
57
- const cached = n("cache_read_tokens");
58
- const input = n("input_tokens");
59
- const output = n("output_tokens") + n("thinking_tokens");
60
- const separate =
61
- typeof u.total_tokens === "number" && u.total_tokens !== input + output
62
- ? u.total_tokens === input + cached + output
63
- : cached > input;
64
- return { input: separate ? input + cached : input, cached, output };
65
- }
66
-
67
- export function eventName(e: Event): string {
68
- const b = bodyOf(e);
69
- if (e.event === "step_update") return `step_update/${b.step_type ?? "?"}/${b.state ?? "?"}`;
70
- if (e.event === "result") return `result/${b.status ?? "?"}`;
71
- return String(e.event);
72
- }
73
-
74
- /** Spec §3.7: a command the worker runs, a file it reads or edits, or its message. */
75
- export function agyActivity(e: Event): string | undefined {
76
- if (e.event !== "step_update") return undefined;
77
- const b = bodyOf(e);
78
- if (b.step_type === "agent_response")
79
- return typeof b.text_delta === "string" && b.text_delta ? b.text_delta : undefined;
80
- if (b.step_type !== "tool" || b.state !== "ACTIVE") return undefined;
81
- const name = String(b.tool_name ?? "tool");
82
- const info = (b.tool_info ?? {}) as Event;
83
- if (typeof info.command === "string") return `$ ${info.command}`;
84
- const path = info.path ?? info.file_path;
85
- if (typeof path === "string") return `${/read|view/i.test(name) ? "read" : "edit"} ${path}`;
86
- return name;
87
- }
88
-
89
- /**
90
- * The `AGY_ERROR: {…}` line a model or agent failure prints on stderr (research §4.4: the canonical status,
91
- * the HTTP/gRPC code, retryability and an error id; the field names are unverified): its text, and its
92
- * `message` when it is JSON that has one.
93
- */
94
- export function agyError(stderr: string): { text: string; message: string } | null {
95
- const line = stderr
96
- .split("\n")
97
- .findLast((l) => l.startsWith("AGY_ERROR:"))
98
- ?.slice("AGY_ERROR:".length)
99
- .trim();
100
- if (line === undefined) return null;
101
- try {
102
- const j = JSON.parse(line) as Event;
103
- const m = j?.message ?? j?.error?.message ?? j?.error;
104
- return { text: line, message: typeof m === "string" && m ? m : line };
105
- } catch {
106
- return { text: line, message: line };
107
- }
108
- }
109
-
110
- export const isLimit = (text: string): boolean => AGY_LIMIT.some((r) => r.test(text));
111
- export const isTooOld = (text: string): boolean => AGY_TOO_OLD.some((r) => r.test(text));
112
- export const isAuthFailure = (text: string): boolean => AGY_AUTH.some((r) => r.test(text));
113
-
114
- export function foldAgyEvents(lines: string[]): AgyFold {
115
- const f: AgyFold = { thread: null, result: null, lastEvent: null };
116
- for (const line of lines) {
117
- const e = parseAgyLine(line);
118
- if (!e) continue;
119
- const b = bodyOf(e);
120
- f.lastEvent = eventName(e);
121
- if (typeof b.conversation_id === "string" && b.conversation_id) f.thread ??= b.conversation_id;
122
- if (e.event === "result")
123
- f.result = {
124
- status: String(b.status ?? ""),
125
- response: typeof b.response === "string" ? b.response : "",
126
- error: typeof b.error === "string" ? b.error : "",
127
- tokens: agyTokens(b.usage),
128
- };
129
- }
130
- return f;
131
- }
@@ -1,46 +0,0 @@
1
- import { join } from "node:path";
2
- import type { Access } from "../../domain/record.ts";
3
- import { dataDir } from "../../infra/paths.ts";
4
- import { ensurePrivateDir, writeJsonAtomic } from "../../infra/store.ts";
5
- import { movedHomeEnv, writableRoots } from "../access.ts";
6
-
7
- /** Where isolated agy runs keep their homes; computing it creates nothing. */
8
- export const isolatedAgyRoot = (): string => join(dataDir(), "agy-home");
9
-
10
- /**
11
- * Spec 1.3 §6.4: an isolated run's HOME. agy reads its permission rules from the one `settings.json` of its home,
12
- * so each access (and a workspace-write role without network) gets its own home, and lanes of different access run
13
- * side by side. A conversation lives in the home it started in; `resume.sameAccessOnly` keeps it there.
14
- */
15
- export const isolatedAgyHome = (access: Access, network = true): string =>
16
- join(isolatedAgyRoot(), access === "workspace-write" && !network ? "workspace-write-offline" : access);
17
-
18
- /**
19
- * The env of an isolated run: the moved HOME, which relocates every agy file, the settings among them (research
20
- * §4.7 [run]), with catherd's own dirs and the toolchain caches kept.
21
- */
22
- export const agyHomeEnv = (access: Access, network = true): Record<string, string> =>
23
- movedHomeEnv(isolatedAgyHome(access, network));
24
-
25
- /**
26
- * The `settings.json` of an isolated home (spec 1.3 §6.3, §6.4; research §4.7, §4.8): the Gemini API as the
27
- * provider, so GEMINI_API_KEY signs it in; read-only denies every write and command; workspace-write allows
28
- * catherd's writable roots (mounted read-write in the sandbox) and, unless the role has no network, every URL;
29
- * `full` runs with every tool approved and needs no rule.
30
- */
31
- export function agySettings(access: Access, network = true): Record<string, unknown> {
32
- const provider = { modelProvider: "gemini" };
33
- if (access === "read-only") return { ...provider, permissions: { deny: ["write_file(*)", "command(*)"] } };
34
- if (access === "full") return provider;
35
- const allow = [...writableRoots().map((r) => `write_file(${r})`), ...(network ? ["read_url(*)"] : [])];
36
- return { ...provider, permissions: { allow } };
37
- }
38
-
39
- /** Makes the isolated home for `access` and writes its settings; catherd never writes the user's own. */
40
- export function prepareAgyHome(access: Access, network = true): string {
41
- const home = isolatedAgyHome(access, network);
42
- const dir = join(home, ".gemini", "antigravity-cli");
43
- ensurePrivateDir(dir);
44
- writeJsonAtomic(join(dir, "settings.json"), agySettings(access, network));
45
- return home;
46
- }
@@ -1,320 +0,0 @@
1
- import { existsSync, readFileSync } from "node:fs";
2
- import { homedir } from "node:os";
3
- import { join } from "node:path";
4
- import { CatherdError } from "../../domain/errors.ts";
5
- import type { Rung } from "../../domain/ids.ts";
6
- import type { Access, RunStatus } from "../../domain/record.ts";
7
- import {
8
- type BackendAdapter,
9
- compareVersions,
10
- type DiscoveredModel,
11
- type EventDelta,
12
- extractVersion,
13
- type FinishedRun,
14
- type Outcome,
15
- type Probe,
16
- type RunRequest,
17
- type SpawnPlan,
18
- } from "../backend.ts";
19
- import { jsonOf, runCli } from "../cli.ts";
20
- import { discovered } from "../discovery.ts";
21
- import {
22
- AGY_LIMIT,
23
- AGY_TOO_OLD,
24
- agyActivity,
25
- agyError,
26
- agyTokens,
27
- bodyOf,
28
- eventName,
29
- foldAgyEvents,
30
- isAuthFailure,
31
- isLimit,
32
- isTooOld,
33
- parseAgyLine,
34
- } from "./events.ts";
35
- import { agyHomeEnv, prepareAgyHome } from "./home.ts";
36
- import { parseAgyModels } from "./models.ts";
37
-
38
- export { isolatedAgyHome, isolatedAgyRoot } from "./home.ts";
39
-
40
- /** Spec 1.3 §6.1: the version whose help, exit codes and changelog were read (research §4). */
41
- export const AGY_MIN_VERSION = "1.2.13";
42
- export const AGY_INSTALL =
43
- "curl -fsSL https://antigravity.google/cli/install.sh | bash (it installs ~/.local/bin/agy: put that dir on PATH), or brew install --cask antigravity-cli";
44
- /** A conversation id: a UUID in the docs' examples; never flag-shaped. */
45
- const THREAD = /^[A-Za-z0-9][\w-]{7,127}$/;
46
- const DAY_MS = 24 * 60 * 60_000;
47
- const LOGIN_FIX =
48
- "run agy once to sign in with Google, or export GEMINI_API_KEY=<key> and catherd profile set harness.antigravity.isolated true";
49
- /** research §4.2: the background self-updater must not swap the binary under a lane (spec 1.3 §3.3) */
50
- const ENV = { AGY_CLI_DISABLE_AUTO_UPDATE: "true" };
51
-
52
- /** How long an `agy` query (version, models, `/usage`) may take; `agy models` took ~10 s logged out (research §4.5). */
53
- export const agyShell = { timeoutMs: 30_000 };
54
-
55
- /**
56
- * Spec 1.3 §6.2: the fixed line `-p` carries. The brief stays in its file and argv holds only its path; agy reads
57
- * no stdin when a prompt comes by flag (research §4.3).
58
- */
59
- export const agyPrompt = (briefPath: string): string =>
60
- `Read the brief in ${briefPath} and follow it. Your final message is your reply.`;
61
-
62
- /**
63
- * Spec 1.3 §6.3. read-only has no flag: it runs only isolated, under deny rules in catherd's settings (§9 Q2).
64
- * workspace-write runs shell in agy's sandbox with every tool approved (headless soft-denies what it would ask).
65
- */
66
- export const AGY_ACCESS: Record<Access, string[]> = {
67
- "read-only": [],
68
- "workspace-write": ["--sandbox", "--dangerously-skip-permissions"],
69
- full: ["--dangerously-skip-permissions"],
70
- };
71
-
72
- /**
73
- * Whether the last probe found agy's own login signed out (null: not probed, or it could not tell). A native run
74
- * then would open a browser and wait (research §4.8), so `prepare` refuses it; an isolated one signs in by key.
75
- */
76
- let nativeLogin: boolean | null = null;
77
-
78
- function plan(r: RunRequest): SpawnPlan {
79
- if (r.thread !== null && !THREAD.test(r.thread))
80
- throw new CatherdError("E_ADMIT_THREAD", `"${r.thread}" is not an agy conversation id`, {
81
- fix: "pass the thread from the earlier RunRecord",
82
- });
83
- const { model, effort } = r.rung;
84
- return {
85
- cmd: "agy",
86
- args: [
87
- "-p",
88
- agyPrompt(r.briefPath),
89
- "--output-format",
90
- "stream-json",
91
- "--model",
92
- model,
93
- ...(effort === "default" ? [] : ["--effort", effort]),
94
- "--disable-slash-commands",
95
- ...AGY_ACCESS[r.access],
96
- // conversations are scoped by cwd: a resume runs in the recorded repo (research §4.6)
97
- ...(r.thread === null ? [] : ["--conversation", r.thread]),
98
- ],
99
- env: { ...ENV, ...(r.isolated ? agyHomeEnv(r.access, r.network !== false) : {}) },
100
- cwd: r.repo,
101
- stdinPath: null,
102
- };
103
- }
104
-
105
- async function listModels(): Promise<DiscoveredModel[]> {
106
- const r = await runCli("agy", ["models"], { ...agyShell, env: ENV });
107
- return r?.ok ? parseAgyModels(r.out) : [];
108
- }
109
-
110
- /**
111
- * Spec 1.3 §6.3, §6.4, §9 Q2: read-only runs only isolated; an isolated run needs GEMINI_API_KEY and gets its
112
- * home and settings; a native one needs agy's own login; the rung's model and effort must be ones agy lists.
113
- */
114
- async function prepare(req: {
115
- rung: Rung;
116
- access: Access;
117
- isolated: boolean;
118
- network?: boolean;
119
- }): Promise<void> {
120
- if (!req.isolated && req.access === "read-only")
121
- throw new CatherdError("E_ADMIT_RUNG", "native agy has no read-only mode", {
122
- fix: "catherd profile set harness.antigravity.isolated true (it needs GEMINI_API_KEY), or put this role on another backend",
123
- });
124
- if (req.isolated) {
125
- if (!process.env.GEMINI_API_KEY)
126
- throw new CatherdError("E_BACKEND_NOT_LOGGED_IN", "an isolated antigravity run needs GEMINI_API_KEY", {
127
- fix: "export GEMINI_API_KEY=<key>, or catherd profile set harness.antigravity.isolated false",
128
- });
129
- prepareAgyHome(req.access, req.network !== false);
130
- } else if (nativeLogin !== true)
131
- // spec 1.3 §6.1: fail closed; agy models failing some other way (a timeout, reworded output) is no proof
132
- throw new CatherdError(
133
- "E_BACKEND_NOT_LOGGED_IN",
134
- nativeLogin === false
135
- ? "agy is not signed in: a native run would open a browser"
136
- : "catherd could not confirm agy is signed in (agy models failed), and a signed-out native run opens a browser",
137
- { fix: LOGIN_FIX },
138
- );
139
- const { model, effort } = req.rung;
140
- const models = await discovered("antigravity", listModels, { maxAgeMs: DAY_MS, need: model });
141
- if (models.length === 0) return; // agy listed nothing: let the run itself say what is wrong
142
- const m = models.find((x) => x.id === model);
143
- if (!m)
144
- throw new CatherdError("E_BACKEND_MODEL_UNKNOWN", `agy does not list ${model}`, {
145
- fix: "run agy models; a rung names the model without its effort suffix",
146
- });
147
- if (!m.efforts.includes(effort))
148
- throw new CatherdError("E_BACKEND_MODEL_UNKNOWN", `agy lists no ${model} at effort ${effort}`, {
149
- fix: `use one of: ${m.efforts.map((e) => `antigravity:${model}#${e}`).join(", ")}`,
150
- });
151
- }
152
-
153
- function finalize(run: FinishedRun): Outcome {
154
- const f = foldAgyEvents(run.eventLines);
155
- const res = f.result;
156
- const stopped = run.exit.reason;
157
- const err = agyError(run.stderr);
158
- const stderrLines = run.stderr.trim().split("\n").filter(Boolean);
159
- const status: RunStatus =
160
- stopped === "cancelled"
161
- ? "cancelled"
162
- : stopped === "idle-timeout" || stopped === "wall-timeout"
163
- ? "timeout"
164
- : res?.status === "SUCCESS"
165
- ? "ok"
166
- : res?.status === "CANCELED" || res?.status === "INTERRUPTED"
167
- ? "cancelled"
168
- : run.exit.code === 2 || isTooOld(run.stderr)
169
- ? "cli-too-old"
170
- : isLimit(`${err?.text ?? ""}\n${res?.error ?? ""}`)
171
- ? "limit"
172
- : "failed";
173
- const message =
174
- res?.error && isAuthFailure(res.error)
175
- ? `${res.error} (fix: ${LOGIN_FIX})`
176
- : (err?.message ??
177
- stderrLines.find(isTooOld) ??
178
- (res?.error ||
179
- (res && res.status !== "SUCCESS" ? `agy ended the run ${res.status}` : "") ||
180
- stderrLines.at(-1) ||
181
- `no result event (${stopped}, exit ${run.exit.code ?? run.exit.signal})`));
182
- return {
183
- status,
184
- thread: f.thread ?? run.request.thread,
185
- tokens: res?.tokens ?? agyTokens(undefined),
186
- costUsd: null,
187
- images: [],
188
- error: status === "ok" ? null : { code: status, message },
189
- ...(status === "ok" ? { reply: res?.response ?? "" } : {}),
190
- };
191
- }
192
-
193
- function parse(line: string): EventDelta {
194
- const e = parseAgyLine(line);
195
- if (!e) return {};
196
- const b = bodyOf(e);
197
- const d: EventDelta = { lastEvent: eventName(e) };
198
- const activity = agyActivity(e);
199
- if (activity) d.activity = activity;
200
- if (typeof b.conversation_id === "string" && b.conversation_id) d.thread = b.conversation_id;
201
- if (e.event === "step_update") {
202
- if (b.usage) d.requestInput = agyTokens(b.usage).input;
203
- // a tool step runs between ACTIVE and DONE, often printing nothing: the run is busy
204
- if (b.step_type === "tool" && typeof b.step_index === "number")
205
- d.item = { id: `step-${b.step_index}`, open: b.state === "ACTIVE" };
206
- }
207
- if (e.event === "result") {
208
- d.final = true;
209
- d.tokens = agyTokens(b.usage);
210
- if (b.status !== "SUCCESS") {
211
- d.failure = String(b.error || b.status || "error");
212
- if (isLimit(d.failure)) d.limit = true;
213
- }
214
- }
215
- return d;
216
- }
217
-
218
- /** The user's own agy settings (read, never written: the desktop app shares them, spec 1.3 §9 Q3). */
219
- function userSettings(): Record<string, unknown> | null {
220
- const file = join(process.env.HOME || homedir(), ".gemini", "antigravity-cli", "settings.json");
221
- if (!existsSync(file)) return null;
222
- const j = jsonOf(readFileSync(file, "utf8"));
223
- return j && typeof j === "object" ? (j as Record<string, unknown>) : null;
224
- }
225
-
226
- /**
227
- * Spec 1.3 §6.1, §3.3. Logged in: `agy models` answered. It fails fast when logged out, where `agy -p` would open
228
- * a browser and wait (research §4.5, §4.8), so the probe never runs `-p`. Logged out is a problem unless
229
- * GEMINI_API_KEY is set, which an isolated run signs in with; `prepare` then refuses the native runs.
230
- */
231
- async function probe(): Promise<Probe> {
232
- const v = await runCli("agy", ["--version"], { ...agyShell, env: ENV });
233
- if (!v)
234
- return {
235
- installed: false,
236
- version: null,
237
- versionOk: false,
238
- loggedIn: null,
239
- problems: [{ code: "E_BACKEND_MISSING", message: "agy is not on PATH", fix: AGY_INSTALL }],
240
- };
241
- const version = extractVersion(v.out);
242
- const versionOk = version !== null && compareVersions(version, AGY_MIN_VERSION) >= 0;
243
- const m = await runCli("agy", ["models"], { ...agyShell, env: ENV });
244
- nativeLogin = m?.ok ? true : isAuthFailure(`${m?.out ?? ""}\n${m?.err ?? ""}`) ? false : null;
245
- // research §4.8: GEMINI_API_KEY signs agy in only with modelProvider "gemini" in its settings
246
- const byKey =
247
- nativeLogin === true && userSettings()?.modelProvider === "gemini" && !!process.env.GEMINI_API_KEY;
248
- const problems: Probe["problems"] = [];
249
- if (!versionOk)
250
- problems.push({
251
- code: "E_BACKEND_TOO_OLD",
252
- message: `agy ${version ?? "?"} is older than ${AGY_MIN_VERSION}`,
253
- fix: "brew upgrade --cask antigravity-cli, or run the install script again",
254
- });
255
- if (nativeLogin === false && !process.env.GEMINI_API_KEY)
256
- problems.push({
257
- code: "E_BACKEND_NOT_LOGGED_IN",
258
- message: "agy is not signed in (agy models says: Please sign in)",
259
- fix: LOGIN_FIX,
260
- });
261
- return {
262
- installed: true,
263
- version,
264
- versionOk,
265
- loggedIn: nativeLogin,
266
- // spec 1.3 §9 Q4: a Google login draws on the plan's quota; the key bills the Gemini API project
267
- ...(nativeLogin
268
- ? {
269
- login: byKey ? "API key" : "Google",
270
- billing: byKey ? ("metered" as const) : ("subscription" as const),
271
- }
272
- : {}),
273
- problems,
274
- };
275
- }
276
-
277
- /**
278
- * Spec 1.3 §6.6: `agy -p "/usage" --output-format json` answers without an agent turn (research §4.8; unverified
279
- * live). Doctor calls it only when the probe found agy logged in. The reply's lines, joined; null when it fails.
280
- */
281
- async function quota(): Promise<string | null> {
282
- const r = await runCli("agy", ["-p", "/usage", "--output-format", "json"], { ...agyShell, env: ENV });
283
- if (!r?.ok) return null;
284
- const j = jsonOf(r.out) as Record<string, unknown> | null;
285
- const text = typeof j?.response === "string" ? j.response : r.out;
286
- return (
287
- text
288
- .split("\n")
289
- .map((l) => l.trim())
290
- .filter(Boolean)
291
- .join(" · ")
292
- .slice(0, 200) || null
293
- );
294
- }
295
-
296
- export const antigravityAdapter: BackendAdapter = {
297
- id: "antigravity",
298
- minVersion: AGY_MIN_VERSION,
299
- install: AGY_INSTALL,
300
- probe,
301
- listModels,
302
- prepare,
303
- plan,
304
- parse,
305
- finalize,
306
- // read-only: deny rules in catherd's settings, never a flag; workspace-write: shell in the sandbox, but the file
307
- // tools, every one approved, are not confined by it (research §4.7)
308
- enforcement: { "read-only": "advisory", "workspace-write": "advisory", full: "enforced" },
309
- errors: { limit: AGY_LIMIT, tooOld: AGY_TOO_OLD },
310
- // until the live kit shows a resume takes the new run's permissions (research §4.6)
311
- resume: { supported: true, sameAccessOnly: true, threadPattern: THREAD },
312
- isolationNote:
313
- "native Antigravity shares its settings and permission rules with the Antigravity desktop app",
314
- isolationKey: "GEMINI_API_KEY",
315
- isolatedOnly: ["read-only"],
316
- quota,
317
- // agy leaves daemon background tasks (dev servers) running after its result (research §4.4)
318
- graceAfterFinalMs: 30_000,
319
- reportsCost: false,
320
- };
@@ -1,37 +0,0 @@
1
- import type { DiscoveredModel } from "../backend.ts";
2
- import { foldEffortSlugs, stripAnsi } from "../discovery.ts";
3
-
4
- /** `agy --effort` (research §4.3 [run]: "(low|medium|high|max)"); `#default` passes none. */
5
- export const AGY_EFFORTS = ["low", "medium", "high", "max"];
6
-
7
- /** A slug: lower-case, with a digit (`gemini-3.8-flash`, `gpt-oss-120b`); headers and tips start upper-case. */
8
- const SLUG = /^[a-z][a-z0-9._-]*$/;
9
-
10
- /**
11
- * `agy models` (spec 1.3 §6.6). Its format is unverified: 1.2.13 rejects the changelog's `--output-format json`
12
- * (research §4.5), so each line's first word is read as a slug when it looks like one, past a list bullet. Slugs
13
- * that differ by an `--effort` word fold into one model (`gemini-3.8-flash-high`) with the efforts listed, and
14
- * `default` when the bare slug is listed too; a model listed only bare offers every `--effort`, which picks
15
- * the variant (research §4.5).
16
- */
17
- export function parseAgyModels(text: string): DiscoveredModel[] {
18
- const slugs = stripAnsi(text)
19
- .split("\n")
20
- .map(
21
- (l) =>
22
- l
23
- .trim()
24
- .replace(/^[*•-]\s+/, "")
25
- .split(/\s/)[0] ?? "",
26
- )
27
- .filter((w) => SLUG.test(w) && /\d/.test(w));
28
- return [...foldEffortSlugs(slugs, AGY_EFFORTS)].map(([id, efforts]) => ({
29
- id,
30
- efforts:
31
- efforts.size === 1 && efforts.has("default")
32
- ? ["default", ...AGY_EFFORTS]
33
- : ["default", ...AGY_EFFORTS].filter((e) => efforts.has(e)),
34
- context: null,
35
- imageIn: false,
36
- }));
37
- }