@qodalis/cli 2.1.1 → 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.1";
521
+ var LIBRARY_VERSION = "2.2.0";
522
522
  var API_VERSION = 2;
523
523
  var ANONYMOUS = "\0anonymous";
524
524
  function aliasOwner(context2) {
@@ -12600,6 +12600,7 @@ var CliThemeCommandProcessor = class {
12600
12600
  }
12601
12601
  async initialize(context2) {
12602
12602
  this.defaultTheme = context2.terminal.options.theme;
12603
+ if (context2.options?.theme !== void 0) return;
12603
12604
  this.applySavedTheme(context2, false);
12604
12605
  context2.state.select(
12605
12606
  (state) => JSON.stringify([
@@ -22870,6 +22871,9 @@ var CliEngine = class {
22870
22871
  this.terminalOptions = getTerminalOptions(
22871
22872
  this.options?.terminalOptions
22872
22873
  );
22874
+ if (this.options?.theme !== void 0 && !this.options?.terminalOptions?.theme) {
22875
+ this.terminalOptions.theme = resolvePanelTheme(this.options.theme, prefersDarkScheme()).terminal;
22876
+ }
22873
22877
  const setup = initializeTerminal(
22874
22878
  this.container,
22875
22879
  this.terminalOptions,
@@ -23122,6 +23126,14 @@ var CliEngine = class {
23122
23126
  this.terminal?.focus();
23123
23127
  });
23124
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
+ }
23125
23137
  /**
23126
23138
  * Get the underlying xterm.js Terminal instance.
23127
23139
  */
@@ -23970,6 +23982,23 @@ function sameJson(a, b) {
23970
23982
  return JSON.stringify(a) === JSON.stringify(b);
23971
23983
  }
23972
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
+ }
23973
24002
  function defaultViewport() {
23974
24003
  if (typeof window === "undefined") return { width: 0, height: 0 };
23975
24004
  return { width: window.innerWidth, height: window.innerHeight };
@@ -23988,6 +24017,9 @@ var CliPanelController = class {
23988
24017
  /* ─── Subscription ──────────────────────────────────────── */
23989
24018
  this.subscribe = (listener) => {
23990
24019
  this.listeners.add(listener);
24020
+ this.watchSystemScheme();
24021
+ this.watchVisualViewport();
24022
+ this.onVisualViewportChange();
23991
24023
  return () => {
23992
24024
  this.listeners.delete(listener);
23993
24025
  };
@@ -24024,11 +24056,22 @@ var CliPanelController = class {
24024
24056
  this.closeMenus();
24025
24057
  this.cancelRename();
24026
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
+ };
24027
24066
  const config = options.config ?? {};
24028
24067
  this.callbacks = options.callbacks ?? {};
24029
24068
  this.controlled = options.controlled ?? {};
24030
24069
  this.viewport = options.viewport ?? defaultViewport;
24031
24070
  this.persistPosition = options.persistPosition ?? true;
24071
+ this.themeInput = config.theme;
24072
+ this.resolvedTheme = resolvePanelTheme(config.theme, prefersDarkScheme());
24073
+ this.watchSystemScheme();
24074
+ this.watchVisualViewport();
24032
24075
  this.internal = {
24033
24076
  collapsed: config.isCollapsed ?? true,
24034
24077
  hidden: config.isHidden ?? false,
@@ -24055,7 +24098,11 @@ var CliPanelController = class {
24055
24098
  isHorizontal: false,
24056
24099
  showStatusIndicators: true,
24057
24100
  panelResizing: false,
24058
- 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(),
24059
24106
  ...this.configFields(config),
24060
24107
  tabs: [],
24061
24108
  activeTabId: 0,
@@ -24073,8 +24120,9 @@ var CliPanelController = class {
24073
24120
  formattedUptime: formatUptime2(0)
24074
24121
  });
24075
24122
  }
24076
- /** Apply a new panel configuration (chrome buttons, theme sync). */
24123
+ /** Apply a new panel configuration (chrome buttons, theme, theme sync). */
24077
24124
  setOptions(config) {
24125
+ if (themeKey(config?.theme) !== themeKey(this.themeInput)) this.setTheme(config?.theme);
24078
24126
  const fields = this.configFields(config ?? {});
24079
24127
  const s = this.snapshot;
24080
24128
  if (fields.closable === s.closable && fields.resizable === s.resizable && fields.hideable === s.hideable && fields.hideAlignment === s.hideAlignment && fields.syncTheme === s.syncTheme) {
@@ -24084,6 +24132,15 @@ var CliPanelController = class {
24084
24132
  if (fields.syncTheme) this.startThemeSync();
24085
24133
  else this.stopThemeSync();
24086
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
+ }
24087
24144
  /** Replace the change callbacks (wrappers call this when their props change). */
24088
24145
  setCallbacks(callbacks) {
24089
24146
  this.callbacks = callbacks;
@@ -24109,6 +24166,9 @@ var CliPanelController = class {
24109
24166
  destroy() {
24110
24167
  this.tracker.destroy();
24111
24168
  this.stopThemeSync();
24169
+ this.schemeQuery?.removeEventListener("change", this.onSystemSchemeChange);
24170
+ this.schemeQuery = void 0;
24171
+ this.unwatchVisualViewport();
24112
24172
  this.endPanelResize();
24113
24173
  this.endPaneResize();
24114
24174
  this.detachDocumentListeners();
@@ -24326,6 +24386,7 @@ var CliPanelController = class {
24326
24386
  if (!this.findTab(tabId)) return;
24327
24387
  this.engines.set(paneId, engine);
24328
24388
  this.tracker.registerEngine(tabId, paneId, engine);
24389
+ if (this.snapshot.terminalTheme) engine.setTheme(this.snapshot.terminalTheme);
24329
24390
  if (this.snapshot.syncTheme) this.startThemeSync();
24330
24391
  }
24331
24392
  getEngine(paneId) {
@@ -24403,7 +24464,7 @@ var CliPanelController = class {
24403
24464
  });
24404
24465
  }
24405
24466
  /* ─── Drag resizing ─────────────────────────────────────── */
24406
- /** Mouse down on the panel's resize bar. */
24467
+ /** Pointer (mouse, touch or pen) down on the panel's resize bar. */
24407
24468
  startPanelResize(clientX, clientY) {
24408
24469
  if (!this.snapshot.resizable || typeof document === "undefined") return;
24409
24470
  if (this.snapshot.collapsed) this.toggle();
@@ -24414,10 +24475,11 @@ var CliPanelController = class {
24414
24475
  startHeight: this.snapshot.height
24415
24476
  };
24416
24477
  this.commit({ panelResizing: true });
24417
- document.addEventListener("mousemove", this.onPanelResizeMove);
24418
- document.addEventListener("mouseup", this.onPanelResizeEnd);
24478
+ document.addEventListener("pointermove", this.onPanelResizeMove);
24479
+ document.addEventListener("pointerup", this.onPanelResizeEnd);
24480
+ document.addEventListener("pointercancel", this.onPanelResizeEnd);
24419
24481
  }
24420
- /** 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. */
24421
24483
  startPaneResize(tabId, dividerIndex, clientX, containerWidth) {
24422
24484
  const tab = this.findTab(tabId);
24423
24485
  if (!tab || typeof document === "undefined") return;
@@ -24430,22 +24492,25 @@ var CliPanelController = class {
24430
24492
  };
24431
24493
  this.commit({ paneResizing: true });
24432
24494
  document.body.classList.add(PANE_RESIZING_BODY_CLASS);
24433
- document.addEventListener("mousemove", this.onPaneResizeMove);
24434
- document.addEventListener("mouseup", this.onPaneResizeEnd);
24495
+ document.addEventListener("pointermove", this.onPaneResizeMove);
24496
+ document.addEventListener("pointerup", this.onPaneResizeEnd);
24497
+ document.addEventListener("pointercancel", this.onPaneResizeEnd);
24435
24498
  }
24436
24499
  endPanelResize() {
24437
24500
  if (!this.panelDrag) return;
24438
24501
  this.panelDrag = void 0;
24439
- document.removeEventListener("mousemove", this.onPanelResizeMove);
24440
- document.removeEventListener("mouseup", this.onPanelResizeEnd);
24502
+ document.removeEventListener("pointermove", this.onPanelResizeMove);
24503
+ document.removeEventListener("pointerup", this.onPanelResizeEnd);
24504
+ document.removeEventListener("pointercancel", this.onPanelResizeEnd);
24441
24505
  this.commit({ panelResizing: false });
24442
24506
  }
24443
24507
  endPaneResize() {
24444
24508
  if (!this.paneDrag) return;
24445
24509
  this.paneDrag = void 0;
24446
24510
  document.body.classList.remove(PANE_RESIZING_BODY_CLASS);
24447
- document.removeEventListener("mousemove", this.onPaneResizeMove);
24448
- document.removeEventListener("mouseup", this.onPaneResizeEnd);
24511
+ document.removeEventListener("pointermove", this.onPaneResizeMove);
24512
+ document.removeEventListener("pointerup", this.onPaneResizeEnd);
24513
+ document.removeEventListener("pointercancel", this.onPaneResizeEnd);
24449
24514
  this.commit({ paneResizing: false });
24450
24515
  }
24451
24516
  syncDocumentListeners() {
@@ -24473,7 +24538,7 @@ var CliPanelController = class {
24473
24538
  if (!engine) return;
24474
24539
  const theme = engine.getTerminal().options.theme;
24475
24540
  if (!theme) return;
24476
- const styles = derivePanelThemeStyles(theme);
24541
+ const styles = sameTerminalTheme(theme, this.resolvedTheme.terminal) ? this.resolvedTheme.styles : derivePanelThemeStyles(theme);
24477
24542
  if (sameRecord(styles, this.snapshot.themeStyles)) return;
24478
24543
  this.commit({ themeStyles: styles });
24479
24544
  }
@@ -24490,6 +24555,44 @@ var CliPanelController = class {
24490
24555
  this.themeObserver = new MutationObserver(() => this.syncThemeFromEngine());
24491
24556
  this.themeObserver.observe(container, { attributes: true, attributeFilter: ["style"] });
24492
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
+ }
24493
24596
  stopThemeSync() {
24494
24597
  this.themeObserver?.disconnect();
24495
24598
  this.themeObserver = void 0;
@@ -24802,6 +24905,8 @@ function positionDropdownItemClass(s, position) {
24802
24905
  }
24803
24906
  function panelWrapperStyle(s) {
24804
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`;
24805
24910
  if (s.hidden) style["display"] = "none";
24806
24911
  return { ...style, ...s.themeStyles };
24807
24912
  }
@@ -24809,7 +24914,7 @@ function paneStyle(widthPercent) {
24809
24914
  return { flex: `${widthPercent} 1 0` };
24810
24915
  }
24811
24916
  function contextMenuStyle(s) {
24812
- 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` };
24813
24918
  }
24814
24919
  function statusIndicators(s) {
24815
24920
  const items = [];
@@ -24926,7 +25031,7 @@ function openStatusDropdown(s) {
24926
25031
  kind: "services",
24927
25032
  header: PANEL_LABELS.servicesHeader,
24928
25033
  emptyText: PANEL_LABELS.servicesEmpty,
24929
- style: s.servicesDropdownStyle,
25034
+ style: { ...s.themeStyles, ...s.servicesDropdownStyle },
24930
25035
  rows: s.global.serviceDetails.map((svc, i) => ({
24931
25036
  key: `${i}-${svc.name}`,
24932
25037
  dotClass: joinClasses("cli-panel-svc-dot", svc.status === "running" ? "svc-running" : "svc-stopped"),
@@ -24942,7 +25047,7 @@ function openStatusDropdown(s) {
24942
25047
  kind: "servers",
24943
25048
  header: PANEL_LABELS.serversHeader,
24944
25049
  emptyText: PANEL_LABELS.serversEmpty,
24945
- style: s.serversDropdownStyle,
25050
+ style: { ...s.themeStyles, ...s.serversDropdownStyle },
24946
25051
  rows: s.global.serverDetails.map((srv, i) => ({
24947
25052
  key: `${i}-${srv.name}`,
24948
25053
  dotClass: joinClasses("cli-panel-svc-dot", srv.connected ? "svc-running" : "svc-stopped"),