polyskills is a Python CLI and library that installs, updates and
lists portable LLM skills (Anthropic's SKILL.md
files) hosted on any git-versioned remote repository. One source of
truth, many runtimes — Claude Code, Codex, Cursor, plus the
Anthropic / OpenAI Python SDKs.
Skills are addressed by a skillName@vX.Y.Z git tag convention, so a
single repository can host many independently versioned skills and
consumers can pin to a specific version exactly like they would pin a
pip package.
Status: pre-alpha. GitHub is the only supported source. Three target adapters are wired up —
ClaudeCodeexposes Claude Code's native~/.claude/skillsand<root>/.claude/skillsinstall paths,Codexexposes OpenAI Codex CLI's native~/.codex/promptsand<root>/.codex/promptscustom prompt paths, andCursorexposes Cursor's native~/.cursor/rulesand<root>/.cursor/rulesrule-bundle paths.SKILL.md→.mdc/.cursorrulesformat conversion for Cursor is still pending, and the CLI does not yet auto-select adapters (a--targetflag is on the roadmap), so for now it writes rawSKILL.mdbundles into the user-specified--directory.
pip install polyskillspolyskills requires Python 3.12+ and pulls in
requests and
packaging at install time.
Installing the package registers the polyskills console command on
your PATH. The equivalent python -m polyskills ... invocation
also works for editable installs and environments where the entry
point script is not on the PATH.
polyskills "https://github.com/<owner>/<repository>" --get-skillsSample output:
Available skills at https://github.com/<owner>/<repository>:
codeReview latest: v1.2.0
releaseNotes latest: v0.4.1
triageInbox latest: v0.1.0
Flag reference:
| Flag | Effect |
|---|---|
--all-versions |
List every published version of every skill, not only the latest. |
--json |
Emit a machine-readable payload ({skill: {latest, tags}}). |
--timeout SECONDS |
HTTP timeout for the tags REST call. Default: 30. |
polyskills "https://github.com/<owner>/<repository>" \
--update codeReview \
--directory .skills/codeReviewThis downloads the repository archive at the codeReview@vX.Y.Z git
tag of the resolved version, extracts only the
skills/codeReview/ subtree, and writes the contents into
.skills/codeReview/. Any prior contents at the target directory are
replaced. Missing parent directories are created.
Pin to an explicit version:
polyskills "https://github.com/<owner>/<repository>" \
--update codeReview --version v1.1.0 \
--directory .skills/codeReviewThe leading v is optional — --version 1.1.0 works too.
The GitHub REST API allows 60 unauthenticated requests per hour per
IP — enough for casual use, easily exhausted in CI. If the
GITHUB_TOKEN environment variable is set (a classic or fine-grained
personal access token with public_repo read scope is sufficient for
public repos), polyskills automatically uses it as a bearer token
and the limit rises to 5000 req/hr:
export GITHUB_TOKEN="ghp_..."
polyskills "https://github.com/<owner>/<repository>" --get-skillsNo CLI flag is exposed for the token to keep secrets out of shell
history. The token is also passed to the archive download endpoint;
GitHub redirects the archive to S3 and requests correctly strips
the Authorization header on cross-host redirect.
| Code | Meaning |
|---|---|
0 |
Success. |
2 |
User error (bad URL, unknown skill, version not found, missing --directory). |
3 |
The requested tag exists but the skills/<name>/ subtree is empty or absent. |
4 |
HTTP error from the REST or archive endpoint. |
5 |
Network error (DNS, TLS, connection reset, timeout). |
A skills repository is any git repository whose top-level layout is:
your-skills-repo/
├── skills/
│ ├── codeReview/
│ │ ├── SKILL.md
│ │ ├── prompts/
│ │ │ └── strict.md
│ │ └── ...
│ ├── releaseNotes/
│ │ ├── SKILL.md
│ │ └── ...
│ └── ...
└── README.md
Each top-level directory under skills/ is one independently
versioned skill. The contents of skills/<skillName>/ are what
polyskills --update <skillName> writes verbatim into the user's
--directory.
polyskills discovers skills and versions by scanning the
repository's git tags for the pattern:
<skillName>@v<semver>
Examples:
codeReview@v0.1.0
codeReview@v1.0.0
codeReview@v1.2.0
releaseNotes@v0.4.1
Notes:
<skillName>must match[A-Za-z0-9_-]+.<semver>is parsed with PEP 440 semantics by thepackaginglibrary —1.2.0,1.2.0rc1,1.2.0.post1all work, though plainMAJOR.MINOR.PATCHis recommended.- Tags that do not match the pattern (
v1.0,release-2024-01, …) are silently ignored when listing. - "Latest" is whichever tag has the highest semver — not the most recently created tag.
# 1. Make and review your changes in skills/codeReview/
git add skills/codeReview
git commit -m "codeReview: tighten the review checklist"
# 2. Tag the new version
git tag codeReview@v1.3.0
# 3. Push the commit and the tag to the remote
git push origin main
git push origin codeReview@v1.3.0Consumers running polyskills <repo> --get-skills will immediately
see codeReview latest: v1.3.0. There is no registry, no publish
step beyond git push.
polyskills does not parse or validate SKILL.md content — it
simply ships the files as authored. For the schema and best practices
refer to Anthropic's skills documentation. A minimal
skeleton:
---
name: codeReview
description: Performs a focused code review on a diff.
---
# Instructions
You are an expert reviewer ...The CLI is a thin wrapper around three classes. They are stable and can be used directly:
from polyskills import SkillsManager, SkillInstaller, ValidSources
skills = SkillsManager(
remote = "https://github.com/<owner>/<repository>",
source = ValidSources.GITHUB,
timeout = 30,
)
# Discover what is available.
catalog = skills.getSkills()
for name, entry in catalog.items():
print(name, entry["latest"])
# Install one skill into the local project. ``installSkill`` returns
# ``(installed_directory, resolved_version)`` so the caller can log
# which version actually landed without paying for a second tag-list
# round-trip.
installer = SkillInstaller(skills, timeout = 60)
target, resolved = installer.installSkill(
skillName = "codeReview",
directory = ".skills/codeReview",
# version = "1.2.0", # omit for latest
)
print(f"Installed codeReview v{resolved} to {target}")The deep-import style still works (from polyskills.manager.skills import SkillsManager, etc.) for code that pins to it.
SkillsManager paginates the GitHub tags REST API; SkillInstaller
performs a single streaming tarball download. Reuse one
SkillsManager across multiple installSkill calls to avoid
re-paginating the tag listing for each install.
Each supported LLM runtime ships a concrete TargetAdapters subclass
that returns the canonical directory the runtime auto-discovers
skills from. For Claude Code:
from pathlib import Path
from polyskills.targets.claude import ClaudeCode
claude = ClaudeCode()
claude.globalDirectory() # ~/.claude/skills
claude.localDirectory(Path.cwd()) # <cwd>/.claude/skillsFor OpenAI Codex CLI, which has no native skills/ directory but
does natively discover custom slash-command prompts under .codex/,
the Codex adapter resolves Codex's prompts/ convention so skills
land where the runtime actually looks for them:
from pathlib import Path
from polyskills.targets.codex import Codex
codex = Codex()
codex.globalDirectory() # ~/.codex/prompts
codex.localDirectory(Path.cwd()) # <cwd>/.codex/promptsFor Cursor, the Cursor adapter resolves the editor's .cursor/rules
directory — the canonical home for .mdc rule bundles that Cursor
auto-loads when the editor is opened on a project. The user-wide
mirror keeps the install layout symmetrical even though Cursor itself
does not auto-discover rules outside the project tree at this time:
from pathlib import Path
from polyskills.targets.cursor import Cursor
cursor = Cursor()
cursor.globalDirectory() # ~/.cursor/rules
cursor.localDirectory(Path.cwd()) # <cwd>/.cursor/rulesTo look up an adapter by name (useful for config-driven workflows or
the upcoming --target CLI flag), go through the registry. The
registry maps the runtime's upper-case identifier to the adapter
class itself; instantiating the class yields a parameterless object
whose globalDirectory() and localDirectory(root) methods resolve
the runtime-native install paths:
from polyskills.targets.registry import LLM_APPLICATIONS
adapter = LLM_APPLICATIONS["CLAUDE"]()
target = adapter.localDirectory(Path.cwd()) / "codeReview"
installer.installSkill("codeReview", target)Currently registered targets: CLAUDE, CODEX, CURSOR.
Implemented:
- GitHub-hosted skills, tag discovery, version resolution.
- Tarball-at-tag streaming install of
skills/<name>/subtree. GITHUB_TOKENauth, CLI exit codes, JSON output.ClaudeCodetarget adapter (native Claude Code install-path resolution).Codextarget adapter (native OpenAI Codex CLI install-path resolution via~/.codex/prompts).Cursortarget adapter (native Cursor install-path resolution via~/.cursor/rules).- Name-keyed adapter registry exposing all three adapters
(
LLM_APPLICATIONS["CLAUDE"],LLM_APPLICATIONS["CODEX"],LLM_APPLICATIONS["CURSOR"]).
Planned (contributions welcome):
- CLI
--targetflag that uses the adapter registry to derive--directoryautomatically (e.g.--target claudewrites to./.claude/skills/<skillName>/without an explicit path). SKILL.md→.mdc/.cursorrulesrule-format conversion so theCursoradapter ships skills in the editor's native rule format.- Additional sources beyond GitHub (GitLab, Bitbucket, generic Git).
- A lockfile for bulk
polyskills updateacross an entire project.
Issues and pull requests are tracked on GitHub. The project follows PEP 8 (88-char lines, flake8 enforced) with Sphinx-style docstrings; see existing modules for the conventions.
MIT. See LICENSE.