@qodalis/cli 2.0.0-beta.13 → 2.0.0-beta.15

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