#2722

feat(add): install a skill under a new name with --rename

Openantfuwants to merge intofrom

Summary

Two sources can publish skills with the same name, and installing the second one silently replaces the first. Until now the only workaround was renaming the folder by hand and deleting the lock entry, which stops updates.

skills add <source> --skill <name> --rename <new-name> installs one skill under a new name:

  • The installed folder, the lock key, and the name in the installed SKILL.md all use the new name, so agents invoke it by that name. The source repo or local path is never modified.
  • The lock entry (project and global) records the source name in a new optional skillName field. Lock files without the field read as before.
  • update, check, experimental_install (including --frozen-lockfile) and experimental_sync find the skill upstream by skillName and reinstall it under the lock key, so the new name survives.
  • list shows (renamed from <name>); list --json and the API's listSkills report renamedFrom.
  • The API takes a map, so one call can rename several skills: installSkills(source, { rename: { shadcn: 'shadcn-ui', button: 'ui-button' } }). Skills not in the map install as-is. Keys must match a skill being installed, and no two skills may end up with one name; both are checked before anything is written.
  • Install telemetry and the security audit keep using the source name.
npx skills add vercel-labs/json-render --skill shadcn --rename json-render-shadcn
// skills-lock.json
"json-render-shadcn": {
  "source": "vercel-labs/json-render",
  "sourceType": "github",
  "skillPath": "skills/shadcn/SKILL.md",
  "skillName": "shadcn",
  "computedHash": "..."
}

--rename applies to exactly one selected skill (new diagnostic SKILLS_RENAME_MULTIPLE_SKILLS). The name must already be a safe folder name (sanitizeName(name) === name); otherwise it fails with a suggested name. This keeps the lock key, the folder, and SKILL.md identical.

In short: you can install a skill under your own name, and every command that reads the lock keeps using that name.

  • refs #1802
  • refs #1906
  • refs #2394
  • Supersedes the closed #369, which predates skills-lock.json restore, sync, and the API.

Change map

ModuleBeforeAfter
add.tsSkills install under their own namerenameSkill() sets name to the new name and renamedFrom to the source name; installToTargets rewrites the installed SKILL.md; every lock writer adds skillName
installer.ts, frontmatter.ts-setInstalledSkillName() / setFrontmatterName() replace only the frontmatter name line; symlinks are skipped, so link targets (sync's link mode, the user's source) are never written
update.ts, skill-relocation.tsUpstream skills matched by lock keyMatched by skillName ?? key for git, well-known and download sources, both scopes; reinstalled under the key only when the entry has skillName
install.tsRestore requests lock keysRequests source names and installs each found skill under every lock key that points at it (the same skill can be installed as-is and renamed)
sync.tsRemote requests matched by keyMatched by source name; a sync-managed renamed entry is reinstalled under its key
list.ts, api/-renamedFrom in list output, installSkills({ rename: { [sourceName]: newName } }), InstalledSkillInfo.renamedFrom

Boundaries and risks

InvariantProtectionEvidence
Entries never renamed behave exactly as beforeUpdate only renames when the lock entry has skillName; restore only passes select when a group has a renamed entryExisting update and install tests unchanged and passing
The user's source is never rewrittenSymlinks are skipped; a skipped result without a reason (source is the destination) is not rewrittenrename.test.ts: the source SKILL.md keeps name: alpha after a symlink-mode install
The lock hash tracks upstream contentcomputedHash is hashed from the source folder, before the rewriteupdate reports "All project skills are up to date" right after an update (rename.test.ts)
Public API changes are additive onlytsnapi snapshotSnapshot diff only adds rename (a map), renamedFrom, and SKILLS_RENAME_MULTIPLE_SKILLS (CLI only)

Rollout and follow-up

None of these block merge:

  • A renamed skill from a skills.sh pack installs from the pack's own copy, not from the original repo the pack points at.
  • Skills shipped by npm packages (node_modules) cannot be renamed; --rename only applies to add.
  • Global update with a rename is covered by a mocked unit test only. add -g from a local file:// repo writes no global lock entry (existing behavior), so a real end-to-end global run needs a GitHub source.
  • Found while testing, not changed here: restoring a file:// git source from skills-lock.json fails because the skill folder gets appended to the clone URL. It fails the same way without --rename, so the restore tests use a local-path source.

User prompts

  • Model: claude-opus-5-5, harness: Claude Code (in Paseo)
  • Asked whether skills can be renamed on install, with the rename stored in the lockfile and respected by install, sync, update, list, and so on, and whether there were existing PRs or issues.
  • Chose the flag name --rename, no prefix option in this PR, and to build it independently of #1914.
  • Asked to create a PR, then to resolve merge conflicts with main.
  • Asked for installSkills to support renaming several skills at once (suggested a map).

Verification

  • pnpm type-check: passes (also at the first commit on its own).
  • pnpm format:check: passes.
  • pnpm build: passes; the tsnapi snapshot was updated with pnpm api:update (additive diff only).
  • pnpm vitest run tests/rename.test.ts: 10 passed. Real git repos through the real CLI: add (copy and symlink modes), check, project update with an upstream change, restore, --frozen-lockfile restore, the same skill installed as-is and renamed, list / list --json, remove, and both error cases.
  • pnpm vitest run src/frontmatter.test.ts src/skill-relocation.test.ts tests/update.test.ts tests/api.test.ts: all passed (new: frontmatter rewrite cases, relocation by skillName, global update with a rename, API rename map: one and several renames, swapping two names, renamedFrom, and each error case).
  • pnpm test (after merging main): 1330 passed, 4 failed. The 4 failures (src/detect-agent.test.ts x3 for Cursor detection, src/use.test.ts single local skill prompt) fail the same way on a clean checkout of c413b44 in this environment, likely because the tests run inside an agent session. Not related to this change.
  • Not verified: an end-to-end update against a live GitHub repo or a well-known endpoint (those paths are covered by unit tests only).

Written with an AI agent (Claude Code). A human gave the task and made the design choices above; the diff has not been reviewed by a human yet.