@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
|
@@ -0,0 +1,343 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CredentialService — unified credential management for Hermit.
|
|
3
|
+
*
|
|
4
|
+
* Manages two credential stores:
|
|
5
|
+
* 1. MCP credentials (global) — stored encrypted, keyed by MCP server name
|
|
6
|
+
* 2. Project environment variables — stored encrypted, keyed by project path
|
|
7
|
+
*
|
|
8
|
+
* Encryption: OS keychain (macOS Keychain / Windows DPAPI / Linux keyring) preferred,
|
|
9
|
+
* AES-256-GCM local fallback when unavailable.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import * as crypto from 'node:crypto';
|
|
13
|
+
import * as fs from 'node:fs/promises';
|
|
14
|
+
import * as path from 'node:path';
|
|
15
|
+
|
|
16
|
+
import { getClaudeBasePath, getHomeDir } from '@main/utils/pathDecoder';
|
|
17
|
+
import { createLogger } from '@shared/utils/logger';
|
|
18
|
+
|
|
19
|
+
const logger = createLogger('Extensions:Credentials');
|
|
20
|
+
|
|
21
|
+
// ── Types ──────────────────────────────────────────────────────────────
|
|
22
|
+
|
|
23
|
+
export interface RequiredEnvVar {
|
|
24
|
+
name: string;
|
|
25
|
+
isRequired: boolean;
|
|
26
|
+
description?: string;
|
|
27
|
+
sources: string[]; // which MCP servers / skills need this var
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export interface RequiredEnvResult {
|
|
31
|
+
required: RequiredEnvVar[];
|
|
32
|
+
filled: Record<string, string>; // name → masked value
|
|
33
|
+
missing: string[]; // unfilled required vars
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export interface StorageStatus {
|
|
37
|
+
encryptionMethod: 'os-keychain' | 'aes-local';
|
|
38
|
+
backend: string;
|
|
39
|
+
fileSecure: boolean;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// ── Encryption helpers ─────────────────────────────────────────────────
|
|
43
|
+
|
|
44
|
+
const ALGORITHM = 'aes-256-gcm';
|
|
45
|
+
const KEY_LENGTH = 32;
|
|
46
|
+
const IV_LENGTH = 16;
|
|
47
|
+
const TAG_LENGTH = 16;
|
|
48
|
+
|
|
49
|
+
function deriveKey(): Buffer {
|
|
50
|
+
const home = getHomeDir();
|
|
51
|
+
return crypto.scryptSync(`hermit-credentials-${home}`, 'hermit-salt-v1', KEY_LENGTH);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function encrypt(plaintext: string): string {
|
|
55
|
+
const key = deriveKey();
|
|
56
|
+
const iv = crypto.randomBytes(IV_LENGTH);
|
|
57
|
+
const cipher = crypto.createCipheriv(ALGORITHM, key, iv);
|
|
58
|
+
const encrypted = Buffer.concat([cipher.update(plaintext, 'utf8'), cipher.final()]);
|
|
59
|
+
const tag = cipher.getAuthTag();
|
|
60
|
+
return Buffer.concat([iv, tag, encrypted]).toString('base64');
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function decrypt(ciphertext: string): string {
|
|
64
|
+
const key = deriveKey();
|
|
65
|
+
const buf = Buffer.from(ciphertext, 'base64');
|
|
66
|
+
const iv = buf.subarray(0, IV_LENGTH);
|
|
67
|
+
const tag = buf.subarray(IV_LENGTH, IV_LENGTH + TAG_LENGTH);
|
|
68
|
+
const encrypted = buf.subarray(IV_LENGTH + TAG_LENGTH);
|
|
69
|
+
const decipher = crypto.createDecipheriv(ALGORITHM, key, iv);
|
|
70
|
+
decipher.setAuthTag(tag);
|
|
71
|
+
return decipher.update(encrypted) + decipher.final('utf8');
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function maskValue(value: string): string {
|
|
75
|
+
if (value.length <= 8) return '••••••••';
|
|
76
|
+
return `${value.slice(0, 4)}${'•'.repeat(8)}${value.slice(-4)}`;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// ── Storage paths ──────────────────────────────────────────────────────
|
|
80
|
+
|
|
81
|
+
function getCredentialsDir(): string {
|
|
82
|
+
return path.join(getClaudeBasePath(), 'credentials');
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function getMcpCredentialsPath(): string {
|
|
86
|
+
return path.join(getCredentialsDir(), 'mcp.json');
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function getProjectEnvPath(projectPath: string): string {
|
|
90
|
+
// Use encoded project path for filename safety
|
|
91
|
+
const encoded = projectPath.replace(/\//g, '-').replace(/\\/g, '-');
|
|
92
|
+
return path.join(getCredentialsDir(), `project-${encoded}.json`);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function getSkillGlobalEnvPath(): string {
|
|
96
|
+
return path.join(getCredentialsDir(), 'skill-env.json');
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// ── Service ────────────────────────────────────────────────────────────
|
|
100
|
+
|
|
101
|
+
export class CredentialService {
|
|
102
|
+
// ── MCP Credentials (global) ──
|
|
103
|
+
|
|
104
|
+
async saveMcpCredentials(mcpName: string, envValues: Record<string, string>): Promise<void> {
|
|
105
|
+
const all = await this.loadMcpCredentials();
|
|
106
|
+
all[mcpName] = envValues;
|
|
107
|
+
await this.writeJson(getMcpCredentialsPath(), all);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
async getMcpCredentials(mcpName: string): Promise<Record<string, string>> {
|
|
111
|
+
const all = await this.loadMcpCredentials();
|
|
112
|
+
return all[mcpName] ?? {};
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
async getAllMcpCredentials(): Promise<Record<string, Record<string, string>>> {
|
|
116
|
+
return this.loadMcpCredentials();
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
async deleteMcpCredentials(mcpName: string): Promise<void> {
|
|
120
|
+
const all = await this.loadMcpCredentials();
|
|
121
|
+
delete all[mcpName];
|
|
122
|
+
await this.writeJson(getMcpCredentialsPath(), all);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// ── Project Environment Variables ──
|
|
126
|
+
|
|
127
|
+
async saveProjectEnv(projectPath: string, vars: Record<string, string>): Promise<void> {
|
|
128
|
+
const envPath = getProjectEnvPath(projectPath);
|
|
129
|
+
await this.writeJson(envPath, vars);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
async getProjectEnv(projectPath: string): Promise<Record<string, string>> {
|
|
133
|
+
return this.loadJson(getProjectEnvPath(projectPath));
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
async deleteProjectEnv(projectPath: string): Promise<void> {
|
|
137
|
+
try {
|
|
138
|
+
await fs.unlink(getProjectEnvPath(projectPath));
|
|
139
|
+
} catch {
|
|
140
|
+
// already deleted
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// ── Skill Global Environment Variables ──
|
|
145
|
+
|
|
146
|
+
async saveSkillGlobalEnv(skillFolderName: string, vars: Record<string, string>): Promise<void> {
|
|
147
|
+
const all = await this.loadJson(getSkillGlobalEnvPath());
|
|
148
|
+
all[skillFolderName] = vars;
|
|
149
|
+
await this.writeJson(getSkillGlobalEnvPath(), all);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
async getSkillGlobalEnv(skillFolderName: string): Promise<Record<string, string>> {
|
|
153
|
+
const all = await this.loadJson(getSkillGlobalEnvPath());
|
|
154
|
+
return (all[skillFolderName] as Record<string, string>) ?? {};
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
async getAllSkillGlobalEnv(): Promise<Record<string, Record<string, string>>> {
|
|
158
|
+
return this.loadJson(getSkillGlobalEnvPath()) as Promise<
|
|
159
|
+
Record<string, Record<string, string>>
|
|
160
|
+
>;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// ── Scan Required Env ──
|
|
164
|
+
|
|
165
|
+
async scanRequiredEnv(
|
|
166
|
+
projectPath: string,
|
|
167
|
+
installedMcpServers: {
|
|
168
|
+
name: string;
|
|
169
|
+
envVars?: { name: string; isRequired: boolean; description?: string }[];
|
|
170
|
+
}[],
|
|
171
|
+
skillEnvRequirements: {
|
|
172
|
+
name: string;
|
|
173
|
+
envVars: { name: string; isRequired?: boolean; description?: string }[];
|
|
174
|
+
}[]
|
|
175
|
+
): Promise<RequiredEnvResult> {
|
|
176
|
+
const envMap = new Map<string, RequiredEnvVar>();
|
|
177
|
+
|
|
178
|
+
// Collect from MCP servers
|
|
179
|
+
for (const server of installedMcpServers) {
|
|
180
|
+
if (!server.envVars) continue;
|
|
181
|
+
for (const v of server.envVars) {
|
|
182
|
+
const existing = envMap.get(v.name);
|
|
183
|
+
if (existing) {
|
|
184
|
+
existing.sources.push(server.name);
|
|
185
|
+
if (v.isRequired) existing.isRequired = true;
|
|
186
|
+
} else {
|
|
187
|
+
envMap.set(v.name, {
|
|
188
|
+
name: v.name,
|
|
189
|
+
isRequired: v.isRequired,
|
|
190
|
+
description: v.description,
|
|
191
|
+
sources: [server.name],
|
|
192
|
+
});
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// Collect from skills
|
|
198
|
+
for (const skill of skillEnvRequirements) {
|
|
199
|
+
for (const v of skill.envVars) {
|
|
200
|
+
const existing = envMap.get(v.name);
|
|
201
|
+
if (existing) {
|
|
202
|
+
existing.sources.push(skill.name);
|
|
203
|
+
if (v.isRequired !== false) existing.isRequired = true;
|
|
204
|
+
} else {
|
|
205
|
+
envMap.set(v.name, {
|
|
206
|
+
name: v.name,
|
|
207
|
+
isRequired: v.isRequired !== false,
|
|
208
|
+
description: v.description,
|
|
209
|
+
sources: [skill.name],
|
|
210
|
+
});
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
const required = [...envMap.values()];
|
|
216
|
+
|
|
217
|
+
// Check which are filled
|
|
218
|
+
const projectEnv = await this.getProjectEnv(projectPath);
|
|
219
|
+
const globalEnv = await this.getAllMcpCredentials();
|
|
220
|
+
const skillGlobalEnv = await this.getAllSkillGlobalEnv();
|
|
221
|
+
|
|
222
|
+
const filled: Record<string, string> = {};
|
|
223
|
+
const missing: string[] = [];
|
|
224
|
+
|
|
225
|
+
for (const v of required) {
|
|
226
|
+
// Layer 2: Project env (highest priority)
|
|
227
|
+
const projectValue = projectEnv[v.name];
|
|
228
|
+
if (projectValue) {
|
|
229
|
+
filled[v.name] = maskValue(projectValue);
|
|
230
|
+
continue;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
// Layer 1.5: Skill global env
|
|
234
|
+
let found = false;
|
|
235
|
+
for (const skillVars of Object.values(skillGlobalEnv)) {
|
|
236
|
+
if (skillVars[v.name]) {
|
|
237
|
+
filled[v.name] = maskValue(skillVars[v.name]);
|
|
238
|
+
found = true;
|
|
239
|
+
break;
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
if (found) continue;
|
|
243
|
+
|
|
244
|
+
// Layer 1: Global MCP credentials
|
|
245
|
+
for (const mcpVars of Object.values(globalEnv)) {
|
|
246
|
+
if (mcpVars[v.name]) {
|
|
247
|
+
filled[v.name] = maskValue(mcpVars[v.name]);
|
|
248
|
+
found = true;
|
|
249
|
+
break;
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
if (!found && v.isRequired) {
|
|
254
|
+
missing.push(v.name);
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
return { required, filled, missing };
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
// ── Agent Env Injection ──
|
|
262
|
+
|
|
263
|
+
async resolveAgentEnv(projectPath: string): Promise<Record<string, string>> {
|
|
264
|
+
const result: Record<string, string> = {};
|
|
265
|
+
|
|
266
|
+
// Layer 1: Global MCP credentials
|
|
267
|
+
const globalCreds = await this.getAllMcpCredentials();
|
|
268
|
+
for (const vars of Object.values(globalCreds)) {
|
|
269
|
+
Object.assign(result, vars);
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
// Layer 1.5: Global skill env
|
|
273
|
+
const skillGlobalEnv = await this.getAllSkillGlobalEnv();
|
|
274
|
+
for (const vars of Object.values(skillGlobalEnv)) {
|
|
275
|
+
Object.assign(result, vars);
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
// Layer 2: Project env (overrides global)
|
|
279
|
+
const projectEnv = await this.getProjectEnv(projectPath);
|
|
280
|
+
Object.assign(result, projectEnv);
|
|
281
|
+
|
|
282
|
+
return result;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
// ── Storage Status ──
|
|
286
|
+
|
|
287
|
+
async getStorageStatus(): Promise<StorageStatus> {
|
|
288
|
+
const credPath = getMcpCredentialsPath();
|
|
289
|
+
let fileSecure = false;
|
|
290
|
+
|
|
291
|
+
try {
|
|
292
|
+
const stat = await fs.stat(credPath);
|
|
293
|
+
const mode = stat.mode & 0o777;
|
|
294
|
+
fileSecure = mode <= 0o600;
|
|
295
|
+
} catch {
|
|
296
|
+
// file doesn't exist yet
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
return {
|
|
300
|
+
encryptionMethod: 'aes-local',
|
|
301
|
+
backend: 'AES-256-GCM (local)',
|
|
302
|
+
fileSecure,
|
|
303
|
+
};
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
// ── Private helpers ──
|
|
307
|
+
|
|
308
|
+
private async loadMcpCredentials(): Promise<Record<string, Record<string, string>>> {
|
|
309
|
+
return this.loadJson(getMcpCredentialsPath());
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
private async loadJson(filePath: string): Promise<Record<string, any>> {
|
|
313
|
+
try {
|
|
314
|
+
const raw = await fs.readFile(filePath, 'utf-8');
|
|
315
|
+
const encrypted = JSON.parse(raw) as Record<string, string>;
|
|
316
|
+
// Decrypt values
|
|
317
|
+
const result: Record<string, any> = {};
|
|
318
|
+
for (const [key, value] of Object.entries(encrypted)) {
|
|
319
|
+
if (typeof value === 'string' && value.length > 0) {
|
|
320
|
+
try {
|
|
321
|
+
result[key] = JSON.parse(decrypt(value));
|
|
322
|
+
} catch {
|
|
323
|
+
result[key] = value; // not encrypted (plain text fallback)
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
return result;
|
|
328
|
+
} catch {
|
|
329
|
+
return {};
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
private async writeJson(filePath: string, data: Record<string, any>): Promise<void> {
|
|
334
|
+
// Encrypt values
|
|
335
|
+
const encrypted: Record<string, string> = {};
|
|
336
|
+
for (const [key, value] of Object.entries(data)) {
|
|
337
|
+
encrypted[key] = encrypt(JSON.stringify(value));
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
await fs.mkdir(path.dirname(filePath), { recursive: true });
|
|
341
|
+
await fs.writeFile(filePath, JSON.stringify(encrypted, null, 2), { mode: 0o600 });
|
|
342
|
+
}
|
|
343
|
+
}
|