@underactive/pi-topping-moa-fusion 0.4.5 → 0.5.0

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.
@@ -1,61 +0,0 @@
1
- import { truncateToWidth, visibleWidth, type SelectListLayoutOptions } from "@earendil-works/pi-tui";
2
-
3
- /**
4
- * Truncates a slash-namespaced model ref (`provider/accounts/.../model-name`)
5
- * so the parts that matter to identify the model survive first. Priority:
6
- * the model name (last segment) is mandatory, then the first segment (the
7
- * namespace root), then the segment right before the model name, then
8
- * whatever else still fits. Skipped runs collapse to a single ellipsis.
9
- */
10
- export function smartTruncateModelLabel(text: string, maxWidth: number): string {
11
- const ELLIPSIS = "…";
12
- const ANSI_RE = /\x1b\[[0-9;]*m/g;
13
- if (visibleWidth(text) <= maxWidth) return text;
14
- const segments = text.split("/");
15
- const n = segments.length;
16
- if (n === 1) return truncateToWidth(text, maxWidth, ELLIPSIS).replace(ANSI_RE, "");
17
-
18
- const lastIndex = n - 1;
19
- const kept = new Set<number>([lastIndex]);
20
- const priority: number[] = [];
21
- priority.push(0);
22
- if (n - 2 > 0) priority.push(n - 2);
23
- for (let i = 1; i <= n - 3; i++) priority.push(i);
24
-
25
- const render = () => {
26
- const parts: string[] = [];
27
- let i = 0;
28
- while (i < n) {
29
- if (kept.has(i)) {
30
- parts.push(segments[i]!);
31
- i++;
32
- } else {
33
- while (i < n && !kept.has(i)) i++;
34
- parts.push(ELLIPSIS);
35
- }
36
- }
37
- return parts.join("/");
38
- };
39
-
40
- let current = render();
41
- if (visibleWidth(current) > maxWidth) {
42
- const skeleton = `${ELLIPSIS}/`;
43
- const budget = Math.max(0, maxWidth - visibleWidth(skeleton));
44
- return (skeleton + truncateToWidth(segments[lastIndex]!, budget, ELLIPSIS)).replace(ANSI_RE, "");
45
- }
46
-
47
- for (const index of priority) {
48
- kept.add(index);
49
- const candidate = render();
50
- if (visibleWidth(candidate) > maxWidth) {
51
- kept.delete(index);
52
- continue;
53
- }
54
- current = candidate;
55
- }
56
- return current;
57
- }
58
-
59
- export const MODEL_LIST_LAYOUT: SelectListLayoutOptions = {
60
- truncatePrimary: ({ text, maxWidth }) => smartTruncateModelLabel(text, maxWidth),
61
- };
package/src/ui/shimmer.ts DELETED
@@ -1,77 +0,0 @@
1
- /**
2
- * Codex-style light-sweep text shimmer.
3
- *
4
- * Vendored from pi-topping-persona-audit (`src/shimmer.ts`) rather than
5
- * depended on, so the extensions stay independently installable. The sweep
6
- * geometry — band half-width, padding, speed scaling — is load-bearing for
7
- * visual parity, so port changes upstream rather than tuning them here.
8
- * test/shimmer.test.mjs mirrors the upstream tests to catch drift.
9
- */
10
-
11
- import type { ThemeColor } from "@earendil-works/pi-coding-agent";
12
-
13
- const SHIMMER_SWEEP_S = 2.0;
14
- const SHIMMER_BAND_HALF = 5.0;
15
- const SHIMMER_PADDING = 10;
16
-
17
- /** The slice of pi's `Theme` shimmerString needs. Structural so callers can supply a stub. */
18
- export interface ShimmerTheme {
19
- getFgAnsi(color: ThemeColor): string;
20
- fg(color: ThemeColor, text: string): string;
21
- }
22
-
23
- /**
24
- * With `invert=true`, the text color is used at rest and the sweep moves toward
25
- * the theme's dim color.
26
- */
27
- export function shimmerString(
28
- text: string,
29
- elapsedMs: number,
30
- theme: ShimmerTheme,
31
- direction: "ltr" | "rtl" = "ltr",
32
- speed: "slow" | "normal" | "fast" = "normal",
33
- invert = false,
34
- ): string {
35
- const chars = [...text];
36
- if (chars.length === 0) return "";
37
- const shimmerBase = ansiToRgb(theme.getFgAnsi(invert ? "text" : "dim"));
38
- const shimmerHighlight = ansiToRgb(theme.getFgAnsi(invert ? "dim" : "text"));
39
- if (!shimmerBase || !shimmerHighlight) return theme.fg("text", text);
40
- const period = chars.length + SHIMMER_PADDING * 2;
41
- const unitsPerS = period / SHIMMER_SWEEP_S;
42
- // The band crosses padding at either end while every character is still dim. Scaling only
43
- // the stretch where it actually overlaps the text keeps that dark pause identical at every
44
- // speed, so `speed` changes the sweep alone rather than the whole cycle.
45
- const litEnter = SHIMMER_PADDING - SHIMMER_BAND_HALF;
46
- const litExit = SHIMMER_PADDING + chars.length - 1 + SHIMMER_BAND_HALF;
47
- const enterS = litEnter / unitsPerS;
48
- const litS = (litExit - litEnter) / unitsPerS / (speed === "slow" ? 0.5 : speed === "fast" ? 2 : 1);
49
- const phase = (elapsedMs / 1000) % (enterS + litS + (period - litExit) / unitsPerS);
50
- const linear =
51
- phase < enterS
52
- ? phase * unitsPerS
53
- : phase < enterS + litS
54
- ? litEnter + ((phase - enterS) / litS) * (litExit - litEnter)
55
- : litExit + (phase - enterS - litS) * unitsPerS;
56
- const pos = direction === "rtl" ? period - linear : linear;
57
-
58
- let out = "";
59
- for (let i = 0; i < chars.length; i++) {
60
- const ch = chars[i]!;
61
- const dist = Math.abs(i + SHIMMER_PADDING - pos);
62
- const t = dist <= SHIMMER_BAND_HALF ? 0.5 * (1 + Math.cos((Math.PI * dist) / SHIMMER_BAND_HALF)) : 0;
63
- const alpha = t * 0.9;
64
- const r = Math.round(shimmerHighlight[0] * alpha + shimmerBase[0] * (1 - alpha));
65
- const g = Math.round(shimmerHighlight[1] * alpha + shimmerBase[1] * (1 - alpha));
66
- const b = Math.round(shimmerHighlight[2] * alpha + shimmerBase[2] * (1 - alpha));
67
- const bold = !invert && t > 0.2 ? "\x1b[1m" : "";
68
- out += `${bold}\x1b[38;2;${r};${g};${b}m${ch}\x1b[22m`;
69
- }
70
- return out + "\x1b[0m";
71
- }
72
-
73
- function ansiToRgb(ansi: string): [number, number, number] | null {
74
- const match = ansi.match(/^\x1b\[38;2;(\d+);(\d+);(\d+)m$/);
75
- if (!match) return null;
76
- return [Number(match[1]), Number(match[2]), Number(match[3])];
77
- }
@@ -1,35 +0,0 @@
1
- /**
2
- * Shared renderer for one-line tool-activity labels (`formatToolActivity`
3
- * output), so the MoA progress table and the cancel overlay color a tool
4
- * name and its argument identically, following pi's own read/grep/edit/write
5
- * renderers (bold `toolTitle` name, `accent` argument).
6
- *
7
- * Splits only at the first run of whitespace — the boundary
8
- * `formatToolActivity` itself produces between a tool name and its argument
9
- * — and never rewrites the text on either side, so activity strings stay
10
- * byte-identical for the runtime's equality-based loop detection.
11
- */
12
-
13
- import type { ThemeColor } from "@earendil-works/pi-coding-agent";
14
-
15
- /** The slice of pi's `Theme` this renderer needs. */
16
- export interface ActivityTheme {
17
- fg(color: ThemeColor, text: string): string;
18
- bold(text: string): string;
19
- }
20
-
21
- const ACTIVITY_SPLIT = /^(\s*)(\S+)(\s*)([\s\S]*)$/;
22
-
23
- /**
24
- * Highlight a plain activity string as a bold `toolTitle` tool name followed
25
- * by its `accent` argument, preserving every character — including leading
26
- * and repeated whitespace — exactly. Falls back to the raw string when no
27
- * tool token can be found at all (empty or whitespace-only input).
28
- */
29
- export function highlightActivity(theme: ActivityTheme, activity: string): string {
30
- const match = activity.match(ACTIVITY_SPLIT);
31
- if (!match) return activity;
32
- const [, leading, tool, sep, detail] = match;
33
- const styledTool = theme.fg("toolTitle", theme.bold(tool));
34
- return detail ? `${leading}${styledTool}${sep}${theme.fg("accent", detail)}` : `${leading}${styledTool}${sep}`;
35
- }