@geminilight/mindos 0.5.20 → 0.5.22

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.
Files changed (46) hide show
  1. package/app/app/api/ask/route.ts +343 -178
  2. package/app/app/api/monitoring/route.ts +95 -0
  3. package/app/components/SettingsModal.tsx +58 -58
  4. package/app/components/settings/AgentsTab.tsx +240 -0
  5. package/app/components/settings/AiTab.tsx +4 -25
  6. package/app/components/settings/AppearanceTab.tsx +31 -13
  7. package/app/components/settings/KnowledgeTab.tsx +13 -28
  8. package/app/components/settings/McpAgentInstall.tsx +227 -0
  9. package/app/components/settings/McpServerStatus.tsx +172 -0
  10. package/app/components/settings/McpSkillsSection.tsx +583 -0
  11. package/app/components/settings/McpTab.tsx +17 -959
  12. package/app/components/settings/MonitoringTab.tsx +202 -0
  13. package/app/components/settings/PluginsTab.tsx +4 -27
  14. package/app/components/settings/Primitives.tsx +69 -0
  15. package/app/components/settings/ShortcutsTab.tsx +2 -4
  16. package/app/components/settings/SyncTab.tsx +8 -24
  17. package/app/components/settings/types.ts +116 -2
  18. package/app/instrumentation.ts +7 -2
  19. package/app/lib/agent/context.ts +151 -87
  20. package/app/lib/agent/index.ts +5 -3
  21. package/app/lib/agent/log.ts +1 -0
  22. package/app/lib/agent/model.ts +76 -10
  23. package/app/lib/agent/skill-rules.ts +70 -0
  24. package/app/lib/agent/stream-consumer.ts +73 -77
  25. package/app/lib/agent/to-agent-messages.ts +106 -0
  26. package/app/lib/agent/tools.ts +260 -266
  27. package/app/lib/api.ts +12 -3
  28. package/app/lib/core/csv.ts +2 -1
  29. package/app/lib/core/fs-ops.ts +7 -6
  30. package/app/lib/core/index.ts +1 -1
  31. package/app/lib/core/lines.ts +7 -6
  32. package/app/lib/core/search-index.ts +174 -0
  33. package/app/lib/core/search.ts +30 -1
  34. package/app/lib/core/security.ts +6 -3
  35. package/app/lib/errors.ts +108 -0
  36. package/app/lib/fs.ts +6 -3
  37. package/app/lib/i18n-en.ts +523 -0
  38. package/app/lib/i18n-zh.ts +548 -0
  39. package/app/lib/i18n.ts +4 -963
  40. package/app/lib/metrics.ts +81 -0
  41. package/app/next-env.d.ts +1 -1
  42. package/app/next.config.ts +1 -1
  43. package/app/package-lock.json +3258 -3093
  44. package/app/package.json +6 -3
  45. package/bin/cli.js +7 -4
  46. package/package.json +4 -1
@@ -0,0 +1,202 @@
1
+ 'use client';
2
+
3
+ import { useEffect, useState, useCallback } from 'react';
4
+ import { Activity, Cpu, Database, HardDrive, Loader2, RefreshCw, Zap } from 'lucide-react';
5
+ import { apiFetch } from '@/lib/api';
6
+ import type { Messages } from '@/lib/i18n';
7
+
8
+ interface MonitoringData {
9
+ system: {
10
+ uptimeMs: number;
11
+ memory: { heapUsed: number; heapTotal: number; rss: number };
12
+ nodeVersion: string;
13
+ };
14
+ application: {
15
+ agentRequests: number;
16
+ toolExecutions: number;
17
+ totalTokens: { input: number; output: number };
18
+ avgResponseTimeMs: number;
19
+ errors: number;
20
+ };
21
+ knowledgeBase: {
22
+ root: string;
23
+ fileCount: number;
24
+ totalSizeBytes: number;
25
+ };
26
+ mcp: {
27
+ running: boolean;
28
+ port: number;
29
+ };
30
+ }
31
+
32
+ function formatBytes(bytes: number): string {
33
+ if (bytes < 1024) return `${bytes} B`;
34
+ if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
35
+ if (bytes < 1024 * 1024 * 1024) return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
36
+ return `${(bytes / (1024 * 1024 * 1024)).toFixed(2)} GB`;
37
+ }
38
+
39
+ function formatUptime(ms: number): string {
40
+ const s = Math.floor(ms / 1000);
41
+ if (s < 60) return `${s}s`;
42
+ const m = Math.floor(s / 60);
43
+ if (m < 60) return `${m}m ${s % 60}s`;
44
+ const h = Math.floor(m / 60);
45
+ if (h < 24) return `${h}h ${m % 60}m`;
46
+ const d = Math.floor(h / 24);
47
+ return `${d}d ${h % 24}h`;
48
+ }
49
+
50
+ function ProgressBar({ value, max, className }: { value: number; max: number; className?: string }) {
51
+ const pct = max > 0 ? Math.min(100, (value / max) * 100) : 0;
52
+ return (
53
+ <div className={`h-2 w-full rounded-full bg-muted ${className ?? ''}`}>
54
+ <div
55
+ className={`h-full rounded-full transition-all duration-300 ${pct > 85 ? 'bg-destructive' : 'bg-amber-500'}`}
56
+ style={{ width: `${pct}%` }}
57
+ />
58
+ </div>
59
+ );
60
+ }
61
+
62
+ function StatCard({ label, value, sub }: { label: string; value: string | number; sub?: string }) {
63
+ return (
64
+ <div className="flex flex-col gap-0.5">
65
+ <span className="text-xs text-muted-foreground">{label}</span>
66
+ <span className="text-sm font-medium tabular-nums">{value}</span>
67
+ {sub && <span className="text-[10px] text-muted-foreground">{sub}</span>}
68
+ </div>
69
+ );
70
+ }
71
+
72
+ export interface MonitoringTabProps {
73
+ t: Messages;
74
+ }
75
+
76
+ export function MonitoringTab({ t }: MonitoringTabProps) {
77
+ const [data, setData] = useState<MonitoringData | null>(null);
78
+ const [loading, setLoading] = useState(true);
79
+ const [error, setError] = useState(false);
80
+
81
+ const mon = t.settings.monitoring;
82
+
83
+ const fetchData = useCallback(async () => {
84
+ try {
85
+ const d = await apiFetch<MonitoringData>('/api/monitoring', { timeout: 5000 });
86
+ setData(d);
87
+ setError(false);
88
+ } catch {
89
+ setError(true);
90
+ } finally {
91
+ setLoading(false);
92
+ }
93
+ }, []);
94
+
95
+ useEffect(() => {
96
+ fetchData();
97
+ const id = setInterval(fetchData, 5000);
98
+ return () => clearInterval(id);
99
+ }, [fetchData]);
100
+
101
+ if (loading && !data) {
102
+ return (
103
+ <div className="flex justify-center py-8">
104
+ <Loader2 size={18} className="animate-spin text-muted-foreground" />
105
+ </div>
106
+ );
107
+ }
108
+
109
+ if (error && !data) {
110
+ return (
111
+ <div className="text-center py-8 text-sm text-muted-foreground">
112
+ {mon.fetchError || 'Failed to load monitoring data'}
113
+ </div>
114
+ );
115
+ }
116
+
117
+ if (!data) return null;
118
+
119
+ const { system, application, knowledgeBase, mcp } = data;
120
+ const heapPct = system.memory.heapTotal > 0
121
+ ? Math.round((system.memory.heapUsed / system.memory.heapTotal) * 100)
122
+ : 0;
123
+
124
+ return (
125
+ <div className="space-y-6">
126
+ {/* System */}
127
+ <section>
128
+ <h3 className="flex items-center gap-1.5 text-xs font-semibold text-foreground mb-3">
129
+ <Cpu size={13} className="text-muted-foreground" />
130
+ {mon.system || 'System'}
131
+ </h3>
132
+ <div className="space-y-3">
133
+ <div>
134
+ <div className="flex justify-between text-xs mb-1">
135
+ <span className="text-muted-foreground">{mon.heapMemory || 'Heap Memory'}</span>
136
+ <span className="tabular-nums">{formatBytes(system.memory.heapUsed)} / {formatBytes(system.memory.heapTotal)} ({heapPct}%)</span>
137
+ </div>
138
+ <ProgressBar value={system.memory.heapUsed} max={system.memory.heapTotal} />
139
+ </div>
140
+ <div className="grid grid-cols-3 gap-4">
141
+ <StatCard label={mon.rss || 'RSS'} value={formatBytes(system.memory.rss)} />
142
+ <StatCard label={mon.uptime || 'Uptime'} value={formatUptime(system.uptimeMs)} />
143
+ <StatCard label={mon.nodeVersion || 'Node'} value={system.nodeVersion} />
144
+ </div>
145
+ </div>
146
+ </section>
147
+
148
+ {/* Application */}
149
+ <section>
150
+ <h3 className="flex items-center gap-1.5 text-xs font-semibold text-foreground mb-3">
151
+ <Zap size={13} className="text-muted-foreground" />
152
+ {mon.application || 'Application'}
153
+ </h3>
154
+ <div className="grid grid-cols-3 gap-4">
155
+ <StatCard label={mon.requests || 'Requests'} value={application.agentRequests} />
156
+ <StatCard label={mon.toolCalls || 'Tool Calls'} value={application.toolExecutions} />
157
+ <StatCard label={mon.avgResponse || 'Avg Response'} value={application.avgResponseTimeMs > 0 ? `${application.avgResponseTimeMs}ms` : '—'} />
158
+ <StatCard
159
+ label={mon.tokens || 'Tokens'}
160
+ value={`${(application.totalTokens.input + application.totalTokens.output).toLocaleString()}`}
161
+ sub={`↑${application.totalTokens.input.toLocaleString()} ↓${application.totalTokens.output.toLocaleString()}`}
162
+ />
163
+ <StatCard label={mon.errors || 'Errors'} value={application.errors} />
164
+ </div>
165
+ </section>
166
+
167
+ {/* Knowledge Base */}
168
+ <section>
169
+ <h3 className="flex items-center gap-1.5 text-xs font-semibold text-foreground mb-3">
170
+ <Database size={13} className="text-muted-foreground" />
171
+ {mon.knowledgeBase || 'Knowledge Base'}
172
+ </h3>
173
+ <div className="grid grid-cols-3 gap-4">
174
+ <StatCard label={mon.files || 'Files'} value={knowledgeBase.fileCount} />
175
+ <StatCard label={mon.totalSize || 'Total Size'} value={formatBytes(knowledgeBase.totalSizeBytes)} />
176
+ <StatCard label={mon.rootPath || 'Root'} value={knowledgeBase.root.split('/').pop() ?? knowledgeBase.root} sub={knowledgeBase.root} />
177
+ </div>
178
+ </section>
179
+
180
+ {/* MCP */}
181
+ <section>
182
+ <h3 className="flex items-center gap-1.5 text-xs font-semibold text-foreground mb-3">
183
+ <HardDrive size={13} className="text-muted-foreground" />
184
+ MCP
185
+ </h3>
186
+ <div className="grid grid-cols-3 gap-4">
187
+ <StatCard
188
+ label={mon.mcpStatus || 'Status'}
189
+ value={mcp.running ? (mon.mcpRunning || 'Running') : (mon.mcpStopped || 'Stopped')}
190
+ />
191
+ <StatCard label={mon.mcpPort || 'Port'} value={mcp.port} />
192
+ </div>
193
+ </section>
194
+
195
+ {/* Refresh indicator */}
196
+ <div className="flex items-center gap-1.5 text-[10px] text-muted-foreground">
197
+ <RefreshCw size={10} className={loading ? 'animate-spin' : ''} />
198
+ {mon.autoRefresh || 'Auto-refresh every 5s'}
199
+ </div>
200
+ </div>
201
+ );
202
+ }
@@ -2,12 +2,8 @@
2
2
 
3
3
  import { Puzzle } from 'lucide-react';
4
4
  import { getAllRenderers, setRendererEnabled } from '@/lib/renderers/registry';
5
-
6
- interface PluginsTabProps {
7
- pluginStates: Record<string, boolean>;
8
- setPluginStates: React.Dispatch<React.SetStateAction<Record<string, boolean>>>;
9
- t: any;
10
- }
5
+ import { Toggle } from './Primitives';
6
+ import type { PluginsTabProps } from './types';
11
7
 
12
8
  export function PluginsTab({ pluginStates, setPluginStates, t }: PluginsTabProps) {
13
9
  return (
@@ -58,28 +54,9 @@ export function PluginsTab({ pluginStates, setPluginStates, t }: PluginsTabProps
58
54
  </div>
59
55
 
60
56
  {isCore ? (
61
- <span
62
- className="shrink-0 w-9 h-5 rounded-full bg-amber-600 relative cursor-not-allowed opacity-60"
63
- title={t.settings.plugins.coreHint ?? 'Core renderer — always enabled'}
64
- >
65
- <span className="absolute top-[3px] left-[18px] w-3.5 h-3.5 rounded-full shadow-sm bg-white" />
66
- </span>
57
+ <Toggle checked={true} disabled />
67
58
  ) : (
68
- <button
69
- onClick={() => {
70
- const next = !enabled;
71
- setRendererEnabled(renderer.id, next);
72
- setPluginStates(s => ({ ...s, [renderer.id]: next }));
73
- }}
74
- role="switch"
75
- aria-checked={enabled}
76
- className={`shrink-0 w-9 h-5 rounded-full transition-colors relative ${enabled ? 'bg-amber-600' : 'bg-muted border border-border'}`}
77
- title={enabled ? t.settings.plugins.enabled : t.settings.plugins.disabled}
78
- >
79
- <span
80
- className={`absolute top-[3px] w-3.5 h-3.5 rounded-full shadow-sm transition-all ${enabled ? 'left-[18px] bg-white' : 'left-[3px] bg-muted-foreground/50'}`}
81
- />
82
- </button>
59
+ <Toggle checked={enabled} onChange={(next) => { setRendererEnabled(renderer.id, next); setPluginStates(s => ({ ...s, [renderer.id]: next })); }} title={enabled ? t.settings.plugins.enabled : t.settings.plugins.disabled} />
83
60
  )}
84
61
  </div>
85
62
  </div>
@@ -39,6 +39,50 @@ export function EnvBadge({ overridden }: { overridden: boolean }) {
39
39
  );
40
40
  }
41
41
 
42
+ /**
43
+ * 🟢 MINOR #6: Toggle component with aria accessibility
44
+ * @param {boolean} checked - Toggle state
45
+ * @param {function} onChange - Called when toggle state changes (if no onClick provided)
46
+ * @param {string} size - 'sm' (h-4 w-7) or 'md' (h-5 w-9)
47
+ * @param {boolean} disabled - Disable toggle
48
+ * @param {string} title - Tooltip text
49
+ * @param {function} onClick - Custom click handler (takes priority over onChange). Call onChange directly if needed.
50
+ *
51
+ * Usage:
52
+ * - Basic: `<Toggle checked={x} onChange={setX} />`
53
+ * - With custom handler: `<Toggle checked={x} onClick={(e) => { e.stopPropagation(); await save(); }} />`
54
+ * - In lists: Use `onClick` to prevent event bubbling; manually call `onChange` for state sync
55
+ */
56
+ export function Toggle({ checked, onChange, size = 'md', disabled, title, onClick }: {
57
+ checked: boolean;
58
+ onChange?: (checked: boolean) => void;
59
+ size?: 'sm' | 'md';
60
+ disabled?: boolean;
61
+ title?: string;
62
+ onClick?: (e: React.MouseEvent) => void;
63
+ }) {
64
+ const sm = size === 'sm';
65
+ return (
66
+ <button
67
+ type="button"
68
+ role="switch"
69
+ aria-checked={checked}
70
+ disabled={disabled}
71
+ title={title}
72
+ onClick={onClick ?? (() => onChange?.(!checked))}
73
+ className={`relative inline-flex shrink-0 cursor-pointer rounded-full border-2 border-transparent transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-60 ${
74
+ sm ? 'h-4 w-7' : 'h-5 w-9'
75
+ } ${checked ? 'bg-amber-500' : 'bg-muted'}`}
76
+ >
77
+ <span
78
+ className={`pointer-events-none inline-block rounded-full bg-white shadow-sm transition-transform ${
79
+ sm ? 'h-3 w-3' : 'h-4 w-4'
80
+ } ${checked ? (sm ? 'translate-x-3' : 'translate-x-4') : 'translate-x-0'}`}
81
+ />
82
+ </button>
83
+ );
84
+ }
85
+
42
86
  export function ApiKeyInput({ value, onChange, placeholder, disabled }: {
43
87
  value: string;
44
88
  onChange: (v: string) => void;
@@ -58,3 +102,28 @@ export function ApiKeyInput({ value, onChange, placeholder, disabled }: {
58
102
  />
59
103
  );
60
104
  }
105
+
106
+ /**
107
+ * 💡 SUGGESTION #10: Unified primary button primitive for amber actions
108
+ * Replaces inline `style={{ background: 'var(--amber)', color: 'var(--amber-foreground)' }}` pattern
109
+ */
110
+ export function PrimaryButton({ children, disabled, onClick, type = 'button', className = '', ...props }: {
111
+ children: React.ReactNode;
112
+ disabled?: boolean;
113
+ onClick?: () => void;
114
+ type?: 'button' | 'submit';
115
+ className?: string;
116
+ } & React.ButtonHTMLAttributes<HTMLButtonElement>) {
117
+ return (
118
+ <button
119
+ type={type}
120
+ onClick={onClick}
121
+ disabled={disabled}
122
+ className={`px-4 py-2 text-sm font-medium rounded-lg transition-colors disabled:opacity-40 disabled:cursor-not-allowed ${className}`}
123
+ style={{ background: 'var(--amber)', color: 'var(--amber-foreground)' }}
124
+ {...props}
125
+ >
126
+ {children}
127
+ </button>
128
+ );
129
+ }
@@ -1,13 +1,11 @@
1
1
  'use client';
2
2
 
3
- interface ShortcutsTabProps {
4
- t: any;
5
- }
3
+ import type { ShortcutsTabProps } from './types';
6
4
 
7
5
  export function ShortcutsTab({ t }: ShortcutsTabProps) {
8
6
  return (
9
7
  <div className="space-y-1">
10
- {t.shortcuts.map((s: { description: string; keys: string[] }, i: number) => (
8
+ {t.shortcuts.map((s: { readonly description: string; readonly keys: readonly string[] }, i: number) => (
11
9
  <div key={i} className="flex items-center justify-between py-2.5 border-b border-border last:border-0">
12
10
  <span className="text-sm text-foreground">{s.description}</span>
13
11
  <div className="flex items-center gap-1">
@@ -2,26 +2,12 @@
2
2
 
3
3
  import { useState, useEffect, useCallback } from 'react';
4
4
  import { RefreshCw, AlertCircle, CheckCircle2, Loader2, GitBranch, ExternalLink, Eye, EyeOff } from 'lucide-react';
5
- import { SectionLabel } from './Primitives';
5
+ import { SectionLabel, PrimaryButton } from './Primitives';
6
6
  import { apiFetch } from '@/lib/api';
7
+ import type { SyncStatus, SyncTabProps } from './types';
8
+ import type { Messages } from '@/lib/i18n';
7
9
 
8
- export interface SyncStatus {
9
- enabled: boolean;
10
- provider?: string;
11
- remote?: string;
12
- branch?: string;
13
- lastSync?: string | null;
14
- lastPull?: string | null;
15
- unpushed?: string;
16
- conflicts?: Array<{ file: string; time: string }>;
17
- lastError?: string | null;
18
- autoCommitInterval?: number;
19
- autoPullInterval?: number;
20
- }
21
-
22
- interface SyncTabProps {
23
- t: any;
24
- }
10
+ export { SyncStatus }; // Re-export for backward compatibility
25
11
 
26
12
  export function timeAgo(iso: string | null | undefined): string {
27
13
  if (!iso) return 'never';
@@ -40,7 +26,7 @@ function isValidGitUrl(url: string): 'https' | 'ssh' | false {
40
26
  return false;
41
27
  }
42
28
 
43
- function SyncEmptyState({ t, onInitComplete }: { t: any; onInitComplete: () => void }) {
29
+ function SyncEmptyState({ t, onInitComplete }: { t: Messages; onInitComplete: () => void }) {
44
30
  const syncT = t.settings?.sync;
45
31
 
46
32
  const [remoteUrl, setRemoteUrl] = useState('');
@@ -169,18 +155,16 @@ function SyncEmptyState({ t, onInitComplete }: { t: any; onInitComplete: () => v
169
155
  </div>
170
156
 
171
157
  {/* Connect button */}
172
- <button
173
- type="button"
158
+ <PrimaryButton
174
159
  onClick={handleConnect}
175
160
  disabled={!isValid || connecting}
176
- className="flex items-center gap-2 px-4 py-2 text-sm font-medium rounded-lg transition-colors disabled:opacity-40 disabled:cursor-not-allowed"
177
- style={{ background: 'var(--amber)', color: 'var(--amber-foreground)' }}
161
+ className="flex items-center gap-2"
178
162
  >
179
163
  {connecting && <Loader2 size={14} className="animate-spin" />}
180
164
  {connecting
181
165
  ? (syncT?.connecting ?? 'Connecting...')
182
166
  : (syncT?.connectButton ?? 'Connect & Start Sync')}
183
- </button>
167
+ </PrimaryButton>
184
168
 
185
169
  {/* Error */}
186
170
  {error && (
@@ -1,4 +1,5 @@
1
- import type { Locale } from '@/lib/i18n';
1
+ import type { Locale, Messages } from '@/lib/i18n';
2
+ import type React from 'react';
2
3
 
3
4
  export interface ProviderConfig {
4
5
  apiKey: string;
@@ -32,7 +33,7 @@ export interface SettingsData {
32
33
  envValues?: Record<string, string>;
33
34
  }
34
35
 
35
- export type Tab = 'ai' | 'appearance' | 'knowledge' | 'mcp' | 'plugins' | 'shortcuts' | 'sync';
36
+ export type Tab = 'ai' | 'appearance' | 'knowledge' | 'mcp' | 'plugins' | 'sync' | 'monitoring' | 'agents';
36
37
 
37
38
  export const CONTENT_WIDTHS = [
38
39
  { value: '680px', label: 'Narrow (680px)' },
@@ -47,3 +48,116 @@ export const FONTS = [
47
48
  { value: 'geist', label: 'Geist', style: { fontFamily: 'var(--font-geist-sans), sans-serif' } },
48
49
  { value: 'ibm-plex-mono', label: 'IBM Plex Mono (mono)', style: { fontFamily: "'IBM Plex Mono', monospace" } },
49
50
  ];
51
+
52
+ /* ── MCP Types ────────────────────────────────────────────────── */
53
+
54
+ export interface McpStatus {
55
+ running: boolean;
56
+ transport: string;
57
+ endpoint: string;
58
+ port: number;
59
+ toolCount: number;
60
+ authConfigured: boolean;
61
+ }
62
+
63
+ export interface AgentInfo {
64
+ key: string;
65
+ name: string;
66
+ present: boolean;
67
+ installed: boolean;
68
+ scope?: string;
69
+ transport?: string;
70
+ configPath?: string;
71
+ hasProjectScope: boolean;
72
+ hasGlobalScope: boolean;
73
+ preferredTransport: 'stdio' | 'http';
74
+ // Snippet generation fields
75
+ format: 'json' | 'toml';
76
+ configKey: string;
77
+ globalNestedKey?: string;
78
+ globalPath: string;
79
+ projectPath?: string | null;
80
+ }
81
+
82
+ export interface SkillInfo {
83
+ name: string;
84
+ description: string;
85
+ path: string;
86
+ source: 'builtin' | 'user';
87
+ enabled: boolean;
88
+ editable: boolean;
89
+ }
90
+
91
+ /** 🟢 MINOR #7: Moved from SyncTab.tsx for consistency */
92
+ export interface SyncStatus {
93
+ enabled: boolean;
94
+ provider?: string;
95
+ remote?: string;
96
+ branch?: string;
97
+ lastSync?: string | null;
98
+ lastPull?: string | null;
99
+ unpushed?: string;
100
+ conflicts?: Array<{ file: string; time: string }>;
101
+ lastError?: string | null;
102
+ autoCommitInterval?: number;
103
+ autoPullInterval?: number;
104
+ }
105
+
106
+ export interface McpTabProps {
107
+ t: Messages;
108
+ }
109
+
110
+ export interface AppearanceTabProps {
111
+ font: string;
112
+ setFont: (v: string) => void;
113
+ contentWidth: string;
114
+ setContentWidth: (v: string) => void;
115
+ dark: boolean;
116
+ setDark: (v: boolean) => void;
117
+ locale: Locale;
118
+ setLocale: (v: Locale) => void;
119
+ t: Messages;
120
+ }
121
+
122
+ export interface AiTabProps {
123
+ data: SettingsData;
124
+ updateAi: (patch: Partial<AiSettings>) => void;
125
+ updateAgent: (patch: Partial<AgentSettings>) => void;
126
+ t: Messages;
127
+ }
128
+
129
+ export interface KnowledgeTabProps {
130
+ data: SettingsData;
131
+ setData: React.Dispatch<React.SetStateAction<SettingsData | null>>;
132
+ t: Messages;
133
+ }
134
+
135
+ export interface PluginsTabProps {
136
+ pluginStates: Record<string, boolean>;
137
+ setPluginStates: React.Dispatch<React.SetStateAction<Record<string, boolean>>>;
138
+ t: Messages;
139
+ }
140
+
141
+ export interface SyncTabProps {
142
+ t: Messages;
143
+ }
144
+
145
+ export interface McpServerStatusProps {
146
+ status: McpStatus | null;
147
+ agents: AgentInfo[];
148
+ t: Messages;
149
+ }
150
+
151
+ export interface McpAgentInstallProps {
152
+ agents: AgentInfo[];
153
+ t: Messages;
154
+ onRefresh: () => void;
155
+ }
156
+
157
+ export interface McpSkillsSectionProps {
158
+ t: Messages;
159
+ }
160
+
161
+ export interface ShortcutsTabProps {
162
+ t: Messages;
163
+ }
@@ -7,9 +7,14 @@ export async function register() {
7
7
  const configPath = join(homedir(), '.mindos', 'config.json');
8
8
  const config = JSON.parse(readFileSync(configPath, 'utf-8'));
9
9
  if (config.sync?.enabled && config.mindRoot) {
10
- // Resolve absolute path to avoid Turbopack bundling issues
10
+ // Turbopack statically analyzes ALL forms of require/import — including
11
+ // createRequire() calls. The only way to load a runtime-computed path
12
+ // is to hide the require call inside a Function constructor, which is
13
+ // opaque to bundler static analysis.
11
14
  const syncModule = resolve(process.cwd(), '..', 'bin', 'lib', 'sync.js');
12
- const { startSyncDaemon } = await import(/* webpackIgnore: true */ syncModule);
15
+ // eslint-disable-next-line @typescript-eslint/no-implied-eval
16
+ const dynamicRequire = new Function('id', 'return require(id)') as (id: string) => any;
17
+ const { startSyncDaemon } = dynamicRequire(syncModule);
13
18
  await startSyncDaemon(config.mindRoot);
14
19
  }
15
20
  } catch {