@geminilight/mindos 0.4.0 → 0.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/app/app/api/setup/check-port/route.ts +41 -0
- package/app/app/layout.tsx +4 -3
- package/app/app/page.tsx +2 -0
- package/app/app/setup/page.tsx +2 -0
- package/app/components/SetupWizard.tsx +675 -321
- package/app/lib/i18n.ts +48 -9
- package/bin/lib/mcp-install.js +4 -1
- package/bin/lib/port.js +8 -2
- package/package.json +1 -1
- package/scripts/setup.js +181 -10
package/app/lib/i18n.ts
CHANGED
|
@@ -271,7 +271,7 @@ export const messages = {
|
|
|
271
271
|
releaseNotes: 'view release notes',
|
|
272
272
|
},
|
|
273
273
|
setup: {
|
|
274
|
-
stepTitles: ['Knowledge Base', 'AI Provider', 'Ports', 'Security', 'Review'],
|
|
274
|
+
stepTitles: ['Knowledge Base', 'AI Provider', 'Ports', 'Security', 'Agent Tools', 'Review'],
|
|
275
275
|
// Step 1
|
|
276
276
|
kbPath: 'Knowledge base path',
|
|
277
277
|
kbPathHint: 'Absolute path to your notes directory.',
|
|
@@ -291,6 +291,11 @@ export const messages = {
|
|
|
291
291
|
mcpPort: 'MCP server port',
|
|
292
292
|
portHint: 'Valid range: 1024–65535',
|
|
293
293
|
portRestartWarning: 'Port changes take effect after server restart.',
|
|
294
|
+
portInUse: (p: number) => `Port ${p} is already in use.`,
|
|
295
|
+
portSuggest: (p: number) => `Use ${p}`,
|
|
296
|
+
portChecking: 'Checking…',
|
|
297
|
+
portConflict: 'Web UI and MCP ports must be different.',
|
|
298
|
+
portVerifyHint: 'Click outside each field to verify, or wait for auto-check.',
|
|
294
299
|
// Step 4
|
|
295
300
|
authToken: 'Auth Token',
|
|
296
301
|
authTokenHint: 'Bearer token for MCP / API clients. Auto-generated.',
|
|
@@ -301,16 +306,30 @@ export const messages = {
|
|
|
301
306
|
copiedToken: 'Copied!',
|
|
302
307
|
webPassword: 'Web UI Password',
|
|
303
308
|
webPasswordHint: 'Optional. Protect browser access with a password.',
|
|
304
|
-
// Step 5
|
|
305
|
-
|
|
309
|
+
// Step 5 — Agent Tools
|
|
310
|
+
agentToolsTitle: 'Agent Tools',
|
|
311
|
+
agentToolsHint: 'Select AI agents to configure with MindOS MCP. Agents marked "not installed" can be configured now — they will work once you install the app.',
|
|
312
|
+
agentTransport: 'Transport',
|
|
313
|
+
agentScope: 'Scope',
|
|
314
|
+
agentToolsLoading: 'Loading agents…',
|
|
315
|
+
agentToolsEmpty: 'No supported agents detected.',
|
|
316
|
+
agentNoneSelected: 'No agents selected — you can configure later in Settings → MCP.',
|
|
317
|
+
agentNotInstalled: 'not installed',
|
|
318
|
+
agentStatusOk: 'configured',
|
|
319
|
+
agentStatusError: 'failed',
|
|
320
|
+
agentInstalling: 'Configuring…',
|
|
321
|
+
// Step 2 — AI skip card
|
|
322
|
+
aiSkipTitle: 'Skip for now',
|
|
323
|
+
aiSkipDesc: 'You can add an API key later in Settings → AI.',
|
|
324
|
+
// Step 6 — Review
|
|
306
325
|
reviewHint: 'Verify your settings before completing setup.',
|
|
307
|
-
|
|
326
|
+
reviewInstallResults: 'Agent configuration results:',
|
|
327
|
+
portAvailable: 'Available',
|
|
308
328
|
portChanged: 'Port changed — please restart the server for it to take effect.',
|
|
309
329
|
// Buttons
|
|
310
330
|
back: 'Back',
|
|
311
331
|
next: 'Next',
|
|
312
332
|
complete: 'Complete Setup',
|
|
313
|
-
skip: 'Skip',
|
|
314
333
|
// Status
|
|
315
334
|
completing: 'Saving...',
|
|
316
335
|
completeDone: 'Setup complete!',
|
|
@@ -587,7 +606,7 @@ export const messages = {
|
|
|
587
606
|
releaseNotes: '查看更新说明',
|
|
588
607
|
},
|
|
589
608
|
setup: {
|
|
590
|
-
stepTitles: ['知识库', 'AI 服务商', '端口', '安全', '确认'],
|
|
609
|
+
stepTitles: ['知识库', 'AI 服务商', '端口', '安全', 'Agent 工具', '确认'],
|
|
591
610
|
// Step 1
|
|
592
611
|
kbPath: '知识库路径',
|
|
593
612
|
kbPathHint: '笔记目录的绝对路径。',
|
|
@@ -607,6 +626,11 @@ export const messages = {
|
|
|
607
626
|
mcpPort: 'MCP 服务端口',
|
|
608
627
|
portHint: '有效范围:1024–65535',
|
|
609
628
|
portRestartWarning: '端口修改需重启服务后生效。',
|
|
629
|
+
portInUse: (p: number) => `端口 ${p} 已被占用。`,
|
|
630
|
+
portSuggest: (p: number) => `使用 ${p}`,
|
|
631
|
+
portChecking: '检测中…',
|
|
632
|
+
portConflict: 'Web UI 端口和 MCP 端口不能相同。',
|
|
633
|
+
portVerifyHint: '点击输入框外部验证,或等待自动检测。',
|
|
610
634
|
// Step 4
|
|
611
635
|
authToken: 'Auth Token',
|
|
612
636
|
authTokenHint: 'MCP / API 客户端使用的 Bearer Token,自动生成。',
|
|
@@ -617,10 +641,25 @@ export const messages = {
|
|
|
617
641
|
copiedToken: '已复制!',
|
|
618
642
|
webPassword: '网页访问密码',
|
|
619
643
|
webPasswordHint: '可选。设置后浏览器访问需要登录。',
|
|
620
|
-
// Step 5
|
|
621
|
-
|
|
644
|
+
// Step 5 — Agent Tools
|
|
645
|
+
agentToolsTitle: 'Agent 工具',
|
|
646
|
+
agentToolsHint: '选择要与 MindOS MCP 配置的 AI Agent。标注「未安装」的 agent 可以先行配置,安装应用后即可生效。',
|
|
647
|
+
agentTransport: '传输方式',
|
|
648
|
+
agentScope: '范围',
|
|
649
|
+
agentToolsLoading: '正在加载 Agent…',
|
|
650
|
+
agentToolsEmpty: '未检测到受支持的 Agent。',
|
|
651
|
+
agentNoneSelected: '未选择 agent — 可稍后在 设置 → MCP 中配置。',
|
|
652
|
+
agentNotInstalled: '未安装',
|
|
653
|
+
agentStatusOk: '已配置',
|
|
654
|
+
agentStatusError: '失败',
|
|
655
|
+
agentInstalling: '配置中…',
|
|
656
|
+
// Step 2 — AI skip card
|
|
657
|
+
aiSkipTitle: '暂时跳过',
|
|
658
|
+
aiSkipDesc: '稍后可在 设置 → AI 中添加 API 密钥。',
|
|
659
|
+
// Step 6 — Review
|
|
622
660
|
reviewHint: '完成设置前请确认以下信息。',
|
|
623
|
-
|
|
661
|
+
reviewInstallResults: 'Agent 配置结果:',
|
|
662
|
+
portAvailable: '可用',
|
|
624
663
|
portChanged: '端口已变更 — 请重启服务以使其生效。',
|
|
625
664
|
// Buttons
|
|
626
665
|
back: '上一步',
|
package/bin/lib/mcp-install.js
CHANGED
|
@@ -232,7 +232,10 @@ export async function mcpInstall() {
|
|
|
232
232
|
const ask2 = (q) => new Promise(r => rl2.question(q, r));
|
|
233
233
|
|
|
234
234
|
if (!url) {
|
|
235
|
-
|
|
235
|
+
let mcpPort = 8787;
|
|
236
|
+
try { mcpPort = JSON.parse(readFileSync(CONFIG_PATH, 'utf-8')).mcpPort || 8787; } catch {}
|
|
237
|
+
const defaultUrl = `http://localhost:${mcpPort}/mcp`;
|
|
238
|
+
url = hasYesFlag ? defaultUrl : (await ask2(`${bold('MCP URL')} ${dim(`[${defaultUrl}]:`)} `)).trim() || defaultUrl;
|
|
236
239
|
}
|
|
237
240
|
|
|
238
241
|
if (!token) {
|
package/bin/lib/port.js
CHANGED
|
@@ -4,8 +4,14 @@ import { bold, dim, red } from './colors.js';
|
|
|
4
4
|
export function isPortInUse(port) {
|
|
5
5
|
return new Promise((resolve) => {
|
|
6
6
|
const sock = createConnection({ port, host: '127.0.0.1' });
|
|
7
|
-
|
|
8
|
-
sock.
|
|
7
|
+
const cleanup = (result) => { sock.destroy(); resolve(result); };
|
|
8
|
+
sock.setTimeout(500, () => cleanup(false));
|
|
9
|
+
sock.once('connect', () => cleanup(true));
|
|
10
|
+
sock.once('error', (err) => {
|
|
11
|
+
// ECONNREFUSED = nothing listening → port is free
|
|
12
|
+
// EACCES / ENETUNREACH / etc. = treat as unavailable (can't bind either)
|
|
13
|
+
cleanup(err.code !== 'ECONNREFUSED');
|
|
14
|
+
});
|
|
9
15
|
});
|
|
10
16
|
}
|
|
11
17
|
|
package/package.json
CHANGED
package/scripts/setup.js
CHANGED
|
@@ -52,8 +52,8 @@ const T = {
|
|
|
52
52
|
// step labels
|
|
53
53
|
step: { en: (n, total) => `Step ${n}/${total}`, zh: (n, total) => `步骤 ${n}/${total}` },
|
|
54
54
|
stepTitles: {
|
|
55
|
-
en: ['Knowledge Base', 'Template', 'Ports', 'Auth Token', 'Web Password', 'AI Provider', 'Start Mode'],
|
|
56
|
-
zh: ['知识库', '模板', '端口', 'Auth Token', 'Web 密码', 'AI 服务商', '启动方式'],
|
|
55
|
+
en: ['Knowledge Base', 'Template', 'Ports', 'Auth Token', 'Web Password', 'AI Provider', 'Start Mode', 'Agent Tools'],
|
|
56
|
+
zh: ['知识库', '模板', '端口', 'Auth Token', 'Web 密码', 'AI 服务商', '启动方式', 'Agent 工具'],
|
|
57
57
|
},
|
|
58
58
|
|
|
59
59
|
// path
|
|
@@ -122,6 +122,15 @@ const T = {
|
|
|
122
122
|
syncSetup: { en: 'Set up cross-device sync via Git?', zh: '是否配置 Git 跨设备同步?' },
|
|
123
123
|
syncLater: { en: ' → Run `mindos sync init` anytime to set up sync later.', zh: ' → 随时运行 `mindos sync init` 配置同步。' },
|
|
124
124
|
|
|
125
|
+
// mcp install step
|
|
126
|
+
mcpStepTitle: { en: 'Agent Tools (MCP)', zh: 'Agent 工具 (MCP)' },
|
|
127
|
+
mcpStepHint: { en: 'Select AI agents to configure with MindOS MCP (Space to toggle, A for all, Enter to confirm).\nAgents not yet installed can be pre-configured — they will work once you install the app.', zh: '选择要配置 MindOS MCP 的 AI Agent(空格切换,A 全选,Enter 确认)。\n未安装的 Agent 可以预先配置,安装应用后即可生效。' },
|
|
128
|
+
mcpInstalling: { en: (n) => `⏳ Configuring ${n} agent(s)...`, zh: (n) => `⏳ 正在配置 ${n} 个 Agent...` },
|
|
129
|
+
mcpInstallOk: { en: (name, path) => ` ${c.green('✔')} ${name} ${c.dim('→ ' + path)}`, zh: (name, path) => ` ${c.green('✔')} ${name} ${c.dim('→ ' + path)}` },
|
|
130
|
+
mcpInstallFail: { en: (name, msg) => ` ${c.red('✘')} ${name} ${c.dim(msg)}`, zh: (name, msg) => ` ${c.red('✘')} ${name} ${c.dim(msg)}` },
|
|
131
|
+
mcpInstallDone: { en: (n) => `✔ ${n} agent(s) configured`, zh: (n) => `✔ 已配置 ${n} 个 Agent` },
|
|
132
|
+
mcpSkipped: { en: ' → Skipped. Run `mindos mcp install` anytime to configure agents.', zh: ' → 已跳过。随时运行 `mindos mcp install` 配置 Agent。' },
|
|
133
|
+
|
|
125
134
|
// next steps (onboard — keep it minimal, details shown on `mindos start`)
|
|
126
135
|
nextSteps: {
|
|
127
136
|
en: (cmd) => [
|
|
@@ -171,7 +180,7 @@ const tf = (key, ...args) => {
|
|
|
171
180
|
|
|
172
181
|
// ── Step header ───────────────────────────────────────────────────────────────
|
|
173
182
|
|
|
174
|
-
const TOTAL_STEPS =
|
|
183
|
+
const TOTAL_STEPS = 8;
|
|
175
184
|
function stepHeader(n) {
|
|
176
185
|
const title = T.stepTitles[uiLang][n - 1] ?? T.stepTitles.en[n - 1];
|
|
177
186
|
const stepLabel = tf('step', n, TOTAL_STEPS);
|
|
@@ -387,15 +396,21 @@ const askYesNoDefault = (labelKey, arg = '') => askYesNo(labelKey, arg, true);
|
|
|
387
396
|
function isPortInUse(port) {
|
|
388
397
|
return new Promise((resolve) => {
|
|
389
398
|
const sock = createConnection({ port, host: '127.0.0.1' });
|
|
390
|
-
|
|
391
|
-
sock.
|
|
399
|
+
const cleanup = (result) => { sock.destroy(); resolve(result); };
|
|
400
|
+
sock.setTimeout(500, () => cleanup(false));
|
|
401
|
+
sock.once('connect', () => cleanup(true));
|
|
402
|
+
sock.once('error', (err) => {
|
|
403
|
+
// ECONNREFUSED = nothing listening → free; other errors = treat as in-use
|
|
404
|
+
cleanup(err.code !== 'ECONNREFUSED');
|
|
405
|
+
});
|
|
392
406
|
});
|
|
393
407
|
}
|
|
394
408
|
|
|
395
409
|
async function findFreePort(from) {
|
|
396
|
-
let p = from;
|
|
397
|
-
|
|
398
|
-
|
|
410
|
+
for (let p = from; p <= 65535; p++) {
|
|
411
|
+
if (!await isPortInUse(p)) return p;
|
|
412
|
+
}
|
|
413
|
+
return from; // fallback (extremely unlikely)
|
|
399
414
|
}
|
|
400
415
|
|
|
401
416
|
async function askPort(labelKey, defaultPort) {
|
|
@@ -503,6 +518,150 @@ async function applyTemplate(tpl, mindDir) {
|
|
|
503
518
|
}
|
|
504
519
|
}
|
|
505
520
|
|
|
521
|
+
// ── MCP Agent definitions (mirrors bin/lib/mcp-install.js) ───────────────────
|
|
522
|
+
|
|
523
|
+
const MCP_AGENTS_SETUP = {
|
|
524
|
+
'claude-code': { name: 'Claude Code', project: '.mcp.json', global: '~/.claude.json', key: 'mcpServers' },
|
|
525
|
+
'claude-desktop': { name: 'Claude Desktop', project: null, global: process.platform === 'darwin' ? '~/Library/Application Support/Claude/claude_desktop_config.json' : '~/.config/Claude/claude_desktop_config.json', key: 'mcpServers' },
|
|
526
|
+
'cursor': { name: 'Cursor', project: '.cursor/mcp.json', global: '~/.cursor/mcp.json', key: 'mcpServers' },
|
|
527
|
+
'windsurf': { name: 'Windsurf', project: null, global: '~/.codeium/windsurf/mcp_config.json', key: 'mcpServers' },
|
|
528
|
+
'cline': { name: 'Cline', project: null, global: process.platform === 'darwin' ? '~/Library/Application Support/Code/User/globalStorage/saoudrizwan.claude-dev/settings/cline_mcp_settings.json' : '~/.config/Code/User/globalStorage/saoudrizwan.claude-dev/settings/cline_mcp_settings.json', key: 'mcpServers' },
|
|
529
|
+
'trae': { name: 'Trae', project: '.trae/mcp.json', global: '~/.trae/mcp.json', key: 'mcpServers' },
|
|
530
|
+
'gemini-cli': { name: 'Gemini CLI', project: '.gemini/settings.json', global: '~/.gemini/settings.json', key: 'mcpServers' },
|
|
531
|
+
'openclaw': { name: 'OpenClaw', project: null, global: '~/.openclaw/mcp.json', key: 'mcpServers' },
|
|
532
|
+
'codebuddy': { name: 'CodeBuddy', project: null, global: '~/.claude-internal/.claude.json', key: 'mcpServers' },
|
|
533
|
+
};
|
|
534
|
+
|
|
535
|
+
function expandHomePath(p) {
|
|
536
|
+
return p.startsWith('~/') ? resolve(homedir(), p.slice(2)) : p;
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
/** Detect if an agent already has mindos configured (for pre-selection). */
|
|
540
|
+
function isAgentInstalled(agentKey) {
|
|
541
|
+
const agent = MCP_AGENTS_SETUP[agentKey];
|
|
542
|
+
if (!agent) return false;
|
|
543
|
+
for (const cfgPath of [agent.global, agent.project]) {
|
|
544
|
+
if (!cfgPath) continue;
|
|
545
|
+
const abs = expandHomePath(cfgPath);
|
|
546
|
+
if (!existsSync(abs)) continue;
|
|
547
|
+
try {
|
|
548
|
+
const config = JSON.parse(readFileSync(abs, 'utf-8'));
|
|
549
|
+
if (config[agent.key]?.mindos) return true;
|
|
550
|
+
} catch { /* ignore */ }
|
|
551
|
+
}
|
|
552
|
+
return false;
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
/**
|
|
556
|
+
* Step 8: interactive multi-select of agents to configure, then install.
|
|
557
|
+
* Uses the same interactiveMultiSelect as mcp-install.js (re-implemented inline
|
|
558
|
+
* because this script uses its own raw-mode helpers).
|
|
559
|
+
*/
|
|
560
|
+
async function runMcpInstallStep(mcpPort, authToken) {
|
|
561
|
+
const keys = Object.keys(MCP_AGENTS_SETUP);
|
|
562
|
+
|
|
563
|
+
// Build options with installed status shown as hint
|
|
564
|
+
const options = keys.map(k => {
|
|
565
|
+
const installed = isAgentInstalled(k);
|
|
566
|
+
return {
|
|
567
|
+
label: MCP_AGENTS_SETUP[k].name,
|
|
568
|
+
hint: installed ? (uiLang === 'zh' ? '已安装' : 'installed') : (uiLang === 'zh' ? '未安装' : 'not installed'),
|
|
569
|
+
value: k,
|
|
570
|
+
preselect: installed,
|
|
571
|
+
};
|
|
572
|
+
});
|
|
573
|
+
|
|
574
|
+
// Multi-select using raw mode
|
|
575
|
+
const selected = await (async () => {
|
|
576
|
+
return new Promise((resolveSelected) => {
|
|
577
|
+
let cursor = 0;
|
|
578
|
+
const chosen = new Set(options.map((o, i) => o.preselect ? i : -1).filter(i => i >= 0));
|
|
579
|
+
|
|
580
|
+
const render = (first = false) => {
|
|
581
|
+
if (!first) write(`\x1b[${options.length + 2}A\x1b[J`);
|
|
582
|
+
write(`${c.bold(uiLang === 'zh' ? '选择 Agent:' : 'Select agents:')} ${c.dim(uiLang === 'zh' ? '(↑↓ 移动 空格 切换 A 全选 Enter 确认)' : '(↑↓ move Space toggle A all Enter confirm)')}\n`);
|
|
583
|
+
for (let i = 0; i < options.length; i++) {
|
|
584
|
+
const o = options[i];
|
|
585
|
+
const check = chosen.has(i) ? c.green('✔') : c.dim('○');
|
|
586
|
+
const pointer = i === cursor ? c.cyan('❯') : ' ';
|
|
587
|
+
const label = i === cursor ? (chosen.has(i) ? c.green(o.label) : c.cyan(o.label)) : (chosen.has(i) ? c.green(o.label) : o.label);
|
|
588
|
+
write(` ${pointer} ${check} ${label} ${c.dim('(' + o.hint + ')')}\n`);
|
|
589
|
+
}
|
|
590
|
+
write(c.dim(` ${chosen.size} ${uiLang === 'zh' ? '已选' : 'selected'}\n`));
|
|
591
|
+
};
|
|
592
|
+
|
|
593
|
+
write('\n');
|
|
594
|
+
render(true);
|
|
595
|
+
|
|
596
|
+
process.stdin.setRawMode(true);
|
|
597
|
+
process.stdin.resume();
|
|
598
|
+
process.stdin.setEncoding('utf8');
|
|
599
|
+
|
|
600
|
+
const onKey = (key) => {
|
|
601
|
+
if (key === '\x03') { cleanup(); process.exit(1); }
|
|
602
|
+
if (key === `${ESC}[A`) { cursor = (cursor - 1 + options.length) % options.length; render(); }
|
|
603
|
+
else if (key === `${ESC}[B`) { cursor = (cursor + 1) % options.length; render(); }
|
|
604
|
+
else if (key === ' ') {
|
|
605
|
+
if (chosen.has(cursor)) chosen.delete(cursor); else chosen.add(cursor);
|
|
606
|
+
render();
|
|
607
|
+
} else if (key === 'a' || key === 'A') {
|
|
608
|
+
if (chosen.size === options.length) chosen.clear();
|
|
609
|
+
else options.forEach((_, i) => chosen.add(i));
|
|
610
|
+
render();
|
|
611
|
+
} else if (key === '\r' || key === '\n') {
|
|
612
|
+
cleanup();
|
|
613
|
+
resolveSelected([...chosen].sort().map(i => options[i].value));
|
|
614
|
+
}
|
|
615
|
+
};
|
|
616
|
+
|
|
617
|
+
const cleanup = () => {
|
|
618
|
+
process.stdin.removeListener('data', onKey);
|
|
619
|
+
process.stdin.setRawMode(false);
|
|
620
|
+
process.stdin.pause();
|
|
621
|
+
};
|
|
622
|
+
|
|
623
|
+
process.stdin.on('data', onKey);
|
|
624
|
+
});
|
|
625
|
+
})();
|
|
626
|
+
|
|
627
|
+
if (selected.length === 0) {
|
|
628
|
+
write(c.dim(t('mcpSkipped') + '\n'));
|
|
629
|
+
return;
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
write('\n' + c.dim(tf('mcpInstalling', selected.length) + '\n'));
|
|
633
|
+
|
|
634
|
+
// stdio entry (same as mcp-install.js)
|
|
635
|
+
const entry = { type: 'stdio', command: 'mindos', args: ['mcp'], env: { MCP_TRANSPORT: 'stdio' } };
|
|
636
|
+
let okCount = 0;
|
|
637
|
+
|
|
638
|
+
for (const agentKey of selected) {
|
|
639
|
+
const agent = MCP_AGENTS_SETUP[agentKey];
|
|
640
|
+
// prefer global scope; fall back to project
|
|
641
|
+
const cfgPath = agent.global || agent.project;
|
|
642
|
+
if (!cfgPath) {
|
|
643
|
+
write(tf('mcpInstallFail', agent.name, 'no config path') + '\n');
|
|
644
|
+
continue;
|
|
645
|
+
}
|
|
646
|
+
const abs = expandHomePath(cfgPath);
|
|
647
|
+
try {
|
|
648
|
+
let config = {};
|
|
649
|
+
if (existsSync(abs)) config = JSON.parse(readFileSync(abs, 'utf-8'));
|
|
650
|
+
if (!config[agent.key]) config[agent.key] = {};
|
|
651
|
+
config[agent.key].mindos = entry;
|
|
652
|
+
const dir = resolve(abs, '..');
|
|
653
|
+
if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
|
|
654
|
+
writeFileSync(abs, JSON.stringify(config, null, 2) + '\n', 'utf-8');
|
|
655
|
+
write(tf('mcpInstallOk', agent.name, cfgPath) + '\n');
|
|
656
|
+
okCount++;
|
|
657
|
+
} catch (err) {
|
|
658
|
+
write(tf('mcpInstallFail', agent.name, String(err)) + '\n');
|
|
659
|
+
}
|
|
660
|
+
}
|
|
661
|
+
|
|
662
|
+
console.log(`\n${c.green(tf('mcpInstallDone', okCount))}`);
|
|
663
|
+
}
|
|
664
|
+
|
|
506
665
|
// ── GUI Setup ─────────────────────────────────────────────────────────────────
|
|
507
666
|
|
|
508
667
|
function openBrowser(url) {
|
|
@@ -671,8 +830,13 @@ async function main() {
|
|
|
671
830
|
// ── Step 3: Ports ─────────────────────────────────────────────────────────
|
|
672
831
|
write('\n');
|
|
673
832
|
stepHeader(3);
|
|
674
|
-
|
|
675
|
-
|
|
833
|
+
let webPort, mcpPort;
|
|
834
|
+
while (true) {
|
|
835
|
+
webPort = await askPort('webPortPrompt', 3000);
|
|
836
|
+
mcpPort = await askPort('mcpPortPrompt', webPort === 8787 ? 8788 : 8787);
|
|
837
|
+
if (webPort !== mcpPort) break;
|
|
838
|
+
write(c.yellow(` ⚠ ${uiLang === 'zh' ? 'Web 端口和 MCP 端口不能相同,请重新选择' : 'Web port and MCP port must be different — please choose again'}\n`));
|
|
839
|
+
}
|
|
676
840
|
|
|
677
841
|
// ── Step 4: Auth token ────────────────────────────────────────────────────
|
|
678
842
|
write('\n');
|
|
@@ -792,6 +956,13 @@ async function main() {
|
|
|
792
956
|
writeFileSync(CONFIG_PATH, JSON.stringify(config, null, 2) + '\n');
|
|
793
957
|
console.log(`\n${c.green(t('cfgSaved'))}: ${c.dim(CONFIG_PATH)}`);
|
|
794
958
|
|
|
959
|
+
// ── Step 8: MCP Agent Install ──────────────────────────────────────────────
|
|
960
|
+
write('\n');
|
|
961
|
+
stepHeader(8);
|
|
962
|
+
write(c.dim(tf('mcpStepHint') + '\n\n'));
|
|
963
|
+
|
|
964
|
+
await runMcpInstallStep(mcpPort, authToken);
|
|
965
|
+
|
|
795
966
|
// ── Sync setup (optional) ──────────────────────────────────────────────────
|
|
796
967
|
const wantSync = await askYesNo('syncSetup');
|
|
797
968
|
if (wantSync) {
|