Prompt file imported from Generous-Corp/pulp (
.claude/commands/ship.md). Fill in{{arguments}}before use. Copyright stays with the author.
Ship a Pulp plugin or app — sign, notarize, package, and generate update feeds.
Before running any ship command
-
Run
pulp config showto check saved credentials. -
If no config exists, OR if the config file exists but all signing fields are commented out (only section headers shown), use AskUserQuestion to offer setup:
- "No signing config found. Would you like to set up signing credentials now?"
- Options: "Yes, set up macOS signing" / "Yes, set up Android signing" / "Skip for now"
- If yes, walk through
pulp config setfor each required field.
-
Check prerequisites for the target platform:
- macOS: Run
security find-identity -v -p codesigningto verify identity exists. - Android: Verify
ANDROID_HOMEis set. Verify keystore file exists. Verifyandroid/Gradle project exists. - Windows: Verify signing certificate is installed. Verify NSIS is on PATH.
- macOS: Run
-
If prerequisites are missing, use AskUserQuestion to offer solutions:
- "Android SDK not found. Would you like help setting it up?"
- Options: "Show install instructions" / "I'll set it up myself" / "Skip Android"
Pick the path first: one-off share vs full release
- "Make a build I can send a friend / share / put on a download page", or any
single
.app/.dmg/.pkgyou want to hand off → usepulp ship share(one command: sign → wrap.appin a DMG → notarize → staple → Gatekeeper-verify). This is the right tool when the user says things like "sign this so my friend can open it", "bundle the app to share", "make a DMG I can send". It does NOT touch the repo / GitHub release pipeline. - Full distribution release (versioned GitHub Release, appcast, all example
plugins) → that runs through CI (
sign-and-release.yml), not these local subcommands. For shipping a PR/release say "ship this" or use theciskill.
Standard workflow order (manual, per-stage)
- Build —
pulp build(must complete before signing) - Sign —
pulp ship sign(must sign before notarizing) - Package —
pulp ship package(creates installer from signed bundles) - Notarize —
pulp ship notarize --path <pkg|dmg|zip>(macOS only, after packaging) - Appcast —
pulp ship appcast(generate update feed pointing to packaged artifact)
Or collapse 2–4 into one command: pulp ship release --pkg / --dmg signs,
packages, and notarizes + staples the .pkg/.dmg it builds (not just the loose
bundles), so the artifact in artifacts/ is Gatekeeper-ready.
Subcommands
Signing:
pulp ship sign— uses identity from~/.pulp/config.toml(macOS/Windows)pulp ship sign --identity "Developer ID Application: ..."— explicit identitypulp ship sign --target android— uses keystore from configpulp ship sign --target android --keystore key.jks --key-alias mykey --store-pass @env:PASS --key-pass @env:KEY_PASS
Note: --key-pass defaults to --store-pass if omitted. sign --target android operates on existing APK/AAB files in artifacts/ — use package --target android to build from Gradle.
pulp ship sign --path MyApp.app— sign one explicit desktop artifact (macOS.app/.dmg/bundle, Windows.exe/bundle; not.pkg)
Notarization (macOS only):
pulp ship notarize --path MyPlugin-1.0.pkg— picks credentials up from config/env/notary.envpulp ship notarize --path MyPlugin-1.0.pkg --apple-id you@example.com --team-id ABCDE12345 --password @keychain:AC_PASSWORDpulp ship notarize --path MyApp-1.0.dmg— notarize + staple one explicit artifact (repeatable)pulp ship notarize --staple— staple only (skip submission)
Prefer pulp ship release or notarize --path <pkg|dmg|zip> for distribution.
Loose plugin bundle scans are a legacy low-level path; package the artifact you
will hand to users before notarizing.
Release pipeline (macOS only):
pulp ship release --pkg --identity "Developer ID Application: ..." --installer-identity "Developer ID Installer: ..."— sign, package, notarize, and staple the installer it buildspulp ship release --dmg --identity "Developer ID Application: ..."— same pipeline for a standalone app DMG--skip-sign,--skip-package, and--skip-notarizemake individual stages explicit for CI dry-runs
One-off share (macOS only) — sign + notarize + verify in one shot:
pulp ship share MyApp.app --identity "Developer ID Application: ..."— for an.app: signs, wraps in a DMG underartifacts/, signs the DMG, notarizes, staples, then runs the exactspctlGatekeeper check. A green result means the recipient won't see "Unnotarized Developer ID".pulp ship share MyApp.dmg/pulp ship share Installer.pkg— skips the wrap steppulp ship share MyApp.app --dry-run— print the plan without doing anything- Credentials resolve through the same chain as
notarize(App Store Connect API key preferred)
Packaging:
pulp ship package --version 1.0.0— .pkg (macOS) or NSIS .exe (Windows)pulp ship package --target android --keystore key.jks— APK + AAB via Gradlepulp ship package --target android --abi all— all ABIs (arm64-v8a, x86_64, armeabi-v7a)pulp ship package --target android --aab-only— AAB only (for Play Store)pulp ship package --target android --apk-only— APK only (for direct distribution)pulp ship package --per-user— per-user NSIS install (Windows, no admin)
AUv3 Xcode project (macOS only):
pulp ship auv3-xcodeproj MyPlugin --sdk iphonesimulator— generate a CMake Xcode build directory for the AUv3 target--sdk iphoneos|macosx,--output <dir>,--open, and--dry-runcontrol the generated project
Update feeds:
pulp ship appcast --url https://example.com/Plugin.pkg --version 1.0.0 --notes "Bug fixes"pulp ship appcast --sign-key ~/keys/ed25519_private.key— EdDSA-signed for Sparklepulp ship appcast --output appcast.xml --title "My Plugin" --min-os 12.0
Status:
pulp ship check— desktop plugin signing statuspulp ship check --target android— Android APK/AAB signing status (v2/v3 scheme, signer CN)
Common errors
- "No signing identity" → Run
security find-identity -v -p codesigningorpulp config set signing.apple.identity "..." - "No Android keystore" → Create one:
keytool -genkey -v -keystore release.jks -keyalg RSA -keysize 2048 -validity 10000 - "Android SDK not found" → Install Android Studio or
export ANDROID_HOME=~/Library/Android/sdk - "Notarization failed" → Ensure hardened runtime is enabled and using Developer ID (not development) cert. Check log:
xcrun notarytool log <UUID> - "NSIS not found" → Install NSIS and add to PATH (Windows only)
- "Gradle build failed" → Run
pulp doctorto check SDK/NDK/Java versions
Config
All signing credentials fall back to ~/.pulp/config.toml (CLI flag > env var > config file):
pulp config init # Create from template
pulp config set signing.apple.identity "Developer ID Application: ..."
pulp config set signing.apple.team_id "ABCDE12345"
pulp config set signing.android.keystore "~/keystores/release.jks"
pulp config show # Show current values
Interactive review pattern
Before executing destructive or external actions (signing, notarizing, uploading), show a summary of what will happen and use AskUserQuestion to confirm:
Ready to sign 3 bundles with:
Identity: Developer ID Application: Dan Raffel (ABCDE12345) (from: config.toml)
Bundles: PulpGain.vst3, PulpGain.clap, PulpGain.component
Proceed?
- Yes, sign all
- Edit signing identity first
- Cancel
If the user chooses "Edit signing identity first", use AskUserQuestion to collect the new value (show the current value as context), then offer to save it with pulp config set.
Similarly for notarize:
Ready to notarize 3 bundles:
Apple ID: dan@example.com (from: config.toml)
Team ID: ABCDE12345 (from: config.toml)
Password: @keychain:AC_PASSWORD
Proceed?
- Yes, submit for notarization
- Edit credentials first
- Cancel
And for Android packaging:
Ready to build Android package:
Gradle project: android/
ABIs: arm64-v8a (phones + tablets)
Signing: debug (no keystore configured)
Proceed?
- Yes, build with debug signing
- Set up release keystore first
- Cancel
This "show then confirm" pattern applies to:
pulp ship sign— show identity/keystore and bundlespulp ship notarize— show credentials and bundlespulp ship package— show target, version, and signing modepulp ship package --target android— show ABIs, keystore, Gradle projectpulp ship release— show the sign/package/notarize/staple stages and resolved identitiespulp ship share <artifact>— show the input, resolved identity, and the sign→wrap→notarize→staple→verify plan (offer--dry-runfirst if creds are unconfirmed)pulp ship auv3-xcodeproj <target>— show target, SDK, output directory, and whether Xcode will open
Run the appropriate subcommand based on {{arguments}}. If no arguments, run pulp ship check to show current signing status, then use AskUserQuestion to suggest next steps:
- "Sign plugin bundles" (if unsigned bundles found)
- "Set up signing credentials" (if no config)
- "Package for distribution" (if bundles are signed)
- "Generate appcast update feed" (if packages exist in artifacts/)
For full CI-driven shipping (PR + validate + merge + release), use the ci skill instead: say "ship this" or use /ci ship.
