A predictable hub for local and subscribed AI agent skills.
skillx keeps hand-maintained local skills and subscribed remote skills in one repository. It validates a YAML source list, clones configured repositories, discovers every directory containing SKILL.md, filters the discovered skills, then writes managed copies into skills/ with a manifest and run summary.
- Local skill storage can be subscribed from repository-relative directories.
- Remote skill subscriptions from Git repositories.
- Recursive
SKILL.mddiscovery, including dot-prefixed directories such as.curated/. flatandnestedoutput modes for generated skill directories.- Include and exclude glob filters per source.
- Optional content-hash deduplication across sources, with source order deciding the winner.
- Conservative cleanup based on
.skills-sync/manifest.json. - Dry-run support for validating clones, filters, and target paths before writing generated skills.
- Symlink sync from
skills/into one or more agent runtime directories for multi-environment reuse. - GitHub Actions automation for post-commit skill sync and release automation.
- Node.js
>=20 - pnpm
10.32.1 - ECMAScript modules
- YAML parsing with
yaml - Glob matching with
minimatch - Testing with Vitest
- Linting with ESLint and
@antfu/eslint-config - Release automation with release-please
skills-sources.yaml
|
v
scripts/sync-skills.mjs
|
+-- clone enabled sources into .skills-sync/tmp/ with bounded concurrency
+-- discover directories containing SKILL.md
+-- apply includes/excludes filters
+-- plan generated targets with flat or nested mode
+-- stage planned copies and safely expand source-local symlinks
+-- atomically replace managed directories in skills/
+-- write .skills-sync/manifest.json and run summaries ```txt
skills/
|
v
scripts/link-skills.mjs
|
+-- scan top-level generated skills once
+-- build one task per skill and target directory
+-- process the queue with bounded concurrency
+-- create, skip, warn, or repair symlinks independently
```
The sync process plans all enabled sources before writing generated output, then commits planned copies into skills/. It removes only generated directories that were previously recorded in the manifest and are no longer active. Unknown directories and protected ids are left untouched. Symlinks from subscribed repositories are expanded into real files or directories when they resolve inside the source repository. Unsafe, circular, broken, or unsupported symlinks are skipped and reported in the sync summary.
The link process never copies skill content. It creates `<target>/<skill-name> -> <repo>/skills/<skill-name>` directory symlinks for top-level generated skills so multiple agent runtimes can reuse the same generated skill set.
Install dependencies:
pnpm installValidate the subscription configuration:
pnpm validatePreview a sync without changing generated skill directories:
pnpm sync:dry-runRun a real sync:
pnpm syncLink generated skills into agent runtime directories:
pnpm run sync:link -- --targets ~/.agents/skills,../other-agents/skillsRun tests and linting:
pnpm test
pnpm lintpnpm sync:dry-run still clones remote repositories, so it requires network access and may take longer than pure local validation.
Skill subscriptions are configured in skills-sources.yaml.
version: 1
defaults:
branch: main
path: skills
mode: flat
enabled: true
preserveOnFailure: false
cloneTimeoutMs: 300000
cloneMaxAttempts: 3
sourceConcurrency: 3
includes: []
excludes: []
deduplicate: true
sources:
- id: skillx
type: local
location: skillx
path: .
- id: openai
type: remote
location: https://github.com/openai/skills.git
branch: main
path: skillsdeduplicate is a top-level switch that removes duplicate skills across all configured sources.
deduplicate: trueOr the explicit object form:
deduplicate:
enabled: true
strategy: content-hashRules:
- Deduplication is disabled by default for backward compatibility.
- Only the bytes of
SKILL.mdare compared. - Names, source ids, directory paths, and generated target ids do not affect duplicate detection.
- Source priority follows the
sourcesarray order inskills-sources.yaml. - When two skills have identical
SKILL.mdcontent, the first source wins and later duplicates are skipped. - When deduplication is disabled, behavior is unchanged and all selected skills are generated.
| Field | Required | Description |
|---|---|---|
id |
Yes | Source id. Must use lowercase kebab-case letters and numbers, and must not match a protected id. |
type |
No | Source type, either remote or local. Defaults to remote. |
location |
Yes | For remote, a Git URL passed to git clone. For local, a repository-relative source directory. |
branch |
No | Branch to clone for remote sources. Defaults to main. Ignored by local sources. |
path |
No | Directory inside the source location that contains skills. Defaults to skills. Use . when the source location itself is a skill root. |
mode |
No | flat writes each discovered skill as its own generated directory. nested keeps skills grouped under skills/<source-id>/. |
enabled |
No | Disabled sources are skipped. Defaults to true. |
preserveOnFailure |
No | Keeps the previous manifest entry and generated targets when a source fails. Defaults to false, so stale generated skills are removed unless preservation is explicitly enabled. |
cloneTimeoutMs |
No | Git clone timeout in milliseconds. Defaults to 300000. |
cloneMaxAttempts |
No | Maximum clone attempts for retryable Git failures. Defaults to 3. |
sourceConcurrency |
No | Maximum number of sources planned concurrently. Defaults to 3. |
includes |
No | Relative glob patterns for selected skill paths. Empty means include all discovered skills. |
excludes |
No | Relative glob patterns to remove from the selected set. Excludes take precedence over includes. |
Top-level fields:
| Field | Required | Description |
|---|---|---|
deduplicate |
No | Global deduplication config. Accepts true, false, or { enabled, strategy }. The only supported strategy is content-hash. |
pnpm run sync:link scans top-level generated directories in skills/ once and links every skill into one or more target directories. Each operation is independent, so a failure in one target does not roll back or block the others.
Target resolution priority:
--targets <paths>CLI option.AGENTS_DIRSenvironment variable.agents.config.ts,agents.config.js, oragents.config.jsonin the repository root.$HOME/.agents.
Targets can be comma-separated strings or string arrays. ~ expands to the current home directory, relative paths resolve from the repository root, and duplicate absolute paths are removed.
// agents.config.js
export default {
link: {
targets: ['~/.agents/skills', '../other-agents/skills'],
concurrency: 48,
},
}The config may also use top-level targets and concurrency; nested link values take precedence when present. Link concurrency defaults to 48 and must be an integer from 1 to 64.
Link behavior is idempotent:
- Existing correct symlinks are skipped.
- Missing symlinks are created.
- Existing wrong symlinks are repaired only with
--force; otherwise they are reported as warnings. - Existing non-symlink paths are never overwritten, even with
--force. --dry-runreports planned creates or repairs without changing the filesystem.
macOS and Linux create directory symlinks directly. On Windows, the command attempts a real directory symlink and reports a clear error if Developer Mode or administrator privileges are required; it does not silently fall back to copying.
This repository uses flat as its configured default in skills-sources.yaml, so generated skill paths are source-of-truth snapshots of the subscribed repositories. Skills removed upstream are removed from generated output on the next successful sync.
In flat mode, discovered skill paths are slugged into separate target ids:
openai + .curated/aspnet-core -> skills/openai-curated-aspnet-core
openai + . -> skills/openaiIn nested mode, all selected skills stay under the source id:
openai + .curated/aspnet-core -> skills/openai/.curated/aspnet-core
openai + . -> skills/openaiFlat mode checks for generated target collisions after slugging and fails fast when two source paths would map to the same target id.
When deduplication is enabled, flat or nested mode keeps only the first skill whose SKILL.md content hash appears in the configured source order.
Source ids no longer need local- or remote- prefixes. Use type to describe where a source comes from, and keep directory names focused on the skill collection name. This repository keeps hand-maintained local source inputs under skillx/ and subscribes to that whole tree as a single local source. Generated output still goes to skills/:
skillx/
escrcpy/
SKILL.md
viarotel/
SKILL.md
skills/
skillx-escrcpy/
SKILL.md
skillx-viarotel/
SKILL.mdThe validator always includes the default protected id subscribe. Additional protectedIds can be configured for generated directories that must never be removed as stale output, and source ids matching those patterns are rejected.
.
├── .github/workflows/ # Unified sync and release automation
├── .skills-sync/ # Generated manifest, summaries, and temporary sync workspace
├── scripts/
│ ├── link-skills.mjs # Symlink generated skills into agent runtime directories
│ ├── sync-skills.mjs # Main sync command
│ ├── validate-skills-config.mjs
│ └── skills-sync/ # Config, filesystem, git, logging, and summary helpers
├── skillx/ # Hand-maintained local source inputs
├── skills/ # Generated skill outputs
├── tests/ # Vitest coverage for config and sync planning logic
├── skills-sources.yaml # Subscription source list
├── release-please-config.json
└── package.jsonpnpm sync writes these generated files:
.skills-sync/manifest.json: managed source metadata, commits, target ids, selected skill paths, and per-skill content hashes for deduplication and preserved-source recovery..skills-sync/summary.md: human-readable sync report..skills-sync/summary.json: machine-readable sync report.
Temporary clone and staging data is created under .skills-sync/tmp/ and cleaned after each run unless --keep-temp is used.
| Command | Description |
|---|---|
pnpm validate |
Validate skills-sources.yaml. |
pnpm sync:dry-run |
Clone enabled sources and validate the sync plan without changing generated skill directories or the manifest. |
pnpm sync |
Sync enabled sources into skills/ and write generated metadata. |
pnpm run sync:link |
Link every top-level generated skill under skills/ into one or more agent runtime directories. |
pnpm test |
Run the Vitest test suite. |
pnpm lint |
Run ESLint. Remote generated skills are ignored by lint config. |
pnpm lint:fix |
Run ESLint with automatic fixes. |
Additional sync options are available through the script:
pnpm sync -- --config ./skills-sources.yaml --verbose
pnpm sync -- --dry-run --keep-temp
pnpm run sync:link -- --dry-run --targets ~/.agents/skills --verbose
pnpm run sync:link -- --targets ~/.agents/skills,../other-agents/skills --forcevalidate-skills-config.mjs supports --config for validating an alternate YAML file.
The test suite focuses on the sync contract:
- Config defaults and validation errors.
- Source id and protected id validation.
- Safe relative path validation.
- Recursive skill discovery.
- Source-local symlink expansion for skill data and scripts.
- Include and exclude filtering.
- Flat and nested target planning.
- Content-hash deduplication, disabled behavior parity, and preserved-source dedup recovery.
- Link target resolution, idempotent symlink handling, forced repair, and target-level failure isolation.
- Stale generated directory detection.
- Unknown directory reporting.
- Path traversal prevention.
Run it with:
pnpm test.github/workflows/sync-skills.yml runs on manual dispatch and on pushes to main that touch skills-sources.yaml or files under skillx/. It installs dependencies, validates the source config, runs tests and linting, runs pnpm sync, uploads the sync summary artifact, and commits changed skills/ content plus .skills-sync/manifest.json back to the default branch.
After a successful sync job, the same workflow runs release-please only when pnpm sync actually changed skills/ content or .skills-sync/manifest.json. Generated-only sync commits still do not retrigger the workflow because the push trigger is limited to skills-sources.yaml and skillx/**. Sync commits use chore: sync skills subscriptions, and release behavior is configured in release-please-config.json so those chore commits still create or update the release PR, changelog, and tag flow for the root Node package.
- Keep hand-maintained local source inputs under
skillx/<name>/; generated copies are written toskills/. - Add or modify local and remote subscriptions in
skills-sources.yamlwithtypeandlocation. - Run
pnpm validatebefore syncing. - Use
pnpm sync:dry-runwhen changing filters, paths, modes, or source ids. - Run
pnpm testfor changes to sync behavior. - Run
pnpm lintbefore opening a pull request or pushing shared changes.
Source ids and generated directory names should stay lowercase kebab-case. Avoid editing generated skill directories by hand; update the local source directory, source repository, or subscription filters instead.
Apache-2.0. See LICENSE for details.
