@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/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: ' ← → switch language / 切换语言 ↑ ↓ navigate Enter confirm', zh: ' ← → switch language / 切换语言 ↑ ↓ 上下切换 Enter 确认' },
44
+ langHint: { en: ' ← → 切换中文 ↑ ↓ navigate Enter confirm', zh: ' ← → switch to English ↑ ↓ 上下切换 Enter 确认' },
43
45
 
44
46
  // mode selection
45
- modePrompt: { en: 'Setup mode', zh: '配置方式' },
46
- modeOpts: { en: ['CLI terminal wizard', 'GUI browser wizard (recommended)'], zh: ['CLI — 终端向导', 'GUI — 浏览器向导(推荐)'] },
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 server for GUI setup...', zh: '⏳ 正在启动服务...' },
49
- guiReady: { en: (url) => `🌐 Complete setup in browser: ${url}`, zh: (url) => `🌐 在浏览器中完成配置: ${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', 'Template', 'Ports', 'Auth Token', 'Web Password', 'AI Provider', 'Start Mode', 'Agent Tools'],
56
- zh: ['知识库', '模板', '端口', 'Auth Token', 'Web 密码', 'AI 服务商', '启动方式', 'Agent 工具'],
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
- let uiLang = 'en';
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 = 8;
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(false));
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 = await findFreePort(defaultPort);
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
- // ── 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
- };
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 = MCP_AGENTS_SETUP[agentKey];
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(MCP_AGENTS_SETUP);
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: MCP_AGENTS_SETUP[k].name,
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 = MCP_AGENTS_SETUP[agentKey];
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(MINDOS_DIR, 'my-mind');
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', 'my-mind', 'pathHintInline', MINDOS_DIR)).trim();
798
- const resolved = input.startsWith('/') ? input : resolve(MINDOS_DIR, input);
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 2: Template ────────────────────────────────────────────────
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 3: Ports ─────────────────────────────────────────────────────────
885
+ // ── Step 2: AI Provider + API Key ─────────────────────────────────────────
831
886
  write('\n');
832
- stepHeader(3);
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
- try {
875
- const existing = JSON.parse(readFileSync(CONFIG_PATH, 'utf-8'));
876
- if (existing.ai?.providers) {
877
- existingProviders = { ...existingProviders, ...existing.ai.providers };
878
- } else if (existing.ai?.anthropicApiKey) {
879
- existingProviders.anthropic = { apiKey: existing.ai.anthropicApiKey || '', model: existing.ai.anthropicModel || 'claude-sonnet-4-6' };
880
- existingProviders.openai = { apiKey: existing.ai.openaiApiKey || '', model: existing.ai.openaiModel || 'gpt-5.4', baseUrl: existing.ai.openaiBaseUrl || '' };
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 7: Start Mode ──────────────────────────────────────────────────
930
+ // ── Step 3: Ports ─────────────────────────────────────────────────────────
909
931
  write('\n');
910
- stepHeader(7);
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 8: MCP Agent Install ──────────────────────────────────────────────
1050
+ // ── Step 7: MCP Agent Install ──────────────────────────────────────────────
960
1051
  write('\n');
961
- stepHeader(8);
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
- execSync(`node "${cliPath}" start --daemon`, { stdio: 'inherit' });
1109
+ exec(`node "${cliPath}" start --daemon`, { stdio: 'inherit' });
1001
1110
  } else {
1002
- execSync(`node "${cliPath}" ${startMode}`, { stdio: 'inherit' });
1111
+ exec(`node "${cliPath}" ${startMode}`, { stdio: 'inherit' });
1003
1112
  }
1004
1113
  }
1005
1114
  }
@@ -1,14 +1,15 @@
1
1
  ---
2
2
  name: mindos
3
3
  description: >
4
- MindOS knowledge base operation guide for agent tasks on local markdown/csv knowledge bases.
5
- Use proactively whenever work touches note files, SOP/workflow docs, profile/context docs,
6
- CSV tables, knowledge-base organization, cross-agent handoff, or decision sync through MindOS MCP tools.
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
- and generally any local knowledge-maintenance workflow even if the user does not explicitly mention MindOS.
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 知识库中文操作指南,用于本地 markdown/csv 知识库上的 Agent 任务。
5
- 只要任务涉及笔记文件、SOP/工作流文档、Profile/上下文文档、CSV 表、知识库整理、
6
- Agent 交接或决策同步,并通过 MindOS MCP 工具执行,就应主动触发。
7
- 典型请求包括”更新笔记””搜索知识库””整理文件””执行 SOP””按团队标准 review”
8
- “把任务交接给另一个 Agent””同步决策””追加 CSV””复盘这段对话””提炼关键经验”
9
- “把复盘结果自适应更新到对应文档””把这些信息路由到对应文件””同步更新所有相关文档”等;
10
- 即使用户未明确提到 MindOS 也应触发。
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 知识库操作指南