steroids-cli 0.8.24 → 0.8.26
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/dist/commands/ai.js +7 -6
- package/dist/commands/ai.js.map +1 -1
- package/dist/commands/completion.d.ts.map +1 -1
- package/dist/commands/completion.js +1 -0
- package/dist/commands/completion.js.map +1 -1
- package/dist/commands/config.js +10 -7
- package/dist/commands/config.js.map +1 -1
- package/dist/commands/llm.d.ts.map +1 -1
- package/dist/commands/llm.js +5 -0
- package/dist/commands/llm.js.map +1 -1
- package/dist/commands/merge.d.ts +3 -0
- package/dist/commands/merge.d.ts.map +1 -0
- package/dist/commands/merge.js +234 -0
- package/dist/commands/merge.js.map +1 -0
- package/dist/commands/runners-list.d.ts +3 -0
- package/dist/commands/runners-list.d.ts.map +1 -0
- package/dist/commands/runners-list.js +287 -0
- package/dist/commands/runners-list.js.map +1 -0
- package/dist/commands/runners-logs.d.ts +2 -0
- package/dist/commands/runners-logs.d.ts.map +1 -0
- package/dist/commands/runners-logs.js +207 -0
- package/dist/commands/runners-logs.js.map +1 -0
- package/dist/commands/runners-management.d.ts +3 -0
- package/dist/commands/runners-management.d.ts.map +1 -0
- package/dist/commands/runners-management.js +205 -0
- package/dist/commands/runners-management.js.map +1 -0
- package/dist/commands/runners-parallel.d.ts +26 -0
- package/dist/commands/runners-parallel.d.ts.map +1 -0
- package/dist/commands/runners-parallel.js +229 -0
- package/dist/commands/runners-parallel.js.map +1 -0
- package/dist/commands/runners-wakeup.d.ts +4 -0
- package/dist/commands/runners-wakeup.d.ts.map +1 -0
- package/dist/commands/runners-wakeup.js +157 -0
- package/dist/commands/runners-wakeup.js.map +1 -0
- package/dist/commands/runners.d.ts.map +1 -1
- package/dist/commands/runners.js +115 -786
- package/dist/commands/runners.js.map +1 -1
- package/dist/commands/workspaces.d.ts +3 -0
- package/dist/commands/workspaces.d.ts.map +1 -0
- package/dist/commands/workspaces.js +409 -0
- package/dist/commands/workspaces.js.map +1 -0
- package/dist/config/ai-setup.d.ts +1 -1
- package/dist/config/ai-setup.d.ts.map +1 -1
- package/dist/config/ai-setup.js +1 -0
- package/dist/config/ai-setup.js.map +1 -1
- package/dist/config/loader.d.ts +10 -3
- package/dist/config/loader.d.ts.map +1 -1
- package/dist/config/loader.js +7 -0
- package/dist/config/loader.js.map +1 -1
- package/dist/config/schema.d.ts.map +1 -1
- package/dist/config/schema.js +32 -3
- package/dist/config/schema.js.map +1 -1
- package/dist/index.js +10 -0
- package/dist/index.js.map +1 -1
- package/dist/providers/api-models.d.ts +11 -6
- package/dist/providers/api-models.d.ts.map +1 -1
- package/dist/providers/api-models.js +80 -0
- package/dist/providers/api-models.js.map +1 -1
- package/dist/providers/index.d.ts +2 -1
- package/dist/providers/index.d.ts.map +1 -1
- package/dist/providers/index.js +6 -1
- package/dist/providers/index.js.map +1 -1
- package/dist/providers/mistral.d.ts +58 -0
- package/dist/providers/mistral.d.ts.map +1 -0
- package/dist/providers/mistral.js +310 -0
- package/dist/providers/mistral.js.map +1 -0
- package/dist/providers/registry.d.ts.map +1 -1
- package/dist/providers/registry.js +2 -0
- package/dist/providers/registry.js.map +1 -1
- package/dist/runners/cron.d.ts.map +1 -1
- package/dist/runners/cron.js +9 -0
- package/dist/runners/cron.js.map +1 -1
- package/dist/runners/wakeup.d.ts +1 -0
- package/dist/runners/wakeup.d.ts.map +1 -1
- package/dist/runners/wakeup.js +27 -0
- package/dist/runners/wakeup.js.map +1 -1
- package/package.json +1 -1
package/dist/commands/runners.js
CHANGED
|
@@ -38,21 +38,16 @@ exports.runnersCommand = runnersCommand;
|
|
|
38
38
|
* steroids runners - Manage runner daemons
|
|
39
39
|
*/
|
|
40
40
|
const node_util_1 = require("node:util");
|
|
41
|
-
const node_child_process_1 = require("node:child_process");
|
|
42
|
-
const fs = __importStar(require("node:fs"));
|
|
43
41
|
const path = __importStar(require("node:path"));
|
|
44
|
-
const os = __importStar(require("node:os"));
|
|
45
|
-
const loader_js_1 = require("../config/loader.js");
|
|
46
42
|
const daemon_js_1 = require("../runners/daemon.js");
|
|
47
|
-
const lock_js_1 = require("../runners/lock.js");
|
|
48
|
-
const wakeup_js_1 = require("../runners/wakeup.js");
|
|
49
|
-
const cron_js_1 = require("../runners/cron.js");
|
|
50
43
|
const connection_js_1 = require("../database/connection.js");
|
|
51
44
|
const queries_js_1 = require("../database/queries.js");
|
|
52
|
-
const node_fs_1 = require("node:fs");
|
|
53
|
-
const node_path_1 = require("node:path");
|
|
54
|
-
const projects_js_1 = require("../runners/projects.js");
|
|
55
45
|
const help_js_1 = require("../cli/help.js");
|
|
46
|
+
const runners_parallel_js_1 = require("./runners-parallel.js");
|
|
47
|
+
const runners_wakeup_js_1 = require("./runners-wakeup.js");
|
|
48
|
+
const runners_list_js_1 = require("./runners-list.js");
|
|
49
|
+
const runners_logs_js_1 = require("./runners-logs.js");
|
|
50
|
+
const runners_management_js_1 = require("./runners-management.js");
|
|
56
51
|
const HELP = (0, help_js_1.generateHelp)({
|
|
57
52
|
command: 'runners',
|
|
58
53
|
description: 'Manage background runner daemons for automated task execution',
|
|
@@ -73,6 +68,9 @@ Runners can be started manually or managed automatically via cron.`,
|
|
|
73
68
|
{ long: 'detach', description: 'Run in background (daemonize) - start subcommand' },
|
|
74
69
|
{ long: 'project', description: 'Project path to work on', values: '<path>' },
|
|
75
70
|
{ long: 'section', description: 'Focus on specific section only', values: '<id|name>' },
|
|
71
|
+
{ long: 'parallel', description: 'Run independent sections across multiple clones' },
|
|
72
|
+
{ long: 'max', description: 'Limit number of concurrent workstreams', values: '<n>' },
|
|
73
|
+
{ long: 'dry-run', description: 'Analyze plan and exit without cloning or spawning' },
|
|
76
74
|
{ long: 'id', description: 'Stop specific runner by ID - stop subcommand', values: '<id>' },
|
|
77
75
|
{ long: 'all', description: 'Stop all runners - stop subcommand' },
|
|
78
76
|
{ long: 'tree', description: 'Show tree view with projects/runners/tasks - list subcommand' },
|
|
@@ -83,6 +81,9 @@ Runners can be started manually or managed automatically via cron.`,
|
|
|
83
81
|
examples: [
|
|
84
82
|
{ command: 'steroids runners start', description: 'Start in foreground' },
|
|
85
83
|
{ command: 'steroids runners start --detach', description: 'Start in background' },
|
|
84
|
+
{ command: 'steroids runners start --parallel', description: 'Analyze and run independent workstreams in parallel clones' },
|
|
85
|
+
{ command: 'steroids runners start --parallel --max 2', description: 'Run up to 2 workstreams concurrently' },
|
|
86
|
+
{ command: 'steroids runners start --parallel --dry-run', description: 'Show planned parallel workstreams and exit' },
|
|
86
87
|
{ command: 'steroids runners start --section "Phase 2"', description: 'Focus on specific section' },
|
|
87
88
|
{ command: 'steroids runners stop', description: 'Stop runner for current project' },
|
|
88
89
|
{ command: 'steroids runners stop --all', description: 'Stop all runners' },
|
|
@@ -119,7 +120,7 @@ async function runnersCommand(args, flags) {
|
|
|
119
120
|
const subArgs = args.slice(1);
|
|
120
121
|
switch (subcommand) {
|
|
121
122
|
case 'start':
|
|
122
|
-
await runStart(subArgs);
|
|
123
|
+
await runStart(subArgs, flags);
|
|
123
124
|
break;
|
|
124
125
|
case 'stop':
|
|
125
126
|
await runStop(subArgs);
|
|
@@ -128,16 +129,16 @@ async function runnersCommand(args, flags) {
|
|
|
128
129
|
await runStatus(subArgs);
|
|
129
130
|
break;
|
|
130
131
|
case 'list':
|
|
131
|
-
await runList(subArgs, flags);
|
|
132
|
+
await (0, runners_list_js_1.runList)(subArgs, flags);
|
|
132
133
|
break;
|
|
133
134
|
case 'logs':
|
|
134
|
-
await runLogs(subArgs);
|
|
135
|
+
await (0, runners_logs_js_1.runLogs)(subArgs);
|
|
135
136
|
break;
|
|
136
137
|
case 'wakeup':
|
|
137
|
-
await runWakeup(subArgs, flags);
|
|
138
|
+
await (0, runners_wakeup_js_1.runWakeup)(subArgs, flags);
|
|
138
139
|
break;
|
|
139
140
|
case 'cron':
|
|
140
|
-
await runCron(subArgs);
|
|
141
|
+
await (0, runners_wakeup_js_1.runCron)(subArgs);
|
|
141
142
|
break;
|
|
142
143
|
default:
|
|
143
144
|
console.error(`Unknown subcommand: ${subcommand}`);
|
|
@@ -145,7 +146,7 @@ async function runnersCommand(args, flags) {
|
|
|
145
146
|
process.exit(1);
|
|
146
147
|
}
|
|
147
148
|
}
|
|
148
|
-
async function runStart(args) {
|
|
149
|
+
async function runStart(args, flags) {
|
|
149
150
|
const { values } = (0, node_util_1.parseArgs)({
|
|
150
151
|
args,
|
|
151
152
|
options: {
|
|
@@ -154,6 +155,11 @@ async function runStart(args) {
|
|
|
154
155
|
detach: { type: 'boolean', short: 'd', default: false },
|
|
155
156
|
project: { type: 'string', short: 'p' },
|
|
156
157
|
section: { type: 'string' },
|
|
158
|
+
parallel: { type: 'boolean', default: false },
|
|
159
|
+
max: { type: 'string' },
|
|
160
|
+
'section-ids': { type: 'string' },
|
|
161
|
+
branch: { type: 'string' },
|
|
162
|
+
'parallel-session-id': { type: 'string' },
|
|
157
163
|
},
|
|
158
164
|
allowPositionals: false,
|
|
159
165
|
});
|
|
@@ -164,22 +170,38 @@ steroids runners start - Start runner daemon
|
|
|
164
170
|
USAGE:
|
|
165
171
|
steroids runners start [options]
|
|
166
172
|
|
|
167
|
-
OPTIONS:
|
|
173
|
+
OPTIONS:
|
|
168
174
|
--detach Run in background
|
|
169
175
|
--project <path> Project path
|
|
170
176
|
--section <id|name> Focus on a specific section only
|
|
177
|
+
--parallel Analyze dependency graph and run independent workstreams in parallel clones
|
|
178
|
+
--max <n> Limit number of parallel workstreams (overrides runners.parallel.maxClones)
|
|
171
179
|
-j, --json Output as JSON
|
|
172
180
|
-h, --help Show help
|
|
181
|
+
--dry-run Print analysis plan and exit
|
|
173
182
|
`);
|
|
174
183
|
return;
|
|
175
184
|
}
|
|
185
|
+
const sectionIdsOption = values['section-ids'];
|
|
186
|
+
const parallelSessionId = values['parallel-session-id'];
|
|
187
|
+
const asJson = values.json || flags.json;
|
|
188
|
+
if (values.parallel && values.section) {
|
|
189
|
+
const errorMsg = '--parallel cannot be combined with --section';
|
|
190
|
+
if (asJson) {
|
|
191
|
+
console.log(JSON.stringify({ success: false, error: errorMsg }));
|
|
192
|
+
}
|
|
193
|
+
else {
|
|
194
|
+
console.error(errorMsg);
|
|
195
|
+
}
|
|
196
|
+
process.exit(1);
|
|
197
|
+
}
|
|
176
198
|
// Check if we can start
|
|
177
199
|
// Default to cwd if --project not specified, to ensure proper per-project tracking
|
|
178
200
|
// Always resolve to absolute path for consistent tracking across processes
|
|
179
201
|
const projectPath = path.resolve(values.project ?? process.cwd());
|
|
180
202
|
const check = (0, daemon_js_1.canStartDaemon)(projectPath);
|
|
181
203
|
if (!check.canStart && !check.reason?.includes('zombie')) {
|
|
182
|
-
if (
|
|
204
|
+
if (asJson) {
|
|
183
205
|
console.log(JSON.stringify({
|
|
184
206
|
success: false,
|
|
185
207
|
error: check.reason,
|
|
@@ -194,9 +216,36 @@ OPTIONS:
|
|
|
194
216
|
}
|
|
195
217
|
process.exit(6);
|
|
196
218
|
}
|
|
219
|
+
const runFromDetachedParent = values.detach && !values.parallel && !parallelSessionId && !sectionIdsOption;
|
|
220
|
+
const sectionIdsFromSpawn = typeof sectionIdsOption === 'string'
|
|
221
|
+
? (0, runners_parallel_js_1.parseSectionIds)(sectionIdsOption)
|
|
222
|
+
: [];
|
|
223
|
+
// Internal parallel runner invocation used by this command when spawning workspace runners.
|
|
224
|
+
if (values.parallel
|
|
225
|
+
&& sectionIdsOption !== undefined
|
|
226
|
+
&& parallelSessionId
|
|
227
|
+
&& values.branch) {
|
|
228
|
+
if (sectionIdsFromSpawn.length === 0) {
|
|
229
|
+
const errorMsg = 'Internal parallel runner received empty section ids';
|
|
230
|
+
if (asJson) {
|
|
231
|
+
console.log(JSON.stringify({ success: false, error: errorMsg }));
|
|
232
|
+
}
|
|
233
|
+
else {
|
|
234
|
+
console.error(errorMsg);
|
|
235
|
+
}
|
|
236
|
+
process.exit(1);
|
|
237
|
+
}
|
|
238
|
+
await (0, daemon_js_1.startDaemon)({
|
|
239
|
+
projectPath,
|
|
240
|
+
sectionIds: sectionIdsFromSpawn,
|
|
241
|
+
branchName: values.branch,
|
|
242
|
+
parallelSessionId,
|
|
243
|
+
});
|
|
244
|
+
return;
|
|
245
|
+
}
|
|
197
246
|
// Resolve section if --section flag is provided
|
|
198
247
|
let focusedSectionId;
|
|
199
|
-
if (values.section) {
|
|
248
|
+
if (!values.parallel && values.section) {
|
|
200
249
|
const sectionInput = values.section;
|
|
201
250
|
const { db, close } = (0, connection_js_1.openDatabase)(projectPath);
|
|
202
251
|
try {
|
|
@@ -274,812 +323,92 @@ OPTIONS:
|
|
|
274
323
|
close();
|
|
275
324
|
}
|
|
276
325
|
}
|
|
277
|
-
if (
|
|
326
|
+
if (runFromDetachedParent) {
|
|
278
327
|
// Spawn detached process - always pass --project for proper tracking
|
|
279
328
|
const spawnArgs = [process.argv[1], 'runners', 'start', '--project', projectPath];
|
|
280
329
|
// Pass --section if specified
|
|
281
330
|
if (values.section) {
|
|
282
331
|
spawnArgs.push('--section', values.section);
|
|
283
332
|
}
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
let logFile;
|
|
288
|
-
let logFd;
|
|
289
|
-
if (daemonLogsEnabled) {
|
|
290
|
-
// Create logs directory and log file for daemon output
|
|
291
|
-
const logsDir = path.join(os.homedir(), '.steroids', 'runners', 'logs');
|
|
292
|
-
fs.mkdirSync(logsDir, { recursive: true });
|
|
293
|
-
// Use timestamp for now, will rename after we have PID
|
|
294
|
-
const tempLogPath = path.join(logsDir, `daemon-${Date.now()}.log`);
|
|
295
|
-
logFd = fs.openSync(tempLogPath, 'a');
|
|
296
|
-
logFile = tempLogPath;
|
|
297
|
-
}
|
|
298
|
-
const child = (0, node_child_process_1.spawn)(process.execPath, spawnArgs, {
|
|
299
|
-
detached: true,
|
|
300
|
-
stdio: daemonLogsEnabled && logFd !== undefined
|
|
301
|
-
? ['ignore', logFd, logFd]
|
|
302
|
-
: 'ignore',
|
|
333
|
+
const { pid, logFile: finalLogPath, } = (0, runners_parallel_js_1.spawnDetachedRunner)({
|
|
334
|
+
projectPath,
|
|
335
|
+
args: spawnArgs,
|
|
303
336
|
});
|
|
304
|
-
|
|
305
|
-
// Clean up file descriptor and rename log file
|
|
306
|
-
if (logFd !== undefined) {
|
|
307
|
-
fs.closeSync(logFd);
|
|
308
|
-
}
|
|
309
|
-
let finalLogPath;
|
|
310
|
-
if (logFile && child.pid) {
|
|
311
|
-
const logsDir = path.dirname(logFile);
|
|
312
|
-
finalLogPath = path.join(logsDir, `daemon-${child.pid}.log`);
|
|
313
|
-
try {
|
|
314
|
-
fs.renameSync(logFile, finalLogPath);
|
|
315
|
-
}
|
|
316
|
-
catch {
|
|
317
|
-
finalLogPath = logFile; // Keep temp name if rename fails
|
|
318
|
-
}
|
|
319
|
-
}
|
|
320
|
-
if (values.json) {
|
|
337
|
+
if (asJson) {
|
|
321
338
|
console.log(JSON.stringify({
|
|
322
339
|
success: true,
|
|
323
|
-
pid
|
|
340
|
+
pid,
|
|
324
341
|
detached: true,
|
|
325
342
|
logFile: finalLogPath,
|
|
326
343
|
}));
|
|
327
344
|
}
|
|
328
345
|
else {
|
|
329
|
-
console.log(`Runner started in background (PID: ${
|
|
346
|
+
console.log(`Runner started in background (PID: ${pid})`);
|
|
330
347
|
if (finalLogPath) {
|
|
331
348
|
console.log(` Log file: ${finalLogPath}`);
|
|
332
349
|
}
|
|
333
350
|
}
|
|
334
351
|
return;
|
|
335
352
|
}
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
json: { type: 'boolean', short: 'j', default: false },
|
|
345
|
-
id: { type: 'string' },
|
|
346
|
-
all: { type: 'boolean', default: false },
|
|
347
|
-
},
|
|
348
|
-
allowPositionals: false,
|
|
349
|
-
});
|
|
350
|
-
if (values.help) {
|
|
351
|
-
console.log(`
|
|
352
|
-
steroids runners stop - Stop runner(s)
|
|
353
|
-
|
|
354
|
-
USAGE:
|
|
355
|
-
steroids runners stop [options]
|
|
356
|
-
|
|
357
|
-
OPTIONS:
|
|
358
|
-
--id <id> Stop specific runner
|
|
359
|
-
--all Stop all runners
|
|
360
|
-
-j, --json Output as JSON
|
|
361
|
-
-h, --help Show help
|
|
362
|
-
`);
|
|
363
|
-
return;
|
|
364
|
-
}
|
|
365
|
-
// Capture stop context for logging
|
|
366
|
-
const stopContext = {
|
|
367
|
-
calledFrom: process.cwd(),
|
|
368
|
-
callerPid: process.pid,
|
|
369
|
-
timestamp: new Date().toISOString(),
|
|
370
|
-
user: process.env.USER || process.env.USERNAME || 'unknown',
|
|
371
|
-
args: {
|
|
372
|
-
id: values.id,
|
|
373
|
-
all: values.all,
|
|
374
|
-
},
|
|
375
|
-
};
|
|
376
|
-
const runners = (0, daemon_js_1.listRunners)();
|
|
377
|
-
let stopped = 0;
|
|
378
|
-
const stoppedRunners = [];
|
|
379
|
-
const runnersToStop = values.id
|
|
380
|
-
? runners.filter((r) => r.id === values.id || r.id.startsWith(values.id))
|
|
381
|
-
: values.all
|
|
382
|
-
? runners
|
|
383
|
-
: runners.filter((r) => r.pid === process.pid || r.pid !== null);
|
|
384
|
-
// Log stop action to daemon logs
|
|
385
|
-
const logsDir = path.join(os.homedir(), '.steroids', 'runners', 'logs');
|
|
386
|
-
const stopLogPath = path.join(logsDir, 'stop-audit.log');
|
|
387
|
-
fs.mkdirSync(logsDir, { recursive: true });
|
|
388
|
-
for (const runner of runnersToStop) {
|
|
389
|
-
if (runner.pid && (0, lock_js_1.isProcessAlive)(runner.pid)) {
|
|
390
|
-
try {
|
|
391
|
-
process.kill(runner.pid, 'SIGTERM');
|
|
392
|
-
stopped++;
|
|
393
|
-
stoppedRunners.push({
|
|
394
|
-
id: runner.id,
|
|
395
|
-
pid: runner.pid,
|
|
396
|
-
project: runner.project_path,
|
|
397
|
-
});
|
|
398
|
-
// Log each stop to audit log
|
|
399
|
-
const logEntry = {
|
|
400
|
-
...stopContext,
|
|
401
|
-
action: 'stop',
|
|
402
|
-
runner: {
|
|
403
|
-
id: runner.id,
|
|
404
|
-
pid: runner.pid,
|
|
405
|
-
project: runner.project_path,
|
|
406
|
-
},
|
|
407
|
-
};
|
|
408
|
-
fs.appendFileSync(stopLogPath, JSON.stringify(logEntry) + '\n');
|
|
409
|
-
}
|
|
410
|
-
catch {
|
|
411
|
-
// Process already dead
|
|
412
|
-
}
|
|
413
|
-
}
|
|
414
|
-
(0, daemon_js_1.unregisterRunner)(runner.id);
|
|
415
|
-
}
|
|
416
|
-
if (values.json) {
|
|
417
|
-
console.log(JSON.stringify({
|
|
418
|
-
success: true,
|
|
419
|
-
stopped,
|
|
420
|
-
stoppedRunners,
|
|
421
|
-
context: stopContext,
|
|
422
|
-
}));
|
|
423
|
-
}
|
|
424
|
-
else {
|
|
425
|
-
console.log(`Stopped ${stopped} runner(s)`);
|
|
426
|
-
if (stopped > 0 && !values.all) {
|
|
427
|
-
console.log(` Called from: ${stopContext.calledFrom}`);
|
|
428
|
-
console.log(` Audit log: ${stopLogPath}`);
|
|
429
|
-
}
|
|
430
|
-
}
|
|
431
|
-
}
|
|
432
|
-
async function runStatus(args) {
|
|
433
|
-
const { values } = (0, node_util_1.parseArgs)({
|
|
434
|
-
args,
|
|
435
|
-
options: {
|
|
436
|
-
help: { type: 'boolean', short: 'h', default: false },
|
|
437
|
-
json: { type: 'boolean', short: 'j', default: false },
|
|
438
|
-
},
|
|
439
|
-
allowPositionals: false,
|
|
440
|
-
});
|
|
441
|
-
if (values.help) {
|
|
442
|
-
console.log(`
|
|
443
|
-
steroids runners status - Show runner status
|
|
444
|
-
|
|
445
|
-
USAGE:
|
|
446
|
-
steroids runners status [options]
|
|
447
|
-
|
|
448
|
-
OPTIONS:
|
|
449
|
-
-j, --json Output as JSON
|
|
450
|
-
-h, --help Show help
|
|
451
|
-
`);
|
|
452
|
-
return;
|
|
453
|
-
}
|
|
454
|
-
const lockStatus = (0, lock_js_1.checkLockStatus)();
|
|
455
|
-
const runners = (0, daemon_js_1.listRunners)();
|
|
456
|
-
const activeRunner = runners.find((r) => r.pid && (0, lock_js_1.isProcessAlive)(r.pid));
|
|
457
|
-
const status = {
|
|
458
|
-
locked: lockStatus.locked,
|
|
459
|
-
lockPid: lockStatus.pid,
|
|
460
|
-
isZombie: lockStatus.isZombie,
|
|
461
|
-
activeRunner: activeRunner
|
|
462
|
-
? {
|
|
463
|
-
id: activeRunner.id,
|
|
464
|
-
pid: activeRunner.pid,
|
|
465
|
-
status: activeRunner.status,
|
|
466
|
-
project: activeRunner.project_path,
|
|
467
|
-
currentTask: activeRunner.current_task_id,
|
|
468
|
-
heartbeat: activeRunner.heartbeat_at,
|
|
469
|
-
}
|
|
470
|
-
: null,
|
|
471
|
-
totalRunners: runners.length,
|
|
472
|
-
};
|
|
473
|
-
if (values.json) {
|
|
474
|
-
console.log(JSON.stringify(status, null, 2));
|
|
475
|
-
return;
|
|
476
|
-
}
|
|
477
|
-
if (activeRunner) {
|
|
478
|
-
console.log(`Runner Status: ACTIVE`);
|
|
479
|
-
console.log(` ID: ${activeRunner.id}`);
|
|
480
|
-
console.log(` PID: ${activeRunner.pid}`);
|
|
481
|
-
console.log(` Status: ${activeRunner.status}`);
|
|
482
|
-
if (activeRunner.project_path) {
|
|
483
|
-
console.log(` Project: ${activeRunner.project_path}`);
|
|
484
|
-
}
|
|
485
|
-
if (activeRunner.current_task_id) {
|
|
486
|
-
console.log(` Current Task: ${activeRunner.current_task_id}`);
|
|
487
|
-
}
|
|
488
|
-
console.log(` Last Heartbeat: ${activeRunner.heartbeat_at}`);
|
|
489
|
-
}
|
|
490
|
-
else if (lockStatus.isZombie) {
|
|
491
|
-
console.log(`Runner Status: ZOMBIE`);
|
|
492
|
-
console.log(` Lock exists but process (PID: ${lockStatus.pid}) is dead`);
|
|
493
|
-
console.log(` Run 'steroids runners wakeup' to clean up`);
|
|
494
|
-
}
|
|
495
|
-
else {
|
|
496
|
-
console.log(`Runner Status: INACTIVE`);
|
|
497
|
-
console.log(` No runner is currently active`);
|
|
498
|
-
}
|
|
499
|
-
}
|
|
500
|
-
async function runList(args, flags) {
|
|
501
|
-
const { values } = (0, node_util_1.parseArgs)({
|
|
502
|
-
args,
|
|
503
|
-
options: {
|
|
504
|
-
help: { type: 'boolean', short: 'h', default: false },
|
|
505
|
-
tree: { type: 'boolean', short: 't', default: false },
|
|
506
|
-
},
|
|
507
|
-
allowPositionals: false,
|
|
508
|
-
});
|
|
509
|
-
if (values.help || flags.help) {
|
|
510
|
-
console.log(`
|
|
511
|
-
steroids runners list - List all runners
|
|
512
|
-
|
|
513
|
-
USAGE:
|
|
514
|
-
steroids runners list [options]
|
|
515
|
-
|
|
516
|
-
OPTIONS:
|
|
517
|
-
-t, --tree Show tree view with tasks
|
|
518
|
-
-j, --json Output as JSON (global flag)
|
|
519
|
-
-h, --help Show help
|
|
520
|
-
`);
|
|
521
|
-
return;
|
|
522
|
-
}
|
|
523
|
-
// Tree view mode
|
|
524
|
-
if (values.tree) {
|
|
525
|
-
await runListTree(flags.json);
|
|
526
|
-
return;
|
|
527
|
-
}
|
|
528
|
-
const runners = (0, daemon_js_1.listRunners)();
|
|
529
|
-
if (flags.json) {
|
|
530
|
-
// For JSON output, enrich with section names if available
|
|
531
|
-
const enrichedRunners = runners.map((runner) => {
|
|
532
|
-
if (!runner.section_id || !runner.project_path) {
|
|
533
|
-
return runner;
|
|
534
|
-
}
|
|
535
|
-
try {
|
|
536
|
-
const { db, close } = (0, connection_js_1.openDatabase)(runner.project_path);
|
|
537
|
-
try {
|
|
538
|
-
const section = (0, queries_js_1.getSection)(db, runner.section_id);
|
|
539
|
-
return { ...runner, section_name: section?.name };
|
|
540
|
-
}
|
|
541
|
-
finally {
|
|
542
|
-
close();
|
|
543
|
-
}
|
|
544
|
-
}
|
|
545
|
-
catch {
|
|
546
|
-
return runner;
|
|
547
|
-
}
|
|
548
|
-
});
|
|
549
|
-
console.log(JSON.stringify({ runners: enrichedRunners }, null, 2));
|
|
550
|
-
return;
|
|
551
|
-
}
|
|
552
|
-
if (runners.length === 0) {
|
|
553
|
-
console.log('No runners registered');
|
|
554
|
-
return;
|
|
555
|
-
}
|
|
556
|
-
console.log('RUNNERS');
|
|
557
|
-
console.log('─'.repeat(120));
|
|
558
|
-
console.log('ID STATUS PID PROJECT SECTION HEARTBEAT');
|
|
559
|
-
console.log('─'.repeat(120));
|
|
560
|
-
for (const runner of runners) {
|
|
561
|
-
const shortId = runner.id.substring(0, 8);
|
|
562
|
-
const status = runner.status.padEnd(10);
|
|
563
|
-
const pid = (runner.pid?.toString() ?? '-').padEnd(9);
|
|
564
|
-
const project = (runner.project_path ?? '-').substring(0, 30).padEnd(30);
|
|
565
|
-
// Fetch section name if available
|
|
566
|
-
let sectionDisplay = '-';
|
|
567
|
-
if (runner.section_id && runner.project_path) {
|
|
568
|
-
try {
|
|
569
|
-
const { db, close } = (0, connection_js_1.openDatabase)(runner.project_path);
|
|
570
|
-
try {
|
|
571
|
-
const section = (0, queries_js_1.getSection)(db, runner.section_id);
|
|
572
|
-
if (section) {
|
|
573
|
-
sectionDisplay = section.name.substring(0, 30);
|
|
574
|
-
}
|
|
353
|
+
if (values.parallel) {
|
|
354
|
+
const maxFromCli = typeof values.max === 'string' ? values.max.trim() : undefined;
|
|
355
|
+
let maxClones;
|
|
356
|
+
if (maxFromCli !== undefined) {
|
|
357
|
+
const parsedMax = Number.parseInt(maxFromCli, 10);
|
|
358
|
+
if (!Number.isInteger(parsedMax) || parsedMax <= 0) {
|
|
359
|
+
if (asJson) {
|
|
360
|
+
console.log(JSON.stringify({ success: false, error: '--max must be a positive integer' }));
|
|
575
361
|
}
|
|
576
|
-
|
|
577
|
-
|
|
362
|
+
else {
|
|
363
|
+
console.error('--max must be a positive integer');
|
|
578
364
|
}
|
|
365
|
+
process.exit(1);
|
|
579
366
|
}
|
|
580
|
-
|
|
581
|
-
// If we can't fetch the section name, just show the ID prefix
|
|
582
|
-
sectionDisplay = runner.section_id.substring(0, 8);
|
|
583
|
-
}
|
|
584
|
-
}
|
|
585
|
-
const section = sectionDisplay.padEnd(30);
|
|
586
|
-
const heartbeat = runner.heartbeat_at.substring(11, 19);
|
|
587
|
-
const alive = runner.pid && (0, lock_js_1.isProcessAlive)(runner.pid) ? '' : ' (dead)';
|
|
588
|
-
console.log(`${shortId} ${status} ${pid} ${project} ${section} ${heartbeat}${alive}`);
|
|
589
|
-
}
|
|
590
|
-
// Check if there are multiple projects
|
|
591
|
-
const uniqueProjects = new Set(runners.map(r => r.project_path).filter(Boolean));
|
|
592
|
-
if (uniqueProjects.size > 1) {
|
|
593
|
-
const currentProject = process.cwd();
|
|
594
|
-
console.log('');
|
|
595
|
-
console.log('─'.repeat(120));
|
|
596
|
-
console.log(`⚠️ MULTI-PROJECT WARNING: ${uniqueProjects.size} different projects have runners.`);
|
|
597
|
-
console.log(` Your current project: ${currentProject}`);
|
|
598
|
-
console.log(' DO NOT modify files in other projects. Each runner works only on its own project.');
|
|
599
|
-
console.log('─'.repeat(120));
|
|
600
|
-
}
|
|
601
|
-
}
|
|
602
|
-
/**
|
|
603
|
-
* Tree view of runners grouped by project with their current tasks
|
|
604
|
-
*/
|
|
605
|
-
async function runListTree(json) {
|
|
606
|
-
const runners = (0, daemon_js_1.listRunners)();
|
|
607
|
-
const projects = (0, projects_js_1.getRegisteredProjects)(false);
|
|
608
|
-
const projectMap = new Map();
|
|
609
|
-
// Initialize with all registered projects
|
|
610
|
-
for (const project of projects) {
|
|
611
|
-
projectMap.set(project.path, {
|
|
612
|
-
path: project.path,
|
|
613
|
-
name: project.name || (0, node_path_1.basename)(project.path),
|
|
614
|
-
runners: [],
|
|
615
|
-
activeTasks: [],
|
|
616
|
-
});
|
|
617
|
-
}
|
|
618
|
-
// Add runners to their projects
|
|
619
|
-
for (const runner of runners) {
|
|
620
|
-
const projectPath = runner.project_path;
|
|
621
|
-
if (!projectPath)
|
|
622
|
-
continue;
|
|
623
|
-
if (!projectMap.has(projectPath)) {
|
|
624
|
-
projectMap.set(projectPath, {
|
|
625
|
-
path: projectPath,
|
|
626
|
-
name: (0, node_path_1.basename)(projectPath),
|
|
627
|
-
runners: [],
|
|
628
|
-
activeTasks: [],
|
|
629
|
-
});
|
|
367
|
+
maxClones = parsedMax;
|
|
630
368
|
}
|
|
631
|
-
|
|
632
|
-
info.runners.push(runner);
|
|
633
|
-
}
|
|
634
|
-
// Fetch active tasks for each project
|
|
635
|
-
for (const [projectPath, info] of projectMap) {
|
|
636
|
-
const dbPath = `${projectPath}/.steroids/steroids.db`;
|
|
637
|
-
if (!(0, node_fs_1.existsSync)(dbPath))
|
|
638
|
-
continue;
|
|
369
|
+
let parallelPlan;
|
|
639
370
|
try {
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
371
|
+
parallelPlan = (0, runners_parallel_js_1.buildParallelRunPlan)(projectPath, maxClones);
|
|
372
|
+
}
|
|
373
|
+
catch (error) {
|
|
374
|
+
const message = error instanceof runners_parallel_js_1.CyclicDependencyError
|
|
375
|
+
? error.message
|
|
376
|
+
: error instanceof Error
|
|
377
|
+
? error.message
|
|
378
|
+
: 'Unable to create parallel plan';
|
|
379
|
+
if (asJson) {
|
|
380
|
+
console.log(JSON.stringify({ success: false, error: message }));
|
|
648
381
|
}
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
// Skip inaccessible projects
|
|
652
|
-
}
|
|
653
|
-
}
|
|
654
|
-
// JSON output
|
|
655
|
-
if (json) {
|
|
656
|
-
const output = Array.from(projectMap.values()).map((info) => ({
|
|
657
|
-
project: info.path,
|
|
658
|
-
name: info.name,
|
|
659
|
-
runners: info.runners.map((r) => ({
|
|
660
|
-
id: r.id,
|
|
661
|
-
status: r.status,
|
|
662
|
-
pid: r.pid,
|
|
663
|
-
currentTaskId: r.current_task_id,
|
|
664
|
-
alive: r.pid ? (0, lock_js_1.isProcessAlive)(r.pid) : false,
|
|
665
|
-
})),
|
|
666
|
-
activeTasks: info.activeTasks.map((t) => ({
|
|
667
|
-
id: t.id,
|
|
668
|
-
title: t.title,
|
|
669
|
-
status: t.status,
|
|
670
|
-
})),
|
|
671
|
-
}));
|
|
672
|
-
console.log(JSON.stringify({ projects: output }, null, 2));
|
|
673
|
-
return;
|
|
674
|
-
}
|
|
675
|
-
// Text tree view
|
|
676
|
-
const projectList = Array.from(projectMap.values());
|
|
677
|
-
const currentProject = process.cwd();
|
|
678
|
-
if (projectList.length === 0) {
|
|
679
|
-
console.log('No registered projects.');
|
|
680
|
-
return;
|
|
681
|
-
}
|
|
682
|
-
console.log('');
|
|
683
|
-
console.log('RUNNERS TREE');
|
|
684
|
-
console.log('═'.repeat(80));
|
|
685
|
-
for (let i = 0; i < projectList.length; i++) {
|
|
686
|
-
const info = projectList[i];
|
|
687
|
-
const isLast = i === projectList.length - 1;
|
|
688
|
-
const isCurrent = info.path === currentProject;
|
|
689
|
-
const currentMarker = isCurrent ? ' ← (current)' : '';
|
|
690
|
-
console.log('');
|
|
691
|
-
console.log(`📁 ${info.name}${currentMarker}`);
|
|
692
|
-
console.log(` ${info.path}`);
|
|
693
|
-
if (info.runners.length === 0) {
|
|
694
|
-
console.log(' └─ (no runners)');
|
|
695
|
-
}
|
|
696
|
-
else {
|
|
697
|
-
for (let j = 0; j < info.runners.length; j++) {
|
|
698
|
-
const runner = info.runners[j];
|
|
699
|
-
const isLastRunner = j === info.runners.length - 1;
|
|
700
|
-
const runnerPrefix = isLastRunner ? '└─' : '├─';
|
|
701
|
-
const childPrefix = isLastRunner ? ' ' : '│ ';
|
|
702
|
-
const alive = runner.pid && (0, lock_js_1.isProcessAlive)(runner.pid);
|
|
703
|
-
const statusIcon = alive ? '🟢' : '🔴';
|
|
704
|
-
const statusText = alive ? runner.status : 'dead';
|
|
705
|
-
const pidText = runner.pid ? ` PID ${runner.pid}` : '';
|
|
706
|
-
console.log(` ${runnerPrefix} ${statusIcon} Runner ${runner.id.substring(0, 8)} (${statusText}${pidText})`);
|
|
707
|
-
// Show section if focused
|
|
708
|
-
if (runner.section_id && runner.project_path) {
|
|
709
|
-
try {
|
|
710
|
-
const { db, close } = (0, connection_js_1.openDatabase)(runner.project_path);
|
|
711
|
-
try {
|
|
712
|
-
const section = (0, queries_js_1.getSection)(db, runner.section_id);
|
|
713
|
-
if (section) {
|
|
714
|
-
console.log(` ${childPrefix} Section: ${section.name}`);
|
|
715
|
-
}
|
|
716
|
-
}
|
|
717
|
-
finally {
|
|
718
|
-
close();
|
|
719
|
-
}
|
|
720
|
-
}
|
|
721
|
-
catch {
|
|
722
|
-
// Ignore section fetch errors
|
|
723
|
-
}
|
|
724
|
-
}
|
|
725
|
-
// Show current task if available
|
|
726
|
-
if (runner.current_task_id && runner.project_path) {
|
|
727
|
-
try {
|
|
728
|
-
const { db, close } = (0, connection_js_1.openDatabase)(runner.project_path);
|
|
729
|
-
try {
|
|
730
|
-
const task = (0, queries_js_1.getTask)(db, runner.current_task_id);
|
|
731
|
-
if (task) {
|
|
732
|
-
const statusMarker = task.status === 'in_progress' ? '🔧' : '👁️';
|
|
733
|
-
console.log(` ${childPrefix} ${statusMarker} ${task.title.substring(0, 50)}`);
|
|
734
|
-
console.log(` ${childPrefix} [${task.status}] ${task.id.substring(0, 8)}`);
|
|
735
|
-
}
|
|
736
|
-
}
|
|
737
|
-
finally {
|
|
738
|
-
close();
|
|
739
|
-
}
|
|
740
|
-
}
|
|
741
|
-
catch {
|
|
742
|
-
console.log(` ${childPrefix} Task: ${runner.current_task_id.substring(0, 8)}`);
|
|
743
|
-
}
|
|
744
|
-
}
|
|
745
|
-
else if (alive) {
|
|
746
|
-
console.log(` ${childPrefix} (idle - no task)`);
|
|
747
|
-
}
|
|
748
|
-
}
|
|
749
|
-
}
|
|
750
|
-
// Show other active tasks not being worked on by runners
|
|
751
|
-
const runnerTaskIds = new Set(info.runners.map((r) => r.current_task_id).filter(Boolean));
|
|
752
|
-
const unassignedTasks = info.activeTasks.filter((t) => !runnerTaskIds.has(t.id));
|
|
753
|
-
if (unassignedTasks.length > 0) {
|
|
754
|
-
console.log(' │');
|
|
755
|
-
console.log(' └─ 📋 Queued active tasks:');
|
|
756
|
-
for (const task of unassignedTasks.slice(0, 5)) {
|
|
757
|
-
const statusIcon = task.status === 'in_progress' ? '🔧' : '👁️';
|
|
758
|
-
console.log(` ${statusIcon} ${task.title.substring(0, 45)} [${task.status}]`);
|
|
759
|
-
}
|
|
760
|
-
if (unassignedTasks.length > 5) {
|
|
761
|
-
console.log(` ... and ${unassignedTasks.length - 5} more`);
|
|
382
|
+
else {
|
|
383
|
+
console.error(message);
|
|
762
384
|
}
|
|
385
|
+
process.exit(1);
|
|
763
386
|
}
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
// Multi-project warning
|
|
768
|
-
const activeProjects = projectList.filter((p) => p.runners.length > 0);
|
|
769
|
-
if (activeProjects.length > 1) {
|
|
770
|
-
console.log('');
|
|
771
|
-
console.log('⚠️ MULTI-PROJECT: Multiple projects have active runners.');
|
|
772
|
-
console.log(' Each runner works ONLY on its own project.');
|
|
773
|
-
}
|
|
774
|
-
}
|
|
775
|
-
async function runLogs(args) {
|
|
776
|
-
const { values, positionals } = (0, node_util_1.parseArgs)({
|
|
777
|
-
args,
|
|
778
|
-
options: {
|
|
779
|
-
help: { type: 'boolean', short: 'h', default: false },
|
|
780
|
-
json: { type: 'boolean', short: 'j', default: false },
|
|
781
|
-
tail: { type: 'string', short: 'n', default: '50' },
|
|
782
|
-
follow: { type: 'boolean', short: 'f', default: false },
|
|
783
|
-
clear: { type: 'boolean', default: false },
|
|
784
|
-
},
|
|
785
|
-
allowPositionals: true,
|
|
786
|
-
});
|
|
787
|
-
if (values.help) {
|
|
788
|
-
console.log(`
|
|
789
|
-
steroids runners logs - View daemon crash/output logs
|
|
790
|
-
|
|
791
|
-
USAGE:
|
|
792
|
-
steroids runners logs [pid] [options]
|
|
793
|
-
|
|
794
|
-
OPTIONS:
|
|
795
|
-
<pid> Show logs for specific daemon PID
|
|
796
|
-
--tail <n> Show last n lines (default: 50)
|
|
797
|
-
--follow Follow log output (latest log)
|
|
798
|
-
--clear Clear all daemon logs
|
|
799
|
-
-j, --json Output as JSON
|
|
800
|
-
-h, --help Show help
|
|
801
|
-
|
|
802
|
-
LOG LOCATION:
|
|
803
|
-
Logs are stored in ~/.steroids/runners/logs/
|
|
804
|
-
Each daemon gets its own log file: daemon-<pid>.log
|
|
805
|
-
|
|
806
|
-
To disable daemon logging, set in config:
|
|
807
|
-
steroids config set runners.daemonLogs false
|
|
808
|
-
|
|
809
|
-
EXAMPLES:
|
|
810
|
-
steroids runners logs # List available log files
|
|
811
|
-
steroids runners logs 12345 # View logs for PID 12345
|
|
812
|
-
steroids runners logs --follow # Follow the latest log
|
|
813
|
-
steroids runners logs --clear # Remove all log files
|
|
814
|
-
`);
|
|
815
|
-
return;
|
|
816
|
-
}
|
|
817
|
-
const logsDir = path.join(os.homedir(), '.steroids', 'runners', 'logs');
|
|
818
|
-
// Handle --clear
|
|
819
|
-
if (values.clear) {
|
|
820
|
-
if (!fs.existsSync(logsDir)) {
|
|
821
|
-
if (values.json) {
|
|
822
|
-
console.log(JSON.stringify({ success: true, cleared: 0 }));
|
|
387
|
+
if (flags.dryRun) {
|
|
388
|
+
if (asJson) {
|
|
389
|
+
console.log(JSON.stringify({ success: true, plan: parallelPlan }));
|
|
823
390
|
}
|
|
824
391
|
else {
|
|
825
|
-
|
|
392
|
+
(0, runners_parallel_js_1.printParallelPlan)(projectPath, parallelPlan);
|
|
826
393
|
}
|
|
827
394
|
return;
|
|
828
395
|
}
|
|
829
|
-
const
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
}
|
|
833
|
-
if (values.json) {
|
|
834
|
-
console.log(JSON.stringify({ success: true, cleared: files.length }));
|
|
396
|
+
const sessionId = (0, runners_parallel_js_1.launchParallelSession)(parallelPlan, projectPath);
|
|
397
|
+
if (asJson) {
|
|
398
|
+
console.log(JSON.stringify({ success: true, sessionId }));
|
|
835
399
|
}
|
|
836
400
|
else {
|
|
837
|
-
console.log(`
|
|
838
|
-
}
|
|
839
|
-
return;
|
|
840
|
-
}
|
|
841
|
-
// Ensure logs directory exists
|
|
842
|
-
if (!fs.existsSync(logsDir)) {
|
|
843
|
-
if (values.json) {
|
|
844
|
-
console.log(JSON.stringify({ logs: [], logsDir }));
|
|
845
|
-
}
|
|
846
|
-
else {
|
|
847
|
-
console.log('No daemon logs found');
|
|
848
|
-
console.log(` Logs are stored in: ${logsDir}`);
|
|
849
|
-
}
|
|
850
|
-
return;
|
|
851
|
-
}
|
|
852
|
-
const logFiles = fs.readdirSync(logsDir)
|
|
853
|
-
.filter((f) => f.startsWith('daemon-') && f.endsWith('.log'))
|
|
854
|
-
.map((f) => {
|
|
855
|
-
const filePath = path.join(logsDir, f);
|
|
856
|
-
const stats = fs.statSync(filePath);
|
|
857
|
-
const pidMatch = f.match(/daemon-(\d+)\.log/);
|
|
858
|
-
return {
|
|
859
|
-
file: f,
|
|
860
|
-
path: filePath,
|
|
861
|
-
pid: pidMatch ? parseInt(pidMatch[1], 10) : null,
|
|
862
|
-
size: stats.size,
|
|
863
|
-
modified: stats.mtime,
|
|
864
|
-
};
|
|
865
|
-
})
|
|
866
|
-
.sort((a, b) => b.modified.getTime() - a.modified.getTime());
|
|
867
|
-
// If a PID is specified, show that log
|
|
868
|
-
if (positionals.length > 0) {
|
|
869
|
-
const pidArg = positionals[0];
|
|
870
|
-
const logFile = logFiles.find((l) => l.pid?.toString() === pidArg || l.file.includes(pidArg));
|
|
871
|
-
if (!logFile) {
|
|
872
|
-
console.error(`No log found for PID: ${pidArg}`);
|
|
873
|
-
process.exit(1);
|
|
874
|
-
}
|
|
875
|
-
const content = fs.readFileSync(logFile.path, 'utf-8');
|
|
876
|
-
const lines = content.split('\n');
|
|
877
|
-
const tailLines = parseInt(values.tail, 10) || 50;
|
|
878
|
-
const output = lines.slice(-tailLines).join('\n');
|
|
879
|
-
if (values.json) {
|
|
880
|
-
console.log(JSON.stringify({ pid: logFile.pid, path: logFile.path, content: output }));
|
|
881
|
-
}
|
|
882
|
-
else {
|
|
883
|
-
console.log(`=== Daemon log for PID ${logFile.pid} ===`);
|
|
884
|
-
console.log(`File: ${logFile.path}`);
|
|
885
|
-
console.log(`Modified: ${logFile.modified.toISOString()}`);
|
|
886
|
-
console.log('─'.repeat(60));
|
|
887
|
-
console.log(output);
|
|
888
|
-
}
|
|
889
|
-
return;
|
|
890
|
-
}
|
|
891
|
-
// If --follow, tail the most recent log
|
|
892
|
-
if (values.follow) {
|
|
893
|
-
if (logFiles.length === 0) {
|
|
894
|
-
console.error('No log files to follow');
|
|
895
|
-
process.exit(1);
|
|
401
|
+
console.log(`Started parallel session: ${sessionId}`);
|
|
896
402
|
}
|
|
897
|
-
const latestLog = logFiles[0];
|
|
898
|
-
console.log(`Following: ${latestLog.path} (PID: ${latestLog.pid})`);
|
|
899
|
-
console.log('─'.repeat(60));
|
|
900
|
-
// Use spawn to tail -f
|
|
901
|
-
const tail = (0, node_child_process_1.spawn)('tail', ['-f', latestLog.path], { stdio: 'inherit' });
|
|
902
|
-
tail.on('error', (err) => {
|
|
903
|
-
console.error(`Error following log: ${err.message}`);
|
|
904
|
-
process.exit(1);
|
|
905
|
-
});
|
|
906
403
|
return;
|
|
907
404
|
}
|
|
908
|
-
//
|
|
909
|
-
|
|
910
|
-
console.log(JSON.stringify({ logs: logFiles, logsDir }, null, 2));
|
|
911
|
-
return;
|
|
912
|
-
}
|
|
913
|
-
if (logFiles.length === 0) {
|
|
914
|
-
console.log('No daemon logs found');
|
|
915
|
-
console.log(` Logs are stored in: ${logsDir}`);
|
|
916
|
-
return;
|
|
917
|
-
}
|
|
918
|
-
console.log('DAEMON LOGS');
|
|
919
|
-
console.log('─'.repeat(80));
|
|
920
|
-
console.log('PID SIZE MODIFIED FILE');
|
|
921
|
-
console.log('─'.repeat(80));
|
|
922
|
-
for (const log of logFiles) {
|
|
923
|
-
const pid = (log.pid?.toString() ?? 'unknown').padEnd(10);
|
|
924
|
-
const size = formatBytes(log.size).padEnd(9);
|
|
925
|
-
const modified = log.modified.toISOString().substring(0, 19).padEnd(22);
|
|
926
|
-
console.log(`${pid} ${size} ${modified} ${log.file}`);
|
|
927
|
-
}
|
|
928
|
-
console.log('');
|
|
929
|
-
console.log(`Logs directory: ${logsDir}`);
|
|
930
|
-
console.log(`Use 'steroids runners logs <pid>' to view a specific log`);
|
|
931
|
-
}
|
|
932
|
-
function formatBytes(bytes) {
|
|
933
|
-
if (bytes === 0)
|
|
934
|
-
return '0 B';
|
|
935
|
-
const k = 1024;
|
|
936
|
-
const sizes = ['B', 'KB', 'MB', 'GB'];
|
|
937
|
-
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
|
938
|
-
return parseFloat((bytes / Math.pow(k, i)).toFixed(1)) + ' ' + sizes[i];
|
|
405
|
+
// Start in foreground
|
|
406
|
+
await (0, daemon_js_1.startDaemon)({ projectPath, sectionId: focusedSectionId });
|
|
939
407
|
}
|
|
940
|
-
async function
|
|
941
|
-
|
|
942
|
-
// If ANY operation hangs (TCC dialog blocking file access, locked DB, etc.),
|
|
943
|
-
// this ensures the process exits instead of becoming a zombie.
|
|
944
|
-
const WAKEUP_TIMEOUT_MS = 30_000;
|
|
945
|
-
setTimeout(() => process.exit(0), WAKEUP_TIMEOUT_MS).unref();
|
|
946
|
-
const { values } = (0, node_util_1.parseArgs)({
|
|
947
|
-
args,
|
|
948
|
-
options: {
|
|
949
|
-
help: { type: 'boolean', short: 'h', default: false },
|
|
950
|
-
json: { type: 'boolean', short: 'j', default: false },
|
|
951
|
-
quiet: { type: 'boolean', short: 'q', default: false },
|
|
952
|
-
},
|
|
953
|
-
allowPositionals: false,
|
|
954
|
-
});
|
|
955
|
-
if (values.help) {
|
|
956
|
-
console.log(`
|
|
957
|
-
steroids runners wakeup - Check and restart stale runners
|
|
958
|
-
|
|
959
|
-
USAGE:
|
|
960
|
-
steroids runners wakeup [options]
|
|
961
|
-
|
|
962
|
-
OPTIONS:
|
|
963
|
-
--quiet Suppress output (for cron)
|
|
964
|
-
--dry-run Check without acting
|
|
965
|
-
-j, --json Output as JSON
|
|
966
|
-
-h, --help Show help
|
|
967
|
-
`);
|
|
968
|
-
return;
|
|
969
|
-
}
|
|
970
|
-
const results = await (0, wakeup_js_1.wakeup)({
|
|
971
|
-
quiet: values.quiet || flags.quiet || values.json || flags.json,
|
|
972
|
-
dryRun: flags.dryRun,
|
|
973
|
-
});
|
|
974
|
-
if (values.json || flags.json) {
|
|
975
|
-
console.log(JSON.stringify({ results }, null, 2));
|
|
976
|
-
return;
|
|
977
|
-
}
|
|
978
|
-
if (!values.quiet && !flags.quiet) {
|
|
979
|
-
// Summarize results
|
|
980
|
-
const started = results.filter(r => r.action === 'started').length;
|
|
981
|
-
const cleaned = results.filter(r => r.action === 'cleaned').length;
|
|
982
|
-
const wouldStart = results.filter(r => r.action === 'would_start').length;
|
|
983
|
-
if (started > 0) {
|
|
984
|
-
console.log(`Started ${started} runner(s)`);
|
|
985
|
-
}
|
|
986
|
-
if (cleaned > 0) {
|
|
987
|
-
console.log(`Cleaned ${cleaned} stale runner(s)`);
|
|
988
|
-
}
|
|
989
|
-
if (wouldStart > 0) {
|
|
990
|
-
console.log(`Would start ${wouldStart} runner(s) (dry-run)`);
|
|
991
|
-
}
|
|
992
|
-
if (started === 0 && cleaned === 0 && wouldStart === 0) {
|
|
993
|
-
console.log('No action needed');
|
|
994
|
-
}
|
|
995
|
-
// Show per-project details
|
|
996
|
-
for (const result of results) {
|
|
997
|
-
if (result.projectPath) {
|
|
998
|
-
const status = result.action === 'started' ? '✓' :
|
|
999
|
-
result.action === 'would_start' ? '~' : '-';
|
|
1000
|
-
console.log(` ${status} ${result.projectPath}: ${result.reason}`);
|
|
1001
|
-
}
|
|
1002
|
-
}
|
|
1003
|
-
}
|
|
1004
|
-
// Force exit — wakeup is a short-lived process spawned by launchd every 60s.
|
|
1005
|
-
// Without this, lingering DB handles (better-sqlite3 native bindings) keep
|
|
1006
|
-
// the Node.js event loop alive indefinitely, causing zombie process accumulation.
|
|
1007
|
-
process.exit(0);
|
|
408
|
+
async function runStop(args) {
|
|
409
|
+
return (0, runners_management_js_1.runStop)(args);
|
|
1008
410
|
}
|
|
1009
|
-
async function
|
|
1010
|
-
|
|
1011
|
-
console.log(`
|
|
1012
|
-
steroids runners cron - Manage cron job
|
|
1013
|
-
|
|
1014
|
-
USAGE:
|
|
1015
|
-
steroids runners cron <subcommand>
|
|
1016
|
-
|
|
1017
|
-
SUBCOMMANDS:
|
|
1018
|
-
install Add cron job (every minute)
|
|
1019
|
-
uninstall Remove cron job
|
|
1020
|
-
status Check cron status
|
|
1021
|
-
|
|
1022
|
-
OPTIONS:
|
|
1023
|
-
-j, --json Output as JSON
|
|
1024
|
-
-h, --help Show help
|
|
1025
|
-
`);
|
|
1026
|
-
return;
|
|
1027
|
-
}
|
|
1028
|
-
const subcommand = args[0];
|
|
1029
|
-
const subArgs = args.slice(1);
|
|
1030
|
-
const { values } = (0, node_util_1.parseArgs)({
|
|
1031
|
-
args: subArgs,
|
|
1032
|
-
options: {
|
|
1033
|
-
json: { type: 'boolean', short: 'j', default: false },
|
|
1034
|
-
},
|
|
1035
|
-
allowPositionals: false,
|
|
1036
|
-
});
|
|
1037
|
-
switch (subcommand) {
|
|
1038
|
-
case 'install': {
|
|
1039
|
-
const result = (0, cron_js_1.cronInstall)();
|
|
1040
|
-
if (values.json) {
|
|
1041
|
-
console.log(JSON.stringify(result, null, 2));
|
|
1042
|
-
}
|
|
1043
|
-
else {
|
|
1044
|
-
console.log(result.message);
|
|
1045
|
-
if (result.error) {
|
|
1046
|
-
console.error(result.error);
|
|
1047
|
-
}
|
|
1048
|
-
}
|
|
1049
|
-
break;
|
|
1050
|
-
}
|
|
1051
|
-
case 'uninstall': {
|
|
1052
|
-
const result = (0, cron_js_1.cronUninstall)();
|
|
1053
|
-
if (values.json) {
|
|
1054
|
-
console.log(JSON.stringify(result, null, 2));
|
|
1055
|
-
}
|
|
1056
|
-
else {
|
|
1057
|
-
console.log(result.message);
|
|
1058
|
-
}
|
|
1059
|
-
break;
|
|
1060
|
-
}
|
|
1061
|
-
case 'status': {
|
|
1062
|
-
const status = (0, cron_js_1.cronStatus)();
|
|
1063
|
-
if (values.json) {
|
|
1064
|
-
console.log(JSON.stringify(status, null, 2));
|
|
1065
|
-
}
|
|
1066
|
-
else {
|
|
1067
|
-
if (status.installed) {
|
|
1068
|
-
console.log('Cron job: INSTALLED');
|
|
1069
|
-
console.log(` Entry: ${status.entry}`);
|
|
1070
|
-
}
|
|
1071
|
-
else {
|
|
1072
|
-
console.log('Cron job: NOT INSTALLED');
|
|
1073
|
-
if (status.error) {
|
|
1074
|
-
console.log(` ${status.error}`);
|
|
1075
|
-
}
|
|
1076
|
-
}
|
|
1077
|
-
}
|
|
1078
|
-
break;
|
|
1079
|
-
}
|
|
1080
|
-
default:
|
|
1081
|
-
console.error(`Unknown cron subcommand: ${subcommand}`);
|
|
1082
|
-
process.exit(1);
|
|
1083
|
-
}
|
|
411
|
+
async function runStatus(args) {
|
|
412
|
+
return (0, runners_management_js_1.runStatus)(args);
|
|
1084
413
|
}
|
|
1085
414
|
//# sourceMappingURL=runners.js.map
|