@yancyyu/openhermit 1.6.28 → 1.6.30
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/dist-renderer/assets/ProjectEditorOverlay-DsQt4FHy.js +52 -0
- package/dist-renderer/assets/{TeamGraphOverlay-Ba5njic5.js → TeamGraphOverlay-BjZC53xf.js} +1 -1
- package/dist-renderer/assets/{_basePickBy-BvnK-OC1.js → _basePickBy-CrWocIjq.js} +1 -1
- package/dist-renderer/assets/{_baseUniq-DmFYXx9G.js → _baseUniq-B6d8ysWi.js} +1 -1
- package/dist-renderer/assets/{arc-DX4ZQFY4.js → arc-DAIYCFP8.js} +1 -1
- package/dist-renderer/assets/{architectureDiagram-VXUJARFQ-DfYr3vEN.js → architectureDiagram-VXUJARFQ-B3UudXJh.js} +1 -1
- package/dist-renderer/assets/{blockDiagram-VD42YOAC-DuXdVeWn.js → blockDiagram-VD42YOAC-DbptKQ4W.js} +1 -1
- package/dist-renderer/assets/{c4Diagram-YG6GDRKO-Bw2nixXe.js → c4Diagram-YG6GDRKO-C4WQuZpV.js} +1 -1
- package/dist-renderer/assets/channel-DbjZvWii.js +1 -0
- package/dist-renderer/assets/{chunk-4BX2VUAB-DLiNGQoE.js → chunk-4BX2VUAB-Dp7fVpI_.js} +1 -1
- package/dist-renderer/assets/{chunk-55IACEB6-B1L_8VIF.js → chunk-55IACEB6-B8KGfbAy.js} +1 -1
- package/dist-renderer/assets/{chunk-B4BG7PRW-DaZMWKGk.js → chunk-B4BG7PRW-BG1oJrjA.js} +1 -1
- package/dist-renderer/assets/{chunk-DI55MBZ5-ku-dflJG.js → chunk-DI55MBZ5-DRmxNjht.js} +1 -1
- package/dist-renderer/assets/{chunk-FMBD7UC4-DV-mF1dP.js → chunk-FMBD7UC4-D6VLvy16.js} +1 -1
- package/dist-renderer/assets/{chunk-QN33PNHL-ByGcDFQ0.js → chunk-QN33PNHL-DZou1667.js} +1 -1
- package/dist-renderer/assets/{chunk-QZHKN3VN-7dv-Min8.js → chunk-QZHKN3VN-CghmasSh.js} +1 -1
- package/dist-renderer/assets/{chunk-TZMSLE5B-WdXL5fTu.js → chunk-TZMSLE5B-B7apcMPK.js} +1 -1
- package/dist-renderer/assets/classDiagram-2ON5EDUG-D_FGxxsl.js +1 -0
- package/dist-renderer/assets/classDiagram-v2-WZHVMYZB-D_FGxxsl.js +1 -0
- package/dist-renderer/assets/clone-CJ1kxO2J.js +1 -0
- package/dist-renderer/assets/{cose-bilkent-S5V4N54A-CNcsvqPl.js → cose-bilkent-S5V4N54A-05e5uQDp.js} +1 -1
- package/dist-renderer/assets/{dagre-6UL2VRFP-DBNx4qqx.js → dagre-6UL2VRFP-B06bRykF.js} +1 -1
- package/dist-renderer/assets/{diagram-PSM6KHXK-BfVlT6sT.js → diagram-PSM6KHXK-CY7VYQ7c.js} +1 -1
- package/dist-renderer/assets/{diagram-QEK2KX5R-HvVjs0K6.js → diagram-QEK2KX5R-BjKEH7dD.js} +1 -1
- package/dist-renderer/assets/{diagram-S2PKOQOG-DYb_KnWS.js → diagram-S2PKOQOG-Bf4ELS1_.js} +1 -1
- package/dist-renderer/assets/{erDiagram-Q2GNP2WA-Ba-IgI5G.js → erDiagram-Q2GNP2WA-DJ753_L9.js} +1 -1
- package/dist-renderer/assets/{flowDiagram-NV44I4VS-2iDN8Kpj.js → flowDiagram-NV44I4VS-B71S-lC-.js} +1 -1
- package/dist-renderer/assets/{ganttDiagram-JELNMOA3-Byjf8Fa3.js → ganttDiagram-JELNMOA3-C_U42mSZ.js} +1 -1
- package/dist-renderer/assets/{gitGraphDiagram-V2S2FVAM-DbKvfZ_j.js → gitGraphDiagram-V2S2FVAM-DKUJU4Ns.js} +1 -1
- package/dist-renderer/assets/{graph-Enirf-f8.js → graph-DY3qbzqj.js} +1 -1
- package/dist-renderer/assets/{index-DY1zqsb6.js → index-BlOrAXp3.js} +551 -537
- package/dist-renderer/assets/{index-AjxP_rE_.js → index-Bs27J5gB.js} +1 -1
- package/dist-renderer/assets/{index-CtlzGepK.js → index-C8B_nKOF.js} +1 -1
- package/dist-renderer/assets/index-CmZPUEhS.css +1 -0
- package/dist-renderer/assets/{index-COZPUWJW.js → index-DLKyDr4T.js} +1 -1
- package/dist-renderer/assets/{index-DdhqolqE.js → index-Dhsk3_DD.js} +1 -1
- package/dist-renderer/assets/{index-ChR1D6ZF.js → index-GpUvV2xs.js} +1 -1
- package/dist-renderer/assets/{infoDiagram-HS3SLOUP-D6uicwz1.js → infoDiagram-HS3SLOUP-BNs0y3IG.js} +1 -1
- package/dist-renderer/assets/{journeyDiagram-XKPGCS4Q-DqwZsXlQ.js → journeyDiagram-XKPGCS4Q-CqPnw4UV.js} +1 -1
- package/dist-renderer/assets/{kanban-definition-3W4ZIXB7-fCDVhVUm.js → kanban-definition-3W4ZIXB7-SLlzcUJ2.js} +1 -1
- package/dist-renderer/assets/{layout-CPFgj98r.js → layout-BZLlNmbr.js} +1 -1
- package/dist-renderer/assets/{linear-CYiQ7Y3M.js → linear-qz6v45xy.js} +1 -1
- package/dist-renderer/assets/{mindmap-definition-VGOIOE7T-D31dS2KE.js → mindmap-definition-VGOIOE7T-B1-kmEWV.js} +1 -1
- package/dist-renderer/assets/{pieDiagram-ADFJNKIX-BOsCJfds.js → pieDiagram-ADFJNKIX-B8a02iNx.js} +1 -1
- package/dist-renderer/assets/{quadrantDiagram-AYHSOK5B-CYTVQCfr.js → quadrantDiagram-AYHSOK5B-BKv1Xfou.js} +1 -1
- package/dist-renderer/assets/{requirementDiagram-UZGBJVZJ-CODCFpkt.js → requirementDiagram-UZGBJVZJ-B3DUpZi2.js} +1 -1
- package/dist-renderer/assets/{sankeyDiagram-TZEHDZUN-Z4ce9ZtZ.js → sankeyDiagram-TZEHDZUN-DmPzuTsy.js} +1 -1
- package/dist-renderer/assets/{sequenceDiagram-WL72ISMW-CmS9TxhW.js → sequenceDiagram-WL72ISMW-Bo7RelRb.js} +1 -1
- package/dist-renderer/assets/{stateDiagram-FKZM4ZOC-o9k-ns3q.js → stateDiagram-FKZM4ZOC-1epX98gV.js} +1 -1
- package/dist-renderer/assets/{stateDiagram-v2-4FDKWEC3-CxHMyEt1.js → stateDiagram-v2-4FDKWEC3-03Ym9PTr.js} +1 -1
- package/dist-renderer/assets/{timeline-definition-IT6M3QCI-B6T3zrde.js → timeline-definition-IT6M3QCI-r6isC62H.js} +1 -1
- package/dist-renderer/assets/{treemap-GDKQZRPO-CVd5GNDw.js → treemap-GDKQZRPO-CGKpOUF2.js} +1 -1
- package/dist-renderer/assets/{xychartDiagram-PRI3JC2R-CleBrdqc.js → xychartDiagram-PRI3JC2R-t4-rwdAw.js} +1 -1
- package/dist-renderer/index.html +2 -2
- package/package.json +4 -1
- package/src/main/ipc/extensions.ts +353 -0
- package/src/main/server.ts +907 -184
- package/src/main/services/extensions/ExtensionFacadeService.ts +135 -0
- package/src/main/services/extensions/catalog/GlamaMcpEnrichmentService.ts +190 -0
- package/src/main/services/extensions/catalog/McpCatalogAggregator.ts +150 -0
- package/src/main/services/extensions/catalog/OfficialMcpRegistryService.ts +381 -0
- package/src/main/services/extensions/catalog/PluginCatalogService.ts +392 -0
- package/src/main/services/extensions/credentials/CredentialService.ts +343 -0
- package/src/main/services/extensions/install/McpInstallService.ts +407 -0
- package/src/main/services/extensions/install/PluginInstallService.ts +198 -0
- package/src/main/services/extensions/runtime/ClaudeCodeAdapter.ts +199 -0
- package/src/main/services/extensions/runtime/CodexAdapter.ts +100 -0
- package/src/main/services/extensions/runtime/CursorAdapter.ts +154 -0
- package/src/main/services/extensions/runtime/ExtensionsRuntimeAdapter.ts +172 -0
- package/src/main/services/extensions/runtime/GeminiAdapter.ts +91 -0
- package/src/main/services/extensions/runtime/HarnessInstallAdapter.ts +49 -0
- package/src/main/services/extensions/runtime/McpConfigStateReader.ts +209 -0
- package/src/main/services/extensions/runtime/OpenCodeAdapter.ts +91 -0
- package/src/main/services/extensions/runtime/adapterRegistry.ts +54 -0
- package/src/main/services/extensions/runtime/mcpDiagnosticsParser.ts +214 -0
- package/src/main/services/extensions/runtime/mcpRuntimeJson.ts +45 -0
- package/src/main/services/extensions/skills/SkillImportService.ts +155 -0
- package/src/main/services/extensions/skills/SkillMetadataParser.ts +323 -0
- package/src/main/services/extensions/skills/SkillPlanService.ts +411 -0
- package/src/main/services/extensions/skills/SkillReviewService.ts +73 -0
- package/src/main/services/extensions/skills/SkillRootsResolver.ts +49 -0
- package/src/main/services/extensions/skills/SkillScaffoldService.ts +89 -0
- package/src/main/services/extensions/skills/SkillScanner.ts +117 -0
- package/src/main/services/extensions/skills/SkillValidator.ts +69 -0
- package/src/main/services/extensions/skills/SkillsCatalogService.ts +92 -0
- package/src/main/services/extensions/skills/SkillsMutationService.ts +146 -0
- package/src/main/services/extensions/skills/SkillsWatcherService.ts +134 -0
- package/src/main/services/extensions/state/McpInstallationStateService.ts +42 -0
- package/src/main/services/extensions/state/PluginInstallationStateService.ts +281 -0
- package/src/main/services/identity/AgentTeamsIdentityStore.ts +218 -0
- package/src/main/services/runtime/providerAwareCliEnv.ts +60 -0
- package/src/main/services/session-intelligence/UsageTelemetryService.ts +33 -18
- package/src/main/services/team/ClaudeBinaryResolver.ts +469 -0
- package/src/main/services/team/ClaudeDoctorProbe.ts +0 -0
- package/src/main/services/team/cliFlavor.ts +54 -0
- package/src/main/services/teams-mvp/CollaborationBoardService.ts +310 -0
- package/src/main/services/teams-mvp/TaskDispatchService.ts +883 -95
- package/src/main/services/teams-mvp/TeamProvisioningService.ts +58 -19
- package/src/main/services/teams-mvp/TeamWorkspaceService.ts +25 -2
- package/src/main/services/teams-mvp/index.ts +3 -0
- package/src/main/utils/atomicWrite.ts +72 -0
- package/src/main/utils/childProcess.ts +554 -0
- package/src/main/utils/cliEnv.ts +54 -0
- package/src/main/utils/cliPathMerge.ts +97 -0
- package/src/main/utils/pathDecoder.ts +664 -0
- package/src/main/utils/pathValidation.ts +432 -0
- package/src/main/utils/shellEnv.ts +331 -0
- package/src/renderer/App.tsx +5 -0
- package/src/renderer/api/httpClient.ts +128 -0
- package/src/renderer/components/extensions/ExtensionStoreView.tsx +59 -34
- package/src/renderer/components/extensions/ExtensionsSubTabTrigger.tsx +1 -1
- package/src/renderer/components/extensions/common/ExtensionToast.tsx +141 -0
- package/src/renderer/components/extensions/common/HarnessSelector.tsx +71 -0
- package/src/renderer/components/extensions/env/EnvVarPanel.tsx +335 -0
- package/src/renderer/components/extensions/env/ProjectEnvPanel.tsx +239 -0
- package/src/renderer/components/extensions/mcp/CustomMcpServerDialog.tsx +14 -223
- package/src/renderer/components/extensions/mcp/McpServerDetailDialog.tsx +11 -0
- package/src/renderer/components/extensions/mcp/McpServersPanel.tsx +51 -1
- package/src/renderer/components/extensions/skills/SkillsPanel.tsx +1 -126
- package/src/renderer/components/layout/PaneContent.tsx +2 -0
- package/src/renderer/components/layout/SortableTab.tsx +1 -0
- package/src/renderer/components/layout/TabBarActions.tsx +12 -12
- package/src/renderer/components/schedules/SchedulesView.tsx +54 -22
- package/src/renderer/components/settings/sections/AdvancedSection.tsx +1 -1
- package/src/renderer/components/settings/sections/HarnessSection.tsx +2 -6
- package/src/renderer/components/settings/sections/TaskBusSection.tsx +144 -84
- package/src/renderer/components/sidebar/SidebarSessions.tsx +23 -0
- package/src/renderer/components/sidebar/WorkspaceBrowser.tsx +1 -7
- package/src/renderer/components/tasks/TasksView.tsx +343 -0
- package/src/renderer/components/team/HarnessSelect.tsx +71 -0
- package/src/renderer/components/team/TeamDetailView.tsx +55 -98
- package/src/renderer/components/team/dialogs/CreateTeamDialog.tsx +21 -12
- package/src/renderer/components/team/dialogs/EditTeamDialog.tsx +8 -13
- package/src/renderer/components/team/dialogs/LaunchTeamDialog.tsx +1 -1
- package/src/renderer/components/team/editor/EditorContextMenu.tsx +8 -23
- package/src/renderer/components/team/editor/EditorFileTree.tsx +0 -4
- package/src/renderer/components/team/editor/EditorSelectionMenu.tsx +1 -8
- package/src/renderer/components/team/editor/ProjectEditorOverlay.tsx +0 -10
- package/src/renderer/components/team/kanban/KanbanBoard.tsx +31 -65
- package/src/renderer/components/team/members/MemberDetailDialog.tsx +8 -33
- package/src/renderer/components/team/messages/MessageComposer.tsx +39 -3
- package/src/renderer/components/team/messages/MessagesPanel.tsx +100 -26
- package/src/renderer/components/team/messages/StatusBlock.tsx +2 -24
- package/src/renderer/components/team/schedule/ScheduleEmptyState.tsx +1 -1
- package/src/renderer/components/terminal/TerminalPanel.tsx +156 -0
- package/src/renderer/components/ui/MentionableTextarea.tsx +0 -1
- package/src/renderer/hooks/useExtensionsTabState.ts +2 -2
- package/src/renderer/store/slices/extensionsSlice.ts +42 -107
- package/src/renderer/store/slices/scheduleSlice.ts +21 -0
- package/src/renderer/store/slices/teamSlice.ts +67 -25
- package/src/renderer/types/tabs.ts +1 -0
- package/src/shared/types/api.ts +58 -0
- package/src/shared/types/extensions/index.ts +1 -0
- package/src/shared/types/extensions/mcp.ts +2 -0
- package/src/shared/types/extensions/plugin.ts +2 -1
- package/src/shared/types/extensions/skill.ts +7 -0
- package/src/shared/types/team.ts +104 -1
- package/src/shared/utils/providerExtensionCapabilities.ts +1 -1
- package/dist-renderer/assets/ProjectEditorOverlay-A4DZTvSy.js +0 -57
- package/dist-renderer/assets/channel-Pre42N5O.js +0 -1
- package/dist-renderer/assets/classDiagram-2ON5EDUG-CdJsTJsj.js +0 -1
- package/dist-renderer/assets/classDiagram-v2-WZHVMYZB-CdJsTJsj.js +0 -1
- package/dist-renderer/assets/clone-BjQBiNfj.js +0 -1
- package/dist-renderer/assets/index-BIOJremZ.css +0 -1
- package/src/features/recent-projects/main/adapters/input/http/registerRecentProjectsHttp.ts +0 -30
- package/src/features/recent-projects/main/adapters/output/presenters/DashboardRecentProjectsPresenter.ts +0 -27
- package/src/features/recent-projects/main/adapters/output/sources/ClaudeRecentProjectsSourceAdapter.ts +0 -91
- package/src/features/recent-projects/main/adapters/output/sources/CodexRecentProjectsSourceAdapter.ts +0 -326
- package/src/features/recent-projects/main/composition/createRecentProjectsFeature.ts +0 -43
- package/src/features/recent-projects/main/index.ts +0 -3
- package/src/features/recent-projects/main/infrastructure/cache/InMemoryRecentProjectsCache.ts +0 -34
- package/src/features/recent-projects/main/infrastructure/codex/CodexAppServerClient.ts +0 -116
- package/src/features/recent-projects/main/infrastructure/identity/RecentProjectIdentityResolver.ts +0 -20
- package/src/features/recent-projects/main/infrastructure/identity/normalizeIdentityPath.ts +0 -10
- package/src/renderer/components/extensions/apikeys/ApiKeyCard.tsx +0 -143
- package/src/renderer/components/extensions/apikeys/ApiKeyFormDialog.tsx +0 -282
- package/src/renderer/components/extensions/apikeys/ApiKeysPanel.tsx +0 -280
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Gemini harness adapter — uses `gemini` CLI for MCP and skills.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { getHomeDir } from '@main/utils/pathDecoder';
|
|
6
|
+
import type { CcAgentType } from '@shared/types/ccConnect';
|
|
7
|
+
import path from 'node:path';
|
|
8
|
+
|
|
9
|
+
import { ClaudeCodeAdapter } from './ClaudeCodeAdapter';
|
|
10
|
+
|
|
11
|
+
import type {
|
|
12
|
+
HarnessInstallAdapter,
|
|
13
|
+
InstallOpts,
|
|
14
|
+
ResolvedSkillRoot,
|
|
15
|
+
} from './HarnessInstallAdapter';
|
|
16
|
+
import type {
|
|
17
|
+
InstalledMcpEntry,
|
|
18
|
+
InstalledPluginEntry,
|
|
19
|
+
McpHeaderDef,
|
|
20
|
+
McpInstallSpec,
|
|
21
|
+
OperationResult,
|
|
22
|
+
} from '@shared/types/extensions';
|
|
23
|
+
|
|
24
|
+
function resolveBinaryFromPath(binaryName: string): Promise<string | null> {
|
|
25
|
+
return new Promise((resolve) => {
|
|
26
|
+
import('node:child_process')
|
|
27
|
+
.then(({ execFile }) => {
|
|
28
|
+
execFile('which', [binaryName], { timeout: 5_000 }, (err, stdout) => {
|
|
29
|
+
resolve(err || !stdout?.trim() ? null : stdout.trim());
|
|
30
|
+
});
|
|
31
|
+
})
|
|
32
|
+
.catch(() => resolve(null));
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export class GeminiAdapter implements HarnessInstallAdapter {
|
|
37
|
+
readonly harnessType: CcAgentType = 'gemini';
|
|
38
|
+
readonly supportsPlugins = false;
|
|
39
|
+
readonly supportsMcp = true;
|
|
40
|
+
readonly supportsSkills = true;
|
|
41
|
+
|
|
42
|
+
private readonly delegate = new ClaudeCodeAdapter();
|
|
43
|
+
|
|
44
|
+
async resolveBinary(): Promise<string | null> {
|
|
45
|
+
return resolveBinaryFromPath('gemini');
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
async installPlugin(): Promise<OperationResult> {
|
|
49
|
+
return { state: 'error', error: 'Gemini does not support Claude plugins' };
|
|
50
|
+
}
|
|
51
|
+
async uninstallPlugin(): Promise<OperationResult> {
|
|
52
|
+
return { state: 'error', error: 'Gemini does not support Claude plugins' };
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
async installMcp(
|
|
56
|
+
name: string,
|
|
57
|
+
spec: McpInstallSpec,
|
|
58
|
+
envValues: Record<string, string>,
|
|
59
|
+
headers: McpHeaderDef[],
|
|
60
|
+
opts: InstallOpts
|
|
61
|
+
): Promise<OperationResult> {
|
|
62
|
+
return this.delegate.installMcp(name, spec, envValues, headers, opts);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
async uninstallMcp(name: string, opts: InstallOpts): Promise<OperationResult> {
|
|
66
|
+
return this.delegate.uninstallMcp(name, opts);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
async listInstalledMcp(projectPath?: string): Promise<InstalledMcpEntry[]> {
|
|
70
|
+
return this.delegate.listInstalledMcp(projectPath);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
async listInstalledPlugins(): Promise<InstalledPluginEntry[]> {
|
|
74
|
+
return [];
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
getSkillRoots(projectPath?: string): ResolvedSkillRoot[] {
|
|
78
|
+
const home = getHomeDir();
|
|
79
|
+
const roots: ResolvedSkillRoot[] = [
|
|
80
|
+
{ kind: 'gemini', scope: 'user', path: path.join(home, '.gemini', 'skills') },
|
|
81
|
+
];
|
|
82
|
+
if (projectPath) {
|
|
83
|
+
roots.push({
|
|
84
|
+
kind: 'gemini',
|
|
85
|
+
scope: 'project',
|
|
86
|
+
path: path.join(projectPath, '.gemini', 'skills'),
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
return roots;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Harness install adapter interface.
|
|
3
|
+
*
|
|
4
|
+
* Each supported harness implements this interface to provide
|
|
5
|
+
* harness-specific installation commands and configuration paths.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type { CcAgentType } from '@shared/types/ccConnect';
|
|
9
|
+
import type { McpHeaderDef, McpInstallSpec, OperationResult } from '@shared/types/extensions';
|
|
10
|
+
|
|
11
|
+
import type { InstalledMcpEntry } from '@shared/types/extensions';
|
|
12
|
+
import type { InstalledPluginEntry } from '@shared/types/extensions';
|
|
13
|
+
|
|
14
|
+
export interface InstallOpts {
|
|
15
|
+
scope: 'user' | 'project' | 'local' | 'global';
|
|
16
|
+
projectPath?: string;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface ResolvedSkillRoot {
|
|
20
|
+
kind: string;
|
|
21
|
+
scope: 'user' | 'project';
|
|
22
|
+
path: string;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export interface HarnessInstallAdapter {
|
|
26
|
+
readonly harnessType: CcAgentType;
|
|
27
|
+
readonly supportsPlugins: boolean;
|
|
28
|
+
readonly supportsMcp: boolean;
|
|
29
|
+
readonly supportsSkills: boolean;
|
|
30
|
+
|
|
31
|
+
resolveBinary(): Promise<string | null>;
|
|
32
|
+
|
|
33
|
+
installPlugin(qualifiedName: string, opts: InstallOpts): Promise<OperationResult>;
|
|
34
|
+
uninstallPlugin(qualifiedName: string, opts: InstallOpts): Promise<OperationResult>;
|
|
35
|
+
|
|
36
|
+
installMcp(
|
|
37
|
+
name: string,
|
|
38
|
+
spec: McpInstallSpec,
|
|
39
|
+
envValues: Record<string, string>,
|
|
40
|
+
headers: McpHeaderDef[],
|
|
41
|
+
opts: InstallOpts
|
|
42
|
+
): Promise<OperationResult>;
|
|
43
|
+
uninstallMcp(name: string, opts: InstallOpts): Promise<OperationResult>;
|
|
44
|
+
|
|
45
|
+
listInstalledMcp(projectPath?: string): Promise<InstalledMcpEntry[]>;
|
|
46
|
+
listInstalledPlugins(projectPath?: string): Promise<InstalledPluginEntry[]>;
|
|
47
|
+
|
|
48
|
+
getSkillRoots(projectPath?: string): ResolvedSkillRoot[];
|
|
49
|
+
}
|
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
import * as fs from 'node:fs/promises';
|
|
2
|
+
import * as path from 'node:path';
|
|
3
|
+
|
|
4
|
+
import { getHomeDir } from '@main/utils/pathDecoder';
|
|
5
|
+
import { createLogger } from '@shared/utils/logger';
|
|
6
|
+
|
|
7
|
+
import type { InstalledMcpEntry } from '@shared/types/extensions';
|
|
8
|
+
|
|
9
|
+
const logger = createLogger('Extensions:McpConfigStateReader');
|
|
10
|
+
|
|
11
|
+
export interface ConfiguredMcpEntry extends InstalledMcpEntry {
|
|
12
|
+
scope: 'local' | 'user' | 'project';
|
|
13
|
+
config: Record<string, unknown>;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export class McpConfigStateReader {
|
|
17
|
+
async readInstalled(projectPath?: string): Promise<InstalledMcpEntry[]> {
|
|
18
|
+
const entries: InstalledMcpEntry[] = [];
|
|
19
|
+
const claudeConfig = await this.readClaudeConfig();
|
|
20
|
+
|
|
21
|
+
entries.push(...this.readUserMcpServers(claudeConfig));
|
|
22
|
+
|
|
23
|
+
// Also read from ~/.claude/settings.json (newer Claude Code versions)
|
|
24
|
+
const settingsConfig = await this.readClaudeSettingsConfig();
|
|
25
|
+
entries.push(...this.readUserMcpServers(settingsConfig));
|
|
26
|
+
|
|
27
|
+
if (projectPath) {
|
|
28
|
+
entries.push(...this.readLocalMcpServers(claudeConfig, projectPath));
|
|
29
|
+
entries.push(...(await this.readProjectMcpServers(projectPath)));
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// Deduplicate by name (settings.json takes precedence if both exist)
|
|
33
|
+
const seen = new Map<string, InstalledMcpEntry>();
|
|
34
|
+
for (const entry of entries) {
|
|
35
|
+
if (!seen.has(entry.name)) {
|
|
36
|
+
seen.set(entry.name, entry);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
return [...seen.values()];
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
async readConfigured(projectPath?: string): Promise<ConfiguredMcpEntry[]> {
|
|
44
|
+
const entries: ConfiguredMcpEntry[] = [];
|
|
45
|
+
const claudeConfig = await this.readClaudeConfig();
|
|
46
|
+
|
|
47
|
+
entries.push(...this.readConfiguredMcpServersFromConfig(claudeConfig?.mcpServers, 'user'));
|
|
48
|
+
|
|
49
|
+
const settingsConfig = await this.readClaudeSettingsConfig();
|
|
50
|
+
entries.push(...this.readConfiguredMcpServersFromConfig(settingsConfig?.mcpServers, 'user'));
|
|
51
|
+
|
|
52
|
+
if (projectPath) {
|
|
53
|
+
entries.push(...this.readLocalConfiguredMcpServers(claudeConfig, projectPath));
|
|
54
|
+
entries.push(...(await this.readProjectConfiguredMcpServers(projectPath)));
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Deduplicate by name
|
|
58
|
+
const seen = new Map<string, ConfiguredMcpEntry>();
|
|
59
|
+
for (const entry of entries) {
|
|
60
|
+
if (!seen.has(entry.name)) {
|
|
61
|
+
seen.set(entry.name, entry);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
return [...seen.values()];
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
private async readClaudeConfig(): Promise<Record<string, unknown> | null> {
|
|
69
|
+
const configPath = path.join(getHomeDir(), '.claude.json');
|
|
70
|
+
try {
|
|
71
|
+
const raw = await fs.readFile(configPath, 'utf-8');
|
|
72
|
+
return JSON.parse(raw) as Record<string, unknown>;
|
|
73
|
+
} catch (err) {
|
|
74
|
+
if ((err as NodeJS.ErrnoException).code === 'ENOENT') {
|
|
75
|
+
return null;
|
|
76
|
+
}
|
|
77
|
+
logger.error(`Failed to read MCP servers from ${configPath}:`, err);
|
|
78
|
+
return null;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Read ~/.claude/settings.json — newer Claude Code versions store MCP configs here.
|
|
84
|
+
*/
|
|
85
|
+
private async readClaudeSettingsConfig(): Promise<Record<string, unknown> | null> {
|
|
86
|
+
const configPath = path.join(getHomeDir(), '.claude', 'settings.json');
|
|
87
|
+
try {
|
|
88
|
+
const raw = await fs.readFile(configPath, 'utf-8');
|
|
89
|
+
return JSON.parse(raw) as Record<string, unknown>;
|
|
90
|
+
} catch (err) {
|
|
91
|
+
if ((err as NodeJS.ErrnoException).code === 'ENOENT') {
|
|
92
|
+
return null;
|
|
93
|
+
}
|
|
94
|
+
logger.error(`Failed to read MCP servers from ${configPath}:`, err);
|
|
95
|
+
return null;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
private readUserMcpServers(config: Record<string, unknown> | null): InstalledMcpEntry[] {
|
|
100
|
+
return this.readMcpServersFromConfig(config?.mcpServers, 'user');
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
private readLocalMcpServers(
|
|
104
|
+
config: Record<string, unknown> | null,
|
|
105
|
+
projectPath: string
|
|
106
|
+
): InstalledMcpEntry[] {
|
|
107
|
+
return this.readLocalConfiguredMcpServers(config, projectPath).map(
|
|
108
|
+
({ config: _config, ...entry }) => entry
|
|
109
|
+
);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
private readLocalConfiguredMcpServers(
|
|
113
|
+
config: Record<string, unknown> | null,
|
|
114
|
+
projectPath: string
|
|
115
|
+
): ConfiguredMcpEntry[] {
|
|
116
|
+
const projects =
|
|
117
|
+
config && typeof config.projects === 'object' && config.projects
|
|
118
|
+
? (config.projects as Record<string, unknown>)
|
|
119
|
+
: null;
|
|
120
|
+
const projectConfig =
|
|
121
|
+
projects && typeof projects[projectPath] === 'object' && projects[projectPath]
|
|
122
|
+
? (projects[projectPath] as Record<string, unknown>)
|
|
123
|
+
: null;
|
|
124
|
+
return this.readConfiguredMcpServersFromConfig(projectConfig?.mcpServers, 'local');
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
private async readProjectMcpServers(projectPath: string): Promise<InstalledMcpEntry[]> {
|
|
128
|
+
const configPath = path.join(projectPath, '.mcp.json');
|
|
129
|
+
return this.readMcpServersFromFile(configPath, 'project');
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
private async readProjectConfiguredMcpServers(
|
|
133
|
+
projectPath: string
|
|
134
|
+
): Promise<ConfiguredMcpEntry[]> {
|
|
135
|
+
const configPath = path.join(projectPath, '.mcp.json');
|
|
136
|
+
return this.readConfiguredMcpServersFromFile(configPath, 'project');
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
private readMcpServersFromConfig(
|
|
140
|
+
value: unknown,
|
|
141
|
+
scope: 'user' | 'project' | 'local'
|
|
142
|
+
): InstalledMcpEntry[] {
|
|
143
|
+
const mcpServers =
|
|
144
|
+
value && typeof value === 'object'
|
|
145
|
+
? (value as Record<string, { command?: string; url?: string }>)
|
|
146
|
+
: null;
|
|
147
|
+
if (!mcpServers) {
|
|
148
|
+
return [];
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
return Object.entries(mcpServers).map(([name, config]): InstalledMcpEntry => {
|
|
152
|
+
let transport: string | undefined;
|
|
153
|
+
if (config.command) transport = 'stdio';
|
|
154
|
+
else if (config.url) transport = 'http';
|
|
155
|
+
|
|
156
|
+
return { name, scope, transport };
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
private readConfiguredMcpServersFromConfig(
|
|
161
|
+
value: unknown,
|
|
162
|
+
scope: 'user' | 'project' | 'local'
|
|
163
|
+
): ConfiguredMcpEntry[] {
|
|
164
|
+
const mcpServers =
|
|
165
|
+
value && typeof value === 'object' ? (value as Record<string, unknown>) : null;
|
|
166
|
+
if (!mcpServers) {
|
|
167
|
+
return [];
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
return Object.entries(mcpServers)
|
|
171
|
+
.filter((entry): entry is [string, Record<string, unknown>] => {
|
|
172
|
+
const [, config] = entry;
|
|
173
|
+
return Boolean(config && typeof config === 'object' && !Array.isArray(config));
|
|
174
|
+
})
|
|
175
|
+
.map(([name, config]): ConfiguredMcpEntry => {
|
|
176
|
+
let transport: string | undefined;
|
|
177
|
+
if (typeof config.command === 'string') transport = 'stdio';
|
|
178
|
+
else if (typeof config.url === 'string') transport = 'http';
|
|
179
|
+
|
|
180
|
+
return { name, scope, transport, config: { ...config } };
|
|
181
|
+
});
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
private async readMcpServersFromFile(
|
|
185
|
+
filePath: string,
|
|
186
|
+
scope: 'user' | 'project'
|
|
187
|
+
): Promise<InstalledMcpEntry[]> {
|
|
188
|
+
return (await this.readConfiguredMcpServersFromFile(filePath, scope)).map(
|
|
189
|
+
({ config: _config, ...entry }) => entry
|
|
190
|
+
);
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
private async readConfiguredMcpServersFromFile(
|
|
194
|
+
filePath: string,
|
|
195
|
+
scope: 'user' | 'project'
|
|
196
|
+
): Promise<ConfiguredMcpEntry[]> {
|
|
197
|
+
try {
|
|
198
|
+
const raw = await fs.readFile(filePath, 'utf-8');
|
|
199
|
+
const json = JSON.parse(raw) as Record<string, unknown>;
|
|
200
|
+
return this.readConfiguredMcpServersFromConfig(json.mcpServers, scope);
|
|
201
|
+
} catch (err) {
|
|
202
|
+
if ((err as NodeJS.ErrnoException).code === 'ENOENT') {
|
|
203
|
+
return [];
|
|
204
|
+
}
|
|
205
|
+
logger.error(`Failed to read MCP servers from ${filePath}:`, err);
|
|
206
|
+
return [];
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OpenCode harness adapter — uses `opencode` CLI for MCP and skills.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { getHomeDir } from '@main/utils/pathDecoder';
|
|
6
|
+
import type { CcAgentType } from '@shared/types/ccConnect';
|
|
7
|
+
import path from 'node:path';
|
|
8
|
+
|
|
9
|
+
import { ClaudeCodeAdapter } from './ClaudeCodeAdapter';
|
|
10
|
+
|
|
11
|
+
import type {
|
|
12
|
+
HarnessInstallAdapter,
|
|
13
|
+
InstallOpts,
|
|
14
|
+
ResolvedSkillRoot,
|
|
15
|
+
} from './HarnessInstallAdapter';
|
|
16
|
+
import type {
|
|
17
|
+
InstalledMcpEntry,
|
|
18
|
+
InstalledPluginEntry,
|
|
19
|
+
McpHeaderDef,
|
|
20
|
+
McpInstallSpec,
|
|
21
|
+
OperationResult,
|
|
22
|
+
} from '@shared/types/extensions';
|
|
23
|
+
|
|
24
|
+
function resolveBinaryFromPath(binaryName: string): Promise<string | null> {
|
|
25
|
+
return new Promise((resolve) => {
|
|
26
|
+
import('node:child_process')
|
|
27
|
+
.then(({ execFile }) => {
|
|
28
|
+
execFile('which', [binaryName], { timeout: 5_000 }, (err, stdout) => {
|
|
29
|
+
resolve(err || !stdout?.trim() ? null : stdout.trim());
|
|
30
|
+
});
|
|
31
|
+
})
|
|
32
|
+
.catch(() => resolve(null));
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export class OpenCodeAdapter implements HarnessInstallAdapter {
|
|
37
|
+
readonly harnessType: CcAgentType = 'opencode';
|
|
38
|
+
readonly supportsPlugins = false;
|
|
39
|
+
readonly supportsMcp = true;
|
|
40
|
+
readonly supportsSkills = true;
|
|
41
|
+
|
|
42
|
+
private readonly delegate = new ClaudeCodeAdapter();
|
|
43
|
+
|
|
44
|
+
async resolveBinary(): Promise<string | null> {
|
|
45
|
+
return resolveBinaryFromPath('opencode');
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
async installPlugin(): Promise<OperationResult> {
|
|
49
|
+
return { state: 'error', error: 'OpenCode does not support Claude plugins' };
|
|
50
|
+
}
|
|
51
|
+
async uninstallPlugin(): Promise<OperationResult> {
|
|
52
|
+
return { state: 'error', error: 'OpenCode does not support Claude plugins' };
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
async installMcp(
|
|
56
|
+
name: string,
|
|
57
|
+
spec: McpInstallSpec,
|
|
58
|
+
envValues: Record<string, string>,
|
|
59
|
+
headers: McpHeaderDef[],
|
|
60
|
+
opts: InstallOpts
|
|
61
|
+
): Promise<OperationResult> {
|
|
62
|
+
return this.delegate.installMcp(name, spec, envValues, headers, opts);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
async uninstallMcp(name: string, opts: InstallOpts): Promise<OperationResult> {
|
|
66
|
+
return this.delegate.uninstallMcp(name, opts);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
async listInstalledMcp(projectPath?: string): Promise<InstalledMcpEntry[]> {
|
|
70
|
+
return this.delegate.listInstalledMcp(projectPath);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
async listInstalledPlugins(): Promise<InstalledPluginEntry[]> {
|
|
74
|
+
return [];
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
getSkillRoots(projectPath?: string): ResolvedSkillRoot[] {
|
|
78
|
+
const home = getHomeDir();
|
|
79
|
+
const roots: ResolvedSkillRoot[] = [
|
|
80
|
+
{ kind: 'opencode', scope: 'user', path: path.join(home, '.opencode', 'skills') },
|
|
81
|
+
];
|
|
82
|
+
if (projectPath) {
|
|
83
|
+
roots.push({
|
|
84
|
+
kind: 'opencode',
|
|
85
|
+
scope: 'project',
|
|
86
|
+
path: path.join(projectPath, '.opencode', 'skills'),
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
return roots;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Adapter registry — maps harness types to their install adapters.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import type { CcAgentType } from '@shared/types/ccConnect';
|
|
6
|
+
|
|
7
|
+
import type { HarnessInstallAdapter } from './HarnessInstallAdapter';
|
|
8
|
+
import { ClaudeCodeAdapter } from './ClaudeCodeAdapter';
|
|
9
|
+
import { CodexAdapter } from './CodexAdapter';
|
|
10
|
+
import { CursorAdapter } from './CursorAdapter';
|
|
11
|
+
import { GeminiAdapter } from './GeminiAdapter';
|
|
12
|
+
import { OpenCodeAdapter } from './OpenCodeAdapter';
|
|
13
|
+
|
|
14
|
+
const adapters = new Map<CcAgentType, HarnessInstallAdapter>();
|
|
15
|
+
|
|
16
|
+
function registerDefaults(): void {
|
|
17
|
+
if (adapters.size > 0) return;
|
|
18
|
+
const instances: HarnessInstallAdapter[] = [
|
|
19
|
+
new ClaudeCodeAdapter(),
|
|
20
|
+
new CodexAdapter(),
|
|
21
|
+
new GeminiAdapter(),
|
|
22
|
+
new OpenCodeAdapter(),
|
|
23
|
+
new CursorAdapter(),
|
|
24
|
+
];
|
|
25
|
+
for (const adapter of instances) {
|
|
26
|
+
adapters.set(adapter.harnessType, adapter);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function getAdapter(harnessType: CcAgentType): HarnessInstallAdapter | null {
|
|
31
|
+
registerDefaults();
|
|
32
|
+
return adapters.get(harnessType) ?? null;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function getAllAdapters(): HarnessInstallAdapter[] {
|
|
36
|
+
registerDefaults();
|
|
37
|
+
return [...adapters.values()];
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export function getAdaptersForCapability(
|
|
41
|
+
capability: 'plugins' | 'mcp' | 'skills'
|
|
42
|
+
): HarnessInstallAdapter[] {
|
|
43
|
+
registerDefaults();
|
|
44
|
+
return [...adapters.values()].filter((a) => {
|
|
45
|
+
switch (capability) {
|
|
46
|
+
case 'plugins':
|
|
47
|
+
return a.supportsPlugins;
|
|
48
|
+
case 'mcp':
|
|
49
|
+
return a.supportsMcp;
|
|
50
|
+
case 'skills':
|
|
51
|
+
return a.supportsSkills;
|
|
52
|
+
}
|
|
53
|
+
});
|
|
54
|
+
}
|