@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/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.13";
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(this.userAliases).forEach(
464
+ Object.entries(visible).forEach(
443
465
  ([alias, command2]) => {
444
466
  writer.writeInfo(` ${alias} -> ${command2}`);
445
467
  }
446
468
  );
447
- if (Object.keys(this.userAliases).length === 0) {
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.state.updateState({
500
- aliases: {
501
- ...this.userAliases,
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["aliases"]).subscribe((aliases) => {
510
- this.userAliases = aliases ?? {};
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 { aliases } = context2.state.getState();
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 baseUrl = this.normalizeUrl(this._config.url);
6015
- const wsUrl = cliCore.buildAuthenticatedWebSocketUrl(
6016
- this.toWebSocketUrl(baseUrl) + "/ws/v1/qcli/events",
6017
- () => this.resolveAuthHeaders()
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 baseUrl = this.normalizeUrl(this._config.url);
6068
- const wsUrl = cliCore.buildAuthenticatedWebSocketUrl(
6069
- this.toWebSocketUrl(baseUrl) + "/ws/v1/qcli/events",
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 [...this.registry.processors]) {
6545
- if (p.metadata?.module === "server:multi" || p.metadata?.requireServer && !p.command.includes(":")) {
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.metadata?.requireServer)
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 ? [wasmUrl, ...WASM_SEARCH_PATHS] : WASM_SEARCH_PATHS;
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["Content-Type"]) {
11490
- headers["Content-Type"] = "application/json";
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
- const errorBody = await response.json();
11516
- if (errorBody.error) {
11517
- errorMessage = errorBody.error;
11518
- } else if (errorBody.message) {
11519
- errorMessage = errorBody.message;
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 Error(errorMessage);
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 || !name ? "" : `\x1B[32m${name}\x1B[0m:`;
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
- context2.process.exit(-1);
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
- context2.process.exit(-1);
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
- async onData(data, context2) {
17285
- if (this.socket?.readyState === WebSocket.OPEN) {
17286
- const msg = { type: "stdin", data };
17287
- this.socket.send(JSON.stringify(msg));
17288
- }
17289
- }
17290
- onResize(cols, rows, _context) {
17291
- if (this.socket?.readyState === WebSocket.OPEN) {
17292
- const msg = { type: "resize", cols, rows };
17293
- this.socket.send(JSON.stringify(msg));
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
- this.socket = null;
17302
- }
17303
- this.context = null;
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
- this.context = context2;
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
- this.resolved = false;
17649
+ let fullScreen = false;
17389
17650
  return new Promise((resolve) => {
17651
+ const signal = context2.signal;
17390
17652
  const resolveOnce = () => {
17391
- if (!this.resolved) {
17392
- this.resolved = true;
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
- this.socket = new WebSocket(wsUrl);
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
- this.socket.onopen = () => {
17682
+ session.socket.onopen = () => {
17408
17683
  };
17409
- this.socket.onmessage = (event) => {
17410
- const msg = JSON.parse(event.data);
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
- this.socket.onclose = () => {
17724
+ let errored = false;
17725
+ session.socket.onclose = (event) => {
17448
17726
  context2.spinner?.hide();
17449
- if (this.context && !this.resolved) {
17450
- context2.exitFullScreenMode();
17451
- context2.writer.writeWarning(
17452
- `Connection to '${serverName}' closed.`
17453
- );
17454
- context2.process.exit(1);
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
- this.socket.onerror = () => {
17460
- context2.spinner?.hide();
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
- return new Promise((resolve) => {
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
- this.socket = new WebSocket(wsUrl);
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
- this.socket.onmessage = (event) => {
17490
- const msg = JSON.parse(event.data);
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
- this.socket.onclose = () => {
17808
+ let errored = false;
17809
+ session.socket.onclose = (event) => {
17520
17810
  context2.spinner?.hide();
17521
- this.cleanup();
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
- this.socket.onerror = () => {
17525
- context2.spinner?.hide();
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
- const baseUrl = connection.config.url.endsWith("/") ? connection.config.url.slice(0, -1) : connection.config.url;
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
- url += `&cmd=${encodeURIComponent(cmd)}`;
17832
+ path += `&cmd=${encodeURIComponent(cmd)}`;
17541
17833
  }
17542
- return cliCore.buildAuthenticatedWebSocketUrl(
17543
- url,
17544
- () => connection.resolveAuthHeaders()
17545
- );
17834
+ return connection.buildWebSocketUrl(path);
17546
17835
  }
17547
- cleanup() {
17548
- if (this.socket) {
17549
- this.socket.onmessage = null;
17550
- this.socket.onclose = null;
17551
- this.socket.onerror = null;
17552
- if (this.socket.readyState === WebSocket.OPEN || this.socket.readyState === WebSocket.CONNECTING) {
17553
- this.socket.close();
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
- this.socket = null;
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;