@qodalis/cli 2.1.0 → 2.2.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
@@ -1,4 +1,4 @@
1
- import { createCliToken, CliLogLevel, DefaultThemes, DefaultThemeInfos, CliForegroundColor, DefaultLibraryAuthor, CliIcon, writeWrapped, LIBRARY_VERSION as LIBRARY_VERSION$1, writeUsage, writeExamples, writeHint, joinShellWords, CliKeyData, unquoteRawArgument, resolveConfigurationCategories, ICliTranslationService_TOKEN, splitShellWords, cliAuthorize, CliUsersPermissions, ICliAuthorizationService_TOKEN, quoteShellWord, resolveHeaders, appendQueryParams, buildServerWebSocketUrl, ServerVersionNegotiator, CliHttpError, formatJson, visibleLength, ICliEventBus_TOKEN, stripAnsi, CancellablePromise, isCliAccessDeniedError, getParameterValue, ICliCommandGuard_TOKEN, getRightOfWord, CLI_CONFIGURE_STORE_NAME, CliModuleRegistry, initializeBrowserEnvironment, ICliPermissionRegistry_TOKEN, satisfiesVersionRange, CliServerPermissions, ICliDragDropService_TOKEN, cliAccessMetadataGuards, ICliServerAuthService_TOKEN, loadPanelPosition, savePanelPosition, derivePanelThemeStyles, delay, colorFirstWord, clearTerminalLine, ICliCompletionProvider_TOKEN, ICliServerAuthTokenProvider_TOKEN, truncateToWidth } from '@qodalis/cli-core';
1
+ import { createCliToken, CliLogLevel, DefaultThemes, DefaultThemeInfos, CliForegroundColor, DefaultLibraryAuthor, CliIcon, writeWrapped, LIBRARY_VERSION as LIBRARY_VERSION$1, writeUsage, writeExamples, writeHint, joinShellWords, CliKeyData, unquoteRawArgument, resolveConfigurationCategories, ICliTranslationService_TOKEN, splitShellWords, cliAuthorize, CliUsersPermissions, ICliAuthorizationService_TOKEN, quoteShellWord, resolveHeaders, appendQueryParams, buildServerWebSocketUrl, ServerVersionNegotiator, CliHttpError, formatJson, visibleLength, ICliEventBus_TOKEN, stripAnsi, CancellablePromise, isCliAccessDeniedError, getParameterValue, ICliCommandGuard_TOKEN, getRightOfWord, CLI_CONFIGURE_STORE_NAME, CliModuleRegistry, initializeBrowserEnvironment, ICliPermissionRegistry_TOKEN, satisfiesVersionRange, CliServerPermissions, ICliDragDropService_TOKEN, cliAccessMetadataGuards, resolvePanelTheme, prefersDarkScheme, ICliServerAuthService_TOKEN, loadPanelPosition, savePanelPosition, derivePanelThemeStyles, delay, colorFirstWord, clearTerminalLine, ICliCompletionProvider_TOKEN, ICliServerAuthTokenProvider_TOKEN, truncateToWidth } from '@qodalis/cli-core';
2
2
  export { ICliUserSessionService_TOKEN, ICliUsersStoreService_TOKEN } from '@qodalis/cli-core';
3
3
  import { Terminal } from '@xterm/xterm';
4
4
  import { FitAddon } from '@xterm/addon-fit';
@@ -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.1.0";
521
+ var LIBRARY_VERSION = "2.2.0";
522
522
  var API_VERSION = 2;
523
523
  var ANONYMOUS = "\0anonymous";
524
524
  function aliasOwner(context2) {
@@ -5397,6 +5397,7 @@ var CliServerConnection = class _CliServerConnection {
5397
5397
  this._eventServiceRegistered = false;
5398
5398
  this._healthCheckTimer = null;
5399
5399
  this._healthCheckAttempts = 0;
5400
+ this._needsSignIn = false;
5400
5401
  }
5401
5402
  static {
5402
5403
  this.WS_MAX_RECONNECT_ATTEMPTS = 5;
@@ -5413,6 +5414,13 @@ var CliServerConnection = class _CliServerConnection {
5413
5414
  get config() {
5414
5415
  return this._config;
5415
5416
  }
5417
+ /**
5418
+ * The last connect was refused for want of a sign-in (401 or 403 on the
5419
+ * command list): the server is up, and a sign-in reconnects it.
5420
+ */
5421
+ get needsSignIn() {
5422
+ return this._needsSignIn;
5423
+ }
5416
5424
  get connected() {
5417
5425
  return this._connected;
5418
5426
  }
@@ -5452,6 +5460,7 @@ var CliServerConnection = class _CliServerConnection {
5452
5460
  });
5453
5461
  }
5454
5462
  async connect() {
5463
+ this._needsSignIn = false;
5455
5464
  try {
5456
5465
  const baseUrl = this.normalizeUrl(this._config.url);
5457
5466
  const negotiated = await ServerVersionNegotiator.discover(
@@ -5534,6 +5543,7 @@ var CliServerConnection = class _CliServerConnection {
5534
5543
  const url = `${this._basePath}/commands`;
5535
5544
  const response = await this.httpFetch(url);
5536
5545
  if (!response.ok) {
5546
+ if (response.status === 401 || response.status === 403) this._needsSignIn = true;
5537
5547
  throw new Error(
5538
5548
  `Server ${this._config.name} returned ${response.status}`
5539
5549
  );
@@ -6224,6 +6234,10 @@ var CliServerManager = class {
6224
6234
  );
6225
6235
  this.registerProxyProcessors(connection, config.name);
6226
6236
  this.emitConnected(connection, false);
6237
+ } else if (connection.needsSignIn) {
6238
+ logger?.info(
6239
+ `Server '${config.name}' needs a sign-in; its commands load once you are signed in.`
6240
+ );
6227
6241
  } else {
6228
6242
  logger?.warn(
6229
6243
  `Could not connect to server '${config.name}' at ${config.url}. Commands from this server will not be available.`
@@ -12586,6 +12600,7 @@ var CliThemeCommandProcessor = class {
12586
12600
  }
12587
12601
  async initialize(context2) {
12588
12602
  this.defaultTheme = context2.terminal.options.theme;
12603
+ if (context2.options?.theme !== void 0) return;
12589
12604
  this.applySavedTheme(context2, false);
12590
12605
  context2.state.select(
12591
12606
  (state) => JSON.stringify([
@@ -19293,8 +19308,9 @@ var welcomeModule = {
19293
19308
  );
19294
19309
  await configureStore.initialize();
19295
19310
  const state = configureStore.getState();
19296
- if (state?.["system"]?.["welcomeMessage"]) {
19297
- showOption = state["system"]["welcomeMessage"];
19311
+ const stored = state?.["system"]?.["welcomeMessage"];
19312
+ if (stored && stored !== "always") {
19313
+ showOption = stored;
19298
19314
  }
19299
19315
  }
19300
19316
  if (showOption) {
@@ -19307,10 +19323,11 @@ var welcomeModule = {
19307
19323
  context2.terminal.writeln(config.message);
19308
19324
  } else {
19309
19325
  const branding = resolveCliBranding(context2.options?.branding);
19326
+ const art = branding.art(context2.terminal.cols);
19310
19327
  const lines = [
19311
19328
  `\u{1F680} Welcome to ${branding.name} [Version ${context2.writer.wrapInColor(LIBRARY_VERSION, CliForegroundColor.Green)}]`,
19312
19329
  `(c) ${(/* @__PURE__ */ new Date()).getFullYear()} Qodalis Solutions. All rights reserved.`,
19313
- branding.art(context2.terminal.cols),
19330
+ art,
19314
19331
  "",
19315
19332
  `\u{1F4D6} ${context2.writer.wrapInColor("Documentation:", CliForegroundColor.Green)} https://cli.qodalis.com/docs/`,
19316
19333
  "",
@@ -19318,7 +19335,10 @@ var welcomeModule = {
19318
19335
  ""
19319
19336
  ];
19320
19337
  lines.forEach((line2) => {
19321
- context2.terminal.write(line2 + "\r\n");
19338
+ const wrapped = line2 === art ? [line2] : wrapWelcomeLine(line2, context2.terminal.cols);
19339
+ wrapped.forEach(
19340
+ (part) => context2.terminal.write(part + "\r\n")
19341
+ );
19322
19342
  });
19323
19343
  }
19324
19344
  recordWelcomeMessageDisplay();
@@ -19350,12 +19370,51 @@ var greetingModule = {
19350
19370
  if (host.typedDuringBoot || host.isReadingInput || context2.currentLine) {
19351
19371
  return;
19352
19372
  }
19353
- await context2.textAnimator?.showText(getGreetingBasedOnTime(), {
19373
+ const buffer = context2.terminal.buffer?.active;
19374
+ const room = context2.terminal.cols - (buffer?.cursorX ?? 0) - 1;
19375
+ const greeting = fitGreeting(getGreetingBasedOnTime(), room);
19376
+ if (!greeting) return;
19377
+ await context2.textAnimator?.showText(greeting, {
19354
19378
  speed: 60,
19355
19379
  removeAfterTyping: true
19356
19380
  });
19357
19381
  }
19358
19382
  };
19383
+ function fitGreeting(text, room) {
19384
+ const width = (value) => Array.from(value).reduce(
19385
+ (sum, ch) => sum + (ch.codePointAt(0) > 65535 || /\p{Extended_Pictographic}/u.test(ch) ? 2 : ch === "\uFE0F" ? 0 : 1),
19386
+ 0
19387
+ );
19388
+ if (width(text) <= room) return text;
19389
+ const words = text.split(" ");
19390
+ let fitted = "";
19391
+ for (const word of words) {
19392
+ const next = fitted ? `${fitted} ${word}` : word;
19393
+ if (width(next) > room) break;
19394
+ fitted = next;
19395
+ }
19396
+ return fitted.split(" ").length >= 3 ? fitted.replace(/[,;:]$/, "") : null;
19397
+ }
19398
+ function wrapWelcomeLine(line2, cols) {
19399
+ const width = Math.max(20, cols - 1);
19400
+ const visible = (value) => Array.from(value.replace(/\x1b\[[0-9;]*m/g, "")).reduce(
19401
+ (sum, ch) => sum + (ch.codePointAt(0) > 65535 ? 2 : ch === "\uFE0F" ? 0 : 1),
19402
+ 0
19403
+ );
19404
+ if (visible(line2) <= width) return [line2];
19405
+ const lines = [];
19406
+ let current = "";
19407
+ for (const word of line2.split(" ")) {
19408
+ if (current && visible(current) + 1 + visible(word) > width) {
19409
+ lines.push(current);
19410
+ current = word;
19411
+ } else {
19412
+ current = current ? `${current} ${word}` : word;
19413
+ }
19414
+ }
19415
+ if (current) lines.push(current);
19416
+ return lines;
19417
+ }
19359
19418
  function shouldDisplayWelcomeMessage(showOption) {
19360
19419
  const lastDisplayed = localStorage.getItem(
19361
19420
  "cliWelcomeMessageLastDisplayed"
@@ -22812,6 +22871,9 @@ var CliEngine = class {
22812
22871
  this.terminalOptions = getTerminalOptions(
22813
22872
  this.options?.terminalOptions
22814
22873
  );
22874
+ if (this.options?.theme !== void 0 && !this.options?.terminalOptions?.theme) {
22875
+ this.terminalOptions.theme = resolvePanelTheme(this.options.theme, prefersDarkScheme()).terminal;
22876
+ }
22815
22877
  const setup = initializeTerminal(
22816
22878
  this.container,
22817
22879
  this.terminalOptions,
@@ -23064,6 +23126,14 @@ var CliEngine = class {
23064
23126
  this.terminal?.focus();
23065
23127
  });
23066
23128
  }
23129
+ /**
23130
+ * Restyle the terminal: its colors and the background around it.
23131
+ * The `theme` command can still change it afterwards.
23132
+ */
23133
+ setTheme(theme) {
23134
+ if (this.terminal) this.terminal.options.theme = theme;
23135
+ this.container.style.background = theme.background ?? "";
23136
+ }
23067
23137
  /**
23068
23138
  * Get the underlying xterm.js Terminal instance.
23069
23139
  */
@@ -23912,6 +23982,23 @@ function sameJson(a, b) {
23912
23982
  return JSON.stringify(a) === JSON.stringify(b);
23913
23983
  }
23914
23984
  var PANE_RESIZING_BODY_CLASS = "cli-pane-resizing";
23985
+ var DARK_SCHEME_QUERY = "(prefers-color-scheme: dark)";
23986
+ function followsSystem(input) {
23987
+ return input === "auto" || typeof input === "object" && input !== null && input.base === "auto";
23988
+ }
23989
+ function themeKey(input) {
23990
+ return input === void 0 ? "" : JSON.stringify(input);
23991
+ }
23992
+ function sameTerminalTheme(a, b) {
23993
+ return !!a && !!b && a.background === b.background && a.foreground === b.foreground && a.cursor === b.cursor;
23994
+ }
23995
+ var MIN_KEYBOARD_INSET = 80;
23996
+ function visualViewportFields() {
23997
+ const vv = typeof window !== "undefined" ? window.visualViewport : null;
23998
+ if (!vv) return { keyboardInset: 0, visibleHeight: 0 };
23999
+ const inset = Math.round(window.innerHeight - vv.height - vv.offsetTop);
24000
+ return { keyboardInset: inset >= MIN_KEYBOARD_INSET ? inset : 0, visibleHeight: Math.round(vv.height) };
24001
+ }
23915
24002
  function defaultViewport() {
23916
24003
  if (typeof window === "undefined") return { width: 0, height: 0 };
23917
24004
  return { width: window.innerWidth, height: window.innerHeight };
@@ -23930,6 +24017,9 @@ var CliPanelController = class {
23930
24017
  /* ─── Subscription ──────────────────────────────────────── */
23931
24018
  this.subscribe = (listener) => {
23932
24019
  this.listeners.add(listener);
24020
+ this.watchSystemScheme();
24021
+ this.watchVisualViewport();
24022
+ this.onVisualViewportChange();
23933
24023
  return () => {
23934
24024
  this.listeners.delete(listener);
23935
24025
  };
@@ -23966,11 +24056,22 @@ var CliPanelController = class {
23966
24056
  this.closeMenus();
23967
24057
  this.cancelRename();
23968
24058
  };
24059
+ this.onSystemSchemeChange = () => this.applyTheme();
24060
+ this.onVisualViewportChange = () => {
24061
+ const fields = visualViewportFields();
24062
+ const s = this.snapshot;
24063
+ if (fields.keyboardInset === s.keyboardInset && fields.visibleHeight === s.visibleHeight) return;
24064
+ this.commit(fields);
24065
+ };
23969
24066
  const config = options.config ?? {};
23970
24067
  this.callbacks = options.callbacks ?? {};
23971
24068
  this.controlled = options.controlled ?? {};
23972
24069
  this.viewport = options.viewport ?? defaultViewport;
23973
24070
  this.persistPosition = options.persistPosition ?? true;
24071
+ this.themeInput = config.theme;
24072
+ this.resolvedTheme = resolvePanelTheme(config.theme, prefersDarkScheme());
24073
+ this.watchSystemScheme();
24074
+ this.watchVisualViewport();
23974
24075
  this.internal = {
23975
24076
  collapsed: config.isCollapsed ?? true,
23976
24077
  hidden: config.isHidden ?? false,
@@ -23997,7 +24098,11 @@ var CliPanelController = class {
23997
24098
  isHorizontal: false,
23998
24099
  showStatusIndicators: true,
23999
24100
  panelResizing: false,
24000
- themeStyles: {},
24101
+ // Without a host theme the stylesheet's colors (and its overrides) apply
24102
+ themeStyles: config.theme === void 0 ? {} : this.resolvedTheme.styles,
24103
+ scheme: this.resolvedTheme.scheme,
24104
+ terminalTheme: config.theme === void 0 ? void 0 : this.resolvedTheme.terminal,
24105
+ ...visualViewportFields(),
24001
24106
  ...this.configFields(config),
24002
24107
  tabs: [],
24003
24108
  activeTabId: 0,
@@ -24015,8 +24120,9 @@ var CliPanelController = class {
24015
24120
  formattedUptime: formatUptime2(0)
24016
24121
  });
24017
24122
  }
24018
- /** Apply a new panel configuration (chrome buttons, theme sync). */
24123
+ /** Apply a new panel configuration (chrome buttons, theme, theme sync). */
24019
24124
  setOptions(config) {
24125
+ if (themeKey(config?.theme) !== themeKey(this.themeInput)) this.setTheme(config?.theme);
24020
24126
  const fields = this.configFields(config ?? {});
24021
24127
  const s = this.snapshot;
24022
24128
  if (fields.closable === s.closable && fields.resizable === s.resizable && fields.hideable === s.hideable && fields.hideAlignment === s.hideAlignment && fields.syncTheme === s.syncTheme) {
@@ -24026,6 +24132,15 @@ var CliPanelController = class {
24026
24132
  if (fields.syncTheme) this.startThemeSync();
24027
24133
  else this.stopThemeSync();
24028
24134
  }
24135
+ /**
24136
+ * Restyle the panel and every terminal in it (see `CliPanelConfig.theme`).
24137
+ * `undefined` returns to the default palette and leaves the terminals' theme alone.
24138
+ */
24139
+ setTheme(theme) {
24140
+ this.themeInput = theme;
24141
+ this.watchSystemScheme();
24142
+ this.applyTheme();
24143
+ }
24029
24144
  /** Replace the change callbacks (wrappers call this when their props change). */
24030
24145
  setCallbacks(callbacks) {
24031
24146
  this.callbacks = callbacks;
@@ -24051,6 +24166,9 @@ var CliPanelController = class {
24051
24166
  destroy() {
24052
24167
  this.tracker.destroy();
24053
24168
  this.stopThemeSync();
24169
+ this.schemeQuery?.removeEventListener("change", this.onSystemSchemeChange);
24170
+ this.schemeQuery = void 0;
24171
+ this.unwatchVisualViewport();
24054
24172
  this.endPanelResize();
24055
24173
  this.endPaneResize();
24056
24174
  this.detachDocumentListeners();
@@ -24087,7 +24205,9 @@ var CliPanelController = class {
24087
24205
  unhide() {
24088
24206
  if (!this.snapshot.hidden) return;
24089
24207
  this.updateHidden(false);
24090
- if (this.snapshot.collapsed !== this.preHideCollapsed) this.updateCollapsed(this.preHideCollapsed);
24208
+ const collapsed = this.snapshot.initialized ? this.preHideCollapsed : false;
24209
+ if (this.snapshot.collapsed !== collapsed) this.updateCollapsed(collapsed);
24210
+ if (!collapsed) this.ensureInitialized();
24091
24211
  }
24092
24212
  toggleHide() {
24093
24213
  if (this.snapshot.hidden) this.unhide();
@@ -24266,6 +24386,7 @@ var CliPanelController = class {
24266
24386
  if (!this.findTab(tabId)) return;
24267
24387
  this.engines.set(paneId, engine);
24268
24388
  this.tracker.registerEngine(tabId, paneId, engine);
24389
+ if (this.snapshot.terminalTheme) engine.setTheme(this.snapshot.terminalTheme);
24269
24390
  if (this.snapshot.syncTheme) this.startThemeSync();
24270
24391
  }
24271
24392
  getEngine(paneId) {
@@ -24343,7 +24464,7 @@ var CliPanelController = class {
24343
24464
  });
24344
24465
  }
24345
24466
  /* ─── Drag resizing ─────────────────────────────────────── */
24346
- /** Mouse down on the panel's resize bar. */
24467
+ /** Pointer (mouse, touch or pen) down on the panel's resize bar. */
24347
24468
  startPanelResize(clientX, clientY) {
24348
24469
  if (!this.snapshot.resizable || typeof document === "undefined") return;
24349
24470
  if (this.snapshot.collapsed) this.toggle();
@@ -24354,10 +24475,11 @@ var CliPanelController = class {
24354
24475
  startHeight: this.snapshot.height
24355
24476
  };
24356
24477
  this.commit({ panelResizing: true });
24357
- document.addEventListener("mousemove", this.onPanelResizeMove);
24358
- document.addEventListener("mouseup", this.onPanelResizeEnd);
24478
+ document.addEventListener("pointermove", this.onPanelResizeMove);
24479
+ document.addEventListener("pointerup", this.onPanelResizeEnd);
24480
+ document.addEventListener("pointercancel", this.onPanelResizeEnd);
24359
24481
  }
24360
- /** Mouse down on the divider before pane `dividerIndex + 1` of a tab. */
24482
+ /** Pointer down on the divider before pane `dividerIndex + 1` of a tab. */
24361
24483
  startPaneResize(tabId, dividerIndex, clientX, containerWidth) {
24362
24484
  const tab = this.findTab(tabId);
24363
24485
  if (!tab || typeof document === "undefined") return;
@@ -24370,22 +24492,25 @@ var CliPanelController = class {
24370
24492
  };
24371
24493
  this.commit({ paneResizing: true });
24372
24494
  document.body.classList.add(PANE_RESIZING_BODY_CLASS);
24373
- document.addEventListener("mousemove", this.onPaneResizeMove);
24374
- document.addEventListener("mouseup", this.onPaneResizeEnd);
24495
+ document.addEventListener("pointermove", this.onPaneResizeMove);
24496
+ document.addEventListener("pointerup", this.onPaneResizeEnd);
24497
+ document.addEventListener("pointercancel", this.onPaneResizeEnd);
24375
24498
  }
24376
24499
  endPanelResize() {
24377
24500
  if (!this.panelDrag) return;
24378
24501
  this.panelDrag = void 0;
24379
- document.removeEventListener("mousemove", this.onPanelResizeMove);
24380
- document.removeEventListener("mouseup", this.onPanelResizeEnd);
24502
+ document.removeEventListener("pointermove", this.onPanelResizeMove);
24503
+ document.removeEventListener("pointerup", this.onPanelResizeEnd);
24504
+ document.removeEventListener("pointercancel", this.onPanelResizeEnd);
24381
24505
  this.commit({ panelResizing: false });
24382
24506
  }
24383
24507
  endPaneResize() {
24384
24508
  if (!this.paneDrag) return;
24385
24509
  this.paneDrag = void 0;
24386
24510
  document.body.classList.remove(PANE_RESIZING_BODY_CLASS);
24387
- document.removeEventListener("mousemove", this.onPaneResizeMove);
24388
- document.removeEventListener("mouseup", this.onPaneResizeEnd);
24511
+ document.removeEventListener("pointermove", this.onPaneResizeMove);
24512
+ document.removeEventListener("pointerup", this.onPaneResizeEnd);
24513
+ document.removeEventListener("pointercancel", this.onPaneResizeEnd);
24389
24514
  this.commit({ paneResizing: false });
24390
24515
  }
24391
24516
  syncDocumentListeners() {
@@ -24413,7 +24538,7 @@ var CliPanelController = class {
24413
24538
  if (!engine) return;
24414
24539
  const theme = engine.getTerminal().options.theme;
24415
24540
  if (!theme) return;
24416
- const styles = derivePanelThemeStyles(theme);
24541
+ const styles = sameTerminalTheme(theme, this.resolvedTheme.terminal) ? this.resolvedTheme.styles : derivePanelThemeStyles(theme);
24417
24542
  if (sameRecord(styles, this.snapshot.themeStyles)) return;
24418
24543
  this.commit({ themeStyles: styles });
24419
24544
  }
@@ -24430,6 +24555,44 @@ var CliPanelController = class {
24430
24555
  this.themeObserver = new MutationObserver(() => this.syncThemeFromEngine());
24431
24556
  this.themeObserver.observe(container, { attributes: true, attributeFilter: ["style"] });
24432
24557
  }
24558
+ applyTheme() {
24559
+ const resolved = resolvePanelTheme(this.themeInput, prefersDarkScheme());
24560
+ this.resolvedTheme = resolved;
24561
+ const terminalTheme = this.themeInput === void 0 ? void 0 : resolved.terminal;
24562
+ const terminalChanged = !!terminalTheme && !sameTerminalTheme(terminalTheme, this.snapshot.terminalTheme);
24563
+ const themeStyles = this.themeInput === void 0 ? {} : resolved.styles;
24564
+ this.commit({ themeStyles, scheme: resolved.scheme, terminalTheme });
24565
+ if (terminalChanged) this.engines.forEach((engine) => engine.setTheme(terminalTheme));
24566
+ if (this.snapshot.syncTheme) this.syncThemeFromEngine();
24567
+ }
24568
+ /** Follow the system's light/dark setting while the theme is 'auto'. */
24569
+ watchSystemScheme() {
24570
+ const follow = followsSystem(this.themeInput) && typeof window !== "undefined" && !!window.matchMedia;
24571
+ if (follow && !this.schemeQuery) {
24572
+ this.schemeQuery = window.matchMedia(DARK_SCHEME_QUERY);
24573
+ this.schemeQuery.addEventListener?.("change", this.onSystemSchemeChange);
24574
+ } else if (!follow && this.schemeQuery) {
24575
+ this.schemeQuery.removeEventListener?.("change", this.onSystemSchemeChange);
24576
+ this.schemeQuery = void 0;
24577
+ }
24578
+ }
24579
+ /* ─── On-screen keyboard ────────────────────────────────── */
24580
+ /**
24581
+ * Phone browsers open the keyboard over the page and shrink only the visual
24582
+ * viewport; follow it so the panel stays above the keyboard.
24583
+ */
24584
+ watchVisualViewport() {
24585
+ const vv = typeof window !== "undefined" ? window.visualViewport : null;
24586
+ if (!vv || this.visualViewport) return;
24587
+ this.visualViewport = vv;
24588
+ vv.addEventListener("resize", this.onVisualViewportChange);
24589
+ vv.addEventListener("scroll", this.onVisualViewportChange);
24590
+ }
24591
+ unwatchVisualViewport() {
24592
+ this.visualViewport?.removeEventListener("resize", this.onVisualViewportChange);
24593
+ this.visualViewport?.removeEventListener("scroll", this.onVisualViewportChange);
24594
+ this.visualViewport = void 0;
24595
+ }
24433
24596
  stopThemeSync() {
24434
24597
  this.themeObserver?.disconnect();
24435
24598
  this.themeObserver = void 0;
@@ -24742,6 +24905,8 @@ function positionDropdownItemClass(s, position) {
24742
24905
  }
24743
24906
  function panelWrapperStyle(s) {
24744
24907
  const style = s.isHorizontal ? { width: `${s.width}px` } : { height: `${s.height}px` };
24908
+ if (s.keyboardInset > 0) style["--cli-panel-keyboard-inset"] = `${s.keyboardInset}px`;
24909
+ if (s.visibleHeight > 0) style["--cli-panel-visible-height"] = `${s.visibleHeight}px`;
24745
24910
  if (s.hidden) style["display"] = "none";
24746
24911
  return { ...style, ...s.themeStyles };
24747
24912
  }
@@ -24749,7 +24914,7 @@ function paneStyle(widthPercent) {
24749
24914
  return { flex: `${widthPercent} 1 0` };
24750
24915
  }
24751
24916
  function contextMenuStyle(s) {
24752
- return { left: `${s.contextMenu.x}px`, top: `${s.contextMenu.y}px` };
24917
+ return { ...s.themeStyles, left: `${s.contextMenu.x}px`, top: `${s.contextMenu.y}px` };
24753
24918
  }
24754
24919
  function statusIndicators(s) {
24755
24920
  const items = [];
@@ -24866,7 +25031,7 @@ function openStatusDropdown(s) {
24866
25031
  kind: "services",
24867
25032
  header: PANEL_LABELS.servicesHeader,
24868
25033
  emptyText: PANEL_LABELS.servicesEmpty,
24869
- style: s.servicesDropdownStyle,
25034
+ style: { ...s.themeStyles, ...s.servicesDropdownStyle },
24870
25035
  rows: s.global.serviceDetails.map((svc, i) => ({
24871
25036
  key: `${i}-${svc.name}`,
24872
25037
  dotClass: joinClasses("cli-panel-svc-dot", svc.status === "running" ? "svc-running" : "svc-stopped"),
@@ -24882,7 +25047,7 @@ function openStatusDropdown(s) {
24882
25047
  kind: "servers",
24883
25048
  header: PANEL_LABELS.serversHeader,
24884
25049
  emptyText: PANEL_LABELS.serversEmpty,
24885
- style: s.serversDropdownStyle,
25050
+ style: { ...s.themeStyles, ...s.serversDropdownStyle },
24886
25051
  rows: s.global.serverDetails.map((srv, i) => ({
24887
25052
  key: `${i}-${srv.name}`,
24888
25053
  dotClass: joinClasses("cli-panel-svc-dot", srv.connected ? "svc-running" : "svc-stopped"),