Skip to content

feat(opencode): install a whole Skill Stack with codev skill stack - #361

Merged
quickbeard merged 1 commit into
mainfrom
feat/skill-stacks
Sep 14, 2026
Merged

quickbeard merged 1 commit into
mainfrom
feat/skill-stacks

Conversation

@quickbeard

Copy link
Copy Markdown
Owner

P0 of the Skill Stack integration: the @codev/skillhub package layer plus the CLI surface. Server endpoints (P1), the TUI dialog (P2) and the desktop Stacks tab (P3) build on this and land separately.

What a Skill Stack is

A public namespace on the hub. GET /api/v1/explore/namespaces lists them; GET /api/v1/namespaces/{slug}/download-all serves one composite ZIP — {slug}/README.md plus {slug}/<skill-name>/… per PUBLIC skill, each skill's own root already stripped server-side. Neither codevhub nor the hub's own CLI has a stack command, so this is new capability rather than a port.

What this adds

codev skill stack list [query] [--limit] [--json]
codev skill stack pull <slug> [--here|--global|--dir] [--agent|--all-agents]
                              [--overwrite|--skip-existing] [--json]

Skills land in exactly the same .claude/skills + .agents/skills folders skill pull writes — one real copy plus links for the other agents, same agent picker, CoDev Code always included. Interactive runs preview the archive's real contents before asking where to put it.

Conflicts are asked about, never guessed. When skills in the stack are already installed, the interactive flow lists them and offers skip-vs-overwrite; the non-interactive path stops and names them unless --skip-existing or --overwrite was passed.

A stack is N installs, not a transaction. A skill that fails to install is reported by name and the rest still land (exit 1); a bundle folder with no SKILL.md is listed as excluded rather than dropped silently.

Implementation notes

  • The extract-and-link half of installResolvedSkill moves into a shared placeSkill(). Both paths now share one copy of the conflict/rm ordering, the zip-slip guard and the Claude-symlink-floor fallback. The single-skill path is behavior-identical — including still refusing an unsafe name before downloading anything.
  • Each skill's subtree is lifted straight out of the composite archive via a new extractEntries(entries, dir, { strip }), so nothing is re-zipped.
  • claude --version is probed once per stack rather than once per link.
  • Archive caps are parameterized: STACK_LIMITS (500 MB / 50k entries) for the bundle, SKILL_LIMITS (100 MB / 15k) still enforced per skill inside it, so one skill can't spend the stack's budget.
  • download-all gets a 300s timeout — the shared 15s JSON-handshake default would fail on any real stack.
  • findStack(slug) resolves a name for the prompt via the public listing and returns null rather than throwing, because a private stack the user can see is absent from that listing but still downloads.

Hub-side issue found while testing

skill-create-and-review advertises 3 skills; the archive carries 2. Two distinct skill records in that namespace share the name skill-creator, and downloadAllSkills keys bundle folders by skill.name, so one silently overwrites the other. The same gap appears for a PUBLIC skill with a null zipUrl — counted in the listing, absent from the ZIP. The interactive flow now says so (the hub lists N skill(s) here, but the archive carries M) rather than installing quietly fewer than advertised. Worth a hub-side fix; nothing here depends on it.

Verification

  • packages/codev-skillhub: 243 pass / 0 fail (49 new across stack, stack-install, stack-search), bun typecheck clean.
  • packages/opencode: bun test test/cli 391 pass / 0 fail, bun typecheck clean, help snapshots regenerated (+3 commands).
  • Exercised live against the hub: stack list, stack pull --dir, the conflict refusal, --skip-existing, and --global --all-agents (verified one real copy in .agents/skills with relative symlinks from .claude/skills).
  • DIVERGENCES.md row 110 updated (skill subcommand list); CHANGELOG.md Unreleased → Skills.

🤖 Generated with Claude Code

https://claude.ai/code/session_01P1MfYkJD3tQZRgqBKB4gig

A Skill Stack is a public namespace on the hub — a team's curated skill
set, served as one composite ZIP. `codev skill stack list` browses them
and `codev skill stack pull <slug>` installs every skill in one into the
same .claude/skills + .agents/skills folders `skill pull` writes, with
the same agent picker and one real copy plus links.

The extract-and-link half of installResolvedSkill moves into a shared
placeSkill(), so a stack reuses the single-skill conflict ordering, zip
guards and Claude-symlink fallback rather than growing a second copy of
them. The stack lifts each skill's subtree straight out of the composite
archive instead of re-zipping it, and probes `claude --version` once for
the whole bundle.

A stack is N installs, not a transaction: a skill that fails is reported
by name and the rest still land, a folder with no SKILL.md is listed as
excluded, and the interactive flow says when the hub's advertised skill
count exceeds what the archive actually carries (the hub keys bundle
folders by skill name, so two same-named skills in a namespace collapse
into one).

Archive caps are parameterized: a stack gets its own ceiling (500 MB /
50k entries) while each skill inside it still gets the 100 MB / 15k one.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01P1MfYkJD3tQZRgqBKB4gig
@quickbeard
quickbeard merged commit df19d3d into main Sep 14, 2026
0 of 5 checks passed
@quickbeard
quickbeard deleted the feat/skill-stacks branch September 14, 2026 01:39
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