mirror of
https://github.com/openclaw/clawhub.git
synced 2026-08-14 00:47:57 +00:00
122 lines
3.5 KiB
TypeScript
122 lines
3.5 KiB
TypeScript
export type GitHubHandoffBlock = { status: 403 | 409 | 410 | 423; message: string };
|
|
|
|
export type GitHubHandoffTarget = {
|
|
installKind: "github";
|
|
repo: string | null;
|
|
path: string | null;
|
|
commit: string | null;
|
|
contentHash: string | null;
|
|
currentStatus: "present" | "missing" | "unknown" | null;
|
|
scanStatus: "clean" | "suspicious" | "malicious" | "pending" | "failed" | null;
|
|
removedAt: number | null;
|
|
} | null;
|
|
|
|
export type ReadyGitHubHandoffTarget = {
|
|
installKind: "github";
|
|
repo: string;
|
|
path: string;
|
|
commit: string;
|
|
contentHash: string;
|
|
currentStatus: "present";
|
|
scanStatus: "clean" | "suspicious";
|
|
removedAt: number | null;
|
|
};
|
|
|
|
type GitHubHandoffScanStatus = NonNullable<GitHubHandoffTarget>["scanStatus"];
|
|
|
|
export function getGitHubHandoffBlock(target: GitHubHandoffTarget): GitHubHandoffBlock | null {
|
|
if (!target || target.installKind !== "github") {
|
|
return {
|
|
status: 409,
|
|
message: "GitHub-backed skill source metadata is incomplete.",
|
|
};
|
|
}
|
|
if (target.removedAt) {
|
|
return {
|
|
status: 410,
|
|
message: "GitHub-backed skill has been removed upstream.",
|
|
};
|
|
}
|
|
if (target.currentStatus === "missing") {
|
|
return {
|
|
status: 410,
|
|
message: "GitHub-backed skill path is missing upstream.",
|
|
};
|
|
}
|
|
if (target.scanStatus === "failed" || target.scanStatus === "malicious") {
|
|
return {
|
|
status: 403,
|
|
message: "GitHub-backed skill failed ClawHub security scanning.",
|
|
};
|
|
}
|
|
if (!target.repo || !target.path) {
|
|
return {
|
|
status: 409,
|
|
message: "GitHub-backed skill source metadata is incomplete.",
|
|
};
|
|
}
|
|
if (
|
|
target.currentStatus !== "present" ||
|
|
!target.commit ||
|
|
!target.contentHash ||
|
|
!isValidGitHubRepo(target.repo)
|
|
) {
|
|
return {
|
|
status: 423,
|
|
message: "GitHub-backed skill needs an upstream freshness check before download.",
|
|
};
|
|
}
|
|
if (!isSuccessfulGitHubHandoffScanStatus(target.scanStatus)) {
|
|
return {
|
|
status: 423,
|
|
message: "GitHub-backed skill security scan is in progress.",
|
|
};
|
|
}
|
|
return null;
|
|
}
|
|
|
|
export function isReadyGitHubHandoffTarget(
|
|
target: GitHubHandoffTarget,
|
|
): target is ReadyGitHubHandoffTarget {
|
|
return Boolean(
|
|
target &&
|
|
target.installKind === "github" &&
|
|
target.repo &&
|
|
target.path &&
|
|
target.commit &&
|
|
target.contentHash &&
|
|
target.currentStatus === "present" &&
|
|
isSuccessfulGitHubHandoffScanStatus(target.scanStatus) &&
|
|
isValidGitHubRepo(target.repo),
|
|
);
|
|
}
|
|
|
|
export function buildGitHubSkillHandoffDescriptor(target: ReadyGitHubHandoffTarget) {
|
|
return {
|
|
sourceRef: "public-github" as const,
|
|
repo: target.repo,
|
|
commit: target.commit,
|
|
path: target.path,
|
|
contentHash: target.contentHash,
|
|
archiveUrl: buildGitHubZipballUrl(target.repo, target.commit),
|
|
};
|
|
}
|
|
|
|
export function isSuccessfulGitHubHandoffScanStatus(scanStatus: GitHubHandoffScanStatus) {
|
|
// Suspicious is review-worthy but not a hard public download block; pending,
|
|
// failed, and malicious states still block handoff above.
|
|
return scanStatus === "clean" || scanStatus === "suspicious";
|
|
}
|
|
|
|
function buildGitHubZipballUrl(repo: string, commit: string) {
|
|
const [owner, name] = repo.split("/");
|
|
return `https://api.github.com/repos/${encodeURIComponent(owner)}/${encodeURIComponent(
|
|
name,
|
|
)}/zipball/${encodeURIComponent(commit)}`;
|
|
}
|
|
|
|
function isValidGitHubRepo(repo: string) {
|
|
const [owner, name, ...rest] = repo.split("/");
|
|
return Boolean(owner && name && rest.length === 0);
|
|
}
|