@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 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
@@ -9,3 +9,4 @@ export * from "./events.js";
9
9
  export * from "./sandbox.js";
10
10
  export * from "./utils.js";
11
11
  export * from "./model-cache.js";
12
+ export * from "./tui-width.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi-unipi/core",
3
- "version": "2.1.2",
3
+ "version": "2.2.0",
4
4
  "description": "Shared utilities, event types, and constants for Unipi extension suite",
5
5
  "type": "module",
6
6
  "license": "MIT",
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
+ }