replicas-engine 0.1.744 → 0.1.745

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.
@@ -0,0 +1,916 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ isServerCacheValid,
4
+ isUiToolVisibleToModel,
5
+ resourceNameToToolName
6
+ } from "./chunk-Y6VZZNT3.js";
7
+ import {
8
+ createPanelKeys
9
+ } from "./chunk-SFWEF25L.js";
10
+ import {
11
+ getToolNameCandidates,
12
+ isServerDisabled,
13
+ isToolAllowed,
14
+ matchesKey,
15
+ resolveToolPrefix,
16
+ sanitizeTerminalText,
17
+ stripOscSequences,
18
+ truncateToWidth,
19
+ visibleWidth
20
+ } from "./chunk-NZRMKTVA.js";
21
+ import "./chunk-5KSXSK7Y.js";
22
+
23
+ // ../node_modules/.bun/pi-mcp-adapter@2.32.1+04b0d7b0bc944965/node_modules/pi-mcp-adapter/mcp-panel.ts
24
+ import { copyToClipboard } from "@earendil-works/pi-coding-agent";
25
+ var DEFAULT_THEME = {
26
+ border: "2",
27
+ title: "2",
28
+ selected: "36",
29
+ direct: "32",
30
+ needsAuth: "33",
31
+ placeholder: "2;3",
32
+ description: "2",
33
+ hint: "2",
34
+ confirm: "32",
35
+ cancel: "31"
36
+ };
37
+ function fg(code, text) {
38
+ if (!code) return text;
39
+ return `\x1B[${code}m${text}\x1B[0m`;
40
+ }
41
+ var RAINBOW_COLORS = [
42
+ "38;2;178;129;214",
43
+ "38;2;215;135;175",
44
+ "38;2;254;188;56",
45
+ "38;2;228;192;15",
46
+ "38;2;137;210;129",
47
+ "38;2;0;175;175",
48
+ "38;2;23;143;185"
49
+ ];
50
+ function rainbowProgress(filled, total) {
51
+ const dots = [];
52
+ for (let i = 0; i < total; i++) {
53
+ const color = RAINBOW_COLORS[i % RAINBOW_COLORS.length];
54
+ if (!color) continue;
55
+ dots.push(fg(color, i < filled ? "\u25CF" : "\u25CB"));
56
+ }
57
+ return dots.join(" ");
58
+ }
59
+ function fuzzyScore(query, text) {
60
+ const lq = query.toLowerCase();
61
+ const lt = text.toLowerCase();
62
+ if (lt.includes(lq)) return 100 + lq.length / lt.length * 50;
63
+ let score = 0;
64
+ let qi = 0;
65
+ let consecutive = 0;
66
+ for (let i = 0; i < lt.length && qi < lq.length; i++) {
67
+ if (lt[i] === lq[qi]) {
68
+ score += 10 + consecutive;
69
+ consecutive += 5;
70
+ qi++;
71
+ } else {
72
+ consecutive = 0;
73
+ }
74
+ }
75
+ return qi === lq.length ? score : 0;
76
+ }
77
+ function sanitizeDisplayText(text) {
78
+ return sanitizeTerminalText(text ?? "");
79
+ }
80
+ function sanitizeRowContent(content) {
81
+ const withoutOsc = stripOscSequences(content);
82
+ let result = "";
83
+ let pendingSpace = false;
84
+ for (let i = 0; i < withoutOsc.length; i++) {
85
+ const rest = withoutOsc.slice(i);
86
+ const ansi = rest.match(/^(?:\x1b\[[0-?]*[ -/]*[@-~]|\x1b[@-Z\\-_])/);
87
+ if (ansi) {
88
+ result += ansi[0];
89
+ i += ansi[0].length - 1;
90
+ continue;
91
+ }
92
+ const code = withoutOsc.charCodeAt(i);
93
+ if (code <= 31 || code === 127 || code >= 128 && code <= 159) {
94
+ pendingSpace = true;
95
+ continue;
96
+ }
97
+ if (pendingSpace && result && !result.endsWith(" ")) {
98
+ result += " ";
99
+ }
100
+ pendingSpace = false;
101
+ result += withoutOsc[i];
102
+ }
103
+ return result;
104
+ }
105
+ function estimateTokens(tool) {
106
+ const schemaLen = JSON.stringify(tool.inputSchema ?? {}).length;
107
+ const descLen = tool.description?.length ?? 0;
108
+ return Math.ceil((tool.name.length + descLen + schemaLen) / 4) + 10;
109
+ }
110
+ var McpPanel = class _McpPanel {
111
+ constructor(config, cache, provenance, callbacks, tui, done, options = {}) {
112
+ this.config = config;
113
+ this.cache = cache;
114
+ this.callbacks = callbacks;
115
+ this.done = done;
116
+ this.tui = tui;
117
+ this.noticeLines = options.noticeLines ?? [];
118
+ this.authOnly = options.authOnly === true;
119
+ this.keys = createPanelKeys(options.keybindings);
120
+ this.prefix = config.settings?.toolPrefix ?? "server";
121
+ for (const [serverName, definition] of Object.entries(config.mcpServers)) {
122
+ if (this.authOnly && !callbacks.canAuthenticate(serverName)) continue;
123
+ const prov = provenance.get(serverName);
124
+ const cachedEntry = this.cache?.servers?.[serverName];
125
+ const serverCache = cachedEntry && isServerCacheValid(cachedEntry, definition) ? cachedEntry : void 0;
126
+ const globalDirect = config.settings?.directTools;
127
+ let toolFilter = false;
128
+ if (definition.directTools !== void 0) {
129
+ toolFilter = definition.directTools;
130
+ } else if (globalDirect) {
131
+ toolFilter = globalDirect;
132
+ }
133
+ const tools = [];
134
+ if (serverCache && !this.authOnly && !isServerDisabled(definition)) {
135
+ for (const tool of serverCache.tools ?? []) {
136
+ if (!isUiToolVisibleToModel(tool.uiVisibility)) continue;
137
+ if (!isToolAllowed(tool.name, serverName, this.prefix, definition.includeTools, definition.excludeTools, this.getOtherCurrentCandidates(serverName, definition, serverCache, tool.name))) {
138
+ continue;
139
+ }
140
+ const isDirect = toolFilter === true || Array.isArray(toolFilter) && toolFilter.includes(tool.name);
141
+ tools.push({
142
+ name: tool.name,
143
+ description: tool.description ?? "",
144
+ isDirect,
145
+ wasDirect: isDirect,
146
+ estimatedTokens: estimateTokens(tool)
147
+ });
148
+ }
149
+ if (definition.exposeResources !== false) {
150
+ for (const resource of serverCache.resources ?? []) {
151
+ const baseName = `read_${resourceNameToToolName(resource.name)}`;
152
+ if (!isToolAllowed(baseName, serverName, this.prefix, definition.includeTools, definition.excludeTools, this.getOtherCurrentCandidates(serverName, definition, serverCache, baseName))) {
153
+ continue;
154
+ }
155
+ const isDirect = toolFilter === true || Array.isArray(toolFilter) && toolFilter.includes(baseName);
156
+ const ct = {
157
+ name: baseName,
158
+ ...resource.description !== void 0 ? { description: resource.description } : {}
159
+ };
160
+ tools.push({
161
+ name: baseName,
162
+ description: resource.description ?? `Read resource: ${resource.uri}`,
163
+ isDirect,
164
+ wasDirect: isDirect,
165
+ estimatedTokens: estimateTokens(ct)
166
+ });
167
+ }
168
+ }
169
+ }
170
+ const status = callbacks.getConnectionStatus(serverName);
171
+ const failureMessage = callbacks.getFailureMessage?.(serverName) ?? null;
172
+ const serverDisabled = isServerDisabled(definition);
173
+ let directCount = 0;
174
+ let directTokens = 0;
175
+ for (const tool of tools) {
176
+ if (!tool.isDirect) continue;
177
+ directCount++;
178
+ directTokens += tool.estimatedTokens;
179
+ }
180
+ this.servers.push({
181
+ name: serverName,
182
+ expanded: false,
183
+ source: prov?.kind ?? "user",
184
+ ...prov?.importKind !== void 0 ? { importKind: prov.importKind } : {},
185
+ ...definition.includeTools !== void 0 ? { includeTools: definition.includeTools } : {},
186
+ ...definition.excludeTools !== void 0 ? { excludeTools: definition.excludeTools } : {},
187
+ exposeResources: definition.exposeResources !== false,
188
+ disabled: serverDisabled,
189
+ wasDisabled: serverDisabled,
190
+ connectionStatus: status,
191
+ failureMessage,
192
+ tools,
193
+ directCount,
194
+ directTokens,
195
+ hasCachedData: !!serverCache
196
+ });
197
+ }
198
+ this.rebuildVisibleItems();
199
+ this.resetInactivityTimeout();
200
+ }
201
+ config;
202
+ cache;
203
+ callbacks;
204
+ done;
205
+ noticeLines;
206
+ prefix;
207
+ servers = [];
208
+ cursorIndex = 0;
209
+ nameQuery = "";
210
+ descSearchActive = false;
211
+ descQuery = "";
212
+ dirty = false;
213
+ confirmingDiscard = false;
214
+ discardSelected = 1;
215
+ importNotice = null;
216
+ authNotice = null;
217
+ authInFlight = null;
218
+ inactivityTimeout = null;
219
+ visibleItems = [];
220
+ tui;
221
+ t = DEFAULT_THEME;
222
+ authOnly;
223
+ keys;
224
+ static MAX_VISIBLE = 12;
225
+ static INACTIVITY_MS = 6e4;
226
+ resetInactivityTimeout() {
227
+ if (this.inactivityTimeout) clearTimeout(this.inactivityTimeout);
228
+ this.inactivityTimeout = setTimeout(() => {
229
+ this.cleanup();
230
+ this.done({ cancelled: true, changes: /* @__PURE__ */ new Map(), disabledChanges: /* @__PURE__ */ new Map() });
231
+ }, _McpPanel.INACTIVITY_MS);
232
+ }
233
+ cleanup() {
234
+ if (this.inactivityTimeout) {
235
+ clearTimeout(this.inactivityTimeout);
236
+ this.inactivityTimeout = null;
237
+ }
238
+ }
239
+ rebuildVisibleItems() {
240
+ const query = this.descSearchActive ? this.descQuery : this.nameQuery;
241
+ const mode = this.descSearchActive ? "desc" : "name";
242
+ this.visibleItems = [];
243
+ for (let si = 0; si < this.servers.length; si++) {
244
+ const server = this.servers[si];
245
+ if (!server) continue;
246
+ if (query && this.authOnly) {
247
+ const score = mode === "name" ? fuzzyScore(query, server.name) : 0;
248
+ if (score > 0) {
249
+ this.visibleItems.push({ type: "server", serverIndex: si });
250
+ }
251
+ continue;
252
+ }
253
+ this.visibleItems.push({ type: "server", serverIndex: si });
254
+ if (server.expanded || query) {
255
+ for (let ti = 0; ti < server.tools.length; ti++) {
256
+ const tool = server.tools[ti];
257
+ if (!tool) continue;
258
+ if (query) {
259
+ const score = mode === "name" ? Math.max(
260
+ fuzzyScore(query, tool.name),
261
+ fuzzyScore(query, server.name) * 0.6
262
+ ) : fuzzyScore(query, tool.description);
263
+ if (score === 0) continue;
264
+ }
265
+ this.visibleItems.push({ type: "tool", serverIndex: si, toolIndex: ti });
266
+ }
267
+ }
268
+ }
269
+ if (query && !this.authOnly) {
270
+ this.visibleItems = this.visibleItems.filter((item) => {
271
+ if (item.type === "server") {
272
+ return this.visibleItems.some(
273
+ (other) => other.type === "tool" && other.serverIndex === item.serverIndex
274
+ );
275
+ }
276
+ return true;
277
+ });
278
+ }
279
+ }
280
+ updateDirty() {
281
+ this.dirty = this.servers.some((s) => s.disabled !== s.wasDisabled || s.tools.some((t) => t.isDirect !== t.wasDirect));
282
+ }
283
+ buildResult() {
284
+ const changes = /* @__PURE__ */ new Map();
285
+ const disabledChanges = /* @__PURE__ */ new Map();
286
+ for (const server of this.servers) {
287
+ if (server.disabled !== server.wasDisabled) {
288
+ disabledChanges.set(server.name, server.disabled);
289
+ }
290
+ const changed = server.tools.some((t) => t.isDirect !== t.wasDirect);
291
+ if (!changed) continue;
292
+ const directTools = server.tools.filter((t) => t.isDirect);
293
+ if (directTools.length === server.tools.length && server.tools.length > 0) {
294
+ changes.set(server.name, true);
295
+ } else if (directTools.length === 0) {
296
+ changes.set(server.name, false);
297
+ } else {
298
+ changes.set(server.name, directTools.map((t) => t.name));
299
+ }
300
+ }
301
+ return { changes, disabledChanges, cancelled: false };
302
+ }
303
+ handleInput(data) {
304
+ this.resetInactivityTimeout();
305
+ this.importNotice = null;
306
+ if (!this.authInFlight) this.authNotice = null;
307
+ if (this.confirmingDiscard) {
308
+ this.handleDiscardInput(data);
309
+ return;
310
+ }
311
+ if (matchesKey(data, "ctrl+c")) {
312
+ this.cleanup();
313
+ this.done({ cancelled: true, changes: /* @__PURE__ */ new Map(), disabledChanges: /* @__PURE__ */ new Map() });
314
+ return;
315
+ }
316
+ if (this.keys.save(data)) {
317
+ this.cleanup();
318
+ this.done(this.buildResult());
319
+ return;
320
+ }
321
+ if (this.descSearchActive) {
322
+ if (matchesKey(data, "escape") || this.keys.selectConfirm(data)) {
323
+ this.descSearchActive = false;
324
+ this.descQuery = "";
325
+ this.rebuildVisibleItems();
326
+ this.cursorIndex = Math.min(this.cursorIndex, Math.max(0, this.visibleItems.length - 1));
327
+ return;
328
+ }
329
+ if (matchesKey(data, "backspace")) {
330
+ if (this.descQuery.length > 0) {
331
+ this.descQuery = this.descQuery.slice(0, -1);
332
+ this.rebuildVisibleItems();
333
+ this.cursorIndex = Math.min(this.cursorIndex, Math.max(0, this.visibleItems.length - 1));
334
+ }
335
+ return;
336
+ }
337
+ if (this.keys.selectUp(data)) {
338
+ this.moveCursor(-1);
339
+ return;
340
+ }
341
+ if (this.keys.selectDown(data)) {
342
+ this.moveCursor(1);
343
+ return;
344
+ }
345
+ if (matchesKey(data, "space")) {
346
+ const item = this.visibleItems[this.cursorIndex];
347
+ if (item) this.toggleItem(item);
348
+ return;
349
+ }
350
+ if (data.length === 1 && data.charCodeAt(0) >= 32) {
351
+ this.descQuery += data;
352
+ this.rebuildVisibleItems();
353
+ this.cursorIndex = Math.min(this.cursorIndex, Math.max(0, this.visibleItems.length - 1));
354
+ return;
355
+ }
356
+ return;
357
+ }
358
+ if (matchesKey(data, "escape")) {
359
+ if (this.nameQuery) {
360
+ this.nameQuery = "";
361
+ this.rebuildVisibleItems();
362
+ this.cursorIndex = Math.min(this.cursorIndex, Math.max(0, this.visibleItems.length - 1));
363
+ return;
364
+ }
365
+ if (this.dirty) {
366
+ this.confirmingDiscard = true;
367
+ this.discardSelected = 1;
368
+ return;
369
+ }
370
+ this.cleanup();
371
+ this.done({ cancelled: true, changes: /* @__PURE__ */ new Map(), disabledChanges: /* @__PURE__ */ new Map() });
372
+ return;
373
+ }
374
+ if (this.keys.selectUp(data)) {
375
+ this.moveCursor(-1);
376
+ return;
377
+ }
378
+ if (this.keys.selectDown(data)) {
379
+ this.moveCursor(1);
380
+ return;
381
+ }
382
+ if (matchesKey(data, "space")) {
383
+ const item = this.visibleItems[this.cursorIndex];
384
+ if (item && !this.authOnly) this.toggleItem(item);
385
+ return;
386
+ }
387
+ if (this.keys.selectConfirm(data)) {
388
+ const item = this.visibleItems[this.cursorIndex];
389
+ if (!item) return;
390
+ const server = this.servers[item.serverIndex];
391
+ if (!server) return;
392
+ if (item.type === "server") {
393
+ if (server.connectionStatus === "disabled") return;
394
+ if (this.authOnly || server.connectionStatus === "needs-auth") {
395
+ this.authenticateServer(server);
396
+ return;
397
+ }
398
+ server.expanded = !server.expanded;
399
+ this.rebuildVisibleItems();
400
+ this.cursorIndex = Math.min(this.cursorIndex, Math.max(0, this.visibleItems.length - 1));
401
+ } else if (item.toolIndex !== void 0) {
402
+ const tool = server.tools[item.toolIndex];
403
+ if (!tool) return;
404
+ this.toggleToolDirect(server, tool);
405
+ if (tool.isDirect && server.source === "import") {
406
+ this.importNotice = `Imported from ${sanitizeDisplayText(server.importKind ?? "external")} \u2014 will copy to user config on save`;
407
+ }
408
+ this.updateDirty();
409
+ }
410
+ return;
411
+ }
412
+ if (matchesKey(data, "ctrl+a")) {
413
+ const item = this.visibleItems[this.cursorIndex];
414
+ if (item) this.authenticateSelectedServer(item);
415
+ return;
416
+ }
417
+ if (matchesKey(data, "ctrl+r")) {
418
+ const item = this.visibleItems[this.cursorIndex];
419
+ if (!item) return;
420
+ const server = this.servers[item.serverIndex];
421
+ if (server) this.reconnectServer(server);
422
+ return;
423
+ }
424
+ if (matchesKey(data, "ctrl+d")) {
425
+ const item = this.visibleItems[this.cursorIndex];
426
+ if (!item || item.type !== "server" || this.authOnly) return;
427
+ const server = this.servers[item.serverIndex];
428
+ if (!server) return;
429
+ server.disabled = !server.disabled;
430
+ this.updateDirty();
431
+ this.tui.requestRender();
432
+ return;
433
+ }
434
+ if (matchesKey(data, "ctrl+y")) {
435
+ const item = this.visibleItems[this.cursorIndex];
436
+ if (!item) return;
437
+ const server = this.servers[item.serverIndex];
438
+ if (!server || server.connectionStatus !== "failed" || !server.failureMessage) return;
439
+ const serverName = sanitizeDisplayText(server.name);
440
+ const failureMessage = sanitizeDisplayText(server.failureMessage);
441
+ copyToClipboard(failureMessage).then(() => {
442
+ this.authNotice = `Copied error for ${serverName} to clipboard`;
443
+ this.tui.requestRender();
444
+ }).catch((error) => {
445
+ const message = sanitizeDisplayText(error instanceof Error ? error.message : String(error));
446
+ this.authNotice = `Failed to copy error for ${serverName}: ${message}`;
447
+ this.tui.requestRender();
448
+ });
449
+ return;
450
+ }
451
+ if (data === "?") {
452
+ if (this.authOnly) return;
453
+ this.descSearchActive = true;
454
+ this.descQuery = "";
455
+ this.rebuildVisibleItems();
456
+ this.cursorIndex = Math.min(this.cursorIndex, Math.max(0, this.visibleItems.length - 1));
457
+ return;
458
+ }
459
+ if (matchesKey(data, "backspace")) {
460
+ if (this.nameQuery.length > 0) {
461
+ this.nameQuery = this.nameQuery.slice(0, -1);
462
+ this.rebuildVisibleItems();
463
+ this.cursorIndex = Math.min(this.cursorIndex, Math.max(0, this.visibleItems.length - 1));
464
+ }
465
+ return;
466
+ }
467
+ if (data.length === 1 && data.charCodeAt(0) >= 32) {
468
+ this.nameQuery += data;
469
+ this.rebuildVisibleItems();
470
+ this.cursorIndex = Math.min(this.cursorIndex, Math.max(0, this.visibleItems.length - 1));
471
+ return;
472
+ }
473
+ }
474
+ authenticateSelectedServer(item) {
475
+ const server = this.servers[item.serverIndex];
476
+ if (server) this.authenticateServer(server);
477
+ }
478
+ authenticateServer(server) {
479
+ if (this.authInFlight) return;
480
+ if (server.connectionStatus === "connecting" || server.connectionStatus === "disabled") return;
481
+ const serverName = sanitizeDisplayText(server.name);
482
+ if (!this.callbacks.canAuthenticate(server.name)) {
483
+ this.authNotice = `${serverName} does not use OAuth authentication.`;
484
+ return;
485
+ }
486
+ this.authInFlight = server.name;
487
+ this.authNotice = `Authenticating ${serverName}...`;
488
+ this.tui.requestRender();
489
+ this.callbacks.authenticate(server.name).then((result) => {
490
+ server.connectionStatus = this.callbacks.getConnectionStatus(server.name);
491
+ if (result.ok) {
492
+ this.authNotice = `OAuth finished for ${serverName}. Reconnecting...`;
493
+ this.authInFlight = null;
494
+ this.tui.requestRender();
495
+ this.reconnectServer(server, { afterAuth: true });
496
+ return;
497
+ }
498
+ const message = sanitizeDisplayText(result.message);
499
+ this.authNotice = `OAuth failed for ${serverName}${message ? `: ${message}` : ". Check the notification for details."}`;
500
+ this.authInFlight = null;
501
+ this.tui.requestRender();
502
+ }).catch((error) => {
503
+ const message = sanitizeDisplayText(error instanceof Error ? error.message : String(error));
504
+ server.connectionStatus = this.callbacks.getConnectionStatus(server.name);
505
+ this.authNotice = `OAuth failed for ${serverName}: ${message}`;
506
+ this.authInFlight = null;
507
+ this.tui.requestRender();
508
+ });
509
+ }
510
+ reconnectServer(server, options = {}) {
511
+ if (server.connectionStatus === "connecting" || server.connectionStatus === "disabled") return;
512
+ const serverName = sanitizeDisplayText(server.name);
513
+ server.connectionStatus = "connecting";
514
+ this.tui.requestRender();
515
+ this.callbacks.reconnect(server.name).then((connected) => {
516
+ server.connectionStatus = this.callbacks.getConnectionStatus(server.name);
517
+ server.failureMessage = this.callbacks.getFailureMessage?.(server.name) ?? null;
518
+ if (server.connectionStatus === "connected") {
519
+ const entry = this.callbacks.refreshCacheAfterReconnect(server.name);
520
+ if (entry) {
521
+ this.cache ??= { version: 1, servers: {} };
522
+ this.cache.servers[server.name] = entry;
523
+ this.rebuildServerTools(server, entry);
524
+ }
525
+ server.hasCachedData = true;
526
+ }
527
+ if (options.afterAuth) {
528
+ this.authNotice = connected && server.connectionStatus === "connected" ? `OAuth finished for ${serverName}. Reconnected.` : `OAuth finished for ${serverName}, but reconnect did not complete. Press ctrl+r to retry.`;
529
+ }
530
+ this.tui.requestRender();
531
+ }).catch((error) => {
532
+ server.connectionStatus = "failed";
533
+ const message = sanitizeDisplayText(error instanceof Error ? error.message : String(error));
534
+ this.authNotice = `Reconnect failed for ${serverName}: ${message}`;
535
+ this.tui.requestRender();
536
+ });
537
+ }
538
+ toggleItem(item) {
539
+ if (this.authOnly) return;
540
+ const server = this.servers[item.serverIndex];
541
+ if (!server) return;
542
+ if (item.type === "server") {
543
+ const newState = !server.tools.every((t) => t.isDirect);
544
+ if (server.source === "import" && newState) {
545
+ this.importNotice = `Imported from ${sanitizeDisplayText(server.importKind ?? "external")} \u2014 will copy to user config on save`;
546
+ }
547
+ let directTokens = 0;
548
+ for (const tool of server.tools) {
549
+ tool.isDirect = newState;
550
+ if (newState) directTokens += tool.estimatedTokens;
551
+ }
552
+ server.directCount = newState ? server.tools.length : 0;
553
+ server.directTokens = directTokens;
554
+ } else if (item.toolIndex !== void 0) {
555
+ const tool = server.tools[item.toolIndex];
556
+ if (!tool) return;
557
+ this.toggleToolDirect(server, tool);
558
+ if (tool.isDirect && server.source === "import") {
559
+ this.importNotice = `Imported from ${sanitizeDisplayText(server.importKind ?? "external")} \u2014 will copy to user config on save`;
560
+ }
561
+ }
562
+ this.updateDirty();
563
+ }
564
+ toggleToolDirect(server, tool) {
565
+ tool.isDirect = !tool.isDirect;
566
+ server.directCount += tool.isDirect ? 1 : -1;
567
+ server.directTokens += tool.isDirect ? tool.estimatedTokens : -tool.estimatedTokens;
568
+ }
569
+ handleDiscardInput(data) {
570
+ if (matchesKey(data, "ctrl+c")) {
571
+ this.cleanup();
572
+ this.done({ cancelled: true, changes: /* @__PURE__ */ new Map(), disabledChanges: /* @__PURE__ */ new Map() });
573
+ return;
574
+ }
575
+ if (matchesKey(data, "escape") || data === "n" || data === "N") {
576
+ this.confirmingDiscard = false;
577
+ return;
578
+ }
579
+ if (this.keys.selectConfirm(data)) {
580
+ this.cleanup();
581
+ if (this.discardSelected === 0) {
582
+ this.done({ cancelled: true, changes: /* @__PURE__ */ new Map(), disabledChanges: /* @__PURE__ */ new Map() });
583
+ } else {
584
+ this.done(this.buildResult());
585
+ }
586
+ return;
587
+ }
588
+ if (data === "y" || data === "Y") {
589
+ this.cleanup();
590
+ this.done({ cancelled: true, changes: /* @__PURE__ */ new Map(), disabledChanges: /* @__PURE__ */ new Map() });
591
+ return;
592
+ }
593
+ if (matchesKey(data, "left") || matchesKey(data, "right") || matchesKey(data, "tab")) {
594
+ this.discardSelected = this.discardSelected === 0 ? 1 : 0;
595
+ }
596
+ }
597
+ moveCursor(delta) {
598
+ if (this.visibleItems.length === 0) return;
599
+ this.cursorIndex = Math.max(0, Math.min(this.visibleItems.length - 1, this.cursorIndex + delta));
600
+ }
601
+ getOtherCurrentCandidates(serverName, definition, currentEntry, toolName) {
602
+ const candidates = /* @__PURE__ */ new Set();
603
+ for (const [otherServerName, otherDefinition] of Object.entries(this.config.mcpServers)) {
604
+ if (isServerDisabled(otherDefinition)) continue;
605
+ const cachedEntry = this.cache?.servers?.[otherServerName];
606
+ const entry = otherServerName === serverName ? currentEntry : cachedEntry && isServerCacheValid(cachedEntry, otherDefinition) ? cachedEntry : void 0;
607
+ if (!entry) continue;
608
+ const otherPrefix = resolveToolPrefix(otherDefinition, this.prefix);
609
+ for (const tool of entry.tools ?? []) {
610
+ if (!isUiToolVisibleToModel(tool.uiVisibility)) continue;
611
+ for (const candidate of getToolNameCandidates(tool.name, otherServerName, otherPrefix, false)) candidates.add(candidate);
612
+ }
613
+ if (otherDefinition.exposeResources !== false) {
614
+ for (const resource of entry.resources ?? []) {
615
+ const baseName = `read_${resourceNameToToolName(resource.name)}`;
616
+ for (const candidate of getToolNameCandidates(baseName, otherServerName, otherPrefix, false)) candidates.add(candidate);
617
+ }
618
+ }
619
+ }
620
+ for (const candidate of getToolNameCandidates(toolName, serverName, resolveToolPrefix(definition, this.prefix), false)) candidates.delete(candidate);
621
+ return candidates;
622
+ }
623
+ rebuildServerTools(server, entry) {
624
+ const existingState = /* @__PURE__ */ new Map();
625
+ for (const t of server.tools) existingState.set(t.name, t.isDirect);
626
+ const newTools = [];
627
+ for (const tool of entry.tools ?? []) {
628
+ if (!isUiToolVisibleToModel(tool.uiVisibility)) continue;
629
+ if (!isToolAllowed(tool.name, server.name, this.prefix, server.includeTools, server.excludeTools, this.getOtherCurrentCandidates(server.name, server, entry, tool.name))) {
630
+ continue;
631
+ }
632
+ const prev = existingState.get(tool.name);
633
+ const isDirect = prev !== void 0 ? prev : false;
634
+ newTools.push({
635
+ name: tool.name,
636
+ description: tool.description ?? "",
637
+ isDirect,
638
+ wasDirect: prev !== void 0 ? server.tools.find((t) => t.name === tool.name)?.wasDirect ?? false : false,
639
+ estimatedTokens: estimateTokens(tool)
640
+ });
641
+ }
642
+ if (server.exposeResources) {
643
+ for (const resource of entry.resources ?? []) {
644
+ const baseName = `read_${resourceNameToToolName(resource.name)}`;
645
+ if (!isToolAllowed(baseName, server.name, this.prefix, server.includeTools, server.excludeTools, this.getOtherCurrentCandidates(server.name, server, entry, baseName))) {
646
+ continue;
647
+ }
648
+ const prev = existingState.get(baseName);
649
+ const isDirect = prev !== void 0 ? prev : false;
650
+ const ct = {
651
+ name: baseName,
652
+ ...resource.description !== void 0 ? { description: resource.description } : {}
653
+ };
654
+ newTools.push({
655
+ name: baseName,
656
+ description: resource.description ?? `Read resource: ${resource.uri}`,
657
+ isDirect,
658
+ wasDirect: prev !== void 0 ? server.tools.find((t) => t.name === baseName)?.wasDirect ?? false : false,
659
+ estimatedTokens: estimateTokens(ct)
660
+ });
661
+ }
662
+ }
663
+ server.tools = newTools;
664
+ server.directCount = 0;
665
+ server.directTokens = 0;
666
+ for (const tool of newTools) {
667
+ if (!tool.isDirect) continue;
668
+ server.directCount++;
669
+ server.directTokens += tool.estimatedTokens;
670
+ }
671
+ this.rebuildVisibleItems();
672
+ this.updateDirty();
673
+ }
674
+ render(width) {
675
+ const innerW = width - 2;
676
+ const lines = [];
677
+ const t = this.t;
678
+ const bold = (s) => `\x1B[1m${s}\x1B[22m`;
679
+ const italic = (s) => `\x1B[3m${s}\x1B[23m`;
680
+ const inverse = (s) => `\x1B[7m${s}\x1B[27m`;
681
+ const row = (content) => fg(t.border, "\u2502") + truncateToWidth(" " + sanitizeRowContent(content), innerW, "\u2026", true) + fg(t.border, "\u2502");
682
+ const emptyRow = () => fg(t.border, "\u2502") + " ".repeat(innerW) + fg(t.border, "\u2502");
683
+ const divider = () => fg(t.border, "\u251C" + "\u2500".repeat(innerW) + "\u2524");
684
+ const titleText = this.authOnly ? " MCP OAuth " : " MCP Servers ";
685
+ const borderLen = innerW - visibleWidth(titleText);
686
+ const leftB = Math.floor(borderLen / 2);
687
+ const rightB = borderLen - leftB;
688
+ lines.push(fg(t.border, "\u256D" + "\u2500".repeat(leftB)) + fg(t.title, titleText) + fg(t.border, "\u2500".repeat(rightB) + "\u256E"));
689
+ lines.push(emptyRow());
690
+ const cursor = fg(t.selected, "\u2502");
691
+ const searchIcon = fg(t.border, "\u25CE");
692
+ if (this.descSearchActive) {
693
+ lines.push(row(`${searchIcon} ${fg(t.needsAuth, "desc:")} ${this.descQuery}${cursor}`));
694
+ } else if (this.nameQuery) {
695
+ lines.push(row(`${searchIcon} ${this.nameQuery}${cursor}`));
696
+ } else {
697
+ lines.push(row(`${searchIcon} ${fg(t.placeholder, italic("search..."))}`));
698
+ }
699
+ lines.push(emptyRow());
700
+ if (this.noticeLines.length > 0) {
701
+ for (const notice of this.noticeLines) {
702
+ lines.push(row(fg(t.hint, italic(sanitizeDisplayText(notice)))));
703
+ }
704
+ lines.push(emptyRow());
705
+ }
706
+ lines.push(divider());
707
+ if (this.servers.length === 0) {
708
+ lines.push(emptyRow());
709
+ lines.push(row(fg(t.hint, italic(this.authOnly ? "No OAuth-capable MCP servers configured." : "No MCP servers configured."))));
710
+ lines.push(emptyRow());
711
+ } else {
712
+ const maxVis = _McpPanel.MAX_VISIBLE;
713
+ const total = this.visibleItems.length;
714
+ const startIdx = Math.max(0, Math.min(this.cursorIndex - Math.floor(maxVis / 2), total - maxVis));
715
+ const endIdx = Math.min(startIdx + maxVis, total);
716
+ lines.push(emptyRow());
717
+ for (let i = startIdx; i < endIdx; i++) {
718
+ const item = this.visibleItems[i];
719
+ if (!item) continue;
720
+ const isCursor = i === this.cursorIndex;
721
+ const server = this.servers[item.serverIndex];
722
+ if (!server) continue;
723
+ if (item.type === "server") {
724
+ lines.push(row(this.renderServerRow(server, isCursor)));
725
+ if (isCursor && server.connectionStatus === "failed" && server.failureMessage) {
726
+ for (const line of this.wrapText(sanitizeDisplayText(server.failureMessage), innerW - 6)) {
727
+ lines.push(row(` ${fg(t.cancel, line)}`));
728
+ }
729
+ }
730
+ } else if (item.toolIndex !== void 0) {
731
+ const tool = server.tools[item.toolIndex];
732
+ if (tool) lines.push(row(this.renderToolRow(tool, isCursor, innerW)));
733
+ }
734
+ }
735
+ lines.push(emptyRow());
736
+ if (total > maxVis) {
737
+ const prog = Math.round((this.cursorIndex + 1) / total * 10);
738
+ lines.push(row(`${rainbowProgress(prog, 10)} ${fg(t.hint, `${this.cursorIndex + 1}/${total}`)}`));
739
+ lines.push(emptyRow());
740
+ }
741
+ if (this.importNotice) {
742
+ lines.push(row(fg(t.needsAuth, italic(sanitizeDisplayText(this.importNotice)))));
743
+ lines.push(emptyRow());
744
+ }
745
+ if (this.authNotice) {
746
+ lines.push(row(fg(t.needsAuth, italic(sanitizeDisplayText(this.authNotice)))));
747
+ lines.push(emptyRow());
748
+ }
749
+ }
750
+ lines.push(divider());
751
+ lines.push(emptyRow());
752
+ if (this.confirmingDiscard) {
753
+ const discardBtn = this.discardSelected === 0 ? inverse(bold(fg(t.cancel, " Discard "))) : fg(t.hint, " Discard ");
754
+ const keepBtn = this.discardSelected === 1 ? inverse(bold(fg(t.confirm, " Keep & Close "))) : fg(t.hint, " Keep & Close ");
755
+ lines.push(row(`Discard unsaved changes? ${discardBtn} ${keepBtn}`));
756
+ } else {
757
+ if (this.authOnly) {
758
+ lines.push(row(fg(t.description, "select a server to authenticate")));
759
+ } else {
760
+ let directCount = 0;
761
+ let directTokens = 0;
762
+ for (const server of this.servers) {
763
+ directCount += server.directCount;
764
+ directTokens += server.directTokens;
765
+ }
766
+ const stats = directCount > 0 ? `${directCount} direct ~${directTokens.toLocaleString()} tokens` : "no direct tools";
767
+ lines.push(row(fg(t.description, stats + (this.dirty ? fg(t.needsAuth, " (unsaved)") : ""))));
768
+ }
769
+ }
770
+ lines.push(emptyRow());
771
+ const saveLabel = this.keys.saveLabel();
772
+ const hints = this.authOnly ? [
773
+ italic("\u2191\u2193") + " navigate",
774
+ italic("\u23CE") + " auth",
775
+ italic("ctrl+a") + " auth",
776
+ italic("esc") + " clear/close",
777
+ italic("ctrl+c") + " quit"
778
+ ] : [
779
+ italic("\u2191\u2193") + " navigate",
780
+ italic("space") + " toggle",
781
+ italic("\u23CE") + " expand/auth",
782
+ italic("ctrl+a") + " auth",
783
+ italic("ctrl+r") + " reconnect",
784
+ italic("ctrl+d") + " disable/enable",
785
+ ...this.selectedServerHasFailureMessage() ? [italic("ctrl+y") + " copy error"] : [],
786
+ italic("?") + " desc search",
787
+ ...saveLabel ? [italic(saveLabel) + " save"] : [],
788
+ italic("esc") + " clear/close",
789
+ italic("ctrl+c") + " quit"
790
+ ];
791
+ const gap = " ";
792
+ const gapW = 2;
793
+ const maxW = innerW - 2;
794
+ let curLine = "";
795
+ let curW = 0;
796
+ for (const hint of hints) {
797
+ const hw = visibleWidth(hint);
798
+ const needed = curW === 0 ? hw : gapW + hw;
799
+ if (curW > 0 && curW + needed > maxW) {
800
+ lines.push(row(fg(t.hint, curLine)));
801
+ curLine = hint;
802
+ curW = hw;
803
+ } else {
804
+ curLine += (curW > 0 ? gap : "") + hint;
805
+ curW += needed;
806
+ }
807
+ }
808
+ if (curLine) lines.push(row(fg(t.hint, curLine)));
809
+ lines.push(fg(t.border, "\u2570" + "\u2500".repeat(innerW) + "\u256F"));
810
+ return lines;
811
+ }
812
+ renderServerRow(server, isCursor) {
813
+ const t = this.t;
814
+ const bold = (s) => `\x1B[1m${s}\x1B[22m`;
815
+ const expandIcon = server.expanded ? "\u25BE" : "\u25B8";
816
+ const prefix = isCursor ? fg(t.selected, expandIcon) : fg(t.border, server.expanded ? expandIcon : "\xB7");
817
+ const serverName = sanitizeDisplayText(server.name);
818
+ const importKind = sanitizeDisplayText(server.importKind ?? "import");
819
+ const nameStr = isCursor ? bold(fg(t.selected, serverName)) : serverName;
820
+ const importLabel = server.source === "import" ? fg(t.description, ` (${importKind})`) : "";
821
+ const statusLabel = this.renderConnectionStatus(server);
822
+ if (!server.hasCachedData && !this.authOnly) {
823
+ return `${prefix} ${nameStr}${importLabel} ${fg(t.description, "(not cached)")}${statusLabel}`;
824
+ }
825
+ const directCount = server.directCount;
826
+ const totalCount = server.tools.length;
827
+ let toggleIcon = fg(t.description, "\u25CB");
828
+ if (directCount === totalCount && totalCount > 0) {
829
+ toggleIcon = fg(t.direct, "\u25CF");
830
+ } else if (directCount > 0) {
831
+ toggleIcon = fg(t.needsAuth, "\u25D0");
832
+ }
833
+ let toolInfo = "";
834
+ if (totalCount > 0) {
835
+ toolInfo = `${directCount}/${totalCount}`;
836
+ if (directCount > 0) {
837
+ toolInfo += ` ~${server.directTokens.toLocaleString()}`;
838
+ }
839
+ toolInfo = fg(t.description, toolInfo);
840
+ }
841
+ return `${prefix} ${toggleIcon} ${nameStr}${importLabel} ${toolInfo}${statusLabel}`;
842
+ }
843
+ selectedServerHasFailureMessage() {
844
+ const item = this.visibleItems[this.cursorIndex];
845
+ if (!item) return false;
846
+ const server = this.servers[item.serverIndex];
847
+ return server?.connectionStatus === "failed" && !!server.failureMessage;
848
+ }
849
+ wrapText(text, width) {
850
+ const max = Math.max(8, width);
851
+ const words = text.split(/\s+/).filter(Boolean);
852
+ const lines = [];
853
+ let current = "";
854
+ const splitLongWord = (word) => {
855
+ let rest = word;
856
+ while (visibleWidth(rest) > max) {
857
+ let take = "";
858
+ let index = 0;
859
+ while (index < rest.length && visibleWidth(take + rest.charAt(index)) <= max) {
860
+ take += rest.charAt(index);
861
+ index++;
862
+ }
863
+ if (!take) take = rest.charAt(0);
864
+ lines.push(take);
865
+ rest = rest.slice(take.length);
866
+ }
867
+ return rest;
868
+ };
869
+ for (const word of words) {
870
+ const candidate = current ? `${current} ${word}` : word;
871
+ if (visibleWidth(candidate) <= max) {
872
+ current = candidate;
873
+ } else {
874
+ if (current) lines.push(current);
875
+ current = splitLongWord(word);
876
+ }
877
+ }
878
+ if (current) lines.push(current);
879
+ return lines.length > 0 ? lines : [text];
880
+ }
881
+ renderConnectionStatus(server) {
882
+ const t = this.t;
883
+ if (this.authInFlight === server.name) return ` ${fg(t.needsAuth, "authenticating")}`;
884
+ if (server.disabled) return ` ${fg(t.description, "disabled")}`;
885
+ if (server.connectionStatus === "needs-auth") return ` ${fg(t.needsAuth, "needs auth")}`;
886
+ if (server.connectionStatus === "connecting") return ` ${fg(t.needsAuth, "connecting")}`;
887
+ if (server.connectionStatus === "failed") return ` ${fg(t.cancel, "failed")}`;
888
+ if (this.authOnly && server.connectionStatus === "connected") return ` ${fg(t.direct, "connected")}`;
889
+ if (this.authOnly) return ` ${fg(t.description, "idle")}`;
890
+ return "";
891
+ }
892
+ renderToolRow(tool, isCursor, innerW) {
893
+ const t = this.t;
894
+ const bold = (s) => `\x1B[1m${s}\x1B[22m`;
895
+ const toggleIcon = tool.isDirect ? fg(t.direct, "\u25CF") : fg(t.description, "\u25CB");
896
+ const cursor = isCursor ? fg(t.selected, "\u25B8") : " ";
897
+ const toolName = sanitizeDisplayText(tool.name);
898
+ const description = sanitizeDisplayText(tool.description);
899
+ const nameStr = isCursor ? bold(fg(t.selected, toolName)) : toolName;
900
+ const prefixLen = 7 + visibleWidth(toolName);
901
+ const maxDescLen = Math.max(0, innerW - prefixLen - 8);
902
+ const descStr = maxDescLen > 5 && description ? fg(t.description, "\u2014 " + truncateToWidth(description, maxDescLen, "\u2026")) : "";
903
+ return ` ${cursor} ${toggleIcon} ${nameStr} ${descStr}`;
904
+ }
905
+ invalidate() {
906
+ }
907
+ dispose() {
908
+ this.cleanup();
909
+ }
910
+ };
911
+ function createMcpPanel(config, cache, provenance, callbacks, tui, done, options) {
912
+ return new McpPanel(config, cache, provenance, callbacks, tui, done, options ?? {});
913
+ }
914
+ export {
915
+ createMcpPanel
916
+ };