@geminilight/mindos 0.5.2 → 0.5.5
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 +1 -0
- package/app/app/api/mcp/install/route.ts +62 -7
- package/app/app/api/mcp/install-skill/route.ts +96 -0
- package/app/app/api/setup/route.ts +5 -2
- package/app/app/view/[...path]/ViewPageClient.tsx +4 -20
- package/app/components/SetupWizard.tsx +136 -16
- package/app/components/renderers/config/manifest.ts +1 -0
- package/app/components/renderers/csv/CsvRenderer.tsx +6 -12
- package/app/components/renderers/csv/manifest.ts +1 -0
- package/app/components/renderers/todo/manifest.ts +1 -0
- package/app/components/settings/McpTab.tsx +85 -7
- package/app/components/settings/PluginsTab.tsx +31 -16
- package/app/lib/i18n.ts +36 -6
- package/app/lib/mcp-agents.ts +10 -9
- package/app/lib/renderers/registry.ts +7 -0
- package/app/lib/renderers/useRendererState.ts +114 -0
- package/bin/cli.js +3 -1
- package/bin/lib/gateway.js +1 -1
- package/bin/lib/mcp-agents.js +9 -9
- package/package.json +1 -1
- package/scripts/setup.js +121 -18
- package/skills/human-insights/SKILL.md +0 -143
|
@@ -4,9 +4,15 @@ import fs from 'fs';
|
|
|
4
4
|
import path from 'path';
|
|
5
5
|
import { MCP_AGENTS, expandHome } from '@/lib/mcp-agents';
|
|
6
6
|
|
|
7
|
+
interface AgentInstallItem {
|
|
8
|
+
key: string;
|
|
9
|
+
scope: 'project' | 'global';
|
|
10
|
+
transport?: 'stdio' | 'http';
|
|
11
|
+
}
|
|
12
|
+
|
|
7
13
|
interface InstallRequest {
|
|
8
|
-
agents:
|
|
9
|
-
transport: 'stdio' | 'http';
|
|
14
|
+
agents: AgentInstallItem[];
|
|
15
|
+
transport: 'stdio' | 'http' | 'auto';
|
|
10
16
|
url?: string;
|
|
11
17
|
token?: string;
|
|
12
18
|
}
|
|
@@ -20,20 +26,59 @@ function buildEntry(transport: string, url?: string, token?: string) {
|
|
|
20
26
|
return entry;
|
|
21
27
|
}
|
|
22
28
|
|
|
29
|
+
async function verifyHttpConnection(url: string, token?: string): Promise<{ verified: boolean; verifyError?: string }> {
|
|
30
|
+
try {
|
|
31
|
+
const headers: Record<string, string> = { 'Content-Type': 'application/json' };
|
|
32
|
+
if (token) headers['Authorization'] = `Bearer ${token}`;
|
|
33
|
+
const controller = new AbortController();
|
|
34
|
+
const timeout = setTimeout(() => controller.abort(), 2000);
|
|
35
|
+
const res = await fetch(url, {
|
|
36
|
+
method: 'POST',
|
|
37
|
+
headers,
|
|
38
|
+
body: JSON.stringify({ jsonrpc: '2.0', id: 1, method: 'tools/list', params: {} }),
|
|
39
|
+
signal: controller.signal,
|
|
40
|
+
});
|
|
41
|
+
clearTimeout(timeout);
|
|
42
|
+
if (res.ok) return { verified: true };
|
|
43
|
+
return { verified: false, verifyError: `HTTP ${res.status}` };
|
|
44
|
+
} catch (err) {
|
|
45
|
+
return { verified: false, verifyError: err instanceof Error ? err.message : String(err) };
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
23
49
|
export async function POST(req: NextRequest) {
|
|
24
50
|
try {
|
|
25
51
|
const body = (await req.json()) as InstallRequest;
|
|
26
|
-
const { agents, transport, url, token } = body;
|
|
27
|
-
const
|
|
28
|
-
|
|
52
|
+
const { agents, transport: globalTransport, url, token } = body;
|
|
53
|
+
const results: Array<{
|
|
54
|
+
agent: string;
|
|
55
|
+
status: string;
|
|
56
|
+
path?: string;
|
|
57
|
+
message?: string;
|
|
58
|
+
transport?: string;
|
|
59
|
+
verified?: boolean;
|
|
60
|
+
verifyError?: string;
|
|
61
|
+
}> = [];
|
|
29
62
|
|
|
30
|
-
for (const
|
|
63
|
+
for (const item of agents) {
|
|
64
|
+
const { key, scope } = item;
|
|
31
65
|
const agent = MCP_AGENTS[key];
|
|
32
66
|
if (!agent) {
|
|
33
67
|
results.push({ agent: key, status: 'error', message: `Unknown agent: ${key}` });
|
|
34
68
|
continue;
|
|
35
69
|
}
|
|
36
70
|
|
|
71
|
+
// Resolve effective transport: agent-level > global-level > auto (use preferredTransport)
|
|
72
|
+
let effectiveTransport: 'stdio' | 'http';
|
|
73
|
+
if (item.transport && item.transport !== 'auto' as string) {
|
|
74
|
+
effectiveTransport = item.transport;
|
|
75
|
+
} else if (globalTransport && globalTransport !== 'auto') {
|
|
76
|
+
effectiveTransport = globalTransport;
|
|
77
|
+
} else {
|
|
78
|
+
effectiveTransport = agent.preferredTransport;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const entry = buildEntry(effectiveTransport, url, token);
|
|
37
82
|
const isGlobal = scope === 'global';
|
|
38
83
|
const configPath = isGlobal ? agent.global : agent.project;
|
|
39
84
|
if (!configPath) {
|
|
@@ -59,7 +104,17 @@ export async function POST(req: NextRequest) {
|
|
|
59
104
|
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
|
|
60
105
|
fs.writeFileSync(absPath, JSON.stringify(config, null, 2) + '\n', 'utf-8');
|
|
61
106
|
|
|
62
|
-
results
|
|
107
|
+
const result: typeof results[number] = { agent: key, status: 'ok', path: configPath, transport: effectiveTransport };
|
|
108
|
+
|
|
109
|
+
// Verify http connections
|
|
110
|
+
if (effectiveTransport === 'http') {
|
|
111
|
+
const mcpUrl = (entry as Record<string, unknown>).url as string;
|
|
112
|
+
const verification = await verifyHttpConnection(mcpUrl, token);
|
|
113
|
+
result.verified = verification.verified;
|
|
114
|
+
if (verification.verifyError) result.verifyError = verification.verifyError;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
results.push(result);
|
|
63
118
|
} catch (err) {
|
|
64
119
|
results.push({ agent: key, status: 'error', message: String(err) });
|
|
65
120
|
}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
export const dynamic = 'force-dynamic';
|
|
2
|
+
import { NextRequest, NextResponse } from 'next/server';
|
|
3
|
+
import { execSync } from 'child_process';
|
|
4
|
+
import path from 'path';
|
|
5
|
+
|
|
6
|
+
/* ── Agent classification ──────────────────────────────────────── */
|
|
7
|
+
|
|
8
|
+
// Universal agents read directly from ~/.agents/skills/ — no symlink needed.
|
|
9
|
+
const UNIVERSAL_AGENTS = new Set([
|
|
10
|
+
'amp', 'cline', 'codex', 'cursor', 'gemini-cli',
|
|
11
|
+
'github-copilot', 'kimi-cli', 'opencode', 'warp',
|
|
12
|
+
]);
|
|
13
|
+
|
|
14
|
+
// Agents that do NOT support Skills at all
|
|
15
|
+
const SKILL_UNSUPPORTED = new Set(['claude-desktop']);
|
|
16
|
+
|
|
17
|
+
// MCP agent key → npx skills agent name (for non-universal agents)
|
|
18
|
+
// Keys not listed here and not in UNIVERSAL/UNSUPPORTED will use the key as-is.
|
|
19
|
+
const AGENT_NAME_MAP: Record<string, string> = {
|
|
20
|
+
'claude-code': 'claude-code',
|
|
21
|
+
'windsurf': 'windsurf',
|
|
22
|
+
'trae': 'trae',
|
|
23
|
+
'openclaw': 'openclaw',
|
|
24
|
+
'codebuddy': 'codebuddy',
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
/* ── POST handler ──────────────────────────────────────────────── */
|
|
28
|
+
|
|
29
|
+
interface SkillInstallRequest {
|
|
30
|
+
skill: 'mindos' | 'mindos-zh';
|
|
31
|
+
agents: string[];
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export async function POST(req: NextRequest) {
|
|
35
|
+
try {
|
|
36
|
+
const body: SkillInstallRequest = await req.json();
|
|
37
|
+
const { skill, agents } = body;
|
|
38
|
+
|
|
39
|
+
if (!skill || !['mindos', 'mindos-zh'].includes(skill)) {
|
|
40
|
+
return NextResponse.json({ error: 'Invalid skill name' }, { status: 400 });
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Source path: project root `skills/` directory
|
|
44
|
+
const source = path.resolve(process.cwd(), 'skills');
|
|
45
|
+
|
|
46
|
+
// Non-universal, skill-capable agents need explicit `-a` for symlink creation
|
|
47
|
+
const additionalAgents = (agents || [])
|
|
48
|
+
.filter(key => !UNIVERSAL_AGENTS.has(key) && !SKILL_UNSUPPORTED.has(key))
|
|
49
|
+
.map(key => AGENT_NAME_MAP[key] || key);
|
|
50
|
+
|
|
51
|
+
let cmd: string;
|
|
52
|
+
if (additionalAgents.length > 0) {
|
|
53
|
+
// Any -a command will also copy to ~/.agents/skills/ (Universal coverage)
|
|
54
|
+
cmd = `npx skills add "${source}" -s ${skill} -a ${additionalAgents.join(',')} -g -y`;
|
|
55
|
+
} else {
|
|
56
|
+
// Fallback: only install to ~/.agents/skills/ for Universal agents
|
|
57
|
+
cmd = `npx skills add "${source}" -s ${skill} -a universal -g -y`;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
let stdout = '';
|
|
61
|
+
let stderr = '';
|
|
62
|
+
try {
|
|
63
|
+
stdout = execSync(cmd, {
|
|
64
|
+
encoding: 'utf-8',
|
|
65
|
+
timeout: 30_000,
|
|
66
|
+
env: { ...process.env, NODE_ENV: 'production' },
|
|
67
|
+
stdio: 'pipe',
|
|
68
|
+
});
|
|
69
|
+
} catch (err: unknown) {
|
|
70
|
+
const e = err as { stdout?: string; stderr?: string; message?: string };
|
|
71
|
+
stdout = e.stdout || '';
|
|
72
|
+
stderr = e.stderr || e.message || 'Unknown error';
|
|
73
|
+
return NextResponse.json({
|
|
74
|
+
ok: false,
|
|
75
|
+
skill,
|
|
76
|
+
agents: additionalAgents,
|
|
77
|
+
cmd,
|
|
78
|
+
stdout,
|
|
79
|
+
stderr,
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
return NextResponse.json({
|
|
84
|
+
ok: true,
|
|
85
|
+
skill,
|
|
86
|
+
agents: additionalAgents,
|
|
87
|
+
cmd,
|
|
88
|
+
stdout: stdout.trim(),
|
|
89
|
+
});
|
|
90
|
+
} catch (e) {
|
|
91
|
+
return NextResponse.json(
|
|
92
|
+
{ error: e instanceof Error ? e.message : String(e) },
|
|
93
|
+
{ status: 500 },
|
|
94
|
+
);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
@@ -90,9 +90,10 @@ export async function POST(req: NextRequest) {
|
|
|
90
90
|
// Use the same resolved values that will actually be written to config
|
|
91
91
|
const resolvedAuthToken = authToken ?? current.authToken ?? '';
|
|
92
92
|
const resolvedWebPassword = webPassword ?? '';
|
|
93
|
-
//
|
|
93
|
+
// First-time onboard always needs restart (temporary setup port → user's chosen port).
|
|
94
|
+
// Re-onboard only needs restart if port/path/auth/password actually changed.
|
|
94
95
|
const isFirstTime = current.setupPending === true || !current.mindRoot;
|
|
95
|
-
const needsRestart =
|
|
96
|
+
const needsRestart = isFirstTime || (
|
|
96
97
|
webPort !== (current.port ?? 3000) ||
|
|
97
98
|
mcpPortNum !== (current.mcpPort ?? 8787) ||
|
|
98
99
|
resolvedRoot !== (current.mindRoot || '') ||
|
|
@@ -101,6 +102,7 @@ export async function POST(req: NextRequest) {
|
|
|
101
102
|
);
|
|
102
103
|
|
|
103
104
|
// Build config
|
|
105
|
+
const disabledSkills = body.template === 'zh' ? ['mindos'] : ['mindos-zh'];
|
|
104
106
|
const config: ServerSettings = {
|
|
105
107
|
ai: ai ?? current.ai,
|
|
106
108
|
mindRoot: resolvedRoot,
|
|
@@ -110,6 +112,7 @@ export async function POST(req: NextRequest) {
|
|
|
110
112
|
webPassword: webPassword ?? '',
|
|
111
113
|
startMode: current.startMode,
|
|
112
114
|
setupPending: false, // clear the flag
|
|
115
|
+
disabledSkills,
|
|
113
116
|
};
|
|
114
117
|
|
|
115
118
|
writeSettings(config);
|
|
@@ -8,6 +8,7 @@ import MarkdownView from '@/components/MarkdownView';
|
|
|
8
8
|
import JsonView from '@/components/JsonView';
|
|
9
9
|
import CsvView from '@/components/CsvView';
|
|
10
10
|
import Backlinks from '@/components/Backlinks';
|
|
11
|
+
import { useRendererState } from '@/lib/renderers/useRendererState';
|
|
11
12
|
import Breadcrumb from '@/components/Breadcrumb';
|
|
12
13
|
import MarkdownEditor, { MdViewMode } from '@/components/MarkdownEditor';
|
|
13
14
|
import TableOfContents from '@/components/TableOfContents';
|
|
@@ -45,22 +46,7 @@ export default function ViewPageClient({
|
|
|
45
46
|
() => false,
|
|
46
47
|
);
|
|
47
48
|
|
|
48
|
-
const useRaw =
|
|
49
|
-
(onStoreChange) => {
|
|
50
|
-
const listener = () => onStoreChange();
|
|
51
|
-
window.addEventListener('storage', listener);
|
|
52
|
-
window.addEventListener('mindos-use-raw-change', listener);
|
|
53
|
-
return () => {
|
|
54
|
-
window.removeEventListener('storage', listener);
|
|
55
|
-
window.removeEventListener('mindos-use-raw-change', listener);
|
|
56
|
-
};
|
|
57
|
-
},
|
|
58
|
-
() => {
|
|
59
|
-
const saved = localStorage.getItem('mindos-use-raw');
|
|
60
|
-
return saved !== null ? saved === 'true' : false;
|
|
61
|
-
},
|
|
62
|
-
() => false,
|
|
63
|
-
);
|
|
49
|
+
const [useRaw, setUseRaw] = useRendererState<boolean>('_raw', filePath, false);
|
|
64
50
|
const router = useRouter();
|
|
65
51
|
const [editing, setEditing] = useState(initialEditing || content === '');
|
|
66
52
|
const [editContent, setEditContent] = useState(content);
|
|
@@ -81,10 +67,8 @@ export default function ViewPageClient({
|
|
|
81
67
|
const effectiveUseRaw = hydrated ? useRaw : false;
|
|
82
68
|
|
|
83
69
|
const handleToggleRaw = useCallback(() => {
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
window.dispatchEvent(new Event('mindos-use-raw-change'));
|
|
87
|
-
}, [useRaw]);
|
|
70
|
+
setUseRaw(prev => !prev);
|
|
71
|
+
}, [setUseRaw]);
|
|
88
72
|
|
|
89
73
|
const renderer = resolveRenderer(filePath, extension);
|
|
90
74
|
const isCsv = extension === 'csv';
|
|
@@ -4,7 +4,7 @@ import { useState, useEffect, useCallback, useRef } from 'react';
|
|
|
4
4
|
import {
|
|
5
5
|
Sparkles, Globe, BookOpen, FileText, Copy, Check, RefreshCw,
|
|
6
6
|
Loader2, ChevronLeft, ChevronRight, AlertTriangle, CheckCircle2,
|
|
7
|
-
XCircle, Zap, Brain, SkipForward,
|
|
7
|
+
XCircle, Zap, Brain, SkipForward, Info,
|
|
8
8
|
} from 'lucide-react';
|
|
9
9
|
import { useLocale } from '@/lib/LocaleContext';
|
|
10
10
|
import { Field, Input, Select, ApiKeyInput } from '@/components/settings/Primitives';
|
|
@@ -39,12 +39,16 @@ interface AgentEntry {
|
|
|
39
39
|
installed: boolean;
|
|
40
40
|
hasProjectScope: boolean;
|
|
41
41
|
hasGlobalScope: boolean;
|
|
42
|
+
preferredTransport: 'stdio' | 'http';
|
|
42
43
|
}
|
|
43
44
|
|
|
44
45
|
type AgentInstallState = 'pending' | 'installing' | 'ok' | 'error';
|
|
45
46
|
interface AgentInstallStatus {
|
|
46
47
|
state: AgentInstallState;
|
|
47
48
|
message?: string;
|
|
49
|
+
transport?: string;
|
|
50
|
+
verified?: boolean;
|
|
51
|
+
verifyError?: string;
|
|
48
52
|
}
|
|
49
53
|
|
|
50
54
|
const TEMPLATES: Array<{ id: Template; icon: React.ReactNode; dirs: string[] }> = [
|
|
@@ -469,7 +473,7 @@ function Step3({
|
|
|
469
473
|
<p className="text-xs" style={{ color: 'var(--muted-foreground)' }}>{s.portVerifyHint}</p>
|
|
470
474
|
)}
|
|
471
475
|
<p className="text-xs flex items-center gap-1.5" style={{ color: 'var(--muted-foreground)' }}>
|
|
472
|
-
<
|
|
476
|
+
<Info size={12} /> {s.portRestartWarning}
|
|
473
477
|
</p>
|
|
474
478
|
</div>
|
|
475
479
|
);
|
|
@@ -479,19 +483,20 @@ function Step3({
|
|
|
479
483
|
function Step5({
|
|
480
484
|
agents, agentsLoading, selectedAgents, setSelectedAgents,
|
|
481
485
|
agentTransport, setAgentTransport, agentScope, setAgentScope,
|
|
482
|
-
agentStatuses, s, settingsMcp,
|
|
486
|
+
agentStatuses, s, settingsMcp, template,
|
|
483
487
|
}: {
|
|
484
488
|
agents: AgentEntry[];
|
|
485
489
|
agentsLoading: boolean;
|
|
486
490
|
selectedAgents: Set<string>;
|
|
487
491
|
setSelectedAgents: React.Dispatch<React.SetStateAction<Set<string>>>;
|
|
488
|
-
agentTransport: 'stdio' | 'http';
|
|
489
|
-
setAgentTransport: (v: 'stdio' | 'http') => void;
|
|
492
|
+
agentTransport: 'auto' | 'stdio' | 'http';
|
|
493
|
+
setAgentTransport: (v: 'auto' | 'stdio' | 'http') => void;
|
|
490
494
|
agentScope: 'global' | 'project';
|
|
491
495
|
setAgentScope: (v: 'global' | 'project') => void;
|
|
492
496
|
agentStatuses: Record<string, AgentInstallStatus>;
|
|
493
497
|
s: ReturnType<typeof useLocale>['t']['setup'];
|
|
494
498
|
settingsMcp: ReturnType<typeof useLocale>['t']['settings']['mcp'];
|
|
499
|
+
template: Template;
|
|
495
500
|
}) {
|
|
496
501
|
const toggleAgent = (key: string) => {
|
|
497
502
|
setSelectedAgents(prev => {
|
|
@@ -501,6 +506,11 @@ function Step5({
|
|
|
501
506
|
});
|
|
502
507
|
};
|
|
503
508
|
|
|
509
|
+
const getEffectiveTransport = (agent: AgentEntry) => {
|
|
510
|
+
if (agentTransport === 'auto') return agent.preferredTransport;
|
|
511
|
+
return agentTransport;
|
|
512
|
+
};
|
|
513
|
+
|
|
504
514
|
const getStatusBadge = (key: string, installed: boolean) => {
|
|
505
515
|
const st = agentStatuses[key];
|
|
506
516
|
if (st) {
|
|
@@ -567,13 +577,24 @@ function Step5({
|
|
|
567
577
|
disabled={agentStatuses[agent.key]?.state === 'installing'}
|
|
568
578
|
/>
|
|
569
579
|
<span className="text-sm flex-1" style={{ color: 'var(--foreground)' }}>{agent.name}</span>
|
|
580
|
+
<span className="text-[10px] px-1.5 py-0.5 rounded font-mono"
|
|
581
|
+
style={{ background: 'rgba(100,100,120,0.08)', color: 'var(--muted-foreground)' }}>
|
|
582
|
+
{getEffectiveTransport(agent)}
|
|
583
|
+
</span>
|
|
570
584
|
{getStatusBadge(agent.key, agent.installed)}
|
|
571
585
|
</label>
|
|
572
586
|
))}
|
|
573
587
|
</div>
|
|
588
|
+
{/* Skill auto-install hint */}
|
|
589
|
+
<div className="flex items-center gap-2 px-3 py-2 rounded-lg text-xs"
|
|
590
|
+
style={{ background: 'rgba(100,100,120,0.06)', color: 'var(--muted-foreground)' }}>
|
|
591
|
+
<Brain size={13} className="shrink-0" />
|
|
592
|
+
<span>{s.skillAutoHint(template === 'zh' ? 'mindos-zh' : 'mindos')}</span>
|
|
593
|
+
</div>
|
|
574
594
|
<div className="grid grid-cols-2 gap-4">
|
|
575
595
|
<Field label={s.agentTransport}>
|
|
576
|
-
<Select value={agentTransport} onChange={e => setAgentTransport(e.target.value as 'stdio' | 'http')}>
|
|
596
|
+
<Select value={agentTransport} onChange={e => setAgentTransport(e.target.value as 'auto' | 'stdio' | 'http')}>
|
|
597
|
+
<option value="auto">{s.agentTransportAuto}</option>
|
|
577
598
|
<option value="stdio">{settingsMcp.transportStdio}</option>
|
|
578
599
|
<option value="http">{settingsMcp.transportHttp}</option>
|
|
579
600
|
</Select>
|
|
@@ -615,8 +636,7 @@ function RestartBlock({ s, newPort }: { s: ReturnType<typeof useLocale>['t']['se
|
|
|
615
636
|
attempts++;
|
|
616
637
|
try {
|
|
617
638
|
const r = await fetch(`http://localhost:${newPort}/api/health`);
|
|
618
|
-
|
|
619
|
-
if (d.service === 'mindos') { clearInterval(poll); redirect(); return; }
|
|
639
|
+
if (r.status < 500) { clearInterval(poll); redirect(); return; }
|
|
620
640
|
} catch { /* not ready yet */ }
|
|
621
641
|
if (attempts >= 10) { clearInterval(poll); redirect(); }
|
|
622
642
|
}, 800);
|
|
@@ -661,6 +681,7 @@ function RestartBlock({ s, newPort }: { s: ReturnType<typeof useLocale>['t']['se
|
|
|
661
681
|
// ─── Step 6: Review ───────────────────────────────────────────────────────────
|
|
662
682
|
function Step6({
|
|
663
683
|
state, selectedAgents, agentStatuses, onRetryAgent, error, needsRestart, maskKey, s,
|
|
684
|
+
skillInstallResult,
|
|
664
685
|
}: {
|
|
665
686
|
state: SetupState;
|
|
666
687
|
selectedAgents: Set<string>;
|
|
@@ -670,7 +691,9 @@ function Step6({
|
|
|
670
691
|
needsRestart: boolean;
|
|
671
692
|
maskKey: (key: string) => string;
|
|
672
693
|
s: ReturnType<typeof useLocale>['t']['setup'];
|
|
694
|
+
skillInstallResult: { ok?: boolean; skill?: string; error?: string } | null;
|
|
673
695
|
}) {
|
|
696
|
+
const skillName = state.template === 'zh' ? 'mindos-zh' : 'mindos';
|
|
674
697
|
const rows: [string, string][] = [
|
|
675
698
|
[s.kbPath, state.mindRoot],
|
|
676
699
|
[s.template, state.template || '—'],
|
|
@@ -684,9 +707,11 @@ function Step6({
|
|
|
684
707
|
[s.authToken, state.authToken || '—'],
|
|
685
708
|
[s.webPassword, state.webPassword ? '••••••••' : '(none)'],
|
|
686
709
|
[s.agentToolsTitle, selectedAgents.size > 0 ? Array.from(selectedAgents).join(', ') : '—'],
|
|
710
|
+
[s.skillLabel, skillName],
|
|
687
711
|
];
|
|
688
712
|
|
|
689
713
|
const failedAgents = Object.entries(agentStatuses).filter(([, v]) => v.state === 'error');
|
|
714
|
+
const successAgents = Object.entries(agentStatuses).filter(([, v]) => v.state === 'ok');
|
|
690
715
|
|
|
691
716
|
return (
|
|
692
717
|
<div className="space-y-5">
|
|
@@ -703,6 +728,43 @@ function Step6({
|
|
|
703
728
|
</div>
|
|
704
729
|
))}
|
|
705
730
|
</div>
|
|
731
|
+
|
|
732
|
+
{/* Agent verification results */}
|
|
733
|
+
{successAgents.length > 0 && (
|
|
734
|
+
<div className="space-y-1.5">
|
|
735
|
+
<p className="text-xs font-medium" style={{ color: 'var(--muted-foreground)' }}>{s.reviewInstallResults}</p>
|
|
736
|
+
{successAgents.map(([key, st]) => (
|
|
737
|
+
<div key={key} className="flex items-center gap-2 text-xs px-3 py-1.5 rounded"
|
|
738
|
+
style={{ background: 'rgba(34,197,94,0.06)' }}>
|
|
739
|
+
<CheckCircle2 size={11} className="text-green-500 shrink-0" />
|
|
740
|
+
<span style={{ color: 'var(--foreground)' }}>{key}</span>
|
|
741
|
+
<span className="font-mono text-[10px] px-1 py-0.5 rounded"
|
|
742
|
+
style={{ background: 'rgba(100,100,120,0.08)', color: 'var(--muted-foreground)' }}>
|
|
743
|
+
{st.transport || 'stdio'}
|
|
744
|
+
</span>
|
|
745
|
+
{st.transport === 'http' ? (
|
|
746
|
+
st.verified ? (
|
|
747
|
+
<span className="text-[10px] px-1.5 py-0.5 rounded"
|
|
748
|
+
style={{ background: 'rgba(34,197,94,0.12)', color: '#22c55e' }}>
|
|
749
|
+
{s.agentVerified}
|
|
750
|
+
</span>
|
|
751
|
+
) : (
|
|
752
|
+
<span className="text-[10px] px-1.5 py-0.5 rounded"
|
|
753
|
+
style={{ background: 'rgba(239,168,68,0.12)', color: '#f59e0b' }}
|
|
754
|
+
title={st.verifyError}>
|
|
755
|
+
{s.agentUnverified}
|
|
756
|
+
</span>
|
|
757
|
+
)
|
|
758
|
+
) : (
|
|
759
|
+
<span className="text-[10px]" style={{ color: 'var(--muted-foreground)' }}>
|
|
760
|
+
{s.agentVerifyNote}
|
|
761
|
+
</span>
|
|
762
|
+
)}
|
|
763
|
+
</div>
|
|
764
|
+
))}
|
|
765
|
+
</div>
|
|
766
|
+
)}
|
|
767
|
+
|
|
706
768
|
{failedAgents.length > 0 && (
|
|
707
769
|
<div className="p-3 rounded-lg space-y-2" style={{ background: 'rgba(239,68,68,0.08)' }}>
|
|
708
770
|
<p className="text-xs font-medium" style={{ color: '#ef4444' }}>{s.reviewInstallResults}</p>
|
|
@@ -724,6 +786,22 @@ function Step6({
|
|
|
724
786
|
<p className="text-xs" style={{ color: 'var(--muted-foreground)' }}>{s.agentFailureNote}</p>
|
|
725
787
|
</div>
|
|
726
788
|
)}
|
|
789
|
+
{/* Skill install result */}
|
|
790
|
+
{skillInstallResult && (
|
|
791
|
+
<div className={`flex items-center gap-2 text-xs px-3 py-2 rounded-lg ${
|
|
792
|
+
skillInstallResult.ok ? '' : ''
|
|
793
|
+
}`} style={{
|
|
794
|
+
background: skillInstallResult.ok ? 'rgba(34,197,94,0.06)' : 'rgba(239,68,68,0.06)',
|
|
795
|
+
}}>
|
|
796
|
+
{skillInstallResult.ok ? (
|
|
797
|
+
<><CheckCircle2 size={11} className="text-green-500 shrink-0" />
|
|
798
|
+
<span style={{ color: 'var(--foreground)' }}>{s.skillInstalled} — {skillInstallResult.skill}</span></>
|
|
799
|
+
) : (
|
|
800
|
+
<><XCircle size={11} className="text-red-500 shrink-0" />
|
|
801
|
+
<span style={{ color: '#ef4444' }}>{s.skillFailed}{skillInstallResult.error ? `: ${skillInstallResult.error}` : ''}</span></>
|
|
802
|
+
)}
|
|
803
|
+
</div>
|
|
804
|
+
)}
|
|
727
805
|
{error && (
|
|
728
806
|
<div className="p-3 rounded-lg text-sm text-red-500" style={{ background: 'rgba(239,68,68,0.1)' }}>
|
|
729
807
|
{s.completeFailed}: {error}
|
|
@@ -799,9 +877,10 @@ export default function SetupWizard() {
|
|
|
799
877
|
const [agents, setAgents] = useState<AgentEntry[]>([]);
|
|
800
878
|
const [agentsLoading, setAgentsLoading] = useState(false);
|
|
801
879
|
const [selectedAgents, setSelectedAgents] = useState<Set<string>>(new Set());
|
|
802
|
-
const [agentTransport, setAgentTransport] = useState<'stdio' | 'http'>('
|
|
880
|
+
const [agentTransport, setAgentTransport] = useState<'auto' | 'stdio' | 'http'>('auto');
|
|
803
881
|
const [agentScope, setAgentScope] = useState<'global' | 'project'>('global');
|
|
804
882
|
const [agentStatuses, setAgentStatuses] = useState<Record<string, AgentInstallStatus>>({});
|
|
883
|
+
const [skillInstallResult, setSkillInstallResult] = useState<{ ok?: boolean; skill?: string; error?: string } | null>(null);
|
|
805
884
|
|
|
806
885
|
// Load existing config as defaults on mount, generate token if none exists
|
|
807
886
|
useEffect(() => {
|
|
@@ -971,7 +1050,13 @@ export default function SetupWizard() {
|
|
|
971
1050
|
setAgentStatuses(initialStatuses);
|
|
972
1051
|
|
|
973
1052
|
try {
|
|
974
|
-
const agentsPayload = Array.from(selectedAgents).map(key =>
|
|
1053
|
+
const agentsPayload = Array.from(selectedAgents).map(key => {
|
|
1054
|
+
const agent = agents.find(a => a.key === key);
|
|
1055
|
+
const effectiveTransport = agentTransport === 'auto'
|
|
1056
|
+
? (agent?.preferredTransport || 'stdio')
|
|
1057
|
+
: agentTransport;
|
|
1058
|
+
return { key, scope: agentScope, transport: effectiveTransport };
|
|
1059
|
+
});
|
|
975
1060
|
const res = await fetch('/api/mcp/install', {
|
|
976
1061
|
method: 'POST',
|
|
977
1062
|
headers: { 'Content-Type': 'application/json' },
|
|
@@ -985,8 +1070,14 @@ export default function SetupWizard() {
|
|
|
985
1070
|
const data = await res.json();
|
|
986
1071
|
if (data.results) {
|
|
987
1072
|
const updated: Record<string, AgentInstallStatus> = {};
|
|
988
|
-
for (const r of data.results as Array<{ agent: string; status: string; message?: string }>) {
|
|
989
|
-
updated[r.agent] = {
|
|
1073
|
+
for (const r of data.results as Array<{ agent: string; status: string; message?: string; transport?: string; verified?: boolean; verifyError?: string }>) {
|
|
1074
|
+
updated[r.agent] = {
|
|
1075
|
+
state: r.status === 'ok' ? 'ok' : 'error',
|
|
1076
|
+
message: r.message,
|
|
1077
|
+
transport: r.transport,
|
|
1078
|
+
verified: r.verified,
|
|
1079
|
+
verifyError: r.verifyError,
|
|
1080
|
+
};
|
|
990
1081
|
}
|
|
991
1082
|
setAgentStatuses(updated);
|
|
992
1083
|
}
|
|
@@ -997,6 +1088,20 @@ export default function SetupWizard() {
|
|
|
997
1088
|
}
|
|
998
1089
|
}
|
|
999
1090
|
|
|
1091
|
+
// 3. Install skill to agents
|
|
1092
|
+
const skillName = state.template === 'zh' ? 'mindos-zh' : 'mindos';
|
|
1093
|
+
try {
|
|
1094
|
+
const skillRes = await fetch('/api/mcp/install-skill', {
|
|
1095
|
+
method: 'POST',
|
|
1096
|
+
headers: { 'Content-Type': 'application/json' },
|
|
1097
|
+
body: JSON.stringify({ skill: skillName, agents: Array.from(selectedAgents) }),
|
|
1098
|
+
});
|
|
1099
|
+
const skillData = await skillRes.json();
|
|
1100
|
+
setSkillInstallResult(skillData);
|
|
1101
|
+
} catch {
|
|
1102
|
+
setSkillInstallResult({ error: 'Failed to install skill' });
|
|
1103
|
+
}
|
|
1104
|
+
|
|
1000
1105
|
setSubmitting(false);
|
|
1001
1106
|
setCompleted(true);
|
|
1002
1107
|
|
|
@@ -1010,11 +1115,15 @@ export default function SetupWizard() {
|
|
|
1010
1115
|
const retryAgent = useCallback(async (key: string) => {
|
|
1011
1116
|
setAgentStatuses(prev => ({ ...prev, [key]: { state: 'installing' } }));
|
|
1012
1117
|
try {
|
|
1118
|
+
const agent = agents.find(a => a.key === key);
|
|
1119
|
+
const effectiveTransport = agentTransport === 'auto'
|
|
1120
|
+
? (agent?.preferredTransport || 'stdio')
|
|
1121
|
+
: agentTransport;
|
|
1013
1122
|
const res = await fetch('/api/mcp/install', {
|
|
1014
1123
|
method: 'POST',
|
|
1015
1124
|
headers: { 'Content-Type': 'application/json' },
|
|
1016
1125
|
body: JSON.stringify({
|
|
1017
|
-
agents: [{ key, scope: agentScope }],
|
|
1126
|
+
agents: [{ key, scope: agentScope, transport: effectiveTransport }],
|
|
1018
1127
|
transport: agentTransport,
|
|
1019
1128
|
url: `http://localhost:${state.mcpPort}/mcp`,
|
|
1020
1129
|
token: state.authToken || undefined,
|
|
@@ -1022,13 +1131,22 @@ export default function SetupWizard() {
|
|
|
1022
1131
|
});
|
|
1023
1132
|
const data = await res.json();
|
|
1024
1133
|
if (data.results?.[0]) {
|
|
1025
|
-
const r = data.results[0] as { agent: string; status: string; message?: string };
|
|
1026
|
-
setAgentStatuses(prev => ({
|
|
1134
|
+
const r = data.results[0] as { agent: string; status: string; message?: string; transport?: string; verified?: boolean; verifyError?: string };
|
|
1135
|
+
setAgentStatuses(prev => ({
|
|
1136
|
+
...prev,
|
|
1137
|
+
[key]: {
|
|
1138
|
+
state: r.status === 'ok' ? 'ok' : 'error',
|
|
1139
|
+
message: r.message,
|
|
1140
|
+
transport: r.transport,
|
|
1141
|
+
verified: r.verified,
|
|
1142
|
+
verifyError: r.verifyError,
|
|
1143
|
+
},
|
|
1144
|
+
}));
|
|
1027
1145
|
}
|
|
1028
1146
|
} catch {
|
|
1029
1147
|
setAgentStatuses(prev => ({ ...prev, [key]: { state: 'error' } }));
|
|
1030
1148
|
}
|
|
1031
|
-
}, [agentScope, agentTransport, state.mcpPort, state.authToken]);
|
|
1149
|
+
}, [agents, agentScope, agentTransport, state.mcpPort, state.authToken]);
|
|
1032
1150
|
|
|
1033
1151
|
return (
|
|
1034
1152
|
<div className="fixed inset-0 z-50 flex items-center justify-center overflow-y-auto"
|
|
@@ -1076,6 +1194,7 @@ export default function SetupWizard() {
|
|
|
1076
1194
|
agentTransport={agentTransport} setAgentTransport={setAgentTransport}
|
|
1077
1195
|
agentScope={agentScope} setAgentScope={setAgentScope}
|
|
1078
1196
|
agentStatuses={agentStatuses} s={s} settingsMcp={t.settings.mcp}
|
|
1197
|
+
template={state.template}
|
|
1079
1198
|
/>
|
|
1080
1199
|
)}
|
|
1081
1200
|
{step === 5 && (
|
|
@@ -1084,6 +1203,7 @@ export default function SetupWizard() {
|
|
|
1084
1203
|
agentStatuses={agentStatuses} onRetryAgent={retryAgent}
|
|
1085
1204
|
error={error} needsRestart={needsRestart}
|
|
1086
1205
|
maskKey={maskKey} s={s}
|
|
1206
|
+
skillInstallResult={skillInstallResult}
|
|
1087
1207
|
/>
|
|
1088
1208
|
)}
|
|
1089
1209
|
|
|
@@ -8,6 +8,7 @@ export const manifest: RendererDefinition = {
|
|
|
8
8
|
icon: '🧩',
|
|
9
9
|
tags: ['config', 'json', 'settings', 'schema'],
|
|
10
10
|
builtin: true,
|
|
11
|
+
core: true,
|
|
11
12
|
entryPath: 'CONFIG.json',
|
|
12
13
|
match: ({ filePath, extension }) => extension === 'json' && /(^|\/)CONFIG\.json$/i.test(filePath),
|
|
13
14
|
load: () => import('./ConfigRenderer').then(m => ({ default: m.ConfigRenderer })),
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
|
-
import {
|
|
3
|
+
import { useMemo, useCallback, useState } from 'react';
|
|
4
4
|
import { LayoutGrid, Columns, Table2, Settings2 } from 'lucide-react';
|
|
5
5
|
import type { RendererContext } from '@/lib/renderers/registry';
|
|
6
6
|
import type { ViewType, CsvConfig } from './types';
|
|
7
|
-
import { defaultConfig,
|
|
7
|
+
import { defaultConfig, parseCSV } from './types';
|
|
8
|
+
import { useRendererState } from '@/lib/renderers/useRendererState';
|
|
8
9
|
import { TableView } from './TableView';
|
|
9
10
|
import { GalleryView } from './GalleryView';
|
|
10
11
|
import { BoardView } from './BoardView';
|
|
@@ -18,21 +19,14 @@ const VIEW_TABS: { id: ViewType; icon: React.ReactNode; label: string }[] = [
|
|
|
18
19
|
|
|
19
20
|
export function CsvRenderer({ filePath, content, saveAction }: RendererContext) {
|
|
20
21
|
const { headers, rows } = useMemo(() => parseCSV(content), [content]);
|
|
21
|
-
const
|
|
22
|
-
const [
|
|
22
|
+
const def = useMemo(() => defaultConfig(headers), [headers]);
|
|
23
|
+
const [cfg, setCfg] = useRendererState<CsvConfig>('csv', filePath, def);
|
|
23
24
|
const [showConfig, setShowConfig] = useState(false);
|
|
24
25
|
|
|
25
|
-
useEffect(() => {
|
|
26
|
-
setCfg(loadConfig(filePath, headers));
|
|
27
|
-
setConfigLoaded(true);
|
|
28
|
-
}, [filePath]); // eslint-disable-line react-hooks/exhaustive-deps
|
|
29
|
-
|
|
30
26
|
const updateConfig = useCallback((next: CsvConfig) => {
|
|
31
27
|
setCfg(next);
|
|
32
|
-
|
|
33
|
-
}, [filePath]);
|
|
28
|
+
}, [setCfg]);
|
|
34
29
|
|
|
35
|
-
if (!configLoaded) return null;
|
|
36
30
|
const view = cfg.activeView;
|
|
37
31
|
|
|
38
32
|
return (
|
|
@@ -8,6 +8,7 @@ export const manifest: RendererDefinition = {
|
|
|
8
8
|
icon: '📊',
|
|
9
9
|
tags: ['csv', 'table', 'gallery', 'board', 'data'],
|
|
10
10
|
builtin: true,
|
|
11
|
+
core: true,
|
|
11
12
|
entryPath: 'Resources/Products.csv',
|
|
12
13
|
match: ({ extension, filePath }) => extension === 'csv' && !/\bTODO\b/i.test(filePath),
|
|
13
14
|
load: () => import('./CsvRenderer').then(m => ({ default: m.CsvRenderer })),
|
|
@@ -8,6 +8,7 @@ export const manifest: RendererDefinition = {
|
|
|
8
8
|
icon: '✅',
|
|
9
9
|
tags: ['productivity', 'tasks', 'markdown'],
|
|
10
10
|
builtin: true,
|
|
11
|
+
core: true,
|
|
11
12
|
entryPath: 'TODO.md',
|
|
12
13
|
match: ({ filePath }) => /\bTODO\b.*\.(md|csv)$/i.test(filePath),
|
|
13
14
|
load: () => import('./TodoRenderer').then(m => ({ default: m.TodoRenderer })),
|