@sayknow-cli/tui 0.3.13 → 0.3.15

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.
Files changed (34) hide show
  1. package/dist/types/animation-scheduler.d.ts +13 -0
  2. package/dist/types/autocomplete.d.ts +83 -0
  3. package/dist/types/bracketed-paste.d.ts +26 -0
  4. package/dist/types/components/box.d.ts +20 -0
  5. package/dist/types/components/cancellable-loader.d.ts +21 -0
  6. package/dist/types/components/editor.d.ts +126 -0
  7. package/dist/types/components/image.d.ts +18 -0
  8. package/dist/types/components/input.d.ts +16 -0
  9. package/dist/types/components/loader.d.ts +23 -0
  10. package/dist/types/components/markdown.d.ts +87 -0
  11. package/dist/types/components/sayknow-pet.d.ts +128 -0
  12. package/dist/types/components/select-list.d.ts +46 -0
  13. package/dist/types/components/settings-list.d.ts +39 -0
  14. package/dist/types/components/spacer.d.ts +11 -0
  15. package/dist/types/components/tab-bar.d.ts +56 -0
  16. package/dist/types/components/text.d.ts +22 -0
  17. package/dist/types/components/truncated-text.d.ts +10 -0
  18. package/dist/types/editor-component.d.ts +36 -0
  19. package/dist/types/fuzzy.d.ts +15 -0
  20. package/dist/types/index.d.ts +28 -0
  21. package/dist/types/keybindings.d.ts +201 -0
  22. package/dist/types/keys.d.ts +208 -0
  23. package/dist/types/kill-ring.d.ts +27 -0
  24. package/dist/types/metrics.d.ts +85 -0
  25. package/dist/types/stdin-buffer.d.ts +50 -0
  26. package/dist/types/symbols.d.ts +23 -0
  27. package/dist/types/terminal-capabilities.d.ts +187 -0
  28. package/dist/types/terminal.d.ts +90 -0
  29. package/dist/types/ttyid.d.ts +9 -0
  30. package/dist/types/tui.d.ts +269 -0
  31. package/dist/types/utils.d.ts +110 -0
  32. package/package.json +9 -8
  33. package/src/terminal-capabilities.ts +77 -4
  34. package/src/tui.ts +50 -21
package/src/tui.ts CHANGED
@@ -9,7 +9,14 @@ import { getKeybindings } from "./keybindings";
9
9
  import { isKeyRelease } from "./keys";
10
10
  import { renderMetrics } from "./metrics";
11
11
  import type { Terminal } from "./terminal";
12
- import { ImageProtocol, setCellDimensions, setTerminalImageProtocol, TERMINAL } from "./terminal-capabilities";
12
+ import {
13
+ ImageProtocol,
14
+ isImageProtocolForced,
15
+ isUnderTerminalMultiplexer,
16
+ setCellDimensions,
17
+ setTerminalImageProtocol,
18
+ TERMINAL,
19
+ } from "./terminal-capabilities";
13
20
  import {
14
21
  Ellipsis,
15
22
  extractSegments,
@@ -217,7 +224,6 @@ function isTermuxSession(env: Record<string, string | undefined> = Bun.env): boo
217
224
  return Boolean(env.TERMUX_VERSION);
218
225
  }
219
226
 
220
- const SKC_TMUX_LAUNCHED_ENV = "SKC_TMUX_LAUNCHED";
221
227
  const DISABLED_ENV_VALUES = new Set(["0", "false", "off", "no"]);
222
228
  const TRUTHY_ENV_VALUES = new Set(["1", "true", "yes", "on", "y"]);
223
229
 
@@ -231,25 +237,38 @@ function envFlagEnabled(value: string | undefined): boolean {
231
237
  return normalized !== undefined && TRUTHY_ENV_VALUES.has(normalized);
232
238
  }
233
239
 
234
- function termLooksMultiplexed(value: string | undefined): boolean {
235
- const term = value?.trim().toLowerCase() ?? "";
236
- return term.startsWith("tmux") || term.startsWith("screen");
237
- }
238
-
239
240
  function isWindowsTerminalSession(env: Record<string, string | undefined> = Bun.env): boolean {
240
241
  return envIsEnabled(env.WT_SESSION) || env.TERM_PROGRAM === "Windows_Terminal";
241
242
  }
242
243
 
243
- /** Detect terminal multiplexers where scrollback clearing and height-change redraws are hostile. */
244
+ /**
245
+ * Detect terminal multiplexers where scrollback clearing and height-change
246
+ * redraws are hostile. Delegates to the shared capability predicate so the
247
+ * renderer and graphics-protocol selection agree on what counts as a
248
+ * multiplexed host.
249
+ */
244
250
  function isMultiplexerSession(env: Record<string, string | undefined> = Bun.env): boolean {
245
- return Boolean(
246
- envIsEnabled(env.TMUX) ||
247
- envIsEnabled(env.TMUX_PANE) ||
248
- envIsEnabled(env.STY) ||
249
- envIsEnabled(env.ZELLIJ) ||
250
- envIsEnabled(env[SKC_TMUX_LAUNCHED_ENV]) ||
251
- termLooksMultiplexed(env.TERM),
252
- );
251
+ return isUnderTerminalMultiplexer(env as NodeJS.ProcessEnv);
252
+ }
253
+
254
+ /**
255
+ * Startup sixel capability probe policy (pure; exported for tests):
256
+ * - Never probe when PI_FORCE_IMAGE_PROTOCOL is set — an explicit
257
+ * configuration (including "off") is authoritative.
258
+ * - Never probe inside a terminal multiplexer: tmux advertises DA1 ";4"
259
+ * whenever it was compiled with sixel support, regardless of whether the
260
+ * attached client terminal can render sixel, so a positive reply is not
261
+ * end-to-end evidence. Graphics under a multiplexer are strictly opt-in
262
+ * via PI_FORCE_IMAGE_PROTOCOL=sixel.
263
+ * - Probe Windows Terminal (>=1.22 renders sixel but exposes no env marker).
264
+ */
265
+ export function shouldProbeSixelCapability(
266
+ env: NodeJS.ProcessEnv = Bun.env,
267
+ platform: NodeJS.Platform = process.platform,
268
+ ): boolean {
269
+ if (isImageProtocolForced()) return false;
270
+ if (isUnderTerminalMultiplexer(env)) return false;
271
+ return platform === "win32" && Boolean(env.WT_SESSION?.trim());
253
272
  }
254
273
 
255
274
  function useLegacyMultiplexerFullRender(env: Record<string, string | undefined> = Bun.env): boolean {
@@ -1008,8 +1027,7 @@ export class TUI extends Container {
1008
1027
 
1009
1028
  #querySixelSupport(): void {
1010
1029
  if (TERMINAL.imageProtocol) return;
1011
- if (process.platform !== "win32") return;
1012
- if (!Bun.env.WT_SESSION) return;
1030
+ if (!this.#isSixelProbeCandidate()) return;
1013
1031
  if (!process.stdin.isTTY || !process.stdout.isTTY) return;
1014
1032
 
1015
1033
  this.#clearSixelProbeState();
@@ -1023,6 +1041,10 @@ export class TUI extends Container {
1023
1041
  }, 250);
1024
1042
  }
1025
1043
 
1044
+ #isSixelProbeCandidate(): boolean {
1045
+ return shouldProbeSixelCapability();
1046
+ }
1047
+
1026
1048
  #handleSixelProbeInput(data: string): InputListenerResult {
1027
1049
  if (!this.#sixelProbePendingDa && !this.#sixelProbePendingGraphics) {
1028
1050
  return undefined;
@@ -1049,11 +1071,15 @@ export class TUI extends Container {
1049
1071
 
1050
1072
  if (useDa && this.#sixelProbePendingDa) {
1051
1073
  this.#sixelProbePendingDa = false;
1052
- const attributes = (match[1] ?? "")
1074
+ const params = (match[1] ?? "")
1053
1075
  .split(";")
1054
1076
  .map(value => Number.parseInt(value, 10))
1055
1077
  .filter(value => Number.isFinite(value));
1056
- const hasSixelAttribute = attributes.includes(4);
1078
+ // The first DA1 parameter is the device/operating class (e.g. 1,
1079
+ // 62, 64), not an extension attribute: `CSI ?4;6c` identifies a
1080
+ // VT132, it does not advertise sixel. Only the parameters after
1081
+ // the class carry attributes like 4 (sixel graphics).
1082
+ const hasSixelAttribute = params.slice(1).includes(4);
1057
1083
  if (hasSixelAttribute) {
1058
1084
  this.#sixelProbePendingGraphics = false;
1059
1085
  probeOutcome = true;
@@ -1062,8 +1088,11 @@ export class TUI extends Container {
1062
1088
  }
1063
1089
  } else if (!useDa && this.#sixelProbePendingGraphics) {
1064
1090
  this.#sixelProbePendingGraphics = false;
1091
+ // XTSMGRAPHICS reply is `CSI ? 2 ; Ps ; ... S` where Ps=0 means
1092
+ // success and 1/2/3 are errors (tmux answers our unsupported
1093
+ // read with `CSI ?2;3;0S`). Only a success reply proves sixel.
1065
1094
  const status = Number.parseInt(match[1] ?? "", 10);
1066
- const supportsSixel = !Number.isNaN(status) && status !== 0;
1095
+ const supportsSixel = status === 0;
1067
1096
  if (supportsSixel) {
1068
1097
  this.#sixelProbePendingDa = false;
1069
1098
  probeOutcome = true;