@geminilight/mindos 0.3.0 → 0.5.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.
Files changed (80) hide show
  1. package/app/app/api/mcp/agents/route.ts +72 -0
  2. package/app/app/api/mcp/install/route.ts +95 -0
  3. package/app/app/api/mcp/status/route.ts +47 -0
  4. package/app/app/api/setup/check-port/route.ts +41 -0
  5. package/app/app/api/skills/route.ts +208 -0
  6. package/app/app/api/sync/route.ts +54 -3
  7. package/app/app/api/update-check/route.ts +52 -0
  8. package/app/app/globals.css +12 -0
  9. package/app/app/layout.tsx +4 -2
  10. package/app/app/login/page.tsx +20 -13
  11. package/app/app/page.tsx +19 -2
  12. package/app/app/setup/page.tsx +2 -0
  13. package/app/app/view/[...path]/ViewPageClient.tsx +47 -21
  14. package/app/app/view/[...path]/loading.tsx +1 -1
  15. package/app/app/view/[...path]/not-found.tsx +101 -0
  16. package/app/components/AskFab.tsx +1 -1
  17. package/app/components/AskModal.tsx +1 -1
  18. package/app/components/Backlinks.tsx +1 -1
  19. package/app/components/Breadcrumb.tsx +13 -3
  20. package/app/components/CsvView.tsx +5 -6
  21. package/app/components/DirView.tsx +42 -21
  22. package/app/components/FindInPage.tsx +211 -0
  23. package/app/components/HomeContent.tsx +97 -44
  24. package/app/components/JsonView.tsx +1 -2
  25. package/app/components/MarkdownEditor.tsx +1 -2
  26. package/app/components/OnboardingView.tsx +6 -7
  27. package/app/components/SettingsModal.tsx +5 -2
  28. package/app/components/SetupWizard.tsx +499 -172
  29. package/app/components/Sidebar.tsx +1 -1
  30. package/app/components/UpdateBanner.tsx +101 -0
  31. package/app/components/renderers/{AgentInspectorRenderer.tsx → agent-inspector/AgentInspectorRenderer.tsx} +13 -11
  32. package/app/components/renderers/agent-inspector/manifest.ts +14 -0
  33. package/app/components/renderers/{BacklinksRenderer.tsx → backlinks/BacklinksRenderer.tsx} +6 -6
  34. package/app/components/renderers/backlinks/manifest.ts +14 -0
  35. package/app/components/renderers/config/manifest.ts +14 -0
  36. package/app/components/renderers/csv/BoardView.tsx +12 -12
  37. package/app/components/renderers/csv/ConfigPanel.tsx +7 -8
  38. package/app/components/renderers/{CsvRenderer.tsx → csv/CsvRenderer.tsx} +8 -9
  39. package/app/components/renderers/csv/GalleryView.tsx +3 -3
  40. package/app/components/renderers/csv/TableView.tsx +4 -5
  41. package/app/components/renderers/csv/manifest.ts +14 -0
  42. package/app/components/renderers/{DiffRenderer.tsx → diff/DiffRenderer.tsx} +10 -9
  43. package/app/components/renderers/diff/manifest.ts +14 -0
  44. package/app/components/renderers/{GraphRenderer.tsx → graph/GraphRenderer.tsx} +4 -5
  45. package/app/components/renderers/graph/manifest.ts +14 -0
  46. package/app/components/renderers/{SummaryRenderer.tsx → summary/SummaryRenderer.tsx} +6 -6
  47. package/app/components/renderers/summary/manifest.ts +14 -0
  48. package/app/components/renderers/{TimelineRenderer.tsx → timeline/TimelineRenderer.tsx} +6 -6
  49. package/app/components/renderers/timeline/manifest.ts +14 -0
  50. package/app/components/renderers/{TodoRenderer.tsx → todo/TodoRenderer.tsx} +2 -2
  51. package/app/components/renderers/todo/manifest.ts +14 -0
  52. package/app/components/renderers/{WorkflowRenderer.tsx → workflow/WorkflowRenderer.tsx} +13 -13
  53. package/app/components/renderers/workflow/manifest.ts +14 -0
  54. package/app/components/settings/McpTab.tsx +549 -0
  55. package/app/components/settings/SyncTab.tsx +139 -50
  56. package/app/components/settings/types.ts +1 -1
  57. package/app/data/pages/home.png +0 -0
  58. package/app/lib/i18n.ts +226 -19
  59. package/app/lib/renderers/index.ts +20 -89
  60. package/app/lib/renderers/registry.ts +4 -1
  61. package/app/lib/settings.ts +3 -0
  62. package/app/package.json +1 -0
  63. package/app/types/semver.d.ts +8 -0
  64. package/bin/cli.js +137 -24
  65. package/bin/lib/build.js +53 -18
  66. package/bin/lib/colors.js +3 -1
  67. package/bin/lib/config.js +4 -0
  68. package/bin/lib/constants.js +2 -0
  69. package/bin/lib/debug.js +10 -0
  70. package/bin/lib/mcp-install.js +4 -1
  71. package/bin/lib/port.js +8 -2
  72. package/bin/lib/startup.js +21 -20
  73. package/bin/lib/stop.js +41 -3
  74. package/bin/lib/sync.js +65 -53
  75. package/bin/lib/update-check.js +94 -0
  76. package/bin/lib/utils.js +2 -2
  77. package/package.json +1 -1
  78. package/scripts/gen-renderer-index.js +57 -0
  79. package/scripts/setup.js +205 -10
  80. /package/app/components/renderers/{ConfigRenderer.tsx → config/ConfigRenderer.tsx} +0 -0
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
@@ -114,12 +114,23 @@ const T = {
114
114
  cfgKept: { en: '✔ Keeping existing config', zh: '✔ 保留现有配置' },
115
115
  cfgKeptNote: { en: ' Settings from this session were not saved', zh: ' 本次填写的设置未保存' },
116
116
  cfgSaved: { en: '✔ Config saved', zh: '✔ 配置已保存' },
117
+ cfgConfirm: { en: 'Save this configuration?', zh: '保存此配置?' },
118
+ cfgAborted: { en: '✘ Setup cancelled. Run `mindos onboard` to try again.', zh: '✘ 设置已取消。运行 `mindos onboard` 重新开始。' },
117
119
  yesNo: { en: '[y/N]', zh: '[y/N]' },
118
120
  yesNoDefault: { en: '[Y/n]', zh: '[Y/n]' },
119
121
  startNow: { en: 'Start MindOS now?', zh: '现在启动 MindOS?' },
120
122
  syncSetup: { en: 'Set up cross-device sync via Git?', zh: '是否配置 Git 跨设备同步?' },
121
123
  syncLater: { en: ' → Run `mindos sync init` anytime to set up sync later.', zh: ' → 随时运行 `mindos sync init` 配置同步。' },
122
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
+
123
134
  // next steps (onboard — keep it minimal, details shown on `mindos start`)
124
135
  nextSteps: {
125
136
  en: (cmd) => [
@@ -169,7 +180,7 @@ const tf = (key, ...args) => {
169
180
 
170
181
  // ── Step header ───────────────────────────────────────────────────────────────
171
182
 
172
- const TOTAL_STEPS = 7;
183
+ const TOTAL_STEPS = 8;
173
184
  function stepHeader(n) {
174
185
  const title = T.stepTitles[uiLang][n - 1] ?? T.stepTitles.en[n - 1];
175
186
  const stepLabel = tf('step', n, TOTAL_STEPS);
@@ -385,15 +396,21 @@ const askYesNoDefault = (labelKey, arg = '') => askYesNo(labelKey, arg, true);
385
396
  function isPortInUse(port) {
386
397
  return new Promise((resolve) => {
387
398
  const sock = createConnection({ port, host: '127.0.0.1' });
388
- sock.once('connect', () => { sock.destroy(); resolve(true); });
389
- sock.once('error', () => { sock.destroy(); resolve(false); });
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
+ });
390
406
  });
391
407
  }
392
408
 
393
409
  async function findFreePort(from) {
394
- let p = from;
395
- while (p <= 65535 && await isPortInUse(p)) p++;
396
- return p;
410
+ for (let p = from; p <= 65535; p++) {
411
+ if (!await isPortInUse(p)) return p;
412
+ }
413
+ return from; // fallback (extremely unlikely)
397
414
  }
398
415
 
399
416
  async function askPort(labelKey, defaultPort) {
@@ -501,6 +518,150 @@ async function applyTemplate(tpl, mindDir) {
501
518
  }
502
519
  }
503
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
+
504
665
  // ── GUI Setup ─────────────────────────────────────────────────────────────────
505
666
 
506
667
  function openBrowser(url) {
@@ -669,8 +830,13 @@ async function main() {
669
830
  // ── Step 3: Ports ─────────────────────────────────────────────────────────
670
831
  write('\n');
671
832
  stepHeader(3);
672
- const webPort = await askPort('webPortPrompt', 3000);
673
- const mcpPort = await askPort('mcpPortPrompt', 8787);
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
+ }
674
840
 
675
841
  // ── Step 4: Auth token ────────────────────────────────────────────────────
676
842
  write('\n');
@@ -764,10 +930,39 @@ async function main() {
764
930
  },
765
931
  };
766
932
 
933
+ // ── Configuration Summary & Confirmation ──────────────────────────────────
934
+ const maskPw = (s) => s ? '•'.repeat(Math.min(s.length, 8)) : '';
935
+ const maskTk = (s) => s && s.length > 8 ? s.slice(0, 8) + '····' : (s ? s.slice(0, 4) + '····' : '');
936
+ const sep = '━'.repeat(40);
937
+ write(`\n${sep}\n`);
938
+ write(`${c.bold(uiLang === 'zh' ? '配置摘要' : 'Configuration Summary')}\n`);
939
+ write(`${sep}\n`);
940
+ write(` ${c.dim('Knowledge base:')} ${mindDir}\n`);
941
+ write(` ${c.dim('Web port:')} ${webPort}\n`);
942
+ write(` ${c.dim('MCP port:')} ${mcpPort}\n`);
943
+ write(` ${c.dim('Auth token:')} ${maskTk(authToken)}\n`);
944
+ if (webPassword) write(` ${c.dim('Web password:')} ${maskPw(webPassword)}\n`);
945
+ write(` ${c.dim('AI provider:')} ${config.ai.provider}\n`);
946
+ write(` ${c.dim('Start mode:')} ${startMode}\n`);
947
+ write(`${sep}\n`);
948
+
949
+ const confirmSave = await askYesNoDefault('cfgConfirm');
950
+ if (!confirmSave) {
951
+ console.log(c.red(t('cfgAborted')));
952
+ process.exit(0);
953
+ }
954
+
767
955
  mkdirSync(MINDOS_DIR, { recursive: true });
768
956
  writeFileSync(CONFIG_PATH, JSON.stringify(config, null, 2) + '\n');
769
957
  console.log(`\n${c.green(t('cfgSaved'))}: ${c.dim(CONFIG_PATH)}`);
770
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
+
771
966
  // ── Sync setup (optional) ──────────────────────────────────────────────────
772
967
  const wantSync = await askYesNo('syncSetup');
773
968
  if (wantSync) {