imcp 0.0.18 → 0.1.1

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 (96) hide show
  1. package/.roo/rules-code/rules.md +88 -0
  2. package/dist/cli/index.js +0 -0
  3. package/dist/core/metadatas/constants.d.ts +7 -0
  4. package/dist/core/metadatas/constants.js +7 -0
  5. package/dist/core/onboard/FeedOnboardService.d.ts +7 -3
  6. package/dist/core/onboard/FeedOnboardService.js +52 -5
  7. package/dist/core/onboard/OnboardProcessor.js +22 -22
  8. package/dist/services/MCPManager.js +66 -6
  9. package/dist/services/TelemetryService.d.ts +15 -0
  10. package/dist/services/TelemetryService.js +54 -0
  11. package/dist/utils/githubAuth.js +65 -0
  12. package/dist/utils/logger.d.ts +16 -0
  13. package/dist/utils/logger.js +78 -1
  14. package/dist/utils/versionUtils.d.ts +1 -0
  15. package/dist/utils/versionUtils.js +29 -0
  16. package/dist/web/public/css/serverCategoryList.css +120 -0
  17. package/dist/web/public/index.html +6 -3
  18. package/dist/web/public/js/flights/flights.js +0 -1
  19. package/dist/web/public/js/onboard/formProcessor.js +18 -11
  20. package/dist/web/public/js/onboard/publishHandler.js +30 -0
  21. package/dist/web/public/js/onboard/templates.js +5 -1
  22. package/dist/web/public/js/onboard/uiHandlers.js +266 -39
  23. package/dist/web/public/js/onboard/validationHandlers.js +71 -39
  24. package/dist/web/public/js/serverCategoryList.js +91 -7
  25. package/dist/web/public/onboard.html +2 -2
  26. package/dist/web/server.js +11 -1
  27. package/{src/web/public/js/onboard → docs}/ONBOARDING_PAGE_DESIGN.md +15 -125
  28. package/docs/Telemetry.md +136 -0
  29. package/memory-bank/activeContext.md +14 -0
  30. package/memory-bank/decisionLog.md +28 -0
  31. package/memory-bank/productContext.md +41 -0
  32. package/memory-bank/progress.md +5 -0
  33. package/memory-bank/systemPatterns.md +3 -0
  34. package/package.json +2 -1
  35. package/src/core/metadatas/constants.ts +9 -0
  36. package/src/core/onboard/FeedOnboardService.ts +59 -5
  37. package/src/core/onboard/OnboardProcessor.ts +25 -23
  38. package/src/services/MCPManager.ts +78 -8
  39. package/src/services/TelemetryService.ts +59 -0
  40. package/src/utils/githubAuth.ts +84 -1
  41. package/src/utils/logger.ts +83 -1
  42. package/src/utils/versionUtils.ts +33 -0
  43. package/src/web/public/css/serverCategoryList.css +120 -0
  44. package/src/web/public/index.html +6 -3
  45. package/src/web/public/js/onboard/formProcessor.js +18 -11
  46. package/src/web/public/js/onboard/publishHandler.js +30 -0
  47. package/src/web/public/js/onboard/templates.js +5 -1
  48. package/src/web/public/js/onboard/uiHandlers.js +266 -39
  49. package/src/web/public/js/onboard/validationHandlers.js +71 -39
  50. package/src/web/public/js/serverCategoryList.js +91 -7
  51. package/src/web/public/onboard.html +2 -2
  52. package/src/web/server.ts +11 -1
  53. package/dist/cli/commands/start.d.ts +0 -2
  54. package/dist/cli/commands/start.js +0 -32
  55. package/dist/cli/commands/sync.d.ts +0 -2
  56. package/dist/cli/commands/sync.js +0 -17
  57. package/dist/core/ConfigurationLoader.d.ts +0 -32
  58. package/dist/core/ConfigurationLoader.js +0 -236
  59. package/dist/core/ConfigurationProvider.d.ts +0 -35
  60. package/dist/core/ConfigurationProvider.js +0 -375
  61. package/dist/core/InstallationService.d.ts +0 -50
  62. package/dist/core/InstallationService.js +0 -350
  63. package/dist/core/MCPManager.d.ts +0 -28
  64. package/dist/core/MCPManager.js +0 -188
  65. package/dist/core/RequirementService.d.ts +0 -40
  66. package/dist/core/RequirementService.js +0 -110
  67. package/dist/core/ServerSchemaLoader.d.ts +0 -11
  68. package/dist/core/ServerSchemaLoader.js +0 -43
  69. package/dist/core/ServerSchemaProvider.d.ts +0 -17
  70. package/dist/core/ServerSchemaProvider.js +0 -120
  71. package/dist/core/constants.d.ts +0 -47
  72. package/dist/core/constants.js +0 -94
  73. package/dist/core/installers/BaseInstaller.d.ts +0 -74
  74. package/dist/core/installers/BaseInstaller.js +0 -253
  75. package/dist/core/installers/ClientInstaller.d.ts +0 -23
  76. package/dist/core/installers/ClientInstaller.js +0 -564
  77. package/dist/core/installers/CommandInstaller.d.ts +0 -37
  78. package/dist/core/installers/CommandInstaller.js +0 -173
  79. package/dist/core/installers/GeneralInstaller.d.ts +0 -33
  80. package/dist/core/installers/GeneralInstaller.js +0 -85
  81. package/dist/core/installers/InstallerFactory.d.ts +0 -54
  82. package/dist/core/installers/InstallerFactory.js +0 -97
  83. package/dist/core/installers/NpmInstaller.d.ts +0 -26
  84. package/dist/core/installers/NpmInstaller.js +0 -127
  85. package/dist/core/installers/PipInstaller.d.ts +0 -28
  86. package/dist/core/installers/PipInstaller.js +0 -127
  87. package/dist/core/installers/RequirementInstaller.d.ts +0 -33
  88. package/dist/core/installers/RequirementInstaller.js +0 -3
  89. package/dist/core/types.d.ts +0 -166
  90. package/dist/core/types.js +0 -16
  91. package/dist/services/InstallRequestValidator.d.ts +0 -21
  92. package/dist/services/InstallRequestValidator.js +0 -99
  93. package/dist/web/public/js/modal/installHandler.js +0 -227
  94. package/dist/web/public/js/modal/loadingUI.js +0 -74
  95. package/dist/web/public/js/modal/modalUI.js +0 -214
  96. package/dist/web/public/js/modal/version.js +0 -20
@@ -127,32 +127,34 @@ export class OnboardProcessor {
127
127
  // Create a mutable copy of mcpServers to update schema paths
128
128
  const updatedMcpServers = [];
129
129
  for (const server of config.mcpServers) {
130
- if (!serverList.includes(server.name)) continue;
131
130
  let updatedServer = { ...server }; // Shallow copy server config
132
- if (updatedServer.schemas && typeof updatedServer.schemas === 'string' && updatedServer.schemas.trim() !== '') {
133
- const originalSchemaPath = updatedServer.schemas;
134
- const serverName = updatedServer.name;
135
- const newSchemaFileName = `${serverName}.json`;
136
-
137
- // Schemas are now directly under categorySchemasPath
138
- const newSchemaPathInRepo = path.join(categorySchemasPath, newSchemaFileName);
139
-
140
- try {
141
- // Read content from the original schema path
142
- const schemaContent = await fs.readFile(originalSchemaPath, 'utf-8');
143
- // Write content to the new schema file in the repo
144
- await fs.writeFile(newSchemaPathInRepo, schemaContent);
145
- Logger.debug(`[${onboardingId}] Copied schema for server '${serverName}' from '${originalSchemaPath}' to '${newSchemaPathInRepo}'`);
146
-
147
- // Update the schemas property to the new filename, as per instruction "rename schemas in McpServer as serverName].json"
148
- updatedServer.schemas = newSchemaFileName;
149
- } catch (schemaError) {
150
- const errorMsg = `Error processing schema for server '${serverName}' (source: ${originalSchemaPath}): ${schemaError instanceof Error ? schemaError.message : String(schemaError)}`;
151
- Logger.error(`[${onboardingId}] ${errorMsg}`);
152
- // Propagate the error to fail the onboarding step
153
- throw new Error(errorMsg);
131
+ if (serverList.includes(server.name)) {
132
+ if (updatedServer.schemas && typeof updatedServer.schemas === 'string' && updatedServer.schemas.trim() !== '') {
133
+ const originalSchemaPath = updatedServer.schemas;
134
+ const serverName = updatedServer.name;
135
+ const newSchemaFileName = `${serverName}.json`;
136
+
137
+ // Schemas are now directly under categorySchemasPath
138
+ const newSchemaPathInRepo = path.join(categorySchemasPath, newSchemaFileName);
139
+
140
+ try {
141
+ // Read content from the original schema path
142
+ const schemaContent = await fs.readFile(originalSchemaPath, 'utf-8');
143
+ // Write content to the new schema file in the repo
144
+ await fs.writeFile(newSchemaPathInRepo, schemaContent);
145
+ Logger.debug(`[${onboardingId}] Copied schema for server '${serverName}' from '${originalSchemaPath}' to '${newSchemaPathInRepo}'`);
146
+
147
+ // Update the schemas property to the new filename, as per instruction "rename schemas in McpServer as serverName].json"
148
+ updatedServer.schemas = newSchemaFileName;
149
+ } catch (schemaError) {
150
+ const errorMsg = `Error processing schema for server '${serverName}' (source: ${originalSchemaPath}): ${schemaError instanceof Error ? schemaError.message : String(schemaError)}`;
151
+ Logger.error(`[${onboardingId}] ${errorMsg}`);
152
+ // Propagate the error to fail the onboarding step
153
+ throw new Error(errorMsg);
154
+ }
154
155
  }
155
156
  }
157
+
156
158
  updatedMcpServers.push(updatedServer);
157
159
  }
158
160
 
@@ -13,8 +13,8 @@ import {
13
13
  InstallationStatus,
14
14
  FeedConfiguration,
15
15
  } from '../core/metadatas/types.js';
16
- import { OperationStatus } from '../core/onboard/OnboardStatus.js';
17
- import { Logger } from '../utils/logger.js';
16
+ import { OnboardingProcessStatus, OperationStatus } from '../core/onboard/OnboardStatus.js';
17
+ import { Logger, EventType, EventStatus } from '../utils/logger.js';
18
18
  import { FeedOnboardService } from '../core/onboard/FeedOnboardService.js';
19
19
  import path from 'path';
20
20
  import { Server } from 'http';
@@ -78,6 +78,15 @@ export class MCPManager extends EventEmitter {
78
78
  }
79
79
  const installResult = await this.installationService.install(categoryName, serverName, requestOptions);
80
80
 
81
+ // Log the event with appropriate status
82
+ Logger.trackEvent(EventType.SERVER_INSTALL, {
83
+ status: installResult.success ? EventStatus.SUCCESS : EventStatus.FAILED,
84
+ errorMessage: !installResult.success ? installResult.message : undefined,
85
+ categoryName,
86
+ serverName,
87
+ ...requestOptions
88
+ });
89
+
81
90
  if (!installResult.success) {
82
91
  return installResult;
83
92
  }
@@ -86,10 +95,19 @@ export class MCPManager extends EventEmitter {
86
95
  return installResult;
87
96
 
88
97
  } catch (error) {
89
- console.error(`Unexpected error during installServer for ${serverName}:`, error);
98
+ const errorMessage = `Failed to install ${serverName}: ${error instanceof Error ? error.message : String(error)}`;
99
+ Logger.error(errorMessage, error);
100
+ Logger.trackEvent(EventType.SERVER_INSTALL, {
101
+ status: EventStatus.FAILED,
102
+ errorMessage,
103
+ categoryName,
104
+ serverName,
105
+ ...requestOptions
106
+ });
107
+
90
108
  return {
91
109
  success: false,
92
- message: `Failed to install ${serverName}: ${error instanceof Error ? error.message : String(error)}`,
110
+ message: errorMessage,
93
111
  error: error instanceof Error ? error : new Error(String(error)),
94
112
  };
95
113
  }
@@ -100,6 +118,8 @@ export class MCPManager extends EventEmitter {
100
118
  serverName: string,
101
119
  options: ServerUninstallOptions = {}
102
120
  ): Promise<ServerOperationResult> {
121
+ const { targets = [], removeData = false } = options;
122
+
103
123
  try {
104
124
  const serverCategory = await this.configProvider.getServerCategory(categoryName);
105
125
  if (!serverCategory) {
@@ -109,8 +129,6 @@ export class MCPManager extends EventEmitter {
109
129
  };
110
130
  }
111
131
 
112
- const { targets = [], removeData = false } = options;
113
-
114
132
  // Clear installation status for specified targets
115
133
  const currentStatus: InstallationStatus = serverCategory.installationStatus || {
116
134
  requirementsStatus: {},
@@ -141,6 +159,13 @@ export class MCPManager extends EventEmitter {
141
159
  currentStatus.requirementsStatus || {},
142
160
  serversStatus
143
161
  );
162
+ Logger.trackEvent(EventType.SERVER_UNINSTALL, {
163
+ status: EventStatus.SUCCESS,
164
+ categoryName,
165
+ serverName,
166
+ targets,
167
+ removeData
168
+ });
144
169
 
145
170
  this.emit(MCPEvent.SERVER_UNINSTALLED, { serverName, targets });
146
171
 
@@ -149,9 +174,20 @@ export class MCPManager extends EventEmitter {
149
174
  message: `Successfully uninstalled ${serverName} from ${targets.join(', ')}`,
150
175
  };
151
176
  } catch (error) {
177
+ const errorMessage = `Failed to uninstall ${serverName}: ${error instanceof Error ? error.message : String(error)}`;
178
+
179
+ Logger.trackEvent(EventType.SERVER_UNINSTALL, {
180
+ status: EventStatus.FAILED,
181
+ errorMessage,
182
+ categoryName,
183
+ serverName,
184
+ targets: targets || [],
185
+ removeData: removeData || false
186
+ });
187
+
152
188
  return {
153
189
  success: false,
154
- message: `Failed to uninstall ${serverName}`,
190
+ message: errorMessage,
155
191
  error: error as Error,
156
192
  };
157
193
  }
@@ -201,15 +237,34 @@ export class MCPManager extends EventEmitter {
201
237
  updatedStatus
202
238
  );
203
239
 
240
+ Logger.trackEvent(EventType.REQUIREMENT_UPDATE, {
241
+ status: EventStatus.SUCCESS,
242
+ categoryName,
243
+ serverName,
244
+ requirementName,
245
+ updateVersion
246
+ });
247
+
204
248
  return {
205
249
  success: true,
206
250
  message: `Successfully updated ${requirementName} to version ${updateVersion}`,
207
251
  };
208
252
  } catch (error) {
253
+ const errorMessage = `Failed to update ${requirementName}: ${error instanceof Error ? error.message : String(error)}`;
209
254
  console.error(`Error updating requirement ${requirementName}:`, error);
255
+
256
+ Logger.trackEvent(EventType.REQUIREMENT_UPDATE, {
257
+ status: EventStatus.FAILED,
258
+ errorMessage,
259
+ categoryName,
260
+ serverName,
261
+ requirementName,
262
+ updateVersion
263
+ });
264
+
210
265
  return {
211
266
  success: false,
212
- message: `Failed to update ${requirementName}: ${error instanceof Error ? error.message : String(error)}`,
267
+ message: errorMessage,
213
268
  error: error instanceof Error ? error : new Error(String(error)),
214
269
  };
215
270
  }
@@ -222,6 +277,13 @@ export class MCPManager extends EventEmitter {
222
277
  async onboardFeed(config: FeedConfiguration): Promise<OperationStatus & { feedConfiguration?: FeedConfiguration }> {
223
278
  try {
224
279
  const result = await this.feedOnboardService.onboardFeed(config);
280
+
281
+ Logger.trackEvent(EventType.FEED_ONBOARD, {
282
+ status: result.status === OnboardingProcessStatus.SUCCEEDED ? EventStatus.SUCCESS : EventStatus.FAILED,
283
+ errorMessage: result.message,
284
+ feedConfig: config
285
+ });
286
+
225
287
  // After successful onboarding initiation, sync feeds to get the latest changes
226
288
  // Syncing should ideally happen after the PR is merged, but for now,
227
289
  // syncing here makes the new (pending) category available locally if needed.
@@ -229,7 +291,15 @@ export class MCPManager extends EventEmitter {
229
291
  await this.syncFeeds();
230
292
  return result;
231
293
  } catch (error) {
294
+ const errorMessage = error instanceof Error ? error.message : String(error);
232
295
  Logger.error('Failed to onboard feed in MCPManager:', error);
296
+
297
+ Logger.trackEvent(EventType.FEED_ONBOARD, {
298
+ status: EventStatus.FAILED,
299
+ errorMessage,
300
+ feedName: config.name
301
+ });
302
+
233
303
  throw error; // Rethrow or handle by returning a failed OperationStatus
234
304
  }
235
305
  }
@@ -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
+ }
@@ -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();
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,3 +1,36 @@
1
+ import fs from 'fs';
2
+ import path from 'path';
3
+ import { fileURLToPath } from 'url';
4
+
5
+ export function getPackageVersion(): string {
6
+ try {
7
+ // First try npm environment variable (available during npm scripts)
8
+ if (process.env.npm_package_version) {
9
+ return process.env.npm_package_version;
10
+ }
11
+
12
+ // Fall back to reading package.json
13
+ // Get directory name of current module
14
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
15
+
16
+ // Traverse up to find package.json (max 3 levels up)
17
+ let currentDir = __dirname;
18
+ for (let i = 0; i < 3; i++) {
19
+ const packagePath = path.join(currentDir, 'package.json');
20
+ if (fs.existsSync(packagePath)) {
21
+ const packageJson = JSON.parse(fs.readFileSync(packagePath, 'utf8'));
22
+ return packageJson.version;
23
+ }
24
+ currentDir = path.join(currentDir, '..');
25
+ }
26
+
27
+ return 'unknown';
28
+ } catch (error) {
29
+ console.error('Failed to get package version:', error);
30
+ return 'unknown';
31
+ }
32
+ }
33
+
1
34
  /**
2
35
  * Utility functions for version comparison and management
3
36
  */
@@ -0,0 +1,120 @@
1
+ /* Server Category List Styles */
2
+ .category-toggle {
3
+ cursor: pointer;
4
+ display: flex;
5
+ align-items: center;
6
+ color: #4b5563; /* gray-600 */
7
+ transition: all 0.2s;
8
+ }
9
+
10
+ .category-toggle:hover {
11
+ color: #1e40af; /* blue-800 */
12
+ }
13
+
14
+ .category-toggle i {
15
+ font-size: 1.25rem;
16
+ transition: transform 0.2s;
17
+ }
18
+
19
+ .category-toggle.collapsed i {
20
+ transform: rotate(-90deg);
21
+ }
22
+
23
+ .server-list-container {
24
+ transition: height 0.3s ease, opacity 0.3s ease, margin 0.3s ease;
25
+ overflow: hidden;
26
+ }
27
+
28
+ .server-list-container.collapsed {
29
+ height: 0 !important;
30
+ opacity: 0;
31
+ margin-top: 0;
32
+ margin-bottom: 0;
33
+ }
34
+
35
+ .category-section {
36
+ margin-bottom: 1rem;
37
+ border-bottom: 1px solid #e5e7eb; /* gray-200 */
38
+ padding-bottom: 0.5rem;
39
+ }
40
+
41
+ .category-section:last-child {
42
+ border-bottom: none;
43
+ margin-bottom: 0;
44
+ }
45
+
46
+ .category-header {
47
+ display: flex;
48
+ justify-content: space-between;
49
+ align-items: center;
50
+ margin-bottom: 0.5rem;
51
+ }
52
+
53
+ /* Pin button styles */
54
+ .pin-button {
55
+ cursor: pointer;
56
+ display: flex;
57
+ align-items: center;
58
+ color: #9ca3af; /* gray-400 */
59
+ margin-right: 8px;
60
+ transition: all 0.2s ease;
61
+ }
62
+
63
+ .pin-button:hover {
64
+ color: #4b5563; /* gray-600 */
65
+ }
66
+
67
+ .pin-button.pinned {
68
+ color: #2563eb; /* blue-600 */
69
+ }
70
+
71
+ .pin-button.pinned:hover {
72
+ color: #1d4ed8; /* blue-700 */
73
+ }
74
+
75
+ /* Pinned server section */
76
+ .server-item.pinned {
77
+ border-left: 3px solid #2563eb; /* blue-600 */
78
+ background-color: #f0f7ff; /* very light blue */
79
+ position: relative;
80
+ transition: all 0.2s ease-in-out;
81
+ }
82
+
83
+ /* Pin animation */
84
+ .server-item {
85
+ transition: transform 0.3s ease, border-left 0.2s ease, background-color 0.2s ease;
86
+ }
87
+
88
+ /* Visual indicator for pinned items at the top */
89
+ .server-item.pinned::before {
90
+ content: "";
91
+ position: absolute;
92
+ top: -3px;
93
+ left: 0;
94
+ right: 0;
95
+ height: 3px;
96
+ background-color: #2563eb; /* blue-600 */
97
+ opacity: 0;
98
+ transition: opacity 0.2s ease;
99
+ }
100
+
101
+ /* Show top indicator for the first pinned item */
102
+ .server-item.pinned:first-child::before {
103
+ opacity: 1;
104
+ }
105
+
106
+ /* Add hover effect to pinned items */
107
+ .server-item.pinned:hover {
108
+ background-color: #e6f0ff; /* slightly darker on hover */
109
+ }
110
+
111
+ /* Pin animation effect */
112
+ @keyframes pin-animation {
113
+ 0% { transform: scale(1); }
114
+ 50% { transform: scale(1.03); }
115
+ 100% { transform: scale(1); }
116
+ }
117
+
118
+ .pin-animation {
119
+ animation: pin-animation 0.3s ease;
120
+ }
@@ -9,11 +9,12 @@
9
9
  <link href="https://unpkg.com/boxicons@2.1.4/css/boxicons.min.css" rel="stylesheet">
10
10
  <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
11
11
  <link href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.7.2/font/bootstrap-icons.css" rel="stylesheet">
12
- <link rel="stylesheet" href="styles.css">
12
+ <link rel="stylesheet" href="styles.css">
13
13
  <link rel="stylesheet" href="css/modal.css">
14
14
  <link rel="stylesheet" href="css/notifications.css">
15
15
  <link rel="stylesheet" href="css/serverDetails.css">
16
16
  <link rel="stylesheet" href="css/detailsWidget.css">
17
+ <link rel="stylesheet" href="css/serverCategoryList.css">
17
18
  <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
18
19
 
19
20
  <!-- Alert container for notifications -->
@@ -25,8 +26,10 @@
25
26
  <div class="container mx-auto px-4 py-6">
26
27
  <div class="flex items-center justify-between mb-8">
27
28
  <h1 class="text-3xl font-bold text-gray-900 flex items-center">
28
- <i class='bx bx-server mr-3 text-blue-600'></i>
29
- IMCP Server Manager
29
+ <a href="index.html" class="flex items-center text-gray-900 hover:text-blue-600 transition-colors">
30
+ <i class='bx bx-server mr-3 text-blue-600'></i>
31
+ IMCP Server Manager
32
+ </a>
30
33
  </h1>
31
34
  <div class="lg:w-2/3 flex justify-end items-center gap-4">
32
35
  <div class="relative w-48 focus-within:w-96 transition-all duration-300 ease-in-out">