imcp 0.0.4 → 0.0.6

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 (88) hide show
  1. package/README.md +3 -4
  2. package/dist/cli/commands/install.js +2 -0
  3. package/dist/cli/commands/list.js +1 -0
  4. package/dist/cli/index.js +1 -2
  5. package/dist/core/ConfigurationLoader.d.ts +32 -0
  6. package/dist/core/ConfigurationLoader.js +213 -0
  7. package/dist/core/ConfigurationProvider.d.ts +2 -3
  8. package/dist/core/ConfigurationProvider.js +13 -182
  9. package/dist/core/InstallationService.d.ts +8 -0
  10. package/dist/core/InstallationService.js +124 -96
  11. package/dist/core/RequirementService.d.ts +1 -1
  12. package/dist/core/RequirementService.js +5 -9
  13. package/dist/core/constants.js +14 -1
  14. package/dist/core/installers/BaseInstaller.d.ts +5 -4
  15. package/dist/core/installers/BaseInstaller.js +17 -28
  16. package/dist/core/installers/ClientInstaller.js +134 -43
  17. package/dist/core/installers/CommandInstaller.d.ts +1 -0
  18. package/dist/core/installers/CommandInstaller.js +3 -0
  19. package/dist/core/installers/GeneralInstaller.d.ts +1 -0
  20. package/dist/core/installers/GeneralInstaller.js +3 -0
  21. package/dist/core/installers/InstallerFactory.d.ts +9 -7
  22. package/dist/core/installers/InstallerFactory.js +10 -8
  23. package/dist/core/installers/NpmInstaller.d.ts +1 -0
  24. package/dist/core/installers/NpmInstaller.js +3 -0
  25. package/dist/core/installers/PipInstaller.d.ts +6 -3
  26. package/dist/core/installers/PipInstaller.js +21 -8
  27. package/dist/core/installers/RequirementInstaller.d.ts +4 -3
  28. package/dist/core/installers/clients/ClientInstaller.d.ts +23 -0
  29. package/dist/core/installers/clients/ClientInstaller.js +573 -0
  30. package/dist/core/installers/clients/ExtensionInstaller.d.ts +26 -0
  31. package/dist/core/installers/clients/ExtensionInstaller.js +149 -0
  32. package/dist/core/installers/index.d.ts +8 -6
  33. package/dist/core/installers/index.js +8 -6
  34. package/dist/core/installers/requirements/BaseInstaller.d.ts +59 -0
  35. package/dist/core/installers/requirements/BaseInstaller.js +168 -0
  36. package/dist/core/installers/requirements/CommandInstaller.d.ts +37 -0
  37. package/dist/core/installers/requirements/CommandInstaller.js +173 -0
  38. package/dist/core/installers/requirements/GeneralInstaller.d.ts +33 -0
  39. package/dist/core/installers/requirements/GeneralInstaller.js +86 -0
  40. package/dist/core/installers/requirements/InstallerFactory.d.ts +54 -0
  41. package/dist/core/installers/requirements/InstallerFactory.js +97 -0
  42. package/dist/core/installers/requirements/NpmInstaller.d.ts +26 -0
  43. package/dist/core/installers/requirements/NpmInstaller.js +128 -0
  44. package/dist/core/installers/requirements/PipInstaller.d.ts +28 -0
  45. package/dist/core/installers/requirements/PipInstaller.js +128 -0
  46. package/{src/core/installers/RequirementInstaller.ts → dist/core/installers/requirements/RequirementInstaller.d.ts} +33 -38
  47. package/dist/core/installers/requirements/RequirementInstaller.js +3 -0
  48. package/dist/core/types.d.ts +4 -1
  49. package/dist/services/ServerService.js +1 -1
  50. package/dist/utils/githubUtils.d.ts +11 -0
  51. package/dist/utils/githubUtils.js +88 -0
  52. package/dist/utils/osUtils.d.ts +17 -0
  53. package/dist/utils/osUtils.js +184 -0
  54. package/dist/web/public/css/modal.css +97 -3
  55. package/dist/web/public/index.html +21 -2
  56. package/dist/web/public/js/modal.js +177 -28
  57. package/dist/web/public/js/serverCategoryDetails.js +12 -10
  58. package/dist/web/public/js/serverCategoryList.js +62 -12
  59. package/dist/web/public/modal.html +27 -13
  60. package/package.json +1 -1
  61. package/src/cli/commands/install.ts +4 -2
  62. package/src/cli/commands/list.ts +1 -0
  63. package/src/cli/index.ts +1 -1
  64. package/src/core/ConfigurationLoader.ts +251 -0
  65. package/src/core/ConfigurationProvider.ts +13 -195
  66. package/src/core/InstallationService.ts +140 -106
  67. package/src/core/RequirementService.ts +5 -10
  68. package/src/core/constants.ts +15 -1
  69. package/src/core/installers/{ClientInstaller.ts → clients/ClientInstaller.ts} +157 -51
  70. package/src/core/installers/clients/ExtensionInstaller.ts +162 -0
  71. package/src/core/installers/index.ts +9 -7
  72. package/src/core/installers/{BaseInstaller.ts → requirements/BaseInstaller.ts} +10 -118
  73. package/src/core/installers/{CommandInstaller.ts → requirements/CommandInstaller.ts} +7 -3
  74. package/src/core/installers/{GeneralInstaller.ts → requirements/GeneralInstaller.ts} +6 -2
  75. package/src/core/installers/{InstallerFactory.ts → requirements/InstallerFactory.ts} +11 -9
  76. package/src/core/installers/{NpmInstaller.ts → requirements/NpmInstaller.ts} +7 -4
  77. package/src/core/installers/{PipInstaller.ts → requirements/PipInstaller.ts} +26 -10
  78. package/src/core/installers/requirements/RequirementInstaller.ts +41 -0
  79. package/src/core/types.ts +4 -1
  80. package/src/services/ServerService.ts +1 -1
  81. package/src/utils/githubUtils.ts +103 -0
  82. package/src/utils/osUtils.ts +206 -15
  83. package/src/web/public/css/modal.css +97 -3
  84. package/src/web/public/index.html +21 -2
  85. package/src/web/public/js/modal.js +177 -28
  86. package/src/web/public/js/serverCategoryDetails.js +12 -10
  87. package/src/web/public/js/serverCategoryList.js +62 -12
  88. package/src/web/public/modal.html +27 -13
@@ -0,0 +1,573 @@
1
+ import { ExtensionInstaller } from './ExtensionInstaller.js';
2
+ import { GetBrowserPath } from '../../../utils/osUtils.js';
3
+ import { ConfigurationProvider } from '../../ConfigurationProvider.js';
4
+ import { SUPPORTED_CLIENTS } from '../../constants.js';
5
+ import { resolveNpmModulePath, readJsonFile, writeJsonFile } from '../../../utils/clientUtils.js';
6
+ import { exec } from 'child_process';
7
+ import { promisify } from 'util';
8
+ import { Logger } from '../../../utils/logger.js';
9
+ import { getPythonPackagePath, getSystemPythonPackageDirectory } from '../../../utils/osUtils.js';
10
+ const execAsync = promisify(exec); // Moved promisify here for reuse
11
+ export class ClientInstaller {
12
+ categoryName;
13
+ serverName;
14
+ clients;
15
+ configProvider;
16
+ operationStatuses;
17
+ constructor(categoryName, serverName, clients) {
18
+ this.categoryName = categoryName;
19
+ this.serverName = serverName;
20
+ this.clients = clients;
21
+ this.configProvider = ConfigurationProvider.getInstance();
22
+ this.operationStatuses = new Map();
23
+ }
24
+ generateOperationId() {
25
+ return `install-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
26
+ }
27
+ async getNpmPath() {
28
+ try {
29
+ // Execute the get-command npm command to find the npm path
30
+ const { stdout } = await execAsync('powershell -Command "get-command npm | Select-Object -ExpandProperty Source"');
31
+ // Extract the directory from the full path (removing npm.cmd)
32
+ const npmPath = stdout.trim().replace(/\\npm\.cmd$/, '');
33
+ return npmPath;
34
+ }
35
+ catch (error) {
36
+ console.error('Error getting npm path:', error);
37
+ // Return a default path if the command fails
38
+ return 'C:\\Program Files\\nodejs';
39
+ }
40
+ }
41
+ /**
42
+ * Check if a command is available on the system
43
+ * @param command The command to check
44
+ * @returns True if the command is available, false otherwise
45
+ */
46
+ async isCommandAvailable(command) {
47
+ try {
48
+ if (process.platform === 'win32') {
49
+ // Windows-specific command check
50
+ await execAsync(`where ${command}`);
51
+ }
52
+ else if (process.platform === 'darwin' && (command === 'code' || command === 'code-insiders')) {
53
+ // macOS-specific VS Code check
54
+ const vscodePath = command === 'code' ?
55
+ '/Applications/Visual Studio Code.app' :
56
+ '/Applications/Visual Studio Code - Insiders.app';
57
+ await execAsync(`test -d "${vscodePath}"`);
58
+ }
59
+ else {
60
+ // Unix-like systems
61
+ await execAsync(`which ${command}`);
62
+ }
63
+ return true;
64
+ }
65
+ catch (error) {
66
+ if (process.platform === 'darwin' && (command === 'code' || command === 'code-insiders')) {
67
+ // Try checking in ~/Applications as well for macOS
68
+ try {
69
+ const homedir = process.env.HOME;
70
+ const vscodePath = command === 'code' ?
71
+ `${homedir}/Applications/Visual Studio Code.app` :
72
+ `${homedir}/Applications/Visual Studio Code - Insiders.app`;
73
+ await execAsync(`test -d "${vscodePath}"`);
74
+ return true;
75
+ }
76
+ catch (error) {
77
+ return false;
78
+ }
79
+ }
80
+ return false;
81
+ }
82
+ }
83
+ // Modified to accept ServerInstallOptions
84
+ async installClient(clientName, options) {
85
+ // Check if client is supported
86
+ if (!SUPPORTED_CLIENTS[clientName]) {
87
+ return {
88
+ status: 'failed',
89
+ type: 'install',
90
+ target: 'server',
91
+ message: `Unsupported client: ${clientName}`,
92
+ operationId: this.generateOperationId()
93
+ };
94
+ }
95
+ // Create initial operation status
96
+ const operationId = this.generateOperationId();
97
+ const initialStatus = {
98
+ status: 'pending',
99
+ type: 'install',
100
+ target: 'server',
101
+ message: `Initializing installation for client: ${clientName}`,
102
+ operationId: operationId
103
+ };
104
+ // Update server status with initial client installation status
105
+ await this.configProvider.updateServerOperationStatus(this.categoryName, this.serverName, clientName, initialStatus);
106
+ // Start the asynchronous installation process without awaiting it
107
+ // Pass options down
108
+ this.processInstallation(clientName, operationId, options);
109
+ // Return the initial status immediately
110
+ return initialStatus;
111
+ }
112
+ // Modified to accept ServerInstallOptions
113
+ async processInstallation(clientName, operationId, options) {
114
+ try {
115
+ await ExtensionInstaller.installExtension(clientName);
116
+ // Check requirements before installation
117
+ let requirementsReady = await this.configProvider.isRequirementsReady(this.categoryName, this.serverName);
118
+ // If requirements are not ready, periodically check with timeout
119
+ if (!requirementsReady) {
120
+ const pendingStatus = {
121
+ status: 'pending',
122
+ type: 'install',
123
+ target: 'server',
124
+ message: `Waiting for requirements to be ready for client: ${clientName}`,
125
+ operationId: operationId
126
+ };
127
+ // Update status to pending with reference to configProvider
128
+ await this.configProvider.updateServerOperationStatus(this.categoryName, this.serverName, clientName, pendingStatus);
129
+ // Set up periodic checking with timeout
130
+ const startTime = Date.now();
131
+ const timeoutMs = 5 * 60 * 1000; // 5 minutes in milliseconds
132
+ const intervalMs = 5 * 1000; // 5 seconds in milliseconds
133
+ while (!requirementsReady && (Date.now() - startTime) < timeoutMs) {
134
+ // Wait for the interval
135
+ await new Promise(resolve => setTimeout(resolve, intervalMs));
136
+ // Check again
137
+ requirementsReady = await this.configProvider.isRequirementsReady(this.categoryName, this.serverName);
138
+ }
139
+ // If still not ready after timeout, update status as failed and exit
140
+ if (!requirementsReady) {
141
+ const failedStatus = {
142
+ status: 'failed',
143
+ type: 'install',
144
+ target: 'server',
145
+ message: `Timed out waiting for requirements to be ready for client: ${clientName} after 5 minutes`,
146
+ operationId: operationId
147
+ };
148
+ await this.configProvider.updateServerOperationStatus(this.categoryName, this.serverName, clientName, failedStatus);
149
+ return; // Exit the installation process
150
+ }
151
+ }
152
+ // If we've reached here, requirements are ready - update status to in-progress
153
+ const inProgressStatus = {
154
+ status: 'in-progress',
155
+ type: 'install',
156
+ target: 'server',
157
+ message: `Installing client: ${clientName}`,
158
+ operationId: operationId
159
+ };
160
+ await this.configProvider.updateServerOperationStatus(this.categoryName, this.serverName, clientName, inProgressStatus);
161
+ // Get feed configuration for the server
162
+ const feedConfiguration = await this.configProvider.getFeedConfiguration(this.categoryName);
163
+ if (!feedConfiguration) {
164
+ const errorStatus = {
165
+ status: 'failed',
166
+ type: 'install',
167
+ target: 'server',
168
+ message: `Failed to get feed configuration for category: ${this.categoryName}`,
169
+ operationId: operationId
170
+ };
171
+ await this.configProvider.updateServerOperationStatus(this.categoryName, this.serverName, clientName, errorStatus);
172
+ return;
173
+ }
174
+ // Find the server config in the feed configuration
175
+ const serverConfig = feedConfiguration.mcpServers.find(s => s.name === this.serverName);
176
+ if (!serverConfig) {
177
+ const errorStatus = {
178
+ status: 'failed',
179
+ type: 'install',
180
+ target: 'server',
181
+ message: `Server ${this.serverName} not found in feed configuration`,
182
+ operationId: operationId
183
+ };
184
+ await this.configProvider.updateServerOperationStatus(this.categoryName, this.serverName, clientName, errorStatus);
185
+ return;
186
+ }
187
+ try {
188
+ // Install extension if available
189
+ if (SUPPORTED_CLIENTS[clientName]?.extension) {
190
+ const extensionResult = await ExtensionInstaller.installExtension(clientName);
191
+ if (!extensionResult) {
192
+ Logger.debug(`Failed to install extension for client ${clientName}`);
193
+ }
194
+ }
195
+ // Install client-specific configuration, passing options down
196
+ const result = await this.installClientConfig(clientName, options, serverConfig, feedConfiguration);
197
+ const finalStatus = {
198
+ status: result.success ? 'completed' : 'failed',
199
+ type: 'install',
200
+ target: 'server',
201
+ message: result.message || `Installation for client ${clientName}: ${result.success ? 'successful' : 'failed'}`,
202
+ operationId: operationId,
203
+ error: result.success ? undefined : result.message
204
+ };
205
+ await this.configProvider.updateServerOperationStatus(this.categoryName, this.serverName, clientName, finalStatus);
206
+ if (result.success) {
207
+ await this.configProvider.reloadClientMCPSettings();
208
+ }
209
+ }
210
+ catch (error) {
211
+ const errorStatus = {
212
+ status: 'failed',
213
+ type: 'install',
214
+ target: 'server',
215
+ message: `Failed to install client: ${clientName}. Error: ${error instanceof Error ? error.message : String(error)}`,
216
+ operationId: operationId,
217
+ error: error instanceof Error ? error.message : String(error)
218
+ };
219
+ await this.configProvider.updateServerOperationStatus(this.categoryName, this.serverName, clientName, errorStatus);
220
+ }
221
+ }
222
+ catch (error) {
223
+ const errorStatus = {
224
+ status: 'failed',
225
+ type: 'install',
226
+ target: 'server',
227
+ message: `Unexpected error in installation process for client: ${clientName}. Error: ${error instanceof Error ? error.message : String(error)}`,
228
+ operationId: operationId,
229
+ error: error instanceof Error ? error.message : String(error)
230
+ };
231
+ await this.configProvider.updateServerOperationStatus(this.categoryName, this.serverName, clientName, errorStatus);
232
+ }
233
+ }
234
+ // Modified to accept ServerInstallOptions
235
+ async installClientConfig(clientName, options, // Use options directly
236
+ serverConfig, feedConfig) {
237
+ try {
238
+ if (!SUPPORTED_CLIENTS[clientName]) {
239
+ Logger.debug(`Client ${clientName} is not supported.`);
240
+ return { success: false, message: `Unsupported client: ${clientName}` };
241
+ }
242
+ const clientSettings = SUPPORTED_CLIENTS[clientName];
243
+ // Get both setting paths for VS Code and VS Code Insiders
244
+ const regularSettingPath = clientSettings.codeSettingPath;
245
+ const insidersSettingPath = clientSettings.codeInsiderSettingPath;
246
+ Logger.debug(`Starting installation of ${this.serverName} for client ${clientName}`);
247
+ Logger.debug(`VS Code settings path configured as: ${regularSettingPath}`);
248
+ Logger.debug(`VS Code Insiders settings path configured as: ${insidersSettingPath}`);
249
+ // Check if VS Code and VS Code Insiders are installed
250
+ const isVSCodeInstalled = await this.isCommandAvailable('code');
251
+ const isVSCodeInsidersInstalled = await this.isCommandAvailable('code-insiders');
252
+ Logger.debug(isVSCodeInstalled ? 'VS Code detected on system' : 'VS Code not detected on system');
253
+ Logger.debug(isVSCodeInsidersInstalled ? 'VS Code Insiders detected on system' : 'VS Code Insiders not detected on system');
254
+ // If neither is installed, we can't proceed
255
+ if (!isVSCodeInstalled && !isVSCodeInsidersInstalled) {
256
+ Logger.debug('No VS Code installations detected on system. Cannot update settings.');
257
+ return {
258
+ success: false,
259
+ message: `Neither VS Code nor VS Code Insiders are installed on this system. Cannot update settings for client: ${clientName}. Please install VS Code or VS Code Insiders and try again.`
260
+ };
261
+ }
262
+ // --- Start of new logic ---
263
+ // Clone the base installation configuration to avoid modifying the original serverConfig
264
+ const installConfig = JSON.parse(JSON.stringify(serverConfig.installation));
265
+ const pythonEnv = options.settings?.pythonEnv;
266
+ let pythonDir = null;
267
+ // 1. Determine which args to use and resolve npm paths
268
+ let finalArgs = [];
269
+ if (options.args && options.args.length > 0) {
270
+ Logger.debug(`Using args from ServerInstallOptions: ${options.args.join(' ')}`);
271
+ finalArgs = options.args.map(arg => resolveNpmModulePath(arg));
272
+ }
273
+ else if (installConfig.args && installConfig.args.length > 0) {
274
+ Logger.debug(`Using args from serverConfig.installation: ${installConfig.args.join(' ')}`);
275
+ finalArgs = installConfig.args.map((arg) => resolveNpmModulePath(arg));
276
+ }
277
+ else {
278
+ Logger.debug('No args found in options or serverConfig.installation.');
279
+ finalArgs = [];
280
+ }
281
+ // 2. Handle pythonEnv settings
282
+ if (pythonEnv) {
283
+ // 2.1 If pythonEnv is set
284
+ Logger.debug(`Python environment specified: ${pythonEnv}`);
285
+ pythonDir = getPythonPackagePath(pythonEnv);
286
+ // 2.1.1 Replace ${PYTHON_PACKAGE} in args
287
+ if (pythonDir) {
288
+ finalArgs = finalArgs.map(arg => arg.includes('${PYTHON_PACKAGE}') ? arg.replace('${PYTHON_PACKAGE}', pythonDir) : arg);
289
+ Logger.debug(`Args after PYTHON_PACKAGE replacement (using pythonEnv): ${finalArgs.join(' ')}`);
290
+ }
291
+ else {
292
+ Logger.debug(`Could not determine directory for pythonEnv: ${pythonEnv}`);
293
+ }
294
+ // 2.1.2 Replace command if it's 'python'
295
+ if (installConfig.command === 'python') {
296
+ Logger.debug(`Replacing command 'python' with specified pythonEnv: ${pythonEnv}`);
297
+ installConfig.command = pythonEnv;
298
+ }
299
+ }
300
+ else {
301
+ // 2.2 If pythonEnv is not set
302
+ Logger.debug('No Python environment specified in settings.');
303
+ // 2.2.1 Replace ${PYTHON_PACKAGE} with system python directory if needed
304
+ if (finalArgs.some(arg => arg.includes('${PYTHON_PACKAGE}'))) {
305
+ Logger.debug('Attempting to find system Python directory for ${PYTHON_PACKAGE} replacement.');
306
+ pythonDir = await getSystemPythonPackageDirectory();
307
+ if (pythonDir) {
308
+ finalArgs = finalArgs.map(arg => arg.includes('${PYTHON_PACKAGE}') ? arg.replace('${PYTHON_PACKAGE}', pythonDir) : arg);
309
+ Logger.debug(`Args after PYTHON_PACKAGE replacement (using system python): ${finalArgs.join(' ')}`);
310
+ }
311
+ else {
312
+ Logger.debug('Could not find system Python directory. ${PYTHON_PACKAGE} replacement skipped.');
313
+ // Optionally, remove or handle the arg containing ${PYTHON_PACKAGE} if Python is required
314
+ // finalArgs = finalArgs.filter(arg => !arg.includes('${PYTHON_PACKAGE}'));
315
+ }
316
+ }
317
+ }
318
+ // Update installConfig with potentially modified args
319
+ installConfig.args = finalArgs;
320
+ // 3. Handle environment variables (merge default, serverConfig, and options.env)
321
+ const baseEnv = serverConfig.installation.env || {};
322
+ const defaultEnv = {};
323
+ for (const [key, config] of Object.entries(baseEnv)) {
324
+ const envConfig = config; // Type assertion
325
+ if (envConfig.Default) {
326
+ defaultEnv[key] = envConfig.Default;
327
+ }
328
+ }
329
+ // Merge: options.env overrides defaultEnv
330
+ installConfig.env = { ...defaultEnv, ...(options.env || {}) };
331
+ // Replace ${BROWSER_PATH} with actual browser path
332
+ const replacements = Object.entries(installConfig.env)
333
+ .filter(([_, value]) => typeof value === 'string' && value.includes('${BROWSER_PATH}'))
334
+ .map(async ([key, value]) => {
335
+ try {
336
+ const browserPath = await GetBrowserPath();
337
+ return [key, value.replace('${BROWSER_PATH}', browserPath)];
338
+ }
339
+ catch (error) {
340
+ Logger.error(`Failed to get system browser path for env var ${key}:`, error);
341
+ return [key, value];
342
+ }
343
+ });
344
+ // Wait for all replacements to complete
345
+ const replacedValues = await Promise.all(replacements);
346
+ replacedValues.forEach(([key, value]) => {
347
+ installConfig.env[key] = value;
348
+ });
349
+ Logger.debug(`Final environment variables: ${JSON.stringify(installConfig.env)}`);
350
+ // --- End of new logic ---
351
+ // Keep track of success for both installations
352
+ let regularSuccess = false;
353
+ let insidersSuccess = false;
354
+ let errorMessages = [];
355
+ // Update client-specific settings for both VS Code and VS Code Insiders
356
+ if (clientName === 'MSRooCode' || clientName === 'Cline') {
357
+ // Update VS Code settings if VS Code is installed
358
+ if (isVSCodeInstalled) {
359
+ try {
360
+ Logger.debug(`Updating VS Code settings for ${clientName} at path: ${regularSettingPath}`);
361
+ // Pass the modified installConfig
362
+ await this.updateClineOrMSRooSettings(regularSettingPath, this.serverName, installConfig, clientName);
363
+ regularSuccess = true;
364
+ Logger.debug(`Settings successfully updated to ${regularSettingPath} for ${clientName} (VS Code)`);
365
+ }
366
+ catch (error) {
367
+ const errorMsg = `Error updating VS Code settings: ${error instanceof Error ? error.message : String(error)}`;
368
+ errorMessages.push(errorMsg);
369
+ console.error(errorMsg);
370
+ }
371
+ }
372
+ // Update VS Code Insiders settings if VS Code Insiders is installed
373
+ if (isVSCodeInsidersInstalled) {
374
+ try {
375
+ Logger.debug(`Updating VS Code Insiders settings for ${clientName} at path: ${insidersSettingPath}`);
376
+ // Pass the modified installConfig
377
+ await this.updateClineOrMSRooSettings(insidersSettingPath, this.serverName, installConfig, clientName);
378
+ insidersSuccess = true;
379
+ Logger.debug(`Settings successfully updated to ${insidersSettingPath} for ${clientName} (VS Code Insiders)`);
380
+ }
381
+ catch (error) {
382
+ const errorMsg = `Error updating VS Code Insiders settings: ${error instanceof Error ? error.message : String(error)}`;
383
+ errorMessages.push(errorMsg);
384
+ console.error(errorMsg);
385
+ }
386
+ }
387
+ }
388
+ else if (clientName === 'GithubCopilot') {
389
+ // Update VS Code settings if VS Code is installed
390
+ if (isVSCodeInstalled) {
391
+ try {
392
+ Logger.debug(`Updating VS Code settings for ${clientName} at path: ${regularSettingPath}`);
393
+ // Pass the modified installConfig
394
+ await this.updateGithubCopilotSettings(regularSettingPath, this.serverName, installConfig);
395
+ regularSuccess = true;
396
+ Logger.debug(`Settings successfully updated to ${regularSettingPath} for ${clientName} (VS Code)`);
397
+ }
398
+ catch (error) {
399
+ const errorMsg = `Error updating VS Code settings: ${error instanceof Error ? error.message : String(error)}`;
400
+ errorMessages.push(errorMsg);
401
+ console.error(errorMsg);
402
+ }
403
+ }
404
+ // Update VS Code Insiders settings if VS Code Insiders is installed
405
+ if (isVSCodeInsidersInstalled) {
406
+ try {
407
+ Logger.debug(`Updating VS Code Insiders settings for ${clientName} at path: ${insidersSettingPath}`);
408
+ // Pass the modified installConfig
409
+ await this.updateGithubCopilotSettings(insidersSettingPath, this.serverName, installConfig);
410
+ insidersSuccess = true;
411
+ Logger.debug(`Settings successfully updated to ${insidersSettingPath} for ${clientName} (VS Code Insiders)`);
412
+ }
413
+ catch (error) {
414
+ const errorMsg = `Error updating VS Code Insiders settings: ${error instanceof Error ? error.message : String(error)}`;
415
+ errorMessages.push(errorMsg);
416
+ console.error(errorMsg);
417
+ }
418
+ }
419
+ }
420
+ else {
421
+ Logger.debug(`No implementation exists for updating settings for client: ${clientName}`);
422
+ return {
423
+ success: false,
424
+ message: `Client ${clientName} is supported but no implementation exists for updating its settings`
425
+ };
426
+ }
427
+ // Determine overall success status and message
428
+ const overallSuccess = regularSuccess || insidersSuccess;
429
+ let message = '';
430
+ if (overallSuccess) {
431
+ const successDetails = [];
432
+ if (regularSuccess)
433
+ successDetails.push('VS Code');
434
+ if (insidersSuccess)
435
+ successDetails.push('VS Code Insiders');
436
+ // Create a more compact success message with specific paths
437
+ const pathDetails = [];
438
+ if (regularSuccess) {
439
+ pathDetails.push(`\n[VS Code]: ${regularSettingPath}`);
440
+ }
441
+ if (insidersSuccess) {
442
+ pathDetails.push(`\n[VS Code Insiders]: ${insidersSettingPath}`);
443
+ }
444
+ message = `Successfully installed ${this.serverName} for client: ${clientName}. ${pathDetails.join(' ')}`;
445
+ // Add partial failure information if applicable
446
+ if (errorMessages.length > 0) {
447
+ message += `\nHowever, some errors occurred:\n${errorMessages.join('\n- ')}`;
448
+ }
449
+ Logger.debug(`Installation complete: ${message}`);
450
+ }
451
+ else {
452
+ // Create a more detailed failure message that includes the detection results
453
+ const detectionInfo = [];
454
+ if (!isVSCodeInstalled)
455
+ detectionInfo.push('VS Code not detected');
456
+ if (!isVSCodeInsidersInstalled)
457
+ detectionInfo.push('VS Code Insiders not detected');
458
+ if (errorMessages.length > 0) {
459
+ message = `Failed to install ${this.serverName} for client: ${clientName}.\nDetection status: [${detectionInfo.join(', ')}].\nErrors:\n- ${errorMessages.join('\n- ')}`;
460
+ }
461
+ else {
462
+ message = `Failed to install ${this.serverName} for client: ${clientName}.\nDetection status: [${detectionInfo.join(', ')}]`;
463
+ }
464
+ console.error(`Installation failed: ${message}`);
465
+ }
466
+ return {
467
+ success: overallSuccess,
468
+ message: message
469
+ };
470
+ }
471
+ catch (error) {
472
+ const errorMsg = `Error installing client ${clientName}: ${error instanceof Error ? error.message : String(error)}`;
473
+ console.error(errorMsg);
474
+ return {
475
+ success: false,
476
+ message: errorMsg
477
+ };
478
+ }
479
+ }
480
+ async updateClineOrMSRooSettings(settingPath, serverName, installConfig, // Use the processed installConfig
481
+ clientName) {
482
+ // Read the Cline/MSRoo settings file
483
+ const settings = await readJsonFile(settingPath, true);
484
+ // Initialize mcpServers section if it doesn't exist
485
+ if (!settings.mcpServers) {
486
+ settings.mcpServers = {};
487
+ }
488
+ // Special handling for Windows when command is npx for Cline and MSROO clients
489
+ // Use a copy to avoid modifying the passed installConfig directly if needed elsewhere
490
+ const serverConfigForClient = { ...installConfig };
491
+ if (process.platform === 'win32' &&
492
+ serverConfigForClient.command === 'npx' &&
493
+ (clientName === 'Cline' || clientName === 'MSRooCode' || clientName === 'MSROO')) {
494
+ // Update command to cmd
495
+ serverConfigForClient.command = 'cmd';
496
+ // Add /c and npx at the beginning of args
497
+ serverConfigForClient.args = ['/c', 'npx', ...serverConfigForClient.args];
498
+ // Add APPDATA environment variable pointing to npm directory
499
+ if (!serverConfigForClient.env) {
500
+ serverConfigForClient.env = {};
501
+ }
502
+ // Dynamically get npm path and set APPDATA to it
503
+ const npmPath = await this.getNpmPath();
504
+ serverConfigForClient.env['APPDATA'] = npmPath;
505
+ Logger.debug(`Windows npx fix: command=${serverConfigForClient.command}, args=${serverConfigForClient.args.join(' ')}, env=${JSON.stringify(serverConfigForClient.env)}`);
506
+ }
507
+ // Convert backslashes to forward slashes in args paths
508
+ if (serverConfigForClient.args) {
509
+ serverConfigForClient.args = serverConfigForClient.args.map((arg) => typeof arg === 'string' ? arg.replace(/\\/g, '/') : arg);
510
+ }
511
+ // Add or update the server configuration
512
+ settings.mcpServers[serverName] = {
513
+ command: serverConfigForClient.command,
514
+ args: serverConfigForClient.args,
515
+ env: serverConfigForClient.env,
516
+ autoApprove: [],
517
+ disabled: false,
518
+ alwaysAllow: []
519
+ };
520
+ Logger.debug(`Updating ${settingPath} for ${serverName}: ${JSON.stringify(settings.mcpServers[serverName])}`);
521
+ // Write the updated settings back to the file
522
+ await writeJsonFile(settingPath, settings);
523
+ }
524
+ async updateGithubCopilotSettings(settingPath, serverName, installConfig // Use the processed installConfig
525
+ ) {
526
+ // Read the VS Code settings.json file
527
+ const settings = await readJsonFile(settingPath, true);
528
+ // Initialize the mcp section if it doesn't exist
529
+ if (!settings.mcp) {
530
+ settings.mcp = {
531
+ servers: {},
532
+ inputs: []
533
+ };
534
+ }
535
+ if (!settings.mcp.servers) {
536
+ settings.mcp.servers = {};
537
+ }
538
+ // Use a copy to avoid modifying the passed installConfig directly
539
+ const serverConfigForClient = { ...installConfig };
540
+ // Convert backslashes to forward slashes in args paths
541
+ if (serverConfigForClient.args) {
542
+ serverConfigForClient.args = serverConfigForClient.args.map((arg) => typeof arg === 'string' ? arg.replace(/\\/g, '/') : arg);
543
+ }
544
+ // Add or update the server configuration
545
+ settings.mcp.servers[serverName] = {
546
+ command: serverConfigForClient.command,
547
+ args: serverConfigForClient.args,
548
+ env: serverConfigForClient.env
549
+ };
550
+ Logger.debug(`Updating ${settingPath} for ${serverName}: ${JSON.stringify(settings.mcp.servers[serverName])}`);
551
+ // Write the updated settings back to the file
552
+ await writeJsonFile(settingPath, settings);
553
+ }
554
+ async install(options) {
555
+ const initialStatuses = [];
556
+ // Start installation for each client asynchronously and collect initial statuses
557
+ // Pass options down to installClient
558
+ const installPromises = this.clients.map(async (clientName) => {
559
+ const initialStatus = await this.installClient(clientName, options);
560
+ initialStatuses.push(initialStatus);
561
+ return initialStatus;
562
+ });
563
+ // Wait for all initial statuses (but actual installation continues asynchronously)
564
+ await Promise.all(installPromises);
565
+ // Return initial result showing installations have been initiated
566
+ return {
567
+ success: true,
568
+ message: 'Client installations initiated successfully',
569
+ status: initialStatuses
570
+ };
571
+ }
572
+ }
573
+ //# sourceMappingURL=ClientInstaller.js.map
@@ -0,0 +1,26 @@
1
+ export declare class ExtensionInstaller {
2
+ /**
3
+ * Get VSCode path based on the OS type
4
+ */
5
+ private static getVSCodePath;
6
+ /**
7
+ * List installed extensions for VSCode or VSCode Insiders
8
+ */
9
+ private static listExtensions;
10
+ /**
11
+ * Check if an extension is installed and get its version
12
+ */
13
+ private static checkExtension;
14
+ /**
15
+ * Install extension from marketplace
16
+ */
17
+ private static installPublicExtension;
18
+ /**
19
+ * Install extension from VSIX file
20
+ */
21
+ private static installPrivateExtension;
22
+ /**
23
+ * Install extension for a specific client
24
+ */
25
+ static installExtension(clientName: string): Promise<boolean>;
26
+ }