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.
- package/README.md +188 -169
- package/THIRD_PARTY_NOTICES.md +217 -35
- package/catalog/jev.json +27 -1
- package/catalog/models.json +56 -239
- package/package.json +6 -8
- package/plugin/.claude-plugin/plugin.json +1 -1
- package/plugin/.codex-plugin/plugin.json +1 -1
- package/plugin/bin/catherd-mcp +1 -1
- package/plugin/commands/catherd.md +2 -2
- package/plugin/skills/catherd/SKILL.md +97 -356
- package/plugin/skills/catherd-setup/SKILL.md +21 -20
- package/src/adapters/all.ts +0 -4
- package/src/adapters/backend.ts +19 -4
- package/src/adapters/claude-code/index.ts +126 -14
- package/src/adapters/codex/events.ts +10 -0
- package/src/adapters/codex/index.ts +17 -5
- package/src/adapters/cursor/home.ts +1 -1
- package/src/adapters/cursor/index.ts +10 -4
- package/src/adapters/opencode/index.ts +38 -1
- package/src/cli.ts +27 -27
- package/src/domain/agents.ts +3 -1
- package/src/domain/bars.ts +33 -107
- package/src/domain/blocked.ts +72 -0
- package/src/domain/branch.ts +51 -0
- package/src/domain/brief.ts +106 -0
- package/src/domain/catalog.ts +69 -313
- package/src/domain/changes.ts +23 -1
- package/src/domain/cost.ts +19 -29
- package/src/domain/errors.ts +6 -2
- package/src/domain/fail-lines.ts +26 -0
- package/src/domain/failover.ts +37 -75
- package/src/domain/findings.ts +47 -0
- package/src/domain/gate-brief.ts +53 -0
- package/src/domain/hints.ts +13 -2
- package/src/domain/ids.ts +13 -2
- package/src/domain/jev.ts +47 -2
- package/src/domain/judge.ts +124 -0
- package/src/domain/lane.ts +118 -3
- package/src/domain/milestone-memory.ts +53 -0
- package/src/domain/notice.ts +32 -8
- package/src/domain/notify.ts +32 -0
- package/src/domain/plan-doc.ts +198 -0
- package/src/domain/plan-lint.ts +141 -0
- package/src/domain/profile-rules.ts +175 -138
- package/src/domain/profile.ts +140 -63
- package/src/domain/record.ts +68 -3
- package/src/domain/reply.ts +29 -0
- package/src/domain/role-prompts.ts +182 -56
- package/src/domain/role-scope.ts +76 -0
- package/src/domain/role-tools.ts +34 -0
- package/src/domain/roles.ts +2 -0
- package/src/domain/route.ts +90 -0
- package/src/domain/rules.ts +33 -0
- package/src/domain/run-events.ts +298 -0
- package/src/domain/run-machine.ts +668 -0
- package/src/domain/run-state.ts +1310 -0
- package/src/domain/scorecard.ts +197 -0
- package/src/domain/select.ts +229 -72
- package/src/domain/slopalytics.ts +72 -0
- package/src/domain/state.ts +5 -0
- package/src/domain/util.ts +22 -0
- package/src/domain/workspace.ts +130 -0
- package/src/entry/answer-command.ts +39 -0
- package/src/entry/catalog-command.ts +24 -165
- package/src/entry/check-bin.ts +17 -0
- package/src/entry/cli-kit.ts +18 -2
- package/src/entry/daemon-bin.ts +22 -0
- package/src/entry/deps.ts +2 -0
- package/src/entry/doctor-command.ts +20 -2
- package/src/entry/glyphs.ts +15 -2
- package/src/entry/host-arg.ts +28 -0
- package/src/entry/init-command.ts +20 -69
- package/src/entry/knowledge-command.ts +121 -5
- package/src/entry/lock-command.ts +103 -8
- package/src/entry/mcp/handshake.ts +6 -2
- package/src/entry/mcp/role-bin.ts +20 -0
- package/src/entry/mcp/role-server.ts +134 -0
- package/src/entry/mcp/server.ts +85 -21
- package/src/entry/mcp/session-tools.ts +149 -0
- package/src/entry/panel/openapi.ts +189 -0
- package/src/entry/panel/server.ts +410 -0
- package/src/entry/panel-command.ts +68 -0
- package/src/entry/pause-command.ts +67 -0
- package/src/entry/profile-command.ts +33 -18
- package/src/entry/role-cli-command.ts +159 -0
- package/src/entry/runs-command.ts +81 -27
- package/src/entry/scorecard-command.ts +187 -0
- package/src/entry/supervise-bin.ts +1 -1
- package/src/entry/supervise-command.ts +30 -3
- package/src/entry/workspace-command.ts +44 -0
- package/src/infra/boot-lock.ts +25 -0
- package/src/infra/delivery.ts +3 -0
- package/src/infra/dispatch-dir.ts +2 -0
- package/src/infra/env.ts +11 -3
- package/src/infra/git.ts +70 -0
- package/src/infra/heavy-lock.ts +133 -6
- package/src/infra/host-probe.ts +16 -0
- package/src/infra/launch.ts +63 -7
- package/src/infra/lock-activity.ts +88 -0
- package/src/infra/login-env.ts +48 -0
- package/src/infra/memory.ts +45 -0
- package/src/infra/ntfy.ts +40 -0
- package/src/infra/open-url.ts +42 -0
- package/src/infra/paths.ts +37 -2
- package/src/infra/role-mcp.ts +69 -0
- package/src/infra/slopalytics.ts +30 -0
- package/src/infra/store.ts +6 -1
- package/src/infra/supervisor.ts +107 -16
- package/src/services/admission.ts +133 -31
- package/src/services/backends.ts +4 -6
- package/src/services/budget.ts +26 -6
- package/src/services/capture.ts +1 -22
- package/src/services/catalog-service.ts +155 -254
- package/src/services/credentials.ts +15 -26
- package/src/services/daemon.ts +181 -0
- package/src/services/dispatch-service.ts +209 -17
- package/src/services/dispatches.ts +30 -2
- package/src/services/doctor-access.ts +8 -3
- package/src/services/doctor-backends.ts +1 -1
- package/src/services/doctor-checks.ts +47 -0
- package/src/services/doctor-docker.ts +221 -0
- package/src/services/doctor-role-mcp.ts +65 -0
- package/src/services/doctor.ts +72 -15
- package/src/services/end-push.ts +157 -0
- package/src/services/failover-rungs.ts +109 -0
- package/src/services/finalize.ts +24 -2
- package/src/services/gate-env.ts +217 -0
- package/src/services/gate-service.ts +231 -45
- package/src/services/global-install.ts +46 -13
- package/src/services/jev-service.ts +2 -4
- package/src/services/lane-edit.ts +154 -0
- package/src/services/lane-service.ts +291 -55
- package/src/services/milestone-check.ts +433 -0
- package/src/services/milestone-tree.ts +170 -0
- package/src/services/milestones.ts +167 -29
- package/src/services/notifier.ts +124 -4
- package/src/services/notify-service.ts +26 -0
- package/src/services/orchestrator-wait.ts +82 -0
- package/src/services/panel-setup.ts +116 -0
- package/src/services/pause.ts +161 -0
- package/src/services/peek.ts +79 -17
- package/src/services/plan-input.ts +151 -0
- package/src/services/ports.ts +60 -5
- package/src/services/preflight.ts +232 -24
- package/src/services/profile-service.ts +31 -3
- package/src/services/profile-store.ts +18 -6
- package/src/services/protocol.ts +123 -25
- package/src/services/provenance.ts +34 -89
- package/src/services/questions.ts +24 -0
- package/src/services/reconcile.ts +33 -1
- package/src/services/reentry.ts +7 -6
- package/src/services/role-access.ts +27 -0
- package/src/services/routing-service.ts +161 -42
- package/src/services/run-briefs.ts +477 -0
- package/src/services/run-client.ts +217 -0
- package/src/services/run-effects.ts +837 -0
- package/src/services/run-events-store.ts +174 -0
- package/src/services/run-evidence.ts +1 -1
- package/src/services/run-pin.ts +125 -0
- package/src/services/run-read.ts +221 -0
- package/src/services/run-report.ts +176 -0
- package/src/services/run-service.ts +204 -23
- package/src/services/run-store.ts +159 -18
- package/src/services/scorecard-metrics.ts +86 -0
- package/src/services/scorecard.ts +382 -0
- package/src/services/scratch.ts +60 -0
- package/src/services/sessions.ts +3 -1
- package/src/services/state.ts +21 -0
- package/src/services/summary.ts +76 -12
- package/src/services/tree-path.ts +27 -0
- package/src/services/verifier-brief.ts +23 -0
- package/src/services/verifier-step.ts +48 -0
- package/src/services/workspace-admission.ts +259 -0
- package/src/services/workspace-service.ts +259 -0
- package/src/services/workspace-store.ts +95 -0
- package/web/dist/assets/Brand-PKlUX5TE.js +1 -0
- package/web/dist/assets/Brand-PKlUX5TE.js.br +0 -0
- package/web/dist/assets/Brand-PKlUX5TE.js.gz +0 -0
- package/web/dist/assets/Modal-Dxc5so53.js +1 -0
- package/web/dist/assets/Modal-Dxc5so53.js.br +0 -0
- package/web/dist/assets/Modal-Dxc5so53.js.gz +0 -0
- package/web/dist/assets/Pressable-C8-T6Ed_.js +1 -0
- package/web/dist/assets/StatusChip-B5xfI_Mn.js +153 -0
- package/web/dist/assets/StatusChip-B5xfI_Mn.js.br +0 -0
- package/web/dist/assets/StatusChip-B5xfI_Mn.js.gz +0 -0
- package/web/dist/assets/_app-Ea5cpyKe.js +1 -0
- package/web/dist/assets/_app-Ea5cpyKe.js.br +0 -0
- package/web/dist/assets/_app-Ea5cpyKe.js.gz +0 -0
- package/web/dist/assets/_runId-pTG0fXVo.js +1 -0
- package/web/dist/assets/_runId-pTG0fXVo.js.br +0 -0
- package/web/dist/assets/_runId-pTG0fXVo.js.gz +0 -0
- package/web/dist/assets/drawer-Du3cdlok.js +1 -0
- package/web/dist/assets/drawer-Du3cdlok.js.br +0 -0
- package/web/dist/assets/drawer-Du3cdlok.js.gz +0 -0
- package/web/dist/assets/geist-cyrillic-ext-wght-normal-DjL33-gN.woff2 +0 -0
- package/web/dist/assets/geist-cyrillic-wght-normal-BEAKL7Jp.woff2 +0 -0
- package/web/dist/assets/geist-latin-ext-wght-normal-DC-KSUi6.woff2 +0 -0
- package/web/dist/assets/geist-latin-wght-normal-BgDaEnEv.woff2 +0 -0
- package/web/dist/assets/geist-mono-cyrillic-ext-wght-normal-X_5orZeX.woff2 +0 -0
- package/web/dist/assets/geist-mono-cyrillic-wght-normal-DiZS0aHC.woff2 +0 -0
- package/web/dist/assets/geist-mono-latin-ext-wght-normal-Bwz-egvJ.woff2 +0 -0
- package/web/dist/assets/geist-mono-latin-wght-normal-XN7g48iV.woff2 +0 -0
- package/web/dist/assets/geist-mono-symbols2-wght-normal-CO5SzqOn.woff2 +0 -0
- package/web/dist/assets/geist-mono-vietnamese-wght-normal-DadHysG0.woff2 +0 -0
- package/web/dist/assets/geist-vietnamese-wght-normal-6IgcOCM7.woff2 +0 -0
- package/web/dist/assets/index-B6GiwDkh.js +11 -0
- package/web/dist/assets/index-B6GiwDkh.js.br +0 -0
- package/web/dist/assets/index-B6GiwDkh.js.gz +0 -0
- package/web/dist/assets/index-C5IbylvM.css +2 -0
- package/web/dist/assets/index-C5IbylvM.css.br +0 -0
- package/web/dist/assets/index-C5IbylvM.css.gz +0 -0
- package/web/dist/assets/label-DFSYBc62.js +1 -0
- package/web/dist/assets/label-DFSYBc62.js.br +0 -0
- package/web/dist/assets/label-DFSYBc62.js.gz +0 -0
- package/web/dist/assets/link-D8EcKZUt.js +1 -0
- package/web/dist/assets/link-D8EcKZUt.js.br +0 -0
- package/web/dist/assets/link-D8EcKZUt.js.gz +0 -0
- package/web/dist/assets/login-DaN5HPPW.js +1 -0
- package/web/dist/assets/login-DaN5HPPW.js.br +0 -0
- package/web/dist/assets/login-DaN5HPPW.js.gz +0 -0
- package/web/dist/assets/query-Cu--ccT3.js +1 -0
- package/web/dist/assets/questions-shoO9OVj.js +1 -0
- package/web/dist/assets/questions-shoO9OVj.js.br +0 -0
- package/web/dist/assets/questions-shoO9OVj.js.gz +0 -0
- package/web/dist/assets/react-query.gen-Bu2yV-qJ.js +12 -0
- package/web/dist/assets/react-query.gen-Bu2yV-qJ.js.br +0 -0
- package/web/dist/assets/react-query.gen-Bu2yV-qJ.js.gz +0 -0
- package/web/dist/assets/rolldown-runtime-XKyaHtl9.js +1 -0
- package/web/dist/assets/run-status-C0DRuV7U.js +1 -0
- package/web/dist/assets/runs-C3rg2zZc.js +1 -0
- package/web/dist/assets/runs-C3rg2zZc.js.br +0 -0
- package/web/dist/assets/runs-C3rg2zZc.js.gz +0 -0
- package/web/dist/assets/setup-5I4Vl9fR.js +1 -0
- package/web/dist/assets/setup-5I4Vl9fR.js.br +0 -0
- package/web/dist/assets/setup-5I4Vl9fR.js.gz +0 -0
- package/web/dist/assets/table-BOxXhqS4.js +1 -0
- package/web/dist/assets/table-BOxXhqS4.js.br +0 -0
- package/web/dist/assets/table-BOxXhqS4.js.gz +0 -0
- package/web/dist/assets/textfield-BzRFkxBF.js +1 -0
- package/web/dist/assets/textfield-BzRFkxBF.js.br +0 -0
- package/web/dist/assets/textfield-BzRFkxBF.js.gz +0 -0
- package/web/dist/assets/time--9kSQbbp.js +1 -0
- package/web/dist/assets/use-css-variable-CpnN9WtA.js +5 -0
- package/web/dist/assets/use-css-variable-CpnN9WtA.js.br +0 -0
- package/web/dist/assets/use-css-variable-CpnN9WtA.js.gz +0 -0
- package/web/dist/assets/useMutation-CVPKlR34.js +1 -0
- package/web/dist/assets/useMutation-CVPKlR34.js.br +0 -0
- package/web/dist/assets/useMutation-CVPKlR34.js.gz +0 -0
- package/web/dist/index.html +15 -0
- package/catalog/ATTRIBUTION.md +0 -36
- package/catalog/scores.json +0 -3202
- package/catalog/sources.json +0 -97
- package/src/adapters/antigravity/events.ts +0 -131
- package/src/adapters/antigravity/home.ts +0 -46
- package/src/adapters/antigravity/index.ts +0 -320
- package/src/adapters/antigravity/models.ts +0 -37
- package/src/adapters/grok/events.ts +0 -112
- package/src/adapters/grok/home.ts +0 -132
- package/src/adapters/grok/index.ts +0 -339
- package/src/adapters/grok/models.ts +0 -48
- package/src/domain/calibration.ts +0 -125
- package/src/domain/sources.ts +0 -144
- package/src/entry/mcp/dispatch-tools.ts +0 -52
- package/src/entry/mcp/lane-tools.ts +0 -84
- package/src/entry/mcp/protocol-tools.ts +0 -57
- package/src/entry/mcp/run-tools.ts +0 -148
- package/src/entry/mcp/setup-tools.ts +0 -108
- package/src/entry/tui/commands.ts +0 -563
- package/src/entry/tui/effects.ts +0 -381
- package/src/entry/tui/fixtures.ts +0 -637
- package/src/entry/tui/profile-edits.ts +0 -146
- package/src/entry/tui/profile-tree.ts +0 -429
- package/src/entry/tui/providers/app.tsx +0 -162
- package/src/entry/tui/providers/boundary.tsx +0 -57
- package/src/entry/tui/providers/data.tsx +0 -160
- package/src/entry/tui/providers/keymap.tsx +0 -166
- package/src/entry/tui/providers/root.tsx +0 -45
- package/src/entry/tui/providers/theme.tsx +0 -17
- package/src/entry/tui/providers/toast.tsx +0 -77
- package/src/entry/tui/run.tsx +0 -114
- package/src/entry/tui/state.ts +0 -373
- package/src/entry/tui/stories.ts +0 -114
- package/src/entry/tui/text.ts +0 -130
- package/src/entry/tui/theme.ts +0 -140
- package/src/entry/tui/views/app.tsx +0 -208
- package/src/entry/tui/views/dialogs.tsx +0 -41
- package/src/entry/tui/views/profile-actions.ts +0 -331
- package/src/entry/tui/views/profiles.tsx +0 -520
- package/src/entry/tui/views/runs.tsx +0 -573
- package/src/entry/tui/views/save-dialog.tsx +0 -340
- package/src/entry/tui/views/status.tsx +0 -209
- package/src/entry/tui/widgets/chrome.tsx +0 -114
- package/src/entry/tui/widgets/dialog-confirm.tsx +0 -58
- package/src/entry/tui/widgets/dialog-prompt.tsx +0 -52
- package/src/entry/tui/widgets/dialog-select.tsx +0 -212
- package/src/entry/tui/widgets/dialog.tsx +0 -68
- package/src/entry/tui/widgets/line.tsx +0 -92
- package/src/entry/tui/widgets/list.tsx +0 -197
- package/src/entry/tui/widgets/spinner.tsx +0 -49
- package/src/entry/tui/widgets/toast.tsx +0 -62
- package/src/infra/sources/arena.ts +0 -87
- package/src/infra/sources/artificial-analysis.ts +0 -112
- package/src/infra/sources/cache.ts +0 -124
- package/src/infra/sources/csv.ts +0 -38
- package/src/infra/sources/epoch.ts +0 -82
- package/src/infra/sources/http.ts +0 -75
- package/src/infra/sources/litellm.ts +0 -58
- package/src/infra/sources/models-dev.ts +0 -59
- package/src/infra/sources/openrouter-endpoints.ts +0 -52
- package/src/infra/sources/openrouter-models.ts +0 -60
- package/src/infra/sources/rows.ts +0 -38
- package/src/infra/sources/vectara.ts +0 -69
- package/src/infra/sources/zip.ts +0 -48
- package/src/services/catalog-refresh.ts +0 -201
- package/src/services/doctor-sources.ts +0 -76
- package/src/services/runs-page.ts +0 -255
- package/src/services/source-derive.ts +0 -374
- package/src/services/source-sync.ts +0 -305
- package/src/services/standins.ts +0 -204
- 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 {
|
|
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 {
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
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(
|
|
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
|
-
/**
|
|
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:
|
|
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
|
|
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
|
|
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: {
|
|
334
|
-
|
|
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
|
});
|