@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 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
+ ![header](docs/header.png)
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);