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.
- package/.roo/rules-code/rules.md +88 -0
- package/dist/cli/index.js +0 -0
- package/dist/core/metadatas/constants.d.ts +7 -0
- package/dist/core/metadatas/constants.js +7 -0
- package/dist/core/onboard/FeedOnboardService.d.ts +7 -3
- package/dist/core/onboard/FeedOnboardService.js +52 -5
- package/dist/core/onboard/OnboardProcessor.js +22 -22
- package/dist/services/MCPManager.js +66 -6
- package/dist/services/TelemetryService.d.ts +15 -0
- package/dist/services/TelemetryService.js +54 -0
- package/dist/utils/githubAuth.js +65 -0
- package/dist/utils/logger.d.ts +16 -0
- package/dist/utils/logger.js +78 -1
- package/dist/utils/versionUtils.d.ts +1 -0
- package/dist/utils/versionUtils.js +29 -0
- package/dist/web/public/css/serverCategoryList.css +120 -0
- package/dist/web/public/index.html +6 -3
- package/dist/web/public/js/flights/flights.js +0 -1
- package/dist/web/public/js/onboard/formProcessor.js +18 -11
- package/dist/web/public/js/onboard/publishHandler.js +30 -0
- package/dist/web/public/js/onboard/templates.js +5 -1
- package/dist/web/public/js/onboard/uiHandlers.js +266 -39
- package/dist/web/public/js/onboard/validationHandlers.js +71 -39
- package/dist/web/public/js/serverCategoryList.js +91 -7
- package/dist/web/public/onboard.html +2 -2
- package/dist/web/server.js +11 -1
- package/{src/web/public/js/onboard → docs}/ONBOARDING_PAGE_DESIGN.md +15 -125
- package/docs/Telemetry.md +136 -0
- package/memory-bank/activeContext.md +14 -0
- package/memory-bank/decisionLog.md +28 -0
- package/memory-bank/productContext.md +41 -0
- package/memory-bank/progress.md +5 -0
- package/memory-bank/systemPatterns.md +3 -0
- package/package.json +2 -1
- package/src/core/metadatas/constants.ts +9 -0
- package/src/core/onboard/FeedOnboardService.ts +59 -5
- package/src/core/onboard/OnboardProcessor.ts +25 -23
- package/src/services/MCPManager.ts +78 -8
- package/src/services/TelemetryService.ts +59 -0
- package/src/utils/githubAuth.ts +84 -1
- package/src/utils/logger.ts +83 -1
- package/src/utils/versionUtils.ts +33 -0
- package/src/web/public/css/serverCategoryList.css +120 -0
- package/src/web/public/index.html +6 -3
- package/src/web/public/js/onboard/formProcessor.js +18 -11
- package/src/web/public/js/onboard/publishHandler.js +30 -0
- package/src/web/public/js/onboard/templates.js +5 -1
- package/src/web/public/js/onboard/uiHandlers.js +266 -39
- package/src/web/public/js/onboard/validationHandlers.js +71 -39
- package/src/web/public/js/serverCategoryList.js +91 -7
- package/src/web/public/onboard.html +2 -2
- package/src/web/server.ts +11 -1
- package/dist/cli/commands/start.d.ts +0 -2
- package/dist/cli/commands/start.js +0 -32
- package/dist/cli/commands/sync.d.ts +0 -2
- package/dist/cli/commands/sync.js +0 -17
- package/dist/core/ConfigurationLoader.d.ts +0 -32
- package/dist/core/ConfigurationLoader.js +0 -236
- package/dist/core/ConfigurationProvider.d.ts +0 -35
- package/dist/core/ConfigurationProvider.js +0 -375
- package/dist/core/InstallationService.d.ts +0 -50
- package/dist/core/InstallationService.js +0 -350
- package/dist/core/MCPManager.d.ts +0 -28
- package/dist/core/MCPManager.js +0 -188
- package/dist/core/RequirementService.d.ts +0 -40
- package/dist/core/RequirementService.js +0 -110
- package/dist/core/ServerSchemaLoader.d.ts +0 -11
- package/dist/core/ServerSchemaLoader.js +0 -43
- package/dist/core/ServerSchemaProvider.d.ts +0 -17
- package/dist/core/ServerSchemaProvider.js +0 -120
- package/dist/core/constants.d.ts +0 -47
- package/dist/core/constants.js +0 -94
- package/dist/core/installers/BaseInstaller.d.ts +0 -74
- package/dist/core/installers/BaseInstaller.js +0 -253
- package/dist/core/installers/ClientInstaller.d.ts +0 -23
- package/dist/core/installers/ClientInstaller.js +0 -564
- package/dist/core/installers/CommandInstaller.d.ts +0 -37
- package/dist/core/installers/CommandInstaller.js +0 -173
- package/dist/core/installers/GeneralInstaller.d.ts +0 -33
- package/dist/core/installers/GeneralInstaller.js +0 -85
- package/dist/core/installers/InstallerFactory.d.ts +0 -54
- package/dist/core/installers/InstallerFactory.js +0 -97
- package/dist/core/installers/NpmInstaller.d.ts +0 -26
- package/dist/core/installers/NpmInstaller.js +0 -127
- package/dist/core/installers/PipInstaller.d.ts +0 -28
- package/dist/core/installers/PipInstaller.js +0 -127
- package/dist/core/installers/RequirementInstaller.d.ts +0 -33
- package/dist/core/installers/RequirementInstaller.js +0 -3
- package/dist/core/types.d.ts +0 -166
- package/dist/core/types.js +0 -16
- package/dist/services/InstallRequestValidator.d.ts +0 -21
- package/dist/services/InstallRequestValidator.js +0 -99
- package/dist/web/public/js/modal/installHandler.js +0 -227
- package/dist/web/public/js/modal/loadingUI.js +0 -74
- package/dist/web/public/js/modal/modalUI.js +0 -214
- 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 (
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
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
|
-
|
|
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:
|
|
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:
|
|
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:
|
|
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
|
+
}
|
package/src/utils/githubAuth.ts
CHANGED
|
@@ -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
|
}
|
package/src/utils/logger.ts
CHANGED
|
@@ -1,11 +1,30 @@
|
|
|
1
1
|
import fs from 'fs';
|
|
2
2
|
import path from 'path';
|
|
3
|
-
import
|
|
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
|
-
<
|
|
29
|
-
|
|
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">
|