@humain/terminal 0.1.1 → 0.2.1
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/CHANGELOG.md +16 -0
- package/dist/bundle/chunks/{anthropic-messages-XULXUBIG.js → anthropic-messages-QFCEKGI3.js} +1 -1
- package/dist/bundle/chunks/{azure-openai-responses-JEG2XOYC.js → azure-openai-responses-G4ZMHV37.js} +1 -1
- package/dist/bundle/chunks/chunk-7T6XCLJF.js +22 -0
- package/dist/bundle/chunks/chunk-D6HHRSB5.js +103 -0
- package/dist/bundle/chunks/chunk-EY4DWHQU.js +2 -0
- package/dist/bundle/chunks/{chunk-LYAMSPR2.js → chunk-I47ZWR55.js} +483 -506
- package/dist/bundle/chunks/{chunk-JUPSJTFQ.js → chunk-K4JAZYQH.js} +1 -1
- package/dist/bundle/chunks/{chunk-HPPQHK3J.js → chunk-KKIB2TVR.js} +1 -1
- package/dist/bundle/chunks/chunk-LI2YVPWP.js +2 -0
- package/dist/bundle/chunks/{chunk-BZQ53VGE.js → chunk-XSR434CO.js} +1 -1
- package/dist/bundle/chunks/compat-QCV26HNS.js +2 -0
- package/dist/bundle/chunks/dist-PRPIUY3U.js +2 -0
- package/dist/bundle/chunks/esm-E7UVEYTO.js +4 -0
- package/dist/bundle/chunks/{extract-TRW5FBY6.js → extract-CTIFQZDJ.js} +15 -14
- package/dist/bundle/chunks/{google-generative-ai-OX6VSCA6.js → google-generative-ai-RSUU4MWO.js} +1 -1
- package/dist/bundle/chunks/{google-vertex-GW76GSS2.js → google-vertex-CKQPOITO.js} +1 -1
- package/dist/bundle/chunks/host-WP3MVKS2.js +2 -0
- package/dist/bundle/chunks/{node-KOIP55YZ.js → node-NF43OPII.js} +1 -1
- package/dist/bundle/chunks/{node-XY3CBY3C.js → node-UMFM7CDZ.js} +1 -1
- package/dist/bundle/chunks/{openai-codex-responses-NPLWP6ZC.js → openai-codex-responses-S34V5ZSD.js} +1 -1
- package/dist/bundle/chunks/{openai-completions-OT2GTJKC.js → openai-completions-T2IGVUEI.js} +1 -1
- package/dist/bundle/chunks/{openai-responses-5A6CR7GI.js → openai-responses-GYZODCT3.js} +1 -1
- package/dist/bundle/chunks/readability-LAGF34IB.js +3 -0
- package/dist/bundle/chunks/turndown.cjs-HRMZVRUL.js +2 -0
- package/dist/bundle/cli.js +1 -1
- package/dist/bundle/index.js +1 -1
- package/dist/bundle/rpc-entry.js +1 -1
- package/dist/core/tools/file-mutation-queue.d.ts.map +1 -1
- package/dist/core/tools/file-mutation-queue.js +21 -8
- package/dist/core/tools/file-mutation-queue.js.map +1 -1
- package/dist/core/tools/subagent-env.d.ts +18 -0
- package/dist/core/tools/subagent-env.d.ts.map +1 -0
- package/dist/core/tools/subagent-env.js +43 -0
- package/dist/core/tools/subagent-env.js.map +1 -0
- package/dist/core/tools/subagent-scheduler.d.ts +31 -0
- package/dist/core/tools/subagent-scheduler.d.ts.map +1 -0
- package/dist/core/tools/subagent-scheduler.js +203 -0
- package/dist/core/tools/subagent-scheduler.js.map +1 -0
- package/dist/core/tools/subagent.d.ts +83 -3
- package/dist/core/tools/subagent.d.ts.map +1 -1
- package/dist/core/tools/subagent.js +214 -76
- package/dist/core/tools/subagent.js.map +1 -1
- package/dist/humain/mcp-adapter/vendor/CHANGELOG.md +78 -0
- package/dist/humain/mcp-adapter/vendor/README.md +125 -19
- package/dist/humain/mcp-adapter/vendor/agent-plugin-loader.ts +69 -30
- package/dist/humain/mcp-adapter/vendor/agent-plugin-provenance.ts +36 -0
- package/dist/humain/mcp-adapter/vendor/claude-plugin-loader.ts +365 -0
- package/dist/humain/mcp-adapter/vendor/commands.ts +13 -15
- package/dist/humain/mcp-adapter/vendor/config.ts +198 -30
- package/dist/humain/mcp-adapter/vendor/consent-manager.ts +40 -9
- package/dist/humain/mcp-adapter/vendor/direct-tool-surface.ts +250 -0
- package/dist/humain/mcp-adapter/vendor/direct-tools.ts +9 -228
- package/dist/humain/mcp-adapter/vendor/dist/agent-plugin-loader.js +74 -32
- package/dist/humain/mcp-adapter/vendor/dist/agent-plugin-loader.js.map +1 -1
- package/dist/humain/mcp-adapter/vendor/dist/agent-plugin-provenance.d.ts +1 -0
- package/dist/humain/mcp-adapter/vendor/dist/agent-plugin-provenance.js +31 -0
- package/dist/humain/mcp-adapter/vendor/dist/agent-plugin-provenance.js.map +1 -0
- package/dist/humain/mcp-adapter/vendor/dist/claude-plugin-loader.d.ts +9 -0
- package/dist/humain/mcp-adapter/vendor/dist/claude-plugin-loader.js +362 -0
- package/dist/humain/mcp-adapter/vendor/dist/claude-plugin-loader.js.map +1 -0
- package/dist/humain/mcp-adapter/vendor/dist/config.d.ts +3 -1
- package/dist/humain/mcp-adapter/vendor/dist/config.js +189 -27
- package/dist/humain/mcp-adapter/vendor/dist/config.js.map +1 -1
- package/dist/humain/mcp-adapter/vendor/dist/metadata-cache.js +7 -16
- package/dist/humain/mcp-adapter/vendor/dist/metadata-cache.js.map +1 -1
- package/dist/humain/mcp-adapter/vendor/dist/package-mcp-loader.js +5 -17
- package/dist/humain/mcp-adapter/vendor/dist/package-mcp-loader.js.map +1 -1
- package/dist/humain/mcp-adapter/vendor/dist/types.d.ts +30 -4
- package/dist/humain/mcp-adapter/vendor/dist/types.js +26 -0
- package/dist/humain/mcp-adapter/vendor/dist/types.js.map +1 -1
- package/dist/humain/mcp-adapter/vendor/dist/utils.d.ts +6 -0
- package/dist/humain/mcp-adapter/vendor/dist/utils.js +65 -6
- package/dist/humain/mcp-adapter/vendor/dist/utils.js.map +1 -1
- package/dist/humain/mcp-adapter/vendor/host-html-template.ts +20 -19
- package/dist/humain/mcp-adapter/vendor/http-ca.ts +84 -0
- package/dist/humain/mcp-adapter/vendor/index.bundle.mjs +37102 -15103
- package/dist/humain/mcp-adapter/vendor/index.ts +828 -164
- package/dist/humain/mcp-adapter/vendor/init.ts +48 -10
- package/dist/humain/mcp-adapter/vendor/lazy-loader.ts +7 -0
- package/dist/humain/mcp-adapter/vendor/mcp-auth-fetch.ts +113 -0
- package/dist/humain/mcp-adapter/vendor/mcp-auth-flow.ts +256 -77
- package/dist/humain/mcp-adapter/vendor/mcp-auth.ts +316 -54
- package/dist/humain/mcp-adapter/vendor/mcp-code.ts +45 -18
- package/dist/humain/mcp-adapter/vendor/mcp-install.ts +60 -0
- package/dist/humain/mcp-adapter/vendor/mcp-oauth-provider.ts +70 -6
- package/dist/humain/mcp-adapter/vendor/mcp-output-guard.ts +53 -42
- package/dist/humain/mcp-adapter/vendor/mcp-panel-theme.ts +105 -0
- package/dist/humain/mcp-adapter/vendor/mcp-panel.ts +366 -331
- package/dist/humain/mcp-adapter/vendor/mcp-references.ts +9 -14
- package/dist/humain/mcp-adapter/vendor/mcp-script-worker.mjs +3 -1
- package/dist/humain/mcp-adapter/vendor/mcp-setup-panel.ts +383 -341
- package/dist/humain/mcp-adapter/vendor/mcp-status.ts +5 -0
- package/dist/humain/mcp-adapter/vendor/metadata-cache.ts +7 -16
- package/dist/humain/mcp-adapter/vendor/namespace-tools.ts +61 -3
- package/dist/humain/mcp-adapter/vendor/oauth.ts +6 -2
- package/dist/humain/mcp-adapter/vendor/package-mcp-loader.ts +5 -17
- package/dist/humain/mcp-adapter/vendor/package.json +13 -3
- package/dist/humain/mcp-adapter/vendor/prompts.ts +35 -3
- package/dist/humain/mcp-adapter/vendor/proxy-modes.ts +108 -7
- package/dist/humain/mcp-adapter/vendor/runtime-owner.ts +16 -1
- package/dist/humain/mcp-adapter/vendor/sampling-handler.ts +9 -32
- package/dist/humain/mcp-adapter/vendor/sandbox-proxy-template.ts +20 -93
- package/dist/humain/mcp-adapter/vendor/server-manager.ts +227 -40
- package/dist/humain/mcp-adapter/vendor/session-approvals.ts +187 -0
- package/dist/humain/mcp-adapter/vendor/skills/mcp-scripting/SKILL.md +9 -1
- package/dist/humain/mcp-adapter/vendor/state.ts +10 -1
- package/dist/humain/mcp-adapter/vendor/tool-approval.ts +10 -22
- package/dist/humain/mcp-adapter/vendor/tool-metadata.ts +14 -0
- package/dist/humain/mcp-adapter/vendor/types.ts +57 -4
- package/dist/humain/mcp-adapter/vendor/ui-server.ts +36 -28
- package/dist/humain/mcp-adapter/vendor/utils.ts +64 -6
- package/dist/humain/web-access/vendor/abortable.d.ts +3 -0
- package/dist/humain/web-access/vendor/abortable.d.ts.map +1 -0
- package/dist/humain/web-access/vendor/abortable.js +22 -0
- package/dist/humain/web-access/vendor/abortable.js.map +1 -0
- package/dist/humain/web-access/vendor/crawl4ai.d.ts +9 -0
- package/dist/humain/web-access/vendor/crawl4ai.d.ts.map +1 -0
- package/dist/humain/web-access/vendor/crawl4ai.js +187 -0
- package/dist/humain/web-access/vendor/crawl4ai.js.map +1 -0
- package/dist/humain/web-access/vendor/credential-source.d.ts.map +1 -1
- package/dist/humain/web-access/vendor/credential-source.js +1 -0
- package/dist/humain/web-access/vendor/credential-source.js.map +1 -1
- package/dist/humain/web-access/vendor/curator-page.d.ts.map +1 -1
- package/dist/humain/web-access/vendor/curator-page.js +20 -2
- package/dist/humain/web-access/vendor/curator-page.js.map +1 -1
- package/dist/humain/web-access/vendor/curator-run.d.ts +11 -0
- package/dist/humain/web-access/vendor/curator-run.d.ts.map +1 -0
- package/dist/humain/web-access/vendor/curator-run.js +38 -0
- package/dist/humain/web-access/vendor/curator-run.js.map +1 -0
- package/dist/humain/web-access/vendor/curator-server.d.ts +1 -0
- package/dist/humain/web-access/vendor/curator-server.d.ts.map +1 -1
- package/dist/humain/web-access/vendor/curator-server.js +2 -0
- package/dist/humain/web-access/vendor/curator-server.js.map +1 -1
- package/dist/humain/web-access/vendor/duckduckgo.d.ts.map +1 -1
- package/dist/humain/web-access/vendor/duckduckgo.js +1 -1
- package/dist/humain/web-access/vendor/duckduckgo.js.map +1 -1
- package/dist/humain/web-access/vendor/extract.d.ts +1 -1
- package/dist/humain/web-access/vendor/extract.d.ts.map +1 -1
- package/dist/humain/web-access/vendor/extract.js +58 -11
- package/dist/humain/web-access/vendor/extract.js.map +1 -1
- package/dist/humain/web-access/vendor/fetch-params.js +1 -1
- package/dist/humain/web-access/vendor/fetch-params.js.map +1 -1
- package/dist/humain/web-access/vendor/gemini-search.d.ts +2 -2
- package/dist/humain/web-access/vendor/gemini-search.d.ts.map +1 -1
- package/dist/humain/web-access/vendor/gemini-search.js +12 -5
- package/dist/humain/web-access/vendor/gemini-search.js.map +1 -1
- package/dist/humain/web-access/vendor/index.d.ts.map +1 -1
- package/dist/humain/web-access/vendor/index.js +23 -19
- package/dist/humain/web-access/vendor/index.js.map +1 -1
- package/dist/humain/web-access/vendor/openai-search.d.ts.map +1 -1
- package/dist/humain/web-access/vendor/openai-search.js +40 -14
- package/dist/humain/web-access/vendor/openai-search.js.map +1 -1
- package/dist/humain/web-access/vendor/page-query.d.ts.map +1 -1
- package/dist/humain/web-access/vendor/page-query.js +6 -2
- package/dist/humain/web-access/vendor/page-query.js.map +1 -1
- package/dist/humain/web-access/vendor/query-rewrite.d.ts.map +1 -1
- package/dist/humain/web-access/vendor/query-rewrite.js +6 -2
- package/dist/humain/web-access/vendor/query-rewrite.js.map +1 -1
- package/dist/humain/web-access/vendor/serpapi.d.ts +4 -0
- package/dist/humain/web-access/vendor/serpapi.d.ts.map +1 -0
- package/dist/humain/web-access/vendor/serpapi.js +215 -0
- package/dist/humain/web-access/vendor/serpapi.js.map +1 -0
- package/dist/humain/web-access/vendor/ssrf-protection.d.ts.map +1 -1
- package/dist/humain/web-access/vendor/ssrf-protection.js +5 -1
- package/dist/humain/web-access/vendor/ssrf-protection.js.map +1 -1
- package/dist/humain/web-access/vendor/storage.d.ts.map +1 -1
- package/dist/humain/web-access/vendor/storage.js +9 -0
- package/dist/humain/web-access/vendor/storage.js.map +1 -1
- package/dist/humain/web-access/vendor/summary-review.d.ts +1 -1
- package/dist/humain/web-access/vendor/summary-review.d.ts.map +1 -1
- package/dist/humain/web-access/vendor/summary-review.js +35 -24
- package/dist/humain/web-access/vendor/summary-review.js.map +1 -1
- package/dist/humain/web-access/vendor/utils.d.ts.map +1 -1
- package/dist/humain/web-access/vendor/utils.js +5 -3
- package/dist/humain/web-access/vendor/utils.js.map +1 -1
- package/dist/modes/json-event.d.ts +8 -0
- package/dist/modes/json-event.d.ts.map +1 -1
- package/dist/modes/json-event.js +17 -0
- package/dist/modes/json-event.js.map +1 -1
- package/dist/modes/print-mode.d.ts.map +1 -1
- package/dist/modes/print-mode.js +3 -2
- package/dist/modes/print-mode.js.map +1 -1
- package/docs/environment-variables.md +8 -0
- package/docs/json.md +16 -0
- package/npm-shrinkwrap.json +6 -6
- package/package.json +2 -2
- package/src/humain/mcp-adapter/UPSTREAM.md +56 -7
- package/src/humain/mcp-adapter/upstream.json +6 -4
- package/src/humain/mcp-adapter/vendor/CHANGELOG.md +78 -0
- package/src/humain/web-access/UPSTREAM.md +18 -8
- package/src/humain/web-access/upstream.json +6 -5
- package/dist/bundle/chunks/chunk-2LC3S3VV.js +0 -105
|
@@ -1,6 +1,8 @@
|
|
|
1
|
-
import { matchesKey, truncateToWidth, visibleWidth } from "@earendil-works/pi-tui";
|
|
1
|
+
import { Container, Text, matchesKey, truncateToWidth, visibleWidth, type Component } from "@earendil-works/pi-tui";
|
|
2
|
+
import { type Theme } from "@earendil-works/pi-coding-agent";
|
|
2
3
|
import { copyToClipboard } from "../../../utils/clipboard.ts";
|
|
3
4
|
import { createPanelKeys, type PanelKeybindings, type PanelKeys } from "./panel-keys.ts";
|
|
5
|
+
import { createMcpPanelTheme, McpPanelFrame, type McpPanelTheme } from "./mcp-panel-theme.ts";
|
|
4
6
|
import { getToolNameCandidates, isServerDisabled, isToolAllowed, resolveToolPrefix } from "./types.ts";
|
|
5
7
|
import type { McpConfig, McpPanelCallbacks, McpPanelResult, ServerProvenance, ToolPrefix } from "./types.ts";
|
|
6
8
|
import { resourceNameToToolName } from "./resource-tools.ts";
|
|
@@ -8,57 +10,6 @@ import { sanitizeTerminalText, stripOscSequences } from "./utils.ts";
|
|
|
8
10
|
import { isServerCacheValid, type MetadataCache, type ServerCacheEntry, type CachedTool } from "./metadata-cache.ts";
|
|
9
11
|
import { isUiToolVisibleToModel } from "./ui-tool-visibility.ts";
|
|
10
12
|
|
|
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
13
|
function fuzzyScore(query: string, text: string): number {
|
|
63
14
|
const lq = query.toLowerCase();
|
|
64
15
|
const lt = text.toLowerCase();
|
|
@@ -150,6 +101,337 @@ interface VisibleItem {
|
|
|
150
101
|
toolIndex?: number;
|
|
151
102
|
}
|
|
152
103
|
|
|
104
|
+
interface McpPanelViewState {
|
|
105
|
+
noticeLines: readonly string[];
|
|
106
|
+
servers: readonly ServerState[];
|
|
107
|
+
visibleItems: readonly VisibleItem[];
|
|
108
|
+
cursorIndex: number;
|
|
109
|
+
nameQuery: string;
|
|
110
|
+
descSearchActive: boolean;
|
|
111
|
+
descQuery: string;
|
|
112
|
+
dirty: boolean;
|
|
113
|
+
confirmingDiscard: boolean;
|
|
114
|
+
discardSelected: number;
|
|
115
|
+
importNotice: string | null;
|
|
116
|
+
authNotice: string | null;
|
|
117
|
+
authInFlight: string | null;
|
|
118
|
+
authOnly: boolean;
|
|
119
|
+
saveLabel: string | null;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* The themed MCP view. It owns only component composition and formatting;
|
|
124
|
+
* input routing and callbacks remain on McpPanel.
|
|
125
|
+
*/
|
|
126
|
+
class McpPanelView implements Component {
|
|
127
|
+
private readonly container = new Container();
|
|
128
|
+
|
|
129
|
+
constructor(
|
|
130
|
+
private readonly getState: () => McpPanelViewState,
|
|
131
|
+
private readonly theme: McpPanelTheme,
|
|
132
|
+
) {}
|
|
133
|
+
|
|
134
|
+
render(width: number): string[] {
|
|
135
|
+
const panelWidth = Math.max(0, width);
|
|
136
|
+
const innerWidth = Math.max(0, panelWidth - 2);
|
|
137
|
+
const state = this.getState();
|
|
138
|
+
this.container.clear();
|
|
139
|
+
|
|
140
|
+
const title = state.authOnly ? "MCP OAuth" : "MCP Servers";
|
|
141
|
+
this.container.addChild(new McpPanelFrame(this.theme, "╭", "╮", title));
|
|
142
|
+
this.addRow("", innerWidth);
|
|
143
|
+
|
|
144
|
+
const cursor = this.theme.selected("│");
|
|
145
|
+
const searchIcon = this.theme.border("◎");
|
|
146
|
+
if (state.descSearchActive) {
|
|
147
|
+
this.addRow(`${searchIcon} ${this.theme.needsAuth("desc:")} ${state.descQuery}${cursor}`, innerWidth);
|
|
148
|
+
} else if (state.nameQuery) {
|
|
149
|
+
this.addRow(`${searchIcon} ${state.nameQuery}${cursor}`, innerWidth);
|
|
150
|
+
} else {
|
|
151
|
+
this.addRow(`${searchIcon} ${this.theme.placeholder(this.theme.italic("search..."))}`, innerWidth);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
this.addRow("", innerWidth);
|
|
155
|
+
if (state.noticeLines.length > 0) {
|
|
156
|
+
for (const notice of state.noticeLines) {
|
|
157
|
+
this.addRow(this.theme.hint(this.theme.italic(sanitizeDisplayText(notice))), innerWidth);
|
|
158
|
+
}
|
|
159
|
+
this.addRow("", innerWidth);
|
|
160
|
+
}
|
|
161
|
+
this.addRule("├", "┤");
|
|
162
|
+
|
|
163
|
+
if (state.servers.length === 0) {
|
|
164
|
+
this.addRow("", innerWidth);
|
|
165
|
+
this.addRow(
|
|
166
|
+
this.theme.hint(
|
|
167
|
+
this.theme.italic(
|
|
168
|
+
state.authOnly ? "No OAuth-capable MCP servers configured." : "No MCP servers configured.",
|
|
169
|
+
),
|
|
170
|
+
),
|
|
171
|
+
innerWidth,
|
|
172
|
+
);
|
|
173
|
+
this.addRow("", innerWidth);
|
|
174
|
+
} else {
|
|
175
|
+
const maxVisible = McpPanel.MAX_VISIBLE;
|
|
176
|
+
const total = state.visibleItems.length;
|
|
177
|
+
const startIndex = Math.max(
|
|
178
|
+
0,
|
|
179
|
+
Math.min(state.cursorIndex - Math.floor(maxVisible / 2), total - maxVisible),
|
|
180
|
+
);
|
|
181
|
+
const endIndex = Math.min(startIndex + maxVisible, total);
|
|
182
|
+
|
|
183
|
+
this.addRow("", innerWidth);
|
|
184
|
+
|
|
185
|
+
for (let index = startIndex; index < endIndex; index++) {
|
|
186
|
+
const item = state.visibleItems[index];
|
|
187
|
+
if (!item) continue;
|
|
188
|
+
const isCursor = index === state.cursorIndex;
|
|
189
|
+
const server = state.servers[item.serverIndex];
|
|
190
|
+
if (!server) continue;
|
|
191
|
+
|
|
192
|
+
if (item.type === "server") {
|
|
193
|
+
this.addRow(this.renderServerRow(state, server, isCursor), innerWidth);
|
|
194
|
+
if (isCursor && server.connectionStatus === "failed" && server.failureMessage) {
|
|
195
|
+
for (const line of this.wrapText(sanitizeDisplayText(server.failureMessage), innerWidth - 6)) {
|
|
196
|
+
this.addRow(` ${this.theme.cancel(line)}`, innerWidth);
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
} else if (item.toolIndex !== undefined) {
|
|
200
|
+
const tool = server.tools[item.toolIndex];
|
|
201
|
+
if (tool) this.addRow(this.renderToolRow(tool, isCursor, innerWidth), innerWidth);
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
this.addRow("", innerWidth);
|
|
206
|
+
|
|
207
|
+
if (total > maxVisible) {
|
|
208
|
+
const progress = Math.round(((state.cursorIndex + 1) / total) * 10);
|
|
209
|
+
this.addRow(
|
|
210
|
+
`${this.progressDots(progress, 10)} ${this.theme.hint(`${state.cursorIndex + 1}/${total}`)}`,
|
|
211
|
+
innerWidth,
|
|
212
|
+
);
|
|
213
|
+
this.addRow("", innerWidth);
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
if (state.importNotice) {
|
|
217
|
+
this.addRow(this.theme.needsAuth(this.theme.italic(sanitizeDisplayText(state.importNotice))), innerWidth);
|
|
218
|
+
this.addRow("", innerWidth);
|
|
219
|
+
}
|
|
220
|
+
if (state.authNotice) {
|
|
221
|
+
this.addRow(this.theme.needsAuth(this.theme.italic(sanitizeDisplayText(state.authNotice))), innerWidth);
|
|
222
|
+
this.addRow("", innerWidth);
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
this.addRule("├", "┤");
|
|
227
|
+
this.addRow("", innerWidth);
|
|
228
|
+
|
|
229
|
+
if (state.confirmingDiscard) {
|
|
230
|
+
const discardButton = state.discardSelected === 0
|
|
231
|
+
? this.theme.inverse(this.theme.bold(this.theme.cancel(" Discard ")))
|
|
232
|
+
: this.theme.hint(" Discard ");
|
|
233
|
+
const keepButton = state.discardSelected === 1
|
|
234
|
+
? this.theme.inverse(this.theme.bold(this.theme.confirm(" Keep & Close ")))
|
|
235
|
+
: this.theme.hint(" Keep & Close ");
|
|
236
|
+
this.addRow(`Discard unsaved changes? ${discardButton} ${keepButton}`, innerWidth);
|
|
237
|
+
} else if (state.authOnly) {
|
|
238
|
+
this.addRow(this.theme.description("select a server to authenticate"), innerWidth);
|
|
239
|
+
} else {
|
|
240
|
+
let directCount = 0;
|
|
241
|
+
let directTokens = 0;
|
|
242
|
+
for (const server of state.servers) {
|
|
243
|
+
directCount += server.directCount;
|
|
244
|
+
directTokens += server.directTokens;
|
|
245
|
+
}
|
|
246
|
+
const stats = directCount > 0
|
|
247
|
+
? `${directCount} direct ~${directTokens.toLocaleString()} tokens`
|
|
248
|
+
: "no direct tools";
|
|
249
|
+
this.addRow(
|
|
250
|
+
this.theme.description(stats + (state.dirty ? this.theme.needsAuth(" (unsaved)") : "")),
|
|
251
|
+
innerWidth,
|
|
252
|
+
);
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
this.addRow("", innerWidth);
|
|
256
|
+
const hints = state.authOnly
|
|
257
|
+
? [
|
|
258
|
+
this.theme.italic("↑↓") + " navigate",
|
|
259
|
+
this.theme.italic("⏎") + " auth",
|
|
260
|
+
this.theme.italic("ctrl+a") + " auth",
|
|
261
|
+
this.theme.italic("esc") + " clear/close",
|
|
262
|
+
this.theme.italic("ctrl+c") + " quit",
|
|
263
|
+
]
|
|
264
|
+
: [
|
|
265
|
+
this.theme.italic("↑↓") + " navigate",
|
|
266
|
+
this.theme.italic("space") + " toggle",
|
|
267
|
+
this.theme.italic("⏎") + " expand/auth",
|
|
268
|
+
this.theme.italic("ctrl+a") + " auth",
|
|
269
|
+
this.theme.italic("ctrl+r") + " reconnect",
|
|
270
|
+
this.theme.italic("ctrl+d") + " disable/enable",
|
|
271
|
+
...(this.selectedServerHasFailureMessage(state) ? [this.theme.italic("ctrl+y") + " copy error"] : []),
|
|
272
|
+
this.theme.italic("?") + " desc search",
|
|
273
|
+
...(state.saveLabel ? [this.theme.italic(state.saveLabel) + " save"] : []),
|
|
274
|
+
this.theme.italic("esc") + " clear/close",
|
|
275
|
+
this.theme.italic("ctrl+c") + " quit",
|
|
276
|
+
];
|
|
277
|
+
const gap = " ";
|
|
278
|
+
const gapWidth = 2;
|
|
279
|
+
const maxWidth = innerWidth - 2;
|
|
280
|
+
let currentLine = "";
|
|
281
|
+
let currentWidth = 0;
|
|
282
|
+
for (const hint of hints) {
|
|
283
|
+
const hintWidth = visibleWidth(hint);
|
|
284
|
+
const needed = currentWidth === 0 ? hintWidth : gapWidth + hintWidth;
|
|
285
|
+
if (currentWidth > 0 && currentWidth + needed > maxWidth) {
|
|
286
|
+
this.addRow(this.theme.hint(currentLine), innerWidth);
|
|
287
|
+
currentLine = hint;
|
|
288
|
+
currentWidth = hintWidth;
|
|
289
|
+
} else {
|
|
290
|
+
currentLine += (currentWidth > 0 ? gap : "") + hint;
|
|
291
|
+
currentWidth += needed;
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
if (currentLine) this.addRow(this.theme.hint(currentLine), innerWidth);
|
|
295
|
+
|
|
296
|
+
this.addRule("╰", "╯");
|
|
297
|
+
return this.container.render(panelWidth);
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
invalidate(): void {
|
|
301
|
+
this.container.invalidate();
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
private addRow(content: string, innerWidth: number): void {
|
|
305
|
+
this.container.addChild(new Text(this.row(content, innerWidth), 0, 0));
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
private addRule(left: string, right: string): void {
|
|
309
|
+
this.container.addChild(new McpPanelFrame(this.theme, left, right));
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
private row(content: string, innerWidth: number): string {
|
|
313
|
+
const fitted = truncateToWidth(" " + sanitizeRowContent(content), innerWidth, "…", true);
|
|
314
|
+
return this.theme.border("│") + fitted + this.theme.border("│");
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
private progressDots(filled: number, total: number): string {
|
|
318
|
+
const dots: string[] = [];
|
|
319
|
+
for (let index = 0; index < total; index++) {
|
|
320
|
+
if (index < filled) {
|
|
321
|
+
dots.push(this.theme.direct("●"));
|
|
322
|
+
} else {
|
|
323
|
+
dots.push(this.theme.hint("○"));
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
return dots.join(" ");
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
private renderServerRow(state: McpPanelViewState, server: ServerState, isCursor: boolean): string {
|
|
330
|
+
const expandIcon = server.expanded ? "▾" : "▸";
|
|
331
|
+
const prefix = isCursor
|
|
332
|
+
? this.theme.selected(expandIcon)
|
|
333
|
+
: this.theme.border(server.expanded ? expandIcon : "·");
|
|
334
|
+
|
|
335
|
+
const serverName = sanitizeDisplayText(server.name);
|
|
336
|
+
const importKind = sanitizeDisplayText(server.importKind ?? "import");
|
|
337
|
+
const name = isCursor
|
|
338
|
+
? this.theme.bold(this.theme.selected(serverName))
|
|
339
|
+
: serverName;
|
|
340
|
+
const importLabel = server.source === "import" ? this.theme.description(` (${importKind})`) : "";
|
|
341
|
+
const statusLabel = this.renderConnectionStatus(state, server);
|
|
342
|
+
|
|
343
|
+
if (!server.hasCachedData && !state.authOnly) {
|
|
344
|
+
return `${prefix} ${name}${importLabel} ${this.theme.description("(not cached)")}${statusLabel}`;
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
const directCount = server.directCount;
|
|
348
|
+
const totalCount = server.tools.length;
|
|
349
|
+
let toggleIcon = this.theme.description("○");
|
|
350
|
+
if (directCount === totalCount && totalCount > 0) {
|
|
351
|
+
toggleIcon = this.theme.direct("●");
|
|
352
|
+
} else if (directCount > 0) {
|
|
353
|
+
toggleIcon = this.theme.needsAuth("◐");
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
let toolInfo = "";
|
|
357
|
+
if (totalCount > 0) {
|
|
358
|
+
toolInfo = `${directCount}/${totalCount}`;
|
|
359
|
+
if (directCount > 0) toolInfo += ` ~${server.directTokens.toLocaleString()}`;
|
|
360
|
+
toolInfo = this.theme.description(toolInfo);
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
return `${prefix} ${toggleIcon} ${name}${importLabel} ${toolInfo}${statusLabel}`;
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
private selectedServerHasFailureMessage(state: McpPanelViewState): boolean {
|
|
367
|
+
const item = state.visibleItems[state.cursorIndex];
|
|
368
|
+
if (!item) return false;
|
|
369
|
+
const server = state.servers[item.serverIndex];
|
|
370
|
+
return server?.connectionStatus === "failed" && !!server.failureMessage;
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
private wrapText(text: string, width: number): string[] {
|
|
374
|
+
const max = Math.max(8, width);
|
|
375
|
+
const words = text.split(/\s+/).filter(Boolean);
|
|
376
|
+
const lines: string[] = [];
|
|
377
|
+
let current = "";
|
|
378
|
+
const splitLongWord = (word: string): string => {
|
|
379
|
+
let rest = word;
|
|
380
|
+
while (visibleWidth(rest) > max) {
|
|
381
|
+
let take = "";
|
|
382
|
+
let index = 0;
|
|
383
|
+
while (index < rest.length && visibleWidth(take + rest.charAt(index)) <= max) {
|
|
384
|
+
take += rest.charAt(index);
|
|
385
|
+
index++;
|
|
386
|
+
}
|
|
387
|
+
if (!take) take = rest.charAt(0);
|
|
388
|
+
lines.push(take);
|
|
389
|
+
rest = rest.slice(take.length);
|
|
390
|
+
}
|
|
391
|
+
return rest;
|
|
392
|
+
};
|
|
393
|
+
|
|
394
|
+
for (const word of words) {
|
|
395
|
+
const candidate = current ? `${current} ${word}` : word;
|
|
396
|
+
if (visibleWidth(candidate) <= max) {
|
|
397
|
+
current = candidate;
|
|
398
|
+
} else {
|
|
399
|
+
if (current) lines.push(current);
|
|
400
|
+
current = splitLongWord(word);
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
if (current) lines.push(current);
|
|
404
|
+
return lines.length > 0 ? lines : [text];
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
private renderConnectionStatus(state: McpPanelViewState, server: ServerState): string {
|
|
408
|
+
if (state.authInFlight === server.name) return ` ${this.theme.needsAuth("authenticating")}`;
|
|
409
|
+
if (server.disabled) return ` ${this.theme.description("disabled")}`;
|
|
410
|
+
if (server.connectionStatus === "needs-auth") return ` ${this.theme.needsAuth("needs auth")}`;
|
|
411
|
+
if (server.connectionStatus === "connecting") return ` ${this.theme.needsAuth("connecting")}`;
|
|
412
|
+
if (server.connectionStatus === "failed") return ` ${this.theme.cancel("failed")}`;
|
|
413
|
+
if (state.authOnly && server.connectionStatus === "connected") return ` ${this.theme.direct("connected")}`;
|
|
414
|
+
if (state.authOnly) return ` ${this.theme.description("idle")}`;
|
|
415
|
+
return "";
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
private renderToolRow(tool: ToolState, isCursor: boolean, innerWidth: number): string {
|
|
419
|
+
const toggleIcon = tool.isDirect ? this.theme.direct("●") : this.theme.description("○");
|
|
420
|
+
const cursor = isCursor ? this.theme.selected("▸") : " ";
|
|
421
|
+
const toolName = sanitizeDisplayText(tool.name);
|
|
422
|
+
const description = sanitizeDisplayText(tool.description);
|
|
423
|
+
const name = isCursor ? this.theme.bold(this.theme.selected(toolName)) : toolName;
|
|
424
|
+
|
|
425
|
+
const prefixLength = 7 + visibleWidth(toolName);
|
|
426
|
+
const maxDescriptionLength = Math.max(0, innerWidth - prefixLength - 8);
|
|
427
|
+
const descriptionText = maxDescriptionLength > 5 && description
|
|
428
|
+
? this.theme.description(`— ${truncateToWidth(description, maxDescriptionLength, "…")}`)
|
|
429
|
+
: "";
|
|
430
|
+
|
|
431
|
+
return ` ${cursor} ${toggleIcon} ${name} ${descriptionText}`;
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
|
|
153
435
|
class McpPanel {
|
|
154
436
|
private noticeLines: string[];
|
|
155
437
|
private prefix: ToolPrefix;
|
|
@@ -167,11 +449,11 @@ class McpPanel {
|
|
|
167
449
|
private inactivityTimeout: ReturnType<typeof setTimeout> | null = null;
|
|
168
450
|
private visibleItems: VisibleItem[] = [];
|
|
169
451
|
private tui: { requestRender(): void };
|
|
170
|
-
private
|
|
452
|
+
private readonly view: McpPanelView;
|
|
171
453
|
private authOnly: boolean;
|
|
172
454
|
private keys: PanelKeys;
|
|
173
455
|
|
|
174
|
-
|
|
456
|
+
static readonly MAX_VISIBLE = 12;
|
|
175
457
|
private static readonly INACTIVITY_MS = 60_000;
|
|
176
458
|
|
|
177
459
|
constructor(
|
|
@@ -181,12 +463,13 @@ class McpPanel {
|
|
|
181
463
|
private callbacks: McpPanelCallbacks,
|
|
182
464
|
tui: { requestRender(): void },
|
|
183
465
|
private done: (result: McpPanelResult) => void,
|
|
184
|
-
options: { noticeLines?: string[]; authOnly?: boolean; keybindings?: PanelKeybindings } = {},
|
|
466
|
+
options: { noticeLines?: string[]; authOnly?: boolean; keybindings?: PanelKeybindings; theme?: Theme } = {},
|
|
185
467
|
) {
|
|
186
468
|
this.tui = tui;
|
|
187
469
|
this.noticeLines = options.noticeLines ?? [];
|
|
188
470
|
this.authOnly = options.authOnly === true;
|
|
189
471
|
this.keys = createPanelKeys(options.keybindings);
|
|
472
|
+
this.view = new McpPanelView(() => this.getViewState(), createMcpPanelTheme(options.theme));
|
|
190
473
|
this.prefix = config.settings?.toolPrefix ?? "server";
|
|
191
474
|
|
|
192
475
|
for (const [serverName, definition] of Object.entries(config.mcpServers)) {
|
|
@@ -197,10 +480,11 @@ class McpPanel {
|
|
|
197
480
|
|
|
198
481
|
const globalDirect = config.settings?.directTools;
|
|
199
482
|
let toolFilter: true | string[] | false = false;
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
483
|
+
const selected = definition.directTools !== undefined ? definition.directTools : globalDirect;
|
|
484
|
+
if (selected === "search") {
|
|
485
|
+
toolFilter = true; // search-activated tools are still direct tools for the panel
|
|
486
|
+
} else if (selected !== undefined) {
|
|
487
|
+
toolFilter = selected;
|
|
204
488
|
}
|
|
205
489
|
|
|
206
490
|
const tools: ToolState[] = [];
|
|
@@ -599,8 +883,8 @@ class McpPanel {
|
|
|
599
883
|
this.cache ??= { version: 1, servers: {} };
|
|
600
884
|
this.cache.servers[server.name] = entry;
|
|
601
885
|
this.rebuildServerTools(server, entry);
|
|
886
|
+
server.hasCachedData = true;
|
|
602
887
|
}
|
|
603
|
-
server.hasCachedData = true;
|
|
604
888
|
}
|
|
605
889
|
if (options.afterAuth) {
|
|
606
890
|
this.authNotice = connected && server.connectionStatus === "connected"
|
|
@@ -770,283 +1054,34 @@ class McpPanel {
|
|
|
770
1054
|
this.updateDirty();
|
|
771
1055
|
}
|
|
772
1056
|
|
|
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;
|
|
1057
|
+
private getViewState(): McpPanelViewState {
|
|
1058
|
+
return {
|
|
1059
|
+
noticeLines: this.noticeLines,
|
|
1060
|
+
servers: this.servers,
|
|
1061
|
+
visibleItems: this.visibleItems,
|
|
1062
|
+
cursorIndex: this.cursorIndex,
|
|
1063
|
+
nameQuery: this.nameQuery,
|
|
1064
|
+
descSearchActive: this.descSearchActive,
|
|
1065
|
+
descQuery: this.descQuery,
|
|
1066
|
+
dirty: this.dirty,
|
|
1067
|
+
confirmingDiscard: this.confirmingDiscard,
|
|
1068
|
+
discardSelected: this.discardSelected,
|
|
1069
|
+
importNotice: this.importNotice,
|
|
1070
|
+
authNotice: this.authNotice,
|
|
1071
|
+
authInFlight: this.authInFlight,
|
|
1072
|
+
authOnly: this.authOnly,
|
|
1073
|
+
saveLabel: this.keys.saveLabel(),
|
|
1001
1074
|
};
|
|
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
1075
|
}
|
|
1015
1076
|
|
|
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 "";
|
|
1077
|
+
render(width: number): string[] {
|
|
1078
|
+
return this.view.render(width);
|
|
1026
1079
|
}
|
|
1027
1080
|
|
|
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}`;
|
|
1081
|
+
invalidate(): void {
|
|
1082
|
+
this.view.invalidate();
|
|
1046
1083
|
}
|
|
1047
1084
|
|
|
1048
|
-
invalidate(): void {}
|
|
1049
|
-
|
|
1050
1085
|
dispose(): void {
|
|
1051
1086
|
this.cleanup();
|
|
1052
1087
|
}
|
|
@@ -1059,7 +1094,7 @@ export function createMcpPanel(
|
|
|
1059
1094
|
callbacks: McpPanelCallbacks,
|
|
1060
1095
|
tui: { requestRender(): void },
|
|
1061
1096
|
done: (result: McpPanelResult) => void,
|
|
1062
|
-
options?: { noticeLines?: string[]; authOnly?: boolean; keybindings?: PanelKeybindings },
|
|
1097
|
+
options?: { noticeLines?: string[]; authOnly?: boolean; keybindings?: PanelKeybindings; theme?: Theme },
|
|
1063
1098
|
): McpPanel & { dispose(): void } {
|
|
1064
1099
|
return new McpPanel(config, cache, provenance, callbacks, tui, done, options ?? {});
|
|
1065
1100
|
}
|