@geminilight/mindos 0.5.7 → 0.5.9
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 +18 -17
- package/README_zh.md +15 -14
- package/app/app/api/mcp/agents/route.ts +7 -0
- package/app/app/api/mcp/install-skill/route.ts +7 -1
- package/app/app/api/setup/check-port/route.ts +27 -3
- package/app/app/api/setup/route.ts +2 -9
- package/app/app/globals.css +18 -2
- package/app/app/login/page.tsx +1 -1
- package/app/app/view/[...path]/ViewPageClient.tsx +9 -9
- package/app/components/AskModal.tsx +1 -1
- package/app/components/FileTree.tsx +5 -5
- package/app/components/HomeContent.tsx +1 -1
- package/app/components/SetupWizard.tsx +283 -141
- package/app/components/SyncStatusBar.tsx +3 -3
- package/app/components/ask/MessageList.tsx +2 -2
- package/app/components/ask/SessionHistory.tsx +1 -1
- package/app/components/renderers/agent-inspector/AgentInspectorRenderer.tsx +5 -5
- package/app/components/renderers/config/ConfigRenderer.tsx +3 -3
- package/app/components/renderers/csv/types.ts +1 -1
- package/app/components/renderers/diff/DiffRenderer.tsx +9 -9
- package/app/components/renderers/timeline/TimelineRenderer.tsx +1 -1
- package/app/components/renderers/workflow/WorkflowRenderer.tsx +2 -2
- package/app/components/settings/McpTab.tsx +66 -24
- package/app/components/settings/Primitives.tsx +3 -3
- package/app/components/settings/SyncTab.tsx +5 -5
- package/app/lib/i18n.ts +48 -4
- package/app/lib/mcp-agents.ts +81 -10
- package/bin/lib/build.js +25 -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 +13 -1
- package/scripts/setup.js +43 -6
- package/skills/project-wiki/SKILL.md +223 -0
- package/skills/project-wiki/assets/api-reference.tmpl.md +49 -0
- package/skills/project-wiki/assets/backlog.tmpl.md +15 -0
- package/skills/project-wiki/assets/changelog.tmpl.md +16 -0
- package/skills/project-wiki/assets/conventions.tmpl.md +29 -0
- package/skills/project-wiki/assets/design-exploration.tmpl.md +26 -0
- package/skills/project-wiki/assets/design-principle.tmpl.md +48 -0
- package/skills/project-wiki/assets/development-guide.tmpl.md +38 -0
- package/skills/project-wiki/assets/glossary.tmpl.md +9 -0
- package/skills/project-wiki/assets/known-pitfalls.tmpl.md +21 -0
- package/skills/project-wiki/assets/postmortem.tmpl.md +38 -0
- package/skills/project-wiki/assets/product-proposal.tmpl.md +41 -0
- package/skills/project-wiki/assets/project-roadmap.tmpl.md +23 -0
- package/skills/project-wiki/assets/stage-x.tmpl.md +78 -0
- package/skills/project-wiki/assets/system-architecture.tmpl.md +62 -0
- package/skills/project-wiki/references/file-reference.md +254 -0
- package/skills/project-wiki/references/writing-guide.md +28 -0
- package/app/data/pages/home-dark.png +0 -0
- package/app/data/pages/home-mobile-crop.png +0 -0
- package/app/data/pages/home-mobile.png +0 -0
- package/app/data/pages/home.png +0 -0
- package/app/data/pages/view-dir.png +0 -0
- package/app/data/pages/view-file-bot.png +0 -0
- package/app/data/pages/view-file-dark-crop.png +0 -0
- package/app/data/pages/view-file-dark.png +0 -0
- package/app/data/pages/view-file-mobile.png +0 -0
- package/app/data/pages/view-file-sm.png +0 -0
- package/app/data/pages/view-file-top.png +0 -0
- package/app/data/pages/view-file.png +0 -0
- package/app/eslint.config.mjs +0 -18
- package/app/public/landing/index.html +0 -353
- package/app/public/landing/style.css +0 -216
- package/app/vitest.config.ts +0 -14
- package/assets/demo-flow-zh.html +0 -622
package/app/lib/mcp-agents.ts
CHANGED
|
@@ -29,16 +29,6 @@ export const MCP_AGENTS: Record<string, AgentDef> = {
|
|
|
29
29
|
presenceCli: 'claude',
|
|
30
30
|
presenceDirs: ['~/.claude/'],
|
|
31
31
|
},
|
|
32
|
-
'claude-desktop': {
|
|
33
|
-
name: 'Claude Desktop',
|
|
34
|
-
project: null,
|
|
35
|
-
global: process.platform === 'darwin'
|
|
36
|
-
? '~/Library/Application Support/Claude/claude_desktop_config.json'
|
|
37
|
-
: '~/.config/Claude/claude_desktop_config.json',
|
|
38
|
-
key: 'mcpServers',
|
|
39
|
-
preferredTransport: 'http',
|
|
40
|
-
presenceDirs: ['~/Library/Application Support/Claude/', '~/.config/Claude/'],
|
|
41
|
-
},
|
|
42
32
|
'cursor': {
|
|
43
33
|
name: 'Cursor',
|
|
44
34
|
project: '.cursor/mcp.json',
|
|
@@ -103,6 +93,87 @@ export const MCP_AGENTS: Record<string, AgentDef> = {
|
|
|
103
93
|
presenceCli: 'claude-internal',
|
|
104
94
|
presenceDirs: ['~/.claude-internal/'],
|
|
105
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
|
+
},
|
|
106
177
|
};
|
|
107
178
|
|
|
108
179
|
/* ── MindOS MCP Install Detection ──────────────────────────────────────── */
|
package/bin/lib/build.js
CHANGED
|
@@ -65,6 +65,17 @@ function writeDepsStamp() {
|
|
|
65
65
|
}
|
|
66
66
|
}
|
|
67
67
|
|
|
68
|
+
/** Critical packages that must exist after npm install for the app to work. */
|
|
69
|
+
const CRITICAL_DEPS = ['next', '@next/env', 'react', 'react-dom'];
|
|
70
|
+
|
|
71
|
+
function verifyDeps() {
|
|
72
|
+
const nm = resolve(ROOT, 'app', 'node_modules');
|
|
73
|
+
for (const dep of CRITICAL_DEPS) {
|
|
74
|
+
if (!existsSync(resolve(nm, dep, 'package.json'))) return false;
|
|
75
|
+
}
|
|
76
|
+
return true;
|
|
77
|
+
}
|
|
78
|
+
|
|
68
79
|
export function ensureAppDeps() {
|
|
69
80
|
const appNext = resolve(ROOT, 'app', 'node_modules', 'next', 'package.json');
|
|
70
81
|
const needsInstall = !existsSync(appNext) || depsChanged();
|
|
@@ -90,5 +101,19 @@ export function ensureAppDeps() {
|
|
|
90
101
|
: 'Installing app dependencies (first run)...\n';
|
|
91
102
|
console.log(yellow(label));
|
|
92
103
|
run('npm install --prefer-offline --no-workspaces', resolve(ROOT, 'app'));
|
|
104
|
+
|
|
105
|
+
// Verify critical deps — npm tar extraction can silently fail (ENOENT race)
|
|
106
|
+
if (!verifyDeps()) {
|
|
107
|
+
console.log(yellow('Some dependencies are incomplete, retrying with clean install...\n'));
|
|
108
|
+
const nm = resolve(ROOT, 'app', 'node_modules');
|
|
109
|
+
rmSync(nm, { recursive: true, force: true });
|
|
110
|
+
run('npm install --no-workspaces', resolve(ROOT, 'app'));
|
|
111
|
+
if (!verifyDeps()) {
|
|
112
|
+
console.error(red('\n✘ Failed to install dependencies after retry.\n'));
|
|
113
|
+
console.error(' Try manually: cd ' + resolve(ROOT, 'app') + ' && rm -rf node_modules && npm install');
|
|
114
|
+
process.exit(1);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
93
118
|
writeDepsStamp();
|
|
94
119
|
}
|
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.9",
|
|
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",
|
|
@@ -42,6 +44,16 @@
|
|
|
42
44
|
"!app/.claude",
|
|
43
45
|
"!app/__tests__",
|
|
44
46
|
"!app/**/*.bak",
|
|
47
|
+
"!app/vitest.config.ts",
|
|
48
|
+
"!app/eslint.config.mjs",
|
|
49
|
+
"!app/tsconfig.tsbuildinfo",
|
|
50
|
+
"!app/data/pages",
|
|
51
|
+
"!assets/node_modules",
|
|
52
|
+
"!assets/package-lock.json",
|
|
53
|
+
"!assets/package.json",
|
|
54
|
+
"!assets/capture-demo.mjs",
|
|
55
|
+
"!assets/demo-flow.html",
|
|
56
|
+
"!assets/demo-flow-zh.html",
|
|
45
57
|
"!mcp/node_modules",
|
|
46
58
|
"!mcp/dist"
|
|
47
59
|
],
|
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
|
|