steroids-cli 0.8.25 → 0.8.27

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.
Files changed (73) hide show
  1. package/dist/commands/ai.js +7 -6
  2. package/dist/commands/ai.js.map +1 -1
  3. package/dist/commands/completion.d.ts.map +1 -1
  4. package/dist/commands/completion.js +1 -0
  5. package/dist/commands/completion.js.map +1 -1
  6. package/dist/commands/config.js +10 -7
  7. package/dist/commands/config.js.map +1 -1
  8. package/dist/commands/llm.d.ts.map +1 -1
  9. package/dist/commands/llm.js +5 -0
  10. package/dist/commands/llm.js.map +1 -1
  11. package/dist/commands/merge.d.ts +3 -0
  12. package/dist/commands/merge.d.ts.map +1 -0
  13. package/dist/commands/merge.js +234 -0
  14. package/dist/commands/merge.js.map +1 -0
  15. package/dist/commands/runners-list.d.ts +3 -0
  16. package/dist/commands/runners-list.d.ts.map +1 -0
  17. package/dist/commands/runners-list.js +287 -0
  18. package/dist/commands/runners-list.js.map +1 -0
  19. package/dist/commands/runners-logs.d.ts +2 -0
  20. package/dist/commands/runners-logs.d.ts.map +1 -0
  21. package/dist/commands/runners-logs.js +207 -0
  22. package/dist/commands/runners-logs.js.map +1 -0
  23. package/dist/commands/runners-management.d.ts +3 -0
  24. package/dist/commands/runners-management.d.ts.map +1 -0
  25. package/dist/commands/runners-management.js +205 -0
  26. package/dist/commands/runners-management.js.map +1 -0
  27. package/dist/commands/runners-wakeup.d.ts +4 -0
  28. package/dist/commands/runners-wakeup.d.ts.map +1 -0
  29. package/dist/commands/runners-wakeup.js +157 -0
  30. package/dist/commands/runners-wakeup.js.map +1 -0
  31. package/dist/commands/runners.d.ts.map +1 -1
  32. package/dist/commands/runners.js +10 -759
  33. package/dist/commands/runners.js.map +1 -1
  34. package/dist/commands/workspaces.d.ts +3 -0
  35. package/dist/commands/workspaces.d.ts.map +1 -0
  36. package/dist/commands/workspaces.js +409 -0
  37. package/dist/commands/workspaces.js.map +1 -0
  38. package/dist/config/ai-setup.d.ts +1 -1
  39. package/dist/config/ai-setup.d.ts.map +1 -1
  40. package/dist/config/ai-setup.js +4 -2
  41. package/dist/config/ai-setup.js.map +1 -1
  42. package/dist/config/loader.d.ts +3 -3
  43. package/dist/config/loader.d.ts.map +1 -1
  44. package/dist/config/loader.js +1 -0
  45. package/dist/config/loader.js.map +1 -1
  46. package/dist/config/schema.js +3 -3
  47. package/dist/config/schema.js.map +1 -1
  48. package/dist/index.js +10 -0
  49. package/dist/index.js.map +1 -1
  50. package/dist/providers/api-models.d.ts +11 -6
  51. package/dist/providers/api-models.d.ts.map +1 -1
  52. package/dist/providers/api-models.js +116 -0
  53. package/dist/providers/api-models.js.map +1 -1
  54. package/dist/providers/codex.d.ts +1 -1
  55. package/dist/providers/codex.d.ts.map +1 -1
  56. package/dist/providers/codex.js +1 -1
  57. package/dist/providers/codex.js.map +1 -1
  58. package/dist/providers/index.d.ts +2 -1
  59. package/dist/providers/index.d.ts.map +1 -1
  60. package/dist/providers/index.js +6 -1
  61. package/dist/providers/index.js.map +1 -1
  62. package/dist/providers/mistral.d.ts +58 -0
  63. package/dist/providers/mistral.d.ts.map +1 -0
  64. package/dist/providers/mistral.js +310 -0
  65. package/dist/providers/mistral.js.map +1 -0
  66. package/dist/providers/registry.d.ts.map +1 -1
  67. package/dist/providers/registry.js +2 -0
  68. package/dist/providers/registry.js.map +1 -1
  69. package/dist/runners/wakeup.d.ts +1 -0
  70. package/dist/runners/wakeup.d.ts.map +1 -1
  71. package/dist/runners/wakeup.js +27 -0
  72. package/dist/runners/wakeup.js.map +1 -1
  73. package/package.json +1 -1
@@ -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
- const { values } = (0, node_util_1.parseArgs)({
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
- const { values } = (0, node_util_1.parseArgs)({
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