zidane/ skills
Discover, parse, define and install Agent Skills, the instruction packages a model loads on demand.
import { … } from 'zidane/skills'src/skills/index.ts
Use this entry point to work with skills outside the default agent setup. defineSkill builds an inline skill, discoverSkills and resolveSkills scan directories, and the parser, validator and writer functions handle SKILL.md files. The allowed-tools helpers and installSkillFromSpec cover tool gating and installing a skill from a source. See Skills for the guide.
import { createAgent } from 'zidane'
import { anthropic } from 'zidane/providers/anthropic'
import { defineSkill } from 'zidane/skills'
const review = defineSkill({
name: 'review',
description: 'Review a diff for bugs. Use when the user asks for a code review.',
instructions: 'Read the diff, list the bugs you find by file and line, and skip style nits.',
})
const agent = createAgent({ provider: anthropic(), skills: { catalog: [review] } })
console.log(await agent.run({ prompt: 'Review my last commit.' }))Functions#
browseSkillSourceFunction#
Lists the skills of a source, with what installs each one.
buildCatalogFunction#
Build the skill catalog XML and behavioral instructions for the system prompt.
clearSkillSourceCacheFunction#
Forgets every cached listing.
createSkillActivationStateFunction#
Creates an empty skill activation state for one agent.
defineSkillFunction#
Define an inline skill configuration.
Strict-validates at author-time — throws if the config would be rejected by
writeSkillToDisk or the spec. Source defaults to 'inline'.
discoverSkillsFunction#
Discover skills from sourced filesystem paths. Each path is scanned for subdirectories containing SKILL.md. Earlier paths have higher priority (first-found wins on name collision).
signal is checked between each parseSkillFile so a rapid cwd switch
in the host (TUI / GUI) can stop a long scan without leaking I/O. Aborts
reject with the signal's reason (or a default AbortError) — same shape
AbortSignal.throwIfAborted() produces in modern runtimes.
getDefaultScanPathsFunction#
Return the default scan paths tagged by source. Project-scope paths come first; their skills therefore win on name collisions against user-scope skills (first-found wins in discovery).
githubAvatarUrlFunction#
The avatar of a GitHub user or organization. GitHub redirects it to its avatar store, which takes no organization names itself.
githubPreviewImageUrlFunction#
The preview image that GitHub draws for links to a repository: 1200 by 600 pixels.
groupSkillPicksFunction#
Picks grouped by the spec that installs them, in pick order, so each source is fetched once.
inferSourceFunction#
Infer a source tag for a user-provided scan path.
Paths under $HOME are treated as 'user'; everything else as 'project'.
installAllowedToolsGateFunction#
Register tool:gate / mcp:tool:gate handlers that enforce the union of
allowedTools across active skills.
No-op when no active skill declares an allow-list (permissive default —
matches the spec's "experimental" note for allowed-tools).
Returns an uninstall fn. The agent calls this at run end to detach the
handlers and prevent cross-run hook leaks.
installSkillFromSpecFunction#
Install a skill (or one skill out of a multi-skill source).
Engine selection: targeted installs (an explicit skill selector) go
through the skills.sh CLI when present on PATH (unless
preferSkillsCli: false) — keeping their cross-agent layout and
lockfile — else the native fetch pipeline. Selector-less installs are
ALWAYS native: only native enumeration can tell single- from
multi-skill sources, and the host's "import all?" prompt rides on the
multiple-candidates error — delegating them to skills add … -y
would let the CLI's own multi-skill behavior silently decide instead.
all: true batches are native for the same reason (skip-existing +
per-skill provenance are our semantics).
interpolateShellCommandsFunction#
Interpolate shell commands in skill instructions.
Runs each !command through the execution context and replaces
the placeholder with the command's stdout. If a command fails,
the placeholder is replaced with an error message (and
options.onFailure is notified when provided).
isToolAllowedByUnionFunction#
Test whether a tool call is allowed by the union of allowedTools across a set
of active skills. Returns true when the union is empty (permissive default)
OR when any entry matches.
listSkillCandidatesFunction#
List installable skills inside a fetched tree.
A skill is ANY directory containing a SKILL.md — found by a
bounded recursive walk (depth CANDIDATE_WALK_MAX_DEPTH),
skipping dot-dirs and node_modules, and never descending INTO a
skill (its references/ / scripts/ subtree ships with it, it
doesn't nest further candidates). A root-level SKILL.md means
"repo IS the skill" and short-circuits, named from the fallback.
Candidates carry their root-relative path (rel) so same-named
skills in different categories stay individually addressable.
Sorted by rel.
matchesAllowedToolFunction#
Check whether a tool call (identified by its wire/displayName and argument input) matches a skill's allow-list entry.
Matching rules (Zidane's interpretation of the spec's unspecified syntax):
- Exact match: displayName === pattern (no parens).
- Prefix match: for
Tool(arg:*), displayName === 'Tool' AND any string value in the input object starts witharg. This is intentionally permissive: it doesn't depend on a convention about which property carries the "command" (schemas vary across tools), and for the commonshell({ command: 'git …' })shape it behaves identically to a "primary string" rule. - For tools whose inputs carry no string values, the arg-match returns false.
parseAllowedToolPatternFunction#
Parse a single allowed-tools entry into its tool name + optional argument pattern.
Examples:
Read→{ tool: 'Read' }Bash(git:*)→{ tool: 'Bash', argPrefix: 'git' }
parseFrontmatterFunction#
Parse a SKILL.md file into frontmatter + body.
Uses a simple regex-based YAML extractor that handles:
- Flat key: value pairs
- Quoted values
- One-level nested maps (for
metadata:) - One-level nested block lists,
key:followed by indented- itemlines (forallowed-toolsand any future array fields). Items are unquoted with the same rules as scalar values. - Flow-style scalars are preserved verbatim — interpretation (e.g.
[a, b]as a list) is left to per-field consumers viatakeStringOrArray. - Unquoted values that contain colons (e.g.
description: Use when: the user asks …): the line splits on the first colon only, so the rest stays in the value. - Block scalars (
|literal,>folded, with chomping and indentation indicators) as top-level or nested map values.
parseSkillContentFunction#
parseSkillFile over content already read: a SKILL.md that is not
on this filesystem (a machine's, docs/MACHINES.md). absPath is where it
lives there; resources what its folder holds (none when unknown).
parseSkillFileFunction#
Parse a SKILL.md file into a SkillConfig (lenient).
Returns null only when the skill is fundamentally unusable:
- The file is missing
- The description is absent (required by spec for disclosure)
All other issues are surfaced as SkillConfig.diagnostics with severity
warning. Deprecated top-level fields (paths, model, thinking, effort)
are auto-migrated into metadata['zidane.*']. source defaults to 'project'.
parseSkillInstallSpecFunction#
Parse a raw skill source. Accepts everything the shared grammar does
(owner/repo[#ref][:subdir], GitHub URLs, pkg@version, …) plus
pasted skills.sh catalog URLs — skills.sh/vercel-labs/agent-skills/ react-best-practices maps to the backing GitHub repo with the third
segment as the skill selector.
readSkillProvenanceFunction#
Read a skill directory's install provenance, null for hand-authored skills.
resolveSkillsFunction#
Resolve all skills from a SkillsConfig:
- Materialize
config.writeentries to a temp directory, tagged asinline - Combine with default + user-provided scan paths (project-first, user-next)
- Run lenient disk discovery
- Merge with any host-provided
config.catalog. Catalog entries seed the resolution map FIRST, so they shadow disk-discovered skills with the same name (shadow diagnostic attached to the winner — same shape as the disk-vs-disk collision case). - Apply filters: optional project-skill trust gate,
exclude,enabledallowlist
Returns { skills, cleanup } — call cleanup() on agent destroy to remove
the temp directory created for inline config.write skills. The agent
factory wires this automatically; standalone callers must invoke it
themselves to avoid leaking OS temp.
sanitizeSkillNameFunction#
A folder-safe skill name: lowercase letters, digits and single hyphens.
skillCandidatesFromPathsFunction#
The skills of a source, from the paths of its files.
skillCatalogEntryFunction#
One skill's entry in the catalog: the lines the system prompt carries for it on every run, whether or not the skill is ever used.
skillCategoryFunction#
The folder a skill sits in, when it says more than the skill's own name and
a "skills" folder do: engineering for skills/engineering/tdd, nothing
for skills/tdd or tdd.
skillPickKeyFunction#
The key of a skill pick: its install spec and selector.
skillPickOfFunction#
A pick for one skill of a listing.
skillTokenWeightsFunction#
Estimated tokens of one skill, in the catalog and when activated.
stripShellInterpolationsFunction#
Replace every !command pattern with a static placeholder, without
executing anything. Used when shell interpolation is disabled
(SkillsConfig.allowShellInterpolation: false): skill content can be
third-party input, and running its embedded commands on activation
bypasses the allowed-tools gate.
validateResourcePathFunction#
Validate that relPath stays inside baseDir when resolved.
Rejects:
- Absolute paths (
/etc/passwd,C:\…) - Parent traversal (
..segments that escape baseDir) - Null-byte tricks
- Empty paths and paths that resolve to
baseDiritself
Returns { valid: true, absolutePath } on success or { valid: false, error }
with a human-readable reason.
validateResourcePathRealFunction#
Symlink-hardened variant of validateResourcePath: after the lexical
check passes, resolve both the target and baseDir through realpath and
re-confirm the target still lives under the skill directory. A symlink
placed inside the skill dir pointing at e.g. /etc/passwd passes the
lexical check but fails here.
Falls back to the lexical verdict when realpath fails (target or baseDir
not present on the LOCAL filesystem) — the common causes are a remote
execution context (docker/sandbox, where the local realpath is meaningless
and the remote side resolves the path itself) or a not-yet-existing file.
The returned absolutePath is always the lexical one: it's what the
execution context resolves; realpath here is verification only.
validateSkillForWriteFunction#
Strict validation for a skill that is about to be authored / written to disk.
Rejects anything that would violate the spec. Use the lenient parser path
(parseSkillFile) for loading third-party skills.
validateSkillNameFunction#
Validate a skill name per the spec:
- 1–64 characters
- Lowercase alphanumeric + hyphens only
- Must not start or end with a hyphen
- Must not contain consecutive hyphens
The parent-directory match is validated separately (it requires knowing the
skill's location, which this function does not).
writeSkillsToDiskFunction#
Write multiple SkillConfig objects to a target directory.
Each skill gets its own subdirectory with a SKILL.md file.
Returns the target directory path (for use as a scan path).
writeSkillToDiskFunction#
Write a SkillConfig to disk as a proper skill directory with SKILL.md.
Returns the path to the written SKILL.md file.
Throws if the skill fails validateSkillForWrite — the authoring path is
strict on purpose. For loading third-party skills, use parseSkillFile
directly (lenient).
Classes#
SkillBrowseErrorClass#
A listing that failed, with the reason in code.
SkillInstallErrorClass#
Error thrown when a skill install fails. code tells why.
Constants#
IMPLICITLY_ALLOWED_SKILL_TOOLSConstant#
Tools that are always allowed regardless of the active skills' allow-list.
RECOMMENDED_SKILL_SOURCESConstant#
The sources that the apps suggest, in display order.
SKILL_CANDIDATE_MAX_DEPTHConstant#
How deep the candidate search goes below the root. Real collections nest
skills under category folders (skills/engineering/tdd/SKILL.md in
mattpocock/skills), which a one-level scan would miss.
SKILL_PROVENANCE_FILEConstant#
Provenance sidecar file written into each natively installed skill directory. It records the spec so the install can be replayed.
Interfaces#
ActiveSkillInterface#
A skill currently active in the state machine.
BrowseSkillSourceOptionsInterface#
Options of browseSkillSource.
InterpolateShellCommandsOptionsInterface#
Options for interpolateShellCommands.
RecommendedSkillSourceInterface#
A source the apps suggest, such as anthropics/skills.
ShellInterpolationApprovalInterface#
Passed to a shell-interpolation approval gate so hosts can render a
meaningful prompt before a skill's embedded !cmd`` command runs.
SkillActivationStateInterface#
Per-agent skill activation state. Public read-surface is the active() list
and isActive(name) predicate; writes go through activate() / deactivate().
SkillActivationStateOptionsInterface#
Options for createSkillActivationState.
SkillCandidateDetailInterface#
One pickable candidate surfaced to host UIs — see SkillInstallError.details.
SkillConfigInterface#
A parsed skill: its frontmatter fields, instructions and origin.
SkillDiagnosticInterface#
Severity + code for lenient-load warnings surfaced to host UIs.
SkillInstallCandidateInterface#
One installable skill found in a fetched source tree.
SkillInstallOptionsInterface#
Options for installSkillFromSpec.
SkillInstallResultInterface#
Outcome of installSkillFromSpec.
SkillInstallSpecInterface#
A parsed skill install source, ready for the install pipeline.
SkillInstallTargetInterface#
What installs one skill: a source spec, and the skill's selector in it.
SkillPathCandidateInterface#
A skill folder found in a list of files.
SkillPickInterface#
A skill picked for install, as a wizard draft or a picker keeps it.
SkillPickGroupInterface#
The picks of one source: one install for all of them.
SkillResourceInterface#
Types for the Agent Skills system.
Follows the Agent Skills open standard (agentskills.io/specification).
Zidane-specific metadata conventionally uses the zidane. key prefix
(e.g. metadata['zidane.paths']) to stay spec-compliant.
SkillsConfigInterface#
Skill system configuration (AgentOptions.skills).
SkillSourceInfoInterface#
Where the skills of a listing come from.
SkillSourceListingInterface#
The skills of one source, as browseSkillSource lists them.
SkillSourceSkillInterface#
One skill of a listing.
SkillTokenWeightsInterface#
Estimated token cost of one skill, from skillTokenWeights.
SkillValidationIssueInterface#
A single issue surfaced by skill-authoring validation.
Distinct from ValidationResult in tools/validation.ts (tool-input JSON
schema validation) — different domain, different module.
SkillValidationResultInterface#
Result of validateSkillForWrite.
SourcedScanPathInterface#
A scan path paired with the source tag that should be attached to any skills found in it.
Types#
ActivationViaType#
How a skill was activated. Surfaced in skills:activate hook ctx.
DeactivationReasonType#
Reason a skill was deactivated. Surfaced in skills:deactivate hook ctx.
'run-end'— the implicit deactivate-all pass at eachrun()boundary.'explicit'— the host calledagent.deactivateSkill(name).'reset'—agent.reset()cleared the activation state along with everything else.'model'— the model calledskills_use({ mode: 'deactivate', name })to release a skill's allow-list mid-run. Session-resume rehydration honors the most recentmodeper skill, so a model-driven deactivate sticks across the next turn (no surprise re-activation).
SkillBrowseErrorCodeType#
Why a listing failed.
invalid-source: the text is not a source.not-found: no such repository, folder or package, or no access to it.rate-limited: GitHub refused more calls without a token, and the download failed too.fetch-failed: the network or the download failed.
SkillInstallErrorCodeType#
Machine-readable reason for a SkillInstallError.
SkillSourceType#
Where the skill came from. Used for collision precedence (project beats user) and for host SDKs to gate project-level skills on a trust check.