@geminilight/mindos 0.3.0 → 0.4.0

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 (76) hide show
  1. package/app/app/api/mcp/agents/route.ts +72 -0
  2. package/app/app/api/mcp/install/route.ts +95 -0
  3. package/app/app/api/mcp/status/route.ts +47 -0
  4. package/app/app/api/skills/route.ts +208 -0
  5. package/app/app/api/sync/route.ts +54 -3
  6. package/app/app/api/update-check/route.ts +52 -0
  7. package/app/app/globals.css +12 -0
  8. package/app/app/layout.tsx +4 -2
  9. package/app/app/login/page.tsx +20 -13
  10. package/app/app/page.tsx +17 -2
  11. package/app/app/view/[...path]/ViewPageClient.tsx +47 -21
  12. package/app/app/view/[...path]/loading.tsx +1 -1
  13. package/app/app/view/[...path]/not-found.tsx +101 -0
  14. package/app/components/AskFab.tsx +1 -1
  15. package/app/components/AskModal.tsx +1 -1
  16. package/app/components/Backlinks.tsx +1 -1
  17. package/app/components/Breadcrumb.tsx +13 -3
  18. package/app/components/CsvView.tsx +5 -6
  19. package/app/components/DirView.tsx +42 -21
  20. package/app/components/FindInPage.tsx +211 -0
  21. package/app/components/HomeContent.tsx +97 -44
  22. package/app/components/JsonView.tsx +1 -2
  23. package/app/components/MarkdownEditor.tsx +1 -2
  24. package/app/components/OnboardingView.tsx +6 -7
  25. package/app/components/SettingsModal.tsx +5 -2
  26. package/app/components/SetupWizard.tsx +4 -4
  27. package/app/components/Sidebar.tsx +1 -1
  28. package/app/components/UpdateBanner.tsx +101 -0
  29. package/app/components/renderers/{AgentInspectorRenderer.tsx → agent-inspector/AgentInspectorRenderer.tsx} +13 -11
  30. package/app/components/renderers/agent-inspector/manifest.ts +14 -0
  31. package/app/components/renderers/{BacklinksRenderer.tsx → backlinks/BacklinksRenderer.tsx} +6 -6
  32. package/app/components/renderers/backlinks/manifest.ts +14 -0
  33. package/app/components/renderers/config/manifest.ts +14 -0
  34. package/app/components/renderers/csv/BoardView.tsx +12 -12
  35. package/app/components/renderers/csv/ConfigPanel.tsx +7 -8
  36. package/app/components/renderers/{CsvRenderer.tsx → csv/CsvRenderer.tsx} +8 -9
  37. package/app/components/renderers/csv/GalleryView.tsx +3 -3
  38. package/app/components/renderers/csv/TableView.tsx +4 -5
  39. package/app/components/renderers/csv/manifest.ts +14 -0
  40. package/app/components/renderers/{DiffRenderer.tsx → diff/DiffRenderer.tsx} +10 -9
  41. package/app/components/renderers/diff/manifest.ts +14 -0
  42. package/app/components/renderers/{GraphRenderer.tsx → graph/GraphRenderer.tsx} +4 -5
  43. package/app/components/renderers/graph/manifest.ts +14 -0
  44. package/app/components/renderers/{SummaryRenderer.tsx → summary/SummaryRenderer.tsx} +6 -6
  45. package/app/components/renderers/summary/manifest.ts +14 -0
  46. package/app/components/renderers/{TimelineRenderer.tsx → timeline/TimelineRenderer.tsx} +6 -6
  47. package/app/components/renderers/timeline/manifest.ts +14 -0
  48. package/app/components/renderers/{TodoRenderer.tsx → todo/TodoRenderer.tsx} +2 -2
  49. package/app/components/renderers/todo/manifest.ts +14 -0
  50. package/app/components/renderers/{WorkflowRenderer.tsx → workflow/WorkflowRenderer.tsx} +13 -13
  51. package/app/components/renderers/workflow/manifest.ts +14 -0
  52. package/app/components/settings/McpTab.tsx +549 -0
  53. package/app/components/settings/SyncTab.tsx +139 -50
  54. package/app/components/settings/types.ts +1 -1
  55. package/app/data/pages/home.png +0 -0
  56. package/app/lib/i18n.ts +178 -10
  57. package/app/lib/renderers/index.ts +20 -89
  58. package/app/lib/renderers/registry.ts +4 -1
  59. package/app/lib/settings.ts +3 -0
  60. package/app/package.json +1 -0
  61. package/app/types/semver.d.ts +8 -0
  62. package/bin/cli.js +137 -24
  63. package/bin/lib/build.js +53 -18
  64. package/bin/lib/colors.js +3 -1
  65. package/bin/lib/config.js +4 -0
  66. package/bin/lib/constants.js +2 -0
  67. package/bin/lib/debug.js +10 -0
  68. package/bin/lib/startup.js +21 -20
  69. package/bin/lib/stop.js +41 -3
  70. package/bin/lib/sync.js +65 -53
  71. package/bin/lib/update-check.js +94 -0
  72. package/bin/lib/utils.js +2 -2
  73. package/package.json +1 -1
  74. package/scripts/gen-renderer-index.js +57 -0
  75. package/scripts/setup.js +24 -0
  76. /package/app/components/renderers/{ConfigRenderer.tsx → config/ConfigRenderer.tsx} +0 -0
@@ -1,7 +1,7 @@
1
1
  'use client';
2
2
 
3
3
  import { useState, useEffect, useCallback } from 'react';
4
- import { RefreshCw, AlertCircle, CheckCircle2, Loader2, GitBranch, Copy, Check, ExternalLink } from 'lucide-react';
4
+ import { RefreshCw, AlertCircle, CheckCircle2, Loader2, GitBranch, ExternalLink, Eye, EyeOff } from 'lucide-react';
5
5
  import { SectionLabel } from './Primitives';
6
6
  import { apiFetch } from '@/lib/api';
7
7
 
@@ -32,35 +32,53 @@ export function timeAgo(iso: string | null | undefined): string {
32
32
  return `${Math.floor(diff / 86400000)}d ago`;
33
33
  }
34
34
 
35
- /* ── Copy-to-clipboard button ──────────────────────────────────── */
35
+ /* ── Empty state GUI sync init form ─────────────────────────── */
36
36
 
37
- function CopyButton({ text }: { text: string }) {
38
- const [copied, setCopied] = useState(false);
39
- const handleCopy = async () => {
40
- await navigator.clipboard.writeText(text);
41
- setCopied(true);
42
- setTimeout(() => setCopied(false), 2000);
43
- };
44
- return (
45
- <button
46
- type="button"
47
- onClick={handleCopy}
48
- className="p-1 rounded hover:bg-muted text-muted-foreground hover:text-foreground transition-colors shrink-0"
49
- title="Copy command"
50
- >
51
- {copied ? <Check size={12} className="text-green-500" /> : <Copy size={12} />}
52
- </button>
53
- );
37
+ function isValidGitUrl(url: string): 'https' | 'ssh' | false {
38
+ if (/^https:\/\/.+/.test(url)) return 'https';
39
+ if (/^git@[\w.-]+:.+/.test(url)) return 'ssh';
40
+ return false;
54
41
  }
55
42
 
56
- /* ── Empty state (Task D) ──────────────────────────────────────── */
57
-
58
- function SyncEmptyState({ t }: { t: any }) {
43
+ function SyncEmptyState({ t, onInitComplete }: { t: any; onInitComplete: () => void }) {
59
44
  const syncT = t.settings?.sync;
60
- const cmd = 'mindos sync init';
45
+
46
+ const [remoteUrl, setRemoteUrl] = useState('');
47
+ const [token, setToken] = useState('');
48
+ const [branch, setBranch] = useState('main');
49
+ const [showToken, setShowToken] = useState(false);
50
+ const [connecting, setConnecting] = useState(false);
51
+ const [error, setError] = useState('');
52
+
53
+ const urlType = remoteUrl.trim() ? isValidGitUrl(remoteUrl.trim()) : null;
54
+ const isValid = urlType === 'https' || urlType === 'ssh';
55
+ const showTokenField = urlType === 'https';
56
+
57
+ const handleConnect = async () => {
58
+ setConnecting(true);
59
+ setError('');
60
+ try {
61
+ await apiFetch('/api/sync', {
62
+ method: 'POST',
63
+ headers: { 'Content-Type': 'application/json' },
64
+ body: JSON.stringify({
65
+ action: 'init',
66
+ remote: remoteUrl.trim(),
67
+ token: token.trim() || undefined,
68
+ branch: branch.trim() || 'main',
69
+ }),
70
+ });
71
+ onInitComplete();
72
+ } catch (err: unknown) {
73
+ const msg = err instanceof Error ? err.message : 'Connection failed';
74
+ setError(msg);
75
+ } finally {
76
+ setConnecting(false);
77
+ }
78
+ };
61
79
 
62
80
  return (
63
- <div className="space-y-6">
81
+ <div className="space-y-5">
64
82
  {/* Header */}
65
83
  <div className="flex items-center gap-3">
66
84
  <div className="w-9 h-9 rounded-lg bg-muted flex items-center justify-center shrink-0">
@@ -76,33 +94,104 @@ function SyncEmptyState({ t }: { t: any }) {
76
94
  </div>
77
95
  </div>
78
96
 
79
- {/* Steps */}
80
- <div className="space-y-3">
81
- <SectionLabel>{syncT?.emptyStepsTitle ?? 'Setup'}</SectionLabel>
82
- <ol className="space-y-2.5 text-xs text-muted-foreground">
83
- <li className="flex items-start gap-2.5">
84
- <span className="w-5 h-5 rounded-full bg-muted flex items-center justify-center shrink-0 text-[10px] font-medium text-foreground mt-0.5">1</span>
85
- <span>{syncT?.emptyStep1 ?? 'Create a private Git repo (GitHub, GitLab, etc.) or use an existing one.'}</span>
86
- </li>
87
- <li className="flex items-start gap-2.5">
88
- <span className="w-5 h-5 rounded-full bg-muted flex items-center justify-center shrink-0 text-[10px] font-medium text-foreground mt-0.5">2</span>
89
- <div className="flex-1">
90
- <span>{syncT?.emptyStep2 ?? 'Run this command in your terminal:'}</span>
91
- <div className="flex items-center gap-1.5 mt-1.5 px-3 py-2 bg-muted rounded-lg font-mono text-xs text-foreground">
92
- <code className="flex-1 select-all">{cmd}</code>
93
- <CopyButton text={cmd} />
94
- </div>
95
- </div>
96
- </li>
97
- <li className="flex items-start gap-2.5">
98
- <span className="w-5 h-5 rounded-full bg-muted flex items-center justify-center shrink-0 text-[10px] font-medium text-foreground mt-0.5">3</span>
99
- <span>{syncT?.emptyStep3 ?? 'Follow the prompts to connect your repo. Sync starts automatically.'}</span>
100
- </li>
101
- </ol>
97
+ {/* Git Remote URL */}
98
+ <div className="space-y-1.5">
99
+ <label className="text-xs font-medium text-foreground block">
100
+ {syncT?.remoteUrl ?? 'Git Remote URL'}
101
+ </label>
102
+ <input
103
+ type="text"
104
+ value={remoteUrl}
105
+ onChange={e => { setRemoteUrl(e.target.value); setError(''); }}
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"
108
+ style={{
109
+ borderColor: remoteUrl.trim() && !isValid ? 'var(--destructive, red)' : 'var(--border)',
110
+ color: 'var(--foreground)',
111
+ }}
112
+ />
113
+ {remoteUrl.trim() && !isValid && (
114
+ <p className="text-[11px]" style={{ color: 'var(--destructive, red)' }}>
115
+ {syncT?.invalidUrl ?? 'Invalid Git URL — use HTTPS (https://...) or SSH (git@...)'}
116
+ </p>
117
+ )}
118
+ {urlType === 'ssh' && (
119
+ <p className="text-[11px] text-muted-foreground flex items-center gap-1">
120
+ <AlertCircle size={11} className="shrink-0" />
121
+ {syncT?.sshHint ?? 'SSH URLs require SSH key configured on this machine. HTTPS with token recommended.'}
122
+ </p>
123
+ )}
102
124
  </div>
103
125
 
126
+ {/* Access Token (HTTPS only) */}
127
+ {showTokenField && (
128
+ <div className="space-y-1.5">
129
+ <label className="text-xs font-medium text-foreground block">
130
+ {syncT?.accessToken ?? 'Access Token'}{' '}
131
+ <span className="text-muted-foreground font-normal">{syncT?.optional ?? '(optional, for private repos)'}</span>
132
+ </label>
133
+ <div className="relative">
134
+ <input
135
+ type={showToken ? 'text' : 'password'}
136
+ value={token}
137
+ onChange={e => setToken(e.target.value)}
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"
140
+ style={{ borderColor: 'var(--border)', color: 'var(--foreground)' }}
141
+ />
142
+ <button
143
+ type="button"
144
+ onClick={() => setShowToken(!showToken)}
145
+ className="absolute right-2 top-1/2 -translate-y-1/2 p-0.5 rounded hover:bg-muted text-muted-foreground transition-colors"
146
+ >
147
+ {showToken ? <EyeOff size={14} /> : <Eye size={14} />}
148
+ </button>
149
+ </div>
150
+ <p className="text-[11px] text-muted-foreground">
151
+ {syncT?.tokenHint ?? 'GitHub: Settings → Developer settings → Personal access tokens → repo scope'}
152
+ </p>
153
+ </div>
154
+ )}
155
+
156
+ {/* Branch */}
157
+ <div className="space-y-1.5">
158
+ <label className="text-xs font-medium text-foreground block">
159
+ {syncT?.branchLabel ?? 'Branch'}
160
+ </label>
161
+ <input
162
+ type="text"
163
+ value={branch}
164
+ onChange={e => setBranch(e.target.value)}
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"
167
+ style={{ borderColor: 'var(--border)', color: 'var(--foreground)' }}
168
+ />
169
+ </div>
170
+
171
+ {/* Connect button */}
172
+ <button
173
+ type="button"
174
+ onClick={handleConnect}
175
+ disabled={!isValid || connecting}
176
+ className="flex items-center gap-2 px-4 py-2 text-sm font-medium rounded-lg transition-colors disabled:opacity-40 disabled:cursor-not-allowed"
177
+ style={{ background: 'var(--amber)', color: '#131210' }}
178
+ >
179
+ {connecting && <Loader2 size={14} className="animate-spin" />}
180
+ {connecting
181
+ ? (syncT?.connecting ?? 'Connecting...')
182
+ : (syncT?.connectButton ?? 'Connect & Start Sync')}
183
+ </button>
184
+
185
+ {/* Error */}
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)' }}>
188
+ <AlertCircle size={13} className="shrink-0 mt-0.5" />
189
+ <span>{error}</span>
190
+ </div>
191
+ )}
192
+
104
193
  {/* Features */}
105
- <div className="grid grid-cols-2 gap-2 text-[11px] text-muted-foreground">
194
+ <div className="grid grid-cols-2 gap-2 text-[11px] text-muted-foreground pt-2">
106
195
  {[
107
196
  syncT?.featureAutoCommit ?? 'Auto-commit on save',
108
197
  syncT?.featureAutoPull ?? 'Auto-pull from remote',
@@ -190,7 +279,7 @@ export function SyncTab({ t }: SyncTabProps) {
190
279
  }
191
280
 
192
281
  if (!status || !status.enabled) {
193
- return <SyncEmptyState t={t} />;
282
+ return <SyncEmptyState t={t} onInitComplete={fetchStatus} />;
194
283
  }
195
284
 
196
285
  const conflicts = status.conflicts || [];
@@ -256,7 +345,7 @@ export function SyncTab({ t }: SyncTabProps) {
256
345
 
257
346
  {/* Message */}
258
347
  {message && (
259
- <div className="flex items-center gap-1.5 text-xs">
348
+ <div className="flex items-center gap-1.5 text-xs" role="status" aria-live="polite">
260
349
  {message.type === 'success' ? (
261
350
  <><CheckCircle2 size={13} className="text-green-500" /><span className="text-green-500">{message.text}</span></>
262
351
  ) : (
@@ -24,7 +24,7 @@ export interface SettingsData {
24
24
  envValues?: Record<string, string>;
25
25
  }
26
26
 
27
- export type Tab = 'ai' | 'appearance' | 'knowledge' | 'plugins' | 'shortcuts' | 'sync';
27
+ export type Tab = 'ai' | 'appearance' | 'knowledge' | 'mcp' | 'plugins' | 'shortcuts' | 'sync';
28
28
 
29
29
  export const CONTENT_WIDTHS = [
30
30
  { value: '680px', label: 'Narrow (680px)' },
Binary file
package/app/lib/i18n.ts CHANGED
@@ -16,6 +16,7 @@ export const messages = {
16
16
  plugins: 'Plugins',
17
17
  showMore: 'Show more',
18
18
  showLess: 'Show less',
19
+ createToActivate: 'Create {file} to activate',
19
20
  shortcuts: {
20
21
  searchFiles: 'Search files',
21
22
  askAI: 'Ask AI',
@@ -96,10 +97,16 @@ export const messages = {
96
97
  listView: 'List view',
97
98
  emptyFolder: 'This folder is empty.',
98
99
  fileCount: (n: number) => `${n} files`,
100
+ newFile: 'New file',
101
+ },
102
+ findInPage: {
103
+ placeholder: 'Find in document…',
104
+ matchCount: (current: number, total: number) => `${current} of ${total}`,
105
+ noResults: 'No results',
99
106
  },
100
107
  settings: {
101
108
  title: 'Settings',
102
- tabs: { ai: 'AI', appearance: 'Appearance', knowledge: 'Knowledge Base', sync: 'Sync', plugins: 'Plugins', shortcuts: 'Shortcuts' },
109
+ tabs: { ai: 'AI', appearance: 'Appearance', knowledge: 'Knowledge Base', sync: 'Sync', mcp: 'MCP', plugins: 'Plugins', shortcuts: 'Shortcuts' },
103
110
  ai: {
104
111
  provider: 'Provider',
105
112
  model: 'Model',
@@ -144,14 +151,19 @@ export const messages = {
144
151
  sync: {
145
152
  emptyTitle: 'Cross-device Sync',
146
153
  emptyDesc: 'Automatically sync your knowledge base across devices via Git.',
147
- emptyStepsTitle: 'Setup',
148
- emptyStep1: 'Create a private Git repo (GitHub, GitLab, etc.) or use an existing one.',
149
- emptyStep2: 'Run this command in your terminal:',
150
- emptyStep3: 'Follow the prompts to connect your repo. Sync starts automatically.',
151
154
  featureAutoCommit: 'Auto-commit on save',
152
155
  featureAutoPull: 'Auto-pull from remote',
153
156
  featureConflict: 'Conflict detection',
154
157
  featureMultiDevice: 'Works across devices',
158
+ remoteUrl: 'Git Remote URL',
159
+ invalidUrl: 'Invalid Git URL — use HTTPS (https://...) or SSH (git@...)',
160
+ sshHint: 'SSH URLs require SSH key configured on this machine. HTTPS with token recommended.',
161
+ accessToken: 'Access Token',
162
+ optional: '(optional, for private repos)',
163
+ tokenHint: 'GitHub: Settings → Developer settings → Personal access tokens → repo scope',
164
+ branchLabel: 'Branch',
165
+ connectButton: 'Connect & Start Sync',
166
+ connecting: 'Connecting...',
155
167
  },
156
168
  plugins: {
157
169
  title: 'Installed Renderers',
@@ -162,6 +174,54 @@ export const messages = {
162
174
  noPlugins: 'No renderers installed.',
163
175
  comingSoon: 'Plugin marketplace coming soon.',
164
176
  },
177
+ mcp: {
178
+ serverTitle: 'MindOS MCP Server',
179
+ status: 'Status',
180
+ running: 'Running',
181
+ stopped: 'Stopped',
182
+ transport: 'Transport',
183
+ endpoint: 'Endpoint',
184
+ tools: 'Tools',
185
+ toolsRegistered: (n: number) => `${n} registered`,
186
+ auth: 'Auth',
187
+ authSet: 'Token set',
188
+ authNotSet: 'No token',
189
+ copyEndpoint: 'Copy Endpoint',
190
+ copyConfig: 'Copy Config',
191
+ copied: 'Copied!',
192
+ agentsTitle: 'Agent Configuration',
193
+ agent: 'Agent',
194
+ scope: 'Scope',
195
+ project: 'Project',
196
+ global: 'Global',
197
+ installed: 'Installed',
198
+ notInstalled: 'Not installed',
199
+ transportStdio: 'stdio (recommended)',
200
+ transportHttp: 'http',
201
+ httpUrl: 'MCP URL',
202
+ httpToken: 'Auth Token',
203
+ installSelected: 'Install Selected',
204
+ installing: 'Installing...',
205
+ installSuccess: (n: number) => `${n} agent(s) configured`,
206
+ installFailed: 'Install failed',
207
+ portLabel: 'MCP Port',
208
+ portHint: 'Changes require server restart',
209
+ skillsTitle: 'Skills',
210
+ skillAutoLoaded: 'Auto-loaded on every request',
211
+ skillSource: 'Source',
212
+ skillBuiltin: 'Built-in',
213
+ skillUser: 'Custom',
214
+ addSkill: '+ Add Skill',
215
+ deleteSkill: 'Delete',
216
+ editSkill: 'Edit',
217
+ saveSkill: 'Save',
218
+ cancelSkill: 'Cancel',
219
+ skillName: 'Name',
220
+ skillDesc: 'Description',
221
+ skillContent: 'Content',
222
+ skillNameConflict: 'A skill with this name already exists',
223
+ skillDeleteConfirm: (name: string) => `Delete skill "${name}"? This cannot be undone.`,
224
+ },
165
225
  save: 'Save',
166
226
  saved: 'Saved',
167
227
  saveFailed: 'Save failed',
@@ -186,6 +246,30 @@ export const messages = {
186
246
  { keys: ['Esc'], description: 'Cancel edit / close modal' },
187
247
  { keys: ['@'], description: 'Attach file in Ask AI' },
188
248
  ],
249
+ login: {
250
+ tagline: 'You think here, Agents act there.',
251
+ subtitle: 'Enter your password to continue',
252
+ passwordLabel: 'Password',
253
+ passwordPlaceholder: 'Enter password',
254
+ signIn: 'Sign in',
255
+ signingIn: 'Signing in…',
256
+ incorrectPassword: 'Incorrect password. Please try again.',
257
+ connectionError: 'Connection error. Please try again.',
258
+ },
259
+ notFound: {
260
+ title: 'File not found',
261
+ description: 'This file does not exist in your knowledge base.',
262
+ createButton: 'Create this file',
263
+ creating: 'Creating...',
264
+ goToParent: 'Go to parent folder',
265
+ goHome: 'Home',
266
+ },
267
+ updateBanner: {
268
+ newVersion: (latest: string, current: string) => `MindOS v${latest} available (current: v${current})`,
269
+ runUpdate: 'Run',
270
+ orSee: 'or',
271
+ releaseNotes: 'view release notes',
272
+ },
189
273
  setup: {
190
274
  stepTitles: ['Knowledge Base', 'AI Provider', 'Ports', 'Security', 'Review'],
191
275
  // Step 1
@@ -248,6 +332,7 @@ export const messages = {
248
332
  plugins: '插件',
249
333
  showMore: '查看更多',
250
334
  showLess: '收起',
335
+ createToActivate: '创建 {file} 以启用此插件',
251
336
  shortcuts: {
252
337
  searchFiles: '搜索文件',
253
338
  askAI: '问 AI',
@@ -328,10 +413,16 @@ export const messages = {
328
413
  listView: '列表视图',
329
414
  emptyFolder: '此目录为空。',
330
415
  fileCount: (n: number) => `${n} 个文件`,
416
+ newFile: '新建文件',
417
+ },
418
+ findInPage: {
419
+ placeholder: '在文档中查找…',
420
+ matchCount: (current: number, total: number) => `${current} / ${total}`,
421
+ noResults: '无结果',
331
422
  },
332
423
  settings: {
333
424
  title: '设置',
334
- tabs: { ai: 'AI', appearance: '外观', knowledge: '知识库', sync: '同步', plugins: '插件', shortcuts: '快捷键' },
425
+ tabs: { ai: 'AI', appearance: '外观', knowledge: '知识库', sync: '同步', mcp: 'MCP', plugins: '插件', shortcuts: '快捷键' },
335
426
  ai: {
336
427
  provider: '服务商',
337
428
  model: '模型',
@@ -376,14 +467,19 @@ export const messages = {
376
467
  sync: {
377
468
  emptyTitle: '跨设备同步',
378
469
  emptyDesc: '通过 Git 自动同步知识库到所有设备。',
379
- emptyStepsTitle: '配置步骤',
380
- emptyStep1: '创建一个私有 Git 仓库(GitHub、GitLab 等),或使用现有仓库。',
381
- emptyStep2: '在终端中运行以下命令:',
382
- emptyStep3: '按照提示连接仓库,同步将自动开始。',
383
470
  featureAutoCommit: '保存时自动提交',
384
471
  featureAutoPull: '自动拉取远程更新',
385
472
  featureConflict: '冲突检测',
386
473
  featureMultiDevice: '多设备同步',
474
+ remoteUrl: 'Git 远程仓库 URL',
475
+ invalidUrl: '无效的 Git URL — 请使用 HTTPS (https://...) 或 SSH (git@...) 格式',
476
+ sshHint: 'SSH URL 需要在本机配置 SSH 密钥。推荐使用 HTTPS + Token。',
477
+ accessToken: '访问令牌',
478
+ optional: '(可选,私有仓库需要)',
479
+ tokenHint: 'GitHub: Settings → Developer settings → Personal access tokens → repo scope',
480
+ branchLabel: '分支',
481
+ connectButton: '连接并开始同步',
482
+ connecting: '连接中...',
387
483
  },
388
484
  plugins: {
389
485
  title: '已安装渲染器',
@@ -394,6 +490,54 @@ export const messages = {
394
490
  noPlugins: '暂无渲染器。',
395
491
  comingSoon: '插件市场即将上线。',
396
492
  },
493
+ mcp: {
494
+ serverTitle: 'MindOS MCP 服务',
495
+ status: '状态',
496
+ running: '运行中',
497
+ stopped: '已停止',
498
+ transport: '传输方式',
499
+ endpoint: '端点',
500
+ tools: '工具',
501
+ toolsRegistered: (n: number) => `已注册 ${n} 个`,
502
+ auth: '认证',
503
+ authSet: '已设置 Token',
504
+ authNotSet: '未设置',
505
+ copyEndpoint: '复制端点',
506
+ copyConfig: '复制配置',
507
+ copied: '已复制!',
508
+ agentsTitle: 'Agent 配置',
509
+ agent: 'Agent',
510
+ scope: '范围',
511
+ project: '项目',
512
+ global: '全局',
513
+ installed: '已安装',
514
+ notInstalled: '未安装',
515
+ transportStdio: 'stdio(推荐)',
516
+ transportHttp: 'http',
517
+ httpUrl: 'MCP URL',
518
+ httpToken: '认证 Token',
519
+ installSelected: '安装选中',
520
+ installing: '安装中...',
521
+ installSuccess: (n: number) => `已配置 ${n} 个 agent`,
522
+ installFailed: '安装失败',
523
+ portLabel: 'MCP 端口',
524
+ portHint: '修改后需重启服务',
525
+ skillsTitle: 'Skills',
526
+ skillAutoLoaded: '每次请求自动加载',
527
+ skillSource: '来源',
528
+ skillBuiltin: '内置',
529
+ skillUser: '自定义',
530
+ addSkill: '+ 添加 Skill',
531
+ deleteSkill: '删除',
532
+ editSkill: '编辑',
533
+ saveSkill: '保存',
534
+ cancelSkill: '取消',
535
+ skillName: '名称',
536
+ skillDesc: '描述',
537
+ skillContent: '内容',
538
+ skillNameConflict: '同名 skill 已存在',
539
+ skillDeleteConfirm: (name: string) => `确定删除「${name}」?此操作不可撤销。`,
540
+ },
397
541
  save: '保存',
398
542
  saved: '已保存',
399
543
  saveFailed: '保存失败',
@@ -418,6 +562,30 @@ export const messages = {
418
562
  { keys: ['Esc'], description: '取消编辑 / 关闭弹窗' },
419
563
  { keys: ['@'], description: '在 AI 对话中添加附件' },
420
564
  ],
565
+ login: {
566
+ tagline: '人类在此思考,Agent 依此行动。',
567
+ subtitle: '请输入密码以继续',
568
+ passwordLabel: '密码',
569
+ passwordPlaceholder: '输入密码',
570
+ signIn: '登录',
571
+ signingIn: '登录中…',
572
+ incorrectPassword: '密码错误,请重试。',
573
+ connectionError: '连接错误,请重试。',
574
+ },
575
+ notFound: {
576
+ title: '文件未找到',
577
+ description: '该文件不存在于你的知识库中。',
578
+ createButton: '创建此文件',
579
+ creating: '创建中...',
580
+ goToParent: '返回上级目录',
581
+ goHome: '首页',
582
+ },
583
+ updateBanner: {
584
+ newVersion: (latest: string, current: string) => `MindOS v${latest} 可用(当前 v${current})`,
585
+ runUpdate: '终端运行',
586
+ orSee: '或',
587
+ releaseNotes: '查看更新说明',
588
+ },
421
589
  setup: {
422
590
  stepTitles: ['知识库', 'AI 服务商', '端口', '安全', '确认'],
423
591
  // Step 1
@@ -1,92 +1,23 @@
1
+ /**
2
+ * AUTO-GENERATED by scripts/gen-renderer-index.js — do not edit manually.
3
+ * To regenerate: node scripts/gen-renderer-index.js
4
+ */
1
5
  import { registerRenderer } from './registry';
2
- import { TodoRenderer } from '@/components/renderers/TodoRenderer';
3
- import { CsvRenderer } from '@/components/renderers/CsvRenderer';
4
- import { GraphRenderer } from '@/components/renderers/GraphRenderer';
5
- import { TimelineRenderer } from '@/components/renderers/TimelineRenderer';
6
- import { SummaryRenderer } from '@/components/renderers/SummaryRenderer';
7
- import { ConfigRenderer } from '@/components/renderers/ConfigRenderer';
8
- import { AgentInspectorRenderer } from '@/components/renderers/AgentInspectorRenderer';
6
+ import { manifest as agentInspector } from '@/components/renderers/agent-inspector/manifest';
7
+ import { manifest as backlinks } from '@/components/renderers/backlinks/manifest';
8
+ import { manifest as config } from '@/components/renderers/config/manifest';
9
+ import { manifest as csv } from '@/components/renderers/csv/manifest';
10
+ import { manifest as diff } from '@/components/renderers/diff/manifest';
11
+ import { manifest as graph } from '@/components/renderers/graph/manifest';
12
+ import { manifest as summary } from '@/components/renderers/summary/manifest';
13
+ import { manifest as timeline } from '@/components/renderers/timeline/manifest';
14
+ import { manifest as todo } from '@/components/renderers/todo/manifest';
15
+ import { manifest as workflow } from '@/components/renderers/workflow/manifest';
9
16
 
10
- registerRenderer({
11
- id: 'todo',
12
- name: 'TODO Board',
13
- description: 'Renders TODO.md/TODO.csv as an interactive kanban board grouped by section. Check items off directly — changes are written back to the source file.',
14
- author: 'MindOS',
15
- icon: '✅',
16
- tags: ['productivity', 'tasks', 'markdown'],
17
- builtin: true,
18
- match: ({ filePath }) => /\bTODO\b.*\.(md|csv)$/i.test(filePath),
19
- component: TodoRenderer,
20
- });
17
+ const manifests = [
18
+ agentInspector, backlinks, config, csv, diff, graph, summary, timeline, todo, workflow,
19
+ ];
21
20
 
22
- registerRenderer({
23
- id: 'csv',
24
- name: 'CSV Views',
25
- description: 'Renders any CSV file as Table, Gallery, or Board. Each view is independently configurable — choose which columns map to title, description, tag, and group.',
26
- author: 'MindOS',
27
- icon: '📊',
28
- tags: ['csv', 'table', 'gallery', 'board', 'data'],
29
- builtin: true,
30
- match: ({ extension, filePath }) => extension === 'csv' && !/\bTODO\b/i.test(filePath),
31
- component: CsvRenderer,
32
- });
33
-
34
- registerRenderer({
35
- id: 'config-panel',
36
- name: 'Config Panel',
37
- description: 'Renders CONFIG.json as an editable control panel based on uiSchema/keySpecs. Changes are written back to the JSON file directly.',
38
- author: 'MindOS',
39
- icon: '🧩',
40
- tags: ['config', 'json', 'settings', 'schema'],
41
- builtin: true,
42
- match: ({ filePath, extension }) => extension === 'json' && /(^|\/)CONFIG\.json$/i.test(filePath),
43
- component: ConfigRenderer,
44
- });
45
-
46
- registerRenderer({
47
- id: 'graph',
48
- name: 'Wiki Graph',
49
- description: 'Force-directed graph of wikilink references across all markdown files. Supports Global and Local (2-hop) scope filters.',
50
- author: 'MindOS',
51
- icon: '🕸️',
52
- tags: ['graph', 'wiki', 'links', 'visualization'],
53
- builtin: true,
54
- match: ({ extension }) => extension === 'md',
55
- component: GraphRenderer,
56
- });
57
-
58
- registerRenderer({
59
- id: 'timeline',
60
- name: 'Timeline',
61
- description: 'Renders changelog and journal files as a vertical timeline. Any markdown with ## date headings (e.g. ## 2025-01-15) becomes a card in the feed.',
62
- author: 'MindOS',
63
- icon: '📅',
64
- tags: ['timeline', 'changelog', 'journal', 'history'],
65
- builtin: true,
66
- match: ({ filePath }) => /\b(CHANGELOG|changelog|TIMELINE|timeline|journal|Journal|diary|Diary)\b.*\.md$/i.test(filePath),
67
- component: TimelineRenderer,
68
- });
69
-
70
- registerRenderer({
71
- id: 'summary',
72
- name: 'AI Briefing',
73
- description: 'Streams an AI-generated daily briefing summarizing your most recently modified files — key changes, recurring themes, and suggested next actions.',
74
- author: 'MindOS',
75
- icon: '✨',
76
- tags: ['ai', 'summary', 'briefing', 'daily'],
77
- builtin: true,
78
- match: ({ filePath }) => /\b(SUMMARY|summary|Summary|BRIEFING|briefing|Briefing|DAILY|daily|Daily)\b.*\.md$/i.test(filePath),
79
- component: SummaryRenderer,
80
- });
81
-
82
- registerRenderer({
83
- id: 'agent-inspector',
84
- name: 'Agent Inspector',
85
- description: 'Visualizes agent tool-call logs as a filterable timeline. Auto-activates on .agent-log.json (JSON Lines format).',
86
- author: 'MindOS',
87
- icon: '🔍',
88
- tags: ['agent', 'inspector', 'log', 'mcp', 'tools'],
89
- builtin: true,
90
- match: ({ filePath }) => /\.agent-log\.json$/i.test(filePath),
91
- component: AgentInspectorRenderer,
92
- });
21
+ for (const m of manifests) {
22
+ registerRenderer(m);
23
+ }
@@ -15,8 +15,11 @@ export interface RendererDefinition {
15
15
  icon: string; // emoji or short string
16
16
  tags: string[];
17
17
  builtin: boolean; // true = ships with MindOS; false = user-installed (future)
18
+ entryPath?: string; // canonical entry file shown on home page (e.g. 'TODO.md')
18
19
  match: (ctx: Pick<RendererContext, 'filePath' | 'extension'>) => boolean;
19
- component: ComponentType<RendererContext>;
20
+ // Provide either `component` (eager) or `load` (lazy). Prefer `load` for code-splitting.
21
+ component?: ComponentType<RendererContext>;
22
+ load?: () => Promise<{ default: ComponentType<RendererContext> }>;
20
23
  }
21
24
 
22
25
  const registry: RendererDefinition[] = [];
@@ -27,6 +27,7 @@ export interface ServerSettings {
27
27
  webPassword?: string;
28
28
  startMode?: 'dev' | 'start' | 'daemon';
29
29
  setupPending?: boolean; // true → / redirects to /setup
30
+ disabledSkills?: string[];
30
31
  }
31
32
 
32
33
  const DEFAULTS: ServerSettings = {
@@ -111,6 +112,7 @@ export function readSettings(): ServerSettings {
111
112
  port: typeof parsed.port === 'number' ? parsed.port : undefined,
112
113
  startMode: typeof parsed.startMode === 'string' ? parsed.startMode as ServerSettings['startMode'] : undefined,
113
114
  setupPending: parsed.setupPending === true ? true : undefined,
115
+ disabledSkills: Array.isArray(parsed.disabledSkills) ? parsed.disabledSkills as string[] : undefined,
114
116
  };
115
117
  } catch {
116
118
  return { ...DEFAULTS, ai: { ...DEFAULTS.ai, providers: { ...DEFAULTS.ai.providers } } };
@@ -129,6 +131,7 @@ export function writeSettings(settings: ServerSettings): void {
129
131
  if (settings.port !== undefined) merged.port = settings.port;
130
132
  if (settings.mcpPort !== undefined) merged.mcpPort = settings.mcpPort;
131
133
  if (settings.startMode !== undefined) merged.startMode = settings.startMode;
134
+ if (settings.disabledSkills !== undefined) merged.disabledSkills = settings.disabledSkills;
132
135
  // setupPending: false/undefined → remove the field (cleanup); true → set it
133
136
  if ('setupPending' in settings) {
134
137
  if (settings.setupPending) merged.setupPending = true;