claudeup 0.3.0 → 0.6.0
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/package.json +23 -12
- package/dist/data/cli-tools.d.ts +0 -13
- package/dist/data/cli-tools.d.ts.map +0 -1
- package/dist/data/cli-tools.js +0 -113
- package/dist/data/cli-tools.js.map +0 -1
- package/dist/data/marketplaces.d.ts +0 -4
- package/dist/data/marketplaces.d.ts.map +0 -1
- package/dist/data/marketplaces.js +0 -26
- package/dist/data/marketplaces.js.map +0 -1
- package/dist/data/mcp-servers.d.ts +0 -8
- package/dist/data/mcp-servers.d.ts.map +0 -1
- package/dist/data/mcp-servers.js +0 -421
- package/dist/data/mcp-servers.js.map +0 -1
- package/dist/data/statuslines.d.ts +0 -10
- package/dist/data/statuslines.d.ts.map +0 -1
- package/dist/data/statuslines.js +0 -160
- package/dist/data/statuslines.js.map +0 -1
- package/dist/index.d.ts +0 -3
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/services/claude-settings.d.ts +0 -46
- package/dist/services/claude-settings.d.ts.map +0 -1
- package/dist/services/claude-settings.js +0 -248
- package/dist/services/claude-settings.js.map +0 -1
- package/dist/services/mcp-registry.d.ts +0 -10
- package/dist/services/mcp-registry.d.ts.map +0 -1
- package/dist/services/mcp-registry.js +0 -88
- package/dist/services/mcp-registry.js.map +0 -1
- package/dist/services/plugin-manager.d.ts +0 -23
- package/dist/services/plugin-manager.d.ts.map +0 -1
- package/dist/services/plugin-manager.js +0 -151
- package/dist/services/plugin-manager.js.map +0 -1
- package/dist/types/index.d.ts +0 -83
- package/dist/types/index.d.ts.map +0 -1
- package/dist/types/index.js +0 -2
- package/dist/types/index.js.map +0 -1
- package/dist/ui/app.d.ts +0 -38
- package/dist/ui/app.d.ts.map +0 -1
- package/dist/ui/app.js +0 -553
- package/dist/ui/app.js.map +0 -1
- package/dist/ui/screens/cli-tools.d.ts +0 -4
- package/dist/ui/screens/cli-tools.d.ts.map +0 -1
- package/dist/ui/screens/cli-tools.js +0 -331
- package/dist/ui/screens/cli-tools.js.map +0 -1
- package/dist/ui/screens/main-menu.d.ts +0 -3
- package/dist/ui/screens/main-menu.d.ts.map +0 -1
- package/dist/ui/screens/main-menu.js +0 -110
- package/dist/ui/screens/main-menu.js.map +0 -1
- package/dist/ui/screens/marketplace.d.ts +0 -3
- package/dist/ui/screens/marketplace.d.ts.map +0 -1
- package/dist/ui/screens/marketplace.js +0 -132
- package/dist/ui/screens/marketplace.js.map +0 -1
- package/dist/ui/screens/mcp-registry.d.ts +0 -10
- package/dist/ui/screens/mcp-registry.d.ts.map +0 -1
- package/dist/ui/screens/mcp-registry.js +0 -310
- package/dist/ui/screens/mcp-registry.js.map +0 -1
- package/dist/ui/screens/mcp-setup.d.ts +0 -4
- package/dist/ui/screens/mcp-setup.d.ts.map +0 -1
- package/dist/ui/screens/mcp-setup.js +0 -673
- package/dist/ui/screens/mcp-setup.js.map +0 -1
- package/dist/ui/screens/plugins.d.ts +0 -3
- package/dist/ui/screens/plugins.d.ts.map +0 -1
- package/dist/ui/screens/plugins.js +0 -449
- package/dist/ui/screens/plugins.js.map +0 -1
- package/dist/ui/screens/statusline.d.ts +0 -5
- package/dist/ui/screens/statusline.d.ts.map +0 -1
- package/dist/ui/screens/statusline.js +0 -235
- package/dist/ui/screens/statusline.js.map +0 -1
|
@@ -1,673 +0,0 @@
|
|
|
1
|
-
import blessed from 'neo-blessed';
|
|
2
|
-
import { createHeader, createFooter, showMessage, showConfirm, showInput, showSelect, } from '../app.js';
|
|
3
|
-
import { getMcpServersByCategory, getCategoryDisplayName, categoryOrder, getAllMcpServers, } from '../../data/mcp-servers.js';
|
|
4
|
-
import { searchMcpServers } from '../../services/mcp-registry.js';
|
|
5
|
-
import { addMcpServer, removeMcpServer, getInstalledMcpServers, getEnabledMcpServers, } from '../../services/claude-settings.js';
|
|
6
|
-
// Search results screen
|
|
7
|
-
export async function createMcpSearchScreen(state, query) {
|
|
8
|
-
createHeader(state, 'MCP Servers');
|
|
9
|
-
const installedServers = await getInstalledMcpServers(state.projectPath);
|
|
10
|
-
const enabledServers = await getEnabledMcpServers(state.projectPath);
|
|
11
|
-
const allServers = getAllMcpServers();
|
|
12
|
-
// Search both local and remote sources
|
|
13
|
-
const q = query.toLowerCase();
|
|
14
|
-
// Search local curated servers
|
|
15
|
-
const localFilteredServers = allServers.filter(s => s.name.toLowerCase().includes(q) ||
|
|
16
|
-
s.description.toLowerCase().includes(q) ||
|
|
17
|
-
(s.category && s.category.toLowerCase().includes(q)));
|
|
18
|
-
// Search remote MCP registry
|
|
19
|
-
let remoteServers = [];
|
|
20
|
-
let remoteError = null;
|
|
21
|
-
try {
|
|
22
|
-
const response = await searchMcpServers({ query, limit: 50 });
|
|
23
|
-
remoteServers = response.servers || [];
|
|
24
|
-
}
|
|
25
|
-
catch (err) {
|
|
26
|
-
// Remote search failed, will show local results only
|
|
27
|
-
remoteError = err;
|
|
28
|
-
remoteServers = [];
|
|
29
|
-
}
|
|
30
|
-
// Combine and deduplicate results (prioritize local/curated)
|
|
31
|
-
const allResults = new Map();
|
|
32
|
-
// Add local results first (they have more metadata)
|
|
33
|
-
for (const server of localFilteredServers) {
|
|
34
|
-
allResults.set(server.name, { server, source: 'local' });
|
|
35
|
-
}
|
|
36
|
-
// Add remote results, converting McpRegistryServer to McpServer format
|
|
37
|
-
for (const remote of remoteServers) {
|
|
38
|
-
if (!allResults.has(remote.name)) {
|
|
39
|
-
// Convert remote server to our format
|
|
40
|
-
const convertedServer = {
|
|
41
|
-
name: remote.name,
|
|
42
|
-
description: remote.short_description || 'No description',
|
|
43
|
-
type: 'http',
|
|
44
|
-
url: remote.url,
|
|
45
|
-
category: 'productivity', // Default category for remote servers
|
|
46
|
-
requiresConfig: false,
|
|
47
|
-
};
|
|
48
|
-
allResults.set(remote.name, { server: convertedServer, source: 'remote' });
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
const combinedResults = Array.from(allResults.values());
|
|
52
|
-
const localCount = localFilteredServers.length;
|
|
53
|
-
let searchInfo = `${combinedResults.length} found`;
|
|
54
|
-
if (localCount > 0) {
|
|
55
|
-
searchInfo += ` ({cyan-fg}${localCount} local{/cyan-fg})`;
|
|
56
|
-
}
|
|
57
|
-
if (remoteError) {
|
|
58
|
-
searchInfo += ` | {red-fg}Remote search failed{/red-fg}`;
|
|
59
|
-
}
|
|
60
|
-
// Search header
|
|
61
|
-
blessed.box({
|
|
62
|
-
parent: state.screen,
|
|
63
|
-
top: 3,
|
|
64
|
-
left: 2,
|
|
65
|
-
width: '50%-3',
|
|
66
|
-
height: 3,
|
|
67
|
-
tags: true,
|
|
68
|
-
border: { type: 'line' },
|
|
69
|
-
style: { border: { fg: 'green' } },
|
|
70
|
-
label: ' Search Results ',
|
|
71
|
-
content: `{white-fg}${query}{/white-fg} {gray-fg}(${searchInfo}){/gray-fg}`,
|
|
72
|
-
});
|
|
73
|
-
// Build list items
|
|
74
|
-
const listItems = combinedResults.map(({ server, source }) => {
|
|
75
|
-
const isInstalled = installedServers[server.name] !== undefined;
|
|
76
|
-
const isEnabled = enabledServers[server.name] === true;
|
|
77
|
-
let status = '{gray-fg}○{/gray-fg}';
|
|
78
|
-
if (isInstalled && isEnabled) {
|
|
79
|
-
status = '{green-fg}●{/green-fg}';
|
|
80
|
-
}
|
|
81
|
-
else if (isInstalled) {
|
|
82
|
-
status = '{yellow-fg}●{/yellow-fg}';
|
|
83
|
-
}
|
|
84
|
-
return {
|
|
85
|
-
label: `${status} {bold}${server.name}{/bold}`,
|
|
86
|
-
server,
|
|
87
|
-
source,
|
|
88
|
-
};
|
|
89
|
-
});
|
|
90
|
-
if (listItems.length === 0) {
|
|
91
|
-
listItems.push({
|
|
92
|
-
label: '{gray-fg}No servers match your search{/gray-fg}',
|
|
93
|
-
server: undefined,
|
|
94
|
-
source: 'local',
|
|
95
|
-
});
|
|
96
|
-
}
|
|
97
|
-
const listLabels = listItems.map((item) => item.label);
|
|
98
|
-
const list = blessed.list({
|
|
99
|
-
parent: state.screen,
|
|
100
|
-
top: 6,
|
|
101
|
-
left: 2,
|
|
102
|
-
width: '50%-3',
|
|
103
|
-
height: '100%-9',
|
|
104
|
-
items: listLabels,
|
|
105
|
-
keys: true,
|
|
106
|
-
mouse: true,
|
|
107
|
-
tags: true,
|
|
108
|
-
scrollable: true,
|
|
109
|
-
border: { type: 'line' },
|
|
110
|
-
style: {
|
|
111
|
-
selected: { bg: 'green', fg: 'white' },
|
|
112
|
-
border: { fg: 'gray' },
|
|
113
|
-
},
|
|
114
|
-
});
|
|
115
|
-
// Detail panel
|
|
116
|
-
const detailBox = blessed.box({
|
|
117
|
-
parent: state.screen,
|
|
118
|
-
top: 3,
|
|
119
|
-
left: '50%',
|
|
120
|
-
width: '50%-2',
|
|
121
|
-
height: '100%-5',
|
|
122
|
-
tags: true,
|
|
123
|
-
border: { type: 'line' },
|
|
124
|
-
style: { border: { fg: 'gray' } },
|
|
125
|
-
label: ' Details ',
|
|
126
|
-
});
|
|
127
|
-
const updateDetail = () => {
|
|
128
|
-
const selected = list.selected;
|
|
129
|
-
const item = listItems[selected];
|
|
130
|
-
if (!item?.server) {
|
|
131
|
-
detailBox.setContent('{gray-fg}No server selected{/gray-fg}');
|
|
132
|
-
state.screen.render();
|
|
133
|
-
return;
|
|
134
|
-
}
|
|
135
|
-
const server = item.server;
|
|
136
|
-
const isInstalled = installedServers[server.name] !== undefined;
|
|
137
|
-
const sourceTag = item.source === 'remote' ? '\n{bold}Source:{/bold} {cyan-fg}MCP Registry{/cyan-fg}' : '';
|
|
138
|
-
detailBox.setContent(`
|
|
139
|
-
{bold}{cyan-fg}${server.name}{/cyan-fg}{/bold}
|
|
140
|
-
|
|
141
|
-
${server.description}
|
|
142
|
-
${sourceTag}
|
|
143
|
-
|
|
144
|
-
${isInstalled
|
|
145
|
-
? '{red-fg}Press Enter to remove{/red-fg}'
|
|
146
|
-
: '{green-fg}Press Enter to install{/green-fg}'}
|
|
147
|
-
`.trim());
|
|
148
|
-
state.screen.render();
|
|
149
|
-
};
|
|
150
|
-
list.on('select item', updateDetail);
|
|
151
|
-
setTimeout(updateDetail, 0);
|
|
152
|
-
// Handle selection
|
|
153
|
-
list.on('select', async (_item, index) => {
|
|
154
|
-
const item = listItems[index];
|
|
155
|
-
if (!item?.server)
|
|
156
|
-
return;
|
|
157
|
-
const server = item.server;
|
|
158
|
-
const isInstalled = installedServers[server.name] !== undefined;
|
|
159
|
-
if (isInstalled) {
|
|
160
|
-
const remove = await showConfirm(state, `Remove ${server.name}?`, 'Remove MCP server?');
|
|
161
|
-
if (remove) {
|
|
162
|
-
await removeMcpServer(server.name, state.projectPath);
|
|
163
|
-
await showMessage(state, 'Removed', `${server.name} removed.`, 'success');
|
|
164
|
-
createMcpSearchScreen(state, query);
|
|
165
|
-
}
|
|
166
|
-
}
|
|
167
|
-
else {
|
|
168
|
-
await installMcpServer(state, server);
|
|
169
|
-
createMcpSearchScreen(state, query);
|
|
170
|
-
}
|
|
171
|
-
});
|
|
172
|
-
// Back to main MCP screen
|
|
173
|
-
list.key(['escape', 'q'], () => {
|
|
174
|
-
createMcpScreen(state);
|
|
175
|
-
});
|
|
176
|
-
// New search
|
|
177
|
-
list.key(['/'], async () => {
|
|
178
|
-
const newQuery = await showInput(state, 'Search', 'Search MCP servers:', query);
|
|
179
|
-
if (newQuery !== null && newQuery.trim()) {
|
|
180
|
-
createMcpSearchScreen(state, newQuery);
|
|
181
|
-
}
|
|
182
|
-
});
|
|
183
|
-
createFooter(state, '↑↓ Navigate │ Enter Install/Remove │ / New Search │ Esc Back');
|
|
184
|
-
list.focus();
|
|
185
|
-
state.screen.render();
|
|
186
|
-
}
|
|
187
|
-
// Main MCP screen (no search filtering)
|
|
188
|
-
export async function createMcpScreen(state) {
|
|
189
|
-
createHeader(state, 'MCP Servers');
|
|
190
|
-
const installedServers = await getInstalledMcpServers(state.projectPath);
|
|
191
|
-
const enabledServers = await getEnabledMcpServers(state.projectPath);
|
|
192
|
-
const serversByCategory = getMcpServersByCategory();
|
|
193
|
-
// Search box - just shows hint
|
|
194
|
-
blessed.box({
|
|
195
|
-
parent: state.screen,
|
|
196
|
-
top: 3,
|
|
197
|
-
left: 2,
|
|
198
|
-
width: '50%-3',
|
|
199
|
-
height: 3,
|
|
200
|
-
tags: true,
|
|
201
|
-
border: { type: 'line' },
|
|
202
|
-
style: { border: { fg: 'cyan' } },
|
|
203
|
-
label: ' Search ',
|
|
204
|
-
content: '{gray-fg}Press / to search...{/gray-fg}',
|
|
205
|
-
});
|
|
206
|
-
const listItems = [];
|
|
207
|
-
for (const category of categoryOrder) {
|
|
208
|
-
const servers = serversByCategory[category];
|
|
209
|
-
if (!servers || servers.length === 0)
|
|
210
|
-
continue;
|
|
211
|
-
listItems.push({
|
|
212
|
-
label: `{bold}{cyan-fg}${getCategoryDisplayName(category)}{/cyan-fg}{/bold}`,
|
|
213
|
-
isCategory: true,
|
|
214
|
-
});
|
|
215
|
-
for (const server of servers) {
|
|
216
|
-
const isInstalled = installedServers[server.name] !== undefined;
|
|
217
|
-
const isEnabled = enabledServers[server.name] === true;
|
|
218
|
-
let status = '{gray-fg}○{/gray-fg}';
|
|
219
|
-
if (isInstalled && isEnabled) {
|
|
220
|
-
status = '{green-fg}●{/green-fg}';
|
|
221
|
-
}
|
|
222
|
-
else if (isInstalled) {
|
|
223
|
-
status = '{yellow-fg}●{/yellow-fg}';
|
|
224
|
-
}
|
|
225
|
-
const configTag = server.requiresConfig ? ' {yellow-fg}*{/yellow-fg}' : '';
|
|
226
|
-
listItems.push({
|
|
227
|
-
label: ` ${status} {bold}${server.name}{/bold}${configTag}`,
|
|
228
|
-
server,
|
|
229
|
-
});
|
|
230
|
-
}
|
|
231
|
-
}
|
|
232
|
-
const list = blessed.list({
|
|
233
|
-
parent: state.screen,
|
|
234
|
-
top: 6,
|
|
235
|
-
left: 2,
|
|
236
|
-
width: '50%-3',
|
|
237
|
-
height: '100%-9',
|
|
238
|
-
items: listItems.map((item) => item.label),
|
|
239
|
-
keys: true,
|
|
240
|
-
mouse: true,
|
|
241
|
-
tags: true,
|
|
242
|
-
scrollable: true,
|
|
243
|
-
border: { type: 'line' },
|
|
244
|
-
style: {
|
|
245
|
-
selected: { bg: 'blue', fg: 'white' },
|
|
246
|
-
border: { fg: 'gray' },
|
|
247
|
-
},
|
|
248
|
-
scrollbar: { ch: '|', style: { bg: 'gray' } },
|
|
249
|
-
});
|
|
250
|
-
// Detail panel on the right
|
|
251
|
-
const detailBox = blessed.box({
|
|
252
|
-
parent: state.screen,
|
|
253
|
-
top: 3,
|
|
254
|
-
left: '50%',
|
|
255
|
-
width: '50%-2',
|
|
256
|
-
height: '100%-5',
|
|
257
|
-
content: '',
|
|
258
|
-
tags: true,
|
|
259
|
-
border: {
|
|
260
|
-
type: 'line',
|
|
261
|
-
},
|
|
262
|
-
style: {
|
|
263
|
-
border: {
|
|
264
|
-
fg: 'gray',
|
|
265
|
-
},
|
|
266
|
-
},
|
|
267
|
-
label: ' Details ',
|
|
268
|
-
});
|
|
269
|
-
// Update detail panel on selection change
|
|
270
|
-
const updateDetail = () => {
|
|
271
|
-
const selected = list.selected;
|
|
272
|
-
const item = listItems[selected];
|
|
273
|
-
if (!item || item.isCategory || !item.server) {
|
|
274
|
-
detailBox.setContent('{gray-fg}Select a server to see details{/gray-fg}');
|
|
275
|
-
state.screen.render();
|
|
276
|
-
return;
|
|
277
|
-
}
|
|
278
|
-
const server = item.server;
|
|
279
|
-
const isInstalled = installedServers[server.name] !== undefined;
|
|
280
|
-
const isEnabled = enabledServers[server.name] === true;
|
|
281
|
-
let statusText = '{gray-fg}Not installed{/gray-fg}';
|
|
282
|
-
if (isInstalled && isEnabled) {
|
|
283
|
-
statusText = '{green-fg}● Installed & Enabled{/green-fg}';
|
|
284
|
-
}
|
|
285
|
-
else if (isInstalled) {
|
|
286
|
-
statusText = '{yellow-fg}● Installed (disabled){/yellow-fg}';
|
|
287
|
-
}
|
|
288
|
-
let typeInfo = '';
|
|
289
|
-
if (server.type === 'http') {
|
|
290
|
-
typeInfo = `{bold}URL:{/bold} {cyan-fg}${server.url}{/cyan-fg}`;
|
|
291
|
-
}
|
|
292
|
-
else {
|
|
293
|
-
typeInfo = `{bold}Command:{/bold} {cyan-fg}${server.command} ${(server.args || []).join(' ')}{/cyan-fg}`;
|
|
294
|
-
}
|
|
295
|
-
let configInfo = '';
|
|
296
|
-
if (isInstalled) {
|
|
297
|
-
// Show current configuration for installed servers
|
|
298
|
-
const currentConfig = installedServers[server.name];
|
|
299
|
-
if (currentConfig?.env && Object.keys(currentConfig.env).length > 0) {
|
|
300
|
-
configInfo = `\n\n{bold}Current Configuration:{/bold}\n`;
|
|
301
|
-
for (const [varName, varValue] of Object.entries(currentConfig.env)) {
|
|
302
|
-
const isReference = varValue.startsWith('${') && varValue.endsWith('}');
|
|
303
|
-
if (isReference) {
|
|
304
|
-
// Extract var name from ${VAR} and check if set in environment
|
|
305
|
-
const refVarName = varValue.slice(2, -1);
|
|
306
|
-
const envValue = process.env[refVarName];
|
|
307
|
-
const envStatus = envValue ? '{green-fg}[SET]{/green-fg}' : '{yellow-fg}[NOT SET]{/yellow-fg}';
|
|
308
|
-
configInfo += ` {cyan-fg}${varName}{/cyan-fg}: {gray-fg}${varValue}{/gray-fg} ${envStatus}\n`;
|
|
309
|
-
}
|
|
310
|
-
else {
|
|
311
|
-
// Hardcoded value - show masked
|
|
312
|
-
const masked = varValue.length > 8 ? varValue.slice(0, 8) + '...' : varValue;
|
|
313
|
-
configInfo += ` {cyan-fg}${varName}{/cyan-fg}: {gray-fg}${masked}{/gray-fg} {blue-fg}[HARDCODED]{/blue-fg}\n`;
|
|
314
|
-
}
|
|
315
|
-
}
|
|
316
|
-
configInfo += `\n{gray-fg}Press 'e' to edit configuration{/gray-fg}`;
|
|
317
|
-
}
|
|
318
|
-
}
|
|
319
|
-
else if (server.requiresConfig && server.configFields) {
|
|
320
|
-
// Show required env vars for servers not yet installed
|
|
321
|
-
configInfo = `\n\n{bold}Required Environment Variables:{/bold}\n`;
|
|
322
|
-
for (const field of server.configFields) {
|
|
323
|
-
const envVarName = field.envVar || field.name;
|
|
324
|
-
const envValue = process.env[envVarName];
|
|
325
|
-
const isSet = envValue !== undefined && envValue !== '';
|
|
326
|
-
const req = field.required ? '{red-fg}*{/red-fg}' : '';
|
|
327
|
-
const status = isSet
|
|
328
|
-
? '{green-fg}[SET]{/green-fg}'
|
|
329
|
-
: '{yellow-fg}[NOT SET]{/yellow-fg}';
|
|
330
|
-
configInfo += ` ${req} ${envVarName} ${status}\n`;
|
|
331
|
-
configInfo += ` {gray-fg}${field.label}{/gray-fg}\n`;
|
|
332
|
-
}
|
|
333
|
-
}
|
|
334
|
-
const actionHint = isInstalled
|
|
335
|
-
? '{gray-fg}Enter: Remove │ e: Edit Config{/gray-fg}'
|
|
336
|
-
: '{green-fg}Press Enter to install{/green-fg}';
|
|
337
|
-
const content = `
|
|
338
|
-
{bold}{cyan-fg}${server.name}{/cyan-fg}{/bold}
|
|
339
|
-
|
|
340
|
-
${server.description}
|
|
341
|
-
|
|
342
|
-
{bold}Status:{/bold} ${statusText}
|
|
343
|
-
|
|
344
|
-
${typeInfo}${configInfo}
|
|
345
|
-
|
|
346
|
-
${actionHint}
|
|
347
|
-
`.trim();
|
|
348
|
-
detailBox.setContent(content);
|
|
349
|
-
state.screen.render();
|
|
350
|
-
};
|
|
351
|
-
list.on('select item', updateDetail);
|
|
352
|
-
// Initial update
|
|
353
|
-
setTimeout(updateDetail, 0);
|
|
354
|
-
// Handle selection
|
|
355
|
-
list.on('select', async (_item, index) => {
|
|
356
|
-
const selected = listItems[index];
|
|
357
|
-
if (!selected || selected.isCategory || !selected.server) {
|
|
358
|
-
return;
|
|
359
|
-
}
|
|
360
|
-
const server = selected.server;
|
|
361
|
-
const isInstalled = installedServers[server.name] !== undefined;
|
|
362
|
-
if (isInstalled) {
|
|
363
|
-
const remove = await showConfirm(state, `Remove ${server.name}?`, 'This will remove the MCP server configuration.');
|
|
364
|
-
if (remove) {
|
|
365
|
-
await removeMcpServer(server.name, state.projectPath);
|
|
366
|
-
await showMessage(state, 'Removed', `${server.name} has been removed.`, 'success');
|
|
367
|
-
createMcpScreen(state);
|
|
368
|
-
}
|
|
369
|
-
}
|
|
370
|
-
else {
|
|
371
|
-
await installMcpServer(state, server);
|
|
372
|
-
}
|
|
373
|
-
});
|
|
374
|
-
// Manual j/k navigation (since vi mode is disabled)
|
|
375
|
-
list.key(['j'], () => {
|
|
376
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
377
|
-
list.down();
|
|
378
|
-
state.screen.render();
|
|
379
|
-
});
|
|
380
|
-
list.key(['k'], () => {
|
|
381
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
382
|
-
list.up();
|
|
383
|
-
state.screen.render();
|
|
384
|
-
});
|
|
385
|
-
// Edit configuration with 'e' key
|
|
386
|
-
list.key(['e'], async () => {
|
|
387
|
-
const selected = list.selected;
|
|
388
|
-
const item = listItems[selected];
|
|
389
|
-
if (!item || item.isCategory || !item.server)
|
|
390
|
-
return;
|
|
391
|
-
const server = item.server;
|
|
392
|
-
const isInstalled = installedServers[server.name] !== undefined;
|
|
393
|
-
if (isInstalled) {
|
|
394
|
-
await editMcpServerConfig(state, server, installedServers[server.name]);
|
|
395
|
-
}
|
|
396
|
-
else {
|
|
397
|
-
await showMessage(state, 'Not Installed', 'Install the server first to configure it.', 'info');
|
|
398
|
-
}
|
|
399
|
-
});
|
|
400
|
-
// Search with / key - opens search screen
|
|
401
|
-
list.key(['/'], async () => {
|
|
402
|
-
const query = await showInput(state, 'Search', 'Search MCP servers:');
|
|
403
|
-
if (query !== null && query.trim()) {
|
|
404
|
-
createMcpSearchScreen(state, query);
|
|
405
|
-
}
|
|
406
|
-
});
|
|
407
|
-
// Switch to registry search with r
|
|
408
|
-
list.key(['r'], async () => {
|
|
409
|
-
const { navigateTo } = await import('../app.js');
|
|
410
|
-
navigateTo(state, 'mcp-registry');
|
|
411
|
-
});
|
|
412
|
-
createFooter(state, '↑↓ Navigate │ Enter Install/Remove │ e Edit │ / Search │ r Registry │ q Back');
|
|
413
|
-
list.focus();
|
|
414
|
-
state.screen.render();
|
|
415
|
-
}
|
|
416
|
-
async function installMcpServer(state, server) {
|
|
417
|
-
let config;
|
|
418
|
-
if (server.type === 'http') {
|
|
419
|
-
// HTTP-based MCP server
|
|
420
|
-
config = {
|
|
421
|
-
type: 'http',
|
|
422
|
-
url: server.url,
|
|
423
|
-
};
|
|
424
|
-
}
|
|
425
|
-
else {
|
|
426
|
-
// Command-based MCP server
|
|
427
|
-
config = {
|
|
428
|
-
command: server.command,
|
|
429
|
-
args: server.args ? [...server.args] : undefined,
|
|
430
|
-
env: server.env ? { ...server.env } : undefined,
|
|
431
|
-
};
|
|
432
|
-
}
|
|
433
|
-
// Collect configuration if required
|
|
434
|
-
if (server.requiresConfig && server.configFields) {
|
|
435
|
-
// Check which env vars are already set
|
|
436
|
-
const envStatus = [];
|
|
437
|
-
for (const field of server.configFields) {
|
|
438
|
-
const envVarName = field.envVar || field.name;
|
|
439
|
-
const existingValue = process.env[envVarName];
|
|
440
|
-
envStatus.push({ field, existingValue });
|
|
441
|
-
}
|
|
442
|
-
// Check if all required env vars are set
|
|
443
|
-
const missingRequired = envStatus.filter((e) => e.field.required && (!e.existingValue || e.existingValue === ''));
|
|
444
|
-
const hasExistingVars = envStatus.some((e) => e.existingValue !== undefined && e.existingValue !== '');
|
|
445
|
-
// If some env vars exist, ask user if they want to use them
|
|
446
|
-
if (hasExistingVars && missingRequired.length === 0) {
|
|
447
|
-
const useExisting = await showConfirm(state, 'Use Environment Variables?', 'All required environment variables are already set.\nUse values from your environment?');
|
|
448
|
-
if (useExisting) {
|
|
449
|
-
// Use existing env vars - reference them with ${VAR} syntax
|
|
450
|
-
config.env = config.env || {};
|
|
451
|
-
for (const { field } of envStatus) {
|
|
452
|
-
const envVarName = field.envVar || field.name;
|
|
453
|
-
config.env[envVarName] = `\${${envVarName}}`;
|
|
454
|
-
}
|
|
455
|
-
}
|
|
456
|
-
else {
|
|
457
|
-
// User wants to enter new values
|
|
458
|
-
for (const { field, existingValue } of envStatus) {
|
|
459
|
-
const envVarName = field.envVar || field.name;
|
|
460
|
-
const hint = existingValue ? ` (current: ${existingValue.slice(0, 8)}...)` : '';
|
|
461
|
-
const value = await showInput(state, `Configure ${server.name}`, `${field.label}${field.required ? ' (required)' : ''}${hint}:`, field.default);
|
|
462
|
-
if (value === null) {
|
|
463
|
-
return; // User cancelled
|
|
464
|
-
}
|
|
465
|
-
if (field.required && !value) {
|
|
466
|
-
await showMessage(state, 'Required Field', `${field.label} is required.`, 'error');
|
|
467
|
-
return;
|
|
468
|
-
}
|
|
469
|
-
if (value) {
|
|
470
|
-
config.env = config.env || {};
|
|
471
|
-
config.env[envVarName] = value;
|
|
472
|
-
}
|
|
473
|
-
}
|
|
474
|
-
}
|
|
475
|
-
}
|
|
476
|
-
else {
|
|
477
|
-
// Some required vars are missing - prompt for each
|
|
478
|
-
if (missingRequired.length > 0) {
|
|
479
|
-
const missingNames = missingRequired.map((e) => e.field.envVar || e.field.name).join(', ');
|
|
480
|
-
await showMessage(state, 'Missing Environment Variables', `The following required variables are not set:\n${missingNames}\n\nYou can set them in your shell or enter values now.`, 'info');
|
|
481
|
-
}
|
|
482
|
-
for (const { field, existingValue } of envStatus) {
|
|
483
|
-
const envVarName = field.envVar || field.name;
|
|
484
|
-
const isSet = existingValue !== undefined && existingValue !== '';
|
|
485
|
-
let defaultValue = field.default;
|
|
486
|
-
let prompt = `${field.label}${field.required ? ' (required)' : ''}:`;
|
|
487
|
-
if (isSet) {
|
|
488
|
-
// Env var is set, offer to use it
|
|
489
|
-
const useIt = await showConfirm(state, `Use ${envVarName}?`, `${envVarName} is set in your environment.\nUse the existing value?`);
|
|
490
|
-
if (useIt) {
|
|
491
|
-
config.env = config.env || {};
|
|
492
|
-
config.env[envVarName] = `\${${envVarName}}`;
|
|
493
|
-
continue;
|
|
494
|
-
}
|
|
495
|
-
prompt = `${field.label} (override existing):`;
|
|
496
|
-
}
|
|
497
|
-
const value = await showInput(state, `Configure ${server.name}`, prompt, defaultValue);
|
|
498
|
-
if (value === null) {
|
|
499
|
-
return; // User cancelled
|
|
500
|
-
}
|
|
501
|
-
if (field.required && !value) {
|
|
502
|
-
await showMessage(state, 'Required Field', `${field.label} is required.`, 'error');
|
|
503
|
-
return;
|
|
504
|
-
}
|
|
505
|
-
if (value) {
|
|
506
|
-
// Replace placeholder in args
|
|
507
|
-
if (config.args) {
|
|
508
|
-
config.args = config.args.map((arg) => arg.replace(`\${${field.name}}`, value));
|
|
509
|
-
}
|
|
510
|
-
config.env = config.env || {};
|
|
511
|
-
config.env[envVarName] = value;
|
|
512
|
-
}
|
|
513
|
-
}
|
|
514
|
-
}
|
|
515
|
-
}
|
|
516
|
-
// Add server to .mcp.json
|
|
517
|
-
await addMcpServer(server.name, config, state.projectPath);
|
|
518
|
-
await showMessage(state, 'Installed', `${server.name} has been configured.\n\nRestart Claude Code to activate.`, 'success');
|
|
519
|
-
createMcpScreen(state);
|
|
520
|
-
}
|
|
521
|
-
async function editMcpServerConfig(state, server, currentConfig) {
|
|
522
|
-
// Get current env vars from config
|
|
523
|
-
const currentEnv = currentConfig.env || {};
|
|
524
|
-
const envVarNames = Object.keys(currentEnv);
|
|
525
|
-
// Get configFields from curated server definition if available
|
|
526
|
-
const allServers = getAllMcpServers();
|
|
527
|
-
const curatedServer = allServers.find((s) => s.name === server.name);
|
|
528
|
-
const configFields = curatedServer?.configFields || [];
|
|
529
|
-
// Build list of all env vars (from current config + configFields)
|
|
530
|
-
const allEnvVars = new Set(envVarNames);
|
|
531
|
-
for (const field of configFields) {
|
|
532
|
-
const envVarName = field.envVar || field.name;
|
|
533
|
-
allEnvVars.add(envVarName);
|
|
534
|
-
}
|
|
535
|
-
if (allEnvVars.size === 0) {
|
|
536
|
-
// No env vars to edit, offer to add new ones
|
|
537
|
-
const addNew = await showConfirm(state, 'No Environment Variables', 'This server has no environment variables configured.\nWould you like to add one?');
|
|
538
|
-
if (addNew) {
|
|
539
|
-
await addNewEnvVar(state, server.name, currentConfig);
|
|
540
|
-
}
|
|
541
|
-
return;
|
|
542
|
-
}
|
|
543
|
-
// Let user choose what to do
|
|
544
|
-
const action = await showSelect(state, `Configure ${server.name}`, 'What would you like to do?', [
|
|
545
|
-
{ label: 'Edit existing variables', value: 'edit' },
|
|
546
|
-
{ label: 'Add new variable', value: 'add' },
|
|
547
|
-
{ label: 'Cancel', value: 'cancel' },
|
|
548
|
-
]);
|
|
549
|
-
if (action === null || action === 'cancel') {
|
|
550
|
-
return;
|
|
551
|
-
}
|
|
552
|
-
if (action === 'add') {
|
|
553
|
-
await addNewEnvVar(state, server.name, currentConfig);
|
|
554
|
-
return;
|
|
555
|
-
}
|
|
556
|
-
// Edit existing variables
|
|
557
|
-
const updatedEnv = { ...currentEnv };
|
|
558
|
-
let modified = false;
|
|
559
|
-
for (const envVarName of allEnvVars) {
|
|
560
|
-
const currentValue = currentEnv[envVarName];
|
|
561
|
-
const field = configFields.find((f) => (f.envVar || f.name) === envVarName);
|
|
562
|
-
const fieldLabel = field?.label || envVarName;
|
|
563
|
-
// Determine current value type
|
|
564
|
-
const isReference = currentValue?.startsWith('${') && currentValue?.endsWith('}');
|
|
565
|
-
const envValueFromShell = process.env[envVarName];
|
|
566
|
-
const hasShellValue = envValueFromShell !== undefined && envValueFromShell !== '';
|
|
567
|
-
// Build description of current state
|
|
568
|
-
let currentDesc = 'Not configured';
|
|
569
|
-
if (currentValue) {
|
|
570
|
-
if (isReference) {
|
|
571
|
-
const refStatus = hasShellValue ? 'set in environment' : 'NOT set in environment';
|
|
572
|
-
currentDesc = `${currentValue} (${refStatus})`;
|
|
573
|
-
}
|
|
574
|
-
else {
|
|
575
|
-
const masked = currentValue.length > 8 ? currentValue.slice(0, 8) + '...' : currentValue;
|
|
576
|
-
currentDesc = `Hardcoded: ${masked}`;
|
|
577
|
-
}
|
|
578
|
-
}
|
|
579
|
-
// Build options
|
|
580
|
-
const options = [
|
|
581
|
-
{ label: `Keep current: ${currentDesc}`, value: 'keep' },
|
|
582
|
-
];
|
|
583
|
-
if (hasShellValue) {
|
|
584
|
-
options.push({
|
|
585
|
-
label: `Use environment variable \${${envVarName}}`,
|
|
586
|
-
value: 'env',
|
|
587
|
-
});
|
|
588
|
-
}
|
|
589
|
-
options.push({ label: 'Enter new value', value: 'new' });
|
|
590
|
-
if (currentValue) {
|
|
591
|
-
options.push({ label: 'Remove this variable', value: 'remove' });
|
|
592
|
-
}
|
|
593
|
-
const choice = await showSelect(state, `Edit ${envVarName}`, `${fieldLabel}\n\nCurrent: ${currentDesc}`, options);
|
|
594
|
-
if (choice === null) {
|
|
595
|
-
// User cancelled - ask if they want to save partial changes
|
|
596
|
-
if (modified) {
|
|
597
|
-
const savePartial = await showConfirm(state, 'Save Changes?', 'You have unsaved changes. Save them?');
|
|
598
|
-
if (savePartial) {
|
|
599
|
-
break;
|
|
600
|
-
}
|
|
601
|
-
}
|
|
602
|
-
return;
|
|
603
|
-
}
|
|
604
|
-
if (choice === 'keep') {
|
|
605
|
-
// Keep current value
|
|
606
|
-
continue;
|
|
607
|
-
}
|
|
608
|
-
else if (choice === 'env') {
|
|
609
|
-
// Use environment variable reference
|
|
610
|
-
updatedEnv[envVarName] = `\${${envVarName}}`;
|
|
611
|
-
modified = true;
|
|
612
|
-
}
|
|
613
|
-
else if (choice === 'new') {
|
|
614
|
-
// Enter new value
|
|
615
|
-
const newValue = await showInput(state, `Set ${envVarName}`, `${fieldLabel}:`, '');
|
|
616
|
-
if (newValue === null) {
|
|
617
|
-
continue; // Skip this var
|
|
618
|
-
}
|
|
619
|
-
if (newValue) {
|
|
620
|
-
updatedEnv[envVarName] = newValue;
|
|
621
|
-
modified = true;
|
|
622
|
-
}
|
|
623
|
-
}
|
|
624
|
-
else if (choice === 'remove') {
|
|
625
|
-
delete updatedEnv[envVarName];
|
|
626
|
-
modified = true;
|
|
627
|
-
}
|
|
628
|
-
}
|
|
629
|
-
if (modified) {
|
|
630
|
-
// Save updated config
|
|
631
|
-
const newConfig = {
|
|
632
|
-
...currentConfig,
|
|
633
|
-
env: Object.keys(updatedEnv).length > 0 ? updatedEnv : undefined,
|
|
634
|
-
};
|
|
635
|
-
await addMcpServer(server.name, newConfig, state.projectPath);
|
|
636
|
-
await showMessage(state, 'Configuration Updated', `${server.name} configuration has been updated.\n\nRestart Claude Code to apply changes.`, 'success');
|
|
637
|
-
}
|
|
638
|
-
createMcpScreen(state);
|
|
639
|
-
}
|
|
640
|
-
async function addNewEnvVar(state, serverName, currentConfig) {
|
|
641
|
-
// Get variable name
|
|
642
|
-
const varName = await showInput(state, 'Add Environment Variable', 'Variable name (e.g., API_KEY):');
|
|
643
|
-
if (varName === null || !varName.trim()) {
|
|
644
|
-
return;
|
|
645
|
-
}
|
|
646
|
-
const cleanVarName = varName.trim().toUpperCase().replace(/[^A-Z0-9_]/g, '_');
|
|
647
|
-
// Check if var exists in environment
|
|
648
|
-
const envValue = process.env[cleanVarName];
|
|
649
|
-
const hasEnvValue = envValue !== undefined && envValue !== '';
|
|
650
|
-
let value = null;
|
|
651
|
-
if (hasEnvValue) {
|
|
652
|
-
const useEnv = await showConfirm(state, `${cleanVarName} Found`, `${cleanVarName} is set in your environment.\nUse the environment variable reference?`);
|
|
653
|
-
if (useEnv) {
|
|
654
|
-
value = `\${${cleanVarName}}`;
|
|
655
|
-
}
|
|
656
|
-
}
|
|
657
|
-
if (value === null) {
|
|
658
|
-
value = await showInput(state, `Set ${cleanVarName}`, 'Enter value:');
|
|
659
|
-
if (value === null || !value) {
|
|
660
|
-
return;
|
|
661
|
-
}
|
|
662
|
-
}
|
|
663
|
-
// Update config
|
|
664
|
-
const updatedEnv = { ...currentConfig.env, [cleanVarName]: value };
|
|
665
|
-
const newConfig = {
|
|
666
|
-
...currentConfig,
|
|
667
|
-
env: updatedEnv,
|
|
668
|
-
};
|
|
669
|
-
await addMcpServer(serverName, newConfig, state.projectPath);
|
|
670
|
-
await showMessage(state, 'Variable Added', `${cleanVarName} has been added to ${serverName}.\n\nRestart Claude Code to apply changes.`, 'success');
|
|
671
|
-
createMcpScreen(state);
|
|
672
|
-
}
|
|
673
|
-
//# sourceMappingURL=mcp-setup.js.map
|