imcp 0.0.12 → 0.0.14
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/dist/core/ConfigurationProvider.d.ts +2 -1
- package/dist/core/ConfigurationProvider.js +20 -24
- package/dist/core/InstallationService.d.ts +17 -0
- package/dist/core/InstallationService.js +127 -61
- package/dist/core/MCPManager.d.ts +1 -0
- package/dist/core/MCPManager.js +3 -0
- package/dist/core/RequirementService.d.ts +4 -4
- package/dist/core/RequirementService.js +11 -7
- package/dist/core/ServerSchemaProvider.d.ts +1 -1
- package/dist/core/ServerSchemaProvider.js +15 -10
- package/dist/core/constants.d.ts +3 -0
- package/dist/core/constants.js +4 -1
- package/dist/core/installers/clients/ClientInstaller.js +58 -40
- package/dist/core/installers/requirements/PipInstaller.js +10 -5
- package/dist/core/onboard/FeedOnboardService.d.ts +35 -0
- package/dist/core/onboard/FeedOnboardService.js +137 -0
- package/dist/core/types.d.ts +6 -1
- package/dist/core/validators/FeedValidator.d.ts +13 -0
- package/dist/core/validators/FeedValidator.js +27 -0
- package/dist/services/ServerService.d.ts +5 -0
- package/dist/services/ServerService.js +15 -0
- package/dist/utils/githubAuth.js +0 -10
- package/dist/utils/githubUtils.d.ts +16 -0
- package/dist/utils/githubUtils.js +55 -39
- package/dist/web/contract/serverContract.d.ts +64 -0
- package/dist/web/contract/serverContract.js +2 -0
- package/dist/web/public/css/detailsWidget.css +157 -32
- package/dist/web/public/css/onboard.css +44 -0
- package/dist/web/public/css/serverDetails.css +35 -19
- package/dist/web/public/index.html +16 -10
- package/dist/web/public/js/detailsWidget.js +43 -40
- package/dist/web/public/js/modal/index.js +58 -0
- package/dist/web/public/js/modal/installHandler.js +227 -0
- package/dist/web/public/js/modal/installModal.js +163 -0
- package/dist/web/public/js/modal/installation.js +281 -0
- package/dist/web/public/js/modal/loadingModal.js +52 -0
- package/dist/web/public/js/modal/loadingUI.js +74 -0
- package/dist/web/public/js/modal/messageQueue.js +112 -0
- package/dist/web/public/js/modal/modalSetup.js +512 -0
- package/dist/web/public/js/modal/modalUI.js +214 -0
- package/dist/web/public/js/modal/modalUtils.js +49 -0
- package/dist/web/public/js/modal/version.js +20 -0
- package/dist/web/public/js/modal/versionUtils.js +20 -0
- package/dist/web/public/js/modal.js +25 -1041
- package/dist/web/public/js/onboard/formProcessor.js +309 -0
- package/dist/web/public/js/onboard/index.js +131 -0
- package/dist/web/public/js/onboard/state.js +32 -0
- package/dist/web/public/js/onboard/templates.js +375 -0
- package/dist/web/public/js/onboard/uiHandlers.js +196 -0
- package/dist/web/public/js/serverCategoryDetails.js +211 -123
- package/dist/web/public/onboard.html +150 -0
- package/dist/web/server.js +25 -0
- package/package.json +3 -4
- package/src/core/ConfigurationProvider.ts +37 -29
- package/src/core/InstallationService.ts +176 -62
- package/src/core/MCPManager.ts +4 -0
- package/src/core/RequirementService.ts +12 -8
- package/src/core/ServerSchemaLoader.ts +48 -0
- package/src/core/ServerSchemaProvider.ts +137 -0
- package/src/core/constants.ts +4 -1
- package/src/core/installers/clients/ClientInstaller.ts +66 -49
- package/src/core/installers/requirements/PipInstaller.ts +10 -5
- package/src/core/types.ts +6 -1
- package/src/services/ServerService.ts +15 -0
- package/src/utils/githubAuth.ts +14 -27
- package/src/utils/githubUtils.ts +84 -47
- package/src/web/public/css/detailsWidget.css +235 -0
- package/src/web/public/css/serverDetails.css +126 -0
- package/src/web/public/index.html +16 -10
- package/src/web/public/js/detailsWidget.js +264 -0
- package/src/web/public/js/modal/index.js +58 -0
- package/src/web/public/js/modal/installModal.js +163 -0
- package/src/web/public/js/modal/installation.js +281 -0
- package/src/web/public/js/modal/loadingModal.js +52 -0
- package/src/web/public/js/modal/messageQueue.js +112 -0
- package/src/web/public/js/modal/modalSetup.js +512 -0
- package/src/web/public/js/modal/modalUtils.js +49 -0
- package/src/web/public/js/modal/versionUtils.js +20 -0
- package/src/web/public/js/modal.js +25 -1041
- package/src/web/public/js/serverCategoryDetails.js +211 -123
- package/src/web/server.ts +31 -0
package/src/core/constants.ts
CHANGED
|
@@ -10,7 +10,9 @@ import path from 'path';
|
|
|
10
10
|
*/
|
|
11
11
|
export const GITHUB_REPO = {
|
|
12
12
|
url: 'https://github.com/ai-microsoft/imcp-feed.git',
|
|
13
|
-
|
|
13
|
+
repoName: 'ai-microsoft/imcp-feed',
|
|
14
|
+
feedsPath: 'feeds',
|
|
15
|
+
feedAssetsName: 'imcp-feeds-${latest}.zip',
|
|
14
16
|
};
|
|
15
17
|
|
|
16
18
|
/**
|
|
@@ -29,6 +31,7 @@ export const SETTINGS_DIR = (() => {
|
|
|
29
31
|
* Local feeds directory path
|
|
30
32
|
*/
|
|
31
33
|
export const LOCAL_FEEDS_DIR = path.join(SETTINGS_DIR, 'feeds');
|
|
34
|
+
export const LOCAL_FEEDS_SCHEMA_DIR = path.join(LOCAL_FEEDS_DIR, 'schemas');
|
|
32
35
|
|
|
33
36
|
const CODE_STRORAGE_DIR = (() => {
|
|
34
37
|
switch (process.platform) {
|
|
@@ -373,6 +373,7 @@ export class ClientInstaller {
|
|
|
373
373
|
|
|
374
374
|
// Clone the base installation configuration to avoid modifying the original serverConfig
|
|
375
375
|
const installConfig = JSON.parse(JSON.stringify(serverConfig.installation));
|
|
376
|
+
installConfig.mode = serverConfig.mode
|
|
376
377
|
const pythonEnv = options.settings?.pythonEnv;
|
|
377
378
|
let pythonDir: string | null = null;
|
|
378
379
|
|
|
@@ -606,46 +607,54 @@ export class ClientInstaller {
|
|
|
606
607
|
settings.mcpServers = {};
|
|
607
608
|
}
|
|
608
609
|
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
serverConfigForClient.env
|
|
610
|
+
if (installConfig.mode === 'stdio') {
|
|
611
|
+
// Special handling for Windows when command is npx for Cline and MSROO clients
|
|
612
|
+
// Use a copy to avoid modifying the passed installConfig directly if needed elsewhere
|
|
613
|
+
const serverConfigForClient = { ...installConfig };
|
|
614
|
+
if (process.platform === 'win32' &&
|
|
615
|
+
serverConfigForClient.command === 'npx' &&
|
|
616
|
+
(clientName === 'Cline' || clientName === 'MSRooCode' || clientName === 'MSROO')) {
|
|
617
|
+
// Update command to cmd
|
|
618
|
+
serverConfigForClient.command = 'cmd';
|
|
619
|
+
|
|
620
|
+
// Add /c and npx at the beginning of args
|
|
621
|
+
serverConfigForClient.args = ['/c', 'npx', ...serverConfigForClient.args];
|
|
622
|
+
|
|
623
|
+
// Add APPDATA environment variable pointing to npm directory
|
|
624
|
+
if (!serverConfigForClient.env) {
|
|
625
|
+
serverConfigForClient.env = {};
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
// Dynamically get npm path and set APPDATA to it
|
|
629
|
+
const npmPath = await this.getNpmPath();
|
|
630
|
+
serverConfigForClient.env['APPDATA'] = npmPath;
|
|
631
|
+
Logger.debug(`Windows npx fix: command=${serverConfigForClient.command}, args=${serverConfigForClient.args.join(' ')}, env=${JSON.stringify(serverConfigForClient.env)}`);
|
|
632
|
+
}
|
|
633
|
+
// Convert backslashes to forward slashes in args paths
|
|
634
|
+
if (serverConfigForClient.args) {
|
|
635
|
+
serverConfigForClient.args = serverConfigForClient.args.map((arg: string) =>
|
|
636
|
+
typeof arg === 'string' ? arg.replace(/\\/g, '/') : arg
|
|
637
|
+
);
|
|
624
638
|
}
|
|
625
639
|
|
|
626
|
-
//
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
);
|
|
640
|
+
// Add or update the server configuration
|
|
641
|
+
settings.mcpServers[serverName] = {
|
|
642
|
+
command: serverConfigForClient.command,
|
|
643
|
+
args: serverConfigForClient.args,
|
|
644
|
+
env: serverConfigForClient.env,
|
|
645
|
+
autoApprove: [],
|
|
646
|
+
disabled: false,
|
|
647
|
+
alwaysAllow: []
|
|
648
|
+
};
|
|
649
|
+
Logger.debug(`Updating ${settingPath} for ${serverName}: ${JSON.stringify(settings.mcpServers[serverName])}`);
|
|
650
|
+
} else if (installConfig.mode === 'sse') {
|
|
651
|
+
// Handle SSE mode for Cline and MSRoo clients
|
|
652
|
+
settings.mcpServers[serverName] = {
|
|
653
|
+
type: 'sse',
|
|
654
|
+
url: installConfig.url
|
|
655
|
+
};
|
|
636
656
|
}
|
|
637
657
|
|
|
638
|
-
// Add or update the server configuration
|
|
639
|
-
settings.mcpServers[serverName] = {
|
|
640
|
-
command: serverConfigForClient.command,
|
|
641
|
-
args: serverConfigForClient.args,
|
|
642
|
-
env: serverConfigForClient.env,
|
|
643
|
-
autoApprove: [],
|
|
644
|
-
disabled: false,
|
|
645
|
-
alwaysAllow: []
|
|
646
|
-
};
|
|
647
|
-
Logger.debug(`Updating ${settingPath} for ${serverName}: ${JSON.stringify(settings.mcpServers[serverName])}`);
|
|
648
|
-
|
|
649
658
|
// Write the updated settings back to the file
|
|
650
659
|
await writeJsonFile(settingPath, settings);
|
|
651
660
|
}
|
|
@@ -673,20 +682,28 @@ export class ClientInstaller {
|
|
|
673
682
|
// Use a copy to avoid modifying the passed installConfig directly
|
|
674
683
|
const serverConfigForClient = { ...installConfig };
|
|
675
684
|
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
685
|
+
if (installConfig.mode === 'stdio') {
|
|
686
|
+
if (serverConfigForClient.args) {
|
|
687
|
+
serverConfigForClient.args = serverConfigForClient.args.map((arg: string) =>
|
|
688
|
+
typeof arg === 'string' ? arg.replace(/\\/g, '/') : arg
|
|
689
|
+
);
|
|
690
|
+
}
|
|
682
691
|
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
692
|
+
// Add or update the server configuration
|
|
693
|
+
settings.mcp.servers[serverName] = {
|
|
694
|
+
command: serverConfigForClient.command,
|
|
695
|
+
args: serverConfigForClient.args,
|
|
696
|
+
env: serverConfigForClient.env
|
|
697
|
+
};
|
|
698
|
+
Logger.debug(`Updating ${settingPath} for ${serverName}: ${JSON.stringify(settings.mcp.servers[serverName])}`);
|
|
699
|
+
}
|
|
700
|
+
else if (installConfig.mode === 'sse') {
|
|
701
|
+
// Handle SSE mode for Github Copilot
|
|
702
|
+
settings.mcp.servers[serverName] = {
|
|
703
|
+
type: 'sse',
|
|
704
|
+
url: installConfig.url
|
|
705
|
+
};
|
|
706
|
+
}
|
|
690
707
|
|
|
691
708
|
// Write the updated settings back to the file
|
|
692
709
|
await writeJsonFile(settingPath, settings);
|
|
@@ -25,7 +25,7 @@ export class PipInstaller extends BaseInstaller {
|
|
|
25
25
|
|
|
26
26
|
supportCheckUpdates(): boolean {
|
|
27
27
|
/// temporarily disabling update check for pip as not able to get which pip of python is being used
|
|
28
|
-
return
|
|
28
|
+
return true;
|
|
29
29
|
}
|
|
30
30
|
/**
|
|
31
31
|
* Check if the Python package is already installed
|
|
@@ -47,7 +47,8 @@ export class PipInstaller extends BaseInstaller {
|
|
|
47
47
|
type: 'pip',
|
|
48
48
|
installed,
|
|
49
49
|
version: installedVersion,
|
|
50
|
-
inProgress: false
|
|
50
|
+
inProgress: false,
|
|
51
|
+
pythonEnv: this.getPythonCommand(options) // Preserve pythonEnv in status check too
|
|
51
52
|
};
|
|
52
53
|
} catch (error) {
|
|
53
54
|
return {
|
|
@@ -55,7 +56,8 @@ export class PipInstaller extends BaseInstaller {
|
|
|
55
56
|
type: 'pip',
|
|
56
57
|
installed: false,
|
|
57
58
|
error: error instanceof Error ? error.message : String(error),
|
|
58
|
-
inProgress: false
|
|
59
|
+
inProgress: false,
|
|
60
|
+
pythonEnv: this.getPythonCommand(options) // Preserve pythonEnv even in error case
|
|
59
61
|
};
|
|
60
62
|
}
|
|
61
63
|
}
|
|
@@ -117,12 +119,14 @@ export class PipInstaller extends BaseInstaller {
|
|
|
117
119
|
}
|
|
118
120
|
}
|
|
119
121
|
|
|
122
|
+
// Store the pythonEnv in the status for future use
|
|
120
123
|
return {
|
|
121
124
|
name: requirement.name,
|
|
122
125
|
type: 'pip',
|
|
123
126
|
installed: true,
|
|
124
127
|
version: requirement.version,
|
|
125
|
-
inProgress: false
|
|
128
|
+
inProgress: false,
|
|
129
|
+
pythonEnv: this.getPythonCommand(options) // Store the python env
|
|
126
130
|
};
|
|
127
131
|
} catch (error) {
|
|
128
132
|
return {
|
|
@@ -130,7 +134,8 @@ export class PipInstaller extends BaseInstaller {
|
|
|
130
134
|
type: 'pip',
|
|
131
135
|
installed: false,
|
|
132
136
|
error: error instanceof Error ? error.message : String(error),
|
|
133
|
-
inProgress: false
|
|
137
|
+
inProgress: false,
|
|
138
|
+
pythonEnv: this.getPythonCommand(options) // Store the python env
|
|
134
139
|
};
|
|
135
140
|
}
|
|
136
141
|
}
|
package/src/core/types.ts
CHANGED
|
@@ -17,6 +17,7 @@ export interface RequirementStatus {
|
|
|
17
17
|
};
|
|
18
18
|
lastCheckTime?: string;
|
|
19
19
|
operationStatus?: OperationStatus;
|
|
20
|
+
pythonEnv?: string; // Store Python environment path for pip requirements
|
|
20
21
|
}
|
|
21
22
|
|
|
22
23
|
export interface MCPServerStatus {
|
|
@@ -98,6 +99,7 @@ export interface InstallationConfig {
|
|
|
98
99
|
command: string;
|
|
99
100
|
args: string[]; // Can use ${packageSource} as template variable for installation path
|
|
100
101
|
env?: Record<string, EnvVariableConfig>; // Note the colon in property name
|
|
102
|
+
url?: string; // URL for sse mode
|
|
101
103
|
}
|
|
102
104
|
|
|
103
105
|
export interface DependencyConfig {
|
|
@@ -114,8 +116,10 @@ export interface DependencyConfig {
|
|
|
114
116
|
export interface McpConfig {
|
|
115
117
|
name: string;
|
|
116
118
|
description: string;
|
|
117
|
-
mode: 'stdio' | '
|
|
119
|
+
mode: 'stdio' | 'sse'; // Currently only stdio mode is supported in ai-coder-tools
|
|
118
120
|
dependencies?: DependencyConfig;
|
|
121
|
+
schemas?: string; // Path to the schema file
|
|
122
|
+
repository?: string; // Repository URL for the server
|
|
119
123
|
installation: InstallationConfig;
|
|
120
124
|
}
|
|
121
125
|
|
|
@@ -147,6 +151,7 @@ export interface FeedConfiguration {
|
|
|
147
151
|
name: string;
|
|
148
152
|
displayName: string;
|
|
149
153
|
description: string;
|
|
154
|
+
repository?: string;
|
|
150
155
|
requirements: RequirementConfig[];
|
|
151
156
|
mcpServers: McpConfig[];
|
|
152
157
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import path from 'path';
|
|
2
2
|
import { fileURLToPath } from 'url';
|
|
3
3
|
import { Logger } from '../utils/logger.js';
|
|
4
|
+
import { getServerSchemaProvider, ServerSchema } from '../core/ServerSchemaProvider.js';
|
|
4
5
|
import {
|
|
5
6
|
MCPServerCategory,
|
|
6
7
|
ServerInstallOptions,
|
|
@@ -123,6 +124,20 @@ export class ServerService {
|
|
|
123
124
|
}
|
|
124
125
|
}
|
|
125
126
|
|
|
127
|
+
/**
|
|
128
|
+
* Gets the schema for a specific server in a category
|
|
129
|
+
*/
|
|
130
|
+
async getServerSchema(categoryName: string, serverName: string): Promise<ServerSchema | undefined> {
|
|
131
|
+
try {
|
|
132
|
+
const provider = await getServerSchemaProvider();
|
|
133
|
+
const serverMcpConfig = await mcpManager.getServerMcpConfig(categoryName, serverName);
|
|
134
|
+
return await provider.getSchema(categoryName, serverMcpConfig?.schemas || `${serverName}.json`);
|
|
135
|
+
} catch (error) {
|
|
136
|
+
Logger.error(`Failed to get schema for server ${serverName} in category ${categoryName}:`, error);
|
|
137
|
+
throw error;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
126
141
|
/**
|
|
127
142
|
* Installs a specific mcp tool for a server.
|
|
128
143
|
* TODO: This might require enhancing MCPManager to handle category-specific installs.
|
package/src/utils/githubAuth.ts
CHANGED
|
@@ -9,7 +9,7 @@ const execAsync = util.promisify(exec);
|
|
|
9
9
|
const spawnAsync = (command: string, args: string[], options: any = {}) => {
|
|
10
10
|
return new Promise((resolve, reject) => {
|
|
11
11
|
const childProcess = spawn(command, args, options);
|
|
12
|
-
|
|
12
|
+
|
|
13
13
|
childProcess.on('close', (code) => {
|
|
14
14
|
if (code === 0) {
|
|
15
15
|
resolve(code);
|
|
@@ -17,7 +17,7 @@ const spawnAsync = (command: string, args: string[], options: any = {}) => {
|
|
|
17
17
|
reject(new Error(`Process exited with code ${code}`));
|
|
18
18
|
}
|
|
19
19
|
});
|
|
20
|
-
|
|
20
|
+
|
|
21
21
|
childProcess.on('error', (err) => {
|
|
22
22
|
reject(err);
|
|
23
23
|
});
|
|
@@ -35,29 +35,16 @@ export async function checkGithubAuth(): Promise<void> {
|
|
|
35
35
|
Logger.debug('Starting GitHub authentication check');
|
|
36
36
|
|
|
37
37
|
try {
|
|
38
|
-
// Check if git is installed
|
|
39
|
-
if (!await isToolInstalled('git')) {
|
|
40
|
-
Logger.log('Installing required Git...');
|
|
41
|
-
await installCLI('git');
|
|
42
|
-
|
|
43
|
-
// Verify git was installed correctly, with retry mechanism
|
|
44
|
-
if (!await isToolInstalled('git')) {
|
|
45
|
-
throw new Error('Failed to install Git. Please install it manually and try again.');
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
Logger.debug('Git installed successfully and verified');
|
|
49
|
-
}
|
|
50
|
-
|
|
51
38
|
// Check if gh CLI is installed
|
|
52
39
|
if (!await isToolInstalled('gh')) {
|
|
53
40
|
Logger.log('Installing required GitHub CLI...');
|
|
54
41
|
await installCLI('gh');
|
|
55
|
-
|
|
42
|
+
|
|
56
43
|
// Verify gh CLI was installed correctly, with retry mechanism
|
|
57
44
|
if (!await isToolInstalled('gh')) {
|
|
58
45
|
throw new Error('Failed to install GitHub CLI. Please install it manually and try again.');
|
|
59
46
|
}
|
|
60
|
-
|
|
47
|
+
|
|
61
48
|
Logger.debug('GitHub CLI installed successfully and verified');
|
|
62
49
|
}
|
|
63
50
|
} catch (error) {
|
|
@@ -70,12 +57,12 @@ export async function checkGithubAuth(): Promise<void> {
|
|
|
70
57
|
// Check if user is authenticated
|
|
71
58
|
const { stdout: viewerData } = await execAsync('gh api user');
|
|
72
59
|
const viewer = JSON.parse(viewerData);
|
|
73
|
-
|
|
60
|
+
|
|
74
61
|
Logger.debug({
|
|
75
62
|
action: 'github_auth_check',
|
|
76
63
|
username: viewer.login
|
|
77
64
|
});
|
|
78
|
-
|
|
65
|
+
|
|
79
66
|
// Check if user is using company account (ends with _microsoft)
|
|
80
67
|
if (!viewer.login.toLowerCase().endsWith('_microsoft')) {
|
|
81
68
|
const error = 'Error: You must be logged in with a Microsoft account (username should end with _microsoft). ' +
|
|
@@ -92,34 +79,34 @@ export async function checkGithubAuth(): Promise<void> {
|
|
|
92
79
|
if (error instanceof GithubAuthError) {
|
|
93
80
|
throw error;
|
|
94
81
|
}
|
|
95
|
-
|
|
82
|
+
|
|
96
83
|
// If the error is due to not being authenticated
|
|
97
84
|
const errorMessage = (error as any)?.stderr || (error as Error).message;
|
|
98
85
|
if (errorMessage.includes('please run: gh auth login') || errorMessage.includes('GH_TOKEN')) {
|
|
99
86
|
Logger.log('GitHub authentication required at the first run. Please login account end with _microsoft.');
|
|
100
|
-
|
|
87
|
+
|
|
101
88
|
try {
|
|
102
89
|
// Use spawnAsync for interactive authentication
|
|
103
90
|
await spawnAsync('gh', ['auth', 'login', '--web', '--hostname', 'github.com', '--git-protocol', 'https'], {
|
|
104
91
|
stdio: 'inherit' // User sees & interacts directly with the process
|
|
105
92
|
});
|
|
106
|
-
|
|
93
|
+
|
|
107
94
|
Logger.debug('GitHub authentication process completed');
|
|
108
|
-
|
|
95
|
+
|
|
109
96
|
// Verify the authentication was successful
|
|
110
97
|
const { stdout: viewerData } = await execAsync('gh api user');
|
|
111
98
|
const viewer = JSON.parse(viewerData);
|
|
112
|
-
|
|
99
|
+
|
|
113
100
|
// Check if user is using company account (ends with _microsoft)
|
|
114
101
|
if (!viewer.login.toLowerCase().endsWith('_microsoft')) {
|
|
115
102
|
throw new GithubAuthError('You must be logged in with a Microsoft account (username should end with _microsoft).');
|
|
116
103
|
}
|
|
117
|
-
|
|
104
|
+
|
|
118
105
|
Logger.debug(`Successfully authenticated as ${viewer.login}`);
|
|
119
106
|
return; // Auth successful, continue execution
|
|
120
107
|
} catch (loginError) {
|
|
121
108
|
Logger.error('Error during GitHub authentication process', loginError);
|
|
122
|
-
|
|
109
|
+
|
|
123
110
|
// If the interactive login failed, provide manual instructions
|
|
124
111
|
const authInstructions =
|
|
125
112
|
'\nError: GitHub authentication required. Please follow these steps:\n\n' +
|
|
@@ -129,7 +116,7 @@ export async function checkGithubAuth(): Promise<void> {
|
|
|
129
116
|
'3. Follow the prompts to login with your Microsoft account (username must end with _microsoft)\n' +
|
|
130
117
|
'4. Authorize ai-microsoft organization.\n' +
|
|
131
118
|
'5. After successful login, run imcp command again.\n\n';
|
|
132
|
-
|
|
119
|
+
|
|
133
120
|
Logger.log(authInstructions);
|
|
134
121
|
throw new GithubAuthError('GitHub authentication required. Please login first and try again.');
|
|
135
122
|
}
|
package/src/utils/githubUtils.ts
CHANGED
|
@@ -9,95 +9,132 @@ import { SETTINGS_DIR } from '../core/constants.js';
|
|
|
9
9
|
|
|
10
10
|
const execAsync = util.promisify(exec);
|
|
11
11
|
|
|
12
|
+
interface DownloadGithubReleaseResult {
|
|
13
|
+
version: string;
|
|
14
|
+
downloadPath: string;
|
|
15
|
+
}
|
|
16
|
+
|
|
12
17
|
/**
|
|
13
|
-
*
|
|
14
|
-
* @param
|
|
15
|
-
* @param
|
|
16
|
-
* @
|
|
18
|
+
* Downloads a GitHub release asset
|
|
19
|
+
* @param repo GitHub repository in format owner/repo
|
|
20
|
+
* @param version Version to download, can be "latest"
|
|
21
|
+
* @param assetsName Assets name pattern (optional, but either assetsName or assetName must be provided)
|
|
22
|
+
* @param assetName Asset name pattern (optional, but either assetsName or assetName must be provided)
|
|
23
|
+
* @param isFolder Whether to treat the downloaded asset as a folder (default: false)
|
|
24
|
+
* @param targetDirectory Target directory for downloads (default: SETTINGS_DIR/downloads)
|
|
25
|
+
* @returns Object containing version and download path
|
|
17
26
|
*/
|
|
18
|
-
export async function
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
27
|
+
export async function downloadGithubRelease(
|
|
28
|
+
repo: string,
|
|
29
|
+
version: string,
|
|
30
|
+
assetsName?: string,
|
|
31
|
+
assetName?: string,
|
|
32
|
+
isFolder: boolean = false,
|
|
33
|
+
targetDirectory?: string
|
|
34
|
+
): Promise<DownloadGithubReleaseResult> {
|
|
35
|
+
if (!repo) {
|
|
36
|
+
throw new Error('GitHub repository is required');
|
|
24
37
|
}
|
|
25
38
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
const { repository, assetsName, assetName } = registry;
|
|
29
|
-
|
|
30
|
-
if (!repository) {
|
|
31
|
-
throw new Error('GitHub repository is required for GitHub release downloads');
|
|
39
|
+
if (!assetsName && !assetName) {
|
|
40
|
+
throw new Error('Either assetsName or assetName must be specified');
|
|
32
41
|
}
|
|
33
42
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
let resolvedAssetsName = assetsName || '';
|
|
43
|
+
const downloadsDir = targetDirectory || path.join(SETTINGS_DIR, 'downloads');
|
|
44
|
+
await fs.mkdir(downloadsDir, { recursive: true });
|
|
37
45
|
|
|
38
|
-
|
|
46
|
+
// Get latest version if needed
|
|
47
|
+
const { stdout } = await execAsync(`gh release view --repo ${repo} --json tagName --jq .tagName`);
|
|
39
48
|
const latestTag = stdout.trim();
|
|
40
49
|
let latestVersion = latestTag;
|
|
41
50
|
|
|
42
51
|
const tagWithVPrefix = latestVersion.startsWith('v');
|
|
43
52
|
if (tagWithVPrefix) latestVersion = latestVersion.substring(1); // Remove 'v' prefix if present
|
|
44
|
-
|
|
53
|
+
const resolvedVersion = version.includes("latest") ? latestVersion : version;
|
|
54
|
+
|
|
55
|
+
// Resolve asset names
|
|
56
|
+
let resolvedAssetsName = '';
|
|
57
|
+
let resolvedAssetName = '';
|
|
45
58
|
if (assetsName) {
|
|
46
|
-
resolvedAssetsName = assetsName.replace('${latest}',
|
|
59
|
+
resolvedAssetsName = assetsName.replace('${latest}', resolvedVersion).replace('${version}', resolvedVersion);
|
|
47
60
|
}
|
|
48
61
|
if (assetName) {
|
|
49
|
-
resolvedAssetName = assetName.replace('${latest}',
|
|
62
|
+
resolvedAssetName = assetName.replace('${latest}', resolvedVersion).replace('${version}', resolvedVersion);
|
|
50
63
|
}
|
|
51
64
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
if (!pattern) {
|
|
58
|
-
throw new Error('Either assetsName or assetName must be specified for GitHub release downloads');
|
|
65
|
+
// Validate zip requirement for isFolder
|
|
66
|
+
const pattern = resolvedAssetsName || resolvedAssetName;
|
|
67
|
+
if (isFolder && (!resolvedAssetsName || !resolvedAssetsName.endsWith('.zip'))) {
|
|
68
|
+
throw new Error('When isFolder is true, assetsName must be provided and end with .zip');
|
|
59
69
|
}
|
|
60
70
|
|
|
61
71
|
// Download the release asset
|
|
62
72
|
const downloadPath = path.join(downloadsDir, path.basename(pattern));
|
|
63
73
|
if (!await fileExists(downloadPath)) {
|
|
64
|
-
await execAsync(`gh release download ${tagWithVPrefix ? `v${
|
|
74
|
+
await execAsync(`gh release download ${tagWithVPrefix ? `v${resolvedVersion}` : resolvedVersion} --repo ${repo} --pattern "${pattern}" -O "${downloadPath}"`);
|
|
65
75
|
}
|
|
66
76
|
|
|
67
|
-
// Handle zip
|
|
68
|
-
if (downloadPath.endsWith('.zip')) {
|
|
77
|
+
// Handle zip extraction if needed
|
|
78
|
+
if (isFolder && downloadPath.endsWith('.zip')) {
|
|
69
79
|
const extractDir = path.join(downloadsDir, path.basename(pattern, '.zip'));
|
|
70
80
|
await fs.mkdir(extractDir, { recursive: true });
|
|
71
|
-
|
|
72
|
-
// Extract the zip file
|
|
73
81
|
await extractZipFile(downloadPath, { dir: extractDir });
|
|
74
|
-
|
|
82
|
+
|
|
75
83
|
// If resolvedAssetName is specified, look for it in the extracted directory
|
|
76
84
|
if (resolvedAssetName) {
|
|
77
|
-
assetPath = path.join(extractDir, resolvedAssetName);
|
|
85
|
+
const assetPath = path.join(extractDir, resolvedAssetName);
|
|
78
86
|
try {
|
|
79
87
|
await fs.access(assetPath);
|
|
80
|
-
return {
|
|
88
|
+
return { version: resolvedVersion, downloadPath: assetPath };
|
|
81
89
|
} catch (error) {
|
|
82
90
|
throw new Error(`Asset ${resolvedAssetName} not found in extracted directory ${extractDir}`);
|
|
83
91
|
}
|
|
84
92
|
}
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
93
|
+
return { version: resolvedVersion, downloadPath: extractDir };
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
return { version: resolvedVersion, downloadPath };
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Helper to handle GitHub release downloads
|
|
101
|
+
* @param requirement The requirement configuration
|
|
102
|
+
* @param registry The GitHub release registry configuration
|
|
103
|
+
* @returns The path to the downloaded file
|
|
104
|
+
*/
|
|
105
|
+
export async function handleGitHubRelease(
|
|
106
|
+
requirement: RequirementConfig,
|
|
107
|
+
registry: RegistryConfig['githubRelease']
|
|
108
|
+
): Promise<{ resolvedVersion: string; resolvedPath: string }> {
|
|
109
|
+
if (!registry) {
|
|
110
|
+
throw new Error('GitHub release registry configuration is required');
|
|
111
|
+
}
|
|
88
112
|
|
|
89
|
-
|
|
90
|
-
|
|
113
|
+
const { repository, assetsName, assetName } = registry;
|
|
114
|
+
if (!repository) {
|
|
115
|
+
throw new Error('GitHub repository is required for GitHub release downloads');
|
|
91
116
|
}
|
|
92
117
|
|
|
93
|
-
|
|
118
|
+
const isZipAsset = assetsName?.endsWith('.zip') || false;
|
|
119
|
+
const result = await downloadGithubRelease(
|
|
120
|
+
repository,
|
|
121
|
+
requirement.version,
|
|
122
|
+
assetsName,
|
|
123
|
+
assetName,
|
|
124
|
+
isZipAsset
|
|
125
|
+
);
|
|
126
|
+
|
|
127
|
+
return {
|
|
128
|
+
resolvedVersion: result.version,
|
|
129
|
+
resolvedPath: result.downloadPath
|
|
130
|
+
};
|
|
94
131
|
}
|
|
95
132
|
|
|
96
133
|
async function fileExists(filePath: string): Promise<boolean> {
|
|
97
134
|
try {
|
|
98
|
-
await fs.access(filePath)
|
|
99
|
-
return true
|
|
135
|
+
await fs.access(filePath);
|
|
136
|
+
return true;
|
|
100
137
|
} catch {
|
|
101
|
-
return false
|
|
138
|
+
return false;
|
|
102
139
|
}
|
|
103
140
|
}
|