@vm0/cli 4.29.1 → 4.31.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 +240 -50
  2. package/package.json +2 -1
package/index.js CHANGED
@@ -190,10 +190,11 @@ async function setupToken() {
190
190
  // src/commands/compose.ts
191
191
  import { Command } from "commander";
192
192
  import chalk2 from "chalk";
193
- import { readFile as readFile3 } from "fs/promises";
193
+ import { readFile as readFile4 } from "fs/promises";
194
194
  import { existsSync as existsSync3 } from "fs";
195
195
  import { dirname as dirname2 } from "path";
196
- import { parse as parseYaml } from "yaml";
196
+ import { parse as parseYaml2 } from "yaml";
197
+ import prompts from "prompts";
197
198
 
198
199
  // ../../packages/core/src/variable-expander.ts
199
200
  var VARIABLE_PATTERN = /\$\{\{\s*(env|vars|secrets)\.([a-zA-Z_][a-zA-Z0-9_]*)\s*\}\}/g;
@@ -12616,7 +12617,8 @@ var runSystemLogContract = c2.router({
12616
12617
  }),
12617
12618
  query: external_exports.object({
12618
12619
  since: external_exports.coerce.number().optional(),
12619
- limit: external_exports.coerce.number().min(1).max(100).default(5)
12620
+ limit: external_exports.coerce.number().min(1).max(100).default(5),
12621
+ order: external_exports.enum(["asc", "desc"]).default("desc")
12620
12622
  }),
12621
12623
  responses: {
12622
12624
  200: systemLogResponseSchema,
@@ -12639,7 +12641,8 @@ var runMetricsContract = c2.router({
12639
12641
  }),
12640
12642
  query: external_exports.object({
12641
12643
  since: external_exports.coerce.number().optional(),
12642
- limit: external_exports.coerce.number().min(1).max(100).default(5)
12644
+ limit: external_exports.coerce.number().min(1).max(100).default(5),
12645
+ order: external_exports.enum(["asc", "desc"]).default("desc")
12643
12646
  }),
12644
12647
  responses: {
12645
12648
  200: metricsResponseSchema,
@@ -12662,7 +12665,8 @@ var runAgentEventsContract = c2.router({
12662
12665
  }),
12663
12666
  query: external_exports.object({
12664
12667
  since: external_exports.coerce.number().optional(),
12665
- limit: external_exports.coerce.number().min(1).max(100).default(5)
12668
+ limit: external_exports.coerce.number().min(1).max(100).default(5),
12669
+ order: external_exports.enum(["asc", "desc"]).default("desc")
12666
12670
  }),
12667
12671
  responses: {
12668
12672
  200: agentEventsResponseSchema,
@@ -12685,7 +12689,8 @@ var runNetworkLogsContract = c2.router({
12685
12689
  }),
12686
12690
  query: external_exports.object({
12687
12691
  since: external_exports.coerce.number().optional(),
12688
- limit: external_exports.coerce.number().min(1).max(100).default(5)
12692
+ limit: external_exports.coerce.number().min(1).max(100).default(5),
12693
+ order: external_exports.enum(["asc", "desc"]).default("desc")
12689
12694
  }),
12690
12695
  responses: {
12691
12696
  200: networkLogsResponseSchema,
@@ -13749,6 +13754,9 @@ var ApiClient = class {
13749
13754
  if (options?.limit !== void 0) {
13750
13755
  params.set("limit", String(options.limit));
13751
13756
  }
13757
+ if (options?.order !== void 0) {
13758
+ params.set("order", options.order);
13759
+ }
13752
13760
  const queryString = params.toString();
13753
13761
  const url2 = `${baseUrl}/api/agent/runs/${runId}/telemetry/system-log${queryString ? `?${queryString}` : ""}`;
13754
13762
  const response = await fetch(url2, {
@@ -13771,6 +13779,9 @@ var ApiClient = class {
13771
13779
  if (options?.limit !== void 0) {
13772
13780
  params.set("limit", String(options.limit));
13773
13781
  }
13782
+ if (options?.order !== void 0) {
13783
+ params.set("order", options.order);
13784
+ }
13774
13785
  const queryString = params.toString();
13775
13786
  const url2 = `${baseUrl}/api/agent/runs/${runId}/telemetry/metrics${queryString ? `?${queryString}` : ""}`;
13776
13787
  const response = await fetch(url2, {
@@ -13793,6 +13804,9 @@ var ApiClient = class {
13793
13804
  if (options?.limit !== void 0) {
13794
13805
  params.set("limit", String(options.limit));
13795
13806
  }
13807
+ if (options?.order !== void 0) {
13808
+ params.set("order", options.order);
13809
+ }
13796
13810
  const queryString = params.toString();
13797
13811
  const url2 = `${baseUrl}/api/agent/runs/${runId}/telemetry/agent${queryString ? `?${queryString}` : ""}`;
13798
13812
  const response = await fetch(url2, {
@@ -13815,6 +13829,9 @@ var ApiClient = class {
13815
13829
  if (options?.limit !== void 0) {
13816
13830
  params.set("limit", String(options.limit));
13817
13831
  }
13832
+ if (options?.order !== void 0) {
13833
+ params.set("order", options.order);
13834
+ }
13818
13835
  const queryString = params.toString();
13819
13836
  const url2 = `${baseUrl}/api/agent/runs/${runId}/telemetry/network${queryString ? `?${queryString}` : ""}`;
13820
13837
  const response = await fetch(url2, {
@@ -14234,6 +14251,7 @@ import * as path from "path";
14234
14251
  import * as os from "os";
14235
14252
  import { exec } from "child_process";
14236
14253
  import { promisify } from "util";
14254
+ import { parse as parseYaml } from "yaml";
14237
14255
  var execAsync = promisify(exec);
14238
14256
  function parseGitHubTreeUrl2(url2) {
14239
14257
  const parsed = parseGitHubTreeUrl(url2);
@@ -14279,6 +14297,37 @@ async function validateSkillDirectory(skillDir) {
14279
14297
  );
14280
14298
  }
14281
14299
  }
14300
+ function parseSkillFrontmatter(content) {
14301
+ const frontmatterMatch = content.match(/^---\r?\n([\s\S]*?)\r?\n---/);
14302
+ if (!frontmatterMatch) {
14303
+ return {};
14304
+ }
14305
+ const yamlContent = frontmatterMatch[1];
14306
+ if (!yamlContent) {
14307
+ return {};
14308
+ }
14309
+ let parsed;
14310
+ try {
14311
+ parsed = parseYaml(yamlContent);
14312
+ } catch {
14313
+ return {};
14314
+ }
14315
+ if (!parsed || typeof parsed !== "object") {
14316
+ return {};
14317
+ }
14318
+ const data = parsed;
14319
+ return {
14320
+ name: typeof data.name === "string" ? data.name : void 0,
14321
+ description: typeof data.description === "string" ? data.description : void 0,
14322
+ vm0_secrets: Array.isArray(data.vm0_secrets) ? data.vm0_secrets.filter((s) => typeof s === "string") : void 0,
14323
+ vm0_vars: Array.isArray(data.vm0_vars) ? data.vm0_vars.filter((s) => typeof s === "string") : void 0
14324
+ };
14325
+ }
14326
+ async function readSkillFrontmatter(skillDir) {
14327
+ const skillMdPath = path.join(skillDir, "SKILL.md");
14328
+ const content = await fs.readFile(skillMdPath, "utf8");
14329
+ return parseSkillFrontmatter(content);
14330
+ }
14282
14331
 
14283
14332
  // src/lib/direct-upload.ts
14284
14333
  import { createHash } from "crypto";
@@ -14615,11 +14664,14 @@ async function uploadSkill(skillUrl) {
14615
14664
  try {
14616
14665
  const skillDir = await downloadGitHubSkill(parsed, tmpDir);
14617
14666
  await validateSkillDirectory(skillDir);
14667
+ const frontmatter = await readSkillFrontmatter(skillDir);
14618
14668
  const result = await directUpload(storageName, "volume", skillDir);
14619
14669
  return {
14620
14670
  name: storageName,
14621
14671
  versionId: result.versionId,
14622
- action: result.deduplicated ? "deduplicated" : "created"
14672
+ action: result.deduplicated ? "deduplicated" : "created",
14673
+ skillName: parsed.skillName,
14674
+ frontmatter
14623
14675
  };
14624
14676
  } finally {
14625
14677
  await fs4.rm(tmpDir, { recursive: true, force: true });
@@ -14654,16 +14706,16 @@ function transformExperimentalShorthand(agent) {
14654
14706
  agent.environment = environment;
14655
14707
  }
14656
14708
  }
14657
- var composeCommand = new Command().name("compose").description("Create or update agent compose").argument("<config-file>", "Path to config YAML file").action(async (configFile) => {
14709
+ var composeCommand = new Command().name("compose").description("Create or update agent compose").argument("<config-file>", "Path to config YAML file").option("-y, --yes", "Skip confirmation prompts for skill requirements").action(async (configFile, options) => {
14658
14710
  try {
14659
14711
  if (!existsSync3(configFile)) {
14660
14712
  console.error(chalk2.red(`\u2717 Config file not found: ${configFile}`));
14661
14713
  process.exit(1);
14662
14714
  }
14663
- const content = await readFile3(configFile, "utf8");
14715
+ const content = await readFile4(configFile, "utf8");
14664
14716
  let config2;
14665
14717
  try {
14666
- config2 = parseYaml(content);
14718
+ config2 = parseYaml2(content);
14667
14719
  } catch (error43) {
14668
14720
  console.error(chalk2.red("\u2717 Invalid YAML format"));
14669
14721
  if (error43 instanceof Error) {
@@ -14740,6 +14792,7 @@ var composeCommand = new Command().name("compose").description("Create or update
14740
14792
  process.exit(1);
14741
14793
  }
14742
14794
  }
14795
+ const skillResults = [];
14743
14796
  if (agent.skills && Array.isArray(agent.skills)) {
14744
14797
  const skillUrls = agent.skills;
14745
14798
  console.log(`Uploading ${skillUrls.length} skill(s)...`);
@@ -14747,9 +14800,10 @@ var composeCommand = new Command().name("compose").description("Create or update
14747
14800
  try {
14748
14801
  console.log(chalk2.dim(` Downloading: ${skillUrl}`));
14749
14802
  const result = await uploadSkill(skillUrl);
14803
+ skillResults.push(result);
14750
14804
  console.log(
14751
14805
  chalk2.green(
14752
- ` \u2713 Skill ${result.action === "deduplicated" ? "(unchanged)" : "uploaded"}: ${result.versionId.slice(0, 8)}`
14806
+ ` \u2713 Skill ${result.action === "deduplicated" ? "(unchanged)" : "uploaded"}: ${result.skillName} (${result.versionId.slice(0, 8)})`
14753
14807
  )
14754
14808
  );
14755
14809
  } catch (error43) {
@@ -14761,6 +14815,83 @@ var composeCommand = new Command().name("compose").description("Create or update
14761
14815
  }
14762
14816
  }
14763
14817
  }
14818
+ const skillSecrets = /* @__PURE__ */ new Map();
14819
+ const skillVars = /* @__PURE__ */ new Map();
14820
+ for (const result of skillResults) {
14821
+ const { frontmatter, skillName } = result;
14822
+ if (frontmatter.vm0_secrets) {
14823
+ for (const secret of frontmatter.vm0_secrets) {
14824
+ if (!skillSecrets.has(secret)) {
14825
+ skillSecrets.set(secret, []);
14826
+ }
14827
+ skillSecrets.get(secret).push(skillName);
14828
+ }
14829
+ }
14830
+ if (frontmatter.vm0_vars) {
14831
+ for (const varName of frontmatter.vm0_vars) {
14832
+ if (!skillVars.has(varName)) {
14833
+ skillVars.set(varName, []);
14834
+ }
14835
+ skillVars.get(varName).push(skillName);
14836
+ }
14837
+ }
14838
+ }
14839
+ const environment = agent.environment || {};
14840
+ const newSecrets = [...skillSecrets.entries()].filter(
14841
+ ([name]) => !(name in environment)
14842
+ );
14843
+ const newVars = [...skillVars.entries()].filter(
14844
+ ([name]) => !(name in environment)
14845
+ );
14846
+ if (newSecrets.length > 0 || newVars.length > 0) {
14847
+ console.log();
14848
+ console.log(
14849
+ chalk2.bold("Skills require the following environment variables:")
14850
+ );
14851
+ console.log();
14852
+ if (newSecrets.length > 0) {
14853
+ console.log(chalk2.cyan(" Secrets:"));
14854
+ for (const [name, skills] of newSecrets) {
14855
+ console.log(` ${name.padEnd(24)} <- ${skills.join(", ")}`);
14856
+ }
14857
+ }
14858
+ if (newVars.length > 0) {
14859
+ console.log(chalk2.cyan(" Vars:"));
14860
+ for (const [name, skills] of newVars) {
14861
+ console.log(` ${name.padEnd(24)} <- ${skills.join(", ")}`);
14862
+ }
14863
+ }
14864
+ console.log();
14865
+ if (!options.yes) {
14866
+ if (!process.stdin.isTTY) {
14867
+ console.error(
14868
+ chalk2.red(
14869
+ "\u2717 Non-interactive terminal. Use --yes flag to skip confirmation."
14870
+ )
14871
+ );
14872
+ process.exit(1);
14873
+ }
14874
+ const response2 = await prompts({
14875
+ type: "confirm",
14876
+ name: "value",
14877
+ message: "Proceed with compose?",
14878
+ initial: true
14879
+ });
14880
+ if (!response2.value) {
14881
+ console.log(chalk2.yellow("Compose cancelled."));
14882
+ process.exit(0);
14883
+ }
14884
+ }
14885
+ for (const [name] of newSecrets) {
14886
+ environment[name] = `\${{ secrets.${name} }}`;
14887
+ }
14888
+ for (const [name] of newVars) {
14889
+ environment[name] = `\${{ vars.${name} }}`;
14890
+ }
14891
+ if (Object.keys(environment).length > 0) {
14892
+ agent.environment = environment;
14893
+ }
14894
+ }
14764
14895
  console.log("Uploading compose...");
14765
14896
  const response = await apiClient.createOrUpdateCompose({
14766
14897
  content: config2
@@ -15799,6 +15930,16 @@ var runCmd = new Command2().name("run").description("Execute an agent").argument
15799
15930
  }
15800
15931
  );
15801
15932
  runCmd.command("resume").description("Resume an agent run from a checkpoint (uses all snapshot data)").argument("<checkpointId>", "Checkpoint ID to resume from").argument("<prompt>", "Prompt for the resumed agent").option(
15933
+ "--vars <KEY=value>",
15934
+ "Variables for ${{ vars.xxx }} (repeatable, falls back to env vars and .env)",
15935
+ collectKeyValue,
15936
+ {}
15937
+ ).option(
15938
+ "--secrets <KEY=value>",
15939
+ "Secrets for ${{ secrets.xxx }} (repeatable, required for resume)",
15940
+ collectKeyValue,
15941
+ {}
15942
+ ).option(
15802
15943
  "--volume-version <name=version>",
15803
15944
  "Volume version override (repeatable)",
15804
15945
  collectVolumeVersions,
@@ -15808,6 +15949,8 @@ runCmd.command("resume").description("Resume an agent run from a checkpoint (use
15808
15949
  const startTimestamp = /* @__PURE__ */ new Date();
15809
15950
  const allOpts = command.optsWithGlobals();
15810
15951
  const verbose = options.verbose || allOpts.verbose;
15952
+ const vars = { ...allOpts.vars, ...options.vars };
15953
+ const secrets = { ...allOpts.secrets, ...options.secrets };
15811
15954
  try {
15812
15955
  if (!isUUID(checkpointId)) {
15813
15956
  console.error(
@@ -15816,10 +15959,20 @@ runCmd.command("resume").description("Resume an agent run from a checkpoint (use
15816
15959
  console.error(chalk5.dim(" Checkpoint ID must be a valid UUID"));
15817
15960
  process.exit(1);
15818
15961
  }
15962
+ const secretNames = Object.keys(secrets);
15963
+ const loadedSecrets = secretNames.length > 0 ? secrets : loadValues({}, []);
15819
15964
  if (verbose) {
15820
15965
  logVerbosePreFlight("Resuming agent run from checkpoint", [
15821
15966
  { label: "Checkpoint ID", value: checkpointId },
15822
15967
  { label: "Prompt", value: prompt },
15968
+ {
15969
+ label: "Variables",
15970
+ value: Object.keys(vars).length > 0 ? JSON.stringify(vars) : void 0
15971
+ },
15972
+ {
15973
+ label: "Secrets",
15974
+ value: loadedSecrets && Object.keys(loadedSecrets).length > 0 ? `${Object.keys(loadedSecrets).length} loaded` : void 0
15975
+ },
15823
15976
  {
15824
15977
  label: "Volume overrides",
15825
15978
  value: Object.keys(allOpts.volumeVersion).length > 0 ? JSON.stringify(allOpts.volumeVersion) : void 0
@@ -15829,6 +15982,8 @@ runCmd.command("resume").description("Resume an agent run from a checkpoint (use
15829
15982
  const response = await apiClient.createRun({
15830
15983
  checkpointId,
15831
15984
  prompt,
15985
+ vars: Object.keys(vars).length > 0 ? vars : void 0,
15986
+ secrets: loadedSecrets,
15832
15987
  volumeVersions: Object.keys(allOpts.volumeVersion).length > 0 ? allOpts.volumeVersion : void 0
15833
15988
  });
15834
15989
  if (response.status === "failed") {
@@ -15872,6 +16027,16 @@ runCmd.command("resume").description("Resume an agent run from a checkpoint (use
15872
16027
  runCmd.command("continue").description(
15873
16028
  "Continue an agent run from a session (uses latest artifact version)"
15874
16029
  ).argument("<agentSessionId>", "Agent session ID to continue from").argument("<prompt>", "Prompt for the continued agent").option(
16030
+ "--vars <KEY=value>",
16031
+ "Variables for ${{ vars.xxx }} (repeatable, falls back to env vars and .env)",
16032
+ collectKeyValue,
16033
+ {}
16034
+ ).option(
16035
+ "--secrets <KEY=value>",
16036
+ "Secrets for ${{ secrets.xxx }} (repeatable, required for continue)",
16037
+ collectKeyValue,
16038
+ {}
16039
+ ).option(
15875
16040
  "--volume-version <name=version>",
15876
16041
  "Volume version override (repeatable)",
15877
16042
  collectVolumeVersions,
@@ -15881,6 +16046,8 @@ runCmd.command("continue").description(
15881
16046
  const startTimestamp = /* @__PURE__ */ new Date();
15882
16047
  const allOpts = command.optsWithGlobals();
15883
16048
  const verbose = options.verbose || allOpts.verbose;
16049
+ const vars = { ...allOpts.vars, ...options.vars };
16050
+ const secrets = { ...allOpts.secrets, ...options.secrets };
15884
16051
  try {
15885
16052
  if (!isUUID(agentSessionId)) {
15886
16053
  console.error(
@@ -15889,11 +16056,21 @@ runCmd.command("continue").description(
15889
16056
  console.error(chalk5.dim(" Agent session ID must be a valid UUID"));
15890
16057
  process.exit(1);
15891
16058
  }
16059
+ const secretNames = Object.keys(secrets);
16060
+ const loadedSecrets = secretNames.length > 0 ? secrets : loadValues({}, []);
15892
16061
  if (verbose) {
15893
16062
  logVerbosePreFlight("Continuing agent run from session", [
15894
16063
  { label: "Session ID", value: agentSessionId },
15895
16064
  { label: "Prompt", value: prompt },
15896
16065
  { label: "Note", value: "Using latest artifact version" },
16066
+ {
16067
+ label: "Variables",
16068
+ value: Object.keys(vars).length > 0 ? JSON.stringify(vars) : void 0
16069
+ },
16070
+ {
16071
+ label: "Secrets",
16072
+ value: loadedSecrets && Object.keys(loadedSecrets).length > 0 ? `${Object.keys(loadedSecrets).length} loaded` : void 0
16073
+ },
15897
16074
  {
15898
16075
  label: "Volume overrides",
15899
16076
  value: Object.keys(allOpts.volumeVersion).length > 0 ? JSON.stringify(allOpts.volumeVersion) : void 0
@@ -15903,6 +16080,8 @@ runCmd.command("continue").description(
15903
16080
  const response = await apiClient.createRun({
15904
16081
  sessionId: agentSessionId,
15905
16082
  prompt,
16083
+ vars: Object.keys(vars).length > 0 ? vars : void 0,
16084
+ secrets: loadedSecrets,
15906
16085
  volumeVersions: Object.keys(allOpts.volumeVersion).length > 0 ? allOpts.volumeVersion : void 0
15907
16086
  });
15908
16087
  if (response.status === "failed") {
@@ -15956,9 +16135,9 @@ import chalk6 from "chalk";
15956
16135
  import path7 from "path";
15957
16136
 
15958
16137
  // src/lib/storage-utils.ts
15959
- import { readFile as readFile4, writeFile as writeFile4, mkdir as mkdir4 } from "fs/promises";
16138
+ import { readFile as readFile5, writeFile as writeFile4, mkdir as mkdir4 } from "fs/promises";
15960
16139
  import { existsSync as existsSync5 } from "fs";
15961
- import { parse as parseYaml2, stringify as stringifyYaml } from "yaml";
16140
+ import { parse as parseYaml3, stringify as stringifyYaml } from "yaml";
15962
16141
  import path6 from "path";
15963
16142
  var CONFIG_DIR2 = ".vm0";
15964
16143
  var CONFIG_FILE2 = "storage.yaml";
@@ -15981,8 +16160,8 @@ async function readStorageConfig(basePath = process.cwd()) {
15981
16160
  if (!actualPath) {
15982
16161
  return null;
15983
16162
  }
15984
- const content = await readFile4(actualPath, "utf8");
15985
- const config2 = parseYaml2(content);
16163
+ const content = await readFile5(actualPath, "utf8");
16164
+ const config2 = parseYaml3(content);
15986
16165
  if (!config2.type) {
15987
16166
  config2.type = "volume";
15988
16167
  }
@@ -16570,11 +16749,11 @@ var artifactCommand = new Command12().name("artifact").description("Manage cloud
16570
16749
  // src/commands/cook.ts
16571
16750
  import { Command as Command13 } from "commander";
16572
16751
  import chalk16 from "chalk";
16573
- import { readFile as readFile6, mkdir as mkdir6, writeFile as writeFile6, appendFile } from "fs/promises";
16752
+ import { readFile as readFile7, mkdir as mkdir6, writeFile as writeFile6, appendFile } from "fs/promises";
16574
16753
  import { existsSync as existsSync7, readFileSync } from "fs";
16575
16754
  import path11 from "path";
16576
16755
  import { spawn as spawn2 } from "child_process";
16577
- import { parse as parseYaml3 } from "yaml";
16756
+ import { parse as parseYaml4 } from "yaml";
16578
16757
  import { config as dotenvConfig2 } from "dotenv";
16579
16758
 
16580
16759
  // src/lib/update-checker.ts
@@ -16688,7 +16867,7 @@ async function checkAndUpgrade(currentVersion, prompt) {
16688
16867
  // src/lib/cook-state.ts
16689
16868
  import { homedir as homedir2 } from "os";
16690
16869
  import { join as join6 } from "path";
16691
- import { readFile as readFile5, writeFile as writeFile5, mkdir as mkdir5 } from "fs/promises";
16870
+ import { readFile as readFile6, writeFile as writeFile5, mkdir as mkdir5 } from "fs/promises";
16692
16871
  import { existsSync as existsSync6 } from "fs";
16693
16872
  var CONFIG_DIR3 = join6(homedir2(), ".vm0");
16694
16873
  var COOK_STATE_FILE = join6(CONFIG_DIR3, "cook.json");
@@ -16698,7 +16877,7 @@ async function loadCookStateFile() {
16698
16877
  return { ppid: {} };
16699
16878
  }
16700
16879
  try {
16701
- const content = await readFile5(COOK_STATE_FILE, "utf8");
16880
+ const content = await readFile6(COOK_STATE_FILE, "utf8");
16702
16881
  const data = JSON.parse(content);
16703
16882
  if (!data.ppid) {
16704
16883
  const oldState = data;
@@ -16909,7 +17088,7 @@ async function autoPullArtifact(runOutput, artifactDir) {
16909
17088
  }
16910
17089
  var cookCmd = new Command13().name("cook").description("One-click agent preparation and execution from vm0.yaml");
16911
17090
  cookCmd.argument("[prompt]", "Prompt for the agent").action(async (prompt) => {
16912
- const shouldExit = await checkAndUpgrade("4.29.1", prompt);
17091
+ const shouldExit = await checkAndUpgrade("4.31.0", prompt);
16913
17092
  if (shouldExit) {
16914
17093
  process.exit(0);
16915
17094
  }
@@ -16921,8 +17100,8 @@ cookCmd.argument("[prompt]", "Prompt for the agent").action(async (prompt) => {
16921
17100
  }
16922
17101
  let config2;
16923
17102
  try {
16924
- const content = await readFile6(CONFIG_FILE3, "utf8");
16925
- config2 = parseYaml3(content);
17103
+ const content = await readFile7(CONFIG_FILE3, "utf8");
17104
+ config2 = parseYaml4(content);
16926
17105
  } catch (error43) {
16927
17106
  console.error(chalk16.red("\u2717 Invalid YAML format"));
16928
17107
  if (error43 instanceof Error) {
@@ -17082,7 +17261,7 @@ cookCmd.argument("[prompt]", "Prompt for the agent").action(async (prompt) => {
17082
17261
  cookCmd.command("logs").description("View logs from the last cook run").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(
17083
17262
  "--since <time>",
17084
17263
  "Show logs since timestamp (e.g., 5m, 2h, 1d, 2024-01-15T10:30:00Z)"
17085
- ).option("--limit <n>", "Maximum number of entries to show (default: 5)").action(
17264
+ ).option("--tail <n>", "Show last N entries (default: 5, max: 100)").option("--head <n>", "Show first N entries (max: 100)").action(
17086
17265
  async (options) => {
17087
17266
  const state = await loadCookState();
17088
17267
  if (!state.lastRunId) {
@@ -17112,9 +17291,13 @@ cookCmd.command("logs").description("View logs from the last cook run").option("
17112
17291
  args.push("--since", options.since);
17113
17292
  displayArgs.push(`--since ${options.since}`);
17114
17293
  }
17115
- if (options.limit) {
17116
- args.push("--limit", options.limit);
17117
- displayArgs.push(`--limit ${options.limit}`);
17294
+ if (options.tail) {
17295
+ args.push("--tail", options.tail);
17296
+ displayArgs.push(`--tail ${options.tail}`);
17297
+ }
17298
+ if (options.head) {
17299
+ args.push("--head", options.head);
17300
+ displayArgs.push(`--head ${options.head}`);
17118
17301
  }
17119
17302
  printCommand(displayArgs.join(" "));
17120
17303
  await execVm0Command(args);
@@ -17192,7 +17375,7 @@ import { Command as Command18 } from "commander";
17192
17375
  // src/commands/image/build.ts
17193
17376
  import { Command as Command14 } from "commander";
17194
17377
  import chalk17 from "chalk";
17195
- import { readFile as readFile7 } from "fs/promises";
17378
+ import { readFile as readFile8 } from "fs/promises";
17196
17379
  import { existsSync as existsSync8 } from "fs";
17197
17380
 
17198
17381
  // src/lib/dockerfile-validator.ts
@@ -17253,7 +17436,7 @@ var buildCommand = new Command14().name("build").description("Build a custom ima
17253
17436
  }
17254
17437
  try {
17255
17438
  const scope = await apiClient.getScope();
17256
- const dockerfile = await readFile7(file2, "utf8");
17439
+ const dockerfile = await readFile8(file2, "utf8");
17257
17440
  const validation = validateDockerfile(dockerfile);
17258
17441
  if (!validation.valid) {
17259
17442
  console.error(chalk17.red("\u2717 Dockerfile validation failed\n"));
@@ -17704,34 +17887,38 @@ function getLogType(options) {
17704
17887
  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(
17705
17888
  "--since <time>",
17706
17889
  "Show logs since timestamp (e.g., 5m, 2h, 1d, 2024-01-15T10:30:00Z, 1705312200)"
17707
- ).option(
17708
- "--limit <n>",
17709
- "Maximum number of entries to show (default: 5, max: 100)",
17710
- "5"
17711
- ).action(
17890
+ ).option("--tail <n>", "Show last N entries (default: 5, max: 100)").option("--head <n>", "Show first N entries (max: 100)").action(
17712
17891
  async (runId, options) => {
17713
17892
  try {
17714
17893
  const logType = getLogType(options);
17894
+ if (options.tail !== void 0 && options.head !== void 0) {
17895
+ console.error(
17896
+ chalk21.red("Options --tail and --head are mutually exclusive")
17897
+ );
17898
+ process.exit(1);
17899
+ }
17715
17900
  let since;
17716
17901
  if (options.since) {
17717
17902
  since = parseTime(options.since);
17718
17903
  }
17904
+ const isHead = options.head !== void 0;
17719
17905
  const limit = Math.min(
17720
- Math.max(1, parseInt(options.limit || "5", 10)),
17906
+ Math.max(1, parseInt(options.head || options.tail || "5", 10)),
17721
17907
  100
17722
17908
  );
17909
+ const order = isHead ? "asc" : "desc";
17723
17910
  switch (logType) {
17724
17911
  case "agent":
17725
- await showAgentEvents(runId, { since, limit });
17912
+ await showAgentEvents(runId, { since, limit, order });
17726
17913
  break;
17727
17914
  case "system":
17728
- await showSystemLog(runId, { since, limit });
17915
+ await showSystemLog(runId, { since, limit, order });
17729
17916
  break;
17730
17917
  case "metrics":
17731
- await showMetrics(runId, { since, limit });
17918
+ await showMetrics(runId, { since, limit, order });
17732
17919
  break;
17733
17920
  case "network":
17734
- await showNetworkLogs(runId, { since, limit });
17921
+ await showNetworkLogs(runId, { since, limit, order });
17735
17922
  break;
17736
17923
  }
17737
17924
  } catch (error43) {
@@ -17746,14 +17933,15 @@ async function showAgentEvents(runId, options) {
17746
17933
  console.log(chalk21.yellow("No agent events found for this run."));
17747
17934
  return;
17748
17935
  }
17749
- for (const event of response.events) {
17936
+ const events = options.order === "desc" ? [...response.events].reverse() : response.events;
17937
+ for (const event of events) {
17750
17938
  renderAgentEvent(event, response.provider);
17751
17939
  }
17752
17940
  if (response.hasMore) {
17753
17941
  console.log();
17754
17942
  console.log(
17755
17943
  chalk21.dim(
17756
- `Showing ${response.events.length} events. Use --limit to see more.`
17944
+ `Showing ${response.events.length} events. Use --tail to see more.`
17757
17945
  )
17758
17946
  );
17759
17947
  }
@@ -17768,7 +17956,7 @@ async function showSystemLog(runId, options) {
17768
17956
  if (response.hasMore) {
17769
17957
  console.log();
17770
17958
  console.log(
17771
- chalk21.dim("More log entries available. Use --limit to see more.")
17959
+ chalk21.dim("More log entries available. Use --tail to see more.")
17772
17960
  );
17773
17961
  }
17774
17962
  }
@@ -17778,14 +17966,15 @@ async function showMetrics(runId, options) {
17778
17966
  console.log(chalk21.yellow("No metrics found for this run."));
17779
17967
  return;
17780
17968
  }
17781
- for (const metric of response.metrics) {
17969
+ const metrics = options.order === "desc" ? [...response.metrics].reverse() : response.metrics;
17970
+ for (const metric of metrics) {
17782
17971
  console.log(formatMetric(metric));
17783
17972
  }
17784
17973
  if (response.hasMore) {
17785
17974
  console.log();
17786
17975
  console.log(
17787
17976
  chalk21.dim(
17788
- `Showing ${response.metrics.length} metrics. Use --limit to see more.`
17977
+ `Showing ${response.metrics.length} metrics. Use --tail to see more.`
17789
17978
  )
17790
17979
  );
17791
17980
  }
@@ -17800,14 +17989,15 @@ async function showNetworkLogs(runId, options) {
17800
17989
  );
17801
17990
  return;
17802
17991
  }
17803
- for (const entry of response.networkLogs) {
17992
+ const networkLogs = options.order === "desc" ? [...response.networkLogs].reverse() : response.networkLogs;
17993
+ for (const entry of networkLogs) {
17804
17994
  console.log(formatNetworkLog(entry));
17805
17995
  }
17806
17996
  if (response.hasMore) {
17807
17997
  console.log();
17808
17998
  console.log(
17809
17999
  chalk21.dim(
17810
- `Showing ${response.networkLogs.length} network logs. Use --limit to see more.`
18000
+ `Showing ${response.networkLogs.length} network logs. Use --tail to see more.`
17811
18001
  )
17812
18002
  );
17813
18003
  }
@@ -18052,9 +18242,9 @@ import { Command as Command24 } from "commander";
18052
18242
  import chalk25 from "chalk";
18053
18243
  import * as readline3 from "readline";
18054
18244
  import { existsSync as existsSync10 } from "fs";
18055
- import { mkdir as mkdir7, readFile as readFile8, writeFile as writeFile8 } from "fs/promises";
18245
+ import { mkdir as mkdir7, readFile as readFile9, writeFile as writeFile8 } from "fs/promises";
18056
18246
  import { execSync, spawnSync } from "child_process";
18057
- import { parse as parseYaml4 } from "yaml";
18247
+ import { parse as parseYaml5 } from "yaml";
18058
18248
  function isGhInstalled() {
18059
18249
  try {
18060
18250
  execSync("gh --version", { stdio: "ignore" });
@@ -18374,8 +18564,8 @@ var setupGithubCommand = new Command24().name("setup-github").description("Initi
18374
18564
  }
18375
18565
  console.log();
18376
18566
  console.log("Analyzing vm0.yaml...");
18377
- const content = await readFile8("vm0.yaml", "utf8");
18378
- const config2 = parseYaml4(content);
18567
+ const content = await readFile9("vm0.yaml", "utf8");
18568
+ const config2 = parseYaml5(content);
18379
18569
  const agents = config2.agents;
18380
18570
  const agentName = Object.keys(agents)[0];
18381
18571
  console.log(chalk25.green(`\u2713 Agent: ${agentName}`));
@@ -18511,7 +18701,7 @@ var setupGithubCommand = new Command24().name("setup-github").description("Initi
18511
18701
 
18512
18702
  // src/index.ts
18513
18703
  var program = new Command25();
18514
- program.name("vm0").description("VM0 CLI - A modern build tool").version("4.29.1");
18704
+ program.name("vm0").description("VM0 CLI - A modern build tool").version("4.31.0");
18515
18705
  program.command("info").description("Display environment information").action(async () => {
18516
18706
  console.log(chalk26.bold("System Information:"));
18517
18707
  console.log(`Node Version: ${process.version}`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vm0/cli",
3
- "version": "4.29.1",
3
+ "version": "4.31.0",
4
4
  "description": "CLI application",
5
5
  "repository": {
6
6
  "type": "git",
@@ -19,6 +19,7 @@
19
19
  "chalk": "^5.6.0",
20
20
  "commander": "^14.0.0",
21
21
  "dotenv": "^17.2.1",
22
+ "prompts": "^2.4.2",
22
23
  "tar": "^7.5.2",
23
24
  "yaml": "^2.3.4"
24
25
  }