akm-cli 0.0.16 → 0.0.18
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/README.md +61 -108
- package/dist/asset-spec.js +9 -9
- package/dist/cli.js +321 -118
- package/dist/common.js +0 -7
- package/dist/config-cli.js +43 -0
- package/dist/config.js +46 -26
- package/dist/file-context.js +3 -28
- package/dist/indexer.js +0 -30
- package/dist/llm.js +1 -1
- package/dist/matchers.js +4 -15
- package/dist/metadata.js +3 -3
- package/dist/provider-registry.js +8 -0
- package/dist/providers/skills-sh.js +165 -0
- package/dist/providers/static-index.js +340 -0
- package/dist/registry-install.js +5 -5
- package/dist/registry-provider.js +1 -0
- package/dist/registry-search.js +65 -226
- package/dist/renderers.js +1 -1
- package/dist/stash-add.js +2 -2
- package/dist/stash-ref.js +7 -9
- package/dist/stash-registry.js +6 -6
- package/dist/stash-resolve.js +7 -44
- package/dist/stash-search.js +8 -12
- package/dist/stash-show.js +1 -2
- package/dist/stash-source.js +6 -7
- package/dist/walker.js +1 -1
- package/package.json +1 -1
package/dist/registry-search.js
CHANGED
|
@@ -1,14 +1,8 @@
|
|
|
1
|
-
import
|
|
2
|
-
import
|
|
3
|
-
import
|
|
4
|
-
import
|
|
5
|
-
|
|
6
|
-
/** Default registry index URL. Override via config or AKM_REGISTRY_URL env var. */
|
|
7
|
-
const DEFAULT_REGISTRY_URL = "https://raw.githubusercontent.com/itlackey/akm-registry/main/index.json";
|
|
8
|
-
/** Cache TTL in milliseconds (1 hour). */
|
|
9
|
-
const CACHE_TTL_MS = 60 * 60 * 1000;
|
|
10
|
-
/** Maximum age before cache is considered stale but still usable as fallback (7 days). */
|
|
11
|
-
const CACHE_STALE_MS = 7 * 24 * 60 * 60 * 1000;
|
|
1
|
+
import { DEFAULT_CONFIG, loadConfig } from "./config";
|
|
2
|
+
import { resolveProviderFactory } from "./provider-registry";
|
|
3
|
+
// ── Eagerly import providers to trigger self-registration ───────────────────
|
|
4
|
+
import "./providers/static-index";
|
|
5
|
+
import "./providers/skills-sh";
|
|
12
6
|
// ── Public API ──────────────────────────────────────────────────────────────
|
|
13
7
|
export async function searchRegistry(query, options) {
|
|
14
8
|
const trimmed = query.trim();
|
|
@@ -16,219 +10,78 @@ export async function searchRegistry(query, options) {
|
|
|
16
10
|
return { query: "", hits: [], warnings: [] };
|
|
17
11
|
}
|
|
18
12
|
const limit = clampLimit(options?.limit);
|
|
19
|
-
|
|
13
|
+
// resolveRegistries() already filters by enabled; explicit registries are filtered here
|
|
14
|
+
const raw = options?.registries ?? resolveRegistries();
|
|
15
|
+
const entries = options?.registries ? raw.filter((r) => r.enabled !== false) : raw;
|
|
20
16
|
const warnings = [];
|
|
21
|
-
//
|
|
22
|
-
const
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
const hits = scoreKits(allKits, trimmed, limit);
|
|
36
|
-
return { query: trimmed, hits, warnings };
|
|
37
|
-
}
|
|
38
|
-
// ── Index loading with cache ────────────────────────────────────────────────
|
|
39
|
-
async function loadIndex(url) {
|
|
40
|
-
const cachePath = indexCachePath(url);
|
|
41
|
-
const cached = readCachedIndex(cachePath);
|
|
42
|
-
// Fresh cache: return immediately
|
|
43
|
-
if (cached && !isCacheExpired(cached.mtime)) {
|
|
44
|
-
return cached.index;
|
|
45
|
-
}
|
|
46
|
-
// Try to fetch fresh index
|
|
47
|
-
try {
|
|
48
|
-
const response = await fetchWithRetry(url, undefined, { timeout: 10_000 });
|
|
49
|
-
if (!response.ok) {
|
|
50
|
-
throw new Error(`HTTP ${response.status}`);
|
|
51
|
-
}
|
|
52
|
-
const data = (await response.json());
|
|
53
|
-
const index = parseRegistryIndex(data);
|
|
54
|
-
if (index) {
|
|
55
|
-
writeCachedIndex(cachePath, index);
|
|
56
|
-
return index;
|
|
57
|
-
}
|
|
58
|
-
throw new Error("Invalid registry index format");
|
|
59
|
-
}
|
|
60
|
-
catch (err) {
|
|
61
|
-
// Fetch failed — use stale cache if available
|
|
62
|
-
if (cached && !isCacheStale(cached.mtime)) {
|
|
63
|
-
return cached.index;
|
|
64
|
-
}
|
|
65
|
-
throw err;
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
function readCachedIndex(cachePath) {
|
|
69
|
-
try {
|
|
70
|
-
const stat = fs.statSync(cachePath);
|
|
71
|
-
const raw = JSON.parse(fs.readFileSync(cachePath, "utf8"));
|
|
72
|
-
const index = parseRegistryIndex(raw);
|
|
73
|
-
if (!index)
|
|
74
|
-
return null;
|
|
75
|
-
return { index, mtime: stat.mtimeMs };
|
|
76
|
-
}
|
|
77
|
-
catch {
|
|
78
|
-
return null;
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
function writeCachedIndex(cachePath, index) {
|
|
82
|
-
try {
|
|
83
|
-
const dir = path.dirname(cachePath);
|
|
84
|
-
fs.mkdirSync(dir, { recursive: true });
|
|
85
|
-
const tmpPath = `${cachePath}.tmp.${process.pid}`;
|
|
86
|
-
fs.writeFileSync(tmpPath, JSON.stringify(index), "utf8");
|
|
87
|
-
fs.renameSync(tmpPath, cachePath);
|
|
88
|
-
}
|
|
89
|
-
catch {
|
|
90
|
-
// Best-effort caching — don't fail the search if we can't write
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
function indexCachePath(url) {
|
|
94
|
-
const indexDir = getRegistryIndexCacheDir();
|
|
95
|
-
// Deterministic filename from URL
|
|
96
|
-
const slug = url
|
|
97
|
-
.replace(/[^a-zA-Z0-9]+/g, "-")
|
|
98
|
-
.replace(/^-+|-+$/g, "")
|
|
99
|
-
.slice(0, 120);
|
|
100
|
-
return path.join(indexDir, `${slug}.json`);
|
|
101
|
-
}
|
|
102
|
-
function isCacheExpired(mtimeMs) {
|
|
103
|
-
return Date.now() - mtimeMs > CACHE_TTL_MS;
|
|
104
|
-
}
|
|
105
|
-
function isCacheStale(mtimeMs) {
|
|
106
|
-
return Date.now() - mtimeMs > CACHE_STALE_MS;
|
|
107
|
-
}
|
|
108
|
-
// ── Index parsing ───────────────────────────────────────────────────────────
|
|
109
|
-
function parseRegistryIndex(data) {
|
|
110
|
-
if (typeof data !== "object" || data === null || Array.isArray(data))
|
|
111
|
-
return null;
|
|
112
|
-
const obj = data;
|
|
113
|
-
if (typeof obj.version !== "number" || obj.version !== 1)
|
|
114
|
-
return null;
|
|
115
|
-
if (typeof obj.updatedAt !== "string")
|
|
116
|
-
return null;
|
|
117
|
-
if (!Array.isArray(obj.kits))
|
|
118
|
-
return null;
|
|
119
|
-
const kits = obj.kits.flatMap((raw) => {
|
|
120
|
-
const kit = parseKitEntry(raw);
|
|
121
|
-
return kit ? [kit] : [];
|
|
122
|
-
});
|
|
123
|
-
return { version: 1, updatedAt: obj.updatedAt, kits };
|
|
124
|
-
}
|
|
125
|
-
function parseKitEntry(raw) {
|
|
126
|
-
if (typeof raw !== "object" || raw === null || Array.isArray(raw))
|
|
127
|
-
return null;
|
|
128
|
-
const obj = raw;
|
|
129
|
-
const id = asString(obj.id);
|
|
130
|
-
const name = asString(obj.name);
|
|
131
|
-
const ref = asString(obj.ref);
|
|
132
|
-
const source = asSource(obj.source);
|
|
133
|
-
if (!id || !name || !ref || !source)
|
|
134
|
-
return null;
|
|
135
|
-
return {
|
|
136
|
-
id,
|
|
137
|
-
name,
|
|
138
|
-
ref,
|
|
139
|
-
source,
|
|
140
|
-
description: asString(obj.description),
|
|
141
|
-
homepage: asString(obj.homepage),
|
|
142
|
-
tags: asStringArray(obj.tags),
|
|
143
|
-
assetTypes: asStringArray(obj.assetTypes),
|
|
144
|
-
author: asString(obj.author),
|
|
145
|
-
license: asString(obj.license),
|
|
146
|
-
latestVersion: asString(obj.latestVersion),
|
|
147
|
-
curated: obj.curated === true ? true : undefined,
|
|
148
|
-
};
|
|
149
|
-
}
|
|
150
|
-
// ── Scoring ─────────────────────────────────────────────────────────────────
|
|
151
|
-
function scoreKits(kits, query, limit) {
|
|
152
|
-
const tokens = query.toLowerCase().split(/\s+/).filter(Boolean);
|
|
153
|
-
const scored = [];
|
|
154
|
-
for (const kit of kits) {
|
|
155
|
-
const score = scoreKit(kit, tokens);
|
|
156
|
-
if (score > 0) {
|
|
157
|
-
scored.push({ kit, score });
|
|
17
|
+
// Resolve and search all providers concurrently
|
|
18
|
+
const results = await Promise.allSettled(entries.map((entry) => {
|
|
19
|
+
const provider = createProvider(entry, warnings);
|
|
20
|
+
if (!provider)
|
|
21
|
+
return Promise.resolve(null);
|
|
22
|
+
return provider.search({ query: trimmed, limit, includeAssets: options?.includeAssets });
|
|
23
|
+
}));
|
|
24
|
+
// Merge results grouped by provider
|
|
25
|
+
const allHits = [];
|
|
26
|
+
const allAssetHits = [];
|
|
27
|
+
for (const result of results) {
|
|
28
|
+
if (result.status === "rejected") {
|
|
29
|
+
warnings.push(toErrorMessage(result.reason));
|
|
30
|
+
continue;
|
|
158
31
|
}
|
|
32
|
+
const value = result.value;
|
|
33
|
+
if (!value)
|
|
34
|
+
continue;
|
|
35
|
+
allHits.push(...value.hits);
|
|
36
|
+
if (value.assetHits)
|
|
37
|
+
allAssetHits.push(...value.assetHits);
|
|
38
|
+
if (value.warnings)
|
|
39
|
+
warnings.push(...value.warnings);
|
|
159
40
|
}
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
const nameLower = kit.name.toLowerCase();
|
|
166
|
-
const descLower = (kit.description ?? "").toLowerCase();
|
|
167
|
-
const tagsLower = (kit.tags ?? []).map((t) => t.toLowerCase());
|
|
168
|
-
for (const token of tokens) {
|
|
169
|
-
// Exact name match is strongest signal
|
|
170
|
-
if (nameLower === token) {
|
|
171
|
-
score += 1.0;
|
|
172
|
-
}
|
|
173
|
-
else if (nameLower.includes(token)) {
|
|
174
|
-
score += 0.6;
|
|
175
|
-
}
|
|
176
|
-
// Tag matches are high-signal (curated keywords)
|
|
177
|
-
if (tagsLower.some((tag) => tag === token)) {
|
|
178
|
-
score += 0.5;
|
|
179
|
-
}
|
|
180
|
-
else if (tagsLower.some((tag) => tag.includes(token))) {
|
|
181
|
-
score += 0.25;
|
|
182
|
-
}
|
|
183
|
-
// Description substring
|
|
184
|
-
if (descLower.includes(token)) {
|
|
185
|
-
score += 0.2;
|
|
186
|
-
}
|
|
187
|
-
// Author match
|
|
188
|
-
if (kit.author?.toLowerCase().includes(token)) {
|
|
189
|
-
score += 0.15;
|
|
190
|
-
}
|
|
191
|
-
}
|
|
192
|
-
// Normalize by token count so multi-word queries don't inflate scores
|
|
193
|
-
return tokens.length > 0 ? score / tokens.length : 0;
|
|
194
|
-
}
|
|
195
|
-
function toSearchHit(kit, score) {
|
|
196
|
-
const metadata = {};
|
|
197
|
-
if (kit.latestVersion)
|
|
198
|
-
metadata.version = kit.latestVersion;
|
|
199
|
-
if (kit.author)
|
|
200
|
-
metadata.author = kit.author;
|
|
201
|
-
if (kit.license)
|
|
202
|
-
metadata.license = kit.license;
|
|
203
|
-
if (kit.assetTypes?.length)
|
|
204
|
-
metadata.assetTypes = kit.assetTypes.join(", ");
|
|
41
|
+
// Sort merged hits by score descending, apply limit
|
|
42
|
+
allHits.sort((a, b) => (b.score ?? 0) - (a.score ?? 0));
|
|
43
|
+
const limitedHits = allHits.slice(0, limit);
|
|
44
|
+
allAssetHits.sort((a, b) => (b.score ?? 0) - (a.score ?? 0));
|
|
45
|
+
const limitedAssetHits = allAssetHits.slice(0, limit);
|
|
205
46
|
return {
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
ref: kit.ref,
|
|
211
|
-
homepage: kit.homepage,
|
|
212
|
-
score: Math.round(score * 1000) / 1000,
|
|
213
|
-
metadata,
|
|
214
|
-
curated: kit.curated,
|
|
47
|
+
query: trimmed,
|
|
48
|
+
hits: limitedHits,
|
|
49
|
+
warnings,
|
|
50
|
+
assetHits: limitedAssetHits.length > 0 ? limitedAssetHits : undefined,
|
|
215
51
|
};
|
|
216
52
|
}
|
|
217
|
-
// ── Registry
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
53
|
+
// ── Registry resolution ─────────────────────────────────────────────────────
|
|
54
|
+
/**
|
|
55
|
+
* Resolve the list of enabled registries.
|
|
56
|
+
*
|
|
57
|
+
* Priority:
|
|
58
|
+
* 1. AKM_REGISTRY_URL env var (CI override, comma-separated)
|
|
59
|
+
* 2. config.registries (filtered by enabled !== false)
|
|
60
|
+
* 3. Default registries from DEFAULT_CONFIG
|
|
61
|
+
*/
|
|
62
|
+
export function resolveRegistries(configRegistries) {
|
|
63
|
+
// Allow env var override (comma-separated URLs) — CI escape hatch
|
|
224
64
|
const envUrls = process.env.AKM_REGISTRY_URL?.trim();
|
|
225
65
|
if (envUrls) {
|
|
226
66
|
return envUrls
|
|
227
67
|
.split(",")
|
|
228
68
|
.map((u) => u.trim())
|
|
229
|
-
.filter(Boolean)
|
|
69
|
+
.filter(Boolean)
|
|
70
|
+
.map((url) => ({ url }));
|
|
230
71
|
}
|
|
231
|
-
|
|
72
|
+
const registries = configRegistries ?? loadConfig().registries ?? DEFAULT_CONFIG.registries ?? [];
|
|
73
|
+
return registries.filter((r) => r.enabled !== false);
|
|
74
|
+
}
|
|
75
|
+
// ── Provider resolution ─────────────────────────────────────────────────────
|
|
76
|
+
function createProvider(entry, warnings) {
|
|
77
|
+
const providerType = entry.provider ?? "static-index";
|
|
78
|
+
const factory = resolveProviderFactory(providerType);
|
|
79
|
+
if (!factory) {
|
|
80
|
+
const label = entry.name ? `${entry.name} (${entry.url})` : entry.url;
|
|
81
|
+
warnings.push(`Registry ${label}: unknown provider type "${providerType}"`);
|
|
82
|
+
return null;
|
|
83
|
+
}
|
|
84
|
+
return factory(entry);
|
|
232
85
|
}
|
|
233
86
|
// ── Utilities ───────────────────────────────────────────────────────────────
|
|
234
87
|
function clampLimit(limit) {
|
|
@@ -236,20 +89,6 @@ function clampLimit(limit) {
|
|
|
236
89
|
return 20;
|
|
237
90
|
return Math.min(100, Math.max(1, Math.trunc(limit)));
|
|
238
91
|
}
|
|
239
|
-
function asString(value) {
|
|
240
|
-
return typeof value === "string" && value ? value : undefined;
|
|
241
|
-
}
|
|
242
|
-
function asSource(value) {
|
|
243
|
-
if (value === "npm" || value === "github" || value === "git" || value === "local")
|
|
244
|
-
return value;
|
|
245
|
-
return undefined;
|
|
246
|
-
}
|
|
247
|
-
function asStringArray(value) {
|
|
248
|
-
if (!Array.isArray(value))
|
|
249
|
-
return undefined;
|
|
250
|
-
const filtered = value.filter((v) => typeof v === "string");
|
|
251
|
-
return filtered.length > 0 ? filtered : undefined;
|
|
252
|
-
}
|
|
253
92
|
function toErrorMessage(error) {
|
|
254
93
|
return error instanceof Error ? error.message : String(error);
|
|
255
94
|
}
|
package/dist/renderers.js
CHANGED
|
@@ -112,7 +112,7 @@ export function detectExecHints(filePath) {
|
|
|
112
112
|
}
|
|
113
113
|
// ── Resolution ───────────────────────────────────────────────────────────────
|
|
114
114
|
/**
|
|
115
|
-
* Resolve execution hints for a script
|
|
115
|
+
* Resolve execution hints for a script asset.
|
|
116
116
|
*
|
|
117
117
|
* Resolution order (first non-empty value wins for each field):
|
|
118
118
|
* 1. `.stash.json` fields (`run`/`setup`/`cwd`) take priority
|
package/dist/stash-add.js
CHANGED
|
@@ -11,7 +11,7 @@ export async function agentikitAdd(input) {
|
|
|
11
11
|
throw new UsageError("Install ref or local directory is required.");
|
|
12
12
|
const stashDir = resolveStashDir();
|
|
13
13
|
const installed = await installRegistryRef(ref);
|
|
14
|
-
const replaced = loadConfig().
|
|
14
|
+
const replaced = (loadConfig().installed ?? []).find((entry) => entry.id === installed.id);
|
|
15
15
|
const config = upsertInstalledRegistryEntry({
|
|
16
16
|
id: installed.id,
|
|
17
17
|
source: installed.source,
|
|
@@ -59,7 +59,7 @@ export async function agentikitAdd(input) {
|
|
|
59
59
|
},
|
|
60
60
|
config: {
|
|
61
61
|
searchPaths: config.searchPaths,
|
|
62
|
-
|
|
62
|
+
installedKitCount: config.installed?.length ?? 0,
|
|
63
63
|
},
|
|
64
64
|
index: {
|
|
65
65
|
mode: index.mode,
|
package/dist/stash-ref.js
CHANGED
|
@@ -6,21 +6,19 @@ import { UsageError } from "./errors";
|
|
|
6
6
|
* Build a ref string from components.
|
|
7
7
|
*
|
|
8
8
|
* Examples:
|
|
9
|
-
* makeAssetRef("
|
|
10
|
-
* → "
|
|
11
|
-
* makeAssetRef("
|
|
12
|
-
* → "npm:@scope/pkg//
|
|
9
|
+
* makeAssetRef("script", "deploy.sh")
|
|
10
|
+
* → "script:deploy.sh"
|
|
11
|
+
* makeAssetRef("script", "deploy.sh", "npm:@scope/pkg")
|
|
12
|
+
* → "npm:@scope/pkg//script:deploy.sh"
|
|
13
13
|
* makeAssetRef("skill", "code-review", "local")
|
|
14
14
|
* → "local//skill:code-review"
|
|
15
|
-
* makeAssetRef("
|
|
16
|
-
* → "owner/repo//
|
|
15
|
+
* makeAssetRef("script", "db/migrate/run.sh", "owner/repo")
|
|
16
|
+
* → "owner/repo//script:db/migrate/run.sh"
|
|
17
17
|
*/
|
|
18
18
|
export function makeAssetRef(type, name, origin) {
|
|
19
19
|
validateName(name);
|
|
20
20
|
const normalized = normalizeName(name);
|
|
21
|
-
|
|
22
|
-
const resolvedType = type === "tool" ? "script" : type;
|
|
23
|
-
const asset = `${resolvedType}:${normalized}`;
|
|
21
|
+
const asset = `${type}:${normalized}`;
|
|
24
22
|
if (!origin)
|
|
25
23
|
return asset;
|
|
26
24
|
return `${origin}//${asset}`;
|
package/dist/stash-registry.js
CHANGED
|
@@ -9,7 +9,7 @@ import { parseRegistryRef } from "./registry-resolve";
|
|
|
9
9
|
export async function agentikitList(input) {
|
|
10
10
|
const stashDir = input?.stashDir ?? resolveStashDir();
|
|
11
11
|
const config = loadConfig();
|
|
12
|
-
const installed = config.
|
|
12
|
+
const installed = config.installed ?? [];
|
|
13
13
|
return {
|
|
14
14
|
schemaVersion: 1,
|
|
15
15
|
stashDir,
|
|
@@ -29,7 +29,7 @@ export async function agentikitRemove(input) {
|
|
|
29
29
|
throw new UsageError("Target is required.");
|
|
30
30
|
const stashDir = input.stashDir ?? resolveStashDir();
|
|
31
31
|
const config = loadConfig();
|
|
32
|
-
const installed = config.
|
|
32
|
+
const installed = config.installed ?? [];
|
|
33
33
|
const entry = resolveInstalledTarget(installed, target);
|
|
34
34
|
const updatedConfig = removeInstalledRegistryEntry(entry.id);
|
|
35
35
|
removeLockEntry(entry.id);
|
|
@@ -52,7 +52,7 @@ export async function agentikitRemove(input) {
|
|
|
52
52
|
},
|
|
53
53
|
config: {
|
|
54
54
|
searchPaths: updatedConfig.searchPaths,
|
|
55
|
-
|
|
55
|
+
installedKitCount: updatedConfig.installed?.length ?? 0,
|
|
56
56
|
},
|
|
57
57
|
index: {
|
|
58
58
|
mode: index.mode,
|
|
@@ -67,7 +67,7 @@ export async function agentikitUpdate(input) {
|
|
|
67
67
|
const target = input?.target?.trim();
|
|
68
68
|
const all = input?.all === true;
|
|
69
69
|
const force = input?.force === true;
|
|
70
|
-
const installedEntries = loadConfig().
|
|
70
|
+
const installedEntries = loadConfig().installed ?? [];
|
|
71
71
|
const selectedEntries = selectTargets(installedEntries, target, all);
|
|
72
72
|
const processed = [];
|
|
73
73
|
for (const entry of selectedEntries) {
|
|
@@ -116,7 +116,7 @@ export async function agentikitUpdate(input) {
|
|
|
116
116
|
processed,
|
|
117
117
|
config: {
|
|
118
118
|
searchPaths: config.searchPaths,
|
|
119
|
-
|
|
119
|
+
installedKitCount: config.installed?.length ?? 0,
|
|
120
120
|
},
|
|
121
121
|
index: {
|
|
122
122
|
mode: index.mode,
|
|
@@ -156,7 +156,7 @@ function resolveInstalledTarget(installed, target) {
|
|
|
156
156
|
if (byParsedId)
|
|
157
157
|
return byParsedId;
|
|
158
158
|
}
|
|
159
|
-
throw new NotFoundError(`No installed
|
|
159
|
+
throw new NotFoundError(`No installed kit matched target: ${target}`);
|
|
160
160
|
}
|
|
161
161
|
function toInstalledEntry(status) {
|
|
162
162
|
return {
|
package/dist/stash-resolve.js
CHANGED
|
@@ -1,47 +1,12 @@
|
|
|
1
1
|
import fs from "node:fs";
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import { isRelevantAssetFile, resolveAssetPathFromName, TYPE_DIRS } from "./asset-spec";
|
|
4
|
-
import { hasErrnoCode, isWithin
|
|
4
|
+
import { hasErrnoCode, isWithin } from "./common";
|
|
5
5
|
import { NotFoundError, UsageError } from "./errors";
|
|
6
6
|
/**
|
|
7
7
|
* Resolve an asset path from a stash directory, type, and name.
|
|
8
|
-
*
|
|
9
|
-
* When `type` is "script" or "tool" (which is a transparent alias for "script"),
|
|
10
|
-
* resolution tries both the primary type directory and the alias directory:
|
|
11
|
-
* - script → tries scripts/ then tools/
|
|
12
|
-
* - tool → tries tools/ then scripts/
|
|
13
|
-
* This ensures that `script:deploy.sh` can find files in either `scripts/` or `tools/`.
|
|
14
8
|
*/
|
|
15
9
|
export function resolveAssetPath(stashDir, type, name) {
|
|
16
|
-
// For script/tool, try the primary directory first, then the alias directory.
|
|
17
|
-
if (type === "script" || type === "tool") {
|
|
18
|
-
const primaryDir = TYPE_DIRS[type];
|
|
19
|
-
const aliasDir = type === "script" ? "tools" : "scripts";
|
|
20
|
-
const dirsToTry = [primaryDir, aliasDir];
|
|
21
|
-
let primaryError;
|
|
22
|
-
let lastError;
|
|
23
|
-
for (let i = 0; i < dirsToTry.length; i++) {
|
|
24
|
-
try {
|
|
25
|
-
return resolveInTypeDir(stashDir, dirsToTry[i], type, name);
|
|
26
|
-
}
|
|
27
|
-
catch (err) {
|
|
28
|
-
const error = err instanceof Error ? err : new Error(String(err));
|
|
29
|
-
if (i === 0)
|
|
30
|
-
primaryError = error;
|
|
31
|
-
lastError = error;
|
|
32
|
-
// Only fall through on NotFoundError -- rethrow security/usage errors immediately
|
|
33
|
-
if (err instanceof UsageError)
|
|
34
|
-
throw err;
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
// Prefer the primary directory's error when it's about extension validation
|
|
38
|
-
// (i.e., the file was found but had the wrong extension) over a generic
|
|
39
|
-
// "not found" from the alias directory.
|
|
40
|
-
const errorToThrow = primaryError?.message.includes("supported script extension")
|
|
41
|
-
? primaryError
|
|
42
|
-
: (lastError ?? new NotFoundError(`Stash asset not found for ref: ${normalizeAssetType(type)}:${name}`));
|
|
43
|
-
throw errorToThrow;
|
|
44
|
-
}
|
|
45
10
|
return resolveInTypeDir(stashDir, TYPE_DIRS[type], type, name);
|
|
46
11
|
}
|
|
47
12
|
/**
|
|
@@ -56,26 +21,24 @@ function resolveInTypeDir(stashDir, typeDir, type, name) {
|
|
|
56
21
|
throw new UsageError("Ref resolves outside the stash root.");
|
|
57
22
|
}
|
|
58
23
|
if (!fs.existsSync(resolvedTarget) || !fs.statSync(resolvedTarget).isFile()) {
|
|
59
|
-
throw new NotFoundError(`Stash asset not found for ref: ${
|
|
24
|
+
throw new NotFoundError(`Stash asset not found for ref: ${type}:${name}`);
|
|
60
25
|
}
|
|
61
26
|
const realTarget = fs.realpathSync(resolvedTarget);
|
|
62
27
|
if (!isWithin(realTarget, resolvedRoot)) {
|
|
63
28
|
throw new UsageError("Ref resolves outside the stash root.");
|
|
64
29
|
}
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
if (!isRelevantAssetFile(relevanceType, path.basename(resolvedTarget))) {
|
|
68
|
-
if (type === "tool" || type === "script") {
|
|
30
|
+
if (!isRelevantAssetFile(type, path.basename(resolvedTarget))) {
|
|
31
|
+
if (type === "script") {
|
|
69
32
|
throw new NotFoundError("Script ref must resolve to a file with a supported script extension. Refer to the akm documentation for the complete list of supported script extensions.");
|
|
70
33
|
}
|
|
71
|
-
throw new NotFoundError(`Stash asset not found for ref: ${
|
|
34
|
+
throw new NotFoundError(`Stash asset not found for ref: ${type}:${name}`);
|
|
72
35
|
}
|
|
73
36
|
return realTarget;
|
|
74
37
|
}
|
|
75
38
|
function resolveAndValidateTypeRoot(root, type, name) {
|
|
76
39
|
const rootStat = readTypeRootStat(root, type, name);
|
|
77
40
|
if (!rootStat.isDirectory()) {
|
|
78
|
-
throw new NotFoundError(`Stash type root is not a directory for ref: ${
|
|
41
|
+
throw new NotFoundError(`Stash type root is not a directory for ref: ${type}:${name}`);
|
|
79
42
|
}
|
|
80
43
|
return fs.realpathSync(root);
|
|
81
44
|
}
|
|
@@ -85,7 +48,7 @@ function readTypeRootStat(root, type, name) {
|
|
|
85
48
|
}
|
|
86
49
|
catch (error) {
|
|
87
50
|
if (hasErrnoCode(error, "ENOENT")) {
|
|
88
|
-
throw new NotFoundError(`Stash type root not found for ref: ${
|
|
51
|
+
throw new NotFoundError(`Stash type root not found for ref: ${type}:${name}`);
|
|
89
52
|
}
|
|
90
53
|
throw error;
|
|
91
54
|
}
|
package/dist/stash-search.js
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import fs from "node:fs";
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import { deriveCanonicalAssetName, TYPE_DIRS } from "./asset-spec";
|
|
4
|
-
import { normalizeAssetType } from "./common";
|
|
5
4
|
import { loadConfig } from "./config";
|
|
6
5
|
import { closeDatabase, getAllEntries, getEntryById, getEntryCount, getMeta, openDatabase, searchFts, searchVec, } from "./db";
|
|
7
6
|
import { UsageError } from "./errors";
|
|
@@ -45,7 +44,7 @@ export async function agentikitSearch(input) {
|
|
|
45
44
|
sources,
|
|
46
45
|
config,
|
|
47
46
|
});
|
|
48
|
-
const registryResult = source === "local" ? undefined : await searchRegistry(query, { limit,
|
|
47
|
+
const registryResult = source === "local" ? undefined : await searchRegistry(query, { limit, registries: config.registries });
|
|
49
48
|
if (source === "local") {
|
|
50
49
|
return {
|
|
51
50
|
schemaVersion: 1,
|
|
@@ -67,6 +66,7 @@ export async function agentikitSearch(input) {
|
|
|
67
66
|
action: `akm add ${installRef} -> then search again`,
|
|
68
67
|
score: hit.score,
|
|
69
68
|
curated: hit.curated,
|
|
69
|
+
registryName: hit.registryName,
|
|
70
70
|
};
|
|
71
71
|
});
|
|
72
72
|
if (source === "registry") {
|
|
@@ -315,7 +315,7 @@ function buildDbHit(input) {
|
|
|
315
315
|
const source = findSourceForPath(input.path, input.sources);
|
|
316
316
|
const editable = isEditable(input.path, input.config);
|
|
317
317
|
const hit = {
|
|
318
|
-
type:
|
|
318
|
+
type: input.entry.type,
|
|
319
319
|
name: input.entry.name,
|
|
320
320
|
path: input.path,
|
|
321
321
|
ref: makeAssetRef(input.entry.type, refName, source?.registryId),
|
|
@@ -325,7 +325,7 @@ function buildDbHit(input) {
|
|
|
325
325
|
description: input.entry.description,
|
|
326
326
|
tags: input.entry.tags,
|
|
327
327
|
size: deriveSize(input.entry.fileSize),
|
|
328
|
-
action: buildLocalAction(
|
|
328
|
+
action: buildLocalAction(input.entry.type, makeAssetRef(input.entry.type, refName, source?.registryId)),
|
|
329
329
|
score,
|
|
330
330
|
whyMatched,
|
|
331
331
|
};
|
|
@@ -364,7 +364,7 @@ function assetToSearchHit(asset, stashDir, sources, config) {
|
|
|
364
364
|
const fileSize = readFileSize(asset.path);
|
|
365
365
|
const size = deriveSize(fileSize);
|
|
366
366
|
const hit = {
|
|
367
|
-
type:
|
|
367
|
+
type: asset.entry.type,
|
|
368
368
|
name: asset.entry.name,
|
|
369
369
|
path: asset.path,
|
|
370
370
|
ref,
|
|
@@ -376,7 +376,7 @@ function assetToSearchHit(asset, stashDir, sources, config) {
|
|
|
376
376
|
description: asset.entry.description,
|
|
377
377
|
tags: asset.entry.tags,
|
|
378
378
|
...(size ? { size } : {}),
|
|
379
|
-
action: buildLocalAction(
|
|
379
|
+
action: buildLocalAction(asset.entry.type, ref),
|
|
380
380
|
};
|
|
381
381
|
const renderer = rendererForType(asset.entry.type);
|
|
382
382
|
if (renderer?.enrichSearchHit) {
|
|
@@ -404,7 +404,6 @@ function mergeSearchHits(localHits, registryHits, limit) {
|
|
|
404
404
|
}
|
|
405
405
|
/** Map asset types to their primary renderer names. */
|
|
406
406
|
const TYPE_TO_RENDERER = {
|
|
407
|
-
tool: "script-source",
|
|
408
407
|
script: "script-source",
|
|
409
408
|
skill: "skill-md",
|
|
410
409
|
command: "command-md",
|
|
@@ -426,8 +425,6 @@ function buildLocalAction(type, ref) {
|
|
|
426
425
|
return `akm show ${ref} -> dispatch with full prompt`;
|
|
427
426
|
case "knowledge":
|
|
428
427
|
return `akm show ${ref} -> read reference material`;
|
|
429
|
-
case "tool":
|
|
430
|
-
return `akm show ${ref} -> execute the run command`;
|
|
431
428
|
}
|
|
432
429
|
}
|
|
433
430
|
function deriveSize(bytes) {
|
|
@@ -449,7 +446,7 @@ function readFileSize(filePath) {
|
|
|
449
446
|
}
|
|
450
447
|
function indexAssets(stashDir, type) {
|
|
451
448
|
const assets = [];
|
|
452
|
-
const filterType = type === "any" ? undefined :
|
|
449
|
+
const filterType = type === "any" ? undefined : type;
|
|
453
450
|
const fileContexts = walkStashFlat(stashDir);
|
|
454
451
|
const dirGroups = new Map();
|
|
455
452
|
for (const ctx of fileContexts) {
|
|
@@ -478,8 +475,7 @@ function indexAssets(stashDir, type) {
|
|
|
478
475
|
stash = generated;
|
|
479
476
|
}
|
|
480
477
|
for (const entry of stash.entries) {
|
|
481
|
-
|
|
482
|
-
if (filterType && normalizedType !== filterType)
|
|
478
|
+
if (filterType && entry.type !== filterType)
|
|
483
479
|
continue;
|
|
484
480
|
const entryPath = entry.filename ? path.join(dirPath, entry.filename) : files[0] || dirPath;
|
|
485
481
|
assets.push({ entry, path: entryPath });
|
package/dist/stash-show.js
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { normalizeAssetType } from "./common";
|
|
2
1
|
import { loadConfig } from "./config";
|
|
3
2
|
import { NotFoundError, UsageError } from "./errors";
|
|
4
3
|
import { buildFileContext, buildRenderContext, getRenderer, runMatchers } from "./file-context";
|
|
@@ -8,7 +7,7 @@ import { resolveAssetPath } from "./stash-resolve";
|
|
|
8
7
|
import { buildEditHint, findSourceForPath, isEditable, resolveStashSources } from "./stash-source";
|
|
9
8
|
export async function agentikitShow(input) {
|
|
10
9
|
const parsed = parseAssetRef(input.ref);
|
|
11
|
-
const displayType =
|
|
10
|
+
const displayType = parsed.type;
|
|
12
11
|
const config = loadConfig();
|
|
13
12
|
const allSources = resolveStashSources();
|
|
14
13
|
const searchSources = resolveSourcesForOrigin(parsed.origin, allSources);
|