@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
package/CHANGELOG.md CHANGED
@@ -2,6 +2,25 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [18.2.7] - 2026-09-21
6
+
7
+ ### Breaking Changes
8
+
9
+ - Removed specialized keyword modules in favor of a centralized registration system
10
+
11
+ ### Added
12
+
13
+ - Added `find` tool renderer to display search results with hit ranking and score visualization
14
+ - Supported collapsing/expanding search hit details and range snippets
15
+ - Enabled file hyperlinking for navigation to absolute paths in search results
16
+ - Added streaming progress display for incomplete find tool operations
17
+ - Added Glyph Protocol support for rendering icons without requiring patched fonts
18
+ - Added `setMagicKeywords` for dynamic configuration of highlightable magic keywords
19
+
20
+ ### Fixed
21
+
22
+ - Prevented magic keywords from triggering spelling autocorrect and underlining
23
+
5
24
  ## [18.2.5] - 2026-09-17
6
25
 
7
26
  ### Added
@@ -335,43 +335,6 @@ This license allows the work and adaptations of it to be shared and used
335
335
  commercially, as long as it is attributed to Poppy Works. The font is bundled
336
336
  here (crates/pi-natives/src/fonts/Silver.ttf) as a CJK/Unicode bitmap fallback.
337
337
 
338
- -------------------------------------------------------------------------------
339
- packages/utils/src/vendor/mermaid-ascii/NOTICE
340
-
341
- This directory contains an in-house Mermaid-diagram-to-ASCII renderer adapted
342
- from beautiful-mermaid (https://github.com/lukilabs/beautiful-mermaid), used
343
- under the MIT License.
344
-
345
- Copyright (c) 2026 Craft Docs
346
-
347
- Only the ASCII rendering pipeline is ported (flowchart/state, sequence, class,
348
- ER, and xychart diagrams); the SVG renderer and its `elkjs` graph-layout
349
- dependency, the browser entry point, and the SVG theme/style modules were
350
- dropped. Terminal display width is reimplemented on `Bun.stringWidth`, and
351
- inline label formatting (HTML tags, markdown emphasis) is reduced to plain text
352
- for ASCII output. Layout and edge-routing logic is preserved faithfully so
353
- ASCII output matches the upstream package.
354
-
355
- MIT License
356
-
357
- Permission is hereby granted, free of charge, to any person obtaining a copy
358
- of this software and associated documentation files (the "Software"), to deal
359
- in the Software without restriction, including without limitation the rights
360
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
361
- copies of the Software, and to permit persons to whom the Software is
362
- furnished to do so, subject to the following conditions:
363
-
364
- The above copyright notice and this permission notice shall be included in all
365
- copies or substantial portions of the Software.
366
-
367
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
368
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
369
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
370
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
371
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
372
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
373
- SOFTWARE.
374
-
375
338
  -------------------------------------------------------------------------------
376
339
  packages/coding-agent/src/markit/NOTICE
377
340
 
@@ -0,0 +1,62 @@
1
+ /** One bundled outline: base64 simple-glyph record plus its scale policy. */
2
+ export interface GlyphBundleEntry {
3
+ /** Base64 OpenType `glyf` simple-glyph record (§8.2 subset). */
4
+ glyf: string;
5
+ /** Fill the cell edge to edge (`size=stretch`, powerline); otherwise `size=contain`. */
6
+ stretch?: boolean;
7
+ }
8
+ /** Generated icon bundle: shared design metrics plus outlines keyed by lowercase hex codepoint. */
9
+ export interface GlyphBundle {
10
+ upm: number;
11
+ /** Authored advance width — identical for every glyph of a Mono face. */
12
+ aw: number;
13
+ /** Authored line height (ascender − descender); outlines are lifted so y=0 is the descender line. */
14
+ lh: number;
15
+ glyphs: Record<string, GlyphBundleEntry>;
16
+ }
17
+ export declare const GLYPH_BUNDLE: GlyphBundle;
18
+ /**
19
+ * Codepoint the handshake queries after registration to confirm the bundle
20
+ * was accepted: the omp mark (`icon.omp`), which the status line always shows.
21
+ */
22
+ export declare const GLYPH_CONFIRMATION_CODEPOINT = 986455;
23
+ /** Terminal reply to any Glyph Protocol verb, decoded from its APC body. */
24
+ export type GlyphProtocolReply = {
25
+ verb: "s";
26
+ formats: string[];
27
+ } | {
28
+ verb: "q";
29
+ cp: number;
30
+ coverage: string[];
31
+ } | {
32
+ verb: "r";
33
+ cp: number;
34
+ status: number;
35
+ reason?: string;
36
+ } | {
37
+ verb: "c";
38
+ status: number;
39
+ reason?: string;
40
+ };
41
+ /** Whether `cp` lies in one of the three Private Use Areas the protocol accepts. */
42
+ export declare function isPrivateUseCodepoint(cp: number): boolean;
43
+ /** `s` verb: doubles as the protocol-detection ping (any reply confirms support). */
44
+ export declare function encodeGlyphSupportQuery(): string;
45
+ /** `q` verb: ask which sources (system font, session glossary) render `cp`. */
46
+ export declare function encodeGlyphCoverageQuery(cp: number): string;
47
+ /**
48
+ * `r` verb for one bundled outline at `cp`. Layout hints follow the bundle's
49
+ * design metrics; `reply=0` suppresses the acknowledgement so bulk startup
50
+ * registration leaves nothing on stdin to drain.
51
+ */
52
+ export declare function encodeGlyphRegistration(cp: number, entry: GlyphBundleEntry, metrics?: GlyphBundle): string;
53
+ /** Every bundled icon as one contiguous write. */
54
+ export declare function encodeBundledGlyphRegistrations(metrics?: GlyphBundle): string;
55
+ /** Whether `sequence` is a complete Glyph Protocol APC message. */
56
+ export declare function isGlyphProtocolSequence(sequence: string): boolean;
57
+ /**
58
+ * Decode a terminal reply. Returns null for anything that is not a well-formed
59
+ * Glyph Protocol message; unknown keys are ignored per §11 so future
60
+ * parameters never break detection.
61
+ */
62
+ export declare function parseGlyphProtocolReply(sequence: string): GlyphProtocolReply | null;
@@ -37,6 +37,7 @@ export type * from "./editor-component.js";
37
37
  export * from "./fuzzy.js";
38
38
  export * from "./keybindings.js";
39
39
  export * from "./keys.js";
40
+ export * from "./glyph-protocol.js";
40
41
  export * from "./kitty-graphics.js";
41
42
  export * from "./latex-block.js";
42
43
  export * from "./latex-to-unicode.js";
@@ -1,11 +1,14 @@
1
1
  import type { Model } from "@oh-my-pi/pi-ai";
2
+ import type { ModelKind } from "@oh-my-pi/pi-catalog/types";
2
3
  import type { Component } from "../tui.js";
3
4
  import type { SgrMouseEvent } from "../mouse.js";
4
5
  import { type ConfiguredThinkingLevel } from "../thinking.js";
5
6
  import { type ThemeColor } from "../theme/theme.js";
6
7
  /** Canonical display ordering of built-in model roles. */
7
- export type ModelRole = "default" | "smol" | "slow" | "vision" | "plan" | "commit" | "tiny" | "task" | "advisor";
8
+ export type ModelRole = "default" | "smol" | "slow" | "vision" | "plan" | "commit" | "tiny" | "memory" | "task" | "advisor" | "image" | "web" | "speech" | "dictation" | "judge";
8
9
  export declare const MODEL_ROLE_IDS: ModelRole[];
10
+ export declare const CHAT_MODEL_ROLE_IDS: ModelRole[];
11
+ export declare const KIND_ROLE_IDS: ModelRole[];
9
12
  /** Measured model performance shown in browser rows. */
10
13
  export interface ModelBrowserPerf {
11
14
  samples: number;
@@ -18,6 +21,8 @@ export interface ModelBrowserRoleInfo {
18
21
  name: string;
19
22
  color?: ThemeColor;
20
23
  hidden?: boolean;
24
+ section: "chat" | "kind";
25
+ accepts(model: Model): boolean;
21
26
  }
22
27
  /** Role lookup used for scoped model resolution. */
23
28
  export interface ModelRoleLookup {
@@ -38,13 +43,14 @@ export interface ModelBrowserSource extends ModelRoleLookup {
38
43
  readonly mruOrder: readonly string[];
39
44
  readonly modelPerf: ReadonlyMap<string, ModelBrowserPerf>;
40
45
  getRoleInfo(role: string): ModelBrowserRoleInfo;
46
+ defaultRoleChain(role: string): string[];
41
47
  resolveRoleValue(value: string | undefined, models: Model[], roleLookup?: ModelRoleLookup): ResolvedModelRoleValue;
42
48
  }
43
49
  /** Read-only catalog surface consumed by model browsers. */
44
50
  export interface ModelBrowserRegistry {
45
51
  getError(): unknown;
46
- getAvailable(): Model[];
47
- getAll(): Model[];
52
+ getAvailable(kind?: ModelKind | "all"): Model[];
53
+ getAll(kind?: ModelKind | "all"): Model[];
48
54
  }
49
55
  /** One selectable row. `selector` is a canonical model key or host-specific virtual key. */
50
56
  export interface ModelBrowserItem {
@@ -9,8 +9,8 @@
9
9
  export type KeywordHighlighter = (text: string, resetTo?: string, phase?: number) => string;
10
10
  /** Declarative spec for {@link createGradientHighlighter}. */
11
11
  export interface GradientHighlightSpec {
12
- /** Cheap, stateless presence probe used to skip the boundary regex on most lines. Must be non-global. */
13
- probe: RegExp;
12
+ /** Literal substring whose absence skips the boundary regex on most lines. */
13
+ probe: string;
14
14
  /** Global, word-bounded match regex walked by `.replace`. */
15
15
  highlight: RegExp;
16
16
  /** Number of color stops swept across the gradient. */
@@ -1,10 +1,37 @@
1
1
  /**
2
- * Gradient-highlight every magic keyword ("ultrathink", "orchestrate",
3
- * "workflowz") that appears as standalone prose, skipping any occurrence inside a
4
- * code block, inline code span, or XML/HTML section. Each highlighter paints its
5
- * own keyword with its own gradient, so chaining is order-independent — the
6
- * earlier passes only inject zero-width SGR escapes (no backticks or angle
7
- * brackets), which never confuse the later passes' markdown masking.
2
+ * Magic-keyword engine: standalone prose words the host registers via
3
+ * {@link setMagicKeywords} glow with a per-word gradient in the editor and in
4
+ * sent bubbles, and are exempt from spelling autocorrect. The host (coding-agent
5
+ * `modes/magic-keywords.ts`) owns the word list and the notices each word
6
+ * injects; this module only knows how to find and paint them.
7
+ */
8
+ /** One registered magic keyword: the prose trigger and its editor gradient. */
9
+ export interface MagicKeywordSpec {
10
+ /** Exact lowercase trigger, matched only as standalone prose. */
11
+ readonly word: string;
12
+ /** HSL hue sweep `[from, to]` in degrees painted across the word; `to` may exceed 360 to wrap through red. */
13
+ readonly hue: readonly [number, number];
14
+ }
15
+ /**
16
+ * Replace the registered keyword set. The host calls this once at startup;
17
+ * until then nothing glows and no word is exempt from autocorrect.
18
+ */
19
+ export declare function setMagicKeywords(specs: readonly MagicKeywordSpec[]): void;
20
+ /** Whether `word` is exactly a registered magic keyword (used to shield it from spelling autocorrect). */
21
+ export declare function isMagicKeyword(word: string): boolean;
22
+ /**
23
+ * Whether `text` contains `word` as standalone lowercase prose — never inside
24
+ * a code block, inline code span, or XML/HTML section. Pure: does not require
25
+ * `word` to be registered.
26
+ */
27
+ export declare function containsMagicKeyword(text: string, word: string): boolean;
28
+ /**
29
+ * Gradient-highlight every registered keyword that appears as standalone
30
+ * prose, skipping any occurrence inside a code block, inline code span, or
31
+ * XML/HTML section. Each pass paints one word with its own gradient, so order
32
+ * is irrelevant — earlier passes only inject zero-width SGR escapes (no
33
+ * backticks or angle brackets), which never confuse later passes' markdown
34
+ * masking.
8
35
  *
9
36
  * `resetTo` is the SGR foreground sequence restored after each painted keyword;
10
37
  * pass the surrounding text color when decorating already-colored content (e.g.
@@ -18,9 +45,9 @@
18
45
  */
19
46
  export declare function highlightMagicKeywords(text: string, resetTo?: string, phase?: number): string;
20
47
  /**
21
- * Cheap test for "does this text contain any magic keyword as standalone prose?".
22
- * Short-circuits on a substring probe before paying for the markdown-aware
23
- * prose check, so the common "no keyword in buffer" path is just three
24
- * `String#indexOf`s. Used by the live editor to gate the shimmer timer.
48
+ * Cheap test for "does this text contain any registered keyword as standalone
49
+ * prose?". Short-circuits on a substring probe before paying for the
50
+ * markdown-aware prose check, so the common "no keyword in buffer" path is one
51
+ * `String#includes` per word. Used by the live editor to gate the shimmer timer.
25
52
  */
26
53
  export declare function hasMagicKeyword(text: string): boolean;
@@ -1,3 +1,8 @@
1
1
  import type { SetupScene } from "./types.js";
2
- /** Preview and persist the terminal glyph preset. */
2
+ /**
3
+ * Preview and persist the terminal glyph preset. Skipped once the Glyph
4
+ * Protocol handshake confirmed the terminal renders omp's bundled icons: every
5
+ * row renders cleanly there, and the default `unicode` preset already upgrades
6
+ * to nerd at runtime while staying safe on terminals without the protocol.
7
+ */
3
8
  export declare const glyphSetupScene: SetupScene;
@@ -215,6 +215,12 @@ export interface RuntimeTerminal extends TerminalInfo {
215
215
  textSizing: boolean;
216
216
  /** Whether the terminal implements colon-subparameter styled underlines (curly + colored). */
217
217
  styledUnderlines: boolean;
218
+ /**
219
+ * Whether the terminal answered the Glyph Protocol support query with a
220
+ * `glyf`-capable reply and the bundled icons have been registered. Probe-
221
+ * driven: false until {@link ProcessTerminal} resolves it.
222
+ */
223
+ glyphProtocol: boolean;
218
224
  }
219
225
  export declare const TERMINAL: RuntimeTerminal;
220
226
  /**
@@ -227,6 +233,8 @@ export declare function setTerminalImageProtocol(imageProtocol: ImageProtocol |
227
233
  * resolved once at import and force-disabled under the test runtime.
228
234
  */
229
235
  export declare function setTerminalDeccara(enabled: boolean): void;
236
+ /** Record the Glyph Protocol probe result (called by ProcessTerminal). */
237
+ export declare function setTerminalGlyphProtocol(supported: boolean): void;
230
238
  /** Override screen-to-scrollback clear support for targeted renderer tests. */
231
239
  export declare function setTerminalScreenToScrollback(enabled: boolean): void;
232
240
  /**
@@ -106,6 +106,13 @@ export type TerminalAppearanceRequestToken = number;
106
106
  * set, 4 permanently reset) when the terminal answered DECRQM.
107
107
  */
108
108
  export type PrivateModeReportHandler = (mode: number, supported: boolean, confirmed?: boolean, status?: number) => void;
109
+ /**
110
+ * Fired once when the Glyph Protocol handshake resolves. `supported` is true
111
+ * only after the bundled icons were written and the terminal confirmed (via a
112
+ * `q` coverage query) that a registered codepoint is served from its
113
+ * glossary, so a host can safely repaint — or switch to the nerd preset.
114
+ */
115
+ export type GlyphProtocolReportHandler = (supported: boolean) => void;
109
116
  /**
110
117
  * Cursor shapes addressable via DECSCUSR (`CSI <n> SP q`). `"default"` (0) hands the shape back to
111
118
  * the terminal's own configuration, which is what teardown restores rather than guessing a shape
@@ -208,6 +215,14 @@ export interface Terminal {
208
215
  * `status` is the DECRPM value when the terminal answered DECRQM.
209
216
  */
210
217
  onPrivateModeReport?(callback: PrivateModeReportHandler): void;
218
+ /**
219
+ * Register a callback fired once the startup Glyph Protocol handshake
220
+ * resolves (see {@link GlyphProtocolReportHandler}). A subscriber that
221
+ * arrives after the handshake already resolved is called immediately with
222
+ * the stored outcome. Optional so custom Terminals built against older
223
+ * pi-tui versions keep working.
224
+ */
225
+ onGlyphProtocolReport?(callback: GlyphProtocolReportHandler): void;
211
226
  }
212
227
  /**
213
228
  * True when stdout flows through a ConPTY pseudo-console (native win32, or
@@ -254,6 +269,7 @@ export declare class ProcessTerminal implements Terminal {
254
269
  */
255
270
  refreshAppearance(requestToken?: TerminalAppearanceRequestToken): TerminalAppearanceRequestToken | void;
256
271
  onPrivateModeReport(callback: PrivateModeReportHandler): void;
272
+ onGlyphProtocolReport(callback: GlyphProtocolReportHandler): void;
257
273
  start(onInput: (data: string) => void, onResize: () => void, onDisconnect?: () => void, options?: TerminalStartOptions): void;
258
274
  enableInput(): void;
259
275
  drainInput(maxMs?: number, idleMs?: number): Promise<void>;
@@ -1,10 +1,10 @@
1
- import { type MermaidAsciiRenderOptions } from "@oh-my-pi/pi-utils/mermaid-ascii";
1
+ import type { MermaidRenderOptions } from "@oh-my-pi/pi-natives";
2
2
  /**
3
3
  * Options controlling how fenced Mermaid source is resolved to terminal ASCII.
4
4
  * Extends the raw render options (theme, color mode, spacing, `useAscii`) with a
5
5
  * viewport-fitting hint.
6
6
  */
7
- export interface MermaidResolveOptions extends MermaidAsciiRenderOptions {
7
+ export interface MermaidResolveOptions extends MermaidRenderOptions {
8
8
  /**
9
9
  * Maximum display width (terminal columns) the diagram should occupy. A
10
10
  * layout that overflows this width is re-rendered in the perpendicular
@@ -68,10 +68,11 @@ interface EvalRenderContext {
68
68
  timeout?: number;
69
69
  }
70
70
  /**
71
- * Append or replace a status event. `agent` events are progress snapshots keyed
72
- * by `id`, so they coalesce in place (preserving first-seen order); every other
73
- * op is a discrete action and simply appends. Keeps the persisted event list
74
- * bounded even when a subagent emits hundreds of throttled progress ticks.
71
+ * Append or replace a status event. `agent` and `judge_batch` events are
72
+ * progress snapshots keyed by `id`, so they coalesce in place (preserving
73
+ * first-seen order); every other op is a discrete action and simply appends.
74
+ * Keeps the persisted event list bounded even when a subagent or batch emits
75
+ * hundreds of throttled progress ticks.
75
76
  */
76
77
  export declare function upsertStatusEvent(events: EvalStatusEvent[], event: EvalStatusEvent): void;
77
78
  /** Render eval code cells, structured display output, and progress events. */
@@ -0,0 +1,82 @@
1
+ import type { Theme } from "../theme/theme.js";
2
+ import type { Component } from "../tui.js";
3
+ import type { OutputMeta } from "./output-meta.js";
4
+ import type { RenderResultOptions } from "./renderer.js";
5
+ /** A verified line range with its yes-probability and a one-line preview. */
6
+ export interface FindRange {
7
+ start: number;
8
+ end: number;
9
+ p: number;
10
+ snippet: string;
11
+ }
12
+ /** A file whose verified passages cleared the threshold; `ranges` are merged positive spans, strongest first. */
13
+ export interface FindHit {
14
+ /** Display path relative to {@link FindToolDetails.cwd}. */
15
+ rel: string;
16
+ /** Filename judgment, when the name batch answered. */
17
+ nameScore?: number;
18
+ /** Best verified passage probability. */
19
+ contentScore: number;
20
+ ranges: FindRange[];
21
+ /** Lines of content actually judged, and whether the file held more. */
22
+ linesSeen: number;
23
+ truncated: boolean;
24
+ }
25
+ /** Search accounting reported alongside the hits. */
26
+ export interface FindStats {
27
+ /** Eligible files under the root. */
28
+ listed: number;
29
+ requests: number;
30
+ errors: number;
31
+ /** Entries judged by name. */
32
+ judged: number;
33
+ /** Files whose content was read and sent. */
34
+ filesRead: number;
35
+ fileBytes: number;
36
+ inputTokens: number;
37
+ outputTokens: number;
38
+ cost: number;
39
+ apiMs: number;
40
+ windowsJudged: number;
41
+ windowsPruned: number;
42
+ mapCards: number;
43
+ /** Distinct request failures, phase-prefixed. */
44
+ failures: string[];
45
+ }
46
+ /** Display metadata for find tool results. */
47
+ export interface FindToolDetails {
48
+ query: string;
49
+ /** Lexical keywords actually used: derived from the query plus `grep_keywords`. */
50
+ keywords: string[];
51
+ /** Verified-passage probability at or above which a file is a hit. */
52
+ threshold: number;
53
+ hits: FindHit[];
54
+ stats: FindStats;
55
+ elapsedMs: number;
56
+ /** Session cwd; hit paths are relative to it. */
57
+ cwd: string;
58
+ /** Display form of the searched directory when narrower than cwd. */
59
+ scopePath?: string;
60
+ meta?: OutputMeta;
61
+ }
62
+ interface FindRenderArgs {
63
+ query?: string;
64
+ grep_keywords?: string[];
65
+ path?: string;
66
+ }
67
+ /** Render find calls and results in the transcript. */
68
+ export declare const findToolRenderer: {
69
+ renderCall(args: FindRenderArgs, options: RenderResultOptions, uiTheme: Theme): Component;
70
+ renderResult(result: {
71
+ content: Array<{
72
+ type: string;
73
+ text?: string;
74
+ }>;
75
+ details?: FindToolDetails;
76
+ isError?: boolean;
77
+ }, options: RenderResultOptions, uiTheme: Theme, args?: FindRenderArgs): Component;
78
+ mergeCallAndResult: true;
79
+ animatedPendingPreview: true;
80
+ animatedPartialResult: true;
81
+ };
82
+ export {};
@@ -66,6 +66,10 @@ export declare const SEARCH_PROVIDER_OPTIONS: readonly [{
66
66
  readonly value: "xai";
67
67
  readonly label: "xAI";
68
68
  readonly description: "Grok web search via xAI Responses API (uses SuperGrok/X Premium+ OAuth via /login xai-oauth, or XAI_API_KEY)";
69
+ }, {
70
+ readonly value: "openrouter";
71
+ readonly label: "OpenRouter";
72
+ readonly description: "OpenRouter plugins-based web search with model-selected grounding";
69
73
  }, {
70
74
  readonly value: "zai";
71
75
  readonly label: "Z.AI";
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "type": "module",
3
3
  "name": "@oh-my-pi/pi-tui",
4
- "version": "18.2.5",
4
+ "version": "18.2.7",
5
5
  "description": "Terminal User Interface library with differential rendering for efficient text-based applications",
6
6
  "homepage": "https://omp.sh",
7
7
  "author": "Stencil Labs, Inc.",
@@ -37,14 +37,14 @@
37
37
  "fmt": "oxfmt --no-error-on-unmatched-pattern 'src/**/*.{ts,tsx}' '{test,bench,examples,scripts}/**/*.ts' '*.ts'"
38
38
  },
39
39
  "dependencies": {
40
- "@oh-my-pi/omptype": "18.2.5",
41
- "@oh-my-pi/pi-agent-core": "18.2.5",
42
- "@oh-my-pi/pi-ai": "18.2.5",
43
- "@oh-my-pi/pi-catalog": "18.2.5",
44
- "@oh-my-pi/pi-natives": "18.2.5",
45
- "@oh-my-pi/pi-utils": "18.2.5",
46
- "@oh-my-pi/pi-wire": "18.2.5",
47
- "@oh-my-pi/snapcompact": "18.2.5"
40
+ "@oh-my-pi/omptype": "18.2.7",
41
+ "@oh-my-pi/pi-agent-core": "18.2.7",
42
+ "@oh-my-pi/pi-ai": "18.2.7",
43
+ "@oh-my-pi/pi-catalog": "18.2.7",
44
+ "@oh-my-pi/pi-natives": "18.2.7",
45
+ "@oh-my-pi/pi-utils": "18.2.7",
46
+ "@oh-my-pi/pi-wire": "18.2.7",
47
+ "@oh-my-pi/snapcompact": "18.2.7"
48
48
  },
49
49
  "devDependencies": {
50
50
  "kitty-vt-wasm": "^0.2.0"
@@ -0,0 +1,143 @@
1
+ /**
2
+ * Glyph Protocol client: ships the TUI's private-use icons to the terminal so
3
+ * the nerd symbol preset renders without a patched font installed.
4
+ *
5
+ * Transport is APC `ESC _ 25a1 ; <verb> [; k=v]* [; payload] ESC \` with four
6
+ * verbs — `s` (support query), `q` (coverage query), `r` (register), `c`
7
+ * (clear). {@link ProcessTerminal} drives the handshake: `s` behind its DA1
8
+ * sentinel at startup; on a `glyf`-capable reply it writes
9
+ * {@link encodeBundledGlyphRegistrations} in one shot followed by a `q` for
10
+ * {@link GLYPH_CONFIRMATION_CODEPOINT}, and a `glossary` answer confirms the
11
+ * bundle landed. Registrations are fire-and-forget (`reply=0`),
12
+ * session-scoped, and confined to the PUA ranges, so they can never restyle
13
+ * real text.
14
+ *
15
+ * Spec: https://github.com/raphamorim/rio/blob/main/specs/glyph-protocol.md
16
+ * The bundle is generated by `scripts/gen-glyphs.ts` (`bun run gen:glyphs`).
17
+ */
18
+ import bundle from "./theme/glyph-bundle.json" with { type: "json" };
19
+
20
+ /** Protocol identifier: U+25A1 WHITE SQUARE as lowercase hex. */
21
+ const GLYPH_PROTOCOL_ID = "25a1";
22
+ const APC = "\x1b_";
23
+ const ST = "\x1b\\";
24
+
25
+ /** One bundled outline: base64 simple-glyph record plus its scale policy. */
26
+ export interface GlyphBundleEntry {
27
+ /** Base64 OpenType `glyf` simple-glyph record (§8.2 subset). */
28
+ glyf: string;
29
+ /** Fill the cell edge to edge (`size=stretch`, powerline); otherwise `size=contain`. */
30
+ stretch?: boolean;
31
+ }
32
+
33
+ /** Generated icon bundle: shared design metrics plus outlines keyed by lowercase hex codepoint. */
34
+ export interface GlyphBundle {
35
+ upm: number;
36
+ /** Authored advance width — identical for every glyph of a Mono face. */
37
+ aw: number;
38
+ /** Authored line height (ascender − descender); outlines are lifted so y=0 is the descender line. */
39
+ lh: number;
40
+ glyphs: Record<string, GlyphBundleEntry>;
41
+ }
42
+
43
+ export const GLYPH_BUNDLE: GlyphBundle = bundle;
44
+
45
+ /**
46
+ * Codepoint the handshake queries after registration to confirm the bundle
47
+ * was accepted: the omp mark (`icon.omp`), which the status line always shows.
48
+ */
49
+ export const GLYPH_CONFIRMATION_CODEPOINT = 0xf0d57;
50
+
51
+ /** Terminal reply to any Glyph Protocol verb, decoded from its APC body. */
52
+ export type GlyphProtocolReply =
53
+ | { verb: "s"; formats: string[] }
54
+ | { verb: "q"; cp: number; coverage: string[] }
55
+ | { verb: "r"; cp: number; status: number; reason?: string }
56
+ | { verb: "c"; status: number; reason?: string };
57
+
58
+ /** Whether `cp` lies in one of the three Private Use Areas the protocol accepts. */
59
+ export function isPrivateUseCodepoint(cp: number): boolean {
60
+ return (cp >= 0xe000 && cp <= 0xf8ff) || (cp >= 0xf0000 && cp <= 0xffffd) || (cp >= 0x100000 && cp <= 0x10fffd);
61
+ }
62
+
63
+ /** `s` verb: doubles as the protocol-detection ping (any reply confirms support). */
64
+ export function encodeGlyphSupportQuery(): string {
65
+ return `${APC}${GLYPH_PROTOCOL_ID};s${ST}`;
66
+ }
67
+
68
+ /** `q` verb: ask which sources (system font, session glossary) render `cp`. */
69
+ export function encodeGlyphCoverageQuery(cp: number): string {
70
+ return `${APC}${GLYPH_PROTOCOL_ID};q;cp=${cp.toString(16)}${ST}`;
71
+ }
72
+
73
+ /**
74
+ * `r` verb for one bundled outline at `cp`. Layout hints follow the bundle's
75
+ * design metrics; `reply=0` suppresses the acknowledgement so bulk startup
76
+ * registration leaves nothing on stdin to drain.
77
+ */
78
+ export function encodeGlyphRegistration(
79
+ cp: number,
80
+ entry: GlyphBundleEntry,
81
+ metrics: GlyphBundle = GLYPH_BUNDLE,
82
+ ): string {
83
+ if (!isPrivateUseCodepoint(cp))
84
+ throw new RangeError(`U+${cp.toString(16).toUpperCase()} is not a private-use codepoint`);
85
+ const size = entry.stretch ? "stretch" : "contain";
86
+ return `${APC}${GLYPH_PROTOCOL_ID};r;cp=${cp.toString(16)};reply=0;upm=${metrics.upm};aw=${metrics.aw};lh=${metrics.lh};size=${size};${entry.glyf}${ST}`;
87
+ }
88
+
89
+ /** Every bundled icon as one contiguous write. */
90
+ export function encodeBundledGlyphRegistrations(metrics: GlyphBundle = GLYPH_BUNDLE): string {
91
+ let out = "";
92
+ for (const hex in metrics.glyphs) {
93
+ out += encodeGlyphRegistration(Number.parseInt(hex, 16), metrics.glyphs[hex]!, metrics);
94
+ }
95
+ return out;
96
+ }
97
+
98
+ /** Whether `sequence` is a complete Glyph Protocol APC message. */
99
+ export function isGlyphProtocolSequence(sequence: string): boolean {
100
+ return sequence.startsWith(`${APC}${GLYPH_PROTOCOL_ID};`) && sequence.endsWith(ST);
101
+ }
102
+
103
+ /**
104
+ * Decode a terminal reply. Returns null for anything that is not a well-formed
105
+ * Glyph Protocol message; unknown keys are ignored per §11 so future
106
+ * parameters never break detection.
107
+ */
108
+ export function parseGlyphProtocolReply(sequence: string): GlyphProtocolReply | null {
109
+ if (!isGlyphProtocolSequence(sequence)) return null;
110
+ const body = sequence.slice(APC.length + GLYPH_PROTOCOL_ID.length + 1, -ST.length);
111
+ const parts = body.split(";");
112
+ const verb = parts[0];
113
+ const params: Record<string, string> = {};
114
+ for (let i = 1; i < parts.length; i++) {
115
+ const part = parts[i]!;
116
+ const eq = part.indexOf("=");
117
+ if (eq > 0) params[part.slice(0, eq)] = part.slice(eq + 1);
118
+ }
119
+ // Empty list values (`fmt=` / `status=`) mean "none", not one empty name.
120
+ const list = (value: string | undefined): string[] => (value ? value.split(",") : []);
121
+ switch (verb) {
122
+ case "s":
123
+ return { verb, formats: list(params.fmt) };
124
+ case "q": {
125
+ const cp = Number.parseInt(params.cp ?? "", 16);
126
+ if (!Number.isFinite(cp)) return null;
127
+ return { verb, cp, coverage: list(params.status) };
128
+ }
129
+ case "r": {
130
+ const cp = Number.parseInt(params.cp ?? "", 16);
131
+ const status = Number.parseInt(params.status ?? "", 10);
132
+ if (!Number.isFinite(cp) || !Number.isFinite(status)) return null;
133
+ return params.reason === undefined ? { verb, cp, status } : { verb, cp, status, reason: params.reason };
134
+ }
135
+ case "c": {
136
+ const status = Number.parseInt(params.status ?? "", 10);
137
+ if (!Number.isFinite(status)) return null;
138
+ return params.reason === undefined ? { verb, status } : { verb, status, reason: params.reason };
139
+ }
140
+ default:
141
+ return null;
142
+ }
143
+ }
package/src/index.ts CHANGED
@@ -48,6 +48,8 @@ export * from "./fuzzy";
48
48
  export * from "./keybindings";
49
49
  // Kitty keyboard protocol helpers
50
50
  export * from "./keys";
51
+ // Glyph Protocol: in-band icon registration (APC 25a1)
52
+ export * from "./glyph-protocol";
51
53
  // Kitty graphics: Unicode placeholders
52
54
  export * from "./kitty-graphics";
53
55
  // LaTeX → Unicode/ANSI math rendering