imcp 0.0.4 → 0.0.5

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 +20 -5
  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 +20 -5
  88. package/src/web/public/modal.html +27 -13
@@ -1,9 +1,12 @@
1
+ import { GetBrowserPath } from '../../utils/osUtils.js';
1
2
  import { ConfigurationProvider } from '../ConfigurationProvider.js';
2
3
  import { SUPPORTED_CLIENTS } from '../constants.js';
3
4
  import { resolveNpmModulePath, readJsonFile, writeJsonFile } from '../../utils/clientUtils.js';
4
5
  import { exec } from 'child_process';
5
6
  import { promisify } from 'util';
6
7
  import { Logger } from '../../utils/logger.js';
8
+ import { getPythonPackagePath, getSystemPythonPackageDirectory } from '../../utils/osUtils.js';
9
+ const execAsync = promisify(exec); // Moved promisify here for reuse
7
10
  export class ClientInstaller {
8
11
  categoryName;
9
12
  serverName;
@@ -21,7 +24,6 @@ export class ClientInstaller {
21
24
  return `install-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
22
25
  }
23
26
  async getNpmPath() {
24
- const execAsync = promisify(exec);
25
27
  try {
26
28
  // Execute the get-command npm command to find the npm path
27
29
  const { stdout } = await execAsync('powershell -Command "get-command npm | Select-Object -ExpandProperty Source"');
@@ -41,7 +43,6 @@ export class ClientInstaller {
41
43
  * @returns True if the command is available, false otherwise
42
44
  */
43
45
  async isCommandAvailable(command) {
44
- const execAsync = promisify(exec);
45
46
  try {
46
47
  if (process.platform === 'win32') {
47
48
  // Windows-specific command check
@@ -78,7 +79,8 @@ export class ClientInstaller {
78
79
  return false;
79
80
  }
80
81
  }
81
- async installClient(clientName, env) {
82
+ // Modified to accept ServerInstallOptions
83
+ async installClient(clientName, options) {
82
84
  // Check if client is supported
83
85
  if (!SUPPORTED_CLIENTS[clientName]) {
84
86
  return {
@@ -101,11 +103,13 @@ export class ClientInstaller {
101
103
  // Update server status with initial client installation status
102
104
  await this.configProvider.updateServerOperationStatus(this.categoryName, this.serverName, clientName, initialStatus);
103
105
  // Start the asynchronous installation process without awaiting it
104
- this.processInstallation(clientName, operationId, env);
106
+ // Pass options down
107
+ this.processInstallation(clientName, operationId, options);
105
108
  // Return the initial status immediately
106
109
  return initialStatus;
107
110
  }
108
- async processInstallation(clientName, operationId, env) {
111
+ // Modified to accept ServerInstallOptions
112
+ async processInstallation(clientName, operationId, options) {
109
113
  try {
110
114
  // Check requirements before installation
111
115
  let requirementsReady = await this.configProvider.isRequirementsReady(this.categoryName, this.serverName);
@@ -179,8 +183,8 @@ export class ClientInstaller {
179
183
  return;
180
184
  }
181
185
  try {
182
- // Install client-specific configuration
183
- const result = await this.installClientConfig(clientName, env || {}, serverConfig, feedConfiguration);
186
+ // Install client-specific configuration, passing options down
187
+ const result = await this.installClientConfig(clientName, options, serverConfig, feedConfiguration);
184
188
  const finalStatus = {
185
189
  status: result.success ? 'completed' : 'failed',
186
190
  type: 'install',
@@ -190,6 +194,9 @@ export class ClientInstaller {
190
194
  error: result.success ? undefined : result.message
191
195
  };
192
196
  await this.configProvider.updateServerOperationStatus(this.categoryName, this.serverName, clientName, finalStatus);
197
+ if (result.success) {
198
+ await this.configProvider.reloadClientMCPSettings();
199
+ }
193
200
  }
194
201
  catch (error) {
195
202
  const errorStatus = {
@@ -215,7 +222,9 @@ export class ClientInstaller {
215
222
  await this.configProvider.updateServerOperationStatus(this.categoryName, this.serverName, clientName, errorStatus);
216
223
  }
217
224
  }
218
- async installClientConfig(clientName, env, serverConfig, feedConfig) {
225
+ // Modified to accept ServerInstallOptions
226
+ async installClientConfig(clientName, options, // Use options directly
227
+ serverConfig, feedConfig) {
219
228
  try {
220
229
  if (!SUPPORTED_CLIENTS[clientName]) {
221
230
  Logger.debug(`Client ${clientName} is not supported.`);
@@ -233,7 +242,6 @@ export class ClientInstaller {
233
242
  const isVSCodeInsidersInstalled = await this.isCommandAvailable('code-insiders');
234
243
  Logger.debug(isVSCodeInstalled ? 'VS Code detected on system' : 'VS Code not detected on system');
235
244
  Logger.debug(isVSCodeInsidersInstalled ? 'VS Code Insiders detected on system' : 'VS Code Insiders not detected on system');
236
- Logger.debug(`VS Code Insiders installed: ${isVSCodeInsidersInstalled}`);
237
245
  // If neither is installed, we can't proceed
238
246
  if (!isVSCodeInstalled && !isVSCodeInsidersInstalled) {
239
247
  Logger.debug('No VS Code installations detected on system. Cannot update settings.');
@@ -242,25 +250,95 @@ export class ClientInstaller {
242
250
  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.`
243
251
  };
244
252
  }
245
- // Clone the installation configuration to make modifications
253
+ // --- Start of new logic ---
254
+ // Clone the base installation configuration to avoid modifying the original serverConfig
246
255
  const installConfig = JSON.parse(JSON.stringify(serverConfig.installation));
247
- // Replace template variables in args
248
- installConfig.args = installConfig.args.map((arg) => resolveNpmModulePath(arg));
249
- // Add environment variables from options
250
- installConfig.env = {};
251
- if (serverConfig.installation.env) {
252
- // Add default env variables from config
253
- for (const [key, config] of Object.entries(serverConfig.installation.env)) {
254
- const envConfig = config; // Type assertion for dynamic access
255
- if (envConfig.Default) {
256
- installConfig.env[key] = envConfig.Default;
256
+ const pythonEnv = options.settings?.pythonEnv;
257
+ let pythonDir = null;
258
+ // 1. Determine which args to use and resolve npm paths
259
+ let finalArgs = [];
260
+ if (options.args && options.args.length > 0) {
261
+ Logger.debug(`Using args from ServerInstallOptions: ${options.args.join(' ')}`);
262
+ finalArgs = options.args.map(arg => resolveNpmModulePath(arg));
263
+ }
264
+ else if (installConfig.args && installConfig.args.length > 0) {
265
+ Logger.debug(`Using args from serverConfig.installation: ${installConfig.args.join(' ')}`);
266
+ finalArgs = installConfig.args.map((arg) => resolveNpmModulePath(arg));
267
+ }
268
+ else {
269
+ Logger.debug('No args found in options or serverConfig.installation.');
270
+ finalArgs = [];
271
+ }
272
+ // 2. Handle pythonEnv settings
273
+ if (pythonEnv) {
274
+ // 2.1 If pythonEnv is set
275
+ Logger.debug(`Python environment specified: ${pythonEnv}`);
276
+ pythonDir = getPythonPackagePath(pythonEnv);
277
+ // 2.1.1 Replace ${PYTHON_PACKAGE} in args
278
+ if (pythonDir) {
279
+ finalArgs = finalArgs.map(arg => arg.includes('${PYTHON_PACKAGE}') ? arg.replace('${PYTHON_PACKAGE}', pythonDir) : arg);
280
+ Logger.debug(`Args after PYTHON_PACKAGE replacement (using pythonEnv): ${finalArgs.join(' ')}`);
281
+ }
282
+ else {
283
+ Logger.debug(`Could not determine directory for pythonEnv: ${pythonEnv}`);
284
+ }
285
+ // 2.1.2 Replace command if it's 'python'
286
+ if (installConfig.command === 'python') {
287
+ Logger.debug(`Replacing command 'python' with specified pythonEnv: ${pythonEnv}`);
288
+ installConfig.command = pythonEnv;
289
+ }
290
+ }
291
+ else {
292
+ // 2.2 If pythonEnv is not set
293
+ Logger.debug('No Python environment specified in settings.');
294
+ // 2.2.1 Replace ${PYTHON_PACKAGE} with system python directory if needed
295
+ if (finalArgs.some(arg => arg.includes('${PYTHON_PACKAGE}'))) {
296
+ Logger.debug('Attempting to find system Python directory for ${PYTHON_PACKAGE} replacement.');
297
+ pythonDir = await getSystemPythonPackageDirectory();
298
+ if (pythonDir) {
299
+ finalArgs = finalArgs.map(arg => arg.includes('${PYTHON_PACKAGE}') ? arg.replace('${PYTHON_PACKAGE}', pythonDir) : arg);
300
+ Logger.debug(`Args after PYTHON_PACKAGE replacement (using system python): ${finalArgs.join(' ')}`);
301
+ }
302
+ else {
303
+ Logger.debug('Could not find system Python directory. ${PYTHON_PACKAGE} replacement skipped.');
304
+ // Optionally, remove or handle the arg containing ${PYTHON_PACKAGE} if Python is required
305
+ // finalArgs = finalArgs.filter(arg => !arg.includes('${PYTHON_PACKAGE}'));
257
306
  }
258
307
  }
259
308
  }
260
- // Override with provided env variables
261
- if (env) {
262
- Object.assign(installConfig.env, env);
309
+ // Update installConfig with potentially modified args
310
+ installConfig.args = finalArgs;
311
+ // 3. Handle environment variables (merge default, serverConfig, and options.env)
312
+ const baseEnv = serverConfig.installation.env || {};
313
+ const defaultEnv = {};
314
+ for (const [key, config] of Object.entries(baseEnv)) {
315
+ const envConfig = config; // Type assertion
316
+ if (envConfig.Default) {
317
+ defaultEnv[key] = envConfig.Default;
318
+ }
263
319
  }
320
+ // Merge: options.env overrides defaultEnv
321
+ installConfig.env = { ...defaultEnv, ...(options.env || {}) };
322
+ // Replace ${BROWSER_PATH} with actual browser path
323
+ const replacements = Object.entries(installConfig.env)
324
+ .filter(([_, value]) => typeof value === 'string' && value.includes('${BROWSER_PATH}'))
325
+ .map(async ([key, value]) => {
326
+ try {
327
+ const browserPath = await GetBrowserPath();
328
+ return [key, value.replace('${BROWSER_PATH}', browserPath)];
329
+ }
330
+ catch (error) {
331
+ Logger.error(`Failed to get system browser path for env var ${key}:`, error);
332
+ return [key, value];
333
+ }
334
+ });
335
+ // Wait for all replacements to complete
336
+ const replacedValues = await Promise.all(replacements);
337
+ replacedValues.forEach(([key, value]) => {
338
+ installConfig.env[key] = value;
339
+ });
340
+ Logger.debug(`Final environment variables: ${JSON.stringify(installConfig.env)}`);
341
+ // --- End of new logic ---
264
342
  // Keep track of success for both installations
265
343
  let regularSuccess = false;
266
344
  let insidersSuccess = false;
@@ -271,6 +349,7 @@ export class ClientInstaller {
271
349
  if (isVSCodeInstalled) {
272
350
  try {
273
351
  Logger.debug(`Updating VS Code settings for ${clientName} at path: ${regularSettingPath}`);
352
+ // Pass the modified installConfig
274
353
  await this.updateClineOrMSRooSettings(regularSettingPath, this.serverName, installConfig, clientName);
275
354
  regularSuccess = true;
276
355
  Logger.debug(`Settings successfully updated to ${regularSettingPath} for ${clientName} (VS Code)`);
@@ -285,6 +364,7 @@ export class ClientInstaller {
285
364
  if (isVSCodeInsidersInstalled) {
286
365
  try {
287
366
  Logger.debug(`Updating VS Code Insiders settings for ${clientName} at path: ${insidersSettingPath}`);
367
+ // Pass the modified installConfig
288
368
  await this.updateClineOrMSRooSettings(insidersSettingPath, this.serverName, installConfig, clientName);
289
369
  insidersSuccess = true;
290
370
  Logger.debug(`Settings successfully updated to ${insidersSettingPath} for ${clientName} (VS Code Insiders)`);
@@ -301,6 +381,7 @@ export class ClientInstaller {
301
381
  if (isVSCodeInstalled) {
302
382
  try {
303
383
  Logger.debug(`Updating VS Code settings for ${clientName} at path: ${regularSettingPath}`);
384
+ // Pass the modified installConfig
304
385
  await this.updateGithubCopilotSettings(regularSettingPath, this.serverName, installConfig);
305
386
  regularSuccess = true;
306
387
  Logger.debug(`Settings successfully updated to ${regularSettingPath} for ${clientName} (VS Code)`);
@@ -315,6 +396,7 @@ export class ClientInstaller {
315
396
  if (isVSCodeInsidersInstalled) {
316
397
  try {
317
398
  Logger.debug(`Updating VS Code Insiders settings for ${clientName} at path: ${insidersSettingPath}`);
399
+ // Pass the modified installConfig
318
400
  await this.updateGithubCopilotSettings(insidersSettingPath, this.serverName, installConfig);
319
401
  insidersSuccess = true;
320
402
  Logger.debug(`Settings successfully updated to ${insidersSettingPath} for ${clientName} (VS Code Insiders)`);
@@ -386,7 +468,8 @@ export class ClientInstaller {
386
468
  };
387
469
  }
388
470
  }
389
- async updateClineOrMSRooSettings(settingPath, serverName, installConfig, clientName) {
471
+ async updateClineOrMSRooSettings(settingPath, serverName, installConfig, // Use the processed installConfig
472
+ clientName) {
390
473
  // Read the Cline/MSRoo settings file
391
474
  const settings = await readJsonFile(settingPath, true);
392
475
  // Initialize mcpServers section if it doesn't exist
@@ -394,39 +477,43 @@ export class ClientInstaller {
394
477
  settings.mcpServers = {};
395
478
  }
396
479
  // Special handling for Windows when command is npx for Cline and MSROO clients
397
- const serverConfig = { ...installConfig };
480
+ // Use a copy to avoid modifying the passed installConfig directly if needed elsewhere
481
+ const serverConfigForClient = { ...installConfig };
398
482
  if (process.platform === 'win32' &&
399
- serverConfig.command === 'npx' &&
483
+ serverConfigForClient.command === 'npx' &&
400
484
  (clientName === 'Cline' || clientName === 'MSRooCode' || clientName === 'MSROO')) {
401
485
  // Update command to cmd
402
- serverConfig.command = 'cmd';
486
+ serverConfigForClient.command = 'cmd';
403
487
  // Add /c and npx at the beginning of args
404
- serverConfig.args = ['/c', 'npx', ...serverConfig.args];
488
+ serverConfigForClient.args = ['/c', 'npx', ...serverConfigForClient.args];
405
489
  // Add APPDATA environment variable pointing to npm directory
406
- if (!serverConfig.env) {
407
- serverConfig.env = {};
490
+ if (!serverConfigForClient.env) {
491
+ serverConfigForClient.env = {};
408
492
  }
409
493
  // Dynamically get npm path and set APPDATA to it
410
494
  const npmPath = await this.getNpmPath();
411
- serverConfig.env['APPDATA'] = npmPath;
495
+ serverConfigForClient.env['APPDATA'] = npmPath;
496
+ Logger.debug(`Windows npx fix: command=${serverConfigForClient.command}, args=${serverConfigForClient.args.join(' ')}, env=${JSON.stringify(serverConfigForClient.env)}`);
412
497
  }
413
498
  // Convert backslashes to forward slashes in args paths
414
- if (serverConfig.args) {
415
- serverConfig.args = serverConfig.args.map((arg) => typeof arg === 'string' ? arg.replace(/\\/g, '/') : arg);
499
+ if (serverConfigForClient.args) {
500
+ serverConfigForClient.args = serverConfigForClient.args.map((arg) => typeof arg === 'string' ? arg.replace(/\\/g, '/') : arg);
416
501
  }
417
502
  // Add or update the server configuration
418
503
  settings.mcpServers[serverName] = {
419
- command: serverConfig.command,
420
- args: serverConfig.args,
421
- env: serverConfig.env,
504
+ command: serverConfigForClient.command,
505
+ args: serverConfigForClient.args,
506
+ env: serverConfigForClient.env,
422
507
  autoApprove: [],
423
508
  disabled: false,
424
509
  alwaysAllow: []
425
510
  };
511
+ Logger.debug(`Updating ${settingPath} for ${serverName}: ${JSON.stringify(settings.mcpServers[serverName])}`);
426
512
  // Write the updated settings back to the file
427
513
  await writeJsonFile(settingPath, settings);
428
514
  }
429
- async updateGithubCopilotSettings(settingPath, serverName, installConfig) {
515
+ async updateGithubCopilotSettings(settingPath, serverName, installConfig // Use the processed installConfig
516
+ ) {
430
517
  // Read the VS Code settings.json file
431
518
  const settings = await readJsonFile(settingPath, true);
432
519
  // Initialize the mcp section if it doesn't exist
@@ -439,24 +526,28 @@ export class ClientInstaller {
439
526
  if (!settings.mcp.servers) {
440
527
  settings.mcp.servers = {};
441
528
  }
529
+ // Use a copy to avoid modifying the passed installConfig directly
530
+ const serverConfigForClient = { ...installConfig };
442
531
  // Convert backslashes to forward slashes in args paths
443
- if (installConfig.args) {
444
- installConfig.args = installConfig.args.map((arg) => typeof arg === 'string' ? arg.replace(/\\/g, '/') : arg);
532
+ if (serverConfigForClient.args) {
533
+ serverConfigForClient.args = serverConfigForClient.args.map((arg) => typeof arg === 'string' ? arg.replace(/\\/g, '/') : arg);
445
534
  }
446
535
  // Add or update the server configuration
447
536
  settings.mcp.servers[serverName] = {
448
- command: installConfig.command,
449
- args: installConfig.args,
450
- env: installConfig.env
537
+ command: serverConfigForClient.command,
538
+ args: serverConfigForClient.args,
539
+ env: serverConfigForClient.env
451
540
  };
541
+ Logger.debug(`Updating ${settingPath} for ${serverName}: ${JSON.stringify(settings.mcp.servers[serverName])}`);
452
542
  // Write the updated settings back to the file
453
543
  await writeJsonFile(settingPath, settings);
454
544
  }
455
545
  async install(options) {
456
546
  const initialStatuses = [];
457
547
  // Start installation for each client asynchronously and collect initial statuses
548
+ // Pass options down to installClient
458
549
  const installPromises = this.clients.map(async (clientName) => {
459
- const initialStatus = await this.installClient(clientName, options.env || {});
550
+ const initialStatus = await this.installClient(clientName, options);
460
551
  initialStatuses.push(initialStatus);
461
552
  return initialStatus;
462
553
  });
@@ -15,6 +15,7 @@ export declare class CommandInstaller extends BaseInstaller {
15
15
  * @returns True if this installer can handle the requirement
16
16
  */
17
17
  canHandle(requirement: RequirementConfig): boolean;
18
+ supportCheckUpdates(): boolean;
18
19
  /**
19
20
  * Get the mapped package ID for a command
20
21
  * @param commandName The command name to map
@@ -22,6 +22,9 @@ export class CommandInstaller extends BaseInstaller {
22
22
  canHandle(requirement) {
23
23
  return requirement.type === 'command';
24
24
  }
25
+ supportCheckUpdates() {
26
+ return false;
27
+ }
25
28
  /**
26
29
  * Get the mapped package ID for a command
27
30
  * @param commandName The command name to map
@@ -11,6 +11,7 @@ export declare class GeneralInstaller extends BaseInstaller {
11
11
  * @returns True if this installer can handle the requirement
12
12
  */
13
13
  canHandle(requirement: RequirementConfig): boolean;
14
+ supportCheckUpdates(): boolean;
14
15
  /**
15
16
  * Check if the requirement is already installed
16
17
  * For general installers, we can't easily check if something is installed
@@ -12,6 +12,9 @@ export class GeneralInstaller extends BaseInstaller {
12
12
  canHandle(requirement) {
13
13
  return requirement.type === 'other';
14
14
  }
15
+ supportCheckUpdates() {
16
+ return false;
17
+ }
15
18
  /**
16
19
  * Check if the requirement is already installed
17
20
  * For general installers, we can't easily check if something is installed
@@ -1,4 +1,4 @@
1
- import { RequirementConfig, RequirementStatus } from '../types.js';
1
+ import { RequirementConfig, RequirementStatus, ServerInstallOptions } from '../types.js';
2
2
  import { RequirementInstaller } from './RequirementInstaller.js';
3
3
  import { exec } from 'child_process';
4
4
  /**
@@ -34,15 +34,17 @@ export declare class InstallerFactory {
34
34
  /**
35
35
  * Install a requirement using the appropriate installer
36
36
  * @param requirement The requirement to install
37
+ * @param options Installation options including python environment
37
38
  * @returns The installation status
38
39
  */
39
- install(requirement: RequirementConfig): Promise<RequirementStatus>;
40
+ install(requirement: RequirementConfig, options?: ServerInstallOptions): Promise<RequirementStatus>;
40
41
  /**
41
- * Check the installation status of a requirement
42
- * @param requirement The requirement to check
43
- * @returns The installation status
44
- */
45
- checkInstallation(requirement: RequirementConfig): Promise<RequirementStatus>;
42
+ * Check the installation status of a requirement
43
+ * @param requirement The requirement to check
44
+ * @param options Installation options including python environment
45
+ * @returns The installation status
46
+ */
47
+ checkInstallation(requirement: RequirementConfig, options?: ServerInstallOptions): Promise<RequirementStatus>;
46
48
  }
47
49
  /**
48
50
  * Create a new InstallerFactory with default settings
@@ -50,9 +50,10 @@ export class InstallerFactory {
50
50
  /**
51
51
  * Install a requirement using the appropriate installer
52
52
  * @param requirement The requirement to install
53
+ * @param options Installation options including python environment
53
54
  * @returns The installation status
54
55
  */
55
- async install(requirement) {
56
+ async install(requirement, options) {
56
57
  const installer = this.getInstaller(requirement);
57
58
  if (!installer) {
58
59
  return {
@@ -63,14 +64,15 @@ export class InstallerFactory {
63
64
  inProgress: false
64
65
  };
65
66
  }
66
- return await installer.install(requirement);
67
+ return await installer.install(requirement, options);
67
68
  }
68
69
  /**
69
- * Check the installation status of a requirement
70
- * @param requirement The requirement to check
71
- * @returns The installation status
72
- */
73
- async checkInstallation(requirement) {
70
+ * Check the installation status of a requirement
71
+ * @param requirement The requirement to check
72
+ * @param options Installation options including python environment
73
+ * @returns The installation status
74
+ */
75
+ async checkInstallation(requirement, options) {
74
76
  const installer = this.getInstaller(requirement);
75
77
  if (!installer) {
76
78
  return {
@@ -81,7 +83,7 @@ export class InstallerFactory {
81
83
  inProgress: false
82
84
  };
83
85
  }
84
- return await installer.checkInstallation(requirement);
86
+ return await installer.checkInstallation(requirement, options);
85
87
  }
86
88
  }
87
89
  /**
@@ -10,6 +10,7 @@ export declare class NpmInstaller extends BaseInstaller {
10
10
  * @returns True if this installer can handle the requirement
11
11
  */
12
12
  canHandle(requirement: RequirementConfig): boolean;
13
+ supportCheckUpdates(): boolean;
13
14
  /**
14
15
  * Check if the NPM package is already installed
15
16
  * @param requirement The requirement to check
@@ -12,6 +12,9 @@ export class NpmInstaller extends BaseInstaller {
12
12
  canHandle(requirement) {
13
13
  return requirement.type === 'npm';
14
14
  }
15
+ supportCheckUpdates() {
16
+ return true;
17
+ }
15
18
  /**
16
19
  * Check if the NPM package is already installed
17
20
  * @param requirement The requirement to check
@@ -1,25 +1,28 @@
1
- import { RequirementConfig, RequirementStatus } from '../types.js';
1
+ import { RequirementConfig, RequirementStatus, ServerInstallOptions } from '../types.js';
2
2
  import { BaseInstaller } from './BaseInstaller.js';
3
3
  /**
4
4
  * Installer implementation for Python packages using pip
5
5
  */
6
6
  export declare class PipInstaller extends BaseInstaller {
7
+ private getPythonCommand;
8
+ private getPipCommand;
7
9
  /**
8
10
  * Check if this installer can handle the given requirement type
9
11
  * @param requirement The requirement to check
10
12
  * @returns True if this installer can handle the requirement
11
13
  */
12
14
  canHandle(requirement: RequirementConfig): boolean;
15
+ supportCheckUpdates(): boolean;
13
16
  /**
14
17
  * Check if the Python package is already installed
15
18
  * @param requirement The requirement to check
16
19
  * @returns The status of the requirement
17
20
  */
18
- checkInstallation(requirement: RequirementConfig): Promise<RequirementStatus>;
21
+ checkInstallation(requirement: RequirementConfig, options?: ServerInstallOptions): Promise<RequirementStatus>;
19
22
  /**
20
23
  * Install the Python package
21
24
  * @param requirement The requirement to install
22
25
  * @returns The status of the installation
23
26
  */
24
- install(requirement: RequirementConfig): Promise<RequirementStatus>;
27
+ install(requirement: RequirementConfig, options?: ServerInstallOptions): Promise<RequirementStatus>;
25
28
  }
@@ -3,6 +3,13 @@ import { BaseInstaller } from './BaseInstaller.js';
3
3
  * Installer implementation for Python packages using pip
4
4
  */
5
5
  export class PipInstaller extends BaseInstaller {
6
+ getPythonCommand(options) {
7
+ return options?.settings?.pythonEnv || 'python';
8
+ }
9
+ getPipCommand(options) {
10
+ const pythonCmd = this.getPythonCommand(options);
11
+ return `${pythonCmd} -m pip`;
12
+ }
6
13
  /**
7
14
  * Check if this installer can handle the given requirement type
8
15
  * @param requirement The requirement to check
@@ -11,14 +18,19 @@ export class PipInstaller extends BaseInstaller {
11
18
  canHandle(requirement) {
12
19
  return requirement.type === 'pip';
13
20
  }
21
+ supportCheckUpdates() {
22
+ /// temporarily disabling update check for pip as not able to get which pip of python is being used
23
+ return false;
24
+ }
14
25
  /**
15
26
  * Check if the Python package is already installed
16
27
  * @param requirement The requirement to check
17
28
  * @returns The status of the requirement
18
29
  */
19
- async checkInstallation(requirement) {
30
+ async checkInstallation(requirement, options) {
20
31
  try {
21
- const { stdout, stderr } = await this.execPromise(`pip show ${requirement.name}`);
32
+ const pipCmd = this.getPipCommand(options);
33
+ const { stdout, stderr } = await this.execPromise(`${pipCmd} show ${requirement.name}`);
22
34
  // If we get an output and no error, the package is installed
23
35
  const installed = stdout.includes(requirement.name);
24
36
  const versionMatch = stdout.match(/Version: (.+)/);
@@ -46,16 +58,17 @@ export class PipInstaller extends BaseInstaller {
46
58
  * @param requirement The requirement to install
47
59
  * @returns The status of the installation
48
60
  */
49
- async install(requirement) {
61
+ async install(requirement, options) {
50
62
  try {
51
- const status = await this.checkInstallation(requirement);
63
+ const status = await this.checkInstallation(requirement, options);
52
64
  if (status.installed) {
53
65
  return status;
54
66
  }
67
+ const pipCmd = this.getPipCommand(options);
55
68
  // If no registry is specified, use standard pip installation
56
69
  if (!requirement.registry) {
57
70
  // Standard pip installation
58
- const { stderr } = await this.execPromise(`pip install ${requirement.name}==${requirement.version}`);
71
+ const { stderr } = await this.execPromise(`${pipCmd} install ${requirement.name}==${requirement.version}`);
59
72
  if (stderr && stderr.toLowerCase().includes('error')) {
60
73
  throw new Error(stderr);
61
74
  }
@@ -67,7 +80,7 @@ export class PipInstaller extends BaseInstaller {
67
80
  const result = await this.handleGitHubRelease(requirement, requirement.registry.githubRelease);
68
81
  packageSource = result.resolvedPath;
69
82
  // Install from the downloaded wheel or tar.gz file
70
- const { stderr } = await this.execPromise(`pip install "${packageSource}"`);
83
+ const { stderr } = await this.execPromise(`${pipCmd} install "${packageSource}"`);
71
84
  if (stderr && stderr.toLowerCase().includes('error')) {
72
85
  throw new Error(stderr);
73
86
  }
@@ -75,7 +88,7 @@ export class PipInstaller extends BaseInstaller {
75
88
  else if (requirement.registry.artifacts) {
76
89
  const registryUrl = requirement.registry.artifacts.registryUrl;
77
90
  // Install using the custom index URL
78
- const { stderr } = await this.execPromise(`pip install ${requirement.name}==${requirement.version} --index-url ${registryUrl}`);
91
+ const { stderr } = await this.execPromise(`${pipCmd} install ${requirement.name}==${requirement.version} --index-url ${registryUrl}`);
79
92
  if (stderr && stderr.toLowerCase().includes('error')) {
80
93
  throw new Error(stderr);
81
94
  }
@@ -83,7 +96,7 @@ export class PipInstaller extends BaseInstaller {
83
96
  else if (requirement.registry.local) {
84
97
  packageSource = await this.handleLocalRegistry(requirement, requirement.registry.local);
85
98
  // Install from the local path
86
- const { stderr } = await this.execPromise(`pip install "${packageSource}"`);
99
+ const { stderr } = await this.execPromise(`${pipCmd} install "${packageSource}"`);
87
100
  if (stderr && stderr.toLowerCase().includes('error')) {
88
101
  throw new Error(stderr);
89
102
  }
@@ -1,4 +1,4 @@
1
- import { RequirementConfig, RequirementStatus } from '../types.js';
1
+ import { RequirementConfig, RequirementStatus, ServerInstallOptions } from '../types.js';
2
2
  /**
3
3
  * Interface for requirement installers.
4
4
  * Implementations should handle specific requirement types.
@@ -10,18 +10,19 @@ export interface RequirementInstaller {
10
10
  * @returns True if this installer can handle the requirement
11
11
  */
12
12
  canHandle(requirement: RequirementConfig): boolean;
13
+ supportCheckUpdates(): boolean;
13
14
  /**
14
15
  * Install the requirement
15
16
  * @param requirement The requirement to install
16
17
  * @returns The status of the installation
17
18
  */
18
- install(requirement: RequirementConfig): Promise<RequirementStatus>;
19
+ install(requirement: RequirementConfig, options?: ServerInstallOptions): Promise<RequirementStatus>;
19
20
  /**
20
21
  * Check if the requirement is already installed
21
22
  * @param requirement The requirement to check
22
23
  * @returns The status of the requirement
23
24
  */
24
- checkInstallation(requirement: RequirementConfig): Promise<RequirementStatus>;
25
+ checkInstallation(requirement: RequirementConfig, options?: ServerInstallOptions): Promise<RequirementStatus>;
25
26
  /**
26
27
  * Check if updates are available for the requirement
27
28
  * @param requirement The requirement to check
@@ -0,0 +1,23 @@
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
+ /**
12
+ * Check if a command is available on the system
13
+ * @param command The command to check
14
+ * @returns True if the command is available, false otherwise
15
+ */
16
+ private isCommandAvailable;
17
+ private installClient;
18
+ private processInstallation;
19
+ private installClientConfig;
20
+ private updateClineOrMSRooSettings;
21
+ private updateGithubCopilotSettings;
22
+ install(options: ServerInstallOptions): Promise<ServerOperationResult>;
23
+ }