imcp 0.0.13 → 0.0.15

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