@rama_nigg/open-cursor 2.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +28 -0
- package/README.md +347 -0
- package/dist/cli/discover.js +202 -0
- package/dist/cli/opencode-cursor.js +430 -0
- package/dist/index.js +19583 -0
- package/dist/plugin-entry.js +18936 -0
- package/package.json +60 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
BSD 3-Clause License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026, Nomadcxx
|
|
4
|
+
|
|
5
|
+
Redistribution and use in source and binary forms, with or without
|
|
6
|
+
modification, are permitted provided that the following conditions are met:
|
|
7
|
+
|
|
8
|
+
1. Redistributions of source code must retain the above copyright notice, this
|
|
9
|
+
list of conditions and the following disclaimer.
|
|
10
|
+
|
|
11
|
+
2. Redistributions in binary form must reproduce the above copyright notice,
|
|
12
|
+
this list of conditions and the following disclaimer in the documentation
|
|
13
|
+
and/or other materials provided with the distribution.
|
|
14
|
+
|
|
15
|
+
3. Neither the name of the copyright holder nor the names of its
|
|
16
|
+
contributors may be used to endorse or promote products derived from
|
|
17
|
+
this software without specific prior written permission.
|
|
18
|
+
|
|
19
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
20
|
+
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
21
|
+
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
22
|
+
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
|
23
|
+
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
24
|
+
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
25
|
+
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
|
26
|
+
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
|
27
|
+
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
28
|
+
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
package/README.md
ADDED
|
@@ -0,0 +1,347 @@
|
|
|
1
|
+

|
|
2
|
+
|
|
3
|
+
<p align="center">
|
|
4
|
+
<img src="https://img.shields.io/badge/Linux-FCC624?style=for-the-badge&logo=linux&logoColor=black" alt="Linux" />
|
|
5
|
+
<img src="https://img.shields.io/badge/macOS-000000?style=for-the-badge&logo=apple&logoColor=white" alt="macOS" />
|
|
6
|
+
</p>
|
|
7
|
+
|
|
8
|
+
No prompt limits. No broken streams. Full thinking + tool support in Opencode. Your Cursor subscription, properly integrated.
|
|
9
|
+
|
|
10
|
+
## Installation
|
|
11
|
+
|
|
12
|
+
**Option A: One-Line Install**
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
curl -fsSL https://raw.githubusercontent.com/Nomadcxx/opencode-cursor/main/install.sh | bash
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
**Option B: npm Package (Use when published)**
|
|
19
|
+
|
|
20
|
+
Check whether the package is available on npm first:
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
npm view @rama_nigg/open-cursor version
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
If that returns a version, install with:
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
npm install -g @rama_nigg/open-cursor
|
|
30
|
+
open-cursor install
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
Upgrade later with:
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
npm update -g @rama_nigg/open-cursor
|
|
37
|
+
open-cursor sync-models
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
If `npm view` returns `404 Not Found`, the release has not been published yet. Use Option A, C, or E.
|
|
41
|
+
|
|
42
|
+
**Option C: TUI Installer**
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
git clone https://github.com/Nomadcxx/opencode-cursor.git
|
|
46
|
+
cd opencode-cursor
|
|
47
|
+
go build -o ./installer ./cmd/installer && ./installer
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
**Option D: Let an LLM do it**
|
|
51
|
+
|
|
52
|
+
Paste this into any LLM agent (Claude Code, OpenCode, Cursor, etc.):
|
|
53
|
+
|
|
54
|
+
```
|
|
55
|
+
Install the cursor-acp plugin for OpenCode:
|
|
56
|
+
|
|
57
|
+
1. Clone and build:
|
|
58
|
+
git clone https://github.com/Nomadcxx/opencode-cursor.git
|
|
59
|
+
cd opencode-cursor
|
|
60
|
+
bun install && bun run build
|
|
61
|
+
|
|
62
|
+
2. Create plugin symlink:
|
|
63
|
+
mkdir -p ~/.config/opencode/plugin
|
|
64
|
+
ln -sf $(pwd)/dist/plugin-entry.js ~/.config/opencode/plugin/cursor-acp.js
|
|
65
|
+
|
|
66
|
+
3. Get available models:
|
|
67
|
+
cursor-agent models
|
|
68
|
+
|
|
69
|
+
4. Add to ~/.config/opencode/opencode.json - merge with existing config:
|
|
70
|
+
- Add "cursor-acp" to the "plugin" array
|
|
71
|
+
- Add a "cursor-acp" provider with models from step 3
|
|
72
|
+
- Set npm to "@ai-sdk/openai-compatible"
|
|
73
|
+
- Set options.baseURL to "http://127.0.0.1:32124/v1"
|
|
74
|
+
|
|
75
|
+
5. Verify: opencode models | grep cursor
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
**Option E: Manual Install**
|
|
79
|
+
|
|
80
|
+
```bash
|
|
81
|
+
bun install && bun run build
|
|
82
|
+
ln -s $(pwd)/dist/plugin-entry.js ~/.config/opencode/plugin/cursor-acp.js
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
The installers handle the rest automatically. If you're doing a manual install, you'll need to do the following steps yourself.
|
|
86
|
+
|
|
87
|
+
Easiest way is to run the sync script, which populates everything for you:
|
|
88
|
+
|
|
89
|
+
```bash
|
|
90
|
+
./scripts/sync-models.sh
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
Or if you'd rather do it by hand, add this to `~/.config/opencode/opencode.json`:
|
|
94
|
+
|
|
95
|
+
```json
|
|
96
|
+
{
|
|
97
|
+
"plugin": ["cursor-acp"],
|
|
98
|
+
"provider": {
|
|
99
|
+
"cursor-acp": {
|
|
100
|
+
"name": "Cursor",
|
|
101
|
+
"npm": "@ai-sdk/openai-compatible",
|
|
102
|
+
"options": { "baseURL": "http://127.0.0.1:32124/v1" },
|
|
103
|
+
"models": {
|
|
104
|
+
"auto": { "name": "Auto" },
|
|
105
|
+
"composer-1.5": { "name": "Composer 1.5" },
|
|
106
|
+
"composer-1": { "name": "Composer 1" },
|
|
107
|
+
"gpt-5.3-codex": { "name": "GPT-5.3 Codex" },
|
|
108
|
+
"gpt-5.3-codex-low": { "name": "GPT-5.3 Codex Low" },
|
|
109
|
+
"gpt-5.3-codex-high": { "name": "GPT-5.3 Codex High" },
|
|
110
|
+
"gpt-5.3-codex-xhigh": { "name": "GPT-5.3 Codex Extra High" },
|
|
111
|
+
"gpt-5.3-codex-fast": { "name": "GPT-5.3 Codex Fast" },
|
|
112
|
+
"gpt-5.3-codex-low-fast": { "name": "GPT-5.3 Codex Low Fast" },
|
|
113
|
+
"gpt-5.3-codex-high-fast": { "name": "GPT-5.3 Codex High Fast" },
|
|
114
|
+
"gpt-5.3-codex-xhigh-fast": { "name": "GPT-5.3 Codex Extra High Fast" },
|
|
115
|
+
"gpt-5.2": { "name": "GPT-5.2" },
|
|
116
|
+
"gpt-5.2-codex": { "name": "GPT-5.2 Codex" },
|
|
117
|
+
"gpt-5.2-codex-high": { "name": "GPT-5.2 Codex High" },
|
|
118
|
+
"gpt-5.2-codex-low": { "name": "GPT-5.2 Codex Low" },
|
|
119
|
+
"gpt-5.2-codex-xhigh": { "name": "GPT-5.2 Codex Extra High" },
|
|
120
|
+
"gpt-5.2-codex-fast": { "name": "GPT-5.2 Codex Fast" },
|
|
121
|
+
"gpt-5.2-codex-high-fast": { "name": "GPT-5.2 Codex High Fast" },
|
|
122
|
+
"gpt-5.2-codex-low-fast": { "name": "GPT-5.2 Codex Low Fast" },
|
|
123
|
+
"gpt-5.2-codex-xhigh-fast": { "name": "GPT-5.2 Codex Extra High Fast" },
|
|
124
|
+
"gpt-5.1-codex-max": { "name": "GPT-5.1 Codex Max" },
|
|
125
|
+
"gpt-5.1-codex-max-high": { "name": "GPT-5.1 Codex Max High" },
|
|
126
|
+
"opus-4.6-thinking": { "name": "Claude 4.6 Opus (Thinking)" },
|
|
127
|
+
"sonnet-4.5-thinking": { "name": "Claude 4.5 Sonnet (Thinking)" },
|
|
128
|
+
"gpt-5.2-high": { "name": "GPT-5.2 High" },
|
|
129
|
+
"opus-4.6": { "name": "Claude 4.6 Opus" },
|
|
130
|
+
"opus-4.5": { "name": "Claude 4.5 Opus" },
|
|
131
|
+
"opus-4.5-thinking": { "name": "Claude 4.5 Opus (Thinking)" },
|
|
132
|
+
"sonnet-4.5": { "name": "Claude 4.5 Sonnet" },
|
|
133
|
+
"gpt-5.1-high": { "name": "GPT-5.1 High" },
|
|
134
|
+
"gemini-3-pro": { "name": "Gemini 3 Pro" },
|
|
135
|
+
"gemini-3-flash": { "name": "Gemini 3 Flash" },
|
|
136
|
+
"grok": { "name": "Grok" }
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
## Authentication
|
|
144
|
+
|
|
145
|
+
### Option 1: Via OpenCode (Recommended)
|
|
146
|
+
|
|
147
|
+
```bash
|
|
148
|
+
opencode auth login
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
Then follow the prompts:
|
|
152
|
+
|
|
153
|
+
1. Select **"Other"** from the provider list
|
|
154
|
+
2. Enter provider id: **cursor-acp**
|
|
155
|
+
3. Browser will open automatically - click "Continue with Cursor"
|
|
156
|
+
4. Return to terminal when you see "Login successful"
|
|
157
|
+
|
|
158
|
+
### Option 2: Direct (CLI only)
|
|
159
|
+
|
|
160
|
+
```bash
|
|
161
|
+
cursor-agent login
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
Then open the URL shown in your browser and complete authentication.
|
|
165
|
+
|
|
166
|
+
Credential file locations:
|
|
167
|
+
|
|
168
|
+
- macOS: `~/.cursor/cli-config.json` (current) or `~/.cursor/auth.json` (legacy)
|
|
169
|
+
- Linux: `~/.config/cursor/cli-config.json` or `~/.config/cursor/auth.json` (or `$XDG_CONFIG_HOME/cursor/`)
|
|
170
|
+
|
|
171
|
+
## Usage
|
|
172
|
+
|
|
173
|
+
```bash
|
|
174
|
+
opencode run "your prompt" --model cursor-acp/auto
|
|
175
|
+
opencode run "your prompt" --model cursor-acp/sonnet-4.5
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
If installed via npm, manage setup with:
|
|
179
|
+
|
|
180
|
+
```bash
|
|
181
|
+
open-cursor status
|
|
182
|
+
open-cursor sync-models
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
## Models
|
|
186
|
+
|
|
187
|
+
Models are pulled from `cursor-agent models` and written to your config during installation. If Cursor adds new models later, re-run:
|
|
188
|
+
|
|
189
|
+
```bash
|
|
190
|
+
./scripts/sync-models.sh
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
The proxy also exposes a `/v1/models` endpoint that fetches models in real-time:
|
|
194
|
+
|
|
195
|
+
```bash
|
|
196
|
+
curl http://127.0.0.1:32124/v1/models
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
Common models: `auto`, `composer-1.5`, `gpt-5.3-codex`, `opus-4.6-thinking`, `sonnet-4.5`, `gemini-3-pro`, `grok`
|
|
200
|
+
|
|
201
|
+
## Architecture
|
|
202
|
+
|
|
203
|
+
```mermaid
|
|
204
|
+
flowchart TB
|
|
205
|
+
OC["OpenCode"] --> SDK["@ai-sdk/openai-compatible"]
|
|
206
|
+
SDK -->|"POST /v1/chat/completions"| PROXY["cursor-acp proxy :32124"]
|
|
207
|
+
PROXY -->|"spawn per request"| AGENT["cursor-agent --output-format stream-json"]
|
|
208
|
+
AGENT -->|"HTTPS"| CURSOR["Cursor API"]
|
|
209
|
+
CURSOR --> AGENT
|
|
210
|
+
|
|
211
|
+
AGENT -->|"assistant / thinking events"| SSE["SSE content chunks"]
|
|
212
|
+
AGENT -->|"tool_call event"| BOUNDARY["Provider boundary (v1 default)"]
|
|
213
|
+
BOUNDARY --> COMPAT["Schema compat + alias normalization"]
|
|
214
|
+
COMPAT --> GUARD["Tool-loop guard"]
|
|
215
|
+
GUARD -->|"emit tool_calls + finish_reason=tool_calls"| SDK
|
|
216
|
+
SDK --> OC
|
|
217
|
+
|
|
218
|
+
OC -->|"execute tool locally"| TOOLRUN["OpenCode tool runtime"]
|
|
219
|
+
TOOLRUN -->|"next request includes role:tool result"| SDK
|
|
220
|
+
SDK -->|"TOOL_RESULT prompt block"| AGENT
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
Default mode is `CURSOR_ACP_TOOL_LOOP_MODE=opencode`: OpenCode owns tool execution, while cursor-acp intercepts and translates `cursor-agent` tool calls into OpenAI-compatible `tool_calls` responses.
|
|
224
|
+
Legacy execution mode (`proxy-exec`) is still available for local/SDK/MCP execution through the internal router.
|
|
225
|
+
|
|
226
|
+
Detailed architecture: [docs/architecture/runtime-tool-loop.md](docs/architecture/runtime-tool-loop.md).
|
|
227
|
+
|
|
228
|
+
## Alternatives
|
|
229
|
+
|
|
230
|
+
| | cursor-acp | [yet-another-opencode-cursor-auth](https://github.com/Yukaii/yet-another-opencode-cursor-auth) | [opencode-cursor-auth](https://github.com/POSO-PocketSolutions/opencode-cursor-auth) | [cursor-opencode-auth](https://github.com/R44VC0RP/cursor-opencode-auth) |
|
|
231
|
+
| ----------------- | :-------------------------: | :--------------------------------------------------------------------------------------------: | :----------------------------------------------------------------------------------: | :----------------------------------------------------------------------: |
|
|
232
|
+
| **Architecture** | HTTP proxy via cursor-agent | Direct Connect-RPC | HTTP proxy via cursor-agent | Direct Connect-RPC/protobuf |
|
|
233
|
+
| **Platform** | Linux, macOS | Linux, macOS | Linux, macOS | macOS only (Keychain) |
|
|
234
|
+
| **Max Prompt** | Unlimited (HTTP body) | Unknown | ~128KB (ARG_MAX) | Unknown |
|
|
235
|
+
| **Streaming** | ✓ SSE | ✓ SSE | Undocumented | ✓ |
|
|
236
|
+
| **Error Parsing** | ✓ (quota/auth/model) | ✗ | ✗ | Debug logging |
|
|
237
|
+
| **Installer** | ✓ TUI + one-liner | ✗ | ✗ | ✗ |
|
|
238
|
+
| **OAuth Flow** | ✓ OpenCode integration | ✓ Native | Browser login | Keychain |
|
|
239
|
+
| **Tool Calling** | ✓ OpenCode-owned loop (default) + proxy-exec mode | ✓ Native | ✓ Experimental | ✗ |
|
|
240
|
+
| **Stability** | Stable (uses official CLI) | Experimental | Stable | Experimental |
|
|
241
|
+
| **Dependencies** | bun, cursor-agent | npm | bun, cursor-agent | Node.js 18+ |
|
|
242
|
+
| **Port** | 32124 | 18741 | 32123 | 4141 |
|
|
243
|
+
|
|
244
|
+
**Key advantages of cursor-acp:**
|
|
245
|
+
|
|
246
|
+
- Avoids E2BIG errors with large prompts (uses HTTP body, not CLI args)
|
|
247
|
+
- Structured error parsing with actionable suggestions
|
|
248
|
+
- Cross-platform (not locked to macOS Keychain)
|
|
249
|
+
- TUI installer for easy setup
|
|
250
|
+
- Native tool calling with 10 built-in tools, SDK/MCP executor support, and a skills/alias system
|
|
251
|
+
- Uses official cursor-agent CLI (more stable than reverse-engineering Connect-RPC)
|
|
252
|
+
|
|
253
|
+
## Prerequisites
|
|
254
|
+
|
|
255
|
+
- [Bun](https://bun.sh/)
|
|
256
|
+
- [cursor-agent](https://cursor.com/) - `curl -fsSL https://cursor.com/install | bash`
|
|
257
|
+
|
|
258
|
+
**Option A (one-line install):** If Go is installed, the script runs the TUI installer; otherwise it performs a shell-only install (Bun + cursor-agent required). For syncing models without the TUI, install [jq](https://jq.org/) or run `./scripts/sync-models.sh` after install.
|
|
259
|
+
|
|
260
|
+
**Option B (TUI installer):** Go 1.21+ required to build the installer.
|
|
261
|
+
|
|
262
|
+
## Features
|
|
263
|
+
|
|
264
|
+
- HTTP proxy (avoids E2BIG errors)
|
|
265
|
+
- Streaming responses with thinking and tool call support
|
|
266
|
+
- OAuth authentication
|
|
267
|
+
- Error parsing (quota/auth/network)
|
|
268
|
+
|
|
269
|
+
## Development
|
|
270
|
+
|
|
271
|
+
Build and run tests locally:
|
|
272
|
+
|
|
273
|
+
```bash
|
|
274
|
+
bun install
|
|
275
|
+
bun run build
|
|
276
|
+
bun run test:ci:unit
|
|
277
|
+
bun run test:ci:integration
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
CI runs split suites in `.github/workflows/ci.yml`:
|
|
281
|
+
|
|
282
|
+
- `unit` job: `bun run test:ci:unit`
|
|
283
|
+
- `integration` job: `bun run test:ci:integration`
|
|
284
|
+
|
|
285
|
+
Integration CI pins OpenCode-owned loop mode to deterministic settings:
|
|
286
|
+
|
|
287
|
+
- `CURSOR_ACP_TOOL_LOOP_MODE=opencode`
|
|
288
|
+
- `CURSOR_ACP_PROVIDER_BOUNDARY=v1`
|
|
289
|
+
- `CURSOR_ACP_PROVIDER_BOUNDARY_AUTOFALLBACK=false`
|
|
290
|
+
- `CURSOR_ACP_TOOL_LOOP_MAX_REPEAT=3`
|
|
291
|
+
- `CURSOR_ACP_ENABLE_OPENCODE_TOOLS=true`
|
|
292
|
+
- `CURSOR_ACP_FORWARD_TOOL_CALLS=false`
|
|
293
|
+
- `CURSOR_ACP_EMIT_TOOL_UPDATES=false`
|
|
294
|
+
|
|
295
|
+
## Publishing
|
|
296
|
+
|
|
297
|
+
For maintainers, release and npm publish steps are documented in [docs/PUBLISHING.md](docs/PUBLISHING.md).
|
|
298
|
+
|
|
299
|
+
## Troubleshooting
|
|
300
|
+
|
|
301
|
+
**"fetch() URL is invalid"** - Run `opencode auth login` without arguments
|
|
302
|
+
|
|
303
|
+
**Model not responding** - Run `cursor-agent login` to re-authenticate
|
|
304
|
+
|
|
305
|
+
**Quota exceeded** - Check [cursor.com/settings](https://cursor.com/settings)
|
|
306
|
+
|
|
307
|
+
**Authentication failed or incomplete** - Enable debug logging to diagnose:
|
|
308
|
+
```bash
|
|
309
|
+
CURSOR_ACP_LOG_LEVEL=debug opencode auth login cursor-acp
|
|
310
|
+
```
|
|
311
|
+
|
|
312
|
+
Common causes:
|
|
313
|
+
- Browser didn't open automatically - manually open the URL shown in the terminal
|
|
314
|
+
- Auth file not created - ensure `cursor-agent login` works directly first
|
|
315
|
+
- Timeout - authentication must complete within 5 minutes
|
|
316
|
+
|
|
317
|
+
### Debug Logging
|
|
318
|
+
|
|
319
|
+
Set the log level via environment variable:
|
|
320
|
+
- `CURSOR_ACP_LOG_LEVEL=debug` - Verbose output for troubleshooting
|
|
321
|
+
- `CURSOR_ACP_LOG_LEVEL=info` - Default level
|
|
322
|
+
- `CURSOR_ACP_LOG_LEVEL=warn` - Warnings and errors only
|
|
323
|
+
- `CURSOR_ACP_LOG_LEVEL=error` - Errors only
|
|
324
|
+
|
|
325
|
+
Provider-boundary rollout:
|
|
326
|
+
- Default: `CURSOR_ACP_PROVIDER_BOUNDARY=v1`
|
|
327
|
+
- Default: `CURSOR_ACP_PROVIDER_BOUNDARY_AUTOFALLBACK=true`
|
|
328
|
+
- `CURSOR_ACP_PROVIDER_BOUNDARY=legacy` - Original provider/runtime boundary behavior
|
|
329
|
+
- `CURSOR_ACP_PROVIDER_BOUNDARY=v1` - Shared boundary/interception path
|
|
330
|
+
- `CURSOR_ACP_PROVIDER_BOUNDARY_AUTOFALLBACK=false` - Disable fallback and keep strict `v1` behavior
|
|
331
|
+
- `CURSOR_ACP_TOOL_LOOP_MAX_REPEAT=3` - Max repeated failing tool-call fingerprints before guard termination (or fallback when enabled)
|
|
332
|
+
|
|
333
|
+
Auto-fallback trigger conditions:
|
|
334
|
+
- Only active when `CURSOR_ACP_PROVIDER_BOUNDARY=v1`
|
|
335
|
+
- Triggered when `v1` boundary extraction throws during tool-call interception
|
|
336
|
+
- Triggered when the tool-loop guard threshold is reached (same tool + arg shape + error class)
|
|
337
|
+
- Does not trigger for normal cases like disallowed tools or no tool call
|
|
338
|
+
- Does not trigger for unrelated runtime errors (for example, tool mapper/tool execution failures)
|
|
339
|
+
|
|
340
|
+
Disable log output entirely:
|
|
341
|
+
```bash
|
|
342
|
+
CURSOR_ACP_LOG_SILENT=true opencode run "your prompt"
|
|
343
|
+
```
|
|
344
|
+
|
|
345
|
+
## License
|
|
346
|
+
|
|
347
|
+
BSD-3-Clause
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { createRequire } from "node:module";
|
|
3
|
+
var __create = Object.create;
|
|
4
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
5
|
+
var __defProp = Object.defineProperty;
|
|
6
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __toESM = (mod, isNodeMode, target) => {
|
|
9
|
+
target = mod != null ? __create(__getProtoOf(mod)) : {};
|
|
10
|
+
const to = isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target;
|
|
11
|
+
for (let key of __getOwnPropNames(mod))
|
|
12
|
+
if (!__hasOwnProp.call(to, key))
|
|
13
|
+
__defProp(to, key, {
|
|
14
|
+
get: () => mod[key],
|
|
15
|
+
enumerable: true
|
|
16
|
+
});
|
|
17
|
+
return to;
|
|
18
|
+
};
|
|
19
|
+
var __export = (target, all) => {
|
|
20
|
+
for (var name in all)
|
|
21
|
+
__defProp(target, name, {
|
|
22
|
+
get: all[name],
|
|
23
|
+
enumerable: true,
|
|
24
|
+
configurable: true,
|
|
25
|
+
set: (newValue) => all[name] = () => newValue
|
|
26
|
+
});
|
|
27
|
+
};
|
|
28
|
+
var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
|
|
29
|
+
var __require = /* @__PURE__ */ createRequire(import.meta.url);
|
|
30
|
+
|
|
31
|
+
// src/utils/errors.ts
|
|
32
|
+
function stripAnsi(str) {
|
|
33
|
+
if (typeof str !== "string")
|
|
34
|
+
return String(str ?? "");
|
|
35
|
+
return str.replace(/\x1b\[[0-9;]*[a-zA-Z]/g, "");
|
|
36
|
+
}
|
|
37
|
+
function parseAgentError(stderr) {
|
|
38
|
+
const input = typeof stderr === "string" ? stderr : String(stderr ?? "");
|
|
39
|
+
const clean = stripAnsi(input).trim();
|
|
40
|
+
if (clean.includes("usage limit") || clean.includes("hit your usage limit")) {
|
|
41
|
+
const savingsMatch = clean.match(/saved \$(\d+(?:\.\d+)?)/i);
|
|
42
|
+
const resetMatch = clean.match(/reset[^0-9]*(\d{1,2}\/\d{1,2}\/\d{4})/i);
|
|
43
|
+
const modelMatch = clean.match(/continue with (\w+)/i);
|
|
44
|
+
const details = {};
|
|
45
|
+
if (savingsMatch)
|
|
46
|
+
details.savings = `$${savingsMatch[1]}`;
|
|
47
|
+
if (resetMatch)
|
|
48
|
+
details.resetDate = resetMatch[1];
|
|
49
|
+
if (modelMatch)
|
|
50
|
+
details.affectedModel = modelMatch[1];
|
|
51
|
+
return {
|
|
52
|
+
type: "quota",
|
|
53
|
+
recoverable: false,
|
|
54
|
+
message: clean,
|
|
55
|
+
userMessage: "You've hit your Cursor usage limit",
|
|
56
|
+
details,
|
|
57
|
+
suggestion: "Switch to a different model or set a Spend Limit in Cursor settings"
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
if (clean.includes("not logged in") || clean.includes("auth") || clean.includes("unauthorized")) {
|
|
61
|
+
return {
|
|
62
|
+
type: "auth",
|
|
63
|
+
recoverable: false,
|
|
64
|
+
message: clean,
|
|
65
|
+
userMessage: "Not authenticated with Cursor",
|
|
66
|
+
details: {},
|
|
67
|
+
suggestion: "Run: opencode auth login → Other → cursor-acp, or: cursor-agent login"
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
if (clean.includes("ECONNREFUSED") || clean.includes("network") || clean.includes("fetch failed")) {
|
|
71
|
+
return {
|
|
72
|
+
type: "network",
|
|
73
|
+
recoverable: true,
|
|
74
|
+
message: clean,
|
|
75
|
+
userMessage: "Connection to Cursor failed",
|
|
76
|
+
details: {},
|
|
77
|
+
suggestion: "Check your internet connection and try again"
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
if (clean.includes("model not found") || clean.includes("invalid model") || clean.includes("Cannot use this model")) {
|
|
81
|
+
const modelMatch = clean.match(/Cannot use this model: ([^.]+)/);
|
|
82
|
+
const availableMatch = clean.match(/Available models: (.+)/);
|
|
83
|
+
const details = {};
|
|
84
|
+
if (modelMatch)
|
|
85
|
+
details.requested = modelMatch[1];
|
|
86
|
+
if (availableMatch)
|
|
87
|
+
details.available = availableMatch[1].split(", ").slice(0, 5).join(", ") + "...";
|
|
88
|
+
return {
|
|
89
|
+
type: "model",
|
|
90
|
+
recoverable: false,
|
|
91
|
+
message: clean,
|
|
92
|
+
userMessage: modelMatch ? `Model '${modelMatch[1]}' not available` : "Requested model not available",
|
|
93
|
+
details,
|
|
94
|
+
suggestion: "Use cursor-acp/auto or check available models with: cursor-agent models"
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
const recoverable = clean.includes("timeout") || clean.includes("ETIMEDOUT");
|
|
98
|
+
return {
|
|
99
|
+
type: "unknown",
|
|
100
|
+
recoverable,
|
|
101
|
+
message: clean,
|
|
102
|
+
userMessage: clean.substring(0, 200) || "An error occurred",
|
|
103
|
+
details: {}
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
function formatErrorForUser(error) {
|
|
107
|
+
let output = `cursor-acp error: ${error.userMessage || error.message || "Unknown error"}`;
|
|
108
|
+
const details = error.details || {};
|
|
109
|
+
if (Object.keys(details).length > 0) {
|
|
110
|
+
const detailParts = Object.entries(details).map(([k, v]) => `${k}: ${v}`).join(" | ");
|
|
111
|
+
output += `
|
|
112
|
+
${detailParts}`;
|
|
113
|
+
}
|
|
114
|
+
if (error.suggestion) {
|
|
115
|
+
output += `
|
|
116
|
+
Suggestion: ${error.suggestion}`;
|
|
117
|
+
}
|
|
118
|
+
return output;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// src/cli/discover.ts
|
|
122
|
+
import { readFileSync, writeFileSync, existsSync } from "fs";
|
|
123
|
+
import { join } from "path";
|
|
124
|
+
import { homedir } from "os";
|
|
125
|
+
|
|
126
|
+
// src/cli/model-discovery.ts
|
|
127
|
+
import { execFileSync } from "child_process";
|
|
128
|
+
function parseCursorModelsOutput(output) {
|
|
129
|
+
const clean = stripAnsi(output);
|
|
130
|
+
const models = [];
|
|
131
|
+
const seen = new Set;
|
|
132
|
+
for (const line of clean.split(`
|
|
133
|
+
`)) {
|
|
134
|
+
const trimmed = line.trim();
|
|
135
|
+
if (!trimmed)
|
|
136
|
+
continue;
|
|
137
|
+
const match = trimmed.match(/^([a-zA-Z0-9._-]+)\s+-\s+(.+?)(?:\s+\((?:current|default)\))*\s*$/);
|
|
138
|
+
if (!match)
|
|
139
|
+
continue;
|
|
140
|
+
const id = match[1];
|
|
141
|
+
if (seen.has(id))
|
|
142
|
+
continue;
|
|
143
|
+
seen.add(id);
|
|
144
|
+
models.push({ id, name: match[2].trim() });
|
|
145
|
+
}
|
|
146
|
+
return models;
|
|
147
|
+
}
|
|
148
|
+
function discoverModelsFromCursorAgent() {
|
|
149
|
+
const raw = execFileSync("cursor-agent", ["models"], {
|
|
150
|
+
encoding: "utf8",
|
|
151
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
152
|
+
});
|
|
153
|
+
const models = parseCursorModelsOutput(raw);
|
|
154
|
+
if (models.length === 0) {
|
|
155
|
+
throw new Error("No models parsed from cursor-agent output");
|
|
156
|
+
}
|
|
157
|
+
return models;
|
|
158
|
+
}
|
|
159
|
+
function fallbackModels() {
|
|
160
|
+
return [
|
|
161
|
+
{ id: "auto", name: "Auto" },
|
|
162
|
+
{ id: "sonnet-4.5", name: "Claude 4.5 Sonnet" },
|
|
163
|
+
{ id: "opus-4.6", name: "Claude 4.6 Opus" },
|
|
164
|
+
{ id: "gpt-5.2", name: "GPT-5.2" }
|
|
165
|
+
];
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// src/cli/discover.ts
|
|
169
|
+
async function main() {
|
|
170
|
+
console.log("Discovering Cursor models...");
|
|
171
|
+
let models = fallbackModels();
|
|
172
|
+
try {
|
|
173
|
+
models = discoverModelsFromCursorAgent();
|
|
174
|
+
} catch (error) {
|
|
175
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
176
|
+
console.warn(`Warning: cursor-agent model discovery failed, using fallback list (${message})`);
|
|
177
|
+
}
|
|
178
|
+
console.log(`Found ${models.length} models:`);
|
|
179
|
+
for (const model of models) {
|
|
180
|
+
console.log(` - ${model.id}: ${model.name}`);
|
|
181
|
+
}
|
|
182
|
+
const configPath = join(homedir(), ".config/opencode/opencode.json");
|
|
183
|
+
if (!existsSync(configPath)) {
|
|
184
|
+
console.error(`Config not found: ${configPath}`);
|
|
185
|
+
process.exit(1);
|
|
186
|
+
}
|
|
187
|
+
const existingConfig = JSON.parse(readFileSync(configPath, "utf-8"));
|
|
188
|
+
if (existingConfig.provider?.["cursor-acp"]) {
|
|
189
|
+
const formatted = Object.fromEntries(models.map((model) => [model.id, { name: model.name }]));
|
|
190
|
+
existingConfig.provider["cursor-acp"].models = {
|
|
191
|
+
...existingConfig.provider["cursor-acp"].models,
|
|
192
|
+
...formatted
|
|
193
|
+
};
|
|
194
|
+
writeFileSync(configPath, JSON.stringify(existingConfig, null, 2));
|
|
195
|
+
console.log(`Updated ${configPath}`);
|
|
196
|
+
} else {
|
|
197
|
+
console.error("cursor-acp provider not found in config");
|
|
198
|
+
process.exit(1);
|
|
199
|
+
}
|
|
200
|
+
console.log("Done!");
|
|
201
|
+
}
|
|
202
|
+
main().catch(console.error);
|