@elliotding/ai-agent-mcp 0.1.25 → 0.1.26

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 (237) hide show
  1. package/package.json +4 -1
  2. package/.prompt-cache/cmd-cmd-client-sdk-ai-hub-generate-testcase.md +0 -101
  3. package/.prompt-cache/cmd-cmd-client-sdk-ai-hub-submit_zct_job.md +0 -158
  4. package/.prompt-cache/skill-skill-client-sdk-ai-hub-analyze-conf-status.md +0 -311
  5. package/.prompt-cache/skill-skill-client-sdk-ai-hub-analyze-sdk-log.md +0 -64
  6. package/.prompt-cache/skill-skill-client-sdk-ai-hub-analyze-zmb-log-errors.md +0 -84
  7. package/ai-resource-telemetry.json +0 -40
  8. package/dist/api/cached-client.d.ts +0 -48
  9. package/dist/api/cached-client.d.ts.map +0 -1
  10. package/dist/api/cached-client.js +0 -126
  11. package/dist/api/cached-client.js.map +0 -1
  12. package/dist/api/client.d.ts +0 -281
  13. package/dist/api/client.d.ts.map +0 -1
  14. package/dist/api/client.js +0 -371
  15. package/dist/api/client.js.map +0 -1
  16. package/dist/auth/index.d.ts +0 -8
  17. package/dist/auth/index.d.ts.map +0 -1
  18. package/dist/auth/index.js +0 -26
  19. package/dist/auth/index.js.map +0 -1
  20. package/dist/auth/middleware.d.ts +0 -36
  21. package/dist/auth/middleware.d.ts.map +0 -1
  22. package/dist/auth/middleware.js +0 -194
  23. package/dist/auth/middleware.js.map +0 -1
  24. package/dist/auth/permissions.d.ts +0 -60
  25. package/dist/auth/permissions.d.ts.map +0 -1
  26. package/dist/auth/permissions.js +0 -262
  27. package/dist/auth/permissions.js.map +0 -1
  28. package/dist/auth/token-validator.d.ts +0 -52
  29. package/dist/auth/token-validator.d.ts.map +0 -1
  30. package/dist/auth/token-validator.js +0 -215
  31. package/dist/auth/token-validator.js.map +0 -1
  32. package/dist/cache/cache-manager.d.ts +0 -49
  33. package/dist/cache/cache-manager.d.ts.map +0 -1
  34. package/dist/cache/cache-manager.js +0 -191
  35. package/dist/cache/cache-manager.js.map +0 -1
  36. package/dist/cache/index.d.ts +0 -6
  37. package/dist/cache/index.d.ts.map +0 -1
  38. package/dist/cache/index.js +0 -12
  39. package/dist/cache/index.js.map +0 -1
  40. package/dist/cache/redis-client.d.ts +0 -45
  41. package/dist/cache/redis-client.d.ts.map +0 -1
  42. package/dist/cache/redis-client.js +0 -210
  43. package/dist/cache/redis-client.js.map +0 -1
  44. package/dist/config/constants.d.ts +0 -28
  45. package/dist/config/constants.d.ts.map +0 -1
  46. package/dist/config/constants.js +0 -31
  47. package/dist/config/constants.js.map +0 -1
  48. package/dist/config/index.d.ts +0 -71
  49. package/dist/config/index.d.ts.map +0 -1
  50. package/dist/config/index.js +0 -190
  51. package/dist/config/index.js.map +0 -1
  52. package/dist/filesystem/manager.d.ts +0 -45
  53. package/dist/filesystem/manager.d.ts.map +0 -1
  54. package/dist/filesystem/manager.js +0 -246
  55. package/dist/filesystem/manager.js.map +0 -1
  56. package/dist/git/multi-source-manager.d.ts +0 -78
  57. package/dist/git/multi-source-manager.d.ts.map +0 -1
  58. package/dist/git/multi-source-manager.js +0 -577
  59. package/dist/git/multi-source-manager.js.map +0 -1
  60. package/dist/git/operations.d.ts +0 -27
  61. package/dist/git/operations.d.ts.map +0 -1
  62. package/dist/git/operations.js +0 -83
  63. package/dist/git/operations.js.map +0 -1
  64. package/dist/index.d.ts +0 -6
  65. package/dist/index.d.ts.map +0 -1
  66. package/dist/index.js +0 -122
  67. package/dist/index.js.map +0 -1
  68. package/dist/monitoring/health.d.ts +0 -35
  69. package/dist/monitoring/health.d.ts.map +0 -1
  70. package/dist/monitoring/health.js +0 -105
  71. package/dist/monitoring/health.js.map +0 -1
  72. package/dist/prompts/cache.d.ts +0 -69
  73. package/dist/prompts/cache.d.ts.map +0 -1
  74. package/dist/prompts/cache.js +0 -163
  75. package/dist/prompts/cache.js.map +0 -1
  76. package/dist/prompts/generator.d.ts +0 -49
  77. package/dist/prompts/generator.d.ts.map +0 -1
  78. package/dist/prompts/generator.js +0 -160
  79. package/dist/prompts/generator.js.map +0 -1
  80. package/dist/prompts/index.d.ts +0 -13
  81. package/dist/prompts/index.d.ts.map +0 -1
  82. package/dist/prompts/index.js +0 -24
  83. package/dist/prompts/index.js.map +0 -1
  84. package/dist/prompts/manager.d.ts +0 -207
  85. package/dist/prompts/manager.d.ts.map +0 -1
  86. package/dist/prompts/manager.js +0 -566
  87. package/dist/prompts/manager.js.map +0 -1
  88. package/dist/resources/index.d.ts +0 -6
  89. package/dist/resources/index.d.ts.map +0 -1
  90. package/dist/resources/index.js +0 -10
  91. package/dist/resources/index.js.map +0 -1
  92. package/dist/resources/loader.d.ts +0 -88
  93. package/dist/resources/loader.d.ts.map +0 -1
  94. package/dist/resources/loader.js +0 -492
  95. package/dist/resources/loader.js.map +0 -1
  96. package/dist/server/http.d.ts +0 -57
  97. package/dist/server/http.d.ts.map +0 -1
  98. package/dist/server/http.js +0 -435
  99. package/dist/server/http.js.map +0 -1
  100. package/dist/server.d.ts +0 -13
  101. package/dist/server.d.ts.map +0 -1
  102. package/dist/server.js +0 -201
  103. package/dist/server.js.map +0 -1
  104. package/dist/session/manager.d.ts +0 -91
  105. package/dist/session/manager.d.ts.map +0 -1
  106. package/dist/session/manager.js +0 -251
  107. package/dist/session/manager.js.map +0 -1
  108. package/dist/telemetry/index.d.ts +0 -3
  109. package/dist/telemetry/index.d.ts.map +0 -1
  110. package/dist/telemetry/index.js +0 -7
  111. package/dist/telemetry/index.js.map +0 -1
  112. package/dist/telemetry/manager.d.ts +0 -151
  113. package/dist/telemetry/manager.d.ts.map +0 -1
  114. package/dist/telemetry/manager.js +0 -367
  115. package/dist/telemetry/manager.js.map +0 -1
  116. package/dist/tools/index.d.ts +0 -13
  117. package/dist/tools/index.d.ts.map +0 -1
  118. package/dist/tools/index.js +0 -29
  119. package/dist/tools/index.js.map +0 -1
  120. package/dist/tools/manage-subscription.d.ts +0 -47
  121. package/dist/tools/manage-subscription.d.ts.map +0 -1
  122. package/dist/tools/manage-subscription.js +0 -317
  123. package/dist/tools/manage-subscription.js.map +0 -1
  124. package/dist/tools/registry.d.ts +0 -40
  125. package/dist/tools/registry.d.ts.map +0 -1
  126. package/dist/tools/registry.js +0 -85
  127. package/dist/tools/registry.js.map +0 -1
  128. package/dist/tools/resolve-prompt-content.d.ts +0 -35
  129. package/dist/tools/resolve-prompt-content.d.ts.map +0 -1
  130. package/dist/tools/resolve-prompt-content.js +0 -99
  131. package/dist/tools/resolve-prompt-content.js.map +0 -1
  132. package/dist/tools/search-resources.d.ts +0 -35
  133. package/dist/tools/search-resources.d.ts.map +0 -1
  134. package/dist/tools/search-resources.js +0 -159
  135. package/dist/tools/search-resources.js.map +0 -1
  136. package/dist/tools/sync-resources.d.ts +0 -54
  137. package/dist/tools/sync-resources.d.ts.map +0 -1
  138. package/dist/tools/sync-resources.js +0 -735
  139. package/dist/tools/sync-resources.js.map +0 -1
  140. package/dist/tools/track-usage.d.ts +0 -63
  141. package/dist/tools/track-usage.d.ts.map +0 -1
  142. package/dist/tools/track-usage.js +0 -90
  143. package/dist/tools/track-usage.js.map +0 -1
  144. package/dist/tools/uninstall-resource.d.ts +0 -30
  145. package/dist/tools/uninstall-resource.d.ts.map +0 -1
  146. package/dist/tools/uninstall-resource.js +0 -174
  147. package/dist/tools/uninstall-resource.js.map +0 -1
  148. package/dist/tools/upload-resource.d.ts +0 -81
  149. package/dist/tools/upload-resource.d.ts.map +0 -1
  150. package/dist/tools/upload-resource.js +0 -393
  151. package/dist/tools/upload-resource.js.map +0 -1
  152. package/dist/transport/sse.d.ts +0 -29
  153. package/dist/transport/sse.d.ts.map +0 -1
  154. package/dist/transport/sse.js +0 -271
  155. package/dist/transport/sse.js.map +0 -1
  156. package/dist/types/errors.d.ts +0 -60
  157. package/dist/types/errors.d.ts.map +0 -1
  158. package/dist/types/errors.js +0 -112
  159. package/dist/types/errors.js.map +0 -1
  160. package/dist/types/index.d.ts +0 -7
  161. package/dist/types/index.d.ts.map +0 -1
  162. package/dist/types/index.js +0 -23
  163. package/dist/types/index.js.map +0 -1
  164. package/dist/types/mcp.d.ts +0 -50
  165. package/dist/types/mcp.d.ts.map +0 -1
  166. package/dist/types/mcp.js +0 -6
  167. package/dist/types/mcp.js.map +0 -1
  168. package/dist/types/resources.d.ts +0 -109
  169. package/dist/types/resources.d.ts.map +0 -1
  170. package/dist/types/resources.js +0 -7
  171. package/dist/types/resources.js.map +0 -1
  172. package/dist/types/tools.d.ts +0 -253
  173. package/dist/types/tools.d.ts.map +0 -1
  174. package/dist/types/tools.js +0 -6
  175. package/dist/types/tools.js.map +0 -1
  176. package/dist/utils/cursor-paths.d.ts +0 -84
  177. package/dist/utils/cursor-paths.d.ts.map +0 -1
  178. package/dist/utils/cursor-paths.js +0 -166
  179. package/dist/utils/cursor-paths.js.map +0 -1
  180. package/dist/utils/log-cleaner.d.ts +0 -18
  181. package/dist/utils/log-cleaner.d.ts.map +0 -1
  182. package/dist/utils/log-cleaner.js +0 -112
  183. package/dist/utils/log-cleaner.js.map +0 -1
  184. package/dist/utils/logger.d.ts +0 -59
  185. package/dist/utils/logger.d.ts.map +0 -1
  186. package/dist/utils/logger.js +0 -292
  187. package/dist/utils/logger.js.map +0 -1
  188. package/dist/utils/validation.d.ts +0 -58
  189. package/dist/utils/validation.d.ts.map +0 -1
  190. package/dist/utils/validation.js +0 -214
  191. package/dist/utils/validation.js.map +0 -1
  192. package/src/api/cached-client.ts +0 -144
  193. package/src/api/client.ts +0 -697
  194. package/src/auth/index.ts +0 -11
  195. package/src/auth/middleware.ts +0 -244
  196. package/src/auth/permissions.ts +0 -323
  197. package/src/auth/token-validator.ts +0 -292
  198. package/src/cache/cache-manager.ts +0 -243
  199. package/src/cache/index.ts +0 -6
  200. package/src/cache/redis-client.ts +0 -249
  201. package/src/config/constants.ts +0 -33
  202. package/src/config/index.ts +0 -269
  203. package/src/filesystem/manager.ts +0 -235
  204. package/src/git/multi-source-manager.ts +0 -654
  205. package/src/git/operations.ts +0 -93
  206. package/src/index.ts +0 -157
  207. package/src/monitoring/health.ts +0 -132
  208. package/src/prompts/cache.ts +0 -140
  209. package/src/prompts/generator.ts +0 -143
  210. package/src/prompts/index.ts +0 -20
  211. package/src/prompts/manager.ts +0 -718
  212. package/src/resources/index.ts +0 -13
  213. package/src/resources/loader.ts +0 -563
  214. package/src/server/http.ts +0 -549
  215. package/src/server.ts +0 -206
  216. package/src/session/manager.ts +0 -296
  217. package/src/telemetry/index.ts +0 -10
  218. package/src/telemetry/manager.ts +0 -419
  219. package/src/tools/index.ts +0 -13
  220. package/src/tools/manage-subscription.ts +0 -388
  221. package/src/tools/registry.ts +0 -97
  222. package/src/tools/resolve-prompt-content.ts +0 -113
  223. package/src/tools/search-resources.ts +0 -185
  224. package/src/tools/sync-resources.ts +0 -829
  225. package/src/tools/track-usage.ts +0 -113
  226. package/src/tools/uninstall-resource.ts +0 -199
  227. package/src/tools/upload-resource.ts +0 -431
  228. package/src/transport/sse.ts +0 -308
  229. package/src/types/errors.ts +0 -146
  230. package/src/types/index.ts +0 -7
  231. package/src/types/mcp.ts +0 -61
  232. package/src/types/resources.ts +0 -141
  233. package/src/types/tools.ts +0 -305
  234. package/src/utils/cursor-paths.ts +0 -135
  235. package/src/utils/log-cleaner.ts +0 -92
  236. package/src/utils/logger.ts +0 -333
  237. package/src/utils/validation.ts +0 -262
@@ -1,13 +0,0 @@
1
- /**
2
- * Resources Module Exports
3
- */
4
-
5
- export { ResourceLoader, resourceLoader } from './loader';
6
- export type {
7
- AIResourcesConfig,
8
- ResourceMetadata,
9
- ResourceType,
10
- ResourceSource,
11
- ResourceConflict,
12
- LoaderStats,
13
- } from '../types/resources';
@@ -1,563 +0,0 @@
1
- /**
2
- * Resource Loader Module
3
- * Manages multi-source AI resource loading and indexing
4
- *
5
- * Implements AI Resources Multi-Source Architecture as defined in:
6
- * @see Docs/AI-Resources-Multi-Source-Architecture.md
7
- * @see AGENTS.md (AI Resources 开发约束)
8
- */
9
-
10
- import * as fs from 'fs/promises';
11
- import * as path from 'path';
12
- import { logger } from '../utils/logger';
13
- import type {
14
- AIResourcesConfig,
15
- ResourceMetadata,
16
- ResourceType,
17
- ResourceConflict,
18
- LoaderStats,
19
- } from '../types/resources';
20
-
21
- /**
22
- * Resource Loader
23
- *
24
- * Key responsibilities:
25
- * 1. Load and validate ai-resources-config.json
26
- * 2. Scan resources from multiple sources
27
- * 3. Build resource index with priority
28
- * 4. Resolve resource name conflicts
29
- * 5. Provide unified resource query interface
30
- */
31
- export class ResourceLoader {
32
- private config: AIResourcesConfig | null = null;
33
- private configFilePath: string | null = null;
34
- private resourceIndex: Map<string, ResourceMetadata> = new Map();
35
- private conflicts: ResourceConflict[] = [];
36
- private stats: LoaderStats | null = null;
37
- private loaded: boolean = false;
38
-
39
- // Cache configuration
40
- private cacheEnabled: boolean = false;
41
- private cacheTTL: number = 300000; // 5 minutes default
42
- private lastLoadTime: number = 0;
43
-
44
- /**
45
- * Load AI Resources configuration file
46
- */
47
- async loadConfig(configPath?: string): Promise<void> {
48
- const startTime = Date.now();
49
-
50
- try {
51
- // Resolve the AI-Resources base directory using the same probing strategy
52
- // as multi-source-manager so both modules agree on the path regardless of
53
- // whether the server runs locally (cwd = SourceCode/) or in Docker (cwd = /app).
54
- let defaultPath = path.resolve(process.cwd(), 'AI-Resources/ai-resources-config.json');
55
- if (!configPath) {
56
- const candidates = [
57
- path.resolve(process.env['AI_RESOURCES_PATH'] ?? '', 'ai-resources-config.json'),
58
- path.resolve(process.cwd(), 'AI-Resources/ai-resources-config.json'),
59
- path.resolve(process.cwd(), '../AI-Resources/ai-resources-config.json'),
60
- path.resolve(__dirname, '../../AI-Resources/ai-resources-config.json'),
61
- ].filter(Boolean);
62
- for (const c of candidates) {
63
- try { await fs.access(c); defaultPath = c; break; } catch { /* try next */ }
64
- }
65
- }
66
- const finalPath = configPath || defaultPath;
67
-
68
- logger.debug({ configPath: finalPath }, 'Loading AI Resources configuration...');
69
-
70
- // Check if config file exists
71
- try {
72
- await fs.access(finalPath);
73
- } catch {
74
- logger.warn({ configPath: finalPath }, 'AI Resources config file not found, using default configuration');
75
- this.config = this.getDefaultConfig();
76
- return;
77
- }
78
-
79
- // Read and parse config file
80
- const configContent = await fs.readFile(finalPath, 'utf-8');
81
- const parsedConfig = JSON.parse(configContent) as AIResourcesConfig;
82
-
83
- // Validate configuration
84
- this.validateConfig(parsedConfig);
85
-
86
- this.config = parsedConfig;
87
- this.configFilePath = finalPath;
88
- this.cacheEnabled = parsedConfig.cache?.enabled ?? true;
89
- this.cacheTTL = (parsedConfig.cache?.ttl ?? 300) * 1000; // Convert to ms
90
-
91
- const duration = Date.now() - startTime;
92
- logger.info(
93
- {
94
- configPath: finalPath,
95
- defaultSource: parsedConfig.default_source.name,
96
- extendedSourcesCount: parsedConfig.extended_sources.length,
97
- duration,
98
- },
99
- 'AI Resources configuration loaded successfully'
100
- );
101
- } catch (error) {
102
- logger.error(
103
- {
104
- type: 'resource',
105
- operation: 'load_config',
106
- error: error instanceof Error ? error.message : String(error),
107
- stack: error instanceof Error ? error.stack : undefined,
108
- },
109
- 'Failed to load AI Resources configuration'
110
- );
111
- throw error;
112
- }
113
- }
114
-
115
- /**
116
- * Validate configuration structure and constraints
117
- */
118
- private validateConfig(config: AIResourcesConfig): void {
119
- // Check version
120
- if (config.version !== '1.0') {
121
- throw new Error(`Unsupported config version: ${config.version}, expected 1.0`);
122
- }
123
-
124
- // Check default source
125
- if (!config.default_source) {
126
- throw new Error('Missing required field: default_source');
127
- }
128
-
129
- if (config.default_source.name !== 'csp') {
130
- logger.warn(
131
- { name: config.default_source.name },
132
- 'Default source name is not "csp", this may cause compatibility issues'
133
- );
134
- }
135
-
136
- if (config.default_source.priority !== 100) {
137
- logger.warn(
138
- { priority: config.default_source.priority },
139
- 'Default source priority is not 100, this may affect conflict resolution'
140
- );
141
- }
142
-
143
- if (!config.default_source.enabled) {
144
- throw new Error('Default source must be enabled');
145
- }
146
-
147
- // Check resource types
148
- const expectedTypes: ResourceType[] = ['commands', 'skills', 'mcp', 'rules'];
149
- if (
150
- !config.resource_types ||
151
- config.resource_types.length !== 4 ||
152
- !expectedTypes.every((type) => config.resource_types.includes(type))
153
- ) {
154
- throw new Error(
155
- `Invalid resource_types, expected: ${expectedTypes.join(', ')}, got: ${config.resource_types?.join(', ') || 'none'}`
156
- );
157
- }
158
-
159
- // Check loading order
160
- if (config.loading_order !== 'priority_desc') {
161
- logger.warn(
162
- { loading_order: config.loading_order },
163
- 'Loading order is not "priority_desc", this may affect resource precedence'
164
- );
165
- }
166
-
167
- // Check conflict resolution
168
- if (config.conflict_resolution !== 'highest_priority_wins') {
169
- logger.warn(
170
- { conflict_resolution: config.conflict_resolution },
171
- 'Conflict resolution is not "highest_priority_wins", this may affect behavior'
172
- );
173
- }
174
-
175
- // Check extended sources priority
176
- const defaultPriority = config.default_source.priority;
177
- for (const source of config.extended_sources || []) {
178
- if (source.priority >= defaultPriority) {
179
- throw new Error(
180
- `Extended source "${source.name}" has priority ${source.priority} which is >= default source priority ${defaultPriority}. Default source must have highest priority.`
181
- );
182
- }
183
- }
184
-
185
- logger.debug('AI Resources configuration validation passed');
186
- }
187
-
188
- /**
189
- * Get default configuration (fallback)
190
- */
191
- private getDefaultConfig(): AIResourcesConfig {
192
- return {
193
- version: '1.0',
194
- description: 'Default AI Resources configuration',
195
- default_source: {
196
- name: 'csp',
197
- path: 'csp/ai-resources',
198
- enabled: true,
199
- priority: 100,
200
- resources: {
201
- commands: 'commands',
202
- skills: 'skills',
203
- mcp: 'mcp',
204
- rules: 'rules',
205
- },
206
- },
207
- extended_sources: [],
208
- resource_types: ['commands', 'skills', 'mcp', 'rules'],
209
- loading_order: 'priority_desc',
210
- conflict_resolution: 'highest_priority_wins',
211
- cache: {
212
- enabled: true,
213
- ttl: 300,
214
- },
215
- };
216
- }
217
-
218
- /**
219
- * Scan and index resources from all sources
220
- */
221
- async scanResources(): Promise<void> {
222
- if (!this.config) {
223
- throw new Error('Configuration not loaded. Call loadConfig() first.');
224
- }
225
-
226
- // Check cache validity
227
- if (this.cacheEnabled && this.loaded) {
228
- const cacheAge = Date.now() - this.lastLoadTime;
229
- if (cacheAge < this.cacheTTL) {
230
- logger.debug({ cacheAge }, 'Using cached resource index');
231
- return;
232
- }
233
- logger.debug('Resource cache expired, reloading...');
234
- }
235
-
236
- const startTime = Date.now();
237
- this.resourceIndex.clear();
238
- this.conflicts = [];
239
-
240
- const stats = {
241
- sourcesLoaded: 0,
242
- resourcesIndexed: 0,
243
- byType: {
244
- commands: 0,
245
- skills: 0,
246
- mcp: 0,
247
- rules: 0,
248
- } as Record<ResourceType, number>,
249
- conflictsDetected: 0,
250
- loadDuration: 0,
251
- };
252
-
253
- try {
254
- // Collect all sources sorted by priority (descending)
255
- const sources = [this.config.default_source, ...this.config.extended_sources.filter((s) => s.enabled)];
256
- sources.sort((a, b) => b.priority - a.priority); // High priority first
257
-
258
- logger.info({ sourceCount: sources.length }, 'Scanning resources from all sources...');
259
-
260
- // Scan each source
261
- for (const source of sources) {
262
- await this.scanSource(source, stats);
263
- stats.sourcesLoaded++;
264
- }
265
-
266
- stats.conflictsDetected = this.conflicts.length;
267
- stats.loadDuration = Date.now() - startTime;
268
- this.stats = stats;
269
- this.loaded = true;
270
- this.lastLoadTime = Date.now();
271
-
272
- logger.info(
273
- {
274
- sourcesLoaded: stats.sourcesLoaded,
275
- resourcesIndexed: stats.resourcesIndexed,
276
- conflictsDetected: stats.conflictsDetected,
277
- duration: stats.loadDuration,
278
- },
279
- 'Resource scanning completed'
280
- );
281
- } catch (error) {
282
- logger.error(
283
- {
284
- type: 'resource',
285
- operation: 'scan_resources',
286
- error: error instanceof Error ? error.message : String(error),
287
- },
288
- 'Failed to scan resources'
289
- );
290
- throw error;
291
- }
292
- }
293
-
294
- /**
295
- * Scan resources from a single source
296
- */
297
- private async scanSource(
298
- source: AIResourcesConfig['default_source'],
299
- stats: LoaderStats
300
- ): Promise<void> {
301
- logger.debug({ source: source.name, path: source.path }, 'Scanning source...');
302
-
303
- // Derive the AI-Resources base from the config file path stored on the instance,
304
- // so the source subdirectory resolves correctly in Docker (cwd = /app) and local
305
- // dev (cwd = SourceCode/) without needing a separate env var.
306
- const aiResourcesBase = this.configFilePath
307
- ? path.dirname(this.configFilePath)
308
- : path.resolve(process.cwd(), 'AI-Resources');
309
- const baseDir = path.resolve(aiResourcesBase, source.path);
310
-
311
- // Check if source directory exists
312
- try {
313
- await fs.access(baseDir);
314
- } catch {
315
- logger.warn({ source: source.name, path: baseDir }, 'Source directory not found, skipping');
316
- return;
317
- }
318
-
319
- // Scan each resource type
320
- for (const type of this.config!.resource_types) {
321
- const subDir = source.resources[type];
322
- if (!subDir) {
323
- logger.debug({ source: source.name, type }, 'Resource type not defined for this source, skipping');
324
- continue;
325
- }
326
-
327
- const resourceDir = path.join(baseDir, subDir);
328
-
329
- try {
330
- await fs.access(resourceDir);
331
- } catch {
332
- logger.debug({ source: source.name, type, path: resourceDir }, 'Resource directory not found, skipping');
333
- continue;
334
- }
335
-
336
- await this.scanResourceType(source, type, resourceDir, stats);
337
- }
338
- }
339
-
340
- /**
341
- * Scan resources of a specific type from a directory
342
- */
343
- private async scanResourceType(
344
- source: AIResourcesConfig['default_source'],
345
- type: ResourceType,
346
- dir: string,
347
- stats: LoaderStats
348
- ): Promise<void> {
349
- try {
350
- const entries = await fs.readdir(dir, { withFileTypes: true });
351
-
352
- for (const entry of entries) {
353
- const fullPath = path.join(dir, entry.name);
354
-
355
- if (entry.isFile() && (entry.name.endsWith('.md') || entry.name.endsWith('.mdc'))) {
356
- // Handle file resources (commands, rules)
357
- const resourceName = path.basename(entry.name, path.extname(entry.name));
358
- await this.indexResource(source, type, resourceName, fullPath, stats);
359
- } else if (entry.isDirectory()) {
360
- // Handle directory resources:
361
- // skill → must contain SKILL.md
362
- // mcp → must contain mcp-config.json
363
- // others → directory layout is not expected; skip
364
- if (type === 'skills') {
365
- const skillFile = path.join(fullPath, 'SKILL.md');
366
- try {
367
- await fs.access(skillFile);
368
- await this.indexResource(source, type, entry.name, skillFile, stats);
369
- } catch {
370
- logger.debug({ dir: fullPath }, 'Directory does not contain SKILL.md, skipping');
371
- }
372
- } else if (type === 'mcp') {
373
- const mcpConfigFile = path.join(fullPath, 'mcp-config.json');
374
- try {
375
- await fs.access(mcpConfigFile);
376
- await this.indexResource(source, type, entry.name, mcpConfigFile, stats);
377
- } catch {
378
- logger.debug({ dir: fullPath }, 'Directory does not contain mcp-config.json, skipping');
379
- }
380
- }
381
- }
382
- }
383
- } catch (error) {
384
- logger.warn(
385
- {
386
- source: source.name,
387
- type,
388
- dir,
389
- error: error instanceof Error ? error.message : String(error),
390
- },
391
- 'Failed to scan resource directory'
392
- );
393
- }
394
- }
395
-
396
- /**
397
- * Index a single resource with conflict detection
398
- */
399
- private async indexResource(
400
- source: AIResourcesConfig['default_source'],
401
- type: ResourceType,
402
- name: string,
403
- filePath: string,
404
- stats: LoaderStats
405
- ): Promise<void> {
406
- const resourceKey = `${type}:${name}`;
407
- const existing = this.resourceIndex.get(resourceKey);
408
-
409
- if (existing) {
410
- // Conflict detected
411
- logger.warn(
412
- {
413
- resourceName: name,
414
- type,
415
- existingSource: existing.source,
416
- existingPriority: existing.priority,
417
- newSource: source.name,
418
- newPriority: source.priority,
419
- },
420
- 'Resource name conflict detected'
421
- );
422
-
423
- // Record conflict
424
- const conflict: ResourceConflict = {
425
- name,
426
- type,
427
- conflicts: [
428
- {
429
- source: existing.source,
430
- priority: existing.priority,
431
- path: existing.path,
432
- },
433
- {
434
- source: source.name,
435
- priority: source.priority,
436
- path: filePath,
437
- },
438
- ],
439
- selected:
440
- source.priority > existing.priority
441
- ? { source: source.name, priority: source.priority, path: filePath }
442
- : { source: existing.source, priority: existing.priority, path: existing.path },
443
- };
444
-
445
- this.conflicts.push(conflict);
446
-
447
- // Keep higher priority resource (already sorted by priority)
448
- if (source.priority <= existing.priority) {
449
- logger.debug(
450
- {
451
- resourceName: name,
452
- selectedSource: existing.source,
453
- },
454
- 'Keeping existing resource (higher priority)'
455
- );
456
- return;
457
- }
458
-
459
- logger.debug(
460
- {
461
- resourceName: name,
462
- selectedSource: source.name,
463
- },
464
- 'Replacing with new resource (higher priority)'
465
- );
466
- }
467
-
468
- // Index resource
469
- const metadata: ResourceMetadata = {
470
- id: resourceKey,
471
- name,
472
- type,
473
- source: source.name,
474
- priority: source.priority,
475
- path: filePath,
476
- };
477
-
478
- this.resourceIndex.set(resourceKey, metadata);
479
- stats.resourcesIndexed++;
480
- stats.byType[type]++;
481
-
482
- logger.debug({ resourceKey, source: source.name }, 'Resource indexed');
483
- }
484
-
485
- /**
486
- * Get all resources by type
487
- */
488
- getResourcesByType(type: ResourceType): ResourceMetadata[] {
489
- this.ensureLoaded();
490
- const results: ResourceMetadata[] = [];
491
- for (const [, metadata] of this.resourceIndex) {
492
- if (metadata.type === type) {
493
- results.push(metadata);
494
- }
495
- }
496
- return results;
497
- }
498
-
499
- /**
500
- * Get resource by ID (type:name)
501
- */
502
- getResourceById(id: string): ResourceMetadata | null {
503
- this.ensureLoaded();
504
- return this.resourceIndex.get(id) || null;
505
- }
506
-
507
- /**
508
- * Search resources by name
509
- */
510
- searchResourcesByName(name: string, type?: ResourceType): ResourceMetadata[] {
511
- this.ensureLoaded();
512
- const results: ResourceMetadata[] = [];
513
- const lowerName = name.toLowerCase();
514
-
515
- for (const metadata of this.resourceIndex.values()) {
516
- if (type && metadata.type !== type) {
517
- continue;
518
- }
519
- if (metadata.name.toLowerCase().includes(lowerName)) {
520
- results.push(metadata);
521
- }
522
- }
523
-
524
- return results;
525
- }
526
-
527
- /**
528
- * Get all detected conflicts
529
- */
530
- getConflicts(): ResourceConflict[] {
531
- this.ensureLoaded();
532
- return [...this.conflicts];
533
- }
534
-
535
- /**
536
- * Get loader statistics
537
- */
538
- getStats(): LoaderStats | null {
539
- return this.stats;
540
- }
541
-
542
- /**
543
- * Refresh resource index (clear cache and rescan)
544
- */
545
- async refresh(): Promise<void> {
546
- logger.info('Refreshing resource index...');
547
- this.loaded = false;
548
- this.lastLoadTime = 0;
549
- await this.scanResources();
550
- }
551
-
552
- /**
553
- * Ensure resources are loaded
554
- */
555
- private ensureLoaded(): void {
556
- if (!this.loaded) {
557
- throw new Error('Resources not loaded. Call scanResources() first.');
558
- }
559
- }
560
- }
561
-
562
- // Singleton instance
563
- export const resourceLoader = new ResourceLoader();