imcp 0.0.14 → 0.0.16

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 (136) hide show
  1. package/dist/core/ConfigurationProvider.d.ts +1 -0
  2. package/dist/core/ConfigurationProvider.js +15 -0
  3. package/dist/core/InstallationService.js +2 -7
  4. package/dist/core/MCPManager.d.ts +11 -2
  5. package/dist/core/MCPManager.js +24 -1
  6. package/dist/core/RequirementService.js +2 -8
  7. package/dist/core/installers/clients/BaseClientInstaller.d.ts +51 -0
  8. package/dist/core/installers/clients/BaseClientInstaller.js +160 -0
  9. package/dist/core/installers/clients/ClientInstaller.d.ts +16 -9
  10. package/dist/core/installers/clients/ClientInstaller.js +80 -527
  11. package/dist/core/installers/clients/ClientInstallerFactory.d.ts +20 -0
  12. package/dist/core/installers/clients/ClientInstallerFactory.js +37 -0
  13. package/dist/core/installers/clients/ClineInstaller.d.ts +18 -0
  14. package/dist/core/installers/clients/ClineInstaller.js +124 -0
  15. package/dist/core/installers/clients/GithubCopilotInstaller.d.ts +34 -0
  16. package/dist/core/installers/clients/GithubCopilotInstaller.js +162 -0
  17. package/dist/core/installers/clients/MSRooCodeInstaller.d.ts +15 -0
  18. package/dist/core/installers/clients/MSRooCodeInstaller.js +122 -0
  19. package/dist/core/installers/requirements/BaseInstaller.d.ts +11 -34
  20. package/dist/core/installers/requirements/BaseInstaller.js +5 -116
  21. package/dist/core/installers/requirements/CommandInstaller.d.ts +6 -1
  22. package/dist/core/installers/requirements/CommandInstaller.js +7 -0
  23. package/dist/core/installers/requirements/GeneralInstaller.d.ts +6 -1
  24. package/dist/core/installers/requirements/GeneralInstaller.js +9 -4
  25. package/dist/core/installers/requirements/NpmInstaller.d.ts +46 -7
  26. package/dist/core/installers/requirements/NpmInstaller.js +150 -58
  27. package/dist/core/installers/requirements/PipInstaller.d.ts +9 -0
  28. package/dist/core/installers/requirements/PipInstaller.js +66 -28
  29. package/dist/core/onboard/FeedOnboardService.d.ts +50 -13
  30. package/dist/core/onboard/FeedOnboardService.js +263 -88
  31. package/dist/core/onboard/OnboardProcessor.d.ts +79 -0
  32. package/dist/core/onboard/OnboardProcessor.js +290 -0
  33. package/dist/core/onboard/OnboardStatus.d.ts +49 -0
  34. package/dist/core/onboard/OnboardStatus.js +10 -0
  35. package/dist/core/onboard/OnboardStatusManager.d.ts +57 -0
  36. package/dist/core/onboard/OnboardStatusManager.js +176 -0
  37. package/dist/core/types.d.ts +4 -5
  38. package/dist/core/validators/FeedValidator.d.ts +8 -1
  39. package/dist/core/validators/FeedValidator.js +60 -7
  40. package/dist/core/validators/IServerValidator.d.ts +19 -0
  41. package/dist/core/validators/IServerValidator.js +2 -0
  42. package/dist/core/validators/SSEServerValidator.d.ts +15 -0
  43. package/dist/core/validators/SSEServerValidator.js +39 -0
  44. package/dist/core/validators/ServerValidatorFactory.d.ts +24 -0
  45. package/dist/core/validators/ServerValidatorFactory.js +45 -0
  46. package/dist/core/validators/StdioServerValidator.d.ts +46 -0
  47. package/dist/core/validators/StdioServerValidator.js +229 -0
  48. package/dist/services/InstallRequestValidator.d.ts +1 -1
  49. package/dist/services/ServerService.d.ts +9 -6
  50. package/dist/services/ServerService.js +18 -7
  51. package/dist/utils/adoUtils.d.ts +29 -0
  52. package/dist/utils/adoUtils.js +252 -0
  53. package/dist/utils/clientUtils.d.ts +0 -7
  54. package/dist/utils/clientUtils.js +0 -42
  55. package/dist/utils/githubUtils.d.ts +10 -0
  56. package/dist/utils/githubUtils.js +22 -0
  57. package/dist/utils/macroExpressionUtils.d.ts +38 -0
  58. package/dist/utils/macroExpressionUtils.js +116 -0
  59. package/dist/utils/osUtils.d.ts +4 -20
  60. package/dist/utils/osUtils.js +78 -23
  61. package/dist/web/contract/serverContract.d.ts +3 -1
  62. package/dist/web/public/css/notifications.css +48 -17
  63. package/dist/web/public/css/onboard.css +66 -3
  64. package/dist/web/public/index.html +84 -16
  65. package/dist/web/public/js/api.js +3 -6
  66. package/dist/web/public/js/flights/flights.js +127 -0
  67. package/dist/web/public/js/modal/installation.js +5 -5
  68. package/dist/web/public/js/modal/modalSetup.js +3 -2
  69. package/dist/web/public/js/notifications.js +66 -27
  70. package/dist/web/public/js/onboard/ONBOARDING_PAGE_DESIGN.md +338 -0
  71. package/dist/web/public/js/onboard/formProcessor.js +810 -255
  72. package/dist/web/public/js/onboard/index.js +328 -85
  73. package/dist/web/public/js/onboard/publishHandler.js +132 -0
  74. package/dist/web/public/js/onboard/state.js +61 -17
  75. package/dist/web/public/js/onboard/templates.js +217 -249
  76. package/dist/web/public/js/onboard/uiHandlers.js +679 -117
  77. package/dist/web/public/js/onboard/validationHandlers.js +378 -0
  78. package/dist/web/public/js/serverCategoryList.js +15 -2
  79. package/dist/web/public/onboard.html +191 -45
  80. package/dist/web/public/styles.css +91 -1
  81. package/dist/web/server.d.ts +0 -10
  82. package/dist/web/server.js +131 -22
  83. package/package.json +2 -2
  84. package/src/core/ConfigurationProvider.ts +15 -0
  85. package/src/core/InstallationService.ts +2 -7
  86. package/src/core/MCPManager.ts +26 -1
  87. package/src/core/RequirementService.ts +2 -9
  88. package/src/core/installers/clients/BaseClientInstaller.ts +196 -0
  89. package/src/core/installers/clients/ClientInstaller.ts +97 -608
  90. package/src/core/installers/clients/ClientInstallerFactory.ts +43 -0
  91. package/src/core/installers/clients/ClineInstaller.ts +135 -0
  92. package/src/core/installers/clients/GithubCopilotInstaller.ts +179 -0
  93. package/src/core/installers/clients/MSRooCodeInstaller.ts +133 -0
  94. package/src/core/installers/requirements/BaseInstaller.ts +13 -136
  95. package/src/core/installers/requirements/CommandInstaller.ts +9 -1
  96. package/src/core/installers/requirements/GeneralInstaller.ts +11 -4
  97. package/src/core/installers/requirements/NpmInstaller.ts +178 -61
  98. package/src/core/installers/requirements/PipInstaller.ts +68 -29
  99. package/src/core/onboard/FeedOnboardService.ts +346 -0
  100. package/src/core/onboard/OnboardProcessor.ts +305 -0
  101. package/src/core/onboard/OnboardStatus.ts +55 -0
  102. package/src/core/onboard/OnboardStatusManager.ts +188 -0
  103. package/src/core/types.ts +4 -5
  104. package/src/core/validators/FeedValidator.ts +79 -0
  105. package/src/core/validators/IServerValidator.ts +21 -0
  106. package/src/core/validators/SSEServerValidator.ts +43 -0
  107. package/src/core/validators/ServerValidatorFactory.ts +51 -0
  108. package/src/core/validators/StdioServerValidator.ts +259 -0
  109. package/src/services/InstallRequestValidator.ts +1 -1
  110. package/src/services/ServerService.ts +22 -7
  111. package/src/utils/adoUtils.ts +291 -0
  112. package/src/utils/clientUtils.ts +0 -44
  113. package/src/utils/githubUtils.ts +24 -0
  114. package/src/utils/macroExpressionUtils.ts +121 -0
  115. package/src/utils/osUtils.ts +89 -24
  116. package/src/web/contract/serverContract.ts +74 -0
  117. package/src/web/public/css/notifications.css +48 -17
  118. package/src/web/public/css/onboard.css +107 -0
  119. package/src/web/public/index.html +84 -16
  120. package/src/web/public/js/api.js +3 -6
  121. package/src/web/public/js/flights/flights.js +127 -0
  122. package/src/web/public/js/modal/installation.js +5 -5
  123. package/src/web/public/js/modal/modalSetup.js +3 -2
  124. package/src/web/public/js/notifications.js +66 -27
  125. package/src/web/public/js/onboard/ONBOARDING_PAGE_DESIGN.md +338 -0
  126. package/src/web/public/js/onboard/formProcessor.js +864 -0
  127. package/src/web/public/js/onboard/index.js +374 -0
  128. package/src/web/public/js/onboard/publishHandler.js +132 -0
  129. package/src/web/public/js/onboard/state.js +76 -0
  130. package/src/web/public/js/onboard/templates.js +343 -0
  131. package/src/web/public/js/onboard/uiHandlers.js +758 -0
  132. package/src/web/public/js/onboard/validationHandlers.js +378 -0
  133. package/src/web/public/js/serverCategoryList.js +15 -2
  134. package/src/web/public/onboard.html +296 -0
  135. package/src/web/public/styles.css +91 -1
  136. package/src/web/server.ts +167 -58
@@ -0,0 +1,290 @@
1
+ import { exec } from 'child_process';
2
+ import { promisify } from 'util';
3
+ import fs from 'fs/promises';
4
+ import path from 'path';
5
+ import { GITHUB_REPO, SETTINGS_DIR } from '../constants.js';
6
+ import { Logger } from '../../utils/logger.js';
7
+ const execAsync = promisify(exec);
8
+ /**
9
+ * Class responsible for handling GitHub operations during the onboarding process
10
+ */
11
+ export class OnboardProcessor {
12
+ /**
13
+ * Forks the GitHub repository specified in GITHUB_REPO constants.
14
+ * Uses the `gh repo fork` command.
15
+ * @param onboardingId The ID of the onboarding process (for logging).
16
+ * @param repoDir The directory of the repository (currently unused but kept for consistency).
17
+ * @throws An error if the forking process fails, with a 'step' property set to 'forkRepo'.
18
+ */
19
+ async forkRepo(onboardingId, repoDir) {
20
+ try {
21
+ // Using --remote=true ensures that if the fork already exists, 'origin' remote is set to it.
22
+ // If the fork doesn't exist, it creates it and sets 'origin' remote.
23
+ await execAsync(`gh repo fork ${GITHUB_REPO.repoName} --clone=false --remote=true`);
24
+ Logger.debug(`[${onboardingId}] Successfully ensured repository is forked and remote 'origin' is set.`);
25
+ }
26
+ catch (error) {
27
+ const errorMessage = error instanceof Error ? error.message : String(error);
28
+ Logger.error(`[${onboardingId}] Error during forkRepo: ${errorMessage}`);
29
+ throw Object.assign(new Error(`Failed to fork repository: ${errorMessage}`), { step: 'forkRepo' });
30
+ }
31
+ }
32
+ /**
33
+ * Clones the user's fork of the repository into a temporary directory.
34
+ * It first removes any existing repository directory for the current onboarding process.
35
+ * Then, it retrieves the GitHub username and constructs the repository URL to clone.
36
+ * @param onboardingId The ID of the onboarding process (for logging).
37
+ * @param tempDir The base temporary directory for the onboarding process.
38
+ * @param repoDir The specific directory within tempDir where the repository will be cloned.
39
+ * @throws An error if cloning fails, with a 'step' property set to 'cloneRepo'.
40
+ */
41
+ async cloneRepo(onboardingId, tempDir, repoDir) {
42
+ try {
43
+ await fs.mkdir(tempDir, { recursive: true });
44
+ let repoExistsAndIsValid = false;
45
+ const originalCwd = process.cwd();
46
+ try {
47
+ const gitDirStat = await fs.stat(path.join(repoDir, '.git'));
48
+ if (gitDirStat.isDirectory()) {
49
+ process.chdir(repoDir);
50
+ const { stdout: remoteUrlStdout } = await execAsync(`git config --get remote.origin.url`);
51
+ const remoteUrl = remoteUrlStdout.trim();
52
+ process.chdir(originalCwd);
53
+ const username = await this.getGitHubUsername();
54
+ const expectedRepoName = GITHUB_REPO.repoName.split('/')[1];
55
+ if (remoteUrl.includes(username) && remoteUrl.includes(expectedRepoName)) {
56
+ Logger.debug(`[${onboardingId}] Repository directory ${repoDir} already exists, is a valid git repository, and has the correct remote origin. Skipping clone.`);
57
+ repoExistsAndIsValid = true;
58
+ }
59
+ else {
60
+ Logger.log(`WARNING: [${onboardingId}] Repository directory ${repoDir} exists and is a git repository, but remote 'origin' URL (${remoteUrl}) does not match expected user/repo. Will re-clone.`);
61
+ }
62
+ }
63
+ }
64
+ catch (checkError) {
65
+ Logger.debug(`[${onboardingId}] Repository directory ${repoDir} check failed or is not the correct repository. Proceeding with clone. Error: ${checkError.message || checkError.code}`);
66
+ if (process.cwd() !== originalCwd) {
67
+ process.chdir(originalCwd);
68
+ }
69
+ }
70
+ if (!repoExistsAndIsValid) {
71
+ await fs.rm(repoDir, { recursive: true, force: true });
72
+ Logger.debug(`[${onboardingId}] Cleaned up existing directory (if any) at ${repoDir}.`);
73
+ const username = await this.getGitHubUsername();
74
+ const repoUrl = `https://github.com/${username}/${GITHUB_REPO.repoName.split('/')[1]}.git`;
75
+ await execAsync(`git clone ${repoUrl} ${repoDir}`);
76
+ Logger.debug(`[${onboardingId}] Successfully cloned repository to ${repoDir}`);
77
+ }
78
+ }
79
+ catch (error) {
80
+ const errorMessage = error instanceof Error ? error.message : String(error);
81
+ Logger.error(`[${onboardingId}] Error during cloneRepo: ${errorMessage}`);
82
+ throw Object.assign(new Error(`Failed to clone repository: ${errorMessage}`), { step: 'cloneRepo' });
83
+ }
84
+ }
85
+ /**
86
+ * Retrieves the GitHub username of the authenticated user using the `gh api user` command.
87
+ * @returns A promise that resolves to the GitHub username as a string.
88
+ * @throws An error if fetching the username fails.
89
+ */
90
+ async getGitHubUsername() {
91
+ try {
92
+ const { stdout } = await execAsync('gh api user -q .login');
93
+ return stdout.trim();
94
+ }
95
+ catch (error) {
96
+ throw new Error(`Failed to get GitHub username: ${error instanceof Error ? error.message : String(error)}`);
97
+ }
98
+ }
99
+ /**
100
+ * Saves the feed configuration as a JSON file into the specified path within the cloned repository.
101
+ * The file will be named `config.name.json`.
102
+ * @param onboardingId The ID of the onboarding process.
103
+ * @param config The feed configuration to save.
104
+ * @param repoDir The root directory of the cloned repository.
105
+ * @throws An error if saving the configuration fails, with a 'step' property set to 'saveFeedConfigToRepo'.
106
+ */
107
+ async saveFeedConfigToRepo(onboardingId, config, repoDir) {
108
+ try {
109
+ const feedsDir = path.join(repoDir, GITHUB_REPO.feedsPath);
110
+ await fs.mkdir(feedsDir, { recursive: true });
111
+ const configPath = path.join(feedsDir, `${config.name}.json`);
112
+ await fs.writeFile(configPath, JSON.stringify(config, null, 2));
113
+ Logger.debug(`[${onboardingId}] Saved feed configuration to ${configPath}`);
114
+ }
115
+ catch (error) {
116
+ const errorMessage = error instanceof Error ? error.message : String(error);
117
+ Logger.error(`[${onboardingId}] Error during saveFeedConfigToRepo: ${errorMessage}`);
118
+ throw Object.assign(new Error(`Failed to save feed configuration to repo: ${errorMessage}`), { step: 'saveFeedConfigToRepo' });
119
+ }
120
+ }
121
+ /**
122
+ * Creates a pull request on GitHub for the newly added feed configuration.
123
+ * @param onboardingId The ID of the onboarding process.
124
+ * @param config The feed configuration, used for branch name and PR details.
125
+ * @param repoDir The root directory of the cloned repository.
126
+ * @returns A promise that resolves to an object containing the URL of the created PR and the branch name.
127
+ * @throws An error if any step of the PR creation process fails.
128
+ */
129
+ /**
130
+ * Sets up a clean branch for the feed configuration changes.
131
+ * @param onboardingId The ID of the onboarding process.
132
+ * @param config The feed configuration.
133
+ * @param repoDir The root directory of the cloned repository.
134
+ * @returns The name of the created branch.
135
+ * @throws An error if branch setup fails.
136
+ */
137
+ async setupBranch(onboardingId, config, repoDir) {
138
+ try {
139
+ process.chdir(repoDir);
140
+ try {
141
+ await execAsync(`git remote get-url upstream`);
142
+ await execAsync(`git remote set-url upstream ${GITHUB_REPO.url}`);
143
+ Logger.debug(`[${onboardingId}] Updated 'upstream' remote URL to ${GITHUB_REPO.url}`);
144
+ }
145
+ catch (remoteError) {
146
+ await execAsync(`git remote add upstream ${GITHUB_REPO.url}`);
147
+ Logger.debug(`[${onboardingId}] Added 'upstream' remote with URL ${GITHUB_REPO.url}`);
148
+ }
149
+ await execAsync('git fetch upstream');
150
+ Logger.debug(`[${onboardingId}] Fetched from upstream.`);
151
+ try {
152
+ await execAsync('git checkout main');
153
+ Logger.debug(`[${onboardingId}] Switched to existing local branch 'main'.`);
154
+ }
155
+ catch (checkoutMainError) {
156
+ const errStdErr = checkoutMainError.stderr || '';
157
+ if (errStdErr.includes('did not match any file(s) known to git') ||
158
+ errStdErr.includes('is not a commit and a branch') ||
159
+ errStdErr.includes('pathspec \'main\' did not match any file(s) known to git')) {
160
+ Logger.debug(`[${onboardingId}] Local branch 'main' not found or invalid. Attempting to create it from 'upstream/main'.`);
161
+ try {
162
+ await execAsync('git checkout -b main upstream/main');
163
+ Logger.debug(`[${onboardingId}] Created and switched to local branch 'main' from 'upstream/main'.`);
164
+ }
165
+ catch (createMainError) {
166
+ const errMsg = createMainError instanceof Error ? createMainError.message : String(createMainError);
167
+ Logger.error(`[${onboardingId}] Failed to create local branch 'main' from 'upstream/main': ${errMsg}. Stderr: ${createMainError.stderr}`);
168
+ throw Object.assign(new Error(`Failed to create local branch 'main': ${errMsg}`), { step: 'setupBranch.setupMainBranch' });
169
+ }
170
+ }
171
+ else {
172
+ const errMsg = checkoutMainError instanceof Error ? checkoutMainError.message : String(checkoutMainError);
173
+ Logger.error(`[${onboardingId}] Failed to checkout local branch 'main': ${errMsg}. Stderr: ${checkoutMainError.stderr}`);
174
+ throw Object.assign(new Error(`Failed to checkout local branch 'main': ${errMsg}`), { step: 'setupBranch.checkoutMainBranch' });
175
+ }
176
+ }
177
+ await execAsync('git reset --hard upstream/main');
178
+ Logger.debug(`[${onboardingId}] Local 'main' branch is now hard reset to 'upstream/main'.`);
179
+ const branchName = `feed/${config.name}`;
180
+ try {
181
+ await execAsync(`git branch -D ${branchName}`);
182
+ Logger.debug(`[${onboardingId}] Deleted local branch '${branchName}' as it may have existed.`);
183
+ }
184
+ catch (deleteError) {
185
+ const errStdErrDelete = deleteError.stderr || '';
186
+ if (errStdErrDelete.includes('not found') || errStdErrDelete.includes('not found.')) {
187
+ Logger.debug(`[${onboardingId}] Local branch '${branchName}' did not exist, no need to delete.`);
188
+ }
189
+ else {
190
+ const errorMessage = deleteError instanceof Error ? deleteError.message : String(deleteError);
191
+ Logger.error(`[${onboardingId}] Failed to delete local branch '${branchName}': ${errorMessage}. Stderr: ${deleteError.stderr}`);
192
+ throw Object.assign(new Error(`Failed to delete local branch '${branchName}': ${errorMessage}`), { step: 'setupBranch.deleteFeatureBranch' });
193
+ }
194
+ }
195
+ await execAsync(`git checkout -b ${branchName} upstream/main`);
196
+ Logger.debug(`[${onboardingId}] Created and checked out new branch '${branchName}' from 'upstream/main'.`);
197
+ return branchName;
198
+ }
199
+ catch (error) {
200
+ const errorMessage = error instanceof Error ? error.message : String(error);
201
+ Logger.error(`[${onboardingId}] Error during branch setup: ${errorMessage}`);
202
+ throw Object.assign(new Error(`Failed to setup branch: ${errorMessage}`), { step: 'setupBranch' });
203
+ }
204
+ }
205
+ /**
206
+ * Creates a pull request for the feed configuration changes.
207
+ * @param onboardingId The ID of the onboarding process.
208
+ * @param config The feed configuration.
209
+ * @param repoDir The root directory of the cloned repository.
210
+ * @param branchName The name of the branch to create the PR from.
211
+ * @returns Object containing the PR URL and branch name.
212
+ * @throws An error if PR creation fails.
213
+ */
214
+ async createPullRequest(onboardingId, config, repoDir, branchName) {
215
+ try {
216
+ process.chdir(repoDir);
217
+ // Stage and commit changes
218
+ await execAsync('git add .');
219
+ await execAsync(`git commit --allow-empty -m "Add feed configuration for ${config.name}"`);
220
+ Logger.debug(`[${onboardingId}] Committed changes (or allowed empty commit) for ${config.name}.`);
221
+ // Push to origin
222
+ await execAsync(`git push -u -f origin ${branchName}`);
223
+ Logger.debug(`[${onboardingId}] Pushed branch ${branchName} to origin. Verifying remote branch...`);
224
+ try {
225
+ const { stdout: remoteBranchCheck } = await execAsync(`git ls-remote origin refs/heads/${branchName}`);
226
+ if (!remoteBranchCheck.includes(`refs/heads/${branchName}`)) {
227
+ throw new Error(`Branch ${branchName} not found on remote 'origin' (fork) after push. Output of ls-remote: ${remoteBranchCheck}`);
228
+ }
229
+ Logger.debug(`[${onboardingId}] Branch ${branchName} confirmed on remote 'origin' (fork).`);
230
+ }
231
+ catch (verifyError) {
232
+ const errorMessage = verifyError instanceof Error ? verifyError.message : String(verifyError);
233
+ Logger.error(`[${onboardingId}] Failed to verify branch ${branchName} on remote 'origin' (fork) after push: ${errorMessage}`);
234
+ throw new Error(`Verification of branch ${branchName} on remote 'origin' (fork) failed: ${errorMessage}`);
235
+ }
236
+ const title = `Add feed configuration for ${config.name}`;
237
+ const body = `Add new feed configuration:\n\n- Name: ${config.name}\n- Display Name: ${config.displayName}\n- Description: ${config.description}`;
238
+ const username = await this.getGitHubUsername();
239
+ const explicitHeadRef = `${username}:${branchName}`;
240
+ const prCheckCommand = `gh pr list --repo ${GITHUB_REPO.repoName} --head "${explicitHeadRef}" --state open --json url --limit 1`;
241
+ Logger.debug(`[${onboardingId}] Checking for existing PR with command: ${prCheckCommand}`);
242
+ const { stdout: existingPrJson } = await execAsync(prCheckCommand);
243
+ let prUrlToReturn;
244
+ const createPRCommand = `gh pr create --repo ${GITHUB_REPO.repoName} --base main --head ${explicitHeadRef} --title "${title}" --body "${body}"`;
245
+ if (existingPrJson && existingPrJson.trim() !== '[]' && existingPrJson.trim() !== '') {
246
+ try {
247
+ const prs = JSON.parse(existingPrJson);
248
+ if (prs.length > 0 && prs[0].url) {
249
+ prUrlToReturn = prs[0].url;
250
+ Logger.log(`[${onboardingId}] Found existing open PR for ${explicitHeadRef} on ${GITHUB_REPO.repoName}: ${prUrlToReturn}`);
251
+ }
252
+ else {
253
+ Logger.debug(`[${onboardingId}] No existing PR found or unable to parse PR list JSON. Proceeding to create PR.`);
254
+ const { stdout: prUrl } = await execAsync(createPRCommand);
255
+ prUrlToReturn = prUrl.trim();
256
+ }
257
+ }
258
+ catch (parseError) {
259
+ Logger.log(`WARNING: [${onboardingId}] Could not parse existing PR JSON: ${existingPrJson}. Proceeding to create new PR. Error: ${parseError}`);
260
+ const { stdout: prUrl } = await execAsync(createPRCommand);
261
+ prUrlToReturn = prUrl.trim();
262
+ }
263
+ }
264
+ else {
265
+ Logger.debug(`[${onboardingId}] No existing PR found. Proceeding to create PR.`);
266
+ const { stdout: prUrl } = await execAsync(createPRCommand);
267
+ prUrlToReturn = prUrl.trim();
268
+ }
269
+ Logger.debug(`[${onboardingId}] Pull request operation completed. PR URL: ${prUrlToReturn}`);
270
+ return { url: prUrlToReturn, branchName };
271
+ }
272
+ catch (error) {
273
+ const errorMessage = error instanceof Error ? error.message : String(error);
274
+ Logger.error(`[${onboardingId}] Error during createPullRequest: ${errorMessage}`);
275
+ throw Object.assign(new Error(`Failed to create pull request: ${errorMessage}`), { step: 'createPullRequest' });
276
+ }
277
+ }
278
+ /**
279
+ * Creates necessary directories for the onboarding process
280
+ * @param onboardingId The ID of the onboarding process
281
+ * @returns Object containing tempDir and repoDir paths
282
+ */
283
+ createDirectories(onboardingId) {
284
+ const tempDir = path.join(SETTINGS_DIR, 'onboard', 'temp', onboardingId);
285
+ const repoDir = path.join(tempDir, 'imcp-feed');
286
+ return { tempDir, repoDir };
287
+ }
288
+ }
289
+ export const onboardProcessor = new OnboardProcessor();
290
+ //# sourceMappingURL=OnboardProcessor.js.map
@@ -0,0 +1,49 @@
1
+ import { FeedConfiguration } from "../types.js";
2
+ export declare enum OnboardingProcessStatus {
3
+ VALIDATING = "validating",
4
+ VALIDATED = "validated",
5
+ PR_CREATING = "prcreating",
6
+ FAILED = "failed",
7
+ SUCCEEDED = "succeeded",
8
+ PENDING = "pending"
9
+ }
10
+ export type OperationType = 'FULL_ONBOARDING' | 'VALIDATION_ONLY';
11
+ export interface ServerValidationResult {
12
+ serverName: string;
13
+ isValid: boolean;
14
+ message?: string;
15
+ }
16
+ export interface OnboardStatus {
17
+ onboardingId: string;
18
+ status: OnboardingProcessStatus;
19
+ currentStep?: string;
20
+ validationStatus?: {
21
+ isValid: boolean;
22
+ message?: string;
23
+ serverResults?: ServerValidationResult[];
24
+ };
25
+ prInfo?: {
26
+ url?: string;
27
+ branchName?: string;
28
+ };
29
+ result?: any;
30
+ errorMessage?: string;
31
+ lastUpdated: string;
32
+ feedName: string;
33
+ serverName?: string;
34
+ operationType?: OperationType;
35
+ }
36
+ export interface OperationStatus {
37
+ onboardingId: string;
38
+ status: OnboardingProcessStatus;
39
+ message?: string;
40
+ lastQueried?: string;
41
+ }
42
+ export interface ValidationOperationResult {
43
+ validationStatus: {
44
+ isValid: boolean;
45
+ message?: string;
46
+ serverResults?: ServerValidationResult[];
47
+ };
48
+ feedConfiguration?: FeedConfiguration;
49
+ }
@@ -0,0 +1,10 @@
1
+ export var OnboardingProcessStatus;
2
+ (function (OnboardingProcessStatus) {
3
+ OnboardingProcessStatus["VALIDATING"] = "validating";
4
+ OnboardingProcessStatus["VALIDATED"] = "validated";
5
+ OnboardingProcessStatus["PR_CREATING"] = "prcreating";
6
+ OnboardingProcessStatus["FAILED"] = "failed";
7
+ OnboardingProcessStatus["SUCCEEDED"] = "succeeded";
8
+ OnboardingProcessStatus["PENDING"] = "pending";
9
+ })(OnboardingProcessStatus || (OnboardingProcessStatus = {}));
10
+ //# sourceMappingURL=OnboardStatus.js.map
@@ -0,0 +1,57 @@
1
+ import { FeedConfiguration } from '../types.js';
2
+ import { OnboardStatus, OperationType } from './OnboardStatus.js';
3
+ export declare class OnboardStatusManager {
4
+ private static instance;
5
+ private activeCategoryOperations;
6
+ private statusLock;
7
+ private constructor();
8
+ static getInstance(): OnboardStatusManager;
9
+ private withLock;
10
+ private loadStatuses;
11
+ private saveStatuses;
12
+ /**
13
+ * Saves the feed configuration associated with a category's active operation.
14
+ * The operation is identified by the categoryName.
15
+ * @param categoryName The name of the category (acting as operationId).
16
+ * @param config The FeedConfiguration to save.
17
+ */
18
+ saveFeedConfiguration(categoryName: string, config: FeedConfiguration): Promise<void>;
19
+ /**
20
+ * Retrieves the feed configuration associated with a category's active operation.
21
+ * @param categoryName The name of the category (acting as operationId).
22
+ * @returns A promise that resolves to the FeedConfiguration, or undefined if not found.
23
+ */
24
+ getFeedConfiguration(categoryName: string): Promise<FeedConfiguration | undefined>;
25
+ /**
26
+ * Retrieves the status of a category's active operation.
27
+ * @param categoryName The name of the category (acting as operationId).
28
+ * @returns A promise that resolves to the OnboardStatus, or undefined if no active operation.
29
+ */
30
+ getStatus(categoryName: string): Promise<OnboardStatus | undefined>;
31
+ /**
32
+ * Retrieves all active category operation statuses.
33
+ * @returns A promise that resolves to a record of all active OnboardStatus objects, keyed by categoryName.
34
+ */
35
+ getAllStatuses(): Promise<Record<string, OnboardStatus>>;
36
+ /**
37
+ * Updates the status of a category's active operation.
38
+ * If no operation exists for the category, it might create one based on the updates,
39
+ * or fail if `onboardingId` (categoryName) is not present in `newStatus`.
40
+ * For clarity, it's better if `createInitialStatus` is called first.
41
+ * @param categoryName The name of the category (acting as operationId).
42
+ * @param updates Partial OnboardStatus object with fields to update.
43
+ * @returns A promise that resolves to the updated OnboardStatus.
44
+ */
45
+ updateStatus(categoryName: string, updates: Partial<OnboardStatus>): Promise<OnboardStatus>;
46
+ /**
47
+ * Creates an initial status for a new category-wide operation.
48
+ * The operation is identified by the categoryName.
49
+ * @param categoryName The name of the category (also used as feedName and operationId).
50
+ * @param operationType The type of operation (e.g., FULL_ONBOARDING, VALIDATION_ONLY).
51
+ * @param serverName Optional: if the operation has a specific server context (e.g. validating a single new server in an existing category).
52
+ * However, the primary operation tracking is still by categoryName.
53
+ * @returns A promise that resolves to the initial OnboardStatus.
54
+ */
55
+ createInitialStatus(categoryName: string, operationType: OperationType, serverName?: string): Promise<OnboardStatus>;
56
+ }
57
+ export declare const onboardStatusManager: OnboardStatusManager;
@@ -0,0 +1,176 @@
1
+ import fs from 'fs/promises';
2
+ import path from 'path';
3
+ import { SETTINGS_DIR } from '../constants.js';
4
+ import { OnboardingProcessStatus } from './OnboardStatus.js';
5
+ import { Logger } from '../../utils/logger.js';
6
+ const ONBOARD_STATUS_DIR = path.join(SETTINGS_DIR, 'onboard');
7
+ const CATEGORY_OPERATIONS_STATUS_FILE = path.join(ONBOARD_STATUS_DIR, 'category_operations_status.json');
8
+ const FEED_CONFIG_DIR = path.join(ONBOARD_STATUS_DIR, 'feed_configs'); // Staging for feed configs during operation
9
+ export class OnboardStatusManager {
10
+ static instance;
11
+ activeCategoryOperations = {};
12
+ statusLock = Promise.resolve();
13
+ constructor() {
14
+ this.loadStatuses().catch(error => Logger.error('Failed to initialize OnboardStatusManager:', error));
15
+ }
16
+ static getInstance() {
17
+ if (!OnboardStatusManager.instance) {
18
+ OnboardStatusManager.instance = new OnboardStatusManager();
19
+ }
20
+ return OnboardStatusManager.instance;
21
+ }
22
+ async withLock(operation) {
23
+ const current = this.statusLock;
24
+ let resolve;
25
+ this.statusLock = new Promise(r => resolve = r);
26
+ try {
27
+ await current;
28
+ return await operation();
29
+ }
30
+ finally {
31
+ resolve();
32
+ }
33
+ }
34
+ async loadStatuses() {
35
+ await this.withLock(async () => {
36
+ try {
37
+ await fs.mkdir(ONBOARD_STATUS_DIR, { recursive: true });
38
+ await fs.mkdir(FEED_CONFIG_DIR, { recursive: true });
39
+ const data = await fs.readFile(CATEGORY_OPERATIONS_STATUS_FILE, 'utf-8');
40
+ this.activeCategoryOperations = JSON.parse(data);
41
+ }
42
+ catch (error) {
43
+ if (error.code === 'ENOENT') {
44
+ this.activeCategoryOperations = {};
45
+ await this.saveStatuses(); // Create the file if it doesn't exist
46
+ }
47
+ else {
48
+ Logger.error('Failed to load onboarding statuses:', error);
49
+ this.activeCategoryOperations = {}; // Initialize with empty statuses in case of other errors
50
+ }
51
+ }
52
+ });
53
+ }
54
+ async saveStatuses() {
55
+ await fs.mkdir(ONBOARD_STATUS_DIR, { recursive: true });
56
+ await fs.writeFile(CATEGORY_OPERATIONS_STATUS_FILE, JSON.stringify(this.activeCategoryOperations, null, 2));
57
+ }
58
+ /**
59
+ * Saves the feed configuration associated with a category's active operation.
60
+ * The operation is identified by the categoryName.
61
+ * @param categoryName The name of the category (acting as operationId).
62
+ * @param config The FeedConfiguration to save.
63
+ */
64
+ async saveFeedConfiguration(categoryName, config) {
65
+ await this.withLock(async () => {
66
+ try {
67
+ await fs.mkdir(FEED_CONFIG_DIR, { recursive: true });
68
+ // Suffix `_feed.json` to distinguish from potential status files if names overlap
69
+ const configPath = path.join(FEED_CONFIG_DIR, `${categoryName}_feed.json`);
70
+ await fs.writeFile(configPath, JSON.stringify(config, null, 2));
71
+ Logger.debug(`Saved feed configuration for category ${categoryName} to ${configPath}`);
72
+ }
73
+ catch (error) {
74
+ Logger.error(`Failed to save feed configuration for category ${categoryName}:`, error);
75
+ throw error; // Re-throw to allow caller to handle
76
+ }
77
+ });
78
+ }
79
+ /**
80
+ * Retrieves the feed configuration associated with a category's active operation.
81
+ * @param categoryName The name of the category (acting as operationId).
82
+ * @returns A promise that resolves to the FeedConfiguration, or undefined if not found.
83
+ */
84
+ async getFeedConfiguration(categoryName) {
85
+ return await this.withLock(async () => {
86
+ try {
87
+ const configPath = path.join(FEED_CONFIG_DIR, `${categoryName}_feed.json`);
88
+ const data = await fs.readFile(configPath, 'utf-8');
89
+ return JSON.parse(data);
90
+ }
91
+ catch (error) {
92
+ if (error.code === 'ENOENT') {
93
+ return undefined; // File not found, not an error state for this getter
94
+ }
95
+ // Log other errors but still return undefined as the config couldn't be retrieved.
96
+ Logger.error(`Failed to read feed configuration for category ${categoryName}:`, error);
97
+ return undefined; // Ensure undefined is returned on other errors too
98
+ }
99
+ });
100
+ }
101
+ /**
102
+ * Retrieves the status of a category's active operation.
103
+ * @param categoryName The name of the category (acting as operationId).
104
+ * @returns A promise that resolves to the OnboardStatus, or undefined if no active operation.
105
+ */
106
+ async getStatus(categoryName) {
107
+ await this.loadStatuses(); // Ensure latest statuses are loaded (though constructor does it initially)
108
+ return this.activeCategoryOperations[categoryName];
109
+ }
110
+ /**
111
+ * Retrieves all active category operation statuses.
112
+ * @returns A promise that resolves to a record of all active OnboardStatus objects, keyed by categoryName.
113
+ */
114
+ async getAllStatuses() {
115
+ await this.loadStatuses();
116
+ return this.activeCategoryOperations;
117
+ }
118
+ /**
119
+ * Updates the status of a category's active operation.
120
+ * If no operation exists for the category, it might create one based on the updates,
121
+ * or fail if `onboardingId` (categoryName) is not present in `newStatus`.
122
+ * For clarity, it's better if `createInitialStatus` is called first.
123
+ * @param categoryName The name of the category (acting as operationId).
124
+ * @param updates Partial OnboardStatus object with fields to update.
125
+ * @returns A promise that resolves to the updated OnboardStatus.
126
+ */
127
+ async updateStatus(categoryName, updates) {
128
+ return await this.withLock(async () => {
129
+ const existingStatus = this.activeCategoryOperations[categoryName] || {
130
+ onboardingId: categoryName, // Ensure onboardingId is set to categoryName if creating new
131
+ feedName: categoryName, // Ensure feedName is set to categoryName
132
+ status: OnboardingProcessStatus.PENDING, // Default status if creating new
133
+ lastUpdated: new Date().toISOString(),
134
+ operationType: updates.operationType || 'FULL_ONBOARDING', // Default operation type
135
+ ...updates // Apply initial updates if creating new
136
+ };
137
+ const updatedStatus = {
138
+ ...existingStatus,
139
+ ...updates,
140
+ lastUpdated: new Date().toISOString(),
141
+ };
142
+ this.activeCategoryOperations[categoryName] = updatedStatus;
143
+ await this.saveStatuses();
144
+ return updatedStatus;
145
+ });
146
+ }
147
+ /**
148
+ * Creates an initial status for a new category-wide operation.
149
+ * The operation is identified by the categoryName.
150
+ * @param categoryName The name of the category (also used as feedName and operationId).
151
+ * @param operationType The type of operation (e.g., FULL_ONBOARDING, VALIDATION_ONLY).
152
+ * @param serverName Optional: if the operation has a specific server context (e.g. validating a single new server in an existing category).
153
+ * However, the primary operation tracking is still by categoryName.
154
+ * @returns A promise that resolves to the initial OnboardStatus.
155
+ */
156
+ async createInitialStatus(categoryName, operationType, serverName) {
157
+ // The operationId is the categoryName itself for category-wide operations.
158
+ const operationId = categoryName;
159
+ const initialStatus = {
160
+ onboardingId: operationId, // This is the categoryName
161
+ feedName: categoryName, // Feed name is the category name
162
+ serverName, // Optional server context
163
+ status: OnboardingProcessStatus.PENDING,
164
+ currentStep: 'Initiated',
165
+ lastUpdated: new Date().toISOString(), // timestamp was an error, OnboardStatus uses lastUpdated
166
+ operationType,
167
+ errorMessage: undefined,
168
+ prInfo: undefined
169
+ };
170
+ // Use updateStatus to ensure it's saved correctly and lock is handled
171
+ return this.updateStatus(operationId, initialStatus);
172
+ }
173
+ }
174
+ // Export singleton instance
175
+ export const onboardStatusManager = OnboardStatusManager.getInstance();
176
+ //# sourceMappingURL=OnboardStatusManager.js.map
@@ -17,10 +17,12 @@ export interface RequirementStatus {
17
17
  lastCheckTime?: string;
18
18
  operationStatus?: OperationStatus;
19
19
  pythonEnv?: string;
20
+ npmPath?: string;
20
21
  }
21
22
  export interface MCPServerStatus {
22
23
  installedStatus: Record<string, OperationStatus>;
23
24
  name: string;
25
+ tags?: string[];
24
26
  error?: string;
25
27
  }
26
28
  export interface OperationStatus {
@@ -41,6 +43,7 @@ export interface MCPServerCategory {
41
43
  displayName: string;
42
44
  description?: string;
43
45
  type: 'local';
46
+ tags?: string[];
44
47
  path?: string;
45
48
  installationStatus?: InstallationStatus;
46
49
  feedConfiguration?: FeedConfiguration;
@@ -114,11 +117,7 @@ export interface RegistryConfig {
114
117
  };
115
118
  artifacts?: {
116
119
  registryUrl: string;
117
- assetName?: string;
118
- };
119
- local?: {
120
- localPath: string;
121
- assetName?: string;
120
+ registryName: string;
122
121
  };
123
122
  }
124
123
  export interface RequirementConfig {
@@ -1,4 +1,4 @@
1
- import { FeedConfiguration } from "../types.js";
1
+ import { FeedConfiguration, McpConfig } from "../types.js";
2
2
  /**
3
3
  * Validates feed configurations to ensure they meet required criteria
4
4
  */
@@ -9,5 +9,12 @@ export declare class FeedValidator {
9
9
  * @returns true if valid, throws error if invalid
10
10
  */
11
11
  validate(config: FeedConfiguration): boolean;
12
+ /**
13
+ * Validates a single MCP server configuration using the appropriate validator
14
+ * @param server The MCP server configuration to validate
15
+ * @param config The feed configuration containing shared requirements
16
+ * @returns true if valid, throws error if invalid
17
+ */
18
+ validateServer(server: McpConfig, config: FeedConfiguration): Promise<boolean>;
12
19
  }
13
20
  export declare const feedValidator: FeedValidator;