@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.
Files changed (2) hide show
  1. package/dist/commands/vm.js +225 -0
  2. package/package.json +1 -1
@@ -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);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@simonfestl/husky-cli",
3
- "version": "0.6.2",
3
+ "version": "0.7.0",
4
4
  "description": "CLI for Huskyv0 Task Orchestration with Claude Agent SDK",
5
5
  "type": "module",
6
6
  "bin": {