generateSkills()
Validate skill folders and write the module defineSkills() reads, from Node.
generateSkills() is the Node API behind pai generate skills. Use it when a test or build script should check or write the module itself; otherwise run the CLI.
import { fileURLToPath } from "node:url";
import { generateSkills } from "@pai/skills/generate";
// The same check as `pai generate skills --check`; use one or the other in CI.
it("ships a module that matches the skill folders", async () => {
const dir = fileURLToPath(new URL(".", import.meta.url)); // this folder
const result = await generateSkills({ dir, check: true });
expect(result.warnings).toEqual([]);
expect(result.status).toBe("unchanged");
});Import
import {
formatDiagnostic,
generateSkills,
SkillValidationError,
} from "@pai/skills/generate";Node only: it reads the filesystem. Your agent imports the generated module, never this entry point.
Signature
function generateSkills(options: GenerateSkillsOptions): Promise<GenerateSkillsResult>;Parameters
options: GenerateSkillsOptions
dir: string
Directory whose child directories are skills, relative to the working
directory. Every child directory must hold a SKILL.md; see
Skill folder rules.
out?: string
Module to write, relative to the working directory. Defaults to
<dir>/generated/index.ts.
check?: boolean
Compare instead of writing, and report missing or stale when the
module is out of date.
Returns
A GenerateSkillsResult.
status: "written" | "unchanged" | "stale" | "missing"
written or unchanged when writing; unchanged, stale, or missing
with check. The comparison is byte for byte, so keep formatters off the
module.
out: string
Absolute path of the module.
skills: string[]
Skill names, in module order.
warnings: SkillDiagnostic[]
Problems that did not stop generation, such as an unknown frontmatter key
or a linked file that does not exist. pai generate skills --check fails
on them.
Generated module
export type SkillName = "invoice-review" | "matter-intake";
export const skills: readonly SkillEntry<SkillName>[];Pass skills to defineSkills(), which documents SkillEntry. Commit the module and import it from server code.
Errors
Any invalid skill rejects with SkillValidationError before anything is
written. Its message lists every error, one per line, such as
error: invoice-review/SKILL.md: description is required, and its
diagnostics hold every problem as { level, skill, path?, message };
formatDiagnostic() prints one in that form, as the CLI does. A
dir that is missing, not a directory, unreadable, or itself a skill folder
rejects with a plain Error.
Skill folder rules
Generation fails on any of these, naming the skill and file:
| Where | Rule |
|---|---|
| Skills directory | Holds no SKILL.md itself. Every child directory holds a file named exactly SKILL.md. Dot-directories, node_modules, __pycache__, the directory holding the output module, and top-level files are skipped. A symlinked directory is an error. |
SKILL.md | Starts with a --- frontmatter block that is a YAML mapping. |
name | Required; equals the folder name; lowercase letters, digits, and single hyphens, not at the start or end; at most 64 characters. |
description | Required; at most 1,024 characters. |
license, compatibility | Strings when present; compatibility at most 500 characters. |
metadata | Maps keys to string values; pai-requires, when present, is sandbox. |
| Files | At most 256 KiB each, and no symlinks. A plain skill's files are UTF-8 text, with no top-level scripts/ folder and no file with an executable permission bit. A sandbox skill may also ship those, and binary files, but executable files must be under scripts/. |
| Output | out is not inside a skill directory. |
Inside a skill, dotfiles, node_modules, __pycache__, .pyc files, and a top-level evals/ folder are skipped.
These produce warnings, returned in warnings. pai generate skills --check fails on them; plain generation and --watch only print them:
- A frontmatter key outside the specification (
name,description,license,compatibility,metadata,allowed-tools). - A
descriptioncontaining<or>. - A
SKILL.mdbody, frontmatter excluded, over 500 lines. - A Markdown link in
SKILL.md, or a backtickedreferences/…,assets/…, orscripts/…path in it, naming a file that is not in the skill. Paths are case-sensitive; links to folders and anything in a fenced code block are not checked.