zidane 5.6.5 → 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-DKpXHp1c.d.ts → index-Cf-131kQ.d.ts} +25 -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-YckkS-Bq.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-ZT3TJDEb.js → presets-DAA0NaK_.js} +2 -2
- package/dist/{presets-ZT3TJDEb.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-Bgx8OBqK.js → tools-B9aQpZVx.js} +77 -12
- 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-CGA0h4AT.d.ts → transcript-anchors-CoSZb1PE.d.ts} +65 -9
- 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 +716 -151
- package/dist/tui.js.map +1 -1
- package/dist/{turn-operations-b2ICQ1Y5.js → turn-operations-D2rHrTQv.js} +410 -20
- package/dist/turn-operations-D2rHrTQv.js.map +1 -0
- package/dist/types.d.ts +2 -2
- package/docs/CHAT.md +11 -8
- package/docs/SKILL.md +1 -0
- package/docs/TUI.md +2 -1
- package/package.json +1 -1
- package/dist/agent-D70rr6Uk.d.ts.map +0 -1
- package/dist/index-DKpXHp1c.d.ts.map +0 -1
- package/dist/login-YckkS-Bq.js.map +0 -1
- package/dist/mcp-ngMS0S6N.js.map +0 -1
- package/dist/messages-B5k4DAXy.js.map +0 -1
- package/dist/tools-Bgx8OBqK.js.map +0 -1
- package/dist/transcript-anchors-CGA0h4AT.d.ts.map +0 -1
- package/dist/turn-operations-b2ICQ1Y5.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
|
|
@@ -2017,7 +2046,7 @@ MarkdownBlock.displayName = "MarkdownBlock";
|
|
|
2017
2046
|
const TOOL_RESULT_MAX_LINES = 6;
|
|
2018
2047
|
function ToolResultBlock({ text, indent }) {
|
|
2019
2048
|
const COLOR = useColors();
|
|
2020
|
-
const rawLines = text.split("\n");
|
|
2049
|
+
const rawLines = stripAnsiSequences(text).split("\n");
|
|
2021
2050
|
let end = rawLines.length;
|
|
2022
2051
|
while (end > 0 && rawLines[end - 1].trim() === "") end--;
|
|
2023
2052
|
if (end === 0) return null;
|
|
@@ -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", {
|
|
@@ -8508,7 +8705,12 @@ function McpsList({ items, enabledSet, totalCount, cursor, highlightBg, query, e
|
|
|
8508
8705
|
fg: COLOR.model,
|
|
8509
8706
|
children: " mcps.json "
|
|
8510
8707
|
}),
|
|
8511
|
-
"
|
|
8708
|
+
"(or",
|
|
8709
|
+
/* @__PURE__ */ jsx("span", {
|
|
8710
|
+
fg: COLOR.model,
|
|
8711
|
+
children: " mcp.json "
|
|
8712
|
+
}),
|
|
8713
|
+
") into",
|
|
8512
8714
|
/* @__PURE__ */ jsx("span", {
|
|
8513
8715
|
fg: COLOR.model,
|
|
8514
8716
|
children: " .zidane/ "
|
|
@@ -8528,53 +8730,150 @@ function McpsList({ items, enabledSet, totalCount, cursor, highlightBg, query, e
|
|
|
8528
8730
|
})
|
|
8529
8731
|
]
|
|
8530
8732
|
});
|
|
8531
|
-
if (
|
|
8733
|
+
if (filteredCount === 0) return /* @__PURE__ */ jsxs("box", {
|
|
8532
8734
|
style: { flexDirection: "column" },
|
|
8533
8735
|
children: [renderMcpErrors(errors, home, COLOR.warn), /* @__PURE__ */ jsx(EmptyRow, { label: `no servers match "${query}"` })]
|
|
8534
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
|
+
}
|
|
8535
8754
|
return /* @__PURE__ */ jsxs("box", {
|
|
8536
8755
|
style: { flexDirection: "column" },
|
|
8537
|
-
children: [renderMcpErrors(errors, home, COLOR.warn),
|
|
8538
|
-
|
|
8539
|
-
|
|
8540
|
-
|
|
8541
|
-
|
|
8542
|
-
|
|
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
|
-
}, name);
|
|
8575
|
-
})]
|
|
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}`))]
|
|
8576
8792
|
});
|
|
8577
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
|
+
}
|
|
8578
8877
|
function KeybindingsList({ bindings, query }) {
|
|
8579
8878
|
const sections = useMemo(() => groupBindings(bindings), [bindings]);
|
|
8580
8879
|
const filteredSections = useMemo(() => {
|
|
@@ -8932,21 +9231,21 @@ function renderMcpDetailPanel(entry, status, COLOR) {
|
|
|
8932
9231
|
function renderInlineMcpBadge(status, COLOR) {
|
|
8933
9232
|
switch (status.kind) {
|
|
8934
9233
|
case "idle": return null;
|
|
8935
|
-
case "authed": return /* @__PURE__ */
|
|
9234
|
+
case "authed": return /* @__PURE__ */ jsx("span", {
|
|
8936
9235
|
fg: COLOR.accent,
|
|
8937
|
-
children:
|
|
9236
|
+
children: " · ✓ authed"
|
|
8938
9237
|
});
|
|
8939
|
-
case "needs-auth": return /* @__PURE__ */
|
|
9238
|
+
case "needs-auth": return /* @__PURE__ */ jsx("span", {
|
|
8940
9239
|
fg: COLOR.warn,
|
|
8941
|
-
children:
|
|
9240
|
+
children: " · ! needs login"
|
|
8942
9241
|
});
|
|
8943
|
-
case "authorizing": return /* @__PURE__ */
|
|
9242
|
+
case "authorizing": return /* @__PURE__ */ jsx("span", {
|
|
8944
9243
|
fg: COLOR.warn,
|
|
8945
|
-
children:
|
|
9244
|
+
children: " · … authorizing"
|
|
8946
9245
|
});
|
|
8947
|
-
case "error": return /* @__PURE__ */
|
|
9246
|
+
case "error": return /* @__PURE__ */ jsx("span", {
|
|
8948
9247
|
fg: COLOR.error,
|
|
8949
|
-
children:
|
|
9248
|
+
children: " · ✗ login failed"
|
|
8950
9249
|
});
|
|
8951
9250
|
}
|
|
8952
9251
|
}
|
|
@@ -8960,10 +9259,11 @@ function renderMcpErrors(errors, home, warnColor) {
|
|
|
8960
9259
|
}, err.path))
|
|
8961
9260
|
});
|
|
8962
9261
|
}
|
|
8963
|
-
function Hints({ activeTab, focusedMcp, focusedMcpStatus, canRefreshSkills, canRefreshMcps }) {
|
|
9262
|
+
function Hints({ activeTab, focusedMcp, focusedMcpRow, focusedMcpStatus, canRefreshSkills, canRefreshMcps }) {
|
|
8964
9263
|
const COLOR = useColors();
|
|
8965
|
-
const
|
|
8966
|
-
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);
|
|
8967
9267
|
const showCancel = activeTab === "mcps" && focusedMcpStatus?.kind === "authorizing";
|
|
8968
9268
|
const showRefresh = activeTab === "skills" && canRefreshSkills || activeTab === "mcps" && canRefreshMcps;
|
|
8969
9269
|
return /* @__PURE__ */ jsxs("text", {
|
|
@@ -8984,6 +9284,14 @@ function Hints({ activeTab, focusedMcp, focusedMcpStatus, canRefreshSkills, canR
|
|
|
8984
9284
|
children: "↵"
|
|
8985
9285
|
}),
|
|
8986
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
|
+
] }),
|
|
8987
9295
|
showLogin && /* @__PURE__ */ jsxs("span", { children: [
|
|
8988
9296
|
" · ",
|
|
8989
9297
|
/* @__PURE__ */ jsx("span", {
|
|
@@ -9039,13 +9347,15 @@ function generalCorpus(item) {
|
|
|
9039
9347
|
function skillCorpus(s) {
|
|
9040
9348
|
return `${s.name} ${s.description ?? ""}`.toLowerCase();
|
|
9041
9349
|
}
|
|
9042
|
-
function mcpCorpus(m) {
|
|
9350
|
+
function mcpCorpus(m, toolsByServer) {
|
|
9351
|
+
const toolCorpus = (toolsByServer[m.config.name] ?? []).map((t) => `${t.name} ${t.description ?? ""}`).join(" ");
|
|
9043
9352
|
return [
|
|
9044
9353
|
m.config.name,
|
|
9045
9354
|
m.config.transport,
|
|
9046
9355
|
m.config.command ?? "",
|
|
9047
9356
|
m.config.url ?? "",
|
|
9048
|
-
(m.config.args ?? []).join(" ")
|
|
9357
|
+
(m.config.args ?? []).join(" "),
|
|
9358
|
+
toolCorpus
|
|
9049
9359
|
].join(" ").toLowerCase();
|
|
9050
9360
|
}
|
|
9051
9361
|
function keybindingCorpus(row) {
|
|
@@ -9892,6 +10202,8 @@ function AppShell() {
|
|
|
9892
10202
|
mcpsCatalogRef.current = mcpsCatalog;
|
|
9893
10203
|
const enabledMcpsRef = useRef(settings.enabledMcps);
|
|
9894
10204
|
enabledMcpsRef.current = settings.enabledMcps;
|
|
10205
|
+
const disabledMcpToolsRef = useRef(settings.disabledMcpTools);
|
|
10206
|
+
disabledMcpToolsRef.current = settings.disabledMcpTools;
|
|
9895
10207
|
const filesCatalogRef = useRef(filesCatalog);
|
|
9896
10208
|
filesCatalogRef.current = filesCatalog;
|
|
9897
10209
|
const ensureFilesCatalogRef = useRef(ensureFilesCatalog);
|
|
@@ -10229,7 +10541,8 @@ function AppShell() {
|
|
|
10229
10541
|
});
|
|
10230
10542
|
const projectMcps = buildMcpServers({
|
|
10231
10543
|
discovered: mcpsCatalogRef.current,
|
|
10232
|
-
enabled: enabledMcpsRef.current
|
|
10544
|
+
enabled: enabledMcpsRef.current,
|
|
10545
|
+
...disabledMcpToolsRef.current ? { disabledTools: disabledMcpToolsRef.current } : {}
|
|
10233
10546
|
});
|
|
10234
10547
|
const allowInteraction = allowInteractionRef.current !== false;
|
|
10235
10548
|
const interactionTools = allowInteraction ? createInteractionTools({ requestInteraction: makeRequestInteraction(interactions) }) : {};
|
|
@@ -10394,6 +10707,15 @@ function AppShell() {
|
|
|
10394
10707
|
reason
|
|
10395
10708
|
});
|
|
10396
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
|
+
});
|
|
10397
10719
|
agent.hooks.hook("mcp:auth:url", ({ name, url }) => {
|
|
10398
10720
|
dispatchAuthRef.current({
|
|
10399
10721
|
type: "auth-url",
|
|
@@ -10629,6 +10951,7 @@ function AppShell() {
|
|
|
10629
10951
|
agent.hooks.hook("agent:done", () => {
|
|
10630
10952
|
pendingAnnotationsRef.current.clear();
|
|
10631
10953
|
});
|
|
10954
|
+
subscribeMcpToolsCache(agent.hooks, { dataDir });
|
|
10632
10955
|
return agent;
|
|
10633
10956
|
}, [
|
|
10634
10957
|
providerRegistry,
|
|
@@ -11306,6 +11629,24 @@ function AppShell() {
|
|
|
11306
11629
|
const onCancelLoginMcp = useCallback((name) => {
|
|
11307
11630
|
mcpLoginAbortsRef.current.get(name)?.abort();
|
|
11308
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]);
|
|
11309
11650
|
const onOpenKeybindingsFile = useCallback(() => {
|
|
11310
11651
|
modal.close();
|
|
11311
11652
|
(async () => {
|
|
@@ -11812,7 +12153,8 @@ function AppShell() {
|
|
|
11812
12153
|
onLogoutMcp,
|
|
11813
12154
|
onCancelLoginMcp,
|
|
11814
12155
|
onRefreshSkills,
|
|
11815
|
-
onRefreshMcps
|
|
12156
|
+
onRefreshMcps,
|
|
12157
|
+
onRefreshMcpTools
|
|
11816
12158
|
}
|
|
11817
12159
|
}));
|
|
11818
12160
|
return;
|
|
@@ -12282,55 +12624,100 @@ function initTreeSitterWorker() {
|
|
|
12282
12624
|
//#endregion
|
|
12283
12625
|
//#region src/tui/mcps-settings.tsx
|
|
12284
12626
|
/**
|
|
12285
|
-
* 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:
|
|
12286
12629
|
*
|
|
12287
|
-
*
|
|
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
|
|
12288
12635
|
*
|
|
12289
12636
|
* Keybindings:
|
|
12290
12637
|
*
|
|
12291
|
-
* ↑ / ↓ / k / j
|
|
12292
|
-
*
|
|
12293
|
-
*
|
|
12294
|
-
*
|
|
12295
|
-
*
|
|
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
|
|
12296
12650
|
*
|
|
12297
|
-
* Status badge legend:
|
|
12651
|
+
* Status badge legend (server row only):
|
|
12298
12652
|
*
|
|
12299
12653
|
* ✓ authed tokens stored + bootstrap connected
|
|
12300
|
-
* ! needs login
|
|
12654
|
+
* ! needs login bootstrap needs OAuth, no tokens
|
|
12301
12655
|
* … authorizing interactive login in flight
|
|
12302
12656
|
* ✗ <error> login attempt failed
|
|
12303
12657
|
*
|
|
12304
|
-
* The
|
|
12305
|
-
*
|
|
12306
|
-
*
|
|
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.
|
|
12307
12663
|
*
|
|
12308
12664
|
* Errors (`DiscoveryError[]`) surface in a warn-colored preamble so a
|
|
12309
12665
|
* broken `mcps.json` is loud rather than invisible.
|
|
12310
12666
|
*/
|
|
12311
|
-
function McpsSettingsModal({ catalog, errors, onLogin, onLogout, onCancelLogin, onRefresh }) {
|
|
12667
|
+
function McpsSettingsModal({ catalog, errors, toolsByServer, onLogin, onLogout, onCancelLogin, onRefresh }) {
|
|
12312
12668
|
const COLOR = useColors();
|
|
12313
12669
|
const home = homedir();
|
|
12314
12670
|
const authState = useMcpAuthState();
|
|
12315
|
-
const { enabledSet, toggle } = useEnabledToggleSet({
|
|
12671
|
+
const { enabledSet: serverEnabledSet, toggle: toggleServer } = useEnabledToggleSet({
|
|
12316
12672
|
catalog,
|
|
12317
12673
|
keyOf: (d) => d.config.name,
|
|
12318
12674
|
settingKey: "enabledMcps"
|
|
12319
12675
|
});
|
|
12676
|
+
const toolToggleMap = useMcpToolToggleMap({ catalogByServer: toolsByServer });
|
|
12677
|
+
const [expanded, setExpanded] = useState(() => /* @__PURE__ */ new Set());
|
|
12320
12678
|
const [cursor, setCursorRaw] = useState(0);
|
|
12679
|
+
const rows = useMemo(() => buildVisibleMcpRows(catalog, expanded, toolsByServer), [
|
|
12680
|
+
catalog,
|
|
12681
|
+
expanded,
|
|
12682
|
+
toolsByServer
|
|
12683
|
+
]);
|
|
12321
12684
|
const moveCursor = useCallback((delta) => setCursorRaw((prev) => {
|
|
12322
|
-
if (
|
|
12323
|
-
return ((prev + delta) %
|
|
12324
|
-
}), [
|
|
12325
|
-
const safeCursor = Math.min(cursor, Math.max(0,
|
|
12326
|
-
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;
|
|
12327
12692
|
const focusedStatus = focusedEntry ? getMcpAuthStatus(authState, focusedEntry.config.name) : void 0;
|
|
12328
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]);
|
|
12329
12717
|
useKeyboard((key) => {
|
|
12330
12718
|
if (key.name === "escape" && focusedEntry) {
|
|
12331
|
-
|
|
12332
|
-
|
|
12333
|
-
onCancelLogin(name);
|
|
12719
|
+
if (getMcpAuthStatus(authState, focusedEntry.config.name).kind === "authorizing") {
|
|
12720
|
+
onCancelLogin(focusedEntry.config.name);
|
|
12334
12721
|
return;
|
|
12335
12722
|
}
|
|
12336
12723
|
}
|
|
@@ -12343,21 +12730,36 @@ function McpsSettingsModal({ catalog, errors, onLogin, onLogout, onCancelLogin,
|
|
|
12343
12730
|
moveCursor(1);
|
|
12344
12731
|
return;
|
|
12345
12732
|
}
|
|
12346
|
-
if (
|
|
12347
|
-
|
|
12348
|
-
if (
|
|
12349
|
-
|
|
12350
|
-
|
|
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
|
+
}
|
|
12351
12750
|
if (key.name === "return" || key.name === "space") {
|
|
12352
|
-
|
|
12751
|
+
if (focusedRow.kind === "server") toggleServer(focusedRow.entry.config.name);
|
|
12752
|
+
else if (focusedRow.kind === "tool") toolToggleMap[focusedRow.serverName]?.toggle(focusedRow.tool.name);
|
|
12353
12753
|
return;
|
|
12354
12754
|
}
|
|
12355
|
-
if (
|
|
12356
|
-
|
|
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);
|
|
12357
12759
|
return;
|
|
12358
12760
|
}
|
|
12359
12761
|
if (key.name === "o" && canLogout(status)) {
|
|
12360
|
-
onLogout(name);
|
|
12762
|
+
onLogout(focusedRow.entry.config.name);
|
|
12361
12763
|
return;
|
|
12362
12764
|
}
|
|
12363
12765
|
if (key.name === "r" && onRefresh) onRefresh();
|
|
@@ -12378,7 +12780,12 @@ function McpsSettingsModal({ catalog, errors, onLogin, onLogout, onCancelLogin,
|
|
|
12378
12780
|
fg: COLOR.model,
|
|
12379
12781
|
children: " mcps.json "
|
|
12380
12782
|
}),
|
|
12381
|
-
"
|
|
12783
|
+
"(or",
|
|
12784
|
+
/* @__PURE__ */ jsx("span", {
|
|
12785
|
+
fg: COLOR.model,
|
|
12786
|
+
children: " mcp.json "
|
|
12787
|
+
}),
|
|
12788
|
+
") into",
|
|
12382
12789
|
/* @__PURE__ */ jsx("span", {
|
|
12383
12790
|
fg: COLOR.model,
|
|
12384
12791
|
children: " .zidane/ "
|
|
@@ -12435,46 +12842,185 @@ function McpsSettingsModal({ catalog, errors, onLogin, onLogout, onCancelLogin,
|
|
|
12435
12842
|
})
|
|
12436
12843
|
]
|
|
12437
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
|
+
}
|
|
12438
12862
|
return /* @__PURE__ */ jsxs(Modal, {
|
|
12439
|
-
title: ` mcp servers · ${
|
|
12863
|
+
title: ` mcp servers · ${serverEnabledSet.size} / ${catalog.length} enabled `,
|
|
12440
12864
|
children: [
|
|
12441
12865
|
renderErrors(errors, home, COLOR.warn),
|
|
12442
12866
|
/* @__PURE__ */ jsx("box", {
|
|
12443
12867
|
style: { flexDirection: "column" },
|
|
12444
|
-
children:
|
|
12445
|
-
|
|
12446
|
-
|
|
12447
|
-
|
|
12448
|
-
|
|
12449
|
-
|
|
12450
|
-
|
|
12451
|
-
|
|
12452
|
-
|
|
12453
|
-
|
|
12454
|
-
|
|
12455
|
-
|
|
12456
|
-
|
|
12457
|
-
|
|
12458
|
-
|
|
12459
|
-
|
|
12460
|
-
|
|
12461
|
-
|
|
12462
|
-
|
|
12463
|
-
|
|
12464
|
-
|
|
12465
|
-
|
|
12466
|
-
children: [" ", detailFor(entry)]
|
|
12467
|
-
}),
|
|
12468
|
-
renderInlineBadge(status, COLOR)
|
|
12469
|
-
]
|
|
12470
|
-
}, name);
|
|
12471
|
-
})
|
|
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}`))
|
|
12472
12890
|
}),
|
|
12473
12891
|
focusedEntry && focusedStatus && renderDetailPanel(focusedEntry, focusedStatus, COLOR),
|
|
12474
|
-
renderActionHints(
|
|
12892
|
+
renderActionHints({
|
|
12893
|
+
focusedRow,
|
|
12894
|
+
focusedEntry,
|
|
12895
|
+
focusedStatus,
|
|
12896
|
+
expanded,
|
|
12897
|
+
showRefresh: !!onRefresh,
|
|
12898
|
+
COLOR
|
|
12899
|
+
})
|
|
12475
12900
|
]
|
|
12476
12901
|
});
|
|
12477
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
|
+
}
|
|
12478
13024
|
function detailFor(entry) {
|
|
12479
13025
|
const transport = entry.config.transport;
|
|
12480
13026
|
return `${transport} · ${transport === "stdio" ? entry.config.command ?? "" : entry.config.url ?? ""}`;
|
|
@@ -12580,11 +13126,14 @@ function renderDetailPanel(entry, status, COLOR) {
|
|
|
12580
13126
|
});
|
|
12581
13127
|
return null;
|
|
12582
13128
|
}
|
|
12583
|
-
function renderActionHints(
|
|
12584
|
-
const effectiveStatus =
|
|
12585
|
-
const
|
|
12586
|
-
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;
|
|
12587
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);
|
|
12588
13137
|
return /* @__PURE__ */ jsxs("text", {
|
|
12589
13138
|
fg: COLOR.mute,
|
|
12590
13139
|
children: [
|
|
@@ -12598,6 +13147,22 @@ function renderActionHints(entry, status, showRefresh, COLOR) {
|
|
|
12598
13147
|
children: "↵"
|
|
12599
13148
|
}),
|
|
12600
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
|
+
] }),
|
|
12601
13166
|
canL && /* @__PURE__ */ jsxs("span", { children: [
|
|
12602
13167
|
" · ",
|
|
12603
13168
|
/* @__PURE__ */ jsx("span", {
|