@vm0/cli 1.6.0 → 1.9.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/index.js +362 -80
  2. package/package.json +6 -1
package/index.js CHANGED
@@ -736,15 +736,15 @@ function mergeDefs(...defs) {
736
736
  function cloneDef(schema) {
737
737
  return mergeDefs(schema._zod.def);
738
738
  }
739
- function getElementAtPath(obj, path8) {
740
- if (!path8)
739
+ function getElementAtPath(obj, path9) {
740
+ if (!path9)
741
741
  return obj;
742
- return path8.reduce((acc, key) => acc?.[key], obj);
742
+ return path9.reduce((acc, key) => acc?.[key], obj);
743
743
  }
744
744
  function promiseAllObject(promisesObj) {
745
745
  const keys = Object.keys(promisesObj);
746
- const promises5 = keys.map((key) => promisesObj[key]);
747
- return Promise.all(promises5).then((results) => {
746
+ const promises6 = keys.map((key) => promisesObj[key]);
747
+ return Promise.all(promises6).then((results) => {
748
748
  const resolvedObj = {};
749
749
  for (let i = 0; i < keys.length; i++) {
750
750
  resolvedObj[keys[i]] = results[i];
@@ -1098,11 +1098,11 @@ function aborted(x, startIndex = 0) {
1098
1098
  }
1099
1099
  return false;
1100
1100
  }
1101
- function prefixIssues(path8, issues) {
1101
+ function prefixIssues(path9, issues) {
1102
1102
  return issues.map((iss) => {
1103
1103
  var _a;
1104
1104
  (_a = iss).path ?? (_a.path = []);
1105
- iss.path.unshift(path8);
1105
+ iss.path.unshift(path9);
1106
1106
  return iss;
1107
1107
  });
1108
1108
  }
@@ -1270,7 +1270,7 @@ function treeifyError(error43, _mapper) {
1270
1270
  return issue2.message;
1271
1271
  };
1272
1272
  const result = { errors: [] };
1273
- const processError = (error44, path8 = []) => {
1273
+ const processError = (error44, path9 = []) => {
1274
1274
  var _a, _b;
1275
1275
  for (const issue2 of error44.issues) {
1276
1276
  if (issue2.code === "invalid_union" && issue2.errors.length) {
@@ -1280,7 +1280,7 @@ function treeifyError(error43, _mapper) {
1280
1280
  } else if (issue2.code === "invalid_element") {
1281
1281
  processError({ issues: issue2.issues }, issue2.path);
1282
1282
  } else {
1283
- const fullpath = [...path8, ...issue2.path];
1283
+ const fullpath = [...path9, ...issue2.path];
1284
1284
  if (fullpath.length === 0) {
1285
1285
  result.errors.push(mapper(issue2));
1286
1286
  continue;
@@ -1312,8 +1312,8 @@ function treeifyError(error43, _mapper) {
1312
1312
  }
1313
1313
  function toDotPath(_path) {
1314
1314
  const segs = [];
1315
- const path8 = _path.map((seg) => typeof seg === "object" ? seg.key : seg);
1316
- for (const seg of path8) {
1315
+ const path9 = _path.map((seg) => typeof seg === "object" ? seg.key : seg);
1316
+ for (const seg of path9) {
1317
1317
  if (typeof seg === "number")
1318
1318
  segs.push(`[${seg}]`);
1319
1319
  else if (typeof seg === "symbol")
@@ -12413,10 +12413,37 @@ var ApiClient = class {
12413
12413
  }
12414
12414
  return await response.json();
12415
12415
  }
12416
+ async continueSession(body) {
12417
+ const baseUrl = await this.getBaseUrl();
12418
+ const headers = await this.getHeaders();
12419
+ const response = await fetch(`${baseUrl}/api/agent/runs/continue`, {
12420
+ method: "POST",
12421
+ headers,
12422
+ body: JSON.stringify(body)
12423
+ });
12424
+ if (!response.ok) {
12425
+ const error43 = await response.json();
12426
+ throw new Error(error43.error?.message || "Failed to continue session");
12427
+ }
12428
+ return await response.json();
12429
+ }
12430
+ async getAgentSession(id) {
12431
+ const baseUrl = await this.getBaseUrl();
12432
+ const headers = await this.getHeaders();
12433
+ const response = await fetch(`${baseUrl}/api/agent/sessions/${id}`, {
12434
+ method: "GET",
12435
+ headers
12436
+ });
12437
+ if (!response.ok) {
12438
+ const error43 = await response.json();
12439
+ throw new Error(error43.error?.message || "Failed to get agent session");
12440
+ }
12441
+ return await response.json();
12442
+ }
12416
12443
  /**
12417
12444
  * Generic GET request
12418
12445
  */
12419
- async get(path8) {
12446
+ async get(path9) {
12420
12447
  const baseUrl = await this.getBaseUrl();
12421
12448
  const token = await getToken();
12422
12449
  if (!token) {
@@ -12429,7 +12456,7 @@ var ApiClient = class {
12429
12456
  if (bypassSecret) {
12430
12457
  headers["x-vercel-protection-bypass"] = bypassSecret;
12431
12458
  }
12432
- return fetch(`${baseUrl}${path8}`, {
12459
+ return fetch(`${baseUrl}${path9}`, {
12433
12460
  method: "GET",
12434
12461
  headers
12435
12462
  });
@@ -12437,7 +12464,7 @@ var ApiClient = class {
12437
12464
  /**
12438
12465
  * Generic POST request
12439
12466
  */
12440
- async post(path8, options) {
12467
+ async post(path9, options) {
12441
12468
  const baseUrl = await this.getBaseUrl();
12442
12469
  const token = await getToken();
12443
12470
  if (!token) {
@@ -12450,7 +12477,7 @@ var ApiClient = class {
12450
12477
  if (bypassSecret) {
12451
12478
  headers["x-vercel-protection-bypass"] = bypassSecret;
12452
12479
  }
12453
- return fetch(`${baseUrl}${path8}`, {
12480
+ return fetch(`${baseUrl}${path9}`, {
12454
12481
  method: "POST",
12455
12482
  headers,
12456
12483
  body: options?.body
@@ -12464,6 +12491,19 @@ function validateAgentName(name) {
12464
12491
  const nameRegex = /^[a-zA-Z0-9]([a-zA-Z0-9-]{1,62}[a-zA-Z0-9])?$/;
12465
12492
  return nameRegex.test(name);
12466
12493
  }
12494
+ function validateVolumeConfig(volumeKey, volumeConfig) {
12495
+ if (!volumeConfig || typeof volumeConfig !== "object") {
12496
+ return `Volume "${volumeKey}" must be an object`;
12497
+ }
12498
+ const vol = volumeConfig;
12499
+ if (!vol.name || typeof vol.name !== "string") {
12500
+ return `Volume "${volumeKey}" must have a 'name' field (string)`;
12501
+ }
12502
+ if (!vol.version || typeof vol.version !== "string") {
12503
+ return `Volume "${volumeKey}" must have a 'version' field (string)`;
12504
+ }
12505
+ return null;
12506
+ }
12467
12507
  function validateAgentConfig(config2) {
12468
12508
  if (!config2 || typeof config2 !== "object") {
12469
12509
  return { valid: false, error: "Config must be an object" };
@@ -12472,22 +12512,83 @@ function validateAgentConfig(config2) {
12472
12512
  if (!cfg.version) {
12473
12513
  return { valid: false, error: "Missing config.version" };
12474
12514
  }
12475
- if (!cfg.agent || typeof cfg.agent !== "object") {
12476
- return { valid: false, error: "Missing config.agent" };
12515
+ if (!cfg.agents || !Array.isArray(cfg.agents)) {
12516
+ return { valid: false, error: "Missing config.agents array" };
12517
+ }
12518
+ if (cfg.agents.length === 0) {
12519
+ return { valid: false, error: "config.agents array must not be empty" };
12520
+ }
12521
+ const agent = cfg.agents[0];
12522
+ if (!agent || typeof agent !== "object") {
12523
+ return { valid: false, error: "First agent must be an object" };
12477
12524
  }
12478
- const agent = cfg.agent;
12479
12525
  if (!agent.name) {
12480
- return { valid: false, error: "Missing agent.name" };
12526
+ return { valid: false, error: "Missing agents[0].name" };
12481
12527
  }
12482
12528
  if (typeof agent.name !== "string") {
12483
- return { valid: false, error: "agent.name must be a string" };
12529
+ return { valid: false, error: "agents[0].name must be a string" };
12484
12530
  }
12485
12531
  if (!validateAgentName(agent.name)) {
12486
12532
  return {
12487
12533
  valid: false,
12488
- error: "Invalid agent.name format. Must be 3-64 characters, letters, numbers, and hyphens only. Must start and end with letter or number."
12534
+ error: "Invalid agents[0].name format. Must be 3-64 characters, letters, numbers, and hyphens only. Must start and end with letter or number."
12489
12535
  };
12490
12536
  }
12537
+ if (!agent.working_dir || typeof agent.working_dir !== "string") {
12538
+ return {
12539
+ valid: false,
12540
+ error: "Missing or invalid agents[0].working_dir (must be a string)"
12541
+ };
12542
+ }
12543
+ if (!agent.image || typeof agent.image !== "string") {
12544
+ return {
12545
+ valid: false,
12546
+ error: "Missing or invalid agents[0].image (must be a string)"
12547
+ };
12548
+ }
12549
+ if (!agent.provider || typeof agent.provider !== "string") {
12550
+ return {
12551
+ valid: false,
12552
+ error: "Missing or invalid agents[0].provider (must be a string)"
12553
+ };
12554
+ }
12555
+ const agentVolumes = agent.volumes;
12556
+ if (agentVolumes && Array.isArray(agentVolumes) && agentVolumes.length > 0) {
12557
+ const volumesSection = cfg.volumes;
12558
+ if (!volumesSection || typeof volumesSection !== "object") {
12559
+ return {
12560
+ valid: false,
12561
+ error: "Agent references volumes but no volumes section defined. Each volume must have explicit name and version."
12562
+ };
12563
+ }
12564
+ for (const volDeclaration of agentVolumes) {
12565
+ if (typeof volDeclaration !== "string") {
12566
+ return {
12567
+ valid: false,
12568
+ error: "Volume declaration must be a string in format 'key:/path'"
12569
+ };
12570
+ }
12571
+ const parts = volDeclaration.split(":");
12572
+ if (parts.length !== 2) {
12573
+ return {
12574
+ valid: false,
12575
+ error: `Invalid volume declaration: ${volDeclaration}. Expected format: volume-key:/mount/path`
12576
+ };
12577
+ }
12578
+ const volumeKey = parts[0].trim();
12579
+ const volumeConfig = volumesSection[volumeKey];
12580
+ if (!volumeConfig) {
12581
+ return {
12582
+ valid: false,
12583
+ error: `Volume "${volumeKey}" is not defined in volumes section. Each volume must have explicit name and version.`
12584
+ };
12585
+ }
12586
+ const volError = validateVolumeConfig(volumeKey, volumeConfig);
12587
+ if (volError) {
12588
+ return { valid: false, error: volError };
12589
+ }
12590
+ }
12591
+ }
12491
12592
  return { valid: true };
12492
12593
  }
12493
12594
 
@@ -12598,7 +12699,11 @@ var buildCommand = new Command().name("build").description("Create or update age
12598
12699
  console.log(chalk2.gray(` Config ID: ${response.configId}`));
12599
12700
  console.log();
12600
12701
  console.log(" Run your agent:");
12601
- console.log(chalk2.cyan(` vm0 run ${response.name} "your prompt"`));
12702
+ console.log(
12703
+ chalk2.cyan(
12704
+ ` vm0 run ${response.name} --artifact-name <artifact> "your prompt"`
12705
+ )
12706
+ );
12602
12707
  } catch (error43) {
12603
12708
  if (error43 instanceof Error) {
12604
12709
  if (error43.message.includes("Not authenticated")) {
@@ -12737,7 +12842,7 @@ var ClaudeEventParser = class {
12737
12842
  agentConfigId: event.agentConfigId,
12738
12843
  agentName: event.agentName,
12739
12844
  prompt: event.prompt,
12740
- dynamicVars: event.dynamicVars
12845
+ templateVars: event.templateVars
12741
12846
  }
12742
12847
  };
12743
12848
  }
@@ -12748,7 +12853,8 @@ var ClaudeEventParser = class {
12748
12853
  data: {
12749
12854
  runId: event.runId,
12750
12855
  checkpointId: event.checkpointId,
12751
- volumeSnapshots: event.volumeSnapshots
12856
+ agentSessionId: event.agentSessionId,
12857
+ hasArtifact: event.hasArtifact
12752
12858
  }
12753
12859
  };
12754
12860
  }
@@ -12879,9 +12985,11 @@ var EventRenderer = class {
12879
12985
  console.log(
12880
12986
  ` Checkpoint: ${chalk3.gray(String(event.data.checkpointId || ""))}`
12881
12987
  );
12882
- const volumeSnapshots = Number(event.data.volumeSnapshots || 0);
12883
- if (volumeSnapshots > 0) {
12884
- console.log(` Volume snapshots: ${chalk3.gray(String(volumeSnapshots))}`);
12988
+ console.log(
12989
+ ` Session: ${chalk3.gray(String(event.data.agentSessionId || ""))}`
12990
+ );
12991
+ if (event.data.hasArtifact) {
12992
+ console.log(` Artifact: ${chalk3.gray("saved")}`);
12885
12993
  }
12886
12994
  }
12887
12995
  static renderVm0Error(event) {
@@ -12905,18 +13013,18 @@ var EventRenderer = class {
12905
13013
  };
12906
13014
 
12907
13015
  // src/commands/run.ts
12908
- function collectEnvVars(value, previous) {
13016
+ function collectVars(value, previous) {
12909
13017
  const [key, ...valueParts] = value.split("=");
12910
13018
  const val = valueParts.join("=");
12911
13019
  if (!key || val === void 0 || val === "") {
12912
- throw new Error(`Invalid env var format: ${value} (expected key=value)`);
13020
+ throw new Error(`Invalid variable format: ${value} (expected key=value)`);
12913
13021
  }
12914
13022
  return { ...previous, [key]: val };
12915
13023
  }
12916
13024
  function isUUID(str) {
12917
13025
  return /^[0-9a-f-]{36}$/i.test(str);
12918
13026
  }
12919
- var DEFAULT_TIMEOUT_SECONDS = 60;
13027
+ var DEFAULT_TIMEOUT_SECONDS = 120;
12920
13028
  async function pollEvents(runId, timeoutSeconds) {
12921
13029
  let nextSequence = -1;
12922
13030
  let complete = false;
@@ -12958,16 +13066,16 @@ var runCmd = new Command2().name("run").description("Execute an agent").argument
12958
13066
  "<identifier>",
12959
13067
  "Agent name or config ID (e.g., 'my-agent' or 'cfg-abc-123')"
12960
13068
  ).argument("<prompt>", "Prompt for the agent").option(
12961
- "-e, --env <key=value>",
12962
- "Environment variables (repeatable)",
12963
- collectEnvVars,
13069
+ "--vars <KEY=value>",
13070
+ "Template variables for config placeholders (repeatable)",
13071
+ collectVars,
12964
13072
  {}
12965
- ).option(
12966
- "-a, --artifact <key>",
12967
- "Artifact key to mount (for VM0 driver artifacts)"
13073
+ ).option("--artifact-name <name>", "Artifact storage name (required for run)").option(
13074
+ "--artifact-version <hash>",
13075
+ "Artifact version hash (defaults to latest)"
12968
13076
  ).option(
12969
13077
  "-t, --timeout <seconds>",
12970
- "Polling timeout in seconds (default: 60)",
13078
+ "Polling timeout in seconds (default: 120)",
12971
13079
  String(DEFAULT_TIMEOUT_SECONDS)
12972
13080
  ).action(
12973
13081
  async (identifier, prompt, options) => {
@@ -12978,6 +13086,15 @@ var runCmd = new Command2().name("run").description("Execute an agent").argument
12978
13086
  );
12979
13087
  process.exit(1);
12980
13088
  }
13089
+ if (!options.artifactName) {
13090
+ console.error(
13091
+ chalk4.red("\u2717 Missing required option: --artifact-name <name>")
13092
+ );
13093
+ console.error(
13094
+ chalk4.gray(" The artifact-name is required for new agent runs.")
13095
+ );
13096
+ process.exit(1);
13097
+ }
12981
13098
  try {
12982
13099
  let configId;
12983
13100
  if (isUUID(identifier)) {
@@ -13003,13 +13120,16 @@ var runCmd = new Command2().name("run").description("Execute an agent").argument
13003
13120
  }
13004
13121
  console.log(chalk4.blue("\nCreating agent run..."));
13005
13122
  console.log(chalk4.gray(` Prompt: ${prompt}`));
13006
- if (Object.keys(options.env).length > 0) {
13123
+ if (Object.keys(options.vars).length > 0) {
13007
13124
  console.log(
13008
- chalk4.gray(` Variables: ${JSON.stringify(options.env)}`)
13125
+ chalk4.gray(` Variables: ${JSON.stringify(options.vars)}`)
13009
13126
  );
13010
13127
  }
13011
- if (options.artifact) {
13012
- console.log(chalk4.gray(` Artifact: ${options.artifact}`));
13128
+ console.log(chalk4.gray(` Artifact: ${options.artifactName}`));
13129
+ if (options.artifactVersion) {
13130
+ console.log(
13131
+ chalk4.gray(` Artifact version: ${options.artifactVersion}`)
13132
+ );
13013
13133
  }
13014
13134
  console.log();
13015
13135
  console.log(chalk4.blue("Executing in sandbox..."));
@@ -13017,8 +13137,9 @@ var runCmd = new Command2().name("run").description("Execute an agent").argument
13017
13137
  const response = await apiClient.createRun({
13018
13138
  agentConfigId: configId,
13019
13139
  prompt,
13020
- dynamicVars: Object.keys(options.env).length > 0 ? options.env : void 0,
13021
- artifactKey: options.artifact
13140
+ templateVars: Object.keys(options.vars).length > 0 ? options.vars : void 0,
13141
+ artifactName: options.artifactName,
13142
+ artifactVersion: options.artifactVersion
13022
13143
  });
13023
13144
  await pollEvents(response.runId, timeoutSeconds);
13024
13145
  } catch (error43) {
@@ -13043,9 +13164,9 @@ var runCmd = new Command2().name("run").description("Execute an agent").argument
13043
13164
  }
13044
13165
  }
13045
13166
  );
13046
- runCmd.command("resume").description("Resume an agent run from a checkpoint").argument("<checkpointId>", "Checkpoint ID to resume from").argument("<prompt>", "Prompt for the resumed agent").option(
13167
+ runCmd.command("resume").description("Resume an agent run from a checkpoint (uses all snapshot data)").argument("<checkpointId>", "Checkpoint ID to resume from").argument("<prompt>", "Prompt for the resumed agent").option(
13047
13168
  "-t, --timeout <seconds>",
13048
- "Polling timeout in seconds (default: 60)",
13169
+ "Polling timeout in seconds (default: 120)",
13049
13170
  String(DEFAULT_TIMEOUT_SECONDS)
13050
13171
  ).action(
13051
13172
  async (checkpointId, prompt, options) => {
@@ -13094,6 +13215,62 @@ runCmd.command("resume").description("Resume an agent run from a checkpoint").ar
13094
13215
  }
13095
13216
  }
13096
13217
  );
13218
+ runCmd.command("continue").description(
13219
+ "Continue an agent run from a session (uses latest artifact version)"
13220
+ ).argument("<agentSessionId>", "Agent session ID to continue from").argument("<prompt>", "Prompt for the continued agent").option(
13221
+ "-t, --timeout <seconds>",
13222
+ "Polling timeout in seconds (default: 120)",
13223
+ String(DEFAULT_TIMEOUT_SECONDS)
13224
+ ).action(
13225
+ async (agentSessionId, prompt, options) => {
13226
+ const timeoutSeconds = parseInt(options.timeout, 10);
13227
+ if (isNaN(timeoutSeconds) || timeoutSeconds <= 0) {
13228
+ console.error(
13229
+ chalk4.red("\u2717 Invalid timeout value. Must be a positive number.")
13230
+ );
13231
+ process.exit(1);
13232
+ }
13233
+ try {
13234
+ if (!isUUID(agentSessionId)) {
13235
+ console.error(
13236
+ chalk4.red(`\u2717 Invalid agent session ID format: ${agentSessionId}`)
13237
+ );
13238
+ console.error(chalk4.gray(" Agent session ID must be a valid UUID"));
13239
+ process.exit(1);
13240
+ }
13241
+ console.log(chalk4.blue("\nContinuing agent run from session..."));
13242
+ console.log(chalk4.gray(` Session ID: ${agentSessionId}`));
13243
+ console.log(chalk4.gray(` Prompt: ${prompt}`));
13244
+ console.log(chalk4.gray(` Note: Using latest artifact version`));
13245
+ console.log();
13246
+ console.log(chalk4.blue("Executing in sandbox..."));
13247
+ console.log();
13248
+ const response = await apiClient.continueSession({
13249
+ agentSessionId,
13250
+ prompt
13251
+ });
13252
+ await pollEvents(response.runId, timeoutSeconds);
13253
+ } catch (error43) {
13254
+ if (error43 instanceof Error) {
13255
+ if (error43.message.includes("Not authenticated")) {
13256
+ console.error(
13257
+ chalk4.red("\u2717 Not authenticated. Run: vm0 auth login")
13258
+ );
13259
+ } else if (error43.message.includes("not found")) {
13260
+ console.error(
13261
+ chalk4.red(`\u2717 Agent session not found: ${agentSessionId}`)
13262
+ );
13263
+ } else {
13264
+ console.error(chalk4.red("\u2717 Continue failed"));
13265
+ console.error(chalk4.gray(` ${error43.message}`));
13266
+ }
13267
+ } else {
13268
+ console.error(chalk4.red("\u2717 An unexpected error occurred"));
13269
+ }
13270
+ process.exit(1);
13271
+ }
13272
+ }
13273
+ );
13097
13274
  var runCommand = runCmd;
13098
13275
 
13099
13276
  // src/commands/volume/index.ts
@@ -13293,9 +13470,80 @@ var pushCommand = new Command4().name("push").description("Push local files to c
13293
13470
  // src/commands/volume/pull.ts
13294
13471
  import { Command as Command5 } from "commander";
13295
13472
  import chalk7 from "chalk";
13296
- import path4 from "path";
13297
- import * as fs2 from "fs";
13473
+ import path5 from "path";
13474
+ import * as fs3 from "fs";
13298
13475
  import AdmZip2 from "adm-zip";
13476
+
13477
+ // src/lib/file-utils.ts
13478
+ import * as fs2 from "fs";
13479
+ import * as path4 from "path";
13480
+ function getRemoteFilesFromZip(zipEntries) {
13481
+ const remoteFiles = /* @__PURE__ */ new Set();
13482
+ for (const entry of zipEntries) {
13483
+ if (!entry.isDirectory) {
13484
+ remoteFiles.add(entry.entryName.replace(/\\/g, "/"));
13485
+ }
13486
+ }
13487
+ return remoteFiles;
13488
+ }
13489
+ async function listLocalFiles(dir, excludeDirs = [".vm0"]) {
13490
+ const files = [];
13491
+ async function walkDir(currentDir, relativePath = "") {
13492
+ const entries = await fs2.promises.readdir(currentDir, {
13493
+ withFileTypes: true
13494
+ });
13495
+ for (const entry of entries) {
13496
+ const entryRelativePath = relativePath ? path4.join(relativePath, entry.name) : entry.name;
13497
+ if (entry.isDirectory()) {
13498
+ if (!excludeDirs.includes(entry.name)) {
13499
+ await walkDir(path4.join(currentDir, entry.name), entryRelativePath);
13500
+ }
13501
+ } else {
13502
+ files.push(entryRelativePath);
13503
+ }
13504
+ }
13505
+ }
13506
+ await walkDir(dir);
13507
+ return files;
13508
+ }
13509
+ async function removeExtraFiles(dir, remoteFiles, excludeDirs = [".vm0"]) {
13510
+ const localFiles = await listLocalFiles(dir, excludeDirs);
13511
+ let removedCount = 0;
13512
+ for (const localFile of localFiles) {
13513
+ const normalizedPath = localFile.replace(/\\/g, "/");
13514
+ if (!remoteFiles.has(normalizedPath)) {
13515
+ const fullPath = path4.join(dir, localFile);
13516
+ await fs2.promises.unlink(fullPath);
13517
+ removedCount++;
13518
+ }
13519
+ }
13520
+ await removeEmptyDirs(dir, excludeDirs);
13521
+ return removedCount;
13522
+ }
13523
+ async function removeEmptyDirs(dir, excludeDirs = [".vm0"]) {
13524
+ const entries = await fs2.promises.readdir(dir, { withFileTypes: true });
13525
+ let isEmpty = true;
13526
+ for (const entry of entries) {
13527
+ const fullPath = path4.join(dir, entry.name);
13528
+ if (entry.isDirectory()) {
13529
+ if (excludeDirs.includes(entry.name)) {
13530
+ isEmpty = false;
13531
+ } else {
13532
+ const subDirEmpty = await removeEmptyDirs(fullPath, excludeDirs);
13533
+ if (subDirEmpty) {
13534
+ await fs2.promises.rmdir(fullPath);
13535
+ } else {
13536
+ isEmpty = false;
13537
+ }
13538
+ }
13539
+ } else {
13540
+ isEmpty = false;
13541
+ }
13542
+ }
13543
+ return isEmpty;
13544
+ }
13545
+
13546
+ // src/commands/volume/pull.ts
13299
13547
  function formatBytes2(bytes) {
13300
13548
  if (bytes === 0) return "0 B";
13301
13549
  const k = 1024;
@@ -13303,7 +13551,7 @@ function formatBytes2(bytes) {
13303
13551
  const i = Math.floor(Math.log(bytes) / Math.log(k));
13304
13552
  return `${(bytes / Math.pow(k, i)).toFixed(2)} ${sizes[i]}`;
13305
13553
  }
13306
- var pullCommand = new Command5().name("pull").description("Pull cloud files to local directory").action(async () => {
13554
+ var pullCommand = new Command5().name("pull").description("Pull cloud files to local directory").argument("[versionId]", "Version ID to pull (default: latest)").action(async (versionId) => {
13307
13555
  try {
13308
13556
  const cwd = process.cwd();
13309
13557
  const config2 = await readStorageConfig(cwd);
@@ -13312,11 +13560,19 @@ var pullCommand = new Command5().name("pull").description("Pull cloud files to l
13312
13560
  console.error(chalk7.gray(" Run: vm0 volume init"));
13313
13561
  process.exit(1);
13314
13562
  }
13315
- console.log(chalk7.cyan(`Pulling volume: ${config2.name}`));
13563
+ if (versionId) {
13564
+ console.log(
13565
+ chalk7.cyan(`Pulling volume: ${config2.name} (version: ${versionId})`)
13566
+ );
13567
+ } else {
13568
+ console.log(chalk7.cyan(`Pulling volume: ${config2.name}`));
13569
+ }
13316
13570
  console.log(chalk7.gray("Downloading..."));
13317
- const response = await apiClient.get(
13318
- `/api/storages?name=${encodeURIComponent(config2.name)}`
13319
- );
13571
+ let url2 = `/api/storages?name=${encodeURIComponent(config2.name)}`;
13572
+ if (versionId) {
13573
+ url2 += `&version=${encodeURIComponent(versionId)}`;
13574
+ }
13575
+ const response = await apiClient.get(url2);
13320
13576
  if (!response.ok) {
13321
13577
  if (response.status === 404) {
13322
13578
  console.error(chalk7.red(`\u2717 Volume "${config2.name}" not found`));
@@ -13340,14 +13596,22 @@ var pullCommand = new Command5().name("pull").description("Pull cloud files to l
13340
13596
  console.log(chalk7.gray("Extracting files..."));
13341
13597
  const zip = new AdmZip2(zipBuffer);
13342
13598
  const zipEntries = zip.getEntries();
13599
+ const remoteFiles = getRemoteFilesFromZip(zipEntries);
13600
+ console.log(chalk7.gray("Syncing local files..."));
13601
+ const removedCount = await removeExtraFiles(cwd, remoteFiles);
13602
+ if (removedCount > 0) {
13603
+ console.log(
13604
+ chalk7.green(`\u2713 Removed ${removedCount} files not in remote`)
13605
+ );
13606
+ }
13343
13607
  let extractedCount = 0;
13344
13608
  for (const entry of zipEntries) {
13345
13609
  if (!entry.isDirectory) {
13346
- const targetPath = path4.join(cwd, entry.entryName);
13347
- const dir = path4.dirname(targetPath);
13348
- await fs2.promises.mkdir(dir, { recursive: true });
13610
+ const targetPath = path5.join(cwd, entry.entryName);
13611
+ const dir = path5.dirname(targetPath);
13612
+ await fs3.promises.mkdir(dir, { recursive: true });
13349
13613
  const data = entry.getData();
13350
- await fs2.promises.writeFile(targetPath, data);
13614
+ await fs3.promises.writeFile(targetPath, data);
13351
13615
  extractedCount++;
13352
13616
  }
13353
13617
  }
@@ -13370,11 +13634,11 @@ import { Command as Command10 } from "commander";
13370
13634
  // src/commands/artifact/init.ts
13371
13635
  import { Command as Command7 } from "commander";
13372
13636
  import chalk8 from "chalk";
13373
- import path5 from "path";
13637
+ import path6 from "path";
13374
13638
  var initCommand2 = new Command7().name("init").description("Initialize an artifact in the current directory").action(async () => {
13375
13639
  try {
13376
13640
  const cwd = process.cwd();
13377
- const dirName = path5.basename(cwd);
13641
+ const dirName = path6.basename(cwd);
13378
13642
  const existingConfig = await readStorageConfig(cwd);
13379
13643
  if (existingConfig) {
13380
13644
  if (existingConfig.type === "artifact") {
@@ -13396,7 +13660,7 @@ var initCommand2 = new Command7().name("init").description("Initialize an artifa
13396
13660
  );
13397
13661
  }
13398
13662
  console.log(
13399
- chalk8.gray(`Config file: ${path5.join(cwd, ".vm0", "storage.yaml")}`)
13663
+ chalk8.gray(`Config file: ${path6.join(cwd, ".vm0", "storage.yaml")}`)
13400
13664
  );
13401
13665
  return;
13402
13666
  }
@@ -13417,7 +13681,7 @@ var initCommand2 = new Command7().name("init").description("Initialize an artifa
13417
13681
  console.log(chalk8.green(`\u2713 Initialized artifact: ${artifactName}`));
13418
13682
  console.log(
13419
13683
  chalk8.gray(
13420
- `\u2713 Config saved to ${path5.join(cwd, ".vm0", "storage.yaml")}`
13684
+ `\u2713 Config saved to ${path6.join(cwd, ".vm0", "storage.yaml")}`
13421
13685
  )
13422
13686
  );
13423
13687
  } catch (error43) {
@@ -13432,15 +13696,15 @@ var initCommand2 = new Command7().name("init").description("Initialize an artifa
13432
13696
  // src/commands/artifact/push.ts
13433
13697
  import { Command as Command8 } from "commander";
13434
13698
  import chalk9 from "chalk";
13435
- import path6 from "path";
13436
- import * as fs3 from "fs";
13699
+ import path7 from "path";
13700
+ import * as fs4 from "fs";
13437
13701
  import AdmZip3 from "adm-zip";
13438
13702
  async function getAllFiles2(dirPath, baseDir = dirPath) {
13439
13703
  const files = [];
13440
- const entries = await fs3.promises.readdir(dirPath, { withFileTypes: true });
13704
+ const entries = await fs4.promises.readdir(dirPath, { withFileTypes: true });
13441
13705
  for (const entry of entries) {
13442
- const fullPath = path6.join(dirPath, entry.name);
13443
- const relativePath = path6.relative(baseDir, fullPath);
13706
+ const fullPath = path7.join(dirPath, entry.name);
13707
+ const relativePath = path7.relative(baseDir, fullPath);
13444
13708
  if (relativePath.startsWith(".vm0")) {
13445
13709
  continue;
13446
13710
  }
@@ -13487,7 +13751,7 @@ var pushCommand2 = new Command8().name("push").description("Push local files to
13487
13751
  }
13488
13752
  let totalSize = 0;
13489
13753
  for (const file2 of files) {
13490
- const stats = await fs3.promises.stat(file2);
13754
+ const stats = await fs4.promises.stat(file2);
13491
13755
  totalSize += stats.size;
13492
13756
  }
13493
13757
  console.log(
@@ -13496,8 +13760,8 @@ var pushCommand2 = new Command8().name("push").description("Push local files to
13496
13760
  console.log(chalk9.gray("Compressing files..."));
13497
13761
  const zip = new AdmZip3();
13498
13762
  for (const file2 of files) {
13499
- const relativePath = path6.relative(cwd, file2);
13500
- zip.addLocalFile(file2, path6.dirname(relativePath));
13763
+ const relativePath = path7.relative(cwd, file2);
13764
+ zip.addLocalFile(file2, path7.dirname(relativePath));
13501
13765
  }
13502
13766
  const zipBuffer = zip.toBuffer();
13503
13767
  console.log(
@@ -13536,8 +13800,8 @@ var pushCommand2 = new Command8().name("push").description("Push local files to
13536
13800
  // src/commands/artifact/pull.ts
13537
13801
  import { Command as Command9 } from "commander";
13538
13802
  import chalk10 from "chalk";
13539
- import path7 from "path";
13540
- import * as fs4 from "fs";
13803
+ import path8 from "path";
13804
+ import * as fs5 from "fs";
13541
13805
  import AdmZip4 from "adm-zip";
13542
13806
  function formatBytes4(bytes) {
13543
13807
  if (bytes === 0) return "0 B";
@@ -13546,7 +13810,7 @@ function formatBytes4(bytes) {
13546
13810
  const i = Math.floor(Math.log(bytes) / Math.log(k));
13547
13811
  return `${(bytes / Math.pow(k, i)).toFixed(2)} ${sizes[i]}`;
13548
13812
  }
13549
- var pullCommand2 = new Command9().name("pull").description("Pull cloud artifact to local directory").action(async () => {
13813
+ var pullCommand2 = new Command9().name("pull").description("Pull cloud artifact to local directory").argument("[versionId]", "Version ID to pull (default: latest)").action(async (versionId) => {
13550
13814
  try {
13551
13815
  const cwd = process.cwd();
13552
13816
  const config2 = await readStorageConfig(cwd);
@@ -13564,11 +13828,21 @@ var pullCommand2 = new Command9().name("pull").description("Pull cloud artifact
13564
13828
  console.error(chalk10.gray(" Use: vm0 volume pull"));
13565
13829
  process.exit(1);
13566
13830
  }
13567
- console.log(chalk10.cyan(`Pulling artifact: ${config2.name}`));
13831
+ if (versionId) {
13832
+ console.log(
13833
+ chalk10.cyan(
13834
+ `Pulling artifact: ${config2.name} (version: ${versionId})`
13835
+ )
13836
+ );
13837
+ } else {
13838
+ console.log(chalk10.cyan(`Pulling artifact: ${config2.name}`));
13839
+ }
13568
13840
  console.log(chalk10.gray("Downloading..."));
13569
- const response = await apiClient.get(
13570
- `/api/storages?name=${encodeURIComponent(config2.name)}`
13571
- );
13841
+ let url2 = `/api/storages?name=${encodeURIComponent(config2.name)}`;
13842
+ if (versionId) {
13843
+ url2 += `&version=${encodeURIComponent(versionId)}`;
13844
+ }
13845
+ const response = await apiClient.get(url2);
13572
13846
  if (!response.ok) {
13573
13847
  if (response.status === 404) {
13574
13848
  console.error(chalk10.red(`\u2717 Artifact "${config2.name}" not found`));
@@ -13592,14 +13866,22 @@ var pullCommand2 = new Command9().name("pull").description("Pull cloud artifact
13592
13866
  console.log(chalk10.gray("Extracting files..."));
13593
13867
  const zip = new AdmZip4(zipBuffer);
13594
13868
  const zipEntries = zip.getEntries();
13869
+ const remoteFiles = getRemoteFilesFromZip(zipEntries);
13870
+ console.log(chalk10.gray("Syncing local files..."));
13871
+ const removedCount = await removeExtraFiles(cwd, remoteFiles);
13872
+ if (removedCount > 0) {
13873
+ console.log(
13874
+ chalk10.green(`\u2713 Removed ${removedCount} files not in remote`)
13875
+ );
13876
+ }
13595
13877
  let extractedCount = 0;
13596
13878
  for (const entry of zipEntries) {
13597
13879
  if (!entry.isDirectory) {
13598
- const targetPath = path7.join(cwd, entry.entryName);
13599
- const dir = path7.dirname(targetPath);
13600
- await fs4.promises.mkdir(dir, { recursive: true });
13880
+ const targetPath = path8.join(cwd, entry.entryName);
13881
+ const dir = path8.dirname(targetPath);
13882
+ await fs5.promises.mkdir(dir, { recursive: true });
13601
13883
  const data = entry.getData();
13602
- await fs4.promises.writeFile(targetPath, data);
13884
+ await fs5.promises.writeFile(targetPath, data);
13603
13885
  extractedCount++;
13604
13886
  }
13605
13887
  }
@@ -13618,7 +13900,7 @@ var artifactCommand = new Command10().name("artifact").description("Manage cloud
13618
13900
 
13619
13901
  // src/index.ts
13620
13902
  var program = new Command11();
13621
- program.name("vm0").description("VM0 CLI - A modern build tool").version("0.1.0");
13903
+ program.name("vm0").description("VM0 CLI - A modern build tool").version("1.9.0");
13622
13904
  program.command("hello").description("Say hello from the App").action(() => {
13623
13905
  console.log(chalk11.blue("Welcome to the VM0 CLI!"));
13624
13906
  console.log(chalk11.green(`Core says: ${FOO}`));
package/package.json CHANGED
@@ -1,7 +1,12 @@
1
1
  {
2
2
  "name": "@vm0/cli",
3
- "version": "1.6.0",
3
+ "version": "1.9.0",
4
4
  "description": "CLI application",
5
+ "repository": {
6
+ "type": "git",
7
+ "url": "https://github.com/vm0-ai/vm0",
8
+ "directory": "turbo/apps/cli"
9
+ },
5
10
  "type": "module",
6
11
  "bin": {
7
12
  "vm0": "index.js"