agentikit 0.0.9 → 0.0.13
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 +139 -208
- package/dist/index.d.ts +8 -2
- package/dist/index.js +4 -1
- package/dist/src/asset-spec.d.ts +2 -0
- package/dist/src/asset-spec.js +22 -3
- package/dist/src/asset-type-handler.d.ts +27 -0
- package/dist/src/asset-type-handler.js +33 -0
- package/dist/src/cli.js +201 -75
- package/dist/src/common.d.ts +6 -1
- package/dist/src/common.js +18 -4
- package/dist/src/config-cli.d.ts +9 -0
- package/dist/src/config-cli.js +473 -0
- package/dist/src/config.d.ts +19 -6
- package/dist/src/config.js +139 -29
- package/dist/src/db.d.ts +46 -0
- package/dist/src/db.js +299 -0
- package/dist/src/embedder.js +12 -7
- package/dist/src/github.d.ts +4 -0
- package/dist/src/github.js +19 -0
- package/dist/src/handlers/agent-handler.d.ts +2 -0
- package/dist/src/handlers/agent-handler.js +26 -0
- package/dist/src/handlers/command-handler.d.ts +2 -0
- package/dist/src/handlers/command-handler.js +23 -0
- package/dist/src/handlers/index.d.ts +6 -0
- package/dist/src/handlers/index.js +23 -0
- package/dist/src/handlers/knowledge-handler.d.ts +2 -0
- package/dist/src/handlers/knowledge-handler.js +56 -0
- package/dist/src/handlers/markdown-helpers.d.ts +7 -0
- package/dist/src/handlers/markdown-helpers.js +15 -0
- package/dist/src/handlers/script-handler.d.ts +2 -0
- package/dist/src/handlers/script-handler.js +78 -0
- package/dist/src/handlers/skill-handler.d.ts +2 -0
- package/dist/src/handlers/skill-handler.js +30 -0
- package/dist/src/handlers/tool-handler.d.ts +2 -0
- package/dist/src/handlers/tool-handler.js +58 -0
- package/dist/src/indexer.d.ts +1 -23
- package/dist/src/indexer.js +162 -155
- package/dist/src/init.d.ts +2 -2
- package/dist/src/init.js +21 -9
- package/dist/src/llm.js +4 -3
- package/dist/src/metadata.d.ts +0 -1
- package/dist/src/metadata.js +6 -64
- package/dist/src/origin-resolve.d.ts +19 -0
- package/dist/src/origin-resolve.js +53 -0
- package/dist/src/registry-install.d.ts +2 -2
- package/dist/src/registry-install.js +142 -35
- package/dist/src/registry-resolve.js +90 -22
- package/dist/src/registry-search.d.ts +22 -0
- package/dist/src/registry-search.js +231 -97
- package/dist/src/registry-types.d.ts +9 -2
- package/dist/src/stash-add.js +4 -4
- package/dist/src/stash-clone.d.ts +22 -0
- package/dist/src/stash-clone.js +83 -0
- package/dist/src/stash-ref.d.ts +27 -3
- package/dist/src/stash-ref.js +63 -24
- package/dist/src/stash-registry.js +12 -12
- package/dist/src/stash-resolve.js +3 -0
- package/dist/src/stash-search.js +168 -164
- package/dist/src/stash-show.d.ts +1 -1
- package/dist/src/stash-show.js +28 -96
- package/dist/src/stash-source.d.ts +24 -0
- package/dist/src/stash-source.js +81 -0
- package/dist/src/stash-types.d.ts +14 -4
- package/dist/src/stash.d.ts +6 -0
- package/dist/src/stash.js +3 -0
- package/dist/src/tool-runner.d.ts +1 -1
- package/dist/src/tool-runner.js +18 -5
- package/package.json +7 -2
- package/src/asset-spec.ts +20 -4
- package/src/asset-type-handler.ts +77 -0
- package/src/cli.ts +213 -82
- package/src/common.ts +23 -5
- package/src/config-cli.ts +499 -0
- package/src/config.ts +160 -38
- package/src/db.ts +411 -0
- package/src/embedder.ts +22 -11
- package/src/github.ts +21 -0
- package/src/handlers/agent-handler.ts +32 -0
- package/src/handlers/command-handler.ts +29 -0
- package/src/handlers/index.ts +25 -0
- package/src/handlers/knowledge-handler.ts +62 -0
- package/src/handlers/markdown-helpers.ts +19 -0
- package/src/handlers/script-handler.ts +92 -0
- package/src/handlers/skill-handler.ts +37 -0
- package/src/handlers/tool-handler.ts +71 -0
- package/src/indexer.ts +208 -187
- package/src/init.ts +17 -9
- package/src/llm.ts +4 -3
- package/src/metadata.ts +5 -65
- package/src/origin-resolve.ts +67 -0
- package/src/registry-install.ts +158 -42
- package/src/registry-resolve.ts +92 -23
- package/src/registry-search.ts +288 -98
- package/src/registry-types.ts +10 -2
- package/src/stash-add.ts +14 -17
- package/src/stash-clone.ts +127 -0
- package/src/stash-ref.ts +84 -26
- package/src/stash-registry.ts +12 -12
- package/src/stash-resolve.ts +3 -0
- package/src/stash-search.ts +202 -184
- package/src/stash-show.ts +33 -90
- package/src/stash-source.ts +103 -0
- package/src/stash-types.ts +14 -4
- package/src/stash.ts +8 -0
- package/src/tool-runner.ts +18 -5
- package/dist/src/similarity.d.ts +0 -34
- package/dist/src/similarity.js +0 -211
- package/src/similarity.ts +0 -271
package/src/registry-search.ts
CHANGED
|
@@ -1,143 +1,333 @@
|
|
|
1
|
+
import fs from "node:fs"
|
|
2
|
+
import path from "node:path"
|
|
3
|
+
import { fetchWithTimeout } from "./common"
|
|
1
4
|
import type { RegistrySearchHit, RegistrySearchResponse } from "./registry-types"
|
|
2
5
|
|
|
3
|
-
|
|
6
|
+
// ── Constants ───────────────────────────────────────────────────────────────
|
|
7
|
+
|
|
8
|
+
/** Default registry index URL. Override via config or AKM_REGISTRY_URL env var. */
|
|
9
|
+
const DEFAULT_REGISTRY_URL =
|
|
10
|
+
"https://raw.githubusercontent.com/itlackey/agentikit-registry/main/index.json"
|
|
11
|
+
|
|
12
|
+
/** Cache TTL in milliseconds (1 hour). */
|
|
13
|
+
const CACHE_TTL_MS = 60 * 60 * 1000
|
|
14
|
+
|
|
15
|
+
/** Maximum age before cache is considered stale but still usable as fallback (7 days). */
|
|
16
|
+
const CACHE_STALE_MS = 7 * 24 * 60 * 60 * 1000
|
|
17
|
+
|
|
18
|
+
// ── Types ───────────────────────────────────────────────────────────────────
|
|
19
|
+
|
|
20
|
+
export interface RegistryIndex {
|
|
21
|
+
version: number
|
|
22
|
+
updatedAt: string
|
|
23
|
+
kits: RegistryKitEntry[]
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export interface RegistryKitEntry {
|
|
27
|
+
id: string
|
|
28
|
+
name: string
|
|
29
|
+
description?: string
|
|
30
|
+
ref: string
|
|
31
|
+
source: "npm" | "github" | "git"
|
|
32
|
+
homepage?: string
|
|
33
|
+
tags?: string[]
|
|
34
|
+
assetTypes?: string[]
|
|
35
|
+
author?: string
|
|
36
|
+
license?: string
|
|
37
|
+
latestVersion?: string
|
|
38
|
+
/** Whether this entry was manually reviewed and approved */
|
|
39
|
+
curated?: boolean
|
|
40
|
+
}
|
|
4
41
|
|
|
5
42
|
export interface RegistrySearchOptions {
|
|
6
43
|
limit?: number
|
|
44
|
+
/** Override registry URL(s). Accepts a single URL or an array. */
|
|
45
|
+
registryUrls?: string | string[]
|
|
7
46
|
}
|
|
8
47
|
|
|
9
|
-
|
|
48
|
+
// ── Public API ──────────────────────────────────────────────────────────────
|
|
49
|
+
|
|
50
|
+
export async function searchRegistry(
|
|
51
|
+
query: string,
|
|
52
|
+
options?: RegistrySearchOptions,
|
|
53
|
+
): Promise<RegistrySearchResponse> {
|
|
10
54
|
const trimmed = query.trim()
|
|
11
55
|
if (!trimmed) {
|
|
12
56
|
return { query: "", hits: [], warnings: [] }
|
|
13
57
|
}
|
|
14
58
|
|
|
15
59
|
const limit = clampLimit(options?.limit)
|
|
16
|
-
const
|
|
17
|
-
searchNpm(trimmed, limit),
|
|
18
|
-
searchGithub(trimmed, limit),
|
|
19
|
-
])
|
|
20
|
-
|
|
21
|
-
const hits: RegistrySearchHit[] = []
|
|
60
|
+
const urls = resolveRegistryUrls(options?.registryUrls)
|
|
22
61
|
const warnings: string[] = []
|
|
23
62
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
63
|
+
// Load index from all configured registries, merge kits
|
|
64
|
+
const allKits: RegistryKitEntry[] = []
|
|
65
|
+
for (const url of urls) {
|
|
66
|
+
try {
|
|
67
|
+
const index = await loadIndex(url)
|
|
68
|
+
if (index) {
|
|
69
|
+
allKits.push(...index.kits)
|
|
70
|
+
}
|
|
71
|
+
} catch (err) {
|
|
72
|
+
warnings.push(`Registry ${url}: ${toErrorMessage(err)}`)
|
|
73
|
+
}
|
|
28
74
|
}
|
|
29
75
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
76
|
+
// Score and rank
|
|
77
|
+
const hits = scoreKits(allKits, trimmed, limit)
|
|
78
|
+
|
|
79
|
+
return { query: trimmed, hits, warnings }
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// ── Index loading with cache ────────────────────────────────────────────────
|
|
83
|
+
|
|
84
|
+
async function loadIndex(url: string): Promise<RegistryIndex | null> {
|
|
85
|
+
const cachePath = indexCachePath(url)
|
|
86
|
+
const cached = readCachedIndex(cachePath)
|
|
87
|
+
|
|
88
|
+
// Fresh cache: return immediately
|
|
89
|
+
if (cached && !isCacheExpired(cached.mtime)) {
|
|
90
|
+
return cached.index
|
|
34
91
|
}
|
|
35
92
|
|
|
36
|
-
|
|
93
|
+
// Try to fetch fresh index
|
|
94
|
+
try {
|
|
95
|
+
const response = await fetchWithTimeout(url, undefined, 10_000)
|
|
96
|
+
if (!response.ok) {
|
|
97
|
+
throw new Error(`HTTP ${response.status}`)
|
|
98
|
+
}
|
|
99
|
+
const data = (await response.json()) as unknown
|
|
100
|
+
const index = parseRegistryIndex(data)
|
|
101
|
+
if (index) {
|
|
102
|
+
writeCachedIndex(cachePath, index)
|
|
103
|
+
return index
|
|
104
|
+
}
|
|
105
|
+
throw new Error("Invalid registry index format")
|
|
106
|
+
} catch (err) {
|
|
107
|
+
// Fetch failed — use stale cache if available
|
|
108
|
+
if (cached && !isCacheStale(cached.mtime)) {
|
|
109
|
+
return cached.index
|
|
110
|
+
}
|
|
111
|
+
throw err
|
|
112
|
+
}
|
|
113
|
+
}
|
|
37
114
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
115
|
+
function readCachedIndex(
|
|
116
|
+
cachePath: string,
|
|
117
|
+
): { index: RegistryIndex; mtime: number } | null {
|
|
118
|
+
try {
|
|
119
|
+
const stat = fs.statSync(cachePath)
|
|
120
|
+
const raw = JSON.parse(fs.readFileSync(cachePath, "utf8"))
|
|
121
|
+
const index = parseRegistryIndex(raw)
|
|
122
|
+
if (!index) return null
|
|
123
|
+
return { index, mtime: stat.mtimeMs }
|
|
124
|
+
} catch {
|
|
125
|
+
return null
|
|
42
126
|
}
|
|
43
127
|
}
|
|
44
128
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
129
|
+
function writeCachedIndex(cachePath: string, index: RegistryIndex): void {
|
|
130
|
+
try {
|
|
131
|
+
const dir = path.dirname(cachePath)
|
|
132
|
+
fs.mkdirSync(dir, { recursive: true })
|
|
133
|
+
const tmpPath = cachePath + `.tmp.${process.pid}`
|
|
134
|
+
fs.writeFileSync(tmpPath, JSON.stringify(index), "utf8")
|
|
135
|
+
fs.renameSync(tmpPath, cachePath)
|
|
136
|
+
} catch {
|
|
137
|
+
// Best-effort caching — don't fail the search if we can't write
|
|
50
138
|
}
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function indexCachePath(url: string): string {
|
|
142
|
+
const cacheRoot = resolveCacheDir()
|
|
143
|
+
// Deterministic filename from URL
|
|
144
|
+
const slug = url
|
|
145
|
+
.replace(/[^a-zA-Z0-9]+/g, "-")
|
|
146
|
+
.replace(/^-+|-+$/g, "")
|
|
147
|
+
.slice(0, 120)
|
|
148
|
+
return path.join(cacheRoot, "registry-index", `${slug}.json`)
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function resolveCacheDir(): string {
|
|
152
|
+
const xdgCache = process.env.XDG_CACHE_HOME?.trim()
|
|
153
|
+
if (xdgCache) return path.join(path.resolve(xdgCache), "agentikit")
|
|
154
|
+
const home = process.env.HOME?.trim()
|
|
155
|
+
if (!home) return path.join("/tmp", "agentikit-cache")
|
|
156
|
+
return path.join(path.resolve(home), ".cache", "agentikit")
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
function isCacheExpired(mtimeMs: number): boolean {
|
|
160
|
+
return Date.now() - mtimeMs > CACHE_TTL_MS
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
function isCacheStale(mtimeMs: number): boolean {
|
|
164
|
+
return Date.now() - mtimeMs > CACHE_STALE_MS
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// ── Index parsing ───────────────────────────────────────────────────────────
|
|
168
|
+
|
|
169
|
+
function parseRegistryIndex(data: unknown): RegistryIndex | null {
|
|
170
|
+
if (typeof data !== "object" || data === null || Array.isArray(data)) return null
|
|
171
|
+
const obj = data as Record<string, unknown>
|
|
172
|
+
|
|
173
|
+
if (typeof obj.version !== "number" || obj.version !== 1) return null
|
|
174
|
+
if (typeof obj.updatedAt !== "string") return null
|
|
175
|
+
if (!Array.isArray(obj.kits)) return null
|
|
176
|
+
|
|
177
|
+
const kits = obj.kits.flatMap((raw): RegistryKitEntry[] => {
|
|
178
|
+
const kit = parseKitEntry(raw)
|
|
179
|
+
return kit ? [kit] : []
|
|
77
180
|
})
|
|
181
|
+
|
|
182
|
+
return { version: 1, updatedAt: obj.updatedAt, kits }
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
function parseKitEntry(raw: unknown): RegistryKitEntry | null {
|
|
186
|
+
if (typeof raw !== "object" || raw === null || Array.isArray(raw)) return null
|
|
187
|
+
const obj = raw as Record<string, unknown>
|
|
188
|
+
|
|
189
|
+
const id = asString(obj.id)
|
|
190
|
+
const name = asString(obj.name)
|
|
191
|
+
const ref = asString(obj.ref)
|
|
192
|
+
const source = asSource(obj.source)
|
|
193
|
+
if (!id || !name || !ref || !source) return null
|
|
194
|
+
|
|
195
|
+
return {
|
|
196
|
+
id,
|
|
197
|
+
name,
|
|
198
|
+
ref,
|
|
199
|
+
source,
|
|
200
|
+
description: asString(obj.description),
|
|
201
|
+
homepage: asString(obj.homepage),
|
|
202
|
+
tags: asStringArray(obj.tags),
|
|
203
|
+
assetTypes: asStringArray(obj.assetTypes),
|
|
204
|
+
author: asString(obj.author),
|
|
205
|
+
license: asString(obj.license),
|
|
206
|
+
latestVersion: asString(obj.latestVersion),
|
|
207
|
+
curated: obj.curated === true ? true : undefined,
|
|
208
|
+
}
|
|
78
209
|
}
|
|
79
210
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
211
|
+
// ── Scoring ─────────────────────────────────────────────────────────────────
|
|
212
|
+
|
|
213
|
+
function scoreKits(
|
|
214
|
+
kits: RegistryKitEntry[],
|
|
215
|
+
query: string,
|
|
216
|
+
limit: number,
|
|
217
|
+
): RegistrySearchHit[] {
|
|
218
|
+
const tokens = query
|
|
219
|
+
.toLowerCase()
|
|
220
|
+
.split(/\s+/)
|
|
221
|
+
.filter(Boolean)
|
|
222
|
+
|
|
223
|
+
const scored: Array<{ kit: RegistryKitEntry; score: number }> = []
|
|
224
|
+
|
|
225
|
+
for (const kit of kits) {
|
|
226
|
+
const score = scoreKit(kit, tokens)
|
|
227
|
+
if (score > 0) {
|
|
228
|
+
scored.push({ kit, score })
|
|
229
|
+
}
|
|
86
230
|
}
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
return
|
|
91
|
-
const repo = asRecord(raw)
|
|
92
|
-
const fullName = asString(repo.full_name)
|
|
93
|
-
if (!fullName) return []
|
|
94
|
-
|
|
95
|
-
const metadata: Record<string, string> = {}
|
|
96
|
-
const stars = asNumber(repo.stargazers_count)
|
|
97
|
-
if (stars > 0) metadata.stars = String(stars)
|
|
98
|
-
const language = asString(repo.language)
|
|
99
|
-
if (language) metadata.language = language
|
|
100
|
-
|
|
101
|
-
return [{
|
|
102
|
-
source: "github",
|
|
103
|
-
id: `github:${fullName}`,
|
|
104
|
-
title: fullName,
|
|
105
|
-
description: asString(repo.description),
|
|
106
|
-
ref: fullName,
|
|
107
|
-
homepage: asString(repo.html_url),
|
|
108
|
-
score: stars,
|
|
109
|
-
metadata,
|
|
110
|
-
}]
|
|
111
|
-
})
|
|
231
|
+
|
|
232
|
+
scored.sort((a, b) => b.score - a.score)
|
|
233
|
+
|
|
234
|
+
return scored.slice(0, limit).map(({ kit, score }) => toSearchHit(kit, score))
|
|
112
235
|
}
|
|
113
236
|
|
|
114
|
-
function
|
|
115
|
-
|
|
116
|
-
const
|
|
117
|
-
|
|
118
|
-
|
|
237
|
+
function scoreKit(kit: RegistryKitEntry, tokens: string[]): number {
|
|
238
|
+
let score = 0
|
|
239
|
+
const nameLower = kit.name.toLowerCase()
|
|
240
|
+
const descLower = (kit.description ?? "").toLowerCase()
|
|
241
|
+
const tagsLower = (kit.tags ?? []).map((t) => t.toLowerCase())
|
|
242
|
+
|
|
243
|
+
for (const token of tokens) {
|
|
244
|
+
// Exact name match is strongest signal
|
|
245
|
+
if (nameLower === token) {
|
|
246
|
+
score += 1.0
|
|
247
|
+
} else if (nameLower.includes(token)) {
|
|
248
|
+
score += 0.6
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
// Tag matches are high-signal (curated keywords)
|
|
252
|
+
if (tagsLower.some((tag) => tag === token)) {
|
|
253
|
+
score += 0.5
|
|
254
|
+
} else if (tagsLower.some((tag) => tag.includes(token))) {
|
|
255
|
+
score += 0.25
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
// Description substring
|
|
259
|
+
if (descLower.includes(token)) {
|
|
260
|
+
score += 0.2
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
// Author match
|
|
264
|
+
if (kit.author?.toLowerCase().includes(token)) {
|
|
265
|
+
score += 0.15
|
|
266
|
+
}
|
|
119
267
|
}
|
|
120
|
-
|
|
121
|
-
|
|
268
|
+
|
|
269
|
+
// Normalize by token count so multi-word queries don't inflate scores
|
|
270
|
+
return tokens.length > 0 ? score / tokens.length : 0
|
|
122
271
|
}
|
|
123
272
|
|
|
273
|
+
function toSearchHit(kit: RegistryKitEntry, score: number): RegistrySearchHit {
|
|
274
|
+
const metadata: Record<string, string> = {}
|
|
275
|
+
if (kit.latestVersion) metadata.version = kit.latestVersion
|
|
276
|
+
if (kit.author) metadata.author = kit.author
|
|
277
|
+
if (kit.license) metadata.license = kit.license
|
|
278
|
+
if (kit.assetTypes?.length) metadata.assetTypes = kit.assetTypes.join(", ")
|
|
279
|
+
|
|
280
|
+
return {
|
|
281
|
+
source: kit.source,
|
|
282
|
+
id: kit.id,
|
|
283
|
+
title: kit.name,
|
|
284
|
+
description: kit.description,
|
|
285
|
+
ref: kit.ref,
|
|
286
|
+
homepage: kit.homepage,
|
|
287
|
+
score: Math.round(score * 1000) / 1000,
|
|
288
|
+
metadata,
|
|
289
|
+
curated: kit.curated,
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
// ── Registry URL resolution ─────────────────────────────────────────────────
|
|
294
|
+
|
|
295
|
+
function resolveRegistryUrls(override?: string | string[]): string[] {
|
|
296
|
+
if (override) {
|
|
297
|
+
const urls = Array.isArray(override) ? override : [override]
|
|
298
|
+
return urls.filter(Boolean)
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
// Allow env var override (comma-separated)
|
|
302
|
+
const envUrls = process.env.AKM_REGISTRY_URL?.trim()
|
|
303
|
+
if (envUrls) {
|
|
304
|
+
return envUrls.split(",").map((u) => u.trim()).filter(Boolean)
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
return [DEFAULT_REGISTRY_URL]
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
// ── Utilities ───────────────────────────────────────────────────────────────
|
|
311
|
+
|
|
124
312
|
function clampLimit(limit: number | undefined): number {
|
|
125
313
|
if (!limit || !Number.isFinite(limit)) return 20
|
|
126
314
|
return Math.min(100, Math.max(1, Math.trunc(limit)))
|
|
127
315
|
}
|
|
128
316
|
|
|
129
|
-
function asRecord(value: unknown): Record<string, unknown> {
|
|
130
|
-
return typeof value === "object" && value !== null && !Array.isArray(value)
|
|
131
|
-
? value as Record<string, unknown>
|
|
132
|
-
: {}
|
|
133
|
-
}
|
|
134
|
-
|
|
135
317
|
function asString(value: unknown): string | undefined {
|
|
136
318
|
return typeof value === "string" && value ? value : undefined
|
|
137
319
|
}
|
|
138
320
|
|
|
139
|
-
function
|
|
140
|
-
return
|
|
321
|
+
function asSource(value: unknown): "npm" | "github" | "git" | undefined {
|
|
322
|
+
return value === "npm" || value === "github" || value === "git"
|
|
323
|
+
? value
|
|
324
|
+
: undefined
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
function asStringArray(value: unknown): string[] | undefined {
|
|
328
|
+
if (!Array.isArray(value)) return undefined
|
|
329
|
+
const filtered = value.filter((v): v is string => typeof v === "string")
|
|
330
|
+
return filtered.length > 0 ? filtered : undefined
|
|
141
331
|
}
|
|
142
332
|
|
|
143
333
|
function toErrorMessage(error: unknown): string {
|
package/src/registry-types.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export type RegistrySource = "npm" | "github"
|
|
1
|
+
export type RegistrySource = "npm" | "github" | "git"
|
|
2
2
|
|
|
3
3
|
export interface RegistryRefBase {
|
|
4
4
|
source: RegistrySource
|
|
@@ -19,7 +19,13 @@ export interface ParsedGithubRef extends RegistryRefBase {
|
|
|
19
19
|
requestedRef?: string
|
|
20
20
|
}
|
|
21
21
|
|
|
22
|
-
export
|
|
22
|
+
export interface ParsedGitRef extends RegistryRefBase {
|
|
23
|
+
source: "git"
|
|
24
|
+
repoRoot: string
|
|
25
|
+
sourcePath: string
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export type ParsedRegistryRef = ParsedNpmRef | ParsedGithubRef | ParsedGitRef
|
|
23
29
|
|
|
24
30
|
export interface ResolvedRegistryArtifact {
|
|
25
31
|
id: string
|
|
@@ -55,6 +61,8 @@ export interface RegistrySearchHit {
|
|
|
55
61
|
homepage?: string
|
|
56
62
|
score?: number
|
|
57
63
|
metadata?: Record<string, string>
|
|
64
|
+
/** Whether this entry was manually reviewed and approved */
|
|
65
|
+
curated?: boolean
|
|
58
66
|
}
|
|
59
67
|
|
|
60
68
|
export interface RegistrySearchResponse {
|
package/src/stash-add.ts
CHANGED
|
@@ -7,25 +7,22 @@ import type { AddResponse } from "./stash-types"
|
|
|
7
7
|
|
|
8
8
|
export async function agentikitAdd(input: { ref: string }): Promise<AddResponse> {
|
|
9
9
|
const ref = input.ref.trim()
|
|
10
|
-
if (!ref) throw new Error("
|
|
10
|
+
if (!ref) throw new Error("Install ref or local git directory is required.")
|
|
11
11
|
|
|
12
12
|
const stashDir = resolveStashDir()
|
|
13
13
|
const installed = await installRegistryRef(ref)
|
|
14
|
-
const replaced = loadConfig(
|
|
15
|
-
const config = upsertInstalledRegistryEntry(
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
},
|
|
27
|
-
stashDir,
|
|
28
|
-
)
|
|
14
|
+
const replaced = loadConfig().registry?.installed.find((entry) => entry.id === installed.id)
|
|
15
|
+
const config = upsertInstalledRegistryEntry({
|
|
16
|
+
id: installed.id,
|
|
17
|
+
source: installed.source,
|
|
18
|
+
ref: installed.ref,
|
|
19
|
+
artifactUrl: installed.artifactUrl,
|
|
20
|
+
resolvedVersion: installed.resolvedVersion,
|
|
21
|
+
resolvedRevision: installed.resolvedRevision,
|
|
22
|
+
stashRoot: installed.stashRoot,
|
|
23
|
+
cacheDir: installed.cacheDir,
|
|
24
|
+
installedAt: installed.installedAt,
|
|
25
|
+
})
|
|
29
26
|
|
|
30
27
|
if (replaced && replaced.cacheDir !== installed.cacheDir) {
|
|
31
28
|
try {
|
|
@@ -53,7 +50,7 @@ export async function agentikitAdd(input: { ref: string }): Promise<AddResponse>
|
|
|
53
50
|
installedAt: installed.installedAt,
|
|
54
51
|
},
|
|
55
52
|
config: {
|
|
56
|
-
|
|
53
|
+
mountedStashDirs: config.mountedStashDirs,
|
|
57
54
|
installedRegistryCount: config.registry?.installed.length ?? 0,
|
|
58
55
|
},
|
|
59
56
|
index: {
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import fs from "node:fs"
|
|
2
|
+
import path from "node:path"
|
|
3
|
+
import { TYPE_DIRS } from "./asset-spec"
|
|
4
|
+
import { parseAssetRef, makeAssetRef } from "./stash-ref"
|
|
5
|
+
import { resolveSourcesForOrigin } from "./origin-resolve"
|
|
6
|
+
import { resolveAssetPath } from "./stash-resolve"
|
|
7
|
+
import { resolveStashSources, findSourceForPath, type StashSource, type StashSourceKind } from "./stash-source"
|
|
8
|
+
|
|
9
|
+
export interface CloneOptions {
|
|
10
|
+
/** Source ref (e.g., npm:@scope/pkg//tool:deploy.sh) */
|
|
11
|
+
sourceRef: string
|
|
12
|
+
/** Optional new name for the cloned asset */
|
|
13
|
+
newName?: string
|
|
14
|
+
/** If true, overwrite existing asset in working stash */
|
|
15
|
+
force?: boolean
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface CloneResponse {
|
|
19
|
+
source: {
|
|
20
|
+
path: string
|
|
21
|
+
sourceKind: StashSourceKind
|
|
22
|
+
registryId?: string
|
|
23
|
+
}
|
|
24
|
+
destination: {
|
|
25
|
+
path: string
|
|
26
|
+
ref: string
|
|
27
|
+
}
|
|
28
|
+
overwritten: boolean
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export async function agentikitClone(options: CloneOptions): Promise<CloneResponse> {
|
|
32
|
+
const parsed = parseAssetRef(options.sourceRef)
|
|
33
|
+
const allSources = resolveStashSources()
|
|
34
|
+
const workingSource = allSources.find((s) => s.kind === "working")
|
|
35
|
+
if (!workingSource) {
|
|
36
|
+
throw new Error("No working stash configured. Run `akm init` first.")
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const searchSources = resolveSourcesForOrigin(parsed.origin, allSources)
|
|
40
|
+
|
|
41
|
+
let sourcePath: string | undefined
|
|
42
|
+
let lastError: Error | undefined
|
|
43
|
+
for (const source of searchSources) {
|
|
44
|
+
try {
|
|
45
|
+
sourcePath = resolveAssetPath(source.path, parsed.type, parsed.name)
|
|
46
|
+
break
|
|
47
|
+
} catch (err) {
|
|
48
|
+
lastError = err instanceof Error ? err : new Error(String(err))
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
if (!sourcePath) {
|
|
52
|
+
throw lastError ?? new Error(`Source asset not found for ref: ${options.sourceRef}`)
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const sourceSource = findSourceForPath(sourcePath, allSources)
|
|
56
|
+
const sourceKind = sourceSource?.kind ?? "working"
|
|
57
|
+
|
|
58
|
+
const destName = options.newName ?? parsed.name
|
|
59
|
+
const typeDir = TYPE_DIRS[parsed.type]
|
|
60
|
+
const workingDir = workingSource.path
|
|
61
|
+
|
|
62
|
+
// Guard against self-clone
|
|
63
|
+
if (parsed.type === "skill") {
|
|
64
|
+
const sourceSkillDir = path.resolve(path.dirname(sourcePath))
|
|
65
|
+
const destSkillDir = path.resolve(path.join(workingDir, typeDir, destName))
|
|
66
|
+
if (sourceSkillDir === destSkillDir) {
|
|
67
|
+
throw new Error(
|
|
68
|
+
`Source and destination are the same path. Use --name to provide a new name for the clone.`,
|
|
69
|
+
)
|
|
70
|
+
}
|
|
71
|
+
} else {
|
|
72
|
+
const resolvedSource = path.resolve(sourcePath)
|
|
73
|
+
const resolvedDest = path.resolve(path.join(workingDir, typeDir, destName))
|
|
74
|
+
if (resolvedSource === resolvedDest) {
|
|
75
|
+
throw new Error(
|
|
76
|
+
`Source and destination are the same path. Use --name to provide a new name for the clone.`,
|
|
77
|
+
)
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
let destPath: string
|
|
82
|
+
if (parsed.type === "skill") {
|
|
83
|
+
const sourceSkillDir = path.dirname(sourcePath)
|
|
84
|
+
const destSkillDir = path.join(workingDir, typeDir, destName)
|
|
85
|
+
const overwritten = fs.existsSync(destSkillDir)
|
|
86
|
+
|
|
87
|
+
if (overwritten && !options.force) {
|
|
88
|
+
throw new Error(
|
|
89
|
+
`Asset already exists in working stash: ${destSkillDir}. Use --force to overwrite.`,
|
|
90
|
+
)
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
if (overwritten) {
|
|
94
|
+
fs.rmSync(destSkillDir, { recursive: true, force: true })
|
|
95
|
+
}
|
|
96
|
+
fs.cpSync(sourceSkillDir, destSkillDir, { recursive: true })
|
|
97
|
+
|
|
98
|
+
destPath = path.join(destSkillDir, "SKILL.md")
|
|
99
|
+
const ref = makeAssetRef(parsed.type, destName, "local")
|
|
100
|
+
|
|
101
|
+
return {
|
|
102
|
+
source: { path: sourcePath, sourceKind, registryId: sourceSource?.registryId },
|
|
103
|
+
destination: { path: destPath, ref },
|
|
104
|
+
overwritten,
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
destPath = path.join(workingDir, typeDir, destName)
|
|
109
|
+
const overwritten = fs.existsSync(destPath)
|
|
110
|
+
|
|
111
|
+
if (overwritten && !options.force) {
|
|
112
|
+
throw new Error(
|
|
113
|
+
`Asset already exists in working stash: ${destPath}. Use --force to overwrite.`,
|
|
114
|
+
)
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
fs.mkdirSync(path.dirname(destPath), { recursive: true })
|
|
118
|
+
fs.copyFileSync(sourcePath, destPath)
|
|
119
|
+
|
|
120
|
+
const ref = makeAssetRef(parsed.type, destName, "local")
|
|
121
|
+
|
|
122
|
+
return {
|
|
123
|
+
source: { path: sourcePath, sourceKind, registryId: sourceSource?.registryId },
|
|
124
|
+
destination: { path: destPath, ref },
|
|
125
|
+
overwritten,
|
|
126
|
+
}
|
|
127
|
+
}
|