Make any Agent Skill (a SKILL.md
directory) portable across Cursor, Claude Code, Claude Web, and
Claude Cowork.
skillship is a thin orchestration layer. It does not reimplement the
multi-agent install matrix (that's npx skills) or host a
registry. It adds the three things the ecosystem is missing:
- Strict, per-surface validation profiles (notably Claude's 200-char upload cap, which the official validator doesn't enforce).
.skillpackaging for Claude Web / Cowork uploads.initscaffolding with reusable release-please CI and commit conventions.- Cursor rules and hooks install —
install -a cursoralso copiescursor/rules/*.mdcand mergescursor/hooks.jsonentries alongside theSKILL.md.
Both commands below use npx, which ships with Node.js
(>= 18). If npx is not found, install Node.js first — e.g. download the LTS
installer from nodejs.org, or use a version manager
(brew install node, nvm install --lts, winget install OpenJS.NodeJS.LTS).
Verify with node -v and npx -v.
Recommended: install the bundled /skillship Agent Skill straight from
GitHub. It drives the CLI for you — invoke /skillship (or ask to publish a
skill) and the agent locates/scaffolds a SKILL.md, validates it, fixes issues,
and installs or packages it for the chosen surface:
npx skills add shivdeepak/skillshipAdd -a <agent> (e.g. -a cursor, repeatable) to target specific agents, or
-g to install globally.
Or run the CLI directly with npx (requires Node.js >= 18):
npx skillship@latest <command>skillship validate <dir> [--profile <p>] [--json]
skillship package <dir> [--out <dir>]
skillship install [source] [-a <a,b>] [--global] [--copy]
skillship init [name] [--ci] [--snippets] [--new-dir]
skillship doctor
<source> for install is a local path (default .) or any remote ref
supported by npx skills add: owner/repo, owner/repo@skill-name, a full
GitHub/GitLab URL, or an SSH git URL. validate and package both default to
.: validate checks every skill found under it (or its skills/) and
package bundles them. A bare skill name resolves to
skills/<name>/ by convention, so validate my-skill finds
skills/my-skill/SKILL.md. Sub-skills are flat sibling skills sharing a name
prefix; a :-namespaced name maps to a hyphenated sibling folder (e.g.
validate skillship:author resolves skills/skillship-author/). A repo can
hold many sibling skills under skills/, and discovery finds them all. All
commands exit non-zero on failure.
skillship validate # validate every skill under ./skills/
skillship validate skillship:author # validate just one (bare-name convention)
skillship validate ./skills/my-skill # validate an explicit skill dirDiscovers skills the same way package does (a lone SKILL.md, a bare name
under skills/, else every skill under skills/), validating each and exiting
non-zero if any fails. For each skill it parses the SKILL.md YAML frontmatter
(name, description, optional license, metadata, allowed-tools) and
body, then applies checks per profile:
| Check | spec | cursor | claude-web | claude-cowork |
|---|---|---|---|---|
name present, lowercase/numbers/hyphens, optional :-namespacing |
yes | yes | yes | yes |
name matches parent folder |
yes | yes | yes | yes |
description non-empty, no </> |
yes | yes | yes | yes |
description length |
<= 1024 | <= 1024 | <= 200 | <= 200 |
| Body recommended <= 500 lines | warn | warn | warn | warn |
--profile is one of spec | cursor | claude-web | claude-cowork | all
(default all, the strictest combination — description must be <= 200 chars).
--json emits machine-readable output for CI.
The frontmatter parser handles YAML block scalars (>, >-, >+, |, |-,
|+) and nested maps (e.g. metadata: with indented children) without
mis-joining keys. If agentskills (the Python spec validator) is on PATH, its
findings are merged in; it is never a hard dependency.
skillship package # bundle every skill under ./skills/ -> dist/<name>.skill
skillship package --out out # -> out/<name>.skill
skillship package ./skills/my-skill # bundle just one skill -> dist/my-skill.skillDiscovers every skill under <dir> (a lone SKILL.md, else each skill under
skills/), runs validate --profile all on each (aborts if any fails), then
bundles them all into a single <name>.skill zip. Each skill lives under
its own flat <skill-name>/ folder, with : mapped to - so sibling skills
sit side by side (e.g. skillship:author → skillship-author/). Claude rejects
archives with files at the zip root. Excludes __pycache__/, .DS_Store,
node_modules/, dist/, .git/.
The bundle <name> is the single skill's name, or for multiple skills their
common prefix (e.g. skillship, skillship-author, skillship-install →
skillship), falling back to the project folder name. Because : and / are
not portable in filenames, they are rewritten to - in the output filename
(so a lone skillship:author packages to skillship-author.skill).
# Local directory — installs every skill discovered under it (all siblings)
skillship install ./my-skill -a cursor -a claude-code
# GitHub shorthand — clones and installs in one step
npx skillship@latest install shivdeepak/knowledge-base-builder -a cursor -a claude-code
# Bare owner/repo installs ALL skills under the repo's skills/ directory
npx skillship@latest install shivdeepak/skillship
# GitHub shorthand with skill-name filter (install just one from a multi-skill repo)
npx skillship@latest install vercel-labs/agent-skills@frontend-design
# Subpath inside a repo
npx skillship@latest install org/monorepo/packages/my-skill
# Full GitHub or GitLab URL (with optional branch/path)
npx skillship@latest install https://github.com/org/repo/tree/main/skills/my-skill
npx skillship@latest install https://gitlab.com/org/repo/-/tree/main/skills/my-skill
# Any git URL (SSH, self-hosted, etc.)
npx skillship@latest install git@github.com:org/repo.git
npx skillship@latest install git@git.company.com:team/skills.gitFor remote sources, skillship runs git clone --depth 1 into a temp directory,
locates the SKILL.md, installs from there, then cleans up. git must be
available on PATH — run skillship doctor to check.
For filesystem agents, shells out to npx skills add <dir> [--global] [--copy] -y -a <agent> [-a <agent>]. Default agents are cursor and claude-code.
-y is always passed so npx skills does not re-prompt — skillship gathers the
answers itself. For upload-only surfaces (claude-web, claude-cowork) it
prints upload instructions instead.
When run in an interactive terminal, install prompts before acting:
- If more than one skill will be installed, it lists them and asks for
confirmation (e.g.
owner/reporesolving to several sibling skills). - If
-g/--globalwas not passed, it asks whether to install globally (all projects) or in the current project. - If
-c/--copywas not passed, it asks whether to copy the files or symlink them.
Pass -y/--yes to skip every prompt and use the provided flags plus defaults
(project scope, symlink). In a non-interactive shell (CI, pipes) prompts are
skipped automatically. Explicit --global/--copy flags always suppress their
respective prompt.
When installing for Cursor, two additional steps run automatically if the corresponding files exist inside the skill directory:
cursor/rules/*.mdc→ copied to~/.cursor/rules/(global) or.cursor/rules/(project)cursor/hooks.json→ entries merged by event key andcommandinto~/.cursor/hooks.jsonor.cursor/hooks.json
This means skillship install is a one-step command: it installs the skill,
its trigger rule, and any hooks without requiring manual file copying.
skillship init demo --ci --snippetsScaffolds a skill repo (see layout below) that auto-releases via
release-please +
Conventional Commits. --ci adds the
GitHub Actions workflows; --snippets adds a Cursor rule and hooks file that
skillship install will automatically deploy.
By default init scaffolds into the current directory (the skill body goes
in skills/<name>/SKILL.md, repo files at the root). Pass --new-dir to create
a new <name>/ project directory instead. The layout below is relative to that
root.
Re-running init on an existing skill is safe: it writes only missing files and
leaves existing ones (including your authored SKILL.md) untouched, so you can
backfill --ci or --snippets scaffolding later.
Scaffolded layout:
my-skill/
skills/my-skill/SKILL.md
cursor/ # if --snippets
rules/
my-skill.mdc # Cursor trigger rule (auto-installed)
hooks.json # Cursor hooks to merge (auto-installed)
release-please-config.json
.release-please-manifest.json
version.txt
.github/workflows/{validate,release}.yml
.gitignore # ignores dist/, node_modules/, .DS_Store
AGENTS.md
README.md
After pushing the scaffolded repo, enable Settings -> Actions -> Workflow permissions: "Read and write" and "Allow GitHub Actions to create and approve pull requests" so release-please can open release PRs and upload the
.skillasset.
The SKILL.md version line uses an inline marker so release-please updates it
in
place and the validator ignores it:
metadata:
version: "1.0.0" # x-release-please-versionChecks the local environment: Node >= 18 and npx (required), plus gh and
agentskills (optional).
npm install
npm run build # tsup -> dist/cli.js
npm run lint # tsc --noEmit
npm test # vitestProject layout:
src/
cli.ts # arg parsing, command dispatch (commander)
commands/{validate,package,install,init,doctor}.ts
lib/frontmatter.ts # YAML frontmatter parser (block scalars + maps)
lib/profiles.ts # profile definitions and checks
lib/zip.ts # .skill packaging (archiver)
lib/exec.ts # spawn wrappers for npx skills / gh / agentskills
lib/load.ts # SKILL.md loader
lib/remote.ts # remote ref detection, URL parsing, git clone + skill dir resolution
templates/ # CI + snippet + AGENTS/README/SKILL templates for init
skills/skillship/ # bundled Agent Skill (the /skillship skill)
test/ # vitest specs + fixtures
Releases are automated via
release-please +
Conventional Commits. On every push to
main, release-please opens/updates a release PR that bumps the version in
package.json and the CHANGELOG.md based on the commits since the last
release (feat: -> minor, fix:/perf: -> patch, feat!:/BREAKING CHANGE
-> major). Merging that PR tags the release and triggers npm publish from
.github/workflows/release.yml. chore:/ci: commits do not trigger a
release.
Publishing uses npm trusted publishing (OIDC) — no NPM_TOKEN secret. The
workflow requests a short-lived OIDC token (id-token: write) that npm verifies
against the trusted-publisher config; provenance attestations are generated
automatically.
.github/workflows/ci.yml runs lint + test + build on every push and PR.
One-time setup:
- Enable Settings -> Actions -> General -> Workflow permissions: "Read and write" and "Allow GitHub Actions to create and approve pull requests" so release-please can open release PRs.
- Publish
1.0.0once manually (npm publish --access public) — trusted publishing can only be configured for a package that already exists. - On npmjs.com, open the package's Access page and add a Trusted
Publisher for GitHub Actions, matching repo
shivdeepak/skillshipand workflow filerelease.ymlexactly. After that, every release publishes tokenlessly. Optionally set the package to "require 2FA and disallow tokens" so only the workflow can publish.
MIT