name: sdk-publish on: workflow_dispatch: inputs: channel: description: "Publish channel" required: true type: choice options: - nightly - latest default: nightly force_publish: description: "Force publish even if there are no commits in the last 24 hours" required: false type: boolean default: false confirm_publish: description: 'Required when channel=latest. Type "publish" to confirm release publish.' required: false type: string schedule: # Run nightly at 2:00 AM UTC - cron: "0 2 * * *" defaults: run: working-directory: . jobs: test: permissions: contents: read uses: ./.github/workflows/sdk-test.yml publish-sdk: needs: test name: Publish SDK Packages permissions: contents: write id-token: write if: | github.repository == 'cline/cline' && github.ref == 'refs/heads/main' && ( github.event_name != 'workflow_dispatch' || inputs.channel != 'latest' || ( inputs.confirm_publish == 'publish' && !endsWith(github.actor, '[bot]') ) ) runs-on: ubuntu-latest steps: - name: Checkout code uses: actions/checkout@v4 - name: Determine publish channel id: channel env: EVENT_NAME: ${{ github.event_name }} INPUT_CHANNEL: ${{ inputs.channel }} run: | # Default to nightly for scheduled runs if [ "$EVENT_NAME" = "schedule" ]; then echo "channel=nightly" >> $GITHUB_OUTPUT else echo "channel=$INPUT_CHANNEL" >> $GITHUB_OUTPUT fi - name: Check for recent commits id: check_commits env: CHANNEL: ${{ steps.channel.outputs.channel }} FORCE_PUBLISH: ${{ inputs.force_publish }} run: | # Always publish for latest (production) releases if [ "$CHANNEL" = "latest" ]; then echo "Production release requested, proceeding with publish" echo "skip=false" >> $GITHUB_OUTPUT exit 0 fi if [ "$FORCE_PUBLISH" = "true" ]; then echo "force_publish enabled, proceeding with publish" echo "skip=false" >> $GITHUB_OUTPUT exit 0 fi if [ "$(git rev-list --count HEAD --since="24 hours ago")" -eq 0 ]; then echo "No commits in last 24 hours, skipping publish" echo "skip=true" >> $GITHUB_OUTPUT else echo "Found recent commits, proceeding with publish" echo "skip=false" >> $GITHUB_OUTPUT fi - name: Verify trusted publishing context if: steps.check_commits.outputs.skip != 'true' run: | if [ -z "${ACTIONS_ID_TOKEN_REQUEST_TOKEN:-}" ] || [ -z "${ACTIONS_ID_TOKEN_REQUEST_URL:-}" ]; then echo "GitHub OIDC request environment is unavailable. Ensure this job has id-token: write for npm trusted publishing." exit 1 fi echo "GitHub OIDC request environment is available for npm trusted publishing." - name: Setup Bun if: steps.check_commits.outputs.skip != 'true' uses: oven-sh/setup-bun@v2 with: bun-version: "1.3.13" - name: Setup Node.js if: steps.check_commits.outputs.skip != 'true' uses: actions/setup-node@v4 with: node-version: "24.x" registry-url: "https://registry.npmjs.org" - name: Verify publish tooling if: steps.check_commits.outputs.skip != 'true' run: | NPM_VERSION=$(npm --version) echo "npm ${NPM_VERSION}" IFS=. read -r major minor patch <> $GITHUB_OUTPUT - name: Update all package versions and lockfile if: steps.check_commits.outputs.skip != 'true' env: VERSION: ${{ steps.version.outputs.version }} run: bun sdk/scripts/version.ts "$VERSION" - name: Verify publishability if: steps.check_commits.outputs.skip != 'true' run: bun sdk/scripts/check-publish.ts - name: Prepare package tarball directory if: steps.check_commits.outputs.skip != 'true' run: mkdir -p "$RUNNER_TEMP/sdk-npm-packs" # Pack with Bun so workspace/catalog protocols are resolved in the tarball, # then publish that tarball with npm so npm trusted publishing can use GitHub OIDC. # Publish sequentially in dependency order: shared → llms → agents → core → sdk - name: Publish @cline/shared if: steps.check_commits.outputs.skip != 'true' env: NPM_CONFIG_PROVENANCE: "true" CHANNEL: ${{ steps.channel.outputs.channel }} VERSION: ${{ steps.version.outputs.version }} run: | echo "Publishing @cline/shared@${VERSION} with tag '${CHANNEL}'..." cd sdk/packages/shared TARBALL=$(bun pm pack --destination "$RUNNER_TEMP/sdk-npm-packs" --quiet) npm publish "$RUNNER_TEMP/sdk-npm-packs/$(basename "$TARBALL")" --tag "$CHANNEL" --access public - name: Publish @cline/llms if: steps.check_commits.outputs.skip != 'true' env: NPM_CONFIG_PROVENANCE: "true" CHANNEL: ${{ steps.channel.outputs.channel }} VERSION: ${{ steps.version.outputs.version }} run: | echo "Publishing @cline/llms@${VERSION} with tag '${CHANNEL}'..." cd sdk/packages/llms TARBALL=$(bun pm pack --destination "$RUNNER_TEMP/sdk-npm-packs" --quiet) npm publish "$RUNNER_TEMP/sdk-npm-packs/$(basename "$TARBALL")" --tag "$CHANNEL" --access public - name: Publish @cline/agents if: steps.check_commits.outputs.skip != 'true' env: NPM_CONFIG_PROVENANCE: "true" CHANNEL: ${{ steps.channel.outputs.channel }} VERSION: ${{ steps.version.outputs.version }} run: | echo "Publishing @cline/agents@${VERSION} with tag '${CHANNEL}'..." cd sdk/packages/agents TARBALL=$(bun pm pack --destination "$RUNNER_TEMP/sdk-npm-packs" --quiet) npm publish "$RUNNER_TEMP/sdk-npm-packs/$(basename "$TARBALL")" --tag "$CHANNEL" --access public - name: Publish @cline/core if: steps.check_commits.outputs.skip != 'true' env: NPM_CONFIG_PROVENANCE: "true" CHANNEL: ${{ steps.channel.outputs.channel }} VERSION: ${{ steps.version.outputs.version }} run: | echo "Publishing @cline/core@${VERSION} with tag '${CHANNEL}'..." cd sdk/packages/core TARBALL=$(bun pm pack --destination "$RUNNER_TEMP/sdk-npm-packs" --quiet) npm publish "$RUNNER_TEMP/sdk-npm-packs/$(basename "$TARBALL")" --tag "$CHANNEL" --access public - name: Publish @cline/sdk if: steps.check_commits.outputs.skip != 'true' env: NPM_CONFIG_PROVENANCE: "true" CHANNEL: ${{ steps.channel.outputs.channel }} VERSION: ${{ steps.version.outputs.version }} run: | echo "Publishing @cline/sdk@${VERSION} with tag '${CHANNEL}'..." cd sdk/packages/sdk TARBALL=$(bun pm pack --destination "$RUNNER_TEMP/sdk-npm-packs" --quiet) npm publish "$RUNNER_TEMP/sdk-npm-packs/$(basename "$TARBALL")" --tag "$CHANNEL" --access public - name: Create package tags for production publish if: steps.check_commits.outputs.skip != 'true' && steps.channel.outputs.channel == 'latest' env: VERSION: ${{ steps.version.outputs.version }} run: | git config user.name "github-actions[bot]" git config user.email "41898282+github-actions[bot]@users.noreply.github.com" for PKG in shared llms agents core sdk; do TAG="sdk/${PKG}/v${VERSION}" if git rev-parse -q --verify "refs/tags/${TAG}" >/dev/null; then echo "Tag already exists locally: ${TAG}" else git tag -a "${TAG}" -m "@cline/${PKG}@${VERSION}" echo "Created tag: ${TAG}" fi # Ensure remote has the tag; this is idempotent if tag already exists remotely. git push origin "refs/tags/${TAG}" done - name: Summary if: steps.check_commits.outputs.skip != 'true' env: VERSION: ${{ steps.version.outputs.version }} CHANNEL: ${{ steps.channel.outputs.channel }} run: | echo "Published SDK packages with tag '${CHANNEL}':" echo " - @cline/shared@${VERSION}" echo " - @cline/llms@${VERSION}" echo " - @cline/agents@${VERSION}" echo " - @cline/core@${VERSION}" echo " - @cline/sdk@${VERSION}" if [ "$CHANNEL" = "latest" ]; then echo "Created git tags:" echo " - sdk/shared/v${VERSION}" echo " - sdk/llms/v${VERSION}" echo " - sdk/agents/v${VERSION}" echo " - sdk/core/v${VERSION}" echo " - sdk/sdk/v${VERSION}" fi