@vm0/cli 4.30.0 → 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 +188 -25
  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;
@@ -14250,6 +14251,7 @@ import * as path from "path";
14250
14251
  import * as os from "os";
14251
14252
  import { exec } from "child_process";
14252
14253
  import { promisify } from "util";
14254
+ import { parse as parseYaml } from "yaml";
14253
14255
  var execAsync = promisify(exec);
14254
14256
  function parseGitHubTreeUrl2(url2) {
14255
14257
  const parsed = parseGitHubTreeUrl(url2);
@@ -14295,6 +14297,37 @@ async function validateSkillDirectory(skillDir) {
14295
14297
  );
14296
14298
  }
14297
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
+ }
14298
14331
 
14299
14332
  // src/lib/direct-upload.ts
14300
14333
  import { createHash } from "crypto";
@@ -14631,11 +14664,14 @@ async function uploadSkill(skillUrl) {
14631
14664
  try {
14632
14665
  const skillDir = await downloadGitHubSkill(parsed, tmpDir);
14633
14666
  await validateSkillDirectory(skillDir);
14667
+ const frontmatter = await readSkillFrontmatter(skillDir);
14634
14668
  const result = await directUpload(storageName, "volume", skillDir);
14635
14669
  return {
14636
14670
  name: storageName,
14637
14671
  versionId: result.versionId,
14638
- action: result.deduplicated ? "deduplicated" : "created"
14672
+ action: result.deduplicated ? "deduplicated" : "created",
14673
+ skillName: parsed.skillName,
14674
+ frontmatter
14639
14675
  };
14640
14676
  } finally {
14641
14677
  await fs4.rm(tmpDir, { recursive: true, force: true });
@@ -14670,16 +14706,16 @@ function transformExperimentalShorthand(agent) {
14670
14706
  agent.environment = environment;
14671
14707
  }
14672
14708
  }
14673
- 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) => {
14674
14710
  try {
14675
14711
  if (!existsSync3(configFile)) {
14676
14712
  console.error(chalk2.red(`\u2717 Config file not found: ${configFile}`));
14677
14713
  process.exit(1);
14678
14714
  }
14679
- const content = await readFile3(configFile, "utf8");
14715
+ const content = await readFile4(configFile, "utf8");
14680
14716
  let config2;
14681
14717
  try {
14682
- config2 = parseYaml(content);
14718
+ config2 = parseYaml2(content);
14683
14719
  } catch (error43) {
14684
14720
  console.error(chalk2.red("\u2717 Invalid YAML format"));
14685
14721
  if (error43 instanceof Error) {
@@ -14756,6 +14792,7 @@ var composeCommand = new Command().name("compose").description("Create or update
14756
14792
  process.exit(1);
14757
14793
  }
14758
14794
  }
14795
+ const skillResults = [];
14759
14796
  if (agent.skills && Array.isArray(agent.skills)) {
14760
14797
  const skillUrls = agent.skills;
14761
14798
  console.log(`Uploading ${skillUrls.length} skill(s)...`);
@@ -14763,9 +14800,10 @@ var composeCommand = new Command().name("compose").description("Create or update
14763
14800
  try {
14764
14801
  console.log(chalk2.dim(` Downloading: ${skillUrl}`));
14765
14802
  const result = await uploadSkill(skillUrl);
14803
+ skillResults.push(result);
14766
14804
  console.log(
14767
14805
  chalk2.green(
14768
- ` \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)})`
14769
14807
  )
14770
14808
  );
14771
14809
  } catch (error43) {
@@ -14777,6 +14815,83 @@ var composeCommand = new Command().name("compose").description("Create or update
14777
14815
  }
14778
14816
  }
14779
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
+ }
14780
14895
  console.log("Uploading compose...");
14781
14896
  const response = await apiClient.createOrUpdateCompose({
14782
14897
  content: config2
@@ -15815,6 +15930,16 @@ var runCmd = new Command2().name("run").description("Execute an agent").argument
15815
15930
  }
15816
15931
  );
15817
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(
15818
15943
  "--volume-version <name=version>",
15819
15944
  "Volume version override (repeatable)",
15820
15945
  collectVolumeVersions,
@@ -15824,6 +15949,8 @@ runCmd.command("resume").description("Resume an agent run from a checkpoint (use
15824
15949
  const startTimestamp = /* @__PURE__ */ new Date();
15825
15950
  const allOpts = command.optsWithGlobals();
15826
15951
  const verbose = options.verbose || allOpts.verbose;
15952
+ const vars = { ...allOpts.vars, ...options.vars };
15953
+ const secrets = { ...allOpts.secrets, ...options.secrets };
15827
15954
  try {
15828
15955
  if (!isUUID(checkpointId)) {
15829
15956
  console.error(
@@ -15832,10 +15959,20 @@ runCmd.command("resume").description("Resume an agent run from a checkpoint (use
15832
15959
  console.error(chalk5.dim(" Checkpoint ID must be a valid UUID"));
15833
15960
  process.exit(1);
15834
15961
  }
15962
+ const secretNames = Object.keys(secrets);
15963
+ const loadedSecrets = secretNames.length > 0 ? secrets : loadValues({}, []);
15835
15964
  if (verbose) {
15836
15965
  logVerbosePreFlight("Resuming agent run from checkpoint", [
15837
15966
  { label: "Checkpoint ID", value: checkpointId },
15838
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
+ },
15839
15976
  {
15840
15977
  label: "Volume overrides",
15841
15978
  value: Object.keys(allOpts.volumeVersion).length > 0 ? JSON.stringify(allOpts.volumeVersion) : void 0
@@ -15845,6 +15982,8 @@ runCmd.command("resume").description("Resume an agent run from a checkpoint (use
15845
15982
  const response = await apiClient.createRun({
15846
15983
  checkpointId,
15847
15984
  prompt,
15985
+ vars: Object.keys(vars).length > 0 ? vars : void 0,
15986
+ secrets: loadedSecrets,
15848
15987
  volumeVersions: Object.keys(allOpts.volumeVersion).length > 0 ? allOpts.volumeVersion : void 0
15849
15988
  });
15850
15989
  if (response.status === "failed") {
@@ -15888,6 +16027,16 @@ runCmd.command("resume").description("Resume an agent run from a checkpoint (use
15888
16027
  runCmd.command("continue").description(
15889
16028
  "Continue an agent run from a session (uses latest artifact version)"
15890
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(
15891
16040
  "--volume-version <name=version>",
15892
16041
  "Volume version override (repeatable)",
15893
16042
  collectVolumeVersions,
@@ -15897,6 +16046,8 @@ runCmd.command("continue").description(
15897
16046
  const startTimestamp = /* @__PURE__ */ new Date();
15898
16047
  const allOpts = command.optsWithGlobals();
15899
16048
  const verbose = options.verbose || allOpts.verbose;
16049
+ const vars = { ...allOpts.vars, ...options.vars };
16050
+ const secrets = { ...allOpts.secrets, ...options.secrets };
15900
16051
  try {
15901
16052
  if (!isUUID(agentSessionId)) {
15902
16053
  console.error(
@@ -15905,11 +16056,21 @@ runCmd.command("continue").description(
15905
16056
  console.error(chalk5.dim(" Agent session ID must be a valid UUID"));
15906
16057
  process.exit(1);
15907
16058
  }
16059
+ const secretNames = Object.keys(secrets);
16060
+ const loadedSecrets = secretNames.length > 0 ? secrets : loadValues({}, []);
15908
16061
  if (verbose) {
15909
16062
  logVerbosePreFlight("Continuing agent run from session", [
15910
16063
  { label: "Session ID", value: agentSessionId },
15911
16064
  { label: "Prompt", value: prompt },
15912
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
+ },
15913
16074
  {
15914
16075
  label: "Volume overrides",
15915
16076
  value: Object.keys(allOpts.volumeVersion).length > 0 ? JSON.stringify(allOpts.volumeVersion) : void 0
@@ -15919,6 +16080,8 @@ runCmd.command("continue").description(
15919
16080
  const response = await apiClient.createRun({
15920
16081
  sessionId: agentSessionId,
15921
16082
  prompt,
16083
+ vars: Object.keys(vars).length > 0 ? vars : void 0,
16084
+ secrets: loadedSecrets,
15922
16085
  volumeVersions: Object.keys(allOpts.volumeVersion).length > 0 ? allOpts.volumeVersion : void 0
15923
16086
  });
15924
16087
  if (response.status === "failed") {
@@ -15972,9 +16135,9 @@ import chalk6 from "chalk";
15972
16135
  import path7 from "path";
15973
16136
 
15974
16137
  // src/lib/storage-utils.ts
15975
- 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";
15976
16139
  import { existsSync as existsSync5 } from "fs";
15977
- import { parse as parseYaml2, stringify as stringifyYaml } from "yaml";
16140
+ import { parse as parseYaml3, stringify as stringifyYaml } from "yaml";
15978
16141
  import path6 from "path";
15979
16142
  var CONFIG_DIR2 = ".vm0";
15980
16143
  var CONFIG_FILE2 = "storage.yaml";
@@ -15997,8 +16160,8 @@ async function readStorageConfig(basePath = process.cwd()) {
15997
16160
  if (!actualPath) {
15998
16161
  return null;
15999
16162
  }
16000
- const content = await readFile4(actualPath, "utf8");
16001
- const config2 = parseYaml2(content);
16163
+ const content = await readFile5(actualPath, "utf8");
16164
+ const config2 = parseYaml3(content);
16002
16165
  if (!config2.type) {
16003
16166
  config2.type = "volume";
16004
16167
  }
@@ -16586,11 +16749,11 @@ var artifactCommand = new Command12().name("artifact").description("Manage cloud
16586
16749
  // src/commands/cook.ts
16587
16750
  import { Command as Command13 } from "commander";
16588
16751
  import chalk16 from "chalk";
16589
- 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";
16590
16753
  import { existsSync as existsSync7, readFileSync } from "fs";
16591
16754
  import path11 from "path";
16592
16755
  import { spawn as spawn2 } from "child_process";
16593
- import { parse as parseYaml3 } from "yaml";
16756
+ import { parse as parseYaml4 } from "yaml";
16594
16757
  import { config as dotenvConfig2 } from "dotenv";
16595
16758
 
16596
16759
  // src/lib/update-checker.ts
@@ -16704,7 +16867,7 @@ async function checkAndUpgrade(currentVersion, prompt) {
16704
16867
  // src/lib/cook-state.ts
16705
16868
  import { homedir as homedir2 } from "os";
16706
16869
  import { join as join6 } from "path";
16707
- 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";
16708
16871
  import { existsSync as existsSync6 } from "fs";
16709
16872
  var CONFIG_DIR3 = join6(homedir2(), ".vm0");
16710
16873
  var COOK_STATE_FILE = join6(CONFIG_DIR3, "cook.json");
@@ -16714,7 +16877,7 @@ async function loadCookStateFile() {
16714
16877
  return { ppid: {} };
16715
16878
  }
16716
16879
  try {
16717
- const content = await readFile5(COOK_STATE_FILE, "utf8");
16880
+ const content = await readFile6(COOK_STATE_FILE, "utf8");
16718
16881
  const data = JSON.parse(content);
16719
16882
  if (!data.ppid) {
16720
16883
  const oldState = data;
@@ -16925,7 +17088,7 @@ async function autoPullArtifact(runOutput, artifactDir) {
16925
17088
  }
16926
17089
  var cookCmd = new Command13().name("cook").description("One-click agent preparation and execution from vm0.yaml");
16927
17090
  cookCmd.argument("[prompt]", "Prompt for the agent").action(async (prompt) => {
16928
- const shouldExit = await checkAndUpgrade("4.30.0", prompt);
17091
+ const shouldExit = await checkAndUpgrade("4.31.0", prompt);
16929
17092
  if (shouldExit) {
16930
17093
  process.exit(0);
16931
17094
  }
@@ -16937,8 +17100,8 @@ cookCmd.argument("[prompt]", "Prompt for the agent").action(async (prompt) => {
16937
17100
  }
16938
17101
  let config2;
16939
17102
  try {
16940
- const content = await readFile6(CONFIG_FILE3, "utf8");
16941
- config2 = parseYaml3(content);
17103
+ const content = await readFile7(CONFIG_FILE3, "utf8");
17104
+ config2 = parseYaml4(content);
16942
17105
  } catch (error43) {
16943
17106
  console.error(chalk16.red("\u2717 Invalid YAML format"));
16944
17107
  if (error43 instanceof Error) {
@@ -17212,7 +17375,7 @@ import { Command as Command18 } from "commander";
17212
17375
  // src/commands/image/build.ts
17213
17376
  import { Command as Command14 } from "commander";
17214
17377
  import chalk17 from "chalk";
17215
- import { readFile as readFile7 } from "fs/promises";
17378
+ import { readFile as readFile8 } from "fs/promises";
17216
17379
  import { existsSync as existsSync8 } from "fs";
17217
17380
 
17218
17381
  // src/lib/dockerfile-validator.ts
@@ -17273,7 +17436,7 @@ var buildCommand = new Command14().name("build").description("Build a custom ima
17273
17436
  }
17274
17437
  try {
17275
17438
  const scope = await apiClient.getScope();
17276
- const dockerfile = await readFile7(file2, "utf8");
17439
+ const dockerfile = await readFile8(file2, "utf8");
17277
17440
  const validation = validateDockerfile(dockerfile);
17278
17441
  if (!validation.valid) {
17279
17442
  console.error(chalk17.red("\u2717 Dockerfile validation failed\n"));
@@ -18079,9 +18242,9 @@ import { Command as Command24 } from "commander";
18079
18242
  import chalk25 from "chalk";
18080
18243
  import * as readline3 from "readline";
18081
18244
  import { existsSync as existsSync10 } from "fs";
18082
- 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";
18083
18246
  import { execSync, spawnSync } from "child_process";
18084
- import { parse as parseYaml4 } from "yaml";
18247
+ import { parse as parseYaml5 } from "yaml";
18085
18248
  function isGhInstalled() {
18086
18249
  try {
18087
18250
  execSync("gh --version", { stdio: "ignore" });
@@ -18401,8 +18564,8 @@ var setupGithubCommand = new Command24().name("setup-github").description("Initi
18401
18564
  }
18402
18565
  console.log();
18403
18566
  console.log("Analyzing vm0.yaml...");
18404
- const content = await readFile8("vm0.yaml", "utf8");
18405
- const config2 = parseYaml4(content);
18567
+ const content = await readFile9("vm0.yaml", "utf8");
18568
+ const config2 = parseYaml5(content);
18406
18569
  const agents = config2.agents;
18407
18570
  const agentName = Object.keys(agents)[0];
18408
18571
  console.log(chalk25.green(`\u2713 Agent: ${agentName}`));
@@ -18538,7 +18701,7 @@ var setupGithubCommand = new Command24().name("setup-github").description("Initi
18538
18701
 
18539
18702
  // src/index.ts
18540
18703
  var program = new Command25();
18541
- program.name("vm0").description("VM0 CLI - A modern build tool").version("4.30.0");
18704
+ program.name("vm0").description("VM0 CLI - A modern build tool").version("4.31.0");
18542
18705
  program.command("info").description("Display environment information").action(async () => {
18543
18706
  console.log(chalk26.bold("System Information:"));
18544
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.30.0",
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
  }