@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.
- package/CHANGELOG.md +19 -0
- package/THIRD-PARTY-NOTICES.txt +0 -37
- package/dist/types/glyph-protocol.d.ts +62 -0
- package/dist/types/index.d.ts +1 -0
- package/dist/types/overlays/model-browser.d.ts +9 -3
- package/dist/types/prompt/gradient-highlight.d.ts +2 -2
- package/dist/types/prompt/magic-keywords.d.ts +37 -10
- package/dist/types/setup/scenes/glyph.d.ts +6 -1
- package/dist/types/terminal-capabilities.d.ts +8 -0
- package/dist/types/terminal.d.ts +16 -0
- package/dist/types/theme/mermaid-cache.d.ts +2 -2
- package/dist/types/tools/eval.d.ts +5 -4
- package/dist/types/tools/find.d.ts +82 -0
- package/dist/types/tools/web-search.d.ts +4 -0
- package/package.json +9 -9
- package/src/glyph-protocol.ts +143 -0
- package/src/index.ts +2 -0
- package/src/overlays/model-browser.ts +47 -6
- package/src/overlays/model-hub.ts +122 -33
- package/src/prompt/gradient-highlight.ts +3 -3
- package/src/prompt/macos-spelling.ts +7 -1
- package/src/prompt/magic-keywords.ts +103 -21
- package/src/prompt/markdown-prose.ts +2 -2
- package/src/prompt/tips.txt +1 -0
- package/src/setup/scenes/glyph.ts +8 -1
- package/src/terminal-capabilities.ts +12 -0
- package/src/terminal.ts +151 -2
- package/src/theme/glyph-bundle.json +797 -0
- package/src/theme/mermaid-cache.ts +5 -4
- package/src/tools/eval.ts +14 -7
- package/src/tools/find.ts +255 -0
- package/src/tools/hub.ts +2 -0
- package/src/tools/index.ts +2 -0
- package/src/tools/web-search.ts +5 -0
- package/src/tui.ts +7 -0
- package/dist/types/prompt/magic-keyword-boundary.d.ts +0 -9
- package/dist/types/prompt/orchestrate.d.ts +0 -13
- package/dist/types/prompt/ultrathink.d.ts +0 -13
- package/dist/types/prompt/workflow.d.ts +0 -13
- package/src/prompt/magic-keyword-boundary.ts +0 -23
- package/src/prompt/orchestrate.ts +0 -26
- package/src/prompt/ultrathink.ts +0 -26
- package/src/prompt/workflow.ts +0 -26
|
@@ -1,11 +1,12 @@
|
|
|
1
|
-
import
|
|
1
|
+
import type { MermaidRenderOptions } from "@oh-my-pi/pi-natives";
|
|
2
|
+
import { renderMermaidAsciiSafe } from "@oh-my-pi/pi-utils/mermaid-ascii";
|
|
2
3
|
|
|
3
4
|
/**
|
|
4
5
|
* Options controlling how fenced Mermaid source is resolved to terminal ASCII.
|
|
5
6
|
* Extends the raw render options (theme, color mode, spacing, `useAscii`) with a
|
|
6
7
|
* viewport-fitting hint.
|
|
7
8
|
*/
|
|
8
|
-
export interface MermaidResolveOptions extends
|
|
9
|
+
export interface MermaidResolveOptions extends MermaidRenderOptions {
|
|
9
10
|
/**
|
|
10
11
|
* Maximum display width (terminal columns) the diagram should occupy. A
|
|
11
12
|
* layout that overflows this width is re-rendered in the perpendicular
|
|
@@ -34,7 +35,7 @@ function asciiDisplayWidth(ascii: string): number {
|
|
|
34
35
|
|
|
35
36
|
function renderVariant(
|
|
36
37
|
source: string,
|
|
37
|
-
baseOptions:
|
|
38
|
+
baseOptions: MermaidRenderOptions,
|
|
38
39
|
baseKey: string,
|
|
39
40
|
direction: "TD" | "LR" | null,
|
|
40
41
|
): string | null {
|
|
@@ -57,7 +58,7 @@ export function resolveMermaidAscii(source: string, options?: MermaidResolveOpti
|
|
|
57
58
|
|
|
58
59
|
const { maxWidth, ...rest } = options ?? {};
|
|
59
60
|
// Default to uncolored output; callers opt into a themed palette explicitly.
|
|
60
|
-
const baseOptions:
|
|
61
|
+
const baseOptions: MermaidRenderOptions = { colorMode: "none", ...rest };
|
|
61
62
|
const baseKey = JSON.stringify(baseOptions);
|
|
62
63
|
|
|
63
64
|
const base = renderVariant(normalizedSource, baseOptions, baseKey, null);
|
package/src/tools/eval.ts
CHANGED
|
@@ -139,15 +139,16 @@ function getRenderCells(args: EvalRenderArgs | undefined): EvalRenderCell[] {
|
|
|
139
139
|
type AgentEventStatus = "pending" | "running" | "completed" | "failed" | "aborted";
|
|
140
140
|
|
|
141
141
|
/**
|
|
142
|
-
* Append or replace a status event. `agent` events are
|
|
143
|
-
* by `id`, so they coalesce in place (preserving
|
|
144
|
-
* op is a discrete action and simply appends.
|
|
145
|
-
* bounded even when a subagent
|
|
142
|
+
* Append or replace a status event. `agent` and `judge_batch` events are
|
|
143
|
+
* progress snapshots keyed by `id`, so they coalesce in place (preserving
|
|
144
|
+
* first-seen order); every other op is a discrete action and simply appends.
|
|
145
|
+
* Keeps the persisted event list bounded even when a subagent or batch emits
|
|
146
|
+
* hundreds of throttled progress ticks.
|
|
146
147
|
*/
|
|
147
148
|
export function upsertStatusEvent(events: EvalStatusEvent[], event: EvalStatusEvent): void {
|
|
148
|
-
if (event.op === "agent" && typeof event.id === "string") {
|
|
149
|
-
const id = event
|
|
150
|
-
const idx = events.findIndex(e => e.op ===
|
|
149
|
+
if ((event.op === "agent" || event.op === "judge_batch") && typeof event.id === "string") {
|
|
150
|
+
const { op, id } = event;
|
|
151
|
+
const idx = events.findIndex(e => e.op === op && e.id === id);
|
|
151
152
|
if (idx >= 0) {
|
|
152
153
|
events[idx] = event;
|
|
153
154
|
return;
|
|
@@ -351,6 +352,12 @@ function formatStatusEvent(event: EvalStatusEvent, theme: Theme): string {
|
|
|
351
352
|
parts.push(data.action === "create" ? `${data.count} agent(s)` : `${data.count} item(s)`);
|
|
352
353
|
}
|
|
353
354
|
break;
|
|
355
|
+
case "judge_batch":
|
|
356
|
+
parts.push(`${data.action} ${data.id}`);
|
|
357
|
+
parts.push(`${data.done ?? 0}/${data.total ?? 0}`);
|
|
358
|
+
if (data.failed) parts.push(`${data.failed} failed`);
|
|
359
|
+
if (data.model) parts.push(String(data.model));
|
|
360
|
+
break;
|
|
354
361
|
case "wc":
|
|
355
362
|
parts.push(`${data.lines}L ${data.words}W ${data.chars}C`);
|
|
356
363
|
break;
|
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Transcript renderer for the `find` tool (semantic grep). Hits are ranked
|
|
3
|
+
* strongest first; each row carries a score gauge, the hyperlinked file, and
|
|
4
|
+
* its strongest verified line ranges with a verbatim snippet.
|
|
5
|
+
*/
|
|
6
|
+
import * as path from "node:path";
|
|
7
|
+
import { formatDuration, formatNumber } from "@oh-my-pi/pi-utils";
|
|
8
|
+
import { renderProgressBar } from "../components/progress-bar";
|
|
9
|
+
import { Text } from "../components/text";
|
|
10
|
+
import { Ellipsis, fileHyperlink, getTreeBranch, renderStatusLine, renderTreeList, truncateToWidth } from "../render";
|
|
11
|
+
import {
|
|
12
|
+
createCachedComponent,
|
|
13
|
+
formatCount,
|
|
14
|
+
formatEmptyMessage,
|
|
15
|
+
formatErrorMessage,
|
|
16
|
+
PREVIEW_LIMITS,
|
|
17
|
+
replaceTabs,
|
|
18
|
+
} from "../render/render-utils";
|
|
19
|
+
import type { Theme, ThemeColor } from "../theme/theme";
|
|
20
|
+
import type { Component } from "../tui";
|
|
21
|
+
import type { OutputMeta } from "./output-meta";
|
|
22
|
+
import type { RenderResultOptions, ToolRenderer } from "./renderer";
|
|
23
|
+
|
|
24
|
+
/** A verified line range with its yes-probability and a one-line preview. */
|
|
25
|
+
export interface FindRange {
|
|
26
|
+
start: number;
|
|
27
|
+
end: number;
|
|
28
|
+
p: number;
|
|
29
|
+
snippet: string;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/** A file whose verified passages cleared the threshold; `ranges` are merged positive spans, strongest first. */
|
|
33
|
+
export interface FindHit {
|
|
34
|
+
/** Display path relative to {@link FindToolDetails.cwd}. */
|
|
35
|
+
rel: string;
|
|
36
|
+
/** Filename judgment, when the name batch answered. */
|
|
37
|
+
nameScore?: number;
|
|
38
|
+
/** Best verified passage probability. */
|
|
39
|
+
contentScore: number;
|
|
40
|
+
ranges: FindRange[];
|
|
41
|
+
/** Lines of content actually judged, and whether the file held more. */
|
|
42
|
+
linesSeen: number;
|
|
43
|
+
truncated: boolean;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** Search accounting reported alongside the hits. */
|
|
47
|
+
export interface FindStats {
|
|
48
|
+
/** Eligible files under the root. */
|
|
49
|
+
listed: number;
|
|
50
|
+
requests: number;
|
|
51
|
+
errors: number;
|
|
52
|
+
/** Entries judged by name. */
|
|
53
|
+
judged: number;
|
|
54
|
+
/** Files whose content was read and sent. */
|
|
55
|
+
filesRead: number;
|
|
56
|
+
fileBytes: number;
|
|
57
|
+
inputTokens: number;
|
|
58
|
+
outputTokens: number;
|
|
59
|
+
cost: number;
|
|
60
|
+
apiMs: number;
|
|
61
|
+
windowsJudged: number;
|
|
62
|
+
windowsPruned: number;
|
|
63
|
+
mapCards: number;
|
|
64
|
+
/** Distinct request failures, phase-prefixed. */
|
|
65
|
+
failures: string[];
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/** Display metadata for find tool results. */
|
|
69
|
+
export interface FindToolDetails {
|
|
70
|
+
query: string;
|
|
71
|
+
/** Lexical keywords actually used: derived from the query plus `grep_keywords`. */
|
|
72
|
+
keywords: string[];
|
|
73
|
+
/** Verified-passage probability at or above which a file is a hit. */
|
|
74
|
+
threshold: number;
|
|
75
|
+
hits: FindHit[];
|
|
76
|
+
stats: FindStats;
|
|
77
|
+
elapsedMs: number;
|
|
78
|
+
/** Session cwd; hit paths are relative to it. */
|
|
79
|
+
cwd: string;
|
|
80
|
+
/** Display form of the searched directory when narrower than cwd. */
|
|
81
|
+
scopePath?: string;
|
|
82
|
+
meta?: OutputMeta;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
interface FindRenderArgs {
|
|
86
|
+
query?: string;
|
|
87
|
+
grep_keywords?: string[];
|
|
88
|
+
path?: string;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/** Cells in the per-hit score gauge. */
|
|
92
|
+
const GAUGE_WIDTH = 6;
|
|
93
|
+
/** Hits shown before expansion. */
|
|
94
|
+
const COLLAPSED_HITS = 5;
|
|
95
|
+
/** Ranges shown per hit: one collapsed, three expanded. */
|
|
96
|
+
const RANGES_COLLAPSED = 1;
|
|
97
|
+
const RANGES_EXPANDED = 3;
|
|
98
|
+
/** Score at or above which a hit renders as strong / plausible. */
|
|
99
|
+
const STRONG = 0.7;
|
|
100
|
+
const PLAUSIBLE = 0.4;
|
|
101
|
+
|
|
102
|
+
function scoreColor(p: number): ThemeColor {
|
|
103
|
+
return p >= STRONG ? "success" : p >= PLAUSIBLE ? "warning" : "muted";
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function gauge(p: number, theme: Theme): string {
|
|
107
|
+
const color = scoreColor(p);
|
|
108
|
+
return renderProgressBar(p, GAUGE_WIDTH, {
|
|
109
|
+
style: {
|
|
110
|
+
filled: theme.symbol("progress.filled"),
|
|
111
|
+
empty: theme.symbol("progress.empty"),
|
|
112
|
+
styleFilled: text => theme.fg(color, text),
|
|
113
|
+
styleEmpty: text => theme.fg("dim", text),
|
|
114
|
+
},
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function renderHit(hit: FindHit, rangeLimit: number, cwd: string | undefined, theme: Theme): string[] {
|
|
119
|
+
const absPath = cwd === undefined ? undefined : path.join(cwd, hit.rel);
|
|
120
|
+
const link = (text: string, line?: number) =>
|
|
121
|
+
absPath === undefined ? text : fileHyperlink(absPath, text, { line });
|
|
122
|
+
const coverage = hit.truncated ? `${hit.linesSeen} lines judged, partial` : `${hit.linesSeen} lines judged`;
|
|
123
|
+
const lines = [
|
|
124
|
+
`${gauge(hit.contentScore, theme)} ${theme.fg(scoreColor(hit.contentScore), hit.contentScore.toFixed(2))} ${link(theme.fg("accent", hit.rel))} ${theme.fg("dim", coverage)}`,
|
|
125
|
+
];
|
|
126
|
+
const ranges = [...hit.ranges].sort((a, b) => b.p - a.p || a.start - b.start).slice(0, rangeLimit);
|
|
127
|
+
ranges.forEach((range, index) => {
|
|
128
|
+
const span = range.start === range.end ? String(range.start) : `${range.start}-${range.end}`;
|
|
129
|
+
const ref = link(theme.fg("muted", `:${span}`), range.start);
|
|
130
|
+
const snippet = theme.fg("toolOutput", replaceTabs(range.snippet.trim()));
|
|
131
|
+
const branch = theme.fg("dim", getTreeBranch(index === ranges.length - 1, theme));
|
|
132
|
+
lines.push(` ${branch} ${ref} ${theme.fg("dim", range.p.toFixed(2))} ${snippet}`);
|
|
133
|
+
});
|
|
134
|
+
return lines;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
function quoteQuery(query: string | undefined): string | undefined {
|
|
138
|
+
return query === undefined ? undefined : `"${query}"`;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/** Render find calls and results in the transcript. */
|
|
142
|
+
export const findToolRenderer = {
|
|
143
|
+
renderCall(args: FindRenderArgs, options: RenderResultOptions, uiTheme: Theme): Component {
|
|
144
|
+
const keywords = args.grep_keywords ?? [];
|
|
145
|
+
const meta = keywords.length > 0 ? [keywords.join(" ")] : [];
|
|
146
|
+
if (args.path) meta.push(`in ${args.path}`);
|
|
147
|
+
const text = renderStatusLine(
|
|
148
|
+
{
|
|
149
|
+
icon: "pending",
|
|
150
|
+
spinnerFrame: options.spinnerFrame,
|
|
151
|
+
title: "Find",
|
|
152
|
+
titleColor: "toolTitle",
|
|
153
|
+
description: quoteQuery(args.query),
|
|
154
|
+
meta,
|
|
155
|
+
},
|
|
156
|
+
uiTheme,
|
|
157
|
+
);
|
|
158
|
+
return new Text(text, 1, 0);
|
|
159
|
+
},
|
|
160
|
+
|
|
161
|
+
renderResult(
|
|
162
|
+
result: { content: Array<{ type: string; text?: string }>; details?: FindToolDetails; isError?: boolean },
|
|
163
|
+
options: RenderResultOptions,
|
|
164
|
+
uiTheme: Theme,
|
|
165
|
+
args?: FindRenderArgs,
|
|
166
|
+
): Component {
|
|
167
|
+
const details = result.details;
|
|
168
|
+
const text = result.content?.find(c => c.type === "text")?.text ?? "";
|
|
169
|
+
|
|
170
|
+
if (result.isError) {
|
|
171
|
+
return new Text(formatErrorMessage(text || "Unknown error", uiTheme), 1, 0);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// Streaming progress: the tool reports the phase it is in.
|
|
175
|
+
if (options.isPartial || details === undefined) {
|
|
176
|
+
const header = renderStatusLine(
|
|
177
|
+
{
|
|
178
|
+
icon: "pending",
|
|
179
|
+
spinnerFrame: options.spinnerFrame,
|
|
180
|
+
title: "Find",
|
|
181
|
+
titleColor: "toolTitle",
|
|
182
|
+
description: quoteQuery(args?.query ?? details?.query),
|
|
183
|
+
meta: text ? [text] : [],
|
|
184
|
+
},
|
|
185
|
+
uiTheme,
|
|
186
|
+
);
|
|
187
|
+
return new Text(header, 1, 0);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
const { hits, stats, threshold } = details;
|
|
191
|
+
const description = quoteQuery(details.query);
|
|
192
|
+
const scope = details.scopePath === undefined ? [] : [`in ${details.scopePath}`];
|
|
193
|
+
const meta = [
|
|
194
|
+
formatCount("hit", hits.length),
|
|
195
|
+
...scope,
|
|
196
|
+
`${stats.filesRead} files read`,
|
|
197
|
+
`τ ${threshold.toFixed(2)}`,
|
|
198
|
+
`${formatNumber(stats.inputTokens)} tokens`,
|
|
199
|
+
`$${stats.cost.toFixed(4)}`,
|
|
200
|
+
formatDuration(details.elapsedMs),
|
|
201
|
+
];
|
|
202
|
+
if (stats.errors > 0) meta.push(uiTheme.fg("warning", `${stats.errors} failed`));
|
|
203
|
+
|
|
204
|
+
if (hits.length === 0) {
|
|
205
|
+
const emptyMeta = ["0 hits", ...scope, `$${stats.cost.toFixed(4)}`, formatDuration(details.elapsedMs)];
|
|
206
|
+
if (stats.errors > 0) emptyMeta.push(uiTheme.fg("warning", `${stats.errors} failed`));
|
|
207
|
+
const header = renderStatusLine(
|
|
208
|
+
{ icon: "warning", title: "Find", titleColor: "toolTitle", description, meta: emptyMeta },
|
|
209
|
+
uiTheme,
|
|
210
|
+
);
|
|
211
|
+
const lines = [header, formatEmptyMessage("No relevant passages found", uiTheme)];
|
|
212
|
+
for (const failure of stats.failures) lines.push(uiTheme.fg("warning", failure));
|
|
213
|
+
return new Text(lines.join("\n"), 1, 0);
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
const header = renderStatusLine(
|
|
217
|
+
{
|
|
218
|
+
iconOverride: uiTheme.fg("toolTitle", uiTheme.symbol("icon.search")),
|
|
219
|
+
title: "Find",
|
|
220
|
+
titleColor: "toolTitle",
|
|
221
|
+
description,
|
|
222
|
+
meta,
|
|
223
|
+
},
|
|
224
|
+
uiTheme,
|
|
225
|
+
);
|
|
226
|
+
|
|
227
|
+
return createCachedComponent(
|
|
228
|
+
() => options.expanded,
|
|
229
|
+
(width, expanded) => {
|
|
230
|
+
const hitLines = renderTreeList(
|
|
231
|
+
{
|
|
232
|
+
items: hits,
|
|
233
|
+
expanded,
|
|
234
|
+
maxCollapsed: COLLAPSED_HITS,
|
|
235
|
+
maxCollapsedLines: PREVIEW_LIMITS.EXPANDED_LINES,
|
|
236
|
+
itemType: "hit",
|
|
237
|
+
renderItem: hit =>
|
|
238
|
+
renderHit(hit, expanded ? RANGES_EXPANDED : RANGES_COLLAPSED, details.cwd, uiTheme),
|
|
239
|
+
},
|
|
240
|
+
uiTheme,
|
|
241
|
+
);
|
|
242
|
+
const extra: string[] = [];
|
|
243
|
+
if (expanded) {
|
|
244
|
+
extra.push(uiTheme.fg("dim", `keywords: ${details.keywords.join(", ")}`));
|
|
245
|
+
for (const failure of stats.failures) extra.push(uiTheme.fg("warning", failure));
|
|
246
|
+
}
|
|
247
|
+
return [header, ...hitLines, ...extra].map(line => truncateToWidth(line, width, Ellipsis.Omit));
|
|
248
|
+
},
|
|
249
|
+
{ paddingX: 1 },
|
|
250
|
+
);
|
|
251
|
+
},
|
|
252
|
+
mergeCallAndResult: true,
|
|
253
|
+
animatedPendingPreview: true,
|
|
254
|
+
animatedPartialResult: true,
|
|
255
|
+
} satisfies ToolRenderer<FindRenderArgs, FindToolDetails>;
|
package/src/tools/hub.ts
CHANGED
|
@@ -870,6 +870,8 @@ export function launchRenderResult(
|
|
|
870
870
|
: "Wait timed out.",
|
|
871
871
|
),
|
|
872
872
|
);
|
|
873
|
+
} else if (details && params.pattern && details.matched === undefined) {
|
|
874
|
+
body.push(theme.fg("warning", `Process exited before output pattern /${params.pattern}/ matched.`));
|
|
873
875
|
}
|
|
874
876
|
break;
|
|
875
877
|
}
|
package/src/tools/index.ts
CHANGED
|
@@ -10,6 +10,7 @@ import { bashToolRenderer } from "./bash";
|
|
|
10
10
|
import { debugToolRenderer } from "./debug";
|
|
11
11
|
import { editToolRenderer } from "./edit";
|
|
12
12
|
import { evalToolRenderer } from "./eval";
|
|
13
|
+
import { findToolRenderer } from "./find";
|
|
13
14
|
import { githubToolRenderer } from "./github";
|
|
14
15
|
import { globToolRenderer } from "./glob";
|
|
15
16
|
import { goalToolRenderer } from "./goal";
|
|
@@ -40,6 +41,7 @@ export const toolRenderers: Record<string, ToolRenderer> = {
|
|
|
40
41
|
eval: evalToolRenderer,
|
|
41
42
|
edit: editToolRenderer,
|
|
42
43
|
apply_patch: editToolRenderer,
|
|
44
|
+
find: findToolRenderer,
|
|
43
45
|
glob: globToolRenderer,
|
|
44
46
|
grep: grepToolRenderer,
|
|
45
47
|
lsp: lspToolRenderer,
|
package/src/tools/web-search.ts
CHANGED
|
@@ -290,6 +290,11 @@ export const SEARCH_PROVIDER_OPTIONS = [
|
|
|
290
290
|
description:
|
|
291
291
|
"Grok web search via xAI Responses API (uses SuperGrok/X Premium+ OAuth via /login xai-oauth, or XAI_API_KEY)",
|
|
292
292
|
},
|
|
293
|
+
{
|
|
294
|
+
value: "openrouter",
|
|
295
|
+
label: "OpenRouter",
|
|
296
|
+
description: "OpenRouter plugins-based web search with model-selected grounding",
|
|
297
|
+
},
|
|
293
298
|
{ value: "zai", label: "Z.AI", description: "Calls Z.AI webSearchPrime MCP" },
|
|
294
299
|
{ value: "exa", label: "Exa", description: "API via /login exa or EXA_API_KEY; explicit keyless fallback via MCP" },
|
|
295
300
|
{ value: "tinyfish", label: "TinyFish", description: "Requires TINYFISH_API_KEY" },
|
package/src/tui.ts
CHANGED
|
@@ -1257,6 +1257,13 @@ export class TUI extends Container {
|
|
|
1257
1257
|
if (!supported && isInsideHerdr() && status === 0) return;
|
|
1258
1258
|
this.#setSynchronizedOutput(supported);
|
|
1259
1259
|
});
|
|
1260
|
+
// Icons painted before the Glyph Protocol registration landed may sit in
|
|
1261
|
+
// the terminal as tofu; a full repaint re-emits them against the glossary.
|
|
1262
|
+
this.terminal.onGlyphProtocolReport?.(supported => {
|
|
1263
|
+
if (!supported || this.#stopped) return;
|
|
1264
|
+
this.invalidate();
|
|
1265
|
+
this.requestRender(true);
|
|
1266
|
+
});
|
|
1260
1267
|
this.terminal.start(
|
|
1261
1268
|
data => this.#handleInput(data),
|
|
1262
1269
|
() => {
|
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Build a case-sensitive magic-keyword matcher for prose punctuation boundaries.
|
|
3
|
-
*
|
|
4
|
-
* Sentence punctuation and quotes may touch the keyword, but letters, digits,
|
|
5
|
-
* underscores, slashes, backslashes, hyphens, file-extension dots, symbol
|
|
6
|
-
* references (`foo::keyword`), and immediate call parentheses (`keyword()`)
|
|
7
|
-
* keep the occurrence embedded in code rather than prose.
|
|
8
|
-
*/
|
|
9
|
-
export declare function magicKeywordRegex(keyword: string, flags?: string): RegExp;
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
import { type KeywordHighlighter } from "./gradient-highlight.js";
|
|
2
|
-
/**
|
|
3
|
-
* Whether `text` contains the standalone keyword "orchestrate" (lowercase,
|
|
4
|
-
* prose-delimited) in prose — never inside a code block, inline code span,
|
|
5
|
-
* or XML/HTML section.
|
|
6
|
-
*/
|
|
7
|
-
export declare function containsOrchestrate(text: string): boolean;
|
|
8
|
-
/**
|
|
9
|
-
* Highlight every standalone "orchestrate" in `text` for editor display with a
|
|
10
|
-
* cool teal→violet gradient (hue 150..280), visually distinct from ultrathink's
|
|
11
|
-
* full-spectrum rainbow.
|
|
12
|
-
*/
|
|
13
|
-
export declare const highlightOrchestrate: KeywordHighlighter;
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
import { type KeywordHighlighter } from "./gradient-highlight.js";
|
|
2
|
-
/**
|
|
3
|
-
* Whether `text` contains the standalone keyword "ultrathink" (lowercase,
|
|
4
|
-
* prose-delimited) in prose — never inside a code block, inline code span,
|
|
5
|
-
* or XML/HTML section.
|
|
6
|
-
*/
|
|
7
|
-
export declare function containsUltrathink(text: string): boolean;
|
|
8
|
-
/**
|
|
9
|
-
* Rainbow-highlight every standalone "ultrathink" in `text` for editor display.
|
|
10
|
-
* Sweeps red→violet (hue 0..330), stopping short of the wrap back to red so the
|
|
11
|
-
* gradient resolves smoothly regardless of casing or match length.
|
|
12
|
-
*/
|
|
13
|
-
export declare const highlightUltrathink: KeywordHighlighter;
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
import { type KeywordHighlighter } from "./gradient-highlight.js";
|
|
2
|
-
/**
|
|
3
|
-
* Whether `text` contains the standalone keyword "workflowz"
|
|
4
|
-
* (lowercase, prose-delimited) in prose — never inside a code block, inline
|
|
5
|
-
* code span, or XML/HTML section.
|
|
6
|
-
*/
|
|
7
|
-
export declare function containsWorkflow(text: string): boolean;
|
|
8
|
-
/**
|
|
9
|
-
* Highlight every standalone "workflowz" in `text` for editor display
|
|
10
|
-
* with a warm amber→green gradient (hue 30..150), visually distinct from
|
|
11
|
-
* ultrathink's rainbow and orchestrate's teal→violet.
|
|
12
|
-
*/
|
|
13
|
-
export declare const highlightWorkflow: KeywordHighlighter;
|
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
/** Characters that bind a magic keyword into an identifier or path segment. */
|
|
2
|
-
const LEFT_BOUNDARY = String.raw`(?<![\p{L}\p{N}_./\\-])(?<!::)`;
|
|
3
|
-
|
|
4
|
-
/** Characters that cannot immediately follow a standalone magic keyword. */
|
|
5
|
-
const RIGHT_BOUNDARY = String.raw`(?![\p{L}\p{N}_/\\-])(?!\.[\p{L}\p{N}_-])(?!\()`;
|
|
6
|
-
|
|
7
|
-
/** Escape a literal string for safe insertion into a RegExp source. */
|
|
8
|
-
function escapeRegExp(value: string): string {
|
|
9
|
-
return value.replace(/[\\^$.*+?()[\]{}|]/g, "\\$&");
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
/**
|
|
13
|
-
* Build a case-sensitive magic-keyword matcher for prose punctuation boundaries.
|
|
14
|
-
*
|
|
15
|
-
* Sentence punctuation and quotes may touch the keyword, but letters, digits,
|
|
16
|
-
* underscores, slashes, backslashes, hyphens, file-extension dots, symbol
|
|
17
|
-
* references (`foo::keyword`), and immediate call parentheses (`keyword()`)
|
|
18
|
-
* keep the occurrence embedded in code rather than prose.
|
|
19
|
-
*/
|
|
20
|
-
export function magicKeywordRegex(keyword: string, flags = ""): RegExp {
|
|
21
|
-
const normalizedFlags = flags.includes("u") ? flags : `${flags}u`;
|
|
22
|
-
return new RegExp(`${LEFT_BOUNDARY}${escapeRegExp(keyword)}${RIGHT_BOUNDARY}`, normalizedFlags);
|
|
23
|
-
}
|
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
import { createGradientHighlighter, type KeywordHighlighter } from "./gradient-highlight";
|
|
2
|
-
import { magicKeywordRegex } from "./magic-keyword-boundary";
|
|
3
|
-
import { keywordInProse } from "./markdown-prose";
|
|
4
|
-
|
|
5
|
-
const ORCHESTRATE_WORD = magicKeywordRegex("orchestrate");
|
|
6
|
-
|
|
7
|
-
/**
|
|
8
|
-
* Whether `text` contains the standalone keyword "orchestrate" (lowercase,
|
|
9
|
-
* prose-delimited) in prose — never inside a code block, inline code span,
|
|
10
|
-
* or XML/HTML section.
|
|
11
|
-
*/
|
|
12
|
-
export function containsOrchestrate(text: string): boolean {
|
|
13
|
-
return keywordInProse(text, ORCHESTRATE_WORD);
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
/**
|
|
17
|
-
* Highlight every standalone "orchestrate" in `text` for editor display with a
|
|
18
|
-
* cool teal→violet gradient (hue 150..280), visually distinct from ultrathink's
|
|
19
|
-
* full-spectrum rainbow.
|
|
20
|
-
*/
|
|
21
|
-
export const highlightOrchestrate: KeywordHighlighter = createGradientHighlighter({
|
|
22
|
-
probe: /orchestrate/,
|
|
23
|
-
highlight: magicKeywordRegex("orchestrate", "g"),
|
|
24
|
-
stops: 14,
|
|
25
|
-
hue: t => 150 + t * 130,
|
|
26
|
-
});
|
package/src/prompt/ultrathink.ts
DELETED
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
import { createGradientHighlighter, type KeywordHighlighter } from "./gradient-highlight";
|
|
2
|
-
import { magicKeywordRegex } from "./magic-keyword-boundary";
|
|
3
|
-
import { keywordInProse } from "./markdown-prose";
|
|
4
|
-
|
|
5
|
-
const ULTRATHINK_WORD = magicKeywordRegex("ultrathink");
|
|
6
|
-
|
|
7
|
-
/**
|
|
8
|
-
* Whether `text` contains the standalone keyword "ultrathink" (lowercase,
|
|
9
|
-
* prose-delimited) in prose — never inside a code block, inline code span,
|
|
10
|
-
* or XML/HTML section.
|
|
11
|
-
*/
|
|
12
|
-
export function containsUltrathink(text: string): boolean {
|
|
13
|
-
return keywordInProse(text, ULTRATHINK_WORD);
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
/**
|
|
17
|
-
* Rainbow-highlight every standalone "ultrathink" in `text` for editor display.
|
|
18
|
-
* Sweeps red→violet (hue 0..330), stopping short of the wrap back to red so the
|
|
19
|
-
* gradient resolves smoothly regardless of casing or match length.
|
|
20
|
-
*/
|
|
21
|
-
export const highlightUltrathink: KeywordHighlighter = createGradientHighlighter({
|
|
22
|
-
probe: /ultrathink/,
|
|
23
|
-
highlight: magicKeywordRegex("ultrathink", "g"),
|
|
24
|
-
stops: 14,
|
|
25
|
-
hue: t => t * 330,
|
|
26
|
-
});
|
package/src/prompt/workflow.ts
DELETED
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
import { createGradientHighlighter, type KeywordHighlighter } from "./gradient-highlight";
|
|
2
|
-
import { magicKeywordRegex } from "./magic-keyword-boundary";
|
|
3
|
-
import { keywordInProse } from "./markdown-prose";
|
|
4
|
-
|
|
5
|
-
const WORKFLOW_WORD = magicKeywordRegex("workflowz");
|
|
6
|
-
|
|
7
|
-
/**
|
|
8
|
-
* Whether `text` contains the standalone keyword "workflowz"
|
|
9
|
-
* (lowercase, prose-delimited) in prose — never inside a code block, inline
|
|
10
|
-
* code span, or XML/HTML section.
|
|
11
|
-
*/
|
|
12
|
-
export function containsWorkflow(text: string): boolean {
|
|
13
|
-
return keywordInProse(text, WORKFLOW_WORD);
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
/**
|
|
17
|
-
* Highlight every standalone "workflowz" in `text` for editor display
|
|
18
|
-
* with a warm amber→green gradient (hue 30..150), visually distinct from
|
|
19
|
-
* ultrathink's rainbow and orchestrate's teal→violet.
|
|
20
|
-
*/
|
|
21
|
-
export const highlightWorkflow: KeywordHighlighter = createGradientHighlighter({
|
|
22
|
-
probe: /workflowz/,
|
|
23
|
-
highlight: magicKeywordRegex("workflowz", "g"),
|
|
24
|
-
stops: 14,
|
|
25
|
-
hue: t => 30 + t * 120,
|
|
26
|
-
});
|