@geminilight/mindos 0.2.0 → 0.2.1
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 +3 -3
- package/README_zh.md +38 -5
- package/app/README.md +1 -1
- package/app/app/api/init/route.ts +56 -0
- package/app/app/layout.tsx +10 -1
- package/app/app/register-sw.tsx +15 -0
- package/app/components/HomeContent.tsx +8 -2
- package/app/components/OnboardingView.tsx +161 -0
- package/app/components/SettingsModal.tsx +7 -1
- package/app/components/Sidebar.tsx +28 -4
- package/app/components/SyncStatusBar.tsx +273 -0
- package/app/components/renderers/AgentInspectorRenderer.tsx +8 -5
- package/app/components/settings/SyncTab.tsx +113 -21
- package/app/lib/agent/log.ts +44 -0
- package/app/lib/agent/tools.ts +39 -18
- package/app/lib/i18n.ts +78 -0
- package/app/lib/renderers/index.ts +13 -0
- package/app/lib/settings.ts +1 -1
- package/app/public/icons/icon-192.png +0 -0
- package/app/public/icons/icon-512.png +0 -0
- package/app/public/manifest.json +26 -0
- package/app/public/sw.js +66 -0
- package/bin/cli.js +21 -0
- package/bin/lib/mcp-install.js +225 -70
- package/bin/lib/startup.js +24 -1
- package/mcp/src/index.ts +37 -10
- package/package.json +1 -1
- package/scripts/setup.js +12 -1
- package/templates/README.md +1 -1
package/bin/lib/mcp-install.js
CHANGED
|
@@ -16,6 +16,141 @@ export const MCP_AGENTS = {
|
|
|
16
16
|
'codebuddy': { name: 'CodeBuddy', project: null, global: '~/.claude-internal/.claude.json', key: 'mcpServers' },
|
|
17
17
|
};
|
|
18
18
|
|
|
19
|
+
// ─── Interactive select (arrow keys) ──────────────────────────────────────────
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Single select with arrow keys.
|
|
23
|
+
* ↑/↓ to move, Enter to confirm.
|
|
24
|
+
*/
|
|
25
|
+
async function interactiveSelect(title, options) {
|
|
26
|
+
return new Promise((resolve) => {
|
|
27
|
+
let cursor = 0;
|
|
28
|
+
const { stdin, stdout } = process;
|
|
29
|
+
|
|
30
|
+
function render() {
|
|
31
|
+
// Move up to clear previous render (except first time)
|
|
32
|
+
stdout.write(`\x1b[${options.length + 1}A\x1b[J`);
|
|
33
|
+
draw();
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function draw() {
|
|
37
|
+
stdout.write(`${bold(title)}\n`);
|
|
38
|
+
for (let i = 0; i < options.length; i++) {
|
|
39
|
+
const o = options[i];
|
|
40
|
+
const prefix = i === cursor ? cyan('❯') : ' ';
|
|
41
|
+
const label = i === cursor ? cyan(o.label) : o.label;
|
|
42
|
+
const hint = o.hint ? ` ${dim(`(${o.hint})`)}` : '';
|
|
43
|
+
stdout.write(` ${prefix} ${label}${hint}\n`);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// Initial draw
|
|
48
|
+
stdout.write('\n');
|
|
49
|
+
draw();
|
|
50
|
+
|
|
51
|
+
stdin.setRawMode(true);
|
|
52
|
+
stdin.resume();
|
|
53
|
+
stdin.setEncoding('utf-8');
|
|
54
|
+
|
|
55
|
+
function onKey(key) {
|
|
56
|
+
if (key === '\x1b[A') { // up
|
|
57
|
+
cursor = (cursor - 1 + options.length) % options.length;
|
|
58
|
+
render();
|
|
59
|
+
} else if (key === '\x1b[B') { // down
|
|
60
|
+
cursor = (cursor + 1) % options.length;
|
|
61
|
+
render();
|
|
62
|
+
} else if (key === '\r' || key === '\n') { // enter
|
|
63
|
+
cleanup();
|
|
64
|
+
resolve(options[cursor]);
|
|
65
|
+
} else if (key === '\x03') { // ctrl+c
|
|
66
|
+
cleanup();
|
|
67
|
+
process.exit(0);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function cleanup() {
|
|
72
|
+
stdin.removeListener('data', onKey);
|
|
73
|
+
stdin.setRawMode(false);
|
|
74
|
+
stdin.pause();
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
stdin.on('data', onKey);
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Multi select with arrow keys.
|
|
83
|
+
* ↑/↓ to move, Space to toggle, A to toggle all, Enter to confirm.
|
|
84
|
+
*/
|
|
85
|
+
async function interactiveMultiSelect(title, options) {
|
|
86
|
+
return new Promise((resolve) => {
|
|
87
|
+
let cursor = 0;
|
|
88
|
+
const selected = new Set();
|
|
89
|
+
const { stdin, stdout } = process;
|
|
90
|
+
|
|
91
|
+
function render() {
|
|
92
|
+
stdout.write(`\x1b[${options.length + 2}A\x1b[J`);
|
|
93
|
+
draw();
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function draw() {
|
|
97
|
+
stdout.write(`${bold(title)} ${dim('(↑↓ move, Space select, A all, Enter confirm)')}\n`);
|
|
98
|
+
for (let i = 0; i < options.length; i++) {
|
|
99
|
+
const o = options[i];
|
|
100
|
+
const check = selected.has(i) ? green('✔') : dim('○');
|
|
101
|
+
const pointer = i === cursor ? cyan('❯') : ' ';
|
|
102
|
+
const label = i === cursor ? (selected.has(i) ? green(o.label) : cyan(o.label)) : (selected.has(i) ? green(o.label) : o.label);
|
|
103
|
+
const hint = o.hint ? ` ${dim(`(${o.hint})`)}` : '';
|
|
104
|
+
stdout.write(` ${pointer} ${check} ${label}${hint}\n`);
|
|
105
|
+
}
|
|
106
|
+
const count = selected.size;
|
|
107
|
+
stdout.write(dim(` ${count} selected\n`));
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
stdout.write('\n');
|
|
111
|
+
draw();
|
|
112
|
+
|
|
113
|
+
stdin.setRawMode(true);
|
|
114
|
+
stdin.resume();
|
|
115
|
+
stdin.setEncoding('utf-8');
|
|
116
|
+
|
|
117
|
+
function onKey(key) {
|
|
118
|
+
if (key === '\x1b[A') { // up
|
|
119
|
+
cursor = (cursor - 1 + options.length) % options.length;
|
|
120
|
+
render();
|
|
121
|
+
} else if (key === '\x1b[B') { // down
|
|
122
|
+
cursor = (cursor + 1) % options.length;
|
|
123
|
+
render();
|
|
124
|
+
} else if (key === ' ') { // space
|
|
125
|
+
if (selected.has(cursor)) selected.delete(cursor);
|
|
126
|
+
else selected.add(cursor);
|
|
127
|
+
render();
|
|
128
|
+
} else if (key === 'a' || key === 'A') { // toggle all
|
|
129
|
+
if (selected.size === options.length) selected.clear();
|
|
130
|
+
else options.forEach((_, i) => selected.add(i));
|
|
131
|
+
render();
|
|
132
|
+
} else if (key === '\r' || key === '\n') { // enter
|
|
133
|
+
cleanup();
|
|
134
|
+
const result = [...selected].sort().map(i => options[i]);
|
|
135
|
+
resolve(result);
|
|
136
|
+
} else if (key === '\x03') { // ctrl+c
|
|
137
|
+
cleanup();
|
|
138
|
+
process.exit(0);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function cleanup() {
|
|
143
|
+
stdin.removeListener('data', onKey);
|
|
144
|
+
stdin.setRawMode(false);
|
|
145
|
+
stdin.pause();
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
stdin.on('data', onKey);
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// ─── Main install flow ────────────────────────────────────────────────────────
|
|
153
|
+
|
|
19
154
|
export async function mcpInstall() {
|
|
20
155
|
// Support both `mindos mcp install [agent] [flags]` and `mindos mcp [flags]`
|
|
21
156
|
const sub = process.argv[3];
|
|
@@ -39,72 +174,65 @@ export async function mcpInstall() {
|
|
|
39
174
|
const readline = await import('node:readline');
|
|
40
175
|
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
41
176
|
const ask = (q) => new Promise(r => rl.question(q, r));
|
|
42
|
-
const choose = async (prompt, options, { defaultIdx = 0, forcePrompt = false } = {}) => {
|
|
43
|
-
if (hasYesFlag && !forcePrompt) return options[defaultIdx];
|
|
44
|
-
console.log(`\n${bold(prompt)}\n`);
|
|
45
|
-
options.forEach((o, i) => console.log(` ${dim(`${i + 1}.`)} ${o.label} ${o.hint ? dim(`(${o.hint})`) : ''}`));
|
|
46
|
-
const ans = await ask(`\n${bold(`Enter number`)} ${dim(`[${defaultIdx + 1}]:`)} `);
|
|
47
|
-
const idx = ans.trim() === '' ? defaultIdx : parseInt(ans.trim(), 10) - 1;
|
|
48
|
-
return options[idx >= 0 && idx < options.length ? idx : defaultIdx];
|
|
49
|
-
};
|
|
50
177
|
|
|
51
178
|
console.log(`\n${bold('🔌 MindOS MCP Install')}\n`);
|
|
52
179
|
|
|
53
|
-
// ── 1. agent
|
|
54
|
-
let
|
|
55
|
-
if (!agentKey) {
|
|
56
|
-
const keys = Object.keys(MCP_AGENTS);
|
|
57
|
-
const picked = await choose('Which Agent would you like to configure?',
|
|
58
|
-
keys.map(k => ({ label: MCP_AGENTS[k].name, hint: k, value: k })), { forcePrompt: true });
|
|
59
|
-
agentKey = picked.value;
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
const agent = MCP_AGENTS[agentKey];
|
|
63
|
-
if (!agent) {
|
|
64
|
-
rl.close();
|
|
65
|
-
console.error(red(`\nUnknown agent: ${agentKey}`));
|
|
66
|
-
console.error(dim(`Supported: ${Object.keys(MCP_AGENTS).join(', ')}`));
|
|
67
|
-
process.exit(1);
|
|
68
|
-
}
|
|
180
|
+
// ── 1. agent(s) ──────────────────────────────────────────────────────────────
|
|
181
|
+
let agentKeys = agentArg ? [agentArg] : [];
|
|
69
182
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
{ label: 'Project', hint: agent.project, value: 'project' },
|
|
76
|
-
{ label: 'Global', hint: agent.global, value: 'global' },
|
|
77
|
-
]);
|
|
78
|
-
isGlobal = picked.value === 'global';
|
|
183
|
+
if (agentKeys.length === 0) {
|
|
184
|
+
const keys = Object.keys(MCP_AGENTS);
|
|
185
|
+
if (hasYesFlag) {
|
|
186
|
+
// -y mode: install all
|
|
187
|
+
agentKeys = keys;
|
|
79
188
|
} else {
|
|
80
|
-
|
|
189
|
+
rl.close(); // close readline so raw mode works
|
|
190
|
+
const picked = await interactiveMultiSelect(
|
|
191
|
+
'Which Agents to configure?',
|
|
192
|
+
keys.map(k => ({ label: MCP_AGENTS[k].name, hint: k, value: k })),
|
|
193
|
+
);
|
|
194
|
+
if (picked.length === 0) {
|
|
195
|
+
console.log(dim('\nNo agents selected. Exiting.\n'));
|
|
196
|
+
process.exit(0);
|
|
197
|
+
}
|
|
198
|
+
agentKeys = picked.map(p => p.value);
|
|
81
199
|
}
|
|
82
200
|
}
|
|
83
201
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
202
|
+
// Validate all keys first
|
|
203
|
+
for (const key of agentKeys) {
|
|
204
|
+
if (!MCP_AGENTS[key]) {
|
|
205
|
+
console.error(red(`\nUnknown agent: ${key}`));
|
|
206
|
+
console.error(dim(`Supported: ${Object.keys(MCP_AGENTS).join(', ')}`));
|
|
207
|
+
process.exit(1);
|
|
208
|
+
}
|
|
89
209
|
}
|
|
90
210
|
|
|
91
|
-
// ──
|
|
211
|
+
// ── 2. shared transport (ask once, apply to all) ───────────────────────────
|
|
92
212
|
let transport = transportArg;
|
|
93
213
|
if (!transport) {
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
214
|
+
if (hasYesFlag) {
|
|
215
|
+
transport = 'stdio';
|
|
216
|
+
} else {
|
|
217
|
+
const picked = await interactiveSelect('Transport type?', [
|
|
218
|
+
{ label: 'stdio', hint: 'local, no server process needed (recommended)' },
|
|
219
|
+
{ label: 'http', hint: 'URL-based, use when server is running separately or remotely' },
|
|
220
|
+
]);
|
|
221
|
+
transport = picked.label;
|
|
222
|
+
}
|
|
99
223
|
}
|
|
100
224
|
|
|
101
|
-
// ──
|
|
225
|
+
// ── 3. url + token (only for http) ─────────────────────────────────────────
|
|
102
226
|
let url = urlArg;
|
|
103
227
|
let token = tokenArg;
|
|
104
228
|
|
|
105
229
|
if (transport === 'http') {
|
|
230
|
+
// Re-open readline for text input
|
|
231
|
+
const rl2 = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
232
|
+
const ask2 = (q) => new Promise(r => rl2.question(q, r));
|
|
233
|
+
|
|
106
234
|
if (!url) {
|
|
107
|
-
url = hasYesFlag ? 'http://localhost:8787/mcp' : (await
|
|
235
|
+
url = hasYesFlag ? 'http://localhost:8787/mcp' : (await ask2(`${bold('MCP URL')} ${dim('[http://localhost:8787/mcp]:')} `)).trim() || 'http://localhost:8787/mcp';
|
|
108
236
|
}
|
|
109
237
|
|
|
110
238
|
if (!token) {
|
|
@@ -112,45 +240,72 @@ export async function mcpInstall() {
|
|
|
112
240
|
if (token) {
|
|
113
241
|
console.log(dim(` Using auth token from ~/.mindos/config.json`));
|
|
114
242
|
} else if (!hasYesFlag) {
|
|
115
|
-
token = (await
|
|
243
|
+
token = (await ask2(`${bold('Auth token')} ${dim('(leave blank to skip):')} `)).trim();
|
|
116
244
|
} else {
|
|
117
245
|
console.log(yellow(` Warning: no auth token found in ~/.mindos/config.json — config will have no auth.`));
|
|
118
246
|
console.log(dim(` Run \`mindos onboard\` to set one, or pass --token <token>.`));
|
|
119
247
|
}
|
|
120
248
|
}
|
|
121
|
-
}
|
|
122
249
|
|
|
123
|
-
|
|
250
|
+
rl2.close();
|
|
251
|
+
}
|
|
124
252
|
|
|
125
|
-
// ── build entry
|
|
253
|
+
// ── 4. build entry ─────────────────────────────────────────────────────────
|
|
126
254
|
const entry = transport === 'stdio'
|
|
127
255
|
? { type: 'stdio', command: 'mindos', args: ['mcp'], env: { MCP_TRANSPORT: 'stdio' } }
|
|
128
256
|
: token
|
|
129
257
|
? { url, headers: { Authorization: `Bearer ${token}` } }
|
|
130
258
|
: { url };
|
|
131
259
|
|
|
132
|
-
// ──
|
|
133
|
-
const
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
260
|
+
// ── 5. install for each selected agent ─────────────────────────────────────
|
|
261
|
+
for (const agentKey of agentKeys) {
|
|
262
|
+
const agent = MCP_AGENTS[agentKey];
|
|
263
|
+
|
|
264
|
+
// scope
|
|
265
|
+
let isGlobal = hasGlobalFlag;
|
|
266
|
+
if (!hasGlobalFlag) {
|
|
267
|
+
if (agent.project && agent.global) {
|
|
268
|
+
if (hasYesFlag) {
|
|
269
|
+
isGlobal = false; // default to project
|
|
270
|
+
} else {
|
|
271
|
+
const picked = await interactiveSelect(`[${agent.name}] Install scope?`, [
|
|
272
|
+
{ label: 'Project', hint: agent.project, value: 'project' },
|
|
273
|
+
{ label: 'Global', hint: agent.global, value: 'global' },
|
|
274
|
+
]);
|
|
275
|
+
isGlobal = picked.value === 'global';
|
|
276
|
+
}
|
|
277
|
+
} else {
|
|
278
|
+
isGlobal = !agent.project;
|
|
279
|
+
}
|
|
139
280
|
}
|
|
140
|
-
}
|
|
141
281
|
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
282
|
+
const configPath = isGlobal ? agent.global : agent.project;
|
|
283
|
+
if (!configPath) {
|
|
284
|
+
console.error(red(` ${agent.name} does not support ${isGlobal ? 'global' : 'project'} scope — skipping.`));
|
|
285
|
+
continue;
|
|
286
|
+
}
|
|
145
287
|
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
288
|
+
// read + merge
|
|
289
|
+
const absPath = expandHome(configPath);
|
|
290
|
+
let config = {};
|
|
291
|
+
if (existsSync(absPath)) {
|
|
292
|
+
try { config = JSON.parse(readFileSync(absPath, 'utf-8')); } catch {
|
|
293
|
+
console.error(red(` Failed to parse existing config: ${absPath} — skipping.`));
|
|
294
|
+
continue;
|
|
295
|
+
}
|
|
296
|
+
}
|
|
149
297
|
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
298
|
+
if (!config[agent.key]) config[agent.key] = {};
|
|
299
|
+
const existed = !!config[agent.key].mindos;
|
|
300
|
+
config[agent.key].mindos = entry;
|
|
301
|
+
|
|
302
|
+
// write
|
|
303
|
+
const dir = resolve(absPath, '..');
|
|
304
|
+
if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
|
|
305
|
+
writeFileSync(absPath, JSON.stringify(config, null, 2) + '\n', 'utf-8');
|
|
306
|
+
|
|
307
|
+
console.log(`${green('✔')} ${existed ? 'Updated' : 'Installed'} MindOS MCP for ${bold(agent.name)} ${dim(`→ ${absPath}`)}`);
|
|
308
|
+
}
|
|
153
309
|
|
|
154
|
-
console.log(`\n${green('
|
|
155
|
-
console.log(dim(` Config: ${absPath}\n`));
|
|
310
|
+
console.log(`\n${green('Done!')} ${agentKeys.length} agent(s) configured.\n`);
|
|
156
311
|
}
|
package/bin/lib/startup.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { readFileSync } from 'node:fs';
|
|
2
2
|
import { networkInterfaces } from 'node:os';
|
|
3
3
|
import { CONFIG_PATH } from './constants.js';
|
|
4
|
-
import { bold, dim, cyan, green } from './colors.js';
|
|
4
|
+
import { bold, dim, cyan, green, yellow } from './colors.js';
|
|
5
|
+
import { getSyncStatus } from './sync.js';
|
|
5
6
|
|
|
6
7
|
export function getLocalIP() {
|
|
7
8
|
try {
|
|
@@ -47,5 +48,27 @@ export function printStartupInfo(webPort, mcpPort) {
|
|
|
47
48
|
}
|
|
48
49
|
console.log(dim('\n Install Skills (optional):'));
|
|
49
50
|
console.log(dim(' npx skills add https://github.com/GeminiLight/MindOS --skill mindos -g -y'));
|
|
51
|
+
|
|
52
|
+
// Sync status
|
|
53
|
+
const mindRoot = config.mindRoot;
|
|
54
|
+
if (mindRoot) {
|
|
55
|
+
try {
|
|
56
|
+
const syncStatus = getSyncStatus(mindRoot);
|
|
57
|
+
if (syncStatus.enabled) {
|
|
58
|
+
if (syncStatus.lastError) {
|
|
59
|
+
console.log(`\n ${yellow('!')} Sync ${yellow('error')}: ${syncStatus.lastError}`);
|
|
60
|
+
} else if (syncStatus.conflicts && syncStatus.conflicts.length > 0) {
|
|
61
|
+
console.log(`\n ${yellow('!')} Sync ${yellow(`${syncStatus.conflicts.length} conflict(s)`)} ${dim('run `mindos sync conflicts` to view')}`);
|
|
62
|
+
} else {
|
|
63
|
+
const unpushed = parseInt(syncStatus.unpushed || '0', 10);
|
|
64
|
+
const extra = unpushed > 0 ? ` ${dim(`(${unpushed} unpushed)`)}` : '';
|
|
65
|
+
console.log(`\n ${green('●')} Sync ${green('enabled')} ${dim(syncStatus.remote || 'origin')}${extra}`);
|
|
66
|
+
}
|
|
67
|
+
} else {
|
|
68
|
+
console.log(`\n ${dim('○')} Sync ${dim('not configured')} ${dim('run `mindos sync init` to set up')}`);
|
|
69
|
+
}
|
|
70
|
+
} catch { /* sync check is best-effort */ }
|
|
71
|
+
}
|
|
72
|
+
|
|
50
73
|
console.log(`${'─'.repeat(53)}\n`);
|
|
51
74
|
}
|
package/mcp/src/index.ts
CHANGED
|
@@ -75,6 +75,23 @@ function truncate(text: string, limit = CHARACTER_LIMIT): string {
|
|
|
75
75
|
return text.slice(0, limit) + `\n\n[... truncated at ${limit} characters. Use offset/limit params for paginated access.]`;
|
|
76
76
|
}
|
|
77
77
|
|
|
78
|
+
// ─── Agent operation logging ────────────────────────────────────────────────
|
|
79
|
+
|
|
80
|
+
async function logOp(tool: string, params: Record<string, unknown>, result: 'ok' | 'error', message: string) {
|
|
81
|
+
try {
|
|
82
|
+
const entry = { ts: new Date().toISOString(), tool, params, result, message: message.slice(0, 200) };
|
|
83
|
+
const line = JSON.stringify(entry) + '\n';
|
|
84
|
+
// Append to .agent-log.json via the app API
|
|
85
|
+
await fetch(new URL("/api/file", BASE_URL).toString(), {
|
|
86
|
+
method: "POST",
|
|
87
|
+
headers: headers(),
|
|
88
|
+
body: JSON.stringify({ op: "append_to_file", path: ".agent-log.json", content: line }),
|
|
89
|
+
}).catch(() => {});
|
|
90
|
+
} catch {
|
|
91
|
+
// Logging should never break tool execution
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
78
95
|
// ─── MCP Server ──────────────────────────────────────────────────────────────
|
|
79
96
|
|
|
80
97
|
const server = new McpServer({ name: "mindos-mcp-server", version: "1.0.0" });
|
|
@@ -91,8 +108,10 @@ server.registerTool("mindos_list_files", {
|
|
|
91
108
|
}, async ({ response_format }) => {
|
|
92
109
|
try {
|
|
93
110
|
const json = await get("/api/files", { format: response_format });
|
|
94
|
-
|
|
95
|
-
|
|
111
|
+
const result = typeof json.tree === "string" ? json.tree : JSON.stringify(json.tree ?? json, null, 2);
|
|
112
|
+
logOp("mindos_list_files", { response_format }, "ok", `${result.length} chars`);
|
|
113
|
+
return ok(result);
|
|
114
|
+
} catch (e) { logOp("mindos_list_files", { response_format }, "error", String(e)); return error(String(e)); }
|
|
96
115
|
});
|
|
97
116
|
|
|
98
117
|
// ── mindos_read_file ────────────────────────────────────────────────────────
|
|
@@ -115,8 +134,9 @@ server.registerTool("mindos_read_file", {
|
|
|
115
134
|
const header = hasMore
|
|
116
135
|
? `[Showing characters ${offset}–${offset + slice.length} of ${content.length}. Use offset=${offset + limit} for next page.]\n\n`
|
|
117
136
|
: offset > 0 ? `[Showing characters ${offset}–${offset + slice.length} of ${content.length}]\n\n` : "";
|
|
137
|
+
logOp("mindos_read_file", { path }, "ok", `${content.length} chars`);
|
|
118
138
|
return ok(header + slice);
|
|
119
|
-
} catch (e) { return error(String(e)); }
|
|
139
|
+
} catch (e) { logOp("mindos_read_file", { path }, "error", String(e)); return error(String(e)); }
|
|
120
140
|
});
|
|
121
141
|
|
|
122
142
|
// ── mindos_write_file ───────────────────────────────────────────────────────
|
|
@@ -131,8 +151,9 @@ server.registerTool("mindos_write_file", {
|
|
|
131
151
|
}, async ({ path, content }) => {
|
|
132
152
|
try {
|
|
133
153
|
await post("/api/file", { op: "save_file", path, content });
|
|
154
|
+
logOp("mindos_write_file", { path }, "ok", `Wrote ${content.length} chars`);
|
|
134
155
|
return ok(`Successfully wrote ${content.length} characters to "${path}"`);
|
|
135
|
-
} catch (e) { return error(String(e)); }
|
|
156
|
+
} catch (e) { logOp("mindos_write_file", { path }, "error", String(e)); return error(String(e)); }
|
|
136
157
|
});
|
|
137
158
|
|
|
138
159
|
// ── mindos_create_file ──────────────────────────────────────────────────────
|
|
@@ -147,8 +168,9 @@ server.registerTool("mindos_create_file", {
|
|
|
147
168
|
}, async ({ path, content }) => {
|
|
148
169
|
try {
|
|
149
170
|
await post("/api/file", { op: "create_file", path, content });
|
|
171
|
+
logOp("mindos_create_file", { path }, "ok", `Created ${content.length} chars`);
|
|
150
172
|
return ok(`Created "${path}" (${content.length} characters)`);
|
|
151
|
-
} catch (e) { return error(String(e)); }
|
|
173
|
+
} catch (e) { logOp("mindos_create_file", { path }, "error", String(e)); return error(String(e)); }
|
|
152
174
|
});
|
|
153
175
|
|
|
154
176
|
// ── mindos_delete_file ──────────────────────────────────────────────────────
|
|
@@ -163,8 +185,9 @@ server.registerTool("mindos_delete_file", {
|
|
|
163
185
|
}, async ({ path }) => {
|
|
164
186
|
try {
|
|
165
187
|
await post("/api/file", { op: "delete_file", path });
|
|
188
|
+
logOp("mindos_delete_file", { path }, "ok", `Deleted "${path}"`);
|
|
166
189
|
return ok(`Deleted "${path}"`);
|
|
167
|
-
} catch (e) { return error(String(e)); }
|
|
190
|
+
} catch (e) { logOp("mindos_delete_file", { path }, "error", String(e)); return error(String(e)); }
|
|
168
191
|
});
|
|
169
192
|
|
|
170
193
|
// ── mindos_rename_file ──────────────────────────────────────────────────────
|
|
@@ -227,8 +250,9 @@ server.registerTool("mindos_search_notes", {
|
|
|
227
250
|
if (modified_after) params.modified_after = modified_after;
|
|
228
251
|
if (response_format) params.format = response_format;
|
|
229
252
|
const json = await get("/api/search", params);
|
|
253
|
+
logOp("mindos_search_notes", { query, limit }, "ok", `Search completed`);
|
|
230
254
|
return ok(truncate(JSON.stringify(json, null, 2)));
|
|
231
|
-
} catch (e) { return error(String(e)); }
|
|
255
|
+
} catch (e) { logOp("mindos_search_notes", { query }, "error", String(e)); return error(String(e)); }
|
|
232
256
|
});
|
|
233
257
|
|
|
234
258
|
// ── mindos_get_recent ───────────────────────────────────────────────────────
|
|
@@ -313,8 +337,9 @@ server.registerTool("mindos_append_to_file", {
|
|
|
313
337
|
}, async ({ path, content }) => {
|
|
314
338
|
try {
|
|
315
339
|
await post("/api/file", { op: "append_to_file", path, content });
|
|
340
|
+
logOp("mindos_append_to_file", { path }, "ok", `Appended ${content.length} chars`);
|
|
316
341
|
return ok(`Appended ${content.length} character(s) to "${path}"`);
|
|
317
|
-
} catch (e) { return error(String(e)); }
|
|
342
|
+
} catch (e) { logOp("mindos_append_to_file", { path }, "error", String(e)); return error(String(e)); }
|
|
318
343
|
});
|
|
319
344
|
|
|
320
345
|
// ── mindos_insert_after_heading ─────────────────────────────────────────────
|
|
@@ -330,8 +355,9 @@ server.registerTool("mindos_insert_after_heading", {
|
|
|
330
355
|
}, async ({ path, heading, content }) => {
|
|
331
356
|
try {
|
|
332
357
|
await post("/api/file", { op: "insert_after_heading", path, heading, content });
|
|
358
|
+
logOp("mindos_insert_after_heading", { path, heading }, "ok", `Inserted after "${heading}"`);
|
|
333
359
|
return ok(`Inserted content after heading "${heading}" in "${path}"`);
|
|
334
|
-
} catch (e) { return error(String(e)); }
|
|
360
|
+
} catch (e) { logOp("mindos_insert_after_heading", { path, heading }, "error", String(e)); return error(String(e)); }
|
|
335
361
|
});
|
|
336
362
|
|
|
337
363
|
// ── mindos_update_section ───────────────────────────────────────────────────
|
|
@@ -347,8 +373,9 @@ server.registerTool("mindos_update_section", {
|
|
|
347
373
|
}, async ({ path, heading, content }) => {
|
|
348
374
|
try {
|
|
349
375
|
await post("/api/file", { op: "update_section", path, heading, content });
|
|
376
|
+
logOp("mindos_update_section", { path, heading }, "ok", `Updated section "${heading}"`);
|
|
350
377
|
return ok(`Updated section "${heading}" in "${path}"`);
|
|
351
|
-
} catch (e) { return error(String(e)); }
|
|
378
|
+
} catch (e) { logOp("mindos_update_section", { path, heading }, "error", String(e)); return error(String(e)); }
|
|
352
379
|
});
|
|
353
380
|
|
|
354
381
|
// ── mindos_append_csv ───────────────────────────────────────────────────────
|
package/package.json
CHANGED
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
|
|
9
|
+
* 1. Choose knowledge base path → default ~/MindOS
|
|
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)
|
|
@@ -109,6 +109,8 @@ const T = {
|
|
|
109
109
|
yesNo: { en: '[y/N]', zh: '[y/N]' },
|
|
110
110
|
yesNoDefault: { en: '[Y/n]', zh: '[Y/n]' },
|
|
111
111
|
startNow: { en: 'Start MindOS now?', zh: '现在启动 MindOS?' },
|
|
112
|
+
syncSetup: { en: 'Set up cross-device sync via Git?', zh: '是否配置 Git 跨设备同步?' },
|
|
113
|
+
syncLater: { en: ' → Run `mindos sync init` anytime to set up sync later.', zh: ' → 随时运行 `mindos sync init` 配置同步。' },
|
|
112
114
|
|
|
113
115
|
// next steps (onboard — keep it minimal, details shown on `mindos start`)
|
|
114
116
|
nextSteps: {
|
|
@@ -674,6 +676,15 @@ async function main() {
|
|
|
674
676
|
writeFileSync(CONFIG_PATH, JSON.stringify(config, null, 2) + '\n');
|
|
675
677
|
console.log(`\n${c.green(t('cfgSaved'))}: ${c.dim(CONFIG_PATH)}`);
|
|
676
678
|
|
|
679
|
+
// ── Sync setup (optional) ──────────────────────────────────────────────────
|
|
680
|
+
const wantSync = await askYesNo('syncSetup');
|
|
681
|
+
if (wantSync) {
|
|
682
|
+
const { initSync } = await import('../bin/lib/sync.js');
|
|
683
|
+
await initSync(mindDir);
|
|
684
|
+
} else {
|
|
685
|
+
console.log(c.dim(t('syncLater')));
|
|
686
|
+
}
|
|
687
|
+
|
|
677
688
|
const installDaemon = startMode === 'daemon' || process.argv.includes('--install-daemon');
|
|
678
689
|
finish(mindDir, config.startMode, config.mcpPort, config.authToken, installDaemon);
|
|
679
690
|
}
|
package/templates/README.md
CHANGED
|
@@ -18,7 +18,7 @@ If you need to manually initialize:
|
|
|
18
18
|
mindos onboard
|
|
19
19
|
|
|
20
20
|
# 2) Or copy a preset manually to your knowledge base directory
|
|
21
|
-
cp -r templates/en
|
|
21
|
+
cp -r templates/en ~/MindOS
|
|
22
22
|
# then set mindRoot in ~/.mindos/config.json
|
|
23
23
|
|
|
24
24
|
# 3) Start filling content from 👤 Profile (en) or 👤 画像 (zh)
|