@geminilight/mindos 0.1.8 → 0.2.0
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 +41 -11
- package/README_zh.md +3 -6
- package/app/app/api/sync/route.ts +124 -0
- package/app/components/SettingsModal.tsx +3 -0
- package/app/components/settings/SyncTab.tsx +219 -0
- package/app/components/settings/types.ts +1 -1
- package/app/lib/i18n.ts +2 -2
- package/app/lib/settings.ts +1 -1
- package/assets/demo-flow-zh.html +30 -30
- package/assets/images/demo-flow-dark.png +0 -0
- package/assets/images/demo-flow-light.png +0 -0
- package/assets/images/demo-flow-zh-dark.png +0 -0
- package/assets/images/demo-flow-zh-light.png +0 -0
- package/bin/cli.js +234 -692
- package/bin/lib/build.js +59 -0
- package/bin/lib/colors.js +7 -0
- package/bin/lib/config.js +58 -0
- package/bin/lib/constants.js +13 -0
- package/bin/lib/gateway.js +244 -0
- package/bin/lib/mcp-install.js +156 -0
- package/bin/lib/mcp-spawn.js +36 -0
- package/bin/lib/pid.js +15 -0
- package/bin/lib/port.js +19 -0
- package/bin/lib/startup.js +51 -0
- package/bin/lib/stop.js +27 -0
- package/bin/lib/sync.js +367 -0
- package/bin/lib/utils.js +16 -0
- package/package.json +6 -2
- package/scripts/release.sh +56 -0
- package/scripts/setup.js +23 -5
package/bin/lib/stop.js
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { execSync } from 'node:child_process';
|
|
2
|
+
import { green, yellow, dim } from './colors.js';
|
|
3
|
+
import { loadPids, clearPids } from './pid.js';
|
|
4
|
+
|
|
5
|
+
export function stopMindos() {
|
|
6
|
+
const pids = loadPids();
|
|
7
|
+
if (!pids.length) {
|
|
8
|
+
console.log(yellow('No PID file found, trying pattern-based stop...'));
|
|
9
|
+
try { execSync('pkill -f "next start|next dev" 2>/dev/null || true', { stdio: 'inherit' }); } catch {}
|
|
10
|
+
try { execSync('pkill -f "mcp/src/index" 2>/dev/null || true', { stdio: 'inherit' }); } catch {}
|
|
11
|
+
console.log(green('\u2714 Done'));
|
|
12
|
+
return;
|
|
13
|
+
}
|
|
14
|
+
let stopped = 0;
|
|
15
|
+
for (const pid of pids) {
|
|
16
|
+
try {
|
|
17
|
+
process.kill(pid, 'SIGTERM');
|
|
18
|
+
stopped++;
|
|
19
|
+
} catch {
|
|
20
|
+
// process already gone — ignore
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
clearPids();
|
|
24
|
+
console.log(stopped
|
|
25
|
+
? green(`\u2714 Stopped ${stopped} process${stopped > 1 ? 'es' : ''}`)
|
|
26
|
+
: dim('No running processes found'));
|
|
27
|
+
}
|
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/bin/lib/utils.js
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { execSync } from 'node:child_process';
|
|
2
|
+
import { resolve } from 'node:path';
|
|
3
|
+
import { homedir } from 'node:os';
|
|
4
|
+
import { ROOT } from './constants.js';
|
|
5
|
+
|
|
6
|
+
export function run(command, cwd = ROOT) {
|
|
7
|
+
try {
|
|
8
|
+
execSync(command, { cwd, stdio: 'inherit', env: process.env });
|
|
9
|
+
} catch {
|
|
10
|
+
process.exit(1);
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function expandHome(p) {
|
|
15
|
+
return p.startsWith('~/') ? resolve(homedir(), p.slice(2)) : p;
|
|
16
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@geminilight/mindos",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
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
|
@@ -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,6 +97,12 @@ 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: '✔ 配置已保存' },
|
|
@@ -153,7 +159,7 @@ const tf = (key, ...args) => {
|
|
|
153
159
|
|
|
154
160
|
// ── Step header ───────────────────────────────────────────────────────────────
|
|
155
161
|
|
|
156
|
-
const TOTAL_STEPS =
|
|
162
|
+
const TOTAL_STEPS = 7;
|
|
157
163
|
function stepHeader(n) {
|
|
158
164
|
const title = T.stepTitles[uiLang][n - 1] ?? T.stepTitles.en[n - 1];
|
|
159
165
|
const stepLabel = tf('step', n, TOTAL_STEPS);
|
|
@@ -639,13 +645,25 @@ async function main() {
|
|
|
639
645
|
}
|
|
640
646
|
}
|
|
641
647
|
|
|
648
|
+
// ── Step 7: Start Mode ──────────────────────────────────────────────────
|
|
649
|
+
write('\n');
|
|
650
|
+
stepHeader(7);
|
|
651
|
+
|
|
652
|
+
let startMode = 'start';
|
|
653
|
+
const daemonPlatform = process.platform === 'darwin' || process.platform === 'linux';
|
|
654
|
+
if (daemonPlatform) {
|
|
655
|
+
startMode = await select('startModePrompt', 'startModeOpts', 'startModeVals');
|
|
656
|
+
} else {
|
|
657
|
+
write(c.dim(t('startModeSkip') + '\n'));
|
|
658
|
+
}
|
|
659
|
+
|
|
642
660
|
const config = {
|
|
643
661
|
mindRoot: mindDir,
|
|
644
662
|
port: webPort,
|
|
645
663
|
mcpPort: mcpPort,
|
|
646
664
|
authToken: authToken,
|
|
647
665
|
webPassword: webPassword || '',
|
|
648
|
-
startMode:
|
|
666
|
+
startMode: startMode,
|
|
649
667
|
ai: {
|
|
650
668
|
provider: isSkip ? existingAiProvider : (isAnthropic ? 'anthropic' : 'openai'),
|
|
651
669
|
providers: existingProviders,
|
|
@@ -656,7 +674,7 @@ async function main() {
|
|
|
656
674
|
writeFileSync(CONFIG_PATH, JSON.stringify(config, null, 2) + '\n');
|
|
657
675
|
console.log(`\n${c.green(t('cfgSaved'))}: ${c.dim(CONFIG_PATH)}`);
|
|
658
676
|
|
|
659
|
-
const installDaemon = process.argv.includes('--install-daemon');
|
|
677
|
+
const installDaemon = startMode === 'daemon' || process.argv.includes('--install-daemon');
|
|
660
678
|
finish(mindDir, config.startMode, config.mcpPort, config.authToken, installDaemon);
|
|
661
679
|
}
|
|
662
680
|
|