agentikit 0.0.13 → 0.0.14
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 +385 -0
- package/README.md +180 -110
- package/dist/cli.js +671 -0
- package/dist/common.js +192 -0
- package/dist/{src/config-cli.js → config-cli.js} +14 -6
- package/dist/{src/config.js → config.js} +92 -24
- package/dist/{src/db.js → db.js} +109 -35
- package/dist/{src/embedder.js → embedder.js} +57 -2
- package/dist/file-context.js +158 -0
- package/dist/{src/handlers → handlers}/command-handler.js +2 -0
- package/dist/{src/handlers → handlers}/index.js +0 -6
- package/dist/{src/indexer.js → indexer.js} +34 -10
- package/dist/init.js +43 -0
- package/dist/lockfile.js +55 -0
- package/dist/matchers.js +157 -0
- package/dist/{src/metadata.js → metadata.js} +12 -1
- package/dist/{src/origin-resolve.js → origin-resolve.js} +10 -9
- package/dist/paths.js +82 -0
- package/dist/{src/registry-install.js → registry-install.js} +145 -17
- package/dist/{src/registry-resolve.js → registry-resolve.js} +178 -18
- package/dist/{src/registry-search.js → registry-search.js} +8 -16
- package/dist/renderers.js +276 -0
- package/dist/{src/ripgrep-install.js → ripgrep-install.js} +5 -5
- package/dist/{src/ripgrep-resolve.js → ripgrep-resolve.js} +21 -11
- package/dist/self-update.js +220 -0
- package/dist/{src/stash-add.js → stash-add.js} +11 -2
- package/dist/stash-clone.js +115 -0
- package/dist/{src/stash-registry.js → stash-registry.js} +15 -41
- package/dist/{src/stash-search.js → stash-search.js} +67 -55
- package/dist/{src/stash-show.js → stash-show.js} +30 -3
- package/dist/{src/stash-source.js → stash-source.js} +56 -9
- package/dist/submit.js +552 -0
- package/dist/{src/walker.js → walker.js} +38 -0
- package/package.json +7 -16
- package/dist/index.d.ts +0 -28
- package/dist/index.js +0 -15
- package/dist/src/asset-spec.d.ts +0 -16
- package/dist/src/asset-type-handler.d.ts +0 -27
- package/dist/src/cli.d.ts +0 -2
- package/dist/src/cli.js +0 -399
- package/dist/src/common.d.ts +0 -13
- package/dist/src/common.js +0 -60
- package/dist/src/config-cli.d.ts +0 -9
- package/dist/src/config.d.ts +0 -50
- package/dist/src/db.d.ts +0 -46
- package/dist/src/embedder.d.ts +0 -10
- package/dist/src/frontmatter.d.ts +0 -30
- package/dist/src/github.d.ts +0 -4
- package/dist/src/handlers/agent-handler.d.ts +0 -2
- package/dist/src/handlers/command-handler.d.ts +0 -2
- package/dist/src/handlers/index.d.ts +0 -6
- package/dist/src/handlers/knowledge-handler.d.ts +0 -2
- package/dist/src/handlers/markdown-helpers.d.ts +0 -7
- package/dist/src/handlers/script-handler.d.ts +0 -2
- package/dist/src/handlers/skill-handler.d.ts +0 -2
- package/dist/src/handlers/tool-handler.d.ts +0 -2
- package/dist/src/indexer.d.ts +0 -22
- package/dist/src/init.d.ts +0 -19
- package/dist/src/init.js +0 -99
- package/dist/src/llm.d.ts +0 -15
- package/dist/src/markdown.d.ts +0 -18
- package/dist/src/metadata.d.ts +0 -41
- package/dist/src/origin-resolve.d.ts +0 -19
- package/dist/src/registry-install.d.ts +0 -11
- package/dist/src/registry-resolve.d.ts +0 -3
- package/dist/src/registry-search.d.ts +0 -27
- package/dist/src/registry-types.d.ts +0 -62
- package/dist/src/ripgrep-install.d.ts +0 -12
- package/dist/src/ripgrep-resolve.d.ts +0 -13
- package/dist/src/ripgrep.d.ts +0 -3
- package/dist/src/stash-add.d.ts +0 -4
- package/dist/src/stash-clone.d.ts +0 -22
- package/dist/src/stash-clone.js +0 -83
- package/dist/src/stash-ref.d.ts +0 -31
- package/dist/src/stash-registry.d.ts +0 -18
- package/dist/src/stash-resolve.d.ts +0 -2
- package/dist/src/stash-search.d.ts +0 -8
- package/dist/src/stash-show.d.ts +0 -5
- package/dist/src/stash-source.d.ts +0 -24
- package/dist/src/stash-types.d.ts +0 -227
- package/dist/src/stash.d.ts +0 -16
- package/dist/src/stash.js +0 -9
- package/dist/src/tool-runner.d.ts +0 -35
- package/dist/src/walker.d.ts +0 -19
- package/src/asset-spec.ts +0 -85
- package/src/asset-type-handler.ts +0 -77
- package/src/cli.ts +0 -427
- package/src/common.ts +0 -76
- package/src/config-cli.ts +0 -499
- package/src/config.ts +0 -305
- package/src/db.ts +0 -411
- package/src/embedder.ts +0 -128
- package/src/frontmatter.ts +0 -95
- package/src/github.ts +0 -21
- package/src/handlers/agent-handler.ts +0 -32
- package/src/handlers/command-handler.ts +0 -29
- package/src/handlers/index.ts +0 -25
- package/src/handlers/knowledge-handler.ts +0 -62
- package/src/handlers/markdown-helpers.ts +0 -19
- package/src/handlers/script-handler.ts +0 -92
- package/src/handlers/skill-handler.ts +0 -37
- package/src/handlers/tool-handler.ts +0 -71
- package/src/indexer.ts +0 -392
- package/src/init.ts +0 -114
- package/src/llm.ts +0 -125
- package/src/markdown.ts +0 -106
- package/src/metadata.ts +0 -333
- package/src/origin-resolve.ts +0 -67
- package/src/registry-install.ts +0 -361
- package/src/registry-resolve.ts +0 -341
- package/src/registry-search.ts +0 -335
- package/src/registry-types.ts +0 -72
- package/src/ripgrep-install.ts +0 -200
- package/src/ripgrep-resolve.ts +0 -72
- package/src/ripgrep.ts +0 -3
- package/src/stash-add.ts +0 -63
- package/src/stash-clone.ts +0 -127
- package/src/stash-ref.ts +0 -99
- package/src/stash-registry.ts +0 -259
- package/src/stash-resolve.ts +0 -50
- package/src/stash-search.ts +0 -613
- package/src/stash-show.ts +0 -55
- package/src/stash-source.ts +0 -103
- package/src/stash-types.ts +0 -231
- package/src/stash.ts +0 -39
- package/src/tool-runner.ts +0 -142
- package/src/walker.ts +0 -53
- /package/dist/{src/asset-spec.js → asset-spec.js} +0 -0
- /package/dist/{src/asset-type-handler.js → asset-type-handler.js} +0 -0
- /package/dist/{src/frontmatter.js → frontmatter.js} +0 -0
- /package/dist/{src/github.js → github.js} +0 -0
- /package/dist/{src/handlers → handlers}/agent-handler.js +0 -0
- /package/dist/{src/handlers → handlers}/knowledge-handler.js +0 -0
- /package/dist/{src/handlers → handlers}/markdown-helpers.js +0 -0
- /package/dist/{src/handlers → handlers}/script-handler.js +0 -0
- /package/dist/{src/handlers → handlers}/skill-handler.js +0 -0
- /package/dist/{src/handlers → handlers}/tool-handler.js +0 -0
- /package/dist/{src/llm.js → llm.js} +0 -0
- /package/dist/{src/markdown.js → markdown.js} +0 -0
- /package/dist/{src/registry-types.js → registry-types.js} +0 -0
- /package/dist/{src/ripgrep.js → ripgrep.js} +0 -0
- /package/dist/{src/stash-ref.js → stash-ref.js} +0 -0
- /package/dist/{src/stash-resolve.js → stash-resolve.js} +0 -0
- /package/dist/{src/stash-types.js → stash-types.js} +0 -0
- /package/dist/{src/tool-runner.js → tool-runner.js} +0 -0
package/src/registry-search.ts
DELETED
|
@@ -1,335 +0,0 @@
|
|
|
1
|
-
import fs from "node:fs"
|
|
2
|
-
import path from "node:path"
|
|
3
|
-
import { fetchWithTimeout } from "./common"
|
|
4
|
-
import type { RegistrySearchHit, RegistrySearchResponse } from "./registry-types"
|
|
5
|
-
|
|
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
|
-
}
|
|
41
|
-
|
|
42
|
-
export interface RegistrySearchOptions {
|
|
43
|
-
limit?: number
|
|
44
|
-
/** Override registry URL(s). Accepts a single URL or an array. */
|
|
45
|
-
registryUrls?: string | string[]
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
// ── Public API ──────────────────────────────────────────────────────────────
|
|
49
|
-
|
|
50
|
-
export async function searchRegistry(
|
|
51
|
-
query: string,
|
|
52
|
-
options?: RegistrySearchOptions,
|
|
53
|
-
): Promise<RegistrySearchResponse> {
|
|
54
|
-
const trimmed = query.trim()
|
|
55
|
-
if (!trimmed) {
|
|
56
|
-
return { query: "", hits: [], warnings: [] }
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
const limit = clampLimit(options?.limit)
|
|
60
|
-
const urls = resolveRegistryUrls(options?.registryUrls)
|
|
61
|
-
const warnings: string[] = []
|
|
62
|
-
|
|
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
|
-
}
|
|
74
|
-
}
|
|
75
|
-
|
|
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
|
|
91
|
-
}
|
|
92
|
-
|
|
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
|
-
}
|
|
114
|
-
|
|
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
|
|
126
|
-
}
|
|
127
|
-
}
|
|
128
|
-
|
|
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
|
|
138
|
-
}
|
|
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] : []
|
|
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
|
-
}
|
|
209
|
-
}
|
|
210
|
-
|
|
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
|
-
}
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
scored.sort((a, b) => b.score - a.score)
|
|
233
|
-
|
|
234
|
-
return scored.slice(0, limit).map(({ kit, score }) => toSearchHit(kit, score))
|
|
235
|
-
}
|
|
236
|
-
|
|
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
|
-
}
|
|
267
|
-
}
|
|
268
|
-
|
|
269
|
-
// Normalize by token count so multi-word queries don't inflate scores
|
|
270
|
-
return tokens.length > 0 ? score / tokens.length : 0
|
|
271
|
-
}
|
|
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
|
-
|
|
312
|
-
function clampLimit(limit: number | undefined): number {
|
|
313
|
-
if (!limit || !Number.isFinite(limit)) return 20
|
|
314
|
-
return Math.min(100, Math.max(1, Math.trunc(limit)))
|
|
315
|
-
}
|
|
316
|
-
|
|
317
|
-
function asString(value: unknown): string | undefined {
|
|
318
|
-
return typeof value === "string" && value ? value : undefined
|
|
319
|
-
}
|
|
320
|
-
|
|
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
|
|
331
|
-
}
|
|
332
|
-
|
|
333
|
-
function toErrorMessage(error: unknown): string {
|
|
334
|
-
return error instanceof Error ? error.message : String(error)
|
|
335
|
-
}
|
package/src/registry-types.ts
DELETED
|
@@ -1,72 +0,0 @@
|
|
|
1
|
-
export type RegistrySource = "npm" | "github" | "git"
|
|
2
|
-
|
|
3
|
-
export interface RegistryRefBase {
|
|
4
|
-
source: RegistrySource
|
|
5
|
-
ref: string
|
|
6
|
-
id: string
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
export interface ParsedNpmRef extends RegistryRefBase {
|
|
10
|
-
source: "npm"
|
|
11
|
-
packageName: string
|
|
12
|
-
requestedVersionOrTag?: string
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
export interface ParsedGithubRef extends RegistryRefBase {
|
|
16
|
-
source: "github"
|
|
17
|
-
owner: string
|
|
18
|
-
repo: string
|
|
19
|
-
requestedRef?: string
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
export interface ParsedGitRef extends RegistryRefBase {
|
|
23
|
-
source: "git"
|
|
24
|
-
repoRoot: string
|
|
25
|
-
sourcePath: string
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
export type ParsedRegistryRef = ParsedNpmRef | ParsedGithubRef | ParsedGitRef
|
|
29
|
-
|
|
30
|
-
export interface ResolvedRegistryArtifact {
|
|
31
|
-
id: string
|
|
32
|
-
source: RegistrySource
|
|
33
|
-
ref: string
|
|
34
|
-
artifactUrl: string
|
|
35
|
-
resolvedVersion?: string
|
|
36
|
-
resolvedRevision?: string
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
export interface RegistryInstalledEntry {
|
|
40
|
-
id: string
|
|
41
|
-
source: RegistrySource
|
|
42
|
-
ref: string
|
|
43
|
-
resolvedVersion?: string
|
|
44
|
-
resolvedRevision?: string
|
|
45
|
-
artifactUrl: string
|
|
46
|
-
stashRoot: string
|
|
47
|
-
cacheDir: string
|
|
48
|
-
installedAt: string
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
export interface RegistryInstallResult extends RegistryInstalledEntry {
|
|
52
|
-
extractedDir: string
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
export interface RegistrySearchHit {
|
|
56
|
-
source: RegistrySource
|
|
57
|
-
id: string
|
|
58
|
-
title: string
|
|
59
|
-
description?: string
|
|
60
|
-
ref: string
|
|
61
|
-
homepage?: string
|
|
62
|
-
score?: number
|
|
63
|
-
metadata?: Record<string, string>
|
|
64
|
-
/** Whether this entry was manually reviewed and approved */
|
|
65
|
-
curated?: boolean
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
export interface RegistrySearchResponse {
|
|
69
|
-
query: string
|
|
70
|
-
hits: RegistrySearchHit[]
|
|
71
|
-
warnings: string[]
|
|
72
|
-
}
|
package/src/ripgrep-install.ts
DELETED
|
@@ -1,200 +0,0 @@
|
|
|
1
|
-
import { spawnSync } from "node:child_process"
|
|
2
|
-
import fs from "node:fs"
|
|
3
|
-
import path from "node:path"
|
|
4
|
-
import { IS_WINDOWS } from "./common"
|
|
5
|
-
import { RG_BINARY, resolveRg } from "./ripgrep-resolve"
|
|
6
|
-
|
|
7
|
-
/**
|
|
8
|
-
* Platform and architecture detection for ripgrep binary downloads.
|
|
9
|
-
*/
|
|
10
|
-
function getRgPlatformTarget(): { platform: string; arch: string; ext: string } | null {
|
|
11
|
-
const platform = process.platform
|
|
12
|
-
const arch = process.arch
|
|
13
|
-
|
|
14
|
-
if (platform === "linux" && arch === "x64") {
|
|
15
|
-
return { platform: "x86_64-unknown-linux-musl", arch: "x64", ext: ".tar.gz" }
|
|
16
|
-
}
|
|
17
|
-
if (platform === "linux" && arch === "arm64") {
|
|
18
|
-
return { platform: "aarch64-unknown-linux-gnu", arch: "arm64", ext: ".tar.gz" }
|
|
19
|
-
}
|
|
20
|
-
if (platform === "darwin" && arch === "x64") {
|
|
21
|
-
return { platform: "x86_64-apple-darwin", arch: "x64", ext: ".tar.gz" }
|
|
22
|
-
}
|
|
23
|
-
if (platform === "darwin" && arch === "arm64") {
|
|
24
|
-
return { platform: "aarch64-apple-darwin", arch: "arm64", ext: ".tar.gz" }
|
|
25
|
-
}
|
|
26
|
-
if (platform === "win32" && arch === "x64") {
|
|
27
|
-
return { platform: "x86_64-pc-windows-msvc", arch: "x64", ext: ".zip" }
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
return null
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
const RG_VERSION = "14.1.1"
|
|
34
|
-
|
|
35
|
-
export interface EnsureRgResult {
|
|
36
|
-
rgPath: string
|
|
37
|
-
installed: boolean
|
|
38
|
-
version: string
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
/**
|
|
42
|
-
* Ensure ripgrep is available. If not found on PATH or in stash/bin,
|
|
43
|
-
* download and install it to stash/bin.
|
|
44
|
-
*
|
|
45
|
-
* Returns the path to the ripgrep binary and whether it was newly installed.
|
|
46
|
-
*/
|
|
47
|
-
export function ensureRg(stashDir: string): EnsureRgResult {
|
|
48
|
-
// Already available?
|
|
49
|
-
const existing = resolveRg(stashDir)
|
|
50
|
-
if (existing) {
|
|
51
|
-
return { rgPath: existing, installed: false, version: getRgVersion(existing) }
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
// Determine platform
|
|
55
|
-
const target = getRgPlatformTarget()
|
|
56
|
-
if (!target) {
|
|
57
|
-
throw new Error(
|
|
58
|
-
`Unsupported platform for ripgrep auto-install: ${process.platform}/${process.arch}. ` +
|
|
59
|
-
`Install ripgrep manually: https://github.com/BurntSushi/ripgrep#installation`
|
|
60
|
-
)
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
const binDir = path.join(stashDir, "bin")
|
|
64
|
-
if (!fs.existsSync(binDir)) {
|
|
65
|
-
fs.mkdirSync(binDir, { recursive: true })
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
const archiveName = `ripgrep-${RG_VERSION}-${target.platform}`
|
|
69
|
-
const url = `https://github.com/BurntSushi/ripgrep/releases/download/${RG_VERSION}/${archiveName}${target.ext}`
|
|
70
|
-
const destBinary = path.join(binDir, RG_BINARY)
|
|
71
|
-
|
|
72
|
-
if (target.ext === ".tar.gz") {
|
|
73
|
-
downloadAndExtractTarGz(url, archiveName, destBinary)
|
|
74
|
-
} else {
|
|
75
|
-
downloadAndExtractZip(url, archiveName, destBinary)
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
// Make executable
|
|
79
|
-
if (!IS_WINDOWS) {
|
|
80
|
-
fs.chmodSync(destBinary, 0o755)
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
return { rgPath: destBinary, installed: true, version: RG_VERSION }
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
function downloadAndExtractTarGz(url: string, archiveName: string, destBinary: string): void {
|
|
87
|
-
const destDir = path.dirname(destBinary)
|
|
88
|
-
const tmpTarGz = path.join(destDir, "rg-download.tar.gz")
|
|
89
|
-
|
|
90
|
-
try {
|
|
91
|
-
// Download archive to a temporary file without using a shell
|
|
92
|
-
const curlResult = spawnSync(
|
|
93
|
-
"curl",
|
|
94
|
-
["-fsSL", "-o", tmpTarGz, url],
|
|
95
|
-
{
|
|
96
|
-
encoding: "utf8",
|
|
97
|
-
timeout: 60_000,
|
|
98
|
-
}
|
|
99
|
-
)
|
|
100
|
-
|
|
101
|
-
if (curlResult.status !== 0) {
|
|
102
|
-
const err = curlResult.stderr?.trim() || curlResult.error?.message || "unknown error"
|
|
103
|
-
throw new Error(`Failed to download ripgrep from ${url}: ${err}`)
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
// Extract the specific binary from the archive into destDir
|
|
107
|
-
const tarResult = spawnSync(
|
|
108
|
-
"tar",
|
|
109
|
-
[
|
|
110
|
-
"xzf",
|
|
111
|
-
tmpTarGz,
|
|
112
|
-
"--strip-components=1",
|
|
113
|
-
"-C",
|
|
114
|
-
destDir,
|
|
115
|
-
`${archiveName}/rg`,
|
|
116
|
-
],
|
|
117
|
-
{
|
|
118
|
-
encoding: "utf8",
|
|
119
|
-
timeout: 60_000,
|
|
120
|
-
}
|
|
121
|
-
)
|
|
122
|
-
|
|
123
|
-
if (tarResult.status !== 0) {
|
|
124
|
-
const err = tarResult.stderr?.trim() || tarResult.error?.message || "unknown error"
|
|
125
|
-
throw new Error(`Failed to extract ripgrep from ${url}: ${err}`)
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
if (!fs.existsSync(destBinary)) {
|
|
129
|
-
throw new Error(`ripgrep binary not found at ${destBinary} after extraction`)
|
|
130
|
-
}
|
|
131
|
-
} finally {
|
|
132
|
-
// Best-effort cleanup of temporary archive
|
|
133
|
-
try {
|
|
134
|
-
if (fs.existsSync(tmpTarGz)) {
|
|
135
|
-
fs.unlinkSync(tmpTarGz)
|
|
136
|
-
}
|
|
137
|
-
} catch {
|
|
138
|
-
// ignore cleanup errors
|
|
139
|
-
}
|
|
140
|
-
}
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
function downloadAndExtractZip(url: string, archiveName: string, destBinary: string): void {
|
|
144
|
-
const destDir = path.dirname(destBinary)
|
|
145
|
-
const tmpZip = path.join(destDir, "rg-download.zip")
|
|
146
|
-
const expandedDir = path.join(destDir, archiveName)
|
|
147
|
-
try {
|
|
148
|
-
// Download
|
|
149
|
-
const dlResult = spawnSync("curl", ["-fsSL", "-o", tmpZip, url], {
|
|
150
|
-
encoding: "utf8",
|
|
151
|
-
timeout: 60_000,
|
|
152
|
-
})
|
|
153
|
-
if (dlResult.status !== 0) {
|
|
154
|
-
throw new Error(dlResult.stderr?.trim() || "download failed")
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
// Extract the zip archive using separate spawnSync calls with argument arrays
|
|
158
|
-
// to avoid shell injection via path interpolation in PowerShell -Command strings
|
|
159
|
-
const expandResult = spawnSync("powershell", [
|
|
160
|
-
"-Command",
|
|
161
|
-
"Expand-Archive",
|
|
162
|
-
"-Path", tmpZip,
|
|
163
|
-
"-DestinationPath", destDir,
|
|
164
|
-
"-Force",
|
|
165
|
-
], {
|
|
166
|
-
encoding: "utf8",
|
|
167
|
-
timeout: 60_000,
|
|
168
|
-
})
|
|
169
|
-
if (expandResult.status !== 0) {
|
|
170
|
-
throw new Error(expandResult.stderr?.trim() || "extraction failed")
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
const srcRgExe = path.join(destDir, archiveName, "rg.exe")
|
|
174
|
-
const moveResult = spawnSync("powershell", [
|
|
175
|
-
"-Command",
|
|
176
|
-
"Move-Item",
|
|
177
|
-
"-Force",
|
|
178
|
-
"-Path", srcRgExe,
|
|
179
|
-
"-Destination", destBinary,
|
|
180
|
-
], {
|
|
181
|
-
encoding: "utf8",
|
|
182
|
-
timeout: 60_000,
|
|
183
|
-
})
|
|
184
|
-
if (moveResult.status !== 0) {
|
|
185
|
-
throw new Error(moveResult.stderr?.trim() || "move failed")
|
|
186
|
-
}
|
|
187
|
-
} finally {
|
|
188
|
-
if (fs.existsSync(tmpZip)) fs.unlinkSync(tmpZip)
|
|
189
|
-
if (fs.existsSync(expandedDir)) fs.rmSync(expandedDir, { recursive: true, force: true })
|
|
190
|
-
}
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
function getRgVersion(rgPath: string): string {
|
|
194
|
-
const result = spawnSync(rgPath, ["--version"], { encoding: "utf8", timeout: 5_000 })
|
|
195
|
-
if (result.status === 0 && result.stdout) {
|
|
196
|
-
const match = result.stdout.match(/ripgrep\s+([\d.]+)/)
|
|
197
|
-
return match ? match[1] : "unknown"
|
|
198
|
-
}
|
|
199
|
-
return "unknown"
|
|
200
|
-
}
|
package/src/ripgrep-resolve.ts
DELETED
|
@@ -1,72 +0,0 @@
|
|
|
1
|
-
import fs from "node:fs"
|
|
2
|
-
import path from "node:path"
|
|
3
|
-
import { IS_WINDOWS } from "./common"
|
|
4
|
-
|
|
5
|
-
export const RG_BINARY = IS_WINDOWS ? "rg.exe" : "rg"
|
|
6
|
-
|
|
7
|
-
function canExecute(filePath: string): boolean {
|
|
8
|
-
if (!fs.existsSync(filePath)) return false
|
|
9
|
-
if (IS_WINDOWS) return true
|
|
10
|
-
try {
|
|
11
|
-
fs.accessSync(filePath, fs.constants.X_OK)
|
|
12
|
-
return true
|
|
13
|
-
} catch {
|
|
14
|
-
return false
|
|
15
|
-
}
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
function resolveFromPath(): string | null {
|
|
19
|
-
const rawPath = process.env.PATH
|
|
20
|
-
if (!rawPath) return null
|
|
21
|
-
|
|
22
|
-
const pathEntries = rawPath.split(path.delimiter).filter(Boolean)
|
|
23
|
-
|
|
24
|
-
if (IS_WINDOWS) {
|
|
25
|
-
const pathext = (process.env.PATHEXT || ".EXE;.CMD;.BAT;.COM")
|
|
26
|
-
.split(";")
|
|
27
|
-
.filter(Boolean)
|
|
28
|
-
.map((ext) => ext.toLowerCase())
|
|
29
|
-
|
|
30
|
-
for (const entry of pathEntries) {
|
|
31
|
-
const directCandidate = path.join(entry, "rg")
|
|
32
|
-
if (canExecute(directCandidate)) return directCandidate
|
|
33
|
-
|
|
34
|
-
for (const ext of pathext) {
|
|
35
|
-
const candidate = path.join(entry, `rg${ext}`)
|
|
36
|
-
if (canExecute(candidate)) return candidate
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
return null
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
for (const entry of pathEntries) {
|
|
43
|
-
const candidate = path.join(entry, "rg")
|
|
44
|
-
if (canExecute(candidate)) return candidate
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
return null
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
/**
|
|
51
|
-
* Resolve the path to a usable ripgrep binary.
|
|
52
|
-
* Checks in order:
|
|
53
|
-
* 1. stashDir/bin/rg
|
|
54
|
-
* 2. system PATH (rg)
|
|
55
|
-
* Returns null if ripgrep is not available.
|
|
56
|
-
*/
|
|
57
|
-
export function resolveRg(stashDir?: string): string | null {
|
|
58
|
-
// Check stash bin directory first
|
|
59
|
-
if (stashDir) {
|
|
60
|
-
const stashRg = path.join(stashDir, "bin", RG_BINARY)
|
|
61
|
-
if (canExecute(stashRg)) return stashRg
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
return resolveFromPath()
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
/**
|
|
68
|
-
* Check if ripgrep is available (either in stash/bin or system PATH).
|
|
69
|
-
*/
|
|
70
|
-
export function isRgAvailable(stashDir?: string): boolean {
|
|
71
|
-
return resolveRg(stashDir) !== null
|
|
72
|
-
}
|
package/src/ripgrep.ts
DELETED