Chris Estreich 1 неделя назад
Родитель
Сommit
4592133624
5 измененных файлов с 786 добавлено и 739 удалено
  1. 397 0
      .github/workflows/cli-release.yml
  2. 45 27
      apps/cli/README.md
  3. 1 1
      apps/cli/package.json
  4. 343 0
      apps/cli/scripts/build.sh
  5. 0 711
      apps/cli/scripts/release.sh

+ 397 - 0
.github/workflows/cli-release.yml

@@ -0,0 +1,397 @@
+name: CLI Release
+
+on:
+  workflow_dispatch:
+    inputs:
+      version:
+        description: 'Version to release (e.g., 0.1.0). Leave empty to use package.json version.'
+        required: false
+        type: string
+      dry_run:
+        description: 'Dry run (build and test but do not create release).'
+        required: false
+        type: boolean
+        default: false
+
+jobs:
+  # Build CLI for each platform.
+  build:
+    strategy:
+      fail-fast: false
+      matrix:
+        include:
+          - os: macos-latest
+            platform: darwin-arm64
+            runs-on: macos-latest
+          - os: macos-13
+            platform: darwin-x64
+            runs-on: macos-13
+          - os: ubuntu-latest
+            platform: linux-x64
+            runs-on: ubuntu-latest
+
+    runs-on: ${{ matrix.runs-on }}
+
+    steps:
+      - name: Checkout code
+        uses: actions/checkout@v4
+        with:
+          fetch-depth: 0
+
+      - name: Setup Node.js and pnpm
+        uses: ./.github/actions/setup-node-pnpm
+
+      - name: Get version
+        id: version
+        run: |
+          if [ -n "${{ inputs.version }}" ]; then
+            VERSION="${{ inputs.version }}"
+          else
+            VERSION=$(node -p "require('./apps/cli/package.json').version")
+          fi
+          echo "version=$VERSION" >> $GITHUB_OUTPUT
+          echo "tag=cli-v$VERSION" >> $GITHUB_OUTPUT
+          echo "Using version: $VERSION"
+
+      - name: Build extension bundle
+        run: pnpm bundle
+
+      - name: Build CLI
+        run: pnpm --filter @roo-code/cli build
+
+      - name: Create release tarball
+        id: tarball
+        env:
+          VERSION: ${{ steps.version.outputs.version }}
+          PLATFORM: ${{ matrix.platform }}
+        run: |
+          RELEASE_DIR="roo-cli-${PLATFORM}"
+          TARBALL="roo-cli-${PLATFORM}.tar.gz"
+
+          # Clean up any previous build.
+          rm -rf "$RELEASE_DIR"
+          rm -f "$TARBALL"
+
+          # Create directory structure.
+          mkdir -p "$RELEASE_DIR/bin"
+          mkdir -p "$RELEASE_DIR/lib"
+          mkdir -p "$RELEASE_DIR/extension"
+
+          # Copy CLI dist files.
+          echo "Copying CLI files..."
+          cp -r apps/cli/dist/* "$RELEASE_DIR/lib/"
+
+          # Create package.json for npm install.
+          echo "Creating package.json..."
+          node -e "
+            const pkg = require('./apps/cli/package.json');
+            const newPkg = {
+              name: '@roo-code/cli',
+              version: '$VERSION',
+              type: 'module',
+              dependencies: {
+                '@inkjs/ui': pkg.dependencies['@inkjs/ui'],
+                '@trpc/client': pkg.dependencies['@trpc/client'],
+                'commander': pkg.dependencies.commander,
+                'fuzzysort': pkg.dependencies.fuzzysort,
+                'ink': pkg.dependencies.ink,
+                'p-wait-for': pkg.dependencies['p-wait-for'],
+                'react': pkg.dependencies.react,
+                'superjson': pkg.dependencies.superjson,
+                'zustand': pkg.dependencies.zustand
+              }
+            };
+            console.log(JSON.stringify(newPkg, null, 2));
+          " > "$RELEASE_DIR/package.json"
+
+          # Copy extension bundle.
+          echo "Copying extension bundle..."
+          cp -r src/dist/* "$RELEASE_DIR/extension/"
+
+          # Add package.json to extension directory for CommonJS.
+          echo '{"type": "commonjs"}' > "$RELEASE_DIR/extension/package.json"
+
+          # Find and copy ripgrep binary.
+          echo "Looking for ripgrep binary..."
+          RIPGREP_PATH=$(find node_modules -path "*/@vscode/ripgrep/bin/rg" -type f 2>/dev/null | head -1)
+          if [ -n "$RIPGREP_PATH" ] && [ -f "$RIPGREP_PATH" ]; then
+            echo "Found ripgrep at: $RIPGREP_PATH"
+            mkdir -p "$RELEASE_DIR/node_modules/@vscode/ripgrep/bin"
+            cp "$RIPGREP_PATH" "$RELEASE_DIR/node_modules/@vscode/ripgrep/bin/"
+            chmod +x "$RELEASE_DIR/node_modules/@vscode/ripgrep/bin/rg"
+            mkdir -p "$RELEASE_DIR/bin"
+            cp "$RIPGREP_PATH" "$RELEASE_DIR/bin/"
+            chmod +x "$RELEASE_DIR/bin/rg"
+          else
+            echo "Warning: ripgrep binary not found"
+          fi
+
+          # Create the wrapper script
+          echo "Creating wrapper script..."
+          cat > "$RELEASE_DIR/bin/roo" << 'WRAPPER_EOF'
+#!/usr/bin/env node
+
+import { fileURLToPath } from 'url';
+import { dirname, join } from 'path';
+
+const __filename = fileURLToPath(import.meta.url);
+const __dirname = dirname(__filename);
+
+// Set environment variables for the CLI
+process.env.ROO_CLI_ROOT = join(__dirname, '..');
+process.env.ROO_EXTENSION_PATH = join(__dirname, '..', 'extension');
+process.env.ROO_RIPGREP_PATH = join(__dirname, 'rg');
+
+// Import and run the actual CLI
+await import(join(__dirname, '..', 'lib', 'index.js'));
+WRAPPER_EOF
+
+          chmod +x "$RELEASE_DIR/bin/roo"
+
+          # Create empty .env file.
+          touch "$RELEASE_DIR/.env"
+
+          # Create tarball.
+          echo "Creating tarball..."
+          tar -czvf "$TARBALL" "$RELEASE_DIR"
+
+          # Clean up release directory.
+          rm -rf "$RELEASE_DIR"
+
+          # Create checksum.
+          if command -v sha256sum &> /dev/null; then
+            sha256sum "$TARBALL" > "${TARBALL}.sha256"
+          elif command -v shasum &> /dev/null; then
+            shasum -a 256 "$TARBALL" > "${TARBALL}.sha256"
+          fi
+
+          echo "tarball=$TARBALL" >> $GITHUB_OUTPUT
+          echo "Created: $TARBALL"
+          ls -la "$TARBALL"
+
+      - name: Verify tarball
+        env:
+          PLATFORM: ${{ matrix.platform }}
+        run: |
+          TARBALL="roo-cli-${PLATFORM}.tar.gz"
+
+          # Create temp directory for verification.
+          VERIFY_DIR=$(mktemp -d)
+
+          # Extract and verify structure.
+          tar -xzf "$TARBALL" -C "$VERIFY_DIR"
+
+          echo "Verifying tarball contents..."
+          ls -la "$VERIFY_DIR/roo-cli-${PLATFORM}/"
+
+          # Check required files exist.
+          test -f "$VERIFY_DIR/roo-cli-${PLATFORM}/bin/roo" || { echo "Missing bin/roo"; exit 1; }
+          test -f "$VERIFY_DIR/roo-cli-${PLATFORM}/lib/index.js" || { echo "Missing lib/index.js"; exit 1; }
+          test -f "$VERIFY_DIR/roo-cli-${PLATFORM}/package.json" || { echo "Missing package.json"; exit 1; }
+          test -d "$VERIFY_DIR/roo-cli-${PLATFORM}/extension" || { echo "Missing extension directory"; exit 1; }
+
+          echo "Tarball verification passed!"
+
+          # Cleanup.
+          rm -rf "$VERIFY_DIR"
+
+      - name: Upload artifact
+        uses: actions/upload-artifact@v4
+        with:
+          name: cli-${{ matrix.platform }}
+          path: |
+            roo-cli-${{ matrix.platform }}.tar.gz
+            roo-cli-${{ matrix.platform }}.tar.gz.sha256
+          retention-days: 7
+
+  # Create GitHub release with all platform artifacts.
+  release:
+    needs: build
+    runs-on: ubuntu-latest
+    if: ${{ !inputs.dry_run }}
+    permissions:
+      contents: write
+
+    steps:
+      - name: Checkout code
+        uses: actions/checkout@v4
+
+      - name: Get version
+        id: version
+        run: |
+          if [ -n "${{ inputs.version }}" ]; then
+            VERSION="${{ inputs.version }}"
+          else
+            VERSION=$(node -p "require('./apps/cli/package.json').version")
+          fi
+          echo "version=$VERSION" >> $GITHUB_OUTPUT
+          echo "tag=cli-v$VERSION" >> $GITHUB_OUTPUT
+
+      - name: Download all artifacts
+        uses: actions/download-artifact@v4
+        with:
+          path: artifacts
+
+      - name: Prepare release files
+        run: |
+          mkdir -p release
+          find artifacts -name "*.tar.gz" -exec cp {} release/ \;
+          find artifacts -name "*.sha256" -exec cp {} release/ \;
+          ls -la release/
+
+      - name: Extract changelog
+        id: changelog
+        env:
+          VERSION: ${{ steps.version.outputs.version }}
+        run: |
+          CHANGELOG_FILE="apps/cli/CHANGELOG.md"
+
+          if [ -f "$CHANGELOG_FILE" ]; then
+            # Extract content between version headers.
+            CONTENT=$(awk -v version="$VERSION" '
+              BEGIN { found = 0; content = ""; target = "[" version "]" }
+              /^## \[/ {
+                if (found) { exit }
+                if (index($0, target) > 0) { found = 1; next }
+              }
+              found { content = content $0 "\n" }
+              END { print content }
+            ' "$CHANGELOG_FILE")
+
+            if [ -n "$CONTENT" ]; then
+              echo "Found changelog content"
+              echo "content<<EOF" >> $GITHUB_OUTPUT
+              echo "$CONTENT" >> $GITHUB_OUTPUT
+              echo "EOF" >> $GITHUB_OUTPUT
+            else
+              echo "No changelog content found for version $VERSION"
+              echo "content=" >> $GITHUB_OUTPUT
+            fi
+          else
+            echo "No changelog file found"
+            echo "content=" >> $GITHUB_OUTPUT
+          fi
+
+      - name: Generate checksums summary
+        id: checksums
+        run: |
+          echo "checksums<<EOF" >> $GITHUB_OUTPUT
+          cat release/*.sha256 >> $GITHUB_OUTPUT
+          echo "EOF" >> $GITHUB_OUTPUT
+
+      - name: Check for existing release
+        id: check_release
+        env:
+          GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+          TAG: ${{ steps.version.outputs.tag }}
+        run: |
+          if gh release view "$TAG" &> /dev/null; then
+            echo "exists=true" >> $GITHUB_OUTPUT
+          else
+            echo "exists=false" >> $GITHUB_OUTPUT
+          fi
+
+      - name: Delete existing release
+        if: steps.check_release.outputs.exists == 'true'
+        env:
+          GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+          TAG: ${{ steps.version.outputs.tag }}
+        run: |
+          echo "Deleting existing release $TAG..."
+          gh release delete "$TAG" --yes || true
+          git push origin ":refs/tags/$TAG" || true
+
+      - name: Create GitHub Release
+        env:
+          GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+          VERSION: ${{ steps.version.outputs.version }}
+          TAG: ${{ steps.version.outputs.tag }}
+          CHANGELOG_CONTENT: ${{ steps.changelog.outputs.content }}
+          CHECKSUMS: ${{ steps.checksums.outputs.checksums }}
+        run: |
+          WHATS_NEW=""
+          if [ -n "$CHANGELOG_CONTENT" ]; then
+            WHATS_NEW="## What's New
+
+$CHANGELOG_CONTENT
+
+"
+          fi
+
+          RELEASE_NOTES=$(cat << EOF
+${WHATS_NEW}## Installation
+
+\`\`\`bash
+curl -fsSL https://raw.githubusercontent.com/RooCodeInc/Roo-Code/main/apps/cli/install.sh | sh
+\`\`\`
+
+Or install a specific version:
+\`\`\`bash
+ROO_VERSION=$VERSION curl -fsSL https://raw.githubusercontent.com/RooCodeInc/Roo-Code/main/apps/cli/install.sh | sh
+\`\`\`
+
+## Requirements
+
+- Node.js 20 or higher
+- macOS (Intel or Apple Silicon) or Linux x64
+
+## Usage
+
+\`\`\`bash
+# Run a task
+roo "What is this project?"
+
+# See all options
+roo --help
+\`\`\`
+
+## Platform Support
+
+This release includes binaries for:
+- \`roo-cli-darwin-arm64.tar.gz\` - macOS Apple Silicon (M1/M2/M3)
+- \`roo-cli-darwin-x64.tar.gz\` - macOS Intel
+- \`roo-cli-linux-x64.tar.gz\` - Linux x64
+
+## Checksums
+
+\`\`\`
+${CHECKSUMS}
+\`\`\`
+EOF
+          )
+
+          gh release create "$TAG" \
+            --title "Roo Code CLI v$VERSION" \
+            --notes "$RELEASE_NOTES" \
+            --prerelease \
+            release/*
+
+          echo "Release created: https://github.com/${{ github.repository }}/releases/tag/$TAG"
+
+  # Summary job for dry runs
+  summary:
+    needs: build
+    runs-on: ubuntu-latest
+    if: ${{ inputs.dry_run }}
+
+    steps:
+      - name: Download all artifacts
+        uses: actions/download-artifact@v4
+        with:
+          path: artifacts
+
+      - name: Show build summary
+        run: |
+          echo "## Dry Run Complete" >> $GITHUB_STEP_SUMMARY
+          echo "" >> $GITHUB_STEP_SUMMARY
+          echo "The following artifacts were built:" >> $GITHUB_STEP_SUMMARY
+          echo "" >> $GITHUB_STEP_SUMMARY
+          find artifacts -name "*.tar.gz" | while read f; do
+            SIZE=$(ls -lh "$f" | awk '{print $5}')
+            echo "- $(basename $f) ($SIZE)" >> $GITHUB_STEP_SUMMARY
+          done
+          echo "" >> $GITHUB_STEP_SUMMARY
+          echo "### Checksums" >> $GITHUB_STEP_SUMMARY
+          echo "\`\`\`" >> $GITHUB_STEP_SUMMARY
+          cat artifacts/*/*.sha256 >> $GITHUB_STEP_SUMMARY
+          echo "\`\`\`" >> $GITHUB_STEP_SUMMARY

+ 45 - 27
apps/cli/README.md

@@ -19,7 +19,7 @@ curl -fsSL https://raw.githubusercontent.com/RooCodeInc/Roo-Code/main/apps/cli/i
 **Requirements:**
 **Requirements:**
 
 
 - Node.js 20 or higher
 - Node.js 20 or higher
-- macOS (Intel or Apple Silicon) or Linux (x64 or ARM64)
+- macOS (Intel or Apple Silicon) or Linux x64
 
 
 **Custom installation directory:**
 **Custom installation directory:**
 
 
@@ -77,7 +77,7 @@ roo "What is this project?"  -w ~/Documents/my-project
 You can also run without a prompt and enter it interactively in TUI mode:
 You can also run without a prompt and enter it interactively in TUI mode:
 
 
 ```bash
 ```bash
-roo ~/Documents/my-project
+roo -w ~/Documents/my-project
 ```
 ```
 
 
 In interactive mode:
 In interactive mode:
@@ -147,21 +147,23 @@ Tokens are valid for 90 days. The CLI will prompt you to re-authenticate when yo
 
 
 ## Options
 ## Options
 
 
-| Option                            | Description                                                                             | Default                       |
-| --------------------------------- | --------------------------------------------------------------------------------------- | ----------------------------- |
-| `[prompt]`                        | Your prompt (positional argument, optional)                                             | None                          |
-| `-w, --workspace <path>`          | Workspace path to operate in                                                            | Current directory             |
-| `-e, --extension <path>`          | Path to the extension bundle directory                                                  | Auto-detected                 |
-| `-d, --debug`                     | Enable debug output (includes detailed debug information, prompts, paths, etc)          | `false`                       |
-| `-x, --exit-on-complete`          | Exit the process when task completes (useful for testing)                               | `false`                       |
-| `-y, --yes`                       | Non-interactive mode: auto-approve all actions                                          | `false`                       |
-| `-k, --api-key <key>`             | API key for the LLM provider                                                            | From env var                  |
-| `-p, --provider <provider>`       | API provider (anthropic, openai, openrouter, etc.)                                      | `openrouter`                  |
-| `-m, --model <model>`             | Model to use                                                                            | `anthropic/claude-sonnet-4.5` |
-| `-M, --mode <mode>`               | Mode to start in (code, architect, ask, debug, etc.)                                    | `code`                        |
-| `-r, --reasoning-effort <effort>` | Reasoning effort level (unspecified, disabled, none, minimal, low, medium, high, xhigh) | `medium`                      |
-| `--ephemeral`                     | Run without persisting state (uses temporary storage)                                   | `false`                       |
-| `--no-tui`                        | Disable TUI, use plain text output                                                      | `false`                       |
+| Option                                      | Description                                                                             | Default                                  |
+| ------------------------------------------- | --------------------------------------------------------------------------------------- | ---------------------------------------- |
+| `[prompt]`                                  | Your prompt (positional argument, optional)                                             | None                                     |
+| `--prompt-file <path>`                      | Read prompt from a file instead of command line argument                                | None                                     |
+| `-w, --workspace <path>`                    | Workspace path to operate in                                                            | Current directory                        |
+| `-p, --print`                               | Print response and exit (non-interactive mode)                                          | `false`                                  |
+| `-e, --extension <path>`                    | Path to the extension bundle directory                                                  | Auto-detected                            |
+| `-d, --debug`                               | Enable debug output (includes detailed debug information, prompts, paths, etc)          | `false`                                  |
+| `-y, --yes, --dangerously-skip-permissions` | Auto-approve all actions (use with caution)                                             | `false`                                  |
+| `-k, --api-key <key>`                       | API key for the LLM provider                                                            | From env var                             |
+| `--provider <provider>`                     | API provider (roo, anthropic, openai, openrouter, etc.)                                 | `openrouter` (or `roo` if authenticated) |
+| `-m, --model <model>`                       | Model to use                                                                            | `anthropic/claude-opus-4.5`              |
+| `--mode <mode>`                             | Mode to start in (code, architect, ask, debug, etc.)                                    | `code`                                   |
+| `-r, --reasoning-effort <effort>`           | Reasoning effort level (unspecified, disabled, none, minimal, low, medium, high, xhigh) | `medium`                                 |
+| `--ephemeral`                               | Run without persisting state (uses temporary storage)                                   | `false`                                  |
+| `--oneshot`                                 | Exit upon task completion                                                               | `false`                                  |
+| `--output-format <format>`                  | Output format with `--print`: `text`, `json`, or `stream-json`                          | `text`                                   |
 
 
 ## Auth Commands
 ## Auth Commands
 
 
@@ -246,17 +248,33 @@ pnpm lint
 
 
 ## Releasing
 ## Releasing
 
 
-To create a new release, execute the /cli-release slash command:
+Official releases are created via the GitHub Actions workflow at `.github/workflows/cli-release.yml`.
 
 
-```bash
-roo "/cli-release"  -w ~/Documents/Roo-Code -y
-```
+To trigger a release:
+
+1. Go to **Actions** → **CLI Release**
+2. Click **Run workflow**
+3. Optionally specify a version (defaults to `package.json` version)
+4. Click **Run workflow**
 
 
 The workflow will:
 The workflow will:
 
 
-1. Bump the version
-2. Update the CHANGELOG
-3. Build the extension and CLI
-4. Create a platform-specific tarball (for your current OS/architecture)
-5. Test the install script
-6. Create a GitHub release with the tarball attached
+1. Build the CLI on all platforms (macOS Intel, macOS ARM, Linux x64)
+2. Create platform-specific tarballs with bundled ripgrep
+3. Verify each tarball
+4. Create a GitHub release with all tarballs attached
+
+### Local Builds
+
+For local development and testing, use the build script:
+
+```bash
+# Build tarball for your current platform
+./apps/cli/scripts/build.sh
+
+# Build and install locally
+./apps/cli/scripts/build.sh --install
+
+# Fast build (skip verification)
+./apps/cli/scripts/build.sh --skip-verify
+```

+ 1 - 1
apps/cli/package.json

@@ -19,7 +19,7 @@
 		"dev": "tsup --watch",
 		"dev": "tsup --watch",
 		"start": "ROO_AUTH_BASE_URL=http://localhost:3000 ROO_SDK_BASE_URL=http://localhost:3001 ROO_CODE_PROVIDER_URL=http://localhost:8080/proxy node dist/index.js",
 		"start": "ROO_AUTH_BASE_URL=http://localhost:3000 ROO_SDK_BASE_URL=http://localhost:3001 ROO_CODE_PROVIDER_URL=http://localhost:8080/proxy node dist/index.js",
 		"start:production": "node dist/index.js",
 		"start:production": "node dist/index.js",
-		"release": "scripts/release.sh",
+		"build:local": "scripts/build.sh",
 		"clean": "rimraf dist .turbo"
 		"clean": "rimraf dist .turbo"
 	},
 	},
 	"dependencies": {
 	"dependencies": {

+ 343 - 0
apps/cli/scripts/build.sh

@@ -0,0 +1,343 @@
+#!/bin/bash
+# Roo Code CLI Local Build Script
+#
+# Usage:
+#   ./apps/cli/scripts/build.sh [options]
+#
+# Options:
+#   --install      Install locally after building
+#   --skip-verify  Skip end-to-end verification tests (faster builds)
+#
+# Examples:
+#   ./apps/cli/scripts/build.sh              # Build for local testing
+#   ./apps/cli/scripts/build.sh --install    # Build and install locally
+#   ./apps/cli/scripts/build.sh --skip-verify  # Fast local build
+#
+# This script builds the CLI for your current platform. For official releases
+# with multi-platform support, use the GitHub Actions workflow instead:
+#   .github/workflows/cli-release.yml
+#
+# Prerequisites:
+#   - pnpm installed
+#   - Run from the monorepo root directory
+
+set -e
+
+# Parse arguments
+LOCAL_INSTALL=false
+SKIP_VERIFY=false
+
+while [[ $# -gt 0 ]]; do
+    case $1 in
+        --install)
+            LOCAL_INSTALL=true
+            shift
+            ;;
+        --skip-verify)
+            SKIP_VERIFY=true
+            shift
+            ;;
+        -*)
+            echo "Unknown option: $1" >&2
+            exit 1
+            ;;
+        *)
+            shift
+            ;;
+    esac
+done
+
+# Colors
+RED='\033[0;31m'
+GREEN='\033[0;32m'
+YELLOW='\033[1;33m'
+BLUE='\033[0;34m'
+BOLD='\033[1m'
+NC='\033[0m'
+
+info() { printf "${GREEN}==>${NC} %s\n" "$1"; }
+warn() { printf "${YELLOW}Warning:${NC} %s\n" "$1"; }
+error() { printf "${RED}Error:${NC} %s\n" "$1" >&2; exit 1; }
+step() { printf "${BLUE}${BOLD}[%s]${NC} %s\n" "$1" "$2"; }
+
+# Get script directory and repo root
+SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
+REPO_ROOT="$(cd "$SCRIPT_DIR/../../.." && pwd)"
+CLI_DIR="$REPO_ROOT/apps/cli"
+
+# Detect current platform
+detect_platform() {
+    OS=$(uname -s | tr '[:upper:]' '[:lower:]')
+    ARCH=$(uname -m)
+
+    case "$OS" in
+        darwin) OS="darwin" ;;
+        linux) OS="linux" ;;
+        *) error "Unsupported OS: $OS" ;;
+    esac
+
+    case "$ARCH" in
+        x86_64|amd64) ARCH="x64" ;;
+        arm64|aarch64) ARCH="arm64" ;;
+        *) error "Unsupported architecture: $ARCH" ;;
+    esac
+
+    PLATFORM="${OS}-${ARCH}"
+}
+
+# Check prerequisites
+check_prerequisites() {
+    step "1/6" "Checking prerequisites..."
+
+    if ! command -v pnpm &> /dev/null; then
+        error "pnpm is not installed."
+    fi
+
+    if ! command -v node &> /dev/null; then
+        error "Node.js is not installed."
+    fi
+
+    info "Prerequisites OK"
+}
+
+# Get version
+get_version() {
+    VERSION=$(node -p "require('$CLI_DIR/package.json').version")
+    GIT_SHORT_HASH=$(git rev-parse --short HEAD 2>/dev/null || echo "unknown")
+    VERSION="${VERSION}-local.${GIT_SHORT_HASH}"
+
+    info "Version: $VERSION"
+}
+
+# Build everything
+build() {
+    step "2/6" "Building extension bundle..."
+    cd "$REPO_ROOT"
+    pnpm bundle
+
+    step "3/6" "Building CLI..."
+    pnpm --filter @roo-code/cli build
+
+    info "Build complete"
+}
+
+# Create release tarball
+create_tarball() {
+    step "4/6" "Creating release tarball for $PLATFORM..."
+
+    RELEASE_DIR="$REPO_ROOT/roo-cli-${PLATFORM}"
+    TARBALL="roo-cli-${PLATFORM}.tar.gz"
+
+    # Clean up any previous build
+    rm -rf "$RELEASE_DIR"
+    rm -f "$REPO_ROOT/$TARBALL"
+
+    # Create directory structure
+    mkdir -p "$RELEASE_DIR/bin"
+    mkdir -p "$RELEASE_DIR/lib"
+    mkdir -p "$RELEASE_DIR/extension"
+
+    # Copy CLI dist files
+    info "Copying CLI files..."
+    cp -r "$CLI_DIR/dist/"* "$RELEASE_DIR/lib/"
+
+    # Create package.json for npm install
+    info "Creating package.json..."
+    node -e "
+      const pkg = require('$CLI_DIR/package.json');
+      const newPkg = {
+        name: '@roo-code/cli',
+        version: '$VERSION',
+        type: 'module',
+        dependencies: {
+          '@inkjs/ui': pkg.dependencies['@inkjs/ui'],
+          '@trpc/client': pkg.dependencies['@trpc/client'],
+          'commander': pkg.dependencies.commander,
+          'fuzzysort': pkg.dependencies.fuzzysort,
+          'ink': pkg.dependencies.ink,
+          'p-wait-for': pkg.dependencies['p-wait-for'],
+          'react': pkg.dependencies.react,
+          'superjson': pkg.dependencies.superjson,
+          'zustand': pkg.dependencies.zustand
+        }
+      };
+      console.log(JSON.stringify(newPkg, null, 2));
+    " > "$RELEASE_DIR/package.json"
+
+    # Copy extension bundle
+    info "Copying extension bundle..."
+    cp -r "$REPO_ROOT/src/dist/"* "$RELEASE_DIR/extension/"
+
+    # Add package.json to extension directory for CommonJS
+    echo '{"type": "commonjs"}' > "$RELEASE_DIR/extension/package.json"
+
+    # Find and copy ripgrep binary
+    info "Looking for ripgrep binary..."
+    RIPGREP_PATH=$(find "$REPO_ROOT/node_modules" -path "*/@vscode/ripgrep/bin/rg" -type f 2>/dev/null | head -1)
+    if [ -n "$RIPGREP_PATH" ] && [ -f "$RIPGREP_PATH" ]; then
+        info "Found ripgrep at: $RIPGREP_PATH"
+        mkdir -p "$RELEASE_DIR/node_modules/@vscode/ripgrep/bin"
+        cp "$RIPGREP_PATH" "$RELEASE_DIR/node_modules/@vscode/ripgrep/bin/"
+        chmod +x "$RELEASE_DIR/node_modules/@vscode/ripgrep/bin/rg"
+        mkdir -p "$RELEASE_DIR/bin"
+        cp "$RIPGREP_PATH" "$RELEASE_DIR/bin/"
+        chmod +x "$RELEASE_DIR/bin/rg"
+    else
+        warn "ripgrep binary not found - users will need ripgrep installed"
+    fi
+
+    # Create the wrapper script
+    info "Creating wrapper script..."
+    cat > "$RELEASE_DIR/bin/roo" << 'WRAPPER_EOF'
+#!/usr/bin/env node
+
+import { fileURLToPath } from 'url';
+import { dirname, join } from 'path';
+
+const __filename = fileURLToPath(import.meta.url);
+const __dirname = dirname(__filename);
+
+// Set environment variables for the CLI
+process.env.ROO_CLI_ROOT = join(__dirname, '..');
+process.env.ROO_EXTENSION_PATH = join(__dirname, '..', 'extension');
+process.env.ROO_RIPGREP_PATH = join(__dirname, 'rg');
+
+// Import and run the actual CLI
+await import(join(__dirname, '..', 'lib', 'index.js'));
+WRAPPER_EOF
+
+    chmod +x "$RELEASE_DIR/bin/roo"
+
+    # Create empty .env file
+    touch "$RELEASE_DIR/.env"
+
+    # Create tarball
+    info "Creating tarball..."
+    cd "$REPO_ROOT"
+    tar -czvf "$TARBALL" "$(basename "$RELEASE_DIR")"
+
+    # Clean up release directory
+    rm -rf "$RELEASE_DIR"
+
+    # Show size
+    TARBALL_PATH="$REPO_ROOT/$TARBALL"
+    TARBALL_SIZE=$(ls -lh "$TARBALL_PATH" | awk '{print $5}')
+    info "Created: $TARBALL ($TARBALL_SIZE)"
+}
+
+# Verify local installation
+verify_local_install() {
+    if [ "$SKIP_VERIFY" = true ]; then
+        step "5/6" "Skipping verification (--skip-verify)"
+        return
+    fi
+
+    step "5/6" "Verifying installation..."
+
+    VERIFY_DIR="$REPO_ROOT/.verify-release"
+    VERIFY_INSTALL_DIR="$VERIFY_DIR/cli"
+    VERIFY_BIN_DIR="$VERIFY_DIR/bin"
+
+    rm -rf "$VERIFY_DIR"
+    mkdir -p "$VERIFY_DIR"
+
+    TARBALL_PATH="$REPO_ROOT/$TARBALL"
+
+    ROO_LOCAL_TARBALL="$TARBALL_PATH" \
+    ROO_INSTALL_DIR="$VERIFY_INSTALL_DIR" \
+    ROO_BIN_DIR="$VERIFY_BIN_DIR" \
+    ROO_VERSION="$VERSION" \
+    "$CLI_DIR/install.sh" || {
+        rm -rf "$VERIFY_DIR"
+        error "Installation verification failed!"
+    }
+
+    # Test --help
+    if ! "$VERIFY_BIN_DIR/roo" --help > /dev/null 2>&1; then
+        rm -rf "$VERIFY_DIR"
+        error "CLI --help check failed!"
+    fi
+    info "CLI --help check passed"
+
+    # Test --version
+    if ! "$VERIFY_BIN_DIR/roo" --version > /dev/null 2>&1; then
+        rm -rf "$VERIFY_DIR"
+        error "CLI --version check failed!"
+    fi
+    info "CLI --version check passed"
+
+    cd "$REPO_ROOT"
+    rm -rf "$VERIFY_DIR"
+
+    info "Verification passed!"
+}
+
+# Install locally
+install_local() {
+    if [ "$LOCAL_INSTALL" = false ]; then
+        step "6/6" "Skipping install (use --install to auto-install)"
+        return
+    fi
+
+    step "6/6" "Installing locally..."
+
+    TARBALL_PATH="$REPO_ROOT/$TARBALL"
+
+    ROO_LOCAL_TARBALL="$TARBALL_PATH" \
+    ROO_VERSION="$VERSION" \
+    "$CLI_DIR/install.sh" || {
+        error "Local installation failed!"
+    }
+
+    info "Local installation complete!"
+}
+
+# Print summary
+print_summary() {
+    echo ""
+    printf "${GREEN}${BOLD}✓ Local build complete for v$VERSION${NC}\n"
+    echo ""
+    echo "  Tarball: $REPO_ROOT/$TARBALL"
+    echo ""
+
+    if [ "$LOCAL_INSTALL" = true ]; then
+        echo "  Installed to: ~/.roo/cli"
+        echo "  Binary: ~/.local/bin/roo"
+        echo ""
+        echo "  Test it out:"
+        echo "    roo --version"
+        echo "    roo --help"
+    else
+        echo "  To install manually:"
+        echo "    ROO_LOCAL_TARBALL=$REPO_ROOT/$TARBALL ./apps/cli/install.sh"
+        echo ""
+        echo "  Or re-run with --install:"
+        echo "    ./apps/cli/scripts/build.sh --install"
+    fi
+    echo ""
+    echo "  For official multi-platform releases, use the GitHub Actions workflow:"
+    echo "    .github/workflows/cli-release.yml"
+    echo ""
+}
+
+# Main
+main() {
+    echo ""
+    printf "${BLUE}${BOLD}"
+    echo "  ╭─────────────────────────────────╮"
+    echo "  │   Roo Code CLI Local Build      │"
+    echo "  ╰─────────────────────────────────╯"
+    printf "${NC}"
+    echo ""
+
+    detect_platform
+    check_prerequisites
+    get_version
+    build
+    create_tarball
+    verify_local_install
+    install_local
+    print_summary
+}
+
+main

+ 0 - 711
apps/cli/scripts/release.sh

@@ -1,711 +0,0 @@
-#!/bin/bash
-# Roo Code CLI Release Script
-#
-# Usage:
-#   ./apps/cli/scripts/release.sh [options] [version]
-#
-# Options:
-#   --dry-run    Run all steps except creating the GitHub release
-#   --local      Build for local testing only (no GitHub checks, no changelog prompts)
-#   --install    Install locally after building (only with --local)
-#   --skip-verify Skip end-to-end verification tests (faster local builds)
-#
-# Examples:
-#   ./apps/cli/scripts/release.sh           # Use version from package.json
-#   ./apps/cli/scripts/release.sh 0.1.0     # Specify version
-#   ./apps/cli/scripts/release.sh --dry-run # Test the release flow without pushing
-#   ./apps/cli/scripts/release.sh --dry-run 0.1.0  # Dry run with specific version
-#   ./apps/cli/scripts/release.sh --local   # Build for local testing
-#   ./apps/cli/scripts/release.sh --local --install  # Build and install locally
-#   ./apps/cli/scripts/release.sh --local --skip-verify  # Fast local build
-#
-# This script:
-# 1. Builds the extension and CLI
-# 2. Creates a tarball for the current platform
-# 3. Creates a GitHub release and uploads the tarball (unless --dry-run or --local)
-#
-# Prerequisites:
-#   - GitHub CLI (gh) installed and authenticated (not needed for --local)
-#   - pnpm installed
-#   - Run from the monorepo root directory
-
-set -e
-
-# Parse arguments
-DRY_RUN=false
-LOCAL_BUILD=false
-LOCAL_INSTALL=false
-SKIP_VERIFY=false
-VERSION_ARG=""
-
-while [[ $# -gt 0 ]]; do
-    case $1 in
-        --dry-run)
-            DRY_RUN=true
-            shift
-            ;;
-        --local)
-            LOCAL_BUILD=true
-            shift
-            ;;
-        --install)
-            LOCAL_INSTALL=true
-            shift
-            ;;
-        --skip-verify)
-            SKIP_VERIFY=true
-            shift
-            ;;
-        -*)
-            echo "Unknown option: $1" >&2
-            exit 1
-            ;;
-        *)
-            VERSION_ARG="$1"
-            shift
-            ;;
-    esac
-done
-
-# Validate option combinations
-if [ "$LOCAL_INSTALL" = true ] && [ "$LOCAL_BUILD" = false ]; then
-    echo "Error: --install can only be used with --local" >&2
-    exit 1
-fi
-
-# Colors
-RED='\033[0;31m'
-GREEN='\033[0;32m'
-YELLOW='\033[1;33m'
-BLUE='\033[0;34m'
-BOLD='\033[1m'
-NC='\033[0m'
-
-info() { printf "${GREEN}==>${NC} %s\n" "$1"; }
-warn() { printf "${YELLOW}Warning:${NC} %s\n" "$1"; }
-error() { printf "${RED}Error:${NC} %s\n" "$1" >&2; exit 1; }
-step() { printf "${BLUE}${BOLD}[%s]${NC} %s\n" "$1" "$2"; }
-
-# Get script directory and repo root
-SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
-REPO_ROOT="$(cd "$SCRIPT_DIR/../../.." && pwd)"
-CLI_DIR="$REPO_ROOT/apps/cli"
-
-# Detect current platform
-detect_platform() {
-    OS=$(uname -s | tr '[:upper:]' '[:lower:]')
-    ARCH=$(uname -m)
-    
-    case "$OS" in
-        darwin) OS="darwin" ;;
-        linux) OS="linux" ;;
-        *) error "Unsupported OS: $OS" ;;
-    esac
-    
-    case "$ARCH" in
-        x86_64|amd64) ARCH="x64" ;;
-        arm64|aarch64) ARCH="arm64" ;;
-        *) error "Unsupported architecture: $ARCH" ;;
-    esac
-    
-    PLATFORM="${OS}-${ARCH}"
-}
-
-# Check prerequisites
-check_prerequisites() {
-    step "1/8" "Checking prerequisites..."
-    
-    # Skip GitHub CLI checks for local builds
-    if [ "$LOCAL_BUILD" = false ]; then
-        if ! command -v gh &> /dev/null; then
-            error "GitHub CLI (gh) is not installed. Install it with: brew install gh"
-        fi
-        
-        if ! gh auth status &> /dev/null; then
-            error "GitHub CLI is not authenticated. Run: gh auth login"
-        fi
-    fi
-    
-    if ! command -v pnpm &> /dev/null; then
-        error "pnpm is not installed."
-    fi
-    
-    if ! command -v node &> /dev/null; then
-        error "Node.js is not installed."
-    fi
-    
-    info "Prerequisites OK"
-}
-
-# Get version
-get_version() {
-    if [ -n "$VERSION_ARG" ]; then
-        VERSION="$VERSION_ARG"
-    else
-        VERSION=$(node -p "require('$CLI_DIR/package.json').version")
-    fi
-    
-    # For local builds, append a local suffix with git short hash
-    # This creates versions like: 0.1.0-local.abc1234
-    if [ "$LOCAL_BUILD" = true ]; then
-        GIT_SHORT_HASH=$(git rev-parse --short HEAD 2>/dev/null || echo "unknown")
-        # Only append suffix if not already a local version
-        if ! echo "$VERSION" | grep -qE '\-local\.'; then
-            VERSION="${VERSION}-local.${GIT_SHORT_HASH}"
-        fi
-    fi
-    
-    # Validate semver format (allow -local.hash suffix)
-    if ! echo "$VERSION" | grep -qE '^[0-9]+\.[0-9]+\.[0-9]+(-[a-zA-Z0-9.]+)?$'; then
-        error "Invalid version format: $VERSION (expected semver like 0.1.0)"
-    fi
-    
-    TAG="cli-v$VERSION"
-    info "Version: $VERSION (tag: $TAG)"
-}
-
-# Extract changelog content for a specific version
-# Returns the content between the version header and the next version header (or EOF)
-get_changelog_content() {
-    CHANGELOG_FILE="$CLI_DIR/CHANGELOG.md"
-    
-    if [ ! -f "$CHANGELOG_FILE" ]; then
-        warn "No CHANGELOG.md found at $CHANGELOG_FILE"
-        CHANGELOG_CONTENT=""
-        return
-    fi
-    
-    # Try to find the version section (handles both "[0.0.43]" and "[0.0.43] - date" formats)
-    # Also handles "Unreleased" marker
-    VERSION_PATTERN="^\#\# \[${VERSION}\]"
-    
-    # Check if the version exists in the changelog
-    if ! grep -qE "$VERSION_PATTERN" "$CHANGELOG_FILE"; then
-        warn "No changelog entry found for version $VERSION"
-        # Skip prompts for local builds
-        if [ "$LOCAL_BUILD" = true ]; then
-            info "Skipping changelog prompt for local build"
-            CHANGELOG_CONTENT=""
-            return
-        fi
-        warn "Please add an entry to $CHANGELOG_FILE before releasing"
-        echo ""
-        echo "Expected format:"
-        echo "  ## [$VERSION] - $(date +%Y-%m-%d)"
-        echo "  "
-        echo "  ### Added"
-        echo "  - Your changes here"
-        echo ""
-        read -p "Continue without changelog content? [y/N] " -n 1 -r
-        echo
-        if [[ ! $REPLY =~ ^[Yy]$ ]]; then
-            error "Aborted. Please add a changelog entry and try again."
-        fi
-        CHANGELOG_CONTENT=""
-        return
-    fi
-    
-    # Extract content between this version and the next version header (or EOF)
-    # Uses awk to capture everything between ## [VERSION] and the next ## [
-    # Using index() with "[VERSION]" ensures exact matching (1.0.1 won't match 1.0.10)
-    CHANGELOG_CONTENT=$(awk -v version="$VERSION" '
-        BEGIN { found = 0; content = ""; target = "[" version "]" }
-        /^## \[/ {
-            if (found) { exit }
-            if (index($0, target) > 0) { found = 1; next }
-        }
-        found { content = content $0 "\n" }
-        END { print content }
-    ' "$CHANGELOG_FILE")
-    
-    # Trim leading/trailing whitespace
-    CHANGELOG_CONTENT=$(echo "$CHANGELOG_CONTENT" | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//')
-    
-    if [ -n "$CHANGELOG_CONTENT" ]; then
-        info "Found changelog content for version $VERSION"
-    else
-        warn "Changelog entry for $VERSION appears to be empty"
-    fi
-}
-
-# Build everything
-build() {
-    step "2/8" "Building extension bundle..."
-    cd "$REPO_ROOT"
-    pnpm bundle
-    
-    step "3/8" "Building CLI..."
-    pnpm --filter @roo-code/cli build
-    
-    info "Build complete"
-}
-
-# Create release tarball
-create_tarball() {
-    step "4/8" "Creating release tarball for $PLATFORM..."
-    
-    RELEASE_DIR="$REPO_ROOT/roo-cli-${PLATFORM}"
-    TARBALL="roo-cli-${PLATFORM}.tar.gz"
-    
-    # Clean up any previous build
-    rm -rf "$RELEASE_DIR"
-    rm -f "$REPO_ROOT/$TARBALL"
-    
-    # Create directory structure
-    mkdir -p "$RELEASE_DIR/bin"
-    mkdir -p "$RELEASE_DIR/lib"
-    mkdir -p "$RELEASE_DIR/extension"
-    
-    # Copy CLI dist files
-    info "Copying CLI files..."
-    cp -r "$CLI_DIR/dist/"* "$RELEASE_DIR/lib/"
-    
-    # Create package.json for npm install (runtime dependencies that can't be bundled)
-    info "Creating package.json..."
-    node -e "
-      const pkg = require('$CLI_DIR/package.json');
-      const newPkg = {
-        name: '@roo-code/cli',
-        version: '$VERSION',
-        type: 'module',
-        dependencies: {
-          '@inkjs/ui': pkg.dependencies['@inkjs/ui'],
-          '@trpc/client': pkg.dependencies['@trpc/client'],
-          'commander': pkg.dependencies.commander,
-          'fuzzysort': pkg.dependencies.fuzzysort,
-          'ink': pkg.dependencies.ink,
-          'p-wait-for': pkg.dependencies['p-wait-for'],
-          'react': pkg.dependencies.react,
-          'superjson': pkg.dependencies.superjson,
-          'zustand': pkg.dependencies.zustand
-        }
-      };
-      console.log(JSON.stringify(newPkg, null, 2));
-    " > "$RELEASE_DIR/package.json"
-    
-    # Copy extension bundle
-    info "Copying extension bundle..."
-    cp -r "$REPO_ROOT/src/dist/"* "$RELEASE_DIR/extension/"
-    
-    # Add package.json to extension directory to mark it as CommonJS
-    # This is necessary because the main package.json has "type": "module"
-    # but the extension bundle is CommonJS
-    echo '{"type": "commonjs"}' > "$RELEASE_DIR/extension/package.json"
-    
-    # Find and copy ripgrep binary
-    # The extension looks for ripgrep at: appRoot/node_modules/@vscode/ripgrep/bin/rg
-    # The CLI sets appRoot to the CLI package root, so we need to put ripgrep there
-    info "Looking for ripgrep binary..."
-    RIPGREP_PATH=$(find "$REPO_ROOT/node_modules" -path "*/@vscode/ripgrep/bin/rg" -type f 2>/dev/null | head -1)
-    if [ -n "$RIPGREP_PATH" ] && [ -f "$RIPGREP_PATH" ]; then
-        info "Found ripgrep at: $RIPGREP_PATH"
-        # Create the expected directory structure for the extension to find ripgrep
-        mkdir -p "$RELEASE_DIR/node_modules/@vscode/ripgrep/bin"
-        cp "$RIPGREP_PATH" "$RELEASE_DIR/node_modules/@vscode/ripgrep/bin/"
-        chmod +x "$RELEASE_DIR/node_modules/@vscode/ripgrep/bin/rg"
-        # Also keep a copy in bin/ for direct access
-        mkdir -p "$RELEASE_DIR/bin"
-        cp "$RIPGREP_PATH" "$RELEASE_DIR/bin/"
-        chmod +x "$RELEASE_DIR/bin/rg"
-    else
-        warn "ripgrep binary not found - users will need ripgrep installed"
-    fi
-    
-    # Create the wrapper script
-    info "Creating wrapper script..."
-    cat > "$RELEASE_DIR/bin/roo" << 'WRAPPER_EOF'
-#!/usr/bin/env node
-
-import { fileURLToPath } from 'url';
-import { dirname, join } from 'path';
-
-const __filename = fileURLToPath(import.meta.url);
-const __dirname = dirname(__filename);
-
-// Set environment variables for the CLI
-// ROO_CLI_ROOT is the installed CLI package root (where node_modules/@vscode/ripgrep is)
-process.env.ROO_CLI_ROOT = join(__dirname, '..');
-process.env.ROO_EXTENSION_PATH = join(__dirname, '..', 'extension');
-process.env.ROO_RIPGREP_PATH = join(__dirname, 'rg');
-
-// Import and run the actual CLI
-await import(join(__dirname, '..', 'lib', 'index.js'));
-WRAPPER_EOF
-
-    chmod +x "$RELEASE_DIR/bin/roo"
-    
-    # Create empty .env file to suppress dotenvx warnings
-    touch "$RELEASE_DIR/.env"
-    
-    # Create empty .env file to suppress dotenvx warnings
-    touch "$RELEASE_DIR/.env"
-    
-    # Create tarball
-    info "Creating tarball..."
-    cd "$REPO_ROOT"
-    tar -czvf "$TARBALL" "$(basename "$RELEASE_DIR")"
-    
-    # Clean up release directory
-    rm -rf "$RELEASE_DIR"
-    
-    # Show size
-    TARBALL_PATH="$REPO_ROOT/$TARBALL"
-    TARBALL_SIZE=$(ls -lh "$TARBALL_PATH" | awk '{print $5}')
-    info "Created: $TARBALL ($TARBALL_SIZE)"
-}
-
-# Verify local installation
-verify_local_install() {
-    if [ "$SKIP_VERIFY" = true ]; then
-        step "5/8" "Skipping verification (--skip-verify)"
-        return
-    fi
-    
-    step "5/8" "Verifying local installation..."
-    
-    VERIFY_DIR="$REPO_ROOT/.verify-release"
-    VERIFY_INSTALL_DIR="$VERIFY_DIR/cli"
-    VERIFY_BIN_DIR="$VERIFY_DIR/bin"
-    
-    # Clean up any previous verification directory
-    rm -rf "$VERIFY_DIR"
-    mkdir -p "$VERIFY_DIR"
-    
-    # Run the actual install script with the local tarball
-    info "Running install script with local tarball..."
-    TARBALL_PATH="$REPO_ROOT/$TARBALL"
-    
-    ROO_LOCAL_TARBALL="$TARBALL_PATH" \
-    ROO_INSTALL_DIR="$VERIFY_INSTALL_DIR" \
-    ROO_BIN_DIR="$VERIFY_BIN_DIR" \
-    ROO_VERSION="$VERSION" \
-    "$CLI_DIR/install.sh" || {
-        echo ""
-        warn "Install script failed. Showing tarball contents:"
-        tar -tzf "$TARBALL_PATH" 2>&1 || true
-        echo ""
-        rm -rf "$VERIFY_DIR"
-        error "Installation verification failed! The install script could not complete successfully."
-    }
-    
-    # Verify the CLI runs correctly with basic commands
-    info "Testing installed CLI..."
-    
-    # Test --help
-    if ! "$VERIFY_BIN_DIR/roo" --help > /dev/null 2>&1; then
-        echo ""
-        warn "CLI --help output:"
-        "$VERIFY_BIN_DIR/roo" --help 2>&1 || true
-        echo ""
-        rm -rf "$VERIFY_DIR"
-        error "CLI --help check failed! The release tarball may have missing dependencies."
-    fi
-    info "CLI --help check passed"
-    
-    # Test --version
-    if ! "$VERIFY_BIN_DIR/roo" --version > /dev/null 2>&1; then
-        echo ""
-        warn "CLI --version output:"
-        "$VERIFY_BIN_DIR/roo" --version 2>&1 || true
-        echo ""
-        rm -rf "$VERIFY_DIR"
-        error "CLI --version check failed! The release tarball may have missing dependencies."
-    fi
-    info "CLI --version check passed"
-    
-    # Run a simple end-to-end test to verify the CLI actually works
-    info "Running end-to-end verification test..."
-    
-    # Create a temporary workspace for the test
-    VERIFY_WORKSPACE="$VERIFY_DIR/workspace"
-    mkdir -p "$VERIFY_WORKSPACE"
-    
-    # Run the CLI with a simple prompt
-    if timeout 60 "$VERIFY_BIN_DIR/roo" --yes --oneshot -w "$VERIFY_WORKSPACE" "1+1=?" > "$VERIFY_DIR/test-output.log" 2>&1; then
-        info "End-to-end test passed"
-    else
-        EXIT_CODE=$?
-        echo ""
-        warn "End-to-end test failed (exit code: $EXIT_CODE). Output:"
-        cat "$VERIFY_DIR/test-output.log" 2>&1 || true
-        echo ""
-        rm -rf "$VERIFY_DIR"
-        error "CLI end-to-end test failed! The CLI may be broken."
-    fi
-    
-    # Clean up verification directory
-    cd "$REPO_ROOT"
-    rm -rf "$VERIFY_DIR"
-    
-    info "Local verification passed!"
-}
-
-# Create checksum
-create_checksum() {
-    step "6/8" "Creating checksum..."
-    cd "$REPO_ROOT"
-    
-    if command -v sha256sum &> /dev/null; then
-        sha256sum "$TARBALL" > "${TARBALL}.sha256"
-    elif command -v shasum &> /dev/null; then
-        shasum -a 256 "$TARBALL" > "${TARBALL}.sha256"
-    else
-        warn "No sha256sum or shasum found, skipping checksum"
-        return
-    fi
-    
-    info "Checksum: $(cat "${TARBALL}.sha256")"
-}
-
-# Check if release already exists
-check_existing_release() {
-    step "7/8" "Checking for existing release..."
-    
-    if gh release view "$TAG" &> /dev/null; then
-        warn "Release $TAG already exists"
-        read -p "Do you want to delete it and create a new one? [y/N] " -n 1 -r
-        echo
-        if [[ $REPLY =~ ^[Yy]$ ]]; then
-            info "Deleting existing release..."
-            gh release delete "$TAG" --yes
-            # Also delete the tag if it exists
-            git tag -d "$TAG" 2>/dev/null || true
-            git push origin ":refs/tags/$TAG" 2>/dev/null || true
-        else
-            error "Aborted. Use a different version or delete the existing release manually."
-        fi
-    fi
-}
-
-# Create GitHub release
-create_release() {
-    step "8/8" "Creating GitHub release..."
-    cd "$REPO_ROOT"
-
-    # Get the current commit SHA for the release target
-    COMMIT_SHA=$(git rev-parse HEAD)
-    
-    # Verify the commit exists on GitHub before attempting to create the release
-    # This prevents the "Release.target_commitish is invalid" error
-    info "Verifying commit ${COMMIT_SHA:0:8} exists on GitHub..."
-    git fetch origin 2>/dev/null || true
-    if ! git branch -r --contains "$COMMIT_SHA" 2>/dev/null | grep -q "origin/"; then
-        warn "Commit ${COMMIT_SHA:0:8} has not been pushed to GitHub"
-        echo ""
-        echo "The release script needs to create a release at your current commit,"
-        echo "but this commit hasn't been pushed to GitHub yet."
-        echo ""
-        read -p "Push current branch to origin now? [Y/n] " -n 1 -r
-        echo
-        if [[ ! $REPLY =~ ^[Nn]$ ]]; then
-            info "Pushing to origin..."
-            git push origin HEAD || error "Failed to push to origin. Please push manually and try again."
-        else
-            error "Aborted. Please push your commits to GitHub and try again."
-        fi
-    fi
-    info "Commit verified on GitHub"
-
-    # Build the What's New section from changelog content
-    WHATS_NEW_SECTION=""
-    if [ -n "$CHANGELOG_CONTENT" ]; then
-        WHATS_NEW_SECTION="## What's New
-
-$CHANGELOG_CONTENT
-
-"
-    fi
-    
-    RELEASE_NOTES=$(cat << EOF
-${WHATS_NEW_SECTION}## Installation
-
-\`\`\`bash
-curl -fsSL https://raw.githubusercontent.com/RooCodeInc/Roo-Code/main/apps/cli/install.sh | sh
-\`\`\`
-
-Or install a specific version:
-\`\`\`bash
-ROO_VERSION=$VERSION curl -fsSL https://raw.githubusercontent.com/RooCodeInc/Roo-Code/main/apps/cli/install.sh | sh
-\`\`\`
-
-## Requirements
-
-- Node.js 20 or higher
-- macOS (Intel or Apple Silicon) or Linux (x64 or ARM64)
-
-## Usage
-
-\`\`\`bash
-# Run a task
-roo "What is this project?"
-
-# See all options
-roo --help
-\`\`\`
-
-## Platform Support
-
-This release includes:
-- \`roo-cli-${PLATFORM}.tar.gz\` - Built on $(uname -s) $(uname -m)
-
-> **Note:** Additional platforms will be added as needed. If you need a different platform, please open an issue.
-
-## Checksum
-
-\`\`\`
-$(cat "${TARBALL}.sha256" 2>/dev/null || echo "N/A")
-\`\`\`
-EOF
-)
-
-    info "Creating release at commit: ${COMMIT_SHA:0:8}"
-    
-    # Create release (gh will create the tag automatically)
-    info "Creating release..."
-    RELEASE_FILES="$TARBALL"
-    if [ -f "${TARBALL}.sha256" ]; then
-        RELEASE_FILES="$RELEASE_FILES ${TARBALL}.sha256"
-    fi
-    
-    gh release create "$TAG" \
-        --title "Roo Code CLI v$VERSION" \
-        --notes "$RELEASE_NOTES" \
-        --prerelease \
-        --target "$COMMIT_SHA" \
-        $RELEASE_FILES
-    
-    info "Release created!"
-}
-
-# Cleanup
-cleanup() {
-    info "Cleaning up..."
-    cd "$REPO_ROOT"
-    rm -f "$TARBALL" "${TARBALL}.sha256"
-}
-
-# Print summary
-print_summary() {
-    echo ""
-    printf "${GREEN}${BOLD}✓ Release v$VERSION created successfully!${NC}\n"
-    echo ""
-    echo "  Release URL: https://github.com/RooCodeInc/Roo-Code/releases/tag/$TAG"
-    echo ""
-    echo "  Install with:"
-    echo "    curl -fsSL https://raw.githubusercontent.com/RooCodeInc/Roo-Code/main/apps/cli/install.sh | sh"
-    echo ""
-}
-
-# Print dry-run summary
-print_dry_run_summary() {
-    echo ""
-    printf "${YELLOW}${BOLD}✓ Dry run complete for v$VERSION${NC}\n"
-    echo ""
-    echo "  The following artifacts were created:"
-    echo "    - $TARBALL"
-    if [ -f "${TARBALL}.sha256" ]; then
-        echo "    - ${TARBALL}.sha256"
-    fi
-    echo ""
-    echo "  To complete the release, run without --dry-run:"
-    echo "    ./apps/cli/scripts/release.sh $VERSION"
-    echo ""
-    echo "  Or manually upload the tarball to a new GitHub release."
-    echo ""
-}
-
-# Print local build summary
-print_local_summary() {
-    echo ""
-    printf "${GREEN}${BOLD}✓ Local build complete for v$VERSION${NC}\n"
-    echo ""
-    echo "  Tarball: $REPO_ROOT/$TARBALL"
-    if [ -f "${TARBALL}.sha256" ]; then
-        echo "  Checksum: $REPO_ROOT/${TARBALL}.sha256"
-    fi
-    echo ""
-    echo "  To install manually:"
-    echo "    ROO_LOCAL_TARBALL=$REPO_ROOT/$TARBALL ./apps/cli/install.sh"
-    echo ""
-    echo "  Or re-run with --install to install automatically:"
-    echo "    ./apps/cli/scripts/release.sh --local --install"
-    echo ""
-}
-
-# Install locally using the install script
-install_local() {
-    step "7/8" "Installing locally..."
-    
-    TARBALL_PATH="$REPO_ROOT/$TARBALL"
-    
-    ROO_LOCAL_TARBALL="$TARBALL_PATH" \
-    ROO_VERSION="$VERSION" \
-    "$CLI_DIR/install.sh" || {
-        error "Local installation failed!"
-    }
-    
-    info "Local installation complete!"
-}
-
-# Print local install summary
-print_local_install_summary() {
-    echo ""
-    printf "${GREEN}${BOLD}✓ Local build installed for v$VERSION${NC}\n"
-    echo ""
-    echo "  Tarball: $REPO_ROOT/$TARBALL"
-    echo "  Installed to: ~/.roo/cli"
-    echo "  Binary: ~/.local/bin/roo"
-    echo ""
-    echo "  Test it out:"
-    echo "    roo --version"
-    echo "    roo --help"
-    echo ""
-}
-
-# Main
-main() {
-    echo ""
-    printf "${BLUE}${BOLD}"
-    echo "  ╭─────────────────────────────────╮"
-    echo "  │   Roo Code CLI Release Script   │"
-    echo "  ╰─────────────────────────────────╯"
-    printf "${NC}"
-    
-    if [ "$DRY_RUN" = true ]; then
-        printf "${YELLOW}         (DRY RUN MODE)${NC}\n"
-    elif [ "$LOCAL_BUILD" = true ]; then
-        printf "${YELLOW}         (LOCAL BUILD MODE)${NC}\n"
-    fi
-    echo ""
-    
-    detect_platform
-    check_prerequisites
-    get_version
-    get_changelog_content
-    build
-    create_tarball
-    verify_local_install
-    create_checksum
-    
-    if [ "$LOCAL_BUILD" = true ]; then
-        step "7/8" "Skipping GitHub checks (local build)"
-        if [ "$LOCAL_INSTALL" = true ]; then
-            install_local
-            print_local_install_summary
-        else
-            step "8/8" "Skipping installation (use --install to auto-install)"
-            print_local_summary
-        fi
-    elif [ "$DRY_RUN" = true ]; then
-        step "7/8" "Skipping existing release check (dry run)"
-        step "8/8" "Skipping GitHub release creation (dry run)"
-        print_dry_run_summary
-    else
-        check_existing_release
-        create_release
-        cleanup
-        print_summary
-    fi
-}
-
-main