Skip to content

feat(cli): skillforge ls — list installed skills - #19

Merged
adityachilka1 merged 3 commits into
mainfrom
feat/ls
May 27, 2026
Merged

feat(cli): skillforge ls — list installed skills#19
adityachilka1 merged 3 commits into
mainfrom
feat/ls

Conversation

@adityachilka1

Copy link
Copy Markdown
Owner

What

skillforge ls — list installed skills in ~/.claude/skills/ (the default install target) with name, version, and absolute path pulled from each skill's SKILL.md frontmatter. The npm ls analogue for the Claude skills tree.

# Default — scan ~/.claude/skills
skillforge ls

# Scan a different tree (CI fixtures, sandbox testing)
skillforge ls --from ./local-skills

# Include skill dirs whose SKILL.md fails validation, tagged `(invalid)`
skillforge ls --include-invalid

# Machine-readable output for shell pipelines
skillforge ls --json | jq '.skills[] | .name'

The human-readable mode prints a tidy 3-column table — Name Version Path — with column widths sized to actual content so narrow trees stay compact and long names still align. Sentence-case heading in tracked uppercase keeps the section signal quiet rather than editorial. Empty-state message names the scanned directory so the reader knows which tree came up empty.

Why

install writes to ~/.claude/skills/<skill-name>/ by default. Once a user has installed more than two or three skills, the natural follow-up is "what have I got?" — and the honest answer was "go ls the directory yourself, then cat SKILL.md on each one to see the version". This command closes that loop with a single read-only directory scan. No network, no writes, no surprises.

Semantics

Aspect Behaviour
Default root ~/.claude/skills/ — same path install writes to. --from <dir> overrides for tests and alt-trees.
What counts as a "skill" A child directory with a SKILL.md at its top level. Loose files at the parent level get ignored silently; child dirs without a SKILL.md are skipped silently too — a half-pulled install shouldn't pollute every ls invocation.
Validation Each skill's SKILL.md frontmatter runs through the same SkillFrontmatterSchema that install enforces. Invalid skills are excluded from the count by default; --include-invalid flips that to "include them with valid: false and an issues list" — useful when debugging "why isn't my skill showing up".
Missing root --from <dir> that doesn't exist → empty result, exit 0. A fresh machine with no ~/.claude/skills/ yet is not an error; the honest answer is "zero installed", same as npm ls in an empty project.
--from is a file Hard error. The user passed a flag; if it points at a file, that's a typo on their end, not "zero skills".
Sort order Name ascending. Stable output is the whole point of a list command — the order should never depend on filesystem readdir order, which varies between platforms.
source discriminator New field on InstalledSkill: "dir". Future-proofs the shape for .skill archive listings later (e.g. ls ~/.claude/skills-cache --bundles).
Side effects None. ls is read-only end-to-end — no fetching, no writing, no temp files.

Implementation — minimal and additive

  • New src/ls.ts (~140 LOC of which ~50 is essence): listInstalledSkills({ fromDir?, includeInvalid? }) returns { fromDir, count, skills }. Reuses gray-matter (already on deps for validate / lint / inspect) and SkillFrontmatterSchema from schema.ts.
  • src/cli.ts: new ls command with --from, --include-invalid, --json flags, plus a tidy 3-column table renderer (printLsReport) and an empty-state message that names the scanned directory.
  • src/index.ts: exports listInstalledSkills, LsOptions, LsResult, InstalledSkill so library consumers can build on the same API the CLI uses.
  • README.md: new ### skillforge ls section after cat, status line and roadmap bumped.
  • No changes to install.ts / cat.ts / skill-loader.ts / validate.ts / lint.ts / pack.ts / inspect.ts / diff.ts / tree.ts / update.ts / init.ts / format.ts.
  • No new runtime deps. Reuses gray-matter and zod (via SkillFrontmatterSchema).

Tests

12 new tests in src/ls.test.ts, written test-first per §17:

Case What it pins
Empty dir Freshly-created empty --fromcount: 0, skills: [].
Single skill One skill dir → count: 1, name + version match frontmatter, source: "dir", valid: true.
Three skills sorted Three dirs added out of order → returned in alphabetical order by name.
Invalid skill excluded (default) One valid + one with description too short → only the valid one appears, count: 1.
Invalid skill included (flag) --include-invalid → both appear, broken one has valid: false and an issues[] array; valid one has no issues field.
--from override Explicit fromDir reflected in LsResult.fromDir.
Non-existent dir --from /does/not/exist → empty result, no throw.
--from is a file Hard error mentioning "not a directory".
Skill dir without SKILL.md Silently skipped — only the real skill shows up.
LsResult shape Object.keys exactly ["count", "fromDir", "skills"]; valid InstalledSkill keys exactly ["name", "path", "source", "valid", "version"] (no issues).
Loose file in parent Files at the parent level (e.g. .DS_Store) are ignored, not counted.
Default fromDir No fromDir → resolves to ~/.claude/skills; doesn't assert count (user state-dependent).

The red→green transition I captured during development: with ls.test.ts written and ls.ts not yet created, vitest run src/ls.test.ts failed with Error: Failed to load url ./ls.js (resolved id: ./ls.js) in /private/tmp/skillforge-ls/src/ls.test.ts. Does the file exist? — exactly the failure the spec calls for. After implementing src/ls.ts, the same vitest run returned ✓ src/ls.test.ts (12 tests) 57ms on the first pass.

Gates

Check Status
pnpm install no drift
tsc --noEmit clean
biome check src (--fix --unsafe) clean
vitest run 167/167 pass (155 existing + 12 new)
tsup build ESM + DTS build success
Smoke: 3 skills in /tmp/sf-ls-smoke/skills./dist/cli.js ls --from … sorted 3-col table, --json | jq .count returns 3
Smoke: --include-invalid on a broken skill row appears with (invalid) tag, dimmed

Don't refactor

No changes to install.ts / cat.ts / skill-loader.ts / validate.ts / lint.ts / pack.ts / inspect.ts / diff.ts / tree.ts / update.ts / init.ts / format.ts. No new runtime deps. SkillFrontmatterSchema reused as-is.

Declaration of AI-Tools / LLMs usage

  • Claude (Opus) for design, implementation, tests, commit message, this PR body — reviewed by @adityachilka1 before push.

@coderabbitai

coderabbitai Bot commented May 27, 2026

Copy link
Copy Markdown

Warning

Review limit reached

@adityachilka1, we couldn't start this review because you've reached your PR review rate limit.

More reviews will be available in 11 minutes and 10 seconds. Learn how PR review limits work.

Your organization has run out of usage credits. Purchase more in the billing tab.

⌛ How to resolve this issue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans include higher PR review limits than trial, open-source, and free plans. In all cases, reviews become available again over time. During sustained high-volume PR review activity, CodeRabbit may temporarily slow when the next review becomes available.

Please see our Fair Usage Limits Policy for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: c5cbfc46-6499-457b-ab34-167b6117f355

📥 Commits

Reviewing files that changed from the base of the PR and between f33e197 and 4083402.

📒 Files selected for processing (5)
  • README.md
  • src/cli.ts
  • src/index.ts
  • src/ls.test.ts
  • src/ls.ts
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/ls

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.

@adityachilka1
adityachilka1 merged commit 1fab9fb into main May 27, 2026
4 checks passed
@adityachilka1
adityachilka1 deleted the feat/ls branch May 27, 2026 21:23
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.

2 participants