# zidane/skills

> Discover, parse, define and install Agent Skills, the instruction packages a model loads on demand.

Import: `import { … } from 'zidane/skills'`. Source: [src/skills/index.ts](https://github.com/Tahul/zidane/blob/main/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](https://zidane.si/docs/skills.md) for the guide.

```ts
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

### `browseSkillSource`

Function.

Lists the skills of a source, with what installs each one.

```ts
function browseSkillSource(
  input: string,
  opts?: BrowseSkillSourceOptions,
): Promise<SkillSourceListing>
```

**Parameters**

- `input` (`string`): A source, as the installer takes it: `owner/repo`, a GitHub URL, `owner/repo/path`, a skills.sh URL, a `skills add` command or an npm package.
- `opts?` (`BrowseSkillSourceOptions`, default `{}`): A GitHub token, a signal, and test overrides.

**Returns** `Promise<SkillSourceListing>`: The listing. A source without skills gives an empty list, not an error.

**Example**

```ts
import { browseSkillSource, installSkillFromSpec } from 'zidane/skills'

const listing = await browseSkillSource('https://github.com/cursor/plugins/tree/HEAD/pstack/skills')
const tdd = listing.skills.find(skill => skill.name === 'tdd')!
await installSkillFromSpec({ source: tdd.install.source, skills: [tdd.install.rel], scope: 'user' })
```

Source: [src/skills/browse.ts:320](https://github.com/Tahul/zidane/blob/main/src/skills/browse.ts#L320)

### `buildCatalog`

Function.

Build the skill catalog XML and behavioral instructions for the system prompt.

```ts
function buildCatalog(skills: SkillConfig[], options?: BuildCatalogOptions): string
```

**Parameters**

- `skills` (`SkillConfig[]`): Skills to list. An empty array yields an empty string.
- `options?` (`BuildCatalogOptions`, default `{}`): Wording options. See `BuildCatalogOptions`.

**Returns** `string`: The catalog text, or an empty string when `skills` is empty.

Source: [src/skills/catalog.ts:56](https://github.com/Tahul/zidane/blob/main/src/skills/catalog.ts#L56)

### `clearSkillSourceCache`

Function.

Forgets every cached listing.

```ts
function clearSkillSourceCache(): void
```

Source: [src/skills/browse.ts:88](https://github.com/Tahul/zidane/blob/main/src/skills/browse.ts#L88)

### `createSkillActivationState`

Function.

Creates an empty skill activation state for one agent.

```ts
function createSkillActivationState(
  options?: SkillActivationStateOptions,
): SkillActivationState
```

**Parameters**

- `options?` (`SkillActivationStateOptions`, default `{}`): Activation cap. See [`SkillActivationStateOptions`](https://zidane.si/docs/reference/skills.md#SkillActivationStateOptions).

**Returns** `SkillActivationState`

Source: [src/skills/activation.ts:102](https://github.com/Tahul/zidane/blob/main/src/skills/activation.ts#L102)

### `defineSkill`

Function.

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'`.

```ts
function defineSkill(
  config: Omit<SkillConfig, 'source'> & { source?: SkillSource },
): SkillConfig
```

**Parameters**

- `config` (`Omit<SkillConfig, 'source'> & { source?: SkillSource }`): Skill fields. `source` defaults to `'inline'`.

**Returns** `SkillConfig`

**Example**

```ts
import { defineSkill } from 'zidane/skills'

const skill = defineSkill({
  name: 'release-notes',
  description: 'Drafts release notes from merged pull requests.',
  instructions: 'List merged PRs since the last tag and group them by area.',
})
```

Source: [src/skills/index.ts:105](https://github.com/Tahul/zidane/blob/main/src/skills/index.ts#L105)

### `discoverSkills`

Function.

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.

```ts
function discoverSkills(
  paths: SourcedScanPath[],
  signal?: AbortSignal,
): Promise<SkillConfig[]>
```

**Parameters**

- `paths` (`SourcedScanPath[]`): Directories to scan, highest priority first.
- `signal?` (`AbortSignal`): Aborts the scan between files.

**Returns** `Promise<SkillConfig[]>`

**Example**

```ts
import { discoverSkills } from 'zidane/skills'

const skills = await discoverSkills([{ path: './.agents/skills', source: 'project' }])
console.log(skills.map(skill => skill.name))
```

Source: [src/skills/discovery.ts:922](https://github.com/Tahul/zidane/blob/main/src/skills/discovery.ts#L922)

### `getDefaultScanPaths`

Function.

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).

```ts
function getDefaultScanPaths(): SourcedScanPath[]
```

**Returns** `SourcedScanPath[]`

Source: [src/skills/discovery.ts:877](https://github.com/Tahul/zidane/blob/main/src/skills/discovery.ts#L877)

### `githubAvatarUrl`

Function.

The avatar of a GitHub user or organization. GitHub redirects it to its
avatar store, which takes no organization names itself.

```ts
function githubAvatarUrl(owner: string, size?: number): string
```

**Parameters**

- `owner` (`string`): The user or organization.
- `size?` (`number`, default `96`): The size in pixels.

**Returns** `string`

Source: [src/skills/sources.ts:227](https://github.com/Tahul/zidane/blob/main/src/skills/sources.ts#L227)

### `githubPreviewImageUrl`

Function.

The preview image that GitHub draws for links to a repository: 1200 by 600 pixels.

```ts
function githubPreviewImageUrl(owner: string, repo: string): string
```

**Parameters**

- `owner` (`string`): The owner.
- `repo` (`string`): The repository.

**Returns** `string`

Source: [src/skills/sources.ts:237](https://github.com/Tahul/zidane/blob/main/src/skills/sources.ts#L237)

### `groupSkillPicks`

Function.

Picks grouped by the spec that installs them, in pick order, so each source is fetched once.

```ts
function groupSkillPicks(picks: readonly SkillPick[]): SkillPickGroup[]
```

**Parameters**

- `picks` (`readonly SkillPick[]`): The picked skills.

**Returns** `SkillPickGroup[]`

**Example**

```ts
import { groupSkillPicks } from 'zidane/chat/pure'

groupSkillPicks([
  { key: 'a', name: 'tdd', label: 'mattpocock/skills', install: { source: 'mattpocock/skills', rel: 'engineering/tdd' } },
  { key: 'b', name: 'pr', label: 'mattpocock/skills', install: { source: 'mattpocock/skills', rel: 'engineering/pr' } },
])
// [{ source: 'mattpocock/skills', label: 'mattpocock/skills', rels: ['engineering/tdd', 'engineering/pr'], names: ['tdd', 'pr'] }]
```

Source: [src/skills/sources.ts:196](https://github.com/Tahul/zidane/blob/main/src/skills/sources.ts#L196)

### `inferSource`

Function.

Infer a source tag for a user-provided scan path.
Paths under `$HOME` are treated as 'user'; everything else as 'project'.

```ts
function inferSource(path: string): SkillSource
```

**Parameters**

- `path` (`string`): Scan path to classify.

**Returns** `SkillSource`

Source: [src/skills/discovery.ts:895](https://github.com/Tahul/zidane/blob/main/src/skills/discovery.ts#L895)

### `installAllowedToolsGate`

Function.

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.

```ts
function installAllowedToolsGate(
  hooks: Hookable<AgentHooks>,
  state: SkillActivationState,
): () => void
```

**Parameters**

- `hooks` (`Hookable<AgentHooks>`): Agent hooks to register the gate handlers on.
- `state` (`SkillActivationState`): Activation state that supplies the active skills and their `allowedTools`.

**Returns** `() => void`

Source: [src/skills/allowed-tools.ts:42](https://github.com/Tahul/zidane/blob/main/src/skills/allowed-tools.ts#L42)

### `installSkillFromSpec`

Function.

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).

```ts
function installSkillFromSpec(opts: SkillInstallOptions): Promise<SkillInstallResult>
```

**Parameters**

- `opts` (`SkillInstallOptions`): Source, scope and install options. See [`SkillInstallOptions`](https://zidane.si/docs/reference/skills.md#SkillInstallOptions).

**Returns** `Promise<SkillInstallResult>`

Source: [src/skills/install.ts:401](https://github.com/Tahul/zidane/blob/main/src/skills/install.ts#L401)

### `interpolateShellCommands`

Function.

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).

```ts
function interpolateShellCommands(
  instructions: string,
  execution: ExecutionContext,
  handle: ExecutionHandle,
  options?: InterpolateShellCommandsOptions,
): Promise<string>
```

**Parameters**

- `instructions` (`string`): Raw skill instructions with potential !`command` patterns
- `execution` (`ExecutionContext`): The execution context to run commands in
- `handle` (`ExecutionHandle`): The active execution handle
- `options?` (`InterpolateShellCommandsOptions`, default `{}`): Failure callback and approval gate. See [`InterpolateShellCommandsOptions`](https://zidane.si/docs/reference/skills.md#InterpolateShellCommandsOptions).

**Returns** `Promise<string>`: Instructions with all !`command` patterns replaced by output

Source: [src/skills/interpolate.ts:63](https://github.com/Tahul/zidane/blob/main/src/skills/interpolate.ts#L63)

### `isToolAllowedByUnion`

Function.

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.

```ts
function isToolAllowedByUnion(
  displayName: string,
  input: Record<string, unknown>,
  union: readonly string[],
): boolean
```

**Parameters**

- `displayName` (`string`): Display name of the tool being called.
- `input` (`Record<string, unknown>`): Tool call input.
- `union` (`readonly string[]`): `allowed-tools` entries gathered from all active skills.

**Returns** `boolean`

Source: [src/skills/validate.ts:357](https://github.com/Tahul/zidane/blob/main/src/skills/validate.ts#L357)

### `listSkillCandidates`

Function.

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`.

```ts
function listSkillCandidates(dir: string, fallbackName: string): SkillInstallCandidate[]
```

**Parameters**

- `dir` (`string`): Root of the fetched source tree.
- `fallbackName` (`string`): Name used when the root itself holds a `SKILL.md`.

**Returns** `SkillInstallCandidate[]`

Source: [src/skills/install.ts:253](https://github.com/Tahul/zidane/blob/main/src/skills/install.ts#L253)

### `matchesAllowedTool`

Function.

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 with `arg`. This is intentionally
  permissive: it doesn't depend on a convention about which property carries
  the "command" (schemas vary across tools), and for the common
  `shell({ command: 'git …' })` shape it behaves identically to a "primary
  string" rule.
- For tools whose inputs carry no string values, the arg-match returns false.

```ts
function matchesAllowedTool(
  displayName: string,
  input: Record<string, unknown>,
  pattern: string,
): boolean
```

**Parameters**

- `displayName` (`string`): Display name of the tool being called.
- `input` (`Record<string, unknown>`): Tool call input.
- `pattern` (`string`): One `allowed-tools` entry.

**Returns** `boolean`

Source: [src/skills/validate.ts:326](https://github.com/Tahul/zidane/blob/main/src/skills/validate.ts#L326)

### `parseAllowedToolPattern`

Function.

Parse a single `allowed-tools` entry into its tool name + optional argument pattern.

Examples:

- `Read` → `{ tool: 'Read' }`
- `Bash(git:*)` → `{ tool: 'Bash', argPrefix: 'git' }`

```ts
function parseAllowedToolPattern(
  entry: string,
): { argPrefix?: string; tool: string } | null
```

**Parameters**

- `entry` (`string`): One `allowed-tools` entry, such as `Bash(git:*)`.

**Returns** `{ argPrefix?: string; tool: string } | null`: The tool name and optional argument prefix, or `null` when the entry is malformed.

Source: [src/skills/validate.ts:294](https://github.com/Tahul/zidane/blob/main/src/skills/validate.ts#L294)

### `parseFrontmatter`

Function.

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 `- item`
  lines (for `allowed-tools` and 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 via
  `takeStringOrArray`.
- 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.

```ts
function parseFrontmatter(content: string): ParsedSkillFile
```

**Parameters**

- `content` (`string`): Full SKILL.md text, with or without frontmatter.

**Returns** `ParsedSkillFile`

Source: [src/skills/discovery.ts:164](https://github.com/Tahul/zidane/blob/main/src/skills/discovery.ts#L164)

### `parseSkillContent`

Function.

[`parseSkillFile`](https://zidane.si/docs/reference/skills.md#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).

```ts
function parseSkillContent(
  content: string,
  absPath: string,
  options?: ParseSkillOptions & { resources?: SkillResource[] },
): SkillConfig | null
```

**Parameters**

- `content` (`string`): Full SKILL.md text.
- `absPath` (`string`): Absolute path the file lives at, stored as the skill `location`.
- `options?` (`ParseSkillOptions & { resources?: SkillResource[] }`, default `{}`): `source` tags the returned skill. `resources` lists the files in the skill folder.

**Returns** `SkillConfig | null`

Source: [src/skills/discovery.ts:646](https://github.com/Tahul/zidane/blob/main/src/skills/discovery.ts#L646)

### `parseSkillFile`

Function.

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'`.

```ts
function parseSkillFile(
  filePath: string,
  options?: ParseSkillOptions,
): Promise<SkillConfig | null>
```

**Parameters**

- `filePath` (`string`): Path of the SKILL.md file.
- `options?` (`ParseSkillOptions`, default `{}`): Parse options. `source` tags the returned skill.

**Returns** `Promise<SkillConfig | null>`

Source: [src/skills/discovery.ts:628](https://github.com/Tahul/zidane/blob/main/src/skills/discovery.ts#L628)

### `parseSkillInstallSpec`

Function.

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.

```ts
function parseSkillInstallSpec(raw: string): SkillInstallSpec
```

**Parameters**

- `raw` (`string`): Source text: shorthand, a URL, a skills.sh URL or a pasted `skills add` command.

**Returns** `SkillInstallSpec`: The parsed source, optional skill selector and CLI spec. Throws [`SkillInstallError`](https://zidane.si/docs/reference/skills.md#SkillInstallError) with `invalid-source` when the text cannot be parsed.

Source: [src/skills/install.ts:150](https://github.com/Tahul/zidane/blob/main/src/skills/install.ts#L150)

### `readSkillProvenance`

Function.

Read a skill directory's install provenance, `null` for hand-authored skills.

```ts
function readSkillProvenance(dir: string): (SkillProvenance & { spec: string }) | null
```

**Parameters**

- `dir` (`string`): Skill directory.

**Returns** `(SkillProvenance & { spec: string }) | null`

Source: [src/skills/install.ts:648](https://github.com/Tahul/zidane/blob/main/src/skills/install.ts#L648)

### `resolveSkills`

Function.

Resolve all skills from a SkillsConfig:

1. Materialize `config.write` entries to a temp directory, tagged as `inline`
2. Combine with default + user-provided scan paths (project-first, user-next)
3. Run lenient disk discovery
4. 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).
5. Apply filters: optional project-skill trust gate, `exclude`,
   `enabled` allowlist

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.

```ts
function resolveSkills(config: SkillsConfig): Promise<ResolvedSkillsBundle>
```

**Parameters**

- `config` (`SkillsConfig`): Skills configuration. See [`SkillsConfig`](https://zidane.si/docs/reference/skills.md#SkillsConfig).

**Returns** `Promise<ResolvedSkillsBundle>`

Source: [src/skills/resolve.ts:51](https://github.com/Tahul/zidane/blob/main/src/skills/resolve.ts#L51)

### `sanitizeSkillName`

Function.

A folder-safe skill name: lowercase letters, digits and single hyphens.

```ts
function sanitizeSkillName(raw: string): string
```

**Parameters**

- `raw` (`string`): A folder, repository or package name.

**Returns** `string`

Source: [src/skills/candidates.ts:32](https://github.com/Tahul/zidane/blob/main/src/skills/candidates.ts#L32)

### `skillCandidatesFromPaths`

Function.

The skills of a source, from the paths of its files.

```ts
function skillCandidatesFromPaths(
  files: readonly string[],
  root: string,
  fallbackName: string,
): SkillPathCandidate[]
```

**Parameters**

- `files` (`readonly string[]`): Source-relative file paths, such as a GitHub tree listing. Only the ones named `SKILL.md` count.
- `root` (`string`): The folder to search in (`pstack/skills`), or `''` for the whole source.
- `fallbackName` (`string`): The name of a skill at the root, such as the repository's.

**Returns** `SkillPathCandidate[]`: The candidates, sorted by `rel`.

**Example**

```ts
import { skillCandidatesFromPaths } from 'zidane/chat/pure'

skillCandidatesFromPaths(['skills/tdd/SKILL.md', 'skills/tdd/references/SKILL.md', 'README.md'], 'skills', 'repo')
// [{ name: 'tdd', rel: 'tdd', dir: 'skills/tdd' }]
```

Source: [src/skills/candidates.ts:52](https://github.com/Tahul/zidane/blob/main/src/skills/candidates.ts#L52)

### `skillCatalogEntry`

Function.

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.

```ts
function skillCatalogEntry(skill: SkillConfig): string
```

**Parameters**

- `skill` (`SkillConfig`): The skill to describe.

**Returns** `string`

Source: [src/skills/catalog.ts:40](https://github.com/Tahul/zidane/blob/main/src/skills/catalog.ts#L40)

### `skillCategory`

Function.

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`.

```ts
function skillCategory(skill: Pick<SkillSourceSkill, 'path'>): string
```

**Parameters**

- `skill` (`Pick<SkillSourceSkill, 'path'>`): The skill, with its path in the listing.

**Returns** `string`

Source: [src/skills/sources.ts:216](https://github.com/Tahul/zidane/blob/main/src/skills/sources.ts#L216)

### `skillPickKey`

Function.

The key of a skill pick: its install spec and selector.

```ts
function skillPickKey(target: SkillInstallTarget): string
```

**Parameters**

- `target` (`SkillInstallTarget`): What installs the skill.

**Returns** `string`

Source: [src/skills/sources.ts:167](https://github.com/Tahul/zidane/blob/main/src/skills/sources.ts#L167)

### `skillPickOf`

Function.

A pick for one skill of a listing.

```ts
function skillPickOf(skill: SkillSourceSkill, listing: SkillSourceListing): SkillPick
```

**Parameters**

- `skill` (`SkillSourceSkill`): The skill.
- `listing` (`SkillSourceListing`): The listing it comes from.

**Returns** `SkillPick`

Source: [src/skills/sources.ts:177](https://github.com/Tahul/zidane/blob/main/src/skills/sources.ts#L177)

### `skillTokenWeights`

Function.

Estimated tokens of one skill, in the catalog and when activated.

```ts
function skillTokenWeights(skill: SkillConfig): SkillTokenWeights
```

**Parameters**

- `skill` (`SkillConfig`): The skill to measure.

**Returns** `SkillTokenWeights`

Source: [src/skills/weights.ts:24](https://github.com/Tahul/zidane/blob/main/src/skills/weights.ts#L24)

### `stripShellInterpolations`

Function.

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.

```ts
function stripShellInterpolations(instructions: string): string
```

**Parameters**

- `instructions` (`string`): Raw skill instructions.

**Returns** `string`

Source: [src/skills/interpolate.ts:27](https://github.com/Tahul/zidane/blob/main/src/skills/interpolate.ts#L27)

### `validateResourcePath`

Function.

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 `baseDir` itself

Returns `{ valid: true, absolutePath }` on success or `{ valid: false, error }`
with a human-readable reason.

```ts
function validateResourcePath(
  relPath: string,
  baseDir: string,
): { absolutePath: string; valid: true } | { error: string; valid: false }
```

**Parameters**

- `relPath` (`string`): Resource path, relative to `baseDir`.
- `baseDir` (`string`): Skill directory the path must stay inside.

**Returns** `{ absolutePath: string; valid: true } | { error: string; valid: false }`

Source: [src/skills/validate.ts:199](https://github.com/Tahul/zidane/blob/main/src/skills/validate.ts#L199)

### `validateResourcePathReal`

Function.

Symlink-hardened variant of [`validateResourcePath`](https://zidane.si/docs/reference/skills.md#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.

```ts
function validateResourcePathReal(
  relPath: string,
  baseDir: string,
): Promise<{ absolutePath: string; valid: true } | { error: string; valid: false }>
```

**Parameters**

- `relPath` (`string`): Resource path, relative to `baseDir`.
- `baseDir` (`string`): Skill directory the path must stay inside.

**Returns** `Promise<{ absolutePath: string; valid: true } | { error: string; valid: false }>`

Source: [src/skills/validate.ts:254](https://github.com/Tahul/zidane/blob/main/src/skills/validate.ts#L254)

### `validateSkillForWrite`

Function.

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.

```ts
function validateSkillForWrite(skill: SkillConfig): SkillValidationResult
```

**Parameters**

- `skill` (`SkillConfig`): Skill to check.

**Returns** `SkillValidationResult`

Source: [src/skills/validate.ts:94](https://github.com/Tahul/zidane/blob/main/src/skills/validate.ts#L94)

### `validateSkillName`

Function.

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).

```ts
function validateSkillName(name: string): boolean
```

**Parameters**

- `name` (`string`): Candidate skill name.

**Returns** `boolean`

Source: [src/skills/validate.ts:74](https://github.com/Tahul/zidane/blob/main/src/skills/validate.ts#L74)

### `writeSkillsToDisk`

Function.

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).

```ts
function writeSkillsToDisk(skills: SkillConfig[], targetDir: string): string
```

**Parameters**

- `skills` (`SkillConfig[]`): Skills to write.
- `targetDir` (`string`): Directory that receives one subdirectory per skill. Created when missing.

**Returns** `string`

Source: [src/skills/writer.ts:110](https://github.com/Tahul/zidane/blob/main/src/skills/writer.ts#L110)

### `writeSkillToDisk`

Function.

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).

```ts
function writeSkillToDisk(skill: SkillConfig, targetDir: string): string
```

**Parameters**

- `skill` (`SkillConfig`): Skill to write. Must pass [`validateSkillForWrite`](https://zidane.si/docs/reference/skills.md#validateSkillForWrite).
- `targetDir` (`string`): Parent directory. The skill goes in `<targetDir>/<name>/SKILL.md`.

**Returns** `string`

Source: [src/skills/writer.ts:72](https://github.com/Tahul/zidane/blob/main/src/skills/writer.ts#L72)

## Classes

### `SkillBrowseError`

Class.

A listing that failed, with the reason in `code`.

```ts
class SkillBrowseError extends Error
```

**Constructor**

```ts
new SkillBrowseError(code: SkillBrowseErrorCode, message: string)
```

**Parameters**

- `code` (`SkillBrowseErrorCode`): Why the listing failed.
- `message` (`string`): What failed, in a sentence a person can read.

**Returns** `SkillBrowseError`

**Properties**

- `code` (`SkillBrowseErrorCode`, readonly): Why it failed.

Source: [src/skills/browse.ts:46](https://github.com/Tahul/zidane/blob/main/src/skills/browse.ts#L46)

### `SkillInstallError`

Class.

Error thrown when a skill install fails. `code` tells why.

```ts
class SkillInstallError extends InstallSourceError
```

**Constructor**

```ts
new SkillInstallError(
  code: SkillInstallErrorCode,
  message: string,
  candidates?: readonly string[],
  details?: readonly SkillCandidateDetail[],
)
```

Creates the error.

**Parameters**

- `code` (`SkillInstallErrorCode`): Reason for the failure.
- `message` (`string`): Human-readable description.
- `candidates?` (`readonly string[]`): Root-relative paths of the skills found, set on `multiple-candidates`.
- `details?` (`readonly SkillCandidateDetail[]`): Metadata per candidate, parallel to `candidates`.

**Returns** `SkillInstallError`

**Properties**

- `candidates?` (`readonly string[]`, readonly, from `InstallSourceError`): Set for candidate-selection errors — the names the caller can pick from.
- `code` (`SkillInstallErrorCode`, readonly): Reason for the failure. See [`SkillInstallErrorCode`](https://zidane.si/docs/reference/skills.md#SkillInstallErrorCode).
- `details?` (`readonly SkillCandidateDetail[]`, readonly): Frontmatter metadata per candidate, parallel to `candidates` — set on
  the selector-less `multiple-candidates` error so host pickers can show
  descriptions without re-fetching the source. Best-effort: candidates
  whose SKILL.md fails to parse appear with no description.

Source: [src/skills/install.ts:60](https://github.com/Tahul/zidane/blob/main/src/skills/install.ts#L60)

## Constants

### `IMPLICITLY_ALLOWED_SKILL_TOOLS`

Constant.

Tools that are always allowed regardless of the active skills' allow-list.

```ts
const IMPLICITLY_ALLOWED_SKILL_TOOLS: readonly string[]
```

Source: [src/skills/allowed-tools.ts:24](https://github.com/Tahul/zidane/blob/main/src/skills/allowed-tools.ts#L24)

### `RECOMMENDED_SKILL_SOURCES`

Constant.

The sources that the apps suggest, in display order.

```ts
const RECOMMENDED_SKILL_SOURCES: readonly RecommendedSkillSource[]
```

Source: [src/skills/sources.ts:28](https://github.com/Tahul/zidane/blob/main/src/skills/sources.ts#L28)

### `SKILL_CANDIDATE_MAX_DEPTH`

Constant.

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.

```ts
const SKILL_CANDIDATE_MAX_DEPTH = 4
```

Source: [src/skills/candidates.ts:15](https://github.com/Tahul/zidane/blob/main/src/skills/candidates.ts#L15)

### `SKILL_PROVENANCE_FILE`

Constant.

Provenance sidecar file written into each natively installed skill directory. It records the spec so the install can be replayed.

```ts
const SKILL_PROVENANCE_FILE = '.zidane-install.json'
```

Source: [src/skills/install.ts:630](https://github.com/Tahul/zidane/blob/main/src/skills/install.ts#L630)

## Interfaces

### `ActiveSkill`

Interface.

A skill currently active in the state machine.

```ts
interface ActiveSkill
```

**Properties**

- `activatedAt` (`number`): Activation time as epoch milliseconds.
- `activatedVia` (`ActivationVia`): What activated the skill. See [`ActivationVia`](https://zidane.si/docs/reference/skills.md#ActivationVia).
- `skill` (`SkillConfig`): The active skill.

Source: [src/skills/activation.ts:48](https://github.com/Tahul/zidane/blob/main/src/skills/activation.ts#L48)

### `BrowseSkillSourceOptions`

Interface.

Options of [`browseSkillSource`](https://zidane.si/docs/reference/skills.md#browseSkillSource).

```ts
interface BrowseSkillSourceOptions
```

**Properties**

- `fresh?` (`boolean`): Ignore the cached listing and list again.
- `io?` (`InstallIo`): Overrides for `fetch`, command execution and `which`. Used by tests.
- `signal?` (`AbortSignal`): Stops the listing.
- `token?` (`string`): A GitHub token: 5,000 API calls an hour instead of 60, and private repositories.

Source: [src/skills/browse.ts:61](https://github.com/Tahul/zidane/blob/main/src/skills/browse.ts#L61)

### `InterpolateShellCommandsOptions`

Interface.

Options for [`interpolateShellCommands`](https://zidane.si/docs/reference/skills.md#interpolateShellCommands).

```ts
interface InterpolateShellCommandsOptions
```

**Properties**

- `approve?` (`(command: string) => boolean | Promise<boolean>`): Optional per-command approval gate, invoked BEFORE each !`command` runs.
  Return `false` to skip executing that command — it is replaced with a
  `[shell interpolation not approved]` placeholder instead of its output.
  When omitted, every command runs without a prompt.
- `onFailure?` (`(command: string, placeholder: string) => void`): Called once per !`command` that failed (non-zero exit or exec throw)
  with the command text and the inline placeholder it was replaced with.
  Lets callers surface a visible warning instead of activation silently
  proceeding on partial content.

Source: [src/skills/interpolate.ts:32](https://github.com/Tahul/zidane/blob/main/src/skills/interpolate.ts#L32)

### `RecommendedSkillSource`

Interface.

A source the apps suggest, such as `anthropics/skills`.

```ts
interface RecommendedSkillSource
```

**Properties**

- `description` (`string`): One line on what its skills do.
- `id` (`string`): Stable id.
- `label` (`string`): Its name in the list.
- `owner` (`string`): GitHub owner.
- `path?` (`string`): The folder of the repository that holds the skills, when it is not the root.
- `repo` (`string`): GitHub repository.
- `spec` (`string`): What the browser opens: anything `browseSkillSource` takes.

Source: [src/skills/sources.ts:10](https://github.com/Tahul/zidane/blob/main/src/skills/sources.ts#L10)

### `ShellInterpolationApproval`

Interface.

Passed to a shell-interpolation approval gate so hosts can render a
meaningful prompt before a skill's embedded `!`cmd\`\` command runs.

```ts
interface ShellInterpolationApproval
```

**Properties**

- `command` (`string`): The exact shell command that would be executed.
- `skillName` (`string`): Name of the skill being activated.
- `source` (`SkillSource | undefined`): Where the skill came from (`project` / `user` / `inline` / `builtin`).

Source: [src/skills/types.ts:34](https://github.com/Tahul/zidane/blob/main/src/skills/types.ts#L34)

### `SkillActivationState`

Interface.

Per-agent skill activation state. Public read-surface is the `active()` list
and `isActive(name)` predicate; writes go through `activate()` / `deactivate()`.

```ts
interface SkillActivationState
```

**Properties**

- `activate` (`(skill: SkillConfig, via: ActivationVia) => 'ok' | 'already-active' | 'cap-reached'`): Mark a skill as active.
  - Returns `'ok'` on a fresh activation (caller should fire `skills:activate`).
  - Returns `'already-active'` if the skill was already in the set (idempotent).
  - Returns `'cap-reached'` if the `maxActive` cap would be exceeded. State is unchanged.
- `active` (`() => readonly ActiveSkill[]`): List of currently active skills in activation order. Returns a snapshot.
- `clear` (`() => readonly ActiveSkill[]`): Remove every active skill. Returns the list of removed records.
- `deactivate` (`(name: string) => ActiveSkill | undefined`): Mark a skill as inactive. Returns the removed `ActiveSkill` record or `undefined`
  if it wasn't active. Callers fire `skills:deactivate` on removal.
- `get` (`(name: string) => ActiveSkill | undefined`): Retrieve the `ActiveSkill` record by name, or `undefined`.
- `isActive` (`(name: string) => boolean`): Is the skill with this canonical name currently active?

Source: [src/skills/activation.ts:61](https://github.com/Tahul/zidane/blob/main/src/skills/activation.ts#L61)

### `SkillActivationStateOptions`

Interface.

Options for [`createSkillActivationState`](https://zidane.si/docs/reference/skills.md#createSkillActivationState).

```ts
interface SkillActivationStateOptions
```

**Properties**

- `maxActive?` (`number`): Cap on concurrent activations. `undefined` (the default) disables the cap.
  When set, `activate()` returns `'cap-reached'` once the set is at size `maxActive`.

Source: [src/skills/activation.ts:85](https://github.com/Tahul/zidane/blob/main/src/skills/activation.ts#L85)

### `SkillCandidateDetail`

Interface.

One pickable candidate surfaced to host UIs — see `SkillInstallError.details`.

```ts
interface SkillCandidateDetail
```

**Properties**

- `description?` (`string`): Frontmatter description, when the candidate's SKILL.md parses.
- `rel` (`string`): Root-relative path — the unambiguous selector (`skills` option).

Source: [src/skills/install.ts:87](https://github.com/Tahul/zidane/blob/main/src/skills/install.ts#L87)

### `SkillConfig`

Interface.

A parsed skill: its frontmatter fields, instructions and origin.

```ts
interface SkillConfig
```

**Properties**

- `allowedTools?` (`string[]`): Pre-approved tool names (experimental per spec)
- `baseDir?` (`string`): Skill directory path for resolving relative references
- `compatibility?` (`string`): Environment requirements
- `description` (`string`): What the skill does and when to use it (1-1024 chars)
- `diagnostics?` (`SkillDiagnostic[]`): Lenient-load warnings recorded during parsing. Host SDKs can surface these
  as inline UI hints. Absent when no issues were found.
- `instructions` (`string`): The SKILL.md body content (after YAML frontmatter)
- `license?` (`string`): License identifier or reference
- `location?` (`string`): Absolute path to SKILL.md (undefined for inline skills)
- `metadata?` (`Record<string, string>`): Flat key-value metadata bag per the spec. For Zidane-specific hints,
  use the `zidane.` key prefix (e.g. `metadata['zidane.paths']`).
- `name` (`string`): Skill name: 1-64 chars, lowercase alphanumeric + hyphens
- `resources?` (`SkillResource[]`): Bundled resource files discovered in the skill directory
- `source?` (`SkillSource`): Where this skill was loaded from. Drives collision precedence and the
  `trustProjectSkills` gate. Optional — `parseSkillFile` stamps it; raw
  fixtures that omit it are treated as `'project'` by downstream readers.

Source: [src/skills/types.ts:60](https://github.com/Tahul/zidane/blob/main/src/skills/types.ts#L60)

### `SkillDiagnostic`

Interface.

Severity + code for lenient-load warnings surfaced to host UIs.

```ts
interface SkillDiagnostic
```

**Properties**

- `code` (`string`): Stable machine-readable code (e.g. `name-mismatch-directory`).
- `field?` (`string`): Optional frontmatter field name the diagnostic relates to.
- `message` (`string`): Human-readable description.
- `severity` (`'error' | 'warning'`): `error` marks a problem that rejects or alters the skill. `warning` is advisory.

Source: [src/skills/types.ts:44](https://github.com/Tahul/zidane/blob/main/src/skills/types.ts#L44)

### `SkillInstallCandidate`

Interface.

One installable skill found in a fetched source tree.

```ts
interface SkillInstallCandidate
```

**Properties**

- `dir` (`string`): Absolute path of the skill directory in the fetched tree.
- `name` (`string`): Directory name candidate (validated against SKILL.md frontmatter later).
- `rel` (`string`): Root-relative path (`engineering/tdd`) — the unambiguous selector
  when the same skill NAME exists in several category directories.

Source: [src/skills/install.ts:216](https://github.com/Tahul/zidane/blob/main/src/skills/install.ts#L216)

### `SkillInstallOptions`

Interface.

Options for [`installSkillFromSpec`](https://zidane.si/docs/reference/skills.md#installSkillFromSpec).

```ts
interface SkillInstallOptions
```

**Properties**

- `all?` (`boolean`): Install EVERY skill a multi-skill source contains (the affirmative
  answer to the host's "import all?" prompt after a
  `multiple-candidates` error). Native engine only — already-installed
  names are skipped (reported via `SkillInstallResult.skipped`) rather
  than failing the batch, unless `overwrite` is also set. Overrides a
  selector embedded in the source string; ignored when an explicit
  `skill` option is present.
- `cwd?` (`string`): Working directory that locates the project scope. Default: the process working directory.
- `home?` (`string`): Home directory that locates the user scope. Default: the current user home.
- `io?` (`InstallIo`): Overrides for `fetch`, command execution and `which`. Used by tests and sandboxed hosts.
- `onProgress?` (`(message: string) => void`): Called with a short status message as the install proceeds.
- `overwrite?` (`boolean`): Overwrite an existing install of the same name.
- `preferSkillsCli?` (`boolean`): Prefer the skills.sh CLI when `skills` is on PATH (default true).
  The CLI owns scope + layout decisions (`.agents/skills/`, their
  lockfile); the native path is the fallback and the forced path in
  tests. Set false to always install natively.
- `prefix?` (`string`): Storage prefix (default `.zidane`) — mirrors `ResolvedConfig.prefix`.
- `scope` (`InstallScope`): Install into the `project` or the `user` skills root.
- `skill?` (`string`): Pick one skill from a multi-skill source (names from the `multiple-candidates` error).
- `skills?` (`readonly string[]`): Install a picked SUBSET of a multi-skill source — the host's checkbox
  picker over the `multiple-candidates` list. Entries match a skill's
  name or its root-relative path (same rules as `skill`); duplicates
  collapse. Batch semantics mirror `all` (skip existing, pre-validate,
  native engine). Takes precedence over `all` and over a selector
  embedded in the source string; ignored when an explicit `skill`
  option is present.
- `source` (`string`): Raw source spec — see [`parseSkillInstallSpec`](https://zidane.si/docs/reference/skills.md#parseSkillInstallSpec) for the grammar.

Source: [src/skills/install.ts:320](https://github.com/Tahul/zidane/blob/main/src/skills/install.ts#L320)

### `SkillInstallResult`

Interface.

Outcome of [`installSkillFromSpec`](https://zidane.si/docs/reference/skills.md#installSkillFromSpec).

```ts
interface SkillInstallResult
```

**Properties**

- `dir?` (`string`): Absolute target directory (native path only — the CLI owns its layout).
- `engine` (`'native' | 'skills-cli'`): `skills-cli` when delegated to the skills.sh CLI, else `native`.
- `failed?` (`readonly string[]`): Candidate paths excluded from an `all` import because they failed validation.
- `installed` (`readonly string[]`): Final skill name(s) installed. The CLI path reports the spec, not names.
- `provenance?` (`string`): Where the skill came from, for example `github:owner/repo@main` or `npm:pkg@1.2.3`. Set on the native path.
- `skipped?` (`readonly string[]`): Names skipped during an `all` import because they were already installed.

Source: [src/skills/install.ts:369](https://github.com/Tahul/zidane/blob/main/src/skills/install.ts#L369)

### `SkillInstallSpec`

Interface.

A parsed skill install source, ready for the install pipeline.

```ts
interface SkillInstallSpec
```

**Properties**

- `cliSpec` (`string`): The spec string to hand the `skills` CLI (its `add` argument).
  skills.sh URLs pass through verbatim — the CLI understands them —
  while parsed shorthand re-serializes to `owner/repo`.
- `skill?` (`string`): Skill name selector for multi-skill sources. From a skills.sh URL's
  third segment or the caller's explicit pick after a
  `multiple-candidates` error.
- `source` (`InstallSource`): Where the skill comes from: a GitHub repository, an npm package or another supported source.

Source: [src/skills/install.ts:124](https://github.com/Tahul/zidane/blob/main/src/skills/install.ts#L124)

### `SkillInstallTarget`

Interface.

What installs one skill: a source spec, and the skill's selector in it.

```ts
interface SkillInstallTarget
```

**Properties**

- `rel` (`string`): The skill's root-relative folder in that source, or `.` when the source is the skill.
- `source` (`string`): A spec for `installSkillFromSpec` (`owner/repo:skills`).

Source: [src/skills/sources.ts:101](https://github.com/Tahul/zidane/blob/main/src/skills/sources.ts#L101)

### `SkillPathCandidate`

Interface.

A skill folder found in a list of files.

```ts
interface SkillPathCandidate
```

**Properties**

- `dir` (`string`): The skill's folder, relative to the source (`skills/engineering/tdd`).
- `name` (`string`): The folder's name, or the source's name for a skill at the root.
- `rel` (`string`): Root-relative folder (`engineering/tdd`), or `.` for a skill at the root: the installer's selector.

Source: [src/skills/candidates.ts:18](https://github.com/Tahul/zidane/blob/main/src/skills/candidates.ts#L18)

### `SkillPick`

Interface.

A skill picked for install, as a wizard draft or a picker keeps it.

```ts
interface SkillPick
```

**Properties**

- `install` (`SkillInstallTarget`): What installs it.
- `key` (`string`): Unique per skill: `skillPickKey(install)`.
- `label` (`string`): The repository or package that holds it, for grouping and recaps.
- `name` (`string`): The skill's name.

Source: [src/skills/sources.ts:139](https://github.com/Tahul/zidane/blob/main/src/skills/sources.ts#L139)

### `SkillPickGroup`

Interface.

The picks of one source: one install for all of them.

```ts
interface SkillPickGroup
```

**Properties**

- `label` (`string`): The repository or package, as a recap names it.
- `names` (`readonly string[]`): Their names.
- `rels` (`readonly string[]`): The selectors of the picked skills.
- `source` (`string`): The spec to install from.

Source: [src/skills/sources.ts:151](https://github.com/Tahul/zidane/blob/main/src/skills/sources.ts#L151)

### `SkillResource`

Interface.

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.

```ts
interface SkillResource
```

**Properties**

- `path` (`string`): Relative path from skill directory
- `type` (`'other' | 'reference' | 'script' | 'asset'`): Resource type inferred from directory

Source: [src/skills/types.ts:13](https://github.com/Tahul/zidane/blob/main/src/skills/types.ts#L13)

### `SkillsConfig`

Interface.

Skill system configuration (`AgentOptions.skills`).

```ts
interface SkillsConfig
```

**Properties**

- `allowShellInterpolation?` (`boolean`): Run `!`-backtick shell interpolation (`!\`cmd\``) in skill instructions
  on activation. Default `true`. Note: skill content can be third-party
  input, and executing its embedded commands at activation time bypasses
  the allowed-tools gate — set `false`for locked-down hosts. When off,
  each`!\`cmd\``is replaced with a`\[shell interpolation disabled]`placeholder. For interactive approval instead of a hard on/off, wire`approveShellInterpolation\` (see the skills tool options).
- `approveShellInterpolation?` (`(request: ShellInterpolationApproval) => boolean | Promise<boolean>`): Optional approval gate for `!`cmd\`\` shell interpolation. Invoked once
  per (skill, command) before the command runs; return `false` to skip it
  (replaced with a `[shell interpolation not approved]` placeholder). When
  omitted, interpolation runs without prompting. Hosts
  (TUI/GUI) can wire this to an interactive confirmation. Only consulted
  when `allowShellInterpolation` is not `false`.
- `catalog?` (`SkillConfig[]`): Pre-resolved skill catalog. Bypasses `parseSkillFile` entirely:
  entries are taken as-is and merged with anything `scan` / `write`
  discovers. Catalog entries seed the resolution map first, so they
  shadow disk-discovered skills on name collision (a diagnostic is
  attached to the catalog winner; same shape as the disk-vs-disk
  collision case).

  Intended for host SDKs that have already done discovery — e.g. a
  durable worker that holds the catalog in memory but runs
  `skills_read` / `skills_run_script` against a remote runner's
  disk. `baseDir` / `location` on each entry must point to paths
  resolvable on whichever side runs the skill tools (NOT the worker,
  in a worker/runner split).

  `exclude` and `enabled` still apply (consistent filter contract).
- `enabled?` (`boolean | string[]`): Control which skills are active.
  - `true` (default): all discovered skills are enabled
  - `false` or `[]`: fully disable the skills system (no resolution, no catalog, no hooks)
  - `string[]`: allowlist — only skills with matching names are enabled
- `exclude?` (`string[]`): Skill names to exclude from the catalog
- `maxActive?` (`number`): Cap on concurrently active skills per run. Default `undefined` (unlimited).
  Attempts to activate past the cap throw from `skills_use`.
- `scan?` (`(string | { path: string; source: SkillSource })[]`): Directories to scan for SKILL.md files. A bare string infers its source
  from the path (under `$HOME` → `'user'`); pass `{ path, source }` to tag
  it explicitly.
- `scriptTimeoutMs?` (`number`): Script timeout for `skills_run_script`, in milliseconds. Default `60000`.
- `skipDefaultPaths?` (`boolean`): Skip default scan paths (\~/.agents/skills, .zidane/skills, etc.)
- `tool?` (`boolean`): Auto-inject `skills_use` / `skills_read` / `skills_run_script` tools
  when the catalog is non-empty. Default `true`. Set `false` to opt out
  (the system prompt will then instruct the model to use its file-read
  tool instead).
- `trustProjectSkills?` (`boolean`): When `false`, skills with `source: 'project'` are skipped during
  resolution with a diagnostic. Default `true` (preserves existing behavior).
  Useful for host SDKs handling untrusted repositories.
- `write?` (`SkillConfig[]`): Dynamic skills written to disk at agent start, then loaded normally

Source: [src/skills/types.ts:102](https://github.com/Tahul/zidane/blob/main/src/skills/types.ts#L102)

### `SkillSourceInfo`

Interface.

Where the skills of a listing come from.

```ts
interface SkillSourceInfo
```

**Properties**

- `avatarUrl?` (`string`): The owner's avatar.
- `collection?` (`boolean`): A pack: the skills come from the SKILL.md links of its README, in other repositories.
- `description?` (`string`): The repository's or package's own description.
- `imageUrl?` (`string`): The repository's preview image, as GitHub draws it for links (2:1).
- `kind` (`'github' | 'npm'`): A GitHub repository, or an npm package.
- `label` (`string`): `owner/repo`, `owner/repo/path`, or the package name.
- `owner?` (`string`): GitHub owner.
- `path?` (`string`): The folder that was listed, when not the root.
- `ref?` (`string`): The branch or tag that was listed.
- `repo?` (`string`): GitHub repository.
- `stars?` (`number`): GitHub stars.
- `url` (`string`): The page of the repository or of the package.

Source: [src/skills/sources.ts:73](https://github.com/Tahul/zidane/blob/main/src/skills/sources.ts#L73)

### `SkillSourceListing`

Interface.

The skills of one source, as `browseSkillSource` lists them.

```ts
interface SkillSourceListing
```

**Properties**

- `input` (`string`): What was typed or picked.
- `preselect?` (`readonly string[]`): Names to check at once: the skill that a skills.sh URL or a `--skill` flag names.
- `skills` (`readonly SkillSourceSkill[]`): The skills, sorted by path.
- `source` (`SkillSourceInfo`): Where the skills come from.
- `truncated?` (`boolean`): GitHub cut the file list short, so some skills are missing.

Source: [src/skills/sources.ts:125](https://github.com/Tahul/zidane/blob/main/src/skills/sources.ts#L125)

### `SkillSourceSkill`

Interface.

One skill of a listing.

```ts
interface SkillSourceSkill
```

**Properties**

- `description` (`string`): The description in its SKILL.md. Empty when it has none.
- `from?` (`string`): In a pack: the repository that holds it, as `owner/repo`.
- `install` (`SkillInstallTarget`): What installs it.
- `name` (`string`): The name in its SKILL.md, or its folder's name.
- `path` (`string`): Its folder, relative to the listed source.
- `url?` (`string`): Its SKILL.md on the web.

Source: [src/skills/sources.ts:109](https://github.com/Tahul/zidane/blob/main/src/skills/sources.ts#L109)

### `SkillTokenWeights`

Interface.

Estimated token cost of one skill, from [`skillTokenWeights`](https://zidane.si/docs/reference/skills.md#skillTokenWeights).

```ts
interface SkillTokenWeights
```

**Properties**

- `body` (`number`): Tokens of its SKILL.md instructions, paid once it is activated.
- `catalog` (`number`): Tokens of its skill-catalog entry, paid on every run.

Source: [src/skills/weights.ts:13](https://github.com/Tahul/zidane/blob/main/src/skills/weights.ts#L13)

### `SkillValidationIssue`

Interface.

A single issue surfaced by skill-authoring validation.
Distinct from `ValidationResult` in `tools/validation.ts` (tool-input JSON
schema validation) — different domain, different module.

```ts
interface SkillValidationIssue
```

**Properties**

- `code` (`string`): Stable machine-readable code for consumer matching.
- `field?` (`string`): Frontmatter field name the issue relates to, when applicable.
- `message` (`string`): Human-readable description.

Source: [src/skills/validate.ts:42](https://github.com/Tahul/zidane/blob/main/src/skills/validate.ts#L42)

### `SkillValidationResult`

Interface.

Result of [`validateSkillForWrite`](https://zidane.si/docs/reference/skills.md#validateSkillForWrite).

```ts
interface SkillValidationResult
```

**Properties**

- `errors` (`SkillValidationIssue[]`): Every issue found. Empty when the skill is valid.
- `valid` (`boolean`): True when `errors` is empty.

Source: [src/skills/validate.ts:52](https://github.com/Tahul/zidane/blob/main/src/skills/validate.ts#L52)

### `SourcedScanPath`

Interface.

A scan path paired with the source tag that should be attached to any skills found in it.

```ts
interface SourcedScanPath
```

**Properties**

- `path` (`string`): Directory to scan for skill folders.
- `source` (`SkillSource`): Source tag attached to every skill found under `path`.

Source: [src/skills/discovery.ts:865](https://github.com/Tahul/zidane/blob/main/src/skills/discovery.ts#L865)

## Types

### `ActivationVia`

Type.

How a skill was activated. Surfaced in `skills:activate` hook ctx.

```ts
type ActivationVia = 'model' | 'explicit' | 'resume'
```

Source: [src/skills/activation.ts:31](https://github.com/Tahul/zidane/blob/main/src/skills/activation.ts#L31)

### `DeactivationReason`

Type.

Reason a skill was deactivated. Surfaced in `skills:deactivate` hook ctx.

- `'run-end'` — the implicit deactivate-all pass at each `run()` boundary.
- `'explicit'` — the host called `agent.deactivateSkill(name)`.
- `'reset'` — `agent.reset()` cleared the activation state along with
  everything else.
- `'model'` — the model called `skills_use({ mode: 'deactivate', name })`
  to release a skill's allow-list mid-run. Session-resume rehydration
  honors the most recent `mode` per skill, so a model-driven deactivate
  sticks across the next turn (no surprise re-activation).

```ts
type DeactivationReason = 'run-end' | 'explicit' | 'reset' | 'model'
```

Source: [src/skills/activation.ts:45](https://github.com/Tahul/zidane/blob/main/src/skills/activation.ts#L45)

### `SkillBrowseErrorCode`

Type.

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.

```ts
type SkillBrowseErrorCode =
  | 'invalid-source'
  | 'not-found'
  | 'rate-limited'
  | 'fetch-failed'
```

Source: [src/skills/browse.ts:43](https://github.com/Tahul/zidane/blob/main/src/skills/browse.ts#L43)

### `SkillInstallErrorCode`

Type.

Machine-readable reason for a [`SkillInstallError`](https://zidane.si/docs/reference/skills.md#SkillInstallError).

```ts
type SkillInstallErrorCode =
  | 'invalid-source'
  | 'fetch-failed'
  | 'no-skill'
  | 'multiple-candidates'
  | 'validate-failed'
  | 'exists'
  | 'cli-failed'
  | 'io-failed'
```

Source: [src/skills/install.ts:49](https://github.com/Tahul/zidane/blob/main/src/skills/install.ts#L49)

### `SkillSource`

Type.

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.

```ts
type SkillSource = 'project' | 'user' | 'inline' | 'builtin'
```

Source: [src/skills/types.ts:28](https://github.com/Tahul/zidane/blob/main/src/skills/types.ts#L28)
