@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.
- package/dist-renderer/assets/{ProjectEditorOverlay-CQm6jUR1.js → ProjectEditorOverlay-DkXfi2pg.js} +1 -1
- package/dist-renderer/assets/{TeamGraphOverlay-h0WDfifv.js → TeamGraphOverlay-CHNNVraw.js} +1 -1
- package/dist-renderer/assets/{_basePickBy-CgG_tjgX.js → _basePickBy-Do-Ff83V.js} +1 -1
- package/dist-renderer/assets/{_baseUniq-DwPTU9lP.js → _baseUniq-nDLhSuJI.js} +1 -1
- package/dist-renderer/assets/{arc-7nIrGRzY.js → arc-Bp7fA6sx.js} +1 -1
- package/dist-renderer/assets/{architectureDiagram-VXUJARFQ-BYhA6Ev2.js → architectureDiagram-VXUJARFQ-CPC1HdGy.js} +1 -1
- package/dist-renderer/assets/{blockDiagram-VD42YOAC-BVpZUGDg.js → blockDiagram-VD42YOAC-DTVKyNTO.js} +1 -1
- package/dist-renderer/assets/{c4Diagram-YG6GDRKO-DsdreMQ9.js → c4Diagram-YG6GDRKO-XVu-AN00.js} +1 -1
- package/dist-renderer/assets/channel-CIwbNcUO.js +1 -0
- package/dist-renderer/assets/{chunk-4BX2VUAB-CcoAs7Jd.js → chunk-4BX2VUAB-BcWmVyA-.js} +1 -1
- package/dist-renderer/assets/{chunk-55IACEB6-CGGAOoXd.js → chunk-55IACEB6-Co4Z2jsE.js} +1 -1
- package/dist-renderer/assets/{chunk-B4BG7PRW-FhpTEPvD.js → chunk-B4BG7PRW-C8q9gfDT.js} +1 -1
- package/dist-renderer/assets/{chunk-DI55MBZ5-DoYySbm1.js → chunk-DI55MBZ5-qDgb1gxO.js} +1 -1
- package/dist-renderer/assets/{chunk-FMBD7UC4-e9l2tGHG.js → chunk-FMBD7UC4-Cm8Gu2gu.js} +1 -1
- package/dist-renderer/assets/{chunk-QN33PNHL-DeiXVTCy.js → chunk-QN33PNHL-DYji1BRS.js} +1 -1
- package/dist-renderer/assets/{chunk-QZHKN3VN-DC2UJLJM.js → chunk-QZHKN3VN-DWAS568H.js} +1 -1
- package/dist-renderer/assets/{chunk-TZMSLE5B-BHFD9eZI.js → chunk-TZMSLE5B-CLFzXLA8.js} +1 -1
- package/dist-renderer/assets/classDiagram-2ON5EDUG-04A-pvql.js +1 -0
- package/dist-renderer/assets/classDiagram-v2-WZHVMYZB-04A-pvql.js +1 -0
- package/dist-renderer/assets/clone-DQnvTIEM.js +1 -0
- package/dist-renderer/assets/{cose-bilkent-S5V4N54A-BdybQraU.js → cose-bilkent-S5V4N54A-CZdGhX_3.js} +1 -1
- package/dist-renderer/assets/{dagre-6UL2VRFP-DdF3pwM3.js → dagre-6UL2VRFP-BVY-G6nO.js} +1 -1
- package/dist-renderer/assets/{diagram-PSM6KHXK-B9Ldd3nh.js → diagram-PSM6KHXK-CUACvAwG.js} +1 -1
- package/dist-renderer/assets/{diagram-QEK2KX5R-XEqkrbpu.js → diagram-QEK2KX5R-3SfnesSG.js} +1 -1
- package/dist-renderer/assets/{diagram-S2PKOQOG-CipwtY59.js → diagram-S2PKOQOG-E3ksXClJ.js} +1 -1
- package/dist-renderer/assets/{erDiagram-Q2GNP2WA-BB-2ISGo.js → erDiagram-Q2GNP2WA-aYjGXss7.js} +1 -1
- package/dist-renderer/assets/{flowDiagram-NV44I4VS-B8XmJ0u2.js → flowDiagram-NV44I4VS-JMHrrTQs.js} +1 -1
- package/dist-renderer/assets/{ganttDiagram-JELNMOA3-D-8XglBb.js → ganttDiagram-JELNMOA3-CVQ-R5rN.js} +1 -1
- package/dist-renderer/assets/{gitGraphDiagram-V2S2FVAM-DL4ChakD.js → gitGraphDiagram-V2S2FVAM-OLn9jq61.js} +1 -1
- package/dist-renderer/assets/{graph-BiFNoBjP.js → graph-BAb2J0l8.js} +1 -1
- package/dist-renderer/assets/{index-qNBNjW4K.js → index-BSoCjBWn.js} +1 -1
- package/dist-renderer/assets/{index-6m1ZAymG.js → index-BtG3HbqP.js} +1 -1
- package/dist-renderer/assets/{index-BowUl0Jb.js → index-CH8e7g1f.js} +583 -573
- package/dist-renderer/assets/index-CSt8DTcn.css +1 -0
- package/dist-renderer/assets/{index-Dp3kJTEe.js → index-Ca4iNkRA.js} +1 -1
- package/dist-renderer/assets/{index-vAykq1H1.js → index-DU9PGgZJ.js} +1 -1
- package/dist-renderer/assets/{index-TOpt_T7A.js → index-DtMzIS9o.js} +1 -1
- package/dist-renderer/assets/{infoDiagram-HS3SLOUP-DRIBfHDi.js → infoDiagram-HS3SLOUP-CY_ptQNL.js} +1 -1
- package/dist-renderer/assets/{journeyDiagram-XKPGCS4Q-BOMiigU4.js → journeyDiagram-XKPGCS4Q-C2vuHEo_.js} +1 -1
- package/dist-renderer/assets/{kanban-definition-3W4ZIXB7-DDxeyjod.js → kanban-definition-3W4ZIXB7-mbdNfu8h.js} +1 -1
- package/dist-renderer/assets/{layout-DNANbrI4.js → layout-Do_ArEB1.js} +1 -1
- package/dist-renderer/assets/{linear-DxEJi1yT.js → linear-BMlMKyiq.js} +1 -1
- package/dist-renderer/assets/{mindmap-definition-VGOIOE7T-nBfGriW8.js → mindmap-definition-VGOIOE7T-Dfntn-o2.js} +1 -1
- package/dist-renderer/assets/{pieDiagram-ADFJNKIX-Din5j6sV.js → pieDiagram-ADFJNKIX-LiWHsGMV.js} +1 -1
- package/dist-renderer/assets/{quadrantDiagram-AYHSOK5B-DMVK2BEQ.js → quadrantDiagram-AYHSOK5B-D87St8AF.js} +1 -1
- package/dist-renderer/assets/{requirementDiagram-UZGBJVZJ-6SC94Gg_.js → requirementDiagram-UZGBJVZJ-DAa6lHBx.js} +1 -1
- package/dist-renderer/assets/{sankeyDiagram-TZEHDZUN-CD2gghhu.js → sankeyDiagram-TZEHDZUN-VOUngars.js} +1 -1
- package/dist-renderer/assets/{sequenceDiagram-WL72ISMW-BnhkN7nZ.js → sequenceDiagram-WL72ISMW-BzwzmFr2.js} +1 -1
- package/dist-renderer/assets/{stateDiagram-FKZM4ZOC-Bn8XdYX-.js → stateDiagram-FKZM4ZOC-BjAQEJ52.js} +1 -1
- package/dist-renderer/assets/{stateDiagram-v2-4FDKWEC3-1b6sI1_g.js → stateDiagram-v2-4FDKWEC3-BDwy4GJm.js} +1 -1
- package/dist-renderer/assets/{timeline-definition-IT6M3QCI-CNs3RPoa.js → timeline-definition-IT6M3QCI-Y5XBZt3W.js} +1 -1
- package/dist-renderer/assets/treemap-GDKQZRPO-DzkdUEow.js +162 -0
- package/dist-renderer/assets/{xychartDiagram-PRI3JC2R-B8o5J2f3.js → xychartDiagram-PRI3JC2R-D-zbvJOv.js} +1 -1
- package/dist-renderer/index.html +2 -2
- package/package.json +4 -1
- package/src/main/ipc/extensions.ts +353 -0
- package/src/main/server.ts +209 -6
- package/src/main/services/extensions/ExtensionFacadeService.ts +135 -0
- package/src/main/services/extensions/catalog/GlamaMcpEnrichmentService.ts +190 -0
- package/src/main/services/extensions/catalog/McpCatalogAggregator.ts +150 -0
- package/src/main/services/extensions/catalog/OfficialMcpRegistryService.ts +381 -0
- package/src/main/services/extensions/catalog/PluginCatalogService.ts +392 -0
- package/src/main/services/extensions/credentials/CredentialService.ts +343 -0
- package/src/main/services/extensions/install/McpInstallService.ts +407 -0
- package/src/main/services/extensions/install/PluginInstallService.ts +198 -0
- package/src/main/services/extensions/runtime/ClaudeCodeAdapter.ts +199 -0
- package/src/main/services/extensions/runtime/CodexAdapter.ts +100 -0
- package/src/main/services/extensions/runtime/CursorAdapter.ts +154 -0
- package/src/main/services/extensions/runtime/ExtensionsRuntimeAdapter.ts +172 -0
- package/src/main/services/extensions/runtime/GeminiAdapter.ts +91 -0
- package/src/main/services/extensions/runtime/HarnessInstallAdapter.ts +49 -0
- package/src/main/services/extensions/runtime/McpConfigStateReader.ts +209 -0
- package/src/main/services/extensions/runtime/OpenCodeAdapter.ts +91 -0
- package/src/main/services/extensions/runtime/adapterRegistry.ts +54 -0
- package/src/main/services/extensions/runtime/mcpDiagnosticsParser.ts +214 -0
- package/src/main/services/extensions/runtime/mcpRuntimeJson.ts +45 -0
- package/src/main/services/extensions/skills/SkillImportService.ts +155 -0
- package/src/main/services/extensions/skills/SkillMetadataParser.ts +323 -0
- package/src/main/services/extensions/skills/SkillPlanService.ts +411 -0
- package/src/main/services/extensions/skills/SkillReviewService.ts +73 -0
- package/src/main/services/extensions/skills/SkillRootsResolver.ts +49 -0
- package/src/main/services/extensions/skills/SkillScaffoldService.ts +89 -0
- package/src/main/services/extensions/skills/SkillScanner.ts +117 -0
- package/src/main/services/extensions/skills/SkillValidator.ts +69 -0
- package/src/main/services/extensions/skills/SkillsCatalogService.ts +92 -0
- package/src/main/services/extensions/skills/SkillsMutationService.ts +146 -0
- package/src/main/services/extensions/skills/SkillsWatcherService.ts +134 -0
- package/src/main/services/extensions/state/McpInstallationStateService.ts +42 -0
- package/src/main/services/extensions/state/PluginInstallationStateService.ts +281 -0
- package/src/main/services/identity/AgentTeamsIdentityStore.ts +218 -0
- package/src/main/services/runtime/providerAwareCliEnv.ts +60 -0
- package/src/main/services/team/ClaudeBinaryResolver.ts +469 -0
- package/src/main/services/team/ClaudeDoctorProbe.ts +0 -0
- package/src/main/services/team/cliFlavor.ts +54 -0
- package/src/main/services/teams-mvp/TaskDispatchService.ts +3 -0
- package/src/main/utils/atomicWrite.ts +72 -0
- package/src/main/utils/childProcess.ts +554 -0
- package/src/main/utils/cliEnv.ts +54 -0
- package/src/main/utils/cliPathMerge.ts +97 -0
- package/src/main/utils/pathDecoder.ts +664 -0
- package/src/main/utils/pathValidation.ts +432 -0
- package/src/main/utils/shellEnv.ts +331 -0
- package/src/renderer/api/httpClient.ts +61 -0
- package/src/renderer/components/extensions/ExtensionStoreView.tsx +63 -35
- package/src/renderer/components/extensions/ExtensionsSubTabTrigger.tsx +1 -1
- package/src/renderer/components/extensions/common/ExtensionToast.tsx +141 -0
- package/src/renderer/components/extensions/common/HarnessSelector.tsx +71 -0
- package/src/renderer/components/extensions/env/EnvVarPanel.tsx +335 -0
- package/src/renderer/components/extensions/env/ProjectEnvPanel.tsx +239 -0
- package/src/renderer/components/extensions/mcp/CustomMcpServerDialog.tsx +14 -223
- package/src/renderer/components/extensions/mcp/McpServerDetailDialog.tsx +111 -15
- package/src/renderer/components/extensions/mcp/McpServersPanel.tsx +51 -1
- package/src/renderer/components/extensions/skills/SkillsPanel.tsx +1 -126
- package/src/renderer/components/settings/sections/HarnessSection.tsx +2 -6
- package/src/renderer/components/settings/sections/TaskBusSection.tsx +17 -7
- package/src/renderer/components/sidebar/SidebarSessions.tsx +23 -0
- package/src/renderer/components/sidebar/WorkspaceBrowser.tsx +1 -7
- package/src/renderer/components/team/HarnessSelect.tsx +71 -0
- package/src/renderer/components/team/TeamDetailView.tsx +74 -123
- package/src/renderer/components/team/TeamListFilterPopover.tsx +0 -16
- package/src/renderer/components/team/TeamListView.tsx +7 -32
- package/src/renderer/components/team/dialogs/CreateTeamDialog.tsx +21 -12
- package/src/renderer/components/team/dialogs/EditTeamDialog.tsx +287 -418
- package/src/renderer/components/team/dialogs/useTeamEditForm.ts +283 -0
- package/src/renderer/components/team/kanban/KanbanBoard.tsx +26 -64
- package/src/renderer/components/team/messages/MessagesPanel.tsx +28 -24
- package/src/renderer/components/terminal/TerminalPanel.tsx +156 -0
- package/src/renderer/hooks/useExtensionsTabState.ts +2 -2
- package/src/renderer/store/slices/extensionsSlice.ts +42 -107
- package/src/renderer/store/slices/teamSlice.ts +8 -2
- package/src/renderer/utils/multimodelProviderVisibility.ts +17 -0
- package/src/renderer/utils/openCodeRuntimeDeliveryDiagnostics.ts +29 -9
- package/src/shared/types/api.ts +29 -0
- package/src/shared/types/extensions/index.ts +1 -0
- package/src/shared/types/extensions/mcp.ts +2 -0
- package/src/shared/types/extensions/plugin.ts +2 -1
- package/src/shared/types/extensions/skill.ts +7 -0
- package/src/shared/utils/providerExtensionCapabilities.ts +1 -1
- package/dist-renderer/assets/channel-C0SqeFU7.js +0 -1
- package/dist-renderer/assets/classDiagram-2ON5EDUG-DWew1HpM.js +0 -1
- package/dist-renderer/assets/classDiagram-v2-WZHVMYZB-DWew1HpM.js +0 -1
- package/dist-renderer/assets/clone-Dm-k63Yr.js +0 -1
- package/dist-renderer/assets/index-BhellmRb.css +0 -1
- package/dist-renderer/assets/treemap-GDKQZRPO-DU_yr827.js +0 -162
- package/src/features/recent-projects/main/adapters/input/http/registerRecentProjectsHttp.ts +0 -30
- package/src/features/recent-projects/main/adapters/output/presenters/DashboardRecentProjectsPresenter.ts +0 -27
- package/src/features/recent-projects/main/adapters/output/sources/ClaudeRecentProjectsSourceAdapter.ts +0 -91
- package/src/features/recent-projects/main/adapters/output/sources/CodexRecentProjectsSourceAdapter.ts +0 -326
- package/src/features/recent-projects/main/composition/createRecentProjectsFeature.ts +0 -43
- package/src/features/recent-projects/main/index.ts +0 -3
- package/src/features/recent-projects/main/infrastructure/cache/InMemoryRecentProjectsCache.ts +0 -34
- package/src/features/recent-projects/main/infrastructure/codex/CodexAppServerClient.ts +0 -116
- package/src/features/recent-projects/main/infrastructure/identity/RecentProjectIdentityResolver.ts +0 -20
- package/src/features/recent-projects/main/infrastructure/identity/normalizeIdentityPath.ts +0 -10
- package/src/renderer/components/extensions/apikeys/ApiKeyCard.tsx +0 -143
- package/src/renderer/components/extensions/apikeys/ApiKeyFormDialog.tsx +0 -282
- package/src/renderer/components/extensions/apikeys/ApiKeysPanel.tsx +0 -280
package/src/main/server.ts
CHANGED
|
@@ -48,6 +48,7 @@ import {
|
|
|
48
48
|
CROSS_TEAM_SOURCE,
|
|
49
49
|
formatCrossTeamText,
|
|
50
50
|
} from '@shared/constants/crossTeam';
|
|
51
|
+
import type { CcAgentType } from '../shared/types/ccConnect';
|
|
51
52
|
import { CcConnectBridge } from './services/ccConnect/CcConnectBridge';
|
|
52
53
|
import { CcConnectClient } from './services/ccConnect/CcConnectClient';
|
|
53
54
|
import { TeamProvisioningService } from './services/teams-mvp';
|
|
@@ -231,7 +232,11 @@ async function initializeTaskBusFromSettings(): Promise<void> {
|
|
|
231
232
|
}
|
|
232
233
|
|
|
233
234
|
taskDispatch.dispose();
|
|
234
|
-
|
|
235
|
+
try {
|
|
236
|
+
await taskDispatch.start(config);
|
|
237
|
+
} catch (err) {
|
|
238
|
+
app.log.warn({ err }, 'Redis connection failed on startup — task bus disabled');
|
|
239
|
+
}
|
|
235
240
|
}
|
|
236
241
|
|
|
237
242
|
function normalizeStringArray(value: unknown): string[] {
|
|
@@ -4617,7 +4622,8 @@ app.get('/api/telemetry/status', async (request, reply) => {
|
|
|
4617
4622
|
// no settings
|
|
4618
4623
|
}
|
|
4619
4624
|
const taskBus = (settings.taskBus ?? {}) as TaskBusConfig;
|
|
4620
|
-
const
|
|
4625
|
+
const redisCfg = taskBus.enabled ? taskBus.redis : undefined;
|
|
4626
|
+
const status = await getTelemetryStatus(redisCfg);
|
|
4621
4627
|
return (
|
|
4622
4628
|
status ?? {
|
|
4623
4629
|
connected: false,
|
|
@@ -4727,10 +4733,207 @@ app.get('/api/events', (request, reply) => {
|
|
|
4727
4733
|
|
|
4728
4734
|
const SSE_FALLBACK_RE = /^\/api\/(.*\/(events|stream|notifications\/stream))$/;
|
|
4729
4735
|
|
|
4730
|
-
|
|
4731
|
-
|
|
4732
|
-
|
|
4733
|
-
|
|
4736
|
+
// ── Extension Store routes (wired to extensionHandlers) ────────────────
|
|
4737
|
+
|
|
4738
|
+
import { extensionHandlers as ext, setSkillsWatcherEmitter } from './ipc/extensions';
|
|
4739
|
+
|
|
4740
|
+
// Broadcast skill file-watcher changes to connected frontends via SSE.
|
|
4741
|
+
setSkillsWatcherEmitter((event) => broadcastSse('skills:changed', event));
|
|
4742
|
+
|
|
4743
|
+
app.get('/api/extensions/plugins', async () => {
|
|
4744
|
+
const result = await ext.pluginGetAll();
|
|
4745
|
+
return result;
|
|
4746
|
+
});
|
|
4747
|
+
|
|
4748
|
+
app.get('/api/extensions/plugins/readme/:pluginId', async (request) => {
|
|
4749
|
+
const { pluginId } = request.params as { pluginId: string };
|
|
4750
|
+
const result = await ext.pluginGetReadme(pluginId);
|
|
4751
|
+
return result;
|
|
4752
|
+
});
|
|
4753
|
+
|
|
4754
|
+
app.post('/api/extensions/plugins/install', async (request) => {
|
|
4755
|
+
const body = request.body as Record<string, unknown>;
|
|
4756
|
+
const result = await ext.pluginInstall(body as any);
|
|
4757
|
+
return result;
|
|
4758
|
+
});
|
|
4759
|
+
|
|
4760
|
+
app.post('/api/extensions/plugins/uninstall', async (request) => {
|
|
4761
|
+
const body = request.body as Record<string, unknown>;
|
|
4762
|
+
const result = await ext.pluginUninstall(
|
|
4763
|
+
body.pluginId as string,
|
|
4764
|
+
body.scope as string,
|
|
4765
|
+
body.projectPath as string,
|
|
4766
|
+
body.harnessType as CcAgentType | undefined
|
|
4767
|
+
);
|
|
4768
|
+
return result;
|
|
4769
|
+
});
|
|
4770
|
+
|
|
4771
|
+
app.get('/api/extensions/mcp/search', async (request) => {
|
|
4772
|
+
const query = (request.query as Record<string, string>).q ?? '';
|
|
4773
|
+
const limit = Number((request.query as Record<string, string>).limit) || 20;
|
|
4774
|
+
const result = await ext.mcpSearch(query, limit);
|
|
4775
|
+
return result;
|
|
4776
|
+
});
|
|
4777
|
+
|
|
4778
|
+
app.get('/api/extensions/mcp/browse', async (request) => {
|
|
4779
|
+
const cursor = (request.query as Record<string, string>).cursor;
|
|
4780
|
+
const limit = Number((request.query as Record<string, string>).limit) || 20;
|
|
4781
|
+
const result = await ext.mcpBrowse(cursor || undefined, limit);
|
|
4782
|
+
return result;
|
|
4783
|
+
});
|
|
4784
|
+
|
|
4785
|
+
app.get('/api/extensions/mcp/installed', async (request) => {
|
|
4786
|
+
const projectPath = (request.query as Record<string, string>).projectPath;
|
|
4787
|
+
const result = await ext.mcpGetInstalled(projectPath);
|
|
4788
|
+
return result;
|
|
4789
|
+
});
|
|
4790
|
+
|
|
4791
|
+
app.get('/api/extensions/mcp/:registryId', async (request) => {
|
|
4792
|
+
const { registryId } = request.params as { registryId: string };
|
|
4793
|
+
const result = await ext.mcpGetById(registryId);
|
|
4794
|
+
return result;
|
|
4795
|
+
});
|
|
4796
|
+
|
|
4797
|
+
app.post('/api/extensions/mcp/install', async (request) => {
|
|
4798
|
+
const body = request.body as Record<string, unknown>;
|
|
4799
|
+
const result = await ext.mcpInstall(body as any);
|
|
4800
|
+
return result;
|
|
4801
|
+
});
|
|
4802
|
+
|
|
4803
|
+
app.post('/api/extensions/mcp/install-custom', async (request) => {
|
|
4804
|
+
const body = request.body as Record<string, unknown>;
|
|
4805
|
+
const result = await ext.mcpInstallCustom(body as any);
|
|
4806
|
+
return result;
|
|
4807
|
+
});
|
|
4808
|
+
|
|
4809
|
+
app.post('/api/extensions/mcp/uninstall', async (request) => {
|
|
4810
|
+
const body = request.body as Record<string, unknown>;
|
|
4811
|
+
const result = await ext.mcpUninstall(
|
|
4812
|
+
body.name as string,
|
|
4813
|
+
body.scope as string,
|
|
4814
|
+
body.projectPath as string,
|
|
4815
|
+
body.harnessType as CcAgentType | undefined
|
|
4816
|
+
);
|
|
4817
|
+
return result;
|
|
4818
|
+
});
|
|
4819
|
+
|
|
4820
|
+
app.get('/api/extensions/skills', async (request) => {
|
|
4821
|
+
const projectPath = (request.query as Record<string, string>).projectPath;
|
|
4822
|
+
const result = await ext.skillsList(projectPath);
|
|
4823
|
+
return result;
|
|
4824
|
+
});
|
|
4825
|
+
|
|
4826
|
+
app.get('/api/extensions/skills/:skillId', async (request) => {
|
|
4827
|
+
const { skillId } = request.params as { skillId: string };
|
|
4828
|
+
const projectPath = (request.query as Record<string, string>).projectPath;
|
|
4829
|
+
const result = await ext.skillsGetDetail(skillId, projectPath);
|
|
4830
|
+
return result;
|
|
4831
|
+
});
|
|
4832
|
+
|
|
4833
|
+
app.post('/api/extensions/skills/upsert', async (request) => {
|
|
4834
|
+
const result = await ext.skillsUpsert(request.body as any);
|
|
4835
|
+
return result;
|
|
4836
|
+
});
|
|
4837
|
+
|
|
4838
|
+
app.post('/api/extensions/skills/delete', async (request) => {
|
|
4839
|
+
const result = await ext.skillsDelete(request.body as any);
|
|
4840
|
+
return result;
|
|
4841
|
+
});
|
|
4842
|
+
|
|
4843
|
+
app.post('/api/extensions/skills/preview-upsert', async (request) => {
|
|
4844
|
+
return ext.skillsPreviewUpsert(request.body as any);
|
|
4845
|
+
});
|
|
4846
|
+
|
|
4847
|
+
app.post('/api/extensions/skills/apply-upsert', async (request) => {
|
|
4848
|
+
return ext.skillsApplyUpsert(request.body as any);
|
|
4849
|
+
});
|
|
4850
|
+
|
|
4851
|
+
app.post('/api/extensions/skills/preview-import', async (request) => {
|
|
4852
|
+
return ext.skillsPreviewImport(request.body as any);
|
|
4853
|
+
});
|
|
4854
|
+
|
|
4855
|
+
app.post('/api/extensions/skills/apply-import', async (request) => {
|
|
4856
|
+
return ext.skillsApplyImport(request.body as any);
|
|
4857
|
+
});
|
|
4858
|
+
|
|
4859
|
+
app.post('/api/extensions/skills/watching/start', async (request) => {
|
|
4860
|
+
const projectPath = (request.query as Record<string, string>).projectPath;
|
|
4861
|
+
return ext.skillsStartWatching(projectPath);
|
|
4862
|
+
});
|
|
4863
|
+
|
|
4864
|
+
app.post('/api/extensions/skills/watching/stop', async (request) => {
|
|
4865
|
+
const { watchId } = (request.body ?? {}) as { watchId?: string };
|
|
4866
|
+
return ext.skillsStopWatching(watchId as string);
|
|
4867
|
+
});
|
|
4868
|
+
|
|
4869
|
+
app.get('/api/extensions/credentials/status', async () => {
|
|
4870
|
+
const result = await ext.credentialsStatus();
|
|
4871
|
+
return result;
|
|
4872
|
+
});
|
|
4873
|
+
|
|
4874
|
+
app.get('/api/extensions/credentials/mcp/:mcpName', async (request) => {
|
|
4875
|
+
const { mcpName } = request.params as { mcpName: string };
|
|
4876
|
+
const result = await ext.credentialsGetMcp(mcpName);
|
|
4877
|
+
return result;
|
|
4878
|
+
});
|
|
4879
|
+
|
|
4880
|
+
app.post('/api/extensions/credentials/mcp', async (request) => {
|
|
4881
|
+
const body = request.body as Record<string, unknown>;
|
|
4882
|
+
const result = await ext.credentialsSaveMcp(
|
|
4883
|
+
body.mcpName as string,
|
|
4884
|
+
body.envValues as Record<string, string>
|
|
4885
|
+
);
|
|
4886
|
+
return result;
|
|
4887
|
+
});
|
|
4888
|
+
|
|
4889
|
+
app.get('/api/extensions/credentials/project-env', async (request) => {
|
|
4890
|
+
const projectPath = (request.query as Record<string, string>).projectPath;
|
|
4891
|
+
if (!projectPath) return { error: 'projectPath required' };
|
|
4892
|
+
const result = await ext.credentialsGetProjectEnv(projectPath);
|
|
4893
|
+
return result;
|
|
4894
|
+
});
|
|
4895
|
+
|
|
4896
|
+
app.post('/api/extensions/credentials/project-env', async (request) => {
|
|
4897
|
+
const body = request.body as Record<string, unknown>;
|
|
4898
|
+
const result = await ext.credentialsSaveProjectEnv(
|
|
4899
|
+
body.projectPath as string,
|
|
4900
|
+
body.vars as Record<string, string>
|
|
4901
|
+
);
|
|
4902
|
+
return result;
|
|
4903
|
+
});
|
|
4904
|
+
|
|
4905
|
+
app.post('/api/extensions/credentials/scan-required', async (request) => {
|
|
4906
|
+
const body = request.body as Record<string, unknown>;
|
|
4907
|
+
const result = await ext.credentialsScanRequired(
|
|
4908
|
+
body.projectPath as string,
|
|
4909
|
+
body.mcpServers as any,
|
|
4910
|
+
body.skillReqs as any
|
|
4911
|
+
);
|
|
4912
|
+
return result;
|
|
4913
|
+
});
|
|
4914
|
+
|
|
4915
|
+
app.get('/api/extensions/credentials/resolve-agent-env', async (request) => {
|
|
4916
|
+
const projectPath = (request.query as Record<string, string>).projectPath;
|
|
4917
|
+
if (!projectPath) return { error: 'projectPath required' };
|
|
4918
|
+
const result = await ext.credentialsResolveAgentEnv(projectPath);
|
|
4919
|
+
return result;
|
|
4920
|
+
});
|
|
4921
|
+
|
|
4922
|
+
app.get('/api/extensions/credentials/skill-env', async (request) => {
|
|
4923
|
+
const folderName = (request.query as Record<string, string>).folderName;
|
|
4924
|
+
if (!folderName) return { error: 'folderName required' };
|
|
4925
|
+
const result = await ext.credentialsGetSkillGlobalEnv(folderName);
|
|
4926
|
+
return result;
|
|
4927
|
+
});
|
|
4928
|
+
|
|
4929
|
+
app.post('/api/extensions/credentials/skill-env', async (request) => {
|
|
4930
|
+
const body = request.body as Record<string, unknown>;
|
|
4931
|
+
const result = await ext.credentialsSaveSkillGlobalEnv(
|
|
4932
|
+
body.folderName as string,
|
|
4933
|
+
body.vars as Record<string, string>
|
|
4934
|
+
);
|
|
4935
|
+
return result;
|
|
4936
|
+
});
|
|
4734
4937
|
|
|
4735
4938
|
app.setNotFoundHandler((request, reply) => {
|
|
4736
4939
|
const u = request.url;
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Facade service that combines plugin catalog + MCP catalog + installation state
|
|
3
|
+
* into enriched data ready for the renderer.
|
|
4
|
+
*
|
|
5
|
+
* Also provides install target resolution for the security model
|
|
6
|
+
* (main-side re-resolution: renderer sends pluginId/registryId, main resolves from catalog).
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { createLogger } from '@shared/utils/logger';
|
|
10
|
+
|
|
11
|
+
import { type McpCatalogAggregator } from './catalog/McpCatalogAggregator';
|
|
12
|
+
import { type PluginCatalogService } from './catalog/PluginCatalogService';
|
|
13
|
+
import { type McpInstallationStateService } from './state/McpInstallationStateService';
|
|
14
|
+
import { type PluginInstallationStateService } from './state/PluginInstallationStateService';
|
|
15
|
+
|
|
16
|
+
import type {
|
|
17
|
+
EnrichedPlugin,
|
|
18
|
+
InstalledMcpEntry,
|
|
19
|
+
McpCatalogItem,
|
|
20
|
+
McpSearchResult,
|
|
21
|
+
PluginCatalogItem,
|
|
22
|
+
} from '@shared/types/extensions';
|
|
23
|
+
|
|
24
|
+
const logger = createLogger('Extensions:Facade');
|
|
25
|
+
|
|
26
|
+
export class ExtensionFacadeService {
|
|
27
|
+
constructor(
|
|
28
|
+
private readonly pluginCatalog: PluginCatalogService,
|
|
29
|
+
private readonly pluginState: PluginInstallationStateService,
|
|
30
|
+
private readonly mcpAggregator: McpCatalogAggregator | null = null,
|
|
31
|
+
private readonly mcpState: McpInstallationStateService | null = null
|
|
32
|
+
) {}
|
|
33
|
+
|
|
34
|
+
// ── Plugin methods ───────────────────────────────────────────────────
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Get all plugins enriched with install status and counts.
|
|
38
|
+
*/
|
|
39
|
+
async getEnrichedPlugins(projectPath?: string, forceRefresh = false): Promise<EnrichedPlugin[]> {
|
|
40
|
+
const [catalog, installed, counts] = await Promise.all([
|
|
41
|
+
this.pluginCatalog.getPlugins(forceRefresh),
|
|
42
|
+
this.pluginState.getInstalledPlugins(projectPath),
|
|
43
|
+
this.pluginState.getInstallCounts(),
|
|
44
|
+
]);
|
|
45
|
+
|
|
46
|
+
// Build installed lookup: pluginId → entries[]
|
|
47
|
+
const installedMap = new Map<string, typeof installed>();
|
|
48
|
+
for (const entry of installed) {
|
|
49
|
+
const list = installedMap.get(entry.pluginId) ?? [];
|
|
50
|
+
list.push(entry);
|
|
51
|
+
installedMap.set(entry.pluginId, list);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
return catalog.map((item): EnrichedPlugin => {
|
|
55
|
+
const installations = installedMap.get(item.pluginId) ?? [];
|
|
56
|
+
const installCount = counts.get(item.pluginId) ?? 0;
|
|
57
|
+
|
|
58
|
+
return {
|
|
59
|
+
...item,
|
|
60
|
+
installCount,
|
|
61
|
+
isInstalled: installations.length > 0,
|
|
62
|
+
installations,
|
|
63
|
+
};
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Get README content for a plugin.
|
|
69
|
+
*/
|
|
70
|
+
async getPluginReadme(pluginId: string): Promise<string | null> {
|
|
71
|
+
return this.pluginCatalog.getPluginReadme(pluginId);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Resolve a pluginId to its install target.
|
|
76
|
+
*/
|
|
77
|
+
async resolvePluginInstallTarget(
|
|
78
|
+
pluginId: string
|
|
79
|
+
): Promise<{ qualifiedName: string; plugin: PluginCatalogItem } | null> {
|
|
80
|
+
const plugin = await this.pluginCatalog.resolvePlugin(pluginId);
|
|
81
|
+
if (!plugin) {
|
|
82
|
+
logger.warn(`Cannot resolve install target: pluginId "${pluginId}" not found in catalog`);
|
|
83
|
+
return null;
|
|
84
|
+
}
|
|
85
|
+
return { qualifiedName: plugin.qualifiedName, plugin };
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// ── MCP methods ──────────────────────────────────────────────────────
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Search MCP servers across both registries.
|
|
92
|
+
*/
|
|
93
|
+
async searchMcp(query: string, limit?: number): Promise<McpSearchResult> {
|
|
94
|
+
if (!this.mcpAggregator) {
|
|
95
|
+
return { servers: [], warnings: ['MCP catalog not configured'] };
|
|
96
|
+
}
|
|
97
|
+
return this.mcpAggregator.search(query, limit);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Browse MCP catalog with pagination.
|
|
102
|
+
*/
|
|
103
|
+
async browseMcp(
|
|
104
|
+
cursor?: string,
|
|
105
|
+
limit?: number
|
|
106
|
+
): Promise<{ servers: McpCatalogItem[]; nextCursor?: string }> {
|
|
107
|
+
if (!this.mcpAggregator) {
|
|
108
|
+
return { servers: [] };
|
|
109
|
+
}
|
|
110
|
+
return this.mcpAggregator.browse(cursor, limit);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Get a single MCP server by registry ID (for install flow).
|
|
115
|
+
*/
|
|
116
|
+
async getMcpById(registryId: string): Promise<McpCatalogItem | null> {
|
|
117
|
+
if (!this.mcpAggregator) return null;
|
|
118
|
+
return this.mcpAggregator.getById(registryId);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Get installed MCP servers.
|
|
123
|
+
*/
|
|
124
|
+
async getInstalledMcp(projectPath?: string): Promise<InstalledMcpEntry[]> {
|
|
125
|
+
if (!this.mcpState) return [];
|
|
126
|
+
return this.mcpState.getInstalled(projectPath);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// ── Cache invalidation ───────────────────────────────────────────────
|
|
130
|
+
|
|
131
|
+
invalidateInstalledCache(): void {
|
|
132
|
+
this.pluginState.invalidateCache();
|
|
133
|
+
this.mcpState?.invalidateCache();
|
|
134
|
+
}
|
|
135
|
+
}
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Fetches MCP server data from the Glama.ai API.
|
|
3
|
+
*
|
|
4
|
+
* Optional enrichment layer — NOT a hard dependency.
|
|
5
|
+
* Provides: license, tools, Glama URL.
|
|
6
|
+
* Does NOT provide install info (no packages/remotes).
|
|
7
|
+
*
|
|
8
|
+
* Base URL: https://glama.ai/api/mcp/v1/servers
|
|
9
|
+
* Cursor-based pagination (after), no auth required.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import http from 'node:http';
|
|
13
|
+
import https from 'node:https';
|
|
14
|
+
|
|
15
|
+
import { createLogger } from '@shared/utils/logger';
|
|
16
|
+
|
|
17
|
+
import type { McpCatalogItem, McpHostingType, McpToolDef } from '@shared/types/extensions';
|
|
18
|
+
|
|
19
|
+
const logger = createLogger('Extensions:GlamaMcp');
|
|
20
|
+
|
|
21
|
+
// ── Constants ──────────────────────────────────────────────────────────────
|
|
22
|
+
|
|
23
|
+
const GLAMA_BASE_URL = 'https://glama.ai/api/mcp/v1/servers';
|
|
24
|
+
const HTTP_TIMEOUT_MS = 15_000;
|
|
25
|
+
const MAX_REDIRECTS = 5;
|
|
26
|
+
const MAX_BODY_SIZE = 10 * 1024 * 1024; // 10MB safety limit
|
|
27
|
+
|
|
28
|
+
// ── HTTP helper ────────────────────────────────────────────────────────────
|
|
29
|
+
|
|
30
|
+
function httpGet(
|
|
31
|
+
url: string,
|
|
32
|
+
redirectsLeft = MAX_REDIRECTS
|
|
33
|
+
): Promise<{ statusCode: number; body: string }> {
|
|
34
|
+
return new Promise((resolve, reject) => {
|
|
35
|
+
const parsedUrl = new URL(url);
|
|
36
|
+
const transport = parsedUrl.protocol === 'http:' ? http : https;
|
|
37
|
+
let settled = false;
|
|
38
|
+
|
|
39
|
+
const settleResolve = (v: { statusCode: number; body: string }): void => {
|
|
40
|
+
if (!settled) {
|
|
41
|
+
settled = true;
|
|
42
|
+
resolve(v);
|
|
43
|
+
}
|
|
44
|
+
};
|
|
45
|
+
const settleReject = (e: Error): void => {
|
|
46
|
+
if (!settled) {
|
|
47
|
+
settled = true;
|
|
48
|
+
reject(e);
|
|
49
|
+
}
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
const req = transport.get(url, (res) => {
|
|
53
|
+
const status = res.statusCode ?? 0;
|
|
54
|
+
if (status >= 300 && status < 400 && res.headers.location) {
|
|
55
|
+
if (redirectsLeft <= 0) {
|
|
56
|
+
res.destroy();
|
|
57
|
+
settleReject(new Error('Too many redirects'));
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
res.destroy();
|
|
61
|
+
httpGet(new URL(res.headers.location, url).toString(), redirectsLeft - 1).then(
|
|
62
|
+
settleResolve,
|
|
63
|
+
settleReject
|
|
64
|
+
);
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
const chunks: Buffer[] = [];
|
|
68
|
+
let totalSize = 0;
|
|
69
|
+
res.on('data', (c: Buffer) => {
|
|
70
|
+
totalSize += c.length;
|
|
71
|
+
if (totalSize > MAX_BODY_SIZE) {
|
|
72
|
+
res.destroy(new Error(`Response body exceeds ${MAX_BODY_SIZE} bytes`));
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
chunks.push(c);
|
|
76
|
+
});
|
|
77
|
+
res.on('end', () =>
|
|
78
|
+
settleResolve({ statusCode: status, body: Buffer.concat(chunks).toString('utf-8') })
|
|
79
|
+
);
|
|
80
|
+
res.on('error', settleReject);
|
|
81
|
+
});
|
|
82
|
+
req.setTimeout(HTTP_TIMEOUT_MS, () => req.destroy(new Error(`Timeout fetching ${url}`)));
|
|
83
|
+
req.on('error', (e) => settleReject(e instanceof Error ? e : new Error(String(e))));
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// ── Raw Glama API shapes ───────────────────────────────────────────────────
|
|
88
|
+
|
|
89
|
+
interface GlamaResponse {
|
|
90
|
+
pageInfo: {
|
|
91
|
+
endCursor?: string;
|
|
92
|
+
hasNextPage?: boolean;
|
|
93
|
+
};
|
|
94
|
+
servers: GlamaServer[];
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
interface GlamaServer {
|
|
98
|
+
id: string;
|
|
99
|
+
name: string;
|
|
100
|
+
namespace?: string;
|
|
101
|
+
description?: string;
|
|
102
|
+
slug?: string;
|
|
103
|
+
url?: string;
|
|
104
|
+
repository?: { url: string };
|
|
105
|
+
spdxLicense?: { name: string; url?: string } | null;
|
|
106
|
+
tools?: { name?: string; description?: string }[];
|
|
107
|
+
attributes?: string[];
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// ── Service ────────────────────────────────────────────────────────────────
|
|
111
|
+
|
|
112
|
+
export class GlamaMcpEnrichmentService {
|
|
113
|
+
/**
|
|
114
|
+
* Search Glama for MCP servers.
|
|
115
|
+
*/
|
|
116
|
+
async search(query: string, limit = 20): Promise<McpCatalogItem[]> {
|
|
117
|
+
const params = new URLSearchParams({ search: query, first: String(limit) });
|
|
118
|
+
const url = `${GLAMA_BASE_URL}?${params}`;
|
|
119
|
+
|
|
120
|
+
try {
|
|
121
|
+
const resp = await httpGet(url);
|
|
122
|
+
if (resp.statusCode !== 200) throw new Error(`HTTP ${resp.statusCode}`);
|
|
123
|
+
const json = JSON.parse(resp.body) as GlamaResponse;
|
|
124
|
+
return json.servers.map((s) => this.normalize(s));
|
|
125
|
+
} catch (err) {
|
|
126
|
+
logger.warn('Glama MCP search failed:', err);
|
|
127
|
+
return [];
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Browse Glama catalog with cursor pagination.
|
|
133
|
+
*/
|
|
134
|
+
async browse(
|
|
135
|
+
cursor?: string,
|
|
136
|
+
limit = 20
|
|
137
|
+
): Promise<{ servers: McpCatalogItem[]; nextCursor?: string }> {
|
|
138
|
+
const params = new URLSearchParams({ first: String(limit) });
|
|
139
|
+
if (cursor) params.set('after', cursor);
|
|
140
|
+
const url = `${GLAMA_BASE_URL}?${params}`;
|
|
141
|
+
|
|
142
|
+
try {
|
|
143
|
+
const resp = await httpGet(url);
|
|
144
|
+
if (resp.statusCode !== 200) throw new Error(`HTTP ${resp.statusCode}`);
|
|
145
|
+
const json = JSON.parse(resp.body) as GlamaResponse;
|
|
146
|
+
return {
|
|
147
|
+
servers: json.servers.map((s) => this.normalize(s)),
|
|
148
|
+
nextCursor: json.pageInfo.hasNextPage ? json.pageInfo.endCursor : undefined,
|
|
149
|
+
};
|
|
150
|
+
} catch (err) {
|
|
151
|
+
logger.warn('Glama MCP browse failed:', err);
|
|
152
|
+
return { servers: [] };
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// ── Private ────────────────────────────────────────────────────────────
|
|
157
|
+
|
|
158
|
+
private normalize(raw: GlamaServer): McpCatalogItem {
|
|
159
|
+
const tools: McpToolDef[] = (raw.tools ?? [])
|
|
160
|
+
.filter((t): t is { name: string; description: string } => Boolean(t.name))
|
|
161
|
+
.map((t) => ({ name: t.name, description: t.description ?? '' }));
|
|
162
|
+
|
|
163
|
+
return {
|
|
164
|
+
id: `glama:${raw.id}`,
|
|
165
|
+
name: raw.name,
|
|
166
|
+
description: raw.description ?? '',
|
|
167
|
+
repositoryUrl: raw.repository?.url,
|
|
168
|
+
version: undefined, // Glama doesn't expose version
|
|
169
|
+
source: 'glama',
|
|
170
|
+
installSpec: null, // Glama has NO install info
|
|
171
|
+
envVars: [],
|
|
172
|
+
license: raw.spdxLicense?.name,
|
|
173
|
+
tools,
|
|
174
|
+
glamaUrl: raw.url,
|
|
175
|
+
requiresAuth: false,
|
|
176
|
+
author: raw.namespace,
|
|
177
|
+
hostingType: this.deriveHostingType(raw.attributes),
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
private deriveHostingType(attributes?: string[]): McpHostingType | undefined {
|
|
182
|
+
if (!attributes?.length) return undefined;
|
|
183
|
+
const hasLocal = attributes.includes('hosting:local-only');
|
|
184
|
+
const hasRemote = attributes.includes('hosting:remote-capable');
|
|
185
|
+
if (hasLocal && hasRemote) return 'both';
|
|
186
|
+
if (hasLocal) return 'local';
|
|
187
|
+
if (hasRemote) return 'remote';
|
|
188
|
+
return undefined;
|
|
189
|
+
}
|
|
190
|
+
}
|