@vm0/cli 4.14.1 → 4.16.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 +482 -99
  2. package/package.json +1 -1
package/index.js CHANGED
@@ -6,8 +6,8 @@ var __export = (target, all) => {
6
6
  };
7
7
 
8
8
  // src/index.ts
9
- import { Command as Command19 } from "commander";
10
- import chalk20 from "chalk";
9
+ import { Command as Command23 } from "commander";
10
+ import chalk23 from "chalk";
11
11
 
12
12
  // src/lib/auth.ts
13
13
  import chalk from "chalk";
@@ -451,6 +451,56 @@ var ApiClient = class {
451
451
  }
452
452
  return await response.json();
453
453
  }
454
+ /**
455
+ * Get current user's scope
456
+ */
457
+ async getScope() {
458
+ const baseUrl = await this.getBaseUrl();
459
+ const headers = await this.getHeaders();
460
+ const response = await fetch(`${baseUrl}/api/scope`, {
461
+ method: "GET",
462
+ headers
463
+ });
464
+ if (!response.ok) {
465
+ const error43 = await response.json();
466
+ throw new Error(error43.error?.message || "Failed to get scope");
467
+ }
468
+ return await response.json();
469
+ }
470
+ /**
471
+ * Create user's scope
472
+ */
473
+ async createScope(body) {
474
+ const baseUrl = await this.getBaseUrl();
475
+ const headers = await this.getHeaders();
476
+ const response = await fetch(`${baseUrl}/api/scope`, {
477
+ method: "POST",
478
+ headers,
479
+ body: JSON.stringify(body)
480
+ });
481
+ if (!response.ok) {
482
+ const error43 = await response.json();
483
+ throw new Error(error43.error?.message || "Failed to create scope");
484
+ }
485
+ return await response.json();
486
+ }
487
+ /**
488
+ * Update user's scope slug
489
+ */
490
+ async updateScope(body) {
491
+ const baseUrl = await this.getBaseUrl();
492
+ const headers = await this.getHeaders();
493
+ const response = await fetch(`${baseUrl}/api/scope`, {
494
+ method: "PUT",
495
+ headers,
496
+ body: JSON.stringify(body)
497
+ });
498
+ if (!response.ok) {
499
+ const error43 = await response.json();
500
+ throw new Error(error43.error?.message || "Failed to update scope");
501
+ }
502
+ return await response.json();
503
+ }
454
504
  /**
455
505
  * Generic GET request
456
506
  */
@@ -4119,7 +4169,7 @@ var $ZodBase64 = /* @__PURE__ */ $constructor("$ZodBase64", (inst, def) => {
4119
4169
  function isValidBase64URL(data) {
4120
4170
  if (!base64url.test(data))
4121
4171
  return false;
4122
- const base643 = data.replace(/[-_]/g, (c11) => c11 === "-" ? "+" : "/");
4172
+ const base643 = data.replace(/[-_]/g, (c12) => c12 === "-" ? "+" : "/");
4123
4173
  const padded = base643.padEnd(Math.ceil(base643.length / 4) * 4, "=");
4124
4174
  return isValidBase64(padded);
4125
4175
  }
@@ -13031,9 +13081,9 @@ var ZodDate = /* @__PURE__ */ $constructor("ZodDate", (inst, def) => {
13031
13081
  ZodType.init(inst, def);
13032
13082
  inst.min = (value, params) => inst.check(_gte(value, params));
13033
13083
  inst.max = (value, params) => inst.check(_lte(value, params));
13034
- const c11 = inst._zod.bag;
13035
- inst.minDate = c11.minimum ? new Date(c11.minimum) : null;
13036
- inst.maxDate = c11.maximum ? new Date(c11.maximum) : null;
13084
+ const c12 = inst._zod.bag;
13085
+ inst.minDate = c12.minimum ? new Date(c12.minimum) : null;
13086
+ inst.maxDate = c12.maximum ? new Date(c12.maximum) : null;
13037
13087
  });
13038
13088
  function date3(params) {
13039
13089
  return _date(ZodDate, params);
@@ -13905,7 +13955,8 @@ var runResultSchema = external_exports.object({
13905
13955
  checkpointId: external_exports.string(),
13906
13956
  agentSessionId: external_exports.string(),
13907
13957
  conversationId: external_exports.string(),
13908
- artifact: external_exports.record(external_exports.string(), external_exports.string()),
13958
+ artifact: external_exports.record(external_exports.string(), external_exports.string()).optional(),
13959
+ // optional when run has no artifact
13909
13960
  volumes: external_exports.record(external_exports.string(), external_exports.string()).optional()
13910
13961
  });
13911
13962
  var runStateSchema = external_exports.object({
@@ -14136,7 +14187,8 @@ var agentSessionSchema = external_exports.object({
14136
14187
  userId: external_exports.string(),
14137
14188
  agentComposeId: external_exports.string(),
14138
14189
  conversationId: external_exports.string().nullable(),
14139
- artifactName: external_exports.string(),
14190
+ artifactName: external_exports.string().nullable(),
14191
+ // nullable when session has no artifact
14140
14192
  vars: external_exports.record(external_exports.string(), external_exports.string()).nullable(),
14141
14193
  createdAt: external_exports.string(),
14142
14194
  updatedAt: external_exports.string()
@@ -14356,7 +14408,7 @@ var webhookCheckpointsContract = c6.router({
14356
14408
  cliAgentType: external_exports.string().min(1, "cliAgentType is required"),
14357
14409
  cliAgentSessionId: external_exports.string().min(1, "cliAgentSessionId is required"),
14358
14410
  cliAgentSessionHistory: external_exports.string().min(1, "cliAgentSessionHistory is required"),
14359
- artifactSnapshot: artifactSnapshotSchema,
14411
+ artifactSnapshot: artifactSnapshotSchema.optional(),
14360
14412
  volumeVersionsSnapshot: volumeVersionsSnapshotSchema.optional()
14361
14413
  }),
14362
14414
  responses: {
@@ -14364,7 +14416,7 @@ var webhookCheckpointsContract = c6.router({
14364
14416
  checkpointId: external_exports.string(),
14365
14417
  agentSessionId: external_exports.string(),
14366
14418
  conversationId: external_exports.string(),
14367
- artifact: artifactSnapshotSchema,
14419
+ artifact: artifactSnapshotSchema.optional(),
14368
14420
  volumes: external_exports.record(external_exports.string(), external_exports.string()).optional()
14369
14421
  }),
14370
14422
  400: apiErrorSchema,
@@ -14600,6 +14652,8 @@ var buildStatusSchema = external_exports.enum(["building", "ready", "error"]);
14600
14652
  var imageInfoSchema = external_exports.object({
14601
14653
  id: external_exports.string(),
14602
14654
  alias: external_exports.string(),
14655
+ versionId: external_exports.string().nullable(),
14656
+ // null for legacy images without versioning
14603
14657
  status: external_exports.string(),
14604
14658
  errorMessage: external_exports.string().nullable(),
14605
14659
  createdAt: external_exports.coerce.date(),
@@ -14619,7 +14673,9 @@ var createImageRequestSchema = external_exports.object({
14619
14673
  var createImageResponseSchema = external_exports.object({
14620
14674
  buildId: external_exports.string(),
14621
14675
  imageId: external_exports.string(),
14622
- alias: external_exports.string()
14676
+ alias: external_exports.string(),
14677
+ versionId: external_exports.string()
14678
+ // nanoid(8), unique per build
14623
14679
  });
14624
14680
  var buildStatusResponseSchema = external_exports.object({
14625
14681
  status: buildStatusSchema,
@@ -14755,6 +14811,92 @@ var proxyErrorSchema = external_exports.object({
14755
14811
  })
14756
14812
  });
14757
14813
 
14814
+ // ../../packages/core/src/contracts/scopes.ts
14815
+ var c11 = initContract();
14816
+ var scopeTypeSchema = external_exports.enum(["personal", "organization", "system"]);
14817
+ var scopeSlugSchema = external_exports.string().min(3, "Scope slug must be at least 3 characters").max(64, "Scope slug must be at most 64 characters").regex(
14818
+ /^[a-z0-9][a-z0-9-]*[a-z0-9]$|^[a-z0-9]{1,2}$/,
14819
+ "Scope slug must contain only lowercase letters, numbers, and hyphens, and must start and end with an alphanumeric character"
14820
+ ).refine(
14821
+ (slug) => !slug.startsWith("vm0"),
14822
+ "Scope slug cannot start with 'vm0' (reserved)"
14823
+ ).transform((s) => s.toLowerCase());
14824
+ var scopeResponseSchema = external_exports.object({
14825
+ id: external_exports.string().uuid(),
14826
+ slug: external_exports.string(),
14827
+ type: scopeTypeSchema,
14828
+ displayName: external_exports.string().nullable(),
14829
+ createdAt: external_exports.string(),
14830
+ updatedAt: external_exports.string()
14831
+ });
14832
+ var createScopeRequestSchema = external_exports.object({
14833
+ slug: scopeSlugSchema,
14834
+ displayName: external_exports.string().max(128).optional()
14835
+ });
14836
+ var updateScopeRequestSchema = external_exports.object({
14837
+ slug: scopeSlugSchema,
14838
+ force: external_exports.boolean().optional().default(false)
14839
+ });
14840
+ var scopeContract = c11.router({
14841
+ /**
14842
+ * GET /api/scope
14843
+ * Get current user's scope
14844
+ */
14845
+ get: {
14846
+ method: "GET",
14847
+ path: "/api/scope",
14848
+ responses: {
14849
+ 200: scopeResponseSchema,
14850
+ 401: apiErrorSchema,
14851
+ 404: apiErrorSchema,
14852
+ 500: apiErrorSchema
14853
+ },
14854
+ summary: "Get current user's scope"
14855
+ },
14856
+ /**
14857
+ * POST /api/scope
14858
+ * Create user's scope
14859
+ */
14860
+ create: {
14861
+ method: "POST",
14862
+ path: "/api/scope",
14863
+ body: createScopeRequestSchema,
14864
+ responses: {
14865
+ 201: scopeResponseSchema,
14866
+ 400: apiErrorSchema,
14867
+ 401: apiErrorSchema,
14868
+ 409: apiErrorSchema,
14869
+ 500: apiErrorSchema
14870
+ },
14871
+ summary: "Create user's scope"
14872
+ },
14873
+ /**
14874
+ * PUT /api/scope
14875
+ * Update user's scope slug
14876
+ */
14877
+ update: {
14878
+ method: "PUT",
14879
+ path: "/api/scope",
14880
+ body: updateScopeRequestSchema,
14881
+ responses: {
14882
+ 200: scopeResponseSchema,
14883
+ 400: apiErrorSchema,
14884
+ 401: apiErrorSchema,
14885
+ 403: apiErrorSchema,
14886
+ 404: apiErrorSchema,
14887
+ 409: apiErrorSchema,
14888
+ 500: apiErrorSchema
14889
+ },
14890
+ summary: "Update user's scope slug"
14891
+ }
14892
+ });
14893
+
14894
+ // ../../packages/core/src/version-id.ts
14895
+ var VERSION_ID_DISPLAY_LENGTH = 8;
14896
+ function formatVersionIdForDisplay(versionId) {
14897
+ return versionId.slice(0, VERSION_ID_DISPLAY_LENGTH);
14898
+ }
14899
+
14758
14900
  // src/commands/run.ts
14759
14901
  function collectKeyValue(value, previous) {
14760
14902
  const [key, ...valueParts] = value.split("=");
@@ -14943,15 +15085,6 @@ var runCmd = new Command2().name("run").description("Execute an agent").argument
14943
15085
  ).option("-v, --verbose", "Show verbose output with timing information").action(
14944
15086
  async (identifier, prompt, options) => {
14945
15087
  const startTimestamp = /* @__PURE__ */ new Date();
14946
- if (!options.artifactName) {
14947
- console.error(
14948
- chalk4.red("\u2717 Missing required option: --artifact-name <name>")
14949
- );
14950
- console.error(
14951
- chalk4.gray(" The artifact-name is required for new agent runs.")
14952
- );
14953
- process.exit(1);
14954
- }
14955
15088
  const verbose = options.verbose;
14956
15089
  try {
14957
15090
  const { name, version: version2 } = parseIdentifier(identifier);
@@ -16131,7 +16264,7 @@ async function generateEnvPlaceholders(missingVars, envFilePath) {
16131
16264
  }
16132
16265
  }
16133
16266
  var cookCommand = new Command13().name("cook").description("One-click agent preparation and execution from vm0.yaml").argument("[prompt]", "Prompt for the agent").action(async (prompt) => {
16134
- const shouldExit = await checkAndUpgrade("4.14.1", prompt);
16267
+ const shouldExit = await checkAndUpgrade("4.16.0", prompt);
16135
16268
  if (shouldExit) {
16136
16269
  process.exit(0);
16137
16270
  }
@@ -16311,7 +16444,7 @@ var cookCommand = new Command13().name("cook").description("One-click agent prep
16311
16444
  });
16312
16445
 
16313
16446
  // src/commands/image/index.ts
16314
- import { Command as Command17 } from "commander";
16447
+ import { Command as Command18 } from "commander";
16315
16448
 
16316
16449
  // src/commands/image/build.ts
16317
16450
  import { Command as Command14 } from "commander";
@@ -16344,16 +16477,16 @@ var buildCommand = new Command14().name("build").description("Build a custom ima
16344
16477
  process.exit(1);
16345
16478
  }
16346
16479
  try {
16480
+ const scope = await apiClient.getScope();
16347
16481
  const dockerfile = await readFile6(file2, "utf8");
16348
- console.log(chalk16.blue(`Building image: ${name}`));
16349
- console.log(chalk16.gray(` Dockerfile: ${file2}`));
16482
+ console.log(chalk16.blue(`Building image: @${scope.slug}/${name}`));
16350
16483
  console.log();
16351
16484
  const buildInfo = await apiClient.createImage({
16352
16485
  dockerfile,
16353
16486
  alias: name,
16354
16487
  deleteExisting
16355
16488
  });
16356
- const { imageId, buildId } = buildInfo;
16489
+ const { imageId, buildId, versionId } = buildInfo;
16357
16490
  console.log(chalk16.gray(` Build ID: ${buildId}`));
16358
16491
  console.log();
16359
16492
  let logsOffset = 0;
@@ -16380,12 +16513,12 @@ var buildCommand = new Command14().name("build").description("Build a custom ima
16380
16513
  }
16381
16514
  console.log();
16382
16515
  if (status === "ready") {
16383
- console.log(chalk16.green(`\u2713 Image built: ${name}`));
16384
- console.log();
16385
- console.log("Use in vm0.yaml:");
16386
- console.log(chalk16.cyan(` agents:`));
16387
- console.log(chalk16.cyan(` your-agent:`));
16388
- console.log(chalk16.cyan(` image: "${name}"`));
16516
+ const shortVersion = formatVersionIdForDisplay(versionId);
16517
+ console.log(
16518
+ chalk16.green(
16519
+ `\u2713 Image built: @${scope.slug}/${name}:${shortVersion}`
16520
+ )
16521
+ );
16389
16522
  } else {
16390
16523
  console.error(chalk16.red(`\u2717 Build failed`));
16391
16524
  process.exit(1);
@@ -16432,24 +16565,43 @@ var listCommand = new Command15().name("list").alias("ls").description("List you
16432
16565
  }
16433
16566
  console.log(chalk17.bold("Your images:"));
16434
16567
  console.log();
16568
+ const imagesByAlias = /* @__PURE__ */ new Map();
16569
+ for (const image of images) {
16570
+ const list2 = imagesByAlias.get(image.alias) || [];
16571
+ list2.push(image);
16572
+ imagesByAlias.set(image.alias, list2);
16573
+ }
16574
+ const latestVersions = /* @__PURE__ */ new Map();
16575
+ for (const [alias, versions] of imagesByAlias) {
16576
+ const latestReady = versions.find((v) => v.status === "ready");
16577
+ latestVersions.set(alias, latestReady?.versionId || null);
16578
+ }
16435
16579
  console.log(
16436
16580
  chalk17.gray(
16437
- `${"NAME".padEnd(30)} ${"STATUS".padEnd(12)} ${"CREATED".padEnd(20)}`
16581
+ `${"NAME".padEnd(40)} ${"STATUS".padEnd(12)} ${"CREATED".padEnd(20)}`
16438
16582
  )
16439
16583
  );
16440
- console.log(chalk17.gray("-".repeat(62)));
16584
+ console.log(chalk17.gray("-".repeat(72)));
16441
16585
  for (const image of images) {
16442
16586
  const statusColor = image.status === "ready" ? chalk17.green : image.status === "building" ? chalk17.yellow : chalk17.red;
16443
16587
  const createdAt = new Date(image.createdAt).toLocaleString();
16588
+ let displayName = image.alias;
16589
+ if (image.versionId) {
16590
+ const shortVersion = formatVersionIdForDisplay(image.versionId);
16591
+ displayName = `${image.alias}:${shortVersion}`;
16592
+ if (image.status === "ready" && latestVersions.get(image.alias) === image.versionId) {
16593
+ displayName = `${displayName} ${chalk17.cyan("(latest)")}`;
16594
+ }
16595
+ }
16444
16596
  console.log(
16445
- `${image.alias.padEnd(30)} ${statusColor(image.status.padEnd(12))} ${createdAt.padEnd(20)}`
16597
+ `${displayName.padEnd(40)} ${statusColor(image.status.padEnd(12))} ${createdAt.padEnd(20)}`
16446
16598
  );
16447
16599
  if (image.status === "error" && image.errorMessage) {
16448
16600
  console.log(chalk17.red(` Error: ${image.errorMessage}`));
16449
16601
  }
16450
16602
  }
16451
16603
  console.log();
16452
- console.log(chalk17.gray(`Total: ${images.length} image(s)`));
16604
+ console.log(chalk17.gray(`Total: ${images.length} version(s)`));
16453
16605
  } catch (error43) {
16454
16606
  if (error43 instanceof Error) {
16455
16607
  if (error43.message.includes("Not authenticated")) {
@@ -16468,68 +16620,184 @@ var listCommand = new Command15().name("list").alias("ls").description("List you
16468
16620
  import { Command as Command16 } from "commander";
16469
16621
  import chalk18 from "chalk";
16470
16622
  import * as readline from "readline";
16471
- var deleteCommand = new Command16().name("delete").alias("rm").description("Delete a custom image").argument("<name>", "Name of the image to delete").option("-f, --force", "Skip confirmation prompt").action(async (name, options) => {
16623
+ var deleteCommand = new Command16().name("delete").alias("rm").description("Delete a custom image or specific version").argument("<name>", "Image name or name:version to delete").option("-f, --force", "Skip confirmation prompt").option("--all", "Delete all versions of the image").action(
16624
+ async (nameArg, options) => {
16625
+ try {
16626
+ const colonIndex = nameArg.lastIndexOf(":");
16627
+ const hasVersion = colonIndex > 0;
16628
+ const name = hasVersion ? nameArg.slice(0, colonIndex) : nameArg;
16629
+ const versionId = hasVersion ? nameArg.slice(colonIndex + 1) : null;
16630
+ const listResponse = await apiClient.get("/api/images");
16631
+ if (!listResponse.ok) {
16632
+ const error43 = await listResponse.json();
16633
+ throw new Error(
16634
+ error43.error?.message || "Failed to list images"
16635
+ );
16636
+ }
16637
+ const data = await listResponse.json();
16638
+ let imagesToDelete;
16639
+ if (versionId) {
16640
+ const matchingVersions = data.images.filter(
16641
+ (img) => img.alias === name && img.versionId && img.versionId.startsWith(versionId.toLowerCase())
16642
+ );
16643
+ if (matchingVersions.length === 0) {
16644
+ console.error(chalk18.red(`Image version not found: ${nameArg}`));
16645
+ process.exit(1);
16646
+ }
16647
+ if (matchingVersions.length > 1) {
16648
+ console.error(
16649
+ chalk18.red(
16650
+ `Ambiguous version prefix "${versionId}". Please use more characters.`
16651
+ )
16652
+ );
16653
+ process.exit(1);
16654
+ }
16655
+ imagesToDelete = [matchingVersions[0]];
16656
+ } else if (options.all) {
16657
+ imagesToDelete = data.images.filter((img) => img.alias === name);
16658
+ if (imagesToDelete.length === 0) {
16659
+ console.error(chalk18.red(`Image not found: ${name}`));
16660
+ process.exit(1);
16661
+ }
16662
+ } else {
16663
+ const matchingImages = data.images.filter(
16664
+ (img) => img.alias === name
16665
+ );
16666
+ if (matchingImages.length === 0) {
16667
+ console.error(chalk18.red(`Image not found: ${name}`));
16668
+ process.exit(1);
16669
+ }
16670
+ const latestReady = matchingImages.find(
16671
+ (img) => img.status === "ready"
16672
+ );
16673
+ if (latestReady) {
16674
+ imagesToDelete = [latestReady];
16675
+ } else {
16676
+ imagesToDelete = [matchingImages[0]];
16677
+ }
16678
+ }
16679
+ const firstImage = imagesToDelete[0];
16680
+ const firstVersionDisplay = firstImage.versionId ? `:${formatVersionIdForDisplay(firstImage.versionId)}` : "";
16681
+ const confirmMsg = imagesToDelete.length === 1 ? `Delete image "${firstImage.alias}${firstVersionDisplay}"?` : `Delete ${imagesToDelete.length} versions of "${name}"?`;
16682
+ if (!options.force) {
16683
+ const rl = readline.createInterface({
16684
+ input: process.stdin,
16685
+ output: process.stdout
16686
+ });
16687
+ const answer = await new Promise((resolve2) => {
16688
+ rl.question(chalk18.yellow(`${confirmMsg} [y/N] `), (answer2) => {
16689
+ rl.close();
16690
+ resolve2(answer2);
16691
+ });
16692
+ });
16693
+ if (answer.toLowerCase() !== "y" && answer.toLowerCase() !== "yes") {
16694
+ console.log(chalk18.gray("Cancelled."));
16695
+ return;
16696
+ }
16697
+ }
16698
+ for (const image of imagesToDelete) {
16699
+ const deleteResponse = await apiClient.delete(
16700
+ `/api/images/${image.id}`
16701
+ );
16702
+ if (!deleteResponse.ok) {
16703
+ const error43 = await deleteResponse.json();
16704
+ throw new Error(
16705
+ error43.error?.message || "Failed to delete image"
16706
+ );
16707
+ }
16708
+ const displayName = image.versionId ? `${image.alias}:${formatVersionIdForDisplay(image.versionId)}` : image.alias;
16709
+ console.log(chalk18.green(`Deleted image: ${displayName}`));
16710
+ }
16711
+ } catch (error43) {
16712
+ if (error43 instanceof Error) {
16713
+ if (error43.message.includes("Not authenticated")) {
16714
+ console.error(chalk18.red("Not authenticated. Run: vm0 auth login"));
16715
+ } else {
16716
+ console.error(chalk18.red(`Error: ${error43.message}`));
16717
+ }
16718
+ } else {
16719
+ console.error(chalk18.red("An unexpected error occurred"));
16720
+ }
16721
+ process.exit(1);
16722
+ }
16723
+ }
16724
+ );
16725
+
16726
+ // src/commands/image/versions.ts
16727
+ import { Command as Command17 } from "commander";
16728
+ import chalk19 from "chalk";
16729
+ var versionsCommand = new Command17().name("versions").description("List all versions of an image").argument("<name>", "Name of the image").action(async (name) => {
16472
16730
  try {
16473
- const listResponse = await apiClient.get("/api/images");
16474
- if (!listResponse.ok) {
16475
- const error43 = await listResponse.json();
16731
+ const response = await apiClient.get("/api/images");
16732
+ if (!response.ok) {
16733
+ const error43 = await response.json();
16476
16734
  throw new Error(
16477
16735
  error43.error?.message || "Failed to list images"
16478
16736
  );
16479
16737
  }
16480
- const data = await listResponse.json();
16481
- const image = data.images.find((img) => img.alias === name);
16482
- if (!image) {
16483
- console.error(chalk18.red(`Image not found: ${name}`));
16738
+ const data = await response.json();
16739
+ const versions = data.images.filter((img) => img.alias === name);
16740
+ if (versions.length === 0) {
16741
+ console.error(chalk19.red(`Image not found: ${name}`));
16484
16742
  process.exit(1);
16485
16743
  }
16486
- if (!options.force) {
16487
- const rl = readline.createInterface({
16488
- input: process.stdin,
16489
- output: process.stdout
16490
- });
16491
- const answer = await new Promise((resolve2) => {
16492
- rl.question(
16493
- chalk18.yellow(`Delete image "${name}"? [y/N] `),
16494
- (answer2) => {
16495
- rl.close();
16496
- resolve2(answer2);
16497
- }
16498
- );
16499
- });
16500
- if (answer.toLowerCase() !== "y" && answer.toLowerCase() !== "yes") {
16501
- console.log(chalk18.gray("Cancelled."));
16502
- return;
16744
+ const latestReady = versions.find((v) => v.status === "ready");
16745
+ const latestVersionId = latestReady?.versionId || null;
16746
+ console.log(chalk19.bold(`Versions of ${name}:`));
16747
+ console.log();
16748
+ console.log(
16749
+ chalk19.gray(
16750
+ `${"VERSION".padEnd(20)} ${"STATUS".padEnd(12)} ${"CREATED".padEnd(24)}`
16751
+ )
16752
+ );
16753
+ console.log(chalk19.gray("-".repeat(56)));
16754
+ for (const version2 of versions) {
16755
+ const statusColor = version2.status === "ready" ? chalk19.green : version2.status === "building" ? chalk19.yellow : chalk19.red;
16756
+ const createdAt = new Date(version2.createdAt).toLocaleString();
16757
+ let versionDisplay = version2.versionId ? formatVersionIdForDisplay(version2.versionId) : "(legacy)";
16758
+ if (version2.status === "ready" && version2.versionId === latestVersionId) {
16759
+ versionDisplay = `${versionDisplay} ${chalk19.cyan("(latest)")}`;
16760
+ }
16761
+ console.log(
16762
+ `${versionDisplay.padEnd(20)} ${statusColor(version2.status.padEnd(12))} ${createdAt.padEnd(24)}`
16763
+ );
16764
+ if (version2.status === "error" && version2.errorMessage) {
16765
+ console.log(chalk19.red(` Error: ${version2.errorMessage}`));
16503
16766
  }
16504
16767
  }
16505
- const deleteResponse = await apiClient.delete(`/api/images/${image.id}`);
16506
- if (!deleteResponse.ok) {
16507
- const error43 = await deleteResponse.json();
16508
- throw new Error(
16509
- error43.error?.message || "Failed to delete image"
16768
+ console.log();
16769
+ console.log(chalk19.gray(`Total: ${versions.length} version(s)`));
16770
+ console.log();
16771
+ console.log(chalk19.gray("Usage:"));
16772
+ console.log(chalk19.gray(` image: "${name}" # uses latest`));
16773
+ if (latestVersionId) {
16774
+ const shortVersion = formatVersionIdForDisplay(latestVersionId);
16775
+ console.log(
16776
+ chalk19.gray(
16777
+ ` image: "${name}:${shortVersion}" # pin to specific version`
16778
+ )
16510
16779
  );
16511
16780
  }
16512
- console.log(chalk18.green(`Deleted image: ${name}`));
16513
16781
  } catch (error43) {
16514
16782
  if (error43 instanceof Error) {
16515
16783
  if (error43.message.includes("Not authenticated")) {
16516
- console.error(chalk18.red("Not authenticated. Run: vm0 auth login"));
16784
+ console.error(chalk19.red("Not authenticated. Run: vm0 auth login"));
16517
16785
  } else {
16518
- console.error(chalk18.red(`Error: ${error43.message}`));
16786
+ console.error(chalk19.red(`Error: ${error43.message}`));
16519
16787
  }
16520
16788
  } else {
16521
- console.error(chalk18.red("An unexpected error occurred"));
16789
+ console.error(chalk19.red("An unexpected error occurred"));
16522
16790
  }
16523
16791
  process.exit(1);
16524
16792
  }
16525
16793
  });
16526
16794
 
16527
16795
  // src/commands/image/index.ts
16528
- var imageCommand = new Command17().name("image").description("Manage custom images").addCommand(buildCommand).addCommand(listCommand).addCommand(deleteCommand);
16796
+ var imageCommand = new Command18().name("image").description("Manage custom images").addCommand(buildCommand).addCommand(listCommand).addCommand(deleteCommand).addCommand(versionsCommand);
16529
16797
 
16530
16798
  // src/commands/logs/index.ts
16531
- import { Command as Command18 } from "commander";
16532
- import chalk19 from "chalk";
16799
+ import { Command as Command19 } from "commander";
16800
+ import chalk20 from "chalk";
16533
16801
 
16534
16802
  // src/lib/time-parser.ts
16535
16803
  function parseTime(timeStr) {
@@ -16591,23 +16859,23 @@ function formatMetric(metric) {
16591
16859
  function formatNetworkLog(entry) {
16592
16860
  let statusColor;
16593
16861
  if (entry.status >= 200 && entry.status < 300) {
16594
- statusColor = chalk19.green;
16862
+ statusColor = chalk20.green;
16595
16863
  } else if (entry.status >= 300 && entry.status < 400) {
16596
- statusColor = chalk19.yellow;
16864
+ statusColor = chalk20.yellow;
16597
16865
  } else if (entry.status >= 400) {
16598
- statusColor = chalk19.red;
16866
+ statusColor = chalk20.red;
16599
16867
  } else {
16600
- statusColor = chalk19.gray;
16868
+ statusColor = chalk20.gray;
16601
16869
  }
16602
16870
  let latencyColor;
16603
16871
  if (entry.latency_ms < 500) {
16604
- latencyColor = chalk19.green;
16872
+ latencyColor = chalk20.green;
16605
16873
  } else if (entry.latency_ms < 2e3) {
16606
- latencyColor = chalk19.yellow;
16874
+ latencyColor = chalk20.yellow;
16607
16875
  } else {
16608
- latencyColor = chalk19.red;
16876
+ latencyColor = chalk20.red;
16609
16877
  }
16610
- return `[${entry.timestamp}] ${chalk19.cyan(entry.method.padEnd(6))} ${statusColor(entry.status)} ${latencyColor(entry.latency_ms + "ms")} ${formatBytes7(entry.request_size)}/${formatBytes7(entry.response_size)} ${chalk19.gray(entry.url)}`;
16878
+ return `[${entry.timestamp}] ${chalk20.cyan(entry.method.padEnd(6))} ${statusColor(entry.status)} ${latencyColor(entry.latency_ms + "ms")} ${formatBytes7(entry.request_size)}/${formatBytes7(entry.response_size)} ${chalk20.gray(entry.url)}`;
16611
16879
  }
16612
16880
  function renderAgentEvent(event) {
16613
16881
  const parsed = ClaudeEventParser.parse(
@@ -16627,7 +16895,7 @@ function getLogType(options) {
16627
16895
  ].filter(Boolean).length;
16628
16896
  if (selected > 1) {
16629
16897
  console.error(
16630
- chalk19.red(
16898
+ chalk20.red(
16631
16899
  "Options --agent, --system, --metrics, and --network are mutually exclusive"
16632
16900
  )
16633
16901
  );
@@ -16638,7 +16906,7 @@ function getLogType(options) {
16638
16906
  if (options.network) return "network";
16639
16907
  return "agent";
16640
16908
  }
16641
- var logsCommand = new Command18().name("logs").description("View logs for an agent run").argument("<runId>", "Run ID to fetch logs for").option("-a, --agent", "Show agent events (default)").option("-s, --system", "Show system log").option("-m, --metrics", "Show metrics").option("-n, --network", "Show network logs (proxy traffic)").option(
16909
+ var logsCommand = new Command19().name("logs").description("View logs for an agent run").argument("<runId>", "Run ID to fetch logs for").option("-a, --agent", "Show agent events (default)").option("-s, --system", "Show system log").option("-m, --metrics", "Show metrics").option("-n, --network", "Show network logs (proxy traffic)").option(
16642
16910
  "--since <time>",
16643
16911
  "Show logs since timestamp (e.g., 5m, 2h, 1d, 2024-01-15T10:30:00Z, 1705312200)"
16644
16912
  ).option(
@@ -16680,7 +16948,7 @@ var logsCommand = new Command18().name("logs").description("View logs for an age
16680
16948
  async function showAgentEvents(runId, options) {
16681
16949
  const response = await apiClient.getAgentEvents(runId, options);
16682
16950
  if (response.events.length === 0) {
16683
- console.log(chalk19.yellow("No agent events found for this run."));
16951
+ console.log(chalk20.yellow("No agent events found for this run."));
16684
16952
  return;
16685
16953
  }
16686
16954
  for (const event of response.events) {
@@ -16689,7 +16957,7 @@ async function showAgentEvents(runId, options) {
16689
16957
  if (response.hasMore) {
16690
16958
  console.log();
16691
16959
  console.log(
16692
- chalk19.gray(
16960
+ chalk20.gray(
16693
16961
  `Showing ${response.events.length} events. Use --limit to see more.`
16694
16962
  )
16695
16963
  );
@@ -16698,21 +16966,21 @@ async function showAgentEvents(runId, options) {
16698
16966
  async function showSystemLog(runId, options) {
16699
16967
  const response = await apiClient.getSystemLog(runId, options);
16700
16968
  if (!response.systemLog) {
16701
- console.log(chalk19.yellow("No system log found for this run."));
16969
+ console.log(chalk20.yellow("No system log found for this run."));
16702
16970
  return;
16703
16971
  }
16704
16972
  console.log(response.systemLog);
16705
16973
  if (response.hasMore) {
16706
16974
  console.log();
16707
16975
  console.log(
16708
- chalk19.gray("More log entries available. Use --limit to see more.")
16976
+ chalk20.gray("More log entries available. Use --limit to see more.")
16709
16977
  );
16710
16978
  }
16711
16979
  }
16712
16980
  async function showMetrics(runId, options) {
16713
16981
  const response = await apiClient.getMetrics(runId, options);
16714
16982
  if (response.metrics.length === 0) {
16715
- console.log(chalk19.yellow("No metrics found for this run."));
16983
+ console.log(chalk20.yellow("No metrics found for this run."));
16716
16984
  return;
16717
16985
  }
16718
16986
  for (const metric of response.metrics) {
@@ -16721,7 +16989,7 @@ async function showMetrics(runId, options) {
16721
16989
  if (response.hasMore) {
16722
16990
  console.log();
16723
16991
  console.log(
16724
- chalk19.gray(
16992
+ chalk20.gray(
16725
16993
  `Showing ${response.metrics.length} metrics. Use --limit to see more.`
16726
16994
  )
16727
16995
  );
@@ -16731,7 +16999,7 @@ async function showNetworkLogs(runId, options) {
16731
16999
  const response = await apiClient.getNetworkLogs(runId, options);
16732
17000
  if (response.networkLogs.length === 0) {
16733
17001
  console.log(
16734
- chalk19.yellow(
17002
+ chalk20.yellow(
16735
17003
  "No network logs found for this run. Network logs are only captured when beta_network_security is enabled."
16736
17004
  )
16737
17005
  );
@@ -16743,7 +17011,7 @@ async function showNetworkLogs(runId, options) {
16743
17011
  if (response.hasMore) {
16744
17012
  console.log();
16745
17013
  console.log(
16746
- chalk19.gray(
17014
+ chalk20.gray(
16747
17015
  `Showing ${response.networkLogs.length} network logs. Use --limit to see more.`
16748
17016
  )
16749
17017
  );
@@ -16752,25 +17020,139 @@ async function showNetworkLogs(runId, options) {
16752
17020
  function handleError(error43, runId) {
16753
17021
  if (error43 instanceof Error) {
16754
17022
  if (error43.message.includes("Not authenticated")) {
16755
- console.error(chalk19.red("Not authenticated. Run: vm0 auth login"));
17023
+ console.error(chalk20.red("Not authenticated. Run: vm0 auth login"));
16756
17024
  } else if (error43.message.includes("not found")) {
16757
- console.error(chalk19.red(`Run not found: ${runId}`));
17025
+ console.error(chalk20.red(`Run not found: ${runId}`));
16758
17026
  } else if (error43.message.includes("Invalid time format")) {
16759
- console.error(chalk19.red(error43.message));
17027
+ console.error(chalk20.red(error43.message));
16760
17028
  } else {
16761
- console.error(chalk19.red("Failed to fetch logs"));
16762
- console.error(chalk19.gray(` ${error43.message}`));
17029
+ console.error(chalk20.red("Failed to fetch logs"));
17030
+ console.error(chalk20.gray(` ${error43.message}`));
16763
17031
  }
16764
17032
  } else {
16765
- console.error(chalk19.red("An unexpected error occurred"));
17033
+ console.error(chalk20.red("An unexpected error occurred"));
16766
17034
  }
16767
17035
  }
16768
17036
 
17037
+ // src/commands/scope/index.ts
17038
+ import { Command as Command22 } from "commander";
17039
+
17040
+ // src/commands/scope/status.ts
17041
+ import { Command as Command20 } from "commander";
17042
+ import chalk21 from "chalk";
17043
+ var statusCommand3 = new Command20().name("status").description("View current scope status").action(async () => {
17044
+ try {
17045
+ const scope = await apiClient.getScope();
17046
+ console.log(chalk21.cyan("Scope Information:"));
17047
+ console.log(` Slug: ${chalk21.green("@" + scope.slug)}`);
17048
+ console.log(` Type: ${scope.type}`);
17049
+ if (scope.displayName) {
17050
+ console.log(` Display Name: ${scope.displayName}`);
17051
+ }
17052
+ console.log(
17053
+ ` Created: ${new Date(scope.createdAt).toLocaleDateString()}`
17054
+ );
17055
+ } catch (error43) {
17056
+ if (error43 instanceof Error) {
17057
+ if (error43.message.includes("Not authenticated")) {
17058
+ console.error(chalk21.red("\u2717 Not authenticated. Run: vm0 auth login"));
17059
+ } else if (error43.message.includes("No scope configured")) {
17060
+ console.log(chalk21.yellow("No scope configured."));
17061
+ console.log();
17062
+ console.log("Set your scope with:");
17063
+ console.log(chalk21.cyan(" vm0 scope set <slug>"));
17064
+ console.log();
17065
+ console.log("Example:");
17066
+ console.log(chalk21.gray(" vm0 scope set myusername"));
17067
+ } else {
17068
+ console.error(chalk21.red(`\u2717 ${error43.message}`));
17069
+ }
17070
+ } else {
17071
+ console.error(chalk21.red("\u2717 An unexpected error occurred"));
17072
+ }
17073
+ process.exit(1);
17074
+ }
17075
+ });
17076
+
17077
+ // src/commands/scope/set.ts
17078
+ import { Command as Command21 } from "commander";
17079
+ import chalk22 from "chalk";
17080
+ var setCommand = new Command21().name("set").description("Set your scope slug").argument("<slug>", "The scope slug (e.g., your username)").option("--force", "Force change existing scope (may break references)").option("--display-name <name>", "Display name for the scope").action(
17081
+ async (slug, options) => {
17082
+ try {
17083
+ let existingScope;
17084
+ try {
17085
+ existingScope = await apiClient.getScope();
17086
+ } catch {
17087
+ }
17088
+ let scope;
17089
+ if (existingScope) {
17090
+ if (!options.force) {
17091
+ console.error(
17092
+ chalk22.yellow(`You already have a scope: @${existingScope.slug}`)
17093
+ );
17094
+ console.error();
17095
+ console.error("To change your scope, use --force:");
17096
+ console.error(chalk22.cyan(` vm0 scope set ${slug} --force`));
17097
+ console.error();
17098
+ console.error(
17099
+ chalk22.yellow(
17100
+ "Warning: Changing your scope may break existing image references."
17101
+ )
17102
+ );
17103
+ process.exit(1);
17104
+ }
17105
+ scope = await apiClient.updateScope({ slug, force: true });
17106
+ console.log(chalk22.green(`\u2713 Scope updated to @${scope.slug}`));
17107
+ } else {
17108
+ scope = await apiClient.createScope({
17109
+ slug,
17110
+ displayName: options.displayName
17111
+ });
17112
+ console.log(chalk22.green(`\u2713 Scope created: @${scope.slug}`));
17113
+ }
17114
+ console.log();
17115
+ console.log("Your images will now be namespaced as:");
17116
+ console.log(chalk22.cyan(` @${scope.slug}/<image-name>`));
17117
+ } catch (error43) {
17118
+ if (error43 instanceof Error) {
17119
+ if (error43.message.includes("Not authenticated")) {
17120
+ console.error(
17121
+ chalk22.red("\u2717 Not authenticated. Run: vm0 auth login")
17122
+ );
17123
+ } else if (error43.message.includes("already exists")) {
17124
+ console.error(
17125
+ chalk22.red(
17126
+ `\u2717 Scope "${slug}" is already taken. Please choose a different slug.`
17127
+ )
17128
+ );
17129
+ } else if (error43.message.includes("reserved")) {
17130
+ console.error(chalk22.red(`\u2717 ${error43.message}`));
17131
+ } else if (error43.message.includes("vm0")) {
17132
+ console.error(
17133
+ chalk22.red(
17134
+ "\u2717 Scope slugs cannot start with 'vm0' (reserved for system use)"
17135
+ )
17136
+ );
17137
+ } else {
17138
+ console.error(chalk22.red(`\u2717 ${error43.message}`));
17139
+ }
17140
+ } else {
17141
+ console.error(chalk22.red("\u2717 An unexpected error occurred"));
17142
+ }
17143
+ process.exit(1);
17144
+ }
17145
+ }
17146
+ );
17147
+
17148
+ // src/commands/scope/index.ts
17149
+ var scopeCommand = new Command22().name("scope").description("Manage your scope (namespace for images)").addCommand(statusCommand3).addCommand(setCommand);
17150
+
16769
17151
  // src/index.ts
16770
- var program = new Command19();
16771
- program.name("vm0").description("VM0 CLI - A modern build tool").version("4.14.1");
17152
+ var program = new Command23();
17153
+ program.name("vm0").description("VM0 CLI - A modern build tool").version("4.16.0");
16772
17154
  program.command("info").description("Display environment information").action(async () => {
16773
- console.log(chalk20.cyan("System Information:"));
17155
+ console.log(chalk23.cyan("System Information:"));
16774
17156
  console.log(`Node Version: ${process.version}`);
16775
17157
  console.log(`Platform: ${process.platform}`);
16776
17158
  console.log(`Architecture: ${process.arch}`);
@@ -16797,6 +17179,7 @@ program.addCommand(artifactCommand);
16797
17179
  program.addCommand(cookCommand);
16798
17180
  program.addCommand(imageCommand);
16799
17181
  program.addCommand(logsCommand);
17182
+ program.addCommand(scopeCommand);
16800
17183
  if (process.argv[1]?.endsWith("index.js") || process.argv[1]?.endsWith("index.ts") || process.argv[1]?.endsWith("vm0")) {
16801
17184
  program.parse();
16802
17185
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vm0/cli",
3
- "version": "4.14.1",
3
+ "version": "4.16.0",
4
4
  "description": "CLI application",
5
5
  "repository": {
6
6
  "type": "git",