@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,654 +0,0 @@
1
- /**
2
- * Multi-Source Git Manager
3
- * Manages multiple AI Resources Git repositories efficiently
4
- */
5
-
6
- import * as fs from 'fs/promises';
7
- import * as path from 'path';
8
- import simpleGit from 'simple-git';
9
- import { logger } from '../utils/logger';
10
- import { createGitError } from '../types/errors';
11
-
12
- interface AIResourcesConfig {
13
- version: string;
14
- default_source: SourceConfig;
15
- extended_sources: SourceConfig[];
16
- }
17
-
18
- interface SourceConfig {
19
- name: string;
20
- path: string;
21
- enabled: boolean;
22
- priority: number;
23
- git_url?: string; // Git repository URL
24
- git_branch?: string; // Git branch (default: main)
25
- resources: {
26
- commands: string;
27
- skills: string;
28
- mcp: string;
29
- rules: string;
30
- };
31
- description: string;
32
- }
33
-
34
- interface SyncResult {
35
- source: string;
36
- action: 'cloned' | 'pulled' | 'up-to-date' | 'skipped';
37
- changes: number;
38
- duration: number;
39
- }
40
-
41
- class MultiSourceGitManager {
42
- private configPath: string;
43
- private baseDir: string;
44
-
45
- constructor(baseDir: string) {
46
- this.baseDir = baseDir;
47
- this.configPath = path.join(baseDir, 'ai-resources-config.json');
48
- }
49
-
50
- /**
51
- * Load AI Resources configuration
52
- */
53
- private async loadConfig(): Promise<AIResourcesConfig> {
54
- try {
55
- const configContent = await fs.readFile(this.configPath, 'utf-8');
56
- return JSON.parse(configContent);
57
- } catch (error) {
58
- throw new Error(`Failed to load AI Resources config: ${error instanceof Error ? error.message : String(error)}`);
59
- }
60
- }
61
-
62
- /**
63
- * Get all enabled sources (including default)
64
- */
65
- private async getEnabledSources(): Promise<SourceConfig[]> {
66
- const config = await this.loadConfig();
67
- const sources: SourceConfig[] = [];
68
-
69
- if (config.default_source.enabled) {
70
- sources.push(config.default_source);
71
- }
72
-
73
- if (config.extended_sources) {
74
- sources.push(...config.extended_sources.filter(s => s.enabled));
75
- }
76
-
77
- return sources;
78
- }
79
-
80
- /**
81
- * Check if a Git repository exists at the given path
82
- */
83
- private async repositoryExists(repoPath: string): Promise<boolean> {
84
- try {
85
- const gitDir = path.join(repoPath, '.git');
86
- const stats = await fs.stat(gitDir);
87
- return stats.isDirectory();
88
- } catch {
89
- return false;
90
- }
91
- }
92
-
93
- /**
94
- * Get Git repository URL from existing repo
95
- */
96
- private async getRepoUrl(repoPath: string): Promise<string | null> {
97
- try {
98
- const git = simpleGit(repoPath);
99
- const remotes = await git.getRemotes(true);
100
- const origin = remotes.find(r => r.name === 'origin');
101
- return origin?.refs.fetch || null;
102
- } catch {
103
- return null;
104
- }
105
- }
106
-
107
- /**
108
- * Clone a Git repository
109
- */
110
- private async cloneRepository(repoUrl: string, targetPath: string, branch: string = 'main'): Promise<void> {
111
- try {
112
- logger.info({ repoUrl, targetPath, branch }, 'Cloning Git repository...');
113
-
114
- // Ensure parent directory exists
115
- const parentDir = path.dirname(targetPath);
116
- await fs.mkdir(parentDir, { recursive: true });
117
-
118
- const git = simpleGit();
119
-
120
- // Clone with --single-branch to limit downloaded history to the target
121
- // branch only, keeping the clone fast without creating a shallow repo
122
- // (shallow repos cause "no merge base" errors on subsequent fetches).
123
- await git.clone(repoUrl, targetPath, [
124
- '--branch', branch,
125
- '--single-branch',
126
- ]);
127
-
128
- logger.info({ repoUrl, targetPath }, 'Repository cloned successfully');
129
- } catch (error) {
130
- throw createGitError('clone', error as Error, repoUrl);
131
- }
132
- }
133
-
134
- /**
135
- * Pull latest changes using fetch + fast-forward merge.
136
- *
137
- * Deliberately avoids --depth=1 on fetch: shallow fetches truncate local
138
- * history, causing "no merge base" divergence errors on subsequent pulls.
139
- */
140
- private async pullRepository(repoPath: string, branch: string = 'main'): Promise<{
141
- hasChanges: boolean;
142
- filesChanged: number;
143
- }> {
144
- try {
145
- const git = simpleGit(repoPath);
146
-
147
- // ── Step 1: read local HEAD commit before fetch ──────────────────────
148
- let localHead = '<unknown>';
149
- try {
150
- localHead = (await git.revparse(['HEAD'])).trim();
151
- } catch (e) {
152
- logger.warn({ repoPath, error: (e as Error).message }, 'git pull: failed to read local HEAD');
153
- }
154
-
155
- // ── Step 2: check current remotes ────────────────────────────────────
156
- let remotes: Array<{ name: string; refs: { fetch: string; push: string } }> = [];
157
- try {
158
- remotes = await git.getRemotes(true);
159
- } catch (e) {
160
- logger.warn({ repoPath, error: (e as Error).message }, 'git pull: failed to list remotes');
161
- }
162
-
163
- logger.info({
164
- repoPath,
165
- branch,
166
- localHead,
167
- remotes: remotes.map((r) => ({ name: r.name, fetch: r.refs.fetch })),
168
- }, 'git pull: starting — reading local state');
169
-
170
- // ── Step 3: detect shallow repo ──────────────────────────────────────
171
- let isShallow = false;
172
- try {
173
- isShallow = (await git.raw(['rev-parse', '--is-shallow-repository'])).trim() === 'true';
174
- } catch (e) {
175
- logger.warn({ repoPath, error: (e as Error).message }, 'git pull: failed to check shallow status — assuming not shallow');
176
- }
177
-
178
- logger.info({ repoPath, isShallow }, 'git pull: shallow-repository check complete');
179
-
180
- // ── Step 4: fetch ─────────────────────────────────────────────────────
181
- if (isShallow) {
182
- logger.info({ repoPath, branch }, 'git pull: shallow repo detected — running fetch --unshallow');
183
- try {
184
- await git.fetch(['--unshallow', 'origin', branch]);
185
- logger.info({ repoPath, branch }, 'git pull: fetch --unshallow succeeded');
186
- } catch (fetchErr) {
187
- logger.error({ repoPath, branch, error: (fetchErr as Error).message }, 'git pull: fetch --unshallow FAILED');
188
- throw fetchErr;
189
- }
190
- } else {
191
- logger.info({ repoPath, branch }, 'git pull: running fetch origin');
192
- try {
193
- await git.fetch(['origin', branch]);
194
- logger.info({ repoPath, branch }, 'git pull: fetch origin succeeded');
195
- } catch (fetchErr) {
196
- logger.error({ repoPath, branch, error: (fetchErr as Error).message }, 'git pull: fetch origin FAILED');
197
- throw fetchErr;
198
- }
199
- }
200
-
201
- // ── Step 5: read remote HEAD after fetch ─────────────────────────────
202
- const remoteBranch = `origin/${branch}`;
203
- let remoteHead = '<unknown>';
204
- try {
205
- remoteHead = (await git.revparse([remoteBranch])).trim();
206
- } catch (e) {
207
- logger.warn({ repoPath, remoteBranch, error: (e as Error).message }, 'git pull: failed to read remote HEAD after fetch');
208
- }
209
-
210
- logger.info({ repoPath, localHead, remoteHead, remoteBranch }, 'git pull: comparing local HEAD vs remote HEAD');
211
-
212
- // ── Step 6: diff to detect actual file changes ────────────────────────
213
- let diffSummary = { files: [] as { file: string }[], insertions: 0, deletions: 0 };
214
- try {
215
- diffSummary = await git.diffSummary([`HEAD...${remoteBranch}`]);
216
- } catch (e) {
217
- logger.warn({ repoPath, remoteBranch, error: (e as Error).message }, 'git pull: diffSummary failed — assuming no changes');
218
- }
219
-
220
- const hasChanges = diffSummary.files.length > 0;
221
-
222
- logger.info({
223
- repoPath,
224
- branch,
225
- remoteBranch,
226
- hasChanges,
227
- filesChanged: diffSummary.files.length,
228
- changedFiles: diffSummary.files.map((f) => f.file),
229
- insertions: diffSummary.insertions,
230
- deletions: diffSummary.deletions,
231
- }, hasChanges ? 'git pull: diff found changes — will merge' : 'git pull: repository is up-to-date');
232
-
233
- if (!hasChanges) {
234
- return { hasChanges: false, filesChanged: 0 };
235
- }
236
-
237
- // ── Step 7: fast-forward merge ────────────────────────────────────────
238
- logger.info({ repoPath, remoteBranch, filesChanged: diffSummary.files.length }, 'git pull: running merge --ff-only');
239
- try {
240
- await git.merge([remoteBranch, '--ff-only']);
241
- } catch (mergeErr) {
242
- logger.error({ repoPath, remoteBranch, error: (mergeErr as Error).message }, 'git pull: merge --ff-only FAILED');
243
- throw mergeErr;
244
- }
245
-
246
- // ── Step 8: read new HEAD after merge ────────────────────────────────
247
- let newHead = '<unknown>';
248
- try {
249
- newHead = (await git.revparse(['HEAD'])).trim();
250
- } catch { /* non-critical */ }
251
-
252
- logger.info({
253
- repoPath,
254
- branch,
255
- prevHead: localHead,
256
- newHead,
257
- filesChanged: diffSummary.files.length,
258
- insertions: diffSummary.insertions,
259
- deletions: diffSummary.deletions,
260
- }, 'git pull: repository updated successfully');
261
-
262
- return { hasChanges: true, filesChanged: diffSummary.files.length };
263
- } catch (error) {
264
- throw createGitError('pull', error as Error, repoPath);
265
- }
266
- }
267
-
268
- /**
269
- * Sync a single source repository
270
- */
271
- private async syncSource(source: SourceConfig): Promise<SyncResult> {
272
- const startTime = Date.now();
273
- const sourcePath = path.join(this.baseDir, source.path);
274
- const branch = source.git_branch || 'main';
275
-
276
- logger.info({
277
- source: source.name,
278
- path: sourcePath,
279
- priority: source.priority,
280
- git_url: source.git_url ?? '(not configured)',
281
- branch,
282
- }, 'Syncing AI Resources source...');
283
-
284
- try {
285
- const exists = await this.repositoryExists(sourcePath);
286
-
287
- logger.info({
288
- source: source.name,
289
- sourcePath,
290
- repoExists: exists,
291
- git_url: source.git_url ?? null,
292
- branch,
293
- }, 'syncSource: repository existence check complete');
294
-
295
- if (!exists) {
296
- if (!source.git_url) {
297
- // No git_url means the directory is Docker-mounted or manually placed —
298
- // files should already be present on disk. Skip clone and let
299
- // readResourceFiles serve them directly.
300
- logger.warn({
301
- source: source.name,
302
- sourcePath,
303
- reason: 'git_url not configured in ai-resources-config.json',
304
- hint: 'If the directory is Docker-mounted, git pull must be done manually or configured with a git_url.',
305
- }, 'Source has no git_url configured, skipping clone');
306
- return {
307
- source: source.name,
308
- action: 'skipped',
309
- changes: 0,
310
- duration: Date.now() - startTime,
311
- };
312
- }
313
-
314
- logger.info({ source: source.name, sourcePath, git_url: source.git_url, branch }, 'Repository does not exist, cloning...');
315
- await this.cloneRepository(source.git_url, sourcePath, branch);
316
- logger.info({ source: source.name, sourcePath, branch }, 'syncSource: clone succeeded');
317
-
318
- return {
319
- source: source.name,
320
- action: 'cloned',
321
- changes: -1,
322
- duration: Date.now() - startTime,
323
- };
324
- } else {
325
- // Repository exists — check if it has a remote we can pull from.
326
- if (!source.git_url) {
327
- // No git_url: Docker-mounted or manual directory — cannot pull.
328
- // Log clearly so operators know why git pull is not happening.
329
- const existingRemote = await this.getRepoUrl(sourcePath);
330
- logger.warn({
331
- source: source.name,
332
- sourcePath,
333
- existingRemoteUrl: existingRemote ?? '(none)',
334
- reason: 'git_url not configured in ai-resources-config.json',
335
- hint: 'Add a git_url to ai-resources-config.json to enable automatic git pull, or pull manually.',
336
- }, 'syncSource: repository exists but has no git_url — skipping pull');
337
- return {
338
- source: source.name,
339
- action: 'skipped',
340
- changes: 0,
341
- duration: Date.now() - startTime,
342
- };
343
- }
344
-
345
- logger.info({ source: source.name, sourcePath, git_url: source.git_url, branch }, 'Repository exists, pulling latest changes...');
346
-
347
- const { hasChanges, filesChanged } = await this.pullRepository(sourcePath, branch);
348
-
349
- logger.info({
350
- source: source.name,
351
- sourcePath,
352
- branch,
353
- action: hasChanges ? 'pulled' : 'up-to-date',
354
- filesChanged,
355
- duration: Date.now() - startTime,
356
- }, 'syncSource: pull complete');
357
-
358
- return {
359
- source: source.name,
360
- action: hasChanges ? 'pulled' : 'up-to-date',
361
- changes: filesChanged,
362
- duration: Date.now() - startTime,
363
- };
364
- }
365
- } catch (error) {
366
- logger.error({
367
- source: source.name,
368
- sourcePath,
369
- git_url: source.git_url ?? null,
370
- branch,
371
- error: error instanceof Error ? error.message : String(error),
372
- stack: error instanceof Error ? error.stack : undefined,
373
- }, 'Failed to sync source');
374
-
375
- throw error;
376
- }
377
- }
378
-
379
- /**
380
- * Sync all enabled sources
381
- */
382
- async syncAllSources(): Promise<SyncResult[]> {
383
- logger.info('Starting multi-source sync...');
384
-
385
- const sources = await this.getEnabledSources();
386
- logger.info({
387
- totalSources: sources.length,
388
- sourceNames: sources.map(s => s.name)
389
- }, 'Found enabled sources');
390
-
391
- const results: SyncResult[] = [];
392
-
393
- for (const source of sources) {
394
- try {
395
- const result = await this.syncSource(source);
396
- results.push(result);
397
- } catch (error) {
398
- logger.error({
399
- source: source.name,
400
- error: error instanceof Error ? error.message : String(error)
401
- }, 'Failed to sync source, continuing with next...');
402
-
403
- results.push({
404
- source: source.name,
405
- action: 'skipped',
406
- changes: 0,
407
- duration: 0
408
- });
409
- }
410
- }
411
-
412
- logger.info({
413
- results,
414
- totalSources: results.length,
415
- cloned: results.filter(r => r.action === 'cloned').length,
416
- pulled: results.filter(r => r.action === 'pulled').length,
417
- upToDate: results.filter(r => r.action === 'up-to-date').length
418
- }, 'Multi-source sync completed');
419
-
420
- return results;
421
- }
422
-
423
- /**
424
- * Read the files for a named Command or Skill resource from the local git
425
- * checkout. Used when the CSP API download returns an empty `files` array
426
- * (which is expected for Command/Skill resources in MCP Prompt mode — the
427
- * API only stores metadata; actual file content lives in the git repo).
428
- *
429
- * Searches all enabled sources in priority order and returns the first match.
430
- *
431
- * @param resourceName The resource name as returned by the subscriptions API.
432
- * @param resourceType 'command' | 'skill'
433
- * @returns Array of { path, content } file entries, or [] when not found.
434
- */
435
- async readResourceFiles(
436
- resourceName: string,
437
- resourceType: 'command' | 'skill' | 'rule' | 'mcp',
438
- ): Promise<Array<{ path: string; content: string }>> {
439
- let sources: SourceConfig[];
440
- try {
441
- sources = await this.getEnabledSources();
442
- } catch (configErr) {
443
- logger.warn(
444
- {
445
- resourceName,
446
- resourceType,
447
- aiResourcesBase: this.baseDir,
448
- error: (configErr as Error).message,
449
- },
450
- 'readResourceFiles: failed to load ai-resources-config.json — returning empty. ' +
451
- 'Set AI_RESOURCES_PATH env var to the directory containing ai-resources-config.json.',
452
- );
453
- return [];
454
- }
455
- // Sort by priority descending so higher-priority sources win.
456
- sources.sort((a, b) => b.priority - a.priority);
457
-
458
- // Map singular type names to the plural directory keys used in config.
459
- const typeToDirKey: Record<string, keyof SourceConfig['resources']> = {
460
- command: 'commands',
461
- commands: 'commands',
462
- skill: 'skills',
463
- skills: 'skills',
464
- rule: 'rules',
465
- rules: 'rules',
466
- mcp: 'mcp',
467
- };
468
- const typeDir = typeToDirKey[resourceType] ?? 'skills';
469
-
470
- logger.info(
471
- {
472
- resourceName,
473
- resourceType,
474
- resolvedDirKey: typeDir,
475
- sourceCount: sources.length,
476
- sourceNames: sources.map((s) => s.name),
477
- },
478
- 'readResourceFiles: start — searching git sources for resource',
479
- );
480
-
481
- for (const source of sources) {
482
- const sourcePath = path.join(this.baseDir, source.path);
483
- const resourcesSubDir = source.resources[typeDir as keyof typeof source.resources];
484
- const resourceDir = path.join(sourcePath, resourcesSubDir, resourceName);
485
- const resourceFile = path.join(sourcePath, resourcesSubDir, `${resourceName}.md`);
486
-
487
- logger.info(
488
- {
489
- source: source.name,
490
- resourceName,
491
- resourceType,
492
- tryDirPath: resourceDir,
493
- tryFilePath: resourceFile,
494
- },
495
- 'readResourceFiles: trying source',
496
- );
497
-
498
- // Try directory-based layout first (e.g. rules/<name>/ or mcp/<name>/)
499
- try {
500
- const stat = await fs.stat(resourceDir);
501
- if (stat.isDirectory()) {
502
- const entries = await fs.readdir(resourceDir);
503
- // For mcp resources also include mcp-config.json (which is the key
504
- // config file and may be the only file present in the directory).
505
- const relevantFiles = entries.filter(
506
- (f) => f.endsWith('.md') || f.endsWith('.mdc') ||
507
- (resourceType === 'mcp' && f === 'mcp-config.json'),
508
- );
509
- if (relevantFiles.length > 0) {
510
- const results: Array<{ path: string; content: string }> = [];
511
- for (const f of relevantFiles) {
512
- const filePath = path.join(resourceDir, f);
513
- const content = await fs.readFile(filePath, 'utf-8');
514
- results.push({ path: f, content });
515
- }
516
- logger.info(
517
- {
518
- source: source.name,
519
- resourceName,
520
- resourceType,
521
- dirPath: resourceDir,
522
- fileCount: results.length,
523
- files: results.map((r) => r.path),
524
- },
525
- 'readResourceFiles: found files in directory layout',
526
- );
527
- return results;
528
- }
529
- logger.info(
530
- { source: source.name, resourceName, resourceType, dirPath: resourceDir },
531
- 'readResourceFiles: directory exists but contains no relevant files — trying flat file',
532
- );
533
- }
534
- } catch { /* not a directory or doesn't exist — try flat file */ }
535
-
536
- // Try flat file layout (e.g. rules/<name>.mdc or rules/<name>.md)
537
- // Also try .mdc extension for rule resources.
538
- const mdcFile = path.join(sourcePath, resourcesSubDir, `${resourceName}.mdc`);
539
- for (const [filePath, ext] of [[resourceFile, '.md'], [mdcFile, '.mdc']] as const) {
540
- try {
541
- const content = await fs.readFile(filePath, 'utf-8');
542
- logger.info(
543
- {
544
- source: source.name,
545
- resourceName,
546
- resourceType,
547
- filePath,
548
- ext,
549
- contentLength: content.length,
550
- },
551
- 'readResourceFiles: found flat file',
552
- );
553
- return [{ path: `${resourceName}${ext}`, content }];
554
- } catch { /* not found — try next extension or source */ }
555
- }
556
-
557
- logger.info(
558
- { source: source.name, resourceName, resourceType },
559
- 'readResourceFiles: resource not found in this source — trying next',
560
- );
561
- }
562
-
563
- logger.warn(
564
- { resourceName, resourceType, resolvedDirKey: typeDir, sourceCount: sources.length },
565
- 'readResourceFiles: resource not found in any git source',
566
- );
567
- return [];
568
- }
569
-
570
- /**
571
- * Check status of all sources without pulling
572
- */
573
- async checkAllSources(): Promise<Array<{
574
- source: string;
575
- exists: boolean;
576
- hasRemote: boolean;
577
- repoUrl: string | null;
578
- }>> {
579
- const sources = await this.getEnabledSources();
580
- const statuses = [];
581
-
582
- for (const source of sources) {
583
- const sourcePath = path.join(this.baseDir, source.path);
584
- const exists = await this.repositoryExists(sourcePath);
585
-
586
- let repoUrl = null;
587
- if (exists) {
588
- repoUrl = await this.getRepoUrl(sourcePath);
589
- }
590
-
591
- statuses.push({
592
- source: source.name,
593
- exists,
594
- hasRemote: !!repoUrl,
595
- repoUrl
596
- });
597
- }
598
-
599
- return statuses;
600
- }
601
- }
602
-
603
- // Resolve the AI-Resources base directory.
604
- //
605
- // Resolution order (first existing path wins):
606
- // 1. AI_RESOURCES_PATH env var (explicit override for production deployments)
607
- // 2. __dirname-relative path: dist/git/ → ../../AI-Resources
608
- // Works when the package is installed as an npm package and run from its
609
- // own directory (the common production case after `npm install -g`).
610
- // 3. process.cwd()-relative path: ../AI-Resources
611
- // Works in local development where cwd is SourceCode/.
612
- //
613
- // Using __dirname (compiled file location) instead of process.cwd() makes the
614
- // path robust to being started from any working directory on the server.
615
- function resolveAiResourcesBase(): string {
616
- if (process.env.AI_RESOURCES_PATH) {
617
- const explicit = path.resolve(process.env.AI_RESOURCES_PATH);
618
- logger.info({ aiResourcesBase: explicit }, 'AI-Resources base resolved from AI_RESOURCES_PATH env');
619
- return explicit;
620
- }
621
-
622
- // Probe candidate locations in priority order:
623
- //
624
- // 1. cwd/AI-Resources — server deployed with cwd = project root (e.g. /app)
625
- // and AI-Resources/ is a sibling of dist/
626
- // 2. cwd/../AI-Resources — local dev: cwd = SourceCode/, AI-Resources/ is one level up
627
- // 3. __dirname/../../AI-Resources — npm global install: dist/git/ → ../../AI-Resources
628
- // (resolves to same as #1 when installed as a local package from project root)
629
- const candidates = [
630
- path.resolve(process.cwd(), 'AI-Resources'), // production: cwd is package root
631
- path.resolve(process.cwd(), '../AI-Resources'), // local dev: cwd is SourceCode/
632
- path.resolve(__dirname, '../../AI-Resources'), // npm global install fallback
633
- ];
634
-
635
- for (const candidate of candidates) {
636
- try {
637
- const configFile = path.join(candidate, 'ai-resources-config.json');
638
- require('fs').accessSync(configFile);
639
- logger.info({ aiResourcesBase: candidate }, 'AI-Resources base resolved');
640
- return candidate;
641
- } catch { /* try next */ }
642
- }
643
-
644
- // Nothing found — fall back to first candidate and let later errors surface clearly.
645
- logger.warn(
646
- { triedPaths: candidates },
647
- 'AI-Resources config not found in any candidate path — using cwd/AI-Resources as fallback. ' +
648
- 'Set AI_RESOURCES_PATH env var to override.',
649
- );
650
- return candidates[0] as string;
651
- }
652
-
653
- const AI_RESOURCES_BASE = resolveAiResourcesBase();
654
- export const multiSourceGitManager = new MultiSourceGitManager(AI_RESOURCES_BASE);