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.
- package/LICENSE +21 -0
- package/README.md +116 -0
- package/dist/bin/aman.d.ts +2 -0
- package/dist/bin/aman.js +165 -0
- package/dist/cli/global-install.d.ts +7 -0
- package/dist/cli/global-install.js +36 -0
- package/dist/cli/help-text.d.ts +1 -0
- package/dist/cli/help-text.js +62 -0
- package/dist/cli/version.d.ts +1 -0
- package/dist/cli/version.js +6 -0
- package/dist/commands/backup.d.ts +11 -0
- package/dist/commands/backup.js +262 -0
- package/dist/commands/browse.d.ts +11 -0
- package/dist/commands/browse.js +641 -0
- package/dist/commands/cache.d.ts +1 -0
- package/dist/commands/cache.js +38 -0
- package/dist/commands/config.d.ts +4 -0
- package/dist/commands/config.js +146 -0
- package/dist/commands/dashboard.d.ts +1 -0
- package/dist/commands/dashboard.js +1004 -0
- package/dist/commands/doctor.d.ts +4 -0
- package/dist/commands/doctor.js +54 -0
- package/dist/commands/export.d.ts +1 -0
- package/dist/commands/export.js +137 -0
- package/dist/commands/help.d.ts +1 -0
- package/dist/commands/help.js +47 -0
- package/dist/commands/import-wizard.d.ts +7 -0
- package/dist/commands/import-wizard.js +374 -0
- package/dist/commands/import.d.ts +9 -0
- package/dist/commands/import.js +351 -0
- package/dist/commands/info.d.ts +1 -0
- package/dist/commands/info.js +174 -0
- package/dist/commands/init.d.ts +20 -0
- package/dist/commands/init.js +146 -0
- package/dist/commands/install.d.ts +10 -0
- package/dist/commands/install.js +342 -0
- package/dist/commands/pack.d.ts +23 -0
- package/dist/commands/pack.js +331 -0
- package/dist/commands/registry.d.ts +6 -0
- package/dist/commands/registry.js +218 -0
- package/dist/commands/remove.d.ts +1 -0
- package/dist/commands/remove.js +76 -0
- package/dist/commands/search.d.ts +7 -0
- package/dist/commands/search.js +295 -0
- package/dist/commands/stack.d.ts +18 -0
- package/dist/commands/stack.js +327 -0
- package/dist/commands/sync.d.ts +9 -0
- package/dist/commands/sync.js +428 -0
- package/dist/commands/update.d.ts +1 -0
- package/dist/commands/update.js +97 -0
- package/dist/config/features.d.ts +2 -0
- package/dist/config/features.js +2 -0
- package/dist/config/index.d.ts +13 -0
- package/dist/config/index.js +80 -0
- package/dist/config/paths.d.ts +23 -0
- package/dist/config/paths.js +45 -0
- package/dist/import/adapters.d.ts +14 -0
- package/dist/import/adapters.js +580 -0
- package/dist/import/discovery.service.d.ts +8 -0
- package/dist/import/discovery.service.js +26 -0
- package/dist/import/import.service.d.ts +7 -0
- package/dist/import/import.service.js +259 -0
- package/dist/import/types.d.ts +71 -0
- package/dist/import/types.js +1 -0
- package/dist/import/utils.d.ts +36 -0
- package/dist/import/utils.js +428 -0
- package/dist/marketplace/cache.d.ts +18 -0
- package/dist/marketplace/cache.js +141 -0
- package/dist/marketplace/github-search.d.ts +17 -0
- package/dist/marketplace/github-search.js +268 -0
- package/dist/marketplace/install-from-candidate.d.ts +6 -0
- package/dist/marketplace/install-from-candidate.js +14 -0
- package/dist/marketplace/install.d.ts +15 -0
- package/dist/marketplace/install.js +54 -0
- package/dist/marketplace/metadata-validator.d.ts +8 -0
- package/dist/marketplace/metadata-validator.js +79 -0
- package/dist/marketplace/types.d.ts +34 -0
- package/dist/marketplace/types.js +1 -0
- package/dist/providers/local.provider.d.ts +9 -0
- package/dist/providers/local.provider.js +51 -0
- package/dist/providers/provider.interface.d.ts +7 -0
- package/dist/providers/provider.interface.js +1 -0
- package/dist/providers/registry.provider.d.ts +2 -0
- package/dist/providers/registry.provider.js +42 -0
- package/dist/providers/skills-sh.provider.d.ts +11 -0
- package/dist/providers/skills-sh.provider.js +56 -0
- package/dist/registry/adapter.interface.d.ts +16 -0
- package/dist/registry/adapter.interface.js +1 -0
- package/dist/registry/errors.d.ts +5 -0
- package/dist/registry/errors.js +8 -0
- package/dist/registry/filesystem-registry.adapter.d.ts +25 -0
- package/dist/registry/filesystem-registry.adapter.js +288 -0
- package/dist/registry/github-registry.adapter.d.ts +11 -0
- package/dist/registry/github-registry.adapter.js +32 -0
- package/dist/registry/index.d.ts +8 -0
- package/dist/registry/index.js +8 -0
- package/dist/registry/local-registry.adapter.d.ts +6 -0
- package/dist/registry/local-registry.adapter.js +9 -0
- package/dist/registry/registry.service.d.ts +44 -0
- package/dist/registry/registry.service.js +163 -0
- package/dist/registry/slug-utils.d.ts +12 -0
- package/dist/registry/slug-utils.js +51 -0
- package/dist/registry/types.d.ts +160 -0
- package/dist/registry/types.js +1 -0
- package/dist/services/asset.service.d.ts +12 -0
- package/dist/services/asset.service.js +142 -0
- package/dist/services/backup.service.d.ts +8 -0
- package/dist/services/backup.service.js +169 -0
- package/dist/services/classification.service.d.ts +31 -0
- package/dist/services/classification.service.js +271 -0
- package/dist/services/config.service.d.ts +9 -0
- package/dist/services/config.service.js +20 -0
- package/dist/services/doctor.service.d.ts +5 -0
- package/dist/services/doctor.service.js +186 -0
- package/dist/services/environment.service.d.ts +42 -0
- package/dist/services/environment.service.js +227 -0
- package/dist/services/github.service.d.ts +7 -0
- package/dist/services/github.service.js +42 -0
- package/dist/services/lock.service.d.ts +12 -0
- package/dist/services/lock.service.js +71 -0
- package/dist/services/marketplace.service.d.ts +40 -0
- package/dist/services/marketplace.service.js +225 -0
- package/dist/services/pack.service.d.ts +9 -0
- package/dist/services/pack.service.js +193 -0
- package/dist/services/stack.service.d.ts +9 -0
- package/dist/services/stack.service.js +94 -0
- package/dist/storage/asset-layout.d.ts +46 -0
- package/dist/storage/asset-layout.js +277 -0
- package/dist/storage/filesystem.d.ts +12 -0
- package/dist/storage/filesystem.js +113 -0
- package/dist/storage/scan-by-type.d.ts +2 -0
- package/dist/storage/scan-by-type.js +8 -0
- package/dist/storage/scanner.d.ts +11 -0
- package/dist/storage/scanner.js +188 -0
- package/dist/types/asset-metadata.d.ts +84 -0
- package/dist/types/asset-metadata.js +104 -0
- package/dist/types/index.d.ts +212 -0
- package/dist/types/index.js +1 -0
- package/dist/ui/animations/ErrorIndicator.d.ts +5 -0
- package/dist/ui/animations/ErrorIndicator.js +6 -0
- package/dist/ui/animations/GithubIndicator.d.ts +6 -0
- package/dist/ui/animations/GithubIndicator.js +9 -0
- package/dist/ui/animations/ProgressBar.d.ts +5 -0
- package/dist/ui/animations/ProgressBar.js +15 -0
- package/dist/ui/animations/Spinner.d.ts +5 -0
- package/dist/ui/animations/Spinner.js +21 -0
- package/dist/ui/animations/SuccessIndicator.d.ts +5 -0
- package/dist/ui/animations/SuccessIndicator.js +6 -0
- package/dist/ui/animations/SyncActivity.d.ts +5 -0
- package/dist/ui/animations/SyncActivity.js +21 -0
- package/dist/ui/animations/TransitionScreen.d.ts +7 -0
- package/dist/ui/animations/TransitionScreen.js +25 -0
- package/dist/ui/animations/useAnimationMode.d.ts +1 -0
- package/dist/ui/animations/useAnimationMode.js +16 -0
- package/dist/ui/assetDisplay.d.ts +19 -0
- package/dist/ui/assetDisplay.js +59 -0
- package/dist/ui/components/Confirm.d.ts +8 -0
- package/dist/ui/components/Confirm.js +14 -0
- package/dist/ui/components/CustomSelect.d.ts +19 -0
- package/dist/ui/components/CustomSelect.js +13 -0
- package/dist/ui/components/Header.d.ts +6 -0
- package/dist/ui/components/Header.js +9 -0
- package/dist/ui/components/HealthReport.d.ts +7 -0
- package/dist/ui/components/HealthReport.js +13 -0
- package/dist/ui/components/MarketplaceInstallConfirm.d.ts +19 -0
- package/dist/ui/components/MarketplaceInstallConfirm.js +23 -0
- package/dist/ui/components/Narrator.d.ts +9 -0
- package/dist/ui/components/Narrator.js +26 -0
- package/dist/ui/components/ScopePrompt.d.ts +8 -0
- package/dist/ui/components/ScopePrompt.js +23 -0
- package/dist/ui/components/TooSmallScreen.d.ts +8 -0
- package/dist/ui/components/TooSmallScreen.js +6 -0
- package/dist/ui/date.d.ts +2 -0
- package/dist/ui/date.js +33 -0
- package/dist/ui/layout.d.ts +23 -0
- package/dist/ui/layout.js +44 -0
- package/dist/ui/list-item.d.ts +12 -0
- package/dist/ui/list-item.js +1 -0
- package/dist/ui/marketplaceDisplay.d.ts +10 -0
- package/dist/ui/marketplaceDisplay.js +36 -0
- package/dist/ui/theme.d.ts +42 -0
- package/dist/ui/theme.js +47 -0
- package/dist/utils/asset-list-fields.d.ts +11 -0
- package/dist/utils/asset-list-fields.js +28 -0
- package/dist/utils/error-message.d.ts +2 -0
- package/dist/utils/error-message.js +6 -0
- package/dist/utils/integrity.d.ts +9 -0
- package/dist/utils/integrity.js +23 -0
- package/dist/utils/lock-migrate.d.ts +25 -0
- package/dist/utils/lock-migrate.js +93 -0
- package/dist/utils/mcp-local.d.ts +15 -0
- package/dist/utils/mcp-local.js +129 -0
- package/dist/utils/slug.d.ts +6 -0
- package/dist/utils/slug.js +13 -0
- package/dist/utils/stack-normalize.d.ts +3 -0
- package/dist/utils/stack-normalize.js +43 -0
- 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,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;
|