@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,199 @@
1
+ /**
2
+ * Claude Code harness adapter — uses `claude` CLI for plugin/MCP/skills.
3
+ */
4
+
5
+ import { ClaudeBinaryResolver } from '@main/services/team/ClaudeBinaryResolver';
6
+ import { getHomeDir } from '@main/utils/pathDecoder';
7
+ import { execCli } from '@main/utils/childProcess';
8
+ import { CLI_NOT_FOUND_MESSAGE } from '@shared/constants/cli';
9
+ import type { CcAgentType } from '@shared/types/ccConnect';
10
+ import { createLogger } from '@shared/utils/logger';
11
+ import path from 'node:path';
12
+
13
+ import { McpConfigStateReader } from './McpConfigStateReader';
14
+
15
+ import type {
16
+ HarnessInstallAdapter,
17
+ InstallOpts,
18
+ ResolvedSkillRoot,
19
+ } from './HarnessInstallAdapter';
20
+ import type {
21
+ InstalledMcpEntry,
22
+ InstalledPluginEntry,
23
+ McpHeaderDef,
24
+ McpInstallSpec,
25
+ OperationResult,
26
+ } from '@shared/types/extensions';
27
+
28
+ const logger = createLogger('Extensions:CCAdapter');
29
+
30
+ const QUALIFIED_NAME_RE = /^[\w.-]+@[\w.-]+$/;
31
+ const VALID_SCOPES = new Set(['local', 'user', 'project', 'global']);
32
+ const SERVER_NAME_RE = /^[\w.-]{1,100}$/;
33
+ const ENV_KEY_RE = /^[A-Z_][A-Z0-9_]{0,100}$/i;
34
+ const HEADER_KEY_RE = /^[A-Za-z][\w-]{0,100}$/;
35
+ const PLUGIN_TIMEOUT_MS = 120_000;
36
+ const MCP_TIMEOUT_MS = 30_000;
37
+
38
+ function scopeRequiresProjectPath(scope?: string): boolean {
39
+ return scope === 'project' || scope === 'local';
40
+ }
41
+
42
+ function maskSecrets(
43
+ message: string,
44
+ envValues: Record<string, string>,
45
+ headerValues: string[]
46
+ ): string {
47
+ let result = message;
48
+ const secrets = [
49
+ ...Object.values(envValues).filter((v) => v.length > 3),
50
+ ...headerValues.filter((v) => v.length > 3),
51
+ ];
52
+ for (const secret of secrets) {
53
+ result = result.replaceAll(secret, '[REDACTED]');
54
+ }
55
+ return result;
56
+ }
57
+
58
+ export class ClaudeCodeAdapter implements HarnessInstallAdapter {
59
+ readonly harnessType: CcAgentType = 'claudecode';
60
+ readonly supportsPlugins = true;
61
+ readonly supportsMcp = true;
62
+ readonly supportsSkills = true;
63
+
64
+ private readonly configReader = new McpConfigStateReader();
65
+
66
+ async resolveBinary(): Promise<string | null> {
67
+ return ClaudeBinaryResolver.resolve();
68
+ }
69
+
70
+ async installPlugin(qualifiedName: string, opts: InstallOpts): Promise<OperationResult> {
71
+ if (!QUALIFIED_NAME_RE.test(qualifiedName)) {
72
+ return { state: 'error', error: `Invalid plugin identifier: ${qualifiedName}` };
73
+ }
74
+ if (opts.scope && !VALID_SCOPES.has(opts.scope)) {
75
+ return { state: 'error', error: `Invalid scope: "${opts.scope}"` };
76
+ }
77
+ if (scopeRequiresProjectPath(opts.scope) && !opts.projectPath) {
78
+ return { state: 'error', error: 'projectPath is required for project-scoped installs' };
79
+ }
80
+
81
+ const args = ['plugin', 'install'];
82
+ if (opts.scope && opts.scope !== 'user') args.push('-s', opts.scope);
83
+ args.push(qualifiedName);
84
+
85
+ return this.runCli(args, { timeout: PLUGIN_TIMEOUT_MS, cwd: opts.projectPath });
86
+ }
87
+
88
+ async uninstallPlugin(qualifiedName: string, opts: InstallOpts): Promise<OperationResult> {
89
+ if (!QUALIFIED_NAME_RE.test(qualifiedName)) {
90
+ return { state: 'error', error: `Invalid plugin identifier: ${qualifiedName}` };
91
+ }
92
+
93
+ const args = ['plugin', 'uninstall'];
94
+ if (opts.scope && opts.scope !== 'user') args.push('-s', opts.scope);
95
+ args.push(qualifiedName);
96
+
97
+ return this.runCli(args, { timeout: 30_000, cwd: opts.projectPath });
98
+ }
99
+
100
+ async installMcp(
101
+ name: string,
102
+ spec: McpInstallSpec,
103
+ envValues: Record<string, string>,
104
+ headers: McpHeaderDef[],
105
+ opts: InstallOpts
106
+ ): Promise<OperationResult> {
107
+ if (!SERVER_NAME_RE.test(name)) {
108
+ return { state: 'error', error: `Invalid server name: "${name}"` };
109
+ }
110
+
111
+ const args: string[] = ['mcp', 'add'];
112
+ if (opts.scope && opts.scope !== 'local') args.push('-s', opts.scope);
113
+
114
+ if (spec.type === 'stdio') {
115
+ for (const [key, value] of Object.entries(envValues)) {
116
+ if (key && value && ENV_KEY_RE.test(key)) args.push('-e', `${key}=${value}`);
117
+ }
118
+ args.push(name, '--', 'npx', '-y');
119
+ args.push(spec.npmVersion ? `${spec.npmPackage}@${spec.npmVersion}` : spec.npmPackage);
120
+ } else if (spec.type === 'http') {
121
+ args.push('-t', spec.transportType === 'sse' ? 'sse' : 'http');
122
+ // Positional args must come before variadic flags (-H, -e)
123
+ // to prevent the CLI parser from consuming them as flag values
124
+ args.push(name, spec.url);
125
+ for (const header of headers) {
126
+ if (header.key && header.value && HEADER_KEY_RE.test(header.key))
127
+ args.push('-H', `${header.key}: ${header.value}`);
128
+ }
129
+ for (const [key, value] of Object.entries(envValues)) {
130
+ if (key && value && ENV_KEY_RE.test(key)) args.push('-e', `${key}=${value}`);
131
+ }
132
+ } else {
133
+ return { state: 'error', error: `Unsupported install spec type` };
134
+ }
135
+
136
+ try {
137
+ return await this.runCli(args, { timeout: MCP_TIMEOUT_MS, cwd: opts.projectPath });
138
+ } catch (err) {
139
+ const message = err instanceof Error ? err.message : String(err);
140
+ const safe = maskSecrets(
141
+ message,
142
+ envValues,
143
+ headers.map((h) => h.value)
144
+ );
145
+ return { state: 'error', error: safe };
146
+ }
147
+ }
148
+
149
+ async uninstallMcp(name: string, opts: InstallOpts): Promise<OperationResult> {
150
+ const args = ['mcp', 'remove'];
151
+ if (opts.scope && opts.scope !== 'local') args.push('-s', opts.scope);
152
+ args.push(name);
153
+ return this.runCli(args, { timeout: MCP_TIMEOUT_MS, cwd: opts.projectPath });
154
+ }
155
+
156
+ async listInstalledMcp(projectPath?: string): Promise<InstalledMcpEntry[]> {
157
+ return this.configReader.readInstalled(projectPath);
158
+ }
159
+
160
+ async listInstalledPlugins(_projectPath?: string): Promise<InstalledPluginEntry[]> {
161
+ // Plugin listing would read from ~/.claude/plugins/installed_plugins.json
162
+ // Stub for now — will be implemented in Phase 3
163
+ return [];
164
+ }
165
+
166
+ getSkillRoots(projectPath?: string): ResolvedSkillRoot[] {
167
+ const home = getHomeDir();
168
+ const roots: ResolvedSkillRoot[] = [
169
+ { kind: 'claude', scope: 'user', path: path.join(home, '.claude', 'commands') },
170
+ ];
171
+ if (projectPath) {
172
+ roots.push({
173
+ kind: 'claude',
174
+ scope: 'project',
175
+ path: path.join(projectPath, '.claude', 'commands'),
176
+ });
177
+ }
178
+ return roots;
179
+ }
180
+
181
+ private async runCli(
182
+ args: string[],
183
+ opts: { timeout: number; cwd?: string }
184
+ ): Promise<OperationResult> {
185
+ const binary = await this.resolveBinary();
186
+ if (!binary) {
187
+ return { state: 'error', error: CLI_NOT_FOUND_MESSAGE };
188
+ }
189
+
190
+ try {
191
+ await execCli(binary, args, { timeout: opts.timeout, cwd: opts.cwd });
192
+ return { state: 'success' };
193
+ } catch (err) {
194
+ const message = err instanceof Error ? err.message : String(err);
195
+ logger.error(`CLI command failed: ${message}`);
196
+ return { state: 'error', error: message };
197
+ }
198
+ }
199
+ }
@@ -0,0 +1,100 @@
1
+ /**
2
+ * Codex harness adapter — uses `codex` CLI for MCP and skills.
3
+ * Shares CLI pattern with ClaudeCodeAdapter but uses `codex` binary.
4
+ */
5
+
6
+ import { getHomeDir } from '@main/utils/pathDecoder';
7
+ import type { CcAgentType } from '@shared/types/ccConnect';
8
+ import path from 'node:path';
9
+
10
+ import { ClaudeCodeAdapter } from './ClaudeCodeAdapter';
11
+
12
+ import type {
13
+ HarnessInstallAdapter,
14
+ InstallOpts,
15
+ ResolvedSkillRoot,
16
+ } from './HarnessInstallAdapter';
17
+ import type {
18
+ InstalledMcpEntry,
19
+ InstalledPluginEntry,
20
+ McpHeaderDef,
21
+ McpInstallSpec,
22
+ OperationResult,
23
+ } from '@shared/types/extensions';
24
+
25
+ function resolveBinaryFromPath(binaryName: string): Promise<string | null> {
26
+ return new Promise((resolve) => {
27
+ import('node:child_process')
28
+ .then(({ execFile }) => {
29
+ execFile('which', [binaryName], { timeout: 5_000 }, (err, stdout) => {
30
+ resolve(err || !stdout?.trim() ? null : stdout.trim());
31
+ });
32
+ })
33
+ .catch(() => resolve(null));
34
+ });
35
+ }
36
+
37
+ /**
38
+ * Codex adapter — delegates to a ClaudeCodeAdapter instance but overrides
39
+ * binary resolution and skill roots. CLI args pattern is the same.
40
+ */
41
+ export class CodexAdapter implements HarnessInstallAdapter {
42
+ readonly harnessType: CcAgentType = 'codex';
43
+ readonly supportsPlugins = false;
44
+ readonly supportsMcp = true;
45
+ readonly supportsSkills = true;
46
+
47
+ private readonly delegate = new ClaudeCodeAdapter();
48
+
49
+ async resolveBinary(): Promise<string | null> {
50
+ const codex = await resolveBinaryFromPath('codex');
51
+ if (codex) return codex;
52
+ // Fallback: try claude binary (codex may be aliased)
53
+ return this.delegate.resolveBinary();
54
+ }
55
+
56
+ async installPlugin(): Promise<OperationResult> {
57
+ return { state: 'error', error: 'Codex does not support Claude plugins' };
58
+ }
59
+
60
+ async uninstallPlugin(): Promise<OperationResult> {
61
+ return { state: 'error', error: 'Codex does not support Claude plugins' };
62
+ }
63
+
64
+ async installMcp(
65
+ name: string,
66
+ spec: McpInstallSpec,
67
+ envValues: Record<string, string>,
68
+ headers: McpHeaderDef[],
69
+ opts: InstallOpts
70
+ ): Promise<OperationResult> {
71
+ return this.delegate.installMcp(name, spec, envValues, headers, opts);
72
+ }
73
+
74
+ async uninstallMcp(name: string, opts: InstallOpts): Promise<OperationResult> {
75
+ return this.delegate.uninstallMcp(name, opts);
76
+ }
77
+
78
+ async listInstalledMcp(projectPath?: string): Promise<InstalledMcpEntry[]> {
79
+ return this.delegate.listInstalledMcp(projectPath);
80
+ }
81
+
82
+ async listInstalledPlugins(): Promise<InstalledPluginEntry[]> {
83
+ return [];
84
+ }
85
+
86
+ getSkillRoots(projectPath?: string): ResolvedSkillRoot[] {
87
+ const home = getHomeDir();
88
+ const roots: ResolvedSkillRoot[] = [
89
+ { kind: 'codex', scope: 'user', path: path.join(home, '.codex', 'skills') },
90
+ ];
91
+ if (projectPath) {
92
+ roots.push({
93
+ kind: 'codex',
94
+ scope: 'project',
95
+ path: path.join(projectPath, '.codex', 'skills'),
96
+ });
97
+ }
98
+ return roots;
99
+ }
100
+ }
@@ -0,0 +1,154 @@
1
+ /**
2
+ * Cursor harness adapter — writes config files directly (no CLI for MCP).
3
+ */
4
+
5
+ import * as fs from 'node:fs/promises';
6
+ import { getHomeDir } from '@main/utils/pathDecoder';
7
+ import { createLogger } from '@shared/utils/logger';
8
+ import path from 'node:path';
9
+
10
+ import type {
11
+ HarnessInstallAdapter,
12
+ InstallOpts,
13
+ ResolvedSkillRoot,
14
+ } from './HarnessInstallAdapter';
15
+ import type {
16
+ InstalledMcpEntry,
17
+ InstalledPluginEntry,
18
+ McpHeaderDef,
19
+ McpInstallSpec,
20
+ OperationResult,
21
+ } from '@shared/types/extensions';
22
+
23
+ const logger = createLogger('Extensions:CursorAdapter');
24
+
25
+ export class CursorAdapter implements HarnessInstallAdapter {
26
+ readonly harnessType = 'cursor' as const;
27
+ readonly supportsPlugins = false;
28
+ readonly supportsMcp = true;
29
+ readonly supportsSkills = true;
30
+
31
+ async resolveBinary(): Promise<string | null> {
32
+ // Cursor doesn't have a CLI for MCP management
33
+ return null;
34
+ }
35
+
36
+ async installMcp(
37
+ name: string,
38
+ spec: McpInstallSpec,
39
+ envValues: Record<string, string>,
40
+ _headers: McpHeaderDef[],
41
+ opts: InstallOpts
42
+ ): Promise<OperationResult> {
43
+ if (!opts.projectPath) {
44
+ return { state: 'error', error: 'Cursor MCP requires a project path (.cursor/mcp.json)' };
45
+ }
46
+
47
+ const mcpPath = path.join(opts.projectPath, '.cursor', 'mcp.json');
48
+ let config: Record<string, unknown> = {};
49
+
50
+ try {
51
+ const raw = await fs.readFile(mcpPath, 'utf-8');
52
+ config = JSON.parse(raw) as Record<string, unknown>;
53
+ } catch {
54
+ // file doesn't exist yet
55
+ }
56
+
57
+ const mcpServers = (config.mcpServers as Record<string, unknown>) ?? {};
58
+
59
+ if (spec.type === 'stdio') {
60
+ mcpServers[name] = {
61
+ command: 'npx',
62
+ args: ['-y', spec.npmVersion ? `${spec.npmPackage}@${spec.npmVersion}` : spec.npmPackage],
63
+ env: envValues,
64
+ };
65
+ } else if (spec.type === 'http') {
66
+ mcpServers[name] = {
67
+ url: spec.url,
68
+ ...(spec.transportType === 'sse' && { type: 'sse' }),
69
+ };
70
+ }
71
+
72
+ config.mcpServers = mcpServers;
73
+
74
+ try {
75
+ await fs.mkdir(path.dirname(mcpPath), { recursive: true });
76
+ await fs.writeFile(mcpPath, JSON.stringify(config, null, 2));
77
+ return { state: 'success' };
78
+ } catch (err) {
79
+ const message = err instanceof Error ? err.message : String(err);
80
+ return { state: 'error', error: `Failed to write .cursor/mcp.json: ${message}` };
81
+ }
82
+ }
83
+
84
+ async uninstallMcp(name: string, opts: InstallOpts): Promise<OperationResult> {
85
+ if (!opts.projectPath) {
86
+ return { state: 'error', error: 'Cursor MCP requires a project path' };
87
+ }
88
+
89
+ const mcpPath = path.join(opts.projectPath, '.cursor', 'mcp.json');
90
+
91
+ try {
92
+ const raw = await fs.readFile(mcpPath, 'utf-8');
93
+ const config = JSON.parse(raw) as Record<string, unknown>;
94
+ const mcpServers = config.mcpServers as Record<string, unknown> | undefined;
95
+ if (mcpServers && name in mcpServers) {
96
+ delete mcpServers[name];
97
+ await fs.writeFile(mcpPath, JSON.stringify(config, null, 2));
98
+ }
99
+ return { state: 'success' };
100
+ } catch (err) {
101
+ const message = err instanceof Error ? err.message : String(err);
102
+ return { state: 'error', error: `Failed to update .cursor/mcp.json: ${message}` };
103
+ }
104
+ }
105
+
106
+ async installPlugin(): Promise<OperationResult> {
107
+ return { state: 'error', error: 'Cursor does not support Claude plugins' };
108
+ }
109
+
110
+ async uninstallPlugin(): Promise<OperationResult> {
111
+ return { state: 'error', error: 'Cursor does not support Claude plugins' };
112
+ }
113
+
114
+ async listInstalledMcp(projectPath?: string): Promise<InstalledMcpEntry[]> {
115
+ if (!projectPath) return [];
116
+ const mcpPath = path.join(projectPath, '.cursor', 'mcp.json');
117
+
118
+ try {
119
+ const raw = await fs.readFile(mcpPath, 'utf-8');
120
+ const config = JSON.parse(raw) as Record<string, unknown>;
121
+ const mcpServers = config.mcpServers as
122
+ | Record<string, { command?: string; url?: string }>
123
+ | undefined;
124
+ if (!mcpServers) return [];
125
+
126
+ return Object.entries(mcpServers).map(([name, server]) => ({
127
+ name,
128
+ scope: 'project' as const,
129
+ transport: server.command ? 'stdio' : server.url ? 'http' : undefined,
130
+ }));
131
+ } catch {
132
+ return [];
133
+ }
134
+ }
135
+
136
+ async listInstalledPlugins(): Promise<InstalledPluginEntry[]> {
137
+ return [];
138
+ }
139
+
140
+ getSkillRoots(projectPath?: string): ResolvedSkillRoot[] {
141
+ const home = getHomeDir();
142
+ const roots: ResolvedSkillRoot[] = [
143
+ { kind: 'cursor', scope: 'user', path: path.join(home, '.cursor', 'skills') },
144
+ ];
145
+ if (projectPath) {
146
+ roots.push({
147
+ kind: 'cursor',
148
+ scope: 'project',
149
+ path: path.join(projectPath, '.cursor', 'skills'),
150
+ });
151
+ }
152
+ return roots;
153
+ }
154
+ }
@@ -0,0 +1,172 @@
1
+ import { buildProviderAwareCliEnv } from '@main/services/runtime/providerAwareCliEnv';
2
+ import { ClaudeBinaryResolver } from '@main/services/team/ClaudeBinaryResolver';
3
+ import { getConfiguredCliFlavor } from '@main/services/team/cliFlavor';
4
+ import { execCli } from '@main/utils/childProcess';
5
+ import { CLI_NOT_FOUND_MESSAGE } from '@shared/constants/cli';
6
+
7
+ import { McpConfigStateReader } from './McpConfigStateReader';
8
+ import { parseMcpDiagnosticsJsonOutput, parseMcpDiagnosticsOutput } from './mcpDiagnosticsParser';
9
+ import { parseInstalledMcpJsonOutput } from './mcpRuntimeJson';
10
+
11
+ import type { CliFlavor } from '@shared/types';
12
+ import type { InstalledMcpEntry, McpServerDiagnostic } from '@shared/types/extensions';
13
+
14
+ const MCP_LIST_TIMEOUT_MS = 15_000;
15
+ const MCP_DIAGNOSE_TIMEOUT_MS = 60_000;
16
+
17
+ async function buildManagementCliEnvForBinary(binaryPath: string): Promise<NodeJS.ProcessEnv> {
18
+ const { env } = await buildProviderAwareCliEnv({
19
+ binaryPath,
20
+ connectionMode: 'augment',
21
+ allowStoredApiKeyDecryption: false,
22
+ });
23
+ return env;
24
+ }
25
+ export interface ExtensionsRuntimeAdapter {
26
+ readonly flavor: CliFlavor;
27
+ buildManagementCliEnv(binaryPath: string): Promise<NodeJS.ProcessEnv>;
28
+ getInstalledMcp(projectPath?: string): Promise<InstalledMcpEntry[]>;
29
+ diagnoseMcp(projectPath?: string): Promise<McpServerDiagnostic[]>;
30
+ }
31
+
32
+ export class ClaudeExtensionsAdapter implements ExtensionsRuntimeAdapter {
33
+ readonly flavor = 'claude' as const;
34
+
35
+ constructor(private readonly stateReader = new McpConfigStateReader()) {}
36
+
37
+ async buildManagementCliEnv(binaryPath: string): Promise<NodeJS.ProcessEnv> {
38
+ return buildManagementCliEnvForBinary(binaryPath);
39
+ }
40
+
41
+ async getInstalledMcp(projectPath?: string): Promise<InstalledMcpEntry[]> {
42
+ return this.stateReader.readInstalled(projectPath);
43
+ }
44
+
45
+ async diagnoseMcp(projectPath?: string): Promise<McpServerDiagnostic[]> {
46
+ const binaryPath = await ClaudeBinaryResolver.resolve();
47
+ if (!binaryPath) {
48
+ throw new Error(CLI_NOT_FOUND_MESSAGE);
49
+ }
50
+
51
+ const env = await this.buildManagementCliEnv(binaryPath);
52
+ const { stdout, stderr } = await execCli(binaryPath, ['mcp', 'list'], {
53
+ timeout: MCP_DIAGNOSE_TIMEOUT_MS,
54
+ cwd: projectPath,
55
+ env,
56
+ });
57
+
58
+ return parseMcpDiagnosticsOutput([stdout, stderr].filter(Boolean).join('\n'));
59
+ }
60
+ }
61
+
62
+ export class MultimodelExtensionsAdapter implements ExtensionsRuntimeAdapter {
63
+ readonly flavor = 'agent_teams_orchestrator' as const;
64
+
65
+ constructor(private readonly stateReader = new McpConfigStateReader()) {}
66
+
67
+ async buildManagementCliEnv(binaryPath: string): Promise<NodeJS.ProcessEnv> {
68
+ return buildManagementCliEnvForBinary(binaryPath);
69
+ }
70
+
71
+ async getInstalledMcp(projectPath?: string): Promise<InstalledMcpEntry[]> {
72
+ const binaryPath = await ClaudeBinaryResolver.resolve();
73
+ if (!binaryPath) {
74
+ return this.stateReader.readInstalled(projectPath);
75
+ }
76
+
77
+ const env = await this.buildManagementCliEnv(binaryPath);
78
+ try {
79
+ const { stdout } = await execCli(binaryPath, ['mcp', 'list', '--json'], {
80
+ timeout: MCP_LIST_TIMEOUT_MS,
81
+ cwd: projectPath,
82
+ env,
83
+ });
84
+
85
+ return parseInstalledMcpJsonOutput(stdout);
86
+ } catch (error) {
87
+ if (!isUnsupportedMcpJsonContractError(error)) {
88
+ throw error;
89
+ }
90
+
91
+ return this.stateReader.readInstalled(projectPath);
92
+ }
93
+ }
94
+
95
+ async diagnoseMcp(projectPath?: string): Promise<McpServerDiagnostic[]> {
96
+ const binaryPath = await ClaudeBinaryResolver.resolve();
97
+ if (!binaryPath) {
98
+ return [];
99
+ }
100
+
101
+ const env = await this.buildManagementCliEnv(binaryPath);
102
+ try {
103
+ const { stdout } = await execCli(binaryPath, ['mcp', 'diagnose', '--json'], {
104
+ timeout: MCP_DIAGNOSE_TIMEOUT_MS,
105
+ cwd: projectPath,
106
+ env,
107
+ });
108
+
109
+ return parseMcpDiagnosticsJsonOutput(stdout);
110
+ } catch (error) {
111
+ if (!isUnsupportedMcpJsonContractError(error)) {
112
+ throw error;
113
+ }
114
+
115
+ const { stdout, stderr } = await execCli(binaryPath, ['mcp', 'list'], {
116
+ timeout: MCP_DIAGNOSE_TIMEOUT_MS,
117
+ cwd: projectPath,
118
+ env,
119
+ });
120
+
121
+ return parseMcpDiagnosticsOutput([stdout, stderr].filter(Boolean).join('\n'));
122
+ }
123
+ }
124
+ }
125
+
126
+ function isUnsupportedMcpJsonContractError(error: unknown): boolean {
127
+ const message = error instanceof Error ? error.message : String(error);
128
+ const normalized = message.toLowerCase();
129
+
130
+ return (
131
+ normalized.includes("unknown command 'diagnose'") ||
132
+ normalized.includes('unknown command "diagnose"') ||
133
+ normalized.includes('unknown option') ||
134
+ normalized.includes('unknown argument') ||
135
+ normalized.includes('unexpected argument') ||
136
+ normalized.includes('unrecognized option')
137
+ );
138
+ }
139
+
140
+ class RuntimeSwitchingExtensionsAdapter implements ExtensionsRuntimeAdapter {
141
+ constructor(
142
+ private readonly claudeAdapter: ClaudeExtensionsAdapter,
143
+ private readonly multimodelAdapter: MultimodelExtensionsAdapter
144
+ ) {}
145
+
146
+ private getActiveAdapter(): ExtensionsRuntimeAdapter {
147
+ return getConfiguredCliFlavor() === 'claude' ? this.claudeAdapter : this.multimodelAdapter;
148
+ }
149
+
150
+ get flavor(): CliFlavor {
151
+ return this.getActiveAdapter().flavor;
152
+ }
153
+
154
+ buildManagementCliEnv(binaryPath: string): Promise<NodeJS.ProcessEnv> {
155
+ return this.getActiveAdapter().buildManagementCliEnv(binaryPath);
156
+ }
157
+
158
+ getInstalledMcp(projectPath?: string): Promise<InstalledMcpEntry[]> {
159
+ return this.getActiveAdapter().getInstalledMcp(projectPath);
160
+ }
161
+
162
+ diagnoseMcp(projectPath?: string): Promise<McpServerDiagnostic[]> {
163
+ return this.getActiveAdapter().diagnoseMcp(projectPath);
164
+ }
165
+ }
166
+
167
+ export function createExtensionsRuntimeAdapter(): ExtensionsRuntimeAdapter {
168
+ return new RuntimeSwitchingExtensionsAdapter(
169
+ new ClaudeExtensionsAdapter(),
170
+ new MultimodelExtensionsAdapter()
171
+ );
172
+ }