@geminilight/mindos 0.6.60 → 0.6.61
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/_standalone/.mindos-build-version +1 -1
- package/_standalone/.next/BUILD_ID +1 -1
- package/_standalone/.next/app-path-routes-manifest.json +22 -22
- package/_standalone/.next/build-manifest.json +2 -2
- package/_standalone/.next/cache/.previewinfo +1 -1
- package/_standalone/.next/cache/.rscinfo +1 -1
- package/_standalone/.next/cache/config.json +3 -3
- package/_standalone/.next/prerender-manifest.json +3 -3
- package/_standalone/.next/server/app/.well-known/agent-card.json/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/_global-error.html +2 -2
- package/_standalone/.next/server/app/_global-error.rsc +1 -1
- package/_standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
- package/_standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
- package/_standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
- package/_standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
- package/_standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
- package/_standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/_standalone/.next/server/app/_not-found/page.js +1 -1
- package/_standalone/.next/server/app/_not-found/page.js.nft.json +1 -1
- package/_standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/agents/[agentKey]/page.js +1 -1
- package/_standalone/.next/server/app/agents/[agentKey]/page.js.nft.json +1 -1
- package/_standalone/.next/server/app/agents/[agentKey]/page_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/agents/page.js +1 -1
- package/_standalone/.next/server/app/agents/page.js.nft.json +1 -1
- package/_standalone/.next/server/app/agents/page_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/a2a/agents/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/a2a/delegations/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/a2a/discover/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/a2a/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/acp/config/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/acp/detect/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/acp/install/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/acp/registry/route.js +1 -1
- package/_standalone/.next/server/app/api/acp/registry/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/acp/session/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/agent-activity/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/agents/copy-skill/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/agents/custom/detect/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/agents/custom/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/ask/route.js +3 -3
- package/_standalone/.next/server/app/api/ask/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/ask-sessions/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/auth/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/backlinks/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/bootstrap/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/changes/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/export/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/extract-pdf/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/file/import/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/file/raw/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/file/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/files/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/git/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/graph/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/health/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/inbox/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/init/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/mcp/agents/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/mcp/install/route.js +1 -1
- package/_standalone/.next/server/app/api/mcp/install/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/mcp/install-skill/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/mcp/restart/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/mcp/status/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/mcp/uninstall/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/monitoring/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/recent-files/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/restart/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/search/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/settings/list-models/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/settings/reset-token/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/settings/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/settings/test-key/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/setup/check-path/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/setup/check-port/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/setup/generate-token/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/setup/ls/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/setup/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/skills/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/sync/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/tree-version/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/uninstall/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/update/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/update-check/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/update-status/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/workflows/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/changes/page.js +1 -1
- package/_standalone/.next/server/app/changes/page.js.nft.json +1 -1
- package/_standalone/.next/server/app/changes/page_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/echo/[segment]/page.js +1 -1
- package/_standalone/.next/server/app/echo/[segment]/page.js.nft.json +1 -1
- package/_standalone/.next/server/app/echo/[segment]/page_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/echo/page.js +1 -1
- package/_standalone/.next/server/app/echo/page.js.nft.json +1 -1
- package/_standalone/.next/server/app/echo/page_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/explore/page.js +1 -1
- package/_standalone/.next/server/app/explore/page.js.nft.json +1 -1
- package/_standalone/.next/server/app/explore/page_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/help/page.js +1 -1
- package/_standalone/.next/server/app/help/page.js.nft.json +1 -1
- package/_standalone/.next/server/app/help/page_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/inbox/history/page.js +1 -1
- package/_standalone/.next/server/app/inbox/history/page.js.nft.json +1 -1
- package/_standalone/.next/server/app/inbox/history/page_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/login/page.js +1 -1
- package/_standalone/.next/server/app/login/page.js.nft.json +1 -1
- package/_standalone/.next/server/app/login/page_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/page.js +1 -1
- package/_standalone/.next/server/app/page.js.nft.json +1 -1
- package/_standalone/.next/server/app/page_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/setup/page.js +1 -1
- package/_standalone/.next/server/app/setup/page.js.nft.json +1 -1
- package/_standalone/.next/server/app/setup/page_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/trash/page.js +3 -3
- package/_standalone/.next/server/app/trash/page_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/view/[...path]/page.js +2 -2
- package/_standalone/.next/server/app/view/[...path]/page.js.nft.json +1 -1
- package/_standalone/.next/server/app/view/[...path]/page_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/wiki/page.js +1 -1
- package/_standalone/.next/server/app/wiki/page.js.nft.json +1 -1
- package/_standalone/.next/server/app/wiki/page_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app-paths-manifest.json +22 -22
- package/_standalone/.next/server/chunks/1550.js +1 -1
- package/_standalone/.next/server/chunks/1750.js +1 -1
- package/_standalone/.next/server/chunks/530.js +31 -31
- package/_standalone/.next/server/chunks/6539.js +1 -1
- package/_standalone/.next/server/chunks/{9753.js → 8955.js} +3 -3
- package/_standalone/.next/server/pages/500.html +2 -2
- package/_standalone/.next/server/server-reference-manifest.js +1 -1
- package/_standalone/.next/server/server-reference-manifest.json +1 -1
- package/_standalone/.next/static/chunks/{5581-974dfb26d1d1b38f.js → 5581-0c700c20718bd916.js} +2 -2
- package/_standalone/.next/static/chunks/app/{layout-6a0169582db11a08.js → layout-a5d5925b47e87cc3.js} +10 -10
- package/_standalone/.next/static/chunks/app/trash/page-40bc7316806acd62.js +1 -0
- package/_standalone/.next/static/chunks/app/view/[...path]/page-6fbb14b8f322d0f0.js +12 -0
- package/_standalone/.next/static/chunks/app/wiki/page-ba36eccf4fe62cfe.js +1 -0
- package/_standalone/.next/static/css/{9c558c42831fae58.css → b57c4eb3cc88308b.css} +1 -1
- package/_standalone/.next/trace +65 -65
- package/_standalone/__tests__/api/mcp-install.test.ts +3 -2
- package/_standalone/components/Panel.tsx +14 -0
- package/_standalone/components/home/InboxSection.tsx +148 -32
- package/_standalone/tsconfig.tsbuildinfo +1 -1
- package/app/app/api/ask/route.ts +2 -2
- package/app/app/api/mcp/install/route.ts +17 -1
- package/app/components/Panel.tsx +14 -0
- package/app/components/home/InboxSection.tsx +148 -32
- package/app/lib/acp/agent-descriptors.ts +3 -0
- package/app/lib/acp/subprocess.ts +56 -8
- package/app/lib/i18n/modules/knowledge.ts +10 -0
- package/app/lib/mcp-agents.ts +9 -0
- package/bin/lib/mcp-agents.js +34 -1
- package/bin/lib/mcp-install.js +94 -20
- package/bin/lib/toml.js +125 -0
- package/package.json +1 -1
- package/scripts/build-runtime-archive.sh +5 -1
- package/scripts/setup.js +19 -0
- package/_standalone/.next/static/chunks/app/trash/page-ed7ba3b0b50223a6.js +0 -1
- package/_standalone/.next/static/chunks/app/view/[...path]/page-678139779971cfcb.js +0 -12
- package/_standalone/.next/static/chunks/app/wiki/page-194ae9af2d461481.js +0 -1
- /package/_standalone/.next/static/{K3wZNf5bj_AFYwtqcRXIf → 5GmVArEG8OX03azKICsGq}/_buildManifest.js +0 -0
- /package/_standalone/.next/static/{K3wZNf5bj_AFYwtqcRXIf → 5GmVArEG8OX03azKICsGq}/_ssgManifest.js +0 -0
package/app/app/api/ask/route.ts
CHANGED
|
@@ -20,7 +20,7 @@ import fs from 'fs';
|
|
|
20
20
|
import path from 'path';
|
|
21
21
|
import { getFileContent, getMindRoot, collectAllFiles } from '@/lib/fs';
|
|
22
22
|
import { getModelConfig, hasImages } from '@/lib/agent/model';
|
|
23
|
-
import { isProviderId, type ProviderId } from '@/lib/agent/providers';
|
|
23
|
+
import { isProviderId, type ProviderId, toPiProvider } from '@/lib/agent/providers';
|
|
24
24
|
import { getRequestScopedTools, getOrganizeTools, getChatTools, WRITE_TOOLS, truncate } from '@/lib/agent/tools';
|
|
25
25
|
import { AGENT_SYSTEM_PROMPT, ORGANIZE_SYSTEM_PROMPT, CHAT_SYSTEM_PROMPT } from '@/lib/agent/prompt';
|
|
26
26
|
import type { AskModeApi } from '@/lib/types';
|
|
@@ -1016,7 +1016,7 @@ export async function POST(req: NextRequest) {
|
|
|
1016
1016
|
const customTools = toPiCustomToolDefinitions(requestTools);
|
|
1017
1017
|
|
|
1018
1018
|
const authStorage = AuthStorage.create();
|
|
1019
|
-
authStorage.setRuntimeApiKey(provider, requestApiKey);
|
|
1019
|
+
authStorage.setRuntimeApiKey(toPiProvider(provider), requestApiKey);
|
|
1020
1020
|
const modelRegistry = new ModelRegistry(authStorage);
|
|
1021
1021
|
const settingsManager = SettingsManager.inMemory({
|
|
1022
1022
|
enableSkillCommands: true,
|
|
@@ -2,8 +2,9 @@ export const dynamic = 'force-dynamic';
|
|
|
2
2
|
import { NextRequest, NextResponse } from 'next/server';
|
|
3
3
|
import fs from 'fs';
|
|
4
4
|
import path from 'path';
|
|
5
|
-
import { MCP_AGENTS, expandHome, resolveSkillWorkspaceProfile } from '@/lib/mcp-agents';
|
|
5
|
+
import { MCP_AGENTS, SKILL_AGENT_REGISTRY, expandHome, resolveSkillWorkspaceProfile } from '@/lib/mcp-agents';
|
|
6
6
|
import { readSettings, recordSkillInstall } from '@/lib/settings';
|
|
7
|
+
import { copyDir, dirExists } from '@/lib/file-ops';
|
|
7
8
|
|
|
8
9
|
/** Parse JSONC — strips comments before JSON.parse. Returns {} for empty/whitespace-only input. */
|
|
9
10
|
function parseJsonc(text: string): Record<string, unknown> {
|
|
@@ -213,6 +214,21 @@ export async function POST(req: NextRequest) {
|
|
|
213
214
|
const activeSkill = settings.disabledSkills?.includes('mindos') ? 'mindos-zh' : 'mindos';
|
|
214
215
|
const skillPath = path.join(skillProfile.workspacePath, activeSkill, 'SKILL.md');
|
|
215
216
|
recordSkillInstall(key, activeSkill, skillPath);
|
|
217
|
+
|
|
218
|
+
// Auto-copy skill for unsupported agents (QClaw, WorkBuddy, Lingma, etc.)
|
|
219
|
+
const reg = SKILL_AGENT_REGISTRY[key];
|
|
220
|
+
if (reg?.mode === 'unsupported') {
|
|
221
|
+
const projectRoot = process.env.MINDOS_PROJECT_ROOT || path.resolve(process.cwd(), '..');
|
|
222
|
+
const candidates = [
|
|
223
|
+
path.join(projectRoot, 'skills', activeSkill),
|
|
224
|
+
path.join(projectRoot, 'app', 'data', 'skills', activeSkill),
|
|
225
|
+
];
|
|
226
|
+
const skillSrc = candidates.find(p => dirExists(p));
|
|
227
|
+
const targetDir = path.join(skillProfile.workspacePath, activeSkill);
|
|
228
|
+
if (skillSrc && !dirExists(targetDir)) {
|
|
229
|
+
await copyDir(skillSrc, targetDir);
|
|
230
|
+
}
|
|
231
|
+
}
|
|
216
232
|
} catch { /* best-effort, don't fail the install */ }
|
|
217
233
|
|
|
218
234
|
// Verify http connections
|
package/app/components/Panel.tsx
CHANGED
|
@@ -324,6 +324,20 @@ export default function Panel({
|
|
|
324
324
|
>
|
|
325
325
|
<FileTree nodes={fileTree} onNavigate={onNavigate} maxOpenDepth={maxOpenDepth} onImport={onImport} />
|
|
326
326
|
</div>
|
|
327
|
+
{/* Inbox quick entry — always visible above sync bar */}
|
|
328
|
+
<button
|
|
329
|
+
type="button"
|
|
330
|
+
onClick={() => router.push('/view/Inbox/')}
|
|
331
|
+
className="flex items-center gap-2 mx-2 px-2 py-1.5 text-sm rounded-md transition-colors hover:bg-muted group border-t border-border/40 shrink-0"
|
|
332
|
+
>
|
|
333
|
+
<Inbox size={14} className="shrink-0 text-[var(--amber)]" />
|
|
334
|
+
<span className="flex-1 text-left text-muted-foreground group-hover:text-foreground transition-colors text-xs">
|
|
335
|
+
{t.sidebar.capture}
|
|
336
|
+
</span>
|
|
337
|
+
{inboxCount > 0 && (
|
|
338
|
+
<span className="text-xs font-medium text-[var(--amber)] tabular-nums">{inboxCount}</span>
|
|
339
|
+
)}
|
|
340
|
+
</button>
|
|
327
341
|
<SyncStatusBar collapsed={false} onOpenSyncSettings={onOpenSyncSettings} />
|
|
328
342
|
</div>
|
|
329
343
|
|
|
@@ -15,7 +15,13 @@ import {
|
|
|
15
15
|
Check,
|
|
16
16
|
Clock,
|
|
17
17
|
ChevronDown,
|
|
18
|
+
X,
|
|
19
|
+
ExternalLink,
|
|
20
|
+
Copy,
|
|
21
|
+
Trash2,
|
|
18
22
|
} from 'lucide-react';
|
|
23
|
+
import { useRouter } from 'next/navigation';
|
|
24
|
+
import { toast } from '@/lib/toast';
|
|
19
25
|
import { useLocale } from '@/lib/stores/locale-store';
|
|
20
26
|
import { encodePath } from '@/lib/utils';
|
|
21
27
|
import { quickDropToInbox } from '@/lib/inbox-upload';
|
|
@@ -55,6 +61,22 @@ export function InboxSection({ isOrganizing: externalOrganizing = false }: Inbox
|
|
|
55
61
|
);
|
|
56
62
|
}, [files, isOrganizing]);
|
|
57
63
|
|
|
64
|
+
const handleDeleteFile = useCallback(async (name: string) => {
|
|
65
|
+
try {
|
|
66
|
+
const res = await fetch('/api/inbox', {
|
|
67
|
+
method: 'DELETE',
|
|
68
|
+
headers: { 'Content-Type': 'application/json' },
|
|
69
|
+
body: JSON.stringify({ names: [name] }),
|
|
70
|
+
});
|
|
71
|
+
if (!res.ok) throw new Error('Failed to delete');
|
|
72
|
+
setFiles(prev => prev.filter(f => f.name !== name));
|
|
73
|
+
window.dispatchEvent(new Event('mindos:inbox-updated'));
|
|
74
|
+
toast.success(t.inbox.fileRemoved);
|
|
75
|
+
} catch {
|
|
76
|
+
toast.error(t.inbox.fileRemoveFailed);
|
|
77
|
+
}
|
|
78
|
+
}, [t]);
|
|
79
|
+
|
|
58
80
|
const handleUpload = useCallback((selected: FileList | null) => {
|
|
59
81
|
if (!selected || selected.length === 0) return;
|
|
60
82
|
quickDropToInbox(Array.from(selected), t);
|
|
@@ -208,7 +230,7 @@ export function InboxSection({ isOrganizing: externalOrganizing = false }: Inbox
|
|
|
208
230
|
<>
|
|
209
231
|
<div className="flex flex-col gap-0.5 mb-3">
|
|
210
232
|
{visibleFiles.map((file) => (
|
|
211
|
-
<InboxFileRow key={file.path} file={file} />
|
|
233
|
+
<InboxFileRow key={file.path} file={file} onDelete={handleDeleteFile} />
|
|
212
234
|
))}
|
|
213
235
|
</div>
|
|
214
236
|
{overflowCount > 0 && (
|
|
@@ -312,45 +334,139 @@ export function InboxSection({ isOrganizing: externalOrganizing = false }: Inbox
|
|
|
312
334
|
);
|
|
313
335
|
}
|
|
314
336
|
|
|
315
|
-
function InboxFileRow({ file }: { file: InboxFile }) {
|
|
337
|
+
function InboxFileRow({ file, onDelete }: { file: InboxFile; onDelete: (name: string) => void }) {
|
|
316
338
|
const { t } = useLocale();
|
|
339
|
+
const router = useRouter();
|
|
317
340
|
const isCSV = file.name.endsWith('.csv');
|
|
318
341
|
const age = formatRelativeTime(file.modifiedAt, t.home.relativeTime);
|
|
342
|
+
const [ctxMenu, setCtxMenu] = useState<{ x: number; y: number } | null>(null);
|
|
343
|
+
|
|
344
|
+
const handleNavigate = () => {
|
|
345
|
+
router.push(`/view/${encodePath(file.path)}`);
|
|
346
|
+
};
|
|
347
|
+
|
|
348
|
+
const handleDelete = (e: React.MouseEvent) => {
|
|
349
|
+
e.stopPropagation();
|
|
350
|
+
e.preventDefault();
|
|
351
|
+
onDelete(file.name);
|
|
352
|
+
};
|
|
353
|
+
|
|
354
|
+
const handleContextMenu = (e: React.MouseEvent) => {
|
|
355
|
+
e.preventDefault();
|
|
356
|
+
e.stopPropagation();
|
|
357
|
+
setCtxMenu({ x: e.clientX, y: e.clientY });
|
|
358
|
+
};
|
|
319
359
|
|
|
320
360
|
return (
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
/>
|
|
330
|
-
{isCSV ? (
|
|
331
|
-
<Table size={12} className="shrink-0 text-success" />
|
|
332
|
-
) : (
|
|
333
|
-
<FileText size={12} className="shrink-0 text-muted-foreground" />
|
|
334
|
-
)}
|
|
335
|
-
<span
|
|
336
|
-
className="text-sm truncate flex-1 text-foreground"
|
|
337
|
-
title={file.name}
|
|
338
|
-
suppressHydrationWarning
|
|
361
|
+
<>
|
|
362
|
+
<div
|
|
363
|
+
role="button"
|
|
364
|
+
tabIndex={0}
|
|
365
|
+
onClick={handleNavigate}
|
|
366
|
+
onKeyDown={(e) => { if (e.key === 'Enter') handleNavigate(); }}
|
|
367
|
+
onContextMenu={handleContextMenu}
|
|
368
|
+
className="flex items-center gap-3 px-3 py-2 rounded-lg transition-all duration-100 hover:translate-x-0.5 hover:bg-muted group cursor-pointer"
|
|
339
369
|
>
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
370
|
+
<span
|
|
371
|
+
className={`w-1.5 h-1.5 rounded-full shrink-0 ${
|
|
372
|
+
file.isAging ? 'bg-[var(--amber)]/60' : 'bg-[var(--amber)]'
|
|
373
|
+
}`}
|
|
374
|
+
/>
|
|
375
|
+
{isCSV ? (
|
|
376
|
+
<Table size={12} className="shrink-0 text-success" />
|
|
377
|
+
) : (
|
|
378
|
+
<FileText size={12} className="shrink-0 text-muted-foreground" />
|
|
379
|
+
)}
|
|
380
|
+
<span
|
|
381
|
+
className="text-sm truncate flex-1 text-foreground"
|
|
382
|
+
title={file.name}
|
|
383
|
+
suppressHydrationWarning
|
|
384
|
+
>
|
|
385
|
+
{file.name}
|
|
386
|
+
</span>
|
|
387
|
+
<span className="text-2xs text-muted-foreground/50 tabular-nums shrink-0 group-hover:hidden">
|
|
388
|
+
{age}
|
|
351
389
|
</span>
|
|
390
|
+
{file.isAging && (
|
|
391
|
+
<span title="7+ days" className="group-hover:hidden">
|
|
392
|
+
<AlertCircle
|
|
393
|
+
size={11}
|
|
394
|
+
className="shrink-0 text-[var(--amber)]/60"
|
|
395
|
+
/>
|
|
396
|
+
</span>
|
|
397
|
+
)}
|
|
398
|
+
{/* Hover delete button */}
|
|
399
|
+
<button
|
|
400
|
+
type="button"
|
|
401
|
+
onClick={handleDelete}
|
|
402
|
+
className="hidden group-hover:flex items-center justify-center w-5 h-5 rounded shrink-0 text-muted-foreground/50 hover:text-destructive hover:bg-destructive/10 transition-colors"
|
|
403
|
+
title={t.inbox.removeFile}
|
|
404
|
+
>
|
|
405
|
+
<X size={12} />
|
|
406
|
+
</button>
|
|
407
|
+
</div>
|
|
408
|
+
{ctxMenu && (
|
|
409
|
+
<InboxFileContextMenu
|
|
410
|
+
x={ctxMenu.x}
|
|
411
|
+
y={ctxMenu.y}
|
|
412
|
+
file={file}
|
|
413
|
+
onDelete={() => { setCtxMenu(null); onDelete(file.name); }}
|
|
414
|
+
onClose={() => setCtxMenu(null)}
|
|
415
|
+
/>
|
|
352
416
|
)}
|
|
353
|
-
|
|
417
|
+
</>
|
|
418
|
+
);
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
/** Right-click context menu for Inbox file items */
|
|
422
|
+
function InboxFileContextMenu({ x, y, file, onDelete, onClose }: {
|
|
423
|
+
x: number; y: number; file: InboxFile; onDelete: () => void; onClose: () => void;
|
|
424
|
+
}) {
|
|
425
|
+
const menuRef = useRef<HTMLDivElement>(null);
|
|
426
|
+
const router = useRouter();
|
|
427
|
+
const { t } = useLocale();
|
|
428
|
+
|
|
429
|
+
useEffect(() => {
|
|
430
|
+
const handleClick = (e: MouseEvent) => {
|
|
431
|
+
if (menuRef.current && !menuRef.current.contains(e.target as Node)) onClose();
|
|
432
|
+
};
|
|
433
|
+
const handleKey = (e: KeyboardEvent) => { if (e.key === 'Escape') onClose(); };
|
|
434
|
+
document.addEventListener('mousedown', handleClick);
|
|
435
|
+
document.addEventListener('keydown', handleKey);
|
|
436
|
+
return () => { document.removeEventListener('mousedown', handleClick); document.removeEventListener('keydown', handleKey); };
|
|
437
|
+
}, [onClose]);
|
|
438
|
+
|
|
439
|
+
const adjX = typeof window !== 'undefined' ? Math.min(x, window.innerWidth - 200) : x;
|
|
440
|
+
const adjY = typeof window !== 'undefined' ? Math.min(y, window.innerHeight - 120) : y;
|
|
441
|
+
|
|
442
|
+
const menuItemClass = 'w-full flex items-center gap-2 px-3 py-1.5 text-sm text-foreground hover:bg-muted transition-colors text-left';
|
|
443
|
+
|
|
444
|
+
return (
|
|
445
|
+
<div
|
|
446
|
+
ref={menuRef}
|
|
447
|
+
className="fixed z-50 min-w-[160px] bg-card border border-border rounded-lg shadow-lg py-1"
|
|
448
|
+
style={{ top: adjY, left: adjX }}
|
|
449
|
+
>
|
|
450
|
+
<button
|
|
451
|
+
className={menuItemClass}
|
|
452
|
+
onClick={() => { router.push(`/view/${encodePath(file.path)}`); onClose(); }}
|
|
453
|
+
>
|
|
454
|
+
<ExternalLink size={14} className="shrink-0" /> {t.inbox.openFile}
|
|
455
|
+
</button>
|
|
456
|
+
<button
|
|
457
|
+
className={menuItemClass}
|
|
458
|
+
onClick={() => { navigator.clipboard.writeText(file.name); toast.copy(); onClose(); }}
|
|
459
|
+
>
|
|
460
|
+
<Copy size={14} className="shrink-0" /> {t.inbox.copyName}
|
|
461
|
+
</button>
|
|
462
|
+
<div className="border-t border-border my-1" />
|
|
463
|
+
<button
|
|
464
|
+
className={`${menuItemClass} text-destructive hover:text-destructive`}
|
|
465
|
+
onClick={onDelete}
|
|
466
|
+
>
|
|
467
|
+
<Trash2 size={14} className="shrink-0" /> {t.inbox.removeFile}
|
|
468
|
+
</button>
|
|
469
|
+
</div>
|
|
354
470
|
);
|
|
355
471
|
}
|
|
356
472
|
|
|
@@ -128,6 +128,9 @@ export const AGENT_DESCRIPTORS: Record<string, AcpAgentDescriptor> = {
|
|
|
128
128
|
'qwen-code': { binary: 'qwen-code', cmd: 'qwen-code', args: [], installCmd: 'npm install -g @qwen-code/qwen-code',
|
|
129
129
|
displayName: 'Qwen Code',
|
|
130
130
|
description: '阿里通义千问 Qwen 编程智能体。基于 Qwen 大模型,支持代码生成、审查和多语言编程,深度适配中文开发场景。' },
|
|
131
|
+
'lingma': { binary: 'lingma', cmd: 'lingma', args: [],
|
|
132
|
+
displayName: 'Lingma',
|
|
133
|
+
description: '阿里通义灵码智能编程助手。提供代码补全、智能问答、多文件修改和编程智能体能力,支持 MCP 工具扩展。' },
|
|
131
134
|
};
|
|
132
135
|
|
|
133
136
|
/* ── Resolution ────────────────────────────────────────────────────────── */
|
|
@@ -27,6 +27,10 @@ export interface AcpProcess {
|
|
|
27
27
|
agentId: string;
|
|
28
28
|
proc: ChildProcess;
|
|
29
29
|
alive: boolean;
|
|
30
|
+
/** Set when the process fails to spawn or exits with an error. */
|
|
31
|
+
spawnError?: string;
|
|
32
|
+
/** Exit code when the process has terminated. */
|
|
33
|
+
exitCode?: number | null;
|
|
30
34
|
}
|
|
31
35
|
|
|
32
36
|
type MessageCallback = (msg: AcpJsonRpcResponse) => void;
|
|
@@ -119,15 +123,18 @@ export function spawnAcpAgent(
|
|
|
119
123
|
|
|
120
124
|
proc.on('close', (code) => {
|
|
121
125
|
acpProc.alive = false;
|
|
122
|
-
|
|
123
|
-
|
|
126
|
+
acpProc.exitCode = code;
|
|
127
|
+
if (code && code !== 0) {
|
|
128
|
+
acpProc.spawnError = stderrBuf.trim().slice(0, 500) || `Process exited with code ${code}`;
|
|
129
|
+
console.error(`[ACP] ${entry.id} exited with code ${code}: ${acpProc.spawnError}`);
|
|
124
130
|
}
|
|
125
|
-
|
|
126
|
-
|
|
131
|
+
// Do NOT delete listeners here — sendAndWait still needs them to reject.
|
|
132
|
+
// Listeners are cleaned up in killAgent() and by individual unsub calls.
|
|
127
133
|
});
|
|
128
134
|
|
|
129
135
|
proc.on('error', (err) => {
|
|
130
136
|
acpProc.alive = false;
|
|
137
|
+
acpProc.spawnError = err.message;
|
|
131
138
|
console.error(`[ACP] ${entry.id} spawn error:`, err.message);
|
|
132
139
|
});
|
|
133
140
|
|
|
@@ -176,20 +183,61 @@ export function sendAndWait(
|
|
|
176
183
|
timeoutMs = 30_000,
|
|
177
184
|
): Promise<AcpJsonRpcResponse> {
|
|
178
185
|
return new Promise((resolve, reject) => {
|
|
179
|
-
|
|
186
|
+
// Fail fast if process is already dead (e.g. binary not found).
|
|
187
|
+
if (!acpProc.alive) {
|
|
188
|
+
const reason = acpProc.spawnError || 'Process is not alive';
|
|
189
|
+
reject(new Error(
|
|
190
|
+
`Agent "${acpProc.agentId}" is not running: ${reason}. ` +
|
|
191
|
+
`Please check that the agent is installed and available on your PATH.`,
|
|
192
|
+
));
|
|
193
|
+
return;
|
|
194
|
+
}
|
|
180
195
|
|
|
181
|
-
|
|
196
|
+
let rpcId: string;
|
|
197
|
+
try {
|
|
198
|
+
rpcId = sendMessage(acpProc, method, params);
|
|
199
|
+
} catch (err) {
|
|
200
|
+
reject(err);
|
|
201
|
+
return;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
const cleanup = () => {
|
|
205
|
+
clearTimeout(timer);
|
|
182
206
|
unsub();
|
|
207
|
+
acpProc.proc.removeListener('close', onClose);
|
|
208
|
+
acpProc.proc.removeListener('error', onError);
|
|
209
|
+
};
|
|
210
|
+
|
|
211
|
+
const timer = setTimeout(() => {
|
|
212
|
+
cleanup();
|
|
183
213
|
reject(new Error(`ACP RPC timeout after ${timeoutMs}ms for method: ${method}`));
|
|
184
214
|
}, timeoutMs);
|
|
185
215
|
|
|
186
216
|
const unsub = onMessage(acpProc, (msg) => {
|
|
187
217
|
if (String(msg.id) === rpcId) {
|
|
188
|
-
|
|
189
|
-
unsub();
|
|
218
|
+
cleanup();
|
|
190
219
|
resolve(msg);
|
|
191
220
|
}
|
|
192
221
|
});
|
|
222
|
+
|
|
223
|
+
// Reject immediately if the process dies while we're waiting.
|
|
224
|
+
const onClose = (code: number | null) => {
|
|
225
|
+
cleanup();
|
|
226
|
+
const reason = acpProc.spawnError || `Process exited with code ${code}`;
|
|
227
|
+
reject(new Error(
|
|
228
|
+
`Agent "${acpProc.agentId}" exited unexpectedly: ${reason}. ` +
|
|
229
|
+
`Please check that the agent is installed and available on your PATH.`,
|
|
230
|
+
));
|
|
231
|
+
};
|
|
232
|
+
const onError = (err: Error) => {
|
|
233
|
+
cleanup();
|
|
234
|
+
reject(new Error(
|
|
235
|
+
`Agent "${acpProc.agentId}" failed to start: ${err.message}. ` +
|
|
236
|
+
`Please check that the agent is installed and available on your PATH.`,
|
|
237
|
+
));
|
|
238
|
+
};
|
|
239
|
+
acpProc.proc.once('close', onClose);
|
|
240
|
+
acpProc.proc.once('error', onError);
|
|
193
241
|
});
|
|
194
242
|
}
|
|
195
243
|
|
|
@@ -119,6 +119,11 @@ export const knowledgeEn = {
|
|
|
119
119
|
noMindRoot: 'Please configure your knowledge base first',
|
|
120
120
|
saveFailed: 'Failed to save files. Check disk space and permissions.',
|
|
121
121
|
organizeFailed: 'Could not read files for organizing. Check file permissions.',
|
|
122
|
+
removeFile: 'Remove from Inbox',
|
|
123
|
+
openFile: 'Open',
|
|
124
|
+
copyName: 'Copy Name',
|
|
125
|
+
fileRemoved: 'File removed from Inbox',
|
|
126
|
+
fileRemoveFailed: 'Failed to remove file',
|
|
122
127
|
},
|
|
123
128
|
pulse: {
|
|
124
129
|
title: 'Your Agents',
|
|
@@ -498,6 +503,11 @@ export const knowledgeZh = {
|
|
|
498
503
|
noMindRoot: '请先配置知识库路径',
|
|
499
504
|
saveFailed: '保存文件失败,请检查磁盘空间和权限。',
|
|
500
505
|
organizeFailed: '无法读取文件进行整理,请检查文件权限。',
|
|
506
|
+
removeFile: '从暂存台移除',
|
|
507
|
+
openFile: '打开',
|
|
508
|
+
copyName: '复制文件名',
|
|
509
|
+
fileRemoved: '文件已从暂存台移除',
|
|
510
|
+
fileRemoveFailed: '移除文件失败',
|
|
501
511
|
},
|
|
502
512
|
pulse: {
|
|
503
513
|
title: '你的 Agent',
|
package/app/lib/mcp-agents.ts
CHANGED
|
@@ -262,6 +262,14 @@ export const MCP_AGENTS: Record<string, AgentDef> = {
|
|
|
262
262
|
presenceCli: 'workbuddy',
|
|
263
263
|
presenceDirs: ['~/.workbuddy/'],
|
|
264
264
|
},
|
|
265
|
+
'lingma': {
|
|
266
|
+
name: 'Lingma',
|
|
267
|
+
project: null,
|
|
268
|
+
global: '~/.lingma/mcp.json',
|
|
269
|
+
key: 'mcpServers',
|
|
270
|
+
preferredTransport: 'stdio',
|
|
271
|
+
presenceDirs: ['~/.lingma/'],
|
|
272
|
+
},
|
|
265
273
|
};
|
|
266
274
|
|
|
267
275
|
/**
|
|
@@ -291,6 +299,7 @@ export const SKILL_AGENT_REGISTRY: Record<string, SkillAgentRegistration> = {
|
|
|
291
299
|
'antigravity': { mode: 'additional', skillAgentName: 'antigravity' },
|
|
292
300
|
'qclaw': { mode: 'unsupported' },
|
|
293
301
|
'workbuddy': { mode: 'unsupported' },
|
|
302
|
+
'lingma': { mode: 'unsupported' },
|
|
294
303
|
};
|
|
295
304
|
|
|
296
305
|
export interface SkillWorkspaceProfile {
|
package/bin/lib/mcp-agents.js
CHANGED
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Shared MCP agent definitions for CLI tools.
|
|
3
|
-
*
|
|
3
|
+
*
|
|
4
|
+
* ⚠️ KEEP IN SYNC WITH:
|
|
5
|
+
* - app/lib/mcp-agents.ts (TypeScript source of truth)
|
|
6
|
+
* - bin/lib/toml.js (TOML format support for agents with format: 'toml')
|
|
7
|
+
* - app/app/api/mcp/install/route.ts (server-side install with TOML merge)
|
|
4
8
|
*
|
|
5
9
|
* Each agent entry includes presenceCli / presenceDirs for detecting
|
|
6
10
|
* whether the agent is installed on the user's machine. To add a new
|
|
@@ -226,6 +230,32 @@ export const MCP_AGENTS = {
|
|
|
226
230
|
presenceCli: 'agy',
|
|
227
231
|
presenceDirs: ['~/.gemini/antigravity/'],
|
|
228
232
|
},
|
|
233
|
+
'qclaw': {
|
|
234
|
+
name: 'QClaw',
|
|
235
|
+
project: null,
|
|
236
|
+
global: '~/.qclaw/mcp.json',
|
|
237
|
+
key: 'mcpServers',
|
|
238
|
+
preferredTransport: 'stdio',
|
|
239
|
+
presenceCli: 'qclaw',
|
|
240
|
+
presenceDirs: ['~/.qclaw/'],
|
|
241
|
+
},
|
|
242
|
+
'workbuddy': {
|
|
243
|
+
name: 'WorkBuddy',
|
|
244
|
+
project: null,
|
|
245
|
+
global: '~/.workbuddy/mcp.json',
|
|
246
|
+
key: 'mcpServers',
|
|
247
|
+
preferredTransport: 'stdio',
|
|
248
|
+
presenceCli: 'workbuddy',
|
|
249
|
+
presenceDirs: ['~/.workbuddy/'],
|
|
250
|
+
},
|
|
251
|
+
'lingma': {
|
|
252
|
+
name: 'Lingma',
|
|
253
|
+
project: null,
|
|
254
|
+
global: '~/.lingma/mcp.json',
|
|
255
|
+
key: 'mcpServers',
|
|
256
|
+
preferredTransport: 'stdio',
|
|
257
|
+
presenceDirs: ['~/.lingma/'],
|
|
258
|
+
},
|
|
229
259
|
};
|
|
230
260
|
|
|
231
261
|
/**
|
|
@@ -253,6 +283,9 @@ export const SKILL_AGENT_REGISTRY = {
|
|
|
253
283
|
'github-copilot': { mode: 'universal' },
|
|
254
284
|
'codex': { mode: 'universal' },
|
|
255
285
|
'antigravity': { mode: 'additional', skillAgentName: 'antigravity' },
|
|
286
|
+
'qclaw': { mode: 'unsupported' },
|
|
287
|
+
'workbuddy': { mode: 'unsupported' },
|
|
288
|
+
'lingma': { mode: 'unsupported' },
|
|
256
289
|
};
|
|
257
290
|
|
|
258
291
|
export function detectAgentPresence(agentKey) {
|
package/bin/lib/mcp-install.js
CHANGED
|
@@ -1,10 +1,62 @@
|
|
|
1
|
-
import { existsSync, readFileSync, writeFileSync, mkdirSync } from 'node:fs';
|
|
2
|
-
import { resolve } from 'node:path';
|
|
3
|
-
import { CONFIG_PATH } from './constants.js';
|
|
1
|
+
import { existsSync, readFileSync, writeFileSync, mkdirSync, readdirSync, copyFileSync } from 'node:fs';
|
|
2
|
+
import { resolve, join } from 'node:path';
|
|
3
|
+
import { CONFIG_PATH, ROOT } from './constants.js';
|
|
4
4
|
import { bold, dim, cyan, green, red, yellow } from './colors.js';
|
|
5
5
|
import { expandHome } from './path-expand.js';
|
|
6
6
|
import { parseJsonc } from './jsonc.js';
|
|
7
|
-
import { MCP_AGENTS, detectAgentPresence } from './mcp-agents.js';
|
|
7
|
+
import { MCP_AGENTS, SKILL_AGENT_REGISTRY, detectAgentPresence } from './mcp-agents.js';
|
|
8
|
+
import { mergeTomlEntry } from './toml.js';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Recursively copy a directory using pure Node.js (cross-platform).
|
|
12
|
+
* Uses cpSync on Node >=16.7, falls back to manual walk otherwise.
|
|
13
|
+
*/
|
|
14
|
+
function copyDirSync(src, dst) {
|
|
15
|
+
mkdirSync(dst, { recursive: true });
|
|
16
|
+
for (const entry of readdirSync(src, { withFileTypes: true })) {
|
|
17
|
+
const s = join(src, entry.name);
|
|
18
|
+
const d = join(dst, entry.name);
|
|
19
|
+
if (entry.isDirectory()) {
|
|
20
|
+
copyDirSync(s, d);
|
|
21
|
+
} else {
|
|
22
|
+
copyFileSync(s, d);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Auto-copy skill folder for agents with mode 'unsupported'.
|
|
29
|
+
* Called after MCP config is written. Best-effort, never throws.
|
|
30
|
+
*/
|
|
31
|
+
function autoInstallSkillForAgent(agentKey, skillName) {
|
|
32
|
+
const reg = SKILL_AGENT_REGISTRY[agentKey];
|
|
33
|
+
if (!reg || reg.mode !== 'unsupported') return null;
|
|
34
|
+
|
|
35
|
+
const agent = MCP_AGENTS[agentKey];
|
|
36
|
+
if (!agent) return null;
|
|
37
|
+
|
|
38
|
+
// Resolve skill source: project skills/ or app/data/skills/
|
|
39
|
+
const candidates = [
|
|
40
|
+
join(ROOT, 'skills', skillName),
|
|
41
|
+
join(ROOT, 'app', 'data', 'skills', skillName),
|
|
42
|
+
];
|
|
43
|
+
const skillSrc = candidates.find(p => existsSync(p));
|
|
44
|
+
if (!skillSrc) return null;
|
|
45
|
+
|
|
46
|
+
// Resolve target: agent's presenceDirs[0]/skills/<skillName>
|
|
47
|
+
const agentRoot = (agent.presenceDirs ?? []).map(d => expandHome(d)).find(d => existsSync(d))
|
|
48
|
+
|| resolve(expandHome(agent.global), '..');
|
|
49
|
+
const targetDir = join(agentRoot, 'skills', skillName);
|
|
50
|
+
|
|
51
|
+
if (existsSync(targetDir)) return 'exists';
|
|
52
|
+
|
|
53
|
+
try {
|
|
54
|
+
copyDirSync(skillSrc, targetDir);
|
|
55
|
+
return 'copied';
|
|
56
|
+
} catch {
|
|
57
|
+
return null;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
8
60
|
|
|
9
61
|
export { MCP_AGENTS };
|
|
10
62
|
|
|
@@ -195,8 +247,15 @@ export async function mcpInstall() {
|
|
|
195
247
|
const abs = expandHome(cfgPath);
|
|
196
248
|
if (!existsSync(abs)) continue;
|
|
197
249
|
try {
|
|
198
|
-
const
|
|
199
|
-
if (
|
|
250
|
+
const content = readFileSync(abs, 'utf-8');
|
|
251
|
+
if (agent.format === 'toml') {
|
|
252
|
+
// TOML: look for [section.mindos] header
|
|
253
|
+
installed = content.includes(`[${agent.key}.mindos]`);
|
|
254
|
+
} else {
|
|
255
|
+
const config = parseJsonc(content);
|
|
256
|
+
if (config[agent.key]?.mindos) installed = true;
|
|
257
|
+
}
|
|
258
|
+
if (installed) break;
|
|
200
259
|
} catch {}
|
|
201
260
|
}
|
|
202
261
|
const hint = installed ? 'configured' : present ? 'detected' : 'not found';
|
|
@@ -312,24 +371,39 @@ export async function mcpInstall() {
|
|
|
312
371
|
|
|
313
372
|
// read + merge — resolve to absolute path for cross-platform safety
|
|
314
373
|
const absPath = resolve(expandHome(configPath));
|
|
315
|
-
let config = {};
|
|
316
|
-
if (existsSync(absPath)) {
|
|
317
|
-
try { config = parseJsonc(readFileSync(absPath, 'utf-8')); } catch {
|
|
318
|
-
console.error(red(` Failed to parse existing config: ${absPath} — skipping.`));
|
|
319
|
-
continue;
|
|
320
|
-
}
|
|
321
|
-
}
|
|
322
|
-
|
|
323
|
-
if (!config[agent.key]) config[agent.key] = {};
|
|
324
|
-
const existed = !!config[agent.key].mindos;
|
|
325
|
-
config[agent.key].mindos = entry;
|
|
326
|
-
|
|
327
|
-
// write
|
|
328
374
|
const dir = resolve(absPath, '..');
|
|
329
375
|
if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
|
|
330
|
-
|
|
376
|
+
|
|
377
|
+
let existed = false;
|
|
378
|
+
|
|
379
|
+
if (agent.format === 'toml') {
|
|
380
|
+
// TOML format (e.g. Codex): line-based merge preserving existing content
|
|
381
|
+
const existing = existsSync(absPath) ? readFileSync(absPath, 'utf-8') : '';
|
|
382
|
+
existed = existing.includes(`[${agent.key}.mindos]`);
|
|
383
|
+
const merged = mergeTomlEntry(existing, agent.key, 'mindos', entry);
|
|
384
|
+
writeFileSync(absPath, merged, 'utf-8');
|
|
385
|
+
} else {
|
|
386
|
+
// JSON format (default)
|
|
387
|
+
let config = {};
|
|
388
|
+
if (existsSync(absPath)) {
|
|
389
|
+
try { config = parseJsonc(readFileSync(absPath, 'utf-8')); } catch {
|
|
390
|
+
console.error(red(` Failed to parse existing config: ${absPath} — skipping.`));
|
|
391
|
+
continue;
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
if (!config[agent.key]) config[agent.key] = {};
|
|
395
|
+
existed = !!config[agent.key].mindos;
|
|
396
|
+
config[agent.key].mindos = entry;
|
|
397
|
+
writeFileSync(absPath, JSON.stringify(config, null, 2) + '\n', 'utf-8');
|
|
398
|
+
}
|
|
331
399
|
|
|
332
400
|
console.log(`${green('✔')} ${existed ? 'Updated' : 'Installed'} MindOS MCP for ${bold(agent.name)} ${dim(`→ ${absPath}`)}`);
|
|
401
|
+
|
|
402
|
+
// Auto-copy skill for unsupported agents
|
|
403
|
+
const skillResult = autoInstallSkillForAgent(agentKey, 'mindos');
|
|
404
|
+
if (skillResult === 'copied') {
|
|
405
|
+
console.log(`${green('✔')} Copied MindOS Skill for ${bold(agent.name)}`);
|
|
406
|
+
}
|
|
333
407
|
}
|
|
334
408
|
|
|
335
409
|
console.log(`\n${green('Done!')} ${agentKeys.length} agent(s) configured.`);
|