(cmd: SkillCommand, targetSkillsDir: string)
| 219 | * Throws on failure so the caller can ack(failed, error). Never retries. |
| 220 | */ |
| 221 | export async function executeSkillCommand(cmd: SkillCommand, targetSkillsDir: string): Promise<void> { |
| 222 | assertSafeResourceName(cmd.skill_slug); |
| 223 | |
| 224 | switch (cmd.type) { |
| 225 | case 'install_skill': |
| 226 | case 'update_skill': { |
| 227 | if (!cmd.download_url) { |
| 228 | throw new Error(`${cmd.type} requires download_url`); |
| 229 | } |
| 230 | const zip = await downloadZip(cmd.download_url); |
| 231 | const actualDir = await installSkillZip(zip, cmd.skill_slug, targetSkillsDir); |
| 232 | log.debug(`[skill-command] ${cmd.type} ${cmd.skill_slug}@${cmd.skill_version ?? '?'} → ${targetSkillsDir}/${actualDir}`); |
| 233 | return; |
| 234 | } |
| 235 | case 'uninstall_skill': { |
| 236 | const dir = path.join(targetSkillsDir, cmd.skill_slug); |
| 237 | if (await pathExists(dir)) { |
| 238 | await remove(dir); |
| 239 | } else { |
| 240 | const map = await readSlugMap(targetSkillsDir); |
| 241 | const mapped = map[cmd.skill_slug]; |
| 242 | if (mapped && mapped !== cmd.skill_slug) { |
| 243 | const mappedDir = path.join(targetSkillsDir, mapped); |
| 244 | if (await pathExists(mappedDir)) { |
| 245 | await remove(mappedDir); |
| 246 | } |
| 247 | } |
| 248 | } |
| 249 | await removeSlugMapEntry(targetSkillsDir, cmd.skill_slug); |
| 250 | log.debug(`[skill-command] uninstall_skill ${cmd.skill_slug} (from ${targetSkillsDir})`); |
| 251 | return; |
| 252 | } |
| 253 | default: |
| 254 | throw new Error(`unknown skill command type: ${(cmd as SkillCommand).type}`); |
| 255 | } |
| 256 | } |
no test coverage detected