@deksden-com/dd-flow-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/README.md +274 -0
- package/dist/cli/help.js +308 -0
- package/dist/cli/run-cli.js +945 -0
- package/dist/cli.js +4 -0
- package/dist/domain/contracts.js +57 -0
- package/dist/domain/entity-ids.js +47 -0
- package/dist/domain/flow-contract.js +233 -0
- package/dist/domain/validation.js +91 -0
- package/dist/protocol/local-files.js +141 -0
- package/dist/runtime/context.js +11 -0
- package/dist/schemas/code-stage-report.schema.json +181 -0
- package/dist/schemas/flow-run-index.schema.json +129 -0
- package/dist/schemas/mb-upgrade-review-data.schema.json +813 -0
- package/dist/schemas/memorybank-permissions-preflight.schema.json +154 -0
- package/dist/schemas/merge-stage-report.schema.json +135 -0
- package/dist/services/audit.js +19 -0
- package/dist/services/cleanup.js +310 -0
- package/dist/services/config.js +143 -0
- package/dist/services/dashboard.js +436 -0
- package/dist/services/hooks.js +929 -0
- package/dist/services/lanes.js +327 -0
- package/dist/services/memory-permissions.js +344 -0
- package/dist/services/merge-queue.js +333 -0
- package/dist/services/plans.js +149 -0
- package/dist/services/projects.js +286 -0
- package/dist/services/protocols.js +606 -0
- package/dist/services/runs.js +359 -0
- package/dist/services/schema-validation.js +185 -0
- package/dist/services/sessions.js +365 -0
- package/dist/services/worktrees.js +204 -0
- package/dist/shared/errors.js +14 -0
- package/dist/shared/json.js +17 -0
- package/dist/storage/database.js +325 -0
- package/dist/storage/paths.js +56 -0
- package/package.json +44 -0
|
@@ -0,0 +1,945 @@
|
|
|
1
|
+
import { createContext } from "../runtime/context.js";
|
|
2
|
+
import { helpForArgs } from "./help.js";
|
|
3
|
+
import { AppError, isAppError } from "../shared/errors.js";
|
|
4
|
+
import { writeJson } from "../shared/json.js";
|
|
5
|
+
import { archiveProject, getProjectStatus, migrateProjectIds, registerProject, resolveProject } from "../services/projects.js";
|
|
6
|
+
import { cancelProtocol, getProtocolStatus, readyForMerge, registerProtocol, requireProtocol, transitionProtocol } from "../services/protocols.js";
|
|
7
|
+
import { blockPlanItem, completePlanItem, getPlanStatus, setProtocolPlan, skipPlanItem, startPlanItem } from "../services/plans.js";
|
|
8
|
+
import { cancelMergeQueueJob, claimNextMergeJob, completeMergeJob, failMergeJob, getMergeQueueStatus, noteMergeJob, waitNextMergeJob } from "../services/merge-queue.js";
|
|
9
|
+
import { cleanupApply, cleanupScan } from "../services/cleanup.js";
|
|
10
|
+
import { getCodexHomeStatus, getCodexHooksStatus, handleCodexHook, initCodexHome, installCodexHooks, planCodexHome, printCodexHomeEnv, printCodexHooks, removeCodexHome, removeCodexHooks } from "../services/hooks.js";
|
|
11
|
+
import { bootstrapWorktree, closeWorktree, createWorktreeRecord, getWorktreeStatus, planWorktree } from "../services/worktrees.js";
|
|
12
|
+
import { acquireLaneLock, checkLaneWorkspace, getLaneStatus, heartbeatLaneLock, releaseLaneLock, setLaneWorkspace, waitForLaneLock } from "../services/lanes.js";
|
|
13
|
+
import { getProjectConfigStatus, setProjectConfigValue } from "../services/config.js";
|
|
14
|
+
import { autoRefreshDashboards, getCmuxStatus, openDashboard, refreshDashboard, refreshGlobalDashboard, renderDashboard, renderGlobalDashboard } from "../services/dashboard.js";
|
|
15
|
+
import { getFlowSessionStatus, registerFlowSession, stopFlowSession, stopMergeWorker } from "../services/sessions.js";
|
|
16
|
+
import { attachFlowRunStage, completeFlowRun, completeFlowRunStage, getFlowRunStatus, listFlowRuns, startFlowRun } from "../services/runs.js";
|
|
17
|
+
import { validateSchema } from "../services/schema-validation.js";
|
|
18
|
+
import { preflightMemoryPermissions } from "../services/memory-permissions.js";
|
|
19
|
+
import { requireProjectByRoot } from "../services/projects.js";
|
|
20
|
+
import { resolveProjectRoot } from "../storage/paths.js";
|
|
21
|
+
const defaultIo = {
|
|
22
|
+
stdout: process.stdout,
|
|
23
|
+
stderr: process.stderr,
|
|
24
|
+
stdin: process.stdin
|
|
25
|
+
};
|
|
26
|
+
const flagValue = "__dd_flow_flag__";
|
|
27
|
+
export async function runCli(args, io = defaultIo, env = process.env) {
|
|
28
|
+
const output = parseOutputOptions(args);
|
|
29
|
+
const progress = progressForCommand(output.args);
|
|
30
|
+
try {
|
|
31
|
+
if (args.includes("--progress-jsonl")) {
|
|
32
|
+
throw new AppError("usage", "--progress-jsonl is not implemented yet; use --json for final structured progress", 2);
|
|
33
|
+
}
|
|
34
|
+
const help = helpForArgs(output.args);
|
|
35
|
+
if (help) {
|
|
36
|
+
io.stdout.write(`${help}\n`);
|
|
37
|
+
return 0;
|
|
38
|
+
}
|
|
39
|
+
const context = createContext(env);
|
|
40
|
+
emitHumanProgress(io, output, progress, "start");
|
|
41
|
+
const result = await dispatch(output.args, context, io);
|
|
42
|
+
refreshDashboardsAfterMutation(context, output.args, result);
|
|
43
|
+
emitHumanProgress(io, output, progress, "done");
|
|
44
|
+
const payload = attachProgress(result, progress, output);
|
|
45
|
+
if (output.json)
|
|
46
|
+
writeJson(io.stdout, payload);
|
|
47
|
+
else
|
|
48
|
+
io.stdout.write(renderHumanResult(output.args, payload));
|
|
49
|
+
return completedCommandExitCode(payload);
|
|
50
|
+
}
|
|
51
|
+
catch (error) {
|
|
52
|
+
const payload = isAppError(error)
|
|
53
|
+
? { ok: false, error: { code: error.code, message: error.message, details: error.details } }
|
|
54
|
+
: { ok: false, error: { code: "unexpected", message: String(error) } };
|
|
55
|
+
emitHumanProgress(io, output, progress, "failed");
|
|
56
|
+
const errorPayload = attachProgress(payload, progress, output);
|
|
57
|
+
if (output.json)
|
|
58
|
+
writeJson(io.stderr, errorPayload);
|
|
59
|
+
else
|
|
60
|
+
io.stderr.write(`dd-flow: ${payload.error.message}\n`);
|
|
61
|
+
return isAppError(error) ? error.exitCode : 1;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
function parseOutputOptions(args) {
|
|
65
|
+
const stripped = [];
|
|
66
|
+
let json = false;
|
|
67
|
+
for (const arg of args) {
|
|
68
|
+
if (arg === "--json") {
|
|
69
|
+
json = true;
|
|
70
|
+
continue;
|
|
71
|
+
}
|
|
72
|
+
stripped.push(arg);
|
|
73
|
+
}
|
|
74
|
+
const hookJsonOnly = stripped[0] === "codex" && stripped[1] === "hook" && stripped[2] === "handle";
|
|
75
|
+
return { json: json || hookJsonOnly, args: stripped, hookJsonOnly };
|
|
76
|
+
}
|
|
77
|
+
function progressForCommand(args) {
|
|
78
|
+
const [family, command, subcommand] = args;
|
|
79
|
+
if (family === "merge-queue" && command === "wait-next") {
|
|
80
|
+
return [
|
|
81
|
+
{ phase: "start", message: "Starting merge queue wait", at: "" },
|
|
82
|
+
{ phase: "running", message: "Waiting for a ready merge job", at: "" },
|
|
83
|
+
{ phase: "done", message: "Merge queue wait finished", at: "" }
|
|
84
|
+
];
|
|
85
|
+
}
|
|
86
|
+
if (family === "lane" && command === "lock" && subcommand === "wait") {
|
|
87
|
+
return [
|
|
88
|
+
{ phase: "start", message: "Starting lane lock wait", at: "" },
|
|
89
|
+
{ phase: "running", message: "Waiting for lane lock", at: "" },
|
|
90
|
+
{ phase: "done", message: "Lane lock wait finished", at: "" }
|
|
91
|
+
];
|
|
92
|
+
}
|
|
93
|
+
if ((family === "cleanup" && command === "apply") ||
|
|
94
|
+
(family === "protocol" && command === "cancel") ||
|
|
95
|
+
(family === "project" && command === "archive") ||
|
|
96
|
+
family === "run") {
|
|
97
|
+
return [
|
|
98
|
+
{ phase: "start", message: `Starting ${family} ${command}`, at: "" },
|
|
99
|
+
{ phase: "done", message: `${family} ${command} finished`, at: "" }
|
|
100
|
+
];
|
|
101
|
+
}
|
|
102
|
+
return [];
|
|
103
|
+
}
|
|
104
|
+
function emitHumanProgress(io, output, progress, phase) {
|
|
105
|
+
if (output.json || output.hookJsonOnly)
|
|
106
|
+
return;
|
|
107
|
+
for (const event of progress.filter((item) => item.phase === phase)) {
|
|
108
|
+
event.at = new Date().toISOString();
|
|
109
|
+
io.stderr.write(`[${event.phase}] ${event.message}\n`);
|
|
110
|
+
}
|
|
111
|
+
if (phase === "start") {
|
|
112
|
+
for (const event of progress.filter((item) => item.phase === "running")) {
|
|
113
|
+
event.at = new Date().toISOString();
|
|
114
|
+
io.stderr.write(`[${event.phase}] ${event.message}\n`);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
function attachProgress(result, progress, output) {
|
|
119
|
+
if (progress.length === 0 || output.hookJsonOnly)
|
|
120
|
+
return result;
|
|
121
|
+
for (const event of progress) {
|
|
122
|
+
if (!event.at)
|
|
123
|
+
event.at = new Date().toISOString();
|
|
124
|
+
}
|
|
125
|
+
if (result && typeof result === "object" && !Array.isArray(result)) {
|
|
126
|
+
return { ...result, progress };
|
|
127
|
+
}
|
|
128
|
+
return { ok: true, result, progress };
|
|
129
|
+
}
|
|
130
|
+
function renderHumanResult(args, result) {
|
|
131
|
+
const [family, command] = args;
|
|
132
|
+
const record = result && typeof result === "object" && !Array.isArray(result) ? result : {};
|
|
133
|
+
const ok = record.ok === false ? "failed" : "ok";
|
|
134
|
+
if (family === "schema" && command === "validate") {
|
|
135
|
+
const schema = record.schema && typeof record.schema === "object" ? record.schema : {};
|
|
136
|
+
const errors = Array.isArray(record.errors) ? record.errors : [];
|
|
137
|
+
const lines = [`dd-flow schema validate: ${errors.length === 0 ? "valid" : "invalid"}`];
|
|
138
|
+
if (schema.name)
|
|
139
|
+
lines.push(`schema: ${String(schema.name)} (${String(schema.id ?? "unknown")})`);
|
|
140
|
+
if (schema.path)
|
|
141
|
+
lines.push(`schema_path: ${String(schema.path)}`);
|
|
142
|
+
if (record.file)
|
|
143
|
+
lines.push(`file: ${String(record.file)}`);
|
|
144
|
+
lines.push(`errors: ${errors.length}`);
|
|
145
|
+
return `${lines.join("\n")}\n`;
|
|
146
|
+
}
|
|
147
|
+
const lines = [`dd-flow ${family ?? ""}${command ? ` ${command}` : ""}: ${ok}`.trim()];
|
|
148
|
+
const project = record.project && typeof record.project === "object" ? record.project : null;
|
|
149
|
+
const protocol = record.protocol && typeof record.protocol === "object" ? record.protocol : null;
|
|
150
|
+
if (project?.id)
|
|
151
|
+
lines.push(`project: ${String(project.id)}`);
|
|
152
|
+
if (protocol?.id)
|
|
153
|
+
lines.push(`protocol: ${String(protocol.id)}`);
|
|
154
|
+
if (record.job && typeof record.job === "object") {
|
|
155
|
+
const job = record.job;
|
|
156
|
+
lines.push(`job: ${String(job.protocol_id ?? "none")} ${String(job.status ?? "")}`.trim());
|
|
157
|
+
}
|
|
158
|
+
if (record.stopped)
|
|
159
|
+
lines.push(`stopped: ${String(record.reason ?? "true")}`);
|
|
160
|
+
if (record.timed_out)
|
|
161
|
+
lines.push("timed_out: true");
|
|
162
|
+
if (record.dashboard && typeof record.dashboard === "object")
|
|
163
|
+
lines.push(`dashboard: ${String(record.dashboard.path ?? "updated")}`);
|
|
164
|
+
if (record.run && typeof record.run === "object") {
|
|
165
|
+
const run = record.run;
|
|
166
|
+
if (run.id)
|
|
167
|
+
lines.push(`run: ${String(run.id)}`);
|
|
168
|
+
if (run.run_index_path)
|
|
169
|
+
lines.push(`run_index: ${String(run.run_index_path)}`);
|
|
170
|
+
}
|
|
171
|
+
return `${lines.join("\n")}\n`;
|
|
172
|
+
}
|
|
173
|
+
async function dispatch(args, context, io) {
|
|
174
|
+
const [family, command, ...rest] = args;
|
|
175
|
+
const parsed = parseArgs(rest);
|
|
176
|
+
if (family === "project" && command === "register") {
|
|
177
|
+
return registerProject(context, { root: requiredOption(parsed, "root") });
|
|
178
|
+
}
|
|
179
|
+
if (family === "project" && command === "status") {
|
|
180
|
+
return getProjectStatus(context, { root: requiredOption(parsed, "root") });
|
|
181
|
+
}
|
|
182
|
+
if (family === "project" && command === "resolve") {
|
|
183
|
+
return resolveProject(context, { idOrAlias: requiredPosition(parsed, 0, "id-or-alias") });
|
|
184
|
+
}
|
|
185
|
+
if (family === "project" && command === "archive") {
|
|
186
|
+
return archiveProject(context, {
|
|
187
|
+
idOrAlias: parsed.positional[0],
|
|
188
|
+
root: optionalOption(parsed, "root"),
|
|
189
|
+
reason: requiredOption(parsed, "reason")
|
|
190
|
+
});
|
|
191
|
+
}
|
|
192
|
+
if (family === "project" && command === "migrate-ids") {
|
|
193
|
+
return migrateProjectIds(context, {
|
|
194
|
+
root: requiredOption(parsed, "root"),
|
|
195
|
+
apply: hasOption(parsed, "apply")
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
if (family === "project" && command === "config") {
|
|
199
|
+
return dispatchProjectConfig(context, parsed);
|
|
200
|
+
}
|
|
201
|
+
if (family === "protocol" && command === "register") {
|
|
202
|
+
const handshakeId = requiredPosition(parsed, 0, "handshake-id");
|
|
203
|
+
return registerProtocol(context, {
|
|
204
|
+
handshakeId,
|
|
205
|
+
projectRoot: requiredOption(parsed, "project-root"),
|
|
206
|
+
workspacePath: optionalOption(parsed, "workspace-path")
|
|
207
|
+
});
|
|
208
|
+
}
|
|
209
|
+
if (family === "protocol" && command === "status") {
|
|
210
|
+
return getProtocolStatus(context, { protocolId: requiredPosition(parsed, 0, "protocol-id") });
|
|
211
|
+
}
|
|
212
|
+
if (family === "protocol" && command === "ready-for-merge") {
|
|
213
|
+
return readyForMerge(context, { protocolId: requiredPosition(parsed, 0, "protocol-id") });
|
|
214
|
+
}
|
|
215
|
+
if (family === "protocol" && command === "cancel") {
|
|
216
|
+
return cancelProtocol(context, {
|
|
217
|
+
protocolId: requiredPosition(parsed, 0, "protocol-id"),
|
|
218
|
+
reason: requiredOption(parsed, "reason"),
|
|
219
|
+
closeSessions: parseOptionalBoolean(optionalOption(parsed, "close-sessions"), "close-sessions") ?? false,
|
|
220
|
+
cancelQueue: parseOptionalBoolean(optionalOption(parsed, "cancel-queue"), "cancel-queue") ?? false,
|
|
221
|
+
releaseLocks: parseOptionalBoolean(optionalOption(parsed, "release-locks"), "release-locks") ?? false,
|
|
222
|
+
worktree: parseWorktreePolicy(optionalOption(parsed, "worktree")),
|
|
223
|
+
force: hasOption(parsed, "force")
|
|
224
|
+
});
|
|
225
|
+
}
|
|
226
|
+
if (family === "transition") {
|
|
227
|
+
const transitionParsed = parseArgs([command ?? "", ...rest]);
|
|
228
|
+
const reason = optionalOption(transitionParsed, "reason");
|
|
229
|
+
return transitionProtocol(context, {
|
|
230
|
+
protocolId: requiredPosition(transitionParsed, 0, "protocol-id"),
|
|
231
|
+
to: requiredOption(transitionParsed, "to"),
|
|
232
|
+
jsonFile: requiredOption(transitionParsed, "json-file"),
|
|
233
|
+
force: hasOption(transitionParsed, "force"),
|
|
234
|
+
...(reason ? { reason } : {})
|
|
235
|
+
});
|
|
236
|
+
}
|
|
237
|
+
if (family === "plan" && command === "set") {
|
|
238
|
+
return setProtocolPlan(context, {
|
|
239
|
+
protocolId: requiredPosition(parsed, 0, "protocol-id"),
|
|
240
|
+
file: requiredOption(parsed, "file")
|
|
241
|
+
});
|
|
242
|
+
}
|
|
243
|
+
if (family === "plan" && command === "status") {
|
|
244
|
+
return getPlanStatus(context, { protocolId: requiredPosition(parsed, 0, "protocol-id") });
|
|
245
|
+
}
|
|
246
|
+
if (family === "plan" && command === "item") {
|
|
247
|
+
return dispatchPlanItem(context, parsed);
|
|
248
|
+
}
|
|
249
|
+
if (family === "lane") {
|
|
250
|
+
return dispatchLane(context, command, parsed);
|
|
251
|
+
}
|
|
252
|
+
if (family === "merge-queue") {
|
|
253
|
+
return dispatchMergeQueue(context, command, parsed);
|
|
254
|
+
}
|
|
255
|
+
if (family === "cleanup") {
|
|
256
|
+
return dispatchCleanup(context, command, parsed);
|
|
257
|
+
}
|
|
258
|
+
if (family === "session") {
|
|
259
|
+
return dispatchSession(context, command, parsed);
|
|
260
|
+
}
|
|
261
|
+
if (family === "run") {
|
|
262
|
+
return dispatchRun(context, command, parsed);
|
|
263
|
+
}
|
|
264
|
+
if (family === "integration" && command === "cmux") {
|
|
265
|
+
return dispatchCmux(context, parsed);
|
|
266
|
+
}
|
|
267
|
+
if (family === "dashboard") {
|
|
268
|
+
return dispatchDashboard(context, command, parsed);
|
|
269
|
+
}
|
|
270
|
+
if (family === "schema") {
|
|
271
|
+
return dispatchSchema(command, parsed);
|
|
272
|
+
}
|
|
273
|
+
if (family === "memory") {
|
|
274
|
+
return dispatchMemory(command, parsed);
|
|
275
|
+
}
|
|
276
|
+
if (family === "codex" && command === "hooks") {
|
|
277
|
+
return dispatchCodexHooks(context, parsed);
|
|
278
|
+
}
|
|
279
|
+
if (family === "codex" && command === "home") {
|
|
280
|
+
return dispatchCodexHome(context, parsed);
|
|
281
|
+
}
|
|
282
|
+
if (family === "codex" && command === "hook") {
|
|
283
|
+
return dispatchCodexHook(context, parsed, await readStdin(io.stdin));
|
|
284
|
+
}
|
|
285
|
+
if (family === "worktree") {
|
|
286
|
+
return dispatchWorktree(context, command, parsed);
|
|
287
|
+
}
|
|
288
|
+
throw new AppError("usage", `Unknown command: ${args.join(" ") || "<empty>"}`, 2);
|
|
289
|
+
}
|
|
290
|
+
function dispatchMergeQueue(context, command, parsed) {
|
|
291
|
+
if (hasOption(parsed, "session-id")) {
|
|
292
|
+
throw new AppError("usage", "--session-id is no longer accepted on merge-queue commands; use --worker-id", 2);
|
|
293
|
+
}
|
|
294
|
+
if (command === "status") {
|
|
295
|
+
return getMergeQueueStatus(context, { projectRoot: requiredOption(parsed, "project-root") });
|
|
296
|
+
}
|
|
297
|
+
if (command === "next") {
|
|
298
|
+
return claimNextMergeJob(context, {
|
|
299
|
+
projectRoot: requiredOption(parsed, "project-root"),
|
|
300
|
+
workerId: requiredWorkerId(parsed),
|
|
301
|
+
workspacePath: workspacePathOption(parsed)
|
|
302
|
+
});
|
|
303
|
+
}
|
|
304
|
+
if (command === "wait-next") {
|
|
305
|
+
return waitNextMergeJob(context, {
|
|
306
|
+
projectRoot: requiredOption(parsed, "project-root"),
|
|
307
|
+
workerId: requiredWorkerId(parsed),
|
|
308
|
+
timeoutSeconds: optionalNumber(parsed, "timeout"),
|
|
309
|
+
pollIntervalSeconds: optionalNumber(parsed, "poll-interval"),
|
|
310
|
+
workspacePath: workspacePathOption(parsed),
|
|
311
|
+
acquireLock: parseOptionalBoolean(optionalOption(parsed, "acquire-lock"), "acquire-lock") ?? false
|
|
312
|
+
});
|
|
313
|
+
}
|
|
314
|
+
if (command === "complete") {
|
|
315
|
+
return completeMergeJob(context, {
|
|
316
|
+
protocolId: requiredPosition(parsed, 0, "protocol-id"),
|
|
317
|
+
workerId: requiredWorkerId(parsed),
|
|
318
|
+
workspacePath: workspacePathOption(parsed),
|
|
319
|
+
summary: requiredOption(parsed, "summary")
|
|
320
|
+
});
|
|
321
|
+
}
|
|
322
|
+
if (command === "fail") {
|
|
323
|
+
return failMergeJob(context, {
|
|
324
|
+
protocolId: requiredPosition(parsed, 0, "protocol-id"),
|
|
325
|
+
workerId: requiredWorkerId(parsed),
|
|
326
|
+
workspacePath: workspacePathOption(parsed),
|
|
327
|
+
reason: requiredOption(parsed, "reason"),
|
|
328
|
+
requeue: parseBoolean(requiredOption(parsed, "requeue"), "requeue")
|
|
329
|
+
});
|
|
330
|
+
}
|
|
331
|
+
if (command === "note") {
|
|
332
|
+
return noteMergeJob(context, {
|
|
333
|
+
protocolId: requiredPosition(parsed, 0, "protocol-id"),
|
|
334
|
+
workerId: requiredWorkerId(parsed),
|
|
335
|
+
summary: requiredOption(parsed, "summary")
|
|
336
|
+
});
|
|
337
|
+
}
|
|
338
|
+
if (command === "cancel") {
|
|
339
|
+
return cancelMergeQueueJob(context, {
|
|
340
|
+
protocolId: requiredPosition(parsed, 0, "protocol-id"),
|
|
341
|
+
reason: requiredOption(parsed, "reason"),
|
|
342
|
+
workerId: optionalOption(parsed, "worker-id"),
|
|
343
|
+
workspacePath: optionalOption(parsed, "path"),
|
|
344
|
+
force: hasOption(parsed, "force")
|
|
345
|
+
});
|
|
346
|
+
}
|
|
347
|
+
throw new AppError("usage", `Unknown merge-queue command: ${command ?? "<empty>"}`, 2);
|
|
348
|
+
}
|
|
349
|
+
function dispatchMemory(command, parsed) {
|
|
350
|
+
const subcommand = requiredPosition(parsed, 0, "memory subcommand");
|
|
351
|
+
if (command === "permissions" && subcommand === "preflight") {
|
|
352
|
+
return preflightMemoryPermissions({
|
|
353
|
+
root: requiredOption(parsed, "root"),
|
|
354
|
+
memoryBank: requiredOption(parsed, "memory-bank"),
|
|
355
|
+
tasks: optionalOption(parsed, "tasks"),
|
|
356
|
+
flow: requiredOption(parsed, "flow"),
|
|
357
|
+
mode: requiredOption(parsed, "mode")
|
|
358
|
+
});
|
|
359
|
+
}
|
|
360
|
+
throw new AppError("usage", `Unknown memory command: ${["memory", command, subcommand].filter(Boolean).join(" ")}`, 2);
|
|
361
|
+
}
|
|
362
|
+
function completedCommandExitCode(payload) {
|
|
363
|
+
if (!payload || typeof payload !== "object" || Array.isArray(payload))
|
|
364
|
+
return 0;
|
|
365
|
+
const exitCode = payload.exit_code;
|
|
366
|
+
return typeof exitCode === "number" && Number.isInteger(exitCode) ? exitCode : 0;
|
|
367
|
+
}
|
|
368
|
+
function dispatchCleanup(context, command, parsed) {
|
|
369
|
+
if (command === "scan") {
|
|
370
|
+
return cleanupScan(context, { projectRoot: requiredOption(parsed, "project-root") });
|
|
371
|
+
}
|
|
372
|
+
if (command === "apply") {
|
|
373
|
+
return cleanupApply(context, {
|
|
374
|
+
projectRoot: requiredOption(parsed, "project-root"),
|
|
375
|
+
planFile: requiredOption(parsed, "plan-file"),
|
|
376
|
+
reason: requiredOption(parsed, "reason"),
|
|
377
|
+
force: hasOption(parsed, "force")
|
|
378
|
+
});
|
|
379
|
+
}
|
|
380
|
+
throw new AppError("usage", `Unknown cleanup command: ${command ?? "<empty>"}`, 2);
|
|
381
|
+
}
|
|
382
|
+
function dispatchProjectConfig(context, parsed) {
|
|
383
|
+
const action = requiredPosition(parsed, 0, "project config action");
|
|
384
|
+
const project = requireProjectByRoot(context, resolveProjectRoot(requiredOption(parsed, "project-root")));
|
|
385
|
+
if (action === "status") {
|
|
386
|
+
return getProjectConfigStatus(context, project);
|
|
387
|
+
}
|
|
388
|
+
if (action === "set") {
|
|
389
|
+
return setProjectConfigValue(context, project, {
|
|
390
|
+
key: requiredOption(parsed, "key"),
|
|
391
|
+
value: requiredOption(parsed, "value")
|
|
392
|
+
});
|
|
393
|
+
}
|
|
394
|
+
throw new AppError("usage", `Unknown project config action: ${action}`, 2);
|
|
395
|
+
}
|
|
396
|
+
function dispatchSession(context, command, parsed) {
|
|
397
|
+
if (command === "register") {
|
|
398
|
+
return registerFlowSession(context, {
|
|
399
|
+
payloadBase64: optionalOption(parsed, "payload-base64"),
|
|
400
|
+
payloadJson: optionalOption(parsed, "payload-json"),
|
|
401
|
+
payloadFile: optionalOption(parsed, "payload-file"),
|
|
402
|
+
sessionId: optionalOption(parsed, "session-id")
|
|
403
|
+
});
|
|
404
|
+
}
|
|
405
|
+
if (command === "status") {
|
|
406
|
+
return getFlowSessionStatus(context, {
|
|
407
|
+
projectRoot: requiredOption(parsed, "project-root"),
|
|
408
|
+
sessionId: optionalOption(parsed, "session-id"),
|
|
409
|
+
workerId: optionalOption(parsed, "worker-id")
|
|
410
|
+
});
|
|
411
|
+
}
|
|
412
|
+
if (command === "stop") {
|
|
413
|
+
return stopFlowSession(context, {
|
|
414
|
+
projectRoot: requiredOption(parsed, "project-root"),
|
|
415
|
+
sessionId: requiredOption(parsed, "session-id"),
|
|
416
|
+
reason: requiredOption(parsed, "reason")
|
|
417
|
+
});
|
|
418
|
+
}
|
|
419
|
+
if (command === "stop-worker") {
|
|
420
|
+
return stopMergeWorker(context, {
|
|
421
|
+
projectRoot: requiredOption(parsed, "project-root"),
|
|
422
|
+
workerId: requiredOption(parsed, "worker-id"),
|
|
423
|
+
reason: requiredOption(parsed, "reason")
|
|
424
|
+
});
|
|
425
|
+
}
|
|
426
|
+
throw new AppError("usage", `Unknown session command: ${command ?? "<empty>"}`, 2);
|
|
427
|
+
}
|
|
428
|
+
function dispatchRun(context, command, parsed) {
|
|
429
|
+
if (command === "start") {
|
|
430
|
+
return startFlowRun(context, {
|
|
431
|
+
projectRoot: requiredOption(parsed, "project-root"),
|
|
432
|
+
workspaceRoot: optionalOption(parsed, "workspace-root"),
|
|
433
|
+
flowKind: requiredOption(parsed, "flow-kind"),
|
|
434
|
+
subjectType: requiredOption(parsed, "subject-type"),
|
|
435
|
+
subjectId: requiredOption(parsed, "subject-id"),
|
|
436
|
+
slug: requiredOption(parsed, "slug"),
|
|
437
|
+
nextAction: optionalOption(parsed, "next-action")
|
|
438
|
+
});
|
|
439
|
+
}
|
|
440
|
+
if (command === "status") {
|
|
441
|
+
return getFlowRunStatus(context, {
|
|
442
|
+
projectRoot: requiredOption(parsed, "project-root"),
|
|
443
|
+
runId: requiredPosition(parsed, 0, "run-id")
|
|
444
|
+
});
|
|
445
|
+
}
|
|
446
|
+
if (command === "list") {
|
|
447
|
+
return listFlowRuns(context, { projectRoot: requiredOption(parsed, "project-root") });
|
|
448
|
+
}
|
|
449
|
+
if (command === "attach-stage") {
|
|
450
|
+
return attachFlowRunStage(context, {
|
|
451
|
+
projectRoot: requiredOption(parsed, "project-root"),
|
|
452
|
+
runId: requiredPosition(parsed, 0, "run-id"),
|
|
453
|
+
stage: requiredOption(parsed, "stage"),
|
|
454
|
+
dir: requiredOption(parsed, "dir"),
|
|
455
|
+
status: requiredOption(parsed, "status"),
|
|
456
|
+
dataSchemaId: optionalOption(parsed, "data-schema-id")
|
|
457
|
+
});
|
|
458
|
+
}
|
|
459
|
+
if (command === "complete-stage") {
|
|
460
|
+
const stageReport = optionalOption(parsed, "stage-report") ?? optionalOption(parsed, "dashboard");
|
|
461
|
+
return completeFlowRunStage(context, {
|
|
462
|
+
projectRoot: requiredOption(parsed, "project-root"),
|
|
463
|
+
runId: requiredPosition(parsed, 0, "run-id"),
|
|
464
|
+
stage: requiredOption(parsed, "stage"),
|
|
465
|
+
status: requiredOption(parsed, "status"),
|
|
466
|
+
stageReport,
|
|
467
|
+
data: optionalOption(parsed, "data"),
|
|
468
|
+
dataSchemaId: optionalOption(parsed, "data-schema-id"),
|
|
469
|
+
report: optionalOption(parsed, "report"),
|
|
470
|
+
aliases: allOptions(parsed, "alias")
|
|
471
|
+
});
|
|
472
|
+
}
|
|
473
|
+
if (command === "complete") {
|
|
474
|
+
return completeFlowRun(context, {
|
|
475
|
+
projectRoot: requiredOption(parsed, "project-root"),
|
|
476
|
+
runId: requiredPosition(parsed, 0, "run-id"),
|
|
477
|
+
status: requiredOption(parsed, "status"),
|
|
478
|
+
verdict: optionalOption(parsed, "verdict"),
|
|
479
|
+
nextAction: optionalOption(parsed, "next-action")
|
|
480
|
+
});
|
|
481
|
+
}
|
|
482
|
+
throw new AppError("usage", `Unknown run command: ${command ?? "<empty>"}`, 2);
|
|
483
|
+
}
|
|
484
|
+
function dispatchCmux(context, parsed) {
|
|
485
|
+
const action = requiredPosition(parsed, 0, "integration cmux action");
|
|
486
|
+
if (action === "status") {
|
|
487
|
+
return getCmuxStatus(context, { projectRoot: requiredOption(parsed, "project-root") });
|
|
488
|
+
}
|
|
489
|
+
throw new AppError("usage", `Unknown integration cmux action: ${action}`, 2);
|
|
490
|
+
}
|
|
491
|
+
function dispatchDashboard(context, command, parsed) {
|
|
492
|
+
if (command === "render") {
|
|
493
|
+
return renderDashboard(context, { projectRoot: requiredOption(parsed, "project-root"), output: optionalOption(parsed, "output") });
|
|
494
|
+
}
|
|
495
|
+
if (command === "render-global") {
|
|
496
|
+
return renderGlobalDashboard(context, { output: optionalOption(parsed, "output") });
|
|
497
|
+
}
|
|
498
|
+
if (command === "open") {
|
|
499
|
+
return openDashboard(context, { projectRoot: requiredOption(parsed, "project-root"), viewer: optionalOption(parsed, "viewer") });
|
|
500
|
+
}
|
|
501
|
+
if (command === "refresh") {
|
|
502
|
+
const open = optionalOption(parsed, "open");
|
|
503
|
+
if (typeof open !== "undefined" && !["auto", "true", "false"].includes(open)) {
|
|
504
|
+
throw new AppError("validation", "--open must be auto, true, or false", 2);
|
|
505
|
+
}
|
|
506
|
+
return refreshDashboard(context, {
|
|
507
|
+
projectRoot: requiredOption(parsed, "project-root"),
|
|
508
|
+
open: open
|
|
509
|
+
});
|
|
510
|
+
}
|
|
511
|
+
if (command === "refresh-global") {
|
|
512
|
+
return refreshGlobalDashboard(context, { output: optionalOption(parsed, "output") });
|
|
513
|
+
}
|
|
514
|
+
throw new AppError("usage", `Unknown dashboard command: ${command ?? "<empty>"}`, 2);
|
|
515
|
+
}
|
|
516
|
+
function dispatchSchema(command, parsed) {
|
|
517
|
+
if (command === "validate") {
|
|
518
|
+
const schemaDir = optionalOption(parsed, "schema-dir");
|
|
519
|
+
const projectRoot = optionalOption(parsed, "project-root");
|
|
520
|
+
return validateSchema({
|
|
521
|
+
schemaName: requiredOption(parsed, "schema"),
|
|
522
|
+
file: requiredOption(parsed, "file"),
|
|
523
|
+
...(schemaDir ? { schemaDir } : {}),
|
|
524
|
+
...(projectRoot ? { projectRoot } : {})
|
|
525
|
+
});
|
|
526
|
+
}
|
|
527
|
+
throw new AppError("usage", `Unknown schema command: ${command ?? "<empty>"}`, 2);
|
|
528
|
+
}
|
|
529
|
+
async function dispatchLane(context, command, parsed) {
|
|
530
|
+
if (command === "status") {
|
|
531
|
+
return getLaneStatus(context, { projectRoot: requiredOption(parsed, "project-root"), lane: optionalOption(parsed, "lane") });
|
|
532
|
+
}
|
|
533
|
+
if (command === "workspace") {
|
|
534
|
+
const action = requiredPosition(parsed, 0, "lane workspace action");
|
|
535
|
+
if (action === "set") {
|
|
536
|
+
return setLaneWorkspace(context, {
|
|
537
|
+
projectRoot: requiredOption(parsed, "project-root"),
|
|
538
|
+
lane: requiredOption(parsed, "lane"),
|
|
539
|
+
workspacePath: requiredOption(parsed, "path"),
|
|
540
|
+
branch: optionalOption(parsed, "branch")
|
|
541
|
+
});
|
|
542
|
+
}
|
|
543
|
+
if (action === "check") {
|
|
544
|
+
return checkLaneWorkspace(context, {
|
|
545
|
+
projectRoot: requiredOption(parsed, "project-root"),
|
|
546
|
+
lane: requiredOption(parsed, "lane"),
|
|
547
|
+
workspacePath: requiredOption(parsed, "path")
|
|
548
|
+
});
|
|
549
|
+
}
|
|
550
|
+
throw new AppError("usage", `Unknown lane workspace action: ${action}`, 2);
|
|
551
|
+
}
|
|
552
|
+
if (command === "lock") {
|
|
553
|
+
const action = requiredPosition(parsed, 0, "lane lock action");
|
|
554
|
+
if (action === "acquire") {
|
|
555
|
+
return acquireLaneLock(context, {
|
|
556
|
+
projectRoot: requiredOption(parsed, "project-root"),
|
|
557
|
+
lane: requiredOption(parsed, "lane"),
|
|
558
|
+
workerId: requiredOption(parsed, "worker-id"),
|
|
559
|
+
workspacePath: workspacePathOption(parsed),
|
|
560
|
+
ttlSeconds: optionalNumber(parsed, "ttl"),
|
|
561
|
+
reason: requiredOption(parsed, "reason")
|
|
562
|
+
});
|
|
563
|
+
}
|
|
564
|
+
if (action === "heartbeat") {
|
|
565
|
+
return heartbeatLaneLock(context, {
|
|
566
|
+
projectRoot: requiredOption(parsed, "project-root"),
|
|
567
|
+
lane: requiredOption(parsed, "lane"),
|
|
568
|
+
workerId: requiredOption(parsed, "worker-id"),
|
|
569
|
+
workspacePath: workspacePathOption(parsed),
|
|
570
|
+
leaseToken: optionalOption(parsed, "lease-token"),
|
|
571
|
+
ttlSeconds: optionalNumber(parsed, "ttl")
|
|
572
|
+
});
|
|
573
|
+
}
|
|
574
|
+
if (action === "release") {
|
|
575
|
+
return releaseLaneLock(context, {
|
|
576
|
+
projectRoot: requiredOption(parsed, "project-root"),
|
|
577
|
+
lane: requiredOption(parsed, "lane"),
|
|
578
|
+
workerId: requiredOption(parsed, "worker-id"),
|
|
579
|
+
workspacePath: workspacePathOption(parsed),
|
|
580
|
+
leaseToken: optionalOption(parsed, "lease-token"),
|
|
581
|
+
reason: requiredOption(parsed, "reason")
|
|
582
|
+
});
|
|
583
|
+
}
|
|
584
|
+
if (action === "status") {
|
|
585
|
+
return getLaneStatus(context, { projectRoot: requiredOption(parsed, "project-root"), lane: requiredOption(parsed, "lane") });
|
|
586
|
+
}
|
|
587
|
+
if (action === "wait") {
|
|
588
|
+
return waitForLaneLock(context, {
|
|
589
|
+
projectRoot: requiredOption(parsed, "project-root"),
|
|
590
|
+
lane: requiredOption(parsed, "lane"),
|
|
591
|
+
workerId: requiredOption(parsed, "worker-id"),
|
|
592
|
+
workspacePath: workspacePathOption(parsed),
|
|
593
|
+
timeoutSeconds: optionalNumber(parsed, "timeout"),
|
|
594
|
+
pollIntervalSeconds: optionalNumber(parsed, "poll-interval")
|
|
595
|
+
});
|
|
596
|
+
}
|
|
597
|
+
throw new AppError("usage", `Unknown lane lock action: ${action}`, 2);
|
|
598
|
+
}
|
|
599
|
+
throw new AppError("usage", `Unknown lane command: ${command ?? "<empty>"}`, 2);
|
|
600
|
+
}
|
|
601
|
+
function dispatchCodexHooks(context, parsed) {
|
|
602
|
+
const action = requiredPosition(parsed, 0, "codex hooks action");
|
|
603
|
+
if (action === "print") {
|
|
604
|
+
return printCodexHooks(context, {
|
|
605
|
+
projectRoot: requiredOption(parsed, "project-root"),
|
|
606
|
+
target: optionalOption(parsed, "target"),
|
|
607
|
+
profile: optionalOption(parsed, "profile")
|
|
608
|
+
});
|
|
609
|
+
}
|
|
610
|
+
if (action === "status") {
|
|
611
|
+
return getCodexHooksStatus(context, {
|
|
612
|
+
projectRoot: requiredOption(parsed, "project-root"),
|
|
613
|
+
target: optionalOption(parsed, "target"),
|
|
614
|
+
profile: optionalOption(parsed, "profile")
|
|
615
|
+
});
|
|
616
|
+
}
|
|
617
|
+
if (action === "install") {
|
|
618
|
+
return installCodexHooks(context, {
|
|
619
|
+
projectRoot: requiredOption(parsed, "project-root"),
|
|
620
|
+
target: optionalOption(parsed, "target"),
|
|
621
|
+
profile: optionalOption(parsed, "profile"),
|
|
622
|
+
yes: hasOption(parsed, "yes")
|
|
623
|
+
});
|
|
624
|
+
}
|
|
625
|
+
if (action === "remove") {
|
|
626
|
+
return removeCodexHooks(context, {
|
|
627
|
+
projectRoot: requiredOption(parsed, "project-root"),
|
|
628
|
+
target: optionalOption(parsed, "target"),
|
|
629
|
+
profile: optionalOption(parsed, "profile"),
|
|
630
|
+
yes: hasOption(parsed, "yes")
|
|
631
|
+
});
|
|
632
|
+
}
|
|
633
|
+
throw new AppError("usage", `Unknown codex hooks action: ${action}`, 2);
|
|
634
|
+
}
|
|
635
|
+
function dispatchCodexHome(context, parsed) {
|
|
636
|
+
const action = requiredPosition(parsed, 0, "codex home action");
|
|
637
|
+
if (action === "plan") {
|
|
638
|
+
return planCodexHome(context, {
|
|
639
|
+
projectRoot: requiredOption(parsed, "project-root"),
|
|
640
|
+
profile: optionalOption(parsed, "profile"),
|
|
641
|
+
sourceHome: optionalOption(parsed, "source-home"),
|
|
642
|
+
targetHome: optionalOption(parsed, "target-home")
|
|
643
|
+
});
|
|
644
|
+
}
|
|
645
|
+
if (action === "init") {
|
|
646
|
+
return initCodexHome(context, {
|
|
647
|
+
projectRoot: requiredOption(parsed, "project-root"),
|
|
648
|
+
profile: optionalOption(parsed, "profile"),
|
|
649
|
+
sourceHome: optionalOption(parsed, "source-home"),
|
|
650
|
+
targetHome: optionalOption(parsed, "target-home")
|
|
651
|
+
});
|
|
652
|
+
}
|
|
653
|
+
if (action === "status") {
|
|
654
|
+
return getCodexHomeStatus(context, {
|
|
655
|
+
projectRoot: requiredOption(parsed, "project-root"),
|
|
656
|
+
profile: optionalOption(parsed, "profile")
|
|
657
|
+
});
|
|
658
|
+
}
|
|
659
|
+
if (action === "print-env") {
|
|
660
|
+
return printCodexHomeEnv(context, {
|
|
661
|
+
projectRoot: requiredOption(parsed, "project-root"),
|
|
662
|
+
profile: optionalOption(parsed, "profile")
|
|
663
|
+
});
|
|
664
|
+
}
|
|
665
|
+
if (action === "remove") {
|
|
666
|
+
return removeCodexHome(context, {
|
|
667
|
+
projectRoot: requiredOption(parsed, "project-root"),
|
|
668
|
+
profile: optionalOption(parsed, "profile"),
|
|
669
|
+
mode: requiredOption(parsed, "mode")
|
|
670
|
+
});
|
|
671
|
+
}
|
|
672
|
+
throw new AppError("usage", `Unknown codex home action: ${action}`, 2);
|
|
673
|
+
}
|
|
674
|
+
function dispatchCodexHook(context, parsed, stdin) {
|
|
675
|
+
const action = requiredPosition(parsed, 0, "codex hook action");
|
|
676
|
+
if (action === "handle") {
|
|
677
|
+
return handleCodexHook(context, {
|
|
678
|
+
event: requiredOption(parsed, "event"),
|
|
679
|
+
projectRoot: requiredOption(parsed, "project-root"),
|
|
680
|
+
stdin
|
|
681
|
+
});
|
|
682
|
+
}
|
|
683
|
+
throw new AppError("usage", `Unknown codex hook action: ${action}`, 2);
|
|
684
|
+
}
|
|
685
|
+
function dispatchWorktree(context, command, parsed) {
|
|
686
|
+
if (command === "plan") {
|
|
687
|
+
return planWorktree(context, { protocolId: requiredOption(parsed, "protocol-id") });
|
|
688
|
+
}
|
|
689
|
+
if (command === "create") {
|
|
690
|
+
return createWorktreeRecord(context, {
|
|
691
|
+
protocolId: requiredOption(parsed, "protocol-id"),
|
|
692
|
+
branch: requiredOption(parsed, "branch"),
|
|
693
|
+
base: requiredOption(parsed, "base"),
|
|
694
|
+
path: requiredOption(parsed, "path")
|
|
695
|
+
});
|
|
696
|
+
}
|
|
697
|
+
if (command === "status") {
|
|
698
|
+
return getWorktreeStatus(context, { protocolId: requiredOption(parsed, "protocol-id") });
|
|
699
|
+
}
|
|
700
|
+
if (command === "bootstrap") {
|
|
701
|
+
return bootstrapWorktree(context, { protocolId: requiredOption(parsed, "protocol-id") });
|
|
702
|
+
}
|
|
703
|
+
if (command === "close") {
|
|
704
|
+
return closeWorktree(context, {
|
|
705
|
+
protocolId: requiredOption(parsed, "protocol-id"),
|
|
706
|
+
mode: requiredOption(parsed, "mode")
|
|
707
|
+
});
|
|
708
|
+
}
|
|
709
|
+
throw new AppError("usage", `Unknown worktree command: ${command ?? "<empty>"}`, 2);
|
|
710
|
+
}
|
|
711
|
+
function dispatchPlanItem(context, parsed) {
|
|
712
|
+
const action = requiredPosition(parsed, 0, "plan item action");
|
|
713
|
+
const protocolId = requiredPosition(parsed, 1, "protocol-id");
|
|
714
|
+
const itemId = requiredPosition(parsed, 2, "item-id");
|
|
715
|
+
if (action === "start") {
|
|
716
|
+
return startPlanItem(context, { protocolId, itemId });
|
|
717
|
+
}
|
|
718
|
+
if (action === "done") {
|
|
719
|
+
return completePlanItem(context, {
|
|
720
|
+
protocolId,
|
|
721
|
+
itemId,
|
|
722
|
+
summary: requiredOption(parsed, "summary"),
|
|
723
|
+
evidence: parsed.options.get("evidence") ?? []
|
|
724
|
+
});
|
|
725
|
+
}
|
|
726
|
+
if (action === "block") {
|
|
727
|
+
return blockPlanItem(context, {
|
|
728
|
+
protocolId,
|
|
729
|
+
itemId,
|
|
730
|
+
reason: requiredOption(parsed, "reason"),
|
|
731
|
+
userRequired: parseBoolean(requiredOption(parsed, "user-required"), "user-required")
|
|
732
|
+
});
|
|
733
|
+
}
|
|
734
|
+
if (action === "skip") {
|
|
735
|
+
return skipPlanItem(context, {
|
|
736
|
+
protocolId,
|
|
737
|
+
itemId,
|
|
738
|
+
reason: requiredOption(parsed, "reason")
|
|
739
|
+
});
|
|
740
|
+
}
|
|
741
|
+
throw new AppError("usage", `Unknown plan item action: ${action}`, 2);
|
|
742
|
+
}
|
|
743
|
+
function parseArgs(args) {
|
|
744
|
+
const positional = [];
|
|
745
|
+
const options = new Map();
|
|
746
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
747
|
+
const value = args[index];
|
|
748
|
+
if (value?.startsWith("--")) {
|
|
749
|
+
const key = value.slice(2);
|
|
750
|
+
const next = args[index + 1];
|
|
751
|
+
if (!next || next.startsWith("--")) {
|
|
752
|
+
options.set(key, [...(options.get(key) ?? []), flagValue]);
|
|
753
|
+
}
|
|
754
|
+
else {
|
|
755
|
+
options.set(key, [...(options.get(key) ?? []), next]);
|
|
756
|
+
index += 1;
|
|
757
|
+
}
|
|
758
|
+
}
|
|
759
|
+
else if (value) {
|
|
760
|
+
positional.push(value);
|
|
761
|
+
}
|
|
762
|
+
}
|
|
763
|
+
return { positional, options };
|
|
764
|
+
}
|
|
765
|
+
function requiredPosition(parsed, index, label) {
|
|
766
|
+
const value = parsed.positional[index];
|
|
767
|
+
if (!value) {
|
|
768
|
+
throw new AppError("usage", `Missing required argument: ${label}`, 2);
|
|
769
|
+
}
|
|
770
|
+
return value;
|
|
771
|
+
}
|
|
772
|
+
function optionalOption(parsed, key) {
|
|
773
|
+
const values = parsed.options.get(key);
|
|
774
|
+
const value = values?.[values.length - 1];
|
|
775
|
+
return value === flagValue ? undefined : value;
|
|
776
|
+
}
|
|
777
|
+
function allOptions(parsed, key) {
|
|
778
|
+
return (parsed.options.get(key) ?? []).filter((value) => value !== flagValue);
|
|
779
|
+
}
|
|
780
|
+
function requiredOption(parsed, key) {
|
|
781
|
+
const value = optionalOption(parsed, key);
|
|
782
|
+
if (!value) {
|
|
783
|
+
throw new AppError("usage", `Missing required option: --${key}`, 2);
|
|
784
|
+
}
|
|
785
|
+
return value;
|
|
786
|
+
}
|
|
787
|
+
function hasOption(parsed, key) {
|
|
788
|
+
return parsed.options.has(key);
|
|
789
|
+
}
|
|
790
|
+
function parseBoolean(value, key) {
|
|
791
|
+
if (value === "true") {
|
|
792
|
+
return true;
|
|
793
|
+
}
|
|
794
|
+
if (value === "false") {
|
|
795
|
+
return false;
|
|
796
|
+
}
|
|
797
|
+
throw new AppError("validation", `--${key} must be true or false`, 2);
|
|
798
|
+
}
|
|
799
|
+
function parseOptionalBoolean(value, key) {
|
|
800
|
+
return typeof value === "string" ? parseBoolean(value, key) : undefined;
|
|
801
|
+
}
|
|
802
|
+
function optionalNumber(parsed, key) {
|
|
803
|
+
const value = optionalOption(parsed, key);
|
|
804
|
+
if (typeof value === "undefined") {
|
|
805
|
+
return undefined;
|
|
806
|
+
}
|
|
807
|
+
const parsedNumber = Number(value);
|
|
808
|
+
if (!Number.isFinite(parsedNumber)) {
|
|
809
|
+
throw new AppError("validation", `--${key} must be a number`, 2);
|
|
810
|
+
}
|
|
811
|
+
return parsedNumber;
|
|
812
|
+
}
|
|
813
|
+
function requiredWorkerId(parsed) {
|
|
814
|
+
return requiredOption(parsed, "worker-id");
|
|
815
|
+
}
|
|
816
|
+
function workspacePathOption(parsed) {
|
|
817
|
+
return optionalOption(parsed, "path") ?? process.cwd();
|
|
818
|
+
}
|
|
819
|
+
function parseWorktreePolicy(value) {
|
|
820
|
+
const policy = value ?? "keep";
|
|
821
|
+
if (policy === "keep" || policy === "remove") {
|
|
822
|
+
return policy;
|
|
823
|
+
}
|
|
824
|
+
throw new AppError("validation", "--worktree must be keep or remove", 2);
|
|
825
|
+
}
|
|
826
|
+
async function readStdin(stream) {
|
|
827
|
+
if (!stream) {
|
|
828
|
+
return "";
|
|
829
|
+
}
|
|
830
|
+
let data = "";
|
|
831
|
+
stream.setEncoding("utf8");
|
|
832
|
+
for await (const chunk of stream) {
|
|
833
|
+
data += chunk;
|
|
834
|
+
}
|
|
835
|
+
return data;
|
|
836
|
+
}
|
|
837
|
+
function refreshDashboardsAfterMutation(context, args, result) {
|
|
838
|
+
if (args[0] === "project" && args[1] === "archive") {
|
|
839
|
+
refreshGlobalDashboard(context, {});
|
|
840
|
+
return;
|
|
841
|
+
}
|
|
842
|
+
const projectRoot = projectRootForMutation(context, args, result);
|
|
843
|
+
if (!projectRoot) {
|
|
844
|
+
return;
|
|
845
|
+
}
|
|
846
|
+
autoRefreshDashboards(context, { projectRoot });
|
|
847
|
+
}
|
|
848
|
+
function projectRootForMutation(context, args, result) {
|
|
849
|
+
const [family, command, ...rest] = args;
|
|
850
|
+
const parsed = parseArgs(rest);
|
|
851
|
+
if (family === "dashboard" || family === "integration") {
|
|
852
|
+
return undefined;
|
|
853
|
+
}
|
|
854
|
+
if (family === "project" && command === "register") {
|
|
855
|
+
return requiredOption(parsed, "root");
|
|
856
|
+
}
|
|
857
|
+
if (family === "project" && command === "config" && requiredPosition(parsed, 0, "project config action") === "set") {
|
|
858
|
+
return requiredOption(parsed, "project-root");
|
|
859
|
+
}
|
|
860
|
+
if (family === "protocol" && command === "register") {
|
|
861
|
+
return requiredOption(parsed, "project-root");
|
|
862
|
+
}
|
|
863
|
+
if (family === "protocol" && command === "ready-for-merge") {
|
|
864
|
+
return protocolProjectRoot(context, requiredPosition(parsed, 0, "protocol-id"));
|
|
865
|
+
}
|
|
866
|
+
if (family === "protocol" && command === "cancel") {
|
|
867
|
+
return protocolProjectRoot(context, requiredPosition(parsed, 0, "protocol-id"));
|
|
868
|
+
}
|
|
869
|
+
if (family === "transition") {
|
|
870
|
+
const transitionParsed = parseArgs([command ?? "", ...rest]);
|
|
871
|
+
return protocolProjectRoot(context, requiredPosition(transitionParsed, 0, "protocol-id"));
|
|
872
|
+
}
|
|
873
|
+
if (family === "plan" && command === "set") {
|
|
874
|
+
return protocolProjectRoot(context, requiredPosition(parsed, 0, "protocol-id"));
|
|
875
|
+
}
|
|
876
|
+
if (family === "plan" && command === "item") {
|
|
877
|
+
return protocolProjectRoot(context, requiredPosition(parsed, 1, "protocol-id"));
|
|
878
|
+
}
|
|
879
|
+
if (family === "lane") {
|
|
880
|
+
return projectRootForLaneMutation(command, parsed);
|
|
881
|
+
}
|
|
882
|
+
if (family === "merge-queue") {
|
|
883
|
+
return projectRootForMergeQueueMutation(context, command, parsed);
|
|
884
|
+
}
|
|
885
|
+
if (family === "cleanup" && command === "apply") {
|
|
886
|
+
return requiredOption(parsed, "project-root");
|
|
887
|
+
}
|
|
888
|
+
if (family === "session") {
|
|
889
|
+
if (command === "register") {
|
|
890
|
+
return projectRootFromResult(result);
|
|
891
|
+
}
|
|
892
|
+
return ["stop", "stop-worker"].includes(command ?? "") ? requiredOption(parsed, "project-root") : undefined;
|
|
893
|
+
}
|
|
894
|
+
if (family === "run" && ["start", "attach-stage", "complete-stage", "complete"].includes(command ?? "")) {
|
|
895
|
+
return requiredOption(parsed, "project-root");
|
|
896
|
+
}
|
|
897
|
+
if (family === "worktree" && ["create", "bootstrap", "close"].includes(command ?? "")) {
|
|
898
|
+
return protocolProjectRoot(context, requiredOption(parsed, "protocol-id"));
|
|
899
|
+
}
|
|
900
|
+
if (family === "codex" && command === "hook" && requiredPosition(parsed, 0, "codex hook action") === "handle") {
|
|
901
|
+
return requiredOption(parsed, "project-root");
|
|
902
|
+
}
|
|
903
|
+
if (family === "codex" && command === "hooks") {
|
|
904
|
+
const action = requiredPosition(parsed, 0, "codex hooks action");
|
|
905
|
+
return ["install", "remove"].includes(action) ? requiredOption(parsed, "project-root") : undefined;
|
|
906
|
+
}
|
|
907
|
+
if (family === "codex" && command === "home") {
|
|
908
|
+
const action = requiredPosition(parsed, 0, "codex home action");
|
|
909
|
+
return ["init", "remove"].includes(action) ? requiredOption(parsed, "project-root") : undefined;
|
|
910
|
+
}
|
|
911
|
+
return undefined;
|
|
912
|
+
}
|
|
913
|
+
function projectRootForLaneMutation(command, parsed) {
|
|
914
|
+
if (command === "workspace" && requiredPosition(parsed, 0, "lane workspace action") === "set") {
|
|
915
|
+
return requiredOption(parsed, "project-root");
|
|
916
|
+
}
|
|
917
|
+
if (command !== "lock") {
|
|
918
|
+
return undefined;
|
|
919
|
+
}
|
|
920
|
+
const action = requiredPosition(parsed, 0, "lane lock action");
|
|
921
|
+
return ["acquire", "heartbeat", "release"].includes(action) ? requiredOption(parsed, "project-root") : undefined;
|
|
922
|
+
}
|
|
923
|
+
function projectRootForMergeQueueMutation(context, command, parsed) {
|
|
924
|
+
if (["next", "wait-next"].includes(command ?? "")) {
|
|
925
|
+
return requiredOption(parsed, "project-root");
|
|
926
|
+
}
|
|
927
|
+
if (["complete", "fail", "cancel"].includes(command ?? "")) {
|
|
928
|
+
return protocolProjectRoot(context, requiredPosition(parsed, 0, "protocol-id"));
|
|
929
|
+
}
|
|
930
|
+
return undefined;
|
|
931
|
+
}
|
|
932
|
+
function protocolProjectRoot(context, protocolId) {
|
|
933
|
+
return requireProtocol(context, protocolId).project_root;
|
|
934
|
+
}
|
|
935
|
+
function projectRootFromResult(result) {
|
|
936
|
+
if (!result || typeof result !== "object") {
|
|
937
|
+
return undefined;
|
|
938
|
+
}
|
|
939
|
+
const session = result.session;
|
|
940
|
+
if (session && typeof session === "object") {
|
|
941
|
+
const projectRoot = session.project_root;
|
|
942
|
+
return typeof projectRoot === "string" ? projectRoot : undefined;
|
|
943
|
+
}
|
|
944
|
+
return undefined;
|
|
945
|
+
}
|