Ver Fonte

Add a cli installer (#10474)

Co-authored-by: roomote[bot] <219738659+roomote[bot]@users.noreply.github.com>
Co-authored-by: ellipsis-dev[bot] <65095814+ellipsis-dev[bot]@users.noreply.github.com>
Chris Estreich há 2 meses atrás
pai
commit
861139ca24
5 ficheiros alterados com 731 adições e 18 exclusões
  1. 68 15
      apps/cli/README.md
  2. 287 0
      apps/cli/install.sh
  3. 363 0
      apps/cli/scripts/release.sh
  4. 9 1
      apps/cli/src/utils.ts
  5. 4 2
      apps/cli/tsup.config.ts

+ 68 - 15
apps/cli/README.md

@@ -8,6 +8,49 @@ This CLI uses the `@roo-code/vscode-shim` package to provide a VSCode API compat
 
 ## Installation
 
+### Quick Install (Recommended)
+
+Install the Roo Code CLI with a single command:
+
+```bash
+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)
+
+**Custom installation directory:**
+
+```bash
+ROO_INSTALL_DIR=/opt/roo-code ROO_BIN_DIR=/usr/local/bin curl -fsSL ... | sh
+```
+
+**Install a specific version:**
+
+```bash
+ROO_VERSION=0.1.0 curl -fsSL https://raw.githubusercontent.com/RooCodeInc/Roo-Code/main/apps/cli/install.sh | sh
+```
+
+### Updating
+
+Re-run the install script to update to the latest version:
+
+```bash
+curl -fsSL https://raw.githubusercontent.com/RooCodeInc/Roo-Code/main/apps/cli/install.sh | sh
+```
+
+### Uninstalling
+
+```bash
+rm -rf ~/.roo/cli ~/.local/bin/roo
+```
+
+### Development Installation
+
+For contributing or development:
+
 ```bash
 # From the monorepo root.
 pnpm install
@@ -28,13 +71,7 @@ By default, the CLI prompts for approval before executing actions:
 ```bash
 export OPENROUTER_API_KEY=sk-or-v1-...
 
-pnpm --filter @roo-code/cli start \
-  -x \
-  -p openrouter \
-  -k $OPENROUTER_API_KEY \
-  -m anthropic/claude-sonnet-4.5 \
-  --workspace ~/Documents/my-project \
-  "What is this project?"
+roo "What is this project?" --workspace ~/Documents/my-project
 ```
 
 In interactive mode:
@@ -49,14 +86,7 @@ In interactive mode:
 For automation and scripts, use `-y` to auto-approve all actions:
 
 ```bash
-pnpm --filter @roo-code/cli start \
-  -y \
-  -x \
-  -p openrouter \
-  -k $OPENROUTER_API_KEY \
-  -m anthropic/claude-sonnet-4.5 \
-  --workspace ~/Documents/my-project \
-  "Refactor the utils.ts file"
+roo -y "Refactor the utils.ts file" --workspace ~/Documents/my-project
 ```
 
 In non-interactive mode:
@@ -158,6 +188,29 @@ pnpm check-types
 pnpm lint
 ```
 
+## Releasing
+
+To create a new release, run the release script from the monorepo root:
+
+```bash
+# Release using version from package.json
+./apps/cli/scripts/release.sh
+
+# Release with a specific version
+./apps/cli/scripts/release.sh 0.1.0
+```
+
+The script will:
+
+1. Build the extension and CLI
+2. Create a platform-specific tarball (for your current OS/architecture)
+3. Create a GitHub release with the tarball attached
+
+**Prerequisites:**
+
+- GitHub CLI (`gh`) installed and authenticated (`gh auth login`)
+- pnpm installed
+
 ## Troubleshooting
 
 ### Extension bundle not found

+ 287 - 0
apps/cli/install.sh

@@ -0,0 +1,287 @@
+#!/bin/sh
+# Roo Code CLI Installer
+# Usage: curl -fsSL https://raw.githubusercontent.com/RooCodeInc/Roo-Code/main/apps/cli/install.sh | sh
+#
+# Environment variables:
+#   ROO_INSTALL_DIR - Installation directory (default: ~/.roo/cli)
+#   ROO_BIN_DIR     - Binary symlink directory (default: ~/.local/bin)
+#   ROO_VERSION     - Specific version to install (default: latest)
+
+set -e
+
+# Configuration
+INSTALL_DIR="${ROO_INSTALL_DIR:-$HOME/.roo/cli}"
+BIN_DIR="${ROO_BIN_DIR:-$HOME/.local/bin}"
+REPO="RooCodeInc/Roo-Code"
+MIN_NODE_VERSION=20
+
+# Color output (only if terminal supports it)
+if [ -t 1 ]; then
+    RED='\033[0;31m'
+    GREEN='\033[0;32m'
+    YELLOW='\033[1;33m'
+    BLUE='\033[0;34m'
+    BOLD='\033[1m'
+    NC='\033[0m'
+else
+    RED=''
+    GREEN=''
+    YELLOW=''
+    BLUE=''
+    BOLD=''
+    NC=''
+fi
+
+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; }
+
+# Check Node.js version
+check_node() {
+    if ! command -v node >/dev/null 2>&1; then
+        error "Node.js is not installed. Please install Node.js $MIN_NODE_VERSION or higher.
+
+Install Node.js:
+  - macOS: brew install node
+  - Linux: https://nodejs.org/en/download/package-manager
+  - Or use a version manager like fnm, nvm, or mise"
+    fi
+    
+    NODE_VERSION=$(node -v | sed 's/v//' | cut -d. -f1)
+    if [ "$NODE_VERSION" -lt "$MIN_NODE_VERSION" ]; then
+        error "Node.js $MIN_NODE_VERSION+ required. Found: $(node -v)
+
+Please upgrade Node.js to version $MIN_NODE_VERSION or higher."
+    fi
+    
+    info "Found Node.js $(node -v)"
+}
+
+# Detect OS and architecture
+detect_platform() {
+    OS=$(uname -s | tr '[:upper:]' '[:lower:]')
+    ARCH=$(uname -m)
+    
+    case "$OS" in
+        darwin) OS="darwin" ;;
+        linux) OS="linux" ;;
+        mingw*|msys*|cygwin*) 
+            error "Windows is not supported by this installer. Please use WSL or install manually."
+            ;;
+        *) 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}"
+    info "Detected platform: $PLATFORM"
+}
+
+# Get latest release version or use specified version
+get_version() {
+    if [ -n "$ROO_VERSION" ]; then
+        VERSION="$ROO_VERSION"
+        info "Using specified version: $VERSION"
+        return
+    fi
+    
+    info "Fetching latest version..."
+    
+    # Try to get the latest cli release
+    RELEASES_JSON=$(curl -fsSL "https://api.github.com/repos/$REPO/releases" 2>/dev/null) || {
+        error "Failed to fetch releases from GitHub. Check your internet connection."
+    }
+    
+    # Extract the latest cli-v* tag
+    VERSION=$(echo "$RELEASES_JSON" | 
+              grep -o '"tag_name": "cli-v[^"]*"' | 
+              head -1 | 
+              sed 's/"tag_name": "cli-v//' | 
+              sed 's/"//')
+    
+    if [ -z "$VERSION" ]; then
+        error "Could not find any CLI releases. The CLI may not have been released yet."
+    fi
+    
+    info "Latest version: $VERSION"
+}
+
+# Download and extract
+download_and_install() {
+    TARBALL="roo-cli-${PLATFORM}.tar.gz"
+    URL="https://github.com/$REPO/releases/download/cli-v${VERSION}/${TARBALL}"
+    
+    info "Downloading from $URL..."
+    
+    # Create temp directory
+    TMP_DIR=$(mktemp -d)
+    trap "rm -rf $TMP_DIR" EXIT
+    
+    # Download with progress indicator
+    HTTP_CODE=$(curl -fsSL -w "%{http_code}" "$URL" -o "$TMP_DIR/$TARBALL" 2>/dev/null) || {
+        if [ "$HTTP_CODE" = "404" ]; then
+            error "Release not found for platform $PLATFORM version $VERSION.
+
+Available at: https://github.com/$REPO/releases"
+        fi
+        error "Download failed. HTTP code: $HTTP_CODE"
+    }
+
+    # Verify we got something
+    if [ ! -s "$TMP_DIR/$TARBALL" ]; then
+        error "Downloaded file is empty. Please try again."
+    fi
+
+    # Remove old installation if exists
+    if [ -d "$INSTALL_DIR" ]; then
+        info "Removing previous installation..."
+        rm -rf "$INSTALL_DIR"
+    fi
+    
+    mkdir -p "$INSTALL_DIR"
+    
+    # Extract
+    info "Extracting to $INSTALL_DIR..."
+    tar -xzf "$TMP_DIR/$TARBALL" -C "$INSTALL_DIR" --strip-components=1 || {
+        error "Failed to extract tarball. The download may be corrupted."
+    }
+    
+    # Save ripgrep binary before npm install (npm install will overwrite node_modules)
+    RIPGREP_BIN=""
+    if [ -f "$INSTALL_DIR/node_modules/@vscode/ripgrep/bin/rg" ]; then
+        RIPGREP_BIN="$TMP_DIR/rg"
+        cp "$INSTALL_DIR/node_modules/@vscode/ripgrep/bin/rg" "$RIPGREP_BIN"
+    fi
+    
+    # Install npm dependencies
+    info "Installing dependencies..."
+    cd "$INSTALL_DIR"
+    npm install --production --silent 2>/dev/null || {
+        warn "npm install failed, trying with --legacy-peer-deps..."
+        npm install --production --legacy-peer-deps --silent 2>/dev/null || {
+            error "Failed to install dependencies. Make sure npm is available."
+        }
+    }
+    cd - > /dev/null
+    
+    # Restore ripgrep binary after npm install
+    if [ -n "$RIPGREP_BIN" ] && [ -f "$RIPGREP_BIN" ]; then
+        mkdir -p "$INSTALL_DIR/node_modules/@vscode/ripgrep/bin"
+        cp "$RIPGREP_BIN" "$INSTALL_DIR/node_modules/@vscode/ripgrep/bin/rg"
+        chmod +x "$INSTALL_DIR/node_modules/@vscode/ripgrep/bin/rg"
+    fi
+    
+    # Make executable
+    chmod +x "$INSTALL_DIR/bin/roo"
+    
+    # Also make ripgrep executable if it exists
+    if [ -f "$INSTALL_DIR/bin/rg" ]; then
+        chmod +x "$INSTALL_DIR/bin/rg"
+    fi
+}
+
+# Create symlink in bin directory
+setup_bin() {
+    mkdir -p "$BIN_DIR"
+    
+    # Remove old symlink if exists
+    if [ -L "$BIN_DIR/roo" ] || [ -f "$BIN_DIR/roo" ]; then
+        rm -f "$BIN_DIR/roo"
+    fi
+    
+    ln -sf "$INSTALL_DIR/bin/roo" "$BIN_DIR/roo"
+    info "Created symlink: $BIN_DIR/roo"
+}
+
+# Check if bin dir is in PATH and provide instructions
+check_path() {
+    case ":$PATH:" in
+        *":$BIN_DIR:"*) 
+            # Already in PATH
+            return 0
+            ;;
+    esac
+    
+    warn "$BIN_DIR is not in your PATH"
+    echo ""
+    echo "Add this line to your shell profile:"
+    echo ""
+    
+    # Detect shell and provide specific instructions
+    SHELL_NAME=$(basename "$SHELL")
+    case "$SHELL_NAME" in
+        zsh)
+            echo "  echo 'export PATH=\"$BIN_DIR:\$PATH\"' >> ~/.zshrc"
+            echo "  source ~/.zshrc"
+            ;;
+        bash)
+            if [ -f "$HOME/.bashrc" ]; then
+                echo "  echo 'export PATH=\"$BIN_DIR:\$PATH\"' >> ~/.bashrc"
+                echo "  source ~/.bashrc"
+            else
+                echo "  echo 'export PATH=\"$BIN_DIR:\$PATH\"' >> ~/.bash_profile"
+                echo "  source ~/.bash_profile"
+            fi
+            ;;
+        fish)
+            echo "  set -Ux fish_user_paths $BIN_DIR \$fish_user_paths"
+            ;;
+        *)
+            echo "  export PATH=\"$BIN_DIR:\$PATH\""
+            ;;
+    esac
+    echo ""
+}
+
+# Verify installation
+verify_install() {
+    if [ -x "$BIN_DIR/roo" ]; then
+        info "Verifying installation..."
+        # Just check if it runs without error
+        "$BIN_DIR/roo" --version >/dev/null 2>&1 || true
+    fi
+}
+
+# Print success message
+print_success() {
+    echo ""
+    printf "${GREEN}${BOLD}✓ Roo Code CLI installed successfully!${NC}\n"
+    echo ""
+    echo "  Installation: $INSTALL_DIR"
+    echo "  Binary: $BIN_DIR/roo"
+    echo "  Version: $VERSION"
+    echo ""
+    echo "  ${BOLD}Get started:${NC}"
+    echo "    roo --help"
+    echo ""
+    echo "  ${BOLD}Example:${NC}"
+    echo "    export OPENROUTER_API_KEY=sk-or-v1-..."
+    echo "    roo \"What is this project?\" --workspace ~/my-project"
+    echo ""
+}
+
+# Main
+main() {
+    echo ""
+    printf "${BLUE}${BOLD}"
+    echo "  ╭─────────────────────────────────╮"
+    echo "  │     Roo Code CLI Installer      │"
+    echo "  ╰─────────────────────────────────╯"
+    printf "${NC}"
+    echo ""
+    
+    check_node
+    detect_platform
+    get_version
+    download_and_install
+    setup_bin
+    check_path
+    verify_install
+    print_success
+}
+
+main "$@"

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

@@ -0,0 +1,363 @@
+#!/bin/bash
+# Roo Code CLI Release Script
+# 
+# Usage:
+#   ./apps/cli/scripts/release.sh [version]
+#
+# Examples:
+#   ./apps/cli/scripts/release.sh           # Use version from package.json
+#   ./apps/cli/scripts/release.sh 0.1.0     # Specify version
+#
+# 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
+#
+# Prerequisites:
+#   - GitHub CLI (gh) installed and authenticated
+#   - pnpm installed
+#   - Run from the monorepo root directory
+
+set -e
+
+# 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/7" "Checking prerequisites..."
+    
+    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
+    
+    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 "$1" ]; then
+        VERSION="$1"
+    else
+        VERSION=$(node -p "require('$CLI_DIR/package.json').version")
+    fi
+    
+    # Validate semver format
+    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)"
+}
+
+# Build everything
+build() {
+    step "2/7" "Building extension bundle..."
+    cd "$REPO_ROOT"
+    pnpm bundle
+    
+    step "3/7" "Building CLI..."
+    pnpm --filter @roo-code/cli build
+    
+    info "Build complete"
+}
+
+# Create release tarball
+create_tarball() {
+    step "4/7" "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 (only runtime dependencies)
+    info "Creating package.json..."
+    node -e "
+      const pkg = require('$CLI_DIR/package.json');
+      const newPkg = {
+        name: '@roo-code/cli',
+        version: pkg.version,
+        type: 'module',
+        dependencies: {
+          commander: pkg.dependencies.commander
+        }
+      };
+      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
+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 version file
+    echo "$VERSION" > "$RELEASE_DIR/VERSION"
+    
+    # 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)"
+}
+
+# Create checksum
+create_checksum() {
+    step "5/7" "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 "6/7" "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 "7/7" "Creating GitHub release..."
+    cd "$REPO_ROOT"
+    
+    RELEASE_NOTES=$(cat << EOF
+## 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
+# Set your API key
+export OPENROUTER_API_KEY=sk-or-v1-...
+
+# Run a task
+roo "What is this project?" --workspace ~/my-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
+)
+
+    # Get the current commit SHA for the release target
+    COMMIT_SHA=$(git rev-parse HEAD)
+    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 ""
+}
+
+# Main
+main() {
+    echo ""
+    printf "${BLUE}${BOLD}"
+    echo "  ╭─────────────────────────────────╮"
+    echo "  │   Roo Code CLI Release Script   │"
+    echo "  ╰─────────────────────────────────╯"
+    printf "${NC}"
+    echo ""
+    
+    detect_platform
+    check_prerequisites
+    get_version "$1"
+    build
+    create_tarball
+    create_checksum
+    check_existing_release
+    create_release
+    cleanup
+    print_summary
+}
+
+main "$@"

+ 9 - 1
apps/cli/src/utils.ts

@@ -38,6 +38,14 @@ export function getApiKeyFromEnv(provider: string): string | undefined {
  * @param dirname - The __dirname equivalent for the calling module
  */
 export function getDefaultExtensionPath(dirname: string): string {
+	// Check for environment variable first (set by install script)
+	if (process.env.ROO_EXTENSION_PATH) {
+		const envPath = process.env.ROO_EXTENSION_PATH
+		if (fs.existsSync(path.join(envPath, "extension.js"))) {
+			return envPath
+		}
+	}
+
 	// __dirname is apps/cli/dist when bundled
 	// The extension is at src/dist (relative to monorepo root)
 	// So from apps/cli/dist, we need to go ../../../src/dist
@@ -48,7 +56,7 @@ export function getDefaultExtensionPath(dirname: string): string {
 		return monorepoPath
 	}
 
-	// Fallback: when installed as npm package, extension might be at ../extension
+	// Fallback: when installed via curl script, extension is at ../extension
 	const packagePath = path.resolve(dirname, "../extension")
 	return packagePath
 }

+ 4 - 2
apps/cli/tsup.config.ts

@@ -11,12 +11,14 @@ export default defineConfig({
 	banner: {
 		js: "#!/usr/bin/env node",
 	},
-	// Bundle these workspace packages that export TypeScript.
+	// Bundle workspace packages that export TypeScript
 	noExternal: ["@roo-code/types", "@roo-code/vscode-shim"],
 	external: [
-		// Keep native modules external.
+		// Keep native modules external
 		"@anthropic-ai/sdk",
 		"@anthropic-ai/bedrock-sdk",
 		"@anthropic-ai/vertex-sdk",
+		// Keep @vscode/ripgrep external - we bundle the binary separately
+		"@vscode/ripgrep",
 	],
 })