Skip to content

Add /skills panel: browse, invoke, open, and delete loaded skills - #1289

Open
lab1207 wants to merge 7 commits into
CodebuffAI:mainfrom
lab1207:feat/skills-panel
Open

Add /skills panel: browse, invoke, open, and delete loaded skills#1289
lab1207 wants to merge 7 commits into
CodebuffAI:mainfrom
lab1207:feat/skills-panel

Conversation

@lab1207

@lab1207 lab1207 commented Sep 6, 2026

Copy link
Copy Markdown

What

A /skills slash command (alias /skill) that opens an interactive panel for
managing loaded skills — the management surface skills currently lack. Follows
the issue discussion on skill discoverability: project skills silently shadow
global ones, and there is no in-CLI answer to "what do I have?" short of
reading directories.

Closes none — related to the /skills feature proposal. (Also includes
test/setup-scm-loader.ts, cherry-picked from my earlier PR, because tests
touching the SDK barrel cannot run without it.)

UX

┌─ ▾ Skills — 2 loaded (1 project, 1 global) ─┐
│ ❯ release-notes   project  Draft release n… │
│   git-helper      global   Helpful git wor… │
│ Enter run · o open · d delete · esc close   │
└─────────────────────────────────────────────┘
  • Enter: invoke the skill through the existing skill input mode (the exact
    path /skill: takes — shared via enterSkillMode so the two entries
    can't drift)
  • o: open the SKILL.md in $EDITOR (VISUAL/EDITOR, notepad/vi fallback)
  • d: delete with an explicit confirmation prompt showing the file path
  • With no skills loaded, /skills prints install guidance (where skills live,
    npx skills add <owner/repo>) instead of an empty panel

Implementation

Clones the queue-panel architecture end to end:

  • state/skills-panel-store.ts — zustand store, mirrors queue-panel-store
  • utils/skills-panel-actions.ts — keymap resolver, renderer-free testable
    like queue-panel-actions
  • components/skills-panel.tsx — panel UI on ClickableTitleBox
  • registration in command-registry.ts + slash-commands.ts + chat.tsx wiring
    (render branch, keyboard-disable, dock takeover, unmount cleanup)

Panel participates in the same keyboard arbitration as queue (review,
ask-user, sponsored menu). One deliberate deviation from queue-panel: while a
delete is pending confirmation, all other keys are swallowed so a held d
cannot chain-delete rows — a risk the queue editor doesn't have.

Verification

  • 9 new tests (keymap + command behavior), all pass
  • CLI suite: 1,586 pass / 12 fail — all 12 are pre-existing Windows
    path-separator and file-locking failures present on main. Happy to file
    separate issues for those.

Tested on Windows 11 / bun 1.3.14.

Update: live reload + Claude Code parity

After studying Claude Code's skills implementation, this PR now also:

  • Reloads skills live (their "live change detection"): skill directories
    are watched, so skills installed, edited, or deleted mid-session appear
    without a restart. This also fixes a bug the original PR had: the skills
    list was a frozen startup snapshot, so deleting from the panel never
    removed the row. The registry now refreshes with content diffing and a
    version that React subscribes to; the panel refreshes on open and after a
    delete. Agent runs already re-read skills from disk per run, so the model
    sees new skills immediately too.
  • Accepts Claude Code frontmatter fieldsuser-invocable: false
    (model-only skills are hidden from the / menu, as in Claude Code) and
    when_to_use (appended as trigger context in the agent's skill listing).
    argument-hint is tolerated. One real-world catch: Claude Code's docs
    write argument-hint: [issue-number] unquoted, which YAML parses as a
    list — we coerce it back to the display string rather than rejecting the
    whole skill.
  • Delete removes the skill's directory, matching Claude Code's "Remove a
    skill" semantics, so skills with supporting files (reference docs,
    scripts/) are not half-deleted.
  • Fixes the project/global badge to derive from the actual skill search
    paths (resolveSkillsDirs, now exported from the SDK) instead of a HOME
    string check that misclassified on Windows.

Verification: +11 tests (frontmatter parsing incl. the YAML coercion, XML
formatting, registry refresh/diff/version/watcher), all pass on Windows /
bun 1.3.14. CLI suite unchanged vs. baseline (only the pre-existing Windows
path-separator and file-locking failures).

Real-world compatibility check: installed the ponytail skill package
(github.com/DietrichGebert/ponytail — built for Claude Code / Codex /
Copilot) via npx skills add DietrichGebert/ponytail -g -a claude-code and
loaded all 6 of its skills through Freebuff's SDK loader
(ponytail-compat.test.ts): every skill parses with name/description/content
intact from ~/.claude/skills, no modification needed.

Update: panel v2 — token estimates + search filter

Two additions to the /skills panel, borrowed from Claude Code's panel where
they fit our complementary scope (theirs toggles enable/disable; ours
invokes, opens, and deletes — see thread below):

  • Token estimates per row. Each row now ends with a right-aligned
    ~token readout (~524 tok style, chars/4, k notation past 999) so you
    can see what each skill costs your context window before invoking.
    Deliberately an estimate — the point is relative weight, not an exact
    count. Helpers are renderer-free (skills-panel-format.ts) and
    unit-tested alongside the keymap, following the same pattern as
    skills-panel-actions.ts.

  • / search filter. Pressing / opens an inline query that filters
    the list by name/description as you type. While searching, letters edit
    the query — d, o, q fire no shortcuts underneath your typing —
    while arrows/enter still navigate and invoke. Escape unwinds one layer
    at a time: search mode → active filter → close the panel. A muted
    "N hidden by filter" line shows what the query excludes, and the footer
    hint switches to "esc clears filter · esc again closes" while filtered.

Live run on Windows 11 with the six ponytail skills installed via
npx skills add DietrichGebert/ponytail -g -a claude-code (the same ones
from the compat test in the previous update):

┌──── ▾ Skills — 6 loaded (0 project, 6 global) ────┐
│ ❯ global  Forces the laziest solution t… 1.9k tok │
│   global  Whole-repo audit for over-engi… 524 tok │
│   global  Harvest every `ponytail:` comm… 534 tok │
│   global  Show ponytail's measured impa…  545 tok │
│   global  Quick-reference card for all p… 765 tok │
│   global  Code review focused exclusiv…   721 tok │
│ / filter · Enter run · o open · d delete · esc close │
└───────────────────────────────────────────────────┘

typing:  / + audit  →  one row, "5 hidden by filter"
  • Test hygiene fix: the two skill-registry live-reload tests from the
    previous update failed on any machine with Claude Code skills installed —
    includeHomeSkills loaded the real ~/.claude/skills into the expected
    counts (7 != 1 with ponytail present). They now redirect HOME/USERPROFILE
    into the temp dir, making them hermetic for any developer's machine and
    CI.

Verification: +12 tests (search keymap incl. the escape-unwind chain,
token formatting, filter matcher, hermetic registry watcher), 25/25 across
the skills test files, typecheck clean, full CLI suite unchanged from the
documented pre-existing Windows baseline.

lab1207 and others added 6 commits September 6, 2026 12:51
cli/bunfig.toml lists test/setup-scm-loader.ts among its preloads, but the
file was never exported to the public mirror. Any test reaching the SDK
barrel (which re-exports code-map, which imports .scm tree-sitter query
files) threw "Unknown file type" at import time, which bun surfaces as an
unhandled error between tests — a fresh clone showed a wall of dead test
files with no obvious cause.

The plugin registers a bun loader that imports .scm files as a
default-exported string, matching what the bundled build does. Verified
against the CLI suite: 1,576 pass, with only the pre-existing Windows-path
failures in export-conversation.test.ts remaining (unrelated).

🤖 Generated with Codebuff
Co-Authored-By: Codebuff <noreply@codebuff.com>
Skills already load from ~/.agents/skills/ and .agents/skills/ and run as
/skill:<name>, but the CLI had no surface answering "what do I have?".
Project skills silently shadow global ones, and removing a skill meant
hunting down files by hand.

/skills (alias /skill) opens a panel cloning the queue-panel pattern:
- rows show each skill's source (project vs global) and description
- Enter invokes via the existing skill input mode (same path as
  /skill:<name>, shared through enterSkillMode, so entries can't drift)
- o opens the SKILL.md in $EDITOR; d deletes with confirmation
- empty registry prints install guidance (npx skills add ...) instead of
  opening an empty panel

The keymap lives in utils/skills-panel-actions.ts, testable without a
renderer like queue-panel-actions. Panel participates in the same
keyboard/dock arbitration as the queue panel (review, ask-user, sponsored
menu). One deviation from queue-panel: the confirm state swallows all
other keys, so a held d can't chain-delete rows.

Includes test/setup-scm-loader.ts (cherry-picked) — the preload
bunfig.toml references but the mirror lacked; without it, tests touching
the SDK barrel die at import.

Verified: new tests pass (9), CLI suite 1,586 pass / 12 fail, all 12
pre-existing Windows-path/locking failures present on main.

🤖 Generated with Codebuff
Co-Authored-By: Codebuff <noreply@codebuff.com>
Skills were frozen at startup: a skill installed mid-session was invisible
until restart, and deleting from the /skills panel never removed the row
because the component read a one-time useMemo snapshot. The registry now
refreshes with content diffing and a version number that React subscribes
to, skill directories are watched so changes land within the session, and
the panel refreshes on open and after a delete.

Frontmatter parity with Claude Code: user-invocable: false hides a skill
from the / menu, when_to_use is appended to the agent's skill listing, and
argument-hint is tolerated (coerced from the YAML list its unquoted docs
form parses into). Deleting a skill now removes its directory rather than
only SKILL.md, so supporting files are not orphaned, and the project/global
badge derives from resolveSkillsDirs (now exported from the SDK) instead of
a HOME string check that misclassified on Windows.

🤖 Generated with Codebuff
Co-Authored-By: Codebuff <noreply@codebuff.com>
The ponytail skills (built for Claude Code, Codex, and Copilot) are
installed by the skills CLI into ~/.claude/skills and must load in
Freebuff unmodified for the cross-platform promise to hold. The test runs
all six through the SDK loader and asserts name, description, content,
and source path survive. Skips where the package is not installed so CI
stays green.

🤖 Generated with Codebuff
Co-Authored-By: Codebuff <noreply@codebuff.com>
Rows now show a right-aligned ~token estimate (chars/4, k-abbreviated past
999) so users can see what each skill costs their context, and `/` enters a
search mode that filters by name/description while keeping arrows/enter
live; letters type instead of firing shortcuts, esc exits, backspace on an
empty query exits too. Helpers live in renderer-free skills-panel-format.ts.

Also fixes the two skill-registry live-reload tests, which passed only on
machines without Claude Code skills: includeHomeSkills loaded the real
~/.claude/skills into the counts (7 != 1 with ponytail installed). Tests now
redirect HOME/USERPROFILE into the tmp dir, making them hermetic.

🤖 Generated with Codebuff
Co-Authored-By: Codebuff <noreply@codebuff.com>
The footer promised "esc clears" the filter, but while browsing escape
closed the panel — the filter could only be cleared by backspacing in
search mode. Escape now unwinds one layer at a time: search mode → active
filter → close, with a footer hint that changes to "esc clears filter ·
esc again closes" while a filter is active. q and ctrl+c still close
directly.

🤖 Generated with Codebuff
Co-Authored-By: Codebuff <noreply@codebuff.com>
@codebuff-team

Copy link
Copy Markdown
Contributor

The core idea — a /skills panel that mirrors the queue-panel architecture (skills-panel-store.ts, skills-panel-actions.ts, skills-panel.tsx) — is well-factored: the keymap resolver is renderer-free and unit-tested (skills-panel-actions.test.ts), and reusing enterSkillMode for the Enter action correctly avoids duplicating the invoke path. That part alone (~600 lines) would be reviewable and worth porting.

But the PR grew mid-flight (see the "Update" section in the body) to also add a filesystem watcher for live skill reload, which is a second, independently-testable feature bolted onto the first. Two concerns there:

  1. startSkillDirWatcher in skill-registry.ts calls watch(dir, { persistent: false }, ...) without { recursive: true }. Skills live at <skillsDir>/<name>/SKILL.md — a subdirectory of the watched path. Non-recursive fs.watch on Linux only fires for changes to entries directly inside the watched directory (new/removed subdirectories), not for edits to files nested one level down inside an existing subdirectory. So the claimed "edited" case in "installed, edited, or deleted" is very likely silently broken on Linux, and the included test (watcher picks up an install without restart) only exercises the create-new-directory case, which is exactly the case that still works without recursive: true. This needs a real edit-in-place test and, likely, a recursive watch or a supplementary poll/re-stat.

  2. ponytail-compat.test.ts depends on an external, personally-installed skill repo and describe.skipIf(!installed)s itself out in CI. It provides no CI signal and bakes in a private dependency and file layout assumption not in this repo — this doesn't belong in the PR regardless of the panel's quality.

At 1615/-16 across 20 files with two feature sets stitched together, this is too large a unit for a maintainer to safely port and review as one change. Recommend splitting into (a) the panel/store/keymap PR, tested as it is, and (b) a live-reload PR with a real recursive-watch fix and an edit-in-place test.

@codebuff-team codebuff-team added bot:triaged Classified by the community triage bot pr:needs-work Right idea, not mergeable as written labels Sep 6, 2026
Per review on CodebuffAI#1289: the filesystem watcher is an independently
reviewable feature and moves to its own PR (recursive-watch semantics
differ per platform). The panel keeps its refresh-on-open and
refresh-after-delete behavior, which covers installs and edits without
any watcher. Also drops ponytail-compat.test.ts (external dependency,
no CI signal).

🤖 Generated with Codebuff
Co-Authored-By: Codebuff <noreply@codebuff.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bot:triaged Classified by the community triage bot pr:needs-work Right idea, not mergeable as written

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants