@kubb/studio 5.8.0 → 5.8.2

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/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { t as __name } from "./rolldown-runtime-CRm0XQPb.js";
2
- import { F as SnapshotUploadMetadata, a as AgentConnectInfo, k as PublishedSnapshot, o as AgentPermissions } from "./protocol-i7JV0P4z.js";
2
+ import { B as SnapshotUploadMetadata, M as PublishedSnapshot, a as AgentConnectInfo, c as ConfigEdit, l as ConfigEditOutcome, m as ExtensionEntry, o as AgentPermissions } from "./protocol-BBGIgdIf.js";
3
3
  import { Config, Hookable, KubbHooks } from "@kubb/core";
4
4
  //#region src/connect.d.ts
5
5
  /**
@@ -242,5 +242,27 @@ export declare function createSnapshot({ studioUrl, apiKey, config, hooks, signa
242
242
  */
243
243
  export declare const defaultStudioUrl = "https://kubb.studio";
244
244
  //#endregion
245
- export type { ConnectAgentOptions, StudioConnectedContext };
245
+ //#region src/extensions.d.ts
246
+ /**
247
+ * The extension packages installed in a project, keyed by package name. Only these, besides
248
+ * `@kubb/plugin-*` packages, may be imported on behalf of Studio.
249
+ */
250
+ type InstalledExtensions = ReadonlyMap<string, ExtensionEntry>;
251
+ //#endregion
252
+ //#region src/configFile.d.ts
253
+ /**
254
+ * Applies `edits` to the config file at `filePath`, writing it only when something changed.
255
+ * `add-plugin` accepts `@kubb/plugin-*` packages and the plugins in `extensions`.
256
+ */
257
+ export declare function writeConfigEdits({ filePath, edits, extensions }: {
258
+ filePath: string;
259
+ edits: Array<ConfigEdit>;
260
+ extensions?: InstalledExtensions;
261
+ }): Promise<{
262
+ source: string;
263
+ outcomes: Array<ConfigEditOutcome>;
264
+ changed: boolean;
265
+ }>;
266
+ //#endregion
267
+ export type { ConnectAgentOptions, InstalledExtensions, StudioConnectedContext };
246
268
  //# sourceMappingURL=index.d.ts.map
package/dist/index.js CHANGED
@@ -9,7 +9,7 @@ import process$1 from "node:process";
9
9
  import { setTimeout as setTimeout$1 } from "node:timers/promises";
10
10
  import { pathToFileURL } from "node:url";
11
11
  import { existsSync } from "node:fs";
12
- import { Diagnostics, Hookable, cacheStorage, createKubb, fsStorage, getInputKind, memoryStorage, resolveCacheDir, resolveMeta } from "@kubb/core";
12
+ import { Diagnostics, Hookable, cacheStorage, createKubb, fsStorage, getInputKind, isPackageName, memoryStorage, readInstalledPackages, resolveCacheDir, resolveMeta } from "@kubb/core";
13
13
  import { RpcTarget, newWebSocketRpcSession } from "capnweb";
14
14
  import { detectCodeFormat, parseExpression, parseModule } from "magicast";
15
15
  import { promisify, stripVTControlCharacters } from "node:util";
@@ -192,7 +192,7 @@ function getElapsedMs(hrStart) {
192
192
  }
193
193
  //#endregion
194
194
  //#region package.json
195
- var version = "5.8.0";
195
+ var version = "5.8.2";
196
196
  //#endregion
197
197
  //#region src/resolveConfig.ts
198
198
  /**
@@ -262,7 +262,7 @@ function isInstalled({ packageName, root }) {
262
262
  /**
263
263
  * The name Studio knows a plugin by: its `@kubb/plugin-*` package when one is installed, otherwise
264
264
  * the plugin's own `name`, so a plugin defined in the user's project is never reported as a Kubb
265
- * package that does not exist.
265
+ * package that does not exist. An installed extension whose manifest `name` matches wins.
266
266
  *
267
267
  * @example
268
268
  * ```ts
@@ -270,7 +270,9 @@ function isInstalled({ packageName, root }) {
270
270
  * toStudioName({ name: 'plugin-zod-operations', root }) // 'plugin-zod-operations', a local plugin
271
271
  * ```
272
272
  */
273
- function toStudioName({ name, root }) {
273
+ function toStudioName({ name, root, extensions = /* @__PURE__ */ new Map() }) {
274
+ const installed = [...extensions.values()].find(({ manifest }) => manifest.kind === "plugin" && manifest.name === name);
275
+ if (installed) return installed.package;
274
276
  const packageName = toPackageName(name);
275
277
  return packageName !== name && isInstalled({
276
278
  packageName,
@@ -289,59 +291,69 @@ function toStudioName({ name, root }) {
289
291
  function toExportName(packageName) {
290
292
  return camelCase(toPluginName(packageName));
291
293
  }
292
- /**
293
- * A `@kubb/plugin-*` package specifier. Nothing else may reach `import()`: only Kubb's own
294
- * plugins are supported, so a payload naming anything else, a third-party package or a path, is
295
- * refused before it can execute.
296
- */
297
294
  const KUBB_PLUGIN_SPECIFIER = /^@kubb\/plugin-[\w.-]+$/;
298
295
  /**
299
- * Whether `name` is a `@kubb/plugin-*` specifier. Exported so `configFile.ts` can refuse the same
300
- * shape before printing a Studio-supplied plugin name into the config file's source text.
296
+ * Whether Studio may import `packageName` as a plugin: a `@kubb/plugin-*` package, or an installed
297
+ * package whose `kubb-extension.json` has `kind: "plugin"`. A path, URL or arbitrary package is
298
+ * refused before it can execute. `configFile.ts` applies the same rule before printing a plugin name.
301
299
  */
302
- function isKubbPluginSpecifier(name) {
303
- return KUBB_PLUGIN_SPECIFIER.test(name);
300
+ function isAllowedPlugin({ packageName, extensions }) {
301
+ if (!isPackageName(packageName)) return false;
302
+ return KUBB_PLUGIN_SPECIFIER.test(packageName) || extensions.get(packageName)?.manifest.kind === "plugin";
304
303
  }
305
304
  /**
306
- * Dynamically imports a `@kubb/plugin-*` package and returns its factory function.
305
+ * Dynamically imports a plugin package and returns its factory. Only packages
306
+ * {@link isAllowedPlugin} accepts reach `import()`.
307
307
  *
308
308
  * Packages must be pre-installed in the Docker image at build time via the `KUBB_PACKAGES`
309
309
  * build ARG, no runtime installation is possible in the distroless container.
310
310
  *
311
- * Resolution order: the camelCase named export the package name implies (e.g. `pluginTs`), then
312
- * the default export.
311
+ * Resolution order: the manifest's `export`, else the camelCase named export the package name
312
+ * implies (e.g. `pluginTs`), then the default export.
313
313
  *
314
- * @throws if the package cannot be imported or exports no callable factory.
314
+ * @throws if the package is not allowed, cannot be imported, or exports no callable factory.
315
315
  */
316
- async function loadPluginFactory(packageName) {
317
- if (!isKubbPluginSpecifier(packageName)) throw new Error(`Plugin "${packageName}" is not a @kubb/plugin-* package. Kubb Studio only supports Kubb's own plugins.`);
316
+ async function loadPluginFactory({ packageName, extensions }) {
317
+ if (!isAllowedPlugin({
318
+ packageName,
319
+ extensions
320
+ })) throw new Error(`Plugin "${packageName}" is not an installed Kubb plugin. Studio loads @kubb/plugin-* packages and installed packages that ship a kubb-extension.json with kind "plugin".`);
318
321
  let mod;
319
322
  try {
320
323
  mod = await importFromProject(packageName);
321
324
  } catch (cause) {
322
325
  throw new Error(`Plugin "${packageName}" could not be loaded. Make sure it is installed: \`npm install ${packageName}\``, { cause });
323
326
  }
327
+ const manifestExport = extensions.get(packageName)?.manifest.export;
328
+ if (manifestExport) {
329
+ if (typeof mod[manifestExport] === "function") return mod[manifestExport];
330
+ throw new Error(`Plugin "${packageName}" does not export "${manifestExport}", the factory its kubb-extension.json names.`);
331
+ }
324
332
  const exportName = toExportName(packageName);
325
333
  if (typeof mod[exportName] === "function") return mod[exportName];
326
334
  if (typeof mod["default"] === "function") return mod["default"];
327
335
  throw new Error(`Plugin "${packageName}" does not export a callable factory. Tried the named export "${exportName}" and "default".`);
328
336
  }
329
337
  /**
330
- * Resolves each plugin entry by dynamically importing the `@kubb/plugin-*` package and
331
- * calling its factory with the provided options.
332
- *
333
- * Packages must be pre-installed in the Docker image at build time, use the `KUBB_PACKAGES`
334
- * build ARG to control which ones are available at runtime.
338
+ * Resolves each plugin entry by dynamically importing its package and calling its factory with the
339
+ * provided options. See {@link loadPluginFactory} for which packages are allowed.
335
340
  *
336
341
  * @example
337
342
  * ```ts
338
- * { name: '@kubb/plugin-react-query', options: { output: { path: './hooks' } } }
339
- * { name: '@kubb/plugin-ts', options: { output: { path: './types' } } }
343
+ * const plugins = await resolvePlugins({
344
+ * plugins: [
345
+ * { name: '@kubb/plugin-react-query', options: { output: { path: './hooks' } } },
346
+ * { name: '@kubb/plugin-ts', options: { output: { path: './types' } } },
347
+ * ],
348
+ * })
340
349
  * ```
341
350
  */
342
- async function resolvePlugins(plugins) {
351
+ async function resolvePlugins({ plugins, extensions = /* @__PURE__ */ new Map() }) {
343
352
  return Promise.all(plugins.map(async ({ name, options }) => {
344
- return (await loadPluginFactory(name))(options ?? {});
353
+ return (await loadPluginFactory({
354
+ packageName: name,
355
+ extensions
356
+ }))(options ?? {});
345
357
  }));
346
358
  }
347
359
  /** The installed version of each plugin's package, plus the names that could not be resolved. */
@@ -459,14 +471,18 @@ function mergeOptions(disk, studio) {
459
471
  * For plugins present in both configs, the plugin is re-instantiated with merged options
460
472
  * so that all internal closures correctly reference the merged values.
461
473
  */
462
- async function mergePlugins(diskPlugins, studioPlugins) {
463
- const disabledNames = new Set((studioPlugins ?? []).filter((entry) => entry.disabled).map((entry) => toPluginName(entry.name)));
474
+ async function mergePlugins({ diskPlugins, studioPlugins, extensions = /* @__PURE__ */ new Map() }) {
475
+ const pluginNameOf = (packageName) => extensions.get(packageName)?.manifest.name ?? toPluginName(packageName);
476
+ const disabledNames = new Set((studioPlugins ?? []).filter((entry) => entry.disabled).map((entry) => pluginNameOf(entry.name)));
464
477
  const activeDiskPlugins = disabledNames.size ? diskPlugins?.filter((plugin) => !disabledNames.has(resolveMeta(plugin).name)) : diskPlugins;
465
478
  const activeStudioPlugins = studioPlugins?.filter((entry) => !entry.disabled);
466
479
  if (!activeDiskPlugins && !activeStudioPlugins?.length) return void 0;
467
480
  if (!activeStudioPlugins?.length) return activeDiskPlugins;
468
- if (!activeDiskPlugins) return resolvePlugins(activeStudioPlugins);
469
- const studioEntryByName = new Map(activeStudioPlugins.map((entry) => [toPluginName(entry.name), entry]));
481
+ if (!activeDiskPlugins) return resolvePlugins({
482
+ plugins: activeStudioPlugins,
483
+ extensions
484
+ });
485
+ const studioEntryByName = new Map(activeStudioPlugins.map((entry) => [pluginNameOf(entry.name), entry]));
470
486
  const diskNames = new Set(activeDiskPlugins.map((plugin) => plugin.name));
471
487
  const merged = await Promise.all(activeDiskPlugins.map(async (diskPlugin) => {
472
488
  const studioEntry = studioEntryByName.get(resolveMeta(diskPlugin).name);
@@ -474,17 +490,23 @@ async function mergePlugins(diskPlugins, studioPlugins) {
474
490
  const diskOptions = diskPlugin.options ?? {};
475
491
  const options = mergeOptions(diskOptions, studioEntry.options ?? {});
476
492
  const unchanged = JSON.stringify(toJSONOptions(options)) === JSON.stringify(toJSONOptions(diskOptions));
477
- const [resolved] = await resolvePlugins([{
478
- name: studioEntry.name,
479
- options
480
- }]).catch((cause) => {
493
+ const [resolved] = await resolvePlugins({
494
+ plugins: [{
495
+ name: studioEntry.name,
496
+ options
497
+ }],
498
+ extensions
499
+ }).catch((cause) => {
481
500
  if (unchanged) return [diskPlugin];
482
- throw new Error(`Plugin "${diskPlugin.name}" is not an installed @kubb/plugin-* package, so Studio cannot change its options. Edit it in the config file, or only enable or disable it from Studio.`, { cause });
501
+ throw new Error(`Plugin "${diskPlugin.name}" is not an installed @kubb/plugin-* package or Kubb extension, so Studio cannot change its options. Edit it in the config file, or only enable or disable it from Studio.`, { cause });
483
502
  });
484
503
  return resolved ?? diskPlugin;
485
504
  }));
486
- const studioOnly = activeStudioPlugins.filter((entry) => !diskNames.has(toPluginName(entry.name)));
487
- return [...merged, ...await resolvePlugins(studioOnly)];
505
+ const studioOnly = activeStudioPlugins.filter((entry) => !diskNames.has(pluginNameOf(entry.name)));
506
+ return [...merged, ...await resolvePlugins({
507
+ plugins: studioOnly,
508
+ extensions
509
+ })];
488
510
  }
489
511
  /**
490
512
  * Merges Studio-provided adapter option overrides into the disk config's adapter.
@@ -851,6 +873,14 @@ function readConfig(source) {
851
873
  })
852
874
  };
853
875
  }
876
+ /** The modules the config file imports, in source order. Empty when the file cannot be parsed. */
877
+ function readImports(source) {
878
+ try {
879
+ return [...new Set(importedFrom(parseModule(source)).values())];
880
+ } catch {
881
+ return [];
882
+ }
883
+ }
854
884
  /**
855
885
  * Walks `path` through a plugin's options to the literal at its last key. Returns `missing` with
856
886
  * the index of the first key that is not there yet, so `set` can write the rest of the path as one
@@ -924,9 +954,12 @@ function insertImport(source, { mod, kind }, importName, moduleSpecifier) {
924
954
  * Adds a `pluginX(...)` call to a config's plugins array, and its import. Replaying the same add is
925
955
  * a no-op, while an import name collision with an unrelated package remains an error.
926
956
  */
927
- function applyAddPlugin(source, parsed, config, edit, quote) {
928
- if (!isKubbPluginSpecifier(edit.plugin)) return { reason: `"${edit.plugin}" is not a @kubb/plugin-* package` };
929
- const importName = edit.importName ?? toExportName(edit.plugin);
957
+ function applyAddPlugin({ source, parsed, config, edit, quote, extensions }) {
958
+ if (!isAllowedPlugin({
959
+ packageName: edit.plugin,
960
+ extensions
961
+ })) return { reason: `"${edit.plugin}" is not a @kubb/plugin-* package or an installed Kubb plugin` };
962
+ const importName = extensions.get(edit.plugin)?.manifest.export ?? edit.importName ?? toExportName(edit.plugin);
930
963
  if (!IDENTIFIER.test(importName)) return { reason: `"${importName}" is not a valid import name` };
931
964
  if (pluginCalls(parsed.mod, config).some((plugin) => plugin.packageName === edit.plugin)) return { source };
932
965
  const taken = importedFrom(parsed.mod).get(importName);
@@ -961,7 +994,7 @@ function enablePlugin(source, config, plugin) {
961
994
  return { source: lines.join("\n") };
962
995
  }
963
996
  /** Applies one edit to `source`. Every edit re-parses the file, since the previous one rewrote it. */
964
- function applyEdit(source, edit, quote) {
997
+ function applyEdit({ source, edit, quote, extensions }) {
965
998
  const parsed = parseConfigs(source);
966
999
  if ("reason" in parsed) return parsed;
967
1000
  const ref = edit.config;
@@ -969,7 +1002,14 @@ function applyEdit(source, edit, quote) {
969
1002
  if (!config) return { reason: `no config entry found for ${JSON.stringify(ref)}` };
970
1003
  if (edit.operation === "disable-plugin") return disablePlugin(source, parsed.mod, config, edit.plugin);
971
1004
  if (edit.operation === "enable-plugin") return enablePlugin(source, config, edit.plugin);
972
- if (edit.operation === "add-plugin") return applyAddPlugin(source, parsed, config, edit, quote);
1005
+ if (edit.operation === "add-plugin") return applyAddPlugin({
1006
+ source,
1007
+ parsed,
1008
+ config,
1009
+ edit,
1010
+ quote,
1011
+ extensions
1012
+ });
973
1013
  const call = pluginCalls(parsed.mod, config).find((plugin) => plugin.packageName === edit.plugin)?.call;
974
1014
  if (!call) return { reason: disabledBlocks(source, config).some((block) => block.packageName === edit.plugin) ? `${edit.plugin} is disabled, enable it first` : `${edit.plugin} is not in the plugins array` };
975
1015
  return edit.operation === "set" ? applySet(source, call, edit.path, edit.value, quote) : applyRemove(source, call, edit.path);
@@ -982,12 +1022,13 @@ function applyEdit(source, edit, quote) {
982
1022
  *
983
1023
  * @example
984
1024
  * ```ts
985
- * const { source, outcomes } = applyConfigEdits(current, [
986
- * { operation: 'set', plugin: '@kubb/plugin-ts', path: ['enum', 'type'], value: 'enum' },
987
- * ])
1025
+ * const { source, outcomes } = applyConfigEdits({
1026
+ * source: current,
1027
+ * edits: [{ operation: 'set', plugin: '@kubb/plugin-ts', path: ['enum', 'type'], value: 'enum' }],
1028
+ * })
988
1029
  * ```
989
1030
  */
990
- function applyConfigEdits(source, edits) {
1031
+ function applyConfigEdits({ source, edits, extensions = /* @__PURE__ */ new Map() }) {
991
1032
  let current = source;
992
1033
  const quote = detectCodeFormat(source).quote === "double" ? `"` : `'`;
993
1034
  const outcomes = edits.map((edit) => {
@@ -998,7 +1039,12 @@ function applyConfigEdits(source, edits) {
998
1039
  reason: invalid
999
1040
  };
1000
1041
  try {
1001
- const result = applyEdit(current, edit, quote);
1042
+ const result = applyEdit({
1043
+ source: current,
1044
+ edit,
1045
+ quote,
1046
+ extensions
1047
+ });
1002
1048
  if ("reason" in result) return {
1003
1049
  edit,
1004
1050
  applied: false,
@@ -1023,13 +1069,46 @@ function applyConfigEdits(source, edits) {
1023
1069
  changed: current !== source
1024
1070
  };
1025
1071
  }
1026
- /** Applies `edits` to the config file at `filePath`, writing it only when something changed. */
1027
- async function writeConfigEdits({ filePath, edits }) {
1028
- const result = applyConfigEdits(await read(filePath), edits);
1072
+ /**
1073
+ * Applies `edits` to the config file at `filePath`, writing it only when something changed.
1074
+ * `add-plugin` accepts `@kubb/plugin-*` packages and the plugins in `extensions`.
1075
+ */
1076
+ async function writeConfigEdits({ filePath, edits, extensions }) {
1077
+ const result = applyConfigEdits({
1078
+ source: await read(filePath),
1079
+ edits,
1080
+ extensions
1081
+ });
1029
1082
  if (result.changed) await writeFile(filePath, result.source, "utf-8");
1030
1083
  return result;
1031
1084
  }
1032
1085
  //#endregion
1086
+ //#region src/extensions.ts
1087
+ /** Extensions `kubb` ships with, so they count as installed without being a direct dependency. */
1088
+ const BUILT_IN_PACKAGES = [
1089
+ "@kubb/adapter-oas",
1090
+ "@kubb/parser-ts",
1091
+ "@kubb/parser-md",
1092
+ "@kubb/plugin-barrel"
1093
+ ];
1094
+ /**
1095
+ * Finds the extensions installed in the project at `root`: its `dependencies` and `devDependencies`,
1096
+ * the `packages` its config file imports, and the extensions `kubb` ships with. Keeps each package
1097
+ * that resolves and ships a valid `kubb-extension.json`. Never throws.
1098
+ */
1099
+ async function discoverExtensions({ root, packages = [] }) {
1100
+ const installed = await readInstalledPackages({
1101
+ root,
1102
+ packages: [...packages, ...BUILT_IN_PACKAGES],
1103
+ resolveFrom: ["kubb"]
1104
+ });
1105
+ return new Map(installed.flatMap(({ package: name, version, manifest }) => version !== void 0 && manifest ? [[name, {
1106
+ package: name,
1107
+ version,
1108
+ manifest
1109
+ }]] : []));
1110
+ }
1111
+ //#endregion
1033
1112
  //#region src/generations.ts
1034
1113
  const READ_CONCURRENCY = 50;
1035
1114
  const MB = 1048576;
@@ -1544,10 +1623,21 @@ var Agent = class extends RpcTarget {
1544
1623
  return;
1545
1624
  }
1546
1625
  }
1626
+ async #installedExtensions() {
1627
+ const source = await read(this.#options.configFile).catch(() => "");
1628
+ return discoverExtensions({
1629
+ root: this.#options.root,
1630
+ packages: readImports(source)
1631
+ });
1632
+ }
1547
1633
  async connect(info) {
1548
1634
  this.#session.info = info;
1549
1635
  const { configPath, root, version: version$1, loadConfig } = this.#options;
1550
- const [config, file] = await Promise.all([loadConfig(), this.#readConfigFileView()]);
1636
+ const [config, file, extensions] = await Promise.all([
1637
+ loadConfig(),
1638
+ this.#readConfigFileView(),
1639
+ this.#installedExtensions()
1640
+ ]);
1551
1641
  this.#session.connected.resolve(info);
1552
1642
  return {
1553
1643
  versions: {
@@ -1561,10 +1651,12 @@ var Agent = class extends RpcTarget {
1561
1651
  plugins: config.plugins.map((plugin) => ({
1562
1652
  name: toStudioName({
1563
1653
  name: resolveMeta(plugin).name,
1564
- root
1654
+ root,
1655
+ extensions
1565
1656
  }),
1566
1657
  options: toJSONOptions(plugin.options)
1567
- }))
1658
+ })),
1659
+ extensions: [...extensions.values()]
1568
1660
  },
1569
1661
  permissions: this.#permissions
1570
1662
  };
@@ -1622,8 +1714,12 @@ var Agent = class extends RpcTarget {
1622
1714
  const { jobId, config: patch } = input;
1623
1715
  const studioInput = this.#sandbox ? patch?.input : void 0;
1624
1716
  if (this.#sandbox && (typeof studioInput !== "string" || !studioInput.trim() || getInputKind(studioInput) !== "inline")) return this.#refuse({ message: "A sandbox agent generates only from the OpenAPI document content Studio sends, not from a file path or URL" });
1625
- const config = await this.#options.loadConfig();
1626
- const [plugins, adapter] = await Promise.all([mergePlugins(config.plugins, patch?.plugins), mergeAdapter(config.adapter, patch?.adapter)]).catch(async (error) => {
1717
+ const [config, extensions] = await Promise.all([this.#options.loadConfig(), this.#installedExtensions()]);
1718
+ const [plugins, adapter] = await Promise.all([mergePlugins({
1719
+ diskPlugins: config.plugins,
1720
+ studioPlugins: patch?.plugins,
1721
+ extensions
1722
+ }), mergeAdapter(config.adapter, patch?.adapter)]).catch(async (error) => {
1627
1723
  await emit(this.#options.hooks, "kubb:error", { error: toError(error) });
1628
1724
  throw error;
1629
1725
  });
@@ -1711,7 +1807,8 @@ var Agent = class extends RpcTarget {
1711
1807
  try {
1712
1808
  const { source, outcomes, changed } = await writeConfigEdits({
1713
1809
  filePath: configFile,
1714
- edits
1810
+ edits,
1811
+ extensions: await this.#installedExtensions()
1715
1812
  });
1716
1813
  const applied = outcomes.filter((outcome) => outcome.applied).length;
1717
1814
  return {
@@ -2236,6 +2333,6 @@ async function createSnapshot({ studioUrl = defaultStudioUrl, apiKey, config, ho
2236
2333
  return _data.snapshot;
2237
2334
  }
2238
2335
  //#endregion
2239
- export { IncompatibleAgentError, InvalidAgentTokenError, PairingCanceledError, PairingDeniedError, PairingExpiredError, connectAgent, createSnapshot, defaultStudioUrl, pairAgent };
2336
+ export { IncompatibleAgentError, InvalidAgentTokenError, PairingCanceledError, PairingDeniedError, PairingExpiredError, connectAgent, createSnapshot, defaultStudioUrl, pairAgent, writeConfigEdits };
2240
2337
 
2241
2338
  //# sourceMappingURL=index.js.map