@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.
- package/dist-renderer/assets/ProjectEditorOverlay-DsQt4FHy.js +52 -0
- package/dist-renderer/assets/{TeamGraphOverlay-Ba5njic5.js → TeamGraphOverlay-BjZC53xf.js} +1 -1
- package/dist-renderer/assets/{_basePickBy-BvnK-OC1.js → _basePickBy-CrWocIjq.js} +1 -1
- package/dist-renderer/assets/{_baseUniq-DmFYXx9G.js → _baseUniq-B6d8ysWi.js} +1 -1
- package/dist-renderer/assets/{arc-DX4ZQFY4.js → arc-DAIYCFP8.js} +1 -1
- package/dist-renderer/assets/{architectureDiagram-VXUJARFQ-DfYr3vEN.js → architectureDiagram-VXUJARFQ-B3UudXJh.js} +1 -1
- package/dist-renderer/assets/{blockDiagram-VD42YOAC-DuXdVeWn.js → blockDiagram-VD42YOAC-DbptKQ4W.js} +1 -1
- package/dist-renderer/assets/{c4Diagram-YG6GDRKO-Bw2nixXe.js → c4Diagram-YG6GDRKO-C4WQuZpV.js} +1 -1
- package/dist-renderer/assets/channel-DbjZvWii.js +1 -0
- package/dist-renderer/assets/{chunk-4BX2VUAB-DLiNGQoE.js → chunk-4BX2VUAB-Dp7fVpI_.js} +1 -1
- package/dist-renderer/assets/{chunk-55IACEB6-B1L_8VIF.js → chunk-55IACEB6-B8KGfbAy.js} +1 -1
- package/dist-renderer/assets/{chunk-B4BG7PRW-DaZMWKGk.js → chunk-B4BG7PRW-BG1oJrjA.js} +1 -1
- package/dist-renderer/assets/{chunk-DI55MBZ5-ku-dflJG.js → chunk-DI55MBZ5-DRmxNjht.js} +1 -1
- package/dist-renderer/assets/{chunk-FMBD7UC4-DV-mF1dP.js → chunk-FMBD7UC4-D6VLvy16.js} +1 -1
- package/dist-renderer/assets/{chunk-QN33PNHL-ByGcDFQ0.js → chunk-QN33PNHL-DZou1667.js} +1 -1
- package/dist-renderer/assets/{chunk-QZHKN3VN-7dv-Min8.js → chunk-QZHKN3VN-CghmasSh.js} +1 -1
- package/dist-renderer/assets/{chunk-TZMSLE5B-WdXL5fTu.js → chunk-TZMSLE5B-B7apcMPK.js} +1 -1
- package/dist-renderer/assets/classDiagram-2ON5EDUG-D_FGxxsl.js +1 -0
- package/dist-renderer/assets/classDiagram-v2-WZHVMYZB-D_FGxxsl.js +1 -0
- package/dist-renderer/assets/clone-CJ1kxO2J.js +1 -0
- package/dist-renderer/assets/{cose-bilkent-S5V4N54A-CNcsvqPl.js → cose-bilkent-S5V4N54A-05e5uQDp.js} +1 -1
- package/dist-renderer/assets/{dagre-6UL2VRFP-DBNx4qqx.js → dagre-6UL2VRFP-B06bRykF.js} +1 -1
- package/dist-renderer/assets/{diagram-PSM6KHXK-BfVlT6sT.js → diagram-PSM6KHXK-CY7VYQ7c.js} +1 -1
- package/dist-renderer/assets/{diagram-QEK2KX5R-HvVjs0K6.js → diagram-QEK2KX5R-BjKEH7dD.js} +1 -1
- package/dist-renderer/assets/{diagram-S2PKOQOG-DYb_KnWS.js → diagram-S2PKOQOG-Bf4ELS1_.js} +1 -1
- package/dist-renderer/assets/{erDiagram-Q2GNP2WA-Ba-IgI5G.js → erDiagram-Q2GNP2WA-DJ753_L9.js} +1 -1
- package/dist-renderer/assets/{flowDiagram-NV44I4VS-2iDN8Kpj.js → flowDiagram-NV44I4VS-B71S-lC-.js} +1 -1
- package/dist-renderer/assets/{ganttDiagram-JELNMOA3-Byjf8Fa3.js → ganttDiagram-JELNMOA3-C_U42mSZ.js} +1 -1
- package/dist-renderer/assets/{gitGraphDiagram-V2S2FVAM-DbKvfZ_j.js → gitGraphDiagram-V2S2FVAM-DKUJU4Ns.js} +1 -1
- package/dist-renderer/assets/{graph-Enirf-f8.js → graph-DY3qbzqj.js} +1 -1
- package/dist-renderer/assets/{index-DY1zqsb6.js → index-BlOrAXp3.js} +551 -537
- package/dist-renderer/assets/{index-AjxP_rE_.js → index-Bs27J5gB.js} +1 -1
- package/dist-renderer/assets/{index-CtlzGepK.js → index-C8B_nKOF.js} +1 -1
- package/dist-renderer/assets/index-CmZPUEhS.css +1 -0
- package/dist-renderer/assets/{index-COZPUWJW.js → index-DLKyDr4T.js} +1 -1
- package/dist-renderer/assets/{index-DdhqolqE.js → index-Dhsk3_DD.js} +1 -1
- package/dist-renderer/assets/{index-ChR1D6ZF.js → index-GpUvV2xs.js} +1 -1
- package/dist-renderer/assets/{infoDiagram-HS3SLOUP-D6uicwz1.js → infoDiagram-HS3SLOUP-BNs0y3IG.js} +1 -1
- package/dist-renderer/assets/{journeyDiagram-XKPGCS4Q-DqwZsXlQ.js → journeyDiagram-XKPGCS4Q-CqPnw4UV.js} +1 -1
- package/dist-renderer/assets/{kanban-definition-3W4ZIXB7-fCDVhVUm.js → kanban-definition-3W4ZIXB7-SLlzcUJ2.js} +1 -1
- package/dist-renderer/assets/{layout-CPFgj98r.js → layout-BZLlNmbr.js} +1 -1
- package/dist-renderer/assets/{linear-CYiQ7Y3M.js → linear-qz6v45xy.js} +1 -1
- package/dist-renderer/assets/{mindmap-definition-VGOIOE7T-D31dS2KE.js → mindmap-definition-VGOIOE7T-B1-kmEWV.js} +1 -1
- package/dist-renderer/assets/{pieDiagram-ADFJNKIX-BOsCJfds.js → pieDiagram-ADFJNKIX-B8a02iNx.js} +1 -1
- package/dist-renderer/assets/{quadrantDiagram-AYHSOK5B-CYTVQCfr.js → quadrantDiagram-AYHSOK5B-BKv1Xfou.js} +1 -1
- package/dist-renderer/assets/{requirementDiagram-UZGBJVZJ-CODCFpkt.js → requirementDiagram-UZGBJVZJ-B3DUpZi2.js} +1 -1
- package/dist-renderer/assets/{sankeyDiagram-TZEHDZUN-Z4ce9ZtZ.js → sankeyDiagram-TZEHDZUN-DmPzuTsy.js} +1 -1
- package/dist-renderer/assets/{sequenceDiagram-WL72ISMW-CmS9TxhW.js → sequenceDiagram-WL72ISMW-Bo7RelRb.js} +1 -1
- package/dist-renderer/assets/{stateDiagram-FKZM4ZOC-o9k-ns3q.js → stateDiagram-FKZM4ZOC-1epX98gV.js} +1 -1
- package/dist-renderer/assets/{stateDiagram-v2-4FDKWEC3-CxHMyEt1.js → stateDiagram-v2-4FDKWEC3-03Ym9PTr.js} +1 -1
- package/dist-renderer/assets/{timeline-definition-IT6M3QCI-B6T3zrde.js → timeline-definition-IT6M3QCI-r6isC62H.js} +1 -1
- package/dist-renderer/assets/{treemap-GDKQZRPO-CVd5GNDw.js → treemap-GDKQZRPO-CGKpOUF2.js} +1 -1
- package/dist-renderer/assets/{xychartDiagram-PRI3JC2R-CleBrdqc.js → xychartDiagram-PRI3JC2R-t4-rwdAw.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 +907 -184
- 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/session-intelligence/UsageTelemetryService.ts +33 -18
- 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/CollaborationBoardService.ts +310 -0
- package/src/main/services/teams-mvp/TaskDispatchService.ts +883 -95
- package/src/main/services/teams-mvp/TeamProvisioningService.ts +58 -19
- package/src/main/services/teams-mvp/TeamWorkspaceService.ts +25 -2
- package/src/main/services/teams-mvp/index.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/App.tsx +5 -0
- package/src/renderer/api/httpClient.ts +128 -0
- package/src/renderer/components/extensions/ExtensionStoreView.tsx +59 -34
- 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 +11 -0
- package/src/renderer/components/extensions/mcp/McpServersPanel.tsx +51 -1
- package/src/renderer/components/extensions/skills/SkillsPanel.tsx +1 -126
- package/src/renderer/components/layout/PaneContent.tsx +2 -0
- package/src/renderer/components/layout/SortableTab.tsx +1 -0
- package/src/renderer/components/layout/TabBarActions.tsx +12 -12
- package/src/renderer/components/schedules/SchedulesView.tsx +54 -22
- package/src/renderer/components/settings/sections/AdvancedSection.tsx +1 -1
- package/src/renderer/components/settings/sections/HarnessSection.tsx +2 -6
- package/src/renderer/components/settings/sections/TaskBusSection.tsx +144 -84
- package/src/renderer/components/sidebar/SidebarSessions.tsx +23 -0
- package/src/renderer/components/sidebar/WorkspaceBrowser.tsx +1 -7
- package/src/renderer/components/tasks/TasksView.tsx +343 -0
- package/src/renderer/components/team/HarnessSelect.tsx +71 -0
- package/src/renderer/components/team/TeamDetailView.tsx +55 -98
- package/src/renderer/components/team/dialogs/CreateTeamDialog.tsx +21 -12
- package/src/renderer/components/team/dialogs/EditTeamDialog.tsx +8 -13
- package/src/renderer/components/team/dialogs/LaunchTeamDialog.tsx +1 -1
- package/src/renderer/components/team/editor/EditorContextMenu.tsx +8 -23
- package/src/renderer/components/team/editor/EditorFileTree.tsx +0 -4
- package/src/renderer/components/team/editor/EditorSelectionMenu.tsx +1 -8
- package/src/renderer/components/team/editor/ProjectEditorOverlay.tsx +0 -10
- package/src/renderer/components/team/kanban/KanbanBoard.tsx +31 -65
- package/src/renderer/components/team/members/MemberDetailDialog.tsx +8 -33
- package/src/renderer/components/team/messages/MessageComposer.tsx +39 -3
- package/src/renderer/components/team/messages/MessagesPanel.tsx +100 -26
- package/src/renderer/components/team/messages/StatusBlock.tsx +2 -24
- package/src/renderer/components/team/schedule/ScheduleEmptyState.tsx +1 -1
- package/src/renderer/components/terminal/TerminalPanel.tsx +156 -0
- package/src/renderer/components/ui/MentionableTextarea.tsx +0 -1
- package/src/renderer/hooks/useExtensionsTabState.ts +2 -2
- package/src/renderer/store/slices/extensionsSlice.ts +42 -107
- package/src/renderer/store/slices/scheduleSlice.ts +21 -0
- package/src/renderer/store/slices/teamSlice.ts +67 -25
- package/src/renderer/types/tabs.ts +1 -0
- package/src/shared/types/api.ts +58 -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/types/team.ts +104 -1
- package/src/shared/utils/providerExtensionCapabilities.ts +1 -1
- package/dist-renderer/assets/ProjectEditorOverlay-A4DZTvSy.js +0 -57
- package/dist-renderer/assets/channel-Pre42N5O.js +0 -1
- package/dist-renderer/assets/classDiagram-2ON5EDUG-CdJsTJsj.js +0 -1
- package/dist-renderer/assets/classDiagram-v2-WZHVMYZB-CdJsTJsj.js +0 -1
- package/dist-renderer/assets/clone-BjQBiNfj.js +0 -1
- package/dist-renderer/assets/index-BIOJremZ.css +0 -1
- 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,664 @@
|
|
|
1
|
+
import * as fs from 'fs';
|
|
2
|
+
import * as os from 'os';
|
|
3
|
+
import * as path from 'path';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Utility functions for encoding/decoding Claude Code project directory names.
|
|
7
|
+
*
|
|
8
|
+
* Directory naming pattern:
|
|
9
|
+
* - Path: /Users/username/projectname
|
|
10
|
+
* - Encoded: -Users-username-projectname
|
|
11
|
+
*
|
|
12
|
+
* IMPORTANT: This encoding is LOSSY for paths containing dashes.
|
|
13
|
+
* For accurate path resolution, use extractCwd() from jsonl.ts to read
|
|
14
|
+
* the actual cwd from session files.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
// =============================================================================
|
|
18
|
+
// Core Encoding/Decoding
|
|
19
|
+
// =============================================================================
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Encodes an absolute path into Claude Code's directory naming format.
|
|
23
|
+
* Replaces all path separators (/ and \) with dashes.
|
|
24
|
+
*
|
|
25
|
+
* @param absolutePath - The absolute path to encode (e.g., "/Users/username/projectname")
|
|
26
|
+
* @returns The encoded directory name (e.g., "-Users-username-projectname")
|
|
27
|
+
*/
|
|
28
|
+
export function encodePath(absolutePath: string): string {
|
|
29
|
+
if (!absolutePath) {
|
|
30
|
+
return '';
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const encoded = absolutePath.replace(/[/\\]/g, '-');
|
|
34
|
+
const windowsDriveMatch = /^([a-zA-Z]):-(.*)$/.exec(encoded);
|
|
35
|
+
if (windowsDriveMatch) {
|
|
36
|
+
return `${windowsDriveMatch[1].toUpperCase()}--${windowsDriveMatch[2]}`;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// Ensure leading dash for absolute paths
|
|
40
|
+
return encoded.startsWith('-') ? encoded : `-${encoded}`;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function isWindowsAbsolutePathLike(name: string): boolean {
|
|
44
|
+
const slashPath = name.replace(/\\/g, '/');
|
|
45
|
+
return /^[a-zA-Z]:\//.test(slashPath) || slashPath.startsWith('//');
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function normalizeWindowsPathForStorageKey(name: string): string {
|
|
49
|
+
if (!isWindowsAbsolutePathLike(name)) {
|
|
50
|
+
return name;
|
|
51
|
+
}
|
|
52
|
+
return name.replace(/\\/g, '/').toLowerCase();
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Matches the orchestrator's cross-platform storage key codec.
|
|
57
|
+
* It lowercases Windows absolute paths, normalizes separators, and replaces
|
|
58
|
+
* every non-ASCII-alphanumeric character with a dash.
|
|
59
|
+
*/
|
|
60
|
+
export function encodePathPortable(absolutePath: string): string {
|
|
61
|
+
if (!absolutePath) {
|
|
62
|
+
return '';
|
|
63
|
+
}
|
|
64
|
+
return normalizeWindowsPathForStorageKey(absolutePath).replace(/[^a-zA-Z0-9]/g, '-');
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Decodes a project directory name to its original path.
|
|
69
|
+
* Note: This is a best-effort decode. Paths with dashes cannot be decoded accurately.
|
|
70
|
+
*
|
|
71
|
+
* @param encodedName - The encoded directory name (e.g., "-Users-username-projectname")
|
|
72
|
+
* @returns The decoded path (e.g., "/Users/username/projectname")
|
|
73
|
+
*/
|
|
74
|
+
export function decodePath(encodedName: string): string {
|
|
75
|
+
if (!encodedName) {
|
|
76
|
+
return '';
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// Legacy Windows format observed in some Claude installs: "C--Users-name-project"
|
|
80
|
+
// (no leading dash, drive separator encoded as "--").
|
|
81
|
+
const legacyWindowsRegex = /^([a-zA-Z])--(.*)$/;
|
|
82
|
+
const legacyWindowsMatch = legacyWindowsRegex.exec(encodedName);
|
|
83
|
+
if (legacyWindowsMatch) {
|
|
84
|
+
const drive = legacyWindowsMatch[1].toUpperCase();
|
|
85
|
+
const rest = legacyWindowsMatch[2].replace(/-/g, '/');
|
|
86
|
+
return `${drive}:/${rest}`;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// Remove leading dash if present (indicates absolute path)
|
|
90
|
+
const withoutLeadingDash = encodedName.startsWith('-') ? encodedName.slice(1) : encodedName;
|
|
91
|
+
|
|
92
|
+
// Replace dashes with slashes
|
|
93
|
+
const decodedPath = withoutLeadingDash.replace(/-/g, '/');
|
|
94
|
+
|
|
95
|
+
// Windows paths may decode to "C:/..."
|
|
96
|
+
if (/^[a-zA-Z]:\//.test(decodedPath)) {
|
|
97
|
+
return decodedPath;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// Ensure leading slash for POSIX-style absolute paths
|
|
101
|
+
const absolutePath = decodedPath.startsWith('/') ? decodedPath : `/${decodedPath}`;
|
|
102
|
+
|
|
103
|
+
// Translate WSL mount paths to Windows drive-letter paths on Windows
|
|
104
|
+
return translateWslMountPath(absolutePath);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Extract the project name (last path segment) from an encoded directory name.
|
|
109
|
+
*
|
|
110
|
+
* @param encodedName - The encoded directory name
|
|
111
|
+
* @returns The project name
|
|
112
|
+
*/
|
|
113
|
+
export function extractProjectName(encodedName: string, cwdHint?: string): string {
|
|
114
|
+
// Prefer cwdHint (actual filesystem path) since decodePath is lossy for
|
|
115
|
+
// paths containing dashes (e.g., "claude-devtools" -> "claude/code/context").
|
|
116
|
+
if (cwdHint) {
|
|
117
|
+
const segments = cwdHint.split(/[/\\]/).filter(Boolean);
|
|
118
|
+
const last = segments[segments.length - 1];
|
|
119
|
+
if (last) return last;
|
|
120
|
+
}
|
|
121
|
+
const decoded = decodePath(encodedName);
|
|
122
|
+
const segments = decoded.split('/').filter(Boolean);
|
|
123
|
+
return segments[segments.length - 1] || encodedName;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Translate WSL mount paths (/mnt/X/...) to Windows drive-letter paths (X:/...)
|
|
128
|
+
* when running on Windows. No-op on other platforms.
|
|
129
|
+
*/
|
|
130
|
+
export function translateWslMountPath(posixPath: string): string {
|
|
131
|
+
if (process.platform !== 'win32') {
|
|
132
|
+
return posixPath;
|
|
133
|
+
}
|
|
134
|
+
const match = /^\/mnt\/([a-zA-Z])(\/.*)?$/.exec(posixPath);
|
|
135
|
+
if (match) {
|
|
136
|
+
const drive = match[1].toUpperCase();
|
|
137
|
+
const rest = match[2] ?? '';
|
|
138
|
+
return `${drive}:${rest}`;
|
|
139
|
+
}
|
|
140
|
+
return posixPath;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// =============================================================================
|
|
144
|
+
// Validation
|
|
145
|
+
// =============================================================================
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Validates if a directory name follows the Claude Code encoding pattern.
|
|
149
|
+
*
|
|
150
|
+
* @param encodedName - The directory name to validate
|
|
151
|
+
* @returns true if valid, false otherwise
|
|
152
|
+
*/
|
|
153
|
+
export function isValidEncodedPath(encodedName: string): boolean {
|
|
154
|
+
if (!encodedName) {
|
|
155
|
+
return false;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// Support legacy Windows format: "C--Users-name-project"
|
|
159
|
+
// (no leading dash, drive separator encoded as "--").
|
|
160
|
+
if (/^[a-zA-Z]--[^\x00-\x1f/\\:*?"<>|]+$/u.test(encodedName)) {
|
|
161
|
+
return true;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// Must start with a dash (indicates absolute path)
|
|
165
|
+
if (!encodedName.startsWith('-')) {
|
|
166
|
+
return false;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// Encoded path is a single directory name. It may contain Unicode project
|
|
170
|
+
// names, but must not contain separators, control chars, or Windows-invalid chars.
|
|
171
|
+
// A single drive colon is allowed only in the old "-C:-Users-name" form.
|
|
172
|
+
if (/[\x00-\x1f/\\*?"<>|]/u.test(encodedName)) {
|
|
173
|
+
return false;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// Windows-style drive syntax is allowed only at the beginning after "-"
|
|
177
|
+
// e.g. "-C:-Users-name-project". Reject stray ":" elsewhere.
|
|
178
|
+
const firstColon = encodedName.indexOf(':');
|
|
179
|
+
if (firstColon === -1) {
|
|
180
|
+
return true;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
if (!/^-[a-zA-Z]:/.test(encodedName)) {
|
|
184
|
+
return false;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
return !encodedName.includes(':', firstColon + 1);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* Validates a project ID that may be either a plain encoded path or
|
|
192
|
+
* a composite subproject ID (`{encodedPath}::{8-char-hex}`).
|
|
193
|
+
*
|
|
194
|
+
* @param projectId - The project ID to validate
|
|
195
|
+
* @returns true if valid
|
|
196
|
+
*/
|
|
197
|
+
export function isValidProjectId(projectId: string): boolean {
|
|
198
|
+
if (!projectId) {
|
|
199
|
+
return false;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
const sep = projectId.indexOf('::');
|
|
203
|
+
if (sep === -1) {
|
|
204
|
+
// Plain encoded path
|
|
205
|
+
return isValidEncodedPath(projectId);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
// Composite ID: validate base part and hash suffix
|
|
209
|
+
const basePart = projectId.slice(0, sep);
|
|
210
|
+
const hashPart = projectId.slice(sep + 2);
|
|
211
|
+
|
|
212
|
+
return isValidEncodedPath(basePart) && /^[a-f0-9]{8}$/.test(hashPart);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* Extract the base directory (encoded path) from a project ID.
|
|
217
|
+
* For composite IDs (`{encoded}::{hash}`), returns the encoded part.
|
|
218
|
+
* For plain IDs, returns the ID as-is.
|
|
219
|
+
*/
|
|
220
|
+
export function extractBaseDir(projectId: string): string {
|
|
221
|
+
const sep = projectId.indexOf('::');
|
|
222
|
+
if (sep !== -1) {
|
|
223
|
+
return projectId.slice(0, sep);
|
|
224
|
+
}
|
|
225
|
+
return projectId;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
function addUniqueCandidate(candidates: string[], candidate: string): void {
|
|
229
|
+
if (candidate && !candidates.includes(candidate)) {
|
|
230
|
+
candidates.push(candidate);
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* Returns possible ~/.claude/projects directory names for a project id.
|
|
236
|
+
* The first candidate is always the id's own base dir. Additional entries cover
|
|
237
|
+
* the orchestrator's portable codec, which lowercases Windows paths and folds
|
|
238
|
+
* underscores/non-ASCII characters to dashes.
|
|
239
|
+
*/
|
|
240
|
+
export function getProjectDirNameCandidates(projectId: string): string[] {
|
|
241
|
+
const baseDir = extractBaseDir(projectId);
|
|
242
|
+
const candidates: string[] = [];
|
|
243
|
+
addUniqueCandidate(candidates, baseDir);
|
|
244
|
+
|
|
245
|
+
const decoded = decodePath(baseDir);
|
|
246
|
+
addUniqueCandidate(candidates, encodePath(decoded));
|
|
247
|
+
addUniqueCandidate(candidates, encodePathPortable(decoded));
|
|
248
|
+
|
|
249
|
+
if (path.isAbsolute(projectId)) {
|
|
250
|
+
addUniqueCandidate(candidates, encodePath(projectId));
|
|
251
|
+
addUniqueCandidate(candidates, encodePathPortable(projectId));
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
return candidates;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
// =============================================================================
|
|
258
|
+
// Session ID Extraction
|
|
259
|
+
// =============================================================================
|
|
260
|
+
|
|
261
|
+
/**
|
|
262
|
+
* Extract session ID from a JSONL filename.
|
|
263
|
+
*
|
|
264
|
+
* @param filename - The filename (e.g., "abc123.jsonl")
|
|
265
|
+
* @returns The session ID (e.g., "abc123")
|
|
266
|
+
*/
|
|
267
|
+
export function extractSessionId(filename: string): string {
|
|
268
|
+
return filename.replace(/\.jsonl$/, '');
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
// =============================================================================
|
|
272
|
+
// Path Construction
|
|
273
|
+
// =============================================================================
|
|
274
|
+
|
|
275
|
+
/**
|
|
276
|
+
* Construct the path to a session JSONL file.
|
|
277
|
+
* Handles composite project IDs by extracting the base directory.
|
|
278
|
+
*/
|
|
279
|
+
export function buildSessionPath(basePath: string, projectId: string, sessionId: string): string {
|
|
280
|
+
return path.join(basePath, extractBaseDir(projectId), `${sessionId}.jsonl`);
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
/**
|
|
284
|
+
* Construct the path to a session's subagents directory.
|
|
285
|
+
* Handles composite project IDs by extracting the base directory.
|
|
286
|
+
*/
|
|
287
|
+
export function buildSubagentsPath(basePath: string, projectId: string, sessionId: string): string {
|
|
288
|
+
return path.join(basePath, extractBaseDir(projectId), sessionId, 'subagents');
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
/**
|
|
292
|
+
* Construct the path to a task list file (stored in todos directory).
|
|
293
|
+
*/
|
|
294
|
+
export function buildTodoPath(claudeBasePath: string, sessionId: string): string {
|
|
295
|
+
return path.join(claudeBasePath, 'todos', `${sessionId}.json`);
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
// =============================================================================
|
|
299
|
+
// Home Directory
|
|
300
|
+
// =============================================================================
|
|
301
|
+
|
|
302
|
+
/**
|
|
303
|
+
* Try Electron's app.getPath('home') which correctly handles Unicode paths
|
|
304
|
+
* on Windows (Cyrillic, CJK, etc.) unlike Node's os.homedir() / env vars
|
|
305
|
+
* that can suffer from UTF-8 vs system codepage mismatches.
|
|
306
|
+
*
|
|
307
|
+
* Returns null when Electron app is unavailable (e.g. in tests).
|
|
308
|
+
*/
|
|
309
|
+
function getElectronHome(): string | null {
|
|
310
|
+
try {
|
|
311
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports -- Lazy require to avoid hard dependency on electron in test environments
|
|
312
|
+
const electron = require('electron') as {
|
|
313
|
+
app?: { getPath: (name: string) => string };
|
|
314
|
+
};
|
|
315
|
+
const app = electron.app;
|
|
316
|
+
if (app && typeof app.getPath === 'function') {
|
|
317
|
+
const home = app.getPath('home');
|
|
318
|
+
if (home) return home;
|
|
319
|
+
}
|
|
320
|
+
} catch {
|
|
321
|
+
// Not in Electron context (tests, standalone builds, etc.)
|
|
322
|
+
}
|
|
323
|
+
return null;
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
/**
|
|
327
|
+
* Get the user's home directory.
|
|
328
|
+
*
|
|
329
|
+
* Priority:
|
|
330
|
+
* 1. Electron app.getPath('home') -- correct Unicode handling on all platforms
|
|
331
|
+
* 2. HOME env var (Unix) / USERPROFILE (Windows)
|
|
332
|
+
* 3. HOMEDRIVE + HOMEPATH (Windows fallback)
|
|
333
|
+
* 4. os.homedir() (Node.js built-in)
|
|
334
|
+
*/
|
|
335
|
+
export function getHomeDir(): string {
|
|
336
|
+
const electronHome = getElectronHome();
|
|
337
|
+
if (electronHome) return electronHome;
|
|
338
|
+
|
|
339
|
+
const windowsHome =
|
|
340
|
+
process.env.HOMEDRIVE && process.env.HOMEPATH
|
|
341
|
+
? `${process.env.HOMEDRIVE}${process.env.HOMEPATH}`
|
|
342
|
+
: null;
|
|
343
|
+
return process.env.HOME || process.env.USERPROFILE || windowsHome || os.homedir() || '/';
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
let claudeBasePathOverride: string | null = null;
|
|
347
|
+
|
|
348
|
+
function getDefaultClaudeBasePath(): string {
|
|
349
|
+
return path.join(getHomeDir(), '.claude');
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
/**
|
|
353
|
+
* Get the auto-detected Claude config base path (~/.claude) without considering overrides.
|
|
354
|
+
*/
|
|
355
|
+
export function getAutoDetectedClaudeBasePath(): string {
|
|
356
|
+
return getDefaultClaudeBasePath();
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
function normalizeOverridePath(claudeBasePath: string): string | null {
|
|
360
|
+
const trimmed = claudeBasePath.trim();
|
|
361
|
+
if (!trimmed) {
|
|
362
|
+
return null;
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
const normalized = path.normalize(trimmed);
|
|
366
|
+
if (!path.isAbsolute(normalized)) {
|
|
367
|
+
return null;
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
const resolved = path.resolve(normalized);
|
|
371
|
+
const root = path.parse(resolved).root;
|
|
372
|
+
if (resolved === root) {
|
|
373
|
+
return resolved;
|
|
374
|
+
}
|
|
375
|
+
let end = resolved.length;
|
|
376
|
+
while (end > root.length) {
|
|
377
|
+
const char = resolved[end - 1];
|
|
378
|
+
if (char !== '/' && char !== '\\') {
|
|
379
|
+
break;
|
|
380
|
+
}
|
|
381
|
+
end--;
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
return resolved.slice(0, end);
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
/**
|
|
388
|
+
* Override the Claude config base path (~/.claude).
|
|
389
|
+
* Pass null to return to auto-detection.
|
|
390
|
+
*/
|
|
391
|
+
export function setClaudeBasePathOverride(claudeBasePath: string | null | undefined): void {
|
|
392
|
+
if (claudeBasePath == null) {
|
|
393
|
+
claudeBasePathOverride = null;
|
|
394
|
+
return;
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
claudeBasePathOverride = normalizeOverridePath(claudeBasePath);
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
/**
|
|
401
|
+
* Get the Claude config base path (~/.claude).
|
|
402
|
+
*/
|
|
403
|
+
export function getClaudeBasePath(): string {
|
|
404
|
+
return claudeBasePathOverride ?? getDefaultClaudeBasePath();
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
/**
|
|
408
|
+
* Get the projects directory path (~/.claude/projects).
|
|
409
|
+
*/
|
|
410
|
+
export function getProjectsBasePath(): string {
|
|
411
|
+
return path.join(getClaudeBasePath(), 'projects');
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
/**
|
|
415
|
+
* Get the todos directory path (~/.claude/todos).
|
|
416
|
+
*/
|
|
417
|
+
export function getTodosBasePath(): string {
|
|
418
|
+
return path.join(getClaudeBasePath(), 'todos');
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
/**
|
|
422
|
+
* Get the teams directory path (~/.claude/teams).
|
|
423
|
+
*/
|
|
424
|
+
export function getTeamsBasePath(): string {
|
|
425
|
+
return path.join(getClaudeBasePath(), 'teams');
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
/**
|
|
429
|
+
* Get the tasks directory path (~/.claude/tasks).
|
|
430
|
+
*/
|
|
431
|
+
export function getTasksBasePath(): string {
|
|
432
|
+
return path.join(getClaudeBasePath(), 'tasks');
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
/**
|
|
436
|
+
* Get the tools directory path (~/.claude/tools).
|
|
437
|
+
*/
|
|
438
|
+
export function getToolsBasePath(): string {
|
|
439
|
+
return path.join(getClaudeBasePath(), 'tools');
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
type CopyDirectoryForMigration = (sourcePath: string, targetPath: string) => void;
|
|
443
|
+
|
|
444
|
+
function copyDirectoryForMigrationSync(sourcePath: string, targetPath: string): void {
|
|
445
|
+
fs.cpSync(sourcePath, targetPath, {
|
|
446
|
+
recursive: true,
|
|
447
|
+
errorOnExist: false,
|
|
448
|
+
force: false,
|
|
449
|
+
});
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
let copyDirectoryForMigration: CopyDirectoryForMigration = copyDirectoryForMigrationSync;
|
|
453
|
+
|
|
454
|
+
export function __setPathDecoderCopyDirectoryForTests(
|
|
455
|
+
copyDirectory: CopyDirectoryForMigration | null
|
|
456
|
+
): void {
|
|
457
|
+
copyDirectoryForMigration = copyDirectory ?? copyDirectoryForMigrationSync;
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
/**
|
|
461
|
+
* Get the schedules directory path (~/.claude/agent-teams-schedules).
|
|
462
|
+
*/
|
|
463
|
+
export function getSchedulesBasePath(): string {
|
|
464
|
+
const basePath = getClaudeBasePath();
|
|
465
|
+
return migrateLegacyDirectoryPath(
|
|
466
|
+
path.join(basePath, 'agent-teams-schedules'),
|
|
467
|
+
path.join(basePath, 'claude-devtools-schedules')
|
|
468
|
+
);
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
function migrateLegacyDirectoryPath(currentPath: string, legacyPath: string): string {
|
|
472
|
+
if (!directoryExists(legacyPath)) {
|
|
473
|
+
return currentPath;
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
if (directoryExists(currentPath)) {
|
|
477
|
+
if (directoryHasEntries(currentPath)) {
|
|
478
|
+
return currentPath;
|
|
479
|
+
}
|
|
480
|
+
return copyLegacyDirectoryPath(currentPath, legacyPath);
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
if (pathExists(currentPath)) {
|
|
484
|
+
return currentPath;
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
return copyLegacyDirectoryPath(currentPath, legacyPath);
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
function copyLegacyDirectoryPath(currentPath: string, legacyPath: string): string {
|
|
491
|
+
const tempPath = `${currentPath}.migrating-${process.pid}`;
|
|
492
|
+
|
|
493
|
+
try {
|
|
494
|
+
fs.mkdirSync(path.dirname(currentPath), { recursive: true });
|
|
495
|
+
if (pathExists(tempPath)) {
|
|
496
|
+
fs.rmSync(tempPath, { recursive: true, force: true });
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
copyDirectoryForMigration(legacyPath, tempPath);
|
|
500
|
+
|
|
501
|
+
if (directoryExists(currentPath) && !directoryHasEntries(currentPath)) {
|
|
502
|
+
fs.rmdirSync(currentPath);
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
fs.renameSync(tempPath, currentPath);
|
|
506
|
+
return currentPath;
|
|
507
|
+
} catch {
|
|
508
|
+
try {
|
|
509
|
+
if (pathExists(tempPath)) {
|
|
510
|
+
fs.rmSync(tempPath, { recursive: true, force: true });
|
|
511
|
+
}
|
|
512
|
+
} catch {
|
|
513
|
+
// Best effort cleanup only.
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
return directoryExists(currentPath) && directoryHasEntries(currentPath)
|
|
517
|
+
? currentPath
|
|
518
|
+
: legacyPath;
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
export function getTaskChangeSummariesBasePath(): string {
|
|
523
|
+
return path.join(getClaudeBasePath(), 'task-change-summaries');
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
export function getTaskChangePresenceBasePath(): string {
|
|
527
|
+
return path.join(getClaudeBasePath(), 'task-change-presence');
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
/**
|
|
531
|
+
* Get the backups directory path for the app's own storage.
|
|
532
|
+
*/
|
|
533
|
+
export function getBackupsBasePath(): string {
|
|
534
|
+
return path.join(getAppDataBasePath(), 'backups');
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
/**
|
|
538
|
+
* Get the app's own data directory (attachments, task-attachments).
|
|
539
|
+
* Separate from ~/.claude/ so CLI cannot delete our data.
|
|
540
|
+
*/
|
|
541
|
+
export function getAppDataPath(): string {
|
|
542
|
+
return path.join(getAppDataBasePath(), 'data');
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
// -- App data root (Electron userData) --
|
|
546
|
+
|
|
547
|
+
const APP_DATA_FALLBACK_DIR_NAME = '.agent-teams-ai';
|
|
548
|
+
const LEGACY_APP_DATA_FALLBACK_DIR_NAME = '.claude-agent-teams-ui';
|
|
549
|
+
|
|
550
|
+
let appDataBasePathOverride: string | null = null;
|
|
551
|
+
|
|
552
|
+
export function setAppDataBasePath(p: string | null | undefined): void {
|
|
553
|
+
appDataBasePathOverride = p ?? null;
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
function getAppDataBasePath(): string {
|
|
557
|
+
if (appDataBasePathOverride) return appDataBasePathOverride;
|
|
558
|
+
// Fallback: resolve lazily from Electron app (safe after app.whenReady)
|
|
559
|
+
try {
|
|
560
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
561
|
+
const { app } = require('electron') as { app: { getPath: (name: string) => string } };
|
|
562
|
+
return app.getPath('userData');
|
|
563
|
+
} catch {
|
|
564
|
+
// Outside Electron (tests, CLI): use the new fallback path and migrate legacy data once.
|
|
565
|
+
return getFallbackAppDataBasePath();
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
function getFallbackAppDataBasePath(): string {
|
|
570
|
+
const home = getHomeDir();
|
|
571
|
+
const currentPath = path.join(home, APP_DATA_FALLBACK_DIR_NAME);
|
|
572
|
+
const legacyPath = path.join(home, LEGACY_APP_DATA_FALLBACK_DIR_NAME);
|
|
573
|
+
|
|
574
|
+
if (!directoryExists(legacyPath)) {
|
|
575
|
+
return currentPath;
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
if (directoryExists(currentPath)) {
|
|
579
|
+
if (directoryHasEntries(currentPath)) {
|
|
580
|
+
return currentPath;
|
|
581
|
+
}
|
|
582
|
+
return migrateFallbackAppDataBasePath(currentPath, legacyPath);
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
if (pathExists(currentPath)) {
|
|
586
|
+
return currentPath;
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
return migrateFallbackAppDataBasePath(currentPath, legacyPath);
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
function migrateFallbackAppDataBasePath(currentPath: string, legacyPath: string): string {
|
|
593
|
+
const tempPath = `${currentPath}.migrating-${process.pid}`;
|
|
594
|
+
|
|
595
|
+
try {
|
|
596
|
+
if (pathExists(tempPath)) {
|
|
597
|
+
fs.rmSync(tempPath, { recursive: true, force: true });
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
copyDirectoryForMigration(legacyPath, tempPath);
|
|
601
|
+
|
|
602
|
+
if (directoryExists(currentPath) && !directoryHasEntries(currentPath)) {
|
|
603
|
+
fs.rmdirSync(currentPath);
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
fs.renameSync(tempPath, currentPath);
|
|
607
|
+
return currentPath;
|
|
608
|
+
} catch {
|
|
609
|
+
try {
|
|
610
|
+
if (pathExists(tempPath)) {
|
|
611
|
+
fs.rmSync(tempPath, { recursive: true, force: true });
|
|
612
|
+
}
|
|
613
|
+
} catch {
|
|
614
|
+
// Best effort cleanup only.
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
return directoryExists(currentPath) && directoryHasEntries(currentPath)
|
|
618
|
+
? currentPath
|
|
619
|
+
: legacyPath;
|
|
620
|
+
}
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
function pathExists(targetPath: string): boolean {
|
|
624
|
+
try {
|
|
625
|
+
fs.accessSync(targetPath);
|
|
626
|
+
return true;
|
|
627
|
+
} catch {
|
|
628
|
+
return false;
|
|
629
|
+
}
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
function directoryExists(targetPath: string): boolean {
|
|
633
|
+
try {
|
|
634
|
+
return fs.statSync(targetPath).isDirectory();
|
|
635
|
+
} catch {
|
|
636
|
+
return false;
|
|
637
|
+
}
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
function directoryHasEntries(targetPath: string): boolean {
|
|
641
|
+
try {
|
|
642
|
+
return fs.readdirSync(targetPath).length > 0;
|
|
643
|
+
} catch {
|
|
644
|
+
return false;
|
|
645
|
+
}
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
/**
|
|
649
|
+
* Directory for per-team MCP config JSON files.
|
|
650
|
+
* Stored in app's userData so they persist across sessions and are
|
|
651
|
+
* accessible by Claude CLI subprocess on all platforms (including AppImage).
|
|
652
|
+
*/
|
|
653
|
+
export function getMcpConfigsBasePath(): string {
|
|
654
|
+
return path.join(getAppDataBasePath(), 'mcp-configs');
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
/**
|
|
658
|
+
* Directory for the stable MCP server bundle copy (packaged builds).
|
|
659
|
+
* Versioned subdirectories contain the copied index.js + package.json
|
|
660
|
+
* so the server runs from a writable, non-FUSE location.
|
|
661
|
+
*/
|
|
662
|
+
export function getMcpServerBasePath(): string {
|
|
663
|
+
return path.join(getAppDataBasePath(), 'mcp-server');
|
|
664
|
+
}
|