Installer and license manager for the skilldecks-* family of Claude Code skills
Project description
skilldecks-installer
Installer and license manager for the skilldecks-* family of Claude Code skills.
What's in this pack → INDEX.md · Skills · Agents · Workflows · Scripts
What this does
skilldecks-installer is the bridge between a customer's purchased licenses and a working installation of skilldecks skills in their Claude Code environment. It:
- Reads per-purchase license files from
~/.skilldecks/licenses/ - Computes the merged entitlement (union by SKU)
- Installs each owned SKU as a Python package via
pip - Copies each SKU's
SKILL.md+ references into~/.claude/skills/skilldecks/<sku>/so Claude Code discovers them
It is also the home of the skilldecks CLI (install, update, add-license, status, setup, auth, init, doctor).
skilldecks update enforces the per-license updates_until field, and skilldecks status shows days-until-expiry per SKU. Lapsed-window SKUs skip the wheel fetch with a friendly renewal nudge; the installed version keeps working. See docs/plans/2026-05-10-perpetual-license-launch.md §6.1 to §6.3 for the full runtime contract, the 7-day signed-URL footgun, and the manual renewal flow.
Status
Alpha. Phase 1 of the marketplace launch plan (docs/plans/2026-05-09-marketplace-launch.md).
R2 URL signing wired 2026-05-09 (see "Operator setup — R2 signing" below).
Operator setup — R2 signing
skd-admin license issue --method wheel signs R2 URLs at issuance time so
the customer's skilldecks install can pip install the wheel directly.
Only the operator needs R2 credentials — the signed URL embedded in the
license YAML is self-authenticating until expiry.
One-time install
pip install 'skilldecks-installer[r2]' # adds boto3
Required environment variables
Set these in your shell rc (~/.zshrc / ~/.bash_profile) so skd-admin
can read them. They never end up in git, license YAMLs, or customer
artifacts.
| Var | Example | Where to find it |
|---|---|---|
SKILLDECKS_R2_ACCOUNT_ID |
2f6b6043df01b12524c5752df11ffb22 |
Cloudflare Dashboard → R2 → bucket details (or the hex prefix in the bucket URL) |
SKILLDECKS_R2_BUCKET |
skilldecks |
The bucket name |
SKILLDECKS_R2_ENDPOINT |
https://2f6b6043df01b12524c5752df11ffb22.r2.cloudflarestorage.com |
The S3 API endpoint URL without the bucket suffix |
SKILLDECKS_R2_ACCESS_KEY |
(redacted) |
Cloudflare Dashboard → R2 → Manage API Tokens → create with "Object Read & Write" scoped to the bucket |
SKILLDECKS_R2_SECRET_KEY |
(redacted) |
Same step as above. Cloudflare shows the secret once at creation — save to a password manager immediately. |
How it behaves
| Scenario | Result |
|---|---|
All 5 env vars set, URL bucket matches SKILLDECKS_R2_BUCKET |
URL signed with X-Amz-Signature=… query string, valid 7 days (AWS Sig v4 max) |
| Any env var missing/empty | Unsigned URL emitted + WARNING printed to stderr. License YAML is still issuable for dev/testing; install will fail at the actual download step. |
URL bucket prefix doesn't match SKILLDECKS_R2_BUCKET |
R2SigningError — operator misconfigured something. Update skus.yaml URL templates or the env var. |
Customer perspective
Customers don't install [r2] and don't set any env vars. Their license YAML
contains a pre-signed URL; skilldecks install runs pip install <url> and
that's it.
URL expiry
Signed URLs are valid for 7 days (the AWS Sig v4 maximum for static
credentials). If a customer takes longer than 7 days to install after
receiving a license, run skd-admin license issue again for the same SKU
and re-email the YAML.
Customer install (eventually)
curl -L https://skilldecks.<tld>/install | sh
This script will install skilldecks-installer (currently from a private wheel URL or git+ssh) and run skilldecks setup to drop the Claude Code skill into ~/.claude/skills/skilldecks/.
To opt into voice/sound feedback during install:
curl -L https://skilldecks.<tld>/install | SKILLDECKS_WITH_VOICE=1 sh
To skip the third-party bundles entirely (minimal install):
curl -L https://skilldecks.<tld>/install | SKILLDECKS_SKIP_BUNDLES=1 sh
License redemption
After a paid purchase, the operator (or, eventually, the skilldecks-store
backend) issues a signed license YAML and uploads it to R2 under
skilldecks/_redeem/<token>/license.yaml. The customer receives a
presigned GET URL (AWS Sig v4, 7-day max TTL) by email and
redeems it with one command:
skilldecks install --redeem 'https://<account>.r2.cloudflarestorage.com/skillsformarketing/_redeem/<token>/license.yaml?X-Amz-Signature=...'
This:
- Downloads the YAML over HTTPS (30s timeout, identifies as
User-Agent: skilldecks-installer/<version>so we're greppable in R2 access logs). - Verifies the embedded Ed25519 signature locally against the bundled public keys (dev + prod). The URL alone is NOT trusted — a compromised email or a typo'd URL must not be able to plant arbitrary YAML on the customer's machine.
- Writes the file atomically to
~/.skilldecks/licenses/<license_id>.yaml. - Continues with the normal install (same code path as bare
skilldecks install).
If anything fails (HTTP error, expired URL, bad signature, a license
with the same license_id is already redeemed) the command exits with
status 1 and does not run any pip install — redemption is
all-or-nothing.
The 7-day TTL is the operator's policy; if a customer's URL has
expired, the operator re-issues a fresh one (skd-admin license issue
or, eventually, the store-backend re-signs). The redeem flow does
NOT extend or refresh URLs on its own.
Security model in one paragraph
The presigned URL is just a delivery mechanism — the trust anchor is
the Ed25519 signature inside the YAML, verified against public keys
shipped in the wheel under skilldecks_installer/_keys/. As long as
the operator's private key stays vaulted (~/.skilldecks/signing/ by
convention), any tampering anywhere along the email/network path
fails the signature check at redeem time, so the customer cannot
end up with an unauthorized license.
What skilldecks setup installs
| Component | Source | Lands at | Default |
|---|---|---|---|
skilldecks CLI + installer skill |
this package | ~/.claude/skills/skilldecks-installer/ + pip user bin |
always |
~/.skilldecks/ scaffold |
this package | ~/.skilldecks/{licenses,credentials,vendor,templates}/ |
always |
| Best-practice agents/commands/rules/skills | shanraisshan/claude-code-best-practice | ~/.claude/{agents,commands,rules,skills}/best-practice/ |
on, --skip-bundles to opt out |
| Best-practice CLAUDE.md template | same | ~/.skilldecks/templates/best-practice-CLAUDE.md (staged, never auto-applied) |
on |
| Voice/sound hooks | shanraisshan/claude-code-hooks | ~/.claude/hooks/skilldecks-voice/ |
off, --with-voice to enable |
Bundles are namespaced under best-practice/ and skilldecks-voice/ subdirs so we never collide with files the user has already authored.
Voice feedback (opt-in)
skilldecks voice enable # fetch + install hooks, merge bindings into ~/.claude/settings.json
skilldecks voice status # show enabled/disabled + binding count
skilldecks voice disable # remove bindings (keeps files cached, fast re-enable)
Bindings are tagged skilldecks-voice:<HookEvent> so disable removes only our entries — your other hooks stay intact.
Activation (online seat enforcement)
A license can grant N concurrent machine activations. The mechanism:
- The license YAML carries a
seats: Nfield (default 1; bundles likeagency_multi_clientshipseats: 5). - On
skilldecks install, the CLI POSTs{license_id, fingerprint}to${SKILLDECKS_ACTIVATION_URL}/activate. The server records the activation if the cap allows, refuses with HTTP 409 if full. - Successful activations are cached at
~/.skilldecks/activation_<license_id>.jsonfor 30 days by default, so subsequent installs on the SAME machine don't re-call. The cache stores ONLY the activation token + seat metadata — never license content. Operators who want a tighter window (e.g. faster seat-revocation propagation) setSKILLDECKS_ACTIVATION_CACHE_DAYS=<N>whereNis an integer in[1, 90]. Out-of-range values raise a clear error atActivationClient()construction time — a typo in a deployment env file fails loud at startup instead of silently leaking seats. - The machine fingerprint is the
install_idalready used for telemetry (~/.skilldecks/telemetry/.install-id). No new per-machine identifier is introduced; if you opted out of telemetry, the activation flow still uses this anonymous random id but doesn't send any telemetry events.
Backwards-compat
If SKILLDECKS_ACTIVATION_URL is unset, activation is skipped and
skilldecks install prints a stderr note: "activation skipped
(SKILLDECKS_ACTIVATION_URL not set)". Customer flows keep working until
the operator deploys the activation server. Once deployed, the env
var flips enforcement on with no client-side code change.
Customer commands
# Start using the license on this machine — happens automatically
# inside `skilldecks install`, no manual activation step required.
skilldecks install
# Free up a seat (e.g. retiring an old laptop):
skilldecks deactivate MKT-AGCY-MULT-CLNT
# Or release a different machine's seat (you'll need its fingerprint;
# the cap-exceeded message lists the active ones):
skilldecks deactivate MKT-AGCY-MULT-CLNT --fingerprint <fp>
# Inspect current usage:
skilldecks seats MKT-AGCY-MULT-CLNT
Cap-exceeded message
When the seat cap is full and you try to activate a new machine,
skilldecks install exits with:
Seat limit reached for MKT-AGCY-MULT-CLNT: 5/5 in use.
Deactivate one of: <fp_a, fp_b, fp_c, fp_d, fp_e> via
`skilldecks deactivate MKT-AGCY-MULT-CLNT --fingerprint <fp>`, or
contact sales for a higher-seat license.
Cache, rotation, and growth
The customer-side cache is a single small JSON file per license. The
server-side log is one <DATA_DIR>/activations/<license_id>.jsonl
per license that grows by one line per (re)activation or
deactivation. At v1 scale (a handful of machines per license over a
multi-year window) growth is negligible. Operators who want hard
caps on file size can run skilldecks_store.activation._compact_inplace
on a daily cron — it rewrites each file with only the live records.
Vendored bundles
Third-party content is fetched into ~/.skilldecks/vendor/<bundle-name>/ at frozen SHAs (see skilldecks_installer/bundle_fetcher.py for current pins). We don't ship the 100MB+ payload in the wheel — it's pulled at skilldecks setup time. Bumping a pin in code requires customers to re-run skilldecks setup to pick up the change.
Local development
cd skilldecks-installer
python -m venv .venv && source .venv/bin/activate
pip install -e ".[dev]"
pytest
Layout
skilldecks-installer/
├── pyproject.toml
├── skilldecks_installer/ # the Python package
│ ├── cli.py # `skilldecks` CLI
│ ├── admin.py # `skd-admin` operator CLI
│ ├── schema.py # Pydantic models for license YAML
│ ├── license_loader.py # reads + merges ~/.skilldecks/licenses/
│ ├── pip_runner.py # subprocess wrapper for pip
│ ├── skill_copier.py # copies SKILL.md from installed packages
│ ├── bundle_fetcher.py # clones third-party bundles to ~/.skilldecks/vendor/
│ └── bundle_installer.py # deploys bundles into ~/.claude/ (namespaced)
├── skill/
│ └── SKILL.md # the Claude Code skill that delegates to `skilldecks`
├── scripts/
│ ├── install.sh # the curl|sh one-liner
│ └── skus.yaml # SKU registry (operator-side)
└── tests/
├── conftest.py
├── fixtures/licenses/ # sample license YAMLs for tests
└── test_*.py
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 skilldecks_installer-0.2.5.tar.gz.
File metadata
- Download URL: skilldecks_installer-0.2.5.tar.gz
- Upload date:
- Size: 433.0 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
8b6e0fbf8b971406e5651196e1b523e7c4a58d961c265841b2306debc26a41f9
|
|
| MD5 |
33e4c79fdda4a68fe135e337f1ffaab2
|
|
| BLAKE2b-256 |
39dd0e8278b8387f14f0cf2f8e23b52579666c9ad92cc128e9a33bfdcff63b5d
|
Provenance
The following attestation bundles were made for skilldecks_installer-0.2.5.tar.gz:
Publisher:
publish-installer.yml on don-devo/skilldecks
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
skilldecks_installer-0.2.5.tar.gz -
Subject digest:
8b6e0fbf8b971406e5651196e1b523e7c4a58d961c265841b2306debc26a41f9 - Sigstore transparency entry: 1847861123
- Sigstore integration time:
-
Permalink:
don-devo/skilldecks@45df917de48e89389123515b4084dca29eb004c2 -
Branch / Tag:
refs/tags/skilldecks-installer-v0.2.5 - Owner: https://github.com/don-devo
-
Access:
private
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish-installer.yml@45df917de48e89389123515b4084dca29eb004c2 -
Trigger Event:
push
-
Statement type:
File details
Details for the file skilldecks_installer-0.2.5-py3-none-any.whl.
File metadata
- Download URL: skilldecks_installer-0.2.5-py3-none-any.whl
- Upload date:
- Size: 450.8 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 |
d3c622a5cfd48fc3373efffcd8ccff48102604df16f1242835f8c3060eb0a6da
|
|
| MD5 |
e48df3b82029c91db77a6d4b13dd2b61
|
|
| BLAKE2b-256 |
fd6eedff7c94b7b4193eef34e42bdac72e8f30bc07adda06ca9e7aaf8e6dd5d3
|
Provenance
The following attestation bundles were made for skilldecks_installer-0.2.5-py3-none-any.whl:
Publisher:
publish-installer.yml on don-devo/skilldecks
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
skilldecks_installer-0.2.5-py3-none-any.whl -
Subject digest:
d3c622a5cfd48fc3373efffcd8ccff48102604df16f1242835f8c3060eb0a6da - Sigstore transparency entry: 1847861709
- Sigstore integration time:
-
Permalink:
don-devo/skilldecks@45df917de48e89389123515b4084dca29eb004c2 -
Branch / Tag:
refs/tags/skilldecks-installer-v0.2.5 - Owner: https://github.com/don-devo
-
Access:
private
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish-installer.yml@45df917de48e89389123515b4084dca29eb004c2 -
Trigger Event:
push
-
Statement type: