agentleFS
Data-WiseData-Wise/craft/skills/distribution/dist-extras/SKILL.md

dist-extras

Skill4 starsChanged 12 days agoMITView on GitHub

This skill should be used when the user asks to "publish to PyPI", "generate an install script", "curl install", "publish to Claude Code marketplace", "create marketplace.json", "PyPI trusted publishing", "private plugin", "plugin drift", "pin refresh", or needs help with non-Homebrew distribution channels (PyPI, curl-based installers, Claude Code marketplace). Covers Python package publishing, GitHub-release curl installers, Claude Code plugin marketplace listings (public and private), post-release pin-refresh, and drift detection.

  • Installs packages

The file

Show its rules and text

We split this skill into the rules it gives an agent

13 rules. About 0 are common, 8 are distinctive, and 5 are very specific to this project.

  • ≈0

    Common · estimate

    Many files like this have a rule of this kind.

  • ≈8

    Distinctive · estimate

    This author's own choices. Worth a look.

  • 5

    Very specific

    Tied to their project's names, files or tools. Probably skip.

  • Common
  • Distinctive
  • Very specific
  1. Very specific:

    Use distribution-strategist first to pick a channel.

    line 16
  2. Distinctive:

    No secret needed — OIDC handshakes the token at runtime

    line 54
  3. Distinctive:

    Every generated script must:

    line 87
  4. Distinctive:

    Verify checksum (SHA256) against published checksum file when available

    line 92
  5. Very specific:

    Default INSTALL_DIR="${INSTALL_DIR:-$HOME/.local/bin}", allow override

    line 93
  6. Very specific:

    Use color helpers (info/success/error) that degrade on non-tty

    line 94
  7. Distinctive:

    Document both invocation styles in the README block.

    line 107
  8. Very specific:

    When the repo is PRIVATE, behave as if --private were passed — no flag required.

    line 171
  9. Very specific:

    Hard guard: If repo is PRIVATE and publish targets a public marketplace, abort:

    line 203
  10. Distinctive:

    After tagging/releasing, installed Claude Code pins do not auto-update.

    line 246
  11. Distinctive:

    Manual gate: Before shipping, verify manually that the Desktop store version matches the release version.

    line 347
  12. Distinctive:

    Private plugin on Desktop: No native private-repo install path exists.

    line 349
  13. Distinctive:

    Tag triggers the workflow: prefer GitHub Release published events over push-to-main for release workflows

    line 377

≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.

Keep what fits and drop the rest. Your version, ready for your agents.
Files
SKILL.md

Distribution Extras (PyPI / curl / Marketplace)

Channel-specific expertise for distribution paths that aren't covered by the Homebrew skills (homebrew-formula-expert, homebrew-multi-formula, homebrew-setup-wizard, homebrew-workflow-expert). Three distinct channels are grouped here because each one alone is too small to warrant its own top-level skill, and they share a common pattern: declarative manifest + GitHub Actions workflow + pre-flight validation.

Use distribution-strategist first to pick a channel. Use this skill once a channel is chosen and you need execution detail.

Full walkthroughs (dry-run examples, workflow YAML templates, check tables) — extracted from the former standalone commands during the v4 consolidation (Phase 3.5, 2026-07-12): references/pypi.md, references/curl-install.md, references/marketplace.md.

When to use which channel

ChannelBest forAvoid when
PyPIPython libraries and CLIs; ecosystem-native pip installNon-Python; binary-heavy distribution
curl installSingle-file CLIs with GitHub releases; quick-start docsSecurity-sensitive envs (pipe-to-bash)
MarketplaceClaude Code plugins (.claude-plugin/plugin.json present)Anything that isn't a Claude Code plugin

PyPI publishing

Channel concern: build sdist+wheel, publish to PyPI, automate via GitHub Actions, validate package metadata before release.

Subcommands (mirrors /craft:dist:pypi)

SubcommandPurpose
validateCheck pyproject.toml, classifiers, README rendering, version sanity
setupGenerate release workflow + walk user through trusted-publishing setup
workflowGenerate .github/workflows/pypi-release.yml (release-triggered)
publishBuild with uv build (or python -m build) and twine upload
checkPre-flight: clean tree, version bumped, tag matches, CHANGELOG entry

Trusted publishing (preferred over API tokens)

PyPI's OIDC-based trusted publishing avoids long-lived tokens.

  1. On PyPI: project → Publishing → Add a new pending publisher
  2. Fields: GitHub owner/repo, workflow filename (pypi-release.yml), environment name (pypi)
  3. In workflow: permissions: id-token: write + pypa/gh-action-pypi-publish@release/v1
  4. No secret needed — OIDC handshakes the token at runtime

Build tools

  • uv (preferred for new projects) — uv build produces both sdist + wheel
  • build (canonical) — python -m build
  • hatch / poetry / setuptools — build backend declared in pyproject.toml

Common failure modes

  • Version on PyPI already exists (PyPI is append-only — bump and retry)
  • README has reST/Markdown content-type mismatch — set readme = "README.md"
  • Missing classifiers (license, Python versions) flagged by twine check
  • Trusted publisher not registered before first run — workflow fails at upload

curl install scripts

Channel concern: generate a portable install.sh that fetches a GitHub release, detects platform, and installs to ~/.local/bin (or user-chosen prefix). Optionally append install instructions to README.

Modes (mirrors /craft:dist:curl-install)

ModeBehavior
--type binaryDownload pre-built binary asset matching OS-ARCH
--type sourceClone repo or fetch source tarball, build locally
--type autoDetect from repo (has release assets? → binary; else source)
previewPrint script to stdout without writing
--update-readmeAppend a curl one-liner to README under "Installation"

Script invariants

Every generated script must:

  • Start with set -euo pipefail
  • Detect uname -s and uname -m, normalize x86_64→amd64, aarch64|arm64→arm64
  • Fetch latest tag from api.github.com/repos/<owner>/<repo>/releases/latest
  • Verify checksum (SHA256) against published checksum file when available
  • Default INSTALL_DIR="${INSTALL_DIR:-$HOME/.local/bin}", allow override
  • Use color helpers (info/success/error) that degrade on non-tty

Security notes

Curl-pipe-bash is a known anti-pattern in hardened environments. For users who object, the generated script should also work via the canonical pattern:

curl -fsSL <url>/install.sh -o install.sh
less install.sh   # review
bash install.sh

Document both invocation styles in the README block.

Claude Code marketplace

Channel concern: list a Claude Code plugin in a GitHub-hosted marketplace that users add with claude plugin marketplace add <owner>/<repo>.

Subcommands (mirrors /craft:dist:marketplace)

SubcommandPurpose
initGenerate .claude-plugin/marketplace.json from plugin.json
validateSchema-check both plugin.json and marketplace.json
testLocal install/uninstall cycle via claude plugin marketplace add
publishPush to GitHub default branch (marketplace polls the repo)

marketplace.json minimal shape

{
  "name": "{org}-{plugin}",
  "owner": { "name": "...", "email": "..." },
  "metadata": { "description": "...", "version": "..." },
  "plugins": [{
    "name": "{plugin-name}",
    "source": { "source": "github", "repo": "{owner}/{repo}" },
    "description": "...",
    "version": "...",
    "homepage": "...",
    "repository": "...",
    "license": "MIT",
    "category": "development",
    "keywords": []
  }]
}

Critical limitation: no build step on install

claude plugin marketplace add clones the repo — it does not run npm install, npm run build, or any build step. Implications:

  • Skills (.md) and commands (.md) work — they're inert files
  • Agents (.md) work — same
  • MCP servers with a gitignored dist/ won't work — the server binary isn't on disk after clone. Solution: publish the MCP server to npm and reference via npx -y @scope/package in .mcp.json.
  • Anything requiring a compile step must ship compiled or use a package registry as the actual delivery channel.

Validation checklist

  • plugin.json and marketplace.json versions agree
  • All listed components (commands/, skills/, agents/, .mcp.json) exist
  • No dist/ / build artifacts referenced from gitignored paths
  • claude plugin marketplace add <test-path> succeeds locally
  • Plugin loads after install (claude plugin list shows it)

Public vs private plugin mode

By default dist:marketplace targets a public shared marketplace. Private plugins (closed-source, PII-carrying) must never appear there.

Auto-detect: gh repo view --json visibility -q .visibility returns PUBLIC or PRIVATE. When the repo is PRIVATE, behave as if --private were passed — no flag required.

--private behaviour (or auto-detected private repo):

Scaffold a self-marketplace inside the plugin's own repo rather than the shared public one:

.claude-plugin/marketplace.json   ← single-plugin manifest; source: "./"

Minimal self-marketplace shape:

{
  "name": "{plugin-name}-marketplace",
  "owner": { "name": "...", "email": "..." },
  "plugins": [{
    "name": "{plugin-name}",
    "source": { "source": "./", "path": "." },
    "description": "...",
    "version": "..."
  }]
}

Emit the private install recipe to stdout after scaffolding:

claude plugin marketplace add <owner>/<repo>
claude plugin install <plugin-name>@<owner>/<repo>

Hard guard: If repo is PRIVATE and publish targets a public marketplace, abort:

ERROR: Repository is PRIVATE. Cannot publish to public marketplace.
       Use --private (or let auto-detect run) to scaffold a self-marketplace instead.

Public repo (default): behaviour unchanged — appends plugin entry to the shared marketplace.json and pushes.

Known upstream limitation: claude plugin install uses SSH, not HTTPS

claude plugin install <name>@<marketplace> clones via git@github.com:... unconditionally — even for fully public repos, and even on machines with a working gh-managed HTTPS credential helper. Unlike claude plugin marketplace add (which correctly falls back to HTTPS when SSH auth fails), install has no HTTPS fallback, so it fails outright with Permission denied (publickey) on any machine without SSH keys registered for GitHub.

This is a Claude Code CLI bug, not a marketplace/manifest problem — nothing in marketplace.json's schema can work around it (confirmed live 2026-07-02: craft's data-wise marketplace entry was correctly registered and installable-in-principle, but install still failed via SSH). Tracked upstream: anthropics/claude-code#26588, #52234, #29722, #18001 (multiple duplicate reports, open as of April 2026).

Workaround (per-machine, not per-repo — apply once):

git config --global url."https://github.com/".insteadOf git@github.com:

Redirects any git@github.com: clone to https://github.com/, so it transparently uses the existing gh auth git-credential helper (already installed by gh auth login) instead of requiring an SSH key. Verified live: craft@data-wise installed successfully after applying this rule, having failed before it.

Post-release pin refresh (drift prevention)

After tagging/releasing, installed Claude Code pins do not auto-update. Without an explicit refresh, every local installation silently stays on the old version.

Always emit these two commands at the end of a release:

claude plugin marketplace update <marketplace-name>   # pull latest manifest
claude plugin update <plugin-name>@<marketplace>      # bump local pin

For private self-marketplaces, <marketplace-name> is <owner>/<repo>.

Drift doctor (dist:marketplace validate --drift):

Compares the canonical source version in plugin.json against the installed pin in ~/.claude/installed_plugins.json and warns on any mismatch.

plugin_version=$(jq -r .version .claude-plugin/plugin.json)
installed_pin=$(jq -r --arg name "<plugin-name>" \
    '.plugins | to_entries[] | select(.value.name == $name) | .value.version' \
    ~/.claude/installed_plugins.json 2>/dev/null)

if [ "$plugin_version" != "$installed_pin" ]; then
  echo "WARN: source $plugin_version vs pin $installed_pin"
  echo "  Fix: claude plugin marketplace update <mkt>"
  echo "       claude plugin update <name>@<mkt>"
fi

Embed this drift check in:

  • dist:marketplace validate output (add --drift flag)
  • Release pipeline Step 13.6 advisory WARN block
  • Any fix-local-plugins.sh-style maintenance scripts (generalize rather than per-plugin)

Cowork & Desktop surfaces

The craft plugin ships to three independent surfaces. This section covers the Cowork and Desktop surfaces — the Code surface (Claude Code CLI) is handled by the Homebrew and marketplace channels above.

The 3-surface model

SurfaceRuntimeDistribution channelGate
CodeClaude Code CLIHomebrew tap + GitHub marketplaceBLOCK
CoworkCowork platformCowork plugin registry (skills-first)WARN
DesktopClaude Desktop appDesktop plugin store (DXT)INFO

A release is "fully shipped" when all BLOCK-gated surfaces report the expected version. WARN surfaces (Cowork, brew-installed, Code-registered) are surfaced in the report but do not gate the automated pipeline.

Registry vs. user model: The registry.json tracks 8 surfaces; the user-facing "3-surface" view collapses them. Code aggregates git-tag, marketplace, tap, brew, code-registered, and aggregator; Cowork maps to the cowork surface (WARN); Desktop maps to desktop-ext (INFO).

Code surface

Distributed via two channels (both BLOCK-gated):

  • Homebrew tap (data-wise/tap) — brew install data-wise/tap/craft
  • GitHub marketplace — claude plugin marketplace add Data-Wise/craft

The aggregator-sync.yml CI action keeps the Data-Wise aggregator marketplace in sync on every release: published event (auto-merge PR, fail-loud on failure).

Cowork surface

Cowork ships a skills-first subset of craft. The surface tracks craft via a separate GUI plugin registry. Key behaviors:

  • Skills-first: Cowork consumes skills/ and commands/ but may present them differently than Claude Code CLI.
  • Personal vs Org marketplace: Cowork supports both personal and org-level marketplaces. The craft plugin is published under the Data-Wise org marketplace.
  • Install path: claude plugin marketplace add data-wise/craft (Cowork variant) — the exact path is marketplace-specific and may differ from the Code CLI install command.
  • Manual update: There is no automated propagation from the release pipeline to the Cowork store. After a release, the Cowork surface requires a manual store update. The pipeline emits a WARN with the Cowork report and a remind.

Private plugin install on Cowork

For plugins not in the public marketplace (git-gated, private repo):

  1. Cowork clones the repo directly — the runtime needs read access (GitHub PAT or SSH key configured in the Cowork environment).
  2. Install command: claude plugin marketplace add <owner>/<private-repo>, then claude plugin install <name>@<owner>/<private-repo>.
  3. Post-release refresh is manual: include the add+install recipe in the pipeline WARN block for the Cowork surface entry.

Desktop surface

The Claude Desktop app ships plugins via its own DXT plugin store. Key behaviors:

  • DXT format: Desktop plugins use the .dxt format (different from the Code CLI plugin format).
  • INFO only: The Desktop surface is informational in the registry — no automated verify or block gate. The pipeline reports it but does not attempt automated verification.
  • Manual gate: Before shipping, verify manually that the Desktop store version matches the release version.
  • Private plugin on Desktop: No native private-repo install path exists. Workaround: clone the repo locally and sideload from the filesystem if the Desktop app supports local plugin paths; otherwise, request a DXT store listing (gated by Anthropic). Emit an INFO note in the release pipeline for plugins with a Desktop surface entry.

Surface registry

The scripts/surfaces/registry.json file is the source of truth for all surfaces. Use /craft:dist:surfaces to view the full surface matrix and current gate states.

/craft:dist:surfaces              # verify + full human matrix
/craft:dist:surfaces --json       # machine JSON matrix (live data)

Integration

Use with:

  • distribution-strategist skill — pick the right channel first
  • homebrew-* skills — when the project also distributes via Homebrew
  • /craft:check --for release — pre-release validation across channels
  • /craft:code:release — orchestrates version bump → tag → release across channels

Cross-channel patterns

  • One canonical version: derive from a single source (pyproject.toml, plugin.json, package.json); never hand-edit in multiple files
  • Tag triggers the workflow: prefer GitHub Release published events over push-to-main for release workflows
  • Pre-flight before publish: each channel has a validate/check subcommand — run it in CI before the release job

Download now (ZIP)

Does it work?

Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.

No reviews yet

People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.

  • It worked
  • Partly
  • It didn't
What did you use it for? e.g. Reviewing pull requests in a Rails app

Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.

Your trail

Files you open will collect here, so you can find them again.

Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.

Now do this for your own.

Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.