@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.
@@ -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
+ });
@@ -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(), '.mindos', 'my-mind');
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
+ }
@@ -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 token = '';
74
- try { token = JSON.parse(readFileSync(CONFIG_PATH, 'utf-8')).authToken || ''; } catch {}
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
- console.log(dim('Add to your Agent MCP config:'));
81
- console.log(` "headers": { "Authorization": "Bearer ${cyan(token)}" }\n`);
82
- console.log(dim('Run `mindos onboard` to regenerate.\n'));
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
- return JSON.parse(readFileSync(CONFIG_PATH, 'utf-8')).startMode || 'start';
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
+ }