@vm0/cli 4.11.0 → 4.13.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 +446 -398
  2. package/package.json +1 -1
package/index.js CHANGED
@@ -163,12 +163,35 @@ async function checkAuthStatus() {
163
163
  console.log(chalk.blue("Using token from VM0_TOKEN environment variable"));
164
164
  }
165
165
  }
166
+ async function setupToken() {
167
+ const token = await getToken();
168
+ if (!token) {
169
+ console.error(chalk.red("Error: Not authenticated."));
170
+ console.error("");
171
+ console.error("To get a token for CI/CD:");
172
+ console.error(" 1. Run 'vm0 auth login' to authenticate");
173
+ console.error(" 2. Run 'vm0 auth setup-token' to get your token");
174
+ console.error(
175
+ " 3. Store the token in your CI/CD secrets (e.g., VM0_TOKEN)"
176
+ );
177
+ process.exit(1);
178
+ }
179
+ console.log(chalk.green("\u2713 Authentication token exported successfully!"));
180
+ console.log("");
181
+ console.log("Your token:");
182
+ console.log("");
183
+ console.log(token);
184
+ console.log("");
185
+ console.log(
186
+ `Use this token by setting: ${chalk.cyan("export VM0_TOKEN=<token>")}`
187
+ );
188
+ }
166
189
 
167
190
  // src/commands/compose.ts
168
191
  import { Command } from "commander";
169
192
  import chalk2 from "chalk";
170
193
  import { readFile as readFile3 } from "fs/promises";
171
- import { existsSync as existsSync2 } from "fs";
194
+ import { existsSync as existsSync3 } from "fs";
172
195
  import { dirname as dirname2 } from "path";
173
196
  import { parse as parseYaml } from "yaml";
174
197
 
@@ -431,7 +454,7 @@ var ApiClient = class {
431
454
  /**
432
455
  * Generic GET request
433
456
  */
434
- async get(path13) {
457
+ async get(path12) {
435
458
  const baseUrl = await this.getBaseUrl();
436
459
  const token = await getToken();
437
460
  if (!token) {
@@ -444,7 +467,7 @@ var ApiClient = class {
444
467
  if (bypassSecret) {
445
468
  headers["x-vercel-protection-bypass"] = bypassSecret;
446
469
  }
447
- return fetch(`${baseUrl}${path13}`, {
470
+ return fetch(`${baseUrl}${path12}`, {
448
471
  method: "GET",
449
472
  headers
450
473
  });
@@ -452,7 +475,7 @@ var ApiClient = class {
452
475
  /**
453
476
  * Generic POST request
454
477
  */
455
- async post(path13, options) {
478
+ async post(path12, options) {
456
479
  const baseUrl = await this.getBaseUrl();
457
480
  const token = await getToken();
458
481
  if (!token) {
@@ -468,7 +491,7 @@ var ApiClient = class {
468
491
  if (bypassSecret) {
469
492
  headers["x-vercel-protection-bypass"] = bypassSecret;
470
493
  }
471
- return fetch(`${baseUrl}${path13}`, {
494
+ return fetch(`${baseUrl}${path12}`, {
472
495
  method: "POST",
473
496
  headers,
474
497
  body: options?.body
@@ -477,7 +500,7 @@ var ApiClient = class {
477
500
  /**
478
501
  * Generic DELETE request
479
502
  */
480
- async delete(path13) {
503
+ async delete(path12) {
481
504
  const baseUrl = await this.getBaseUrl();
482
505
  const token = await getToken();
483
506
  if (!token) {
@@ -490,7 +513,7 @@ var ApiClient = class {
490
513
  if (bypassSecret) {
491
514
  headers["x-vercel-protection-bypass"] = bypassSecret;
492
515
  }
493
- return fetch(`${baseUrl}${path13}`, {
516
+ return fetch(`${baseUrl}${path12}`, {
494
517
  method: "DELETE",
495
518
  headers
496
519
  });
@@ -694,10 +717,9 @@ function validateAgentCompose(config2) {
694
717
  }
695
718
 
696
719
  // src/lib/system-storage.ts
697
- import * as fs2 from "fs/promises";
698
- import * as path2 from "path";
699
- import * as os2 from "os";
700
- import * as tar from "tar";
720
+ import * as fs4 from "fs/promises";
721
+ import * as path4 from "path";
722
+ import * as os3 from "os";
701
723
 
702
724
  // src/lib/github-skills.ts
703
725
  import * as fs from "fs/promises";
@@ -772,100 +794,330 @@ async function validateSkillDirectory(skillDir) {
772
794
  }
773
795
  }
774
796
 
797
+ // src/lib/direct-upload.ts
798
+ import { createHash } from "crypto";
799
+ import * as fs3 from "fs";
800
+ import * as path3 from "path";
801
+ import * as os2 from "os";
802
+ import * as tar2 from "tar";
803
+
804
+ // src/lib/file-utils.ts
805
+ import * as fs2 from "fs";
806
+ import * as path2 from "path";
807
+ import * as tar from "tar";
808
+ function excludeVm0Filter(filePath) {
809
+ const shouldExclude = filePath === ".vm0" || filePath.startsWith(".vm0/") || filePath.startsWith("./.vm0");
810
+ return !shouldExclude;
811
+ }
812
+ function listTarFiles(tarPath) {
813
+ return new Promise((resolve2, reject) => {
814
+ const files = [];
815
+ tar.list({
816
+ file: tarPath,
817
+ onReadEntry: (entry) => {
818
+ if (entry.type === "File") {
819
+ files.push(entry.path);
820
+ }
821
+ }
822
+ }).then(() => resolve2(files)).catch(reject);
823
+ });
824
+ }
825
+ async function listLocalFiles(dir, excludeDirs = [".vm0"]) {
826
+ const files = [];
827
+ async function walkDir(currentDir, relativePath = "") {
828
+ const entries = await fs2.promises.readdir(currentDir, {
829
+ withFileTypes: true
830
+ });
831
+ for (const entry of entries) {
832
+ const entryRelativePath = relativePath ? path2.join(relativePath, entry.name) : entry.name;
833
+ if (entry.isDirectory()) {
834
+ if (!excludeDirs.includes(entry.name)) {
835
+ await walkDir(path2.join(currentDir, entry.name), entryRelativePath);
836
+ }
837
+ } else {
838
+ files.push(entryRelativePath);
839
+ }
840
+ }
841
+ }
842
+ await walkDir(dir);
843
+ return files;
844
+ }
845
+ async function removeExtraFiles(dir, remoteFiles, excludeDirs = [".vm0"]) {
846
+ const localFiles = await listLocalFiles(dir, excludeDirs);
847
+ let removedCount = 0;
848
+ for (const localFile of localFiles) {
849
+ const normalizedPath = localFile.replace(/\\/g, "/");
850
+ if (!remoteFiles.has(normalizedPath)) {
851
+ const fullPath = path2.join(dir, localFile);
852
+ await fs2.promises.unlink(fullPath);
853
+ removedCount++;
854
+ }
855
+ }
856
+ await removeEmptyDirs(dir, excludeDirs);
857
+ return removedCount;
858
+ }
859
+ async function removeEmptyDirs(dir, excludeDirs = [".vm0"]) {
860
+ const entries = await fs2.promises.readdir(dir, { withFileTypes: true });
861
+ let isEmpty = true;
862
+ for (const entry of entries) {
863
+ const fullPath = path2.join(dir, entry.name);
864
+ if (entry.isDirectory()) {
865
+ if (excludeDirs.includes(entry.name)) {
866
+ isEmpty = false;
867
+ } else {
868
+ const subDirEmpty = await removeEmptyDirs(fullPath, excludeDirs);
869
+ if (subDirEmpty) {
870
+ await fs2.promises.rmdir(fullPath);
871
+ } else {
872
+ isEmpty = false;
873
+ }
874
+ }
875
+ } else {
876
+ isEmpty = false;
877
+ }
878
+ }
879
+ return isEmpty;
880
+ }
881
+
882
+ // src/lib/direct-upload.ts
883
+ async function hashFileStream(filePath) {
884
+ return new Promise((resolve2, reject) => {
885
+ const hash2 = createHash("sha256");
886
+ const stream = fs3.createReadStream(filePath);
887
+ stream.on("data", (chunk) => hash2.update(chunk));
888
+ stream.on("end", () => resolve2(hash2.digest("hex")));
889
+ stream.on("error", reject);
890
+ });
891
+ }
892
+ async function getAllFiles(dirPath, baseDir = dirPath) {
893
+ const files = [];
894
+ const entries = await fs3.promises.readdir(dirPath, { withFileTypes: true });
895
+ for (const entry of entries) {
896
+ const fullPath = path3.join(dirPath, entry.name);
897
+ const relativePath = path3.relative(baseDir, fullPath);
898
+ if (relativePath.startsWith(".vm0")) {
899
+ continue;
900
+ }
901
+ if (entry.isDirectory()) {
902
+ const subFiles = await getAllFiles(fullPath, baseDir);
903
+ files.push(...subFiles);
904
+ } else {
905
+ files.push(fullPath);
906
+ }
907
+ }
908
+ return files;
909
+ }
910
+ async function collectFileMetadata(cwd, files, onProgress) {
911
+ const fileEntries = [];
912
+ for (let i = 0; i < files.length; i++) {
913
+ const file2 = files[i];
914
+ const relativePath = path3.relative(cwd, file2);
915
+ const [hash2, stats] = await Promise.all([
916
+ hashFileStream(file2),
917
+ fs3.promises.stat(file2)
918
+ ]);
919
+ fileEntries.push({
920
+ path: relativePath,
921
+ hash: hash2,
922
+ size: stats.size
923
+ });
924
+ if (onProgress && (i + 1) % 100 === 0) {
925
+ onProgress(`Hashing files... ${i + 1}/${files.length}`);
926
+ }
927
+ }
928
+ return fileEntries;
929
+ }
930
+ async function createArchive(cwd, files) {
931
+ const tmpDir = fs3.mkdtempSync(path3.join(os2.tmpdir(), "vm0-"));
932
+ const tarPath = path3.join(tmpDir, "archive.tar.gz");
933
+ try {
934
+ const relativePaths = files.map((file2) => path3.relative(cwd, file2));
935
+ if (relativePaths.length > 0) {
936
+ await tar2.create(
937
+ {
938
+ gzip: true,
939
+ file: tarPath,
940
+ cwd
941
+ },
942
+ relativePaths
943
+ );
944
+ } else {
945
+ await tar2.create(
946
+ {
947
+ gzip: true,
948
+ file: tarPath,
949
+ cwd,
950
+ filter: excludeVm0Filter
951
+ },
952
+ ["."]
953
+ );
954
+ }
955
+ const tarBuffer = await fs3.promises.readFile(tarPath);
956
+ return tarBuffer;
957
+ } finally {
958
+ if (fs3.existsSync(tarPath)) {
959
+ await fs3.promises.unlink(tarPath);
960
+ }
961
+ await fs3.promises.rmdir(tmpDir);
962
+ }
963
+ }
964
+ function createManifest(files) {
965
+ const manifest = {
966
+ version: 1,
967
+ files,
968
+ createdAt: (/* @__PURE__ */ new Date()).toISOString()
969
+ };
970
+ return Buffer.from(JSON.stringify(manifest, null, 2));
971
+ }
972
+ function sleep(ms) {
973
+ return new Promise((resolve2) => setTimeout(resolve2, ms));
974
+ }
975
+ async function uploadToPresignedUrl(presignedUrl, data, contentType, maxRetries = 3) {
976
+ let lastError = null;
977
+ for (let attempt = 1; attempt <= maxRetries; attempt++) {
978
+ try {
979
+ const response = await fetch(presignedUrl, {
980
+ method: "PUT",
981
+ body: data,
982
+ headers: {
983
+ "Content-Type": contentType
984
+ }
985
+ });
986
+ if (response.ok) {
987
+ return;
988
+ }
989
+ if (response.status >= 400 && response.status < 500) {
990
+ const text2 = await response.text();
991
+ throw new Error(`S3 upload failed: ${response.status} - ${text2}`);
992
+ }
993
+ const text = await response.text();
994
+ lastError = new Error(`S3 upload failed: ${response.status} - ${text}`);
995
+ } catch (error43) {
996
+ lastError = error43 instanceof Error ? error43 : new Error("Unknown upload error");
997
+ if (lastError.message.includes("400") || lastError.message.includes("403")) {
998
+ throw lastError;
999
+ }
1000
+ }
1001
+ if (attempt < maxRetries) {
1002
+ const backoffMs = Math.pow(2, attempt - 1) * 1e3;
1003
+ await sleep(backoffMs);
1004
+ }
1005
+ }
1006
+ throw lastError || new Error("S3 upload failed after retries");
1007
+ }
1008
+ async function directUpload(storageName, storageType, cwd, options) {
1009
+ const { onProgress, force } = options || {};
1010
+ onProgress?.("Collecting files...");
1011
+ const files = await getAllFiles(cwd);
1012
+ onProgress?.("Computing file hashes...");
1013
+ const fileEntries = await collectFileMetadata(cwd, files, onProgress);
1014
+ const totalSize = fileEntries.reduce((sum, f) => sum + f.size, 0);
1015
+ onProgress?.("Preparing upload...");
1016
+ const prepareResponse = await apiClient.post("/api/storages/prepare", {
1017
+ body: JSON.stringify({
1018
+ storageName,
1019
+ storageType,
1020
+ files: fileEntries,
1021
+ force
1022
+ })
1023
+ });
1024
+ if (!prepareResponse.ok) {
1025
+ const error43 = await prepareResponse.json();
1026
+ throw new Error(error43.error?.message || "Prepare failed");
1027
+ }
1028
+ const prepareResult = await prepareResponse.json();
1029
+ if (prepareResult.existing) {
1030
+ return {
1031
+ versionId: prepareResult.versionId,
1032
+ size: totalSize,
1033
+ fileCount: fileEntries.length,
1034
+ deduplicated: true,
1035
+ empty: fileEntries.length === 0
1036
+ };
1037
+ }
1038
+ onProgress?.("Compressing files...");
1039
+ const archiveBuffer = await createArchive(cwd, files);
1040
+ onProgress?.("Uploading archive to S3...");
1041
+ if (!prepareResult.uploads) {
1042
+ throw new Error("No upload URLs received from prepare endpoint");
1043
+ }
1044
+ await uploadToPresignedUrl(
1045
+ prepareResult.uploads.archive.presignedUrl,
1046
+ archiveBuffer,
1047
+ "application/gzip"
1048
+ );
1049
+ onProgress?.("Uploading manifest...");
1050
+ const manifestBuffer = createManifest(fileEntries);
1051
+ await uploadToPresignedUrl(
1052
+ prepareResult.uploads.manifest.presignedUrl,
1053
+ manifestBuffer,
1054
+ "application/json"
1055
+ );
1056
+ onProgress?.("Committing...");
1057
+ const commitResponse = await apiClient.post("/api/storages/commit", {
1058
+ body: JSON.stringify({
1059
+ storageName,
1060
+ storageType,
1061
+ versionId: prepareResult.versionId,
1062
+ files: fileEntries
1063
+ })
1064
+ });
1065
+ if (!commitResponse.ok) {
1066
+ const error43 = await commitResponse.json();
1067
+ throw new Error(error43.error?.message || "Commit failed");
1068
+ }
1069
+ const commitResult = await commitResponse.json();
1070
+ return {
1071
+ versionId: commitResult.versionId,
1072
+ size: commitResult.size,
1073
+ fileCount: commitResult.fileCount,
1074
+ deduplicated: commitResult.deduplicated || false,
1075
+ empty: commitResult.fileCount === 0
1076
+ };
1077
+ }
1078
+
775
1079
  // src/lib/system-storage.ts
776
1080
  async function uploadSystemPrompt(agentName, promptFilePath, basePath) {
777
1081
  const storageName = getSystemPromptStorageName(agentName);
778
- const absolutePath = path2.isAbsolute(promptFilePath) ? promptFilePath : path2.join(basePath, promptFilePath);
779
- const content = await fs2.readFile(absolutePath, "utf8");
780
- const tmpDir = await fs2.mkdtemp(path2.join(os2.tmpdir(), "vm0-prompt-"));
781
- const promptDir = path2.join(tmpDir, "prompt");
782
- await fs2.mkdir(promptDir);
783
- await fs2.writeFile(path2.join(promptDir, "CLAUDE.md"), content);
1082
+ const absolutePath = path4.isAbsolute(promptFilePath) ? promptFilePath : path4.join(basePath, promptFilePath);
1083
+ const content = await fs4.readFile(absolutePath, "utf8");
1084
+ const tmpDir = await fs4.mkdtemp(path4.join(os3.tmpdir(), "vm0-prompt-"));
1085
+ const promptDir = path4.join(tmpDir, "prompt");
1086
+ await fs4.mkdir(promptDir);
1087
+ await fs4.writeFile(path4.join(promptDir, "CLAUDE.md"), content);
784
1088
  try {
785
- const tarPath = path2.join(tmpDir, "prompt.tar.gz");
786
- await tar.create(
787
- {
788
- gzip: true,
789
- file: tarPath,
790
- cwd: promptDir
791
- },
792
- ["."]
793
- );
794
- const tarBuffer = await fs2.readFile(tarPath);
795
- const formData = new FormData();
796
- formData.append("name", storageName);
797
- formData.append("type", "volume");
798
- formData.append(
799
- "file",
800
- new Blob([new Uint8Array(tarBuffer)], { type: "application/gzip" }),
801
- "volume.tar.gz"
802
- );
803
- const response = await apiClient.post("/api/storages", {
804
- body: formData
805
- });
806
- if (!response.ok) {
807
- const errorBody = await response.json();
808
- const errorMessage = typeof errorBody.error === "string" ? errorBody.error : errorBody.error?.message || "Upload failed";
809
- throw new Error(errorMessage);
810
- }
811
- const result = await response.json();
1089
+ const result = await directUpload(storageName, "volume", promptDir);
812
1090
  return {
813
1091
  name: storageName,
814
1092
  versionId: result.versionId,
815
1093
  action: result.deduplicated ? "deduplicated" : "created"
816
1094
  };
817
1095
  } finally {
818
- await fs2.rm(tmpDir, { recursive: true, force: true });
1096
+ await fs4.rm(tmpDir, { recursive: true, force: true });
819
1097
  }
820
1098
  }
821
1099
  async function uploadSystemSkill(skillUrl) {
822
1100
  const parsed = parseGitHubTreeUrl(skillUrl);
823
1101
  const storageName = getSkillStorageName(parsed);
824
- const tmpDir = await fs2.mkdtemp(path2.join(os2.tmpdir(), "vm0-skill-"));
1102
+ const tmpDir = await fs4.mkdtemp(path4.join(os3.tmpdir(), "vm0-skill-"));
825
1103
  try {
826
1104
  const skillDir = await downloadGitHubSkill(parsed, tmpDir);
827
1105
  await validateSkillDirectory(skillDir);
828
- const tarPath = path2.join(tmpDir, "skill.tar.gz");
829
- await tar.create(
830
- {
831
- gzip: true,
832
- file: tarPath,
833
- cwd: skillDir
834
- },
835
- ["."]
836
- );
837
- const tarBuffer = await fs2.readFile(tarPath);
838
- const formData = new FormData();
839
- formData.append("name", storageName);
840
- formData.append("type", "volume");
841
- formData.append(
842
- "file",
843
- new Blob([new Uint8Array(tarBuffer)], { type: "application/gzip" }),
844
- "volume.tar.gz"
845
- );
846
- const response = await apiClient.post("/api/storages", {
847
- body: formData
848
- });
849
- if (!response.ok) {
850
- const errorBody = await response.json();
851
- const errorMessage = typeof errorBody.error === "string" ? errorBody.error : errorBody.error?.message || "Upload failed";
852
- throw new Error(errorMessage);
853
- }
854
- const result = await response.json();
1106
+ const result = await directUpload(storageName, "volume", skillDir);
855
1107
  return {
856
1108
  name: storageName,
857
1109
  versionId: result.versionId,
858
1110
  action: result.deduplicated ? "deduplicated" : "created"
859
1111
  };
860
1112
  } finally {
861
- await fs2.rm(tmpDir, { recursive: true, force: true });
1113
+ await fs4.rm(tmpDir, { recursive: true, force: true });
862
1114
  }
863
1115
  }
864
1116
 
865
1117
  // src/commands/compose.ts
866
1118
  var composeCommand = new Command().name("compose").description("Create or update agent compose").argument("<config-file>", "Path to config YAML file").action(async (configFile) => {
867
1119
  try {
868
- if (!existsSync2(configFile)) {
1120
+ if (!existsSync3(configFile)) {
869
1121
  console.error(chalk2.red(`\u2717 Config file not found: ${configFile}`));
870
1122
  process.exit(1);
871
1123
  }
@@ -988,8 +1240,8 @@ var composeCommand = new Command().name("compose").description("Create or update
988
1240
  // src/commands/run.ts
989
1241
  import { Command as Command2 } from "commander";
990
1242
  import chalk4 from "chalk";
991
- import * as fs3 from "fs";
992
- import * as path3 from "path";
1243
+ import * as fs5 from "fs";
1244
+ import * as path5 from "path";
993
1245
  import { config as dotenvConfig } from "dotenv";
994
1246
 
995
1247
  // src/lib/event-parser.ts
@@ -2073,15 +2325,15 @@ function mergeDefs(...defs) {
2073
2325
  function cloneDef(schema) {
2074
2326
  return mergeDefs(schema._zod.def);
2075
2327
  }
2076
- function getElementAtPath(obj, path13) {
2077
- if (!path13)
2328
+ function getElementAtPath(obj, path12) {
2329
+ if (!path12)
2078
2330
  return obj;
2079
- return path13.reduce((acc, key) => acc?.[key], obj);
2331
+ return path12.reduce((acc, key) => acc?.[key], obj);
2080
2332
  }
2081
2333
  function promiseAllObject(promisesObj) {
2082
2334
  const keys = Object.keys(promisesObj);
2083
- const promises6 = keys.map((key) => promisesObj[key]);
2084
- return Promise.all(promises6).then((results) => {
2335
+ const promises5 = keys.map((key) => promisesObj[key]);
2336
+ return Promise.all(promises5).then((results) => {
2085
2337
  const resolvedObj = {};
2086
2338
  for (let i = 0; i < keys.length; i++) {
2087
2339
  resolvedObj[keys[i]] = results[i];
@@ -2435,11 +2687,11 @@ function aborted(x, startIndex = 0) {
2435
2687
  }
2436
2688
  return false;
2437
2689
  }
2438
- function prefixIssues(path13, issues) {
2690
+ function prefixIssues(path12, issues) {
2439
2691
  return issues.map((iss) => {
2440
2692
  var _a;
2441
2693
  (_a = iss).path ?? (_a.path = []);
2442
- iss.path.unshift(path13);
2694
+ iss.path.unshift(path12);
2443
2695
  return iss;
2444
2696
  });
2445
2697
  }
@@ -2607,7 +2859,7 @@ function treeifyError(error43, _mapper) {
2607
2859
  return issue2.message;
2608
2860
  };
2609
2861
  const result = { errors: [] };
2610
- const processError = (error44, path13 = []) => {
2862
+ const processError = (error44, path12 = []) => {
2611
2863
  var _a, _b;
2612
2864
  for (const issue2 of error44.issues) {
2613
2865
  if (issue2.code === "invalid_union" && issue2.errors.length) {
@@ -2617,7 +2869,7 @@ function treeifyError(error43, _mapper) {
2617
2869
  } else if (issue2.code === "invalid_element") {
2618
2870
  processError({ issues: issue2.issues }, issue2.path);
2619
2871
  } else {
2620
- const fullpath = [...path13, ...issue2.path];
2872
+ const fullpath = [...path12, ...issue2.path];
2621
2873
  if (fullpath.length === 0) {
2622
2874
  result.errors.push(mapper(issue2));
2623
2875
  continue;
@@ -2649,8 +2901,8 @@ function treeifyError(error43, _mapper) {
2649
2901
  }
2650
2902
  function toDotPath(_path) {
2651
2903
  const segs = [];
2652
- const path13 = _path.map((seg) => typeof seg === "object" ? seg.key : seg);
2653
- for (const seg of path13) {
2904
+ const path12 = _path.map((seg) => typeof seg === "object" ? seg.key : seg);
2905
+ for (const seg of path12) {
2654
2906
  if (typeof seg === "number")
2655
2907
  segs.push(`[${seg}]`);
2656
2908
  else if (typeof seg === "symbol")
@@ -14521,9 +14773,9 @@ function loadValues(cliValues, configNames) {
14521
14773
  const result = { ...cliValues };
14522
14774
  const missingNames = configNames.filter((name) => !(name in result));
14523
14775
  if (missingNames.length > 0) {
14524
- const envFilePath = path3.resolve(process.cwd(), ".env");
14776
+ const envFilePath = path5.resolve(process.cwd(), ".env");
14525
14777
  let dotenvValues = {};
14526
- if (fs3.existsSync(envFilePath)) {
14778
+ if (fs5.existsSync(envFilePath)) {
14527
14779
  const dotenvResult = dotenvConfig({ path: envFilePath });
14528
14780
  if (dotenvResult.parsed) {
14529
14781
  dotenvValues = Object.fromEntries(
@@ -15006,13 +15258,13 @@ import { Command as Command6 } from "commander";
15006
15258
  // src/commands/volume/init.ts
15007
15259
  import { Command as Command3 } from "commander";
15008
15260
  import chalk5 from "chalk";
15009
- import path5 from "path";
15261
+ import path7 from "path";
15010
15262
 
15011
15263
  // src/lib/storage-utils.ts
15012
15264
  import { readFile as readFile4, writeFile as writeFile4, mkdir as mkdir4 } from "fs/promises";
15013
- import { existsSync as existsSync4 } from "fs";
15265
+ import { existsSync as existsSync5 } from "fs";
15014
15266
  import { parse as parseYaml2, stringify as stringifyYaml } from "yaml";
15015
- import path4 from "path";
15267
+ import path6 from "path";
15016
15268
  var CONFIG_DIR2 = ".vm0";
15017
15269
  var CONFIG_FILE2 = "storage.yaml";
15018
15270
  function isValidStorageName(name) {
@@ -15023,12 +15275,12 @@ function isValidStorageName(name) {
15023
15275
  return pattern.test(name) && !name.includes("--");
15024
15276
  }
15025
15277
  async function readStorageConfig(basePath = process.cwd()) {
15026
- const configPath = path4.join(basePath, CONFIG_DIR2, CONFIG_FILE2);
15027
- const legacyConfigPath = path4.join(basePath, CONFIG_DIR2, "volume.yaml");
15278
+ const configPath = path6.join(basePath, CONFIG_DIR2, CONFIG_FILE2);
15279
+ const legacyConfigPath = path6.join(basePath, CONFIG_DIR2, "volume.yaml");
15028
15280
  let actualPath = null;
15029
- if (existsSync4(configPath)) {
15281
+ if (existsSync5(configPath)) {
15030
15282
  actualPath = configPath;
15031
- } else if (existsSync4(legacyConfigPath)) {
15283
+ } else if (existsSync5(legacyConfigPath)) {
15032
15284
  actualPath = legacyConfigPath;
15033
15285
  }
15034
15286
  if (!actualPath) {
@@ -15042,9 +15294,9 @@ async function readStorageConfig(basePath = process.cwd()) {
15042
15294
  return config2;
15043
15295
  }
15044
15296
  async function writeStorageConfig(storageName, basePath = process.cwd(), type = "volume") {
15045
- const configDir = path4.join(basePath, CONFIG_DIR2);
15046
- const configPath = path4.join(configDir, CONFIG_FILE2);
15047
- if (!existsSync4(configDir)) {
15297
+ const configDir = path6.join(basePath, CONFIG_DIR2);
15298
+ const configPath = path6.join(configDir, CONFIG_FILE2);
15299
+ if (!existsSync5(configDir)) {
15048
15300
  await mkdir4(configDir, { recursive: true });
15049
15301
  }
15050
15302
  const config2 = {
@@ -15059,14 +15311,14 @@ async function writeStorageConfig(storageName, basePath = process.cwd(), type =
15059
15311
  var initCommand = new Command3().name("init").description("Initialize a volume in the current directory").action(async () => {
15060
15312
  try {
15061
15313
  const cwd = process.cwd();
15062
- const dirName = path5.basename(cwd);
15314
+ const dirName = path7.basename(cwd);
15063
15315
  const existingConfig = await readStorageConfig(cwd);
15064
15316
  if (existingConfig) {
15065
15317
  console.log(
15066
15318
  chalk5.yellow(`Volume already initialized: ${existingConfig.name}`)
15067
15319
  );
15068
15320
  console.log(
15069
- chalk5.gray(`Config file: ${path5.join(cwd, ".vm0", "storage.yaml")}`)
15321
+ chalk5.gray(`Config file: ${path7.join(cwd, ".vm0", "storage.yaml")}`)
15070
15322
  );
15071
15323
  return;
15072
15324
  }
@@ -15087,7 +15339,7 @@ var initCommand = new Command3().name("init").description("Initialize a volume i
15087
15339
  console.log(chalk5.green(`\u2713 Initialized volume: ${volumeName}`));
15088
15340
  console.log(
15089
15341
  chalk5.gray(
15090
- `\u2713 Config saved to ${path5.join(cwd, ".vm0", "storage.yaml")}`
15342
+ `\u2713 Config saved to ${path7.join(cwd, ".vm0", "storage.yaml")}`
15091
15343
  )
15092
15344
  );
15093
15345
  } catch (error43) {
@@ -15102,108 +15354,6 @@ var initCommand = new Command3().name("init").description("Initialize a volume i
15102
15354
  // src/commands/volume/push.ts
15103
15355
  import { Command as Command4 } from "commander";
15104
15356
  import chalk6 from "chalk";
15105
- import path7 from "path";
15106
- import * as fs5 from "fs";
15107
- import * as os3 from "os";
15108
- import * as tar3 from "tar";
15109
-
15110
- // src/lib/file-utils.ts
15111
- import * as fs4 from "fs";
15112
- import * as path6 from "path";
15113
- import * as tar2 from "tar";
15114
- function excludeVm0Filter(filePath) {
15115
- const shouldExclude = filePath === ".vm0" || filePath.startsWith(".vm0/") || filePath.startsWith("./.vm0");
15116
- return !shouldExclude;
15117
- }
15118
- function listTarFiles(tarPath) {
15119
- return new Promise((resolve2, reject) => {
15120
- const files = [];
15121
- tar2.list({
15122
- file: tarPath,
15123
- onReadEntry: (entry) => {
15124
- if (entry.type === "File") {
15125
- files.push(entry.path);
15126
- }
15127
- }
15128
- }).then(() => resolve2(files)).catch(reject);
15129
- });
15130
- }
15131
- async function listLocalFiles(dir, excludeDirs = [".vm0"]) {
15132
- const files = [];
15133
- async function walkDir(currentDir, relativePath = "") {
15134
- const entries = await fs4.promises.readdir(currentDir, {
15135
- withFileTypes: true
15136
- });
15137
- for (const entry of entries) {
15138
- const entryRelativePath = relativePath ? path6.join(relativePath, entry.name) : entry.name;
15139
- if (entry.isDirectory()) {
15140
- if (!excludeDirs.includes(entry.name)) {
15141
- await walkDir(path6.join(currentDir, entry.name), entryRelativePath);
15142
- }
15143
- } else {
15144
- files.push(entryRelativePath);
15145
- }
15146
- }
15147
- }
15148
- await walkDir(dir);
15149
- return files;
15150
- }
15151
- async function removeExtraFiles(dir, remoteFiles, excludeDirs = [".vm0"]) {
15152
- const localFiles = await listLocalFiles(dir, excludeDirs);
15153
- let removedCount = 0;
15154
- for (const localFile of localFiles) {
15155
- const normalizedPath = localFile.replace(/\\/g, "/");
15156
- if (!remoteFiles.has(normalizedPath)) {
15157
- const fullPath = path6.join(dir, localFile);
15158
- await fs4.promises.unlink(fullPath);
15159
- removedCount++;
15160
- }
15161
- }
15162
- await removeEmptyDirs(dir, excludeDirs);
15163
- return removedCount;
15164
- }
15165
- async function removeEmptyDirs(dir, excludeDirs = [".vm0"]) {
15166
- const entries = await fs4.promises.readdir(dir, { withFileTypes: true });
15167
- let isEmpty = true;
15168
- for (const entry of entries) {
15169
- const fullPath = path6.join(dir, entry.name);
15170
- if (entry.isDirectory()) {
15171
- if (excludeDirs.includes(entry.name)) {
15172
- isEmpty = false;
15173
- } else {
15174
- const subDirEmpty = await removeEmptyDirs(fullPath, excludeDirs);
15175
- if (subDirEmpty) {
15176
- await fs4.promises.rmdir(fullPath);
15177
- } else {
15178
- isEmpty = false;
15179
- }
15180
- }
15181
- } else {
15182
- isEmpty = false;
15183
- }
15184
- }
15185
- return isEmpty;
15186
- }
15187
-
15188
- // src/commands/volume/push.ts
15189
- async function getAllFiles(dirPath, baseDir = dirPath) {
15190
- const files = [];
15191
- const entries = await fs5.promises.readdir(dirPath, { withFileTypes: true });
15192
- for (const entry of entries) {
15193
- const fullPath = path7.join(dirPath, entry.name);
15194
- const relativePath = path7.relative(baseDir, fullPath);
15195
- if (relativePath.startsWith(".vm0")) {
15196
- continue;
15197
- }
15198
- if (entry.isDirectory()) {
15199
- const subFiles = await getAllFiles(fullPath, baseDir);
15200
- files.push(...subFiles);
15201
- } else {
15202
- files.push(fullPath);
15203
- }
15204
- }
15205
- return files;
15206
- }
15207
15357
  function formatBytes(bytes) {
15208
15358
  if (bytes === 0) return "0 B";
15209
15359
  const k = 1024;
@@ -15224,73 +15374,16 @@ var pushCommand = new Command4().name("push").description("Push local files to c
15224
15374
  process.exit(1);
15225
15375
  }
15226
15376
  console.log(chalk6.cyan(`Pushing volume: ${config2.name}`));
15227
- console.log(chalk6.gray("Collecting files..."));
15228
- const files = await getAllFiles(cwd);
15229
- let totalSize = 0;
15230
- for (const file2 of files) {
15231
- const stats = await fs5.promises.stat(file2);
15232
- totalSize += stats.size;
15233
- }
15234
- if (files.length === 0) {
15235
- console.log(chalk6.gray("No files found (empty volume)"));
15236
- } else {
15237
- console.log(
15238
- chalk6.gray(`Found ${files.length} files (${formatBytes(totalSize)})`)
15239
- );
15240
- }
15241
- console.log(chalk6.gray("Compressing files..."));
15242
- const tmpDir = fs5.mkdtempSync(path7.join(os3.tmpdir(), "vm0-"));
15243
- const tarPath = path7.join(tmpDir, "volume.tar.gz");
15244
- const relativePaths = files.map((file2) => path7.relative(cwd, file2));
15245
- if (relativePaths.length > 0) {
15246
- await tar3.create(
15247
- {
15248
- gzip: true,
15249
- file: tarPath,
15250
- cwd
15251
- },
15252
- relativePaths
15253
- );
15254
- } else {
15255
- await tar3.create(
15256
- {
15257
- gzip: true,
15258
- file: tarPath,
15259
- cwd,
15260
- filter: excludeVm0Filter
15261
- },
15262
- ["."]
15263
- );
15264
- }
15265
- const tarBuffer = await fs5.promises.readFile(tarPath);
15266
- await fs5.promises.unlink(tarPath);
15267
- await fs5.promises.rmdir(tmpDir);
15268
- console.log(
15269
- chalk6.green(`\u2713 Compressed to ${formatBytes(tarBuffer.length)}`)
15270
- );
15271
- console.log(chalk6.gray("Uploading..."));
15272
- const formData = new FormData();
15273
- formData.append("name", config2.name);
15274
- formData.append("type", "volume");
15275
- if (options.force) {
15276
- formData.append("force", "true");
15277
- }
15278
- formData.append(
15279
- "file",
15280
- new Blob([tarBuffer], { type: "application/gzip" }),
15281
- "volume.tar.gz"
15282
- );
15283
- const response = await apiClient.post("/api/storages", {
15284
- body: formData
15377
+ const result = await directUpload(config2.name, "volume", cwd, {
15378
+ onProgress: (message) => {
15379
+ console.log(chalk6.gray(message));
15380
+ },
15381
+ force: options.force
15285
15382
  });
15286
- if (!response.ok) {
15287
- const error43 = await response.json();
15288
- const message = error43.cause ? `${error43.error} (cause: ${error43.cause})` : error43.error || "Upload failed";
15289
- throw new Error(message);
15290
- }
15291
- const result = await response.json();
15292
15383
  const shortVersion = result.versionId.slice(0, 8);
15293
- if (result.deduplicated) {
15384
+ if (result.empty) {
15385
+ console.log(chalk6.yellow("No files found (empty volume)"));
15386
+ } else if (result.deduplicated) {
15294
15387
  console.log(chalk6.green("\u2713 Content unchanged (deduplicated)"));
15295
15388
  } else {
15296
15389
  console.log(chalk6.green("\u2713 Upload complete"));
@@ -15313,7 +15406,7 @@ import chalk8 from "chalk";
15313
15406
  import path8 from "path";
15314
15407
  import * as fs6 from "fs";
15315
15408
  import * as os4 from "os";
15316
- import * as tar4 from "tar";
15409
+ import * as tar3 from "tar";
15317
15410
 
15318
15411
  // src/lib/pull-utils.ts
15319
15412
  import chalk7 from "chalk";
@@ -15351,8 +15444,8 @@ var pullCommand = new Command5().name("pull").description("Pull cloud files to l
15351
15444
  } else {
15352
15445
  console.log(chalk8.cyan(`Pulling volume: ${config2.name}`));
15353
15446
  }
15354
- console.log(chalk8.gray("Downloading..."));
15355
- let url2 = `/api/storages?name=${encodeURIComponent(config2.name)}&type=volume`;
15447
+ console.log(chalk8.gray("Getting download URL..."));
15448
+ let url2 = `/api/storages/download?name=${encodeURIComponent(config2.name)}&type=volume`;
15356
15449
  if (versionId) {
15357
15450
  url2 += `&version=${encodeURIComponent(versionId)}`;
15358
15451
  }
@@ -15374,11 +15467,20 @@ var pullCommand = new Command5().name("pull").description("Pull cloud files to l
15374
15467
  }
15375
15468
  process.exit(1);
15376
15469
  }
15377
- if (response.status === 204) {
15470
+ const downloadInfo = await response.json();
15471
+ if (downloadInfo.empty) {
15378
15472
  await handleEmptyStorageResponse(cwd);
15379
15473
  return;
15380
15474
  }
15381
- const arrayBuffer = await response.arrayBuffer();
15475
+ if (!downloadInfo.url) {
15476
+ throw new Error("No download URL returned");
15477
+ }
15478
+ console.log(chalk8.gray("Downloading from S3..."));
15479
+ const s3Response = await fetch(downloadInfo.url);
15480
+ if (!s3Response.ok) {
15481
+ throw new Error(`S3 download failed: ${s3Response.status}`);
15482
+ }
15483
+ const arrayBuffer = await s3Response.arrayBuffer();
15382
15484
  const tarBuffer = Buffer.from(arrayBuffer);
15383
15485
  console.log(chalk8.green(`\u2713 Downloaded ${formatBytes2(tarBuffer.length)}`));
15384
15486
  const tmpDir = fs6.mkdtempSync(path8.join(os4.tmpdir(), "vm0-"));
@@ -15396,7 +15498,7 @@ var pullCommand = new Command5().name("pull").description("Pull cloud files to l
15396
15498
  );
15397
15499
  }
15398
15500
  console.log(chalk8.gray("Extracting files..."));
15399
- await tar4.extract({
15501
+ await tar3.extract({
15400
15502
  file: tarPath,
15401
15503
  cwd,
15402
15504
  gzip: true
@@ -15484,28 +15586,6 @@ var initCommand2 = new Command7().name("init").description("Initialize an artifa
15484
15586
  // src/commands/artifact/push.ts
15485
15587
  import { Command as Command8 } from "commander";
15486
15588
  import chalk10 from "chalk";
15487
- import path10 from "path";
15488
- import * as fs7 from "fs";
15489
- import * as os5 from "os";
15490
- import * as tar5 from "tar";
15491
- async function getAllFiles2(dirPath, baseDir = dirPath) {
15492
- const files = [];
15493
- const entries = await fs7.promises.readdir(dirPath, { withFileTypes: true });
15494
- for (const entry of entries) {
15495
- const fullPath = path10.join(dirPath, entry.name);
15496
- const relativePath = path10.relative(baseDir, fullPath);
15497
- if (relativePath.startsWith(".vm0")) {
15498
- continue;
15499
- }
15500
- if (entry.isDirectory()) {
15501
- const subFiles = await getAllFiles2(fullPath, baseDir);
15502
- files.push(...subFiles);
15503
- } else {
15504
- files.push(fullPath);
15505
- }
15506
- }
15507
- return files;
15508
- }
15509
15589
  function formatBytes3(bytes) {
15510
15590
  if (bytes === 0) return "0 B";
15511
15591
  const k = 1024;
@@ -15535,72 +15615,16 @@ var pushCommand2 = new Command8().name("push").description("Push local files to
15535
15615
  process.exit(1);
15536
15616
  }
15537
15617
  console.log(chalk10.cyan(`Pushing artifact: ${config2.name}`));
15538
- console.log(chalk10.gray("Collecting files..."));
15539
- const files = await getAllFiles2(cwd);
15540
- let totalSize = 0;
15541
- for (const file2 of files) {
15542
- const stats = await fs7.promises.stat(file2);
15543
- totalSize += stats.size;
15544
- }
15545
- if (files.length === 0) {
15546
- console.log(chalk10.gray("No files found (empty artifact)"));
15547
- } else {
15548
- console.log(
15549
- chalk10.gray(`Found ${files.length} files (${formatBytes3(totalSize)})`)
15550
- );
15551
- }
15552
- console.log(chalk10.gray("Compressing files..."));
15553
- const tmpDir = fs7.mkdtempSync(path10.join(os5.tmpdir(), "vm0-"));
15554
- const tarPath = path10.join(tmpDir, "artifact.tar.gz");
15555
- const relativePaths = files.map((file2) => path10.relative(cwd, file2));
15556
- if (relativePaths.length > 0) {
15557
- await tar5.create(
15558
- {
15559
- gzip: true,
15560
- file: tarPath,
15561
- cwd
15562
- },
15563
- relativePaths
15564
- );
15565
- } else {
15566
- await tar5.create(
15567
- {
15568
- gzip: true,
15569
- file: tarPath,
15570
- cwd,
15571
- filter: excludeVm0Filter
15572
- },
15573
- ["."]
15574
- );
15575
- }
15576
- const tarBuffer = await fs7.promises.readFile(tarPath);
15577
- await fs7.promises.unlink(tarPath);
15578
- await fs7.promises.rmdir(tmpDir);
15579
- console.log(
15580
- chalk10.green(`\u2713 Compressed to ${formatBytes3(tarBuffer.length)}`)
15581
- );
15582
- console.log(chalk10.gray("Uploading..."));
15583
- const formData = new FormData();
15584
- formData.append("name", config2.name);
15585
- formData.append("type", "artifact");
15586
- if (options.force) {
15587
- formData.append("force", "true");
15588
- }
15589
- formData.append(
15590
- "file",
15591
- new Blob([tarBuffer], { type: "application/gzip" }),
15592
- "artifact.tar.gz"
15593
- );
15594
- const response = await apiClient.post("/api/storages", {
15595
- body: formData
15618
+ const result = await directUpload(config2.name, "artifact", cwd, {
15619
+ onProgress: (message) => {
15620
+ console.log(chalk10.gray(message));
15621
+ },
15622
+ force: options.force
15596
15623
  });
15597
- if (!response.ok) {
15598
- const error43 = await response.json();
15599
- throw new Error(error43.error || "Upload failed");
15600
- }
15601
- const result = await response.json();
15602
15624
  const shortVersion = result.versionId.slice(0, 8);
15603
- if (result.deduplicated) {
15625
+ if (result.empty) {
15626
+ console.log(chalk10.yellow("No files found (empty artifact)"));
15627
+ } else if (result.deduplicated) {
15604
15628
  console.log(chalk10.green("\u2713 Content unchanged (deduplicated)"));
15605
15629
  } else {
15606
15630
  console.log(chalk10.green("\u2713 Upload complete"));
@@ -15620,10 +15644,10 @@ var pushCommand2 = new Command8().name("push").description("Push local files to
15620
15644
  // src/commands/artifact/pull.ts
15621
15645
  import { Command as Command9 } from "commander";
15622
15646
  import chalk11 from "chalk";
15623
- import path11 from "path";
15624
- import * as fs8 from "fs";
15625
- import * as os6 from "os";
15626
- import * as tar6 from "tar";
15647
+ import path10 from "path";
15648
+ import * as fs7 from "fs";
15649
+ import * as os5 from "os";
15650
+ import * as tar4 from "tar";
15627
15651
  function formatBytes4(bytes) {
15628
15652
  if (bytes === 0) return "0 B";
15629
15653
  const k = 1024;
@@ -15658,8 +15682,8 @@ var pullCommand2 = new Command9().name("pull").description("Pull cloud artifact
15658
15682
  } else {
15659
15683
  console.log(chalk11.cyan(`Pulling artifact: ${config2.name}`));
15660
15684
  }
15661
- console.log(chalk11.gray("Downloading..."));
15662
- let url2 = `/api/storages?name=${encodeURIComponent(config2.name)}&type=artifact`;
15685
+ console.log(chalk11.gray("Getting download URL..."));
15686
+ let url2 = `/api/storages/download?name=${encodeURIComponent(config2.name)}&type=artifact`;
15663
15687
  if (versionId) {
15664
15688
  url2 += `&version=${encodeURIComponent(versionId)}`;
15665
15689
  }
@@ -15681,16 +15705,25 @@ var pullCommand2 = new Command9().name("pull").description("Pull cloud artifact
15681
15705
  }
15682
15706
  process.exit(1);
15683
15707
  }
15684
- if (response.status === 204) {
15708
+ const downloadInfo = await response.json();
15709
+ if (downloadInfo.empty) {
15685
15710
  await handleEmptyStorageResponse(cwd);
15686
15711
  return;
15687
15712
  }
15688
- const arrayBuffer = await response.arrayBuffer();
15713
+ if (!downloadInfo.url) {
15714
+ throw new Error("No download URL returned");
15715
+ }
15716
+ console.log(chalk11.gray("Downloading from S3..."));
15717
+ const s3Response = await fetch(downloadInfo.url);
15718
+ if (!s3Response.ok) {
15719
+ throw new Error(`S3 download failed: ${s3Response.status}`);
15720
+ }
15721
+ const arrayBuffer = await s3Response.arrayBuffer();
15689
15722
  const tarBuffer = Buffer.from(arrayBuffer);
15690
15723
  console.log(chalk11.green(`\u2713 Downloaded ${formatBytes4(tarBuffer.length)}`));
15691
- const tmpDir = fs8.mkdtempSync(path11.join(os6.tmpdir(), "vm0-"));
15692
- const tarPath = path11.join(tmpDir, "artifact.tar.gz");
15693
- await fs8.promises.writeFile(tarPath, tarBuffer);
15724
+ const tmpDir = fs7.mkdtempSync(path10.join(os5.tmpdir(), "vm0-"));
15725
+ const tarPath = path10.join(tmpDir, "artifact.tar.gz");
15726
+ await fs7.promises.writeFile(tarPath, tarBuffer);
15694
15727
  console.log(chalk11.gray("Syncing local files..."));
15695
15728
  const remoteFiles = await listTarFiles(tarPath);
15696
15729
  const remoteFilesSet = new Set(
@@ -15703,13 +15736,13 @@ var pullCommand2 = new Command9().name("pull").description("Pull cloud artifact
15703
15736
  );
15704
15737
  }
15705
15738
  console.log(chalk11.gray("Extracting files..."));
15706
- await tar6.extract({
15739
+ await tar4.extract({
15707
15740
  file: tarPath,
15708
15741
  cwd,
15709
15742
  gzip: true
15710
15743
  });
15711
- await fs8.promises.unlink(tarPath);
15712
- await fs8.promises.rmdir(tmpDir);
15744
+ await fs7.promises.unlink(tarPath);
15745
+ await fs7.promises.rmdir(tmpDir);
15713
15746
  console.log(chalk11.green(`\u2713 Extracted ${remoteFiles.length} files`));
15714
15747
  } catch (error43) {
15715
15748
  console.error(chalk11.red("\u2717 Pull failed"));
@@ -15727,8 +15760,8 @@ var artifactCommand = new Command10().name("artifact").description("Manage cloud
15727
15760
  import { Command as Command11 } from "commander";
15728
15761
  import chalk13 from "chalk";
15729
15762
  import { readFile as readFile5, mkdir as mkdir5, writeFile as writeFile5, appendFile } from "fs/promises";
15730
- import { existsSync as existsSync5, readFileSync } from "fs";
15731
- import path12 from "path";
15763
+ import { existsSync as existsSync6, readFileSync } from "fs";
15764
+ import path11 from "path";
15732
15765
  import { spawn as spawn2 } from "child_process";
15733
15766
  import { parse as parseYaml3 } from "yaml";
15734
15767
  import { config as dotenvConfig2 } from "dotenv";
@@ -15740,6 +15773,13 @@ import chalk12 from "chalk";
15740
15773
  var PACKAGE_NAME = "@vm0/cli";
15741
15774
  var NPM_REGISTRY_URL = `https://registry.npmjs.org/${encodeURIComponent(PACKAGE_NAME)}/latest`;
15742
15775
  var TIMEOUT_MS = 5e3;
15776
+ function detectPackageManager() {
15777
+ const execPath = process.argv[1] ?? "";
15778
+ if (execPath.includes("pnpm")) {
15779
+ return "pnpm";
15780
+ }
15781
+ return "npm";
15782
+ }
15743
15783
  function escapeForShell(str) {
15744
15784
  return `"${str.replace(/"/g, '\\"')}"`;
15745
15785
  }
@@ -15774,12 +15814,14 @@ function getLatestVersion() {
15774
15814
  });
15775
15815
  });
15776
15816
  }
15777
- function performUpgrade() {
15817
+ function performUpgrade(packageManager) {
15778
15818
  return new Promise((resolve2) => {
15779
- const npm = process.platform === "win32" ? "npm.cmd" : "npm";
15780
- const child = spawn(npm, ["install", "-g", `${PACKAGE_NAME}@latest`], {
15819
+ const isWindows = process.platform === "win32";
15820
+ const command = isWindows ? `${packageManager}.cmd` : packageManager;
15821
+ const args = packageManager === "pnpm" ? ["add", "-g", `${PACKAGE_NAME}@latest`] : ["install", "-g", `${PACKAGE_NAME}@latest`];
15822
+ const child = spawn(command, args, {
15781
15823
  stdio: "inherit",
15782
- shell: process.platform === "win32"
15824
+ shell: isWindows
15783
15825
  });
15784
15826
  child.on("close", (code) => {
15785
15827
  resolve2(code === 0);
@@ -15811,8 +15853,9 @@ async function checkAndUpgrade(currentVersion, prompt) {
15811
15853
  )
15812
15854
  );
15813
15855
  console.log();
15814
- console.log("Upgrading...");
15815
- const success2 = await performUpgrade();
15856
+ const packageManager = detectPackageManager();
15857
+ console.log(`Upgrading via ${packageManager}...`);
15858
+ const success2 = await performUpgrade(packageManager);
15816
15859
  if (success2) {
15817
15860
  console.log(chalk12.green(`Upgraded to ${latestVersion}`));
15818
15861
  console.log();
@@ -15823,6 +15866,8 @@ async function checkAndUpgrade(currentVersion, prompt) {
15823
15866
  console.log();
15824
15867
  console.log(chalk12.red("Upgrade failed. Please run manually:"));
15825
15868
  console.log(chalk12.cyan(` npm install -g ${PACKAGE_NAME}@latest`));
15869
+ console.log(chalk12.gray(" # or"));
15870
+ console.log(chalk12.cyan(` pnpm add -g ${PACKAGE_NAME}@latest`));
15826
15871
  console.log();
15827
15872
  console.log("Then re-run:");
15828
15873
  console.log(chalk12.cyan(` ${buildRerunCommand(prompt)}`));
@@ -15916,7 +15961,7 @@ function extractRequiredVarNames(config2) {
15916
15961
  }
15917
15962
  function checkMissingVariables(varNames, envFilePath) {
15918
15963
  let dotenvValues = {};
15919
- if (existsSync5(envFilePath)) {
15964
+ if (existsSync6(envFilePath)) {
15920
15965
  const result = dotenvConfig2({ path: envFilePath });
15921
15966
  if (result.parsed) {
15922
15967
  dotenvValues = result.parsed;
@@ -15934,7 +15979,7 @@ function checkMissingVariables(varNames, envFilePath) {
15934
15979
  }
15935
15980
  async function generateEnvPlaceholders(missingVars, envFilePath) {
15936
15981
  const placeholders = missingVars.map((name) => `${name}=`).join("\n");
15937
- if (existsSync5(envFilePath)) {
15982
+ if (existsSync6(envFilePath)) {
15938
15983
  const existingContent = readFileSync(envFilePath, "utf8");
15939
15984
  const needsNewline = existingContent.length > 0 && !existingContent.endsWith("\n");
15940
15985
  const prefix = needsNewline ? "\n" : "";
@@ -15946,13 +15991,13 @@ async function generateEnvPlaceholders(missingVars, envFilePath) {
15946
15991
  }
15947
15992
  }
15948
15993
  var cookCommand = new Command11().name("cook").description("One-click agent preparation and execution from vm0.yaml").argument("[prompt]", "Prompt for the agent").action(async (prompt) => {
15949
- const shouldExit = await checkAndUpgrade("4.11.0", prompt);
15994
+ const shouldExit = await checkAndUpgrade("4.13.0", prompt);
15950
15995
  if (shouldExit) {
15951
15996
  process.exit(0);
15952
15997
  }
15953
15998
  const cwd = process.cwd();
15954
15999
  console.log(chalk13.blue(`Reading config: ${CONFIG_FILE3}`));
15955
- if (!existsSync5(CONFIG_FILE3)) {
16000
+ if (!existsSync6(CONFIG_FILE3)) {
15956
16001
  console.error(chalk13.red(`\u2717 Config file not found: ${CONFIG_FILE3}`));
15957
16002
  process.exit(1);
15958
16003
  }
@@ -15980,7 +16025,7 @@ var cookCommand = new Command11().name("cook").description("One-click agent prep
15980
16025
  );
15981
16026
  const requiredVarNames = extractRequiredVarNames(config2);
15982
16027
  if (requiredVarNames.length > 0) {
15983
- const envFilePath = path12.join(cwd, ".env");
16028
+ const envFilePath = path11.join(cwd, ".env");
15984
16029
  const missingVars = checkMissingVariables(requiredVarNames, envFilePath);
15985
16030
  if (missingVars.length > 0) {
15986
16031
  await generateEnvPlaceholders(missingVars, envFilePath);
@@ -16000,9 +16045,9 @@ var cookCommand = new Command11().name("cook").description("One-click agent prep
16000
16045
  console.log();
16001
16046
  console.log(chalk13.blue("Processing volumes..."));
16002
16047
  for (const volumeConfig of Object.values(config2.volumes)) {
16003
- const volumeDir = path12.join(cwd, volumeConfig.name);
16048
+ const volumeDir = path11.join(cwd, volumeConfig.name);
16004
16049
  console.log(chalk13.gray(` ${volumeConfig.name}/`));
16005
- if (!existsSync5(volumeDir)) {
16050
+ if (!existsSync6(volumeDir)) {
16006
16051
  console.error(
16007
16052
  chalk13.red(
16008
16053
  ` \u2717 Directory not found. Create the directory and add files first.`
@@ -16035,10 +16080,10 @@ var cookCommand = new Command11().name("cook").description("One-click agent prep
16035
16080
  }
16036
16081
  console.log();
16037
16082
  console.log(chalk13.blue("Processing artifact..."));
16038
- const artifactDir = path12.join(cwd, ARTIFACT_DIR);
16083
+ const artifactDir = path11.join(cwd, ARTIFACT_DIR);
16039
16084
  console.log(chalk13.gray(` ${ARTIFACT_DIR}/`));
16040
16085
  try {
16041
- if (!existsSync5(artifactDir)) {
16086
+ if (!existsSync6(artifactDir)) {
16042
16087
  await mkdir5(artifactDir, { recursive: true });
16043
16088
  console.log(chalk13.green(` \u2713 Created directory`));
16044
16089
  }
@@ -16132,12 +16177,12 @@ import { Command as Command15 } from "commander";
16132
16177
  import { Command as Command12 } from "commander";
16133
16178
  import chalk14 from "chalk";
16134
16179
  import { readFile as readFile6 } from "fs/promises";
16135
- import { existsSync as existsSync6 } from "fs";
16136
- var sleep = (ms) => new Promise((resolve2) => setTimeout(resolve2, ms));
16180
+ import { existsSync as existsSync7 } from "fs";
16181
+ var sleep2 = (ms) => new Promise((resolve2) => setTimeout(resolve2, ms));
16137
16182
  var buildCommand = new Command12().name("build").description("Build a custom image from a Dockerfile").requiredOption("-f, --file <path>", "Path to Dockerfile").requiredOption("-n, --name <name>", "Name for the image").option("--delete-existing", "Delete existing image before building").action(
16138
16183
  async (options) => {
16139
16184
  const { file: file2, name, deleteExisting } = options;
16140
- if (!existsSync6(file2)) {
16185
+ if (!existsSync7(file2)) {
16141
16186
  console.error(chalk14.red(`\u2717 Dockerfile not found: ${file2}`));
16142
16187
  process.exit(1);
16143
16188
  }
@@ -16190,7 +16235,7 @@ var buildCommand = new Command12().name("build").description("Build a custom ima
16190
16235
  logsOffset = statusData.logsOffset;
16191
16236
  status = statusData.status;
16192
16237
  if (status === "building") {
16193
- await sleep(2e3);
16238
+ await sleep2(2e3);
16194
16239
  }
16195
16240
  }
16196
16241
  console.log();
@@ -16583,7 +16628,7 @@ function handleError(error43, runId) {
16583
16628
 
16584
16629
  // src/index.ts
16585
16630
  var program = new Command17();
16586
- program.name("vm0").description("VM0 CLI - A modern build tool").version("4.11.0");
16631
+ program.name("vm0").description("VM0 CLI - A modern build tool").version("4.13.0");
16587
16632
  program.command("info").description("Display environment information").action(async () => {
16588
16633
  console.log(chalk18.cyan("System Information:"));
16589
16634
  console.log(`Node Version: ${process.version}`);
@@ -16602,6 +16647,9 @@ authCommand.command("logout").description("Log out of VM0").action(async () => {
16602
16647
  authCommand.command("status").description("Show current authentication status").action(async () => {
16603
16648
  await checkAuthStatus();
16604
16649
  });
16650
+ authCommand.command("setup-token").description("Output auth token for CI/CD environments").action(async () => {
16651
+ await setupToken();
16652
+ });
16605
16653
  program.addCommand(composeCommand);
16606
16654
  program.addCommand(runCommand);
16607
16655
  program.addCommand(volumeCommand);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vm0/cli",
3
- "version": "4.11.0",
3
+ "version": "4.13.0",
4
4
  "description": "CLI application",
5
5
  "repository": {
6
6
  "type": "git",