imcp 0.0.13 → 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.
Files changed (44) hide show
  1. package/dist/core/installers/clients/ClientInstaller.js +58 -40
  2. package/dist/core/onboard/FeedOnboardService.d.ts +35 -0
  3. package/dist/core/onboard/FeedOnboardService.js +137 -0
  4. package/dist/core/types.d.ts +2 -1
  5. package/dist/core/validators/FeedValidator.d.ts +13 -0
  6. package/dist/core/validators/FeedValidator.js +27 -0
  7. package/dist/web/contract/serverContract.d.ts +64 -0
  8. package/dist/web/contract/serverContract.js +2 -0
  9. package/dist/web/public/css/onboard.css +44 -0
  10. package/dist/web/public/index.html +17 -13
  11. package/dist/web/public/js/modal/index.js +58 -0
  12. package/dist/web/public/js/modal/installHandler.js +227 -0
  13. package/dist/web/public/js/modal/installModal.js +163 -0
  14. package/dist/web/public/js/modal/installation.js +281 -0
  15. package/dist/web/public/js/modal/loadingModal.js +52 -0
  16. package/dist/web/public/js/modal/loadingUI.js +74 -0
  17. package/dist/web/public/js/modal/messageQueue.js +112 -0
  18. package/dist/web/public/js/modal/modalSetup.js +512 -0
  19. package/dist/web/public/js/modal/modalUI.js +214 -0
  20. package/dist/web/public/js/modal/modalUtils.js +49 -0
  21. package/dist/web/public/js/modal/version.js +20 -0
  22. package/dist/web/public/js/modal/versionUtils.js +20 -0
  23. package/dist/web/public/js/modal.js +25 -1041
  24. package/dist/web/public/js/onboard/formProcessor.js +309 -0
  25. package/dist/web/public/js/onboard/index.js +131 -0
  26. package/dist/web/public/js/onboard/state.js +32 -0
  27. package/dist/web/public/js/onboard/templates.js +375 -0
  28. package/dist/web/public/js/onboard/uiHandlers.js +196 -0
  29. package/dist/web/public/js/serverCategoryDetails.js +43 -17
  30. package/dist/web/public/onboard.html +150 -0
  31. package/package.json +1 -1
  32. package/src/core/installers/clients/ClientInstaller.ts +66 -49
  33. package/src/core/types.ts +2 -1
  34. package/src/web/public/index.html +17 -13
  35. package/src/web/public/js/modal/index.js +58 -0
  36. package/src/web/public/js/modal/installModal.js +163 -0
  37. package/src/web/public/js/modal/installation.js +281 -0
  38. package/src/web/public/js/modal/loadingModal.js +52 -0
  39. package/src/web/public/js/modal/messageQueue.js +112 -0
  40. package/src/web/public/js/modal/modalSetup.js +512 -0
  41. package/src/web/public/js/modal/modalUtils.js +49 -0
  42. package/src/web/public/js/modal/versionUtils.js +20 -0
  43. package/src/web/public/js/modal.js +25 -1041
  44. package/src/web/public/js/serverCategoryDetails.js +43 -17
@@ -276,6 +276,7 @@ export class ClientInstaller {
276
276
  // --- Start of new logic ---
277
277
  // Clone the base installation configuration to avoid modifying the original serverConfig
278
278
  const installConfig = JSON.parse(JSON.stringify(serverConfig.installation));
279
+ installConfig.mode = serverConfig.mode;
279
280
  const pythonEnv = options.settings?.pythonEnv;
280
281
  let pythonDir = null;
281
282
  // 1. Determine which args to use and resolve npm paths
@@ -499,39 +500,48 @@ export class ClientInstaller {
499
500
  if (!settings.mcpServers) {
500
501
  settings.mcpServers = {};
501
502
  }
502
- // Special handling for Windows when command is npx for Cline and MSROO clients
503
- // Use a copy to avoid modifying the passed installConfig directly if needed elsewhere
504
- const serverConfigForClient = { ...installConfig };
505
- if (process.platform === 'win32' &&
506
- serverConfigForClient.command === 'npx' &&
507
- (clientName === 'Cline' || clientName === 'MSRooCode' || clientName === 'MSROO')) {
508
- // Update command to cmd
509
- serverConfigForClient.command = 'cmd';
510
- // Add /c and npx at the beginning of args
511
- serverConfigForClient.args = ['/c', 'npx', ...serverConfigForClient.args];
512
- // Add APPDATA environment variable pointing to npm directory
513
- if (!serverConfigForClient.env) {
514
- serverConfigForClient.env = {};
503
+ if (installConfig.mode === 'stdio') {
504
+ // Special handling for Windows when command is npx for Cline and MSROO clients
505
+ // Use a copy to avoid modifying the passed installConfig directly if needed elsewhere
506
+ const serverConfigForClient = { ...installConfig };
507
+ if (process.platform === 'win32' &&
508
+ serverConfigForClient.command === 'npx' &&
509
+ (clientName === 'Cline' || clientName === 'MSRooCode' || clientName === 'MSROO')) {
510
+ // Update command to cmd
511
+ serverConfigForClient.command = 'cmd';
512
+ // Add /c and npx at the beginning of args
513
+ serverConfigForClient.args = ['/c', 'npx', ...serverConfigForClient.args];
514
+ // Add APPDATA environment variable pointing to npm directory
515
+ if (!serverConfigForClient.env) {
516
+ serverConfigForClient.env = {};
517
+ }
518
+ // Dynamically get npm path and set APPDATA to it
519
+ const npmPath = await this.getNpmPath();
520
+ serverConfigForClient.env['APPDATA'] = npmPath;
521
+ Logger.debug(`Windows npx fix: command=${serverConfigForClient.command}, args=${serverConfigForClient.args.join(' ')}, env=${JSON.stringify(serverConfigForClient.env)}`);
522
+ }
523
+ // Convert backslashes to forward slashes in args paths
524
+ if (serverConfigForClient.args) {
525
+ serverConfigForClient.args = serverConfigForClient.args.map((arg) => typeof arg === 'string' ? arg.replace(/\\/g, '/') : arg);
515
526
  }
516
- // Dynamically get npm path and set APPDATA to it
517
- const npmPath = await this.getNpmPath();
518
- serverConfigForClient.env['APPDATA'] = npmPath;
519
- Logger.debug(`Windows npx fix: command=${serverConfigForClient.command}, args=${serverConfigForClient.args.join(' ')}, env=${JSON.stringify(serverConfigForClient.env)}`);
527
+ // Add or update the server configuration
528
+ settings.mcpServers[serverName] = {
529
+ command: serverConfigForClient.command,
530
+ args: serverConfigForClient.args,
531
+ env: serverConfigForClient.env,
532
+ autoApprove: [],
533
+ disabled: false,
534
+ alwaysAllow: []
535
+ };
536
+ Logger.debug(`Updating ${settingPath} for ${serverName}: ${JSON.stringify(settings.mcpServers[serverName])}`);
520
537
  }
521
- // Convert backslashes to forward slashes in args paths
522
- if (serverConfigForClient.args) {
523
- serverConfigForClient.args = serverConfigForClient.args.map((arg) => typeof arg === 'string' ? arg.replace(/\\/g, '/') : arg);
538
+ else if (installConfig.mode === 'sse') {
539
+ // Handle SSE mode for Cline and MSRoo clients
540
+ settings.mcpServers[serverName] = {
541
+ type: 'sse',
542
+ url: installConfig.url
543
+ };
524
544
  }
525
- // Add or update the server configuration
526
- settings.mcpServers[serverName] = {
527
- command: serverConfigForClient.command,
528
- args: serverConfigForClient.args,
529
- env: serverConfigForClient.env,
530
- autoApprove: [],
531
- disabled: false,
532
- alwaysAllow: []
533
- };
534
- Logger.debug(`Updating ${settingPath} for ${serverName}: ${JSON.stringify(settings.mcpServers[serverName])}`);
535
545
  // Write the updated settings back to the file
536
546
  await writeJsonFile(settingPath, settings);
537
547
  }
@@ -551,17 +561,25 @@ export class ClientInstaller {
551
561
  }
552
562
  // Use a copy to avoid modifying the passed installConfig directly
553
563
  const serverConfigForClient = { ...installConfig };
554
- // Convert backslashes to forward slashes in args paths
555
- if (serverConfigForClient.args) {
556
- serverConfigForClient.args = serverConfigForClient.args.map((arg) => typeof arg === 'string' ? arg.replace(/\\/g, '/') : arg);
564
+ if (installConfig.mode === 'stdio') {
565
+ if (serverConfigForClient.args) {
566
+ serverConfigForClient.args = serverConfigForClient.args.map((arg) => typeof arg === 'string' ? arg.replace(/\\/g, '/') : arg);
567
+ }
568
+ // Add or update the server configuration
569
+ settings.mcp.servers[serverName] = {
570
+ command: serverConfigForClient.command,
571
+ args: serverConfigForClient.args,
572
+ env: serverConfigForClient.env
573
+ };
574
+ Logger.debug(`Updating ${settingPath} for ${serverName}: ${JSON.stringify(settings.mcp.servers[serverName])}`);
575
+ }
576
+ else if (installConfig.mode === 'sse') {
577
+ // Handle SSE mode for Github Copilot
578
+ settings.mcp.servers[serverName] = {
579
+ type: 'sse',
580
+ url: installConfig.url
581
+ };
557
582
  }
558
- // Add or update the server configuration
559
- settings.mcp.servers[serverName] = {
560
- command: serverConfigForClient.command,
561
- args: serverConfigForClient.args,
562
- env: serverConfigForClient.env
563
- };
564
- Logger.debug(`Updating ${settingPath} for ${serverName}: ${JSON.stringify(settings.mcp.servers[serverName])}`);
565
583
  // Write the updated settings back to the file
566
584
  await writeJsonFile(settingPath, settings);
567
585
  }
@@ -0,0 +1,35 @@
1
+ import { FeedConfiguration } from '../types.js';
2
+ /**
3
+ * Service for handling feed onboarding operations
4
+ */
5
+ export declare class FeedOnboardService {
6
+ private tempDir;
7
+ private repoDir;
8
+ constructor();
9
+ /**
10
+ * Onboard a new feed configuration
11
+ * @param config Feed configuration to onboard
12
+ */
13
+ onboardFeed(config: FeedConfiguration): Promise<void>;
14
+ /**
15
+ * Fork the repository using gh CLI
16
+ */
17
+ private forkRepo;
18
+ /**
19
+ * Clone the forked repository
20
+ */
21
+ private cloneRepo;
22
+ /**
23
+ * Get GitHub username using gh CLI
24
+ */
25
+ private getGitHubUsername;
26
+ /**
27
+ * Save feed configuration to file
28
+ */
29
+ private saveFeedConfig;
30
+ /**
31
+ * Create pull request using gh CLI
32
+ */
33
+ private createPullRequest;
34
+ }
35
+ export declare const feedOnboardService: FeedOnboardService;
@@ -0,0 +1,137 @@
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 { feedValidator } from '../validators/FeedValidator.js';
7
+ import { Logger } from '../../utils/logger.js';
8
+ const execAsync = promisify(exec);
9
+ /**
10
+ * Service for handling feed onboarding operations
11
+ */
12
+ export class FeedOnboardService {
13
+ tempDir;
14
+ repoDir;
15
+ constructor() {
16
+ this.tempDir = path.join(SETTINGS_DIR, 'temp');
17
+ this.repoDir = path.join(this.tempDir, 'imcp-feed');
18
+ }
19
+ /**
20
+ * Onboard a new feed configuration
21
+ * @param config Feed configuration to onboard
22
+ */
23
+ async onboardFeed(config) {
24
+ try {
25
+ // Validate feed configuration
26
+ feedValidator.validate(config);
27
+ // Create temp directory
28
+ await fs.mkdir(this.tempDir, { recursive: true });
29
+ // Fork the repo
30
+ await this.forkRepo();
31
+ // Clone the forked repo
32
+ await this.cloneRepo();
33
+ // Save feed configuration
34
+ await this.saveFeedConfig(config);
35
+ // Create branch, push changes and create PR
36
+ await this.createPullRequest(config);
37
+ Logger.log(`Successfully onboarded feed: ${config.name}`);
38
+ }
39
+ catch (error) {
40
+ Logger.error('Feed onboarding failed:', error);
41
+ throw error;
42
+ }
43
+ finally {
44
+ // Cleanup temp directory
45
+ await fs.rm(this.tempDir, { recursive: true, force: true });
46
+ }
47
+ }
48
+ /**
49
+ * Fork the repository using gh CLI
50
+ */
51
+ async forkRepo() {
52
+ try {
53
+ await execAsync(`gh repo fork ${GITHUB_REPO.repoName} --clone=false`);
54
+ Logger.debug('Successfully forked repository');
55
+ }
56
+ catch (error) {
57
+ throw new Error(`Failed to fork repository: ${error instanceof Error ? error.message : String(error)}`);
58
+ }
59
+ }
60
+ /**
61
+ * Clone the forked repository
62
+ */
63
+ async cloneRepo() {
64
+ try {
65
+ // Clean up existing repo directory
66
+ await fs.rm(this.repoDir, { recursive: true, force: true });
67
+ const username = await this.getGitHubUsername();
68
+ const repoUrl = `https://github.com/${username}/${GITHUB_REPO.repoName.split('/')[1]}.git`;
69
+ // Ensure temp dir exists
70
+ await fs.mkdir(this.tempDir, { recursive: true });
71
+ // Clone the repo into imcp-feed subdirectory
72
+ await execAsync(`git clone ${repoUrl} ${this.repoDir}`);
73
+ Logger.debug('Successfully cloned repository');
74
+ }
75
+ catch (error) {
76
+ throw new Error(`Failed to clone repository: ${error instanceof Error ? error.message : String(error)}`);
77
+ }
78
+ }
79
+ /**
80
+ * Get GitHub username using gh CLI
81
+ */
82
+ async getGitHubUsername() {
83
+ try {
84
+ const { stdout } = await execAsync('gh api user -q .login');
85
+ return stdout.trim();
86
+ }
87
+ catch (error) {
88
+ throw new Error(`Failed to get GitHub username: ${error instanceof Error ? error.message : String(error)}`);
89
+ }
90
+ }
91
+ /**
92
+ * Save feed configuration to file
93
+ */
94
+ async saveFeedConfig(config) {
95
+ try {
96
+ const feedsDir = path.join(this.repoDir, GITHUB_REPO.feedsPath);
97
+ await fs.mkdir(feedsDir, { recursive: true });
98
+ const configPath = path.join(feedsDir, `${config.name}.json`);
99
+ await fs.writeFile(configPath, JSON.stringify(config, null, 2));
100
+ Logger.debug(`Saved feed configuration to ${configPath}`);
101
+ }
102
+ catch (error) {
103
+ throw new Error(`Failed to save feed configuration: ${error instanceof Error ? error.message : String(error)}`);
104
+ }
105
+ }
106
+ /**
107
+ * Create pull request using gh CLI
108
+ */
109
+ async createPullRequest(config) {
110
+ try {
111
+ process.chdir(this.repoDir);
112
+ // Ensure we're up to date with upstream main
113
+ await execAsync(`git remote add upstream ${GITHUB_REPO.url}`);
114
+ await execAsync('git fetch upstream');
115
+ await execAsync('git reset --hard upstream/main');
116
+ // Create and checkout new branch
117
+ const branchName = `feed/${config.name}`;
118
+ await execAsync(`git checkout -b ${branchName}`);
119
+ // Stage and commit changes
120
+ await execAsync('git add .');
121
+ await execAsync(`git commit -m "Add feed configuration for ${config.name}"`);
122
+ // Push to origin
123
+ await execAsync(`git push -f origin ${branchName}`);
124
+ const title = `Add feed configuration for ${config.name}`;
125
+ const body = `Add new feed configuration:\n\n- Name: ${config.name}\n- Display Name: ${config.displayName}\n- Description: ${config.description}`;
126
+ const username = await this.getGitHubUsername();
127
+ await execAsync(`gh pr create --title "${title}" --body "${body}" --repo ${GITHUB_REPO.repoName} --base main --head ${username}:${branchName}`);
128
+ Logger.debug('Successfully created pull request');
129
+ }
130
+ catch (error) {
131
+ throw new Error(`Failed to create pull request: ${error instanceof Error ? error.message : String(error)}`);
132
+ }
133
+ }
134
+ }
135
+ // Export singleton instance
136
+ export const feedOnboardService = new FeedOnboardService();
137
+ //# sourceMappingURL=FeedOnboardService.js.map
@@ -85,6 +85,7 @@ export interface InstallationConfig {
85
85
  command: string;
86
86
  args: string[];
87
87
  env?: Record<string, EnvVariableConfig>;
88
+ url?: string;
88
89
  }
89
90
  export interface DependencyConfig {
90
91
  requirements?: Array<{
@@ -99,7 +100,7 @@ export interface DependencyConfig {
99
100
  export interface McpConfig {
100
101
  name: string;
101
102
  description: string;
102
- mode: 'stdio' | 'http';
103
+ mode: 'stdio' | 'sse';
103
104
  dependencies?: DependencyConfig;
104
105
  schemas?: string;
105
106
  repository?: string;
@@ -0,0 +1,13 @@
1
+ import { FeedConfiguration } from "../types.js";
2
+ /**
3
+ * Validates feed configurations to ensure they meet required criteria
4
+ */
5
+ export declare class FeedValidator {
6
+ /**
7
+ * Validates a feed configuration
8
+ * @param config The feed configuration to validate
9
+ * @returns true if valid, throws error if invalid
10
+ */
11
+ validate(config: FeedConfiguration): boolean;
12
+ }
13
+ export declare const feedValidator: FeedValidator;
@@ -0,0 +1,27 @@
1
+ /**
2
+ * Validates feed configurations to ensure they meet required criteria
3
+ */
4
+ export class FeedValidator {
5
+ /**
6
+ * Validates a feed configuration
7
+ * @param config The feed configuration to validate
8
+ * @returns true if valid, throws error if invalid
9
+ */
10
+ validate(config) {
11
+ try {
12
+ // For now return true as requested
13
+ return true;
14
+ // TODO: Implement full validation logic:
15
+ // - Required fields (name, displayName, etc)
16
+ // - Server configuration validation
17
+ // - Requirements validation
18
+ // - Repository validation
19
+ }
20
+ catch (error) {
21
+ throw new Error(`Feed validation failed: ${error instanceof Error ? error.message : String(error)}`);
22
+ }
23
+ }
24
+ }
25
+ // Export singleton instance
26
+ export const feedValidator = new FeedValidator();
27
+ //# sourceMappingURL=FeedValidator.js.map
@@ -0,0 +1,64 @@
1
+ import { DependencyConfig, RegistryConfig, ServerInstallOptions } from '../../core/types.js';
2
+ export interface OnboardServerConfig {
3
+ name: string;
4
+ description: string;
5
+ mode: 'stdio' | 'http';
6
+ schemas?: string;
7
+ dependencies?: DependencyConfig;
8
+ repository?: string;
9
+ installation: {
10
+ command: string;
11
+ args?: string[];
12
+ env?: Record<string, {
13
+ name: string;
14
+ default?: string;
15
+ required: boolean;
16
+ description?: string;
17
+ }>;
18
+ };
19
+ }
20
+ export type RequirementType = 'npm' | 'pip' | 'command' | 'extension' | 'other';
21
+ export interface OnboardRequirementConfig {
22
+ type: RequirementType;
23
+ name: string;
24
+ version: string;
25
+ alias?: string;
26
+ registry?: RegistryConfig;
27
+ }
28
+ export interface OnboardRequestBody {
29
+ categoryData: {
30
+ name: string;
31
+ displayName: string;
32
+ description?: string;
33
+ repository?: string;
34
+ requirements?: OnboardRequirementConfig[];
35
+ mcpServers?: OnboardServerConfig[];
36
+ };
37
+ isUpdate: boolean;
38
+ }
39
+ export interface ListQueryParams {
40
+ local?: string;
41
+ }
42
+ export interface UpdateRequirementsRequestBody {
43
+ requirements: {
44
+ name: string;
45
+ updateVersion: string;
46
+ }[];
47
+ }
48
+ export interface InstallServersRequestBody {
49
+ serverList: Record<string, ServerInstallOptions>;
50
+ }
51
+ export interface UninstallServersRequestBody {
52
+ serverList: Record<string, {
53
+ removeData?: boolean;
54
+ }>;
55
+ options: {
56
+ targets: string[];
57
+ removeData?: boolean;
58
+ };
59
+ }
60
+ export interface ApiResponse<T> {
61
+ success: boolean;
62
+ data?: T;
63
+ error?: string;
64
+ }
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=serverContract.js.map
@@ -0,0 +1,44 @@
1
+ /* Onboarding form styles */
2
+ .requirement-item,
3
+ .server-item {
4
+ background-color: #ffffff;
5
+ transition: all 0.2s ease;
6
+ }
7
+
8
+ .requirement-item:hover,
9
+ .server-item:hover {
10
+ box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
11
+ }
12
+
13
+ .registry-config,
14
+ .installation-config,
15
+ .env-variables {
16
+ background-color: #f9fafb;
17
+ border-radius: 0.5rem;
18
+ padding: 1rem;
19
+ margin-top: 1rem;
20
+ }
21
+
22
+ .registry-github,
23
+ .registry-artifacts {
24
+ background-color: #ffffff;
25
+ border-radius: 0.5rem;
26
+ padding: 1rem;
27
+ }
28
+
29
+ /* Order field styling */
30
+ input[type="number"][name$=".order"] {
31
+ -moz-appearance: textfield;
32
+ }
33
+
34
+ input[type="number"][name$=".order"]::-webkit-outer-spin-button,
35
+ input[type="number"][name$=".order"]::-webkit-inner-spin-button {
36
+ -webkit-appearance: none;
37
+ margin: 0;
38
+ }
39
+
40
+ input[type="number"][name$=".order"]:focus {
41
+ border-color: #3b82f6;
42
+ outline: none;
43
+ box-shadow: 0 0 0 2px rgba(59, 130, 246, 0.2);
44
+ }
@@ -162,19 +162,23 @@
162
162
  setupSearch();
163
163
  setupModalOutsideClick();
164
164
 
165
- // Check URL parameters for category
166
- const urlParams = new URLSearchParams(window.location.search);
167
- const categoryParam = urlParams.get('category');
168
-
169
- // If we have a category parameter or last selected category
170
- const lastSelected = categoryParam || localStorage.getItem('lastSelectedCategory');
171
-
172
- // First fetch categories
173
- await fetchServerCategories();
174
-
175
- // Then show the selected category if it exists
176
- if (lastSelected) {
177
- showServerDetails(lastSelected);
165
+ try {
166
+ // First fetch categories and wait for completion
167
+ await fetchServerCategories();
168
+
169
+ // Check URL parameters for category
170
+ const urlParams = new URLSearchParams(window.location.search);
171
+ const categoryParam = urlParams.get('category');
172
+
173
+ // If we have a category parameter or last selected category
174
+ const lastSelected = categoryParam || localStorage.getItem('lastSelectedCategory');
175
+
176
+ // Only show details after data is loaded
177
+ if (lastSelected) {
178
+ await showServerDetails(lastSelected);
179
+ }
180
+ } catch (error) {
181
+ console.error('Error during initialization:', error);
178
182
  }
179
183
  });
180
184
  </script>
@@ -0,0 +1,58 @@
1
+ // Import all modal-related functionality
2
+ import { compareVersions } from './versionUtils.js';
3
+ import { delayedAppendInstallLoadingMessage } from './messageQueue.js';
4
+ import { showInstallLoadingModal, appendInstallLoadingMessage, hideInstallLoadingModal } from './loadingModal.js';
5
+ import { closeModal, setupModalOutsideClick } from './modalUtils.js';
6
+ import { handleBulkClientInstall, uninstallTools } from './installation.js';
7
+ import { showInstallModal } from './installModal.js';
8
+ import {
9
+ setupClientItems,
10
+ setupEnvironmentVariables,
11
+ setupInstallationArguments,
12
+ setupServerRequirements,
13
+ setupFormSubmitHandler
14
+ } from './modalSetup.js';
15
+
16
+ // Export all modal functionality
17
+ export {
18
+ // Version utilities
19
+ compareVersions,
20
+
21
+ // Message queue
22
+ delayedAppendInstallLoadingMessage,
23
+
24
+ // Loading modal
25
+ showInstallLoadingModal,
26
+ appendInstallLoadingMessage,
27
+ hideInstallLoadingModal,
28
+
29
+ // Modal utilities
30
+ closeModal,
31
+ setupModalOutsideClick,
32
+
33
+ // Installation
34
+ handleBulkClientInstall,
35
+ uninstallTools,
36
+
37
+ // Install modal
38
+ showInstallModal,
39
+
40
+ // Modal setup
41
+ setupClientItems,
42
+ setupEnvironmentVariables,
43
+ setupInstallationArguments,
44
+ setupServerRequirements,
45
+ setupFormSubmitHandler
46
+ };
47
+
48
+ // Initialize modal functionality
49
+ document.addEventListener('DOMContentLoaded', () => {
50
+ setupModalOutsideClick();
51
+ });
52
+
53
+ // Make certain functions available globally
54
+ window.showInstallModal = showInstallModal;
55
+ window.showInstallLoadingModal = showInstallLoadingModal;
56
+ window.appendInstallLoadingMessage = appendInstallLoadingMessage;
57
+ window.hideInstallLoadingModal = hideInstallLoadingModal;
58
+ window.uninstallTools = uninstallTools;