Skip to content

PyUtility/polyskills

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 — ClaudeCode exposes Claude Code's native ~/.claude/skills and <root>/.claude/skills install paths, Codex exposes OpenAI Codex CLI's native ~/.codex/prompts and <root>/.codex/prompts custom prompt paths, and Cursor exposes Cursor's native ~/.cursor/rules and <root>/.cursor/rules rule-bundle paths. SKILL.md.mdc / .cursorrules format conversion for Cursor is still pending, and the CLI does not yet auto-select adapters (a --target flag is on the roadmap), so for now it writes raw SKILL.md bundles 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 the packaging library — 1.2.0, 1.2.0rc1, 1.2.0.post1 all work, though plain MAJOR.MINOR.PATCH is 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_TOKEN auth, CLI exit codes, JSON output.
  • ClaudeCode target adapter (native Claude Code install-path resolution).
  • Codex target adapter (native OpenAI Codex CLI install-path resolution via ~/.codex/prompts).
  • Cursor target 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 --target flag that uses the adapter registry to derive --directory automatically (e.g. --target claude writes to ./.claude/skills/<skillName>/ without an explicit path).
  • SKILL.md.mdc / .cursorrules rule-format conversion so the Cursor adapter ships skills in the editor's native rule format.
  • Additional sources beyond GitHub (GitLab, Bitbucket, generic Git).
  • A lockfile for bulk polyskills update across 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.

About

No description, website, or topics provided.

Resources

License

Code of conduct

Contributing

Stars

Watchers

Forks

Packages

 
 
 

Contributors

Languages