@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 +21 -8
- package/README_zh.md +20 -7
- package/app/app/api/mcp/agents/route.ts +3 -1
- package/app/app/api/mcp/install-skill/route.ts +70 -39
- package/app/app/api/restart/route.ts +28 -4
- package/app/app/view/[...path]/ViewPageClient.tsx +37 -7
- package/app/components/HomeContent.tsx +3 -1
- package/app/components/SetupWizard.tsx +12 -5
- package/app/components/renderers/graph/manifest.ts +3 -2
- package/app/components/settings/McpTab.tsx +4 -1
- package/app/lib/i18n.ts +8 -0
- package/app/lib/mcp-agents.ts +110 -9
- package/app/lib/renderers/index.ts +2 -2
- package/app/package-lock.json +311 -2
- package/app/proxy.ts +3 -2
- package/app/vitest.config.ts +1 -1
- package/assets/images/wechat-qr.png +0 -0
- package/bin/cli.js +35 -2
- package/bin/lib/mcp-agents.js +112 -9
- package/bin/lib/stop.js +86 -33
- package/mcp/src/index.ts +5 -0
- package/package.json +1 -1
- package/scripts/gen-renderer-index.js +9 -2
- package/scripts/setup.js +33 -23
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://
|
|
18
|
-
<a href="
|
|
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
|
|
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
|
|
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 —
|
|
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
|
|
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
|
-
- **
|
|
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
|
|
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://
|
|
18
|
-
<a href="
|
|
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 是一个**人机协同心智系统**——基于本地优先的协作知识库,让你的笔记、工作流、个人上下文既对人类阅读友好,也能直接被
|
|
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
|
|
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 自动运维** —
|
|
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
|
-
/* ──
|
|
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
|
-
/* ──
|
|
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
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
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
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
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:
|
|
114
|
+
ok: false,
|
|
85
115
|
skill,
|
|
86
116
|
agents: additionalAgents,
|
|
87
|
-
cmd,
|
|
88
|
-
stdout:
|
|
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
|
-
|
|
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:
|
|
34
|
+
env: childEnv,
|
|
14
35
|
});
|
|
15
36
|
child.unref();
|
|
16
|
-
// Give a brief moment for the response to be sent before exiting
|
|
17
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
{/*
|
|
212
|
-
{
|
|
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 ${
|
|
250
|
+
title={effectiveUseRaw ? `Switch to ${registryRenderer?.name}` : 'View raw'}
|
|
221
251
|
>
|
|
222
252
|
<LayoutTemplate size={13} />
|
|
223
|
-
<span className="hidden sm:inline">{effectiveUseRaw ?
|
|
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
|
-
|
|
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,
|
|
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
|
|
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
|
|
12
|
-
|
|
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">
|
|
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: '配置中…',
|