@drover/skills
Agent Skills loader + registry + skill_load / skill_resource tools.
Implements the Agent Skills specification.
SkillSpec
interface SkillSpec {
name: string; // 1-64 chars, [a-z0-9-]+
description: string; // 1-1024 chars
license?: string; // optional
compatibility?: string; // optional, ≤500 chars
metadata: Readonly<Record<string, string>>; // typed metadata field
allowedTools?: string; // raw `allowed-tools` value
body: string; // markdown after frontmatter
path: string; // abs path to SKILL.md
dir: string; // abs path to skill root
extra: Readonly<Record<string, unknown>>; // non-spec frontmatter keys
}parseSkill
function parseSkill(
contents: string,
filepath: string,
opts?: { mode?: "strict" | "lenient"; skipParentDirCheck?: boolean },
): { spec: SkillSpec; warnings: readonly SkillIssue[] };
interface SkillIssue {
field: string;
message: string;
}Parses YAML frontmatter + body. Validates per the spec:
name: 1-64 chars,[a-z0-9-]+, no leading/trailing/consecutive hyphens, must equal parent directory name (skip viaskipParentDirCheck).description: 1-1024 chars.compatibility: ≤500 chars.metadata: string→string map.allowed-tools: string OR YAML list of strings.
Strict mode (default) throws SkillLoadError on any violation. Lenient
mode returns the spec with warnings populated.
parseSkillFile
function parseSkillFile(contents: string, filepath: string): SkillSpec;Strict-mode convenience around parseSkill. Returns the spec or throws.
parseAllowedTools
function parseAllowedTools(raw: string | undefined): readonly string[];Tokenises an allowed-tools string. Accepts space- and comma-separated
forms; preserves parenthesised sub-patterns like Bash(git:*) as one
token.
scanSkillDirs
function scanSkillDirs(
roots: readonly string[],
opts?: {
filename?: string; // default "SKILL.md"
maxDepth?: number; // default 3
skipPrefixes?: readonly string[]; // default [".", "_", "node_modules"]
mode?: "strict" | "lenient"; // default "strict"
onIssue?: (filepath: string, issues: readonly SkillIssue[]) => void;
},
): Promise<readonly SkillSpec[]>;Recursively scans for skill files. Each leaf dir holds at most one
skill — descent stops once found. Dedups by name (first wins).
Absent roots are silently skipped. Strict mode throws; lenient mode
reports parse failures via onIssue and continues.
createSkillRegistry
function createSkillRegistry(skills: readonly SkillSpec[]): SkillRegistry;
interface SkillRegistry {
get(name: string): SkillSpec | undefined;
has(name: string): boolean;
list(): readonly SkillSpec[];
}In-process directory. First-wins dedup matches scanSkillDirs.
listSkillResources
function listSkillResources(spec: SkillSpec): Promise<{
scripts: readonly string[];
references: readonly string[];
assets: readonly string[];
other: readonly string[];
}>;Enumerates supporting files one level deep under spec.dir. Returns
basenames relative to each subdirectory (scripts/extract.py →
extract.py under scripts). other collects top-level files outside
the three well-known dirs (e.g. README.md, LICENSE).
readSkillResource
function readSkillResource(spec: SkillSpec, relativePath: string): Promise<string>;Reads a file under the skill root. Refuses absolute paths and any ..
segments — access stays inside spec.dir.
skillLoadTool
function skillLoadTool(opts: {
registry: SkillRegistry;
allowed: readonly string[];
hintResources?: boolean; // default true
}): ToolDef<{ name: string }>;The activation half of progressive disclosure — auto-injected when
spec.skills is declared. allowed is the spec’s skill list; names
outside it return a deny result (the model can recover).
When hintResources is true, the response body is suffixed with a
pointer to skill_resource if the skill ships scripts, references, or
assets.
skillResourceTool
function skillResourceTool(opts: {
registry: SkillRegistry;
allowed: readonly string[];
maxBytes?: number; // default 65536
}): ToolDef<{ name: string; resource?: string }>;The on-demand half of progressive disclosure — also auto-injected when
spec.skills is declared. Without resource, lists supporting files;
with resource, reads one (sandboxed to the skill directory).
Resources larger than maxBytes return a truncation notice.
renderSkillsBlock
function renderSkillsBlock(
registry: SkillRegistry,
allowed: readonly string[],
): string;Builds the “Available skills” block appended to the system prompt.
Lists name + one-line description (plus a compatibility hint when
set) for each allowlisted skill the registry knows about. Empty string
when nothing matches.
SkillLoadError
class SkillLoadError extends Error {
readonly issues: readonly SkillIssue[];
}Thrown by parseSkill in strict mode. issues lists every violation
detected; the error message concatenates them with field: message.