Skip to content

feat(agents): add skill-to-subagent converter command#34

Merged
rohitg00 merged 2 commits intomainfrom
feat/agent-from-skill-command
Feb 1, 2026
Merged

feat(agents): add skill-to-subagent converter command#34
rohitg00 merged 2 commits intomainfrom
feat/agent-from-skill-command

Conversation

@rohitg00
Copy link
Copy Markdown
Owner

@rohitg00 rohitg00 commented Feb 1, 2026

Add skillkit agent from-skill command to convert SkillKit skills into Claude Code native subagent format (.md files in .claude/agents/).

Features:

  • Reference mode (default): generates subagent with skills: [skill-name]
  • Inline mode (--inline): embeds full skill content in system prompt
  • Options: --model, --permission, --global, --output, --dry-run

New files:

  • packages/core/src/agents/skill-converter.ts
  • packages/core/src/agents/tests/skill-converter.test.ts (23 tests)

Closes #22


Open with Devin

Summary by CodeRabbit

  • New Features

    • Added a new CLI command to convert skills into subagents with inline or reference modes, model & permission options, output control and dry-run support.
  • Public API

    • Exposed skill-to-subagent conversion APIs and added a tools/frontmatter field to support richer subagent metadata.
  • Tests

    • Added comprehensive tests covering conversion logic, content generation, modes, and edge cases.

✏️ Tip: You can customize this high-level summary in your review settings.

Add `skillkit agent from-skill` command to convert SkillKit skills into
Claude Code native subagent format (.md files in .claude/agents/).

Features:
- Reference mode (default): generates subagent with `skills: [skill-name]`
- Inline mode (--inline): embeds full skill content in system prompt
- Options: --model, --permission, --global, --output, --dry-run

New files:
- packages/core/src/agents/skill-converter.ts
- packages/core/src/agents/__tests__/skill-converter.test.ts (23 tests)

Closes #22
@vercel
Copy link
Copy Markdown

vercel bot commented Feb 1, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
skillkit Ready Ready Preview, Comment Feb 1, 2026 7:48pm
skillkit-docs Ready Ready Preview, Comment Feb 1, 2026 7:48pm

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Feb 1, 2026

📝 Walkthrough

Walkthrough

Adds an AgentFromSkill CLI command and a skill-to-subagent conversion library, plus tests and type updates, enabling generation of Claude Code subagent markdown from SkillKit skills with inline/reference modes and frontmatter-driven metadata.

Changes

Cohort / File(s) Summary
CLI registration & exports
apps/skillkit/src/cli.ts, packages/cli/src/commands/index.ts
Import and register AgentFromSkillCommand in the top-level CLI and re-export it from the commands barrel.
CLI command implementation
packages/cli/src/commands/agent.ts
New AgentFromSkillCommand (agent from-skill) added: discovers/validates skills, reads content, validates options, calls generator, writes .md output (global/project), supports dry-run, overwrite, inline vs reference modes, and user messaging.
Core converter API & implementation
packages/core/src/agents/index.ts, packages/core/src/agents/skill-converter.ts
New exports and implementation: skillToSubagent, generateSubagentFromSkill, loadAndConvertSkill, SkillToSubagentOptions; functions to parse frontmatter, determine tools, produce canonical agent and render YAML-frontmatter + markdown.
Types
packages/core/src/agents/types.ts
Adds optional tools field to AgentFrontmatter alongside existing allowedTools.
Tests
packages/core/src/agents/__tests__/skill-converter.test.ts
Comprehensive tests covering reference/inline conversion, frontmatter parsing, tool handling, generated markdown content, and edge cases.

Sequence Diagram

sequenceDiagram
    participant User
    participant CLI
    participant Core
    participant FS
    User->>CLI: run `skillkit agent from-skill --skillName=...`
    CLI->>Core: discoverSkills()
    Core-->>CLI: skills list
    CLI->>Core: validate & select skill
    CLI->>FS: read skill file
    FS-->>CLI: skill content
    CLI->>Core: generateSubagentFromSkill(skill, content, options)
    Core->>Core: skillToSubagent() (parse frontmatter, choose inline/reference, merge tools)
    Core->>Core: generateSubagentMarkdown()
    Core-->>CLI: markdown string
    CLI->>FS: write .md (or dry-run)
    FS-->>CLI: write success
    CLI-->>User: success message / usage guidance
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

Poem

🐰 I hopped through YAML, tags, and tools,
Turned skills to subagents, no need for rules,
Inline or reference, I stitched them with care,
A markdown delight, with metadata to spare,
Hooray — the CLI blooms, carrot-powered flair!

🚥 Pre-merge checks | ✅ 4 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 76.92% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main feature added: a skill-to-subagent converter command for the agents module.
Linked Issues check ✅ Passed The PR implements skill-to-subagent conversion with reference and inline modes, file generation to .claude/agents/, and scope handling (global/local) via --global and --output options, directly addressing issue #22's requirements.
Out of Scope Changes check ✅ Passed All changes are directly related to implementing the skill-to-subagent converter feature: CLI command registration, core conversion logic, tests, and public API exports.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/agent-from-skill-command

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@devin-ai-integration devin-ai-integration bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Devin Review found 1 potential issue.

View issue and 5 additional flags in Devin Review.

Open in Devin Review

Comment on lines +90 to +93
return allowedToolsStr
.split(',')
.map(t => t.trim())
.filter(Boolean);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔴 parseAllowedTools only splits by comma, missing space-separated format support

The parseAllowedTools function in skill-converter.ts only splits the allowed-tools string by comma (,), but the codebase supports both comma-separated and space-separated formats for this field.

Click to expand

Evidence of space-separated format usage

The test suite and e2e tests show that allowed-tools can be space-separated:

  • tests/skills.test.ts:428: allowed-tools: Bash(git:*) Read Write
  • e2e/core-commands.e2e.test.ts:195: allowed-tools: Bash(npm:*) Read Write Edit

The bug

In packages/core/src/agents/skill-converter.ts:90-93:

return allowedToolsStr
  .split(',')
  .map(t => t.trim())
  .filter(Boolean);

This only splits by comma. When a skill uses space-separated tools like Bash(git:*) Read Write, the function returns ['Bash(git:*) Read Write'] (a single element array) instead of ['Bash(git:*)', 'Read', 'Write'].

Expected behavior

The function should handle both comma-separated and space-separated formats, similar to how packages/core/src/agents/parser.ts:187 handles it:

allowedTools = fm.allowedTools.split(/[,\n]/).map(t => t.trim()).filter(Boolean);

Or even better, split by comma, newline, or whitespace to handle all cases.

Impact

When converting skills that use space-separated allowed-tools to subagents, the generated agent will have incorrect tool restrictions - a single malformed tool name instead of multiple valid tools.

Recommendation: Change the split pattern to handle both comma-separated and space-separated formats. Consider using a regex like /[,\s]+/ or /[,\n\s]+/ to split by comma, newline, or whitespace, while being careful to preserve tool arguments like Bash(git:*).

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 Fix all issues with AI agents
In `@packages/cli/src/commands/agent.ts`:
- Around line 870-876: The CLI option help for permission is out of sync with
accepted values; update the Option.String description for permission (the
permission = Option.String(...) declaration) to list all valid modes including
"inherit" and "bypassPermissions" in addition to "default, plan, auto-edit,
full-auto" so the help text matches actual supported values; also scan the model
= Option.String(...) declaration to confirm its description lists the actual
supported model names and update if any additional models exist.
- Around line 936-942: The filename construction allows a malicious --output to
escape the agents directory; sanitize and validate the output stem before
joining into outputPath: when this.output is set, ensure you extract only a safe
basename/stem (no path separators or absolute paths, reject or strip leading ../
or /, allow only a limited character set) and append the .md extension, then
build outputPath from targetDir and that validated filename; update the logic
around the filename variable (used with generateSubagentFromSkill, targetDir,
outputPath) to perform this validation/sanitization and error out or fallback if
the provided output is unsafe.

In `@packages/core/src/agents/skill-converter.ts`:
- Around line 77-94: parseAllowedTools currently assumes
frontmatter['allowed-tools'] is a string and calls .split(','), which fails for
YAML-parsed arrays; update parseAllowedTools (function name: parseAllowedTools,
types: SkillFrontmatter/frontmatter) to accept both string and string[]: if
optionTools present return it; then check if allowedToolsStr is an array
(Array.isArray) — normalize by mapping/trimming/filtering and return; if it's a
string, split by ',' then trim/filter as before; ensure the function returns
string[] | undefined in all branches.
🧹 Nitpick comments (2)
packages/core/src/agents/skill-converter.ts (1)

148-195: Escape list items and author/tags in YAML.
Tools/tags/author values with special characters can break frontmatter. Reuse escapeYamlString for list items and scalar fields.

♻️ Suggested hardening
   if (canonical.author) {
-    lines.push(`author: ${canonical.author}`);
+    lines.push(`author: ${escapeYamlString(canonical.author)}`);
   }
   if (canonical.tags && canonical.tags.length > 0) {
-    lines.push(`tags: [${canonical.tags.join(', ')}]`);
+    lines.push(`tags: [${canonical.tags.map(escapeYamlString).join(', ')}]`);
   }

   if (canonical.userInvocable !== undefined) {
     lines.push(`user-invocable: ${canonical.userInvocable}`);
   }

   lines.push('---', '', canonical.content);

   return lines.join('\n');
 }

 function appendYamlList(lines: string[], key: string, items?: string[]): void {
   if (!items || items.length === 0) return;
   lines.push(`${key}:`);
   for (const item of items) {
-    lines.push(`  - ${item}`);
+    lines.push(`  - ${escapeYamlString(item)}`);
   }
 }
packages/cli/src/commands/agent.ts (1)

953-959: Consider warning on same-name agents across scopes.
If a global agent exists while creating a project agent (or vice versa), users can end up with ambiguous behavior. A warning aligns with the scope-handling objective.

💡 Optional warning
     const outputPath = join(targetDir, filename);

+    const otherDir = this.global
+      ? join(process.cwd(), '.claude', 'agents')
+      : join(homedir(), '.claude', 'agents');
+    const otherPath = join(otherDir, filename);
+    if (existsSync(otherPath)) {
+      console.log(
+        chalk.yellow(`Note: a ${this.global ? 'project' : 'global'} agent with the same name exists at ${otherPath}`)
+      );
+    }
+
     if (this.dryRun) {
       console.log(chalk.cyan('Preview (dry run):\n'));

- Update CLI option descriptions to list all valid values (model: inherit, permission: bypassPermissions)
- Add filename sanitization to prevent path traversal attacks via --output
- Fix parseAllowedTools to handle both string and YAML array formats
- Support space-separated tool lists in allowed-tools
- Add tests for array and space-separated formats
@rohitg00 rohitg00 merged commit 847af0f into main Feb 1, 2026
10 checks passed
@rohitg00 rohitg00 deleted the feat/agent-from-skill-command branch February 1, 2026 20:00
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Claude's Subagents

1 participant