input-kanban 0.0.7 → 0.0.9
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/PROJECT_GUIDE.md +168 -6
- package/README.en.md +51 -5
- package/README.md +51 -5
- package/RELEASE_NOTES.md +19 -0
- package/bin/input-kanban.js +755 -29
- package/package.json +4 -3
- package/public/index.html +138 -20
- package/src/orchestrator.js +492 -175
- package/src/server.js +8 -4
- package/src/utils.js +5 -0
package/bin/input-kanban.js
CHANGED
|
@@ -1,20 +1,54 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import fsp from 'node:fs/promises';
|
|
2
3
|
import path from 'node:path';
|
|
3
4
|
import { spawn } from 'node:child_process';
|
|
5
|
+
import { fileURLToPath } from 'node:url';
|
|
4
6
|
|
|
7
|
+
const PACKAGE_VERSION = JSON.parse(await fsp.readFile(new URL('../package.json', import.meta.url), 'utf8')).version;
|
|
5
8
|
const VALID_RUNNERS = ['headless', 'tmux'];
|
|
9
|
+
const VALID_SANDBOXES = ['read-only', 'workspace-write', 'danger-full-access'];
|
|
10
|
+
const COMMANDS = new Set(['serve', 'submit', 'runs', 'status', 'result', 'retry', 'stop', 'auto']);
|
|
11
|
+
const delay = ms => new Promise(resolve => setTimeout(resolve, ms));
|
|
12
|
+
const STATUS_TEXT = {
|
|
13
|
+
created: '已创建', planning: '拆分中', plan_failed: '拆分失败', plan_empty: '拆分为空', planned: '已拆分',
|
|
14
|
+
running: '执行中', batch_blocked: '批次阻塞', batches_completed: '批次完成', judging: '验收中', judged: '已验收',
|
|
15
|
+
judge_failed: '验收失败', stopped: '已停止'
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
function validateChoice(value, source, choices) {
|
|
19
|
+
if (choices.includes(value)) return value;
|
|
20
|
+
throw new Error(`invalid ${source}: ${value}; expected one of: ${choices.join(', ')}`);
|
|
21
|
+
}
|
|
6
22
|
|
|
7
23
|
function validateRunner(value, source) {
|
|
8
|
-
|
|
9
|
-
|
|
24
|
+
return validateChoice(value, source, VALID_RUNNERS);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function validateSandbox(value, source) {
|
|
28
|
+
return validateChoice(value, source, VALID_SANDBOXES);
|
|
10
29
|
}
|
|
11
30
|
|
|
12
|
-
function
|
|
13
|
-
|
|
31
|
+
function splitCommand(argv) {
|
|
32
|
+
let index = 0;
|
|
33
|
+
const globals = { json: false };
|
|
34
|
+
while (index < argv.length) {
|
|
35
|
+
const arg = argv[index];
|
|
36
|
+
if (arg === '--json' || arg === '-j') { globals.json = true; index++; continue; }
|
|
37
|
+
break;
|
|
38
|
+
}
|
|
39
|
+
const rest = argv.slice(index);
|
|
40
|
+
if (rest[0] === '--version' || rest[0] === '-v' || rest[0] === 'version') return { command: 'version', rest: rest.slice(1), globals };
|
|
41
|
+
if (rest[0] && COMMANDS.has(rest[0])) return { command: rest[0], rest: rest.slice(1), globals };
|
|
42
|
+
return { command: 'serve', rest, globals };
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function parseServeArgs(argv) {
|
|
46
|
+
const args = { host: '127.0.0.1', port: undefined, repo: undefined, runsDir: undefined, codexBin: undefined, runner: undefined, open: false, json: false, help: false };
|
|
14
47
|
for (let i = 0; i < argv.length; i++) {
|
|
15
48
|
const arg = argv[i];
|
|
16
49
|
const next = () => argv[++i];
|
|
17
50
|
if (arg === '--help' || arg === '-h') args.help = true;
|
|
51
|
+
else if (arg === '--json' || arg === '-j') args.json = true;
|
|
18
52
|
else if (arg === '--open') args.open = true;
|
|
19
53
|
else if (arg === '--no-open') args.open = false;
|
|
20
54
|
else if (arg === '--host') args.host = next();
|
|
@@ -28,22 +62,347 @@ function parseArgs(argv) {
|
|
|
28
62
|
return args;
|
|
29
63
|
}
|
|
30
64
|
|
|
65
|
+
function parseRunsArgs(argv) {
|
|
66
|
+
const args = { runsDir: undefined, active: false, includeArchived: false, limit: 20, json: false, help: false };
|
|
67
|
+
for (let i = 0; i < argv.length; i++) {
|
|
68
|
+
const arg = argv[i];
|
|
69
|
+
const next = () => argv[++i];
|
|
70
|
+
if (arg === '--help' || arg === '-h') args.help = true;
|
|
71
|
+
else if (arg === '--json' || arg === '-j') args.json = true;
|
|
72
|
+
else if (arg === '--runs-dir') args.runsDir = next();
|
|
73
|
+
else if (arg === '--active') args.active = true;
|
|
74
|
+
else if (arg === '--include-archived') args.includeArchived = true;
|
|
75
|
+
else if (arg === '--limit') args.limit = Number(next());
|
|
76
|
+
else throw new Error(`unknown runs argument: ${arg}`);
|
|
77
|
+
}
|
|
78
|
+
return args;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function parseStatusArgs(argv) {
|
|
82
|
+
const args = { host: '127.0.0.1', port: 8787, runsDir: undefined, runId: undefined, watch: false, json: false, pollMs: 3000, help: false };
|
|
83
|
+
for (let i = 0; i < argv.length; i++) {
|
|
84
|
+
const arg = argv[i];
|
|
85
|
+
const next = () => argv[++i];
|
|
86
|
+
if (arg === '--help' || arg === '-h') args.help = true;
|
|
87
|
+
else if (arg === '--json' || arg === '-j') args.json = true;
|
|
88
|
+
else if (arg === '--host') args.host = next();
|
|
89
|
+
else if (arg === '--port' || arg === '-p') args.port = Number(next());
|
|
90
|
+
else if (arg === '--runs-dir') args.runsDir = next();
|
|
91
|
+
else if (arg === '--watch') args.watch = true;
|
|
92
|
+
else if (arg === '--poll-ms') args.pollMs = Number(next());
|
|
93
|
+
else if (!arg.startsWith('-') && !args.runId) args.runId = arg;
|
|
94
|
+
else throw new Error(`unknown status argument: ${arg}`);
|
|
95
|
+
}
|
|
96
|
+
return args;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function parseResultArgs(argv) {
|
|
100
|
+
const args = { runsDir: undefined, runId: undefined, copy: false, json: false, help: false };
|
|
101
|
+
for (let i = 0; i < argv.length; i++) {
|
|
102
|
+
const arg = argv[i];
|
|
103
|
+
const next = () => argv[++i];
|
|
104
|
+
if (arg === '--help' || arg === '-h') args.help = true;
|
|
105
|
+
else if (arg === '--json' || arg === '-j') args.json = true;
|
|
106
|
+
else if (arg === '--runs-dir') args.runsDir = next();
|
|
107
|
+
else if (arg === '--copy') args.copy = true;
|
|
108
|
+
else if (!arg.startsWith('-') && !args.runId) args.runId = arg;
|
|
109
|
+
else throw new Error(`unknown result argument: ${arg}`);
|
|
110
|
+
}
|
|
111
|
+
return args;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function parseRetryArgs(argv) {
|
|
115
|
+
const args = { runsDir: undefined, runId: undefined, taskId: undefined, reason: 'manual retry from CLI', maxRetries: 1, json: false, help: false };
|
|
116
|
+
for (let i = 0; i < argv.length; i++) {
|
|
117
|
+
const arg = argv[i];
|
|
118
|
+
const next = () => argv[++i];
|
|
119
|
+
if (arg === '--help' || arg === '-h') args.help = true;
|
|
120
|
+
else if (arg === '--json' || arg === '-j') args.json = true;
|
|
121
|
+
else if (arg === '--runs-dir') args.runsDir = next();
|
|
122
|
+
else if (arg === '--reason') args.reason = next();
|
|
123
|
+
else if (arg === '--max-retries') args.maxRetries = Number(next());
|
|
124
|
+
else if (!arg.startsWith('-') && !args.runId) args.runId = arg;
|
|
125
|
+
else if (!arg.startsWith('-') && !args.taskId) args.taskId = arg;
|
|
126
|
+
else throw new Error(`unknown retry argument: ${arg}`);
|
|
127
|
+
}
|
|
128
|
+
return args;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function parseStopArgs(argv) {
|
|
132
|
+
const args = { runsDir: undefined, runId: undefined, reason: 'stopped from CLI', json: false, help: false };
|
|
133
|
+
for (let i = 0; i < argv.length; i++) {
|
|
134
|
+
const arg = argv[i];
|
|
135
|
+
const next = () => argv[++i];
|
|
136
|
+
if (arg === '--help' || arg === '-h') args.help = true;
|
|
137
|
+
else if (arg === '--json' || arg === '-j') args.json = true;
|
|
138
|
+
else if (arg === '--runs-dir') args.runsDir = next();
|
|
139
|
+
else if (arg === '--reason') args.reason = next();
|
|
140
|
+
else if (!arg.startsWith('-') && !args.runId) args.runId = arg;
|
|
141
|
+
else throw new Error(`unknown stop argument: ${arg}`);
|
|
142
|
+
}
|
|
143
|
+
return args;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
function parseAutoArgs(argv) {
|
|
147
|
+
const args = { host: '127.0.0.1', port: 8787, runsDir: undefined, codexBin: undefined, runner: undefined, runId: undefined, json: false, pollMs: 3000, maxRetries: 1, help: false };
|
|
148
|
+
for (let i = 0; i < argv.length; i++) {
|
|
149
|
+
const arg = argv[i];
|
|
150
|
+
const next = () => argv[++i];
|
|
151
|
+
if (arg === '--help' || arg === '-h') args.help = true;
|
|
152
|
+
else if (arg === '--json' || arg === '-j') args.json = true;
|
|
153
|
+
else if (arg === '--host') args.host = next();
|
|
154
|
+
else if (arg === '--port' || arg === '-p') args.port = Number(next());
|
|
155
|
+
else if (arg === '--runs-dir') args.runsDir = next();
|
|
156
|
+
else if (arg === '--codex-bin') args.codexBin = next();
|
|
157
|
+
else if (arg === '--runner') args.runner = validateRunner(next(), '--runner');
|
|
158
|
+
else if (arg === '--poll-ms') args.pollMs = Number(next());
|
|
159
|
+
else if (arg === '--max-retries') args.maxRetries = Number(next());
|
|
160
|
+
else if (!arg.startsWith('-') && !args.runId) args.runId = arg;
|
|
161
|
+
else throw new Error(`unknown auto argument: ${arg}`);
|
|
162
|
+
}
|
|
163
|
+
return args;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function parseSubmitArgs(argv) {
|
|
167
|
+
const args = {
|
|
168
|
+
host: '127.0.0.1', port: 8787, repo: undefined, runsDir: undefined, codexBin: undefined,
|
|
169
|
+
runner: undefined, label: undefined, taskText: undefined, taskFile: undefined, maxParallel: 3,
|
|
170
|
+
workerSandbox: 'workspace-write', auto: true, detach: false, watch: true, json: false, pollMs: 3000, maxRetries: 1, help: false
|
|
171
|
+
};
|
|
172
|
+
for (let i = 0; i < argv.length; i++) {
|
|
173
|
+
const arg = argv[i];
|
|
174
|
+
const next = () => argv[++i];
|
|
175
|
+
if (arg === '--help' || arg === '-h') args.help = true;
|
|
176
|
+
else if (arg === '--json' || arg === '-j') args.json = true;
|
|
177
|
+
else if (arg === '--host') args.host = next();
|
|
178
|
+
else if (arg === '--port' || arg === '-p') args.port = Number(next());
|
|
179
|
+
else if (arg === '--repo' || arg === '-r') args.repo = next();
|
|
180
|
+
else if (arg === '--runs-dir') args.runsDir = next();
|
|
181
|
+
else if (arg === '--codex-bin') args.codexBin = next();
|
|
182
|
+
else if (arg === '--runner') args.runner = validateRunner(next(), '--runner');
|
|
183
|
+
else if (arg === '--label' || arg === '-l') args.label = next();
|
|
184
|
+
else if (arg === '--task') args.taskText = next();
|
|
185
|
+
else if (arg === '--task-file') args.taskFile = next();
|
|
186
|
+
else if (arg === '--max-parallel') args.maxParallel = Number(next());
|
|
187
|
+
else if (arg === '--worker-sandbox') args.workerSandbox = validateSandbox(next(), '--worker-sandbox');
|
|
188
|
+
else if (arg === '--auto') { args.auto = true; args.watch = true; }
|
|
189
|
+
else if (arg === '--no-auto') { args.auto = false; args.watch = false; }
|
|
190
|
+
else if (arg === '--detach' || arg === '-d') args.detach = true;
|
|
191
|
+
else if (arg === '--watch') args.watch = true;
|
|
192
|
+
else if (arg === '--poll-ms') args.pollMs = Number(next());
|
|
193
|
+
else if (arg === '--max-retries') args.maxRetries = Number(next());
|
|
194
|
+
else throw new Error(`unknown submit argument: ${arg}`);
|
|
195
|
+
}
|
|
196
|
+
return args;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
function applyRuntimeEnv(args) {
|
|
200
|
+
if (args.port) process.env.PORT = String(args.port);
|
|
201
|
+
if (args.host) process.env.HOST = args.host;
|
|
202
|
+
if (args.repo) process.env.KANBAN_DEFAULT_REPO = path.resolve(args.repo);
|
|
203
|
+
else if (!process.env.KANBAN_DEFAULT_REPO) process.env.KANBAN_DEFAULT_REPO = process.cwd();
|
|
204
|
+
if (args.runsDir) process.env.KANBAN_RUNS_DIR = path.resolve(args.runsDir);
|
|
205
|
+
if (args.codexBin) process.env.KANBAN_CODEX_BIN = args.codexBin;
|
|
206
|
+
if (args.runner) process.env.KANBAN_RUNNER = args.runner;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
function printJson(value) {
|
|
210
|
+
console.log(JSON.stringify(value, null, 2));
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
function printVersion() {
|
|
214
|
+
console.log(`input-kanban v${PACKAGE_VERSION}`);
|
|
215
|
+
}
|
|
216
|
+
|
|
31
217
|
function printHelp() {
|
|
32
|
-
console.log(`input-kanban
|
|
218
|
+
console.log(`input-kanban v${PACKAGE_VERSION}
|
|
33
219
|
|
|
34
220
|
Usage:
|
|
35
221
|
input-kanban [options]
|
|
222
|
+
input-kanban serve [options]
|
|
223
|
+
input-kanban submit [options]
|
|
224
|
+
input-kanban --version
|
|
225
|
+
input-kanban runs [options]
|
|
226
|
+
input-kanban status [runId] [options]
|
|
227
|
+
input-kanban result [runId] [options]
|
|
228
|
+
input-kanban retry <runId> [taskId] [options]
|
|
229
|
+
input-kanban stop <runId> [options]
|
|
230
|
+
|
|
231
|
+
Serve options:
|
|
232
|
+
--host <host> Host to bind, default 127.0.0.1
|
|
233
|
+
-p, --port <port> Port to bind, default 8787
|
|
234
|
+
-r, --repo <path> Default target repository, default current directory
|
|
235
|
+
--runs-dir <path> Runtime runs directory, default ~/.input-kanban/runs
|
|
236
|
+
--codex-bin <path> Codex CLI executable, default codex
|
|
237
|
+
--runner <mode> Runner mode: headless or tmux, default headless
|
|
238
|
+
-j, --json Emit JSON startup output
|
|
239
|
+
-v, --version Print version and exit
|
|
240
|
+
--open Open browser after starting
|
|
241
|
+
--no-open Do not open browser, default
|
|
242
|
+
|
|
243
|
+
Runs options:
|
|
244
|
+
--runs-dir <path> Runtime runs directory shared with the Web UI
|
|
245
|
+
--active Show only active or pending-action runs
|
|
246
|
+
--include-archived Include archived runs
|
|
247
|
+
--limit <n> Maximum rows to print, default 20
|
|
248
|
+
-j, --json Emit JSON output instead of human text
|
|
249
|
+
|
|
250
|
+
Status options:
|
|
251
|
+
--runs-dir <path> Runtime runs directory shared with the Web UI
|
|
252
|
+
--watch Keep printing status until the run reaches a terminal state
|
|
253
|
+
--poll-ms <ms> Watch poll interval, default 3000
|
|
254
|
+
-j, --json Emit JSON output instead of human text
|
|
255
|
+
|
|
256
|
+
Result options:
|
|
257
|
+
--runs-dir <path> Runtime runs directory shared with the Web UI
|
|
258
|
+
--copy Copy final result to clipboard
|
|
259
|
+
-j, --json Emit JSON output instead of human text
|
|
260
|
+
|
|
261
|
+
Retry options:
|
|
262
|
+
--runs-dir <path> Runtime runs directory shared with the Web UI
|
|
263
|
+
--reason <text> Retry reason stored in task retry history
|
|
264
|
+
--max-retries <n> Retry limit for automatic retry policy, default 1
|
|
265
|
+
-j, --json Emit JSON output instead of human text
|
|
266
|
+
|
|
267
|
+
Stop options:
|
|
268
|
+
--runs-dir <path> Runtime runs directory shared with the Web UI
|
|
269
|
+
--reason <text> Stop reason stored in run state
|
|
270
|
+
-j, --json Emit JSON output instead of human text
|
|
271
|
+
|
|
272
|
+
Submit options:
|
|
273
|
+
-r, --repo <path> Target Git work tree, default current directory
|
|
274
|
+
-l, --label <label> Task batch name, default generated from task text
|
|
275
|
+
--task <text> Task description text
|
|
276
|
+
--task-file <path> Read task description from file, use - for stdin
|
|
277
|
+
--max-parallel <n> Default max parallel workers, default 3
|
|
278
|
+
--worker-sandbox <mode> read-only, workspace-write, or danger-full-access
|
|
279
|
+
--runner <mode> Runner mode: headless or tmux
|
|
280
|
+
--runs-dir <path> Runtime runs directory shared with the Web UI
|
|
281
|
+
--auto Plan, dispatch all batches, judge, and watch, default for submit
|
|
282
|
+
--no-auto Only create the run and start planning
|
|
283
|
+
-d, --detach Run the default auto loop in a background supervisor
|
|
284
|
+
--watch Watch status after starting the planner
|
|
285
|
+
--poll-ms <ms> Watch poll interval, default 3000
|
|
286
|
+
-j, --json Emit JSON output instead of human text
|
|
287
|
+
-h, --help Show help
|
|
288
|
+
`);
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
function printSubmitHelp() {
|
|
292
|
+
console.log(`input-kanban submit
|
|
293
|
+
|
|
294
|
+
Usage:
|
|
295
|
+
input-kanban submit --repo <path> --task-file task.md
|
|
296
|
+
input-kanban submit --repo <path> --task "fix the bug" --label "bugfix"
|
|
297
|
+
input-kanban submit --task-file task.md -d
|
|
298
|
+
|
|
299
|
+
Options:
|
|
300
|
+
-r, --repo <path> Target Git work tree, default current directory
|
|
301
|
+
-l, --label <label> Task batch name, default generated from task text
|
|
302
|
+
--task <text> Task description text
|
|
303
|
+
--task-file <path> Read task description from file, use - for stdin
|
|
304
|
+
--max-parallel <n> Default max parallel workers, default 3
|
|
305
|
+
--worker-sandbox <mode> read-only, workspace-write, or danger-full-access
|
|
306
|
+
--runner <mode> Runner mode: headless or tmux
|
|
307
|
+
--runs-dir <path> Runtime runs directory shared with the Web UI
|
|
308
|
+
--auto Plan, dispatch all batches, judge, and watch, default for submit
|
|
309
|
+
--no-auto Only create the run and start planning
|
|
310
|
+
-d, --detach Run the default auto loop in a background supervisor
|
|
311
|
+
--watch Watch status after starting the planner
|
|
312
|
+
--poll-ms <ms> Watch poll interval, default 3000
|
|
313
|
+
`);
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
function printRunsHelp() {
|
|
317
|
+
console.log(`input-kanban runs
|
|
318
|
+
|
|
319
|
+
Usage:
|
|
320
|
+
input-kanban runs
|
|
321
|
+
input-kanban runs --active
|
|
322
|
+
input-kanban --json runs --active
|
|
36
323
|
|
|
37
324
|
Options:
|
|
38
|
-
--
|
|
39
|
-
|
|
40
|
-
-
|
|
41
|
-
--
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
325
|
+
--runs-dir <path> Runtime runs directory shared with the Web UI
|
|
326
|
+
--active Show only active or pending-action runs
|
|
327
|
+
--include-archived Include archived runs
|
|
328
|
+
--limit <n> Maximum rows to print, default 20
|
|
329
|
+
-j, --json Emit JSON output instead of human text
|
|
330
|
+
`);
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
function printStatusHelp() {
|
|
334
|
+
console.log(`input-kanban status
|
|
335
|
+
|
|
336
|
+
Usage:
|
|
337
|
+
input-kanban status
|
|
338
|
+
input-kanban status <runId>
|
|
339
|
+
input-kanban status --watch
|
|
340
|
+
input-kanban status <runId> --watch
|
|
341
|
+
|
|
342
|
+
Options:
|
|
343
|
+
--runs-dir <path> Runtime runs directory shared with the Web UI
|
|
344
|
+
--watch Keep printing status until the run reaches a terminal state
|
|
345
|
+
--poll-ms <ms> Watch poll interval, default 3000
|
|
346
|
+
-j, --json Emit JSON output instead of human text
|
|
347
|
+
`);
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
function printResultHelp() {
|
|
351
|
+
console.log(`input-kanban result
|
|
352
|
+
|
|
353
|
+
Usage:
|
|
354
|
+
input-kanban result
|
|
355
|
+
input-kanban result <runId>
|
|
356
|
+
input-kanban result <runId> --copy
|
|
357
|
+
|
|
358
|
+
Options:
|
|
359
|
+
--runs-dir <path> Runtime runs directory shared with the Web UI
|
|
360
|
+
--copy Copy final result to clipboard
|
|
361
|
+
-j, --json Emit JSON output instead of human text
|
|
362
|
+
`);
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
function printRetryHelp() {
|
|
366
|
+
console.log(`input-kanban retry
|
|
367
|
+
|
|
368
|
+
Usage:
|
|
369
|
+
input-kanban retry <runId>
|
|
370
|
+
input-kanban retry <runId> <taskId>
|
|
371
|
+
input-kanban --json retry <runId> <taskId>
|
|
372
|
+
|
|
373
|
+
Options:
|
|
374
|
+
--runs-dir <path> Runtime runs directory shared with the Web UI
|
|
375
|
+
--reason <text> Retry reason stored in task retry history
|
|
376
|
+
--max-retries <n> Retry limit for automatic retry policy, default 1
|
|
377
|
+
-j, --json Emit JSON output instead of human text
|
|
378
|
+
`);
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
function printStopHelp() {
|
|
382
|
+
console.log(`input-kanban stop
|
|
383
|
+
|
|
384
|
+
Usage:
|
|
385
|
+
input-kanban stop <runId>
|
|
386
|
+
|
|
387
|
+
Options:
|
|
388
|
+
--runs-dir <path> Runtime runs directory shared with the Web UI
|
|
389
|
+
--reason <text> Stop reason stored in run state
|
|
390
|
+
-j, --json Emit JSON output instead of human text
|
|
391
|
+
`);
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
function printAutoHelp() {
|
|
395
|
+
console.log(`input-kanban auto
|
|
396
|
+
|
|
397
|
+
Usage:
|
|
398
|
+
input-kanban auto <runId>
|
|
399
|
+
|
|
400
|
+
Options:
|
|
401
|
+
--runs-dir <path> Runtime runs directory shared with the Web UI
|
|
402
|
+
--codex-bin <path> Codex CLI executable, default codex
|
|
403
|
+
--runner <mode> Runner mode: headless or tmux
|
|
404
|
+
--poll-ms <ms> Watch poll interval, default 3000
|
|
405
|
+
-j, --json Emit JSON output instead of human text
|
|
47
406
|
`);
|
|
48
407
|
}
|
|
49
408
|
|
|
@@ -54,28 +413,395 @@ function openBrowser(url) {
|
|
|
54
413
|
child.unref();
|
|
55
414
|
}
|
|
56
415
|
|
|
57
|
-
|
|
58
|
-
const
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
if (args.
|
|
65
|
-
if (args.
|
|
66
|
-
if (args.
|
|
416
|
+
async function readStdin() {
|
|
417
|
+
const chunks = [];
|
|
418
|
+
for await (const chunk of process.stdin) chunks.push(Buffer.from(chunk));
|
|
419
|
+
return Buffer.concat(chunks).toString('utf8');
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
async function readTaskText(args) {
|
|
423
|
+
if (args.taskText !== undefined) return args.taskText;
|
|
424
|
+
if (args.taskFile === '-') return await readStdin();
|
|
425
|
+
if (args.taskFile) return await fsp.readFile(path.resolve(args.taskFile), 'utf8');
|
|
426
|
+
throw new Error('submit requires --task or --task-file');
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
function baseUrl(args) {
|
|
430
|
+
return `http://${args.host || '127.0.0.1'}:${Number(args.port || 8787)}`;
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
function webUrl(args, runId = '') {
|
|
434
|
+
return `${baseUrl(args)}${runId ? ` (runId: ${runId})` : ''}`;
|
|
435
|
+
}
|
|
67
436
|
|
|
437
|
+
function displayStatus(status) {
|
|
438
|
+
const text = STATUS_TEXT[status] || status || '-';
|
|
439
|
+
return status && text !== status ? `${text}(${status})` : text;
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
function countByStatus(state) {
|
|
443
|
+
const tasks = state.tasks || [];
|
|
444
|
+
return {
|
|
445
|
+
total: tasks.length,
|
|
446
|
+
completed: tasks.filter(task => task.status === 'completed').length,
|
|
447
|
+
running: tasks.filter(task => task.status === 'running').length,
|
|
448
|
+
failed: tasks.filter(task => ['failed', 'unknown'].includes(task.status)).length
|
|
449
|
+
};
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
function currentBatchText(state) {
|
|
453
|
+
const batch = (state.batches || []).find(item => item.status !== 'completed');
|
|
454
|
+
if (!batch) return '-';
|
|
455
|
+
const tasks = batch.tasks || [];
|
|
456
|
+
const completed = tasks.filter(task => task.status === 'completed').length;
|
|
457
|
+
return `${batch.name || batch.id}(${batch.id}) ${displayStatus(batch.status)} ${completed}/${tasks.length}`;
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
function statusLine(state) {
|
|
461
|
+
const counts = countByStatus(state);
|
|
462
|
+
return `${state.label || state.runId}|${state.runId}|状态 ${displayStatus(state.status)}|进度 ${counts.completed}/${counts.total}|执行中 ${counts.running}|失败 ${counts.failed}`;
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
function printRunStatus(state) {
|
|
466
|
+
const counts = countByStatus(state);
|
|
467
|
+
console.log(`任务批次: ${state.label || '-'}`);
|
|
468
|
+
console.log(`Run ID: ${state.runId}`);
|
|
469
|
+
console.log(`状态: ${displayStatus(state.status)}`);
|
|
470
|
+
console.log(`仓库: ${state.repo || '-'}`);
|
|
471
|
+
console.log(`当前批次: ${currentBatchText(state)}`);
|
|
472
|
+
console.log(`进度: ${counts.completed}/${counts.total} |执行中 ${counts.running} |失败 ${counts.failed}`);
|
|
473
|
+
if (state.judge?.status && state.judge.status !== 'pending') console.log(`验收: ${displayStatus(state.judge.status)}`);
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
function printRunsTable(runs) {
|
|
477
|
+
if (!runs.length) { console.log('没有找到任务批次'); return; }
|
|
478
|
+
for (const run of runs) {
|
|
479
|
+
console.log(`${run.runId}|${run.label || '-'}|${displayStatus(run.status)}|进度 ${run.completed}/${run.total}|执行中 ${run.running}|失败 ${run.failed}|runner ${run.runner || '-'}|沙箱 ${run.workerSandbox || '-'}|仓库 ${run.repo || '-'}`);
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
function isTerminal(state) {
|
|
484
|
+
return ['judged', 'judge_failed', 'batch_blocked', 'plan_failed', 'plan_empty', 'stopped'].includes(state.status);
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
function isFailureTerminal(state) {
|
|
488
|
+
return ['judge_failed', 'batch_blocked', 'plan_failed', 'plan_empty', 'stopped'].includes(state.status);
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
function isActiveRunSummary(run) {
|
|
492
|
+
if (!run) return false;
|
|
493
|
+
if (Number(run.running) > 0) return true;
|
|
494
|
+
return !['judged', 'judge_failed', 'batch_blocked', 'plan_failed', 'plan_empty', 'stopped'].includes(run.status);
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
function hasRecoverableUnknownTask(state) {
|
|
498
|
+
return (state.tasks || []).some(task => task.status === 'unknown' && (task.exitCode === undefined || task.exitCode === 0));
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
async function confirmFailureTerminal(runId, state, refreshRun, pollMs) {
|
|
502
|
+
let confirmed = state;
|
|
503
|
+
const deadline = Date.now() + 30000;
|
|
504
|
+
while (confirmed?.status === 'batch_blocked' && hasRecoverableUnknownTask(confirmed) && Date.now() < deadline) {
|
|
505
|
+
await delay(Math.max(500, Number(pollMs) || 3000));
|
|
506
|
+
confirmed = await refreshRun(runId);
|
|
507
|
+
if (!confirmed || !isTerminal(confirmed) || confirmed.status !== state.status) return { confirmed: false, state: confirmed };
|
|
508
|
+
}
|
|
509
|
+
return { confirmed: true, state: confirmed };
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
async function watchRun(runId, { auto = false, pollMs = 3000, quiet = false, maxRetries = 1 } = {}) {
|
|
513
|
+
const { dispatchRun, refreshRun, retryRun, startJudge } = await import('../src/orchestrator.js');
|
|
514
|
+
let lastStatus = '';
|
|
515
|
+
let judgeStarted = false;
|
|
516
|
+
while (true) {
|
|
517
|
+
const state = await refreshRun(runId);
|
|
518
|
+
if (!state) throw new Error(`run not found: ${runId}`);
|
|
519
|
+
const line = statusLine(state);
|
|
520
|
+
if (line !== lastStatus) {
|
|
521
|
+
if (!quiet) console.log(`[${new Date().toLocaleTimeString()}] ${line}`);
|
|
522
|
+
lastStatus = line;
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
if (auto && state.status === 'batch_blocked') {
|
|
526
|
+
try {
|
|
527
|
+
const retryState = await retryRun(runId, { reason: 'auto retry from CLI', maxRetries, auto: true });
|
|
528
|
+
if (!quiet) console.log(`自动重试任务: ${retryState.retriedTaskIds?.join(', ') || '-'}`);
|
|
529
|
+
lastStatus = '';
|
|
530
|
+
continue;
|
|
531
|
+
} catch (error) {
|
|
532
|
+
if (!quiet) console.log(`自动重试跳过: ${error.message || String(error)}`);
|
|
533
|
+
}
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
if (auto && state.status === 'planned') {
|
|
537
|
+
if (!quiet) console.log('自动派发任务...');
|
|
538
|
+
await dispatchRun(runId);
|
|
539
|
+
} else if (auto && state.status === 'batches_completed' && state.judge?.status !== 'running' && !judgeStarted) {
|
|
540
|
+
if (!quiet) console.log('自动发起最终验收...');
|
|
541
|
+
judgeStarted = true;
|
|
542
|
+
await startJudge(runId);
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
if (isTerminal(state)) {
|
|
546
|
+
if (isFailureTerminal(state)) {
|
|
547
|
+
const result = await confirmFailureTerminal(runId, state, refreshRun, pollMs);
|
|
548
|
+
if (!result.confirmed) {
|
|
549
|
+
lastStatus = '';
|
|
550
|
+
continue;
|
|
551
|
+
}
|
|
552
|
+
return result.state || state;
|
|
553
|
+
}
|
|
554
|
+
return state;
|
|
555
|
+
}
|
|
556
|
+
await delay(Math.max(500, Number(pollMs) || 3000));
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
async function serve(args) {
|
|
561
|
+
applyRuntimeEnv(args);
|
|
68
562
|
const { startServer } = await import('../src/server.js');
|
|
69
563
|
const instance = await startServer({ host: process.env.HOST, port: Number(process.env.PORT || 8787), log: false });
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
564
|
+
if (args.json) {
|
|
565
|
+
printJson({ ok: true, command: 'serve', version: instance.version, url: instance.url, repo: instance.defaultRepo, runsDir: instance.runsDir, runner: instance.runner });
|
|
566
|
+
} else {
|
|
567
|
+
console.log(`Input Kanban v${PACKAGE_VERSION} started`);
|
|
568
|
+
console.log(`URL: ${instance.url}`);
|
|
569
|
+
console.log(`Repo: ${instance.defaultRepo}`);
|
|
570
|
+
console.log(`Runs: ${instance.runsDir}`);
|
|
571
|
+
console.log(`Runner: ${instance.runner}`);
|
|
572
|
+
}
|
|
75
573
|
if (args.open) openBrowser(instance.url);
|
|
76
574
|
const shutdown = () => { instance.stop().finally(() => process.exit(0)); };
|
|
77
575
|
process.on('SIGINT', shutdown);
|
|
78
576
|
process.on('SIGTERM', shutdown);
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
function detachedAutoArgs(runId, args) {
|
|
580
|
+
const cliPath = fileURLToPath(import.meta.url);
|
|
581
|
+
const values = [cliPath, 'auto', runId, '--host', args.host || '127.0.0.1', '--port', String(args.port || 8787), '--poll-ms', String(args.pollMs || 3000), '--max-retries', String(args.maxRetries ?? 1)];
|
|
582
|
+
if (args.runsDir) values.push('--runs-dir', path.resolve(args.runsDir));
|
|
583
|
+
if (args.codexBin) values.push('--codex-bin', args.codexBin);
|
|
584
|
+
if (args.runner) values.push('--runner', args.runner);
|
|
585
|
+
return values;
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
function startDetachedAuto(runId, args) {
|
|
589
|
+
const child = spawn(process.execPath, detachedAutoArgs(runId, args), {
|
|
590
|
+
detached: true,
|
|
591
|
+
stdio: 'ignore',
|
|
592
|
+
env: process.env
|
|
593
|
+
});
|
|
594
|
+
child.unref();
|
|
595
|
+
return child.pid;
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
async function latestRunId() {
|
|
599
|
+
const { listRuns } = await import('../src/orchestrator.js');
|
|
600
|
+
const runs = await listRuns();
|
|
601
|
+
if (!runs.length) throw new Error('没有找到任务批次');
|
|
602
|
+
return runs[0].runId;
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
async function runs(args) {
|
|
606
|
+
applyRuntimeEnv(args);
|
|
607
|
+
const { listRuns } = await import('../src/orchestrator.js');
|
|
608
|
+
const limit = Number.isFinite(Number(args.limit)) && Number(args.limit) > 0 ? Number(args.limit) : 20;
|
|
609
|
+
const allRuns = await listRuns({ includeArchived: !!args.includeArchived });
|
|
610
|
+
const filtered = (args.active ? allRuns.filter(isActiveRunSummary) : allRuns).slice(0, limit);
|
|
611
|
+
if (args.json) {
|
|
612
|
+
printJson({ ok: true, command: 'runs', active: !!args.active, includeArchived: !!args.includeArchived, limit, count: filtered.length, runs: filtered });
|
|
613
|
+
return;
|
|
614
|
+
}
|
|
615
|
+
printRunsTable(filtered);
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
async function status(args) {
|
|
619
|
+
applyRuntimeEnv(args);
|
|
620
|
+
const runId = args.runId || await latestRunId();
|
|
621
|
+
const { refreshRun, summaryOfRun } = await import('../src/orchestrator.js');
|
|
622
|
+
if (args.watch) {
|
|
623
|
+
const finalState = await watchRun(runId, { auto: false, pollMs: args.pollMs, quiet: args.json });
|
|
624
|
+
if (isFailureTerminal(finalState)) process.exitCode = 1;
|
|
625
|
+
if (args.json) printJson({ ok: true, command: 'status', run: summaryOfRun(finalState) });
|
|
626
|
+
return;
|
|
627
|
+
}
|
|
628
|
+
const state = await refreshRun(runId);
|
|
629
|
+
if (!state) throw new Error(`run not found: ${runId}`);
|
|
630
|
+
if (args.json) { printJson({ ok: true, command: 'status', run: summaryOfRun(state) }); return; }
|
|
631
|
+
printRunStatus(state);
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
async function readFinalResult(runId) {
|
|
635
|
+
const { loadRun, readRunFile } = await import('../src/orchestrator.js');
|
|
636
|
+
const state = await loadRun(runId);
|
|
637
|
+
if (!state) throw new Error(`run not found: ${runId}`);
|
|
638
|
+
try {
|
|
639
|
+
const text = await readRunFile(runId, 'judge', 'verdict.json');
|
|
640
|
+
let parsed = null;
|
|
641
|
+
try { parsed = JSON.parse(text); } catch {}
|
|
642
|
+
return { state, source: 'judge/verdict.json', text, parsed };
|
|
643
|
+
}
|
|
644
|
+
catch {}
|
|
645
|
+
try {
|
|
646
|
+
const text = await readRunFile(runId, 'judge', 'last_message.md');
|
|
647
|
+
return { state, source: 'judge/last_message.md', text, parsed: null };
|
|
648
|
+
}
|
|
649
|
+
catch {}
|
|
650
|
+
throw new Error(`最终结果尚未生成:当前状态 ${displayStatus(state.status)}`);
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
function clipboardCommands() {
|
|
654
|
+
if (process.platform === 'darwin') return [['pbcopy', []]];
|
|
655
|
+
if (process.platform === 'win32') return [['clip', []]];
|
|
656
|
+
return [['wl-copy', []], ['xclip', ['-selection', 'clipboard']], ['xsel', ['--clipboard', '--input']]];
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
async function copyToClipboard(text) {
|
|
660
|
+
let lastError = null;
|
|
661
|
+
for (const [command, args] of clipboardCommands()) {
|
|
662
|
+
try {
|
|
663
|
+
await new Promise((resolve, reject) => {
|
|
664
|
+
const child = spawn(command, args, { stdio: ['pipe', 'ignore', 'ignore'] });
|
|
665
|
+
child.on('error', reject);
|
|
666
|
+
child.on('exit', code => code === 0 ? resolve() : reject(new Error(`${command} exited with ${code}`)));
|
|
667
|
+
child.stdin.end(text);
|
|
668
|
+
});
|
|
669
|
+
return;
|
|
670
|
+
} catch (error) {
|
|
671
|
+
lastError = error;
|
|
672
|
+
}
|
|
673
|
+
}
|
|
674
|
+
throw new Error(`无法复制到剪贴板:${lastError?.message || '未找到可用剪贴板命令'}`);
|
|
675
|
+
}
|
|
676
|
+
|
|
677
|
+
async function result(args) {
|
|
678
|
+
applyRuntimeEnv(args);
|
|
679
|
+
const runId = args.runId || await latestRunId();
|
|
680
|
+
const { state, source, text, parsed } = await readFinalResult(runId);
|
|
681
|
+
const { summaryOfRun } = await import('../src/orchestrator.js');
|
|
682
|
+
if (args.copy) {
|
|
683
|
+
await copyToClipboard(text);
|
|
684
|
+
if (args.json) { printJson({ ok: true, command: 'result', run: summaryOfRun(state), source, copied: true }); return; }
|
|
685
|
+
console.log(`已复制最终结果: ${runId}`);
|
|
686
|
+
return;
|
|
687
|
+
}
|
|
688
|
+
if (args.json) {
|
|
689
|
+
printJson({ ok: true, command: 'result', run: summaryOfRun(state), source, result: parsed, text: parsed ? null : text });
|
|
690
|
+
return;
|
|
691
|
+
}
|
|
692
|
+
console.log(text);
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
async function retry(args) {
|
|
696
|
+
applyRuntimeEnv(args);
|
|
697
|
+
if (!args.runId) throw new Error('retry requires a runId');
|
|
698
|
+
const { retryRun, summaryOfRun } = await import('../src/orchestrator.js');
|
|
699
|
+
const state = await retryRun(args.runId, { taskId: args.taskId, reason: args.reason, maxRetries: args.maxRetries });
|
|
700
|
+
if (args.json) { printJson({ ok: true, command: 'retry', run: summaryOfRun(state), retriedTaskIds: state.retriedTaskIds || [] }); return; }
|
|
701
|
+
console.log(`已重试任务: ${(state.retriedTaskIds || []).join(', ') || '-'}`);
|
|
702
|
+
}
|
|
703
|
+
|
|
704
|
+
async function stop(args) {
|
|
705
|
+
applyRuntimeEnv(args);
|
|
706
|
+
if (!args.runId) throw new Error('stop requires a runId');
|
|
707
|
+
const { stopRun, summaryOfRun } = await import('../src/orchestrator.js');
|
|
708
|
+
const state = await stopRun(args.runId, { reason: args.reason });
|
|
709
|
+
if (args.json) { printJson({ ok: true, command: 'stop', run: summaryOfRun(state), reason: args.reason }); return; }
|
|
710
|
+
console.log(`已停止任务批次: ${state.runId}`);
|
|
711
|
+
}
|
|
712
|
+
|
|
713
|
+
async function autoRun(args) {
|
|
714
|
+
applyRuntimeEnv(args);
|
|
715
|
+
if (!args.runId) throw new Error('auto requires a runId');
|
|
716
|
+
const { loadRun, startPlanner, summaryOfRun } = await import('../src/orchestrator.js');
|
|
717
|
+
const state = await loadRun(args.runId);
|
|
718
|
+
if (!state) throw new Error(`run not found: ${args.runId}`);
|
|
719
|
+
if (state.status === 'created') await startPlanner(args.runId);
|
|
720
|
+
const finalState = await watchRun(args.runId, { auto: true, pollMs: args.pollMs, quiet: args.json, maxRetries: args.maxRetries });
|
|
721
|
+
if (isFailureTerminal(finalState)) process.exitCode = 1;
|
|
722
|
+
if (args.json) { printJson({ ok: true, command: 'auto', run: summaryOfRun(finalState) }); return; }
|
|
723
|
+
}
|
|
724
|
+
|
|
725
|
+
async function submit(args) {
|
|
726
|
+
if (args.detach && !args.auto) throw new Error('--detach requires auto mode; remove --no-auto');
|
|
727
|
+
applyRuntimeEnv(args);
|
|
728
|
+
const taskText = await readTaskText(args);
|
|
729
|
+
const { createRun, startPlanner, summaryOfRun } = await import('../src/orchestrator.js');
|
|
730
|
+
const state = await createRun({
|
|
731
|
+
label: args.label,
|
|
732
|
+
taskText,
|
|
733
|
+
repo: process.env.KANBAN_DEFAULT_REPO,
|
|
734
|
+
maxParallel: args.maxParallel,
|
|
735
|
+
workerSandbox: args.workerSandbox
|
|
736
|
+
});
|
|
737
|
+
if (!args.json) {
|
|
738
|
+
console.log(`已创建任务批次: ${state.runId}`);
|
|
739
|
+
console.log(`看板地址: ${webUrl(args, state.runId)}`);
|
|
740
|
+
console.log(`终端查看: input-kanban status ${state.runId} --watch`);
|
|
741
|
+
}
|
|
742
|
+
if (args.detach) {
|
|
743
|
+
const pid = startDetachedAuto(state.runId, args);
|
|
744
|
+
if (args.json) { printJson({ ok: true, command: 'submit', phase: 'detached', url: baseUrl(args), supervisorPid: pid, run: summaryOfRun(state) }); return; }
|
|
745
|
+
console.log(`后台执行中: supervisor pid ${pid}`);
|
|
746
|
+
return;
|
|
747
|
+
}
|
|
748
|
+
if (!args.json) console.log('发起任务拆分...');
|
|
749
|
+
const plannedState = await startPlanner(state.runId);
|
|
750
|
+
if (!args.watch && !args.auto) {
|
|
751
|
+
if (args.json) { printJson({ ok: true, command: 'submit', phase: 'planned', url: baseUrl(args), auto: args.auto, watch: args.watch, run: summaryOfRun(plannedState || state) }); }
|
|
752
|
+
return;
|
|
753
|
+
}
|
|
754
|
+
const finalState = await watchRun(state.runId, { auto: args.auto, pollMs: args.pollMs, quiet: args.json, maxRetries: args.maxRetries });
|
|
755
|
+
if (isFailureTerminal(finalState)) process.exitCode = 1;
|
|
756
|
+
if (args.json) { printJson({ ok: true, command: 'submit', phase: 'final', url: baseUrl(args), auto: args.auto, watch: args.watch, run: summaryOfRun(finalState) }); return; }
|
|
757
|
+
console.log(`最终状态: ${finalState.status}`);
|
|
758
|
+
}
|
|
759
|
+
|
|
760
|
+
try {
|
|
761
|
+
const { command, rest, globals = {} } = splitCommand(process.argv.slice(2));
|
|
762
|
+
if (command === 'serve') {
|
|
763
|
+
const args = parseServeArgs(rest);
|
|
764
|
+
args.json = args.json || globals.json;
|
|
765
|
+
if (args.help) { printHelp(); process.exit(0); }
|
|
766
|
+
await serve(args);
|
|
767
|
+
} else if (command === 'version') {
|
|
768
|
+
printVersion();
|
|
769
|
+
} else if (command === 'submit') {
|
|
770
|
+
const args = parseSubmitArgs(rest);
|
|
771
|
+
args.json = args.json || globals.json;
|
|
772
|
+
if (args.help) { printSubmitHelp(); process.exit(0); }
|
|
773
|
+
await submit(args);
|
|
774
|
+
} else if (command === 'runs') {
|
|
775
|
+
const args = parseRunsArgs(rest);
|
|
776
|
+
args.json = args.json || globals.json;
|
|
777
|
+
if (args.help) { printRunsHelp(); process.exit(0); }
|
|
778
|
+
await runs(args);
|
|
779
|
+
} else if (command === 'status') {
|
|
780
|
+
const args = parseStatusArgs(rest);
|
|
781
|
+
args.json = args.json || globals.json;
|
|
782
|
+
if (args.help) { printStatusHelp(); process.exit(0); }
|
|
783
|
+
await status(args);
|
|
784
|
+
} else if (command === 'result') {
|
|
785
|
+
const args = parseResultArgs(rest);
|
|
786
|
+
args.json = args.json || globals.json;
|
|
787
|
+
if (args.help) { printResultHelp(); process.exit(0); }
|
|
788
|
+
await result(args);
|
|
789
|
+
} else if (command === 'retry') {
|
|
790
|
+
const args = parseRetryArgs(rest);
|
|
791
|
+
args.json = args.json || globals.json;
|
|
792
|
+
if (args.help) { printRetryHelp(); process.exit(0); }
|
|
793
|
+
await retry(args);
|
|
794
|
+
} else if (command === 'stop') {
|
|
795
|
+
const args = parseStopArgs(rest);
|
|
796
|
+
args.json = args.json || globals.json;
|
|
797
|
+
if (args.help) { printStopHelp(); process.exit(0); }
|
|
798
|
+
await stop(args);
|
|
799
|
+
} else if (command === 'auto') {
|
|
800
|
+
const args = parseAutoArgs(rest);
|
|
801
|
+
args.json = args.json || globals.json;
|
|
802
|
+
if (args.help) { printAutoHelp(); process.exit(0); }
|
|
803
|
+
await autoRun(args);
|
|
804
|
+
}
|
|
79
805
|
} catch (error) {
|
|
80
806
|
console.error(error.message || String(error));
|
|
81
807
|
process.exit(1);
|