imcp 0.0.19 → 0.1.2

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 (183) hide show
  1. package/.roo/rules-code/rules.md +88 -0
  2. package/dist/cli/index.js +1 -45
  3. package/dist/core/installers/clients/BaseClientInstaller.d.ts +1 -5
  4. package/dist/core/installers/clients/BaseClientInstaller.js +40 -38
  5. package/dist/core/installers/clients/ClientInstaller.d.ts +9 -9
  6. package/dist/core/installers/clients/ClientInstaller.js +105 -99
  7. package/dist/core/installers/requirements/BaseInstaller.d.ts +9 -1
  8. package/dist/core/installers/requirements/CommandInstaller.d.ts +9 -1
  9. package/dist/core/installers/requirements/CommandInstaller.js +46 -12
  10. package/dist/core/installers/requirements/GeneralInstaller.d.ts +11 -1
  11. package/dist/core/installers/requirements/GeneralInstaller.js +46 -10
  12. package/dist/core/installers/requirements/InstallerFactory.d.ts +3 -1
  13. package/dist/core/installers/requirements/InstallerFactory.js +3 -2
  14. package/dist/core/installers/requirements/NpmInstaller.d.ts +4 -2
  15. package/dist/core/installers/requirements/NpmInstaller.js +38 -22
  16. package/dist/core/installers/requirements/PipInstaller.d.ts +3 -1
  17. package/dist/core/installers/requirements/PipInstaller.js +58 -36
  18. package/dist/core/installers/requirements/RequirementInstaller.d.ts +4 -1
  19. package/dist/core/loaders/InstallOperationManager.d.ts +115 -0
  20. package/dist/core/loaders/InstallOperationManager.js +311 -0
  21. package/dist/core/loaders/SystemSettingsManager.d.ts +54 -0
  22. package/dist/core/loaders/SystemSettingsManager.js +257 -0
  23. package/dist/core/metadatas/constants.d.ts +7 -0
  24. package/dist/core/metadatas/constants.js +7 -0
  25. package/dist/core/metadatas/recordingConstants.d.ts +44 -0
  26. package/dist/core/metadatas/recordingConstants.js +45 -0
  27. package/dist/core/metadatas/types.d.ts +21 -0
  28. package/dist/core/onboard/FeedOnboardService.d.ts +7 -3
  29. package/dist/core/onboard/FeedOnboardService.js +52 -5
  30. package/dist/core/onboard/InstallOperationManager.d.ts +23 -0
  31. package/dist/core/onboard/InstallOperationManager.js +144 -0
  32. package/dist/core/onboard/OnboardStatusManager.js +2 -1
  33. package/dist/core/validators/StdioServerValidator.js +4 -3
  34. package/dist/services/InstallationService.d.ts +2 -37
  35. package/dist/services/InstallationService.js +45 -313
  36. package/dist/services/MCPManager.d.ts +1 -1
  37. package/dist/services/MCPManager.js +53 -47
  38. package/dist/services/RequirementService.d.ts +85 -12
  39. package/dist/services/RequirementService.js +488 -49
  40. package/dist/services/ServerService.d.ts +0 -6
  41. package/dist/services/ServerService.js +0 -74
  42. package/dist/services/TelemetryService.d.ts +15 -0
  43. package/dist/services/TelemetryService.js +54 -0
  44. package/dist/utils/adoUtils.js +6 -3
  45. package/dist/utils/githubAuth.js +65 -0
  46. package/dist/utils/logger.d.ts +16 -0
  47. package/dist/utils/logger.js +78 -1
  48. package/dist/utils/macroExpressionUtils.js +3 -25
  49. package/dist/utils/osUtils.d.ts +22 -1
  50. package/dist/utils/osUtils.js +92 -1
  51. package/dist/utils/versionUtils.d.ts +20 -0
  52. package/dist/utils/versionUtils.js +76 -0
  53. package/dist/web/public/css/modal.css +292 -1
  54. package/dist/web/public/css/serverCategoryList.css +120 -0
  55. package/dist/web/public/css/serverDetails.css +14 -1
  56. package/dist/web/public/index.html +126 -21
  57. package/dist/web/public/js/flights/flights.js +1 -1
  58. package/dist/web/public/js/modal/index.js +8 -14
  59. package/dist/web/public/js/modal/installModal.js +3 -4
  60. package/dist/web/public/js/modal/installation.js +122 -137
  61. package/dist/web/public/js/modal/loadingModal.js +155 -25
  62. package/dist/web/public/js/modal/messageQueue.js +45 -101
  63. package/dist/web/public/js/modal/modalSetup.js +125 -43
  64. package/dist/web/public/js/modal/modalUtils.js +0 -12
  65. package/dist/web/public/js/modal.js +23 -10
  66. package/dist/web/public/js/onboard/formProcessor.js +18 -11
  67. package/dist/web/public/js/onboard/publishHandler.js +35 -3
  68. package/dist/web/public/js/onboard/templates.js +5 -1
  69. package/dist/web/public/js/onboard/uiHandlers.js +266 -39
  70. package/dist/web/public/js/onboard/validationHandlers.js +71 -39
  71. package/dist/web/public/js/serverCategoryDetails.js +60 -11
  72. package/dist/web/public/js/serverCategoryList.js +93 -9
  73. package/dist/web/public/js/settings.js +314 -0
  74. package/dist/web/public/onboard.html +2 -2
  75. package/dist/web/public/settings.html +135 -0
  76. package/dist/web/public/styles.css +32 -0
  77. package/dist/web/server.js +93 -1
  78. package/{src/web/public/js/onboard → docs}/ONBOARDING_PAGE_DESIGN.md +15 -125
  79. package/docs/Telemetry.md +136 -0
  80. package/memory-bank/activeContext.md +26 -0
  81. package/memory-bank/decisionLog.md +91 -0
  82. package/memory-bank/productContext.md +41 -0
  83. package/memory-bank/progress.md +35 -0
  84. package/memory-bank/systemPatterns.md +10 -0
  85. package/package.json +2 -1
  86. package/src/cli/index.ts +1 -48
  87. package/src/core/installers/clients/BaseClientInstaller.ts +64 -50
  88. package/src/core/installers/clients/ClientInstaller.ts +130 -130
  89. package/src/core/installers/requirements/BaseInstaller.ts +9 -1
  90. package/src/core/installers/requirements/CommandInstaller.ts +47 -13
  91. package/src/core/installers/requirements/GeneralInstaller.ts +48 -10
  92. package/src/core/installers/requirements/InstallerFactory.ts +4 -3
  93. package/src/core/installers/requirements/NpmInstaller.ts +90 -68
  94. package/src/core/installers/requirements/PipInstaller.ts +81 -55
  95. package/src/core/installers/requirements/RequirementInstaller.ts +4 -3
  96. package/src/core/loaders/InstallOperationManager.ts +367 -0
  97. package/src/core/loaders/SystemSettingsManager.ts +278 -0
  98. package/src/core/metadatas/constants.ts +9 -0
  99. package/src/core/metadatas/recordingConstants.ts +62 -0
  100. package/src/core/metadatas/types.ts +23 -0
  101. package/src/core/onboard/FeedOnboardService.ts +59 -5
  102. package/src/core/onboard/OnboardStatusManager.ts +2 -1
  103. package/src/core/validators/StdioServerValidator.ts +4 -3
  104. package/src/services/InstallationService.ts +54 -399
  105. package/src/services/MCPManager.ts +61 -64
  106. package/src/services/RequirementService.ts +564 -67
  107. package/src/services/ServerService.ts +0 -90
  108. package/src/services/TelemetryService.ts +59 -0
  109. package/src/utils/adoUtils.ts +6 -4
  110. package/src/utils/githubAuth.ts +84 -1
  111. package/src/utils/logger.ts +83 -1
  112. package/src/utils/macroExpressionUtils.ts +4 -21
  113. package/src/utils/osUtils.ts +92 -1
  114. package/src/utils/versionUtils.ts +98 -13
  115. package/src/web/public/css/modal.css +292 -1
  116. package/src/web/public/css/serverCategoryList.css +120 -0
  117. package/src/web/public/css/serverDetails.css +14 -1
  118. package/src/web/public/index.html +126 -21
  119. package/src/web/public/js/flights/flights.js +1 -1
  120. package/src/web/public/js/modal/index.js +8 -14
  121. package/src/web/public/js/modal/installModal.js +3 -4
  122. package/src/web/public/js/modal/installation.js +122 -137
  123. package/src/web/public/js/modal/loadingModal.js +155 -25
  124. package/src/web/public/js/modal/modalSetup.js +125 -43
  125. package/src/web/public/js/modal/modalUtils.js +0 -12
  126. package/src/web/public/js/modal.js +23 -10
  127. package/src/web/public/js/onboard/formProcessor.js +18 -11
  128. package/src/web/public/js/onboard/publishHandler.js +35 -3
  129. package/src/web/public/js/onboard/templates.js +5 -1
  130. package/src/web/public/js/onboard/uiHandlers.js +266 -39
  131. package/src/web/public/js/onboard/validationHandlers.js +71 -39
  132. package/src/web/public/js/serverCategoryDetails.js +60 -11
  133. package/src/web/public/js/serverCategoryList.js +93 -9
  134. package/src/web/public/js/settings.js +314 -0
  135. package/src/web/public/onboard.html +2 -2
  136. package/src/web/public/settings.html +135 -0
  137. package/src/web/public/styles.css +32 -0
  138. package/src/web/server.ts +96 -1
  139. package/dist/cli/commands/start.d.ts +0 -2
  140. package/dist/cli/commands/start.js +0 -32
  141. package/dist/cli/commands/sync.d.ts +0 -2
  142. package/dist/cli/commands/sync.js +0 -17
  143. package/dist/core/ConfigurationLoader.d.ts +0 -32
  144. package/dist/core/ConfigurationLoader.js +0 -236
  145. package/dist/core/ConfigurationProvider.d.ts +0 -35
  146. package/dist/core/ConfigurationProvider.js +0 -375
  147. package/dist/core/InstallationService.d.ts +0 -50
  148. package/dist/core/InstallationService.js +0 -350
  149. package/dist/core/MCPManager.d.ts +0 -28
  150. package/dist/core/MCPManager.js +0 -188
  151. package/dist/core/RequirementService.d.ts +0 -40
  152. package/dist/core/RequirementService.js +0 -110
  153. package/dist/core/ServerSchemaLoader.d.ts +0 -11
  154. package/dist/core/ServerSchemaLoader.js +0 -43
  155. package/dist/core/ServerSchemaProvider.d.ts +0 -17
  156. package/dist/core/ServerSchemaProvider.js +0 -120
  157. package/dist/core/constants.d.ts +0 -47
  158. package/dist/core/constants.js +0 -94
  159. package/dist/core/installers/BaseInstaller.d.ts +0 -74
  160. package/dist/core/installers/BaseInstaller.js +0 -253
  161. package/dist/core/installers/ClientInstaller.d.ts +0 -23
  162. package/dist/core/installers/ClientInstaller.js +0 -564
  163. package/dist/core/installers/CommandInstaller.d.ts +0 -37
  164. package/dist/core/installers/CommandInstaller.js +0 -173
  165. package/dist/core/installers/GeneralInstaller.d.ts +0 -33
  166. package/dist/core/installers/GeneralInstaller.js +0 -85
  167. package/dist/core/installers/InstallerFactory.d.ts +0 -54
  168. package/dist/core/installers/InstallerFactory.js +0 -97
  169. package/dist/core/installers/NpmInstaller.d.ts +0 -26
  170. package/dist/core/installers/NpmInstaller.js +0 -127
  171. package/dist/core/installers/PipInstaller.d.ts +0 -28
  172. package/dist/core/installers/PipInstaller.js +0 -127
  173. package/dist/core/installers/RequirementInstaller.d.ts +0 -33
  174. package/dist/core/installers/RequirementInstaller.js +0 -3
  175. package/dist/core/types.d.ts +0 -166
  176. package/dist/core/types.js +0 -16
  177. package/dist/services/InstallRequestValidator.d.ts +0 -21
  178. package/dist/services/InstallRequestValidator.js +0 -99
  179. package/dist/web/public/js/modal/installHandler.js +0 -227
  180. package/dist/web/public/js/modal/loadingUI.js +0 -74
  181. package/dist/web/public/js/modal/modalUI.js +0 -214
  182. package/dist/web/public/js/modal/version.js +0 -20
  183. package/src/web/public/js/modal/messageQueue.js +0 -112
@@ -13,8 +13,6 @@ import {
13
13
  } from '../core/metadatas/types.js';
14
14
  import { mcpManager } from './MCPManager.js';
15
15
  import { OperationStatus } from '../core/onboard/OnboardStatus.js';
16
- import { UPDATE_CHECK_INTERVAL_MS } from '../core/metadatas/constants.js';
17
- import { updateCheckTracker } from '../utils/UpdateCheckTracker.js';
18
16
 
19
17
  const __dirname = path.dirname(fileURLToPath(import.meta.url));
20
18
 
@@ -36,97 +34,9 @@ export class ServerService {
36
34
  async getServerCategory(categoryName: string): Promise<MCPServerCategory | undefined> {
37
35
  const serverCategories = await this.listServerCategories();
38
36
  const serverCategory = serverCategories.find(s => s.name === categoryName);
39
-
40
- // Start async check for requirement updates if one isn't already in progress
41
- if (serverCategory && serverCategory.feedConfiguration && serverCategory.name) {
42
- // Check if update is already in progress using the tracker
43
- const shouldCheckForUpdates = await updateCheckTracker.startOperation(serverCategory.name);
44
-
45
- if (shouldCheckForUpdates) {
46
- this.checkRequirementsForUpdate(serverCategory).catch(error => {
47
- // Ensure we mark the operation as complete on error
48
- if (serverCategory.name) {
49
- updateCheckTracker.endOperation(serverCategory.name)
50
- .catch(lockError => console.error(`Failed to mark update check as complete: ${lockError.message}`));
51
- }
52
- Logger.error(`Error checking requirements for updates: ${error.message}`);
53
- });
54
- } else {
55
- Logger.debug(`Update check already in progress for ${serverCategory.name}, skipping`);
56
- }
57
- }
58
-
59
37
  return serverCategory;
60
38
  }
61
39
 
62
- /**
63
- * Check for updates to requirements for a server category
64
- * @param serverCategory The server category to check
65
- * @private
66
- */
67
- private async checkRequirementsForUpdate(serverCategory: MCPServerCategory): Promise<void> {
68
- if (!serverCategory.name || !serverCategory.feedConfiguration?.requirements?.length) {
69
- return;
70
- }
71
-
72
- try {
73
- const { requirementService } = await import('./RequirementService.js');
74
- const { configProvider } = await import('../core/loaders/ConfigurationProvider.js');
75
-
76
- for (const requirement of serverCategory.feedConfiguration.requirements) {
77
- if (requirement.version.includes('latest')) {
78
- // Get current status if available
79
- const currentStatus = serverCategory.installationStatus?.requirementsStatus[requirement.name];
80
- if (!currentStatus) continue;
81
-
82
- // Skip update check if last check was less than UPDATE_CHECK_INTERVAL_MS ago
83
- if (currentStatus.lastCheckTime) {
84
- const lastCheckTime = new Date(currentStatus.lastCheckTime);
85
- const currentTime = new Date();
86
- const timeSinceLastCheck = currentTime.getTime() - lastCheckTime.getTime();
87
-
88
- if (timeSinceLastCheck < UPDATE_CHECK_INTERVAL_MS) {
89
- Logger.debug(`Skipping update check for ${requirement.name}, last check was ${Math.round(timeSinceLastCheck / 1000)} seconds ago`);
90
- continue;
91
- }
92
- }
93
-
94
- // Check for updates
95
- const updatedStatus = await requirementService.checkRequirementForUpdates(requirement, currentStatus);
96
-
97
- // If update information is found, update the configuration
98
- if (updatedStatus.availableUpdate && serverCategory.name) {
99
- await configProvider.updateRequirementStatus(
100
- serverCategory.name,
101
- requirement.name,
102
- updatedStatus
103
- );
104
-
105
- // Also update the in-memory status for immediate use
106
- if (serverCategory.installationStatus?.requirementsStatus) {
107
- serverCategory.installationStatus.requirementsStatus[requirement.name] = updatedStatus;
108
- }
109
- }
110
- currentStatus.lastCheckTime = new Date().toISOString();
111
- await configProvider.updateRequirementStatus(
112
- serverCategory.name,
113
- requirement.name,
114
- currentStatus
115
- );
116
- if (serverCategory.installationStatus?.requirementsStatus) {
117
- serverCategory.installationStatus.requirementsStatus[requirement.name] = updatedStatus;
118
- }
119
- }
120
- }
121
- } finally {
122
- // Always mark the operation as complete when done, even if there was an error
123
- if (serverCategory.name) {
124
- await updateCheckTracker.endOperation(serverCategory.name)
125
- .catch(error => console.error(`Failed to mark update check as complete: ${error.message}`));
126
- }
127
- }
128
- }
129
-
130
40
  /**
131
41
  * Gets the schema for a specific server in a category
132
42
  */
@@ -0,0 +1,59 @@
1
+ import * as appInsights from 'applicationinsights';
2
+
3
+ export class TelemetryService {
4
+ private static client: appInsights.TelemetryClient | null = null;
5
+ private static readonly instrumentationKey = 'InstrumentationKey=c5fc06c7-a96c-4d80-9aff-bc9c933db0d1';
6
+
7
+ static {
8
+ // Initialize Application Insights
9
+ try {
10
+ const client = new appInsights.TelemetryClient(TelemetryService.instrumentationKey);
11
+ client.config.disableAppInsights = false;
12
+ client.config.maxBatchSize = 250;
13
+ client.context.tags[client.context.keys.cloudRole] = 'imcp';
14
+ TelemetryService.client = client;
15
+ console.log('Application Insights initialized');
16
+ } catch (error) {
17
+ console.error('Failed to initialize Application Insights:', error);
18
+ }
19
+ }
20
+
21
+ static trackEvent(name: string, properties?: { [key: string]: string }): void {
22
+ if (!this.client) {
23
+ return;
24
+ }
25
+ this.client.trackEvent({ name, properties });
26
+ }
27
+
28
+ static trackException(error: Error, properties?: { [key: string]: string }): void {
29
+ if (!this.client) {
30
+ return;
31
+ }
32
+ this.client.trackException({ exception: error, properties });
33
+ }
34
+
35
+ static trackTrace(message: string, properties?: { [key: string]: string }): void {
36
+ if (!this.client) {
37
+ return;
38
+ }
39
+ this.client.trackTrace({ message, properties });
40
+ }
41
+
42
+ static trackMetric(name: string, value: number): void {
43
+ if (!this.client) {
44
+ return;
45
+ }
46
+ this.client.trackMetric({ name, value });
47
+ }
48
+
49
+ static flush(): Promise<void> {
50
+ return new Promise((resolve) => {
51
+ if (!this.client) {
52
+ resolve();
53
+ return;
54
+ }
55
+ this.client.flush();
56
+ resolve();
57
+ });
58
+ }
59
+ }
@@ -124,13 +124,12 @@ export async function handleArtifact(
124
124
  if (!registry) {
125
125
  throw new Error('Azure DevOps artifacts registry configuration is required.');
126
126
  }
127
- if (!registry.registryName || !registry.registryUrl) {
128
- throw new Error('Registry name and URL are required for Azure DevOps artifacts.');
129
- }
130
-
131
127
  Logger.debug(`Handling ADO artifact for requirement: ${requirement.name}, type: ${requirement.type}`);
132
128
 
133
129
  if (requirement.type === 'npm') {
130
+ if (!registry.registryName || !registry.registryUrl) {
131
+ throw new Error('Registry name and URL are required for NPM source in Azure DevOps artifacts.');
132
+ }
134
133
  const requirementDir = targetDir || path.join(SETTINGS_DIR, 'requirements', requirement.name, requirement.version);
135
134
  await fs.mkdir(requirementDir, { recursive: true });
136
135
  Logger.debug(`Ensured directory for npm requirement: ${requirementDir}`);
@@ -153,6 +152,9 @@ export async function handleArtifact(
153
152
  }
154
153
 
155
154
  } else if (requirement.type === 'pip') {
155
+ if (!registry.registryUrl) {
156
+ throw new Error('Registry URL are required for PIP source in Azure DevOps artifacts.');
157
+ }
156
158
  await _installPipRequirements(pythonCommand);
157
159
 
158
160
  const packageName = requirement.version.toLowerCase().includes('latest')
@@ -2,6 +2,16 @@ import { isToolInstalled, installCLI } from './osUtils.js';
2
2
  import { exec, spawn } from 'child_process';
3
3
  import util from 'util';
4
4
  import { Logger } from './logger.js';
5
+ import fs from 'fs/promises';
6
+ import path from 'path';
7
+ import { USER_INFO_PATH } from '../core/metadatas/constants.js';
8
+
9
+ interface UserInfo {
10
+ alias?: string;
11
+ name?: string;
12
+ email?: string;
13
+ }
14
+
5
15
 
6
16
  const execAsync = util.promisify(exec);
7
17
 
@@ -74,6 +84,9 @@ export async function checkGithubAuth(): Promise<void> {
74
84
  throw new GithubAuthError(error);
75
85
  }
76
86
 
87
+ // After Microsoft account verification, persist user information
88
+ await persistUserInfo();
89
+
77
90
  Logger.debug('GitHub authentication verified successfully with Microsoft account');
78
91
  } catch (error) {
79
92
  if (error instanceof GithubAuthError) {
@@ -101,7 +114,9 @@ export async function checkGithubAuth(): Promise<void> {
101
114
  if (!viewer.login.toLowerCase().endsWith('_microsoft')) {
102
115
  throw new GithubAuthError('You must be logged in with a Microsoft account (username should end with _microsoft).');
103
116
  }
104
-
117
+
118
+ // After Microsoft account verification, persist user information
119
+ await persistUserInfo();
105
120
  Logger.debug(`Successfully authenticated as ${viewer.login}`);
106
121
  return; // Auth successful, continue execution
107
122
  } catch (loginError) {
@@ -126,4 +141,72 @@ export async function checkGithubAuth(): Promise<void> {
126
141
  throw new GithubAuthError(errorMessage);
127
142
  }
128
143
  }
144
+ }
145
+
146
+ /**
147
+ * Persists GitHub user information to the system-specific settings directory.
148
+ * Only performs persistence on non-Windows systems (Linux/macOS).
149
+ */
150
+ async function persistUserInfo(): Promise<void> {
151
+ try {
152
+ Logger.debug('Starting user information persistence check');
153
+
154
+ // 1. Skip persistence on Windows systems
155
+ if (process.platform === 'win32') {
156
+ Logger.debug('Skipping user info persistence on Windows system');
157
+ return;
158
+ }
159
+
160
+ // 2. Check if file exists and has all required keys
161
+ try {
162
+ if (await fs.access(USER_INFO_PATH).then(() => true).catch(() => false)) {
163
+ const existingContent = await fs.readFile(USER_INFO_PATH, 'utf8');
164
+ const existingData: UserInfo = JSON.parse(existingContent);
165
+
166
+ if (existingData.alias && existingData.name && existingData.email) {
167
+ Logger.debug('User info already exists with all required fields, skipping update');
168
+ return;
169
+ }
170
+ }
171
+ } catch (err) {
172
+ Logger.debug('No valid existing user info found, proceeding with persistence');
173
+ }
174
+
175
+ // Proceed with persistence since both checks passed
176
+ Logger.debug('Proceeding with user information persistence');
177
+
178
+ // Get user info from GitHub API
179
+ const { stdout: userDataStr } = await execAsync('gh api user');
180
+ const userData = JSON.parse(userDataStr);
181
+ const { login, name, email } = userData;
182
+
183
+ Logger.debug({ message: 'Retrieved user information from GitHub API', login, name, email });
184
+
185
+ // Check if login ends with _microsoft
186
+ if (!login.toLowerCase().endsWith('_microsoft')) {
187
+ Logger.log(`GitHub login "${login}" does not end with _microsoft, skipping user information persistence`);
188
+ return;
189
+ }
190
+
191
+ // Create directory if it doesn't exist
192
+ await fs.mkdir(path.dirname(USER_INFO_PATH), { recursive: true });
193
+ Logger.debug(`Ensuring directory exists for user info at ${USER_INFO_PATH}`);
194
+
195
+ // Extract alias from login (remove _microsoft suffix)
196
+ const alias = login.toLowerCase().replace(/_microsoft$/, '');
197
+
198
+ // Prepare user information
199
+ const userInfo: UserInfo = {
200
+ alias,
201
+ name,
202
+ email
203
+ };
204
+
205
+ // Write user information to file
206
+ await fs.writeFile(USER_INFO_PATH, JSON.stringify(userInfo, null, 2));
207
+ Logger.debug({ message: 'User information persisted successfully', path: USER_INFO_PATH, alias });
208
+ } catch (error) {
209
+ Logger.error('Failed to persist user information:', error);
210
+ // Don't throw - persistence failure shouldn't block auth flow
211
+ }
129
212
  }
@@ -1,11 +1,30 @@
1
1
  import fs from 'fs';
2
2
  import path from 'path';
3
- import { SETTINGS_DIR } from '../core/metadatas/constants.js';
3
+ import os from 'os';
4
+ import { SETTINGS_DIR, USER_INFO_PATH } from '../core/metadatas/constants.js';
5
+ import { TelemetryService } from '../services/TelemetryService.js';
6
+ import { getPackageVersion } from './versionUtils.js';
7
+
8
+ export enum EventType {
9
+ IMCP_SERVE = 'imcp_serve',
10
+ SERVER_INSTALL = 'server_install',
11
+ SERVER_UNINSTALL = 'server_uninstall',
12
+ REQUIREMENT_UPDATE = 'requirement_update',
13
+ FEED_ONBOARD = 'feed_onboard',
14
+ FEED_VALIDATE = 'feed_validate'
15
+ }
16
+
17
+ export enum EventStatus {
18
+ SUCCESS = 'success',
19
+ FAILED = 'failed'
20
+ }
4
21
 
5
22
  export class Logger {
6
23
  private static verbose = false;
7
24
  private static fileLoggingEnabled = true;
8
25
  private static logsDir = path.join(SETTINGS_DIR, 'logs');
26
+ private static isTestEnvironment: boolean = false;
27
+ private static packageVersion = getPackageVersion().packageVersion;
9
28
 
10
29
  static setVerbose(isVerbose: boolean): void {
11
30
  this.verbose = isVerbose;
@@ -110,4 +129,67 @@ export class Logger {
110
129
 
111
130
  await this.writeToLogFile('ERROR', logMessage);
112
131
  }
132
+
133
+ private static getUsername(): string {
134
+ try {
135
+ // If on Windows, use os username directly
136
+ if (process.platform === 'win32') {
137
+ return os.userInfo().username;
138
+ }
139
+
140
+ // For Mac/Linux users
141
+ try {
142
+ const userInfoPath = USER_INFO_PATH;
143
+
144
+ if (fs.existsSync(userInfoPath)) {
145
+ const userInfo = JSON.parse(fs.readFileSync(userInfoPath, 'utf8'));
146
+ if (userInfo.alias) {
147
+ return userInfo.alias;
148
+ }
149
+ }
150
+ } catch (error) {
151
+ this.error('Failed to get user info from file', error);
152
+ }
153
+
154
+ // Fall back to os username if all else fails
155
+ return os.userInfo().username;
156
+ } catch (error) {
157
+ this.error('Failed to get username', error);
158
+ return 'unknown';
159
+ }
160
+ }
161
+
162
+ static trackEvent(eventType: EventType, dimensions: Record<string, any>): void {
163
+ try {
164
+ const username = this.getUsername();
165
+
166
+ // Add username and package version to dimensions
167
+ const allDimensions = {
168
+ username,
169
+ packageVersion: this.packageVersion,
170
+ ...dimensions
171
+ };
172
+
173
+ // Log to file
174
+ if (this.verbose) {
175
+ this.log(`Event: ${eventType}`);
176
+ this.log(JSON.stringify(allDimensions, null, 2));
177
+ }
178
+
179
+ // Skip AppInsights in test environment
180
+ if (Logger.isTestEnvironment) {
181
+ return;
182
+ }
183
+
184
+ // Log to Application Insights
185
+ try {
186
+ TelemetryService.trackEvent(eventType, allDimensions);
187
+ TelemetryService.flush();
188
+ } catch (insightsError) {
189
+ this.error('Failed to track event in Application Insights', insightsError);
190
+ }
191
+ } catch (error) {
192
+ this.error('Failed to log event', error);
193
+ }
194
+ }
113
195
  }
@@ -1,6 +1,7 @@
1
1
  import { Logger } from './logger.js';
2
- import { getPythonPackagePath, getSystemPythonPackageDirectory, GetBrowserPath } from './osUtils.js';
2
+ import { getPythonPackagePath, getSystemPythonPackageDirectory, getBrowserPath, getGlobalNPMPackagePath } from './osUtils.js';
3
3
  import { ServerInstallOptions } from '../core/metadatas/types.js'; // Adjusted path
4
+ import { SystemSettingsManager } from '../core/loaders/SystemSettingsManager.js';
4
5
  import * as fsSync from 'fs';
5
6
  import * as path from 'path';
6
7
  import { execSync } from 'child_process';
@@ -64,25 +65,7 @@ export function resolveNpmModulePath(providedNpmPath: string | undefined): strin
64
65
  return `${providedNpmPath}/node_modules`;
65
66
  }
66
67
 
67
- const nvmHome = process.env.NVM_HOME;
68
- if (nvmHome) {
69
- try {
70
- const nodeVersion = execSync('node -v').toString().trim();
71
- const nvmNodePath = path.join(nvmHome, nodeVersion);
72
- // Check if this path exists
73
- try {
74
- fsSync.accessSync(nvmNodePath);
75
- Logger.debug(`Resolved ${MACRO_EXPRESSIONS.NPMPATH} via NVM to: ${nvmNodePath}`);
76
- return nvmNodePath;
77
- } catch (error) {
78
- Logger.debug(`NVM controlled path doesn't exist: ${nvmNodePath}, will try global npm`);
79
- }
80
- } catch (error) {
81
- Logger.debug(`Error determining Node version for NVM: ${error}, will use global npm`);
82
- }
83
- }
84
-
85
- const globalNpmPath = execSync('npm root -g').toString().trim();
68
+ const globalNpmPath = getGlobalNPMPackagePath();
86
69
  Logger.debug(`Resolved ${MACRO_EXPRESSIONS.NPMPATH} via global npm to: ${globalNpmPath}`);
87
70
  return globalNpmPath;
88
71
  }
@@ -111,7 +94,7 @@ export async function resolveNpmPathMacro(_finalConfig: any, options: ServerInst
111
94
  export async function resolveBrowserPathMacro(): Promise<string | undefined> {
112
95
  Logger.debug(`Resolving ${MACRO_EXPRESSIONS.BROWSER_PATH}`);
113
96
  try {
114
- const browserPath = await GetBrowserPath();
97
+ const browserPath = await getBrowserPath();
115
98
  Logger.debug(`Resolved ${MACRO_EXPRESSIONS.BROWSER_PATH} to: ${browserPath}`);
116
99
  return browserPath;
117
100
  } catch (error) {
@@ -5,6 +5,8 @@ import util from 'util';
5
5
  import { Logger } from './logger.js';
6
6
  import fs from 'fs';
7
7
  import path from 'path';
8
+ import { execSync } from 'child_process';
9
+ import * as fsSync from 'fs';
8
10
 
9
11
  const execAsync = util.promisify(exec);
10
12
 
@@ -421,8 +423,40 @@ export async function getSystemPythonPackageDirectory(): Promise<string | null>
421
423
  return null;
422
424
  }
423
425
  }
426
+ /**
427
+ * Get the system Python executable path.
428
+ * This function returns the absolute path to the system Python executable (e.g., /Users/penwa/miniconda3/envs/browser-use-temp/bin/python).
429
+ * It uses 'which python' (Unix) or 'where python' (Windows) to locate the executable.
430
+ * @returns {Promise<string | null>} The path to the Python executable, or null if not found.
431
+ */
432
+ export async function getSystemPythonExecutablePath(): Promise<string | null> {
433
+ const command = process.platform === 'win32' ? 'where python' : 'which python';
434
+
435
+ Logger.debug({
436
+ action: 'get_system_python_executable_path',
437
+ command
438
+ });
439
+
440
+ try {
441
+ const { stdout } = await execAsync(command);
442
+ // Use the first path found, trim whitespace
443
+ const pythonPath = stdout.split('\n')[0].trim();
444
+ if (pythonPath) {
445
+ Logger.debug({
446
+ action: 'get_system_python_executable_path_success',
447
+ pythonPath
448
+ });
449
+ return pythonPath;
450
+ }
451
+ Logger.debug('No Python executable found');
452
+ return null;
453
+ } catch (error) {
454
+ Logger.debug(`Could not find system python using "${command}": ${error}`);
455
+ return null;
456
+ }
457
+ }
424
458
 
425
- export async function GetBrowserPath(): Promise<string> {
459
+ export async function getBrowserPath(): Promise<string> {
426
460
  const osType = getOSType();
427
461
  Logger.debug({
428
462
  action: 'get_system_browser_path',
@@ -503,4 +537,61 @@ export async function GetBrowserPath(): Promise<string> {
503
537
  Logger.error('Failed to get browser path', error);
504
538
  throw error;
505
539
  }
540
+ }
541
+
542
+ /**
543
+ * Get the global NPM path
544
+ * This function checks if NVM is installed and retrieves the global NPM path accordingly.
545
+ * If NVM is not installed, it falls back to the global NPM path.
546
+ * @returns The global NPM path as a string.
547
+ * @throws Error if the NPM path cannot be determined.
548
+ * */
549
+ export function getGlobalNPMPackagePath(): string {
550
+ const nvmHome = process.env.NVM_HOME;
551
+ if (nvmHome) {
552
+ try {
553
+ const nodeVersion = execSync('node -v').toString().trim();
554
+ const nvmNodePath = path.join(nvmHome, nodeVersion);
555
+ // Check if this path exists
556
+ try {
557
+ fsSync.accessSync(nvmNodePath);
558
+ return nvmNodePath;
559
+ } catch (error) {
560
+ Logger.debug(`NVM controlled path doesn't exist: ${nvmNodePath}, will try global npm`);
561
+ }
562
+ } catch (error) {
563
+ Logger.debug(`Error determining Node version for NVM: ${error}, will use global npm`);
564
+ }
565
+ }
566
+
567
+ const globalNpmPath = execSync('npm root -g').toString().trim();
568
+ return globalNpmPath;
569
+ }
570
+
571
+
572
+ /**
573
+ * Get the NPM executable path on the current platform (Windows, macOS, Linux).
574
+ * On Windows, uses PowerShell to locate npm. On macOS/Linux, uses 'which' to locate npm.
575
+ * Returns the directory containing the npm executable, or a platform-appropriate default if not found.
576
+ */
577
+ export async function getNpmExecutablePath(): Promise<string> {
578
+ try {
579
+ if (process.platform === 'win32') {
580
+ const { stdout } = await execAsync('powershell -Command "get-command npm | Select-Object -ExpandProperty Source"');
581
+ return stdout.trim().replace(/\\npm\.cmd$/, '');
582
+ } else {
583
+ // macOS or Linux
584
+ const { stdout } = await execAsync('which npm');
585
+ // Remove the trailing '/npm' to get the directory
586
+ return stdout.trim().replace(/\/npm$/, '');
587
+ }
588
+ } catch (error) {
589
+ Logger.error('Error getting npm path:', error);
590
+ if (process.platform === 'win32') {
591
+ return 'C:\\Program Files\\nodejs';
592
+ } else {
593
+ // Common default for Unix systems
594
+ return '/usr/local/bin';
595
+ }
596
+ }
506
597
  }
@@ -1,3 +1,46 @@
1
+ import fs from 'fs';
2
+ import path from 'path';
3
+ import { fileURLToPath } from 'url';
4
+ import axios from 'axios';
5
+ import { Logger } from './logger.js'; // Assuming logger.js is in the same directory
6
+
7
+ // ANSI color codes
8
+ const COLORS = {
9
+ reset: '\x1b[0m',
10
+ yellow: '\x1b[33m'
11
+ };
12
+
13
+ const PACKAGE_NAME = 'imcp'; // Default package name
14
+
15
+ export function getPackageVersion(): { packageName: string, packageVersion: string } {
16
+ try {
17
+ // First try npm environment variable (available during npm scripts)
18
+ if (process.env.npm_package_version) {
19
+ return { packageName: PACKAGE_NAME, packageVersion: process.env.npm_package_version };
20
+ }
21
+
22
+ // Fall back to reading package.json
23
+ // Get directory name of current module
24
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
25
+
26
+ // Traverse up to find package.json (max 3 levels up)
27
+ let currentDir = __dirname;
28
+ for (let i = 0; i < 3; i++) {
29
+ const packagePath = path.join(currentDir, 'package.json');
30
+ if (fs.existsSync(packagePath)) {
31
+ const packageJson = JSON.parse(fs.readFileSync(packagePath, 'utf8'));
32
+ return { packageName: PACKAGE_NAME, packageVersion: packageJson.version };
33
+ }
34
+ currentDir = path.join(currentDir, '..');
35
+ }
36
+
37
+ return { packageName: PACKAGE_NAME, packageVersion: 'unknown' };
38
+ } catch (error) {
39
+ console.error('Failed to get package version:', error);
40
+ return { packageName: PACKAGE_NAME, packageVersion: 'unknown' };
41
+ }
42
+ }
43
+
1
44
  /**
2
45
  * Utility functions for version comparison and management
3
46
  */
@@ -11,19 +54,61 @@
11
54
  * a positive number if v1 > v2, 0 if equal)
12
55
  */
13
56
  export function compareVersions(v1: string, v2: string): number {
14
- const v1Parts = v1.split('.').map(Number);
15
- const v2Parts = v2.split('.').map(Number);
16
-
17
- for (let i = 0; i < Math.max(v1Parts.length, v2Parts.length); i++) {
18
- const v1Part = i < v1Parts.length ? v1Parts[i] : 0;
19
- const v2Part = i < v2Parts.length ? v2Parts[i] : 0;
20
-
21
- if (v1Part !== v2Part) {
22
- // This returns the actual difference, which is:
23
- // negative if v1Part < v2Part, positive if v1Part > v2Part
24
- return v1Part - v2Part;
25
- }
57
+ const v1Parts = v1.split('.').map(Number);
58
+ const v2Parts = v2.split('.').map(Number);
59
+
60
+ for (let i = 0; i < Math.max(v1Parts.length, v2Parts.length); i++) {
61
+ const v1Part = i < v1Parts.length ? v1Parts[i] : 0;
62
+ const v2Part = i < v2Parts.length ? v2Parts[i] : 0;
63
+
64
+ if (v1Part !== v2Part) {
65
+ // This returns the actual difference, which is:
66
+ // negative if v1Part < v2Part, positive if v1Part > v2Part
67
+ return v1Part - v2Part;
68
+ }
69
+ }
70
+
71
+ return 0;
72
+ }
73
+
74
+ /**
75
+ * Check if there's a newer version of the package available
76
+ */
77
+ export async function checkForUpdates(): Promise<void> {
78
+ Logger.debug(`Checking for updates...`);
79
+ try {
80
+ const version = await getAppVersion();
81
+ if (version.availableUpdate) {
82
+ console.log(`${COLORS.yellow}Update available for ${version.name}: ${version.version} → ${version.availableUpdate.latestVersion}${COLORS.reset}`);
83
+ console.log(`${COLORS.yellow}Relaunch with \`npx -y ${version.name}@latest serve\` or update package with \`npm install -g ${version.name}@latest\`${COLORS.reset}`);
84
+ }
85
+
86
+ } catch (error) {
87
+ // Log the npm error
88
+ Logger.debug(`Failed to check npm registry: ${error instanceof Error ? error.message : String(error)}`);
89
+ }
90
+ }
91
+
92
+ /**
93
+ * Retrieves the application version and information about available updates.
94
+ * @returns A promise that resolves to an object containing the current packageVersion and any availableUpdates.
95
+ */
96
+ export async function getAppVersion(): Promise<{ name: string, version: string; availableUpdate?: { latestVersion: string; message: string} }> {
97
+ let availableUpdate: { latestVersion: string; message: string } | undefined = undefined;
98
+
99
+ const { packageName, packageVersion } = getPackageVersion();
100
+ // Check for updates from npm registry
101
+ try {
102
+ const npmResponse = await axios.get(`https://registry.npmjs.org/${packageName}`);
103
+ if (npmResponse.data && npmResponse.data['dist-tags'] && npmResponse.data['dist-tags'].latest) {
104
+ const latestVersion = npmResponse.data['dist-tags'].latest;
105
+ if (compareVersions(latestVersion, packageVersion) > 0) {
106
+ availableUpdate = { latestVersion, message: `New version (${latestVersion}) is available. Relaunch with \`npx -y ${packageName}@latest serve\`` };
107
+ }
26
108
  }
109
+ } catch (npmError) {
110
+ Logger.debug(`Failed to fetch latest version from npm for ${packageName}: ${npmError instanceof Error ? npmError.message : String(npmError)}`);
111
+ }
27
112
 
28
- return 0;
113
+ return { name: packageName, version: packageVersion, availableUpdate };
29
114
  }