@qodalis/cli 2.0.0-beta.12 → 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
@@ -409,10 +409,31 @@ var ICliPingServerService_TOKEN = "cli-ping-server-service";
409
409
  var CliModuleRegistry_TOKEN = "cli-module-registry";
410
410
  var CliBackgroundServiceRegistry_TOKEN = "cli-background-service-registry";
411
411
  var CliPackageManagerService_TOKEN = "cli-package-manager-service";
412
+ var CliKeyValueStore_TOKEN = "cli-key-value-store";
413
+ var SyntaxHighlighterRegistry_TOKEN = "syntax-highlighter-registry";
412
414
 
413
415
  // src/lib/version.ts
414
- var LIBRARY_VERSION = "2.0.0-beta.12";
416
+ var LIBRARY_VERSION = "2.0.0-beta.14";
415
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
+ }
416
437
  var CliAliasCommandProcessor = class {
417
438
  constructor() {
418
439
  this.command = "alias";
@@ -429,26 +450,33 @@ var CliAliasCommandProcessor = class {
429
450
  },
430
451
  storeName: "aliases"
431
452
  };
453
+ /** Shared aliases (used when no auth module is loaded) */
432
454
  this.userAliases = {};
455
+ this.aliasState = {};
433
456
  this.processors = [
434
457
  {
435
458
  command: "ls",
436
459
  description: "List all aliases",
437
460
  processCommand: async (_, context2) => {
438
461
  const { writer } = context2;
462
+ const visible = this.aliasesFor(context2);
439
463
  writer.writeln("Aliases:");
440
- Object.entries(this.userAliases).forEach(
464
+ Object.entries(visible).forEach(
441
465
  ([alias, command2]) => {
442
466
  writer.writeInfo(` ${alias} -> ${command2}`);
443
467
  }
444
468
  );
445
- if (Object.keys(this.userAliases).length === 0) {
469
+ if (Object.keys(visible).length === 0) {
446
470
  writer.writeInfo(" No aliases defined");
447
471
  }
448
472
  }
449
473
  }
450
474
  ];
451
475
  }
476
+ /** Aliases visible to the current principal */
477
+ aliasesFor(context2) {
478
+ return aliasesFor(this.aliasState, aliasOwner(context2));
479
+ }
452
480
  writeDescription(context2) {
453
481
  const { writer, translator: t } = context2;
454
482
  writer.writeln(t.t("cli.alias.long_description", "Create shortcut aliases for frequently used commands"));
@@ -474,7 +502,7 @@ var CliAliasCommandProcessor = class {
474
502
  );
475
503
  }
476
504
  async processCommand(command2, context2) {
477
- const registry = context2.services.get(
505
+ const registry = context2.services.getRequired(
478
506
  CliProcessorsRegistry_TOKEN
479
507
  );
480
508
  const { writer } = context2;
@@ -494,18 +522,20 @@ var CliAliasCommandProcessor = class {
494
522
  }
495
523
  context2.writer.writeInfo(`${alias} -> ${command2.args[alias]}`);
496
524
  }
497
- context2.state.updateState({
498
- aliases: {
499
- ...this.userAliases,
525
+ const owner = aliasOwner(context2);
526
+ context2.state.updateState(
527
+ withAliases(this.aliasState, owner, {
528
+ ...aliasesFor(this.aliasState, owner),
500
529
  ...command2.args
501
- }
502
- });
530
+ })
531
+ );
503
532
  await context2.state.persist();
504
533
  }
505
534
  async initialize(context2) {
506
535
  this.stateSubscription?.unsubscribe();
507
- this.stateSubscription = context2.state.select((x) => x["aliases"]).subscribe((aliases) => {
508
- this.userAliases = aliases ?? {};
536
+ this.stateSubscription = context2.state.select((x) => x).subscribe((state) => {
537
+ this.aliasState = state ?? {};
538
+ this.userAliases = this.aliasState.aliases ?? {};
509
539
  });
510
540
  }
511
541
  };
@@ -1333,7 +1363,9 @@ var CliEvalCommandProcessor = class {
1333
1363
  this.acceptsRawInput = true;
1334
1364
  this.metadata = {
1335
1365
  icon: "\u{1F9EE}",
1336
- module: "misc"
1366
+ module: "misc",
1367
+ // Enforced only when an auth module is loaded (eval also needs allowEval)
1368
+ requiredPermissions: ["system:eval"]
1337
1369
  };
1338
1370
  }
1339
1371
  async processCommand(command, context) {
@@ -2448,7 +2480,9 @@ var CliUnAliasCommandProcessor = class {
2448
2480
  async processCommand(command2, context2) {
2449
2481
  const { writer } = context2;
2450
2482
  const aliasToRemove = command2.value;
2451
- const { aliases } = context2.state.getState();
2483
+ const state = context2.state.getState();
2484
+ const owner = aliasOwner(context2);
2485
+ const aliases = aliasesFor(state, owner);
2452
2486
  if (!aliases[aliasToRemove]) {
2453
2487
  writer.writeError(`Alias ${aliasToRemove} not found`);
2454
2488
  context2.process.exit(-1);
@@ -2458,9 +2492,7 @@ var CliUnAliasCommandProcessor = class {
2458
2492
  Object.keys(aliases).filter((alias) => alias !== aliasToRemove).forEach((alias) => {
2459
2493
  updated[alias] = aliases[alias];
2460
2494
  });
2461
- context2.state.updateState({
2462
- aliases: updated
2463
- });
2495
+ context2.state.updateState(withAliases(state, owner, updated));
2464
2496
  await context2.state.persist();
2465
2497
  }
2466
2498
  };
@@ -2814,7 +2846,7 @@ var CliConfigureCommandProcessor = class _CliConfigureCommandProcessor {
2814
2846
  ];
2815
2847
  }
2816
2848
  async processCommand(command2, context2) {
2817
- const registry = context2.services.get(
2849
+ const registry = context2.services.getRequired(
2818
2850
  CliProcessorsRegistry_TOKEN
2819
2851
  );
2820
2852
  const pluginCategories = cliCore.resolveConfigurationCategories(registry);
@@ -2911,10 +2943,10 @@ var CliConfigureCommandProcessor = class _CliConfigureCommandProcessor {
2911
2943
  if (stateKey === "system") {
2912
2944
  const langOption = options.find((o) => o.key === "language");
2913
2945
  if (langOption) {
2914
- try {
2915
- const translator = context2.services.get(
2916
- cliCore.ICliTranslationService_TOKEN
2917
- );
2946
+ const translator = context2.services.get(
2947
+ cliCore.ICliTranslationService_TOKEN
2948
+ );
2949
+ if (translator) {
2918
2950
  const locales = translator.getAvailableLocales();
2919
2951
  if (locales.length > 0) {
2920
2952
  langOption.options = locales.map((l) => ({
@@ -2922,7 +2954,6 @@ var CliConfigureCommandProcessor = class _CliConfigureCommandProcessor {
2922
2954
  value: l
2923
2955
  }));
2924
2956
  }
2925
- } catch {
2926
2957
  }
2927
2958
  }
2928
2959
  }
@@ -3067,13 +3098,10 @@ var CliConfigureCommandProcessor = class _CliConfigureCommandProcessor {
3067
3098
  }
3068
3099
  }
3069
3100
  if (settings["language"]) {
3070
- try {
3071
- const translator = context2.services.get(
3072
- cliCore.ICliTranslationService_TOKEN
3073
- );
3074
- translator.setLocale(settings["language"]);
3075
- } catch {
3076
- }
3101
+ const translator = context2.services.get(
3102
+ cliCore.ICliTranslationService_TOKEN
3103
+ );
3104
+ translator?.setLocale(settings["language"]);
3077
3105
  }
3078
3106
  if (context2.terminal?.options) {
3079
3107
  if (settings["cursorBlink"] !== void 0) {
@@ -3103,7 +3131,7 @@ var CliConfigureCommandProcessor = class _CliConfigureCommandProcessor {
3103
3131
  aliases: ["ls"],
3104
3132
  description: "List all configuration options and their current values",
3105
3133
  processCommand: async (_, context2) => {
3106
- const registry = context2.services.get(
3134
+ const registry = context2.services.getRequired(
3107
3135
  CliProcessorsRegistry_TOKEN
3108
3136
  );
3109
3137
  const allOptions = this.getAllOptions(registry);
@@ -3165,7 +3193,7 @@ var CliConfigureCommandProcessor = class _CliConfigureCommandProcessor {
3165
3193
  }
3166
3194
  const category = path.substring(0, dotIndex);
3167
3195
  const key = path.substring(dotIndex + 1);
3168
- const registry = context2.services.get(
3196
+ const registry = context2.services.getRequired(
3169
3197
  CliProcessorsRegistry_TOKEN
3170
3198
  );
3171
3199
  const allOptions = this.getAllOptions(registry);
@@ -3217,7 +3245,7 @@ var CliConfigureCommandProcessor = class _CliConfigureCommandProcessor {
3217
3245
  }
3218
3246
  const category = path.substring(0, dotIndex);
3219
3247
  const key = path.substring(dotIndex + 1);
3220
- const registry = context2.services.get(
3248
+ const registry = context2.services.getRequired(
3221
3249
  CliProcessorsRegistry_TOKEN
3222
3250
  );
3223
3251
  const allOptions = this.getAllOptions(registry);
@@ -3277,7 +3305,7 @@ var CliConfigureCommandProcessor = class _CliConfigureCommandProcessor {
3277
3305
  t.t("cli.configure.system_reset", "System configuration reset to defaults")
3278
3306
  );
3279
3307
  } else {
3280
- const registry = context2.services.get(
3308
+ const registry = context2.services.getRequired(
3281
3309
  CliProcessorsRegistry_TOKEN
3282
3310
  );
3283
3311
  const categories2 = cliCore.resolveConfigurationCategories(registry);
@@ -3673,7 +3701,7 @@ var CliHelpCommandProcessor = class {
3673
3701
  async processCommand(command2, context2) {
3674
3702
  const { writer } = context2;
3675
3703
  const t = context2.translator;
3676
- const registry = context2.services.get(
3704
+ const registry = context2.services.getRequired(
3677
3705
  CliProcessorsRegistry_TOKEN
3678
3706
  );
3679
3707
  const [_, ...commandsToHelp] = command2.command.split(" ");
@@ -3773,24 +3801,23 @@ var CliHelpCommandProcessor = class {
3773
3801
  writeProcessorDescription(processor, context2) {
3774
3802
  const { writer } = context2;
3775
3803
  const t = context2.translator;
3776
- const icon = processor.metadata?.icon || "";
3777
- const name = `${BOLD}${writer.wrapInColor(processor.command, cliCore.CliForegroundColor.Cyan)}${RESET}`;
3778
3804
  const version = `${DIM}v${processor.version || "1.0.0"}${RESET}`;
3779
- writer.writeln();
3780
- writer.writeln(` ${icon}${icon ? " " : ""}${name} ${version}`);
3805
+ const headerLines = [];
3781
3806
  if (processor.description) {
3782
- const desc = this.translateDescription(
3783
- processor.description,
3784
- processor.command,
3785
- context2
3807
+ headerLines.push(
3808
+ this.translateDescription(processor.description, processor.command, context2)
3786
3809
  );
3787
- writer.writeln(` ${desc}`);
3788
3810
  }
3789
3811
  if (processor.aliases?.length) {
3790
- writer.writeln(
3791
- ` ${DIM}${t.t("cli.help.aliases", "Aliases:")}${RESET} ${processor.aliases.map((a) => writer.wrapInColor(a, cliCore.CliForegroundColor.Magenta)).join(", ")}`
3812
+ headerLines.push(
3813
+ `${DIM}${t.t("cli.help.aliases", "Aliases:")}${RESET} ${processor.aliases.map((a) => writer.wrapInColor(a, cliCore.CliForegroundColor.Magenta)).join(", ")}`
3792
3814
  );
3793
3815
  }
3816
+ writer.writeln();
3817
+ writer.writeBox(headerLines, {
3818
+ title: `${writer.wrapInColor(processor.command, cliCore.CliForegroundColor.Cyan)} ${version}`,
3819
+ borderColor: cliCore.CliForegroundColor.White
3820
+ });
3794
3821
  if (processor.originalProcessor) {
3795
3822
  writer.writeln();
3796
3823
  writer.writeln(
@@ -3956,20 +3983,13 @@ var CliVersionCommandProcessor = class {
3956
3983
  writer.writeln(
3957
3984
  writer.wrapInColor(getCliNameArt(context2.terminal.cols), cliCore.CliForegroundColor.Cyan)
3958
3985
  );
3959
- writer.writeln(
3960
- ` ${DIM2}CLI${RESET2} ${writer.wrapInColor(`v${LIBRARY_VERSION}`, cliCore.CliForegroundColor.Green)} ${DIM2}Core${RESET2} ${writer.wrapInColor(`v${cliCore.LIBRARY_VERSION}`, cliCore.CliForegroundColor.Green)}`
3961
- );
3962
- let framework = "vanilla";
3963
- try {
3964
- framework = context2.services.get("cli-framework");
3965
- } catch {
3966
- }
3967
- writer.writeln(
3968
- ` ${DIM2}Framework${RESET2} ${writer.wrapInColor(framework, cliCore.CliForegroundColor.Cyan)}`
3969
- );
3970
- writer.writeln(
3971
- ` ${DIM2}Docs${RESET2} ${writer.wrapInColor("https://cli.qodalis.com/docs/", cliCore.CliForegroundColor.Blue)}`
3972
- );
3986
+ const framework = context2.services.get("cli-framework") ?? "vanilla";
3987
+ writer.writeBox([
3988
+ `${DIM2}CLI${RESET2} ${writer.wrapInColor(`v${LIBRARY_VERSION}`, cliCore.CliForegroundColor.Green)}`,
3989
+ `${DIM2}Core${RESET2} ${writer.wrapInColor(`v${cliCore.LIBRARY_VERSION}`, cliCore.CliForegroundColor.Green)}`,
3990
+ `${DIM2}Framework${RESET2} ${writer.wrapInColor(framework, cliCore.CliForegroundColor.Cyan)}`,
3991
+ `${DIM2}Docs${RESET2} ${writer.wrapInColor("https://cli.qodalis.com/docs/", cliCore.CliForegroundColor.Blue)}`
3992
+ ], { borderColor: cliCore.CliForegroundColor.White });
3973
3993
  writer.writeln();
3974
3994
  }
3975
3995
  writeDescription(context2) {
@@ -4027,11 +4047,7 @@ function collectSystemInfo(context2) {
4027
4047
  const lines = [];
4028
4048
  lines.push(`- **CLI Version:** ${LIBRARY_VERSION}`);
4029
4049
  lines.push(`- **Core Version:** ${cliCore.LIBRARY_VERSION}`);
4030
- let framework = "vanilla";
4031
- try {
4032
- framework = context2.services.get("cli-framework");
4033
- } catch {
4034
- }
4050
+ const framework = context2.services.get("cli-framework") ?? "vanilla";
4035
4051
  lines.push(`- **Framework:** ${framework}`);
4036
4052
  lines.push(`- **Terminal:** ${context2.terminal.cols}x${context2.terminal.rows}`);
4037
4053
  if (typeof navigator !== "undefined") {
@@ -4103,12 +4119,8 @@ async function runInteractiveFlow(type, initialTitle, context2) {
4103
4119
  `${writer.wrapInColor("\u2500\u2500 System Info (auto-attached) \u2500\u2500", cliCore.CliForegroundColor.Cyan)}`
4104
4120
  );
4105
4121
  writer.writeln(` CLI: ${LIBRARY_VERSION} Core: ${cliCore.LIBRARY_VERSION}`);
4106
- let framework = "vanilla";
4107
- try {
4108
- framework = context2.services.get("cli-framework");
4109
- } catch {
4110
- }
4111
- writer.writeln(` Framework: ${framework} Terminal: ${context2.terminal.cols}x${context2.terminal.rows}`);
4122
+ const previewFramework = context2.services.get("cli-framework") ?? "vanilla";
4123
+ writer.writeln(` Framework: ${previewFramework} Terminal: ${context2.terminal.cols}x${context2.terminal.rows}`);
4112
4124
  if (typeof navigator !== "undefined") {
4113
4125
  writer.writeln(` Platform: ${navigator.platform} Language: ${navigator.language}`);
4114
4126
  }
@@ -4250,7 +4262,7 @@ var CliHistoryCommandProcessor = class {
4250
4262
  command: "clear",
4251
4263
  description: "Clears the command history",
4252
4264
  processCommand: async (_, context2) => {
4253
- const commandHistory = context2.services.get(
4265
+ const commandHistory = context2.services.getRequired(
4254
4266
  CliCommandHistory_TOKEN
4255
4267
  );
4256
4268
  await commandHistory.clearHistory();
@@ -4273,7 +4285,7 @@ var CliHistoryCommandProcessor = class {
4273
4285
  description: "Search command history for a pattern",
4274
4286
  parameters: searchParameters,
4275
4287
  processCommand: async (cmd, context2) => {
4276
- const commandHistory = context2.services.get(
4288
+ const commandHistory = context2.services.getRequired(
4277
4289
  CliCommandHistory_TOKEN
4278
4290
  );
4279
4291
  const query = (cmd.value ?? "").trim();
@@ -4327,7 +4339,7 @@ var CliHistoryCommandProcessor = class {
4327
4339
  }
4328
4340
  async processCommand(_, context2) {
4329
4341
  const { writer } = context2;
4330
- const commandHistory = context2.services.get(
4342
+ const commandHistory = context2.services.getRequired(
4331
4343
  CliCommandHistory_TOKEN
4332
4344
  );
4333
4345
  const history = commandHistory.getHistory();
@@ -4716,7 +4728,9 @@ var CliPackagesCommandProcessor = class {
4716
4728
  this.metadata = {
4717
4729
  sealed: true,
4718
4730
  icon: "\u{1F4E6}",
4719
- module: "system"
4731
+ module: "system",
4732
+ // Enforced only when an auth module is loaded
4733
+ requiredPermissions: ["system:packages"]
4720
4734
  };
4721
4735
  this.stateConfiguration = {
4722
4736
  initialState: {
@@ -4774,7 +4788,7 @@ var CliPackagesCommandProcessor = class {
4774
4788
  const { progressBar, writer } = context2;
4775
4789
  const { signal, cleanup } = scope.createAbortSignal(context2);
4776
4790
  progressBar.show();
4777
- context2.setStatusText("Browsing packages");
4791
+ context2.notifier.info("Browsing packages");
4778
4792
  const prefix = packagesManager.QODALIS_COMMAND_PREFIX;
4779
4793
  const knownShortNames = [
4780
4794
  "browser-storage",
@@ -4967,7 +4981,7 @@ var CliPackagesCommandProcessor = class {
4967
4981
  const packages = command2.value.split(" ");
4968
4982
  for (const pkg of packages) {
4969
4983
  if (signal.aborted) break;
4970
- context2.setStatusText(`Installing ${pkg}`);
4984
+ context2.notifier.info(`Installing ${pkg}`);
4971
4985
  await scope.addPackage(pkg, context2, signal);
4972
4986
  }
4973
4987
  cleanup();
@@ -5008,7 +5022,7 @@ var CliPackagesCommandProcessor = class {
5008
5022
  const packages = command2.value.split(" ");
5009
5023
  for (const pkg of packages) {
5010
5024
  if (signal.aborted) break;
5011
- context2.setStatusText(`Removing ${pkg}`);
5025
+ context2.notifier.info(`Removing ${pkg}`);
5012
5026
  await scope.removePackage(pkg, context2);
5013
5027
  }
5014
5028
  cleanup();
@@ -5044,7 +5058,7 @@ var CliPackagesCommandProcessor = class {
5044
5058
  const fullName = name.startsWith(prefix) ? name : `${prefix}${name}`;
5045
5059
  progressBar.show();
5046
5060
  progressBar.setText(`Fetching versions for ${name}`);
5047
- context2.setStatusText(`Fetching versions for ${name}`);
5061
+ context2.notifier.info(`Fetching versions for ${name}`);
5048
5062
  try {
5049
5063
  const response = await scriptsLoader.getScript(
5050
5064
  `https://registry.npmjs.org/${encodeURIComponent(fullName)}`,
@@ -5508,11 +5522,11 @@ var CliPackagesCommandProcessor = class {
5508
5522
  );
5509
5523
  }
5510
5524
  async initialize(context2) {
5511
- this.registry = context2.services.get(
5525
+ this.registry = context2.services.getRequired(
5512
5526
  CliProcessorsRegistry_TOKEN
5513
5527
  );
5514
- const store = context2.services.get(
5515
- "cli-key-value-store"
5528
+ const store = context2.services.getRequired(
5529
+ CliKeyValueStore_TOKEN
5516
5530
  );
5517
5531
  this.packagesManager.setStore(store);
5518
5532
  context2.services.set([{
@@ -5778,10 +5792,11 @@ var CliPackagesCommandProcessor = class {
5778
5792
  }
5779
5793
  };
5780
5794
  var CliServerConnection = class _CliServerConnection {
5781
- constructor(_config, _backgroundServices, _logger) {
5795
+ constructor(_config, _backgroundServices, _logger, _authService) {
5782
5796
  this._config = _config;
5783
5797
  this._backgroundServices = _backgroundServices;
5784
5798
  this._logger = _logger;
5799
+ this._authService = _authService;
5785
5800
  this._connected = false;
5786
5801
  this._commands = [];
5787
5802
  this._capabilities = null;
@@ -5790,6 +5805,8 @@ var CliServerConnection = class _CliServerConnection {
5790
5805
  this._basePath = "";
5791
5806
  this._wsReconnectAttempts = 0;
5792
5807
  this._wsReconnectTimer = null;
5808
+ /** True once this connection registered its `server-events:<name>` background service */
5809
+ this._eventServiceRegistered = false;
5793
5810
  this._healthCheckTimer = null;
5794
5811
  this._healthCheckAttempts = 0;
5795
5812
  }
@@ -5820,10 +5837,33 @@ var CliServerConnection = class _CliServerConnection {
5820
5837
  get apiVersion() {
5821
5838
  return this._apiVersion;
5822
5839
  }
5840
+ /**
5841
+ * Resolve auth headers for this server connection.
5842
+ * Uses the auth service (which merges config headers with registered
5843
+ * ICliServerAuthTokenProviders), or falls back to config headers directly.
5844
+ */
5845
+ resolveAuthHeaders() {
5846
+ if (this._authService) {
5847
+ return this._authService.getHeaders(this._config.name, this._config.headers);
5848
+ }
5849
+ return cliCore.resolveHeaders(this._config.headers);
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
+ }
5823
5863
  async connect() {
5824
5864
  try {
5825
5865
  const baseUrl = this.normalizeUrl(this._config.url);
5826
- const negotiated = await cliCore.ServerVersionNegotiator.discover(baseUrl);
5866
+ const negotiated = await cliCore.ServerVersionNegotiator.discover(baseUrl, () => this.resolveAuthHeaders());
5827
5867
  if (negotiated) {
5828
5868
  this._apiVersion = negotiated.apiVersion;
5829
5869
  this._basePath = negotiated.basePath;
@@ -6011,15 +6051,25 @@ var CliServerConnection = class _CliServerConnection {
6011
6051
  return;
6012
6052
  }
6013
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
+ }
6014
6063
  try {
6015
6064
  this._backgroundServices.register({
6016
6065
  name: serviceName,
6017
6066
  description: `Event stream for server '${this._config.name}'`,
6018
6067
  type: "daemon",
6019
6068
  onStart: async (ctx) => {
6020
- const baseUrl = this.normalizeUrl(this._config.url);
6021
- const wsUrl = this.toWebSocketUrl(baseUrl) + "/ws/v1/qcli/events";
6022
- 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();
6023
6073
  this._eventSocket = new WebSocket(wsUrl);
6024
6074
  this._eventSocket.onopen = () => {
6025
6075
  this._wsReconnectAttempts = 0;
@@ -6042,7 +6092,11 @@ var CliServerConnection = class _CliServerConnection {
6042
6092
  } catch {
6043
6093
  }
6044
6094
  };
6045
- 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
+ }
6046
6100
  if (this._connected) {
6047
6101
  ctx.log("WebSocket closed unexpectedly, scheduling reconnect", "warn");
6048
6102
  this.scheduleWsReconnect();
@@ -6056,6 +6110,7 @@ var CliServerConnection = class _CliServerConnection {
6056
6110
  this.closeEventSocketDirect();
6057
6111
  }
6058
6112
  });
6113
+ this._eventServiceRegistered = true;
6059
6114
  this._backgroundServices.start(serviceName).catch((e) => {
6060
6115
  this._logger?.debug(`Background event service failed for "${this._config.name}", falling back to direct: ${e}`);
6061
6116
  this.connectEventSocketDirect();
@@ -6066,9 +6121,13 @@ var CliServerConnection = class _CliServerConnection {
6066
6121
  }
6067
6122
  }
6068
6123
  connectEventSocketDirect() {
6124
+ void this.connectEventSocketDirectAsync();
6125
+ }
6126
+ async connectEventSocketDirectAsync() {
6069
6127
  try {
6070
- const baseUrl = this.normalizeUrl(this._config.url);
6071
- const wsUrl = this.toWebSocketUrl(baseUrl) + "/ws/v1/qcli/events";
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 = [];
@@ -6171,7 +6241,7 @@ var CliServerConnection = class _CliServerConnection {
6171
6241
  const controller = new AbortController();
6172
6242
  const timer = setTimeout(() => controller.abort(), timeout);
6173
6243
  const headers = {
6174
- ...this._config.headers ?? {},
6244
+ ...this.resolveAuthHeaders(),
6175
6245
  ...init?.headers ?? {}
6176
6246
  };
6177
6247
  return fetch(url, {
@@ -6252,7 +6322,7 @@ async function executeOnServer(connection, serverName, descriptor, command2, con
6252
6322
  chainCommands: commandPath && commandPath.length > 1 ? commandPath.slice(1) : []
6253
6323
  };
6254
6324
  const cmdLabel = commandPath && commandPath.length > 0 ? commandPath.join(" ") : descriptor.command;
6255
- context2.setStatusText(`executing command: ${cmdLabel} on server ${serverName}`);
6325
+ context2.notifier.info(`executing command: ${cmdLabel} on server ${serverName}`);
6256
6326
  try {
6257
6327
  if (connection.capabilities?.streaming) {
6258
6328
  const result = await connection.executeStream(
@@ -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;
@@ -6440,14 +6512,26 @@ var CliServerMultiProxyProcessor = class _CliServerMultiProxyProcessor {
6440
6512
  // src/lib/server/cli-server-manager.ts
6441
6513
  var CliServerManager_TOKEN = "cli-server-manager";
6442
6514
  var CliServerManager = class {
6443
- constructor(registry) {
6515
+ constructor(registry, authService) {
6444
6516
  this.registry = registry;
6517
+ this.authService = authService;
6445
6518
  this.connections = /* @__PURE__ */ new Map();
6446
6519
  this._defaultServer = null;
6520
+ /** Bare-name aliases this manager registered (only these are ever removed) */
6521
+ this._bareAliases = /* @__PURE__ */ new Set();
6447
6522
  }
6448
6523
  get defaultServer() {
6449
6524
  return this._defaultServer;
6450
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
+ }
6451
6535
  setDefaultServer(name) {
6452
6536
  if (name !== null && !this.connections.has(name)) {
6453
6537
  throw new Error(`Unknown server: ${name}`);
@@ -6462,7 +6546,7 @@ var CliServerManager = class {
6462
6546
  this._backgroundServices = backgroundServices;
6463
6547
  for (const config of servers) {
6464
6548
  if (config.enabled === false) continue;
6465
- const connection = new CliServerConnection(config, backgroundServices, cliLogger);
6549
+ const connection = new CliServerConnection(config, backgroundServices, cliLogger, this.authService);
6466
6550
  this.connections.set(config.name, connection);
6467
6551
  connection.onDisconnect = () => {
6468
6552
  this.handleDisconnect(config.name);
@@ -6510,6 +6594,7 @@ var CliServerManager = class {
6510
6594
  await connection.connect();
6511
6595
  if (connection.connected) {
6512
6596
  this.registerProxyProcessors(connection, name);
6597
+ this.unregisterBareAliases();
6513
6598
  this.registerBareAliases();
6514
6599
  return { success: true, commandCount: connection.commands.length };
6515
6600
  }
@@ -6540,11 +6625,12 @@ var CliServerManager = class {
6540
6625
  this.registerBareAliases();
6541
6626
  }
6542
6627
  unregisterBareAliases() {
6543
- for (const p of [...this.registry.processors]) {
6544
- 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)) {
6545
6630
  this.registry.unregisterProcessor(p);
6546
6631
  }
6547
6632
  }
6633
+ this._bareAliases.clear();
6548
6634
  }
6549
6635
  registerProxyProcessors(connection, serverName) {
6550
6636
  for (const descriptor of connection.commands) {
@@ -6568,7 +6654,7 @@ var CliServerManager = class {
6568
6654
  }
6569
6655
  for (const [command2, servers] of commandCounts) {
6570
6656
  const existingProcessor = this.registry.findProcessor(command2, []);
6571
- if (existingProcessor && !existingProcessor.metadata?.requireServer)
6657
+ if (existingProcessor && !this._bareAliases.has(existingProcessor))
6572
6658
  continue;
6573
6659
  if (servers.length === 1) {
6574
6660
  const serverName = servers[0];
@@ -6590,6 +6676,7 @@ var CliServerManager = class {
6590
6676
  alias.command = command2;
6591
6677
  alias.aliases = [namespacedCommand];
6592
6678
  this.registry.registerProcessor(alias);
6679
+ this._bareAliases.add(alias);
6593
6680
  } else {
6594
6681
  const entries = servers.map((serverName) => {
6595
6682
  const connection = this.connections.get(serverName);
@@ -6600,6 +6687,7 @@ var CliServerManager = class {
6600
6687
  });
6601
6688
  const multi = new CliServerMultiProxyProcessor(entries, this);
6602
6689
  this.registry.registerProcessor(multi);
6690
+ this._bareAliases.add(multi);
6603
6691
  }
6604
6692
  }
6605
6693
  }
@@ -6745,6 +6833,28 @@ var WASM_SEARCH_PATHS = [
6745
6833
  "./assets/wasm/qodalis_cli_wasm_bg.wasm",
6746
6834
  "/wasm/qodalis_cli_wasm_bg.wasm"
6747
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
+ }
6748
6858
  var wasm;
6749
6859
  var cachedDataViewMemory0 = null;
6750
6860
  var cachedInt32ArrayMemory0 = null;
@@ -6973,7 +7083,7 @@ async function initWasmAccelerator(wasmUrl) {
6973
7083
  return accelerator;
6974
7084
  }
6975
7085
  try {
6976
- const paths = wasmUrl ? [wasmUrl, ...WASM_SEARCH_PATHS] : WASM_SEARCH_PATHS;
7086
+ const paths = wasmCandidates(wasmUrl);
6977
7087
  const bytes = await fetchWasmBinary(paths);
6978
7088
  await instantiateWasm(bytes);
6979
7089
  accelerator = createWasmAccelerator();
@@ -7013,7 +7123,7 @@ var CliDebugCommandProcessor = class {
7013
7123
  metadata: { icon: cliCore.CliIcon.Extension, module: "system" },
7014
7124
  processCommand: async (_, context2) => {
7015
7125
  const { writer } = context2;
7016
- const registry = context2.services.get(
7126
+ const registry = context2.services.getRequired(
7017
7127
  CliProcessorsRegistry_TOKEN
7018
7128
  );
7019
7129
  writer.writeln(
@@ -7064,12 +7174,10 @@ var CliDebugCommandProcessor = class {
7064
7174
  metadata: { icon: cliCore.CliIcon.Module, module: "system" },
7065
7175
  processCommand: async (_, context2) => {
7066
7176
  const { writer } = context2;
7067
- let moduleRegistry;
7068
- try {
7069
- moduleRegistry = context2.services.get(
7070
- CliModuleRegistry_TOKEN
7071
- );
7072
- } catch {
7177
+ const moduleRegistry = context2.services.get(
7178
+ CliModuleRegistry_TOKEN
7179
+ );
7180
+ if (!moduleRegistry) {
7073
7181
  writer.writeError("Module registry not available.");
7074
7182
  return;
7075
7183
  }
@@ -7110,7 +7218,7 @@ var CliDebugCommandProcessor = class {
7110
7218
  metadata: { icon: cliCore.CliIcon.Database, module: "system" },
7111
7219
  processCommand: async (_, context2) => {
7112
7220
  const { writer } = context2;
7113
- const storeManager = context2.services.get(
7221
+ const storeManager = context2.services.getRequired(
7114
7222
  CliStateStoreManager_TOKEN
7115
7223
  );
7116
7224
  const entries = storeManager.getStoreEntries();
@@ -7189,11 +7297,7 @@ var CliDebugCommandProcessor = class {
7189
7297
  writer.writeln(
7190
7298
  ` ${writer.wrapInColor("CLI Version:", cliCore.CliForegroundColor.Cyan)} ${LIBRARY_VERSION}`
7191
7299
  );
7192
- let framework = "vanilla";
7193
- try {
7194
- framework = context2.services.get("cli-framework");
7195
- } catch {
7196
- }
7300
+ const framework = context2.services.get("cli-framework") ?? "vanilla";
7197
7301
  writer.writeln(
7198
7302
  ` ${writer.wrapInColor("Framework:", cliCore.CliForegroundColor.Cyan)} ${framework}`
7199
7303
  );
@@ -7234,7 +7338,7 @@ var CliDebugCommandProcessor = class {
7234
7338
  metadata: { icon: cliCore.CliIcon.Logs, module: "system" },
7235
7339
  processCommand: async (_, context2) => {
7236
7340
  const { writer } = context2;
7237
- const history = context2.services.get(
7341
+ const history = context2.services.getRequired(
7238
7342
  CliCommandHistory_TOKEN
7239
7343
  );
7240
7344
  const commands = history.getHistory();
@@ -7323,14 +7427,14 @@ var CliDebugCommandProcessor = class {
7323
7427
  writer.writeln(
7324
7428
  ` ${online ? cliCore.CliIcon.CheckIcon : cliCore.CliIcon.CrossIcon} Network: ${online ? "online" : "offline"}`
7325
7429
  );
7326
- const registry = context2.services.get(
7430
+ const registry = context2.services.getRequired(
7327
7431
  CliProcessorsRegistry_TOKEN
7328
7432
  );
7329
7433
  writer.writeln(
7330
7434
  ` ${cliCore.CliIcon.CheckIcon} Processors: ${registry.processors.length} loaded`
7331
7435
  );
7332
7436
  try {
7333
- const moduleRegistry = context2.services.get(
7437
+ const moduleRegistry = context2.services.getRequired(
7334
7438
  CliModuleRegistry_TOKEN
7335
7439
  );
7336
7440
  const modules = moduleRegistry.getAll();
@@ -7346,7 +7450,7 @@ var CliDebugCommandProcessor = class {
7346
7450
  writer.writeln(
7347
7451
  ` ${wasmActive ? cliCore.CliIcon.CheckIcon : cliCore.CliIcon.WarningIcon} WASM Accelerator: ${wasmActive ? "loaded" : "using JS fallback"}`
7348
7452
  );
7349
- const historyService = context2.services.get(
7453
+ const historyService = context2.services.getRequired(
7350
7454
  CliCommandHistory_TOKEN
7351
7455
  );
7352
7456
  writer.writeln(
@@ -7360,13 +7464,13 @@ var CliDebugCommandProcessor = class {
7360
7464
  metadata: { icon: cliCore.CliIcon.Save, module: "system" },
7361
7465
  processCommand: async (_, context2) => {
7362
7466
  const { writer } = context2;
7363
- const registry = context2.services.get(
7467
+ const registry = context2.services.getRequired(
7364
7468
  CliProcessorsRegistry_TOKEN
7365
7469
  );
7366
- const history = context2.services.get(
7470
+ const history = context2.services.getRequired(
7367
7471
  CliCommandHistory_TOKEN
7368
7472
  );
7369
- const storeManager = context2.services.get(
7473
+ const storeManager = context2.services.getRequired(
7370
7474
  CliStateStoreManager_TOKEN
7371
7475
  );
7372
7476
  const report = {
@@ -7399,7 +7503,7 @@ var CliDebugCommandProcessor = class {
7399
7503
  state: storeManager.getStoreEntries()
7400
7504
  };
7401
7505
  try {
7402
- const moduleRegistry = context2.services.get(
7506
+ const moduleRegistry = context2.services.getRequired(
7403
7507
  CliModuleRegistry_TOKEN
7404
7508
  );
7405
7509
  report["modules"] = moduleRegistry.getAll().map((m) => ({
@@ -7417,7 +7521,7 @@ var CliDebugCommandProcessor = class {
7417
7521
  report["services"] = container.getRegisteredTokens();
7418
7522
  }
7419
7523
  try {
7420
- const serverManager = context2.services.get(
7524
+ const serverManager = context2.services.getRequired(
7421
7525
  CliServerManager_TOKEN
7422
7526
  );
7423
7527
  if (serverManager && serverManager.connections.size > 0) {
@@ -7441,13 +7545,9 @@ var CliDebugCommandProcessor = class {
7441
7545
  metadata: { icon: cliCore.CliIcon.Server, module: "system" },
7442
7546
  processCommand: async (_, context2) => {
7443
7547
  const { writer } = context2;
7444
- let manager;
7445
- try {
7446
- manager = context2.services.get(
7447
- CliServerManager_TOKEN
7448
- );
7449
- } catch {
7450
- }
7548
+ const manager = context2.services.get(
7549
+ CliServerManager_TOKEN
7550
+ );
7451
7551
  if (!manager || manager.connections.size === 0) {
7452
7552
  writer.writeInfo("No servers configured.");
7453
7553
  return;
@@ -7487,70 +7587,48 @@ var CliDebugCommandProcessor = class {
7487
7587
  }
7488
7588
  async processCommand(_, context2) {
7489
7589
  const { writer } = context2;
7490
- const registry = context2.services.get(
7590
+ const registry = context2.services.getRequired(
7491
7591
  CliProcessorsRegistry_TOKEN
7492
7592
  );
7493
- writer.writeln(
7494
- writer.wrapInColor(
7495
- `${cliCore.CliIcon.Bug} CLI Debug Info`,
7496
- cliCore.CliForegroundColor.Yellow
7497
- )
7498
- );
7499
- writer.writeDivider({ char: "=" });
7500
- writer.writeln(
7501
- ` ${writer.wrapInColor("Core Version:", cliCore.CliForegroundColor.Cyan)} ${cliCore.LIBRARY_VERSION}`
7502
- );
7503
- writer.writeln(
7504
- ` ${writer.wrapInColor("CLI Version:", cliCore.CliForegroundColor.Cyan)} ${LIBRARY_VERSION}`
7593
+ const summaryFramework = context2.services.get("cli-framework") ?? "vanilla";
7594
+ const summaryModuleRegistry = context2.services.get(
7595
+ CliModuleRegistry_TOKEN
7505
7596
  );
7506
- let framework = "vanilla";
7507
- try {
7508
- framework = context2.services.get("cli-framework");
7509
- } catch {
7510
- }
7511
- writer.writeln(
7512
- ` ${writer.wrapInColor("Framework:", cliCore.CliForegroundColor.Cyan)} ${framework}`
7513
- );
7514
- writer.writeln(
7515
- ` ${writer.wrapInColor("Terminal:", cliCore.CliForegroundColor.Cyan)} ${context2.terminal.cols}x${context2.terminal.rows}`
7597
+ const summaryServerManager = context2.services.get(
7598
+ CliServerManager_TOKEN
7516
7599
  );
7517
- writer.writeln(
7518
- ` ${writer.wrapInColor("Processors:", cliCore.CliForegroundColor.Cyan)} ${registry.processors.length}`
7600
+ const history = context2.services.getRequired(
7601
+ CliCommandHistory_TOKEN
7519
7602
  );
7520
- try {
7521
- const moduleRegistry = context2.services.get(
7522
- CliModuleRegistry_TOKEN
7523
- );
7524
- writer.writeln(
7525
- ` ${writer.wrapInColor("Modules:", cliCore.CliForegroundColor.Cyan)} ${moduleRegistry.getAll().length}`
7603
+ const wasmStatus = isWasmAccelerated() ? "active" : "inactive (JS fallback)";
7604
+ const lines = [
7605
+ `${writer.wrapInColor("Core Version:", cliCore.CliForegroundColor.Cyan)} ${cliCore.LIBRARY_VERSION}`,
7606
+ `${writer.wrapInColor("CLI Version:", cliCore.CliForegroundColor.Cyan)} ${LIBRARY_VERSION}`,
7607
+ `${writer.wrapInColor("Framework:", cliCore.CliForegroundColor.Cyan)} ${summaryFramework}`,
7608
+ `${writer.wrapInColor("Terminal:", cliCore.CliForegroundColor.Cyan)} ${context2.terminal.cols}x${context2.terminal.rows}`,
7609
+ `${writer.wrapInColor("Processors:", cliCore.CliForegroundColor.Cyan)} ${registry.processors.length}`
7610
+ ];
7611
+ if (summaryModuleRegistry) {
7612
+ lines.push(
7613
+ `${writer.wrapInColor("Modules:", cliCore.CliForegroundColor.Cyan)} ${summaryModuleRegistry.getAll().length}`
7526
7614
  );
7527
- } catch {
7528
7615
  }
7529
- try {
7530
- const serverManager = context2.services.get(
7531
- CliServerManager_TOKEN
7616
+ if (summaryServerManager && summaryServerManager.connections.size > 0) {
7617
+ const connected = Array.from(
7618
+ summaryServerManager.connections.values()
7619
+ ).filter((c) => c.connected).length;
7620
+ lines.push(
7621
+ `${writer.wrapInColor("Servers:", cliCore.CliForegroundColor.Cyan)} ${connected}/${summaryServerManager.connections.size} connected`
7532
7622
  );
7533
- if (serverManager && serverManager.connections.size > 0) {
7534
- const connected = Array.from(
7535
- serverManager.connections.values()
7536
- ).filter((c) => c.connected).length;
7537
- writer.writeln(
7538
- ` ${writer.wrapInColor("Servers:", cliCore.CliForegroundColor.Cyan)} ${connected}/${serverManager.connections.size} connected`
7539
- );
7540
- }
7541
- } catch {
7542
7623
  }
7543
- const history = context2.services.get(
7544
- CliCommandHistory_TOKEN
7624
+ lines.push(
7625
+ `${writer.wrapInColor("History:", cliCore.CliForegroundColor.Cyan)} ${history.getHistory().length} commands`,
7626
+ `${writer.wrapInColor("WASM Accelerator:", cliCore.CliForegroundColor.Cyan)} ${wasmStatus}`
7545
7627
  );
7546
- writer.writeln(
7547
- ` ${writer.wrapInColor("History:", cliCore.CliForegroundColor.Cyan)} ${history.getHistory().length} commands`
7548
- );
7549
- const wasmStatus = isWasmAccelerated() ? "active" : "inactive (JS fallback)";
7550
- writer.writeln(
7551
- ` ${writer.wrapInColor("WASM Accelerator:", cliCore.CliForegroundColor.Cyan)} ${wasmStatus}`
7552
- );
7553
- writer.writeDivider({ char: "=" });
7628
+ writer.writeBox(lines, {
7629
+ title: "CLI Debug Info",
7630
+ borderColor: cliCore.CliForegroundColor.Yellow
7631
+ });
7554
7632
  writer.writeln();
7555
7633
  writer.writeln(`${cliCore.CliIcon.Light} Subcommands:`);
7556
7634
  for (const sub of this.processors || []) {
@@ -8136,7 +8214,7 @@ var CliNanoCommandProcessor = class {
8136
8214
  this.replaceWith = "";
8137
8215
  this.statusMessage = void 0;
8138
8216
  try {
8139
- this.fs = context2.services.get(FS_TOKEN);
8217
+ this.fs = context2.services.getRequired(FS_TOKEN);
8140
8218
  } catch {
8141
8219
  this.fs = null;
8142
8220
  }
@@ -8475,8 +8553,8 @@ var CliNanoCommandProcessor = class {
8475
8553
  return;
8476
8554
  }
8477
8555
  try {
8478
- const registry = this.context.services.get(
8479
- "syntax-highlighter-registry"
8556
+ const registry = this.context.services.getRequired(
8557
+ SyntaxHighlighterRegistry_TOKEN
8480
8558
  );
8481
8559
  const ext = "." + filePath.split(".").pop()?.toLowerCase();
8482
8560
  const highlighter = registry.getByExtension(ext);
@@ -8741,7 +8819,9 @@ var CliServicesCommandProcessor = class {
8741
8819
  this.metadata = {
8742
8820
  sealed: true,
8743
8821
  icon: "\u2699",
8744
- module: "system"
8822
+ module: "system",
8823
+ // Enforced only when an auth module is loaded
8824
+ requiredPermissions: ["system:services"]
8745
8825
  };
8746
8826
  this.processors = [
8747
8827
  // ── list ──────────────────────────────────────────────
@@ -9113,7 +9193,7 @@ var CliPsCommandProcessor = class {
9113
9193
  async processCommand(_command, context2) {
9114
9194
  let registry;
9115
9195
  try {
9116
- registry = context2.services.get(
9196
+ registry = context2.services.getRequired(
9117
9197
  CliProcessRegistry_TOKEN
9118
9198
  );
9119
9199
  } catch {
@@ -9152,7 +9232,7 @@ var CliKillCommandProcessor = class {
9152
9232
  async processCommand(command2, context2) {
9153
9233
  let registry;
9154
9234
  try {
9155
- registry = context2.services.get(
9235
+ registry = context2.services.getRequired(
9156
9236
  CliProcessRegistry_TOKEN
9157
9237
  );
9158
9238
  } catch {
@@ -9267,11 +9347,10 @@ var CliHtopCommandProcessor = class {
9267
9347
  this.errorTimeout = null;
9268
9348
  }
9269
9349
  async processCommand(_command, context2) {
9270
- try {
9271
- this.registry = context2.services.get(
9272
- CliProcessRegistry_TOKEN
9273
- );
9274
- } catch {
9350
+ this.registry = context2.services.get(
9351
+ CliProcessRegistry_TOKEN
9352
+ ) ?? null;
9353
+ if (!this.registry) {
9275
9354
  context2.writer.writeError("Process registry not available");
9276
9355
  return;
9277
9356
  }
@@ -10411,7 +10490,7 @@ var CliPingCommandProcessor = class {
10411
10490
  context2.writer.writeln(
10412
10491
  `PING ${host} (${url}):`
10413
10492
  );
10414
- context2.setStatusText(`ping ${host}`);
10493
+ context2.notifier.info(`ping ${host}`);
10415
10494
  const times = [];
10416
10495
  let transmitted = 0;
10417
10496
  let received = 0;
@@ -10426,18 +10505,17 @@ var CliPingCommandProcessor = class {
10426
10505
  transmitted++;
10427
10506
  const start = performance.now();
10428
10507
  try {
10429
- const response = await fetch(url, {
10508
+ const response = await context2.http.fetch(url, {
10430
10509
  method: "HEAD",
10431
10510
  mode: "no-cors",
10432
- cache: "no-store",
10433
- signal: context2.signal
10511
+ cache: "no-store"
10434
10512
  });
10435
10513
  const elapsed = performance.now() - start;
10436
10514
  const ms = elapsed.toFixed(1);
10437
10515
  received++;
10438
10516
  times.push(elapsed);
10439
10517
  const status = response.type === "opaque" ? "ok" : `status=${response.status}`;
10440
- context2.setStatusText(`ping ${host}: seq=${seq} time=${ms}ms`);
10518
+ context2.notifier.info(`ping ${host}: seq=${seq} time=${ms}ms`);
10441
10519
  context2.writer.writeln(
10442
10520
  `${url}: seq=${seq} ${status} time=${ms} ms`
10443
10521
  );
@@ -10571,7 +10649,7 @@ var CliExportCommandProcessor = class {
10571
10649
  this.metadata = { icon: "\u{1F4E6}", module: "system" };
10572
10650
  }
10573
10651
  async processCommand(command2, context2) {
10574
- const env = context2.services.get(
10652
+ const env = context2.services.getRequired(
10575
10653
  ICliEnvironment_TOKEN
10576
10654
  );
10577
10655
  const raw = (command2.value || "").trim();
@@ -10625,7 +10703,7 @@ var CliUnsetCommandProcessor = class {
10625
10703
  this.metadata = { icon: "\u{1F5D1}\uFE0F", module: "system" };
10626
10704
  }
10627
10705
  async processCommand(command2, context2) {
10628
- const env = context2.services.get(
10706
+ const env = context2.services.getRequired(
10629
10707
  ICliEnvironment_TOKEN
10630
10708
  );
10631
10709
  const varName = (command2.value || "").trim();
@@ -10659,7 +10737,7 @@ var CliEnvCommandProcessor = class {
10659
10737
  this.metadata = { icon: "\u{1F30D}", module: "system" };
10660
10738
  }
10661
10739
  async processCommand(command2, context2) {
10662
- const env = context2.services.get(
10740
+ const env = context2.services.getRequired(
10663
10741
  ICliEnvironment_TOKEN
10664
10742
  );
10665
10743
  const varName = (command2.value || "").trim();
@@ -10907,16 +10985,9 @@ var CliCommandExecutionContext = class {
10907
10985
  this.createInterval = (cb, ms) => context2.createInterval(cb, ms);
10908
10986
  this.createTimeout = (cb, ms) => context2.createTimeout(cb, ms);
10909
10987
  this.backgroundServices = context2.backgroundServices;
10910
- this.state = context2.services.get(CliStateStoreManager_TOKEN).getProcessorStateStore(processor);
10911
- }
10912
- setStatusText(text) {
10913
- this.context.setStatusText(text);
10914
- }
10915
- clearStatusText() {
10916
- this.context.clearStatusText();
10917
- }
10918
- getStatusText() {
10919
- return this.context.getStatusText();
10988
+ this.http = context2.http;
10989
+ this.notifier = context2.notifier;
10990
+ this.state = context2.services.getRequired(CliStateStoreManager_TOKEN).getProcessorStateStore(processor);
10920
10991
  }
10921
10992
  get promptLength() {
10922
10993
  return this.context.promptLength;
@@ -11466,6 +11537,175 @@ var CliBackgroundServiceRegistry = class {
11466
11537
  }
11467
11538
  }
11468
11539
  };
11540
+ var CliHttpClient = class {
11541
+ constructor(signal) {
11542
+ this.signal = signal;
11543
+ }
11544
+ async fetch(url, init) {
11545
+ return fetch(url, {
11546
+ ...init,
11547
+ signal: this.mergeSignal(init?.signal)
11548
+ });
11549
+ }
11550
+ async get(url, options) {
11551
+ return this.jsonRequest("GET", url, void 0, options);
11552
+ }
11553
+ async post(url, body, options) {
11554
+ return this.jsonRequest("POST", url, body, options);
11555
+ }
11556
+ async put(url, body, options) {
11557
+ return this.jsonRequest("PUT", url, body, options);
11558
+ }
11559
+ async patch(url, body, options) {
11560
+ return this.jsonRequest("PATCH", url, body, options);
11561
+ }
11562
+ async delete(url, options) {
11563
+ return this.jsonRequest("DELETE", url, void 0, options);
11564
+ }
11565
+ async head(url, options) {
11566
+ const { timeout, signal: callerSignal, ...fetchOptions } = options ?? {};
11567
+ let timeoutController;
11568
+ let timeoutId;
11569
+ if (timeout) {
11570
+ timeoutController = new AbortController();
11571
+ timeoutId = setTimeout(() => timeoutController.abort(), timeout);
11572
+ }
11573
+ try {
11574
+ return await fetch(url, {
11575
+ ...fetchOptions,
11576
+ method: "HEAD",
11577
+ signal: this.mergeSignal(callerSignal, timeoutController?.signal)
11578
+ });
11579
+ } finally {
11580
+ if (timeoutId !== void 0) clearTimeout(timeoutId);
11581
+ }
11582
+ }
11583
+ async options(url, options) {
11584
+ return this.jsonRequest("OPTIONS", url, void 0, options);
11585
+ }
11586
+ // ── Internals ────────────────────────────────────────────────────
11587
+ async jsonRequest(method, url, body, options) {
11588
+ const { timeout, ...fetchOptions } = options ?? {};
11589
+ const headers = new Headers(fetchOptions.headers);
11590
+ let resolvedBody;
11591
+ if (body !== void 0 && body !== null) {
11592
+ if (this.isRawBody(body)) {
11593
+ resolvedBody = body;
11594
+ } else {
11595
+ resolvedBody = JSON.stringify(body);
11596
+ if (!headers.has("Content-Type")) {
11597
+ headers.set("Content-Type", "application/json");
11598
+ }
11599
+ }
11600
+ }
11601
+ let timeoutId;
11602
+ let timeoutController;
11603
+ if (timeout) {
11604
+ timeoutController = new AbortController();
11605
+ timeoutId = setTimeout(() => timeoutController.abort(), timeout);
11606
+ }
11607
+ const signal = this.mergeSignal(
11608
+ fetchOptions.signal,
11609
+ timeoutController?.signal
11610
+ );
11611
+ try {
11612
+ const response = await fetch(url, {
11613
+ ...fetchOptions,
11614
+ method,
11615
+ headers,
11616
+ body: resolvedBody,
11617
+ signal
11618
+ });
11619
+ if (!response.ok) {
11620
+ let errorMessage = `${response.status} ${response.statusText}`;
11621
+ let errorBody;
11622
+ try {
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;
11629
+ }
11630
+ } catch {
11631
+ }
11632
+ throw new cliCore.CliHttpError(
11633
+ errorMessage,
11634
+ response.status,
11635
+ response.statusText,
11636
+ errorBody
11637
+ );
11638
+ }
11639
+ const text = await response.text();
11640
+ if (!text) {
11641
+ return void 0;
11642
+ }
11643
+ return JSON.parse(text);
11644
+ } finally {
11645
+ if (timeoutId !== void 0) {
11646
+ clearTimeout(timeoutId);
11647
+ }
11648
+ }
11649
+ }
11650
+ isRawBody(body) {
11651
+ return typeof body === "string" || body instanceof FormData || body instanceof Blob || body instanceof ArrayBuffer || body instanceof URLSearchParams || body instanceof ReadableStream;
11652
+ }
11653
+ /**
11654
+ * Merge the command signal with any additional signals (caller, timeout).
11655
+ * Uses AbortSignal.any() when available, falls back to a manual composite
11656
+ * controller for older browsers (pre-Chrome 116 / Safari 17.4).
11657
+ */
11658
+ mergeSignal(...additional) {
11659
+ const signals = [];
11660
+ if (this.signal) signals.push(this.signal);
11661
+ for (const s of additional) {
11662
+ if (s) signals.push(s);
11663
+ }
11664
+ if (signals.length === 0) return void 0;
11665
+ if (signals.length === 1) return signals[0];
11666
+ if (typeof AbortSignal.any === "function") {
11667
+ return AbortSignal.any(signals);
11668
+ }
11669
+ const controller = new AbortController();
11670
+ for (const s of signals) {
11671
+ if (s.aborted) {
11672
+ controller.abort(s.reason);
11673
+ return controller.signal;
11674
+ }
11675
+ s.addEventListener("abort", () => controller.abort(s.reason), { once: true });
11676
+ }
11677
+ return controller.signal;
11678
+ }
11679
+ };
11680
+ var CliNotifier = class {
11681
+ constructor() {
11682
+ this._change$ = new rxjs.Subject();
11683
+ this.change$ = this._change$.asObservable();
11684
+ }
11685
+ get current() {
11686
+ return this._current;
11687
+ }
11688
+ info(message) {
11689
+ this.set("info", message);
11690
+ }
11691
+ success(message) {
11692
+ this.set("success", message);
11693
+ }
11694
+ warn(message) {
11695
+ this.set("warn", message);
11696
+ }
11697
+ error(message) {
11698
+ this.set("error", message);
11699
+ }
11700
+ clear() {
11701
+ this._current = void 0;
11702
+ this._change$.next(void 0);
11703
+ }
11704
+ set(level, message) {
11705
+ this._current = { level, message, timestamp: Date.now() };
11706
+ this._change$.next(this._current);
11707
+ }
11708
+ };
11469
11709
 
11470
11710
  // src/lib/context/cli-fullscreen-manager.ts
11471
11711
  var CliFullScreenManager = class {
@@ -14427,7 +14667,7 @@ var CliTerminalLineRenderer = class {
14427
14667
  */
14428
14668
  getPromptString(options) {
14429
14669
  const name = options.userName ?? "";
14430
- 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:` : "";
14431
14671
  if (options.contextProcessor) {
14432
14672
  promptStart = `${options.contextProcessor}`;
14433
14673
  }
@@ -14489,7 +14729,6 @@ var CliExecutionContext = class {
14489
14729
  * Set by the command executor at start/end of executeCommand().
14490
14730
  */
14491
14731
  this.isExecuting = false;
14492
- this.statusTextChange$ = new rxjs.Subject();
14493
14732
  this.lineBuffer = new CliLineBuffer();
14494
14733
  this.modeStack = [];
14495
14734
  this.timerManager = new CliTimerManager();
@@ -14534,6 +14773,8 @@ var CliExecutionContext = class {
14534
14773
  this.logger.setCliLogLevel(cliOptions?.logLevel || cliCore.CliLogLevel.ERROR);
14535
14774
  this.commandHistory = deps.commandHistory;
14536
14775
  this.lineRenderer = new CliTerminalLineRenderer(terminal, this.writer);
14776
+ this.http = new CliHttpClient();
14777
+ this.notifier = new CliNotifier();
14537
14778
  this.backgroundServices = new CliBackgroundServiceRegistry(
14538
14779
  this.state,
14539
14780
  deps.services,
@@ -14544,17 +14785,6 @@ var CliExecutionContext = class {
14544
14785
  this.backgroundServices
14545
14786
  );
14546
14787
  }
14547
- setStatusText(text) {
14548
- this._statusText = text;
14549
- this.statusTextChange$.next(text);
14550
- }
14551
- clearStatusText() {
14552
- this._statusText = void 0;
14553
- this.statusTextChange$.next(void 0);
14554
- }
14555
- getStatusText() {
14556
- return this._statusText;
14557
- }
14558
14788
  // -- Public API (ICliExecutionContext) --
14559
14789
  writeToTerminal(text) {
14560
14790
  this.terminal.write(text);
@@ -14748,15 +14978,13 @@ var CliExecutionContext = class {
14748
14978
  }
14749
14979
  // -- CommandLineModeHost interface --
14750
14980
  getPromptOptions() {
14751
- let hideUserName = false;
14752
- try {
14753
- const config = this.services.get("cli-users-module-config");
14754
- hideUserName = config?.hideUserName ?? false;
14755
- } catch {
14756
- }
14981
+ const usersConfig = this.services.get("cli-users-module-config");
14982
+ const hideUserName = usersConfig?.hideUserName ?? false;
14983
+ const anonymousName = usersConfig && !this.userSession ? (usersConfig.anonymousName ?? "anonymous") || void 0 : void 0;
14757
14984
  return {
14758
14985
  userName: this.userSession?.displayName,
14759
14986
  hideUserName,
14987
+ anonymousName,
14760
14988
  contextProcessor: this.contextProcessor?.command,
14761
14989
  pathProvider: this.promptPathProvider
14762
14990
  };
@@ -15001,10 +15229,8 @@ var contextGlobalParameter = {
15001
15229
  // src/lib/executor/cli-io-redirect-handler.ts
15002
15230
  var FS_TOKEN2 = "cli-file-system-service";
15003
15231
  async function writeOutputToFile(filePath, context2) {
15004
- let fs;
15005
- try {
15006
- fs = context2.services.get(FS_TOKEN2);
15007
- } catch {
15232
+ const fs = context2.services.get(FS_TOKEN2);
15233
+ if (!fs) {
15008
15234
  context2.writer.writeError(
15009
15235
  "> redirect requires @qodalis/cli-files plugin"
15010
15236
  );
@@ -15028,10 +15254,8 @@ async function writeOutputToFile(filePath, context2) {
15028
15254
  }
15029
15255
  }
15030
15256
  async function appendOutputToFile(filePath, context2) {
15031
- let fs;
15032
- try {
15033
- fs = context2.services.get(FS_TOKEN2);
15034
- } catch {
15257
+ const fs = context2.services.get(FS_TOKEN2);
15258
+ if (!fs) {
15035
15259
  context2.writer.writeError(
15036
15260
  ">> redirect requires @qodalis/cli-files plugin"
15037
15261
  );
@@ -15057,10 +15281,8 @@ async function appendOutputToFile(filePath, context2) {
15057
15281
  async function writeStderrToFile(filePath, context2, capturingWriter) {
15058
15282
  const stderr = capturingWriter?.getCapturedStderr();
15059
15283
  if (!stderr) return;
15060
- let fs;
15061
- try {
15062
- fs = context2.services.get(FS_TOKEN2);
15063
- } catch {
15284
+ const fs = context2.services.get(FS_TOKEN2);
15285
+ if (!fs) {
15064
15286
  context2.writer.writeError(
15065
15287
  "2> redirect requires @qodalis/cli-files plugin"
15066
15288
  );
@@ -15081,10 +15303,8 @@ async function writeStderrToFile(filePath, context2, capturingWriter) {
15081
15303
  async function appendStderrToFile(filePath, context2, capturingWriter) {
15082
15304
  const stderr = capturingWriter?.getCapturedStderr();
15083
15305
  if (!stderr) return;
15084
- let fs;
15085
- try {
15086
- fs = context2.services.get(FS_TOKEN2);
15087
- } catch {
15306
+ const fs = context2.services.get(FS_TOKEN2);
15307
+ if (!fs) {
15088
15308
  context2.writer.writeError(
15089
15309
  "2>> redirect requires @qodalis/cli-files plugin"
15090
15310
  );
@@ -15104,10 +15324,8 @@ async function appendStderrToFile(filePath, context2, capturingWriter) {
15104
15324
  }
15105
15325
  async function tryExecuteScript(filePath, context2, executeCommand) {
15106
15326
  const FS_TOKEN3 = "cli-file-system-service";
15107
- let fs;
15108
- try {
15109
- fs = context2.services.get(FS_TOKEN3);
15110
- } catch {
15327
+ const fs = context2.services.get(FS_TOKEN3);
15328
+ if (!fs) {
15111
15329
  return false;
15112
15330
  }
15113
15331
  const resolved = fs.resolvePath(filePath);
@@ -15184,10 +15402,8 @@ function substituteVars(input, variables) {
15184
15402
  return result;
15185
15403
  }
15186
15404
  function expandEnvironmentVars(command2, context2) {
15187
- let env;
15188
- try {
15189
- env = context2.services.get(ICliEnvironment_TOKEN);
15190
- } catch {
15405
+ const env = context2.services.get(ICliEnvironment_TOKEN);
15406
+ if (!env) {
15191
15407
  return command2;
15192
15408
  }
15193
15409
  const parts = command2.split("'");
@@ -15332,7 +15548,7 @@ var CliCommandExecutor = class {
15332
15548
  }
15333
15549
  } finally {
15334
15550
  rootContext.isExecuting = false;
15335
- rootContext.clearStatusText?.();
15551
+ rootContext.notifier?.clear();
15336
15552
  }
15337
15553
  }
15338
15554
  async executeSingleCommand(command2, data, context2) {
@@ -15340,8 +15556,8 @@ var CliCommandExecutor = class {
15340
15556
  command2 = expandEnvironmentVars(command2, context2);
15341
15557
  const assignMatch = command2.match(/^([A-Za-z_][A-Za-z0-9_]*)=(.*)$/);
15342
15558
  if (assignMatch) {
15343
- try {
15344
- const env = context2.services.get(ICliEnvironment_TOKEN);
15559
+ const env = context2.services.get(ICliEnvironment_TOKEN);
15560
+ if (env) {
15345
15561
  let val = assignMatch[2];
15346
15562
  if (val.startsWith('"') && val.endsWith('"') || val.startsWith("'") && val.endsWith("'")) {
15347
15563
  val = val.slice(1, -1);
@@ -15350,15 +15566,13 @@ var CliCommandExecutor = class {
15350
15566
  process.start();
15351
15567
  process.end();
15352
15568
  return;
15353
- } catch {
15354
15569
  }
15355
15570
  }
15356
15571
  process.start();
15357
15572
  let processEntry;
15358
- try {
15359
- const procRegistry = context2.services.get(CliProcessRegistry_TOKEN);
15573
+ const procRegistry = context2.services.get(CliProcessRegistry_TOKEN);
15574
+ if (procRegistry) {
15360
15575
  processEntry = procRegistry.register(command2);
15361
- } catch {
15362
15576
  }
15363
15577
  const parsed = this.commandParser.parse(command2);
15364
15578
  let { commandName } = parsed;
@@ -15381,7 +15595,7 @@ var CliCommandExecutor = class {
15381
15595
  }
15382
15596
  if (!processor) {
15383
15597
  const aliasProcessor = this.registry.findProcessor("alias", []);
15384
- const aliases = aliasProcessor?.userAliases ?? {};
15598
+ const aliases = aliasProcessor?.aliasesFor?.(context2) ?? aliasProcessor?.userAliases ?? {};
15385
15599
  if (aliases[mainCommand]) {
15386
15600
  this.completeProcess(processEntry, context2, 0);
15387
15601
  return await this.executeSingleCommand(
@@ -15438,13 +15652,21 @@ var CliCommandExecutor = class {
15438
15652
  }
15439
15653
  const value = processor.acceptsRawInput || processor.valueRequired ? cliCore.getRightOfWord(commandName, processor.command) : void 0;
15440
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
+ }
15441
15662
  const hasPositionalInput = !!value || !!data;
15442
15663
  const hasNamedArgs = Object.keys(args).length > 0;
15443
15664
  if (processor.valueRequired && !hasPositionalInput && !hasNamedArgs) {
15444
15665
  context2.writer.writeError(
15445
15666
  `Value required: ${context2.writer.wrapInColor(`${commandName} <value>`, cliCore.CliForegroundColor.Cyan)}`
15446
15667
  );
15447
- context2.process.exit(-1);
15668
+ this.failProcess(processEntry, context2);
15669
+ context2.process.exit(-1, { silent: true });
15448
15670
  return;
15449
15671
  }
15450
15672
  if (!this.validateBeforeExecution(context2, processor, args, hasPositionalInput)) {
@@ -15460,7 +15682,8 @@ var CliCommandExecutor = class {
15460
15682
  context2.writer.writeError(
15461
15683
  validationResult?.message || "An error occurred while validating the command."
15462
15684
  );
15463
- context2.process.exit(-1);
15685
+ this.failProcess(processEntry, context2);
15686
+ context2.process.exit(-1, { silent: true });
15464
15687
  return;
15465
15688
  }
15466
15689
  }
@@ -15471,6 +15694,7 @@ var CliCommandExecutor = class {
15471
15694
  );
15472
15695
  const commandAbortController = new AbortController();
15473
15696
  commandContext.signal = commandAbortController.signal;
15697
+ commandContext.http = new CliHttpClient(commandAbortController.signal);
15474
15698
  const abortSub = context2.onAbort.subscribe(() => {
15475
15699
  commandAbortController.abort();
15476
15700
  });
@@ -15523,6 +15747,10 @@ var CliCommandExecutor = class {
15523
15747
  "Process exited successfully with code 0"
15524
15748
  );
15525
15749
  }
15750
+ } else if (cliCore.isCliAccessDeniedError(e)) {
15751
+ this.failProcess(processEntry, context2);
15752
+ context2.writer.writeError(e.message);
15753
+ context2.process.exit(-1);
15526
15754
  } else {
15527
15755
  this.failProcess(processEntry, context2);
15528
15756
  context2.writer.writeError(`Error executing command: ${e}`);
@@ -15535,19 +15763,13 @@ var CliCommandExecutor = class {
15535
15763
  }
15536
15764
  completeProcess(processEntry, context2, exitCode) {
15537
15765
  if (!processEntry) return;
15538
- try {
15539
- const procRegistry = context2.services.get(CliProcessRegistry_TOKEN);
15540
- procRegistry.complete(processEntry.pid, exitCode);
15541
- } catch {
15542
- }
15766
+ const procRegistry = context2.services.get(CliProcessRegistry_TOKEN);
15767
+ procRegistry?.complete(processEntry.pid, exitCode);
15543
15768
  }
15544
15769
  failProcess(processEntry, context2) {
15545
15770
  if (!processEntry) return;
15546
- try {
15547
- const procRegistry = context2.services.get(CliProcessRegistry_TOKEN);
15548
- procRegistry.fail(processEntry.pid);
15549
- } catch {
15550
- }
15771
+ const failRegistry = context2.services.get(CliProcessRegistry_TOKEN);
15772
+ failRegistry?.fail(processEntry.pid);
15551
15773
  }
15552
15774
  async showHelp(command2, context2) {
15553
15775
  try {
@@ -15613,6 +15835,52 @@ var CliCommandExecutor = class {
15613
15835
  * parameter. In that case the global handler must be skipped so the
15614
15836
  * processor can handle the flag itself.
15615
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
+ }
15616
15884
  processorOwnsParameter(processor, globalParam) {
15617
15885
  const globalNames = /* @__PURE__ */ new Set([
15618
15886
  globalParam.name,
@@ -15629,29 +15897,94 @@ var CliCommandExecutor = class {
15629
15897
  // src/lib/services/cli-service-container.ts
15630
15898
  var CliServiceContainer = class {
15631
15899
  constructor() {
15632
- /** Stores single-value providers (resolved or pending). */
15900
+ /** Stores single-value providers (resolved singletons). */
15633
15901
  this.services = /* @__PURE__ */ new Map();
15634
15902
  /** Stores multi-provider arrays. */
15635
15903
  this.multiServices = /* @__PURE__ */ new Map();
15904
+ /** Tokens that cannot be overwritten by subsequent `set()` calls. */
15905
+ this.sealedTokens = /* @__PURE__ */ new Set();
15906
+ /** Stores provider recipes for transient tokens (re-resolved on every get). */
15907
+ this.transientRecipes = /* @__PURE__ */ new Map();
15636
15908
  }
15637
15909
  /**
15638
15910
  * Retrieve a service by its token.
15911
+ * Returns `undefined` when no provider is registered for the token.
15912
+ * For multi-provider tokens, returns the last registered value (use `getAll` for the full array).
15639
15913
  *
15640
15914
  * @param token The token used to register the service.
15641
- * @returns The resolved service instance (or array for multi providers).
15642
- * @throws Error if the token has not been registered.
15915
+ * @returns The resolved service instance, or `undefined` if not registered.
15643
15916
  */
15644
15917
  get(token) {
15918
+ if (this.transientRecipes.has(token)) {
15919
+ return this.resolveProvider(this.transientRecipes.get(token));
15920
+ }
15645
15921
  if (this.multiServices.has(token)) {
15646
- return this.multiServices.get(token);
15922
+ const arr = this.multiServices.get(token);
15923
+ return arr[arr.length - 1];
15647
15924
  }
15648
15925
  if (this.services.has(token)) {
15649
15926
  return this.services.get(token);
15650
15927
  }
15651
- const tokenName = typeof token === "string" ? token : typeof token === "function" ? token.name : String(token);
15652
- throw new Error(
15653
- `CliServiceContainer: No provider found for token "${tokenName}".`
15654
- );
15928
+ return void 0;
15929
+ }
15930
+ /**
15931
+ * Retrieve all registered services for a multi-provider token.
15932
+ * Returns an empty array when no provider is registered for the token.
15933
+ *
15934
+ * @param token The token used to register the service.
15935
+ * @returns Array of all resolved service instances.
15936
+ */
15937
+ getAll(token) {
15938
+ if (this.multiServices.has(token)) {
15939
+ return this.multiServices.get(token);
15940
+ }
15941
+ const single = this.get(token);
15942
+ return single !== void 0 ? [single] : [];
15943
+ }
15944
+ /**
15945
+ * Retrieve a service by its token, throwing if not registered.
15946
+ * Use this when the service is required and its absence is a programming error.
15947
+ * For multi-provider tokens, returns the last registered value (use `getAll` for the full array).
15948
+ *
15949
+ * @param token The token used to register the service.
15950
+ * @returns The resolved service instance.
15951
+ * @throws Error if the token has not been registered.
15952
+ */
15953
+ getRequired(token) {
15954
+ const value = this.get(token);
15955
+ if (value === void 0 && !this.has(token)) {
15956
+ throw new Error(
15957
+ `CliServiceContainer: No provider found for token "${this.tokenToString(token)}".`
15958
+ );
15959
+ }
15960
+ return value;
15961
+ }
15962
+ /**
15963
+ * Check whether a service is registered for the given token.
15964
+ */
15965
+ has(token) {
15966
+ return this.services.has(token) || this.multiServices.has(token) || this.transientRecipes.has(token);
15967
+ }
15968
+ /**
15969
+ * Mark a token as sealed so it cannot be overwritten by future `set()` calls.
15970
+ * Only available on the concrete container — not exposed on `ICliServiceProvider`.
15971
+ */
15972
+ seal(token) {
15973
+ this.sealedTokens.add(token);
15974
+ }
15975
+ /**
15976
+ * Mark multiple tokens as sealed at once.
15977
+ */
15978
+ sealAll(tokens) {
15979
+ for (const token of tokens) {
15980
+ this.sealedTokens.add(token);
15981
+ }
15982
+ }
15983
+ /**
15984
+ * Check whether a token is sealed.
15985
+ */
15986
+ isSealed(token) {
15987
+ return this.sealedTokens.has(token);
15655
15988
  }
15656
15989
  /**
15657
15990
  * Register one or more providers.
@@ -15673,13 +16006,13 @@ var CliServiceContainer = class {
15673
16006
  getRegisteredTokens() {
15674
16007
  const tokens = [];
15675
16008
  for (const key of this.services.keys()) {
15676
- tokens.push(
15677
- typeof key === "string" ? key : typeof key === "function" ? key.name : String(key)
15678
- );
16009
+ tokens.push(this.tokenToString(key));
16010
+ }
16011
+ for (const key of this.transientRecipes.keys()) {
16012
+ tokens.push(`${this.tokenToString(key)} (transient)`);
15679
16013
  }
15680
16014
  for (const key of this.multiServices.keys()) {
15681
- const name = typeof key === "string" ? key : typeof key === "function" ? key.name : String(key);
15682
- tokens.push(`${name} (multi)`);
16015
+ tokens.push(`${this.tokenToString(key)} (multi)`);
15683
16016
  }
15684
16017
  return tokens;
15685
16018
  }
@@ -15690,20 +16023,34 @@ var CliServiceContainer = class {
15690
16023
  const details = [];
15691
16024
  for (const [key, value] of this.services.entries()) {
15692
16025
  details.push({
15693
- token: typeof key === "string" ? key : typeof key === "function" ? key.name : String(key),
16026
+ token: this.tokenToString(key),
15694
16027
  type: this.describeValue(value),
15695
16028
  multi: false
15696
16029
  });
15697
16030
  }
16031
+ for (const [key, provider] of this.transientRecipes.entries()) {
16032
+ const className = provider["useClass"]?.name ?? provider["useFactory"]?.name ?? "transient";
16033
+ details.push({
16034
+ token: this.tokenToString(key),
16035
+ type: `transient(${className})`,
16036
+ multi: false
16037
+ });
16038
+ }
15698
16039
  for (const [key, values] of this.multiServices.entries()) {
15699
16040
  details.push({
15700
- token: typeof key === "string" ? key : typeof key === "function" ? key.name : String(key),
16041
+ token: this.tokenToString(key),
15701
16042
  type: `[${values.map((v) => this.describeValue(v)).join(", ")}]`,
15702
16043
  multi: true
15703
16044
  });
15704
16045
  }
15705
16046
  return details;
15706
16047
  }
16048
+ /**
16049
+ * Convert a token to a human-readable string for diagnostics/errors.
16050
+ */
16051
+ tokenToString(token) {
16052
+ return typeof token === "string" ? token : typeof token === "function" ? token.name : String(token);
16053
+ }
15707
16054
  /**
15708
16055
  * Describe a resolved value for diagnostic display.
15709
16056
  */
@@ -15723,27 +16070,45 @@ var CliServiceContainer = class {
15723
16070
  * Register a single provider.
15724
16071
  */
15725
16072
  registerProvider(provider) {
15726
- const value = this.resolveProvider(provider);
15727
- if (provider.multi) {
15728
- const existing = this.multiServices.get(provider.provide) ?? [];
15729
- existing.push(value);
15730
- this.multiServices.set(provider.provide, existing);
16073
+ if (!provider.multi && this.sealedTokens.has(provider.provide)) {
16074
+ throw new Error(
16075
+ `CliServiceContainer: Cannot override sealed service "${this.tokenToString(provider.provide)}".`
16076
+ );
16077
+ }
16078
+ if (provider["transient"] && !provider.multi) {
16079
+ this.transientRecipes.set(provider.provide, provider);
16080
+ this.services.delete(provider.provide);
15731
16081
  } else {
15732
- this.services.set(provider.provide, value);
16082
+ const value = this.resolveProvider(provider);
16083
+ if (provider.multi) {
16084
+ const existing = this.multiServices.get(provider.provide) ?? [];
16085
+ existing.push(value);
16086
+ this.multiServices.set(provider.provide, existing);
16087
+ } else {
16088
+ this.services.set(provider.provide, value);
16089
+ this.transientRecipes.delete(provider.provide);
16090
+ }
16091
+ }
16092
+ if (provider.sealed) {
16093
+ this.sealedTokens.add(provider.provide);
15733
16094
  }
15734
16095
  }
15735
16096
  /**
15736
16097
  * Resolve a provider definition to a concrete value.
16098
+ *
16099
+ * Note: `deps` are resolved eagerly at registration time, so
16100
+ * dependency tokens must be registered before the provider that needs them.
15737
16101
  */
15738
16102
  resolveProvider(provider) {
15739
16103
  if ("useValue" in provider) {
15740
16104
  return provider.useValue;
15741
16105
  }
16106
+ const deps = (provider["deps"] ?? []).map((dep) => this.getRequired(dep));
15742
16107
  if ("useFactory" in provider) {
15743
- return provider.useFactory();
16108
+ return provider.useFactory(...deps);
15744
16109
  }
15745
16110
  if ("useClass" in provider) {
15746
- return new provider.useClass();
16111
+ return new provider.useClass(...deps);
15747
16112
  }
15748
16113
  throw new Error(
15749
16114
  "CliServiceContainer: Provider must specify useValue, useFactory, or useClass."
@@ -15884,10 +16249,10 @@ var welcomeModule = {
15884
16249
  async onAfterBoot(context2) {
15885
16250
  const config = this.config || {};
15886
16251
  let showOption = config.show;
15887
- try {
15888
- const storeManager = context2.services.get(
15889
- CliStateStoreManager_TOKEN
15890
- );
16252
+ const storeManager = context2.services.get(
16253
+ CliStateStoreManager_TOKEN
16254
+ );
16255
+ if (storeManager) {
15891
16256
  const configureStore = storeManager.getStateStore(
15892
16257
  cliCore.CLI_CONFIGURE_STORE_NAME
15893
16258
  );
@@ -15896,7 +16261,6 @@ var welcomeModule = {
15896
16261
  if (state?.["system"]?.["welcomeMessage"]) {
15897
16262
  showOption = state["system"]["welcomeMessage"];
15898
16263
  }
15899
- } catch {
15900
16264
  }
15901
16265
  if (showOption) {
15902
16266
  if (!shouldDisplayWelcomeMessage(showOption)) {
@@ -15924,11 +16288,11 @@ var welcomeModule = {
15924
16288
  recordWelcomeMessageDisplay();
15925
16289
  context2.showPrompt();
15926
16290
  let showGreeting = true;
15927
- try {
15928
- const storeManager = context2.services.get(
15929
- CliStateStoreManager_TOKEN
15930
- );
15931
- const configureStore = storeManager.getStateStore(
16291
+ const greetingStoreManager = context2.services.get(
16292
+ CliStateStoreManager_TOKEN
16293
+ );
16294
+ if (greetingStoreManager) {
16295
+ const configureStore = greetingStoreManager.getStateStore(
15932
16296
  cliCore.CLI_CONFIGURE_STORE_NAME
15933
16297
  );
15934
16298
  await configureStore.initialize();
@@ -15936,7 +16300,6 @@ var welcomeModule = {
15936
16300
  if (configState?.["system"]?.["greeting"] === false) {
15937
16301
  showGreeting = false;
15938
16302
  }
15939
- } catch {
15940
16303
  }
15941
16304
  if (showGreeting) {
15942
16305
  await context2.textAnimator?.showText(getGreetingBasedOnTime(), {
@@ -16040,6 +16403,18 @@ var CliBoot = class {
16040
16403
  if (module.services && module.services.length > 0) {
16041
16404
  this.services.set(module.services);
16042
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
+ }
16043
16418
  if (module.translations) {
16044
16419
  for (const [locale, translations] of Object.entries(module.translations)) {
16045
16420
  context2.translator.addTranslations(locale, translations);
@@ -16056,8 +16431,8 @@ var CliBoot = class {
16056
16431
  }
16057
16432
  }
16058
16433
  if (module.onSetup) {
16059
- const kvStore = context2.services.get(
16060
- "cli-key-value-store"
16434
+ const kvStore = context2.services.getRequired(
16435
+ CliKeyValueStore_TOKEN
16061
16436
  );
16062
16437
  const setupKey = `cli-module-setup:${module.name}`;
16063
16438
  const setupState = await kvStore.get(setupKey);
@@ -16087,19 +16462,47 @@ var CliBoot = class {
16087
16462
  this.registry.registerProcessor(processor);
16088
16463
  }
16089
16464
  await this.initializeProcessorsInternal(context2, filtered);
16465
+ this.adoptMergedSubProcessors(filtered);
16090
16466
  }
16091
16467
  this.bootedModules.add(module.name);
16092
16468
  if (!this.moduleRegistry.has(module.name)) {
16093
16469
  this.moduleRegistry.track(module);
16094
16470
  }
16095
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
+ }
16096
16493
  buildCoreModule() {
16097
16494
  return {
16098
16495
  apiVersion: API_VERSION,
16099
16496
  name: "@qodalis/cli-core",
16100
16497
  version: cliCore.LIBRARY_VERSION,
16101
16498
  description: "Core CLI services and utilities",
16102
- 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
+ ]
16103
16506
  };
16104
16507
  }
16105
16508
  /**
@@ -16231,6 +16634,29 @@ var CliDragDropService = class {
16231
16634
  }
16232
16635
  };
16233
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
+
16234
16660
  // src/lib/addons/overlay.ts
16235
16661
  var OverlayAddon = class {
16236
16662
  constructor() {
@@ -16437,16 +16863,19 @@ var CliStateStore = class {
16437
16863
  this.state$.next(this.initialState);
16438
16864
  }
16439
16865
  async persist() {
16440
- const keyValueStore = this.services.get(
16441
- "cli-key-value-store"
16866
+ const keyValueStore = this.services.getRequired(
16867
+ CliKeyValueStore_TOKEN
16442
16868
  );
16443
16869
  await keyValueStore.set(this.storageKey, this.getState());
16444
16870
  }
16445
16871
  async initialize() {
16872
+ const keyValueStore = this.services.get(
16873
+ CliKeyValueStore_TOKEN
16874
+ );
16875
+ if (!keyValueStore) {
16876
+ return;
16877
+ }
16446
16878
  try {
16447
- const keyValueStore = this.services.get(
16448
- "cli-key-value-store"
16449
- );
16450
16879
  const state = await keyValueStore.get(this.storageKey);
16451
16880
  if (state) {
16452
16881
  this.state$.next(state);
@@ -16513,6 +16942,39 @@ var CliStateStoreManager = class {
16513
16942
  this.stores.clear();
16514
16943
  }
16515
16944
  };
16945
+ var CliServerAuthService = class {
16946
+ constructor(services) {
16947
+ this.services = services;
16948
+ }
16949
+ getHeaders(serverName, configHeaders) {
16950
+ const merged = {
16951
+ ...cliCore.resolveHeaders(configHeaders)
16952
+ };
16953
+ const providers = this.services.getAll(
16954
+ cliCore.ICliServerAuthTokenProvider_TOKEN
16955
+ );
16956
+ for (const provider of providers) {
16957
+ Object.assign(merged, provider.getHeaders(serverName));
16958
+ }
16959
+ return merged;
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
+ }
16977
+ };
16516
16978
 
16517
16979
  // src/lib/completion/cli-command-completion-provider.ts
16518
16980
  var CliCommandCompletionProvider = class {
@@ -16660,12 +17122,10 @@ var CliPackageNameCompletionProvider = class {
16660
17122
  if (!INSTALLED_PKG_SUBCOMMANDS.has(subCommand)) {
16661
17123
  return [];
16662
17124
  }
16663
- let manager;
16664
- try {
16665
- manager = this.services.get(
16666
- CliPackageManagerService_TOKEN
16667
- );
16668
- } catch {
17125
+ const manager = this.services.get(
17126
+ CliPackageManagerService_TOKEN
17127
+ );
17128
+ if (!manager) {
16669
17129
  return [];
16670
17130
  }
16671
17131
  const packages = await manager.getPackages();
@@ -16868,7 +17328,7 @@ var ServerListProcessor = class {
16868
17328
  this.description = "Show configured servers and their connection status";
16869
17329
  }
16870
17330
  async processCommand(_command, context2) {
16871
- const manager = context2.services.get(
17331
+ const manager = context2.services.getRequired(
16872
17332
  CliServerManager_TOKEN
16873
17333
  );
16874
17334
  if (!manager || manager.connections.size === 0) {
@@ -16907,7 +17367,7 @@ var ServerStatusProcessor = class {
16907
17367
  context2.process.exit(1);
16908
17368
  return;
16909
17369
  }
16910
- const manager = context2.services.get(
17370
+ const manager = context2.services.getRequired(
16911
17371
  CliServerManager_TOKEN
16912
17372
  );
16913
17373
  const connection = manager?.getConnection(serverName);
@@ -16917,7 +17377,7 @@ var ServerStatusProcessor = class {
16917
17377
  return;
16918
17378
  }
16919
17379
  context2.spinner?.show(`Pinging ${serverName}...`);
16920
- context2.setStatusText(`Pinging ${serverName}`);
17380
+ context2.notifier.info(`Pinging ${serverName}`);
16921
17381
  const reachable = await connection.ping();
16922
17382
  context2.spinner?.hide();
16923
17383
  if (reachable) {
@@ -16948,7 +17408,7 @@ var ServerReconnectProcessor = class {
16948
17408
  context2.process.exit(1);
16949
17409
  return;
16950
17410
  }
16951
- const manager = context2.services.get(
17411
+ const manager = context2.services.getRequired(
16952
17412
  CliServerManager_TOKEN
16953
17413
  );
16954
17414
  if (!manager) {
@@ -16957,7 +17417,7 @@ var ServerReconnectProcessor = class {
16957
17417
  return;
16958
17418
  }
16959
17419
  context2.spinner?.show(`Reconnecting to ${serverName}...`);
16960
- context2.setStatusText(`Reconnecting to ${serverName}`);
17420
+ context2.notifier.info(`Reconnecting to ${serverName}`);
16961
17421
  const result = await manager.reconnect(serverName);
16962
17422
  context2.spinner?.hide();
16963
17423
  if (result.success) {
@@ -16987,7 +17447,7 @@ var ServerDefaultProcessor = class {
16987
17447
  ];
16988
17448
  }
16989
17449
  async processCommand(command2, context2) {
16990
- const manager = context2.services.get(
17450
+ const manager = context2.services.getRequired(
16991
17451
  CliServerManager_TOKEN
16992
17452
  );
16993
17453
  if (!manager) {
@@ -17023,8 +17483,6 @@ var ServerDefaultProcessor = class {
17023
17483
  }
17024
17484
  }
17025
17485
  };
17026
-
17027
- // src/lib/server/cli-ssh-command-processor.ts
17028
17486
  var CliSshCommandProcessor = class {
17029
17487
  constructor() {
17030
17488
  this.command = "ssh";
@@ -17033,7 +17491,9 @@ var CliSshCommandProcessor = class {
17033
17491
  this.valueRequired = false;
17034
17492
  this.metadata = {
17035
17493
  module: "@qodalis/cli-server",
17036
- 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]
17037
17497
  };
17038
17498
  this.parameters = [
17039
17499
  {
@@ -17043,12 +17503,9 @@ var CliSshCommandProcessor = class {
17043
17503
  type: "string"
17044
17504
  }
17045
17505
  ];
17046
- this.socket = null;
17047
- this.context = null;
17048
- this.resolved = false;
17049
17506
  }
17050
17507
  async processCommand(command2, context2) {
17051
- const manager = context2.services.get(
17508
+ const manager = context2.services.getRequired(
17052
17509
  CliServerManager_TOKEN
17053
17510
  );
17054
17511
  if (!manager) {
@@ -17077,26 +17534,34 @@ var CliSshCommandProcessor = class {
17077
17534
  await this.executeInteractive(connection, serverName, context2);
17078
17535
  }
17079
17536
  }
17080
- async onData(data, context2) {
17081
- if (this.socket?.readyState === WebSocket.OPEN) {
17082
- const msg = { type: "stdin", data };
17083
- this.socket.send(JSON.stringify(msg));
17084
- }
17085
- }
17086
- onResize(cols, rows, _context) {
17087
- if (this.socket?.readyState === WebSocket.OPEN) {
17088
- const msg = { type: "resize", cols, rows };
17089
- this.socket.send(JSON.stringify(msg));
17090
- }
17091
- }
17092
- onDispose(_context) {
17093
- if (this.socket) {
17094
- if (this.socket.readyState === WebSocket.OPEN || this.socket.readyState === WebSocket.CONNECTING) {
17095
- 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));
17096
17547
  }
17097
- this.socket = null;
17098
- }
17099
- 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;
17100
17565
  }
17101
17566
  async resolveServer(command2, shellServers, context2) {
17102
17567
  const empty = { serverName: null, oneShotCmd: null, connection: null };
@@ -17122,7 +17587,7 @@ var CliSshCommandProcessor = class {
17122
17587
  const candidateName = parts[0];
17123
17588
  const entry2 = shellServers.find((s) => s.name === candidateName);
17124
17589
  if (entry2) {
17125
- const oneShotCmd = parts.slice(1).join(" ") || null;
17590
+ const oneShotCmd = this.remoteCommandFromRaw(command2.rawCommand, candidateName) ?? (parts.slice(1).join(" ") || null);
17126
17591
  return {
17127
17592
  serverName: candidateName,
17128
17593
  oneShotCmd,
@@ -17142,7 +17607,7 @@ var CliSshCommandProcessor = class {
17142
17607
  connection: shellServers[0].connection
17143
17608
  };
17144
17609
  }
17145
- const manager = context2.services.get(
17610
+ const manager = context2.services.getRequired(
17146
17611
  CliServerManager_TOKEN
17147
17612
  );
17148
17613
  if (manager?.defaultServer) {
@@ -17176,41 +17641,57 @@ var CliSshCommandProcessor = class {
17176
17641
  };
17177
17642
  }
17178
17643
  async executeInteractive(connection, serverName, context2) {
17179
- this.context = context2;
17644
+ const session = { socket: null, resolved: false, disposed: false };
17180
17645
  const cols = context2.terminal.cols;
17181
17646
  const rows = context2.terminal.rows;
17182
- const wsUrl = this.buildShellUrl(connection, cols, rows);
17647
+ const wsUrl = await this.buildShellUrl(connection, cols, rows);
17183
17648
  context2.spinner?.show(`Connecting to ${serverName}...`);
17184
- this.resolved = false;
17649
+ let fullScreen = false;
17185
17650
  return new Promise((resolve) => {
17651
+ const signal = context2.signal;
17186
17652
  const resolveOnce = () => {
17187
- if (!this.resolved) {
17188
- this.resolved = true;
17653
+ signal?.removeEventListener("abort", onAbort);
17654
+ if (!session.resolved) {
17655
+ session.resolved = true;
17189
17656
  resolve();
17190
17657
  }
17191
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 });
17192
17671
  try {
17193
- this.socket = new WebSocket(wsUrl);
17672
+ session.socket = new WebSocket(wsUrl);
17194
17673
  } catch (e) {
17195
17674
  context2.spinner?.hide();
17196
17675
  context2.writer.writeError(
17197
17676
  `Failed to connect to '${serverName}': ${e.message}`
17198
17677
  );
17199
- context2.process.exit(1);
17678
+ context2.process.exit(1, { silent: true });
17200
17679
  resolveOnce();
17201
17680
  return;
17202
17681
  }
17203
- this.socket.onopen = () => {
17682
+ session.socket.onopen = () => {
17204
17683
  };
17205
- this.socket.onmessage = (event) => {
17206
- const msg = JSON.parse(event.data);
17684
+ session.socket.onmessage = (event) => {
17685
+ const msg = parseShellMessage(event.data);
17686
+ if (!msg) return;
17207
17687
  switch (msg.type) {
17208
17688
  case "ready":
17209
17689
  context2.spinner?.hide();
17210
17690
  context2.writer.writeInfo(
17211
17691
  `Connected to ${serverName} (${msg.os}, ${msg.shell})`
17212
17692
  );
17213
- context2.enterFullScreenMode(this);
17693
+ context2.enterFullScreenMode(this.fullScreenHandler(session));
17694
+ fullScreen = true;
17214
17695
  context2.terminal.write("\x1B[2J\x1B[H\x1B[?25h");
17215
17696
  break;
17216
17697
  case "stdout":
@@ -17220,70 +17701,82 @@ var CliSshCommandProcessor = class {
17220
17701
  context2.terminal.write(msg.data);
17221
17702
  break;
17222
17703
  case "exit":
17223
- context2.exitFullScreenMode();
17704
+ if (fullScreen) context2.exitFullScreenMode();
17224
17705
  context2.writer.writeInfo(
17225
17706
  `Shell exited with code ${msg.code}.`
17226
17707
  );
17227
- context2.process.exit(msg.code);
17228
- this.cleanup();
17708
+ context2.process.exit(msg.code, { silent: true });
17709
+ this.cleanup(session);
17229
17710
  resolveOnce();
17230
17711
  break;
17231
17712
  case "error":
17232
17713
  context2.spinner?.hide();
17233
- context2.exitFullScreenMode();
17714
+ if (fullScreen) context2.exitFullScreenMode();
17234
17715
  context2.writer.writeError(
17235
17716
  `Server error: ${msg.message}`
17236
17717
  );
17237
- context2.process.exit(1);
17238
- this.cleanup();
17718
+ context2.process.exit(1, { silent: true });
17719
+ this.cleanup(session);
17239
17720
  resolveOnce();
17240
17721
  break;
17241
17722
  }
17242
17723
  };
17243
- this.socket.onclose = () => {
17724
+ let errored = false;
17725
+ session.socket.onclose = (event) => {
17244
17726
  context2.spinner?.hide();
17245
- if (this.context && !this.resolved) {
17246
- context2.exitFullScreenMode();
17247
- context2.writer.writeWarning(
17248
- `Connection to '${serverName}' closed.`
17249
- );
17250
- 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 });
17251
17734
  }
17252
- this.cleanup();
17735
+ this.cleanup(session);
17253
17736
  resolveOnce();
17254
17737
  };
17255
- this.socket.onerror = () => {
17256
- context2.spinner?.hide();
17257
- if (!this.resolved) {
17258
- context2.writer.writeError(
17259
- `WebSocket error connecting to '${serverName}'.`
17260
- );
17261
- context2.process.exit(1);
17262
- }
17263
- this.cleanup();
17264
- resolveOnce();
17738
+ session.socket.onerror = () => {
17739
+ errored = true;
17265
17740
  };
17266
17741
  });
17267
17742
  }
17268
17743
  async executeOneShot(connection, serverName, cmd, context2) {
17269
17744
  const cols = context2.terminal.cols;
17270
17745
  const rows = context2.terminal.rows;
17271
- const wsUrl = this.buildShellUrl(connection, cols, rows, cmd);
17746
+ const wsUrl = await this.buildShellUrl(connection, cols, rows, cmd);
17272
17747
  context2.spinner?.show(`Running on ${serverName}...`);
17273
- 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 });
17274
17766
  try {
17275
- this.socket = new WebSocket(wsUrl);
17767
+ session.socket = new WebSocket(wsUrl);
17276
17768
  } catch (e) {
17277
17769
  context2.spinner?.hide();
17278
17770
  context2.writer.writeError(
17279
17771
  `Failed to connect to '${serverName}': ${e.message}`
17280
17772
  );
17281
- context2.process.exit(1);
17773
+ context2.process.exit(1, { silent: true });
17282
17774
  resolve();
17283
17775
  return;
17284
17776
  }
17285
- this.socket.onmessage = (event) => {
17286
- const msg = JSON.parse(event.data);
17777
+ session.socket.onmessage = (event) => {
17778
+ const msg = parseShellMessage(event.data);
17779
+ if (!msg) return;
17287
17780
  switch (msg.type) {
17288
17781
  case "ready":
17289
17782
  context2.spinner?.hide();
@@ -17296,9 +17789,9 @@ var CliSshCommandProcessor = class {
17296
17789
  break;
17297
17790
  case "exit":
17298
17791
  if (msg.code !== 0) {
17299
- context2.process.exit(msg.code);
17792
+ context2.process.exit(msg.code, { silent: true });
17300
17793
  }
17301
- this.cleanup();
17794
+ this.cleanup(session);
17302
17795
  resolve();
17303
17796
  break;
17304
17797
  case "error":
@@ -17306,50 +17799,82 @@ var CliSshCommandProcessor = class {
17306
17799
  context2.writer.writeError(
17307
17800
  `Server error: ${msg.message}`
17308
17801
  );
17309
- context2.process.exit(1);
17310
- this.cleanup();
17802
+ context2.process.exit(1, { silent: true });
17803
+ this.cleanup(session);
17311
17804
  resolve();
17312
17805
  break;
17313
17806
  }
17314
17807
  };
17315
- this.socket.onclose = () => {
17808
+ let errored = false;
17809
+ session.socket.onclose = (event) => {
17316
17810
  context2.spinner?.hide();
17317
- 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);
17318
17817
  resolve();
17319
17818
  };
17320
- this.socket.onerror = () => {
17321
- context2.spinner?.hide();
17322
- context2.writer.writeError(
17323
- `WebSocket error connecting to '${serverName}'.`
17324
- );
17325
- context2.process.exit(1);
17326
- this.cleanup();
17327
- resolve();
17819
+ session.socket.onerror = () => {
17820
+ errored = true;
17328
17821
  };
17329
17822
  });
17330
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
+ }
17331
17829
  buildShellUrl(connection, cols, rows, cmd) {
17332
- const baseUrl = connection.config.url.endsWith("/") ? connection.config.url.slice(0, -1) : connection.config.url;
17333
- const wsBase = baseUrl.replace(/^https:/, "wss:").replace(/^http:/, "ws:");
17334
- let url = `${wsBase}/ws/v1/qcli/shell?cols=${cols}&rows=${rows}`;
17830
+ let path = `/ws/v1/qcli/shell?cols=${cols}&rows=${rows}`;
17335
17831
  if (cmd) {
17336
- url += `&cmd=${encodeURIComponent(cmd)}`;
17832
+ path += `&cmd=${encodeURIComponent(cmd)}`;
17337
17833
  }
17338
- return url;
17834
+ return connection.buildWebSocketUrl(path);
17339
17835
  }
17340
- cleanup() {
17341
- if (this.socket) {
17342
- this.socket.onmessage = null;
17343
- this.socket.onclose = null;
17344
- this.socket.onerror = null;
17345
- if (this.socket.readyState === WebSocket.OPEN || this.socket.readyState === WebSocket.CONNECTING) {
17346
- 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();
17347
17844
  }
17348
- this.socket = null;
17845
+ session.socket = null;
17349
17846
  }
17350
- this.context = null;
17351
17847
  }
17352
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
+ }
17353
17878
 
17354
17879
  // src/lib/server/cli-server-module.ts
17355
17880
  function createServerModule() {
@@ -17384,6 +17909,11 @@ function initializeTerminal(container, opts) {
17384
17909
  terminal.open(container);
17385
17910
  fitAddon.fit();
17386
17911
  requestAnimationFrame(() => fitAddon.fit());
17912
+ if (document.fonts?.ready) {
17913
+ document.fonts.ready.then(() => {
17914
+ requestAnimationFrame(() => fitAddon.fit());
17915
+ });
17916
+ }
17387
17917
  container.classList.add("terminal-container");
17388
17918
  container.style.background = opts.theme?.background ?? "#000";
17389
17919
  const wheelListener = (e) => e.preventDefault();
@@ -17626,19 +18156,21 @@ async function initializeServices(options) {
17626
18156
  const logger = new CliLogger();
17627
18157
  const commandHistory = new CliCommandHistory(store);
17628
18158
  await commandHistory.initialize();
17629
- services.set([{ provide: "cli-key-value-store", useValue: store }]);
18159
+ services.set([{ provide: CliKeyValueStore_TOKEN, useValue: store, sealed: true }]);
17630
18160
  const stateStoreManager = new CliStateStoreManager(services, registry);
17631
18161
  const processRegistry = new CliProcessRegistry();
17632
18162
  services.set([
17633
18163
  {
17634
18164
  provide: CliStateStoreManager_TOKEN,
17635
- useValue: stateStoreManager
18165
+ useValue: stateStoreManager,
18166
+ sealed: true
17636
18167
  },
17637
- { provide: CliProcessorsRegistry_TOKEN, useValue: registry },
17638
- { provide: CliCommandHistory_TOKEN, useValue: commandHistory },
18168
+ { provide: CliProcessorsRegistry_TOKEN, useValue: registry, sealed: true },
18169
+ { provide: CliCommandHistory_TOKEN, useValue: commandHistory, sealed: true },
17639
18170
  {
17640
18171
  provide: CliProcessRegistry_TOKEN,
17641
- useValue: processRegistry
18172
+ useValue: processRegistry,
18173
+ sealed: true
17642
18174
  }
17643
18175
  ]);
17644
18176
  if (pendingServices.length > 0) {
@@ -17649,7 +18181,8 @@ async function initializeServices(options) {
17649
18181
  services.set([
17650
18182
  {
17651
18183
  provide: ICliPingServerService_TOKEN,
17652
- useValue: new CliDefaultPingServerService()
18184
+ useClass: CliDefaultPingServerService,
18185
+ sealed: true
17653
18186
  }
17654
18187
  ]);
17655
18188
  }
@@ -17659,25 +18192,41 @@ async function initializeServices(options) {
17659
18192
  syntaxRegistry.register(new MarkdownHighlighter());
17660
18193
  syntaxRegistry.register(new YamlHighlighter());
17661
18194
  services.set([
17662
- { provide: "syntax-highlighter-registry", useValue: syntaxRegistry }
18195
+ { provide: SyntaxHighlighterRegistry_TOKEN, useValue: syntaxRegistry, sealed: true }
17663
18196
  ]);
17664
18197
  if (!pendingTokens.has(ICliEnvironment_TOKEN)) {
17665
18198
  services.set([
17666
18199
  {
17667
18200
  provide: ICliEnvironment_TOKEN,
17668
- useValue: new CliEnvironment()
18201
+ useClass: CliEnvironment,
18202
+ sealed: true
17669
18203
  }
17670
18204
  ]);
17671
18205
  }
17672
18206
  services.set([
17673
18207
  {
17674
18208
  provide: cliCore.ICliDragDropService_TOKEN,
17675
- useValue: dragDropService
18209
+ useValue: dragDropService,
18210
+ sealed: true
18211
+ }
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
17676
18225
  }
17677
18226
  ]);
17678
18227
  const translator = new CliTranslationService();
17679
18228
  services.set([
17680
- { provide: cliCore.ICliTranslationService_TOKEN, useValue: translator }
18229
+ { provide: cliCore.ICliTranslationService_TOKEN, useValue: translator, sealed: true }
17681
18230
  ]);
17682
18231
  return {
17683
18232
  services,
@@ -17746,7 +18295,7 @@ var CliEngine = class {
17746
18295
  * Initialize the terminal, wire up services, boot modules, and show welcome message.
17747
18296
  */
17748
18297
  async start() {
17749
- initWasmAccelerator().catch(() => {
18298
+ initWasmAccelerator(this.options?.wasmUrl).catch(() => {
17750
18299
  });
17751
18300
  await waitForLayout(this.container);
17752
18301
  const terminalOpts = getTerminalOptions(this.options?.terminalOptions);
@@ -17789,10 +18338,18 @@ var CliEngine = class {
17789
18338
  provide: CliBackgroundServiceRegistry_TOKEN,
17790
18339
  useValue: this.executionContext.backgroundServices
17791
18340
  }]);
17792
- const serverManager = new CliServerManager(this.registry);
18341
+ const serverAuthService = new CliServerAuthService(services);
17793
18342
  services.set([
17794
- { provide: CliServerManager_TOKEN, useValue: serverManager }
18343
+ { provide: cliCore.ICliServerAuthService_TOKEN, useValue: serverAuthService, sealed: true }
18344
+ ]);
18345
+ services.set([
18346
+ {
18347
+ provide: CliServerManager_TOKEN,
18348
+ useClass: CliServerManager,
18349
+ deps: [CliProcessorsRegistry_TOKEN, cliCore.ICliServerAuthService_TOKEN]
18350
+ }
17795
18351
  ]);
18352
+ const serverManager = services.getRequired(CliServerManager_TOKEN);
17796
18353
  if (this.options?.servers && this.options.servers.length > 0) {
17797
18354
  await serverManager.connectAll(this.options.servers, {
17798
18355
  warn: (msg) => this.executionContext.logger.warn(msg),
@@ -17812,13 +18369,9 @@ var CliEngine = class {
17812
18369
  new CliCommandCompletionProvider(this.registry),
17813
18370
  new CliParameterCompletionProvider(this.registry, executor)
17814
18371
  ];
17815
- let pluginProviders = [];
17816
- try {
17817
- pluginProviders = services.get(
17818
- cliCore.ICliCompletionProvider_TOKEN
17819
- ) ?? [];
17820
- } catch {
17821
- }
18372
+ const pluginProviders = services.getAll(
18373
+ cliCore.ICliCompletionProvider_TOKEN
18374
+ );
17822
18375
  this.executionContext.completionEngine.setProviders([
17823
18376
  ...pluginProviders,
17824
18377
  ...defaultProviders
@@ -17926,7 +18479,7 @@ var CliEngine = class {
17926
18479
  if (!this.executionContext) {
17927
18480
  throw new Error("Cannot snapshot before engine has started");
17928
18481
  }
17929
- const stateStoreManager = this.executionContext.services.get(
18482
+ const stateStoreManager = this.executionContext.services.getRequired(
17930
18483
  CliStateStoreManager_TOKEN
17931
18484
  );
17932
18485
  return {
@@ -17953,7 +18506,7 @@ var CliEngine = class {
17953
18506
  });
17954
18507
  }
17955
18508
  await this.executionContext.commandHistory.setHistory(snap.commandHistory);
17956
- const stateStoreManager = this.executionContext.services.get(
18509
+ const stateStoreManager = this.executionContext.services.getRequired(
17957
18510
  CliStateStoreManager_TOKEN
17958
18511
  );
17959
18512
  for (const entry of snap.stateStores) {
@@ -18013,7 +18566,7 @@ var CliTestHarness = class {
18013
18566
  this.services = new CliServiceContainer();
18014
18567
  this.processRegistry = new CliProcessRegistry();
18015
18568
  this.services.set({
18016
- provide: "cli-key-value-store",
18569
+ provide: CliKeyValueStore_TOKEN,
18017
18570
  useValue: createInMemoryKvStore()
18018
18571
  });
18019
18572
  this.services.set({
@@ -18036,6 +18589,24 @@ var CliTestHarness = class {
18036
18589
  provide: ICliEnvironment_TOKEN,
18037
18590
  useValue: new CliEnvironment()
18038
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;
18039
18610
  }
18040
18611
  /**
18041
18612
  * Register a service in the test container.
@@ -18120,9 +18691,10 @@ var CliTestHarness = class {
18120
18691
  options: void 0,
18121
18692
  logger: createNoopLogger(),
18122
18693
  process,
18123
- userSession: this.userSession,
18694
+ userSession: this.userSessionProvider ? this.userSessionProvider() : this.userSession,
18124
18695
  state: this.stateStoreManager.getStateStore("shared"),
18125
18696
  backgroundServices: createNoopBackgroundServices(),
18697
+ http: new CliHttpClient(),
18126
18698
  promptLength: 0,
18127
18699
  currentLine: "",
18128
18700
  cursorPosition: 0,
@@ -18145,11 +18717,7 @@ var CliTestHarness = class {
18145
18717
  submitCommand: async (command2) => {
18146
18718
  await this.executor.executeCommand(command2, context2);
18147
18719
  },
18148
- setStatusText: () => {
18149
- },
18150
- clearStatusText: () => {
18151
- },
18152
- getStatusText: () => void 0,
18720
+ notifier: new CliNotifier(),
18153
18721
  createInterval: (cb, ms) => {
18154
18722
  const id = setInterval(cb, ms);
18155
18723
  return {
@@ -18402,18 +18970,22 @@ exports.CliHelpCommandProcessor = CliHelpCommandProcessor;
18402
18970
  exports.CliHistoryCommandProcessor = CliHistoryCommandProcessor;
18403
18971
  exports.CliHotKeysCommandProcessor = CliHotKeysCommandProcessor;
18404
18972
  exports.CliHtopCommandProcessor = CliHtopCommandProcessor;
18973
+ exports.CliHttpClient = CliHttpClient;
18405
18974
  exports.CliInputReader = CliInputReader;
18406
18975
  exports.CliJsonCommandProcessor = CliJsonCommandProcessor;
18407
18976
  exports.CliKeyValueStore = CliKeyValueStore;
18977
+ exports.CliKeyValueStore_TOKEN = CliKeyValueStore_TOKEN;
18408
18978
  exports.CliKillCommandProcessor = CliKillCommandProcessor;
18409
18979
  exports.CliLogger = CliLogger;
18410
18980
  exports.CliLoremCommandProcessor = CliLoremCommandProcessor;
18411
18981
  exports.CliModuleRegistry_TOKEN = CliModuleRegistry_TOKEN;
18412
18982
  exports.CliNanoCommandProcessor = CliNanoCommandProcessor;
18983
+ exports.CliNotifier = CliNotifier;
18413
18984
  exports.CliOpenCommandProcessor = CliOpenCommandProcessor;
18414
18985
  exports.CliPackageManagerService = CliPackageManagerService;
18415
18986
  exports.CliPackageManagerService_TOKEN = CliPackageManagerService_TOKEN;
18416
18987
  exports.CliPackagesCommandProcessor = CliPackagesCommandProcessor;
18988
+ exports.CliPermissionRegistry = CliPermissionRegistry;
18417
18989
  exports.CliPingCommandProcessor = CliPingCommandProcessor;
18418
18990
  exports.CliProcessRegistry = CliProcessRegistry;
18419
18991
  exports.CliProcessRegistry_TOKEN = CliProcessRegistry_TOKEN;
@@ -18459,12 +19031,14 @@ exports.LIBRARY_VERSION = LIBRARY_VERSION;
18459
19031
  exports.OverlayAddon = OverlayAddon;
18460
19032
  exports.ProcessExitedError = ProcessExitedError;
18461
19033
  exports.ScriptLoaderService = ScriptLoaderService;
19034
+ exports.SyntaxHighlighterRegistry_TOKEN = SyntaxHighlighterRegistry_TOKEN;
18462
19035
  exports.appendOutputToFile = appendOutputToFile;
18463
19036
  exports.appendStderrToFile = appendStderrToFile;
18464
19037
  exports.builtinProcessors = builtinProcessors;
18465
19038
  exports.computeDiff = computeDiff;
18466
19039
  exports.contextGlobalParameter = contextGlobalParameter;
18467
19040
  exports.createServerModule = createServerModule;
19041
+ exports.describeShellClose = describeShellClose;
18468
19042
  exports.executeOnServer = executeOnServer;
18469
19043
  exports.expandEnvironmentVars = expandEnvironmentVars;
18470
19044
  exports.formatDiff = formatDiff;