@geminilight/mindos 0.1.9 → 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 +42 -12
- package/README_zh.md +38 -5
- package/app/README.md +1 -1
- package/app/app/api/init/route.ts +56 -0
- package/app/app/api/sync/route.ts +124 -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 +10 -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 +311 -0
- package/app/components/settings/types.ts +1 -1
- package/app/lib/agent/log.ts +44 -0
- package/app/lib/agent/tools.ts +39 -18
- package/app/lib/i18n.ts +80 -2
- package/app/lib/renderers/index.ts +13 -0
- package/app/lib/settings.ts +2 -2
- 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 +214 -10
- package/bin/lib/config.js +12 -1
- package/bin/lib/mcp-install.js +225 -70
- package/bin/lib/startup.js +24 -1
- package/bin/lib/sync.js +367 -0
- package/mcp/src/index.ts +37 -10
- package/package.json +6 -2
- package/scripts/release.sh +56 -0
- package/scripts/setup.js +35 -6
- package/templates/README.md +1 -1
package/bin/lib/sync.js
ADDED
|
@@ -0,0 +1,367 @@
|
|
|
1
|
+
import { execSync } from 'node:child_process';
|
|
2
|
+
import { existsSync, readFileSync, writeFileSync, mkdirSync } from 'node:fs';
|
|
3
|
+
import { resolve } from 'node:path';
|
|
4
|
+
import { CONFIG_PATH, MINDOS_DIR } from './constants.js';
|
|
5
|
+
import { bold, dim, cyan, green, red, yellow } from './colors.js';
|
|
6
|
+
|
|
7
|
+
// ── Config helpers ──────────────────────────────────────────────────────────
|
|
8
|
+
|
|
9
|
+
function loadSyncConfig() {
|
|
10
|
+
try {
|
|
11
|
+
const config = JSON.parse(readFileSync(CONFIG_PATH, 'utf-8'));
|
|
12
|
+
return config.sync || {};
|
|
13
|
+
} catch {
|
|
14
|
+
return {};
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function saveSyncConfig(syncConfig) {
|
|
19
|
+
let config = {};
|
|
20
|
+
try { config = JSON.parse(readFileSync(CONFIG_PATH, 'utf-8')); } catch {}
|
|
21
|
+
config.sync = syncConfig;
|
|
22
|
+
writeFileSync(CONFIG_PATH, JSON.stringify(config, null, 2) + '\n', 'utf-8');
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function getMindRoot() {
|
|
26
|
+
try {
|
|
27
|
+
const config = JSON.parse(readFileSync(CONFIG_PATH, 'utf-8'));
|
|
28
|
+
return config.mindRoot;
|
|
29
|
+
} catch {
|
|
30
|
+
return null;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const SYNC_STATE_PATH = resolve(MINDOS_DIR, 'sync-state.json');
|
|
35
|
+
|
|
36
|
+
function loadSyncState() {
|
|
37
|
+
try {
|
|
38
|
+
return JSON.parse(readFileSync(SYNC_STATE_PATH, 'utf-8'));
|
|
39
|
+
} catch {
|
|
40
|
+
return {};
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function saveSyncState(state) {
|
|
45
|
+
if (!existsSync(MINDOS_DIR)) mkdirSync(MINDOS_DIR, { recursive: true });
|
|
46
|
+
writeFileSync(SYNC_STATE_PATH, JSON.stringify(state, null, 2) + '\n', 'utf-8');
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// ── Git helpers ─────────────────────────────────────────────────────────────
|
|
50
|
+
|
|
51
|
+
function isGitRepo(dir) {
|
|
52
|
+
return existsSync(resolve(dir, '.git'));
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function gitExec(cmd, cwd) {
|
|
56
|
+
return execSync(cmd, { cwd, encoding: 'utf-8', stdio: 'pipe' }).trim();
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function getRemoteUrl(cwd) {
|
|
60
|
+
try {
|
|
61
|
+
return gitExec('git remote get-url origin', cwd);
|
|
62
|
+
} catch {
|
|
63
|
+
return null;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function getBranch(cwd) {
|
|
68
|
+
try {
|
|
69
|
+
return gitExec('git rev-parse --abbrev-ref HEAD', cwd);
|
|
70
|
+
} catch {
|
|
71
|
+
return 'main';
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function getUnpushedCount(cwd) {
|
|
76
|
+
try {
|
|
77
|
+
return gitExec('git rev-list --count @{u}..HEAD', cwd);
|
|
78
|
+
} catch {
|
|
79
|
+
return '?';
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// ── Core sync functions ─────────────────────────────────────────────────────
|
|
84
|
+
|
|
85
|
+
function autoCommitAndPush(mindRoot) {
|
|
86
|
+
try {
|
|
87
|
+
execSync('git add -A', { cwd: mindRoot, stdio: 'pipe' });
|
|
88
|
+
const status = gitExec('git status --porcelain', mindRoot);
|
|
89
|
+
if (!status) return;
|
|
90
|
+
const timestamp = new Date().toISOString().replace('T', ' ').slice(0, 19);
|
|
91
|
+
execSync(`git commit -m "auto-sync: ${timestamp}"`, { cwd: mindRoot, stdio: 'pipe' });
|
|
92
|
+
execSync('git push', { cwd: mindRoot, stdio: 'pipe' });
|
|
93
|
+
saveSyncState({ ...loadSyncState(), lastSync: new Date().toISOString(), lastError: null });
|
|
94
|
+
} catch (err) {
|
|
95
|
+
saveSyncState({ ...loadSyncState(), lastError: err.message, lastErrorTime: new Date().toISOString() });
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function autoPull(mindRoot) {
|
|
100
|
+
try {
|
|
101
|
+
execSync('git pull --rebase --autostash', { cwd: mindRoot, stdio: 'pipe' });
|
|
102
|
+
saveSyncState({ ...loadSyncState(), lastPull: new Date().toISOString() });
|
|
103
|
+
} catch {
|
|
104
|
+
// rebase conflict → abort → merge
|
|
105
|
+
try { execSync('git rebase --abort', { cwd: mindRoot, stdio: 'pipe' }); } catch {}
|
|
106
|
+
try {
|
|
107
|
+
execSync('git pull --no-rebase', { cwd: mindRoot, stdio: 'pipe' });
|
|
108
|
+
saveSyncState({ ...loadSyncState(), lastPull: new Date().toISOString() });
|
|
109
|
+
} catch {
|
|
110
|
+
// merge conflict → keep both versions
|
|
111
|
+
try {
|
|
112
|
+
const conflicts = gitExec('git diff --name-only --diff-filter=U', mindRoot).split('\n').filter(Boolean);
|
|
113
|
+
for (const file of conflicts) {
|
|
114
|
+
try {
|
|
115
|
+
const theirs = execSync(`git show :3:${file}`, { cwd: mindRoot, encoding: 'utf-8' });
|
|
116
|
+
writeFileSync(resolve(mindRoot, file + '.sync-conflict'), theirs, 'utf-8');
|
|
117
|
+
} catch {}
|
|
118
|
+
try { execSync(`git checkout --ours "${file}"`, { cwd: mindRoot, stdio: 'pipe' }); } catch {}
|
|
119
|
+
}
|
|
120
|
+
execSync('git add -A', { cwd: mindRoot, stdio: 'pipe' });
|
|
121
|
+
execSync('git commit -m "auto-sync: resolved conflicts (kept both versions)"', { cwd: mindRoot, stdio: 'pipe' });
|
|
122
|
+
saveSyncState({
|
|
123
|
+
...loadSyncState(),
|
|
124
|
+
lastPull: new Date().toISOString(),
|
|
125
|
+
conflicts: conflicts.map(f => ({ file: f, time: new Date().toISOString() })),
|
|
126
|
+
});
|
|
127
|
+
} catch (err) {
|
|
128
|
+
saveSyncState({ ...loadSyncState(), lastError: err.message, lastErrorTime: new Date().toISOString() });
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// ── Exported API ────────────────────────────────────────────────────────────
|
|
135
|
+
|
|
136
|
+
let activeWatcher = null;
|
|
137
|
+
let activePullInterval = null;
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Interactive sync init — configure remote git repo
|
|
141
|
+
*/
|
|
142
|
+
export async function initSync(mindRoot) {
|
|
143
|
+
if (!mindRoot) { console.error(red('No mindRoot configured.')); process.exit(1); }
|
|
144
|
+
|
|
145
|
+
const readline = await import('node:readline');
|
|
146
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
147
|
+
const ask = (q) => new Promise(r => rl.question(q, r));
|
|
148
|
+
|
|
149
|
+
// 1. Ensure git repo
|
|
150
|
+
if (!isGitRepo(mindRoot)) {
|
|
151
|
+
console.log(dim('Initializing git repository...'));
|
|
152
|
+
execSync('git init', { cwd: mindRoot, stdio: 'inherit' });
|
|
153
|
+
execSync('git checkout -b main', { cwd: mindRoot, stdio: 'pipe' }).toString();
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// 2. Remote URL
|
|
157
|
+
const currentRemote = getRemoteUrl(mindRoot);
|
|
158
|
+
const defaultUrl = currentRemote || '';
|
|
159
|
+
const urlPrompt = currentRemote
|
|
160
|
+
? `${bold('Remote URL')} ${dim(`[${currentRemote}]`)}: `
|
|
161
|
+
: `${bold('Remote URL')} ${dim('(HTTPS or SSH)')}: `;
|
|
162
|
+
let remoteUrl = (await ask(urlPrompt)).trim() || defaultUrl;
|
|
163
|
+
|
|
164
|
+
if (!remoteUrl) {
|
|
165
|
+
console.error(red('Remote URL is required.'));
|
|
166
|
+
rl.close();
|
|
167
|
+
process.exit(1);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// 3. Token for HTTPS
|
|
171
|
+
let token = '';
|
|
172
|
+
if (remoteUrl.startsWith('https://')) {
|
|
173
|
+
token = (await ask(`${bold('Access Token')} ${dim('(GitHub PAT / GitLab PAT, leave empty if SSH)')}: `)).trim();
|
|
174
|
+
if (token) {
|
|
175
|
+
// Inject token into URL for credential storage
|
|
176
|
+
const urlObj = new URL(remoteUrl);
|
|
177
|
+
urlObj.username = 'oauth2';
|
|
178
|
+
urlObj.password = token;
|
|
179
|
+
const authUrl = urlObj.toString();
|
|
180
|
+
// Configure credential helper
|
|
181
|
+
try { execSync(`git config credential.helper store`, { cwd: mindRoot, stdio: 'pipe' }); } catch {}
|
|
182
|
+
// Store the credential
|
|
183
|
+
try {
|
|
184
|
+
const credInput = `protocol=${urlObj.protocol.replace(':', '')}\nhost=${urlObj.host}\nusername=oauth2\npassword=${token}\n\n`;
|
|
185
|
+
execSync('git credential approve', { cwd: mindRoot, input: credInput, stdio: 'pipe' });
|
|
186
|
+
} catch {}
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// 4. Set remote
|
|
191
|
+
try {
|
|
192
|
+
execSync(`git remote add origin "${remoteUrl}"`, { cwd: mindRoot, stdio: 'pipe' });
|
|
193
|
+
} catch {
|
|
194
|
+
execSync(`git remote set-url origin "${remoteUrl}"`, { cwd: mindRoot, stdio: 'pipe' });
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// 5. Test connection
|
|
198
|
+
console.log(dim('Testing connection...'));
|
|
199
|
+
try {
|
|
200
|
+
execSync('git ls-remote --exit-code origin', { cwd: mindRoot, stdio: 'pipe' });
|
|
201
|
+
console.log(green('✔ Connection successful'));
|
|
202
|
+
} catch {
|
|
203
|
+
console.error(red('✘ Could not connect to remote. Check your URL and credentials.'));
|
|
204
|
+
rl.close();
|
|
205
|
+
process.exit(1);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
rl.close();
|
|
209
|
+
|
|
210
|
+
// 6. Save sync config
|
|
211
|
+
const syncConfig = {
|
|
212
|
+
enabled: true,
|
|
213
|
+
provider: 'git',
|
|
214
|
+
remote: 'origin',
|
|
215
|
+
branch: getBranch(mindRoot),
|
|
216
|
+
autoCommitInterval: 30,
|
|
217
|
+
autoPullInterval: 300,
|
|
218
|
+
};
|
|
219
|
+
saveSyncConfig(syncConfig);
|
|
220
|
+
console.log(green('✔ Sync configured'));
|
|
221
|
+
|
|
222
|
+
// 7. First sync: pull if remote has content, push otherwise
|
|
223
|
+
try {
|
|
224
|
+
const refs = gitExec('git ls-remote --heads origin', mindRoot);
|
|
225
|
+
if (refs) {
|
|
226
|
+
console.log(dim('Pulling from remote...'));
|
|
227
|
+
try {
|
|
228
|
+
execSync(`git pull origin ${syncConfig.branch} --allow-unrelated-histories`, { cwd: mindRoot, stdio: 'inherit' });
|
|
229
|
+
} catch {
|
|
230
|
+
// Might fail if empty or conflicts — that's fine for initial setup
|
|
231
|
+
console.log(yellow('Pull completed with warnings. Check for conflicts.'));
|
|
232
|
+
}
|
|
233
|
+
} else {
|
|
234
|
+
console.log(dim('Pushing to remote...'));
|
|
235
|
+
autoCommitAndPush(mindRoot);
|
|
236
|
+
}
|
|
237
|
+
} catch {
|
|
238
|
+
console.log(dim('Performing initial push...'));
|
|
239
|
+
autoCommitAndPush(mindRoot);
|
|
240
|
+
}
|
|
241
|
+
console.log(green('✔ Initial sync complete\n'));
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
/**
|
|
245
|
+
* Start file watcher + periodic pull
|
|
246
|
+
*/
|
|
247
|
+
export async function startSyncDaemon(mindRoot) {
|
|
248
|
+
const config = loadSyncConfig();
|
|
249
|
+
if (!config.enabled) return null;
|
|
250
|
+
if (!mindRoot || !isGitRepo(mindRoot)) return null;
|
|
251
|
+
|
|
252
|
+
const chokidar = await import('chokidar');
|
|
253
|
+
|
|
254
|
+
// File watcher → debounced auto-commit + push
|
|
255
|
+
let commitTimer = null;
|
|
256
|
+
const watcher = chokidar.watch(mindRoot, {
|
|
257
|
+
ignored: [/(^|[/\\])\.git/, /node_modules/, /\.sync-conflict$/],
|
|
258
|
+
persistent: true,
|
|
259
|
+
ignoreInitial: true,
|
|
260
|
+
});
|
|
261
|
+
watcher.on('all', () => {
|
|
262
|
+
clearTimeout(commitTimer);
|
|
263
|
+
commitTimer = setTimeout(() => autoCommitAndPush(mindRoot), (config.autoCommitInterval || 30) * 1000);
|
|
264
|
+
});
|
|
265
|
+
|
|
266
|
+
// Periodic pull
|
|
267
|
+
const pullInterval = setInterval(() => autoPull(mindRoot), (config.autoPullInterval || 300) * 1000);
|
|
268
|
+
|
|
269
|
+
// Pull on startup
|
|
270
|
+
autoPull(mindRoot);
|
|
271
|
+
|
|
272
|
+
activeWatcher = watcher;
|
|
273
|
+
activePullInterval = pullInterval;
|
|
274
|
+
|
|
275
|
+
return { watcher, pullInterval };
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
/**
|
|
279
|
+
* Stop sync daemon
|
|
280
|
+
*/
|
|
281
|
+
export function stopSyncDaemon() {
|
|
282
|
+
if (activeWatcher) {
|
|
283
|
+
activeWatcher.close();
|
|
284
|
+
activeWatcher = null;
|
|
285
|
+
}
|
|
286
|
+
if (activePullInterval) {
|
|
287
|
+
clearInterval(activePullInterval);
|
|
288
|
+
activePullInterval = null;
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
/**
|
|
293
|
+
* Get current sync status
|
|
294
|
+
*/
|
|
295
|
+
export function getSyncStatus(mindRoot) {
|
|
296
|
+
const config = loadSyncConfig();
|
|
297
|
+
const state = loadSyncState();
|
|
298
|
+
|
|
299
|
+
if (!config.enabled) {
|
|
300
|
+
return { enabled: false };
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
const remote = mindRoot ? getRemoteUrl(mindRoot) : null;
|
|
304
|
+
const branch = mindRoot ? getBranch(mindRoot) : null;
|
|
305
|
+
const unpushed = mindRoot ? getUnpushedCount(mindRoot) : '?';
|
|
306
|
+
|
|
307
|
+
return {
|
|
308
|
+
enabled: true,
|
|
309
|
+
provider: config.provider || 'git',
|
|
310
|
+
remote: remote || '(not configured)',
|
|
311
|
+
branch: branch || 'main',
|
|
312
|
+
lastSync: state.lastSync || null,
|
|
313
|
+
lastPull: state.lastPull || null,
|
|
314
|
+
unpushed,
|
|
315
|
+
conflicts: state.conflicts || [],
|
|
316
|
+
lastError: state.lastError || null,
|
|
317
|
+
autoCommitInterval: config.autoCommitInterval || 30,
|
|
318
|
+
autoPullInterval: config.autoPullInterval || 300,
|
|
319
|
+
};
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
/**
|
|
323
|
+
* Manual trigger of full sync cycle
|
|
324
|
+
*/
|
|
325
|
+
export function manualSync(mindRoot) {
|
|
326
|
+
if (!mindRoot || !isGitRepo(mindRoot)) {
|
|
327
|
+
console.error(red('Not a git repository. Run `mindos sync init` first.'));
|
|
328
|
+
process.exit(1);
|
|
329
|
+
}
|
|
330
|
+
console.log(dim('Pulling...'));
|
|
331
|
+
autoPull(mindRoot);
|
|
332
|
+
console.log(dim('Committing & pushing...'));
|
|
333
|
+
autoCommitAndPush(mindRoot);
|
|
334
|
+
console.log(green('✔ Sync complete'));
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
/**
|
|
338
|
+
* List conflict files
|
|
339
|
+
*/
|
|
340
|
+
export function listConflicts(mindRoot) {
|
|
341
|
+
const state = loadSyncState();
|
|
342
|
+
const conflicts = state.conflicts || [];
|
|
343
|
+
if (!conflicts.length) {
|
|
344
|
+
console.log(green('No conflicts'));
|
|
345
|
+
return [];
|
|
346
|
+
}
|
|
347
|
+
console.log(bold(`${conflicts.length} conflict(s):\n`));
|
|
348
|
+
for (const c of conflicts) {
|
|
349
|
+
console.log(` ${yellow('●')} ${c.file} ${dim(c.time)}`);
|
|
350
|
+
const conflictPath = resolve(mindRoot, c.file + '.sync-conflict');
|
|
351
|
+
if (existsSync(conflictPath)) {
|
|
352
|
+
console.log(dim(` Remote version saved: ${c.file}.sync-conflict`));
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
console.log();
|
|
356
|
+
return conflicts;
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
/**
|
|
360
|
+
* Enable/disable sync
|
|
361
|
+
*/
|
|
362
|
+
export function setSyncEnabled(enabled) {
|
|
363
|
+
const config = loadSyncConfig();
|
|
364
|
+
config.enabled = enabled;
|
|
365
|
+
saveSyncConfig(config);
|
|
366
|
+
console.log(enabled ? green('✔ Auto-sync enabled') : yellow('Auto-sync disabled'));
|
|
367
|
+
}
|
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
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@geminilight/mindos",
|
|
3
|
-
"version": "0.1
|
|
3
|
+
"version": "0.2.1",
|
|
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",
|
|
@@ -51,9 +51,13 @@
|
|
|
51
51
|
"build": "mindos build",
|
|
52
52
|
"start": "mindos start",
|
|
53
53
|
"mcp": "mindos mcp",
|
|
54
|
-
"test": "cd app && npx vitest run"
|
|
54
|
+
"test": "cd app && npx vitest run",
|
|
55
|
+
"release": "bash scripts/release.sh"
|
|
55
56
|
},
|
|
56
57
|
"engines": {
|
|
57
58
|
"node": ">=18"
|
|
59
|
+
},
|
|
60
|
+
"dependencies": {
|
|
61
|
+
"chokidar": "^5.0.0"
|
|
58
62
|
}
|
|
59
63
|
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
set -euo pipefail
|
|
3
|
+
|
|
4
|
+
# ── Usage ────────────────────────────────────────────────────────────────
|
|
5
|
+
# npm run release [patch|minor|major] (default: patch)
|
|
6
|
+
# ─────────────────────────────────────────────────────────────────────────
|
|
7
|
+
|
|
8
|
+
BUMP="${1:-patch}"
|
|
9
|
+
|
|
10
|
+
# 1. Ensure clean working tree
|
|
11
|
+
if ! git diff --quiet || ! git diff --cached --quiet; then
|
|
12
|
+
echo "❌ Working tree is not clean. Commit or stash changes first."
|
|
13
|
+
exit 1
|
|
14
|
+
fi
|
|
15
|
+
|
|
16
|
+
# 2. Run tests
|
|
17
|
+
echo "🧪 Running tests..."
|
|
18
|
+
npm test
|
|
19
|
+
echo ""
|
|
20
|
+
|
|
21
|
+
# 3. Bump version (creates commit + tag automatically)
|
|
22
|
+
echo "📦 Bumping version ($BUMP)..."
|
|
23
|
+
npm version "$BUMP" -m "%s"
|
|
24
|
+
VERSION="v$(node -p "require('./package.json').version")"
|
|
25
|
+
echo " Version: $VERSION"
|
|
26
|
+
echo ""
|
|
27
|
+
|
|
28
|
+
# 4. Push commit + tag
|
|
29
|
+
echo "🚀 Pushing to origin..."
|
|
30
|
+
git push origin main
|
|
31
|
+
git push origin "$VERSION"
|
|
32
|
+
echo ""
|
|
33
|
+
|
|
34
|
+
# 5. Wait for CI (if gh is available)
|
|
35
|
+
if command -v gh &>/dev/null; then
|
|
36
|
+
echo "⏳ Waiting for CI publish workflow..."
|
|
37
|
+
TIMEOUT=120
|
|
38
|
+
ELAPSED=0
|
|
39
|
+
RUN_ID=""
|
|
40
|
+
|
|
41
|
+
# Wait for the workflow run to appear
|
|
42
|
+
while [ -z "$RUN_ID" ] && [ "$ELAPSED" -lt 30 ]; do
|
|
43
|
+
sleep 3
|
|
44
|
+
ELAPSED=$((ELAPSED + 3))
|
|
45
|
+
RUN_ID=$(gh run list --workflow=publish-npm.yml --limit=1 --json databaseId,headBranch --jq ".[0].databaseId" 2>/dev/null || true)
|
|
46
|
+
done
|
|
47
|
+
|
|
48
|
+
if [ -n "$RUN_ID" ]; then
|
|
49
|
+
gh run watch "$RUN_ID" --exit-status && echo "✅ Published $VERSION to npm" || echo "❌ CI failed — check: gh run view $RUN_ID --log"
|
|
50
|
+
else
|
|
51
|
+
echo "⚠️ Could not find CI run. Check manually: https://github.com/GeminiLight/mindos-dev/actions"
|
|
52
|
+
fi
|
|
53
|
+
else
|
|
54
|
+
echo "💡 Install 'gh' CLI to auto-watch CI status."
|
|
55
|
+
echo " Check publish status: https://github.com/GeminiLight/mindos-dev/actions"
|
|
56
|
+
fi
|
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)
|
|
@@ -44,8 +44,8 @@ const T = {
|
|
|
44
44
|
// step labels
|
|
45
45
|
step: { en: (n, total) => `Step ${n}/${total}`, zh: (n, total) => `步骤 ${n}/${total}` },
|
|
46
46
|
stepTitles: {
|
|
47
|
-
en: ['Knowledge Base', 'Template', 'Ports', 'Auth Token', 'Web Password', 'AI Provider'],
|
|
48
|
-
zh: ['知识库', '模板', '端口', 'Auth Token', 'Web 密码', 'AI 服务商'],
|
|
47
|
+
en: ['Knowledge Base', 'Template', 'Ports', 'Auth Token', 'Web Password', 'AI Provider', 'Start Mode'],
|
|
48
|
+
zh: ['知识库', '模板', '端口', 'Auth Token', 'Web 密码', 'AI 服务商', '启动方式'],
|
|
49
49
|
},
|
|
50
50
|
|
|
51
51
|
// path
|
|
@@ -97,12 +97,20 @@ const T = {
|
|
|
97
97
|
|
|
98
98
|
// config
|
|
99
99
|
cfgExists: { en: (p) => `${p} already exists. Overwrite?`, zh: (p) => `${p} 已存在,是否覆盖?` },
|
|
100
|
+
|
|
101
|
+
// start mode
|
|
102
|
+
startModePrompt: { en: 'Start Mode', zh: '启动方式' },
|
|
103
|
+
startModeOpts: { en: ['Background service (recommended, auto-start on boot)', 'Foreground (manual start each time)'], zh: ['后台服务(推荐,开机自启)', '前台运行(每次手动启动)'] },
|
|
104
|
+
startModeVals: ['daemon', 'start'],
|
|
105
|
+
startModeSkip: { en: ' → Daemon not supported on this platform, using foreground mode', zh: ' → 当前平台不支持后台服务,使用前台模式' },
|
|
100
106
|
cfgKept: { en: '✔ Keeping existing config', zh: '✔ 保留现有配置' },
|
|
101
107
|
cfgKeptNote: { en: ' Settings from this session were not saved', zh: ' 本次填写的设置未保存' },
|
|
102
108
|
cfgSaved: { en: '✔ Config saved', zh: '✔ 配置已保存' },
|
|
103
109
|
yesNo: { en: '[y/N]', zh: '[y/N]' },
|
|
104
110
|
yesNoDefault: { en: '[Y/n]', zh: '[Y/n]' },
|
|
105
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` 配置同步。' },
|
|
106
114
|
|
|
107
115
|
// next steps (onboard — keep it minimal, details shown on `mindos start`)
|
|
108
116
|
nextSteps: {
|
|
@@ -153,7 +161,7 @@ const tf = (key, ...args) => {
|
|
|
153
161
|
|
|
154
162
|
// ── Step header ───────────────────────────────────────────────────────────────
|
|
155
163
|
|
|
156
|
-
const TOTAL_STEPS =
|
|
164
|
+
const TOTAL_STEPS = 7;
|
|
157
165
|
function stepHeader(n) {
|
|
158
166
|
const title = T.stepTitles[uiLang][n - 1] ?? T.stepTitles.en[n - 1];
|
|
159
167
|
const stepLabel = tf('step', n, TOTAL_STEPS);
|
|
@@ -639,13 +647,25 @@ async function main() {
|
|
|
639
647
|
}
|
|
640
648
|
}
|
|
641
649
|
|
|
650
|
+
// ── Step 7: Start Mode ──────────────────────────────────────────────────
|
|
651
|
+
write('\n');
|
|
652
|
+
stepHeader(7);
|
|
653
|
+
|
|
654
|
+
let startMode = 'start';
|
|
655
|
+
const daemonPlatform = process.platform === 'darwin' || process.platform === 'linux';
|
|
656
|
+
if (daemonPlatform) {
|
|
657
|
+
startMode = await select('startModePrompt', 'startModeOpts', 'startModeVals');
|
|
658
|
+
} else {
|
|
659
|
+
write(c.dim(t('startModeSkip') + '\n'));
|
|
660
|
+
}
|
|
661
|
+
|
|
642
662
|
const config = {
|
|
643
663
|
mindRoot: mindDir,
|
|
644
664
|
port: webPort,
|
|
645
665
|
mcpPort: mcpPort,
|
|
646
666
|
authToken: authToken,
|
|
647
667
|
webPassword: webPassword || '',
|
|
648
|
-
startMode:
|
|
668
|
+
startMode: startMode,
|
|
649
669
|
ai: {
|
|
650
670
|
provider: isSkip ? existingAiProvider : (isAnthropic ? 'anthropic' : 'openai'),
|
|
651
671
|
providers: existingProviders,
|
|
@@ -656,7 +676,16 @@ async function main() {
|
|
|
656
676
|
writeFileSync(CONFIG_PATH, JSON.stringify(config, null, 2) + '\n');
|
|
657
677
|
console.log(`\n${c.green(t('cfgSaved'))}: ${c.dim(CONFIG_PATH)}`);
|
|
658
678
|
|
|
659
|
-
|
|
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
|
+
|
|
688
|
+
const installDaemon = startMode === 'daemon' || process.argv.includes('--install-daemon');
|
|
660
689
|
finish(mindDir, config.startMode, config.mcpPort, config.authToken, installDaemon);
|
|
661
690
|
}
|
|
662
691
|
|
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)
|