@geminilight/mindos 0.5.8 → 0.5.9

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 (34) hide show
  1. package/README.md +8 -9
  2. package/README_zh.md +8 -9
  3. package/app/app/api/mcp/agents/route.ts +7 -0
  4. package/app/app/api/mcp/install-skill/route.ts +6 -0
  5. package/app/app/api/setup/check-port/route.ts +27 -3
  6. package/app/app/api/setup/route.ts +2 -9
  7. package/app/app/globals.css +18 -2
  8. package/app/app/login/page.tsx +1 -1
  9. package/app/app/view/[...path]/ViewPageClient.tsx +9 -9
  10. package/app/components/AskModal.tsx +1 -1
  11. package/app/components/FileTree.tsx +5 -5
  12. package/app/components/HomeContent.tsx +1 -1
  13. package/app/components/SetupWizard.tsx +283 -141
  14. package/app/components/SyncStatusBar.tsx +3 -3
  15. package/app/components/ask/MessageList.tsx +2 -2
  16. package/app/components/ask/SessionHistory.tsx +1 -1
  17. package/app/components/renderers/agent-inspector/AgentInspectorRenderer.tsx +5 -5
  18. package/app/components/renderers/config/ConfigRenderer.tsx +3 -3
  19. package/app/components/renderers/csv/types.ts +1 -1
  20. package/app/components/renderers/diff/DiffRenderer.tsx +9 -9
  21. package/app/components/renderers/timeline/TimelineRenderer.tsx +1 -1
  22. package/app/components/renderers/workflow/WorkflowRenderer.tsx +2 -2
  23. package/app/components/settings/McpTab.tsx +66 -24
  24. package/app/components/settings/Primitives.tsx +3 -3
  25. package/app/components/settings/SyncTab.tsx +5 -5
  26. package/app/lib/i18n.ts +48 -4
  27. package/app/lib/mcp-agents.ts +81 -0
  28. package/bin/lib/gateway.js +44 -4
  29. package/bin/lib/mcp-agents.js +81 -0
  30. package/bin/lib/mcp-install.js +34 -4
  31. package/package.json +3 -1
  32. package/scripts/setup.js +43 -6
  33. package/app/public/landing/index.html +0 -353
  34. package/app/public/landing/style.css +0 -216
package/README.md CHANGED
@@ -70,22 +70,21 @@ All that experience from your conversations — gone the moment you close the wi
70
70
 
71
71
  **For Humans**
72
72
 
73
- - **GUI Collaboration Workbench**: use one command entry to browse, edit, and search efficiently (`⌘K` / `⌘/`).
74
- - **Built-in Agent Assistant**: converse in context while edits are captured into managed knowledge.
75
- - **Plugin Views**: use scenario-focused views like TODO, Kanban, and Timeline.
73
+ - **GUI Workbench**: browse, edit, search notes with unified search + AI entry (`⌘K` / `⌘/`), designed for human-AI co-creation.
74
+ - **Built-in Agent Assistant**: converse with the knowledge base in context; edits seamlessly capture human-curated knowledge.
75
+ - **Plugin Extensions**: multiple built-in renderer plugins TODO Board, CSV Views, Wiki Graph, Timeline, Agent Inspector, and more.
76
76
 
77
77
  **For Agents**
78
78
 
79
- - **MCP Server + Skills**: connect any compatible agent to read, write, search, and run workflows.
80
- - **Structured Templates**: start quickly with Profile, Workflows, and Configurations scaffolds.
81
- - **Experience Auto-Distillation**: automatically distill daily work into reusable, executable SOP experience.
79
+ - **MCP Server + Skills**: stdio + HTTP dual transport, full-lineup Agent compatible (OpenClaw, Claude Code, Cursor, etc.). Zero-config access.
80
+ - **Structured Templates**: pre-set directory structures for Profiles, Workflows, Configurations, etc., to jumpstart personal context.
81
+ - **Agent-Ready Docs**: everyday notes naturally double as high-quality executable Agent commands — no format conversion needed, write and dispatch.
82
82
 
83
83
  **Infrastructure**
84
84
 
85
85
  - **Security**: Bearer Token auth, path sandboxing, INSTRUCTION.md write-protection, atomic writes.
86
- - **Knowledge Graph**: visualize relationships and dependencies across notes.
87
- - **Git Time Machine**: track every edit, audit history, and roll back safely.
88
- - **Cross-Device Sync**: auto-commit, push, and pull via Git — edits on one device appear on all others within minutes.
86
+ - **Knowledge Graph**: dynamically parses and visualizes inter-file references and dependencies.
87
+ - **Git Time Machine**: Git auto-sync (commit/push/pull), records every edit by both humans and Agents. One-click rollback, cross-device sync.
89
88
 
90
89
  <details>
91
90
  <summary><strong>Coming Soon</strong></summary>
package/README_zh.md CHANGED
@@ -70,22 +70,21 @@ Agent 记了什么、记对没有,用户无从知晓。**MindOS 将每次读
70
70
 
71
71
  **人类侧**
72
72
 
73
- - **GUI 协作工作台**:以统一入口高效浏览、编辑与搜索(`⌘K` / `⌘/`)。
74
- - **内置 Agent 助手**:在上下文中对话,编辑内容可持续沉淀为可管理知识。
75
- - **插件视图**:按场景使用 TODO、看板、时间线等视图。
73
+ - **GUI 工作台**:浏览、编辑、搜索笔记,统一搜索 + AI 入口(`⌘K` / `⌘/`),专为人机共创设计。
74
+ - **内置 Agent 助手**:在上下文中与知识库对话,编辑无缝沉淀为可管理知识。
75
+ - **插件扩展**:多种内置渲染器插件——TODO Board、CSV Views、Wiki Graph、Timeline、Agent Inspector 等。
76
76
 
77
77
  **Agent 侧**
78
78
 
79
- - **MCP Server + Skills**:让兼容 Agent 统一接入读写、搜索与工作流执行。
80
- - **结构化模板**:通过 Profile、Workflows、Configurations 快速冷启动。
81
- - **经验自动沉淀**:将日常记录自动化沉淀为可执行 SOP 经验。
79
+ - **MCP Server + Skills**:stdio + HTTP 双传输,全阵容 Agent 兼容(OpenClaw, Claude Code, Cursor 等),零配置接入。
80
+ - **结构化模板**:预置 Profile、Workflows、Configurations 等目录骨架,快速冷启动个人 Context。
81
+ - **笔记即指令**:日常笔记天然就是 Agent 可直接执行的高质量指令——无需额外格式转换,写下即可调度。
82
82
 
83
83
  **基础设施**
84
84
 
85
85
  - **安全防线**:Bearer Token 认证、路径沙箱、INSTRUCTION.md 写保护、原子写入。
86
- - **知识图谱**:可视化笔记间关系与依赖。
87
- - **Git 时光机**:记录修改历史,支持审计与安全回滚。
88
- - **跨设备同步**:通过 Git 自动 commit、push、pull —— 一台设备的编辑几分钟内同步到所有设备。
86
+ - **知识图谱**:动态解析并可视化文件间的引用与依赖关系。
87
+ - **Git 时光机**:Git 自动同步(commit/push/pull),记录人类与 Agent 的每次编辑历史,一键回滚,跨设备同步。
89
88
 
90
89
  <details>
91
90
  <summary><strong>即将到来</strong></summary>
@@ -20,6 +20,13 @@ export async function GET() {
20
20
  preferredTransport: agent.preferredTransport,
21
21
  };
22
22
  });
23
+
24
+ // Sort: installed first, then detected, then not found
25
+ agents.sort((a, b) => {
26
+ const rank = (x: typeof a) => x.installed ? 0 : x.present ? 1 : 2;
27
+ return rank(a) - rank(b);
28
+ });
29
+
23
30
  return NextResponse.json({ agents });
24
31
  } catch (err) {
25
32
  return NextResponse.json({ error: String(err) }, { status: 500 });
@@ -24,6 +24,12 @@ const AGENT_NAME_MAP: Record<string, string> = {
24
24
  'trae': 'trae',
25
25
  'openclaw': 'openclaw',
26
26
  'codebuddy': 'codebuddy',
27
+ 'iflow-cli': 'iflow-cli',
28
+ 'pi': 'pi',
29
+ 'augment': 'augment',
30
+ 'qwen-code': 'qwen-code',
31
+ 'trae-cn': 'trae-cn',
32
+ 'roo': 'roo',
27
33
  };
28
34
 
29
35
  /* ── Helpers ──────────────────────────────────────────────────── */
@@ -27,29 +27,53 @@ async function isSelfPort(port: number): Promise<boolean> {
27
27
  }
28
28
  }
29
29
 
30
- async function findFreePort(start: number): Promise<number | null> {
30
+ async function findFreePort(start: number, selfPorts: Set<number>): Promise<number | null> {
31
31
  for (let p = start; p <= 65535; p++) {
32
+ if (selfPorts.has(p)) continue;
32
33
  if (!await isPortInUse(p)) return p;
33
34
  }
34
35
  return null;
35
36
  }
36
37
 
38
+ /**
39
+ * The port this MindOS web server is actually listening on.
40
+ * Derived from the incoming request URL — always reliable, no network round-trip.
41
+ *
42
+ * Note: We intentionally do NOT read settings here. Settings contain *configured*
43
+ * ports (webPort / mcpPort), which may not actually be listening yet (e.g. during
44
+ * first onboard, or if MCP server hasn't started). Treating configured-but-not-
45
+ * listening ports as "self" would mask real conflicts.
46
+ */
47
+ function getListeningPort(req: NextRequest): number {
48
+ return parseInt(req.nextUrl.port || '0', 10);
49
+ }
50
+
37
51
  export async function POST(req: NextRequest) {
38
52
  try {
39
53
  const { port } = await req.json() as { port: number };
40
54
  if (!port || port < 1024 || port > 65535) {
41
55
  return NextResponse.json({ error: 'Invalid port' }, { status: 400 });
42
56
  }
57
+
58
+ const myPort = getListeningPort(req);
59
+
60
+ // Fast path: if checking the port we're currently listening on, skip network round-trip
61
+ if (myPort > 0 && port === myPort) {
62
+ return NextResponse.json({ available: true, isSelf: true });
63
+ }
64
+
43
65
  const inUse = await isPortInUse(port);
44
66
  if (!inUse) {
45
67
  return NextResponse.json({ available: true, isSelf: false });
46
68
  }
47
- // Port is occupied — check if it's this MindOS instance
69
+ // Port is occupied — check if it's another MindOS instance
48
70
  const self = await isSelfPort(port);
49
71
  if (self) {
50
72
  return NextResponse.json({ available: true, isSelf: true });
51
73
  }
52
- const suggestion = await findFreePort(port + 1);
74
+ const skipPorts = new Set<number>();
75
+ if (myPort > 0) skipPorts.add(myPort);
76
+ const suggestion = await findFreePort(port + 1, skipPorts);
53
77
  return NextResponse.json({ available: false, isSelf: false, suggestion });
54
78
  } catch (err) {
55
79
  return NextResponse.json({ error: String(err) }, { status: 500 });
@@ -67,17 +67,10 @@ export async function POST(req: NextRequest) {
67
67
  return NextResponse.json({ error: `Invalid MCP port: ${mcpPortNum}` }, { status: 400 });
68
68
  }
69
69
 
70
- // Apply template if mindRoot doesn't exist or is empty
70
+ // Apply template (copyRecursive has skip-existing protection)
71
71
  const dirExists = fs.existsSync(resolvedRoot);
72
- let dirEmpty = true;
73
- if (dirExists) {
74
- try {
75
- const entries = fs.readdirSync(resolvedRoot).filter(e => !e.startsWith('.'));
76
- dirEmpty = entries.length === 0;
77
- } catch { /* treat as empty */ }
78
- }
79
72
 
80
- if (template && (!dirExists || dirEmpty)) {
73
+ if (template) {
81
74
  applyTemplate(template, resolvedRoot);
82
75
  } else if (!dirExists) {
83
76
  fs.mkdirSync(resolvedRoot, { recursive: true });
@@ -26,6 +26,8 @@
26
26
  --color-input: var(--input);
27
27
  --color-border: var(--border);
28
28
  --color-destructive: var(--destructive);
29
+ --color-success: var(--success);
30
+ --color-error: var(--error);
29
31
  --color-accent-foreground: var(--accent-foreground);
30
32
  --color-accent: var(--accent);
31
33
  --color-muted-foreground: var(--muted-foreground);
@@ -69,10 +71,12 @@ body {
69
71
  --destructive: oklch(0.58 0.22 27);
70
72
  --border: rgba(28, 26, 23, 0.1);
71
73
  --input: rgba(28, 26, 23, 0.12);
72
- --ring: rgba(28, 26, 23, 0.3);
74
+ --ring: var(--amber);
73
75
  --radius: 0.5rem;
74
76
  --amber: #c8873a;
75
77
  --amber-dim: rgba(200, 135, 58, 0.12);
78
+ --success: #7aad80;
79
+ --error: #c85050;
76
80
  --sidebar: #ede9e1;
77
81
  --sidebar-foreground: #1c1a17;
78
82
  --sidebar-primary: #1c1a17;
@@ -101,9 +105,11 @@ body {
101
105
  --destructive: oklch(0.704 0.191 22.216);
102
106
  --border: rgba(232, 228, 220, 0.08);
103
107
  --input: rgba(232, 228, 220, 0.1);
104
- --ring: rgba(232, 228, 220, 0.2);
108
+ --ring: var(--amber);
105
109
  --amber: #d4954a;
106
110
  --amber-dim: rgba(212, 149, 74, 0.12);
111
+ --success: #7aad80;
112
+ --error: #c85050;
107
113
  --sidebar: #1c1a17;
108
114
  --sidebar-foreground: #e8e4dc;
109
115
  --sidebar-primary: #d4954a;
@@ -290,6 +296,16 @@ body {
290
296
  button, a { -webkit-tap-highlight-color: transparent; }
291
297
  }
292
298
 
299
+ /* Respect user's reduced-motion preference */
300
+ @media (prefers-reduced-motion: reduce) {
301
+ *, *::before, *::after {
302
+ animation-duration: 0.01ms !important;
303
+ animation-iteration-count: 1 !important;
304
+ transition-duration: 0.01ms !important;
305
+ scroll-behavior: auto !important;
306
+ }
307
+ }
308
+
293
309
  /* Global focus-visible ring for interactive elements */
294
310
  button:focus-visible,
295
311
  a:focus-visible,
@@ -90,7 +90,7 @@ function LoginForm() {
90
90
  autoFocus
91
91
  autoComplete="current-password"
92
92
  required
93
- className="w-full px-3 py-2 text-sm bg-background border border-border rounded-lg text-foreground placeholder:text-muted-foreground outline-none focus:ring-1 focus:ring-ring disabled:opacity-50"
93
+ className="w-full px-3 py-2 text-sm bg-background border border-border rounded-lg text-foreground placeholder:text-muted-foreground outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:opacity-50"
94
94
  />
95
95
  </div>
96
96
 
@@ -2,7 +2,7 @@
2
2
 
3
3
  import { useState, useTransition, useCallback, useEffect, useRef, useSyncExternalStore, useMemo, Suspense } from 'react';
4
4
  import { useRouter } from 'next/navigation';
5
- import { Edit3, Save, X, Loader2, LayoutTemplate, ArrowLeft } from 'lucide-react';
5
+ import { Edit3, Save, X, Loader2, LayoutTemplate, ArrowLeft, Share2, FileText, Code } from 'lucide-react';
6
6
  import { lazy } from 'react';
7
7
  import MarkdownView from '@/components/MarkdownView';
8
8
  import JsonView from '@/components/JsonView';
@@ -213,13 +213,13 @@ export default function ViewPageClient({
213
213
 
214
214
  <div className="flex items-center gap-1.5 md:gap-2 shrink-0">
215
215
  {saveSuccess && (
216
- <span className="text-xs flex items-center gap-1.5 font-display" style={{ color: '#7aad80' }}>
217
- <span className="w-1.5 h-1.5 rounded-full" style={{ background: '#7aad80' }} />
216
+ <span className="text-xs flex items-center gap-1.5 font-display" style={{ color: 'var(--success)' }}>
217
+ <span className="w-1.5 h-1.5 rounded-full" style={{ background: 'var(--success)' }} />
218
218
  <span className="hidden sm:inline">saved</span>
219
219
  </span>
220
220
  )}
221
221
  {saveError && (
222
- <span className="text-xs text-red-400 hidden sm:inline">{saveError}</span>
222
+ <span className="text-xs text-error hidden sm:inline">{saveError}</span>
223
223
  )}
224
224
 
225
225
  {/* Graph toggle — only for md files, hidden when graph plugin is disabled */}
@@ -233,8 +233,8 @@ export default function ViewPageClient({
233
233
  }}
234
234
  title={effectiveGraphMode ? 'Switch to document view' : 'Switch to Wiki Graph'}
235
235
  >
236
- <span>🕸️</span>
237
- <span className="hidden sm:inline">Graph</span>
236
+ {effectiveGraphMode ? <FileText size={13} /> : <Share2 size={13} />}
237
+ <span className="hidden sm:inline">{effectiveGraphMode ? 'Doc' : 'Graph'}</span>
238
238
  </button>
239
239
  )}
240
240
 
@@ -244,12 +244,12 @@ export default function ViewPageClient({
244
244
  onClick={handleToggleRaw}
245
245
  className="flex items-center gap-1.5 px-3 py-1.5 rounded-md text-xs font-medium transition-colors font-display"
246
246
  style={{
247
- background: effectiveUseRaw ? 'var(--muted)' : `${'var(--amber)'}22`,
248
- color: effectiveUseRaw ? 'var(--muted-foreground)' : 'var(--amber)',
247
+ background: effectiveUseRaw ? `${'var(--amber)'}22` : 'var(--muted)',
248
+ color: effectiveUseRaw ? 'var(--amber)' : 'var(--muted-foreground)',
249
249
  }}
250
250
  title={effectiveUseRaw ? `Switch to ${registryRenderer?.name}` : 'View raw'}
251
251
  >
252
- <LayoutTemplate size={13} />
252
+ {effectiveUseRaw ? <LayoutTemplate size={13} /> : <Code size={13} />}
253
253
  <span className="hidden sm:inline">{effectiveUseRaw ? registryRenderer.name : 'Raw'}</span>
254
254
  </button>
255
255
  )}
@@ -310,7 +310,7 @@ export default function AskModal({ open, onClose, currentFile }: AskModalProps)
310
310
  )}
311
311
 
312
312
  {upload.uploadError && (
313
- <div className="px-4 pb-1 text-xs text-red-400">{upload.uploadError}</div>
313
+ <div className="px-4 pb-1 text-xs text-error">{upload.uploadError}</div>
314
314
  )}
315
315
 
316
316
  {/* @-mention dropdown */}
@@ -65,7 +65,7 @@ function NewFileInline({ dirPath, depth, onDone }: { dirPath: string; depth: num
65
65
  className="
66
66
  flex-1 bg-muted border border-border rounded px-2 py-1
67
67
  text-xs text-foreground placeholder:text-muted-foreground
68
- focus:outline-none focus:border-blue-500/60
68
+ focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring
69
69
  "
70
70
  />
71
71
  {isPending
@@ -80,7 +80,7 @@ function NewFileInline({ dirPath, depth, onDone }: { dirPath: string; depth: num
80
80
  )
81
81
  }
82
82
  </div>
83
- {error && <p className="text-xs text-red-400 mt-0.5 px-1">{error}</p>}
83
+ {error && <p className="text-xs text-error mt-0.5 px-1">{error}</p>}
84
84
  </div>
85
85
  );
86
86
  }
@@ -161,7 +161,7 @@ function DirectoryNode({ node, depth, currentPath, onNavigate }: {
161
161
  if (e.key === 'Escape') setRenaming(false);
162
162
  }}
163
163
  onBlur={commitRename}
164
- className="w-full bg-muted border border-blue-500/60 rounded px-2 py-0.5 text-xs text-foreground focus:outline-none"
164
+ className="w-full bg-muted border border-border rounded px-2 py-0.5 text-xs text-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring"
165
165
  />
166
166
  {isPending && <Loader2 size={12} className="absolute right-3 top-1/2 -translate-y-1/2 animate-spin text-zinc-500" />}
167
167
  </div>
@@ -304,7 +304,7 @@ function FileNodeItem({ node, depth, currentPath, onNavigate }: {
304
304
  if (e.key === 'Escape') setRenaming(false);
305
305
  }}
306
306
  onBlur={commitRename}
307
- className="w-full bg-muted border border-blue-500/60 rounded px-2 py-0.5 text-xs text-foreground focus:outline-none"
307
+ className="w-full bg-muted border border-border rounded px-2 py-0.5 text-xs text-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring"
308
308
  />
309
309
  {isPending && <Loader2 size={12} className="absolute right-3 top-1/2 -translate-y-1/2 animate-spin text-zinc-500" />}
310
310
  </div>
@@ -334,7 +334,7 @@ function FileNodeItem({ node, depth, currentPath, onNavigate }: {
334
334
  <button onClick={startRename} className="p-0.5 rounded text-muted-foreground hover:text-foreground hover:bg-muted transition-colors" title={t.fileTree.rename}>
335
335
  <Pencil size={12} />
336
336
  </button>
337
- <button onClick={handleDelete} className="p-0.5 rounded text-muted-foreground hover:text-red-400 hover:bg-muted transition-colors" title={t.fileTree.delete}>
337
+ <button onClick={handleDelete} className="p-0.5 rounded text-muted-foreground hover:text-error hover:bg-muted transition-colors" title={t.fileTree.delete}>
338
338
  <Trash2 size={12} />
339
339
  </button>
340
340
  </div>
@@ -278,7 +278,7 @@ export default function HomeContent({ recent, existingFiles }: { recent: RecentF
278
278
  className="flex items-center gap-3 px-3 py-2.5 rounded-lg transition-all duration-100 group-hover:translate-x-0.5 hover:bg-muted"
279
279
  >
280
280
  {isCSV
281
- ? <Table size={13} className="shrink-0" style={{ color: '#7aad80' }} />
281
+ ? <Table size={13} className="shrink-0" style={{ color: 'var(--success)' }} />
282
282
  : <FileText size={13} className="shrink-0" style={{ color: 'var(--muted-foreground)' }} />
283
283
  }
284
284
  <div className="flex-1 min-w-0">