@qodalis/cli 2.4.1 → 2.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -3
- package/assets/cli-panel.css +35 -0
- package/package.json +2 -2
- package/public-api.d.mts +212 -5
- package/public-api.d.ts +212 -5
- package/public-api.js +1132 -103
- package/public-api.js.map +1 -1
- package/public-api.mjs +1112 -105
- package/public-api.mjs.map +1 -1
- package/wasm/qodalis_cli_wasm_bg.wasm +0 -0
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.
|
|
521
|
+
var LIBRARY_VERSION = "2.5.0";
|
|
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
|
-
|
|
6023
|
-
|
|
6024
|
-
|
|
6025
|
-
|
|
6026
|
-
};
|
|
6027
|
-
this.parameters = descriptor
|
|
6028
|
-
|
|
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
|
-
|
|
6062
|
-
|
|
6063
|
-
|
|
6064
|
-
|
|
6065
|
-
|
|
6066
|
-
this.parameters = first
|
|
6067
|
-
|
|
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
|
-
|
|
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
|
-
|
|
6212
|
-
|
|
6213
|
-
|
|
6214
|
-
|
|
6215
|
-
|
|
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.
|
|
6218
|
-
connection.
|
|
6219
|
-
|
|
6220
|
-
|
|
6221
|
-
|
|
6222
|
-
|
|
6223
|
-
|
|
6224
|
-
|
|
6225
|
-
|
|
6226
|
-
|
|
6227
|
-
|
|
6228
|
-
|
|
6229
|
-
|
|
6230
|
-
|
|
6231
|
-
|
|
6232
|
-
|
|
6233
|
-
|
|
6234
|
-
|
|
6235
|
-
|
|
6236
|
-
|
|
6237
|
-
|
|
6238
|
-
|
|
6239
|
-
|
|
6240
|
-
|
|
6241
|
-
|
|
6242
|
-
|
|
6243
|
-
|
|
6244
|
-
|
|
6245
|
-
|
|
6246
|
-
|
|
6247
|
-
|
|
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
|
+
});
|
|
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`);
|
|
6250
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(
|
|
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
|
-
|
|
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
|
});
|
|
@@ -17999,6 +18250,19 @@ var CliExecutionContext = class {
|
|
|
17999
18250
|
isProgressRunning() {
|
|
18000
18251
|
return this.progressBar.isRunning || this.spinner.isRunning || this.textAnimator.isRunning;
|
|
18001
18252
|
}
|
|
18253
|
+
/**
|
|
18254
|
+
* What pressing Ctrl+C does: the current input mode handles the key, so a
|
|
18255
|
+
* running command is aborted, a prompt cancelled or a context left exactly
|
|
18256
|
+
* as with the keyboard.
|
|
18257
|
+
*/
|
|
18258
|
+
interrupt() {
|
|
18259
|
+
const event = new KeyboardEvent("keydown", {
|
|
18260
|
+
key: "c",
|
|
18261
|
+
code: "KeyC",
|
|
18262
|
+
ctrlKey: true
|
|
18263
|
+
});
|
|
18264
|
+
this.currentMode?.handleKeyEvent(event);
|
|
18265
|
+
}
|
|
18002
18266
|
abort() {
|
|
18003
18267
|
if (this.progressBar.isRunning) {
|
|
18004
18268
|
this.progressBar.complete();
|
|
@@ -19374,7 +19638,7 @@ var CliCommandHistory = class {
|
|
|
19374
19638
|
return this.loading;
|
|
19375
19639
|
}
|
|
19376
19640
|
async addCommand(command2) {
|
|
19377
|
-
const normalizedCommand = command2.trim();
|
|
19641
|
+
const normalizedCommand = redactSecrets(command2.trim());
|
|
19378
19642
|
if (!normalizedCommand) return;
|
|
19379
19643
|
await this.loading;
|
|
19380
19644
|
const existingIndex = this.commandHistory.indexOf(normalizedCommand);
|
|
@@ -20963,6 +21227,129 @@ var CliStateStoreManager = class {
|
|
|
20963
21227
|
};
|
|
20964
21228
|
}
|
|
20965
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
|
+
}
|
|
20966
21353
|
var CliServerAuthService = class {
|
|
20967
21354
|
constructor(services) {
|
|
20968
21355
|
this.services = services;
|
|
@@ -21034,14 +21421,17 @@ function writeUnknownServer(context2, manager, name) {
|
|
|
21034
21421
|
function writeConfigureHint(context2) {
|
|
21035
21422
|
const { writer } = context2;
|
|
21036
21423
|
writer.writeln(
|
|
21037
|
-
`
|
|
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)}`
|
|
21038
21425
|
);
|
|
21039
21426
|
writer.writeln(
|
|
21040
21427
|
` See ${writer.wrapInColor("https://cli.qodalis.com/docs/server-integration", CliForegroundColor.Blue)}`
|
|
21041
21428
|
);
|
|
21042
21429
|
}
|
|
21043
21430
|
function statusLabel(context2, connection) {
|
|
21044
|
-
|
|
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);
|
|
21045
21435
|
}
|
|
21046
21436
|
async function pingServer(connection) {
|
|
21047
21437
|
const started = performance.now();
|
|
@@ -21083,6 +21473,546 @@ function editDistance(a, b) {
|
|
|
21083
21473
|
return row[b.length];
|
|
21084
21474
|
}
|
|
21085
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
|
+
|
|
21086
22016
|
// src/lib/server/cli-server-command-processor.ts
|
|
21087
22017
|
var CliServerCommandProcessor = class {
|
|
21088
22018
|
constructor() {
|
|
@@ -21104,7 +22034,11 @@ var CliServerCommandProcessor = class {
|
|
|
21104
22034
|
new ServerStatusProcessor(),
|
|
21105
22035
|
new ServerCommandsProcessor(),
|
|
21106
22036
|
new ServerReconnectProcessor(),
|
|
21107
|
-
new ServerDefaultProcessor()
|
|
22037
|
+
new ServerDefaultProcessor(),
|
|
22038
|
+
new ServerAddProcessor(),
|
|
22039
|
+
new ServerRemoveProcessor(),
|
|
22040
|
+
new ServerEditProcessor(),
|
|
22041
|
+
new ServerForgetAllProcessor()
|
|
21108
22042
|
];
|
|
21109
22043
|
}
|
|
21110
22044
|
/** Restore the default server saved by `server default <name>`. */
|
|
@@ -21120,7 +22054,7 @@ var CliServerCommandProcessor = class {
|
|
|
21120
22054
|
const t = context2.translator;
|
|
21121
22055
|
const manager = serverManagerOf(context2);
|
|
21122
22056
|
if (!manager) return;
|
|
21123
|
-
writeServerTable(context2, manager);
|
|
22057
|
+
writeServerTable(context2, manager, await linkedTokenLabels(context2, manager));
|
|
21124
22058
|
context2.writer.writeln();
|
|
21125
22059
|
writeHint(context2, "help server", t.t("cli.server.help.see_everything_you_can", "to see everything you can do"));
|
|
21126
22060
|
}
|
|
@@ -21151,6 +22085,10 @@ var CliServerCommandProcessor = class {
|
|
|
21151
22085
|
["server commands|cmds [name]", t.t("cli.server.help.commands_each_server_provides", "Commands each server provides")],
|
|
21152
22086
|
["server reconnect|rc [name]", t.t("cli.server.help.connect_again_unreachable_servers", "Connect again (unreachable servers, or one)")],
|
|
21153
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")],
|
|
21154
22092
|
["ssh [name] [command]", t.t("cli.server.help.open_shell_server_run", "Open a shell on a server, or run one command")]
|
|
21155
22093
|
]);
|
|
21156
22094
|
writer.writeln();
|
|
@@ -21173,7 +22111,7 @@ var ServerListProcessor = class {
|
|
|
21173
22111
|
}
|
|
21174
22112
|
async processCommand(_command, context2) {
|
|
21175
22113
|
const manager = serverManagerOf(context2);
|
|
21176
|
-
if (manager) writeServerTable(context2, manager);
|
|
22114
|
+
if (manager) writeServerTable(context2, manager, await linkedTokenLabels(context2, manager));
|
|
21177
22115
|
}
|
|
21178
22116
|
writeDescription(context2) {
|
|
21179
22117
|
const t = context2.translator;
|
|
@@ -21185,7 +22123,7 @@ var ServerListProcessor = class {
|
|
|
21185
22123
|
writeUsage(context2, [["server list", ""], ["server ls", ""]]);
|
|
21186
22124
|
}
|
|
21187
22125
|
};
|
|
21188
|
-
function writeServerTable(context2, manager) {
|
|
22126
|
+
function writeServerTable(context2, manager, tokenLabels = /* @__PURE__ */ new Map()) {
|
|
21189
22127
|
const t = context2.translator;
|
|
21190
22128
|
const { writer } = context2;
|
|
21191
22129
|
if (manager.connections.size === 0) {
|
|
@@ -21205,12 +22143,13 @@ function writeServerTable(context2, manager) {
|
|
|
21205
22143
|
connected ? connection.capabilities?.version ?? "-" : "-",
|
|
21206
22144
|
connected ? `v${connection.apiVersion}` : "-",
|
|
21207
22145
|
connected ? String(connection.commands.length) : "-",
|
|
21208
|
-
connected ? connection.capabilities?.shell ? "yes" : "no" : "-"
|
|
22146
|
+
connected ? connection.capabilities?.shell ? "yes" : "no" : "-",
|
|
22147
|
+
manager.isLinked(name) ? `linked ${tokenLabels.get(name) ?? ""}`.trim() : "app"
|
|
21209
22148
|
]);
|
|
21210
22149
|
}
|
|
21211
22150
|
writer.writeln(`\u{1F5A5} Servers (${manager.connections.size}):`);
|
|
21212
22151
|
writer.writeTable(
|
|
21213
|
-
["Name", "Status", "URL", "Version", "API", "Commands", "Shell"],
|
|
22152
|
+
["Name", "Status", "URL", "Version", "API", "Commands", "Shell", "Source"],
|
|
21214
22153
|
rows
|
|
21215
22154
|
);
|
|
21216
22155
|
if (manager.defaultServer) {
|
|
@@ -22473,6 +23412,7 @@ function resolveCompletionPath(tokens, tokenIndex, processors, currentToken = ""
|
|
|
22473
23412
|
let descriptors = [];
|
|
22474
23413
|
let candidates = processors;
|
|
22475
23414
|
let isFlagValue = false;
|
|
23415
|
+
let flag;
|
|
22476
23416
|
for (let i = 0; i < parsed.length; i++) {
|
|
22477
23417
|
const token = parsed[i];
|
|
22478
23418
|
if (token.kind === "flag") {
|
|
@@ -22481,6 +23421,7 @@ function resolveCompletionPath(tokens, tokenIndex, processors, currentToken = ""
|
|
|
22481
23421
|
if (parsed[i + 1].kind === "word") i++;
|
|
22482
23422
|
} else if (!currentToken.startsWith("-")) {
|
|
22483
23423
|
isFlagValue = true;
|
|
23424
|
+
flag = findParameter(token.name, descriptors);
|
|
22484
23425
|
}
|
|
22485
23426
|
continue;
|
|
22486
23427
|
}
|
|
@@ -22494,7 +23435,11 @@ function resolveCompletionPath(tokens, tokenIndex, processors, currentToken = ""
|
|
|
22494
23435
|
}
|
|
22495
23436
|
candidates = match?.processors?.length ? match.processors : void 0;
|
|
22496
23437
|
}
|
|
22497
|
-
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));
|
|
22498
23443
|
}
|
|
22499
23444
|
function toParsedToken(text) {
|
|
22500
23445
|
const flag = /^(--?)([a-zA-Z0-9_][^\s=]*)(=.*)?$/.exec(text);
|
|
@@ -22593,6 +23538,36 @@ var CliParameterCompletionProvider = class {
|
|
|
22593
23538
|
}
|
|
22594
23539
|
};
|
|
22595
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
|
+
|
|
22596
23571
|
// src/lib/completion/cli-service-name-completion-provider.ts
|
|
22597
23572
|
var SERVICE_NAME_SUBCOMMANDS = /* @__PURE__ */ new Set([
|
|
22598
23573
|
"start",
|
|
@@ -22880,6 +23855,7 @@ function createCompletionProviders(deps) {
|
|
|
22880
23855
|
new CliServerNameCompletionProvider(serverManager),
|
|
22881
23856
|
new CliConfigKeyCompletionProvider(registry),
|
|
22882
23857
|
new CliCommandCompletionProvider(registry),
|
|
23858
|
+
new CliParameterValueCompletionProvider(registry),
|
|
22883
23859
|
new CliParameterCompletionProvider(registry, executor)
|
|
22884
23860
|
];
|
|
22885
23861
|
const fromPlugins = services.getAll(
|
|
@@ -23100,29 +24076,31 @@ var CliEngine = class {
|
|
|
23100
24076
|
]);
|
|
23101
24077
|
return services.getRequired(CliServerManager_TOKEN);
|
|
23102
24078
|
}
|
|
23103
|
-
/**
|
|
24079
|
+
/**
|
|
24080
|
+
* 4. Connect to the configured servers, one boot step each, then to the
|
|
24081
|
+
* servers the user linked (`server add`).
|
|
24082
|
+
*/
|
|
23104
24083
|
async connectServers(serverManager, bootLog) {
|
|
23105
|
-
const servers = this.options?.servers;
|
|
23106
|
-
if (!servers || servers.length === 0) return;
|
|
23107
24084
|
const ctx = this.executionContext;
|
|
23108
|
-
|
|
23109
|
-
|
|
23110
|
-
|
|
23111
|
-
|
|
23112
|
-
|
|
23113
|
-
ctx.logger.warn(msg);
|
|
23114
|
-
bootLog.warn(msg);
|
|
23115
|
-
bootLog.completeStep();
|
|
23116
|
-
},
|
|
23117
|
-
info: (msg) => {
|
|
23118
|
-
ctx.logger.info(msg);
|
|
23119
|
-
bootLog.ok(msg);
|
|
23120
|
-
bootLog.completeStep();
|
|
23121
|
-
}
|
|
24085
|
+
const logger = {
|
|
24086
|
+
warn: (msg) => {
|
|
24087
|
+
ctx.logger.warn(msg);
|
|
24088
|
+
bootLog.warn(msg);
|
|
24089
|
+
bootLog.completeStep();
|
|
23122
24090
|
},
|
|
23123
|
-
|
|
23124
|
-
|
|
23125
|
-
|
|
24091
|
+
info: (msg) => {
|
|
24092
|
+
ctx.logger.info(msg);
|
|
24093
|
+
bootLog.ok(msg);
|
|
24094
|
+
bootLog.completeStep();
|
|
24095
|
+
}
|
|
24096
|
+
};
|
|
24097
|
+
serverManager.configure(logger, ctx.backgroundServices, ctx.logger);
|
|
24098
|
+
const servers = this.options?.servers;
|
|
24099
|
+
if (servers && servers.length > 0) {
|
|
24100
|
+
bootLog.setCurrent("Connecting to servers");
|
|
24101
|
+
await serverManager.connectAll(servers, logger, ctx.backgroundServices, ctx.logger);
|
|
24102
|
+
}
|
|
24103
|
+
await restoreLinkedServers(ctx, serverManager, logger.warn);
|
|
23126
24104
|
}
|
|
23127
24105
|
/**
|
|
23128
24106
|
* 5. Built-in welcome and server modules, the app's modules, and the
|
|
@@ -23998,6 +24976,18 @@ function killProcess(engine, pid) {
|
|
|
23998
24976
|
}
|
|
23999
24977
|
return registry?.kill(pid) ?? false;
|
|
24000
24978
|
}
|
|
24979
|
+
function interruptCommand(engines) {
|
|
24980
|
+
let interrupted = false;
|
|
24981
|
+
for (const engine of engines) {
|
|
24982
|
+
const context2 = engine.getContext();
|
|
24983
|
+
if (!context2?.interrupt) continue;
|
|
24984
|
+
if (context2.isExecuting || context2.contextProcessor) {
|
|
24985
|
+
context2.interrupt();
|
|
24986
|
+
interrupted = true;
|
|
24987
|
+
}
|
|
24988
|
+
}
|
|
24989
|
+
return interrupted;
|
|
24990
|
+
}
|
|
24001
24991
|
var CliPanelStatusTracker = class {
|
|
24002
24992
|
constructor(listener) {
|
|
24003
24993
|
this.listener = listener;
|
|
@@ -24067,6 +25057,11 @@ var CliPanelStatusTracker = class {
|
|
|
24067
25057
|
const entry = this.activeTabId != null ? this.tabs.get(this.activeTabId) : void 0;
|
|
24068
25058
|
return entry?.engines.values().next().value;
|
|
24069
25059
|
}
|
|
25060
|
+
/** The engines of the active tab, one per pane. */
|
|
25061
|
+
get activeTabEngines() {
|
|
25062
|
+
const entry = this.activeTabId != null ? this.tabs.get(this.activeTabId) : void 0;
|
|
25063
|
+
return entry ? [...entry.engines.values()] : [];
|
|
25064
|
+
}
|
|
24070
25065
|
/** Recompute everything now. */
|
|
24071
25066
|
refresh() {
|
|
24072
25067
|
for (const tabId of this.tabs.keys()) this.refreshTab(tabId);
|
|
@@ -24585,6 +25580,15 @@ var CliPanelController = class {
|
|
|
24585
25580
|
serversDropdownOpen: false
|
|
24586
25581
|
});
|
|
24587
25582
|
}
|
|
25583
|
+
/**
|
|
25584
|
+
* Stop the command running in the active tab, as Ctrl+C does. False when
|
|
25585
|
+
* no command runs there.
|
|
25586
|
+
*/
|
|
25587
|
+
stopCommand() {
|
|
25588
|
+
const stopped = interruptCommand(this.tracker.activeTabEngines);
|
|
25589
|
+
if (stopped) this.tracker.refresh();
|
|
25590
|
+
return stopped;
|
|
25591
|
+
}
|
|
24588
25592
|
/**
|
|
24589
25593
|
* Stop a running background service listed in the services dropdown, as
|
|
24590
25594
|
* `kill <pid>` does; the list refreshes. False when nothing was running
|
|
@@ -25024,6 +26028,8 @@ var PANEL_LABELS = {
|
|
|
25024
26028
|
servicesHeader: "Background Services",
|
|
25025
26029
|
servicesEmpty: "No services registered",
|
|
25026
26030
|
stopService: "Stop",
|
|
26031
|
+
stopCommand: "Stop",
|
|
26032
|
+
stopCommandTitle: "Stop the running command (Ctrl+C)",
|
|
25027
26033
|
serversHeader: "Server Connections",
|
|
25028
26034
|
serversEmpty: "No servers configured"
|
|
25029
26035
|
};
|
|
@@ -25088,10 +26094,11 @@ function statusIndicators(s) {
|
|
|
25088
26094
|
items.push({
|
|
25089
26095
|
key: "execution",
|
|
25090
26096
|
itemClass: running ? "status-running" : "",
|
|
25091
|
-
compactClass: "",
|
|
26097
|
+
compactClass: running ? "status-clickable" : "",
|
|
25092
26098
|
dotClass: running ? "dot-running" : "dot-idle",
|
|
25093
26099
|
label: s.status.executionState,
|
|
25094
|
-
title: s.status.executionState
|
|
26100
|
+
title: running ? PANEL_LABELS.stopCommandTitle : s.status.executionState,
|
|
26101
|
+
stop: running ? { label: PANEL_LABELS.stopCommand, title: PANEL_LABELS.stopCommandTitle } : void 0
|
|
25095
26102
|
});
|
|
25096
26103
|
if (s.global.serviceCount.total > 0) {
|
|
25097
26104
|
const label = `${s.global.serviceCount.running}/${s.global.serviceCount.total} services`;
|
|
@@ -25228,6 +26235,6 @@ function openStatusDropdown(s) {
|
|
|
25228
26235
|
return null;
|
|
25229
26236
|
}
|
|
25230
26237
|
|
|
25231
|
-
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, 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 };
|
|
26238
|
+
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 };
|
|
25232
26239
|
//# sourceMappingURL=public-api.mjs.map
|
|
25233
26240
|
//# sourceMappingURL=public-api.mjs.map
|