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
@@ -3,11 +3,11 @@ import { HOST_ARG, terminalHost } from "./host-arg.ts";
3
3
  import { assertProfileName } from "../domain/profile.ts";
4
4
  import { defineCommand } from "citty";
5
5
  import type { Catalog } from "../domain/catalog.ts";
6
- import { rungInfo } from "../domain/catalog.ts";
6
+ import { pairOf } from "../domain/catalog.ts";
7
7
  import { CatherdError } from "../domain/errors.ts";
8
8
  import type { OrchestrationHost } from "../domain/host.ts";
9
9
  import { ProfileDocSchema, type Change, patchAt, type Profile } from "../domain/profile.ts";
10
- import { inferredScores, type Issue } from "../domain/profile-rules.ts";
10
+ import type { Issue } from "../domain/profile-rules.ts";
11
11
  import { ROLES, type Role } from "../domain/roles.ts";
12
12
  import { gitToplevel } from "../infra/git.ts";
13
13
  import { loadCatalog } from "../services/catalog-service.ts";
@@ -34,28 +34,24 @@ import {
34
34
  runnableBackends,
35
35
  validateNamed,
36
36
  } from "../services/profile-store.ts";
37
- import { EXIT, JSON_ARG, mark, printJson } from "./cli-kit.ts";
37
+ import { EXIT, JSON_ARG, mark, printJson, refuseInRole } from "./cli-kit.ts";
38
38
 
39
39
  const json = JSON_ARG;
40
40
 
41
41
  export interface StandIn {
42
42
  from: string;
43
43
  to: string;
44
- inferred: boolean;
45
- via: string | null;
46
- /** what the treat-like lends, as `profile show` says it */
44
+ /** `no Slopalytics index` when the stand-in is not indexed, so routing skips it */
47
45
  note: string | null;
48
46
  }
49
47
 
50
- /** Spec §7.2: each stand-in; `via` names the rung whose scores it borrows (spec 1.1 §11). */
48
+ /** Spec §7.2: each stand-in, noting one that has no Slopalytics index. */
51
49
  export function standIns(p: Profile, c: Catalog): StandIn[] {
52
- return Object.entries(p.failover).map(([from, to]) => {
53
- try {
54
- return { from, to, ...inferredScores(c, rungInfo(c, to)) };
55
- } catch {
56
- return { from, to, inferred: false, via: null, note: null };
57
- }
58
- });
50
+ return Object.entries(p.failover).map(([from, to]) => ({
51
+ from,
52
+ to,
53
+ note: pairOf(c, to) === null ? "no Slopalytics index" : null,
54
+ }));
59
55
  }
60
56
 
61
57
  const value = (v: unknown) => (v === null ? "none" : typeof v === "string" ? v : JSON.stringify(v));
@@ -82,6 +78,10 @@ export function formatProfile(
82
78
  `objective ${p.objective} · jev ${p.jev.use} · heavy slots ${p.lock.heavy} · notify ${p.notify.join(", ") || "none"}`,
83
79
  "roles",
84
80
  ];
81
+ const accessOf = (role: Role) =>
82
+ `${p.roles[role].access}${p.roles[role].network === false ? " (no network)" : ""}, ${o.enforcement[role]}`;
83
+ // the ladders start in one column, however long the longest access text ("(no network)" included)
84
+ const accessWidth = Math.max(27, ...ROLES.filter((r) => p.roles[r].enabled).map((r) => accessOf(r).length));
85
85
  for (const role of ROLES) {
86
86
  const rc = p.roles[role];
87
87
  if (!rc.enabled) {
@@ -89,9 +89,7 @@ export function formatProfile(
89
89
  continue;
90
90
  }
91
91
  const ladder = rc.rungs.map((r) => (r === rc.defaultRung ? `${r} (default)` : r)).join(" → ");
92
- lines.push(
93
- ` ${role.padEnd(width)} ${`${rc.access}${rc.network === false ? " (no network)" : ""}, ${o.enforcement[role]}`.padEnd(27)} ${ladder || "no rungs"}`,
94
- );
92
+ lines.push(` ${role.padEnd(width)} ${accessOf(role).padEnd(accessWidth)} ${ladder || "no rungs"}`);
95
93
  }
96
94
  // only what catherd can run today: a backend without an adapter has nothing to bill or isolate
97
95
  const runs = ([key]: [string, unknown]) => keyRunnable(key, o.backends);
@@ -104,6 +102,16 @@ export function formatProfile(
104
102
  const budget = Object.entries(p.budget).map(([k, v]) => (k === "usd" ? `$${v}` : `${v} ${k}`));
105
103
  lines.push(`budget ${budget.join(" · ") || "no cap"}`);
106
104
  lines.push(`timeouts idle ${p.timeouts.idleMin} min · wall ${p.timeouts.wallMin} min`);
105
+ // plan 23: a role's own timeouts, under the profile's
106
+ for (const role of ROLES) {
107
+ const t = p.roles[role].timeouts;
108
+ if (!t) continue;
109
+ const parts = [
110
+ ...(t.idleMin !== undefined ? [`idle ${t.idleMin} min`] : []),
111
+ ...(t.wallMin !== undefined ? [`wall ${t.wallMin} min`] : []),
112
+ ];
113
+ lines.push(` ${role} ${parts.join(" · ")}`);
114
+ }
107
115
  lines.push(
108
116
  `preflight ${p.preflight.confirm ? "shows its commands and asks first" : "runs the checks without asking"}`,
109
117
  );
@@ -186,7 +194,7 @@ const show = defineCommand({
186
194
  const o = {
187
195
  active: name === here,
188
196
  enforcement: roleEnforcement(p),
189
- standIns: standIns(p, loadCatalog({ timings: false })),
197
+ standIns: standIns(p, loadCatalog()),
190
198
  };
191
199
  if (args.json) return printJson({ profile: p, ...o });
192
200
  for (const l of formatProfile(p, { ...o, backends: runnableBackends() })) console.log(l);
@@ -206,6 +214,7 @@ const use = defineCommand({
206
214
  clear: { type: "boolean", description: "with --repo and no name: unbind the git repo you are in" },
207
215
  },
208
216
  async run({ args }) {
217
+ refuseInRole(process.env, "catherd profile use");
209
218
  if (args.clear) {
210
219
  if (!args.repo || args.name !== undefined)
211
220
  throw new CatherdError("E_INPUT_INVALID", "--clear goes with --repo and no profile name", {
@@ -234,6 +243,7 @@ const create = defineCommand({
234
243
  from: { type: "string", description: "copy this profile instead of the default one" },
235
244
  },
236
245
  run({ args }) {
246
+ refuseInRole(process.env, "catherd profile new");
237
247
  const r = createProfile(args.name, args.from, terminalHost(args.host).host);
238
248
  if (!r.saved) throw refused(r.errors);
239
249
  console.log(`${mark("ok")} created ${args.name}${args.from ? ` from ${args.from}` : ""}`);
@@ -249,6 +259,7 @@ const copy = defineCommand({
249
259
  to: { type: "positional", required: true, description: "new profile name" },
250
260
  },
251
261
  run({ args }) {
262
+ refuseInRole(process.env, "catherd profile copy");
252
263
  const r = createProfile(args.to, args.from, terminalHost(args.host).host);
253
264
  if (!r.saved) throw refused(r.errors);
254
265
  console.log(`${mark("ok")} copied ${args.from} to ${args.to}`);
@@ -263,6 +274,7 @@ const rm = defineCommand({
263
274
  name: { type: "positional", required: true, description: "profile name" },
264
275
  },
265
276
  run({ args }) {
277
+ refuseInRole(process.env, "catherd profile rm");
266
278
  deleteProfile(args.name);
267
279
  console.log(`${mark("ok")} deleted ${args.name}`);
268
280
  },
@@ -281,6 +293,7 @@ const set = defineCommand({
281
293
  profile: { type: "string", description: "the profile to change (default: the one this repo runs on)" },
282
294
  },
283
295
  async run({ args }) {
296
+ refuseInRole(process.env, "catherd profile set");
284
297
  // the CLI never creates a profile by setting a field of it: a typo would start a new one
285
298
  if (args.profile !== undefined && !profileExists(assertProfileName(args.profile)))
286
299
  throw new CatherdError("E_INPUT_INVALID", `no profile named "${args.profile}"`, {
@@ -342,6 +355,7 @@ const validate = defineCommand({
342
355
  args.name === undefined ? activeName(repo) : requireProfile(args.name),
343
356
  repo,
344
357
  terminalHost(args.host).host,
358
+ { reach: true },
345
359
  );
346
360
  if (args.json) printJson({ valid: v.errors.length === 0, ...v });
347
361
  else if (v.errors.length === 0 && v.warnings.length === 0) console.log(`${mark("ok")} valid`);
@@ -370,6 +384,7 @@ const resetDefaults = defineCommand({
370
384
  let r;
371
385
  if (args.preview) r = resetHostDefaults(name, host, { preview: true });
372
386
  else {
387
+ refuseInRole(process.env, "catherd profile reset-host-defaults");
373
388
  if (!args.expect)
374
389
  throw new CatherdError(
375
390
  "E_INPUT_INVALID",
@@ -0,0 +1,159 @@
1
+ import { defineCommand } from "citty";
2
+ import { CatherdError } from "../domain/errors.ts";
3
+ import { ID_PATTERN } from "../domain/ids.ts";
4
+ import { roleScopeFromEnv } from "../domain/role-scope.ts";
5
+ import { gateCheckOrList, gatePass } from "../services/gate-service.ts";
6
+ import { DISPATCH_ID_ENV, activityReporter } from "../infra/lock-activity.ts";
7
+ import { runMilestoneCheck } from "../services/milestone-check.ts";
8
+ import { assertRoleMay } from "../services/role-access.ts";
9
+ import { readRunFile, writeRunFile } from "../services/run-service.ts";
10
+ import { printJson } from "./cli-kit.ts";
11
+ import { defaultDeps } from "./deps.ts";
12
+
13
+ /**
14
+ * Spec 1.5 plan 21: the role server's operations as commands any role can run from its shell, on a backend
15
+ * whose harness has no role server (opencode, Cursor) or when no MCP tool is listed. In a role
16
+ * (CATHERD_ROLE, or its scratch TMPDIR) they are bound to its own run and its role's operations.
17
+ */
18
+ const scope = () => roleScopeFromEnv(process.env);
19
+
20
+ const RUN_PATH = {
21
+ run: { type: "positional", required: true, description: "the run id" },
22
+ path: { type: "positional", required: true, description: "a path relative to the run folder" },
23
+ } as const;
24
+
25
+ const read = defineCommand({
26
+ meta: { name: "read", description: "Print a file of the run folder (read_run_file)" },
27
+ args: RUN_PATH,
28
+ run({ args }) {
29
+ assertRoleMay(scope(), args.run, "read_run_file");
30
+ const text = readRunFile({ run: args.run, path: args.path });
31
+ process.stdout.write(text.endsWith("\n") ? text : `${text}\n`);
32
+ },
33
+ });
34
+
35
+ const write = defineCommand({
36
+ meta: { name: "write", description: "Write stdin to a file of the run folder (write_run_file)" },
37
+ args: RUN_PATH,
38
+ async run({ args }) {
39
+ assertRoleMay(scope(), args.run, "write_run_file");
40
+ const content = await Bun.stdin.text();
41
+ printJson(writeRunFile({ run: args.run, path: args.path, content }));
42
+ },
43
+ });
44
+
45
+ export const runFileCommand = defineCommand({
46
+ meta: { name: "run-file", description: "Read or write a run's plan, lanes, dossier and notes" },
47
+ subCommands: { read, write },
48
+ });
49
+
50
+ const GATE = {
51
+ run: { type: "positional", required: true, description: "the run id" },
52
+ item: { type: "string", required: true, description: "the gate item's name" },
53
+ command: { type: "string", required: true, description: "the command that checks it" },
54
+ paths: { type: "string", required: true, description: "the repo paths it depends on, comma-separated" },
55
+ } as const;
56
+
57
+ const pathsOf = (s: string): string[] => {
58
+ const paths = s
59
+ .split(",")
60
+ .map((p) => p.trim())
61
+ .filter(Boolean);
62
+ if (!paths.length)
63
+ throw new CatherdError("E_INPUT_INVALID", "--paths names no path", {
64
+ fix: "pass --paths src,package.json",
65
+ });
66
+ return paths;
67
+ };
68
+
69
+ const check = defineCommand({
70
+ meta: { name: "check", description: "Whether a gate item passed on unchanged content (gate_check)" },
71
+ // plan 23: with only the run and --milestone, it lists the milestone's recorded items (as gate_check does)
72
+ args: {
73
+ run: GATE.run,
74
+ item: { ...GATE.item, required: false },
75
+ command: { ...GATE.command, required: false },
76
+ paths: { ...GATE.paths, required: false },
77
+ milestone: { type: "string", description: "the milestone you verify, like M1" },
78
+ },
79
+ async run({ args }) {
80
+ assertRoleMay(scope(), args.run, "gate_check");
81
+ if (args.milestone !== undefined && !ID_PATTERN.test(args.milestone))
82
+ throw new CatherdError("E_INPUT_INVALID", `bad milestone "${args.milestone}"`, {
83
+ fix: "pass --milestone M1",
84
+ });
85
+ printJson(
86
+ await gateCheckOrList(defaultDeps(), {
87
+ run: args.run,
88
+ item: args.item,
89
+ command: args.command,
90
+ paths: args.paths === undefined ? undefined : pathsOf(args.paths),
91
+ ...(args.milestone ? { milestone: args.milestone } : {}),
92
+ }),
93
+ );
94
+ },
95
+ });
96
+
97
+ const pass = defineCommand({
98
+ meta: { name: "pass", description: "Record a passed gate item with its evidence (gate_pass)" },
99
+ args: { ...GATE, evidence: { type: "string", required: true, description: "what showed it passed" } },
100
+ async run({ args }) {
101
+ assertRoleMay(scope(), args.run, "gate_pass");
102
+ printJson(
103
+ await gatePass(defaultDeps(), {
104
+ run: args.run,
105
+ item: args.item,
106
+ command: args.command,
107
+ paths: pathsOf(args.paths),
108
+ evidence: args.evidence,
109
+ }),
110
+ );
111
+ },
112
+ });
113
+
114
+ const runCheck = defineCommand({
115
+ meta: {
116
+ name: "run",
117
+ description: "Run the milestone's full check on the committed HEAD, detached (gate_run)",
118
+ },
119
+ args: {
120
+ run: GATE.run,
121
+ milestone: { type: "string", required: true, description: "the milestone you verify, like M1" },
122
+ command: GATE.command,
123
+ at: { type: "string", description: "a checkout of the run's base, to record the base's own failures" },
124
+ force: { type: "boolean", description: "run it again although a record of this HEAD exists" },
125
+ },
126
+ async run({ args }) {
127
+ assertRoleMay(scope(), args.run, "gate_run");
128
+ if (!ID_PATTERN.test(args.milestone))
129
+ throw new CatherdError("E_INPUT_INVALID", `bad milestone "${args.milestone}"`, {
130
+ fix: "pass --milestone M1",
131
+ });
132
+ // a shell tool call may be cut off; the check goes on, and this ticks the dispatch's activity while it waits
133
+ const reporter = activityReporter(process.env[DISPATCH_ID_ENV]);
134
+ try {
135
+ const r = await runMilestoneCheck(
136
+ {
137
+ run: args.run,
138
+ milestone: args.milestone,
139
+ command: args.command,
140
+ ...(args.at ? { at: args.at } : {}),
141
+ ...(args.force ? { force: true } : {}),
142
+ },
143
+ reporter ? { onWait: reporter.tick } : {},
144
+ );
145
+ printJson(r);
146
+ if (!r.running && r.missing.length > 0) process.exitCode = 1;
147
+ } finally {
148
+ reporter?.done();
149
+ }
150
+ },
151
+ });
152
+
153
+ export const gateCommand = defineCommand({
154
+ meta: {
155
+ name: "gate",
156
+ description: "A verifier's gate evidence: check an item, record a pass, run the check",
157
+ },
158
+ subCommands: { check, pass, run: runCheck },
159
+ });
@@ -4,6 +4,7 @@ import { assertId } from "../domain/ids.ts";
4
4
  import { knownQueueCapability, UNCHECKED_QUEUE } from "../infra/codex-queue.ts";
5
5
  import { listDispatches } from "../services/dispatches.ts";
6
6
  import { retryDelivery } from "../services/notifier.ts";
7
+ import { pauseLine } from "../services/pause.ts";
7
8
  import { inspectDelivery } from "../services/run-debug.ts";
8
9
  import { terminalHost } from "./host-arg.ts";
9
10
  import { formatBudget } from "../domain/budget.ts";
@@ -13,10 +14,13 @@ import { redact } from "../infra/log.ts";
13
14
  import { cancel } from "../services/dispatch-service.ts";
14
15
  import { registerSavedSecrets } from "../services/jev-service.ts";
15
16
  import { runDebug } from "../services/run-debug.ts";
17
+ import { cleanScratch } from "../services/scratch.ts";
18
+ import { repin } from "../services/run-pin.ts";
19
+ import { supersedeRun } from "../services/run-service.ts";
16
20
  import { findRun, listRuns, readRecords, type Run } from "../services/run-store.ts";
17
21
  import { groupRuns, type RunSession, type SessionGroup } from "../services/session-view.ts";
18
22
  import { type RunSummary, status, summarizeRun } from "../services/summary.ts";
19
- import { JSON_ARG, mark, printJson } from "./cli-kit.ts";
23
+ import { JSON_ARG, mark, printJson, refuseInRole } from "./cli-kit.ts";
20
24
  import { defaultDeps } from "./deps.ts";
21
25
 
22
26
  const json = JSON_ARG;
@@ -40,6 +44,10 @@ export function formatRun(s: RunSummary, now: number = Date.now()): string[] {
40
44
  ];
41
45
  for (const q of s.questions) lines.push(` ${mark("warn")} parked ${q.milestone}: ${q.question}`);
42
46
  for (const l of s.live) lines.push(` live ${l.name} ${l.rung} ${l.state} ${l.secs}s`);
47
+ if (s.waiting)
48
+ lines.push(
49
+ ` ${s.waiting.stalled ? `${mark("warn")} stalled · ` : ""}waiting for orchestrator since ${s.waiting.since} · ${s.waiting.seconds}s · ${s.waiting.unread} unread result(s)`,
50
+ );
43
51
  lines.push(
44
52
  ` done ${t.runs} role run(s), ${t.ok} ok${t.notOk.length ? `; not ok: ${t.notOk.join(", ")}` : ""}`,
45
53
  ` tokens ${n(t.tokens.input)} in (${n(t.tokens.cached)} cached) · ${n(t.tokens.output)} out · $${t.costUsd.toFixed(2)}` +
@@ -56,7 +64,7 @@ export function formatRun(s: RunSummary, now: number = Date.now()): string[] {
56
64
  for (const m of s.milestones) lines.push(` landed ${m}`);
57
65
  if (s.verifier)
58
66
  lines.push(
59
- ` verifier step ${s.verifier.item}${s.verifier.carried ? " (carried over)" : ""} at ${s.verifier.at.slice(11, 16)}`,
67
+ ` verifier step ${s.verifier.item}${s.verifier.carried ? " (carried over)" : ""} at ${s.verifier.at.slice(11, 16)} · ${Math.floor(s.verifier.secs / 60)} min ago · ${s.verifier.open ? "open" : `closed (${s.verifier.closedBy})`}`,
60
68
  );
61
69
  for (const d of s.delivery)
62
70
  lines.push(
@@ -83,6 +91,8 @@ async function printStatus(runId: string | undefined, asJson: boolean, live = fa
83
91
  : null;
84
92
  const r = redact(status(defaultDeps(host), runId, queue));
85
93
  if (asJson) return printJson(r);
94
+ // spec 1.5 "Group pause": a pause comes first, before any run
95
+ for (const p of r.paused) console.log(`${mark("warn")} ${pauseLine(p)}`);
86
96
  if (r.runs.length === 0) console.log("no runs yet");
87
97
  // grouped by session, as the runs page is (spec §4); a run shows once, under the session that started it
88
98
  const byId = new Map(r.runs.map((s) => [s.id, s]));
@@ -105,7 +115,7 @@ async function printStatus(runId: string | undefined, asJson: boolean, live = fa
105
115
  for (const l of rest) console.log(l);
106
116
  }
107
117
  }
108
- console.log(`host ${r.host.host}${r.host.conflict ? ` · conflict: ${r.host.conflict}` : ""}`);
118
+ console.log(`inspection host ${r.host.host}${r.host.conflict ? ` · conflict: ${r.host.conflict}` : ""}`);
109
119
  if (r.queue)
110
120
  console.log(
111
121
  r.queue.checked === false
@@ -115,11 +125,12 @@ async function printStatus(runId: string | undefined, asJson: boolean, live = fa
115
125
  for (const w of r.warnings) console.log(`${mark("warn")} ${w}`);
116
126
  }
117
127
 
118
- /** Spec §8 `catherd status [run] [--json]`: that run, else every run with a live role, else the newest. */
128
+ /** `catherd status [run] [--json]`: that run, else the live runs, else the runs waiting for their orchestrator, else the newest. */
119
129
  export const statusCommand = defineCommand({
120
130
  meta: {
121
131
  name: "status",
122
- description: "A run at a glance (default: runs with a live role, else the newest)",
132
+ description:
133
+ "A run at a glance (default: live runs, else runs waiting for their orchestrator, else the newest)",
123
134
  },
124
135
  args: { run: { type: "positional", required: false, description: "run id" }, ...json },
125
136
  run({ args }) {
@@ -138,36 +149,20 @@ export function redrawMs(interval: string | undefined): number {
138
149
  return Math.max(1, secs) * 1000;
139
150
  }
140
151
 
141
- /** Spec §8 `catherd watch [--once]`: the TUI's Runs tab on a terminal, else a plain redraw. */
152
+ /** Spec §8 `catherd watch [--once]`: the live runs redrawn as text. */
142
153
  export const watchCommand = defineCommand({
143
154
  meta: {
144
155
  name: "watch",
145
- description:
146
- "The live runs: the dashboard's Runs tab on a terminal, else redrawn as text (--once: print once)",
156
+ description: "The live runs, redrawn as text (--once: print once); catherd panel shows them in a browser",
147
157
  },
148
158
  args: {
149
159
  once: { type: "boolean", description: "print one snapshot and exit" },
150
- interval: { type: "string", description: "seconds between text redraws when piped (default 2)" },
151
- plain: {
152
- type: "boolean",
153
- description: "the dashboard in ASCII without colour (NO_COLOR also drops colour)",
154
- },
155
- "reduced-motion": { type: "boolean", description: "the dashboard without animation" },
160
+ interval: { type: "string", description: "seconds between text redraws (default 2)" },
156
161
  ...json,
157
162
  },
158
163
  async run({ args }) {
159
164
  const every = redrawMs(args.interval);
160
165
  if (args.once || args.json) return printStatus(undefined, args.json === true);
161
- // on a terminal, watch is the dashboard's Runs tab (plan 6 Ruling 10); piped output keeps the plain view
162
- if (process.stdin.isTTY && process.stdout.isTTY) {
163
- const { openTui } = await import("./tui/run.tsx");
164
- const rawArgs = [
165
- ...(args.plain ? ["--plain"] : []),
166
- ...(args["reduced-motion"] ? ["--reduced-motion"] : []),
167
- ];
168
- process.exitCode = await openTui({ rawArgs, tab: "runs", tty: true });
169
- return;
170
- }
171
166
  for (;;) {
172
167
  if (process.stdout.isTTY) process.stdout.write("\x1b[2J\x1b[H");
173
168
  await printStatus(undefined, false, true);
@@ -200,6 +195,8 @@ const list = defineCommand({
200
195
  roleRuns: s.totals.runs,
201
196
  session: s.session,
202
197
  continuedIn: s.continuedIn,
198
+ waiting: s.waiting,
199
+ supersededBy: s.supersededBy ?? null,
203
200
  };
204
201
  };
205
202
  const groups = groupRuns(shown);
@@ -210,7 +207,7 @@ const list = defineCommand({
210
207
  for (const x of g.runs) {
211
208
  const r = row(x.run);
212
209
  console.log(
213
- ` ${r.id} ${r.live ? `${r.live} live` : "idle"} ${r.roleRuns} role run(s) ${r.title} ${r.repo}${movedNote(x)}`,
210
+ ` ${r.id} ${r.supersededBy ? `superseded by ${r.supersededBy}` : r.live ? `${r.live} live` : r.waiting ? `${r.waiting.stalled ? "stalled · " : ""}waiting for orchestrator ${r.waiting.seconds}s` : "idle"} ${r.roleRuns} role run(s) ${r.title} ${r.repo}${movedNote(x)}`,
214
211
  );
215
212
  }
216
213
  }
@@ -278,12 +275,31 @@ const cancelCmd = defineCommand({
278
275
  name: { type: "positional", required: true, description: "the role's name, as status lists it" },
279
276
  },
280
277
  async run({ args }) {
278
+ refuseInRole(process.env, "catherd runs cancel");
281
279
  const r = await cancel(defaultDeps(), args.id, args.name);
282
280
  console.log(`${mark("ok")} ${r.record.name} ${r.record.status}`);
283
281
  for (const h of r.hints) console.log(` ${h}`);
284
282
  },
285
283
  });
286
284
 
285
+ const clean = defineCommand({
286
+ meta: {
287
+ name: "clean",
288
+ description:
289
+ "Remove the roles' scratch folders (their TMPDIR) of a run with no live role, or of every such run",
290
+ },
291
+ args: { id: { type: "positional", required: false, description: "run id (default: every run)" }, ...json },
292
+ run({ args }) {
293
+ refuseInRole(process.env, "catherd runs clean");
294
+ const r = cleanScratch({ run: args.id });
295
+ if (args.json) return printJson(r);
296
+ for (const x of r.removed)
297
+ console.log(`${mark("ok")} ${x.run} scratch removed (${n(Math.round(x.bytes / 1024))} KB)`);
298
+ for (const x of r.kept) console.log(`${mark("skip")} ${x.run} scratch kept: ${x.why}`);
299
+ if (!r.removed.length && !r.kept.length) console.log("no scratch to remove");
300
+ },
301
+ });
302
+
287
303
  const retryPush = defineCommand({
288
304
  meta: {
289
305
  name: "retry-push",
@@ -300,6 +316,7 @@ const retryPush = defineCommand({
300
316
  ...json,
301
317
  },
302
318
  async run({ args }) {
319
+ refuseInRole(process.env, "catherd runs retry-push");
303
320
  if (args["acknowledge-possible-duplicate"] !== true)
304
321
  throw new CatherdError("E_INPUT_INVALID", "Retry requires --acknowledge-possible-duplicate");
305
322
  assertId("role name", args.name);
@@ -328,9 +345,46 @@ const retryPush = defineCommand({
328
345
  },
329
346
  });
330
347
 
348
+ const supersede = defineCommand({
349
+ meta: {
350
+ name: "supersede",
351
+ description: "Close a run with a pointer to the run that took it over; status hides it",
352
+ },
353
+ args: {
354
+ id: { type: "positional", required: true, description: "the run to close" },
355
+ by: { type: "string", required: true, description: "the run that took it over" },
356
+ ...json,
357
+ },
358
+ async run({ args }) {
359
+ refuseInRole(process.env, "catherd runs supersede");
360
+ const r = await supersedeRun(defaultDeps(), { run: args.id, by: args.by });
361
+ if (args.json) return printJson(r);
362
+ console.log(`${mark("ok")} ${r.run} superseded by ${r.by}`);
363
+ for (const h of r.hints ?? []) console.log(` ${h}`);
364
+ },
365
+ });
366
+
367
+ const pin = defineCommand({
368
+ meta: {
369
+ name: "pin",
370
+ description: "Re-pin a run to the profile, access and isolation its repo runs on now",
371
+ },
372
+ args: { id: { type: "positional", required: true, description: "run id" }, ...json },
373
+ async run({ args }) {
374
+ refuseInRole(process.env, "catherd runs pin");
375
+ const r = await repin(defaultDeps(), { run: args.id });
376
+ if (args.json) return printJson(r);
377
+ console.log(`${mark("ok")} ${args.id} pinned to ${r.pin.profile}`);
378
+ for (const c of r.changed) console.log(` was: ${c}`);
379
+ },
380
+ });
381
+
331
382
  /** Spec §8 `catherd runs list|show [--debug]|cancel`; a bare `catherd runs` lists them, as `status` needs no run. */
332
383
  export const runsCommand = defineCommand({
333
- meta: { name: "runs", description: "Runs: list them (the default), show one, cancel a live role" },
334
- subCommands: { list, show, cancel: cancelCmd, "retry-push": retryPush },
384
+ meta: {
385
+ name: "runs",
386
+ description: "Runs: list them (the default), show one, cancel a live role, supersede or re-pin one",
387
+ },
388
+ subCommands: { list, show, cancel: cancelCmd, clean, "retry-push": retryPush, supersede, pin },
335
389
  default: "list",
336
390
  });