@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
@@ -22,8 +22,8 @@ export function getStatusLevel(status: SyncStatus | null, syncing: boolean): Sta
22
22
  export const DOT_COLORS: Record<StatusLevel, string> = {
23
23
  synced: 'bg-green-500',
24
24
  unpushed: 'bg-yellow-500',
25
- conflicts: 'bg-red-500', // #6 — conflicts more prominent than unpushed
26
- error: 'bg-red-500',
25
+ conflicts: 'bg-error', // #6 — conflicts more prominent than unpushed
26
+ error: 'bg-error',
27
27
  off: 'bg-muted-foreground/40',
28
28
  syncing: 'bg-blue-500',
29
29
  };
@@ -232,7 +232,7 @@ export default function SyncStatusBar({ collapsed, onOpenSyncSettings }: SyncSta
232
232
  <div className="flex items-center gap-1 shrink-0 ml-2">
233
233
  {/* #2 — sync result flash */}
234
234
  {(syncResult === 'success' || toast) && <CheckCircle2 size={12} className="text-green-500 animate-in fade-in duration-200" />}
235
- {syncResult === 'error' && <XCircle size={12} className="text-red-500 animate-in fade-in duration-200" />}
235
+ {syncResult === 'error' && <XCircle size={12} className="text-error animate-in fade-in duration-200" />}
236
236
  <button
237
237
  onClick={handleSyncNow}
238
238
  disabled={syncing}
@@ -94,8 +94,8 @@ export default function MessageList({
94
94
  {m.content}
95
95
  </div>
96
96
  ) : m.content.startsWith('__error__') ? (
97
- <div className="max-w-[85%] px-3 py-2.5 rounded-xl rounded-bl-sm border border-red-500/20 bg-red-500/8 text-sm">
98
- <div className="flex items-start gap-2 text-red-400">
97
+ <div className="max-w-[85%] px-3 py-2.5 rounded-xl rounded-bl-sm border border-error/20 bg-error/8 text-sm">
98
+ <div className="flex items-start gap-2 text-error">
99
99
  <AlertCircle size={14} className="shrink-0 mt-0.5" />
100
100
  <span className="leading-relaxed">{m.content.slice(9)}</span>
101
101
  </div>
@@ -36,7 +36,7 @@ export default function SessionHistory({ sessions, activeSessionId, onLoad, onDe
36
36
  <button
37
37
  type="button"
38
38
  onClick={() => onDelete(s.id)}
39
- className="p-1 rounded text-muted-foreground hover:text-red-400 hover:bg-muted"
39
+ className="p-1 rounded text-muted-foreground hover:text-error hover:bg-muted"
40
40
  title="Delete session"
41
41
  >
42
42
  <Trash2 size={12} />
@@ -59,8 +59,8 @@ function opKind(tool: string): OpKind {
59
59
  const KIND_STYLE: Record<OpKind, { bg: string; text: string; border: string }> = {
60
60
  read: { bg: 'rgba(138,180,216,0.10)', text: '#8ab4d8', border: 'rgba(138,180,216,0.25)' },
61
61
  write: { bg: 'rgba(200,135,58,0.10)', text: 'var(--amber)', border: 'rgba(200,135,58,0.25)' },
62
- create: { bg: 'rgba(122,173,128,0.10)', text: '#7aad80', border: 'rgba(122,173,128,0.25)' },
63
- delete: { bg: 'rgba(200,80,80,0.10)', text: '#c85050', border: 'rgba(200,80,80,0.25)' },
62
+ create: { bg: 'rgba(122,173,128,0.10)', text: 'var(--success)', border: 'rgba(122,173,128,0.25)' },
63
+ delete: { bg: 'rgba(200,80,80,0.10)', text: 'var(--error)', border: 'rgba(200,80,80,0.25)' },
64
64
  search: { bg: 'rgba(200,160,216,0.10)', text: '#c8a0d8', border: 'rgba(200,160,216,0.25)' },
65
65
  other: { bg: 'var(--muted)', text: 'var(--muted-foreground)', border: 'var(--border)' },
66
66
  };
@@ -159,8 +159,8 @@ function OpCard({ op }: { op: AgentOp }) {
159
159
  <div style={{ marginLeft: 'auto', display: 'flex', alignItems: 'center', gap: 8, flexShrink: 0 }}>
160
160
  {/* result */}
161
161
  {op.result === 'ok'
162
- ? <CheckCircle2 size={13} style={{ color: '#7aad80' }} />
163
- : <AlertCircle size={13} style={{ color: '#c85050' }} />
162
+ ? <CheckCircle2 size={13} style={{ color: 'var(--success)' }} />
163
+ : <AlertCircle size={13} style={{ color: 'var(--error)' }} />
164
164
  }
165
165
  {/* timestamp */}
166
166
  <span className="font-display" style={{ fontSize: '0.68rem', color: 'var(--muted-foreground)', opacity: 0.6 }} title={formatTs(op.ts)}>
@@ -191,7 +191,7 @@ function OpCard({ op }: { op: AgentOp }) {
191
191
  {op.message && (
192
192
  <div className="font-display" style={{ marginTop: 6, padding: '5px 9px', borderRadius: 5, fontSize: '0.72rem',
193
193
  background: op.result === 'error' ? 'rgba(200,80,80,0.08)' : 'rgba(122,173,128,0.08)',
194
- color: op.result === 'error' ? '#c85050' : '#7aad80',
194
+ color: op.result === 'error' ? 'var(--error)' : 'var(--success)',
195
195
  border: `1px solid ${op.result === 'error' ? 'rgba(200,80,80,0.2)' : 'rgba(122,173,128,0.2)'}`,
196
196
  }}>
197
197
  {op.message}
@@ -98,7 +98,7 @@ export function ConfigRenderer({ content, saveAction }: RendererContext) {
98
98
 
99
99
  if (!parsed || !data) {
100
100
  return (
101
- <div className="rounded-xl border border-border p-4 text-sm text-red-400">
101
+ <div className="rounded-xl border border-border p-4 text-sm text-error">
102
102
  CONFIG.json parse failed. Please check JSON format.
103
103
  </div>
104
104
  );
@@ -110,11 +110,11 @@ export function ConfigRenderer({ content, saveAction }: RendererContext) {
110
110
  <div className="text-xs text-muted-foreground">CONFIG Control Panel</div>
111
111
  <div className="text-xs flex items-center gap-2">
112
112
  {saving && <span className="inline-flex items-center gap-1 text-muted-foreground"><Loader2 size={12} className="animate-spin" />Saving</span>}
113
- {!saving && saved && <span className="inline-flex items-center gap-1" style={{ color: '#7aad80' }}><Check size={12} />Saved</span>}
113
+ {!saving && saved && <span className="inline-flex items-center gap-1" style={{ color: 'var(--success)' }}><Check size={12} />Saved</span>}
114
114
  </div>
115
115
  </div>
116
116
 
117
- {error && <div className="rounded-lg border border-red-500/30 bg-red-500/10 px-3 py-2 text-xs text-red-300">{error}</div>}
117
+ {error && <div className="rounded-lg border border-error/30 bg-error/10 px-3 py-2 text-xs text-error">{error}</div>}
118
118
 
119
119
  {sections.map((section) => (
120
120
  <div key={section.id} className="rounded-xl border border-border bg-card p-4">
@@ -73,7 +73,7 @@ export function serializeCSV(headers: string[], rows: string[][]) {
73
73
 
74
74
  const TAG_COLORS = [
75
75
  { bg: 'rgba(200,135,58,0.12)', text: 'var(--amber)' },
76
- { bg: 'rgba(122,173,128,0.12)', text: '#7aad80' },
76
+ { bg: 'rgba(122,173,128,0.12)', text: 'var(--success)' },
77
77
  { bg: 'rgba(138,180,216,0.12)', text: '#8ab4d8' },
78
78
  { bg: 'rgba(200,160,216,0.12)', text: '#c8a0d8' },
79
79
  { bg: 'rgba(200,96,96,0.12)', text: '#c86060' },
@@ -191,8 +191,8 @@ function DiffCard({ entry, saveAction, fullContent }: {
191
191
  </span>
192
192
 
193
193
  {/* diff stats */}
194
- <span className="font-display" style={{ fontSize: '0.7rem', color: '#7aad80', flexShrink: 0 }}>+{added}</span>
195
- <span className="font-display" style={{ fontSize: '0.7rem', color: '#c85050', flexShrink: 0 }}>−{removed}</span>
194
+ <span className="font-display" style={{ fontSize: '0.7rem', color: 'var(--success)', flexShrink: 0 }}>+{added}</span>
195
+ <span className="font-display" style={{ fontSize: '0.7rem', color: 'var(--error)', flexShrink: 0 }}>−{removed}</span>
196
196
 
197
197
  {/* tool badge */}
198
198
  <span className="font-display" style={{ fontSize: '0.65rem', padding: '1px 7px', borderRadius: 999, background: 'var(--muted)', color: 'var(--muted-foreground)', flexShrink: 0 }}>
@@ -210,20 +210,20 @@ function DiffCard({ entry, saveAction, fullContent }: {
210
210
  <button
211
211
  onClick={handleApprove}
212
212
  title="Approve this change"
213
- style={{ background: 'none', border: 'none', cursor: 'pointer', padding: 2, color: '#7aad80', display: 'flex', alignItems: 'center' }}
213
+ style={{ background: 'none', border: 'none', cursor: 'pointer', padding: 2, color: 'var(--success)', display: 'flex', alignItems: 'center' }}
214
214
  >
215
215
  <CheckCircle2 size={15} />
216
216
  </button>
217
217
  <button
218
218
  onClick={handleReject}
219
219
  title="Reject & revert this change"
220
- style={{ background: 'none', border: 'none', cursor: 'pointer', padding: 2, color: '#c85050', display: 'flex', alignItems: 'center' }}
220
+ style={{ background: 'none', border: 'none', cursor: 'pointer', padding: 2, color: 'var(--error)', display: 'flex', alignItems: 'center' }}
221
221
  >
222
222
  <XCircle size={15} />
223
223
  </button>
224
224
  </>
225
225
  ) : (
226
- <span className="font-display" style={{ fontSize: '0.68rem', color: approved ? '#7aad80' : '#c85050' }}>
226
+ <span className="font-display" style={{ fontSize: '0.68rem', color: approved ? 'var(--success)' : 'var(--error)' }}>
227
227
  {approved ? '✓ approved' : '✕ reverted'}
228
228
  </span>
229
229
  )}
@@ -252,8 +252,8 @@ function DiffCard({ entry, saveAction, fullContent }: {
252
252
  line.type === 'delete' ? 'rgba(200,80,80,0.10)' :
253
253
  'transparent';
254
254
  const color =
255
- line.type === 'insert' ? '#7aad80' :
256
- line.type === 'delete' ? '#c85050' :
255
+ line.type === 'insert' ? 'var(--success)' :
256
+ line.type === 'delete' ? 'var(--error)' :
257
257
  'var(--muted-foreground)';
258
258
  const prefix =
259
259
  line.type === 'insert' ? '+' :
@@ -299,8 +299,8 @@ export function DiffRenderer({ content, saveAction }: RendererContext) {
299
299
  {/* stats bar */}
300
300
  <div className="font-display" style={{ display: 'flex', alignItems: 'center', gap: 12, marginBottom: '1.2rem', fontSize: 11, color: 'var(--muted-foreground)' }}>
301
301
  <span>{entries.length} change{entries.length !== 1 ? 's' : ''}</span>
302
- <span style={{ color: '#7aad80' }}>+{totalAdded}</span>
303
- <span style={{ color: '#c85050' }}>−{totalRemoved}</span>
302
+ <span style={{ color: 'var(--success)' }}>+{totalAdded}</span>
303
+ <span style={{ color: 'var(--error)' }}>−{totalRemoved}</span>
304
304
  </div>
305
305
 
306
306
  {entries.map((entry, i) => (
@@ -110,7 +110,7 @@ function renderBody(body: string): string {
110
110
 
111
111
  const TAG_PALETTE = [
112
112
  { bg: 'rgba(200,135,58,0.12)', text: 'var(--amber)' },
113
- { bg: 'rgba(122,173,128,0.12)', text: '#7aad80' },
113
+ { bg: 'rgba(122,173,128,0.12)', text: 'var(--success)' },
114
114
  { bg: 'rgba(138,180,216,0.12)', text: '#8ab4d8' },
115
115
  { bg: 'rgba(200,160,216,0.12)', text: '#c8a0d8' },
116
116
  ];
@@ -92,9 +92,9 @@ function renderBody(body: string): string {
92
92
  function StatusIcon({ status }: { status: StepStatus }) {
93
93
  if (status === 'pending') return <Circle size={15} style={{ color: 'var(--border)' }} />;
94
94
  if (status === 'running') return <Loader2 size={15} style={{ color: 'var(--amber)', animation: 'spin 1s linear infinite' }} />;
95
- if (status === 'done') return <CheckCircle2 size={15} style={{ color: '#7aad80' }} />;
95
+ if (status === 'done') return <CheckCircle2 size={15} style={{ color: 'var(--success)' }} />;
96
96
  if (status === 'skipped') return <SkipForward size={15} style={{ color: 'var(--muted-foreground)', opacity: .5 }} />;
97
- return <AlertCircle size={15} style={{ color: '#c85050' }} />;
97
+ return <AlertCircle size={15} style={{ color: 'var(--error)' }} />;
98
98
  }
99
99
 
100
100
  const STATUS_BORDER: Record<StepStatus, string> = {
@@ -5,7 +5,6 @@ import {
5
5
  Plug, CheckCircle2, AlertCircle, Loader2, Copy, Check,
6
6
  ChevronDown, ChevronRight, Trash2, Plus, X,
7
7
  } from 'lucide-react';
8
- import { SectionLabel } from './Primitives';
9
8
  import { apiFetch } from '@/lib/api';
10
9
 
11
10
  /* ── Types ─────────────────────────────────────────────────────── */
@@ -206,9 +205,7 @@ function AgentInstall({ agents, t, onRefresh }: { agents: AgentInfo[]; t: any; o
206
205
  }));
207
206
 
208
207
  return (
209
- <div className="space-y-3">
210
- <SectionLabel>{m?.agentsTitle ?? 'Agent Configuration'}</SectionLabel>
211
-
208
+ <div className="space-y-3 pt-2">
212
209
  {/* Agent list */}
213
210
  <div className="space-y-1">
214
211
  {agents.map(agent => (
@@ -251,6 +248,23 @@ function AgentInstall({ agents, t, onRefresh }: { agents: AgentInfo[]; t: any; o
251
248
  ))}
252
249
  </div>
253
250
 
251
+ {/* Select detected / Clear buttons */}
252
+ <div className="flex gap-2 text-xs pt-1">
253
+ <button type="button"
254
+ onClick={() => setSelected(new Set(
255
+ agents.filter(a => !a.installed && a.present).map(a => a.key)
256
+ ))}
257
+ className="px-2.5 py-1 rounded-md border transition-colors hover:bg-muted/50"
258
+ style={{ borderColor: 'var(--amber)', color: 'var(--amber)' }}>
259
+ {m?.selectDetected ?? 'Select Detected'}
260
+ </button>
261
+ <button type="button"
262
+ onClick={() => setSelected(new Set())}
263
+ className="px-2.5 py-1 rounded-md border border-border text-muted-foreground transition-colors hover:bg-muted/50 hover:text-foreground">
264
+ {m?.clearSelection ?? 'Clear'}
265
+ </button>
266
+ </div>
267
+
254
268
  {/* Transport selector */}
255
269
  <div className="flex items-center gap-4 text-xs pt-1">
256
270
  <label className="flex items-center gap-1.5 cursor-pointer">
@@ -294,7 +308,7 @@ function AgentInstall({ agents, t, onRefresh }: { agents: AgentInfo[]; t: any; o
294
308
  type="text"
295
309
  value={httpUrl}
296
310
  onChange={e => setHttpUrl(e.target.value)}
297
- className="w-full px-2.5 py-1.5 text-xs rounded-md border border-border bg-background font-mono text-foreground outline-none focus:ring-1 focus:ring-ring"
311
+ className="w-full px-2.5 py-1.5 text-xs rounded-md border border-border bg-background font-mono text-foreground outline-none focus-visible:ring-1 focus-visible:ring-ring"
298
312
  />
299
313
  </div>
300
314
  <div className="space-y-1">
@@ -304,7 +318,7 @@ function AgentInstall({ agents, t, onRefresh }: { agents: AgentInfo[]; t: any; o
304
318
  value={httpToken}
305
319
  onChange={e => setHttpToken(e.target.value)}
306
320
  placeholder="Bearer token"
307
- className="w-full px-2.5 py-1.5 text-xs rounded-md border border-border bg-background font-mono text-foreground outline-none focus:ring-1 focus:ring-ring"
321
+ className="w-full px-2.5 py-1.5 text-xs rounded-md border border-border bg-background font-mono text-foreground outline-none focus-visible:ring-1 focus-visible:ring-ring"
308
322
  />
309
323
  </div>
310
324
  </div>
@@ -414,9 +428,7 @@ function SkillsSection({ t }: { t: any }) {
414
428
  }
415
429
 
416
430
  return (
417
- <div className="space-y-3">
418
- <SectionLabel>{m?.skillsTitle ?? 'Skills'}</SectionLabel>
419
-
431
+ <div className="space-y-3 pt-2">
420
432
  {/* Skill language switcher */}
421
433
  {(() => {
422
434
  const mindosEnabled = skills.find(s => s.name === 'mindos')?.enabled ?? true;
@@ -520,7 +532,7 @@ function SkillsSection({ t }: { t: any }) {
520
532
  value={newName}
521
533
  onChange={e => setNewName(e.target.value.replace(/[^a-z0-9-]/g, ''))}
522
534
  placeholder="my-skill"
523
- className="w-full px-2.5 py-1.5 text-xs rounded-md border border-border bg-background font-mono text-foreground outline-none focus:ring-1 focus:ring-ring"
535
+ className="w-full px-2.5 py-1.5 text-xs rounded-md border border-border bg-background font-mono text-foreground outline-none focus-visible:ring-1 focus-visible:ring-ring"
524
536
  />
525
537
  </div>
526
538
  <div className="space-y-1">
@@ -530,7 +542,7 @@ function SkillsSection({ t }: { t: any }) {
530
542
  value={newDesc}
531
543
  onChange={e => setNewDesc(e.target.value)}
532
544
  placeholder="What does this skill do?"
533
- className="w-full px-2.5 py-1.5 text-xs rounded-md border border-border bg-background text-foreground outline-none focus:ring-1 focus:ring-ring"
545
+ className="w-full px-2.5 py-1.5 text-xs rounded-md border border-border bg-background text-foreground outline-none focus-visible:ring-1 focus-visible:ring-ring"
534
546
  />
535
547
  </div>
536
548
  <div className="space-y-1">
@@ -540,7 +552,7 @@ function SkillsSection({ t }: { t: any }) {
540
552
  onChange={e => setNewContent(e.target.value)}
541
553
  rows={6}
542
554
  placeholder="Skill instructions (markdown)..."
543
- className="w-full px-2.5 py-1.5 text-xs rounded-md border border-border bg-background text-foreground outline-none focus:ring-1 focus:ring-ring resize-y font-mono"
555
+ className="w-full px-2.5 py-1.5 text-xs rounded-md border border-border bg-background text-foreground outline-none focus-visible:ring-1 focus-visible:ring-ring resize-y font-mono"
544
556
  />
545
557
  </div>
546
558
  {error && (
@@ -586,6 +598,8 @@ export function McpTab({ t }: McpTabProps) {
586
598
  const [mcpStatus, setMcpStatus] = useState<McpStatus | null>(null);
587
599
  const [agents, setAgents] = useState<AgentInfo[]>([]);
588
600
  const [loading, setLoading] = useState(true);
601
+ const [showAgents, setShowAgents] = useState(false);
602
+ const [showSkills, setShowSkills] = useState(false);
589
603
 
590
604
  const fetchAll = useCallback(async () => {
591
605
  try {
@@ -609,22 +623,50 @@ export function McpTab({ t }: McpTabProps) {
609
623
  );
610
624
  }
611
625
 
626
+ const m = t.settings?.mcp;
627
+
612
628
  return (
613
629
  <div className="space-y-6">
614
- {/* MCP Server Status */}
615
- <ServerStatus status={mcpStatus} t={t} />
616
-
617
- {/* Divider */}
618
- <div className="border-t border-border" />
619
-
620
- {/* Agent Install */}
621
- <AgentInstall agents={agents} t={t} onRefresh={fetchAll} />
630
+ {/* MCP Server Status — prominent card */}
631
+ <div className="rounded-xl border p-4" style={{ borderColor: 'var(--border)', background: 'var(--card)' }}>
632
+ <ServerStatus status={mcpStatus} t={t} />
633
+ </div>
622
634
 
623
- {/* Divider */}
624
- <div className="border-t border-border" />
635
+ {/* Agent Install — collapsible */}
636
+ <div className="rounded-xl border overflow-hidden" style={{ borderColor: 'var(--border)' }}>
637
+ <button
638
+ type="button"
639
+ onClick={() => setShowAgents(!showAgents)}
640
+ className="w-full flex items-center justify-between px-4 py-3 text-sm font-medium hover:bg-muted/50 transition-colors"
641
+ style={{ color: 'var(--foreground)' }}
642
+ >
643
+ <span>{m?.agentsTitle ?? 'Agent Configuration'}</span>
644
+ <ChevronDown size={14} className={`transition-transform text-muted-foreground ${showAgents ? 'rotate-180' : ''}`} />
645
+ </button>
646
+ {showAgents && (
647
+ <div className="px-4 pb-4 border-t" style={{ borderColor: 'var(--border)' }}>
648
+ <AgentInstall agents={agents} t={t} onRefresh={fetchAll} />
649
+ </div>
650
+ )}
651
+ </div>
625
652
 
626
- {/* Skills */}
627
- <SkillsSection t={t} />
653
+ {/* Skills — collapsible */}
654
+ <div className="rounded-xl border overflow-hidden" style={{ borderColor: 'var(--border)' }}>
655
+ <button
656
+ type="button"
657
+ onClick={() => setShowSkills(!showSkills)}
658
+ className="w-full flex items-center justify-between px-4 py-3 text-sm font-medium hover:bg-muted/50 transition-colors"
659
+ style={{ color: 'var(--foreground)' }}
660
+ >
661
+ <span>{m?.skillsTitle ?? 'Skills'}</span>
662
+ <ChevronDown size={14} className={`transition-transform text-muted-foreground ${showSkills ? 'rotate-180' : ''}`} />
663
+ </button>
664
+ {showSkills && (
665
+ <div className="px-4 pb-4 border-t" style={{ borderColor: 'var(--border)' }}>
666
+ <SkillsSection t={t} />
667
+ </div>
668
+ )}
669
+ </div>
628
670
  </div>
629
671
  );
630
672
  }
@@ -18,7 +18,7 @@ export function Input({ className = '', ...props }: React.InputHTMLAttributes<HT
18
18
  return (
19
19
  <input
20
20
  {...props}
21
- 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 ${className}`}
21
+ 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 ${className}`}
22
22
  />
23
23
  );
24
24
  }
@@ -27,7 +27,7 @@ export function Select({ className = '', ...props }: React.SelectHTMLAttributes<
27
27
  return (
28
28
  <select
29
29
  {...props}
30
- className={`w-full px-3 py-2 text-sm bg-background border border-border rounded-lg text-foreground outline-none focus:ring-1 focus:ring-ring disabled:opacity-50 ${className}`}
30
+ className={`w-full px-3 py-2 text-sm bg-background border border-border rounded-lg text-foreground outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:opacity-50 ${className}`}
31
31
  />
32
32
  );
33
33
  }
@@ -53,7 +53,7 @@ export function ApiKeyInput({ value, onChange, placeholder, disabled }: {
53
53
  onChange={e => onChange(e.target.value)}
54
54
  placeholder={placeholder ?? 'sk-...'}
55
55
  disabled={disabled}
56
- 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"
56
+ 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"
57
57
  onFocus={() => { if (isMasked) onChange(''); }}
58
58
  />
59
59
  );
@@ -104,7 +104,7 @@ function SyncEmptyState({ t, onInitComplete }: { t: any; onInitComplete: () => v
104
104
  value={remoteUrl}
105
105
  onChange={e => { setRemoteUrl(e.target.value); setError(''); }}
106
106
  placeholder="https://github.com/user/my-mind.git"
107
- className="w-full px-3 py-2 text-sm rounded-lg border bg-transparent font-mono text-xs transition-colors focus:outline-none focus:ring-1"
107
+ className="w-full px-3 py-2 text-sm rounded-lg border bg-transparent font-mono text-xs transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring"
108
108
  style={{
109
109
  borderColor: remoteUrl.trim() && !isValid ? 'var(--destructive, red)' : 'var(--border)',
110
110
  color: 'var(--foreground)',
@@ -136,7 +136,7 @@ function SyncEmptyState({ t, onInitComplete }: { t: any; onInitComplete: () => v
136
136
  value={token}
137
137
  onChange={e => setToken(e.target.value)}
138
138
  placeholder="ghp_xxxxxxxxxxxx"
139
- className="w-full px-3 py-2 pr-9 text-sm rounded-lg border bg-transparent font-mono text-xs transition-colors focus:outline-none focus:ring-1"
139
+ className="w-full px-3 py-2 pr-9 text-sm rounded-lg border bg-transparent font-mono text-xs transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring"
140
140
  style={{ borderColor: 'var(--border)', color: 'var(--foreground)' }}
141
141
  />
142
142
  <button
@@ -163,7 +163,7 @@ function SyncEmptyState({ t, onInitComplete }: { t: any; onInitComplete: () => v
163
163
  value={branch}
164
164
  onChange={e => setBranch(e.target.value)}
165
165
  placeholder="main"
166
- className="w-full max-w-[200px] px-3 py-2 text-sm rounded-lg border bg-transparent font-mono text-xs transition-colors focus:outline-none focus:ring-1"
166
+ className="w-full max-w-[200px] px-3 py-2 text-sm rounded-lg border bg-transparent font-mono text-xs transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring"
167
167
  style={{ borderColor: 'var(--border)', color: 'var(--foreground)' }}
168
168
  />
169
169
  </div>
@@ -184,7 +184,7 @@ function SyncEmptyState({ t, onInitComplete }: { t: any; onInitComplete: () => v
184
184
 
185
185
  {/* Error */}
186
186
  {error && (
187
- <div className="flex items-start gap-2 text-xs p-3 rounded-lg" role="alert" aria-live="polite" style={{ background: 'rgba(239,68,68,0.1)', color: 'var(--destructive, red)' }}>
187
+ <div className="flex items-start gap-2 text-xs p-3 rounded-lg" role="alert" aria-live="polite" style={{ background: 'rgba(200,80,80,0.1)', color: 'var(--error)' }}>
188
188
  <AlertCircle size={13} className="shrink-0 mt-0.5" />
189
189
  <span>{error}</span>
190
190
  </div>
@@ -361,7 +361,7 @@ export function SyncTab({ t }: SyncTabProps) {
361
361
  <div className="space-y-1.5">
362
362
  {conflicts.map((c, i) => (
363
363
  <div key={i} className="flex items-center gap-2 text-xs group">
364
- <AlertCircle size={12} className="text-red-500 shrink-0" />
364
+ <AlertCircle size={12} className="text-error shrink-0" />
365
365
  <a
366
366
  href={`/view/${encodeURIComponent(c.file)}`}
367
367
  className="font-mono truncate hover:text-foreground hover:underline transition-colors"
package/app/lib/i18n.ts CHANGED
@@ -228,6 +228,8 @@ export const messages = {
228
228
  skillLanguage: 'Skill Language',
229
229
  skillLangEn: 'English',
230
230
  skillLangZh: '中文',
231
+ selectDetected: 'Select Detected',
232
+ clearSelection: 'Clear',
231
233
  }, save: 'Save',
232
234
  saved: 'Saved',
233
235
  saveFailed: 'Save failed',
@@ -283,7 +285,11 @@ export const messages = {
283
285
  kbPath: 'Knowledge base path',
284
286
  kbPathHint: 'Absolute path to your notes directory.',
285
287
  kbPathDefault: '~/MindOS/mind',
288
+ kbPathUseDefault: (path: string) => `Use ${path}`,
286
289
  kbPathExists: (n: number) => `Directory already has ${n} file(s) — template will not be applied.`,
290
+ kbPathHasFiles: (n: number) => `This directory already contains ${n} file${n > 1 ? 's' : ''}. You can skip the template or merge (existing files won't be overwritten).`,
291
+ kbTemplateSkip: 'Skip template',
292
+ kbTemplateMerge: 'Choose a template to merge',
287
293
  template: 'Starter template',
288
294
  templateSkip: 'Skip (directory already has files)',
289
295
  // Step 2
@@ -325,7 +331,16 @@ export const messages = {
325
331
  agentToolsLoading: 'Loading agents…',
326
332
  agentToolsEmpty: 'No supported agents detected.',
327
333
  agentNoneSelected: 'No agents selected — you can configure later in Settings → MCP.',
328
- agentSkipLater: 'Skip — configure later',
334
+ agentSkipLater: 'Skip — install agents later in Settings > MCP',
335
+ agentSelectDetected: 'Select detected agents',
336
+ agentNoneDetected: 'No agents detected on your system.',
337
+ agentShowMore: (n: number) => `Show ${n} more agents`,
338
+ agentAdvanced: 'Advanced options',
339
+ agentScopeGlobal: 'Install for all projects',
340
+ agentScopeProject: 'This project only',
341
+ badgeInstalled: 'Installed',
342
+ badgeDetected: 'Detected',
343
+ badgeNotFound: 'Not found',
329
344
  agentNotInstalled: 'not installed',
330
345
  agentDetected: 'detected',
331
346
  agentNotFound: 'not found',
@@ -338,6 +353,7 @@ export const messages = {
338
353
  agentUnverified: 'unverified',
339
354
  agentVerifyNote: 'stdio agents are verified after restart',
340
355
  // Skill auto-install
356
+ skillWhat: 'Skills teach AI agents how to use your knowledge base — like instructions for reading, writing, and organizing your notes.',
341
357
  skillAutoHint: (name: string) => `Based on your template, the "${name}" skill will be installed to selected agents.`,
342
358
  skillLabel: 'Skill',
343
359
  skillInstalling: 'Installing skill…',
@@ -347,9 +363,15 @@ export const messages = {
347
363
  aiSkipTitle: 'Skip for now',
348
364
  aiSkipDesc: 'You can add an API key later in Settings → AI.',
349
365
  // Step 6 — Review
350
- reviewHint: 'Verify your settings before completing setup.',
366
+ reviewHint: 'Verify your settings, then press Complete Setup.',
351
367
  reviewInstallResults: 'Agent configuration results:',
368
+ phaseSaving: 'Saving configuration…',
369
+ phaseAgents: 'Configuring agents…',
370
+ phaseSkill: 'Installing skill…',
371
+ phaseDone: 'Setup complete!',
352
372
  retryAgent: 'Retry',
373
+ agentFailedCount: (n: number) => `${n} agent${n > 1 ? 's' : ''} failed`,
374
+ agentCountSummary: (n: number) => `${n} agent${n > 1 ? 's' : ''}`,
353
375
  agentFailureNote: 'Agent failures are non-blocking — you can enter MindOS and retry from Settings → MCP.',
354
376
  portAvailable: 'Available',
355
377
  portChanged: 'Port changed — please restart the server for it to take effect.',
@@ -601,6 +623,8 @@ export const messages = {
601
623
  skillLanguage: 'Skill 语言',
602
624
  skillLangEn: 'English',
603
625
  skillLangZh: '中文',
626
+ selectDetected: '选择已检测',
627
+ clearSelection: '清除',
604
628
  }, save: '保存',
605
629
  saved: '已保存',
606
630
  saveFailed: '保存失败',
@@ -656,7 +680,11 @@ export const messages = {
656
680
  kbPath: '知识库路径',
657
681
  kbPathHint: '笔记目录的绝对路径。',
658
682
  kbPathDefault: '~/MindOS/mind',
683
+ kbPathUseDefault: (path: string) => `使用 ${path}`,
659
684
  kbPathExists: (n: number) => `目录已有 ${n} 个文件 — 将不会应用模板。`,
685
+ kbPathHasFiles: (n: number) => `该目录已有 ${n} 个文件。可以跳过模板,或选择合并(已有文件不会被覆盖)。`,
686
+ kbTemplateSkip: '跳过模板',
687
+ kbTemplateMerge: '选择模板合并',
660
688
  template: '初始模板',
661
689
  templateSkip: '跳过(目录已有文件)',
662
690
  // Step 2
@@ -698,7 +726,16 @@ export const messages = {
698
726
  agentToolsLoading: '正在加载 Agent…',
699
727
  agentToolsEmpty: '未检测到受支持的 Agent。',
700
728
  agentNoneSelected: '未选择 agent — 可稍后在 设置 → MCP 中配置。',
701
- agentSkipLater: '跳过 — 稍后配置',
729
+ agentSkipLater: '跳过 — 稍后在 设置 > MCP 中安装',
730
+ agentSelectDetected: '选择已检测到的 Agent',
731
+ agentNoneDetected: '未在系统中检测到已安装的 Agent。',
732
+ agentShowMore: (n: number) => `显示另外 ${n} 个 Agent`,
733
+ agentAdvanced: '高级选项',
734
+ agentScopeGlobal: '为所有项目安装',
735
+ agentScopeProject: '仅当前项目',
736
+ badgeInstalled: '已安装',
737
+ badgeDetected: '已检测到',
738
+ badgeNotFound: '未找到',
702
739
  agentNotInstalled: '未安装',
703
740
  agentDetected: '已检测到',
704
741
  agentNotFound: '未找到',
@@ -711,6 +748,7 @@ export const messages = {
711
748
  agentUnverified: '未验证',
712
749
  agentVerifyNote: 'stdio agent 需重启后验证',
713
750
  // Skill auto-install
751
+ skillWhat: 'Skill 教 AI Agent 如何使用你的知识库 — 包括笔记的读取、写入和整理规则。',
714
752
  skillAutoHint: (name: string) => `根据您选择的模板,将向选中的 Agent 安装「${name}」Skill。`,
715
753
  skillLabel: 'Skill',
716
754
  skillInstalling: '正在安装 Skill…',
@@ -720,9 +758,15 @@ export const messages = {
720
758
  aiSkipTitle: '暂时跳过',
721
759
  aiSkipDesc: '稍后可在 设置 → AI 中添加 API 密钥。',
722
760
  // Step 6 — Review
723
- reviewHint: '完成设置前请确认以下信息。',
761
+ reviewHint: '确认设置无误后,点击完成。',
724
762
  reviewInstallResults: 'Agent 配置结果:',
763
+ phaseSaving: '正在保存配置…',
764
+ phaseAgents: '正在配置 Agent…',
765
+ phaseSkill: '正在安装 Skill…',
766
+ phaseDone: '设置完成!',
725
767
  retryAgent: '重试',
768
+ agentFailedCount: (n: number) => `${n} 个 Agent 配置失败`,
769
+ agentCountSummary: (n: number) => `${n} 个 Agent`,
726
770
  agentFailureNote: 'Agent 安装失败不影响进入 MindOS — 可稍后在 设置 → MCP 中重试。',
727
771
  portAvailable: '可用',
728
772
  portChanged: '端口已变更 — 请重启服务以使其生效。',