@qodalis/cli 2.2.0 → 2.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/public-api.mjs CHANGED
@@ -518,7 +518,7 @@ var CliKeyValueStore_TOKEN = createCliToken("cli-key-value-store");
518
518
  var SyntaxHighlighterRegistry_TOKEN = createCliToken("syntax-highlighter-registry");
519
519
 
520
520
  // src/lib/version.ts
521
- var LIBRARY_VERSION = "2.2.0";
521
+ var LIBRARY_VERSION = "2.4.0";
522
522
  var API_VERSION = 2;
523
523
  var ANONYMOUS = "\0anonymous";
524
524
  function aliasOwner(context2) {
@@ -11187,7 +11187,78 @@ var CliServicesCommandProcessor = class {
11187
11187
  );
11188
11188
  }
11189
11189
  };
11190
+ var CLI_EVENT_HISTORY_SIZE = 200;
11191
+ function matchesEventPattern(type, pattern) {
11192
+ if (pattern === "*") return true;
11193
+ if (pattern.endsWith(":*")) {
11194
+ return type.startsWith(pattern.slice(0, -1));
11195
+ }
11196
+ return type === pattern;
11197
+ }
11198
+ var CliEventBus = class {
11199
+ constructor(onHandlerError = (error) => console.error("[CLI] Event handler failed:", error), historySize = CLI_EVENT_HISTORY_SIZE) {
11200
+ this.onHandlerError = onHandlerError;
11201
+ this.historySize = historySize;
11202
+ this.subject = new Subject();
11203
+ this.recent = [];
11204
+ this.disposed = false;
11205
+ }
11206
+ emit(type, data, options) {
11207
+ if (this.disposed) return;
11208
+ const event = {
11209
+ type,
11210
+ data,
11211
+ source: options?.source,
11212
+ timestamp: Date.now()
11213
+ };
11214
+ this.recent.push(event);
11215
+ if (this.recent.length > this.historySize) {
11216
+ this.recent.shift();
11217
+ }
11218
+ this.subject.next(event);
11219
+ }
11220
+ on(pattern, handler) {
11221
+ const subscription = this.events$(pattern).subscribe((event) => {
11222
+ try {
11223
+ handler(event);
11224
+ } catch (error) {
11225
+ this.onHandlerError(error, event);
11226
+ }
11227
+ });
11228
+ return () => subscription.unsubscribe();
11229
+ }
11230
+ once(pattern, handler) {
11231
+ let done = false;
11232
+ const unsubscribe = this.on(pattern, (event) => {
11233
+ if (done) return;
11234
+ done = true;
11235
+ unsubscribe();
11236
+ handler(event);
11237
+ });
11238
+ return unsubscribe;
11239
+ }
11240
+ events$(pattern = "*") {
11241
+ return this.subject.asObservable().pipe(filter((event) => matchesEventPattern(event.type, pattern)));
11242
+ }
11243
+ history(pattern = "*", limit) {
11244
+ const matching = this.recent.filter(
11245
+ (event) => matchesEventPattern(event.type, pattern)
11246
+ );
11247
+ return limit !== void 0 ? matching.slice(-limit) : matching;
11248
+ }
11249
+ /** Stop delivering events and complete all observables. */
11250
+ dispose() {
11251
+ if (this.disposed) return;
11252
+ this.disposed = true;
11253
+ this.subject.complete();
11254
+ this.recent.length = 0;
11255
+ }
11256
+ };
11257
+
11258
+ // src/lib/processors/system/cli-events-command-processor.ts
11190
11259
  var DATA_PREVIEW_LENGTH = 120;
11260
+ var SERVER_EVENT = "server:event";
11261
+ var SERVER_ENVELOPE_FIELDS = /* @__PURE__ */ new Set(["type", "timestamp"]);
11191
11262
  var RESERVED_NAMESPACES = [
11192
11263
  "boot",
11193
11264
  "command",
@@ -11199,26 +11270,83 @@ var RESERVED_NAMESPACES = [
11199
11270
  "session",
11200
11271
  "user"
11201
11272
  ];
11273
+ function serverEventOf(event) {
11274
+ return event.type === SERVER_EVENT ? event.data : void 0;
11275
+ }
11276
+ function matchesFilter(event, filter2) {
11277
+ const remote = serverEventOf(event);
11278
+ if (!remote) {
11279
+ return !filter2.server && matchesEventPattern(event.type, filter2.pattern);
11280
+ }
11281
+ if (filter2.local) return false;
11282
+ if (filter2.server && remote.server.toLowerCase() !== filter2.server.toLowerCase()) return false;
11283
+ return matchesEventPattern(remote.type, filter2.pattern) || matchesEventPattern(SERVER_EVENT, filter2.pattern);
11284
+ }
11202
11285
  function canSee(event, context2) {
11203
11286
  return !event.type.startsWith("user:") || cliAuthorize(context2, CliUsersPermissions.AuditRead);
11204
11287
  }
11288
+ function formatServerEventFields(data) {
11289
+ if (!data || typeof data !== "object") return "";
11290
+ const parts = [];
11291
+ for (const [key, value] of Object.entries(data)) {
11292
+ if (SERVER_ENVELOPE_FIELDS.has(key) || value === void 0 || value === null) continue;
11293
+ const text = typeof value === "string" ? value : JSON.stringify(value);
11294
+ parts.push(`${key}=${/\s/.test(text) ? JSON.stringify(text) : text}`);
11295
+ }
11296
+ return shorten(parts.join(" "));
11297
+ }
11298
+ function shorten(text) {
11299
+ return text.length > DATA_PREVIEW_LENGTH ? `${text.slice(0, DATA_PREVIEW_LENGTH - 1)}\u2026` : text;
11300
+ }
11205
11301
  function formatEvent(event, context2) {
11206
11302
  const { writer } = context2;
11207
- const time = new Date(event.timestamp).toISOString().slice(11, 23);
11208
- const source = event.source ? ` ${writer.wrapInColor(`[${event.source}]`, CliForegroundColor.Blue)}` : "";
11303
+ const remote = serverEventOf(event);
11304
+ let time = event.timestamp;
11305
+ let type = event.type;
11306
+ let source = event.source;
11209
11307
  let data = "";
11210
- if (event.data !== void 0) {
11308
+ if (remote) {
11309
+ const sent = remote.data?.timestamp;
11310
+ if (typeof sent === "string" && !isNaN(Date.parse(sent))) time = sent;
11311
+ type = remote.type;
11312
+ source = remote.server;
11313
+ data = formatServerEventFields(remote.data);
11314
+ } else if (event.data !== void 0) {
11211
11315
  try {
11212
11316
  data = JSON.stringify(event.data) ?? "";
11213
11317
  } catch {
11214
11318
  data = String(event.data);
11215
11319
  }
11216
- if (data.length > DATA_PREVIEW_LENGTH) {
11217
- data = `${data.slice(0, DATA_PREVIEW_LENGTH - 1)}\u2026`;
11218
- }
11320
+ data = shorten(data);
11219
11321
  }
11220
- return `${writer.wrapInColor(time, CliForegroundColor.Yellow)} ${writer.wrapInColor(event.type, CliForegroundColor.Cyan)}${source}${data ? ` ${data}` : ""}`;
11322
+ const when = new Date(time).toISOString().slice(11, 23);
11323
+ const from = source ? ` ${writer.wrapInColor(`[${source}]`, remote ? CliForegroundColor.Magenta : CliForegroundColor.Blue)}` : "";
11324
+ return `${writer.wrapInColor(when, CliForegroundColor.Yellow)} ${writer.wrapInColor(type, CliForegroundColor.Cyan)}${from}${data ? ` ${data}` : ""}`;
11325
+ }
11326
+ function filterOf(command2) {
11327
+ const server = command2.args["server"] ?? command2.args["s"];
11328
+ return {
11329
+ pattern: joinShellWords(command2.value ?? "") || "*",
11330
+ server: server === void 0 || server === true ? void 0 : String(server),
11331
+ local: !!(command2.args["local"] || command2.args["l"])
11332
+ };
11221
11333
  }
11334
+ var FILTER_PARAMETERS = [
11335
+ {
11336
+ name: "server",
11337
+ aliases: ["s"],
11338
+ type: "string",
11339
+ description: "Only the events of this server",
11340
+ required: false
11341
+ },
11342
+ {
11343
+ name: "local",
11344
+ aliases: ["l"],
11345
+ type: "boolean",
11346
+ description: "Only the terminal's own events",
11347
+ required: false
11348
+ }
11349
+ ];
11222
11350
  function busOf(context2) {
11223
11351
  if (!context2.events) {
11224
11352
  context2.writer.writeError(
@@ -11230,7 +11358,7 @@ function busOf(context2) {
11230
11358
  var CliEventsCommandProcessor = class {
11231
11359
  constructor() {
11232
11360
  this.command = "events";
11233
- this.description = "Watch, list and emit events on the event bus";
11361
+ this.description = "Watch, list and emit events of the terminal and the connected servers";
11234
11362
  this.author = DefaultLibraryAuthor;
11235
11363
  this.version = "1.0.0";
11236
11364
  this.metadata = {
@@ -11243,16 +11371,17 @@ var CliEventsCommandProcessor = class {
11243
11371
  command: "watch",
11244
11372
  description: "Print events as they happen (Ctrl+C to stop)",
11245
11373
  acceptsRawInput: true,
11374
+ parameters: FILTER_PARAMETERS,
11246
11375
  processCommand: async (command2, context2) => {
11247
11376
  const bus = busOf(context2);
11248
11377
  if (!bus) return;
11249
- const pattern = joinShellWords(command2.value ?? "") || "*";
11378
+ const filter2 = filterOf(command2);
11250
11379
  context2.writer.writeInfo(
11251
- `Watching ${context2.writer.wrapInColor(pattern, CliForegroundColor.Cyan)} \u2014 press Ctrl+C to stop`
11380
+ `Watching ${context2.writer.wrapInColor(filter2.pattern, CliForegroundColor.Cyan)} \u2014 press Ctrl+C to stop`
11252
11381
  );
11253
11382
  await new Promise((resolve) => {
11254
- const off = bus.on(pattern, (event) => {
11255
- if (canSee(event, context2)) {
11383
+ const off = bus.on("*", (event) => {
11384
+ if (matchesFilter(event, filter2) && canSee(event, context2)) {
11256
11385
  context2.writer.writeln(formatEvent(event, context2));
11257
11386
  }
11258
11387
  });
@@ -11278,6 +11407,12 @@ var CliEventsCommandProcessor = class {
11278
11407
  context2.writer.writeln(
11279
11408
  ` events watch boot:completed ${t.t("cli.events.watch.help.one_type", "One type")}`
11280
11409
  );
11410
+ context2.writer.writeln(
11411
+ ` events watch job:* -s node ${t.t("cli.events.watch.help.server_jobs", "Job events of the server node")}`
11412
+ );
11413
+ context2.writer.writeln(
11414
+ ` events watch --local ${t.t("cli.events.watch.help.local_only", "Only the terminal's own events")}`
11415
+ );
11281
11416
  }
11282
11417
  },
11283
11418
  {
@@ -11286,6 +11421,7 @@ var CliEventsCommandProcessor = class {
11286
11421
  description: "Show recent events (kept in memory, not persisted)",
11287
11422
  acceptsRawInput: true,
11288
11423
  parameters: [
11424
+ ...FILTER_PARAMETERS,
11289
11425
  {
11290
11426
  name: "limit",
11291
11427
  aliases: ["n"],
@@ -11297,13 +11433,13 @@ var CliEventsCommandProcessor = class {
11297
11433
  processCommand: async (command2, context2) => {
11298
11434
  const bus = busOf(context2);
11299
11435
  if (!bus) return;
11300
- const pattern = joinShellWords(command2.value ?? "") || "*";
11436
+ const filter2 = filterOf(command2);
11301
11437
  const limit = Number(
11302
11438
  command2.args["limit"] ?? command2.args["n"] ?? 20
11303
11439
  );
11304
- const events = bus.history(pattern).filter((event) => canSee(event, context2)).slice(-(limit > 0 ? limit : 20));
11440
+ const events = bus.history("*").filter((event) => matchesFilter(event, filter2) && canSee(event, context2)).slice(-(limit > 0 ? limit : 20));
11305
11441
  if (events.length === 0) {
11306
- context2.writer.writeInfo(`No events matching ${pattern}`);
11442
+ context2.writer.writeInfo(`No events matching ${filter2.pattern}`);
11307
11443
  return;
11308
11444
  }
11309
11445
  for (const event of events) {
@@ -11322,6 +11458,9 @@ var CliEventsCommandProcessor = class {
11322
11458
  context2.writer.writeln(
11323
11459
  ` events log server:* -n 50 ${t.t("cli.events.log.help.last_50_server", "Last 50 server events")}`
11324
11460
  );
11461
+ context2.writer.writeln(
11462
+ ` events log job:failed ${t.t("cli.events.log.help.failed_jobs", "Recent failed job runs, on every server")}`
11463
+ );
11325
11464
  }
11326
11465
  },
11327
11466
  {
@@ -11393,6 +11532,9 @@ var CliEventsCommandProcessor = class {
11393
11532
  writer.writeln(
11394
11533
  t.t("cli.events.help.patterns", "Patterns: an exact type (package:installed), a namespace (package:*) or * for all.")
11395
11534
  );
11535
+ writer.writeln(
11536
+ t.t("cli.events.help.servers", "Events of the connected servers (job runs, user, token and file changes, ...) are shown with the server name; each server sends only the events you may see. Filter with --server <name>, or --local for the terminal's own events.")
11537
+ );
11396
11538
  writer.writeln(
11397
11539
  t.t("cli.events.help.in_memory", "Events are kept in memory for this terminal only; nothing is persisted.")
11398
11540
  );
@@ -12164,6 +12306,53 @@ var themeInfos = {
12164
12306
  ...DefaultThemeInfos
12165
12307
  };
12166
12308
 
12309
+ // src/lib/processors/theme/terminal-theme.ts
12310
+ var records = /* @__PURE__ */ new WeakMap();
12311
+ function recordOf(terminal) {
12312
+ let record = records.get(terminal);
12313
+ if (!record) {
12314
+ record = { base: terminal.options.theme ?? {}, userChosen: false };
12315
+ records.set(terminal, record);
12316
+ }
12317
+ return record;
12318
+ }
12319
+ var CLI_TERMINAL_THEME_EVENT = "qcli-terminal-theme";
12320
+ function paintTerminalBackground(terminal) {
12321
+ const container = terminal.element?.closest(".terminal-container");
12322
+ if (!container) return;
12323
+ const theme = terminal.options.theme ?? {};
12324
+ container.style.background = theme.background ?? "";
12325
+ if (typeof CustomEvent === "function") {
12326
+ container.dispatchEvent(new CustomEvent(CLI_TERMINAL_THEME_EVENT, { bubbles: true, detail: theme }));
12327
+ }
12328
+ }
12329
+ function setHostTerminalTheme(terminal, theme) {
12330
+ const record = recordOf(terminal);
12331
+ record.base = theme;
12332
+ if (!record.userChosen) {
12333
+ terminal.options.theme = theme;
12334
+ paintTerminalBackground(terminal);
12335
+ }
12336
+ }
12337
+ function applyUserTerminalTheme(terminal, theme) {
12338
+ recordOf(terminal).userChosen = true;
12339
+ terminal.options.theme = theme;
12340
+ paintTerminalBackground(terminal);
12341
+ }
12342
+ function resetTerminalTheme(terminal) {
12343
+ const record = recordOf(terminal);
12344
+ record.userChosen = false;
12345
+ terminal.options.theme = { ...record.base };
12346
+ paintTerminalBackground(terminal);
12347
+ }
12348
+ function previewTerminalTheme(terminal, theme) {
12349
+ terminal.options.theme = theme;
12350
+ paintTerminalBackground(terminal);
12351
+ }
12352
+ function initTerminalTheme(terminal) {
12353
+ recordOf(terminal);
12354
+ }
12355
+
12167
12356
  // src/lib/processors/theme/cli-theme-command-processor.ts
12168
12357
  function hexToRgb(hex) {
12169
12358
  const m = /^#?([0-9A-Fa-f]{6})$/.exec(hex);
@@ -12364,15 +12553,19 @@ var CliThemeCommandProcessor = class {
12364
12553
  {
12365
12554
  onChange: (value) => {
12366
12555
  if (themes[value]) {
12367
- context2.terminal.options.theme = themes[value];
12368
- this.applyStyles(context2);
12556
+ previewTerminalTheme(
12557
+ context2.terminal,
12558
+ themes[value]
12559
+ );
12369
12560
  }
12370
12561
  }
12371
12562
  }
12372
12563
  );
12373
12564
  if (!selected) {
12374
- context2.terminal.options.theme = originalTheme;
12375
- this.applyStyles(context2);
12565
+ previewTerminalTheme(
12566
+ context2.terminal,
12567
+ originalTheme
12568
+ );
12376
12569
  context2.writer.writeInfo(
12377
12570
  "Theme selection cancelled"
12378
12571
  );
@@ -12386,13 +12579,12 @@ var CliThemeCommandProcessor = class {
12386
12579
  );
12387
12580
  return;
12388
12581
  }
12389
- context2.terminal.options.theme = themes[themeName];
12582
+ applyUserTerminalTheme(context2.terminal, themes[themeName]);
12390
12583
  context2.state.updateState({
12391
12584
  selectedTheme: themeName,
12392
12585
  customOptions: null
12393
12586
  });
12394
12587
  await context2.state.persist();
12395
- this.applyStyles(context2);
12396
12588
  context2.writer.writeSuccess(`Theme "${themeName}" applied`);
12397
12589
  }
12398
12590
  },
@@ -12428,10 +12620,10 @@ var CliThemeCommandProcessor = class {
12428
12620
  );
12429
12621
  return;
12430
12622
  }
12431
- context2.terminal.options.theme = {
12623
+ applyUserTerminalTheme(context2.terminal, {
12432
12624
  ...context2.terminal.options.theme,
12433
12625
  [key]: value
12434
- };
12626
+ });
12435
12627
  context2.state.updateState({
12436
12628
  selectedTheme: null,
12437
12629
  customOptions: context2.terminal.options.theme
@@ -12440,7 +12632,6 @@ var CliThemeCommandProcessor = class {
12440
12632
  context2.writer.writeSuccess(
12441
12633
  `Set ${key} to ${swatch} ${value}`
12442
12634
  );
12443
- this.applyStyles(context2);
12444
12635
  if (command2.args["save"]) {
12445
12636
  await this.saveTheme(context2);
12446
12637
  }
@@ -12466,10 +12657,9 @@ var CliThemeCommandProcessor = class {
12466
12657
  command: "reset",
12467
12658
  description: "Reset the theme to the default",
12468
12659
  processCommand: async (_, context2) => {
12469
- context2.terminal.options.theme = { ...this.defaultTheme };
12660
+ resetTerminalTheme(context2.terminal);
12470
12661
  context2.state.reset();
12471
12662
  await context2.state.persist();
12472
- this.applyStyles(context2);
12473
12663
  context2.writer.writeSuccess("Theme reset to default");
12474
12664
  }
12475
12665
  },
@@ -12527,13 +12717,12 @@ var CliThemeCommandProcessor = class {
12527
12717
  );
12528
12718
  }
12529
12719
  const [name, info] = candidates[Math.floor(Math.random() * candidates.length)];
12530
- context2.terminal.options.theme = info.theme;
12720
+ applyUserTerminalTheme(context2.terminal, info.theme);
12531
12721
  context2.state.updateState({
12532
12722
  selectedTheme: name,
12533
12723
  customOptions: null
12534
12724
  });
12535
12725
  await context2.state.persist();
12536
- this.applyStyles(context2);
12537
12726
  context2.writer.writeSuccess(
12538
12727
  `Random theme "${name}" applied`
12539
12728
  );
@@ -12584,13 +12773,12 @@ var CliThemeCommandProcessor = class {
12584
12773
  );
12585
12774
  return;
12586
12775
  }
12587
- context2.terminal.options.theme = imported;
12776
+ applyUserTerminalTheme(context2.terminal, imported);
12588
12777
  context2.state.updateState({
12589
12778
  selectedTheme: null,
12590
12779
  customOptions: imported
12591
12780
  });
12592
12781
  await context2.state.persist();
12593
- this.applyStyles(context2);
12594
12782
  context2.writer.writeSuccess(
12595
12783
  "Custom theme imported and applied"
12596
12784
  );
@@ -12599,8 +12787,7 @@ var CliThemeCommandProcessor = class {
12599
12787
  ];
12600
12788
  }
12601
12789
  async initialize(context2) {
12602
- this.defaultTheme = context2.terminal.options.theme;
12603
- if (context2.options?.theme !== void 0) return;
12790
+ initTerminalTheme(context2.terminal);
12604
12791
  this.applySavedTheme(context2, false);
12605
12792
  context2.state.select(
12606
12793
  (state) => JSON.stringify([
@@ -12612,17 +12799,14 @@ var CliThemeCommandProcessor = class {
12612
12799
  /** Apply the saved theme (or the default one when `reset` and none is saved). */
12613
12800
  applySavedTheme(context2, reset) {
12614
12801
  const state = context2.state.getState();
12615
- if (state.selectedTheme && state.selectedTheme !== "default") {
12616
- const theme = themes[state.selectedTheme];
12617
- if (theme) {
12618
- context2.terminal.options.theme = theme;
12619
- }
12620
- } else if (state.customOptions) {
12621
- context2.terminal.options.theme = state.customOptions;
12802
+ const saved = state.selectedTheme && state.selectedTheme !== "default" ? themes[state.selectedTheme] : state.customOptions;
12803
+ if (saved) {
12804
+ applyUserTerminalTheme(context2.terminal, saved);
12622
12805
  } else if (reset) {
12623
- context2.terminal.options.theme = { ...this.defaultTheme };
12806
+ resetTerminalTheme(context2.terminal);
12807
+ } else {
12808
+ paintTerminalBackground(context2.terminal);
12624
12809
  }
12625
- this.applyStyles(context2);
12626
12810
  }
12627
12811
  async processCommand(command2, context2) {
12628
12812
  context2.executor.showHelp(command2, context2);
@@ -12701,12 +12885,6 @@ var CliThemeCommandProcessor = class {
12701
12885
  );
12702
12886
  }
12703
12887
  }
12704
- applyStyles(context2) {
12705
- const parents = document.getElementsByClassName("terminal-container");
12706
- for (const parent of Array.from(parents)) {
12707
- parent.style.background = context2.terminal.options.theme?.background || this.defaultTheme.background;
12708
- }
12709
- }
12710
12888
  async saveTheme(context2) {
12711
12889
  await context2.state.persist();
12712
12890
  context2.writer.writeSuccess("Theme saved");
@@ -13207,6 +13385,7 @@ var CliEnvCommandProcessor = class {
13207
13385
  var MIN_FONT_SIZE = 8;
13208
13386
  var MAX_FONT_SIZE = 40;
13209
13387
  var STEP = 2;
13388
+ var hostFontSizes = /* @__PURE__ */ new WeakMap();
13210
13389
  var CliFontSizeCommandProcessor = class {
13211
13390
  constructor() {
13212
13391
  this.command = "font-size";
@@ -13261,14 +13440,16 @@ var CliFontSizeCommandProcessor = class {
13261
13440
  ];
13262
13441
  }
13263
13442
  async initialize(context2) {
13264
- this.hostFontSize = context2.terminal?.options?.fontSize;
13443
+ if (context2.terminal) {
13444
+ hostFontSizes.set(context2.terminal, context2.terminal.options?.fontSize);
13445
+ }
13265
13446
  const state = context2.state.getState();
13266
13447
  if (state?.fontSize != null && context2.terminal?.options) {
13267
13448
  context2.terminal.options.fontSize = state.fontSize;
13268
13449
  }
13269
13450
  context2.state.select((s) => s.fontSize ?? null).pipe(skip(1)).subscribe((fontSize) => {
13270
13451
  if (!context2.terminal?.options) return;
13271
- context2.terminal.options.fontSize = fontSize ?? this.hostFontSize ?? context2.terminal.options.fontSize;
13452
+ context2.terminal.options.fontSize = fontSize ?? this.getDefault(context2);
13272
13453
  });
13273
13454
  }
13274
13455
  async processCommand(_, context2) {
@@ -13286,7 +13467,7 @@ var CliFontSizeCommandProcessor = class {
13286
13467
  ` ${context2.writer.wrapInColor("font-size set <n>", CliForegroundColor.Cyan)} Set to exact size`
13287
13468
  );
13288
13469
  context2.writer.writeln(
13289
- ` ${context2.writer.wrapInColor("font-size reset", CliForegroundColor.Cyan)} Reset to default (${this.getDefault()}px)`
13470
+ ` ${context2.writer.wrapInColor("font-size reset", CliForegroundColor.Cyan)} Reset to default (${this.getDefault(context2)}px)`
13290
13471
  );
13291
13472
  }
13292
13473
  writeDescription(context2) {
@@ -13319,13 +13500,13 @@ var CliFontSizeCommandProcessor = class {
13319
13500
  return fromTerminal;
13320
13501
  }
13321
13502
  const state = context2.state.getState();
13322
- return state?.fontSize ?? this.getDefault();
13503
+ return state?.fontSize ?? this.getDefault(context2);
13323
13504
  }
13324
- getDefault() {
13325
- return this.hostFontSize ?? defaultFontSize();
13505
+ getDefault(context2) {
13506
+ return (context2.terminal && hostFontSizes.get(context2.terminal)) ?? defaultFontSize();
13326
13507
  }
13327
13508
  async resetFontSize(context2) {
13328
- const size = this.getDefault();
13509
+ const size = this.getDefault(context2);
13329
13510
  if (context2.terminal?.options) {
13330
13511
  context2.terminal.options.fontSize = size;
13331
13512
  }
@@ -21959,73 +22140,6 @@ function createServerModule() {
21959
22140
  services: []
21960
22141
  };
21961
22142
  }
21962
- var CLI_EVENT_HISTORY_SIZE = 200;
21963
- function matchesEventPattern(type, pattern) {
21964
- if (pattern === "*") return true;
21965
- if (pattern.endsWith(":*")) {
21966
- return type.startsWith(pattern.slice(0, -1));
21967
- }
21968
- return type === pattern;
21969
- }
21970
- var CliEventBus = class {
21971
- constructor(onHandlerError = (error) => console.error("[CLI] Event handler failed:", error), historySize = CLI_EVENT_HISTORY_SIZE) {
21972
- this.onHandlerError = onHandlerError;
21973
- this.historySize = historySize;
21974
- this.subject = new Subject();
21975
- this.recent = [];
21976
- this.disposed = false;
21977
- }
21978
- emit(type, data, options) {
21979
- if (this.disposed) return;
21980
- const event = {
21981
- type,
21982
- data,
21983
- source: options?.source,
21984
- timestamp: Date.now()
21985
- };
21986
- this.recent.push(event);
21987
- if (this.recent.length > this.historySize) {
21988
- this.recent.shift();
21989
- }
21990
- this.subject.next(event);
21991
- }
21992
- on(pattern, handler) {
21993
- const subscription = this.events$(pattern).subscribe((event) => {
21994
- try {
21995
- handler(event);
21996
- } catch (error) {
21997
- this.onHandlerError(error, event);
21998
- }
21999
- });
22000
- return () => subscription.unsubscribe();
22001
- }
22002
- once(pattern, handler) {
22003
- let done = false;
22004
- const unsubscribe = this.on(pattern, (event) => {
22005
- if (done) return;
22006
- done = true;
22007
- unsubscribe();
22008
- handler(event);
22009
- });
22010
- return unsubscribe;
22011
- }
22012
- events$(pattern = "*") {
22013
- return this.subject.asObservable().pipe(filter((event) => matchesEventPattern(event.type, pattern)));
22014
- }
22015
- history(pattern = "*", limit) {
22016
- const matching = this.recent.filter(
22017
- (event) => matchesEventPattern(event.type, pattern)
22018
- );
22019
- return limit !== void 0 ? matching.slice(-limit) : matching;
22020
- }
22021
- /** Stop delivering events and complete all observables. */
22022
- dispose() {
22023
- if (this.disposed) return;
22024
- this.disposed = true;
22025
- this.subject.complete();
22026
- this.recent.length = 0;
22027
- }
22028
- };
22029
22143
 
22030
22144
  // src/lib/editor/syntax/registry.ts
22031
22145
  var SyntaxHighlighterRegistry = class {
@@ -23127,12 +23241,13 @@ var CliEngine = class {
23127
23241
  });
23128
23242
  }
23129
23243
  /**
23130
- * Restyle the terminal: its colors and the background around it.
23131
- * The `theme` command can still change it afterwards.
23244
+ * The host's theme for this terminal: its colors and the background around it.
23245
+ * A theme the user chose with the `theme` command stays until `theme reset`,
23246
+ * which returns to this one.
23132
23247
  */
23133
23248
  setTheme(theme) {
23134
- if (this.terminal) this.terminal.options.theme = theme;
23135
- this.container.style.background = theme.background ?? "";
23249
+ if (this.terminal) setHostTerminalTheme(this.terminal, theme);
23250
+ else this.container.style.background = theme.background ?? "";
23136
23251
  }
23137
23252
  /**
23138
23253
  * Get the underlying xterm.js Terminal instance.
@@ -23237,6 +23352,8 @@ var CliEngine = class {
23237
23352
  window.addEventListener("resize", this.resizeListener);
23238
23353
  this.resizeObserver = new ResizeObserver(() => this.safeFit());
23239
23354
  this.resizeObserver.observe(this.container);
23355
+ const screen2 = this.terminal?.element?.querySelector(".xterm-screen");
23356
+ if (screen2) this.resizeObserver.observe(screen2);
23240
23357
  }
23241
23358
  /**
23242
23359
  * Call fitAddon.fit() only when the container has non-zero dimensions.
@@ -23983,6 +24100,9 @@ function sameJson(a, b) {
23983
24100
  }
23984
24101
  var PANE_RESIZING_BODY_CLASS = "cli-pane-resizing";
23985
24102
  var DARK_SCHEME_QUERY = "(prefers-color-scheme: dark)";
24103
+ function asEventTarget(node) {
24104
+ return node && typeof node.addEventListener === "function" ? node : null;
24105
+ }
23986
24106
  function followsSystem(input) {
23987
24107
  return input === "auto" || typeof input === "object" && input !== null && input.base === "auto";
23988
24108
  }
@@ -24057,6 +24177,13 @@ var CliPanelController = class {
24057
24177
  this.cancelRename();
24058
24178
  };
24059
24179
  this.onSystemSchemeChange = () => this.applyTheme();
24180
+ /** A terminal in the panel changed theme: follow the first one. */
24181
+ this.onTerminalTheme = (event) => {
24182
+ if (!this.followsTerminalTheme) return;
24183
+ const first = this.rootElement?.querySelector(".terminal-container");
24184
+ if (event.target !== first) return;
24185
+ this.syncThemeFromEngine(event.detail);
24186
+ };
24060
24187
  this.onVisualViewportChange = () => {
24061
24188
  const fields = visualViewportFields();
24062
24189
  const s = this.snapshot;
@@ -24129,7 +24256,7 @@ var CliPanelController = class {
24129
24256
  return;
24130
24257
  }
24131
24258
  this.commit(fields);
24132
- if (fields.syncTheme) this.startThemeSync();
24259
+ if (this.followsTerminalTheme) this.startThemeSync();
24133
24260
  else this.stopThemeSync();
24134
24261
  }
24135
24262
  /**
@@ -24140,6 +24267,15 @@ var CliPanelController = class {
24140
24267
  this.themeInput = theme;
24141
24268
  this.watchSystemScheme();
24142
24269
  this.applyTheme();
24270
+ if (this.followsTerminalTheme) this.startThemeSync();
24271
+ else this.stopThemeSync();
24272
+ }
24273
+ /**
24274
+ * Whether the panel's colors follow its first terminal: with `syncTheme`, or
24275
+ * with a host theme so a theme the user applies restyles the panel too.
24276
+ */
24277
+ get followsTerminalTheme() {
24278
+ return this.snapshot.syncTheme || this.themeInput !== void 0;
24143
24279
  }
24144
24280
  /** Replace the change callbacks (wrappers call this when their props change). */
24145
24281
  setCallbacks(callbacks) {
@@ -24155,8 +24291,11 @@ var CliPanelController = class {
24155
24291
  }
24156
24292
  /** Element containing the terminals, used for theme sync. */
24157
24293
  setRootElement(element) {
24294
+ if (element === this.rootElement) return;
24295
+ asEventTarget(this.rootElement)?.removeEventListener(CLI_TERMINAL_THEME_EVENT, this.onTerminalTheme);
24158
24296
  this.rootElement = element;
24159
- if (element && this.snapshot.syncTheme) this.startThemeSync();
24297
+ asEventTarget(element)?.addEventListener(CLI_TERMINAL_THEME_EVENT, this.onTerminalTheme);
24298
+ if (element && this.followsTerminalTheme) this.startThemeSync();
24160
24299
  }
24161
24300
  /**
24162
24301
  * Stop timers, observers and document listeners. Engines are not
@@ -24384,10 +24523,11 @@ var CliPanelController = class {
24384
24523
  /** Called by the view once a pane's terminal engine is running. */
24385
24524
  registerEngine(tabId, paneId, engine) {
24386
24525
  if (!this.findTab(tabId)) return;
24526
+ const known = this.engines.get(paneId) === engine;
24387
24527
  this.engines.set(paneId, engine);
24388
24528
  this.tracker.registerEngine(tabId, paneId, engine);
24389
- if (this.snapshot.terminalTheme) engine.setTheme(this.snapshot.terminalTheme);
24390
- if (this.snapshot.syncTheme) this.startThemeSync();
24529
+ if (this.snapshot.terminalTheme && !known) engine.setTheme(this.snapshot.terminalTheme);
24530
+ if (this.followsTerminalTheme) this.startThemeSync();
24391
24531
  }
24392
24532
  getEngine(paneId) {
24393
24533
  return this.engines.get(paneId ?? this.snapshot.activePaneId);
@@ -24532,11 +24672,12 @@ var CliPanelController = class {
24532
24672
  this.documentListenersAttached = false;
24533
24673
  }
24534
24674
  /* ─── Theme sync ────────────────────────────────────────── */
24535
- /** Read the first terminal's theme and derive the panel colours from it. */
24536
- syncThemeFromEngine() {
24537
- const engine = this.engines.values().next().value;
24538
- if (!engine) return;
24539
- const theme = engine.getTerminal().options.theme;
24675
+ /** Read the first terminal's theme (or use `theme`) and derive the panel colours from it. */
24676
+ syncThemeFromEngine(theme) {
24677
+ if (!theme) {
24678
+ const engine = this.engines.values().next().value;
24679
+ theme = engine?.getTerminal().options.theme;
24680
+ }
24540
24681
  if (!theme) return;
24541
24682
  const styles = sameTerminalTheme(theme, this.resolvedTheme.terminal) ? this.resolvedTheme.styles : derivePanelThemeStyles(theme);
24542
24683
  if (sameRecord(styles, this.snapshot.themeStyles)) return;
@@ -24563,7 +24704,7 @@ var CliPanelController = class {
24563
24704
  const themeStyles = this.themeInput === void 0 ? {} : resolved.styles;
24564
24705
  this.commit({ themeStyles, scheme: resolved.scheme, terminalTheme });
24565
24706
  if (terminalChanged) this.engines.forEach((engine) => engine.setTheme(terminalTheme));
24566
- if (this.snapshot.syncTheme) this.syncThemeFromEngine();
24707
+ if (this.followsTerminalTheme) this.syncThemeFromEngine();
24567
24708
  }
24568
24709
  /** Follow the system's light/dark setting while the theme is 'auto'. */
24569
24710
  watchSystemScheme() {
@@ -24662,7 +24803,7 @@ var CliPanelController = class {
24662
24803
  if (this.themeObserver) {
24663
24804
  this.stopThemeSync();
24664
24805
  setTimeout(() => {
24665
- if (this.snapshot.syncTheme && !this.themeObserver) this.startThemeSync();
24806
+ if (this.followsTerminalTheme && !this.themeObserver) this.startThemeSync();
24666
24807
  }, 0);
24667
24808
  }
24668
24809
  try {