@qodalis/cli 2.0.0-beta.13 → 2.0.0-beta.14
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/package.json +2 -2
- package/public-api.d.mts +77 -9
- package/public-api.d.ts +77 -9
- package/public-api.js +502 -150
- package/public-api.js.map +1 -1
- package/public-api.mjs +502 -152
- package/public-api.mjs.map +1 -1
- package/wasm/qodalis_cli_wasm_bg.wasm +0 -0
package/public-api.js
CHANGED
|
@@ -413,8 +413,27 @@ var CliKeyValueStore_TOKEN = "cli-key-value-store";
|
|
|
413
413
|
var SyntaxHighlighterRegistry_TOKEN = "syntax-highlighter-registry";
|
|
414
414
|
|
|
415
415
|
// src/lib/version.ts
|
|
416
|
-
var LIBRARY_VERSION = "2.0.0-beta.
|
|
416
|
+
var LIBRARY_VERSION = "2.0.0-beta.14";
|
|
417
417
|
var API_VERSION = 2;
|
|
418
|
+
var ANONYMOUS = "\0anonymous";
|
|
419
|
+
function aliasOwner(context2) {
|
|
420
|
+
let auth;
|
|
421
|
+
try {
|
|
422
|
+
auth = context2.services.get(cliCore.ICliAuthorizationService_TOKEN);
|
|
423
|
+
} catch {
|
|
424
|
+
auth = void 0;
|
|
425
|
+
}
|
|
426
|
+
if (!auth) return void 0;
|
|
427
|
+
return auth.getPrincipal()?.name ?? ANONYMOUS;
|
|
428
|
+
}
|
|
429
|
+
function aliasesFor(state, owner) {
|
|
430
|
+
if (owner === void 0) return state?.aliases ?? {};
|
|
431
|
+
return state?.aliasesByUser?.[owner] ?? {};
|
|
432
|
+
}
|
|
433
|
+
function withAliases(state, owner, aliases) {
|
|
434
|
+
if (owner === void 0) return { aliases };
|
|
435
|
+
return { aliasesByUser: { ...state?.aliasesByUser ?? {}, [owner]: aliases } };
|
|
436
|
+
}
|
|
418
437
|
var CliAliasCommandProcessor = class {
|
|
419
438
|
constructor() {
|
|
420
439
|
this.command = "alias";
|
|
@@ -431,26 +450,33 @@ var CliAliasCommandProcessor = class {
|
|
|
431
450
|
},
|
|
432
451
|
storeName: "aliases"
|
|
433
452
|
};
|
|
453
|
+
/** Shared aliases (used when no auth module is loaded) */
|
|
434
454
|
this.userAliases = {};
|
|
455
|
+
this.aliasState = {};
|
|
435
456
|
this.processors = [
|
|
436
457
|
{
|
|
437
458
|
command: "ls",
|
|
438
459
|
description: "List all aliases",
|
|
439
460
|
processCommand: async (_, context2) => {
|
|
440
461
|
const { writer } = context2;
|
|
462
|
+
const visible = this.aliasesFor(context2);
|
|
441
463
|
writer.writeln("Aliases:");
|
|
442
|
-
Object.entries(
|
|
464
|
+
Object.entries(visible).forEach(
|
|
443
465
|
([alias, command2]) => {
|
|
444
466
|
writer.writeInfo(` ${alias} -> ${command2}`);
|
|
445
467
|
}
|
|
446
468
|
);
|
|
447
|
-
if (Object.keys(
|
|
469
|
+
if (Object.keys(visible).length === 0) {
|
|
448
470
|
writer.writeInfo(" No aliases defined");
|
|
449
471
|
}
|
|
450
472
|
}
|
|
451
473
|
}
|
|
452
474
|
];
|
|
453
475
|
}
|
|
476
|
+
/** Aliases visible to the current principal */
|
|
477
|
+
aliasesFor(context2) {
|
|
478
|
+
return aliasesFor(this.aliasState, aliasOwner(context2));
|
|
479
|
+
}
|
|
454
480
|
writeDescription(context2) {
|
|
455
481
|
const { writer, translator: t } = context2;
|
|
456
482
|
writer.writeln(t.t("cli.alias.long_description", "Create shortcut aliases for frequently used commands"));
|
|
@@ -496,18 +522,20 @@ var CliAliasCommandProcessor = class {
|
|
|
496
522
|
}
|
|
497
523
|
context2.writer.writeInfo(`${alias} -> ${command2.args[alias]}`);
|
|
498
524
|
}
|
|
499
|
-
context2
|
|
500
|
-
|
|
501
|
-
|
|
525
|
+
const owner = aliasOwner(context2);
|
|
526
|
+
context2.state.updateState(
|
|
527
|
+
withAliases(this.aliasState, owner, {
|
|
528
|
+
...aliasesFor(this.aliasState, owner),
|
|
502
529
|
...command2.args
|
|
503
|
-
}
|
|
504
|
-
|
|
530
|
+
})
|
|
531
|
+
);
|
|
505
532
|
await context2.state.persist();
|
|
506
533
|
}
|
|
507
534
|
async initialize(context2) {
|
|
508
535
|
this.stateSubscription?.unsubscribe();
|
|
509
|
-
this.stateSubscription = context2.state.select((x) => x
|
|
510
|
-
this.
|
|
536
|
+
this.stateSubscription = context2.state.select((x) => x).subscribe((state) => {
|
|
537
|
+
this.aliasState = state ?? {};
|
|
538
|
+
this.userAliases = this.aliasState.aliases ?? {};
|
|
511
539
|
});
|
|
512
540
|
}
|
|
513
541
|
};
|
|
@@ -1335,7 +1363,9 @@ var CliEvalCommandProcessor = class {
|
|
|
1335
1363
|
this.acceptsRawInput = true;
|
|
1336
1364
|
this.metadata = {
|
|
1337
1365
|
icon: "\u{1F9EE}",
|
|
1338
|
-
module: "misc"
|
|
1366
|
+
module: "misc",
|
|
1367
|
+
// Enforced only when an auth module is loaded (eval also needs allowEval)
|
|
1368
|
+
requiredPermissions: ["system:eval"]
|
|
1339
1369
|
};
|
|
1340
1370
|
}
|
|
1341
1371
|
async processCommand(command, context) {
|
|
@@ -2450,7 +2480,9 @@ var CliUnAliasCommandProcessor = class {
|
|
|
2450
2480
|
async processCommand(command2, context2) {
|
|
2451
2481
|
const { writer } = context2;
|
|
2452
2482
|
const aliasToRemove = command2.value;
|
|
2453
|
-
const
|
|
2483
|
+
const state = context2.state.getState();
|
|
2484
|
+
const owner = aliasOwner(context2);
|
|
2485
|
+
const aliases = aliasesFor(state, owner);
|
|
2454
2486
|
if (!aliases[aliasToRemove]) {
|
|
2455
2487
|
writer.writeError(`Alias ${aliasToRemove} not found`);
|
|
2456
2488
|
context2.process.exit(-1);
|
|
@@ -2460,9 +2492,7 @@ var CliUnAliasCommandProcessor = class {
|
|
|
2460
2492
|
Object.keys(aliases).filter((alias) => alias !== aliasToRemove).forEach((alias) => {
|
|
2461
2493
|
updated[alias] = aliases[alias];
|
|
2462
2494
|
});
|
|
2463
|
-
context2.state.updateState(
|
|
2464
|
-
aliases: updated
|
|
2465
|
-
});
|
|
2495
|
+
context2.state.updateState(withAliases(state, owner, updated));
|
|
2466
2496
|
await context2.state.persist();
|
|
2467
2497
|
}
|
|
2468
2498
|
};
|
|
@@ -4698,7 +4728,9 @@ var CliPackagesCommandProcessor = class {
|
|
|
4698
4728
|
this.metadata = {
|
|
4699
4729
|
sealed: true,
|
|
4700
4730
|
icon: "\u{1F4E6}",
|
|
4701
|
-
module: "system"
|
|
4731
|
+
module: "system",
|
|
4732
|
+
// Enforced only when an auth module is loaded
|
|
4733
|
+
requiredPermissions: ["system:packages"]
|
|
4702
4734
|
};
|
|
4703
4735
|
this.stateConfiguration = {
|
|
4704
4736
|
initialState: {
|
|
@@ -5773,6 +5805,8 @@ var CliServerConnection = class _CliServerConnection {
|
|
|
5773
5805
|
this._basePath = "";
|
|
5774
5806
|
this._wsReconnectAttempts = 0;
|
|
5775
5807
|
this._wsReconnectTimer = null;
|
|
5808
|
+
/** True once this connection registered its `server-events:<name>` background service */
|
|
5809
|
+
this._eventServiceRegistered = false;
|
|
5776
5810
|
this._healthCheckTimer = null;
|
|
5777
5811
|
this._healthCheckAttempts = 0;
|
|
5778
5812
|
}
|
|
@@ -5814,6 +5848,18 @@ var CliServerConnection = class _CliServerConnection {
|
|
|
5814
5848
|
}
|
|
5815
5849
|
return cliCore.resolveHeaders(this._config.headers);
|
|
5816
5850
|
}
|
|
5851
|
+
/**
|
|
5852
|
+
* WebSocket URL for this server with auth: single-use upgrade params from
|
|
5853
|
+
* auth providers when available, otherwise headers copied into the URL.
|
|
5854
|
+
*/
|
|
5855
|
+
async buildWebSocketUrl(path) {
|
|
5856
|
+
const url = this.toWebSocketUrl(this.normalizeUrl(this._config.url)) + path;
|
|
5857
|
+
const params = await this._authService?.getWebSocketParams?.(this._config.name);
|
|
5858
|
+
if (params) {
|
|
5859
|
+
return cliCore.appendQueryParams(url, params);
|
|
5860
|
+
}
|
|
5861
|
+
return cliCore.buildAuthenticatedWebSocketUrl(url, () => this.resolveAuthHeaders());
|
|
5862
|
+
}
|
|
5817
5863
|
async connect() {
|
|
5818
5864
|
try {
|
|
5819
5865
|
const baseUrl = this.normalizeUrl(this._config.url);
|
|
@@ -6005,18 +6051,25 @@ var CliServerConnection = class _CliServerConnection {
|
|
|
6005
6051
|
return;
|
|
6006
6052
|
}
|
|
6007
6053
|
const serviceName = `server-events:${this._config.name}`;
|
|
6054
|
+
if (this._eventServiceRegistered && this._backgroundServices.getStatus(serviceName)) {
|
|
6055
|
+
const status = this._backgroundServices.getStatus(serviceName).status;
|
|
6056
|
+
const restart = status === "running" ? this._backgroundServices.restart(serviceName) : this._backgroundServices.start(serviceName);
|
|
6057
|
+
restart.catch((e) => {
|
|
6058
|
+
this._logger?.debug(`Background event service failed for "${this._config.name}", falling back to direct: ${e}`);
|
|
6059
|
+
this.connectEventSocketDirect();
|
|
6060
|
+
});
|
|
6061
|
+
return;
|
|
6062
|
+
}
|
|
6008
6063
|
try {
|
|
6009
6064
|
this._backgroundServices.register({
|
|
6010
6065
|
name: serviceName,
|
|
6011
6066
|
description: `Event stream for server '${this._config.name}'`,
|
|
6012
6067
|
type: "daemon",
|
|
6013
6068
|
onStart: async (ctx) => {
|
|
6014
|
-
const
|
|
6015
|
-
|
|
6016
|
-
|
|
6017
|
-
|
|
6018
|
-
);
|
|
6019
|
-
ctx.log(`Connecting to ${wsUrl}`);
|
|
6069
|
+
const wsUrl = await this.buildWebSocketUrl("/ws/v1/qcli/events");
|
|
6070
|
+
if (!this._connected) return;
|
|
6071
|
+
ctx.log(`Connecting to ${wsUrl.split("?")[0]}`);
|
|
6072
|
+
this.closeEventSocketDirect();
|
|
6020
6073
|
this._eventSocket = new WebSocket(wsUrl);
|
|
6021
6074
|
this._eventSocket.onopen = () => {
|
|
6022
6075
|
this._wsReconnectAttempts = 0;
|
|
@@ -6039,7 +6092,11 @@ var CliServerConnection = class _CliServerConnection {
|
|
|
6039
6092
|
} catch {
|
|
6040
6093
|
}
|
|
6041
6094
|
};
|
|
6042
|
-
this._eventSocket.onclose = () => {
|
|
6095
|
+
this._eventSocket.onclose = (event) => {
|
|
6096
|
+
if (this.isSessionEndedClose(event)) {
|
|
6097
|
+
ctx.log("Event socket closed: session ended", "info");
|
|
6098
|
+
return;
|
|
6099
|
+
}
|
|
6043
6100
|
if (this._connected) {
|
|
6044
6101
|
ctx.log("WebSocket closed unexpectedly, scheduling reconnect", "warn");
|
|
6045
6102
|
this.scheduleWsReconnect();
|
|
@@ -6053,6 +6110,7 @@ var CliServerConnection = class _CliServerConnection {
|
|
|
6053
6110
|
this.closeEventSocketDirect();
|
|
6054
6111
|
}
|
|
6055
6112
|
});
|
|
6113
|
+
this._eventServiceRegistered = true;
|
|
6056
6114
|
this._backgroundServices.start(serviceName).catch((e) => {
|
|
6057
6115
|
this._logger?.debug(`Background event service failed for "${this._config.name}", falling back to direct: ${e}`);
|
|
6058
6116
|
this.connectEventSocketDirect();
|
|
@@ -6063,12 +6121,13 @@ var CliServerConnection = class _CliServerConnection {
|
|
|
6063
6121
|
}
|
|
6064
6122
|
}
|
|
6065
6123
|
connectEventSocketDirect() {
|
|
6124
|
+
void this.connectEventSocketDirectAsync();
|
|
6125
|
+
}
|
|
6126
|
+
async connectEventSocketDirectAsync() {
|
|
6066
6127
|
try {
|
|
6067
|
-
const
|
|
6068
|
-
|
|
6069
|
-
|
|
6070
|
-
() => this.resolveAuthHeaders()
|
|
6071
|
-
);
|
|
6128
|
+
const wsUrl = await this.buildWebSocketUrl("/ws/v1/qcli/events");
|
|
6129
|
+
if (!this._connected) return;
|
|
6130
|
+
this.closeEventSocketDirect();
|
|
6072
6131
|
this._eventSocket = new WebSocket(wsUrl);
|
|
6073
6132
|
this._eventSocket.onopen = () => {
|
|
6074
6133
|
this._wsReconnectAttempts = 0;
|
|
@@ -6086,7 +6145,8 @@ var CliServerConnection = class _CliServerConnection {
|
|
|
6086
6145
|
} catch {
|
|
6087
6146
|
}
|
|
6088
6147
|
};
|
|
6089
|
-
this._eventSocket.onclose = () => {
|
|
6148
|
+
this._eventSocket.onclose = (event) => {
|
|
6149
|
+
if (this.isSessionEndedClose(event)) return;
|
|
6090
6150
|
if (this._connected) {
|
|
6091
6151
|
this.scheduleWsReconnect();
|
|
6092
6152
|
}
|
|
@@ -6097,6 +6157,16 @@ var CliServerConnection = class _CliServerConnection {
|
|
|
6097
6157
|
this._logger?.debug(`WebSocket not available for "${this._config.name}": ${e}`);
|
|
6098
6158
|
}
|
|
6099
6159
|
}
|
|
6160
|
+
/**
|
|
6161
|
+
* The server closes the event socket with 4401 when the session behind it
|
|
6162
|
+
* ended (logout, revoke, expiry). Retrying with the same credentials cannot
|
|
6163
|
+
* succeed; the auth layer reconnects the server when the session changes.
|
|
6164
|
+
*/
|
|
6165
|
+
isSessionEndedClose(event) {
|
|
6166
|
+
if (event?.code !== 4401) return false;
|
|
6167
|
+
this.closeEventSocketDirect();
|
|
6168
|
+
return true;
|
|
6169
|
+
}
|
|
6100
6170
|
handleServerDisconnect() {
|
|
6101
6171
|
this._connected = false;
|
|
6102
6172
|
this._commands = [];
|
|
@@ -6307,6 +6377,7 @@ var CliServerProxyProcessor = class _CliServerProxyProcessor {
|
|
|
6307
6377
|
this.processors = descriptor.processors?.map(
|
|
6308
6378
|
(sub) => new _CliServerProxyProcessor(connection, sub, serverName, true, this.commandPath)
|
|
6309
6379
|
);
|
|
6380
|
+
this.acceptsRawInput = !this.processors?.length;
|
|
6310
6381
|
}
|
|
6311
6382
|
async processCommand(command2, context2) {
|
|
6312
6383
|
return executeOnServer(
|
|
@@ -6355,6 +6426,7 @@ var CliServerMultiProxyProcessor = class _CliServerMultiProxyProcessor {
|
|
|
6355
6426
|
}).filter((e) => e !== null);
|
|
6356
6427
|
return new _CliServerMultiProxyProcessor(subEntries, this.provider, this.commandPath);
|
|
6357
6428
|
});
|
|
6429
|
+
this.acceptsRawInput = !this.processors?.length;
|
|
6358
6430
|
}
|
|
6359
6431
|
async processCommand(command2, context2) {
|
|
6360
6432
|
const defaultServer = this.provider.defaultServer;
|
|
@@ -6445,10 +6517,21 @@ var CliServerManager = class {
|
|
|
6445
6517
|
this.authService = authService;
|
|
6446
6518
|
this.connections = /* @__PURE__ */ new Map();
|
|
6447
6519
|
this._defaultServer = null;
|
|
6520
|
+
/** Bare-name aliases this manager registered (only these are ever removed) */
|
|
6521
|
+
this._bareAliases = /* @__PURE__ */ new Set();
|
|
6448
6522
|
}
|
|
6449
6523
|
get defaultServer() {
|
|
6450
6524
|
return this._defaultServer;
|
|
6451
6525
|
}
|
|
6526
|
+
preferredServer() {
|
|
6527
|
+
if (this._defaultServer && this.connections.get(this._defaultServer)?.connected) {
|
|
6528
|
+
return this._defaultServer;
|
|
6529
|
+
}
|
|
6530
|
+
for (const [name, connection] of this.connections) {
|
|
6531
|
+
if (connection.connected) return name;
|
|
6532
|
+
}
|
|
6533
|
+
return null;
|
|
6534
|
+
}
|
|
6452
6535
|
setDefaultServer(name) {
|
|
6453
6536
|
if (name !== null && !this.connections.has(name)) {
|
|
6454
6537
|
throw new Error(`Unknown server: ${name}`);
|
|
@@ -6511,6 +6594,7 @@ var CliServerManager = class {
|
|
|
6511
6594
|
await connection.connect();
|
|
6512
6595
|
if (connection.connected) {
|
|
6513
6596
|
this.registerProxyProcessors(connection, name);
|
|
6597
|
+
this.unregisterBareAliases();
|
|
6514
6598
|
this.registerBareAliases();
|
|
6515
6599
|
return { success: true, commandCount: connection.commands.length };
|
|
6516
6600
|
}
|
|
@@ -6541,11 +6625,12 @@ var CliServerManager = class {
|
|
|
6541
6625
|
this.registerBareAliases();
|
|
6542
6626
|
}
|
|
6543
6627
|
unregisterBareAliases() {
|
|
6544
|
-
for (const p of
|
|
6545
|
-
if (
|
|
6628
|
+
for (const p of this._bareAliases) {
|
|
6629
|
+
if (this.registry.processors.includes(p)) {
|
|
6546
6630
|
this.registry.unregisterProcessor(p);
|
|
6547
6631
|
}
|
|
6548
6632
|
}
|
|
6633
|
+
this._bareAliases.clear();
|
|
6549
6634
|
}
|
|
6550
6635
|
registerProxyProcessors(connection, serverName) {
|
|
6551
6636
|
for (const descriptor of connection.commands) {
|
|
@@ -6569,7 +6654,7 @@ var CliServerManager = class {
|
|
|
6569
6654
|
}
|
|
6570
6655
|
for (const [command2, servers] of commandCounts) {
|
|
6571
6656
|
const existingProcessor = this.registry.findProcessor(command2, []);
|
|
6572
|
-
if (existingProcessor && !existingProcessor
|
|
6657
|
+
if (existingProcessor && !this._bareAliases.has(existingProcessor))
|
|
6573
6658
|
continue;
|
|
6574
6659
|
if (servers.length === 1) {
|
|
6575
6660
|
const serverName = servers[0];
|
|
@@ -6591,6 +6676,7 @@ var CliServerManager = class {
|
|
|
6591
6676
|
alias.command = command2;
|
|
6592
6677
|
alias.aliases = [namespacedCommand];
|
|
6593
6678
|
this.registry.registerProcessor(alias);
|
|
6679
|
+
this._bareAliases.add(alias);
|
|
6594
6680
|
} else {
|
|
6595
6681
|
const entries = servers.map((serverName) => {
|
|
6596
6682
|
const connection = this.connections.get(serverName);
|
|
@@ -6601,6 +6687,7 @@ var CliServerManager = class {
|
|
|
6601
6687
|
});
|
|
6602
6688
|
const multi = new CliServerMultiProxyProcessor(entries, this);
|
|
6603
6689
|
this.registry.registerProcessor(multi);
|
|
6690
|
+
this._bareAliases.add(multi);
|
|
6604
6691
|
}
|
|
6605
6692
|
}
|
|
6606
6693
|
}
|
|
@@ -6746,6 +6833,28 @@ var WASM_SEARCH_PATHS = [
|
|
|
6746
6833
|
"./assets/wasm/qodalis_cli_wasm_bg.wasm",
|
|
6747
6834
|
"/wasm/qodalis_cli_wasm_bg.wasm"
|
|
6748
6835
|
];
|
|
6836
|
+
function wasmCandidates(wasmUrl, baseUri) {
|
|
6837
|
+
const baseUrl = (typeof document !== "undefined" ? document.baseURI : void 0);
|
|
6838
|
+
let base;
|
|
6839
|
+
try {
|
|
6840
|
+
base = baseUrl ? new URL(baseUrl) : void 0;
|
|
6841
|
+
} catch {
|
|
6842
|
+
base = void 0;
|
|
6843
|
+
}
|
|
6844
|
+
const resolve = (url) => {
|
|
6845
|
+
if (!base) return url;
|
|
6846
|
+
try {
|
|
6847
|
+
return new URL(url, base).href;
|
|
6848
|
+
} catch {
|
|
6849
|
+
return url;
|
|
6850
|
+
}
|
|
6851
|
+
};
|
|
6852
|
+
const candidates = [];
|
|
6853
|
+
if (wasmUrl) candidates.push(resolve(wasmUrl));
|
|
6854
|
+
if (base) candidates.push(resolve("assets/wasm/qodalis_cli_wasm_bg.wasm"));
|
|
6855
|
+
candidates.push(...WASM_SEARCH_PATHS.map(resolve));
|
|
6856
|
+
return [...new Set(candidates)];
|
|
6857
|
+
}
|
|
6749
6858
|
var wasm;
|
|
6750
6859
|
var cachedDataViewMemory0 = null;
|
|
6751
6860
|
var cachedInt32ArrayMemory0 = null;
|
|
@@ -6974,7 +7083,7 @@ async function initWasmAccelerator(wasmUrl) {
|
|
|
6974
7083
|
return accelerator;
|
|
6975
7084
|
}
|
|
6976
7085
|
try {
|
|
6977
|
-
const paths = wasmUrl
|
|
7086
|
+
const paths = wasmCandidates(wasmUrl);
|
|
6978
7087
|
const bytes = await fetchWasmBinary(paths);
|
|
6979
7088
|
await instantiateWasm(bytes);
|
|
6980
7089
|
accelerator = createWasmAccelerator();
|
|
@@ -8710,7 +8819,9 @@ var CliServicesCommandProcessor = class {
|
|
|
8710
8819
|
this.metadata = {
|
|
8711
8820
|
sealed: true,
|
|
8712
8821
|
icon: "\u2699",
|
|
8713
|
-
module: "system"
|
|
8822
|
+
module: "system",
|
|
8823
|
+
// Enforced only when an auth module is loaded
|
|
8824
|
+
requiredPermissions: ["system:services"]
|
|
8714
8825
|
};
|
|
8715
8826
|
this.processors = [
|
|
8716
8827
|
// ── list ──────────────────────────────────────────────
|
|
@@ -11426,8 +11537,6 @@ var CliBackgroundServiceRegistry = class {
|
|
|
11426
11537
|
}
|
|
11427
11538
|
}
|
|
11428
11539
|
};
|
|
11429
|
-
|
|
11430
|
-
// src/lib/services/cli-http-client.ts
|
|
11431
11540
|
var CliHttpClient = class {
|
|
11432
11541
|
constructor(signal) {
|
|
11433
11542
|
this.signal = signal;
|
|
@@ -11477,17 +11586,15 @@ var CliHttpClient = class {
|
|
|
11477
11586
|
// ── Internals ────────────────────────────────────────────────────
|
|
11478
11587
|
async jsonRequest(method, url, body, options) {
|
|
11479
11588
|
const { timeout, ...fetchOptions } = options ?? {};
|
|
11480
|
-
const headers =
|
|
11481
|
-
...fetchOptions.headers ?? {}
|
|
11482
|
-
};
|
|
11589
|
+
const headers = new Headers(fetchOptions.headers);
|
|
11483
11590
|
let resolvedBody;
|
|
11484
11591
|
if (body !== void 0 && body !== null) {
|
|
11485
11592
|
if (this.isRawBody(body)) {
|
|
11486
11593
|
resolvedBody = body;
|
|
11487
11594
|
} else {
|
|
11488
11595
|
resolvedBody = JSON.stringify(body);
|
|
11489
|
-
if (!headers
|
|
11490
|
-
headers
|
|
11596
|
+
if (!headers.has("Content-Type")) {
|
|
11597
|
+
headers.set("Content-Type", "application/json");
|
|
11491
11598
|
}
|
|
11492
11599
|
}
|
|
11493
11600
|
}
|
|
@@ -11511,16 +11618,23 @@ var CliHttpClient = class {
|
|
|
11511
11618
|
});
|
|
11512
11619
|
if (!response.ok) {
|
|
11513
11620
|
let errorMessage = `${response.status} ${response.statusText}`;
|
|
11621
|
+
let errorBody;
|
|
11514
11622
|
try {
|
|
11515
|
-
|
|
11516
|
-
|
|
11517
|
-
|
|
11518
|
-
|
|
11519
|
-
|
|
11623
|
+
errorBody = await response.json();
|
|
11624
|
+
const parsed = errorBody;
|
|
11625
|
+
if (parsed.error) {
|
|
11626
|
+
errorMessage = parsed.error;
|
|
11627
|
+
} else if (parsed.message) {
|
|
11628
|
+
errorMessage = parsed.message;
|
|
11520
11629
|
}
|
|
11521
11630
|
} catch {
|
|
11522
11631
|
}
|
|
11523
|
-
throw new
|
|
11632
|
+
throw new cliCore.CliHttpError(
|
|
11633
|
+
errorMessage,
|
|
11634
|
+
response.status,
|
|
11635
|
+
response.statusText,
|
|
11636
|
+
errorBody
|
|
11637
|
+
);
|
|
11524
11638
|
}
|
|
11525
11639
|
const text = await response.text();
|
|
11526
11640
|
if (!text) {
|
|
@@ -14553,7 +14667,7 @@ var CliTerminalLineRenderer = class {
|
|
|
14553
14667
|
*/
|
|
14554
14668
|
getPromptString(options) {
|
|
14555
14669
|
const name = options.userName ?? "";
|
|
14556
|
-
let promptStart = options.hideUserName
|
|
14670
|
+
let promptStart = options.hideUserName ? "" : name ? `\x1B[32m${name}\x1B[0m:` : options.anonymousName ? `\x1B[90m${options.anonymousName}\x1B[0m:` : "";
|
|
14557
14671
|
if (options.contextProcessor) {
|
|
14558
14672
|
promptStart = `${options.contextProcessor}`;
|
|
14559
14673
|
}
|
|
@@ -14866,9 +14980,11 @@ var CliExecutionContext = class {
|
|
|
14866
14980
|
getPromptOptions() {
|
|
14867
14981
|
const usersConfig = this.services.get("cli-users-module-config");
|
|
14868
14982
|
const hideUserName = usersConfig?.hideUserName ?? false;
|
|
14983
|
+
const anonymousName = usersConfig && !this.userSession ? (usersConfig.anonymousName ?? "anonymous") || void 0 : void 0;
|
|
14869
14984
|
return {
|
|
14870
14985
|
userName: this.userSession?.displayName,
|
|
14871
14986
|
hideUserName,
|
|
14987
|
+
anonymousName,
|
|
14872
14988
|
contextProcessor: this.contextProcessor?.command,
|
|
14873
14989
|
pathProvider: this.promptPathProvider
|
|
14874
14990
|
};
|
|
@@ -15479,7 +15595,7 @@ var CliCommandExecutor = class {
|
|
|
15479
15595
|
}
|
|
15480
15596
|
if (!processor) {
|
|
15481
15597
|
const aliasProcessor = this.registry.findProcessor("alias", []);
|
|
15482
|
-
const aliases = aliasProcessor?.userAliases ?? {};
|
|
15598
|
+
const aliases = aliasProcessor?.aliasesFor?.(context2) ?? aliasProcessor?.userAliases ?? {};
|
|
15483
15599
|
if (aliases[mainCommand]) {
|
|
15484
15600
|
this.completeProcess(processEntry, context2, 0);
|
|
15485
15601
|
return await this.executeSingleCommand(
|
|
@@ -15536,13 +15652,21 @@ var CliCommandExecutor = class {
|
|
|
15536
15652
|
}
|
|
15537
15653
|
const value = processor.acceptsRawInput || processor.valueRequired ? cliCore.getRightOfWord(commandName, processor.command) : void 0;
|
|
15538
15654
|
commandToProcess.value = value;
|
|
15655
|
+
const blockedBy = await this.runGuards(commandToProcess, processor, context2);
|
|
15656
|
+
if (blockedBy) {
|
|
15657
|
+
context2.writer.writeError(blockedBy);
|
|
15658
|
+
this.failProcess(processEntry, context2);
|
|
15659
|
+
context2.process.exit(-1, { silent: true });
|
|
15660
|
+
return;
|
|
15661
|
+
}
|
|
15539
15662
|
const hasPositionalInput = !!value || !!data;
|
|
15540
15663
|
const hasNamedArgs = Object.keys(args).length > 0;
|
|
15541
15664
|
if (processor.valueRequired && !hasPositionalInput && !hasNamedArgs) {
|
|
15542
15665
|
context2.writer.writeError(
|
|
15543
15666
|
`Value required: ${context2.writer.wrapInColor(`${commandName} <value>`, cliCore.CliForegroundColor.Cyan)}`
|
|
15544
15667
|
);
|
|
15545
|
-
|
|
15668
|
+
this.failProcess(processEntry, context2);
|
|
15669
|
+
context2.process.exit(-1, { silent: true });
|
|
15546
15670
|
return;
|
|
15547
15671
|
}
|
|
15548
15672
|
if (!this.validateBeforeExecution(context2, processor, args, hasPositionalInput)) {
|
|
@@ -15558,7 +15682,8 @@ var CliCommandExecutor = class {
|
|
|
15558
15682
|
context2.writer.writeError(
|
|
15559
15683
|
validationResult?.message || "An error occurred while validating the command."
|
|
15560
15684
|
);
|
|
15561
|
-
|
|
15685
|
+
this.failProcess(processEntry, context2);
|
|
15686
|
+
context2.process.exit(-1, { silent: true });
|
|
15562
15687
|
return;
|
|
15563
15688
|
}
|
|
15564
15689
|
}
|
|
@@ -15622,6 +15747,10 @@ var CliCommandExecutor = class {
|
|
|
15622
15747
|
"Process exited successfully with code 0"
|
|
15623
15748
|
);
|
|
15624
15749
|
}
|
|
15750
|
+
} else if (cliCore.isCliAccessDeniedError(e)) {
|
|
15751
|
+
this.failProcess(processEntry, context2);
|
|
15752
|
+
context2.writer.writeError(e.message);
|
|
15753
|
+
context2.process.exit(-1);
|
|
15625
15754
|
} else {
|
|
15626
15755
|
this.failProcess(processEntry, context2);
|
|
15627
15756
|
context2.writer.writeError(`Error executing command: ${e}`);
|
|
@@ -15706,6 +15835,52 @@ var CliCommandExecutor = class {
|
|
|
15706
15835
|
* parameter. In that case the global handler must be skipped so the
|
|
15707
15836
|
* processor can handle the flag itself.
|
|
15708
15837
|
*/
|
|
15838
|
+
/**
|
|
15839
|
+
* Runs all registered {@link ICliCommandGuard}s that apply to this command,
|
|
15840
|
+
* in priority order. Returns the block message of the first guard that
|
|
15841
|
+
* denies the command, or `undefined` when every guard allows it.
|
|
15842
|
+
* A guard that throws is treated as a denial (fail closed).
|
|
15843
|
+
*/
|
|
15844
|
+
async runGuards(command2, processor, context2) {
|
|
15845
|
+
let guards;
|
|
15846
|
+
try {
|
|
15847
|
+
guards = context2.services.getAll(cliCore.ICliCommandGuard_TOKEN);
|
|
15848
|
+
} catch {
|
|
15849
|
+
return void 0;
|
|
15850
|
+
}
|
|
15851
|
+
if (!guards.length) {
|
|
15852
|
+
return void 0;
|
|
15853
|
+
}
|
|
15854
|
+
const ordered = [...guards].sort(
|
|
15855
|
+
(a, b) => (a.priority ?? 0) - (b.priority ?? 0)
|
|
15856
|
+
);
|
|
15857
|
+
for (const guard of ordered) {
|
|
15858
|
+
let result;
|
|
15859
|
+
try {
|
|
15860
|
+
if (guard.appliesTo && !await guard.appliesTo(processor, command2, context2)) {
|
|
15861
|
+
continue;
|
|
15862
|
+
}
|
|
15863
|
+
result = await guard.canActivate(command2, processor, context2);
|
|
15864
|
+
} catch (e) {
|
|
15865
|
+
return `${processor.command}: blocked by guard '${guard.name}' (${e?.message ?? e})`;
|
|
15866
|
+
}
|
|
15867
|
+
const denial = this.guardDenial(result, processor.command);
|
|
15868
|
+
if (denial) {
|
|
15869
|
+
return denial;
|
|
15870
|
+
}
|
|
15871
|
+
}
|
|
15872
|
+
return void 0;
|
|
15873
|
+
}
|
|
15874
|
+
guardDenial(result, commandName) {
|
|
15875
|
+
const denied = `${commandName}: permission denied`;
|
|
15876
|
+
if (result === void 0 || result === true) return void 0;
|
|
15877
|
+
if (result === false) return denied;
|
|
15878
|
+
if (typeof result === "string") return result || denied;
|
|
15879
|
+
if (typeof result === "object" && result !== null) {
|
|
15880
|
+
return result.allowed ? void 0 : result.message || denied;
|
|
15881
|
+
}
|
|
15882
|
+
return void 0;
|
|
15883
|
+
}
|
|
15709
15884
|
processorOwnsParameter(processor, globalParam) {
|
|
15710
15885
|
const globalNames = /* @__PURE__ */ new Set([
|
|
15711
15886
|
globalParam.name,
|
|
@@ -16228,6 +16403,18 @@ var CliBoot = class {
|
|
|
16228
16403
|
if (module.services && module.services.length > 0) {
|
|
16229
16404
|
this.services.set(module.services);
|
|
16230
16405
|
}
|
|
16406
|
+
if (module.guards && module.guards.length > 0) {
|
|
16407
|
+
this.services.set(
|
|
16408
|
+
module.guards.map((guard) => ({
|
|
16409
|
+
provide: cliCore.ICliCommandGuard_TOKEN,
|
|
16410
|
+
useValue: guard,
|
|
16411
|
+
multi: true
|
|
16412
|
+
}))
|
|
16413
|
+
);
|
|
16414
|
+
}
|
|
16415
|
+
if (module.permissions && module.permissions.length > 0) {
|
|
16416
|
+
this.services.get(cliCore.ICliPermissionRegistry_TOKEN)?.register(module.permissions, module.name);
|
|
16417
|
+
}
|
|
16231
16418
|
if (module.translations) {
|
|
16232
16419
|
for (const [locale, translations] of Object.entries(module.translations)) {
|
|
16233
16420
|
context2.translator.addTranslations(locale, translations);
|
|
@@ -16275,19 +16462,47 @@ var CliBoot = class {
|
|
|
16275
16462
|
this.registry.registerProcessor(processor);
|
|
16276
16463
|
}
|
|
16277
16464
|
await this.initializeProcessorsInternal(context2, filtered);
|
|
16465
|
+
this.adoptMergedSubProcessors(filtered);
|
|
16278
16466
|
}
|
|
16279
16467
|
this.bootedModules.add(module.name);
|
|
16280
16468
|
if (!this.moduleRegistry.has(module.name)) {
|
|
16281
16469
|
this.moduleRegistry.track(module);
|
|
16282
16470
|
}
|
|
16283
16471
|
}
|
|
16472
|
+
/**
|
|
16473
|
+
* Sub-processors of an `extendsProcessor` processor are merged into the
|
|
16474
|
+
* processor it extends, so they belong to that one: re-parent them so they
|
|
16475
|
+
* inherit its access rules (`requiredPermissions` etc.) and help/root
|
|
16476
|
+
* lookups resolve to the registered processor.
|
|
16477
|
+
*/
|
|
16478
|
+
adoptMergedSubProcessors(processors) {
|
|
16479
|
+
for (const processor of processors) {
|
|
16480
|
+
if (!processor.extendsProcessor) continue;
|
|
16481
|
+
const name = processor.command.toLowerCase();
|
|
16482
|
+
const host = this.registry.processors.find(
|
|
16483
|
+
(p) => p.command.toLowerCase() === name
|
|
16484
|
+
);
|
|
16485
|
+
if (!host || host === processor) continue;
|
|
16486
|
+
for (const sub of processor.processors ?? []) {
|
|
16487
|
+
if (host.processors?.includes(sub)) {
|
|
16488
|
+
sub.parent = host;
|
|
16489
|
+
}
|
|
16490
|
+
}
|
|
16491
|
+
}
|
|
16492
|
+
}
|
|
16284
16493
|
buildCoreModule() {
|
|
16285
16494
|
return {
|
|
16286
16495
|
apiVersion: API_VERSION,
|
|
16287
16496
|
name: "@qodalis/cli-core",
|
|
16288
16497
|
version: cliCore.LIBRARY_VERSION,
|
|
16289
16498
|
description: "Core CLI services and utilities",
|
|
16290
|
-
processors: [...builtinProcessors]
|
|
16499
|
+
processors: [...builtinProcessors],
|
|
16500
|
+
permissions: [
|
|
16501
|
+
{ name: "system:packages", description: "Install, update and remove packages (pkg)" },
|
|
16502
|
+
{ name: "system:services", description: "Start and stop background services" },
|
|
16503
|
+
{ name: "system:eval", description: "Evaluate JavaScript (eval, also requires allowEval)" },
|
|
16504
|
+
{ name: "shell:execute", description: "Open a remote shell on a server (ssh)" }
|
|
16505
|
+
]
|
|
16291
16506
|
};
|
|
16292
16507
|
}
|
|
16293
16508
|
/**
|
|
@@ -16419,6 +16634,29 @@ var CliDragDropService = class {
|
|
|
16419
16634
|
}
|
|
16420
16635
|
};
|
|
16421
16636
|
|
|
16637
|
+
// src/lib/services/cli-permission-registry.ts
|
|
16638
|
+
var CliPermissionRegistry = class {
|
|
16639
|
+
constructor() {
|
|
16640
|
+
this.definitions = /* @__PURE__ */ new Map();
|
|
16641
|
+
}
|
|
16642
|
+
register(definitions, module) {
|
|
16643
|
+
for (const def of definitions ?? []) {
|
|
16644
|
+
const name = def?.name?.trim();
|
|
16645
|
+
if (!name) continue;
|
|
16646
|
+
const key = name.toLowerCase();
|
|
16647
|
+
if (!this.definitions.has(key)) {
|
|
16648
|
+
this.definitions.set(key, { ...def, name, module: def.module ?? module });
|
|
16649
|
+
}
|
|
16650
|
+
}
|
|
16651
|
+
}
|
|
16652
|
+
list() {
|
|
16653
|
+
return [...this.definitions.values()].sort((a, b) => a.name.localeCompare(b.name));
|
|
16654
|
+
}
|
|
16655
|
+
get(name) {
|
|
16656
|
+
return this.definitions.get(name.trim().toLowerCase());
|
|
16657
|
+
}
|
|
16658
|
+
};
|
|
16659
|
+
|
|
16422
16660
|
// src/lib/addons/overlay.ts
|
|
16423
16661
|
var OverlayAddon = class {
|
|
16424
16662
|
constructor() {
|
|
@@ -16720,6 +16958,22 @@ var CliServerAuthService = class {
|
|
|
16720
16958
|
}
|
|
16721
16959
|
return merged;
|
|
16722
16960
|
}
|
|
16961
|
+
async getWebSocketParams(serverName) {
|
|
16962
|
+
const providers = this.services.getAll(
|
|
16963
|
+
cliCore.ICliServerAuthTokenProvider_TOKEN
|
|
16964
|
+
);
|
|
16965
|
+
let handled = false;
|
|
16966
|
+
const merged = {};
|
|
16967
|
+
for (const provider of providers) {
|
|
16968
|
+
if (!provider.getWebSocketParams) continue;
|
|
16969
|
+
const params = await provider.getWebSocketParams(serverName);
|
|
16970
|
+
if (params) {
|
|
16971
|
+
handled = true;
|
|
16972
|
+
Object.assign(merged, params);
|
|
16973
|
+
}
|
|
16974
|
+
}
|
|
16975
|
+
return handled ? merged : void 0;
|
|
16976
|
+
}
|
|
16723
16977
|
};
|
|
16724
16978
|
|
|
16725
16979
|
// src/lib/completion/cli-command-completion-provider.ts
|
|
@@ -17237,7 +17491,9 @@ var CliSshCommandProcessor = class {
|
|
|
17237
17491
|
this.valueRequired = false;
|
|
17238
17492
|
this.metadata = {
|
|
17239
17493
|
module: "@qodalis/cli-server",
|
|
17240
|
-
icon: "\u{1F5A5}"
|
|
17494
|
+
icon: "\u{1F5A5}",
|
|
17495
|
+
// Enforced only when an auth module is loaded; the server checks it too
|
|
17496
|
+
requiredPermissions: [cliCore.CliServerPermissions.ShellExecute]
|
|
17241
17497
|
};
|
|
17242
17498
|
this.parameters = [
|
|
17243
17499
|
{
|
|
@@ -17247,9 +17503,6 @@ var CliSshCommandProcessor = class {
|
|
|
17247
17503
|
type: "string"
|
|
17248
17504
|
}
|
|
17249
17505
|
];
|
|
17250
|
-
this.socket = null;
|
|
17251
|
-
this.context = null;
|
|
17252
|
-
this.resolved = false;
|
|
17253
17506
|
}
|
|
17254
17507
|
async processCommand(command2, context2) {
|
|
17255
17508
|
const manager = context2.services.getRequired(
|
|
@@ -17281,26 +17534,34 @@ var CliSshCommandProcessor = class {
|
|
|
17281
17534
|
await this.executeInteractive(connection, serverName, context2);
|
|
17282
17535
|
}
|
|
17283
17536
|
}
|
|
17284
|
-
|
|
17285
|
-
|
|
17286
|
-
|
|
17287
|
-
|
|
17288
|
-
|
|
17289
|
-
|
|
17290
|
-
|
|
17291
|
-
|
|
17292
|
-
|
|
17293
|
-
|
|
17294
|
-
}
|
|
17295
|
-
}
|
|
17296
|
-
onDispose(_context) {
|
|
17297
|
-
if (this.socket) {
|
|
17298
|
-
if (this.socket.readyState === WebSocket.OPEN || this.socket.readyState === WebSocket.CONNECTING) {
|
|
17299
|
-
this.socket.close();
|
|
17537
|
+
/**
|
|
17538
|
+
* Full-screen handler for one session: input, resize and disposal go to
|
|
17539
|
+
* that session's socket only.
|
|
17540
|
+
*/
|
|
17541
|
+
fullScreenHandler(session) {
|
|
17542
|
+
const handler = Object.create(this);
|
|
17543
|
+
handler.onData = async (data) => {
|
|
17544
|
+
if (session.socket?.readyState === WebSocket.OPEN) {
|
|
17545
|
+
const msg = { type: "stdin", data };
|
|
17546
|
+
session.socket.send(JSON.stringify(msg));
|
|
17300
17547
|
}
|
|
17301
|
-
|
|
17302
|
-
|
|
17303
|
-
|
|
17548
|
+
};
|
|
17549
|
+
handler.onResize = (cols, rows) => {
|
|
17550
|
+
if (session.socket?.readyState === WebSocket.OPEN) {
|
|
17551
|
+
const msg = { type: "resize", cols, rows };
|
|
17552
|
+
session.socket.send(JSON.stringify(msg));
|
|
17553
|
+
}
|
|
17554
|
+
};
|
|
17555
|
+
handler.onDispose = () => {
|
|
17556
|
+
session.disposed = true;
|
|
17557
|
+
if (session.socket) {
|
|
17558
|
+
if (session.socket.readyState === WebSocket.OPEN || session.socket.readyState === WebSocket.CONNECTING) {
|
|
17559
|
+
session.socket.close();
|
|
17560
|
+
}
|
|
17561
|
+
session.socket = null;
|
|
17562
|
+
}
|
|
17563
|
+
};
|
|
17564
|
+
return handler;
|
|
17304
17565
|
}
|
|
17305
17566
|
async resolveServer(command2, shellServers, context2) {
|
|
17306
17567
|
const empty = { serverName: null, oneShotCmd: null, connection: null };
|
|
@@ -17326,7 +17587,7 @@ var CliSshCommandProcessor = class {
|
|
|
17326
17587
|
const candidateName = parts[0];
|
|
17327
17588
|
const entry2 = shellServers.find((s) => s.name === candidateName);
|
|
17328
17589
|
if (entry2) {
|
|
17329
|
-
const oneShotCmd = parts.slice(1).join(" ") || null;
|
|
17590
|
+
const oneShotCmd = this.remoteCommandFromRaw(command2.rawCommand, candidateName) ?? (parts.slice(1).join(" ") || null);
|
|
17330
17591
|
return {
|
|
17331
17592
|
serverName: candidateName,
|
|
17332
17593
|
oneShotCmd,
|
|
@@ -17380,41 +17641,57 @@ var CliSshCommandProcessor = class {
|
|
|
17380
17641
|
};
|
|
17381
17642
|
}
|
|
17382
17643
|
async executeInteractive(connection, serverName, context2) {
|
|
17383
|
-
|
|
17644
|
+
const session = { socket: null, resolved: false, disposed: false };
|
|
17384
17645
|
const cols = context2.terminal.cols;
|
|
17385
17646
|
const rows = context2.terminal.rows;
|
|
17386
|
-
const wsUrl = this.buildShellUrl(connection, cols, rows);
|
|
17647
|
+
const wsUrl = await this.buildShellUrl(connection, cols, rows);
|
|
17387
17648
|
context2.spinner?.show(`Connecting to ${serverName}...`);
|
|
17388
|
-
|
|
17649
|
+
let fullScreen = false;
|
|
17389
17650
|
return new Promise((resolve) => {
|
|
17651
|
+
const signal = context2.signal;
|
|
17390
17652
|
const resolveOnce = () => {
|
|
17391
|
-
|
|
17392
|
-
|
|
17653
|
+
signal?.removeEventListener("abort", onAbort);
|
|
17654
|
+
if (!session.resolved) {
|
|
17655
|
+
session.resolved = true;
|
|
17393
17656
|
resolve();
|
|
17394
17657
|
}
|
|
17395
17658
|
};
|
|
17659
|
+
const onAbort = () => {
|
|
17660
|
+
context2.spinner?.hide();
|
|
17661
|
+
this.cleanup(session);
|
|
17662
|
+
if (fullScreen) context2.exitFullScreenMode();
|
|
17663
|
+
context2.process.exit(130, { silent: true });
|
|
17664
|
+
resolveOnce();
|
|
17665
|
+
};
|
|
17666
|
+
if (signal?.aborted) {
|
|
17667
|
+
onAbort();
|
|
17668
|
+
return;
|
|
17669
|
+
}
|
|
17670
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
17396
17671
|
try {
|
|
17397
|
-
|
|
17672
|
+
session.socket = new WebSocket(wsUrl);
|
|
17398
17673
|
} catch (e) {
|
|
17399
17674
|
context2.spinner?.hide();
|
|
17400
17675
|
context2.writer.writeError(
|
|
17401
17676
|
`Failed to connect to '${serverName}': ${e.message}`
|
|
17402
17677
|
);
|
|
17403
|
-
context2.process.exit(1);
|
|
17678
|
+
context2.process.exit(1, { silent: true });
|
|
17404
17679
|
resolveOnce();
|
|
17405
17680
|
return;
|
|
17406
17681
|
}
|
|
17407
|
-
|
|
17682
|
+
session.socket.onopen = () => {
|
|
17408
17683
|
};
|
|
17409
|
-
|
|
17410
|
-
const msg =
|
|
17684
|
+
session.socket.onmessage = (event) => {
|
|
17685
|
+
const msg = parseShellMessage(event.data);
|
|
17686
|
+
if (!msg) return;
|
|
17411
17687
|
switch (msg.type) {
|
|
17412
17688
|
case "ready":
|
|
17413
17689
|
context2.spinner?.hide();
|
|
17414
17690
|
context2.writer.writeInfo(
|
|
17415
17691
|
`Connected to ${serverName} (${msg.os}, ${msg.shell})`
|
|
17416
17692
|
);
|
|
17417
|
-
context2.enterFullScreenMode(this);
|
|
17693
|
+
context2.enterFullScreenMode(this.fullScreenHandler(session));
|
|
17694
|
+
fullScreen = true;
|
|
17418
17695
|
context2.terminal.write("\x1B[2J\x1B[H\x1B[?25h");
|
|
17419
17696
|
break;
|
|
17420
17697
|
case "stdout":
|
|
@@ -17424,70 +17701,82 @@ var CliSshCommandProcessor = class {
|
|
|
17424
17701
|
context2.terminal.write(msg.data);
|
|
17425
17702
|
break;
|
|
17426
17703
|
case "exit":
|
|
17427
|
-
context2.exitFullScreenMode();
|
|
17704
|
+
if (fullScreen) context2.exitFullScreenMode();
|
|
17428
17705
|
context2.writer.writeInfo(
|
|
17429
17706
|
`Shell exited with code ${msg.code}.`
|
|
17430
17707
|
);
|
|
17431
|
-
context2.process.exit(msg.code);
|
|
17432
|
-
this.cleanup();
|
|
17708
|
+
context2.process.exit(msg.code, { silent: true });
|
|
17709
|
+
this.cleanup(session);
|
|
17433
17710
|
resolveOnce();
|
|
17434
17711
|
break;
|
|
17435
17712
|
case "error":
|
|
17436
17713
|
context2.spinner?.hide();
|
|
17437
|
-
context2.exitFullScreenMode();
|
|
17714
|
+
if (fullScreen) context2.exitFullScreenMode();
|
|
17438
17715
|
context2.writer.writeError(
|
|
17439
17716
|
`Server error: ${msg.message}`
|
|
17440
17717
|
);
|
|
17441
|
-
context2.process.exit(1);
|
|
17442
|
-
this.cleanup();
|
|
17718
|
+
context2.process.exit(1, { silent: true });
|
|
17719
|
+
this.cleanup(session);
|
|
17443
17720
|
resolveOnce();
|
|
17444
17721
|
break;
|
|
17445
17722
|
}
|
|
17446
17723
|
};
|
|
17447
|
-
|
|
17724
|
+
let errored = false;
|
|
17725
|
+
session.socket.onclose = (event) => {
|
|
17448
17726
|
context2.spinner?.hide();
|
|
17449
|
-
if (
|
|
17450
|
-
context2.exitFullScreenMode();
|
|
17451
|
-
|
|
17452
|
-
|
|
17453
|
-
);
|
|
17454
|
-
context2.
|
|
17727
|
+
if (!session.disposed && !session.resolved) {
|
|
17728
|
+
if (fullScreen) context2.exitFullScreenMode();
|
|
17729
|
+
const reason = describeShellClose(event ?? {}, serverName);
|
|
17730
|
+
if (reason) context2.writer.writeError(reason);
|
|
17731
|
+
else if (errored) context2.writer.writeError(`WebSocket error connecting to '${serverName}'.`);
|
|
17732
|
+
else context2.writer.writeWarning(`Connection to '${serverName}' closed.`);
|
|
17733
|
+
context2.process.exit(1, { silent: true });
|
|
17455
17734
|
}
|
|
17456
|
-
this.cleanup();
|
|
17735
|
+
this.cleanup(session);
|
|
17457
17736
|
resolveOnce();
|
|
17458
17737
|
};
|
|
17459
|
-
|
|
17460
|
-
|
|
17461
|
-
if (!this.resolved) {
|
|
17462
|
-
context2.writer.writeError(
|
|
17463
|
-
`WebSocket error connecting to '${serverName}'.`
|
|
17464
|
-
);
|
|
17465
|
-
context2.process.exit(1);
|
|
17466
|
-
}
|
|
17467
|
-
this.cleanup();
|
|
17468
|
-
resolveOnce();
|
|
17738
|
+
session.socket.onerror = () => {
|
|
17739
|
+
errored = true;
|
|
17469
17740
|
};
|
|
17470
17741
|
});
|
|
17471
17742
|
}
|
|
17472
17743
|
async executeOneShot(connection, serverName, cmd, context2) {
|
|
17473
17744
|
const cols = context2.terminal.cols;
|
|
17474
17745
|
const rows = context2.terminal.rows;
|
|
17475
|
-
const wsUrl = this.buildShellUrl(connection, cols, rows, cmd);
|
|
17746
|
+
const wsUrl = await this.buildShellUrl(connection, cols, rows, cmd);
|
|
17476
17747
|
context2.spinner?.show(`Running on ${serverName}...`);
|
|
17477
|
-
|
|
17748
|
+
const session = { socket: null, resolved: false, disposed: false };
|
|
17749
|
+
return new Promise((resolvePromise) => {
|
|
17750
|
+
const signal = context2.signal;
|
|
17751
|
+
const resolve = () => {
|
|
17752
|
+
signal?.removeEventListener("abort", onAbort);
|
|
17753
|
+
resolvePromise();
|
|
17754
|
+
};
|
|
17755
|
+
const onAbort = () => {
|
|
17756
|
+
context2.spinner?.hide();
|
|
17757
|
+
this.cleanup(session);
|
|
17758
|
+
context2.process.exit(130, { silent: true });
|
|
17759
|
+
resolve();
|
|
17760
|
+
};
|
|
17761
|
+
if (signal?.aborted) {
|
|
17762
|
+
onAbort();
|
|
17763
|
+
return;
|
|
17764
|
+
}
|
|
17765
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
17478
17766
|
try {
|
|
17479
|
-
|
|
17767
|
+
session.socket = new WebSocket(wsUrl);
|
|
17480
17768
|
} catch (e) {
|
|
17481
17769
|
context2.spinner?.hide();
|
|
17482
17770
|
context2.writer.writeError(
|
|
17483
17771
|
`Failed to connect to '${serverName}': ${e.message}`
|
|
17484
17772
|
);
|
|
17485
|
-
context2.process.exit(1);
|
|
17773
|
+
context2.process.exit(1, { silent: true });
|
|
17486
17774
|
resolve();
|
|
17487
17775
|
return;
|
|
17488
17776
|
}
|
|
17489
|
-
|
|
17490
|
-
const msg =
|
|
17777
|
+
session.socket.onmessage = (event) => {
|
|
17778
|
+
const msg = parseShellMessage(event.data);
|
|
17779
|
+
if (!msg) return;
|
|
17491
17780
|
switch (msg.type) {
|
|
17492
17781
|
case "ready":
|
|
17493
17782
|
context2.spinner?.hide();
|
|
@@ -17500,9 +17789,9 @@ var CliSshCommandProcessor = class {
|
|
|
17500
17789
|
break;
|
|
17501
17790
|
case "exit":
|
|
17502
17791
|
if (msg.code !== 0) {
|
|
17503
|
-
context2.process.exit(msg.code);
|
|
17792
|
+
context2.process.exit(msg.code, { silent: true });
|
|
17504
17793
|
}
|
|
17505
|
-
this.cleanup();
|
|
17794
|
+
this.cleanup(session);
|
|
17506
17795
|
resolve();
|
|
17507
17796
|
break;
|
|
17508
17797
|
case "error":
|
|
@@ -17510,53 +17799,82 @@ var CliSshCommandProcessor = class {
|
|
|
17510
17799
|
context2.writer.writeError(
|
|
17511
17800
|
`Server error: ${msg.message}`
|
|
17512
17801
|
);
|
|
17513
|
-
context2.process.exit(1);
|
|
17514
|
-
this.cleanup();
|
|
17802
|
+
context2.process.exit(1, { silent: true });
|
|
17803
|
+
this.cleanup(session);
|
|
17515
17804
|
resolve();
|
|
17516
17805
|
break;
|
|
17517
17806
|
}
|
|
17518
17807
|
};
|
|
17519
|
-
|
|
17808
|
+
let errored = false;
|
|
17809
|
+
session.socket.onclose = (event) => {
|
|
17520
17810
|
context2.spinner?.hide();
|
|
17521
|
-
|
|
17811
|
+
const reason = describeShellClose(event ?? {}, serverName);
|
|
17812
|
+
if (reason || errored) {
|
|
17813
|
+
context2.writer.writeError(reason ?? `WebSocket error connecting to '${serverName}'.`);
|
|
17814
|
+
context2.process.exit(1, { silent: true });
|
|
17815
|
+
}
|
|
17816
|
+
this.cleanup(session);
|
|
17522
17817
|
resolve();
|
|
17523
17818
|
};
|
|
17524
|
-
|
|
17525
|
-
|
|
17526
|
-
context2.writer.writeError(
|
|
17527
|
-
`WebSocket error connecting to '${serverName}'.`
|
|
17528
|
-
);
|
|
17529
|
-
context2.process.exit(1);
|
|
17530
|
-
this.cleanup();
|
|
17531
|
-
resolve();
|
|
17819
|
+
session.socket.onerror = () => {
|
|
17820
|
+
errored = true;
|
|
17532
17821
|
};
|
|
17533
17822
|
});
|
|
17534
17823
|
}
|
|
17824
|
+
/** `ssh <server> <cmd...>` → `<cmd...>` exactly as typed */
|
|
17825
|
+
remoteCommandFromRaw(raw, serverName) {
|
|
17826
|
+
const match = raw?.trim().match(/^\S+\s+(\S+)\s+([\s\S]+)$/);
|
|
17827
|
+
return match && match[1] === serverName ? match[2].trim() : null;
|
|
17828
|
+
}
|
|
17535
17829
|
buildShellUrl(connection, cols, rows, cmd) {
|
|
17536
|
-
|
|
17537
|
-
const wsBase = baseUrl.replace(/^https:/, "wss:").replace(/^http:/, "ws:");
|
|
17538
|
-
let url = `${wsBase}/ws/v1/qcli/shell?cols=${cols}&rows=${rows}`;
|
|
17830
|
+
let path = `/ws/v1/qcli/shell?cols=${cols}&rows=${rows}`;
|
|
17539
17831
|
if (cmd) {
|
|
17540
|
-
|
|
17832
|
+
path += `&cmd=${encodeURIComponent(cmd)}`;
|
|
17541
17833
|
}
|
|
17542
|
-
return
|
|
17543
|
-
url,
|
|
17544
|
-
() => connection.resolveAuthHeaders()
|
|
17545
|
-
);
|
|
17834
|
+
return connection.buildWebSocketUrl(path);
|
|
17546
17835
|
}
|
|
17547
|
-
cleanup() {
|
|
17548
|
-
|
|
17549
|
-
|
|
17550
|
-
|
|
17551
|
-
|
|
17552
|
-
|
|
17553
|
-
|
|
17836
|
+
cleanup(session) {
|
|
17837
|
+
const socket = session.socket;
|
|
17838
|
+
if (socket) {
|
|
17839
|
+
socket.onmessage = null;
|
|
17840
|
+
socket.onclose = null;
|
|
17841
|
+
socket.onerror = null;
|
|
17842
|
+
if (socket.readyState === WebSocket.OPEN || socket.readyState === WebSocket.CONNECTING) {
|
|
17843
|
+
socket.close();
|
|
17554
17844
|
}
|
|
17555
|
-
|
|
17845
|
+
session.socket = null;
|
|
17556
17846
|
}
|
|
17557
|
-
this.context = null;
|
|
17558
17847
|
}
|
|
17559
17848
|
};
|
|
17849
|
+
function parseShellMessage(data) {
|
|
17850
|
+
try {
|
|
17851
|
+
const msg = typeof data === "string" ? JSON.parse(data) : null;
|
|
17852
|
+
return msg && typeof msg === "object" && typeof msg.type === "string" ? msg : null;
|
|
17853
|
+
} catch {
|
|
17854
|
+
return null;
|
|
17855
|
+
}
|
|
17856
|
+
}
|
|
17857
|
+
function describeShellClose(event, serverName) {
|
|
17858
|
+
const reason = (event.reason ?? "").toLowerCase();
|
|
17859
|
+
switch (event.code) {
|
|
17860
|
+
case 4401:
|
|
17861
|
+
if (reason === "session ended") return `ssh: shell closed by '${serverName}' (session ended or shell access revoked)`;
|
|
17862
|
+
if (reason === "shell access is not configured") {
|
|
17863
|
+
return `ssh: shell access is not configured on '${serverName}'`;
|
|
17864
|
+
}
|
|
17865
|
+
return `ssh: login required on '${serverName}'`;
|
|
17866
|
+
case 4403:
|
|
17867
|
+
return `ssh: permission denied on '${serverName}' (requires shell:execute or a valid shell token)`;
|
|
17868
|
+
case 4404:
|
|
17869
|
+
return `ssh: the shell is disabled on '${serverName}'`;
|
|
17870
|
+
case 4408:
|
|
17871
|
+
return `ssh: shell timed out on '${serverName}'`;
|
|
17872
|
+
case 4429:
|
|
17873
|
+
return `ssh: too many shell sessions on '${serverName}'`;
|
|
17874
|
+
default:
|
|
17875
|
+
return null;
|
|
17876
|
+
}
|
|
17877
|
+
}
|
|
17560
17878
|
|
|
17561
17879
|
// src/lib/server/cli-server-module.ts
|
|
17562
17880
|
function createServerModule() {
|
|
@@ -17892,6 +18210,20 @@ async function initializeServices(options) {
|
|
|
17892
18210
|
sealed: true
|
|
17893
18211
|
}
|
|
17894
18212
|
]);
|
|
18213
|
+
services.set(
|
|
18214
|
+
cliCore.cliAccessMetadataGuards.map((guard) => ({
|
|
18215
|
+
provide: cliCore.ICliCommandGuard_TOKEN,
|
|
18216
|
+
useValue: guard,
|
|
18217
|
+
multi: true
|
|
18218
|
+
}))
|
|
18219
|
+
);
|
|
18220
|
+
services.set([
|
|
18221
|
+
{
|
|
18222
|
+
provide: cliCore.ICliPermissionRegistry_TOKEN,
|
|
18223
|
+
useValue: new CliPermissionRegistry(),
|
|
18224
|
+
sealed: true
|
|
18225
|
+
}
|
|
18226
|
+
]);
|
|
17895
18227
|
const translator = new CliTranslationService();
|
|
17896
18228
|
services.set([
|
|
17897
18229
|
{ provide: cliCore.ICliTranslationService_TOKEN, useValue: translator, sealed: true }
|
|
@@ -17963,7 +18295,7 @@ var CliEngine = class {
|
|
|
17963
18295
|
* Initialize the terminal, wire up services, boot modules, and show welcome message.
|
|
17964
18296
|
*/
|
|
17965
18297
|
async start() {
|
|
17966
|
-
initWasmAccelerator().catch(() => {
|
|
18298
|
+
initWasmAccelerator(this.options?.wasmUrl).catch(() => {
|
|
17967
18299
|
});
|
|
17968
18300
|
await waitForLayout(this.container);
|
|
17969
18301
|
const terminalOpts = getTerminalOptions(this.options?.terminalOptions);
|
|
@@ -18257,6 +18589,24 @@ var CliTestHarness = class {
|
|
|
18257
18589
|
provide: ICliEnvironment_TOKEN,
|
|
18258
18590
|
useValue: new CliEnvironment()
|
|
18259
18591
|
});
|
|
18592
|
+
this.services.set({
|
|
18593
|
+
provide: cliCore.ICliPermissionRegistry_TOKEN,
|
|
18594
|
+
useValue: new CliPermissionRegistry()
|
|
18595
|
+
});
|
|
18596
|
+
this.services.set(
|
|
18597
|
+
cliCore.cliAccessMetadataGuards.map((guard) => ({
|
|
18598
|
+
provide: cliCore.ICliCommandGuard_TOKEN,
|
|
18599
|
+
useValue: guard,
|
|
18600
|
+
multi: true
|
|
18601
|
+
}))
|
|
18602
|
+
);
|
|
18603
|
+
}
|
|
18604
|
+
/**
|
|
18605
|
+
* Resolve `context.userSession` on every command from this function
|
|
18606
|
+
* (e.g. a session service), instead of the value set via setUserSession().
|
|
18607
|
+
*/
|
|
18608
|
+
setUserSessionProvider(provider) {
|
|
18609
|
+
this.userSessionProvider = provider;
|
|
18260
18610
|
}
|
|
18261
18611
|
/**
|
|
18262
18612
|
* Register a service in the test container.
|
|
@@ -18341,7 +18691,7 @@ var CliTestHarness = class {
|
|
|
18341
18691
|
options: void 0,
|
|
18342
18692
|
logger: createNoopLogger(),
|
|
18343
18693
|
process,
|
|
18344
|
-
userSession: this.userSession,
|
|
18694
|
+
userSession: this.userSessionProvider ? this.userSessionProvider() : this.userSession,
|
|
18345
18695
|
state: this.stateStoreManager.getStateStore("shared"),
|
|
18346
18696
|
backgroundServices: createNoopBackgroundServices(),
|
|
18347
18697
|
http: new CliHttpClient(),
|
|
@@ -18635,6 +18985,7 @@ exports.CliOpenCommandProcessor = CliOpenCommandProcessor;
|
|
|
18635
18985
|
exports.CliPackageManagerService = CliPackageManagerService;
|
|
18636
18986
|
exports.CliPackageManagerService_TOKEN = CliPackageManagerService_TOKEN;
|
|
18637
18987
|
exports.CliPackagesCommandProcessor = CliPackagesCommandProcessor;
|
|
18988
|
+
exports.CliPermissionRegistry = CliPermissionRegistry;
|
|
18638
18989
|
exports.CliPingCommandProcessor = CliPingCommandProcessor;
|
|
18639
18990
|
exports.CliProcessRegistry = CliProcessRegistry;
|
|
18640
18991
|
exports.CliProcessRegistry_TOKEN = CliProcessRegistry_TOKEN;
|
|
@@ -18687,6 +19038,7 @@ exports.builtinProcessors = builtinProcessors;
|
|
|
18687
19038
|
exports.computeDiff = computeDiff;
|
|
18688
19039
|
exports.contextGlobalParameter = contextGlobalParameter;
|
|
18689
19040
|
exports.createServerModule = createServerModule;
|
|
19041
|
+
exports.describeShellClose = describeShellClose;
|
|
18690
19042
|
exports.executeOnServer = executeOnServer;
|
|
18691
19043
|
exports.expandEnvironmentVars = expandEnvironmentVars;
|
|
18692
19044
|
exports.formatDiff = formatDiff;
|