imcp 0.0.12 → 0.0.13

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/ConfigurationProvider.d.ts +2 -1
  2. package/dist/core/ConfigurationProvider.js +20 -24
  3. package/dist/core/InstallationService.d.ts +17 -0
  4. package/dist/core/InstallationService.js +127 -61
  5. package/dist/core/MCPManager.d.ts +1 -0
  6. package/dist/core/MCPManager.js +3 -0
  7. package/dist/core/RequirementService.d.ts +4 -4
  8. package/dist/core/RequirementService.js +11 -7
  9. package/dist/core/ServerSchemaProvider.d.ts +1 -1
  10. package/dist/core/ServerSchemaProvider.js +15 -10
  11. package/dist/core/constants.d.ts +3 -0
  12. package/dist/core/constants.js +4 -1
  13. package/dist/core/installers/requirements/PipInstaller.js +10 -5
  14. package/dist/core/types.d.ts +4 -0
  15. package/dist/services/ServerService.d.ts +5 -0
  16. package/dist/services/ServerService.js +15 -0
  17. package/dist/utils/githubAuth.js +0 -10
  18. package/dist/utils/githubUtils.d.ts +16 -0
  19. package/dist/utils/githubUtils.js +55 -39
  20. package/dist/web/public/css/detailsWidget.css +157 -32
  21. package/dist/web/public/css/serverDetails.css +35 -19
  22. package/dist/web/public/index.html +2 -0
  23. package/dist/web/public/js/detailsWidget.js +43 -40
  24. package/dist/web/public/js/serverCategoryDetails.js +182 -120
  25. package/dist/web/server.js +25 -0
  26. package/package.json +3 -4
  27. package/src/core/ConfigurationProvider.ts +37 -29
  28. package/src/core/InstallationService.ts +176 -62
  29. package/src/core/MCPManager.ts +4 -0
  30. package/src/core/RequirementService.ts +12 -8
  31. package/src/core/ServerSchemaLoader.ts +48 -0
  32. package/src/core/ServerSchemaProvider.ts +137 -0
  33. package/src/core/constants.ts +4 -1
  34. package/src/core/installers/requirements/PipInstaller.ts +10 -5
  35. package/src/core/types.ts +4 -0
  36. package/src/services/ServerService.ts +15 -0
  37. package/src/utils/githubAuth.ts +14 -27
  38. package/src/utils/githubUtils.ts +84 -47
  39. package/src/web/public/css/detailsWidget.css +235 -0
  40. package/src/web/public/css/serverDetails.css +126 -0
  41. package/src/web/public/index.html +2 -0
  42. package/src/web/public/js/detailsWidget.js +264 -0
  43. package/src/web/public/js/serverCategoryDetails.js +182 -120
  44. package/src/web/server.ts +31 -0
@@ -10,7 +10,9 @@ import path from 'path';
10
10
  */
11
11
  export const GITHUB_REPO = {
12
12
  url: 'https://github.com/ai-microsoft/imcp-feed.git',
13
- feedsPath: 'feeds'
13
+ repoName: 'ai-microsoft/imcp-feed',
14
+ feedsPath: 'feeds',
15
+ feedAssetsName: 'imcp-feeds-${latest}.zip',
14
16
  };
15
17
 
16
18
  /**
@@ -29,6 +31,7 @@ export const SETTINGS_DIR = (() => {
29
31
  * Local feeds directory path
30
32
  */
31
33
  export const LOCAL_FEEDS_DIR = path.join(SETTINGS_DIR, 'feeds');
34
+ export const LOCAL_FEEDS_SCHEMA_DIR = path.join(LOCAL_FEEDS_DIR, 'schemas');
32
35
 
33
36
  const CODE_STRORAGE_DIR = (() => {
34
37
  switch (process.platform) {
@@ -25,7 +25,7 @@ export class PipInstaller extends BaseInstaller {
25
25
 
26
26
  supportCheckUpdates(): boolean {
27
27
  /// temporarily disabling update check for pip as not able to get which pip of python is being used
28
- return false;
28
+ return true;
29
29
  }
30
30
  /**
31
31
  * Check if the Python package is already installed
@@ -47,7 +47,8 @@ export class PipInstaller extends BaseInstaller {
47
47
  type: 'pip',
48
48
  installed,
49
49
  version: installedVersion,
50
- inProgress: false
50
+ inProgress: false,
51
+ pythonEnv: this.getPythonCommand(options) // Preserve pythonEnv in status check too
51
52
  };
52
53
  } catch (error) {
53
54
  return {
@@ -55,7 +56,8 @@ export class PipInstaller extends BaseInstaller {
55
56
  type: 'pip',
56
57
  installed: false,
57
58
  error: error instanceof Error ? error.message : String(error),
58
- inProgress: false
59
+ inProgress: false,
60
+ pythonEnv: this.getPythonCommand(options) // Preserve pythonEnv even in error case
59
61
  };
60
62
  }
61
63
  }
@@ -117,12 +119,14 @@ export class PipInstaller extends BaseInstaller {
117
119
  }
118
120
  }
119
121
 
122
+ // Store the pythonEnv in the status for future use
120
123
  return {
121
124
  name: requirement.name,
122
125
  type: 'pip',
123
126
  installed: true,
124
127
  version: requirement.version,
125
- inProgress: false
128
+ inProgress: false,
129
+ pythonEnv: this.getPythonCommand(options) // Store the python env
126
130
  };
127
131
  } catch (error) {
128
132
  return {
@@ -130,7 +134,8 @@ export class PipInstaller extends BaseInstaller {
130
134
  type: 'pip',
131
135
  installed: false,
132
136
  error: error instanceof Error ? error.message : String(error),
133
- inProgress: false
137
+ inProgress: false,
138
+ pythonEnv: this.getPythonCommand(options) // Store the python env
134
139
  };
135
140
  }
136
141
  }
package/src/core/types.ts CHANGED
@@ -17,6 +17,7 @@ export interface RequirementStatus {
17
17
  };
18
18
  lastCheckTime?: string;
19
19
  operationStatus?: OperationStatus;
20
+ pythonEnv?: string; // Store Python environment path for pip requirements
20
21
  }
21
22
 
22
23
  export interface MCPServerStatus {
@@ -116,6 +117,8 @@ export interface McpConfig {
116
117
  description: string;
117
118
  mode: 'stdio' | 'http'; // Currently only stdio mode is supported in ai-coder-tools
118
119
  dependencies?: DependencyConfig;
120
+ schemas?: string; // Path to the schema file
121
+ repository?: string; // Repository URL for the server
119
122
  installation: InstallationConfig;
120
123
  }
121
124
 
@@ -147,6 +150,7 @@ export interface FeedConfiguration {
147
150
  name: string;
148
151
  displayName: string;
149
152
  description: string;
153
+ repository?: string;
150
154
  requirements: RequirementConfig[];
151
155
  mcpServers: McpConfig[];
152
156
  }
@@ -1,6 +1,7 @@
1
1
  import path from 'path';
2
2
  import { fileURLToPath } from 'url';
3
3
  import { Logger } from '../utils/logger.js';
4
+ import { getServerSchemaProvider, ServerSchema } from '../core/ServerSchemaProvider.js';
4
5
  import {
5
6
  MCPServerCategory,
6
7
  ServerInstallOptions,
@@ -123,6 +124,20 @@ export class ServerService {
123
124
  }
124
125
  }
125
126
 
127
+ /**
128
+ * Gets the schema for a specific server in a category
129
+ */
130
+ async getServerSchema(categoryName: string, serverName: string): Promise<ServerSchema | undefined> {
131
+ try {
132
+ const provider = await getServerSchemaProvider();
133
+ const serverMcpConfig = await mcpManager.getServerMcpConfig(categoryName, serverName);
134
+ return await provider.getSchema(categoryName, serverMcpConfig?.schemas || `${serverName}.json`);
135
+ } catch (error) {
136
+ Logger.error(`Failed to get schema for server ${serverName} in category ${categoryName}:`, error);
137
+ throw error;
138
+ }
139
+ }
140
+
126
141
  /**
127
142
  * Installs a specific mcp tool for a server.
128
143
  * TODO: This might require enhancing MCPManager to handle category-specific installs.
@@ -9,7 +9,7 @@ const execAsync = util.promisify(exec);
9
9
  const spawnAsync = (command: string, args: string[], options: any = {}) => {
10
10
  return new Promise((resolve, reject) => {
11
11
  const childProcess = spawn(command, args, options);
12
-
12
+
13
13
  childProcess.on('close', (code) => {
14
14
  if (code === 0) {
15
15
  resolve(code);
@@ -17,7 +17,7 @@ const spawnAsync = (command: string, args: string[], options: any = {}) => {
17
17
  reject(new Error(`Process exited with code ${code}`));
18
18
  }
19
19
  });
20
-
20
+
21
21
  childProcess.on('error', (err) => {
22
22
  reject(err);
23
23
  });
@@ -35,29 +35,16 @@ export async function checkGithubAuth(): Promise<void> {
35
35
  Logger.debug('Starting GitHub authentication check');
36
36
 
37
37
  try {
38
- // Check if git is installed
39
- if (!await isToolInstalled('git')) {
40
- Logger.log('Installing required Git...');
41
- await installCLI('git');
42
-
43
- // Verify git was installed correctly, with retry mechanism
44
- if (!await isToolInstalled('git')) {
45
- throw new Error('Failed to install Git. Please install it manually and try again.');
46
- }
47
-
48
- Logger.debug('Git installed successfully and verified');
49
- }
50
-
51
38
  // Check if gh CLI is installed
52
39
  if (!await isToolInstalled('gh')) {
53
40
  Logger.log('Installing required GitHub CLI...');
54
41
  await installCLI('gh');
55
-
42
+
56
43
  // Verify gh CLI was installed correctly, with retry mechanism
57
44
  if (!await isToolInstalled('gh')) {
58
45
  throw new Error('Failed to install GitHub CLI. Please install it manually and try again.');
59
46
  }
60
-
47
+
61
48
  Logger.debug('GitHub CLI installed successfully and verified');
62
49
  }
63
50
  } catch (error) {
@@ -70,12 +57,12 @@ export async function checkGithubAuth(): Promise<void> {
70
57
  // Check if user is authenticated
71
58
  const { stdout: viewerData } = await execAsync('gh api user');
72
59
  const viewer = JSON.parse(viewerData);
73
-
60
+
74
61
  Logger.debug({
75
62
  action: 'github_auth_check',
76
63
  username: viewer.login
77
64
  });
78
-
65
+
79
66
  // Check if user is using company account (ends with _microsoft)
80
67
  if (!viewer.login.toLowerCase().endsWith('_microsoft')) {
81
68
  const error = 'Error: You must be logged in with a Microsoft account (username should end with _microsoft). ' +
@@ -92,34 +79,34 @@ export async function checkGithubAuth(): Promise<void> {
92
79
  if (error instanceof GithubAuthError) {
93
80
  throw error;
94
81
  }
95
-
82
+
96
83
  // If the error is due to not being authenticated
97
84
  const errorMessage = (error as any)?.stderr || (error as Error).message;
98
85
  if (errorMessage.includes('please run: gh auth login') || errorMessage.includes('GH_TOKEN')) {
99
86
  Logger.log('GitHub authentication required at the first run. Please login account end with _microsoft.');
100
-
87
+
101
88
  try {
102
89
  // Use spawnAsync for interactive authentication
103
90
  await spawnAsync('gh', ['auth', 'login', '--web', '--hostname', 'github.com', '--git-protocol', 'https'], {
104
91
  stdio: 'inherit' // User sees & interacts directly with the process
105
92
  });
106
-
93
+
107
94
  Logger.debug('GitHub authentication process completed');
108
-
95
+
109
96
  // Verify the authentication was successful
110
97
  const { stdout: viewerData } = await execAsync('gh api user');
111
98
  const viewer = JSON.parse(viewerData);
112
-
99
+
113
100
  // Check if user is using company account (ends with _microsoft)
114
101
  if (!viewer.login.toLowerCase().endsWith('_microsoft')) {
115
102
  throw new GithubAuthError('You must be logged in with a Microsoft account (username should end with _microsoft).');
116
103
  }
117
-
104
+
118
105
  Logger.debug(`Successfully authenticated as ${viewer.login}`);
119
106
  return; // Auth successful, continue execution
120
107
  } catch (loginError) {
121
108
  Logger.error('Error during GitHub authentication process', loginError);
122
-
109
+
123
110
  // If the interactive login failed, provide manual instructions
124
111
  const authInstructions =
125
112
  '\nError: GitHub authentication required. Please follow these steps:\n\n' +
@@ -129,7 +116,7 @@ export async function checkGithubAuth(): Promise<void> {
129
116
  '3. Follow the prompts to login with your Microsoft account (username must end with _microsoft)\n' +
130
117
  '4. Authorize ai-microsoft organization.\n' +
131
118
  '5. After successful login, run imcp command again.\n\n';
132
-
119
+
133
120
  Logger.log(authInstructions);
134
121
  throw new GithubAuthError('GitHub authentication required. Please login first and try again.');
135
122
  }
@@ -9,95 +9,132 @@ import { SETTINGS_DIR } from '../core/constants.js';
9
9
 
10
10
  const execAsync = util.promisify(exec);
11
11
 
12
+ interface DownloadGithubReleaseResult {
13
+ version: string;
14
+ downloadPath: string;
15
+ }
16
+
12
17
  /**
13
- * Helper to handle GitHub release downloads
14
- * @param requirement The requirement configuration
15
- * @param registry The GitHub release registry configuration
16
- * @returns The path to the downloaded file
18
+ * Downloads a GitHub release asset
19
+ * @param repo GitHub repository in format owner/repo
20
+ * @param version Version to download, can be "latest"
21
+ * @param assetsName Assets name pattern (optional, but either assetsName or assetName must be provided)
22
+ * @param assetName Asset name pattern (optional, but either assetsName or assetName must be provided)
23
+ * @param isFolder Whether to treat the downloaded asset as a folder (default: false)
24
+ * @param targetDirectory Target directory for downloads (default: SETTINGS_DIR/downloads)
25
+ * @returns Object containing version and download path
17
26
  */
18
- export async function handleGitHubRelease(
19
- requirement: RequirementConfig,
20
- registry: RegistryConfig['githubRelease']
21
- ): Promise<{ resolvedVersion: string; resolvedPath: string }> {
22
- if (!registry) {
23
- throw new Error('GitHub release registry configuration is required');
27
+ export async function downloadGithubRelease(
28
+ repo: string,
29
+ version: string,
30
+ assetsName?: string,
31
+ assetName?: string,
32
+ isFolder: boolean = false,
33
+ targetDirectory?: string
34
+ ): Promise<DownloadGithubReleaseResult> {
35
+ if (!repo) {
36
+ throw new Error('GitHub repository is required');
24
37
  }
25
38
 
26
- const downloadsDir = path.join(SETTINGS_DIR, 'downloads');
27
- await fs.mkdir(downloadsDir, { recursive: true });
28
- const { repository, assetsName, assetName } = registry;
29
-
30
- if (!repository) {
31
- throw new Error('GitHub repository is required for GitHub release downloads');
39
+ if (!assetsName && !assetName) {
40
+ throw new Error('Either assetsName or assetName must be specified');
32
41
  }
33
42
 
34
- let version = requirement.version;
35
- let resolvedAssetName = assetName || '';
36
- let resolvedAssetsName = assetsName || '';
43
+ const downloadsDir = targetDirectory || path.join(SETTINGS_DIR, 'downloads');
44
+ await fs.mkdir(downloadsDir, { recursive: true });
37
45
 
38
- const { stdout } = await execAsync(`gh release view --repo ${repository} --json tagName --jq .tagName`);
46
+ // Get latest version if needed
47
+ const { stdout } = await execAsync(`gh release view --repo ${repo} --json tagName --jq .tagName`);
39
48
  const latestTag = stdout.trim();
40
49
  let latestVersion = latestTag;
41
50
 
42
51
  const tagWithVPrefix = latestVersion.startsWith('v');
43
52
  if (tagWithVPrefix) latestVersion = latestVersion.substring(1); // Remove 'v' prefix if present
44
- version = version.includes("latest") ? latestVersion : version;
53
+ const resolvedVersion = version.includes("latest") ? latestVersion : version;
54
+
55
+ // Resolve asset names
56
+ let resolvedAssetsName = '';
57
+ let resolvedAssetName = '';
45
58
  if (assetsName) {
46
- resolvedAssetsName = assetsName.replace('${latest}', version).replace('${version}', version);
59
+ resolvedAssetsName = assetsName.replace('${latest}', resolvedVersion).replace('${version}', resolvedVersion);
47
60
  }
48
61
  if (assetName) {
49
- resolvedAssetName = assetName.replace('${latest}', version).replace('${version}', version);
62
+ resolvedAssetName = assetName.replace('${latest}', resolvedVersion).replace('${version}', resolvedVersion);
50
63
  }
51
64
 
52
- Logger.debug(`Downloading ${requirement.name} from GitHub release ${repository} version ${version}`);
53
- Logger.debug(`ResolvedAssetsName} ${resolvedAssetName}; ResolvedAsetName} ${resolvedAssetName}`);
54
- const pattern = resolvedAssetsName ? resolvedAssetsName : resolvedAssetName;
55
- Logger.debug(`Resolved pattern: ${pattern}`);
56
-
57
- if (!pattern) {
58
- throw new Error('Either assetsName or assetName must be specified for GitHub release downloads');
65
+ // Validate zip requirement for isFolder
66
+ const pattern = resolvedAssetsName || resolvedAssetName;
67
+ if (isFolder && (!resolvedAssetsName || !resolvedAssetsName.endsWith('.zip'))) {
68
+ throw new Error('When isFolder is true, assetsName must be provided and end with .zip');
59
69
  }
60
70
 
61
71
  // Download the release asset
62
72
  const downloadPath = path.join(downloadsDir, path.basename(pattern));
63
73
  if (!await fileExists(downloadPath)) {
64
- await execAsync(`gh release download ${tagWithVPrefix ? `v${version}` : version} --repo ${repository} --pattern "${pattern}" -O "${downloadPath}"`);
74
+ await execAsync(`gh release download ${tagWithVPrefix ? `v${resolvedVersion}` : resolvedVersion} --repo ${repo} --pattern "${pattern}" -O "${downloadPath}"`);
65
75
  }
66
76
 
67
- // Handle zip file extraction if the downloaded file is a zip
68
- if (downloadPath.endsWith('.zip')) {
77
+ // Handle zip extraction if needed
78
+ if (isFolder && downloadPath.endsWith('.zip')) {
69
79
  const extractDir = path.join(downloadsDir, path.basename(pattern, '.zip'));
70
80
  await fs.mkdir(extractDir, { recursive: true });
71
-
72
- // Extract the zip file
73
81
  await extractZipFile(downloadPath, { dir: extractDir });
74
- let assetPath = '';
82
+
75
83
  // If resolvedAssetName is specified, look for it in the extracted directory
76
84
  if (resolvedAssetName) {
77
- assetPath = path.join(extractDir, resolvedAssetName);
85
+ const assetPath = path.join(extractDir, resolvedAssetName);
78
86
  try {
79
87
  await fs.access(assetPath);
80
- return { resolvedVersion: version, resolvedPath: assetPath };
88
+ return { version: resolvedVersion, downloadPath: assetPath };
81
89
  } catch (error) {
82
90
  throw new Error(`Asset ${resolvedAssetName} not found in extracted directory ${extractDir}`);
83
91
  }
84
92
  }
85
- else {
86
- assetPath = path.join(extractDir, path.basename(pattern, '.zip') + '.tgz');
87
- }
93
+ return { version: resolvedVersion, downloadPath: extractDir };
94
+ }
95
+
96
+ return { version: resolvedVersion, downloadPath };
97
+ }
98
+
99
+ /**
100
+ * Helper to handle GitHub release downloads
101
+ * @param requirement The requirement configuration
102
+ * @param registry The GitHub release registry configuration
103
+ * @returns The path to the downloaded file
104
+ */
105
+ export async function handleGitHubRelease(
106
+ requirement: RequirementConfig,
107
+ registry: RegistryConfig['githubRelease']
108
+ ): Promise<{ resolvedVersion: string; resolvedPath: string }> {
109
+ if (!registry) {
110
+ throw new Error('GitHub release registry configuration is required');
111
+ }
88
112
 
89
- // If no specific asset is required, return the extraction directory
90
- return { resolvedVersion: version, resolvedPath: extractDir };
113
+ const { repository, assetsName, assetName } = registry;
114
+ if (!repository) {
115
+ throw new Error('GitHub repository is required for GitHub release downloads');
91
116
  }
92
117
 
93
- return { resolvedVersion: version, resolvedPath: downloadPath };
118
+ const isZipAsset = assetsName?.endsWith('.zip') || false;
119
+ const result = await downloadGithubRelease(
120
+ repository,
121
+ requirement.version,
122
+ assetsName,
123
+ assetName,
124
+ isZipAsset
125
+ );
126
+
127
+ return {
128
+ resolvedVersion: result.version,
129
+ resolvedPath: result.downloadPath
130
+ };
94
131
  }
95
132
 
96
133
  async function fileExists(filePath: string): Promise<boolean> {
97
134
  try {
98
- await fs.access(filePath)
99
- return true
135
+ await fs.access(filePath);
136
+ return true;
100
137
  } catch {
101
- return false
138
+ return false;
102
139
  }
103
140
  }
@@ -0,0 +1,235 @@
1
+ .details-widget {
2
+ transition: all 0.3s ease-in-out;
3
+ width: 100%;
4
+ max-width: 100%;
5
+ box-sizing: border-box;
6
+ margin: 0;
7
+ padding: 0;
8
+ display: block;
9
+ overflow: hidden;
10
+ }
11
+
12
+ .tools-list {
13
+ width: 100%;
14
+ max-width: 100%;
15
+ margin: 0;
16
+ padding: 0.1rem;
17
+ box-sizing: border-box;
18
+ overflow: hidden;
19
+ }
20
+
21
+ .tool-card {
22
+ width: 100%;
23
+ margin-bottom: -1px;
24
+ border-radius: 0;
25
+ box-sizing: border-box;
26
+ }
27
+
28
+ .tool-card:first-child {
29
+ border-top-left-radius: 6px;
30
+ border-top-right-radius: 6px;
31
+ }
32
+
33
+ .tool-card:last-child {
34
+ border-bottom-left-radius: 6px;
35
+ border-bottom-right-radius: 6px;
36
+ margin-bottom: 0;
37
+ }
38
+
39
+ .tool-card {
40
+ transition: all 0.3s ease-out;
41
+ border: 1px solid #e5e7eb;
42
+ padding: 0.5rem;
43
+ background-color: white;
44
+ position: relative;
45
+ z-index: 1;
46
+ font-size: 0.9rem;
47
+ width: 100%;
48
+ box-sizing: border-box;
49
+ }
50
+
51
+ .tool-card .text-gray-600 {
52
+ font-size: 0.75rem;
53
+ line-height: 1.3;
54
+ }
55
+
56
+ .tool-card.active {
57
+ box-shadow: 0 4px 15px rgba(0, 0, 0, 0.12);
58
+ border-color: transparent;
59
+ background-color: #f8fafc;
60
+ }
61
+
62
+ .tool-card:hover {
63
+ transform: translateY(-1px);
64
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08);
65
+ }
66
+
67
+ .tool-card-header {
68
+ position: relative;
69
+ padding-right: 2rem;
70
+ width: 100%;
71
+ box-sizing: border-box;
72
+ cursor: pointer;
73
+ }
74
+
75
+ .tool-card-header::after {
76
+ content: '';
77
+ position: absolute;
78
+ right: 1rem;
79
+ top: 50%;
80
+ transform: translateY(-50%);
81
+ width: 12px;
82
+ height: 12px;
83
+ border-right: 2px solid #64748b;
84
+ border-bottom: 2px solid #64748b;
85
+ transform-origin: 75% 75%;
86
+ transition: transform 0.3s ease;
87
+ }
88
+
89
+ .tool-card.active .tool-card-header::after {
90
+ transform: translateY(-50%) rotate(45deg);
91
+ }
92
+
93
+ .tool-details {
94
+ max-height: 0;
95
+ opacity: 0;
96
+ overflow: hidden;
97
+ transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1);
98
+ background-color: #f9fafb;
99
+ border-radius: 6px;
100
+ width: 100%;
101
+ max-width: 100%;
102
+ box-sizing: border-box;
103
+ }
104
+
105
+ .tool-details.visible {
106
+ max-height: 2000px;
107
+ opacity: 1;
108
+ padding: 0.1rem;
109
+ margin-top: 0.5rem;
110
+ border-top: 1px solid #e5e7eb;
111
+ width: 100%;
112
+ max-width: 100%;
113
+ box-sizing: border-box;
114
+ }
115
+
116
+ .property-item {
117
+ margin-bottom: 0.15rem;
118
+ padding: 0.5rem;
119
+ border-left: 2px solid #e5e7eb;
120
+ transition: all 0.2s ease;
121
+ font-size: 0.8rem;
122
+ background-color: white;
123
+ border-radius: 4px;
124
+ }
125
+
126
+ .property-item:hover {
127
+ border-left-color: #3b82f6;
128
+ }
129
+
130
+ .property-header {
131
+ margin-bottom: 0.25rem;
132
+ }
133
+
134
+ .property-title {
135
+ display: flex;
136
+ align-items: center;
137
+ gap: 0.5rem;
138
+ flex-wrap: wrap;
139
+ }
140
+
141
+ .property-name {
142
+ font-weight: 600;
143
+ color: #1e293b;
144
+ font-size: 0.85rem;
145
+ background-color: #f8fafc;
146
+ padding: 0.2rem 0.4rem;
147
+ border-radius: 4px;
148
+ box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);
149
+ display: inline-block;
150
+ }
151
+
152
+ .property-type {
153
+ color: #64748b;
154
+ font-size: 0.65rem;
155
+ padding: 0.1rem 0.3rem;
156
+ background: #f1f5f9;
157
+ border-radius: 3px;
158
+ font-family: 'Courier New', monospace;
159
+ margin-left: 0.3rem;
160
+ }
161
+
162
+ .property-desc {
163
+ color: #6b7280;
164
+ font-size: 0.7rem;
165
+ margin-left: 0.5rem;
166
+ display: inline-block;
167
+ font-style: italic;
168
+ }
169
+
170
+ .property-default {
171
+ font-size: 0.75rem;
172
+ color: #6b7280;
173
+ margin-top: 0.15rem;
174
+ }
175
+
176
+ .property-default code {
177
+ background: #f1f5f9;
178
+ padding: 0.2rem 0.4rem;
179
+ border-radius: 4px;
180
+ font-family: 'Courier New', monospace;
181
+ font-size: 0.85rem;
182
+ }
183
+
184
+ .required-fields {
185
+ border-left: 2px solid #64748b;
186
+ padding: 0.25rem 0.5rem;
187
+ margin-bottom: 0.5rem;
188
+ font-size: 0.8rem;
189
+ border-radius: 2px;
190
+ color: #64748b;
191
+ }
192
+ .required-star {
193
+ color: #dc2626;
194
+ margin-left: 2px;
195
+ font-weight: bold;
196
+ }
197
+
198
+
199
+ /* Ensure proper container behavior for the widget */
200
+ .details-widget-container {
201
+ width: 100%;
202
+ max-width: 100%;
203
+ box-sizing: border-box;
204
+ position: relative;
205
+ margin: 0;
206
+ padding: 0;
207
+ overflow: hidden;
208
+ }
209
+
210
+ .nested-properties {
211
+ margin: 0.25rem 0 0.25rem 0.5rem;
212
+ padding-left: 0.5rem;
213
+ border-left: 1px solid #e5e7eb;
214
+ font-size: 0.8rem;
215
+ }
216
+
217
+ .nested-property-item {
218
+ margin-bottom: 0.5rem;
219
+ padding: 0.25rem 0.5rem;
220
+ }
221
+
222
+ .nested-property-item .property-name {
223
+ font-size: 0.85rem;
224
+ }
225
+
226
+ .nested-property-item .property-type {
227
+ font-size: 0.8rem;
228
+ padding: 0.1rem 0.3rem;
229
+ }
230
+
231
+ .nested-property-item .property-desc {
232
+ font-size: 0.8rem;
233
+ margin-top: 0.25rem;
234
+ color: #64748b;
235
+ }