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,428 @@
|
|
|
1
|
+
import os from 'os';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import { promises as fs } from 'fs';
|
|
4
|
+
import { exists, readJson, readFrontmatter } from '../storage/filesystem.js';
|
|
5
|
+
import { defaultSlugForName } from '../utils/slug.js';
|
|
6
|
+
import { randomUUID } from 'crypto';
|
|
7
|
+
export function expandHome(inputPath) {
|
|
8
|
+
if (inputPath === '~')
|
|
9
|
+
return os.homedir();
|
|
10
|
+
if (inputPath.startsWith('~/') || inputPath.startsWith(`~${path.sep}`)) {
|
|
11
|
+
return path.join(os.homedir(), inputPath.slice(2));
|
|
12
|
+
}
|
|
13
|
+
return inputPath;
|
|
14
|
+
}
|
|
15
|
+
export function vscodeUserMcpPath() {
|
|
16
|
+
if (process.platform === 'win32') {
|
|
17
|
+
return path.join(process.env.APPDATA ?? path.join(os.homedir(), 'AppData', 'Roaming'), 'Code', 'User', 'mcp.json');
|
|
18
|
+
}
|
|
19
|
+
if (process.platform === 'darwin') {
|
|
20
|
+
return path.join(os.homedir(), 'Library', 'Application Support', 'Code', 'User', 'mcp.json');
|
|
21
|
+
}
|
|
22
|
+
return path.join(os.homedir(), '.config', 'Code', 'User', 'mcp.json');
|
|
23
|
+
}
|
|
24
|
+
export function slugifyImportName(name) {
|
|
25
|
+
const base = name
|
|
26
|
+
.trim()
|
|
27
|
+
.toLowerCase()
|
|
28
|
+
.replace(/[^a-z0-9]+/g, '-')
|
|
29
|
+
.replace(/^-+|-+$/g, '');
|
|
30
|
+
return base || defaultSlugForName(name);
|
|
31
|
+
}
|
|
32
|
+
export function makeAssetId() {
|
|
33
|
+
return randomUUID();
|
|
34
|
+
}
|
|
35
|
+
export async function discoverSkillDir(skillDir, adapterId, originLabel) {
|
|
36
|
+
const skillMd = path.join(skillDir, 'SKILL.md');
|
|
37
|
+
if (!exists(skillMd))
|
|
38
|
+
return null;
|
|
39
|
+
const hasMeta = exists(path.join(skillDir, 'metadata.json'));
|
|
40
|
+
let description = '';
|
|
41
|
+
const fm = await readFrontmatter(skillMd);
|
|
42
|
+
if (fm) {
|
|
43
|
+
description = (typeof fm.description === 'string' ? fm.description : '') ||
|
|
44
|
+
(typeof fm.name === 'string' ? fm.name : '');
|
|
45
|
+
}
|
|
46
|
+
const name = path.basename(skillDir);
|
|
47
|
+
return {
|
|
48
|
+
id: makeAssetId(),
|
|
49
|
+
type: 'skill',
|
|
50
|
+
name: slugifyImportName(name),
|
|
51
|
+
description,
|
|
52
|
+
sourcePath: skillDir,
|
|
53
|
+
originLabel,
|
|
54
|
+
canonicalStatus: hasMeta ? 'canonical' : 'convertible',
|
|
55
|
+
canonicalNote: hasMeta ? undefined : 'Will generate metadata.json on import',
|
|
56
|
+
confidence: hasMeta ? 95 : 85,
|
|
57
|
+
adapterId,
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
export async function discoverPromptFile(filePath, adapterId, originLabel, suggestedName) {
|
|
61
|
+
const base = suggestedName ?? path.basename(filePath, path.extname(filePath));
|
|
62
|
+
let description = '';
|
|
63
|
+
const fm = await readFrontmatter(filePath);
|
|
64
|
+
if (fm && typeof fm.description === 'string') {
|
|
65
|
+
description = fm.description;
|
|
66
|
+
}
|
|
67
|
+
const canonicalStatus = path.basename(filePath).toUpperCase() === 'PROMPT.MD'
|
|
68
|
+
? 'canonical'
|
|
69
|
+
: 'convertible';
|
|
70
|
+
return {
|
|
71
|
+
id: makeAssetId(),
|
|
72
|
+
type: 'prompt',
|
|
73
|
+
name: slugifyImportName(base),
|
|
74
|
+
description,
|
|
75
|
+
sourcePath: filePath,
|
|
76
|
+
originLabel,
|
|
77
|
+
canonicalStatus,
|
|
78
|
+
canonicalNote: canonicalStatus === 'convertible' ? 'Markdown will be normalized to PROMPT.md layout' : undefined,
|
|
79
|
+
confidence: canonicalStatus === 'canonical' ? 95 : 75,
|
|
80
|
+
adapterId,
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
export async function listSkillDirs(root) {
|
|
84
|
+
if (!exists(root))
|
|
85
|
+
return [];
|
|
86
|
+
const { listDirs } = await import('../storage/filesystem.js');
|
|
87
|
+
const dirs = await listDirs(root);
|
|
88
|
+
return dirs.map((d) => path.join(root, d)).filter((d) => exists(path.join(d, 'SKILL.md')));
|
|
89
|
+
}
|
|
90
|
+
export function extractMcpServerMap(raw) {
|
|
91
|
+
if (raw.mcpServers && typeof raw.mcpServers === 'object' && raw.mcpServers !== null) {
|
|
92
|
+
return raw.mcpServers;
|
|
93
|
+
}
|
|
94
|
+
if (raw.servers && typeof raw.servers === 'object' && raw.servers !== null) {
|
|
95
|
+
return raw.servers;
|
|
96
|
+
}
|
|
97
|
+
return {};
|
|
98
|
+
}
|
|
99
|
+
export async function discoverMcpFromJsonFile(filePath, adapterId, originPrefix) {
|
|
100
|
+
if (!exists(filePath))
|
|
101
|
+
return [];
|
|
102
|
+
const raw = await readJson(filePath);
|
|
103
|
+
if (!raw)
|
|
104
|
+
return [];
|
|
105
|
+
const servers = extractMcpServerMap(raw);
|
|
106
|
+
const results = [];
|
|
107
|
+
for (const [serverName, config] of Object.entries(servers)) {
|
|
108
|
+
if (!config || typeof config !== 'object')
|
|
109
|
+
continue;
|
|
110
|
+
results.push({
|
|
111
|
+
id: makeAssetId(),
|
|
112
|
+
type: 'mcp',
|
|
113
|
+
name: slugifyImportName(serverName),
|
|
114
|
+
description: `MCP server "${serverName}" from ${originPrefix}`,
|
|
115
|
+
sourcePath: filePath,
|
|
116
|
+
originLabel: `${originPrefix} → ${serverName}`,
|
|
117
|
+
canonicalStatus: 'convertible',
|
|
118
|
+
canonicalNote: `Will extract server "${serverName}" into canonical mcp.json`,
|
|
119
|
+
confidence: 80,
|
|
120
|
+
adapterId,
|
|
121
|
+
mcpServerName: serverName,
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
return results;
|
|
125
|
+
}
|
|
126
|
+
/** Minimal TOML [mcp_servers.name] block parser for Codex config.toml */
|
|
127
|
+
export function parseCodexMcpBlocks(tomlText) {
|
|
128
|
+
const blocks = [];
|
|
129
|
+
const lines = tomlText.split(/\r?\n/);
|
|
130
|
+
let current = null;
|
|
131
|
+
for (const line of lines) {
|
|
132
|
+
const section = line.match(/^\[mcp_servers\.([^\]]+)\]\s*$/);
|
|
133
|
+
if (section) {
|
|
134
|
+
if (current)
|
|
135
|
+
blocks.push({ name: current.name, body: current.body.join('\n') });
|
|
136
|
+
current = { name: section[1], body: [] };
|
|
137
|
+
continue;
|
|
138
|
+
}
|
|
139
|
+
if (current && line.trim() && !line.startsWith('[')) {
|
|
140
|
+
current.body.push(line);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
if (current)
|
|
144
|
+
blocks.push({ name: current.name, body: current.body.join('\n') });
|
|
145
|
+
return blocks;
|
|
146
|
+
}
|
|
147
|
+
export function codexBlockToMcpJson(block) {
|
|
148
|
+
const server = {};
|
|
149
|
+
for (const line of block.body.split('\n')) {
|
|
150
|
+
const kv = line.match(/^([a-zA-Z0-9_]+)\s*=\s*(.+)$/);
|
|
151
|
+
if (!kv)
|
|
152
|
+
continue;
|
|
153
|
+
const key = kv[1];
|
|
154
|
+
let val = kv[2].trim();
|
|
155
|
+
if (typeof val === 'string' && val.startsWith('"') && val.endsWith('"')) {
|
|
156
|
+
val = val.slice(1, -1);
|
|
157
|
+
}
|
|
158
|
+
else if (val === 'true' || val === 'false') {
|
|
159
|
+
val = val === 'true';
|
|
160
|
+
}
|
|
161
|
+
server[key] = val;
|
|
162
|
+
}
|
|
163
|
+
if (typeof server.url === 'string') {
|
|
164
|
+
return {
|
|
165
|
+
mcpServers: {
|
|
166
|
+
[block.name]: {
|
|
167
|
+
type: 'http',
|
|
168
|
+
url: server.url,
|
|
169
|
+
...(typeof server.bearer_token_env_var === 'string'
|
|
170
|
+
? { headers: { Authorization: `Bearer $${server.bearer_token_env_var}` } }
|
|
171
|
+
: {}),
|
|
172
|
+
},
|
|
173
|
+
},
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
const command = typeof server.command === 'string' ? server.command : undefined;
|
|
177
|
+
const args = Array.isArray(server.args)
|
|
178
|
+
? server.args
|
|
179
|
+
: typeof server.args === 'string'
|
|
180
|
+
? [server.args]
|
|
181
|
+
: [];
|
|
182
|
+
return {
|
|
183
|
+
mcpServers: {
|
|
184
|
+
[block.name]: {
|
|
185
|
+
...(command ? { command, args } : {}),
|
|
186
|
+
...(server.env && typeof server.env === 'object' ? { env: server.env } : {}),
|
|
187
|
+
},
|
|
188
|
+
},
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
/**
|
|
192
|
+
* Discover rules-style markdown files as skills.
|
|
193
|
+
* Used by Cursor (.cursor/rules/, .cursorrules) and Windsurf (.windsurf/rules/, .windsurfrules).
|
|
194
|
+
*/
|
|
195
|
+
export async function discoverRulesAsSkills(rulePaths, dotfilePaths, adapterId, originPrefix) {
|
|
196
|
+
const found = [];
|
|
197
|
+
const seen = new Set();
|
|
198
|
+
// Scan directories of rule files
|
|
199
|
+
for (const rulesDir of rulePaths) {
|
|
200
|
+
if (!exists(rulesDir))
|
|
201
|
+
continue;
|
|
202
|
+
let files;
|
|
203
|
+
try {
|
|
204
|
+
files = await fs.readdir(rulesDir);
|
|
205
|
+
}
|
|
206
|
+
catch {
|
|
207
|
+
continue;
|
|
208
|
+
}
|
|
209
|
+
for (const file of files) {
|
|
210
|
+
if (!file.endsWith('.md') && !file.endsWith('.mdc'))
|
|
211
|
+
continue;
|
|
212
|
+
const filePath = path.join(rulesDir, file);
|
|
213
|
+
const key = `skill:${filePath}`;
|
|
214
|
+
if (seen.has(key))
|
|
215
|
+
continue;
|
|
216
|
+
seen.add(key);
|
|
217
|
+
let description = '';
|
|
218
|
+
const fm = await readFrontmatter(filePath);
|
|
219
|
+
if (fm && typeof fm.description === 'string') {
|
|
220
|
+
description = fm.description;
|
|
221
|
+
}
|
|
222
|
+
const name = file.replace(/\.(md|mdc)$/, '');
|
|
223
|
+
found.push({
|
|
224
|
+
id: makeAssetId(),
|
|
225
|
+
type: 'skill',
|
|
226
|
+
name: slugifyImportName(name),
|
|
227
|
+
description: description || `Rule file from ${adapterId}`,
|
|
228
|
+
sourcePath: filePath,
|
|
229
|
+
originLabel: `${originPrefix}/${file}`,
|
|
230
|
+
canonicalStatus: 'convertible',
|
|
231
|
+
canonicalNote: 'Rule file will be converted to SKILL.md',
|
|
232
|
+
confidence: 80,
|
|
233
|
+
adapterId,
|
|
234
|
+
provenance: {
|
|
235
|
+
tool: adapterId,
|
|
236
|
+
sourcePath: filePath,
|
|
237
|
+
importedAt: new Date().toISOString(),
|
|
238
|
+
},
|
|
239
|
+
});
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
// Scan dotfiles (.cursorrules, .windsurfrules)
|
|
243
|
+
for (const dotfile of dotfilePaths) {
|
|
244
|
+
if (!exists(dotfile))
|
|
245
|
+
continue;
|
|
246
|
+
const key = `skill:${dotfile}`;
|
|
247
|
+
if (seen.has(key))
|
|
248
|
+
continue;
|
|
249
|
+
seen.add(key);
|
|
250
|
+
const baseName = path.basename(dotfile).replace(/^\./, '');
|
|
251
|
+
found.push({
|
|
252
|
+
id: makeAssetId(),
|
|
253
|
+
type: 'skill',
|
|
254
|
+
name: slugifyImportName(baseName),
|
|
255
|
+
description: `Root rules file from ${adapterId}`,
|
|
256
|
+
sourcePath: dotfile,
|
|
257
|
+
originLabel: path.basename(dotfile),
|
|
258
|
+
canonicalStatus: 'convertible',
|
|
259
|
+
canonicalNote: 'Dotfile will be converted to SKILL.md',
|
|
260
|
+
confidence: 75,
|
|
261
|
+
adapterId,
|
|
262
|
+
provenance: {
|
|
263
|
+
tool: adapterId,
|
|
264
|
+
sourcePath: dotfile,
|
|
265
|
+
importedAt: new Date().toISOString(),
|
|
266
|
+
},
|
|
267
|
+
});
|
|
268
|
+
}
|
|
269
|
+
return found;
|
|
270
|
+
}
|
|
271
|
+
/**
|
|
272
|
+
* Discover Continue.dev prompt files from ~/.continue/prompts/.
|
|
273
|
+
*/
|
|
274
|
+
export async function discoverContinuePrompts(promptsDir, adapterId) {
|
|
275
|
+
if (!exists(promptsDir))
|
|
276
|
+
return [];
|
|
277
|
+
const found = [];
|
|
278
|
+
let files;
|
|
279
|
+
try {
|
|
280
|
+
files = await fs.readdir(promptsDir);
|
|
281
|
+
}
|
|
282
|
+
catch {
|
|
283
|
+
return [];
|
|
284
|
+
}
|
|
285
|
+
for (const file of files) {
|
|
286
|
+
if (!file.endsWith('.md') && !file.endsWith('.prompt'))
|
|
287
|
+
continue;
|
|
288
|
+
const filePath = path.join(promptsDir, file);
|
|
289
|
+
let description = '';
|
|
290
|
+
const fm = await readFrontmatter(filePath);
|
|
291
|
+
if (fm && typeof fm.description === 'string') {
|
|
292
|
+
description = fm.description;
|
|
293
|
+
}
|
|
294
|
+
const name = file.replace(/\.(md|prompt)$/, '');
|
|
295
|
+
found.push({
|
|
296
|
+
id: makeAssetId(),
|
|
297
|
+
type: 'prompt',
|
|
298
|
+
name: slugifyImportName(name),
|
|
299
|
+
description: description || `Continue prompt "${name}"`,
|
|
300
|
+
sourcePath: filePath,
|
|
301
|
+
originLabel: `~/.continue/prompts/${file}`,
|
|
302
|
+
canonicalStatus: 'convertible',
|
|
303
|
+
canonicalNote: 'Prompt file will be normalized to PROMPT.md',
|
|
304
|
+
confidence: 85,
|
|
305
|
+
adapterId,
|
|
306
|
+
provenance: {
|
|
307
|
+
tool: adapterId,
|
|
308
|
+
sourcePath: filePath,
|
|
309
|
+
importedAt: new Date().toISOString(),
|
|
310
|
+
},
|
|
311
|
+
});
|
|
312
|
+
}
|
|
313
|
+
return found;
|
|
314
|
+
}
|
|
315
|
+
/**
|
|
316
|
+
* Discover MCP servers from Continue's config.json (legacy) or config.yaml.
|
|
317
|
+
*/
|
|
318
|
+
export async function discoverContinueMcpFromConfig(configPath, adapterId) {
|
|
319
|
+
if (!exists(configPath))
|
|
320
|
+
return [];
|
|
321
|
+
try {
|
|
322
|
+
const text = await fs.readFile(configPath, 'utf-8');
|
|
323
|
+
let data = null;
|
|
324
|
+
if (configPath.endsWith('.json')) {
|
|
325
|
+
data = JSON.parse(text);
|
|
326
|
+
}
|
|
327
|
+
else if (configPath.endsWith('.yaml') || configPath.endsWith('.yml')) {
|
|
328
|
+
const yaml = await import('js-yaml');
|
|
329
|
+
const parsed = yaml.load(text);
|
|
330
|
+
if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) {
|
|
331
|
+
data = parsed;
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
if (!data)
|
|
335
|
+
return [];
|
|
336
|
+
const servers = extractMcpServerMap(data);
|
|
337
|
+
const results = [];
|
|
338
|
+
for (const [serverName, config] of Object.entries(servers)) {
|
|
339
|
+
if (!config || typeof config !== 'object')
|
|
340
|
+
continue;
|
|
341
|
+
const home = os.homedir();
|
|
342
|
+
results.push({
|
|
343
|
+
id: makeAssetId(),
|
|
344
|
+
type: 'mcp',
|
|
345
|
+
name: slugifyImportName(serverName),
|
|
346
|
+
description: `Continue MCP server "${serverName}"`,
|
|
347
|
+
sourcePath: configPath,
|
|
348
|
+
originLabel: `${configPath.replace(home, '~')} → ${serverName}`,
|
|
349
|
+
canonicalStatus: 'convertible',
|
|
350
|
+
canonicalNote: `Will extract server "${serverName}" into canonical mcp.json`,
|
|
351
|
+
confidence: 80,
|
|
352
|
+
adapterId,
|
|
353
|
+
mcpServerName: serverName,
|
|
354
|
+
provenance: {
|
|
355
|
+
tool: adapterId,
|
|
356
|
+
sourcePath: configPath,
|
|
357
|
+
importedAt: new Date().toISOString(),
|
|
358
|
+
},
|
|
359
|
+
});
|
|
360
|
+
}
|
|
361
|
+
return results;
|
|
362
|
+
}
|
|
363
|
+
catch {
|
|
364
|
+
return [];
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
/**
|
|
368
|
+
* Discover canonical assets from an existing Aman environment directory.
|
|
369
|
+
*/
|
|
370
|
+
export async function discoverAmanEnvironmentAssets(envRoot, adapterId) {
|
|
371
|
+
const found = [];
|
|
372
|
+
const home = os.homedir();
|
|
373
|
+
const typeMap = [
|
|
374
|
+
{ type: 'skill', dir: path.join(envRoot, 'skills'), contentFile: 'SKILL.md' },
|
|
375
|
+
{ type: 'prompt', dir: path.join(envRoot, 'prompts'), contentFile: 'PROMPT.md' },
|
|
376
|
+
{ type: 'mcp', dir: path.join(envRoot, 'mcps'), contentFile: 'mcp.json' },
|
|
377
|
+
];
|
|
378
|
+
for (const { type, dir, contentFile } of typeMap) {
|
|
379
|
+
if (!exists(dir))
|
|
380
|
+
continue;
|
|
381
|
+
let entries;
|
|
382
|
+
try {
|
|
383
|
+
const dirents = await fs.readdir(dir, { withFileTypes: true });
|
|
384
|
+
entries = dirents.filter((d) => d.isDirectory()).map((d) => d.name);
|
|
385
|
+
}
|
|
386
|
+
catch {
|
|
387
|
+
continue;
|
|
388
|
+
}
|
|
389
|
+
for (const name of entries) {
|
|
390
|
+
const assetPath = path.join(dir, name);
|
|
391
|
+
const content = path.join(assetPath, contentFile);
|
|
392
|
+
if (!exists(content))
|
|
393
|
+
continue;
|
|
394
|
+
const hasMeta = exists(path.join(assetPath, 'metadata.json'));
|
|
395
|
+
let description = '';
|
|
396
|
+
if (hasMeta) {
|
|
397
|
+
const meta = await readJson(path.join(assetPath, 'metadata.json'));
|
|
398
|
+
if (meta && typeof meta.description === 'string') {
|
|
399
|
+
description = meta.description;
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
else if (type === 'skill' || type === 'prompt') {
|
|
403
|
+
const fm = await readFrontmatter(content);
|
|
404
|
+
if (fm && typeof fm.description === 'string') {
|
|
405
|
+
description = fm.description;
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
found.push({
|
|
409
|
+
id: makeAssetId(),
|
|
410
|
+
type,
|
|
411
|
+
name: slugifyImportName(name),
|
|
412
|
+
description,
|
|
413
|
+
sourcePath: assetPath,
|
|
414
|
+
originLabel: assetPath.replace(home, '~'),
|
|
415
|
+
canonicalStatus: hasMeta ? 'canonical' : 'convertible',
|
|
416
|
+
canonicalNote: hasMeta ? undefined : 'Will generate metadata.json on import',
|
|
417
|
+
confidence: hasMeta ? 95 : 85,
|
|
418
|
+
adapterId,
|
|
419
|
+
provenance: {
|
|
420
|
+
tool: 'aman',
|
|
421
|
+
sourcePath: assetPath,
|
|
422
|
+
importedAt: new Date().toISOString(),
|
|
423
|
+
},
|
|
424
|
+
});
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
return found;
|
|
428
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { MarketplaceAsset } from './types.js';
|
|
2
|
+
export declare const MARKETPLACE_CACHE_DIR: string;
|
|
3
|
+
export declare const MARKETPLACE_CACHE_TTL_MS: number;
|
|
4
|
+
export declare function marketplaceCacheKey(query: string, typeFilter: string): string;
|
|
5
|
+
export declare function readMarketplaceCache(query: string, typeFilter: string): Promise<{
|
|
6
|
+
assets: MarketplaceAsset[];
|
|
7
|
+
fetchedAt: Date;
|
|
8
|
+
ageMs: number;
|
|
9
|
+
} | null>;
|
|
10
|
+
export declare function writeMarketplaceCache(query: string, typeFilter: string, assets: MarketplaceAsset[]): Promise<void>;
|
|
11
|
+
export declare function isCacheFresh(ageMs: number): boolean;
|
|
12
|
+
export declare function getMarketplaceCacheStatus(): Promise<{
|
|
13
|
+
entryCount: number;
|
|
14
|
+
totalBytes: number;
|
|
15
|
+
oldestAgeMinutes: number | null;
|
|
16
|
+
newestAgeMinutes: number | null;
|
|
17
|
+
}>;
|
|
18
|
+
export declare function clearMarketplaceCache(): Promise<number>;
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
import path from 'path';
|
|
2
|
+
import { createHash } from 'crypto';
|
|
3
|
+
import { GLOBAL_CACHE } from '../config/paths.js';
|
|
4
|
+
import { ensureDir, readJson, writeJson, removeDir } from '../storage/filesystem.js';
|
|
5
|
+
import { promises as fs } from 'fs';
|
|
6
|
+
import { parsePublisherMetadata } from './metadata-validator.js';
|
|
7
|
+
export const MARKETPLACE_CACHE_DIR = path.join(GLOBAL_CACHE, 'marketplace');
|
|
8
|
+
const INDEX_FILE = path.join(MARKETPLACE_CACHE_DIR, 'index.json');
|
|
9
|
+
export const MARKETPLACE_CACHE_TTL_MS = 60 * 60 * 1000;
|
|
10
|
+
export function marketplaceCacheKey(query, typeFilter) {
|
|
11
|
+
return createHash('sha256').update(`${query}\0${typeFilter}`).digest('hex').slice(0, 24);
|
|
12
|
+
}
|
|
13
|
+
function cacheFilePath(key) {
|
|
14
|
+
return path.join(MARKETPLACE_CACHE_DIR, `${key}.json`);
|
|
15
|
+
}
|
|
16
|
+
async function readIndex() {
|
|
17
|
+
const data = await readJson(INDEX_FILE);
|
|
18
|
+
if (data?.version === 1 && Array.isArray(data.entries))
|
|
19
|
+
return data;
|
|
20
|
+
return { version: 1, entries: [] };
|
|
21
|
+
}
|
|
22
|
+
async function writeIndex(index) {
|
|
23
|
+
await ensureDir(MARKETPLACE_CACHE_DIR);
|
|
24
|
+
await writeJson(INDEX_FILE, index);
|
|
25
|
+
}
|
|
26
|
+
function validateCachedAsset(raw) {
|
|
27
|
+
if (!raw || typeof raw !== 'object')
|
|
28
|
+
return null;
|
|
29
|
+
const a = raw;
|
|
30
|
+
if (a.type !== 'skill' && a.type !== 'prompt' && a.type !== 'mcp')
|
|
31
|
+
return null;
|
|
32
|
+
if (typeof a.slug !== 'string' || typeof a.name !== 'string')
|
|
33
|
+
return null;
|
|
34
|
+
if (typeof a.source !== 'string' || !a.source.startsWith('github:'))
|
|
35
|
+
return null;
|
|
36
|
+
try {
|
|
37
|
+
parsePublisherMetadata({
|
|
38
|
+
schemaVersion: 1,
|
|
39
|
+
id: a.id ?? a.slug,
|
|
40
|
+
slug: a.slug,
|
|
41
|
+
type: a.type,
|
|
42
|
+
name: a.localName ?? a.name,
|
|
43
|
+
description: a.description ?? '',
|
|
44
|
+
version: a.version ?? '1.0.0',
|
|
45
|
+
author: a.author ?? 'unknown',
|
|
46
|
+
tags: a.tags ?? [],
|
|
47
|
+
integrity: { algorithm: 'sha256', checksum: a.checksum, signature: null },
|
|
48
|
+
}, a.type, a.localName ?? a.name, a.slug, a.author ?? 'unknown');
|
|
49
|
+
}
|
|
50
|
+
catch {
|
|
51
|
+
return null;
|
|
52
|
+
}
|
|
53
|
+
return {
|
|
54
|
+
...a,
|
|
55
|
+
verified: a.slug.startsWith('@aman/'),
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
export async function readMarketplaceCache(query, typeFilter) {
|
|
59
|
+
const key = marketplaceCacheKey(query, typeFilter);
|
|
60
|
+
const file = cacheFilePath(key);
|
|
61
|
+
const payload = await readJson(file);
|
|
62
|
+
if (!payload?.fetchedAt || !Array.isArray(payload.assets))
|
|
63
|
+
return null;
|
|
64
|
+
const valid = [];
|
|
65
|
+
for (const item of payload.assets) {
|
|
66
|
+
const v = validateCachedAsset(item);
|
|
67
|
+
if (v)
|
|
68
|
+
valid.push(v);
|
|
69
|
+
}
|
|
70
|
+
if (valid.length !== payload.assets.length) {
|
|
71
|
+
await fs.unlink(file).catch(() => { });
|
|
72
|
+
const index = await readIndex();
|
|
73
|
+
index.entries = index.entries.filter((e) => e.key !== key);
|
|
74
|
+
await writeIndex(index);
|
|
75
|
+
return null;
|
|
76
|
+
}
|
|
77
|
+
const fetchedAt = new Date(payload.fetchedAt);
|
|
78
|
+
return { assets: valid, fetchedAt, ageMs: Date.now() - fetchedAt.getTime() };
|
|
79
|
+
}
|
|
80
|
+
export async function writeMarketplaceCache(query, typeFilter, assets) {
|
|
81
|
+
await ensureDir(MARKETPLACE_CACHE_DIR);
|
|
82
|
+
const key = marketplaceCacheKey(query, typeFilter);
|
|
83
|
+
const fetchedAt = new Date().toISOString();
|
|
84
|
+
const payload = {
|
|
85
|
+
version: 1,
|
|
86
|
+
fetchedAt,
|
|
87
|
+
query,
|
|
88
|
+
typeFilter,
|
|
89
|
+
assets,
|
|
90
|
+
};
|
|
91
|
+
await writeJson(cacheFilePath(key), payload);
|
|
92
|
+
const index = await readIndex();
|
|
93
|
+
const entry = {
|
|
94
|
+
key,
|
|
95
|
+
query,
|
|
96
|
+
typeFilter,
|
|
97
|
+
fetchedAt,
|
|
98
|
+
file: `${key}.json`,
|
|
99
|
+
assetCount: assets.length,
|
|
100
|
+
};
|
|
101
|
+
const existing = index.entries.findIndex((e) => e.key === key);
|
|
102
|
+
if (existing >= 0)
|
|
103
|
+
index.entries[existing] = entry;
|
|
104
|
+
else
|
|
105
|
+
index.entries.push(entry);
|
|
106
|
+
await writeIndex(index);
|
|
107
|
+
}
|
|
108
|
+
export function isCacheFresh(ageMs) {
|
|
109
|
+
return ageMs < MARKETPLACE_CACHE_TTL_MS;
|
|
110
|
+
}
|
|
111
|
+
export async function getMarketplaceCacheStatus() {
|
|
112
|
+
await ensureDir(MARKETPLACE_CACHE_DIR);
|
|
113
|
+
const index = await readIndex();
|
|
114
|
+
let totalBytes = 0;
|
|
115
|
+
const ages = [];
|
|
116
|
+
const now = Date.now();
|
|
117
|
+
for (const entry of index.entries) {
|
|
118
|
+
const file = cacheFilePath(entry.key);
|
|
119
|
+
try {
|
|
120
|
+
const stat = await fs.stat(file);
|
|
121
|
+
totalBytes += stat.size;
|
|
122
|
+
ages.push(now - new Date(entry.fetchedAt).getTime());
|
|
123
|
+
}
|
|
124
|
+
catch {
|
|
125
|
+
// orphan index entry
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
return {
|
|
129
|
+
entryCount: index.entries.length,
|
|
130
|
+
totalBytes,
|
|
131
|
+
oldestAgeMinutes: ages.length ? Math.round(Math.max(...ages) / 60000) : null,
|
|
132
|
+
newestAgeMinutes: ages.length ? Math.round(Math.min(...ages) / 60000) : null,
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
export async function clearMarketplaceCache() {
|
|
136
|
+
const index = await readIndex();
|
|
137
|
+
const count = index.entries.length;
|
|
138
|
+
await removeDir(MARKETPLACE_CACHE_DIR);
|
|
139
|
+
await ensureDir(MARKETPLACE_CACHE_DIR);
|
|
140
|
+
return count;
|
|
141
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { MarketplaceAsset, MarketplaceSearchResult, MarketplaceTypeFilter } from './types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Search GitHub for `topic:aman-asset` publisher repos and load validated metadata.
|
|
4
|
+
* Uses ~/.aman/cache/marketplace/ with 1h TTL; serves stale cache on rate limit/offline.
|
|
5
|
+
*/
|
|
6
|
+
export declare function searchGitHubMarketplace(options: {
|
|
7
|
+
query?: string;
|
|
8
|
+
typeFilter?: MarketplaceTypeFilter;
|
|
9
|
+
sort?: 'stars' | 'recent' | 'name';
|
|
10
|
+
forceRefresh?: boolean;
|
|
11
|
+
}): Promise<MarketplaceSearchResult>;
|
|
12
|
+
export declare function indexMarketplaceAssets(assets: MarketplaceAsset[]): void;
|
|
13
|
+
export declare function findMarketplaceAsset(ref: string): MarketplaceAsset | undefined;
|
|
14
|
+
export declare function loadMarketplaceIndex(options?: {
|
|
15
|
+
query?: string;
|
|
16
|
+
typeFilter?: MarketplaceTypeFilter;
|
|
17
|
+
}): Promise<MarketplaceSearchResult>;
|