@geminilight/mindos 0.2.1 → 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 (82) hide show
  1. package/app/app/api/init/route.ts +7 -41
  2. package/app/app/api/mcp/agents/route.ts +72 -0
  3. package/app/app/api/mcp/install/route.ts +95 -0
  4. package/app/app/api/mcp/status/route.ts +47 -0
  5. package/app/app/api/settings/route.ts +3 -0
  6. package/app/app/api/setup/generate-token/route.ts +23 -0
  7. package/app/app/api/setup/route.ts +81 -0
  8. package/app/app/api/skills/route.ts +208 -0
  9. package/app/app/api/sync/route.ts +54 -3
  10. package/app/app/api/update-check/route.ts +52 -0
  11. package/app/app/globals.css +12 -0
  12. package/app/app/layout.tsx +4 -2
  13. package/app/app/login/page.tsx +20 -13
  14. package/app/app/page.tsx +22 -2
  15. package/app/app/setup/page.tsx +9 -0
  16. package/app/app/view/[...path]/ViewPageClient.tsx +47 -21
  17. package/app/app/view/[...path]/loading.tsx +1 -1
  18. package/app/app/view/[...path]/not-found.tsx +101 -0
  19. package/app/components/AskFab.tsx +1 -1
  20. package/app/components/AskModal.tsx +1 -1
  21. package/app/components/Backlinks.tsx +1 -1
  22. package/app/components/Breadcrumb.tsx +13 -3
  23. package/app/components/CsvView.tsx +5 -6
  24. package/app/components/DirView.tsx +42 -21
  25. package/app/components/FindInPage.tsx +211 -0
  26. package/app/components/HomeContent.tsx +97 -44
  27. package/app/components/JsonView.tsx +1 -2
  28. package/app/components/MarkdownEditor.tsx +1 -2
  29. package/app/components/OnboardingView.tsx +6 -7
  30. package/app/components/SettingsModal.tsx +5 -2
  31. package/app/components/SetupWizard.tsx +479 -0
  32. package/app/components/Sidebar.tsx +1 -1
  33. package/app/components/UpdateBanner.tsx +101 -0
  34. package/app/components/renderers/{AgentInspectorRenderer.tsx → agent-inspector/AgentInspectorRenderer.tsx} +13 -11
  35. package/app/components/renderers/agent-inspector/manifest.ts +14 -0
  36. package/app/components/renderers/{BacklinksRenderer.tsx → backlinks/BacklinksRenderer.tsx} +6 -6
  37. package/app/components/renderers/backlinks/manifest.ts +14 -0
  38. package/app/components/renderers/config/manifest.ts +14 -0
  39. package/app/components/renderers/csv/BoardView.tsx +12 -12
  40. package/app/components/renderers/csv/ConfigPanel.tsx +7 -8
  41. package/app/components/renderers/{CsvRenderer.tsx → csv/CsvRenderer.tsx} +8 -9
  42. package/app/components/renderers/csv/GalleryView.tsx +3 -3
  43. package/app/components/renderers/csv/TableView.tsx +4 -5
  44. package/app/components/renderers/csv/manifest.ts +14 -0
  45. package/app/components/renderers/{DiffRenderer.tsx → diff/DiffRenderer.tsx} +10 -9
  46. package/app/components/renderers/diff/manifest.ts +14 -0
  47. package/app/components/renderers/{GraphRenderer.tsx → graph/GraphRenderer.tsx} +4 -5
  48. package/app/components/renderers/graph/manifest.ts +14 -0
  49. package/app/components/renderers/{SummaryRenderer.tsx → summary/SummaryRenderer.tsx} +6 -6
  50. package/app/components/renderers/summary/manifest.ts +14 -0
  51. package/app/components/renderers/{TimelineRenderer.tsx → timeline/TimelineRenderer.tsx} +6 -6
  52. package/app/components/renderers/timeline/manifest.ts +14 -0
  53. package/app/components/renderers/{TodoRenderer.tsx → todo/TodoRenderer.tsx} +2 -2
  54. package/app/components/renderers/todo/manifest.ts +14 -0
  55. package/app/components/renderers/{WorkflowRenderer.tsx → workflow/WorkflowRenderer.tsx} +13 -13
  56. package/app/components/renderers/workflow/manifest.ts +14 -0
  57. package/app/components/settings/McpTab.tsx +549 -0
  58. package/app/components/settings/SyncTab.tsx +139 -50
  59. package/app/components/settings/types.ts +1 -1
  60. package/app/data/pages/home.png +0 -0
  61. package/app/lib/i18n.ts +270 -10
  62. package/app/lib/renderers/index.ts +20 -89
  63. package/app/lib/renderers/registry.ts +4 -1
  64. package/app/lib/settings.ts +15 -1
  65. package/app/lib/template.ts +45 -0
  66. package/app/package.json +1 -0
  67. package/app/types/semver.d.ts +8 -0
  68. package/bin/cli.js +137 -24
  69. package/bin/lib/build.js +53 -18
  70. package/bin/lib/colors.js +3 -1
  71. package/bin/lib/config.js +4 -0
  72. package/bin/lib/constants.js +2 -0
  73. package/bin/lib/debug.js +10 -0
  74. package/bin/lib/startup.js +21 -20
  75. package/bin/lib/stop.js +41 -3
  76. package/bin/lib/sync.js +65 -53
  77. package/bin/lib/update-check.js +94 -0
  78. package/bin/lib/utils.js +2 -2
  79. package/package.json +1 -1
  80. package/scripts/gen-renderer-index.js +57 -0
  81. package/scripts/setup.js +117 -1
  82. /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,76 @@ 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
+ },
273
+ setup: {
274
+ stepTitles: ['Knowledge Base', 'AI Provider', 'Ports', 'Security', 'Review'],
275
+ // Step 1
276
+ kbPath: 'Knowledge base path',
277
+ kbPathHint: 'Absolute path to your notes directory.',
278
+ kbPathDefault: '~/MindOS',
279
+ template: 'Starter template',
280
+ templateSkip: 'Skip (directory already has files)',
281
+ // Step 2
282
+ aiProvider: 'AI Provider',
283
+ aiProviderHint: 'Choose your preferred AI service.',
284
+ aiSkip: 'Skip — configure later',
285
+ apiKey: 'API Key',
286
+ model: 'Model',
287
+ baseUrl: 'Base URL',
288
+ baseUrlHint: 'Optional. For proxies or OpenAI-compatible APIs.',
289
+ // Step 3
290
+ webPort: 'Web UI port',
291
+ mcpPort: 'MCP server port',
292
+ portHint: 'Valid range: 1024–65535',
293
+ portRestartWarning: 'Port changes take effect after server restart.',
294
+ // Step 4
295
+ authToken: 'Auth Token',
296
+ authTokenHint: 'Bearer token for MCP / API clients. Auto-generated.',
297
+ authTokenSeed: 'Custom seed (optional)',
298
+ authTokenSeedHint: 'Enter a passphrase to derive a deterministic token.',
299
+ generateToken: 'Generate',
300
+ copyToken: 'Copy',
301
+ copiedToken: 'Copied!',
302
+ webPassword: 'Web UI Password',
303
+ webPasswordHint: 'Optional. Protect browser access with a password.',
304
+ // Step 5
305
+ reviewTitle: 'Review Configuration',
306
+ reviewHint: 'Verify your settings before completing setup.',
307
+ keyMasked: (key: string) => key.slice(0, 6) + '•••' + key.slice(-3),
308
+ portChanged: 'Port changed — please restart the server for it to take effect.',
309
+ // Buttons
310
+ back: 'Back',
311
+ next: 'Next',
312
+ complete: 'Complete Setup',
313
+ skip: 'Skip',
314
+ // Status
315
+ completing: 'Saving...',
316
+ completeDone: 'Setup complete!',
317
+ completeFailed: 'Setup failed. Please try again.',
318
+ },
189
319
  },
190
320
  zh: {
191
321
  common: {
@@ -202,6 +332,7 @@ export const messages = {
202
332
  plugins: '插件',
203
333
  showMore: '查看更多',
204
334
  showLess: '收起',
335
+ createToActivate: '创建 {file} 以启用此插件',
205
336
  shortcuts: {
206
337
  searchFiles: '搜索文件',
207
338
  askAI: '问 AI',
@@ -282,10 +413,16 @@ export const messages = {
282
413
  listView: '列表视图',
283
414
  emptyFolder: '此目录为空。',
284
415
  fileCount: (n: number) => `${n} 个文件`,
416
+ newFile: '新建文件',
417
+ },
418
+ findInPage: {
419
+ placeholder: '在文档中查找…',
420
+ matchCount: (current: number, total: number) => `${current} / ${total}`,
421
+ noResults: '无结果',
285
422
  },
286
423
  settings: {
287
424
  title: '设置',
288
- tabs: { ai: 'AI', appearance: '外观', knowledge: '知识库', sync: '同步', plugins: '插件', shortcuts: '快捷键' },
425
+ tabs: { ai: 'AI', appearance: '外观', knowledge: '知识库', sync: '同步', mcp: 'MCP', plugins: '插件', shortcuts: '快捷键' },
289
426
  ai: {
290
427
  provider: '服务商',
291
428
  model: '模型',
@@ -330,14 +467,19 @@ export const messages = {
330
467
  sync: {
331
468
  emptyTitle: '跨设备同步',
332
469
  emptyDesc: '通过 Git 自动同步知识库到所有设备。',
333
- emptyStepsTitle: '配置步骤',
334
- emptyStep1: '创建一个私有 Git 仓库(GitHub、GitLab 等),或使用现有仓库。',
335
- emptyStep2: '在终端中运行以下命令:',
336
- emptyStep3: '按照提示连接仓库,同步将自动开始。',
337
470
  featureAutoCommit: '保存时自动提交',
338
471
  featureAutoPull: '自动拉取远程更新',
339
472
  featureConflict: '冲突检测',
340
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: '连接中...',
341
483
  },
342
484
  plugins: {
343
485
  title: '已安装渲染器',
@@ -348,6 +490,54 @@ export const messages = {
348
490
  noPlugins: '暂无渲染器。',
349
491
  comingSoon: '插件市场即将上线。',
350
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
+ },
351
541
  save: '保存',
352
542
  saved: '已保存',
353
543
  saveFailed: '保存失败',
@@ -372,6 +562,76 @@ export const messages = {
372
562
  { keys: ['Esc'], description: '取消编辑 / 关闭弹窗' },
373
563
  { keys: ['@'], description: '在 AI 对话中添加附件' },
374
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
+ },
589
+ setup: {
590
+ stepTitles: ['知识库', 'AI 服务商', '端口', '安全', '确认'],
591
+ // Step 1
592
+ kbPath: '知识库路径',
593
+ kbPathHint: '笔记目录的绝对路径。',
594
+ kbPathDefault: '~/MindOS',
595
+ template: '初始模板',
596
+ templateSkip: '跳过(目录已有文件)',
597
+ // Step 2
598
+ aiProvider: 'AI 服务商',
599
+ aiProviderHint: '选择你偏好的 AI 服务。',
600
+ aiSkip: '跳过 — 稍后配置',
601
+ apiKey: 'API 密钥',
602
+ model: '模型',
603
+ baseUrl: '接口地址',
604
+ baseUrlHint: '可选。用于代理或 OpenAI 兼容 API。',
605
+ // Step 3
606
+ webPort: 'Web UI 端口',
607
+ mcpPort: 'MCP 服务端口',
608
+ portHint: '有效范围:1024–65535',
609
+ portRestartWarning: '端口修改需重启服务后生效。',
610
+ // Step 4
611
+ authToken: 'Auth Token',
612
+ authTokenHint: 'MCP / API 客户端使用的 Bearer Token,自动生成。',
613
+ authTokenSeed: '自定义种子(可选)',
614
+ authTokenSeedHint: '输入口令短语生成确定性 Token。',
615
+ generateToken: '生成',
616
+ copyToken: '复制',
617
+ copiedToken: '已复制!',
618
+ webPassword: '网页访问密码',
619
+ webPasswordHint: '可选。设置后浏览器访问需要登录。',
620
+ // Step 5
621
+ reviewTitle: '确认配置',
622
+ reviewHint: '完成设置前请确认以下信息。',
623
+ keyMasked: (key: string) => key.slice(0, 6) + '•••' + key.slice(-3),
624
+ portChanged: '端口已变更 — 请重启服务以使其生效。',
625
+ // Buttons
626
+ back: '上一步',
627
+ next: '下一步',
628
+ complete: '完成设置',
629
+ skip: '跳过',
630
+ // Status
631
+ completing: '保存中...',
632
+ completeDone: '设置完成!',
633
+ completeFailed: '设置失败,请重试。',
634
+ },
375
635
  },
376
636
  } as const;
377
637