@pi-unipi/unipi 2.1.3 → 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/README.md +6 -2
- package/package.json +23 -22
- package/packages/ask-user/ask-ui.ts +45 -31
- package/packages/ask-user/launcher-ui.ts +26 -22
- package/packages/ask-user/package.json +2 -2
- package/packages/ask-user/settings-tui.ts +22 -9
- package/packages/autocomplete/package.json +1 -1
- package/packages/autocomplete/src/constants.ts +5 -0
- package/packages/btw/package.json +2 -2
- package/packages/cocoindex/package.json +2 -2
- package/packages/compactor/package.json +3 -3
- package/packages/compactor/src/tui/settings-overlay.ts +2 -1
- package/packages/core/constants.ts +18 -0
- package/packages/core/index.ts +1 -0
- package/packages/core/package.json +1 -1
- package/packages/core/tui-width.ts +127 -0
- package/packages/footer/package.json +2 -2
- package/packages/footer/src/tui/settings-tui.ts +2 -1
- package/packages/image/README.md +82 -0
- package/packages/image/index.ts +1 -0
- package/packages/image/package.json +57 -0
- package/packages/image/skills/image/SKILL.md +73 -0
- package/packages/image/src/commands.ts +17 -0
- package/packages/image/src/generate.ts +201 -0
- package/packages/image/src/image-source.ts +204 -0
- package/packages/image/src/index.ts +99 -0
- package/packages/image/src/models.ts +290 -0
- package/packages/image/src/recognize.ts +223 -0
- package/packages/image/src/settings.ts +149 -0
- package/packages/image/src/tools.ts +296 -0
- package/packages/image/src/tui/model-selector.ts +279 -0
- package/packages/image/src/tui/settings-dialog.ts +236 -0
- package/packages/info-screen/package.json +2 -2
- package/packages/info-screen/tui/info-overlay.ts +505 -0
- package/packages/input-shortcuts/package.json +2 -2
- package/packages/kanboard/package.json +2 -2
- package/packages/kanboard/tui/kanboard-overlay.ts +300 -0
- package/packages/mcp/package.json +2 -2
- package/packages/mcp/src/tui/add-overlay.ts +10 -7
- package/packages/mcp/src/tui/settings-overlay.ts +10 -7
- package/packages/memory/package.json +3 -3
- package/packages/memory/tui/settings-tui.ts +281 -0
- package/packages/milestone/package.json +2 -2
- package/packages/notify/README.md +2 -0
- package/packages/notify/events.ts +11 -0
- package/packages/notify/package.json +2 -2
- package/packages/notify/permission-prompt-message.ts +95 -0
- package/packages/notify/platforms/focus-win.ts +123 -0
- package/packages/notify/platforms/focus.ts +33 -0
- package/packages/notify/platforms/gotify.ts +36 -0
- package/packages/notify/platforms/native.ts +79 -0
- package/packages/notify/platforms/node-notifier.d.ts +20 -0
- package/packages/notify/platforms/ntfy.ts +48 -0
- package/packages/notify/platforms/telegram.ts +77 -0
- package/packages/notify/settings.ts +1 -0
- package/packages/notify/skills/configure-notify/SKILL.md +28 -1
- package/packages/notify/src/__tests__/permission-prompt-message.test.ts +172 -0
- package/packages/notify/tui/gotify-setup.ts +529 -0
- package/packages/notify/tui/ntfy-setup.ts +653 -0
- package/packages/notify/tui/recap-model-selector.ts +301 -0
- package/packages/notify/tui/settings-overlay.ts +353 -0
- package/packages/notify/tui/telegram-setup.ts +302 -0
- package/packages/ralph/package.json +3 -3
- package/packages/subagents/package.json +4 -4
- package/packages/subagents/src/__tests__/shutdown-stale-ctx.test.ts +185 -0
- package/packages/subagents/src/index.ts +52 -10
- package/packages/unipi/index.ts +2 -0
- package/packages/updater/package.json +2 -2
- package/packages/updater/src/tui/changelog-overlay.ts +2 -2
- package/packages/updater/src/tui/readme-overlay.ts +2 -1
- package/packages/updater/src/tui/update-overlay.ts +2 -1
- package/packages/utility/package.json +2 -2
- package/packages/utility/src/tui/badge-settings-tui.ts +2 -2
- package/packages/utility/src/tui/util-settings-tui.ts +2 -2
- package/packages/web-api/README.md +28 -11
- package/packages/web-api/package.json +8 -2
- package/packages/web-api/skills/web/SKILL.md +36 -9
- package/packages/web-api/src/index.ts +15 -0
- package/packages/web-api/src/providers/duckduckgo.ts +71 -27
- package/packages/web-api/src/providers/firecrawl.ts +1 -1
- package/packages/web-api/src/providers/jina-reader.ts +1 -1
- package/packages/web-api/src/providers/jina-search.ts +1 -1
- package/packages/web-api/src/providers/perplexity.ts +2 -2
- package/packages/web-api/src/providers/serpapi.ts +1 -1
- package/packages/web-api/src/providers/tavily.ts +1 -1
- package/packages/web-api/src/providers/wigolo-client.ts +196 -0
- package/packages/web-api/src/providers/wigolo.ts +117 -0
- package/packages/web-api/src/settings.ts +1 -0
- package/packages/web-api/src/tools.ts +93 -36
- package/packages/workflow/package.json +2 -2
|
@@ -0,0 +1,300 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @pi-unipi/kanboard — Kanboard TUI Overlay
|
|
3
|
+
*
|
|
4
|
+
* Two tabs: Tasks list and Kanban Board.
|
|
5
|
+
* Uses pi-tui overlay API (same pattern as MCP add overlay).
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import {
|
|
9
|
+
Key,
|
|
10
|
+
matchesKey,
|
|
11
|
+
truncateToWidth,
|
|
12
|
+
type TUI,
|
|
13
|
+
visibleWidth,
|
|
14
|
+
} from "@earendil-works/pi-tui";
|
|
15
|
+
import type { Theme, KeybindingsManager } from "@earendil-works/pi-coding-agent";
|
|
16
|
+
import type { ParsedDoc, ParsedItem, ItemStatus } from "../types.js";
|
|
17
|
+
import { createDefaultRegistry } from "../parser/index.js";
|
|
18
|
+
|
|
19
|
+
type Tab = "tasks" | "board";
|
|
20
|
+
|
|
21
|
+
interface KanboardState {
|
|
22
|
+
tab: Tab;
|
|
23
|
+
tasks: ParsedItem[];
|
|
24
|
+
taskIndex: number;
|
|
25
|
+
taskScroll: number;
|
|
26
|
+
/** Board columns: index into tasks array grouped by status */
|
|
27
|
+
boardColumns: {
|
|
28
|
+
todo: number[];
|
|
29
|
+
inProgress: number[];
|
|
30
|
+
done: number[];
|
|
31
|
+
};
|
|
32
|
+
boardCol: number; // 0=todo, 1=inProgress, 2=done
|
|
33
|
+
boardIndex: number[];
|
|
34
|
+
pendingG: boolean;
|
|
35
|
+
docsRoot: string;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/** Status icon */
|
|
39
|
+
function statusIcon(status: ItemStatus): string {
|
|
40
|
+
switch (status) {
|
|
41
|
+
case "done":
|
|
42
|
+
return "✓";
|
|
43
|
+
case "in-progress":
|
|
44
|
+
return "◐";
|
|
45
|
+
default:
|
|
46
|
+
return "○";
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/** Pad string to visible width */
|
|
51
|
+
function padVisible(content: string, targetWidth: number): string {
|
|
52
|
+
const pad = Math.max(0, targetWidth - visibleWidth(content));
|
|
53
|
+
return content + " ".repeat(pad);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Render the kanboard overlay.
|
|
58
|
+
*/
|
|
59
|
+
export function renderKanboardOverlay(params?: {
|
|
60
|
+
docsRoot?: string;
|
|
61
|
+
onComplete?: () => void;
|
|
62
|
+
}) {
|
|
63
|
+
return (
|
|
64
|
+
tui: TUI,
|
|
65
|
+
theme: Theme,
|
|
66
|
+
_kb: KeybindingsManager,
|
|
67
|
+
done: (result: { viewed: boolean } | null) => void,
|
|
68
|
+
) => {
|
|
69
|
+
const state: KanboardState = {
|
|
70
|
+
tab: "tasks",
|
|
71
|
+
tasks: [],
|
|
72
|
+
taskIndex: 0,
|
|
73
|
+
taskScroll: 0,
|
|
74
|
+
boardColumns: { todo: [], inProgress: [], done: [] },
|
|
75
|
+
boardCol: 0,
|
|
76
|
+
boardIndex: [0, 0, 0],
|
|
77
|
+
pendingG: false,
|
|
78
|
+
docsRoot: params?.docsRoot ?? ".unipi/docs",
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
// Load data
|
|
82
|
+
let loaded = false;
|
|
83
|
+
const ensureLoaded = async () => {
|
|
84
|
+
if (loaded) return;
|
|
85
|
+
const registry = await createDefaultRegistry();
|
|
86
|
+
const docs = registry.parseAll(state.docsRoot);
|
|
87
|
+
state.tasks = docs.flatMap((d) => d.items);
|
|
88
|
+
|
|
89
|
+
// Build board columns
|
|
90
|
+
state.boardColumns = { todo: [], inProgress: [], done: [] };
|
|
91
|
+
state.tasks.forEach((item, idx) => {
|
|
92
|
+
if (item.status === "done") state.boardColumns.done.push(idx);
|
|
93
|
+
else if (item.status === "in-progress") state.boardColumns.inProgress.push(idx);
|
|
94
|
+
else state.boardColumns.todo.push(idx);
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
// Init board indices
|
|
98
|
+
state.boardIndex = [
|
|
99
|
+
0,
|
|
100
|
+
0,
|
|
101
|
+
0,
|
|
102
|
+
];
|
|
103
|
+
|
|
104
|
+
loaded = true;
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
const render = async () => {
|
|
108
|
+
await ensureLoaded();
|
|
109
|
+
|
|
110
|
+
const width = tui.terminal?.columns ?? 80;
|
|
111
|
+
const height = tui.terminal?.rows ?? 24;
|
|
112
|
+
const lines: string[] = [];
|
|
113
|
+
|
|
114
|
+
// Header
|
|
115
|
+
const header = " 📋 Kanboard ";
|
|
116
|
+
const tabLine =
|
|
117
|
+
" [T]asks | [B]oard ".replace(
|
|
118
|
+
state.tab === "tasks" ? "[T]" : "T",
|
|
119
|
+
state.tab === "tasks" ? "▸T" : " T",
|
|
120
|
+
).replace(
|
|
121
|
+
state.tab === "board" ? "[B]" : "B",
|
|
122
|
+
state.tab === "board" ? "▸B" : " B",
|
|
123
|
+
);
|
|
124
|
+
lines.push(truncateToWidth(header, width));
|
|
125
|
+
lines.push(truncateToWidth(tabLine, width));
|
|
126
|
+
lines.push("─".repeat(width));
|
|
127
|
+
|
|
128
|
+
if (state.tab === "tasks") {
|
|
129
|
+
renderTasksTab(lines, width, height - 5);
|
|
130
|
+
} else {
|
|
131
|
+
renderBoardTab(lines, width, height - 5);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// Footer
|
|
135
|
+
lines.push("─".repeat(width));
|
|
136
|
+
const footer = " j/k: navigate Tab: switch tab q/Esc: close ";
|
|
137
|
+
lines.push(truncateToWidth(footer, width));
|
|
138
|
+
|
|
139
|
+
(tui as unknown as { setContent(lines: string[]): void }).setContent(lines);
|
|
140
|
+
};
|
|
141
|
+
|
|
142
|
+
const renderTasksTab = (lines: string[], width: number, maxLines: number) => {
|
|
143
|
+
if (state.tasks.length === 0) {
|
|
144
|
+
lines.push(truncateToWidth(" No tasks found.", width));
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// Ensure index in range
|
|
149
|
+
state.taskIndex = Math.min(state.taskIndex, state.tasks.length - 1);
|
|
150
|
+
state.taskIndex = Math.max(0, state.taskIndex);
|
|
151
|
+
|
|
152
|
+
// Scroll to keep selected visible
|
|
153
|
+
if (state.taskIndex < state.taskScroll) state.taskScroll = state.taskIndex;
|
|
154
|
+
if (state.taskIndex >= state.taskScroll + maxLines) {
|
|
155
|
+
state.taskScroll = state.taskIndex - maxLines + 1;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
const visible = state.tasks.slice(
|
|
159
|
+
state.taskScroll,
|
|
160
|
+
state.taskScroll + maxLines,
|
|
161
|
+
);
|
|
162
|
+
|
|
163
|
+
for (let i = 0; i < visible.length; i++) {
|
|
164
|
+
const item = visible[i];
|
|
165
|
+
const globalIdx = state.taskScroll + i;
|
|
166
|
+
const selected = globalIdx === state.taskIndex;
|
|
167
|
+
const prefix = selected ? " ▸ " : " ";
|
|
168
|
+
const icon = statusIcon(item.status);
|
|
169
|
+
const source = `[${item.sourceFile}]`;
|
|
170
|
+
const line = `${prefix}${icon} ${item.text} ${source}`;
|
|
171
|
+
lines.push(
|
|
172
|
+
truncateToWidth(
|
|
173
|
+
selected ? `\x1b[7m${padVisible(line, width)}\x1b[0m` : line,
|
|
174
|
+
width,
|
|
175
|
+
),
|
|
176
|
+
);
|
|
177
|
+
}
|
|
178
|
+
};
|
|
179
|
+
|
|
180
|
+
const renderBoardTab = (lines: string[], width: number, maxLines: number) => {
|
|
181
|
+
const colWidth = Math.floor(width / 3);
|
|
182
|
+
const cols: Array<{ title: string; indices: number[]; colIdx: number }> = [
|
|
183
|
+
{ title: "To Do", indices: state.boardColumns.todo, colIdx: 0 },
|
|
184
|
+
{ title: "In Progress", indices: state.boardColumns.inProgress, colIdx: 1 },
|
|
185
|
+
{ title: "Done", indices: state.boardColumns.done, colIdx: 2 },
|
|
186
|
+
];
|
|
187
|
+
|
|
188
|
+
// Column headers
|
|
189
|
+
let headerLine = "";
|
|
190
|
+
for (const col of cols) {
|
|
191
|
+
const isActive = col.colIdx === state.boardCol;
|
|
192
|
+
const title = isActive ? `▸ ${col.title}` : ` ${col.title}`;
|
|
193
|
+
headerLine += padVisible(` ${title} (${col.indices.length})`, colWidth);
|
|
194
|
+
}
|
|
195
|
+
lines.push(truncateToWidth(headerLine, width));
|
|
196
|
+
lines.push("─".repeat(width));
|
|
197
|
+
|
|
198
|
+
// Column items
|
|
199
|
+
const maxItems = Math.max(
|
|
200
|
+
...cols.map((c) => c.indices.length),
|
|
201
|
+
maxLines - 2,
|
|
202
|
+
);
|
|
203
|
+
for (let row = 0; row < Math.min(maxItems, maxLines - 2); row++) {
|
|
204
|
+
let rowLine = "";
|
|
205
|
+
for (const col of cols) {
|
|
206
|
+
const active = col.colIdx === state.boardCol;
|
|
207
|
+
const boardIdx = state.boardIndex[col.colIdx] ?? 0;
|
|
208
|
+
if (row < col.indices.length) {
|
|
209
|
+
const item = state.tasks[col.indices[row]];
|
|
210
|
+
const selected = active && row === boardIdx;
|
|
211
|
+
const icon = statusIcon(item.status);
|
|
212
|
+
const text = truncateToWidth(` ${icon} ${item.text}`, colWidth - 1);
|
|
213
|
+
if (selected) {
|
|
214
|
+
rowLine += `\x1b[7m${padVisible(text, colWidth)}\x1b[0m`;
|
|
215
|
+
} else {
|
|
216
|
+
rowLine += padVisible(text, colWidth);
|
|
217
|
+
}
|
|
218
|
+
} else {
|
|
219
|
+
rowLine += padVisible("", colWidth);
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
lines.push(truncateToWidth(rowLine, width));
|
|
223
|
+
}
|
|
224
|
+
};
|
|
225
|
+
|
|
226
|
+
const handleKey = async (key: string) => {
|
|
227
|
+
await ensureLoaded();
|
|
228
|
+
|
|
229
|
+
// Close
|
|
230
|
+
if (matchesKey(key, "q") || matchesKey(key, "escape")) {
|
|
231
|
+
done({ viewed: true });
|
|
232
|
+
return;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
// Tab switching
|
|
236
|
+
if (matchesKey(key, "tab") || matchesKey(key, "b")) {
|
|
237
|
+
state.tab = state.tab === "tasks" ? "board" : "tasks";
|
|
238
|
+
render();
|
|
239
|
+
return;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
if (matchesKey(key, "t")) {
|
|
243
|
+
state.tab = "tasks";
|
|
244
|
+
render();
|
|
245
|
+
return;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
// Navigation
|
|
249
|
+
if (state.tab === "tasks") {
|
|
250
|
+
if (matchesKey(key, "j") || matchesKey(key, "down")) {
|
|
251
|
+
state.taskIndex = Math.min(state.taskIndex + 1, state.tasks.length - 1);
|
|
252
|
+
} else if (matchesKey(key, "k") || matchesKey(key, "up")) {
|
|
253
|
+
state.taskIndex = Math.max(state.taskIndex - 1, 0);
|
|
254
|
+
} else if (matchesKey(key, "g")) {
|
|
255
|
+
if (state.pendingG) {
|
|
256
|
+
state.taskIndex = 0;
|
|
257
|
+
state.pendingG = false;
|
|
258
|
+
} else {
|
|
259
|
+
state.pendingG = true;
|
|
260
|
+
setTimeout(() => {
|
|
261
|
+
state.pendingG = false;
|
|
262
|
+
}, 500);
|
|
263
|
+
}
|
|
264
|
+
} else if (matchesKey(key, "shift+g")) {
|
|
265
|
+
state.taskIndex = state.tasks.length - 1;
|
|
266
|
+
}
|
|
267
|
+
} else {
|
|
268
|
+
// Board navigation
|
|
269
|
+
const cols = [
|
|
270
|
+
state.boardColumns.todo,
|
|
271
|
+
state.boardColumns.inProgress,
|
|
272
|
+
state.boardColumns.done,
|
|
273
|
+
];
|
|
274
|
+
|
|
275
|
+
if (matchesKey(key, "h") || matchesKey(key, "left")) {
|
|
276
|
+
state.boardCol = Math.max(0, state.boardCol - 1);
|
|
277
|
+
} else if (matchesKey(key, "l") || matchesKey(key, "right")) {
|
|
278
|
+
state.boardCol = Math.min(2, state.boardCol + 1);
|
|
279
|
+
} else if (matchesKey(key, "j") || matchesKey(key, "down")) {
|
|
280
|
+
const col = cols[state.boardCol];
|
|
281
|
+
state.boardIndex[state.boardCol] = Math.min(
|
|
282
|
+
state.boardIndex[state.boardCol] + 1,
|
|
283
|
+
col.length - 1,
|
|
284
|
+
);
|
|
285
|
+
} else if (matchesKey(key, "k") || matchesKey(key, "up")) {
|
|
286
|
+
state.boardIndex[state.boardCol] = Math.max(
|
|
287
|
+
state.boardIndex[state.boardCol] - 1,
|
|
288
|
+
0,
|
|
289
|
+
);
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
render();
|
|
294
|
+
};
|
|
295
|
+
|
|
296
|
+
// Start
|
|
297
|
+
render();
|
|
298
|
+
(tui as unknown as { on(event: string, handler: (data: string) => void): void }).on("key", handleKey);
|
|
299
|
+
};
|
|
300
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pi-unipi/mcp",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.2.0",
|
|
4
4
|
"description": "MCP server management extension for Pi coding agent — browse, add, configure, and use MCP servers",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "src/index.ts",
|
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
"README.md"
|
|
28
28
|
],
|
|
29
29
|
"dependencies": {
|
|
30
|
-
"@pi-unipi/core": "2.
|
|
30
|
+
"@pi-unipi/core": "2.2.0"
|
|
31
31
|
},
|
|
32
32
|
"peerDependencies": {
|
|
33
33
|
"@earendil-works/pi-coding-agent": "^0.80.0",
|
|
@@ -31,6 +31,7 @@ import {
|
|
|
31
31
|
getProjectConfigDir,
|
|
32
32
|
} from "../config/manager.js";
|
|
33
33
|
import { validateMcpConfig, DEFAULT_MCP_CONFIG, DEFAULT_METADATA } from "../config/schema.js";
|
|
34
|
+
import { boxInnerWidth, normalizeWidth, WidthKeyedCache } from "@pi-unipi/core";
|
|
34
35
|
|
|
35
36
|
type Mode = "normal" | "search" | "editor";
|
|
36
37
|
type StatusKind = "info" | "success" | "warn" | "error";
|
|
@@ -158,11 +159,12 @@ export function renderMcpAddOverlay(params?: {
|
|
|
158
159
|
// Ctrl+S handles saving; no submit path needed.
|
|
159
160
|
(editor as any).disableSubmit = true;
|
|
160
161
|
|
|
161
|
-
|
|
162
|
+
// Width-keyed so a terminal resize can never serve stale, over-wide lines.
|
|
163
|
+
const lineCache = new WidthKeyedCache();
|
|
162
164
|
let lastListHeight = 12;
|
|
163
165
|
|
|
164
166
|
function refresh() {
|
|
165
|
-
|
|
167
|
+
lineCache.clear();
|
|
166
168
|
tui.requestRender();
|
|
167
169
|
}
|
|
168
170
|
|
|
@@ -537,11 +539,13 @@ export function renderMcpAddOverlay(params?: {
|
|
|
537
539
|
|
|
538
540
|
// ─── Rendering ────────────────────────────────────────────────────
|
|
539
541
|
|
|
540
|
-
function render(
|
|
541
|
-
|
|
542
|
+
function render(rawWidth: number): string[] {
|
|
543
|
+
const width = normalizeWidth(rawWidth);
|
|
544
|
+
const cached = lineCache.get(width);
|
|
545
|
+
if (cached) return cached;
|
|
542
546
|
|
|
543
547
|
const lines: string[] = [];
|
|
544
|
-
const innerWidth =
|
|
548
|
+
const innerWidth = boxInnerWidth(width);
|
|
545
549
|
|
|
546
550
|
// Pane widths: account for left │, middle │, right │ borders
|
|
547
551
|
const leftW = Math.floor((innerWidth - 1) / 2);
|
|
@@ -776,8 +780,7 @@ export function renderMcpAddOverlay(params?: {
|
|
|
776
780
|
);
|
|
777
781
|
lines.push(border(`╰${"─".repeat(innerWidth)}╯`));
|
|
778
782
|
|
|
779
|
-
|
|
780
|
-
return lines;
|
|
783
|
+
return lineCache.set(width, lines);
|
|
781
784
|
}
|
|
782
785
|
|
|
783
786
|
return { render, invalidate: refresh, handleInput };
|
|
@@ -16,6 +16,7 @@ import {
|
|
|
16
16
|
getGlobalConfigDir,
|
|
17
17
|
getProjectConfigDir,
|
|
18
18
|
} from "../config/manager.js";
|
|
19
|
+
import { boxInnerWidth, normalizeWidth, WidthKeyedCache } from "@pi-unipi/core";
|
|
19
20
|
|
|
20
21
|
/** Server display item */
|
|
21
22
|
interface ServerDisplayItem {
|
|
@@ -65,7 +66,8 @@ export function renderMcpSettingsOverlay(params?: {
|
|
|
65
66
|
confirmDelete: null,
|
|
66
67
|
};
|
|
67
68
|
|
|
68
|
-
|
|
69
|
+
// Width-keyed so a terminal resize can never serve stale, over-wide lines.
|
|
70
|
+
const lineCache = new WidthKeyedCache();
|
|
69
71
|
|
|
70
72
|
function refreshServers() {
|
|
71
73
|
const configDir =
|
|
@@ -115,7 +117,7 @@ export function renderMcpSettingsOverlay(params?: {
|
|
|
115
117
|
refreshServers();
|
|
116
118
|
|
|
117
119
|
function refresh() {
|
|
118
|
-
|
|
120
|
+
lineCache.clear();
|
|
119
121
|
tui.requestRender();
|
|
120
122
|
}
|
|
121
123
|
|
|
@@ -275,11 +277,13 @@ export function renderMcpSettingsOverlay(params?: {
|
|
|
275
277
|
return content + " ".repeat(pad);
|
|
276
278
|
}
|
|
277
279
|
|
|
278
|
-
function render(
|
|
279
|
-
|
|
280
|
+
function render(rawWidth: number): string[] {
|
|
281
|
+
const width = normalizeWidth(rawWidth);
|
|
282
|
+
const cached = lineCache.get(width);
|
|
283
|
+
if (cached) return cached;
|
|
280
284
|
|
|
281
285
|
const lines: string[] = [];
|
|
282
|
-
const innerWidth =
|
|
286
|
+
const innerWidth = boxInnerWidth(width);
|
|
283
287
|
|
|
284
288
|
// ── Header ──────────────────────────────────────────────────────
|
|
285
289
|
const header = " MCP Settings ";
|
|
@@ -356,8 +360,7 @@ export function renderMcpSettingsOverlay(params?: {
|
|
|
356
360
|
);
|
|
357
361
|
lines.push(theme.fg("accent", `╰${"─".repeat(innerWidth)}╯`));
|
|
358
362
|
|
|
359
|
-
|
|
360
|
-
return lines;
|
|
363
|
+
return lineCache.set(width, lines);
|
|
361
364
|
}
|
|
362
365
|
|
|
363
366
|
return { render, invalidate: refresh, handleInput };
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pi-unipi/memory",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.2.0",
|
|
4
4
|
"description": "Persistent cross-session memory with MemPalace backend (auto-installed) and SQLite fallback for Pi coding agent",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "index.ts",
|
|
@@ -43,8 +43,8 @@
|
|
|
43
43
|
"better-sqlite3": "^12.9.0",
|
|
44
44
|
"sqlite-vec": "^0.1.9",
|
|
45
45
|
"js-yaml": "^4.1.0",
|
|
46
|
-
"@pi-unipi/core": "2.
|
|
47
|
-
"@pi-unipi/info-screen": "2.
|
|
46
|
+
"@pi-unipi/core": "2.2.0",
|
|
47
|
+
"@pi-unipi/info-screen": "2.2.0"
|
|
48
48
|
},
|
|
49
49
|
"peerDependencies": {
|
|
50
50
|
"@earendil-works/pi-coding-agent": "^0.80.0",
|