@geminilight/mindos 0.5.7 → 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 (67) hide show
  1. package/README.md +18 -17
  2. package/README_zh.md +15 -14
  3. package/app/app/api/mcp/agents/route.ts +7 -0
  4. package/app/app/api/mcp/install-skill/route.ts +7 -1
  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 -10
  28. package/bin/lib/build.js +25 -0
  29. package/bin/lib/gateway.js +44 -4
  30. package/bin/lib/mcp-agents.js +81 -0
  31. package/bin/lib/mcp-install.js +34 -4
  32. package/package.json +13 -1
  33. package/scripts/setup.js +43 -6
  34. package/skills/project-wiki/SKILL.md +223 -0
  35. package/skills/project-wiki/assets/api-reference.tmpl.md +49 -0
  36. package/skills/project-wiki/assets/backlog.tmpl.md +15 -0
  37. package/skills/project-wiki/assets/changelog.tmpl.md +16 -0
  38. package/skills/project-wiki/assets/conventions.tmpl.md +29 -0
  39. package/skills/project-wiki/assets/design-exploration.tmpl.md +26 -0
  40. package/skills/project-wiki/assets/design-principle.tmpl.md +48 -0
  41. package/skills/project-wiki/assets/development-guide.tmpl.md +38 -0
  42. package/skills/project-wiki/assets/glossary.tmpl.md +9 -0
  43. package/skills/project-wiki/assets/known-pitfalls.tmpl.md +21 -0
  44. package/skills/project-wiki/assets/postmortem.tmpl.md +38 -0
  45. package/skills/project-wiki/assets/product-proposal.tmpl.md +41 -0
  46. package/skills/project-wiki/assets/project-roadmap.tmpl.md +23 -0
  47. package/skills/project-wiki/assets/stage-x.tmpl.md +78 -0
  48. package/skills/project-wiki/assets/system-architecture.tmpl.md +62 -0
  49. package/skills/project-wiki/references/file-reference.md +254 -0
  50. package/skills/project-wiki/references/writing-guide.md +28 -0
  51. package/app/data/pages/home-dark.png +0 -0
  52. package/app/data/pages/home-mobile-crop.png +0 -0
  53. package/app/data/pages/home-mobile.png +0 -0
  54. package/app/data/pages/home.png +0 -0
  55. package/app/data/pages/view-dir.png +0 -0
  56. package/app/data/pages/view-file-bot.png +0 -0
  57. package/app/data/pages/view-file-dark-crop.png +0 -0
  58. package/app/data/pages/view-file-dark.png +0 -0
  59. package/app/data/pages/view-file-mobile.png +0 -0
  60. package/app/data/pages/view-file-sm.png +0 -0
  61. package/app/data/pages/view-file-top.png +0 -0
  62. package/app/data/pages/view-file.png +0 -0
  63. package/app/eslint.config.mjs +0 -18
  64. package/app/public/landing/index.html +0 -353
  65. package/app/public/landing/style.css +0 -216
  66. package/app/vitest.config.ts +0 -14
  67. package/assets/demo-flow-zh.html +0 -622
package/README.md CHANGED
@@ -48,42 +48,43 @@ MindOS is a **Human-AI Collaborative Mind System**—a local-first knowledge bas
48
48
  > Help me execute the XXX SOP from MindOS.
49
49
  > ```
50
50
 
51
- ## 🧠 Core Value: Human-AI Shared Mind
51
+ ## 🧠 Human-AI Shared Mind
52
52
 
53
- **1. Global Sync Break Memory Silos**
53
+ > No more fragmented memory, no more black-box behavior, no more lost experience.
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
+ **1. Global Sync Breaking Memory Silos**
56
56
 
57
- **2. Transparent and ControllableEliminate Memory Black Boxes**
57
+ Each Agent keeps its own memory — switching tools means manually hauling context. **MindOS lets all Agents share one knowledge base via MCP and Skillsrecord once, reuse everywhere.**
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
+ **2. Transparent & Controllable No More Black Boxes**
60
+
61
+ What did your Agent remember? Is it even correct? You have no way to know. **MindOS saves every read/write as local plain text — humans can audit, correct, and delete in the GUI.**
60
62
 
61
63
  **3. Symbiotic Evolution — Experience Flows Back as Instructions**
62
64
 
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.
65
+ All that experience from your conversations gone the moment you close the window. **MindOS auto-distills conversation experience into Skills/SOPs. Notes are instructions. The knowledge base gets better with use.**
64
66
 
65
- > **Foundation:** Local-first by default - all data stays in local plain text for privacy, ownership, and speed.
67
+ > **Foundation:** Local-first by default all data stays in local plain text for privacy, ownership, and speed.
66
68
 
67
69
  ## ✨ Features
68
70
 
69
71
  **For Humans**
70
72
 
71
- - **GUI Collaboration Workbench**: use one command entry to browse, edit, and search efficiently (`⌘K` / `⌘/`).
72
- - **Built-in Agent Assistant**: converse in context while edits are captured into managed knowledge.
73
- - **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.
74
76
 
75
77
  **For Agents**
76
78
 
77
- - **MCP Server + Skills**: connect any compatible agent to read, write, search, and run workflows.
78
- - **Structured Templates**: start quickly with Profile, Workflows, and Configurations scaffolds.
79
- - **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.
80
82
 
81
83
  **Infrastructure**
82
84
 
83
- - **Reference Sync**: keep cross-file status and context aligned via links/backlinks.
84
- - **Knowledge Graph**: visualize relationships and dependencies across notes.
85
- - **Git Time Machine**: track every edit, audit history, and roll back safely.
86
- - **Cross-Device Sync**: auto-commit, push, and pull via Git — edits on one device appear on all others within minutes.
85
+ - **Security**: Bearer Token auth, path sandboxing, INSTRUCTION.md write-protection, atomic writes.
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.
87
88
 
88
89
  <details>
89
90
  <summary><strong>Coming Soon</strong></summary>
package/README_zh.md CHANGED
@@ -48,19 +48,21 @@ MindOS 是一个**人机协同心智系统**——基于本地优先的协作知
48
48
  > 帮我执行 MindOS 里的 XXX 工作流。
49
49
  > ```
50
50
 
51
- ## 🧠 核心价值:人机共享心智
51
+ ## 🧠 人机共享心智
52
+
53
+ > 记忆不再割裂,行为不再黑箱,经验不再断流。
52
54
 
53
55
  **1. 全局同步 — 打破记忆割裂**
54
56
 
55
- 传统笔记分散在不同工具和接口中,Agent 在关键时刻拿不到你的真实上下文。MindOS 把本地知识统一为 MCP 可读的单一来源,让所有 Agent 同步你的 Profile、SOP 与实时记忆。
57
+ 多个 Agent 各记各的,切换工具靠人工搬运上下文。**MindOS 通过 MCP Skill 让所有 Agent 共享同一份知识库——一处记录,全局复用。**
56
58
 
57
59
  **2. 透明可控 — 消除记忆黑箱**
58
60
 
59
- 多数助手记忆封闭在黑箱里,人类难以审查和纠正决策过程。MindOS 将检索与执行轨迹沉淀为本地纯文本,让你可以持续审计、干预与优化。
61
+ Agent 记了什么、记对没有,用户无从知晓。**MindOS 将每次读写沉淀为本地纯文本,人类可在 GUI 中审查、修正、删除。**
60
62
 
61
63
  **3. 共生演进 — 经验回流为指令**
62
64
 
63
- 静态文档难同步,也难在真实人机协作中承担执行系统角色。MindOS 让笔记天然成为 Agent 可执行的指令,通过引用链接组织知识,日常记录自然变成可执行工作流并持续进化。
65
+ 对话里攒下的经验,关掉窗口就散了。**MindOS 自动将对话经验沉淀为 Skill/SOP,笔记即指令,知识库越用越好。**
64
66
 
65
67
  > **底层原则:** 默认本地优先,全部数据以本地纯文本保存,兼顾隐私、主权与性能。
66
68
 
@@ -68,22 +70,21 @@ MindOS 是一个**人机协同心智系统**——基于本地优先的协作知
68
70
 
69
71
  **人类侧**
70
72
 
71
- - **GUI 协作工作台**:以统一入口高效浏览、编辑与搜索(`⌘K` / `⌘/`)。
72
- - **内置 Agent 助手**:在上下文中对话,编辑内容可持续沉淀为可管理知识。
73
- - **插件视图**:按场景使用 TODO、看板、时间线等视图。
73
+ - **GUI 工作台**:浏览、编辑、搜索笔记,统一搜索 + AI 入口(`⌘K` / `⌘/`),专为人机共创设计。
74
+ - **内置 Agent 助手**:在上下文中与知识库对话,编辑无缝沉淀为可管理知识。
75
+ - **插件扩展**:多种内置渲染器插件——TODO Board、CSV Views、Wiki Graph、Timeline、Agent Inspector 等。
74
76
 
75
77
  **Agent 侧**
76
78
 
77
- - **MCP Server + Skills**:让兼容 Agent 统一接入读写、搜索与工作流执行。
78
- - **结构化模板**:通过 Profile、Workflows、Configurations 快速冷启动。
79
- - **经验自动沉淀**:将日常记录自动化沉淀为可执行 SOP 经验。
79
+ - **MCP Server + Skills**:stdio + HTTP 双传输,全阵容 Agent 兼容(OpenClaw, Claude Code, Cursor 等),零配置接入。
80
+ - **结构化模板**:预置 Profile、Workflows、Configurations 等目录骨架,快速冷启动个人 Context。
81
+ - **笔记即指令**:日常笔记天然就是 Agent 可直接执行的高质量指令——无需额外格式转换,写下即可调度。
80
82
 
81
83
  **基础设施**
82
84
 
83
- - **引用同步**:通过引用与反向链接保持跨文件状态一致。
84
- - **知识图谱**:可视化笔记间关系与依赖。
85
- - **Git 时光机**:记录修改历史,支持审计与安全回滚。
86
- - **跨设备同步**:通过 Git 自动 commit、push、pull —— 一台设备的编辑几分钟内同步到所有设备。
85
+ - **安全防线**:Bearer Token 认证、路径沙箱、INSTRUCTION.md 写保护、原子写入。
86
+ - **知识图谱**:动态解析并可视化文件间的引用与依赖关系。
87
+ - **Git 时光机**:Git 自动同步(commit/push/pull),记录人类与 Agent 的每次编辑历史,一键回滚,跨设备同步。
87
88
 
88
89
  <details>
89
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 });
@@ -15,7 +15,7 @@ const UNIVERSAL_AGENTS = new Set([
15
15
  ]);
16
16
 
17
17
  // Agents that do NOT support Skills at all
18
- const SKILL_UNSUPPORTED = new Set(['claude-desktop']);
18
+ const SKILL_UNSUPPORTED = new Set<string>([]);
19
19
 
20
20
  // MCP agent key → npx skills agent name (for non-universal agents)
21
21
  const AGENT_NAME_MAP: Record<string, string> = {
@@ -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">