mirror of
https://github.com/iflytek/skillhub.git
synced 2026-09-16 23:41:19 +00:00
- Interactive agent selection for install/uninstall/list - Namespace search selection for unspecified namespace - --skill-version option to skip interactive selection - --from option for local/github source installation - Remove deprecated add command (use install --from instead) - Path-based grouping for uninstall results
69 lines
2.2 KiB
TypeScript
69 lines
2.2 KiB
TypeScript
import { existsSync } from "node:fs";
|
|
import { resolve } from "node:path";
|
|
|
|
const DEFAULT_GITHUB_HOST = "github.com";
|
|
|
|
function getGitHubHost(): string {
|
|
return process.env.GITHUB_MIRROR || DEFAULT_GITHUB_HOST;
|
|
}
|
|
|
|
function isGitHubHost(hostname: string): boolean {
|
|
const ghHost = getGitHubHost();
|
|
return hostname === ghHost || hostname.endsWith(`.${ghHost}`);
|
|
}
|
|
|
|
export interface ParsedSource {
|
|
type: "local" | "github" | "gitlab" | "url";
|
|
owner?: string;
|
|
repo?: string;
|
|
ref?: string;
|
|
subpath?: string;
|
|
localPath?: string;
|
|
cloneUrl?: string;
|
|
skillFilter?: string;
|
|
}
|
|
|
|
export function parseSource(input: string): ParsedSource {
|
|
if (input.startsWith(".") || input.startsWith("/") || /^[a-zA-Z]:\\/.test(input)) {
|
|
const localPath = resolve(process.cwd(), input);
|
|
if (!existsSync(localPath)) {
|
|
throw new Error(`Local path not found: ${localPath}`);
|
|
}
|
|
return { type: "local", localPath };
|
|
}
|
|
|
|
if (input.startsWith("http://") || input.startsWith("https://")) {
|
|
const url = new URL(input);
|
|
if (isGitHubHost(url.hostname)) {
|
|
const [, owner, repo, , ref] = url.pathname.split("/");
|
|
return { type: "github", owner, repo: repo?.replace(/\.git$/, ""), ref, cloneUrl: input };
|
|
}
|
|
if (url.hostname.includes("gitlab.com")) {
|
|
const [, owner, repo] = url.pathname.split("/");
|
|
return { type: "gitlab", owner, repo, cloneUrl: input };
|
|
}
|
|
return { type: "url", cloneUrl: input };
|
|
}
|
|
|
|
const parts = input.split("/");
|
|
if (parts.length === 2) {
|
|
const atIndex = parts[1].indexOf("@");
|
|
if (atIndex > 0) {
|
|
const repo = parts[1].substring(0, atIndex);
|
|
const skillFilter = parts[1].substring(atIndex + 1);
|
|
return { type: "github", owner: parts[0], repo, skillFilter };
|
|
}
|
|
return { type: "github", owner: parts[0], repo: parts[1] };
|
|
}
|
|
|
|
throw new Error(`Invalid source format: ${input}. Use owner/repo, local path, URL, or registry namespace/slug.`);
|
|
}
|
|
|
|
export function getCloneUrl(source: ParsedSource): string {
|
|
if (source.cloneUrl) return source.cloneUrl;
|
|
if (source.type === "github" && source.owner && source.repo) {
|
|
const ghHost = getGitHubHost();
|
|
return `https://${ghHost}/${source.owner}/${source.repo}.git`;
|
|
}
|
|
throw new Error("Cannot determine clone URL");
|
|
}
|