Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ jobs:
- { suffix: "-claude", dockerfile: "Dockerfile.claude", artifact: "claude" }
- { suffix: "-gemini", dockerfile: "Dockerfile.gemini", artifact: "gemini" }
- { suffix: "-copilot", dockerfile: "Dockerfile.copilot", artifact: "copilot" }
- { suffix: "-cursor", dockerfile: "Dockerfile.cursor", artifact: "cursor" }
platform:
- { os: linux/amd64, runner: ubuntu-latest }
- { os: linux/arm64, runner: ubuntu-24.04-arm }
Expand Down Expand Up @@ -131,6 +132,7 @@ jobs:
- { suffix: "-claude", artifact: "claude" }
- { suffix: "-gemini", artifact: "gemini" }
- { suffix: "-copilot", artifact: "copilot" }
- { suffix: "-cursor", artifact: "cursor" }
runs-on: ubuntu-latest
permissions:
contents: read
Expand Down Expand Up @@ -179,6 +181,7 @@ jobs:
- { suffix: "-claude" }
- { suffix: "-gemini" }
- { suffix: "-copilot" }
- { suffix: "-cursor" }
runs-on: ubuntu-latest
permissions:
contents: read
Expand Down
46 changes: 46 additions & 0 deletions Dockerfile.cursor
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
# --- Build stage ---
FROM rust:1-bookworm AS builder
WORKDIR /build
COPY Cargo.toml Cargo.lock ./
RUN mkdir src && echo 'fn main() {}' > src/main.rs && cargo build --release && rm -rf src
COPY src/ src/
RUN touch src/main.rs && cargo build --release

# --- Runtime stage ---
FROM debian:bookworm-slim
RUN apt-get update && apt-get install -y --no-install-recommends ca-certificates curl procps && rm -rf /var/lib/apt/lists/*

# Install Cursor Agent CLI (pinned version)
# Tarball source: https://downloads.cursor.com/lab/<version>/linux/<arch>/agent-cli-package.tar.gz
# URL scheme scraped from Cursor's official downloads page — no apt/yum package exists.
# If Cursor changes this pattern, the build fails with curl 404. Monitor
# https://cursor.com/cli or https://docs.cursor.com/cli for version/URL updates.
ARG CURSOR_VERSION=2026.04.08-a41fba1
RUN ARCH=$(dpkg --print-architecture) && \
if [ "$ARCH" = "arm64" ]; then ARCH=arm64; else ARCH=x64; fi && \
curl -fSL "https://downloads.cursor.com/lab/${CURSOR_VERSION}/linux/${ARCH}/agent-cli-package.tar.gz" \
-o /tmp/cursor.tar.gz && \
tar xzf /tmp/cursor.tar.gz -C /opt && \
mv /opt/dist-package /opt/cursor-agent && \
ln -s /opt/cursor-agent/cursor-agent /usr/local/bin/cursor-agent && \
rm /tmp/cursor.tar.gz

# Install gh CLI (for auth and token management)
RUN curl -fsSL https://cli.github.com/packages/githubcli-archive-keyring.gpg \
-o /usr/share/keyrings/githubcli-archive-keyring.gpg && \
echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/githubcli-archive-keyring.gpg] https://cli.github.com/packages stable main" \
> /etc/apt/sources.list.d/github-cli.list && \
apt-get update && apt-get install -y --no-install-recommends gh && \
rm -rf /var/lib/apt/lists/*

RUN useradd -m -s /bin/bash -u 1000 agent
ENV HOME=/home/agent
WORKDIR /home/agent

COPY --from=builder --chown=agent:agent /build/target/release/openab /usr/local/bin/openab

USER agent
HEALTHCHECK --interval=30s --timeout=5s --retries=3 \
CMD pgrep -x openab || exit 1
ENTRYPOINT ["openab"]
CMD ["/etc/openab/config.toml"]
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ The bot creates a thread. After that, just type in the thread — no @mention ne
| Codex | `codex-acp` | [@zed-industries/codex-acp](https://github.com/zed-industries/codex-acp) | [docs/codex.md](docs/codex.md) |
| Gemini | `gemini --acp` | Native | [docs/gemini.md](docs/gemini.md) |
| Copilot CLI ⚠️ | `copilot --acp --stdio` | Native | [docs/copilot.md](docs/copilot.md) |
| Cursor | `cursor-agent acp` | Native | [docs/cursor.md](docs/cursor.md) |

> 🔧 Running multiple agents? See [docs/multi-agent.md](docs/multi-agent.md)

Expand Down
4 changes: 3 additions & 1 deletion RELEASING.md
Original file line number Diff line number Diff line change
Expand Up @@ -140,13 +140,15 @@ release-pr.yml 在 Release PR 中自動更新以下檔案的版本:

## Image Variants

每次 build 產出 4 個 multi-arch image (linux/amd64 + linux/arm64):
每次 build 產出 6 個 multi-arch image (linux/amd64 + linux/arm64):

```
ghcr.io/openabdev/openab # default (kiro-cli)
ghcr.io/openabdev/openab-codex # codex
ghcr.io/openabdev/openab-claude # claude
ghcr.io/openabdev/openab-gemini # gemini
ghcr.io/openabdev/openab-copilot # copilot
ghcr.io/openabdev/openab-cursor # cursor
```

Image tags 依 release 類型不同:
Expand Down
3 changes: 3 additions & 0 deletions charts/openab/templates/NOTES.txt
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@ Agents deployed:
{{- else if eq $cfg.command "gemini" }}
Authenticate:
kubectl exec -it deployment/{{ include "openab.agentFullname" (dict "ctx" $ "agent" $name) }} -- gemini
{{- else if eq $cfg.command "cursor-agent" }}
Authenticate:
kubectl exec -it deployment/{{ include "openab.agentFullname" (dict "ctx" $ "agent" $name) }} -- cursor-agent login
{{- end }}

Restart after auth:
Expand Down
27 changes: 27 additions & 0 deletions charts/openab/values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,33 @@ agents:
# tolerations: []
# affinity: {}
# image: "ghcr.io/openabdev/openab-claude:latest"
# cursor:
# command: cursor-agent
# args:
# - acp
# - --model
# - auto
# - --workspace
# - /home/agent
# discord:
# botToken: ""
# allowedChannels:
# - "YOUR_CHANNEL_ID"
# allowedUsers: []
# workingDir: /home/agent
# env: {}
# envFrom: []
# pool:
# maxSessions: 10
# sessionTtlHours: 24
# reactions:
# enabled: true
# removeAfterReply: false
# persistence:
# enabled: true
# storageClass: ""
# size: 1Gi
# image: "ghcr.io/openabdev/openab-cursor:latest"
image: ""
command: kiro-cli
args:
Expand Down
6 changes: 6 additions & 0 deletions config.toml.example
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,12 @@ working_dir = "/home/agent"
# working_dir = "/home/agent"
# env = {} # Auth via: kubectl exec -it <pod> -- gh auth login -p https -w

# [agent]
# command = "cursor-agent"
# args = ["acp", "--model", "auto", "--workspace", "/home/agent"]
# working_dir = "/home/agent"
# env = {} # Auth via: kubectl exec -it <pod> -- cursor-agent login

[pool]
max_sessions = 10
session_ttl_hours = 24
Expand Down
157 changes: 157 additions & 0 deletions docs/cursor.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
# Cursor Agent CLI — Agent Backend Guide

How to run OpenAB with [Cursor Agent CLI](https://www.cursor.com/) as the agent backend.

## Prerequisites

- A paid [Cursor](https://www.cursor.com/pricing) subscription (**Pro or Business** — Free tier does not include Agent CLI access)
- Cursor Agent CLI with native ACP support

## Architecture

```
┌──────────────┐ Gateway WS ┌──────────────┐ ACP stdio ┌──────────────────────┐
│ Discord │◄─────────────►│ openab │──────────────►│ cursor-agent acp │
│ User │ │ (Rust) │◄── JSON-RPC ──│ (Cursor Agent CLI) │
└──────────────┘ └──────────────┘ └──────────────────────┘
```

OpenAB spawns `cursor-agent acp` as a child process and communicates via stdio JSON-RPC. No intermediate layers.

## Configuration

```toml
[agent]
command = "cursor-agent"
args = ["acp"]
working_dir = "/home/agent"
# Auth via: kubectl exec -it <pod> -- cursor-agent login
```

## Docker

Build with the Cursor-specific Dockerfile:

```bash
docker build -f Dockerfile.cursor -t openab-cursor .
```

The Dockerfile installs a pinned version of Cursor Agent CLI via direct download from `downloads.cursor.com`. The version is controlled by the `CURSOR_VERSION` build arg.

## Authentication

Cursor Agent CLI uses its own login flow. In a headless container:

```bash
# 1. Exec into the running pod/container
kubectl exec -it deployment/openab-cursor -- bash

# 2. Authenticate via device flow
cursor-agent login

# 3. Follow the device code flow in your browser

# 4. Restart the pod (token is persisted via PVC)
kubectl rollout restart deployment/openab-cursor
```

The auth token is stored under `~/.cursor/` and persisted across pod restarts via PVC.

## Helm Install

> **Note**: The `ghcr.io/openabdev/openab-cursor` image is not published yet. You must build it locally first with `docker build -f Dockerfile.cursor -t openab-cursor .` and push to your own registry, or use a local image.

```bash
helm install openab openab/openab \
--set agents.kiro.enabled=false \
--set agents.cursor.discord.botToken="$DISCORD_BOT_TOKEN" \
--set-string 'agents.cursor.discord.allowedChannels[0]=YOUR_CHANNEL_ID' \
--set agents.cursor.image=ghcr.io/openabdev/openab-cursor:latest \
--set agents.cursor.command=cursor-agent \
--set 'agents.cursor.args={acp}' \
--set agents.cursor.persistence.enabled=true \
--set agents.cursor.workingDir=/home/agent
```

## Model Selection

List available models:

```bash
cursor-agent --list-models
# or
cursor-agent models
```

To specify a model, pass `--model` as an arg:

```toml
[agent]
command = "cursor-agent"
args = ["acp", "--model", "auto"]
```

In ACP mode, `--model` can be appended after `acp`. If omitted, the account default is used.

To verify which model is active, ask the agent "who are you" — the underlying model will typically self-identify (e.g. "I am Gemini, a large language model built by Google.").

## MCP Usage (ACP mode caveats)

Cursor Agent CLI supports MCP servers configured via `.cursor/mcp.json` in the active workspace directory. **Which directory counts as the workspace is determined by the `--workspace` flag** — if omitted, cursor-agent auto-detects from `cwd`, which is usually `/home/agent` in OpenAB containers via the Dockerfile `WORKDIR` directive but can drift in interactive or local runs. For reproducible MCP loading, pass `--workspace` explicitly:

```toml
[agent]
command = "cursor-agent"
args = ["acp", "--model", "auto", "--workspace", "/home/agent"]
```

This anchors:
- **MCP config lookup**: `/home/agent/.cursor/mcp.json`
- **Approval file path**: `/home/agent/.cursor/projects/home-agent/mcp-approvals.json` (slug = URL-safe workspace path)

Without `--workspace`, a different cwd would produce a different slug and cursor-agent would not find previously saved approvals.

### Example MCP config

```json
{
"mcpServers": {
"playwright": {
"command": "/usr/bin/npx",
"args": ["-y", "@playwright/mcp@latest"]
}
}
}
```

### Approval quirk in ACP mode

Cursor's `--approve-mcps` flag **does not apply in ACP mode** — it only affects the interactive CLI. In ACP mode, MCP servers are gated by an approval file. Two options:

1. **Pre-create the approvals file** at `<workspace>/.cursor/projects/<slug>/mcp-approvals.json`:
```json
["<server-name>-<sha256_hash>"]
```
Hash is derived from workspace path + server config.

2. **Approve once interactively**, then let Cursor persist the approval:
```bash
kubectl exec -it deployment/openab-cursor -- cursor-agent
# invoke an MCP tool, approve the prompt; approval is saved
```

OpenAB itself auto-responds to ACP `session/request_permission` with `allow_always` (see `src/acp/connection.rs`), so once an MCP server is *loaded*, subsequent tool calls pass without prompting. The approval file only gates the initial load.

### Verifying MCP is loaded

```bash
kubectl exec deployment/openab-cursor -- cursor-agent mcp list
# Expected: "<server-name>: ready"
```

## Known Limitations

- Cursor Agent CLI is a separate distribution from Cursor Desktop — they are not the same binary
- No official apt/yum package; the Dockerfile downloads a pinned tarball directly
- `cursor-agent login` requires an interactive terminal for the device flow
- Auth token persistence requires a PVC mount at the user home directory
Loading