Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
13 changes: 13 additions & 0 deletions .github/ISSUE_TEMPLATE/add-provider-model.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
---
name: Add provider model
about: Add a provider model that isn't htere
title: ''
labels: model
assignees: ''
type: Task

---

Name of model: (GPT 5.4)
Model creator: (i.e. Open AI)
Link to model:
34 changes: 34 additions & 0 deletions .github/ISSUE_TEMPLATE/bug_report.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
---
name: Bug report
about: Create a report to help us improve
title: "[BUG] "
labels: bug
assignees: ''
type: Bug

---

**Describe the bug**
A clear and concise description of what the bug is.

**To Reproduce**
Steps to reproduce the behavior:
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
4. See error

**Expected behavior**
A clear and concise description of what you expected to happen.

**Screenshots**
If applicable, add screenshots to help explain your problem.

**Video**
Add a video of the incident happening, it will help us debug

**What version of zoo are you running**
i.e. 3.55

**Additional context**
Add any other context about the problem here.
21 changes: 21 additions & 0 deletions .github/ISSUE_TEMPLATE/feature_request.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
---
name: Feature request
about: What do you want to add to zoo?
title: ''
labels: enhancement
assignees: ''
type: Feature

---

**Is your feature request related to a problem? Please describe.**
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]

**Describe the solution you'd like**
A clear and concise description of what you want to happen.

**Describe alternatives you've considered**
A clear and concise description of any alternative solutions or features you've considered.

**Additional context**
Add any other context or screenshots about the feature request here.
106 changes: 35 additions & 71 deletions apps/cli/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -130,101 +130,65 @@ printf '{"command":"start","requestId":"1","prompt":"1+1=?"}\n' | roo --print --
printf '{"command":"start","requestId":"1","taskId":"018f7fc8-7c96-7f7c-98aa-2ec4ff7f6d87","prompt":"1+1=?"}\n' | roo --print --stdin-prompt-stream --output-format stream-json
```

### Roo Code Cloud Authentication
### Legacy Roo Auth Token Cleanup

To use Roo Code Cloud features (like the provider proxy), you need to authenticate:
Normal CLI usage is login-free. Use `--provider` with your own API key, or set the provider environment variable directly.

```bash
# Log in to Roo Code Cloud (opens browser)
roo auth login
Roo Code Router has been removed from the CLI. The remaining `auth` commands only help inspect or delete any legacy Roo auth token still stored from older releases:

# Check authentication status
```bash
# Check whether a legacy Roo auth token is still stored
roo auth status

# Log out
# Remove an old stored Roo auth token
roo auth logout
```

The `auth login` command:

1. Opens your browser to authenticate with Roo Code Cloud
2. Receives a secure token via localhost callback
3. Stores the token in `~/.config/roo/credentials.json`

Tokens are valid for 90 days. The CLI will prompt you to re-authenticate when your token expires.

**Authentication Flow:**

```
┌──────┐ ┌─────────┐ ┌───────────────┐
│ CLI │ │ Browser │ │ Roo Code Cloud│
└──┬───┘ └────┬────┘ └───────┬───────┘
│ │ │
│ Open auth URL │ │
│─────────────────>│ │
│ │ │
│ │ Authenticate │
│ │─────────────────────>│
│ │ │
│ │<─────────────────────│
│ │ Token via callback │
│<─────────────────│ │
│ │ │
│ Store token │ │
│ │ │
```
If you never used Roo Code Router, you can ignore this section entirely.

## Options

| Option | Description | Default |
| --------------------------------------- | --------------------------------------------------------------------------------------- | ---------------------------------------- |
| `[prompt]` | Your prompt (positional argument, optional) | None |
| `--prompt-file <path>` | Read prompt from a file instead of command line argument | None |
| `--create-with-session-id <session-id>` | Create a new task using the provided session ID (UUID) | None |
| `-w, --workspace <path>` | Workspace path to operate in | Current directory |
| `-p, --print` | Print response and exit (non-interactive mode) | `false` |
| `--stdin-prompt-stream` | Read NDJSON control commands from stdin (requires `--print`) | `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` |
| `-a, --require-approval` | Require manual approval before actions execute | `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.6` |
| `--mode <mode>` | Mode to start in (code, architect, ask, debug, etc.) | `code` |
| `--terminal-shell <path>` | Absolute shell path for inline terminal command execution | Auto-detected shell |
| `-r, --reasoning-effort <effort>` | Reasoning effort level (unspecified, disabled, none, minimal, low, medium, high, xhigh) | `medium` |
| `--consecutive-mistake-limit <n>` | Consecutive error/repetition limit before guidance prompt (`0` disables the limit) | `10` |
| `--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` |
| Option | Description | Default |
| --------------------------------------- | --------------------------------------------------------------------------------------- | --------------------------- |
| `[prompt]` | Your prompt (positional argument, optional) | None |
| `--prompt-file <path>` | Read prompt from a file instead of command line argument | None |
| `--create-with-session-id <session-id>` | Create a new task using the provided session ID (UUID) | None |
| `-w, --workspace <path>` | Workspace path to operate in | Current directory |
| `-p, --print` | Print response and exit (non-interactive mode) | `false` |
| `--stdin-prompt-stream` | Read NDJSON control commands from stdin (requires `--print`) | `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` |
| `-a, --require-approval` | Require manual approval before actions execute | `false` |
| `-k, --api-key <key>` | API key for the LLM provider | From env var |
| `--provider <provider>` | API provider (anthropic, openai-native, gemini, openrouter, vercel-ai-gateway) | `openrouter` |
| `-m, --model <model>` | Model to use | `anthropic/claude-opus-4.6` |
| `--mode <mode>` | Mode to start in (code, architect, ask, debug, etc.) | `code` |
| `--terminal-shell <path>` | Absolute shell path for inline terminal command execution | Auto-detected shell |
| `-r, --reasoning-effort <effort>` | Reasoning effort level (unspecified, disabled, none, minimal, low, medium, high, xhigh) | `medium` |
| `--consecutive-mistake-limit <n>` | Consecutive error/repetition limit before guidance prompt (`0` disables the limit) | `10` |
| `--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

| Command | Description |
| ----------------- | ---------------------------------- |
| `roo auth login` | Authenticate with Roo Code Cloud |
| `roo auth logout` | Clear stored authentication token |
| `roo auth status` | Show current authentication status |
| Command | Description |
| ----------------- | ------------------------------------ |
| `roo auth logout` | Clear a stored legacy Roo auth token |
| `roo auth status` | Show legacy Roo token status |

## Environment Variables

The CLI will look for API keys in environment variables if not provided via `--api-key`:

| Provider | Environment Variable |
| ----------------- | --------------------------- |
| roo | `ROO_API_KEY` |
| anthropic | `ANTHROPIC_API_KEY` |
| openai-native | `OPENAI_API_KEY` |
| openrouter | `OPENROUTER_API_KEY` |
| gemini | `GOOGLE_API_KEY` |
| vercel-ai-gateway | `VERCEL_AI_GATEWAY_API_KEY` |

**Authentication Environment Variables:**

| Variable | Description |
| ----------------- | -------------------------------------------------------------------- |
| `ROO_WEB_APP_URL` | Override the Roo Code Cloud URL (default: `https://app.roocode.com`) |

## Architecture

```
Expand Down Expand Up @@ -268,7 +232,7 @@ The CLI will look for API keys in environment variables if not provided via `--a

```bash
# Run directly from source (no build required)
pnpm dev --provider roo --api-key $ROO_API_KEY --print "Hello"
pnpm dev --provider openrouter --api-key $OPENROUTER_API_KEY --print "Hello"

# Run tests
pnpm test
Expand All @@ -280,10 +244,10 @@ pnpm check-types
pnpm lint
```

By default the `start` script points `ROO_CODE_PROVIDER_URL` at `http://localhost:8080/proxy` for local development. To point at the production API instead, override the environment variable:
By default the dev script still points `ROO_CODE_PROVIDER_URL` at `http://localhost:8080/proxy` for local extension-host development. The CLI provider selection itself should use a non-Router provider such as OpenRouter. To point the backend URL at production instead, override the environment variable:

```bash
ROO_CODE_PROVIDER_URL=https://api.roocode.com/proxy pnpm dev --provider roo --api-key $ROO_API_KEY --print "Hello"
ROO_CODE_PROVIDER_URL=https://api.roocode.com/proxy pnpm dev --provider openrouter --api-key $OPENROUTER_API_KEY --print "Hello"
```

## Releasing
Expand Down
76 changes: 76 additions & 0 deletions apps/cli/src/commands/auth/__tests__/auth-commands.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
vi.mock("@/lib/storage/index.js", () => ({
loadToken: vi.fn(),
loadCredentials: vi.fn(),
getCredentialsPath: vi.fn(() => "/tmp/roo/cli-credentials.json"),
hasToken: vi.fn(),
clearToken: vi.fn(),
}))

vi.mock("@/lib/auth/index.js", () => ({
isTokenExpired: vi.fn(),
isTokenValid: vi.fn(),
getTokenExpirationDate: vi.fn(),
}))

import { status } from "../status.js"
import { logout } from "../logout.js"
import { loadToken, loadCredentials, getCredentialsPath, hasToken, clearToken } from "@/lib/storage/index.js"
import { isTokenExpired, isTokenValid, getTokenExpirationDate } from "@/lib/auth/index.js"

describe("auth commands", () => {
beforeEach(() => {
vi.clearAllMocks()
})

it("reports missing Roo auth tokens as normal for standard CLI usage", async () => {
vi.mocked(loadToken).mockResolvedValue(null)
const consoleLog = vi.spyOn(console, "log").mockImplementation(() => {})

const result = await status()

expect(result).toEqual({ authenticated: false })
expect(consoleLog.mock.calls.flat().join("\n")).toContain("Normal CLI usage does not require login.")
expect(consoleLog.mock.calls.flat().join("\n")).toContain("Roo Code Router has been removed")
})

it("reports optional Roo auth token details when available", async () => {
const token = "header.payload.signature"
const expiresAt = new Date("2026-05-01T00:00:00.000Z")

vi.mocked(loadToken).mockResolvedValue(token)
vi.mocked(loadCredentials).mockResolvedValue({ token, createdAt: "2026-04-01T00:00:00.000Z" })
vi.mocked(isTokenValid).mockReturnValue(true)
vi.mocked(isTokenExpired).mockReturnValue(false)
vi.mocked(getTokenExpirationDate).mockReturnValue(expiresAt)
vi.mocked(getCredentialsPath).mockReturnValue("/tmp/roo/cli-credentials.json")
const consoleLog = vi.spyOn(console, "log").mockImplementation(() => {})

const result = await status({ verbose: true })

expect(result.authenticated).toBe(true)
expect(consoleLog.mock.calls.flat().join("\n")).toContain("Legacy Roo auth token still stored")
expect(consoleLog.mock.calls.flat().join("\n")).toContain("/tmp/roo/cli-credentials.json")
})

it("removes stored Roo auth tokens", async () => {
vi.mocked(hasToken).mockResolvedValue(true)
const consoleLog = vi.spyOn(console, "log").mockImplementation(() => {})

const result = await logout()

expect(result).toEqual({ success: true, wasLoggedIn: true })
expect(clearToken).toHaveBeenCalledTimes(1)
expect(consoleLog.mock.calls.flat().join("\n")).toContain("Removed stored legacy Roo auth token")
})

it("treats missing Roo auth tokens as already logged out", async () => {
vi.mocked(hasToken).mockResolvedValue(false)
const consoleLog = vi.spyOn(console, "log").mockImplementation(() => {})

const result = await logout()

expect(result).toEqual({ success: true, wasLoggedIn: false })
expect(clearToken).not.toHaveBeenCalled()
expect(consoleLog.mock.calls.flat().join("\n")).toContain("No legacy Roo auth token stored.")
})
})
1 change: 0 additions & 1 deletion apps/cli/src/commands/auth/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,2 @@
export * from "./login.js"
export * from "./logout.js"
export * from "./status.js"
Loading