steroids-cli 0.8.25 → 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-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 +10 -759
- 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 +3 -3
- package/dist/config/loader.d.ts.map +1 -1
- package/dist/config/loader.js +1 -0
- package/dist/config/loader.js.map +1 -1
- package/dist/config/schema.js +3 -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/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
42
|
const daemon_js_1 = require("../runners/daemon.js");
|
|
46
|
-
const lock_js_1 = require("../runners/lock.js");
|
|
47
|
-
const wakeup_js_1 = require("../runners/wakeup.js");
|
|
48
|
-
const cron_js_1 = require("../runners/cron.js");
|
|
49
43
|
const connection_js_1 = require("../database/connection.js");
|
|
50
44
|
const queries_js_1 = require("../database/queries.js");
|
|
51
|
-
const node_fs_1 = require("node:fs");
|
|
52
|
-
const node_path_1 = require("node:path");
|
|
53
|
-
const projects_js_1 = require("../runners/projects.js");
|
|
54
45
|
const help_js_1 = require("../cli/help.js");
|
|
55
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',
|
|
@@ -134,16 +129,16 @@ async function runnersCommand(args, flags) {
|
|
|
134
129
|
await runStatus(subArgs);
|
|
135
130
|
break;
|
|
136
131
|
case 'list':
|
|
137
|
-
await runList(subArgs, flags);
|
|
132
|
+
await (0, runners_list_js_1.runList)(subArgs, flags);
|
|
138
133
|
break;
|
|
139
134
|
case 'logs':
|
|
140
|
-
await runLogs(subArgs);
|
|
135
|
+
await (0, runners_logs_js_1.runLogs)(subArgs);
|
|
141
136
|
break;
|
|
142
137
|
case 'wakeup':
|
|
143
|
-
await runWakeup(subArgs, flags);
|
|
138
|
+
await (0, runners_wakeup_js_1.runWakeup)(subArgs, flags);
|
|
144
139
|
break;
|
|
145
140
|
case 'cron':
|
|
146
|
-
await runCron(subArgs);
|
|
141
|
+
await (0, runners_wakeup_js_1.runCron)(subArgs);
|
|
147
142
|
break;
|
|
148
143
|
default:
|
|
149
144
|
console.error(`Unknown subcommand: ${subcommand}`);
|
|
@@ -411,753 +406,9 @@ USAGE:
|
|
|
411
406
|
await (0, daemon_js_1.startDaemon)({ projectPath, sectionId: focusedSectionId });
|
|
412
407
|
}
|
|
413
408
|
async function runStop(args) {
|
|
414
|
-
|
|
415
|
-
args,
|
|
416
|
-
options: {
|
|
417
|
-
help: { type: 'boolean', short: 'h', default: false },
|
|
418
|
-
json: { type: 'boolean', short: 'j', default: false },
|
|
419
|
-
id: { type: 'string' },
|
|
420
|
-
all: { type: 'boolean', default: false },
|
|
421
|
-
},
|
|
422
|
-
allowPositionals: false,
|
|
423
|
-
});
|
|
424
|
-
if (values.help) {
|
|
425
|
-
console.log(`
|
|
426
|
-
steroids runners stop - Stop runner(s)
|
|
427
|
-
|
|
428
|
-
USAGE:
|
|
429
|
-
steroids runners stop [options]
|
|
430
|
-
|
|
431
|
-
OPTIONS:
|
|
432
|
-
--id <id> Stop specific runner
|
|
433
|
-
--all Stop all runners
|
|
434
|
-
-j, --json Output as JSON
|
|
435
|
-
-h, --help Show help
|
|
436
|
-
`);
|
|
437
|
-
return;
|
|
438
|
-
}
|
|
439
|
-
// Capture stop context for logging
|
|
440
|
-
const stopContext = {
|
|
441
|
-
calledFrom: process.cwd(),
|
|
442
|
-
callerPid: process.pid,
|
|
443
|
-
timestamp: new Date().toISOString(),
|
|
444
|
-
user: process.env.USER || process.env.USERNAME || 'unknown',
|
|
445
|
-
args: {
|
|
446
|
-
id: values.id,
|
|
447
|
-
all: values.all,
|
|
448
|
-
},
|
|
449
|
-
};
|
|
450
|
-
const runners = (0, daemon_js_1.listRunners)();
|
|
451
|
-
let stopped = 0;
|
|
452
|
-
const stoppedRunners = [];
|
|
453
|
-
const runnersToStop = values.id
|
|
454
|
-
? runners.filter((r) => r.id === values.id || r.id.startsWith(values.id))
|
|
455
|
-
: values.all
|
|
456
|
-
? runners
|
|
457
|
-
: runners.filter((r) => r.pid === process.pid || r.pid !== null);
|
|
458
|
-
// Log stop action to daemon logs
|
|
459
|
-
const logsDir = path.join(os.homedir(), '.steroids', 'runners', 'logs');
|
|
460
|
-
const stopLogPath = path.join(logsDir, 'stop-audit.log');
|
|
461
|
-
fs.mkdirSync(logsDir, { recursive: true });
|
|
462
|
-
for (const runner of runnersToStop) {
|
|
463
|
-
if (runner.pid && (0, lock_js_1.isProcessAlive)(runner.pid)) {
|
|
464
|
-
try {
|
|
465
|
-
process.kill(runner.pid, 'SIGTERM');
|
|
466
|
-
stopped++;
|
|
467
|
-
stoppedRunners.push({
|
|
468
|
-
id: runner.id,
|
|
469
|
-
pid: runner.pid,
|
|
470
|
-
project: runner.project_path,
|
|
471
|
-
});
|
|
472
|
-
// Log each stop to audit log
|
|
473
|
-
const logEntry = {
|
|
474
|
-
...stopContext,
|
|
475
|
-
action: 'stop',
|
|
476
|
-
runner: {
|
|
477
|
-
id: runner.id,
|
|
478
|
-
pid: runner.pid,
|
|
479
|
-
project: runner.project_path,
|
|
480
|
-
},
|
|
481
|
-
};
|
|
482
|
-
fs.appendFileSync(stopLogPath, JSON.stringify(logEntry) + '\n');
|
|
483
|
-
}
|
|
484
|
-
catch {
|
|
485
|
-
// Process already dead
|
|
486
|
-
}
|
|
487
|
-
}
|
|
488
|
-
(0, daemon_js_1.unregisterRunner)(runner.id);
|
|
489
|
-
}
|
|
490
|
-
if (values.json) {
|
|
491
|
-
console.log(JSON.stringify({
|
|
492
|
-
success: true,
|
|
493
|
-
stopped,
|
|
494
|
-
stoppedRunners,
|
|
495
|
-
context: stopContext,
|
|
496
|
-
}));
|
|
497
|
-
}
|
|
498
|
-
else {
|
|
499
|
-
console.log(`Stopped ${stopped} runner(s)`);
|
|
500
|
-
if (stopped > 0 && !values.all) {
|
|
501
|
-
console.log(` Called from: ${stopContext.calledFrom}`);
|
|
502
|
-
console.log(` Audit log: ${stopLogPath}`);
|
|
503
|
-
}
|
|
504
|
-
}
|
|
409
|
+
return (0, runners_management_js_1.runStop)(args);
|
|
505
410
|
}
|
|
506
411
|
async function runStatus(args) {
|
|
507
|
-
|
|
508
|
-
args,
|
|
509
|
-
options: {
|
|
510
|
-
help: { type: 'boolean', short: 'h', default: false },
|
|
511
|
-
json: { type: 'boolean', short: 'j', default: false },
|
|
512
|
-
},
|
|
513
|
-
allowPositionals: false,
|
|
514
|
-
});
|
|
515
|
-
if (values.help) {
|
|
516
|
-
console.log(`
|
|
517
|
-
steroids runners status - Show runner status
|
|
518
|
-
|
|
519
|
-
USAGE:
|
|
520
|
-
steroids runners status [options]
|
|
521
|
-
|
|
522
|
-
OPTIONS:
|
|
523
|
-
-j, --json Output as JSON
|
|
524
|
-
-h, --help Show help
|
|
525
|
-
`);
|
|
526
|
-
return;
|
|
527
|
-
}
|
|
528
|
-
const lockStatus = (0, lock_js_1.checkLockStatus)();
|
|
529
|
-
const runners = (0, daemon_js_1.listRunners)();
|
|
530
|
-
const activeRunner = runners.find((r) => r.pid && (0, lock_js_1.isProcessAlive)(r.pid));
|
|
531
|
-
const status = {
|
|
532
|
-
locked: lockStatus.locked,
|
|
533
|
-
lockPid: lockStatus.pid,
|
|
534
|
-
isZombie: lockStatus.isZombie,
|
|
535
|
-
activeRunner: activeRunner
|
|
536
|
-
? {
|
|
537
|
-
id: activeRunner.id,
|
|
538
|
-
pid: activeRunner.pid,
|
|
539
|
-
status: activeRunner.status,
|
|
540
|
-
project: activeRunner.project_path,
|
|
541
|
-
currentTask: activeRunner.current_task_id,
|
|
542
|
-
heartbeat: activeRunner.heartbeat_at,
|
|
543
|
-
}
|
|
544
|
-
: null,
|
|
545
|
-
totalRunners: runners.length,
|
|
546
|
-
};
|
|
547
|
-
if (values.json) {
|
|
548
|
-
console.log(JSON.stringify(status, null, 2));
|
|
549
|
-
return;
|
|
550
|
-
}
|
|
551
|
-
if (activeRunner) {
|
|
552
|
-
console.log(`Runner Status: ACTIVE`);
|
|
553
|
-
console.log(` ID: ${activeRunner.id}`);
|
|
554
|
-
console.log(` PID: ${activeRunner.pid}`);
|
|
555
|
-
console.log(` Status: ${activeRunner.status}`);
|
|
556
|
-
if (activeRunner.project_path) {
|
|
557
|
-
console.log(` Project: ${activeRunner.project_path}`);
|
|
558
|
-
}
|
|
559
|
-
if (activeRunner.current_task_id) {
|
|
560
|
-
console.log(` Current Task: ${activeRunner.current_task_id}`);
|
|
561
|
-
}
|
|
562
|
-
console.log(` Last Heartbeat: ${activeRunner.heartbeat_at}`);
|
|
563
|
-
}
|
|
564
|
-
else if (lockStatus.isZombie) {
|
|
565
|
-
console.log(`Runner Status: ZOMBIE`);
|
|
566
|
-
console.log(` Lock exists but process (PID: ${lockStatus.pid}) is dead`);
|
|
567
|
-
console.log(` Run 'steroids runners wakeup' to clean up`);
|
|
568
|
-
}
|
|
569
|
-
else {
|
|
570
|
-
console.log(`Runner Status: INACTIVE`);
|
|
571
|
-
console.log(` No runner is currently active`);
|
|
572
|
-
}
|
|
573
|
-
}
|
|
574
|
-
async function runList(args, flags) {
|
|
575
|
-
const { values } = (0, node_util_1.parseArgs)({
|
|
576
|
-
args,
|
|
577
|
-
options: {
|
|
578
|
-
help: { type: 'boolean', short: 'h', default: false },
|
|
579
|
-
tree: { type: 'boolean', short: 't', default: false },
|
|
580
|
-
},
|
|
581
|
-
allowPositionals: false,
|
|
582
|
-
});
|
|
583
|
-
if (values.help || flags.help) {
|
|
584
|
-
console.log(`
|
|
585
|
-
steroids runners list - List all runners
|
|
586
|
-
|
|
587
|
-
USAGE:
|
|
588
|
-
steroids runners list [options]
|
|
589
|
-
|
|
590
|
-
OPTIONS:
|
|
591
|
-
-t, --tree Show tree view with tasks
|
|
592
|
-
-j, --json Output as JSON (global flag)
|
|
593
|
-
-h, --help Show help
|
|
594
|
-
`);
|
|
595
|
-
return;
|
|
596
|
-
}
|
|
597
|
-
// Tree view mode
|
|
598
|
-
if (values.tree) {
|
|
599
|
-
await runListTree(flags.json);
|
|
600
|
-
return;
|
|
601
|
-
}
|
|
602
|
-
const runners = (0, daemon_js_1.listRunners)();
|
|
603
|
-
if (flags.json) {
|
|
604
|
-
// For JSON output, enrich with section names if available
|
|
605
|
-
const enrichedRunners = runners.map((runner) => {
|
|
606
|
-
if (!runner.section_id || !runner.project_path) {
|
|
607
|
-
return runner;
|
|
608
|
-
}
|
|
609
|
-
try {
|
|
610
|
-
const { db, close } = (0, connection_js_1.openDatabase)(runner.project_path);
|
|
611
|
-
try {
|
|
612
|
-
const section = (0, queries_js_1.getSection)(db, runner.section_id);
|
|
613
|
-
return { ...runner, section_name: section?.name };
|
|
614
|
-
}
|
|
615
|
-
finally {
|
|
616
|
-
close();
|
|
617
|
-
}
|
|
618
|
-
}
|
|
619
|
-
catch {
|
|
620
|
-
return runner;
|
|
621
|
-
}
|
|
622
|
-
});
|
|
623
|
-
console.log(JSON.stringify({ runners: enrichedRunners }, null, 2));
|
|
624
|
-
return;
|
|
625
|
-
}
|
|
626
|
-
if (runners.length === 0) {
|
|
627
|
-
console.log('No runners registered');
|
|
628
|
-
return;
|
|
629
|
-
}
|
|
630
|
-
console.log('RUNNERS');
|
|
631
|
-
console.log('─'.repeat(120));
|
|
632
|
-
console.log('ID STATUS PID PROJECT SECTION HEARTBEAT');
|
|
633
|
-
console.log('─'.repeat(120));
|
|
634
|
-
for (const runner of runners) {
|
|
635
|
-
const shortId = runner.id.substring(0, 8);
|
|
636
|
-
const status = runner.status.padEnd(10);
|
|
637
|
-
const pid = (runner.pid?.toString() ?? '-').padEnd(9);
|
|
638
|
-
const project = (runner.project_path ?? '-').substring(0, 30).padEnd(30);
|
|
639
|
-
// Fetch section name if available
|
|
640
|
-
let sectionDisplay = '-';
|
|
641
|
-
if (runner.section_id && runner.project_path) {
|
|
642
|
-
try {
|
|
643
|
-
const { db, close } = (0, connection_js_1.openDatabase)(runner.project_path);
|
|
644
|
-
try {
|
|
645
|
-
const section = (0, queries_js_1.getSection)(db, runner.section_id);
|
|
646
|
-
if (section) {
|
|
647
|
-
sectionDisplay = section.name.substring(0, 30);
|
|
648
|
-
}
|
|
649
|
-
}
|
|
650
|
-
finally {
|
|
651
|
-
close();
|
|
652
|
-
}
|
|
653
|
-
}
|
|
654
|
-
catch {
|
|
655
|
-
// If we can't fetch the section name, just show the ID prefix
|
|
656
|
-
sectionDisplay = runner.section_id.substring(0, 8);
|
|
657
|
-
}
|
|
658
|
-
}
|
|
659
|
-
const section = sectionDisplay.padEnd(30);
|
|
660
|
-
const heartbeat = runner.heartbeat_at.substring(11, 19);
|
|
661
|
-
const alive = runner.pid && (0, lock_js_1.isProcessAlive)(runner.pid) ? '' : ' (dead)';
|
|
662
|
-
console.log(`${shortId} ${status} ${pid} ${project} ${section} ${heartbeat}${alive}`);
|
|
663
|
-
}
|
|
664
|
-
// Check if there are multiple projects
|
|
665
|
-
const uniqueProjects = new Set(runners.map(r => r.project_path).filter(Boolean));
|
|
666
|
-
if (uniqueProjects.size > 1) {
|
|
667
|
-
const currentProject = process.cwd();
|
|
668
|
-
console.log('');
|
|
669
|
-
console.log('─'.repeat(120));
|
|
670
|
-
console.log(`⚠️ MULTI-PROJECT WARNING: ${uniqueProjects.size} different projects have runners.`);
|
|
671
|
-
console.log(` Your current project: ${currentProject}`);
|
|
672
|
-
console.log(' DO NOT modify files in other projects. Each runner works only on its own project.');
|
|
673
|
-
console.log('─'.repeat(120));
|
|
674
|
-
}
|
|
675
|
-
}
|
|
676
|
-
/**
|
|
677
|
-
* Tree view of runners grouped by project with their current tasks
|
|
678
|
-
*/
|
|
679
|
-
async function runListTree(json) {
|
|
680
|
-
const runners = (0, daemon_js_1.listRunners)();
|
|
681
|
-
const projects = (0, projects_js_1.getRegisteredProjects)(false);
|
|
682
|
-
const projectMap = new Map();
|
|
683
|
-
// Initialize with all registered projects
|
|
684
|
-
for (const project of projects) {
|
|
685
|
-
projectMap.set(project.path, {
|
|
686
|
-
path: project.path,
|
|
687
|
-
name: project.name || (0, node_path_1.basename)(project.path),
|
|
688
|
-
runners: [],
|
|
689
|
-
activeTasks: [],
|
|
690
|
-
});
|
|
691
|
-
}
|
|
692
|
-
// Add runners to their projects
|
|
693
|
-
for (const runner of runners) {
|
|
694
|
-
const projectPath = runner.project_path;
|
|
695
|
-
if (!projectPath)
|
|
696
|
-
continue;
|
|
697
|
-
if (!projectMap.has(projectPath)) {
|
|
698
|
-
projectMap.set(projectPath, {
|
|
699
|
-
path: projectPath,
|
|
700
|
-
name: (0, node_path_1.basename)(projectPath),
|
|
701
|
-
runners: [],
|
|
702
|
-
activeTasks: [],
|
|
703
|
-
});
|
|
704
|
-
}
|
|
705
|
-
const info = projectMap.get(projectPath);
|
|
706
|
-
info.runners.push(runner);
|
|
707
|
-
}
|
|
708
|
-
// Fetch active tasks for each project
|
|
709
|
-
for (const [projectPath, info] of projectMap) {
|
|
710
|
-
const dbPath = `${projectPath}/.steroids/steroids.db`;
|
|
711
|
-
if (!(0, node_fs_1.existsSync)(dbPath))
|
|
712
|
-
continue;
|
|
713
|
-
try {
|
|
714
|
-
const { db, close } = (0, connection_js_1.openDatabase)(projectPath);
|
|
715
|
-
try {
|
|
716
|
-
const inProgress = (0, queries_js_1.listTasks)(db, { status: 'in_progress' });
|
|
717
|
-
const review = (0, queries_js_1.listTasks)(db, { status: 'review' });
|
|
718
|
-
info.activeTasks = [...inProgress, ...review];
|
|
719
|
-
}
|
|
720
|
-
finally {
|
|
721
|
-
close();
|
|
722
|
-
}
|
|
723
|
-
}
|
|
724
|
-
catch {
|
|
725
|
-
// Skip inaccessible projects
|
|
726
|
-
}
|
|
727
|
-
}
|
|
728
|
-
// JSON output
|
|
729
|
-
if (json) {
|
|
730
|
-
const output = Array.from(projectMap.values()).map((info) => ({
|
|
731
|
-
project: info.path,
|
|
732
|
-
name: info.name,
|
|
733
|
-
runners: info.runners.map((r) => ({
|
|
734
|
-
id: r.id,
|
|
735
|
-
status: r.status,
|
|
736
|
-
pid: r.pid,
|
|
737
|
-
currentTaskId: r.current_task_id,
|
|
738
|
-
alive: r.pid ? (0, lock_js_1.isProcessAlive)(r.pid) : false,
|
|
739
|
-
})),
|
|
740
|
-
activeTasks: info.activeTasks.map((t) => ({
|
|
741
|
-
id: t.id,
|
|
742
|
-
title: t.title,
|
|
743
|
-
status: t.status,
|
|
744
|
-
})),
|
|
745
|
-
}));
|
|
746
|
-
console.log(JSON.stringify({ projects: output }, null, 2));
|
|
747
|
-
return;
|
|
748
|
-
}
|
|
749
|
-
// Text tree view
|
|
750
|
-
const projectList = Array.from(projectMap.values());
|
|
751
|
-
const currentProject = process.cwd();
|
|
752
|
-
if (projectList.length === 0) {
|
|
753
|
-
console.log('No registered projects.');
|
|
754
|
-
return;
|
|
755
|
-
}
|
|
756
|
-
console.log('');
|
|
757
|
-
console.log('RUNNERS TREE');
|
|
758
|
-
console.log('═'.repeat(80));
|
|
759
|
-
for (let i = 0; i < projectList.length; i++) {
|
|
760
|
-
const info = projectList[i];
|
|
761
|
-
const isLast = i === projectList.length - 1;
|
|
762
|
-
const isCurrent = info.path === currentProject;
|
|
763
|
-
const currentMarker = isCurrent ? ' ← (current)' : '';
|
|
764
|
-
console.log('');
|
|
765
|
-
console.log(`📁 ${info.name}${currentMarker}`);
|
|
766
|
-
console.log(` ${info.path}`);
|
|
767
|
-
if (info.runners.length === 0) {
|
|
768
|
-
console.log(' └─ (no runners)');
|
|
769
|
-
}
|
|
770
|
-
else {
|
|
771
|
-
for (let j = 0; j < info.runners.length; j++) {
|
|
772
|
-
const runner = info.runners[j];
|
|
773
|
-
const isLastRunner = j === info.runners.length - 1;
|
|
774
|
-
const runnerPrefix = isLastRunner ? '└─' : '├─';
|
|
775
|
-
const childPrefix = isLastRunner ? ' ' : '│ ';
|
|
776
|
-
const alive = runner.pid && (0, lock_js_1.isProcessAlive)(runner.pid);
|
|
777
|
-
const statusIcon = alive ? '🟢' : '🔴';
|
|
778
|
-
const statusText = alive ? runner.status : 'dead';
|
|
779
|
-
const pidText = runner.pid ? ` PID ${runner.pid}` : '';
|
|
780
|
-
console.log(` ${runnerPrefix} ${statusIcon} Runner ${runner.id.substring(0, 8)} (${statusText}${pidText})`);
|
|
781
|
-
// Show section if focused
|
|
782
|
-
if (runner.section_id && runner.project_path) {
|
|
783
|
-
try {
|
|
784
|
-
const { db, close } = (0, connection_js_1.openDatabase)(runner.project_path);
|
|
785
|
-
try {
|
|
786
|
-
const section = (0, queries_js_1.getSection)(db, runner.section_id);
|
|
787
|
-
if (section) {
|
|
788
|
-
console.log(` ${childPrefix} Section: ${section.name}`);
|
|
789
|
-
}
|
|
790
|
-
}
|
|
791
|
-
finally {
|
|
792
|
-
close();
|
|
793
|
-
}
|
|
794
|
-
}
|
|
795
|
-
catch {
|
|
796
|
-
// Ignore section fetch errors
|
|
797
|
-
}
|
|
798
|
-
}
|
|
799
|
-
// Show current task if available
|
|
800
|
-
if (runner.current_task_id && runner.project_path) {
|
|
801
|
-
try {
|
|
802
|
-
const { db, close } = (0, connection_js_1.openDatabase)(runner.project_path);
|
|
803
|
-
try {
|
|
804
|
-
const task = (0, queries_js_1.getTask)(db, runner.current_task_id);
|
|
805
|
-
if (task) {
|
|
806
|
-
const statusMarker = task.status === 'in_progress' ? '🔧' : '👁️';
|
|
807
|
-
console.log(` ${childPrefix} ${statusMarker} ${task.title.substring(0, 50)}`);
|
|
808
|
-
console.log(` ${childPrefix} [${task.status}] ${task.id.substring(0, 8)}`);
|
|
809
|
-
}
|
|
810
|
-
}
|
|
811
|
-
finally {
|
|
812
|
-
close();
|
|
813
|
-
}
|
|
814
|
-
}
|
|
815
|
-
catch {
|
|
816
|
-
console.log(` ${childPrefix} Task: ${runner.current_task_id.substring(0, 8)}`);
|
|
817
|
-
}
|
|
818
|
-
}
|
|
819
|
-
else if (alive) {
|
|
820
|
-
console.log(` ${childPrefix} (idle - no task)`);
|
|
821
|
-
}
|
|
822
|
-
}
|
|
823
|
-
}
|
|
824
|
-
// Show other active tasks not being worked on by runners
|
|
825
|
-
const runnerTaskIds = new Set(info.runners.map((r) => r.current_task_id).filter(Boolean));
|
|
826
|
-
const unassignedTasks = info.activeTasks.filter((t) => !runnerTaskIds.has(t.id));
|
|
827
|
-
if (unassignedTasks.length > 0) {
|
|
828
|
-
console.log(' │');
|
|
829
|
-
console.log(' └─ 📋 Queued active tasks:');
|
|
830
|
-
for (const task of unassignedTasks.slice(0, 5)) {
|
|
831
|
-
const statusIcon = task.status === 'in_progress' ? '🔧' : '👁️';
|
|
832
|
-
console.log(` ${statusIcon} ${task.title.substring(0, 45)} [${task.status}]`);
|
|
833
|
-
}
|
|
834
|
-
if (unassignedTasks.length > 5) {
|
|
835
|
-
console.log(` ... and ${unassignedTasks.length - 5} more`);
|
|
836
|
-
}
|
|
837
|
-
}
|
|
838
|
-
}
|
|
839
|
-
console.log('');
|
|
840
|
-
console.log('═'.repeat(80));
|
|
841
|
-
// Multi-project warning
|
|
842
|
-
const activeProjects = projectList.filter((p) => p.runners.length > 0);
|
|
843
|
-
if (activeProjects.length > 1) {
|
|
844
|
-
console.log('');
|
|
845
|
-
console.log('⚠️ MULTI-PROJECT: Multiple projects have active runners.');
|
|
846
|
-
console.log(' Each runner works ONLY on its own project.');
|
|
847
|
-
}
|
|
848
|
-
}
|
|
849
|
-
async function runLogs(args) {
|
|
850
|
-
const { values, positionals } = (0, node_util_1.parseArgs)({
|
|
851
|
-
args,
|
|
852
|
-
options: {
|
|
853
|
-
help: { type: 'boolean', short: 'h', default: false },
|
|
854
|
-
json: { type: 'boolean', short: 'j', default: false },
|
|
855
|
-
tail: { type: 'string', short: 'n', default: '50' },
|
|
856
|
-
follow: { type: 'boolean', short: 'f', default: false },
|
|
857
|
-
clear: { type: 'boolean', default: false },
|
|
858
|
-
},
|
|
859
|
-
allowPositionals: true,
|
|
860
|
-
});
|
|
861
|
-
if (values.help) {
|
|
862
|
-
console.log(`
|
|
863
|
-
steroids runners logs - View daemon crash/output logs
|
|
864
|
-
|
|
865
|
-
USAGE:
|
|
866
|
-
steroids runners logs [pid] [options]
|
|
867
|
-
|
|
868
|
-
OPTIONS:
|
|
869
|
-
<pid> Show logs for specific daemon PID
|
|
870
|
-
--tail <n> Show last n lines (default: 50)
|
|
871
|
-
--follow Follow log output (latest log)
|
|
872
|
-
--clear Clear all daemon logs
|
|
873
|
-
-j, --json Output as JSON
|
|
874
|
-
-h, --help Show help
|
|
875
|
-
|
|
876
|
-
LOG LOCATION:
|
|
877
|
-
Logs are stored in ~/.steroids/runners/logs/
|
|
878
|
-
Each daemon gets its own log file: daemon-<pid>.log
|
|
879
|
-
|
|
880
|
-
To disable daemon logging, set in config:
|
|
881
|
-
steroids config set runners.daemonLogs false
|
|
882
|
-
|
|
883
|
-
EXAMPLES:
|
|
884
|
-
steroids runners logs # List available log files
|
|
885
|
-
steroids runners logs 12345 # View logs for PID 12345
|
|
886
|
-
steroids runners logs --follow # Follow the latest log
|
|
887
|
-
steroids runners logs --clear # Remove all log files
|
|
888
|
-
`);
|
|
889
|
-
return;
|
|
890
|
-
}
|
|
891
|
-
const logsDir = path.join(os.homedir(), '.steroids', 'runners', 'logs');
|
|
892
|
-
// Handle --clear
|
|
893
|
-
if (values.clear) {
|
|
894
|
-
if (!fs.existsSync(logsDir)) {
|
|
895
|
-
if (values.json) {
|
|
896
|
-
console.log(JSON.stringify({ success: true, cleared: 0 }));
|
|
897
|
-
}
|
|
898
|
-
else {
|
|
899
|
-
console.log('No logs directory found');
|
|
900
|
-
}
|
|
901
|
-
return;
|
|
902
|
-
}
|
|
903
|
-
const files = fs.readdirSync(logsDir).filter((f) => f.endsWith('.log'));
|
|
904
|
-
for (const file of files) {
|
|
905
|
-
fs.unlinkSync(path.join(logsDir, file));
|
|
906
|
-
}
|
|
907
|
-
if (values.json) {
|
|
908
|
-
console.log(JSON.stringify({ success: true, cleared: files.length }));
|
|
909
|
-
}
|
|
910
|
-
else {
|
|
911
|
-
console.log(`Cleared ${files.length} log file(s)`);
|
|
912
|
-
}
|
|
913
|
-
return;
|
|
914
|
-
}
|
|
915
|
-
// Ensure logs directory exists
|
|
916
|
-
if (!fs.existsSync(logsDir)) {
|
|
917
|
-
if (values.json) {
|
|
918
|
-
console.log(JSON.stringify({ logs: [], logsDir }));
|
|
919
|
-
}
|
|
920
|
-
else {
|
|
921
|
-
console.log('No daemon logs found');
|
|
922
|
-
console.log(` Logs are stored in: ${logsDir}`);
|
|
923
|
-
}
|
|
924
|
-
return;
|
|
925
|
-
}
|
|
926
|
-
const logFiles = fs.readdirSync(logsDir)
|
|
927
|
-
.filter((f) => f.startsWith('daemon-') && f.endsWith('.log'))
|
|
928
|
-
.map((f) => {
|
|
929
|
-
const filePath = path.join(logsDir, f);
|
|
930
|
-
const stats = fs.statSync(filePath);
|
|
931
|
-
const pidMatch = f.match(/daemon-(\d+)\.log/);
|
|
932
|
-
return {
|
|
933
|
-
file: f,
|
|
934
|
-
path: filePath,
|
|
935
|
-
pid: pidMatch ? parseInt(pidMatch[1], 10) : null,
|
|
936
|
-
size: stats.size,
|
|
937
|
-
modified: stats.mtime,
|
|
938
|
-
};
|
|
939
|
-
})
|
|
940
|
-
.sort((a, b) => b.modified.getTime() - a.modified.getTime());
|
|
941
|
-
// If a PID is specified, show that log
|
|
942
|
-
if (positionals.length > 0) {
|
|
943
|
-
const pidArg = positionals[0];
|
|
944
|
-
const logFile = logFiles.find((l) => l.pid?.toString() === pidArg || l.file.includes(pidArg));
|
|
945
|
-
if (!logFile) {
|
|
946
|
-
console.error(`No log found for PID: ${pidArg}`);
|
|
947
|
-
process.exit(1);
|
|
948
|
-
}
|
|
949
|
-
const content = fs.readFileSync(logFile.path, 'utf-8');
|
|
950
|
-
const lines = content.split('\n');
|
|
951
|
-
const tailLines = parseInt(values.tail, 10) || 50;
|
|
952
|
-
const output = lines.slice(-tailLines).join('\n');
|
|
953
|
-
if (values.json) {
|
|
954
|
-
console.log(JSON.stringify({ pid: logFile.pid, path: logFile.path, content: output }));
|
|
955
|
-
}
|
|
956
|
-
else {
|
|
957
|
-
console.log(`=== Daemon log for PID ${logFile.pid} ===`);
|
|
958
|
-
console.log(`File: ${logFile.path}`);
|
|
959
|
-
console.log(`Modified: ${logFile.modified.toISOString()}`);
|
|
960
|
-
console.log('─'.repeat(60));
|
|
961
|
-
console.log(output);
|
|
962
|
-
}
|
|
963
|
-
return;
|
|
964
|
-
}
|
|
965
|
-
// If --follow, tail the most recent log
|
|
966
|
-
if (values.follow) {
|
|
967
|
-
if (logFiles.length === 0) {
|
|
968
|
-
console.error('No log files to follow');
|
|
969
|
-
process.exit(1);
|
|
970
|
-
}
|
|
971
|
-
const latestLog = logFiles[0];
|
|
972
|
-
console.log(`Following: ${latestLog.path} (PID: ${latestLog.pid})`);
|
|
973
|
-
console.log('─'.repeat(60));
|
|
974
|
-
// Use spawn to tail -f
|
|
975
|
-
const tail = (0, node_child_process_1.spawn)('tail', ['-f', latestLog.path], { stdio: 'inherit' });
|
|
976
|
-
tail.on('error', (err) => {
|
|
977
|
-
console.error(`Error following log: ${err.message}`);
|
|
978
|
-
process.exit(1);
|
|
979
|
-
});
|
|
980
|
-
return;
|
|
981
|
-
}
|
|
982
|
-
// List all log files
|
|
983
|
-
if (values.json) {
|
|
984
|
-
console.log(JSON.stringify({ logs: logFiles, logsDir }, null, 2));
|
|
985
|
-
return;
|
|
986
|
-
}
|
|
987
|
-
if (logFiles.length === 0) {
|
|
988
|
-
console.log('No daemon logs found');
|
|
989
|
-
console.log(` Logs are stored in: ${logsDir}`);
|
|
990
|
-
return;
|
|
991
|
-
}
|
|
992
|
-
console.log('DAEMON LOGS');
|
|
993
|
-
console.log('─'.repeat(80));
|
|
994
|
-
console.log('PID SIZE MODIFIED FILE');
|
|
995
|
-
console.log('─'.repeat(80));
|
|
996
|
-
for (const log of logFiles) {
|
|
997
|
-
const pid = (log.pid?.toString() ?? 'unknown').padEnd(10);
|
|
998
|
-
const size = formatBytes(log.size).padEnd(9);
|
|
999
|
-
const modified = log.modified.toISOString().substring(0, 19).padEnd(22);
|
|
1000
|
-
console.log(`${pid} ${size} ${modified} ${log.file}`);
|
|
1001
|
-
}
|
|
1002
|
-
console.log('');
|
|
1003
|
-
console.log(`Logs directory: ${logsDir}`);
|
|
1004
|
-
console.log(`Use 'steroids runners logs <pid>' to view a specific log`);
|
|
1005
|
-
}
|
|
1006
|
-
function formatBytes(bytes) {
|
|
1007
|
-
if (bytes === 0)
|
|
1008
|
-
return '0 B';
|
|
1009
|
-
const k = 1024;
|
|
1010
|
-
const sizes = ['B', 'KB', 'MB', 'GB'];
|
|
1011
|
-
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
|
1012
|
-
return parseFloat((bytes / Math.pow(k, i)).toFixed(1)) + ' ' + sizes[i];
|
|
1013
|
-
}
|
|
1014
|
-
async function runWakeup(args, flags) {
|
|
1015
|
-
// Hard timeout: wakeup is spawned by cron/launchd every 60s.
|
|
1016
|
-
// If the wakeup function itself hangs (locked DB, TCC blocking file access),
|
|
1017
|
-
// force-kill after 30s. SIGKILL is needed because process.exit() can also
|
|
1018
|
-
// hang if native addon destructors (better-sqlite3) try to fsync blocked paths.
|
|
1019
|
-
const WAKEUP_TIMEOUT_MS = 30_000;
|
|
1020
|
-
const killTimer = setTimeout(() => process.kill(process.pid, 'SIGKILL'), WAKEUP_TIMEOUT_MS);
|
|
1021
|
-
killTimer.unref();
|
|
1022
|
-
const { values } = (0, node_util_1.parseArgs)({
|
|
1023
|
-
args,
|
|
1024
|
-
options: {
|
|
1025
|
-
help: { type: 'boolean', short: 'h', default: false },
|
|
1026
|
-
json: { type: 'boolean', short: 'j', default: false },
|
|
1027
|
-
quiet: { type: 'boolean', short: 'q', default: false },
|
|
1028
|
-
},
|
|
1029
|
-
allowPositionals: false,
|
|
1030
|
-
});
|
|
1031
|
-
if (values.help) {
|
|
1032
|
-
console.log(`
|
|
1033
|
-
steroids runners wakeup - Check and restart stale runners
|
|
1034
|
-
|
|
1035
|
-
USAGE:
|
|
1036
|
-
steroids runners wakeup [options]
|
|
1037
|
-
|
|
1038
|
-
OPTIONS:
|
|
1039
|
-
--quiet Suppress output (for cron)
|
|
1040
|
-
--dry-run Check without acting
|
|
1041
|
-
-j, --json Output as JSON
|
|
1042
|
-
-h, --help Show help
|
|
1043
|
-
`);
|
|
1044
|
-
return;
|
|
1045
|
-
}
|
|
1046
|
-
const results = await (0, wakeup_js_1.wakeup)({
|
|
1047
|
-
quiet: values.quiet || flags.quiet || values.json || flags.json,
|
|
1048
|
-
dryRun: flags.dryRun,
|
|
1049
|
-
});
|
|
1050
|
-
if (values.json || flags.json) {
|
|
1051
|
-
console.log(JSON.stringify({ results }, null, 2));
|
|
1052
|
-
return;
|
|
1053
|
-
}
|
|
1054
|
-
if (!values.quiet && !flags.quiet) {
|
|
1055
|
-
// Summarize results
|
|
1056
|
-
const started = results.filter(r => r.action === 'started').length;
|
|
1057
|
-
const cleaned = results.filter(r => r.action === 'cleaned').length;
|
|
1058
|
-
const wouldStart = results.filter(r => r.action === 'would_start').length;
|
|
1059
|
-
if (started > 0) {
|
|
1060
|
-
console.log(`Started ${started} runner(s)`);
|
|
1061
|
-
}
|
|
1062
|
-
if (cleaned > 0) {
|
|
1063
|
-
console.log(`Cleaned ${cleaned} stale runner(s)`);
|
|
1064
|
-
}
|
|
1065
|
-
if (wouldStart > 0) {
|
|
1066
|
-
console.log(`Would start ${wouldStart} runner(s) (dry-run)`);
|
|
1067
|
-
}
|
|
1068
|
-
if (started === 0 && cleaned === 0 && wouldStart === 0) {
|
|
1069
|
-
console.log('No action needed');
|
|
1070
|
-
}
|
|
1071
|
-
// Show per-project details
|
|
1072
|
-
for (const result of results) {
|
|
1073
|
-
if (result.projectPath) {
|
|
1074
|
-
const status = result.action === 'started' ? '✓' :
|
|
1075
|
-
result.action === 'would_start' ? '~' : '-';
|
|
1076
|
-
console.log(` ${status} ${result.projectPath}: ${result.reason}`);
|
|
1077
|
-
}
|
|
1078
|
-
}
|
|
1079
|
-
}
|
|
1080
|
-
// Two-phase exit for wakeup (cron/launchd spawns this every 60s):
|
|
1081
|
-
// 1. Try graceful exit first (exit code 0, clean shutdown)
|
|
1082
|
-
// 2. If process.exit() hangs (e.g. better-sqlite3 destructors fsyncing WAL
|
|
1083
|
-
// files on TCC-protected paths), SIGKILL fires after 2s as a backstop.
|
|
1084
|
-
setTimeout(() => process.kill(process.pid, 'SIGKILL'), 2000).unref();
|
|
1085
|
-
process.exit(0);
|
|
1086
|
-
}
|
|
1087
|
-
async function runCron(args) {
|
|
1088
|
-
if (args.length === 0 || args[0] === '-h' || args[0] === '--help') {
|
|
1089
|
-
console.log(`
|
|
1090
|
-
steroids runners cron - Manage cron job
|
|
1091
|
-
|
|
1092
|
-
USAGE:
|
|
1093
|
-
steroids runners cron <subcommand>
|
|
1094
|
-
|
|
1095
|
-
SUBCOMMANDS:
|
|
1096
|
-
install Add cron job (every minute)
|
|
1097
|
-
uninstall Remove cron job
|
|
1098
|
-
status Check cron status
|
|
1099
|
-
|
|
1100
|
-
OPTIONS:
|
|
1101
|
-
-j, --json Output as JSON
|
|
1102
|
-
-h, --help Show help
|
|
1103
|
-
`);
|
|
1104
|
-
return;
|
|
1105
|
-
}
|
|
1106
|
-
const subcommand = args[0];
|
|
1107
|
-
const subArgs = args.slice(1);
|
|
1108
|
-
const { values } = (0, node_util_1.parseArgs)({
|
|
1109
|
-
args: subArgs,
|
|
1110
|
-
options: {
|
|
1111
|
-
json: { type: 'boolean', short: 'j', default: false },
|
|
1112
|
-
},
|
|
1113
|
-
allowPositionals: false,
|
|
1114
|
-
});
|
|
1115
|
-
switch (subcommand) {
|
|
1116
|
-
case 'install': {
|
|
1117
|
-
const result = (0, cron_js_1.cronInstall)();
|
|
1118
|
-
if (values.json) {
|
|
1119
|
-
console.log(JSON.stringify(result, null, 2));
|
|
1120
|
-
}
|
|
1121
|
-
else {
|
|
1122
|
-
console.log(result.message);
|
|
1123
|
-
if (result.error) {
|
|
1124
|
-
console.error(result.error);
|
|
1125
|
-
}
|
|
1126
|
-
}
|
|
1127
|
-
break;
|
|
1128
|
-
}
|
|
1129
|
-
case 'uninstall': {
|
|
1130
|
-
const result = (0, cron_js_1.cronUninstall)();
|
|
1131
|
-
if (values.json) {
|
|
1132
|
-
console.log(JSON.stringify(result, null, 2));
|
|
1133
|
-
}
|
|
1134
|
-
else {
|
|
1135
|
-
console.log(result.message);
|
|
1136
|
-
}
|
|
1137
|
-
break;
|
|
1138
|
-
}
|
|
1139
|
-
case 'status': {
|
|
1140
|
-
const status = (0, cron_js_1.cronStatus)();
|
|
1141
|
-
if (values.json) {
|
|
1142
|
-
console.log(JSON.stringify(status, null, 2));
|
|
1143
|
-
}
|
|
1144
|
-
else {
|
|
1145
|
-
if (status.installed) {
|
|
1146
|
-
console.log('Cron job: INSTALLED');
|
|
1147
|
-
console.log(` Entry: ${status.entry}`);
|
|
1148
|
-
}
|
|
1149
|
-
else {
|
|
1150
|
-
console.log('Cron job: NOT INSTALLED');
|
|
1151
|
-
if (status.error) {
|
|
1152
|
-
console.log(` ${status.error}`);
|
|
1153
|
-
}
|
|
1154
|
-
}
|
|
1155
|
-
}
|
|
1156
|
-
break;
|
|
1157
|
-
}
|
|
1158
|
-
default:
|
|
1159
|
-
console.error(`Unknown cron subcommand: ${subcommand}`);
|
|
1160
|
-
process.exit(1);
|
|
1161
|
-
}
|
|
412
|
+
return (0, runners_management_js_1.runStatus)(args);
|
|
1162
413
|
}
|
|
1163
414
|
//# sourceMappingURL=runners.js.map
|