A skill is a prompt that lands in a model's system context. Most teams install
them the way we installed libraries in 2009: clone a repo, copy a folder, hope
nobody touches it. Then the folder drifts and the agent quietly loads something
nobody reviewed.
I maintain Skillporter, a package manager for AI agent skills. This is the design
problem behind it, not a feature tour.
Every harness reads skills from a different directory:
Claude Code .claude/skills
OpenAI Codex .agents/skills
Cursor .agents/skills
GitHub Copilot .github/skills
Windsurf .windsurf/skills
Cline .cline/skills
OpenCode .opencode/skills
Hermes Agent $HERMES_HOME/skills
Eight harnesses share .agents/skills as their primary project path, and six more
read it as a documented compatibility path. That directory is the trap. Remove a
skill to clean up Cursor and you have just broken Codex, Gemini CLI, Zed, OpenHands
and Goose for the rest of the team.
Two other approaches fail in predictable ways. Symlinks break inside Docker
builds, git worktrees and Windows checkouts. Copy scripts drift, because nothing
records what the copy was supposed to be.
The package manager model maps cleanly:
snapshots, so rollback is possible after a bad upstream change
uvx skillporter add anthropics/skills --skill pdf --harness all
Real output from a clean workspace:
+ Installed pdf across 19 harness(es) (+12 created, ~0 updated, =7 deduplicated/unchanged)
+ Claude Code created (copy via .claude/skills)
+ GitHub Copilot created (copy via .github/skills)
+ Hermes Agent created (copy via .hermes/skills)
Twelve directories, seven shared paths deduplicated. That tally is the whole
argument for a resolver: a copy script would have written nineteen copies and
called it success.
Once a skill is pinned, the interesting question stops being what is installed and
becomes what changed.
skillporter sync # reconcile workspace with manifest, write the lock
skillporter lock --check # exit 1 on drift, exit 2 if the check could not run
skillporter outdated # compare version, commit and tree hash upstream
skillporter rollback pdf # restore the last snapshot
The exit code split matters more than it looks. A pipeline needs to distinguish a
failed check from a check that never ran, otherwise a broken tool reads as a pass.
A static scanner finds known dangerous patterns: curl piped to shell, base64
payloads, credential reads, risky permission requests. It reports finding codes
and a risk score, and it never executes skill code during install.
What it cannot do is catch novel prompt injection written in ordinary prose. A URL
can serve harmless content when inspected and something else when the agent fetches
it later. If a vendor tells you their scanner proves a skill is safe at runtime,
they are selling you certainty they do not have. The docs for this one say so
explicitly, which is the main reason I trust the rest of them.
The MCP mode is worth a paragraph. An MCP server is designed to be called by a
model, which means its arguments may be hostile. So the server exposes detect,
diagnose, scan, inspect and matrix, and denies add, install, sync and remove unless
started with --allow-writes. The workspace root comes from a flag or an environment
variable, not from tool arguments, and traversal, escaping symlinks and UNC paths
are rejected.
Not a marketplace. Not a sandbox. It does not run skills, and it cannot tell you
what a skill will do when a model follows it. What it does is make the set of
skills deterministic, visible and reversible.
v3.2.0, MIT, Python 3.9+, zero runtime dependencies, single maintainer. Nineteen
adapters, of which 1 is runtime verified, 3 are integration tested and 15 are
fixture tested. The docs state the tier per harness because pretending otherwise
would be the fastest way to lose the trust of the people who hit the bugs.
Install, if you want to try it in a throwaway repo:
uvx skillporter demo
uvx skillporter diagnose .
Site: https://skillporter.dev
Repo: https://github.com/Shanmukh-l33t/skillporter
If a harness path is wrong on your machine, that is the bug report I want most.