@geminilight/mindos 0.5.5 → 0.5.7

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 CHANGED
@@ -14,11 +14,12 @@
14
14
 
15
15
  <p align="center">
16
16
  <a href="https://tianfuwang.tech/MindOS"><img src="https://img.shields.io/badge/Website-MindOS-0ea5e9.svg?style=for-the-badge" alt="Website"></a>
17
- <a href="https://deepwiki.com/GeminiLight/MindOS"><img src="https://img.shields.io/badge/DeepWiki-MindOS-blue.svg?style=for-the-badge" alt="DeepWiki"></a>
18
- <a href="LICENSE"><img src="https://img.shields.io/badge/License-MIT-blue.svg?style=for-the-badge" alt="MIT License"></a>
17
+ <a href="https://www.npmjs.com/package/@geminilight/mindos"><img src="https://img.shields.io/npm/v/@geminilight/mindos.svg?style=for-the-badge&color=f59e0b" alt="npm version"></a>
18
+ <a href="#wechat"><img src="https://img.shields.io/badge/WeChat-Group-07C160.svg?style=for-the-badge&logo=wechat&logoColor=white" alt="WeChat"></a>
19
+ <a href="LICENSE"><img src="https://img.shields.io/badge/License-MIT-6366f1.svg?style=for-the-badge" alt="MIT License"></a>
19
20
  </p>
20
21
 
21
- MindOS is a **Human-AI Collaborative Mind System**—a local-first knowledge base that ensures your notes, workflows, and personal context are both human-readable and directly executable by AI Agents. **Globally sync your mind for all agents: transparent, controllable, and evolving symbiotically.**
22
+ MindOS is a **Human-AI Collaborative Mind System**—a local-first knowledge base that ensures your notes, workflows, and personal context are both human-readable and directly executable by Agents. **One shared memory layer for all Agents — auditable, correctable, and smarter with every use.**
22
23
 
23
24
  ---
24
25
 
@@ -49,7 +50,7 @@ MindOS is a **Human-AI Collaborative Mind System**—a local-first knowledge bas
49
50
 
50
51
  ## 🧠 Core Value: Human-AI Shared Mind
51
52
 
52
- **1. Global Sync — Break Mind Silos**
53
+ **1. Global Sync — Break Memory Silos**
53
54
 
54
55
  Traditional notes are scattered across tools and APIs, so agents miss your real context when it matters. MindOS turns your local knowledge into one MCP-ready source, so every agent can sync your Profile, SOPs, and live working memory.
55
56
 
@@ -57,9 +58,9 @@ Traditional notes are scattered across tools and APIs, so agents miss your real
57
58
 
58
59
  Most assistant memory lives in black boxes, leaving humans unable to inspect or correct how decisions are made. MindOS writes retrieval and execution traces into local plain text, so you can audit, intervene, and improve continuously.
59
60
 
60
- **3. Symbiotic Evolution — Dynamic Instruction Flow**
61
+ **3. Symbiotic Evolution — Experience Flows Back as Instructions**
61
62
 
62
- Static documents are hard to synchronize and weak as execution systems in real human-agent collaboration. MindOS makes notes prompt-native and reference-linked, so daily writing naturally becomes executable workflows that evolve with you.
63
+ Static documents are hard to synchronize and weak as execution systems in real human-agent collaboration. MindOS makes notes agent-ready and reference-linked, so daily writing naturally becomes executable workflows that evolve with you.
63
64
 
64
65
  > **Foundation:** Local-first by default - all data stays in local plain text for privacy, ownership, and speed.
65
66
 
@@ -374,10 +375,10 @@ graph LR
374
375
 
375
376
  **Who is this for?**
376
377
 
377
- - **AI Independent Developer** — Store personal SOPs, tech stack preferences, and project context in MindOS. Any Agent instantly inherits your work habits.
378
+ - **Independent Developer** — Store personal SOPs, tech stack preferences, and project context in MindOS. Any Agent instantly inherits your work habits.
378
379
  - **Knowledge Worker** — Manage research materials with bi-directional links. Your AI assistant answers questions grounded in your full context, not generic knowledge.
379
380
  - **Team Collaboration** — Share a MindOS knowledge base across team members as a single source of truth. Humans and Agents read from the same playbook, keeping everyone aligned.
380
- - **Automated Agent Operations** — Write standard workflows as Prompt-Driven documents. Agents execute directly, humans audit the results.
381
+ - **Automated Agent Operations** — Write standard workflows as Agent-Ready documents. Agents execute directly, humans audit the results.
381
382
 
382
383
  ---
383
384
 
@@ -470,6 +471,18 @@ MindOS/
470
471
 
471
472
  ---
472
473
 
474
+ ## 💬 Community <a name="wechat"></a>
475
+
476
+ Join our WeChat group for early access, feedback, and AI workflow discussions:
477
+
478
+ <p align="center">
479
+ <img src="assets/images/wechat-qr.png" alt="WeChat Group QR Code" width="200" />
480
+ </p>
481
+
482
+ > Scan the QR code or ask an existing member to invite you.
483
+
484
+ ---
485
+
473
486
  ## 📄 License
474
487
 
475
488
  MIT © GeminiLight
package/README_zh.md CHANGED
@@ -14,11 +14,12 @@
14
14
 
15
15
  <p align="center">
16
16
  <a href="https://tianfuwang.tech/MindOS"><img src="https://img.shields.io/badge/Website-MindOS-0ea5e9.svg?style=for-the-badge" alt="Website"></a>
17
- <a href="https://deepwiki.com/GeminiLight/MindOS"><img src="https://img.shields.io/badge/DeepWiki-MindOS-blue.svg?style=for-the-badge" alt="DeepWiki"></a>
18
- <a href="LICENSE"><img src="https://img.shields.io/badge/License-MIT-blue.svg?style=for-the-badge" alt="MIT License"></a>
17
+ <a href="https://www.npmjs.com/package/@geminilight/mindos"><img src="https://img.shields.io/npm/v/@geminilight/mindos.svg?style=for-the-badge&color=f59e0b" alt="npm version"></a>
18
+ <a href="#wechat"><img src="https://img.shields.io/badge/WeChat-群聊-07C160.svg?style=for-the-badge&logo=wechat&logoColor=white" alt="WeChat"></a>
19
+ <a href="LICENSE"><img src="https://img.shields.io/badge/License-MIT-6366f1.svg?style=for-the-badge" alt="MIT License"></a>
19
20
  </p>
20
21
 
21
- MindOS 是一个**人机协同心智系统**——基于本地优先的协作知识库,让你的笔记、工作流、个人上下文既对人类阅读友好,也能直接被 AI Agent 调用和执行。**为所有 Agents 全局同步你的心智,透明可控,共生演进。**
22
+ MindOS 是一个**人机协同心智系统**——基于本地优先的协作知识库,让你的笔记、工作流、个人上下文既对人类阅读友好,也能直接被 Agent 调用和执行。**让所有 Agent 共享一个记忆层——可审计、可修正、越用越聪明。**
22
23
 
23
24
  ---
24
25
 
@@ -49,7 +50,7 @@ MindOS 是一个**人机协同心智系统**——基于本地优先的协作知
49
50
 
50
51
  ## 🧠 核心价值:人机共享心智
51
52
 
52
- **1. 全局同步 — 打破心智孤岛**
53
+ **1. 全局同步 — 打破记忆割裂**
53
54
 
54
55
  传统笔记分散在不同工具和接口中,Agent 在关键时刻拿不到你的真实上下文。MindOS 把本地知识统一为 MCP 可读的单一来源,让所有 Agent 同步你的 Profile、SOP 与实时记忆。
55
56
 
@@ -57,9 +58,9 @@ MindOS 是一个**人机协同心智系统**——基于本地优先的协作知
57
58
 
58
59
  多数助手记忆封闭在黑箱里,人类难以审查和纠正决策过程。MindOS 将检索与执行轨迹沉淀为本地纯文本,让你可以持续审计、干预与优化。
59
60
 
60
- **3. 共生演进 — 动态指令流转**
61
+ **3. 共生演进 — 经验回流为指令**
61
62
 
62
- 静态文档难同步,也难在真实人机协作中承担执行系统角色。MindOS Prompt-Native 与引用链接组织知识,让日常记录自然变成可执行工作流并持续进化。
63
+ 静态文档难同步,也难在真实人机协作中承担执行系统角色。MindOS 让笔记天然成为 Agent 可执行的指令,通过引用链接组织知识,日常记录自然变成可执行工作流并持续进化。
63
64
 
64
65
  > **底层原则:** 默认本地优先,全部数据以本地纯文本保存,兼顾隐私、主权与性能。
65
66
 
@@ -379,7 +380,7 @@ graph LR
379
380
  - **AI 独立开发者** — 将个人 SOP、技术栈偏好、项目上下文存入 MindOS,任何 Agent 即插即用你的工作习惯。
380
381
  - **知识工作者** — 用双链笔记管理研究资料,AI 助手基于你的完整上下文回答问题,而非泛泛而谈。
381
382
  - **团队协作** — 团队成员共享同一个 MindOS 知识库作为 Single Source of Truth,人与 Agent 读同一份剧本,保持对齐。
382
- - **Agent 自动运维** — 将标准流程写成 Prompt-Driven 文档,Agent 直接执行,人类审计结果。
383
+ - **Agent 自动运维** — 将标准流程写成笔记即指令的文档,Agent 直接执行,人类审计结果。
383
384
 
384
385
  ---
385
386
 
@@ -473,6 +474,18 @@ MindOS/
473
474
 
474
475
  ---
475
476
 
477
+ ## 💬 社区 <a name="wechat"></a>
478
+
479
+ 加入微信内测群,抢先体验、反馈建议、交流 AI 工作流:
480
+
481
+ <p align="center">
482
+ <img src="assets/images/wechat-qr.png" alt="微信群二维码" width="200" />
483
+ </p>
484
+
485
+ > 扫码加入,或请群内成员邀请你。
486
+
487
+ ---
488
+
476
489
  ## 📄 License
477
490
 
478
491
  MIT © GeminiLight
@@ -1,14 +1,16 @@
1
1
  export const dynamic = 'force-dynamic';
2
2
  import { NextResponse } from 'next/server';
3
- import { MCP_AGENTS, detectInstalled } from '@/lib/mcp-agents';
3
+ import { MCP_AGENTS, detectInstalled, detectAgentPresence } from '@/lib/mcp-agents';
4
4
 
5
5
  export async function GET() {
6
6
  try {
7
7
  const agents = Object.entries(MCP_AGENTS).map(([key, agent]) => {
8
8
  const status = detectInstalled(key);
9
+ const present = detectAgentPresence(key);
9
10
  return {
10
11
  key,
11
12
  name: agent.name,
13
+ present,
12
14
  installed: status.installed,
13
15
  scope: status.scope,
14
16
  transport: status.transport,
@@ -2,8 +2,11 @@ export const dynamic = 'force-dynamic';
2
2
  import { NextRequest, NextResponse } from 'next/server';
3
3
  import { execSync } from 'child_process';
4
4
  import path from 'path';
5
+ import fs from 'fs';
5
6
 
6
- /* ── Agent classification ──────────────────────────────────────── */
7
+ /* ── Constants ────────────────────────────────────────────────── */
8
+
9
+ const GITHUB_SOURCE = 'GeminiLight/MindOS';
7
10
 
8
11
  // Universal agents read directly from ~/.agents/skills/ — no symlink needed.
9
12
  const UNIVERSAL_AGENTS = new Set([
@@ -15,7 +18,6 @@ const UNIVERSAL_AGENTS = new Set([
15
18
  const SKILL_UNSUPPORTED = new Set(['claude-desktop']);
16
19
 
17
20
  // MCP agent key → npx skills agent name (for non-universal agents)
18
- // Keys not listed here and not in UNIVERSAL/UNSUPPORTED will use the key as-is.
19
21
  const AGENT_NAME_MAP: Record<string, string> = {
20
22
  'claude-code': 'claude-code',
21
23
  'windsurf': 'windsurf',
@@ -24,7 +26,35 @@ const AGENT_NAME_MAP: Record<string, string> = {
24
26
  'codebuddy': 'codebuddy',
25
27
  };
26
28
 
27
- /* ── POST handler ──────────────────────────────────────────────── */
29
+ /* ── Helpers ──────────────────────────────────────────────────── */
30
+
31
+ /** Fallback: find local skills directory for offline installs */
32
+ function findLocalSkillsDir(): string | null {
33
+ const candidates = [
34
+ path.resolve(process.cwd(), 'data/skills'), // app/data/skills/
35
+ path.resolve(process.cwd(), '..', 'skills'), // project-root/skills/
36
+ ];
37
+ for (const dir of candidates) {
38
+ if (fs.existsSync(dir)) return dir;
39
+ }
40
+ return null;
41
+ }
42
+
43
+ function buildCommand(
44
+ source: string,
45
+ skill: string,
46
+ additionalAgents: string[],
47
+ ): string {
48
+ // Each agent needs its own -a flag (skills CLI does NOT accept comma-separated)
49
+ const agentFlags = additionalAgents.length > 0
50
+ ? additionalAgents.map(a => `-a ${a}`).join(' ')
51
+ : '-a universal';
52
+ // Quote source if it looks like a local path (contains / or \)
53
+ const quotedSource = /[/\\]/.test(source) ? `"${source}"` : source;
54
+ return `npx skills add ${quotedSource} --skill ${skill} ${agentFlags} -g -y`;
55
+ }
56
+
57
+ /* ── POST handler ─────────────────────────────────────────────── */
28
58
 
29
59
  interface SkillInstallRequest {
30
60
  skill: 'mindos' | 'mindos-zh';
@@ -40,52 +70,53 @@ export async function POST(req: NextRequest) {
40
70
  return NextResponse.json({ error: 'Invalid skill name' }, { status: 400 });
41
71
  }
42
72
 
43
- // Source path: project root `skills/` directory
44
- const source = path.resolve(process.cwd(), 'skills');
45
-
46
- // Non-universal, skill-capable agents need explicit `-a` for symlink creation
47
73
  const additionalAgents = (agents || [])
48
74
  .filter(key => !UNIVERSAL_AGENTS.has(key) && !SKILL_UNSUPPORTED.has(key))
49
75
  .map(key => AGENT_NAME_MAP[key] || key);
50
76
 
51
- let cmd: string;
52
- if (additionalAgents.length > 0) {
53
- // Any -a command will also copy to ~/.agents/skills/ (Universal coverage)
54
- cmd = `npx skills add "${source}" -s ${skill} -a ${additionalAgents.join(',')} -g -y`;
55
- } else {
56
- // Fallback: only install to ~/.agents/skills/ for Universal agents
57
- cmd = `npx skills add "${source}" -s ${skill} -a universal -g -y`;
58
- }
77
+ // Try GitHub source first, fall back to local path
78
+ const sources = [GITHUB_SOURCE];
79
+ const localDir = findLocalSkillsDir();
80
+ if (localDir) sources.push(localDir);
81
+
82
+ let lastCmd = '';
83
+ let lastStdout = '';
84
+ let lastStderr = '';
59
85
 
60
- let stdout = '';
61
- let stderr = '';
62
- try {
63
- stdout = execSync(cmd, {
64
- encoding: 'utf-8',
65
- timeout: 30_000,
66
- env: { ...process.env, NODE_ENV: 'production' },
67
- stdio: 'pipe',
68
- });
69
- } catch (err: unknown) {
70
- const e = err as { stdout?: string; stderr?: string; message?: string };
71
- stdout = e.stdout || '';
72
- stderr = e.stderr || e.message || 'Unknown error';
73
- return NextResponse.json({
74
- ok: false,
75
- skill,
76
- agents: additionalAgents,
77
- cmd,
78
- stdout,
79
- stderr,
80
- });
86
+ for (const source of sources) {
87
+ const cmd = buildCommand(source, skill, additionalAgents);
88
+ lastCmd = cmd;
89
+ try {
90
+ lastStdout = execSync(cmd, {
91
+ encoding: 'utf-8',
92
+ timeout: 30_000,
93
+ env: { ...process.env, NODE_ENV: 'production' },
94
+ stdio: 'pipe',
95
+ });
96
+ // Success return immediately
97
+ return NextResponse.json({
98
+ ok: true,
99
+ skill,
100
+ agents: additionalAgents,
101
+ cmd,
102
+ stdout: lastStdout.trim(),
103
+ });
104
+ } catch (err: unknown) {
105
+ const e = err as { stdout?: string; stderr?: string; message?: string };
106
+ lastStdout = e.stdout || '';
107
+ lastStderr = e.stderr || e.message || 'Unknown error';
108
+ // Try next source
109
+ }
81
110
  }
82
111
 
112
+ // All sources failed
83
113
  return NextResponse.json({
84
- ok: true,
114
+ ok: false,
85
115
  skill,
86
116
  agents: additionalAgents,
87
- cmd,
88
- stdout: stdout.trim(),
117
+ cmd: lastCmd,
118
+ stdout: lastStdout,
119
+ stderr: lastStderr,
89
120
  });
90
121
  } catch (e) {
91
122
  return NextResponse.json(
@@ -7,14 +7,38 @@ export async function POST() {
7
7
  try {
8
8
  // process.cwd() is the Next.js app directory; cli.js is one level up at project root/bin/
9
9
  const cliPath = resolve(process.cwd(), '../bin/cli.js');
10
- const child = spawn(process.execPath, [cliPath, 'start'], {
10
+ // Use 'restart' (stop all → wait for ports free → start) instead of bare
11
+ // 'start' which would fail assertPortFree because the current process and
12
+ // its MCP child are still holding the ports.
13
+ //
14
+ // IMPORTANT: Strip MINDOS_* env vars so the child's loadConfig() reads
15
+ // the *updated* config file instead of inheriting stale values from this
16
+ // process. Without this, changing ports in the GUI has no effect on the
17
+ // restarted server — it would start on the old ports.
18
+ //
19
+ // Pass the current (old) ports via MINDOS_OLD_* so the restart command
20
+ // can clean up processes still listening on the previous ports.
21
+ const childEnv = { ...process.env };
22
+ const oldWebPort = childEnv.MINDOS_WEB_PORT;
23
+ const oldMcpPort = childEnv.MINDOS_MCP_PORT;
24
+ delete childEnv.MINDOS_WEB_PORT;
25
+ delete childEnv.MINDOS_MCP_PORT;
26
+ delete childEnv.MIND_ROOT;
27
+ delete childEnv.AUTH_TOKEN;
28
+ delete childEnv.WEB_PASSWORD;
29
+ if (oldWebPort) childEnv.MINDOS_OLD_WEB_PORT = oldWebPort;
30
+ if (oldMcpPort) childEnv.MINDOS_OLD_MCP_PORT = oldMcpPort;
31
+ const child = spawn(process.execPath, [cliPath, 'restart'], {
11
32
  detached: true,
12
33
  stdio: 'ignore',
13
- env: process.env,
34
+ env: childEnv,
14
35
  });
15
36
  child.unref();
16
- // Give a brief moment for the response to be sent before exiting
17
- setTimeout(() => process.exit(0), 500);
37
+ // Give a brief moment for the response to be sent before exiting.
38
+ // The spawned 'restart' command will handle stopping this process via
39
+ // stopMindos() (kill by PID + port cleanup), so process.exit here is
40
+ // just a safety net in case the parent isn't killed cleanly.
41
+ setTimeout(() => process.exit(0), 1500);
18
42
  return NextResponse.json({ ok: true });
19
43
  } catch (err) {
20
44
  return NextResponse.json({ error: String(err) }, { status: 500 });
@@ -13,7 +13,7 @@ import Breadcrumb from '@/components/Breadcrumb';
13
13
  import MarkdownEditor, { MdViewMode } from '@/components/MarkdownEditor';
14
14
  import TableOfContents from '@/components/TableOfContents';
15
15
  import FindInPage from '@/components/FindInPage';
16
- import { resolveRenderer } from '@/lib/renderers/registry';
16
+ import { resolveRenderer, isRendererEnabled } from '@/lib/renderers/registry';
17
17
  import { encodePath } from '@/lib/utils';
18
18
  import '@/lib/renderers/index'; // registers all renderers
19
19
 
@@ -47,6 +47,8 @@ export default function ViewPageClient({
47
47
  );
48
48
 
49
49
  const [useRaw, setUseRaw] = useRendererState<boolean>('_raw', filePath, false);
50
+ // Global graph mode — shared across all md files (not per-file)
51
+ const [graphMode, setGraphMode] = useRendererState<boolean>('_graphMode', '_global', false);
50
52
  const router = useRouter();
51
53
  const [editing, setEditing] = useState(initialEditing || content === '');
52
54
  const [editContent, setEditContent] = useState(content);
@@ -70,9 +72,21 @@ export default function ViewPageClient({
70
72
  setUseRaw(prev => !prev);
71
73
  }, [setUseRaw]);
72
74
 
73
- const renderer = resolveRenderer(filePath, extension);
75
+ const handleToggleGraph = useCallback(() => {
76
+ setGraphMode(prev => !prev);
77
+ }, [setGraphMode]);
78
+
79
+ const effectiveGraphMode = hydrated ? graphMode : false;
80
+
81
+ // Resolve renderer: for md files, graph mode overrides normal resolution
82
+ const registryRenderer = resolveRenderer(filePath, extension);
83
+ const graphRenderer = extension === 'md' && effectiveGraphMode
84
+ ? resolveRenderer(filePath, extension, 'graph')
85
+ : undefined;
86
+ const renderer = graphRenderer || registryRenderer;
74
87
  const isCsv = extension === 'csv';
75
- const showRenderer = !editing && !effectiveUseRaw && !!renderer;
88
+ // Graph mode overrides Raw when graph is active, always show the renderer
89
+ const showRenderer = !editing && !!renderer && (!effectiveUseRaw || !!graphRenderer);
76
90
 
77
91
  // Lazily resolve the renderer component for code-splitting
78
92
  const LazyComponent = useMemo(() => {
@@ -208,8 +222,24 @@ export default function ViewPageClient({
208
222
  <span className="text-xs text-red-400 hidden sm:inline">{saveError}</span>
209
223
  )}
210
224
 
211
- {/* Renderer toggle — only shown when a custom renderer exists */}
212
- {renderer && !editing && !isDraft && (
225
+ {/* Graph toggle — only for md files, hidden when graph plugin is disabled */}
226
+ {extension === 'md' && !editing && !isDraft && isRendererEnabled('graph') && (
227
+ <button
228
+ onClick={handleToggleGraph}
229
+ className="flex items-center gap-1.5 px-3 py-1.5 rounded-md text-xs font-medium transition-colors font-display"
230
+ style={{
231
+ background: effectiveGraphMode ? `${'var(--amber)'}22` : 'var(--muted)',
232
+ color: effectiveGraphMode ? 'var(--amber)' : 'var(--muted-foreground)',
233
+ }}
234
+ title={effectiveGraphMode ? 'Switch to document view' : 'Switch to Wiki Graph'}
235
+ >
236
+ <span>🕸️</span>
237
+ <span className="hidden sm:inline">Graph</span>
238
+ </button>
239
+ )}
240
+
241
+ {/* Renderer toggle — only shown when a custom renderer exists (excludes graph-mode override) */}
242
+ {registryRenderer && !editing && !isDraft && !graphRenderer && (
213
243
  <button
214
244
  onClick={handleToggleRaw}
215
245
  className="flex items-center gap-1.5 px-3 py-1.5 rounded-md text-xs font-medium transition-colors font-display"
@@ -217,10 +247,10 @@ export default function ViewPageClient({
217
247
  background: effectiveUseRaw ? 'var(--muted)' : `${'var(--amber)'}22`,
218
248
  color: effectiveUseRaw ? 'var(--muted-foreground)' : 'var(--amber)',
219
249
  }}
220
- title={effectiveUseRaw ? `Switch to ${renderer.name}` : 'View raw'}
250
+ title={effectiveUseRaw ? `Switch to ${registryRenderer?.name}` : 'View raw'}
221
251
  >
222
252
  <LayoutTemplate size={13} />
223
- <span className="hidden sm:inline">{effectiveUseRaw ? renderer.name : 'Raw'}</span>
253
+ <span className="hidden sm:inline">{effectiveUseRaw ? registryRenderer.name : 'Raw'}</span>
224
254
  </button>
225
255
  )}
226
256
 
@@ -62,7 +62,9 @@ export default function HomeContent({ recent, existingFiles }: { recent: RecentF
62
62
 
63
63
  const formatTime = (mtime: number) => relativeTime(mtime, t.home.relativeTime);
64
64
 
65
- const renderers = getAllRenderers();
65
+ // Only show renderers with an entryPath on the home page grid.
66
+ // Opt-in renderers (like Graph) have no entryPath and are toggled from the view toolbar.
67
+ const renderers = getAllRenderers().filter(r => r.entryPath);
66
68
 
67
69
  const lastFile = recent[0];
68
70
 
@@ -36,6 +36,7 @@ interface PortStatus {
36
36
  interface AgentEntry {
37
37
  key: string;
38
38
  name: string;
39
+ present: boolean;
39
40
  installed: boolean;
40
41
  hasProjectScope: boolean;
41
42
  hasGlobalScope: boolean;
@@ -511,7 +512,7 @@ function Step5({
511
512
  return agentTransport;
512
513
  };
513
514
 
514
- const getStatusBadge = (key: string, installed: boolean) => {
515
+ const getStatusBadge = (key: string, agent: AgentEntry) => {
515
516
  const st = agentStatuses[key];
516
517
  if (st) {
517
518
  if (st.state === 'installing') return (
@@ -533,16 +534,22 @@ function Step5({
533
534
  </span>
534
535
  );
535
536
  }
536
- if (installed) return (
537
+ if (agent.installed) return (
537
538
  <span className="text-[11px] px-1.5 py-0.5 rounded"
538
539
  style={{ background: 'rgba(34,197,94,0.12)', color: '#22c55e' }}>
539
540
  {settingsMcp.installed}
540
541
  </span>
541
542
  );
543
+ if (agent.present) return (
544
+ <span className="text-[11px] px-1.5 py-0.5 rounded"
545
+ style={{ background: 'rgba(245,158,11,0.12)', color: '#f59e0b' }}>
546
+ {s.agentDetected ?? 'detected'}
547
+ </span>
548
+ );
542
549
  return (
543
550
  <span className="text-[11px] px-1.5 py-0.5 rounded"
544
551
  style={{ background: 'rgba(100,100,120,0.1)', color: 'var(--muted-foreground)' }}>
545
- {s.agentNotInstalled}
552
+ {s.agentNotFound ?? s.agentNotInstalled}
546
553
  </span>
547
554
  );
548
555
  };
@@ -581,7 +588,7 @@ function Step5({
581
588
  style={{ background: 'rgba(100,100,120,0.08)', color: 'var(--muted-foreground)' }}>
582
589
  {getEffectiveTransport(agent)}
583
590
  </span>
584
- {getStatusBadge(agent.key, agent.installed)}
591
+ {getStatusBadge(agent.key, agent)}
585
592
  </label>
586
593
  ))}
587
594
  </div>
@@ -936,7 +943,7 @@ export default function SetupWizard() {
936
943
  if (data.agents) {
937
944
  setAgents(data.agents);
938
945
  setSelectedAgents(new Set(
939
- (data.agents as AgentEntry[]).filter(a => a.installed).map(a => a.key)
946
+ (data.agents as AgentEntry[]).filter(a => a.installed || a.present).map(a => a.key)
940
947
  ));
941
948
  }
942
949
  })
@@ -8,7 +8,8 @@ export const manifest: RendererDefinition = {
8
8
  icon: '🕸️',
9
9
  tags: ['graph', 'wiki', 'links', 'visualization'],
10
10
  builtin: true,
11
- entryPath: 'README.md',
12
- match: ({ extension }) => extension === 'md',
11
+ // No entryPath — Graph is a global toggle, not bound to a specific file.
12
+ // Graph is opt-in via a global toggle; never auto-match in the registry.
13
+ match: () => false,
13
14
  load: () => import('./GraphRenderer').then(m => ({ default: m.GraphRenderer })),
14
15
  };
@@ -22,6 +22,7 @@ interface McpStatus {
22
22
  interface AgentInfo {
23
23
  key: string;
24
24
  name: string;
25
+ present: boolean;
25
26
  installed: boolean;
26
27
  scope?: string;
27
28
  transport?: string;
@@ -231,7 +232,9 @@ function AgentInstall({ agents, t, onRefresh }: { agents: AgentInfo[]; t: any; o
231
232
  <span className="text-[10px] text-muted-foreground">{agent.scope}</span>
232
233
  </>
233
234
  ) : (
234
- <span className="text-[10px] text-muted-foreground">{m?.notInstalled ?? 'Not installed'}</span>
235
+ <span className="text-[10px] text-muted-foreground">
236
+ {agent.present ? (m?.detected ?? 'Detected') : (m?.notFound ?? 'Not found')}
237
+ </span>
235
238
  )}
236
239
  {/* Scope selector */}
237
240
  {selected.has(agent.key) && agent.hasProjectScope && agent.hasGlobalScope && (
package/app/lib/i18n.ts CHANGED
@@ -197,6 +197,8 @@ export const messages = {
197
197
  global: 'Global',
198
198
  installed: 'Installed',
199
199
  notInstalled: 'Not installed',
200
+ detected: 'Detected',
201
+ notFound: 'Not found',
200
202
  transportStdio: 'stdio (recommended)',
201
203
  transportHttp: 'http',
202
204
  transportAuto: 'auto (recommended)',
@@ -325,6 +327,8 @@ export const messages = {
325
327
  agentNoneSelected: 'No agents selected — you can configure later in Settings → MCP.',
326
328
  agentSkipLater: 'Skip — configure later',
327
329
  agentNotInstalled: 'not installed',
330
+ agentDetected: 'detected',
331
+ agentNotFound: 'not found',
328
332
  agentStatusOk: 'configured',
329
333
  agentStatusError: 'failed',
330
334
  agentInstalling: 'Configuring…',
@@ -566,6 +570,8 @@ export const messages = {
566
570
  global: '全局',
567
571
  installed: '已安装',
568
572
  notInstalled: '未安装',
573
+ detected: '已检测',
574
+ notFound: '未找到',
569
575
  transportStdio: 'stdio(推荐)',
570
576
  transportHttp: 'http',
571
577
  transportAuto: '自动(推荐)',
@@ -694,6 +700,8 @@ export const messages = {
694
700
  agentNoneSelected: '未选择 agent — 可稍后在 设置 → MCP 中配置。',
695
701
  agentSkipLater: '跳过 — 稍后配置',
696
702
  agentNotInstalled: '未安装',
703
+ agentDetected: '已检测到',
704
+ agentNotFound: '未找到',
697
705
  agentStatusOk: '已配置',
698
706
  agentStatusError: '失败',
699
707
  agentInstalling: '配置中…',