overpower
A CLI that installs curated AI Frameworks — named bodies of agent equipment from a single upstream — into a repository or onto a machine.
uvx overpower@latest install --skill panlabs-python-standards --runtime claude-code
Three commands, and they answer three questions. list says what there is,
install writes it, doctor says whether what was written is still what was
written.
Typed bare in a terminal, overpower opens the banner and the help and exits 0;
under a pipe the banner stays behind and the help goes through alone, so grep
and a file get something readable. overpower --version answers the version that
arrived, read from the installed metadata rather than from a constant — which is
what makes it evidence that the package landed intact.
Always @latest
uvx freezes the version on first use, with no TTL. Since the version of
overpower is the version of the catalog it embeds, a bare uvx overpower
serves a catalog that can never age out. @latest is a correctness requirement,
not README style.
list — what there is
Bare, it prints the whole embedded catalog in three blocks:
uvx overpower@latest list
| block | what it holds | how it installs |
|---|---|---|
| AI Frameworks | a body of equipment from one upstream | whole, never a slice of one |
| Pool skills | artifacts curated to stand alone | alone, by name |
| Bundles | a named set of pool artifacts | expands to exactly what its manifest names |
Every item arrives with its size, its file count and its description whole — never truncated, at 80 columns and at 60, because the description is what the decision to install is made on.
One selector opens one item, and each takes one name:
uvx overpower@latest list --ai-framework matt-pocock # every artifact inside it, typed
uvx overpower@latest list --skill panlabs-python-standards
uvx overpower@latest list --bundle api-python
--skill and --bundle carry the same short forms here as on install, -s
and -b. Two selectors on one line is a question with two answers, and exits 2
naming both flags. A name outside the catalog exits 2 with the closed list in
the message: the list is closed, so the defect is in what was typed.
install — write it
uvx overpower@latest install --ai-framework matt-pocock --runtime claude-code,cursor
The three selectors mix freely on one line, and each accepts a comma-separated value, a repeated flag, or both — they accumulate either way:
| flag | short | unit |
|---|---|---|
--ai-framework |
— | an AI Framework, installed whole |
--bundle |
-b |
a bundle, expanded to the pool artifacts it names |
--skill |
-s |
one pool skill |
--ai-framework has no short flag on purpose: -f is spoken for by --force,
and a letter that means one thing on one line and another elsewhere is worse than
typing the word.
A line that mixes all three produces one plan, in a fixed order — framework, then bundle, then individual artifact. Where two selectors would write the same destination the order decides it rather than an error: the individual artifact is the most specific unit, so it is the last write and its content is what survives.
Runtimes
--runtime takes the keys of a closed table of 76 runtimes and has no
default. A line that names something to install and no runtime to equip exits 2
rather than guessing; a key outside the table exits 2 naming the whole set. A
named runtime whose directory does not exist yet is created, never skipped.
Several runtimes read the same directory: nineteen of the seventy-six read
.agents/skills. When they do, the plan says so — it prints the path and who
reads it, so a selection that lands in one shared place says that, instead of
promising one installation per runtime.
$ overpower install --skill panlabs-python-standards --runtime claude-code,cursor,codex --dry-run
╭─ plan ───────────────────────────────────────────────────────────────────────╮
│ │
│ panlabs-python-standards 1 skill │
│ .claude/skills/ ← claude-code 8 files │
│ .agents/skills/ ← codex, cursor 8 files │
│ │
╰──────────────────────────────────────────────────────────────────────────────╯
overpower install typed bare in a terminal opens a wizard instead of
refusing: artifacts, then scope, then runtimes, then confirmation. The order is
mechanical, not aesthetic — the runtime step probes the target root, so it cannot
be asked before the scope is known. It builds the same request the flags do.
Scope
Inside a git repository the default is the repository. Outside one a flag line
refuses, exit 2, unless --global/-g says explicitly to write under the home
directory: the git is the manifest only holds where there is git, and nothing
else on the machine would audit what a silent write left behind. The wizard
reaches the same explicitness from the other side — outside a repository it does
not ask a question that has one legal answer, and goes global without the step.
In project scope every landing is a real copy. Under core.symlinks=false —
a value git detects and records into the clone — a committed link checks out as
an ordinary text file, and the equipment is broken for whoever cloned.
In global scope each selection climbs a ladder: the first destination in the
table's own order receives the copy, and every destination after it becomes a
relative symlink to it — relative, so the link survives $HOME moving. On
Windows the same rung is a junction, which needs no privilege. Where neither can
be created, the write degrades to a real copy, says so as a warning, and
still exits 0: nothing is lost either way.
--force/-f is a global-scope gate and nothing else. A destination that
already exists is refused, exit 3, unless it says to overwrite — global scope has
no git status to reveal or undo a clobbered write. Both that refusal and a
runtime with no destination in the requested scope (eve and promptscript have
none globally) are detected before a single byte is written.
The plan comes first
Every run prints the plan — every path, and who reads each one — before writing
anything. In a terminal it then asks; --yes/-y skips that confirmation and
nothing else. Off a terminal it does not ask at all, so the same line runs
identically in CI.
--dry-run resolves everything, prints the same plan, mirrors the exit code and
leaves nothing behind, not even an empty directory.
--from — any GitHub repository
uvx overpower@latest install --from https://github.com/owner/repo --skill some-skill --runtime codex
The vendored catalog ages by construction, and --from is the escape hatch that
does not wait for a curation refresh. It points --skill at any GitHub
repository, with no registration, and it is exclusive: with it, only the
remote is consulted, which extinguishes the question of precedence rather than
answering it. It holds for --skill alone — a skill is the one unit that exists
in the market, while a bundle and an AI Framework only exist in a repository that
already knows the overpower — and a line naming either of them alongside --from
is refused by name before anything is fetched.
The URL is a search root, not an address. The repository root, a subfolder,
or the skill's own folder all give the same result. tree/<ref>/<path> pins a
branch, a tag or a full SHA, so reproducibility comes free with the address
someone pasted.
Obtention uses the local git as transport and reuses whatever credential it
already has, falling back to the anonymous tarball — standard library only — so
no third-party binary is a requirement. There is no cache: remote content is
fresh by decision.
doctor — is it still what it was
uvx overpower@latest doctor
Two halves in one output. The terminal half reports tty, colour, width and
NO_COLOR — the four facts that explain a screen that came out strange, without
a round trip. The integrity half reads the runtime table in both scopes,
because the target carries no manifest and the closed table is therefore the only
thing that knows where equipment can be. That is also why there is no
--global here: one flag switching between the halves would make it two outputs.
Outside a git repository it still answers, unlike install.
Three checks, and each pays a hole nothing else closes:
core.symlinks=falsebreaking links — the case wheregit statusstays clean while the equipment is a text file. The git lies, anddoctoris what contradicts it. Both places the value can live are read, in git's own precedence order.- A link that does not resolve — invisible equipment: the listing shows a name with nothing behind it.
- Copies of one artifact that disagree — the debt taken on when project scope chose to copy instead of link.
Exit 3 when it found something, 0 when it did not, which is what makes it
usable as a CI gate next to --dry-run.
Exit codes
| code | meaning |
|---|---|
0 |
did what was asked |
1 |
could not run |
2 |
you invoked me wrong |
3 |
ran, and the answer is no |
The axis between 2 and 3 is whose defect it is, and it is what makes
both usable in a pipeline. A --runtime outside the table is 2; a
--runtime in the table with no destination in the requested scope is 3. A
--from whose search root could not be obtained is 1, and the transport's
own error is passed through because it is the one that names the problem;
obtained, searched, and the skill is not there — or is there twice — is 3.
A traceback never reaches the terminal: an unhandled exception becomes an error panel and exits 1, saying it is a bug in the overpower and not in what was typed.
The op alias collides
op is the command of the 1Password CLI.
Aliasing op=overpower shadows it. If you use both, pick another alias — this
is written down rather than left to be discovered:
alias opw='uvx overpower@latest'
Development
Four commands, and they are the whole loop. There is no task runner, and the
absence is deliberate: uv already does what one would add.
uv run ruff format --check . # formatting
uv run ruff check . # lint
uv run --group typecheck pyright # types, strict
uv run pytest # tests
The same four run in CI, from the same lockfile, so "it passed locally" and "it passed in CI" mean the same thing.
Local hooks
lefthook install # once per clone; worktrees inherit
The hook is the shortcut, not the gate — the gate is the ruleset on main, and
a ruleset has no --no-verify. lefthook and gitleaks are equipment of the
machine, not of the repository; a clone on a machine without them loses the
shortcut by design.
Coverage
Ephemeral by decision: no threshold, no badge, nothing in pyproject.toml. One
command, for when the question is "what has no test at all":
uv run --with pytest-cov pytest --cov=src/overpower --cov-report=term-missing
Screens
Recorded screens live in tests/snapshots/, one file per screen, at 80 and 60
columns, without colour. They render a fixture and not the shipped catalog, so
a content refresh does not rewrite them. Rewriting them is an explicit act:
uv run pytest --snapshot-update tests/test_screens.py
Releasing
The version is a literal in pyproject.toml, moved by hand:
uv version --bump patch # or minor / major
uv run towncrier build --version "$(uv version --short)"
Merging that into main is what publishes: the tagger workflow creates v<X>
and dispatches the release. A merge that does not move the version publishes
nothing.
Curation
Refreshing the catalog is an act, not a job. A gate blocks what this repository controls; what depends on a third party is verified here, by hand, on purpose.
Refreshing the vendored content
The trees under src/overpower/content/ are vendored at a fixed upstream
reference, which NOTICE records per origin. A refresh is:
- Read the upstream's own manifest at the new reference to get the set — for
matt-pocock, theskillsarray of.claude-plugin/plugin.json, never theversionfield beside it, which has been measured standing still while the array moved. - Replace the tree, applying the curation transform: the slice excludes what the upstream declares unshipped, and the shape is the one the target discovers, because the transformation happens at curation and never in the product.
- Update
NOTICE— reference and commit per origin — andlicenses/if the upstream licence file moved. - Run the four development commands, plus the network test below.
- Bump the version. By rule 5 the version of the overpower is the version of the catalog, so a refresh nobody can install is not a refresh.
Tests that touch the real GitHub
They exist, they are documented, and they run in no CI job — not on a pull request and not on a release:
OVERPOWER_NETWORK_TESTS=1 uv run pytest -m network
With the variable set, the skip condition can no longer be satisfied — a network test that gets renamed or lost turns red instead of disappearing in silence.
Where the reasoning lives
Every position in this repository was decided in a ticket, and the ticket is where the argument is. The map is Mapa: overpower v0.1.0 publicada no PyPI.
| Document | Role |
|---|---|
docs/agents/domain.md |
Vocabulary, model rules, curation criteria, axioms |
docs/agents/workflow.md |
Branch policy, gates, autonomous implementation mode |
docs/agents/testing.md |
What is a double, what runs for real, how screens are asserted |
docs/adr/ |
The decisions that read as arbitrary until you know why |
Licence
MIT. Attribution owed to vendored upstreams travels in NOTICE, which
names each origin and the reference it was taken at, and in licenses/,
which carries their licence files verbatim. PEP 639 places both in
dist-info/licenses/ — inside the wheel, never in your repository.
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 overpower-0.1.0.tar.gz.
File metadata
- Download URL: overpower-0.1.0.tar.gz
- Upload date:
- Size: 361.7 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: uv/0.12.1 {"installer":{"name":"uv","version":"0.12.1","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
b2a094d0d7220f2c6e4bfcda49afae0e08c20d1709aafc22019011b341b9e5e7
|
|
| MD5 |
bf39a62aaf9ee1973cfbb7a291993790
|
|
| BLAKE2b-256 |
1bd5e94ee5973367d89a9a17240e1bda84ad76d02d4f676d8704d74cbcf7be1d
|
File details
Details for the file overpower-0.1.0-py3-none-any.whl.
File metadata
- Download URL: overpower-0.1.0-py3-none-any.whl
- Upload date:
- Size: 290.8 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: uv/0.12.1 {"installer":{"name":"uv","version":"0.12.1","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
11eb0a4a70b36bce87222637ba6690d977a4216bed2d010d7fb678bbca914712
|
|
| MD5 |
3e6b087aed97afc355ad41cab53d29d7
|
|
| BLAKE2b-256 |
53b5bfe188a8fe01277ab438edc0be18de6a5eb918cb234994f6d37fe4f6754a
|