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,268 @@
1
+ import { defaultSlugForName } from '../utils/slug.js';
2
+ import { parsePublisherMetadata, isVerifiedPublisherSlug } from './metadata-validator.js';
3
+ import { isCacheFresh, readMarketplaceCache, writeMarketplaceCache, } from './cache.js';
4
+ const GITHUB_API = 'https://api.github.com';
5
+ const REQUEST_TIMEOUT_MS = 10_000;
6
+ const USER_AGENT = 'aman-cli-marketplace';
7
+ class GitHubMarketplaceError extends Error {
8
+ rateLimited;
9
+ offline;
10
+ constructor(message, rateLimited = false, offline = false) {
11
+ super(message);
12
+ this.rateLimited = rateLimited;
13
+ this.offline = offline;
14
+ this.name = 'GitHubMarketplaceError';
15
+ }
16
+ }
17
+ async function fetchWithTimeout(url, init) {
18
+ const controller = new AbortController();
19
+ const timer = setTimeout(() => controller.abort(), REQUEST_TIMEOUT_MS);
20
+ try {
21
+ return await fetch(url, {
22
+ ...init,
23
+ signal: controller.signal,
24
+ headers: {
25
+ Accept: 'application/vnd.github+json',
26
+ 'User-Agent': USER_AGENT,
27
+ ...(init?.headers ?? {}),
28
+ },
29
+ });
30
+ }
31
+ catch (err) {
32
+ if (err instanceof Error && err.name === 'AbortError') {
33
+ throw new GitHubMarketplaceError('GitHub request timed out (10s)', false, false);
34
+ }
35
+ throw new GitHubMarketplaceError('Network unavailable', false, true);
36
+ }
37
+ finally {
38
+ clearTimeout(timer);
39
+ }
40
+ }
41
+ function buildSearchQuery(userQuery) {
42
+ const q = userQuery.trim();
43
+ if (!q)
44
+ return 'topic:aman-asset';
45
+ return `topic:aman-asset ${q}`;
46
+ }
47
+ async function searchRepositories(userQuery) {
48
+ const q = encodeURIComponent(buildSearchQuery(userQuery));
49
+ const url = `${GITHUB_API}/search/repositories?q=${q}&sort=stars&order=desc&per_page=30`;
50
+ const res = await fetchWithTimeout(url);
51
+ if (res.status === 403 || res.status === 429) {
52
+ const remaining = res.headers.get('x-ratelimit-remaining');
53
+ throw new GitHubMarketplaceError(`GitHub API rate limit exceeded${remaining !== null ? ` (${remaining} remaining)` : ''}. Try again later or use cached results.`, true, false);
54
+ }
55
+ if (!res.ok) {
56
+ throw new GitHubMarketplaceError(`GitHub search failed (${res.status})`, false, res.status >= 500);
57
+ }
58
+ const data = (await res.json());
59
+ return data.items ?? [];
60
+ }
61
+ async function listRepoDirectories(owner, repo, branch, typeFolder) {
62
+ const url = `${GITHUB_API}/repos/${owner}/${repo}/contents/${typeFolder}?ref=${encodeURIComponent(branch)}`;
63
+ const res = await fetchWithTimeout(url);
64
+ if (res.status === 404)
65
+ return [];
66
+ if (res.status === 403 || res.status === 429) {
67
+ throw new GitHubMarketplaceError('GitHub API rate limit exceeded while listing assets', true, false);
68
+ }
69
+ if (!res.ok)
70
+ return [];
71
+ const entries = (await res.json());
72
+ if (!Array.isArray(entries))
73
+ return [];
74
+ return entries.filter((e) => e.type === 'dir').map((e) => e.name);
75
+ }
76
+ async function fetchRawMetadata(owner, repo, branch, type, localName) {
77
+ const typeFolder = type === 'skill' ? 'skills' : type === 'prompt' ? 'prompts' : 'mcps';
78
+ const url = `https://raw.githubusercontent.com/${owner}/${repo}/${branch}/${typeFolder}/${localName}/metadata.json`;
79
+ const res = await fetchWithTimeout(url);
80
+ if (res.status === 404)
81
+ return null;
82
+ if (res.status === 403 || res.status === 429) {
83
+ throw new GitHubMarketplaceError('GitHub rate limit exceeded while fetching metadata', true, false);
84
+ }
85
+ if (!res.ok)
86
+ return null;
87
+ try {
88
+ return await res.json();
89
+ }
90
+ catch {
91
+ return null;
92
+ }
93
+ }
94
+ async function discoverAssetsInRepo(item) {
95
+ const [owner, repo] = item.full_name.split('/');
96
+ if (!owner || !repo)
97
+ return [];
98
+ const branch = item.default_branch || 'main';
99
+ const source = `github:${owner}/${repo}`;
100
+ const stars = item.stargazers_count ?? 0;
101
+ const assets = [];
102
+ const typeFolders = [
103
+ { type: 'skill', folder: 'skills' },
104
+ { type: 'prompt', folder: 'prompts' },
105
+ { type: 'mcp', folder: 'mcps' },
106
+ ];
107
+ for (const { type, folder } of typeFolders) {
108
+ let names;
109
+ try {
110
+ names = await listRepoDirectories(owner, repo, branch, folder);
111
+ }
112
+ catch {
113
+ continue;
114
+ }
115
+ for (const localName of names) {
116
+ try {
117
+ const raw = await fetchRawMetadata(owner, repo, branch, type, localName);
118
+ if (!raw)
119
+ continue;
120
+ const fallbackSlug = defaultSlugForName(localName, owner);
121
+ const meta = parsePublisherMetadata(raw, type, localName, fallbackSlug, owner);
122
+ const checksum = meta.integrity?.checksum ??
123
+ 'sha256:0000000000000000000000000000000000000000000000000000000000000000';
124
+ assets.push({
125
+ slug: meta.slug,
126
+ type: meta.type,
127
+ name: meta.name,
128
+ localName: meta.name,
129
+ description: meta.description || item.description || '',
130
+ version: meta.version,
131
+ author: meta.author,
132
+ tags: meta.tags,
133
+ stars,
134
+ source,
135
+ verified: isVerifiedPublisherSlug(meta.slug),
136
+ owner,
137
+ repo,
138
+ defaultBranch: branch,
139
+ contentPath: `${folder}/${localName}`,
140
+ checksum,
141
+ id: meta.id,
142
+ updatedAt: meta.updatedAt,
143
+ });
144
+ }
145
+ catch {
146
+ // Skip invalid or rejected metadata
147
+ }
148
+ }
149
+ }
150
+ return assets;
151
+ }
152
+ function filterByType(assets, typeFilter) {
153
+ if (typeFilter === 'all')
154
+ return assets;
155
+ return assets.filter((a) => a.type === typeFilter);
156
+ }
157
+ function filterByQuery(assets, query) {
158
+ const q = query.trim().toLowerCase();
159
+ if (!q)
160
+ return assets;
161
+ return assets.filter((a) => {
162
+ const haystack = [a.name, a.slug, a.description, a.author, ...(a.tags ?? [])]
163
+ .join(' ')
164
+ .toLowerCase();
165
+ return haystack.includes(q);
166
+ });
167
+ }
168
+ function sortAssets(assets, sort) {
169
+ const copy = [...assets];
170
+ if (sort === 'stars') {
171
+ return copy.sort((a, b) => b.stars - a.stars || a.name.localeCompare(b.name));
172
+ }
173
+ if (sort === 'recent') {
174
+ return copy.sort((a, b) => {
175
+ const ta = a.updatedAt ? Date.parse(a.updatedAt) : 0;
176
+ const tb = b.updatedAt ? Date.parse(b.updatedAt) : 0;
177
+ return tb - ta || b.stars - a.stars;
178
+ });
179
+ }
180
+ return copy.sort((a, b) => a.name.localeCompare(b.name));
181
+ }
182
+ function cacheAgeMinutes(ageMs) {
183
+ return Math.max(0, Math.round(ageMs / 60000));
184
+ }
185
+ /**
186
+ * Search GitHub for `topic:aman-asset` publisher repos and load validated metadata.
187
+ * Uses ~/.aman/cache/marketplace/ with 1h TTL; serves stale cache on rate limit/offline.
188
+ */
189
+ export async function searchGitHubMarketplace(options) {
190
+ const query = options.query ?? '';
191
+ const typeFilter = options.typeFilter ?? 'all';
192
+ const sort = options.sort ?? 'stars';
193
+ const cached = await readMarketplaceCache(query, typeFilter);
194
+ const fresh = cached && isCacheFresh(cached.ageMs);
195
+ if (cached && fresh && !options.forceRefresh) {
196
+ return {
197
+ assets: sortAssets(filterByQuery(cached.assets, query), sort),
198
+ fromCache: true,
199
+ cacheAgeMinutes: cacheAgeMinutes(cached.ageMs),
200
+ rateLimited: false,
201
+ offline: false,
202
+ };
203
+ }
204
+ try {
205
+ const repos = await searchRepositories(query);
206
+ const all = [];
207
+ for (const repo of repos) {
208
+ const discovered = await discoverAssetsInRepo(repo);
209
+ all.push(...discovered);
210
+ }
211
+ const filtered = filterByQuery(filterByType(all, typeFilter), query);
212
+ const sorted = sortAssets(filtered, sort);
213
+ await writeMarketplaceCache(query, typeFilter, sorted);
214
+ return {
215
+ assets: sorted,
216
+ fromCache: false,
217
+ cacheAgeMinutes: 0,
218
+ rateLimited: false,
219
+ offline: false,
220
+ };
221
+ }
222
+ catch (err) {
223
+ const rateLimited = err instanceof GitHubMarketplaceError && err.rateLimited;
224
+ const offline = err instanceof GitHubMarketplaceError && err.offline;
225
+ if (cached) {
226
+ return {
227
+ assets: sortAssets(filterByQuery(cached.assets, query), sort),
228
+ fromCache: true,
229
+ cacheAgeMinutes: cacheAgeMinutes(cached.ageMs),
230
+ rateLimited,
231
+ offline,
232
+ message: err instanceof Error ? err.message : String(err),
233
+ };
234
+ }
235
+ return {
236
+ assets: [],
237
+ fromCache: false,
238
+ cacheAgeMinutes: null,
239
+ rateLimited,
240
+ offline,
241
+ message: offline || rateLimited
242
+ ? 'Marketplace unavailable (offline). No cached results.'
243
+ : err instanceof Error
244
+ ? err.message
245
+ : String(err),
246
+ };
247
+ }
248
+ }
249
+ const assetIndex = new Map();
250
+ export function indexMarketplaceAssets(assets) {
251
+ for (const a of assets) {
252
+ assetIndex.set(a.slug.toLowerCase(), a);
253
+ assetIndex.set(a.name.toLowerCase(), a);
254
+ assetIndex.set(a.localName.toLowerCase(), a);
255
+ }
256
+ }
257
+ export function findMarketplaceAsset(ref) {
258
+ const key = ref.trim().toLowerCase();
259
+ return assetIndex.get(key);
260
+ }
261
+ export async function loadMarketplaceIndex(options) {
262
+ const result = await searchGitHubMarketplace({
263
+ query: options?.query ?? '',
264
+ typeFilter: options?.typeFilter ?? 'all',
265
+ });
266
+ indexMarketplaceAssets(result.assets);
267
+ return result;
268
+ }
@@ -0,0 +1,6 @@
1
+ import { Scope } from '../types/index.js';
2
+ import { InstallCandidate } from '../services/marketplace.service.js';
3
+ export declare function installFromCandidate(candidate: InstallCandidate, scope: Scope): Promise<{
4
+ path: string;
5
+ checksum: string;
6
+ }>;
@@ -0,0 +1,14 @@
1
+ import { assetService } from '../services/asset.service.js';
2
+ import { installMarketplaceAsset } from './install.js';
3
+ export async function installFromCandidate(candidate, scope) {
4
+ if (candidate.marketplaceAsset) {
5
+ const result = await installMarketplaceAsset(candidate.marketplaceAsset, scope);
6
+ return { path: result.installPath, checksum: result.checksum };
7
+ }
8
+ await assetService.install(candidate.name, candidate.type, scope, candidate.sourcePath, candidate.source);
9
+ const rootLabel = scope === 'project' ? '.aman' : '~/.aman';
10
+ return {
11
+ path: `${rootLabel}/${candidate.type}s/${candidate.name}`,
12
+ checksum: candidate.checksum ?? '',
13
+ };
14
+ }
@@ -0,0 +1,15 @@
1
+ import { Scope } from '../types/index.js';
2
+ import { MarketplaceAsset } from './types.js';
3
+ export interface MarketplaceInstallResult {
4
+ scope: Scope;
5
+ localName: string;
6
+ type: MarketplaceAsset['type'];
7
+ installPath: string;
8
+ source: string;
9
+ checksum: string;
10
+ }
11
+ /**
12
+ * Install a marketplace asset from its GitHub publisher repo (read-only clone + verify).
13
+ * Does not use registry adapters. Rolls back on checksum failure before lockfile update.
14
+ */
15
+ export declare function installMarketplaceAsset(asset: MarketplaceAsset, scope: Scope): Promise<MarketplaceInstallResult>;
@@ -0,0 +1,54 @@
1
+ import path from 'path';
2
+ import { LOCAL_DIR } from '../config/paths.js';
3
+ import { assetService } from '../services/asset.service.js';
4
+ import { environmentService } from '../services/environment.service.js';
5
+ import { githubService } from '../services/github.service.js';
6
+ import { removeDir } from '../storage/filesystem.js';
7
+ import { computeAssetChecksum } from '../utils/integrity.js';
8
+ import { assetDir } from '../storage/asset-layout.js';
9
+ function typeRootDir(scope, type) {
10
+ const root = scope === 'global' ? environmentService.getActiveEnvironmentDir() : LOCAL_DIR;
11
+ const folder = type === 'skill' ? 'skills' : type === 'prompt' ? 'prompts' : 'mcps';
12
+ return path.join(root, folder);
13
+ }
14
+ /**
15
+ * Install a marketplace asset from its GitHub publisher repo (read-only clone + verify).
16
+ * Does not use registry adapters. Rolls back on checksum failure before lockfile update.
17
+ */
18
+ export async function installMarketplaceAsset(asset, scope) {
19
+ const repoRef = `${asset.owner}/${asset.repo}`;
20
+ let tempDir;
21
+ try {
22
+ tempDir = await githubService.import(repoRef);
23
+ const srcDir = path.join(tempDir, asset.contentPath);
24
+ const actualChecksum = await computeAssetChecksum(asset.type, srcDir);
25
+ if (asset.checksum && !asset.checksum.endsWith('0000000000000000000000000000000000')) {
26
+ if (actualChecksum !== asset.checksum) {
27
+ throw new Error(`Checksum mismatch for ${asset.slug}: expected ${asset.checksum}, got ${actualChecksum}`);
28
+ }
29
+ }
30
+ await assetService.install(asset.localName, asset.type, scope, srcDir, asset.source, {
31
+ slug: asset.slug,
32
+ version: asset.version,
33
+ author: asset.author,
34
+ description: asset.description,
35
+ tags: asset.tags,
36
+ id: asset.id,
37
+ originalName: asset.localName,
38
+ });
39
+ const installPath = assetDir(asset.type, typeRootDir(scope, asset.type), asset.localName);
40
+ return {
41
+ scope,
42
+ localName: asset.localName,
43
+ type: asset.type,
44
+ installPath,
45
+ source: asset.source,
46
+ checksum: actualChecksum,
47
+ };
48
+ }
49
+ finally {
50
+ if (tempDir) {
51
+ await removeDir(tempDir).catch(() => { });
52
+ }
53
+ }
54
+ }
@@ -0,0 +1,8 @@
1
+ import { AssetMetadata } from '../types/asset-metadata.js';
2
+ import { AssetType } from '../types/index.js';
3
+ export declare function isVerifiedPublisherSlug(slug: string): boolean;
4
+ /**
5
+ * Parse and validate metadata.json from a GitHub publisher repo.
6
+ * Ignores metadata.trust.verified — only @aman/ namespace is verified.
7
+ */
8
+ export declare function parsePublisherMetadata(raw: unknown, fallbackType: AssetType, fallbackLocalName: string, fallbackSlug: string, fallbackAuthor: string): AssetMetadata;
@@ -0,0 +1,79 @@
1
+ import { normalizeAssetMetadata } from '../types/asset-metadata.js';
2
+ /** Top-level keys allowed in publisher metadata.json (unknown keys are rejected). */
3
+ const ALLOWED_METADATA_KEYS = new Set([
4
+ 'schemaVersion',
5
+ 'id',
6
+ 'slug',
7
+ 'type',
8
+ 'name',
9
+ 'description',
10
+ 'version',
11
+ 'author',
12
+ 'tags',
13
+ 'scope',
14
+ 'visibility',
15
+ 'createdAt',
16
+ 'updatedAt',
17
+ 'integrity',
18
+ 'dependencies',
19
+ 'trust',
20
+ 'deprecated',
21
+ 'source',
22
+ 'installedAt',
23
+ 'originalName',
24
+ 'originalSlug',
25
+ // Legacy / publisher extras that are inert for the CLI
26
+ 'abstract',
27
+ 'category',
28
+ 'organization',
29
+ 'date',
30
+ 'installs',
31
+ 'rating',
32
+ ]);
33
+ /** Keys that must never appear in external metadata (install-time execution risk). */
34
+ const FORBIDDEN_METADATA_KEYS = new Set([
35
+ 'scripts',
36
+ 'hooks',
37
+ 'install',
38
+ 'postInstall',
39
+ 'preInstall',
40
+ 'bin',
41
+ 'main',
42
+ 'exports',
43
+ ]);
44
+ export function isVerifiedPublisherSlug(slug) {
45
+ return slug.startsWith('@aman/');
46
+ }
47
+ /**
48
+ * Parse and validate metadata.json from a GitHub publisher repo.
49
+ * Ignores metadata.trust.verified — only @aman/ namespace is verified.
50
+ */
51
+ export function parsePublisherMetadata(raw, fallbackType, fallbackLocalName, fallbackSlug, fallbackAuthor) {
52
+ if (!raw || typeof raw !== 'object' || Array.isArray(raw)) {
53
+ throw new Error('metadata.json must be a JSON object');
54
+ }
55
+ const record = raw;
56
+ for (const key of Object.keys(record)) {
57
+ if (FORBIDDEN_METADATA_KEYS.has(key)) {
58
+ throw new Error(`Rejected metadata field "${key}" (not permitted in marketplace assets)`);
59
+ }
60
+ if (!ALLOWED_METADATA_KEYS.has(key)) {
61
+ throw new Error(`Unexpected metadata field "${key}"`);
62
+ }
63
+ }
64
+ const typeRaw = record.type;
65
+ const type = typeRaw === 'skill' || typeRaw === 'prompt' || typeRaw === 'mcp' ? typeRaw : fallbackType;
66
+ const localName = typeof record.name === 'string' && record.name.trim().length > 0
67
+ ? record.name.trim()
68
+ : fallbackLocalName;
69
+ const slug = typeof record.slug === 'string' && record.slug.trim().length > 0 ? record.slug.trim() : fallbackSlug;
70
+ const meta = normalizeAssetMetadata(record, type, localName);
71
+ meta.slug = slug;
72
+ meta.type = type;
73
+ meta.name = localName;
74
+ meta.author = typeof record.author === 'string' ? record.author : fallbackAuthor;
75
+ if (meta.trust) {
76
+ meta.trust.verified = isVerifiedPublisherSlug(meta.slug);
77
+ }
78
+ return meta;
79
+ }
@@ -0,0 +1,34 @@
1
+ import { AssetType } from '../types/index.js';
2
+ /** Asset discovered from GitHub topic `aman-asset` publisher repositories. */
3
+ export interface MarketplaceAsset {
4
+ slug: string;
5
+ type: AssetType;
6
+ name: string;
7
+ localName: string;
8
+ description: string;
9
+ version: string;
10
+ author: string;
11
+ tags: string[];
12
+ stars: number;
13
+ /** Provenance ref written to lockfile: `github:owner/repo` */
14
+ source: string;
15
+ verified: boolean;
16
+ owner: string;
17
+ repo: string;
18
+ defaultBranch: string;
19
+ /** Relative path inside repo, e.g. `skills/my-skill` */
20
+ contentPath: string;
21
+ checksum: string;
22
+ id?: string;
23
+ updatedAt?: string;
24
+ }
25
+ export interface MarketplaceSearchResult {
26
+ assets: MarketplaceAsset[];
27
+ fromCache: boolean;
28
+ cacheAgeMinutes: number | null;
29
+ rateLimited: boolean;
30
+ offline: boolean;
31
+ message?: string;
32
+ }
33
+ export type MarketplaceSort = 'stars' | 'recent' | 'name';
34
+ export type MarketplaceTypeFilter = 'all' | AssetType;
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,9 @@
1
+ import { AssetProvider } from './provider.interface.js';
2
+ import { AssetType, ProviderResult } from '../types/index.js';
3
+ export declare class LocalProvider implements AssetProvider {
4
+ name: string;
5
+ search(query: string, type?: AssetType): Promise<ProviderResult[]>;
6
+ fetch(name: string, type: AssetType): Promise<string>;
7
+ available(): Promise<boolean>;
8
+ }
9
+ export declare const localProvider: LocalProvider;
@@ -0,0 +1,51 @@
1
+ import { assetService } from '../services/asset.service.js';
2
+ import { assetListCategory, assetListDescription, assetListInstalls, assetListOrganization, assetListRating, assetListTags, assetListUpdated, assetListVersion, } from '../utils/asset-list-fields.js';
3
+ import Fuse from 'fuse.js';
4
+ export class LocalProvider {
5
+ name = 'local';
6
+ async search(query, type) {
7
+ const results = [];
8
+ const types = type ? [type] : ['skill', 'prompt', 'mcp'];
9
+ for (const t of types) {
10
+ const items = await assetService.list(t);
11
+ for (const item of items) {
12
+ results.push({
13
+ type: t,
14
+ name: item.name,
15
+ source: item.source || this.name,
16
+ sources: [item.source || this.name],
17
+ description: assetListDescription(item),
18
+ tags: assetListTags(item),
19
+ category: assetListCategory(item),
20
+ installs: assetListInstalls(item),
21
+ rating: assetListRating(item),
22
+ updated: assetListUpdated(item),
23
+ version: assetListVersion(item),
24
+ organization: assetListOrganization(item),
25
+ installed: item.source === 'installed',
26
+ confidence: 1.0,
27
+ });
28
+ }
29
+ }
30
+ if (!query) {
31
+ return results;
32
+ }
33
+ const fuse = new Fuse(results, {
34
+ keys: ['name', 'description'],
35
+ threshold: 0.3,
36
+ });
37
+ return fuse.search(query).map((r) => r.item);
38
+ }
39
+ async fetch(name, type) {
40
+ const items = await assetService.list(type);
41
+ const item = items.find((i) => i.name === name);
42
+ if (!item) {
43
+ throw new Error(`Asset not found in local provider: ${name}`);
44
+ }
45
+ return item.path;
46
+ }
47
+ async available() {
48
+ return true;
49
+ }
50
+ }
51
+ export const localProvider = new LocalProvider();
@@ -0,0 +1,7 @@
1
+ import { AssetType, ProviderResult } from '../types/index.js';
2
+ export interface AssetProvider {
3
+ name: string;
4
+ search(query: string, type?: AssetType): Promise<ProviderResult[]>;
5
+ fetch(name: string, type: AssetType): Promise<string>;
6
+ available(): Promise<boolean>;
7
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,2 @@
1
+ import { AssetProvider } from './provider.interface.js';
2
+ export declare const registryProvider: AssetProvider;
@@ -0,0 +1,42 @@
1
+ import { registryService } from '../registry/registry.service.js';
2
+ import { localNameFromSlug } from '../utils/slug.js';
3
+ export const registryProvider = {
4
+ name: 'registry',
5
+ async available() {
6
+ return registryService.getAdapter().available();
7
+ },
8
+ async search(query, type) {
9
+ const result = await registryService.search({
10
+ query,
11
+ filters: type ? { type, includeDeprecated: true } : { includeDeprecated: true },
12
+ });
13
+ return result.hits.map((hit) => ({
14
+ name: localNameFromSlug(hit.slug),
15
+ type: hit.record.type,
16
+ source: 'registry',
17
+ sources: ['registry'],
18
+ description: hit.record.description,
19
+ tags: hit.record.tags,
20
+ version: hit.latestVersion,
21
+ organization: hit.record.trust.publisher ?? undefined,
22
+ installs: hit.record.trust.downloads,
23
+ rating: hit.record.trust.rating ?? undefined,
24
+ confidence: hit.record.trust.verified ? 1 : 0.5,
25
+ installed: false,
26
+ slug: hit.slug,
27
+ }));
28
+ },
29
+ async fetch(name, type) {
30
+ const slug = name.startsWith('@') ? name : `@aman/${name}`;
31
+ const listed = await registryService.list({ slug });
32
+ const match = listed.versions.find((v) => v.type === type);
33
+ if (!match) {
34
+ throw new Error(`Registry asset not found: ${slug} (${type})`);
35
+ }
36
+ const resolved = await registryService.resolve({ slug, version: match.version });
37
+ if (resolved.content.kind !== 'filesystem') {
38
+ throw new Error('Registry provider requires filesystem content');
39
+ }
40
+ return resolved.content.path;
41
+ },
42
+ };
@@ -0,0 +1,11 @@
1
+ import { AssetProvider } from './provider.interface.js';
2
+ import { AssetType, ProviderResult } from '../types/index.js';
3
+ export declare class SkillsShProvider implements AssetProvider {
4
+ name: string;
5
+ private bundledSkillsCache;
6
+ private getBundledSkills;
7
+ search(query: string, type?: AssetType): Promise<ProviderResult[]>;
8
+ fetch(name: string, type: AssetType): Promise<string>;
9
+ available(): Promise<boolean>;
10
+ }
11
+ export declare const skillsShProvider: SkillsShProvider;
@@ -0,0 +1,56 @@
1
+ import Fuse from 'fuse.js';
2
+ import { BUNDLED_SKILLS } from '../config/paths.js';
3
+ import { scanSkills } from '../storage/scanner.js';
4
+ export class SkillsShProvider {
5
+ name = 'skills.sh';
6
+ bundledSkillsCache = null;
7
+ async getBundledSkills() {
8
+ if (!this.bundledSkillsCache) {
9
+ this.bundledSkillsCache = await scanSkills(BUNDLED_SKILLS, this.name);
10
+ }
11
+ return this.bundledSkillsCache;
12
+ }
13
+ async search(query, type) {
14
+ if (type && type !== 'skill')
15
+ return [];
16
+ const skills = await this.getBundledSkills();
17
+ const results = skills.map((skill) => ({
18
+ type: 'skill',
19
+ name: skill.name,
20
+ source: this.name,
21
+ sources: [this.name],
22
+ description: skill.description,
23
+ tags: skill.tags,
24
+ category: skill.category,
25
+ installs: skill.installs,
26
+ rating: skill.rating,
27
+ updated: skill.updated,
28
+ version: skill.version,
29
+ organization: skill.organization,
30
+ installed: false,
31
+ confidence: 1,
32
+ }));
33
+ if (!query.trim())
34
+ return results;
35
+ const fuse = new Fuse(results, {
36
+ keys: ['name', 'description'],
37
+ threshold: 0.3,
38
+ });
39
+ return fuse.search(query).map((result) => result.item);
40
+ }
41
+ async fetch(name, type) {
42
+ if (type !== 'skill') {
43
+ throw new Error('skills.sh currently provides skills only.');
44
+ }
45
+ const skills = await this.getBundledSkills();
46
+ const skill = skills.find((item) => item.name === name);
47
+ if (!skill) {
48
+ throw new Error(`Skill not found on skills.sh: ${name}`);
49
+ }
50
+ return skill.path;
51
+ }
52
+ async available() {
53
+ return true;
54
+ }
55
+ }
56
+ export const skillsShProvider = new SkillsShProvider();