← Back to Plugins
Tools

Cursor Cli

tenderism By tenderism 👁 66 views ▲ 0 votes

OpenClaw CLI-backend plugin for the Cursor Agent CLI — use a Cursor subscription for an OpenClaw agent's own reasoning.

GitHub

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

Loading comments...