@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.
- package/app/app/api/mcp/agents/route.ts +72 -0
- package/app/app/api/mcp/install/route.ts +95 -0
- package/app/app/api/mcp/status/route.ts +47 -0
- package/app/app/api/skills/route.ts +208 -0
- package/app/app/api/sync/route.ts +54 -3
- package/app/app/api/update-check/route.ts +52 -0
- package/app/app/globals.css +12 -0
- package/app/app/layout.tsx +4 -2
- package/app/app/login/page.tsx +20 -13
- package/app/app/page.tsx +17 -2
- package/app/app/view/[...path]/ViewPageClient.tsx +47 -21
- package/app/app/view/[...path]/loading.tsx +1 -1
- package/app/app/view/[...path]/not-found.tsx +101 -0
- package/app/components/AskFab.tsx +1 -1
- package/app/components/AskModal.tsx +1 -1
- package/app/components/Backlinks.tsx +1 -1
- package/app/components/Breadcrumb.tsx +13 -3
- package/app/components/CsvView.tsx +5 -6
- package/app/components/DirView.tsx +42 -21
- package/app/components/FindInPage.tsx +211 -0
- package/app/components/HomeContent.tsx +97 -44
- package/app/components/JsonView.tsx +1 -2
- package/app/components/MarkdownEditor.tsx +1 -2
- package/app/components/OnboardingView.tsx +6 -7
- package/app/components/SettingsModal.tsx +5 -2
- package/app/components/SetupWizard.tsx +4 -4
- package/app/components/Sidebar.tsx +1 -1
- package/app/components/UpdateBanner.tsx +101 -0
- package/app/components/renderers/{AgentInspectorRenderer.tsx → agent-inspector/AgentInspectorRenderer.tsx} +13 -11
- package/app/components/renderers/agent-inspector/manifest.ts +14 -0
- package/app/components/renderers/{BacklinksRenderer.tsx → backlinks/BacklinksRenderer.tsx} +6 -6
- package/app/components/renderers/backlinks/manifest.ts +14 -0
- package/app/components/renderers/config/manifest.ts +14 -0
- package/app/components/renderers/csv/BoardView.tsx +12 -12
- package/app/components/renderers/csv/ConfigPanel.tsx +7 -8
- package/app/components/renderers/{CsvRenderer.tsx → csv/CsvRenderer.tsx} +8 -9
- package/app/components/renderers/csv/GalleryView.tsx +3 -3
- package/app/components/renderers/csv/TableView.tsx +4 -5
- package/app/components/renderers/csv/manifest.ts +14 -0
- package/app/components/renderers/{DiffRenderer.tsx → diff/DiffRenderer.tsx} +10 -9
- package/app/components/renderers/diff/manifest.ts +14 -0
- package/app/components/renderers/{GraphRenderer.tsx → graph/GraphRenderer.tsx} +4 -5
- package/app/components/renderers/graph/manifest.ts +14 -0
- package/app/components/renderers/{SummaryRenderer.tsx → summary/SummaryRenderer.tsx} +6 -6
- package/app/components/renderers/summary/manifest.ts +14 -0
- package/app/components/renderers/{TimelineRenderer.tsx → timeline/TimelineRenderer.tsx} +6 -6
- package/app/components/renderers/timeline/manifest.ts +14 -0
- package/app/components/renderers/{TodoRenderer.tsx → todo/TodoRenderer.tsx} +2 -2
- package/app/components/renderers/todo/manifest.ts +14 -0
- package/app/components/renderers/{WorkflowRenderer.tsx → workflow/WorkflowRenderer.tsx} +13 -13
- package/app/components/renderers/workflow/manifest.ts +14 -0
- package/app/components/settings/McpTab.tsx +549 -0
- package/app/components/settings/SyncTab.tsx +139 -50
- package/app/components/settings/types.ts +1 -1
- package/app/data/pages/home.png +0 -0
- package/app/lib/i18n.ts +178 -10
- package/app/lib/renderers/index.ts +20 -89
- package/app/lib/renderers/registry.ts +4 -1
- package/app/lib/settings.ts +3 -0
- package/app/package.json +1 -0
- package/app/types/semver.d.ts +8 -0
- package/bin/cli.js +137 -24
- package/bin/lib/build.js +53 -18
- package/bin/lib/colors.js +3 -1
- package/bin/lib/config.js +4 -0
- package/bin/lib/constants.js +2 -0
- package/bin/lib/debug.js +10 -0
- package/bin/lib/startup.js +21 -20
- package/bin/lib/stop.js +41 -3
- package/bin/lib/sync.js +65 -53
- package/bin/lib/update-check.js +94 -0
- package/bin/lib/utils.js +2 -2
- package/package.json +1 -1
- package/scripts/gen-renderer-index.js +57 -0
- package/scripts/setup.js +24 -0
- /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,
|
|
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
|
-
/* ──
|
|
35
|
+
/* ── Empty state — GUI sync init form ─────────────────────────── */
|
|
36
36
|
|
|
37
|
-
function
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
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
|
-
|
|
57
|
-
|
|
58
|
-
function SyncEmptyState({ t }: { t: any }) {
|
|
43
|
+
function SyncEmptyState({ t, onInitComplete }: { t: any; onInitComplete: () => void }) {
|
|
59
44
|
const syncT = t.settings?.sync;
|
|
60
|
-
|
|
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-
|
|
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
|
-
{/*
|
|
80
|
-
<div className="space-y-
|
|
81
|
-
<
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
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)' },
|
package/app/data/pages/home.png
CHANGED
|
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 {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
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
|
-
|
|
11
|
-
|
|
12
|
-
|
|
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
|
-
|
|
23
|
-
|
|
24
|
-
|
|
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
|
|
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[] = [];
|
package/app/lib/settings.ts
CHANGED
|
@@ -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;
|