@oh-my-pi/pi-tui 18.2.5 → 18.2.7

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 (43) hide show
  1. package/CHANGELOG.md +19 -0
  2. package/THIRD-PARTY-NOTICES.txt +0 -37
  3. package/dist/types/glyph-protocol.d.ts +62 -0
  4. package/dist/types/index.d.ts +1 -0
  5. package/dist/types/overlays/model-browser.d.ts +9 -3
  6. package/dist/types/prompt/gradient-highlight.d.ts +2 -2
  7. package/dist/types/prompt/magic-keywords.d.ts +37 -10
  8. package/dist/types/setup/scenes/glyph.d.ts +6 -1
  9. package/dist/types/terminal-capabilities.d.ts +8 -0
  10. package/dist/types/terminal.d.ts +16 -0
  11. package/dist/types/theme/mermaid-cache.d.ts +2 -2
  12. package/dist/types/tools/eval.d.ts +5 -4
  13. package/dist/types/tools/find.d.ts +82 -0
  14. package/dist/types/tools/web-search.d.ts +4 -0
  15. package/package.json +9 -9
  16. package/src/glyph-protocol.ts +143 -0
  17. package/src/index.ts +2 -0
  18. package/src/overlays/model-browser.ts +47 -6
  19. package/src/overlays/model-hub.ts +122 -33
  20. package/src/prompt/gradient-highlight.ts +3 -3
  21. package/src/prompt/macos-spelling.ts +7 -1
  22. package/src/prompt/magic-keywords.ts +103 -21
  23. package/src/prompt/markdown-prose.ts +2 -2
  24. package/src/prompt/tips.txt +1 -0
  25. package/src/setup/scenes/glyph.ts +8 -1
  26. package/src/terminal-capabilities.ts +12 -0
  27. package/src/terminal.ts +151 -2
  28. package/src/theme/glyph-bundle.json +797 -0
  29. package/src/theme/mermaid-cache.ts +5 -4
  30. package/src/tools/eval.ts +14 -7
  31. package/src/tools/find.ts +255 -0
  32. package/src/tools/hub.ts +2 -0
  33. package/src/tools/index.ts +2 -0
  34. package/src/tools/web-search.ts +5 -0
  35. package/src/tui.ts +7 -0
  36. package/dist/types/prompt/magic-keyword-boundary.d.ts +0 -9
  37. package/dist/types/prompt/orchestrate.d.ts +0 -13
  38. package/dist/types/prompt/ultrathink.d.ts +0 -13
  39. package/dist/types/prompt/workflow.d.ts +0 -13
  40. package/src/prompt/magic-keyword-boundary.ts +0 -23
  41. package/src/prompt/orchestrate.ts +0 -26
  42. package/src/prompt/ultrathink.ts +0 -26
  43. package/src/prompt/workflow.ts +0 -26
@@ -10,6 +10,7 @@ Did you know? Each kitty/tmux/cmux/zellij/wezterm split keeps its own session
10
10
  Drop the word `ultrathink` in your message for harder multi-step reasoning — watch it glow rainbow as you type
11
11
  Say `orchestrate` in your message to drive a multi-phase task with parallel subagents — watch it glow as you type
12
12
  Say `workflowz` in your message to drive the task with parallel subagents in eval — watch it glow as you type
13
+ Bulk data to classify or triage? Say `jevify` in your message — the eval kernel's `judge()` processes the batch and you read only what it flags — watch it glow as you type
13
14
  Log in to several accounts of the same provider — `/login` again — and omp load-balances across them automatically
14
15
  Run `omp auth-broker serve` once and every machine pulls live tokens over the wire — refresh keys never leave the host; `omp auth-gateway` fronts it as a drop-in proxy any OpenAI-compatible client can hit
15
16
  Press alt+p (or /switch) to switch provider, and ctrl+p to cycle role models smol -> slow -> etc
@@ -1,4 +1,5 @@
1
1
  import { type SgrMouseEvent } from "../../mouse";
2
+ import { TERMINAL } from "../../terminal-capabilities";
2
3
  import { type SelectItem, SelectList } from "../../components/select-list";
3
4
  import { Text } from "../../components/text";
4
5
  import { WizardStep } from "../../components/wizard-step";
@@ -111,10 +112,16 @@ class GlyphSceneController implements SetupSceneController {
111
112
  }
112
113
  }
113
114
 
114
- /** Preview and persist the terminal glyph preset. */
115
+ /**
116
+ * Preview and persist the terminal glyph preset. Skipped once the Glyph
117
+ * Protocol handshake confirmed the terminal renders omp's bundled icons: every
118
+ * row renders cleanly there, and the default `unicode` preset already upgrades
119
+ * to nerd at runtime while staying safe on terminals without the protocol.
120
+ */
115
121
  export const glyphSetupScene: SetupScene = {
116
122
  id: "glyph-mode",
117
123
  title: "Choose glyph mode",
118
124
  minVersion: 1,
125
+ shouldRun: () => !TERMINAL.glyphProtocol,
119
126
  mount: host => new GlyphSceneController(host),
120
127
  };
@@ -732,6 +732,12 @@ export interface RuntimeTerminal extends TerminalInfo {
732
732
  textSizing: boolean;
733
733
  /** Whether the terminal implements colon-subparameter styled underlines (curly + colored). */
734
734
  styledUnderlines: boolean;
735
+ /**
736
+ * Whether the terminal answered the Glyph Protocol support query with a
737
+ * `glyf`-capable reply and the bundled icons have been registered. Probe-
738
+ * driven: false until {@link ProcessTerminal} resolves it.
739
+ */
740
+ glyphProtocol: boolean;
735
741
  }
736
742
 
737
743
  export const TERMINAL: RuntimeTerminal = (() => {
@@ -763,6 +769,7 @@ export const TERMINAL: RuntimeTerminal = (() => {
763
769
  // depth), so Apple Terminal and other unproven hosts fall back to the flat
764
770
  // CSI 4 m / CSI 24 m underline the typo renderer needs to avoid black bars.
765
771
  resolved.styledUnderlines = detectStyledUnderlineSupport(resolved.id, Bun.env);
772
+ resolved.glyphProtocol = false;
766
773
  return resolved;
767
774
  })();
768
775
 
@@ -788,6 +795,11 @@ export function setTerminalDeccara(enabled: boolean): void {
788
795
  TERMINAL.deccara = enabled;
789
796
  }
790
797
 
798
+ /** Record the Glyph Protocol probe result (called by ProcessTerminal). */
799
+ export function setTerminalGlyphProtocol(supported: boolean): void {
800
+ TERMINAL.glyphProtocol = supported;
801
+ }
802
+
791
803
  /** Override screen-to-scrollback clear support for targeted renderer tests. */
792
804
  export function setTerminalScreenToScrollback(enabled: boolean): void {
793
805
  TERMINAL.supportsScreenToScrollback = enabled;
package/src/terminal.ts CHANGED
@@ -5,6 +5,14 @@ import { $env, isBunTestRuntime, isTerminalHeadless, isWsl } from "@oh-my-pi/pi-
5
5
  import * as logger from "@oh-my-pi/pi-utils/logger";
6
6
  import * as postmortem from "@oh-my-pi/pi-utils/postmortem";
7
7
  import { restoreTerminalStderr, suppressTerminalStderr } from "@oh-my-pi/pi-utils/stderr-guard";
8
+ import {
9
+ encodeBundledGlyphRegistrations,
10
+ encodeGlyphCoverageQuery,
11
+ encodeGlyphSupportQuery,
12
+ GLYPH_CONFIRMATION_CODEPOINT,
13
+ isGlyphProtocolSequence,
14
+ parseGlyphProtocolReply,
15
+ } from "./glyph-protocol";
8
16
  import { setKittyProtocolActive } from "./keys";
9
17
  import { StdinBuffer } from "./stdin-buffer";
10
18
  import {
@@ -12,6 +20,7 @@ import {
12
20
  NotifyProtocol,
13
21
  setCellDimensions,
14
22
  setOsc99Supported,
23
+ setTerminalGlyphProtocol,
15
24
  TERMINAL,
16
25
  } from "./terminal-capabilities";
17
26
  import { isInsideTmux, wrapTmuxPassthrough } from "./tmux";
@@ -456,6 +465,13 @@ export type TerminalAppearanceRequestToken = number;
456
465
  * set, 4 permanently reset) when the terminal answered DECRQM.
457
466
  */
458
467
  export type PrivateModeReportHandler = (mode: number, supported: boolean, confirmed?: boolean, status?: number) => void;
468
+ /**
469
+ * Fired once when the Glyph Protocol handshake resolves. `supported` is true
470
+ * only after the bundled icons were written and the terminal confirmed (via a
471
+ * `q` coverage query) that a registered codepoint is served from its
472
+ * glossary, so a host can safely repaint — or switch to the nerd preset.
473
+ */
474
+ export type GlyphProtocolReportHandler = (supported: boolean) => void;
459
475
 
460
476
  /**
461
477
  * Cursor shapes addressable via DECSCUSR (`CSI <n> SP q`). `"default"` (0) hands the shape back to
@@ -616,6 +632,14 @@ export interface Terminal {
616
632
  * `status` is the DECRPM value when the terminal answered DECRQM.
617
633
  */
618
634
  onPrivateModeReport?(callback: PrivateModeReportHandler): void;
635
+ /**
636
+ * Register a callback fired once the startup Glyph Protocol handshake
637
+ * resolves (see {@link GlyphProtocolReportHandler}). A subscriber that
638
+ * arrives after the handshake already resolved is called immediately with
639
+ * the stored outcome. Optional so custom Terminals built against older
640
+ * pi-tui versions keep working.
641
+ */
642
+ onGlyphProtocolReport?(callback: GlyphProtocolReportHandler): void;
619
643
  }
620
644
 
621
645
  /**
@@ -637,7 +661,8 @@ type Da1SentinelOwner =
637
661
  | { kind: "keyboard" }
638
662
  | { kind: "osc11" }
639
663
  | { kind: "privateMode"; mode: number }
640
- | { kind: "osc99Probe"; id: string };
664
+ | { kind: "osc99Probe"; id: string }
665
+ | { kind: "glyphProtocol"; phase: "support" | "confirm" };
641
666
 
642
667
  let nextOsc99ProbeId = 1;
643
668
 
@@ -761,6 +786,14 @@ export class ProcessTerminal implements Terminal {
761
786
  #osc99PendingId: string | undefined;
762
787
  #osc99ResponseBuffer = "";
763
788
  #osc99Capabilities = new Map<string, string>();
789
+ /**
790
+ * Handshake phase: `support` awaits the `s` reply, `confirm` awaits the `q`
791
+ * coverage reply sent after the bundle was written.
792
+ */
793
+ #glyphProtocolPhase: "idle" | "support" | "confirm" = "idle";
794
+ #glyphProtocolReplyBuffer = "";
795
+ #glyphProtocolResult: boolean | undefined;
796
+ #glyphProtocolCallbacks: GlyphProtocolReportHandler[] = [];
764
797
  #privateCsiResponseBuffer = "";
765
798
  #da1SentinelOwners: Da1SentinelOwner[] = [];
766
799
  /** Resolved DECRQM support per private mode (mode → supported). */
@@ -860,6 +893,13 @@ export class ProcessTerminal implements Terminal {
860
893
  this.#privateModeCallbacks.push(callback);
861
894
  }
862
895
 
896
+ onGlyphProtocolReport(callback: GlyphProtocolReportHandler): void {
897
+ this.#glyphProtocolCallbacks.push(callback);
898
+ // The handshake runs from enableInput(), which can precede the host's
899
+ // subscription during startup; replay so the outcome is never missed.
900
+ if (this.#glyphProtocolResult !== undefined) callback(this.#glyphProtocolResult);
901
+ }
902
+
863
903
  start(
864
904
  onInput: (data: string) => void,
865
905
  onResize: () => void,
@@ -996,6 +1036,11 @@ export class ProcessTerminal implements Terminal {
996
1036
  // without leaking probe bytes to application input.
997
1037
  this.#queryOsc99Support();
998
1038
 
1039
+ // Glyph Protocol support query (`s` verb), same DA1 sentinel FIFO. A reply
1040
+ // triggers the bundled icon registration so the nerd symbol preset renders
1041
+ // without a patched font installed.
1042
+ this.#queryGlyphProtocolSupport();
1043
+
999
1044
  // Subscribe to Mode 2031 appearance change notifications.
1000
1045
  // When the terminal reports a change, we re-query OSC 11 to get the
1001
1046
  // actual background color (following Neovim convention) with 100ms debounce.
@@ -1133,7 +1178,8 @@ export class ProcessTerminal implements Terminal {
1133
1178
  this.#privateCsiResponseBuffer.length === 0 &&
1134
1179
  this.#inBandResizeBuffer.length === 0 &&
1135
1180
  this.#osc11ResponseBuffer.length === 0 &&
1136
- this.#osc99ResponseBuffer.length === 0
1181
+ this.#osc99ResponseBuffer.length === 0 &&
1182
+ this.#glyphProtocolReplyBuffer.length === 0
1137
1183
  ) {
1138
1184
  if (this.#inputHandler) {
1139
1185
  this.#inputHandler(sequence);
@@ -1292,6 +1338,13 @@ export class ProcessTerminal implements Terminal {
1292
1338
  this.#resolveOsc99Support(owner.id, false);
1293
1339
  break;
1294
1340
  }
1341
+ case "glyphProtocol": {
1342
+ // The support-phase sentinel is answered after the `s` reply that
1343
+ // already advanced the handshake; only a sentinel from the phase
1344
+ // still awaiting its reply means that reply never came.
1345
+ if (owner.phase === this.#glyphProtocolPhase) this.#resolveGlyphProtocolSupport(false);
1346
+ break;
1347
+ }
1295
1348
  }
1296
1349
  return;
1297
1350
  }
@@ -1376,6 +1429,23 @@ export class ProcessTerminal implements Terminal {
1376
1429
  }
1377
1430
  }
1378
1431
 
1432
+ // Glyph Protocol APC replies (`ESC _ 25a1 ; … ESC \`). Swallowed for
1433
+ // the whole session, not just while the probe is outstanding: an APC is
1434
+ // exclusively terminal->host data, and registrations use `reply=0`, so
1435
+ // any late `r`/`c` acknowledgement must never reach the composer.
1436
+ if (this.#glyphProtocolReplyBuffer || sequence.startsWith("\x1b_25a1;")) {
1437
+ if (this.#glyphProtocolReplyBuffer && sequence.startsWith("\x1b") && sequence !== "\x1b\\") {
1438
+ this.#glyphProtocolReplyBuffer = "";
1439
+ } else {
1440
+ this.#glyphProtocolReplyBuffer += sequence;
1441
+ if (!this.#glyphProtocolReplyBuffer.endsWith("\x1b\\")) return;
1442
+ const reply = this.#glyphProtocolReplyBuffer;
1443
+ this.#glyphProtocolReplyBuffer = "";
1444
+ this.#handleGlyphProtocolReply(reply);
1445
+ return;
1446
+ }
1447
+ }
1448
+
1379
1449
  // Mode 2031 change notification: re-query OSC 11 with 100ms debounce
1380
1450
  // (Neovim convention — coalesces rapid notifications during transitions)
1381
1451
  const appearanceMatch = sequence.match(appearanceDsrPattern);
@@ -1504,6 +1574,80 @@ export class ProcessTerminal implements Terminal {
1504
1574
  setOsc99Supported(supported);
1505
1575
  }
1506
1576
 
1577
+ #shouldQueryGlyphProtocolSupport(): boolean {
1578
+ // `PI_NO_GLYPH_PROTOCOL=1` keeps the terminal's own font coverage (tofu
1579
+ // included) — the registration shadows a system Nerd Font at render time.
1580
+ if ($env.PI_NO_GLYPH_PROTOCOL === "1") return false;
1581
+ // Same multiplexer rule as the OSC 99 probe: tmux/screen cannot route the
1582
+ // APC reply back to the sending pane, so it would leak as literal text.
1583
+ if (isInsideTerminalMultiplexer($env)) return false;
1584
+ return !isBunTestRuntime() || $env.PI_TUI_GLYPH_PROTOCOL_PROBE === "1";
1585
+ }
1586
+
1587
+ #queryGlyphProtocolSupport(): void {
1588
+ setTerminalGlyphProtocol(false);
1589
+ this.#glyphProtocolPhase = "idle";
1590
+ this.#glyphProtocolResult = undefined;
1591
+ this.#glyphProtocolReplyBuffer = "";
1592
+ if (this.#dead || !this.#shouldQueryGlyphProtocolSupport()) return;
1593
+ this.#glyphProtocolPhase = "support";
1594
+ this.#da1SentinelOwners.push({ kind: "glyphProtocol", phase: "support" });
1595
+ this.#safeWrite(`${encodeGlyphSupportQuery()}\x1b[c`);
1596
+ }
1597
+
1598
+ #handleGlyphProtocolReply(sequence: string): void {
1599
+ if (!isGlyphProtocolSequence(sequence)) return;
1600
+ const reply = parseGlyphProtocolReply(sequence);
1601
+ if (!reply) {
1602
+ logger.debug("Glyph Protocol: unparsable reply", { sequence });
1603
+ return;
1604
+ }
1605
+ if (reply.verb === "s" && this.#glyphProtocolPhase === "support") {
1606
+ // Every bundled payload is `fmt=glyf`; a terminal that recognises the
1607
+ // protocol but advertises no formats would reject each registration.
1608
+ if (!reply.formats.includes("glyf") || this.#dead) {
1609
+ this.#resolveGlyphProtocolSupport(false);
1610
+ return;
1611
+ }
1612
+ // Registrations are fire-and-forget (`reply=0`); the coverage query
1613
+ // that follows them is the success check — the terminal processes the
1614
+ // stream in order, so a `glossary` answer proves the bundle landed.
1615
+ this.#glyphProtocolPhase = "confirm";
1616
+ this.#da1SentinelOwners.push({ kind: "glyphProtocol", phase: "confirm" });
1617
+ this.#safeWrite(
1618
+ `${encodeBundledGlyphRegistrations()}${encodeGlyphCoverageQuery(GLYPH_CONFIRMATION_CODEPOINT)}\x1b[c`,
1619
+ );
1620
+ return;
1621
+ }
1622
+ if (reply.verb === "q" && this.#glyphProtocolPhase === "confirm" && reply.cp === GLYPH_CONFIRMATION_CODEPOINT) {
1623
+ const confirmed = reply.coverage.includes("glossary");
1624
+ if (!confirmed) logger.warn("Glyph Protocol: registered codepoint not served from glossary", reply);
1625
+ this.#resolveGlyphProtocolSupport(confirmed);
1626
+ return;
1627
+ }
1628
+ // Registrations are sent with `reply=0`, so this is a terminal that
1629
+ // ignored the reply gate or a stray acknowledgement: keep it out of input.
1630
+ if ((reply.verb === "r" || reply.verb === "c") && reply.status !== 0) {
1631
+ logger.warn("Glyph Protocol: registration rejected", reply);
1632
+ }
1633
+ }
1634
+
1635
+ /** Finish the handshake in either phase and notify subscribers once. */
1636
+ #resolveGlyphProtocolSupport(supported: boolean): void {
1637
+ if (this.#glyphProtocolPhase === "idle") return;
1638
+ this.#glyphProtocolPhase = "idle";
1639
+ this.#glyphProtocolReplyBuffer = "";
1640
+ this.#glyphProtocolResult = supported;
1641
+ setTerminalGlyphProtocol(supported);
1642
+ for (const cb of this.#glyphProtocolCallbacks) {
1643
+ try {
1644
+ cb(supported);
1645
+ } catch {
1646
+ // Ignore subscriber errors — capability reporting must not crash input.
1647
+ }
1648
+ }
1649
+ }
1650
+
1507
1651
  /**
1508
1652
  * Parse an OSC 11 background color response and compute BT.601 luminance.
1509
1653
  * Handles 1-, 2-, 3-, and 4-digit XParseColor hex components.
@@ -1810,6 +1954,11 @@ export class ProcessTerminal implements Terminal {
1810
1954
  this.#osc99ResponseBuffer = "";
1811
1955
  this.#osc99Capabilities.clear();
1812
1956
  setOsc99Supported(false);
1957
+ this.#glyphProtocolPhase = "idle";
1958
+ this.#glyphProtocolResult = undefined;
1959
+ this.#glyphProtocolReplyBuffer = "";
1960
+ this.#glyphProtocolCallbacks = [];
1961
+ setTerminalGlyphProtocol(false);
1813
1962
  this.#privateCsiResponseBuffer = "";
1814
1963
  this.#inBandResizeBuffer = "";
1815
1964
  this.#da1SentinelOwners.length = 0;