@oh-my-pi/pi-tui 15.12.3 → 15.12.4

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/CHANGELOG.md CHANGED
@@ -2,6 +2,16 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [15.12.4] - 2026-06-13
6
+
7
+ ### Added
8
+
9
+ - `PI_FORCE_HYPERLINKS=1` / `PI_NO_HYPERLINKS=1` env overrides for the OSC 8 hyperlink capability, mirroring the `PI_FORCE_SYNC_OUTPUT`/`PI_NO_SYNC_OUTPUT` shape (opt-out beats force-on).
10
+
11
+ ### Changed
12
+
13
+ - Auto-enable OSC 8 hyperlinks inside tmux when tmux self-reports >= 3.4 via `TERM_PROGRAM_VERSION`; tmux 3.4 stores OSC 8 as a cell attribute and forwards it to outer terminals whose `terminal-features` include `hyperlinks`. Older tmux, GNU screen, and tmux without a reported version still default off. Resolution is factored into `hyperlinksUserOverride()` and `shouldEnableHyperlinksByDefault()` mirroring the sync-output helpers ([#2403](https://github.com/can1357/oh-my-pi/issues/2403)).
14
+
5
15
  ## [15.11.8] - 2026-06-12
6
16
 
7
17
  ### Changed
@@ -85,6 +85,38 @@ export declare function shouldEnableSynchronizedOutputByDefault(env?: NodeJS.Pro
85
85
  * `PI_NO_DECCARA` kill switch. Pure helper for tests and `TERMINAL` construction.
86
86
  */
87
87
  export declare function detectRectangularSgrSupport(terminalId: TerminalId, env?: NodeJS.ProcessEnv): boolean;
88
+ /**
89
+ * Resolve an explicit user override for OSC 8 hyperlinks. Returns `false` for
90
+ * an opt-out, `true` for a force-on, or `null` when the user has expressed no
91
+ * preference. Opt-out beats force-on so a kill switch is unambiguous, mirroring
92
+ * {@link synchronizedOutputUserOverride}.
93
+ */
94
+ export declare function hyperlinksUserOverride(env?: NodeJS.ProcessEnv): boolean | null;
95
+ /**
96
+ * Whether OSC 8 hyperlinks should be enabled by default.
97
+ *
98
+ * Policy (highest precedence first):
99
+ * 1. Explicit user override (`PI_NO_HYPERLINKS=1` off, `PI_FORCE_HYPERLINKS=1`
100
+ * on). Opt-out wins ties.
101
+ * 2. Static terminal capability — terminals whose {@link TerminalInfo} marks
102
+ * `hyperlinks: false` (e.g. `base`) stay off unless the user forced on.
103
+ * 3. GNU screen's explicit session marker (`STY`) always off, even if tmux is
104
+ * also present: a screen layer anywhere in the path cannot forward OSC 8.
105
+ * 4. tmux session (`TMUX` set): enabled when tmux self-reports >= 3.4 via
106
+ * `TERM_PROGRAM_VERSION` (tmux 3.4 stores OSC 8 as a cell attribute and
107
+ * forwards it to outer terminals whose `terminal-features` include
108
+ * `hyperlinks`). Older or unknown versions stay off; on outer terminals
109
+ * without the feature configured, tmux silently drops the sequence —
110
+ * identical to today. Checked before the screen-family TERM heuristic
111
+ * because tmux's historical `default-terminal` is `screen-256color`, so
112
+ * `TERM=screen*` inside a tmux session must NOT short-circuit to off.
113
+ * 5. screen-family TERM without `TMUX` always off: screen never gained OSC 8
114
+ * support.
115
+ * 6. tmux-family TERM without `TMUX` env — unusual (e.g. inspection scripts);
116
+ * no version available, so off.
117
+ * 7. Otherwise honor the static terminal capability.
118
+ */
119
+ export declare function shouldEnableHyperlinksByDefault(env?: NodeJS.ProcessEnv, terminalId?: TerminalId): boolean;
88
120
  export declare const TERMINAL_ID: TerminalId;
89
121
  /**
90
122
  * The process-wide {@link TERMINAL} singleton: a {@link TerminalInfo} whose
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "type": "module",
3
3
  "name": "@oh-my-pi/pi-tui",
4
- "version": "15.12.3",
4
+ "version": "15.12.4",
5
5
  "description": "Terminal User Interface library with differential rendering for efficient text-based applications",
6
6
  "homepage": "https://omp.sh",
7
7
  "author": "Can Boluk",
@@ -37,10 +37,10 @@
37
37
  "fmt": "biome format --write ."
38
38
  },
39
39
  "dependencies": {
40
- "@oh-my-pi/pi-natives": "15.12.3",
41
- "@oh-my-pi/pi-utils": "15.12.3",
40
+ "@oh-my-pi/pi-natives": "15.12.4",
41
+ "@oh-my-pi/pi-utils": "15.12.4",
42
42
  "lru-cache": "11.5.1",
43
- "marked": "^18.0.4"
43
+ "marked": "^18.0.5"
44
44
  },
45
45
  "devDependencies": {
46
46
  "chalk": "^5.6.2",
@@ -249,6 +249,83 @@ export function detectRectangularSgrSupport(terminalId: TerminalId, env: NodeJS.
249
249
  }
250
250
  return true;
251
251
  }
252
+ /**
253
+ * Resolve an explicit user override for OSC 8 hyperlinks. Returns `false` for
254
+ * an opt-out, `true` for a force-on, or `null` when the user has expressed no
255
+ * preference. Opt-out beats force-on so a kill switch is unambiguous, mirroring
256
+ * {@link synchronizedOutputUserOverride}.
257
+ */
258
+ export function hyperlinksUserOverride(env: NodeJS.ProcessEnv = Bun.env): boolean | null {
259
+ if (env.PI_NO_HYPERLINKS === "1") return false;
260
+ if (env.PI_FORCE_HYPERLINKS === "1") return true;
261
+ return null;
262
+ }
263
+
264
+ /**
265
+ * Parse tmux's self-reported version from `TERM_PROGRAM_VERSION`. tmux sets
266
+ * `TERM_PROGRAM=tmux` and `TERM_PROGRAM_VERSION=<version>` automatically since
267
+ * 3.2a; older releases (or any path that does not surface the version) yield
268
+ * `null` and the caller treats tmux conservatively.
269
+ */
270
+ function parseTmuxVersionFromEnv(env: NodeJS.ProcessEnv): { major: number; minor: number } | null {
271
+ if (env.TERM_PROGRAM?.toLowerCase() !== "tmux") return null;
272
+ return parseMajorMinorVersion(env.TERM_PROGRAM_VERSION);
273
+ }
274
+
275
+ /**
276
+ * Whether OSC 8 hyperlinks should be enabled by default.
277
+ *
278
+ * Policy (highest precedence first):
279
+ * 1. Explicit user override (`PI_NO_HYPERLINKS=1` off, `PI_FORCE_HYPERLINKS=1`
280
+ * on). Opt-out wins ties.
281
+ * 2. Static terminal capability — terminals whose {@link TerminalInfo} marks
282
+ * `hyperlinks: false` (e.g. `base`) stay off unless the user forced on.
283
+ * 3. GNU screen's explicit session marker (`STY`) always off, even if tmux is
284
+ * also present: a screen layer anywhere in the path cannot forward OSC 8.
285
+ * 4. tmux session (`TMUX` set): enabled when tmux self-reports >= 3.4 via
286
+ * `TERM_PROGRAM_VERSION` (tmux 3.4 stores OSC 8 as a cell attribute and
287
+ * forwards it to outer terminals whose `terminal-features` include
288
+ * `hyperlinks`). Older or unknown versions stay off; on outer terminals
289
+ * without the feature configured, tmux silently drops the sequence —
290
+ * identical to today. Checked before the screen-family TERM heuristic
291
+ * because tmux's historical `default-terminal` is `screen-256color`, so
292
+ * `TERM=screen*` inside a tmux session must NOT short-circuit to off.
293
+ * 5. screen-family TERM without `TMUX` always off: screen never gained OSC 8
294
+ * support.
295
+ * 6. tmux-family TERM without `TMUX` env — unusual (e.g. inspection scripts);
296
+ * no version available, so off.
297
+ * 7. Otherwise honor the static terminal capability.
298
+ */
299
+ export function shouldEnableHyperlinksByDefault(
300
+ env: NodeJS.ProcessEnv = Bun.env,
301
+ terminalId: TerminalId = TERMINAL_ID,
302
+ ): boolean {
303
+ const override = hyperlinksUserOverride(env);
304
+ if (override !== null) return override;
305
+
306
+ if (!getTerminalInfo(terminalId).hyperlinks) return false;
307
+
308
+ // STY is GNU screen's explicit session marker. It vetoes tmux enabling when
309
+ // multiplexers are nested because screen cannot forward OSC 8 anywhere in the
310
+ // path.
311
+ if (env.STY) return false;
312
+
313
+ // tmux check before TERM heuristics: TMUX is the authoritative current-session
314
+ // signal and supersedes TERM, which may be `screen-256color` under tmux's
315
+ // historical default-terminal setting.
316
+ if (env.TMUX) {
317
+ const version = parseTmuxVersionFromEnv(env);
318
+ if (!version) return false;
319
+ return version.major > 3 || (version.major === 3 && version.minor >= 4);
320
+ }
321
+
322
+ const term = env.TERM?.toLowerCase() ?? "";
323
+ if (term.startsWith("screen")) return false;
324
+ if (term.startsWith("tmux")) return false;
325
+
326
+ return true;
327
+ }
328
+
252
329
  function getFallbackImageProtocol(terminalId: TerminalId): ImageProtocol | null {
253
330
  if (!process.stdout.isTTY) return null;
254
331
  if (terminalId === "vscode" || terminalId === "alacritty") return null;
@@ -336,12 +413,12 @@ export const TERMINAL: RuntimeTerminal = (() => {
336
413
  const fallbackImageProtocol = getFallbackImageProtocol(resolved.id);
337
414
  if (fallbackImageProtocol) resolved.imageProtocol = fallbackImageProtocol;
338
415
  }
339
- // tmux and screen multiplexers do not reliably forward OSC 8 hyperlinks
340
- // to the outer terminal, so force them off regardless of detected terminal.
341
- const term = Bun.env.TERM?.toLowerCase() ?? "";
342
- if (resolved.hyperlinks && (Bun.env.TMUX || term.startsWith("tmux") || term.startsWith("screen"))) {
343
- resolved.hyperlinks = false;
344
- }
416
+ // Hyperlink (OSC 8) capability. The static per-terminal flag lives on
417
+ // KNOWN_TERMINALS; shouldEnableHyperlinksByDefault folds in runtime context
418
+ // PI_FORCE_HYPERLINKS / PI_NO_HYPERLINKS overrides plus a tmux>=3.4 gate so
419
+ // modern tmux forwards OSC 8 to outer terminals that opt in via
420
+ // `terminal-features "*:hyperlinks"`.
421
+ resolved.hyperlinks = shouldEnableHyperlinksByDefault(Bun.env, resolved.id);
345
422
  // DECCARA rectangular-SGR background fills. The static per-terminal capability
346
423
  // lives on KNOWN_TERMINALS; here we fold in runtime context — multiplexer and
347
424
  // the PI_NO_DECCARA kill switch via detectRectangularSgrSupport — and force it