@qodalis/cli 2.4.2 → 2.5.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.
package/public-api.mjs CHANGED
@@ -1,4 +1,4 @@
1
- import { createCliToken, CliLogLevel, DefaultThemes, DefaultThemeInfos, CliForegroundColor, DefaultLibraryAuthor, CliIcon, writeWrapped, LIBRARY_VERSION as LIBRARY_VERSION$1, writeUsage, writeExamples, writeHint, joinShellWords, CliKeyData, unquoteRawArgument, resolveConfigurationCategories, ICliTranslationService_TOKEN, splitShellWords, cliAuthorize, CliUsersPermissions, ICliAuthorizationService_TOKEN, quoteShellWord, resolveHeaders, appendQueryParams, buildServerWebSocketUrl, ServerVersionNegotiator, CliHttpError, formatJson, visibleLength, ICliEventBus_TOKEN, stripAnsi, CancellablePromise, isCliAccessDeniedError, getParameterValue, ICliCommandGuard_TOKEN, getRightOfWord, CLI_CONFIGURE_STORE_NAME, CliModuleRegistry, initializeBrowserEnvironment, ICliPermissionRegistry_TOKEN, satisfiesVersionRange, CliServerPermissions, ICliDragDropService_TOKEN, cliAccessMetadataGuards, resolvePanelTheme, prefersDarkScheme, ICliServerAuthService_TOKEN, loadPanelPosition, savePanelPosition, derivePanelThemeStyles, delay, colorFirstWord, clearTerminalLine, ICliCompletionProvider_TOKEN, ICliServerAuthTokenProvider_TOKEN, truncateToWidth } from '@qodalis/cli-core';
1
+ import { createCliToken, CliLogLevel, DefaultThemes, DefaultThemeInfos, CliForegroundColor, DefaultLibraryAuthor, CliIcon, writeWrapped, LIBRARY_VERSION as LIBRARY_VERSION$1, writeUsage, writeExamples, writeHint, joinShellWords, CliKeyData, unquoteRawArgument, resolveConfigurationCategories, ICliTranslationService_TOKEN, splitShellWords, cliAuthorize, CliUsersPermissions, ICliAuthorizationService_TOKEN, quoteShellWord, resolveHeaders, appendQueryParams, buildServerWebSocketUrl, ServerVersionNegotiator, CliHttpError, formatJson, visibleLength, ICliEventBus_TOKEN, stripAnsi, CancellablePromise, isCliAccessDeniedError, getParameterValue, ICliCommandGuard_TOKEN, getRightOfWord, CLI_CONFIGURE_STORE_NAME, CliModuleRegistry, initializeBrowserEnvironment, ICliPermissionRegistry_TOKEN, satisfiesVersionRange, ICliSecretStore_TOKEN, ICliKeyValueStore_TOKEN, CliServerPermissions, ICliDragDropService_TOKEN, cliAccessMetadataGuards, resolvePanelTheme, prefersDarkScheme, ICliServerAuthService_TOKEN, loadPanelPosition, savePanelPosition, derivePanelThemeStyles, delay, colorFirstWord, clearTerminalLine, ICliCompletionProvider_TOKEN, ICliServerAuthTokenProvider_TOKEN, truncateToWidth } from '@qodalis/cli-core';
2
2
  export { ICliUserSessionService_TOKEN, ICliUsersStoreService_TOKEN } from '@qodalis/cli-core';
3
3
  import { Terminal } from '@xterm/xterm';
4
4
  import { FitAddon } from '@xterm/addon-fit';
@@ -518,7 +518,7 @@ var CliKeyValueStore_TOKEN = createCliToken("cli-key-value-store");
518
518
  var SyntaxHighlighterRegistry_TOKEN = createCliToken("syntax-highlighter-registry");
519
519
 
520
520
  // src/lib/version.ts
521
- var LIBRARY_VERSION = "2.4.2";
521
+ var LIBRARY_VERSION = "2.5.1";
522
522
  var API_VERSION = 2;
523
523
  var ANONYMOUS = "\0anonymous";
524
524
  function aliasOwner(context2) {
@@ -5886,6 +5886,72 @@ var CliServerConnection = class _CliServerConnection {
5886
5886
  return httpUrl.replace(/^https:/, "wss:").replace(/^http:/, "ws:");
5887
5887
  }
5888
5888
  };
5889
+ function plainText(value, keepLineBreaks = false) {
5890
+ const text = String(value ?? "");
5891
+ return keepLineBreaks ? text.replace(/[\u0000-\u0008\u000b-\u001f\u007f-\u009f]/g, "") : text.replace(/[\u0000-\u001f\u007f-\u009f]/g, "");
5892
+ }
5893
+ function optionalText(value) {
5894
+ return value === void 0 || value === null ? void 0 : plainText(value);
5895
+ }
5896
+ function serverCommandAliases(descriptor) {
5897
+ return (descriptor.aliases ?? []).map((a) => plainText(a).trim()).filter((a) => a && !/\s/.test(a));
5898
+ }
5899
+ var DEFAULT_SERVER_COMMAND_ICON = "\u{1F5A5}";
5900
+ function serverCommandMetadata(descriptor, fallbackModule, server) {
5901
+ return {
5902
+ module: plainText(descriptor.module) || fallbackModule,
5903
+ icon: plainText(descriptor.icon) || DEFAULT_SERVER_COMMAND_ICON,
5904
+ requireServer: true,
5905
+ ...server ? { server } : {}
5906
+ };
5907
+ }
5908
+ function serverCommandParameters(descriptor) {
5909
+ return descriptor.parameters?.map((p) => {
5910
+ const allowed = p.allowedValues?.length ? p.allowedValues.map((v) => plainText(v)) : void 0;
5911
+ const examples = p.examples?.map((v) => plainText(v)).filter(Boolean);
5912
+ return {
5913
+ name: p.name,
5914
+ aliases: p.aliases?.map((a) => plainText(a).replace(/^-+/, "")).filter(Boolean),
5915
+ description: optionalText(p.description),
5916
+ required: p.required,
5917
+ type: p.type,
5918
+ defaultValue: p.defaultValue,
5919
+ ...examples?.length ? { examples } : {},
5920
+ ...allowed ? {
5921
+ allowedValues: allowed,
5922
+ validator: (value) => {
5923
+ const values = Array.isArray(value) ? value : [value];
5924
+ const valid = values.every(
5925
+ (v) => allowed.some((a) => a.toLowerCase() === String(v).toLowerCase())
5926
+ );
5927
+ return valid ? { valid: true } : { valid: false, message: `--${p.name} must be one of: ${allowed.join(", ")}` };
5928
+ }
5929
+ } : {}
5930
+ };
5931
+ });
5932
+ }
5933
+ function serverCommandWriteDescription(descriptor) {
5934
+ const paragraphs = plainText(descriptor.documentation, true).split(/\r?\n\s*\r?\n/).map((p) => p.trim()).filter(Boolean);
5935
+ const examples = (descriptor.examples ?? []).filter((e) => e?.command).map((e) => ({ command: plainText(e.command), description: optionalText(e.description) }));
5936
+ if (paragraphs.length === 0 && examples.length === 0) {
5937
+ return void 0;
5938
+ }
5939
+ return (context2) => {
5940
+ paragraphs.forEach((paragraph, i) => {
5941
+ if (i > 0) context2.writer.writeln();
5942
+ writeWrapped(context2, paragraph);
5943
+ });
5944
+ if (examples.length > 0) {
5945
+ if (paragraphs.length > 0) context2.writer.writeln();
5946
+ writeExamples(
5947
+ context2,
5948
+ examples.map((e) => [e.command, e.description ?? ""])
5949
+ );
5950
+ }
5951
+ };
5952
+ }
5953
+
5954
+ // src/lib/server/cli-server-proxy-processor.ts
5889
5955
  function renderSingleOutput(output2, context2, rawCommand) {
5890
5956
  switch (output2.type) {
5891
5957
  case "text": {
@@ -6017,21 +6083,15 @@ var CliServerProxyProcessor = class _CliServerProxyProcessor {
6017
6083
  this.serverName = serverName;
6018
6084
  this.commandPath = [...parentPath, descriptor.command];
6019
6085
  this.command = isNested ? descriptor.command : `${serverName}:${descriptor.command}`;
6020
- this.description = descriptor.description;
6086
+ this.description = descriptor.description === void 0 ? void 0 : plainText(descriptor.description);
6021
6087
  this.version = descriptor.version;
6022
- this.metadata = {
6023
- module: `server:${serverName}`,
6024
- icon: "\u{1F5A5}",
6025
- requireServer: true
6026
- };
6027
- this.parameters = descriptor.parameters?.map((p) => ({
6028
- name: p.name,
6029
- aliases: p.aliases,
6030
- description: p.description,
6031
- required: p.required,
6032
- type: p.type,
6033
- defaultValue: p.defaultValue
6034
- }));
6088
+ const aliases = serverCommandAliases(descriptor);
6089
+ if (aliases.length) {
6090
+ this.aliases = isNested ? [...aliases] : aliases.map((a) => `${serverName}:${a}`);
6091
+ }
6092
+ this.metadata = serverCommandMetadata(descriptor, `server:${serverName}`, serverName);
6093
+ this.parameters = serverCommandParameters(descriptor);
6094
+ this.writeDescription = serverCommandWriteDescription(descriptor);
6035
6095
  this.processors = descriptor.processors?.map(
6036
6096
  (sub) => new _CliServerProxyProcessor(connection, sub, serverName, true, this.commandPath)
6037
6097
  );
@@ -6057,20 +6117,14 @@ var CliServerMultiProxyProcessor = class _CliServerMultiProxyProcessor {
6057
6117
  const first = entries[0].descriptor;
6058
6118
  this.commandPath = [...parentPath, first.command];
6059
6119
  this.command = first.command;
6060
- this.description = first.description;
6061
- this.metadata = {
6062
- module: "server:multi",
6063
- icon: "\u{1F5A5}",
6064
- requireServer: true
6065
- };
6066
- this.parameters = first.parameters?.map((p) => ({
6067
- name: p.name,
6068
- aliases: p.aliases,
6069
- description: p.description,
6070
- required: p.required,
6071
- type: p.type,
6072
- defaultValue: p.defaultValue
6073
- }));
6120
+ this.description = first.description === void 0 ? void 0 : plainText(first.description);
6121
+ const aliases = serverCommandAliases(first);
6122
+ if (parentPath.length > 0 && aliases.length) {
6123
+ this.aliases = aliases;
6124
+ }
6125
+ this.metadata = serverCommandMetadata(first, "server:multi");
6126
+ this.parameters = serverCommandParameters(first);
6127
+ this.writeDescription = serverCommandWriteDescription(first);
6074
6128
  this.processors = first.processors?.map((sub) => {
6075
6129
  const subEntries = entries.map((e) => {
6076
6130
  const subDesc = e.descriptor.processors?.find(
@@ -6179,9 +6233,15 @@ var CliServerManager = class {
6179
6233
  /** Bare-name aliases this manager registered (only these are ever removed) */
6180
6234
  this._bareAliases = /* @__PURE__ */ new Set();
6181
6235
  this._hidden = [];
6236
+ /** Servers the user linked (`server add`), as opposed to the host's */
6237
+ this._linked = /* @__PURE__ */ new Set();
6182
6238
  /** `server:command` pairs already warned about, so each warns once */
6183
6239
  this._warnedHidden = /* @__PURE__ */ new Set();
6184
6240
  }
6241
+ /** Whether the user linked this server (`server add`): only those can be removed or edited. */
6242
+ isLinked(name) {
6243
+ return this._linked.has(name);
6244
+ }
6185
6245
  get defaultServer() {
6186
6246
  return this._defaultServer;
6187
6247
  }
@@ -6202,53 +6262,97 @@ var CliServerManager = class {
6202
6262
  this.unregisterBareAliases();
6203
6263
  this.registerBareAliases();
6204
6264
  }
6205
- async connectAll(servers, logger, backgroundServices, cliLogger) {
6265
+ /** Where connections report and run (set once at boot, before any server connects). */
6266
+ configure(logger, backgroundServices, cliLogger) {
6206
6267
  this._logger = logger;
6207
6268
  this._cliLogger = cliLogger;
6208
6269
  this._backgroundServices = backgroundServices;
6270
+ }
6271
+ async connectAll(servers, logger, backgroundServices, cliLogger) {
6272
+ this.configure(logger, backgroundServices, cliLogger);
6209
6273
  for (const config of servers) {
6210
6274
  if (config.enabled === false) continue;
6211
- const connection = new CliServerConnection(
6212
- config,
6213
- backgroundServices,
6214
- cliLogger,
6215
- this.authService
6275
+ await this.connectServer(config);
6276
+ }
6277
+ this.registerBareAliases();
6278
+ }
6279
+ /**
6280
+ * Connects one server and registers its commands (without the bare
6281
+ * aliases: callers refresh them once every server is connected).
6282
+ */
6283
+ async connectServer(config) {
6284
+ const logger = this._logger;
6285
+ const connection = new CliServerConnection(
6286
+ config,
6287
+ this._backgroundServices,
6288
+ this._cliLogger,
6289
+ this.authService
6290
+ );
6291
+ this.connections.set(config.name, connection);
6292
+ connection.onDisconnect = () => {
6293
+ this.handleDisconnect(config.name);
6294
+ };
6295
+ connection.onReconnected = () => {
6296
+ logger?.info(
6297
+ `Server '${config.name}' reconnected (${connection.commands.length} commands, API v${connection.apiVersion}).`
6216
6298
  );
6217
- this.connections.set(config.name, connection);
6218
- connection.onDisconnect = () => {
6219
- this.handleDisconnect(config.name);
6220
- };
6221
- connection.onReconnected = () => {
6222
- logger?.info(
6223
- `Server '${config.name}' reconnected (${connection.commands.length} commands, API v${connection.apiVersion}).`
6224
- );
6225
- this.emitConnected(connection, true);
6226
- this.registerProxyProcessors(connection, config.name);
6227
- this.unregisterBareAliases();
6228
- this.registerBareAliases();
6229
- };
6230
- await connection.connect();
6231
- if (connection.connected) {
6232
- logger?.info(
6233
- `Connected to server '${config.name}' (${connection.commands.length} commands)`
6234
- );
6235
- this.registerProxyProcessors(connection, config.name);
6236
- this.emitConnected(connection, false);
6237
- } else if (connection.needsSignIn) {
6238
- logger?.info(
6239
- `Server '${config.name}' needs a sign-in; its commands load once you are signed in.`
6240
- );
6241
- } else {
6242
- logger?.warn(
6243
- `Could not connect to server '${config.name}' at ${config.url}. Commands from this server will not be available.`
6244
- );
6245
- this.events?.emit("server:unavailable", {
6246
- name: config.name,
6247
- url: config.url
6248
- });
6249
- }
6299
+ this.emitConnected(connection, true);
6300
+ this.registerProxyProcessors(connection, config.name);
6301
+ this.unregisterBareAliases();
6302
+ this.registerBareAliases();
6303
+ };
6304
+ await connection.connect();
6305
+ if (connection.connected) {
6306
+ logger?.info(
6307
+ `Connected to server '${config.name}' (${connection.commands.length} commands)`
6308
+ );
6309
+ this.registerProxyProcessors(connection, config.name);
6310
+ this.emitConnected(connection, false);
6311
+ } else if (connection.needsSignIn) {
6312
+ logger?.info(
6313
+ `Server '${config.name}' needs a sign-in; its commands load once you are signed in.`
6314
+ );
6315
+ } else {
6316
+ logger?.warn(
6317
+ `Could not connect to server '${config.name}' at ${config.url}. Commands from this server will not be available.`
6318
+ );
6319
+ this.events?.emit("server:unavailable", {
6320
+ name: config.name,
6321
+ url: config.url
6322
+ });
6250
6323
  }
6324
+ return connection;
6325
+ }
6326
+ /**
6327
+ * Connects a server added while the terminal runs (`server add`): its
6328
+ * commands are available at once. Throws when the name is taken.
6329
+ */
6330
+ async addServer(config, options = {}) {
6331
+ if (this.connections.has(config.name)) {
6332
+ throw new Error(`Server '${config.name}' already exists`);
6333
+ }
6334
+ if (options.linked) this._linked.add(config.name);
6335
+ const connection = await this.connectServer(config);
6336
+ this.unregisterBareAliases();
6251
6337
  this.registerBareAliases();
6338
+ return connection;
6339
+ }
6340
+ /**
6341
+ * Disconnects a server and forgets it (`server remove`): its commands go
6342
+ * away. False when there is no such server.
6343
+ */
6344
+ removeServer(name) {
6345
+ const connection = this.connections.get(name);
6346
+ if (!connection) return false;
6347
+ connection.onDisconnect = void 0;
6348
+ connection.onReconnected = void 0;
6349
+ connection.disconnect();
6350
+ this.connections.delete(name);
6351
+ this._linked.delete(name);
6352
+ if (this._defaultServer === name) this._defaultServer = null;
6353
+ this.unregisterServerProcessors(name);
6354
+ this.events?.emit("server:disconnected", { name });
6355
+ return true;
6252
6356
  }
6253
6357
  async reconnect(name) {
6254
6358
  const connection = this.connections.get(name);
@@ -6378,7 +6482,7 @@ var CliServerManager = class {
6378
6482
  serverName
6379
6483
  );
6380
6484
  alias.command = command2;
6381
- alias.aliases = [namespacedCommand];
6485
+ alias.aliases = [namespacedCommand, ...this.freeAliases(descriptor)];
6382
6486
  this.registry.registerProcessor(alias);
6383
6487
  this._bareAliases.add(alias);
6384
6488
  } else {
@@ -6390,11 +6494,20 @@ var CliServerManager = class {
6390
6494
  return { serverName, connection, descriptor };
6391
6495
  });
6392
6496
  const multi = new CliServerMultiProxyProcessor(entries, this);
6497
+ const aliases = this.freeAliases(entries[0].descriptor);
6498
+ if (aliases.length) multi.aliases = aliases;
6393
6499
  this.registry.registerProcessor(multi);
6394
6500
  this._bareAliases.add(multi);
6395
6501
  }
6396
6502
  }
6397
6503
  }
6504
+ /**
6505
+ * The server's aliases of a command that no registered command answers
6506
+ * to yet: a local command (or an earlier server alias) keeps its name.
6507
+ */
6508
+ freeAliases(descriptor) {
6509
+ return serverCommandAliases(descriptor).filter((a) => !this.registry.findProcessor(a, []));
6510
+ }
6398
6511
  recordHidden(server, command2, localCommand) {
6399
6512
  this._hidden.push({ server, command: command2, localCommand });
6400
6513
  const key = `${server}:${command2}`;
@@ -6654,9 +6767,13 @@ var CliHelpCommandProcessor = class {
6654
6767
  }
6655
6768
  }
6656
6769
  if (processor.metadata?.requireServer) {
6770
+ const server = processor.metadata.server;
6657
6771
  writer.writeln();
6658
6772
  writer.writeln(
6659
- ` ${writer.wrapInColor(t.t("cli.help.requires_server", "Requires a connected server"), CliForegroundColor.Red)}`
6773
+ ` ${writer.wrapInColor(
6774
+ server ? t.t("cli.help.runs_on_server", "Runs on server {server}", { server }) : t.t("cli.help.requires_server", "Requires a connected server"),
6775
+ CliForegroundColor.Red
6776
+ )}`
6660
6777
  );
6661
6778
  }
6662
6779
  writer.writeln();
@@ -6682,14 +6799,24 @@ var CliHelpCommandProcessor = class {
6682
6799
  const typeStr = `${DIM}<${param.type}>${RESET}`;
6683
6800
  const required = param.required ? writer.wrapInColor(` ${translator.t("cli.help.required", "(required)")}`, CliForegroundColor.Red) : "";
6684
6801
  const defaultVal = param.defaultValue !== void 0 && !param.required ? `${DIM} [${param.defaultValue}]${RESET}` : "";
6802
+ const allowed = param.allowedValues?.length ? `${DIM}${translator.t("cli.help.one_of", "one of:")}${RESET} ${param.allowedValues.map((v) => writer.wrapInColor(v, CliForegroundColor.Magenta)).join(", ")}` : "";
6803
+ const examples = param.examples?.length ? `${DIM}${translator.t("cli.help.for_example", "e.g.")}${RESET} ${param.examples.map((v) => writer.wrapInColor(v, CliForegroundColor.Magenta)).join(", ")}` : "";
6685
6804
  if (narrow) {
6686
6805
  writer.writeln(` ${nameStr} ${typeStr}${required}${defaultVal}`);
6687
6806
  writeWrapped(context2, param.description, 6);
6807
+ if (allowed) writer.writeln(` ${allowed}`);
6808
+ if (examples) writer.writeln(` ${examples}`);
6688
6809
  return;
6689
6810
  }
6690
6811
  writer.writeln(
6691
6812
  ` ${nameStr} ${typeStr} ${param.description}${required}${defaultVal}`
6692
6813
  );
6814
+ if (allowed) {
6815
+ writer.writeln(` ${allowed}`);
6816
+ }
6817
+ if (examples) {
6818
+ writer.writeln(` ${examples}`);
6819
+ }
6693
6820
  }
6694
6821
  };
6695
6822
  function isNarrow(context2) {
@@ -15156,6 +15283,129 @@ var CliClipboard = class {
15156
15283
  }
15157
15284
  }
15158
15285
  };
15286
+
15287
+ // src/lib/server/linked/cli-linked-servers.ts
15288
+ var SERVERS_KEY = "qcli-linked-servers";
15289
+ var tokenKey = (name) => `server-token:${name.toLowerCase()}`;
15290
+ var SERVER_NAME_PATTERN = /^[A-Za-z0-9][A-Za-z0-9_-]{0,31}$/;
15291
+ function normalizeServerUrl(input) {
15292
+ const value = input.trim();
15293
+ if (!value) return null;
15294
+ const withScheme = /^[a-z][a-z0-9+.-]*:\/\//i.test(value) ? value : `https://${value}`;
15295
+ try {
15296
+ const url = new URL(withScheme);
15297
+ if (url.protocol !== "http:" && url.protocol !== "https:") return null;
15298
+ if (!url.hostname || url.username || url.password) return null;
15299
+ return `${url.origin}${url.pathname.replace(/\/+$/, "")}`;
15300
+ } catch {
15301
+ return null;
15302
+ }
15303
+ }
15304
+ function isPrivateHost(hostname) {
15305
+ const host = hostname.toLowerCase().replace(/^\[|\]$/g, "");
15306
+ if (host === "localhost" || host.endsWith(".localhost") || host.endsWith(".local")) return true;
15307
+ if (host.includes(":")) {
15308
+ return host === "::1" || /^f[cd][0-9a-f]{0,2}:/.test(host) || /^fe[89ab][0-9a-f]?:/.test(host);
15309
+ }
15310
+ const ip = /^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/.exec(host);
15311
+ if (!ip) return false;
15312
+ const [a, b] = [Number(ip[1]), Number(ip[2])];
15313
+ return a === 127 || a === 10 || a === 172 && b >= 16 && b <= 31 || a === 192 && b === 168 || a === 169 && b === 254;
15314
+ }
15315
+ function checkServerUrl(input, pageProtocol) {
15316
+ const url = normalizeServerUrl(input);
15317
+ if (!url) return "Not a valid http(s) URL";
15318
+ const { protocol, hostname } = new URL(url);
15319
+ if (protocol === "http:" && !isPrivateHost(hostname)) {
15320
+ return "Only https servers can be linked (http is accepted for localhost and private addresses)";
15321
+ }
15322
+ return {
15323
+ url,
15324
+ insecure: protocol === "http:",
15325
+ mixedContent: protocol === "http:" && pageProtocol === "https:"
15326
+ };
15327
+ }
15328
+ function userServersAllow(options, url) {
15329
+ const policy = options?.userServers;
15330
+ if (policy === false) return false;
15331
+ if (policy === void 0 || policy === true) return true;
15332
+ const origin = new URL(url).origin.toLowerCase();
15333
+ return (policy.allowedOrigins ?? []).some((pattern) => {
15334
+ const escaped = pattern.toLowerCase().replace(/\/+$/, "").replace(/[.+?^${}()|[\]\\]/g, "\\$&").replace(/\*/g, "[^/]*");
15335
+ return new RegExp(`^${escaped}$`).test(origin);
15336
+ });
15337
+ }
15338
+ function userServersEnabled(options) {
15339
+ return options?.userServers !== false;
15340
+ }
15341
+ function suggestServerName(url, taken) {
15342
+ const { hostname, port } = new URL(url);
15343
+ const host = hostname.replace(/^\[|\]$/g, "");
15344
+ const isIp = /^[\d.]+$/.test(host) || host.includes(":");
15345
+ const base = (isIp ? host.replace(/[.:]/g, "-") : host.split(".")[0]) + (port && (isIp || host === "localhost") ? `-${port}` : "");
15346
+ let name = base.replace(/[^A-Za-z0-9_-]/g, "-").replace(/^[-_]+/, "").slice(0, 28) || "server";
15347
+ if (!SERVER_NAME_PATTERN.test(name)) name = "server";
15348
+ let candidate = name;
15349
+ for (let i = 2; taken(candidate); i++) candidate = `${name}-${i}`;
15350
+ return candidate;
15351
+ }
15352
+ function maskToken(token) {
15353
+ if (!token) return "-";
15354
+ return `\u2022\u2022\u2022\u2022${token.slice(-4)}`;
15355
+ }
15356
+ function redactSecrets(command2) {
15357
+ return command2.replace(
15358
+ // A following flag (`--token --default`) is not a value
15359
+ /(--(?:token|password)(?:=|\s+))("[^"]*"|'[^']*'|(?!--)\S+)/gi,
15360
+ (_match, flag) => `${flag}***`
15361
+ );
15362
+ }
15363
+ var CliLinkedServers = class {
15364
+ constructor(kv, secrets) {
15365
+ this.kv = kv;
15366
+ this.secrets = secrets;
15367
+ }
15368
+ async list() {
15369
+ const saved = await this.kv.get(SERVERS_KEY);
15370
+ return Array.isArray(saved) ? saved.filter((s) => s && typeof s.name === "string" && typeof s.url === "string") : [];
15371
+ }
15372
+ async get(name) {
15373
+ return (await this.list()).find((s) => s.name.toLowerCase() === name.toLowerCase());
15374
+ }
15375
+ /** Saves a server (replacing one with the same name) and its token (none: deleted). */
15376
+ async save(server, token) {
15377
+ if (token) await this.secrets.set(tokenKey(server.name), token);
15378
+ else await this.secrets.delete(tokenKey(server.name));
15379
+ const others = (await this.list()).filter((s) => s.name.toLowerCase() !== server.name.toLowerCase());
15380
+ await this.kv.set(SERVERS_KEY, [...others, server]);
15381
+ }
15382
+ async setToken(name, token) {
15383
+ await this.secrets.set(tokenKey(name), token);
15384
+ }
15385
+ async token(name) {
15386
+ return this.secrets.get(tokenKey(name));
15387
+ }
15388
+ async remove(name) {
15389
+ const all = await this.list();
15390
+ const rest = all.filter((s) => s.name.toLowerCase() !== name.toLowerCase());
15391
+ await this.secrets.delete(tokenKey(name));
15392
+ if (rest.length === all.length) return false;
15393
+ await this.kv.set(SERVERS_KEY, rest);
15394
+ return true;
15395
+ }
15396
+ /** Removes every linked server and every secret. */
15397
+ async clear() {
15398
+ await this.kv.remove(SERVERS_KEY);
15399
+ await this.secrets.clear();
15400
+ }
15401
+ };
15402
+ function linkedServerConfig(server, token) {
15403
+ return {
15404
+ name: server.name,
15405
+ url: server.url,
15406
+ ...token ? { headers: () => ({ Authorization: `Bearer ${token}` }) } : {}
15407
+ };
15408
+ }
15159
15409
  var InputModeBase = class {
15160
15410
  constructor(host, resolve) {
15161
15411
  this.host = host;
@@ -17907,7 +18157,8 @@ var CliExecutionContext = class {
17907
18157
  const startedAt = Date.now();
17908
18158
  await this.executor.executeCommand(command2, this);
17909
18159
  this.events?.emit("command:executed", {
17910
- command: command2,
18160
+ // As in the history: `--token`/`--password` values never leave
18161
+ command: redactSecrets(command2),
17911
18162
  exitCode: this.executor.exitStatus ?? 0,
17912
18163
  durationMs: Date.now() - startedAt
17913
18164
  });
@@ -19387,7 +19638,7 @@ var CliCommandHistory = class {
19387
19638
  return this.loading;
19388
19639
  }
19389
19640
  async addCommand(command2) {
19390
- const normalizedCommand = command2.trim();
19641
+ const normalizedCommand = redactSecrets(command2.trim());
19391
19642
  if (!normalizedCommand) return;
19392
19643
  await this.loading;
19393
19644
  const existingIndex = this.commandHistory.indexOf(normalizedCommand);
@@ -20976,6 +21227,129 @@ var CliStateStoreManager = class {
20976
21227
  };
20977
21228
  }
20978
21229
  };
21230
+
21231
+ // src/lib/server/linked/cli-web-crypto-secret-store.ts
21232
+ var KEY_KEY = "qcli-secret-key";
21233
+ var NAMES_KEY = "qcli-secret-names";
21234
+ var SECRET_PREFIX = "qcli-secret:";
21235
+ var CliWebCryptoSecretStore = class {
21236
+ constructor(kv, crypto2 = globalThis.crypto ?? null) {
21237
+ this.kv = kv;
21238
+ this.crypto = crypto2;
21239
+ this.keyPromise = null;
21240
+ }
21241
+ isAvailable() {
21242
+ return !!this.crypto?.subtle && typeof this.crypto.getRandomValues === "function";
21243
+ }
21244
+ async get(key) {
21245
+ if (!this.isAvailable()) return void 0;
21246
+ const record = await this.kv.get(SECRET_PREFIX + key);
21247
+ if (!record?.iv || !record?.data) return void 0;
21248
+ const cryptoKey = await this.kv.get(KEY_KEY);
21249
+ if (!cryptoKey) return void 0;
21250
+ try {
21251
+ const plain = await this.crypto.subtle.decrypt(
21252
+ { name: "AES-GCM", iv: record.iv },
21253
+ cryptoKey,
21254
+ record.data
21255
+ );
21256
+ return new TextDecoder().decode(plain);
21257
+ } catch {
21258
+ return void 0;
21259
+ }
21260
+ }
21261
+ async set(key, value) {
21262
+ if (!this.isAvailable()) {
21263
+ throw new Error("Secrets cannot be saved: this page has no Web Crypto (it must be served over https)");
21264
+ }
21265
+ const cryptoKey = await this.key();
21266
+ const iv = this.crypto.getRandomValues(new Uint8Array(12));
21267
+ const data = await this.crypto.subtle.encrypt(
21268
+ { name: "AES-GCM", iv },
21269
+ cryptoKey,
21270
+ new TextEncoder().encode(value)
21271
+ );
21272
+ await this.kv.set(SECRET_PREFIX + key, { iv, data: new Uint8Array(data) });
21273
+ const names = await this.kv.get(NAMES_KEY) ?? [];
21274
+ if (!names.includes(key)) await this.kv.set(NAMES_KEY, [...names, key]);
21275
+ }
21276
+ async delete(key) {
21277
+ await this.kv.remove(SECRET_PREFIX + key);
21278
+ const names = await this.kv.get(NAMES_KEY) ?? [];
21279
+ if (names.includes(key)) await this.kv.set(NAMES_KEY, names.filter((n) => n !== key));
21280
+ }
21281
+ async clear() {
21282
+ for (const name of await this.kv.get(NAMES_KEY) ?? []) {
21283
+ await this.kv.remove(SECRET_PREFIX + name);
21284
+ }
21285
+ await this.kv.remove(NAMES_KEY);
21286
+ await this.kv.remove(KEY_KEY);
21287
+ this.keyPromise = null;
21288
+ }
21289
+ /** The saved key, or a new one (created once, even for parallel calls). */
21290
+ key() {
21291
+ this.keyPromise ??= (async () => {
21292
+ const saved = await this.kv.get(KEY_KEY);
21293
+ if (saved) return saved;
21294
+ const created = await this.crypto.subtle.generateKey(
21295
+ { name: "AES-GCM", length: 256 },
21296
+ false,
21297
+ ["encrypt", "decrypt"]
21298
+ );
21299
+ await this.kv.set(KEY_KEY, created);
21300
+ return created;
21301
+ })();
21302
+ const pending = this.keyPromise;
21303
+ pending.catch(() => {
21304
+ if (this.keyPromise === pending) this.keyPromise = null;
21305
+ });
21306
+ return pending;
21307
+ }
21308
+ };
21309
+
21310
+ // src/lib/server/linked/cli-linked-servers-runtime.ts
21311
+ var webCryptoStores = /* @__PURE__ */ new WeakMap();
21312
+ function secretStoreOf(context2) {
21313
+ const provided = context2.services.get(ICliSecretStore_TOKEN);
21314
+ if (provided) return provided;
21315
+ const kv = context2.services.getRequired(ICliKeyValueStore_TOKEN);
21316
+ let store = webCryptoStores.get(kv);
21317
+ if (!store) {
21318
+ store = new CliWebCryptoSecretStore(kv);
21319
+ webCryptoStores.set(kv, store);
21320
+ }
21321
+ return store;
21322
+ }
21323
+ function linkedServersOf(context2) {
21324
+ const kv = context2.services.getRequired(ICliKeyValueStore_TOKEN);
21325
+ return new CliLinkedServers(kv, secretStoreOf(context2));
21326
+ }
21327
+ function rejectedTokenMessage(name) {
21328
+ return `Server '${name}' rejected its token. Link it again with server add ${name}, or set a new token with server edit ${name} --token.`;
21329
+ }
21330
+ async function restoreLinkedServers(context2, manager, warn) {
21331
+ if (!userServersEnabled(context2.options)) return;
21332
+ const linked = linkedServersOf(context2);
21333
+ let servers;
21334
+ try {
21335
+ servers = await linked.list();
21336
+ } catch {
21337
+ return;
21338
+ }
21339
+ for (const server of servers) {
21340
+ if (manager.connections.has(server.name)) {
21341
+ warn(`Linked server '${server.name}' is not connected: the app has a server with the same name.`);
21342
+ continue;
21343
+ }
21344
+ if (!userServersAllow(context2.options, server.url)) {
21345
+ warn(`Linked server '${server.name}' (${server.url}) is not allowed by this app.`);
21346
+ continue;
21347
+ }
21348
+ const token = await linked.token(server.name).catch(() => void 0);
21349
+ const connection = await manager.addServer(linkedServerConfig(server, token), { linked: true });
21350
+ if (token && connection.needsSignIn) warn(rejectedTokenMessage(server.name));
21351
+ }
21352
+ }
20979
21353
  var CliServerAuthService = class {
20980
21354
  constructor(services) {
20981
21355
  this.services = services;
@@ -21047,14 +21421,17 @@ function writeUnknownServer(context2, manager, name) {
21047
21421
  function writeConfigureHint(context2) {
21048
21422
  const { writer } = context2;
21049
21423
  writer.writeln(
21050
- ` Servers are configured by the app: ${writer.wrapInColor("servers: [{ name: 'node', url: 'http://localhost:8047' }]", CliForegroundColor.Yellow)}`
21424
+ ` Link one: ${writer.wrapInColor("server add", CliForegroundColor.Cyan)}, or configure it in the app: ${writer.wrapInColor("servers: [{ name: 'node', url: 'http://localhost:8047' }]", CliForegroundColor.Yellow)}`
21051
21425
  );
21052
21426
  writer.writeln(
21053
21427
  ` See ${writer.wrapInColor("https://cli.qodalis.com/docs/server-integration", CliForegroundColor.Blue)}`
21054
21428
  );
21055
21429
  }
21056
21430
  function statusLabel(context2, connection) {
21057
- return connection.connected ? context2.writer.wrapInColor("\u25CF connected", CliForegroundColor.Green) : context2.writer.wrapInColor("\u25CB unreachable", CliForegroundColor.Red);
21431
+ if (connection.connected) {
21432
+ return context2.writer.wrapInColor("\u25CF connected", CliForegroundColor.Green);
21433
+ }
21434
+ return connection.needsSignIn ? context2.writer.wrapInColor("\u25D0 sign-in needed", CliForegroundColor.Yellow) : context2.writer.wrapInColor("\u25CB unreachable", CliForegroundColor.Red);
21058
21435
  }
21059
21436
  async function pingServer(connection) {
21060
21437
  const started = performance.now();
@@ -21096,6 +21473,546 @@ function editDistance(a, b) {
21096
21473
  return row[b.length];
21097
21474
  }
21098
21475
 
21476
+ // src/lib/server/linked/cli-server-link-client.ts
21477
+ var V1 = "/api/v1/qcli";
21478
+ function plainText2(value) {
21479
+ return String(value ?? "").replace(/[\u0000-\u001f\u007f-\u009f]/g, "");
21480
+ }
21481
+ async function readJson(response) {
21482
+ try {
21483
+ return await response.json();
21484
+ } catch {
21485
+ return null;
21486
+ }
21487
+ }
21488
+ async function probeServer(fetchFn, url) {
21489
+ let versions;
21490
+ try {
21491
+ versions = await fetchFn(`${url}/api/qcli/versions`, { headers: { Accept: "application/json" } });
21492
+ } catch (e) {
21493
+ throw new Error(`Cannot reach ${url}: ${e.message || "network error"}`, { cause: e });
21494
+ }
21495
+ const body = versions.ok ? await readJson(versions) : null;
21496
+ if (!body || !Array.isArray(body.supportedVersions)) {
21497
+ throw new Error(`${url} is not a Qodalis CLI server (no version discovery at /api/qcli/versions)`);
21498
+ }
21499
+ let capabilities = null;
21500
+ try {
21501
+ const res = await fetchFn(`${url}${V1}/capabilities`, { headers: { Accept: "application/json" } });
21502
+ if (res.ok) capabilities = await readJson(res);
21503
+ } catch {
21504
+ }
21505
+ let needsSignIn = false;
21506
+ try {
21507
+ const res = await fetchFn(`${url}${V1}/commands`, { headers: { Accept: "application/json" } });
21508
+ needsSignIn = res.status === 401 || res.status === 403;
21509
+ } catch {
21510
+ }
21511
+ return { version: capabilities?.version, capabilities, needsSignIn };
21512
+ }
21513
+ async function requestLink(fetchFn, url, client) {
21514
+ const res = await fetchFn(`${url}${V1}/link`, {
21515
+ method: "POST",
21516
+ headers: { "Content-Type": "application/json", Accept: "application/json" },
21517
+ body: JSON.stringify({ client })
21518
+ });
21519
+ if (res.status === 404 || res.status === 405) return null;
21520
+ const body = await readJson(res);
21521
+ if (!res.ok || !body?.linkId) {
21522
+ throw new Error(body?.error ? plainText2(body.error) : `The server refused the link request (${res.status})`);
21523
+ }
21524
+ let verificationUrl;
21525
+ try {
21526
+ verificationUrl = new URL(String(body.verificationUrl), `${url}/`);
21527
+ } catch {
21528
+ throw new Error("The server sent an invalid approval page address");
21529
+ }
21530
+ if (verificationUrl.protocol !== "https:" && verificationUrl.protocol !== "http:") {
21531
+ throw new Error("The server sent an invalid approval page address");
21532
+ }
21533
+ return {
21534
+ linkId: String(body.linkId),
21535
+ userCode: plainText2(body.userCode),
21536
+ verificationUrl: verificationUrl.toString(),
21537
+ interval: Math.max(1, Number(body.interval) || 2),
21538
+ expiresIn: Number(body.expiresIn) || 600
21539
+ };
21540
+ }
21541
+ async function pollLink(fetchFn, url, linkId) {
21542
+ const res = await fetchFn(`${url}${V1}/link/${encodeURIComponent(linkId)}`, { headers: { Accept: "application/json" } });
21543
+ if (res.status === 404) return { status: "expired" };
21544
+ const body = await readJson(res);
21545
+ if (!res.ok || !body) throw new Error(`Link status unavailable (${res.status})`);
21546
+ if (body.status === "approved" && body.token) return { status: "approved", token: body.token, userName: body.userName };
21547
+ if (body.status === "denied") return { status: "denied" };
21548
+ if (body.status === "expired") return { status: "expired" };
21549
+ return { status: "pending" };
21550
+ }
21551
+ async function createTokenWithPassword(fetchFn, url, username, password, tokenName) {
21552
+ const login = await fetchFn(`${url}${V1}/sessions`, {
21553
+ method: "POST",
21554
+ headers: { "Content-Type": "application/json" },
21555
+ body: JSON.stringify({ username, password })
21556
+ });
21557
+ const session = await readJson(login);
21558
+ if (!login.ok || !session?.token) {
21559
+ throw new Error(login.status === 404 ? "This server has no user sign-in" : plainText2(session?.error ?? "Sign-in failed"));
21560
+ }
21561
+ const auth = { Authorization: `Bearer ${session.token}` };
21562
+ try {
21563
+ for (let suffix = 1; ; suffix++) {
21564
+ const name = suffix === 1 ? tokenName : `${tokenName.slice(0, 60)} ${suffix}`;
21565
+ const created = await fetchFn(`${url}${V1}/tokens`, {
21566
+ method: "POST",
21567
+ headers: { "Content-Type": "application/json", ...auth },
21568
+ body: JSON.stringify({ name })
21569
+ });
21570
+ const body = await readJson(created);
21571
+ if (created.status === 409 && suffix < 20) continue;
21572
+ if (!created.ok || !body?.token) throw new Error(body?.error ? plainText2(body.error) : `Could not create a token (${created.status})`);
21573
+ return body.token;
21574
+ }
21575
+ } finally {
21576
+ await fetchFn(`${url}${V1}/sessions/current`, { method: "DELETE", headers: auth }).catch(() => void 0);
21577
+ }
21578
+ }
21579
+ function deviceName(appName = "Qodalis CLI") {
21580
+ const nav = globalThis.navigator;
21581
+ const ua = nav?.userAgent ?? "";
21582
+ const platform = nav?.userAgentData?.platform || (/Android/i.test(ua) ? "Android" : /iPhone|iPad/i.test(ua) ? "iOS" : /Mac/i.test(ua) ? "macOS" : /Windows/i.test(ua) ? "Windows" : /Linux/i.test(ua) ? "Linux" : "");
21583
+ const name = platform ? `${appName} on ${platform}` : appName;
21584
+ return name.replace(/[^A-Za-z0-9._ -]/g, "-").slice(0, 64);
21585
+ }
21586
+
21587
+ // src/lib/server/linked/cli-server-link-processors.ts
21588
+ var fetchOf = (context2) => (url, init) => context2.http.fetch(url, init);
21589
+ function sleep(ms, signal) {
21590
+ return new Promise((resolve) => {
21591
+ const timer = setTimeout(resolve, ms);
21592
+ signal?.addEventListener("abort", () => {
21593
+ clearTimeout(timer);
21594
+ resolve();
21595
+ }, { once: true });
21596
+ });
21597
+ }
21598
+ function linkingDisabled(context2) {
21599
+ if (userServersEnabled(context2.options)) return false;
21600
+ context2.writer.writeError(
21601
+ context2.translator.t("cli.server.link.disabled", "This app does not allow linking servers from the terminal.")
21602
+ );
21603
+ return true;
21604
+ }
21605
+ function parseAddArgs(value) {
21606
+ const words = splitShellWords(value ?? "");
21607
+ if (words.length >= 2) return { name: words[0], url: words[1] };
21608
+ if (words.length === 1) {
21609
+ return /[.:/]/.test(words[0]) ? { url: words[0] } : { name: words[0] };
21610
+ }
21611
+ return {};
21612
+ }
21613
+ async function replaceConnection(manager, config) {
21614
+ const current = [...manager.connections.keys()].find((n) => n.toLowerCase() === config.name.toLowerCase());
21615
+ const wasDefault = !!current && manager.defaultServer === current;
21616
+ if (current) manager.removeServer(current);
21617
+ const connection = await manager.addServer(config, { linked: true });
21618
+ if (wasDefault) manager.setDefaultServer(config.name);
21619
+ return connection;
21620
+ }
21621
+ var ServerAddProcessor = class {
21622
+ constructor() {
21623
+ this.command = "add";
21624
+ this.aliases = ["link"];
21625
+ this.description = "Link a server to this terminal (kept across reloads)";
21626
+ this.acceptsRawInput = true;
21627
+ this.valueRequired = false;
21628
+ this.parameters = [
21629
+ { name: "token", description: "API token (qcli_pat_\u2026) to link with (asked for, hidden, when given without a value)", required: false, type: "string" },
21630
+ { name: "default", description: "Make it the default server", required: false, type: "boolean" },
21631
+ { name: "no-browser", description: "Link with a token or a password, without the browser", required: false, type: "boolean" }
21632
+ ];
21633
+ }
21634
+ async processCommand(command2, context2) {
21635
+ const t = context2.translator;
21636
+ const { writer, reader } = context2;
21637
+ const manager = serverManagerOf(context2);
21638
+ if (!manager || linkingDisabled(context2)) return;
21639
+ const linked = linkedServersOf(context2);
21640
+ const args = parseAddArgs(command2.value);
21641
+ const rawUrl = args.url ?? await reader.readLine(t.t("cli.server.link.url_prompt", "Server URL: "), { placeholder: "https://my-server.example.com" });
21642
+ if (!rawUrl) return;
21643
+ const pageProtocol = globalThis.location?.protocol;
21644
+ const checked = checkServerUrl(rawUrl, pageProtocol);
21645
+ if (typeof checked === "string") {
21646
+ writer.writeError(checked);
21647
+ return;
21648
+ }
21649
+ const url = checked.url;
21650
+ if (!userServersAllow(context2.options, url)) {
21651
+ writer.writeError(t.t("cli.server.link.origin_not_allowed", "This app does not allow linking {url}.", { url }));
21652
+ return;
21653
+ }
21654
+ if (checked.mixedContent) {
21655
+ writer.writeWarning(t.t("cli.server.link.mixed_content", "This page is served over https: the browser may block requests to an http server."));
21656
+ }
21657
+ let probe;
21658
+ context2.spinner?.show(t.t("cli.server.link.checking", "Checking {url}\u2026", { url }));
21659
+ try {
21660
+ probe = await probeServer(fetchOf(context2), url);
21661
+ } catch (e) {
21662
+ writer.writeError(e.message);
21663
+ return;
21664
+ } finally {
21665
+ context2.spinner?.hide();
21666
+ }
21667
+ writeProbe(context2, url, probe);
21668
+ if (checked.insecure) {
21669
+ writer.writeWarning(t.t("cli.server.link.http_warning", "{url} is not encrypted (http): its token travels in clear text on the network.", { url }));
21670
+ const go = await reader.readConfirm(t.t("cli.server.link.http_confirm", "Link it anyway?"), false);
21671
+ if (!go) return;
21672
+ }
21673
+ let name = args.name;
21674
+ if (name) {
21675
+ if (!await this.nameAvailable(context2, manager, linked, name)) return;
21676
+ }
21677
+ const tokenFlag = command2.args?.["token"];
21678
+ const tokenArg = typeof tokenFlag === "string" && tokenFlag.trim() ? tokenFlag.trim() : void 0;
21679
+ const method = tokenFlag !== void 0 && tokenFlag !== false ? "token" : await this.chooseMethod(context2, probe, !!command2.args?.["no-browser"]);
21680
+ if (!method) return;
21681
+ if (method !== "none" && !await secretStoreOf(context2).isAvailable()) {
21682
+ writer.writeError(t.t("cli.server.link.no_secret_store", "Tokens cannot be kept safely on this page (no Web Crypto: serve it over https). The server was not linked."));
21683
+ return;
21684
+ }
21685
+ let token;
21686
+ let userName;
21687
+ if (method === "browser") {
21688
+ const result = await this.linkInBrowser(context2, url);
21689
+ if (!result) return;
21690
+ token = result.token;
21691
+ userName = result.userName;
21692
+ } else if (method === "token") {
21693
+ token = tokenArg ?? (await reader.readPassword(t.t("cli.server.link.token_prompt", "API token: ")))?.trim();
21694
+ if (!token) return;
21695
+ } else if (method === "password") {
21696
+ const username = await reader.readLine(t.t("cli.server.link.username_prompt", "Username: "));
21697
+ if (!username) return;
21698
+ const password = await reader.readPassword(t.t("cli.server.link.password_prompt", "Password: "));
21699
+ if (!password) return;
21700
+ try {
21701
+ token = await createTokenWithPassword(fetchOf(context2), url, username.trim(), password, deviceName());
21702
+ userName = username.trim();
21703
+ } catch (e) {
21704
+ writer.writeError(e.message);
21705
+ return;
21706
+ }
21707
+ }
21708
+ if (!name) {
21709
+ const suggested = suggestServerName(url, (n) => manager.connections.has(n));
21710
+ const answer = await reader.readLine(t.t("cli.server.link.name_prompt", "Name for this server: "), {
21711
+ default: suggested,
21712
+ placeholder: suggested,
21713
+ validate: (value) => !value || SERVER_NAME_PATTERN.test(value.trim()) ? null : t.t("cli.server.link.invalid_name", "Letters, digits, - and _ (at most 32), starting with a letter or digit")
21714
+ });
21715
+ if (answer === null) return;
21716
+ name = answer.trim() || suggested;
21717
+ if (!await this.nameAvailable(context2, manager, linked, name)) return;
21718
+ }
21719
+ const server = { name, url, addedAt: Date.now() };
21720
+ const previous = (await linked.get(name))?.name;
21721
+ if (previous && previous !== name) await linked.remove(previous);
21722
+ await linked.save(server, token);
21723
+ const connection = await replaceConnection(manager, linkedServerConfig(server, token));
21724
+ if (connection.connected) {
21725
+ writer.writeSuccess(
21726
+ t.t("cli.server.link.linked", "Linked {name}: {count} commands{user}.", {
21727
+ name,
21728
+ count: connection.commands.length,
21729
+ user: userName ? t.t("cli.server.link.as_user", " as {user}", { user: userName }) : ""
21730
+ })
21731
+ );
21732
+ } else if (token && connection.needsSignIn) {
21733
+ writer.writeWarning(rejectedTokenMessage(name));
21734
+ } else if (connection.needsSignIn) {
21735
+ writer.writeWarning(t.t("cli.server.link.needs_sign_in", "Linked {name}, but its commands need a sign-in: link it with a token or through the browser.", { name }));
21736
+ } else {
21737
+ writer.writeWarning(t.t("cli.server.link.unreachable", "Linked {name}, but it is unreachable now: its commands load when it is back.", { name }));
21738
+ }
21739
+ if (command2.args?.["default"]) {
21740
+ manager.setDefaultServer(name);
21741
+ context2.state.updateState({ defaultServer: name });
21742
+ writer.writeln(t.t("cli.server.link.now_default", " {name} is the default server.", { name }));
21743
+ }
21744
+ }
21745
+ async nameAvailable(context2, manager, linked, name) {
21746
+ const t = context2.translator;
21747
+ if (!SERVER_NAME_PATTERN.test(name)) {
21748
+ context2.writer.writeError(t.t("cli.server.link.invalid_name", "Letters, digits, - and _ (at most 32), starting with a letter or digit"));
21749
+ return false;
21750
+ }
21751
+ const existing = [...manager.connections.keys()].find((n) => n.toLowerCase() === name.toLowerCase());
21752
+ if (existing && !manager.isLinked(existing)) {
21753
+ context2.writer.writeError(t.t("cli.server.link.host_server", "'{name}' is configured by the app and cannot be replaced.", { name: existing }));
21754
+ return false;
21755
+ }
21756
+ if (existing || await linked.get(name)) {
21757
+ const replace = await context2.reader.readConfirm(
21758
+ t.t("cli.server.link.replace_confirm", "A linked server '{name}' exists. Replace it?", { name }),
21759
+ false
21760
+ );
21761
+ return !!replace;
21762
+ }
21763
+ return true;
21764
+ }
21765
+ async chooseMethod(context2, probe, noBrowser) {
21766
+ const t = context2.translator;
21767
+ const options = [
21768
+ ...noBrowser ? [] : [{ label: t.t("cli.server.link.method_browser", "Open the server in the browser and approve (recommended)"), value: "browser" }],
21769
+ { label: t.t("cli.server.link.method_token", "Paste an API token"), value: "token" },
21770
+ { label: t.t("cli.server.link.method_password", "Sign in with user and password"), value: "password" },
21771
+ ...probe.needsSignIn ? [] : [{ label: t.t("cli.server.link.method_none", "No sign-in (open server)"), value: "none" }]
21772
+ ];
21773
+ const choice = await context2.reader.readSelect(t.t("cli.server.link.method_prompt", "How do you want to link it?"), options);
21774
+ return choice ?? null;
21775
+ }
21776
+ /** The device flow: a link request, the page to approve it, then polling until a decision. */
21777
+ async linkInBrowser(context2, url) {
21778
+ const t = context2.translator;
21779
+ const { writer } = context2;
21780
+ const fetchFn = fetchOf(context2);
21781
+ let request;
21782
+ try {
21783
+ request = await requestLink(fetchFn, url, deviceName());
21784
+ } catch (e) {
21785
+ writer.writeError(e.message);
21786
+ return null;
21787
+ }
21788
+ if (!request) {
21789
+ writer.writeError(t.t("cli.server.link.no_browser_link", "This server cannot link through the browser (it needs the users plugin, 1.0.0-beta.25 or later). Use server add --no-browser."));
21790
+ return null;
21791
+ }
21792
+ writer.writeln();
21793
+ writer.writeln(t.t("cli.server.link.open_page", " Open this page, sign in and approve:"));
21794
+ writer.write(" ");
21795
+ writer.writeLink(request.verificationUrl, request.verificationUrl);
21796
+ writer.writeln();
21797
+ writer.writeln(
21798
+ t.t("cli.server.link.compare_code", " Check that it shows the code {code}", {
21799
+ code: writer.wrapInColor(request.userCode, CliForegroundColor.Yellow)
21800
+ })
21801
+ );
21802
+ writer.writeln();
21803
+ try {
21804
+ globalThis.open?.(request.verificationUrl, "_blank");
21805
+ } catch {
21806
+ }
21807
+ const deadline = Date.now() + request.expiresIn * 1e3;
21808
+ const cancelled = () => {
21809
+ context2.spinner?.hide();
21810
+ const minutes = Math.max(1, Math.ceil((deadline - Date.now()) / 6e4));
21811
+ writer.writeInfo(
21812
+ t.t("cli.server.link.cancelled", "Cancelled. The request expires on the server in {minutes} min.", { minutes })
21813
+ );
21814
+ return null;
21815
+ };
21816
+ context2.spinner?.show(t.t("cli.server.link.waiting", "Waiting for the approval\u2026 (Ctrl+C to cancel)"));
21817
+ try {
21818
+ while (Date.now() < deadline) {
21819
+ if (context2.signal?.aborted) return cancelled();
21820
+ await sleep(request.interval * 1e3, context2.signal);
21821
+ if (context2.signal?.aborted) return cancelled();
21822
+ let poll;
21823
+ try {
21824
+ poll = await pollLink(fetchFn, url, request.linkId);
21825
+ } catch {
21826
+ continue;
21827
+ }
21828
+ if (poll.status === "approved") return { token: poll.token, userName: poll.userName };
21829
+ if (poll.status === "denied") {
21830
+ context2.spinner?.hide();
21831
+ writer.writeError(t.t("cli.server.link.denied", "The link request was denied."));
21832
+ return null;
21833
+ }
21834
+ if (poll.status === "expired") break;
21835
+ }
21836
+ } finally {
21837
+ context2.spinner?.hide();
21838
+ }
21839
+ writer.writeError(t.t("cli.server.link.expired", "The link request expired. Run server add again."));
21840
+ return null;
21841
+ }
21842
+ writeDescription(context2) {
21843
+ const t = context2.translator;
21844
+ writeWrapped(
21845
+ context2,
21846
+ t.t("cli.server.add.help.description", "Links a Node, .NET or Python server to this terminal: its commands are available at once and after every reload. Asks for what is missing: the URL, how to sign in (approve in the browser, paste an API token, or a user and password) and a name. Tokens are kept encrypted and never shown again; only https servers can be linked (http for localhost and private addresses).")
21847
+ );
21848
+ context2.writer.writeln();
21849
+ writeUsage(context2, [
21850
+ ["server add", t.t("cli.server.add.help.interactive", "Asks for everything")],
21851
+ ["server add <url>", t.t("cli.server.add.help.url", "Link this server")],
21852
+ ["server add <name> <url> --token [token]", t.t("cli.server.add.help.token", "Link with an API token (scripts)")]
21853
+ ]);
21854
+ context2.writer.writeln();
21855
+ writeExamples(context2, [
21856
+ ["server add https://cli.example.com", t.t("cli.server.add.help.example_browser", "Approve in the browser")],
21857
+ ["server add home http://192.168.1.20:8047 --no-browser", t.t("cli.server.add.help.example_lan", "A server on the local network")]
21858
+ ]);
21859
+ }
21860
+ };
21861
+ function writeProbe(context2, url, probe) {
21862
+ const { writer } = context2;
21863
+ const caps = probe.capabilities;
21864
+ const details = [caps?.os, caps?.shell ? "shell" : null, caps?.streaming ? "streaming" : null, probe.needsSignIn ? "sign-in required" : null].filter(Boolean).join(" \xB7 ");
21865
+ writer.writeSuccess(
21866
+ `${context2.translator.t("cli.server.link.found", "Qodalis CLI server")} ${probe.version ?? ""} ${writer.wrapInColor(url, CliForegroundColor.Cyan)}${details ? ` (${details})` : ""}`.replace(/\s+/g, " ")
21867
+ );
21868
+ }
21869
+ var ServerRemoveProcessor = class {
21870
+ constructor() {
21871
+ this.command = "remove";
21872
+ this.aliases = ["rm", "unlink"];
21873
+ this.description = "Unlink a server linked with server add (its token is deleted)";
21874
+ this.acceptsRawInput = true;
21875
+ this.valueRequired = true;
21876
+ }
21877
+ async processCommand(command2, context2) {
21878
+ const t = context2.translator;
21879
+ const manager = serverManagerOf(context2);
21880
+ if (!manager) return;
21881
+ const name = splitShellWords(command2.value ?? "")[0] ?? "";
21882
+ const linked = linkedServersOf(context2);
21883
+ const connected = [...manager.connections.keys()].find((n) => n.toLowerCase() === name.toLowerCase());
21884
+ const saved = await linked.get(name);
21885
+ if (connected && !manager.isLinked(connected) && !saved) {
21886
+ context2.writer.writeError(t.t("cli.server.link.cannot_remove_host", "'{name}' is configured by the app and cannot be removed.", { name: connected }));
21887
+ return;
21888
+ }
21889
+ if (!saved && !connected) {
21890
+ context2.writer.writeError(t.t("cli.server.link.unknown_linked", "No linked server '{name}'. See server list.", { name }));
21891
+ return;
21892
+ }
21893
+ await linked.remove(saved?.name ?? name);
21894
+ const linkedConnection = connected && manager.isLinked(connected) ? connected : void 0;
21895
+ if (linkedConnection) manager.removeServer(linkedConnection);
21896
+ const removedName = saved?.name ?? linkedConnection ?? name;
21897
+ if (linkedConnection && context2.state.getState().defaultServer === linkedConnection) {
21898
+ context2.state.updateState({ defaultServer: null });
21899
+ }
21900
+ context2.writer.writeSuccess(t.t("cli.server.link.removed", "Unlinked {name}; its token is deleted from this device.", { name: removedName }));
21901
+ context2.writer.writeln(t.t("cli.server.link.revoke_hint", " The token stays valid on the server until it expires: revoke it there with token revoke."));
21902
+ }
21903
+ };
21904
+ var ServerEditProcessor = class {
21905
+ constructor() {
21906
+ this.command = "edit";
21907
+ this.description = "Change the URL or the token of a linked server";
21908
+ this.acceptsRawInput = true;
21909
+ this.valueRequired = true;
21910
+ this.parameters = [
21911
+ { name: "url", description: "New URL", required: false, type: "string" },
21912
+ { name: "token", description: "New API token (asked for, hidden, when given without a value)", required: false, type: "string" }
21913
+ ];
21914
+ }
21915
+ async processCommand(command2, context2) {
21916
+ const t = context2.translator;
21917
+ const { writer } = context2;
21918
+ const manager = serverManagerOf(context2);
21919
+ if (!manager || linkingDisabled(context2)) return;
21920
+ const name = splitShellWords(command2.value ?? "")[0] ?? "";
21921
+ const connected = [...manager.connections.keys()].find((n) => n.toLowerCase() === name.toLowerCase());
21922
+ if (connected && !manager.isLinked(connected)) {
21923
+ writer.writeError(t.t("cli.server.link.cannot_edit_host", "'{name}' is configured by the app and cannot be edited.", { name: connected }));
21924
+ return;
21925
+ }
21926
+ const linked = linkedServersOf(context2);
21927
+ const saved = await linked.get(name);
21928
+ if (!saved) {
21929
+ writer.writeError(t.t("cli.server.link.unknown_linked", "No linked server '{name}'. See server list.", { name }));
21930
+ return;
21931
+ }
21932
+ const urlArg = command2.args?.["url"];
21933
+ const tokenArg = command2.args?.["token"];
21934
+ if (urlArg === void 0 && tokenArg === void 0) {
21935
+ writer.writeError(t.t("cli.server.link.edit_nothing", "Give --url <url> and/or --token."));
21936
+ return;
21937
+ }
21938
+ let url = saved.url;
21939
+ if (typeof urlArg === "string") {
21940
+ const checked = checkServerUrl(urlArg, globalThis.location?.protocol);
21941
+ if (typeof checked === "string") {
21942
+ writer.writeError(checked);
21943
+ return;
21944
+ }
21945
+ if (!userServersAllow(context2.options, checked.url)) {
21946
+ writer.writeError(t.t("cli.server.link.origin_not_allowed", "This app does not allow linking {url}.", { url: checked.url }));
21947
+ return;
21948
+ }
21949
+ if (checked.insecure) {
21950
+ writer.writeWarning(t.t("cli.server.link.http_warning", "{url} is not encrypted (http): its token travels in clear text on the network.", { url: checked.url }));
21951
+ if (!await context2.reader.readConfirm(t.t("cli.server.link.http_confirm", "Link it anyway?"), false)) return;
21952
+ }
21953
+ if (checked.mixedContent) {
21954
+ writer.writeWarning(t.t("cli.server.link.mixed_content", "This page is served over https: the browser may block requests to an http server."));
21955
+ }
21956
+ url = checked.url;
21957
+ }
21958
+ let token = await linked.token(saved.name);
21959
+ if (tokenArg !== void 0) {
21960
+ token = typeof tokenArg === "string" && tokenArg.trim() ? tokenArg.trim() : (await context2.reader.readPassword(t.t("cli.server.link.token_prompt", "API token: ")))?.trim();
21961
+ if (!token) return;
21962
+ }
21963
+ const updated = { ...saved, url };
21964
+ await linked.save(updated, token);
21965
+ const connection = await replaceConnection(manager, linkedServerConfig(updated, token));
21966
+ if (connection.connected) {
21967
+ writer.writeSuccess(t.t("cli.server.link.updated", "Updated {name}: {count} commands.", { name: saved.name, count: connection.commands.length }));
21968
+ } else if (token && connection.needsSignIn) {
21969
+ writer.writeWarning(rejectedTokenMessage(saved.name));
21970
+ } else {
21971
+ writer.writeWarning(t.t("cli.server.link.updated_unreachable", "Updated {name}, but it is unreachable now.", { name: saved.name }));
21972
+ }
21973
+ }
21974
+ };
21975
+ var ServerForgetAllProcessor = class {
21976
+ constructor() {
21977
+ this.command = "forget-all";
21978
+ this.description = "Unlink every linked server and delete every saved token";
21979
+ }
21980
+ async processCommand(_command, context2) {
21981
+ const t = context2.translator;
21982
+ const manager = serverManagerOf(context2);
21983
+ if (!manager) return;
21984
+ const linked = linkedServersOf(context2);
21985
+ const servers = await linked.list();
21986
+ if (servers.length === 0) {
21987
+ context2.writer.writeInfo(t.t("cli.server.link.none_linked", "No linked servers. Link one with server add."));
21988
+ return;
21989
+ }
21990
+ const ok = await context2.reader.readConfirm(
21991
+ t.t("cli.server.link.forget_all_confirm", "Unlink every linked server ({count}) and delete their saved tokens?", { count: servers.length }),
21992
+ false
21993
+ );
21994
+ if (!ok) return;
21995
+ await linked.clear();
21996
+ for (const name of [...manager.connections.keys()]) {
21997
+ if (manager.isLinked(name)) manager.removeServer(name);
21998
+ }
21999
+ const saved = context2.state.getState().defaultServer;
22000
+ if (saved && !manager.connections.has(saved)) context2.state.updateState({ defaultServer: null });
22001
+ context2.writer.writeSuccess(t.t("cli.server.link.forgot_all", "Every linked server and saved token is gone."));
22002
+ }
22003
+ };
22004
+ async function linkedTokenLabels(context2, manager) {
22005
+ const labels = /* @__PURE__ */ new Map();
22006
+ const names = [...manager.connections.keys()].filter((n) => manager.isLinked(n));
22007
+ if (names.length === 0) return labels;
22008
+ const linked = linkedServersOf(context2);
22009
+ for (const name of names) {
22010
+ const token = await linked.token(name).catch(() => void 0);
22011
+ if (token) labels.set(name, maskToken(token));
22012
+ }
22013
+ return labels;
22014
+ }
22015
+
21099
22016
  // src/lib/server/cli-server-command-processor.ts
21100
22017
  var CliServerCommandProcessor = class {
21101
22018
  constructor() {
@@ -21117,7 +22034,11 @@ var CliServerCommandProcessor = class {
21117
22034
  new ServerStatusProcessor(),
21118
22035
  new ServerCommandsProcessor(),
21119
22036
  new ServerReconnectProcessor(),
21120
- new ServerDefaultProcessor()
22037
+ new ServerDefaultProcessor(),
22038
+ new ServerAddProcessor(),
22039
+ new ServerRemoveProcessor(),
22040
+ new ServerEditProcessor(),
22041
+ new ServerForgetAllProcessor()
21121
22042
  ];
21122
22043
  }
21123
22044
  /** Restore the default server saved by `server default <name>`. */
@@ -21133,7 +22054,7 @@ var CliServerCommandProcessor = class {
21133
22054
  const t = context2.translator;
21134
22055
  const manager = serverManagerOf(context2);
21135
22056
  if (!manager) return;
21136
- writeServerTable(context2, manager);
22057
+ writeServerTable(context2, manager, await linkedTokenLabels(context2, manager));
21137
22058
  context2.writer.writeln();
21138
22059
  writeHint(context2, "help server", t.t("cli.server.help.see_everything_you_can", "to see everything you can do"));
21139
22060
  }
@@ -21164,6 +22085,10 @@ var CliServerCommandProcessor = class {
21164
22085
  ["server commands|cmds [name]", t.t("cli.server.help.commands_each_server_provides", "Commands each server provides")],
21165
22086
  ["server reconnect|rc [name]", t.t("cli.server.help.connect_again_unreachable_servers", "Connect again (unreachable servers, or one)")],
21166
22087
  ["server default|use [name]", t.t("cli.server.help.show_set_server_shared", "Show or set the server for shared commands")],
22088
+ ["server add [name] [url]", t.t("cli.server.help.add", "Link a server to this terminal (kept across reloads)")],
22089
+ ["server edit <name> --url|--token", t.t("cli.server.help.edit", "Change a linked server")],
22090
+ ["server remove <name>", t.t("cli.server.help.remove", "Unlink a server and delete its token")],
22091
+ ["server forget-all", t.t("cli.server.help.forget_all", "Unlink every linked server")],
21167
22092
  ["ssh [name] [command]", t.t("cli.server.help.open_shell_server_run", "Open a shell on a server, or run one command")]
21168
22093
  ]);
21169
22094
  writer.writeln();
@@ -21186,7 +22111,7 @@ var ServerListProcessor = class {
21186
22111
  }
21187
22112
  async processCommand(_command, context2) {
21188
22113
  const manager = serverManagerOf(context2);
21189
- if (manager) writeServerTable(context2, manager);
22114
+ if (manager) writeServerTable(context2, manager, await linkedTokenLabels(context2, manager));
21190
22115
  }
21191
22116
  writeDescription(context2) {
21192
22117
  const t = context2.translator;
@@ -21198,7 +22123,7 @@ var ServerListProcessor = class {
21198
22123
  writeUsage(context2, [["server list", ""], ["server ls", ""]]);
21199
22124
  }
21200
22125
  };
21201
- function writeServerTable(context2, manager) {
22126
+ function writeServerTable(context2, manager, tokenLabels = /* @__PURE__ */ new Map()) {
21202
22127
  const t = context2.translator;
21203
22128
  const { writer } = context2;
21204
22129
  if (manager.connections.size === 0) {
@@ -21218,12 +22143,13 @@ function writeServerTable(context2, manager) {
21218
22143
  connected ? connection.capabilities?.version ?? "-" : "-",
21219
22144
  connected ? `v${connection.apiVersion}` : "-",
21220
22145
  connected ? String(connection.commands.length) : "-",
21221
- connected ? connection.capabilities?.shell ? "yes" : "no" : "-"
22146
+ connected ? connection.capabilities?.shell ? "yes" : "no" : "-",
22147
+ manager.isLinked(name) ? `linked ${tokenLabels.get(name) ?? ""}`.trim() : "app"
21222
22148
  ]);
21223
22149
  }
21224
22150
  writer.writeln(`\u{1F5A5} Servers (${manager.connections.size}):`);
21225
22151
  writer.writeTable(
21226
- ["Name", "Status", "URL", "Version", "API", "Commands", "Shell"],
22152
+ ["Name", "Status", "URL", "Version", "API", "Commands", "Shell", "Source"],
21227
22153
  rows
21228
22154
  );
21229
22155
  if (manager.defaultServer) {
@@ -22486,6 +23412,7 @@ function resolveCompletionPath(tokens, tokenIndex, processors, currentToken = ""
22486
23412
  let descriptors = [];
22487
23413
  let candidates = processors;
22488
23414
  let isFlagValue = false;
23415
+ let flag;
22489
23416
  for (let i = 0; i < parsed.length; i++) {
22490
23417
  const token = parsed[i];
22491
23418
  if (token.kind === "flag") {
@@ -22494,6 +23421,7 @@ function resolveCompletionPath(tokens, tokenIndex, processors, currentToken = ""
22494
23421
  if (parsed[i + 1].kind === "word") i++;
22495
23422
  } else if (!currentToken.startsWith("-")) {
22496
23423
  isFlagValue = true;
23424
+ flag = findParameter(token.name, descriptors);
22497
23425
  }
22498
23426
  continue;
22499
23427
  }
@@ -22507,7 +23435,11 @@ function resolveCompletionPath(tokens, tokenIndex, processors, currentToken = ""
22507
23435
  }
22508
23436
  candidates = match?.processors?.length ? match.processors : void 0;
22509
23437
  }
22510
- return { processor, descriptors, subCommands: candidates, isFlagValue };
23438
+ return { processor, descriptors, subCommands: candidates, isFlagValue, flag };
23439
+ }
23440
+ function findParameter(name, descriptors) {
23441
+ const bare = (a) => a.replace(/^-+/, "");
23442
+ return descriptors.find((d) => d.name === name) ?? descriptors.find((d) => d.aliases?.some((a) => bare(a) === name));
22511
23443
  }
22512
23444
  function toParsedToken(text) {
22513
23445
  const flag = /^(--?)([a-zA-Z0-9_][^\s=]*)(=.*)?$/.exec(text);
@@ -22606,6 +23538,36 @@ var CliParameterCompletionProvider = class {
22606
23538
  }
22607
23539
  };
22608
23540
 
23541
+ // src/lib/completion/cli-parameter-value-completion-provider.ts
23542
+ var CliParameterValueCompletionProvider = class {
23543
+ constructor(registry) {
23544
+ this.registry = registry;
23545
+ this.priority = 190;
23546
+ }
23547
+ getCompletions(context2) {
23548
+ const { tokens, tokenIndex, token } = context2;
23549
+ if (tokens.length === 0) return [];
23550
+ const inline = /^(--?)([^\s=]+)=(.*)$/.exec(token);
23551
+ if (inline) {
23552
+ const path2 = resolveCompletionPath(tokens, tokenIndex, this.registry.processors, token);
23553
+ const values2 = findParameter(inline[2], path2.descriptors)?.allowedValues;
23554
+ if (!values2?.length) return [];
23555
+ const prefix = `${inline[1]}${inline[2]}=`;
23556
+ return matchIgnoringCase(values2, inline[3]).map((v) => `${prefix}${v}`);
23557
+ }
23558
+ if (token.startsWith("-")) return [];
23559
+ const path = resolveCompletionPath(tokens, tokenIndex, this.registry.processors, token);
23560
+ const values = path.isFlagValue ? path.flag?.allowedValues : void 0;
23561
+ return values?.length ? matchIgnoringCase(values, token) : [];
23562
+ }
23563
+ };
23564
+ function matchIgnoringCase(values, prefix) {
23565
+ const exact = getAccelerator().prefixMatch(values, prefix);
23566
+ if (exact.length) return exact;
23567
+ const lower = prefix.toLowerCase();
23568
+ return values.filter((v) => v.toLowerCase().startsWith(lower));
23569
+ }
23570
+
22609
23571
  // src/lib/completion/cli-service-name-completion-provider.ts
22610
23572
  var SERVICE_NAME_SUBCOMMANDS = /* @__PURE__ */ new Set([
22611
23573
  "start",
@@ -22893,6 +23855,7 @@ function createCompletionProviders(deps) {
22893
23855
  new CliServerNameCompletionProvider(serverManager),
22894
23856
  new CliConfigKeyCompletionProvider(registry),
22895
23857
  new CliCommandCompletionProvider(registry),
23858
+ new CliParameterValueCompletionProvider(registry),
22896
23859
  new CliParameterCompletionProvider(registry, executor)
22897
23860
  ];
22898
23861
  const fromPlugins = services.getAll(
@@ -22901,6 +23864,143 @@ function createCompletionProviders(deps) {
22901
23864
  return [...fromPlugins, ...builtIn];
22902
23865
  }
22903
23866
 
23867
+ // src/lib/engine/cli-touch-scroll.ts
23868
+ var SLOP_PX = 8;
23869
+ var MIN_FLING = 0.15;
23870
+ var FRICTION = 0.997;
23871
+ var VELOCITY_WINDOW_MS = 100;
23872
+ var KEYBOARD_HOLD_MS = 400;
23873
+ function enableTouchScroll(terminal, paused = () => false) {
23874
+ const element = terminal.element;
23875
+ if (!element) {
23876
+ return () => {
23877
+ };
23878
+ }
23879
+ let startY = 0;
23880
+ let lastY = 0;
23881
+ let scrolling = false;
23882
+ let carry = 0;
23883
+ let moves = [];
23884
+ let frame = 0;
23885
+ let holdTimer = 0;
23886
+ let holding = false;
23887
+ const textarea = terminal.textarea;
23888
+ const keyboardOpen = () => window.innerHeight < screen.height * 0.75;
23889
+ const holdKeyboard = () => {
23890
+ clearTimeout(holdTimer);
23891
+ if (holding || !textarea || textarea.hasAttribute("inputmode") || keyboardOpen()) {
23892
+ return;
23893
+ }
23894
+ holding = true;
23895
+ textarea.setAttribute("inputmode", "none");
23896
+ };
23897
+ const releaseKeyboard = () => {
23898
+ clearTimeout(holdTimer);
23899
+ if (holding) {
23900
+ holding = false;
23901
+ textarea?.removeAttribute("inputmode");
23902
+ }
23903
+ };
23904
+ const rowHeight = () => {
23905
+ const screen2 = element.querySelector(".xterm-screen");
23906
+ return (screen2?.clientHeight || element.clientHeight) / terminal.rows || 16;
23907
+ };
23908
+ const scrollBy = (pixels) => {
23909
+ carry += pixels;
23910
+ const height = rowHeight();
23911
+ const rows = Math.trunc(carry / height);
23912
+ if (rows === 0) {
23913
+ return true;
23914
+ }
23915
+ carry -= rows * height;
23916
+ const buffer = terminal.buffer.active;
23917
+ const before = buffer.viewportY;
23918
+ terminal.scrollLines(-rows);
23919
+ const moved = buffer.viewportY !== before;
23920
+ if (!moved) {
23921
+ carry = 0;
23922
+ }
23923
+ return moved;
23924
+ };
23925
+ const stopGlide = () => {
23926
+ cancelAnimationFrame(frame);
23927
+ frame = 0;
23928
+ };
23929
+ const glide = (velocity) => {
23930
+ let previous = performance.now();
23931
+ const step = (now) => {
23932
+ const elapsed = Math.min(now - previous, 50);
23933
+ previous = now;
23934
+ velocity *= Math.pow(FRICTION, elapsed);
23935
+ if (Math.abs(velocity) < 0.02 || !scrollBy(velocity * elapsed)) {
23936
+ frame = 0;
23937
+ return;
23938
+ }
23939
+ frame = requestAnimationFrame(step);
23940
+ };
23941
+ frame = requestAnimationFrame(step);
23942
+ };
23943
+ const onStart = (e) => {
23944
+ stopGlide();
23945
+ scrolling = false;
23946
+ if (e.touches.length !== 1 || paused()) {
23947
+ return;
23948
+ }
23949
+ holdKeyboard();
23950
+ startY = lastY = e.touches[0].clientY;
23951
+ carry = 0;
23952
+ moves = [{ y: startY, time: e.timeStamp }];
23953
+ };
23954
+ const onMove = (e) => {
23955
+ if (e.touches.length !== 1 || paused() || moves.length === 0) {
23956
+ return;
23957
+ }
23958
+ const y = e.touches[0].clientY;
23959
+ if (!scrolling && Math.abs(y - startY) < SLOP_PX) {
23960
+ return;
23961
+ }
23962
+ scrolling = true;
23963
+ e.preventDefault();
23964
+ scrollBy(y - lastY);
23965
+ lastY = y;
23966
+ moves.push({ y, time: e.timeStamp });
23967
+ while (moves.length > 2 && e.timeStamp - moves[0].time > VELOCITY_WINDOW_MS) {
23968
+ moves.shift();
23969
+ }
23970
+ };
23971
+ const onEnd = (e) => {
23972
+ if (!scrolling) {
23973
+ releaseKeyboard();
23974
+ moves = [];
23975
+ return;
23976
+ }
23977
+ e.preventDefault();
23978
+ scrolling = false;
23979
+ holdTimer = window.setTimeout(releaseKeyboard, KEYBOARD_HOLD_MS);
23980
+ const first = moves[0];
23981
+ const last = moves[moves.length - 1];
23982
+ moves = [];
23983
+ const elapsed = last.time - first.time;
23984
+ const velocity = elapsed > 0 && e.timeStamp - last.time < VELOCITY_WINDOW_MS ? (last.y - first.y) / elapsed : 0;
23985
+ if (Math.abs(velocity) >= MIN_FLING) {
23986
+ glide(velocity);
23987
+ }
23988
+ };
23989
+ const options = { passive: false, capture: true };
23990
+ element.addEventListener("touchstart", onStart, options);
23991
+ element.addEventListener("touchmove", onMove, options);
23992
+ element.addEventListener("touchend", onEnd, options);
23993
+ element.addEventListener("touchcancel", onEnd, options);
23994
+ return () => {
23995
+ stopGlide();
23996
+ releaseKeyboard();
23997
+ element.removeEventListener("touchstart", onStart, options);
23998
+ element.removeEventListener("touchmove", onMove, options);
23999
+ element.removeEventListener("touchend", onEnd, options);
24000
+ element.removeEventListener("touchcancel", onEnd, options);
24001
+ };
24002
+ }
24003
+
22904
24004
  // src/lib/engine/cli-engine.ts
22905
24005
  var CliEngine = class {
22906
24006
  constructor(container, options) {
@@ -23012,6 +24112,12 @@ var CliEngine = class {
23012
24112
  this.serializeAddon = setup.serializeAddon;
23013
24113
  this.dragDropService = setup.dragDropService;
23014
24114
  this.wheelListener = setup.wheelListener;
24115
+ if (this.options?.touchScroll !== false) {
24116
+ this.disposeTouchScroll = enableTouchScroll(
24117
+ this.terminal,
24118
+ () => !!this.executionContext?.contextProcessor
24119
+ );
24120
+ }
23015
24121
  this.handleResize();
23016
24122
  }
23017
24123
  /** 2. Service container, boot service, executor and execution context. */
@@ -23113,29 +24219,31 @@ var CliEngine = class {
23113
24219
  ]);
23114
24220
  return services.getRequired(CliServerManager_TOKEN);
23115
24221
  }
23116
- /** 4. Connect to the configured servers, one boot step each. */
24222
+ /**
24223
+ * 4. Connect to the configured servers, one boot step each, then to the
24224
+ * servers the user linked (`server add`).
24225
+ */
23117
24226
  async connectServers(serverManager, bootLog) {
23118
- const servers = this.options?.servers;
23119
- if (!servers || servers.length === 0) return;
23120
24227
  const ctx = this.executionContext;
23121
- bootLog.setCurrent("Connecting to servers");
23122
- await serverManager.connectAll(
23123
- servers,
23124
- {
23125
- warn: (msg) => {
23126
- ctx.logger.warn(msg);
23127
- bootLog.warn(msg);
23128
- bootLog.completeStep();
23129
- },
23130
- info: (msg) => {
23131
- ctx.logger.info(msg);
23132
- bootLog.ok(msg);
23133
- bootLog.completeStep();
23134
- }
24228
+ const logger = {
24229
+ warn: (msg) => {
24230
+ ctx.logger.warn(msg);
24231
+ bootLog.warn(msg);
24232
+ bootLog.completeStep();
23135
24233
  },
23136
- ctx.backgroundServices,
23137
- ctx.logger
23138
- );
24234
+ info: (msg) => {
24235
+ ctx.logger.info(msg);
24236
+ bootLog.ok(msg);
24237
+ bootLog.completeStep();
24238
+ }
24239
+ };
24240
+ serverManager.configure(logger, ctx.backgroundServices, ctx.logger);
24241
+ const servers = this.options?.servers;
24242
+ if (servers && servers.length > 0) {
24243
+ bootLog.setCurrent("Connecting to servers");
24244
+ await serverManager.connectAll(servers, logger, ctx.backgroundServices, ctx.logger);
24245
+ }
24246
+ await restoreLinkedServers(ctx, serverManager, logger.warn);
23139
24247
  }
23140
24248
  /**
23141
24249
  * 5. Built-in welcome and server modules, the app's modules, and the
@@ -23239,6 +24347,7 @@ var CliEngine = class {
23239
24347
  if (this.wheelListener) {
23240
24348
  this.container.removeEventListener("wheel", this.wheelListener);
23241
24349
  }
24350
+ this.disposeTouchScroll?.();
23242
24351
  this.resizeVersion = -1;
23243
24352
  this.resizeScheduled = false;
23244
24353
  this.resizeObserver?.disconnect();
@@ -24147,6 +25256,21 @@ var DARK_SCHEME_QUERY = "(prefers-color-scheme: dark)";
24147
25256
  function asEventTarget(node) {
24148
25257
  return node && typeof node.addEventListener === "function" ? node : null;
24149
25258
  }
25259
+ function canScrollWithin(target, root, dx, dy) {
25260
+ for (let el = target; el && el !== root; el = el.parentElement) {
25261
+ if (!(el instanceof HTMLElement)) continue;
25262
+ const style = getComputedStyle(el);
25263
+ const scrollsY = /auto|scroll/.test(style.overflowY) && el.scrollHeight > el.clientHeight;
25264
+ const scrollsX = /auto|scroll/.test(style.overflowX) && el.scrollWidth > el.clientWidth;
25265
+ if (dy !== 0 && scrollsY) {
25266
+ if (dy < 0 ? el.scrollTop > 0 : el.scrollTop + el.clientHeight < el.scrollHeight) return true;
25267
+ }
25268
+ if (dx !== 0 && scrollsX) {
25269
+ if (dx < 0 ? el.scrollLeft > 0 : el.scrollLeft + el.clientWidth < el.scrollWidth) return true;
25270
+ }
25271
+ }
25272
+ return false;
25273
+ }
24150
25274
  function followsSystem(input) {
24151
25275
  return input === "auto" || typeof input === "object" && input !== null && input.base === "auto";
24152
25276
  }
@@ -24228,6 +25352,16 @@ var CliPanelController = class {
24228
25352
  if (event.target !== first) return;
24229
25353
  this.syncThemeFromEngine(event.detail);
24230
25354
  };
25355
+ /**
25356
+ * A wheel over the panel chrome (header, icon strip, empty pane) would
25357
+ * scroll the page behind it; the terminals already keep their own wheel.
25358
+ */
25359
+ this.onPanelWheel = (event) => {
25360
+ const wheel = event;
25361
+ const root = this.rootElement;
25362
+ if (!root || canScrollWithin(wheel.target, root, wheel.deltaX, wheel.deltaY)) return;
25363
+ wheel.preventDefault();
25364
+ };
24231
25365
  this.onVisualViewportChange = () => {
24232
25366
  const fields = visualViewportFields();
24233
25367
  const s = this.snapshot;
@@ -24336,9 +25470,13 @@ var CliPanelController = class {
24336
25470
  /** Element containing the terminals, used for theme sync. */
24337
25471
  setRootElement(element) {
24338
25472
  if (element === this.rootElement) return;
24339
- asEventTarget(this.rootElement)?.removeEventListener(CLI_TERMINAL_THEME_EVENT, this.onTerminalTheme);
25473
+ const previous = asEventTarget(this.rootElement);
25474
+ previous?.removeEventListener(CLI_TERMINAL_THEME_EVENT, this.onTerminalTheme);
25475
+ previous?.removeEventListener("wheel", this.onPanelWheel);
24340
25476
  this.rootElement = element;
24341
- asEventTarget(element)?.addEventListener(CLI_TERMINAL_THEME_EVENT, this.onTerminalTheme);
25477
+ const next = asEventTarget(element);
25478
+ next?.addEventListener(CLI_TERMINAL_THEME_EVENT, this.onTerminalTheme);
25479
+ next?.addEventListener("wheel", this.onPanelWheel, { passive: false });
24342
25480
  if (element && this.followsTerminalTheme) this.startThemeSync();
24343
25481
  }
24344
25482
  /**
@@ -25270,6 +26408,6 @@ function openStatusDropdown(s) {
25270
26408
  return null;
25271
26409
  }
25272
26410
 
25273
- export { API_VERSION, CLI_ENV_GLOBAL_PERMISSION, CLI_FRAMEWORK_SERVICE, CLOSE_GLYPH, CLOSE_ICON, CapturingTerminalWriter, CliAliasCommandProcessor, CliArgsParser, CliBackgroundServiceRegistry_TOKEN, CliBoot, CliBootLog, CliBootLog_TOKEN, CliCalCommandProcessor, CliCaptureCommandProcessor, CliClearCommandProcessor, CliClipboard, CliClipboardCommandProcessor, CliColorCommandProcessor, CliCommandExecutionContext, CliCommandExecutor, CliCommandHistory, CliCommandHistory_TOKEN, CliCommandProcessorRegistry, CliConfigureCommandProcessor, CliConvertCommandProcessor, CliDebugCommandProcessor, CliDefaultPingServerService, CliDiffCommandProcessor, CliDragDropService, CliEchoCommandProcessor, CliEngine, CliEnvCommandProcessor, CliEnvironment, CliEvalCommandProcessor, CliEventsCommandProcessor, CliExecutionContext, CliExecutionProcess, CliExportCommandProcessor, CliFalseCommandProcessor, CliFeedbackCommandProcessor, CliFontSizeCommandProcessor, CliFullScreenManager, CliHelpCommandProcessor, CliHistoryCommandProcessor, CliHotKeysCommandProcessor, CliHtopCommandProcessor, CliHttpClient, CliInputReader, CliJsonCommandProcessor, CliKeyValueStore, CliKeyValueStore_TOKEN, CliKillCommandProcessor, CliLogger, CliLoremCommandProcessor, CliModuleRegistry_TOKEN, CliNanoCommandProcessor, CliNotifier, CliOpenCommandProcessor, CliPackageManagerService, CliPackageManagerService_TOKEN, CliPackagesCommandProcessor, CliPanelController, CliPanelStatusTracker, CliPermissionRegistry, CliPingCommandProcessor, CliProcessRegistry, CliProcessRegistry_TOKEN, CliProcessorsRegistry_TOKEN, CliPsCommandProcessor, CliRandomCommandProcessor, CliScreenCommandProcessor, CliScriptSyntaxError, CliSeqCommandProcessor, CliServerCommandProcessor, CliServerConnection, CliServerManager, CliServerManager_TOKEN, CliServerMultiProxyProcessor, CliServerProxyProcessor, CliServiceContainer, CliServicesCommandProcessor, CliSleepCommandProcessor, CliSnippetCommandProcessor, CliSshCommandProcessor, CliStateStore, CliStateStoreManager, CliStateStoreManager_TOKEN, CliTerminalProgressBar, CliTerminalSpinner, CliTerminalTextAnimator, CliTerminalWriter, CliTestCommandProcessor, CliTestHarness, CliThemeCommandProcessor, CliTimeCommandProcessor, CliTimerManager, CliTimestampCommandProcessor, CliTrueCommandProcessor, CliUnAliasCommandProcessor, CliUnameCommandProcessor, CliUnsetCommandProcessor, CliUptimeCommandProcessor, CliVersionCommandProcessor, CliYesCommandProcessor, CommandParser, DEFAULT_CLI_NAME, DEFAULT_GLOBAL_STATUS, DEFAULT_PANEL_HEIGHT, DEFAULT_PANEL_WIDTH, DEFAULT_TAB_STATUS, ERROR_ICON, EXEC_POLL_MS, GLOBAL_POLL_MS, HIDE_ICON, HIDE_TAB_TERMINAL_ICON, ICliEnvironment_TOKEN, ICliPingServerService_TOKEN, ICliScriptRunner_TOKEN, JsFallbackAccelerator, LIBRARY_VERSION, MAXIMIZE_ICON, MIN_PANEL_SIZE, MIN_PANE_WIDTH_PERCENT, OverlayAddon, PANEL_LABELS, PANEL_POSITIONS, ProcessExitedError, RESTORE_ICON, SERVICES_ICON, SUCCESS_ICON, SVG_COMMON_ATTRS, SVG_VIEW_BOX, ScriptLoaderService, SyntaxHighlighterRegistry_TOKEN, TERMINAL_ICON, UPTIME_ICON, appendOutputToFile, appendStderrToFile, applyEnvironmentChange, builtinProcessors, capitalize, clampPanelSize, cliScriptRunner, collapseChevronIcon, computeDiff, computeGlobalStatus, computeTabStatus, contextGlobalParameter, contextMenuItems, contextMenuStyle, createCliEngine, createServerModule, defaultFontSize, describeShellClose, distributePaneWidths, dropdownAnchorStyle, executeOnServer, expandBraces, expandEnvironmentVars, expandGlobs, formatDiff, formatUptime2 as formatUptime, getAccelerator, getCliNameArt, getGreetingBasedOnTime, getTerminalOptions, glowLineClass, greetingModule, groupBy, handleRenameKey, helpGlobalParameter, hideTabChevronIcon, hotkeysInfo, initWasmAccelerator, initializeServices, initializeTerminal, interruptCommand, isHorizontalPosition, isWasmAccelerated, joinClasses, killProcess, matchGlob, mergeCliOptions, miscProcessors, nextRowsCommand, normalizePaneWidths, openLink, openStatusDropdown, paneClass, paneStyle, panelBranding, panelTabClass, panelTabDotClass, panelWrapperClass, panelWrapperStyle, panesContainerClass, panesContainerWidth, parseAssignments, parseScript, positionDropdownItemClass, positionIcon, positionOptions, reconcileArgs, renderServerResponse, renderSingleOutput, resizePaneWidths, resizedPanelSize, resolveCliBranding, resolveCliHostConfig, runContextMenuAction, runScript, splitWords, statusIndicators, substituteCommands, substituteVars, svgAttrs, systemProcessors, tabDotClass, themeInfos, themes, tryExecuteScript, versionGlobalParameter, waitForLayout, welcomeModule, writeOutputToFile, writeStderrToFile };
26411
+ export { API_VERSION, CLI_ENV_GLOBAL_PERMISSION, CLI_FRAMEWORK_SERVICE, CLOSE_GLYPH, CLOSE_ICON, CapturingTerminalWriter, CliAliasCommandProcessor, CliArgsParser, CliBackgroundServiceRegistry_TOKEN, CliBoot, CliBootLog, CliBootLog_TOKEN, CliCalCommandProcessor, CliCaptureCommandProcessor, CliClearCommandProcessor, CliClipboard, CliClipboardCommandProcessor, CliColorCommandProcessor, CliCommandExecutionContext, CliCommandExecutor, CliCommandHistory, CliCommandHistory_TOKEN, CliCommandProcessorRegistry, CliConfigureCommandProcessor, CliConvertCommandProcessor, CliDebugCommandProcessor, CliDefaultPingServerService, CliDiffCommandProcessor, CliDragDropService, CliEchoCommandProcessor, CliEngine, CliEnvCommandProcessor, CliEnvironment, CliEvalCommandProcessor, CliEventsCommandProcessor, CliExecutionContext, CliExecutionProcess, CliExportCommandProcessor, CliFalseCommandProcessor, CliFeedbackCommandProcessor, CliFontSizeCommandProcessor, CliFullScreenManager, CliHelpCommandProcessor, CliHistoryCommandProcessor, CliHotKeysCommandProcessor, CliHtopCommandProcessor, CliHttpClient, CliInputReader, CliJsonCommandProcessor, CliKeyValueStore, CliKeyValueStore_TOKEN, CliKillCommandProcessor, CliLinkedServers, CliLogger, CliLoremCommandProcessor, CliModuleRegistry_TOKEN, CliNanoCommandProcessor, CliNotifier, CliOpenCommandProcessor, CliPackageManagerService, CliPackageManagerService_TOKEN, CliPackagesCommandProcessor, CliPanelController, CliPanelStatusTracker, CliPermissionRegistry, CliPingCommandProcessor, CliProcessRegistry, CliProcessRegistry_TOKEN, CliProcessorsRegistry_TOKEN, CliPsCommandProcessor, CliRandomCommandProcessor, CliScreenCommandProcessor, CliScriptSyntaxError, CliSeqCommandProcessor, CliServerCommandProcessor, CliServerConnection, CliServerManager, CliServerManager_TOKEN, CliServerMultiProxyProcessor, CliServerProxyProcessor, CliServiceContainer, CliServicesCommandProcessor, CliSleepCommandProcessor, CliSnippetCommandProcessor, CliSshCommandProcessor, CliStateStore, CliStateStoreManager, CliStateStoreManager_TOKEN, CliTerminalProgressBar, CliTerminalSpinner, CliTerminalTextAnimator, CliTerminalWriter, CliTestCommandProcessor, CliTestHarness, CliThemeCommandProcessor, CliTimeCommandProcessor, CliTimerManager, CliTimestampCommandProcessor, CliTrueCommandProcessor, CliUnAliasCommandProcessor, CliUnameCommandProcessor, CliUnsetCommandProcessor, CliUptimeCommandProcessor, CliVersionCommandProcessor, CliWebCryptoSecretStore, CliYesCommandProcessor, CommandParser, DEFAULT_CLI_NAME, DEFAULT_GLOBAL_STATUS, DEFAULT_PANEL_HEIGHT, DEFAULT_PANEL_WIDTH, DEFAULT_TAB_STATUS, ERROR_ICON, EXEC_POLL_MS, GLOBAL_POLL_MS, HIDE_ICON, HIDE_TAB_TERMINAL_ICON, ICliEnvironment_TOKEN, ICliPingServerService_TOKEN, ICliScriptRunner_TOKEN, JsFallbackAccelerator, LIBRARY_VERSION, MAXIMIZE_ICON, MIN_PANEL_SIZE, MIN_PANE_WIDTH_PERCENT, OverlayAddon, PANEL_LABELS, PANEL_POSITIONS, ProcessExitedError, RESTORE_ICON, SERVER_NAME_PATTERN, SERVICES_ICON, SUCCESS_ICON, SVG_COMMON_ATTRS, SVG_VIEW_BOX, ScriptLoaderService, SyntaxHighlighterRegistry_TOKEN, TERMINAL_ICON, UPTIME_ICON, appendOutputToFile, appendStderrToFile, applyEnvironmentChange, builtinProcessors, capitalize, checkServerUrl, clampPanelSize, cliScriptRunner, collapseChevronIcon, computeDiff, computeGlobalStatus, computeTabStatus, contextGlobalParameter, contextMenuItems, contextMenuStyle, createCliEngine, createServerModule, createTokenWithPassword, defaultFontSize, describeShellClose, deviceName, distributePaneWidths, dropdownAnchorStyle, executeOnServer, expandBraces, expandEnvironmentVars, expandGlobs, formatDiff, formatUptime2 as formatUptime, getAccelerator, getCliNameArt, getGreetingBasedOnTime, getTerminalOptions, glowLineClass, greetingModule, groupBy, handleRenameKey, helpGlobalParameter, hideTabChevronIcon, hotkeysInfo, initWasmAccelerator, initializeServices, initializeTerminal, interruptCommand, isHorizontalPosition, isPrivateHost, isWasmAccelerated, joinClasses, killProcess, linkedServerConfig, linkedServersOf, maskToken, matchGlob, mergeCliOptions, miscProcessors, nextRowsCommand, normalizePaneWidths, normalizeServerUrl, openLink, openStatusDropdown, paneClass, paneStyle, panelBranding, panelTabClass, panelTabDotClass, panelWrapperClass, panelWrapperStyle, panesContainerClass, panesContainerWidth, parseAssignments, parseScript, plainText2 as plainText, pollLink, positionDropdownItemClass, positionIcon, positionOptions, probeServer, reconcileArgs, redactSecrets, renderServerResponse, renderSingleOutput, requestLink, resizePaneWidths, resizedPanelSize, resolveCliBranding, resolveCliHostConfig, restoreLinkedServers, runContextMenuAction, runScript, secretStoreOf, splitWords, statusIndicators, substituteCommands, substituteVars, suggestServerName, svgAttrs, systemProcessors, tabDotClass, themeInfos, themes, tryExecuteScript, userServersAllow, userServersEnabled, versionGlobalParameter, waitForLayout, welcomeModule, writeOutputToFile, writeStderrToFile };
25274
26412
  //# sourceMappingURL=public-api.mjs.map
25275
26413
  //# sourceMappingURL=public-api.mjs.map