catherd-cli 1.0.0 → 1.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +66 -42
- package/package.json +1 -1
- package/plugin/.claude-plugin/plugin.json +1 -1
- package/plugin/.mcp.json +2 -2
- package/plugin/bin/catherd-mcp +30 -0
- package/plugin/commands/catherd.md +1 -1
- package/plugin/skills/catherd/SKILL.md +112 -95
- package/plugin/skills/catherd-setup/SKILL.md +3 -3
- package/src/adapters/access.ts +118 -0
- package/src/adapters/backend.ts +19 -4
- package/src/adapters/claude-code/index.ts +95 -1
- package/src/adapters/codex/index.ts +78 -23
- package/src/adapters/opencode/index.ts +17 -0
- package/src/domain/errors.ts +2 -0
- package/src/domain/failover.ts +119 -0
- package/src/domain/lane.ts +23 -0
- package/src/domain/notice.ts +95 -0
- package/src/domain/profile-rules.ts +43 -9
- package/src/domain/profile.ts +12 -6
- package/src/domain/record.ts +2 -0
- package/src/domain/role-prompts.ts +33 -1
- package/src/domain/state.ts +18 -2
- package/src/entry/deps.ts +2 -1
- package/src/entry/doctor-command.ts +7 -1
- package/src/entry/glyphs.ts +2 -1
- package/src/entry/init-command.ts +41 -1
- package/src/entry/mcp/command.ts +5 -1
- package/src/entry/mcp/dispatch-tools.ts +10 -40
- package/src/entry/mcp/handshake.ts +19 -9
- package/src/entry/mcp/lane-tools.ts +7 -6
- package/src/entry/mcp/protocol-tools.ts +57 -0
- package/src/entry/mcp/run-tools.ts +4 -3
- package/src/entry/mcp/server.ts +17 -1
- package/src/entry/mcp/setup-tools.ts +1 -1
- package/src/entry/profile-command.ts +3 -5
- package/src/entry/runs-command.ts +64 -21
- package/src/entry/tui/commands.ts +1 -1
- package/src/entry/tui/effects.ts +98 -56
- package/src/entry/tui/fixtures.ts +352 -85
- package/src/entry/tui/profile-edits.ts +2 -2
- package/src/entry/tui/profile-tree.ts +2 -1
- package/src/entry/tui/providers/data.tsx +35 -8
- package/src/entry/tui/run.tsx +11 -1
- package/src/entry/tui/state.ts +26 -6
- package/src/entry/tui/stories.ts +36 -4
- package/src/entry/tui/text.ts +12 -0
- package/src/entry/tui/theme.ts +1 -0
- package/src/entry/tui/views/runs.tsx +409 -117
- package/src/entry/tui/views/status.tsx +5 -2
- package/src/entry/tui/widgets/list.tsx +4 -3
- package/src/infra/claude-session.ts +98 -0
- package/src/infra/dispatch-dir.ts +14 -6
- package/src/infra/env.ts +26 -2
- package/src/infra/peer-inbox.ts +92 -0
- package/src/infra/supervisor.ts +33 -3
- package/src/services/admission.ts +38 -12
- package/src/services/dispatch-service.ts +338 -197
- package/src/services/dispatches.ts +30 -1
- package/src/services/doctor-access.ts +231 -0
- package/src/services/doctor-backends.ts +3 -18
- package/src/services/doctor-checks.ts +46 -1
- package/src/services/doctor-push.ts +109 -0
- package/src/services/doctor.ts +59 -79
- package/src/services/finalize.ts +43 -25
- package/src/services/gate-service.ts +288 -0
- package/src/services/global-install.ts +99 -0
- package/src/services/lane-service.ts +150 -8
- package/src/services/milestones.ts +208 -0
- package/src/services/notifier.ts +296 -0
- package/src/services/peek.ts +103 -0
- package/src/services/ports.ts +7 -4
- package/src/services/preflight.ts +17 -3
- package/src/services/protocol.ts +212 -0
- package/src/services/questions.ts +102 -0
- package/src/services/reconcile.ts +40 -11
- package/src/services/reentry.ts +22 -0
- package/src/services/run-service.ts +42 -9
- package/src/services/run-store.ts +18 -0
- package/src/services/runs-page.ts +254 -0
- package/src/services/session-view.ts +152 -0
- package/src/services/sessions.ts +92 -0
- package/src/services/state.ts +10 -3
- package/src/services/summary.ts +14 -0
package/src/entry/tui/state.ts
CHANGED
|
@@ -120,8 +120,12 @@ export interface AppState {
|
|
|
120
120
|
drafts: Record<string, Draft>;
|
|
121
121
|
/** the open dialogs; only the top one is drawn and takes keys */
|
|
122
122
|
dialogs: Dialog[];
|
|
123
|
-
/** the
|
|
124
|
-
|
|
123
|
+
/** spec §4: the session the Runs tab has open (`key` null: "earlier runs"); null shows the sessions */
|
|
124
|
+
session: { key: string | null } | null;
|
|
125
|
+
/** the role the open session shows, by its dispatch */
|
|
126
|
+
role: { run: string; dispatchId: string } | null;
|
|
127
|
+
/** spec 1.1 §10: the milestone the open session shows, its digest */
|
|
128
|
+
milestone: { run: string; name: string } | null;
|
|
125
129
|
paused: boolean;
|
|
126
130
|
armed: Armed | null;
|
|
127
131
|
}
|
|
@@ -145,7 +149,14 @@ export type Action =
|
|
|
145
149
|
| { type: "saving"; name: string; on: boolean }
|
|
146
150
|
| { type: "input"; value: string }
|
|
147
151
|
| { type: "invalid"; error: string | null }
|
|
148
|
-
|
|
152
|
+
/** opens a session of the Runs tab (`key` null: "earlier runs") */
|
|
153
|
+
| { type: "session"; key: string | null }
|
|
154
|
+
/** opens one role of the open session */
|
|
155
|
+
| { type: "role"; run: string; dispatchId: string }
|
|
156
|
+
/** opens one milestone of the open session, on its digest */
|
|
157
|
+
| { type: "milestone"; run: string; name: string }
|
|
158
|
+
/** the Runs tab goes back one level: a milestone or a role to its session, a session to the list */
|
|
159
|
+
| { type: "up" }
|
|
149
160
|
| { type: "pause" }
|
|
150
161
|
| { type: "arm"; what: Armed["what"]; target: string; at: number }
|
|
151
162
|
| { type: "disarm" };
|
|
@@ -155,7 +166,9 @@ export const initialState = (tab: Tab = "status"): AppState => ({
|
|
|
155
166
|
profile: null,
|
|
156
167
|
drafts: {},
|
|
157
168
|
dialogs: [],
|
|
158
|
-
|
|
169
|
+
session: null,
|
|
170
|
+
role: null,
|
|
171
|
+
milestone: null,
|
|
159
172
|
paused: false,
|
|
160
173
|
armed: null,
|
|
161
174
|
});
|
|
@@ -324,8 +337,15 @@ export function reduce(s: AppState, a: Action): AppState {
|
|
|
324
337
|
else if (top.kind === "save" && a.type === "invalid") next = { ...top, error: a.error };
|
|
325
338
|
return next === top ? s : { ...s, dialogs: [...s.dialogs.slice(0, -1), next] };
|
|
326
339
|
}
|
|
327
|
-
case "
|
|
328
|
-
return { ...s,
|
|
340
|
+
case "session":
|
|
341
|
+
return { ...s, session: { key: a.key }, role: null, milestone: null, armed: null };
|
|
342
|
+
case "role":
|
|
343
|
+
return { ...s, role: { run: a.run, dispatchId: a.dispatchId }, milestone: null, armed: null };
|
|
344
|
+
case "milestone":
|
|
345
|
+
return { ...s, milestone: { run: a.run, name: a.name }, role: null, armed: null };
|
|
346
|
+
case "up":
|
|
347
|
+
if (s.milestone) return { ...s, milestone: null, armed: null };
|
|
348
|
+
return s.role ? { ...s, role: null, armed: null } : { ...s, session: null, armed: null };
|
|
329
349
|
case "pause":
|
|
330
350
|
return { ...s, paused: !s.paused };
|
|
331
351
|
case "arm":
|
package/src/entry/tui/stories.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { ProfilePatch } from "../../domain/profile.ts";
|
|
2
2
|
import type { CommandId } from "./commands.ts";
|
|
3
|
+
import type { FixtureOptions } from "./fixtures.ts";
|
|
3
4
|
import type { AppApi } from "./providers/app.tsx";
|
|
4
5
|
import type { AppKeymap } from "./providers/keymap.tsx";
|
|
5
6
|
import type { Action } from "./state.ts";
|
|
@@ -11,6 +12,8 @@ export interface Story {
|
|
|
11
12
|
name: string;
|
|
12
13
|
title: string;
|
|
13
14
|
steps: Step[];
|
|
15
|
+
/** the fixtures the frame snapshots render it on (the storybook shows it on its one set) */
|
|
16
|
+
fixtures?: FixtureOptions;
|
|
14
17
|
}
|
|
15
18
|
|
|
16
19
|
const SHOWN: Step[] = [{ show: "default" }];
|
|
@@ -45,11 +48,40 @@ export const STORIES: Story[] = [
|
|
|
45
48
|
steps: [{ command: "tab.status" }, { command: "app.palette" }],
|
|
46
49
|
},
|
|
47
50
|
{ name: "help", title: "Keyboard shortcuts", steps: [{ command: "tab.status" }, { command: "app.help" }] },
|
|
48
|
-
{ name: "runs", title: "Runs: the list", steps: [{ command: "tab.runs" }] },
|
|
49
51
|
{
|
|
50
|
-
name: "
|
|
51
|
-
title: "Runs:
|
|
52
|
-
steps: [{ command: "tab.runs" }
|
|
52
|
+
name: "runs-empty",
|
|
53
|
+
title: "Runs: no runs yet",
|
|
54
|
+
steps: [{ command: "tab.runs" }],
|
|
55
|
+
fixtures: { runs: [], sessions: [] },
|
|
56
|
+
},
|
|
57
|
+
{ name: "runs", title: "Runs: the sessions", steps: [{ command: "tab.runs" }] },
|
|
58
|
+
{
|
|
59
|
+
name: "session",
|
|
60
|
+
title: "Runs: one session with two runs, one continued elsewhere",
|
|
61
|
+
steps: [{ command: "tab.runs" }, { action: { type: "session", key: "s-auth" } }],
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
name: "session-live",
|
|
65
|
+
title: "Runs: a live session with three live roles",
|
|
66
|
+
steps: [{ command: "tab.runs" }, { action: { type: "session", key: "s-jobs" } }],
|
|
67
|
+
},
|
|
68
|
+
{
|
|
69
|
+
name: "role",
|
|
70
|
+
title: "Runs: a role opened",
|
|
71
|
+
steps: [
|
|
72
|
+
{ command: "tab.runs" },
|
|
73
|
+
{ action: { type: "session", key: "s-jobs" } },
|
|
74
|
+
{ action: { type: "role", run: "20260926-114800-jobs-screen", dispatchId: "d1" } },
|
|
75
|
+
],
|
|
76
|
+
},
|
|
77
|
+
{
|
|
78
|
+
name: "milestone",
|
|
79
|
+
title: "Runs: a milestone opened on its digest",
|
|
80
|
+
steps: [
|
|
81
|
+
{ command: "tab.runs" },
|
|
82
|
+
{ action: { type: "session", key: "s-jobs" } },
|
|
83
|
+
{ action: { type: "milestone", run: "20260926-114800-jobs-screen", name: "M0" } },
|
|
84
|
+
],
|
|
53
85
|
},
|
|
54
86
|
];
|
|
55
87
|
|
package/src/entry/tui/text.ts
CHANGED
|
@@ -57,6 +57,18 @@ export function wrap(s: string, max: number): string[] {
|
|
|
57
57
|
return lines;
|
|
58
58
|
}
|
|
59
59
|
|
|
60
|
+
/**
|
|
61
|
+
* `wrap` with a hanging indent: a line's leading spaces and list marker (`- `, `* `, `1. `) are kept on its first
|
|
62
|
+
* line, and its continuation lines are indented to match, so a wrapped bullet does not read as a new line.
|
|
63
|
+
*/
|
|
64
|
+
export function wrapHanging(s: string, max: number): string[] {
|
|
65
|
+
const lead = /^\s*(?:[-*+] |\d+[.)] )?/.exec(s)?.[0] ?? "";
|
|
66
|
+
const hang = width(lead);
|
|
67
|
+
if (!lead || max - hang < 1) return wrap(s, max);
|
|
68
|
+
const pad = " ".repeat(hang);
|
|
69
|
+
return wrap(s.slice(lead.length), max - hang).map((l, i) => (i === 0 ? lead : pad) + l);
|
|
70
|
+
}
|
|
71
|
+
|
|
60
72
|
/** `04:12`, or `1:02:03` past an hour. */
|
|
61
73
|
export function clock(secs: number): string {
|
|
62
74
|
const s = Math.max(0, Math.floor(secs));
|