@geminilight/mindos 0.5.50 → 0.5.52

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.
@@ -2,7 +2,7 @@ import { existsSync, readFileSync, writeFileSync, mkdirSync } from 'node:fs';
2
2
  import { resolve } from 'node:path';
3
3
  import { CONFIG_PATH } from './constants.js';
4
4
  import { bold, dim, cyan, green, red, yellow } from './colors.js';
5
- import { expandHome } from './utils.js';
5
+ import { expandHome, parseJsonc } from './utils.js';
6
6
  import { MCP_AGENTS, detectAgentPresence } from './mcp-agents.js';
7
7
 
8
8
  export { MCP_AGENTS };
@@ -194,7 +194,7 @@ export async function mcpInstall() {
194
194
  const abs = expandHome(cfgPath);
195
195
  if (!existsSync(abs)) continue;
196
196
  try {
197
- const config = JSON.parse(readFileSync(abs, 'utf-8'));
197
+ const config = parseJsonc(readFileSync(abs, 'utf-8'));
198
198
  if (config[agent.key]?.mindos) { installed = true; break; }
199
199
  } catch {}
200
200
  }
@@ -313,7 +313,7 @@ export async function mcpInstall() {
313
313
  const absPath = expandHome(configPath);
314
314
  let config = {};
315
315
  if (existsSync(absPath)) {
316
- try { config = JSON.parse(readFileSync(absPath, 'utf-8')); } catch {
316
+ try { config = parseJsonc(readFileSync(absPath, 'utf-8')); } catch {
317
317
  console.error(red(` Failed to parse existing config: ${absPath} — skipping.`));
318
318
  continue;
319
319
  }
package/bin/lib/utils.js CHANGED
@@ -37,3 +37,15 @@ export function npmInstall(cwd, extraFlags = '') {
37
37
  export function expandHome(p) {
38
38
  return p.startsWith('~/') ? resolve(homedir(), p.slice(2)) : p;
39
39
  }
40
+
41
+ /**
42
+ * Parse JSONC — strips single-line and block comments before JSON.parse.
43
+ * VS Code-based editors (Cursor, Windsurf, Cline) use JSONC for config files.
44
+ */
45
+ export function parseJsonc(text) {
46
+ // Strip single-line comments (not inside quoted strings)
47
+ let stripped = text.replace(/\\"|"(?:\\"|[^"])*"|(\/\/.*$)/gm, (m, g) => g ? '' : m);
48
+ // Strip block comments
49
+ stripped = stripped.replace(/\/\*[\s\S]*?\*\//g, '');
50
+ return JSON.parse(stripped);
51
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@geminilight/mindos",
3
- "version": "0.5.50",
3
+ "version": "0.5.52",
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",
package/scripts/setup.js CHANGED
@@ -598,6 +598,13 @@ function expandHomePath(p) {
598
598
  return p.startsWith('~/') ? resolve(homedir(), p.slice(2)) : p;
599
599
  }
600
600
 
601
+ /** Parse JSONC (JSON with // and /* */ comments) — for VS Code-based editor configs */
602
+ function parseJsonc(text) {
603
+ let stripped = text.replace(/\\"|"(?:\\"|[^"])*"|(\/\/.*$)/gm, (m, g) => g ? '' : m);
604
+ stripped = stripped.replace(/\/\*[\s\S]*?\*\//g, '');
605
+ return JSON.parse(stripped);
606
+ }
607
+
601
608
  /** Detect if an agent already has mindos configured (for pre-selection). */
602
609
  function isAgentInstalled(agentKey) {
603
610
  const agent = MCP_AGENTS[agentKey];
@@ -607,7 +614,7 @@ function isAgentInstalled(agentKey) {
607
614
  const abs = expandHomePath(cfgPath);
608
615
  if (!existsSync(abs)) continue;
609
616
  try {
610
- const config = JSON.parse(readFileSync(abs, 'utf-8'));
617
+ const config = parseJsonc(readFileSync(abs, 'utf-8'));
611
618
  if (config[agent.key]?.mindos) return true;
612
619
  } catch { /* ignore */ }
613
620
  }
@@ -720,7 +727,7 @@ async function runMcpInstallStep(mcpPort, authToken) {
720
727
  const abs = expandHomePath(cfgPath);
721
728
  try {
722
729
  let config = {};
723
- if (existsSync(abs)) config = JSON.parse(readFileSync(abs, 'utf-8'));
730
+ if (existsSync(abs)) config = parseJsonc(readFileSync(abs, 'utf-8'));
724
731
  if (!config[agent.key]) config[agent.key] = {};
725
732
  config[agent.key].mindos = entry;
726
733
  const dir = resolve(abs, '..');
@@ -740,7 +747,7 @@ async function runMcpInstallStep(mcpPort, authToken) {
740
747
  /* ── Skill auto-install ────────────────────────────────────────────────────── */
741
748
 
742
749
  const UNIVERSAL_AGENTS = new Set([
743
- 'amp', 'cline', 'codex', 'cursor', 'gemini-cli',
750
+ 'cline', 'codex', 'cursor', 'gemini-cli',
744
751
  'github-copilot', 'kimi-cli', 'opencode', 'warp',
745
752
  ]);
746
753
  const SKILL_UNSUPPORTED = new Set(['claude-desktop']);
@@ -1,240 +0,0 @@
1
- import { useState, useEffect, useCallback, useRef } from 'react';
2
- import { Loader2, RefreshCw, ChevronDown, ChevronRight, ExternalLink } from 'lucide-react';
3
- import { apiFetch } from '@/lib/api';
4
- import type { McpStatus, AgentInfo } from './types';
5
- import type { Messages } from '@/lib/i18n';
6
-
7
- interface AgentsTabProps {
8
- t: Messages;
9
- }
10
-
11
- export function AgentsTab({ t }: AgentsTabProps) {
12
- const [agents, setAgents] = useState<AgentInfo[]>([]);
13
- const [mcpStatus, setMcpStatus] = useState<McpStatus | null>(null);
14
- const [loading, setLoading] = useState(true);
15
- const [error, setError] = useState(false);
16
- const [refreshing, setRefreshing] = useState(false);
17
- const [showNotDetected, setShowNotDetected] = useState(false);
18
- const intervalRef = useRef<ReturnType<typeof setInterval>>(undefined);
19
-
20
- const a = t.settings?.agents as Record<string, unknown> | undefined;
21
-
22
- // i18n helpers with fallbacks
23
- const txt = (key: string, fallback: string) => (a?.[key] as string) ?? fallback;
24
- const txtFn = <T,>(key: string, fallback: (v: T) => string) =>
25
- (a?.[key] as ((v: T) => string) | undefined) ?? fallback;
26
-
27
- const fetchAll = useCallback(async (silent = false) => {
28
- if (!silent) setError(false);
29
- try {
30
- const [statusData, agentsData] = await Promise.all([
31
- apiFetch<McpStatus>('/api/mcp/status'),
32
- apiFetch<{ agents: AgentInfo[] }>('/api/mcp/agents'),
33
- ]);
34
- setMcpStatus(statusData);
35
- setAgents(agentsData.agents);
36
- setError(false);
37
- } catch {
38
- if (!silent) setError(true);
39
- }
40
- setLoading(false);
41
- setRefreshing(false);
42
- }, []);
43
-
44
- // Initial fetch + 30s auto-refresh
45
- useEffect(() => {
46
- fetchAll();
47
- intervalRef.current = setInterval(() => fetchAll(true), 30_000);
48
- return () => clearInterval(intervalRef.current);
49
- }, [fetchAll]);
50
-
51
- const handleRefresh = () => {
52
- setRefreshing(true);
53
- fetchAll();
54
- };
55
-
56
- if (loading) {
57
- return (
58
- <div className="flex justify-center py-8">
59
- <Loader2 size={18} className="animate-spin text-muted-foreground" />
60
- </div>
61
- );
62
- }
63
-
64
- if (error && agents.length === 0) {
65
- return (
66
- <div className="flex flex-col items-center gap-3 py-8 text-center">
67
- <p className="text-sm text-destructive">{txt('fetchError', 'Failed to load agent data')}</p>
68
- <button
69
- onClick={handleRefresh}
70
- className="flex items-center gap-1.5 px-3 py-1.5 text-xs rounded-lg border border-border text-muted-foreground hover:text-foreground hover:bg-muted transition-colors"
71
- >
72
- <RefreshCw size={12} />
73
- {txt('refresh', 'Refresh')}
74
- </button>
75
- </div>
76
- );
77
- }
78
-
79
- // Group agents
80
- const connected = agents.filter(a => a.present && a.installed);
81
- const detected = agents.filter(a => a.present && !a.installed);
82
- const notFound = agents.filter(a => !a.present);
83
-
84
- return (
85
- <div className="space-y-5">
86
- {/* MCP Server Status */}
87
- <div className="rounded-xl border border-border bg-card p-4 flex items-center justify-between">
88
- <div className="flex items-center gap-3">
89
- <span className="text-sm font-medium text-foreground">{txt('mcpServer', 'MCP Server')}</span>
90
- {mcpStatus?.running ? (
91
- <span className="flex items-center gap-1.5 text-xs">
92
- <span className="w-2 h-2 rounded-full bg-emerald-500 inline-block" />
93
- <span className="text-emerald-600 dark:text-emerald-400">
94
- {txt('running', 'Running')} {txtFn<number>('onPort', (p) => `on :${p}`)(mcpStatus.port)}
95
- </span>
96
- </span>
97
- ) : (
98
- <span className="flex items-center gap-1.5 text-xs">
99
- <span className="w-2 h-2 rounded-full bg-zinc-400 inline-block" />
100
- <span className="text-muted-foreground">{txt('stopped', 'Not running')}</span>
101
- </span>
102
- )}
103
- </div>
104
- <button
105
- onClick={handleRefresh}
106
- disabled={refreshing}
107
- className="flex items-center gap-1.5 px-2.5 py-1 text-xs rounded-lg border border-border text-muted-foreground hover:text-foreground hover:bg-muted disabled:opacity-50 transition-colors"
108
- >
109
- <RefreshCw size={12} className={refreshing ? 'animate-spin' : ''} />
110
- {refreshing ? txt('refreshing', 'Refreshing...') : txt('refresh', 'Refresh')}
111
- </button>
112
- </div>
113
-
114
- {/* Connected Agents */}
115
- {connected.length > 0 && (
116
- <section>
117
- <h3 className="text-xs font-medium text-muted-foreground uppercase tracking-wider mb-3">
118
- {txtFn<number>('connectedCount', (n) => `Connected (${n})`)(connected.length)}
119
- </h3>
120
- <div className="space-y-2">
121
- {connected.map(agent => (
122
- <AgentCard key={agent.key} agent={agent} status="connected" />
123
- ))}
124
- </div>
125
- </section>
126
- )}
127
-
128
- {/* Detected but not configured */}
129
- {detected.length > 0 && (
130
- <section>
131
- <h3 className="text-xs font-medium text-muted-foreground uppercase tracking-wider mb-3">
132
- {txtFn<number>('detectedCount', (n) => `Detected but not configured (${n})`)(detected.length)}
133
- </h3>
134
- <div className="space-y-2">
135
- {detected.map(agent => (
136
- <AgentCard
137
- key={agent.key}
138
- agent={agent}
139
- status="detected"
140
- connectLabel={txt('connect', 'Connect')}
141
- />
142
- ))}
143
- </div>
144
- </section>
145
- )}
146
-
147
- {/* Not Detected */}
148
- {notFound.length > 0 && (
149
- <section>
150
- <button
151
- onClick={() => setShowNotDetected(!showNotDetected)}
152
- className="flex items-center gap-1.5 text-xs font-medium text-muted-foreground uppercase tracking-wider mb-3 hover:text-foreground transition-colors"
153
- >
154
- {showNotDetected ? <ChevronDown size={14} /> : <ChevronRight size={14} />}
155
- {txtFn<number>('notDetectedCount', (n) => `Not Detected (${n})`)(notFound.length)}
156
- </button>
157
- {showNotDetected && (
158
- <div className="space-y-2">
159
- {notFound.map(agent => (
160
- <AgentCard key={agent.key} agent={agent} status="notFound" />
161
- ))}
162
- </div>
163
- )}
164
- </section>
165
- )}
166
-
167
- {/* Empty state */}
168
- {agents.length === 0 && (
169
- <p className="text-sm text-muted-foreground text-center py-4">
170
- {txt('noAgents', 'No agents detected on this machine.')}
171
- </p>
172
- )}
173
-
174
- {/* Auto-refresh hint */}
175
- <p className="text-[10px] text-muted-foreground/60 text-center">
176
- {txt('autoRefresh', 'Auto-refresh every 30s')}
177
- </p>
178
- </div>
179
- );
180
- }
181
-
182
- /* ── Agent Card ──────────────────────────────────────────────── */
183
-
184
- function AgentCard({
185
- agent,
186
- status,
187
- connectLabel,
188
- }: {
189
- agent: AgentInfo;
190
- status: 'connected' | 'detected' | 'notFound';
191
- connectLabel?: string;
192
- }) {
193
- const dot =
194
- status === 'connected' ? 'bg-emerald-500' :
195
- status === 'detected' ? 'bg-amber-500' :
196
- 'bg-zinc-400';
197
-
198
- return (
199
- <div className="rounded-lg border border-border bg-card/50 px-4 py-3 flex items-center justify-between gap-3">
200
- <div className="flex items-center gap-3 min-w-0">
201
- <span className={`w-2 h-2 rounded-full shrink-0 ${dot}`} />
202
- <span className="text-sm font-medium text-foreground truncate">{agent.name}</span>
203
- {status === 'connected' && (
204
- <div className="flex items-center gap-2 text-[11px] text-muted-foreground">
205
- <span className="px-1.5 py-0.5 rounded bg-muted">{agent.transport}</span>
206
- <span className="px-1.5 py-0.5 rounded bg-muted">{agent.scope}</span>
207
- {agent.configPath && (
208
- <span className="truncate max-w-[200px]" title={agent.configPath}>
209
- {agent.configPath.replace(/^.*[/\\]/, '')}
210
- </span>
211
- )}
212
- </div>
213
- )}
214
- </div>
215
- {status === 'detected' && (
216
- <a
217
- href="#"
218
- onClick={(e) => {
219
- e.preventDefault();
220
- // Navigate to MCP tab by dispatching a custom event
221
- const settingsModal = document.querySelector('[role="dialog"][aria-label="Settings"]');
222
- if (settingsModal) {
223
- const mcpBtn = settingsModal.querySelectorAll('button');
224
- for (const btn of mcpBtn) {
225
- if (btn.textContent?.trim() === 'MCP') {
226
- btn.click();
227
- break;
228
- }
229
- }
230
- }
231
- }}
232
- className="flex items-center gap-1 px-2.5 py-1 text-xs rounded-lg bg-amber-500/10 text-amber-600 dark:text-amber-400 hover:bg-amber-500/20 transition-colors shrink-0"
233
- >
234
- {connectLabel ?? 'Connect'}
235
- <ExternalLink size={11} />
236
- </a>
237
- )}
238
- </div>
239
- );
240
- }