Skip to content

feat: downloadable service skill bundles#25

Merged
rodaddy merged 3 commits into
mainfrom
feat/skill-bundles
Jun 9, 2026
Merged

feat: downloadable service skill bundles#25
rodaddy merged 3 commits into
mainfrom
feat/skill-bundles

Conversation

@rodaddy

@rodaddy rodaddy commented Jun 9, 2026

Copy link
Copy Markdown
Owner

Summary

New `mcp2cli skills` command for managing service skill bundles — the user-friendly interface for the existing `generate-skills` engine. Agents can now list available skills, pipe them to files, install them to Hermes skill directories, and check for drift.

Also adds metadata (tool count, generation timestamp, schema hash) to generated SKILL.md frontmatter so agents can detect when skills are stale.

Closes #17

New Command: `mcp2cli skills`

```bash

List all services with skill status

mcp2cli skills list

open-brain ok 14 tools

n8n stale 12 tools (cache has 15)

vaultwarden missing

Output SKILL.md to stdout (pipeable)

mcp2cli skills get open-brain
mcp2cli skills get open-brain > /path/to/SKILL.md

Install skill bundle to a directory

mcp2cli skills install open-brain --target ~/.hermes/skills/mcp/open-brain

Preview what would change on regeneration

mcp2cli skills diff n8n

Generate/regenerate skill files

mcp2cli skills generate n8n --conflict=merge
```

Subcommand Details

Subcommand What it does
`list` Lists all configured services with skill status: `generated` (SKILL.md exists, tool count matches cache), `stale` (tool count mismatch), or `missing`. Supports `--json`.
`get ` Outputs the SKILL.md content to stdout. Designed for piping to files or other tools.
`install --target ` Copies the entire skill bundle (SKILL.md + references/) to a target directory. Supports `--target=path` and `--target path` syntax.
`diff ` Wraps `generate-skills --diff` — previews tool additions/removals/changes.
`generate ` Wraps `generate-skills` — full generation with all flags (`--dry-run`, `--conflict`, `--output`).

New: SKILL.md Metadata

Generated SKILL.md files now include metadata in the YAML frontmatter:

```yaml

name: open-brain
description: MCP tools for open-brain
tool_count: 14
generated_at: 2026-06-08T18:30:00.000Z
schema_hash: a1b2c3d4e5f67890
triggers:

  • open-brain
  • session
  • search
  • brain

```

  • `tool_count`: Number of tools documented — used by `skills list` to detect staleness
  • `generated_at`: When the skill was generated — useful for "how old is this?"
  • `schema_hash`: Truncated SHA-256 of sorted tool names — enables drift detection without reconnecting to the MCP server

Files Changed

File Lines What
`src/cli/commands/skills.ts` +195 New skills command with 5 subcommands
`src/cli/index.ts` +2 Register skills command
`src/cli/help.ts` +6 Add skills to help output
`src/generation/types.ts` +3 Add metadata fields to SkillTemplateInput
`src/generation/templates.ts` +9 Emit metadata in SKILL.md frontmatter
`src/cli/commands/generate-skills.ts` +10 Compute schema hash, pass metadata to template
`tests/cli/skills.test.ts` +167 10 tests covering all subcommands

Test Plan

  • 853/853 tests pass (10 new)
  • Build compiles clean (306 modules)
  • `skills list` shows configured services with status
  • `skills get` outputs SKILL.md to stdout
  • `skills install` copies bundle to target directory
  • `skills install --target=path` syntax works
  • Missing skill file produces clear error
  • Usage text shown for no/unknown subcommand

🤖 Generated with Claude Code

New `mcp2cli skills` command with 5 subcommands:
- list: shows all services with skill status (generated/stale/missing)
- get: outputs SKILL.md to stdout for piping
- install: copies skill bundle to target directory (e.g., Hermes skills)
- diff: previews what would change on regeneration
- generate: generates/regenerates skill files (wraps generate-skills)

Also adds metadata to generated SKILL.md frontmatter:
- tool_count: number of tools documented
- generated_at: ISO timestamp
- schema_hash: truncated SHA-256 of tool names for drift detection

Closes #17

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@github-actions

github-actions Bot commented Jun 9, 2026

Copy link
Copy Markdown
Contributor

Claude encountered an error —— View job


I'll analyze this and get back to you.

…epth

17 findings from 5 reviewers, all fixed:

HIGH (3):
- H1: Added validateIdentifier to get/install — blocks path traversal
- H2: Tilde expansion now happens BEFORE mkdir
- H3: generatedAt only updates when schema_hash changes (deterministic)

MEDIUM (6):
- M4/M5: Stale detection uses schema_hash from frontmatter, not regex
- M6: --json flag parsed from args, not process.argv
- M7: Install warns when overwriting existing skill bundle
- M8: Schema hash now includes full inputSchema (catches param changes)
- M9: diff/generate validate service name before delegating

LOW (8):
- L10-L11: Added tests for diff/generate/stale/ok/--json
- L12: Deduplicated dynamic import with shared lazy getter
- L13: Extracted resolveSkillDir shared function
- L14: Pipe chars escaped in markdown tables
- L15: Removed inaccurate file count from install output
- L16: cp uses dereference:true (copies contents, not symlinks)
- L17: auto-regen now computes and passes metadata

New: src/generation/skill-hash.ts with shared computeSchemaHash
and computeFullSchemaHash functions.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@github-actions

github-actions Bot commented Jun 9, 2026

Copy link
Copy Markdown
Contributor

Claude encountered an error —— View job


I'll analyze this and get back to you.

@rodaddy

rodaddy commented Jun 9, 2026

Copy link
Copy Markdown
Owner Author

Review Swarm Results

5 parallel reviewers. 17 unique findings after deduplication. ALL fixed in commit `7b16330`.

HIGH Fixes (3)

# Finding Fix
H1 Path traversal in get/install — no service name validation Added `validateIdentifier` calls + shared `resolveSkillDir` helper
H2 Tilde expansion after mkdir — creates literal `~` directory Moved expansion BEFORE mkdir; `resolvedTarget` used for both operations
H3 generatedAt makes SKILL.md non-deterministic Only updates timestamp when `schema_hash` actually changes

MEDIUM Fixes (6)

# Finding Fix
M4/M5 Stale detection used brittle regex tool count Now parses `schema_hash` from frontmatter, compares with `computeSchemaHash`
M6 `--json` used `process.argv` instead of args `handleSkillsList` now accepts args parameter
M7 `cp` silently overwrites existing files Warns when overwriting existing skill bundle
M8 schema_hash only covered tool names Now includes full inputSchema via `computeFullSchemaHash`
M9 diff/generate passed empty string for missing service Validates service name before delegating

LOW Fixes (8)

# Finding Fix
L10-L11 Missing test coverage Added tests for diff, generate, stale, ok, --json
L12 Duplicated dynamic import Shared lazy getter at module scope
L13 Duplicated skill-existence check Extracted `resolveSkillDir` shared function
L14 Pipe chars break markdown tables Escaped with `|` in templates
L15 Inaccurate file count Removed from install output
L16 cp preserves symlinks Added `dereference: true`
L17 auto-regen omits metadata Now computes schemaHash + generatedAt

New Infrastructure

`src/generation/skill-hash.ts` — shared hash functions:

  • `computeSchemaHash(tools)` — name + description hash (for stale detection)
  • `computeFullSchemaHash(schemas)` — name + inputSchema hash (for generate)

Verification

  • 862 tests pass, 0 fail (19 new tests)
  • Build compiles clean (307 modules)
  • All 17 findings addressed — zero deferred

…h rejection

12 findings from R2 verification, all fixed:

HIGH (2):
- H1: Standardized on computeSchemaHash everywhere — removed
  computeFullSchemaHash. All paths (generate, auto-regen, skills list)
  now use the same name+description hash. Eliminates always-stale bug.
- H2: --target path validated — rejects .. segments and HOME root

MEDIUM (3):
- M3: Bare ~ to HOME covered by H2
- M4: Added --force flag for install overwrite — refuses without it
- M5: Added validateIdentifier to handleSkillsDiff/handleSkillsGenerate

LOW (7):
- L6: Removed redundant double validateIdentifier in get/install
- L7: Consolidated duplicate file reads in auto-regen
- L8: Tests align with corrected hash function
- L9: Added 6 unit tests for computeSchemaHash (skill-hash.test.ts)
- L10: --json test now validates content fields
- L11: PAI_SKILLS_DIR validated for .. traversal
- L12: Added rejectPathSeparators to validateIdentifier pipeline

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@github-actions

github-actions Bot commented Jun 9, 2026

Copy link
Copy Markdown
Contributor

Claude encountered an error —— View job


I'll analyze this and get back to you.

@rodaddy

rodaddy commented Jun 9, 2026

Copy link
Copy Markdown
Owner Author

Review Swarm — Round 2 Verification

5 parallel reviewers verified R1 fixes and found 12 new issues. All fixed in commit `15f39d8`.

R2 HIGH Fixes (2)

# Finding Fix
H1 Hash function mismatch — generate-skills wrote `computeFullSchemaHash` (name+inputSchema) but skills list and auto-regen compared with `computeSchemaHash` (name+description). Skills list always reported "stale". Standardized on `computeSchemaHash` everywhere. Removed `computeFullSchemaHash` entirely. All paths now produce identical hashes.
H2 --target path unvalidated — install could write to arbitrary filesystem locations Validates resolved target: rejects `..` segments and HOME root (bare `~`).

R2 MEDIUM Fixes (3)

# Finding Fix
M3 Bare `~` resolves to HOME root Covered by H2 — HOME root now rejected
M4 Overwrite warning cosmetic only Added `--force` flag. Without it, refuses to overwrite existing bundles.
M5 diff/generate had M9 comments but no validation Added `validateIdentifier` calls before delegating

R2 LOW Fixes (7)

# Finding Fix
L6 Double validation in get/install Removed redundant inline calls
L7 Duplicate file reads in auto-regen Consolidated to single read
L8 Test validated buggy behavior Aligned with corrected hash function
L9 No unit tests for skill-hash Added 6 tests (sort independence, empty array, etc.)
L10 --json test didn't validate content Added field assertions
L11 PAI_SKILLS_DIR unvalidated Rejects `..` traversal
L12 validateIdentifier allowed bare slashes Added `rejectPathSeparators` to pipeline

Test Growth

Round Tests
Before PR 843
R1 initial 853
R1 fixes 862
R2 fixes 869

Verification

  • 869 tests pass, 0 fail
  • Build compiles clean (307 modules)

@rodaddy rodaddy merged commit d6d1cfa into main Jun 9, 2026
3 of 4 checks passed
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.

feat: generate downloadable service skill bundles

1 participant