GitHub Action to install ctx CLI for publishing skills, MCP servers, and CLI tools to the getctx.org registry.
steps:
- uses: actions/checkout@v4
- uses: ctx-hq/setup-ctx@v1
- run: ctx publish --yes
env:
CTX_TOKEN: ${{ secrets.CTX_TOKEN }}| Input | Required | Default | Description |
|---|---|---|---|
version |
No | latest |
ctx version to install |
token |
No | — | Registry token. Can also use CTX_TOKEN env var. |
ctx token create --name github-ci --scope publish --package "@yourscope/*"Go to Settings > Secrets and variables > Actions and add CTX_TOKEN with the token value.
Choose the template that matches your repo structure below.
Publishes on every GitHub Release.
# .github/workflows/ctx-publish.yml
name: Publish to ctx
on:
release:
types: [published]
jobs:
publish:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: ctx-hq/setup-ctx@v1
- run: ctx publish --yes
env:
CTX_TOKEN: ${{ secrets.CTX_TOKEN }}Publishes only the skills that changed in each push to main.
# .github/workflows/ctx-publish.yml
name: Publish changed skills
on:
push:
branches: [main]
paths: ['skills/**']
jobs:
publish:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 2 # needed for git diff
- uses: ctx-hq/setup-ctx@v1
- run: ctx publish --yes --all --changed
env:
CTX_TOKEN: ${{ secrets.CTX_TOKEN }}Automatic version management with release-please. Versions are bumped based on Conventional Commits:
feat(skill-name): ...→ minor bumpfix(skill-name): ...→ patch bumpfeat!: ...orBREAKING CHANGE:→ major bump
# .github/workflows/ctx-publish.yml
name: Release & Publish
on:
push:
branches: [main]
jobs:
release:
runs-on: ubuntu-latest
outputs:
releases_created: ${{ steps.release.outputs.releases_created }}
paths_released: ${{ steps.release.outputs.paths_released }}
steps:
- uses: googleapis/release-please-action@v4
id: release
with:
config-file: release-please-config.json
manifest-file: .release-please-manifest.json
publish:
needs: release
if: needs.release.outputs.releases_created == 'true'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: ctx-hq/setup-ctx@v1
- run: |
for path in ${{ needs.release.outputs.paths_released }}; do
ctx publish --yes "${path}"
done
env:
CTX_TOKEN: ${{ secrets.CTX_TOKEN }}Setup for release-please: Run ctx init --import in your repo — it generates the required release-please-config.json and .release-please-manifest.json automatically.
Publish prerelease versions from pull requests for testing before merge.
# .github/workflows/ctx-canary.yml
name: Canary publish
on:
pull_request:
paths: ['skills/**']
jobs:
canary:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 2
- uses: ctx-hq/setup-ctx@v1
- run: ctx publish --yes --all --changed --tag canary
env:
CTX_TOKEN: ${{ secrets.CTX_TOKEN }}Install canary versions with: ctx install @scope/skill@canary
Publishes a CLI binary with platform-specific artifacts to ctx registry on tag push. Add these steps after goreleaser-action in your existing release workflow:
- uses: ctx-hq/setup-ctx@v1
with:
token: ${{ secrets.CTX_TOKEN }}
- name: Publish to ctx
run: |
VERSION="${GITHUB_REF_NAME#v}"
ctx publish --yes --version "$VERSION" || echo "::warning::ctx publish skipped (version may already exist)"
ctx artifact upload "$(grep '^name:' ctx.yaml | sed "s/name: *['\"]\\{0,1\\}\\([^'\"]*\\)['\"]\\{0,1\\}/\\1/")@${VERSION}" --dir dist/ || echo "::warning::ctx artifact upload skipped (artifacts may already exist)"Users install with: ctx install @scope/my-cli
Tip: The
|| echo "::warning::..."pattern makes the workflow idempotent — safe to rerun after partial failures (e.g., goreleaser succeeded but ctx publish didn't). Without it, a rerun would fail on "version already exists".
Auto-yank a version if publish succeeds but you detect issues downstream.
- name: Publish with rollback
run: |
VERSION=$(grep '^version:' ctx.yaml | awk '{print $2}' | tr -d '"')
NAME=$(grep '^name:' ctx.yaml | awk '{print $2}' | tr -d '"')
if ! ctx publish --yes; then
echo "::error::Publish failed, yanking ${NAME}@${VERSION}"
ctx yank --yes "${NAME}@${VERSION}" || true
exit 1
fi
env:
CTX_TOKEN: ${{ secrets.CTX_TOKEN }}If you have an existing skill repo (with SKILL.md files, marketplace.json, or any common format), ctx can auto-detect and generate ctx.yaml for you:
# Install ctx
curl -fsSL https://getctx.org/install.sh | sh
# Import your repo (auto-detects format)
cd your-skill-repo
ctx init --import --name @yourscope
# This generates:
# - ctx.yaml (workspace root for monorepos, or single-skill manifest)
# - Per-skill ctx.yaml files (for monorepos)
# - release-please-config.json + .release-please-manifest.jsonSupported formats:
| Format | Example repos |
|---|---|
| Single SKILL.md | codebase-to-course, frontend-slides |
Flat skill dirs (*/SKILL.md) |
Skills-dimillian, creative |
Nested skill dirs (*/*/SKILL.md) |
claude-skills |
| marketplace.json | baoyu-skills, anthropic/skills |
Codex format (.curated/) |
codex-skills |
| Bare markdown (no frontmatter) | lark-minutes-tasks |
┌──────────────┐ push / release ┌──────────────────┐
│ Your repo │ ──────────────────────▶ │ GitHub Actions │
│ │ │ │
│ ctx.yaml │ ← SSOT │ 1. checkout │
│ SKILL.md │ │ 2. setup-ctx │
│ │ │ 3. ctx publish │
└──────────────┘ └────────┬─────────┘
│
▼
┌──────────────────┐
│ getctx.org │
│ registry │
└──────────────────┘
- ctx.yaml is the single source of truth for package metadata
- SKILL.md frontmatter (description, triggers) is auto-merged at publish time — no duplication needed
- release-please handles version bumps via Conventional Commits
--changedflag detects modified skills viagit diff— only publishes what changed
| Approach | Complexity | Best for |
|---|---|---|
Manual (ctx publish --bump patch) |
Low | Solo projects |
| GitHub Release trigger | Low | Single-skill repos |
| release-please | Medium | Monorepos with multiple skills |
--changed on push to main |
Low | Fast iteration, skip release-please |
- Never commit your token. Always use GitHub Secrets (
${{ secrets.CTX_TOKEN }}). - Use scoped tokens. Create tokens with
--scope publish --package "@yourscope/*"to limit blast radius. - Token rotation. Tokens support expiration:
--expires 90(days).
MIT