@simonfestl/husky-cli 0.3.0 → 0.5.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.
@@ -1,6 +1,7 @@
1
1
  import { Command } from "commander";
2
2
  import { getConfig } from "./config.js";
3
3
  import * as fs from "fs";
4
+ import * as readline from "readline";
4
5
  export const taskCommand = new Command("task")
5
6
  .description("Manage tasks");
6
7
  // Helper: Get task ID from --id flag or HUSKY_TASK_ID env var
@@ -21,6 +22,19 @@ function ensureConfig() {
21
22
  }
22
23
  return config;
23
24
  }
25
+ // Helper: Prompt for confirmation
26
+ async function confirm(message) {
27
+ const rl = readline.createInterface({
28
+ input: process.stdin,
29
+ output: process.stdout,
30
+ });
31
+ return new Promise((resolve) => {
32
+ rl.question(`${message} (y/N): `, (answer) => {
33
+ rl.close();
34
+ resolve(answer.toLowerCase() === "y" || answer.toLowerCase() === "yes");
35
+ });
36
+ });
37
+ }
24
38
  // husky task list
25
39
  taskCommand
26
40
  .command("list")
@@ -152,6 +166,132 @@ taskCommand
152
166
  process.exit(1);
153
167
  }
154
168
  });
169
+ // husky task update <id>
170
+ taskCommand
171
+ .command("update <id>")
172
+ .description("Update task properties")
173
+ .option("-t, --title <title>", "New title")
174
+ .option("-d, --description <desc>", "New description")
175
+ .option("--status <status>", "New status (backlog, in_progress, review, done)")
176
+ .option("--priority <priority>", "New priority (low, medium, high, urgent)")
177
+ .option("--assignee <assignee>", "New assignee (human, llm, unassigned)")
178
+ .option("--project <projectId>", "Link to project")
179
+ .option("--json", "Output as JSON")
180
+ .action(async (id, options) => {
181
+ const config = ensureConfig();
182
+ // Build update payload with only changed fields
183
+ const updates = {};
184
+ if (options.title)
185
+ updates.title = options.title;
186
+ if (options.description)
187
+ updates.description = options.description;
188
+ if (options.status)
189
+ updates.status = options.status;
190
+ if (options.priority)
191
+ updates.priority = options.priority;
192
+ if (options.assignee)
193
+ updates.assignee = options.assignee;
194
+ if (options.project)
195
+ updates.projectId = options.project;
196
+ if (Object.keys(updates).length === 0) {
197
+ console.error("Error: No update options provided. Use --help for available options.");
198
+ process.exit(1);
199
+ }
200
+ try {
201
+ const res = await fetch(`${config.apiUrl}/api/tasks/${id}`, {
202
+ method: "PATCH",
203
+ headers: {
204
+ "Content-Type": "application/json",
205
+ ...(config.apiKey ? { "x-api-key": config.apiKey } : {}),
206
+ },
207
+ body: JSON.stringify(updates),
208
+ });
209
+ if (!res.ok) {
210
+ if (res.status === 404) {
211
+ console.error(`Error: Task ${id} not found`);
212
+ }
213
+ else {
214
+ console.error(`Error: API returned ${res.status}`);
215
+ }
216
+ process.exit(1);
217
+ }
218
+ const task = await res.json();
219
+ if (options.json) {
220
+ console.log(JSON.stringify(task, null, 2));
221
+ }
222
+ else {
223
+ console.log(`✓ Updated: ${task.title}`);
224
+ const changedFields = Object.keys(updates).join(", ");
225
+ console.log(` Changed: ${changedFields}`);
226
+ }
227
+ }
228
+ catch (error) {
229
+ console.error("Error updating task:", error);
230
+ process.exit(1);
231
+ }
232
+ });
233
+ // husky task delete <id>
234
+ taskCommand
235
+ .command("delete <id>")
236
+ .description("Delete a task")
237
+ .option("--force", "Skip confirmation prompt")
238
+ .option("--json", "Output as JSON")
239
+ .action(async (id, options) => {
240
+ const config = ensureConfig();
241
+ // Confirm deletion unless --force is provided
242
+ if (!options.force) {
243
+ // First fetch task details to show what will be deleted
244
+ try {
245
+ const getRes = await fetch(`${config.apiUrl}/api/tasks/${id}`, {
246
+ headers: config.apiKey ? { "x-api-key": config.apiKey } : {},
247
+ });
248
+ if (!getRes.ok) {
249
+ if (getRes.status === 404) {
250
+ console.error(`Error: Task ${id} not found`);
251
+ }
252
+ else {
253
+ console.error(`Error: API returned ${getRes.status}`);
254
+ }
255
+ process.exit(1);
256
+ }
257
+ const task = await getRes.json();
258
+ const confirmed = await confirm(`Delete task "${task.title}" (${id})?`);
259
+ if (!confirmed) {
260
+ console.log("Deletion cancelled.");
261
+ process.exit(0);
262
+ }
263
+ }
264
+ catch (error) {
265
+ console.error("Error fetching task:", error);
266
+ process.exit(1);
267
+ }
268
+ }
269
+ try {
270
+ const res = await fetch(`${config.apiUrl}/api/tasks/${id}`, {
271
+ method: "DELETE",
272
+ headers: config.apiKey ? { "x-api-key": config.apiKey } : {},
273
+ });
274
+ if (!res.ok) {
275
+ if (res.status === 404) {
276
+ console.error(`Error: Task ${id} not found`);
277
+ }
278
+ else {
279
+ console.error(`Error: API returned ${res.status}`);
280
+ }
281
+ process.exit(1);
282
+ }
283
+ if (options.json) {
284
+ console.log(JSON.stringify({ deleted: true, id }, null, 2));
285
+ }
286
+ else {
287
+ console.log(`✓ Deleted task ${id}`);
288
+ }
289
+ }
290
+ catch (error) {
291
+ console.error("Error deleting task:", error);
292
+ process.exit(1);
293
+ }
294
+ });
155
295
  // husky task get [--id <id>] [--json]
156
296
  taskCommand
157
297
  .command("get")
@@ -601,6 +741,109 @@ taskCommand
601
741
  process.exit(1);
602
742
  }
603
743
  });
744
+ // ============================================
745
+ // MERGE CONFLICT RESOLUTION
746
+ // ============================================
747
+ // husky task merge-conflict --file <path> --ours <content> --theirs <content> [--base <content>] [--context <text>]
748
+ taskCommand
749
+ .command("merge-conflict")
750
+ .description("Resolve a Git merge conflict using AI")
751
+ .requiredOption("--file <path>", "Path to the conflicted file")
752
+ .requiredOption("--ours <content>", "Content from current branch (ours)")
753
+ .requiredOption("--theirs <content>", "Content from incoming branch (theirs)")
754
+ .option("--base <content>", "Content from common ancestor (for 3-way merge)")
755
+ .option("--context <text>", "Additional context about the merge")
756
+ .option("--json", "Output as JSON")
757
+ .option("--ours-file <path>", "Read ours content from file")
758
+ .option("--theirs-file <path>", "Read theirs content from file")
759
+ .option("--base-file <path>", "Read base content from file")
760
+ .action(async (options) => {
761
+ const config = ensureConfig();
762
+ // Read content from files if specified
763
+ let oursContent = options.ours;
764
+ let theirsContent = options.theirs;
765
+ let baseContent = options.base;
766
+ if (options.oursFile) {
767
+ try {
768
+ oursContent = fs.readFileSync(options.oursFile, "utf-8");
769
+ }
770
+ catch (error) {
771
+ console.error(`Error reading ours file ${options.oursFile}:`, error);
772
+ process.exit(1);
773
+ }
774
+ }
775
+ if (options.theirsFile) {
776
+ try {
777
+ theirsContent = fs.readFileSync(options.theirsFile, "utf-8");
778
+ }
779
+ catch (error) {
780
+ console.error(`Error reading theirs file ${options.theirsFile}:`, error);
781
+ process.exit(1);
782
+ }
783
+ }
784
+ if (options.baseFile) {
785
+ try {
786
+ baseContent = fs.readFileSync(options.baseFile, "utf-8");
787
+ }
788
+ catch (error) {
789
+ console.error(`Error reading base file ${options.baseFile}:`, error);
790
+ process.exit(1);
791
+ }
792
+ }
793
+ try {
794
+ const res = await fetch(`${config.apiUrl}/api/merge-conflict/resolve`, {
795
+ method: "POST",
796
+ headers: {
797
+ "Content-Type": "application/json",
798
+ ...(config.apiKey ? { "x-api-key": config.apiKey } : {}),
799
+ },
800
+ body: JSON.stringify({
801
+ filePath: options.file,
802
+ oursContent,
803
+ theirsContent,
804
+ baseContent,
805
+ context: options.context,
806
+ }),
807
+ });
808
+ if (!res.ok) {
809
+ const errorData = await res.json().catch(() => ({ error: `API error: ${res.status}` }));
810
+ throw new Error(errorData.error || `API error: ${res.status}`);
811
+ }
812
+ const result = await res.json();
813
+ if (options.json) {
814
+ console.log(JSON.stringify(result, null, 2));
815
+ }
816
+ else {
817
+ console.log(`\n Merge Conflict Resolution: ${options.file}`);
818
+ console.log(" " + "=".repeat(50));
819
+ console.log(`\n Confidence: ${result.confidence}%`);
820
+ // Confidence indicator
821
+ if (result.confidence >= 90) {
822
+ console.log(" Status: High confidence - safe to use");
823
+ }
824
+ else if (result.confidence >= 70) {
825
+ console.log(" Status: Good confidence - review recommended");
826
+ }
827
+ else if (result.confidence >= 50) {
828
+ console.log(" Status: Medium confidence - careful review needed");
829
+ }
830
+ else {
831
+ console.log(" Status: Low confidence - manual resolution recommended");
832
+ }
833
+ console.log(`\n Explanation:`);
834
+ console.log(` ${result.explanation}`);
835
+ console.log("\n " + "-".repeat(50));
836
+ console.log(" Resolved Content:");
837
+ console.log(" " + "-".repeat(50));
838
+ console.log(result.resolvedContent);
839
+ console.log("");
840
+ }
841
+ }
842
+ catch (error) {
843
+ console.error("Error resolving merge conflict:", error);
844
+ process.exit(1);
845
+ }
846
+ });
604
847
  function printTasks(tasks) {
605
848
  const byStatus = {
606
849
  backlog: [],
@@ -628,7 +871,7 @@ function printTasks(tasks) {
628
871
  for (const task of statusTasks) {
629
872
  const agentStr = task.agent ? ` (${task.agent})` : "";
630
873
  const doneStr = status === "done" ? " ✓" : "";
631
- console.log(` #${task.id.slice(0, 6)} ${task.title.padEnd(30)} ${task.priority}${agentStr}${doneStr}`);
874
+ console.log(` ${task.id} ${task.title.slice(0, 30).padEnd(30)} ${task.priority}${agentStr}${doneStr}`);
632
875
  }
633
876
  }
634
877
  console.log("");
@@ -0,0 +1,2 @@
1
+ import { Command } from "commander";
2
+ export declare const vmConfigCommand: Command;
@@ -0,0 +1,318 @@
1
+ import { Command } from "commander";
2
+ import { getConfig } from "./config.js";
3
+ import * as readline from "readline";
4
+ export const vmConfigCommand = new Command("vm-config")
5
+ .description("Manage VM configurations");
6
+ // Helper: Ensure API is configured
7
+ function ensureConfig() {
8
+ const config = getConfig();
9
+ if (!config.apiUrl) {
10
+ console.error("Error: API URL not configured. Run: husky config set api-url <url>");
11
+ process.exit(1);
12
+ }
13
+ return config;
14
+ }
15
+ // Helper: Prompt for confirmation
16
+ async function confirm(message) {
17
+ const rl = readline.createInterface({
18
+ input: process.stdin,
19
+ output: process.stdout,
20
+ });
21
+ return new Promise((resolve) => {
22
+ rl.question(`${message} (y/N): `, (answer) => {
23
+ rl.close();
24
+ resolve(answer.toLowerCase() === "y" || answer.toLowerCase() === "yes");
25
+ });
26
+ });
27
+ }
28
+ // husky vm-config list
29
+ vmConfigCommand
30
+ .command("list")
31
+ .description("List all VM configurations")
32
+ .option("--json", "Output as JSON")
33
+ .action(async (options) => {
34
+ const config = ensureConfig();
35
+ try {
36
+ const res = await fetch(`${config.apiUrl}/api/vm-configs`, {
37
+ headers: config.apiKey ? { "x-api-key": config.apiKey } : {},
38
+ });
39
+ if (!res.ok) {
40
+ throw new Error(`API error: ${res.status}`);
41
+ }
42
+ const configs = await res.json();
43
+ if (options.json) {
44
+ console.log(JSON.stringify(configs, null, 2));
45
+ }
46
+ else {
47
+ printVMConfigs(configs);
48
+ }
49
+ }
50
+ catch (error) {
51
+ console.error("Error fetching VM configs:", error);
52
+ process.exit(1);
53
+ }
54
+ });
55
+ // husky vm-config get <id>
56
+ vmConfigCommand
57
+ .command("get <id>")
58
+ .description("Get VM configuration details")
59
+ .option("--json", "Output as JSON")
60
+ .action(async (id, options) => {
61
+ const config = ensureConfig();
62
+ try {
63
+ const res = await fetch(`${config.apiUrl}/api/vm-configs/${id}`, {
64
+ headers: config.apiKey ? { "x-api-key": config.apiKey } : {},
65
+ });
66
+ if (!res.ok) {
67
+ if (res.status === 404) {
68
+ console.error(`Error: VM config ${id} not found`);
69
+ }
70
+ else {
71
+ console.error(`Error: API returned ${res.status}`);
72
+ }
73
+ process.exit(1);
74
+ }
75
+ const vmConfig = await res.json();
76
+ if (options.json) {
77
+ console.log(JSON.stringify(vmConfig, null, 2));
78
+ }
79
+ else {
80
+ printVMConfigDetail(vmConfig);
81
+ }
82
+ }
83
+ catch (error) {
84
+ console.error("Error fetching VM config:", error);
85
+ process.exit(1);
86
+ }
87
+ });
88
+ // husky vm-config create <name>
89
+ vmConfigCommand
90
+ .command("create <name>")
91
+ .description("Create a new VM configuration")
92
+ .option("--machine-type <type>", "GCP machine type (e.g., e2-medium)", "e2-medium")
93
+ .option("--zone <zone>", "GCP zone", "europe-west1-b")
94
+ .option("--disk-size <size>", "Disk size in GB", "20")
95
+ .option("--image <image>", "Base image")
96
+ .option("--max-runtime <minutes>", "Max runtime in minutes", "60")
97
+ .option("--max-concurrent <n>", "Max concurrent VMs", "3")
98
+ .option("--daily-budget <usd>", "Daily budget in USD", "10")
99
+ .option("--json", "Output as JSON")
100
+ .action(async (name, options) => {
101
+ const config = ensureConfig();
102
+ try {
103
+ const createPayload = {
104
+ name,
105
+ machineType: options.machineType,
106
+ zone: options.zone,
107
+ diskSizeGb: parseInt(options.diskSize, 10),
108
+ maxRuntimeMinutes: parseInt(options.maxRuntime, 10),
109
+ maxConcurrentVMs: parseInt(options.maxConcurrent, 10),
110
+ dailyBudgetUsd: parseFloat(options.dailyBudget),
111
+ };
112
+ if (options.image) {
113
+ createPayload.baseImage = options.image;
114
+ }
115
+ const res = await fetch(`${config.apiUrl}/api/vm-configs`, {
116
+ method: "POST",
117
+ headers: {
118
+ "Content-Type": "application/json",
119
+ ...(config.apiKey ? { "x-api-key": config.apiKey } : {}),
120
+ },
121
+ body: JSON.stringify(createPayload),
122
+ });
123
+ if (!res.ok) {
124
+ const errorData = await res.json().catch(() => ({}));
125
+ throw new Error(errorData.error || `API error: ${res.status}`);
126
+ }
127
+ const vmConfig = await res.json();
128
+ if (options.json) {
129
+ console.log(JSON.stringify(vmConfig, null, 2));
130
+ }
131
+ else {
132
+ console.log(`Created VM config: ${vmConfig.name}`);
133
+ console.log(` ID: ${vmConfig.id}`);
134
+ console.log(` Machine Type: ${vmConfig.machineType}`);
135
+ console.log(` Zone: ${vmConfig.zone}`);
136
+ console.log(` Disk Size: ${vmConfig.diskSizeGb} GB`);
137
+ }
138
+ }
139
+ catch (error) {
140
+ console.error("Error creating VM config:", error);
141
+ process.exit(1);
142
+ }
143
+ });
144
+ // husky vm-config update <id>
145
+ vmConfigCommand
146
+ .command("update <id>")
147
+ .description("Update a VM configuration")
148
+ .option("-n, --name <name>", "New name")
149
+ .option("--machine-type <type>", "GCP machine type (e.g., e2-medium)")
150
+ .option("--zone <zone>", "GCP zone")
151
+ .option("--disk-size <size>", "Disk size in GB")
152
+ .option("--image <image>", "Base image")
153
+ .option("--max-runtime <minutes>", "Max runtime in minutes")
154
+ .option("--max-concurrent <n>", "Max concurrent VMs")
155
+ .option("--daily-budget <usd>", "Daily budget in USD")
156
+ .option("--json", "Output as JSON")
157
+ .action(async (id, options) => {
158
+ const config = ensureConfig();
159
+ // Build update payload
160
+ const updateData = {};
161
+ if (options.name)
162
+ updateData.name = options.name;
163
+ if (options.machineType)
164
+ updateData.machineType = options.machineType;
165
+ if (options.zone)
166
+ updateData.zone = options.zone;
167
+ if (options.diskSize)
168
+ updateData.diskSizeGb = parseInt(options.diskSize, 10);
169
+ if (options.image)
170
+ updateData.baseImage = options.image;
171
+ if (options.maxRuntime)
172
+ updateData.maxRuntimeMinutes = parseInt(options.maxRuntime, 10);
173
+ if (options.maxConcurrent)
174
+ updateData.maxConcurrentVMs = parseInt(options.maxConcurrent, 10);
175
+ if (options.dailyBudget)
176
+ updateData.dailyBudgetUsd = parseFloat(options.dailyBudget);
177
+ if (Object.keys(updateData).length === 0) {
178
+ console.error("Error: No update options provided. Use --help for available options.");
179
+ process.exit(1);
180
+ }
181
+ try {
182
+ const res = await fetch(`${config.apiUrl}/api/vm-configs/${id}`, {
183
+ method: "PATCH",
184
+ headers: {
185
+ "Content-Type": "application/json",
186
+ ...(config.apiKey ? { "x-api-key": config.apiKey } : {}),
187
+ },
188
+ body: JSON.stringify(updateData),
189
+ });
190
+ if (!res.ok) {
191
+ if (res.status === 404) {
192
+ console.error(`Error: VM config ${id} not found`);
193
+ }
194
+ else {
195
+ const errorData = await res.json().catch(() => ({}));
196
+ console.error(`Error: ${errorData.error || `API returned ${res.status}`}`);
197
+ }
198
+ process.exit(1);
199
+ }
200
+ const vmConfig = await res.json();
201
+ if (options.json) {
202
+ console.log(JSON.stringify(vmConfig, null, 2));
203
+ }
204
+ else {
205
+ console.log(`VM config updated successfully`);
206
+ console.log(` Name: ${vmConfig.name}`);
207
+ console.log(` Machine Type: ${vmConfig.machineType}`);
208
+ console.log(` Zone: ${vmConfig.zone}`);
209
+ }
210
+ }
211
+ catch (error) {
212
+ console.error("Error updating VM config:", error);
213
+ process.exit(1);
214
+ }
215
+ });
216
+ // husky vm-config delete <id>
217
+ vmConfigCommand
218
+ .command("delete <id>")
219
+ .description("Delete a VM configuration")
220
+ .option("--force", "Skip confirmation")
221
+ .action(async (id, options) => {
222
+ const config = ensureConfig();
223
+ // Prevent deletion of default config
224
+ if (id === "default") {
225
+ console.error("Error: Cannot delete the default VM config");
226
+ process.exit(1);
227
+ }
228
+ // Confirm deletion unless --force is provided
229
+ if (!options.force) {
230
+ try {
231
+ const getRes = await fetch(`${config.apiUrl}/api/vm-configs/${id}`, {
232
+ headers: config.apiKey ? { "x-api-key": config.apiKey } : {},
233
+ });
234
+ if (!getRes.ok) {
235
+ if (getRes.status === 404) {
236
+ console.error(`Error: VM config ${id} not found`);
237
+ }
238
+ else {
239
+ console.error(`Error: API returned ${getRes.status}`);
240
+ }
241
+ process.exit(1);
242
+ }
243
+ const vmConfig = await getRes.json();
244
+ const confirmed = await confirm(`Delete VM config "${vmConfig.name}" (${id})?`);
245
+ if (!confirmed) {
246
+ console.log("Deletion cancelled.");
247
+ process.exit(0);
248
+ }
249
+ }
250
+ catch (error) {
251
+ console.error("Error fetching VM config:", error);
252
+ process.exit(1);
253
+ }
254
+ }
255
+ try {
256
+ const res = await fetch(`${config.apiUrl}/api/vm-configs/${id}`, {
257
+ method: "DELETE",
258
+ headers: config.apiKey ? { "x-api-key": config.apiKey } : {},
259
+ });
260
+ if (!res.ok) {
261
+ if (res.status === 404) {
262
+ console.error(`Error: VM config ${id} not found`);
263
+ }
264
+ else {
265
+ const errorData = await res.json().catch(() => ({}));
266
+ console.error(`Error: ${errorData.error || `API returned ${res.status}`}`);
267
+ }
268
+ process.exit(1);
269
+ }
270
+ console.log(`VM config deleted`);
271
+ }
272
+ catch (error) {
273
+ console.error("Error deleting VM config:", error);
274
+ process.exit(1);
275
+ }
276
+ });
277
+ // Print helpers
278
+ function printVMConfigs(configs) {
279
+ if (configs.length === 0) {
280
+ console.log("\n No VM configs found.");
281
+ console.log(" Create one with: husky vm-config create <name>\n");
282
+ return;
283
+ }
284
+ console.log("\n VM CONFIGURATIONS");
285
+ console.log(" " + "-".repeat(90));
286
+ console.log(` ${"ID".padEnd(24)} ${"NAME".padEnd(20)} ${"MACHINE TYPE".padEnd(14)} ${"ZONE".padEnd(18)} DISK`);
287
+ console.log(" " + "-".repeat(90));
288
+ for (const cfg of configs) {
289
+ const truncatedName = cfg.name.length > 18 ? cfg.name.substring(0, 15) + "..." : cfg.name;
290
+ console.log(` ${cfg.id.padEnd(24)} ${truncatedName.padEnd(20)} ${cfg.machineType.padEnd(14)} ${cfg.zone.padEnd(18)} ${cfg.diskSizeGb}GB`);
291
+ }
292
+ console.log("");
293
+ }
294
+ function printVMConfigDetail(vmConfig) {
295
+ console.log(`\n VM Config: ${vmConfig.name}`);
296
+ console.log(" " + "-".repeat(60));
297
+ console.log(` ID: ${vmConfig.id}`);
298
+ console.log(` Name: ${vmConfig.name}`);
299
+ console.log("\n GCP Settings:");
300
+ console.log(` Machine Type: ${vmConfig.machineType}`);
301
+ console.log(` Zone: ${vmConfig.zone}`);
302
+ console.log(` Disk Size: ${vmConfig.diskSizeGb} GB`);
303
+ console.log("\n Limits:");
304
+ console.log(` Max Runtime: ${vmConfig.maxRuntimeMinutes} minutes`);
305
+ console.log(` Max Concurrent: ${vmConfig.maxConcurrentVMs}`);
306
+ console.log(` Daily Budget: $${vmConfig.dailyBudgetUsd.toFixed(2)}`);
307
+ console.log("\n Auto-Start:");
308
+ console.log(` Enabled: ${vmConfig.autoStartEnabled ? "Yes" : "No"}`);
309
+ console.log("\n Review Settings:");
310
+ console.log(` AI Review: ${vmConfig.aiReviewEnabled ? "Enabled" : "Disabled"}`);
311
+ console.log(` Human Review: ${vmConfig.humanReviewRequired ? "Required" : "Optional"}`);
312
+ console.log(` Auto-Approve: ${vmConfig.autoApproveThreshold}% confidence threshold`);
313
+ console.log("\n Retry Settings:");
314
+ console.log(` Max Retries: ${vmConfig.maxRetries}`);
315
+ console.log(`\n Created: ${new Date(vmConfig.createdAt).toLocaleString()}`);
316
+ console.log(` Updated: ${new Date(vmConfig.updatedAt).toLocaleString()}`);
317
+ console.log("");
318
+ }
@@ -0,0 +1,2 @@
1
+ import { Command } from "commander";
2
+ export declare const vmCommand: Command;