@simonfestl/husky-cli 0.6.2 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/commands/vm.js +225 -0
- package/package.json +1 -1
package/dist/commands/vm.js
CHANGED
|
@@ -569,6 +569,13 @@ function printVMSessionDetail(session) {
|
|
|
569
569
|
if (session.workflowId) {
|
|
570
570
|
console.log(` Workflow ID: ${session.workflowId}`);
|
|
571
571
|
}
|
|
572
|
+
// Pool info
|
|
573
|
+
if (session.sessionType) {
|
|
574
|
+
console.log(` Session Type: ${session.sessionType === 'pool' ? 'Pool VM' : 'On-Demand'}`);
|
|
575
|
+
if (session.poolVmId) {
|
|
576
|
+
console.log(` Pool VM ID: ${session.poolVmId}`);
|
|
577
|
+
}
|
|
578
|
+
}
|
|
572
579
|
console.log(`\n Prompt:`);
|
|
573
580
|
console.log(` ${session.prompt}`);
|
|
574
581
|
if (session.costEstimate !== undefined) {
|
|
@@ -619,3 +626,221 @@ function printLog(log) {
|
|
|
619
626
|
}
|
|
620
627
|
console.log(`${prefix}${timestamp} ${level} ${source} ${log.message}`);
|
|
621
628
|
}
|
|
629
|
+
// ============================================
|
|
630
|
+
// VM POOL COMMANDS
|
|
631
|
+
// ============================================
|
|
632
|
+
const poolCommand = new Command("pool")
|
|
633
|
+
.description("Manage VM pool (admin)");
|
|
634
|
+
// husky vm pool status
|
|
635
|
+
poolCommand
|
|
636
|
+
.command("status")
|
|
637
|
+
.description("Show VM pool status")
|
|
638
|
+
.option("--json", "Output as JSON")
|
|
639
|
+
.action(async (options) => {
|
|
640
|
+
const config = ensureConfig();
|
|
641
|
+
try {
|
|
642
|
+
const res = await fetch(`${config.apiUrl}/api/admin/vm-pool`, {
|
|
643
|
+
headers: config.apiKey ? { "x-api-key": config.apiKey } : {},
|
|
644
|
+
});
|
|
645
|
+
if (!res.ok) {
|
|
646
|
+
const errorData = await res.json().catch(() => ({}));
|
|
647
|
+
throw new Error(errorData.error || `API error: ${res.status}`);
|
|
648
|
+
}
|
|
649
|
+
const data = await res.json();
|
|
650
|
+
const vms = data.vms || [];
|
|
651
|
+
if (options.json) {
|
|
652
|
+
console.log(JSON.stringify(data, null, 2));
|
|
653
|
+
}
|
|
654
|
+
else {
|
|
655
|
+
printPoolStatus(vms);
|
|
656
|
+
}
|
|
657
|
+
}
|
|
658
|
+
catch (error) {
|
|
659
|
+
console.error("Error fetching VM pool status:", error);
|
|
660
|
+
process.exit(1);
|
|
661
|
+
}
|
|
662
|
+
});
|
|
663
|
+
// husky vm pool init
|
|
664
|
+
poolCommand
|
|
665
|
+
.command("init")
|
|
666
|
+
.description("Initialize VM pool (1 HOT + 4 COLD VMs)")
|
|
667
|
+
.option("--json", "Output as JSON")
|
|
668
|
+
.action(async (options) => {
|
|
669
|
+
const config = ensureConfig();
|
|
670
|
+
console.log("Initializing VM pool...");
|
|
671
|
+
console.log("This will create 1 HOT VM + 4 COLD VMs\n");
|
|
672
|
+
try {
|
|
673
|
+
const res = await fetch(`${config.apiUrl}/api/admin/vm-pool/initialize`, {
|
|
674
|
+
method: "POST",
|
|
675
|
+
headers: {
|
|
676
|
+
"Content-Type": "application/json",
|
|
677
|
+
...(config.apiKey ? { "x-api-key": config.apiKey } : {}),
|
|
678
|
+
},
|
|
679
|
+
});
|
|
680
|
+
if (!res.ok) {
|
|
681
|
+
const errorData = await res.json().catch(() => ({}));
|
|
682
|
+
throw new Error(errorData.error || `API error: ${res.status}`);
|
|
683
|
+
}
|
|
684
|
+
const data = await res.json();
|
|
685
|
+
if (options.json) {
|
|
686
|
+
console.log(JSON.stringify(data, null, 2));
|
|
687
|
+
}
|
|
688
|
+
else {
|
|
689
|
+
console.log(`VM Pool initialized!`);
|
|
690
|
+
console.log(` HOT VMs: ${data.hotCount || 1}`);
|
|
691
|
+
console.log(` COLD VMs: ${data.coldCount || 4}`);
|
|
692
|
+
console.log(`\nRun 'husky vm pool status' to see pool details`);
|
|
693
|
+
}
|
|
694
|
+
}
|
|
695
|
+
catch (error) {
|
|
696
|
+
console.error("Error initializing VM pool:", error);
|
|
697
|
+
process.exit(1);
|
|
698
|
+
}
|
|
699
|
+
});
|
|
700
|
+
// husky vm pool suspend <id>
|
|
701
|
+
poolCommand
|
|
702
|
+
.command("suspend <id>")
|
|
703
|
+
.description("Suspend a pool VM")
|
|
704
|
+
.option("--json", "Output as JSON")
|
|
705
|
+
.action(async (id, options) => {
|
|
706
|
+
const config = ensureConfig();
|
|
707
|
+
console.log(`Suspending VM ${id}...\n`);
|
|
708
|
+
try {
|
|
709
|
+
const res = await fetch(`${config.apiUrl}/api/admin/vm-pool/${id}/suspend`, {
|
|
710
|
+
method: "POST",
|
|
711
|
+
headers: {
|
|
712
|
+
"Content-Type": "application/json",
|
|
713
|
+
...(config.apiKey ? { "x-api-key": config.apiKey } : {}),
|
|
714
|
+
},
|
|
715
|
+
});
|
|
716
|
+
if (!res.ok) {
|
|
717
|
+
const errorData = await res.json().catch(() => ({}));
|
|
718
|
+
throw new Error(errorData.error || `API error: ${res.status}`);
|
|
719
|
+
}
|
|
720
|
+
const data = await res.json();
|
|
721
|
+
if (options.json) {
|
|
722
|
+
console.log(JSON.stringify(data, null, 2));
|
|
723
|
+
}
|
|
724
|
+
else {
|
|
725
|
+
console.log(`VM suspended successfully`);
|
|
726
|
+
console.log(` VM Name: ${data.vmName}`);
|
|
727
|
+
}
|
|
728
|
+
}
|
|
729
|
+
catch (error) {
|
|
730
|
+
console.error("Error suspending VM:", error);
|
|
731
|
+
process.exit(1);
|
|
732
|
+
}
|
|
733
|
+
});
|
|
734
|
+
// husky vm pool resume <id>
|
|
735
|
+
poolCommand
|
|
736
|
+
.command("resume <id>")
|
|
737
|
+
.description("Resume a suspended pool VM")
|
|
738
|
+
.option("--json", "Output as JSON")
|
|
739
|
+
.action(async (id, options) => {
|
|
740
|
+
const config = ensureConfig();
|
|
741
|
+
console.log(`Resuming VM ${id}...\n`);
|
|
742
|
+
try {
|
|
743
|
+
const res = await fetch(`${config.apiUrl}/api/admin/vm-pool/${id}/resume`, {
|
|
744
|
+
method: "POST",
|
|
745
|
+
headers: {
|
|
746
|
+
"Content-Type": "application/json",
|
|
747
|
+
...(config.apiKey ? { "x-api-key": config.apiKey } : {}),
|
|
748
|
+
},
|
|
749
|
+
});
|
|
750
|
+
if (!res.ok) {
|
|
751
|
+
const errorData = await res.json().catch(() => ({}));
|
|
752
|
+
throw new Error(errorData.error || `API error: ${res.status}`);
|
|
753
|
+
}
|
|
754
|
+
const data = await res.json();
|
|
755
|
+
if (options.json) {
|
|
756
|
+
console.log(JSON.stringify(data, null, 2));
|
|
757
|
+
}
|
|
758
|
+
else {
|
|
759
|
+
console.log(`VM resumed successfully`);
|
|
760
|
+
console.log(` VM Name: ${data.vmName}`);
|
|
761
|
+
}
|
|
762
|
+
}
|
|
763
|
+
catch (error) {
|
|
764
|
+
console.error("Error resuming VM:", error);
|
|
765
|
+
process.exit(1);
|
|
766
|
+
}
|
|
767
|
+
});
|
|
768
|
+
// husky vm pool setup <id>
|
|
769
|
+
poolCommand
|
|
770
|
+
.command("setup <id>")
|
|
771
|
+
.description("Start Claude subscription setup for a pool VM")
|
|
772
|
+
.option("--json", "Output as JSON")
|
|
773
|
+
.action(async (id, options) => {
|
|
774
|
+
const config = ensureConfig();
|
|
775
|
+
console.log(`Starting subscription setup for VM ${id}...\n`);
|
|
776
|
+
try {
|
|
777
|
+
const res = await fetch(`${config.apiUrl}/api/admin/vm-pool/${id}/setup`, {
|
|
778
|
+
method: "POST",
|
|
779
|
+
headers: {
|
|
780
|
+
"Content-Type": "application/json",
|
|
781
|
+
...(config.apiKey ? { "x-api-key": config.apiKey } : {}),
|
|
782
|
+
},
|
|
783
|
+
});
|
|
784
|
+
if (!res.ok) {
|
|
785
|
+
const errorData = await res.json().catch(() => ({}));
|
|
786
|
+
throw new Error(errorData.error || `API error: ${res.status}`);
|
|
787
|
+
}
|
|
788
|
+
const data = await res.json();
|
|
789
|
+
if (options.json) {
|
|
790
|
+
console.log(JSON.stringify(data, null, 2));
|
|
791
|
+
}
|
|
792
|
+
else {
|
|
793
|
+
console.log(`Setup session created!`);
|
|
794
|
+
console.log(` Session ID: ${data.sessionId}`);
|
|
795
|
+
console.log(` VM Name: ${data.vmName}`);
|
|
796
|
+
console.log(` VM IP: ${data.vmIpAddress || "pending"}`);
|
|
797
|
+
console.log(`\nInstructions:`);
|
|
798
|
+
for (const instruction of data.instructions || []) {
|
|
799
|
+
console.log(` ${instruction}`);
|
|
800
|
+
}
|
|
801
|
+
console.log(`\nExpires: ${data.expiresAt}`);
|
|
802
|
+
}
|
|
803
|
+
}
|
|
804
|
+
catch (error) {
|
|
805
|
+
console.error("Error starting setup:", error);
|
|
806
|
+
process.exit(1);
|
|
807
|
+
}
|
|
808
|
+
});
|
|
809
|
+
// Print helper for pool status
|
|
810
|
+
function printPoolStatus(vms) {
|
|
811
|
+
if (vms.length === 0) {
|
|
812
|
+
console.log("\n VM Pool is empty.");
|
|
813
|
+
console.log(" Initialize with: husky vm pool init\n");
|
|
814
|
+
return;
|
|
815
|
+
}
|
|
816
|
+
// Calculate stats
|
|
817
|
+
const total = vms.length;
|
|
818
|
+
const available = vms.filter(v => v.status === 'available').length;
|
|
819
|
+
const inUse = vms.filter(v => v.status === 'in_use').length;
|
|
820
|
+
const suspended = vms.filter(v => v.status === 'suspended').length;
|
|
821
|
+
const hotVms = vms.filter(v => v.tier === 'hot').length;
|
|
822
|
+
const authenticated = vms.filter(v => v.subscriptionAuth).length;
|
|
823
|
+
// Estimate monthly cost
|
|
824
|
+
const runningCost = (available + inUse) * 12.24; // $12.24/month for e2-small running
|
|
825
|
+
const suspendedCost = suspended * 0.30; // $0.30/month for suspended
|
|
826
|
+
const totalCost = runningCost + suspendedCost;
|
|
827
|
+
console.log("\n VM POOL STATUS");
|
|
828
|
+
console.log(" " + "=".repeat(70));
|
|
829
|
+
console.log(` Total: ${total} | Available: ${available} | In Use: ${inUse} | Suspended: ${suspended}`);
|
|
830
|
+
console.log(` HOT VMs: ${hotVms} | Authenticated: ${authenticated}/${total}`);
|
|
831
|
+
console.log(` Est. Monthly Cost: ~$${totalCost.toFixed(2)}`);
|
|
832
|
+
console.log(" " + "-".repeat(70));
|
|
833
|
+
console.log(` ${"ID".padEnd(24)} ${"NAME".padEnd(18)} ${"STATUS".padEnd(12)} ${"TIER".padEnd(6)} ${"AUTH".padEnd(5)} LAST USED`);
|
|
834
|
+
console.log(" " + "-".repeat(70));
|
|
835
|
+
for (const vm of vms) {
|
|
836
|
+
const status = vm.status.toUpperCase().padEnd(12);
|
|
837
|
+
const tier = vm.tier.toUpperCase().padEnd(6);
|
|
838
|
+
const auth = vm.subscriptionAuth ? "Yes" : "No";
|
|
839
|
+
const lastUsed = new Date(vm.lastUsedAt).toLocaleDateString();
|
|
840
|
+
const truncatedName = vm.vmName.length > 16 ? vm.vmName.substring(0, 13) + "..." : vm.vmName;
|
|
841
|
+
console.log(` ${vm.id.padEnd(24)} ${truncatedName.padEnd(18)} ${status} ${tier} ${auth.padEnd(5)} ${lastUsed}`);
|
|
842
|
+
}
|
|
843
|
+
console.log("");
|
|
844
|
+
}
|
|
845
|
+
// Add pool command to vm command
|
|
846
|
+
vmCommand.addCommand(poolCommand);
|