Tools
Cursor Cli
OpenClaw CLI-backend plugin for the Cursor Agent CLI — use a Cursor subscription for an OpenClaw agent's own reasoning.
Install
openclaw plugins install /path/to/openclaw-cursor-cli
README
# openclaw-cursor-cli
An [OpenClaw](https://docs.openclaw.ai) CLI-backend plugin that registers the
[Cursor Agent CLI](https://cursor.com/cli) (`cursor-agent`) as a model
provider — `cursor-cli/<model>`.
## Why
OpenClaw's bundled `acpx` plugin already lets an agent *spawn* Cursor as a
coding worker via ACP. That's a different thing from what this plugin does:
this lets an agent's own reasoning — the part that reads a message and
decides what to do — run on a Cursor subscription, the same way
`claude-cli` lets it run on a Claude Code subscription.
The practical use case is resilience: if you have both a Claude and a
Cursor subscription, you don't have to be locked into one running out of
usage. Add `cursor-cli/<model>` as a fallback (`agents.list.<id>.model.fallbacks`)
alongside your primary, and OpenClaw fails over automatically.
## Requirements
- [Cursor Agent CLI](https://cursor.com/cli) installed and logged in
(`cursor-agent login`) on the same host as the OpenClaw gateway.
- OpenClaw with plugin support (any recent version — built against the
`cliBackends` / `registerCliBackend` plugin API).
## Install
```bash
openclaw plugins install /path/to/openclaw-cursor-cli
openclaw plugins enable cursor-cli
```
(Or point `openclaw plugins install` at this repo once it's published —
git URL or npm spec both work with OpenClaw's plugin installer.)
Restart the gateway after enabling — CLI-backend registration needs a
restart to take effect, same as any other plugin enable/disable.
### Required extra step (third-party plugin limitation, not this plugin's bug)
As installed above, `cursor-cli/<model>` will fail every request with
`Unknown model: cursor-cli/<model>` (or, if you've hit the variant of this
bug, `No API provider registered for api: ...`). This isn't something
`register(api)` can fix from inside the plugin — traced it live, with
instrumented debug logging, all the way to OpenClaw's own
`resolveRuntimeCliBackends()` (`model-selection-*.js`), which reads from
`loadPluginRuntime()?.getActivePluginRegistry()?.cliBackends`. For a
**bundled** CLI backend (`claude-cli`, `google-gemini-cli`) that registry is
populated correctly. For a **third-party/linked** plugin — this one, and
presumably any other non-bundled `registerCliBackend` plugin — it comes back
empty at model-resolution time, even though `openclaw plugins inspect`
correctly shows the backend as loaded. The bundled-only fallback path
(`resolveBundledSetupCliBackends`) makes this explicit: it filters
`plugin.origin === "bundled"`, which a linked/npm-installed plugin never is.
The fix is a second, independent lookup path OpenClaw does support:
statically declaring the backend's config directly under
`agents.defaults.cliBackends`, which `resolveCliBackendConfig` consults
*before* falling back to the (broken, for third-party plugins)
registry-based lookup. Add this once, after installing the plugin:
```bash
openclaw config set 'agents.defaults.cliBackends' '{
"cursor-cli": {
"command": "cursor-agent",
"args": ["--print", "--force", "--output-format", "stream-json"],
"resumeArgs": ["--print", "--force", "--output-format", "stream-json", "--resume", "{sessionId}"],
"output": "jsonl",
"input": "stdin",
"modelArg": "--model",
"modelAliases": { "auto": "auto" },
"sessionMode": "existing",
"sessionIdFields": ["session_id"],
"clearEnv": ["CURSOR_API_KEY", "CURSOR_API_ENDPOINT"],
"serialize": true,
"reliability": {
"watchdog": {
"fresh": { "noOutputTimeoutRatio": 0.8, "minMs": 180000, "maxMs": 600000 },
"resume": { "noOutputTimeoutRatio": 0.3, "minMs": 60000, "maxMs": 180000 }
}
}
}
}' --strict-json
```
(If you already have other entries under `agents.defaults.cliBackends`,
merge rather than overwrite — this sets the whole map.) Restart the gateway
after. This must stay in sync with `src/index.js`'s `buildCursorCliBackend()`
— if you change one, change the other. Verified live end-to-end after
applying this: real `cursor-agent` subprocess spawn, correct output, and
session `--resume` correctly reusing context across turns.
## Configure
Optional plugin config, if `cursor-agent` isn't on the gateway's `PATH`:
```json5
{
plugins: {
entries: {
"cursor-cli": {
enabled: true,
config: { command: "/absolute/path/to/cursor-agent" }
}
}
}
}
```
## Use
Set it as a primary or fallback model for any agent, scoped per-agent
(don't use the `--agent` flag on `openclaw models set`/`fallbacks add` —
it doesn't scope the write the way you'd expect; edit the agent's config
path directly):
```bash
openclaw config set 'agents.list[N].model.fallbacks' '["cursor-cli/auto"]' --strict-json
```
Model ids pass straight through to `cursor-agent --model`. Run
`cursor-agent --list-models` for the current catalog (it's large and moves
fast — Claude, GPT, Gemini, Grok, Kimi, GLM variants, each with several
effort tiers) — anything in that list works directly as `cursor-cli/<id>`.
`cursor-cli/auto` (Cursor's own auto-routing) is the default and the safest
choice if you don't want to hardcode a specific model that might age out.
## How it works
`cursor-agent --print --force --output-format stream-json` emits
newline-delimited JSON events shaped like Claude's own stream-json protocol
(`type`/`subtype`, `message.content[]`, a final `result` event with the
answer text and token usage) with a `session_id` field on every line. This
plugin declares that shape to OpenClaw's generic CLI-backend machinery —
it's a small declarative config, not a custom parser.
One real difference from `claude-cli`: cursor-agent assigns its own session
id on the first call (there's no flag to hand it one up front) and only
accepts `--resume <id>` on later calls. That's `sessionMode: "existing"`
here, versus `"always"` for claude-cli.
## Known limitations
- **Requires the `agents.defaults.cliBackends` config step above.** Not
optional, not this plugin's bug — see "Required extra step" under Install
for the root cause (OpenClaw's runtime CLI-backend registry doesn't pick up
third-party/linked plugins the same way it picks up bundled ones).
- **No daemon/server mode.** Unlike OpenAI's Codex (which ships an
`app-server` protocol specifically for external tools like this),
`cursor-agent` has no persistent process another tool can talk to — every
turn is a fresh subprocess spawn, same as `claude-cli`. Works fine, just
means no protocol-level session pinning beyond `--resume`.
- **`--force` auto-approves everything** within the run (matches the same
risk posture OpenClaw's bundled `claude-cli` backend already uses via
`--permission-mode bypassPermissions`) — this is a reasoning backend for
an already-trusted agent, not a sandboxed execution mode.
- **Model aliases are intentionally minimal.** Cursor's model catalog is
large and changes often; hardcoding friendly names beyond `auto` would go
stale. Use literal model ids from `cursor-agent --list-models`.
- Auth is whatever `cursor-agent login` already set up on the host — this
plugin doesn't manage credentials itself. Concretely: `registerProvider`'s
`auth` array is empty and `resolveSyntheticAuth` returns a placeholder
token purely so OpenClaw's "is this provider authenticated" check passes
— no credential is ever read, stored, or exchanged by OpenClaw.
- **Both `registerCliBackend` *and* `registerProvider` are required** for a
`cursor-cli/<model>` ref to resolve at all — this wasn't obvious going in
and is worth calling out for anyone extending this plugin. OpenClaw keeps
two separate plugin registries: `registerCliBackend` only wires up *how to
run* a model once one has already resolved (the subprocess command/args/
session handling below); `registerProvider` (specifically its
`resolveDynamicModel` hook) is what makes `cursor-cli/<model>` resolve to
a model object in the first place. A CLI backend with no matching
provider fails every request with "Unknown model", regardless of
`agents.defaults.models` allowlist entries (that's a separate permission
gate, not model resolution). Every bundled CLI-backend plugin
(`claude-cli`, `google-gemini-cli`, Codex's app-server) registers both
together for this reason.
## License
MIT
tools
Comments
Sign in to leave a comment