| 80 | |
| 81 | // 解析 Skill ZIP 包:提取 SKILL.md、scripts/*.js、references/* |
| 82 | export async function parseSkillZip(data: ArrayBuffer): Promise<{ |
| 83 | skillMd: string; |
| 84 | scripts: Array<{ name: string; code: string }>; |
| 85 | references: Array<{ name: string; content: string }>; |
| 86 | }> { |
| 87 | const zip = await loadAsyncJSZip(data); |
| 88 | const files: Record<string, JSZipObject> = {}; |
| 89 | zip.forEach((relativePath, file) => { |
| 90 | files[relativePath] = file; |
| 91 | }); |
| 92 | |
| 93 | // 查找 SKILL.cat.md 或 SKILL.md:根目录或第一层子目录 |
| 94 | let skillMdPath = ""; |
| 95 | let prefix = ""; |
| 96 | const skillFileNames = ["SKILL.cat.md", "SKILL.md"]; |
| 97 | for (const path of Object.keys(files)) { |
| 98 | const normalized = path.replace(/\\/g, "/"); |
| 99 | const fileName = normalized.split("/").pop() || ""; |
| 100 | if (!skillFileNames.includes(fileName)) continue; |
| 101 | const parts = normalized.split("/"); |
| 102 | if (parts.length === 1) { |
| 103 | // 根目录(优先 SKILL.cat.md) |
| 104 | if (!skillMdPath || fileName === "SKILL.cat.md") { |
| 105 | skillMdPath = path; |
| 106 | prefix = ""; |
| 107 | } |
| 108 | } else if (parts.length === 2 && !skillMdPath) { |
| 109 | // 一层子目录 |
| 110 | skillMdPath = path; |
| 111 | prefix = parts[0] + "/"; |
| 112 | } |
| 113 | } |
| 114 | |
| 115 | if (!skillMdPath) { |
| 116 | throw new Error("ZIP 包中未找到 SKILL.cat.md 或 SKILL.md"); |
| 117 | } |
| 118 | |
| 119 | const skillMd: string = await files[skillMdPath].async("string"); |
| 120 | |
| 121 | // 提取 scripts/*.js |
| 122 | const scripts: Array<{ name: string; code: string }> = []; |
| 123 | const scriptsDir = prefix + "scripts/"; |
| 124 | for (const [path, file] of Object.entries(files)) { |
| 125 | if (file.dir) continue; |
| 126 | const normalized = path.replace(/\\/g, "/"); |
| 127 | if (normalized.startsWith(scriptsDir) && normalized.endsWith(".js")) { |
| 128 | const name = normalized.slice(scriptsDir.length); |
| 129 | if (name && !name.includes("/")) { |
| 130 | const code = await file.async("string"); |
| 131 | scripts.push({ name, code }); |
| 132 | } |
| 133 | } |
| 134 | } |
| 135 | |
| 136 | // 提取 references/* |
| 137 | const references: Array<{ name: string; content: string }> = []; |
| 138 | const refsDir = prefix + "references/"; |
| 139 | for (const [path, file] of Object.entries(files)) { |