Developer guide
How to find, install, write and publish a skill — in the browser, from a terminal, or from inside an agent. Everything here works against the live catalogue.
On this page
Install the CLISign inInstall a skillAnatomy of a skillThe manifestLimitsPublishingPrivate skillsEvalsVersions and digestsTokens and CIThe HTTP APIMirrored skillsInstall the CLI
Show the prompt
Install the LLM SkillHub CLI (llmsh) on this machine for me. Work out which
route suits what is already here rather than assuming — there are three and they
install the same binaries.
1. Look at the machine first, and tell me what you found:
uname -s && uname -m # darwin / linux, x86_64 / arm64
command -v node npm go curl # which of these exist
2. Choose from what you found. The three install the same binaries,
covered by the same checksums:
node is here → npm
npm install -g llmskillhub
The package is "llmskillhub"; the command it installs is "llmsh" — llmsh
was already taken on the registry. If this fails with EACCES, do not re-run
it with sudo: that leaves root-owned files in a prefix meant to be mine and
breaks the next global install. Either set a user-writable npm prefix, or
use one of the routes below.
macOS or Linux, no node → the installer
curl -fsSL https://raw.githubusercontent.com/llmskillhub/cli/main/install.sh | sh
It picks the release binary for this OS and architecture, checks it against
the release's SHA256SUMS and refuses on a mismatch, then puts it in
~/.local/bin. It is POSIX shell and handles darwin and linux on amd64 and
arm64 only — it exits with a clear message on anything else.
the Go toolchain is here → go
go install github.com/llmskillhub/cli/cmd/llmsh@latest
The binary lands in $(go env GOPATH)/bin.
Windows → npm or go from the list above, or take
llmsh_<version>_windows_amd64.exe from
https://github.com/llmskillhub/cli/releases
The installer script is POSIX-only and will not run there.
3. Check it is callable, not merely downloaded:
llmsh version
"command not found" here usually means it installed fine and its directory is
not on PATH — ~/.local/bin for the installer, $(go env GOPATH)/bin for go.
Work out which one it is and tell me the exact line to add to my shell
profile. Don't edit my profile, my PATH or my shell configuration yourself.
Install the tool and stop there. Don't run llmsh login, don't install any skill,
and don't add anything to this project — those are separate decisions and I will
make them. Tell me which route you used, what version "llmsh version" printed,
and anything you had to leave for me to do.Three ways in. They serve the same binaries, covered by the same checksums.
$ npm install -g llmskillhub$ curl -fsSL https://raw.githubusercontent.com/llmskillhub/cli/main/install.sh | sh$ go install github.com/llmskillhub/cli/cmd/llmsh@latestThe npm package is llmskillhub and the command it installs is llmsh — llmsh was taken on the registry. The installer checks each download against the release's SHA256SUMS and refuses a mismatch.
Sign in
Public skills need no account. For your private skills, or to publish, make a token on your account page (see Tokens) and give it to the CLI once:
$ llmsh loginPaste the token at the prompt. It is hidden as you paste — a star for each character, then only its last four, the way your account page lists it — and stored in llmsh’s own config, readable only by you.
$ llmsh whoami$ llmsh logoutFrom 0.6.0, llmsh trades that token for a short-lived access token and sends that instead, keeping it for its fifteen minutes beside the config; after that it trades again, on its own — you paste once. Older versions send the token itself, and keep working.
Install a skill
$ llmsh install zalpha/llmskillhubThat resolves the latest approved version, downloads it, recomputes the tree digest, compares it to the manifest, and only then unpacks. A mismatch is refused rather than warned about.
$ llmsh install zalpha/llmskillhub@0.1.0$ llmsh install zalpha/llmskillhub --dir ./skillsA published version never changes, so pinning one means the bytes you tested are the bytes you get.
The same command installs a private skill, once you are signed in with a token that reaches it; without one it answers that there is no such skill, which is all a stranger is told. And it installs an eval too, into ./evals — the registry says which kind a package is, and llmsh puts it in the right place.
Any agent, not just Claude
A skill is a folder of instructions, which is not a Claude idea — but where those instructions have to live to be read is different for every agent. -for puts them where yours looks.
$ llmsh install zalpha/llmskillhub -for cursor| claude | .claude/skills/ — Claude Code reads the folder itself, and picks a skill by its description |
| opencode | .opencode/skills/ — read as a folder too, and chosen by description |
| cursor | .cursor/skills/ — Cursor reads the folder itself, and picks a skill by its description or /skill-name |
| codex | a block in AGENTS.md, files in .agents/skills/ |
| gemini | a block in GEMINI.md, files in .agents/skills/ |
-for and --for are the same flag; Go accepts either, so a habit from any other CLI works here.
Claude Code, Cursor and opencode read a folder of skills and pick one by its description, so installing for them is just the unpacking — and Cursor and opencode also read .claude/skills and .agents/skills, so a skill installed for any of the others is already visible to them. The remaining two read a single project file rather than a folder, so the skill's instructions are written into it between two comment markers naming the skill. Everything outside those markers is yours and is never touched: installing again replaces only that block, and a file whose markers look half-written is refused rather than repaired. Codex and Gemini CLI share .agents/skills/, so installing the same skill for the second of them re-uses the first copy once its digest matches, rather than downloading it twice.
Anatomy of a skill
my-skill/
├── SKILL.md # required: the manifest and the instructions
├── scripts/ # optional: code the agent can run
├── references/ # optional: documentation it can read on demand
├── assets/ # optional: templates, images, data
└── examples/ # optionalOnly SKILL.md is required, and only those four directory names are recognised. Anything else is allowed — you will see a note at publish, but nothing is refused for its layout.
$ llmsh init my-skillScaffolds the folder and asks for what the validator requires.
The manifest
YAML frontmatter at the top of SKILL.md. Two fields are required.
---
name: llmskillhub
description: >
How one can create llm skill and publish it to llmskillhub.com
license: MIT
metadata:
llmskillhub:
version: 0.1.0
categories: [agents]
capabilities:
network: false
filesystem: read
shell: false
secrets: []
---
# LLM Skill Hub
Guide to creating an LLM skill and publishing it to llmskillhub.com.
## When to reach for this
Instructions for the agent go here...That is the manifest of zalpha/llmskillhub, a skill published here. Every field below is optional except the first two.
Fields the spec defines
namerequired- Lowercase letters, digits and single hyphens. Must match the directory name. It is unique to you, not to the catalogue: a skill is addressed as
owner/name, so two publishers may each have apdf-toolsand neither blocks the other. descriptionrequired- What it does and when to use it. This is the field an agent routes on, so name the phrases someone would actually type. No angle brackets — descriptions are embedded in prompt scaffolding, where they are reserved.
license- A licence name, or a pointer to a bundled file.
compatibility- Environment requirements, if there are any. Most skills need none.
allowed-tools- Space-separated tools pre-approved to run. Experimental in the spec.
metadata- Anything else. Ours lives under
metadata.llmskillhub.
metadata.llmskillhub
version- Semver. Without it you pass
--versionat publish; with it, the publish is refused if the two disagree rather than quietly relabelled. categories,keywords- How it is found. Both feed search and the category browser.
repository,homepage,authors- Where it came from and who wrote it.
capabilities- What it touches:
network,shell,filesystem(none,read,read-write) and namedsecrets. Worth filling in — it is what tells someone installing your skill what it will do, and it is shown on the page beside it.
Limits
| Limit | Maximum | |
|---|---|---|
| Archive on the wire | 5 MiB | the .skill zip itself |
| Uncompressed total | 25 MiB | every entry inflated, summed |
| Compression ratio | 50:1 | uncompressed over compressed |
| Files | 500 | |
| Single file | 4 MiB | |
| SKILL.md | 256 KiB | |
| Path length / depth | 255 / 12 | |
| name | 64 chars | lowercase, digits and single hyphens |
| description | 1024 chars | 40 minimum; 500 is the comfortable length |
| compatibility | 500 chars |
A package well inside these is the normal case — most skills are a few tens of kilobytes. Exceeding one is a refusal rather than a warning, so it is worth knowing before you publish.
Publishing
Check it locally first — this never touches the network:
$ llmsh validate ./my-skillThen the same checks on the server, storing nothing:
$ llmsh publish ./my-skill --dry-runAnd for real:
$ llmsh publish ./my-skillPublishing reads your git repository. It refuses to ship uncommitted work by accident — pass --allow-dirty if you mean it — and records the commit the version came from.
Every public version is reviewed before it is listed, including updates to a skill that is already public — the live version keeps serving while the new one waits, so publishing never takes your skill down. Send several while one is waiting and approving the newest discards the older ones still queued; an approved version is never removed.
$ llmsh status you/my-skillShows each version and where it has got to.
Working copies
llmsh clone owner/name fetches a skill’s source to work on, and remembers where it came from: llmsh push from inside it publishes back there — to someone else’s skill, if they shared it with you to publish. --fork publishes your own copy instead, and the working copy follows it from then on.
$ llmsh clone owner/name$ llmsh push$ llmsh publish owner/name ./dirPrivate skills
$ llmsh publish ./my-skill --privateInto your own space: available at once, not reviewed, and visible only to you and the people you share it with. Later versions keep the skill’s visibility without the flag. Publishing, installing and status all need a token whose reach includes the skill — the default reach is public, and a public-reach token is refused a private publish rather than quietly publishing it in the open.
Evals
A directory with a manifest.yaml is an eval rather than a skill, and llmsh tells them apart by looking. Publishing one from the command line is still behind a switch:
$ LLMSH_EVALS=1 llmsh publish ./my-eval$ llmsh install owner/my-evalInstalling needs no switch, and lands in ./evals (or --dir). The layout is described on the browser tab.
Versions and digests
A version's identity is the tree digest: a hash over the file list and contents, not over the zip. Zip output is not reproducible across producers, so hashing the archive would make the same files package to different identities on different machines.
Published versions are immutable. The same bytes cannot be published twice under different numbers either — that is refused as duplicate content.
/owner/skill the current approved version
/owner/skill/v/1.2.0 those exact bytes, permanently
/compare/owner/skill any two versions, side by sideYanking withdraws a version from resolution without deleting it: anyone who pinned it keeps working, and nothing new resolves to it.
Tokens and CI
Create one at your account. A token can do less than you can, which is the point — one pasted into a build job is copied into places you do not control.
| Read | Browse and download anything you can see, view your own versions still in review, see who your space is shared with, and add stars. |
| Publish | Everything Read can do, plus publish new versions. In the catalogue a version is reviewed first; in your space it is available at once. |
| Manage | Everything Publish can do, plus deprecate, yank and delete what you have published, move it between the catalogue and your space, change its categories, and change who it is shared with. |
And what it can reach. A token sees your private skills only if you say so when you make it:
| public | the catalogue only. Nothing in your private space, including your own. |
| selected | the catalogue, plus the private skills you tick. |
| all | everything you can see, including private work shared with you and anything you make private later. |
On your own machine, sign in with it once. In CI, pass it in the environment, which writes nothing to disk:
LLMSH_TOKEN=skh_live_... llmsh publish ./my-skillThe token is shown once, when you create it, and never again. Lost means make a new one.
The HTTP API
Everything the CLI does is over HTTP, and reading the public catalogue needs no authentication. Base URL https://api.llmskillhub.com.
Anything private, and every write, takes Authorization: Bearer <token>: a personal token (skh_live_…), or the fifteen-minute access token you get by POSTing one to /v1/auth/token — an Ed25519-signed JWT, checked without a database lookup, with the same permissions and reach as the token it came from. The keys that verify them are published at /.well-known/jwks.json.
| GET /v1/skills | list and search; q, category, sort, limit |
| GET /v1/skills/{owner}/{slug} | one skill |
| GET /v1/skills/{owner}/{slug}/versions | every version you can see |
| GET /v1/skills/{owner}/{slug}/files | the file list at a version |
| GET /v1/skills/{owner}/{slug}/file | one file's contents |
| GET /v1/skills/{owner}/{slug}/download | a short-lived URL for the archive |
| GET /v1/skills/{owner}/{slug}/diff | between two versions |
| GET /v1/categories | categories with counts |
| GET /v1/users/{handle} | a publisher and their skills |
| GET /v1/me | who a token belongs to |
| POST /v1/auth/token | a personal token in, a fifteen-minute access token out |
| GET /.well-known/jwks.json | the public keys that verify access tokens |
$ curl https://api.llmskillhub.com/v1/skills?q=pdfErrors are RFC 7807 problem documents with a stable code, so you can branch on the machine-readable field rather than on prose.
Mirrored skills
Some skills here were published by us on behalf of their authors, from open-licensed repositories. Those pages say so, name the source commit and the licence, and state that the author has not claimed the page.
A mirror is only ever made from a licence that permits redistribution, and the original licence file travels inside the package.
If one is yours: sign in with the GitHub account that owns the source and the page becomes yours, with its skills. It is matched on your numeric GitHub id, so a rename does not break it and nobody who later takes your old username can claim it. To have a mirror removed instead, use the link in the footer.