@yancyyu/openhermit 1.6.29 → 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 (152) hide show
  1. package/dist-renderer/assets/{ProjectEditorOverlay-CQm6jUR1.js → ProjectEditorOverlay-DsQt4FHy.js} +1 -1
  2. package/dist-renderer/assets/{TeamGraphOverlay-h0WDfifv.js → TeamGraphOverlay-BjZC53xf.js} +1 -1
  3. package/dist-renderer/assets/{_basePickBy-CgG_tjgX.js → _basePickBy-CrWocIjq.js} +1 -1
  4. package/dist-renderer/assets/{_baseUniq-DwPTU9lP.js → _baseUniq-B6d8ysWi.js} +1 -1
  5. package/dist-renderer/assets/{arc-7nIrGRzY.js → arc-DAIYCFP8.js} +1 -1
  6. package/dist-renderer/assets/{architectureDiagram-VXUJARFQ-BYhA6Ev2.js → architectureDiagram-VXUJARFQ-B3UudXJh.js} +1 -1
  7. package/dist-renderer/assets/{blockDiagram-VD42YOAC-BVpZUGDg.js → blockDiagram-VD42YOAC-DbptKQ4W.js} +1 -1
  8. package/dist-renderer/assets/{c4Diagram-YG6GDRKO-DsdreMQ9.js → c4Diagram-YG6GDRKO-C4WQuZpV.js} +1 -1
  9. package/dist-renderer/assets/channel-DbjZvWii.js +1 -0
  10. package/dist-renderer/assets/{chunk-4BX2VUAB-CcoAs7Jd.js → chunk-4BX2VUAB-Dp7fVpI_.js} +1 -1
  11. package/dist-renderer/assets/{chunk-55IACEB6-CGGAOoXd.js → chunk-55IACEB6-B8KGfbAy.js} +1 -1
  12. package/dist-renderer/assets/{chunk-B4BG7PRW-FhpTEPvD.js → chunk-B4BG7PRW-BG1oJrjA.js} +1 -1
  13. package/dist-renderer/assets/{chunk-DI55MBZ5-DoYySbm1.js → chunk-DI55MBZ5-DRmxNjht.js} +1 -1
  14. package/dist-renderer/assets/{chunk-FMBD7UC4-e9l2tGHG.js → chunk-FMBD7UC4-D6VLvy16.js} +1 -1
  15. package/dist-renderer/assets/{chunk-QN33PNHL-DeiXVTCy.js → chunk-QN33PNHL-DZou1667.js} +1 -1
  16. package/dist-renderer/assets/{chunk-QZHKN3VN-DC2UJLJM.js → chunk-QZHKN3VN-CghmasSh.js} +1 -1
  17. package/dist-renderer/assets/{chunk-TZMSLE5B-BHFD9eZI.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-BdybQraU.js → cose-bilkent-S5V4N54A-05e5uQDp.js} +1 -1
  22. package/dist-renderer/assets/{dagre-6UL2VRFP-DdF3pwM3.js → dagre-6UL2VRFP-B06bRykF.js} +1 -1
  23. package/dist-renderer/assets/{diagram-PSM6KHXK-B9Ldd3nh.js → diagram-PSM6KHXK-CY7VYQ7c.js} +1 -1
  24. package/dist-renderer/assets/{diagram-QEK2KX5R-XEqkrbpu.js → diagram-QEK2KX5R-BjKEH7dD.js} +1 -1
  25. package/dist-renderer/assets/{diagram-S2PKOQOG-CipwtY59.js → diagram-S2PKOQOG-Bf4ELS1_.js} +1 -1
  26. package/dist-renderer/assets/{erDiagram-Q2GNP2WA-BB-2ISGo.js → erDiagram-Q2GNP2WA-DJ753_L9.js} +1 -1
  27. package/dist-renderer/assets/{flowDiagram-NV44I4VS-B8XmJ0u2.js → flowDiagram-NV44I4VS-B71S-lC-.js} +1 -1
  28. package/dist-renderer/assets/{ganttDiagram-JELNMOA3-D-8XglBb.js → ganttDiagram-JELNMOA3-C_U42mSZ.js} +1 -1
  29. package/dist-renderer/assets/{gitGraphDiagram-V2S2FVAM-DL4ChakD.js → gitGraphDiagram-V2S2FVAM-DKUJU4Ns.js} +1 -1
  30. package/dist-renderer/assets/{graph-BiFNoBjP.js → graph-DY3qbzqj.js} +1 -1
  31. package/dist-renderer/assets/{index-BowUl0Jb.js → index-BlOrAXp3.js} +542 -532
  32. package/dist-renderer/assets/{index-6m1ZAymG.js → index-Bs27J5gB.js} +1 -1
  33. package/dist-renderer/assets/{index-Dp3kJTEe.js → index-C8B_nKOF.js} +1 -1
  34. package/dist-renderer/assets/index-CmZPUEhS.css +1 -0
  35. package/dist-renderer/assets/{index-TOpt_T7A.js → index-DLKyDr4T.js} +1 -1
  36. package/dist-renderer/assets/{index-qNBNjW4K.js → index-Dhsk3_DD.js} +1 -1
  37. package/dist-renderer/assets/{index-vAykq1H1.js → index-GpUvV2xs.js} +1 -1
  38. package/dist-renderer/assets/{infoDiagram-HS3SLOUP-DRIBfHDi.js → infoDiagram-HS3SLOUP-BNs0y3IG.js} +1 -1
  39. package/dist-renderer/assets/{journeyDiagram-XKPGCS4Q-BOMiigU4.js → journeyDiagram-XKPGCS4Q-CqPnw4UV.js} +1 -1
  40. package/dist-renderer/assets/{kanban-definition-3W4ZIXB7-DDxeyjod.js → kanban-definition-3W4ZIXB7-SLlzcUJ2.js} +1 -1
  41. package/dist-renderer/assets/{layout-DNANbrI4.js → layout-BZLlNmbr.js} +1 -1
  42. package/dist-renderer/assets/{linear-DxEJi1yT.js → linear-qz6v45xy.js} +1 -1
  43. package/dist-renderer/assets/{mindmap-definition-VGOIOE7T-nBfGriW8.js → mindmap-definition-VGOIOE7T-B1-kmEWV.js} +1 -1
  44. package/dist-renderer/assets/{pieDiagram-ADFJNKIX-Din5j6sV.js → pieDiagram-ADFJNKIX-B8a02iNx.js} +1 -1
  45. package/dist-renderer/assets/{quadrantDiagram-AYHSOK5B-DMVK2BEQ.js → quadrantDiagram-AYHSOK5B-BKv1Xfou.js} +1 -1
  46. package/dist-renderer/assets/{requirementDiagram-UZGBJVZJ-6SC94Gg_.js → requirementDiagram-UZGBJVZJ-B3DUpZi2.js} +1 -1
  47. package/dist-renderer/assets/{sankeyDiagram-TZEHDZUN-CD2gghhu.js → sankeyDiagram-TZEHDZUN-DmPzuTsy.js} +1 -1
  48. package/dist-renderer/assets/{sequenceDiagram-WL72ISMW-BnhkN7nZ.js → sequenceDiagram-WL72ISMW-Bo7RelRb.js} +1 -1
  49. package/dist-renderer/assets/{stateDiagram-FKZM4ZOC-Bn8XdYX-.js → stateDiagram-FKZM4ZOC-1epX98gV.js} +1 -1
  50. package/dist-renderer/assets/{stateDiagram-v2-4FDKWEC3-1b6sI1_g.js → stateDiagram-v2-4FDKWEC3-03Ym9PTr.js} +1 -1
  51. package/dist-renderer/assets/{timeline-definition-IT6M3QCI-CNs3RPoa.js → timeline-definition-IT6M3QCI-r6isC62H.js} +1 -1
  52. package/dist-renderer/assets/treemap-GDKQZRPO-CGKpOUF2.js +162 -0
  53. package/dist-renderer/assets/{xychartDiagram-PRI3JC2R-B8o5J2f3.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 +209 -6
  58. package/src/main/services/extensions/ExtensionFacadeService.ts +135 -0
  59. package/src/main/services/extensions/catalog/GlamaMcpEnrichmentService.ts +190 -0
  60. package/src/main/services/extensions/catalog/McpCatalogAggregator.ts +150 -0
  61. package/src/main/services/extensions/catalog/OfficialMcpRegistryService.ts +381 -0
  62. package/src/main/services/extensions/catalog/PluginCatalogService.ts +392 -0
  63. package/src/main/services/extensions/credentials/CredentialService.ts +343 -0
  64. package/src/main/services/extensions/install/McpInstallService.ts +407 -0
  65. package/src/main/services/extensions/install/PluginInstallService.ts +198 -0
  66. package/src/main/services/extensions/runtime/ClaudeCodeAdapter.ts +199 -0
  67. package/src/main/services/extensions/runtime/CodexAdapter.ts +100 -0
  68. package/src/main/services/extensions/runtime/CursorAdapter.ts +154 -0
  69. package/src/main/services/extensions/runtime/ExtensionsRuntimeAdapter.ts +172 -0
  70. package/src/main/services/extensions/runtime/GeminiAdapter.ts +91 -0
  71. package/src/main/services/extensions/runtime/HarnessInstallAdapter.ts +49 -0
  72. package/src/main/services/extensions/runtime/McpConfigStateReader.ts +209 -0
  73. package/src/main/services/extensions/runtime/OpenCodeAdapter.ts +91 -0
  74. package/src/main/services/extensions/runtime/adapterRegistry.ts +54 -0
  75. package/src/main/services/extensions/runtime/mcpDiagnosticsParser.ts +214 -0
  76. package/src/main/services/extensions/runtime/mcpRuntimeJson.ts +45 -0
  77. package/src/main/services/extensions/skills/SkillImportService.ts +155 -0
  78. package/src/main/services/extensions/skills/SkillMetadataParser.ts +323 -0
  79. package/src/main/services/extensions/skills/SkillPlanService.ts +411 -0
  80. package/src/main/services/extensions/skills/SkillReviewService.ts +73 -0
  81. package/src/main/services/extensions/skills/SkillRootsResolver.ts +49 -0
  82. package/src/main/services/extensions/skills/SkillScaffoldService.ts +89 -0
  83. package/src/main/services/extensions/skills/SkillScanner.ts +117 -0
  84. package/src/main/services/extensions/skills/SkillValidator.ts +69 -0
  85. package/src/main/services/extensions/skills/SkillsCatalogService.ts +92 -0
  86. package/src/main/services/extensions/skills/SkillsMutationService.ts +146 -0
  87. package/src/main/services/extensions/skills/SkillsWatcherService.ts +134 -0
  88. package/src/main/services/extensions/state/McpInstallationStateService.ts +42 -0
  89. package/src/main/services/extensions/state/PluginInstallationStateService.ts +281 -0
  90. package/src/main/services/identity/AgentTeamsIdentityStore.ts +218 -0
  91. package/src/main/services/runtime/providerAwareCliEnv.ts +60 -0
  92. package/src/main/services/team/ClaudeBinaryResolver.ts +469 -0
  93. package/src/main/services/team/ClaudeDoctorProbe.ts +0 -0
  94. package/src/main/services/team/cliFlavor.ts +54 -0
  95. package/src/main/services/teams-mvp/TaskDispatchService.ts +3 -0
  96. package/src/main/utils/atomicWrite.ts +72 -0
  97. package/src/main/utils/childProcess.ts +554 -0
  98. package/src/main/utils/cliEnv.ts +54 -0
  99. package/src/main/utils/cliPathMerge.ts +97 -0
  100. package/src/main/utils/pathDecoder.ts +664 -0
  101. package/src/main/utils/pathValidation.ts +432 -0
  102. package/src/main/utils/shellEnv.ts +331 -0
  103. package/src/renderer/api/httpClient.ts +61 -0
  104. package/src/renderer/components/extensions/ExtensionStoreView.tsx +59 -34
  105. package/src/renderer/components/extensions/ExtensionsSubTabTrigger.tsx +1 -1
  106. package/src/renderer/components/extensions/common/ExtensionToast.tsx +141 -0
  107. package/src/renderer/components/extensions/common/HarnessSelector.tsx +71 -0
  108. package/src/renderer/components/extensions/env/EnvVarPanel.tsx +335 -0
  109. package/src/renderer/components/extensions/env/ProjectEnvPanel.tsx +239 -0
  110. package/src/renderer/components/extensions/mcp/CustomMcpServerDialog.tsx +14 -223
  111. package/src/renderer/components/extensions/mcp/McpServerDetailDialog.tsx +11 -0
  112. package/src/renderer/components/extensions/mcp/McpServersPanel.tsx +51 -1
  113. package/src/renderer/components/extensions/skills/SkillsPanel.tsx +1 -126
  114. package/src/renderer/components/settings/sections/HarnessSection.tsx +2 -6
  115. package/src/renderer/components/settings/sections/TaskBusSection.tsx +17 -7
  116. package/src/renderer/components/sidebar/SidebarSessions.tsx +23 -0
  117. package/src/renderer/components/sidebar/WorkspaceBrowser.tsx +1 -7
  118. package/src/renderer/components/team/HarnessSelect.tsx +71 -0
  119. package/src/renderer/components/team/TeamDetailView.tsx +35 -0
  120. package/src/renderer/components/team/dialogs/CreateTeamDialog.tsx +21 -12
  121. package/src/renderer/components/team/dialogs/EditTeamDialog.tsx +8 -13
  122. package/src/renderer/components/team/kanban/KanbanBoard.tsx +26 -64
  123. package/src/renderer/components/team/messages/MessagesPanel.tsx +28 -24
  124. package/src/renderer/components/terminal/TerminalPanel.tsx +156 -0
  125. package/src/renderer/hooks/useExtensionsTabState.ts +2 -2
  126. package/src/renderer/store/slices/extensionsSlice.ts +42 -107
  127. package/src/renderer/store/slices/teamSlice.ts +8 -2
  128. package/src/shared/types/api.ts +29 -0
  129. package/src/shared/types/extensions/index.ts +1 -0
  130. package/src/shared/types/extensions/mcp.ts +2 -0
  131. package/src/shared/types/extensions/plugin.ts +2 -1
  132. package/src/shared/types/extensions/skill.ts +7 -0
  133. package/src/shared/utils/providerExtensionCapabilities.ts +1 -1
  134. package/dist-renderer/assets/channel-C0SqeFU7.js +0 -1
  135. package/dist-renderer/assets/classDiagram-2ON5EDUG-DWew1HpM.js +0 -1
  136. package/dist-renderer/assets/classDiagram-v2-WZHVMYZB-DWew1HpM.js +0 -1
  137. package/dist-renderer/assets/clone-Dm-k63Yr.js +0 -1
  138. package/dist-renderer/assets/index-BhellmRb.css +0 -1
  139. package/dist-renderer/assets/treemap-GDKQZRPO-DU_yr827.js +0 -162
  140. package/src/features/recent-projects/main/adapters/input/http/registerRecentProjectsHttp.ts +0 -30
  141. package/src/features/recent-projects/main/adapters/output/presenters/DashboardRecentProjectsPresenter.ts +0 -27
  142. package/src/features/recent-projects/main/adapters/output/sources/ClaudeRecentProjectsSourceAdapter.ts +0 -91
  143. package/src/features/recent-projects/main/adapters/output/sources/CodexRecentProjectsSourceAdapter.ts +0 -326
  144. package/src/features/recent-projects/main/composition/createRecentProjectsFeature.ts +0 -43
  145. package/src/features/recent-projects/main/index.ts +0 -3
  146. package/src/features/recent-projects/main/infrastructure/cache/InMemoryRecentProjectsCache.ts +0 -34
  147. package/src/features/recent-projects/main/infrastructure/codex/CodexAppServerClient.ts +0 -116
  148. package/src/features/recent-projects/main/infrastructure/identity/RecentProjectIdentityResolver.ts +0 -20
  149. package/src/features/recent-projects/main/infrastructure/identity/normalizeIdentityPath.ts +0 -10
  150. package/src/renderer/components/extensions/apikeys/ApiKeyCard.tsx +0 -143
  151. package/src/renderer/components/extensions/apikeys/ApiKeyFormDialog.tsx +0 -282
  152. package/src/renderer/components/extensions/apikeys/ApiKeysPanel.tsx +0 -280
@@ -1,10 +0,0 @@
1
- import path from 'path';
2
-
3
- export function normalizeIdentityPath(projectPath: string): string {
4
- let normalized = path.normalize(projectPath);
5
- while (normalized.length > 1 && (normalized.endsWith('/') || normalized.endsWith('\\'))) {
6
- normalized = normalized.slice(0, -1);
7
- }
8
-
9
- return process.platform === 'win32' ? normalized.toLowerCase() : normalized;
10
- }
@@ -1,143 +0,0 @@
1
- /**
2
- * ApiKeyCard — displays a single API key entry with edit/delete controls.
3
- */
4
-
5
- import { useState } from 'react';
6
-
7
- import { Badge } from '@renderer/components/ui/badge';
8
- import { Button } from '@renderer/components/ui/button';
9
- import {
10
- Tooltip,
11
- TooltipContent,
12
- TooltipProvider,
13
- TooltipTrigger,
14
- } from '@renderer/components/ui/tooltip';
15
- import { useStore } from '@renderer/store';
16
- import { Check, Copy, Pencil, Trash2 } from 'lucide-react';
17
-
18
- import type { ApiKeyEntry } from '@shared/types/extensions';
19
-
20
- interface ApiKeyCardProps {
21
- apiKey: ApiKeyEntry;
22
- onEdit: (key: ApiKeyEntry) => void;
23
- }
24
-
25
- export const ApiKeyCard = ({ apiKey, onEdit }: ApiKeyCardProps): React.JSX.Element => {
26
- const deleteApiKey = useStore((s) => s.deleteApiKey);
27
- const [copied, setCopied] = useState(false);
28
- const [confirmDelete, setConfirmDelete] = useState(false);
29
-
30
- const handleCopyEnvVar = async (): Promise<void> => {
31
- await navigator.clipboard.writeText(apiKey.envVarName);
32
- setCopied(true);
33
- setTimeout(() => setCopied(false), 1500);
34
- };
35
-
36
- const handleDelete = (): void => {
37
- if (!confirmDelete) {
38
- setConfirmDelete(true);
39
- setTimeout(() => setConfirmDelete(false), 3000);
40
- return;
41
- }
42
- void deleteApiKey(apiKey.id).catch(() => undefined);
43
- setConfirmDelete(false);
44
- };
45
-
46
- const createdDate = new Date(apiKey.createdAt).toLocaleDateString(undefined, {
47
- month: 'short',
48
- day: 'numeric',
49
- year: 'numeric',
50
- });
51
-
52
- return (
53
- <div className="flex flex-col gap-2.5 rounded-lg border border-border bg-surface p-4 transition-colors hover:border-border-emphasis">
54
- {/* Name + scope badge */}
55
- <div className="flex items-start justify-between gap-2">
56
- <h3 className="truncate text-sm font-medium text-text">{apiKey.name}</h3>
57
- <Badge
58
- variant="outline"
59
- className={
60
- apiKey.scope === 'user'
61
- ? 'border-blue-500/30 bg-blue-500/10 text-blue-400'
62
- : 'border-purple-500/30 bg-purple-500/10 text-purple-400'
63
- }
64
- >
65
- {apiKey.scope}
66
- </Badge>
67
- </div>
68
-
69
- {apiKey.scope === 'project' && apiKey.projectPath && (
70
- <p className="truncate text-xs text-text-muted" title={apiKey.projectPath}>
71
- {apiKey.projectPath}
72
- </p>
73
- )}
74
-
75
- {/* Env var name */}
76
- <div className="flex items-center gap-1.5">
77
- <code className="rounded bg-surface-raised px-1.5 py-0.5 text-xs text-blue-400">
78
- {apiKey.envVarName}
79
- </code>
80
- <TooltipProvider>
81
- <Tooltip>
82
- <TooltipTrigger asChild>
83
- <Button
84
- variant="ghost"
85
- size="icon"
86
- className="size-5"
87
- onClick={() => void handleCopyEnvVar()}
88
- >
89
- {copied ? (
90
- <Check className="size-3 text-emerald-400" />
91
- ) : (
92
- <Copy className="size-3 text-text-muted" />
93
- )}
94
- </Button>
95
- </TooltipTrigger>
96
- <TooltipContent>{copied ? 'Copied!' : 'Copy env var name'}</TooltipContent>
97
- </Tooltip>
98
- </TooltipProvider>
99
- </div>
100
-
101
- {/* Masked value */}
102
- <p className="font-mono text-xs text-text-muted">{apiKey.maskedValue}</p>
103
-
104
- {/* Footer: date + actions */}
105
- <div className="flex items-center justify-between pt-1">
106
- <span className="text-xs text-text-muted">{createdDate}</span>
107
- <div className="flex items-center gap-1">
108
- <TooltipProvider>
109
- <Tooltip>
110
- <TooltipTrigger asChild>
111
- <Button
112
- variant="ghost"
113
- size="icon"
114
- className="size-7"
115
- onClick={() => onEdit(apiKey)}
116
- >
117
- <Pencil className="size-3.5 text-text-muted" />
118
- </Button>
119
- </TooltipTrigger>
120
- <TooltipContent>Edit</TooltipContent>
121
- </Tooltip>
122
- </TooltipProvider>
123
-
124
- <TooltipProvider>
125
- <Tooltip>
126
- <TooltipTrigger asChild>
127
- <Button
128
- variant="ghost"
129
- size="icon"
130
- className={`size-7 ${confirmDelete ? 'text-red-400 hover:bg-red-500/10' : 'text-text-muted'}`}
131
- onClick={handleDelete}
132
- >
133
- <Trash2 className="size-3.5" />
134
- </Button>
135
- </TooltipTrigger>
136
- <TooltipContent>{confirmDelete ? 'Click again to confirm' : 'Delete'}</TooltipContent>
137
- </Tooltip>
138
- </TooltipProvider>
139
- </div>
140
- </div>
141
- </div>
142
- );
143
- };
@@ -1,282 +0,0 @@
1
- /**
2
- * ApiKeyFormDialog — create or edit an API key entry.
3
- * Edit mode pre-fills all fields except the value (which must be re-entered).
4
- */
5
-
6
- import { useEffect, useState } from 'react';
7
-
8
- import { Button } from '@renderer/components/ui/button';
9
- import {
10
- Dialog,
11
- DialogContent,
12
- DialogDescription,
13
- DialogHeader,
14
- DialogTitle,
15
- } from '@renderer/components/ui/dialog';
16
- import { Input } from '@renderer/components/ui/input';
17
- import { Label } from '@renderer/components/ui/label';
18
- import {
19
- Select,
20
- SelectContent,
21
- SelectItem,
22
- SelectTrigger,
23
- SelectValue,
24
- } from '@renderer/components/ui/select';
25
- import { useStore } from '@renderer/store';
26
- import { AlertTriangle, Key } from 'lucide-react';
27
-
28
- import type { ApiKeyEntry } from '@shared/types/extensions';
29
-
30
- const ENV_KEY_RE = /^[A-Z_][A-Z0-9_]{0,100}$/i;
31
-
32
- interface ApiKeyFormDialogProps {
33
- open: boolean;
34
- editingKey: ApiKeyEntry | null;
35
- currentProjectPath: string | null;
36
- currentProjectLabel: string | null;
37
- onClose: () => void;
38
- }
39
-
40
- type Scope = 'user' | 'project';
41
-
42
- const SCOPE_OPTIONS: { value: Scope; label: string }[] = [
43
- { value: 'user', label: '个人(全局)' },
44
- { value: 'project', label: '项目' },
45
- ];
46
-
47
- export const ApiKeyFormDialog = ({
48
- open,
49
- editingKey,
50
- currentProjectPath,
51
- currentProjectLabel,
52
- onClose,
53
- }: ApiKeyFormDialogProps): React.JSX.Element => {
54
- const saveApiKey = useStore((s) => s.saveApiKey);
55
- const apiKeySaving = useStore((s) => s.apiKeySaving);
56
- const storageStatus = useStore((s) => s.apiKeyStorageStatus);
57
-
58
- const [name, setName] = useState('');
59
- const [envVarName, setEnvVarName] = useState('');
60
- const [value, setValue] = useState('');
61
- const [scope, setScope] = useState<Scope>('user');
62
- const [error, setError] = useState<string | null>(null);
63
- const [envVarError, setEnvVarError] = useState<string | null>(null);
64
- const editingProjectPath =
65
- editingKey?.scope === 'project' ? (editingKey.projectPath ?? null) : null;
66
- const effectiveProjectPath = editingProjectPath ?? currentProjectPath;
67
- const effectiveProjectLabel =
68
- effectiveProjectPath && effectiveProjectPath === currentProjectPath
69
- ? currentProjectLabel
70
- : effectiveProjectPath;
71
- const canUseProjectScope = Boolean(effectiveProjectPath);
72
-
73
- // Reset form when dialog opens/closes or editing key changes
74
- useEffect(() => {
75
- if (open) {
76
- if (editingKey) {
77
- setName(editingKey.name);
78
- setEnvVarName(editingKey.envVarName);
79
- setScope(editingKey.scope);
80
- setValue('');
81
- } else {
82
- setName('');
83
- setEnvVarName('');
84
- setValue('');
85
- setScope('user');
86
- }
87
- setError(null);
88
- setEnvVarError(null);
89
- }
90
- }, [open, editingKey]);
91
-
92
- useEffect(() => {
93
- if (open && scope === 'project' && !canUseProjectScope) {
94
- setScope('user');
95
- }
96
- }, [canUseProjectScope, open, scope]);
97
-
98
- const validateEnvVar = (v: string) => {
99
- if (!v.trim()) {
100
- setEnvVarError(null);
101
- return;
102
- }
103
- if (!ENV_KEY_RE.test(v)) {
104
- setEnvVarError('Use letters, digits, underscores. Must start with a letter or underscore.');
105
- } else {
106
- setEnvVarError(null);
107
- }
108
- };
109
-
110
- const handleSubmit = async (e: React.FormEvent) => {
111
- e.preventDefault();
112
- setError(null);
113
-
114
- if (!name.trim()) {
115
- setError('Name is required');
116
- return;
117
- }
118
- if (!envVarName.trim()) {
119
- setError('Environment variable name is required');
120
- return;
121
- }
122
- if (!ENV_KEY_RE.test(envVarName)) {
123
- setError('Invalid environment variable name');
124
- return;
125
- }
126
- if (!value) {
127
- setError('Key value is required');
128
- return;
129
- }
130
- if (scope === 'project' && !effectiveProjectPath) {
131
- setError('Project-scoped API keys require an active project');
132
- return;
133
- }
134
-
135
- try {
136
- await saveApiKey({
137
- id: editingKey?.id,
138
- name: name.trim(),
139
- envVarName: envVarName.trim(),
140
- value,
141
- scope,
142
- projectPath: scope === 'project' ? (effectiveProjectPath ?? undefined) : undefined,
143
- });
144
- onClose();
145
- } catch (err) {
146
- setError(err instanceof Error ? err.message : 'Failed to save');
147
- }
148
- };
149
-
150
- const isEdit = editingKey !== null;
151
- const canSubmit =
152
- name.trim() &&
153
- envVarName.trim() &&
154
- value &&
155
- !envVarError &&
156
- !apiKeySaving &&
157
- (scope !== 'project' || canUseProjectScope);
158
-
159
- return (
160
- <Dialog open={open} onOpenChange={(o) => !o && onClose()}>
161
- <DialogContent className="max-w-md">
162
- <DialogHeader>
163
- <div className="flex items-center gap-2">
164
- <div className="flex size-8 items-center justify-center rounded-lg border border-border bg-surface-raised">
165
- <Key className="size-4 text-text-muted" />
166
- </div>
167
- <div>
168
- <DialogTitle>{isEdit ? '编辑 API Key' : '添加 API Key'}</DialogTitle>
169
- <DialogDescription>
170
- {isEdit
171
- ? '更新 Key 信息。你需要重新输入 Key 值。'
172
- : '保存 API Key,用于安装 MCP 服务器时自动填充。'}
173
- </DialogDescription>
174
- </div>
175
- </div>
176
- </DialogHeader>
177
-
178
- {storageStatus && storageStatus.encryptionMethod !== 'os-keychain' && (
179
- <div className="flex items-center gap-2 rounded-md border border-amber-500/30 bg-amber-500/5 px-3 py-2 text-xs text-amber-400">
180
- <AlertTriangle className="size-3.5 shrink-0" />
181
- 系统钥匙串不可用,Key 会用 AES-256 在本地加密。安装 gnome-keyring 可获得系统级保护。
182
- </div>
183
- )}
184
-
185
- <form onSubmit={(e) => void handleSubmit(e)} className="space-y-4">
186
- {/* Name */}
187
- <div className="space-y-1.5">
188
- <Label htmlFor="apikey-name" className="text-xs">
189
- 名称
190
- </Label>
191
- <Input
192
- id="apikey-name"
193
- value={name}
194
- onChange={(e) => setName(e.target.value)}
195
- placeholder="e.g. OpenAI Production"
196
- className="h-8 text-sm"
197
- autoFocus
198
- />
199
- </div>
200
-
201
- {/* Env var name */}
202
- <div className="space-y-1.5">
203
- <Label htmlFor="apikey-envvar" className="text-xs">
204
- 环境变量名
205
- </Label>
206
- <Input
207
- id="apikey-envvar"
208
- value={envVarName}
209
- onChange={(e) => {
210
- setEnvVarName(e.target.value);
211
- validateEnvVar(e.target.value);
212
- }}
213
- placeholder="e.g. OPENAI_API_KEY"
214
- className={`h-8 font-mono text-sm ${envVarError ? 'border-red-500/50' : ''}`}
215
- />
216
- {envVarError && <p className="text-xs text-red-400">{envVarError}</p>}
217
- </div>
218
-
219
- {/* Value */}
220
- <div className="space-y-1.5">
221
- <Label htmlFor="apikey-value" className="text-xs">
222
-
223
- </Label>
224
- <Input
225
- id="apikey-value"
226
- type="password"
227
- value={value}
228
- onChange={(e) => setValue(e.target.value)}
229
- placeholder={isEdit ? '重新输入 Key 值' : 'sk-...'}
230
- className="h-8 text-sm"
231
- />
232
- </div>
233
-
234
- {/* Scope */}
235
- <div className="space-y-1.5">
236
- <Label className="text-xs">作用域</Label>
237
- <Select value={scope} onValueChange={(v) => setScope(v as Scope)}>
238
- <SelectTrigger className="h-8 text-sm">
239
- <SelectValue />
240
- </SelectTrigger>
241
- <SelectContent>
242
- {SCOPE_OPTIONS.map((opt) => (
243
- <SelectItem
244
- key={opt.value}
245
- value={opt.value}
246
- disabled={opt.value === 'project' && !canUseProjectScope}
247
- >
248
- {opt.value === 'project'
249
- ? effectiveProjectPath
250
- ? `项目:${effectiveProjectLabel}`
251
- : '当前无可用项目'
252
- : opt.label}
253
- </SelectItem>
254
- ))}
255
- </SelectContent>
256
- </Select>
257
- {scope === 'project' && effectiveProjectPath && (
258
- <p className="text-xs text-text-muted">绑定到 {effectiveProjectPath}</p>
259
- )}
260
- </div>
261
-
262
- {/* Error display */}
263
- {error && (
264
- <div className="rounded-md border border-red-500/30 bg-red-500/5 px-3 py-2 text-xs text-red-400">
265
- {error}
266
- </div>
267
- )}
268
-
269
- {/* Actions */}
270
- <div className="flex justify-end gap-2 pt-2">
271
- <Button type="button" variant="ghost" size="sm" onClick={onClose}>
272
- 取消
273
- </Button>
274
- <Button type="submit" size="sm" disabled={!canSubmit}>
275
- {apiKeySaving ? '保存中...' : isEdit ? '更新' : '保存'}
276
- </Button>
277
- </div>
278
- </form>
279
- </DialogContent>
280
- </Dialog>
281
- );
282
- };