zidane 5.6.7 → 5.6.8
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/dist/{agent-D70rr6Uk.d.ts → agent-CZEvtmJk.d.ts} +45 -2
- package/dist/agent-CZEvtmJk.d.ts.map +1 -0
- package/dist/chat.d.ts +261 -5
- package/dist/chat.d.ts.map +1 -1
- package/dist/chat.js +3 -3
- package/dist/{index-BjwwNjQd.d.ts → index-BBH6XWFt.d.ts} +2 -2
- package/dist/{index-BjwwNjQd.d.ts.map → index-BBH6XWFt.d.ts.map} +1 -1
- package/dist/{index-8mn3PIaa.d.ts → index-Cf-131kQ.d.ts} +2 -2
- package/dist/index-Cf-131kQ.d.ts.map +1 -0
- package/dist/index.d.ts +3 -3
- package/dist/index.js +7 -7
- package/dist/{login-Btpliwct.js → login-CwLWX6vP.js} +17 -37
- package/dist/login-CwLWX6vP.js.map +1 -0
- package/dist/{mcp-ngMS0S6N.js → mcp-Wzf0qxaj.js} +120 -26
- package/dist/mcp-Wzf0qxaj.js.map +1 -0
- package/dist/mcp.d.ts +1 -1
- package/dist/mcp.js +1 -1
- package/dist/{messages-B5k4DAXy.js → messages-BfmXLDT4.js} +56 -2
- package/dist/messages-BfmXLDT4.js.map +1 -0
- package/dist/{presets-BXmWG3kd.js → presets-DAA0NaK_.js} +2 -2
- package/dist/{presets-BXmWG3kd.js.map → presets-DAA0NaK_.js.map} +1 -1
- package/dist/presets.d.ts +2 -2
- package/dist/presets.js +1 -1
- package/dist/{providers-CaJE2ToS.js → providers-C_ahnRBS.js} +2 -2
- package/dist/{providers-CaJE2ToS.js.map → providers-C_ahnRBS.js.map} +1 -1
- package/dist/providers.d.ts +1 -1
- package/dist/providers.js +2 -2
- package/dist/restate.d.ts +1 -1
- package/dist/session/sqlite.d.ts +1 -1
- package/dist/{session-BoEW_wCR.js → session-PUzXZlG6.js} +2 -2
- package/dist/{session-BoEW_wCR.js.map → session-PUzXZlG6.js.map} +1 -1
- package/dist/session.d.ts +1 -1
- package/dist/session.js +2 -2
- package/dist/skills.d.ts +2 -2
- package/dist/{tools-FerA0zSl.js → tools-B9aQpZVx.js} +6 -4
- package/dist/tools-B9aQpZVx.js.map +1 -0
- package/dist/tools.d.ts +2 -2
- package/dist/tools.js +1 -1
- package/dist/{transcript-anchors-C5Sp1Snh.d.ts → transcript-anchors-CoSZb1PE.d.ts} +42 -4
- package/dist/transcript-anchors-CoSZb1PE.d.ts.map +1 -0
- package/dist/tui.d.ts +47 -14
- package/dist/tui.d.ts.map +1 -1
- package/dist/tui.js +703 -148
- package/dist/tui.js.map +1 -1
- package/dist/{turn-operations-D-OQYUgS.js → turn-operations-D2rHrTQv.js} +375 -14
- package/dist/turn-operations-D2rHrTQv.js.map +1 -0
- package/dist/types.d.ts +2 -2
- package/docs/CHAT.md +4 -1
- package/docs/SKILL.md +1 -0
- package/docs/TUI.md +1 -0
- package/package.json +1 -1
- package/dist/agent-D70rr6Uk.d.ts.map +0 -1
- package/dist/index-8mn3PIaa.d.ts.map +0 -1
- package/dist/login-Btpliwct.js.map +0 -1
- package/dist/mcp-ngMS0S6N.js.map +0 -1
- package/dist/messages-B5k4DAXy.js.map +0 -1
- package/dist/tools-FerA0zSl.js.map +0 -1
- package/dist/transcript-anchors-C5Sp1Snh.d.ts.map +0 -1
- package/dist/turn-operations-D-OQYUgS.js.map +0 -1
package/dist/tui.js
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { A as resolvePersistDir, B as formatTaskStatus, H as previewLine, I as ageString, L as compactPath, O as cleanupPersistedSession, R as fmtTokens, U as shortId, V as formatTaskSummary, j as resolveTasksDir, p as createAgent, z as formatDuration } from "./tools-
|
|
1
|
+
import { $n as ensureKeybindingsFile, A as getSafelist, At as clipHintsToWidth, B as oauthUsesManualCodePaste, Bi as buildPlanSystem, Bt as SETTINGS_CATEGORIES, Cn as stripSpawnTokensLine, Cr as bootTick, D as useSafeModeQueue, Dn as toolResultText, Dt as useInteractionsActions, E as useSafeModeActions, En as toolCallPreview, F as suggestSafelistEntry, Fn as extractEditPayload, Fr as shouldAutoCompact, Ft as buildHints, G as indexOfEntry, Gn as resolveApprovalForPayload, Gt as useSettings, H as supportsOAuth, Hr as setProviderCredential, Ht as SETTINGS_TOGGLES, In as filetypeFromPath, Ir as detectAuth, J as discoverProjectMcps, Jn as summarizeOutcomes, Jt as resolveChipColor, K as buildMcpServers, Kn as rewriteMultiEditHeader, L as splitPromptSegments, Ln as previewEditPayload, Lt as listProjectFiles, Mn as buildUnifiedDiff, Mt as truncateTrailing, Oi as useActiveTodos, On as turnSelectionOwnership, Ot as useInteractionsQueue, Pr as AUTO_COMPACT_MIN_GROWTH_FRACTION, Pt as generateSessionTitle, Q as loadMcpToolsCache, Qn as KEYBINDING_KEY_COL_WIDTH, Qr as modelSupportsReasoning, R as formatPathForCwd, Rt as useEnabledToggleSet, Sn as selectableTurnIds, St as createInteractionTools, T as SafeModeProvider, Tr as useUpdateCheck, Tt as pendingInteractionsFromTurns, U as buildModelCatalog, Un as mergeApprovalAndBodyOutcomes, Ut as SettingsProvider, V as runOAuthLogin, Vi as envSection, Vn as buildEditOutcomesAnnotation, Vt as SETTINGS_CHOICES, W as filterModelCatalog, Wn as parseEditOutcomesFromResult, Wt as clampFps, Xn as KEYBINDING_DEFS, Xr as getContextWindow, Yt as resolveTheme, _ as turnContextSize, _n as lastContextSizeFromTurns, _t as splitMarkdownCodeBlocks, a as computeTurnAnchors, ai as findGitRoot, an as ConfigProvider, b as defaultSkillScanPaths, bn as marginTopFor, br as buildLinearRamp, c as formatToolCall, ct as parentServerName, d as useSelectStyle, er as formatBindingForDisplay, et as refreshMcpToolsCatalog, f as useSurfaces, fi as accentColor, fn as deriveSessionTitle, fr as createFilesCompletionProvider, ft as McpAuthProvider, g as finalizeStreamingMarkdownForOwner, gn as isVisible, h as finalizeStreamingMarkdown, hn as isTurnHighlighted, ht as getMcpAuthStatus, i as turnAsText, in as useDiscoveryOptional, it as useMcpToolToggleMap, j as isOnSafelist, jn as buildContextualDiff, jt as hintsLength, k as addToSafelist, kn as updateToolEventOutcomes, kt as EMPTY_HINTS, l as ThemeProvider, lr as createSkillsCompletionProvider, lt as createFileMcpCredentialStore, m as useTheme, mn as isEditErrorResult, mt as useMcpAuthState, n as deleteTurnSafely, ni as piIdOf, nn as DiscoveryProvider, nr as keybindingsPath, nt as subscribeMcpToolsCache, o as TOOL_DISPLAY, on as useConfig, ot as buildVisibleMcpRows, pn as eventsFromTurns, pt as useMcpAuthDispatch, qn as stripEditOutcomesAnnotation, r as truncateTurnsAt, ri as discoverAgentsMd, rn as useDiscovery, rr as matchesBinding, s as displayNameFor, sn as resolveConfig, st as indexOfServerRow, tn as createDiscoverySlot, tr as groupBindings, u as useColors, un as EDIT_TOOL_NAMES, ur as uniqueSkillNamesFromReferences, v as useStreamBuffer, vi as TODO_STATUS_GLYPHS, vn as listSessionMeta, vr as useCompletion, w as writeSessionExport, wn as sumRunCosts, wr as buildUpdateHint, wt as makeRequestInteraction, x as discoverProjectSkills, xr as tryOpenBrowser, xt as buildResumedToolResultsTurn, y as buildSkillsConfig, yr as blendHsl, yt as InteractionsProvider, z as fetchOAuthRedirect, zi as buildBuildSystem, zn as summarizeEditPayload, zt as DEFAULT_SETTINGS } from "./turn-operations-D2rHrTQv.js";
|
|
2
|
+
import { A as resolvePersistDir, B as formatTaskStatus, H as previewLine, I as ageString, L as compactPath, O as cleanupPersistedSession, R as fmtTokens, U as shortId, V as formatTaskSummary, j as resolveTasksDir, p as createAgent, z as formatDuration } from "./tools-B9aQpZVx.js";
|
|
3
3
|
import { n as createProcessContext } from "./contexts-BOtMvzli.js";
|
|
4
4
|
import { c as errorMessage } from "./errors-DdZXnyXE.js";
|
|
5
|
-
import { s as McpOAuthProvider, t as connectMcpServers } from "./mcp-
|
|
6
|
-
import { C as summaryToTurn, a as selectFilesFromSession, r as buildPostCompactAttachments, s as compactConversation, t as loginMcpServer } from "./login-
|
|
5
|
+
import { s as McpOAuthProvider, t as connectMcpServers } from "./mcp-Wzf0qxaj.js";
|
|
6
|
+
import { C as summaryToTurn, a as selectFilesFromSession, r as buildPostCompactAttachments, s as compactConversation, t as loginMcpServer } from "./login-CwLWX6vP.js";
|
|
7
7
|
import { n as formatTokenUsage } from "./stats-Lc3zL3RM.js";
|
|
8
|
-
import { n as loadSession, t as createSession } from "./session-
|
|
8
|
+
import { n as loadSession, t as createSession } from "./session-PUzXZlG6.js";
|
|
9
9
|
import { createTuiStore } from "./session/sqlite.js";
|
|
10
10
|
import { basename, join, relative } from "node:path";
|
|
11
11
|
import { homedir } from "node:os";
|
|
@@ -1967,11 +1967,40 @@ function makeMarkdownRenderNode(bag) {
|
|
|
1967
1967
|
wrapper.marginTop = topMargin;
|
|
1968
1968
|
return wrapper;
|
|
1969
1969
|
}
|
|
1970
|
+
if (inner instanceof CodeRenderable) pinContentTrim(inner);
|
|
1970
1971
|
inner.marginTop = topMargin;
|
|
1971
1972
|
return inner;
|
|
1972
1973
|
};
|
|
1973
1974
|
}
|
|
1974
1975
|
/**
|
|
1976
|
+
* Install an instance-level `content` accessor on a prose
|
|
1977
|
+
* {@link CodeRenderable} that strips trailing newlines before
|
|
1978
|
+
* delegating to the prototype setter. Runs once per block on
|
|
1979
|
+
* creation; subsequent markdown delta passes call
|
|
1980
|
+
* `applyMarkdownCodeRenderable` which writes `token.raw` directly into
|
|
1981
|
+
* `.content` and re-enters our setter, so updates stay trimmed too.
|
|
1982
|
+
*
|
|
1983
|
+
* Idempotent — re-applies cleanly if a block is recreated on a
|
|
1984
|
+
* type-change (which destroys + re-creates the renderable anyway).
|
|
1985
|
+
*/
|
|
1986
|
+
function pinContentTrim(renderable) {
|
|
1987
|
+
const desc = Object.getOwnPropertyDescriptor(Object.getPrototypeOf(renderable), "content");
|
|
1988
|
+
if (!desc?.set || !desc.get) return;
|
|
1989
|
+
const protoSet = desc.set;
|
|
1990
|
+
const protoGet = desc.get;
|
|
1991
|
+
Object.defineProperty(renderable, "content", {
|
|
1992
|
+
get() {
|
|
1993
|
+
return protoGet.call(this);
|
|
1994
|
+
},
|
|
1995
|
+
set(value) {
|
|
1996
|
+
const next = typeof value === "string" ? value.replace(/\n+$/, "") : value;
|
|
1997
|
+
protoSet.call(this, next);
|
|
1998
|
+
},
|
|
1999
|
+
configurable: true
|
|
2000
|
+
});
|
|
2001
|
+
renderable.content = renderable.content;
|
|
2002
|
+
}
|
|
2003
|
+
/**
|
|
1975
2004
|
* Pull the trailing block index out of an OpenTUI renderable id. Returns
|
|
1976
2005
|
* `0` for any id that doesn't match the expected `…-block-N` shape — the
|
|
1977
2006
|
* spacing rule then treats unparseable ids as "first block" (no
|
|
@@ -2428,6 +2457,7 @@ function ToolCallBlock({ event, display, dim }) {
|
|
|
2428
2457
|
});
|
|
2429
2458
|
return /* @__PURE__ */ jsxs("text", {
|
|
2430
2459
|
fg: dim ? COLOR.dim : COLOR.model,
|
|
2460
|
+
wrapMode: "none",
|
|
2431
2461
|
children: [
|
|
2432
2462
|
/* @__PURE__ */ jsx("span", {
|
|
2433
2463
|
fg: COLOR.mute,
|
|
@@ -2807,6 +2837,7 @@ function CwdPickerModal({ currentCwd, onPick }) {
|
|
|
2807
2837
|
*/
|
|
2808
2838
|
const FILES_REFRESH_THROTTLE_MS = 3e3;
|
|
2809
2839
|
const SKILLS_REFRESH_THROTTLE_MS = 3e4;
|
|
2840
|
+
const MCP_TOOLS_CACHE_POLL_MS = 4e3;
|
|
2810
2841
|
function debugLog$1(...args) {
|
|
2811
2842
|
if (process.env.ZIDANE_DEBUG) process.stderr.write(`[zidane/tui] ${args.map(errorMessage).join(" ")}\n`);
|
|
2812
2843
|
}
|
|
@@ -2822,6 +2853,7 @@ function DiscoveryShell({ children }) {
|
|
|
2822
2853
|
const [mcpsCatalog, setMcpsCatalog] = useState([]);
|
|
2823
2854
|
const [mcpsErrors, setMcpsErrors] = useState([]);
|
|
2824
2855
|
const [filesCatalog, setFilesCatalog] = useState([]);
|
|
2856
|
+
const [mcpToolsByServer, setMcpToolsByServer] = useState(() => readToolsByServer(dataDir));
|
|
2825
2857
|
const filesSlotRef = useRef(null);
|
|
2826
2858
|
const skillsSlotRef = useRef(null);
|
|
2827
2859
|
useEffect(() => {
|
|
@@ -2923,11 +2955,22 @@ function DiscoveryShell({ children }) {
|
|
|
2923
2955
|
config.prefix,
|
|
2924
2956
|
mcpCredentialStore
|
|
2925
2957
|
]);
|
|
2958
|
+
useEffect(() => {
|
|
2959
|
+
const id = setInterval(() => {
|
|
2960
|
+
try {
|
|
2961
|
+
setMcpToolsByServer(readToolsByServer(dataDir));
|
|
2962
|
+
} catch (err) {
|
|
2963
|
+
debugLog$1("mcp-tools-cache refresh failed", err);
|
|
2964
|
+
}
|
|
2965
|
+
}, MCP_TOOLS_CACHE_POLL_MS);
|
|
2966
|
+
return () => clearInterval(id);
|
|
2967
|
+
}, [dataDir]);
|
|
2926
2968
|
return /* @__PURE__ */ jsx(DiscoveryProvider, {
|
|
2927
2969
|
value: useMemo(() => ({
|
|
2928
2970
|
skillsCatalog,
|
|
2929
2971
|
mcpsCatalog,
|
|
2930
2972
|
mcpsErrors,
|
|
2973
|
+
mcpToolsByServer,
|
|
2931
2974
|
filesCatalog,
|
|
2932
2975
|
refreshSkills,
|
|
2933
2976
|
refreshMcps,
|
|
@@ -2938,6 +2981,7 @@ function DiscoveryShell({ children }) {
|
|
|
2938
2981
|
skillsCatalog,
|
|
2939
2982
|
mcpsCatalog,
|
|
2940
2983
|
mcpsErrors,
|
|
2984
|
+
mcpToolsByServer,
|
|
2941
2985
|
filesCatalog,
|
|
2942
2986
|
refreshSkills,
|
|
2943
2987
|
refreshMcps,
|
|
@@ -2948,6 +2992,19 @@ function DiscoveryShell({ children }) {
|
|
|
2948
2992
|
children
|
|
2949
2993
|
});
|
|
2950
2994
|
}
|
|
2995
|
+
/**
|
|
2996
|
+
* Read the on-disk MCP tools cache into a `serverName → tools` map.
|
|
2997
|
+
* Strips the per-entry timestamp / transport metadata that the cache
|
|
2998
|
+
* file keeps for diagnostics; consumers downstream of the context
|
|
2999
|
+
* only need the schemas. Best-effort — a corrupt cache produces an
|
|
3000
|
+
* empty map and the next bootstrap re-populates.
|
|
3001
|
+
*/
|
|
3002
|
+
function readToolsByServer(dataDir) {
|
|
3003
|
+
const cache = loadMcpToolsCache({ dataDir });
|
|
3004
|
+
const out = {};
|
|
3005
|
+
for (const [name, entry] of Object.entries(cache)) out[name] = entry.tools;
|
|
3006
|
+
return out;
|
|
3007
|
+
}
|
|
2951
3008
|
//#endregion
|
|
2952
3009
|
//#region src/tui/effort-picker.tsx
|
|
2953
3010
|
const BASE_LEVELS = [
|
|
@@ -7964,6 +8021,13 @@ function statusColor(status, COLOR) {
|
|
|
7964
8021
|
}
|
|
7965
8022
|
//#endregion
|
|
7966
8023
|
//#region src/tui/settings-modal.tsx
|
|
8024
|
+
/**
|
|
8025
|
+
* Stable empty default for `mcpToolsByServer` — using a fresh `{}` per
|
|
8026
|
+
* render would invalidate `useMemo` deps that reference it (and
|
|
8027
|
+
* needlessly re-run filtered / visible-row computation when the cache
|
|
8028
|
+
* is cold).
|
|
8029
|
+
*/
|
|
8030
|
+
const EMPTY_TOOLS = Object.freeze({});
|
|
7967
8031
|
const TAB_LABELS = {
|
|
7968
8032
|
...Object.fromEntries(SETTINGS_CATEGORIES.map((c) => [c.id, c.label])),
|
|
7969
8033
|
keybindings: "Keybindings",
|
|
@@ -7979,6 +8043,29 @@ function anchorIdFor(index) {
|
|
|
7979
8043
|
}
|
|
7980
8044
|
const COL_TITLE = " ";
|
|
7981
8045
|
const SPACER_CHECKBOX_WIDTH = " ";
|
|
8046
|
+
/**
|
|
8047
|
+
* Truncate `s` to `maxCols` terminal columns, appending an ellipsis
|
|
8048
|
+
* when it overflows. Used by every settings list to keep long
|
|
8049
|
+
* descriptions on a single line (`wrapMode="none"` clips silently
|
|
8050
|
+
* mid-word without an indicator; this gives the reader a clear "more
|
|
8051
|
+
* text was here" signal instead).
|
|
8052
|
+
*
|
|
8053
|
+
* Whitespace is normalised first so embedded `\n` in descriptions
|
|
8054
|
+
* doesn't blow up the budget — single space as separator everywhere.
|
|
8055
|
+
*
|
|
8056
|
+
* Column counting assumes 1 col per code point. Most descriptions in
|
|
8057
|
+
* this UI are ASCII; a CJK or wide-emoji description would over-count
|
|
8058
|
+
* its width by ~1 col per wide char but that just slightly under-
|
|
8059
|
+
* truncates rather than overflowing the row, which is the failure
|
|
8060
|
+
* mode we care about.
|
|
8061
|
+
*/
|
|
8062
|
+
function truncateToCols(s, maxCols) {
|
|
8063
|
+
const clean = s.replace(/\s+/g, " ").trim();
|
|
8064
|
+
if (maxCols <= 0) return "";
|
|
8065
|
+
if (clean.length <= maxCols) return clean;
|
|
8066
|
+
if (maxCols === 1) return "…";
|
|
8067
|
+
return `${clean.slice(0, maxCols - 1)}…`;
|
|
8068
|
+
}
|
|
7982
8069
|
function SettingsModal({ skillsCatalog: skillsCatalogProp, mcpsCatalog: mcpsCatalogProp, mcpsErrors: mcpsErrorsProp, keybindings, keybindingsPath, authentication, actions } = {}) {
|
|
7983
8070
|
const COLOR = useColors();
|
|
7984
8071
|
const SURFACE = useSurfaces();
|
|
@@ -7986,10 +8073,13 @@ function SettingsModal({ skillsCatalog: skillsCatalogProp, mcpsCatalog: mcpsCata
|
|
|
7986
8073
|
const authState = useMcpAuthState();
|
|
7987
8074
|
const inputRef = useRef(null);
|
|
7988
8075
|
const scrollboxRef = useRef(null);
|
|
8076
|
+
const { width: termWidth } = useTerminalDimensions();
|
|
8077
|
+
const contentCols = Math.max(40, Math.min(160, termWidth - 4) - 2 - 4 - 2 - 1);
|
|
7989
8078
|
const discovery = useDiscoveryOptional();
|
|
7990
8079
|
const skillsCatalog = discovery?.skillsCatalog ?? skillsCatalogProp ?? [];
|
|
7991
8080
|
const mcpsCatalog = discovery?.mcpsCatalog ?? mcpsCatalogProp ?? [];
|
|
7992
8081
|
const mcpsErrors = discovery?.mcpsErrors ?? mcpsErrorsProp;
|
|
8082
|
+
const mcpToolsByServer = discovery?.mcpToolsByServer ?? EMPTY_TOOLS;
|
|
7993
8083
|
const skillsToggle = useEnabledToggleSet({
|
|
7994
8084
|
catalog: skillsCatalog,
|
|
7995
8085
|
keyOf: (s) => s.name,
|
|
@@ -8000,6 +8090,10 @@ function SettingsModal({ skillsCatalog: skillsCatalogProp, mcpsCatalog: mcpsCata
|
|
|
8000
8090
|
keyOf: (m) => m.config.name,
|
|
8001
8091
|
settingKey: "enabledMcps"
|
|
8002
8092
|
});
|
|
8093
|
+
const mcpToolToggleMap = useMcpToolToggleMap({ catalogByServer: mcpToolsByServer });
|
|
8094
|
+
const [expandedMcps, setExpandedMcps] = useState(() => /* @__PURE__ */ new Set());
|
|
8095
|
+
const onRefreshMcpToolsRef = useRef(actions?.onRefreshMcpTools);
|
|
8096
|
+
onRefreshMcpToolsRef.current = actions?.onRefreshMcpTools;
|
|
8003
8097
|
const tabOrder = useMemo(() => {
|
|
8004
8098
|
const tabs = [...SETTINGS_CATEGORIES.map((c) => c.id)];
|
|
8005
8099
|
if (keybindings) tabs.push("keybindings");
|
|
@@ -8013,6 +8107,10 @@ function SettingsModal({ skillsCatalog: skillsCatalogProp, mcpsCatalog: mcpsCata
|
|
|
8013
8107
|
useEffect(() => {
|
|
8014
8108
|
if (!tabOrder.includes(activeTab)) setActiveTab(tabOrder[0]);
|
|
8015
8109
|
}, [tabOrder, activeTab]);
|
|
8110
|
+
useEffect(() => {
|
|
8111
|
+
if (activeTab !== "mcps") return;
|
|
8112
|
+
onRefreshMcpToolsRef.current?.();
|
|
8113
|
+
}, [activeTab]);
|
|
8016
8114
|
useEffect(() => {
|
|
8017
8115
|
inputRef.current?.focus();
|
|
8018
8116
|
}, []);
|
|
@@ -8061,13 +8159,22 @@ function SettingsModal({ skillsCatalog: skillsCatalogProp, mcpsCatalog: mcpsCata
|
|
|
8061
8159
|
return buckets;
|
|
8062
8160
|
}, [generalItems, query]);
|
|
8063
8161
|
const filteredSkills = useMemo(() => skillsCatalog.filter((s) => matchesQuery(skillCorpus(s), query)), [skillsCatalog, query]);
|
|
8064
|
-
const filteredMcps = useMemo(() => mcpsCatalog.filter((m) => matchesQuery(mcpCorpus(m), query)), [
|
|
8162
|
+
const filteredMcps = useMemo(() => mcpsCatalog.filter((m) => matchesQuery(mcpCorpus(m, mcpToolsByServer), query)), [
|
|
8163
|
+
mcpsCatalog,
|
|
8164
|
+
query,
|
|
8165
|
+
mcpToolsByServer
|
|
8166
|
+
]);
|
|
8167
|
+
const mcpVisibleRows = useMemo(() => buildVisibleMcpRows(filteredMcps, expandedMcps, mcpToolsByServer), [
|
|
8168
|
+
filteredMcps,
|
|
8169
|
+
expandedMcps,
|
|
8170
|
+
mcpToolsByServer
|
|
8171
|
+
]);
|
|
8065
8172
|
const filteredProviders = useMemo(() => (authentication?.providers ?? []).filter((p) => matchesQuery(providerCorpus(p), query)), [authentication?.providers, query]);
|
|
8066
8173
|
const authRowCount = filteredProviders.length + (authentication ? 1 : 0);
|
|
8067
8174
|
const filteredSize = {
|
|
8068
8175
|
...Object.fromEntries(SETTINGS_CATEGORIES.map((c) => [c.id, filteredByCategory[c.id].length])),
|
|
8069
8176
|
skills: filteredSkills.length,
|
|
8070
|
-
mcps:
|
|
8177
|
+
mcps: mcpVisibleRows.length,
|
|
8071
8178
|
keybindings: 0,
|
|
8072
8179
|
authentication: authRowCount
|
|
8073
8180
|
};
|
|
@@ -8075,7 +8182,7 @@ function SettingsModal({ skillsCatalog: skillsCatalogProp, mcpsCatalog: mcpsCata
|
|
|
8075
8182
|
const moveCursor = useCallback((delta) => setCursorByTab((prev) => {
|
|
8076
8183
|
const size = filteredSize[activeTab];
|
|
8077
8184
|
if (size === 0) return prev;
|
|
8078
|
-
const next = ((prev[activeTab] + delta) % size + size) % size;
|
|
8185
|
+
const next = ((Math.min(prev[activeTab], size - 1) + delta) % size + size) % size;
|
|
8079
8186
|
return {
|
|
8080
8187
|
...prev,
|
|
8081
8188
|
[activeTab]: next
|
|
@@ -8088,19 +8195,86 @@ function SettingsModal({ skillsCatalog: skillsCatalogProp, mcpsCatalog: mcpsCata
|
|
|
8088
8195
|
[activeTab]: 0
|
|
8089
8196
|
}));
|
|
8090
8197
|
}, [activeTab]);
|
|
8198
|
+
const expandFocusedMcpRow = useCallback(() => {
|
|
8199
|
+
const row = mcpVisibleRows[cursor];
|
|
8200
|
+
if (!row || row.kind !== "server") return;
|
|
8201
|
+
const name = row.entry.config.name;
|
|
8202
|
+
setExpandedMcps((prev) => {
|
|
8203
|
+
if (prev.has(name)) return prev;
|
|
8204
|
+
const next = new Set(prev);
|
|
8205
|
+
next.add(name);
|
|
8206
|
+
return next;
|
|
8207
|
+
});
|
|
8208
|
+
}, [mcpVisibleRows, cursor]);
|
|
8209
|
+
const collapseFocusedMcpRow = useCallback(() => {
|
|
8210
|
+
const row = mcpVisibleRows[cursor];
|
|
8211
|
+
if (!row) return;
|
|
8212
|
+
const parentName = parentServerName(row);
|
|
8213
|
+
if (row.kind !== "server") {
|
|
8214
|
+
const parentIndex = indexOfServerRow(mcpVisibleRows, parentName);
|
|
8215
|
+
if (parentIndex >= 0) setCursorByTab((prev) => ({
|
|
8216
|
+
...prev,
|
|
8217
|
+
mcps: parentIndex
|
|
8218
|
+
}));
|
|
8219
|
+
}
|
|
8220
|
+
setExpandedMcps((prev) => {
|
|
8221
|
+
if (!prev.has(parentName)) return prev;
|
|
8222
|
+
const next = new Set(prev);
|
|
8223
|
+
next.delete(parentName);
|
|
8224
|
+
return next;
|
|
8225
|
+
});
|
|
8226
|
+
}, [mcpVisibleRows, cursor]);
|
|
8227
|
+
/**
|
|
8228
|
+
* Single `tab` press toggles between expanded / collapsed based on
|
|
8229
|
+
* the focused row's current state. The split helpers above remain
|
|
8230
|
+
* useful for `→` / `←` (a "tree-style" navigation contract some
|
|
8231
|
+
* users expect — expand-only / collapse-only). Toggle is the
|
|
8232
|
+
* default that the footer hint advertises.
|
|
8233
|
+
*/
|
|
8234
|
+
const toggleExpandFocusedMcpRow = useCallback(() => {
|
|
8235
|
+
const row = mcpVisibleRows[cursor];
|
|
8236
|
+
if (!row) return;
|
|
8237
|
+
const parentName = parentServerName(row);
|
|
8238
|
+
if (expandedMcps.has(parentName)) collapseFocusedMcpRow();
|
|
8239
|
+
else expandFocusedMcpRow();
|
|
8240
|
+
}, [
|
|
8241
|
+
mcpVisibleRows,
|
|
8242
|
+
cursor,
|
|
8243
|
+
expandedMcps,
|
|
8244
|
+
collapseFocusedMcpRow,
|
|
8245
|
+
expandFocusedMcpRow
|
|
8246
|
+
]);
|
|
8247
|
+
const mcpRowCount = mcpVisibleRows.length;
|
|
8091
8248
|
useEffect(() => {
|
|
8092
8249
|
if (activeTab === "keybindings") return;
|
|
8093
8250
|
const sb = scrollboxRef.current;
|
|
8094
8251
|
if (!sb) return;
|
|
8095
|
-
|
|
8096
|
-
|
|
8252
|
+
let inner = 0;
|
|
8253
|
+
const outer = requestAnimationFrame(() => {
|
|
8254
|
+
inner = requestAnimationFrame(() => {
|
|
8255
|
+
sb.scrollChildIntoView(anchorIdFor(cursor));
|
|
8256
|
+
});
|
|
8097
8257
|
});
|
|
8098
|
-
return () =>
|
|
8258
|
+
return () => {
|
|
8259
|
+
cancelAnimationFrame(outer);
|
|
8260
|
+
if (inner) cancelAnimationFrame(inner);
|
|
8261
|
+
};
|
|
8099
8262
|
}, [
|
|
8100
8263
|
cursor,
|
|
8101
8264
|
activeTab,
|
|
8102
|
-
query
|
|
8265
|
+
query,
|
|
8266
|
+
mcpRowCount
|
|
8103
8267
|
]);
|
|
8268
|
+
useEffect(() => {
|
|
8269
|
+
if (mcpRowCount === 0) return;
|
|
8270
|
+
setCursorByTab((prev) => {
|
|
8271
|
+
if (prev.mcps < mcpRowCount) return prev;
|
|
8272
|
+
return {
|
|
8273
|
+
...prev,
|
|
8274
|
+
mcps: mcpRowCount - 1
|
|
8275
|
+
};
|
|
8276
|
+
});
|
|
8277
|
+
}, [mcpRowCount]);
|
|
8104
8278
|
useEffect(() => {
|
|
8105
8279
|
if (activeTab !== "keybindings") return;
|
|
8106
8280
|
const sb = scrollboxRef.current;
|
|
@@ -8110,7 +8284,8 @@ function SettingsModal({ skillsCatalog: skillsCatalogProp, mcpsCatalog: mcpsCata
|
|
|
8110
8284
|
});
|
|
8111
8285
|
return () => cancelAnimationFrame(handle);
|
|
8112
8286
|
}, [activeTab]);
|
|
8113
|
-
const
|
|
8287
|
+
const focusedMcpRow = activeTab === "mcps" ? mcpVisibleRows[cursor] : void 0;
|
|
8288
|
+
const focusedMcp = focusedMcpRow ? mcpsCatalog.find((m) => m.config.name === parentServerName(focusedMcpRow)) : void 0;
|
|
8114
8289
|
const focusedMcpStatus = focusedMcp ? getMcpAuthStatus(authState, focusedMcp.config.name) : void 0;
|
|
8115
8290
|
const oauthPasteActive = activeTab === "mcps" && focusedMcpStatus?.kind === "authorizing" && !!focusedMcpStatus.url;
|
|
8116
8291
|
useKeyboard((key) => {
|
|
@@ -8155,6 +8330,10 @@ function SettingsModal({ skillsCatalog: skillsCatalogProp, mcpsCatalog: mcpsCata
|
|
|
8155
8330
|
});
|
|
8156
8331
|
return;
|
|
8157
8332
|
}
|
|
8333
|
+
if (activeTab === "mcps" && key.name === "tab") {
|
|
8334
|
+
toggleExpandFocusedMcpRow();
|
|
8335
|
+
return;
|
|
8336
|
+
}
|
|
8158
8337
|
if (key.name === "return") {
|
|
8159
8338
|
if (isCategoryTab(activeTab)) {
|
|
8160
8339
|
const it = filteredByCategory[activeTab][cursor];
|
|
@@ -8174,8 +8353,10 @@ function SettingsModal({ skillsCatalog: skillsCatalogProp, mcpsCatalog: mcpsCata
|
|
|
8174
8353
|
return;
|
|
8175
8354
|
}
|
|
8176
8355
|
if (activeTab === "mcps") {
|
|
8177
|
-
const
|
|
8178
|
-
if (
|
|
8356
|
+
const row = mcpVisibleRows[cursor];
|
|
8357
|
+
if (!row) return;
|
|
8358
|
+
if (row.kind === "server") mcpsToggle.toggle(row.entry.config.name);
|
|
8359
|
+
else if (row.kind === "tool") mcpToolToggleMap[row.serverName]?.toggle(row.tool.name);
|
|
8179
8360
|
return;
|
|
8180
8361
|
}
|
|
8181
8362
|
if (activeTab === "keybindings") {
|
|
@@ -8198,11 +8379,12 @@ function SettingsModal({ skillsCatalog: skillsCatalogProp, mcpsCatalog: mcpsCata
|
|
|
8198
8379
|
return;
|
|
8199
8380
|
}
|
|
8200
8381
|
if (activeTab === "mcps" && focusedMcp && focusedMcpStatus) {
|
|
8201
|
-
|
|
8382
|
+
const onServerRow = focusedMcpRow?.kind === "server";
|
|
8383
|
+
if (onServerRow && key.ctrl && key.name === "l" && canLogin$1(focusedMcp, focusedMcpStatus)) {
|
|
8202
8384
|
actions?.onLoginMcp?.(focusedMcp.config.name);
|
|
8203
8385
|
return;
|
|
8204
8386
|
}
|
|
8205
|
-
if (key.ctrl && key.name === "o" && canLogout$1(focusedMcpStatus)) {
|
|
8387
|
+
if (onServerRow && key.ctrl && key.name === "o" && canLogout$1(focusedMcpStatus)) {
|
|
8206
8388
|
actions?.onLogoutMcp?.(focusedMcp.config.name);
|
|
8207
8389
|
return;
|
|
8208
8390
|
}
|
|
@@ -8213,7 +8395,10 @@ function SettingsModal({ skillsCatalog: skillsCatalogProp, mcpsCatalog: mcpsCata
|
|
|
8213
8395
|
actions.onRefreshSkills();
|
|
8214
8396
|
return;
|
|
8215
8397
|
}
|
|
8216
|
-
if (activeTab === "mcps"
|
|
8398
|
+
if (activeTab === "mcps") {
|
|
8399
|
+
if (actions?.onRefreshMcps) actions.onRefreshMcps();
|
|
8400
|
+
if (actions?.onRefreshMcpTools) actions.onRefreshMcpTools();
|
|
8401
|
+
}
|
|
8217
8402
|
}
|
|
8218
8403
|
});
|
|
8219
8404
|
const totalsByCategory = useMemo(() => {
|
|
@@ -8277,12 +8462,14 @@ function SettingsModal({ skillsCatalog: skillsCatalogProp, mcpsCatalog: mcpsCata
|
|
|
8277
8462
|
minHeight: 4
|
|
8278
8463
|
},
|
|
8279
8464
|
stickyScroll: false,
|
|
8465
|
+
viewportCulling: false,
|
|
8280
8466
|
children: [
|
|
8281
8467
|
isCategoryTab(activeTab) && /* @__PURE__ */ jsx(GeneralList, {
|
|
8282
8468
|
items: filteredByCategory[activeTab],
|
|
8283
8469
|
cursor,
|
|
8284
8470
|
highlightBg: SURFACE.selection,
|
|
8285
|
-
query
|
|
8471
|
+
query,
|
|
8472
|
+
contentCols
|
|
8286
8473
|
}),
|
|
8287
8474
|
activeTab === "skills" && /* @__PURE__ */ jsx(SkillsList, {
|
|
8288
8475
|
items: filteredSkills,
|
|
@@ -8290,16 +8477,22 @@ function SettingsModal({ skillsCatalog: skillsCatalogProp, mcpsCatalog: mcpsCata
|
|
|
8290
8477
|
totalCount: skillsCatalog.length,
|
|
8291
8478
|
cursor,
|
|
8292
8479
|
highlightBg: SURFACE.selection,
|
|
8293
|
-
query
|
|
8480
|
+
query,
|
|
8481
|
+
contentCols
|
|
8294
8482
|
}),
|
|
8295
8483
|
activeTab === "mcps" && /* @__PURE__ */ jsx(McpsList, {
|
|
8296
|
-
|
|
8484
|
+
rows: mcpVisibleRows,
|
|
8485
|
+
filteredCount: filteredMcps.length,
|
|
8297
8486
|
enabledSet: mcpsToggle.enabledSet,
|
|
8487
|
+
toolToggleMap: mcpToolToggleMap,
|
|
8488
|
+
toolsByServer: mcpToolsByServer,
|
|
8489
|
+
expanded: expandedMcps,
|
|
8298
8490
|
totalCount: mcpsCatalog.length,
|
|
8299
8491
|
cursor,
|
|
8300
8492
|
highlightBg: SURFACE.selection,
|
|
8301
8493
|
query,
|
|
8302
8494
|
errors: mcpsErrors,
|
|
8495
|
+
contentCols,
|
|
8303
8496
|
authState
|
|
8304
8497
|
}),
|
|
8305
8498
|
activeTab === "keybindings" && keybindings && /* @__PURE__ */ jsx(KeybindingsList, {
|
|
@@ -8331,6 +8524,7 @@ function SettingsModal({ skillsCatalog: skillsCatalogProp, mcpsCatalog: mcpsCata
|
|
|
8331
8524
|
children: /* @__PURE__ */ jsx(Hints, {
|
|
8332
8525
|
activeTab,
|
|
8333
8526
|
focusedMcp,
|
|
8527
|
+
focusedMcpRow,
|
|
8334
8528
|
focusedMcpStatus,
|
|
8335
8529
|
canRefreshSkills: !!actions?.onRefreshSkills,
|
|
8336
8530
|
canRefreshMcps: !!actions?.onRefreshMcps
|
|
@@ -8374,9 +8568,10 @@ function TabStrip({ order, active, counts }) {
|
|
|
8374
8568
|
})
|
|
8375
8569
|
});
|
|
8376
8570
|
}
|
|
8377
|
-
function GeneralList({ items, cursor, highlightBg, query }) {
|
|
8571
|
+
function GeneralList({ items, cursor, highlightBg, query, contentCols }) {
|
|
8378
8572
|
const { settings } = useSettings();
|
|
8379
8573
|
if (items.length === 0) return /* @__PURE__ */ jsx(EmptyRow, { label: query ? `no settings match "${query}"` : "no settings" });
|
|
8574
|
+
const descCols = Math.max(0, contentCols - 1 - 1 - 6);
|
|
8380
8575
|
return /* @__PURE__ */ jsx("box", {
|
|
8381
8576
|
style: { flexDirection: "column" },
|
|
8382
8577
|
children: items.map((item, i) => {
|
|
@@ -8386,7 +8581,7 @@ function GeneralList({ items, cursor, highlightBg, query }) {
|
|
|
8386
8581
|
if (item.kind === "toggle") return /* @__PURE__ */ jsx(ToggleRow, {
|
|
8387
8582
|
id,
|
|
8388
8583
|
label: item.label,
|
|
8389
|
-
description: item.description,
|
|
8584
|
+
description: truncateToCols(item.description, descCols),
|
|
8390
8585
|
enabled: settings[item.key],
|
|
8391
8586
|
focused,
|
|
8392
8587
|
bg
|
|
@@ -8397,7 +8592,7 @@ function GeneralList({ items, cursor, highlightBg, query }) {
|
|
|
8397
8592
|
return /* @__PURE__ */ jsx(ChoiceRow, {
|
|
8398
8593
|
id,
|
|
8399
8594
|
label: item.label,
|
|
8400
|
-
description: item.description,
|
|
8595
|
+
description: truncateToCols(item.description, descCols),
|
|
8401
8596
|
value: opt?.label ?? String(current),
|
|
8402
8597
|
cyclable: item.options.length > 1,
|
|
8403
8598
|
focused,
|
|
@@ -8407,14 +8602,14 @@ function GeneralList({ items, cursor, highlightBg, query }) {
|
|
|
8407
8602
|
return /* @__PURE__ */ jsx(ActionRow$1, {
|
|
8408
8603
|
id,
|
|
8409
8604
|
label: item.label,
|
|
8410
|
-
description: item.description,
|
|
8605
|
+
description: truncateToCols(item.description, descCols),
|
|
8411
8606
|
focused,
|
|
8412
8607
|
bg
|
|
8413
8608
|
}, item.id);
|
|
8414
8609
|
})
|
|
8415
8610
|
});
|
|
8416
8611
|
}
|
|
8417
|
-
function SkillsList({ items, enabledSet, totalCount, cursor, highlightBg, query }) {
|
|
8612
|
+
function SkillsList({ items, enabledSet, totalCount, cursor, highlightBg, query, contentCols }) {
|
|
8418
8613
|
const COLOR = useColors();
|
|
8419
8614
|
if (totalCount === 0) return /* @__PURE__ */ jsxs("box", {
|
|
8420
8615
|
style: { flexDirection: "column" },
|
|
@@ -8449,12 +8644,14 @@ function SkillsList({ items, enabledSet, totalCount, cursor, highlightBg, query
|
|
|
8449
8644
|
})]
|
|
8450
8645
|
});
|
|
8451
8646
|
if (items.length === 0) return /* @__PURE__ */ jsx(EmptyRow, { label: `no skills match "${query}"` });
|
|
8647
|
+
const descCols = Math.max(0, contentCols - 1 - 1 - 6);
|
|
8452
8648
|
return /* @__PURE__ */ jsx("box", {
|
|
8453
8649
|
style: { flexDirection: "column" },
|
|
8454
8650
|
children: items.map((entry, i) => {
|
|
8455
8651
|
const focused = i === cursor;
|
|
8456
8652
|
const bg = focused ? highlightBg : void 0;
|
|
8457
8653
|
const enabled = enabledSet.has(entry.name);
|
|
8654
|
+
const description = truncateToCols(entry.description ?? "", descCols);
|
|
8458
8655
|
return /* @__PURE__ */ jsxs("box", {
|
|
8459
8656
|
id: anchorIdFor(i),
|
|
8460
8657
|
style: {
|
|
@@ -8483,13 +8680,13 @@ function SkillsList({ items, enabledSet, totalCount, cursor, highlightBg, query
|
|
|
8483
8680
|
}), /* @__PURE__ */ jsx("text", {
|
|
8484
8681
|
wrapMode: "none",
|
|
8485
8682
|
fg: COLOR.mute,
|
|
8486
|
-
children: `${COL_TITLE}${
|
|
8683
|
+
children: `${COL_TITLE}${description}`
|
|
8487
8684
|
})]
|
|
8488
8685
|
}, entry.name);
|
|
8489
8686
|
})
|
|
8490
8687
|
});
|
|
8491
8688
|
}
|
|
8492
|
-
function McpsList({
|
|
8689
|
+
function McpsList({ rows, filteredCount, enabledSet, toolToggleMap, toolsByServer, expanded, totalCount, cursor, highlightBg, query, errors, authState, contentCols }) {
|
|
8493
8690
|
const COLOR = useColors();
|
|
8494
8691
|
const home = homedir();
|
|
8495
8692
|
if (totalCount === 0) return /* @__PURE__ */ jsxs("box", {
|
|
@@ -8533,53 +8730,150 @@ function McpsList({ items, enabledSet, totalCount, cursor, highlightBg, query, e
|
|
|
8533
8730
|
})
|
|
8534
8731
|
]
|
|
8535
8732
|
});
|
|
8536
|
-
if (
|
|
8733
|
+
if (filteredCount === 0) return /* @__PURE__ */ jsxs("box", {
|
|
8537
8734
|
style: { flexDirection: "column" },
|
|
8538
8735
|
children: [renderMcpErrors(errors, home, COLOR.warn), /* @__PURE__ */ jsx(EmptyRow, { label: `no servers match "${query}"` })]
|
|
8539
8736
|
});
|
|
8737
|
+
const groups = [];
|
|
8738
|
+
for (let i = 0; i < rows.length; i++) {
|
|
8739
|
+
const row = rows[i];
|
|
8740
|
+
if (row.kind === "server") {
|
|
8741
|
+
groups.push({
|
|
8742
|
+
serverIndex: i,
|
|
8743
|
+
server: row,
|
|
8744
|
+
children: []
|
|
8745
|
+
});
|
|
8746
|
+
continue;
|
|
8747
|
+
}
|
|
8748
|
+
const last = groups[groups.length - 1];
|
|
8749
|
+
if (last) last.children.push({
|
|
8750
|
+
row,
|
|
8751
|
+
index: i
|
|
8752
|
+
});
|
|
8753
|
+
}
|
|
8540
8754
|
return /* @__PURE__ */ jsxs("box", {
|
|
8541
8755
|
style: { flexDirection: "column" },
|
|
8542
|
-
children: [renderMcpErrors(errors, home, COLOR.warn),
|
|
8543
|
-
|
|
8544
|
-
|
|
8545
|
-
|
|
8546
|
-
|
|
8547
|
-
|
|
8548
|
-
|
|
8549
|
-
|
|
8550
|
-
|
|
8551
|
-
|
|
8552
|
-
|
|
8553
|
-
|
|
8554
|
-
|
|
8555
|
-
|
|
8556
|
-
|
|
8557
|
-
|
|
8558
|
-
|
|
8559
|
-
|
|
8560
|
-
|
|
8561
|
-
|
|
8562
|
-
|
|
8563
|
-
|
|
8564
|
-
|
|
8565
|
-
|
|
8566
|
-
|
|
8567
|
-
|
|
8568
|
-
|
|
8569
|
-
|
|
8570
|
-
|
|
8571
|
-
|
|
8572
|
-
|
|
8573
|
-
|
|
8574
|
-
|
|
8575
|
-
|
|
8576
|
-
|
|
8577
|
-
|
|
8578
|
-
})]
|
|
8579
|
-
}, name);
|
|
8580
|
-
})]
|
|
8756
|
+
children: [renderMcpErrors(errors, home, COLOR.warn), groups.map((group) => /* @__PURE__ */ jsxs("box", {
|
|
8757
|
+
style: { flexDirection: "column" },
|
|
8758
|
+
children: [renderServerListRow({
|
|
8759
|
+
entry: group.server.entry,
|
|
8760
|
+
focused: group.serverIndex === cursor,
|
|
8761
|
+
bg: group.serverIndex === cursor ? highlightBg : void 0,
|
|
8762
|
+
enabled: enabledSet.has(group.server.entry.config.name),
|
|
8763
|
+
expanded: expanded.has(group.server.entry.config.name),
|
|
8764
|
+
toolEnabledSet: toolToggleMap[group.server.entry.config.name]?.enabledSet,
|
|
8765
|
+
toolTotal: toolsByServer[group.server.entry.config.name]?.length,
|
|
8766
|
+
anchorId: anchorIdFor(group.serverIndex),
|
|
8767
|
+
status: getMcpAuthStatus(authState, group.server.entry.config.name),
|
|
8768
|
+
COLOR,
|
|
8769
|
+
contentCols
|
|
8770
|
+
}), group.children.map(({ row, index }) => {
|
|
8771
|
+
const focused = index === cursor;
|
|
8772
|
+
const bg = focused ? highlightBg : void 0;
|
|
8773
|
+
if (row.kind === "tool") return renderToolListRow({
|
|
8774
|
+
serverName: row.serverName,
|
|
8775
|
+
tool: row.tool,
|
|
8776
|
+
focused,
|
|
8777
|
+
bg,
|
|
8778
|
+
enabled: toolToggleMap[row.serverName]?.enabledSet.has(row.tool.name) ?? true,
|
|
8779
|
+
anchorId: anchorIdFor(index),
|
|
8780
|
+
COLOR,
|
|
8781
|
+
contentCols
|
|
8782
|
+
});
|
|
8783
|
+
return renderEmptyToolsListRow({
|
|
8784
|
+
serverName: row.serverName,
|
|
8785
|
+
anchorId: anchorIdFor(index),
|
|
8786
|
+
focused,
|
|
8787
|
+
bg,
|
|
8788
|
+
COLOR
|
|
8789
|
+
});
|
|
8790
|
+
})]
|
|
8791
|
+
}, `group:${group.server.entry.config.name}`))]
|
|
8581
8792
|
});
|
|
8582
8793
|
}
|
|
8794
|
+
function renderServerListRow({ entry, focused, bg, enabled, expanded, toolEnabledSet, toolTotal, anchorId, status, COLOR, contentCols }) {
|
|
8795
|
+
const name = entry.config.name;
|
|
8796
|
+
const chevron = expanded ? "▾" : "▸";
|
|
8797
|
+
const toolCountChip = toolEnabledSet && typeof toolTotal === "number" ? ` (${toolEnabledSet.size}/${toolTotal})` : "";
|
|
8798
|
+
const detailRaw = mcpDetail(entry);
|
|
8799
|
+
const detailBudget = Math.max(0, contentCols - 2 - 2 - 4 - name.length - toolCountChip.length - 10 - 3);
|
|
8800
|
+
const detail = detailRaw ? truncateToCols(detailRaw, detailBudget) : "";
|
|
8801
|
+
return /* @__PURE__ */ jsxs("text", {
|
|
8802
|
+
id: anchorId,
|
|
8803
|
+
wrapMode: "none",
|
|
8804
|
+
bg,
|
|
8805
|
+
children: [
|
|
8806
|
+
/* @__PURE__ */ jsx("span", {
|
|
8807
|
+
fg: focused ? COLOR.brand : COLOR.mute,
|
|
8808
|
+
children: focused ? "▶ " : " "
|
|
8809
|
+
}),
|
|
8810
|
+
/* @__PURE__ */ jsx("span", {
|
|
8811
|
+
fg: focused ? COLOR.brand : COLOR.mute,
|
|
8812
|
+
children: `${chevron} `
|
|
8813
|
+
}),
|
|
8814
|
+
/* @__PURE__ */ jsx("span", {
|
|
8815
|
+
fg: enabled ? COLOR.accent : COLOR.mute,
|
|
8816
|
+
children: enabled ? "[✓] " : "[ ] "
|
|
8817
|
+
}),
|
|
8818
|
+
/* @__PURE__ */ jsx("span", {
|
|
8819
|
+
fg: focused ? COLOR.brand : COLOR.dim,
|
|
8820
|
+
children: name
|
|
8821
|
+
}),
|
|
8822
|
+
toolCountChip && /* @__PURE__ */ jsx("span", {
|
|
8823
|
+
fg: COLOR.mute,
|
|
8824
|
+
children: toolCountChip
|
|
8825
|
+
}),
|
|
8826
|
+
renderInlineMcpBadge(status, COLOR),
|
|
8827
|
+
detail && /* @__PURE__ */ jsx("span", {
|
|
8828
|
+
fg: COLOR.mute,
|
|
8829
|
+
children: ` · ${detail}`
|
|
8830
|
+
})
|
|
8831
|
+
]
|
|
8832
|
+
}, `server:${name}`);
|
|
8833
|
+
}
|
|
8834
|
+
const TOOL_ROW_INDENT$1 = " ";
|
|
8835
|
+
const TOOL_ROW_FOCUS_INDENT$1 = " ▶ ";
|
|
8836
|
+
function renderToolListRow({ serverName, tool, focused, bg, enabled, anchorId, COLOR, contentCols }) {
|
|
8837
|
+
const descBudget = Math.max(0, contentCols - 6 - 4 - tool.name.length - 2);
|
|
8838
|
+
const description = truncateToCols(tool.description ?? "", descBudget);
|
|
8839
|
+
return /* @__PURE__ */ jsxs("text", {
|
|
8840
|
+
id: anchorId,
|
|
8841
|
+
wrapMode: "none",
|
|
8842
|
+
bg,
|
|
8843
|
+
children: [
|
|
8844
|
+
/* @__PURE__ */ jsx("span", {
|
|
8845
|
+
fg: focused ? COLOR.brand : COLOR.mute,
|
|
8846
|
+
children: focused ? TOOL_ROW_FOCUS_INDENT$1 : TOOL_ROW_INDENT$1
|
|
8847
|
+
}),
|
|
8848
|
+
/* @__PURE__ */ jsx("span", {
|
|
8849
|
+
fg: enabled ? COLOR.accent : COLOR.mute,
|
|
8850
|
+
children: enabled ? "[✓] " : "[ ] "
|
|
8851
|
+
}),
|
|
8852
|
+
/* @__PURE__ */ jsx("span", {
|
|
8853
|
+
fg: focused ? COLOR.brand : COLOR.dim,
|
|
8854
|
+
children: tool.name
|
|
8855
|
+
}),
|
|
8856
|
+
description && /* @__PURE__ */ jsxs("span", {
|
|
8857
|
+
fg: focused ? COLOR.dim : COLOR.mute,
|
|
8858
|
+
children: [" ", description]
|
|
8859
|
+
})
|
|
8860
|
+
]
|
|
8861
|
+
}, `tool:${serverName}/${tool.name}`);
|
|
8862
|
+
}
|
|
8863
|
+
function renderEmptyToolsListRow({ serverName, anchorId, focused, bg, COLOR }) {
|
|
8864
|
+
return /* @__PURE__ */ jsxs("text", {
|
|
8865
|
+
id: anchorId,
|
|
8866
|
+
wrapMode: "none",
|
|
8867
|
+
bg,
|
|
8868
|
+
children: [/* @__PURE__ */ jsx("span", {
|
|
8869
|
+
fg: focused ? COLOR.brand : COLOR.mute,
|
|
8870
|
+
children: focused ? TOOL_ROW_FOCUS_INDENT$1 : TOOL_ROW_INDENT$1
|
|
8871
|
+
}), /* @__PURE__ */ jsx("span", {
|
|
8872
|
+
fg: COLOR.mute,
|
|
8873
|
+
children: "⟲ No cached tools yet. Send any prompt with this server enabled to populate."
|
|
8874
|
+
})]
|
|
8875
|
+
}, `tool-empty:${serverName}`);
|
|
8876
|
+
}
|
|
8583
8877
|
function KeybindingsList({ bindings, query }) {
|
|
8584
8878
|
const sections = useMemo(() => groupBindings(bindings), [bindings]);
|
|
8585
8879
|
const filteredSections = useMemo(() => {
|
|
@@ -8937,21 +9231,21 @@ function renderMcpDetailPanel(entry, status, COLOR) {
|
|
|
8937
9231
|
function renderInlineMcpBadge(status, COLOR) {
|
|
8938
9232
|
switch (status.kind) {
|
|
8939
9233
|
case "idle": return null;
|
|
8940
|
-
case "authed": return /* @__PURE__ */
|
|
9234
|
+
case "authed": return /* @__PURE__ */ jsx("span", {
|
|
8941
9235
|
fg: COLOR.accent,
|
|
8942
|
-
children:
|
|
9236
|
+
children: " · ✓ authed"
|
|
8943
9237
|
});
|
|
8944
|
-
case "needs-auth": return /* @__PURE__ */
|
|
9238
|
+
case "needs-auth": return /* @__PURE__ */ jsx("span", {
|
|
8945
9239
|
fg: COLOR.warn,
|
|
8946
|
-
children:
|
|
9240
|
+
children: " · ! needs login"
|
|
8947
9241
|
});
|
|
8948
|
-
case "authorizing": return /* @__PURE__ */
|
|
9242
|
+
case "authorizing": return /* @__PURE__ */ jsx("span", {
|
|
8949
9243
|
fg: COLOR.warn,
|
|
8950
|
-
children:
|
|
9244
|
+
children: " · … authorizing"
|
|
8951
9245
|
});
|
|
8952
|
-
case "error": return /* @__PURE__ */
|
|
9246
|
+
case "error": return /* @__PURE__ */ jsx("span", {
|
|
8953
9247
|
fg: COLOR.error,
|
|
8954
|
-
children:
|
|
9248
|
+
children: " · ✗ login failed"
|
|
8955
9249
|
});
|
|
8956
9250
|
}
|
|
8957
9251
|
}
|
|
@@ -8965,10 +9259,11 @@ function renderMcpErrors(errors, home, warnColor) {
|
|
|
8965
9259
|
}, err.path))
|
|
8966
9260
|
});
|
|
8967
9261
|
}
|
|
8968
|
-
function Hints({ activeTab, focusedMcp, focusedMcpStatus, canRefreshSkills, canRefreshMcps }) {
|
|
9262
|
+
function Hints({ activeTab, focusedMcp, focusedMcpRow, focusedMcpStatus, canRefreshSkills, canRefreshMcps }) {
|
|
8969
9263
|
const COLOR = useColors();
|
|
8970
|
-
const
|
|
8971
|
-
const
|
|
9264
|
+
const onServerRow = focusedMcpRow?.kind === "server";
|
|
9265
|
+
const showLogin = activeTab === "mcps" && onServerRow && !!focusedMcp && !!focusedMcpStatus && canLogin$1(focusedMcp, focusedMcpStatus);
|
|
9266
|
+
const showLogout = activeTab === "mcps" && onServerRow && !!focusedMcpStatus && canLogout$1(focusedMcpStatus);
|
|
8972
9267
|
const showCancel = activeTab === "mcps" && focusedMcpStatus?.kind === "authorizing";
|
|
8973
9268
|
const showRefresh = activeTab === "skills" && canRefreshSkills || activeTab === "mcps" && canRefreshMcps;
|
|
8974
9269
|
return /* @__PURE__ */ jsxs("text", {
|
|
@@ -8989,6 +9284,14 @@ function Hints({ activeTab, focusedMcp, focusedMcpStatus, canRefreshSkills, canR
|
|
|
8989
9284
|
children: "↵"
|
|
8990
9285
|
}),
|
|
8991
9286
|
isCategoryTab(activeTab) ? " toggle/cycle/select" : activeTab === "keybindings" ? " edit file" : activeTab === "authentication" ? " switch / re-authenticate" : " toggle",
|
|
9287
|
+
activeTab === "mcps" && focusedMcpRow && /* @__PURE__ */ jsxs("span", { children: [
|
|
9288
|
+
" · ",
|
|
9289
|
+
/* @__PURE__ */ jsx("span", {
|
|
9290
|
+
fg: COLOR.warn,
|
|
9291
|
+
children: "tab"
|
|
9292
|
+
}),
|
|
9293
|
+
focusedMcpRow.kind === "server" ? " expand/collapse" : " collapse"
|
|
9294
|
+
] }),
|
|
8992
9295
|
showLogin && /* @__PURE__ */ jsxs("span", { children: [
|
|
8993
9296
|
" · ",
|
|
8994
9297
|
/* @__PURE__ */ jsx("span", {
|
|
@@ -9044,13 +9347,15 @@ function generalCorpus(item) {
|
|
|
9044
9347
|
function skillCorpus(s) {
|
|
9045
9348
|
return `${s.name} ${s.description ?? ""}`.toLowerCase();
|
|
9046
9349
|
}
|
|
9047
|
-
function mcpCorpus(m) {
|
|
9350
|
+
function mcpCorpus(m, toolsByServer) {
|
|
9351
|
+
const toolCorpus = (toolsByServer[m.config.name] ?? []).map((t) => `${t.name} ${t.description ?? ""}`).join(" ");
|
|
9048
9352
|
return [
|
|
9049
9353
|
m.config.name,
|
|
9050
9354
|
m.config.transport,
|
|
9051
9355
|
m.config.command ?? "",
|
|
9052
9356
|
m.config.url ?? "",
|
|
9053
|
-
(m.config.args ?? []).join(" ")
|
|
9357
|
+
(m.config.args ?? []).join(" "),
|
|
9358
|
+
toolCorpus
|
|
9054
9359
|
].join(" ").toLowerCase();
|
|
9055
9360
|
}
|
|
9056
9361
|
function keybindingCorpus(row) {
|
|
@@ -9897,6 +10202,8 @@ function AppShell() {
|
|
|
9897
10202
|
mcpsCatalogRef.current = mcpsCatalog;
|
|
9898
10203
|
const enabledMcpsRef = useRef(settings.enabledMcps);
|
|
9899
10204
|
enabledMcpsRef.current = settings.enabledMcps;
|
|
10205
|
+
const disabledMcpToolsRef = useRef(settings.disabledMcpTools);
|
|
10206
|
+
disabledMcpToolsRef.current = settings.disabledMcpTools;
|
|
9900
10207
|
const filesCatalogRef = useRef(filesCatalog);
|
|
9901
10208
|
filesCatalogRef.current = filesCatalog;
|
|
9902
10209
|
const ensureFilesCatalogRef = useRef(ensureFilesCatalog);
|
|
@@ -10234,7 +10541,8 @@ function AppShell() {
|
|
|
10234
10541
|
});
|
|
10235
10542
|
const projectMcps = buildMcpServers({
|
|
10236
10543
|
discovered: mcpsCatalogRef.current,
|
|
10237
|
-
enabled: enabledMcpsRef.current
|
|
10544
|
+
enabled: enabledMcpsRef.current,
|
|
10545
|
+
...disabledMcpToolsRef.current ? { disabledTools: disabledMcpToolsRef.current } : {}
|
|
10238
10546
|
});
|
|
10239
10547
|
const allowInteraction = allowInteractionRef.current !== false;
|
|
10240
10548
|
const interactionTools = allowInteraction ? createInteractionTools({ requestInteraction: makeRequestInteraction(interactions) }) : {};
|
|
@@ -10399,6 +10707,15 @@ function AppShell() {
|
|
|
10399
10707
|
reason
|
|
10400
10708
|
});
|
|
10401
10709
|
});
|
|
10710
|
+
agent.hooks.hook("mcp:bootstrap:end", (ctx) => {
|
|
10711
|
+
if (ctx.ok) return;
|
|
10712
|
+
if (mcpsCatalogRef.current.find((d) => d.config.name === ctx.name)?.config.auth !== "oauth") return;
|
|
10713
|
+
dispatchAuthRef.current({
|
|
10714
|
+
type: "auth-error",
|
|
10715
|
+
name: ctx.name,
|
|
10716
|
+
error: ctx.error.message
|
|
10717
|
+
});
|
|
10718
|
+
});
|
|
10402
10719
|
agent.hooks.hook("mcp:auth:url", ({ name, url }) => {
|
|
10403
10720
|
dispatchAuthRef.current({
|
|
10404
10721
|
type: "auth-url",
|
|
@@ -10634,6 +10951,7 @@ function AppShell() {
|
|
|
10634
10951
|
agent.hooks.hook("agent:done", () => {
|
|
10635
10952
|
pendingAnnotationsRef.current.clear();
|
|
10636
10953
|
});
|
|
10954
|
+
subscribeMcpToolsCache(agent.hooks, { dataDir });
|
|
10637
10955
|
return agent;
|
|
10638
10956
|
}, [
|
|
10639
10957
|
providerRegistry,
|
|
@@ -11311,6 +11629,24 @@ function AppShell() {
|
|
|
11311
11629
|
const onCancelLoginMcp = useCallback((name) => {
|
|
11312
11630
|
mcpLoginAbortsRef.current.get(name)?.abort();
|
|
11313
11631
|
}, []);
|
|
11632
|
+
const onRefreshMcpTools = useCallback(async () => {
|
|
11633
|
+
const agent = agentRef.current;
|
|
11634
|
+
if (!agent) return;
|
|
11635
|
+
const servers = mcpsCatalogRef.current.map((d) => d.config);
|
|
11636
|
+
if (servers.length === 0) return;
|
|
11637
|
+
try {
|
|
11638
|
+
await refreshMcpToolsCatalog({
|
|
11639
|
+
servers,
|
|
11640
|
+
hooks: agent.hooks,
|
|
11641
|
+
buildAuthProvider: (cfg) => new McpOAuthProvider({
|
|
11642
|
+
name: cfg.name,
|
|
11643
|
+
store: mcpCredentialStore
|
|
11644
|
+
})
|
|
11645
|
+
});
|
|
11646
|
+
} catch (err) {
|
|
11647
|
+
debugLog("refreshMcpToolsCatalog failed", err);
|
|
11648
|
+
}
|
|
11649
|
+
}, [mcpCredentialStore]);
|
|
11314
11650
|
const onOpenKeybindingsFile = useCallback(() => {
|
|
11315
11651
|
modal.close();
|
|
11316
11652
|
(async () => {
|
|
@@ -11817,7 +12153,8 @@ function AppShell() {
|
|
|
11817
12153
|
onLogoutMcp,
|
|
11818
12154
|
onCancelLoginMcp,
|
|
11819
12155
|
onRefreshSkills,
|
|
11820
|
-
onRefreshMcps
|
|
12156
|
+
onRefreshMcps,
|
|
12157
|
+
onRefreshMcpTools
|
|
11821
12158
|
}
|
|
11822
12159
|
}));
|
|
11823
12160
|
return;
|
|
@@ -12287,55 +12624,100 @@ function initTreeSitterWorker() {
|
|
|
12287
12624
|
//#endregion
|
|
12288
12625
|
//#region src/tui/mcps-settings.tsx
|
|
12289
12626
|
/**
|
|
12290
|
-
* MCP server picker.
|
|
12627
|
+
* MCP server picker. Hierarchical: each server is one row, and an
|
|
12628
|
+
* expanded server reveals one row per advertised tool. The full surface:
|
|
12291
12629
|
*
|
|
12292
|
-
*
|
|
12630
|
+
* ▼ [✓] linear stdio · npx -y mcp-remote … (5/7) ✓ authed
|
|
12631
|
+
* [✓] create_issue Create a new Linear issue
|
|
12632
|
+
* [✓] list_issues Query issues by status / assignee
|
|
12633
|
+
* [ ] update_issue Update fields on an existing issue
|
|
12634
|
+
* ▶ [✓] chrome-devtools stdio · npx -y chrome-devtools-mcp@latest
|
|
12293
12635
|
*
|
|
12294
12636
|
* Keybindings:
|
|
12295
12637
|
*
|
|
12296
|
-
* ↑ / ↓ / k / j
|
|
12297
|
-
*
|
|
12298
|
-
*
|
|
12299
|
-
*
|
|
12300
|
-
*
|
|
12638
|
+
* ↑ / ↓ / k / j navigate (over servers AND visible tool rows)
|
|
12639
|
+
* → / tab expand the focused server
|
|
12640
|
+
* ← / shift+tab collapse — on a tool row, also snaps the cursor
|
|
12641
|
+
* back to the parent server
|
|
12642
|
+
* enter / space toggle — server toggles `enabledMcps`; tool toggles
|
|
12643
|
+
* `disabledMcpTools[serverName]`
|
|
12644
|
+
* l login (server rows only — needs-auth / error)
|
|
12645
|
+
* o logout (server rows only — authed / error /
|
|
12646
|
+
* authorizing)
|
|
12647
|
+
* r refresh discovery
|
|
12648
|
+
* esc close — also cancels an in-flight `authorizing`
|
|
12649
|
+
* row
|
|
12301
12650
|
*
|
|
12302
|
-
* Status badge legend:
|
|
12651
|
+
* Status badge legend (server row only):
|
|
12303
12652
|
*
|
|
12304
12653
|
* ✓ authed tokens stored + bootstrap connected
|
|
12305
|
-
* ! needs login
|
|
12654
|
+
* ! needs login bootstrap needs OAuth, no tokens
|
|
12306
12655
|
* … authorizing interactive login in flight
|
|
12307
12656
|
* ✗ <error> login attempt failed
|
|
12308
12657
|
*
|
|
12309
|
-
* The
|
|
12310
|
-
*
|
|
12311
|
-
*
|
|
12658
|
+
* The (N/M) chip on a server header counts ENABLED / TOTAL advertised
|
|
12659
|
+
* tools, drawn only when the tool catalog has loaded. Without a catalog
|
|
12660
|
+
* (server never bootstrapped this profile, cache cold) the chip is
|
|
12661
|
+
* suppressed and the empty-state row under the expanded server explains
|
|
12662
|
+
* how to populate.
|
|
12312
12663
|
*
|
|
12313
12664
|
* Errors (`DiscoveryError[]`) surface in a warn-colored preamble so a
|
|
12314
12665
|
* broken `mcps.json` is loud rather than invisible.
|
|
12315
12666
|
*/
|
|
12316
|
-
function McpsSettingsModal({ catalog, errors, onLogin, onLogout, onCancelLogin, onRefresh }) {
|
|
12667
|
+
function McpsSettingsModal({ catalog, errors, toolsByServer, onLogin, onLogout, onCancelLogin, onRefresh }) {
|
|
12317
12668
|
const COLOR = useColors();
|
|
12318
12669
|
const home = homedir();
|
|
12319
12670
|
const authState = useMcpAuthState();
|
|
12320
|
-
const { enabledSet, toggle } = useEnabledToggleSet({
|
|
12671
|
+
const { enabledSet: serverEnabledSet, toggle: toggleServer } = useEnabledToggleSet({
|
|
12321
12672
|
catalog,
|
|
12322
12673
|
keyOf: (d) => d.config.name,
|
|
12323
12674
|
settingKey: "enabledMcps"
|
|
12324
12675
|
});
|
|
12676
|
+
const toolToggleMap = useMcpToolToggleMap({ catalogByServer: toolsByServer });
|
|
12677
|
+
const [expanded, setExpanded] = useState(() => /* @__PURE__ */ new Set());
|
|
12325
12678
|
const [cursor, setCursorRaw] = useState(0);
|
|
12679
|
+
const rows = useMemo(() => buildVisibleMcpRows(catalog, expanded, toolsByServer), [
|
|
12680
|
+
catalog,
|
|
12681
|
+
expanded,
|
|
12682
|
+
toolsByServer
|
|
12683
|
+
]);
|
|
12326
12684
|
const moveCursor = useCallback((delta) => setCursorRaw((prev) => {
|
|
12327
|
-
if (
|
|
12328
|
-
return ((prev + delta) %
|
|
12329
|
-
}), [
|
|
12330
|
-
const safeCursor = Math.min(cursor, Math.max(0,
|
|
12331
|
-
const
|
|
12685
|
+
if (rows.length === 0) return prev;
|
|
12686
|
+
return ((prev + delta) % rows.length + rows.length) % rows.length;
|
|
12687
|
+
}), [rows.length]);
|
|
12688
|
+
const safeCursor = Math.min(cursor, Math.max(0, rows.length - 1));
|
|
12689
|
+
const focusedRow = rows[safeCursor];
|
|
12690
|
+
const focusedServerName = focusedRow ? focusedRow.kind === "server" ? focusedRow.entry.config.name : focusedRow.serverName : void 0;
|
|
12691
|
+
const focusedEntry = focusedServerName ? catalog.find((c) => c.config.name === focusedServerName) : void 0;
|
|
12332
12692
|
const focusedStatus = focusedEntry ? getMcpAuthStatus(authState, focusedEntry.config.name) : void 0;
|
|
12333
12693
|
const inputActive = focusedStatus?.kind === "authorizing" && !!focusedStatus.url;
|
|
12694
|
+
const collapseFocused = useCallback(() => {
|
|
12695
|
+
if (!focusedRow) return;
|
|
12696
|
+
const parentName = focusedRow.kind === "server" ? focusedRow.entry.config.name : focusedRow.serverName;
|
|
12697
|
+
if (focusedRow.kind !== "server") {
|
|
12698
|
+
const parentIndex = indexOfServerRow(rows, parentName);
|
|
12699
|
+
if (parentIndex >= 0) setCursorRaw(parentIndex);
|
|
12700
|
+
}
|
|
12701
|
+
setExpanded((prev) => {
|
|
12702
|
+
if (!prev.has(parentName)) return prev;
|
|
12703
|
+
const next = new Set(prev);
|
|
12704
|
+
next.delete(parentName);
|
|
12705
|
+
return next;
|
|
12706
|
+
});
|
|
12707
|
+
}, [focusedRow, rows]);
|
|
12708
|
+
const expandFocused = useCallback(() => {
|
|
12709
|
+
if (!focusedRow || focusedRow.kind !== "server") return;
|
|
12710
|
+
setExpanded((prev) => {
|
|
12711
|
+
if (prev.has(focusedRow.entry.config.name)) return prev;
|
|
12712
|
+
const next = new Set(prev);
|
|
12713
|
+
next.add(focusedRow.entry.config.name);
|
|
12714
|
+
return next;
|
|
12715
|
+
});
|
|
12716
|
+
}, [focusedRow]);
|
|
12334
12717
|
useKeyboard((key) => {
|
|
12335
12718
|
if (key.name === "escape" && focusedEntry) {
|
|
12336
|
-
|
|
12337
|
-
|
|
12338
|
-
onCancelLogin(name);
|
|
12719
|
+
if (getMcpAuthStatus(authState, focusedEntry.config.name).kind === "authorizing") {
|
|
12720
|
+
onCancelLogin(focusedEntry.config.name);
|
|
12339
12721
|
return;
|
|
12340
12722
|
}
|
|
12341
12723
|
}
|
|
@@ -12348,21 +12730,36 @@ function McpsSettingsModal({ catalog, errors, onLogin, onLogout, onCancelLogin,
|
|
|
12348
12730
|
moveCursor(1);
|
|
12349
12731
|
return;
|
|
12350
12732
|
}
|
|
12351
|
-
if (
|
|
12352
|
-
|
|
12353
|
-
if (
|
|
12354
|
-
|
|
12355
|
-
|
|
12733
|
+
if (rows.length === 0) return;
|
|
12734
|
+
if (!focusedRow) return;
|
|
12735
|
+
if (key.name === "tab") {
|
|
12736
|
+
const row = rows[safeCursor];
|
|
12737
|
+
const parentName = row ? parentServerName(row) : void 0;
|
|
12738
|
+
if (parentName && expanded.has(parentName)) collapseFocused();
|
|
12739
|
+
else expandFocused();
|
|
12740
|
+
return;
|
|
12741
|
+
}
|
|
12742
|
+
if (key.name === "right") {
|
|
12743
|
+
expandFocused();
|
|
12744
|
+
return;
|
|
12745
|
+
}
|
|
12746
|
+
if (key.name === "left") {
|
|
12747
|
+
collapseFocused();
|
|
12748
|
+
return;
|
|
12749
|
+
}
|
|
12356
12750
|
if (key.name === "return" || key.name === "space") {
|
|
12357
|
-
|
|
12751
|
+
if (focusedRow.kind === "server") toggleServer(focusedRow.entry.config.name);
|
|
12752
|
+
else if (focusedRow.kind === "tool") toolToggleMap[focusedRow.serverName]?.toggle(focusedRow.tool.name);
|
|
12358
12753
|
return;
|
|
12359
12754
|
}
|
|
12360
|
-
if (
|
|
12361
|
-
|
|
12755
|
+
if (focusedRow.kind !== "server") return;
|
|
12756
|
+
const status = getMcpAuthStatus(authState, focusedRow.entry.config.name);
|
|
12757
|
+
if (key.name === "l" && canLogin(focusedRow.entry, status)) {
|
|
12758
|
+
onLogin(focusedRow.entry.config.name);
|
|
12362
12759
|
return;
|
|
12363
12760
|
}
|
|
12364
12761
|
if (key.name === "o" && canLogout(status)) {
|
|
12365
|
-
onLogout(name);
|
|
12762
|
+
onLogout(focusedRow.entry.config.name);
|
|
12366
12763
|
return;
|
|
12367
12764
|
}
|
|
12368
12765
|
if (key.name === "r" && onRefresh) onRefresh();
|
|
@@ -12445,46 +12842,185 @@ function McpsSettingsModal({ catalog, errors, onLogin, onLogout, onCancelLogin,
|
|
|
12445
12842
|
})
|
|
12446
12843
|
]
|
|
12447
12844
|
});
|
|
12845
|
+
const groups = [];
|
|
12846
|
+
for (let i = 0; i < rows.length; i++) {
|
|
12847
|
+
const row = rows[i];
|
|
12848
|
+
if (row.kind === "server") {
|
|
12849
|
+
groups.push({
|
|
12850
|
+
serverIndex: i,
|
|
12851
|
+
server: row,
|
|
12852
|
+
children: []
|
|
12853
|
+
});
|
|
12854
|
+
continue;
|
|
12855
|
+
}
|
|
12856
|
+
const last = groups[groups.length - 1];
|
|
12857
|
+
if (last) last.children.push({
|
|
12858
|
+
row,
|
|
12859
|
+
index: i
|
|
12860
|
+
});
|
|
12861
|
+
}
|
|
12448
12862
|
return /* @__PURE__ */ jsxs(Modal, {
|
|
12449
|
-
title: ` mcp servers · ${
|
|
12863
|
+
title: ` mcp servers · ${serverEnabledSet.size} / ${catalog.length} enabled `,
|
|
12450
12864
|
children: [
|
|
12451
12865
|
renderErrors(errors, home, COLOR.warn),
|
|
12452
12866
|
/* @__PURE__ */ jsx("box", {
|
|
12453
12867
|
style: { flexDirection: "column" },
|
|
12454
|
-
children:
|
|
12455
|
-
|
|
12456
|
-
|
|
12457
|
-
|
|
12458
|
-
|
|
12459
|
-
|
|
12460
|
-
|
|
12461
|
-
|
|
12462
|
-
|
|
12463
|
-
|
|
12464
|
-
|
|
12465
|
-
|
|
12466
|
-
|
|
12467
|
-
|
|
12468
|
-
|
|
12469
|
-
|
|
12470
|
-
|
|
12471
|
-
|
|
12472
|
-
|
|
12473
|
-
|
|
12474
|
-
|
|
12475
|
-
|
|
12476
|
-
children: [" ", detailFor(entry)]
|
|
12477
|
-
}),
|
|
12478
|
-
renderInlineBadge(status, COLOR)
|
|
12479
|
-
]
|
|
12480
|
-
}, name);
|
|
12481
|
-
})
|
|
12868
|
+
children: groups.map((group) => /* @__PURE__ */ jsxs("box", {
|
|
12869
|
+
style: { flexDirection: "column" },
|
|
12870
|
+
children: [renderRow({
|
|
12871
|
+
row: group.server,
|
|
12872
|
+
focused: group.serverIndex === safeCursor,
|
|
12873
|
+
serverEnabledSet,
|
|
12874
|
+
toolEnabledSet: toolToggleMap[group.server.entry.config.name]?.enabledSet,
|
|
12875
|
+
toolCatalog: toolsByServer,
|
|
12876
|
+
authState,
|
|
12877
|
+
expanded,
|
|
12878
|
+
COLOR
|
|
12879
|
+
}), group.children.map(({ row, index }) => renderRow({
|
|
12880
|
+
row,
|
|
12881
|
+
focused: index === safeCursor,
|
|
12882
|
+
serverEnabledSet,
|
|
12883
|
+
toolEnabledSet: toolToggleMap[parentServerName(row)]?.enabledSet,
|
|
12884
|
+
toolCatalog: toolsByServer,
|
|
12885
|
+
authState,
|
|
12886
|
+
expanded,
|
|
12887
|
+
COLOR
|
|
12888
|
+
}))]
|
|
12889
|
+
}, `group:${group.server.entry.config.name}`))
|
|
12482
12890
|
}),
|
|
12483
12891
|
focusedEntry && focusedStatus && renderDetailPanel(focusedEntry, focusedStatus, COLOR),
|
|
12484
|
-
renderActionHints(
|
|
12892
|
+
renderActionHints({
|
|
12893
|
+
focusedRow,
|
|
12894
|
+
focusedEntry,
|
|
12895
|
+
focusedStatus,
|
|
12896
|
+
expanded,
|
|
12897
|
+
showRefresh: !!onRefresh,
|
|
12898
|
+
COLOR
|
|
12899
|
+
})
|
|
12485
12900
|
]
|
|
12486
12901
|
});
|
|
12487
12902
|
}
|
|
12903
|
+
function renderRow({ row, focused, serverEnabledSet, toolEnabledSet, toolCatalog, authState, expanded, COLOR }) {
|
|
12904
|
+
if (row.kind === "server") return renderServerRow({
|
|
12905
|
+
entry: row.entry,
|
|
12906
|
+
focused,
|
|
12907
|
+
serverEnabledSet,
|
|
12908
|
+
toolEnabledSet,
|
|
12909
|
+
toolCatalog,
|
|
12910
|
+
authState,
|
|
12911
|
+
expanded,
|
|
12912
|
+
COLOR
|
|
12913
|
+
});
|
|
12914
|
+
if (row.kind === "tool") return renderToolRow({
|
|
12915
|
+
row,
|
|
12916
|
+
focused,
|
|
12917
|
+
toolEnabledSet,
|
|
12918
|
+
COLOR
|
|
12919
|
+
});
|
|
12920
|
+
return renderEmptyToolsRow({
|
|
12921
|
+
row,
|
|
12922
|
+
focused,
|
|
12923
|
+
COLOR
|
|
12924
|
+
});
|
|
12925
|
+
}
|
|
12926
|
+
function renderServerRow({ entry, focused, serverEnabledSet, toolEnabledSet, toolCatalog, authState, expanded, COLOR }) {
|
|
12927
|
+
const name = entry.config.name;
|
|
12928
|
+
const enabled = serverEnabledSet.has(name);
|
|
12929
|
+
const status = getMcpAuthStatus(authState, name);
|
|
12930
|
+
const cached = toolCatalog[name];
|
|
12931
|
+
const chevron = expanded.has(name) ? "▼" : "◀";
|
|
12932
|
+
const toolCountChip = cached && toolEnabledSet ? ` (${toolEnabledSet.size}/${cached.length})` : "";
|
|
12933
|
+
return /* @__PURE__ */ jsxs("box", {
|
|
12934
|
+
style: {
|
|
12935
|
+
flexDirection: "row",
|
|
12936
|
+
height: 1,
|
|
12937
|
+
paddingRight: 1
|
|
12938
|
+
},
|
|
12939
|
+
children: [
|
|
12940
|
+
/* @__PURE__ */ jsxs("text", {
|
|
12941
|
+
wrapMode: "none",
|
|
12942
|
+
fg: focused ? COLOR.brand : COLOR.dim,
|
|
12943
|
+
children: [
|
|
12944
|
+
/* @__PURE__ */ jsx("span", {
|
|
12945
|
+
fg: focused ? COLOR.brand : COLOR.mute,
|
|
12946
|
+
children: focused ? "▶ " : " "
|
|
12947
|
+
}),
|
|
12948
|
+
/* @__PURE__ */ jsx("span", {
|
|
12949
|
+
fg: enabled ? COLOR.accent : COLOR.mute,
|
|
12950
|
+
children: enabled ? "[✓] " : "[ ] "
|
|
12951
|
+
}),
|
|
12952
|
+
/* @__PURE__ */ jsx("span", {
|
|
12953
|
+
fg: focused ? COLOR.brand : COLOR.dim,
|
|
12954
|
+
children: name
|
|
12955
|
+
}),
|
|
12956
|
+
/* @__PURE__ */ jsxs("span", {
|
|
12957
|
+
fg: COLOR.mute,
|
|
12958
|
+
children: [" ", detailFor(entry)]
|
|
12959
|
+
}),
|
|
12960
|
+
toolCountChip && /* @__PURE__ */ jsx("span", {
|
|
12961
|
+
fg: COLOR.mute,
|
|
12962
|
+
children: toolCountChip
|
|
12963
|
+
}),
|
|
12964
|
+
renderInlineBadge(status, COLOR)
|
|
12965
|
+
]
|
|
12966
|
+
}),
|
|
12967
|
+
/* @__PURE__ */ jsx("box", { style: { flexGrow: 1 } }),
|
|
12968
|
+
/* @__PURE__ */ jsx("text", {
|
|
12969
|
+
wrapMode: "none",
|
|
12970
|
+
children: /* @__PURE__ */ jsx("span", {
|
|
12971
|
+
fg: focused ? COLOR.brand : COLOR.mute,
|
|
12972
|
+
children: chevron
|
|
12973
|
+
})
|
|
12974
|
+
})
|
|
12975
|
+
]
|
|
12976
|
+
}, `server:${name}`);
|
|
12977
|
+
}
|
|
12978
|
+
const TOOL_ROW_INDENT = " ";
|
|
12979
|
+
const TOOL_ROW_FOCUS_INDENT = " ▶ ";
|
|
12980
|
+
function renderToolRow({ row, focused, toolEnabledSet, COLOR }) {
|
|
12981
|
+
const enabled = toolEnabledSet?.has(row.tool.name) ?? true;
|
|
12982
|
+
const description = trimToWidth(row.tool.description ?? "", 60);
|
|
12983
|
+
return /* @__PURE__ */ jsxs("text", {
|
|
12984
|
+
fg: focused ? COLOR.brand : COLOR.dim,
|
|
12985
|
+
children: [
|
|
12986
|
+
/* @__PURE__ */ jsx("span", {
|
|
12987
|
+
fg: focused ? COLOR.brand : COLOR.mute,
|
|
12988
|
+
children: focused ? TOOL_ROW_FOCUS_INDENT : TOOL_ROW_INDENT
|
|
12989
|
+
}),
|
|
12990
|
+
/* @__PURE__ */ jsx("span", {
|
|
12991
|
+
fg: enabled ? COLOR.accent : COLOR.mute,
|
|
12992
|
+
children: enabled ? "[✓] " : "[ ] "
|
|
12993
|
+
}),
|
|
12994
|
+
/* @__PURE__ */ jsx("span", {
|
|
12995
|
+
fg: focused ? COLOR.brand : COLOR.dim,
|
|
12996
|
+
children: row.tool.name
|
|
12997
|
+
}),
|
|
12998
|
+
description && /* @__PURE__ */ jsxs("span", {
|
|
12999
|
+
fg: COLOR.mute,
|
|
13000
|
+
children: [" ", description]
|
|
13001
|
+
})
|
|
13002
|
+
]
|
|
13003
|
+
}, `tool:${row.serverName}/${row.tool.name}`);
|
|
13004
|
+
}
|
|
13005
|
+
function renderEmptyToolsRow({ row, focused, COLOR }) {
|
|
13006
|
+
return /* @__PURE__ */ jsxs("text", { children: [/* @__PURE__ */ jsx("span", {
|
|
13007
|
+
fg: focused ? COLOR.brand : COLOR.mute,
|
|
13008
|
+
children: focused ? TOOL_ROW_FOCUS_INDENT : TOOL_ROW_INDENT
|
|
13009
|
+
}), /* @__PURE__ */ jsx("span", {
|
|
13010
|
+
fg: COLOR.mute,
|
|
13011
|
+
children: "⟲ No cached tools yet. Send any prompt with this server enabled to populate."
|
|
13012
|
+
})] }, `tool-empty:${row.serverName}`);
|
|
13013
|
+
}
|
|
13014
|
+
/**
|
|
13015
|
+
* Cap a single-line description at `max` columns, suffixing `…` if it
|
|
13016
|
+
* overflows. Renderer uses one column per char; surrogate-pair safety
|
|
13017
|
+
* isn't a concern here because tool descriptions are ASCII in practice.
|
|
13018
|
+
*/
|
|
13019
|
+
function trimToWidth(s, max) {
|
|
13020
|
+
const clean = s.replace(/\s+/g, " ").trim();
|
|
13021
|
+
if (clean.length <= max) return clean;
|
|
13022
|
+
return `${clean.slice(0, Math.max(0, max - 1))}…`;
|
|
13023
|
+
}
|
|
12488
13024
|
function detailFor(entry) {
|
|
12489
13025
|
const transport = entry.config.transport;
|
|
12490
13026
|
return `${transport} · ${transport === "stdio" ? entry.config.command ?? "" : entry.config.url ?? ""}`;
|
|
@@ -12590,11 +13126,14 @@ function renderDetailPanel(entry, status, COLOR) {
|
|
|
12590
13126
|
});
|
|
12591
13127
|
return null;
|
|
12592
13128
|
}
|
|
12593
|
-
function renderActionHints(
|
|
12594
|
-
const effectiveStatus =
|
|
12595
|
-
const
|
|
12596
|
-
const
|
|
13129
|
+
function renderActionHints({ focusedRow, focusedEntry, focusedStatus, expanded, showRefresh, COLOR }) {
|
|
13130
|
+
const effectiveStatus = focusedStatus ?? { kind: "idle" };
|
|
13131
|
+
const onServerRow = focusedRow?.kind === "server";
|
|
13132
|
+
const canL = onServerRow && focusedEntry ? canLogin(focusedEntry, effectiveStatus) : false;
|
|
13133
|
+
const canO = onServerRow ? canLogout(effectiveStatus) : false;
|
|
12597
13134
|
const canCancel = effectiveStatus.kind === "authorizing";
|
|
13135
|
+
const canExpand = onServerRow && focusedEntry ? !expanded.has(focusedEntry.config.name) : false;
|
|
13136
|
+
const canCollapse = focusedRow?.kind === "tool" || (onServerRow && focusedEntry ? expanded.has(focusedEntry.config.name) : false);
|
|
12598
13137
|
return /* @__PURE__ */ jsxs("text", {
|
|
12599
13138
|
fg: COLOR.mute,
|
|
12600
13139
|
children: [
|
|
@@ -12608,6 +13147,22 @@ function renderActionHints(entry, status, showRefresh, COLOR) {
|
|
|
12608
13147
|
children: "↵"
|
|
12609
13148
|
}),
|
|
12610
13149
|
" toggle",
|
|
13150
|
+
canExpand && /* @__PURE__ */ jsxs("span", { children: [
|
|
13151
|
+
" · ",
|
|
13152
|
+
/* @__PURE__ */ jsx("span", {
|
|
13153
|
+
fg: COLOR.warn,
|
|
13154
|
+
children: "→"
|
|
13155
|
+
}),
|
|
13156
|
+
" expand"
|
|
13157
|
+
] }),
|
|
13158
|
+
canCollapse && /* @__PURE__ */ jsxs("span", { children: [
|
|
13159
|
+
" · ",
|
|
13160
|
+
/* @__PURE__ */ jsx("span", {
|
|
13161
|
+
fg: COLOR.warn,
|
|
13162
|
+
children: "←"
|
|
13163
|
+
}),
|
|
13164
|
+
" collapse"
|
|
13165
|
+
] }),
|
|
12611
13166
|
canL && /* @__PURE__ */ jsxs("span", { children: [
|
|
12612
13167
|
" · ",
|
|
12613
13168
|
/* @__PURE__ */ jsx("span", {
|