name: cli-publish on: schedule: - cron: "0 12 * * *" workflow_dispatch: inputs: publish_target: description: "Which publish flow to run" required: true default: "main" type: choice options: - main - nightly git_tag: description: "Existing release tag to publish when publish_target=main, for example cli-v0.1.0" required: false type: string confirm_publish: description: 'Required when publish_target=main. Type "publish" to confirm release publish.' required: false type: string force_nightly_publish: description: "Force nightly publish even with no commits in last 24h" required: false type: boolean default: false permissions: contents: read id-token: write defaults: run: working-directory: . jobs: publish-main: name: Publish cline permissions: contents: write id-token: write if: | github.repository == 'cline/cline' && github.ref == 'refs/heads/main' && github.event_name == 'workflow_dispatch' && github.event.inputs.publish_target == 'main' && github.event.inputs.confirm_publish == 'publish' && !endsWith(github.actor, '[bot]') runs-on: ubuntu-latest steps: - name: Checkout code uses: actions/checkout@v4 with: ref: ${{ github.event.inputs.git_tag }} fetch-depth: 0 fetch-tags: true - name: Setup Bun uses: oven-sh/setup-bun@v2 with: bun-version: "1.3.13" - name: Setup Node.js uses: actions/setup-node@v4 with: node-version: "24.x" registry-url: "https://registry.npmjs.org" - name: Verify publish tooling run: | NPM_VERSION=$(npm --version) echo "npm ${NPM_VERSION}" IFS=. read -r major minor patch <> "$GITHUB_OUTPUT" echo "tag=${TAG}" >> "$GITHUB_OUTPUT" - name: Build SDK packages run: bun run build:sdk env: TELEMETRY_SERVICE_API_KEY: ${{ secrets.TELEMETRY_SERVICE_API_KEY }} ERROR_SERVICE_API_KEY: ${{ secrets.ERROR_SERVICE_API_KEY }} OTEL_TELEMETRY_ENABLED: ${{ secrets.OTEL_TELEMETRY_ENABLED }} OTEL_LOGS_EXPORTER: otlp OTEL_METRICS_EXPORTER: otlp OTEL_EXPORTER_OTLP_PROTOCOL: ${{ secrets.OTEL_EXPORTER_OTLP_PROTOCOL }} OTEL_EXPORTER_OTLP_ENDPOINT: ${{ secrets.OTEL_EXPORTER_OTLP_ENDPOINT }} OTEL_EXPORTER_OTLP_HEADERS: ${{ secrets.OTEL_EXPORTER_OTLP_HEADERS }} - name: Run tests run: bun run test - name: Build platform binaries run: bun script/build.ts --install-native-variants --skip-sdk-build working-directory: apps/cli env: TELEMETRY_SERVICE_API_KEY: ${{ secrets.TELEMETRY_SERVICE_API_KEY }} ERROR_SERVICE_API_KEY: ${{ secrets.ERROR_SERVICE_API_KEY }} OTEL_TELEMETRY_ENABLED: ${{ secrets.OTEL_TELEMETRY_ENABLED }} OTEL_LOGS_EXPORTER: otlp OTEL_METRICS_EXPORTER: otlp OTEL_EXPORTER_OTLP_PROTOCOL: ${{ secrets.OTEL_EXPORTER_OTLP_PROTOCOL }} OTEL_EXPORTER_OTLP_ENDPOINT: ${{ secrets.OTEL_EXPORTER_OTLP_ENDPOINT }} OTEL_EXPORTER_OTLP_HEADERS: ${{ secrets.OTEL_EXPORTER_OTLP_HEADERS }} - name: Verify build output env: VERSION: ${{ steps.version.outputs.version }} run: | EXPECTED=( "@cline/cli-darwin-arm64" "@cline/cli-darwin-x64" "@cline/cli-linux-arm64" "@cline/cli-linux-x64" "@cline/cli-windows-arm64" "@cline/cli-windows-x64" ) for package_name in "${EXPECTED[@]}"; do dir="apps/cli/dist/${package_name#@cline/}" if [ ! -f "$dir/package.json" ]; then echo "Missing package manifest: $dir/package.json" exit 1 fi actual_name=$(node -p "require('./$dir/package.json').name") actual_version=$(node -p "require('./$dir/package.json').version") if [ "$actual_name" != "$package_name" ]; then echo "Expected $package_name, got $actual_name" exit 1 fi if [ "$actual_version" != "$VERSION" ]; then echo "Expected $package_name@$VERSION, got $actual_version" exit 1 fi ls -lh "$dir/bin/" done - name: Publish to NPM with latest tag env: NPM_CONFIG_PROVENANCE: "true" run: bun script/publish-npm.ts --tag latest working-directory: apps/cli - name: Get Previous CLI Tag id: prev_tag env: CURRENT_TAG: ${{ steps.version.outputs.tag }} run: | PREV_TAG=$(git describe --tags --abbrev=0 --match 'cli-v*' "$CURRENT_TAG^" 2>/dev/null || echo "") echo "prev_tag=$PREV_TAG" >> $GITHUB_OUTPUT - name: Get Changelog Entry id: changelog run: | # Grab content between the first "## " header and the next one in apps/cli/CHANGELOG.md CONTENT=$(awk '/^## [0-9]/{if(found) exit; found=1; next} found{print}' apps/cli/CHANGELOG.md) echo "content<> $GITHUB_OUTPUT echo "$CONTENT" >> $GITHUB_OUTPUT echo "EOF" >> $GITHUB_OUTPUT - name: Create GitHub Release uses: softprops/action-gh-release@v1 with: tag_name: ${{ steps.version.outputs.tag }} name: "CLI v${{ steps.version.outputs.version }}" body: | ${{ steps.changelog.outputs.content }} ${{ steps.prev_tag.outputs.prev_tag != '' && format('**Full Changelog**: https://github.com/{0}/compare/{1}...{2}', github.repository, steps.prev_tag.outputs.prev_tag, steps.version.outputs.tag) || '' }} env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - name: Summary env: VERSION: ${{ steps.version.outputs.version }} run: | echo "Published cline@${VERSION} to npm with dist-tag 'latest'" echo "Install with: npm install -g cline" - name: Post release to Slack uses: slackapi/slack-github-action@v3.0.1 with: method: chat.postMessage token: ${{ secrets.SLACK_RELEASE_BOT_TOKEN }} payload: | channel: "C0APVKGGZFC" text: "Cline CLI v${{ steps.version.outputs.version }}" blocks: - type: "section" text: type: "mrkdwn" text: "Cline CLI v${{ steps.version.outputs.version }}" - type: "section" text: type: "mrkdwn" text: ${{ toJSON(steps.changelog.outputs.content) }} - type: "context" elements: - type: "mrkdwn" text: "${{ steps.prev_tag.outputs.prev_tag != '' && format(' | Full Changelog: https://github.com/{0}/compare/{1}...{2}', github.repository, steps.prev_tag.outputs.prev_tag, steps.version.outputs.tag) || '' }}" publish-nightly: name: Publish cline nightly permissions: contents: read id-token: write if: | github.repository == 'cline/cline' && github.ref == 'refs/heads/main' && ( github.event_name == 'schedule' || ( github.event_name == 'workflow_dispatch' && github.event.inputs.publish_target == 'nightly' ) ) runs-on: ubuntu-latest steps: - name: Checkout code uses: actions/checkout@v4 with: fetch-depth: 0 - name: Check for recent commits id: check_commits env: FORCE_PUBLISH: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.force_nightly_publish == 'true' }} run: | if [ "$FORCE_PUBLISH" = "true" ]; then echo "force_nightly_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: 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" echo "version=${VERSION}" >> "$GITHUB_OUTPUT" - name: Update nightly package version if: steps.check_commits.outputs.skip != 'true' env: VERSION: ${{ steps.version.outputs.version }} run: | node -e ' const fs = require("node:fs"); const path = "apps/cli/package.json"; const pkg = JSON.parse(fs.readFileSync(path, "utf8")); pkg.version = process.env.VERSION; fs.writeFileSync(path, `${JSON.stringify(pkg, null, "\t")}\n`); ' cat apps/cli/package.json | grep '"version"' - name: Build platform binaries if: steps.check_commits.outputs.skip != 'true' run: bun script/build.ts --install-native-variants --skip-sdk-build working-directory: apps/cli env: TELEMETRY_SERVICE_API_KEY: ${{ secrets.TELEMETRY_SERVICE_API_KEY }} ERROR_SERVICE_API_KEY: ${{ secrets.ERROR_SERVICE_API_KEY }} OTEL_TELEMETRY_ENABLED: ${{ secrets.OTEL_TELEMETRY_ENABLED }} OTEL_LOGS_EXPORTER: otlp OTEL_METRICS_EXPORTER: otlp OTEL_EXPORTER_OTLP_PROTOCOL: ${{ secrets.OTEL_EXPORTER_OTLP_PROTOCOL }} OTEL_EXPORTER_OTLP_ENDPOINT: ${{ secrets.OTEL_EXPORTER_OTLP_ENDPOINT }} OTEL_EXPORTER_OTLP_HEADERS: ${{ secrets.OTEL_EXPORTER_OTLP_HEADERS }} - name: Verify build output if: steps.check_commits.outputs.skip != 'true' env: VERSION: ${{ steps.version.outputs.version }} run: | EXPECTED=( "@cline/cli-darwin-arm64" "@cline/cli-darwin-x64" "@cline/cli-linux-arm64" "@cline/cli-linux-x64" "@cline/cli-windows-arm64" "@cline/cli-windows-x64" ) for package_name in "${EXPECTED[@]}"; do dir="apps/cli/dist/${package_name#@cline/}" if [ ! -f "$dir/package.json" ]; then echo "Missing package manifest: $dir/package.json" exit 1 fi actual_name=$(node -p "require('./$dir/package.json').name") actual_version=$(node -p "require('./$dir/package.json').version") if [ "$actual_name" != "$package_name" ]; then echo "Expected $package_name, got $actual_name" exit 1 fi if [ "$actual_version" != "$VERSION" ]; then echo "Expected $package_name@$VERSION, got $actual_version" exit 1 fi ls -lh "$dir/bin/" done - name: Publish to NPM with nightly tag if: steps.check_commits.outputs.skip != 'true' env: NPM_CONFIG_PROVENANCE: "true" run: bun script/publish-npm.ts --tag nightly working-directory: apps/cli - name: Summary if: steps.check_commits.outputs.skip != 'true' env: VERSION: ${{ steps.version.outputs.version }} run: | echo "Published cline@${VERSION} to npm with dist-tag 'nightly'" echo "Install with: npm install -g cline@nightly"