( input: string, skillName: string | undefined, options: AddOptions )
| 231 | } |
| 232 | |
| 233 | async function installCommand( |
| 234 | input: string, |
| 235 | skillName: string | undefined, |
| 236 | options: AddOptions |
| 237 | ): Promise<void> { |
| 238 | trackEvent("command", { name: "install" }); |
| 239 | const parsed = parseSkillInput(input); |
| 240 | if (!parsed) { |
| 241 | log.error(`Invalid input format: ${input}`); |
| 242 | log.info(`Expected: /owner/repo or full GitHub URL`); |
| 243 | log.info(`Example: ctx7 skills install /anthropics/skills pdf`); |
| 244 | log.blank(); |
| 245 | return; |
| 246 | } |
| 247 | const repo = `/${parsed.owner}/${parsed.repo}`; |
| 248 | |
| 249 | log.blank(); |
| 250 | const spinner = ora(`Fetching skills from ${repo}...`).start(); |
| 251 | |
| 252 | let selectedSkills: (Skill & { project: string })[]; |
| 253 | |
| 254 | // When a specific skill name is provided, fetch only that skill |
| 255 | if (skillName) { |
| 256 | spinner.text = `Fetching skill: ${skillName}...`; |
| 257 | const skillData = await getSkill(repo, skillName); |
| 258 | |
| 259 | if (skillData.error || !skillData.name) { |
| 260 | if (skillData.error === "prompt_injection_detected") { |
| 261 | spinner.fail(pc.red(`Prompt injection detected in skill: ${skillName}`)); |
| 262 | log.warn("This skill contains potentially malicious content and cannot be installed."); |
| 263 | return; |
| 264 | } |
| 265 | |
| 266 | spinner.text = `Fetching skill from GitHub: ${skillName}...`; |
| 267 | const ghResult = await getSkillFromGitHub(repo, skillName); |
| 268 | if (ghResult.status === "repo_not_found") { |
| 269 | spinner.fail(pc.red(`Repository not found: ${repo}`)); |
| 270 | return; |
| 271 | } |
| 272 | if (ghResult.status !== "ok" || !ghResult.skill) { |
| 273 | spinner.fail(pc.red(`Skill not found: ${skillName}`)); |
| 274 | return; |
| 275 | } |
| 276 | |
| 277 | spinner.succeed(`Found skill: ${skillName}`); |
| 278 | selectedSkills = [ghResult.skill]; |
| 279 | } else { |
| 280 | spinner.succeed(`Found skill: ${skillName}`); |
| 281 | selectedSkills = [ |
| 282 | { |
| 283 | name: skillData.name, |
| 284 | description: skillData.description, |
| 285 | url: skillData.url, |
| 286 | project: repo, |
| 287 | }, |
| 288 | ]; |
| 289 | } |
| 290 | } else { |
no test coverage detected