@vm0/cli 9.21.0 → 9.22.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 +238 -122
  2. package/package.json +1 -1
package/index.js CHANGED
@@ -3,13 +3,7 @@
3
3
  // src/instrument.ts
4
4
  import * as Sentry from "@sentry/node";
5
5
  import * as os from "os";
6
- var TELEMETRY_DISABLED = process.env.VM0_TELEMETRY === "false";
7
- var IS_CI = Boolean(process.env.CI || process.env.GITHUB_ACTIONS);
8
- var IS_DEV = process.env.NODE_ENV === "development";
9
- var PRODUCTION_API_URL = "https://www.vm0.ai";
10
- var API_URL = process.env.VM0_API_URL ?? "";
11
- var IS_PRODUCTION_API = API_URL === "" || API_URL === PRODUCTION_API_URL;
12
- var DSN = "https://268d9b4cd051531805af76a5b3934dca@o4510583739777024.ingest.us.sentry.io/4510832047947776";
6
+ var DSN = process.env.SENTRY_DSN ?? "https://268d9b4cd051531805af76a5b3934dca@o4510583739777024.ingest.us.sentry.io/4510832047947776";
13
7
  var OPERATIONAL_ERROR_PATTERNS = [
14
8
  // Authentication errors (user needs to login)
15
9
  /not authenticated/i,
@@ -46,7 +40,7 @@ function isOperationalError(error) {
46
40
  const message = error.message;
47
41
  return OPERATIONAL_ERROR_PATTERNS.some((pattern) => pattern.test(message));
48
42
  }
49
- if (!TELEMETRY_DISABLED && !IS_CI && !IS_DEV && IS_PRODUCTION_API) {
43
+ if (DSN) {
50
44
  Sentry.init({
51
45
  dsn: DSN,
52
46
  sendDefaultPii: false,
@@ -67,7 +61,7 @@ if (!TELEMETRY_DISABLED && !IS_CI && !IS_DEV && IS_PRODUCTION_API) {
67
61
  }
68
62
  });
69
63
  Sentry.setContext("cli", {
70
- version: "9.21.0",
64
+ version: "9.22.0",
71
65
  command: process.argv.slice(2).join(" ")
72
66
  });
73
67
  Sentry.setContext("runtime", {
@@ -2323,7 +2317,7 @@ var MODEL_PROVIDER_TYPES = {
2323
2317
  "aws-bedrock": {
2324
2318
  framework: "claude-code",
2325
2319
  label: "AWS Bedrock",
2326
- helpText: "Run Claude on AWS Bedrock.\nSetup guide: https://docs.anthropic.com/en/docs/claude-code/bedrock",
2320
+ helpText: "Run Claude on AWS Bedrock.\nSetup guide: https://code.claude.com/docs/en/amazon-bedrock",
2327
2321
  authMethods: {
2328
2322
  "api-key": {
2329
2323
  label: "Bedrock API Key",
@@ -5362,9 +5356,15 @@ function getSecretsFromComposeContent(content) {
5362
5356
  const grouped = groupVariablesBySource(refs);
5363
5357
  return new Set(grouped.secrets.map((r) => r.name));
5364
5358
  }
5365
- async function loadAndValidateConfig(configFile) {
5359
+ async function loadAndValidateConfig(configFile, porcelainMode) {
5366
5360
  if (!existsSync4(configFile)) {
5367
- console.error(chalk4.red(`\u2717 Config file not found: ${configFile}`));
5361
+ if (porcelainMode) {
5362
+ console.log(
5363
+ JSON.stringify({ error: `Config file not found: ${configFile}` })
5364
+ );
5365
+ } else {
5366
+ console.error(chalk4.red(`\u2717 Config file not found: ${configFile}`));
5367
+ }
5368
5368
  process.exit(1);
5369
5369
  }
5370
5370
  const content = await readFile4(configFile, "utf8");
@@ -5372,15 +5372,22 @@ async function loadAndValidateConfig(configFile) {
5372
5372
  try {
5373
5373
  config = parseYaml2(content);
5374
5374
  } catch (error) {
5375
- console.error(chalk4.red("\u2717 Invalid YAML format"));
5376
- if (error instanceof Error) {
5377
- console.error(chalk4.dim(` ${error.message}`));
5375
+ const message = error instanceof Error ? error.message : "Unknown error";
5376
+ if (porcelainMode) {
5377
+ console.log(JSON.stringify({ error: `Invalid YAML format: ${message}` }));
5378
+ } else {
5379
+ console.error(chalk4.red("\u2717 Invalid YAML format"));
5380
+ console.error(chalk4.dim(` ${message}`));
5378
5381
  }
5379
5382
  process.exit(1);
5380
5383
  }
5381
5384
  const validation = validateAgentCompose(config);
5382
5385
  if (!validation.valid) {
5383
- console.error(chalk4.red(`\u2717 ${validation.error}`));
5386
+ if (porcelainMode) {
5387
+ console.log(JSON.stringify({ error: validation.error }));
5388
+ } else {
5389
+ console.error(chalk4.red(`\u2717 ${validation.error}`));
5390
+ }
5384
5391
  process.exit(1);
5385
5392
  }
5386
5393
  const cfg = config;
@@ -5416,33 +5423,43 @@ function checkLegacyImageFormat(config) {
5416
5423
  }
5417
5424
  }
5418
5425
  }
5419
- async function uploadAssets(agentName, agent, basePath) {
5426
+ async function uploadAssets(agentName, agent, basePath, porcelainMode) {
5420
5427
  if (agent.instructions) {
5421
- console.log(`Uploading instructions: ${agent.instructions}`);
5428
+ if (!porcelainMode) {
5429
+ console.log(`Uploading instructions: ${agent.instructions}`);
5430
+ }
5422
5431
  const result = await uploadInstructions(
5423
5432
  agentName,
5424
5433
  agent.instructions,
5425
5434
  basePath,
5426
5435
  agent.framework
5427
5436
  );
5428
- console.log(
5429
- chalk4.green(
5430
- `\u2713 Instructions ${result.action === "deduplicated" ? "(unchanged)" : "uploaded"}: ${result.versionId.slice(0, 8)}`
5431
- )
5432
- );
5437
+ if (!porcelainMode) {
5438
+ console.log(
5439
+ chalk4.green(
5440
+ `\u2713 Instructions ${result.action === "deduplicated" ? "(unchanged)" : "uploaded"}: ${result.versionId.slice(0, 8)}`
5441
+ )
5442
+ );
5443
+ }
5433
5444
  }
5434
5445
  const skillResults = [];
5435
5446
  if (agent.skills && Array.isArray(agent.skills)) {
5436
- console.log(`Uploading ${agent.skills.length} skill(s)...`);
5447
+ if (!porcelainMode) {
5448
+ console.log(`Uploading ${agent.skills.length} skill(s)...`);
5449
+ }
5437
5450
  for (const skillUrl of agent.skills) {
5438
- console.log(chalk4.dim(` Downloading: ${skillUrl}`));
5451
+ if (!porcelainMode) {
5452
+ console.log(chalk4.dim(` Downloading: ${skillUrl}`));
5453
+ }
5439
5454
  const result = await uploadSkill(skillUrl);
5440
5455
  skillResults.push(result);
5441
- console.log(
5442
- chalk4.green(
5443
- ` \u2713 Skill ${result.action === "deduplicated" ? "(unchanged)" : "uploaded"}: ${result.skillName} (${result.versionId.slice(0, 8)})`
5444
- )
5445
- );
5456
+ if (!porcelainMode) {
5457
+ console.log(
5458
+ chalk4.green(
5459
+ ` \u2713 Skill ${result.action === "deduplicated" ? "(unchanged)" : "uploaded"}: ${result.skillName} (${result.versionId.slice(0, 8)})`
5460
+ )
5461
+ );
5462
+ }
5446
5463
  }
5447
5464
  }
5448
5465
  return skillResults;
@@ -5488,36 +5505,48 @@ async function displayAndConfirmVariables(variables, options) {
5488
5505
  if (newSecrets.length === 0 && newVars.length === 0) {
5489
5506
  return true;
5490
5507
  }
5491
- console.log();
5492
- console.log(
5493
- chalk4.bold("Skills require the following environment variables:")
5494
- );
5495
- console.log();
5496
- if (newSecrets.length > 0) {
5497
- console.log(chalk4.cyan(" Secrets:"));
5498
- for (const [name, skills] of newSecrets) {
5499
- const isNew = trulyNewSecrets.includes(name);
5500
- const newMarker = isNew ? chalk4.yellow(" (new)") : "";
5501
- console.log(` ${name.padEnd(24)}${newMarker} <- ${skills.join(", ")}`);
5508
+ if (!options.porcelain) {
5509
+ console.log();
5510
+ console.log(
5511
+ chalk4.bold("Skills require the following environment variables:")
5512
+ );
5513
+ console.log();
5514
+ if (newSecrets.length > 0) {
5515
+ console.log(chalk4.cyan(" Secrets:"));
5516
+ for (const [name, skills] of newSecrets) {
5517
+ const isNew = trulyNewSecrets.includes(name);
5518
+ const newMarker = isNew ? chalk4.yellow(" (new)") : "";
5519
+ console.log(
5520
+ ` ${name.padEnd(24)}${newMarker} <- ${skills.join(", ")}`
5521
+ );
5522
+ }
5502
5523
  }
5503
- }
5504
- if (newVars.length > 0) {
5505
- console.log(chalk4.cyan(" Vars:"));
5506
- for (const [name, skills] of newVars) {
5507
- console.log(` ${name.padEnd(24)} <- ${skills.join(", ")}`);
5524
+ if (newVars.length > 0) {
5525
+ console.log(chalk4.cyan(" Vars:"));
5526
+ for (const [name, skills] of newVars) {
5527
+ console.log(` ${name.padEnd(24)} <- ${skills.join(", ")}`);
5528
+ }
5508
5529
  }
5530
+ console.log();
5509
5531
  }
5510
- console.log();
5511
5532
  if (trulyNewSecrets.length > 0 && !options.yes) {
5512
5533
  if (!isInteractive()) {
5513
- console.error(
5514
- chalk4.red(`\u2717 New secrets detected: ${trulyNewSecrets.join(", ")}`)
5515
- );
5516
- console.error(
5517
- chalk4.dim(
5518
- " Use --yes flag to approve new secrets in non-interactive mode."
5519
- )
5520
- );
5534
+ if (options.porcelain) {
5535
+ console.log(
5536
+ JSON.stringify({
5537
+ error: `New secrets detected: ${trulyNewSecrets.join(", ")}. Use --yes flag to approve.`
5538
+ })
5539
+ );
5540
+ } else {
5541
+ console.error(
5542
+ chalk4.red(`\u2717 New secrets detected: ${trulyNewSecrets.join(", ")}`)
5543
+ );
5544
+ console.error(
5545
+ chalk4.dim(
5546
+ " Use --yes flag to approve new secrets in non-interactive mode."
5547
+ )
5548
+ );
5549
+ }
5521
5550
  process.exit(1);
5522
5551
  }
5523
5552
  const confirmed = await promptConfirm(
@@ -5525,7 +5554,9 @@ async function displayAndConfirmVariables(variables, options) {
5525
5554
  true
5526
5555
  );
5527
5556
  if (!confirmed) {
5528
- console.log(chalk4.yellow("Compose cancelled"));
5557
+ if (!options.porcelain) {
5558
+ console.log(chalk4.yellow("Compose cancelled"));
5559
+ }
5529
5560
  return false;
5530
5561
  }
5531
5562
  }
@@ -5553,57 +5584,94 @@ async function finalizeCompose(config, agent, variables, options) {
5553
5584
  process.exit(0);
5554
5585
  }
5555
5586
  mergeSkillVariables(agent, variables);
5556
- console.log("Uploading compose...");
5587
+ if (!options.porcelain) {
5588
+ console.log("Uploading compose...");
5589
+ }
5557
5590
  const response = await createOrUpdateCompose({ content: config });
5558
5591
  const scopeResponse = await getScope();
5559
5592
  const shortVersionId = response.versionId.slice(0, 8);
5560
5593
  const displayName = `${scopeResponse.slug}/${response.name}`;
5561
- if (response.action === "created") {
5562
- console.log(chalk4.green(`\u2713 Compose created: ${displayName}`));
5563
- } else {
5564
- console.log(chalk4.green(`\u2713 Compose version exists: ${displayName}`));
5594
+ const result = {
5595
+ composeId: response.composeId,
5596
+ composeName: response.name,
5597
+ versionId: response.versionId,
5598
+ action: response.action,
5599
+ displayName
5600
+ };
5601
+ if (!options.porcelain) {
5602
+ if (response.action === "created") {
5603
+ console.log(chalk4.green(`\u2713 Compose created: ${displayName}`));
5604
+ } else {
5605
+ console.log(chalk4.green(`\u2713 Compose version exists: ${displayName}`));
5606
+ }
5607
+ console.log(chalk4.dim(` Version: ${shortVersionId}`));
5608
+ console.log();
5609
+ console.log(" Run your agent:");
5610
+ console.log(
5611
+ chalk4.cyan(
5612
+ ` vm0 run ${displayName}:${shortVersionId} --artifact-name <artifact> "your prompt"`
5613
+ )
5614
+ );
5565
5615
  }
5566
- console.log(chalk4.dim(` Version: ${shortVersionId}`));
5567
- console.log();
5568
- console.log(" Run your agent:");
5569
- console.log(
5570
- chalk4.cyan(
5571
- ` vm0 run ${displayName}:${shortVersionId} --artifact-name <artifact> "your prompt"`
5572
- )
5573
- );
5574
5616
  if (options.autoUpdate !== false) {
5575
- await silentUpgradeAfterCommand("9.21.0");
5617
+ await silentUpgradeAfterCommand("9.22.0");
5576
5618
  }
5619
+ return result;
5577
5620
  }
5578
5621
  async function handleGitHubCompose(url, options) {
5579
- console.log(`Downloading from GitHub: ${url}`);
5622
+ if (!options.porcelain) {
5623
+ console.log(`Downloading from GitHub: ${url}`);
5624
+ }
5580
5625
  const { dir: downloadedDir, tempRoot } = await downloadGitHubDirectory(url);
5581
5626
  const configFile = join6(downloadedDir, "vm0.yaml");
5582
5627
  try {
5583
5628
  if (!existsSync4(configFile)) {
5584
- console.error(chalk4.red(`\u2717 vm0.yaml not found in the GitHub directory`));
5585
- console.error(chalk4.dim(` URL: ${url}`));
5629
+ if (options.porcelain) {
5630
+ console.log(
5631
+ JSON.stringify({
5632
+ error: "vm0.yaml not found in the GitHub directory"
5633
+ })
5634
+ );
5635
+ } else {
5636
+ console.error(
5637
+ chalk4.red(`\u2717 vm0.yaml not found in the GitHub directory`)
5638
+ );
5639
+ console.error(chalk4.dim(` URL: ${url}`));
5640
+ }
5586
5641
  process.exit(1);
5587
5642
  }
5588
- const { config, agentName, agent, basePath } = await loadAndValidateConfig(configFile);
5643
+ const { config, agentName, agent, basePath } = await loadAndValidateConfig(
5644
+ configFile,
5645
+ options.porcelain
5646
+ );
5589
5647
  const existingCompose = await getComposeByName(agentName);
5590
5648
  if (existingCompose) {
5591
- console.log();
5592
- console.log(
5593
- chalk4.yellow(`\u26A0 An agent named "${agentName}" already exists.`)
5594
- );
5649
+ if (!options.porcelain) {
5650
+ console.log();
5651
+ console.log(
5652
+ chalk4.yellow(`\u26A0 An agent named "${agentName}" already exists.`)
5653
+ );
5654
+ }
5595
5655
  if (!isInteractive()) {
5596
5656
  if (!options.yes) {
5597
- console.error(
5598
- chalk4.red(
5599
- `\u2717 Cannot overwrite existing agent in non-interactive mode`
5600
- )
5601
- );
5602
- console.error(
5603
- chalk4.dim(
5604
- ` Use --yes flag to confirm overwriting the existing agent.`
5605
- )
5606
- );
5657
+ if (options.porcelain) {
5658
+ console.log(
5659
+ JSON.stringify({
5660
+ error: "Cannot overwrite existing agent in non-interactive mode"
5661
+ })
5662
+ );
5663
+ } else {
5664
+ console.error(
5665
+ chalk4.red(
5666
+ `\u2717 Cannot overwrite existing agent in non-interactive mode`
5667
+ )
5668
+ );
5669
+ console.error(
5670
+ chalk4.dim(
5671
+ ` Use --yes flag to confirm overwriting the existing agent.`
5672
+ )
5673
+ );
5674
+ }
5607
5675
  process.exit(1);
5608
5676
  }
5609
5677
  } else {
@@ -5612,31 +5680,48 @@ async function handleGitHubCompose(url, options) {
5612
5680
  false
5613
5681
  );
5614
5682
  if (!confirmed) {
5615
- console.log(chalk4.yellow("Compose cancelled."));
5683
+ if (!options.porcelain) {
5684
+ console.log(chalk4.yellow("Compose cancelled."));
5685
+ }
5616
5686
  process.exit(0);
5617
5687
  }
5618
5688
  }
5619
5689
  }
5620
5690
  if (hasVolumes(config)) {
5621
- console.error(
5622
- chalk4.red(`\u2717 Volumes are not supported for GitHub URL compose`)
5623
- );
5624
- console.error(
5625
- chalk4.dim(
5626
- ` Clone the repository locally and run: vm0 compose ./path/to/vm0.yaml`
5627
- )
5628
- );
5691
+ if (options.porcelain) {
5692
+ console.log(
5693
+ JSON.stringify({
5694
+ error: "Volumes are not supported for GitHub URL compose"
5695
+ })
5696
+ );
5697
+ } else {
5698
+ console.error(
5699
+ chalk4.red(`\u2717 Volumes are not supported for GitHub URL compose`)
5700
+ );
5701
+ console.error(
5702
+ chalk4.dim(
5703
+ ` Clone the repository locally and run: vm0 compose ./path/to/vm0.yaml`
5704
+ )
5705
+ );
5706
+ }
5629
5707
  process.exit(1);
5630
5708
  }
5631
- checkLegacyImageFormat(config);
5632
- const skillResults = await uploadAssets(agentName, agent, basePath);
5709
+ if (!options.porcelain) {
5710
+ checkLegacyImageFormat(config);
5711
+ }
5712
+ const skillResults = await uploadAssets(
5713
+ agentName,
5714
+ agent,
5715
+ basePath,
5716
+ options.porcelain
5717
+ );
5633
5718
  const environment = agent.environment || {};
5634
5719
  const variables = await collectSkillVariables(
5635
5720
  skillResults,
5636
5721
  environment,
5637
5722
  agentName
5638
5723
  );
5639
- await finalizeCompose(config, agent, variables, options);
5724
+ return await finalizeCompose(config, agent, variables, options);
5640
5725
  } finally {
5641
5726
  await rm3(tempRoot, { recursive: true, force: true });
5642
5727
  }
@@ -5647,42 +5732,73 @@ var composeCommand = new Command7().name("compose").description("Create or updat
5647
5732
  ).option("-y, --yes", "Skip confirmation prompts for skill requirements").option(
5648
5733
  "--experimental-shared-compose",
5649
5734
  "Enable GitHub URL compose (experimental)"
5735
+ ).option(
5736
+ "--porcelain",
5737
+ "Output stable JSON for scripts (suppresses interactive output)"
5650
5738
  ).addOption(new Option("--no-auto-update").hideHelp()).action(
5651
5739
  async (configFile, options) => {
5652
5740
  const resolvedConfigFile = configFile ?? DEFAULT_CONFIG_FILE;
5741
+ if (options.porcelain) {
5742
+ options.yes = true;
5743
+ options.autoUpdate = false;
5744
+ }
5653
5745
  try {
5746
+ let result;
5654
5747
  if (isGitHubUrl(resolvedConfigFile)) {
5655
5748
  if (!options.experimentalSharedCompose) {
5656
- console.error(
5657
- chalk4.red(
5658
- "\u2717 Composing shared agents requires --experimental-shared-compose flag"
5659
- )
5660
- );
5661
- console.error();
5662
- console.error(
5663
- chalk4.dim(
5664
- " Composing agents from other users carries security risks."
5665
- )
5666
- );
5667
- console.error(
5668
- chalk4.dim(" Only compose agents from users you trust.")
5669
- );
5749
+ if (options.porcelain) {
5750
+ console.log(
5751
+ JSON.stringify({
5752
+ error: "Composing shared agents requires --experimental-shared-compose flag"
5753
+ })
5754
+ );
5755
+ } else {
5756
+ console.error(
5757
+ chalk4.red(
5758
+ "\u2717 Composing shared agents requires --experimental-shared-compose flag"
5759
+ )
5760
+ );
5761
+ console.error();
5762
+ console.error(
5763
+ chalk4.dim(
5764
+ " Composing agents from other users carries security risks."
5765
+ )
5766
+ );
5767
+ console.error(
5768
+ chalk4.dim(" Only compose agents from users you trust.")
5769
+ );
5770
+ }
5670
5771
  process.exit(1);
5671
5772
  }
5672
- await handleGitHubCompose(resolvedConfigFile, options);
5773
+ result = await handleGitHubCompose(resolvedConfigFile, options);
5673
5774
  } else {
5674
- const { config, agentName, agent, basePath } = await loadAndValidateConfig(resolvedConfigFile);
5675
- checkLegacyImageFormat(config);
5676
- const skillResults = await uploadAssets(agentName, agent, basePath);
5775
+ const { config, agentName, agent, basePath } = await loadAndValidateConfig(resolvedConfigFile, options.porcelain);
5776
+ if (!options.porcelain) {
5777
+ checkLegacyImageFormat(config);
5778
+ }
5779
+ const skillResults = await uploadAssets(
5780
+ agentName,
5781
+ agent,
5782
+ basePath,
5783
+ options.porcelain
5784
+ );
5677
5785
  const environment = agent.environment || {};
5678
5786
  const variables = await collectSkillVariables(
5679
5787
  skillResults,
5680
5788
  environment,
5681
5789
  agentName
5682
5790
  );
5683
- await finalizeCompose(config, agent, variables, options);
5791
+ result = await finalizeCompose(config, agent, variables, options);
5792
+ }
5793
+ if (options.porcelain) {
5794
+ console.log(JSON.stringify(result));
5684
5795
  }
5685
5796
  } catch (error) {
5797
+ if (options.porcelain) {
5798
+ const message = error instanceof Error ? error.message : "An unexpected error occurred";
5799
+ console.log(JSON.stringify({ error: message }));
5800
+ process.exit(1);
5801
+ }
5686
5802
  if (error instanceof Error) {
5687
5803
  if (error.message.includes("Not authenticated")) {
5688
5804
  console.error(
@@ -7940,7 +8056,7 @@ var mainRunCommand = new Command8().name("run").description("Run an agent").argu
7940
8056
  }
7941
8057
  showNextSteps(result);
7942
8058
  if (options.autoUpdate !== false) {
7943
- await silentUpgradeAfterCommand("9.21.0");
8059
+ await silentUpgradeAfterCommand("9.22.0");
7944
8060
  }
7945
8061
  } catch (error) {
7946
8062
  handleRunError(error, identifier);
@@ -9447,7 +9563,7 @@ var cookAction = new Command27().name("cook").description("Quick start: prepare,
9447
9563
  ).option("-y, --yes", "Skip confirmation prompts").option("-v, --verbose", "Show full tool inputs and outputs").addOption(new Option5("--debug-no-mock-claude").hideHelp()).addOption(new Option5("--no-auto-update").hideHelp()).action(
9448
9564
  async (prompt, options) => {
9449
9565
  if (options.autoUpdate !== false) {
9450
- const shouldExit = await checkAndUpgrade("9.21.0", prompt);
9566
+ const shouldExit = await checkAndUpgrade("9.22.0", prompt);
9451
9567
  if (shouldExit) {
9452
9568
  process.exit(0);
9453
9569
  }
@@ -13545,7 +13661,7 @@ var setupClaudeCommand = new Command67().name("setup-claude").description("Insta
13545
13661
 
13546
13662
  // src/index.ts
13547
13663
  var program = new Command68();
13548
- program.name("vm0").description("VM0 CLI - Build and run agents with natural language").version("9.21.0");
13664
+ program.name("vm0").description("VM0 CLI - Build and run agents with natural language").version("9.22.0");
13549
13665
  program.addCommand(authCommand);
13550
13666
  program.addCommand(infoCommand);
13551
13667
  program.addCommand(composeCommand);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vm0/cli",
3
- "version": "9.21.0",
3
+ "version": "9.22.0",
4
4
  "description": "CLI application",
5
5
  "repository": {
6
6
  "type": "git",