@herbertgao/pi-extensions 2026.9.4 → 2026.9.6
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 +3 -15
- package/node_modules/@dietrichgebert/ponytail/.qoder-plugin/plugin.json +1 -1
- package/node_modules/@dietrichgebert/ponytail/README.es.md +33 -2
- package/node_modules/@dietrichgebert/ponytail/README.ko.md +33 -2
- package/node_modules/@dietrichgebert/ponytail/README.md +49 -5
- package/node_modules/@dietrichgebert/ponytail/assets/retriever-icon.png +0 -0
- package/node_modules/@dietrichgebert/ponytail/assets/retriever-logo-dark.svg +4 -0
- package/node_modules/@dietrichgebert/ponytail/assets/retriever-logo-light.svg +4 -0
- package/node_modules/@dietrichgebert/ponytail/hooks/claude-codex-hooks.json +0 -3
- package/node_modules/@dietrichgebert/ponytail/hooks/cursor-hooks.json +17 -0
- package/node_modules/@dietrichgebert/ponytail/hooks/ponytail-activate.js +22 -3
- package/node_modules/@dietrichgebert/ponytail/hooks/ponytail-mode-tracker.js +27 -2
- package/node_modules/@dietrichgebert/ponytail/hooks/ponytail-runtime.js +61 -2
- package/node_modules/@dietrichgebert/ponytail/package.json +2 -1
- package/node_modules/@dietrichgebert/ponytail/scripts/cursor-hooks.js +135 -0
- package/node_modules/@dietrichgebert/ponytail/scripts/uninstall.js +21 -4
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/feature/reference/index.ts +33 -15
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/mouse/interaction.ts +6 -2
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/mouse/scroll.ts +23 -1
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/diff/diff-renderer.ts +69 -14
- package/node_modules/@herbertgao/pi-cc-extensions/package.json +3 -3
- package/node_modules/@herbertgao/sol-pi/docs/compatibility.md +1 -1
- package/node_modules/@herbertgao/sol-pi/package.json +1 -1
- package/node_modules/@herbertgao/sol-pi/src/sol-pi/extensions/action-fusion/index.ts +0 -5
- package/node_modules/@herbertgao/sol-pi/src/sol-pi/extensions/online-context-compact/economics.ts +4 -4
- package/node_modules/@herbertgao/sol-pi/src/sol-pi/extensions/online-context-compact/extension.ts +41 -1
- package/node_modules/@herbertgao/sol-pi/src/sol-pi/extensions/online-context-compact/state.ts +3 -1
- package/node_modules/@juicesharp/rpiv-ask-user-question/ask-user-question.ts +5 -1
- package/node_modules/@juicesharp/rpiv-ask-user-question/events.ts +5 -1
- package/node_modules/@juicesharp/rpiv-ask-user-question/package.json +3 -2
- package/node_modules/@juicesharp/rpiv-ask-user-question/tool/normalize-params.ts +56 -0
- package/node_modules/@juicesharp/rpiv-ask-user-question/view/components/option-list-view.ts +1 -1
- package/node_modules/@juicesharp/rpiv-ask-user-question/view/components/tab-bar.ts +1 -2
- package/node_modules/@pi-plugins/fast-mode/README.md +31 -44
- package/node_modules/@pi-plugins/fast-mode/dist/index.mjs +20914 -30
- package/node_modules/@pi-plugins/fast-mode/dist/index.mjs.map +1 -1
- package/node_modules/@pi-plugins/fast-mode/package.json +5 -7
- package/node_modules/@tifan/pi-copy-response/README.md +2 -2
- package/node_modules/@tifan/pi-copy-response/package.json +3 -2
- package/node_modules/@tifan/pi-inline-skills/README.md +2 -2
- package/node_modules/@tifan/pi-inline-skills/package.json +3 -2
- package/node_modules/@tifan/pi-mermaid-open/README.md +1 -1
- package/node_modules/@tifan/pi-mermaid-open/package.json +3 -2
- package/node_modules/@tifan/pi-preferred-thinking/package.json +3 -2
- package/node_modules/@tifan/pi-recap/package.json +3 -2
- package/node_modules/pi-mcp-adapter/CHANGELOG.md +78 -0
- package/node_modules/pi-mcp-adapter/README.md +125 -19
- package/node_modules/pi-mcp-adapter/agent-plugin-loader.ts +69 -30
- package/node_modules/pi-mcp-adapter/agent-plugin-provenance.ts +36 -0
- package/node_modules/pi-mcp-adapter/claude-plugin-loader.ts +365 -0
- package/node_modules/pi-mcp-adapter/commands.ts +13 -15
- package/node_modules/pi-mcp-adapter/config.ts +198 -30
- package/node_modules/pi-mcp-adapter/consent-manager.ts +40 -9
- package/node_modules/pi-mcp-adapter/direct-tool-surface.ts +250 -0
- package/node_modules/pi-mcp-adapter/direct-tools.ts +9 -228
- package/node_modules/pi-mcp-adapter/dist/agent-plugin-loader.js +74 -32
- package/node_modules/pi-mcp-adapter/dist/agent-plugin-loader.js.map +1 -1
- package/node_modules/pi-mcp-adapter/dist/agent-plugin-provenance.d.ts +1 -0
- package/node_modules/pi-mcp-adapter/dist/agent-plugin-provenance.js +31 -0
- package/node_modules/pi-mcp-adapter/dist/agent-plugin-provenance.js.map +1 -0
- package/node_modules/pi-mcp-adapter/dist/claude-plugin-loader.d.ts +9 -0
- package/node_modules/pi-mcp-adapter/dist/claude-plugin-loader.js +362 -0
- package/node_modules/pi-mcp-adapter/dist/claude-plugin-loader.js.map +1 -0
- package/node_modules/pi-mcp-adapter/dist/config.d.ts +3 -1
- package/node_modules/pi-mcp-adapter/dist/config.js +189 -27
- package/node_modules/pi-mcp-adapter/dist/config.js.map +1 -1
- package/node_modules/pi-mcp-adapter/dist/metadata-cache.js +7 -16
- package/node_modules/pi-mcp-adapter/dist/metadata-cache.js.map +1 -1
- package/node_modules/pi-mcp-adapter/dist/package-mcp-loader.js +5 -17
- package/node_modules/pi-mcp-adapter/dist/package-mcp-loader.js.map +1 -1
- package/node_modules/pi-mcp-adapter/dist/types.d.ts +30 -4
- package/node_modules/pi-mcp-adapter/dist/types.js +26 -0
- package/node_modules/pi-mcp-adapter/dist/types.js.map +1 -1
- package/node_modules/pi-mcp-adapter/dist/utils.d.ts +6 -0
- package/node_modules/pi-mcp-adapter/dist/utils.js +65 -6
- package/node_modules/pi-mcp-adapter/dist/utils.js.map +1 -1
- package/node_modules/pi-mcp-adapter/host-html-template.ts +20 -19
- package/node_modules/pi-mcp-adapter/http-ca.ts +84 -0
- package/node_modules/pi-mcp-adapter/index.ts +827 -164
- package/node_modules/pi-mcp-adapter/init.ts +48 -10
- package/node_modules/pi-mcp-adapter/lazy-loader.ts +7 -0
- package/node_modules/pi-mcp-adapter/mcp-auth-fetch.ts +113 -0
- package/node_modules/pi-mcp-adapter/mcp-auth-flow.ts +256 -77
- package/node_modules/pi-mcp-adapter/mcp-auth.ts +316 -54
- package/node_modules/pi-mcp-adapter/mcp-code.ts +45 -18
- package/node_modules/pi-mcp-adapter/mcp-install.ts +60 -0
- package/node_modules/pi-mcp-adapter/mcp-oauth-provider.ts +70 -6
- package/node_modules/pi-mcp-adapter/mcp-output-guard.ts +53 -42
- package/node_modules/pi-mcp-adapter/mcp-panel-theme.ts +104 -0
- package/node_modules/pi-mcp-adapter/mcp-panel.ts +366 -332
- package/node_modules/pi-mcp-adapter/mcp-references.ts +9 -14
- package/node_modules/pi-mcp-adapter/mcp-script-worker.mjs +3 -1
- package/node_modules/pi-mcp-adapter/mcp-setup-panel.ts +383 -341
- package/node_modules/pi-mcp-adapter/mcp-status.ts +5 -0
- package/node_modules/pi-mcp-adapter/metadata-cache.ts +7 -16
- package/node_modules/pi-mcp-adapter/namespace-tools.ts +61 -3
- package/node_modules/pi-mcp-adapter/oauth.ts +6 -2
- package/node_modules/pi-mcp-adapter/package-mcp-loader.ts +5 -17
- package/node_modules/pi-mcp-adapter/package.json +13 -3
- package/node_modules/pi-mcp-adapter/prompts.ts +35 -3
- package/node_modules/pi-mcp-adapter/proxy-modes.ts +108 -7
- package/node_modules/pi-mcp-adapter/runtime-owner.ts +16 -1
- package/node_modules/pi-mcp-adapter/sampling-handler.ts +9 -32
- package/node_modules/pi-mcp-adapter/sandbox-proxy-template.ts +20 -93
- package/node_modules/pi-mcp-adapter/server-manager.ts +227 -40
- package/node_modules/pi-mcp-adapter/session-approvals.ts +187 -0
- package/node_modules/pi-mcp-adapter/skills/mcp-scripting/SKILL.md +9 -1
- package/node_modules/pi-mcp-adapter/state.ts +10 -1
- package/node_modules/pi-mcp-adapter/tool-approval.ts +10 -22
- package/node_modules/pi-mcp-adapter/tool-metadata.ts +14 -0
- package/node_modules/pi-mcp-adapter/types.ts +57 -4
- package/node_modules/pi-mcp-adapter/ui-server.ts +36 -28
- package/node_modules/pi-mcp-adapter/utils.ts +64 -6
- package/package.json +13 -21
- package/examples/pi-footer.json +0 -275
- package/node_modules/@czottmann/pi-automode/CHANGELOG.md +0 -70
- package/node_modules/@czottmann/pi-automode/LICENSE.md +0 -21
- package/node_modules/@czottmann/pi-automode/README.md +0 -215
- package/node_modules/@czottmann/pi-automode/docs/GLOSSARY.md +0 -39
- package/node_modules/@czottmann/pi-automode/docs/adr/ADR-001-permission-precedence-and-trust-boundaries.md +0 -46
- package/node_modules/@czottmann/pi-automode/docs/adr/ADR-002-global-config-in-extension-data-directory.md +0 -60
- package/node_modules/@czottmann/pi-automode/docs/adr/INDEX.md +0 -6
- package/node_modules/@czottmann/pi-automode/docs/automode-classifier-flow.md +0 -428
- package/node_modules/@czottmann/pi-automode/docs/configuration.md +0 -171
- package/node_modules/@czottmann/pi-automode/docs/defaults.md +0 -178
- package/node_modules/@czottmann/pi-automode/docs/diagnostics.md +0 -90
- package/node_modules/@czottmann/pi-automode/docs/observability-logging.md +0 -160
- package/node_modules/@czottmann/pi-automode/examples/automode.local.json +0 -44
- package/node_modules/@czottmann/pi-automode/extensions/auto-mode/bash.ts +0 -692
- package/node_modules/@czottmann/pi-automode/extensions/auto-mode/classifier.ts +0 -852
- package/node_modules/@czottmann/pi-automode/extensions/auto-mode/config.ts +0 -904
- package/node_modules/@czottmann/pi-automode/extensions/auto-mode/constants.ts +0 -228
- package/node_modules/@czottmann/pi-automode/extensions/auto-mode/extension.ts +0 -954
- package/node_modules/@czottmann/pi-automode/extensions/auto-mode/hard-deny.ts +0 -429
- package/node_modules/@czottmann/pi-automode/extensions/auto-mode/log.ts +0 -173
- package/node_modules/@czottmann/pi-automode/extensions/auto-mode/model-selector.ts +0 -113
- package/node_modules/@czottmann/pi-automode/extensions/auto-mode/model.ts +0 -13
- package/node_modules/@czottmann/pi-automode/extensions/auto-mode/paths.ts +0 -297
- package/node_modules/@czottmann/pi-automode/extensions/auto-mode/permissions.ts +0 -623
- package/node_modules/@czottmann/pi-automode/extensions/auto-mode/state.ts +0 -102
- package/node_modules/@czottmann/pi-automode/extensions/auto-mode/transcript.ts +0 -236
- package/node_modules/@czottmann/pi-automode/extensions/auto-mode/types.ts +0 -205
- package/node_modules/@czottmann/pi-automode/extensions/auto-mode/utils.ts +0 -54
- package/node_modules/@czottmann/pi-automode/extensions/auto-mode.ts +0 -26
- package/node_modules/@czottmann/pi-automode/package.json +0 -61
- package/node_modules/@czottmann/pi-automode/skills/automode-diagnostics/SKILL.md +0 -63
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import { matchesKey, truncateToWidth, visibleWidth } from "@earendil-works/pi-tui";
|
|
2
|
-
import { copyToClipboard } from "@earendil-works/pi-coding-agent";
|
|
1
|
+
import { Container, Text, matchesKey, truncateToWidth, visibleWidth, type Component } from "@earendil-works/pi-tui";
|
|
2
|
+
import { copyToClipboard, type Theme } from "@earendil-works/pi-coding-agent";
|
|
3
3
|
import { createPanelKeys, type PanelKeybindings, type PanelKeys } from "./panel-keys.ts";
|
|
4
|
+
import { createMcpPanelTheme, McpPanelFrame, type McpPanelTheme } from "./mcp-panel-theme.ts";
|
|
4
5
|
import { getToolNameCandidates, isServerDisabled, isToolAllowed, resolveToolPrefix } from "./types.ts";
|
|
5
6
|
import type { McpConfig, McpPanelCallbacks, McpPanelResult, ServerProvenance, ToolPrefix } from "./types.ts";
|
|
6
7
|
import { resourceNameToToolName } from "./resource-tools.ts";
|
|
@@ -8,57 +9,6 @@ import { sanitizeTerminalText, stripOscSequences } from "./utils.ts";
|
|
|
8
9
|
import { isServerCacheValid, type MetadataCache, type ServerCacheEntry, type CachedTool } from "./metadata-cache.ts";
|
|
9
10
|
import { isUiToolVisibleToModel } from "./ui-tool-visibility.ts";
|
|
10
11
|
|
|
11
|
-
interface PanelTheme {
|
|
12
|
-
border: string;
|
|
13
|
-
title: string;
|
|
14
|
-
selected: string;
|
|
15
|
-
direct: string;
|
|
16
|
-
needsAuth: string;
|
|
17
|
-
placeholder: string;
|
|
18
|
-
description: string;
|
|
19
|
-
hint: string;
|
|
20
|
-
confirm: string;
|
|
21
|
-
cancel: string;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
const DEFAULT_THEME: PanelTheme = {
|
|
25
|
-
border: "2",
|
|
26
|
-
title: "2",
|
|
27
|
-
selected: "36",
|
|
28
|
-
direct: "32",
|
|
29
|
-
needsAuth: "33",
|
|
30
|
-
placeholder: "2;3",
|
|
31
|
-
description: "2",
|
|
32
|
-
hint: "2",
|
|
33
|
-
confirm: "32",
|
|
34
|
-
cancel: "31",
|
|
35
|
-
};
|
|
36
|
-
|
|
37
|
-
function fg(code: string, text: string): string {
|
|
38
|
-
if (!code) return text;
|
|
39
|
-
return `\x1b[${code}m${text}\x1b[0m`;
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
const RAINBOW_COLORS = [
|
|
43
|
-
"38;2;178;129;214",
|
|
44
|
-
"38;2;215;135;175",
|
|
45
|
-
"38;2;254;188;56",
|
|
46
|
-
"38;2;228;192;15",
|
|
47
|
-
"38;2;137;210;129",
|
|
48
|
-
"38;2;0;175;175",
|
|
49
|
-
"38;2;23;143;185",
|
|
50
|
-
];
|
|
51
|
-
|
|
52
|
-
function rainbowProgress(filled: number, total: number): string {
|
|
53
|
-
const dots: string[] = [];
|
|
54
|
-
for (let i = 0; i < total; i++) {
|
|
55
|
-
const color = RAINBOW_COLORS[i % RAINBOW_COLORS.length];
|
|
56
|
-
if (!color) continue;
|
|
57
|
-
dots.push(fg(color, i < filled ? "●" : "○"));
|
|
58
|
-
}
|
|
59
|
-
return dots.join(" ");
|
|
60
|
-
}
|
|
61
|
-
|
|
62
12
|
function fuzzyScore(query: string, text: string): number {
|
|
63
13
|
const lq = query.toLowerCase();
|
|
64
14
|
const lt = text.toLowerCase();
|
|
@@ -150,6 +100,337 @@ interface VisibleItem {
|
|
|
150
100
|
toolIndex?: number;
|
|
151
101
|
}
|
|
152
102
|
|
|
103
|
+
interface McpPanelViewState {
|
|
104
|
+
noticeLines: readonly string[];
|
|
105
|
+
servers: readonly ServerState[];
|
|
106
|
+
visibleItems: readonly VisibleItem[];
|
|
107
|
+
cursorIndex: number;
|
|
108
|
+
nameQuery: string;
|
|
109
|
+
descSearchActive: boolean;
|
|
110
|
+
descQuery: string;
|
|
111
|
+
dirty: boolean;
|
|
112
|
+
confirmingDiscard: boolean;
|
|
113
|
+
discardSelected: number;
|
|
114
|
+
importNotice: string | null;
|
|
115
|
+
authNotice: string | null;
|
|
116
|
+
authInFlight: string | null;
|
|
117
|
+
authOnly: boolean;
|
|
118
|
+
saveLabel: string | null;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* The themed MCP view. It owns only component composition and formatting;
|
|
123
|
+
* input routing and callbacks remain on McpPanel.
|
|
124
|
+
*/
|
|
125
|
+
class McpPanelView implements Component {
|
|
126
|
+
private readonly container = new Container();
|
|
127
|
+
|
|
128
|
+
constructor(
|
|
129
|
+
private readonly getState: () => McpPanelViewState,
|
|
130
|
+
private readonly theme: McpPanelTheme,
|
|
131
|
+
) {}
|
|
132
|
+
|
|
133
|
+
render(width: number): string[] {
|
|
134
|
+
const panelWidth = Math.max(0, width);
|
|
135
|
+
const innerWidth = Math.max(0, panelWidth - 2);
|
|
136
|
+
const state = this.getState();
|
|
137
|
+
this.container.clear();
|
|
138
|
+
|
|
139
|
+
const title = state.authOnly ? "MCP OAuth" : "MCP Servers";
|
|
140
|
+
this.container.addChild(new McpPanelFrame(this.theme, "╭", "╮", title));
|
|
141
|
+
this.addRow("", innerWidth);
|
|
142
|
+
|
|
143
|
+
const cursor = this.theme.selected("│");
|
|
144
|
+
const searchIcon = this.theme.border("◎");
|
|
145
|
+
if (state.descSearchActive) {
|
|
146
|
+
this.addRow(`${searchIcon} ${this.theme.needsAuth("desc:")} ${state.descQuery}${cursor}`, innerWidth);
|
|
147
|
+
} else if (state.nameQuery) {
|
|
148
|
+
this.addRow(`${searchIcon} ${state.nameQuery}${cursor}`, innerWidth);
|
|
149
|
+
} else {
|
|
150
|
+
this.addRow(`${searchIcon} ${this.theme.placeholder(this.theme.italic("search..."))}`, innerWidth);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
this.addRow("", innerWidth);
|
|
154
|
+
if (state.noticeLines.length > 0) {
|
|
155
|
+
for (const notice of state.noticeLines) {
|
|
156
|
+
this.addRow(this.theme.hint(this.theme.italic(sanitizeDisplayText(notice))), innerWidth);
|
|
157
|
+
}
|
|
158
|
+
this.addRow("", innerWidth);
|
|
159
|
+
}
|
|
160
|
+
this.addRule("├", "┤");
|
|
161
|
+
|
|
162
|
+
if (state.servers.length === 0) {
|
|
163
|
+
this.addRow("", innerWidth);
|
|
164
|
+
this.addRow(
|
|
165
|
+
this.theme.hint(
|
|
166
|
+
this.theme.italic(
|
|
167
|
+
state.authOnly ? "No OAuth-capable MCP servers configured." : "No MCP servers configured.",
|
|
168
|
+
),
|
|
169
|
+
),
|
|
170
|
+
innerWidth,
|
|
171
|
+
);
|
|
172
|
+
this.addRow("", innerWidth);
|
|
173
|
+
} else {
|
|
174
|
+
const maxVisible = McpPanel.MAX_VISIBLE;
|
|
175
|
+
const total = state.visibleItems.length;
|
|
176
|
+
const startIndex = Math.max(
|
|
177
|
+
0,
|
|
178
|
+
Math.min(state.cursorIndex - Math.floor(maxVisible / 2), total - maxVisible),
|
|
179
|
+
);
|
|
180
|
+
const endIndex = Math.min(startIndex + maxVisible, total);
|
|
181
|
+
|
|
182
|
+
this.addRow("", innerWidth);
|
|
183
|
+
|
|
184
|
+
for (let index = startIndex; index < endIndex; index++) {
|
|
185
|
+
const item = state.visibleItems[index];
|
|
186
|
+
if (!item) continue;
|
|
187
|
+
const isCursor = index === state.cursorIndex;
|
|
188
|
+
const server = state.servers[item.serverIndex];
|
|
189
|
+
if (!server) continue;
|
|
190
|
+
|
|
191
|
+
if (item.type === "server") {
|
|
192
|
+
this.addRow(this.renderServerRow(state, server, isCursor), innerWidth);
|
|
193
|
+
if (isCursor && server.connectionStatus === "failed" && server.failureMessage) {
|
|
194
|
+
for (const line of this.wrapText(sanitizeDisplayText(server.failureMessage), innerWidth - 6)) {
|
|
195
|
+
this.addRow(` ${this.theme.cancel(line)}`, innerWidth);
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
} else if (item.toolIndex !== undefined) {
|
|
199
|
+
const tool = server.tools[item.toolIndex];
|
|
200
|
+
if (tool) this.addRow(this.renderToolRow(tool, isCursor, innerWidth), innerWidth);
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
this.addRow("", innerWidth);
|
|
205
|
+
|
|
206
|
+
if (total > maxVisible) {
|
|
207
|
+
const progress = Math.round(((state.cursorIndex + 1) / total) * 10);
|
|
208
|
+
this.addRow(
|
|
209
|
+
`${this.progressDots(progress, 10)} ${this.theme.hint(`${state.cursorIndex + 1}/${total}`)}`,
|
|
210
|
+
innerWidth,
|
|
211
|
+
);
|
|
212
|
+
this.addRow("", innerWidth);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
if (state.importNotice) {
|
|
216
|
+
this.addRow(this.theme.needsAuth(this.theme.italic(sanitizeDisplayText(state.importNotice))), innerWidth);
|
|
217
|
+
this.addRow("", innerWidth);
|
|
218
|
+
}
|
|
219
|
+
if (state.authNotice) {
|
|
220
|
+
this.addRow(this.theme.needsAuth(this.theme.italic(sanitizeDisplayText(state.authNotice))), innerWidth);
|
|
221
|
+
this.addRow("", innerWidth);
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
this.addRule("├", "┤");
|
|
226
|
+
this.addRow("", innerWidth);
|
|
227
|
+
|
|
228
|
+
if (state.confirmingDiscard) {
|
|
229
|
+
const discardButton = state.discardSelected === 0
|
|
230
|
+
? this.theme.inverse(this.theme.bold(this.theme.cancel(" Discard ")))
|
|
231
|
+
: this.theme.hint(" Discard ");
|
|
232
|
+
const keepButton = state.discardSelected === 1
|
|
233
|
+
? this.theme.inverse(this.theme.bold(this.theme.confirm(" Keep & Close ")))
|
|
234
|
+
: this.theme.hint(" Keep & Close ");
|
|
235
|
+
this.addRow(`Discard unsaved changes? ${discardButton} ${keepButton}`, innerWidth);
|
|
236
|
+
} else if (state.authOnly) {
|
|
237
|
+
this.addRow(this.theme.description("select a server to authenticate"), innerWidth);
|
|
238
|
+
} else {
|
|
239
|
+
let directCount = 0;
|
|
240
|
+
let directTokens = 0;
|
|
241
|
+
for (const server of state.servers) {
|
|
242
|
+
directCount += server.directCount;
|
|
243
|
+
directTokens += server.directTokens;
|
|
244
|
+
}
|
|
245
|
+
const stats = directCount > 0
|
|
246
|
+
? `${directCount} direct ~${directTokens.toLocaleString()} tokens`
|
|
247
|
+
: "no direct tools";
|
|
248
|
+
this.addRow(
|
|
249
|
+
this.theme.description(stats + (state.dirty ? this.theme.needsAuth(" (unsaved)") : "")),
|
|
250
|
+
innerWidth,
|
|
251
|
+
);
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
this.addRow("", innerWidth);
|
|
255
|
+
const hints = state.authOnly
|
|
256
|
+
? [
|
|
257
|
+
this.theme.italic("↑↓") + " navigate",
|
|
258
|
+
this.theme.italic("⏎") + " auth",
|
|
259
|
+
this.theme.italic("ctrl+a") + " auth",
|
|
260
|
+
this.theme.italic("esc") + " clear/close",
|
|
261
|
+
this.theme.italic("ctrl+c") + " quit",
|
|
262
|
+
]
|
|
263
|
+
: [
|
|
264
|
+
this.theme.italic("↑↓") + " navigate",
|
|
265
|
+
this.theme.italic("space") + " toggle",
|
|
266
|
+
this.theme.italic("⏎") + " expand/auth",
|
|
267
|
+
this.theme.italic("ctrl+a") + " auth",
|
|
268
|
+
this.theme.italic("ctrl+r") + " reconnect",
|
|
269
|
+
this.theme.italic("ctrl+d") + " disable/enable",
|
|
270
|
+
...(this.selectedServerHasFailureMessage(state) ? [this.theme.italic("ctrl+y") + " copy error"] : []),
|
|
271
|
+
this.theme.italic("?") + " desc search",
|
|
272
|
+
...(state.saveLabel ? [this.theme.italic(state.saveLabel) + " save"] : []),
|
|
273
|
+
this.theme.italic("esc") + " clear/close",
|
|
274
|
+
this.theme.italic("ctrl+c") + " quit",
|
|
275
|
+
];
|
|
276
|
+
const gap = " ";
|
|
277
|
+
const gapWidth = 2;
|
|
278
|
+
const maxWidth = innerWidth - 2;
|
|
279
|
+
let currentLine = "";
|
|
280
|
+
let currentWidth = 0;
|
|
281
|
+
for (const hint of hints) {
|
|
282
|
+
const hintWidth = visibleWidth(hint);
|
|
283
|
+
const needed = currentWidth === 0 ? hintWidth : gapWidth + hintWidth;
|
|
284
|
+
if (currentWidth > 0 && currentWidth + needed > maxWidth) {
|
|
285
|
+
this.addRow(this.theme.hint(currentLine), innerWidth);
|
|
286
|
+
currentLine = hint;
|
|
287
|
+
currentWidth = hintWidth;
|
|
288
|
+
} else {
|
|
289
|
+
currentLine += (currentWidth > 0 ? gap : "") + hint;
|
|
290
|
+
currentWidth += needed;
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
if (currentLine) this.addRow(this.theme.hint(currentLine), innerWidth);
|
|
294
|
+
|
|
295
|
+
this.addRule("╰", "╯");
|
|
296
|
+
return this.container.render(panelWidth);
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
invalidate(): void {
|
|
300
|
+
this.container.invalidate();
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
private addRow(content: string, innerWidth: number): void {
|
|
304
|
+
this.container.addChild(new Text(this.row(content, innerWidth), 0, 0));
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
private addRule(left: string, right: string): void {
|
|
308
|
+
this.container.addChild(new McpPanelFrame(this.theme, left, right));
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
private row(content: string, innerWidth: number): string {
|
|
312
|
+
const fitted = truncateToWidth(" " + sanitizeRowContent(content), innerWidth, "…", true);
|
|
313
|
+
return this.theme.border("│") + fitted + this.theme.border("│");
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
private progressDots(filled: number, total: number): string {
|
|
317
|
+
const dots: string[] = [];
|
|
318
|
+
for (let index = 0; index < total; index++) {
|
|
319
|
+
if (index < filled) {
|
|
320
|
+
dots.push(this.theme.direct("●"));
|
|
321
|
+
} else {
|
|
322
|
+
dots.push(this.theme.hint("○"));
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
return dots.join(" ");
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
private renderServerRow(state: McpPanelViewState, server: ServerState, isCursor: boolean): string {
|
|
329
|
+
const expandIcon = server.expanded ? "▾" : "▸";
|
|
330
|
+
const prefix = isCursor
|
|
331
|
+
? this.theme.selected(expandIcon)
|
|
332
|
+
: this.theme.border(server.expanded ? expandIcon : "·");
|
|
333
|
+
|
|
334
|
+
const serverName = sanitizeDisplayText(server.name);
|
|
335
|
+
const importKind = sanitizeDisplayText(server.importKind ?? "import");
|
|
336
|
+
const name = isCursor
|
|
337
|
+
? this.theme.bold(this.theme.selected(serverName))
|
|
338
|
+
: serverName;
|
|
339
|
+
const importLabel = server.source === "import" ? this.theme.description(` (${importKind})`) : "";
|
|
340
|
+
const statusLabel = this.renderConnectionStatus(state, server);
|
|
341
|
+
|
|
342
|
+
if (!server.hasCachedData && !state.authOnly) {
|
|
343
|
+
return `${prefix} ${name}${importLabel} ${this.theme.description("(not cached)")}${statusLabel}`;
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
const directCount = server.directCount;
|
|
347
|
+
const totalCount = server.tools.length;
|
|
348
|
+
let toggleIcon = this.theme.description("○");
|
|
349
|
+
if (directCount === totalCount && totalCount > 0) {
|
|
350
|
+
toggleIcon = this.theme.direct("●");
|
|
351
|
+
} else if (directCount > 0) {
|
|
352
|
+
toggleIcon = this.theme.needsAuth("◐");
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
let toolInfo = "";
|
|
356
|
+
if (totalCount > 0) {
|
|
357
|
+
toolInfo = `${directCount}/${totalCount}`;
|
|
358
|
+
if (directCount > 0) toolInfo += ` ~${server.directTokens.toLocaleString()}`;
|
|
359
|
+
toolInfo = this.theme.description(toolInfo);
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
return `${prefix} ${toggleIcon} ${name}${importLabel} ${toolInfo}${statusLabel}`;
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
private selectedServerHasFailureMessage(state: McpPanelViewState): boolean {
|
|
366
|
+
const item = state.visibleItems[state.cursorIndex];
|
|
367
|
+
if (!item) return false;
|
|
368
|
+
const server = state.servers[item.serverIndex];
|
|
369
|
+
return server?.connectionStatus === "failed" && !!server.failureMessage;
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
private wrapText(text: string, width: number): string[] {
|
|
373
|
+
const max = Math.max(8, width);
|
|
374
|
+
const words = text.split(/\s+/).filter(Boolean);
|
|
375
|
+
const lines: string[] = [];
|
|
376
|
+
let current = "";
|
|
377
|
+
const splitLongWord = (word: string): string => {
|
|
378
|
+
let rest = word;
|
|
379
|
+
while (visibleWidth(rest) > max) {
|
|
380
|
+
let take = "";
|
|
381
|
+
let index = 0;
|
|
382
|
+
while (index < rest.length && visibleWidth(take + rest.charAt(index)) <= max) {
|
|
383
|
+
take += rest.charAt(index);
|
|
384
|
+
index++;
|
|
385
|
+
}
|
|
386
|
+
if (!take) take = rest.charAt(0);
|
|
387
|
+
lines.push(take);
|
|
388
|
+
rest = rest.slice(take.length);
|
|
389
|
+
}
|
|
390
|
+
return rest;
|
|
391
|
+
};
|
|
392
|
+
|
|
393
|
+
for (const word of words) {
|
|
394
|
+
const candidate = current ? `${current} ${word}` : word;
|
|
395
|
+
if (visibleWidth(candidate) <= max) {
|
|
396
|
+
current = candidate;
|
|
397
|
+
} else {
|
|
398
|
+
if (current) lines.push(current);
|
|
399
|
+
current = splitLongWord(word);
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
if (current) lines.push(current);
|
|
403
|
+
return lines.length > 0 ? lines : [text];
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
private renderConnectionStatus(state: McpPanelViewState, server: ServerState): string {
|
|
407
|
+
if (state.authInFlight === server.name) return ` ${this.theme.needsAuth("authenticating")}`;
|
|
408
|
+
if (server.disabled) return ` ${this.theme.description("disabled")}`;
|
|
409
|
+
if (server.connectionStatus === "needs-auth") return ` ${this.theme.needsAuth("needs auth")}`;
|
|
410
|
+
if (server.connectionStatus === "connecting") return ` ${this.theme.needsAuth("connecting")}`;
|
|
411
|
+
if (server.connectionStatus === "failed") return ` ${this.theme.cancel("failed")}`;
|
|
412
|
+
if (state.authOnly && server.connectionStatus === "connected") return ` ${this.theme.direct("connected")}`;
|
|
413
|
+
if (state.authOnly) return ` ${this.theme.description("idle")}`;
|
|
414
|
+
return "";
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
private renderToolRow(tool: ToolState, isCursor: boolean, innerWidth: number): string {
|
|
418
|
+
const toggleIcon = tool.isDirect ? this.theme.direct("●") : this.theme.description("○");
|
|
419
|
+
const cursor = isCursor ? this.theme.selected("▸") : " ";
|
|
420
|
+
const toolName = sanitizeDisplayText(tool.name);
|
|
421
|
+
const description = sanitizeDisplayText(tool.description);
|
|
422
|
+
const name = isCursor ? this.theme.bold(this.theme.selected(toolName)) : toolName;
|
|
423
|
+
|
|
424
|
+
const prefixLength = 7 + visibleWidth(toolName);
|
|
425
|
+
const maxDescriptionLength = Math.max(0, innerWidth - prefixLength - 8);
|
|
426
|
+
const descriptionText = maxDescriptionLength > 5 && description
|
|
427
|
+
? this.theme.description(`— ${truncateToWidth(description, maxDescriptionLength, "…")}`)
|
|
428
|
+
: "";
|
|
429
|
+
|
|
430
|
+
return ` ${cursor} ${toggleIcon} ${name} ${descriptionText}`;
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
|
|
153
434
|
class McpPanel {
|
|
154
435
|
private noticeLines: string[];
|
|
155
436
|
private prefix: ToolPrefix;
|
|
@@ -167,11 +448,11 @@ class McpPanel {
|
|
|
167
448
|
private inactivityTimeout: ReturnType<typeof setTimeout> | null = null;
|
|
168
449
|
private visibleItems: VisibleItem[] = [];
|
|
169
450
|
private tui: { requestRender(): void };
|
|
170
|
-
private
|
|
451
|
+
private readonly view: McpPanelView;
|
|
171
452
|
private authOnly: boolean;
|
|
172
453
|
private keys: PanelKeys;
|
|
173
454
|
|
|
174
|
-
|
|
455
|
+
static readonly MAX_VISIBLE = 12;
|
|
175
456
|
private static readonly INACTIVITY_MS = 60_000;
|
|
176
457
|
|
|
177
458
|
constructor(
|
|
@@ -181,12 +462,13 @@ class McpPanel {
|
|
|
181
462
|
private callbacks: McpPanelCallbacks,
|
|
182
463
|
tui: { requestRender(): void },
|
|
183
464
|
private done: (result: McpPanelResult) => void,
|
|
184
|
-
options: { noticeLines?: string[]; authOnly?: boolean; keybindings?: PanelKeybindings } = {},
|
|
465
|
+
options: { noticeLines?: string[]; authOnly?: boolean; keybindings?: PanelKeybindings; theme?: Theme } = {},
|
|
185
466
|
) {
|
|
186
467
|
this.tui = tui;
|
|
187
468
|
this.noticeLines = options.noticeLines ?? [];
|
|
188
469
|
this.authOnly = options.authOnly === true;
|
|
189
470
|
this.keys = createPanelKeys(options.keybindings);
|
|
471
|
+
this.view = new McpPanelView(() => this.getViewState(), createMcpPanelTheme(options.theme));
|
|
190
472
|
this.prefix = config.settings?.toolPrefix ?? "server";
|
|
191
473
|
|
|
192
474
|
for (const [serverName, definition] of Object.entries(config.mcpServers)) {
|
|
@@ -197,10 +479,11 @@ class McpPanel {
|
|
|
197
479
|
|
|
198
480
|
const globalDirect = config.settings?.directTools;
|
|
199
481
|
let toolFilter: true | string[] | false = false;
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
482
|
+
const selected = definition.directTools !== undefined ? definition.directTools : globalDirect;
|
|
483
|
+
if (selected === "search") {
|
|
484
|
+
toolFilter = true; // search-activated tools are still direct tools for the panel
|
|
485
|
+
} else if (selected !== undefined) {
|
|
486
|
+
toolFilter = selected;
|
|
204
487
|
}
|
|
205
488
|
|
|
206
489
|
const tools: ToolState[] = [];
|
|
@@ -599,8 +882,8 @@ class McpPanel {
|
|
|
599
882
|
this.cache ??= { version: 1, servers: {} };
|
|
600
883
|
this.cache.servers[server.name] = entry;
|
|
601
884
|
this.rebuildServerTools(server, entry);
|
|
885
|
+
server.hasCachedData = true;
|
|
602
886
|
}
|
|
603
|
-
server.hasCachedData = true;
|
|
604
887
|
}
|
|
605
888
|
if (options.afterAuth) {
|
|
606
889
|
this.authNotice = connected && server.connectionStatus === "connected"
|
|
@@ -770,283 +1053,34 @@ class McpPanel {
|
|
|
770
1053
|
this.updateDirty();
|
|
771
1054
|
}
|
|
772
1055
|
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
lines.push(fg(t.border, "╭" + "─".repeat(leftB)) + fg(t.title, titleText) + fg(t.border, "─".repeat(rightB) + "╮"));
|
|
791
|
-
|
|
792
|
-
lines.push(emptyRow());
|
|
793
|
-
|
|
794
|
-
const cursor = fg(t.selected, "│");
|
|
795
|
-
const searchIcon = fg(t.border, "◎");
|
|
796
|
-
if (this.descSearchActive) {
|
|
797
|
-
lines.push(row(`${searchIcon} ${fg(t.needsAuth, "desc:")} ${this.descQuery}${cursor}`));
|
|
798
|
-
} else if (this.nameQuery) {
|
|
799
|
-
lines.push(row(`${searchIcon} ${this.nameQuery}${cursor}`));
|
|
800
|
-
} else {
|
|
801
|
-
lines.push(row(`${searchIcon} ${fg(t.placeholder, italic("search..."))}`));
|
|
802
|
-
}
|
|
803
|
-
|
|
804
|
-
lines.push(emptyRow());
|
|
805
|
-
if (this.noticeLines.length > 0) {
|
|
806
|
-
for (const notice of this.noticeLines) {
|
|
807
|
-
lines.push(row(fg(t.hint, italic(sanitizeDisplayText(notice)))));
|
|
808
|
-
}
|
|
809
|
-
lines.push(emptyRow());
|
|
810
|
-
}
|
|
811
|
-
lines.push(divider());
|
|
812
|
-
|
|
813
|
-
if (this.servers.length === 0) {
|
|
814
|
-
lines.push(emptyRow());
|
|
815
|
-
lines.push(row(fg(t.hint, italic(this.authOnly ? "No OAuth-capable MCP servers configured." : "No MCP servers configured."))));
|
|
816
|
-
lines.push(emptyRow());
|
|
817
|
-
} else {
|
|
818
|
-
const maxVis = McpPanel.MAX_VISIBLE;
|
|
819
|
-
const total = this.visibleItems.length;
|
|
820
|
-
const startIdx = Math.max(0, Math.min(this.cursorIndex - Math.floor(maxVis / 2), total - maxVis));
|
|
821
|
-
const endIdx = Math.min(startIdx + maxVis, total);
|
|
822
|
-
|
|
823
|
-
lines.push(emptyRow());
|
|
824
|
-
|
|
825
|
-
for (let i = startIdx; i < endIdx; i++) {
|
|
826
|
-
const item = this.visibleItems[i];
|
|
827
|
-
if (!item) continue;
|
|
828
|
-
const isCursor = i === this.cursorIndex;
|
|
829
|
-
const server = this.servers[item.serverIndex];
|
|
830
|
-
if (!server) continue;
|
|
831
|
-
|
|
832
|
-
if (item.type === "server") {
|
|
833
|
-
lines.push(row(this.renderServerRow(server, isCursor)));
|
|
834
|
-
if (isCursor && server.connectionStatus === "failed" && server.failureMessage) {
|
|
835
|
-
for (const line of this.wrapText(sanitizeDisplayText(server.failureMessage), innerW - 6)) {
|
|
836
|
-
lines.push(row(` ${fg(t.cancel, line)}`));
|
|
837
|
-
}
|
|
838
|
-
}
|
|
839
|
-
} else if (item.toolIndex !== undefined) {
|
|
840
|
-
const tool = server.tools[item.toolIndex];
|
|
841
|
-
if (tool) lines.push(row(this.renderToolRow(tool, isCursor, innerW)));
|
|
842
|
-
}
|
|
843
|
-
}
|
|
844
|
-
|
|
845
|
-
lines.push(emptyRow());
|
|
846
|
-
|
|
847
|
-
if (total > maxVis) {
|
|
848
|
-
const prog = Math.round(((this.cursorIndex + 1) / total) * 10);
|
|
849
|
-
lines.push(row(`${rainbowProgress(prog, 10)} ${fg(t.hint, `${this.cursorIndex + 1}/${total}`)}`));
|
|
850
|
-
lines.push(emptyRow());
|
|
851
|
-
}
|
|
852
|
-
|
|
853
|
-
if (this.importNotice) {
|
|
854
|
-
lines.push(row(fg(t.needsAuth, italic(sanitizeDisplayText(this.importNotice)))));
|
|
855
|
-
lines.push(emptyRow());
|
|
856
|
-
}
|
|
857
|
-
if (this.authNotice) {
|
|
858
|
-
lines.push(row(fg(t.needsAuth, italic(sanitizeDisplayText(this.authNotice)))));
|
|
859
|
-
lines.push(emptyRow());
|
|
860
|
-
}
|
|
861
|
-
}
|
|
862
|
-
|
|
863
|
-
lines.push(divider());
|
|
864
|
-
lines.push(emptyRow());
|
|
865
|
-
|
|
866
|
-
if (this.confirmingDiscard) {
|
|
867
|
-
const discardBtn = this.discardSelected === 0
|
|
868
|
-
? inverse(bold(fg(t.cancel, " Discard ")))
|
|
869
|
-
: fg(t.hint, " Discard ");
|
|
870
|
-
const keepBtn = this.discardSelected === 1
|
|
871
|
-
? inverse(bold(fg(t.confirm, " Keep & Close ")))
|
|
872
|
-
: fg(t.hint, " Keep & Close ");
|
|
873
|
-
lines.push(row(`Discard unsaved changes? ${discardBtn} ${keepBtn}`));
|
|
874
|
-
} else {
|
|
875
|
-
if (this.authOnly) {
|
|
876
|
-
lines.push(row(fg(t.description, "select a server to authenticate")));
|
|
877
|
-
} else {
|
|
878
|
-
let directCount = 0;
|
|
879
|
-
let directTokens = 0;
|
|
880
|
-
for (const server of this.servers) {
|
|
881
|
-
directCount += server.directCount;
|
|
882
|
-
directTokens += server.directTokens;
|
|
883
|
-
}
|
|
884
|
-
const stats =
|
|
885
|
-
directCount > 0 ? `${directCount} direct ~${directTokens.toLocaleString()} tokens` : "no direct tools";
|
|
886
|
-
lines.push(row(fg(t.description, stats + (this.dirty ? fg(t.needsAuth, " (unsaved)") : ""))));
|
|
887
|
-
}
|
|
888
|
-
}
|
|
889
|
-
|
|
890
|
-
lines.push(emptyRow());
|
|
891
|
-
const saveLabel = this.keys.saveLabel();
|
|
892
|
-
const hints = this.authOnly
|
|
893
|
-
? [
|
|
894
|
-
italic("↑↓") + " navigate",
|
|
895
|
-
italic("⏎") + " auth",
|
|
896
|
-
italic("ctrl+a") + " auth",
|
|
897
|
-
italic("esc") + " clear/close",
|
|
898
|
-
italic("ctrl+c") + " quit",
|
|
899
|
-
]
|
|
900
|
-
: [
|
|
901
|
-
italic("↑↓") + " navigate",
|
|
902
|
-
italic("space") + " toggle",
|
|
903
|
-
italic("⏎") + " expand/auth",
|
|
904
|
-
italic("ctrl+a") + " auth",
|
|
905
|
-
italic("ctrl+r") + " reconnect",
|
|
906
|
-
italic("ctrl+d") + " disable/enable",
|
|
907
|
-
...(this.selectedServerHasFailureMessage() ? [italic("ctrl+y") + " copy error"] : []),
|
|
908
|
-
italic("?") + " desc search",
|
|
909
|
-
...(saveLabel ? [italic(saveLabel) + " save"] : []),
|
|
910
|
-
italic("esc") + " clear/close",
|
|
911
|
-
italic("ctrl+c") + " quit",
|
|
912
|
-
];
|
|
913
|
-
const gap = " ";
|
|
914
|
-
const gapW = 2;
|
|
915
|
-
const maxW = innerW - 2;
|
|
916
|
-
let curLine = "";
|
|
917
|
-
let curW = 0;
|
|
918
|
-
for (const hint of hints) {
|
|
919
|
-
const hw = visibleWidth(hint);
|
|
920
|
-
const needed = curW === 0 ? hw : gapW + hw;
|
|
921
|
-
if (curW > 0 && curW + needed > maxW) {
|
|
922
|
-
lines.push(row(fg(t.hint, curLine)));
|
|
923
|
-
curLine = hint;
|
|
924
|
-
curW = hw;
|
|
925
|
-
} else {
|
|
926
|
-
curLine += (curW > 0 ? gap : "") + hint;
|
|
927
|
-
curW += needed;
|
|
928
|
-
}
|
|
929
|
-
}
|
|
930
|
-
if (curLine) lines.push(row(fg(t.hint, curLine)));
|
|
931
|
-
|
|
932
|
-
lines.push(fg(t.border, "╰" + "─".repeat(innerW) + "╯"));
|
|
933
|
-
|
|
934
|
-
return lines;
|
|
935
|
-
}
|
|
936
|
-
|
|
937
|
-
private renderServerRow(server: ServerState, isCursor: boolean): string {
|
|
938
|
-
const t = this.t;
|
|
939
|
-
const bold = (s: string) => `\x1b[1m${s}\x1b[22m`;
|
|
940
|
-
|
|
941
|
-
const expandIcon = server.expanded ? "▾" : "▸";
|
|
942
|
-
const prefix = isCursor ? fg(t.selected, expandIcon) : fg(t.border, server.expanded ? expandIcon : "·");
|
|
943
|
-
|
|
944
|
-
const serverName = sanitizeDisplayText(server.name);
|
|
945
|
-
const importKind = sanitizeDisplayText(server.importKind ?? "import");
|
|
946
|
-
const nameStr = isCursor ? bold(fg(t.selected, serverName)) : serverName;
|
|
947
|
-
const importLabel = server.source === "import" ? fg(t.description, ` (${importKind})`) : "";
|
|
948
|
-
const statusLabel = this.renderConnectionStatus(server);
|
|
949
|
-
|
|
950
|
-
if (!server.hasCachedData && !this.authOnly) {
|
|
951
|
-
return `${prefix} ${nameStr}${importLabel} ${fg(t.description, "(not cached)")}${statusLabel}`;
|
|
952
|
-
}
|
|
953
|
-
|
|
954
|
-
const directCount = server.directCount;
|
|
955
|
-
const totalCount = server.tools.length;
|
|
956
|
-
let toggleIcon = fg(t.description, "○");
|
|
957
|
-
if (directCount === totalCount && totalCount > 0) {
|
|
958
|
-
toggleIcon = fg(t.direct, "●");
|
|
959
|
-
} else if (directCount > 0) {
|
|
960
|
-
toggleIcon = fg(t.needsAuth, "◐");
|
|
961
|
-
}
|
|
962
|
-
|
|
963
|
-
let toolInfo = "";
|
|
964
|
-
if (totalCount > 0) {
|
|
965
|
-
toolInfo = `${directCount}/${totalCount}`;
|
|
966
|
-
if (directCount > 0) {
|
|
967
|
-
toolInfo += ` ~${server.directTokens.toLocaleString()}`;
|
|
968
|
-
}
|
|
969
|
-
toolInfo = fg(t.description, toolInfo);
|
|
970
|
-
}
|
|
971
|
-
|
|
972
|
-
return `${prefix} ${toggleIcon} ${nameStr}${importLabel} ${toolInfo}${statusLabel}`;
|
|
973
|
-
}
|
|
974
|
-
|
|
975
|
-
private selectedServerHasFailureMessage(): boolean {
|
|
976
|
-
const item = this.visibleItems[this.cursorIndex];
|
|
977
|
-
if (!item) return false;
|
|
978
|
-
const server = this.servers[item.serverIndex];
|
|
979
|
-
return server?.connectionStatus === "failed" && !!server.failureMessage;
|
|
980
|
-
}
|
|
981
|
-
|
|
982
|
-
private wrapText(text: string, width: number): string[] {
|
|
983
|
-
const max = Math.max(8, width);
|
|
984
|
-
const words = text.split(/\s+/).filter(Boolean);
|
|
985
|
-
const lines: string[] = [];
|
|
986
|
-
let current = "";
|
|
987
|
-
const splitLongWord = (word: string): string => {
|
|
988
|
-
let rest = word;
|
|
989
|
-
while (visibleWidth(rest) > max) {
|
|
990
|
-
let take = "";
|
|
991
|
-
let index = 0;
|
|
992
|
-
while (index < rest.length && visibleWidth(take + rest.charAt(index)) <= max) {
|
|
993
|
-
take += rest.charAt(index);
|
|
994
|
-
index++;
|
|
995
|
-
}
|
|
996
|
-
if (!take) take = rest.charAt(0);
|
|
997
|
-
lines.push(take);
|
|
998
|
-
rest = rest.slice(take.length);
|
|
999
|
-
}
|
|
1000
|
-
return rest;
|
|
1056
|
+
private getViewState(): McpPanelViewState {
|
|
1057
|
+
return {
|
|
1058
|
+
noticeLines: this.noticeLines,
|
|
1059
|
+
servers: this.servers,
|
|
1060
|
+
visibleItems: this.visibleItems,
|
|
1061
|
+
cursorIndex: this.cursorIndex,
|
|
1062
|
+
nameQuery: this.nameQuery,
|
|
1063
|
+
descSearchActive: this.descSearchActive,
|
|
1064
|
+
descQuery: this.descQuery,
|
|
1065
|
+
dirty: this.dirty,
|
|
1066
|
+
confirmingDiscard: this.confirmingDiscard,
|
|
1067
|
+
discardSelected: this.discardSelected,
|
|
1068
|
+
importNotice: this.importNotice,
|
|
1069
|
+
authNotice: this.authNotice,
|
|
1070
|
+
authInFlight: this.authInFlight,
|
|
1071
|
+
authOnly: this.authOnly,
|
|
1072
|
+
saveLabel: this.keys.saveLabel(),
|
|
1001
1073
|
};
|
|
1002
|
-
|
|
1003
|
-
for (const word of words) {
|
|
1004
|
-
const candidate = current ? `${current} ${word}` : word;
|
|
1005
|
-
if (visibleWidth(candidate) <= max) {
|
|
1006
|
-
current = candidate;
|
|
1007
|
-
} else {
|
|
1008
|
-
if (current) lines.push(current);
|
|
1009
|
-
current = splitLongWord(word);
|
|
1010
|
-
}
|
|
1011
|
-
}
|
|
1012
|
-
if (current) lines.push(current);
|
|
1013
|
-
return lines.length > 0 ? lines : [text];
|
|
1014
1074
|
}
|
|
1015
1075
|
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
if (this.authInFlight === server.name) return ` ${fg(t.needsAuth, "authenticating")}`;
|
|
1019
|
-
if (server.disabled) return ` ${fg(t.description, "disabled")}`;
|
|
1020
|
-
if (server.connectionStatus === "needs-auth") return ` ${fg(t.needsAuth, "needs auth")}`;
|
|
1021
|
-
if (server.connectionStatus === "connecting") return ` ${fg(t.needsAuth, "connecting")}`;
|
|
1022
|
-
if (server.connectionStatus === "failed") return ` ${fg(t.cancel, "failed")}`;
|
|
1023
|
-
if (this.authOnly && server.connectionStatus === "connected") return ` ${fg(t.direct, "connected")}`;
|
|
1024
|
-
if (this.authOnly) return ` ${fg(t.description, "idle")}`;
|
|
1025
|
-
return "";
|
|
1076
|
+
render(width: number): string[] {
|
|
1077
|
+
return this.view.render(width);
|
|
1026
1078
|
}
|
|
1027
1079
|
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
const bold = (s: string) => `\x1b[1m${s}\x1b[22m`;
|
|
1031
|
-
|
|
1032
|
-
const toggleIcon = tool.isDirect ? fg(t.direct, "●") : fg(t.description, "○");
|
|
1033
|
-
const cursor = isCursor ? fg(t.selected, "▸") : " ";
|
|
1034
|
-
const toolName = sanitizeDisplayText(tool.name);
|
|
1035
|
-
const description = sanitizeDisplayText(tool.description);
|
|
1036
|
-
const nameStr = isCursor ? bold(fg(t.selected, toolName)) : toolName;
|
|
1037
|
-
|
|
1038
|
-
const prefixLen = 7 + visibleWidth(toolName);
|
|
1039
|
-
const maxDescLen = Math.max(0, innerW - prefixLen - 8);
|
|
1040
|
-
const descStr =
|
|
1041
|
-
maxDescLen > 5 && description
|
|
1042
|
-
? fg(t.description, "— " + truncateToWidth(description, maxDescLen, "…"))
|
|
1043
|
-
: "";
|
|
1044
|
-
|
|
1045
|
-
return ` ${cursor} ${toggleIcon} ${nameStr} ${descStr}`;
|
|
1080
|
+
invalidate(): void {
|
|
1081
|
+
this.view.invalidate();
|
|
1046
1082
|
}
|
|
1047
1083
|
|
|
1048
|
-
invalidate(): void {}
|
|
1049
|
-
|
|
1050
1084
|
dispose(): void {
|
|
1051
1085
|
this.cleanup();
|
|
1052
1086
|
}
|
|
@@ -1059,7 +1093,7 @@ export function createMcpPanel(
|
|
|
1059
1093
|
callbacks: McpPanelCallbacks,
|
|
1060
1094
|
tui: { requestRender(): void },
|
|
1061
1095
|
done: (result: McpPanelResult) => void,
|
|
1062
|
-
options?: { noticeLines?: string[]; authOnly?: boolean; keybindings?: PanelKeybindings },
|
|
1096
|
+
options?: { noticeLines?: string[]; authOnly?: boolean; keybindings?: PanelKeybindings; theme?: Theme },
|
|
1063
1097
|
): McpPanel & { dispose(): void } {
|
|
1064
1098
|
return new McpPanel(config, cache, provenance, callbacks, tui, done, options ?? {});
|
|
1065
1099
|
}
|