@rama_nigg/open-cursor 2.3.0 → 2.3.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/README.md +92 -28
- package/dist/cli/opencode-cursor.js +44 -4
- package/package.json +1 -1
- package/src/cli/opencode-cursor.ts +45 -4
package/README.md
CHANGED
|
@@ -9,27 +9,63 @@ No prompt limits. No broken streams. Full thinking + tool support in Opencode. Y
|
|
|
9
9
|
|
|
10
10
|
## Installation
|
|
11
11
|
|
|
12
|
-
**Option A
|
|
12
|
+
**Option A — Add to opencode.json (Recommended)**
|
|
13
|
+
|
|
14
|
+
The simplest approach—just add the npm package to your OpenCode config:
|
|
15
|
+
|
|
16
|
+
```json
|
|
17
|
+
{
|
|
18
|
+
"plugin": ["@rama_nigg/open-cursor@latest"],
|
|
19
|
+
"provider": {
|
|
20
|
+
"cursor-acp": {
|
|
21
|
+
"name": "Cursor ACP",
|
|
22
|
+
"npm": "@ai-sdk/openai-compatible",
|
|
23
|
+
"models": {
|
|
24
|
+
"cursor-acp/claude-sonnet": { "name": "Claude Sonnet" },
|
|
25
|
+
"cursor-acp/gpt-4o": { "name": "GPT-4o" },
|
|
26
|
+
"cursor-acp/gemini-2.5-pro": { "name": "Gemini 2.5 Pro" },
|
|
27
|
+
"cursor-acp/cursor-small": { "name": "Cursor Small" }
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
> **Note:** The model identifiers shown above are examples. Run `cursor-agent models` after authentication to see the full list of models available for your account.
|
|
35
|
+
|
|
36
|
+
After authenticating with `cursor-agent login`, run `cursor-agent models` to see the full model list, or use one of the automated installers below to auto-discover models.
|
|
37
|
+
|
|
38
|
+
**Prerequisites:** OpenCode and cursor-agent required. Install cursor-agent with: `curl -fsSL https://cursor.com/install | bash && cursor-agent login`. OpenCode's Bun runtime resolves the npm package automatically.
|
|
39
|
+
|
|
40
|
+
**Option B — One-line installer (curl)**
|
|
13
41
|
|
|
14
42
|
```bash
|
|
15
43
|
curl -fsSL https://raw.githubusercontent.com/Nomadcxx/opencode-cursor/main/install.sh | bash
|
|
16
44
|
```
|
|
17
45
|
|
|
18
|
-
|
|
46
|
+
This automated installer detects your environment and chooses the best installation method.
|
|
47
|
+
|
|
48
|
+
**Option C — npm global install + CLI symlink**
|
|
49
|
+
|
|
50
|
+
Installs globally via npm and creates a symlink in the OpenCode plugin directory:
|
|
19
51
|
|
|
20
52
|
```bash
|
|
21
53
|
npm install -g @rama_nigg/open-cursor
|
|
22
54
|
open-cursor install
|
|
23
55
|
```
|
|
24
56
|
|
|
57
|
+
The `open-cursor install` command creates a symlink at `~/.config/opencode/plugin/cursor-acp.js` pointing to the npm package's entry file.
|
|
58
|
+
|
|
25
59
|
Upgrade later with:
|
|
26
60
|
|
|
27
61
|
```bash
|
|
28
62
|
npm update -g @rama_nigg/open-cursor
|
|
29
|
-
|
|
63
|
+
# Symlink automatically uses updated files
|
|
30
64
|
```
|
|
31
65
|
|
|
32
|
-
**Option
|
|
66
|
+
**Option D — Go TUI installer**
|
|
67
|
+
|
|
68
|
+
Interactive visual installer with dual-mode support (quick install or build from source):
|
|
33
69
|
|
|
34
70
|
```bash
|
|
35
71
|
git clone https://github.com/Nomadcxx/opencode-cursor.git
|
|
@@ -37,46 +73,55 @@ cd opencode-cursor
|
|
|
37
73
|
go build -o ./installer ./cmd/installer && ./installer
|
|
38
74
|
```
|
|
39
75
|
|
|
40
|
-
**Option
|
|
76
|
+
**Option E — LLM instructions**
|
|
41
77
|
|
|
42
78
|
Paste this into any LLM agent (Claude Code, OpenCode, Cursor, etc.):
|
|
43
79
|
|
|
44
80
|
```
|
|
45
|
-
Install the cursor-acp plugin for OpenCode:
|
|
46
|
-
|
|
47
|
-
1.
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
81
|
+
Install the cursor-acp plugin for OpenCode using the npm package approach:
|
|
82
|
+
|
|
83
|
+
1. Edit your opencode.json config file (usually at ~/.config/opencode/opencode.json)
|
|
84
|
+
2. Add "@rama_nigg/open-cursor@latest" to the plugin array
|
|
85
|
+
3. Add the cursor-acp provider configuration with models
|
|
86
|
+
4. Restart OpenCode
|
|
87
|
+
|
|
88
|
+
Example configuration:
|
|
89
|
+
{
|
|
90
|
+
"plugin": ["@rama_nigg/open-cursor@latest"],
|
|
91
|
+
"provider": {
|
|
92
|
+
"cursor-acp": {
|
|
93
|
+
"name": "Cursor ACP",
|
|
94
|
+
"npm": "@ai-sdk/openai-compatible",
|
|
95
|
+
"models": {
|
|
96
|
+
"cursor-acp/claude-sonnet": { "name": "Claude Sonnet" }
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
5. Authenticate: cursor-agent login
|
|
103
|
+
6. Verify: opencode models | grep cursor-acp
|
|
61
104
|
```
|
|
62
105
|
|
|
63
|
-
**Option
|
|
106
|
+
**Option F — Manual (from source)**
|
|
107
|
+
|
|
108
|
+
For developers and contributors who want full control:
|
|
64
109
|
|
|
65
110
|
```bash
|
|
111
|
+
git clone https://github.com/Nomadcxx/opencode-cursor.git
|
|
112
|
+
cd opencode-cursor
|
|
66
113
|
bun install && bun run build
|
|
67
114
|
mkdir -p ~/.config/opencode/plugin
|
|
68
115
|
ln -sf $(pwd)/dist/plugin-entry.js ~/.config/opencode/plugin/cursor-acp.js
|
|
69
116
|
```
|
|
70
117
|
|
|
71
|
-
The installers handle
|
|
72
|
-
|
|
73
|
-
Easiest way is to run the sync script, which populates everything for you:
|
|
118
|
+
The automated installers handle configuration automatically. For manual installs, run the sync script to populate models:
|
|
74
119
|
|
|
75
120
|
```bash
|
|
76
121
|
./scripts/sync-models.sh
|
|
77
122
|
```
|
|
78
123
|
|
|
79
|
-
Or
|
|
124
|
+
Or configure manually by adding this to `~/.config/opencode/opencode.json` (then run `./scripts/sync-models.sh` to populate models):
|
|
80
125
|
|
|
81
126
|
```json
|
|
82
127
|
{
|
|
@@ -126,6 +171,22 @@ Or if you'd rather do it by hand, add this to `~/.config/opencode/opencode.json`
|
|
|
126
171
|
}
|
|
127
172
|
```
|
|
128
173
|
|
|
174
|
+
### Plugin Configuration Reference
|
|
175
|
+
|
|
176
|
+
Depending on your installation method, use the appropriate plugin identifier:
|
|
177
|
+
|
|
178
|
+
**npm package (recommended for production):**
|
|
179
|
+
```json
|
|
180
|
+
"plugin": ["@rama_nigg/open-cursor@latest"]
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
**Local build (for development):**
|
|
184
|
+
```json
|
|
185
|
+
"plugin": ["cursor-acp"]
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
Both approaches work—the npm package is resolved automatically by OpenCode's Bun runtime, while the local build requires the symlink setup shown in Option F above.
|
|
189
|
+
|
|
129
190
|
## Authentication
|
|
130
191
|
|
|
131
192
|
### Option 1: Via OpenCode (Recommended)
|
|
@@ -206,12 +267,15 @@ Detailed architecture: [docs/architecture/runtime-tool-loop.md](docs/architectur
|
|
|
206
267
|
|
|
207
268
|
## Prerequisites
|
|
208
269
|
|
|
270
|
+
**For Option A (npm-direct):** [OpenCode](https://opencode.ai/) and [cursor-agent](https://cursor.com/) required. The npm package is resolved by OpenCode's Bun runtime, but cursor-agent handles the actual API communication.
|
|
271
|
+
|
|
272
|
+
**For Options B-F:**
|
|
209
273
|
- [Bun](https://bun.sh/)
|
|
210
274
|
- [cursor-agent](https://cursor.com/) - `curl -fsSL https://cursor.com/install | bash`
|
|
211
275
|
|
|
212
|
-
**Option
|
|
276
|
+
**Option B (one-line install):** If Go is installed, the script runs the TUI installer; otherwise it performs a shell-only install (OpenCode + cursor-agent required). For syncing models without the TUI, install [jq](https://jq.org/) or run `./scripts/sync-models.sh` after install.
|
|
213
277
|
|
|
214
|
-
**Option
|
|
278
|
+
**Option D (TUI installer):** Go 1.21+ required to build the installer.
|
|
215
279
|
|
|
216
280
|
## Features
|
|
217
281
|
|
|
@@ -236,9 +236,24 @@ function checkOpenCode() {
|
|
|
236
236
|
};
|
|
237
237
|
}
|
|
238
238
|
}
|
|
239
|
-
function
|
|
239
|
+
function isNpmDirectInstalled(config) {
|
|
240
|
+
if (!config || typeof config !== "object")
|
|
241
|
+
return false;
|
|
242
|
+
const plugins = config.plugin;
|
|
243
|
+
if (!Array.isArray(plugins))
|
|
244
|
+
return false;
|
|
245
|
+
return plugins.some((p) => typeof p === "string" && p.startsWith(NPM_PACKAGE_PREFIX));
|
|
246
|
+
}
|
|
247
|
+
function checkPluginFile(pluginPath, config) {
|
|
240
248
|
try {
|
|
241
249
|
if (!existsSync(pluginPath)) {
|
|
250
|
+
if (isNpmDirectInstalled(config)) {
|
|
251
|
+
return {
|
|
252
|
+
name: "Plugin file",
|
|
253
|
+
passed: true,
|
|
254
|
+
message: "Installed via npm package (no symlink needed)"
|
|
255
|
+
};
|
|
256
|
+
}
|
|
242
257
|
return {
|
|
243
258
|
name: "Plugin file",
|
|
244
259
|
passed: false,
|
|
@@ -308,17 +323,24 @@ function checkAiSdk(opencodeDir) {
|
|
|
308
323
|
}
|
|
309
324
|
function runDoctorChecks(configPath, pluginPath) {
|
|
310
325
|
const opencodeDir = dirname(configPath);
|
|
326
|
+
let config;
|
|
327
|
+
try {
|
|
328
|
+
config = readConfig(configPath);
|
|
329
|
+
} catch {
|
|
330
|
+
config = undefined;
|
|
331
|
+
}
|
|
311
332
|
return [
|
|
312
333
|
checkBun(),
|
|
313
334
|
checkCursorAgent(),
|
|
314
335
|
checkCursorAgentLogin(),
|
|
315
336
|
checkOpenCode(),
|
|
316
|
-
checkPluginFile(pluginPath),
|
|
337
|
+
checkPluginFile(pluginPath, config),
|
|
317
338
|
checkProviderConfig(configPath),
|
|
318
339
|
checkAiSdk(opencodeDir)
|
|
319
340
|
];
|
|
320
341
|
}
|
|
321
342
|
var PROVIDER_ID = "cursor-acp";
|
|
343
|
+
var NPM_PACKAGE_PREFIX = "@rama_nigg/open-cursor";
|
|
322
344
|
var DEFAULT_BASE_URL = "http://127.0.0.1:32124/v1";
|
|
323
345
|
function printHelp() {
|
|
324
346
|
const binName = basename(process.argv[1] || "open-cursor");
|
|
@@ -527,13 +549,20 @@ function commandSyncModels(options) {
|
|
|
527
549
|
console.log(`Models synced: ${models.length}`);
|
|
528
550
|
console.log(`Config path: ${configPath}`);
|
|
529
551
|
}
|
|
552
|
+
var NPM_PACKAGE = "@rama_nigg/open-cursor";
|
|
530
553
|
function commandUninstall(options) {
|
|
531
554
|
const { configPath, pluginPath } = resolvePaths(options);
|
|
532
555
|
rmSync(pluginPath, { force: true });
|
|
533
556
|
if (existsSync(configPath)) {
|
|
534
557
|
const config = readConfig(configPath);
|
|
535
558
|
if (Array.isArray(config.plugin)) {
|
|
536
|
-
config.plugin = config.plugin.filter((name) =>
|
|
559
|
+
config.plugin = config.plugin.filter((name) => {
|
|
560
|
+
if (name === PROVIDER_ID)
|
|
561
|
+
return false;
|
|
562
|
+
if (typeof name === "string" && name.startsWith(NPM_PACKAGE))
|
|
563
|
+
return false;
|
|
564
|
+
return true;
|
|
565
|
+
});
|
|
537
566
|
}
|
|
538
567
|
if (config.provider && typeof config.provider === "object") {
|
|
539
568
|
delete config.provider[PROVIDER_ID];
|
|
@@ -565,22 +594,32 @@ function getStatusResult(configPath, pluginPath) {
|
|
|
565
594
|
pluginTarget = undefined;
|
|
566
595
|
}
|
|
567
596
|
}
|
|
597
|
+
let config;
|
|
568
598
|
let providerEnabled = false;
|
|
569
599
|
let baseUrl = "http://127.0.0.1:32124/v1";
|
|
570
600
|
let modelCount = 0;
|
|
571
601
|
if (existsSync(configPath)) {
|
|
572
|
-
|
|
602
|
+
config = readConfig(configPath);
|
|
573
603
|
const provider = config.provider?.["cursor-acp"];
|
|
574
604
|
providerEnabled = !!provider;
|
|
575
605
|
if (provider?.options?.baseURL) {
|
|
576
606
|
baseUrl = provider.options.baseURL;
|
|
577
607
|
}
|
|
578
608
|
modelCount = Object.keys(provider?.models || {}).length;
|
|
609
|
+
} else {
|
|
610
|
+
config = undefined;
|
|
579
611
|
}
|
|
580
612
|
const opencodeDir = dirname(configPath);
|
|
581
613
|
const sdkPath = join(opencodeDir, "node_modules", "@ai-sdk", "openai-compatible");
|
|
582
614
|
const aiSdkInstalled = existsSync(sdkPath);
|
|
615
|
+
let installMethod = "none";
|
|
616
|
+
if (pluginType !== "missing") {
|
|
617
|
+
installMethod = "symlink";
|
|
618
|
+
} else if (isNpmDirectInstalled(config)) {
|
|
619
|
+
installMethod = "npm-direct";
|
|
620
|
+
}
|
|
583
621
|
return {
|
|
622
|
+
installMethod,
|
|
584
623
|
plugin: {
|
|
585
624
|
path: pluginPath,
|
|
586
625
|
type: pluginType,
|
|
@@ -615,6 +654,7 @@ function commandStatus(options) {
|
|
|
615
654
|
} else {
|
|
616
655
|
console.log(` Type: missing`);
|
|
617
656
|
}
|
|
657
|
+
console.log(` Install method: ${result.installMethod}`);
|
|
618
658
|
console.log("");
|
|
619
659
|
console.log("Provider");
|
|
620
660
|
console.log(` Config: ${result.provider.configPath}`);
|
package/package.json
CHANGED
|
@@ -37,6 +37,7 @@ type CheckResult = {
|
|
|
37
37
|
};
|
|
38
38
|
|
|
39
39
|
type StatusResult = {
|
|
40
|
+
installMethod: "symlink" | "npm-direct" | "none";
|
|
40
41
|
plugin: {
|
|
41
42
|
path: string;
|
|
42
43
|
type: "symlink" | "file" | "missing";
|
|
@@ -110,9 +111,23 @@ function checkOpenCode(): CheckResult {
|
|
|
110
111
|
}
|
|
111
112
|
}
|
|
112
113
|
|
|
113
|
-
function
|
|
114
|
+
function isNpmDirectInstalled(config: unknown): boolean {
|
|
115
|
+
if (!config || typeof config !== "object") return false;
|
|
116
|
+
const plugins = (config as Record<string, unknown>).plugin;
|
|
117
|
+
if (!Array.isArray(plugins)) return false;
|
|
118
|
+
return plugins.some((p) => typeof p === "string" && p.startsWith(NPM_PACKAGE_PREFIX));
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function checkPluginFile(pluginPath: string, config: unknown): CheckResult {
|
|
114
122
|
try {
|
|
115
123
|
if (!existsSync(pluginPath)) {
|
|
124
|
+
if (isNpmDirectInstalled(config)) {
|
|
125
|
+
return {
|
|
126
|
+
name: "Plugin file",
|
|
127
|
+
passed: true,
|
|
128
|
+
message: "Installed via npm package (no symlink needed)",
|
|
129
|
+
};
|
|
130
|
+
}
|
|
116
131
|
return {
|
|
117
132
|
name: "Plugin file",
|
|
118
133
|
passed: false,
|
|
@@ -185,12 +200,18 @@ function checkAiSdk(opencodeDir: string): CheckResult {
|
|
|
185
200
|
|
|
186
201
|
export function runDoctorChecks(configPath: string, pluginPath: string): CheckResult[] {
|
|
187
202
|
const opencodeDir = dirname(configPath);
|
|
203
|
+
let config: unknown;
|
|
204
|
+
try {
|
|
205
|
+
config = readConfig(configPath);
|
|
206
|
+
} catch {
|
|
207
|
+
config = undefined;
|
|
208
|
+
}
|
|
188
209
|
return [
|
|
189
210
|
checkBun(),
|
|
190
211
|
checkCursorAgent(),
|
|
191
212
|
checkCursorAgentLogin(),
|
|
192
213
|
checkOpenCode(),
|
|
193
|
-
checkPluginFile(pluginPath),
|
|
214
|
+
checkPluginFile(pluginPath, config),
|
|
194
215
|
checkProviderConfig(configPath),
|
|
195
216
|
checkAiSdk(opencodeDir),
|
|
196
217
|
];
|
|
@@ -209,6 +230,7 @@ type Options = {
|
|
|
209
230
|
};
|
|
210
231
|
|
|
211
232
|
const PROVIDER_ID = "cursor-acp";
|
|
233
|
+
const NPM_PACKAGE_PREFIX = "@rama_nigg/open-cursor";
|
|
212
234
|
const DEFAULT_BASE_URL = "http://127.0.0.1:32124/v1";
|
|
213
235
|
|
|
214
236
|
function printHelp() {
|
|
@@ -443,6 +465,8 @@ function commandSyncModels(options: Options) {
|
|
|
443
465
|
console.log(`Config path: ${configPath}`);
|
|
444
466
|
}
|
|
445
467
|
|
|
468
|
+
const NPM_PACKAGE = "@rama_nigg/open-cursor";
|
|
469
|
+
|
|
446
470
|
function commandUninstall(options: Options) {
|
|
447
471
|
const { configPath, pluginPath } = resolvePaths(options);
|
|
448
472
|
rmSync(pluginPath, { force: true });
|
|
@@ -450,7 +474,12 @@ function commandUninstall(options: Options) {
|
|
|
450
474
|
if (existsSync(configPath)) {
|
|
451
475
|
const config = readConfig(configPath);
|
|
452
476
|
if (Array.isArray(config.plugin)) {
|
|
453
|
-
|
|
477
|
+
// Remove both cursor-acp (symlink) and @rama_nigg/open-cursor (npm-direct) entries
|
|
478
|
+
config.plugin = config.plugin.filter((name: string) => {
|
|
479
|
+
if (name === PROVIDER_ID) return false;
|
|
480
|
+
if (typeof name === "string" && name.startsWith(NPM_PACKAGE)) return false;
|
|
481
|
+
return true;
|
|
482
|
+
});
|
|
454
483
|
}
|
|
455
484
|
if (config.provider && typeof config.provider === "object") {
|
|
456
485
|
delete config.provider[PROVIDER_ID];
|
|
@@ -487,17 +516,20 @@ export function getStatusResult(configPath: string, pluginPath: string): StatusR
|
|
|
487
516
|
}
|
|
488
517
|
|
|
489
518
|
// Provider
|
|
519
|
+
let config: any;
|
|
490
520
|
let providerEnabled = false;
|
|
491
521
|
let baseUrl = "http://127.0.0.1:32124/v1";
|
|
492
522
|
let modelCount = 0;
|
|
493
523
|
if (existsSync(configPath)) {
|
|
494
|
-
|
|
524
|
+
config = readConfig(configPath);
|
|
495
525
|
const provider = config.provider?.["cursor-acp"];
|
|
496
526
|
providerEnabled = !!provider;
|
|
497
527
|
if (provider?.options?.baseURL) {
|
|
498
528
|
baseUrl = provider.options.baseURL;
|
|
499
529
|
}
|
|
500
530
|
modelCount = Object.keys(provider?.models || {}).length;
|
|
531
|
+
} else {
|
|
532
|
+
config = undefined;
|
|
501
533
|
}
|
|
502
534
|
|
|
503
535
|
// AI SDK
|
|
@@ -505,7 +537,15 @@ export function getStatusResult(configPath: string, pluginPath: string): StatusR
|
|
|
505
537
|
const sdkPath = join(opencodeDir, "node_modules", "@ai-sdk", "openai-compatible");
|
|
506
538
|
const aiSdkInstalled = existsSync(sdkPath);
|
|
507
539
|
|
|
540
|
+
let installMethod: "symlink" | "npm-direct" | "none" = "none";
|
|
541
|
+
if (pluginType !== "missing") {
|
|
542
|
+
installMethod = "symlink";
|
|
543
|
+
} else if (isNpmDirectInstalled(config)) {
|
|
544
|
+
installMethod = "npm-direct";
|
|
545
|
+
}
|
|
546
|
+
|
|
508
547
|
return {
|
|
548
|
+
installMethod,
|
|
509
549
|
plugin: {
|
|
510
550
|
path: pluginPath,
|
|
511
551
|
type: pluginType,
|
|
@@ -543,6 +583,7 @@ function commandStatus(options: Options) {
|
|
|
543
583
|
} else {
|
|
544
584
|
console.log(` Type: missing`);
|
|
545
585
|
}
|
|
586
|
+
console.log(` Install method: ${result.installMethod}`);
|
|
546
587
|
|
|
547
588
|
console.log("");
|
|
548
589
|
console.log("Provider");
|