A Python CLI Tool for Skills Management
Project description
Polyskills — Portable LLM Skills Manager
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.
Getting started
Install
pip install polyskills
polyskills 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.
List the skills published on a remote
polyskills "https://github.com/<owner>/<repository>" --get-skills
Sample 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. |
Install (or update) a single skill
polyskills "https://github.com/<owner>/<repository>" \
--update codeReview \
--directory .skills/codeReview
This 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/codeReview
The leading v is optional — --version 1.1.0 works too.
Authentication and rate limits
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-skills
No 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.
Exit codes
| 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). |
Authoring a skills repository
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.
Versioning with skillName@vX.Y.Z tags
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.
Publishing a new version
# 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.0
Consumers running polyskills <repo> --get-skills will immediately
see codeReview latest: v1.3.0. There is no registry, no publish
step beyond git push.
Authoring SKILL.md
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 ...
Using as a library
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.
Resolving runtime-native install paths
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/skills
For 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/prompts
For 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/rules
To 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.
Roadmap
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.
Contributing
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.
License
MIT. See LICENSE.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file polyskills-1.0.0.tar.gz.
File metadata
- Download URL: polyskills-1.0.0.tar.gz
- Upload date:
- Size: 26.9 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
776eb7d1b077293aedda13ca44e607722c9688b3ea11d0ce8a3d3777606088dd
|
|
| MD5 |
33e7a8f495c270562ea586d3f4758d59
|
|
| BLAKE2b-256 |
173bafb4a27119758d90a786f06917e782671e6857d49bd53c12a0251d557c4e
|
Provenance
The following attestation bundles were made for polyskills-1.0.0.tar.gz:
Publisher:
publish.yml on PyUtility/polyskills
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
polyskills-1.0.0.tar.gz -
Subject digest:
776eb7d1b077293aedda13ca44e607722c9688b3ea11d0ce8a3d3777606088dd - Sigstore transparency entry: 1630421338
- Sigstore integration time:
-
Permalink:
PyUtility/polyskills@3228610c82b0381eb783564897caea62dc74e6cf -
Branch / Tag:
refs/tags/v1.0.0 - Owner: https://github.com/PyUtility
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@3228610c82b0381eb783564897caea62dc74e6cf -
Trigger Event:
release
-
Statement type:
File details
Details for the file polyskills-1.0.0-py3-none-any.whl.
File metadata
- Download URL: polyskills-1.0.0-py3-none-any.whl
- Upload date:
- Size: 28.5 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
c11e39544fc36c49853dad8ea1d49166055ddc78ce75ee8b878c2e39fe464583
|
|
| MD5 |
6ad5cd6aa13f6223e13b64470ce0ff5c
|
|
| BLAKE2b-256 |
2a90aeaf9b9ddb0dc9e5f8287490a09887b3c745c709739d95222e0d9bac01d3
|
Provenance
The following attestation bundles were made for polyskills-1.0.0-py3-none-any.whl:
Publisher:
publish.yml on PyUtility/polyskills
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
polyskills-1.0.0-py3-none-any.whl -
Subject digest:
c11e39544fc36c49853dad8ea1d49166055ddc78ce75ee8b878c2e39fe464583 - Sigstore transparency entry: 1630421373
- Sigstore integration time:
-
Permalink:
PyUtility/polyskills@3228610c82b0381eb783564897caea62dc74e6cf -
Branch / Tag:
refs/tags/v1.0.0 - Owner: https://github.com/PyUtility
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@3228610c82b0381eb783564897caea62dc74e6cf -
Trigger Event:
release
-
Statement type: