@pi-unipi/core 2.1.2 → 2.2.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.
- package/constants.ts +18 -0
- package/index.ts +1 -0
- package/package.json +1 -1
- package/tui-width.ts +127 -0
package/constants.ts
CHANGED
|
@@ -39,6 +39,7 @@ export const MODULES = {
|
|
|
39
39
|
UPDATER: "@pi-unipi/updater",
|
|
40
40
|
INPUT_SHORTCUTS: "@pi-unipi/input-shortcuts",
|
|
41
41
|
COCOINDEX: "@pi-unipi/cocoindex",
|
|
42
|
+
IMAGE: "@pi-unipi/image",
|
|
42
43
|
} as const;
|
|
43
44
|
|
|
44
45
|
/** Workflow command names */
|
|
@@ -192,6 +193,23 @@ export const ASK_USER_TOOLS = {
|
|
|
192
193
|
ASK: "ask_user",
|
|
193
194
|
} as const;
|
|
194
195
|
|
|
196
|
+
/** Image tool names */
|
|
197
|
+
export const IMAGE_TOOLS = {
|
|
198
|
+
GENERATE: "image_generate",
|
|
199
|
+
RECOGNIZE: "image_recognize",
|
|
200
|
+
} as const;
|
|
201
|
+
|
|
202
|
+
/** Image command names */
|
|
203
|
+
export const IMAGE_COMMANDS = {
|
|
204
|
+
SETTINGS: "image-settings",
|
|
205
|
+
} as const;
|
|
206
|
+
|
|
207
|
+
/** Image directory paths */
|
|
208
|
+
export const IMAGE_DIRS = {
|
|
209
|
+
CONFIG: "~/.unipi/config/image",
|
|
210
|
+
OUTPUT: "~/.unipi/images",
|
|
211
|
+
} as const;
|
|
212
|
+
|
|
195
213
|
/** MCP directory paths */
|
|
196
214
|
export const MCP_DIRS = {
|
|
197
215
|
GLOBAL_CONFIG: "~/.unipi/config/mcp",
|
package/index.ts
CHANGED
package/package.json
CHANGED
package/tui-width.ts
ADDED
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @pi-unipi/core — TUI width helpers
|
|
3
|
+
*
|
|
4
|
+
* pi-tui's differential renderer throws when a rendered line is wider than the
|
|
5
|
+
* terminal (see `TUI.render` in @earendil-works/pi-tui — it writes
|
|
6
|
+
* `~/.pi/agent/pi-crash.log`, stops the TUI and rethrows). That makes any
|
|
7
|
+
* "minimum width" floor a crash waiting to happen on a narrow terminal:
|
|
8
|
+
*
|
|
9
|
+
* const innerWidth = Math.max(40, width - 2); // ← 42-col lines at width=20
|
|
10
|
+
*
|
|
11
|
+
* The invariant every component must hold is:
|
|
12
|
+
*
|
|
13
|
+
* for every returned line: visibleWidth(line) <= width
|
|
14
|
+
*
|
|
15
|
+
* These helpers make that invariant easy to satisfy. They are pure arithmetic
|
|
16
|
+
* so this module stays free of a pi-tui dependency.
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Terminals narrower than this cannot usefully show a bordered box: two
|
|
21
|
+
* columns go to the border, leaving too little for content. Below the
|
|
22
|
+
* threshold, callers should render borderless (see {@link shouldRenderBorder}).
|
|
23
|
+
*/
|
|
24
|
+
export const MIN_BORDERED_WIDTH = 12;
|
|
25
|
+
|
|
26
|
+
/** Smallest width any layout is asked to cope with. */
|
|
27
|
+
export const MIN_RENDER_WIDTH = 1;
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Normalize an incoming render width. Guards against `0`, negative, `NaN`
|
|
31
|
+
* and fractional widths, all of which have been observed during terminal
|
|
32
|
+
* resize races.
|
|
33
|
+
*/
|
|
34
|
+
export function normalizeWidth(width: number): number {
|
|
35
|
+
if (!Number.isFinite(width)) return MIN_RENDER_WIDTH;
|
|
36
|
+
return Math.max(MIN_RENDER_WIDTH, Math.floor(width));
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Whether a bordered box fits at this width. When false, render the content
|
|
41
|
+
* without `│` side borders so the full width is usable.
|
|
42
|
+
*/
|
|
43
|
+
export function shouldRenderBorder(width: number): boolean {
|
|
44
|
+
return normalizeWidth(width) >= MIN_BORDERED_WIDTH;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Content width inside a bordered box, i.e. the terminal width minus the two
|
|
49
|
+
* border columns, so that `│ + content + │` is `<= width`.
|
|
50
|
+
*
|
|
51
|
+
* Use this for components that always draw a border. Components that can drop
|
|
52
|
+
* the border on narrow terminals should branch on {@link shouldRenderBorder}
|
|
53
|
+
* and use {@link adaptiveInnerWidth} instead.
|
|
54
|
+
*/
|
|
55
|
+
export function boxInnerWidth(width: number): number {
|
|
56
|
+
return Math.max(1, normalizeWidth(width) - 2);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Content width for components that drop their border on narrow terminals:
|
|
61
|
+
* the box inner width when a border fits, otherwise the full width.
|
|
62
|
+
*
|
|
63
|
+
* Pair with {@link shouldRenderBorder} to decide whether to emit the border
|
|
64
|
+
* characters. Together they guarantee every emitted line is `<= width` at any
|
|
65
|
+
* width down to 1.
|
|
66
|
+
*/
|
|
67
|
+
export function adaptiveInnerWidth(width: number): number {
|
|
68
|
+
const w = normalizeWidth(width);
|
|
69
|
+
return shouldRenderBorder(w) ? boxInnerWidth(w) : w;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Width remaining after reserving `reserved` columns for a prefix, indent or
|
|
74
|
+
* gutter. Never returns less than 1, so it is safe to pass to wrapping and
|
|
75
|
+
* truncation helpers (which throw or misbehave on non-positive widths).
|
|
76
|
+
*/
|
|
77
|
+
export function contentWidth(available: number, reserved: number): number {
|
|
78
|
+
return Math.max(1, normalizeWidth(available) - Math.max(0, Math.floor(reserved)));
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Clamp a repeat count to a non-negative integer.
|
|
83
|
+
*
|
|
84
|
+
* `String.prototype.repeat` throws `RangeError: Invalid count value` for
|
|
85
|
+
* negative counts, which crashes the render pass.
|
|
86
|
+
*/
|
|
87
|
+
export function safeRepeatCount(count: number): number {
|
|
88
|
+
if (!Number.isFinite(count)) return 0;
|
|
89
|
+
return Math.max(0, Math.floor(count));
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/** `" ".repeat(n)` that cannot throw. */
|
|
93
|
+
export function safeRepeat(char: string, count: number): string {
|
|
94
|
+
return char.repeat(safeRepeatCount(count));
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Width-keyed render cache.
|
|
99
|
+
*
|
|
100
|
+
* Components that cache their rendered lines must invalidate on width change.
|
|
101
|
+
* pi-tui's `requestRender()` does *not* call `invalidate()`, so a component
|
|
102
|
+
* that caches `string[]` without keying on width will return stale, over-wide
|
|
103
|
+
* lines after the terminal is made narrower — and the next differential frame
|
|
104
|
+
* throws.
|
|
105
|
+
*/
|
|
106
|
+
export class WidthKeyedCache {
|
|
107
|
+
private lines: string[] | null = null;
|
|
108
|
+
private width = -1;
|
|
109
|
+
|
|
110
|
+
/** Cached lines for this width, or `null` on miss. */
|
|
111
|
+
get(width: number): string[] | null {
|
|
112
|
+
return this.lines !== null && this.width === normalizeWidth(width) ? this.lines : null;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/** Store lines for this width. Returns the lines for convenient chaining. */
|
|
116
|
+
set(width: number, lines: string[]): string[] {
|
|
117
|
+
this.lines = lines;
|
|
118
|
+
this.width = normalizeWidth(width);
|
|
119
|
+
return lines;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/** Drop the cache — call from `invalidate()` and on any state change. */
|
|
123
|
+
clear(): void {
|
|
124
|
+
this.lines = null;
|
|
125
|
+
this.width = -1;
|
|
126
|
+
}
|
|
127
|
+
}
|