Skip to content

feat: list Agent Skills a repo publishes - #8

Merged
jdx merged 2 commits into
mainfrom
feat/vendor-skills
Jul 27, 2026
Merged

feat: list Agent Skills a repo publishes#8
jdx merged 2 commits into
mainfrom
feat/vendor-skills

Conversation

@jdx

@jdx jdx commented Jul 27, 2026

Copy link
Copy Markdown
Owner

Stacked on #7 — review that first; this diff is only the skills work.

usage.sh now detects skills/<name>/SKILL.md and lists what it finds, following the agentskills layout. Detecting a convention that already exists means a repo shipping skills for Claude Code, Cursor or Copilot gets a page here for free, with no second place to publish to.

Why this is the version of jdx/mise#9479 that works

The objection to mise bridging tool-bundled skills was concrete: 86.5% of mise's registry is binary tarballs with nowhere to put markdown, and auto-injecting third-party prose into an agent's context is a supply-chain surface — 37% of skills in public registries carry a security flaw.

Serving them from the repo sidesteps both. Nothing ships in the tarball, so the packaging problem disappears and it works for every backend. And the trust posture inverts: an agent pulls a skill for a CLI it asked about, instead of prose being pushed at it for every installed tool.

So the page serves skills and does not rank, recommend or merge them. Provenance stays attached — name, license, and a link to the file in the repo — and the section says the vendor published it, not usage.sh.

On not using a YAML parser

skills.ts reads the four scalar fields the spec defines and skips the rest. That is a deliberate departure from the reasoning in #7, where I argued for a real KDL parser because a subtly wrong command tree is worse than none.

The difference is blast radius. A mis-parsed spec produces a plausible-but-wrong command tree; a mis-parsed frontmatter costs a missing subtitle. The fields are all scalars, so this handles plain, quoted, folded (>) and literal (|) values and drops anything it cannot read. A general YAML parser is ~100KB for four fields.

A skill with no description — which the spec requires — is skipped rather than shown half-parsed. It is someone else's file, not ours to guess at.

Verified against real files, not just fixtures

repo what it exercises
anthropics/skills brand-guidelines bare description, license as free text
crazyguitar/pysheeet 381-char unquoted description
tanweai/pua quoted description with em-dashes and CJK, license: MIT

All three parse correctly. 11 unit tests cover the forms the spec allows and the ones it does not: quoted, folded, literal, CRLF, nested metadata: blocks not leaking into fields, missing description, absent and unterminated frontmatter.

Cost

One directory listing plus one fetch per skill, capped at 25. That is paid on a cold hit for any repo anyone visits, and nothing stops a repo having a hundred directories under skills/. Both cached at the edge for 10 minutes like the rest.

npm test 26/26 · npm run typecheck 0 errors · npm run build clean.

Next

MCP over both halves — get_cli, get_command, get_skill — now that there is a command tree and a skill list to expose.

This PR was generated by an AI coding assistant.


Note

Medium Risk
Adds up to 26 GitHub/raw fetches per cold repo page (listing + capped skill files) and surfaces third-party markdown with attribution only; no auth or data-store changes.

Overview
Adds automatic discovery and listing of Agent Skills at skills/<name>/SKILL.md on GitHub repo pages, alongside existing Usage command specs.

A new skills.ts module parses SKILL.md frontmatter with a small scalar-only reader (no full YAML dependency): required description, optional license / compatibility / allowed-tools, and skips invalid or half-parsed files. Forge.skills() is added to the forge interface; the GitHub adapter lists skills/ via the Contents API, fetches each SKILL.md from raw.githubusercontent.com, caps at 25 skills, and distinguishes null (no skills/ dir) from [] (dir exists but nothing parseable).

repoData loads skills in parallel with other sections; ?format=json includes them too. The repo Astro page gets a Skills section with cards (name, description, link to source on GitHub) and copy that skills are published by the repo, not usage.sh.

11 unit tests cover frontmatter edge cases; global.css adds card layout for the section.

Reviewed by Cursor Bugbot for commit 8eb0451. Bugbot is set up for automated code reviews on this repo. Configure here.

@coderabbitai

coderabbitai Bot commented Jul 27, 2026

Copy link
Copy Markdown

Warning

Review limit reached

@jdx, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 58 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

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.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Central YAML (base), Organization UI (inherited)

Review profile: CHILL

Plan: Pro Plus

Run ID: f3dfc98a-f4c6-434e-896e-54de6b185288

📥 Commits

Reviewing files that changed from the base of the PR and between aab8dfa and 8eb0451.

📒 Files selected for processing (7)
  • src/forges/github.ts
  • src/forges/index.ts
  • src/lib/data.ts
  • src/pages/gh/[owner]/[repo].astro
  • src/skills.test.ts
  • src/skills.ts
  • src/styles/global.css

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.

usage.sh now detects `skills/<name>/SKILL.md` and lists what it finds, the
layout from https://agentskills.io/specification. Detecting the convention
that already exists means a repo shipping skills for Claude Code, Cursor or
Copilot gets a page here for free, with no second place to publish to.

This is the part of jdx/mise#9479 that does not need a packaging channel.
The objection to bridging tool-bundled skills was that 86.5% of mise's
registry is binary tarballs with nowhere to put markdown, and that
auto-injecting third-party prose into an agent's context is a supply-chain
surface. Serving them from a repo sidesteps both: nothing ships in the
tarball, and the agent pulls a skill for a CLI it asked about rather than
having prose pushed at it for every installed tool.

So the page serves skills, and does not rank, recommend or merge them.
Provenance stays attached — the name, the license, and a link to the file
in the repo it came from — and the section says the vendor published it,
not usage.sh.

skills.ts reads the four scalar fields the spec defines and skips the rest.
It is deliberately not a YAML parser: the fields are all scalars, so it
handles plain, quoted, folded and literal values and drops anything it
cannot read, which costs a missing subtitle where a general parser would
cost far more bundle than four fields justify. A skill without a
description — which the spec requires — is skipped rather than shown
half-parsed.

Verified against real published files, not just fixtures: anthropics/skills
brand-guidelines, crazyguitar/pysheeet, and tanweai/pua, which between them
cover a bare description, a quoted one containing em-dashes and CJK, and a
license field.

Reading skills is capped per repo, since the cost is one fetch each on a
cold hit for any repo anyone visits.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Comment thread src/forges/github.ts Outdated
@jdx
jdx force-pushed the feat/vendor-skills branch from 70e33c4 to b2a974e Compare July 27, 2026 00:50
@jdx
jdx changed the base branch from feat/parse-usage-spec to main July 27, 2026 00:50
@jdx

jdx commented Jul 27, 2026

Copy link
Copy Markdown
Owner Author

Rebased onto main now that #7 has merged, and retargeted the base from feat/parse-usage-spec to main. It is a single commit and the diff is only the skills work — the seven files above, none of them carried over from #7.

npm ci from the lockfile, 26/26 tests, typecheck and build all clean.

This comment was generated by an AI coding assistant.

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes and found 1 potential issue.

There are 2 total unresolved issues (including 1 from previous review).

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit b2a974e. Configure here.

Comment thread src/skills.ts
@greptile-apps

greptile-apps Bot commented Jul 27, 2026

Copy link
Copy Markdown

Greptile Summary

This PR adds repository-published Agent Skill discovery and presentation.

  • Fetches and parses up to 25 skills/<name>/SKILL.md files from GitHub repositories.
  • Adds skills to assembled repository data and JSON responses.
  • Renders skill metadata and provenance links on repository pages.
  • Adds parser tests and styles for skill cards.

Confidence Score: 3/5

This PR is not yet safe to merge because valid skills can be omitted and invalid or incorrectly parsed skill metadata can still be published.

The previously reported defects remain in current HEAD: discovery truncates directories before validating skills, missing names are synthesized, and the scalar parser diverges from valid YAML behavior before its output reaches HTML and JSON consumers.

Files Needing Attention: src/forges/github.ts, src/skills.ts, src/skills.test.ts

Important Files Changed

Filename Overview
src/forges/github.ts Adds GitHub skill discovery and fetching, but the fetch cap is applied before candidate directories are validated.
src/skills.ts Adds the SKILL.md frontmatter parser, which still accepts absent required names and does not preserve several valid YAML scalar semantics.
src/skills.test.ts Covers common scalar forms and malformed inputs while explicitly preserving the missing-name fallback.
src/lib/data.ts Loads forge skills alongside existing repository data and exposes them through RepoData.
src/pages/gh/[owner]/[repo].astro Adds HTML and JSON presentation of parsed skill metadata with repository provenance.
src/forges/index.ts Extends the forge contract with skill discovery.
src/styles/global.css Adds layout and typography rules for skill cards.

Reviews (3): Last reviewed commit: "fix: tell the two empty skill states apa..." | Re-trigger Greptile

Comment thread src/forges/github.ts
Comment on lines +112 to +116

const dirs = entries
.filter((e) => e.type === "dir")
.map((e) => e.name)
.sort()

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Cap excludes valid skill directories

When more than 25 directories exist under skills/, this truncates the sorted directory list before checking for SKILL.md, so unrelated or invalid directories can consume the limit and cause valid published skills to be omitted or the page to report that no skills exist.

Fix in Claude Code

Comment thread src/skills.ts
Comment on lines +94 to +98
const { fields, body } = parseFrontmatter(source);
const name = fields.name ?? dir;
const description = fields.description;
if (!description) return null;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Missing required names are synthesized

When a SKILL.md has a description but omits its required name, this substitutes the directory name and publishes the invalid file in the HTML and JSON listings instead of rejecting it as the function contract describes.

Fix in Claude Code

Comment thread src/skills.ts
Comment on lines +57 to +78
const match = /^([A-Za-z][\w-]*):[ \t]*(.*)$/.exec(line);
if (!match) continue;

const key = match[1];
let value = match[2].trim();

if (value === "|" || value === ">" || /^[|>][-+]?$/.test(value)) {
// Block scalar: take the indented lines that follow.
const folded = value.startsWith(">");
const block: string[] = [];
while (i + 1 < lines.length && /^(\s+|$)/.test(lines[i + 1])) {
block.push(lines[++i].replace(/^\s{1,4}/, ""));
}
value = folded
? block.join(" ").replace(/\s+/g, " ").trim()
: block.join("\n").trimEnd();
} else if (
(value.startsWith('"') && value.endsWith('"') && value.length > 1) ||
(value.startsWith("'") && value.endsWith("'") && value.length > 1)
) {
value = value.slice(1, -1);
if (line.includes('"')) value = value.replace(/\\"/g, '"');

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Valid YAML scalars are misparsed

When valid frontmatter uses YAML features such as an inline comment, double-quoted escapes, or block content indented by more than four spaces, this partial parser retains comment text, leaves escapes literal, or preserves unintended indentation, causing incorrect descriptions and metadata in both the rendered page and JSON output.

Fix in Claude Code

`skills()` returned null both when there was no `skills/` directory and when
there was one that yielded nothing readable, so the page claimed a repo had
no skills while the API had just listed the directory — loose files instead
of subdirectories, a missing SKILL.md, or frontmatter that would not parse
all landed on the wrong message.

Null now means the directory is absent and an empty array means it is there
but unreadable, and the page says so, pointing at what a SKILL.md needs.

Separately, a quoted or block `description` holding only whitespace passed
the required-field check, because the value was tested for truthiness before
being trimmed. `description: "   "` listed a skill with no description at
all. Values are trimmed before the check now, covered both ways.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@jdx

jdx commented Jul 27, 2026

Copy link
Copy Markdown
Owner Author

Both correct; fixed.

Skills empty state misleading — the important one, because it made the page assert something false. skills() returned null both when there was no skills/ directory and when there was one that yielded nothing readable, so a repo with loose files instead of subdirectories, or a missing SKILL.md, or frontmatter that would not parse, was told it had no skills at all. Null now means absent, an empty array means present-but-unreadable, and the page distinguishes them — the second case points at what a SKILL.md actually needs, which is the useful thing to say to someone who tried.

Whitespace-only description — also right, and it defeated the check I had written specifically to avoid listing half-parsed skills. The value was tested for truthiness before being trimmed, so description: " " passed as present. Trimmed before the check now, with tests for the quoted and block forms.

27/27 tests, typecheck and build clean.

This comment was generated by an AI coding assistant.

@jdx
jdx merged commit 9f18171 into main Jul 27, 2026
6 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.

1 participant