vlt / docs

  • PricingBenchmarks (opens in new window)Community (opens in new window)Feedback
  • Overview
  • bugs
  • build
  • cache
  • ci
  • config
  • create
  • docs
  • exec-cache
  • exec-local
  • exec
  • help
  • init
  • install
  • list
  • login
  • logout
  • outdated
  • pack
  • ping
  • pkg
  • publish
  • query
  • registry
  • repo
  • run-exec
  • run
  • setup
  • skills
  • token
  • uninstall
  • version
  • view
  • whoami
  • How vlt finds skills
  • Which packages
  • Where in a package
  • What counts as a skill
  • Shipping skills in a package
  • Same name in different packages
  • A skill isn't found
  • Where skills are linked
  • skills-dir
  • Symlinked dirs
  • Pick skills
  • Layout
  • Subcommands
  • list
  • link
  • Linking on install
  • Safety
  • Ignoring linked skills in git
  • Limits
  1. Client
  2. /
  3. Commands
  4. /
  5. skills

vlt skills

Usage:

Terminal
$ vlt skills [list|link] [<query>] [--view=human|json|mermaid]

Manage the agent skills shipped by your installed dependencies.

An agent skill is a folder with a SKILL.md file, as defined by the Agent Skills specification. Packages can ship them alongside their code, and vlt skills links them into your project's agent skill dirs so coding agents can find them.

How vlt finds skills

vlt only looks in installed packages, it never downloads skills:

  1. Install the package as usual, e.g. vlt install agent-browser.
  2. A DSS query picks the packages to look in: allow-skills on install, or the query given to vlt skills (* for list by default).
  3. vlt scans each picked package for skills (below), then keeps the ones :skill() picks.
  4. Each skill is linked into the project's agent skill dirs, named after the skill. Agents read skills from these dirs.

vlt skills list shows what vlt finds, and vlt query ':skills' lists the packages that ship skills.

Which packages

  • Packages installed in the node_modules/.vlt store, direct or transitive.
  • Not the project or its workspaces, and not file: dependencies (folders and tarballs).
  • One version per package: the one the project depends on directly, else a workspace's, else the highest version, even if it ships no skills. Different origins (e.g. registry and git) are different packages.

Where in a package

vlt looks for skills in each package, in this order:

  1. SKILL.md at the package root – a single skill, named after the name field of its frontmatter (or the package name, without its scope)
  2. skills/<name>/SKILL.md – one folder per skill, named <name>
  3. dist/skills/<name>/SKILL.md – same, for built packages
  4. .agents/skills/<name>/SKILL.md – same

A package can ship skills in several of these places. When the same skill name (in any case) is in more than one, the first one in this order is used.

What counts as a skill

  • A folder with a SKILL.md file, directly in one of the dirs above: skills/group/<name>/SKILL.md is not found.
  • Folder names use letters, digits, ., _ and -, and start with a letter or digit. Other folders, and one named SKILL.md, are skipped.
  • Symlinks are not followed: a symlinked skill folder or SKILL.md, or a symlinked dir on the way (skills/, dist/, dist/skills/, .agents/, .agents/skills/), is skipped, so a skill always lives inside its package.
  • A root SKILL.md is named after its frontmatter name when that is a valid skill name (lowercase letters, digits and single -, up to 64 characters), else after the package.
  • vlt reads name and description from the SKILL.md frontmatter. A missing or invalid frontmatter doesn't hide the skill. :skill() matches the skill name or the frontmatter name.

Shipping skills in a package

Layout
my-tool/
├── package.json
├── dist/
└── skills/
  └── my-skill/
      └── SKILL.md
  • Publish the skills: if package.json has files, add the skill dirs (or a root SKILL.md) to it. Check with vlt pack --dry-run.
  • A package that is a single skill can put SKILL.md at its root.
  • Skills built into dist/skills/ are found too.

Same name in different packages

When packages ship skills with the same name, one gets the link in every dir: the package an existing link points to, while that skill is installed, else a direct dependency, then the package id. The others are reported. Installs keep a dir's link to another installed skill; vlt skills link re-points it. Narrowing the query and running vlt skills link <query> frees a name. A fresh clone (no links yet) may pick another package than a machine with older links. :skill() applies first: a skill the query doesn't pick never takes a name.

A skill isn't found

  • vlt skills list "#<package>" lists what vlt finds in it.
  • Check the package is installed from a registry, git or a remote tarball, not a workspace or file: dependency.
  • Check the layout and folder name rules above.
  • vlt skills link and installs warn about a :skill() pattern that matches nothing, listing the skills its packages ship.
  • With a security selector such as :not(:malware), packages with no security data are left out and reported. See Safety.

Where skills are linked

Unless skills-dir is set, skills are linked into:

  • .agents/skills, always. Codex, Cursor, GitHub Copilot, Gemini CLI and many other agents read it.
  • <folder>/skills for each agent folder at the project root: .claude/ gives .claude/skills, .kiro/ gives .kiro/skills, and so on (.tabnine/ gives .tabnine/agent/skills, .posit/assistant/ gives .posit/assistant/skills). Missing dirs are created. Only the project root is checked, not workspaces.
  • .claude/skills when run inside Claude Code (CLAUDECODE is set), even without a .claude/ folder. Once created, .claude/ keeps it a target outside Claude Code too.
  • Any existing .<name>/skills dir, such as .cursor/skills or .github/skills. These are never created.

.github/ alone is not an agent folder. Home dirs (~/.claude) are not read, so in a plain terminal a project with no .claude/ gets .agents/skills only: run mkdir .claude, or set skills-dir.

Dirs are created only when the query picks a skill.

skills-dir

Set skills-dir to a list of dirs, relative to the project root, to replace detection:

Terminal
# link into .claude/skills only
$ vlt config set skills-dir=.claude/skills
# or for one install
$ vlt install --allow-skills="*" --skills-dir=.claude/skills
  • A dir outside the project or in node_modules is an error; one that links outside is skipped.
  • [] in vlt.json (or --skills-dir=) links nothing.
  • null in vlt.json goes back to detection.
  • This is also how to drop .agents/skills or an unwanted agent dir.

Links in a dir that is no longer a target stay until the next vlt skills link, which removes them. A custom dir dropped from skills-dir (not named .<name>/skills) is no longer scanned: remove its links by hand.

Symlinked dirs

A symlinked dir or agent folder is followed when it points inside the project, and skipped and reported when it points outside or into node_modules. So is a dir that can't be read or created. A dir reached twice (e.g. .claude/skills -> ../.agents/skills) gets each link once.

Pick skills

End a selector with :skill(<name>, …) to link only some of its packages' skills:

Terminal
# one skill of one package
$ vlt skills link "#skills:skill(find-skills)"
$ vlt skills link '#skills:skill("find-skills")'
# some skills of every direct dependency
$ vlt skills link ":root > *:skill(performance, find-skills)"
# globs
$ vlt skills link ":root > *:skill(vercel*, *-review)"
# any package, same as *:skill(find-skills)
$ vlt skills link ":skill(find-skills)"
  • A pattern matches the skill name (its folder name, or the root SKILL.md name) or the name in its SKILL.md frontmatter, in any case. * matches any run of characters. Quote names with spaces: :skill("my skill").
  • A selector without :skill() picks every skill of its packages, and comma lists are a union. So :root > *, #skills:skill(find-skills) links every skill of every direct dependency, skills included. To narrow one package, leave it out of the other selectors: :root > *:not(#skills), #skills:skill(find-skills).
  • :skill() must end a top-level selector: #a:skill(x) > * is an error.
  • :skills (no ()) matches packages that ship skills. :skills(x) is an error (did you mean :skill(x)?) on its own, and matches nothing inside a comma list.
  • A pattern that matches no skill of its packages prints a warning listing the skills they ship. The install still succeeds.

Preview a query with vlt skills list '<query>' before link saves it. See :skill().

Layout

Each skill is linked into every dir, named after the skill:

Layout
.agents/skills/
├── lint-rules -> ../../node_modules/.vlt/~npm~foo@1.0.0/node_modules/foo/skills/lint-rules
└── bar -> ../../node_modules/.vlt/~npm~@scope+bar@2.0.0/node_modules/@scope/bar
.claude/skills/
├── lint-rules -> ../../node_modules/.vlt/~npm~foo@1.0.0/node_modules/foo/skills/lint-rules
└── bar -> ../../node_modules/.vlt/~npm~@scope+bar@2.0.0/node_modules/@scope/bar

On Windows, links are directory junctions. Junctions hold absolute paths, so after moving or copying the project, run vlt skills link <query> (or vlt install with allow-skills set) to re-point them.

Subcommands

list

The default. Lists the skills of installed packages as a tree, like vlt ls: <skill> (<package>), marked (linked) when linked in every agent skill dir, (linked in <dirs>) when only in some. With colors on, linked skills are highlighted like the packages vlt query selects. Warnings follow the tree. Selects all packages (*) by default and applies :skill(). Creates and saves nothing.

Terminal
$ vlt skills
my-project
├── find-skills (skills@1.7.2) (linked)
└── get-tsconfig (get-tsconfig@5.0.0-beta.6)
$ vlt skills list ":root > *"
$ vlt skills list "#skills:skill(find-*)"
  • --view=json: each skill's name is the skill, then its description, dependency-name the dependency it is installed as, package-id its package id, path and the dirs it is linked in; dirs lists the agent skill dirs.
  • --view=mermaid: a Mermaid flowchart of the dependency graph, as vlt query --view=mermaid draws it, from the project down to each package with skills, its skills as highlighted leaves. Warnings follow as %% comments. List only.
Terminal
$ vlt skills list --view=mermaid
flowchart TD
a("root:my-project")
a -->|"skills#64;github:vercel-labs/skills"| b("git(github:vercel-labs/skills):skills#64;1.7.2")
b -->|"skills#64;1.7.2"| f("find-skills (linked)"):::b
b -->|"obuild#64;^0.4.22 (dev)"| c("npm:obuild#64;0.4.45")
c -->|"rolldown-plugin-dts#64;^0.28.8"| d("npm:rolldown-plugin-dts#64;0.28.8")
d -->|"get-tsconfig#64;5.0.0-beta.6"| e("npm:get-tsconfig#64;5.0.0-beta.6")
e -->|"get-tsconfig#64;5.0.0-beta.6"| g("get-tsconfig"):::b
classDef b fill:turquoise,color:#242424

link

Sets, saves and applies allow-skills. Links what the query selects and removes vlt links to other skills in every scanned dir: skills the query doesn't pick, skills that are gone, and links in dirs that are no longer targets. It is the only command that removes links to installed skills. With no query, it uses the stored allow-skills, else :not(:malware), like vlt build.

Then it saves the query when it changed: to the project vlt.json, or to the user one with --config=user. A query in the other file is never deleted: a project value shadows the user one.

Query givenStored-yTTYResult
yesnoanyanylink it, save it
yesyesnoyesReplace it? [y/N]: y opens the editor with the query, N changes nothing
yesyesyesanylink it, save it
noyesnoyesEdit it? [y/N]: y opens the editor with the stored query, N links it
noyesyesanylink the stored query
nononoyesthe editor, prefilled with :not(:malware)
nonoyesanylink :not(:malware), save it
yesyesnonoerror: pass -y to replace it
noyesnonolink the stored query
nonononolink :not(:malware), save it

A query equal to the stored one links without asking or writing.

Terminal
$ vlt skills link ":root > *:skill(find-skills)"
allow-skills is already set for this project (vlt.json): ":root > *". Replace it? [y/N] y

⚠️ The new allow-skills replaces the current one. Make sure it still
selects every skill you want: skills it no longer selects are unlinked.

Current allow-skills: :root > *
New allow-skills: :root > *:skill(find-skills)
1 agent skill already linked.
Saved allow-skills=":root > *:skill(find-skills)" to vlt.json

In the editor, the new query is prefilled: edit it and press Enter. An empty answer cancels with no changes, and an invalid query prints the error and asks again. -y skips the prompts. Current and New are colored when colors are on.

Terminal
# a single package, no prompts
$ vlt skills link "#my-package" -y
# save to the user vlt.json
$ vlt skills link "#my-package" --config=user
# remove every skill link
$ vlt skills link ":not(*)" -y

Select packages with a DSS query after the subcommand, or with --target=<query>, which wins over the positional argument.

Add skill packages one at a time:

Terminal
$ vlt install github:vercel-labs/skills
$ vlt skills link "#skills:skill(find-skills)"   # saved to vlt.json
$ vlt install agent-skills@github:vercel-labs/agent-skills
$ vlt skills link
# Edit it? y
# New allow-skills: #skills:skill(find-skills), #agent-skills:skill(react*)
# on another machine, both are linked
$ vlt ci

Linking on install

With allow-skills unset (the default) or :not(*), vlt install, vlt ci, vlt update and vlt uninstall do no skills work at all: nothing is scanned, created or removed, and leftover links stay until vlt skills link.

With allow-skills set, on the command line or in vlt.json, they link what it selects after a successful install:

Terminal
# link, and save the query to vlt.json
$ vlt install --allow-skills="#agent-browser:skill(agent-browser)"
Saved allow-skills="#agent-browser:skill(agent-browser)" to vlt.json
  • Installs only add links. They re-point a link to the new version of its package, in every dir, and remove links whose skill is gone: dangling, package removed, or skill no longer shipped. They never remove links to installed skills: after narrowing allow-skills, run vlt skills link.
  • vlt install and vlt update save --allow-skills=<query> to the project vlt.json. When another query is stored (:not(*) too), it is kept and a hint says so: change it with vlt skills link.
  • vlt ci never writes config: in a fresh clone, it links what vlt.json selects.
  • --lockfile-only links and saves nothing.
  • vlt uninstall with a stored query removes the removed package's links.
  • Dir changes take effect for new links; old ones go on the next vlt skills link. An invalid query or skills-dir fails before anything is installed.

Safety

  • vlt only ever replaces or removes links that point into a node_modules/.vlt store. Anything else at a link path (your own skills, other tools' copies or links) is skipped and reported for that dir, never changed.
  • Dirs are never removed.
  • Security queries fail closed, like allow-scripts: with a security selector such as :not(:malware), skills of npm registry packages with no security data (the request timed out, or Socket has not scanned them yet) are not linked and are reported. Installs keep their existing links, vlt skills link removes them: try again later. See Security.
  • Linked skills are views of installed package files: don't edit them, a reinstall replaces them. Files hardlinked from the global store get private copies when linked, so edits stay in this project.

Ignoring linked skills in git

Links dangle in a fresh clone until install, so vlt keeps their names in a marked block of each dir's .gitignore:

.claude/skills/.gitignore
# >>> vlt agent skills (links into node_modules)
/bar
/lint-rules
# <<< vlt agent skills

Lines are added for new links and removed with them, or when your own entry takes the name. The rest of the file is kept, and the file is never deleted: committed, it keeps the dir in a fresh clone. A dir with no vlt links is never touched. A .gitignore that is a symlink, not a file or can't be written is left alone and reported.

Limits

  • Skills nested deeper than <dir>/<name>/SKILL.md are not found.
  • Skills are linked at the project root only, not per workspace.

PrevioussetupNexttoken

On this page

  • How vlt finds skills
  • Which packages
  • Where in a package
  • What counts as a skill
  • Shipping skills in a package
  • Same name in different packages
  • A skill isn't found
  • Where skills are linked
  • skills-dir
  • Symlinked dirs
  • Pick skills
  • Layout
  • Subcommands
  • list
  • link
  • Linking on install
  • Safety
  • Ignoring linked skills in git
  • Limits
Edit this page

Deploy your package on vlt.io

Publish scoped and private packages, manage organizations and access, and give every developer and CI environment a consistent source for public and private JavaScript dependencies.

Publish now