@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.
Files changed (177) hide show
  1. package/dist-renderer/assets/ProjectEditorOverlay-DsQt4FHy.js +52 -0
  2. package/dist-renderer/assets/{TeamGraphOverlay-Ba5njic5.js → TeamGraphOverlay-BjZC53xf.js} +1 -1
  3. package/dist-renderer/assets/{_basePickBy-BvnK-OC1.js → _basePickBy-CrWocIjq.js} +1 -1
  4. package/dist-renderer/assets/{_baseUniq-DmFYXx9G.js → _baseUniq-B6d8ysWi.js} +1 -1
  5. package/dist-renderer/assets/{arc-DX4ZQFY4.js → arc-DAIYCFP8.js} +1 -1
  6. package/dist-renderer/assets/{architectureDiagram-VXUJARFQ-DfYr3vEN.js → architectureDiagram-VXUJARFQ-B3UudXJh.js} +1 -1
  7. package/dist-renderer/assets/{blockDiagram-VD42YOAC-DuXdVeWn.js → blockDiagram-VD42YOAC-DbptKQ4W.js} +1 -1
  8. package/dist-renderer/assets/{c4Diagram-YG6GDRKO-Bw2nixXe.js → c4Diagram-YG6GDRKO-C4WQuZpV.js} +1 -1
  9. package/dist-renderer/assets/channel-DbjZvWii.js +1 -0
  10. package/dist-renderer/assets/{chunk-4BX2VUAB-DLiNGQoE.js → chunk-4BX2VUAB-Dp7fVpI_.js} +1 -1
  11. package/dist-renderer/assets/{chunk-55IACEB6-B1L_8VIF.js → chunk-55IACEB6-B8KGfbAy.js} +1 -1
  12. package/dist-renderer/assets/{chunk-B4BG7PRW-DaZMWKGk.js → chunk-B4BG7PRW-BG1oJrjA.js} +1 -1
  13. package/dist-renderer/assets/{chunk-DI55MBZ5-ku-dflJG.js → chunk-DI55MBZ5-DRmxNjht.js} +1 -1
  14. package/dist-renderer/assets/{chunk-FMBD7UC4-DV-mF1dP.js → chunk-FMBD7UC4-D6VLvy16.js} +1 -1
  15. package/dist-renderer/assets/{chunk-QN33PNHL-ByGcDFQ0.js → chunk-QN33PNHL-DZou1667.js} +1 -1
  16. package/dist-renderer/assets/{chunk-QZHKN3VN-7dv-Min8.js → chunk-QZHKN3VN-CghmasSh.js} +1 -1
  17. package/dist-renderer/assets/{chunk-TZMSLE5B-WdXL5fTu.js → chunk-TZMSLE5B-B7apcMPK.js} +1 -1
  18. package/dist-renderer/assets/classDiagram-2ON5EDUG-D_FGxxsl.js +1 -0
  19. package/dist-renderer/assets/classDiagram-v2-WZHVMYZB-D_FGxxsl.js +1 -0
  20. package/dist-renderer/assets/clone-CJ1kxO2J.js +1 -0
  21. package/dist-renderer/assets/{cose-bilkent-S5V4N54A-CNcsvqPl.js → cose-bilkent-S5V4N54A-05e5uQDp.js} +1 -1
  22. package/dist-renderer/assets/{dagre-6UL2VRFP-DBNx4qqx.js → dagre-6UL2VRFP-B06bRykF.js} +1 -1
  23. package/dist-renderer/assets/{diagram-PSM6KHXK-BfVlT6sT.js → diagram-PSM6KHXK-CY7VYQ7c.js} +1 -1
  24. package/dist-renderer/assets/{diagram-QEK2KX5R-HvVjs0K6.js → diagram-QEK2KX5R-BjKEH7dD.js} +1 -1
  25. package/dist-renderer/assets/{diagram-S2PKOQOG-DYb_KnWS.js → diagram-S2PKOQOG-Bf4ELS1_.js} +1 -1
  26. package/dist-renderer/assets/{erDiagram-Q2GNP2WA-Ba-IgI5G.js → erDiagram-Q2GNP2WA-DJ753_L9.js} +1 -1
  27. package/dist-renderer/assets/{flowDiagram-NV44I4VS-2iDN8Kpj.js → flowDiagram-NV44I4VS-B71S-lC-.js} +1 -1
  28. package/dist-renderer/assets/{ganttDiagram-JELNMOA3-Byjf8Fa3.js → ganttDiagram-JELNMOA3-C_U42mSZ.js} +1 -1
  29. package/dist-renderer/assets/{gitGraphDiagram-V2S2FVAM-DbKvfZ_j.js → gitGraphDiagram-V2S2FVAM-DKUJU4Ns.js} +1 -1
  30. package/dist-renderer/assets/{graph-Enirf-f8.js → graph-DY3qbzqj.js} +1 -1
  31. package/dist-renderer/assets/{index-DY1zqsb6.js → index-BlOrAXp3.js} +551 -537
  32. package/dist-renderer/assets/{index-AjxP_rE_.js → index-Bs27J5gB.js} +1 -1
  33. package/dist-renderer/assets/{index-CtlzGepK.js → index-C8B_nKOF.js} +1 -1
  34. package/dist-renderer/assets/index-CmZPUEhS.css +1 -0
  35. package/dist-renderer/assets/{index-COZPUWJW.js → index-DLKyDr4T.js} +1 -1
  36. package/dist-renderer/assets/{index-DdhqolqE.js → index-Dhsk3_DD.js} +1 -1
  37. package/dist-renderer/assets/{index-ChR1D6ZF.js → index-GpUvV2xs.js} +1 -1
  38. package/dist-renderer/assets/{infoDiagram-HS3SLOUP-D6uicwz1.js → infoDiagram-HS3SLOUP-BNs0y3IG.js} +1 -1
  39. package/dist-renderer/assets/{journeyDiagram-XKPGCS4Q-DqwZsXlQ.js → journeyDiagram-XKPGCS4Q-CqPnw4UV.js} +1 -1
  40. package/dist-renderer/assets/{kanban-definition-3W4ZIXB7-fCDVhVUm.js → kanban-definition-3W4ZIXB7-SLlzcUJ2.js} +1 -1
  41. package/dist-renderer/assets/{layout-CPFgj98r.js → layout-BZLlNmbr.js} +1 -1
  42. package/dist-renderer/assets/{linear-CYiQ7Y3M.js → linear-qz6v45xy.js} +1 -1
  43. package/dist-renderer/assets/{mindmap-definition-VGOIOE7T-D31dS2KE.js → mindmap-definition-VGOIOE7T-B1-kmEWV.js} +1 -1
  44. package/dist-renderer/assets/{pieDiagram-ADFJNKIX-BOsCJfds.js → pieDiagram-ADFJNKIX-B8a02iNx.js} +1 -1
  45. package/dist-renderer/assets/{quadrantDiagram-AYHSOK5B-CYTVQCfr.js → quadrantDiagram-AYHSOK5B-BKv1Xfou.js} +1 -1
  46. package/dist-renderer/assets/{requirementDiagram-UZGBJVZJ-CODCFpkt.js → requirementDiagram-UZGBJVZJ-B3DUpZi2.js} +1 -1
  47. package/dist-renderer/assets/{sankeyDiagram-TZEHDZUN-Z4ce9ZtZ.js → sankeyDiagram-TZEHDZUN-DmPzuTsy.js} +1 -1
  48. package/dist-renderer/assets/{sequenceDiagram-WL72ISMW-CmS9TxhW.js → sequenceDiagram-WL72ISMW-Bo7RelRb.js} +1 -1
  49. package/dist-renderer/assets/{stateDiagram-FKZM4ZOC-o9k-ns3q.js → stateDiagram-FKZM4ZOC-1epX98gV.js} +1 -1
  50. package/dist-renderer/assets/{stateDiagram-v2-4FDKWEC3-CxHMyEt1.js → stateDiagram-v2-4FDKWEC3-03Ym9PTr.js} +1 -1
  51. package/dist-renderer/assets/{timeline-definition-IT6M3QCI-B6T3zrde.js → timeline-definition-IT6M3QCI-r6isC62H.js} +1 -1
  52. package/dist-renderer/assets/{treemap-GDKQZRPO-CVd5GNDw.js → treemap-GDKQZRPO-CGKpOUF2.js} +1 -1
  53. package/dist-renderer/assets/{xychartDiagram-PRI3JC2R-CleBrdqc.js → xychartDiagram-PRI3JC2R-t4-rwdAw.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 +907 -184
  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/session-intelligence/UsageTelemetryService.ts +33 -18
  93. package/src/main/services/team/ClaudeBinaryResolver.ts +469 -0
  94. package/src/main/services/team/ClaudeDoctorProbe.ts +0 -0
  95. package/src/main/services/team/cliFlavor.ts +54 -0
  96. package/src/main/services/teams-mvp/CollaborationBoardService.ts +310 -0
  97. package/src/main/services/teams-mvp/TaskDispatchService.ts +883 -95
  98. package/src/main/services/teams-mvp/TeamProvisioningService.ts +58 -19
  99. package/src/main/services/teams-mvp/TeamWorkspaceService.ts +25 -2
  100. package/src/main/services/teams-mvp/index.ts +3 -0
  101. package/src/main/utils/atomicWrite.ts +72 -0
  102. package/src/main/utils/childProcess.ts +554 -0
  103. package/src/main/utils/cliEnv.ts +54 -0
  104. package/src/main/utils/cliPathMerge.ts +97 -0
  105. package/src/main/utils/pathDecoder.ts +664 -0
  106. package/src/main/utils/pathValidation.ts +432 -0
  107. package/src/main/utils/shellEnv.ts +331 -0
  108. package/src/renderer/App.tsx +5 -0
  109. package/src/renderer/api/httpClient.ts +128 -0
  110. package/src/renderer/components/extensions/ExtensionStoreView.tsx +59 -34
  111. package/src/renderer/components/extensions/ExtensionsSubTabTrigger.tsx +1 -1
  112. package/src/renderer/components/extensions/common/ExtensionToast.tsx +141 -0
  113. package/src/renderer/components/extensions/common/HarnessSelector.tsx +71 -0
  114. package/src/renderer/components/extensions/env/EnvVarPanel.tsx +335 -0
  115. package/src/renderer/components/extensions/env/ProjectEnvPanel.tsx +239 -0
  116. package/src/renderer/components/extensions/mcp/CustomMcpServerDialog.tsx +14 -223
  117. package/src/renderer/components/extensions/mcp/McpServerDetailDialog.tsx +11 -0
  118. package/src/renderer/components/extensions/mcp/McpServersPanel.tsx +51 -1
  119. package/src/renderer/components/extensions/skills/SkillsPanel.tsx +1 -126
  120. package/src/renderer/components/layout/PaneContent.tsx +2 -0
  121. package/src/renderer/components/layout/SortableTab.tsx +1 -0
  122. package/src/renderer/components/layout/TabBarActions.tsx +12 -12
  123. package/src/renderer/components/schedules/SchedulesView.tsx +54 -22
  124. package/src/renderer/components/settings/sections/AdvancedSection.tsx +1 -1
  125. package/src/renderer/components/settings/sections/HarnessSection.tsx +2 -6
  126. package/src/renderer/components/settings/sections/TaskBusSection.tsx +144 -84
  127. package/src/renderer/components/sidebar/SidebarSessions.tsx +23 -0
  128. package/src/renderer/components/sidebar/WorkspaceBrowser.tsx +1 -7
  129. package/src/renderer/components/tasks/TasksView.tsx +343 -0
  130. package/src/renderer/components/team/HarnessSelect.tsx +71 -0
  131. package/src/renderer/components/team/TeamDetailView.tsx +55 -98
  132. package/src/renderer/components/team/dialogs/CreateTeamDialog.tsx +21 -12
  133. package/src/renderer/components/team/dialogs/EditTeamDialog.tsx +8 -13
  134. package/src/renderer/components/team/dialogs/LaunchTeamDialog.tsx +1 -1
  135. package/src/renderer/components/team/editor/EditorContextMenu.tsx +8 -23
  136. package/src/renderer/components/team/editor/EditorFileTree.tsx +0 -4
  137. package/src/renderer/components/team/editor/EditorSelectionMenu.tsx +1 -8
  138. package/src/renderer/components/team/editor/ProjectEditorOverlay.tsx +0 -10
  139. package/src/renderer/components/team/kanban/KanbanBoard.tsx +31 -65
  140. package/src/renderer/components/team/members/MemberDetailDialog.tsx +8 -33
  141. package/src/renderer/components/team/messages/MessageComposer.tsx +39 -3
  142. package/src/renderer/components/team/messages/MessagesPanel.tsx +100 -26
  143. package/src/renderer/components/team/messages/StatusBlock.tsx +2 -24
  144. package/src/renderer/components/team/schedule/ScheduleEmptyState.tsx +1 -1
  145. package/src/renderer/components/terminal/TerminalPanel.tsx +156 -0
  146. package/src/renderer/components/ui/MentionableTextarea.tsx +0 -1
  147. package/src/renderer/hooks/useExtensionsTabState.ts +2 -2
  148. package/src/renderer/store/slices/extensionsSlice.ts +42 -107
  149. package/src/renderer/store/slices/scheduleSlice.ts +21 -0
  150. package/src/renderer/store/slices/teamSlice.ts +67 -25
  151. package/src/renderer/types/tabs.ts +1 -0
  152. package/src/shared/types/api.ts +58 -0
  153. package/src/shared/types/extensions/index.ts +1 -0
  154. package/src/shared/types/extensions/mcp.ts +2 -0
  155. package/src/shared/types/extensions/plugin.ts +2 -1
  156. package/src/shared/types/extensions/skill.ts +7 -0
  157. package/src/shared/types/team.ts +104 -1
  158. package/src/shared/utils/providerExtensionCapabilities.ts +1 -1
  159. package/dist-renderer/assets/ProjectEditorOverlay-A4DZTvSy.js +0 -57
  160. package/dist-renderer/assets/channel-Pre42N5O.js +0 -1
  161. package/dist-renderer/assets/classDiagram-2ON5EDUG-CdJsTJsj.js +0 -1
  162. package/dist-renderer/assets/classDiagram-v2-WZHVMYZB-CdJsTJsj.js +0 -1
  163. package/dist-renderer/assets/clone-BjQBiNfj.js +0 -1
  164. package/dist-renderer/assets/index-BIOJremZ.css +0 -1
  165. package/src/features/recent-projects/main/adapters/input/http/registerRecentProjectsHttp.ts +0 -30
  166. package/src/features/recent-projects/main/adapters/output/presenters/DashboardRecentProjectsPresenter.ts +0 -27
  167. package/src/features/recent-projects/main/adapters/output/sources/ClaudeRecentProjectsSourceAdapter.ts +0 -91
  168. package/src/features/recent-projects/main/adapters/output/sources/CodexRecentProjectsSourceAdapter.ts +0 -326
  169. package/src/features/recent-projects/main/composition/createRecentProjectsFeature.ts +0 -43
  170. package/src/features/recent-projects/main/index.ts +0 -3
  171. package/src/features/recent-projects/main/infrastructure/cache/InMemoryRecentProjectsCache.ts +0 -34
  172. package/src/features/recent-projects/main/infrastructure/codex/CodexAppServerClient.ts +0 -116
  173. package/src/features/recent-projects/main/infrastructure/identity/RecentProjectIdentityResolver.ts +0 -20
  174. package/src/features/recent-projects/main/infrastructure/identity/normalizeIdentityPath.ts +0 -10
  175. package/src/renderer/components/extensions/apikeys/ApiKeyCard.tsx +0 -143
  176. package/src/renderer/components/extensions/apikeys/ApiKeyFormDialog.tsx +0 -282
  177. package/src/renderer/components/extensions/apikeys/ApiKeysPanel.tsx +0 -280
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@yancyyu/openhermit",
3
3
  "type": "module",
4
- "version": "1.6.28",
4
+ "version": "1.6.30",
5
5
  "description": "openHermit: team-oriented agent management workbench atop cc-connect.",
6
6
  "license": "AGPL-3.0",
7
7
  "author": {
@@ -107,7 +107,9 @@
107
107
  "@tiptap/pm": "^3.20.4",
108
108
  "@tiptap/react": "^3.20.4",
109
109
  "@tiptap/starter-kit": "^3.20.4",
110
+ "anser": "^2.3.5",
110
111
  "cc-connect": "1.3.3-beta.3",
112
+ "chokidar": "^5.0.0",
111
113
  "class-variance-authority": "^0.7.1",
112
114
  "clsx": "^2.1.1",
113
115
  "cmdk": "1.0.4",
@@ -119,6 +121,7 @@
119
121
  "fastify": "^5.7.4",
120
122
  "highlight.js": "^11.11.1",
121
123
  "idb-keyval": "^6.2.2",
124
+ "isbinaryfile": "^6.0.0",
122
125
  "lucide-react": "^0.577.0",
123
126
  "mdast-util-to-hast": "^13.2.1",
124
127
  "mermaid": "^11.12.3",
@@ -0,0 +1,353 @@
1
+ /**
2
+ * IPC handlers for the Extension Store.
3
+ *
4
+ * Bridges renderer requests to the extension services (catalog, install,
5
+ * state, skills, credentials) and returns results via IPC.
6
+ */
7
+
8
+ import type { CcAgentType } from '@shared/types/ccConnect';
9
+ import type {
10
+ McpInstallRequest,
11
+ McpCustomInstallRequest,
12
+ OperationResult,
13
+ PluginInstallRequest,
14
+ SkillDeleteRequest,
15
+ SkillImportRequest,
16
+ SkillUpsertRequest,
17
+ SkillWatcherEvent,
18
+ } from '@shared/types/extensions';
19
+
20
+ import { PluginCatalogService } from '@main/services/extensions/catalog/PluginCatalogService';
21
+ import { McpCatalogAggregator } from '@main/services/extensions/catalog/McpCatalogAggregator';
22
+ import { OfficialMcpRegistryService } from '@main/services/extensions/catalog/OfficialMcpRegistryService';
23
+ import { GlamaMcpEnrichmentService } from '@main/services/extensions/catalog/GlamaMcpEnrichmentService';
24
+ import { ExtensionFacadeService } from '@main/services/extensions/ExtensionFacadeService';
25
+ import { PluginInstallationStateService } from '@main/services/extensions/state/PluginInstallationStateService';
26
+ import { McpInstallationStateService } from '@main/services/extensions/state/McpInstallationStateService';
27
+ import { SkillsCatalogService } from '@main/services/extensions/skills/SkillsCatalogService';
28
+ import { SkillsMutationService } from '@main/services/extensions/skills/SkillsMutationService';
29
+ import { SkillsWatcherService } from '@main/services/extensions/skills/SkillsWatcherService';
30
+ import { CredentialService } from '@main/services/extensions/credentials/CredentialService';
31
+ import { getAdapter } from '@main/services/extensions/runtime/adapterRegistry';
32
+ import { createLogger } from '@shared/utils/logger';
33
+
34
+ const logger = createLogger('Extensions:IPC');
35
+
36
+ // ── Result wrapper ──
37
+
38
+ interface IpcResult<T> {
39
+ success: boolean;
40
+ data?: T;
41
+ error?: string;
42
+ }
43
+
44
+ function wrapHandler<T>(handler: () => Promise<T>): Promise<IpcResult<T>> {
45
+ return handler()
46
+ .then((data) => ({ success: true as const, data }))
47
+ .catch((err) => ({
48
+ success: false as const,
49
+ error: err instanceof Error ? err.message : String(err),
50
+ }));
51
+ }
52
+
53
+ // ── Service instances (singleton) ──
54
+
55
+ let facade: ExtensionFacadeService | null = null;
56
+ let skillsCatalog: SkillsCatalogService | null = null;
57
+ let skillsMutation: SkillsMutationService | null = null;
58
+ let skillsWatcher: SkillsWatcherService | null = null;
59
+ let credentials: CredentialService | null = null;
60
+
61
+ function getFacade(): ExtensionFacadeService {
62
+ if (!facade) {
63
+ const pluginCatalog = new PluginCatalogService();
64
+ const pluginState = new PluginInstallationStateService();
65
+ const mcpOfficial = new OfficialMcpRegistryService();
66
+ const mcpGlama = new GlamaMcpEnrichmentService();
67
+ const mcpAggregator = new McpCatalogAggregator(mcpOfficial, mcpGlama);
68
+ const mcpState = new McpInstallationStateService();
69
+ facade = new ExtensionFacadeService(pluginCatalog, pluginState, mcpAggregator, mcpState);
70
+ }
71
+ return facade;
72
+ }
73
+
74
+ function getSkillsCatalog(): SkillsCatalogService {
75
+ if (!skillsCatalog) skillsCatalog = new SkillsCatalogService();
76
+ return skillsCatalog;
77
+ }
78
+
79
+ function getSkillsMutation(): SkillsMutationService {
80
+ if (!skillsMutation) skillsMutation = new SkillsMutationService();
81
+ return skillsMutation;
82
+ }
83
+
84
+ function getSkillsWatcher(): SkillsWatcherService {
85
+ if (!skillsWatcher) skillsWatcher = new SkillsWatcherService();
86
+ return skillsWatcher;
87
+ }
88
+
89
+ /**
90
+ * Wire the skills file-watcher to a transport-specific event emitter.
91
+ * Called once at startup by each transport (Electron IPC → webContents.send,
92
+ * standalone server → SSE broadcast).
93
+ */
94
+ export function setSkillsWatcherEmitter(emit: (event: SkillWatcherEvent) => void): void {
95
+ getSkillsWatcher().setEmitter(emit);
96
+ }
97
+
98
+ function getCredentials(): CredentialService {
99
+ if (!credentials) credentials = new CredentialService();
100
+ return credentials;
101
+ }
102
+
103
+ // ── IPC Channel Names ──
104
+
105
+ const channels = {
106
+ PLUGIN_GET_ALL: 'extensions:plugin:getAll',
107
+ PLUGIN_GET_README: 'extensions:plugin:getReadme',
108
+ PLUGIN_INSTALL: 'extensions:plugin:install',
109
+ PLUGIN_UNINSTALL: 'extensions:plugin:uninstall',
110
+ MCP_SEARCH: 'extensions:mcp:search',
111
+ MCP_BROWSE: 'extensions:mcp:browse',
112
+ MCP_GET_BY_ID: 'extensions:mcp:getById',
113
+ MCP_GET_INSTALLED: 'extensions:mcp:getInstalled',
114
+ MCP_INSTALL: 'extensions:mcp:install',
115
+ MCP_INSTALL_CUSTOM: 'extensions:mcp:installCustom',
116
+ MCP_UNINSTALL: 'extensions:mcp:uninstall',
117
+ SKILLS_LIST: 'extensions:skills:list',
118
+ SKILLS_GET_DETAIL: 'extensions:skills:getDetail',
119
+ SKILLS_UPSERT: 'extensions:skills:upsert',
120
+ SKILLS_DELETE: 'extensions:skills:delete',
121
+ CREDENTIALS_GET_MCP: 'extensions:credentials:getMcp',
122
+ CREDENTIALS_SAVE_MCP: 'extensions:credentials:saveMcp',
123
+ CREDENTIALS_GET_PROJECT_ENV: 'extensions:credentials:getProjectEnv',
124
+ CREDENTIALS_SAVE_PROJECT_ENV: 'extensions:credentials:saveProjectEnv',
125
+ CREDENTIALS_SCAN_REQUIRED: 'extensions:credentials:scanRequired',
126
+ CREDENTIALS_RESOLVE_AGENT_ENV: 'extensions:credentials:resolveAgentEnv',
127
+ CREDENTIALS_STATUS: 'extensions:credentials:status',
128
+ } as const;
129
+
130
+ export { channels as extensionChannels };
131
+
132
+ // ── Handler Registration ──
133
+
134
+ // For Hermit's web-based architecture, handlers are exposed as an API object
135
+ // rather than Electron ipcMain.handle. The server.ts or API layer calls these.
136
+
137
+ export const extensionHandlers = {
138
+ // ── Plugins ──
139
+
140
+ pluginGetAll: () => wrapHandler(() => getFacade().getEnrichedPlugins()),
141
+
142
+ pluginGetReadme: (pluginId: string) => wrapHandler(() => getFacade().getPluginReadme(pluginId)),
143
+
144
+ pluginInstall: (request: PluginInstallRequest) =>
145
+ wrapHandler(async () => {
146
+ // Plugins are claudecode-only — no other harness supports them
147
+ const harnessType = 'claudecode' as CcAgentType;
148
+ const adapter = getAdapter(harnessType);
149
+ if (!adapter) return { state: 'error' as const, error: `No adapter for ${harnessType}` };
150
+
151
+ // Resolve qualifiedName from catalog
152
+ const resolved = await getFacade().getPluginReadme(request.pluginId);
153
+ // For now, use pluginId as qualifiedName (catalog resolution TBD)
154
+ const result = await adapter.installPlugin(request.pluginId, {
155
+ scope: request.scope ?? 'user',
156
+ projectPath: request.projectPath,
157
+ });
158
+
159
+ if (result.state === 'success') {
160
+ getFacade().invalidateInstalledCache();
161
+ }
162
+ return result;
163
+ }),
164
+
165
+ pluginUninstall: (
166
+ pluginId: string,
167
+ scope?: string,
168
+ projectPath?: string,
169
+ harnessType?: CcAgentType
170
+ ) =>
171
+ wrapHandler(async () => {
172
+ const ht = (harnessType ?? 'claudecode') as CcAgentType;
173
+ const adapter = getAdapter(ht);
174
+ if (!adapter) return { state: 'error' as const, error: `No adapter for ${ht}` };
175
+
176
+ const result = await adapter.uninstallPlugin(pluginId, {
177
+ scope: (scope as InstallOpts['scope']) ?? 'user',
178
+ projectPath,
179
+ });
180
+
181
+ if (result.state === 'success') {
182
+ getFacade().invalidateInstalledCache();
183
+ }
184
+ return result;
185
+ }),
186
+
187
+ // ── MCP ──
188
+
189
+ mcpSearch: (query: string, limit?: number) =>
190
+ wrapHandler(() => getFacade().searchMcp(query, limit)),
191
+
192
+ mcpBrowse: (cursor?: string, limit?: number) =>
193
+ wrapHandler(() => getFacade().browseMcp(cursor, limit)),
194
+
195
+ mcpGetById: (registryId: string) => wrapHandler(() => getFacade().getMcpById(registryId)),
196
+
197
+ mcpGetInstalled: (projectPath?: string) =>
198
+ wrapHandler(() => getFacade().getInstalledMcp(projectPath)),
199
+
200
+ mcpInstall: (request: McpInstallRequest) =>
201
+ wrapHandler(async () => {
202
+ const harnessType = (request.harnessType ?? 'claudecode') as CcAgentType;
203
+ const adapter = getAdapter(harnessType);
204
+ if (!adapter || !adapter.supportsMcp) {
205
+ return { state: 'error' as const, error: `MCP not supported by ${harnessType}` };
206
+ }
207
+
208
+ // Re-fetch server from registry (security: don't trust renderer)
209
+ const server = await getFacade().getMcpById(request.registryId);
210
+ if (!server?.installSpec) {
211
+ return {
212
+ state: 'error' as const,
213
+ error: `Server "${request.registryId}" not found or no install spec`,
214
+ };
215
+ }
216
+
217
+ const result = await adapter.installMcp(
218
+ request.serverName,
219
+ server.installSpec,
220
+ request.envValues,
221
+ request.headers,
222
+ { scope: request.scope ?? 'user', projectPath: request.projectPath }
223
+ );
224
+
225
+ if (result.state === 'success') {
226
+ getFacade().invalidateInstalledCache();
227
+ // Save credentials for auto-fill next time
228
+ await getCredentials().saveMcpCredentials(request.serverName, request.envValues);
229
+ }
230
+ return result;
231
+ }),
232
+
233
+ mcpInstallCustom: (request: McpCustomInstallRequest) =>
234
+ wrapHandler(async () => {
235
+ const harnessType = (request.harnessType ?? 'claudecode') as CcAgentType;
236
+ const adapter = getAdapter(harnessType);
237
+ if (!adapter || !adapter.supportsMcp) {
238
+ return { state: 'error' as const, error: `MCP not supported by ${harnessType}` };
239
+ }
240
+
241
+ const result = await adapter.installMcp(
242
+ request.serverName,
243
+ request.installSpec,
244
+ request.envValues,
245
+ request.headers,
246
+ { scope: request.scope ?? 'user', projectPath: request.projectPath }
247
+ );
248
+
249
+ if (result.state === 'success') {
250
+ getFacade().invalidateInstalledCache();
251
+ }
252
+ return result;
253
+ }),
254
+
255
+ mcpUninstall: (name: string, scope?: string, projectPath?: string, harnessType?: CcAgentType) =>
256
+ wrapHandler(async () => {
257
+ const ht = (harnessType ?? 'claudecode') as CcAgentType;
258
+ const adapter = getAdapter(ht);
259
+ if (!adapter) return { state: 'error' as const, error: `No adapter for ${ht}` };
260
+
261
+ const result = await adapter.uninstallMcp(name, {
262
+ scope: (scope as InstallOpts['scope']) ?? 'user',
263
+ projectPath,
264
+ });
265
+
266
+ if (result.state === 'success') {
267
+ getFacade().invalidateInstalledCache();
268
+ }
269
+ return result;
270
+ }),
271
+
272
+ // ── Skills ──
273
+
274
+ skillsList: (projectPath?: string) => wrapHandler(() => getSkillsCatalog().list(projectPath)),
275
+
276
+ skillsGetDetail: (skillId: string, projectPath?: string) =>
277
+ wrapHandler(() => getSkillsCatalog().getDetail(skillId, projectPath)),
278
+
279
+ skillsUpsert: (request: SkillUpsertRequest) =>
280
+ wrapHandler(async () => {
281
+ const mutation = getSkillsMutation();
282
+ await mutation.applyUpsert(request);
283
+ return { ok: true };
284
+ }),
285
+
286
+ skillsDelete: (request: SkillDeleteRequest) =>
287
+ wrapHandler(async () => {
288
+ const mutation = getSkillsMutation();
289
+ await mutation.deleteSkill(request);
290
+ return { ok: true };
291
+ }),
292
+
293
+ skillsPreviewUpsert: (request: SkillUpsertRequest) =>
294
+ wrapHandler(() => getSkillsMutation().previewUpsert(request)),
295
+
296
+ skillsApplyUpsert: (request: SkillUpsertRequest) =>
297
+ wrapHandler(() => getSkillsMutation().applyUpsert(request)),
298
+
299
+ skillsPreviewImport: (request: SkillImportRequest) =>
300
+ wrapHandler(() => getSkillsMutation().previewImport(request)),
301
+
302
+ skillsApplyImport: (request: SkillImportRequest) =>
303
+ wrapHandler(() => getSkillsMutation().applyImport(request)),
304
+
305
+ skillsStartWatching: (projectPath?: string) =>
306
+ wrapHandler(() => getSkillsWatcher().start(projectPath)),
307
+
308
+ skillsStopWatching: (watchId: string) =>
309
+ wrapHandler(async () => {
310
+ await getSkillsWatcher().stop(watchId);
311
+ return { ok: true };
312
+ }),
313
+
314
+ // ── Credentials ──
315
+
316
+ credentialsGetMcp: (mcpName: string) =>
317
+ wrapHandler(() => getCredentials().getMcpCredentials(mcpName)),
318
+
319
+ credentialsSaveMcp: (mcpName: string, envValues: Record<string, string>) =>
320
+ wrapHandler(() => getCredentials().saveMcpCredentials(mcpName, envValues)),
321
+
322
+ credentialsGetProjectEnv: (projectPath: string) =>
323
+ wrapHandler(() => getCredentials().getProjectEnv(projectPath)),
324
+
325
+ credentialsSaveProjectEnv: (projectPath: string, vars: Record<string, string>) =>
326
+ wrapHandler(() => getCredentials().saveProjectEnv(projectPath, vars)),
327
+
328
+ credentialsScanRequired: (
329
+ projectPath: string,
330
+ mcpServers: {
331
+ name: string;
332
+ envVars?: { name: string; isRequired: boolean; description?: string }[];
333
+ }[],
334
+ skillReqs: {
335
+ name: string;
336
+ envVars: { name: string; isRequired?: boolean; description?: string }[];
337
+ }[]
338
+ ) => wrapHandler(() => getCredentials().scanRequiredEnv(projectPath, mcpServers, skillReqs)),
339
+
340
+ credentialsResolveAgentEnv: (projectPath: string) =>
341
+ wrapHandler(() => getCredentials().resolveAgentEnv(projectPath)),
342
+
343
+ credentialsGetSkillGlobalEnv: (skillFolderName: string) =>
344
+ wrapHandler(() => getCredentials().getSkillGlobalEnv(skillFolderName)),
345
+
346
+ credentialsSaveSkillGlobalEnv: (skillFolderName: string, vars: Record<string, string>) =>
347
+ wrapHandler(() => getCredentials().saveSkillGlobalEnv(skillFolderName, vars)),
348
+
349
+ credentialsStatus: () => wrapHandler(() => getCredentials().getStorageStatus()),
350
+ };
351
+
352
+ // Re-export for type usage
353
+ type InstallOpts = import('@main/services/extensions/runtime/HarnessInstallAdapter').InstallOpts;