Skip to content

Shipping skills

The framework can ship Claude Code skills (like /populate-site) to every client repo. Skills are authored once in the @drawnagency/authoring package and linked into each client repo’s .claude/skills/ automatically on install. This page explains the packaging and linking mechanism, and how to add a new skill.

Only one package ships skills: @drawnagency/authoring. They live as plain directories under packages/authoring/skills/ — each a folder containing a SKILL.md (with the standard name / description frontmatter) plus any helper files. They’re published in the npm tarball because the package’s package.json lists the directory in files:

"files": ["dist", "skills", "scripts"]

skills/ is the skill content; scripts/ holds the linker. Note the exports map does not expose skills/ — skills aren’t JS modules, they’re read off disk by the linker.

Today the package ships one skill, /populate-site (see Populating content).

A client repo depends on @drawnagency/authoring and runs the linker from its postinstall hook:

"postinstall": "node node_modules/@drawnagency/authoring/scripts/link-skills.mjs || true"

link-skills.mjs symlinks every skill the package ships into the repo’s .claude/skills/:

node_modules/@drawnagency/authoring/skills/<name> → .claude/skills/<name>

Claude Code then discovers each skill in .claude/skills/. The end-to-end chain:

  1. A repo created from the template depends on @drawnagency/authoring.
  2. pnpm install runs the postinstall.
  3. link-skills.mjs symlinks skills/populate-site.claude/skills/populate-site.
  4. Claude Code picks up /populate-site.

The generated symlinks are gitignored (.claude/skills/) and regenerated on every install — never committed.

link-skills.mjs is written to be safe to run on every install. Its load-bearing behaviors:

  • Idempotent. A symlink already pointing at the right target is a no-op; a stale or incorrect one is replaced.
  • Prunes removed skills — carefully. A skill the package no longer ships has its link removed, but only if that entry is a managed symlink pointing back into the package’s skills/ directory. Real directories — a copy-fallback’d skill, or one you hand-authored — are never deleted.
  • Never clobbers your own skills. If a real file or directory already occupies a target name, the linker skips it with a warning instead of overwriting.
  • Copy fallback. On platforms without working symlinks it falls back to a recursive copy. A copied skill won’t auto-refresh on the next install and isn’t pruned (it can’t be told apart from a user directory) — acceptable, since supported platforms have working symlinks.
  • Never fails the install. Any error is caught and logged, and the script exits 0 (the postinstall is also suffixed with || true). A skill-linking problem will never break pnpm install.

A CI guard (scripts/validate-template.mjs) enforces the contract: the template must depend on @drawnagency/authoring, its postinstall must run link-skills.mjs, and .gitignore must ignore .claude/skills/.

Skills are added to the framework package, not to individual client repos:

  1. Create the skill in the authoring package: packages/authoring/skills/<your-skill>/SKILL.md, with the standard skill frontmatter (name, description). Put any helper files the skill needs alongside it.
  2. No package.json change is neededfiles: ["dist", "skills", "scripts"] already includes the whole skills/ directory in the published tarball.
  3. Publish a new @drawnagency/authoring (see Publishing packages).
  4. Client repos pick it up on their next pnpm install — the linker symlinks the new skill into .claude/skills/. Renamed or removed skills are pruned automatically on the next install.

During local development, because skills are symlinked (not copied) on supported platforms, editing a skill’s SKILL.md under node_modules/@drawnagency/authoring/skills/ is reflected immediately in .claude/skills/.