@vm0/cli 1.5.0 → 1.8.1

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 +419 -113
  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
  }
@@ -12821,9 +12927,7 @@ var EventRenderer = class {
12821
12927
  if (input && typeof input === "object") {
12822
12928
  for (const [key, value] of Object.entries(input)) {
12823
12929
  if (value !== void 0 && value !== null) {
12824
- const valueStr = String(value);
12825
- const displayValue = valueStr.length > 100 ? valueStr.substring(0, 100) + "..." : valueStr;
12826
- console.log(` ${key}: ${chalk3.gray(displayValue)}`);
12930
+ console.log(` ${key}: ${chalk3.gray(String(value))}`);
12827
12931
  }
12828
12932
  }
12829
12933
  }
@@ -12834,8 +12938,7 @@ var EventRenderer = class {
12834
12938
  const color = isError ? chalk3.red : chalk3.green;
12835
12939
  console.log(color("[tool_result]") + " " + status);
12836
12940
  const result = String(event.data.result || "");
12837
- const displayResult = result.length > 200 ? result.substring(0, 200) + "..." : result;
12838
- console.log(` ${chalk3.gray(displayResult)}`);
12941
+ console.log(` ${chalk3.gray(result)}`);
12839
12942
  }
12840
12943
  static renderResult(event) {
12841
12944
  const success2 = Boolean(event.data.success);
@@ -12872,8 +12975,7 @@ var EventRenderer = class {
12872
12975
  console.log(` Run ID: ${chalk3.gray(String(event.data.runId))}`);
12873
12976
  }
12874
12977
  const prompt = String(event.data.prompt || "");
12875
- const displayPrompt = prompt.length > 100 ? prompt.substring(0, 100) + "..." : prompt;
12876
- console.log(` Prompt: ${chalk3.gray(displayPrompt)}`);
12978
+ console.log(` Prompt: ${chalk3.gray(prompt)}`);
12877
12979
  if (event.data.agentName) {
12878
12980
  console.log(` Agent: ${chalk3.gray(String(event.data.agentName))}`);
12879
12981
  }
@@ -12883,9 +12985,11 @@ var EventRenderer = class {
12883
12985
  console.log(
12884
12986
  ` Checkpoint: ${chalk3.gray(String(event.data.checkpointId || ""))}`
12885
12987
  );
12886
- const volumeSnapshots = Number(event.data.volumeSnapshots || 0);
12887
- if (volumeSnapshots > 0) {
12888
- 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")}`);
12889
12993
  }
12890
12994
  }
12891
12995
  static renderVm0Error(event) {
@@ -12909,29 +13013,31 @@ var EventRenderer = class {
12909
13013
  };
12910
13014
 
12911
13015
  // src/commands/run.ts
12912
- function collectEnvVars(value, previous) {
13016
+ function collectVars(value, previous) {
12913
13017
  const [key, ...valueParts] = value.split("=");
12914
13018
  const val = valueParts.join("=");
12915
13019
  if (!key || val === void 0 || val === "") {
12916
- throw new Error(`Invalid env var format: ${value} (expected key=value)`);
13020
+ throw new Error(`Invalid variable format: ${value} (expected key=value)`);
12917
13021
  }
12918
13022
  return { ...previous, [key]: val };
12919
13023
  }
12920
13024
  function isUUID(str) {
12921
13025
  return /^[0-9a-f-]{36}$/i.test(str);
12922
13026
  }
12923
- async function pollEvents(runId) {
13027
+ var DEFAULT_TIMEOUT_SECONDS = 60;
13028
+ async function pollEvents(runId, timeoutSeconds) {
12924
13029
  let nextSequence = -1;
12925
13030
  let complete = false;
12926
13031
  const pollIntervalMs = 500;
12927
- const timeoutMs = 3 * 60 * 1e3;
13032
+ const timeoutMs = timeoutSeconds * 1e3;
12928
13033
  const startTime = Date.now();
12929
13034
  while (!complete) {
12930
13035
  const elapsed = Date.now() - startTime;
12931
13036
  if (elapsed > timeoutMs) {
12932
13037
  console.error(
12933
13038
  chalk4.red(
12934
- "\n\u2717 Agent execution timed out after 3 minutes without receiving events"
13039
+ `
13040
+ \u2717 Agent execution timed out after ${timeoutSeconds} seconds without receiving events`
12935
13041
  )
12936
13042
  );
12937
13043
  throw new Error("Agent execution timed out");
@@ -12960,15 +13066,35 @@ var runCmd = new Command2().name("run").description("Execute an agent").argument
12960
13066
  "<identifier>",
12961
13067
  "Agent name or config ID (e.g., 'my-agent' or 'cfg-abc-123')"
12962
13068
  ).argument("<prompt>", "Prompt for the agent").option(
12963
- "-e, --env <key=value>",
12964
- "Environment variables (repeatable)",
12965
- collectEnvVars,
13069
+ "--vars <KEY=value>",
13070
+ "Template variables for config placeholders (repeatable)",
13071
+ collectVars,
12966
13072
  {}
13073
+ ).option("--artifact-name <name>", "Artifact storage name (required for run)").option(
13074
+ "--artifact-version <hash>",
13075
+ "Artifact version hash (defaults to latest)"
12967
13076
  ).option(
12968
- "-a, --artifact <key>",
12969
- "Artifact key to mount (for VM0 driver artifacts)"
13077
+ "-t, --timeout <seconds>",
13078
+ "Polling timeout in seconds (default: 60)",
13079
+ String(DEFAULT_TIMEOUT_SECONDS)
12970
13080
  ).action(
12971
13081
  async (identifier, prompt, options) => {
13082
+ const timeoutSeconds = parseInt(options.timeout, 10);
13083
+ if (isNaN(timeoutSeconds) || timeoutSeconds <= 0) {
13084
+ console.error(
13085
+ chalk4.red("\u2717 Invalid timeout value. Must be a positive number.")
13086
+ );
13087
+ process.exit(1);
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
+ }
12972
13098
  try {
12973
13099
  let configId;
12974
13100
  if (isUUID(identifier)) {
@@ -12994,13 +13120,16 @@ var runCmd = new Command2().name("run").description("Execute an agent").argument
12994
13120
  }
12995
13121
  console.log(chalk4.blue("\nCreating agent run..."));
12996
13122
  console.log(chalk4.gray(` Prompt: ${prompt}`));
12997
- if (Object.keys(options.env).length > 0) {
13123
+ if (Object.keys(options.vars).length > 0) {
12998
13124
  console.log(
12999
- chalk4.gray(` Variables: ${JSON.stringify(options.env)}`)
13125
+ chalk4.gray(` Variables: ${JSON.stringify(options.vars)}`)
13000
13126
  );
13001
13127
  }
13002
- if (options.artifact) {
13003
- 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
+ );
13004
13133
  }
13005
13134
  console.log();
13006
13135
  console.log(chalk4.blue("Executing in sandbox..."));
@@ -13008,10 +13137,11 @@ var runCmd = new Command2().name("run").description("Execute an agent").argument
13008
13137
  const response = await apiClient.createRun({
13009
13138
  agentConfigId: configId,
13010
13139
  prompt,
13011
- dynamicVars: Object.keys(options.env).length > 0 ? options.env : void 0,
13012
- artifactKey: options.artifact
13140
+ templateVars: Object.keys(options.vars).length > 0 ? options.vars : void 0,
13141
+ artifactName: options.artifactName,
13142
+ artifactVersion: options.artifactVersion
13013
13143
  });
13014
- await pollEvents(response.runId);
13144
+ await pollEvents(response.runId, timeoutSeconds);
13015
13145
  } catch (error43) {
13016
13146
  if (error43 instanceof Error) {
13017
13147
  if (error43.message.includes("Not authenticated")) {
@@ -13034,42 +13164,113 @@ var runCmd = new Command2().name("run").description("Execute an agent").argument
13034
13164
  }
13035
13165
  }
13036
13166
  );
13037
- 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").action(async (checkpointId, prompt) => {
13038
- try {
13039
- if (!isUUID(checkpointId)) {
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(
13168
+ "-t, --timeout <seconds>",
13169
+ "Polling timeout in seconds (default: 60)",
13170
+ String(DEFAULT_TIMEOUT_SECONDS)
13171
+ ).action(
13172
+ async (checkpointId, prompt, options) => {
13173
+ const timeoutSeconds = parseInt(options.timeout, 10);
13174
+ if (isNaN(timeoutSeconds) || timeoutSeconds <= 0) {
13040
13175
  console.error(
13041
- chalk4.red(`\u2717 Invalid checkpoint ID format: ${checkpointId}`)
13176
+ chalk4.red("\u2717 Invalid timeout value. Must be a positive number.")
13042
13177
  );
13043
- console.error(chalk4.gray(" Checkpoint ID must be a valid UUID"));
13044
13178
  process.exit(1);
13045
13179
  }
13046
- console.log(chalk4.blue("\nResuming agent run from checkpoint..."));
13047
- console.log(chalk4.gray(` Checkpoint ID: ${checkpointId}`));
13048
- console.log(chalk4.gray(` Prompt: ${prompt}`));
13049
- console.log();
13050
- console.log(chalk4.blue("Executing in sandbox..."));
13051
- console.log();
13052
- const response = await apiClient.resumeRun({
13053
- checkpointId,
13054
- prompt
13055
- });
13056
- await pollEvents(response.runId);
13057
- } catch (error43) {
13058
- if (error43 instanceof Error) {
13059
- if (error43.message.includes("Not authenticated")) {
13060
- console.error(chalk4.red("\u2717 Not authenticated. Run: vm0 auth login"));
13061
- } else if (error43.message.includes("not found")) {
13062
- console.error(chalk4.red(`\u2717 Checkpoint not found: ${checkpointId}`));
13180
+ try {
13181
+ if (!isUUID(checkpointId)) {
13182
+ console.error(
13183
+ chalk4.red(`\u2717 Invalid checkpoint ID format: ${checkpointId}`)
13184
+ );
13185
+ console.error(chalk4.gray(" Checkpoint ID must be a valid UUID"));
13186
+ process.exit(1);
13187
+ }
13188
+ console.log(chalk4.blue("\nResuming agent run from checkpoint..."));
13189
+ console.log(chalk4.gray(` Checkpoint ID: ${checkpointId}`));
13190
+ console.log(chalk4.gray(` Prompt: ${prompt}`));
13191
+ console.log();
13192
+ console.log(chalk4.blue("Executing in sandbox..."));
13193
+ console.log();
13194
+ const response = await apiClient.resumeRun({
13195
+ checkpointId,
13196
+ prompt
13197
+ });
13198
+ await pollEvents(response.runId, timeoutSeconds);
13199
+ } catch (error43) {
13200
+ if (error43 instanceof Error) {
13201
+ if (error43.message.includes("Not authenticated")) {
13202
+ console.error(
13203
+ chalk4.red("\u2717 Not authenticated. Run: vm0 auth login")
13204
+ );
13205
+ } else if (error43.message.includes("not found")) {
13206
+ console.error(chalk4.red(`\u2717 Checkpoint not found: ${checkpointId}`));
13207
+ } else {
13208
+ console.error(chalk4.red("\u2717 Resume failed"));
13209
+ console.error(chalk4.gray(` ${error43.message}`));
13210
+ }
13063
13211
  } else {
13064
- console.error(chalk4.red("\u2717 Resume failed"));
13065
- console.error(chalk4.gray(` ${error43.message}`));
13212
+ console.error(chalk4.red("\u2717 An unexpected error occurred"));
13066
13213
  }
13067
- } else {
13068
- console.error(chalk4.red("\u2717 An unexpected error occurred"));
13214
+ process.exit(1);
13069
13215
  }
13070
- process.exit(1);
13071
13216
  }
13072
- });
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: 60)",
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
+ );
13073
13274
  var runCommand = runCmd;
13074
13275
 
13075
13276
  // src/commands/volume/index.ts
@@ -13269,9 +13470,80 @@ var pushCommand = new Command4().name("push").description("Push local files to c
13269
13470
  // src/commands/volume/pull.ts
13270
13471
  import { Command as Command5 } from "commander";
13271
13472
  import chalk7 from "chalk";
13272
- import path4 from "path";
13273
- import * as fs2 from "fs";
13473
+ import path5 from "path";
13474
+ import * as fs3 from "fs";
13274
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
13275
13547
  function formatBytes2(bytes) {
13276
13548
  if (bytes === 0) return "0 B";
13277
13549
  const k = 1024;
@@ -13279,7 +13551,7 @@ function formatBytes2(bytes) {
13279
13551
  const i = Math.floor(Math.log(bytes) / Math.log(k));
13280
13552
  return `${(bytes / Math.pow(k, i)).toFixed(2)} ${sizes[i]}`;
13281
13553
  }
13282
- 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) => {
13283
13555
  try {
13284
13556
  const cwd = process.cwd();
13285
13557
  const config2 = await readStorageConfig(cwd);
@@ -13288,11 +13560,19 @@ var pullCommand = new Command5().name("pull").description("Pull cloud files to l
13288
13560
  console.error(chalk7.gray(" Run: vm0 volume init"));
13289
13561
  process.exit(1);
13290
13562
  }
13291
- 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
+ }
13292
13570
  console.log(chalk7.gray("Downloading..."));
13293
- const response = await apiClient.get(
13294
- `/api/storages?name=${encodeURIComponent(config2.name)}`
13295
- );
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);
13296
13576
  if (!response.ok) {
13297
13577
  if (response.status === 404) {
13298
13578
  console.error(chalk7.red(`\u2717 Volume "${config2.name}" not found`));
@@ -13316,14 +13596,22 @@ var pullCommand = new Command5().name("pull").description("Pull cloud files to l
13316
13596
  console.log(chalk7.gray("Extracting files..."));
13317
13597
  const zip = new AdmZip2(zipBuffer);
13318
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
+ }
13319
13607
  let extractedCount = 0;
13320
13608
  for (const entry of zipEntries) {
13321
13609
  if (!entry.isDirectory) {
13322
- const targetPath = path4.join(cwd, entry.entryName);
13323
- const dir = path4.dirname(targetPath);
13324
- 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 });
13325
13613
  const data = entry.getData();
13326
- await fs2.promises.writeFile(targetPath, data);
13614
+ await fs3.promises.writeFile(targetPath, data);
13327
13615
  extractedCount++;
13328
13616
  }
13329
13617
  }
@@ -13346,11 +13634,11 @@ import { Command as Command10 } from "commander";
13346
13634
  // src/commands/artifact/init.ts
13347
13635
  import { Command as Command7 } from "commander";
13348
13636
  import chalk8 from "chalk";
13349
- import path5 from "path";
13637
+ import path6 from "path";
13350
13638
  var initCommand2 = new Command7().name("init").description("Initialize an artifact in the current directory").action(async () => {
13351
13639
  try {
13352
13640
  const cwd = process.cwd();
13353
- const dirName = path5.basename(cwd);
13641
+ const dirName = path6.basename(cwd);
13354
13642
  const existingConfig = await readStorageConfig(cwd);
13355
13643
  if (existingConfig) {
13356
13644
  if (existingConfig.type === "artifact") {
@@ -13372,7 +13660,7 @@ var initCommand2 = new Command7().name("init").description("Initialize an artifa
13372
13660
  );
13373
13661
  }
13374
13662
  console.log(
13375
- chalk8.gray(`Config file: ${path5.join(cwd, ".vm0", "storage.yaml")}`)
13663
+ chalk8.gray(`Config file: ${path6.join(cwd, ".vm0", "storage.yaml")}`)
13376
13664
  );
13377
13665
  return;
13378
13666
  }
@@ -13393,7 +13681,7 @@ var initCommand2 = new Command7().name("init").description("Initialize an artifa
13393
13681
  console.log(chalk8.green(`\u2713 Initialized artifact: ${artifactName}`));
13394
13682
  console.log(
13395
13683
  chalk8.gray(
13396
- `\u2713 Config saved to ${path5.join(cwd, ".vm0", "storage.yaml")}`
13684
+ `\u2713 Config saved to ${path6.join(cwd, ".vm0", "storage.yaml")}`
13397
13685
  )
13398
13686
  );
13399
13687
  } catch (error43) {
@@ -13408,15 +13696,15 @@ var initCommand2 = new Command7().name("init").description("Initialize an artifa
13408
13696
  // src/commands/artifact/push.ts
13409
13697
  import { Command as Command8 } from "commander";
13410
13698
  import chalk9 from "chalk";
13411
- import path6 from "path";
13412
- import * as fs3 from "fs";
13699
+ import path7 from "path";
13700
+ import * as fs4 from "fs";
13413
13701
  import AdmZip3 from "adm-zip";
13414
13702
  async function getAllFiles2(dirPath, baseDir = dirPath) {
13415
13703
  const files = [];
13416
- const entries = await fs3.promises.readdir(dirPath, { withFileTypes: true });
13704
+ const entries = await fs4.promises.readdir(dirPath, { withFileTypes: true });
13417
13705
  for (const entry of entries) {
13418
- const fullPath = path6.join(dirPath, entry.name);
13419
- const relativePath = path6.relative(baseDir, fullPath);
13706
+ const fullPath = path7.join(dirPath, entry.name);
13707
+ const relativePath = path7.relative(baseDir, fullPath);
13420
13708
  if (relativePath.startsWith(".vm0")) {
13421
13709
  continue;
13422
13710
  }
@@ -13463,7 +13751,7 @@ var pushCommand2 = new Command8().name("push").description("Push local files to
13463
13751
  }
13464
13752
  let totalSize = 0;
13465
13753
  for (const file2 of files) {
13466
- const stats = await fs3.promises.stat(file2);
13754
+ const stats = await fs4.promises.stat(file2);
13467
13755
  totalSize += stats.size;
13468
13756
  }
13469
13757
  console.log(
@@ -13472,8 +13760,8 @@ var pushCommand2 = new Command8().name("push").description("Push local files to
13472
13760
  console.log(chalk9.gray("Compressing files..."));
13473
13761
  const zip = new AdmZip3();
13474
13762
  for (const file2 of files) {
13475
- const relativePath = path6.relative(cwd, file2);
13476
- zip.addLocalFile(file2, path6.dirname(relativePath));
13763
+ const relativePath = path7.relative(cwd, file2);
13764
+ zip.addLocalFile(file2, path7.dirname(relativePath));
13477
13765
  }
13478
13766
  const zipBuffer = zip.toBuffer();
13479
13767
  console.log(
@@ -13512,8 +13800,8 @@ var pushCommand2 = new Command8().name("push").description("Push local files to
13512
13800
  // src/commands/artifact/pull.ts
13513
13801
  import { Command as Command9 } from "commander";
13514
13802
  import chalk10 from "chalk";
13515
- import path7 from "path";
13516
- import * as fs4 from "fs";
13803
+ import path8 from "path";
13804
+ import * as fs5 from "fs";
13517
13805
  import AdmZip4 from "adm-zip";
13518
13806
  function formatBytes4(bytes) {
13519
13807
  if (bytes === 0) return "0 B";
@@ -13522,7 +13810,7 @@ function formatBytes4(bytes) {
13522
13810
  const i = Math.floor(Math.log(bytes) / Math.log(k));
13523
13811
  return `${(bytes / Math.pow(k, i)).toFixed(2)} ${sizes[i]}`;
13524
13812
  }
13525
- 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) => {
13526
13814
  try {
13527
13815
  const cwd = process.cwd();
13528
13816
  const config2 = await readStorageConfig(cwd);
@@ -13540,11 +13828,21 @@ var pullCommand2 = new Command9().name("pull").description("Pull cloud artifact
13540
13828
  console.error(chalk10.gray(" Use: vm0 volume pull"));
13541
13829
  process.exit(1);
13542
13830
  }
13543
- 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
+ }
13544
13840
  console.log(chalk10.gray("Downloading..."));
13545
- const response = await apiClient.get(
13546
- `/api/storages?name=${encodeURIComponent(config2.name)}`
13547
- );
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);
13548
13846
  if (!response.ok) {
13549
13847
  if (response.status === 404) {
13550
13848
  console.error(chalk10.red(`\u2717 Artifact "${config2.name}" not found`));
@@ -13568,14 +13866,22 @@ var pullCommand2 = new Command9().name("pull").description("Pull cloud artifact
13568
13866
  console.log(chalk10.gray("Extracting files..."));
13569
13867
  const zip = new AdmZip4(zipBuffer);
13570
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
+ }
13571
13877
  let extractedCount = 0;
13572
13878
  for (const entry of zipEntries) {
13573
13879
  if (!entry.isDirectory) {
13574
- const targetPath = path7.join(cwd, entry.entryName);
13575
- const dir = path7.dirname(targetPath);
13576
- 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 });
13577
13883
  const data = entry.getData();
13578
- await fs4.promises.writeFile(targetPath, data);
13884
+ await fs5.promises.writeFile(targetPath, data);
13579
13885
  extractedCount++;
13580
13886
  }
13581
13887
  }
package/package.json CHANGED
@@ -1,7 +1,12 @@
1
1
  {
2
2
  "name": "@vm0/cli",
3
- "version": "1.5.0",
3
+ "version": "1.8.1",
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"