import { registerMarketplaceAdapter } from "../../registry.js";
import { sortMarketplaceCategories } from "../../marketplace-category-order.js";
import { basename } from "node:path";
//#region src/agent/skills/marketplace/adapters/skillhub/adapter.ts
/**
* SkillHub (skillhub.cn) skills marketplace adapter.
*/
const SKILLHUB_API_BASE = "https://api.skillhub.cn";
const MAX_SKILL_ZIP_BYTES = 15 * 1024 * 1024;
const MAX_SKILLHUB_README_BYTES = 512 * 1024;
const SKILL_ID_RE = /^[a-zA-Z0-9]([a-zA-Z0-9._-]{0,62})$/;
const REGISTRY_SKILL_BATCH_CHUNK = 80;
const DEFAULT_SKILLS_INDEX_URL = "https://skillhub-1388575217.cos.ap-guangzhou.myqcloud.com/skills.json";
const DEFAULT_SEARCH_URL = "https://lightmake.site/api/v1/search";
const DEFAULT_PRIMARY_DOWNLOAD_TEMPLATE = "https://lightmake.site/api/v1/download?slug={slug}";
const DEFAULT_FALLBACK_DOWNLOAD_TEMPLATE = "https://skillhub-1388575217.cos.ap-guangzhou.myqcloud.com/skills/{slug}.zip";
const DEFAULT_ALLOWED_DOWNLOAD_HOSTS = new Set([
	"lightmake.site",
	"api.skillhub.cn",
	"skillhub-1388575217.cos.ap-guangzhou.myqcloud.com"
]);
const LIGHTMAKE_SEARCH_MAX = 100;
const SKILLSET_DISCOVERY_PAGE_CAP = 25;
const MAX_DEFAULT_SLUGS = 200;
const DEFAULT_CACHE_MS = 300 * 1e3;
const MAX_BATCH_CACHE_KEYS = 48;
function isValidSkillId(id) {
	return SKILL_ID_RE.test(id);
}
async function registryFetchJson(url, init) {
	const res = await fetch(url, {
		...init,
		headers: {
			Accept: "application/json",
			...init?.headers
		}
	});
	const text = await res.text();
	if (!res.ok) {
		let msg = `SkillHub request failed (${res.status})`;
		try {
			const j = JSON.parse(text);
			if (j.message) msg = j.message;
			else if (j.error) msg = j.error;
		} catch {
			if (text) msg = text.slice(0, 200);
		}
		throw new Error(msg);
	}
	try {
		return JSON.parse(text);
	} catch {
		throw new Error("SkillHub returned invalid JSON");
	}
}
function basenameSkillPath(p) {
	const norm = p.replace(/\\/g, "/");
	const parts = norm.split("/");
	return parts[parts.length - 1] || norm;
}
function pickSkillHubDocFilePath(files) {
	const rows = files.map((f) => ({
		path: f.path.replace(/\\/g, "/"),
		base: basenameSkillPath(f.path).toLowerCase()
	}));
	const firstBase = (name) => rows.find((r) => r.base === name.toLowerCase());
	const skillMd = firstBase("SKILL.md") ?? firstBase("skill.md");
	if (skillMd) return skillMd.path;
	const readme = firstBase("README.md") ?? firstBase("readme.md");
	if (readme) return readme.path;
	const how = firstBase("HOW_TO_USE.md");
	if (how) return how.path;
	return null;
}
function assertSkillHubReadmeResponseUrl(finalUrl) {
	let u;
	try {
		u = new URL(finalUrl);
	} catch {
		throw new Error("Invalid SkillHub file response URL");
	}
	if (u.protocol !== "https:") throw new Error("SkillHub file response must use HTTPS");
	const host = u.hostname.toLowerCase();
	if (host === "api.skillhub.cn") return;
	if (host.endsWith(".myqcloud.com")) return;
	throw new Error("SkillHub file redirect host is not allowlisted");
}
async function getSkillHubSkillFileText(slug, filePath, version) {
	const enc = encodeURIComponent(slug.trim());
	const normPath = filePath.replace(/\\/g, "/");
	const sp = new URLSearchParams({ path: normPath });
	if (version?.trim()) sp.set("version", version.trim());
	const url = `${SKILLHUB_API_BASE}/api/v1/skills/${enc}/file?${sp.toString()}`;
	const res = await fetch(url, {
		redirect: "follow",
		headers: { Accept: "text/markdown,text/plain,*/*" }
	});
	if (!res.ok) {
		const text = await res.text().catch(() => "");
		let msg = `SkillHub file request failed (${res.status})`;
		try {
			const j = JSON.parse(text);
			if (typeof j.message === "string") msg = j.message;
			else if (typeof j.error === "string") msg = j.error;
		} catch {
			if (text) msg = text.slice(0, 200);
		}
		throw new Error(msg);
	}
	assertSkillHubReadmeResponseUrl(res.url);
	const len = res.headers.get("content-length");
	if (len) {
		const n = Number(len);
		if (Number.isFinite(n) && n > MAX_SKILLHUB_README_BYTES) throw new Error(`SkillHub file exceeds max size`);
	}
	const ab = await res.arrayBuffer();
	if (ab.byteLength > MAX_SKILLHUB_README_BYTES) throw new Error(`SkillHub file exceeds max size`);
	return new TextDecoder("utf-8").decode(ab);
}
async function getSkillHubSkill(slug) {
	return registryFetchJson(`${SKILLHUB_API_BASE}/api/v1/skills/${encodeURIComponent(slug.trim())}`);
}
async function getSkillHubSkillFiles(slug, version) {
	return registryFetchJson(`${SKILLHUB_API_BASE}/api/v1/skills/${encodeURIComponent(slug.trim())}/files${version ? `?version=${encodeURIComponent(version)}` : ""}`);
}
async function downloadSkillHubZipBuffer(slug, version) {
	let url = `${SKILLHUB_API_BASE}/api/v1/download?slug=${encodeURIComponent(slug.trim())}`;
	if (version?.trim()) url += `&version=${encodeURIComponent(version.trim())}`;
	const res = await fetch(url, { redirect: "follow" });
	if (!res.ok) throw new Error(`Failed to download skill archive (${res.status})`);
	const len = res.headers.get("content-length");
	if (len) {
		const n = Number(len);
		if (Number.isFinite(n) && n > MAX_SKILL_ZIP_BYTES) throw new Error(`Zip exceeds max size`);
	}
	const ab = await res.arrayBuffer();
	const buf = Buffer.from(ab);
	if (buf.length > MAX_SKILL_ZIP_BYTES) throw new Error(`Zip exceeds max size`);
	let resolvedVersion = version ?? "1.0.0";
	if (!version?.trim()) try {
		resolvedVersion = (await getSkillHubSkillFiles(slug)).version;
	} catch {}
	return {
		buffer: buf,
		version: resolvedVersion
	};
}
async function batchGetSkillHubSkills(slugs) {
	if (slugs.length === 0) return [];
	return (await registryFetchJson(`${SKILLHUB_API_BASE}/api/v1/skills/batch`, {
		method: "POST",
		headers: { "Content-Type": "application/json" },
		body: JSON.stringify({ slugs })
	})).items ?? [];
}
async function listSkillHubRegistryCategories() {
	const raw = await registryFetchJson(`${SKILLHUB_API_BASE}/api/v1/categories`);
	return (Array.isArray(raw.items) ? raw.items : []).filter((c) => c && typeof c.key === "string" && c.key.trim().length > 0 && c.active !== false);
}
async function listSkillHubSkillsets(opts = {}) {
	const page = opts.page ?? 1;
	const pageSize = opts.pageSize ?? 20;
	const sp = new URLSearchParams({
		page: String(page),
		pageSize: String(pageSize)
	});
	if (opts.keyword?.trim()) sp.set("keyword", opts.keyword.trim());
	if (opts.scene?.trim()) sp.set("scene", opts.scene.trim());
	return registryFetchJson(`${SKILLHUB_API_BASE}/api/v1/skillsets?${sp.toString()}`);
}
async function getDefaultSkillSlugs() {
	const slugs = /* @__PURE__ */ new Set();
	let page = 1;
	const pageSize = 50;
	let total = Infinity;
	while (page <= SKILLSET_DISCOVERY_PAGE_CAP && slugs.size < MAX_DEFAULT_SLUGS) {
		const response = await listSkillHubSkillsets({
			page,
			pageSize
		});
		total = response.total;
		for (const skillset of response.skillSets) for (const slug of skillset.skillSlugs) slugs.add(slug);
		if (page * pageSize >= total) break;
		page += 1;
	}
	return Array.from(slugs).slice(0, MAX_DEFAULT_SLUGS);
}
async function searchSkillHubSkills(query, maxSlugs = 200) {
	const keyword = query.trim();
	if (!keyword) return {
		slugs: [],
		total: 0
	};
	const slugs = [];
	const seen = /* @__PURE__ */ new Set();
	let page = 1;
	const batchSize = 50;
	while (page <= SKILLSET_DISCOVERY_PAGE_CAP) {
		const response = await listSkillHubSkillsets({
			page,
			pageSize: batchSize,
			keyword
		});
		for (const skillset of response.skillSets) for (const slug of skillset.skillSlugs) if (!seen.has(slug)) {
			seen.add(slug);
			slugs.push(slug);
		}
		if (page * batchSize >= response.total) break;
		page += 1;
	}
	const capped = slugs.slice(0, maxSlugs);
	return {
		slugs: capped,
		total: capped.length
	};
}
function templateWithSlug(template, slug) {
	const raw = template.trim();
	if (!raw) return "";
	if (raw.includes("{slug}")) return raw.replaceAll("{slug}", encodeURIComponent(slug.trim()));
	return `${raw.replace(/\/$/, "")}/${encodeURIComponent(slug.trim())}.zip`;
}
function hostFromTemplate(template) {
	const t = templateWithSlug(template, "x");
	try {
		return new URL(t).hostname.toLowerCase();
	} catch {
		return;
	}
}
function resolveSkillHubEcosystemUrls() {
	return {
		skillsIndexUrl: process.env.XOPC_SKILLHUB_SKILLS_INDEX_URL?.trim() || DEFAULT_SKILLS_INDEX_URL,
		searchUrl: process.env.XOPC_SKILLHUB_SEARCH_URL?.trim() || DEFAULT_SEARCH_URL,
		primaryDownloadTemplate: process.env.XOPC_SKILLHUB_PRIMARY_DOWNLOAD_TEMPLATE?.trim() || DEFAULT_PRIMARY_DOWNLOAD_TEMPLATE,
		fallbackDownloadTemplate: process.env.XOPC_SKILLHUB_FALLBACK_DOWNLOAD_TEMPLATE?.trim() || DEFAULT_FALLBACK_DOWNLOAD_TEMPLATE
	};
}
function allowedDownloadHosts(urls) {
	const hosts = new Set(DEFAULT_ALLOWED_DOWNLOAD_HOSTS);
	for (const h of [hostFromTemplate(urls.primaryDownloadTemplate), hostFromTemplate(urls.fallbackDownloadTemplate)]) if (h) hosts.add(h);
	try {
		hosts.add(new URL(urls.skillsIndexUrl).hostname.toLowerCase());
	} catch {}
	try {
		hosts.add(new URL(urls.searchUrl).hostname.toLowerCase());
	} catch {}
	return hosts;
}
function assertSkillHubDownloadUrlAllowed(downloadUrl, urls) {
	let u;
	try {
		u = new URL(downloadUrl);
	} catch {
		throw new Error("Invalid SkillHub download URL");
	}
	if (u.protocol !== "https:") throw new Error("SkillHub download URL must use HTTPS");
	if (!allowedDownloadHosts(urls).has(u.hostname.toLowerCase())) throw new Error("SkillHub download URL host is not allowlisted");
	return u;
}
async function ecoFetchJson(url, init) {
	const res = await fetch(url, {
		...init,
		headers: {
			Accept: "application/json",
			...init?.headers
		}
	});
	const text = await res.text();
	if (!res.ok) {
		let msg = `SkillHub ecosystem request failed (${res.status})`;
		try {
			const j = JSON.parse(text);
			if (j.message) msg = j.message;
			else if (j.error) msg = j.error;
		} catch {
			if (text) msg = text.slice(0, 200);
		}
		throw new Error(msg);
	}
	try {
		return JSON.parse(text);
	} catch {
		throw new Error("SkillHub ecosystem returned invalid JSON");
	}
}
async function fetchSkillHubCuratedIndex(urls) {
	const raw = await ecoFetchJson(urls.skillsIndexUrl);
	if (Array.isArray(raw)) return { skills: raw };
	if (raw && typeof raw === "object" && Array.isArray(raw.skills)) return raw;
	throw new Error("SkillHub index JSON must be an object with a skills array");
}
function sourceLabelFromHomepage(homepage) {
	const h = homepage?.trim().toLowerCase() ?? "";
	if (h.includes("clawhub")) return "ClawHub";
	if (h.includes("skillhub")) return "SkillHub";
}
function curatedSkillsToPackageItems(skills) {
	return skills.map((s) => ({
		id: s.slug,
		name: (s.name ?? s.slug).trim() || s.slug,
		type: "skill",
		description: (s.description ?? "").trim(),
		downloads: typeof s.downloads === "number" ? s.downloads : 0,
		author: {
			username: "skillhub",
			avatarUrl: null
		},
		latestVersion: (s.version ?? "").trim() || void 0,
		updatedAt: String(s.rank ?? s.score ?? 0),
		categories: (s.categories ?? []).map((c) => String(c).trim()).filter(Boolean),
		stars: typeof s.stars === "number" ? s.stars : void 0,
		sourceLabel: sourceLabelFromHomepage(s.homepage)
	}));
}
async function findCuratedIndexSkill(slug) {
	const want = slug.trim();
	if (!want) return null;
	const ecoUrls = resolveSkillHubEcosystemUrls();
	try {
		return (await cachedFetchSkillHubCuratedIndex(ecoUrls)).skills.find((s) => s.slug?.trim() === want) ?? null;
	} catch {
		return null;
	}
}
function curatedSkillFallbackReadmeMarkdown(skill) {
	const title = (skill.name ?? skill.slug).trim() || skill.slug;
	const desc = (skill.description ?? "").trim() || "_No description._";
	const version = (skill.version ?? "").trim() || "1.0.0";
	return `## ${title}\n\n**${skill.slug}** · v${version}\n\n${desc}`;
}
function packageDetailFromCuratedSkill(skill) {
	const slug = skill.slug.trim();
	const version = (skill.version ?? "").trim() || "1.0.0";
	const categories = (skill.categories ?? []).map((c) => String(c).trim()).filter(Boolean);
	const sourceLabel = sourceLabelFromHomepage(skill.homepage);
	return {
		id: slug,
		name: (skill.name ?? slug).trim() || slug,
		type: "skill",
		description: (skill.description ?? "").trim(),
		readme: curatedSkillFallbackReadmeMarkdown(skill),
		downloads: typeof skill.downloads === "number" ? skill.downloads : 0,
		author: {
			username: sourceLabel?.toLowerCase() ?? "skillhub",
			avatarUrl: null
		},
		latestVersion: {
			version,
			changelog: null,
			publishedAt: String(skill.rank ?? skill.score ?? 0)
		},
		provider: "skillhub",
		skillHubInfo: {
			category: categories[0] ?? "",
			installs: 0,
			stars: typeof skill.stars === "number" ? skill.stars : 0
		}
	};
}
function lightmakeHitToPackageItem(hit) {
	const slug = String(hit.slug || "").trim();
	const desc = (hit.summary ?? hit.description_zh ?? hit.description ?? "").trim() || "";
	const updated = hit.updatedAt ?? hit.updated_at ?? 0;
	const cat = hit.category?.trim();
	return {
		id: slug,
		name: (hit.displayName ?? hit.name ?? slug).trim() || slug,
		type: "skill",
		description: desc,
		downloads: typeof hit.downloads === "number" ? hit.downloads : 0,
		author: {
			username: (hit.owner_name ?? "skillhub").trim() || "skillhub",
			avatarUrl: null
		},
		latestVersion: (hit.version ?? "").trim() || void 0,
		updatedAt: String(updated),
		categories: cat ? [cat] : [],
		stars: typeof hit.stars === "number" ? hit.stars : void 0,
		sourceLabel: sourceLabelFromSkillSource(hit.source) ?? "Lightmake"
	};
}
async function searchSkillHubLightmake(urls, query, limit, timeoutMs = 4e3) {
	const q = query.trim().toLowerCase();
	if (!q) return [];
	const search = new URL(urls.searchUrl);
	if (search.protocol !== "https:") throw new Error("SkillHub search URL must use HTTPS");
	search.searchParams.set("q", q);
	search.searchParams.set("limit", String(Math.max(1, Math.min(LIGHTMAKE_SEARCH_MAX, limit))));
	const controller = new AbortController();
	const timer = setTimeout(() => controller.abort(), timeoutMs);
	try {
		const results = (await ecoFetchJson(search.toString(), { signal: controller.signal })).results;
		if (!Array.isArray(results)) return [];
		const out = [];
		for (const item of results) {
			if (!item || typeof item !== "object") continue;
			const hit = item;
			if (!String(hit.slug ?? "").trim()) continue;
			out.push(lightmakeHitToPackageItem(hit));
		}
		return out;
	} finally {
		clearTimeout(timer);
	}
}
async function downloadSkillHubZipFromAllowlistedUrl(urls, downloadUrl) {
	const normalized = assertSkillHubDownloadUrlAllowed(downloadUrl, urls);
	const res = await fetch(normalized.toString(), { redirect: "follow" });
	if (!res.ok) throw new Error(`Failed to download skill archive (${res.status})`);
	const len = res.headers.get("content-length");
	if (len) {
		const n = Number(len);
		if (Number.isFinite(n) && n > MAX_SKILL_ZIP_BYTES) throw new Error(`Zip exceeds max size`);
	}
	const ab = await res.arrayBuffer();
	const buf = Buffer.from(ab);
	if (buf.length > MAX_SKILL_ZIP_BYTES) throw new Error(`Zip exceeds max size`);
	return buf;
}
async function downloadSkillHubZipFromEcosystem(urls, slug) {
	const candidates = [templateWithSlug(urls.primaryDownloadTemplate, slug), templateWithSlug(urls.fallbackDownloadTemplate, slug)].filter(Boolean);
	const seen = /* @__PURE__ */ new Set();
	let lastErr;
	for (const url of candidates) {
		if (seen.has(url)) continue;
		seen.add(url);
		try {
			return await downloadSkillHubZipFromAllowlistedUrl(urls, url);
		} catch (e) {
			lastErr = e instanceof Error ? e : new Error(String(e));
		}
	}
	throw lastErr ?? /* @__PURE__ */ new Error("SkillHub ecosystem download failed");
}
function cacheTtlMs() {
	const raw = process.env.XOPC_SKILLHUB_CACHE_MS?.trim();
	if (raw === "0" || raw === "false") return 0;
	const n = Number(raw);
	if (!Number.isFinite(n) || n < 0) return DEFAULT_CACHE_MS;
	return n;
}
function getFresh(entry) {
	if (!entry || entry.expiresAt <= Date.now()) return void 0;
	return entry.value;
}
const curatedByIndexUrl = /* @__PURE__ */ new Map();
let defaultSlugsEntry;
let registryCategoriesEntry;
const batchBySlugsKey = /* @__PURE__ */ new Map();
const lightmakeSearchByQuery = /* @__PURE__ */ new Map();
function evictOldestBatchKey() {
	const first = batchBySlugsKey.keys().next().value;
	if (first !== void 0) batchBySlugsKey.delete(first);
}
function evictOldestSearchKey() {
	const first = lightmakeSearchByQuery.keys().next().value;
	if (first !== void 0) lightmakeSearchByQuery.delete(first);
}
function batchSlugsCacheKey(slugs) {
	if (slugs.length === 0) return "";
	return [...slugs].sort().join("\n");
}
async function cachedFetchSkillHubCuratedIndex(urls) {
	const ttl = cacheTtlMs();
	const key = urls.skillsIndexUrl;
	if (ttl > 0) {
		const hit = getFresh(curatedByIndexUrl.get(key));
		if (hit) return hit;
	}
	const value = await fetchSkillHubCuratedIndex(urls);
	if (ttl > 0) curatedByIndexUrl.set(key, {
		value,
		expiresAt: Date.now() + ttl
	});
	return value;
}
async function cachedGetDefaultSkillSlugs() {
	const ttl = cacheTtlMs();
	if (ttl > 0) {
		const hit = getFresh(defaultSlugsEntry);
		if (hit) return hit;
	}
	const value = await getDefaultSkillSlugs();
	if (ttl > 0) defaultSlugsEntry = {
		value,
		expiresAt: Date.now() + ttl
	};
	return value;
}
async function cachedListSkillHubRegistryCategories() {
	const ttl = cacheTtlMs();
	if (ttl > 0) {
		const hit = getFresh(registryCategoriesEntry);
		if (hit) return hit;
	}
	const value = await listSkillHubRegistryCategories();
	if (ttl > 0) registryCategoriesEntry = {
		value,
		expiresAt: Date.now() + ttl
	};
	return value;
}
async function cachedBatchGetSkillHubSkills(slugs) {
	if (slugs.length === 0) return [];
	const ttl = cacheTtlMs();
	const key = batchSlugsCacheKey(slugs);
	if (ttl > 0) {
		const hit = getFresh(batchBySlugsKey.get(key));
		if (hit) return hit;
	}
	const value = await batchGetSkillHubSkills(slugs);
	if (ttl > 0) {
		while (batchBySlugsKey.size >= MAX_BATCH_CACHE_KEYS) evictOldestBatchKey();
		batchBySlugsKey.set(key, {
			value,
			expiresAt: Date.now() + ttl
		});
	}
	return value;
}
async function cachedSearchSkillHubLightmake(urls, query, limit) {
	const q = query.trim().toLowerCase();
	if (!q) return [];
	const ttl = cacheTtlMs();
	const key = `${urls.searchUrl}|${q}|${limit}`;
	if (ttl > 0) {
		const hit = getFresh(lightmakeSearchByQuery.get(key));
		if (hit) return hit;
	}
	const value = await searchSkillHubLightmake(urls, q, limit);
	if (ttl > 0) {
		while (lightmakeSearchByQuery.size >= MAX_BATCH_CACHE_KEYS) evictOldestSearchKey();
		lightmakeSearchByQuery.set(key, {
			value,
			expiresAt: Date.now() + ttl
		});
	}
	return value;
}
function humanizeRegistryCategoryKey(slug) {
	return slug.replace(/_/g, "-").split("-").filter(Boolean).map((w) => w.charAt(0).toUpperCase() + w.slice(1).toLowerCase()).join(" ");
}
function sourceLabelFromSkillSource(source) {
	const s = source?.trim();
	if (!s) return void 0;
	const lower = s.toLowerCase();
	if (lower === "clawhub") return "ClawHub";
	if (lower === "lightmake") return "Lightmake";
	if (lower === "skillhub") return "SkillHub";
	return s.charAt(0).toUpperCase() + s.slice(1);
}
function filterByCategory(rows, category) {
	const want = category?.trim();
	if (!want) return rows;
	return rows.filter((r) => (r.categories ?? []).includes(want));
}
async function collectRegistryCategoryKeysFromSlugs(slugs) {
	const used = /* @__PURE__ */ new Set();
	for (let i = 0; i < slugs.length; i += REGISTRY_SKILL_BATCH_CHUNK) {
		const details = await cachedBatchGetSkillHubSkills(slugs.slice(i, i + REGISTRY_SKILL_BATCH_CHUNK));
		for (const d of details) {
			const k = d.skill.category?.trim();
			if (k) used.add(k);
		}
	}
	return used;
}
function isPipelineOnlyChangelog(text) {
	if (!text?.trim()) return true;
	return /^synced by skillhub pipeline\.?$/i.test(text.trim());
}
function skillHubFallbackReadmeMarkdown(detail) {
	const s = detail.skill;
	const title = s.displayName?.trim() || s.slug;
	const zh = s.summary_zh?.trim();
	const en = s.summary?.trim();
	const body = zh && en && zh !== en ? `${zh}\n\n${en}` : zh || en || "_No description._";
	return `## ${title}\n\n**${s.slug}** · v${detail.latestVersion.version}\n\n${body}`;
}
function convertSkillHubToPackageListItem(detail) {
	const cat = detail.category?.trim();
	return {
		id: detail.slug,
		name: detail.displayName?.trim() || detail.slug,
		type: "skill",
		description: detail.summary_zh || detail.summary,
		downloads: detail.stats.downloads,
		author: {
			username: detail.source || "skillhub",
			avatarUrl: null
		},
		latestVersion: detail.tags.latest || "1.0.0",
		updatedAt: String(detail.updatedAt),
		categories: cat ? [cat] : [],
		stars: detail.stats.stars,
		sourceLabel: sourceLabelFromSkillSource(detail.source)
	};
}
const skillHubMarketplaceAdapter = {
	id: "skillhub",
	async listCategories(_config) {
		const sortByLabel = (a, b) => a.label.localeCompare(b.label, "zh-Hans-CN", { sensitivity: "base" });
		const ecoUrls = resolveSkillHubEcosystemUrls();
		try {
			const idx = await cachedFetchSkillHubCuratedIndex(ecoUrls);
			if (idx.skills?.length) {
				const map = /* @__PURE__ */ new Map();
				for (const s of idx.skills) for (const raw of s.categories ?? []) {
					const label = String(raw).trim();
					if (label) map.set(label, {
						id: label,
						label
					});
				}
				return sortMarketplaceCategories(Array.from(map.values()).filter((c) => c.id.trim() && c.label.trim()), sortByLabel);
			}
		} catch {}
		try {
			const [taxonomy, slugs] = await Promise.all([cachedListSkillHubRegistryCategories(), cachedGetDefaultSkillSlugs()]);
			const usedKeys = await collectRegistryCategoryKeysFromSlugs(slugs);
			const taxByKey = new Map(taxonomy.map((t) => [t.key, t]));
			const options = [];
			for (const key of usedKeys) {
				const t = taxByKey.get(key);
				const label = t?.name?.trim() || t?.nameEn?.trim() || humanizeRegistryCategoryKey(key).trim();
				if (!label) continue;
				options.push({
					id: key,
					label
				});
			}
			return sortMarketplaceCategories(options, (a, b) => {
				const oa = taxByKey.get(a.id)?.sortOrder ?? 999;
				const ob = taxByKey.get(b.id)?.sortOrder ?? 999;
				if (oa !== ob) return oa - ob;
				return sortByLabel(a, b);
			});
		} catch {
			return [];
		}
	},
	async listPackages(_config, params) {
		const pageSize = params.pageSize ?? 20;
		const page = params.page ?? 1;
		const ecoUrls = resolveSkillHubEcosystemUrls();
		if (params.q?.trim()) {
			const q = params.q.trim();
			let rows = [];
			try {
				rows = await cachedSearchSkillHubLightmake(ecoUrls, q, 100);
			} catch {
				try {
					rows = (await cachedBatchGetSkillHubSkills((await searchSkillHubSkills(q, 200)).slugs)).map((d) => convertSkillHubToPackageListItem(d.skill));
				} catch {
					rows = [];
				}
			}
			rows = filterByCategory(rows, params.category);
			if (params.sort === "downloads") rows = [...rows].sort((a, b) => b.downloads - a.downloads);
			else if (params.sort === "newest") rows = [...rows].sort((a, b) => Number(b.updatedAt) - Number(a.updatedAt));
			const total = rows.length;
			const start = (page - 1) * pageSize;
			return {
				items: rows.slice(start, start + pageSize),
				meta: {
					page,
					pageSize,
					total,
					totalPages: Math.max(1, Math.ceil(total / pageSize))
				},
				provider: "skillhub"
			};
		}
		try {
			let skills = [...(await cachedFetchSkillHubCuratedIndex(ecoUrls)).skills].filter((s) => s.slug?.trim());
			if (params.category?.trim()) {
				const want = params.category.trim();
				skills = skills.filter((s) => (s.categories ?? []).some((x) => String(x).trim() === want));
			}
			if (params.sort === "downloads") skills.sort((a, b) => (b.downloads ?? 0) - (a.downloads ?? 0));
			else if (params.sort === "newest") skills.sort((a, b) => (a.rank ?? 999) - (b.rank ?? 999));
			const rows = curatedSkillsToPackageItems(skills);
			const total = rows.length;
			const start = (page - 1) * pageSize;
			return {
				items: rows.slice(start, start + pageSize),
				meta: {
					page,
					pageSize,
					total,
					totalPages: Math.max(1, Math.ceil(total / pageSize))
				},
				provider: "skillhub"
			};
		} catch {}
		const slugs = await cachedGetDefaultSkillSlugs();
		if (params.category?.trim()) {
			let allItems = (await cachedBatchGetSkillHubSkills(slugs)).map((d) => convertSkillHubToPackageListItem(d.skill));
			allItems = filterByCategory(allItems, params.category);
			if (params.sort === "downloads") allItems = [...allItems].sort((a, b) => b.downloads - a.downloads);
			else if (params.sort === "newest") allItems = [...allItems].sort((a, b) => Number(b.updatedAt) - Number(a.updatedAt));
			const total = allItems.length;
			const start = (page - 1) * pageSize;
			return {
				items: allItems.slice(start, start + pageSize),
				meta: {
					page,
					pageSize,
					total,
					totalPages: Math.max(1, Math.ceil(total / pageSize))
				},
				provider: "skillhub"
			};
		}
		const total = slugs.length;
		const start = (page - 1) * pageSize;
		const paginatedSlugs = slugs.slice(start, start + pageSize);
		const totalPages = Math.max(1, Math.ceil(total / pageSize));
		return {
			items: (await cachedBatchGetSkillHubSkills(paginatedSlugs)).map((d) => convertSkillHubToPackageListItem(d.skill)),
			meta: {
				page,
				pageSize,
				total,
				totalPages
			},
			provider: "skillhub"
		};
	},
	async getPackageDetail(_config, packageName) {
		const slug = packageName.trim();
		let detail;
		try {
			detail = await getSkillHubSkill(slug);
		} catch (registryErr) {
			const curated = await findCuratedIndexSkill(slug);
			if (curated) return packageDetailFromCuratedSkill(curated);
			throw registryErr;
		}
		const version = detail.latestVersion.version;
		const changelog = detail.latestVersion.changelog;
		let readme = null;
		let docPath = null;
		try {
			const { files } = await getSkillHubSkillFiles(slug, version);
			docPath = pickSkillHubDocFilePath(files);
			if (docPath) readme = await getSkillHubSkillFileText(slug, docPath, version);
		} catch {
			readme = null;
		}
		const trimmed = readme?.trim() ?? "";
		const isSkillMd = (docPath ? basename(docPath.replace(/\\/g, "/")).toLowerCase() : "") === "skill.md";
		if (!trimmed) readme = skillHubFallbackReadmeMarkdown(detail);
		else if (isSkillMd) {
			readme = trimmed;
			if (changelog?.trim() && !isPipelineOnlyChangelog(changelog)) readme = `${trimmed}\n\n---\n\n## Changelog\n\n${changelog.trim()}`;
		} else {
			readme = trimmed;
			if (changelog?.trim() && !isPipelineOnlyChangelog(changelog)) readme = `${trimmed}\n\n---\n\n## Changelog\n\n${changelog.trim()}`;
		}
		return {
			id: detail.skill.slug,
			name: detail.skill.slug,
			type: "skill",
			description: detail.skill.summary_zh || detail.skill.summary,
			readme,
			downloads: detail.skill.stats.downloads,
			author: {
				username: detail.owner.handle,
				avatarUrl: detail.owner.image
			},
			latestVersion: {
				version: detail.latestVersion.version,
				changelog: detail.latestVersion.changelog,
				publishedAt: String(detail.latestVersion.createdAt)
			},
			provider: "skillhub",
			skillHubInfo: {
				category: detail.skill.category,
				installs: detail.skill.stats.installs,
				stars: detail.skill.stats.stars,
				securityReports: detail.latestVersion.securityReports
			}
		};
	},
	async downloadPackage(_config, packageName, version) {
		const slug = packageName.trim();
		if (version?.trim()) {
			const { buffer, version: resolvedVersion } = await downloadSkillHubZipBuffer(slug, version);
			return {
				buffer,
				skillId: isValidSkillId(slug) ? slug : "unknown",
				version: resolvedVersion
			};
		}
		const ecoUrls = resolveSkillHubEcosystemUrls();
		try {
			const buffer = await downloadSkillHubZipFromEcosystem(ecoUrls, slug);
			let resolvedVersion = "1.0.0";
			try {
				resolvedVersion = (await getSkillHubSkillFiles(slug)).version;
			} catch {}
			return {
				buffer,
				skillId: isValidSkillId(slug) ? slug : "unknown",
				version: resolvedVersion
			};
		} catch {
			const { buffer, version: resolvedVersion } = await downloadSkillHubZipBuffer(slug);
			return {
				buffer,
				skillId: isValidSkillId(slug) ? slug : "unknown",
				version: resolvedVersion
			};
		}
	}
};
registerMarketplaceAdapter({
	adapter: skillHubMarketplaceAdapter,
	displayName: "SkillHub"
});
//#endregion
export { skillHubMarketplaceAdapter };

//# sourceMappingURL=adapter.js.map