imcp 0.0.14 → 0.0.16

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 (136) 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 -9
  10. package/dist/core/installers/clients/ClientInstaller.js +80 -527
  11. package/dist/core/installers/clients/ClientInstallerFactory.d.ts +20 -0
  12. package/dist/core/installers/clients/ClientInstallerFactory.js +37 -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 +50 -13
  30. package/dist/core/onboard/FeedOnboardService.js +263 -88
  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 +4 -5
  38. package/dist/core/validators/FeedValidator.d.ts +8 -1
  39. package/dist/core/validators/FeedValidator.js +60 -7
  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 +3 -1
  62. package/dist/web/public/css/notifications.css +48 -17
  63. package/dist/web/public/css/onboard.css +66 -3
  64. package/dist/web/public/index.html +84 -16
  65. package/dist/web/public/js/api.js +3 -6
  66. package/dist/web/public/js/flights/flights.js +127 -0
  67. package/dist/web/public/js/modal/installation.js +5 -5
  68. package/dist/web/public/js/modal/modalSetup.js +3 -2
  69. package/dist/web/public/js/notifications.js +66 -27
  70. package/dist/web/public/js/onboard/ONBOARDING_PAGE_DESIGN.md +338 -0
  71. package/dist/web/public/js/onboard/formProcessor.js +810 -255
  72. package/dist/web/public/js/onboard/index.js +328 -85
  73. package/dist/web/public/js/onboard/publishHandler.js +132 -0
  74. package/dist/web/public/js/onboard/state.js +61 -17
  75. package/dist/web/public/js/onboard/templates.js +217 -249
  76. package/dist/web/public/js/onboard/uiHandlers.js +679 -117
  77. package/dist/web/public/js/onboard/validationHandlers.js +378 -0
  78. package/dist/web/public/js/serverCategoryList.js +15 -2
  79. package/dist/web/public/onboard.html +191 -45
  80. package/dist/web/public/styles.css +91 -1
  81. package/dist/web/server.d.ts +0 -10
  82. package/dist/web/server.js +131 -22
  83. package/package.json +2 -2
  84. package/src/core/ConfigurationProvider.ts +15 -0
  85. package/src/core/InstallationService.ts +2 -7
  86. package/src/core/MCPManager.ts +26 -1
  87. package/src/core/RequirementService.ts +2 -9
  88. package/src/core/installers/clients/BaseClientInstaller.ts +196 -0
  89. package/src/core/installers/clients/ClientInstaller.ts +97 -608
  90. package/src/core/installers/clients/ClientInstallerFactory.ts +43 -0
  91. package/src/core/installers/clients/ClineInstaller.ts +135 -0
  92. package/src/core/installers/clients/GithubCopilotInstaller.ts +179 -0
  93. package/src/core/installers/clients/MSRooCodeInstaller.ts +133 -0
  94. package/src/core/installers/requirements/BaseInstaller.ts +13 -136
  95. package/src/core/installers/requirements/CommandInstaller.ts +9 -1
  96. package/src/core/installers/requirements/GeneralInstaller.ts +11 -4
  97. package/src/core/installers/requirements/NpmInstaller.ts +178 -61
  98. package/src/core/installers/requirements/PipInstaller.ts +68 -29
  99. package/src/core/onboard/FeedOnboardService.ts +346 -0
  100. package/src/core/onboard/OnboardProcessor.ts +305 -0
  101. package/src/core/onboard/OnboardStatus.ts +55 -0
  102. package/src/core/onboard/OnboardStatusManager.ts +188 -0
  103. package/src/core/types.ts +4 -5
  104. package/src/core/validators/FeedValidator.ts +79 -0
  105. package/src/core/validators/IServerValidator.ts +21 -0
  106. package/src/core/validators/SSEServerValidator.ts +43 -0
  107. package/src/core/validators/ServerValidatorFactory.ts +51 -0
  108. package/src/core/validators/StdioServerValidator.ts +259 -0
  109. package/src/services/InstallRequestValidator.ts +1 -1
  110. package/src/services/ServerService.ts +22 -7
  111. package/src/utils/adoUtils.ts +291 -0
  112. package/src/utils/clientUtils.ts +0 -44
  113. package/src/utils/githubUtils.ts +24 -0
  114. package/src/utils/macroExpressionUtils.ts +121 -0
  115. package/src/utils/osUtils.ts +89 -24
  116. package/src/web/contract/serverContract.ts +74 -0
  117. package/src/web/public/css/notifications.css +48 -17
  118. package/src/web/public/css/onboard.css +107 -0
  119. package/src/web/public/index.html +84 -16
  120. package/src/web/public/js/api.js +3 -6
  121. package/src/web/public/js/flights/flights.js +127 -0
  122. package/src/web/public/js/modal/installation.js +5 -5
  123. package/src/web/public/js/modal/modalSetup.js +3 -2
  124. package/src/web/public/js/notifications.js +66 -27
  125. package/src/web/public/js/onboard/ONBOARDING_PAGE_DESIGN.md +338 -0
  126. package/src/web/public/js/onboard/formProcessor.js +864 -0
  127. package/src/web/public/js/onboard/index.js +374 -0
  128. package/src/web/public/js/onboard/publishHandler.js +132 -0
  129. package/src/web/public/js/onboard/state.js +76 -0
  130. package/src/web/public/js/onboard/templates.js +343 -0
  131. package/src/web/public/js/onboard/uiHandlers.js +758 -0
  132. package/src/web/public/js/onboard/validationHandlers.js +378 -0
  133. package/src/web/public/js/serverCategoryList.js +15 -2
  134. package/src/web/public/onboard.html +296 -0
  135. package/src/web/public/styles.css +91 -1
  136. package/src/web/server.ts +167 -58
@@ -0,0 +1,43 @@
1
+ import { MSRooCodeInstaller } from './MSRooCodeInstaller.js';
2
+ import { ClineInstaller } from './ClineInstaller.js';
3
+ import { GithubCopilotInstaller } from './GithubCopilotInstaller.js';
4
+ import { BaseClientInstaller } from './BaseClientInstaller.js';
5
+ import { SUPPORTED_CLIENTS } from '../../constants.js';
6
+
7
+ /**
8
+ * Factory for creating client-specific installers
9
+ * Handles creation of appropriate installer based on client type
10
+ */
11
+ export class ClientInstallerFactory {
12
+ private static readonly installerMap: Record<string, new () => BaseClientInstaller> = {
13
+ [Object.keys(SUPPORTED_CLIENTS)[0]]: MSRooCodeInstaller,
14
+ [Object.keys(SUPPORTED_CLIENTS)[1]]: ClineInstaller,
15
+ [Object.keys(SUPPORTED_CLIENTS)[2]]: GithubCopilotInstaller
16
+ };
17
+
18
+ /**
19
+ * Get a client installer instance based on client type
20
+ * @param clientName Name of the client to get installer for
21
+ * @returns Client-specific installer instance or undefined if client not supported
22
+ */
23
+ static getInstaller(
24
+ clientName: string
25
+ ): BaseClientInstaller | undefined {
26
+ // Check if client is supported
27
+ if (!SUPPORTED_CLIENTS[clientName]) {
28
+ return undefined;
29
+ }
30
+
31
+ const InstallerClass = this.installerMap[clientName as keyof typeof this.installerMap];
32
+ return InstallerClass ? new InstallerClass() : undefined;
33
+ }
34
+
35
+ /**
36
+ * Check if a client is supported
37
+ * @param clientName Name of the client to check
38
+ * @returns True if client is supported, false otherwise
39
+ */
40
+ static isClientSupported(clientName: string): boolean {
41
+ return !!SUPPORTED_CLIENTS[clientName];
42
+ }
43
+ }
@@ -0,0 +1,135 @@
1
+ import { BaseClientInstaller } from './BaseClientInstaller.js';
2
+ import { Logger } from '../../../utils/logger.js';
3
+ import { readJsonFile, writeJsonFile } from '../../../utils/clientUtils.js';
4
+ import { ServerInstallOptions, OperationStatus, McpConfig } from '../../types.js';
5
+ import { ExtensionInstaller } from './ExtensionInstaller.js';
6
+ import { SUPPORTED_CLIENTS } from '../../constants.js';
7
+ import { isCommandAvailable } from '../../../utils/osUtils.js';
8
+
9
+ /**
10
+ * Cline client installer implementation
11
+ * Handles installation of Cline client including extension installation and settings configuration
12
+ */
13
+ export class ClineInstaller extends BaseClientInstaller {
14
+ /**
15
+ * Install Cline client
16
+ * @param options Installation options including environment variables and arguments
17
+ */
18
+ async install(serverConfig: McpConfig, options: ServerInstallOptions): Promise<OperationStatus> {
19
+ const operationId = this.generateOperationId();
20
+
21
+ try {
22
+ // Check if VS Code or VS Code Insiders is installed
23
+ const isVSCodeInstalled = await isCommandAvailable('code');
24
+ const isVSCodeInsidersInstalled = await isCommandAvailable('code-insiders');
25
+
26
+ if (!isVSCodeInstalled && !isVSCodeInsidersInstalled) {
27
+ return {
28
+ status: 'failed',
29
+ type: 'install',
30
+ target: 'server',
31
+ message: 'Neither VS Code nor VS Code Insiders are installed on this system',
32
+ operationId
33
+ };
34
+ }
35
+
36
+ // Install extension
37
+ const extensionResult = await ExtensionInstaller.installExtension('Cline');
38
+ if (!extensionResult) {
39
+ Logger.debug('Failed to install Cline extension');
40
+ return {
41
+ status: 'failed',
42
+ type: 'install',
43
+ target: 'server',
44
+ message: 'Failed to install Cline extension',
45
+ operationId
46
+ };
47
+ }
48
+
49
+
50
+ const installConfig = await this.setupInstallConfig(serverConfig, options);
51
+ // Track success for both VS Code variants
52
+ const results: Array<{ success: boolean; path: string; error?: string }> = [];
53
+
54
+ // Update settings for VS Code if installed
55
+ if (isVSCodeInstalled) {
56
+ try {
57
+ const settingPath = SUPPORTED_CLIENTS['Cline'].codeSettingPath;
58
+ await this.setupClientSettings(settingPath, serverConfig.name, installConfig);
59
+ results.push({ success: true, path: settingPath });
60
+ } catch (error) {
61
+ results.push({
62
+ success: false,
63
+ path: SUPPORTED_CLIENTS['Cline'].codeSettingPath,
64
+ error: error instanceof Error ? error.message : String(error)
65
+ });
66
+ }
67
+ }
68
+
69
+ // Update settings for VS Code Insiders if installed
70
+ if (isVSCodeInsidersInstalled) {
71
+ try {
72
+ const settingPath = SUPPORTED_CLIENTS['Cline'].codeInsiderSettingPath;
73
+ await this.setupClientSettings(settingPath, serverConfig.name, installConfig);
74
+ results.push({ success: true, path: settingPath });
75
+ } catch (error) {
76
+ results.push({
77
+ success: false,
78
+ path: SUPPORTED_CLIENTS['Cline'].codeInsiderSettingPath,
79
+ error: error instanceof Error ? error.message : String(error)
80
+ });
81
+ }
82
+ }
83
+
84
+ // Determine overall success
85
+ const anySuccess = results.some(r => r.success);
86
+ const successPaths = results.filter(r => r.success).map(r => r.path);
87
+ const errors = results.filter(r => !r.success).map(r => r.error);
88
+
89
+ return {
90
+ status: anySuccess ? 'completed' : 'failed',
91
+ type: 'install',
92
+ target: 'server',
93
+ message: anySuccess
94
+ ? `Successfully installed Cline client. Updated settings in: ${successPaths.join(', ')}`
95
+ : `Failed to install Cline client. Errors: ${errors.join('; ')}`,
96
+ operationId,
97
+ error: anySuccess ? undefined : errors.join('; ')
98
+ };
99
+ } catch (error) {
100
+ return {
101
+ status: 'failed',
102
+ type: 'install',
103
+ target: 'server',
104
+ message: `Unexpected error installing Cline client: ${error instanceof Error ? error.message : String(error)}`,
105
+ operationId,
106
+ error: error instanceof Error ? error.message : String(error)
107
+ };
108
+ }
109
+ }
110
+
111
+ /**
112
+ * Set up Cline client settings
113
+ * Updates VS Code settings with MCP server configuration
114
+ */
115
+ async setupClientSettings(settingPath: string, serverName: string, installConfig: any): Promise<void> {
116
+ // Read existing settings
117
+ const settings = await readJsonFile(settingPath, true);
118
+
119
+ // Initialize settings with client-specific structure
120
+ this.initializeSettings(settings);
121
+
122
+ // Handle different modes
123
+ if (installConfig.mode === 'stdio') {
124
+ // Process config for Windows NPX if needed
125
+ const finalConfig = await this.handleWindowsNpx(installConfig);
126
+
127
+ await this.handleStdioMode(settings, serverName, finalConfig);
128
+ } else if (installConfig.mode === 'sse') {
129
+ this.handleSseMode(settings, serverName, installConfig);
130
+ }
131
+
132
+ // Write updated settings
133
+ await writeJsonFile(settingPath, settings);
134
+ }
135
+ }
@@ -0,0 +1,179 @@
1
+ import { BaseClientInstaller } from './BaseClientInstaller.js';
2
+ import { Logger } from '../../../utils/logger.js';
3
+ import { readJsonFile, writeJsonFile } from '../../../utils/clientUtils.js';
4
+ import { ServerInstallOptions, OperationStatus, McpConfig } from '../../types.js';
5
+ import { ExtensionInstaller } from './ExtensionInstaller.js';
6
+ import { SUPPORTED_CLIENTS } from '../../constants.js';
7
+ import { isCommandAvailable } from '../../../utils/osUtils.js';
8
+
9
+ /**
10
+ * Github Copilot client installer implementation
11
+ * Handles installation of Github Copilot client including extension installation and settings configuration
12
+ */
13
+ export class GithubCopilotInstaller extends BaseClientInstaller {
14
+
15
+ /**
16
+ * Install Github Copilot client
17
+ * @param options Installation options including environment variables and arguments
18
+ */
19
+ async install(serverConfig: McpConfig, options: ServerInstallOptions): Promise<OperationStatus> {
20
+ const operationId = this.generateOperationId();
21
+
22
+ try {
23
+ // Check if VS Code or VS Code Insiders is installed
24
+ const isVSCodeInstalled = await isCommandAvailable('code');
25
+ const isVSCodeInsidersInstalled = await isCommandAvailable('code-insiders');
26
+
27
+ if (!isVSCodeInstalled && !isVSCodeInsidersInstalled) {
28
+ return {
29
+ status: 'failed',
30
+ type: 'install',
31
+ target: 'server',
32
+ message: 'Neither VS Code nor VS Code Insiders are installed on this system',
33
+ operationId
34
+ };
35
+ }
36
+
37
+ // Install extension
38
+ const extensionResult = await ExtensionInstaller.installExtension('GithubCopilot');
39
+ if (!extensionResult) {
40
+ Logger.debug('Failed to install Github Copilot extension');
41
+ return {
42
+ status: 'failed',
43
+ type: 'install',
44
+ target: 'server',
45
+ message: 'Failed to install Github Copilot extension',
46
+ operationId
47
+ };
48
+ }
49
+
50
+ const installConfig = await this.setupInstallConfig(serverConfig, options);
51
+
52
+ // Track success for both VS Code variants
53
+ const results: Array<{ success: boolean; path: string; error?: string }> = [];
54
+
55
+ // Update settings for VS Code if installed
56
+ if (isVSCodeInstalled) {
57
+ try {
58
+ const settingPath = SUPPORTED_CLIENTS['GithubCopilot'].codeSettingPath;
59
+ await this.setupClientSettings(settingPath, serverConfig.name, installConfig);
60
+ results.push({ success: true, path: settingPath });
61
+ } catch (error) {
62
+ results.push({
63
+ success: false,
64
+ path: SUPPORTED_CLIENTS['GithubCopilot'].codeSettingPath,
65
+ error: error instanceof Error ? error.message : String(error)
66
+ });
67
+ }
68
+ }
69
+
70
+ // Update settings for VS Code Insiders if installed
71
+ if (isVSCodeInsidersInstalled) {
72
+ try {
73
+ const settingPath = SUPPORTED_CLIENTS['GithubCopilot'].codeInsiderSettingPath;
74
+ await this.setupClientSettings(settingPath, serverConfig.name, installConfig);
75
+ results.push({ success: true, path: settingPath });
76
+ } catch (error) {
77
+ results.push({
78
+ success: false,
79
+ path: SUPPORTED_CLIENTS['GithubCopilot'].codeInsiderSettingPath,
80
+ error: error instanceof Error ? error.message : String(error)
81
+ });
82
+ }
83
+ }
84
+
85
+ // Determine overall success
86
+ const anySuccess = results.some(r => r.success);
87
+ const successPaths = results.filter(r => r.success).map(r => r.path);
88
+ const errors = results.filter(r => !r.success).map(r => r.error);
89
+
90
+ return {
91
+ status: anySuccess ? 'completed' : 'failed',
92
+ type: 'install',
93
+ target: 'server',
94
+ message: anySuccess
95
+ ? `Successfully installed Github Copilot client. Updated settings in: ${successPaths.join(', ')}`
96
+ : `Failed to install Github Copilot client. Errors: ${errors.join('; ')}`,
97
+ operationId,
98
+ error: anySuccess ? undefined : errors.join('; ')
99
+ };
100
+ } catch (error) {
101
+ return {
102
+ status: 'failed',
103
+ type: 'install',
104
+ target: 'server',
105
+ message: `Unexpected error installing Github Copilot client: ${error instanceof Error ? error.message : String(error)}`,
106
+ operationId,
107
+ error: error instanceof Error ? error.message : String(error)
108
+ };
109
+ }
110
+ }
111
+
112
+ /**
113
+ * Initialize settings with GithubCopilot-specific structure
114
+ * Overrides base method to provide custom initialization
115
+ */
116
+ protected initializeSettings(settings: any): void {
117
+ if (!settings.mcp) {
118
+ settings.mcp = {
119
+ servers: {},
120
+ inputs: []
121
+ };
122
+ }
123
+
124
+ if (!settings.mcp.servers) {
125
+ settings.mcp.servers = {};
126
+ }
127
+ }
128
+
129
+ /**
130
+ * Handle stdio mode configuration for GithubCopilot
131
+ * Overrides base method to provide custom stdio configuration
132
+ */
133
+ protected async handleStdioMode(settings: any, serverName: string, finalConfig: any): Promise<void> {
134
+ if (finalConfig.args) {
135
+ finalConfig.args = finalConfig.args.map((arg: string) =>
136
+ typeof arg === 'string' ? arg.replace(/\\/g, '/') : arg
137
+ );
138
+ }
139
+ settings.mcp.servers[serverName] = {
140
+ command: finalConfig.command,
141
+ args: finalConfig.args,
142
+ env: finalConfig.env
143
+ };
144
+ }
145
+
146
+ /**
147
+ * Handle SSE mode configuration for GithubCopilot
148
+ * Overrides base method to provide custom SSE configuration
149
+ */
150
+ protected handleSseMode(settings: any, serverName: string, installConfig: any): void {
151
+ settings.mcp.servers[serverName] = {
152
+ type: 'sse',
153
+ url: installConfig.url
154
+ };
155
+ }
156
+
157
+ /**
158
+ * Set up Github Copilot client settings
159
+ * Updates VS Code settings with MCP server configuration
160
+ * Note: GithubCopilot uses a different settings structure compared to other clients
161
+ */
162
+ async setupClientSettings(settingPath: string, serverName: string, installConfig: any): Promise<void> {
163
+ // Read existing settings
164
+ const settings = await readJsonFile(settingPath, true);
165
+
166
+ // Initialize settings with client-specific structure
167
+ this.initializeSettings(settings);
168
+
169
+ // Handle different modes
170
+ if (installConfig.mode === 'stdio') {
171
+ await this.handleStdioMode(settings, serverName, installConfig);
172
+ } else if (installConfig.mode === 'sse') {
173
+ this.handleSseMode(settings, serverName, installConfig);
174
+ }
175
+
176
+ // Write updated settings
177
+ await writeJsonFile(settingPath, settings);
178
+ }
179
+ }
@@ -0,0 +1,133 @@
1
+ import { BaseClientInstaller } from './BaseClientInstaller.js';
2
+ import { Logger } from '../../../utils/logger.js';
3
+ import { readJsonFile, writeJsonFile } from '../../../utils/clientUtils.js';
4
+ import { ServerInstallOptions, OperationStatus, McpConfig } from '../../types.js';
5
+ import { ExtensionInstaller } from './ExtensionInstaller.js';
6
+ import { SUPPORTED_CLIENTS } from '../../constants.js';
7
+ import { isCommandAvailable } from '../../../utils/osUtils.js';
8
+
9
+ /**
10
+ * MSRooCode client installer implementation
11
+ */
12
+ export class MSRooCodeInstaller extends BaseClientInstaller {
13
+ /**
14
+ * Install MSRooCode client
15
+ */
16
+ async install(serverConfig: McpConfig, options: ServerInstallOptions): Promise<OperationStatus> {
17
+ const operationId = this.generateOperationId();
18
+
19
+ try {
20
+ // Check if VS Code or VS Code Insiders is installed
21
+ const isVSCodeInstalled = await isCommandAvailable('code');
22
+ const isVSCodeInsidersInstalled = await isCommandAvailable('code-insiders');
23
+
24
+ if (!isVSCodeInstalled && !isVSCodeInsidersInstalled) {
25
+ return {
26
+ status: 'failed',
27
+ type: 'install',
28
+ target: 'server',
29
+ message: 'Neither VS Code nor VS Code Insiders are installed on this system',
30
+ operationId
31
+ };
32
+ }
33
+
34
+ // Install extension
35
+ const extensionResult = await ExtensionInstaller.installExtension('MSRooCode');
36
+ if (!extensionResult) {
37
+ Logger.debug('Failed to install MSRooCode extension');
38
+ return {
39
+ status: 'failed',
40
+ type: 'install',
41
+ target: 'server',
42
+ message: 'Failed to install MSRooCode extension',
43
+ operationId
44
+ };
45
+ }
46
+
47
+ const installConfig = await this.setupInstallConfig(serverConfig, options);
48
+ installConfig.mode = serverConfig.mode;
49
+
50
+ // Track success for both VS Code variants
51
+ const results: Array<{ success: boolean; path: string; error?: string }> = [];
52
+
53
+ // Update settings for VS Code if installed
54
+ if (isVSCodeInstalled) {
55
+ try {
56
+ const settingPath = SUPPORTED_CLIENTS['MSRooCode'].codeSettingPath;
57
+ await this.setupClientSettings(settingPath, serverConfig.name, installConfig);
58
+ results.push({ success: true, path: settingPath });
59
+ } catch (error) {
60
+ results.push({
61
+ success: false,
62
+ path: SUPPORTED_CLIENTS['MSRooCode'].codeSettingPath,
63
+ error: error instanceof Error ? error.message : String(error)
64
+ });
65
+ }
66
+ }
67
+
68
+ // Update settings for VS Code Insiders if installed
69
+ if (isVSCodeInsidersInstalled) {
70
+ try {
71
+ const settingPath = SUPPORTED_CLIENTS['MSRooCode'].codeInsiderSettingPath;
72
+ await this.setupClientSettings(settingPath, serverConfig.name, installConfig);
73
+ results.push({ success: true, path: settingPath });
74
+ } catch (error) {
75
+ results.push({
76
+ success: false,
77
+ path: SUPPORTED_CLIENTS['MSRooCode'].codeInsiderSettingPath,
78
+ error: error instanceof Error ? error.message : String(error)
79
+ });
80
+ }
81
+ }
82
+
83
+ // Determine overall success
84
+ const anySuccess = results.some(r => r.success);
85
+ const successPaths = results.filter(r => r.success).map(r => r.path);
86
+ const errors = results.filter(r => !r.success).map(r => r.error);
87
+
88
+ return {
89
+ status: anySuccess ? 'completed' : 'failed',
90
+ type: 'install',
91
+ target: 'server',
92
+ message: anySuccess
93
+ ? `Successfully installed MSRooCode client. Updated settings in: ${successPaths.join(', ')}`
94
+ : `Failed to install MSRooCode client. Errors: ${errors.join('; ')}`,
95
+ operationId,
96
+ error: anySuccess ? undefined : errors.join('; ')
97
+ };
98
+ } catch (error) {
99
+ return {
100
+ status: 'failed',
101
+ type: 'install',
102
+ target: 'server',
103
+ message: `Unexpected error installing MSRooCode client: ${error instanceof Error ? error.message : String(error)}`,
104
+ operationId,
105
+ error: error instanceof Error ? error.message : String(error)
106
+ };
107
+ }
108
+ }
109
+
110
+ /**
111
+ * Set up MSRooCode client settings
112
+ */
113
+ async setupClientSettings(settingPath: string, serverName: string, installConfig: any): Promise<void> {
114
+ // Read existing settings
115
+ const settings = await readJsonFile(settingPath, true);
116
+
117
+ // Initialize settings with client-specific structure
118
+ this.initializeSettings(settings);
119
+
120
+ // Handle different modes
121
+ if (installConfig.mode === 'stdio') {
122
+ // Process config for Windows NPX if needed
123
+ const finalConfig = await this.handleWindowsNpx(installConfig);
124
+
125
+ await this.handleStdioMode(settings, serverName, finalConfig);
126
+ } else if (installConfig.mode === 'sse') {
127
+ this.handleSseMode(settings, serverName, installConfig);
128
+ }
129
+
130
+ // Write updated settings
131
+ await writeJsonFile(settingPath, settings);
132
+ }
133
+ }
@@ -1,8 +1,6 @@
1
1
  import { RequirementConfig, RequirementStatus, RegistryConfig, ServerInstallOptions } from '../../types.js';
2
2
  import path from 'path';
3
- import fs from 'fs/promises';
4
3
  import { SETTINGS_DIR } from '../../constants.js';
5
- import { extractZipFile } from '../../../utils/clientUtils.js';
6
4
  import { RequirementInstaller } from './RequirementInstaller.js';
7
5
  import { Logger } from '../../../utils/logger.js';
8
6
 
@@ -22,14 +20,24 @@ export abstract class BaseInstaller implements RequirementInstaller {
22
20
  abstract supportCheckUpdates(): boolean;
23
21
  abstract install(requirement: RequirementConfig, options?: ServerInstallOptions): Promise<RequirementStatus>;
24
22
  abstract checkInstallation(requirement: RequirementConfig, options?: ServerInstallOptions): Promise<RequirementStatus>;
23
+ /**
24
+ * Get the latest version available for the requirement.
25
+ * This method needs to be implemented by concrete installers.
26
+ * @param requirement The requirement to check.
27
+ * @param options Optional server install options.
28
+ * @returns The latest version string, or undefined if not found or not applicable.
29
+ */
30
+ abstract getLatestVersion(requirement: RequirementConfig, options?: ServerInstallOptions): Promise<string | undefined>;
31
+
25
32
 
26
33
  /**
27
34
  * Check if updates are available for the requirement
28
35
  * @param requirement The requirement to check
29
36
  * @param currentStatus The current status of the requirement
37
+ * @param options Optional server install options.
30
38
  * @returns The status of the requirement with update information
31
39
  */
32
- async checkForUpdates(requirement: RequirementConfig, currentStatus: RequirementStatus): Promise<RequirementStatus> {
40
+ async checkForUpdates(requirement: RequirementConfig, currentStatus: RequirementStatus, options?: ServerInstallOptions): Promise<RequirementStatus> {
33
41
  try {
34
42
  // If requirement is not installed, no need to check for updates
35
43
  if (!currentStatus.installed) {
@@ -41,23 +49,7 @@ export abstract class BaseInstaller implements RequirementInstaller {
41
49
  return currentStatus;
42
50
  }
43
51
 
44
- let latestVersion: string | undefined;
45
-
46
- // Check based on registry type
47
- if (requirement.registry?.githubRelease) {
48
- latestVersion = await this.getGitHubLatestVersion(requirement.registry.githubRelease.repository);
49
- } else {
50
- // Use common practice based on requirement type
51
- switch (requirement.type) {
52
- case 'npm':
53
- latestVersion = await this.getNpmLatestVersion(requirement.name);
54
- break;
55
- case 'pip':
56
- latestVersion = await this.getPipLatestVersion(requirement.name);
57
- break;
58
- // Add other types as needed
59
- }
60
- }
52
+ const latestVersion = await this.getLatestVersion(requirement, options);
61
53
 
62
54
  // If we found a latest version and it's different from current
63
55
  if (latestVersion && latestVersion !== currentStatus.version) {
@@ -78,123 +70,8 @@ export abstract class BaseInstaller implements RequirementInstaller {
78
70
 
79
71
  } catch (error) {
80
72
  // Don't update status on error, just log it
81
- console.warn(`Error checking for updates for ${requirement.name}: ${error instanceof Error ? error.message : String(error)}`);
73
+ Logger.log(`Error checking for updates for ${requirement.name}: ${error instanceof Error ? error.message : String(error)}`);
82
74
  return currentStatus;
83
75
  }
84
76
  }
85
-
86
- /**
87
- * Helper to handle artifact registry downloads
88
- * @param requirement The requirement configuration
89
- * @param registry The artifacts registry configuration
90
- * @returns The registry URL
91
- */
92
- protected async handleArtifactsRegistry(
93
- requirement: RequirementConfig,
94
- registry: RegistryConfig['artifacts']
95
- ): Promise<string> {
96
- if (!registry) {
97
- throw new Error('Artifacts registry configuration is required');
98
- }
99
-
100
- const { registryUrl, assetName } = registry;
101
-
102
- if (!registryUrl) {
103
- throw new Error('Registry URL is required for artifacts downloads');
104
- }
105
-
106
- return registryUrl;
107
- }
108
-
109
- /**
110
- * Helper to handle local path registry
111
- * @param requirement The requirement configuration
112
- * @param registry The local registry configuration
113
- * @returns The local path or extracted asset path
114
- */
115
- protected async handleLocalRegistry(
116
- requirement: RequirementConfig,
117
- registry: RegistryConfig['local']
118
- ): Promise<string> {
119
- if (!registry) {
120
- throw new Error('Local registry configuration is required');
121
- }
122
-
123
- const { localPath, assetName } = registry;
124
-
125
- if (!localPath) {
126
- throw new Error('Local path is required for local registry');
127
- }
128
-
129
- // Verify the local path exists
130
- try {
131
- await fs.access(localPath);
132
- } catch (error) {
133
- throw new Error(`Local path ${localPath} does not exist or is not accessible`);
134
- }
135
-
136
- // If the path is a zip file and assetName is specified, extract it
137
- if (localPath.endsWith('.zip') && assetName) {
138
- const extractDir = path.join(this.downloadsDir, path.basename(localPath, '.zip'));
139
- await fs.mkdir(extractDir, { recursive: true });
140
-
141
- // Extract the zip file
142
- await extractZipFile(localPath, { dir: extractDir });
143
-
144
- // Find the asset in the extracted directory
145
- const assetPath = path.join(extractDir, assetName);
146
- try {
147
- await fs.access(assetPath);
148
- return assetPath;
149
- } catch (error) {
150
- throw new Error(`Asset ${assetName} not found in extracted directory ${extractDir}`);
151
- }
152
- }
153
-
154
- return localPath;
155
- }
156
-
157
- /**
158
- * Get the latest version available for a GitHub repository
159
- * @param repository The GitHub repository in format 'owner/repo'
160
- * @returns The latest version or tag
161
- */
162
- protected async getGitHubLatestVersion(repository: string): Promise<string> {
163
- try {
164
- // Use GitHub CLI to get the latest release
165
- const { stdout } = await this.execPromise(`gh release view --repo ${repository} --json tagName --jq .tagName`);
166
- const latestTag = stdout.trim();
167
-
168
- // Remove 'v' prefix if present
169
- return latestTag.startsWith('v') ? latestTag.substring(1) : latestTag;
170
- } catch (error) {
171
- // If gh command fails, try to get the latest tag
172
- const { stdout } = await this.execPromise(`git ls-remote --tags --refs https://github.com/${repository}.git | sort -t '/' -k 3 -V | tail -n 1 | awk -F/ '{print $3}'`);
173
- let latestTag = stdout.trim();
174
-
175
- // Remove 'v' prefix if present
176
- return latestTag.startsWith('v') ? latestTag.substring(1) : latestTag;
177
- }
178
- }
179
-
180
- /**
181
- * Get the latest version available for an NPM package
182
- * @param packageName The name of the NPM package
183
- * @returns The latest version
184
- */
185
- protected async getNpmLatestVersion(packageName: string): Promise<string> {
186
- const { stdout } = await this.execPromise(`npm view ${packageName} version`);
187
- return stdout.trim();
188
- }
189
-
190
- /**
191
- * Get the latest version available for a pip package
192
- * @param packageName The name of the pip package
193
- * @returns The latest version
194
- */
195
- protected async getPipLatestVersion(packageName: string, options?: ServerInstallOptions): Promise<string> {
196
- const pythonCmd = options?.settings?.pythonEnv as string || 'python';
197
- const { stdout } = await this.execPromise(`${pythonCmd} -m pip index versions ${packageName} --pre=0 | grep -oP "(?<=Latest:\\s)[^\\s]+" | head -1`);
198
- return stdout.trim();
199
- }
200
77
  }