imcp 0.0.1

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 (124) hide show
  1. package/.github/ISSUE_TEMPLATE/JitAccess.yml +28 -0
  2. package/.github/acl/access.yml +20 -0
  3. package/.github/compliance/inventory.yml +5 -0
  4. package/.github/policies/jit.yml +19 -0
  5. package/README.md +137 -0
  6. package/dist/cli/commands/install.d.ts +2 -0
  7. package/dist/cli/commands/install.js +105 -0
  8. package/dist/cli/commands/list.d.ts +2 -0
  9. package/dist/cli/commands/list.js +90 -0
  10. package/dist/cli/commands/pull.d.ts +2 -0
  11. package/dist/cli/commands/pull.js +17 -0
  12. package/dist/cli/commands/serve.d.ts +2 -0
  13. package/dist/cli/commands/serve.js +32 -0
  14. package/dist/cli/commands/start.d.ts +2 -0
  15. package/dist/cli/commands/start.js +32 -0
  16. package/dist/cli/commands/sync.d.ts +2 -0
  17. package/dist/cli/commands/sync.js +17 -0
  18. package/dist/cli/commands/uninstall.d.ts +2 -0
  19. package/dist/cli/commands/uninstall.js +39 -0
  20. package/dist/cli/index.d.ts +2 -0
  21. package/dist/cli/index.js +114 -0
  22. package/dist/core/ConfigurationProvider.d.ts +31 -0
  23. package/dist/core/ConfigurationProvider.js +416 -0
  24. package/dist/core/InstallationService.d.ts +17 -0
  25. package/dist/core/InstallationService.js +144 -0
  26. package/dist/core/MCPManager.d.ts +17 -0
  27. package/dist/core/MCPManager.js +98 -0
  28. package/dist/core/RequirementService.d.ts +45 -0
  29. package/dist/core/RequirementService.js +123 -0
  30. package/dist/core/constants.d.ts +29 -0
  31. package/dist/core/constants.js +55 -0
  32. package/dist/core/installers/BaseInstaller.d.ts +73 -0
  33. package/dist/core/installers/BaseInstaller.js +247 -0
  34. package/dist/core/installers/ClientInstaller.d.ts +17 -0
  35. package/dist/core/installers/ClientInstaller.js +307 -0
  36. package/dist/core/installers/CommandInstaller.d.ts +36 -0
  37. package/dist/core/installers/CommandInstaller.js +170 -0
  38. package/dist/core/installers/GeneralInstaller.d.ts +32 -0
  39. package/dist/core/installers/GeneralInstaller.js +87 -0
  40. package/dist/core/installers/InstallerFactory.d.ts +52 -0
  41. package/dist/core/installers/InstallerFactory.js +95 -0
  42. package/dist/core/installers/NpmInstaller.d.ts +25 -0
  43. package/dist/core/installers/NpmInstaller.js +123 -0
  44. package/dist/core/installers/PipInstaller.d.ts +25 -0
  45. package/dist/core/installers/PipInstaller.js +114 -0
  46. package/dist/core/installers/RequirementInstaller.d.ts +32 -0
  47. package/dist/core/installers/RequirementInstaller.js +3 -0
  48. package/dist/core/installers/index.d.ts +6 -0
  49. package/dist/core/installers/index.js +7 -0
  50. package/dist/core/types.d.ts +152 -0
  51. package/dist/core/types.js +16 -0
  52. package/dist/index.d.ts +11 -0
  53. package/dist/index.js +19 -0
  54. package/dist/services/InstallRequestValidator.d.ts +21 -0
  55. package/dist/services/InstallRequestValidator.js +99 -0
  56. package/dist/services/ServerService.d.ts +47 -0
  57. package/dist/services/ServerService.js +145 -0
  58. package/dist/utils/UpdateCheckTracker.d.ts +39 -0
  59. package/dist/utils/UpdateCheckTracker.js +80 -0
  60. package/dist/utils/clientUtils.d.ts +29 -0
  61. package/dist/utils/clientUtils.js +105 -0
  62. package/dist/utils/feedUtils.d.ts +5 -0
  63. package/dist/utils/feedUtils.js +29 -0
  64. package/dist/utils/githubAuth.d.ts +1 -0
  65. package/dist/utils/githubAuth.js +123 -0
  66. package/dist/utils/logger.d.ts +14 -0
  67. package/dist/utils/logger.js +90 -0
  68. package/dist/utils/osUtils.d.ts +16 -0
  69. package/dist/utils/osUtils.js +235 -0
  70. package/dist/web/public/css/modal.css +250 -0
  71. package/dist/web/public/css/notifications.css +70 -0
  72. package/dist/web/public/index.html +157 -0
  73. package/dist/web/public/js/api.js +213 -0
  74. package/dist/web/public/js/modal.js +572 -0
  75. package/dist/web/public/js/notifications.js +99 -0
  76. package/dist/web/public/js/serverCategoryDetails.js +210 -0
  77. package/dist/web/public/js/serverCategoryList.js +82 -0
  78. package/dist/web/public/modal.html +61 -0
  79. package/dist/web/public/styles.css +155 -0
  80. package/dist/web/server.d.ts +5 -0
  81. package/dist/web/server.js +150 -0
  82. package/package.json +53 -0
  83. package/src/cli/commands/install.ts +140 -0
  84. package/src/cli/commands/list.ts +112 -0
  85. package/src/cli/commands/pull.ts +16 -0
  86. package/src/cli/commands/serve.ts +37 -0
  87. package/src/cli/commands/uninstall.ts +54 -0
  88. package/src/cli/index.ts +127 -0
  89. package/src/core/ConfigurationProvider.ts +489 -0
  90. package/src/core/InstallationService.ts +173 -0
  91. package/src/core/MCPManager.ts +134 -0
  92. package/src/core/RequirementService.ts +147 -0
  93. package/src/core/constants.ts +61 -0
  94. package/src/core/installers/BaseInstaller.ts +292 -0
  95. package/src/core/installers/ClientInstaller.ts +423 -0
  96. package/src/core/installers/CommandInstaller.ts +185 -0
  97. package/src/core/installers/GeneralInstaller.ts +89 -0
  98. package/src/core/installers/InstallerFactory.ts +109 -0
  99. package/src/core/installers/NpmInstaller.ts +128 -0
  100. package/src/core/installers/PipInstaller.ts +121 -0
  101. package/src/core/installers/RequirementInstaller.ts +38 -0
  102. package/src/core/installers/index.ts +9 -0
  103. package/src/core/types.ts +163 -0
  104. package/src/index.ts +44 -0
  105. package/src/services/InstallRequestValidator.ts +112 -0
  106. package/src/services/ServerService.ts +181 -0
  107. package/src/utils/UpdateCheckTracker.ts +86 -0
  108. package/src/utils/clientUtils.ts +112 -0
  109. package/src/utils/feedUtils.ts +31 -0
  110. package/src/utils/githubAuth.ts +142 -0
  111. package/src/utils/logger.ts +101 -0
  112. package/src/utils/osUtils.ts +250 -0
  113. package/src/web/public/css/modal.css +250 -0
  114. package/src/web/public/css/notifications.css +70 -0
  115. package/src/web/public/index.html +157 -0
  116. package/src/web/public/js/api.js +213 -0
  117. package/src/web/public/js/modal.js +572 -0
  118. package/src/web/public/js/notifications.js +99 -0
  119. package/src/web/public/js/serverCategoryDetails.js +210 -0
  120. package/src/web/public/js/serverCategoryList.js +82 -0
  121. package/src/web/public/modal.html +61 -0
  122. package/src/web/public/styles.css +155 -0
  123. package/src/web/server.ts +195 -0
  124. package/tsconfig.json +18 -0
@@ -0,0 +1,247 @@
1
+ import path from 'path';
2
+ import fs from 'fs/promises';
3
+ import { SETTINGS_DIR } from '../constants.js';
4
+ import { extractZipFile } from '../../utils/clientUtils.js';
5
+ /**
6
+ * Abstract base class with common functionality for all requirement installers
7
+ */
8
+ export class BaseInstaller {
9
+ execPromise;
10
+ downloadsDir;
11
+ constructor(execPromise) {
12
+ this.execPromise = execPromise;
13
+ this.downloadsDir = path.join(SETTINGS_DIR, 'downloads');
14
+ }
15
+ /**
16
+ * Check if updates are available for the requirement
17
+ * @param requirement The requirement to check
18
+ * @param currentStatus The current status of the requirement
19
+ * @returns The status of the requirement with update information
20
+ */
21
+ async checkForUpdates(requirement, currentStatus) {
22
+ try {
23
+ // If requirement is not installed, no need to check for updates
24
+ if (!currentStatus.installed) {
25
+ return currentStatus;
26
+ }
27
+ // If the version doesn't contain "latest", no update check needed
28
+ if (!requirement.version.includes('latest')) {
29
+ return currentStatus;
30
+ }
31
+ let latestVersion;
32
+ // Check based on registry type
33
+ if (requirement.registry?.githubRelease) {
34
+ latestVersion = await this.getGitHubLatestVersion(requirement.registry.githubRelease.repository);
35
+ }
36
+ else {
37
+ // Use common practice based on requirement type
38
+ switch (requirement.type) {
39
+ case 'npm':
40
+ latestVersion = await this.getNpmLatestVersion(requirement.name);
41
+ break;
42
+ case 'pip':
43
+ latestVersion = await this.getPipLatestVersion(requirement.name);
44
+ break;
45
+ // Add other types as needed
46
+ }
47
+ }
48
+ // If we found a latest version and it's different from current
49
+ if (latestVersion && latestVersion !== currentStatus.version) {
50
+ return {
51
+ ...currentStatus,
52
+ availableUpdate: {
53
+ version: latestVersion,
54
+ message: `Update available: ${currentStatus.version} → ${latestVersion}`,
55
+ checkTime: new Date().toISOString()
56
+ }
57
+ };
58
+ }
59
+ return currentStatus;
60
+ }
61
+ catch (error) {
62
+ // Don't update status on error, just log it
63
+ console.warn(`Error checking for updates for ${requirement.name}: ${error instanceof Error ? error.message : String(error)}`);
64
+ return currentStatus;
65
+ }
66
+ }
67
+ /**
68
+ * Helper method to ensure downloads directory exists
69
+ */
70
+ async ensureDownloadsDir() {
71
+ await fs.mkdir(this.downloadsDir, { recursive: true });
72
+ }
73
+ /**
74
+ * Helper to handle GitHub release downloads
75
+ * @param requirement The requirement configuration
76
+ * @param registry The GitHub release registry configuration
77
+ * @returns The path to the downloaded file
78
+ */
79
+ async handleGitHubRelease(requirement, registry) {
80
+ if (!registry) {
81
+ throw new Error('GitHub release registry configuration is required');
82
+ }
83
+ await this.ensureDownloadsDir();
84
+ const { repository, assetsName, assetName } = registry;
85
+ if (!repository) {
86
+ throw new Error('GitHub repository is required for GitHub release downloads');
87
+ }
88
+ let version = requirement.version;
89
+ let resolvedAssetName = assetName || '';
90
+ let resolvedAssetsName = assetsName || '';
91
+ // Handle latest version detection
92
+ if (version.includes('${latest}') || version === 'latest') {
93
+ const { stdout } = await this.execPromise(`gh release view --repo ${repository} --json tagName --jq .tagName`);
94
+ const latestTag = stdout.trim();
95
+ let latestVersion = latestTag;
96
+ if (latestVersion.startsWith('v') && version.startsWith('v')) {
97
+ latestVersion = latestVersion.substring(1); // Remove 'v' prefix if present
98
+ // Replace ${latest} in version and asset names
99
+ version = version.replace('${latest}', latestVersion);
100
+ if (assetsName) {
101
+ resolvedAssetsName = assetsName.replace('${latest}', latestVersion);
102
+ }
103
+ if (assetName) {
104
+ resolvedAssetName = assetName.replace('${latest}', latestVersion);
105
+ }
106
+ }
107
+ }
108
+ const pattern = resolvedAssetsName ? resolvedAssetsName : resolvedAssetName;
109
+ if (!pattern) {
110
+ throw new Error('Either assetsName or assetName must be specified for GitHub release downloads');
111
+ }
112
+ // Download the release asset
113
+ const downloadPath = path.join(this.downloadsDir, path.basename(pattern));
114
+ if (!await this.fileExists(downloadPath)) {
115
+ await this.execPromise(`gh release download ${version} --repo ${repository} --pattern "${pattern}" -O "${downloadPath}"`);
116
+ }
117
+ // Handle zip file extraction if the downloaded file is a zip
118
+ if (downloadPath.endsWith('.zip')) {
119
+ const extractDir = path.join(this.downloadsDir, path.basename(pattern, '.zip'));
120
+ await fs.mkdir(extractDir, { recursive: true });
121
+ // Extract the zip file
122
+ await extractZipFile(downloadPath, { dir: extractDir });
123
+ let assetPath = '';
124
+ // If resolvedAssetName is specified, look for it in the extracted directory
125
+ if (resolvedAssetName) {
126
+ assetPath = path.join(extractDir, resolvedAssetName);
127
+ try {
128
+ await fs.access(assetPath);
129
+ return { resolvedVersion: version, resolvedPath: assetPath };
130
+ }
131
+ catch (error) {
132
+ throw new Error(`Asset ${resolvedAssetName} not found in extracted directory ${extractDir}`);
133
+ }
134
+ }
135
+ else {
136
+ assetPath = path.join(extractDir, path.basename(pattern, '.zip') + '.tgz');
137
+ }
138
+ // If no specific asset is required, return the extraction directory
139
+ return { resolvedVersion: version, resolvedPath: extractDir };
140
+ }
141
+ return { resolvedVersion: version, resolvedPath: downloadPath };
142
+ }
143
+ /**
144
+ * Helper to handle artifact registry downloads
145
+ * @param requirement The requirement configuration
146
+ * @param registry The artifacts registry configuration
147
+ * @returns The registry URL
148
+ */
149
+ async handleArtifactsRegistry(requirement, registry) {
150
+ if (!registry) {
151
+ throw new Error('Artifacts registry configuration is required');
152
+ }
153
+ const { registryUrl, assetName } = registry;
154
+ if (!registryUrl) {
155
+ throw new Error('Registry URL is required for artifacts downloads');
156
+ }
157
+ return registryUrl;
158
+ }
159
+ /**
160
+ * Helper to handle local path registry
161
+ * @param requirement The requirement configuration
162
+ * @param registry The local registry configuration
163
+ * @returns The local path or extracted asset path
164
+ */
165
+ async handleLocalRegistry(requirement, registry) {
166
+ if (!registry) {
167
+ throw new Error('Local registry configuration is required');
168
+ }
169
+ const { localPath, assetName } = registry;
170
+ if (!localPath) {
171
+ throw new Error('Local path is required for local registry');
172
+ }
173
+ // Verify the local path exists
174
+ try {
175
+ await fs.access(localPath);
176
+ }
177
+ catch (error) {
178
+ throw new Error(`Local path ${localPath} does not exist or is not accessible`);
179
+ }
180
+ // If the path is a zip file and assetName is specified, extract it
181
+ if (localPath.endsWith('.zip') && assetName) {
182
+ const extractDir = path.join(this.downloadsDir, path.basename(localPath, '.zip'));
183
+ await fs.mkdir(extractDir, { recursive: true });
184
+ // Extract the zip file
185
+ await extractZipFile(localPath, { dir: extractDir });
186
+ // Find the asset in the extracted directory
187
+ const assetPath = path.join(extractDir, assetName);
188
+ try {
189
+ await fs.access(assetPath);
190
+ return assetPath;
191
+ }
192
+ catch (error) {
193
+ throw new Error(`Asset ${assetName} not found in extracted directory ${extractDir}`);
194
+ }
195
+ }
196
+ return localPath;
197
+ }
198
+ async fileExists(filePath) {
199
+ try {
200
+ await fs.access(filePath);
201
+ return true;
202
+ }
203
+ catch {
204
+ return false;
205
+ }
206
+ }
207
+ /**
208
+ * Get the latest version available for a GitHub repository
209
+ * @param repository The GitHub repository in format 'owner/repo'
210
+ * @returns The latest version or tag
211
+ */
212
+ async getGitHubLatestVersion(repository) {
213
+ try {
214
+ // Use GitHub CLI to get the latest release
215
+ const { stdout } = await this.execPromise(`gh release view --repo ${repository} --json tagName --jq .tagName`);
216
+ const latestTag = stdout.trim();
217
+ // Remove 'v' prefix if present
218
+ return latestTag.startsWith('v') ? latestTag.substring(1) : latestTag;
219
+ }
220
+ catch (error) {
221
+ // If gh command fails, try to get the latest tag
222
+ const { stdout } = await this.execPromise(`git ls-remote --tags --refs https://github.com/${repository}.git | sort -t '/' -k 3 -V | tail -n 1 | awk -F/ '{print $3}'`);
223
+ let latestTag = stdout.trim();
224
+ // Remove 'v' prefix if present
225
+ return latestTag.startsWith('v') ? latestTag.substring(1) : latestTag;
226
+ }
227
+ }
228
+ /**
229
+ * Get the latest version available for an NPM package
230
+ * @param packageName The name of the NPM package
231
+ * @returns The latest version
232
+ */
233
+ async getNpmLatestVersion(packageName) {
234
+ const { stdout } = await this.execPromise(`npm view ${packageName} version`);
235
+ return stdout.trim();
236
+ }
237
+ /**
238
+ * Get the latest version available for a pip package
239
+ * @param packageName The name of the pip package
240
+ * @returns The latest version
241
+ */
242
+ async getPipLatestVersion(packageName) {
243
+ const { stdout } = await this.execPromise(`pip index versions ${packageName} --pre=0 | grep -oP "(?<=Latest:\\s)[^\\s]+" | head -1`);
244
+ return stdout.trim();
245
+ }
246
+ }
247
+ //# sourceMappingURL=BaseInstaller.js.map
@@ -0,0 +1,17 @@
1
+ import { ServerOperationResult, ServerInstallOptions } from '../types.js';
2
+ export declare class ClientInstaller {
3
+ private categoryName;
4
+ private serverName;
5
+ private clients;
6
+ private configProvider;
7
+ private operationStatuses;
8
+ constructor(categoryName: string, serverName: string, clients: string[]);
9
+ private generateOperationId;
10
+ private getNpmPath;
11
+ private installClient;
12
+ private processInstallation;
13
+ private installClientConfig;
14
+ private updateClineOrMSRooSettings;
15
+ private updateGithubCopilotSettings;
16
+ install(options: ServerInstallOptions): Promise<ServerOperationResult>;
17
+ }
@@ -0,0 +1,307 @@
1
+ import { ConfigurationProvider } from '../ConfigurationProvider.js';
2
+ import { SUPPORTED_CLIENTS } from '../constants.js';
3
+ import { resolveNpmModulePath, readJsonFile, writeJsonFile } from '../../utils/clientUtils.js';
4
+ import { exec } from 'child_process';
5
+ import { promisify } from 'util';
6
+ export class ClientInstaller {
7
+ categoryName;
8
+ serverName;
9
+ clients;
10
+ configProvider;
11
+ operationStatuses;
12
+ constructor(categoryName, serverName, clients) {
13
+ this.categoryName = categoryName;
14
+ this.serverName = serverName;
15
+ this.clients = clients;
16
+ this.configProvider = ConfigurationProvider.getInstance();
17
+ this.operationStatuses = new Map();
18
+ }
19
+ generateOperationId() {
20
+ return `install-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
21
+ }
22
+ async getNpmPath() {
23
+ const execAsync = promisify(exec);
24
+ try {
25
+ // Execute the get-command npm command to find the npm path
26
+ const { stdout } = await execAsync('powershell -Command "get-command npm | Select-Object -ExpandProperty Source"');
27
+ // Extract the directory from the full path (removing npm.cmd)
28
+ const npmPath = stdout.trim().replace(/\\npm\.cmd$/, '');
29
+ return npmPath;
30
+ }
31
+ catch (error) {
32
+ console.error('Error getting npm path:', error);
33
+ // Return a default path if the command fails
34
+ return 'C:\\Program Files\\nodejs';
35
+ }
36
+ }
37
+ async installClient(clientName, env) {
38
+ // Check if client is supported
39
+ if (!SUPPORTED_CLIENTS[clientName]) {
40
+ return {
41
+ status: 'failed',
42
+ type: 'install',
43
+ target: 'server',
44
+ message: `Unsupported client: ${clientName}`,
45
+ operationId: this.generateOperationId()
46
+ };
47
+ }
48
+ // Create initial operation status
49
+ const operationId = this.generateOperationId();
50
+ const initialStatus = {
51
+ status: 'pending',
52
+ type: 'install',
53
+ target: 'server',
54
+ message: `Initializing installation for client: ${clientName}`,
55
+ operationId: operationId
56
+ };
57
+ // Update server status with initial client installation status
58
+ await this.configProvider.updateServerOperationStatus(this.categoryName, this.serverName, clientName, initialStatus);
59
+ // Start the asynchronous installation process without awaiting it
60
+ this.processInstallation(clientName, operationId, env);
61
+ // Return the initial status immediately
62
+ return initialStatus;
63
+ }
64
+ async processInstallation(clientName, operationId, env) {
65
+ try {
66
+ // Check requirements before installation
67
+ let requirementsReady = await this.configProvider.isRequirementsReady(this.categoryName, this.serverName);
68
+ // If requirements are not ready, periodically check with timeout
69
+ if (!requirementsReady) {
70
+ const pendingStatus = {
71
+ status: 'pending',
72
+ type: 'install',
73
+ target: 'server',
74
+ message: `Waiting for requirements to be ready for client: ${clientName}`,
75
+ operationId: operationId
76
+ };
77
+ // Update status to pending with reference to configProvider
78
+ await this.configProvider.updateServerOperationStatus(this.categoryName, this.serverName, clientName, pendingStatus);
79
+ // Set up periodic checking with timeout
80
+ const startTime = Date.now();
81
+ const timeoutMs = 5 * 60 * 1000; // 5 minutes in milliseconds
82
+ const intervalMs = 5 * 1000; // 5 seconds in milliseconds
83
+ while (!requirementsReady && (Date.now() - startTime) < timeoutMs) {
84
+ // Wait for the interval
85
+ await new Promise(resolve => setTimeout(resolve, intervalMs));
86
+ // Check again
87
+ requirementsReady = await this.configProvider.isRequirementsReady(this.categoryName, this.serverName);
88
+ }
89
+ // If still not ready after timeout, update status as failed and exit
90
+ if (!requirementsReady) {
91
+ const failedStatus = {
92
+ status: 'failed',
93
+ type: 'install',
94
+ target: 'server',
95
+ message: `Timed out waiting for requirements to be ready for client: ${clientName} after 5 minutes`,
96
+ operationId: operationId
97
+ };
98
+ await this.configProvider.updateServerOperationStatus(this.categoryName, this.serverName, clientName, failedStatus);
99
+ return; // Exit the installation process
100
+ }
101
+ }
102
+ // If we've reached here, requirements are ready - update status to in-progress
103
+ const inProgressStatus = {
104
+ status: 'in-progress',
105
+ type: 'install',
106
+ target: 'server',
107
+ message: `Installing client: ${clientName}`,
108
+ operationId: operationId
109
+ };
110
+ await this.configProvider.updateServerOperationStatus(this.categoryName, this.serverName, clientName, inProgressStatus);
111
+ // Get feed configuration for the server
112
+ const feedConfiguration = await this.configProvider.getFeedConfiguration(this.categoryName);
113
+ if (!feedConfiguration) {
114
+ const errorStatus = {
115
+ status: 'failed',
116
+ type: 'install',
117
+ target: 'server',
118
+ message: `Failed to get feed configuration for category: ${this.categoryName}`,
119
+ operationId: operationId
120
+ };
121
+ await this.configProvider.updateServerOperationStatus(this.categoryName, this.serverName, clientName, errorStatus);
122
+ return;
123
+ }
124
+ // Find the server config in the feed configuration
125
+ const serverConfig = feedConfiguration.mcpServers.find(s => s.name === this.serverName);
126
+ if (!serverConfig) {
127
+ const errorStatus = {
128
+ status: 'failed',
129
+ type: 'install',
130
+ target: 'server',
131
+ message: `Server ${this.serverName} not found in feed configuration`,
132
+ operationId: operationId
133
+ };
134
+ await this.configProvider.updateServerOperationStatus(this.categoryName, this.serverName, clientName, errorStatus);
135
+ return;
136
+ }
137
+ try {
138
+ // Install client-specific configuration
139
+ const result = await this.installClientConfig(clientName, env || {}, serverConfig, feedConfiguration);
140
+ const finalStatus = {
141
+ status: result.success ? 'completed' : 'failed',
142
+ type: 'install',
143
+ target: 'server',
144
+ message: result.message || `Installation for client ${clientName}: ${result.success ? 'successful' : 'failed'}`,
145
+ operationId: operationId,
146
+ error: result.success ? undefined : result.message
147
+ };
148
+ await this.configProvider.updateServerOperationStatus(this.categoryName, this.serverName, clientName, finalStatus);
149
+ }
150
+ catch (error) {
151
+ const errorStatus = {
152
+ status: 'failed',
153
+ type: 'install',
154
+ target: 'server',
155
+ message: `Failed to install client: ${clientName}. Error: ${error instanceof Error ? error.message : String(error)}`,
156
+ operationId: operationId,
157
+ error: error instanceof Error ? error.message : String(error)
158
+ };
159
+ await this.configProvider.updateServerOperationStatus(this.categoryName, this.serverName, clientName, errorStatus);
160
+ }
161
+ }
162
+ catch (error) {
163
+ const errorStatus = {
164
+ status: 'failed',
165
+ type: 'install',
166
+ target: 'server',
167
+ message: `Unexpected error in installation process for client: ${clientName}. Error: ${error instanceof Error ? error.message : String(error)}`,
168
+ operationId: operationId,
169
+ error: error instanceof Error ? error.message : String(error)
170
+ };
171
+ await this.configProvider.updateServerOperationStatus(this.categoryName, this.serverName, clientName, errorStatus);
172
+ }
173
+ }
174
+ async installClientConfig(clientName, env, serverConfig, feedConfig) {
175
+ try {
176
+ if (!SUPPORTED_CLIENTS[clientName]) {
177
+ return { success: false, message: `Unsupported client: ${clientName}` };
178
+ }
179
+ const clientSettings = SUPPORTED_CLIENTS[clientName];
180
+ // Determine which setting path to use based on VS Code type (regular or insiders)
181
+ const settingPath = process.env.CODE_INSIDERS
182
+ ? clientSettings.codeInsiderSettingPath
183
+ : clientSettings.codeSettingPath;
184
+ if (!settingPath) {
185
+ return { success: false, message: `No settings path found for client: ${clientName}` };
186
+ }
187
+ // Clone the installation configuration to make modifications
188
+ const installConfig = JSON.parse(JSON.stringify(serverConfig.installation));
189
+ // Replace template variables in args
190
+ installConfig.args = installConfig.args.map((arg) => resolveNpmModulePath(arg));
191
+ // Add environment variables from options
192
+ installConfig.env = {};
193
+ if (serverConfig.installation.env) {
194
+ // Add default env variables from config
195
+ for (const [key, config] of Object.entries(serverConfig.installation.env)) {
196
+ const envConfig = config; // Type assertion for dynamic access
197
+ if (envConfig.Default) {
198
+ installConfig.env[key] = envConfig.Default;
199
+ }
200
+ }
201
+ }
202
+ // Override with provided env variables
203
+ if (env) {
204
+ Object.assign(installConfig.env, env);
205
+ }
206
+ // Update client-specific settings
207
+ if (clientName === 'MSRooCode' || clientName === 'Cline') {
208
+ await this.updateClineOrMSRooSettings(settingPath, this.serverName, installConfig, clientName);
209
+ }
210
+ else if (clientName === 'GithubCopilot') {
211
+ await this.updateGithubCopilotSettings(settingPath, this.serverName, installConfig);
212
+ }
213
+ else {
214
+ return {
215
+ success: false,
216
+ message: `Client ${clientName} is supported but no implementation exists for updating its settings`
217
+ };
218
+ }
219
+ return {
220
+ success: true,
221
+ message: `Successfully installed ${this.serverName} for client: ${clientName}`
222
+ };
223
+ }
224
+ catch (error) {
225
+ return {
226
+ success: false,
227
+ message: `Error installing client ${clientName}: ${error instanceof Error ? error.message : String(error)}`
228
+ };
229
+ }
230
+ }
231
+ async updateClineOrMSRooSettings(settingPath, serverName, installConfig, clientName) {
232
+ // Read the Cline/MSRoo settings file
233
+ const settings = await readJsonFile(settingPath, true);
234
+ // Initialize mcpServers section if it doesn't exist
235
+ if (!settings.mcpServers) {
236
+ settings.mcpServers = {};
237
+ }
238
+ // Special handling for Windows when command is npx for Cline and MSROO clients
239
+ const serverConfig = { ...installConfig };
240
+ if (process.platform === 'win32' &&
241
+ serverConfig.command === 'npx' &&
242
+ (clientName === 'Cline' || clientName === 'MSRooCode' || clientName === 'MSROO')) {
243
+ // Update command to cmd
244
+ serverConfig.command = 'cmd';
245
+ // Add /c and npx at the beginning of args
246
+ serverConfig.args = ['/c', 'npx', ...serverConfig.args];
247
+ // Add APPDATA environment variable pointing to npm directory
248
+ if (!serverConfig.env) {
249
+ serverConfig.env = {};
250
+ }
251
+ // Dynamically get npm path and set APPDATA to it
252
+ const npmPath = await this.getNpmPath();
253
+ serverConfig.env['APPDATA'] = npmPath;
254
+ }
255
+ // Add or update the server configuration
256
+ settings.mcpServers[serverName] = {
257
+ command: serverConfig.command,
258
+ args: serverConfig.args,
259
+ env: serverConfig.env,
260
+ autoApprove: [],
261
+ disabled: false,
262
+ alwaysAllow: []
263
+ };
264
+ // Write the updated settings back to the file
265
+ await writeJsonFile(settingPath, settings);
266
+ }
267
+ async updateGithubCopilotSettings(settingPath, serverName, installConfig) {
268
+ // Read the VS Code settings.json file
269
+ const settings = await readJsonFile(settingPath, true);
270
+ // Initialize the mcp section if it doesn't exist
271
+ if (!settings.mcp) {
272
+ settings.mcp = {
273
+ servers: {},
274
+ inputs: []
275
+ };
276
+ }
277
+ if (!settings.mcp.servers) {
278
+ settings.mcp.servers = {};
279
+ }
280
+ // Add or update the server configuration
281
+ settings.mcp.servers[serverName] = {
282
+ command: installConfig.command,
283
+ args: installConfig.args,
284
+ env: installConfig.env
285
+ };
286
+ // Write the updated settings back to the file
287
+ await writeJsonFile(settingPath, settings);
288
+ }
289
+ async install(options) {
290
+ const initialStatuses = [];
291
+ // Start installation for each client asynchronously and collect initial statuses
292
+ const installPromises = this.clients.map(async (clientName) => {
293
+ const initialStatus = await this.installClient(clientName, options.env || {});
294
+ initialStatuses.push(initialStatus);
295
+ return initialStatus;
296
+ });
297
+ // Wait for all initial statuses (but actual installation continues asynchronously)
298
+ await Promise.all(installPromises);
299
+ // Return initial result showing installations have been initiated
300
+ return {
301
+ success: true,
302
+ message: 'Client installations initiated successfully',
303
+ status: initialStatuses
304
+ };
305
+ }
306
+ }
307
+ //# sourceMappingURL=ClientInstaller.js.map
@@ -0,0 +1,36 @@
1
+ import { RequirementConfig, RequirementStatus } from '../types.js';
2
+ import { BaseInstaller } from './BaseInstaller.js';
3
+ /**
4
+ * Installer implementation for command-line tools
5
+ */
6
+ export declare class CommandInstaller extends BaseInstaller {
7
+ /**
8
+ * Mapping of command names to their package IDs
9
+ * This handles special cases where the command name differs from the package ID
10
+ */
11
+ private commandMappings;
12
+ /**
13
+ * Check if this installer can handle the given requirement type
14
+ * @param requirement The requirement to check
15
+ * @returns True if this installer can handle the requirement
16
+ */
17
+ canHandle(requirement: RequirementConfig): boolean;
18
+ /**
19
+ * Get the mapped package ID for a command
20
+ * @param commandName The command name to map
21
+ * @returns The mapped package ID
22
+ */
23
+ private getMappedPackageId;
24
+ /**
25
+ * Check if the command is already installed
26
+ * @param requirement The requirement to check
27
+ * @returns The status of the requirement
28
+ */
29
+ checkInstallation(requirement: RequirementConfig): Promise<RequirementStatus>;
30
+ /**
31
+ * Install the command
32
+ * @param requirement The requirement to install
33
+ * @returns The status of the installation
34
+ */
35
+ install(requirement: RequirementConfig): Promise<RequirementStatus>;
36
+ }