@rama_nigg/open-cursor 2.3.20 → 2.4.1

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/README.md CHANGED
@@ -3,6 +3,7 @@
3
3
  <p align="center">
4
4
  <img src="https://img.shields.io/badge/Linux-FCC624?style=for-the-badge&logo=linux&logoColor=black" alt="Linux" />
5
5
  <img src="https://img.shields.io/badge/macOS-000000?style=for-the-badge&logo=apple&logoColor=white" alt="macOS" />
6
+ <img src="https://img.shields.io/badge/Windows-0078D6?style=for-the-badge&logo=windows&logoColor=white" alt="Windows" />
6
7
  </p>
7
8
 
8
9
  No prompt limits. No broken streams. Full thinking + tool support in OpenCode. Your Cursor subscription, properly integrated.
@@ -11,14 +12,23 @@ No prompt limits. No broken streams. Full thinking + tool support in OpenCode. Y
11
12
 
12
13
  ### Option A — One-line installer
13
14
 
15
+ **Linux & macOS:**
14
16
  ```bash
15
17
  curl -fsSL https://raw.githubusercontent.com/Nomadcxx/opencode-cursor/main/install.sh | bash
16
18
  ```
17
19
 
20
+ **Windows:**
21
+ ```powershell
22
+ # Windows installer coming soon.
23
+ # In the meantime, use Option C (npm install):
24
+ npm install -g @rama_nigg/open-cursor
25
+ open-cursor install
26
+ ```
27
+
18
28
  <details>
19
29
  <summary><b>Option B</b> — Add to opencode.json</summary>
20
30
 
21
- Add to `~/.config/opencode/opencode.json`:
31
+ Add to `~/.config/opencode/opencode.json` (or `%USERPROFILE%\.config\opencode\opencode.json` on Windows):
22
32
 
23
33
  ```json
24
34
  {
@@ -31,58 +41,50 @@ Add to `~/.config/opencode/opencode.json`:
31
41
  "baseURL": "http://127.0.0.1:32124/v1"
32
42
  },
33
43
  "models": {
34
- "cursor-acp/auto": { "name": "Auto" },
35
- "cursor-acp/composer-1.5": { "name": "Composer 1.5" },
36
- "cursor-acp/composer-1": { "name": "Composer 1" },
37
- "cursor-acp/opus-4.6-thinking": { "name": "Claude 4.6 Opus (Thinking)" },
38
- "cursor-acp/opus-4.6": { "name": "Claude 4.6 Opus" },
39
- "cursor-acp/sonnet-4.6": { "name": "Claude 4.6 Sonnet" },
40
- "cursor-acp/sonnet-4.6-thinking": { "name": "Claude 4.6 Sonnet (Thinking)" },
41
- "cursor-acp/opus-4.5": { "name": "Claude 4.5 Opus" },
42
- "cursor-acp/opus-4.5-thinking": { "name": "Claude 4.5 Opus (Thinking)" },
43
- "cursor-acp/sonnet-4.5": { "name": "Claude 4.5 Sonnet" },
44
- "cursor-acp/sonnet-4.5-thinking": { "name": "Claude 4.5 Sonnet (Thinking)" },
45
- "cursor-acp/gpt-5.4-high": { "name": "GPT-5.4 High" },
46
- "cursor-acp/gpt-5.4-high-fast": { "name": "GPT-5.4 High Fast" },
47
- "cursor-acp/gpt-5.4-xhigh": { "name": "GPT-5.4 Extra High" },
48
- "cursor-acp/gpt-5.4-xhigh-fast": { "name": "GPT-5.4 Extra High Fast" },
49
- "cursor-acp/gpt-5.4-medium": { "name": "GPT-5.4" },
50
- "cursor-acp/gpt-5.4-medium-fast": { "name": "GPT-5.4 Fast" },
51
- "cursor-acp/gpt-5.3-codex": { "name": "GPT-5.3 Codex" },
52
- "cursor-acp/gpt-5.3-codex-fast": { "name": "GPT-5.3 Codex Fast" },
53
- "cursor-acp/gpt-5.3-codex-low": { "name": "GPT-5.3 Codex Low" },
54
- "cursor-acp/gpt-5.3-codex-low-fast": { "name": "GPT-5.3 Codex Low Fast" },
55
- "cursor-acp/gpt-5.3-codex-high": { "name": "GPT-5.3 Codex High" },
56
- "cursor-acp/gpt-5.3-codex-high-fast": { "name": "GPT-5.3 Codex High Fast" },
57
- "cursor-acp/gpt-5.3-codex-xhigh": { "name": "GPT-5.3 Codex Extra High" },
58
- "cursor-acp/gpt-5.3-codex-xhigh-fast": { "name": "GPT-5.3 Codex Extra High Fast" },
59
- "cursor-acp/gpt-5.3-codex-spark-preview": { "name": "GPT-5.3 Codex Spark" },
60
- "cursor-acp/gpt-5.2": { "name": "GPT-5.2" },
61
- "cursor-acp/gpt-5.2-high": { "name": "GPT-5.2 High" },
62
- "cursor-acp/gpt-5.2-codex": { "name": "GPT-5.2 Codex" },
63
- "cursor-acp/gpt-5.2-codex-fast": { "name": "GPT-5.2 Codex Fast" },
64
- "cursor-acp/gpt-5.2-codex-low": { "name": "GPT-5.2 Codex Low" },
65
- "cursor-acp/gpt-5.2-codex-low-fast": { "name": "GPT-5.2 Codex Low Fast" },
66
- "cursor-acp/gpt-5.2-codex-high": { "name": "GPT-5.2 Codex High" },
67
- "cursor-acp/gpt-5.2-codex-high-fast": { "name": "GPT-5.2 Codex High Fast" },
68
- "cursor-acp/gpt-5.2-codex-xhigh": { "name": "GPT-5.2 Codex Extra High" },
69
- "cursor-acp/gpt-5.2-codex-xhigh-fast": { "name": "GPT-5.2 Codex Extra High Fast" },
44
+ "cursor-acp/auto": { "name": "Auto" },
45
+
46
+ "cursor-acp/claude-opus-4-7": { "name": "Claude 4.7 Opus" },
47
+ "cursor-acp/claude-4.6-opus": { "name": "Claude 4.6 Opus" },
48
+ "cursor-acp/claude-4.6-sonnet": { "name": "Claude 4.6 Sonnet" },
49
+ "cursor-acp/claude-4.5-opus": { "name": "Claude 4.5 Opus" },
50
+ "cursor-acp/claude-4.5-sonnet": { "name": "Claude 4.5 Sonnet" },
51
+ "cursor-acp/claude-4.5-haiku": { "name": "Claude 4.5 Haiku" },
52
+ "cursor-acp/claude-4-sonnet": { "name": "Claude 4 Sonnet" },
53
+
54
+ "cursor-acp/gpt-5.5": { "name": "GPT-5.5" },
55
+ "cursor-acp/gpt-5.4": { "name": "GPT-5.4" },
56
+ "cursor-acp/gpt-5.4-mini": { "name": "GPT-5.4 Mini" },
57
+ "cursor-acp/gpt-5.4-nano": { "name": "GPT-5.4 Nano" },
58
+ "cursor-acp/gpt-5.3-codex": { "name": "GPT-5.3 Codex" },
59
+ "cursor-acp/gpt-5.2": { "name": "GPT-5.2" },
60
+ "cursor-acp/gpt-5.2-codex": { "name": "GPT-5.2 Codex" },
61
+ "cursor-acp/gpt-5.1-codex": { "name": "GPT-5.1 Codex" },
70
62
  "cursor-acp/gpt-5.1-codex-max": { "name": "GPT-5.1 Codex Max" },
71
- "cursor-acp/gpt-5.1-codex-max-high": { "name": "GPT-5.1 Codex Max High" },
72
- "cursor-acp/gpt-5.1-codex-mini": { "name": "GPT-5.1 Codex Mini" },
73
- "cursor-acp/gpt-5.1-high": { "name": "GPT-5.1 High" },
74
- "cursor-acp/gemini-3.1-pro": { "name": "Gemini 3.1 Pro" },
75
- "cursor-acp/gemini-3-pro": { "name": "Gemini 3 Pro" },
76
- "cursor-acp/gemini-3-flash": { "name": "Gemini 3 Flash" },
77
- "cursor-acp/grok": { "name": "Grok" },
78
- "cursor-acp/kimi-k2.5": { "name": "Kimi K2.5" }
63
+ "cursor-acp/gpt-5.1-codex-mini":{ "name": "GPT-5.1 Codex Mini" },
64
+ "cursor-acp/gpt-5-mini": { "name": "GPT-5 Mini" },
65
+
66
+ "cursor-acp/gemini-3.1-pro": { "name": "Gemini 3.1 Pro" },
67
+ "cursor-acp/gemini-3-pro": { "name": "Gemini 3 Pro" },
68
+ "cursor-acp/gemini-3-flash": { "name": "Gemini 3 Flash" },
69
+
70
+ "cursor-acp/composer-2": { "name": "Composer 2" },
71
+ "cursor-acp/composer-2-fast": { "name": "Composer 2 Fast" },
72
+ "cursor-acp/composer-1.5": { "name": "Composer 1.5" },
73
+
74
+ "cursor-acp/grok-4-20": { "name": "Grok 4.20" },
75
+ "cursor-acp/kimi-k2.5": { "name": "Kimi K2.5" }
79
76
  }
80
77
  }
81
78
  }
82
79
  }
83
80
  ```
84
81
 
85
- > Update models anytime: `cursor-agent models`
82
+ > **Refresh anytime** with the bundled CLI:
83
+ > ```bash
84
+ > open-cursor sync-models # plain list
85
+ > open-cursor sync-models --variants --compact # group thinking / fast / -low/-high variants under each base
86
+ > ```
87
+ > The `--variants --compact` form is recommended — it folds dozens of `*-thinking-fast`, `*-high-fast`, etc. into a single entry per family with a `variants` map, and includes `cost` from the official Cursor pricing table so OpenCode TokenSpeed can render usage correctly. Direct `cursor-agent models` still works if you prefer the raw list.
86
88
  </details>
87
89
 
88
90
  <details>
@@ -110,7 +112,7 @@ go build -o ./installer ./cmd/installer && ./installer
110
112
  <summary><b>Option E</b> — LLM paste</summary>
111
113
 
112
114
  ```
113
- Install open-cursor for OpenCode: edit ~/.config/opencode/opencode.json, add "@rama_nigg/open-cursor@latest" to "plugin", add a "cursor-acp" provider with npm "@ai-sdk/openai-compatible" and models from `cursor-agent models` prefixed with "cursor-acp/". Auth: `cursor-agent login`. Verify: `opencode models | grep cursor-acp`.
115
+ Install open-cursor for OpenCode: edit ~/.config/opencode/opencode.json, add "@rama_nigg/open-cursor@latest" to "plugin", add a "cursor-acp" provider with npm "@ai-sdk/openai-compatible" and a baseURL of http://127.0.0.1:32124/v1. Populate models by running `open-cursor sync-models --variants --compact` after install (or copy the model list from the README). Auth: `cursor-agent login`. Verify: `opencode models | grep cursor-acp`.
114
116
  ```
115
117
  </details>
116
118
 
@@ -188,7 +190,7 @@ THERE is currently not a single perfect plugin for cursor in opencode, my advice
188
190
  | | open-cursor | [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) |
189
191
  | ----------------- | :------------------------: | :--------------------------------------------------------------------------------------------: | :----------------------------------------------------------------------------------: | :----------------------------------------------------------------------: |
190
192
  | **Architecture** | HTTP proxy via cursor-agent | Direct Connect-RPC | HTTP proxy via cursor-agent | Direct Connect-RPC/protobuf |
191
- | **Platform** | Linux, macOS | Linux, macOS | Linux, macOS | macOS only (Keychain) |
193
+ | **Platform** | Linux, macOS, Windows | Linux, macOS | Linux, macOS | macOS only (Keychain) |
192
194
  | **Max Prompt** | Unlimited (HTTP body) | Unknown | ~128KB (ARG_MAX) | Unknown |
193
195
  | **Streaming** | ✓ SSE | ✓ SSE | Undocumented | ✓ |
194
196
  | **Error Parsing** | ✓ (quota/auth/model) | ✗ | ✗ | Debug logging |
@@ -137,6 +137,173 @@ function formatErrorForUser(error) {
137
137
  return output;
138
138
  }
139
139
 
140
+ // src/utils/logger.ts
141
+ import * as fs from "node:fs";
142
+ import * as path from "node:path";
143
+ import * as os from "node:os";
144
+ function getConfiguredLevel() {
145
+ const env = process.env.CURSOR_ACP_LOG_LEVEL?.toLowerCase();
146
+ if (env && env in LEVEL_PRIORITY) {
147
+ return env;
148
+ }
149
+ return "info";
150
+ }
151
+ function isSilent() {
152
+ return process.env.CURSOR_ACP_LOG_SILENT === "1" || process.env.CURSOR_ACP_LOG_SILENT === "true";
153
+ }
154
+ function shouldLog(level) {
155
+ if (isSilent())
156
+ return false;
157
+ const configured = getConfiguredLevel();
158
+ return LEVEL_PRIORITY[level] >= LEVEL_PRIORITY[configured];
159
+ }
160
+ function formatMessage(level, component, message, data) {
161
+ const timestamp = new Date().toISOString();
162
+ const prefix = `[cursor-acp:${component}]`;
163
+ const levelTag = level.toUpperCase().padEnd(5);
164
+ let formatted = `${prefix} ${levelTag} ${message}`;
165
+ if (data !== undefined) {
166
+ if (typeof data === "object") {
167
+ formatted += ` ${JSON.stringify(data)}`;
168
+ } else {
169
+ formatted += ` ${data}`;
170
+ }
171
+ }
172
+ return formatted;
173
+ }
174
+ function isConsoleEnabled() {
175
+ const consoleEnv = process.env.CURSOR_ACP_LOG_CONSOLE;
176
+ return consoleEnv === "1" || consoleEnv === "true";
177
+ }
178
+ function ensureLogDir() {
179
+ if (logDirEnsured)
180
+ return;
181
+ try {
182
+ if (!fs.existsSync(LOG_DIR)) {
183
+ fs.mkdirSync(LOG_DIR, { recursive: true });
184
+ }
185
+ logDirEnsured = true;
186
+ } catch {
187
+ logFileError = true;
188
+ }
189
+ }
190
+ function rotateIfNeeded() {
191
+ try {
192
+ const stats = fs.statSync(LOG_FILE);
193
+ if (stats.size >= MAX_LOG_SIZE) {
194
+ const backupFile = LOG_FILE + ".1";
195
+ fs.renameSync(LOG_FILE, backupFile);
196
+ }
197
+ } catch {}
198
+ }
199
+ function writeToFile(message) {
200
+ if (logFileError)
201
+ return;
202
+ ensureLogDir();
203
+ if (logFileError)
204
+ return;
205
+ try {
206
+ rotateIfNeeded();
207
+ const timestamp = new Date().toISOString();
208
+ fs.appendFileSync(LOG_FILE, `${timestamp} ${message}
209
+ `);
210
+ } catch {
211
+ if (!logFileError) {
212
+ logFileError = true;
213
+ console.error(`[cursor-acp] Failed to write logs. Using: ${LOG_FILE}`);
214
+ }
215
+ }
216
+ }
217
+ function createLogger(component) {
218
+ return {
219
+ debug: (message, data) => {
220
+ if (!shouldLog("debug"))
221
+ return;
222
+ const formatted = formatMessage("debug", component, message, data);
223
+ writeToFile(formatted);
224
+ if (isConsoleEnabled())
225
+ console.error(formatted);
226
+ },
227
+ info: (message, data) => {
228
+ if (!shouldLog("info"))
229
+ return;
230
+ const formatted = formatMessage("info", component, message, data);
231
+ writeToFile(formatted);
232
+ if (isConsoleEnabled())
233
+ console.error(formatted);
234
+ },
235
+ warn: (message, data) => {
236
+ if (!shouldLog("warn"))
237
+ return;
238
+ const formatted = formatMessage("warn", component, message, data);
239
+ writeToFile(formatted);
240
+ if (isConsoleEnabled())
241
+ console.error(formatted);
242
+ },
243
+ error: (message, data) => {
244
+ if (!shouldLog("error"))
245
+ return;
246
+ const formatted = formatMessage("error", component, message, data);
247
+ writeToFile(formatted);
248
+ if (isConsoleEnabled())
249
+ console.error(formatted);
250
+ }
251
+ };
252
+ }
253
+ var LOG_DIR, LOG_FILE, MAX_LOG_SIZE, LEVEL_PRIORITY, logDirEnsured = false, logFileError = false;
254
+ var init_logger = __esm(() => {
255
+ LOG_DIR = path.join(os.homedir(), ".opencode-cursor");
256
+ LOG_FILE = path.join(LOG_DIR, "plugin.log");
257
+ MAX_LOG_SIZE = 5 * 1024 * 1024;
258
+ LEVEL_PRIORITY = {
259
+ debug: 0,
260
+ info: 1,
261
+ warn: 2,
262
+ error: 3
263
+ };
264
+ });
265
+
266
+ // src/utils/binary.ts
267
+ import { existsSync as fsExistsSync } from "fs";
268
+ import * as pathModule from "path";
269
+ import { homedir as osHomedir } from "os";
270
+ function resolveCursorAgentBinary(deps = {}) {
271
+ const platform = deps.platform ?? process.platform;
272
+ const env = deps.env ?? process.env;
273
+ const checkExists = deps.existsSync ?? fsExistsSync;
274
+ const home = (deps.homedir ?? osHomedir)();
275
+ const envOverride = env.CURSOR_AGENT_EXECUTABLE;
276
+ if (envOverride && envOverride.length > 0) {
277
+ return envOverride;
278
+ }
279
+ if (platform === "win32") {
280
+ const pathJoin = pathModule.win32.join;
281
+ const localAppData = env.LOCALAPPDATA ?? pathJoin(home, "AppData", "Local");
282
+ const knownPath = pathJoin(localAppData, "cursor-agent", "cursor-agent.cmd");
283
+ if (checkExists(knownPath)) {
284
+ return knownPath;
285
+ }
286
+ log.warn("cursor-agent not found at known Windows path, falling back to PATH", { checkedPath: knownPath });
287
+ return "cursor-agent.cmd";
288
+ }
289
+ const knownPaths = [
290
+ pathModule.join(home, ".cursor-agent", "cursor-agent"),
291
+ "/usr/local/bin/cursor-agent"
292
+ ];
293
+ for (const p of knownPaths) {
294
+ if (checkExists(p)) {
295
+ return p;
296
+ }
297
+ }
298
+ log.warn("cursor-agent not found at known paths, falling back to PATH", { checkedPaths: knownPaths });
299
+ return "cursor-agent";
300
+ }
301
+ var log;
302
+ var init_binary = __esm(() => {
303
+ init_logger();
304
+ log = createLogger("binary");
305
+ });
306
+
140
307
  // src/cli/model-discovery.ts
141
308
  import { execFileSync } from "child_process";
142
309
  function parseCursorModelsOutput(output) {
@@ -160,9 +327,9 @@ function parseCursorModelsOutput(output) {
160
327
  return models;
161
328
  }
162
329
  function discoverModelsFromCursorAgent() {
163
- const raw = execFileSync("cursor-agent", ["models"], {
330
+ const raw = execFileSync(resolveCursorAgentBinary(), ["models"], {
164
331
  encoding: "utf8",
165
- killSignal: "SIGTERM",
332
+ ...process.platform !== "win32" && { killSignal: "SIGTERM" },
166
333
  stdio: ["ignore", "pipe", "pipe"],
167
334
  timeout: MODEL_DISCOVERY_TIMEOUT_MS
168
335
  });
@@ -197,13 +364,15 @@ function fallbackModels() {
197
364
  ];
198
365
  }
199
366
  var MODEL_DISCOVERY_TIMEOUT_MS = 5000;
200
- var init_model_discovery = () => {};
367
+ var init_model_discovery = __esm(() => {
368
+ init_binary();
369
+ });
201
370
 
202
371
  // src/cli/discover.ts
203
372
  init_model_discovery();
204
- import { readFileSync, writeFileSync, existsSync } from "fs";
205
- import { join } from "path";
206
- import { homedir } from "os";
373
+ import { readFileSync, writeFileSync, existsSync as existsSync2 } from "fs";
374
+ import { join as join3 } from "path";
375
+ import { homedir as homedir2 } from "os";
207
376
  async function main() {
208
377
  console.log("Discovering Cursor models...");
209
378
  let models = fallbackModels();
@@ -217,8 +386,8 @@ async function main() {
217
386
  for (const model of models) {
218
387
  console.log(` - ${model.id}: ${model.name}`);
219
388
  }
220
- const configPath = join(homedir(), ".config/opencode/opencode.json");
221
- if (!existsSync(configPath)) {
389
+ const configPath = join3(homedir2(), ".config/opencode/opencode.json");
390
+ if (!existsSync2(configPath)) {
222
391
  console.error(`Config not found: ${configPath}`);
223
392
  process.exit(1);
224
393
  }
@@ -72,6 +72,11 @@ function isCursorPluginEnabledInConfig(config) {
72
72
  return true;
73
73
  }
74
74
  const configObject = config;
75
+ if (configObject.provider && typeof configObject.provider === "object") {
76
+ if (CURSOR_PROVIDER_ID in configObject.provider) {
77
+ return true;
78
+ }
79
+ }
75
80
  if (Array.isArray(configObject.plugin)) {
76
81
  return configObject.plugin.some((entry) => matchesPlugin(entry));
77
82
  }
@@ -93,7 +98,7 @@ function shouldEnableCursorPlugin(env = process.env) {
93
98
  return {
94
99
  enabled,
95
100
  configPath,
96
- reason: enabled ? "enabled_in_plugin_array_or_legacy" : "disabled_in_plugin_array"
101
+ reason: enabled ? "enabled" : "disabled_in_plugin_array"
97
102
  };
98
103
  } catch {
99
104
  return {