@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
|
@@ -5,6 +5,7 @@ import { GraphRenderer } from '@/components/renderers/GraphRenderer';
|
|
|
5
5
|
import { TimelineRenderer } from '@/components/renderers/TimelineRenderer';
|
|
6
6
|
import { SummaryRenderer } from '@/components/renderers/SummaryRenderer';
|
|
7
7
|
import { ConfigRenderer } from '@/components/renderers/ConfigRenderer';
|
|
8
|
+
import { AgentInspectorRenderer } from '@/components/renderers/AgentInspectorRenderer';
|
|
8
9
|
|
|
9
10
|
registerRenderer({
|
|
10
11
|
id: 'todo',
|
|
@@ -77,3 +78,15 @@ registerRenderer({
|
|
|
77
78
|
match: ({ filePath }) => /\b(SUMMARY|summary|Summary|BRIEFING|briefing|Briefing|DAILY|daily|Daily)\b.*\.md$/i.test(filePath),
|
|
78
79
|
component: SummaryRenderer,
|
|
79
80
|
});
|
|
81
|
+
|
|
82
|
+
registerRenderer({
|
|
83
|
+
id: 'agent-inspector',
|
|
84
|
+
name: 'Agent Inspector',
|
|
85
|
+
description: 'Visualizes agent tool-call logs as a filterable timeline. Auto-activates on .agent-log.json (JSON Lines format).',
|
|
86
|
+
author: 'MindOS',
|
|
87
|
+
icon: '🔍',
|
|
88
|
+
tags: ['agent', 'inspector', 'log', 'mcp', 'tools'],
|
|
89
|
+
builtin: true,
|
|
90
|
+
match: ({ filePath }) => /\.agent-log\.json$/i.test(filePath),
|
|
91
|
+
component: AgentInspectorRenderer,
|
|
92
|
+
});
|
package/app/lib/settings.ts
CHANGED
|
@@ -26,7 +26,7 @@ export interface ServerSettings {
|
|
|
26
26
|
mcpPort?: number;
|
|
27
27
|
authToken?: string;
|
|
28
28
|
webPassword?: string;
|
|
29
|
-
startMode?: 'dev' | 'start';
|
|
29
|
+
startMode?: 'dev' | 'start' | 'daemon';
|
|
30
30
|
}
|
|
31
31
|
|
|
32
32
|
const DEFAULTS: ServerSettings = {
|
|
@@ -146,5 +146,5 @@ export function effectiveAiConfig() {
|
|
|
146
146
|
/** Effective MIND_ROOT — settings file can override, env var is fallback */
|
|
147
147
|
export function effectiveSopRoot(): string {
|
|
148
148
|
const s = readSettings();
|
|
149
|
-
return s.mindRoot || process.env.MIND_ROOT || path.join(os.homedir(), '
|
|
149
|
+
return s.mindRoot || process.env.MIND_ROOT || path.join(os.homedir(), 'MindOS');
|
|
150
150
|
}
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "MindOS",
|
|
3
|
+
"short_name": "MindOS",
|
|
4
|
+
"description": "Personal knowledge OS — browse, edit, and query your second brain.",
|
|
5
|
+
"start_url": "/",
|
|
6
|
+
"display": "standalone",
|
|
7
|
+
"background_color": "#0a0a0a",
|
|
8
|
+
"theme_color": "#c8871e",
|
|
9
|
+
"icons": [
|
|
10
|
+
{
|
|
11
|
+
"src": "/icons/icon-192.png",
|
|
12
|
+
"sizes": "192x192",
|
|
13
|
+
"type": "image/png"
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
"src": "/icons/icon-512.png",
|
|
17
|
+
"sizes": "512x512",
|
|
18
|
+
"type": "image/png"
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
"src": "/logo-square.svg",
|
|
22
|
+
"sizes": "any",
|
|
23
|
+
"type": "image/svg+xml"
|
|
24
|
+
}
|
|
25
|
+
]
|
|
26
|
+
}
|
package/app/public/sw.js
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
// MindOS Service Worker — cache static assets, skip API/dynamic routes
|
|
2
|
+
const CACHE_NAME = 'mindos-v1';
|
|
3
|
+
|
|
4
|
+
const PRECACHE_URLS = [
|
|
5
|
+
'/',
|
|
6
|
+
'/logo-square.svg',
|
|
7
|
+
'/logo.svg',
|
|
8
|
+
];
|
|
9
|
+
|
|
10
|
+
self.addEventListener('install', (event) => {
|
|
11
|
+
event.waitUntil(
|
|
12
|
+
caches.open(CACHE_NAME).then((cache) => cache.addAll(PRECACHE_URLS))
|
|
13
|
+
);
|
|
14
|
+
self.skipWaiting();
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
self.addEventListener('activate', (event) => {
|
|
18
|
+
event.waitUntil(
|
|
19
|
+
caches.keys().then((keys) =>
|
|
20
|
+
Promise.all(
|
|
21
|
+
keys.filter((k) => k !== CACHE_NAME).map((k) => caches.delete(k))
|
|
22
|
+
)
|
|
23
|
+
)
|
|
24
|
+
);
|
|
25
|
+
self.clients.claim();
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
self.addEventListener('fetch', (event) => {
|
|
29
|
+
const url = new URL(event.request.url);
|
|
30
|
+
|
|
31
|
+
// Never cache API routes or Next.js internals
|
|
32
|
+
if (
|
|
33
|
+
url.pathname.startsWith('/api/') ||
|
|
34
|
+
url.pathname.startsWith('/_next/') ||
|
|
35
|
+
event.request.method !== 'GET'
|
|
36
|
+
) {
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Cache-first for static assets (images, fonts, SVGs)
|
|
41
|
+
if (
|
|
42
|
+
url.pathname.startsWith('/icons/') ||
|
|
43
|
+
url.pathname.endsWith('.svg') ||
|
|
44
|
+
url.pathname.endsWith('.png') ||
|
|
45
|
+
url.pathname.endsWith('.woff2')
|
|
46
|
+
) {
|
|
47
|
+
event.respondWith(
|
|
48
|
+
caches.match(event.request).then((cached) => {
|
|
49
|
+
if (cached) return cached;
|
|
50
|
+
return fetch(event.request).then((response) => {
|
|
51
|
+
if (response.ok) {
|
|
52
|
+
const clone = response.clone();
|
|
53
|
+
caches.open(CACHE_NAME).then((cache) => cache.put(event.request, clone));
|
|
54
|
+
}
|
|
55
|
+
return response;
|
|
56
|
+
});
|
|
57
|
+
})
|
|
58
|
+
);
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// Network-first for HTML pages
|
|
63
|
+
event.respondWith(
|
|
64
|
+
fetch(event.request).catch(() => caches.match(event.request))
|
|
65
|
+
);
|
|
66
|
+
});
|
package/bin/cli.js
CHANGED
|
@@ -15,7 +15,13 @@
|
|
|
15
15
|
* mindos mcp — start MCP server only
|
|
16
16
|
* mindos stop — stop running MindOS processes
|
|
17
17
|
* mindos restart — stop then start
|
|
18
|
+
* mindos open — open Web UI in the default browser
|
|
18
19
|
* mindos token — show current auth token and MCP config snippet
|
|
20
|
+
* mindos sync — show sync status
|
|
21
|
+
* mindos sync init — configure remote git repo for sync
|
|
22
|
+
* mindos sync now — manual trigger sync
|
|
23
|
+
* mindos sync conflicts — list conflict files
|
|
24
|
+
* mindos sync on|off — enable/disable auto-sync
|
|
19
25
|
* mindos gateway install — install background service (systemd/launchd)
|
|
20
26
|
* mindos gateway uninstall — remove background service
|
|
21
27
|
* mindos gateway start — start the background service
|
|
@@ -38,20 +44,21 @@ import { homedir } from 'node:os';
|
|
|
38
44
|
import { ROOT, CONFIG_PATH, BUILD_STAMP, LOG_PATH } from './lib/constants.js';
|
|
39
45
|
import { bold, dim, cyan, green, red, yellow } from './lib/colors.js';
|
|
40
46
|
import { run } from './lib/utils.js';
|
|
41
|
-
import { loadConfig, getStartMode } from './lib/config.js';
|
|
47
|
+
import { loadConfig, getStartMode, isDaemonMode } from './lib/config.js';
|
|
42
48
|
import { needsBuild, writeBuildStamp, clearBuildLock, cleanNextDir, ensureAppDeps } from './lib/build.js';
|
|
43
49
|
import { isPortInUse, assertPortFree } from './lib/port.js';
|
|
44
50
|
import { savePids, clearPids } from './lib/pid.js';
|
|
45
51
|
import { stopMindos } from './lib/stop.js';
|
|
46
52
|
import { getPlatform, ensureMindosDir, waitForHttp, runGatewayCommand } from './lib/gateway.js';
|
|
47
|
-
import { printStartupInfo } from './lib/startup.js';
|
|
53
|
+
import { printStartupInfo, getLocalIP } from './lib/startup.js';
|
|
48
54
|
import { spawnMcp } from './lib/mcp-spawn.js';
|
|
49
55
|
import { mcpInstall } from './lib/mcp-install.js';
|
|
56
|
+
import { initSync, startSyncDaemon, stopSyncDaemon, getSyncStatus, manualSync, listConflicts, setSyncEnabled } from './lib/sync.js';
|
|
50
57
|
|
|
51
58
|
// ── Commands ──────────────────────────────────────────────────────────────────
|
|
52
59
|
|
|
53
60
|
const cmd = process.argv[2];
|
|
54
|
-
const isDaemon = process.argv.includes('--daemon');
|
|
61
|
+
const isDaemon = process.argv.includes('--daemon') || (!cmd && isDaemonMode());
|
|
55
62
|
const isVerbose = process.argv.includes('--verbose');
|
|
56
63
|
const extra = process.argv.slice(3).filter(a => a !== '--daemon' && a !== '--verbose').join(' ');
|
|
57
64
|
|
|
@@ -64,22 +71,117 @@ const commands = {
|
|
|
64
71
|
init: () => run(`node ${resolve(ROOT, 'scripts/setup.js')}`),
|
|
65
72
|
setup: () => run(`node ${resolve(ROOT, 'scripts/setup.js')}`),
|
|
66
73
|
|
|
74
|
+
// ── open ───────────────────────────────────────────────────────────────────
|
|
75
|
+
open: () => {
|
|
76
|
+
loadConfig();
|
|
77
|
+
const webPort = process.env.MINDOS_WEB_PORT || '3000';
|
|
78
|
+
const url = `http://localhost:${webPort}`;
|
|
79
|
+
let cmd;
|
|
80
|
+
if (process.platform === 'darwin') {
|
|
81
|
+
cmd = 'open';
|
|
82
|
+
} else if (process.platform === 'linux') {
|
|
83
|
+
// WSL detection
|
|
84
|
+
try {
|
|
85
|
+
const uname = execSync('uname -r', { encoding: 'utf-8' });
|
|
86
|
+
cmd = uname.toLowerCase().includes('microsoft') ? 'wslview' : 'xdg-open';
|
|
87
|
+
} catch {
|
|
88
|
+
cmd = 'xdg-open';
|
|
89
|
+
}
|
|
90
|
+
} else {
|
|
91
|
+
cmd = 'start';
|
|
92
|
+
}
|
|
93
|
+
try {
|
|
94
|
+
execSync(`${cmd} ${url}`, { stdio: 'ignore' });
|
|
95
|
+
console.log(`${green('✔')} Opening ${cyan(url)}`);
|
|
96
|
+
} catch {
|
|
97
|
+
console.log(dim(`Could not open browser automatically. Visit: ${cyan(url)}`));
|
|
98
|
+
}
|
|
99
|
+
},
|
|
100
|
+
|
|
67
101
|
// ── token ──────────────────────────────────────────────────────────────────
|
|
68
102
|
token: () => {
|
|
69
103
|
if (!existsSync(CONFIG_PATH)) {
|
|
70
104
|
console.error(red('No config found. Run `mindos onboard` first.'));
|
|
71
105
|
process.exit(1);
|
|
72
106
|
}
|
|
73
|
-
let
|
|
74
|
-
try {
|
|
107
|
+
let config = {};
|
|
108
|
+
try { config = JSON.parse(readFileSync(CONFIG_PATH, 'utf-8')); } catch {}
|
|
109
|
+
const token = config.authToken || '';
|
|
75
110
|
if (!token) {
|
|
76
111
|
console.log(dim('No auth token set. Run `mindos onboard` to configure one.'));
|
|
77
112
|
process.exit(0);
|
|
78
113
|
}
|
|
114
|
+
const mcpPort = config.mcpPort || 8787;
|
|
115
|
+
const localIP = getLocalIP();
|
|
116
|
+
|
|
117
|
+
const localUrl = `http://localhost:${mcpPort}/mcp`;
|
|
118
|
+
const sep = '━'.repeat(40);
|
|
119
|
+
|
|
79
120
|
console.log(`\n${bold('🔑 Auth token:')} ${cyan(token)}\n`);
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
console.log(
|
|
121
|
+
|
|
122
|
+
// Claude Code
|
|
123
|
+
console.log(`${sep}`);
|
|
124
|
+
console.log(`${bold('Claude Code')}`);
|
|
125
|
+
console.log(`${sep}`);
|
|
126
|
+
console.log(dim('一键安装:') + ` mindos mcp install claude-code -g -y`);
|
|
127
|
+
console.log(dim('\n手动配置 (~/.claude.json):'));
|
|
128
|
+
console.log(JSON.stringify({
|
|
129
|
+
mcpServers: {
|
|
130
|
+
mindos: {
|
|
131
|
+
url: localUrl,
|
|
132
|
+
headers: { Authorization: `Bearer ${token}` },
|
|
133
|
+
},
|
|
134
|
+
},
|
|
135
|
+
}, null, 2));
|
|
136
|
+
|
|
137
|
+
// CodeBuddy (Claude Code Internal)
|
|
138
|
+
console.log(`\n${sep}`);
|
|
139
|
+
console.log(`${bold('CodeBuddy (Claude Code Internal)')}`);
|
|
140
|
+
console.log(`${sep}`);
|
|
141
|
+
console.log(dim('一键安装:') + ` mindos mcp install codebuddy -g -y`);
|
|
142
|
+
console.log(dim('\n手动配置 (~/.claude-internal/.claude.json):'));
|
|
143
|
+
console.log(JSON.stringify({
|
|
144
|
+
mcpServers: {
|
|
145
|
+
mindos: {
|
|
146
|
+
url: localUrl,
|
|
147
|
+
headers: { Authorization: `Bearer ${token}` },
|
|
148
|
+
},
|
|
149
|
+
},
|
|
150
|
+
}, null, 2));
|
|
151
|
+
|
|
152
|
+
// Cursor
|
|
153
|
+
console.log(`\n${sep}`);
|
|
154
|
+
console.log(`${bold('Cursor')}`);
|
|
155
|
+
console.log(`${sep}`);
|
|
156
|
+
console.log(dim('一键安装:') + ` mindos mcp install cursor -g -y`);
|
|
157
|
+
console.log(dim('\n手动配置 (~/.cursor/mcp.json):'));
|
|
158
|
+
console.log(JSON.stringify({
|
|
159
|
+
mcpServers: {
|
|
160
|
+
mindos: {
|
|
161
|
+
url: localUrl,
|
|
162
|
+
headers: { Authorization: `Bearer ${token}` },
|
|
163
|
+
},
|
|
164
|
+
},
|
|
165
|
+
}, null, 2));
|
|
166
|
+
|
|
167
|
+
// Remote
|
|
168
|
+
if (localIP) {
|
|
169
|
+
const remoteUrl = `http://${localIP}:${mcpPort}/mcp`;
|
|
170
|
+
console.log(`\n${sep}`);
|
|
171
|
+
console.log(`${bold('Remote (其他设备)')}`);
|
|
172
|
+
console.log(`${sep}`);
|
|
173
|
+
console.log(`URL: ${cyan(remoteUrl)}`);
|
|
174
|
+
console.log(JSON.stringify({
|
|
175
|
+
mcpServers: {
|
|
176
|
+
mindos: {
|
|
177
|
+
url: remoteUrl,
|
|
178
|
+
headers: { Authorization: `Bearer ${token}` },
|
|
179
|
+
},
|
|
180
|
+
},
|
|
181
|
+
}, null, 2));
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
console.log(dim('\nRun `mindos onboard` to regenerate.\n'));
|
|
83
185
|
},
|
|
84
186
|
|
|
85
187
|
// ── dev ────────────────────────────────────────────────────────────────────
|
|
@@ -92,7 +194,12 @@ const commands = {
|
|
|
92
194
|
ensureAppDeps();
|
|
93
195
|
const mcp = spawnMcp(isVerbose);
|
|
94
196
|
savePids(process.pid, mcp.pid);
|
|
95
|
-
process.on('exit', clearPids);
|
|
197
|
+
process.on('exit', () => { stopSyncDaemon(); clearPids(); });
|
|
198
|
+
// Start sync daemon if enabled
|
|
199
|
+
const devMindRoot = process.env.MIND_ROOT;
|
|
200
|
+
if (devMindRoot) {
|
|
201
|
+
startSyncDaemon(devMindRoot).catch(() => {});
|
|
202
|
+
}
|
|
96
203
|
printStartupInfo(webPort, mcpPort);
|
|
97
204
|
run(`npx next dev -p ${webPort} ${extra}`, resolve(ROOT, 'app'));
|
|
98
205
|
},
|
|
@@ -119,6 +226,14 @@ const commands = {
|
|
|
119
226
|
process.exit(1);
|
|
120
227
|
}
|
|
121
228
|
printStartupInfo(webPort, mcpPort);
|
|
229
|
+
// System notification
|
|
230
|
+
try {
|
|
231
|
+
if (process.platform === 'darwin') {
|
|
232
|
+
execSync(`osascript -e 'display notification "http://localhost:${webPort}" with title "MindOS 已就绪"'`, { stdio: 'ignore' });
|
|
233
|
+
} else if (process.platform === 'linux') {
|
|
234
|
+
execSync(`notify-send "MindOS 已就绪" "http://localhost:${webPort}"`, { stdio: 'ignore' });
|
|
235
|
+
}
|
|
236
|
+
} catch { /* notification is best-effort */ }
|
|
122
237
|
console.log(`${green('✔ MindOS is running as a background service')}`);
|
|
123
238
|
console.log(dim(' View logs: mindos logs'));
|
|
124
239
|
console.log(dim(' Stop: mindos gateway stop'));
|
|
@@ -140,7 +255,12 @@ const commands = {
|
|
|
140
255
|
}
|
|
141
256
|
const mcp = spawnMcp(isVerbose);
|
|
142
257
|
savePids(process.pid, mcp.pid);
|
|
143
|
-
process.on('exit', clearPids);
|
|
258
|
+
process.on('exit', () => { stopSyncDaemon(); clearPids(); });
|
|
259
|
+
// Start sync daemon if enabled
|
|
260
|
+
const mindRoot = process.env.MIND_ROOT;
|
|
261
|
+
if (mindRoot) {
|
|
262
|
+
startSyncDaemon(mindRoot).catch(() => {});
|
|
263
|
+
}
|
|
144
264
|
printStartupInfo(webPort, mcpPort);
|
|
145
265
|
run(`npx next start -p ${webPort} ${extra}`, resolve(ROOT, 'app'));
|
|
146
266
|
},
|
|
@@ -325,6 +445,27 @@ ${dim('Shortcut: mindos start --daemon → install + start in one step')}
|
|
|
325
445
|
}
|
|
326
446
|
}
|
|
327
447
|
|
|
448
|
+
// 8. Sync status
|
|
449
|
+
if (config?.mindRoot) {
|
|
450
|
+
try {
|
|
451
|
+
const syncStatus = getSyncStatus(config.mindRoot);
|
|
452
|
+
if (!syncStatus.enabled) {
|
|
453
|
+
warn(`Cross-device sync is not configured ${dim('(run `mindos sync init` to set up)')}`);
|
|
454
|
+
} else if (syncStatus.lastError) {
|
|
455
|
+
err(`Sync error: ${syncStatus.lastError}`);
|
|
456
|
+
hasError = true;
|
|
457
|
+
} else if (syncStatus.conflicts && syncStatus.conflicts.length > 0) {
|
|
458
|
+
warn(`Sync has ${syncStatus.conflicts.length} unresolved conflict(s) ${dim('(run `mindos sync conflicts` to view)')}`);
|
|
459
|
+
} else {
|
|
460
|
+
const unpushed = parseInt(syncStatus.unpushed || '0', 10);
|
|
461
|
+
const extra = unpushed > 0 ? ` ${dim(`(${unpushed} unpushed commit(s))`)}` : '';
|
|
462
|
+
ok(`Sync enabled ${dim(syncStatus.remote || 'origin')}${extra}`);
|
|
463
|
+
}
|
|
464
|
+
} catch {
|
|
465
|
+
warn('Could not check sync status');
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
|
|
328
469
|
console.log(hasError
|
|
329
470
|
? `\n${red('Some checks failed.')} Run ${cyan('mindos onboard')} to reconfigure.\n`
|
|
330
471
|
: `\n${green('All checks passed.')}\n`);
|
|
@@ -521,6 +662,67 @@ ${bold('Examples:')}
|
|
|
521
662
|
${dim('mindos config set ai.provider openai')}
|
|
522
663
|
`);
|
|
523
664
|
},
|
|
665
|
+
|
|
666
|
+
// ── sync ──────────────────────────────────────────────────────────────────
|
|
667
|
+
sync: async () => {
|
|
668
|
+
const sub = process.argv[3];
|
|
669
|
+
loadConfig();
|
|
670
|
+
const mindRoot = process.env.MIND_ROOT;
|
|
671
|
+
|
|
672
|
+
if (sub === 'init') {
|
|
673
|
+
await initSync(mindRoot);
|
|
674
|
+
return;
|
|
675
|
+
}
|
|
676
|
+
|
|
677
|
+
if (sub === 'now') {
|
|
678
|
+
manualSync(mindRoot);
|
|
679
|
+
return;
|
|
680
|
+
}
|
|
681
|
+
|
|
682
|
+
if (sub === 'conflicts') {
|
|
683
|
+
listConflicts(mindRoot);
|
|
684
|
+
return;
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
if (sub === 'on') {
|
|
688
|
+
setSyncEnabled(true);
|
|
689
|
+
return;
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
if (sub === 'off') {
|
|
693
|
+
setSyncEnabled(false);
|
|
694
|
+
stopSyncDaemon();
|
|
695
|
+
return;
|
|
696
|
+
}
|
|
697
|
+
|
|
698
|
+
// default: sync status
|
|
699
|
+
const status = getSyncStatus(mindRoot);
|
|
700
|
+
if (!status.enabled) {
|
|
701
|
+
console.log(`\n${bold('🔄 Sync Status')}`);
|
|
702
|
+
console.log(dim(' Not configured. Run `mindos sync init` to set up.\n'));
|
|
703
|
+
return;
|
|
704
|
+
}
|
|
705
|
+
const ago = status.lastSync
|
|
706
|
+
? (() => {
|
|
707
|
+
const diff = Date.now() - new Date(status.lastSync).getTime();
|
|
708
|
+
if (diff < 60000) return 'just now';
|
|
709
|
+
if (diff < 3600000) return `${Math.floor(diff / 60000)} minutes ago`;
|
|
710
|
+
return `${Math.floor(diff / 3600000)} hours ago`;
|
|
711
|
+
})()
|
|
712
|
+
: 'never';
|
|
713
|
+
|
|
714
|
+
console.log(`\n${bold('🔄 Sync Status')}`);
|
|
715
|
+
console.log(` ${dim('Provider:')} ${cyan(`${status.provider} (${status.remote})`)}`);
|
|
716
|
+
console.log(` ${dim('Branch:')} ${cyan(status.branch)}`);
|
|
717
|
+
console.log(` ${dim('Last sync:')} ${ago}`);
|
|
718
|
+
console.log(` ${dim('Unpushed:')} ${status.unpushed} commits`);
|
|
719
|
+
console.log(` ${dim('Conflicts:')} ${status.conflicts.length ? yellow(`${status.conflicts.length} file(s)`) : green('none')}`);
|
|
720
|
+
console.log(` ${dim('Auto-sync:')} ${green('● enabled')} ${dim(`(commit: ${status.autoCommitInterval}s, pull: ${status.autoPullInterval / 60}min)`)}`);
|
|
721
|
+
if (status.lastError) {
|
|
722
|
+
console.log(` ${dim('Last error:')} ${red(status.lastError)}`);
|
|
723
|
+
}
|
|
724
|
+
console.log();
|
|
725
|
+
},
|
|
524
726
|
};
|
|
525
727
|
|
|
526
728
|
// ── Entry ─────────────────────────────────────────────────────────────────────
|
|
@@ -545,7 +747,9 @@ ${row('mindos restart', 'Stop then start again')}
|
|
|
545
747
|
${row('mindos build', 'Build the app for production')}
|
|
546
748
|
${row('mindos mcp', 'Start MCP server only')}
|
|
547
749
|
${row('mindos mcp install [agent]', 'Install MindOS MCP config into Agent (claude-code/cursor/windsurf/…) [-g]')}
|
|
750
|
+
${row('mindos open', 'Open Web UI in the default browser')}
|
|
548
751
|
${row('mindos token', 'Show current auth token and MCP config snippet')}
|
|
752
|
+
${row('mindos sync', 'Show sync status (init/now/conflicts/on/off)')}
|
|
549
753
|
${row('mindos gateway <subcommand>', 'Manage background service (install/uninstall/start/stop/status/logs)')}
|
|
550
754
|
${row('mindos doctor', 'Health check (config, ports, build, daemon)')}
|
|
551
755
|
${row('mindos update', 'Update MindOS to the latest version')}
|
package/bin/lib/config.js
CHANGED
|
@@ -40,8 +40,19 @@ export function loadConfig() {
|
|
|
40
40
|
|
|
41
41
|
export function getStartMode() {
|
|
42
42
|
try {
|
|
43
|
-
|
|
43
|
+
const mode = JSON.parse(readFileSync(CONFIG_PATH, 'utf-8')).startMode || 'start';
|
|
44
|
+
// 'daemon' is stored in config when user chose background service;
|
|
45
|
+
// CLI maps it to the 'start' command with --daemon flag
|
|
46
|
+
return mode === 'daemon' ? 'start' : mode;
|
|
44
47
|
} catch {
|
|
45
48
|
return 'start';
|
|
46
49
|
}
|
|
47
50
|
}
|
|
51
|
+
|
|
52
|
+
export function isDaemonMode() {
|
|
53
|
+
try {
|
|
54
|
+
return JSON.parse(readFileSync(CONFIG_PATH, 'utf-8')).startMode === 'daemon';
|
|
55
|
+
} catch {
|
|
56
|
+
return false;
|
|
57
|
+
}
|
|
58
|
+
}
|