ab49fa8c9c
Refactored skill discovery to download skills in parallel instead of sequentially, reducing load times when multiple skills need to be fetched from remote URLs. Also updated AGENTS.md with guidance on using dev branch for diffs.
98 lines
2.7 KiB
TypeScript
98 lines
2.7 KiB
TypeScript
import path from "path"
|
|
import { mkdir } from "fs/promises"
|
|
import { Log } from "../util/log"
|
|
import { Global } from "../global"
|
|
|
|
export namespace Discovery {
|
|
const log = Log.create({ service: "skill-discovery" })
|
|
|
|
type Index = {
|
|
skills: Array<{
|
|
name: string
|
|
description: string
|
|
files: string[]
|
|
}>
|
|
}
|
|
|
|
export function dir() {
|
|
return path.join(Global.Path.cache, "skills")
|
|
}
|
|
|
|
async function get(url: string, dest: string): Promise<boolean> {
|
|
if (await Bun.file(dest).exists()) return true
|
|
return fetch(url)
|
|
.then(async (response) => {
|
|
if (!response.ok) {
|
|
log.error("failed to download", { url, status: response.status })
|
|
return false
|
|
}
|
|
await Bun.write(dest, await response.text())
|
|
return true
|
|
})
|
|
.catch((err) => {
|
|
log.error("failed to download", { url, err })
|
|
return false
|
|
})
|
|
}
|
|
|
|
export async function pull(url: string): Promise<string[]> {
|
|
const result: string[] = []
|
|
const base = url.endsWith("/") ? url : `${url}/`
|
|
const index = new URL("index.json", base).href
|
|
const cache = dir()
|
|
const host = base.slice(0, -1)
|
|
|
|
log.info("fetching index", { url: index })
|
|
const data = await fetch(index)
|
|
.then(async (response) => {
|
|
if (!response.ok) {
|
|
log.error("failed to fetch index", { url: index, status: response.status })
|
|
return undefined
|
|
}
|
|
return response
|
|
.json()
|
|
.then((json) => json as Index)
|
|
.catch((err) => {
|
|
log.error("failed to parse index", { url: index, err })
|
|
return undefined
|
|
})
|
|
})
|
|
.catch((err) => {
|
|
log.error("failed to fetch index", { url: index, err })
|
|
return undefined
|
|
})
|
|
|
|
if (!data?.skills || !Array.isArray(data.skills)) {
|
|
log.warn("invalid index format", { url: index })
|
|
return result
|
|
}
|
|
|
|
const list = data.skills.filter((skill) => {
|
|
if (!skill?.name || !Array.isArray(skill.files)) {
|
|
log.warn("invalid skill entry", { url: index, skill })
|
|
return false
|
|
}
|
|
return true
|
|
})
|
|
|
|
await Promise.all(
|
|
list.map(async (skill) => {
|
|
const root = path.join(cache, skill.name)
|
|
await Promise.all(
|
|
skill.files.map(async (file) => {
|
|
const link = new URL(file, `${host}/${skill.name}/`).href
|
|
const dest = path.join(root, file)
|
|
await mkdir(path.dirname(dest), { recursive: true })
|
|
await get(link, dest)
|
|
}),
|
|
)
|
|
|
|
const md = path.join(root, "SKILL.md")
|
|
if (await Bun.file(md).exists()) result.push(root)
|
|
}),
|
|
)
|
|
|
|
return result
|
|
}
|
|
}
|