catherd-cli 0.1.0
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/LICENSE +21 -0
- package/README.md +83 -0
- package/catalog/catalog.json +112 -0
- package/catalog/models-dev.json +56453 -0
- package/package.json +52 -0
- package/plugin/.claude-plugin/plugin.json +13 -0
- package/plugin/.mcp.json +8 -0
- package/plugin/commands/catherd-setup.md +8 -0
- package/plugin/commands/catherd.md +10 -0
- package/plugin/skills/catherd/SKILL.md +301 -0
- package/plugin/skills/catherd-setup/SKILL.md +65 -0
- package/src/cli.ts +28 -0
- package/src/core/codex-events.ts +49 -0
- package/src/core/codex.ts +166 -0
- package/src/core/harness.ts +89 -0
- package/src/core/lanes.ts +67 -0
- package/src/core/lock.ts +111 -0
- package/src/core/opencode.ts +207 -0
- package/src/core/reconcile.ts +26 -0
- package/src/core/reply.ts +54 -0
- package/src/core/runstore.ts +163 -0
- package/src/core/spawn.ts +89 -0
- package/src/core/status.ts +193 -0
- package/src/files.ts +19 -0
- package/src/mcp/commands.ts +7 -0
- package/src/mcp/dispatch.ts +154 -0
- package/src/mcp/lane-tools.ts +200 -0
- package/src/mcp/out.ts +6 -0
- package/src/mcp/preflight.ts +69 -0
- package/src/mcp/run-tools.ts +121 -0
- package/src/mcp/runs.ts +118 -0
- package/src/mcp/server.ts +26 -0
- package/src/mcp/setup-tools.ts +175 -0
- package/src/paths.ts +20 -0
- package/src/profile/agents.ts +118 -0
- package/src/profile/profile.ts +228 -0
- package/src/profile/role-prompts.ts +129 -0
- package/src/routing/catalog.ts +227 -0
- package/src/routing/commands.ts +22 -0
- package/src/routing/jev.ts +181 -0
- package/src/routing/route.ts +155 -0
- package/src/routing/select.ts +90 -0
- package/src/tui/backends.ts +57 -0
- package/src/tui/commands.ts +51 -0
- package/src/tui/editor.tsx +221 -0
- package/src/tui/init.tsx +221 -0
- package/src/tui/matrix-model.ts +211 -0
- package/src/tui/matrix.tsx +350 -0
- package/src/tui/profiles.ts +38 -0
- package/src/tui/theme.ts +140 -0
- package/src/tui/ui.tsx +104 -0
- package/src/tui/watch-model.ts +83 -0
- package/src/tui/watch.tsx +258 -0
- package/src/types.ts +184 -0
- package/src/version.ts +7 -0
package/src/types.ts
ADDED
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
export const ROLES = [
|
|
2
|
+
"architect",
|
|
3
|
+
"verifier",
|
|
4
|
+
"worker",
|
|
5
|
+
"reviewer",
|
|
6
|
+
"ui-reviewer",
|
|
7
|
+
"artist",
|
|
8
|
+
"writer",
|
|
9
|
+
"researcher",
|
|
10
|
+
] as const;
|
|
11
|
+
export type Role = (typeof ROLES)[number];
|
|
12
|
+
|
|
13
|
+
export type Backend = "codex" | "opencode" | "claude";
|
|
14
|
+
|
|
15
|
+
/** "<model-id>#<effort>". Model ids may contain "/" (opencode providers); split at the LAST "#". */
|
|
16
|
+
export type RungId = string;
|
|
17
|
+
|
|
18
|
+
export function splitRung(rung: RungId): { model: string; effort: string } {
|
|
19
|
+
const i = rung.lastIndexOf("#");
|
|
20
|
+
if (i < 1 || i === rung.length - 1) throw new Error(`catherd: bad rung "${rung}", expected model#effort`);
|
|
21
|
+
return { model: rung.slice(0, i), effort: rung.slice(i + 1) };
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export const rungOf = (model: string, effort: string): RungId => `${model}#${effort}`;
|
|
25
|
+
|
|
26
|
+
export type Sandbox = "read-only" | "workspace-write" | "danger-full-access";
|
|
27
|
+
|
|
28
|
+
/** Spec §4: the sandbox a Codex or opencode run of each role gets. */
|
|
29
|
+
export const ROLE_SANDBOX: Record<Role, Sandbox> = {
|
|
30
|
+
architect: "read-only",
|
|
31
|
+
verifier: "danger-full-access",
|
|
32
|
+
worker: "workspace-write",
|
|
33
|
+
reviewer: "read-only",
|
|
34
|
+
"ui-reviewer": "danger-full-access",
|
|
35
|
+
artist: "workspace-write",
|
|
36
|
+
writer: "workspace-write",
|
|
37
|
+
researcher: "read-only",
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
export interface Capabilities {
|
|
41
|
+
toolCall: boolean;
|
|
42
|
+
imageIn: boolean;
|
|
43
|
+
imageOut: boolean;
|
|
44
|
+
reasoning: boolean;
|
|
45
|
+
context: number;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/** Spec §4: what a model must have to be offered for a role. */
|
|
49
|
+
export const ROLE_NEEDS: Record<Role, Partial<Capabilities>> = {
|
|
50
|
+
architect: { toolCall: true },
|
|
51
|
+
verifier: { toolCall: true },
|
|
52
|
+
worker: { toolCall: true },
|
|
53
|
+
reviewer: { toolCall: true },
|
|
54
|
+
"ui-reviewer": { toolCall: true, imageIn: true },
|
|
55
|
+
artist: { imageOut: true },
|
|
56
|
+
writer: { toolCall: true },
|
|
57
|
+
researcher: { toolCall: true },
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
export const KINDS = ["repo_code", "terminal", "ui", "prose", "research"] as const;
|
|
61
|
+
export type Kind = (typeof KINDS)[number];
|
|
62
|
+
|
|
63
|
+
export const DIFFICULTIES = ["copy", "build", "logic", "hard"] as const;
|
|
64
|
+
export type Difficulty = (typeof DIFFICULTIES)[number];
|
|
65
|
+
|
|
66
|
+
export type ScoreDim = "repo_code" | "terminal" | "honesty";
|
|
67
|
+
|
|
68
|
+
export interface Scores {
|
|
69
|
+
repo_code?: number;
|
|
70
|
+
terminal?: number;
|
|
71
|
+
honesty?: number;
|
|
72
|
+
secs_per_task?: number;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export interface CatalogModel {
|
|
76
|
+
id: string;
|
|
77
|
+
backend: Backend;
|
|
78
|
+
efforts: string[];
|
|
79
|
+
capabilities: Capabilities;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export interface CatalogEntry {
|
|
83
|
+
rung: RungId;
|
|
84
|
+
scores: Scores;
|
|
85
|
+
costRank: number;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export type Bars = Record<Kind, Record<Difficulty, Partial<Record<ScoreDim, number>>>>;
|
|
89
|
+
|
|
90
|
+
export interface Catalog {
|
|
91
|
+
version: string;
|
|
92
|
+
models: CatalogModel[];
|
|
93
|
+
entries: CatalogEntry[];
|
|
94
|
+
bars: Bars;
|
|
95
|
+
/** unscored rung -> scored rung whose scores it borrows */
|
|
96
|
+
treatLike: Record<RungId, RungId>;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export interface RoleConfig {
|
|
100
|
+
enabled: boolean;
|
|
101
|
+
/** model id -> enabled efforts */
|
|
102
|
+
models: Record<string, string[]>;
|
|
103
|
+
defaultRung?: RungId;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export type NotifyMoment = "milestone" | "finish" | "blocked";
|
|
107
|
+
|
|
108
|
+
/** Spec §8.2: false (the default) runs the vendor harness exactly as the user configured it. */
|
|
109
|
+
export interface HarnessConfig {
|
|
110
|
+
isolated: boolean;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
export interface Profile {
|
|
114
|
+
name: string;
|
|
115
|
+
objective: "cost" | "speed";
|
|
116
|
+
roles: Record<Role, RoleConfig>;
|
|
117
|
+
harness: { codex: HarnessConfig; opencode: HarnessConfig };
|
|
118
|
+
lock: { heavy: number | "cpus/2" };
|
|
119
|
+
notify: NotifyMoment[];
|
|
120
|
+
/** spec §11b: a rung's stand-in on another backend, used on a `limit` result */
|
|
121
|
+
failover?: Record<RungId, RungId>;
|
|
122
|
+
/** spec §11b: the run budget; `status` shows spend against it, `route` and `dispatch` react at 80%/100% */
|
|
123
|
+
budget?: { minutes?: number; tokens?: number; usd?: number };
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
export interface RouteDecision {
|
|
127
|
+
kind: Kind | null;
|
|
128
|
+
difficulty: Difficulty | null;
|
|
129
|
+
confidence: { kind: number | null; difficulty: number | null };
|
|
130
|
+
source: "jev" | "default";
|
|
131
|
+
rung: RungId;
|
|
132
|
+
ladder: RungId[];
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
export type ReplyStatus = "complete" | "partial" | "blocked" | "refused";
|
|
136
|
+
export type RunStatus = "ok" | "failed" | "limit" | "cli-too-old" | "timeout" | "interrupted";
|
|
137
|
+
|
|
138
|
+
export interface Tokens {
|
|
139
|
+
input: number;
|
|
140
|
+
cached: number;
|
|
141
|
+
output: number;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/** One Codex or opencode role run. Appended to <run>/runs.jsonl; the only record of it. */
|
|
145
|
+
export interface RunRecord {
|
|
146
|
+
name: string;
|
|
147
|
+
role: Role;
|
|
148
|
+
backend: "codex" | "opencode";
|
|
149
|
+
rung: RungId;
|
|
150
|
+
thread: string | null;
|
|
151
|
+
status: RunStatus;
|
|
152
|
+
startedAt: string;
|
|
153
|
+
secs: number;
|
|
154
|
+
pid: number | null;
|
|
155
|
+
tokens: Tokens;
|
|
156
|
+
costUsd: number | null;
|
|
157
|
+
changedOwned: string[];
|
|
158
|
+
replyStatus: ReplyStatus | null;
|
|
159
|
+
replyWhy: string | null;
|
|
160
|
+
threadHeavy: boolean;
|
|
161
|
+
/** the harness ran isolated (spec §8.2); lets reports measure what the user's customizations cost */
|
|
162
|
+
isolated: boolean;
|
|
163
|
+
images: string[];
|
|
164
|
+
error: string | null;
|
|
165
|
+
replyPath: string;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
export interface RunMeta {
|
|
169
|
+
id: string;
|
|
170
|
+
repo: string;
|
|
171
|
+
title: string;
|
|
172
|
+
aLines: string[];
|
|
173
|
+
createdAt: string;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
export interface StateSnapshot {
|
|
177
|
+
head: string;
|
|
178
|
+
dirty: { path: string; owner: string | null }[];
|
|
179
|
+
running: { name: string; rung: RungId; thread: string | null; brief: string; since: string }[];
|
|
180
|
+
lastCheck: string | null;
|
|
181
|
+
next: string;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
export const THREAD_HEAVY_INPUT = 8_000_000;
|