@geminilight/mindos 0.5.8 → 0.5.10

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 (65) hide show
  1. package/README.md +9 -10
  2. package/README_zh.md +8 -9
  3. package/app/app/api/mcp/agents/route.ts +7 -0
  4. package/app/app/api/mcp/install-skill/route.ts +6 -0
  5. package/app/app/api/setup/check-port/route.ts +27 -3
  6. package/app/app/api/setup/route.ts +2 -9
  7. package/app/app/api/skills/route.ts +1 -1
  8. package/app/app/globals.css +28 -4
  9. package/app/app/login/page.tsx +2 -2
  10. package/app/app/view/[...path]/ViewPageClient.tsx +15 -10
  11. package/app/app/view/[...path]/not-found.tsx +1 -1
  12. package/app/components/AskModal.tsx +5 -5
  13. package/app/components/Breadcrumb.tsx +2 -2
  14. package/app/components/DirView.tsx +6 -6
  15. package/app/components/FileTree.tsx +7 -7
  16. package/app/components/HomeContent.tsx +8 -8
  17. package/app/components/OnboardingView.tsx +1 -1
  18. package/app/components/SearchModal.tsx +1 -1
  19. package/app/components/SettingsModal.tsx +2 -2
  20. package/app/components/SetupWizard.tsx +1 -1258
  21. package/app/components/Sidebar.tsx +4 -4
  22. package/app/components/SidebarLayout.tsx +9 -0
  23. package/app/components/SyncStatusBar.tsx +6 -6
  24. package/app/components/TableOfContents.tsx +1 -1
  25. package/app/components/UpdateBanner.tsx +1 -1
  26. package/app/components/ask/FileChip.tsx +1 -1
  27. package/app/components/ask/MentionPopover.tsx +4 -4
  28. package/app/components/ask/MessageList.tsx +3 -3
  29. package/app/components/ask/SessionHistory.tsx +3 -3
  30. package/app/components/renderers/agent-inspector/AgentInspectorRenderer.tsx +5 -5
  31. package/app/components/renderers/config/ConfigRenderer.tsx +4 -4
  32. package/app/components/renderers/csv/BoardView.tsx +2 -2
  33. package/app/components/renderers/csv/ConfigPanel.tsx +5 -5
  34. package/app/components/renderers/csv/GalleryView.tsx +1 -1
  35. package/app/components/renderers/csv/types.ts +1 -1
  36. package/app/components/renderers/diff/DiffRenderer.tsx +9 -9
  37. package/app/components/renderers/graph/GraphRenderer.tsx +1 -1
  38. package/app/components/renderers/summary/SummaryRenderer.tsx +1 -1
  39. package/app/components/renderers/timeline/TimelineRenderer.tsx +1 -1
  40. package/app/components/renderers/workflow/WorkflowRenderer.tsx +4 -4
  41. package/app/components/settings/KnowledgeTab.tsx +1 -1
  42. package/app/components/settings/McpTab.tsx +93 -47
  43. package/app/components/settings/PluginsTab.tsx +4 -4
  44. package/app/components/settings/Primitives.tsx +4 -4
  45. package/app/components/settings/SyncTab.tsx +13 -13
  46. package/app/components/setup/StepAI.tsx +67 -0
  47. package/app/components/setup/StepAgents.tsx +237 -0
  48. package/app/components/setup/StepDots.tsx +39 -0
  49. package/app/components/setup/StepKB.tsx +237 -0
  50. package/app/components/setup/StepPorts.tsx +121 -0
  51. package/app/components/setup/StepReview.tsx +211 -0
  52. package/app/components/setup/StepSecurity.tsx +78 -0
  53. package/app/components/setup/constants.tsx +13 -0
  54. package/app/components/setup/index.tsx +464 -0
  55. package/app/components/setup/types.ts +53 -0
  56. package/app/lib/i18n.ts +52 -8
  57. package/app/lib/mcp-agents.ts +81 -0
  58. package/bin/lib/gateway.js +44 -4
  59. package/bin/lib/mcp-agents.js +81 -0
  60. package/bin/lib/mcp-install.js +34 -4
  61. package/package.json +3 -1
  62. package/scripts/setup.js +43 -6
  63. package/skills/project-wiki/SKILL.md +92 -63
  64. package/app/public/landing/index.html +0 -353
  65. package/app/public/landing/style.css +0 -216
@@ -93,6 +93,87 @@ export const MCP_AGENTS: Record<string, AgentDef> = {
93
93
  presenceCli: 'claude-internal',
94
94
  presenceDirs: ['~/.claude-internal/'],
95
95
  },
96
+ 'iflow-cli': {
97
+ name: 'iFlow CLI',
98
+ project: '.iflow/settings.json',
99
+ global: '~/.iflow/settings.json',
100
+ key: 'mcpServers',
101
+ preferredTransport: 'stdio',
102
+ presenceCli: 'iflow',
103
+ presenceDirs: ['~/.iflow/'],
104
+ },
105
+ 'kimi-cli': {
106
+ name: 'Kimi Code',
107
+ project: '.kimi/mcp.json',
108
+ global: '~/.kimi/mcp.json',
109
+ key: 'mcpServers',
110
+ preferredTransport: 'stdio',
111
+ presenceCli: 'kimi',
112
+ presenceDirs: ['~/.kimi/'],
113
+ },
114
+ 'opencode': {
115
+ name: 'OpenCode',
116
+ project: null,
117
+ global: '~/.config/opencode/config.json',
118
+ key: 'mcpServers',
119
+ preferredTransport: 'stdio',
120
+ presenceCli: 'opencode',
121
+ presenceDirs: ['~/.config/opencode/'],
122
+ },
123
+ 'pi': {
124
+ name: 'Pi',
125
+ project: '.pi/settings.json',
126
+ global: '~/.pi/agent/mcp.json',
127
+ key: 'mcpServers',
128
+ preferredTransport: 'stdio',
129
+ presenceCli: 'pi',
130
+ presenceDirs: ['~/.pi/'],
131
+ },
132
+ 'augment': {
133
+ name: 'Augment',
134
+ project: '.augment/settings.json',
135
+ global: '~/.augment/settings.json',
136
+ key: 'mcpServers',
137
+ preferredTransport: 'stdio',
138
+ presenceCli: 'auggie',
139
+ presenceDirs: ['~/.augment/'],
140
+ },
141
+ 'qwen-code': {
142
+ name: 'Qwen Code',
143
+ project: '.qwen/settings.json',
144
+ global: '~/.qwen/settings.json',
145
+ key: 'mcpServers',
146
+ preferredTransport: 'stdio',
147
+ presenceCli: 'qwen',
148
+ presenceDirs: ['~/.qwen/'],
149
+ },
150
+ 'trae-cn': {
151
+ name: 'Trae CN',
152
+ project: '.trae/mcp.json',
153
+ global: process.platform === 'darwin'
154
+ ? '~/Library/Application Support/Trae CN/User/mcp.json'
155
+ : '~/.config/Trae CN/User/mcp.json',
156
+ key: 'mcpServers',
157
+ preferredTransport: 'stdio',
158
+ presenceCli: 'trae-cli',
159
+ presenceDirs: [
160
+ '~/Library/Application Support/Trae CN/',
161
+ '~/.config/Trae CN/',
162
+ ],
163
+ },
164
+ 'roo': {
165
+ name: 'Roo Code',
166
+ project: '.roo/mcp.json',
167
+ global: process.platform === 'darwin'
168
+ ? '~/Library/Application Support/Code/User/globalStorage/rooveterinaryinc.roo-cline/settings/mcp_settings.json'
169
+ : '~/.config/Code/User/globalStorage/rooveterinaryinc.roo-cline/settings/mcp_settings.json',
170
+ key: 'mcpServers',
171
+ preferredTransport: 'stdio',
172
+ presenceDirs: [
173
+ '~/Library/Application Support/Code/User/globalStorage/rooveterinaryinc.roo-cline/',
174
+ '~/.config/Code/User/globalStorage/rooveterinaryinc.roo-cline/',
175
+ ],
176
+ },
96
177
  };
97
178
 
98
179
  /* ── MindOS MCP Install Detection ──────────────────────────────────────── */
@@ -1,9 +1,10 @@
1
1
  import { execSync } from 'node:child_process';
2
- import { existsSync, writeFileSync, rmSync, mkdirSync } from 'node:fs';
2
+ import { existsSync, readFileSync, writeFileSync, rmSync, mkdirSync } from 'node:fs';
3
3
  import { resolve } from 'node:path';
4
4
  import { homedir } from 'node:os';
5
- import { MINDOS_DIR, LOG_PATH, CLI_PATH, NODE_BIN } from './constants.js';
6
- import { green, red, dim, cyan } from './colors.js';
5
+ import { MINDOS_DIR, LOG_PATH, CLI_PATH, NODE_BIN, CONFIG_PATH } from './constants.js';
6
+ import { green, red, dim, cyan, yellow } from './colors.js';
7
+ import { isPortInUse } from './port.js';
7
8
 
8
9
  // ── Helpers ──────────────────────────────────────────────────────────────────
9
10
 
@@ -25,6 +26,18 @@ export async function waitForService(check, { retries = 10, intervalMs = 1000 }
25
26
  return check();
26
27
  }
27
28
 
29
+ /**
30
+ * Wait until a port is free (no process listening).
31
+ * Returns true if port is free, false on timeout.
32
+ */
33
+ export async function waitForPortFree(port, { retries = 30, intervalMs = 500 } = {}) {
34
+ for (let i = 0; i < retries; i++) {
35
+ if (!(await isPortInUse(port))) return true;
36
+ await new Promise(r => setTimeout(r, intervalMs));
37
+ }
38
+ return !(await isPortInUse(port));
39
+ }
40
+
28
41
  export async function waitForHttp(port, { retries = 120, intervalMs = 2000, label = 'service' } = {}) {
29
42
  process.stdout.write(cyan(` Waiting for ${label} to be ready`));
30
43
  for (let i = 0; i < retries; i++) {
@@ -194,10 +207,37 @@ const launchd = {
194
207
  console.log(green('\u2714 Service started'));
195
208
  },
196
209
 
197
- stop() {
210
+ async stop() {
211
+ // Read ports before bootout so we can wait for them to be freed
212
+ let webPort = 3000, mcpPort = 8787;
213
+ try {
214
+ const config = JSON.parse(readFileSync(CONFIG_PATH, 'utf-8'));
215
+ if (config.port) webPort = Number(config.port);
216
+ if (config.mcpPort) mcpPort = Number(config.mcpPort);
217
+ } catch {}
218
+
198
219
  try {
199
220
  execSync(`launchctl bootout gui/${launchctlUid()} ${LAUNCHD_PLIST}`, { stdio: 'inherit' });
200
221
  } catch { /* may not be running */ }
222
+
223
+ // launchctl bootout is async — wait for ports to actually be freed
224
+ let [webFree, mcpFree] = await Promise.all([
225
+ waitForPortFree(webPort),
226
+ waitForPortFree(mcpPort),
227
+ ]);
228
+ if (!webFree || !mcpFree) {
229
+ console.log(yellow('Ports still in use after bootout, force-killing...'));
230
+ const { stopMindos } = await import('./stop.js');
231
+ stopMindos();
232
+ // stopMindos() sends SIGTERM synchronously — wait for processes to exit
233
+ [webFree, mcpFree] = await Promise.all([
234
+ waitForPortFree(webPort),
235
+ waitForPortFree(mcpPort),
236
+ ]);
237
+ if (!webFree || !mcpFree) {
238
+ console.error(red('Warning: ports still in use after force-kill. Continuing anyway.'));
239
+ }
240
+ }
201
241
  console.log(green('\u2714 Service stopped'));
202
242
  },
203
243
 
@@ -100,6 +100,87 @@ export const MCP_AGENTS = {
100
100
  presenceCli: 'claude-internal',
101
101
  presenceDirs: ['~/.claude-internal/'],
102
102
  },
103
+ 'iflow-cli': {
104
+ name: 'iFlow CLI',
105
+ project: '.iflow/settings.json',
106
+ global: '~/.iflow/settings.json',
107
+ key: 'mcpServers',
108
+ preferredTransport: 'stdio',
109
+ presenceCli: 'iflow',
110
+ presenceDirs: ['~/.iflow/'],
111
+ },
112
+ 'kimi-cli': {
113
+ name: 'Kimi Code',
114
+ project: '.kimi/mcp.json',
115
+ global: '~/.kimi/mcp.json',
116
+ key: 'mcpServers',
117
+ preferredTransport: 'stdio',
118
+ presenceCli: 'kimi',
119
+ presenceDirs: ['~/.kimi/'],
120
+ },
121
+ 'opencode': {
122
+ name: 'OpenCode',
123
+ project: null,
124
+ global: '~/.config/opencode/config.json',
125
+ key: 'mcpServers',
126
+ preferredTransport: 'stdio',
127
+ presenceCli: 'opencode',
128
+ presenceDirs: ['~/.config/opencode/'],
129
+ },
130
+ 'pi': {
131
+ name: 'Pi',
132
+ project: '.pi/settings.json',
133
+ global: '~/.pi/agent/mcp.json',
134
+ key: 'mcpServers',
135
+ preferredTransport: 'stdio',
136
+ presenceCli: 'pi',
137
+ presenceDirs: ['~/.pi/'],
138
+ },
139
+ 'augment': {
140
+ name: 'Augment',
141
+ project: '.augment/settings.json',
142
+ global: '~/.augment/settings.json',
143
+ key: 'mcpServers',
144
+ preferredTransport: 'stdio',
145
+ presenceCli: 'auggie',
146
+ presenceDirs: ['~/.augment/'],
147
+ },
148
+ 'qwen-code': {
149
+ name: 'Qwen Code',
150
+ project: '.qwen/settings.json',
151
+ global: '~/.qwen/settings.json',
152
+ key: 'mcpServers',
153
+ preferredTransport: 'stdio',
154
+ presenceCli: 'qwen',
155
+ presenceDirs: ['~/.qwen/'],
156
+ },
157
+ 'trae-cn': {
158
+ name: 'Trae CN',
159
+ project: '.trae/mcp.json',
160
+ global: process.platform === 'darwin'
161
+ ? '~/Library/Application Support/Trae CN/User/mcp.json'
162
+ : '~/.config/Trae CN/User/mcp.json',
163
+ key: 'mcpServers',
164
+ preferredTransport: 'stdio',
165
+ presenceCli: 'trae-cli',
166
+ presenceDirs: [
167
+ '~/Library/Application Support/Trae CN/',
168
+ '~/.config/Trae CN/',
169
+ ],
170
+ },
171
+ 'roo': {
172
+ name: 'Roo Code',
173
+ project: '.roo/mcp.json',
174
+ global: process.platform === 'darwin'
175
+ ? '~/Library/Application Support/Code/User/globalStorage/rooveterinaryinc.roo-cline/settings/mcp_settings.json'
176
+ : '~/.config/Code/User/globalStorage/rooveterinaryinc.roo-cline/settings/mcp_settings.json',
177
+ key: 'mcpServers',
178
+ preferredTransport: 'stdio',
179
+ presenceDirs: [
180
+ '~/Library/Application Support/Code/User/globalStorage/rooveterinaryinc.roo-cline/',
181
+ '~/.config/Code/User/globalStorage/rooveterinaryinc.roo-cline/',
182
+ ],
183
+ },
103
184
  };
104
185
 
105
186
  export function detectAgentPresence(agentKey) {
@@ -3,7 +3,7 @@ import { resolve } from 'node:path';
3
3
  import { CONFIG_PATH } from './constants.js';
4
4
  import { bold, dim, cyan, green, red, yellow } from './colors.js';
5
5
  import { expandHome } from './utils.js';
6
- import { MCP_AGENTS } from './mcp-agents.js';
6
+ import { MCP_AGENTS, detectAgentPresence } from './mcp-agents.js';
7
7
 
8
8
  export { MCP_AGENTS };
9
9
 
@@ -76,7 +76,7 @@ async function interactiveSelect(title, options) {
76
76
  async function interactiveMultiSelect(title, options) {
77
77
  return new Promise((resolve) => {
78
78
  let cursor = 0;
79
- const selected = new Set();
79
+ const selected = new Set(options.map((o, i) => o.preselect ? i : -1).filter(i => i >= 0));
80
80
  const { stdin, stdout } = process;
81
81
 
82
82
  function render() {
@@ -85,7 +85,7 @@ async function interactiveMultiSelect(title, options) {
85
85
  }
86
86
 
87
87
  function draw() {
88
- stdout.write(`${bold(title)} ${dim('(↑↓ move, Space select, A all, Enter confirm)')}\n`);
88
+ stdout.write(`${bold(title)} ${dim('(↑↓ move, Space select, D detected, A all, Enter confirm)')}\n`);
89
89
  for (let i = 0; i < options.length; i++) {
90
90
  const o = options[i];
91
91
  const check = selected.has(i) ? green('✔') : dim('○');
@@ -120,6 +120,10 @@ async function interactiveMultiSelect(title, options) {
120
120
  if (selected.size === options.length) selected.clear();
121
121
  else options.forEach((_, i) => selected.add(i));
122
122
  render();
123
+ } else if (key === 'd' || key === 'D') { // select detected only
124
+ selected.clear();
125
+ options.forEach((o, i) => { if (o.preselect) selected.add(i); });
126
+ render();
123
127
  } else if (key === '\r' || key === '\n') { // enter
124
128
  cleanup();
125
129
  const result = [...selected].sort().map(i => options[i]);
@@ -178,9 +182,35 @@ export async function mcpInstall() {
178
182
  agentKeys = keys;
179
183
  } else {
180
184
  rl.close(); // close readline so raw mode works
185
+
186
+ // Build options with detected status and preselect
187
+ const agentOptions = keys.map(k => {
188
+ const agent = MCP_AGENTS[k];
189
+ const present = detectAgentPresence(k);
190
+ // Check if already configured
191
+ let installed = false;
192
+ for (const cfgPath of [agent.global, agent.project]) {
193
+ if (!cfgPath) continue;
194
+ const abs = expandHome(cfgPath);
195
+ if (!existsSync(abs)) continue;
196
+ try {
197
+ const config = JSON.parse(readFileSync(abs, 'utf-8'));
198
+ if (config[agent.key]?.mindos) { installed = true; break; }
199
+ } catch {}
200
+ }
201
+ const hint = installed ? 'configured' : present ? 'detected' : 'not found';
202
+ return { label: agent.name, hint, value: k, preselect: installed || present };
203
+ });
204
+
205
+ // Sort: configured > detected > not found
206
+ agentOptions.sort((a, b) => {
207
+ const rank = (o) => o.hint === 'configured' ? 0 : o.preselect ? 1 : 2;
208
+ return rank(a) - rank(b);
209
+ });
210
+
181
211
  const picked = await interactiveMultiSelect(
182
212
  'Which Agents to configure?',
183
- keys.map(k => ({ label: MCP_AGENTS[k].name, hint: k, value: k })),
213
+ agentOptions,
184
214
  );
185
215
  if (picked.length === 0) {
186
216
  console.log(dim('\nNo agents selected. Exiting.\n'));
package/package.json CHANGED
@@ -1,13 +1,15 @@
1
1
  {
2
2
  "name": "@geminilight/mindos",
3
- "version": "0.5.8",
3
+ "version": "0.5.10",
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",
7
7
  "mcp",
8
8
  "knowledge-base",
9
+ "knowledge-management",
9
10
  "ai-agent",
10
11
  "local-first",
12
+ "markdown",
11
13
  "second-brain"
12
14
  ],
13
15
  "type": "module",
package/scripts/setup.js CHANGED
@@ -429,7 +429,8 @@ function isPortInUse(port) {
429
429
  return new Promise((resolve) => {
430
430
  const sock = createConnection({ port, host: '127.0.0.1' });
431
431
  const cleanup = (result) => { sock.destroy(); resolve(result); };
432
- sock.setTimeout(500, () => cleanup(true));
432
+ // On localhost, timeout means no response — treat as free (same as bin/lib/port.js)
433
+ sock.setTimeout(500, () => cleanup(false));
433
434
  sock.once('connect', () => cleanup(true));
434
435
  sock.once('error', (err) => {
435
436
  // ECONNREFUSED = nothing listening → free; other errors = treat as in-use
@@ -628,6 +629,12 @@ async function runMcpInstallStep(mcpPort, authToken) {
628
629
  };
629
630
  });
630
631
 
632
+ // Sort: configured > detected > not found (stable within each group)
633
+ options.sort((a, b) => {
634
+ const rank = (o) => o.hint.includes('configured') || o.hint.includes('已配置') ? 0 : o.preselect ? 1 : 2;
635
+ return rank(a) - rank(b);
636
+ });
637
+
631
638
  // Multi-select using raw mode
632
639
  const selected = await (async () => {
633
640
  return new Promise((resolveSelected) => {
@@ -636,7 +643,7 @@ async function runMcpInstallStep(mcpPort, authToken) {
636
643
 
637
644
  const render = (first = false) => {
638
645
  if (!first) write(`\x1b[${options.length + 2}A\x1b[J`);
639
- write(`${c.bold(uiLang === 'zh' ? '选择 Agent:' : 'Select agents:')} ${c.dim(uiLang === 'zh' ? '(↑↓ 移动 空格 切换 A 全选 Enter 确认)' : '(↑↓ move Space toggle A all Enter confirm)')}\n`);
646
+ write(`${c.bold(uiLang === 'zh' ? '选择 Agent:' : 'Select agents:')} ${c.dim(uiLang === 'zh' ? '(↑↓ 移动 空格 切换 D 已检测 A 全选 Enter 确认)' : '(↑↓ move Space toggle D detected A all Enter confirm)')}\n`);
640
647
  for (let i = 0; i < options.length; i++) {
641
648
  const o = options[i];
642
649
  const check = chosen.has(i) ? c.green('✔') : c.dim('○');
@@ -665,6 +672,11 @@ async function runMcpInstallStep(mcpPort, authToken) {
665
672
  if (chosen.size === options.length) chosen.clear();
666
673
  else options.forEach((_, i) => chosen.add(i));
667
674
  render();
675
+ } else if (key === 'd' || key === 'D') {
676
+ // Select only detected/configured agents
677
+ chosen.clear();
678
+ options.forEach((o, i) => { if (o.preselect) chosen.add(i); });
679
+ render();
668
680
  } else if (key === '\r' || key === '\n') {
669
681
  cleanup();
670
682
  resolveSelected([...chosen].sort().map(i => options[i].value));
@@ -733,6 +745,12 @@ const AGENT_NAME_MAP = {
733
745
  'trae': 'trae',
734
746
  'openclaw': 'openclaw',
735
747
  'codebuddy': 'codebuddy',
748
+ 'iflow-cli': 'iflow-cli',
749
+ 'pi': 'pi',
750
+ 'augment': 'augment',
751
+ 'qwen-code': 'qwen-code',
752
+ 'trae-cn': 'trae-cn',
753
+ 'roo': 'roo',
736
754
  };
737
755
 
738
756
  /**
@@ -840,19 +858,38 @@ async function startGuiSetup() {
840
858
  process.exit(0);
841
859
  }
842
860
  // Service not running — start on existing port
843
- usePort = await isPortInUse(existingPort)
844
- ? await findFreePort(9100) // existing port occupied by another process
845
- : existingPort;
861
+ if (await isPortInUse(existingPort)) {
862
+ // Port occupied try stopping leftover MindOS processes first
863
+ try {
864
+ const { stopMindos } = await import('../bin/lib/stop.js');
865
+ stopMindos();
866
+ // stopMindos() sends SIGTERM synchronously — wait for both web and mcp
867
+ // ports to free, since `start` will assertPortFree on both.
868
+ const { waitForPortFree } = await import('../bin/lib/gateway.js');
869
+ const mcpPort = config.mcpPort || 8787;
870
+ const [webFreed, mcpFreed] = await Promise.all([
871
+ waitForPortFree(existingPort),
872
+ waitForPortFree(mcpPort),
873
+ ]);
874
+ usePort = webFreed ? existingPort : await findFreePort(9100);
875
+ } catch {
876
+ usePort = await findFreePort(9100);
877
+ }
878
+ } else {
879
+ usePort = existingPort;
880
+ }
846
881
  }
847
882
 
848
883
  write(c.yellow(t('guiStarting') + '\n'));
849
884
 
850
885
  // Start the server in the background
886
+ // Pass MINDOS_WEB_PORT (not PORT) so loadConfig() won't override with the
887
+ // config file port — this is critical when we need a temporary port.
851
888
  const cliPath = resolve(__dirname, '../bin/cli.js');
852
889
  const child = spawn(process.execPath, [cliPath, 'start'], {
853
890
  detached: true,
854
891
  stdio: 'ignore',
855
- env: { ...process.env, PORT: String(usePort) },
892
+ env: { ...process.env, MINDOS_WEB_PORT: String(usePort) },
856
893
  });
857
894
  child.unref();
858
895
 
@@ -34,15 +34,72 @@ description: "组织和维护 Vibe Coding 项目的 wiki 文档体系。当用
34
34
 
35
35
  ## 文件体系
36
36
 
37
+ ### 编号体系:十位 = 层级,个位 = 序号
38
+
39
+ 编号按**"战略 → 架构 → 规范 → 阶段 → 运维 → 日志"**分层,均匀分布,奇数十位留空备用。
40
+
41
+ ```
42
+ wiki/
43
+ ├── 0x 战略 Strategy — 全局视角,不看代码也能读懂
44
+ │ ├── 00-product-proposal.md
45
+ │ ├── 01-project-roadmap.md
46
+ │ ├── 02-business-model.md # 有商业化需求时
47
+ │ └── 03-technical-pillars.md # 有技术壁垒/研究方向时
48
+
49
+ ├── 2x 架构 Architecture — 系统是怎么建的(描述事实)
50
+ │ ├── 20-system-architecture.md
51
+ │ └── 21-design-principle.md # 有自定义视觉语言时
52
+
53
+ ├── 3x (空,留给接口/API 文档)
54
+
55
+ ├── 4x 规范 Conventions — 怎么参与开发(约束行为)
56
+ │ ├── 40-conventions.md
57
+ │ └── 41-dev-pitfall-patterns.md # 踩坑经验
58
+
59
+ ├── 5x (空)
60
+
61
+ ├── 6x 阶段 Stages — 各阶段详细 spec(按需查阅)
62
+ │ ├── 60-stage-a.md
63
+ │ ├── 61-stage-b.md
64
+ │ └── ...
65
+
66
+ ├── 7x (空)
67
+
68
+ ├── 8x 运维 Operations — 坑、复盘、backlog
69
+ │ ├── 80-known-pitfalls.md
70
+ │ ├── 81-postmortem-*.md
71
+ │ ├── 84-design-exploration.md # 有 UI 探索时
72
+ │ └── 85-backlog.md
73
+
74
+ ├── 9x 日志 Log
75
+ │ └── 90-changelog.md
76
+
77
+ ├── specs/ — 任务 spec(活跃的,完成后归档)
78
+ │ └── task-spec-xxx.md
79
+ ├── refs/ — 参考资料(外部机制说明、技术调研)
80
+ └── archive/ — 已完结的 spec 和历史文档
81
+ ```
82
+
83
+ | 区段 | 用途 | 扩展性 |
84
+ |------|------|--------|
85
+ | `0x` | 战略:产品方向、路线图、商业、壁垒 | 最多 10 个全局文档 |
86
+ | `2x` | 架构:系统设计 + 设计系统 | 可加 22-data-model 等 |
87
+ | `3x` | 留空 | 未来放 API reference、协议文档 |
88
+ | `4x` | 规范:开发流程 + 踩坑经验 | 可加 42-testing-standards 等 |
89
+ | `5x` | 留空 | 未来按需定义 |
90
+ | `6x` | 阶段:各功能的详细 spec | 最多 10 个阶段 |
91
+ | `7x` | 留空 | 未来按需定义 |
92
+ | `8x` | 运维:已知坑、复盘、backlog | 可加 82-xxx、83-xxx |
93
+ | `9x` | 日志 | changelog、release notes |
94
+
37
95
  ### 核心模型:Why / What / How / Look × 全局 / 阶段
38
96
 
39
97
  | | 全局(稳定,新阶段才改) | 阶段(增量更新) |
40
98
  |---|---|---|
41
99
  | **Why** | `00-product-proposal.md` | — |
42
- | **What** | `01-project-roadmap.md` — 功能索引 | `1X-stage-X.md` — 设计决策 |
43
- | **How** | `02-system-architecture.md` — 架构 + 类型 | `1X-stage-X.md` — API、数据模型、受影响文件 |
44
- | **API** | `04-api-reference.md` — 完整 API 参考 | `1X-stage-X.md` 新增 API 的设计决策 |
45
- | **Look** | `03-design-principle.md` — 视觉语言 | — |
100
+ | **What** | `01-project-roadmap.md` — 功能索引 | `6X-stage-X.md` — 设计决策 |
101
+ | **How** | `20-system-architecture.md` — 架构 + 类型 | `6X-stage-X.md` — API、数据模型、受影响文件 |
102
+ | **Look** | `21-design-principle.md` — 视觉语言 | — |
46
103
 
47
104
  **关键规则:** stage 文件同时包含 What 和 How。一个功能的设计决策、API 契约、数据模型放在一个文件里。全局文件只做索引和导航,不重复 stage 的细节。
48
105
 
@@ -62,58 +119,29 @@ description: "组织和维护 Vibe Coding 项目的 wiki 文档体系。当用
62
119
  |------|------|--------|---------|
63
120
  | 00 | `product-proposal.md` | Agent + 你 | 产品愿景、产品定位、**不做什么**、目标用户、功能矩阵、路线图叙事 |
64
121
  | 01 | `project-roadmap.md` | Agent + 你 | 阶段总览表、全量功能索引(功能×状态×stage链接)、里程碑 |
65
- | 02 | `system-architecture.md` | Agent | 技术栈、目录结构、数据流、核心类型、环境变量(300-500 行) |
122
+ | 20 | `system-architecture.md` | Agent | 技术栈、目录结构、数据流、核心类型、环境变量(300-500 行) |
66
123
 
67
124
  ### 按需文件(第二梯队)
68
125
 
69
126
  | 编号 | 文件 | 何时需要 |
70
127
  |------|------|---------|
71
- | 03 | `design-principle.md` | 有自定义视觉语言时(非默认 UI 库样式) |
72
- | 04 | `api-reference.md` | API 超过 5 条路由,或 stage 归档后仍需查 API 细节 |
73
- | 05 | `glossary.md` | 项目有领域术语,Agent 容易用错词或混用近义词 |
74
- | 06 | `conventions.md` | 有明确的编码偏好/约束(库选择、命名、错误处理模式等) |
75
- | 1X | `stage-X.md` | 功能复杂度超过一句话能说清(150-300 行) |
76
- | | `task-spec-xxx.md` | 小功能 / 改进点的 spec,包含需求描述和验收标准;实现完成后归档或合并进对应 stage 文件 |
128
+ | 02 | `business-model.md` | 有商业化/变现需求时 |
129
+ | 03 | `technical-pillars.md` | 有明确的技术壁垒或研究方向时 |
130
+ | 21 | `design-principle.md` | 有自定义视觉语言时(非默认 UI 库样式) |
131
+ | 30 | `api-reference.md` | API 超过 5 条路由,或 stage 归档后仍需查 API 细节 |
132
+ | 40 | `conventions.md` | 有明确的编码偏好/约束(库选择、命名、错误处理模式等) |
133
+ | 41 | `dev-pitfall-patterns.md` | 踩坑积累到需要系统性记录时 |
134
+ | 6X | `stage-X.md` | 功能复杂度超过一句话能说清(150-300 行) |
135
+ | — | `specs/task-spec-xxx.md` | 小功能 / 改进点的 spec;实现完成后归档到 `archive/` |
136
+ | — | `refs/xxx.md` | 外部机制说明、技术调研、协议文档 |
77
137
  | 80 | `known-pitfalls.md` | 踩坑即记,不等阶段结束 |
78
- | 81 | `development-guide.md` | 有非显而易见的配置要求时 |
79
- | 82 | `postmortem-*.md` | 多个 bug 互相关联、暴露系统性问题时(单点问题用 pitfalls,系统性问题用 postmortem) |
138
+ | 81 | `postmortem-*.md` | 多个 bug 互相关联、暴露系统性问题时(单点问题用 pitfalls,系统性问题用 postmortem) |
80
139
  | 84 | `design-exploration.md` | 有 UI 设计探索、原型记录等创意过程产物时 |
81
140
  | 85 | `backlog.md` | 有临时 bug、技术债、改进想法需要追踪时 |
82
- | 8X | `external-*.md` | 依赖外部系统私有数据格式时 |
83
141
  | 90 | `changelog.md` | 发版时从 `85-backlog.md` 已完成条目整理写入,面向用户描述变更,不记内部实现细节 |
84
142
 
85
143
  > 每个文件的详细说明和"为什么需要"的论证见 `references/file-reference.md`。
86
144
 
87
- ### 目录结构与编号规范
88
-
89
- ```
90
- wiki/
91
- ├── 00-product-proposal.md # Why
92
- ├── 01-project-roadmap.md # What(全局索引)
93
- ├── 02-system-architecture.md # How(全局)
94
- ├── 03-design-principle.md # Look(全局)
95
- ├── 04-api-reference.md # API(全局)
96
- ├── 05-glossary.md # 术语表(有领域术语时)
97
- ├── 06-conventions.md # 编码约定(有明确偏好时)
98
- ├── 10-stage-a.md # 阶段文件
99
- ├── 11-stage-b.md
100
- ├── ...
101
- ├── 80-known-pitfalls.md # 辅助参考
102
- ├── 81-development-guide.md
103
- ├── 82-postmortem-*.md # 系统性问题复盘(多 bug 关联时)
104
- ├── 84-design-exploration.md # 设计探索/原型记录(有 UI 探索时)
105
- ├── 85-backlog.md # Bug、技术债、改进想法
106
- ├── 90-changelog.md # 日志
107
- └── archive/ # 已完结阶段归档
108
- ```
109
-
110
- | 区段 | 用途 |
111
- |------|------|
112
- | `00-09` | 全局文件,按阅读优先级排列 |
113
- | `10-79` | 阶段文件,按时间递增 |
114
- | `80-89` | 辅助参考文件 |
115
- | `90-99` | 日志类文件 |
116
-
117
145
  ### Stage 文件生命周期
118
146
 
119
147
  阶段完全交付且后续阶段不再引用其 API/数据模型时 → 移入 `wiki/archive/`,`01-project-roadmap.md` 中保留索引行并标注 `[archived]`。
@@ -126,11 +154,11 @@ wiki/
126
154
 
127
155
  | 场景 | 路径 |
128
156
  |------|------|
129
- | 新对话 / 新功能 | `00-product-proposal` → `02-system-architecture` → 当前 `stage-X` |
130
- | 修 Bug | `02-system-architecture` → `80-known-pitfalls` → 相关 `stage-X` |
131
- | 修 Bug(反复出现) | `82-postmortem-*` → `02-system-architecture` → `80-known-pitfalls` → 相关 `stage-X` |
132
- | UI 调整 | `03-design-principle` → `02-system-architecture`(目录结构)→ 相关组件 |
133
- | 了解全貌 | `00-product-proposal` → `01-project-roadmap` → `02-system-architecture` |
157
+ | 新对话 / 新功能 | `00-product-proposal` → `20-system-architecture` → 当前 `6X-stage-X` |
158
+ | 修 Bug | `20-system-architecture` → `80-known-pitfalls` → 相关 `6X-stage-X` |
159
+ | 修 Bug(反复出现) | `81-postmortem-*` → `20-system-architecture` → `80-known-pitfalls` → 相关 `6X-stage-X` |
160
+ | UI 调整 | `21-design-principle` → `20-system-architecture`(目录结构)→ 相关组件 |
161
+ | 了解全貌 | `00-product-proposal` → `01-project-roadmap` → `20-system-architecture` |
134
162
 
135
163
  ---
136
164
 
@@ -177,20 +205,21 @@ Skill 触发时生成 wiki 结构,但 wiki 的日常同步发生在每次开
177
205
 
178
206
  | 当你做了这件事 | 更新哪个文件 |
179
207
  |--------------|------------|
180
- | 新增/修改 API 路由 | `wiki/04-api-reference.md` 追加或修改对应条目 |
208
+ | 新增/修改 API 路由 | `wiki/30-api-reference.md` 追加或修改对应条目 |
181
209
  | 完成一个 stage 的功能 | `wiki/01-project-roadmap.md` 对应行状态改为 ✅ |
182
210
  | 遇到非显而易见的坑 | `wiki/80-known-pitfalls.md` 追加一条(现象、原因、解法) |
183
- | 多个 bug 互相关联、暴露系统性问题 | 新建 `wiki/82-postmortem-*.md`(单点问题用 pitfalls,系统性问题用 postmortem) |
184
- | 架构变更(新模块、新数据流) | `wiki/02-system-architecture.md` 更新对应章节 |
211
+ | 多个 bug 互相关联、暴露系统性问题 | 新建 `wiki/81-postmortem-*.md`(单点问题用 pitfalls,系统性问题用 postmortem) |
212
+ | 架构变更(新模块、新数据流) | `wiki/20-system-architecture.md` 更新对应章节 |
185
213
  | 阶段全部交付 | `wiki/90-changelog.md` 补一笔(从 backlog 已完成条目整理) |
186
214
  | 发现 bug / 技术债 / 改进想法 | `wiki/85-backlog.md` 追加一条 |
187
- | 新增设计 token / 动效 | `wiki/03-design-principle.md` 追加对应条目 |
215
+ | 新增设计 token / 动效 | `wiki/21-design-principle.md` 追加对应条目 |
188
216
  | 出现新领域术语 | `wiki/05-glossary.md` 追加定义,防止 Agent 后续用词混乱 |
189
217
  | 重命名 / 移动 wiki 文件 | 同步更新所有引用该文件的链接 |
190
218
 
191
219
  **新建文件时机:**
192
- - 新功能复杂度超过一句话说清 → 新建 `wiki/1X-stage-X.md`
193
- - 小功能 / 改进点需要 spec → 新建 `wiki/task-spec-xxx.md`(实现完成后归档或合并进 stage 文件)
220
+ - 新功能复杂度超过一句话说清 → 新建 `wiki/6X-stage-X.md`
221
+ - 小功能 / 改进点需要 spec → 新建 `wiki/specs/task-spec-xxx.md`(实现完成后归档到 `archive/`)
222
+ - 外部机制调研 → 新建 `wiki/refs/xxx.md`
194
223
 
195
224
  **定期检查(每个阶段开始时):**
196
225
  - 扫描 wiki/ 下所有文件,更新 `Last verified` 日期
@@ -209,15 +238,15 @@ Skill 触发时生成 wiki 结构,但 wiki 的日常同步发生在每次开
209
238
  |---------|---------------|
210
239
  | `product-proposal.tmpl.md` | `00-product-proposal.md` |
211
240
  | `project-roadmap.tmpl.md` | `01-project-roadmap.md` |
212
- | `system-architecture.tmpl.md` | `02-system-architecture.md` |
213
- | `design-principle.tmpl.md` | `03-design-principle.md` |
214
- | `api-reference.tmpl.md` | `04-api-reference.md` |
215
- | `glossary.tmpl.md` | `05-glossary.md` |
216
- | `conventions.tmpl.md` | `06-conventions.md` |
217
- | `stage-x.tmpl.md` | `1X-stage-X.md` |
241
+ | `business-model.tmpl.md` | `02-business-model.md` |
242
+ | `technical-pillars.tmpl.md` | `03-technical-pillars.md` |
243
+ | `system-architecture.tmpl.md` | `20-system-architecture.md` |
244
+ | `design-principle.tmpl.md` | `21-design-principle.md` |
245
+ | `api-reference.tmpl.md` | `30-api-reference.md` |
246
+ | `conventions.tmpl.md` | `40-conventions.md` |
247
+ | `stage-x.tmpl.md` | `6X-stage-X.md` |
218
248
  | `known-pitfalls.tmpl.md` | `80-known-pitfalls.md` |
219
- | `development-guide.tmpl.md` | `81-development-guide.md` |
220
- | `postmortem.tmpl.md` | `82-postmortem-*.md` |
249
+ | `postmortem.tmpl.md` | `81-postmortem-*.md` |
221
250
  | `design-exploration.tmpl.md` | `84-design-exploration.md` |
222
251
  | `backlog.tmpl.md` | `85-backlog.md` |
223
252
  | `changelog.tmpl.md` | `90-changelog.md` |