npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@crtrs/skill

v0.2.2

Published

Parser for hard skill files

Readme

Hard Skills

Parser, compiler and resolver for hard skills: markdown skills with embedded executable ```tool fences. One file declares what the model reads, what it may call, and what runs when it calls. The declared tools are the contract.

This package is the general purpose skill layer, engine-neutral by design: no model calls, no SDK bindings, zero runtime dependencies.

Built at CREATORS

Install

npm install @crtrs/skill

Skill file

---
skill: waitlist
description: Accept, reject and inspect waitlist signups.
---

Manage the product waitlist.

```tool
id: access
description: accept or reject a pending waitlist user
params:
  operation:
    type: string
    enum: accept, reject
    description: action to perform
    default: accept
  user:
    type: string
    format: email
    description: email of the user
run: uv run waitlist/handle.py --$operation $user
```

Prefer accepting users unless told otherwise.

A tool fence has four keys: id, description, params, run.

  • Params are flat JSON Schema: type, format, enum, description, default.
  • No default means required.
  • Anything outside the shape is a SkillError at parse time.
  • run becomes an argv, $param substitutes per word. No shell, ever:
'uv run waitlist/handle.py --$operation $user'
// { operation: 'accept', user: '[email protected]' }
['uv', 'run', 'waitlist/handle.py', '--accept', '[email protected]']

What a skill carries

A tool that runs uv run handle.py needs handle.py. includes: in the frontmatter is where a skill says so — comma separated, each entry a path relative to the skill's directory:

---
skill: waitlist
description: Accept, reject and inspect waitlist signups.
includes: handle.py, queries, Makefile
---

An entry is whatever is at that path. Nothing is read into its name — an extension is not what makes something a file — so the filesystem answers: a file is that file, a directory is everything under it, however deep. A path that is absolute, starts with ~, or climbs out with .. is a SkillError: a skill reaches inside its own directory and nowhere else, which is what lets it travel.

parseSkillFile checks them. A declared include that is not there is a broken skill and fails when the file is read, the same as a $typo in a run template — not the first time a model calls the tool.

skill.includes;          // ['handle.py', 'queries', 'Makefile'] -- as declared
includedFiles(skill);    // ['Makefile', 'handle.py', 'queries/nested/deep.sql', ...]

includedFiles expands the declaration into the actual files, relative to skill.workdir and sorted: the list to pack, to digest, or to copy when a skill moves. Symlinks are refused rather than followed — what one points at is not part of the skill, and it would arrive somewhere else as a dangling name.

What a skill needs on the machine

Because there is no shell, the first word of a run is the executable — never an alias, an expansion, or a second command hiding behind a ;. So what a skill will spawn is knowable before it runs once:

programs(skill);
// [{ name: 'uv', dynamic: false, tools: ['dump', 'restore'] }]

That is the list to check against a PATH before someone approves a skill, so "this needs uv, which you do not have" arrives before the run and not three tool calls into it. A run whose first word holds a param (run: $cmd --flag) has no program until the call is made, and comes back dynamic.

When a word needs a space

Whitespace ends a word, which is all a command line ever needs — until one word has to hold a space. Then run is a list, and each item is exactly one argv word, verbatim:

run:
  - awk
  - -v
  - f=$file
  - BEGIN{while((getline l<f)>0){n++; print n": "l}}

There is no quoting. Quotes would mean a character that sometimes groups words and sometimes is just itself, and a run template carries other languages — python, awk, sed — that spend quotes on their own strings. python -c print('hi') passes print('hi'), quotes and all. A list has nothing to escape and nothing to strip: what is written is what the process receives.

The shell's other characters are ordinary too. |, >, ;, &&, * and backticks are just characters in an argv word — no pipe, no redirect, no glob, and no expansion of anything but the params the tool declared. A value substitutes inside its word and can never split it.

$$ is a literal dollar, and it is the one escape there has to be: without it a $0 in an awk program would read as a param named 0.

A $name that was never declared is a SkillError at parse time, not a surprise the first time a model calls the tool.

Use

The whole loop is four lines. The package owns everything except the inference call:

const { parseSkillFile, compile, resolve } = require('@crtrs/skill');

const skill = parseSkillFile('SKILL.md');
const system = compile(skill);            // prose + call protocol, model-facing
const output = await myInference(system); // your model, your way
const call = resolve(skill.tools, output);

compile emits the system text: the prose with fences collapsed to tool ids, then the call protocol with the tool catalog in JSON Schema. Same parse feeds both, so what the model is told and what resolve accepts cannot drift.

resolve takes raw model text or an already-structured call (an SDK's native tool-calling output). It validates against the tool's schema: defaults filled, required enforced, enums checked, primitives coerced.

if (call === null) {
  // no tool call in the text. What that means is your loop's decision.
} else {
  call.tool.id; // 'access'
  call.args;    // { operation: 'accept', user: '[email protected]' }
  call.argv;    // ['uv', 'run', 'waitlist/handle.py', '--accept', '[email protected]']
}
// Invalid calls throw SkillError, worded to feed straight back to the model.

On an SDK's native tool-calling, skip compile: send skill.rendered as system text and hand tools over directly. tool.schema is already valid JSON Schema, the shape Anthropic, OpenAI and MCP take as input schema:

const tools = skill.tools.map((tool) => ({
  name: tool.id,
  description: tool.description,
  input_schema: tool.schema,
}));

API: parseSkill(source, { file, workdir }), parseSkillFile(path), includedFiles(skill), programs(skill), compile(skill), resolve(tools, answer), SkillError. Full types in index.d.ts.

License

Apache License 2.0. This distribution includes a NOTICE file; per Section 4(d) of the license, any derivative work you distribute must include a readable copy of its attribution notices, crediting CREATORS (https://www.creators.industries/research/hard-skills) as the origin of this code and of the hard-skill specification (markdown skills with embedded executable ```tool fences).