@geminilight/mindos 0.5.11 → 0.5.13

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 (42) hide show
  1. package/README.md +9 -9
  2. package/README_zh.md +9 -9
  3. package/app/README.md +2 -2
  4. package/app/app/api/ask/route.ts +191 -19
  5. package/app/app/api/mcp/install/route.ts +1 -1
  6. package/app/app/api/mcp/status/route.ts +11 -16
  7. package/app/app/api/settings/route.ts +3 -1
  8. package/app/app/api/setup/route.ts +7 -7
  9. package/app/app/api/sync/route.ts +18 -15
  10. package/app/components/AskModal.tsx +28 -32
  11. package/app/components/SettingsModal.tsx +7 -3
  12. package/app/components/ask/MessageList.tsx +65 -3
  13. package/app/components/ask/ThinkingBlock.tsx +55 -0
  14. package/app/components/ask/ToolCallBlock.tsx +97 -0
  15. package/app/components/settings/AiTab.tsx +76 -2
  16. package/app/components/settings/types.ts +8 -0
  17. package/app/components/setup/StepReview.tsx +31 -25
  18. package/app/components/setup/index.tsx +6 -3
  19. package/app/lib/agent/context.ts +317 -0
  20. package/app/lib/agent/index.ts +4 -0
  21. package/app/lib/agent/prompt.ts +46 -31
  22. package/app/lib/agent/stream-consumer.ts +212 -0
  23. package/app/lib/agent/tools.ts +159 -4
  24. package/app/lib/i18n.ts +28 -0
  25. package/app/lib/settings.ts +22 -0
  26. package/app/lib/types.ts +23 -0
  27. package/app/package.json +2 -3
  28. package/bin/cli.js +41 -21
  29. package/bin/lib/build.js +6 -2
  30. package/bin/lib/gateway.js +24 -3
  31. package/bin/lib/mcp-install.js +2 -2
  32. package/bin/lib/mcp-spawn.js +3 -3
  33. package/bin/lib/stop.js +1 -1
  34. package/bin/lib/sync.js +81 -40
  35. package/mcp/README.md +5 -5
  36. package/mcp/src/index.ts +2 -2
  37. package/package.json +3 -2
  38. package/scripts/setup.js +17 -12
  39. package/scripts/upgrade-prompt.md +6 -6
  40. package/skills/mindos/SKILL.md +47 -183
  41. package/skills/mindos-zh/SKILL.md +47 -183
  42. package/app/package-lock.json +0 -15615
package/bin/lib/sync.js CHANGED
@@ -1,10 +1,19 @@
1
- import { execSync } from 'node:child_process';
2
- import { existsSync, readFileSync, writeFileSync, mkdirSync } from 'node:fs';
1
+ import { execFileSync } from 'node:child_process';
2
+ import { existsSync, readFileSync, writeFileSync, mkdirSync, renameSync } from 'node:fs';
3
3
  import { resolve } from 'node:path';
4
4
  import { homedir } from 'node:os';
5
5
  import { CONFIG_PATH, MINDOS_DIR } from './constants.js';
6
6
  import { bold, dim, cyan, green, red, yellow } from './colors.js';
7
7
 
8
+ // ── Atomic write helper ────────────────────────────────────────────────────
9
+
10
+ function atomicWriteJSON(filePath, data) {
11
+ const content = JSON.stringify(data, null, 2) + '\n';
12
+ const tmp = filePath + '.tmp';
13
+ writeFileSync(tmp, content, 'utf-8');
14
+ renameSync(tmp, filePath);
15
+ }
16
+
8
17
  // ── Config helpers ──────────────────────────────────────────────────────────
9
18
 
10
19
  function loadSyncConfig() {
@@ -20,7 +29,7 @@ function saveSyncConfig(syncConfig) {
20
29
  let config = {};
21
30
  try { config = JSON.parse(readFileSync(CONFIG_PATH, 'utf-8')); } catch {}
22
31
  config.sync = syncConfig;
23
- writeFileSync(CONFIG_PATH, JSON.stringify(config, null, 2) + '\n', 'utf-8');
32
+ atomicWriteJSON(CONFIG_PATH, config);
24
33
  }
25
34
 
26
35
  function getMindRoot() {
@@ -44,7 +53,7 @@ function loadSyncState() {
44
53
 
45
54
  function saveSyncState(state) {
46
55
  if (!existsSync(MINDOS_DIR)) mkdirSync(MINDOS_DIR, { recursive: true });
47
- writeFileSync(SYNC_STATE_PATH, JSON.stringify(state, null, 2) + '\n', 'utf-8');
56
+ atomicWriteJSON(SYNC_STATE_PATH, state);
48
57
  }
49
58
 
50
59
  // ── Git helpers ─────────────────────────────────────────────────────────────
@@ -53,13 +62,13 @@ function isGitRepo(dir) {
53
62
  return existsSync(resolve(dir, '.git'));
54
63
  }
55
64
 
56
- function gitExec(cmd, cwd) {
57
- return execSync(cmd, { cwd, encoding: 'utf-8', stdio: 'pipe' }).trim();
65
+ function gitExec(args, cwd) {
66
+ return execFileSync('git', args, { cwd, encoding: 'utf-8', stdio: 'pipe' }).trim();
58
67
  }
59
68
 
60
69
  function getRemoteUrl(cwd) {
61
70
  try {
62
- return gitExec('git remote get-url origin', cwd);
71
+ return gitExec(['remote', 'get-url', 'origin'], cwd);
63
72
  } catch {
64
73
  return null;
65
74
  }
@@ -67,7 +76,7 @@ function getRemoteUrl(cwd) {
67
76
 
68
77
  function getBranch(cwd) {
69
78
  try {
70
- return gitExec('git rev-parse --abbrev-ref HEAD', cwd);
79
+ return gitExec(['rev-parse', '--abbrev-ref', 'HEAD'], cwd);
71
80
  } catch {
72
81
  return 'main';
73
82
  }
@@ -75,7 +84,7 @@ function getBranch(cwd) {
75
84
 
76
85
  function getUnpushedCount(cwd) {
77
86
  try {
78
- return gitExec('git rev-list --count @{u}..HEAD', cwd);
87
+ return gitExec(['rev-list', '--count', '@{u}..HEAD'], cwd);
79
88
  } catch {
80
89
  return '?';
81
90
  }
@@ -85,12 +94,12 @@ function getUnpushedCount(cwd) {
85
94
 
86
95
  function autoCommitAndPush(mindRoot) {
87
96
  try {
88
- execSync('git add -A', { cwd: mindRoot, stdio: 'pipe' });
89
- const status = gitExec('git status --porcelain', mindRoot);
97
+ execFileSync('git', ['add', '-A'], { cwd: mindRoot, stdio: 'pipe' });
98
+ const status = gitExec(['status', '--porcelain'], mindRoot);
90
99
  if (!status) return;
91
100
  const timestamp = new Date().toISOString().replace('T', ' ').slice(0, 19);
92
- execSync(`git commit -m "auto-sync: ${timestamp}"`, { cwd: mindRoot, stdio: 'pipe' });
93
- execSync('git push', { cwd: mindRoot, stdio: 'pipe' });
101
+ execFileSync('git', ['commit', '-m', `auto-sync: ${timestamp}`], { cwd: mindRoot, stdio: 'pipe' });
102
+ execFileSync('git', ['push', '-u', 'origin', 'HEAD'], { cwd: mindRoot, stdio: 'pipe' });
94
103
  saveSyncState({ ...loadSyncState(), lastSync: new Date().toISOString(), lastError: null });
95
104
  } catch (err) {
96
105
  saveSyncState({ ...loadSyncState(), lastError: err.message, lastErrorTime: new Date().toISOString() });
@@ -99,31 +108,34 @@ function autoCommitAndPush(mindRoot) {
99
108
 
100
109
  function autoPull(mindRoot) {
101
110
  try {
102
- execSync('git pull --rebase --autostash', { cwd: mindRoot, stdio: 'pipe' });
111
+ execFileSync('git', ['pull', '--rebase', '--autostash'], { cwd: mindRoot, stdio: 'pipe' });
103
112
  saveSyncState({ ...loadSyncState(), lastPull: new Date().toISOString() });
104
113
  } catch {
105
114
  // rebase conflict → abort → merge
106
- try { execSync('git rebase --abort', { cwd: mindRoot, stdio: 'pipe' }); } catch {}
115
+ try { execFileSync('git', ['rebase', '--abort'], { cwd: mindRoot, stdio: 'pipe' }); } catch {}
107
116
  try {
108
- execSync('git pull --no-rebase', { cwd: mindRoot, stdio: 'pipe' });
117
+ execFileSync('git', ['pull', '--no-rebase'], { cwd: mindRoot, stdio: 'pipe' });
109
118
  saveSyncState({ ...loadSyncState(), lastPull: new Date().toISOString() });
110
119
  } catch {
111
120
  // merge conflict → keep both versions
112
121
  try {
113
- const conflicts = gitExec('git diff --name-only --diff-filter=U', mindRoot).split('\n').filter(Boolean);
122
+ const conflicts = gitExec(['diff', '--name-only', '--diff-filter=U'], mindRoot).split('\n').filter(Boolean);
123
+ const conflictWarnings = [];
114
124
  for (const file of conflicts) {
115
125
  try {
116
- const theirs = execSync(`git show :3:${file}`, { cwd: mindRoot, encoding: 'utf-8' });
126
+ const theirs = execFileSync('git', ['show', `:3:${file}`], { cwd: mindRoot, encoding: 'utf-8' });
117
127
  writeFileSync(resolve(mindRoot, file + '.sync-conflict'), theirs, 'utf-8');
118
- } catch {}
119
- try { execSync(`git checkout --ours "${file}"`, { cwd: mindRoot, stdio: 'pipe' }); } catch {}
128
+ } catch {
129
+ conflictWarnings.push(file);
130
+ }
131
+ try { execFileSync('git', ['checkout', '--ours', file], { cwd: mindRoot, stdio: 'pipe' }); } catch {}
120
132
  }
121
- execSync('git add -A', { cwd: mindRoot, stdio: 'pipe' });
122
- execSync('git commit -m "auto-sync: resolved conflicts (kept both versions)"', { cwd: mindRoot, stdio: 'pipe' });
133
+ execFileSync('git', ['add', '-A'], { cwd: mindRoot, stdio: 'pipe' });
134
+ execFileSync('git', ['commit', '-m', 'auto-sync: resolved conflicts (kept both versions)'], { cwd: mindRoot, stdio: 'pipe' });
123
135
  saveSyncState({
124
136
  ...loadSyncState(),
125
137
  lastPull: new Date().toISOString(),
126
- conflicts: conflicts.map(f => ({ file: f, time: new Date().toISOString() })),
138
+ conflicts: conflicts.map(f => ({ file: f, time: new Date().toISOString(), noBackup: conflictWarnings.includes(f) })),
127
139
  });
128
140
  } catch (err) {
129
141
  saveSyncState({ ...loadSyncState(), lastError: err.message, lastErrorTime: new Date().toISOString() });
@@ -133,9 +145,9 @@ function autoPull(mindRoot) {
133
145
 
134
146
  // Retry any pending pushes (handles previous push failures)
135
147
  try {
136
- const unpushed = gitExec('git rev-list --count @{u}..HEAD', mindRoot);
148
+ const unpushed = gitExec(['rev-list', '--count', '@{u}..HEAD'], mindRoot);
137
149
  if (parseInt(unpushed) > 0) {
138
- execSync('git push', { cwd: mindRoot, stdio: 'pipe' });
150
+ execFileSync('git', ['push'], { cwd: mindRoot, stdio: 'pipe' });
139
151
  saveSyncState({ ...loadSyncState(), lastSync: new Date().toISOString(), lastError: null });
140
152
  }
141
153
  } catch {
@@ -196,8 +208,8 @@ export async function initSync(mindRoot, opts = {}) {
196
208
  // 1. Ensure git repo
197
209
  if (!isGitRepo(mindRoot)) {
198
210
  if (!nonInteractive) console.log(dim('Initializing git repository...'));
199
- execSync('git init', { cwd: mindRoot, stdio: 'pipe' });
200
- try { execSync('git checkout -b main', { cwd: mindRoot, stdio: 'pipe' }); } catch {}
211
+ execFileSync('git', ['init'], { cwd: mindRoot, stdio: 'pipe' });
212
+ try { execFileSync('git', ['checkout', '-b', 'main'], { cwd: mindRoot, stdio: 'pipe' }); } catch {}
201
213
  }
202
214
 
203
215
  // 1b. Ensure .gitignore exists
@@ -226,35 +238,61 @@ export async function initSync(mindRoot, opts = {}) {
226
238
  if (platform === 'darwin') helper = 'osxkeychain';
227
239
  else if (platform === 'win32') helper = 'manager';
228
240
  else helper = 'store';
229
- try { execSync(`git config credential.helper '${helper}'`, { cwd: mindRoot, stdio: 'pipe' }); } catch {}
230
- // Store the credential via git credential approve
241
+ try { execFileSync('git', ['config', 'credential.helper', helper], { cwd: mindRoot, stdio: 'pipe' }); } catch (e) {
242
+ console.error(`[sync] credential.helper setup failed: ${e.message}`);
243
+ }
244
+ // Store the credential via git credential approve, then verify it stuck
245
+ let credentialStored = false;
231
246
  try {
232
247
  const credInput = `protocol=${urlObj.protocol.replace(':', '')}\nhost=${urlObj.host}\nusername=oauth2\npassword=${token}\n\n`;
233
- execSync('git credential approve', { cwd: mindRoot, input: credInput, stdio: 'pipe' });
234
- } catch {}
248
+ execFileSync('git', ['credential', 'approve'], { cwd: mindRoot, input: credInput, stdio: 'pipe' });
249
+ // Verify: credential fill should return the password we just stored
250
+ try {
251
+ const fillInput = `protocol=${urlObj.protocol.replace(':', '')}\nhost=${urlObj.host}\nusername=oauth2\n\n`;
252
+ const fillResult = execFileSync('git', ['credential', 'fill'], {
253
+ cwd: mindRoot, input: fillInput, encoding: 'utf-8',
254
+ stdio: ['pipe', 'pipe', 'pipe'], timeout: 5000,
255
+ env: { ...process.env, GIT_TERMINAL_PROMPT: '0' },
256
+ });
257
+ credentialStored = fillResult.includes(`password=${token}`);
258
+ } catch {
259
+ credentialStored = false;
260
+ }
261
+ } catch (e) {
262
+ if (!nonInteractive) console.error(`[sync] credential approve failed: ${e.message}`);
263
+ }
264
+ // If credential helper didn't actually persist, embed token in URL
265
+ if (!credentialStored) {
266
+ if (!nonInteractive) console.log(dim('Credential helper unavailable, using inline token'));
267
+ const fallbackUrl = new URL(remoteUrl);
268
+ fallbackUrl.username = 'oauth2';
269
+ fallbackUrl.password = token;
270
+ remoteUrl = fallbackUrl.toString();
271
+ }
235
272
  // For 'store' helper, restrict file permissions AFTER credential file is created
236
273
  if (helper === 'store') {
237
274
  const credFile = resolve(process.env.HOME || homedir(), '.git-credentials');
238
- try { execSync(`chmod 600 "${credFile}"`, { stdio: 'pipe' }); } catch {}
275
+ try { execFileSync('chmod', ['600', credFile], { stdio: 'pipe' }); } catch {}
239
276
  }
240
277
  }
241
278
 
242
279
  // 4. Set remote
243
280
  try {
244
- execSync(`git remote add origin "${remoteUrl}"`, { cwd: mindRoot, stdio: 'pipe' });
281
+ execFileSync('git', ['remote', 'add', 'origin', remoteUrl], { cwd: mindRoot, stdio: 'pipe' });
245
282
  } catch {
246
- execSync(`git remote set-url origin "${remoteUrl}"`, { cwd: mindRoot, stdio: 'pipe' });
283
+ execFileSync('git', ['remote', 'set-url', 'origin', remoteUrl], { cwd: mindRoot, stdio: 'pipe' });
247
284
  }
248
285
 
249
286
  // 5. Test connection
250
287
  if (!nonInteractive) console.log(dim('Testing connection...'));
251
288
  try {
252
- execSync('git ls-remote --exit-code origin', { cwd: mindRoot, stdio: 'pipe', timeout: 15000 });
289
+ execFileSync('git', ['ls-remote', '--exit-code', 'origin'], { cwd: mindRoot, stdio: 'pipe', timeout: 15000 });
253
290
  if (!nonInteractive) console.log(green('✔ Connection successful'));
254
- } catch {
255
- const errMsg = 'Remote not reachable check URL and credentials';
291
+ } catch (lsErr) {
292
+ const detail = lsErr.stderr ? lsErr.stderr.toString().trim() : '';
293
+ const errMsg = `Remote not reachable${detail ? ': ' + detail : ''} — check URL and credentials`;
256
294
  if (nonInteractive) throw new Error(errMsg);
257
- console.error(red('✘ Could not connect to remote. Check your URL and credentials.'));
295
+ console.error(red(`✘ ${errMsg}`));
258
296
  process.exit(1);
259
297
  }
260
298
 
@@ -272,11 +310,11 @@ export async function initSync(mindRoot, opts = {}) {
272
310
 
273
311
  // 7. First sync: pull if remote has content, push otherwise
274
312
  try {
275
- const refs = gitExec('git ls-remote --heads origin', mindRoot);
313
+ const refs = gitExec(['ls-remote', '--heads', 'origin'], mindRoot);
276
314
  if (refs) {
277
315
  if (!nonInteractive) console.log(dim('Pulling from remote...'));
278
316
  try {
279
- execSync(`git pull origin ${syncConfig.branch} --allow-unrelated-histories`, { cwd: mindRoot, stdio: nonInteractive ? 'pipe' : 'inherit' });
317
+ execFileSync('git', ['pull', 'origin', syncConfig.branch, '--allow-unrelated-histories'], { cwd: mindRoot, stdio: nonInteractive ? 'pipe' : 'inherit' });
280
318
  } catch {
281
319
  if (!nonInteractive) console.log(yellow('Pull completed with warnings. Check for conflicts.'));
282
320
  }
@@ -321,7 +359,10 @@ export async function startSyncDaemon(mindRoot) {
321
359
  autoPull(mindRoot);
322
360
 
323
361
  // Graceful shutdown: flush pending changes before exit
362
+ let shutdownInProgress = false;
324
363
  const gracefulShutdown = () => {
364
+ if (shutdownInProgress) return;
365
+ shutdownInProgress = true;
325
366
  if (commitTimer) { clearTimeout(commitTimer); commitTimer = null; }
326
367
  try { autoCommitAndPush(mindRoot); } catch {}
327
368
  stopSyncDaemon();
package/mcp/README.md CHANGED
@@ -26,7 +26,7 @@ mindos start # starts app + MCP server together
26
26
  Or run MCP server only:
27
27
 
28
28
  ```bash
29
- mindos mcp # HTTP mode (default, port 8787)
29
+ mindos mcp # HTTP mode (default, port 8781)
30
30
  MCP_TRANSPORT=stdio mindos mcp # stdio mode
31
31
  ```
32
32
 
@@ -34,11 +34,11 @@ MCP_TRANSPORT=stdio mindos mcp # stdio mode
34
34
 
35
35
  | Variable | Default | Description |
36
36
  |----------|---------|-------------|
37
- | `MINDOS_URL` | `http://localhost:3000` | App server base URL |
37
+ | `MINDOS_URL` | `http://localhost:3456` | App server base URL |
38
38
  | `AUTH_TOKEN` | — | Optional: bearer token (must match App's `AUTH_TOKEN`) |
39
39
  | `MCP_TRANSPORT` | `http` | Transport mode: `http` or `stdio` |
40
40
  | `MCP_HOST` | `127.0.0.1` | HTTP bind address (`0.0.0.0` for remote access) |
41
- | `MCP_PORT` | `8787` | HTTP listen port (configurable via `mindos onboard`) |
41
+ | `MCP_PORT` | `8781` | HTTP listen port (configurable via `mindos onboard`) |
42
42
  | `MCP_ENDPOINT` | `/mcp` | HTTP endpoint path |
43
43
 
44
44
  ## MCP Tools (20)
@@ -75,7 +75,7 @@ Add to your Agent's MCP config (field names vary by client):
75
75
  {
76
76
  "mcpServers": {
77
77
  "mindos": {
78
- "url": "http://localhost:8787/mcp",
78
+ "url": "http://localhost:8781/mcp",
79
79
  "headers": { "Authorization": "Bearer your-token" }
80
80
  }
81
81
  }
@@ -101,7 +101,7 @@ Add to your Agent's MCP config (field names vary by client):
101
101
  {
102
102
  "mcpServers": {
103
103
  "mindos": {
104
- "url": "http://<server-ip>:8787/mcp",
104
+ "url": "http://<server-ip>:8781/mcp",
105
105
  "headers": { "Authorization": "Bearer your-token" }
106
106
  }
107
107
  }
package/mcp/src/index.ts CHANGED
@@ -22,11 +22,11 @@ import { z } from "zod";
22
22
 
23
23
  // ─── Config ──────────────────────────────────────────────────────────────────
24
24
 
25
- const BASE_URL = process.env.MINDOS_URL ?? "http://localhost:3000";
25
+ const BASE_URL = process.env.MINDOS_URL ?? "http://localhost:3456";
26
26
  const AUTH_TOKEN = process.env.AUTH_TOKEN;
27
27
  const MCP_TRANSPORT = process.env.MCP_TRANSPORT ?? "http"; // "http" | "stdio"
28
28
  const MCP_HOST = process.env.MCP_HOST ?? "127.0.0.1";
29
- const MCP_PORT = parseInt(process.env.MCP_PORT ?? "8787", 10);
29
+ const MCP_PORT = parseInt(process.env.MCP_PORT ?? "8781", 10);
30
30
  const MCP_ENDPOINT = process.env.MCP_ENDPOINT ?? "/mcp";
31
31
  const CHARACTER_LIMIT = 25_000;
32
32
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@geminilight/mindos",
3
- "version": "0.5.11",
3
+ "version": "0.5.13",
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",
@@ -57,7 +57,8 @@
57
57
  "!assets/images",
58
58
  "!mcp/node_modules",
59
59
  "!mcp/dist",
60
- "!mcp/package-lock.json"
60
+ "!mcp/package-lock.json",
61
+ "!app/package-lock.json"
61
62
  ],
62
63
  "scripts": {
63
64
  "setup": "node scripts/setup.js",
package/scripts/setup.js CHANGED
@@ -844,11 +844,11 @@ async function startGuiSetup() {
844
844
  if (isFirstTime) {
845
845
  // First-time onboard: use a temporary port (scan from 9100) so the user's
846
846
  // chosen port in Step 3 can differ without a mid-setup restart.
847
- // 9100 is chosen to avoid conflicts with common services (5000=AirPlay, 3000/8080=dev).
847
+ // 9100 is chosen to avoid conflicts with common services (5000=AirPlay, 3456/8080=dev).
848
848
  usePort = await findFreePort(9100);
849
849
  } else {
850
850
  // Re-onboard: service is already running on config.port — reuse it.
851
- const existingPort = config.port || 3000;
851
+ const existingPort = config.port || 3456;
852
852
  if (await isSelfPort(existingPort)) {
853
853
  // Service already running — just open the setup page, no need to spawn.
854
854
  const url = `http://localhost:${existingPort}/setup`;
@@ -866,7 +866,7 @@ async function startGuiSetup() {
866
866
  // stopMindos() sends SIGTERM synchronously — wait for both web and mcp
867
867
  // ports to free, since `start` will assertPortFree on both.
868
868
  const { waitForPortFree } = await import('../bin/lib/gateway.js');
869
- const mcpPort = config.mcpPort || 8787;
869
+ const mcpPort = config.mcpPort || 8781;
870
870
  const [webFreed, mcpFreed] = await Promise.all([
871
871
  waitForPortFree(existingPort),
872
872
  waitForPortFree(mcpPort),
@@ -941,8 +941,8 @@ async function main() {
941
941
 
942
942
  console.log(c.bold('\nExisting config:'));
943
943
  console.log(row('Knowledge base:', c.cyan(existing.mindRoot || '(not set)')));
944
- console.log(row('Web port:', c.cyan(String(existing.port || '3000'))));
945
- console.log(row('MCP port:', c.cyan(String(existing.mcpPort || '8787'))));
944
+ console.log(row('Web port:', c.cyan(String(existing.port || '3456'))));
945
+ console.log(row('MCP port:', c.cyan(String(existing.mcpPort || '8781'))));
946
946
  console.log(row('Auth token:', existing.authToken ? mask(existing.authToken) : c.dim('(not set)')));
947
947
  console.log(row('Web password:', existing.webPassword ? '••••••••' : c.dim('(none)')));
948
948
  console.log(row('AI provider:', c.cyan(existing.ai?.provider || '(not set)')));
@@ -953,7 +953,7 @@ async function main() {
953
953
  const overwrite = await askYesNo('cfgExists', CONFIG_PATH);
954
954
  if (!overwrite) {
955
955
  const existingMode = existing.startMode || 'start';
956
- const existingMcpPort = existing.mcpPort || 8787;
956
+ const existingMcpPort = existing.mcpPort || 8781;
957
957
  const existingAuth = existing.authToken || '';
958
958
  const existingMindRoot = existing.mindRoot || resolve(homedir(), 'MindOS', 'mind');
959
959
  console.log(`\n${c.green(t('cfgKept'))} ${c.dim(CONFIG_PATH)}`);
@@ -1072,8 +1072,8 @@ async function main() {
1072
1072
  write('\n');
1073
1073
  stepHeader(3);
1074
1074
  const existingCfg = resumeCfg;
1075
- const defaultWebPort = typeof existingCfg.port === 'number' ? existingCfg.port : 3000;
1076
- const defaultMcpPort = typeof existingCfg.mcpPort === 'number' ? existingCfg.mcpPort : (defaultWebPort === 8787 ? 8788 : 8787);
1075
+ const defaultWebPort = typeof existingCfg.port === 'number' ? existingCfg.port : 3456;
1076
+ const defaultMcpPort = typeof existingCfg.mcpPort === 'number' ? existingCfg.mcpPort : (defaultWebPort === 8781 ? 8782 : 8781);
1077
1077
  let webPort, mcpPort;
1078
1078
  while (true) {
1079
1079
  webPort = await askPort('webPortPrompt', defaultWebPort);
@@ -1178,8 +1178,8 @@ async function main() {
1178
1178
 
1179
1179
  const isResuming = Object.keys(resumeCfg).length > 0;
1180
1180
  const needsRestart = isResuming && (
1181
- config.port !== (resumeCfg.port ?? 3000) ||
1182
- config.mcpPort !== (resumeCfg.mcpPort ?? 8787) ||
1181
+ config.port !== (resumeCfg.port ?? 3456) ||
1182
+ config.mcpPort !== (resumeCfg.mcpPort ?? 8781) ||
1183
1183
  config.mindRoot !== (resumeCfg.mindRoot ?? '') ||
1184
1184
  config.authToken !== (resumeCfg.authToken ?? '') ||
1185
1185
  config.webPassword !== (resumeCfg.webPassword ?? '')
@@ -1212,7 +1212,7 @@ async function main() {
1212
1212
  }
1213
1213
 
1214
1214
  const installDaemon = startMode === 'daemon' || process.argv.includes('--install-daemon');
1215
- finish(mindDir, config.startMode, config.mcpPort, config.authToken, installDaemon, needsRestart, resumeCfg.port ?? 3000);
1215
+ finish(mindDir, config.startMode, config.mcpPort, config.authToken, installDaemon, needsRestart, resumeCfg.port ?? 3456);
1216
1216
  }
1217
1217
 
1218
1218
  function getLocalIP() {
@@ -1224,7 +1224,12 @@ function getLocalIP() {
1224
1224
  return null;
1225
1225
  }
1226
1226
 
1227
- async function finish(mindDir, startMode = 'start', mcpPort = 8787, authToken = '', installDaemon = false, needsRestart = false, oldPort = 3000) {
1227
+ async function finish(mindDir, startMode = 'start', mcpPort = 8781, authToken = '', installDaemon = false, needsRestart = false, oldPort = 3456) {
1228
+ // startMode 'daemon' stored in config is equivalent to installDaemon flag
1229
+ if (startMode === 'daemon') {
1230
+ installDaemon = true;
1231
+ startMode = 'start';
1232
+ }
1228
1233
  if (needsRestart) {
1229
1234
  const isRunning = await isSelfPort(oldPort);
1230
1235
  if (isRunning) {
@@ -37,8 +37,8 @@ Help me upgrade my MindOS installation from the old source-based setup to the ne
37
37
  ```json
38
38
  {
39
39
  "mindRoot": "<value of MIND_ROOT>",
40
- "port": 3000,
41
- "mcpPort": 8787,
40
+ "port": 3456,
41
+ "mcpPort": 8781,
42
42
  "authToken": "<value of AUTH_TOKEN or empty string>",
43
43
  "webPassword": "",
44
44
  "ai": {
@@ -69,7 +69,7 @@ Help me upgrade my MindOS installation from the old source-based setup to the ne
69
69
  ```
70
70
  (First run will build automatically — this may take a minute.)
71
71
 
72
- 7. **Confirm** the app is accessible at http://localhost:3000 and MCP is running at http://localhost:8787/mcp.
72
+ 7. **Confirm** the app is accessible at http://localhost:3456 and MCP is running at http://localhost:8781/mcp.
73
73
 
74
74
  Do not delete the old cloned repository — keep it as a backup. The `app/.env.local` file can also be kept as reference.
75
75
  ```
@@ -109,8 +109,8 @@ Do not delete the old cloned repository — keep it as a backup. The `app/.env.l
109
109
  ```json
110
110
  {
111
111
  "mindRoot": "<MIND_ROOT 的值>",
112
- "port": 3000,
113
- "mcpPort": 8787,
112
+ "port": 3456,
113
+ "mcpPort": 8781,
114
114
  "authToken": "<AUTH_TOKEN 的值,没有则留空字符串>",
115
115
  "webPassword": "",
116
116
  "ai": {
@@ -141,7 +141,7 @@ Do not delete the old cloned repository — keep it as a backup. The `app/.env.l
141
141
  ```
142
142
  (首次运行会自动构建,可能需要一两分钟。)
143
143
 
144
- 7. **确认** http://localhost:3000 可以访问,http://localhost:8787/mcp 的 MCP 服务也在运行。
144
+ 7. **确认** http://localhost:3456 可以访问,http://localhost:8781/mcp 的 MCP 服务也在运行。
145
145
 
146
146
  不要删除旧的克隆仓库,保留作为备份。`app/.env.local` 也可以保留作参考。
147
147
  ```