* Create a new skill * @param name - Skill name (must be valid per agentskills.io spec) * @param source - "global" or "project" * @param description - Skill description * @param modeSlugs - Optional mode restrictions (undefined/empty = any mode) * @returns Path to created SKILL.md file
( name: string, source: "global" | "project", description: string, modeSlugs?: string[], )
| 346 | * @returns Path to created SKILL.md file |
| 347 | */ |
| 348 | async createSkill( |
| 349 | name: string, |
| 350 | source: "global" | "project", |
| 351 | description: string, |
| 352 | modeSlugs?: string[], |
| 353 | ): Promise<string> { |
| 354 | // Validate skill name |
| 355 | const validation = this.validateSkillName(name) |
| 356 | if (!validation.valid) { |
| 357 | throw new Error(validation.error) |
| 358 | } |
| 359 | |
| 360 | // Validate description |
| 361 | const trimmedDescription = description.trim() |
| 362 | if (trimmedDescription.length < 1 || trimmedDescription.length > 1024) { |
| 363 | throw new Error(t("skills:errors.description_length", { length: trimmedDescription.length })) |
| 364 | } |
| 365 | |
| 366 | // Determine base directory |
| 367 | let baseDir: string |
| 368 | if (source === "global") { |
| 369 | baseDir = getGlobalRooDirectory() |
| 370 | } else { |
| 371 | const provider = this.providerRef.deref() |
| 372 | if (!provider?.cwd) { |
| 373 | throw new Error(t("skills:errors.no_workspace")) |
| 374 | } |
| 375 | baseDir = path.join(provider.cwd, ".roo") |
| 376 | } |
| 377 | |
| 378 | // Always use the generic skills directory (mode info stored in frontmatter now) |
| 379 | const skillsDir = path.join(baseDir, "skills") |
| 380 | const skillDir = path.join(skillsDir, name) |
| 381 | const skillMdPath = path.join(skillDir, "SKILL.md") |
| 382 | |
| 383 | // Check if skill already exists |
| 384 | if (await fileExists(skillMdPath)) { |
| 385 | throw new Error(t("skills:errors.already_exists", { name, path: skillMdPath })) |
| 386 | } |
| 387 | |
| 388 | // Create the skill directory |
| 389 | await fs.mkdir(skillDir, { recursive: true }) |
| 390 | |
| 391 | // Generate SKILL.md content with frontmatter |
| 392 | const titleName = name |
| 393 | .split("-") |
| 394 | .map((word) => word.charAt(0).toUpperCase() + word.slice(1)) |
| 395 | .join(" ") |
| 396 | |
| 397 | // Build frontmatter with optional modeSlugs |
| 398 | const frontmatterLines = [`name: ${name}`, `description: ${trimmedDescription}`] |
| 399 | if (modeSlugs && modeSlugs.length > 0) { |
| 400 | frontmatterLines.push(`modeSlugs:`) |
| 401 | for (const slug of modeSlugs) { |
| 402 | frontmatterLines.push(` - ${slug}`) |
| 403 | } |
| 404 | } |
| 405 |
no test coverage detected