multiclaw 0.7.0 → 0.8.1

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.
@@ -0,0 +1,28 @@
1
+ export interface PruneOptions {
2
+ config?: string;
3
+ history?: boolean;
4
+ runs?: boolean;
5
+ all?: boolean;
6
+ dryRun?: boolean;
7
+ yes?: boolean;
8
+ }
9
+ export interface PruneItem {
10
+ id: string;
11
+ label: string;
12
+ path: string;
13
+ size: number;
14
+ kind: "history" | "run" | "all-runs";
15
+ }
16
+ /** Scan `.multiclaw/` and list prune-able items with their sizes. */
17
+ export declare function scanPruneItems(workDir: string): PruneItem[];
18
+ /** Delete the selected items. Returns what was removed. */
19
+ export declare function deletePruneItems(items: PruneItem[]): {
20
+ deleted: string[];
21
+ failed: Array<{
22
+ path: string;
23
+ error: string;
24
+ }>;
25
+ };
26
+ /** Resolve non-interactive flags into item ids, or undefined if interactive. */
27
+ export declare function resolvePruneTargets(options: PruneOptions, items: PruneItem[]): string[] | undefined;
28
+ export declare function pruneCommand(options: PruneOptions): Promise<void>;
@@ -0,0 +1 @@
1
+ export {};
@@ -1,4 +1,4 @@
1
- import type { MultiClawConfig, OrchestratorResult } from "@multiclawcli/core";
1
+ import type { OrchestratorResult } from "@multiclawcli/core";
2
2
  /** A previous run's report, as much of it as resume needs. */
3
3
  export interface ResumeTarget {
4
4
  runDir: string;
@@ -28,5 +28,8 @@ export declare function readReport(runDir: string): OrchestratorResult;
28
28
  */
29
29
  export declare function resumeOptionsFromReport(report: OrchestratorResult, requirementOverride?: string): {
30
30
  requirement: string;
31
- resume: NonNullable<MultiClawConfig["resume"]>;
31
+ resume: {
32
+ agents: string[];
33
+ sessions?: Record<string, string>;
34
+ };
32
35
  };
package/dist/index.js CHANGED
@@ -1,8 +1,8 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // src/index.ts
4
- import { readFileSync as readFileSync3 } from "fs";
5
- import { dirname as dirname4, join as join5 } from "path";
4
+ import { readFileSync as readFileSync4 } from "fs";
5
+ import { dirname as dirname4, join as join6 } from "path";
6
6
  import { fileURLToPath as fileURLToPath2 } from "url";
7
7
  import { Command } from "commander";
8
8
 
@@ -468,9 +468,217 @@ async function initCommand() {
468
468
  );
469
469
  }
470
470
 
471
+ // src/commands/prune.ts
472
+ import * as fs3 from "fs";
473
+ import * as path3 from "path";
474
+ import { checkbox as checkbox2, confirm } from "@inquirer/prompts";
475
+ function scanPruneItems(workDir) {
476
+ const items = [];
477
+ const multiclawDir = path3.join(workDir, ".multiclaw");
478
+ const historyFile = path3.join(multiclawDir, "console", "history.json");
479
+ if (fs3.existsSync(historyFile)) {
480
+ const stat = fs3.statSync(historyFile);
481
+ const raw = fs3.readFileSync(historyFile, "utf-8");
482
+ let turns = 0;
483
+ try {
484
+ const parsed = JSON.parse(raw);
485
+ if (Array.isArray(parsed)) turns = parsed.length;
486
+ } catch {
487
+ }
488
+ items.push({
489
+ id: "history",
490
+ label: `Console history (${turns} turn${turns === 1 ? "" : "s"}, ${formatBytes(stat.size)})`,
491
+ path: historyFile,
492
+ size: stat.size,
493
+ kind: "history"
494
+ });
495
+ }
496
+ const runsDir = path3.join(multiclawDir, "runs");
497
+ if (fs3.existsSync(runsDir)) {
498
+ const entries = fs3.readdirSync(runsDir, { withFileTypes: true }).filter((e) => e.isDirectory() && e.name.startsWith("run-")).sort((a, b) => a.name.localeCompare(b.name));
499
+ let totalRunsSize = 0;
500
+ for (const entry of entries) {
501
+ const runPath = path3.join(runsDir, entry.name);
502
+ const size = dirSize(runPath);
503
+ totalRunsSize += size;
504
+ const agentCount = countAgentsInRun(runPath);
505
+ items.push({
506
+ id: entry.name,
507
+ label: `${entry.name} (${agentCount} agent${agentCount === 1 ? "" : "s"}, ${formatBytes(size)})`,
508
+ path: runPath,
509
+ size,
510
+ kind: "run"
511
+ });
512
+ }
513
+ if (entries.length > 1) {
514
+ items.push({
515
+ id: "all-runs",
516
+ label: `All runs (${entries.length} total, ${formatBytes(totalRunsSize)})`,
517
+ path: runsDir,
518
+ size: totalRunsSize,
519
+ kind: "all-runs"
520
+ });
521
+ }
522
+ }
523
+ return items;
524
+ }
525
+ function deletePruneItems(items) {
526
+ const deleted = [];
527
+ const failed = [];
528
+ for (const item of items) {
529
+ try {
530
+ if (item.kind === "history") {
531
+ fs3.unlinkSync(item.path);
532
+ deleted.push(item.id);
533
+ } else if (item.kind === "run") {
534
+ fs3.rmSync(item.path, { recursive: true, force: true });
535
+ deleted.push(item.id);
536
+ } else if (item.kind === "all-runs") {
537
+ const runsDir = item.path;
538
+ const entries = fs3.readdirSync(runsDir, { withFileTypes: true }).filter((e) => e.isDirectory() && e.name.startsWith("run-"));
539
+ for (const entry of entries) {
540
+ fs3.rmSync(path3.join(runsDir, entry.name), { recursive: true, force: true });
541
+ }
542
+ try {
543
+ fs3.rmdirSync(runsDir);
544
+ } catch {
545
+ }
546
+ deleted.push(item.id);
547
+ }
548
+ } catch (err) {
549
+ failed.push({ path: item.path, error: err instanceof Error ? err.message : String(err) });
550
+ }
551
+ }
552
+ return { deleted, failed };
553
+ }
554
+ function resolvePruneTargets(options, items) {
555
+ if (options.all) {
556
+ return items.map((i) => i.id);
557
+ }
558
+ if (options.history && options.runs) {
559
+ return items.map((i) => i.id);
560
+ }
561
+ if (options.history) {
562
+ return items.filter((i) => i.kind === "history").map((i) => i.id);
563
+ }
564
+ if (options.runs) {
565
+ return items.filter((i) => i.kind === "run" || i.kind === "all-runs").map((i) => i.id);
566
+ }
567
+ return void 0;
568
+ }
569
+ async function pruneCommand(options) {
570
+ const config = await loadConfig(options.config);
571
+ const items = scanPruneItems(config.workDir);
572
+ if (items.length === 0) {
573
+ console.log("Nothing to prune \u2014 no history or runs found.");
574
+ return;
575
+ }
576
+ let selectedIds;
577
+ const nonInteractive = resolvePruneTargets(options, items);
578
+ if (nonInteractive !== void 0) {
579
+ selectedIds = nonInteractive;
580
+ } else {
581
+ const choices = items.map((item) => ({
582
+ value: item.id,
583
+ name: item.label
584
+ }));
585
+ selectedIds = await checkbox2({
586
+ message: "Select items to prune (space to select, enter to confirm):",
587
+ choices
588
+ });
589
+ }
590
+ if (selectedIds.length === 0) {
591
+ console.log("No items selected \u2014 nothing pruned.");
592
+ return;
593
+ }
594
+ const selected = items.filter((i) => selectedIds.includes(i.id));
595
+ const totalSize = selected.reduce((sum, i) => sum + i.size, 0);
596
+ if (options.dryRun) {
597
+ console.log("");
598
+ console.log("\x1B[2mDry run \u2014 the following would be deleted:\x1B[0m");
599
+ for (const item of selected) {
600
+ console.log(` \x1B[31m- ${item.label}\x1B[0m`);
601
+ }
602
+ console.log(`
603
+ Total: ${formatBytes(totalSize)}`);
604
+ return;
605
+ }
606
+ if (!options.yes) {
607
+ const ok = await confirm({
608
+ message: `Delete ${selected.length} item${selected.length === 1 ? "" : "s"} (${formatBytes(totalSize)})? This cannot be undone.`,
609
+ default: false
610
+ });
611
+ if (!ok) {
612
+ console.log("Cancelled \u2014 nothing pruned.");
613
+ return;
614
+ }
615
+ }
616
+ const { deleted, failed } = deletePruneItems(selected);
617
+ console.log("");
618
+ if (deleted.length > 0) {
619
+ console.log(
620
+ `\x1B[32m\u2713\x1B[0m Pruned ${deleted.length} item${deleted.length === 1 ? "" : "s"} (${formatBytes(totalSize)} freed)`
621
+ );
622
+ for (const id of deleted) {
623
+ const item = items.find((i) => i.id === id);
624
+ if (item) console.log(` \x1B[2m- ${item.label}\x1B[0m`);
625
+ }
626
+ }
627
+ if (failed.length > 0) {
628
+ console.log("");
629
+ console.log(
630
+ `\x1B[31m\u2717\x1B[0m Failed to delete ${failed.length} item${failed.length === 1 ? "" : "s"}:`
631
+ );
632
+ for (const { path: p, error } of failed) {
633
+ console.log(` \x1B[31m- ${p}: ${error}\x1B[0m`);
634
+ }
635
+ process.exitCode = 1;
636
+ }
637
+ }
638
+ function formatBytes(bytes) {
639
+ if (bytes === 0) return "0 B";
640
+ const k = 1024;
641
+ const sizes = ["B", "KB", "MB", "GB"];
642
+ const i = Math.min(Math.floor(Math.log(bytes) / Math.log(k)), sizes.length - 1);
643
+ return `${(bytes / k ** i).toFixed(i === 0 ? 0 : 1)} ${sizes[i]}`;
644
+ }
645
+ function dirSize(root) {
646
+ let total = 0;
647
+ const stack = [root];
648
+ while (stack.length > 0) {
649
+ const dir = stack.pop();
650
+ if (dir === void 0) continue;
651
+ for (const name of fs3.readdirSync(dir)) {
652
+ const absolute = path3.join(dir, name);
653
+ const stat = fs3.statSync(absolute);
654
+ if (stat.isDirectory()) {
655
+ stack.push(absolute);
656
+ } else {
657
+ total += stat.size;
658
+ }
659
+ }
660
+ }
661
+ return total;
662
+ }
663
+ function countAgentsInRun(runDir) {
664
+ const logsDir = path3.join(runDir, "logs");
665
+ if (!fs3.existsSync(logsDir)) return 0;
666
+ const reportPath = path3.join(logsDir, "report.json");
667
+ if (!fs3.existsSync(reportPath)) {
668
+ return fs3.readdirSync(logsDir).filter((n) => n.endsWith(".log")).length;
669
+ }
670
+ try {
671
+ const raw = fs3.readFileSync(reportPath, "utf-8");
672
+ const report = JSON.parse(raw);
673
+ return report.agentResults?.length ?? 0;
674
+ } catch {
675
+ return fs3.readdirSync(logsDir).filter((n) => n.endsWith(".log")).length;
676
+ }
677
+ }
678
+
471
679
  // src/commands/run.ts
472
- import * as fs5 from "fs";
473
- import * as path5 from "path";
680
+ import * as fs6 from "fs";
681
+ import * as path6 from "path";
474
682
  import {
475
683
  describeResumePlan,
476
684
  Orchestrator as OrchestratorImpl
@@ -669,8 +877,8 @@ ${C.bold}${C.purple}${edge[0]}${line}${edge[3]}`);
669
877
  };
670
878
 
671
879
  // src/reporters/FileLogWriter.ts
672
- import * as fs3 from "fs";
673
- import * as path3 from "path";
880
+ import * as fs4 from "fs";
881
+ import * as path4 from "path";
674
882
  var FileLogWriter = class {
675
883
  constructor(logDir, onError = (message) => process.stderr.write(`
676
884
  [multiclaw] ${message}
@@ -681,7 +889,7 @@ var FileLogWriter = class {
681
889
  /** Agent ids whose log could not be opened — never retried, so a broken
682
890
  * directory does not produce one failing stream per output chunk. */
683
891
  this.failed = /* @__PURE__ */ new Set();
684
- fs3.mkdirSync(logDir, { recursive: true });
892
+ fs4.mkdirSync(logDir, { recursive: true });
685
893
  }
686
894
  attach(eventBus) {
687
895
  return eventBus.subscribe((e) => this.handle(e));
@@ -705,7 +913,7 @@ var FileLogWriter = class {
705
913
  }
706
914
  if (e.type === "orchestration:complete") {
707
915
  try {
708
- fs3.writeFileSync(path3.join(this.logDir, "report.json"), JSON.stringify(e.payload, null, 2));
916
+ fs4.writeFileSync(path4.join(this.logDir, "report.json"), JSON.stringify(e.payload, null, 2));
709
917
  } catch (err) {
710
918
  this.onError(`Could not write report.json: ${errorMessage(err)}`);
711
919
  }
@@ -716,7 +924,7 @@ var FileLogWriter = class {
716
924
  if (this.failed.has(agentId)) return void 0;
717
925
  let stream = this.streams.get(agentId);
718
926
  if (!stream) {
719
- stream = fs3.createWriteStream(path3.join(this.logDir, `${agentId}.log`));
927
+ stream = fs4.createWriteStream(path4.join(this.logDir, `${agentId}.log`));
720
928
  stream.on("error", (err) => {
721
929
  this.failed.add(agentId);
722
930
  this.streams.delete(agentId);
@@ -747,32 +955,32 @@ function errorMessage(err) {
747
955
  }
748
956
 
749
957
  // src/commands/resume.ts
750
- import * as fs4 from "fs";
751
- import * as path4 from "path";
958
+ import * as fs5 from "fs";
959
+ import * as path5 from "path";
752
960
  function resolveRunDir(runsRoot, target) {
753
961
  if (typeof target !== "string" || target === "latest") {
754
962
  return latestRunDir(runsRoot);
755
963
  }
756
- const resolved = path4.resolve(target);
757
- const runDir = path4.basename(resolved) === "logs" ? path4.dirname(resolved) : resolved;
758
- if (!fs4.existsSync(runDir)) {
964
+ const resolved = path5.resolve(target);
965
+ const runDir = path5.basename(resolved) === "logs" ? path5.dirname(resolved) : resolved;
966
+ if (!fs5.existsSync(runDir)) {
759
967
  throw new Error(`Run directory not found: ${runDir}`);
760
968
  }
761
969
  return runDir;
762
970
  }
763
971
  function latestRunDir(runsRoot) {
764
- const names = fs4.existsSync(runsRoot) ? fs4.readdirSync(runsRoot, { withFileTypes: true }).filter((entry) => entry.isDirectory() && entry.name.startsWith("run-")).map((entry) => entry.name).sort() : [];
972
+ const names = fs5.existsSync(runsRoot) ? fs5.readdirSync(runsRoot, { withFileTypes: true }).filter((entry) => entry.isDirectory() && entry.name.startsWith("run-")).map((entry) => entry.name).sort() : [];
765
973
  const latest = names[names.length - 1];
766
974
  if (!latest) {
767
975
  throw new Error(`No runs found to resume \u2014 looked in ${runsRoot}`);
768
976
  }
769
- return path4.join(runsRoot, latest);
977
+ return path5.join(runsRoot, latest);
770
978
  }
771
979
  function readReport(runDir) {
772
- const reportPath = path4.join(runDir, "logs", "report.json");
980
+ const reportPath = path5.join(runDir, "logs", "report.json");
773
981
  let raw;
774
982
  try {
775
- raw = fs4.readFileSync(reportPath, "utf-8");
983
+ raw = fs5.readFileSync(reportPath, "utf-8");
776
984
  } catch {
777
985
  throw new Error(`No report.json in ${runDir} \u2014 it may not be a complete run`);
778
986
  }
@@ -817,14 +1025,14 @@ async function runCommand(requirementArg, options) {
817
1025
  }
818
1026
  const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-").slice(0, 19);
819
1027
  if (options.resume !== void 0) {
820
- const runsRoot = path5.join(config.workDir, ".multiclaw", "runs");
1028
+ const runsRoot = path6.join(config.workDir, ".multiclaw", "runs");
821
1029
  const runDir = resolveRunDir(runsRoot, options.resume);
822
1030
  const report = readReport(runDir);
823
1031
  const { requirement, resume } = resumeOptionsFromReport(report, requirementArg);
824
1032
  config.workDir = runDir;
825
1033
  config.resume = resume;
826
1034
  config.context = { ...config.context, requirement };
827
- config.logDir = path5.join(runDir, `logs-resume-${stamp}`);
1035
+ config.logDir = path6.join(runDir, `logs-resume-${stamp}`);
828
1036
  const plan = describeResumePlan({
829
1037
  agents: resume.agents,
830
1038
  sessions: resume.sessions,
@@ -832,7 +1040,7 @@ async function runCommand(requirementArg, options) {
832
1040
  });
833
1041
  console.log(`\x1B[2mResuming ${runDir} (${resume.agents.length} agents) \u2014 ${plan}\x1B[0m`);
834
1042
  } else {
835
- config.workDir = path5.join(config.workDir, ".multiclaw", "runs", `run-${stamp}`);
1043
+ config.workDir = path6.join(config.workDir, ".multiclaw", "runs", `run-${stamp}`);
836
1044
  }
837
1045
  if (options.dryRun) {
838
1046
  const preview = await new OrchestratorImpl(config).preview();
@@ -848,7 +1056,7 @@ async function runCommand(requirementArg, options) {
848
1056
  event.payload.deliverables = collectDeliverables(config.workDir, before);
849
1057
  });
850
1058
  new ConsoleReporter().attach(orchestrator.eventBus);
851
- const logDir = config.logDir ?? path5.join(config.workDir, "logs");
1059
+ const logDir = config.logDir ?? path6.join(config.workDir, "logs");
852
1060
  const logs = new FileLogWriter(logDir);
853
1061
  logs.attach(orchestrator.eventBus);
854
1062
  console.log(`\x1B[2mLogs: ${logDir}\x1B[0m`);
@@ -906,16 +1114,16 @@ function collectDeliverables(root, before) {
906
1114
  return deliverables.sort((a, b) => a.path.localeCompare(b.path)).slice(0, 20);
907
1115
  }
908
1116
  function walkFiles(root, visit) {
909
- if (!fs5.existsSync(root)) return;
1117
+ if (!fs6.existsSync(root)) return;
910
1118
  const stack = [root];
911
1119
  while (stack.length > 0) {
912
1120
  const dir = stack.pop();
913
- for (const name of fs5.readdirSync(dir)) {
1121
+ for (const name of fs6.readdirSync(dir)) {
914
1122
  if (name === ".multiclaw") continue;
915
- const absolute = path5.join(dir, name);
916
- const stat = fs5.statSync(absolute);
1123
+ const absolute = path6.join(dir, name);
1124
+ const stat = fs6.statSync(absolute);
917
1125
  if (stat.isDirectory()) stack.push(absolute);
918
- else if (stat.isFile()) visit(absolute, path5.relative(root, absolute), stat);
1126
+ else if (stat.isFile()) visit(absolute, path6.relative(root, absolute), stat);
919
1127
  }
920
1128
  }
921
1129
  }
@@ -943,18 +1151,18 @@ async function tuiCommand(options) {
943
1151
 
944
1152
  // src/commands/upgrade.ts
945
1153
  import { execSync } from "child_process";
946
- import { existsSync as existsSync5, readFileSync as readFileSync2 } from "fs";
947
- import { dirname as dirname3, join as join4, resolve as resolve4 } from "path";
1154
+ import { existsSync as existsSync6, readFileSync as readFileSync3 } from "fs";
1155
+ import { dirname as dirname3, join as join5, resolve as resolve4 } from "path";
948
1156
  import { fileURLToPath } from "url";
949
1157
  var PACKAGE_NAME = "multiclaw";
950
1158
  var RED2 = "\x1B[31m";
951
1159
  var GREEN2 = "\x1B[32m";
952
1160
  var RESET2 = "\x1B[0m";
953
1161
  function readManifest(dir) {
954
- const manifest = join4(dir, "package.json");
955
- if (!existsSync5(manifest)) return void 0;
1162
+ const manifest = join5(dir, "package.json");
1163
+ if (!existsSync6(manifest)) return void 0;
956
1164
  try {
957
- return JSON.parse(readFileSync2(manifest, "utf8"));
1165
+ return JSON.parse(readFileSync3(manifest, "utf8"));
958
1166
  } catch {
959
1167
  return void 0;
960
1168
  }
@@ -972,11 +1180,11 @@ function readInstalledVersion(pkgRoot) {
972
1180
  return readManifest(pkgRoot)?.version;
973
1181
  }
974
1182
  function detectInstaller(pkgRoot) {
975
- const path6 = pkgRoot.replaceAll("\\", "/");
976
- if (path6.includes("/pnpm/global/") || path6.includes("/.pnpm/")) return "pnpm";
977
- if (path6.includes("/install/global/") || path6.includes("/.bun/")) return "bun";
978
- if (path6.includes("/yarn/global/")) return "yarn";
979
- if (path6.includes("/node_modules/")) return "npm";
1183
+ const path7 = pkgRoot.replaceAll("\\", "/");
1184
+ if (path7.includes("/pnpm/global/") || path7.includes("/.pnpm/")) return "pnpm";
1185
+ if (path7.includes("/install/global/") || path7.includes("/.bun/")) return "bun";
1186
+ if (path7.includes("/yarn/global/")) return "yarn";
1187
+ if (path7.includes("/node_modules/")) return "npm";
980
1188
  return "unknown";
981
1189
  }
982
1190
  var COMMANDS = {
@@ -1068,7 +1276,7 @@ async function upgradeCommand() {
1068
1276
  // src/index.ts
1069
1277
  var __filename2 = fileURLToPath2(import.meta.url);
1070
1278
  var __dirname2 = dirname4(__filename2);
1071
- var pkg = JSON.parse(readFileSync3(join5(__dirname2, "../package.json"), "utf-8"));
1279
+ var pkg = JSON.parse(readFileSync4(join6(__dirname2, "../package.json"), "utf-8"));
1072
1280
  var program = new Command();
1073
1281
  program.name("multiclaw").description("Multi-agent orchestration CLI").version(pkg.version, "-v, --version", "Display version number").action((options) => tuiCommand(options));
1074
1282
  program.command("init").description("Scaffold a config file (interactive)").action(() => initCommand());
@@ -1078,6 +1286,7 @@ program.command("run [requirement]").description("Run an orchestration headlessl
1078
1286
  ).action((requirement, opts) => runCommand(requirement, opts));
1079
1287
  program.command("upgrade").description("Upgrade multiclaw to the latest version").action(() => upgradeCommand());
1080
1288
  program.command("doctor").description("Check which agent providers are installed and ready").option("--config <path>", "Config file path (default: auto-discover ./multiclaw.config.ts)").action((opts) => doctorCommand(opts.config));
1289
+ program.command("prune").description("Clean up history entries and old run directories (interactive)").option("--config <path>", "Config file path (default: auto-discover ./multiclaw.config.ts)").option("--history", "Prune console history only (non-interactive)").option("--runs", "Prune all runs only (non-interactive)").option("--all", "Prune everything (non-interactive)").option("--dry-run", "Show what would be deleted without deleting").option("--yes", "Skip confirmation prompt").action((opts) => pruneCommand(opts));
1081
1290
  program.parseAsync(process.argv).catch((err) => {
1082
1291
  const message = err instanceof Error ? err.message : String(err);
1083
1292
  console.error(`\x1B[31mError: ${message}\x1B[0m`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "multiclaw",
3
- "version": "0.7.0",
3
+ "version": "0.8.1",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "multiclaw": "dist/index.js"
@@ -30,8 +30,8 @@
30
30
  "@agentclientprotocol/claude-agent-acp": "^0.95.0",
31
31
  "@agentclientprotocol/codex-acp": "^2.2.2",
32
32
  "@inquirer/prompts": "^8.7.4",
33
- "@multiclawcli/core": "0.7.0",
34
- "@multiclawcli/dashboard": "0.7.0",
33
+ "@multiclawcli/core": "0.8.1",
34
+ "@multiclawcli/dashboard": "0.8.1",
35
35
  "commander": "^15.0.0",
36
36
  "jiti": "^2.7.0"
37
37
  },