LOCAL-ONLY PyPI publishing with Doppler credential management. Use when publishing to PyPI from LOCAL machine ONLY. NEVER use in CI/CD pipelines. Workspace-wide policy enforces local publishing via scripts/publish-to-pypi.sh with CI detection guards.
/plugin marketplace add terrylica/cc-skills/plugin install terrylica-itp-plugins-itp@terrylica/cc-skillsThis skill inherits all available tools. When active, it can use any tool Claude has access to.
scripts/publish-to-pypi.shThis skill supports LOCAL machine publishing ONLY.
❌ Publishing from GitHub Actions
❌ Publishing from any CI/CD pipeline (GitHub Actions, GitLab CI, Jenkins, CircleCI)
❌ publishCmd in semantic-release configuration
❌ Building packages in CI (uv build in prepareCmd)
❌ Storing PyPI tokens in GitHub secrets
✅ Use scripts/publish-to-pypi.sh on local machine
✅ CI detection guards in publish script
✅ Manual approval before each release
✅ Doppler credential management (no plaintext tokens)
✅ Repository verification (prevents fork abuse)
See: ADR-0027, docs/development/PUBLISHING.md
This skill provides local-only PyPI publishing using Doppler for secure credential management. It integrates with the workspace-wide release workflow where:
| Script | Purpose |
|---|---|
scripts/publish-to-pypi.sh | Local PyPI publishing with CI detection guards |
Usage: Copy to your project's scripts/ directory:
/usr/bin/env bash << 'DOPPLER_EOF'
# Environment-agnostic path
PLUGIN_DIR="${CLAUDE_PLUGIN_ROOT:-$HOME/.claude/plugins/marketplaces/cc-skills/plugins/itp}"
cp "$PLUGIN_DIR/skills/pypi-doppler/scripts/publish-to-pypi.sh" scripts/
chmod +x scripts/publish-to-pypi.sh
DOPPLER_EOF
Install Doppler CLI:
brew install dopplerhq/cli/doppler
Authenticate with Doppler:
doppler login
Verify access to claude-config project:
doppler whoami
doppler projects
Create PyPI API token:
pypi-AgEIcHlwaS5vcmc..., ~180 characters)Store token in Doppler:
doppler secrets set PYPI_TOKEN='pypi-AgEIcHlwaS5vcmc...' \
--project claude-config \
--config prd
Verify token stored:
doppler secrets get PYPI_TOKEN \
--project claude-config \
--config prd \
--plain
Pre-publish validation: Before publishing to PyPI, verify that the version has incremented from the previous release. Publishing without a version increment is invalid and wastes resources.
Autonomous check sequence:
pyproject.toml version against latest PyPI versionfeat: or fix: types."Why this matters: PyPI rejects duplicate versions, but more importantly, users and package managers rely on version increments to detect updates. A release workflow that doesn't increment version is broken.
Step 1: Development & Commit (Conventional Commits):
# Make your changes
git add .
# Commit with conventional format (determines version bump)
git commit -m "feat: add new feature" # MINOR bump
# Push to main
git push origin main
Step 2: Automated Versioning (GitHub Actions - 40-60s):
GitHub Actions workflow automatically:
@semantic-release/commit-analyzerv7.1.0)pyproject.toml, package.json versionsCHANGELOG.mdv7.1.0)[skip ci] message⚠️ PyPI publishing does NOT happen here (by design - see ADR-0027)
Step 3: Local PyPI Publishing (30 seconds):
After GitHub Actions completes, publish to PyPI locally:
# Pull the latest release commit
git pull origin main
# Publish to PyPI (uses pypi-doppler skill)
./scripts/publish-to-pypi.sh
Expected output:
🚀 Publishing to PyPI (Local Workflow)
======================================
🔐 Step 0: Verifying Doppler credentials...
✅ Doppler token verified
📥 Step 1: Pulling latest release commit...
Current version: v7.1.0
🧹 Step 2: Cleaning old builds...
✅ Cleaned
📦 Step 3: Building package...
✅ Built: dist/gapless_crypto_clickhouse-7.1.0-py3-none-any.whl
📤 Step 4: Publishing to PyPI...
Using PYPI_TOKEN from Doppler
✅ Published to PyPI
🔍 Step 5: Verifying on PyPI...
✅ Verified: https://pypi.org/project/gapless-crypto-clickhouse/7.1.0/
✅ Complete! Published v7.1.0 to PyPI in 28 seconds
CRITICAL: This command must ONLY run on your local machine, NEVER in CI/CD.
/usr/bin/env bash << 'GIT_EOF'
# First time: copy script from skill to your project (environment-agnostic)
PLUGIN_DIR="${CLAUDE_PLUGIN_ROOT:-$HOME/.claude/plugins/marketplaces/cc-skills/plugins/itp}"
cp "$PLUGIN_DIR/skills/pypi-doppler/scripts/publish-to-pypi.sh" scripts/
chmod +x scripts/publish-to-pypi.sh
# After semantic-release creates GitHub release
git pull origin main
# Publish using local copy of bundled script
./scripts/publish-to-pypi.sh
GIT_EOF
Bundled script features:
For manual publishing without the canonical script:
/usr/bin/env bash << 'CONFIG_EOF'
# Retrieve token from Doppler
PYPI_TOKEN=$(doppler secrets get PYPI_TOKEN \
--project claude-config \
--config prd \
--plain)
# Build package
uv build
# Publish to PyPI
UV_PUBLISH_TOKEN="${PYPI_TOKEN}" uv publish
CONFIG_EOF
⚠️ WARNING: Manual publishing bypasses CI detection guards and repository verification. Use canonical script unless you have a specific reason not to.
The canonical publish script (scripts/publish-to-pypi.sh) includes CI detection guards to prevent accidental execution in CI/CD pipelines.
$CI - Generic CI indicator$GITHUB_ACTIONS - GitHub Actions$GITLAB_CI - GitLab CI$JENKINS_URL - Jenkins$CIRCLECI - CircleCIIf any CI variable detected, script exits with error:
❌ ERROR: This script must ONLY be run on your LOCAL machine
Detected CI environment variables:
- CI: true
- GITHUB_ACTIONS: <not set>
...
This project enforces LOCAL-ONLY PyPI publishing for:
- Security: No long-lived PyPI tokens in GitHub secrets
- Speed: 30 seconds locally vs 3-5 minutes in CI
- Control: Manual approval step before production release
See: docs/development/PUBLISHING.md (ADR-0027)
# This should FAIL with error message
CI=true ./scripts/publish-to-pypi.sh
# Expected: ❌ ERROR: This script must ONLY be run on your LOCAL machine
Project: claude-config
Configs: prd (production), dev (development)
Secret Name: PYPI_TOKEN
Valid PyPI token format:
pypi-AgEIcHlwaS5vcmcpypi-AgEIcHlwaS5vcmcCJGI4YmNhMDA5LTg...Account-wide token (recommended):
Project-scoped token:
# 1. Create new token on PyPI
# Visit: https://pypi.org/manage/account/token/
# 2. Update Doppler
doppler secrets set PYPI_TOKEN='new-token' \
--project claude-config \
--config prd
# 3. Verify new token works
doppler secrets get PYPI_TOKEN \
--project claude-config \
--config prd \
--plain
# 4. Test publish (dry-run not available, use TestPyPI)
# See: Troubleshooting → TestPyPI Testing
Symptom: Script fails at Step 0
Fix:
# Verify token exists
doppler secrets --project claude-config --config prd | grep PYPI_TOKEN
# If missing, get new token from PyPI
# Visit: https://pypi.org/manage/account/token/
# Create token with scope: "Entire account" or specific project
# Store in Doppler
doppler secrets set PYPI_TOKEN='your-token' \
--project claude-config \
--config prd
Symptom: Script fails at Step 4 with authentication error
Root Cause: Token expired or invalid (PyPI requires 2FA since 2024)
Fix:
doppler secrets set PYPI_TOKEN='new-token' --project claude-config --config prdSymptom:
❌ ERROR: This script must ONLY be run on your LOCAL machine
Detected CI environment variables:
- CI: true
Root Cause: Running in CI environment OR CI variable set locally
Fix:
# Check if CI variable set in your shell
env | grep CI
# If set, unset it
unset CI
unset GITHUB_ACTIONS
# Retry publish
./scripts/publish-to-pypi.sh
Expected behavior: This is INTENTIONAL - script should ONLY run locally.
Symptom: Local publish uses old version number
Root Cause: Didn't pull latest release commit from GitHub
Fix:
# Always pull before publishing
git pull origin main
# Verify version updated
grep '^version = ' pyproject.toml
# Retry publish
./scripts/publish-to-pypi.sh
Symptom: Script fails at startup before any steps
Root Cause: uv not installed or not discoverable
How the script discovers uv (in priority order):
~/.local/bin/uv, ~/.cargo/bin/uv, /opt/homebrew/bin/uv)Fix: Install uv using any method:
# Official installer (recommended)
curl -LsSf https://astral.sh/uv/install.sh | sh
# Homebrew
brew install uv
# Cargo
cargo install uv
# mise (if you use it)
mise use uv@latest
The script doesn't force any particular installation method.
Symptom: Script starts but produces no output, eventually times out
Root Cause: Script sources ~/.zshrc or ~/.bashrc which waits for interactive input
Fix: Never source shell config files in scripts. The bundled script uses:
/usr/bin/env bash << 'MISE_EOF'
# CORRECT - safe for non-interactive shells
eval "$(mise activate bash 2>/dev/null)" || true
# WRONG - hangs in non-interactive shells
source ~/.zshrc
MISE_EOF
To test publishing workflow without affecting production:
Get TestPyPI token:
Store in Doppler (separate key):
doppler secrets set TESTPYPI_TOKEN='your-test-token' \
--project claude-config \
--config prd
Modify publish script temporarily:
/usr/bin/env bash << 'DOPPLER_EOF_2'
uv publish --token "${PYPI_TOKEN}"
TESTPYPI_TOKEN=$(doppler secrets get TESTPYPI_TOKEN --plain) uv publish --repository testpypi --token "${TESTPYPI_TOKEN}"
DOPPLER_EOF_2
4. **Test publish**:
```bash
./scripts/publish-to-pypi.sh
Verify on TestPyPI:
Restore script to production configuration
docs/architecture/decisions/0027-local-only-pypi-publishing.md - Architectural decision for local-only publishingdocs/architecture/decisions/0028-skills-documentation-alignment.md - Skills alignment with ADR-0027docs/development/PUBLISHING.md - Complete release workflow guidesemantic-release - Versioning automation (NO publishing)scripts/publish-to-pypi.sh - Reference implementation with CI guardsdiscover_uv() checks PATH → direct installs → version managers (priority order)Last Updated: 2025-12-03 Policy: Workspace-wide local-only PyPI publishing (ADR-0027) Supersedes: None (created with ADR-0027 compliance from start)
This skill should be used when the user asks to "create a slash command", "add a command", "write a custom command", "define command arguments", "use command frontmatter", "organize commands", "create command with file references", "interactive command", "use AskUserQuestion in command", or needs guidance on slash command structure, YAML frontmatter fields, dynamic arguments, bash execution in commands, user interaction patterns, or command development best practices for Claude Code.
This skill should be used when the user asks to "create an agent", "add an agent", "write a subagent", "agent frontmatter", "when to use description", "agent examples", "agent tools", "agent colors", "autonomous agent", or needs guidance on agent structure, system prompts, triggering conditions, or agent development best practices for Claude Code plugins.
This skill should be used when the user asks to "create a hook", "add a PreToolUse/PostToolUse/Stop hook", "validate tool use", "implement prompt-based hooks", "use ${CLAUDE_PLUGIN_ROOT}", "set up event-driven automation", "block dangerous commands", or mentions hook events (PreToolUse, PostToolUse, Stop, SubagentStop, SessionStart, SessionEnd, UserPromptSubmit, PreCompact, Notification). Provides comprehensive guidance for creating and implementing Claude Code plugin hooks with focus on advanced prompt-based hooks API.