PAIPAI

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.

server/skills/skills.test.ts
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

server/skills/generated/index.ts
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:

WhereRule
Skills directoryHolds 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.mdStarts with a --- frontmatter block that is a YAML mapping.
nameRequired; equals the folder name; lowercase letters, digits, and single hyphens, not at the start or end; at most 64 characters.
descriptionRequired; at most 1,024 characters.
license, compatibilityStrings when present; compatibility at most 500 characters.
metadataMaps keys to string values; pai-requires, when present, is sandbox.
FilesAt 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/.
Outputout 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 description containing < or >.
  • A SKILL.md body, frontmatter excluded, over 500 lines.
  • A Markdown link in SKILL.md, or a backticked references/…, assets/…, or scripts/… 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.

Learn more

On this page