imcp 0.0.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 (124) hide show
  1. package/.github/ISSUE_TEMPLATE/JitAccess.yml +28 -0
  2. package/.github/acl/access.yml +20 -0
  3. package/.github/compliance/inventory.yml +5 -0
  4. package/.github/policies/jit.yml +19 -0
  5. package/README.md +137 -0
  6. package/dist/cli/commands/install.d.ts +2 -0
  7. package/dist/cli/commands/install.js +105 -0
  8. package/dist/cli/commands/list.d.ts +2 -0
  9. package/dist/cli/commands/list.js +90 -0
  10. package/dist/cli/commands/pull.d.ts +2 -0
  11. package/dist/cli/commands/pull.js +17 -0
  12. package/dist/cli/commands/serve.d.ts +2 -0
  13. package/dist/cli/commands/serve.js +32 -0
  14. package/dist/cli/commands/start.d.ts +2 -0
  15. package/dist/cli/commands/start.js +32 -0
  16. package/dist/cli/commands/sync.d.ts +2 -0
  17. package/dist/cli/commands/sync.js +17 -0
  18. package/dist/cli/commands/uninstall.d.ts +2 -0
  19. package/dist/cli/commands/uninstall.js +39 -0
  20. package/dist/cli/index.d.ts +2 -0
  21. package/dist/cli/index.js +114 -0
  22. package/dist/core/ConfigurationProvider.d.ts +31 -0
  23. package/dist/core/ConfigurationProvider.js +416 -0
  24. package/dist/core/InstallationService.d.ts +17 -0
  25. package/dist/core/InstallationService.js +144 -0
  26. package/dist/core/MCPManager.d.ts +17 -0
  27. package/dist/core/MCPManager.js +98 -0
  28. package/dist/core/RequirementService.d.ts +45 -0
  29. package/dist/core/RequirementService.js +123 -0
  30. package/dist/core/constants.d.ts +29 -0
  31. package/dist/core/constants.js +55 -0
  32. package/dist/core/installers/BaseInstaller.d.ts +73 -0
  33. package/dist/core/installers/BaseInstaller.js +247 -0
  34. package/dist/core/installers/ClientInstaller.d.ts +17 -0
  35. package/dist/core/installers/ClientInstaller.js +307 -0
  36. package/dist/core/installers/CommandInstaller.d.ts +36 -0
  37. package/dist/core/installers/CommandInstaller.js +170 -0
  38. package/dist/core/installers/GeneralInstaller.d.ts +32 -0
  39. package/dist/core/installers/GeneralInstaller.js +87 -0
  40. package/dist/core/installers/InstallerFactory.d.ts +52 -0
  41. package/dist/core/installers/InstallerFactory.js +95 -0
  42. package/dist/core/installers/NpmInstaller.d.ts +25 -0
  43. package/dist/core/installers/NpmInstaller.js +123 -0
  44. package/dist/core/installers/PipInstaller.d.ts +25 -0
  45. package/dist/core/installers/PipInstaller.js +114 -0
  46. package/dist/core/installers/RequirementInstaller.d.ts +32 -0
  47. package/dist/core/installers/RequirementInstaller.js +3 -0
  48. package/dist/core/installers/index.d.ts +6 -0
  49. package/dist/core/installers/index.js +7 -0
  50. package/dist/core/types.d.ts +152 -0
  51. package/dist/core/types.js +16 -0
  52. package/dist/index.d.ts +11 -0
  53. package/dist/index.js +19 -0
  54. package/dist/services/InstallRequestValidator.d.ts +21 -0
  55. package/dist/services/InstallRequestValidator.js +99 -0
  56. package/dist/services/ServerService.d.ts +47 -0
  57. package/dist/services/ServerService.js +145 -0
  58. package/dist/utils/UpdateCheckTracker.d.ts +39 -0
  59. package/dist/utils/UpdateCheckTracker.js +80 -0
  60. package/dist/utils/clientUtils.d.ts +29 -0
  61. package/dist/utils/clientUtils.js +105 -0
  62. package/dist/utils/feedUtils.d.ts +5 -0
  63. package/dist/utils/feedUtils.js +29 -0
  64. package/dist/utils/githubAuth.d.ts +1 -0
  65. package/dist/utils/githubAuth.js +123 -0
  66. package/dist/utils/logger.d.ts +14 -0
  67. package/dist/utils/logger.js +90 -0
  68. package/dist/utils/osUtils.d.ts +16 -0
  69. package/dist/utils/osUtils.js +235 -0
  70. package/dist/web/public/css/modal.css +250 -0
  71. package/dist/web/public/css/notifications.css +70 -0
  72. package/dist/web/public/index.html +157 -0
  73. package/dist/web/public/js/api.js +213 -0
  74. package/dist/web/public/js/modal.js +572 -0
  75. package/dist/web/public/js/notifications.js +99 -0
  76. package/dist/web/public/js/serverCategoryDetails.js +210 -0
  77. package/dist/web/public/js/serverCategoryList.js +82 -0
  78. package/dist/web/public/modal.html +61 -0
  79. package/dist/web/public/styles.css +155 -0
  80. package/dist/web/server.d.ts +5 -0
  81. package/dist/web/server.js +150 -0
  82. package/package.json +53 -0
  83. package/src/cli/commands/install.ts +140 -0
  84. package/src/cli/commands/list.ts +112 -0
  85. package/src/cli/commands/pull.ts +16 -0
  86. package/src/cli/commands/serve.ts +37 -0
  87. package/src/cli/commands/uninstall.ts +54 -0
  88. package/src/cli/index.ts +127 -0
  89. package/src/core/ConfigurationProvider.ts +489 -0
  90. package/src/core/InstallationService.ts +173 -0
  91. package/src/core/MCPManager.ts +134 -0
  92. package/src/core/RequirementService.ts +147 -0
  93. package/src/core/constants.ts +61 -0
  94. package/src/core/installers/BaseInstaller.ts +292 -0
  95. package/src/core/installers/ClientInstaller.ts +423 -0
  96. package/src/core/installers/CommandInstaller.ts +185 -0
  97. package/src/core/installers/GeneralInstaller.ts +89 -0
  98. package/src/core/installers/InstallerFactory.ts +109 -0
  99. package/src/core/installers/NpmInstaller.ts +128 -0
  100. package/src/core/installers/PipInstaller.ts +121 -0
  101. package/src/core/installers/RequirementInstaller.ts +38 -0
  102. package/src/core/installers/index.ts +9 -0
  103. package/src/core/types.ts +163 -0
  104. package/src/index.ts +44 -0
  105. package/src/services/InstallRequestValidator.ts +112 -0
  106. package/src/services/ServerService.ts +181 -0
  107. package/src/utils/UpdateCheckTracker.ts +86 -0
  108. package/src/utils/clientUtils.ts +112 -0
  109. package/src/utils/feedUtils.ts +31 -0
  110. package/src/utils/githubAuth.ts +142 -0
  111. package/src/utils/logger.ts +101 -0
  112. package/src/utils/osUtils.ts +250 -0
  113. package/src/web/public/css/modal.css +250 -0
  114. package/src/web/public/css/notifications.css +70 -0
  115. package/src/web/public/index.html +157 -0
  116. package/src/web/public/js/api.js +213 -0
  117. package/src/web/public/js/modal.js +572 -0
  118. package/src/web/public/js/notifications.js +99 -0
  119. package/src/web/public/js/serverCategoryDetails.js +210 -0
  120. package/src/web/public/js/serverCategoryList.js +82 -0
  121. package/src/web/public/modal.html +61 -0
  122. package/src/web/public/styles.css +155 -0
  123. package/src/web/server.ts +195 -0
  124. package/tsconfig.json +18 -0
@@ -0,0 +1,145 @@
1
+ import path from 'path';
2
+ import { fileURLToPath } from 'url';
3
+ import { Logger } from '../utils/logger.js';
4
+ import { mcpManager } from '../core/MCPManager.js';
5
+ import { updateCheckTracker } from '../utils/UpdateCheckTracker.js';
6
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
7
+ /**
8
+ * ServerService provides a unified interface for server management operations.
9
+ * This layer handles business logic that's shared between the CLI and web interface.
10
+ */
11
+ export class ServerService {
12
+ /**
13
+ * Lists available MCP servers based on the provided options
14
+ */
15
+ async listServerCategories(options = {}) {
16
+ return mcpManager.listServerCategories(options);
17
+ }
18
+ /**
19
+ * Gets a server by name
20
+ */
21
+ async getServerCategory(categoryName) {
22
+ const serverCategories = await this.listServerCategories();
23
+ const serverCategory = serverCategories.find(s => s.name === categoryName);
24
+ // Start async check for requirement updates if one isn't already in progress
25
+ if (serverCategory && serverCategory.feedConfiguration && serverCategory.name) {
26
+ // Check if update is already in progress using the tracker
27
+ const shouldCheckForUpdates = await updateCheckTracker.startOperation(serverCategory.name);
28
+ if (shouldCheckForUpdates) {
29
+ this.checkRequirementsForUpdate(serverCategory).catch(error => {
30
+ // Ensure we mark the operation as complete on error
31
+ if (serverCategory.name) {
32
+ updateCheckTracker.endOperation(serverCategory.name)
33
+ .catch(lockError => console.error(`Failed to mark update check as complete: ${lockError.message}`));
34
+ }
35
+ Logger.error(`Error checking requirements for updates: ${error.message}`);
36
+ });
37
+ }
38
+ else {
39
+ Logger.debug(`Update check already in progress for ${serverCategory.name}, skipping`);
40
+ }
41
+ }
42
+ return serverCategory;
43
+ }
44
+ /**
45
+ * Check for updates to requirements for a server category
46
+ * @param serverCategory The server category to check
47
+ * @private
48
+ */
49
+ async checkRequirementsForUpdate(serverCategory) {
50
+ if (!serverCategory.name || !serverCategory.feedConfiguration?.requirements?.length) {
51
+ return;
52
+ }
53
+ try {
54
+ const { requirementService } = await import('../core/RequirementService.js');
55
+ const { configProvider } = await import('../core/ConfigurationProvider.js');
56
+ for (const requirement of serverCategory.feedConfiguration.requirements) {
57
+ if (requirement.version.includes('latest')) {
58
+ // Get current status if available
59
+ const currentStatus = serverCategory.installationStatus?.requirementsStatus[requirement.name];
60
+ if (!currentStatus)
61
+ continue;
62
+ // Check for updates
63
+ const updatedStatus = await requirementService.checkRequirementForUpdates(requirement);
64
+ // If update information is found, update the configuration
65
+ if (updatedStatus.availableUpdate && serverCategory.name) {
66
+ await configProvider.updateRequirementStatus(serverCategory.name, requirement.name, updatedStatus);
67
+ // Also update the in-memory status for immediate use
68
+ if (serverCategory.installationStatus?.requirementsStatus) {
69
+ serverCategory.installationStatus.requirementsStatus[requirement.name] = updatedStatus;
70
+ }
71
+ }
72
+ }
73
+ }
74
+ }
75
+ finally {
76
+ // Always mark the operation as complete when done, even if there was an error
77
+ if (serverCategory.name) {
78
+ await updateCheckTracker.endOperation(serverCategory.name)
79
+ .catch(error => console.error(`Failed to mark update check as complete: ${error.message}`));
80
+ }
81
+ }
82
+ }
83
+ /**
84
+ * Installs a specific mcp tool for a server.
85
+ * TODO: This might require enhancing MCPManager to handle category-specific installs.
86
+ */
87
+ async installMcpServer(category, serverName, options = {} // Reuse ServerInstallOptions for env etc.
88
+ ) {
89
+ Logger.debug(`Installing MCP server: ${JSON.stringify({ category, serverName, options })}`);
90
+ try {
91
+ const result = await mcpManager.installServer(category, serverName, options);
92
+ Logger.debug(`Installation result: ${JSON.stringify(result)}`);
93
+ return result;
94
+ }
95
+ catch (error) {
96
+ Logger.error(`Failed to install MCP server: ${serverName}`, error);
97
+ throw error;
98
+ }
99
+ }
100
+ /**
101
+ * Installs a specific mcp tool for a server.
102
+ * TODO: This might require enhancing MCPManager to handle category-specific installs.
103
+ */
104
+ async uninstallMcpServer(category, serverName, options = {} // Reuse ServerInstallOptions for env etc.
105
+ ) {
106
+ return mcpManager.uninstallServer(category, serverName, options);
107
+ }
108
+ /**
109
+ * Validates server names
110
+ */
111
+ async validateServerName(category, name) {
112
+ Logger.debug(`Validating server name: ${JSON.stringify({ category, name })}`);
113
+ // Check if category exists in feeds
114
+ const feedConfig = await mcpManager.getFeedConfiguration(category);
115
+ if (!feedConfig) {
116
+ Logger.debug(`Validation failed: Category "${category}" not found in feeds`);
117
+ return false;
118
+ }
119
+ // Check if server exists in the category's mcpServers
120
+ const serverExists = feedConfig.mcpServers.some(server => server.name === name);
121
+ if (!serverExists) {
122
+ Logger.debug(`Validation failed: Server "${name}" not found in category "${category}"`);
123
+ return false;
124
+ }
125
+ return true;
126
+ }
127
+ /**
128
+ * Formats success/error messages for operations
129
+ */
130
+ formatOperationResults(results) {
131
+ return {
132
+ success: results.every(r => r.success),
133
+ messages: results.map(r => r.message).filter((m) => m !== undefined)
134
+ };
135
+ }
136
+ /**
137
+ * Syncs MCP server configurations from remote feed source
138
+ */
139
+ async syncFeeds() {
140
+ return mcpManager.syncFeeds();
141
+ }
142
+ }
143
+ // Export a singleton instance
144
+ export const serverService = new ServerService();
145
+ //# sourceMappingURL=ServerService.js.map
@@ -0,0 +1,39 @@
1
+ /**
2
+ * Utility class to track update check operations in progress
3
+ * Provides thread-safe access to check status with proper locking
4
+ */
5
+ export declare class UpdateCheckTracker {
6
+ private static instance;
7
+ private operationsInProgress;
8
+ private operationsLock;
9
+ private constructor();
10
+ /**
11
+ * Get the singleton instance of the tracker
12
+ */
13
+ static getInstance(): UpdateCheckTracker;
14
+ /**
15
+ * Execute an operation with a lock to ensure thread safety
16
+ * @param operation The operation to execute
17
+ * @returns The result of the operation
18
+ * @private
19
+ */
20
+ private withLock;
21
+ /**
22
+ * Check if an operation can be started and mark it as in progress if so
23
+ * @param key The key to identify the operation
24
+ * @returns True if the operation can be started, false if already in progress
25
+ */
26
+ startOperation(key: string): Promise<boolean>;
27
+ /**
28
+ * Mark an operation as complete
29
+ * @param key The key to identify the operation
30
+ */
31
+ endOperation(key: string): Promise<void>;
32
+ /**
33
+ * Check if an operation is in progress
34
+ * @param key The key to identify the operation
35
+ * @returns True if the operation is in progress
36
+ */
37
+ isOperationInProgress(key: string): Promise<boolean>;
38
+ }
39
+ export declare const updateCheckTracker: UpdateCheckTracker;
@@ -0,0 +1,80 @@
1
+ /**
2
+ * Utility class to track update check operations in progress
3
+ * Provides thread-safe access to check status with proper locking
4
+ */
5
+ export class UpdateCheckTracker {
6
+ static instance;
7
+ // Map to track operations in progress (key -> inProgress)
8
+ operationsInProgress = new Map();
9
+ // Lock for thread safety when checking/updating the in-progress map
10
+ operationsLock = Promise.resolve();
11
+ constructor() { }
12
+ /**
13
+ * Get the singleton instance of the tracker
14
+ */
15
+ static getInstance() {
16
+ if (!UpdateCheckTracker.instance) {
17
+ UpdateCheckTracker.instance = new UpdateCheckTracker();
18
+ }
19
+ return UpdateCheckTracker.instance;
20
+ }
21
+ /**
22
+ * Execute an operation with a lock to ensure thread safety
23
+ * @param operation The operation to execute
24
+ * @returns The result of the operation
25
+ * @private
26
+ */
27
+ async withLock(operation) {
28
+ const current = this.operationsLock;
29
+ let resolve;
30
+ this.operationsLock = new Promise(r => resolve = r);
31
+ try {
32
+ await current;
33
+ return await operation();
34
+ }
35
+ finally {
36
+ resolve();
37
+ }
38
+ }
39
+ /**
40
+ * Check if an operation can be started and mark it as in progress if so
41
+ * @param key The key to identify the operation
42
+ * @returns True if the operation can be started, false if already in progress
43
+ */
44
+ async startOperation(key) {
45
+ return await this.withLock(async () => {
46
+ if (!this.operationsInProgress.get(key)) {
47
+ this.operationsInProgress.set(key, true);
48
+ return true;
49
+ }
50
+ return false;
51
+ });
52
+ }
53
+ /**
54
+ * Mark an operation as complete
55
+ * @param key The key to identify the operation
56
+ */
57
+ async endOperation(key) {
58
+ try {
59
+ await this.withLock(async () => {
60
+ this.operationsInProgress.set(key, false);
61
+ });
62
+ }
63
+ catch (error) {
64
+ console.error(`Failed to end operation for key: ${key}`, error);
65
+ }
66
+ }
67
+ /**
68
+ * Check if an operation is in progress
69
+ * @param key The key to identify the operation
70
+ * @returns True if the operation is in progress
71
+ */
72
+ async isOperationInProgress(key) {
73
+ return await this.withLock(async () => {
74
+ return !!this.operationsInProgress.get(key);
75
+ });
76
+ }
77
+ }
78
+ // Export a singleton instance
79
+ export const updateCheckTracker = UpdateCheckTracker.getInstance();
80
+ //# sourceMappingURL=UpdateCheckTracker.js.map
@@ -0,0 +1,29 @@
1
+ /**
2
+ * Extract a zip file to a directory
3
+ * @param zipPath The path to the zip file
4
+ * @param options Extraction options
5
+ * @returns A promise that resolves when extraction is complete
6
+ */
7
+ export declare function extractZipFile(zipPath: string, options: {
8
+ dir: string;
9
+ }): Promise<void>;
10
+ /**
11
+ * Resolves the path to an NPM module, replacing template variables
12
+ * First checks if the module path exists under NVM-controlled Node, then falls back to global npm
13
+ * @param pathString The path string potentially containing template variables
14
+ * @returns The resolved path
15
+ */
16
+ export declare function resolveNpmModulePath(pathString: string): string;
17
+ /**
18
+ * Reads a JSON file and parses its content
19
+ * @param filePath Path to the JSON file
20
+ * @param createIfNotExist Whether to create the file if it doesn't exist
21
+ * @returns The parsed JSON content
22
+ */
23
+ export declare function readJsonFile(filePath: string, createIfNotExist?: boolean): Promise<any>;
24
+ /**
25
+ * Writes content to a JSON file
26
+ * @param filePath Path to the JSON file
27
+ * @param content Content to write to the file
28
+ */
29
+ export declare function writeJsonFile(filePath: string, content: any): Promise<void>;
@@ -0,0 +1,105 @@
1
+ import fs from 'fs/promises';
2
+ import * as fsSync from 'fs';
3
+ import * as path from 'path';
4
+ import { execSync } from 'child_process';
5
+ import extractZipModule from 'extract-zip';
6
+ import { Logger } from './logger.js';
7
+ /**
8
+ * Extract a zip file to a directory
9
+ * @param zipPath The path to the zip file
10
+ * @param options Extraction options
11
+ * @returns A promise that resolves when extraction is complete
12
+ */
13
+ export async function extractZipFile(zipPath, options) {
14
+ try {
15
+ // Try to use extract-zip if available
16
+ if (typeof extractZipModule === 'function') {
17
+ return await extractZipModule(zipPath, options);
18
+ }
19
+ // Fallback to Node.js built-in unzipper
20
+ const fs = require('fs');
21
+ const unzipper = require('unzipper');
22
+ await fs.promises.mkdir(options.dir, { recursive: true });
23
+ return new Promise((resolve, reject) => {
24
+ fs.createReadStream(zipPath)
25
+ .pipe(unzipper.Extract({ path: options.dir }))
26
+ .on('close', resolve)
27
+ .on('error', reject);
28
+ });
29
+ }
30
+ catch (error) {
31
+ console.error(`Error extracting zip: ${error instanceof Error ? error.message : String(error)}`);
32
+ throw new Error(`Failed to extract zip file ${zipPath}: ${error instanceof Error ? error.message : 'Unknown error'}`);
33
+ }
34
+ }
35
+ /**
36
+ * Resolves the path to an NPM module, replacing template variables
37
+ * First checks if the module path exists under NVM-controlled Node, then falls back to global npm
38
+ * @param pathString The path string potentially containing template variables
39
+ * @returns The resolved path
40
+ */
41
+ export function resolveNpmModulePath(pathString) {
42
+ // If the path doesn't contain the ${NPMPATH} template, return it as is
43
+ if (!pathString.includes('${NPMPATH}')) {
44
+ return pathString;
45
+ }
46
+ // First try to get NVM-controlled npm path
47
+ const nvmHome = process.env.NVM_HOME;
48
+ if (nvmHome) {
49
+ try {
50
+ // Get current node version
51
+ const nodeVersion = execSync('node -v').toString().trim();
52
+ // Construct the path to npm in the NVM directory
53
+ const nvmNodePath = path.join(nvmHome, nodeVersion);
54
+ const resolvedPath = pathString.replace('${NPMPATH}', nvmNodePath);
55
+ // Check if this path exists
56
+ try {
57
+ fsSync.accessSync(resolvedPath);
58
+ return resolvedPath;
59
+ }
60
+ catch (error) {
61
+ Logger.debug(`NVM controlled path doesn't exist: ${resolvedPath}, will try global npm`);
62
+ // Path doesn't exist, will fall back to global npm
63
+ }
64
+ }
65
+ catch (error) {
66
+ Logger.debug(`Error determining Node version: ${error}, will use global npm`);
67
+ // Error getting node version, will fall back to global npm
68
+ }
69
+ }
70
+ // Fall back to global npm path
71
+ const globalNpmPath = execSync('npm root -g').toString().trim();
72
+ return pathString.replace('${NPMPATH}', globalNpmPath);
73
+ }
74
+ /**
75
+ * Reads a JSON file and parses its content
76
+ * @param filePath Path to the JSON file
77
+ * @param createIfNotExist Whether to create the file if it doesn't exist
78
+ * @returns The parsed JSON content
79
+ */
80
+ export async function readJsonFile(filePath, createIfNotExist = false) {
81
+ try {
82
+ // Ensure directory exists
83
+ if (createIfNotExist) {
84
+ await fs.mkdir(path.dirname(filePath), { recursive: true });
85
+ }
86
+ const content = await fs.readFile(filePath, 'utf8');
87
+ return JSON.parse(content);
88
+ }
89
+ catch (error) {
90
+ if (error.code === 'ENOENT' && createIfNotExist) {
91
+ return {};
92
+ }
93
+ throw error;
94
+ }
95
+ }
96
+ /**
97
+ * Writes content to a JSON file
98
+ * @param filePath Path to the JSON file
99
+ * @param content Content to write to the file
100
+ */
101
+ export async function writeJsonFile(filePath, content) {
102
+ await fs.mkdir(path.dirname(filePath), { recursive: true });
103
+ await fs.writeFile(filePath, JSON.stringify(content, null, 2), 'utf8');
104
+ }
105
+ //# sourceMappingURL=clientUtils.js.map
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Checks if local feeds exist in the LOCAL_FEEDS_DIR
3
+ * Returns true if the directory exists and contains at least one .json file
4
+ */
5
+ export declare function hasLocalFeeds(): Promise<boolean>;
@@ -0,0 +1,29 @@
1
+ import fs from 'fs/promises';
2
+ import { Logger } from './logger.js';
3
+ import { LOCAL_FEEDS_DIR } from '../core/constants.js';
4
+ /**
5
+ * Checks if local feeds exist in the LOCAL_FEEDS_DIR
6
+ * Returns true if the directory exists and contains at least one .json file
7
+ */
8
+ export async function hasLocalFeeds() {
9
+ try {
10
+ Logger.debug('Checking for local feeds existence');
11
+ const feedsExist = await fs.access(LOCAL_FEEDS_DIR)
12
+ .then(() => true)
13
+ .catch(() => false);
14
+ if (!feedsExist) {
15
+ Logger.debug('Local feeds directory does not exist');
16
+ return false;
17
+ }
18
+ // Check if directory contains any json files
19
+ const files = await fs.readdir(LOCAL_FEEDS_DIR);
20
+ const hasJsonFiles = files.some(file => file.endsWith('.json'));
21
+ Logger.debug(`Local feeds directory ${hasJsonFiles ? 'contains' : 'does not contain'} JSON files`);
22
+ return hasJsonFiles;
23
+ }
24
+ catch (error) {
25
+ Logger.error('Error checking local feeds:', error);
26
+ return false;
27
+ }
28
+ }
29
+ //# sourceMappingURL=feedUtils.js.map
@@ -0,0 +1 @@
1
+ export declare function checkGithubAuth(): Promise<void>;
@@ -0,0 +1,123 @@
1
+ import { isToolInstalled, installCLI } from './osUtils.js';
2
+ import { exec, spawn } from 'child_process';
3
+ import util from 'util';
4
+ import { Logger } from './logger.js';
5
+ const execAsync = util.promisify(exec);
6
+ // Create a promisified version of spawn that returns a Promise
7
+ const spawnAsync = (command, args, options = {}) => {
8
+ return new Promise((resolve, reject) => {
9
+ const childProcess = spawn(command, args, options);
10
+ childProcess.on('close', (code) => {
11
+ if (code === 0) {
12
+ resolve(code);
13
+ }
14
+ else {
15
+ reject(new Error(`Process exited with code ${code}`));
16
+ }
17
+ });
18
+ childProcess.on('error', (err) => {
19
+ reject(err);
20
+ });
21
+ });
22
+ };
23
+ class GithubAuthError extends Error {
24
+ constructor(message) {
25
+ super(message);
26
+ this.name = 'GithubAuthError';
27
+ }
28
+ }
29
+ export async function checkGithubAuth() {
30
+ Logger.debug('Starting GitHub authentication check');
31
+ try {
32
+ // Check if git is installed
33
+ if (!await isToolInstalled('git')) {
34
+ Logger.log('Installing required Git...');
35
+ await installCLI('git');
36
+ // Verify git was installed correctly, with retry mechanism
37
+ if (!await isToolInstalled('git')) {
38
+ throw new Error('Failed to install Git. Please install it manually and try again.');
39
+ }
40
+ Logger.debug('Git installed successfully and verified');
41
+ }
42
+ // Check if gh CLI is installed
43
+ if (!await isToolInstalled('gh')) {
44
+ Logger.log('Installing required GitHub CLI...');
45
+ await installCLI('gh');
46
+ // Verify gh CLI was installed correctly, with retry mechanism
47
+ if (!await isToolInstalled('gh')) {
48
+ throw new Error('Failed to install GitHub CLI. Please install it manually and try again.');
49
+ }
50
+ Logger.debug('GitHub CLI installed successfully and verified');
51
+ }
52
+ }
53
+ catch (error) {
54
+ Logger.error('Error during tool installation:', error);
55
+ throw new Error(`Tool installation failed: ${error.message}`);
56
+ }
57
+ try {
58
+ Logger.debug('Checking GitHub authentication status');
59
+ // Check if user is authenticated
60
+ const { stdout: viewerData } = await execAsync('gh api user');
61
+ const viewer = JSON.parse(viewerData);
62
+ Logger.debug({
63
+ action: 'github_auth_check',
64
+ username: viewer.login
65
+ });
66
+ // Check if user is using company account (ends with _microsoft)
67
+ if (!viewer.login.toLowerCase().endsWith('_microsoft')) {
68
+ const error = 'Error: You must be logged in with a Microsoft account (username should end with _microsoft). ' +
69
+ 'Please run "gh auth logout" and then "gh auth login" with your Microsoft account. Current username: ' +
70
+ viewer.login;
71
+ Logger.error(error, {
72
+ username: viewer.login
73
+ });
74
+ throw new GithubAuthError(error);
75
+ }
76
+ Logger.debug('GitHub authentication verified successfully with Microsoft account');
77
+ }
78
+ catch (error) {
79
+ if (error instanceof GithubAuthError) {
80
+ throw error;
81
+ }
82
+ // If the error is due to not being authenticated
83
+ const errorMessage = error?.stderr || error.message;
84
+ if (errorMessage.includes('please run: gh auth login') || errorMessage.includes('GH_TOKEN')) {
85
+ Logger.log('GitHub authentication required at the first run. Please login account end with _microsoft.');
86
+ try {
87
+ // Use spawnAsync for interactive authentication
88
+ await spawnAsync('gh', ['auth', 'login', '--web', '--hostname', 'github.com', '--git-protocol', 'https'], {
89
+ stdio: 'inherit' // User sees & interacts directly with the process
90
+ });
91
+ Logger.debug('GitHub authentication process completed');
92
+ // Verify the authentication was successful
93
+ const { stdout: viewerData } = await execAsync('gh api user');
94
+ const viewer = JSON.parse(viewerData);
95
+ // Check if user is using company account (ends with _microsoft)
96
+ if (!viewer.login.toLowerCase().endsWith('_microsoft')) {
97
+ throw new GithubAuthError('You must be logged in with a Microsoft account (username should end with _microsoft).');
98
+ }
99
+ Logger.debug(`Successfully authenticated as ${viewer.login}`);
100
+ return; // Auth successful, continue execution
101
+ }
102
+ catch (loginError) {
103
+ Logger.error('Error during GitHub authentication process', loginError);
104
+ // If the interactive login failed, provide manual instructions
105
+ const authInstructions = '\nError: GitHub authentication required. Please follow these steps:\n\n' +
106
+ '1. Run this command:\n' +
107
+ ' gh auth login --web --hostname github.com --git-protocol https\n' +
108
+ '2. Choose Y when prompted authenticating Git with your GitHub credentials.\n' +
109
+ '3. Follow the prompts to login with your Microsoft account (username must end with _microsoft)\n' +
110
+ '4. Authorize ai-microsoft organization.\n' +
111
+ '5. After successful login, run imcp command again.\n\n';
112
+ Logger.log(authInstructions);
113
+ throw new GithubAuthError('GitHub authentication required. Please login first and try again.');
114
+ }
115
+ }
116
+ else {
117
+ const errorMessage = `Failed to verify GitHub authentication: ${error.message}`;
118
+ Logger.error(errorMessage, error);
119
+ throw new GithubAuthError(errorMessage);
120
+ }
121
+ }
122
+ }
123
+ //# sourceMappingURL=githubAuth.js.map
@@ -0,0 +1,14 @@
1
+ export declare class Logger {
2
+ private static verbose;
3
+ private static fileLoggingEnabled;
4
+ private static logsDir;
5
+ static setVerbose(isVerbose: boolean): void;
6
+ static setFileLogging(enabled: boolean): void;
7
+ private static ensureLogsDirExists;
8
+ private static getLogFileName;
9
+ private static getTimestamp;
10
+ private static writeToLogFile;
11
+ static log(message: string): Promise<void>;
12
+ static debug(message: string | object): Promise<void>;
13
+ static error(message: string, error?: unknown): Promise<void>;
14
+ }
@@ -0,0 +1,90 @@
1
+ import fs from 'fs';
2
+ import path from 'path';
3
+ import { SETTINGS_DIR } from '../core/constants.js';
4
+ export class Logger {
5
+ static verbose = false;
6
+ static fileLoggingEnabled = true;
7
+ static logsDir = path.join(SETTINGS_DIR, 'logs');
8
+ static setVerbose(isVerbose) {
9
+ this.verbose = isVerbose;
10
+ }
11
+ static setFileLogging(enabled) {
12
+ this.fileLoggingEnabled = enabled;
13
+ }
14
+ static async ensureLogsDirExists() {
15
+ try {
16
+ await fs.promises.mkdir(this.logsDir, { recursive: true });
17
+ }
18
+ catch (error) {
19
+ console.error(`Failed to create logs directory: ${error instanceof Error ? error.message : String(error)}`);
20
+ // Disable file logging if directory creation fails
21
+ this.fileLoggingEnabled = false;
22
+ }
23
+ }
24
+ static getLogFileName() {
25
+ const now = new Date();
26
+ const year = now.getFullYear();
27
+ const month = String(now.getMonth() + 1).padStart(2, '0');
28
+ const day = String(now.getDate()).padStart(2, '0');
29
+ return `${year}-${month}-${day}.log`;
30
+ }
31
+ static getTimestamp() {
32
+ return new Date().toISOString();
33
+ }
34
+ static async writeToLogFile(level, content) {
35
+ if (!this.fileLoggingEnabled)
36
+ return;
37
+ try {
38
+ await this.ensureLogsDirExists();
39
+ const logFile = path.join(this.logsDir, this.getLogFileName());
40
+ const logEntry = `[${this.getTimestamp()}] [${level}] ${content}\n`;
41
+ await fs.promises.appendFile(logFile, logEntry, { encoding: 'utf8' });
42
+ }
43
+ catch (error) {
44
+ console.error(`Failed to write to log file: ${error instanceof Error ? error.message : String(error)}`);
45
+ }
46
+ }
47
+ static async log(message) {
48
+ console.log(message);
49
+ await this.writeToLogFile('INFO', message);
50
+ }
51
+ static async debug(message) {
52
+ let formattedMessage;
53
+ if (typeof message === 'object') {
54
+ formattedMessage = JSON.stringify(message, null, 2);
55
+ if (this.verbose) {
56
+ console.log(formattedMessage);
57
+ }
58
+ }
59
+ else {
60
+ formattedMessage = message;
61
+ if (this.verbose) {
62
+ console.log(message);
63
+ }
64
+ }
65
+ if (this.verbose) {
66
+ await this.writeToLogFile('DEBUG', formattedMessage);
67
+ }
68
+ }
69
+ static async error(message, error) {
70
+ console.error(message);
71
+ let logMessage = message;
72
+ if (this.verbose && error) {
73
+ if (error instanceof Error) {
74
+ const errorDetails = {
75
+ name: error.name,
76
+ message: error.message,
77
+ stack: error.stack
78
+ };
79
+ console.error('Error details:', errorDetails);
80
+ logMessage += `\nError details: ${JSON.stringify(errorDetails, null, 2)}`;
81
+ }
82
+ else {
83
+ console.error('Error details:', error);
84
+ logMessage += `\nError details: ${JSON.stringify(error, null, 2)}`;
85
+ }
86
+ }
87
+ await this.writeToLogFile('ERROR', logMessage);
88
+ }
89
+ }
90
+ //# sourceMappingURL=logger.js.map