aman-intelligence 0.1.0

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 (197) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +116 -0
  3. package/dist/bin/aman.d.ts +2 -0
  4. package/dist/bin/aman.js +165 -0
  5. package/dist/cli/global-install.d.ts +7 -0
  6. package/dist/cli/global-install.js +36 -0
  7. package/dist/cli/help-text.d.ts +1 -0
  8. package/dist/cli/help-text.js +62 -0
  9. package/dist/cli/version.d.ts +1 -0
  10. package/dist/cli/version.js +6 -0
  11. package/dist/commands/backup.d.ts +11 -0
  12. package/dist/commands/backup.js +262 -0
  13. package/dist/commands/browse.d.ts +11 -0
  14. package/dist/commands/browse.js +641 -0
  15. package/dist/commands/cache.d.ts +1 -0
  16. package/dist/commands/cache.js +38 -0
  17. package/dist/commands/config.d.ts +4 -0
  18. package/dist/commands/config.js +146 -0
  19. package/dist/commands/dashboard.d.ts +1 -0
  20. package/dist/commands/dashboard.js +1004 -0
  21. package/dist/commands/doctor.d.ts +4 -0
  22. package/dist/commands/doctor.js +54 -0
  23. package/dist/commands/export.d.ts +1 -0
  24. package/dist/commands/export.js +137 -0
  25. package/dist/commands/help.d.ts +1 -0
  26. package/dist/commands/help.js +47 -0
  27. package/dist/commands/import-wizard.d.ts +7 -0
  28. package/dist/commands/import-wizard.js +374 -0
  29. package/dist/commands/import.d.ts +9 -0
  30. package/dist/commands/import.js +351 -0
  31. package/dist/commands/info.d.ts +1 -0
  32. package/dist/commands/info.js +174 -0
  33. package/dist/commands/init.d.ts +20 -0
  34. package/dist/commands/init.js +146 -0
  35. package/dist/commands/install.d.ts +10 -0
  36. package/dist/commands/install.js +342 -0
  37. package/dist/commands/pack.d.ts +23 -0
  38. package/dist/commands/pack.js +331 -0
  39. package/dist/commands/registry.d.ts +6 -0
  40. package/dist/commands/registry.js +218 -0
  41. package/dist/commands/remove.d.ts +1 -0
  42. package/dist/commands/remove.js +76 -0
  43. package/dist/commands/search.d.ts +7 -0
  44. package/dist/commands/search.js +295 -0
  45. package/dist/commands/stack.d.ts +18 -0
  46. package/dist/commands/stack.js +327 -0
  47. package/dist/commands/sync.d.ts +9 -0
  48. package/dist/commands/sync.js +428 -0
  49. package/dist/commands/update.d.ts +1 -0
  50. package/dist/commands/update.js +97 -0
  51. package/dist/config/features.d.ts +2 -0
  52. package/dist/config/features.js +2 -0
  53. package/dist/config/index.d.ts +13 -0
  54. package/dist/config/index.js +80 -0
  55. package/dist/config/paths.d.ts +23 -0
  56. package/dist/config/paths.js +45 -0
  57. package/dist/import/adapters.d.ts +14 -0
  58. package/dist/import/adapters.js +580 -0
  59. package/dist/import/discovery.service.d.ts +8 -0
  60. package/dist/import/discovery.service.js +26 -0
  61. package/dist/import/import.service.d.ts +7 -0
  62. package/dist/import/import.service.js +259 -0
  63. package/dist/import/types.d.ts +71 -0
  64. package/dist/import/types.js +1 -0
  65. package/dist/import/utils.d.ts +36 -0
  66. package/dist/import/utils.js +428 -0
  67. package/dist/marketplace/cache.d.ts +18 -0
  68. package/dist/marketplace/cache.js +141 -0
  69. package/dist/marketplace/github-search.d.ts +17 -0
  70. package/dist/marketplace/github-search.js +268 -0
  71. package/dist/marketplace/install-from-candidate.d.ts +6 -0
  72. package/dist/marketplace/install-from-candidate.js +14 -0
  73. package/dist/marketplace/install.d.ts +15 -0
  74. package/dist/marketplace/install.js +54 -0
  75. package/dist/marketplace/metadata-validator.d.ts +8 -0
  76. package/dist/marketplace/metadata-validator.js +79 -0
  77. package/dist/marketplace/types.d.ts +34 -0
  78. package/dist/marketplace/types.js +1 -0
  79. package/dist/providers/local.provider.d.ts +9 -0
  80. package/dist/providers/local.provider.js +51 -0
  81. package/dist/providers/provider.interface.d.ts +7 -0
  82. package/dist/providers/provider.interface.js +1 -0
  83. package/dist/providers/registry.provider.d.ts +2 -0
  84. package/dist/providers/registry.provider.js +42 -0
  85. package/dist/providers/skills-sh.provider.d.ts +11 -0
  86. package/dist/providers/skills-sh.provider.js +56 -0
  87. package/dist/registry/adapter.interface.d.ts +16 -0
  88. package/dist/registry/adapter.interface.js +1 -0
  89. package/dist/registry/errors.d.ts +5 -0
  90. package/dist/registry/errors.js +8 -0
  91. package/dist/registry/filesystem-registry.adapter.d.ts +25 -0
  92. package/dist/registry/filesystem-registry.adapter.js +288 -0
  93. package/dist/registry/github-registry.adapter.d.ts +11 -0
  94. package/dist/registry/github-registry.adapter.js +32 -0
  95. package/dist/registry/index.d.ts +8 -0
  96. package/dist/registry/index.js +8 -0
  97. package/dist/registry/local-registry.adapter.d.ts +6 -0
  98. package/dist/registry/local-registry.adapter.js +9 -0
  99. package/dist/registry/registry.service.d.ts +44 -0
  100. package/dist/registry/registry.service.js +163 -0
  101. package/dist/registry/slug-utils.d.ts +12 -0
  102. package/dist/registry/slug-utils.js +51 -0
  103. package/dist/registry/types.d.ts +160 -0
  104. package/dist/registry/types.js +1 -0
  105. package/dist/services/asset.service.d.ts +12 -0
  106. package/dist/services/asset.service.js +142 -0
  107. package/dist/services/backup.service.d.ts +8 -0
  108. package/dist/services/backup.service.js +169 -0
  109. package/dist/services/classification.service.d.ts +31 -0
  110. package/dist/services/classification.service.js +271 -0
  111. package/dist/services/config.service.d.ts +9 -0
  112. package/dist/services/config.service.js +20 -0
  113. package/dist/services/doctor.service.d.ts +5 -0
  114. package/dist/services/doctor.service.js +186 -0
  115. package/dist/services/environment.service.d.ts +42 -0
  116. package/dist/services/environment.service.js +227 -0
  117. package/dist/services/github.service.d.ts +7 -0
  118. package/dist/services/github.service.js +42 -0
  119. package/dist/services/lock.service.d.ts +12 -0
  120. package/dist/services/lock.service.js +71 -0
  121. package/dist/services/marketplace.service.d.ts +40 -0
  122. package/dist/services/marketplace.service.js +225 -0
  123. package/dist/services/pack.service.d.ts +9 -0
  124. package/dist/services/pack.service.js +193 -0
  125. package/dist/services/stack.service.d.ts +9 -0
  126. package/dist/services/stack.service.js +94 -0
  127. package/dist/storage/asset-layout.d.ts +46 -0
  128. package/dist/storage/asset-layout.js +277 -0
  129. package/dist/storage/filesystem.d.ts +12 -0
  130. package/dist/storage/filesystem.js +113 -0
  131. package/dist/storage/scan-by-type.d.ts +2 -0
  132. package/dist/storage/scan-by-type.js +8 -0
  133. package/dist/storage/scanner.d.ts +11 -0
  134. package/dist/storage/scanner.js +188 -0
  135. package/dist/types/asset-metadata.d.ts +84 -0
  136. package/dist/types/asset-metadata.js +104 -0
  137. package/dist/types/index.d.ts +212 -0
  138. package/dist/types/index.js +1 -0
  139. package/dist/ui/animations/ErrorIndicator.d.ts +5 -0
  140. package/dist/ui/animations/ErrorIndicator.js +6 -0
  141. package/dist/ui/animations/GithubIndicator.d.ts +6 -0
  142. package/dist/ui/animations/GithubIndicator.js +9 -0
  143. package/dist/ui/animations/ProgressBar.d.ts +5 -0
  144. package/dist/ui/animations/ProgressBar.js +15 -0
  145. package/dist/ui/animations/Spinner.d.ts +5 -0
  146. package/dist/ui/animations/Spinner.js +21 -0
  147. package/dist/ui/animations/SuccessIndicator.d.ts +5 -0
  148. package/dist/ui/animations/SuccessIndicator.js +6 -0
  149. package/dist/ui/animations/SyncActivity.d.ts +5 -0
  150. package/dist/ui/animations/SyncActivity.js +21 -0
  151. package/dist/ui/animations/TransitionScreen.d.ts +7 -0
  152. package/dist/ui/animations/TransitionScreen.js +25 -0
  153. package/dist/ui/animations/useAnimationMode.d.ts +1 -0
  154. package/dist/ui/animations/useAnimationMode.js +16 -0
  155. package/dist/ui/assetDisplay.d.ts +19 -0
  156. package/dist/ui/assetDisplay.js +59 -0
  157. package/dist/ui/components/Confirm.d.ts +8 -0
  158. package/dist/ui/components/Confirm.js +14 -0
  159. package/dist/ui/components/CustomSelect.d.ts +19 -0
  160. package/dist/ui/components/CustomSelect.js +13 -0
  161. package/dist/ui/components/Header.d.ts +6 -0
  162. package/dist/ui/components/Header.js +9 -0
  163. package/dist/ui/components/HealthReport.d.ts +7 -0
  164. package/dist/ui/components/HealthReport.js +13 -0
  165. package/dist/ui/components/MarketplaceInstallConfirm.d.ts +19 -0
  166. package/dist/ui/components/MarketplaceInstallConfirm.js +23 -0
  167. package/dist/ui/components/Narrator.d.ts +9 -0
  168. package/dist/ui/components/Narrator.js +26 -0
  169. package/dist/ui/components/ScopePrompt.d.ts +8 -0
  170. package/dist/ui/components/ScopePrompt.js +23 -0
  171. package/dist/ui/components/TooSmallScreen.d.ts +8 -0
  172. package/dist/ui/components/TooSmallScreen.js +6 -0
  173. package/dist/ui/date.d.ts +2 -0
  174. package/dist/ui/date.js +33 -0
  175. package/dist/ui/layout.d.ts +23 -0
  176. package/dist/ui/layout.js +44 -0
  177. package/dist/ui/list-item.d.ts +12 -0
  178. package/dist/ui/list-item.js +1 -0
  179. package/dist/ui/marketplaceDisplay.d.ts +10 -0
  180. package/dist/ui/marketplaceDisplay.js +36 -0
  181. package/dist/ui/theme.d.ts +42 -0
  182. package/dist/ui/theme.js +47 -0
  183. package/dist/utils/asset-list-fields.d.ts +11 -0
  184. package/dist/utils/asset-list-fields.js +28 -0
  185. package/dist/utils/error-message.d.ts +2 -0
  186. package/dist/utils/error-message.js +6 -0
  187. package/dist/utils/integrity.d.ts +9 -0
  188. package/dist/utils/integrity.js +23 -0
  189. package/dist/utils/lock-migrate.d.ts +25 -0
  190. package/dist/utils/lock-migrate.js +93 -0
  191. package/dist/utils/mcp-local.d.ts +15 -0
  192. package/dist/utils/mcp-local.js +129 -0
  193. package/dist/utils/slug.d.ts +6 -0
  194. package/dist/utils/slug.js +13 -0
  195. package/dist/utils/stack-normalize.d.ts +3 -0
  196. package/dist/utils/stack-normalize.js +43 -0
  197. package/package.json +77 -0
@@ -0,0 +1,271 @@
1
+ import path from 'path';
2
+ import { promises as fs } from 'fs';
3
+ import { GLOBAL_CACHE } from '../config/paths.js';
4
+ import { readJson, writeJson, exists, ensureDir, readFrontmatter } from '../storage/filesystem.js';
5
+ const MEMORY_PATH = path.join(GLOBAL_CACHE, 'classifications.json');
6
+ // ── Patterns for classification ─────────────────────────────────────
7
+ const SKILL_INDICATORS = [
8
+ /^SKILL\.md$/i,
9
+ /instructions?/i,
10
+ /guide/i,
11
+ /best.?practices/i,
12
+ /workflow/i,
13
+ /how.?to/i,
14
+ /pattern/i,
15
+ /architecture/i,
16
+ /setup/i,
17
+ /conventions?/i,
18
+ ];
19
+ const PROMPT_INDICATORS = [
20
+ /^prompt/i,
21
+ /\bprompt\b/i,
22
+ /^system/i,
23
+ /\brole\b/i,
24
+ /\byou are\b/i,
25
+ /\bact as\b/i,
26
+ /\brespond/i,
27
+ /\bgenerate\b/i,
28
+ /\btask\b/i,
29
+ ];
30
+ const MCP_INDICATORS = [
31
+ /mcpServers/i,
32
+ /\bcommand\b/,
33
+ /\bargs\b/,
34
+ /\"servers\"/,
35
+ /stdio/i,
36
+ /sse/i,
37
+ /transport/i,
38
+ ];
39
+ const STACK_INDICATORS = [
40
+ /\bstack\b/i,
41
+ /\bworkspace\b/i,
42
+ /\bprofile\b/i,
43
+ /\"skills\"/,
44
+ /\"prompts\"/,
45
+ /\"mcps\"/,
46
+ ];
47
+ const IGNORED = new Set(['.git', 'node_modules', 'dist', 'build', '.next', '.aman', '__pycache__']);
48
+ export class ClassificationService {
49
+ memory = null;
50
+ // ── Load / save memory ──────────────────────────────────────────
51
+ async loadMemory() {
52
+ if (this.memory)
53
+ return this.memory;
54
+ this.memory = (await readJson(MEMORY_PATH)) || {};
55
+ return this.memory;
56
+ }
57
+ async saveMemory() {
58
+ if (!this.memory)
59
+ return;
60
+ await ensureDir(path.dirname(MEMORY_PATH));
61
+ await writeJson(MEMORY_PATH, this.memory);
62
+ }
63
+ async remember(relativePath, type) {
64
+ const mem = await this.loadMemory();
65
+ mem[relativePath] = type;
66
+ await this.saveMemory();
67
+ }
68
+ async rememberBatch(decisions) {
69
+ const mem = await this.loadMemory();
70
+ for (const [key, val] of Object.entries(decisions)) {
71
+ mem[key] = val;
72
+ }
73
+ await this.saveMemory();
74
+ }
75
+ async getRemembered(relativePath) {
76
+ const mem = await this.loadMemory();
77
+ return mem[relativePath];
78
+ }
79
+ // ── Walk a directory ────────────────────────────────────────────
80
+ async walk(root, base = root) {
81
+ const entries = await fs.readdir(root, { withFileTypes: true });
82
+ const files = [];
83
+ for (const entry of entries) {
84
+ if (IGNORED.has(entry.name))
85
+ continue;
86
+ const full = path.join(root, entry.name);
87
+ if (entry.isDirectory()) {
88
+ files.push(...(await this.walk(full, base)));
89
+ }
90
+ else if (entry.isFile()) {
91
+ files.push(full);
92
+ }
93
+ }
94
+ return files;
95
+ }
96
+ // ── Classify a single file ──────────────────────────────────────
97
+ async classifyFile(filePath, rootDir) {
98
+ const relative = path.relative(rootDir, filePath);
99
+ const basename = path.basename(filePath);
100
+ const ext = path.extname(filePath).toLowerCase();
101
+ // Check memory first
102
+ const remembered = await this.getRemembered(relative);
103
+ if (remembered) {
104
+ return {
105
+ file: relative,
106
+ type: remembered,
107
+ confidence: 100,
108
+ reason: 'Previously classified',
109
+ };
110
+ }
111
+ // SKILL.md is always a skill marker
112
+ if (basename === 'SKILL.md') {
113
+ return { file: relative, type: 'skill', confidence: 99, reason: 'SKILL.md file' };
114
+ }
115
+ // JSON files
116
+ if (ext === '.json') {
117
+ return await this.classifyJson(filePath, relative);
118
+ }
119
+ // Markdown files
120
+ if (ext === '.md') {
121
+ return await this.classifyMarkdown(filePath, relative);
122
+ }
123
+ return { file: relative, type: 'unknown', confidence: 20, reason: 'Unrecognized file type' };
124
+ }
125
+ async classifyJson(filePath, relative) {
126
+ try {
127
+ const raw = await fs.readFile(filePath, 'utf-8');
128
+ const data = JSON.parse(raw);
129
+ // MCP detection
130
+ let mcpScore = 0;
131
+ for (const pattern of MCP_INDICATORS) {
132
+ if (pattern.test(raw))
133
+ mcpScore += 15;
134
+ }
135
+ if (data.command || data.mcpServers || data.servers)
136
+ mcpScore += 30;
137
+ mcpScore = Math.min(mcpScore, 99);
138
+ // Stack detection
139
+ let stackScore = 0;
140
+ if (data.skills && Array.isArray(data.skills))
141
+ stackScore += 40;
142
+ if (data.prompts && Array.isArray(data.prompts))
143
+ stackScore += 25;
144
+ if (data.mcps && Array.isArray(data.mcps))
145
+ stackScore += 25;
146
+ for (const pattern of STACK_INDICATORS) {
147
+ if (pattern.test(raw))
148
+ stackScore += 5;
149
+ }
150
+ stackScore = Math.min(stackScore, 99);
151
+ if (mcpScore > stackScore && mcpScore >= 50) {
152
+ return { file: relative, type: 'mcp', confidence: mcpScore, reason: 'MCP configuration detected' };
153
+ }
154
+ if (stackScore >= 50) {
155
+ return { file: relative, type: 'stack', confidence: stackScore, reason: 'Stack definition detected' };
156
+ }
157
+ return { file: relative, type: 'unknown', confidence: Math.max(mcpScore, stackScore, 25), reason: 'JSON file, unclear purpose' };
158
+ }
159
+ catch {
160
+ return { file: relative, type: 'unknown', confidence: 10, reason: 'Invalid JSON' };
161
+ }
162
+ }
163
+ async classifyMarkdown(filePath, relative) {
164
+ try {
165
+ const content = await fs.readFile(filePath, 'utf-8');
166
+ const frontmatter = await readFrontmatter(filePath);
167
+ // Check if parent dir has SKILL.md (part of a skill)
168
+ const parentSkillMd = path.join(path.dirname(filePath), 'SKILL.md');
169
+ if (exists(parentSkillMd) && path.basename(filePath) !== 'SKILL.md') {
170
+ return { file: relative, type: 'skill', confidence: 90, reason: 'Part of a skill directory' };
171
+ }
172
+ // Frontmatter hints
173
+ if (frontmatter?.type === 'prompt') {
174
+ return { file: relative, type: 'prompt', confidence: 98, reason: 'Frontmatter declares prompt' };
175
+ }
176
+ if (frontmatter?.type === 'skill') {
177
+ return { file: relative, type: 'skill', confidence: 98, reason: 'Frontmatter declares skill' };
178
+ }
179
+ // Content-based scoring
180
+ let skillScore = 0;
181
+ let promptScore = 0;
182
+ for (const pattern of SKILL_INDICATORS) {
183
+ if (pattern.test(content.slice(0, 2000)))
184
+ skillScore += 10;
185
+ }
186
+ for (const pattern of PROMPT_INDICATORS) {
187
+ if (pattern.test(content.slice(0, 2000)))
188
+ promptScore += 12;
189
+ }
190
+ // Long documents with headers tend to be skills
191
+ const headerCount = (content.match(/^#+\s/gm) || []).length;
192
+ if (headerCount >= 3)
193
+ skillScore += 15;
194
+ if (content.length > 3000)
195
+ skillScore += 10;
196
+ // Short documents with direct instructions tend to be prompts
197
+ if (content.length < 2000 && promptScore > 0)
198
+ promptScore += 15;
199
+ skillScore = Math.min(skillScore, 97);
200
+ promptScore = Math.min(promptScore, 97);
201
+ if (skillScore > promptScore && skillScore >= 50) {
202
+ return { file: relative, type: 'skill', confidence: skillScore, reason: 'Content matches skill patterns' };
203
+ }
204
+ if (promptScore >= 50) {
205
+ return { file: relative, type: 'prompt', confidence: promptScore, reason: 'Content matches prompt patterns' };
206
+ }
207
+ const best = Math.max(skillScore, promptScore);
208
+ return { file: relative, type: 'unknown', confidence: best || 25, reason: 'Could not determine type' };
209
+ }
210
+ catch {
211
+ return { file: relative, type: 'unknown', confidence: 10, reason: 'Could not read file' };
212
+ }
213
+ }
214
+ // ── Classify an entire directory ────────────────────────────────
215
+ async classifyDirectory(rootDir) {
216
+ const files = await this.walk(rootDir);
217
+ const results = [];
218
+ // First pass: find SKILL.md markers and mark their parent dirs
219
+ const skillDirs = new Set();
220
+ for (const file of files) {
221
+ if (path.basename(file) === 'SKILL.md') {
222
+ skillDirs.add(path.dirname(file));
223
+ }
224
+ }
225
+ for (const file of files) {
226
+ const dir = path.dirname(file);
227
+ // Skip files inside known skill directories (they're part of the skill)
228
+ if (skillDirs.has(dir) && path.basename(file) !== 'SKILL.md') {
229
+ continue;
230
+ }
231
+ // Skip files in subdirectories of skill directories
232
+ let inSkillDir = false;
233
+ for (const sd of skillDirs) {
234
+ if (dir.startsWith(sd + path.sep) && dir !== sd) {
235
+ inSkillDir = true;
236
+ break;
237
+ }
238
+ }
239
+ if (inSkillDir)
240
+ continue;
241
+ const result = await this.classifyFile(file, rootDir);
242
+ results.push(result);
243
+ }
244
+ return results;
245
+ }
246
+ // ── Summary helpers ─────────────────────────────────────────────
247
+ summarize(results) {
248
+ const summary = { skills: 0, prompts: 0, mcps: 0, stacks: 0, unknown: 0 };
249
+ for (const r of results) {
250
+ if (r.type === 'skill')
251
+ summary.skills++;
252
+ else if (r.type === 'prompt')
253
+ summary.prompts++;
254
+ else if (r.type === 'mcp')
255
+ summary.mcps++;
256
+ else if (r.type === 'stack')
257
+ summary.stacks++;
258
+ else
259
+ summary.unknown++;
260
+ }
261
+ return summary;
262
+ }
263
+ /** Auto-import threshold: 50%+ per import confidence bands; below 50 is manual review only. */
264
+ highConfidence(results, threshold = 50) {
265
+ return results.filter((r) => r.confidence >= threshold && r.type !== 'unknown');
266
+ }
267
+ lowConfidence(results, threshold = 50) {
268
+ return results.filter((r) => r.confidence < threshold || r.type === 'unknown');
269
+ }
270
+ }
271
+ export const classificationService = new ClassificationService();
@@ -0,0 +1,9 @@
1
+ import { AmanConfig } from '../types/index.js';
2
+ export declare class ConfigService {
3
+ get<K extends keyof AmanConfig>(key: K): AmanConfig[K];
4
+ set<K extends keyof AmanConfig>(key: K, value: AmanConfig[K]): void;
5
+ reset(): void;
6
+ list(): AmanConfig;
7
+ validate(): boolean;
8
+ }
9
+ export declare const configService: ConfigService;
@@ -0,0 +1,20 @@
1
+ import { config } from '../config/index.js';
2
+ export class ConfigService {
3
+ get(key) {
4
+ return config.get(key);
5
+ }
6
+ set(key, value) {
7
+ config.set(key, value);
8
+ }
9
+ reset() {
10
+ config.reset();
11
+ }
12
+ list() {
13
+ return config.load();
14
+ }
15
+ validate() {
16
+ const data = this.list();
17
+ return typeof data === 'object' && data !== null;
18
+ }
19
+ }
20
+ export const configService = new ConfigService();
@@ -0,0 +1,5 @@
1
+ import { HealthCheck } from '../types/index.js';
2
+ export declare class DoctorService {
3
+ runChecks(scope?: 'global' | 'project'): Promise<HealthCheck[]>;
4
+ }
5
+ export declare const doctorService: DoctorService;
@@ -0,0 +1,186 @@
1
+ import { BUNDLED_SKILLS, BUNDLED_PROMPTS, BUNDLED_MCPS, LOCAL_DIR } from '../config/paths.js';
2
+ import { exists } from '../storage/filesystem.js';
3
+ import { execSync } from 'child_process';
4
+ import { lockService } from './lock.service.js';
5
+ import { environmentService } from './environment.service.js';
6
+ import path from 'path';
7
+ import { migrateScopeLayout, findLayoutViolations, assetDir, metadataFilePath, mcpLocalFilePath } from '../storage/asset-layout.js';
8
+ import { countEmptyMcpLocalValues, gitignoreIncludesMcpLocalAsync } from '../utils/mcp-local.js';
9
+ export class DoctorService {
10
+ async runChecks(scope = 'global') {
11
+ const checks = [];
12
+ const targetDir = scope === 'global' ? environmentService.getActiveEnvironmentDir() : LOCAL_DIR;
13
+ const dirExists = exists(targetDir);
14
+ checks.push({
15
+ name: `${scope} directory exists`,
16
+ status: dirExists ? 'pass' : 'fail',
17
+ message: dirExists ? `Found ${targetDir}` : `Missing ${targetDir}`,
18
+ fix: dirExists ? undefined : `Run 'aman init' to create the environment.`,
19
+ });
20
+ const bundledSkills = exists(BUNDLED_SKILLS);
21
+ const bundledPrompts = exists(BUNDLED_PROMPTS);
22
+ const bundledMcps = exists(BUNDLED_MCPS);
23
+ const bundledOk = bundledSkills && bundledPrompts && bundledMcps;
24
+ const anyBundled = bundledSkills || bundledPrompts || bundledMcps;
25
+ checks.push({
26
+ name: `Bundled assets (optional)`,
27
+ status: bundledOk ? 'pass' : anyBundled ? 'warn' : 'pass',
28
+ message: bundledOk
29
+ ? `Optional dev/catalog tree found beside the CLI package`
30
+ : anyBundled
31
+ ? `Partial catalog tree (skills: ${bundledSkills}, prompts: ${bundledPrompts}, mcps: ${bundledMcps})`
32
+ : `No bundled assets ship with the CLI — install via registry or import`,
33
+ });
34
+ let gitAvailable = false;
35
+ try {
36
+ execSync('git --version', { stdio: 'ignore' });
37
+ gitAvailable = true;
38
+ }
39
+ catch {
40
+ // Ignore
41
+ }
42
+ checks.push({
43
+ name: `Git installed`,
44
+ status: gitAvailable ? 'pass' : 'warn',
45
+ message: gitAvailable ? `Git is available` : `Git not found - install git for import/sync features`,
46
+ });
47
+ let ghAvailable = false;
48
+ try {
49
+ execSync('gh --version', { stdio: 'ignore' });
50
+ ghAvailable = true;
51
+ }
52
+ catch {
53
+ // Ignore
54
+ }
55
+ checks.push({
56
+ name: `GitHub CLI`,
57
+ status: ghAvailable ? 'pass' : 'warn',
58
+ message: ghAvailable ? `GitHub CLI is available` : `GitHub CLI not found - install for sync/GitHub storage`,
59
+ fix: ghAvailable ? undefined : `Install using winget/brew/apt or scoop`,
60
+ });
61
+ if (ghAvailable) {
62
+ let ghAuth = false;
63
+ try {
64
+ execSync('gh auth status', { stdio: 'ignore' });
65
+ ghAuth = true;
66
+ }
67
+ catch {
68
+ // Ignore
69
+ }
70
+ checks.push({
71
+ name: `GitHub auth`,
72
+ status: ghAuth ? 'pass' : 'warn',
73
+ message: ghAuth ? `Authenticated with GitHub` : `Not authenticated with GitHub`,
74
+ fix: ghAuth ? undefined : `Run 'gh auth login' to authenticate`,
75
+ });
76
+ }
77
+ const nodeVersion = process.version;
78
+ const isV18 = parseInt(nodeVersion.slice(1).split('.')[0], 10) >= 18;
79
+ checks.push({
80
+ name: `Node.js version`,
81
+ status: isV18 ? 'pass' : 'fail',
82
+ message: `Running ${nodeVersion}`,
83
+ fix: isV18 ? undefined : `Upgrade Node.js to v18 or newer.`,
84
+ });
85
+ if (dirExists) {
86
+ await migrateScopeLayout(targetDir);
87
+ try {
88
+ await lockService.read(scope);
89
+ checks.push({
90
+ name: `Lockfile valid`,
91
+ status: 'pass',
92
+ message: `Lockfile parsed successfully`,
93
+ });
94
+ }
95
+ catch {
96
+ checks.push({
97
+ name: `Lockfile valid`,
98
+ status: 'fail',
99
+ message: `Could not parse aman.lock`,
100
+ fix: `Remove and reinstall assets.`,
101
+ });
102
+ }
103
+ try {
104
+ const lock = await lockService.read(scope);
105
+ let missingCount = 0;
106
+ const allEntries = lock.assets;
107
+ for (const entry of allEntries) {
108
+ const typeRoot = entry.type === 'skill'
109
+ ? path.join(targetDir, 'skills')
110
+ : entry.type === 'prompt'
111
+ ? path.join(targetDir, 'prompts')
112
+ : path.join(targetDir, 'mcps');
113
+ const metaPath = metadataFilePath(assetDir(entry.type, typeRoot, entry.localName));
114
+ if (!exists(metaPath)) {
115
+ missingCount++;
116
+ }
117
+ }
118
+ checks.push({
119
+ name: `Metadata integrity`,
120
+ status: missingCount === 0 ? 'pass' : 'warn',
121
+ message: missingCount === 0
122
+ ? `All assets have valid metadata`
123
+ : `Found ${missingCount} assets with missing metadata`,
124
+ fix: missingCount === 0 ? undefined : `Re-install missing assets to regenerate metadata.`,
125
+ });
126
+ const violations = await findLayoutViolations(targetDir);
127
+ checks.push({
128
+ name: `Canonical asset layout`,
129
+ status: violations.length === 0 ? 'pass' : 'warn',
130
+ message: violations.length === 0
131
+ ? `All assets use directory layout (SKILL.md / PROMPT.md / mcp.json)`
132
+ : `${violations.length} layout issue(s): e.g. ${violations[0].type} "${violations[0].localName}" — ${violations[0].issue}`,
133
+ fix: violations.length === 0
134
+ ? undefined
135
+ : `Run any install command or open the dashboard to auto-migrate flat files to directories.`,
136
+ });
137
+ let mcpLocalMissing = 0;
138
+ let mcpEmptyValues = 0;
139
+ for (const entry of allEntries) {
140
+ if (entry.type !== 'mcp' || !entry.requiresLocalConfig)
141
+ continue;
142
+ const mcpDir = assetDir('mcp', path.join(targetDir, 'mcps'), entry.localName);
143
+ const localSecrets = mcpLocalFilePath(mcpDir);
144
+ if (!exists(localSecrets)) {
145
+ mcpLocalMissing++;
146
+ }
147
+ else {
148
+ mcpEmptyValues += await countEmptyMcpLocalValues(mcpDir);
149
+ }
150
+ }
151
+ checks.push({
152
+ name: `MCP local configuration`,
153
+ status: mcpLocalMissing === 0 ? 'pass' : 'warn',
154
+ message: mcpLocalMissing === 0
155
+ ? `All MCPs requiring local config have mcp.local.json`
156
+ : `${mcpLocalMissing} MCP(s) missing mcp.local.json`,
157
+ fix: mcpLocalMissing === 0
158
+ ? undefined
159
+ : `Re-install the MCP or create mcp.local.json in the asset directory.`,
160
+ });
161
+ if (mcpEmptyValues > 0) {
162
+ checks.push({
163
+ name: `MCP local secrets filled`,
164
+ status: 'warn',
165
+ message: `${mcpEmptyValues} empty value(s) in mcp.local.json — fill in required secrets`,
166
+ fix: `Edit mcps/{name}/mcp.local.json and set non-empty values for each key.`,
167
+ });
168
+ }
169
+ const gitignoreOk = await gitignoreIncludesMcpLocalAsync(targetDir);
170
+ checks.push({
171
+ name: `mcp.local.json gitignored`,
172
+ status: gitignoreOk ? 'pass' : 'fail',
173
+ message: gitignoreOk
174
+ ? `.gitignore excludes mcps/**/mcp.local.json`
175
+ : `mcp.local.json is not listed in ${path.join(targetDir, '.gitignore')}`,
176
+ fix: gitignoreOk ? undefined : `Add "mcps/**/mcp.local.json" to .gitignore at the scope root.`,
177
+ });
178
+ }
179
+ catch {
180
+ // Ignore
181
+ }
182
+ }
183
+ return checks;
184
+ }
185
+ }
186
+ export const doctorService = new DoctorService();
@@ -0,0 +1,42 @@
1
+ export type StorageMode = 'local' | 'github';
2
+ export interface EnvironmentManifest {
3
+ name: string;
4
+ version: 1;
5
+ createdAt: string;
6
+ storage: {
7
+ type: StorageMode;
8
+ repository?: string;
9
+ };
10
+ }
11
+ export interface InitLocalOptions {
12
+ storagePath?: string;
13
+ }
14
+ export interface InitGithubOptions {
15
+ repository: string;
16
+ mode: 'create' | 'existing';
17
+ }
18
+ export declare class EnvironmentService {
19
+ getActiveEnvironmentDir(): string;
20
+ getStorage(): EnvironmentManifest['storage'];
21
+ getProjectEnvironmentDir(): string;
22
+ isEnvironmentInitialized(scope?: 'global' | 'project'): boolean;
23
+ resolveStoragePath(storagePath?: string): string;
24
+ isGithubCliAvailable(): boolean;
25
+ installGithubCli(): void;
26
+ ensureEnvironment(baseDir: string, storage: EnvironmentManifest['storage']): Promise<void>;
27
+ ensureProjectEnvironment(): Promise<string>;
28
+ ensureActiveEnvironment(): Promise<string>;
29
+ initLocal(options?: InitLocalOptions): Promise<string>;
30
+ initGithub(options: InitGithubOptions): Promise<string>;
31
+ importStandardized(sourceDir: string, targetDir: string): Promise<void>;
32
+ private ensureGhAvailable;
33
+ private addKnownGithubCliPaths;
34
+ private githubCliInstallAttempts;
35
+ private ensureGhAuthenticated;
36
+ private cloneRepository;
37
+ private gitInit;
38
+ private gitCommit;
39
+ private gitCommitAndPush;
40
+ private createAndPushRepository;
41
+ }
42
+ export declare const environmentService: EnvironmentService;