@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.
- package/index.js +446 -398
- 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
|
|
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(
|
|
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}${
|
|
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(
|
|
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}${
|
|
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(
|
|
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}${
|
|
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
|
|
698
|
-
import * as
|
|
699
|
-
import * as
|
|
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 =
|
|
779
|
-
const content = await
|
|
780
|
-
const tmpDir = await
|
|
781
|
-
const promptDir =
|
|
782
|
-
await
|
|
783
|
-
await
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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 (!
|
|
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
|
|
992
|
-
import * as
|
|
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,
|
|
2077
|
-
if (!
|
|
2328
|
+
function getElementAtPath(obj, path12) {
|
|
2329
|
+
if (!path12)
|
|
2078
2330
|
return obj;
|
|
2079
|
-
return
|
|
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
|
|
2084
|
-
return Promise.all(
|
|
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(
|
|
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(
|
|
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,
|
|
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 = [...
|
|
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
|
|
2653
|
-
for (const seg of
|
|
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 =
|
|
14776
|
+
const envFilePath = path5.resolve(process.cwd(), ".env");
|
|
14525
14777
|
let dotenvValues = {};
|
|
14526
|
-
if (
|
|
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
|
|
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
|
|
15265
|
+
import { existsSync as existsSync5 } from "fs";
|
|
15014
15266
|
import { parse as parseYaml2, stringify as stringifyYaml } from "yaml";
|
|
15015
|
-
import
|
|
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 =
|
|
15027
|
-
const legacyConfigPath =
|
|
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 (
|
|
15281
|
+
if (existsSync5(configPath)) {
|
|
15030
15282
|
actualPath = configPath;
|
|
15031
|
-
} else if (
|
|
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 =
|
|
15046
|
-
const configPath =
|
|
15047
|
-
if (!
|
|
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 =
|
|
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: ${
|
|
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 ${
|
|
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
|
-
|
|
15228
|
-
|
|
15229
|
-
|
|
15230
|
-
|
|
15231
|
-
|
|
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.
|
|
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
|
|
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("
|
|
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
|
-
|
|
15470
|
+
const downloadInfo = await response.json();
|
|
15471
|
+
if (downloadInfo.empty) {
|
|
15378
15472
|
await handleEmptyStorageResponse(cwd);
|
|
15379
15473
|
return;
|
|
15380
15474
|
}
|
|
15381
|
-
|
|
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
|
|
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
|
-
|
|
15539
|
-
|
|
15540
|
-
|
|
15541
|
-
|
|
15542
|
-
|
|
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.
|
|
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
|
|
15624
|
-
import * as
|
|
15625
|
-
import * as
|
|
15626
|
-
import * as
|
|
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("
|
|
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
|
-
|
|
15708
|
+
const downloadInfo = await response.json();
|
|
15709
|
+
if (downloadInfo.empty) {
|
|
15685
15710
|
await handleEmptyStorageResponse(cwd);
|
|
15686
15711
|
return;
|
|
15687
15712
|
}
|
|
15688
|
-
|
|
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 =
|
|
15692
|
-
const tarPath =
|
|
15693
|
-
await
|
|
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
|
|
15739
|
+
await tar4.extract({
|
|
15707
15740
|
file: tarPath,
|
|
15708
15741
|
cwd,
|
|
15709
15742
|
gzip: true
|
|
15710
15743
|
});
|
|
15711
|
-
await
|
|
15712
|
-
await
|
|
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
|
|
15731
|
-
import
|
|
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
|
|
15780
|
-
const
|
|
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:
|
|
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
|
-
|
|
15815
|
-
|
|
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 (
|
|
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 (
|
|
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.
|
|
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 (!
|
|
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 =
|
|
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 =
|
|
16048
|
+
const volumeDir = path11.join(cwd, volumeConfig.name);
|
|
16004
16049
|
console.log(chalk13.gray(` ${volumeConfig.name}/`));
|
|
16005
|
-
if (!
|
|
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 =
|
|
16083
|
+
const artifactDir = path11.join(cwd, ARTIFACT_DIR);
|
|
16039
16084
|
console.log(chalk13.gray(` ${ARTIFACT_DIR}/`));
|
|
16040
16085
|
try {
|
|
16041
|
-
if (!
|
|
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
|
|
16136
|
-
var
|
|
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 (!
|
|
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
|
|
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.
|
|
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);
|