@apicircle/cli 1.0.6 → 1.0.7

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.
package/dist/bin/cli.cjs CHANGED
@@ -37,10 +37,30 @@ var init_package = __esm({
37
37
  "package.json"() {
38
38
  package_default = {
39
39
  name: "@apicircle/cli",
40
- version: "1.0.6",
40
+ version: "1.0.7",
41
41
  private: false,
42
42
  type: "module",
43
43
  description: "Command-line interface for API Circle Studio. Run mock servers, drive the MCP server, and import OpenAPI / Postman / Insomnia collections from any terminal.",
44
+ keywords: [
45
+ "apicircle",
46
+ "api-circle",
47
+ "cli",
48
+ "command-line",
49
+ "api",
50
+ "api-client",
51
+ "http",
52
+ "http-client",
53
+ "mock-server",
54
+ "mcp",
55
+ "mcp-server",
56
+ "openapi",
57
+ "swagger",
58
+ "postman",
59
+ "insomnia",
60
+ "import",
61
+ "workspace",
62
+ "headless"
63
+ ],
44
64
  license: "SEE LICENSE IN LICENSE",
45
65
  repository: {
46
66
  type: "git",
@@ -246,8 +266,8 @@ async function ensureWorkspace(dir) {
246
266
  ui: {
247
267
  activeRequestId: null,
248
268
  sidebarExpandedSections: [],
249
- themeId: "command-center",
250
- fontId: "cascadia-code",
269
+ themeId: "one-dark-pro",
270
+ fontId: "system-sans",
251
271
  fontSizePercent: import_shared2.FONT_SIZE_PERCENT_DEFAULT
252
272
  },
253
273
  settings: { validateOnSend: true, monacoConsumesWheel: false },
@@ -461,8 +481,8 @@ function buildEmptyState(workspaceId, now, withSample) {
461
481
  ui: {
462
482
  activeRequestId: sample?.id ?? null,
463
483
  sidebarExpandedSections: [],
464
- themeId: "command-center",
465
- fontId: "cascadia-code",
484
+ themeId: "one-dark-pro",
485
+ fontId: "system-sans",
466
486
  fontSizePercent: 100
467
487
  },
468
488
  settings: { validateOnSend: true, monacoConsumesWheel: false },
@@ -551,7 +571,10 @@ var init_mcp = __esm({
551
571
 
552
572
  // src/commands/import.ts
553
573
  function registerImportCommand(program) {
554
- program.command("import").description("Import a spec into a workspace folder").argument("<type>", "Source type: openapi | postman | insomnia | curl").argument("<input>", "Path to a spec file, or `-` to read from stdin").option(
574
+ program.command("import").description("Import a spec into a workspace folder").argument(
575
+ "<type>",
576
+ "Source type: openapi | postman | insomnia | curl | apicircle (the apicircle.folder/v1 envelope produced by `apicircle export folder`)"
577
+ ).argument("<input>", "Path to a spec file, or `-` to read from stdin").option(
555
578
  "--workspace-name <name-or-id>",
556
579
  "Registry workspace name (case-insensitive) or id. Defaults to the active workspace."
557
580
  ).option(
@@ -641,6 +664,40 @@ function registerImportCommand(program) {
641
664
  })
642
665
  );
643
666
  }
667
+ } else if (type === "apicircle") {
668
+ let parsedEnvelope;
669
+ try {
670
+ parsedEnvelope = (0, import_core.parseApicircleFolderExport)(raw);
671
+ } catch (err) {
672
+ process.stderr.write(
673
+ `${import_kleur3.default.red("error")}: ${err instanceof Error ? err.message : String(err)}
674
+ `
675
+ );
676
+ process.exit(2);
677
+ }
678
+ const out = (0, import_core.applyMutation)(
679
+ { synced: nextSynced, local: nextLocal },
680
+ { kind: "folder.import_apicircle", parsed: parsedEnvelope, parentFolderId: null }
681
+ );
682
+ nextSynced = out.next.synced;
683
+ nextLocal = out.next.local;
684
+ for (const r of parsedEnvelope.requests) created.push(r.id);
685
+ for (const w of parsedEnvelope.warnings) {
686
+ process.stderr.write(`${import_kleur3.default.yellow("warning")}: ${w}
687
+ `);
688
+ }
689
+ await (0, import_file_backed3.saveToFile)(dir, { synced: nextSynced, local: nextLocal });
690
+ process.stdout.write(
691
+ `${import_kleur3.default.green("imported")} folder "${parsedEnvelope.rootFolder.name}" (${parsedEnvelope.subfolders.length + 1} folders, ${parsedEnvelope.requests.length} requests) into ${dir}
692
+ `
693
+ );
694
+ if (parsedEnvelope.dependencies.files.length > 0) {
695
+ process.stderr.write(
696
+ `${import_kleur3.default.yellow("note")}: ${parsedEnvelope.dependencies.files.length} file asset${parsedEnvelope.dependencies.files.length === 1 ? "" : "s"} landed without bytes \u2014 re-attach them inside Global Assets \u2192 Global Files.
697
+ `
698
+ );
699
+ }
700
+ return;
644
701
  } else {
645
702
  process.stderr.write(`${import_kleur3.default.red("error")}: unknown type '${String(type)}'
646
703
  `);
@@ -655,13 +712,13 @@ function registerImportCommand(program) {
655
712
  }
656
713
  async function readInput(p) {
657
714
  if (p === "-") {
658
- return new Promise((resolve7, reject) => {
715
+ return new Promise((resolve8, reject) => {
659
716
  let data = "";
660
717
  process.stdin.setEncoding("utf-8");
661
718
  process.stdin.on("data", (chunk) => {
662
719
  data += typeof chunk === "string" ? chunk : chunk.toString("utf-8");
663
720
  });
664
- process.stdin.on("end", () => resolve7(data));
721
+ process.stdin.on("end", () => resolve8(data));
665
722
  process.stdin.on("error", reject);
666
723
  });
667
724
  }
@@ -700,14 +757,120 @@ var init_import = __esm({
700
757
  }
701
758
  });
702
759
 
760
+ // src/commands/export.ts
761
+ function registerExportCommand(program) {
762
+ const exportCmd = program.command("export").description("Export workspace entities to portable JSON.");
763
+ exportCmd.command("folder").description("Export a folder (and its subtree) as an apicircle.folder/v1 JSON envelope.").argument(
764
+ "<folder>",
765
+ "Folder id, or display name (case-insensitive). Unique within the workspace."
766
+ ).option("-o, --out <path>", "Write the JSON to this file. Defaults to stdout.").option(
767
+ "--include-credential <id>",
768
+ "Keep the credential field with this id (repeatable). Use --list-credentials to see ids.",
769
+ (value, prev = []) => [...prev, value],
770
+ []
771
+ ).option(
772
+ "--list-credentials",
773
+ "Print the detected credentials + their ids and exit without writing anything."
774
+ ).option(
775
+ "--workspace-name <name-or-id>",
776
+ "Registry workspace name (case-insensitive) or id. Defaults to the active workspace."
777
+ ).option(
778
+ "-w, --workspace-path <dir>",
779
+ "Filesystem directory containing workspace.synced.json (skips the registry)."
780
+ ).action(async (folder, opts) => {
781
+ let dir;
782
+ try {
783
+ const resolved = await resolveWorkspace({
784
+ name: opts.workspaceName,
785
+ path: opts.workspacePath
786
+ });
787
+ dir = resolved.dir;
788
+ } catch (err) {
789
+ if (err instanceof WorkspaceResolutionError) {
790
+ process.stderr.write(`${import_kleur4.default.red("error")}: ${err.message}
791
+ `);
792
+ process.exit(2);
793
+ }
794
+ throw err;
795
+ }
796
+ const state = await ensureWorkspace(dir);
797
+ const folderId = resolveFolderId(state.synced.collections.folders, folder);
798
+ if (!folderId) {
799
+ process.stderr.write(`${import_kleur4.default.red("error")}: no folder matches "${folder}" in ${dir}
800
+ `);
801
+ process.exit(2);
802
+ }
803
+ const collected = (0, import_core2.collectFolderExport)({ synced: state.synced, folderId });
804
+ if (!collected) {
805
+ process.stderr.write(`${import_kleur4.default.red("error")}: folder "${folder}" no longer exists
806
+ `);
807
+ process.exit(2);
808
+ }
809
+ if (opts.listCredentials) {
810
+ if (collected.report.credentials.length === 0) {
811
+ process.stdout.write("No credential-bearing auth fields detected.\n");
812
+ return;
813
+ }
814
+ for (const cred of collected.report.credentials) {
815
+ process.stdout.write(`${cred.id} ${cred.label} ${cred.ownerName}
816
+ `);
817
+ }
818
+ return;
819
+ }
820
+ const includeIds = new Set(opts.includeCredential ?? []);
821
+ const envelope = (0, import_core2.redactFolderExportCredentials)(collected.envelope, includeIds);
822
+ const json = (0, import_core2.serializeFolderExport)(envelope);
823
+ if (opts.out) {
824
+ const outPath = path5.resolve(opts.out);
825
+ await import_node_fs5.promises.writeFile(outPath, json, "utf-8");
826
+ process.stderr.write(
827
+ `${import_kleur4.default.green("exported")} folder "${collected.report.folderName}" \u2192 ${outPath}
828
+ `
829
+ );
830
+ } else {
831
+ process.stdout.write(json);
832
+ process.stdout.write("\n");
833
+ process.stderr.write(
834
+ `${import_kleur4.default.green("exported")} folder "${collected.report.folderName}" (${collected.report.totalFolderCount} folders, ${collected.report.requestCount} requests, ${collected.report.credentials.length - includeIds.size} credentials redacted)
835
+ `
836
+ );
837
+ }
838
+ if (!opts.out) {
839
+ process.stderr.write(
840
+ `${import_kleur4.default.dim("hint")}: save with .apicircle.json, e.g. ${(0, import_core2.suggestFolderExportFilename)(envelope)}
841
+ `
842
+ );
843
+ }
844
+ });
845
+ }
846
+ function resolveFolderId(folders, query) {
847
+ if (folders[query]) return query;
848
+ const norm = query.trim().toLowerCase();
849
+ const matches = Object.values(folders).filter((f) => f.name.trim().toLowerCase() === norm);
850
+ if (matches.length === 1) return matches[0].id;
851
+ return null;
852
+ }
853
+ var import_node_fs5, path5, import_kleur4, import_core2;
854
+ var init_export = __esm({
855
+ "src/commands/export.ts"() {
856
+ "use strict";
857
+ import_node_fs5 = require("fs");
858
+ path5 = __toESM(require("path"), 1);
859
+ import_kleur4 = __toESM(require("kleur"), 1);
860
+ import_core2 = require("@apicircle/core");
861
+ init_loadWorkspace();
862
+ init_resolveWorkspace();
863
+ }
864
+ });
865
+
703
866
  // src/util/secrets.ts
704
867
  async function buildSecretsFromCli(options = {}) {
705
868
  const env = options.env ?? process.env;
706
869
  const prefix = options.envPrefix ?? DEFAULT_PREFIX;
707
870
  const byId = {};
708
871
  if (options.secretsFile) {
709
- const resolved = path5.resolve(options.secretsFile);
710
- const raw = await import_node_fs5.promises.readFile(resolved, "utf8");
872
+ const resolved = path6.resolve(options.secretsFile);
873
+ const raw = await import_node_fs6.promises.readFile(resolved, "utf8");
711
874
  const parsed = JSON.parse(raw);
712
875
  if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
713
876
  throw new Error(
@@ -728,21 +891,21 @@ async function buildSecretsFromCli(options = {}) {
728
891
  }
729
892
  return { byId };
730
893
  }
731
- var path5, import_node_fs5, DEFAULT_PREFIX;
894
+ var path6, import_node_fs6, DEFAULT_PREFIX;
732
895
  var init_secrets = __esm({
733
896
  "src/util/secrets.ts"() {
734
897
  "use strict";
735
- path5 = __toESM(require("path"), 1);
736
- import_node_fs5 = require("fs");
898
+ path6 = __toESM(require("path"), 1);
899
+ import_node_fs6 = require("fs");
737
900
  DEFAULT_PREFIX = "APICIRCLE_SECRET_";
738
901
  }
739
902
  });
740
903
 
741
904
  // src/util/executionAttachments.ts
742
905
  async function prepareExecutionAttachments(workspaceDir, state, plan) {
743
- const cacheDir = path6.resolve(workspaceDir, ATTACHMENTS_DIR);
906
+ const cacheDir = path7.resolve(workspaceDir, ATTACHMENTS_DIR);
744
907
  const requirements = collectExecutionAttachmentRequirements(state, plan);
745
- await import_node_fs6.promises.mkdir(cacheDir, { recursive: true });
908
+ await import_node_fs7.promises.mkdir(cacheDir, { recursive: true });
746
909
  let downloaded = 0;
747
910
  let alreadyPresent = 0;
748
911
  let failed = 0;
@@ -751,7 +914,7 @@ async function prepareExecutionAttachments(workspaceDir, state, plan) {
751
914
  };
752
915
  const entries = [];
753
916
  for (const requirement of requirements) {
754
- const localPath = path6.join(cacheDir, encodeURIComponent(requirement.slotId));
917
+ const localPath = path7.join(cacheDir, encodeURIComponent(requirement.slotId));
755
918
  const present = await hasExpectedFile(localPath, requirement.sha256);
756
919
  if (present) {
757
920
  alreadyPresent++;
@@ -768,7 +931,7 @@ async function prepareExecutionAttachments(workspaceDir, state, plan) {
768
931
  `Attachment ${attachmentLabel(requirement)} failed checksum verification.`
769
932
  );
770
933
  }
771
- await import_node_fs6.promises.writeFile(localPath, bytes, { mode: 384 });
934
+ await import_node_fs7.promises.writeFile(localPath, bytes, { mode: 384 });
772
935
  downloaded++;
773
936
  } catch (err) {
774
937
  failed++;
@@ -825,7 +988,7 @@ function createFileAttachmentResolver(state) {
825
988
  return async (slotId) => {
826
989
  const meta = state.local.attachmentCache?.[slotId];
827
990
  if (!meta) return null;
828
- const bytes = await import_node_fs6.promises.readFile(meta.localPath);
991
+ const bytes = await import_node_fs7.promises.readFile(meta.localPath);
829
992
  const view = new Uint8Array(bytes);
830
993
  const body = view.buffer.slice(view.byteOffset, view.byteOffset + view.byteLength);
831
994
  return {
@@ -845,7 +1008,7 @@ function collectExecutionAttachmentRequirements(state, plan) {
845
1008
  )
846
1009
  )
847
1010
  } : state.synced.collections;
848
- const workspaceSlots = (0, import_core2.collectAttachmentSlots)({ ...state.synced, collections: localCollections });
1011
+ const workspaceSlots = (0, import_core3.collectAttachmentSlots)({ ...state.synced, collections: localCollections });
849
1012
  for (const slot of workspaceSlots) {
850
1013
  const requiredBy = collectRequiredBy(localCollections.requests, slot.slotId);
851
1014
  if (requiredBy.length === 0) continue;
@@ -877,7 +1040,7 @@ function collectExecutionAttachmentRequirements(state, plan) {
877
1040
  environments: snapshot.environments,
878
1041
  globalAssets: snapshot.globalAssets ?? state.synced.globalAssets
879
1042
  };
880
- for (const slot of (0, import_core2.collectAttachmentSlots)(linkedSynced)) {
1043
+ for (const slot of (0, import_core3.collectAttachmentSlots)(linkedSynced)) {
881
1044
  const requiredBy = collectRequiredBy(linkedCollections.requests, slot.slotId);
882
1045
  if (requiredBy.length === 0) continue;
883
1046
  addRequirement(seen, {
@@ -930,7 +1093,7 @@ function bodyReferencesSlot(body, slotId) {
930
1093
  }
931
1094
  async function hasExpectedFile(localPath, sha256) {
932
1095
  try {
933
- const bytes = await import_node_fs6.promises.readFile(localPath);
1096
+ const bytes = await import_node_fs7.promises.readFile(localPath);
934
1097
  if (!sha256) return true;
935
1098
  return sha256Hex(bytes) === sha256;
936
1099
  } catch {
@@ -939,7 +1102,7 @@ async function hasExpectedFile(localPath, sha256) {
939
1102
  }
940
1103
  async function fileSize(localPath) {
941
1104
  try {
942
- return (await import_node_fs6.promises.stat(localPath)).size;
1105
+ return (await import_node_fs7.promises.stat(localPath)).size;
943
1106
  } catch {
944
1107
  return 0;
945
1108
  }
@@ -998,15 +1161,15 @@ function sourceLabel(requirement) {
998
1161
  function requiredByLabel(requirement) {
999
1162
  return requirement.requiredBy.map((item) => item.requestName).join(", ") || "a request";
1000
1163
  }
1001
- var import_node_crypto, import_node_fs6, path6, import_core2, ATTACHMENTS_DIR;
1164
+ var import_node_crypto, import_node_fs7, path7, import_core3, ATTACHMENTS_DIR;
1002
1165
  var init_executionAttachments = __esm({
1003
1166
  "src/util/executionAttachments.ts"() {
1004
1167
  "use strict";
1005
1168
  import_node_crypto = require("crypto");
1006
- import_node_fs6 = require("fs");
1007
- path6 = __toESM(require("path"), 1);
1008
- import_core2 = require("@apicircle/core");
1009
- ATTACHMENTS_DIR = path6.join(".apicircle", "attachments");
1169
+ import_node_fs7 = require("fs");
1170
+ path7 = __toESM(require("path"), 1);
1171
+ import_core3 = require("@apicircle/core");
1172
+ ATTACHMENTS_DIR = path7.join(".apicircle", "attachments");
1010
1173
  }
1011
1174
  });
1012
1175
 
@@ -1029,7 +1192,7 @@ function registerRunCommand(program) {
1029
1192
  dir = resolved.dir;
1030
1193
  if (resolved.fromRegistry) {
1031
1194
  process.stderr.write(
1032
- `${import_kleur4.default.dim("workspace")}: ${import_kleur4.default.cyan(resolved.name ?? resolved.id ?? "")} ${import_kleur4.default.dim(`(${dir})`)}
1195
+ `${import_kleur5.default.dim("workspace")}: ${import_kleur5.default.cyan(resolved.name ?? resolved.id ?? "")} ${import_kleur5.default.dim(`(${dir})`)}
1033
1196
  `
1034
1197
  );
1035
1198
  }
@@ -1050,7 +1213,7 @@ function registerRunCommand(program) {
1050
1213
  fail(`no workspace found at ${dir} (expected workspace.synced.json)`);
1051
1214
  return;
1052
1215
  }
1053
- const ref = (0, import_core3.resolvePlanRef)(state.synced, planRef);
1216
+ const ref = (0, import_core4.resolvePlanRef)(state.synced, planRef);
1054
1217
  if (!ref.ok) {
1055
1218
  fail(ref.error);
1056
1219
  if (ref.available.length > 0) {
@@ -1095,7 +1258,7 @@ function registerRunCommand(program) {
1095
1258
  }
1096
1259
  let result;
1097
1260
  try {
1098
- result = await (0, import_core3.runPlan)(prepared.state, ref.id, {
1261
+ result = await (0, import_core4.runPlan)(prepared.state, ref.id, {
1099
1262
  withAssertions,
1100
1263
  bail: opts.bail === true,
1101
1264
  env: opts.env,
@@ -1108,7 +1271,7 @@ function registerRunCommand(program) {
1108
1271
  });
1109
1272
  } catch (err) {
1110
1273
  process.off("SIGINT", onSigint);
1111
- if (err instanceof import_core3.PlanRunDeniedError) {
1274
+ if (err instanceof import_core4.PlanRunDeniedError) {
1112
1275
  fail(err.message, 3, "denied");
1113
1276
  return;
1114
1277
  }
@@ -1147,7 +1310,7 @@ function resolveActor(local, override) {
1147
1310
  if (username) return { kind: "os", name: username };
1148
1311
  } catch {
1149
1312
  }
1150
- return import_core3.ANONYMOUS_ACTOR;
1313
+ return import_core4.ANONYMOUS_ACTOR;
1151
1314
  }
1152
1315
  function checkRunPermission(_ctx) {
1153
1316
  }
@@ -1158,17 +1321,17 @@ function formatHeader(plan, actor, withAssertions, opts) {
1158
1321
  opts.bail ? "bail" : null,
1159
1322
  opts.env ? `env=${opts.env}` : null
1160
1323
  ].filter((f) => f !== null);
1161
- return `${import_kleur4.default.bold("Plan")} ${plan.name} ${import_kleur4.default.dim(
1324
+ return `${import_kleur5.default.bold("Plan")} ${plan.name} ${import_kleur5.default.dim(
1162
1325
  `(${enabled}/${plan.steps.length} steps \xB7 ${flags.join(" \xB7 ")})`
1163
1326
  )}
1164
- ${import_kleur4.default.dim("Run by")} ${actor.name} ${import_kleur4.default.dim(`(${actor.kind})`)}
1327
+ ${import_kleur5.default.dim("Run by")} ${actor.name} ${import_kleur5.default.dim(`(${actor.kind})`)}
1165
1328
 
1166
1329
  `;
1167
1330
  }
1168
1331
  function formatAttachmentPreparation(summary) {
1169
1332
  const status = `${summary.downloaded} downloaded, ${summary.alreadyPresent} already local`;
1170
1333
  const lines = [
1171
- `${import_kleur4.default.bold("Attachments")} ${summary.total} required ${import_kleur4.default.dim(
1334
+ `${import_kleur5.default.bold("Attachments")} ${summary.total} required ${import_kleur5.default.dim(
1172
1335
  `(${status} - ${summary.cacheDir})`
1173
1336
  )}`
1174
1337
  ];
@@ -1176,7 +1339,7 @@ function formatAttachmentPreparation(summary) {
1176
1339
  const source = entry3.source === "linked-workspace" ? `linked:${entry3.linkedWorkspaceId ?? "unknown"}` : "workspace";
1177
1340
  const requiredBy = entry3.requiredBy.map((item) => item.requestName).join(", ");
1178
1341
  lines.push(
1179
- ` ${import_kleur4.default.dim("file")} ${entry3.filename} ${import_kleur4.default.dim(
1342
+ ` ${import_kleur5.default.dim("file")} ${entry3.filename} ${import_kleur5.default.dim(
1180
1343
  `${source} - ${requiredBy} - ${entry3.localPath}`
1181
1344
  )}`
1182
1345
  );
@@ -1189,31 +1352,31 @@ function formatStepLine(step) {
1189
1352
  const n = `${step.stepIndex + 1}.`.padEnd(3);
1190
1353
  const method = (step.requestMethod || "\u2014").padEnd(7);
1191
1354
  if (step.skipped) {
1192
- return ` ${import_kleur4.default.dim("\u2013")} ${import_kleur4.default.dim(n)} ${import_kleur4.default.dim(method)} ${import_kleur4.default.dim(
1355
+ return ` ${import_kleur5.default.dim("\u2013")} ${import_kleur5.default.dim(n)} ${import_kleur5.default.dim(method)} ${import_kleur5.default.dim(
1193
1356
  `${step.requestName} skipped`
1194
1357
  )}
1195
1358
  `;
1196
1359
  }
1197
- const mark = step.passed ? import_kleur4.default.green("\u2713") : import_kleur4.default.red("\u2717");
1360
+ const mark = step.passed ? import_kleur5.default.green("\u2713") : import_kleur5.default.red("\u2717");
1198
1361
  const status = step.result?.status != null ? String(step.result.status) : "\u2014";
1199
1362
  const duration = step.result ? `${step.result.durationMs}ms` : "";
1200
1363
  const name = step.requestName.padEnd(28);
1201
- let line = ` ${mark} ${n} ${method} ${name} ${status.padEnd(4)} ${import_kleur4.default.dim(duration)}`;
1364
+ let line = ` ${mark} ${n} ${method} ${name} ${status.padEnd(4)} ${import_kleur5.default.dim(duration)}`;
1202
1365
  if (step.assertionResults.length > 0) {
1203
1366
  const passed = step.assertionResults.filter((a) => a.passed).length;
1204
- line += ` ${import_kleur4.default.dim(`${passed}/${step.assertionResults.length} assertions`)}`;
1367
+ line += ` ${import_kleur5.default.dim(`${passed}/${step.assertionResults.length} assertions`)}`;
1205
1368
  }
1206
1369
  line += "\n";
1207
1370
  if (step.error) {
1208
- line += ` ${import_kleur4.default.red(step.error)}
1371
+ line += ` ${import_kleur5.default.red(step.error)}
1209
1372
  `;
1210
1373
  }
1211
1374
  for (const a of step.assertionResults) {
1212
- if (!a.passed) line += ` ${import_kleur4.default.red("\u2717")} ${a.detail ?? `${a.kind} ${a.op}`}
1375
+ if (!a.passed) line += ` ${import_kleur5.default.red("\u2717")} ${a.detail ?? `${a.kind} ${a.op}`}
1213
1376
  `;
1214
1377
  }
1215
1378
  if (step.missingVariables.length > 0) {
1216
- line += ` ${import_kleur4.default.yellow("\u26A0")} unresolved: ${step.missingVariables.map((v) => `{{${v}}}`).join(", ")}
1379
+ line += ` ${import_kleur5.default.yellow("\u26A0")} unresolved: ${step.missingVariables.map((v) => `{{${v}}}`).join(", ")}
1217
1380
  `;
1218
1381
  }
1219
1382
  return line;
@@ -1232,24 +1395,24 @@ function tally(result) {
1232
1395
  function formatSummary(result, saved, aborted) {
1233
1396
  if (result.steps.length === 0) {
1234
1397
  return `
1235
- ${import_kleur4.default.yellow("Plan has no steps.")}
1398
+ ${import_kleur5.default.yellow("Plan has no steps.")}
1236
1399
  `;
1237
1400
  }
1238
1401
  const { passed, failed, skipped } = tally(result);
1239
1402
  const parts = [
1240
- import_kleur4.default.green(`${passed} passed`),
1241
- failed > 0 ? import_kleur4.default.red(`${failed} failed`) : import_kleur4.default.dim(`${failed} failed`),
1242
- import_kleur4.default.dim(`${skipped} skipped`)
1403
+ import_kleur5.default.green(`${passed} passed`),
1404
+ failed > 0 ? import_kleur5.default.red(`${failed} failed`) : import_kleur5.default.dim(`${failed} failed`),
1405
+ import_kleur5.default.dim(`${skipped} skipped`)
1243
1406
  ];
1244
- const verdict = result.passed && !aborted ? import_kleur4.default.green("PASS") : import_kleur4.default.red("FAIL");
1407
+ const verdict = result.passed && !aborted ? import_kleur5.default.green("PASS") : import_kleur5.default.red("FAIL");
1245
1408
  let out = `
1246
- ${verdict} ${parts.join(import_kleur4.default.dim(" \xB7 "))} ${import_kleur4.default.dim(
1409
+ ${verdict} ${parts.join(import_kleur5.default.dim(" \xB7 "))} ${import_kleur5.default.dim(
1247
1410
  `\xB7 ${result.planRun.durationMs}ms`
1248
1411
  )}
1249
1412
  `;
1250
- if (aborted) out += `${import_kleur4.default.yellow("Run aborted before every step finished.")}
1413
+ if (aborted) out += `${import_kleur5.default.yellow("Run aborted before every step finished.")}
1251
1414
  `;
1252
- out += saved ? import_kleur4.default.dim("Plan run saved to workspace history.\n") : import_kleur4.default.dim("Plan run not saved (--no-save).\n");
1415
+ out += saved ? import_kleur5.default.dim("Plan run saved to workspace history.\n") : import_kleur5.default.dim("Plan run not saved (--no-save).\n");
1253
1416
  return out;
1254
1417
  }
1255
1418
  function buildJsonReport(workspace, planId, plan, actor, result, saved, aborted, attachments) {
@@ -1325,17 +1488,17 @@ ${cases.join("\n")}
1325
1488
  `;
1326
1489
  }
1327
1490
  function fail(message, code = 2, kind = "error") {
1328
- process.stderr.write(`${import_kleur4.default.red(kind)}: ${message}
1491
+ process.stderr.write(`${import_kleur5.default.red(kind)}: ${message}
1329
1492
  `);
1330
1493
  process.exitCode = code;
1331
1494
  }
1332
- var os2, import_kleur4, import_core3, import_file_backed4, REPORTERS;
1495
+ var os2, import_kleur5, import_core4, import_file_backed4, REPORTERS;
1333
1496
  var init_run = __esm({
1334
1497
  "src/commands/run.ts"() {
1335
1498
  "use strict";
1336
1499
  os2 = __toESM(require("os"), 1);
1337
- import_kleur4 = __toESM(require("kleur"), 1);
1338
- import_core3 = require("@apicircle/core");
1500
+ import_kleur5 = __toESM(require("kleur"), 1);
1501
+ import_core4 = require("@apicircle/core");
1339
1502
  import_file_backed4 = require("@apicircle/core/workspace/file-backed");
1340
1503
  init_secrets();
1341
1504
  init_resolveWorkspace();
@@ -1355,15 +1518,15 @@ function registerWorkspacesCommand(program) {
1355
1518
  }
1356
1519
  if (registry.workspaces.length === 0) {
1357
1520
  process.stdout.write(
1358
- `${import_kleur5.default.dim("No workspaces registered yet at")} ${root}
1359
- ${import_kleur5.default.dim("Run")} ${import_kleur5.default.cyan("apicircle workspaces create <name>")} ${import_kleur5.default.dim(
1521
+ `${import_kleur6.default.dim("No workspaces registered yet at")} ${root}
1522
+ ${import_kleur6.default.dim("Run")} ${import_kleur6.default.cyan("apicircle workspaces create <name>")} ${import_kleur6.default.dim(
1360
1523
  "or open the desktop app to seed one."
1361
1524
  )}
1362
1525
  `
1363
1526
  );
1364
1527
  return;
1365
1528
  }
1366
- process.stdout.write(`${import_kleur5.default.dim("registry")}: ${root}
1529
+ process.stdout.write(`${import_kleur6.default.dim("registry")}: ${root}
1367
1530
 
1368
1531
  `);
1369
1532
  const rows = [...registry.workspaces].sort(
@@ -1372,22 +1535,22 @@ ${import_kleur5.default.dim("Run")} ${import_kleur5.default.cyan("apicircle work
1372
1535
  const nameWidth = Math.max(4, ...rows.map((r) => r.name.length));
1373
1536
  const idWidth = Math.max(2, ...rows.map((r) => r.id.length));
1374
1537
  process.stdout.write(
1375
- import_kleur5.default.bold(
1538
+ import_kleur6.default.bold(
1376
1539
  ` ${"".padEnd(1)} ${"NAME".padEnd(nameWidth)} ${"ID".padEnd(idWidth)} LAST OPENED
1377
1540
  `
1378
1541
  )
1379
1542
  );
1380
1543
  for (const w of rows) {
1381
- const mark = w.id === registry.activeWorkspaceId ? import_kleur5.default.green("\u25CF") : " ";
1544
+ const mark = w.id === registry.activeWorkspaceId ? import_kleur6.default.green("\u25CF") : " ";
1382
1545
  process.stdout.write(
1383
- ` ${mark} ${w.name.padEnd(nameWidth)} ${import_kleur5.default.dim(
1546
+ ` ${mark} ${w.name.padEnd(nameWidth)} ${import_kleur6.default.dim(
1384
1547
  w.id.padEnd(idWidth)
1385
- )} ${import_kleur5.default.dim(w.lastOpenedAt)}
1548
+ )} ${import_kleur6.default.dim(w.lastOpenedAt)}
1386
1549
  `
1387
1550
  );
1388
1551
  }
1389
1552
  process.stdout.write(`
1390
- ${import_kleur5.default.dim("\u25CF = active")}
1553
+ ${import_kleur6.default.dim("\u25CF = active")}
1391
1554
  `);
1392
1555
  });
1393
1556
  ws.command("create").description("Create a new workspace and add it to the registry").argument("<name>", "Human-readable label for the workspace").option("--sample", "Seed the workspace with one sample request", false).action(async (name, opts) => {
@@ -1397,17 +1560,17 @@ ${import_kleur5.default.dim("\u25CF = active")}
1397
1560
  sampleRequest: opts.sample ?? false
1398
1561
  });
1399
1562
  process.stdout.write(
1400
- `${import_kleur5.default.green("created")} workspace ${import_kleur5.default.cyan(entry3.name)} ${import_kleur5.default.dim(`(${entry3.id})`)}
1563
+ `${import_kleur6.default.green("created")} workspace ${import_kleur6.default.cyan(entry3.name)} ${import_kleur6.default.dim(`(${entry3.id})`)}
1401
1564
  at ${dir}
1402
1565
  `
1403
1566
  );
1404
1567
  if (registry.activeWorkspaceId === entry3.id) {
1405
- process.stdout.write(`${import_kleur5.default.dim("marked as active")}
1568
+ process.stdout.write(`${import_kleur6.default.dim("marked as active")}
1406
1569
  `);
1407
1570
  }
1408
1571
  } catch (err) {
1409
1572
  process.stderr.write(
1410
- `${import_kleur5.default.red("error")}: ${err instanceof Error ? err.message : String(err)}
1573
+ `${import_kleur6.default.red("error")}: ${err instanceof Error ? err.message : String(err)}
1411
1574
  `
1412
1575
  );
1413
1576
  process.exit(2);
@@ -1418,8 +1581,8 @@ ${import_kleur5.default.dim("\u25CF = active")}
1418
1581
  const entry3 = (0, import_registry2.findWorkspaceEntry)(registry, selector);
1419
1582
  if (!entry3) {
1420
1583
  process.stderr.write(
1421
- `${import_kleur5.default.red("error")}: no workspace named "${selector}" in the registry at ${root}.
1422
- ${import_kleur5.default.dim("Run")} ${import_kleur5.default.cyan("apicircle workspaces list")} ${import_kleur5.default.dim("to see what is available.")}
1584
+ `${import_kleur6.default.red("error")}: no workspace named "${selector}" in the registry at ${root}.
1585
+ ${import_kleur6.default.dim("Run")} ${import_kleur6.default.cyan("apicircle workspaces list")} ${import_kleur6.default.dim("to see what is available.")}
1423
1586
  `
1424
1587
  );
1425
1588
  process.exit(2);
@@ -1428,7 +1591,7 @@ ${import_kleur5.default.dim("Run")} ${import_kleur5.default.cyan("apicircle work
1428
1591
  const next = await (0, import_registry2.setActiveWorkspace)(root, entry3.id);
1429
1592
  void next;
1430
1593
  process.stdout.write(
1431
- `${import_kleur5.default.green("active")} workspace is now ${import_kleur5.default.cyan(entry3.name)} ${import_kleur5.default.dim(`(${entry3.id})`)}
1594
+ `${import_kleur6.default.green("active")} workspace is now ${import_kleur6.default.cyan(entry3.name)} ${import_kleur6.default.dim(`(${entry3.id})`)}
1432
1595
  `
1433
1596
  );
1434
1597
  });
@@ -1441,7 +1604,7 @@ ${import_kleur5.default.dim("Run")} ${import_kleur5.default.cyan("apicircle work
1441
1604
  const entry3 = (0, import_registry2.findWorkspaceEntry)(registry, selector);
1442
1605
  if (!entry3) {
1443
1606
  process.stderr.write(
1444
- `${import_kleur5.default.red("error")}: no workspace named "${selector}" in the registry at ${root}.
1607
+ `${import_kleur6.default.red("error")}: no workspace named "${selector}" in the registry at ${root}.
1445
1608
  `
1446
1609
  );
1447
1610
  process.exit(2);
@@ -1452,11 +1615,11 @@ ${import_kleur5.default.dim("Run")} ${import_kleur5.default.cyan("apicircle work
1452
1615
  process.stdout.write(workspaceDirFor2(root, entry3.id) + "\n");
1453
1616
  });
1454
1617
  }
1455
- var import_kleur5, import_registry2;
1618
+ var import_kleur6, import_registry2;
1456
1619
  var init_workspaces = __esm({
1457
1620
  "src/commands/workspaces.ts"() {
1458
1621
  "use strict";
1459
- import_kleur5 = __toESM(require("kleur"), 1);
1622
+ import_kleur6 = __toESM(require("kleur"), 1);
1460
1623
  init_resolveWorkspace();
1461
1624
  import_registry2 = require("@apicircle/core/workspace/registry");
1462
1625
  }
@@ -1474,6 +1637,7 @@ function buildProgram() {
1474
1637
  registerMockCommand(program);
1475
1638
  registerMcpCommand(program);
1476
1639
  registerImportCommand(program);
1640
+ registerExportCommand(program);
1477
1641
  registerRunCommand(program);
1478
1642
  registerWorkspacesCommand(program);
1479
1643
  return program;
@@ -1489,6 +1653,7 @@ var init_src = __esm({
1489
1653
  init_mock();
1490
1654
  init_mcp();
1491
1655
  init_import();
1656
+ init_export();
1492
1657
  init_run();
1493
1658
  init_workspaces();
1494
1659
  init_packageVersion();