@geminilight/mindos 0.5.3 → 0.5.6
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 +21 -8
- package/README_zh.md +20 -7
- package/app/app/api/mcp/agents/route.ts +1 -0
- package/app/app/api/mcp/install/route.ts +62 -7
- package/app/app/api/mcp/install-skill/route.ts +127 -0
- package/app/app/api/setup/route.ts +5 -2
- package/app/app/view/[...path]/ViewPageClient.tsx +41 -27
- package/app/components/HomeContent.tsx +3 -1
- package/app/components/SetupWizard.tsx +135 -14
- package/app/components/renderers/config/manifest.ts +1 -0
- package/app/components/renderers/csv/CsvRenderer.tsx +6 -12
- package/app/components/renderers/csv/manifest.ts +1 -0
- package/app/components/renderers/graph/manifest.ts +3 -2
- package/app/components/renderers/todo/manifest.ts +1 -0
- package/app/components/settings/McpTab.tsx +85 -7
- package/app/components/settings/PluginsTab.tsx +31 -16
- package/app/lib/i18n.ts +36 -6
- package/app/lib/mcp-agents.ts +10 -9
- package/app/lib/renderers/index.ts +2 -2
- package/app/lib/renderers/registry.ts +7 -0
- package/app/lib/renderers/useRendererState.ts +114 -0
- package/app/package-lock.json +311 -2
- package/bin/cli.js +11 -1
- package/bin/lib/mcp-agents.js +9 -9
- package/bin/lib/stop.js +72 -32
- package/mcp/src/index.ts +5 -0
- package/package.json +1 -1
- package/scripts/gen-renderer-index.js +9 -2
- package/scripts/setup.js +131 -19
package/app/lib/i18n.ts
CHANGED
|
@@ -171,6 +171,7 @@ export const messages = {
|
|
|
171
171
|
enabled: 'Enabled',
|
|
172
172
|
disabled: 'Disabled',
|
|
173
173
|
matchHint: 'Auto-activates on',
|
|
174
|
+
coreHint: 'Core renderer — always enabled',
|
|
174
175
|
noPlugins: 'No renderers installed.',
|
|
175
176
|
comingSoon: 'Plugin marketplace coming soon.',
|
|
176
177
|
},
|
|
@@ -198,6 +199,7 @@ export const messages = {
|
|
|
198
199
|
notInstalled: 'Not installed',
|
|
199
200
|
transportStdio: 'stdio (recommended)',
|
|
200
201
|
transportHttp: 'http',
|
|
202
|
+
transportAuto: 'auto (recommended)',
|
|
201
203
|
httpUrl: 'MCP URL',
|
|
202
204
|
httpToken: 'Auth Token',
|
|
203
205
|
installSelected: 'Install Selected',
|
|
@@ -221,8 +223,10 @@ export const messages = {
|
|
|
221
223
|
skillContent: 'Content',
|
|
222
224
|
skillNameConflict: 'A skill with this name already exists',
|
|
223
225
|
skillDeleteConfirm: (name: string) => `Delete skill "${name}"? This cannot be undone.`,
|
|
224
|
-
|
|
225
|
-
|
|
226
|
+
skillLanguage: 'Skill Language',
|
|
227
|
+
skillLangEn: 'English',
|
|
228
|
+
skillLangZh: '中文',
|
|
229
|
+
}, save: 'Save',
|
|
226
230
|
saved: 'Saved',
|
|
227
231
|
saveFailed: 'Save failed',
|
|
228
232
|
reconfigure: 'Reconfigure',
|
|
@@ -292,7 +296,7 @@ export const messages = {
|
|
|
292
296
|
webPort: 'Web UI port',
|
|
293
297
|
mcpPort: 'MCP server port',
|
|
294
298
|
portHint: 'Valid range: 1024–65535',
|
|
295
|
-
portRestartWarning: '
|
|
299
|
+
portRestartWarning: 'The service will start on these ports after setup completes.',
|
|
296
300
|
portInUse: (p: number) => `Port ${p} is already in use.`,
|
|
297
301
|
portSuggest: (p: number) => `Use ${p}`,
|
|
298
302
|
portChecking: 'Checking…',
|
|
@@ -324,6 +328,17 @@ export const messages = {
|
|
|
324
328
|
agentStatusOk: 'configured',
|
|
325
329
|
agentStatusError: 'failed',
|
|
326
330
|
agentInstalling: 'Configuring…',
|
|
331
|
+
agentTransportAuto: 'Auto (recommended)',
|
|
332
|
+
agentTransportLabel: 'Transport',
|
|
333
|
+
agentVerified: 'verified',
|
|
334
|
+
agentUnverified: 'unverified',
|
|
335
|
+
agentVerifyNote: 'stdio agents are verified after restart',
|
|
336
|
+
// Skill auto-install
|
|
337
|
+
skillAutoHint: (name: string) => `Based on your template, the "${name}" skill will be installed to selected agents.`,
|
|
338
|
+
skillLabel: 'Skill',
|
|
339
|
+
skillInstalling: 'Installing skill…',
|
|
340
|
+
skillInstalled: 'Skill installed',
|
|
341
|
+
skillFailed: 'Skill install failed',
|
|
327
342
|
// Step 2 — AI skip card
|
|
328
343
|
aiSkipTitle: 'Skip for now',
|
|
329
344
|
aiSkipDesc: 'You can add an API key later in Settings → AI.',
|
|
@@ -525,6 +540,7 @@ export const messages = {
|
|
|
525
540
|
enabled: '已启用',
|
|
526
541
|
disabled: '已禁用',
|
|
527
542
|
matchHint: '自动匹配',
|
|
543
|
+
coreHint: '核心渲染器 — 始终启用',
|
|
528
544
|
noPlugins: '暂无渲染器。',
|
|
529
545
|
comingSoon: '插件市场即将上线。',
|
|
530
546
|
},
|
|
@@ -552,6 +568,7 @@ export const messages = {
|
|
|
552
568
|
notInstalled: '未安装',
|
|
553
569
|
transportStdio: 'stdio(推荐)',
|
|
554
570
|
transportHttp: 'http',
|
|
571
|
+
transportAuto: '自动(推荐)',
|
|
555
572
|
httpUrl: 'MCP URL',
|
|
556
573
|
httpToken: '认证 Token',
|
|
557
574
|
installSelected: '安装选中',
|
|
@@ -575,8 +592,10 @@ export const messages = {
|
|
|
575
592
|
skillContent: '内容',
|
|
576
593
|
skillNameConflict: '同名 skill 已存在',
|
|
577
594
|
skillDeleteConfirm: (name: string) => `确定删除「${name}」?此操作不可撤销。`,
|
|
578
|
-
|
|
579
|
-
|
|
595
|
+
skillLanguage: 'Skill 语言',
|
|
596
|
+
skillLangEn: 'English',
|
|
597
|
+
skillLangZh: '中文',
|
|
598
|
+
}, save: '保存',
|
|
580
599
|
saved: '已保存',
|
|
581
600
|
saveFailed: '保存失败',
|
|
582
601
|
reconfigure: '重新配置',
|
|
@@ -646,7 +665,7 @@ export const messages = {
|
|
|
646
665
|
webPort: 'Web UI 端口',
|
|
647
666
|
mcpPort: 'MCP 服务端口',
|
|
648
667
|
portHint: '有效范围:1024–65535',
|
|
649
|
-
portRestartWarning: '
|
|
668
|
+
portRestartWarning: '完成配置后,服务将以这些端口启动。',
|
|
650
669
|
portInUse: (p: number) => `端口 ${p} 已被占用。`,
|
|
651
670
|
portSuggest: (p: number) => `使用 ${p}`,
|
|
652
671
|
portChecking: '检测中…',
|
|
@@ -678,6 +697,17 @@ export const messages = {
|
|
|
678
697
|
agentStatusOk: '已配置',
|
|
679
698
|
agentStatusError: '失败',
|
|
680
699
|
agentInstalling: '配置中…',
|
|
700
|
+
agentTransportAuto: '自动(推荐)',
|
|
701
|
+
agentTransportLabel: '传输方式',
|
|
702
|
+
agentVerified: '已验证',
|
|
703
|
+
agentUnverified: '未验证',
|
|
704
|
+
agentVerifyNote: 'stdio agent 需重启后验证',
|
|
705
|
+
// Skill auto-install
|
|
706
|
+
skillAutoHint: (name: string) => `根据您选择的模板,将向选中的 Agent 安装「${name}」Skill。`,
|
|
707
|
+
skillLabel: 'Skill',
|
|
708
|
+
skillInstalling: '正在安装 Skill…',
|
|
709
|
+
skillInstalled: 'Skill 已安装',
|
|
710
|
+
skillFailed: 'Skill 安装失败',
|
|
681
711
|
// Step 2 — AI skip card
|
|
682
712
|
aiSkipTitle: '暂时跳过',
|
|
683
713
|
aiSkipDesc: '稍后可在 设置 → AI 中添加 API 密钥。',
|
package/app/lib/mcp-agents.ts
CHANGED
|
@@ -11,18 +11,19 @@ export interface AgentDef {
|
|
|
11
11
|
project: string | null;
|
|
12
12
|
global: string;
|
|
13
13
|
key: string;
|
|
14
|
+
preferredTransport: 'stdio' | 'http';
|
|
14
15
|
}
|
|
15
16
|
|
|
16
17
|
export const MCP_AGENTS: Record<string, AgentDef> = {
|
|
17
|
-
'claude-code': { name: 'Claude Code', project: '.mcp.json', global: '~/.claude.json', key: 'mcpServers' },
|
|
18
|
-
'claude-desktop': { name: 'Claude Desktop', project: null, global: process.platform === 'darwin' ? '~/Library/Application Support/Claude/claude_desktop_config.json' : '~/.config/Claude/claude_desktop_config.json', key: 'mcpServers' },
|
|
19
|
-
'cursor': { name: 'Cursor', project: '.cursor/mcp.json', global: '~/.cursor/mcp.json', key: 'mcpServers' },
|
|
20
|
-
'windsurf': { name: 'Windsurf', project: null, global: '~/.codeium/windsurf/mcp_config.json', key: 'mcpServers' },
|
|
21
|
-
'cline': { name: 'Cline', project: null, global: process.platform === 'darwin' ? '~/Library/Application Support/Code/User/globalStorage/saoudrizwan.claude-dev/settings/cline_mcp_settings.json' : '~/.config/Code/User/globalStorage/saoudrizwan.claude-dev/settings/cline_mcp_settings.json', key: 'mcpServers' },
|
|
22
|
-
'trae': { name: 'Trae', project: '.trae/mcp.json', global: '~/.trae/mcp.json', key: 'mcpServers' },
|
|
23
|
-
'gemini-cli': { name: 'Gemini CLI', project: '.gemini/settings.json', global: '~/.gemini/settings.json', key: 'mcpServers' },
|
|
24
|
-
'openclaw': { name: 'OpenClaw', project: null, global: '~/.openclaw/mcp.json', key: 'mcpServers' },
|
|
25
|
-
'codebuddy': { name: 'CodeBuddy', project: null, global: '~/.claude-internal/.claude.json', key: 'mcpServers' },
|
|
18
|
+
'claude-code': { name: 'Claude Code', project: '.mcp.json', global: '~/.claude.json', key: 'mcpServers', preferredTransport: 'stdio' },
|
|
19
|
+
'claude-desktop': { name: 'Claude Desktop', project: null, global: process.platform === 'darwin' ? '~/Library/Application Support/Claude/claude_desktop_config.json' : '~/.config/Claude/claude_desktop_config.json', key: 'mcpServers', preferredTransport: 'http' },
|
|
20
|
+
'cursor': { name: 'Cursor', project: '.cursor/mcp.json', global: '~/.cursor/mcp.json', key: 'mcpServers', preferredTransport: 'stdio' },
|
|
21
|
+
'windsurf': { name: 'Windsurf', project: null, global: '~/.codeium/windsurf/mcp_config.json', key: 'mcpServers', preferredTransport: 'stdio' },
|
|
22
|
+
'cline': { name: 'Cline', project: null, global: process.platform === 'darwin' ? '~/Library/Application Support/Code/User/globalStorage/saoudrizwan.claude-dev/settings/cline_mcp_settings.json' : '~/.config/Code/User/globalStorage/saoudrizwan.claude-dev/settings/cline_mcp_settings.json', key: 'mcpServers', preferredTransport: 'stdio' },
|
|
23
|
+
'trae': { name: 'Trae', project: '.trae/mcp.json', global: '~/.trae/mcp.json', key: 'mcpServers', preferredTransport: 'stdio' },
|
|
24
|
+
'gemini-cli': { name: 'Gemini CLI', project: '.gemini/settings.json', global: '~/.gemini/settings.json', key: 'mcpServers', preferredTransport: 'stdio' },
|
|
25
|
+
'openclaw': { name: 'OpenClaw', project: null, global: '~/.openclaw/mcp.json', key: 'mcpServers', preferredTransport: 'stdio' },
|
|
26
|
+
'codebuddy': { name: 'CodeBuddy', project: null, global: '~/.claude-internal/.claude.json', key: 'mcpServers', preferredTransport: 'stdio' },
|
|
26
27
|
};
|
|
27
28
|
|
|
28
29
|
export function detectInstalled(agentKey: string): { installed: boolean; scope?: string; transport?: string; configPath?: string } {
|
|
@@ -8,14 +8,14 @@ import { manifest as backlinks } from '@/components/renderers/backlinks/manifest
|
|
|
8
8
|
import { manifest as config } from '@/components/renderers/config/manifest';
|
|
9
9
|
import { manifest as csv } from '@/components/renderers/csv/manifest';
|
|
10
10
|
import { manifest as diff } from '@/components/renderers/diff/manifest';
|
|
11
|
-
import { manifest as graph } from '@/components/renderers/graph/manifest';
|
|
12
11
|
import { manifest as summary } from '@/components/renderers/summary/manifest';
|
|
13
12
|
import { manifest as timeline } from '@/components/renderers/timeline/manifest';
|
|
14
13
|
import { manifest as todo } from '@/components/renderers/todo/manifest';
|
|
15
14
|
import { manifest as workflow } from '@/components/renderers/workflow/manifest';
|
|
15
|
+
import { manifest as graph } from '@/components/renderers/graph/manifest';
|
|
16
16
|
|
|
17
17
|
const manifests = [
|
|
18
|
-
agentInspector, backlinks, config, csv, diff,
|
|
18
|
+
agentInspector, backlinks, config, csv, diff, summary, timeline, todo, workflow, graph,
|
|
19
19
|
];
|
|
20
20
|
|
|
21
21
|
for (const m of manifests) {
|
|
@@ -15,6 +15,7 @@ export interface RendererDefinition {
|
|
|
15
15
|
icon: string; // emoji or short string
|
|
16
16
|
tags: string[];
|
|
17
17
|
builtin: boolean; // true = ships with MindOS; false = user-installed (future)
|
|
18
|
+
core?: boolean; // true = default renderer for a file type, cannot be disabled by user
|
|
18
19
|
entryPath?: string; // canonical entry file shown on home page (e.g. 'TODO.md')
|
|
19
20
|
match: (ctx: Pick<RendererContext, 'filePath' | 'extension'>) => boolean;
|
|
20
21
|
// Provide either `component` (eager) or `load` (lazy). Prefer `load` for code-splitting.
|
|
@@ -38,6 +39,9 @@ export function loadDisabledState() {
|
|
|
38
39
|
}
|
|
39
40
|
|
|
40
41
|
export function setRendererEnabled(id: string, enabled: boolean) {
|
|
42
|
+
// Core renderers cannot be disabled
|
|
43
|
+
const def = registry.find(r => r.id === id);
|
|
44
|
+
if (def?.core) return;
|
|
41
45
|
if (enabled) {
|
|
42
46
|
_disabledIds.delete(id);
|
|
43
47
|
} else {
|
|
@@ -49,6 +53,9 @@ export function setRendererEnabled(id: string, enabled: boolean) {
|
|
|
49
53
|
}
|
|
50
54
|
|
|
51
55
|
export function isRendererEnabled(id: string): boolean {
|
|
56
|
+
// Core renderers cannot be disabled
|
|
57
|
+
const def = registry.find(r => r.id === id);
|
|
58
|
+
if (def?.core) return true;
|
|
52
59
|
return !_disabledIds.has(id);
|
|
53
60
|
}
|
|
54
61
|
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { useSyncExternalStore, useCallback, useRef } from 'react';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Unified per-file state hook for renderers.
|
|
7
|
+
*
|
|
8
|
+
* Each renderer stores its state under a namespaced localStorage key:
|
|
9
|
+
* `mindos-renderer:{rendererId}:{filePath}`
|
|
10
|
+
*
|
|
11
|
+
* Usage:
|
|
12
|
+
* const [cfg, setCfg] = useRendererState<CsvConfig>('csv', filePath, defaultCfg);
|
|
13
|
+
*
|
|
14
|
+
* The state is reactive — changes from other tabs/windows or from other
|
|
15
|
+
* components calling the setter will trigger a re-render via
|
|
16
|
+
* `useSyncExternalStore`.
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
const CHANGE_EVENT = 'mindos-renderer-state-change';
|
|
20
|
+
|
|
21
|
+
function storageKey(rendererId: string, filePath: string): string {
|
|
22
|
+
return `mindos-renderer:${rendererId}:${filePath}`;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function useRendererState<T>(
|
|
26
|
+
rendererId: string,
|
|
27
|
+
filePath: string,
|
|
28
|
+
defaultValue: T,
|
|
29
|
+
): [T, (value: T | ((prev: T) => T)) => void] {
|
|
30
|
+
const key = storageKey(rendererId, filePath);
|
|
31
|
+
|
|
32
|
+
// Cache parsed value to maintain referential stability for useSyncExternalStore.
|
|
33
|
+
// Without this, JSON.parse returns a new object on every getSnapshot call,
|
|
34
|
+
// causing Object.is to fail → infinite re-renders for non-primitive types.
|
|
35
|
+
const cacheRef = useRef<{ key: string; raw: string | null; parsed: T }>({ key, raw: null, parsed: defaultValue });
|
|
36
|
+
|
|
37
|
+
// Reset cache when key changes (different file or different renderer)
|
|
38
|
+
if (cacheRef.current.key !== key) {
|
|
39
|
+
cacheRef.current = { key, raw: null, parsed: defaultValue };
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const state = useSyncExternalStore(
|
|
43
|
+
(onStoreChange) => {
|
|
44
|
+
const listener = () => onStoreChange();
|
|
45
|
+
window.addEventListener('storage', listener);
|
|
46
|
+
window.addEventListener(CHANGE_EVENT, listener);
|
|
47
|
+
return () => {
|
|
48
|
+
window.removeEventListener('storage', listener);
|
|
49
|
+
window.removeEventListener(CHANGE_EVENT, listener);
|
|
50
|
+
};
|
|
51
|
+
},
|
|
52
|
+
() => {
|
|
53
|
+
try {
|
|
54
|
+
const raw = localStorage.getItem(key);
|
|
55
|
+
if (raw === cacheRef.current.raw) return cacheRef.current.parsed;
|
|
56
|
+
if (raw === null) {
|
|
57
|
+
cacheRef.current = { key, raw: null, parsed: defaultValue };
|
|
58
|
+
return defaultValue;
|
|
59
|
+
}
|
|
60
|
+
const parsed = JSON.parse(raw) as T;
|
|
61
|
+
cacheRef.current = { key, raw, parsed };
|
|
62
|
+
return parsed;
|
|
63
|
+
} catch {
|
|
64
|
+
return defaultValue;
|
|
65
|
+
}
|
|
66
|
+
},
|
|
67
|
+
() => defaultValue,
|
|
68
|
+
);
|
|
69
|
+
|
|
70
|
+
const setState = useCallback(
|
|
71
|
+
(value: T | ((prev: T) => T)) => {
|
|
72
|
+
try {
|
|
73
|
+
const current = (() => {
|
|
74
|
+
try {
|
|
75
|
+
const raw = localStorage.getItem(key);
|
|
76
|
+
return raw !== null ? (JSON.parse(raw) as T) : defaultValue;
|
|
77
|
+
} catch {
|
|
78
|
+
return defaultValue;
|
|
79
|
+
}
|
|
80
|
+
})();
|
|
81
|
+
const next = typeof value === 'function' ? (value as (prev: T) => T)(current) : value;
|
|
82
|
+
const serialized = JSON.stringify(next);
|
|
83
|
+
localStorage.setItem(key, serialized);
|
|
84
|
+
// Update cache eagerly so the next getSnapshot returns stable ref
|
|
85
|
+
cacheRef.current = { key, raw: serialized, parsed: next };
|
|
86
|
+
} catch { /* ignore quota errors */ }
|
|
87
|
+
window.dispatchEvent(new Event(CHANGE_EVENT));
|
|
88
|
+
},
|
|
89
|
+
[key, defaultValue],
|
|
90
|
+
);
|
|
91
|
+
|
|
92
|
+
return [state, setState];
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Non-hook helpers for reading/writing renderer state outside React components.
|
|
97
|
+
*/
|
|
98
|
+
export function getRendererState<T>(rendererId: string, filePath: string, defaultValue: T): T {
|
|
99
|
+
if (typeof window === 'undefined') return defaultValue;
|
|
100
|
+
try {
|
|
101
|
+
const raw = localStorage.getItem(storageKey(rendererId, filePath));
|
|
102
|
+
return raw !== null ? (JSON.parse(raw) as T) : defaultValue;
|
|
103
|
+
} catch {
|
|
104
|
+
return defaultValue;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
export function setRendererState<T>(rendererId: string, filePath: string, value: T): void {
|
|
109
|
+
if (typeof window === 'undefined') return;
|
|
110
|
+
try {
|
|
111
|
+
localStorage.setItem(storageKey(rendererId, filePath), JSON.stringify(value));
|
|
112
|
+
} catch { /* ignore */ }
|
|
113
|
+
window.dispatchEvent(new Event(CHANGE_EVENT));
|
|
114
|
+
}
|