diff --git a/.changeset/config.json b/.changeset/config.json
index e2acc37662..310bc51094 100644
--- a/.changeset/config.json
+++ b/.changeset/config.json
@@ -7,5 +7,5 @@
"access": "restricted",
"baseBranch": "main",
"updateInternalDependencies": "patch",
- "ignore": []
+ "ignore": ["@roo-code/cli"]
}
diff --git a/.dockerignore b/.dockerignore
index 6359978833..4579d61580 100644
--- a/.dockerignore
+++ b/.dockerignore
@@ -76,14 +76,18 @@ src/node_modules
!pnpm-workspace.yaml
!scripts/bootstrap.mjs
!apps/web-evals/
+!apps/cli/
!src/
!webview-ui/
!packages/evals/.docker/entrypoints/runner.sh
!packages/build/
!packages/config-eslint/
!packages/config-typescript/
+!packages/core/
!packages/evals/
!packages/ipc/
!packages/telemetry/
!packages/types/
+!packages/vscode-shim/
+!packages/cloud/
!locales/
diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml
index 16e0c17e7a..443842c856 100644
--- a/.github/ISSUE_TEMPLATE/bug_report.yml
+++ b/.github/ISSUE_TEMPLATE/bug_report.yml
@@ -84,7 +84,6 @@ body:
- Google Gemini
- Google Vertex AI
- Groq
- - Human Relay Provider
- LiteLLM
- LM Studio
- Mistral AI
diff --git a/.roo/commands/release.md b/.roo/commands/release.md
index 707844cdc4..2e09783a58 100644
--- a/.roo/commands/release.md
+++ b/.roo/commands/release.md
@@ -1,6 +1,7 @@
---
description: "Create a new release of the Roo Code extension"
argument-hint: patch | minor | major
+mode: code
---
1. Identify the SHA corresponding to the most recent release using GitHub CLI: `gh release view --json tagName,targetCommitish,publishedAt`
diff --git a/.roo/rules-translate/instructions-zh-cn.md b/.roo/rules-translate/instructions-zh-cn.md
index 6141038728..b166a1e6a8 100644
--- a/.roo/rules-translate/instructions-zh-cn.md
+++ b/.roo/rules-translate/instructions-zh-cn.md
@@ -16,7 +16,6 @@
| Auto-approve | 自动批准 | 始终批准 | 权限相关术语 |
| Checkpoint | 存档点 | 检查点/快照 | 技术概念统一 |
| MCP Server | MCP 服务 | MCP 服务器 | 技术组件 |
-| Human Relay | 人工辅助模式 | 人工中继 | 功能描述清晰 |
| Network Timeout | 请求超时 | 网络超时 | 更准确描述 |
| Terminal | 终端 | 命令行 | 技术术语统一 |
| diff | 差异更新 | 差分/补丁 | 代码变更 |
diff --git a/.roo/skills/evals-context/SKILL.md b/.roo/skills/evals-context/SKILL.md
new file mode 100644
index 0000000000..985b788b94
--- /dev/null
+++ b/.roo/skills/evals-context/SKILL.md
@@ -0,0 +1,188 @@
+---
+name: evals-context
+description: Provides context about the Roo Code evals system structure in this monorepo. Use when tasks mention "evals", "evaluation", "eval runs", "eval exercises", or working with the evals infrastructure. Helps distinguish between the evals execution system (packages/evals, apps/web-evals) and the public website evals display page (apps/web-roo-code/src/app/evals).
+---
+
+# Evals Codebase Context
+
+## When to Use This Skill
+
+Use this skill when the task involves:
+
+- Modifying or debugging the evals execution infrastructure
+- Adding new eval exercises or languages
+- Working with the evals web interface (apps/web-evals)
+- Modifying the public evals display page on roocode.com
+- Understanding where evals code lives in this monorepo
+
+## When NOT to Use This Skill
+
+Do NOT use this skill when:
+
+- Working on unrelated parts of the codebase (extension, webview-ui, etc.)
+- The task is purely about the VS Code extension's core functionality
+- Working on the main website pages that don't involve evals
+
+## Key Disambiguation: Two "Evals" Locations
+
+This monorepo has **two distinct evals-related locations** that can cause confusion:
+
+| Component | Path | Purpose |
+| --------------------------- | -------------------------------------------------------------- | -------------------------------------------------------------- |
+| **Evals Execution System** | `packages/evals/` | Core eval infrastructure: CLI, DB schema, Docker configs |
+| **Evals Management UI** | `apps/web-evals/` | Next.js app for creating/monitoring eval runs (localhost:3446) |
+| **Website Evals Page** | `apps/web-roo-code/src/app/evals/` | Public roocode.com page displaying eval results |
+| **External Exercises Repo** | [Roo-Code-Evals](https://github.com/RooCodeInc/Roo-Code-Evals) | Actual coding exercises (NOT in this monorepo) |
+
+## Directory Structure Reference
+
+### `packages/evals/` - Core Evals Package
+
+```
+packages/evals/
+├── ARCHITECTURE.md # Detailed architecture documentation
+├── ADDING-EVALS.md # Guide for adding new exercises/languages
+├── README.md # Setup and running instructions
+├── docker-compose.yml # Container orchestration
+├── Dockerfile.runner # Runner container definition
+├── Dockerfile.web # Web app container
+├── drizzle.config.ts # Database ORM config
+├── src/
+│ ├── index.ts # Package exports
+│ ├── cli/ # CLI commands for running evals
+│ │ ├── runEvals.ts # Orchestrates complete eval runs
+│ │ ├── runTask.ts # Executes individual tasks in containers
+│ │ ├── runUnitTest.ts # Validates task completion via tests
+│ │ └── redis.ts # Redis pub/sub integration
+│ ├── db/
+│ │ ├── schema.ts # Database schema (runs, tasks)
+│ │ ├── queries/ # Database query functions
+│ │ └── migrations/ # SQL migrations
+│ └── exercises/
+│ └── index.ts # Exercise loading utilities
+└── scripts/
+ └── setup.sh # Local macOS setup script
+```
+
+### `apps/web-evals/` - Evals Management Web App
+
+```
+apps/web-evals/
+├── src/
+│ ├── app/
+│ │ ├── page.tsx # Home page (runs list)
+│ │ ├── runs/
+│ │ │ ├── new/ # Create new eval run
+│ │ │ └── [id]/ # View specific run status
+│ │ └── api/runs/ # SSE streaming endpoint
+│ ├── actions/ # Server actions
+│ │ ├── runs.ts # Run CRUD operations
+│ │ ├── tasks.ts # Task queries
+│ │ ├── exercises.ts # Exercise listing
+│ │ └── heartbeat.ts # Controller health checks
+│ ├── hooks/ # React hooks (SSE, models, etc.)
+│ └── lib/ # Utilities and schemas
+```
+
+### `apps/web-roo-code/src/app/evals/` - Public Website Evals Page
+
+```
+apps/web-roo-code/src/app/evals/
+├── page.tsx # Fetches and displays public eval results
+├── evals.tsx # Main evals display component
+├── plot.tsx # Visualization component
+└── types.ts # EvalRun type (extends packages/evals types)
+```
+
+This page **displays** eval results on the public roocode.com website. It imports types from `@roo-code/evals` but does NOT run evals.
+
+## Architecture Overview
+
+The evals system is a distributed evaluation platform that runs AI coding tasks in isolated VS Code environments:
+
+```
+┌─────────────────────────────────────────────────────────────┐
+│ Web App (apps/web-evals) ──────────────────────────────── │
+│ │ │
+│ ▼ │
+│ PostgreSQL ◄────► Controller Container │
+│ │ │ │
+│ ▼ ▼ │
+│ Redis ◄───► Runner Containers (1-25 parallel) │
+└─────────────────────────────────────────────────────────────┘
+```
+
+**Key components:**
+
+- **Controller**: Orchestrates eval runs, spawns runners, manages task queue (p-queue)
+- **Runner**: Isolated Docker container with VS Code + Roo Code extension + language runtimes
+- **Redis**: Pub/sub for real-time events (NOT task queuing)
+- **PostgreSQL**: Stores runs, tasks, metrics
+
+## Common Tasks Quick Reference
+
+### Adding a New Eval Exercise
+
+1. Add exercise to [Roo-Code-Evals](https://github.com/RooCodeInc/Roo-Code-Evals) repo (external)
+2. See [`packages/evals/ADDING-EVALS.md`](packages/evals/ADDING-EVALS.md) for structure
+
+### Modifying Eval CLI Behavior
+
+Edit files in [`packages/evals/src/cli/`](packages/evals/src/cli/):
+
+- [`runEvals.ts`](packages/evals/src/cli/runEvals.ts) - Run orchestration
+- [`runTask.ts`](packages/evals/src/cli/runTask.ts) - Task execution
+- [`runUnitTest.ts`](packages/evals/src/cli/runUnitTest.ts) - Test validation
+
+### Modifying the Evals Web Interface
+
+Edit files in [`apps/web-evals/src/`](apps/web-evals/src/):
+
+- [`app/runs/new/new-run.tsx`](apps/web-evals/src/app/runs/new/new-run.tsx) - New run form
+- [`actions/runs.ts`](apps/web-evals/src/actions/runs.ts) - Run server actions
+
+### Modifying the Public Evals Display Page
+
+Edit files in [`apps/web-roo-code/src/app/evals/`](apps/web-roo-code/src/app/evals/):
+
+- [`evals.tsx`](apps/web-roo-code/src/app/evals/evals.tsx) - Display component
+- [`plot.tsx`](apps/web-roo-code/src/app/evals/plot.tsx) - Charts
+
+### Database Schema Changes
+
+1. Edit [`packages/evals/src/db/schema.ts`](packages/evals/src/db/schema.ts)
+2. Generate migration: `cd packages/evals && pnpm drizzle-kit generate`
+3. Apply migration: `pnpm drizzle-kit migrate`
+
+## Running Evals Locally
+
+```bash
+# From repo root
+pnpm evals
+
+# Opens web UI at http://localhost:3446
+```
+
+**Ports (defaults):**
+
+- PostgreSQL: 5433
+- Redis: 6380
+- Web: 3446
+
+## Testing
+
+```bash
+# packages/evals tests
+cd packages/evals && npx vitest run
+
+# apps/web-evals tests
+cd apps/web-evals && npx vitest run
+```
+
+## Key Types/Exports from `@roo-code/evals`
+
+The package exports are defined in [`packages/evals/src/index.ts`](packages/evals/src/index.ts):
+
+- Database queries: `getRuns`, `getTasks`, `getTaskMetrics`, etc.
+- Schema types: `Run`, `Task`, `TaskMetrics`
+- Used by both `apps/web-evals` and `apps/web-roo-code`
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 10d7557569..5abc65c291 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,88 @@
# Roo Code Changelog
+## [3.39.0] - 2026-01-08
+
+
+
+- Implement sticky provider profile for task-level API config persistence (#8010 by @hannesrudolph, PR #10018 by @hannesrudolph)
+- Add support for image file @mentions (PR #10189 by @hannesrudolph)
+- Rename YOLO to BRRR (#8574 by @mojomast, PR #10507 by @roomote)
+- Add debug-mode proxy routing for debugging API calls (#7042 by @SleeperSmith, PR #10467 by @hannesrudolph)
+- Add Kimi K2 thinking model to Fireworks AI provider (#9201 by @kavehsfv, PR #9202 by @roomote)
+- Add xhigh reasoning effort to OpenAI compatible endpoints (#10060 by @Soorma718, PR #10061 by @roomote)
+- Filter @ mention file search results using .rooignore (#10169 by @jerrill-johnson-bitwerx, PR #10174 by @roomote)
+- Add image support documentation to read_file native tool description (#10440 by @nabilfreeman, PR #10442 by @roomote)
+- Add zai-glm-4.7 to Cerebras models (PR #10500 by @sebastiand-cerebras)
+- VSCode shim and basic CLI for running Roo Code headlessly (PR #10452 by @cte)
+- Add CLI installer for headless Roo Code (PR #10474 by @cte)
+- Add option to use CLI for evals (PR #10456 by @cte)
+- Remember last Roo model selection in web-evals and add evals skill (PR #10470 by @hannesrudolph)
+- Tweak the style of follow up suggestion modes (PR #9260 by @mrubens)
+- Fix: Handle PowerShell ENOENT error in os-name on Windows (#9859 by @Yang-strive, PR #9897 by @roomote)
+- Fix: Make command chaining examples shell-aware for Windows compatibility (#10352 by @AlexNek, PR #10434 by @roomote)
+- Fix: Preserve tool_use blocks for all tool_results in kept messages during condensation (PR #10471 by @daniel-lxs)
+- Fix: Add additionalProperties: false to MCP tool schemas for OpenAI Responses API (PR #10472 by @daniel-lxs)
+- Fix: Prevent duplicate tool_result blocks causing API errors (PR #10497 by @daniel-lxs)
+- Fix: Add explicit deduplication for duplicate tool_result blocks (#10465 by @nabilfreeman, PR #10466 by @roomote)
+- Fix: Use task stored API config as fallback for rate limit (PR #10266 by @roomote)
+- Fix: Remove legacy Claude 2 series models from Bedrock provider (#9220 by @KevinZhao, PR #10501 by @roomote)
+- Fix: Add missing description fields for debugProxy configuration (PR #10505 by @roomote)
+- Fix: Glitchy kangaroo bounce animation on welcome screen (PR #10035 by @objectiveSee)
+
+## [3.38.3] - 2026-01-03
+
+- Feat: Add option in Context settings to recursively load `.roo/rules` and `AGENTS.md` from subdirectories (PR #10446 by @mrubens)
+- Fix: Stop frequent Claude Code sign-ins by hardening OAuth refresh token handling (PR #10410 by @hannesrudolph)
+- Fix: Add `maxConcurrentFileReads` limit to native `read_file` tool schema (PR #10449 by @app/roomote)
+- Fix: Add type check for `lastMessage.text` in TTS useEffect to prevent runtime errors (PR #10431 by @app/roomote)
+
+## [3.38.2] - 2025-12-31
+
+
+
+- Align skills system with Agent Skills specification (PR #10409 by @hannesrudolph)
+- Prevent write_to_file from creating files at truncated paths (PR #10415 by @mrubens and @daniel-lxs)
+- Update Cerebras maxTokens to 16384 (PR #10387 by @sebastiand-cerebras)
+- Fix rate limit wait display (PR #10389 by @hannesrudolph)
+- Remove human-relay provider (PR #10388 by @hannesrudolph)
+- Replace Todo Lists video with Context Management video in documentation (PR #10375 by @SannidhyaSah)
+
+## [3.38.1] - 2025-12-29
+
+
+
+- Fix: Flush pending tool results before condensing context (PR #10379 by @daniel-lxs)
+- Fix: Revert mergeToolResultText for OpenAI-compatible providers (PR #10381 by @hannesrudolph)
+- Fix: Enforce maxConcurrentFileReads limit in read_file tool (PR #10363 by @roomote)
+- Fix: Improve feedback message when read_file is used on a directory (PR #10371 by @roomote)
+- Fix: Handle custom tool use similarly to MCP tools for IPC schema purposes (PR #10364 by @jr)
+- Fix: Correct GitHub repository URL in marketing page (#10376 by @jishnuteegala, PR #10377 by @roomote)
+- Docs: Clarify path to Security Settings in privacy policy (PR #10367 by @roomote)
+
+## [3.38.0] - 2025-12-27
+
+
+
+- Add support for [Agent Skills](https://agentskills.io/), enabling reusable packages of prompts, tools, and resources to extend Roo's capabilities (PR #10335 by @mrubens)
+- Add optional mode field to slash command front matter, allowing commands to automatically switch to a specific mode when triggered (PR #10344 by @app/roomote)
+- Add support for npm packages and .env files to custom tools, allowing custom tools to import dependencies and access environment variables (PR #10336 by @cte)
+- Remove simpleReadFileTool feature, streamlining the file reading experience (PR #10254 by @app/roomote)
+- Remove OpenRouter Transforms feature (PR #10341 by @app/roomote)
+- Fix mergeToolResultText handling in Roo provider (PR #10359 by @mrubens)
+
+## [3.37.1] - 2025-12-23
+
+
+
+- Fix: Send native tool definitions by default for OpenAI to ensure proper tool usage (PR #10314 by @hannesrudolph)
+- Fix: Preserve reasoning_details shape to prevent malformed responses when processing model output (PR #10313 by @hannesrudolph)
+- Fix: Drain queued messages while waiting for ask to prevent message loss (PR #10315 by @hannesrudolph)
+- Feat: Add grace retry for empty assistant messages to improve reliability (PR #10297 by @hannesrudolph)
+- Feat: Enable mergeToolResultText for all OpenAI-compatible providers for better tool result handling (PR #10299 by @hannesrudolph)
+- Feat: Enable mergeToolResultText for Roo Code Cloud provider (PR #10301 by @hannesrudolph)
+- Feat: Strengthen native tool-use guidance in prompts for improved model behavior (PR #10311 by @hannesrudolph)
+- UX: Account-centric signup flow for improved onboarding experience (PR #10306 by @brunobergher)
+
## [3.37.0] - 2025-12-22

diff --git a/README.md b/README.md
index 9e98cd8845..5f6469237a 100644
--- a/README.md
+++ b/README.md
@@ -35,7 +35,7 @@
- [简体中文](locales/zh-CN/README.md)
- [繁體中文](locales/zh-TW/README.md)
- ...
-
+
---
@@ -66,10 +66,10 @@ Learn more: [Using Modes](https://docs.roocode.com/basic-usage/using-modes) •
-| | | |
-| :-----------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------: |
-|
Installing Roo Code |
Configuring Profiles |
Codebase Indexing |
-|
Custom Modes |
Checkpoints |
Todo Lists |
+| | | |
+| :-----------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------: |
+|
Installing Roo Code |
Configuring Profiles |
Codebase Indexing |
+|
Custom Modes |
Checkpoints |
Context Management |
diff --git a/apps/cli/README.md b/apps/cli/README.md
new file mode 100644
index 0000000000..3e78192d4e
--- /dev/null
+++ b/apps/cli/README.md
@@ -0,0 +1,231 @@
+# @roo-code/cli
+
+Command Line Interface for Roo Code - Run the Roo Code agent from the terminal without VSCode.
+
+## Overview
+
+This CLI uses the `@roo-code/vscode-shim` package to provide a VSCode API compatibility layer, allowing the main Roo Code extension to run in a Node.js environment.
+
+## 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
+
+# Build the main extension first.
+pnpm --filter roo-cline bundle
+
+# Build the cli.
+pnpm --filter @roo-code/cli build
+```
+
+## Usage
+
+### Interactive Mode (Default)
+
+By default, the CLI prompts for approval before executing actions:
+
+```bash
+export OPENROUTER_API_KEY=sk-or-v1-...
+
+roo "What is this project?" --workspace ~/Documents/my-project
+```
+
+In interactive mode:
+
+- Tool executions prompt for yes/no approval
+- Commands prompt for yes/no approval
+- Followup questions show suggestions and wait for user input
+- Browser and MCP actions prompt for approval
+
+### Non-Interactive Mode (`-y`)
+
+For automation and scripts, use `-y` to auto-approve all actions:
+
+```bash
+roo -y "Refactor the utils.ts file" --workspace ~/Documents/my-project
+```
+
+In non-interactive mode:
+
+- Tool, command, browser, and MCP actions are auto-approved
+- Followup questions show a 10-second timeout, then auto-select the first suggestion
+- Typing any key cancels the timeout and allows manual input
+
+## Options
+
+| Option | Description | Default |
+| --------------------------------- | ------------------------------------------------------------------------------ | ----------------- |
+| `-w, --workspace ` | Workspace path to operate in | Current directory |
+| `-e, --extension ` | Path to the extension bundle directory | Auto-detected |
+| `-v, --verbose` | Enable verbose output (show VSCode and extension logs) | `false` |
+| `-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 ` | API key for the LLM provider | From env var |
+| `-p, --provider ` | API provider (anthropic, openai, openrouter, etc.) | `openrouter` |
+| `-m, --model ` | Model to use | Provider default |
+| `-M, --mode ` | Mode to start in (code, architect, ask, debug, etc.) | `code` |
+| `-r, --reasoning-effort ` | Reasoning effort level (none, minimal, low, medium, high, xhigh) | `medium` |
+
+By default, the CLI runs in quiet mode (suppressing VSCode/extension logs) and only shows assistant output. Use `-v` to see all logs, or `-d` for detailed debug information.
+
+## Environment Variables
+
+The CLI will look for API keys in environment variables if not provided via `--api-key`:
+
+| Provider | Environment Variable |
+| ------------- | -------------------- |
+| anthropic | `ANTHROPIC_API_KEY` |
+| openai | `OPENAI_API_KEY` |
+| openrouter | `OPENROUTER_API_KEY` |
+| google/gemini | `GOOGLE_API_KEY` |
+| mistral | `MISTRAL_API_KEY` |
+| deepseek | `DEEPSEEK_API_KEY` |
+| bedrock | `AWS_ACCESS_KEY_ID` |
+
+## Architecture
+
+```
+┌─────────────────┐
+│ CLI Entry │
+│ (index.ts) │
+└────────┬────────┘
+ │
+ ▼
+┌─────────────────┐
+│ ExtensionHost │
+│ (extension- │
+│ host.ts) │
+└────────┬────────┘
+ │
+ ┌────┴────┐
+ │ │
+ ▼ ▼
+┌───────┐ ┌──────────┐
+│vscode │ │Extension │
+│-shim │ │ Bundle │
+└───────┘ └──────────┘
+```
+
+## How It Works
+
+1. **CLI Entry Point** (`index.ts`): Parses command line arguments and initializes the ExtensionHost
+
+2. **ExtensionHost** (`extension-host.ts`):
+
+ - Creates a VSCode API mock using `@roo-code/vscode-shim`
+ - Intercepts `require('vscode')` to return the mock
+ - Loads and activates the extension bundle
+ - Manages bidirectional message flow
+
+3. **Message Flow**:
+ - CLI → Extension: `emit("webviewMessage", {...})`
+ - Extension → CLI: `emit("extensionWebviewMessage", {...})`
+
+## Current Limitations
+
+- **No TUI**: Output is plain text (no React/Ink UI yet)
+- **No configuration file**: Settings are passed via command line flags
+- **No persistence**: Each run is a fresh session
+
+## Development
+
+```bash
+# Watch mode for development
+pnpm dev
+
+# Run tests
+pnpm test
+
+# Type checking
+pnpm check-types
+
+# Linting
+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
+
+Make sure you've built the main extension first:
+
+```bash
+cd src
+pnpm bundle
+```
+
+### Module resolution errors
+
+The CLI expects the extension to be a CommonJS bundle. Make sure the extension's esbuild config outputs CommonJS.
+
+### "vscode" module not found
+
+The CLI intercepts `require('vscode')` calls. If you see this error, the module resolution interception may have failed.
diff --git a/apps/cli/eslint.config.mjs b/apps/cli/eslint.config.mjs
new file mode 100644
index 0000000000..694bf73664
--- /dev/null
+++ b/apps/cli/eslint.config.mjs
@@ -0,0 +1,4 @@
+import { config } from "@roo-code/config-eslint/base"
+
+/** @type {import("eslint").Linter.Config} */
+export default [...config]
diff --git a/apps/cli/install.sh b/apps/cli/install.sh
new file mode 100755
index 0000000000..ca82ecfdd8
--- /dev/null
+++ b/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 "$@"
diff --git a/apps/cli/package.json b/apps/cli/package.json
new file mode 100644
index 0000000000..f4c4a3bcb5
--- /dev/null
+++ b/apps/cli/package.json
@@ -0,0 +1,35 @@
+{
+ "name": "@roo-code/cli",
+ "version": "0.1.0",
+ "description": "Roo Code CLI - Run the Roo Code agent from the command line",
+ "private": true,
+ "type": "module",
+ "main": "dist/index.js",
+ "bin": {
+ "roo": "dist/index.js"
+ },
+ "scripts": {
+ "format": "prettier --write 'src/**/*.ts'",
+ "lint": "eslint src --ext .ts --max-warnings=0",
+ "check-types": "tsc --noEmit",
+ "test": "vitest run",
+ "build": "tsup",
+ "start": "node dist/index.js",
+ "clean": "rimraf dist .turbo"
+ },
+ "dependencies": {
+ "@roo-code/types": "workspace:^",
+ "@roo-code/vscode-shim": "workspace:^",
+ "@vscode/ripgrep": "^1.15.9",
+ "commander": "^12.1.0"
+ },
+ "devDependencies": {
+ "@roo-code/config-eslint": "workspace:^",
+ "@roo-code/config-typescript": "workspace:^",
+ "@types/node": "^24.1.0",
+ "rimraf": "^6.0.1",
+ "tsup": "^8.4.0",
+ "typescript": "5.8.3",
+ "vitest": "^3.2.3"
+ }
+}
diff --git a/apps/cli/scripts/release.sh b/apps/cli/scripts/release.sh
new file mode 100755
index 0000000000..43d5298956
--- /dev/null
+++ b/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 "$@"
diff --git a/apps/cli/src/__tests__/extension-host.test.ts b/apps/cli/src/__tests__/extension-host.test.ts
new file mode 100644
index 0000000000..509ad27d1e
--- /dev/null
+++ b/apps/cli/src/__tests__/extension-host.test.ts
@@ -0,0 +1,1164 @@
+// pnpm --filter @roo-code/cli test src/__tests__/extension-host.test.ts
+
+import { ExtensionHost, type ExtensionHostOptions } from "../extension-host.js"
+import { EventEmitter } from "events"
+import type { ProviderName } from "@roo-code/types"
+
+vi.mock("@roo-code/vscode-shim", () => ({
+ createVSCodeAPI: vi.fn(() => ({
+ context: { extensionPath: "/test/extension" },
+ })),
+}))
+
+/**
+ * Create a test ExtensionHost with default options
+ */
+function createTestHost({
+ mode = "code",
+ apiProvider = "openrouter",
+ model = "test-model",
+ ...options
+}: Partial = {}): ExtensionHost {
+ return new ExtensionHost({
+ mode,
+ apiProvider,
+ model,
+ workspacePath: "/test/workspace",
+ extensionPath: "/test/extension",
+ ...options,
+ })
+}
+
+// Type for accessing private members
+type PrivateHost = Record
+
+/**
+ * Helper to access private members for testing
+ */
+function getPrivate(host: ExtensionHost, key: string): T {
+ return (host as unknown as PrivateHost)[key] as T
+}
+
+/**
+ * Helper to call private methods for testing
+ */
+function callPrivate(host: ExtensionHost, method: string, ...args: unknown[]): T {
+ const fn = (host as unknown as PrivateHost)[method] as ((...a: unknown[]) => T) | undefined
+ if (!fn) throw new Error(`Method ${method} not found`)
+ return fn.apply(host, args)
+}
+
+/**
+ * Helper to spy on private methods
+ * This uses a more permissive type to avoid TypeScript errors with vi.spyOn on private methods
+ */
+function spyOnPrivate(host: ExtensionHost, method: string) {
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ return vi.spyOn(host as any, method)
+}
+
+describe("ExtensionHost", () => {
+ beforeEach(() => {
+ vi.resetAllMocks()
+ // Clean up globals
+ delete (global as Record).vscode
+ delete (global as Record).__extensionHost
+ })
+
+ describe("constructor", () => {
+ it("should store options correctly", () => {
+ const options: ExtensionHostOptions = {
+ mode: "code",
+ workspacePath: "/my/workspace",
+ extensionPath: "/my/extension",
+ verbose: true,
+ quiet: true,
+ apiKey: "test-key",
+ apiProvider: "openrouter",
+ model: "test-model",
+ }
+
+ const host = new ExtensionHost(options)
+
+ expect(getPrivate(host, "options")).toEqual(options)
+ })
+
+ it("should be an EventEmitter instance", () => {
+ const host = createTestHost()
+ expect(host).toBeInstanceOf(EventEmitter)
+ })
+
+ it("should initialize with default state values", () => {
+ const host = createTestHost()
+
+ expect(getPrivate(host, "isWebviewReady")).toBe(false)
+ expect(getPrivate(host, "pendingMessages")).toEqual([])
+ expect(getPrivate(host, "vscode")).toBeNull()
+ expect(getPrivate(host, "extensionModule")).toBeNull()
+ })
+ })
+
+ describe("buildApiConfiguration", () => {
+ it.each([
+ [
+ "anthropic",
+ "test-key",
+ "test-model",
+ { apiProvider: "anthropic", apiKey: "test-key", apiModelId: "test-model" },
+ ],
+ [
+ "openrouter",
+ "or-key",
+ "or-model",
+ {
+ apiProvider: "openrouter",
+ openRouterApiKey: "or-key",
+ openRouterModelId: "or-model",
+ },
+ ],
+ [
+ "gemini",
+ "gem-key",
+ "gem-model",
+ { apiProvider: "gemini", geminiApiKey: "gem-key", apiModelId: "gem-model" },
+ ],
+ [
+ "openai-native",
+ "oai-key",
+ "oai-model",
+ { apiProvider: "openai-native", openAiNativeApiKey: "oai-key", apiModelId: "oai-model" },
+ ],
+ [
+ "openai",
+ "oai-key",
+ "oai-model",
+ { apiProvider: "openai", openAiApiKey: "oai-key", openAiModelId: "oai-model" },
+ ],
+ [
+ "mistral",
+ "mis-key",
+ "mis-model",
+ { apiProvider: "mistral", mistralApiKey: "mis-key", apiModelId: "mis-model" },
+ ],
+ [
+ "deepseek",
+ "ds-key",
+ "ds-model",
+ { apiProvider: "deepseek", deepSeekApiKey: "ds-key", apiModelId: "ds-model" },
+ ],
+ ["xai", "xai-key", "xai-model", { apiProvider: "xai", xaiApiKey: "xai-key", apiModelId: "xai-model" }],
+ [
+ "groq",
+ "groq-key",
+ "groq-model",
+ { apiProvider: "groq", groqApiKey: "groq-key", apiModelId: "groq-model" },
+ ],
+ [
+ "fireworks",
+ "fw-key",
+ "fw-model",
+ { apiProvider: "fireworks", fireworksApiKey: "fw-key", apiModelId: "fw-model" },
+ ],
+ [
+ "cerebras",
+ "cer-key",
+ "cer-model",
+ { apiProvider: "cerebras", cerebrasApiKey: "cer-key", apiModelId: "cer-model" },
+ ],
+ [
+ "sambanova",
+ "sn-key",
+ "sn-model",
+ { apiProvider: "sambanova", sambaNovaApiKey: "sn-key", apiModelId: "sn-model" },
+ ],
+ [
+ "ollama",
+ "oll-key",
+ "oll-model",
+ { apiProvider: "ollama", ollamaApiKey: "oll-key", ollamaModelId: "oll-model" },
+ ],
+ ["lmstudio", undefined, "lm-model", { apiProvider: "lmstudio", lmStudioModelId: "lm-model" }],
+ [
+ "litellm",
+ "lite-key",
+ "lite-model",
+ { apiProvider: "litellm", litellmApiKey: "lite-key", litellmModelId: "lite-model" },
+ ],
+ [
+ "huggingface",
+ "hf-key",
+ "hf-model",
+ { apiProvider: "huggingface", huggingFaceApiKey: "hf-key", huggingFaceModelId: "hf-model" },
+ ],
+ ["chutes", "ch-key", "ch-model", { apiProvider: "chutes", chutesApiKey: "ch-key", apiModelId: "ch-model" }],
+ [
+ "featherless",
+ "fl-key",
+ "fl-model",
+ { apiProvider: "featherless", featherlessApiKey: "fl-key", apiModelId: "fl-model" },
+ ],
+ [
+ "unbound",
+ "ub-key",
+ "ub-model",
+ { apiProvider: "unbound", unboundApiKey: "ub-key", unboundModelId: "ub-model" },
+ ],
+ [
+ "requesty",
+ "req-key",
+ "req-model",
+ { apiProvider: "requesty", requestyApiKey: "req-key", requestyModelId: "req-model" },
+ ],
+ [
+ "deepinfra",
+ "di-key",
+ "di-model",
+ { apiProvider: "deepinfra", deepInfraApiKey: "di-key", deepInfraModelId: "di-model" },
+ ],
+ [
+ "vercel-ai-gateway",
+ "vai-key",
+ "vai-model",
+ {
+ apiProvider: "vercel-ai-gateway",
+ vercelAiGatewayApiKey: "vai-key",
+ vercelAiGatewayModelId: "vai-model",
+ },
+ ],
+ ["zai", "zai-key", "zai-model", { apiProvider: "zai", zaiApiKey: "zai-key", apiModelId: "zai-model" }],
+ [
+ "baseten",
+ "bt-key",
+ "bt-model",
+ { apiProvider: "baseten", basetenApiKey: "bt-key", apiModelId: "bt-model" },
+ ],
+ ["doubao", "db-key", "db-model", { apiProvider: "doubao", doubaoApiKey: "db-key", apiModelId: "db-model" }],
+ [
+ "moonshot",
+ "ms-key",
+ "ms-model",
+ { apiProvider: "moonshot", moonshotApiKey: "ms-key", apiModelId: "ms-model" },
+ ],
+ [
+ "minimax",
+ "mm-key",
+ "mm-model",
+ { apiProvider: "minimax", minimaxApiKey: "mm-key", apiModelId: "mm-model" },
+ ],
+ [
+ "io-intelligence",
+ "io-key",
+ "io-model",
+ { apiProvider: "io-intelligence", ioIntelligenceApiKey: "io-key", ioIntelligenceModelId: "io-model" },
+ ],
+ ])("should configure %s provider correctly", (provider, apiKey, model, expected) => {
+ const host = createTestHost({
+ apiProvider: provider as ProviderName,
+ apiKey,
+ model,
+ })
+
+ const config = callPrivate>(host, "buildApiConfiguration")
+
+ expect(config).toEqual(expected)
+ })
+
+ it("should use default provider when not specified", () => {
+ const host = createTestHost({
+ apiKey: "test-key",
+ model: "test-model",
+ })
+
+ const config = callPrivate>(host, "buildApiConfiguration")
+
+ expect(config.apiProvider).toBe("openrouter")
+ })
+
+ it("should handle missing apiKey gracefully", () => {
+ const host = createTestHost({
+ apiProvider: "anthropic",
+ model: "test-model",
+ })
+
+ const config = callPrivate>(host, "buildApiConfiguration")
+
+ expect(config.apiProvider).toBe("anthropic")
+ expect(config.apiKey).toBeUndefined()
+ expect(config.apiModelId).toBe("test-model")
+ })
+
+ it("should use default config for unknown providers", () => {
+ const host = createTestHost({
+ apiProvider: "unknown-provider" as ProviderName,
+ apiKey: "test-key",
+ model: "test-model",
+ })
+
+ const config = callPrivate>(host, "buildApiConfiguration")
+
+ expect(config.apiProvider).toBe("unknown-provider")
+ expect(config.apiKey).toBe("test-key")
+ expect(config.apiModelId).toBe("test-model")
+ })
+ })
+
+ describe("webview provider registration", () => {
+ it("should register webview provider", () => {
+ const host = createTestHost()
+ const mockProvider = { resolveWebviewView: vi.fn() }
+
+ host.registerWebviewProvider("test-view", mockProvider)
+
+ const providers = getPrivate