@geminilight/mindos 0.5.16 → 0.5.18

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.
@@ -18,6 +18,12 @@ export async function GET() {
18
18
  hasProjectScope: !!agent.project,
19
19
  hasGlobalScope: !!agent.global,
20
20
  preferredTransport: agent.preferredTransport,
21
+ // Snippet generation fields
22
+ format: agent.format ?? 'json',
23
+ configKey: agent.key,
24
+ globalNestedKey: agent.globalNestedKey,
25
+ globalPath: agent.global,
26
+ projectPath: agent.project,
21
27
  };
22
28
  });
23
29
 
@@ -29,6 +29,12 @@ interface AgentInfo {
29
29
  hasProjectScope: boolean;
30
30
  hasGlobalScope: boolean;
31
31
  preferredTransport: 'stdio' | 'http';
32
+ // Snippet generation fields
33
+ format: 'json' | 'toml';
34
+ configKey: string;
35
+ globalNestedKey?: string;
36
+ globalPath: string;
37
+ projectPath?: string | null;
32
38
  }
33
39
 
34
40
  interface SkillInfo {
@@ -66,19 +72,72 @@ function CopyButton({ text, label }: { text: string; label: string }) {
66
72
  );
67
73
  }
68
74
 
75
+ /* ── Config Snippet Generator ─────────────────────────────────── */
76
+
77
+ function generateConfigSnippet(
78
+ agent: AgentInfo,
79
+ status: McpStatus,
80
+ token?: string,
81
+ ): { snippet: string; path: string } {
82
+ const isRunning = status.running;
83
+
84
+ // Determine entry (stdio vs http)
85
+ const stdioEntry: Record<string, unknown> = { type: 'stdio', command: 'mindos', args: ['mcp'] };
86
+ const httpEntry: Record<string, unknown> = { url: status.endpoint };
87
+ if (token) httpEntry.headers = { Authorization: `Bearer ${token}` };
88
+ const entry = isRunning ? httpEntry : stdioEntry;
89
+
90
+ // TOML format (Codex)
91
+ if (agent.format === 'toml') {
92
+ const lines: string[] = [`[${agent.configKey}.mindos]`];
93
+ if (isRunning) {
94
+ lines.push(`type = "http"`);
95
+ lines.push(`url = "${status.endpoint}"`);
96
+ if (token) {
97
+ lines.push('');
98
+ lines.push(`[${agent.configKey}.mindos.headers]`);
99
+ lines.push(`Authorization = "Bearer ${token}"`);
100
+ }
101
+ } else {
102
+ lines.push(`command = "mindos"`);
103
+ lines.push(`args = ["mcp"]`);
104
+ lines.push('');
105
+ lines.push(`[${agent.configKey}.mindos.env]`);
106
+ lines.push(`MCP_TRANSPORT = "stdio"`);
107
+ }
108
+ return { snippet: lines.join('\n'), path: agent.globalPath };
109
+ }
110
+
111
+ // JSON with globalNestedKey (VS Code project-level uses flat key)
112
+ if (agent.globalNestedKey) {
113
+ // project-level: flat key structure
114
+ const projectSnippet = JSON.stringify({ [agent.configKey]: { mindos: entry } }, null, 2);
115
+ return { snippet: projectSnippet, path: agent.projectPath ?? agent.globalPath };
116
+ }
117
+
118
+ // Standard JSON
119
+ const snippet = JSON.stringify({ [agent.configKey]: { mindos: entry } }, null, 2);
120
+ return { snippet, path: agent.globalPath };
121
+ }
122
+
69
123
  /* ── MCP Server Status ─────────────────────────────────────────── */
70
124
 
71
- function ServerStatus({ status, t }: { status: McpStatus | null; t: any }) {
125
+ function ServerStatus({ status, agents, t }: { status: McpStatus | null; agents: AgentInfo[]; t: any }) {
72
126
  const m = t.settings?.mcp;
127
+ const [selectedAgent, setSelectedAgent] = useState<string>('');
128
+
129
+ // Auto-select first installed or first detected agent
130
+ useEffect(() => {
131
+ if (agents.length > 0 && !selectedAgent) {
132
+ const first = agents.find(a => a.installed) ?? agents.find(a => a.present) ?? agents[0];
133
+ if (first) setSelectedAgent(first.key);
134
+ }
135
+ }, [agents, selectedAgent]);
136
+
73
137
  if (!status) return null;
74
138
 
75
- const configSnippet = JSON.stringify({
76
- mcpServers: {
77
- mindos: status.running
78
- ? { url: status.endpoint }
79
- : { type: 'stdio', command: 'mindos', args: ['mcp'] },
80
- },
81
- }, null, 2);
139
+ const currentAgent = agents.find(a => a.key === selectedAgent);
140
+ const snippetResult = currentAgent ? generateConfigSnippet(currentAgent, status) : null;
82
141
 
83
142
  return (
84
143
  <div className="space-y-3">
@@ -123,8 +182,48 @@ function ServerStatus({ status, t }: { status: McpStatus | null; t: any }) {
123
182
 
124
183
  <div className="flex items-center gap-2 pl-11">
125
184
  <CopyButton text={status.endpoint} label={m?.copyEndpoint ?? 'Copy Endpoint'} />
126
- <CopyButton text={configSnippet} label={m?.copyConfig ?? 'Copy Config'} />
127
185
  </div>
186
+
187
+ {/* Quick Setup — agent-specific config snippet */}
188
+ {agents.length > 0 && (
189
+ <div className="pl-11 pt-2 space-y-2.5">
190
+ <div className="flex items-center gap-2">
191
+ <span className="text-xs text-muted-foreground font-medium">
192
+ ── {m?.quickSetup ?? 'Quick Setup'} ──
193
+ </span>
194
+ </div>
195
+
196
+ <div className="flex items-center gap-2">
197
+ <span className="text-xs text-muted-foreground shrink-0">{m?.configureFor ?? 'Configure for'}</span>
198
+ <select
199
+ value={selectedAgent}
200
+ onChange={e => setSelectedAgent(e.target.value)}
201
+ className="text-xs px-2 py-1 rounded-md border border-border bg-background text-foreground outline-none focus-visible:ring-1 focus-visible:ring-ring"
202
+ >
203
+ {agents.map(a => (
204
+ <option key={a.key} value={a.key}>
205
+ {a.name}{a.installed ? ` ✓` : a.present ? ` ·` : ''}
206
+ </option>
207
+ ))}
208
+ </select>
209
+ </div>
210
+
211
+ {snippetResult && (
212
+ <>
213
+ <div className="flex items-center gap-2">
214
+ <span className="text-xs text-muted-foreground shrink-0">{m?.configPath ?? 'Config path'}</span>
215
+ <span className="text-xs font-mono text-foreground">{snippetResult.path}</span>
216
+ </div>
217
+
218
+ <pre className="text-xs font-mono bg-muted/50 border border-border rounded-lg p-3 overflow-x-auto whitespace-pre">
219
+ {snippetResult.snippet}
220
+ </pre>
221
+
222
+ <CopyButton text={snippetResult.snippet} label={m?.copyConfig ?? 'Copy Config'} />
223
+ </>
224
+ )}
225
+ </div>
226
+ )}
128
227
  </div>
129
228
  );
130
229
  }
@@ -633,7 +732,7 @@ export function McpTab({ t }: McpTabProps) {
633
732
  <div className="space-y-6">
634
733
  {/* MCP Server Status — prominent card */}
635
734
  <div className="rounded-xl border p-4" style={{ borderColor: 'var(--border)', background: 'var(--card)' }}>
636
- <ServerStatus status={mcpStatus} t={t} />
735
+ <ServerStatus status={mcpStatus} agents={agents} t={t} />
637
736
  </div>
638
737
 
639
738
  {/* Agent Install — collapsible */}
package/app/lib/i18n.ts CHANGED
@@ -253,6 +253,9 @@ export const messages = {
253
253
  skillLangZh: '中文',
254
254
  selectDetected: 'Select Detected',
255
255
  clearSelection: 'Clear',
256
+ quickSetup: 'Quick Setup',
257
+ configureFor: 'Configure for',
258
+ configPath: 'Config path',
256
259
  },
257
260
  save: 'Save',
258
261
  saved: 'Saved',
@@ -320,6 +323,7 @@ export const messages = {
320
323
  aiProviderHint: 'Choose your preferred AI service.',
321
324
  aiSkip: 'Skip — configure later',
322
325
  apiKey: 'API Key',
326
+ apiKeyExisting: 'Existing key configured. Leave blank to keep it.',
323
327
  model: 'Model',
324
328
  baseUrl: 'Base URL',
325
329
  baseUrlHint: 'Optional. For proxies or OpenAI-compatible APIs.',
@@ -418,6 +422,55 @@ export const messages = {
418
422
  welcomeLinkAskAI: 'Ask AI',
419
423
  welcomeLinkMCP: 'MCP Settings',
420
424
  },
425
+ guide: {
426
+ title: 'Get Started with MindOS',
427
+ showGuide: 'Show getting started guide',
428
+ close: 'Close',
429
+ skip: 'Skip',
430
+ kb: {
431
+ title: 'Explore your knowledge base',
432
+ cta: 'Start',
433
+ fullDesc: 'Your knowledge base has 6 areas — try clicking one:',
434
+ dirs: {
435
+ profile: 'Who you are, preferences, goals',
436
+ notes: 'Daily capture: ideas, meetings, todos',
437
+ connections: 'Your network of people',
438
+ workflows: 'Reusable process SOPs',
439
+ resources: 'Structured data: product lists, tool lists',
440
+ projects: 'Project plans and progress',
441
+ },
442
+ instructionHint: 'Click INSTRUCTION.md to see how AI agents behave.',
443
+ emptyDesc: 'Your knowledge base has 3 core files:',
444
+ emptyFiles: {
445
+ instruction: 'INSTRUCTION.md — Rules that all AI agents follow',
446
+ readme: 'README.md — Directory index and navigation',
447
+ config: 'CONFIG.json — Machine-readable preferences',
448
+ },
449
+ emptyHint: 'Create your own folder structure anytime.',
450
+ progress: (n: number) => `Browsed ${n}/1 file`,
451
+ done: 'Done',
452
+ },
453
+ ai: {
454
+ title: 'Chat with AI',
455
+ cta: 'Start',
456
+ prompt: 'Read my knowledge base and help me write a self-introduction into Profile.',
457
+ promptEmpty: 'Help me design a knowledge base folder structure that fits my needs',
458
+ },
459
+ sync: {
460
+ title: 'Set up sync',
461
+ optional: 'Optional',
462
+ cta: 'Configure',
463
+ },
464
+ done: {
465
+ title: "You're all set!",
466
+ titleFinal: "You've mastered MindOS essentials!",
467
+ steps: [
468
+ { hint: 'Next: try saving an article →', prompt: 'Help me save the key points from this article into MindOS.' },
469
+ { hint: 'Next: try using your KB in another Agent →', prompt: 'Help me start coding based on the plan in MindOS.' },
470
+ { hint: 'Next: try turning experience into a reusable SOP →', prompt: 'Help me distill this conversation into a reusable workflow in MindOS.' },
471
+ ],
472
+ },
473
+ },
421
474
  },
422
475
  zh: {
423
476
  common: {
@@ -671,6 +724,9 @@ export const messages = {
671
724
  skillLangZh: '中文',
672
725
  selectDetected: '选择已检测',
673
726
  clearSelection: '清除',
727
+ quickSetup: '快速配置',
728
+ configureFor: '配置目标',
729
+ configPath: '配置路径',
674
730
  },
675
731
  save: '保存',
676
732
  saved: '已保存',
@@ -738,6 +794,7 @@ export const messages = {
738
794
  aiProviderHint: '选择你偏好的 AI 服务。',
739
795
  aiSkip: '跳过 — 稍后配置',
740
796
  apiKey: 'API 密钥',
797
+ apiKeyExisting: '已配置密钥。留空即保持不变。',
741
798
  model: '模型',
742
799
  baseUrl: '接口地址',
743
800
  baseUrlHint: '可选。用于代理或 OpenAI 兼容 API。',
@@ -837,6 +894,55 @@ export const messages = {
837
894
  welcomeLinkAskAI: '问 AI',
838
895
  welcomeLinkMCP: 'MCP 设置',
839
896
  },
897
+ guide: {
898
+ title: '开始使用 MindOS',
899
+ showGuide: '显示新手引导',
900
+ close: '关闭',
901
+ skip: '跳过',
902
+ kb: {
903
+ title: '探索你的知识库',
904
+ cta: '开始',
905
+ fullDesc: '你的知识库有 6 个区域,试试点开看看:',
906
+ dirs: {
907
+ profile: '你是谁、偏好、目标',
908
+ notes: '日常捕捉:想法、会议、待办',
909
+ connections: '你的人脉关系网',
910
+ workflows: '可复用的工作流程 SOP',
911
+ resources: '结构化数据:产品库、工具库',
912
+ projects: '项目计划和进展',
913
+ },
914
+ instructionHint: '点击 INSTRUCTION.md 看看 AI 的行为规则。',
915
+ emptyDesc: '你的知识库有 3 个核心文件:',
916
+ emptyFiles: {
917
+ instruction: 'INSTRUCTION.md — 所有 AI Agent 遵循的规则',
918
+ readme: 'README.md — 目录索引和导航',
919
+ config: 'CONFIG.json — 机器可读的配置偏好',
920
+ },
921
+ emptyHint: '你可以随时创建自己的目录结构。',
922
+ progress: (n: number) => `已浏览 ${n}/1 个文件`,
923
+ done: '完成',
924
+ },
925
+ ai: {
926
+ title: '和 AI 对话',
927
+ cta: '开始',
928
+ prompt: '读一下我的知识库,帮我把自我介绍写进 Profile。',
929
+ promptEmpty: '帮我设计一个适合我的知识库目录结构',
930
+ },
931
+ sync: {
932
+ title: '配置同步',
933
+ optional: '可选',
934
+ cta: '设置',
935
+ },
936
+ done: {
937
+ title: '你已准备好使用 MindOS',
938
+ titleFinal: '你已掌握 MindOS 核心用法',
939
+ steps: [
940
+ { hint: '下一步:试试把一篇文章存进来 →', prompt: '帮我把这篇文章的要点整理到 MindOS 里。' },
941
+ { hint: '下一步:试试在另一个 Agent 里调用知识库 →', prompt: '帮我按 MindOS 里的方案开始写代码。' },
942
+ { hint: '下一步:试试把经验沉淀为 SOP →', prompt: '帮我把这次对话的经验沉淀到 MindOS,形成可复用的工作流。' },
943
+ ],
944
+ },
945
+ },
840
946
  },
841
947
  } as const;
842
948
 
@@ -13,6 +13,10 @@ export interface AgentDef {
13
13
  global: string;
14
14
  key: string;
15
15
  preferredTransport: 'stdio' | 'http';
16
+ /** Config file format: 'json' (default) or 'toml'. */
17
+ format?: 'json' | 'toml';
18
+ /** For agents whose global config nests under a parent key (e.g. VS Code: mcp.servers). */
19
+ globalNestedKey?: string;
16
20
  /** CLI binary name for presence detection (e.g. 'claude'). Optional. */
17
21
  presenceCli?: string;
18
22
  /** Data directories for presence detection. Any one existing → present. */
@@ -174,6 +178,31 @@ export const MCP_AGENTS: Record<string, AgentDef> = {
174
178
  '~/.config/Code/User/globalStorage/rooveterinaryinc.roo-cline/',
175
179
  ],
176
180
  },
181
+ 'vscode': {
182
+ name: 'VS Code',
183
+ project: '.vscode/mcp.json',
184
+ global: process.platform === 'darwin'
185
+ ? '~/Library/Application Support/Code/User/settings.json'
186
+ : '~/.config/Code/User/settings.json',
187
+ key: 'servers',
188
+ globalNestedKey: 'mcp.servers',
189
+ preferredTransport: 'stdio',
190
+ presenceDirs: [
191
+ '~/Library/Application Support/Code/',
192
+ '~/.config/Code/',
193
+ ],
194
+ presenceCli: 'code',
195
+ },
196
+ 'codex': {
197
+ name: 'Codex',
198
+ project: null,
199
+ global: '~/.codex/config.toml',
200
+ key: 'mcp_servers',
201
+ format: 'toml',
202
+ preferredTransport: 'stdio',
203
+ presenceCli: 'codex',
204
+ presenceDirs: ['~/.codex/'],
205
+ },
177
206
  };
178
207
 
179
208
  /* ── MindOS MCP Install Detection ──────────────────────────────────────── */
package/bin/cli.js CHANGED
@@ -242,8 +242,7 @@ const commands = {
242
242
  // Do NOT call start() here — kickstart -k would kill the just-started process,
243
243
  // causing a port-conflict race condition with KeepAlive restart loops.
244
244
  console.log(dim(' (First run may take a few minutes to install dependencies and build the app.)'));
245
- console.log(dim(' Follow live progress with: mindos logs\n'));
246
- const ready = await waitForHttp(Number(webPort), { retries: 60, intervalMs: 2000, label: 'Web UI' });
245
+ const ready = await waitForHttp(Number(webPort), { retries: 60, intervalMs: 2000, label: 'Web UI', logFile: LOG_PATH });
247
246
  if (!ready) {
248
247
  console.error(red('\n✘ Service started but Web UI did not become ready in time.'));
249
248
  console.error(dim(' Check logs with: mindos logs\n'));
@@ -326,7 +325,13 @@ const commands = {
326
325
  const mcpSdk = resolve(ROOT, 'mcp', 'node_modules', '@modelcontextprotocol', 'sdk', 'package.json');
327
326
  if (!existsSync(mcpSdk)) {
328
327
  console.log(yellow('Installing MCP dependencies (first run)...\n'));
329
- run('npm install --prefer-offline --no-workspaces', resolve(ROOT, 'mcp'));
328
+ const mcpCwd = resolve(ROOT, 'mcp');
329
+ try {
330
+ execSync('npm install --prefer-offline --no-workspaces', { cwd: mcpCwd, stdio: 'inherit' });
331
+ } catch {
332
+ console.log(yellow('Offline install failed, retrying online...\n'));
333
+ run('npm install --no-workspaces', mcpCwd);
334
+ }
330
335
  }
331
336
  // Map config env vars to what the MCP server expects
332
337
  const mcpPort = process.env.MINDOS_MCP_PORT || '8781';
@@ -616,7 +621,7 @@ ${dim('Shortcut: mindos start --daemon → install + start in one step')}
616
621
  const webPort = updateConfig.port ?? 3456;
617
622
  const mcpPort = updateConfig.mcpPort ?? 8781;
618
623
  console.log(dim(' (Waiting for Web UI to come back up — first run after update includes a rebuild...)'));
619
- const ready = await waitForHttp(Number(webPort), { retries: 60, intervalMs: 2000, label: 'Web UI' });
624
+ const ready = await waitForHttp(Number(webPort), { retries: 60, intervalMs: 2000, label: 'Web UI', logFile: LOG_PATH });
620
625
  if (ready) {
621
626
  const localIP = getLocalIP();
622
627
  console.log(`\n${'─'.repeat(53)}`);
package/bin/lib/build.js CHANGED
@@ -105,7 +105,12 @@ export function ensureAppDeps() {
105
105
  ? 'Updating app dependencies (package-lock.json changed)...\n'
106
106
  : 'Installing app dependencies (first run)...\n';
107
107
  console.log(yellow(label));
108
- run('npm install --prefer-offline --no-workspaces', resolve(ROOT, 'app'));
108
+ try {
109
+ execSync('npm install --prefer-offline --no-workspaces', { cwd: resolve(ROOT, 'app'), stdio: 'inherit' });
110
+ } catch {
111
+ console.log(yellow('Offline install failed, retrying online...\n'));
112
+ run('npm install --no-workspaces', resolve(ROOT, 'app'));
113
+ }
109
114
 
110
115
  // Verify critical deps — npm tar extraction can silently fail (ENOENT race)
111
116
  if (!verifyDeps()) {
@@ -1,5 +1,5 @@
1
1
  import { execSync } from 'node:child_process';
2
- import { existsSync, readFileSync, writeFileSync, rmSync, mkdirSync, statSync, renameSync, unlinkSync } from 'node:fs';
2
+ import { existsSync, readFileSync, writeFileSync, rmSync, mkdirSync, statSync, renameSync, unlinkSync, openSync, readSync, closeSync } from 'node:fs';
3
3
  import { resolve } from 'node:path';
4
4
  import { homedir } from 'node:os';
5
5
  import { MINDOS_DIR, LOG_PATH, CLI_PATH, NODE_BIN, CONFIG_PATH } from './constants.js';
@@ -50,18 +50,76 @@ export async function waitForPortFree(port, { retries = 30, intervalMs = 500 } =
50
50
  return !(await isPortInUse(port));
51
51
  }
52
52
 
53
- export async function waitForHttp(port, { retries = 60, intervalMs = 2000, label = 'service' } = {}) {
53
+ /**
54
+ * Parse a log line to extract a user-friendly status hint.
55
+ * Returns null if the line isn't interesting enough to show.
56
+ * @internal Exported for testing only.
57
+ */
58
+ export function parseLogHint(line) {
59
+ const l = line.trim();
60
+ if (!l) return null;
61
+ // npm install progress
62
+ if (/added \d+ packages/i.test(l)) return 'dependencies installed';
63
+ // MCP-specific must come before generic "Installing" match
64
+ if (/Installing MCP dependencies/i.test(l)) return 'installing MCP…';
65
+ if (/Installing app dependencies/i.test(l)) return 'installing dependencies…';
66
+ if (/Updating app dependencies/i.test(l)) return 'updating dependencies…';
67
+ // next build progress
68
+ if (/Building MindOS/i.test(l)) return 'building app…';
69
+ if (/Creating.*optimized.*production/i.test(l)) return 'building app…';
70
+ if (/Compil/i.test(l)) return 'compiling…';
71
+ if (/Collecting page data/i.test(l)) return 'collecting page data…';
72
+ if (/Generating static pages/i.test(l)) return 'generating pages…';
73
+ if (/Finalizing page optimization/i.test(l)) return 'optimizing…';
74
+ if (/[○●◐λƒ]\s+\/\S+.*\d+(\.\d+)?\s*kB/i.test(l)) return 'bundling routes…';
75
+ // next start / ready
76
+ if (/▲ Next\.js/i.test(l)) return 'starting server…';
77
+ if (/Ready in/i.test(l)) return 'starting server…';
78
+ return null;
79
+ }
80
+
81
+ export async function waitForHttp(port, { retries = 60, intervalMs = 2000, label = 'service', logFile = null } = {}) {
54
82
  const start = Date.now();
55
83
  const elapsed = () => {
56
84
  const s = Math.round((Date.now() - start) / 1000);
57
85
  return s < 60 ? `${s}s` : `${Math.floor(s / 60)}m${String(s % 60).padStart(2, '0')}s`;
58
86
  };
59
- const phases = [
60
- { after: 0, msg: 'installing dependencies' },
61
- { after: 15, msg: 'building app' },
62
- { after: 60, msg: 'still building (first run takes a while)' },
63
- ];
64
- let currentPhase = -1;
87
+
88
+ // Track log file position for incremental reads
89
+ let logOffset = 0;
90
+ let lastHint = 'starting…';
91
+ if (logFile) {
92
+ try {
93
+ logOffset = statSync(logFile).size; // start from current end
94
+ } catch { /* file may not exist yet */ }
95
+ }
96
+
97
+ /** Read new lines from logFile and update lastHint */
98
+ function updateHintFromLog() {
99
+ if (!logFile) return;
100
+ try {
101
+ const st = statSync(logFile);
102
+ // File was truncated or rotated — reset offset
103
+ if (st.size < logOffset) logOffset = 0;
104
+ if (st.size <= logOffset) return;
105
+ // Read new chunk (at most 4KB to avoid large reads)
106
+ const readSize = Math.min(st.size - logOffset, 4096);
107
+ const buf = Buffer.alloc(readSize);
108
+ const fd = openSync(logFile, 'r');
109
+ try {
110
+ readSync(fd, buf, 0, readSize, logOffset);
111
+ } finally {
112
+ closeSync(fd);
113
+ }
114
+ logOffset = logOffset + readSize;
115
+ const newLines = buf.toString('utf-8').split('\n');
116
+ // Walk lines in order, keep the last interesting hint
117
+ for (const line of newLines) {
118
+ const hint = parseLogHint(line);
119
+ if (hint) lastHint = hint;
120
+ }
121
+ } catch { /* log file may not exist yet or be rotated */ }
122
+ }
65
123
 
66
124
  for (let i = 0; i < retries; i++) {
67
125
  try {
@@ -74,24 +132,18 @@ export async function waitForHttp(port, { retries = 60, intervalMs = 2000, label
74
132
  req.end();
75
133
  });
76
134
  if (ok) {
77
- // Clear line and print success
78
135
  process.stdout.write(`\r\x1b[K`);
79
136
  process.stdout.write(` ${green('\u2714')} ${label} ready ${dim(`(${elapsed()})`)}\n`);
80
137
  return true;
81
138
  }
82
139
  } catch { /* not ready yet */ }
83
140
 
84
- // Update phase hint
85
- const secs = (Date.now() - start) / 1000;
86
- const nextPhase = phases.reduce((idx, p, i) => secs >= p.after ? i : idx, -1);
87
- if (nextPhase !== currentPhase) {
88
- currentPhase = nextPhase;
89
- }
90
- const hint = currentPhase >= 0 ? phases[currentPhase].msg : '';
141
+ // Update hint from real log output
142
+ updateHintFromLog();
91
143
 
92
144
  // Rewrite the status line in place
93
145
  process.stdout.write(`\r\x1b[K`);
94
- process.stdout.write(cyan(` ⏳ Waiting for ${label}`) + dim(` — ${hint} (${elapsed()})`));
146
+ process.stdout.write(cyan(` ⏳ Waiting for ${label}`) + dim(` — ${lastHint} (${elapsed()})`));
95
147
 
96
148
  await new Promise(r => setTimeout(r, intervalMs));
97
149
  }
@@ -11,7 +11,19 @@ export function spawnMcp(verbose = false) {
11
11
  const mcpSdk = resolve(ROOT, 'mcp', 'node_modules', '@modelcontextprotocol', 'sdk', 'package.json');
12
12
  if (!existsSync(mcpSdk)) {
13
13
  console.log(yellow('Installing MCP dependencies (first run)...\n'));
14
- execSync('npm install --prefer-offline --no-workspaces', { cwd: resolve(ROOT, 'mcp'), stdio: 'inherit' });
14
+ const mcpCwd = resolve(ROOT, 'mcp');
15
+ try {
16
+ execSync('npm install --prefer-offline --no-workspaces', { cwd: mcpCwd, stdio: 'inherit' });
17
+ } catch {
18
+ console.log(yellow('Offline install failed, retrying online...\n'));
19
+ try {
20
+ execSync('npm install --no-workspaces', { cwd: mcpCwd, stdio: 'inherit' });
21
+ } catch (err) {
22
+ console.error(red('Failed to install MCP dependencies.'));
23
+ console.error(` Try manually: cd ${mcpCwd} && npm install\n`);
24
+ process.exit(1);
25
+ }
26
+ }
15
27
  }
16
28
  const env = {
17
29
  ...process.env,
package/mcp/package.json CHANGED
@@ -7,7 +7,7 @@
7
7
  "start": "npx tsx src/index.ts"
8
8
  },
9
9
  "dependencies": {
10
- "@modelcontextprotocol/sdk": "^1.6.1",
10
+ "@modelcontextprotocol/sdk": "^1.25.0",
11
11
  "zod": "^3.23.8"
12
12
  },
13
13
  "devDependencies": {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@geminilight/mindos",
3
- "version": "0.5.16",
3
+ "version": "0.5.18",
4
4
  "description": "MindOS — Human-Agent Collaborative Mind System. Local-first knowledge base that syncs your mind to all AI Agents via MCP.",
5
5
  "keywords": [
6
6
  "mindos",
package/scripts/setup.js CHANGED
@@ -902,7 +902,7 @@ async function startGuiSetup() {
902
902
 
903
903
  // Wait for the server to be ready (120s timeout)
904
904
  const { waitForHttp } = await import('../bin/lib/gateway.js');
905
- const ready = await waitForHttp(usePort, { retries: 120, intervalMs: 1000, label: 'MindOS' });
905
+ const ready = await waitForHttp(usePort, { retries: 120, intervalMs: 1000, label: 'MindOS', logFile: LOG_PATH });
906
906
 
907
907
  if (!ready) {
908
908
  write(c.red('\n✘ Server failed to start.\n'));