(res: http.ServerResponse, urlPath: string)
| 1181 | } |
| 1182 | |
| 1183 | private serveSkillDownload(res: http.ServerResponse, urlPath: string): void { |
| 1184 | const skillId = urlPath.replace("/api/skill/", "").replace("/download", ""); |
| 1185 | const skill = this.store.getSkill(skillId); |
| 1186 | if (!skill) { |
| 1187 | res.writeHead(404, { "Content-Type": "application/json" }); |
| 1188 | res.end(JSON.stringify({ error: "Skill not found" })); |
| 1189 | return; |
| 1190 | } |
| 1191 | |
| 1192 | if (!fs.existsSync(skill.dirPath)) { |
| 1193 | res.writeHead(404, { "Content-Type": "application/json" }); |
| 1194 | res.end(JSON.stringify({ error: "Skill directory not found" })); |
| 1195 | return; |
| 1196 | } |
| 1197 | |
| 1198 | const zipName = `${skill.name}-v${skill.version}.zip`; |
| 1199 | const tmpPath = path.join(require("os").tmpdir(), zipName); |
| 1200 | |
| 1201 | try { |
| 1202 | try { fs.unlinkSync(tmpPath); } catch { /* no-op */ } |
| 1203 | execSync( |
| 1204 | `cd "${path.dirname(skill.dirPath)}" && zip -r "${tmpPath}" "${path.basename(skill.dirPath)}"`, |
| 1205 | { timeout: 15_000 }, |
| 1206 | ); |
| 1207 | |
| 1208 | const data = fs.readFileSync(tmpPath); |
| 1209 | res.writeHead(200, { |
| 1210 | "Content-Type": "application/zip", |
| 1211 | "Content-Disposition": `attachment; filename="${zipName}"`, |
| 1212 | "Content-Length": String(data.length), |
| 1213 | }); |
| 1214 | res.end(data); |
| 1215 | |
| 1216 | try { fs.unlinkSync(tmpPath); } catch { /* cleanup */ } |
| 1217 | } catch (err) { |
| 1218 | this.log.error(`Skill download zip failed: ${err}`); |
| 1219 | res.writeHead(500, { "Content-Type": "application/json" }); |
| 1220 | res.end(JSON.stringify({ error: `Failed to create zip: ${err}` })); |
| 1221 | } |
| 1222 | } |
| 1223 | |
| 1224 | private handleSkillVisibility(req: http.IncomingMessage, res: http.ServerResponse, urlPath: string): void { |
| 1225 | const segments = urlPath.split("/"); |
no test coverage detected