@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.
- package/README.md +9 -10
- package/README_zh.md +8 -9
- package/app/app/api/mcp/agents/route.ts +7 -0
- package/app/app/api/mcp/install-skill/route.ts +6 -0
- package/app/app/api/setup/check-port/route.ts +27 -3
- package/app/app/api/setup/route.ts +2 -9
- package/app/app/api/skills/route.ts +1 -1
- package/app/app/globals.css +28 -4
- package/app/app/login/page.tsx +2 -2
- package/app/app/view/[...path]/ViewPageClient.tsx +15 -10
- package/app/app/view/[...path]/not-found.tsx +1 -1
- package/app/components/AskModal.tsx +5 -5
- package/app/components/Breadcrumb.tsx +2 -2
- package/app/components/DirView.tsx +6 -6
- package/app/components/FileTree.tsx +7 -7
- package/app/components/HomeContent.tsx +8 -8
- package/app/components/OnboardingView.tsx +1 -1
- package/app/components/SearchModal.tsx +1 -1
- package/app/components/SettingsModal.tsx +2 -2
- package/app/components/SetupWizard.tsx +1 -1258
- package/app/components/Sidebar.tsx +4 -4
- package/app/components/SidebarLayout.tsx +9 -0
- package/app/components/SyncStatusBar.tsx +6 -6
- package/app/components/TableOfContents.tsx +1 -1
- package/app/components/UpdateBanner.tsx +1 -1
- package/app/components/ask/FileChip.tsx +1 -1
- package/app/components/ask/MentionPopover.tsx +4 -4
- package/app/components/ask/MessageList.tsx +3 -3
- package/app/components/ask/SessionHistory.tsx +3 -3
- package/app/components/renderers/agent-inspector/AgentInspectorRenderer.tsx +5 -5
- package/app/components/renderers/config/ConfigRenderer.tsx +4 -4
- package/app/components/renderers/csv/BoardView.tsx +2 -2
- package/app/components/renderers/csv/ConfigPanel.tsx +5 -5
- package/app/components/renderers/csv/GalleryView.tsx +1 -1
- package/app/components/renderers/csv/types.ts +1 -1
- package/app/components/renderers/diff/DiffRenderer.tsx +9 -9
- package/app/components/renderers/graph/GraphRenderer.tsx +1 -1
- package/app/components/renderers/summary/SummaryRenderer.tsx +1 -1
- package/app/components/renderers/timeline/TimelineRenderer.tsx +1 -1
- package/app/components/renderers/workflow/WorkflowRenderer.tsx +4 -4
- package/app/components/settings/KnowledgeTab.tsx +1 -1
- package/app/components/settings/McpTab.tsx +93 -47
- package/app/components/settings/PluginsTab.tsx +4 -4
- package/app/components/settings/Primitives.tsx +4 -4
- package/app/components/settings/SyncTab.tsx +13 -13
- package/app/components/setup/StepAI.tsx +67 -0
- package/app/components/setup/StepAgents.tsx +237 -0
- package/app/components/setup/StepDots.tsx +39 -0
- package/app/components/setup/StepKB.tsx +237 -0
- package/app/components/setup/StepPorts.tsx +121 -0
- package/app/components/setup/StepReview.tsx +211 -0
- package/app/components/setup/StepSecurity.tsx +78 -0
- package/app/components/setup/constants.tsx +13 -0
- package/app/components/setup/index.tsx +464 -0
- package/app/components/setup/types.ts +53 -0
- package/app/lib/i18n.ts +52 -8
- package/app/lib/mcp-agents.ts +81 -0
- package/bin/lib/gateway.js +44 -4
- package/bin/lib/mcp-agents.js +81 -0
- package/bin/lib/mcp-install.js +34 -4
- package/package.json +3 -1
- package/scripts/setup.js +43 -6
- package/skills/project-wiki/SKILL.md +92 -63
- package/app/public/landing/index.html +0 -353
- package/app/public/landing/style.css +0 -216
package/app/lib/mcp-agents.ts
CHANGED
|
@@ -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 ──────────────────────────────────────── */
|
package/bin/lib/gateway.js
CHANGED
|
@@ -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
|
|
package/bin/lib/mcp-agents.js
CHANGED
|
@@ -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) {
|
package/bin/lib/mcp-install.js
CHANGED
|
@@ -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
|
-
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
844
|
-
|
|
845
|
-
|
|
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,
|
|
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` — 功能索引 | `
|
|
43
|
-
| **How** | `
|
|
44
|
-
| **
|
|
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
|
-
|
|
|
122
|
+
| 20 | `system-architecture.md` | Agent | 技术栈、目录结构、数据流、核心类型、环境变量(300-500 行) |
|
|
66
123
|
|
|
67
124
|
### 按需文件(第二梯队)
|
|
68
125
|
|
|
69
126
|
| 编号 | 文件 | 何时需要 |
|
|
70
127
|
|------|------|---------|
|
|
71
|
-
|
|
|
72
|
-
|
|
|
73
|
-
|
|
|
74
|
-
|
|
|
75
|
-
|
|
|
76
|
-
|
|
|
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 | `
|
|
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` → `
|
|
130
|
-
| 修 Bug | `
|
|
131
|
-
| 修 Bug(反复出现) | `
|
|
132
|
-
| UI 调整 | `
|
|
133
|
-
| 了解全貌 | `00-product-proposal` → `01-project-roadmap` → `
|
|
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/
|
|
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/
|
|
184
|
-
| 架构变更(新模块、新数据流) | `wiki/
|
|
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/
|
|
215
|
+
| 新增设计 token / 动效 | `wiki/21-design-principle.md` 追加对应条目 |
|
|
188
216
|
| 出现新领域术语 | `wiki/05-glossary.md` 追加定义,防止 Agent 后续用词混乱 |
|
|
189
217
|
| 重命名 / 移动 wiki 文件 | 同步更新所有引用该文件的链接 |
|
|
190
218
|
|
|
191
219
|
**新建文件时机:**
|
|
192
|
-
- 新功能复杂度超过一句话说清 → 新建 `wiki/
|
|
193
|
-
- 小功能 / 改进点需要 spec → 新建 `wiki/task-spec-xxx.md
|
|
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
|
-
| `
|
|
213
|
-
| `
|
|
214
|
-
| `
|
|
215
|
-
| `
|
|
216
|
-
| `
|
|
217
|
-
| `
|
|
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
|
-
| `
|
|
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` |
|