@yancyyu/openhermit 1.6.29 → 1.6.31

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (157) hide show
  1. package/dist-renderer/assets/{ProjectEditorOverlay-CQm6jUR1.js → ProjectEditorOverlay-DkXfi2pg.js} +1 -1
  2. package/dist-renderer/assets/{TeamGraphOverlay-h0WDfifv.js → TeamGraphOverlay-CHNNVraw.js} +1 -1
  3. package/dist-renderer/assets/{_basePickBy-CgG_tjgX.js → _basePickBy-Do-Ff83V.js} +1 -1
  4. package/dist-renderer/assets/{_baseUniq-DwPTU9lP.js → _baseUniq-nDLhSuJI.js} +1 -1
  5. package/dist-renderer/assets/{arc-7nIrGRzY.js → arc-Bp7fA6sx.js} +1 -1
  6. package/dist-renderer/assets/{architectureDiagram-VXUJARFQ-BYhA6Ev2.js → architectureDiagram-VXUJARFQ-CPC1HdGy.js} +1 -1
  7. package/dist-renderer/assets/{blockDiagram-VD42YOAC-BVpZUGDg.js → blockDiagram-VD42YOAC-DTVKyNTO.js} +1 -1
  8. package/dist-renderer/assets/{c4Diagram-YG6GDRKO-DsdreMQ9.js → c4Diagram-YG6GDRKO-XVu-AN00.js} +1 -1
  9. package/dist-renderer/assets/channel-CIwbNcUO.js +1 -0
  10. package/dist-renderer/assets/{chunk-4BX2VUAB-CcoAs7Jd.js → chunk-4BX2VUAB-BcWmVyA-.js} +1 -1
  11. package/dist-renderer/assets/{chunk-55IACEB6-CGGAOoXd.js → chunk-55IACEB6-Co4Z2jsE.js} +1 -1
  12. package/dist-renderer/assets/{chunk-B4BG7PRW-FhpTEPvD.js → chunk-B4BG7PRW-C8q9gfDT.js} +1 -1
  13. package/dist-renderer/assets/{chunk-DI55MBZ5-DoYySbm1.js → chunk-DI55MBZ5-qDgb1gxO.js} +1 -1
  14. package/dist-renderer/assets/{chunk-FMBD7UC4-e9l2tGHG.js → chunk-FMBD7UC4-Cm8Gu2gu.js} +1 -1
  15. package/dist-renderer/assets/{chunk-QN33PNHL-DeiXVTCy.js → chunk-QN33PNHL-DYji1BRS.js} +1 -1
  16. package/dist-renderer/assets/{chunk-QZHKN3VN-DC2UJLJM.js → chunk-QZHKN3VN-DWAS568H.js} +1 -1
  17. package/dist-renderer/assets/{chunk-TZMSLE5B-BHFD9eZI.js → chunk-TZMSLE5B-CLFzXLA8.js} +1 -1
  18. package/dist-renderer/assets/classDiagram-2ON5EDUG-04A-pvql.js +1 -0
  19. package/dist-renderer/assets/classDiagram-v2-WZHVMYZB-04A-pvql.js +1 -0
  20. package/dist-renderer/assets/clone-DQnvTIEM.js +1 -0
  21. package/dist-renderer/assets/{cose-bilkent-S5V4N54A-BdybQraU.js → cose-bilkent-S5V4N54A-CZdGhX_3.js} +1 -1
  22. package/dist-renderer/assets/{dagre-6UL2VRFP-DdF3pwM3.js → dagre-6UL2VRFP-BVY-G6nO.js} +1 -1
  23. package/dist-renderer/assets/{diagram-PSM6KHXK-B9Ldd3nh.js → diagram-PSM6KHXK-CUACvAwG.js} +1 -1
  24. package/dist-renderer/assets/{diagram-QEK2KX5R-XEqkrbpu.js → diagram-QEK2KX5R-3SfnesSG.js} +1 -1
  25. package/dist-renderer/assets/{diagram-S2PKOQOG-CipwtY59.js → diagram-S2PKOQOG-E3ksXClJ.js} +1 -1
  26. package/dist-renderer/assets/{erDiagram-Q2GNP2WA-BB-2ISGo.js → erDiagram-Q2GNP2WA-aYjGXss7.js} +1 -1
  27. package/dist-renderer/assets/{flowDiagram-NV44I4VS-B8XmJ0u2.js → flowDiagram-NV44I4VS-JMHrrTQs.js} +1 -1
  28. package/dist-renderer/assets/{ganttDiagram-JELNMOA3-D-8XglBb.js → ganttDiagram-JELNMOA3-CVQ-R5rN.js} +1 -1
  29. package/dist-renderer/assets/{gitGraphDiagram-V2S2FVAM-DL4ChakD.js → gitGraphDiagram-V2S2FVAM-OLn9jq61.js} +1 -1
  30. package/dist-renderer/assets/{graph-BiFNoBjP.js → graph-BAb2J0l8.js} +1 -1
  31. package/dist-renderer/assets/{index-qNBNjW4K.js → index-BSoCjBWn.js} +1 -1
  32. package/dist-renderer/assets/{index-6m1ZAymG.js → index-BtG3HbqP.js} +1 -1
  33. package/dist-renderer/assets/{index-BowUl0Jb.js → index-CH8e7g1f.js} +583 -573
  34. package/dist-renderer/assets/index-CSt8DTcn.css +1 -0
  35. package/dist-renderer/assets/{index-Dp3kJTEe.js → index-Ca4iNkRA.js} +1 -1
  36. package/dist-renderer/assets/{index-vAykq1H1.js → index-DU9PGgZJ.js} +1 -1
  37. package/dist-renderer/assets/{index-TOpt_T7A.js → index-DtMzIS9o.js} +1 -1
  38. package/dist-renderer/assets/{infoDiagram-HS3SLOUP-DRIBfHDi.js → infoDiagram-HS3SLOUP-CY_ptQNL.js} +1 -1
  39. package/dist-renderer/assets/{journeyDiagram-XKPGCS4Q-BOMiigU4.js → journeyDiagram-XKPGCS4Q-C2vuHEo_.js} +1 -1
  40. package/dist-renderer/assets/{kanban-definition-3W4ZIXB7-DDxeyjod.js → kanban-definition-3W4ZIXB7-mbdNfu8h.js} +1 -1
  41. package/dist-renderer/assets/{layout-DNANbrI4.js → layout-Do_ArEB1.js} +1 -1
  42. package/dist-renderer/assets/{linear-DxEJi1yT.js → linear-BMlMKyiq.js} +1 -1
  43. package/dist-renderer/assets/{mindmap-definition-VGOIOE7T-nBfGriW8.js → mindmap-definition-VGOIOE7T-Dfntn-o2.js} +1 -1
  44. package/dist-renderer/assets/{pieDiagram-ADFJNKIX-Din5j6sV.js → pieDiagram-ADFJNKIX-LiWHsGMV.js} +1 -1
  45. package/dist-renderer/assets/{quadrantDiagram-AYHSOK5B-DMVK2BEQ.js → quadrantDiagram-AYHSOK5B-D87St8AF.js} +1 -1
  46. package/dist-renderer/assets/{requirementDiagram-UZGBJVZJ-6SC94Gg_.js → requirementDiagram-UZGBJVZJ-DAa6lHBx.js} +1 -1
  47. package/dist-renderer/assets/{sankeyDiagram-TZEHDZUN-CD2gghhu.js → sankeyDiagram-TZEHDZUN-VOUngars.js} +1 -1
  48. package/dist-renderer/assets/{sequenceDiagram-WL72ISMW-BnhkN7nZ.js → sequenceDiagram-WL72ISMW-BzwzmFr2.js} +1 -1
  49. package/dist-renderer/assets/{stateDiagram-FKZM4ZOC-Bn8XdYX-.js → stateDiagram-FKZM4ZOC-BjAQEJ52.js} +1 -1
  50. package/dist-renderer/assets/{stateDiagram-v2-4FDKWEC3-1b6sI1_g.js → stateDiagram-v2-4FDKWEC3-BDwy4GJm.js} +1 -1
  51. package/dist-renderer/assets/{timeline-definition-IT6M3QCI-CNs3RPoa.js → timeline-definition-IT6M3QCI-Y5XBZt3W.js} +1 -1
  52. package/dist-renderer/assets/treemap-GDKQZRPO-DzkdUEow.js +162 -0
  53. package/dist-renderer/assets/{xychartDiagram-PRI3JC2R-B8o5J2f3.js → xychartDiagram-PRI3JC2R-D-zbvJOv.js} +1 -1
  54. package/dist-renderer/index.html +2 -2
  55. package/package.json +4 -1
  56. package/src/main/ipc/extensions.ts +353 -0
  57. package/src/main/server.ts +209 -6
  58. package/src/main/services/extensions/ExtensionFacadeService.ts +135 -0
  59. package/src/main/services/extensions/catalog/GlamaMcpEnrichmentService.ts +190 -0
  60. package/src/main/services/extensions/catalog/McpCatalogAggregator.ts +150 -0
  61. package/src/main/services/extensions/catalog/OfficialMcpRegistryService.ts +381 -0
  62. package/src/main/services/extensions/catalog/PluginCatalogService.ts +392 -0
  63. package/src/main/services/extensions/credentials/CredentialService.ts +343 -0
  64. package/src/main/services/extensions/install/McpInstallService.ts +407 -0
  65. package/src/main/services/extensions/install/PluginInstallService.ts +198 -0
  66. package/src/main/services/extensions/runtime/ClaudeCodeAdapter.ts +199 -0
  67. package/src/main/services/extensions/runtime/CodexAdapter.ts +100 -0
  68. package/src/main/services/extensions/runtime/CursorAdapter.ts +154 -0
  69. package/src/main/services/extensions/runtime/ExtensionsRuntimeAdapter.ts +172 -0
  70. package/src/main/services/extensions/runtime/GeminiAdapter.ts +91 -0
  71. package/src/main/services/extensions/runtime/HarnessInstallAdapter.ts +49 -0
  72. package/src/main/services/extensions/runtime/McpConfigStateReader.ts +209 -0
  73. package/src/main/services/extensions/runtime/OpenCodeAdapter.ts +91 -0
  74. package/src/main/services/extensions/runtime/adapterRegistry.ts +54 -0
  75. package/src/main/services/extensions/runtime/mcpDiagnosticsParser.ts +214 -0
  76. package/src/main/services/extensions/runtime/mcpRuntimeJson.ts +45 -0
  77. package/src/main/services/extensions/skills/SkillImportService.ts +155 -0
  78. package/src/main/services/extensions/skills/SkillMetadataParser.ts +323 -0
  79. package/src/main/services/extensions/skills/SkillPlanService.ts +411 -0
  80. package/src/main/services/extensions/skills/SkillReviewService.ts +73 -0
  81. package/src/main/services/extensions/skills/SkillRootsResolver.ts +49 -0
  82. package/src/main/services/extensions/skills/SkillScaffoldService.ts +89 -0
  83. package/src/main/services/extensions/skills/SkillScanner.ts +117 -0
  84. package/src/main/services/extensions/skills/SkillValidator.ts +69 -0
  85. package/src/main/services/extensions/skills/SkillsCatalogService.ts +92 -0
  86. package/src/main/services/extensions/skills/SkillsMutationService.ts +146 -0
  87. package/src/main/services/extensions/skills/SkillsWatcherService.ts +134 -0
  88. package/src/main/services/extensions/state/McpInstallationStateService.ts +42 -0
  89. package/src/main/services/extensions/state/PluginInstallationStateService.ts +281 -0
  90. package/src/main/services/identity/AgentTeamsIdentityStore.ts +218 -0
  91. package/src/main/services/runtime/providerAwareCliEnv.ts +60 -0
  92. package/src/main/services/team/ClaudeBinaryResolver.ts +469 -0
  93. package/src/main/services/team/ClaudeDoctorProbe.ts +0 -0
  94. package/src/main/services/team/cliFlavor.ts +54 -0
  95. package/src/main/services/teams-mvp/TaskDispatchService.ts +3 -0
  96. package/src/main/utils/atomicWrite.ts +72 -0
  97. package/src/main/utils/childProcess.ts +554 -0
  98. package/src/main/utils/cliEnv.ts +54 -0
  99. package/src/main/utils/cliPathMerge.ts +97 -0
  100. package/src/main/utils/pathDecoder.ts +664 -0
  101. package/src/main/utils/pathValidation.ts +432 -0
  102. package/src/main/utils/shellEnv.ts +331 -0
  103. package/src/renderer/api/httpClient.ts +61 -0
  104. package/src/renderer/components/extensions/ExtensionStoreView.tsx +63 -35
  105. package/src/renderer/components/extensions/ExtensionsSubTabTrigger.tsx +1 -1
  106. package/src/renderer/components/extensions/common/ExtensionToast.tsx +141 -0
  107. package/src/renderer/components/extensions/common/HarnessSelector.tsx +71 -0
  108. package/src/renderer/components/extensions/env/EnvVarPanel.tsx +335 -0
  109. package/src/renderer/components/extensions/env/ProjectEnvPanel.tsx +239 -0
  110. package/src/renderer/components/extensions/mcp/CustomMcpServerDialog.tsx +14 -223
  111. package/src/renderer/components/extensions/mcp/McpServerDetailDialog.tsx +111 -15
  112. package/src/renderer/components/extensions/mcp/McpServersPanel.tsx +51 -1
  113. package/src/renderer/components/extensions/skills/SkillsPanel.tsx +1 -126
  114. package/src/renderer/components/settings/sections/HarnessSection.tsx +2 -6
  115. package/src/renderer/components/settings/sections/TaskBusSection.tsx +17 -7
  116. package/src/renderer/components/sidebar/SidebarSessions.tsx +23 -0
  117. package/src/renderer/components/sidebar/WorkspaceBrowser.tsx +1 -7
  118. package/src/renderer/components/team/HarnessSelect.tsx +71 -0
  119. package/src/renderer/components/team/TeamDetailView.tsx +74 -123
  120. package/src/renderer/components/team/TeamListFilterPopover.tsx +0 -16
  121. package/src/renderer/components/team/TeamListView.tsx +7 -32
  122. package/src/renderer/components/team/dialogs/CreateTeamDialog.tsx +21 -12
  123. package/src/renderer/components/team/dialogs/EditTeamDialog.tsx +287 -418
  124. package/src/renderer/components/team/dialogs/useTeamEditForm.ts +283 -0
  125. package/src/renderer/components/team/kanban/KanbanBoard.tsx +26 -64
  126. package/src/renderer/components/team/messages/MessagesPanel.tsx +28 -24
  127. package/src/renderer/components/terminal/TerminalPanel.tsx +156 -0
  128. package/src/renderer/hooks/useExtensionsTabState.ts +2 -2
  129. package/src/renderer/store/slices/extensionsSlice.ts +42 -107
  130. package/src/renderer/store/slices/teamSlice.ts +8 -2
  131. package/src/renderer/utils/multimodelProviderVisibility.ts +17 -0
  132. package/src/renderer/utils/openCodeRuntimeDeliveryDiagnostics.ts +29 -9
  133. package/src/shared/types/api.ts +29 -0
  134. package/src/shared/types/extensions/index.ts +1 -0
  135. package/src/shared/types/extensions/mcp.ts +2 -0
  136. package/src/shared/types/extensions/plugin.ts +2 -1
  137. package/src/shared/types/extensions/skill.ts +7 -0
  138. package/src/shared/utils/providerExtensionCapabilities.ts +1 -1
  139. package/dist-renderer/assets/channel-C0SqeFU7.js +0 -1
  140. package/dist-renderer/assets/classDiagram-2ON5EDUG-DWew1HpM.js +0 -1
  141. package/dist-renderer/assets/classDiagram-v2-WZHVMYZB-DWew1HpM.js +0 -1
  142. package/dist-renderer/assets/clone-Dm-k63Yr.js +0 -1
  143. package/dist-renderer/assets/index-BhellmRb.css +0 -1
  144. package/dist-renderer/assets/treemap-GDKQZRPO-DU_yr827.js +0 -162
  145. package/src/features/recent-projects/main/adapters/input/http/registerRecentProjectsHttp.ts +0 -30
  146. package/src/features/recent-projects/main/adapters/output/presenters/DashboardRecentProjectsPresenter.ts +0 -27
  147. package/src/features/recent-projects/main/adapters/output/sources/ClaudeRecentProjectsSourceAdapter.ts +0 -91
  148. package/src/features/recent-projects/main/adapters/output/sources/CodexRecentProjectsSourceAdapter.ts +0 -326
  149. package/src/features/recent-projects/main/composition/createRecentProjectsFeature.ts +0 -43
  150. package/src/features/recent-projects/main/index.ts +0 -3
  151. package/src/features/recent-projects/main/infrastructure/cache/InMemoryRecentProjectsCache.ts +0 -34
  152. package/src/features/recent-projects/main/infrastructure/codex/CodexAppServerClient.ts +0 -116
  153. package/src/features/recent-projects/main/infrastructure/identity/RecentProjectIdentityResolver.ts +0 -20
  154. package/src/features/recent-projects/main/infrastructure/identity/normalizeIdentityPath.ts +0 -10
  155. package/src/renderer/components/extensions/apikeys/ApiKeyCard.tsx +0 -143
  156. package/src/renderer/components/extensions/apikeys/ApiKeyFormDialog.tsx +0 -282
  157. 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
+ }