@geminilight/mindos 0.5.0 → 0.5.2
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/app/app/api/health/route.ts +6 -0
- package/app/app/api/mcp/agents/route.ts +1 -49
- package/app/app/api/mcp/install/route.ts +1 -24
- package/app/app/api/restart/route.ts +22 -0
- package/app/app/api/setup/check-path/route.ts +35 -0
- package/app/app/api/setup/check-port/route.ts +21 -5
- package/app/app/api/setup/ls/route.ts +38 -0
- package/app/app/api/setup/route.ts +49 -0
- package/app/app/layout.tsx +4 -3
- package/app/app/setup/page.tsx +3 -2
- package/app/components/HomeContent.tsx +2 -0
- package/app/components/SettingsModal.tsx +9 -0
- package/app/components/SetupWizard.tsx +747 -422
- package/app/components/WelcomeBanner.tsx +63 -0
- package/app/lib/i18n.ts +40 -2
- package/app/lib/mcp-agents.ts +48 -0
- package/app/lib/settings.ts +1 -1
- package/bin/lib/mcp-agents.js +16 -0
- package/bin/lib/mcp-install.js +2 -11
- package/package.json +1 -1
- package/scripts/setup.js +195 -86
- package/skills/human-insights/SKILL.md +143 -0
- package/skills/mindos/SKILL.md +7 -6
- package/skills/mindos-zh/SKILL.md +7 -7
package/scripts/setup.js
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* Usage: npm run setup OR mindos onboard
|
|
7
7
|
*
|
|
8
8
|
* Steps:
|
|
9
|
-
* 1. Choose knowledge base path → default ~/MindOS
|
|
9
|
+
* 1. Choose knowledge base path → default ~/MindOS/mind (same as GUI)
|
|
10
10
|
* 2. Choose template (en / zh / empty / custom) → copy to knowledge base path
|
|
11
11
|
* 3. Choose ports (web + mcp) — checked for conflicts upfront
|
|
12
12
|
* 4. Auth token (auto-generated or passphrase-seeded)
|
|
@@ -29,6 +29,8 @@ import { pipeline } from 'node:stream/promises';
|
|
|
29
29
|
import { execSync, spawn } from 'node:child_process';
|
|
30
30
|
import { randomBytes, createHash } from 'node:crypto';
|
|
31
31
|
import { createConnection } from 'node:net';
|
|
32
|
+
import http from 'node:http';
|
|
33
|
+
import { MCP_AGENTS } from '../bin/lib/mcp-agents.js';
|
|
32
34
|
|
|
33
35
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
34
36
|
const ROOT = resolve(__dirname, '..');
|
|
@@ -39,21 +41,21 @@ const CONFIG_PATH = resolve(MINDOS_DIR, 'config.json');
|
|
|
39
41
|
|
|
40
42
|
const T = {
|
|
41
43
|
title: { en: '🧠 MindOS Setup', zh: '🧠 MindOS 初始化' },
|
|
42
|
-
langHint: { en: ' ← →
|
|
44
|
+
langHint: { en: ' ← → 切换中文 ↑ ↓ navigate Enter confirm', zh: ' ← → switch to English ↑ ↓ 上下切换 Enter 确认' },
|
|
43
45
|
|
|
44
46
|
// mode selection
|
|
45
|
-
modePrompt: { en: '
|
|
46
|
-
modeOpts: { en: ['
|
|
47
|
+
modePrompt: { en: 'How would you like to set up?', zh: '选择配置方式' },
|
|
48
|
+
modeOpts: { en: ['Continue here in terminal (CLI)', 'Open browser to set up (recommended)'], zh: ['在终端继续配置(CLI)', '打开浏览器配置(推荐)'] },
|
|
47
49
|
modeVals: ['cli', 'gui'],
|
|
48
|
-
guiStarting: { en: '⏳ Starting
|
|
49
|
-
guiReady: { en: (url) => `🌐
|
|
50
|
+
guiStarting: { en: '⏳ Starting MindOS, please wait...', zh: '⏳ 正在启动 MindOS,请稍候...' },
|
|
51
|
+
guiReady: { en: (url) => `🌐 Opening setup in browser: ${url}`, zh: (url) => `🌐 在浏览器中打开配置页面: ${url}` },
|
|
50
52
|
guiOpenFailed: { en: (url) => ` Could not open browser automatically. Open this URL manually:\n ${url}`, zh: (url) => ` 无法自动打开浏览器,请手动访问:\n ${url}` },
|
|
51
53
|
|
|
52
54
|
// step labels
|
|
53
55
|
step: { en: (n, total) => `Step ${n}/${total}`, zh: (n, total) => `步骤 ${n}/${total}` },
|
|
54
56
|
stepTitles: {
|
|
55
|
-
en: ['Knowledge Base', '
|
|
56
|
-
zh: ['知识库', '
|
|
57
|
+
en: ['Knowledge Base', 'AI Provider', 'Ports', 'Auth Token', 'Web Password', 'Start Mode', 'Agent Tools'],
|
|
58
|
+
zh: ['知识库', 'AI 服务商', '端口', 'Auth Token', 'Web 密码', '启动方式', 'Agent 工具'],
|
|
57
59
|
},
|
|
58
60
|
|
|
59
61
|
// path
|
|
@@ -131,6 +133,11 @@ const T = {
|
|
|
131
133
|
mcpInstallDone: { en: (n) => `✔ ${n} agent(s) configured`, zh: (n) => `✔ 已配置 ${n} 个 Agent` },
|
|
132
134
|
mcpSkipped: { en: ' → Skipped. Run `mindos mcp install` anytime to configure agents.', zh: ' → 已跳过。随时运行 `mindos mcp install` 配置 Agent。' },
|
|
133
135
|
|
|
136
|
+
// restart prompts (re-onboard with config changes)
|
|
137
|
+
restartRequired: { en: 'Config changed. Service restart required.', zh: '配置已变更,需要重启服务。' },
|
|
138
|
+
restartNow: { en: 'Restart now?', zh: '立即重启?' },
|
|
139
|
+
changesOnNextStart: { en: 'Changes will take effect on next start.', zh: '变更将在下次启动时生效。' },
|
|
140
|
+
|
|
134
141
|
// next steps (onboard — keep it minimal, details shown on `mindos start`)
|
|
135
142
|
nextSteps: {
|
|
136
143
|
en: (cmd) => [
|
|
@@ -171,7 +178,26 @@ function write(s) { process.stdout.write(s); }
|
|
|
171
178
|
|
|
172
179
|
// ── State ─────────────────────────────────────────────────────────────────────
|
|
173
180
|
|
|
174
|
-
|
|
181
|
+
function detectSystemLang() {
|
|
182
|
+
// Check env vars first (Linux / macOS / WSL)
|
|
183
|
+
const vars = [
|
|
184
|
+
process.env.LANG,
|
|
185
|
+
process.env.LC_ALL,
|
|
186
|
+
process.env.LC_MESSAGES,
|
|
187
|
+
process.env.LANGUAGE,
|
|
188
|
+
].filter(Boolean).join(' ').toLowerCase();
|
|
189
|
+
if (vars.includes('zh')) return 'zh';
|
|
190
|
+
|
|
191
|
+
// Fallback: Intl API (works on Windows where LANG is often unset)
|
|
192
|
+
try {
|
|
193
|
+
const locale = Intl.DateTimeFormat().resolvedOptions().locale;
|
|
194
|
+
if (locale.toLowerCase().startsWith('zh')) return 'zh';
|
|
195
|
+
} catch {}
|
|
196
|
+
|
|
197
|
+
return 'en';
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
let uiLang = detectSystemLang();
|
|
175
201
|
const t = (key) => T[key]?.[uiLang] ?? T[key]?.en ?? '';
|
|
176
202
|
const tf = (key, ...args) => {
|
|
177
203
|
const v = T[key]?.[uiLang] ?? T[key]?.en;
|
|
@@ -180,7 +206,7 @@ const tf = (key, ...args) => {
|
|
|
180
206
|
|
|
181
207
|
// ── Step header ───────────────────────────────────────────────────────────────
|
|
182
208
|
|
|
183
|
-
const TOTAL_STEPS =
|
|
209
|
+
const TOTAL_STEPS = 7;
|
|
184
210
|
function stepHeader(n) {
|
|
185
211
|
const title = T.stepTitles[uiLang][n - 1] ?? T.stepTitles.en[n - 1];
|
|
186
212
|
const stepLabel = tf('step', n, TOTAL_STEPS);
|
|
@@ -397,7 +423,7 @@ function isPortInUse(port) {
|
|
|
397
423
|
return new Promise((resolve) => {
|
|
398
424
|
const sock = createConnection({ port, host: '127.0.0.1' });
|
|
399
425
|
const cleanup = (result) => { sock.destroy(); resolve(result); };
|
|
400
|
-
sock.setTimeout(500, () => cleanup(
|
|
426
|
+
sock.setTimeout(500, () => cleanup(true));
|
|
401
427
|
sock.once('connect', () => cleanup(true));
|
|
402
428
|
sock.once('error', (err) => {
|
|
403
429
|
// ECONNREFUSED = nothing listening → free; other errors = treat as in-use
|
|
@@ -406,6 +432,25 @@ function isPortInUse(port) {
|
|
|
406
432
|
});
|
|
407
433
|
}
|
|
408
434
|
|
|
435
|
+
async function isSelfPort(port) {
|
|
436
|
+
try {
|
|
437
|
+
return await new Promise((resolve) => {
|
|
438
|
+
const req = http.get(`http://127.0.0.1:${port}/api/health`, { timeout: 800 }, (res) => {
|
|
439
|
+
let body = '';
|
|
440
|
+
res.on('data', chunk => { body += chunk; });
|
|
441
|
+
res.on('end', () => {
|
|
442
|
+
try {
|
|
443
|
+
const data = JSON.parse(body);
|
|
444
|
+
resolve(data.service === 'mindos');
|
|
445
|
+
} catch { resolve(false); }
|
|
446
|
+
});
|
|
447
|
+
});
|
|
448
|
+
req.on('error', () => resolve(false));
|
|
449
|
+
req.on('timeout', () => { req.destroy(); resolve(false); });
|
|
450
|
+
});
|
|
451
|
+
} catch { return false; }
|
|
452
|
+
}
|
|
453
|
+
|
|
409
454
|
async function findFreePort(from) {
|
|
410
455
|
for (let p = from; p <= 65535; p++) {
|
|
411
456
|
if (!await isPortInUse(p)) return p;
|
|
@@ -414,7 +459,15 @@ async function findFreePort(from) {
|
|
|
414
459
|
}
|
|
415
460
|
|
|
416
461
|
async function askPort(labelKey, defaultPort) {
|
|
417
|
-
let port =
|
|
462
|
+
let port = defaultPort;
|
|
463
|
+
// If the default port is in use, check if it's our own service (self = ok to keep)
|
|
464
|
+
if (await isPortInUse(port)) {
|
|
465
|
+
if (await isSelfPort(port)) {
|
|
466
|
+
// Already running on this port — keep it as default
|
|
467
|
+
} else {
|
|
468
|
+
port = await findFreePort(port + 1);
|
|
469
|
+
}
|
|
470
|
+
}
|
|
418
471
|
while (true) {
|
|
419
472
|
const input = (await askText(labelKey, String(port))).trim();
|
|
420
473
|
const parsed = parseInt(input, 10);
|
|
@@ -423,6 +476,10 @@ async function askPort(labelKey, defaultPort) {
|
|
|
423
476
|
continue;
|
|
424
477
|
}
|
|
425
478
|
if (await isPortInUse(parsed)) {
|
|
479
|
+
// Check if it's our own service — acceptable to keep
|
|
480
|
+
if (await isSelfPort(parsed)) {
|
|
481
|
+
return parsed;
|
|
482
|
+
}
|
|
426
483
|
const next = await findFreePort(parsed + 1);
|
|
427
484
|
write(c.yellow(tf('portInUse', parsed) + '\n'));
|
|
428
485
|
port = next;
|
|
@@ -518,19 +575,7 @@ async function applyTemplate(tpl, mindDir) {
|
|
|
518
575
|
}
|
|
519
576
|
}
|
|
520
577
|
|
|
521
|
-
//
|
|
522
|
-
|
|
523
|
-
const MCP_AGENTS_SETUP = {
|
|
524
|
-
'claude-code': { name: 'Claude Code', project: '.mcp.json', global: '~/.claude.json', key: 'mcpServers' },
|
|
525
|
-
'claude-desktop': { name: 'Claude Desktop', project: null, global: process.platform === 'darwin' ? '~/Library/Application Support/Claude/claude_desktop_config.json' : '~/.config/Claude/claude_desktop_config.json', key: 'mcpServers' },
|
|
526
|
-
'cursor': { name: 'Cursor', project: '.cursor/mcp.json', global: '~/.cursor/mcp.json', key: 'mcpServers' },
|
|
527
|
-
'windsurf': { name: 'Windsurf', project: null, global: '~/.codeium/windsurf/mcp_config.json', key: 'mcpServers' },
|
|
528
|
-
'cline': { name: 'Cline', project: null, global: process.platform === 'darwin' ? '~/Library/Application Support/Code/User/globalStorage/saoudrizwan.claude-dev/settings/cline_mcp_settings.json' : '~/.config/Code/User/globalStorage/saoudrizwan.claude-dev/settings/cline_mcp_settings.json', key: 'mcpServers' },
|
|
529
|
-
'trae': { name: 'Trae', project: '.trae/mcp.json', global: '~/.trae/mcp.json', key: 'mcpServers' },
|
|
530
|
-
'gemini-cli': { name: 'Gemini CLI', project: '.gemini/settings.json', global: '~/.gemini/settings.json', key: 'mcpServers' },
|
|
531
|
-
'openclaw': { name: 'OpenClaw', project: null, global: '~/.openclaw/mcp.json', key: 'mcpServers' },
|
|
532
|
-
'codebuddy': { name: 'CodeBuddy', project: null, global: '~/.claude-internal/.claude.json', key: 'mcpServers' },
|
|
533
|
-
};
|
|
578
|
+
// MCP_AGENTS imported from bin/lib/mcp-agents.js
|
|
534
579
|
|
|
535
580
|
function expandHomePath(p) {
|
|
536
581
|
return p.startsWith('~/') ? resolve(homedir(), p.slice(2)) : p;
|
|
@@ -538,7 +583,7 @@ function expandHomePath(p) {
|
|
|
538
583
|
|
|
539
584
|
/** Detect if an agent already has mindos configured (for pre-selection). */
|
|
540
585
|
function isAgentInstalled(agentKey) {
|
|
541
|
-
const agent =
|
|
586
|
+
const agent = MCP_AGENTS[agentKey];
|
|
542
587
|
if (!agent) return false;
|
|
543
588
|
for (const cfgPath of [agent.global, agent.project]) {
|
|
544
589
|
if (!cfgPath) continue;
|
|
@@ -558,13 +603,13 @@ function isAgentInstalled(agentKey) {
|
|
|
558
603
|
* because this script uses its own raw-mode helpers).
|
|
559
604
|
*/
|
|
560
605
|
async function runMcpInstallStep(mcpPort, authToken) {
|
|
561
|
-
const keys = Object.keys(
|
|
606
|
+
const keys = Object.keys(MCP_AGENTS);
|
|
562
607
|
|
|
563
608
|
// Build options with installed status shown as hint
|
|
564
609
|
const options = keys.map(k => {
|
|
565
610
|
const installed = isAgentInstalled(k);
|
|
566
611
|
return {
|
|
567
|
-
label:
|
|
612
|
+
label: MCP_AGENTS[k].name,
|
|
568
613
|
hint: installed ? (uiLang === 'zh' ? '已安装' : 'installed') : (uiLang === 'zh' ? '未安装' : 'not installed'),
|
|
569
614
|
value: k,
|
|
570
615
|
preselect: installed,
|
|
@@ -636,7 +681,7 @@ async function runMcpInstallStep(mcpPort, authToken) {
|
|
|
636
681
|
let okCount = 0;
|
|
637
682
|
|
|
638
683
|
for (const agentKey of selected) {
|
|
639
|
-
const agent =
|
|
684
|
+
const agent = MCP_AGENTS[agentKey];
|
|
640
685
|
// prefer global scope; fall back to project
|
|
641
686
|
const cfgPath = agent.global || agent.project;
|
|
642
687
|
if (!cfgPath) {
|
|
@@ -778,7 +823,7 @@ async function main() {
|
|
|
778
823
|
const existingMode = existing.startMode || 'start';
|
|
779
824
|
const existingMcpPort = existing.mcpPort || 8787;
|
|
780
825
|
const existingAuth = existing.authToken || '';
|
|
781
|
-
const existingMindRoot = existing.mindRoot || resolve(
|
|
826
|
+
const existingMindRoot = existing.mindRoot || resolve(homedir(), 'MindOS', 'mind');
|
|
782
827
|
console.log(`\n${c.green(t('cfgKept'))} ${c.dim(CONFIG_PATH)}`);
|
|
783
828
|
write(c.dim(t('cfgKeptNote') + '\n'));
|
|
784
829
|
const installDaemon = process.argv.includes('--install-daemon');
|
|
@@ -790,12 +835,23 @@ async function main() {
|
|
|
790
835
|
// ── Step 1: Knowledge base path ───────────────────────────────────────────
|
|
791
836
|
stepHeader(1);
|
|
792
837
|
|
|
838
|
+
// Resume: read existing config to offer current values as defaults
|
|
839
|
+
let resumeCfg = {};
|
|
840
|
+
try { resumeCfg = JSON.parse(readFileSync(CONFIG_PATH, 'utf-8')); } catch { /* first run */ }
|
|
841
|
+
|
|
793
842
|
const { readdirSync } = await import('node:fs');
|
|
794
843
|
let mindDir;
|
|
795
844
|
|
|
845
|
+
// Default KB path: existing mindRoot if set, otherwise ~/MindOS (same as GUI default)
|
|
846
|
+
const HOME = homedir();
|
|
847
|
+
const kbDefault = resumeCfg.mindRoot || resolve(HOME, 'MindOS', 'mind');
|
|
848
|
+
|
|
796
849
|
while (true) {
|
|
797
|
-
const input = (await askText('pathPrompt',
|
|
798
|
-
|
|
850
|
+
const input = (await askText('pathPrompt', kbDefault)).trim();
|
|
851
|
+
// If absolute path entered, use as-is; if relative (no leading /), resolve from home
|
|
852
|
+
const resolved = input.startsWith('/') || input.startsWith('~/')
|
|
853
|
+
? (input.startsWith('~/') ? resolve(HOME, input.slice(2)) : input)
|
|
854
|
+
: resolve(HOME, input);
|
|
799
855
|
write(tf('pathResolved', resolved) + '\n');
|
|
800
856
|
mindDir = resolved;
|
|
801
857
|
|
|
@@ -817,9 +873,8 @@ async function main() {
|
|
|
817
873
|
if (choice === 'reselect') { write('\n'); continue; }
|
|
818
874
|
break;
|
|
819
875
|
} else {
|
|
820
|
-
// ── Step
|
|
876
|
+
// ── Template selection (part of Step 1) ─────────────────────────────
|
|
821
877
|
write('\n');
|
|
822
|
-
stepHeader(2);
|
|
823
878
|
const tpl = await select('tplPrompt', 'tplOptions', 'tplValues');
|
|
824
879
|
mkdirSync(mindDir, { recursive: true });
|
|
825
880
|
await applyTemplate(tpl, mindDir);
|
|
@@ -827,60 +882,27 @@ async function main() {
|
|
|
827
882
|
}
|
|
828
883
|
}
|
|
829
884
|
|
|
830
|
-
// ── Step
|
|
885
|
+
// ── Step 2: AI Provider + API Key ─────────────────────────────────────────
|
|
831
886
|
write('\n');
|
|
832
|
-
stepHeader(
|
|
833
|
-
let webPort, mcpPort;
|
|
834
|
-
while (true) {
|
|
835
|
-
webPort = await askPort('webPortPrompt', 3000);
|
|
836
|
-
mcpPort = await askPort('mcpPortPrompt', webPort === 8787 ? 8788 : 8787);
|
|
837
|
-
if (webPort !== mcpPort) break;
|
|
838
|
-
write(c.yellow(` ⚠ ${uiLang === 'zh' ? 'Web 端口和 MCP 端口不能相同,请重新选择' : 'Web port and MCP port must be different — please choose again'}\n`));
|
|
839
|
-
}
|
|
840
|
-
|
|
841
|
-
// ── Step 4: Auth token ────────────────────────────────────────────────────
|
|
842
|
-
write('\n');
|
|
843
|
-
stepHeader(4);
|
|
844
|
-
const authSeed = await askText('authPrompt');
|
|
845
|
-
const authToken = generateToken(authSeed);
|
|
846
|
-
console.log(`${c.green(t('tokenGenerated'))}: ${c.cyan(authToken)}`);
|
|
847
|
-
|
|
848
|
-
// ── Step 5: Web UI password ───────────────────────────────────────────────
|
|
849
|
-
write('\n');
|
|
850
|
-
stepHeader(5);
|
|
851
|
-
let webPassword = '';
|
|
852
|
-
while (true) {
|
|
853
|
-
webPassword = await askText('webPassPrompt');
|
|
854
|
-
if (webPassword) break;
|
|
855
|
-
write(c.yellow(t('webPassWarn') + '\n'));
|
|
856
|
-
const confirmed = await askYesNo('webPassSkip');
|
|
857
|
-
if (confirmed) break;
|
|
858
|
-
}
|
|
859
|
-
|
|
860
|
-
// ── Step 6: AI Provider + API Key ─────────────────────────────────────────
|
|
861
|
-
write('\n');
|
|
862
|
-
stepHeader(6);
|
|
887
|
+
stepHeader(2);
|
|
863
888
|
|
|
864
889
|
const provider = await select('providerPrompt', 'providerOpts', 'providerVals');
|
|
865
890
|
const isSkip = provider === 'skip';
|
|
866
891
|
const isAnthropic = provider === 'anthropic';
|
|
867
892
|
|
|
868
|
-
// preserve existing provider configs
|
|
893
|
+
// preserve existing provider configs (use resumeCfg already read at top of main)
|
|
869
894
|
let existingProviders = {
|
|
870
895
|
anthropic: { apiKey: '', model: 'claude-sonnet-4-6' },
|
|
871
896
|
openai: { apiKey: '', model: 'gpt-5.4', baseUrl: '' },
|
|
872
897
|
};
|
|
873
898
|
let existingAiProvider = 'anthropic';
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
}
|
|
882
|
-
if (existing.ai?.provider) existingAiProvider = existing.ai.provider;
|
|
883
|
-
} catch { /* ignore */ }
|
|
899
|
+
if (resumeCfg.ai?.providers) {
|
|
900
|
+
existingProviders = { ...existingProviders, ...resumeCfg.ai.providers };
|
|
901
|
+
} else if (resumeCfg.ai?.anthropicApiKey) {
|
|
902
|
+
existingProviders.anthropic = { apiKey: resumeCfg.ai.anthropicApiKey || '', model: resumeCfg.ai.anthropicModel || 'claude-sonnet-4-6' };
|
|
903
|
+
existingProviders.openai = { apiKey: resumeCfg.ai.openaiApiKey || '', model: resumeCfg.ai.openaiModel || 'gpt-5.4', baseUrl: resumeCfg.ai.openaiBaseUrl || '' };
|
|
904
|
+
}
|
|
905
|
+
if (resumeCfg.ai?.provider) existingAiProvider = resumeCfg.ai.provider;
|
|
884
906
|
|
|
885
907
|
if (isSkip) {
|
|
886
908
|
write(c.dim(t('providerSkip') + '\n'));
|
|
@@ -905,9 +927,69 @@ async function main() {
|
|
|
905
927
|
}
|
|
906
928
|
}
|
|
907
929
|
|
|
908
|
-
// ── Step
|
|
930
|
+
// ── Step 3: Ports ─────────────────────────────────────────────────────────
|
|
909
931
|
write('\n');
|
|
910
|
-
stepHeader(
|
|
932
|
+
stepHeader(3);
|
|
933
|
+
const existingCfg = resumeCfg;
|
|
934
|
+
const defaultWebPort = typeof existingCfg.port === 'number' ? existingCfg.port : 3000;
|
|
935
|
+
const defaultMcpPort = typeof existingCfg.mcpPort === 'number' ? existingCfg.mcpPort : (defaultWebPort === 8787 ? 8788 : 8787);
|
|
936
|
+
let webPort, mcpPort;
|
|
937
|
+
while (true) {
|
|
938
|
+
webPort = await askPort('webPortPrompt', defaultWebPort);
|
|
939
|
+
mcpPort = await askPort('mcpPortPrompt', defaultMcpPort);
|
|
940
|
+
if (webPort !== mcpPort) break;
|
|
941
|
+
write(c.yellow(` ⚠ ${uiLang === 'zh' ? 'Web 端口和 MCP 端口不能相同,请重新选择' : 'Web port and MCP port must be different — please choose again'}\n`));
|
|
942
|
+
}
|
|
943
|
+
|
|
944
|
+
// ── Step 4: Auth token ────────────────────────────────────────────────────
|
|
945
|
+
write('\n');
|
|
946
|
+
stepHeader(4);
|
|
947
|
+
// Resume: if config already has a token, offer it as the default (Enter = keep)
|
|
948
|
+
const existingToken = existingCfg.authToken || '';
|
|
949
|
+
let authToken;
|
|
950
|
+
if (existingToken) {
|
|
951
|
+
const masked = existingToken.length > 8 ? existingToken.slice(0, 8) + '····' : existingToken;
|
|
952
|
+
write(c.dim(` ${uiLang === 'zh' ? '现有 token:' : 'Current token:'} ${c.cyan(masked)}\n`));
|
|
953
|
+
const keepToken = await askYesNoDefault('cfgConfirm');
|
|
954
|
+
if (keepToken) {
|
|
955
|
+
authToken = existingToken;
|
|
956
|
+
console.log(`${c.green(t('tokenGenerated'))}: ${c.cyan(existingToken.slice(0, 8) + '····')}`);
|
|
957
|
+
} else {
|
|
958
|
+
const authSeed = await askText('authPrompt');
|
|
959
|
+
authToken = generateToken(authSeed);
|
|
960
|
+
console.log(`${c.green(t('tokenGenerated'))}: ${c.cyan(authToken)}`);
|
|
961
|
+
}
|
|
962
|
+
} else {
|
|
963
|
+
const authSeed = await askText('authPrompt');
|
|
964
|
+
authToken = generateToken(authSeed);
|
|
965
|
+
console.log(`${c.green(t('tokenGenerated'))}: ${c.cyan(authToken)}`);
|
|
966
|
+
}
|
|
967
|
+
|
|
968
|
+
// ── Step 5: Web UI password ───────────────────────────────────────────────
|
|
969
|
+
write('\n');
|
|
970
|
+
stepHeader(5);
|
|
971
|
+
let webPassword = '';
|
|
972
|
+
const existingPassword = existingCfg.webPassword || '';
|
|
973
|
+
if (existingPassword) {
|
|
974
|
+
write(c.dim(` ${uiLang === 'zh' ? '已设置密码(Enter 保留)' : 'Password is set (Enter to keep)'}\n`));
|
|
975
|
+
const keepPass = await askYesNoDefault('cfgConfirm');
|
|
976
|
+
if (keepPass) {
|
|
977
|
+
webPassword = existingPassword;
|
|
978
|
+
}
|
|
979
|
+
}
|
|
980
|
+
if (!webPassword) {
|
|
981
|
+
while (true) {
|
|
982
|
+
webPassword = await askText('webPassPrompt');
|
|
983
|
+
if (webPassword) break;
|
|
984
|
+
write(c.yellow(t('webPassWarn') + '\n'));
|
|
985
|
+
const confirmed = await askYesNo('webPassSkip');
|
|
986
|
+
if (confirmed) break;
|
|
987
|
+
}
|
|
988
|
+
}
|
|
989
|
+
|
|
990
|
+
// ── Step 6: Start Mode ──────────────────────────────────────────────────
|
|
991
|
+
write('\n');
|
|
992
|
+
stepHeader(6);
|
|
911
993
|
|
|
912
994
|
let startMode = 'start';
|
|
913
995
|
const daemonPlatform = process.platform === 'darwin' || process.platform === 'linux';
|
|
@@ -952,13 +1034,22 @@ async function main() {
|
|
|
952
1034
|
process.exit(0);
|
|
953
1035
|
}
|
|
954
1036
|
|
|
1037
|
+
const isResuming = Object.keys(resumeCfg).length > 0;
|
|
1038
|
+
const needsRestart = isResuming && (
|
|
1039
|
+
config.port !== (resumeCfg.port ?? 3000) ||
|
|
1040
|
+
config.mcpPort !== (resumeCfg.mcpPort ?? 8787) ||
|
|
1041
|
+
config.mindRoot !== (resumeCfg.mindRoot ?? '') ||
|
|
1042
|
+
config.authToken !== (resumeCfg.authToken ?? '') ||
|
|
1043
|
+
config.webPassword !== (resumeCfg.webPassword ?? '')
|
|
1044
|
+
);
|
|
1045
|
+
|
|
955
1046
|
mkdirSync(MINDOS_DIR, { recursive: true });
|
|
956
1047
|
writeFileSync(CONFIG_PATH, JSON.stringify(config, null, 2) + '\n');
|
|
957
1048
|
console.log(`\n${c.green(t('cfgSaved'))}: ${c.dim(CONFIG_PATH)}`);
|
|
958
1049
|
|
|
959
|
-
// ── Step
|
|
1050
|
+
// ── Step 7: MCP Agent Install ──────────────────────────────────────────────
|
|
960
1051
|
write('\n');
|
|
961
|
-
stepHeader(
|
|
1052
|
+
stepHeader(7);
|
|
962
1053
|
write(c.dim(tf('mcpStepHint') + '\n\n'));
|
|
963
1054
|
|
|
964
1055
|
await runMcpInstallStep(mcpPort, authToken);
|
|
@@ -973,7 +1064,7 @@ async function main() {
|
|
|
973
1064
|
}
|
|
974
1065
|
|
|
975
1066
|
const installDaemon = startMode === 'daemon' || process.argv.includes('--install-daemon');
|
|
976
|
-
finish(mindDir, config.startMode, config.mcpPort, config.authToken, installDaemon);
|
|
1067
|
+
finish(mindDir, config.startMode, config.mcpPort, config.authToken, installDaemon, needsRestart, resumeCfg.port ?? 3000);
|
|
977
1068
|
}
|
|
978
1069
|
|
|
979
1070
|
function getLocalIP() {
|
|
@@ -985,7 +1076,25 @@ function getLocalIP() {
|
|
|
985
1076
|
return null;
|
|
986
1077
|
}
|
|
987
1078
|
|
|
988
|
-
async function finish(mindDir, startMode = 'start', mcpPort = 8787, authToken = '', installDaemon = false) {
|
|
1079
|
+
async function finish(mindDir, startMode = 'start', mcpPort = 8787, authToken = '', installDaemon = false, needsRestart = false, oldPort = 3000) {
|
|
1080
|
+
if (needsRestart) {
|
|
1081
|
+
const isRunning = await isSelfPort(oldPort);
|
|
1082
|
+
if (isRunning) {
|
|
1083
|
+
write(c.yellow(t('restartRequired') + '\n'));
|
|
1084
|
+
const doRestart = await askYesNoDefault('restartNow');
|
|
1085
|
+
if (doRestart) {
|
|
1086
|
+
const cliPath = resolve(__dirname, '../bin/cli.js');
|
|
1087
|
+
execSync(`node "${cliPath}" start`, { stdio: 'inherit' });
|
|
1088
|
+
} else {
|
|
1089
|
+
write(c.dim(t('restartManual') + '\n'));
|
|
1090
|
+
}
|
|
1091
|
+
return;
|
|
1092
|
+
} else {
|
|
1093
|
+
write(c.dim(t('changesOnNextStart') + '\n'));
|
|
1094
|
+
// fall through to normal Start now? prompt
|
|
1095
|
+
}
|
|
1096
|
+
}
|
|
1097
|
+
|
|
989
1098
|
const startCmd = installDaemon ? 'mindos start --daemon' : (startMode === 'dev' ? 'mindos dev' : 'mindos start');
|
|
990
1099
|
const lines = T.nextSteps[uiLang](startCmd);
|
|
991
1100
|
console.log('');
|
|
@@ -993,13 +1102,13 @@ async function finish(mindDir, startMode = 'start', mcpPort = 8787, authToken =
|
|
|
993
1102
|
|
|
994
1103
|
const doStart = await askYesNoDefault('startNow');
|
|
995
1104
|
if (doStart) {
|
|
996
|
-
const { execSync } = await import('node:child_process');
|
|
1105
|
+
const { execSync: exec } = await import('node:child_process');
|
|
997
1106
|
const cliPath = resolve(__dirname, '../bin/cli.js');
|
|
998
1107
|
if (installDaemon) {
|
|
999
1108
|
// Install and start as background service — returns immediately
|
|
1000
|
-
|
|
1109
|
+
exec(`node "${cliPath}" start --daemon`, { stdio: 'inherit' });
|
|
1001
1110
|
} else {
|
|
1002
|
-
|
|
1111
|
+
exec(`node "${cliPath}" ${startMode}`, { stdio: 'inherit' });
|
|
1003
1112
|
}
|
|
1004
1113
|
}
|
|
1005
1114
|
}
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: human-insights
|
|
3
|
+
description: "捕捉、记录和提炼人机协作过程中产生的隐性知识,存入项目 human-insights/ 目录。当用户说「记录下这个发现」「这个方法很有效,存一下」「回顾下我们的协作」「总结下 SOP」「这个 AI 总是犯这个错」「把这个变成规则」「整理下 insights」「提炼下规律」「升华到 CLAUDE.md」时使用此 Skill。对话中出现值得沉淀的协作模式、认知升级、反模式时也应触发。"
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Human Insights Skill
|
|
7
|
+
|
|
8
|
+
捕捉人机协作中产生的隐性知识,让它不随对话消失,并定期提炼为可复用的 SOP。
|
|
9
|
+
|
|
10
|
+
核心设计原则:**零摩擦**。记录的成本越低,记录的频率越高,积累的价值越大。所有让用户需要思考"怎么配合"的设计都是反模式。
|
|
11
|
+
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
## 核心理念
|
|
15
|
+
|
|
16
|
+
insights 不是终点,是 CLAUDE.md 的原材料。生命周期:
|
|
17
|
+
|
|
18
|
+
```
|
|
19
|
+
对话碎片 → 捕捉 → human-insights/ → 定期回顾提炼 → CLAUDE.md / Skill
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
已升华进 CLAUDE.md 的 insight 标记为 `[已采纳]`,避免重复处理。
|
|
23
|
+
|
|
24
|
+
---
|
|
25
|
+
|
|
26
|
+
## 三种模式
|
|
27
|
+
|
|
28
|
+
### 模式一:捕捉(用户主动触发)
|
|
29
|
+
|
|
30
|
+
用户说"记录下这个"、"这个很有用"时触发。
|
|
31
|
+
|
|
32
|
+
1. 理解内容 — 读懂要记录什么,必要时追问一句
|
|
33
|
+
2. **AI 判断分类**,用户不需要选(见下方分类体系)
|
|
34
|
+
3. 检查目标文件,避免重复记录
|
|
35
|
+
4. 写入,自动填写时间,来源一句话
|
|
36
|
+
5. 告知用户记录到了哪里
|
|
37
|
+
|
|
38
|
+
### 模式二:主动提议(AI 识别信号)
|
|
39
|
+
|
|
40
|
+
对话中出现以下信号时,AI 在回答完后顺带问一句"这个发现值得记进 human-insights,要我记一下吗?"
|
|
41
|
+
|
|
42
|
+
**值得提议的信号(需同时满足:有新意 + 可复用):**
|
|
43
|
+
- 用户明确纠正了 AI 的错误认知或行为模式
|
|
44
|
+
- 用户发现某个做法比之前的方式明显更高效,且说清楚了为什么
|
|
45
|
+
- 对话中归纳出了一条可以跨场景复用的规律或原则
|
|
46
|
+
|
|
47
|
+
**不需要提议的情况:**
|
|
48
|
+
- 只是某个任务执行得顺利(不代表有可复用的模式)
|
|
49
|
+
- 用户已经在说"记一下"(模式一已覆盖)
|
|
50
|
+
- 同一对话中已经提议过一次(避免反复打断)
|
|
51
|
+
|
|
52
|
+
用户说"好"/"记一下"即执行,说"不用"则跳过。不打断主流程,轻量提议。
|
|
53
|
+
|
|
54
|
+
### 模式三:回顾提炼(定期整理)
|
|
55
|
+
|
|
56
|
+
用户说"回顾下协作"、"总结 SOP"、"升华到 CLAUDE.md" 时触发。
|
|
57
|
+
|
|
58
|
+
1. 读取 `human-insights/` 下所有文件,统计各类别条目数和"待提炼"数量
|
|
59
|
+
2. 识别模式 — 找出:① 反复出现的主题 ② 相互印证的发现 ③ 已有足够证据支撑的规律
|
|
60
|
+
3. 起草提炼结果,每条产出明确其形态:
|
|
61
|
+
- **规则**:适合写进 CLAUDE.md 的行为约束("做 X 之前先做 Y")
|
|
62
|
+
- **流程**:可复用的步骤序列(适合写成 Skill 或 CLAUDE.md 流程节)
|
|
63
|
+
- **Prompt 模板**:可直接复用的指令结构
|
|
64
|
+
4. 展示给用户确认,用户可修改或拒绝某条
|
|
65
|
+
5. 写入 CLAUDE.md 对应章节,原 insight 标记 `[已采纳 → CLAUDE.md #章节名]`
|
|
66
|
+
6. 更新 `README.md` 各文件摘要
|
|
67
|
+
|
|
68
|
+
---
|
|
69
|
+
|
|
70
|
+
## 分类体系(AI 判断,用户无感)
|
|
71
|
+
|
|
72
|
+
| 类别 | 文件 | 记录什么 |
|
|
73
|
+
|------|------|---------|
|
|
74
|
+
| Prompt 模式 | `prompt-patterns.md` | 有效的提问方式、指令结构、上下文给法 |
|
|
75
|
+
| 工作流发现 | `workflow.md` | 有效的协作流程、任务拆分方式、节奏把握 |
|
|
76
|
+
| AI 行为规律 | `ai-behavior.md` | AI 在特定场景下的规律性表现、容易犯的错 |
|
|
77
|
+
| 认知升级 | `mindset.md` | 对某个概念/方法的新理解,之前认知被纠正 |
|
|
78
|
+
| 反模式 | `anti-patterns.md` | 踩过的坑、低效做法、应该避免的模式 |
|
|
79
|
+
|
|
80
|
+
一条 insight 可以属于多个类别时,以最主要的类别为准写入一个文件,不重复写入。
|
|
81
|
+
|
|
82
|
+
---
|
|
83
|
+
|
|
84
|
+
## 记录格式
|
|
85
|
+
|
|
86
|
+
轻量为主,自动填时间,来源一句话够了:
|
|
87
|
+
|
|
88
|
+
```markdown
|
|
89
|
+
## <标题>
|
|
90
|
+
|
|
91
|
+
**时间:** YYYY-MM-DD **来源:** <一句话场景,如"MindOS CLAUDE.md 整理中">
|
|
92
|
+
|
|
93
|
+
<发现了什么、为什么有价值、怎么用。1-3 句话。>
|
|
94
|
+
|
|
95
|
+
**状态:** 待提炼 / [已采纳 → CLAUDE.md #章节名]
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
---
|
|
99
|
+
|
|
100
|
+
## 目录结构
|
|
101
|
+
|
|
102
|
+
```
|
|
103
|
+
human-insights/
|
|
104
|
+
├── README.md # 索引 + 各文件一句话摘要(只列已创建的文件)
|
|
105
|
+
├── prompt-patterns.md
|
|
106
|
+
├── workflow.md
|
|
107
|
+
├── ai-behavior.md
|
|
108
|
+
├── mindset.md
|
|
109
|
+
└── anti-patterns.md
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
首次使用时只创建 `README.md` 和当前需要写入的文件,其余按需创建。
|
|
113
|
+
|
|
114
|
+
---
|
|
115
|
+
|
|
116
|
+
## 初始化
|
|
117
|
+
|
|
118
|
+
`human-insights/` 不存在时自动创建目录和 `README.md`:
|
|
119
|
+
|
|
120
|
+
```markdown
|
|
121
|
+
# Human Insights
|
|
122
|
+
|
|
123
|
+
记录人机协作过程中产生的隐性知识,定期提炼为可复用的 SOP。
|
|
124
|
+
|
|
125
|
+
## 文件索引
|
|
126
|
+
|
|
127
|
+
(按需添加,创建新文件时同步更新此表)
|
|
128
|
+
|
|
129
|
+
| 文件 | 摘要 |
|
|
130
|
+
|------|------|
|
|
131
|
+
|
|
132
|
+
## 升华路径
|
|
133
|
+
|
|
134
|
+
积累 → 回顾提炼 → 写入 CLAUDE.md → 标记 [已采纳]
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
---
|
|
138
|
+
|
|
139
|
+
## 边界说明
|
|
140
|
+
|
|
141
|
+
- vs `wiki/80-known-pitfalls.md`:pitfalls 记客观技术坑(别人也会踩),insights 记主观协作发现(你个人的认知)
|
|
142
|
+
- vs `wiki/06-conventions.md`:conventions 是已确定的规范,insights 是还在验证中的发现
|
|
143
|
+
- vs `CLAUDE.md`:CLAUDE.md 是已固化的规则,insights 是原材料
|
package/skills/mindos/SKILL.md
CHANGED
|
@@ -1,14 +1,15 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: mindos
|
|
3
3
|
description: >
|
|
4
|
-
MindOS knowledge base operation guide for agent tasks on
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
Trigger on requests like "update notes", "search knowledge base", "organize files", "execute SOP",
|
|
4
|
+
MindOS knowledge base operation guide, only for agent tasks on files inside the MindOS knowledge base (mindRoot path).
|
|
5
|
+
Trigger only when the target files are inside the MindOS knowledge base directory.
|
|
6
|
+
Typical requests: "update notes", "search knowledge base", "organize files", "execute SOP",
|
|
8
7
|
"review with our standards", "handoff to another agent", "sync decisions", "append CSV",
|
|
9
8
|
"retrospective", "distill this conversation", "capture key learnings", "update related docs adaptively",
|
|
10
|
-
"route this to the right files", "update everything related", "sync this across my knowledge base"
|
|
11
|
-
|
|
9
|
+
"route this to the right files", "update everything related", "sync this across my knowledge base".
|
|
10
|
+
Do NOT trigger when: the target is a local code repository file (e.g. /code/xxx/wiki/*.md),
|
|
11
|
+
the user provides an absolute path that is not under MindOS mindRoot,
|
|
12
|
+
or the task involves modifying project source code or project documentation.
|
|
12
13
|
---
|
|
13
14
|
|
|
14
15
|
# MindOS Knowledge Base Operation Guide
|