@geminilight/mindos 0.5.1 → 0.5.3
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/health/route.ts +6 -0
- package/app/app/api/mcp/agents/route.ts +1 -49
- package/app/app/api/mcp/install/route.ts +1 -24
- package/app/app/api/restart/route.ts +22 -0
- package/app/app/api/setup/check-path/route.ts +35 -0
- package/app/app/api/setup/check-port/route.ts +21 -5
- package/app/app/api/setup/ls/route.ts +38 -0
- package/app/app/api/setup/route.ts +49 -0
- package/app/app/setup/page.tsx +3 -2
- package/app/components/HomeContent.tsx +2 -0
- package/app/components/SettingsModal.tsx +9 -0
- package/app/components/SetupWizard.tsx +343 -46
- package/app/components/WelcomeBanner.tsx +63 -0
- package/app/lib/i18n.ts +40 -2
- package/app/lib/mcp-agents.ts +48 -0
- package/app/lib/settings.ts +1 -1
- package/bin/cli.js +3 -1
- package/bin/lib/gateway.js +1 -1
- package/bin/lib/mcp-agents.js +16 -0
- package/bin/lib/mcp-install.js +2 -11
- package/package.json +1 -1
- package/scripts/setup.js +195 -86
- package/skills/mindos/SKILL.md +7 -6
- package/skills/mindos-zh/SKILL.md +7 -7
package/scripts/setup.js
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* Usage: npm run setup OR mindos onboard
|
|
7
7
|
*
|
|
8
8
|
* Steps:
|
|
9
|
-
* 1. Choose knowledge base path → default ~/MindOS
|
|
9
|
+
* 1. Choose knowledge base path → default ~/MindOS/mind (same as GUI)
|
|
10
10
|
* 2. Choose template (en / zh / empty / custom) → copy to knowledge base path
|
|
11
11
|
* 3. Choose ports (web + mcp) — checked for conflicts upfront
|
|
12
12
|
* 4. Auth token (auto-generated or passphrase-seeded)
|
|
@@ -29,6 +29,8 @@ import { pipeline } from 'node:stream/promises';
|
|
|
29
29
|
import { execSync, spawn } from 'node:child_process';
|
|
30
30
|
import { randomBytes, createHash } from 'node:crypto';
|
|
31
31
|
import { createConnection } from 'node:net';
|
|
32
|
+
import http from 'node:http';
|
|
33
|
+
import { MCP_AGENTS } from '../bin/lib/mcp-agents.js';
|
|
32
34
|
|
|
33
35
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
34
36
|
const ROOT = resolve(__dirname, '..');
|
|
@@ -39,21 +41,21 @@ const CONFIG_PATH = resolve(MINDOS_DIR, 'config.json');
|
|
|
39
41
|
|
|
40
42
|
const T = {
|
|
41
43
|
title: { en: '🧠 MindOS Setup', zh: '🧠 MindOS 初始化' },
|
|
42
|
-
langHint: { en: ' ← →
|
|
44
|
+
langHint: { en: ' ← → 切换中文 ↑ ↓ navigate Enter confirm', zh: ' ← → switch to English ↑ ↓ 上下切换 Enter 确认' },
|
|
43
45
|
|
|
44
46
|
// mode selection
|
|
45
|
-
modePrompt: { en: '
|
|
46
|
-
modeOpts: { en: ['
|
|
47
|
+
modePrompt: { en: 'How would you like to set up?', zh: '选择配置方式' },
|
|
48
|
+
modeOpts: { en: ['Continue here in terminal (CLI)', 'Open browser to set up (recommended)'], zh: ['在终端继续配置(CLI)', '打开浏览器配置(推荐)'] },
|
|
47
49
|
modeVals: ['cli', 'gui'],
|
|
48
|
-
guiStarting: { en: '⏳ Starting
|
|
49
|
-
guiReady: { en: (url) => `🌐
|
|
50
|
+
guiStarting: { en: '⏳ Starting MindOS, please wait...', zh: '⏳ 正在启动 MindOS,请稍候...' },
|
|
51
|
+
guiReady: { en: (url) => `🌐 Opening setup in browser: ${url}`, zh: (url) => `🌐 在浏览器中打开配置页面: ${url}` },
|
|
50
52
|
guiOpenFailed: { en: (url) => ` Could not open browser automatically. Open this URL manually:\n ${url}`, zh: (url) => ` 无法自动打开浏览器,请手动访问:\n ${url}` },
|
|
51
53
|
|
|
52
54
|
// step labels
|
|
53
55
|
step: { en: (n, total) => `Step ${n}/${total}`, zh: (n, total) => `步骤 ${n}/${total}` },
|
|
54
56
|
stepTitles: {
|
|
55
|
-
en: ['Knowledge Base', '
|
|
56
|
-
zh: ['知识库', '
|
|
57
|
+
en: ['Knowledge Base', 'AI Provider', 'Ports', 'Auth Token', 'Web Password', 'Start Mode', 'Agent Tools'],
|
|
58
|
+
zh: ['知识库', 'AI 服务商', '端口', 'Auth Token', 'Web 密码', '启动方式', 'Agent 工具'],
|
|
57
59
|
},
|
|
58
60
|
|
|
59
61
|
// path
|
|
@@ -131,6 +133,11 @@ const T = {
|
|
|
131
133
|
mcpInstallDone: { en: (n) => `✔ ${n} agent(s) configured`, zh: (n) => `✔ 已配置 ${n} 个 Agent` },
|
|
132
134
|
mcpSkipped: { en: ' → Skipped. Run `mindos mcp install` anytime to configure agents.', zh: ' → 已跳过。随时运行 `mindos mcp install` 配置 Agent。' },
|
|
133
135
|
|
|
136
|
+
// restart prompts (re-onboard with config changes)
|
|
137
|
+
restartRequired: { en: 'Config changed. Service restart required.', zh: '配置已变更,需要重启服务。' },
|
|
138
|
+
restartNow: { en: 'Restart now?', zh: '立即重启?' },
|
|
139
|
+
changesOnNextStart: { en: 'Changes will take effect on next start.', zh: '变更将在下次启动时生效。' },
|
|
140
|
+
|
|
134
141
|
// next steps (onboard — keep it minimal, details shown on `mindos start`)
|
|
135
142
|
nextSteps: {
|
|
136
143
|
en: (cmd) => [
|
|
@@ -171,7 +178,26 @@ function write(s) { process.stdout.write(s); }
|
|
|
171
178
|
|
|
172
179
|
// ── State ─────────────────────────────────────────────────────────────────────
|
|
173
180
|
|
|
174
|
-
|
|
181
|
+
function detectSystemLang() {
|
|
182
|
+
// Check env vars first (Linux / macOS / WSL)
|
|
183
|
+
const vars = [
|
|
184
|
+
process.env.LANG,
|
|
185
|
+
process.env.LC_ALL,
|
|
186
|
+
process.env.LC_MESSAGES,
|
|
187
|
+
process.env.LANGUAGE,
|
|
188
|
+
].filter(Boolean).join(' ').toLowerCase();
|
|
189
|
+
if (vars.includes('zh')) return 'zh';
|
|
190
|
+
|
|
191
|
+
// Fallback: Intl API (works on Windows where LANG is often unset)
|
|
192
|
+
try {
|
|
193
|
+
const locale = Intl.DateTimeFormat().resolvedOptions().locale;
|
|
194
|
+
if (locale.toLowerCase().startsWith('zh')) return 'zh';
|
|
195
|
+
} catch {}
|
|
196
|
+
|
|
197
|
+
return 'en';
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
let uiLang = detectSystemLang();
|
|
175
201
|
const t = (key) => T[key]?.[uiLang] ?? T[key]?.en ?? '';
|
|
176
202
|
const tf = (key, ...args) => {
|
|
177
203
|
const v = T[key]?.[uiLang] ?? T[key]?.en;
|
|
@@ -180,7 +206,7 @@ const tf = (key, ...args) => {
|
|
|
180
206
|
|
|
181
207
|
// ── Step header ───────────────────────────────────────────────────────────────
|
|
182
208
|
|
|
183
|
-
const TOTAL_STEPS =
|
|
209
|
+
const TOTAL_STEPS = 7;
|
|
184
210
|
function stepHeader(n) {
|
|
185
211
|
const title = T.stepTitles[uiLang][n - 1] ?? T.stepTitles.en[n - 1];
|
|
186
212
|
const stepLabel = tf('step', n, TOTAL_STEPS);
|
|
@@ -397,7 +423,7 @@ function isPortInUse(port) {
|
|
|
397
423
|
return new Promise((resolve) => {
|
|
398
424
|
const sock = createConnection({ port, host: '127.0.0.1' });
|
|
399
425
|
const cleanup = (result) => { sock.destroy(); resolve(result); };
|
|
400
|
-
sock.setTimeout(500, () => cleanup(
|
|
426
|
+
sock.setTimeout(500, () => cleanup(true));
|
|
401
427
|
sock.once('connect', () => cleanup(true));
|
|
402
428
|
sock.once('error', (err) => {
|
|
403
429
|
// ECONNREFUSED = nothing listening → free; other errors = treat as in-use
|
|
@@ -406,6 +432,25 @@ function isPortInUse(port) {
|
|
|
406
432
|
});
|
|
407
433
|
}
|
|
408
434
|
|
|
435
|
+
async function isSelfPort(port) {
|
|
436
|
+
try {
|
|
437
|
+
return await new Promise((resolve) => {
|
|
438
|
+
const req = http.get(`http://127.0.0.1:${port}/api/health`, { timeout: 800 }, (res) => {
|
|
439
|
+
let body = '';
|
|
440
|
+
res.on('data', chunk => { body += chunk; });
|
|
441
|
+
res.on('end', () => {
|
|
442
|
+
try {
|
|
443
|
+
const data = JSON.parse(body);
|
|
444
|
+
resolve(data.service === 'mindos');
|
|
445
|
+
} catch { resolve(false); }
|
|
446
|
+
});
|
|
447
|
+
});
|
|
448
|
+
req.on('error', () => resolve(false));
|
|
449
|
+
req.on('timeout', () => { req.destroy(); resolve(false); });
|
|
450
|
+
});
|
|
451
|
+
} catch { return false; }
|
|
452
|
+
}
|
|
453
|
+
|
|
409
454
|
async function findFreePort(from) {
|
|
410
455
|
for (let p = from; p <= 65535; p++) {
|
|
411
456
|
if (!await isPortInUse(p)) return p;
|
|
@@ -414,7 +459,15 @@ async function findFreePort(from) {
|
|
|
414
459
|
}
|
|
415
460
|
|
|
416
461
|
async function askPort(labelKey, defaultPort) {
|
|
417
|
-
let port =
|
|
462
|
+
let port = defaultPort;
|
|
463
|
+
// If the default port is in use, check if it's our own service (self = ok to keep)
|
|
464
|
+
if (await isPortInUse(port)) {
|
|
465
|
+
if (await isSelfPort(port)) {
|
|
466
|
+
// Already running on this port — keep it as default
|
|
467
|
+
} else {
|
|
468
|
+
port = await findFreePort(port + 1);
|
|
469
|
+
}
|
|
470
|
+
}
|
|
418
471
|
while (true) {
|
|
419
472
|
const input = (await askText(labelKey, String(port))).trim();
|
|
420
473
|
const parsed = parseInt(input, 10);
|
|
@@ -423,6 +476,10 @@ async function askPort(labelKey, defaultPort) {
|
|
|
423
476
|
continue;
|
|
424
477
|
}
|
|
425
478
|
if (await isPortInUse(parsed)) {
|
|
479
|
+
// Check if it's our own service — acceptable to keep
|
|
480
|
+
if (await isSelfPort(parsed)) {
|
|
481
|
+
return parsed;
|
|
482
|
+
}
|
|
426
483
|
const next = await findFreePort(parsed + 1);
|
|
427
484
|
write(c.yellow(tf('portInUse', parsed) + '\n'));
|
|
428
485
|
port = next;
|
|
@@ -518,19 +575,7 @@ async function applyTemplate(tpl, mindDir) {
|
|
|
518
575
|
}
|
|
519
576
|
}
|
|
520
577
|
|
|
521
|
-
//
|
|
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
|
-
};
|
|
578
|
+
// MCP_AGENTS imported from bin/lib/mcp-agents.js
|
|
534
579
|
|
|
535
580
|
function expandHomePath(p) {
|
|
536
581
|
return p.startsWith('~/') ? resolve(homedir(), p.slice(2)) : p;
|
|
@@ -538,7 +583,7 @@ function expandHomePath(p) {
|
|
|
538
583
|
|
|
539
584
|
/** Detect if an agent already has mindos configured (for pre-selection). */
|
|
540
585
|
function isAgentInstalled(agentKey) {
|
|
541
|
-
const agent =
|
|
586
|
+
const agent = MCP_AGENTS[agentKey];
|
|
542
587
|
if (!agent) return false;
|
|
543
588
|
for (const cfgPath of [agent.global, agent.project]) {
|
|
544
589
|
if (!cfgPath) continue;
|
|
@@ -558,13 +603,13 @@ function isAgentInstalled(agentKey) {
|
|
|
558
603
|
* because this script uses its own raw-mode helpers).
|
|
559
604
|
*/
|
|
560
605
|
async function runMcpInstallStep(mcpPort, authToken) {
|
|
561
|
-
const keys = Object.keys(
|
|
606
|
+
const keys = Object.keys(MCP_AGENTS);
|
|
562
607
|
|
|
563
608
|
// Build options with installed status shown as hint
|
|
564
609
|
const options = keys.map(k => {
|
|
565
610
|
const installed = isAgentInstalled(k);
|
|
566
611
|
return {
|
|
567
|
-
label:
|
|
612
|
+
label: MCP_AGENTS[k].name,
|
|
568
613
|
hint: installed ? (uiLang === 'zh' ? '已安装' : 'installed') : (uiLang === 'zh' ? '未安装' : 'not installed'),
|
|
569
614
|
value: k,
|
|
570
615
|
preselect: installed,
|
|
@@ -636,7 +681,7 @@ async function runMcpInstallStep(mcpPort, authToken) {
|
|
|
636
681
|
let okCount = 0;
|
|
637
682
|
|
|
638
683
|
for (const agentKey of selected) {
|
|
639
|
-
const agent =
|
|
684
|
+
const agent = MCP_AGENTS[agentKey];
|
|
640
685
|
// prefer global scope; fall back to project
|
|
641
686
|
const cfgPath = agent.global || agent.project;
|
|
642
687
|
if (!cfgPath) {
|
|
@@ -778,7 +823,7 @@ async function main() {
|
|
|
778
823
|
const existingMode = existing.startMode || 'start';
|
|
779
824
|
const existingMcpPort = existing.mcpPort || 8787;
|
|
780
825
|
const existingAuth = existing.authToken || '';
|
|
781
|
-
const existingMindRoot = existing.mindRoot || resolve(
|
|
826
|
+
const existingMindRoot = existing.mindRoot || resolve(homedir(), 'MindOS', 'mind');
|
|
782
827
|
console.log(`\n${c.green(t('cfgKept'))} ${c.dim(CONFIG_PATH)}`);
|
|
783
828
|
write(c.dim(t('cfgKeptNote') + '\n'));
|
|
784
829
|
const installDaemon = process.argv.includes('--install-daemon');
|
|
@@ -790,12 +835,23 @@ async function main() {
|
|
|
790
835
|
// ── Step 1: Knowledge base path ───────────────────────────────────────────
|
|
791
836
|
stepHeader(1);
|
|
792
837
|
|
|
838
|
+
// Resume: read existing config to offer current values as defaults
|
|
839
|
+
let resumeCfg = {};
|
|
840
|
+
try { resumeCfg = JSON.parse(readFileSync(CONFIG_PATH, 'utf-8')); } catch { /* first run */ }
|
|
841
|
+
|
|
793
842
|
const { readdirSync } = await import('node:fs');
|
|
794
843
|
let mindDir;
|
|
795
844
|
|
|
845
|
+
// Default KB path: existing mindRoot if set, otherwise ~/MindOS (same as GUI default)
|
|
846
|
+
const HOME = homedir();
|
|
847
|
+
const kbDefault = resumeCfg.mindRoot || resolve(HOME, 'MindOS', 'mind');
|
|
848
|
+
|
|
796
849
|
while (true) {
|
|
797
|
-
const input = (await askText('pathPrompt',
|
|
798
|
-
|
|
850
|
+
const input = (await askText('pathPrompt', kbDefault)).trim();
|
|
851
|
+
// If absolute path entered, use as-is; if relative (no leading /), resolve from home
|
|
852
|
+
const resolved = input.startsWith('/') || input.startsWith('~/')
|
|
853
|
+
? (input.startsWith('~/') ? resolve(HOME, input.slice(2)) : input)
|
|
854
|
+
: resolve(HOME, input);
|
|
799
855
|
write(tf('pathResolved', resolved) + '\n');
|
|
800
856
|
mindDir = resolved;
|
|
801
857
|
|
|
@@ -817,9 +873,8 @@ async function main() {
|
|
|
817
873
|
if (choice === 'reselect') { write('\n'); continue; }
|
|
818
874
|
break;
|
|
819
875
|
} else {
|
|
820
|
-
// ── Step
|
|
876
|
+
// ── Template selection (part of Step 1) ─────────────────────────────
|
|
821
877
|
write('\n');
|
|
822
|
-
stepHeader(2);
|
|
823
878
|
const tpl = await select('tplPrompt', 'tplOptions', 'tplValues');
|
|
824
879
|
mkdirSync(mindDir, { recursive: true });
|
|
825
880
|
await applyTemplate(tpl, mindDir);
|
|
@@ -827,60 +882,27 @@ async function main() {
|
|
|
827
882
|
}
|
|
828
883
|
}
|
|
829
884
|
|
|
830
|
-
// ── Step
|
|
885
|
+
// ── Step 2: AI Provider + API Key ─────────────────────────────────────────
|
|
831
886
|
write('\n');
|
|
832
|
-
stepHeader(
|
|
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
|
-
}
|
|
840
|
-
|
|
841
|
-
// ── Step 4: Auth token ────────────────────────────────────────────────────
|
|
842
|
-
write('\n');
|
|
843
|
-
stepHeader(4);
|
|
844
|
-
const authSeed = await askText('authPrompt');
|
|
845
|
-
const authToken = generateToken(authSeed);
|
|
846
|
-
console.log(`${c.green(t('tokenGenerated'))}: ${c.cyan(authToken)}`);
|
|
847
|
-
|
|
848
|
-
// ── Step 5: Web UI password ───────────────────────────────────────────────
|
|
849
|
-
write('\n');
|
|
850
|
-
stepHeader(5);
|
|
851
|
-
let webPassword = '';
|
|
852
|
-
while (true) {
|
|
853
|
-
webPassword = await askText('webPassPrompt');
|
|
854
|
-
if (webPassword) break;
|
|
855
|
-
write(c.yellow(t('webPassWarn') + '\n'));
|
|
856
|
-
const confirmed = await askYesNo('webPassSkip');
|
|
857
|
-
if (confirmed) break;
|
|
858
|
-
}
|
|
859
|
-
|
|
860
|
-
// ── Step 6: AI Provider + API Key ─────────────────────────────────────────
|
|
861
|
-
write('\n');
|
|
862
|
-
stepHeader(6);
|
|
887
|
+
stepHeader(2);
|
|
863
888
|
|
|
864
889
|
const provider = await select('providerPrompt', 'providerOpts', 'providerVals');
|
|
865
890
|
const isSkip = provider === 'skip';
|
|
866
891
|
const isAnthropic = provider === 'anthropic';
|
|
867
892
|
|
|
868
|
-
// preserve existing provider configs
|
|
893
|
+
// preserve existing provider configs (use resumeCfg already read at top of main)
|
|
869
894
|
let existingProviders = {
|
|
870
895
|
anthropic: { apiKey: '', model: 'claude-sonnet-4-6' },
|
|
871
896
|
openai: { apiKey: '', model: 'gpt-5.4', baseUrl: '' },
|
|
872
897
|
};
|
|
873
898
|
let existingAiProvider = 'anthropic';
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
}
|
|
882
|
-
if (existing.ai?.provider) existingAiProvider = existing.ai.provider;
|
|
883
|
-
} catch { /* ignore */ }
|
|
899
|
+
if (resumeCfg.ai?.providers) {
|
|
900
|
+
existingProviders = { ...existingProviders, ...resumeCfg.ai.providers };
|
|
901
|
+
} else if (resumeCfg.ai?.anthropicApiKey) {
|
|
902
|
+
existingProviders.anthropic = { apiKey: resumeCfg.ai.anthropicApiKey || '', model: resumeCfg.ai.anthropicModel || 'claude-sonnet-4-6' };
|
|
903
|
+
existingProviders.openai = { apiKey: resumeCfg.ai.openaiApiKey || '', model: resumeCfg.ai.openaiModel || 'gpt-5.4', baseUrl: resumeCfg.ai.openaiBaseUrl || '' };
|
|
904
|
+
}
|
|
905
|
+
if (resumeCfg.ai?.provider) existingAiProvider = resumeCfg.ai.provider;
|
|
884
906
|
|
|
885
907
|
if (isSkip) {
|
|
886
908
|
write(c.dim(t('providerSkip') + '\n'));
|
|
@@ -905,9 +927,69 @@ async function main() {
|
|
|
905
927
|
}
|
|
906
928
|
}
|
|
907
929
|
|
|
908
|
-
// ── Step
|
|
930
|
+
// ── Step 3: Ports ─────────────────────────────────────────────────────────
|
|
909
931
|
write('\n');
|
|
910
|
-
stepHeader(
|
|
932
|
+
stepHeader(3);
|
|
933
|
+
const existingCfg = resumeCfg;
|
|
934
|
+
const defaultWebPort = typeof existingCfg.port === 'number' ? existingCfg.port : 3000;
|
|
935
|
+
const defaultMcpPort = typeof existingCfg.mcpPort === 'number' ? existingCfg.mcpPort : (defaultWebPort === 8787 ? 8788 : 8787);
|
|
936
|
+
let webPort, mcpPort;
|
|
937
|
+
while (true) {
|
|
938
|
+
webPort = await askPort('webPortPrompt', defaultWebPort);
|
|
939
|
+
mcpPort = await askPort('mcpPortPrompt', defaultMcpPort);
|
|
940
|
+
if (webPort !== mcpPort) break;
|
|
941
|
+
write(c.yellow(` ⚠ ${uiLang === 'zh' ? 'Web 端口和 MCP 端口不能相同,请重新选择' : 'Web port and MCP port must be different — please choose again'}\n`));
|
|
942
|
+
}
|
|
943
|
+
|
|
944
|
+
// ── Step 4: Auth token ────────────────────────────────────────────────────
|
|
945
|
+
write('\n');
|
|
946
|
+
stepHeader(4);
|
|
947
|
+
// Resume: if config already has a token, offer it as the default (Enter = keep)
|
|
948
|
+
const existingToken = existingCfg.authToken || '';
|
|
949
|
+
let authToken;
|
|
950
|
+
if (existingToken) {
|
|
951
|
+
const masked = existingToken.length > 8 ? existingToken.slice(0, 8) + '····' : existingToken;
|
|
952
|
+
write(c.dim(` ${uiLang === 'zh' ? '现有 token:' : 'Current token:'} ${c.cyan(masked)}\n`));
|
|
953
|
+
const keepToken = await askYesNoDefault('cfgConfirm');
|
|
954
|
+
if (keepToken) {
|
|
955
|
+
authToken = existingToken;
|
|
956
|
+
console.log(`${c.green(t('tokenGenerated'))}: ${c.cyan(existingToken.slice(0, 8) + '····')}`);
|
|
957
|
+
} else {
|
|
958
|
+
const authSeed = await askText('authPrompt');
|
|
959
|
+
authToken = generateToken(authSeed);
|
|
960
|
+
console.log(`${c.green(t('tokenGenerated'))}: ${c.cyan(authToken)}`);
|
|
961
|
+
}
|
|
962
|
+
} else {
|
|
963
|
+
const authSeed = await askText('authPrompt');
|
|
964
|
+
authToken = generateToken(authSeed);
|
|
965
|
+
console.log(`${c.green(t('tokenGenerated'))}: ${c.cyan(authToken)}`);
|
|
966
|
+
}
|
|
967
|
+
|
|
968
|
+
// ── Step 5: Web UI password ───────────────────────────────────────────────
|
|
969
|
+
write('\n');
|
|
970
|
+
stepHeader(5);
|
|
971
|
+
let webPassword = '';
|
|
972
|
+
const existingPassword = existingCfg.webPassword || '';
|
|
973
|
+
if (existingPassword) {
|
|
974
|
+
write(c.dim(` ${uiLang === 'zh' ? '已设置密码(Enter 保留)' : 'Password is set (Enter to keep)'}\n`));
|
|
975
|
+
const keepPass = await askYesNoDefault('cfgConfirm');
|
|
976
|
+
if (keepPass) {
|
|
977
|
+
webPassword = existingPassword;
|
|
978
|
+
}
|
|
979
|
+
}
|
|
980
|
+
if (!webPassword) {
|
|
981
|
+
while (true) {
|
|
982
|
+
webPassword = await askText('webPassPrompt');
|
|
983
|
+
if (webPassword) break;
|
|
984
|
+
write(c.yellow(t('webPassWarn') + '\n'));
|
|
985
|
+
const confirmed = await askYesNo('webPassSkip');
|
|
986
|
+
if (confirmed) break;
|
|
987
|
+
}
|
|
988
|
+
}
|
|
989
|
+
|
|
990
|
+
// ── Step 6: Start Mode ──────────────────────────────────────────────────
|
|
991
|
+
write('\n');
|
|
992
|
+
stepHeader(6);
|
|
911
993
|
|
|
912
994
|
let startMode = 'start';
|
|
913
995
|
const daemonPlatform = process.platform === 'darwin' || process.platform === 'linux';
|
|
@@ -952,13 +1034,22 @@ async function main() {
|
|
|
952
1034
|
process.exit(0);
|
|
953
1035
|
}
|
|
954
1036
|
|
|
1037
|
+
const isResuming = Object.keys(resumeCfg).length > 0;
|
|
1038
|
+
const needsRestart = isResuming && (
|
|
1039
|
+
config.port !== (resumeCfg.port ?? 3000) ||
|
|
1040
|
+
config.mcpPort !== (resumeCfg.mcpPort ?? 8787) ||
|
|
1041
|
+
config.mindRoot !== (resumeCfg.mindRoot ?? '') ||
|
|
1042
|
+
config.authToken !== (resumeCfg.authToken ?? '') ||
|
|
1043
|
+
config.webPassword !== (resumeCfg.webPassword ?? '')
|
|
1044
|
+
);
|
|
1045
|
+
|
|
955
1046
|
mkdirSync(MINDOS_DIR, { recursive: true });
|
|
956
1047
|
writeFileSync(CONFIG_PATH, JSON.stringify(config, null, 2) + '\n');
|
|
957
1048
|
console.log(`\n${c.green(t('cfgSaved'))}: ${c.dim(CONFIG_PATH)}`);
|
|
958
1049
|
|
|
959
|
-
// ── Step
|
|
1050
|
+
// ── Step 7: MCP Agent Install ──────────────────────────────────────────────
|
|
960
1051
|
write('\n');
|
|
961
|
-
stepHeader(
|
|
1052
|
+
stepHeader(7);
|
|
962
1053
|
write(c.dim(tf('mcpStepHint') + '\n\n'));
|
|
963
1054
|
|
|
964
1055
|
await runMcpInstallStep(mcpPort, authToken);
|
|
@@ -973,7 +1064,7 @@ async function main() {
|
|
|
973
1064
|
}
|
|
974
1065
|
|
|
975
1066
|
const installDaemon = startMode === 'daemon' || process.argv.includes('--install-daemon');
|
|
976
|
-
finish(mindDir, config.startMode, config.mcpPort, config.authToken, installDaemon);
|
|
1067
|
+
finish(mindDir, config.startMode, config.mcpPort, config.authToken, installDaemon, needsRestart, resumeCfg.port ?? 3000);
|
|
977
1068
|
}
|
|
978
1069
|
|
|
979
1070
|
function getLocalIP() {
|
|
@@ -985,7 +1076,25 @@ function getLocalIP() {
|
|
|
985
1076
|
return null;
|
|
986
1077
|
}
|
|
987
1078
|
|
|
988
|
-
async function finish(mindDir, startMode = 'start', mcpPort = 8787, authToken = '', installDaemon = false) {
|
|
1079
|
+
async function finish(mindDir, startMode = 'start', mcpPort = 8787, authToken = '', installDaemon = false, needsRestart = false, oldPort = 3000) {
|
|
1080
|
+
if (needsRestart) {
|
|
1081
|
+
const isRunning = await isSelfPort(oldPort);
|
|
1082
|
+
if (isRunning) {
|
|
1083
|
+
write(c.yellow(t('restartRequired') + '\n'));
|
|
1084
|
+
const doRestart = await askYesNoDefault('restartNow');
|
|
1085
|
+
if (doRestart) {
|
|
1086
|
+
const cliPath = resolve(__dirname, '../bin/cli.js');
|
|
1087
|
+
execSync(`node "${cliPath}" start`, { stdio: 'inherit' });
|
|
1088
|
+
} else {
|
|
1089
|
+
write(c.dim(t('restartManual') + '\n'));
|
|
1090
|
+
}
|
|
1091
|
+
return;
|
|
1092
|
+
} else {
|
|
1093
|
+
write(c.dim(t('changesOnNextStart') + '\n'));
|
|
1094
|
+
// fall through to normal Start now? prompt
|
|
1095
|
+
}
|
|
1096
|
+
}
|
|
1097
|
+
|
|
989
1098
|
const startCmd = installDaemon ? 'mindos start --daemon' : (startMode === 'dev' ? 'mindos dev' : 'mindos start');
|
|
990
1099
|
const lines = T.nextSteps[uiLang](startCmd);
|
|
991
1100
|
console.log('');
|
|
@@ -993,13 +1102,13 @@ async function finish(mindDir, startMode = 'start', mcpPort = 8787, authToken =
|
|
|
993
1102
|
|
|
994
1103
|
const doStart = await askYesNoDefault('startNow');
|
|
995
1104
|
if (doStart) {
|
|
996
|
-
const { execSync } = await import('node:child_process');
|
|
1105
|
+
const { execSync: exec } = await import('node:child_process');
|
|
997
1106
|
const cliPath = resolve(__dirname, '../bin/cli.js');
|
|
998
1107
|
if (installDaemon) {
|
|
999
1108
|
// Install and start as background service — returns immediately
|
|
1000
|
-
|
|
1109
|
+
exec(`node "${cliPath}" start --daemon`, { stdio: 'inherit' });
|
|
1001
1110
|
} else {
|
|
1002
|
-
|
|
1111
|
+
exec(`node "${cliPath}" ${startMode}`, { stdio: 'inherit' });
|
|
1003
1112
|
}
|
|
1004
1113
|
}
|
|
1005
1114
|
}
|
package/skills/mindos/SKILL.md
CHANGED
|
@@ -1,14 +1,15 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: mindos
|
|
3
3
|
description: >
|
|
4
|
-
MindOS knowledge base operation guide for agent tasks on
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
Trigger on requests like "update notes", "search knowledge base", "organize files", "execute SOP",
|
|
4
|
+
MindOS knowledge base operation guide, only for agent tasks on files inside the MindOS knowledge base (mindRoot path).
|
|
5
|
+
Trigger only when the target files are inside the MindOS knowledge base directory.
|
|
6
|
+
Typical requests: "update notes", "search knowledge base", "organize files", "execute SOP",
|
|
8
7
|
"review with our standards", "handoff to another agent", "sync decisions", "append CSV",
|
|
9
8
|
"retrospective", "distill this conversation", "capture key learnings", "update related docs adaptively",
|
|
10
|
-
"route this to the right files", "update everything related", "sync this across my knowledge base"
|
|
11
|
-
|
|
9
|
+
"route this to the right files", "update everything related", "sync this across my knowledge base".
|
|
10
|
+
Do NOT trigger when: the target is a local code repository file (e.g. /code/xxx/wiki/*.md),
|
|
11
|
+
the user provides an absolute path that is not under MindOS mindRoot,
|
|
12
|
+
or the task involves modifying project source code or project documentation.
|
|
12
13
|
---
|
|
13
14
|
|
|
14
15
|
# MindOS Knowledge Base Operation Guide
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: mindos-zh
|
|
3
3
|
description: >
|
|
4
|
-
MindOS
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
4
|
+
MindOS 知识库中文操作指南,仅用于 MindOS 知识库(mindRoot 路径)内的 Agent 任务。
|
|
5
|
+
仅当操作目标是 MindOS 知识库目录下的文件时触发,典型请求包括”更新笔记””搜索知识库”
|
|
6
|
+
“整理文件””执行 SOP””按团队标准 review””把任务交接给另一个 Agent””同步决策”
|
|
7
|
+
“追加 CSV””复盘这段对话””提炼关键经验””把复盘结果自适应更新到对应文档”
|
|
8
|
+
“把这些信息路由到对应文件””同步更新所有相关文档”等。
|
|
9
|
+
不触发:操作目标是本地代码仓库文件(如 /code/xxx/wiki/*.md)、
|
|
10
|
+
用户给出的是绝对路径且不在 MindOS mindRoot 下、或任务是修改项目源码/文档。
|
|
11
11
|
---
|
|
12
12
|
|
|
13
13
|
# MindOS 知识库操作指南
|