Registries
Load skills from skills.sh, from disk, or from your own private registry using the .well-known/agent-skills protocol.
Skills can come from three places: inline in your code, a file on disk, or a remote registry. Registries are how you share skills across repos, teams, or the public community.
skills.sh — The Built-in Community Registry
skills.sh is the community registry for SKILL.md files. Crux exports it as the skillsSh registry value:
import { skill, skillsSh } from '@crux/core/skill'
const seo = skill.fromRegistry(skillsSh, 'mattpocock/skills/seo-analysis')
const research = skill.fromRegistry(skillsSh, 'owner/repo/skill-name')Registry skills use the same shape everywhere: pass a registry value and the skill path inside that registry.
Content is fetched lazily on the first prompt.resolve() and cached in process memory for the lifetime of the runtime — subsequent resolves do not re-fetch.
Custom Registries
Host your own skills on any HTTPS server that implements the .well-known/agent-skills/ protocol. Useful for shared internal skills — brand voice, legal review, compliance checks — that you don't want to publish publicly.
import { skill, registry } from '@crux/core/skill'
const internal = registry({
name: 'internal',
baseUrl: 'https://skills.mycompany.com',
auth: () => process.env.SKILLS_TOKEN,
})
const brand = skill.fromRegistry(internal, 'brand-guidelines')
const legal = skill.fromRegistry(internal, 'legal/disclosure-language')Registry configuration
| Field | Type | Description |
|---|---|---|
name | string | Stable registry name used in skill ids and local Project Model output |
baseUrl | string | Registry base URL — requests go to <baseUrl>/.well-known/agent-skills/<path> |
auth | () => string | undefined | Optional callback returning a bearer token. Called per request, so it can read fresh env vars or refreshed tokens. |
registry() returns a registry value. Pass that value to skill.fromRegistry(internal, 'brand-guidelines'); do not repeat registries in crux.config.ts. Crux does not scan project files at runtime to find custom registries, so create registry-backed skills from the registry value in the module that uses them.
Loading from Disk
For skills you own and version-control inside your repo, use skill.fromFile():
const seo = skill.fromFile('./skills/seo-analysis/SKILL.md')This reads synchronously at import time and automatically picks up any sibling references/*.md files. See Writing skills for the directory layout.
Disk-loaded skills don't need a registry — they're already available the moment the module imports.
Error Handling
All three loaders throw SkillLoadError (a tagged error) when something goes wrong:
| Cause | When |
|---|---|
| File not found | skill.fromFile() with a bad path |
| Invalid frontmatter | Missing name or description, or malformed YAML |
| Missing path | skill.fromRegistry(internal) without a registry-local skill path |
| Unknown registry value | Passing a value that is not a Registry |
| Network failure / 404 | Registry fetch fails at resolve time |
For fromFile and inline, errors surface at import time. For fromRegistry, remote fetch errors surface at the first prompt.resolve() because registry content is loaded lazily.
import { SkillLoadError } from '@crux/core/skill'
try {
await myPrompt.resolve({ input })
} catch (err) {
if (err instanceof SkillLoadError) {
console.error(`Skill ${err.skillId} failed: ${err.reason}`)
}
throw err
}Caching
Registry fetches are cached in memory for the lifetime of the process, keyed by the full identifier. There is no TTL — if you publish a new version of a skill, restart the runtime to pick it up. The instrumentation hooks onSkillCacheHit and onSkillCacheMiss let you observe cache behavior in production (see the API reference).