@patrity/skills
v0.2.1
Published
Assemble an opinionated Claude Code setup (.claude/ + CLAUDE.md) from skills.patrity.com bundles.
Readme
@patrity/skills
Assembles a Claude Code setup, .claude/ plus CLAUDE.md, out of the bundles published on skills.patrity.com. One command, no install step.
Quick start
pnpx @patrity/skillsNo install step: pnpx/npx/bunx all fetch and run the package straight from npm. With no
subcommand it runs init, the interactive wizard. Node 22 or newer is required; Windows, macOS
and Linux are all supported.
Commands
Every command accepts --dir <path> (default .), --registry <url> (default: the project's
lockfile, else https://skills.patrity.com), --yes (take defaults, never prompt), --force
(overwrite files and CLAUDE.md blocks edited since install) and --json (print one JSON object on
stdout and nothing else, which implies non-interactive).
| Command | Aliases | Positional | What it does |
| --- | --- | --- | --- |
| init | none (default) | none | The wizard: asks the base questions, offers profiles and the bundle list grouped by tag, shows a dry-run plan, and applies it once confirmed. Also --profile <name>, --with <slugs> (repeatable/comma-separated), --answer <axis>=<option> (repeatable). Re-run it to edit a project, not to start it over. See below. |
| add <slug…> | none | one or more bundle slugs | Adds bundles to an already-initialised project, re-rendering from the answers on record and pulling in anything the new bundles declare in dependsOn. |
| remove <slug…> | rm | one or more bundle slugs | Removes a bundle's marker blocks and the files it owns. Files it did not create are left alone. |
| update [slug…] | up | bundle slugs (optional) | Re-renders every installed bundle from the current registry. Naming slugs only narrows which must already be installed. The plan is always a full re-render, so upstream schema changes reach every bundle. |
| diff | none | none | Compares local files against the lockfile's hashes (your hand edits) and the lockfile's recorded sha against the registry (upstream drift). |
| list | ls | none | Prints the installed bundles, the recorded answers, and whether the install is behind the registry. |
add/remove/update all read the previous answers and bundle list from
.claude/skills.lock.json, so re-run init (not add) to change an axis answer.
Re-running init on an initialised project edits it. The answers on record are the starting
point. A --profile and any --answer flags override them, and interactively they come up
pre-selected. Every installed bundle stays ticked, so a bundle you added later with add
survives. Untick one in the wizard (or use remove) to take it out.
Two things the lockfile can outlive:
- A bundle that is no longer published.
update,addandremovewarn (ghost is installed but no longer in the registry; removing its files) and remove its files rather than refusing to run. Naming it explicitly, as inadd ghost, is still an error. - An answer the base no longer has. An axis that vanished upstream is dropped, an answer that is no longer one of its options falls back to the axis default, an axis added upstream gets its default, and each correction is reported and written back to the lockfile.
What it writes
Everything lands under the project root you point it at:
.claude/
├── skills/… # bundle skills
├── rules/… # bundle rules, globs rewritten for your app directory
├── hooks/… # hook scripts, made executable
├── settings.json # committed; deep-merged
├── settings.local.json # gitignored; permission allowlists
├── .env.example # committed; the variables the installed bundles read
└── skills.lock.json # committed; what is installed and at which sha, plus your answers
CLAUDE.md
.gitignore # a managed block, regenerated in placeThe .gitignore entries live in one managed block, between # >>> skills … and # <<< skills.
It holds .claude/settings.local.json, .claude/.env when any installed bundle declares
variables, and whatever paths the bundles themselves declare (a cache directory, say). Every run
regenerates the block from the current selection and leaves every line outside it alone; the block
goes away with the last entry. A bundle that declares variables also gets a group in
.claude/.env.example, one commented line per variable with its description and a sample value.
Copy it to .claude/.env and fill it in. The CLI never creates, reads or deletes that file. When
the last bundle declaring variables is removed the example is deleted, unless you edited it, in
which case it is left in place and reported.
CLAUDE.md is created with a title line if it does not exist yet. Each contribution is wrapped in
a marker pair:
<!-- skills:bundle:nuxt -->
- Nuxt 4 uses `app/` as srcDir…
<!-- /skills:bundle:nuxt -->Everything outside those markers is yours and is never touched.
settings.json and settings.local.json are treated differently because one is meant to be
committed and the other is per-machine: bundle hooks are unioned into settings.json by matcher
and command string (a changed timeout replaces the installed entry rather than duplicating it), and
permissions.deny entries are merged there too; permissions.allow entries go into
settings.local.json instead, and the CLI makes sure that file is gitignored.
Each bundle's contribution is recorded in the lockfile once per settings file, so remove takes its
hooks and permissions back out of the file it merged them into, so a fail-closed hook never stays
armed after its script is deleted, and update replaces its own entries instead of stacking new
ones beside them. Anything you added by hand survives remove and update unless it is
byte-identical to an entry the bundle contributed to that same file: an allow you keep in
settings.json stays put when the bundle's copy leaves settings.local.json.
The lockfile
.claude/skills.lock.json records the registry a project was set up against, the answers given to
every axis, which bundle owns which file (with its content hash), what each bundle merged into each
of the two settings files, and the hash of every CLAUDE.md marker block. add, remove, update, diff
and list all read it. There is no other place the CLI keeps state, and it is meant to be
committed.
Updating
Every file and CLAUDE.md block the CLI wrote is hashed at install time. On a later run:
- A bundle file whose content still matches the hash on record is
unchanged, a no-op. - One that was edited by hand since install is
protected: the CLI refuses to overwrite it and leaves it alone, unless you pass--force. - A file that already existed and was never installed by this CLI is a
conflict: interactively you are asked, per file, to skip or overwrite it; non-interactively (--yes) it is always skipped. A skipped conflict is never recorded in the lockfile, so it stays entirely yours. The summary names every conflicting and protected path, one per line, and--jsonreturns them inskipped. - A file a bundle used to ship and no longer does is deleted if it still matches its recorded hash, and kept with a warning if you edited it.
- A CLAUDE.md block you edited by hand no longer matches its recorded hash, so it is kept as-is
on
updateunless you pass--forceto replace it with the upstream version.
pnpx @patrity/skills diff # what drifted, locally and upstream. Nothing is written
pnpx @patrity/skills update # re-render and apply, with a confirmationNon-interactive use
Every prompt has a flag, so the wizard runs unattended in a script or a CI job:
pnpx @patrity/skills init --yes --profile nuxt-appStart from a profile and override individual answers, or skip profiles entirely and list bundles by hand:
pnpx @patrity/skills init --yes \
--profile library \
--answer pm=npm \
--answer deploy=vercel \
--with nuxt,nuxt-ui--answer is repeatable and takes axis=option pairs; --with takes a comma-separated list of
bundle slugs (also repeatable). Add --json when a script needs to read the result instead of the
human-readable summary.
Using another registry
The CLI talks to https://skills.patrity.com by default. Point it at any host that serves the same
API: a fork, a staging deploy, or a local pnpm dev.
pnpx @patrity/skills init --registry http://localhost:3000--registry works on every command. The registry a project was initialised against is recorded in
.claude/skills.lock.json and used automatically by add/remove/update/diff/list after
that, so you only need to pass it again to point the project somewhere else.
Releases
Published to npm as @patrity/skills. Releases
are tagged cli-vX.Y.Z and built with npm provenance.
Full documentation also lives at skills.patrity.com/docs/cli.
Changelog
0.2.1
--help, every prompt and the run summary were rewritten in the same voice as the site: plainer
questions, a summary that says what it did rather than what it touched, and none where a —
placeholder used to stand. Nothing about what the CLI does changed, and no lockfile is rewritten by
the upgrade.
0.2.0
The .gitignore line became a managed block: # >>> skills … # <<< skills, regenerated on every
run from the current selection, with every line outside it left alone. A project set up with 0.1.0
keeps its old loose .claude/settings.local.json line above the block; it is harmless, and yours to
delete. A block that was opened and never closed stops the CLI touching the file at all: it warns
and moves on, because there is no telling where that block was meant to end.
Bundles declare gitignore paths and env variables in their frontmatter now. The variables are
collected into .claude/.env.example, one commented line each, and the file is deleted again when
the last bundle declaring any is removed. It is fully managed: edit it and the next run overwrites
you. Copy it to .claude/.env and edit that instead; the CLI never creates, reads or deletes
.claude/.env.
The lockfile carries bundles.<slug>.gitignore and bundles.<slug>.env, so remove subtracts
exactly what a bundle added, and an envExample hash so an example you edited is left in place
rather than deleted. A 0.1.0 lockfile still parses; it just has none of that until the next run
records it.
The renderer is shared with the web builder at
skills.patrity.com/build. Unzip a setup from there and it is
already a project this CLI understands: diff reports no drift, and add, update and remove
work without an init first.
License
MIT
