@rama_nigg/open-cursor 2.1.5 → 2.1.7
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 +38 -66
- package/dist/cli/discover.js +0 -15
- package/dist/cli/opencode-cursor.js +262 -35
- package/dist/index.js +217 -53
- package/dist/plugin-entry.js +190 -49
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -15,8 +15,6 @@ No prompt limits. No broken streams. Full thinking + tool support in Opencode. Y
|
|
|
15
15
|
curl -fsSL https://raw.githubusercontent.com/Nomadcxx/opencode-cursor/main/install.sh | bash
|
|
16
16
|
```
|
|
17
17
|
|
|
18
|
-
Note: if `npm` is available, `install.sh` installs/upgrades `@rama_nigg/open-cursor` and configures OpenCode automatically. Otherwise it falls back to building from source.
|
|
19
|
-
|
|
20
18
|
**Option B: npm Package (Recommended)**
|
|
21
19
|
|
|
22
20
|
```bash
|
|
@@ -28,7 +26,7 @@ Upgrade later with:
|
|
|
28
26
|
|
|
29
27
|
```bash
|
|
30
28
|
npm update -g @rama_nigg/open-cursor
|
|
31
|
-
open-cursor
|
|
29
|
+
open-cursor install
|
|
32
30
|
```
|
|
33
31
|
|
|
34
32
|
**Option C: TUI Installer**
|
|
@@ -89,7 +87,39 @@ Or if you'd rather do it by hand, add this to `~/.config/opencode/opencode.json`
|
|
|
89
87
|
"npm": "@ai-sdk/openai-compatible",
|
|
90
88
|
"options": { "baseURL": "http://127.0.0.1:32124/v1" },
|
|
91
89
|
"models": {
|
|
92
|
-
"auto": { "name": "Auto" }
|
|
90
|
+
"auto": { "name": "Auto" },
|
|
91
|
+
"composer-1.5": { "name": "Composer 1.5" },
|
|
92
|
+
"composer-1": { "name": "Composer 1" },
|
|
93
|
+
"gpt-5.3-codex": { "name": "GPT-5.3 Codex" },
|
|
94
|
+
"gpt-5.3-codex-low": { "name": "GPT-5.3 Codex Low" },
|
|
95
|
+
"gpt-5.3-codex-high": { "name": "GPT-5.3 Codex High" },
|
|
96
|
+
"gpt-5.3-codex-xhigh": { "name": "GPT-5.3 Codex Extra High" },
|
|
97
|
+
"gpt-5.3-codex-fast": { "name": "GPT-5.3 Codex Fast" },
|
|
98
|
+
"gpt-5.3-codex-low-fast": { "name": "GPT-5.3 Codex Low Fast" },
|
|
99
|
+
"gpt-5.3-codex-high-fast": { "name": "GPT-5.3 Codex High Fast" },
|
|
100
|
+
"gpt-5.3-codex-xhigh-fast": { "name": "GPT-5.3 Codex Extra High Fast" },
|
|
101
|
+
"gpt-5.2": { "name": "GPT-5.2" },
|
|
102
|
+
"gpt-5.2-codex": { "name": "GPT-5.2 Codex" },
|
|
103
|
+
"gpt-5.2-codex-high": { "name": "GPT-5.2 Codex High" },
|
|
104
|
+
"gpt-5.2-codex-low": { "name": "GPT-5.2 Codex Low" },
|
|
105
|
+
"gpt-5.2-codex-xhigh": { "name": "GPT-5.2 Codex Extra High" },
|
|
106
|
+
"gpt-5.2-codex-fast": { "name": "GPT-5.2 Codex Fast" },
|
|
107
|
+
"gpt-5.2-codex-high-fast": { "name": "GPT-5.2 Codex High Fast" },
|
|
108
|
+
"gpt-5.2-codex-low-fast": { "name": "GPT-5.2 Codex Low Fast" },
|
|
109
|
+
"gpt-5.2-codex-xhigh-fast": { "name": "GPT-5.2 Codex Extra High Fast" },
|
|
110
|
+
"gpt-5.1-codex-max": { "name": "GPT-5.1 Codex Max" },
|
|
111
|
+
"gpt-5.1-codex-max-high": { "name": "GPT-5.1 Codex Max High" },
|
|
112
|
+
"opus-4.6-thinking": { "name": "Claude 4.6 Opus (Thinking)" },
|
|
113
|
+
"sonnet-4.5-thinking": { "name": "Claude 4.5 Sonnet (Thinking)" },
|
|
114
|
+
"gpt-5.2-high": { "name": "GPT-5.2 High" },
|
|
115
|
+
"opus-4.6": { "name": "Claude 4.6 Opus" },
|
|
116
|
+
"opus-4.5": { "name": "Claude 4.5 Opus" },
|
|
117
|
+
"opus-4.5-thinking": { "name": "Claude 4.5 Opus (Thinking)" },
|
|
118
|
+
"sonnet-4.5": { "name": "Claude 4.5 Sonnet" },
|
|
119
|
+
"gpt-5.1-high": { "name": "GPT-5.1 High" },
|
|
120
|
+
"gemini-3-pro": { "name": "Gemini 3 Pro" },
|
|
121
|
+
"gemini-3-flash": { "name": "Gemini 3 Flash" },
|
|
122
|
+
"grok": { "name": "Grok" }
|
|
93
123
|
}
|
|
94
124
|
}
|
|
95
125
|
}
|
|
@@ -131,35 +161,6 @@ opencode run "your prompt" --model cursor-acp/auto
|
|
|
131
161
|
opencode run "your prompt" --model cursor-acp/sonnet-4.5
|
|
132
162
|
```
|
|
133
163
|
|
|
134
|
-
If installed via npm, manage setup with:
|
|
135
|
-
|
|
136
|
-
```bash
|
|
137
|
-
open-cursor status
|
|
138
|
-
open-cursor sync-models
|
|
139
|
-
```
|
|
140
|
-
|
|
141
|
-
## Models
|
|
142
|
-
|
|
143
|
-
Models are pulled from `cursor-agent models` and written to your config during installation. If Cursor adds new models later:
|
|
144
|
-
|
|
145
|
-
```bash
|
|
146
|
-
open-cursor sync-models
|
|
147
|
-
```
|
|
148
|
-
|
|
149
|
-
Or, if you installed from source (no npm CLI):
|
|
150
|
-
|
|
151
|
-
```bash
|
|
152
|
-
./scripts/sync-models.sh
|
|
153
|
-
```
|
|
154
|
-
|
|
155
|
-
The proxy also exposes a `/v1/models` endpoint that fetches models in real-time:
|
|
156
|
-
|
|
157
|
-
```bash
|
|
158
|
-
curl http://127.0.0.1:32124/v1/models
|
|
159
|
-
```
|
|
160
|
-
|
|
161
|
-
Common models: `auto`, `composer-1.5`, `gpt-5.3-codex`, `opus-4.6-thinking`, `sonnet-4.5`, `gemini-3-pro`, `grok`
|
|
162
|
-
|
|
163
164
|
## Architecture
|
|
164
165
|
|
|
165
166
|
```mermaid
|
|
@@ -203,15 +204,6 @@ Detailed architecture: [docs/architecture/runtime-tool-loop.md](docs/architectur
|
|
|
203
204
|
| **Dependencies** | bun, cursor-agent | npm | bun, cursor-agent | Node.js 18+ |
|
|
204
205
|
| **Port** | 32124 | 18741 | 32123 | 4141 |
|
|
205
206
|
|
|
206
|
-
**Key advantages of cursor-acp:**
|
|
207
|
-
|
|
208
|
-
- Avoids E2BIG errors with large prompts (uses HTTP body, not CLI args)
|
|
209
|
-
- Structured error parsing with actionable suggestions
|
|
210
|
-
- Cross-platform (not locked to macOS Keychain)
|
|
211
|
-
- TUI installer for easy setup
|
|
212
|
-
- Native tool calling with 10 built-in tools, SDK/MCP executor support, and a skills/alias system
|
|
213
|
-
- Uses official cursor-agent CLI (more stable than reverse-engineering Connect-RPC)
|
|
214
|
-
|
|
215
207
|
## Prerequisites
|
|
216
208
|
|
|
217
209
|
- [Bun](https://bun.sh/)
|
|
@@ -254,10 +246,6 @@ Integration CI pins OpenCode-owned loop mode to deterministic settings:
|
|
|
254
246
|
- `CURSOR_ACP_FORWARD_TOOL_CALLS=false`
|
|
255
247
|
- `CURSOR_ACP_EMIT_TOOL_UPDATES=false`
|
|
256
248
|
|
|
257
|
-
## Publishing
|
|
258
|
-
|
|
259
|
-
For maintainers, release and npm publish steps are documented in [docs/PUBLISHING.md](docs/PUBLISHING.md).
|
|
260
|
-
|
|
261
249
|
## Troubleshooting
|
|
262
250
|
|
|
263
251
|
**"fetch() URL is invalid"** - Run `opencode auth login` without arguments
|
|
@@ -278,26 +266,10 @@ Common causes:
|
|
|
278
266
|
|
|
279
267
|
### Debug Logging
|
|
280
268
|
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
- `CURSOR_ACP_LOG_LEVEL=error` - Errors only
|
|
286
|
-
|
|
287
|
-
Provider-boundary rollout:
|
|
288
|
-
- Default: `CURSOR_ACP_PROVIDER_BOUNDARY=v1`
|
|
289
|
-
- Default: `CURSOR_ACP_PROVIDER_BOUNDARY_AUTOFALLBACK=true`
|
|
290
|
-
- `CURSOR_ACP_PROVIDER_BOUNDARY=legacy` - Original provider/runtime boundary behavior
|
|
291
|
-
- `CURSOR_ACP_PROVIDER_BOUNDARY=v1` - Shared boundary/interception path
|
|
292
|
-
- `CURSOR_ACP_PROVIDER_BOUNDARY_AUTOFALLBACK=false` - Disable fallback and keep strict `v1` behavior
|
|
293
|
-
- `CURSOR_ACP_TOOL_LOOP_MAX_REPEAT=3` - Max repeated failing tool-call fingerprints before guard termination (or fallback when enabled)
|
|
294
|
-
|
|
295
|
-
Auto-fallback trigger conditions:
|
|
296
|
-
- Only active when `CURSOR_ACP_PROVIDER_BOUNDARY=v1`
|
|
297
|
-
- Triggered when `v1` boundary extraction throws during tool-call interception
|
|
298
|
-
- Triggered when the tool-loop guard threshold is reached (same tool + arg shape + error class)
|
|
299
|
-
- Does not trigger for normal cases like disallowed tools or no tool call
|
|
300
|
-
- Does not trigger for unrelated runtime errors (for example, tool mapper/tool execution failures)
|
|
269
|
+
Enable verbose logs:
|
|
270
|
+
```bash
|
|
271
|
+
CURSOR_ACP_LOG_LEVEL=debug opencode run "your prompt" --model cursor-acp/auto
|
|
272
|
+
```
|
|
301
273
|
|
|
302
274
|
Disable log output entirely:
|
|
303
275
|
```bash
|
package/dist/cli/discover.js
CHANGED
|
@@ -1,21 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { createRequire } from "node:module";
|
|
3
|
-
var __create = Object.create;
|
|
4
|
-
var __getProtoOf = Object.getPrototypeOf;
|
|
5
3
|
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
4
|
var __export = (target, all) => {
|
|
20
5
|
for (var name in all)
|
|
21
6
|
__defProp(target, name, {
|
|
@@ -1,21 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { createRequire } from "node:module";
|
|
3
|
-
var __create = Object.create;
|
|
4
|
-
var __getProtoOf = Object.getPrototypeOf;
|
|
5
3
|
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
4
|
var __export = (target, all) => {
|
|
20
5
|
for (var name in all)
|
|
21
6
|
__defProp(target, name, {
|
|
@@ -177,18 +162,170 @@ function fallbackModels() {
|
|
|
177
162
|
}
|
|
178
163
|
|
|
179
164
|
// src/cli/opencode-cursor.ts
|
|
165
|
+
var BRANDING_HEADER = `
|
|
166
|
+
▄▄▄ ▄▄▄▄ ▄▄▄▄▄ ▄▄ ▄▄ ▄▄▄ ▄▄ ▄▄ ▄▄▄▄ ▄▄▄▄ ▄▄▄ ▄▄▄▄
|
|
167
|
+
██ ██ ██ ██ ██▄▄ ███▄██ ▄▄▄ ██ ▀▀ ██ ██ ██ ██ ██▄▄▄ ██ ██ ██ ██
|
|
168
|
+
▀█▄█▀ ██▀▀ ██▄▄▄ ██ ▀██ ▀█▄█▀ ▀█▄█▀ ██▀█▄ ▄▄▄█▀ ▀█▄█▀ ██▀█▄
|
|
169
|
+
`;
|
|
170
|
+
function getBrandingHeader() {
|
|
171
|
+
return BRANDING_HEADER.trim();
|
|
172
|
+
}
|
|
173
|
+
function checkBun() {
|
|
174
|
+
try {
|
|
175
|
+
const version = execFileSync2("bun", ["--version"], { encoding: "utf8" }).trim();
|
|
176
|
+
return { name: "bun", passed: true, message: `v${version}` };
|
|
177
|
+
} catch {
|
|
178
|
+
return {
|
|
179
|
+
name: "bun",
|
|
180
|
+
passed: false,
|
|
181
|
+
message: "not found - install with: curl -fsSL https://bun.sh/install | bash"
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
function checkCursorAgent() {
|
|
186
|
+
try {
|
|
187
|
+
const output = execFileSync2("cursor-agent", ["--version"], { encoding: "utf8" }).trim();
|
|
188
|
+
const version = output.split(`
|
|
189
|
+
`)[0] || "installed";
|
|
190
|
+
return { name: "cursor-agent", passed: true, message: version };
|
|
191
|
+
} catch {
|
|
192
|
+
return {
|
|
193
|
+
name: "cursor-agent",
|
|
194
|
+
passed: false,
|
|
195
|
+
message: "not found - install with: curl -fsS https://cursor.com/install | bash"
|
|
196
|
+
};
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
function checkCursorAgentLogin() {
|
|
200
|
+
try {
|
|
201
|
+
execFileSync2("cursor-agent", ["models"], { encoding: "utf8", stdio: ["ignore", "pipe", "pipe"] });
|
|
202
|
+
return { name: "cursor-agent login", passed: true, message: "logged in" };
|
|
203
|
+
} catch {
|
|
204
|
+
return {
|
|
205
|
+
name: "cursor-agent login",
|
|
206
|
+
passed: false,
|
|
207
|
+
message: "not logged in - run: cursor-agent login",
|
|
208
|
+
warning: true
|
|
209
|
+
};
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
function checkOpenCode() {
|
|
213
|
+
try {
|
|
214
|
+
const version = execFileSync2("opencode", ["--version"], { encoding: "utf8" }).trim();
|
|
215
|
+
return { name: "OpenCode", passed: true, message: version };
|
|
216
|
+
} catch {
|
|
217
|
+
return {
|
|
218
|
+
name: "OpenCode",
|
|
219
|
+
passed: false,
|
|
220
|
+
message: "not found - install with: curl -fsSL https://opencode.ai/install | bash"
|
|
221
|
+
};
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
function checkPluginFile(pluginPath) {
|
|
225
|
+
try {
|
|
226
|
+
if (!existsSync(pluginPath)) {
|
|
227
|
+
return {
|
|
228
|
+
name: "Plugin file",
|
|
229
|
+
passed: false,
|
|
230
|
+
message: "not found - run: open-cursor install"
|
|
231
|
+
};
|
|
232
|
+
}
|
|
233
|
+
const stat = lstatSync(pluginPath);
|
|
234
|
+
if (stat.isSymbolicLink()) {
|
|
235
|
+
const target = readFileSync(pluginPath, "utf8");
|
|
236
|
+
return { name: "Plugin file", passed: true, message: `symlink → ${target}` };
|
|
237
|
+
}
|
|
238
|
+
return { name: "Plugin file", passed: true, message: "file (copy)" };
|
|
239
|
+
} catch {
|
|
240
|
+
return {
|
|
241
|
+
name: "Plugin file",
|
|
242
|
+
passed: false,
|
|
243
|
+
message: "error reading plugin file"
|
|
244
|
+
};
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
function checkProviderConfig(configPath) {
|
|
248
|
+
try {
|
|
249
|
+
if (!existsSync(configPath)) {
|
|
250
|
+
return {
|
|
251
|
+
name: "Provider config",
|
|
252
|
+
passed: false,
|
|
253
|
+
message: "config not found - run: open-cursor install"
|
|
254
|
+
};
|
|
255
|
+
}
|
|
256
|
+
const config = readConfig(configPath);
|
|
257
|
+
const provider = config.provider?.["cursor-acp"];
|
|
258
|
+
if (!provider) {
|
|
259
|
+
return {
|
|
260
|
+
name: "Provider config",
|
|
261
|
+
passed: false,
|
|
262
|
+
message: "cursor-acp provider missing - run: open-cursor install"
|
|
263
|
+
};
|
|
264
|
+
}
|
|
265
|
+
const modelCount = Object.keys(provider.models || {}).length;
|
|
266
|
+
return { name: "Provider config", passed: true, message: `${modelCount} models` };
|
|
267
|
+
} catch {
|
|
268
|
+
return {
|
|
269
|
+
name: "Provider config",
|
|
270
|
+
passed: false,
|
|
271
|
+
message: "error reading config"
|
|
272
|
+
};
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
function checkAiSdk(opencodeDir) {
|
|
276
|
+
try {
|
|
277
|
+
const sdkPath = join(opencodeDir, "node_modules", "@ai-sdk", "openai-compatible");
|
|
278
|
+
if (existsSync(sdkPath)) {
|
|
279
|
+
return { name: "AI SDK", passed: true, message: "@ai-sdk/openai-compatible installed" };
|
|
280
|
+
}
|
|
281
|
+
return {
|
|
282
|
+
name: "AI SDK",
|
|
283
|
+
passed: false,
|
|
284
|
+
message: "not installed - run: open-cursor install"
|
|
285
|
+
};
|
|
286
|
+
} catch {
|
|
287
|
+
return {
|
|
288
|
+
name: "AI SDK",
|
|
289
|
+
passed: false,
|
|
290
|
+
message: "error checking AI SDK"
|
|
291
|
+
};
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
function runDoctorChecks(configPath, pluginPath) {
|
|
295
|
+
const opencodeDir = dirname(configPath);
|
|
296
|
+
return [
|
|
297
|
+
checkBun(),
|
|
298
|
+
checkCursorAgent(),
|
|
299
|
+
checkCursorAgentLogin(),
|
|
300
|
+
checkOpenCode(),
|
|
301
|
+
checkPluginFile(pluginPath),
|
|
302
|
+
checkProviderConfig(configPath),
|
|
303
|
+
checkAiSdk(opencodeDir)
|
|
304
|
+
];
|
|
305
|
+
}
|
|
180
306
|
var PROVIDER_ID = "cursor-acp";
|
|
181
307
|
var DEFAULT_BASE_URL = "http://127.0.0.1:32124/v1";
|
|
182
308
|
function printHelp() {
|
|
183
309
|
const binName = basename(process.argv[1] || "open-cursor");
|
|
310
|
+
console.log(getBrandingHeader());
|
|
184
311
|
console.log(`${binName}
|
|
185
312
|
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
313
|
+
Commands:
|
|
314
|
+
install Configure OpenCode for Cursor (idempotent, safe to re-run)
|
|
315
|
+
sync-models Refresh model list from cursor-agent
|
|
316
|
+
status Show current configuration state
|
|
317
|
+
doctor Diagnose common issues
|
|
318
|
+
uninstall Remove cursor-acp from OpenCode config
|
|
319
|
+
help Show this help message
|
|
320
|
+
|
|
321
|
+
Options:
|
|
322
|
+
--config <path> Path to opencode.json (default: ~/.config/opencode/opencode.json)
|
|
323
|
+
--plugin-dir <path> Path to plugin directory (default: ~/.config/opencode/plugin)
|
|
324
|
+
--base-url <url> Proxy base URL (default: http://127.0.0.1:32124/v1)
|
|
325
|
+
--copy Copy plugin instead of symlink
|
|
326
|
+
--skip-models Skip model sync during install
|
|
327
|
+
--no-backup Don't create config backup
|
|
328
|
+
--json Output in JSON format (status command only)
|
|
192
329
|
`);
|
|
193
330
|
}
|
|
194
331
|
function parseArgs(argv) {
|
|
@@ -212,6 +349,8 @@ function parseArgs(argv) {
|
|
|
212
349
|
} else if (arg === "--base-url" && rest[i + 1]) {
|
|
213
350
|
options.baseUrl = rest[i + 1];
|
|
214
351
|
i += 1;
|
|
352
|
+
} else if (arg === "--json") {
|
|
353
|
+
options.json = true;
|
|
215
354
|
} else {
|
|
216
355
|
throw new Error(`Unknown argument: ${arg}`);
|
|
217
356
|
}
|
|
@@ -219,13 +358,14 @@ function parseArgs(argv) {
|
|
|
219
358
|
return { command, options };
|
|
220
359
|
}
|
|
221
360
|
function normalizeCommand(value) {
|
|
222
|
-
switch ((value || "
|
|
361
|
+
switch ((value || "help").toLowerCase()) {
|
|
223
362
|
case "install":
|
|
224
363
|
case "sync-models":
|
|
225
364
|
case "uninstall":
|
|
226
365
|
case "status":
|
|
366
|
+
case "doctor":
|
|
227
367
|
case "help":
|
|
228
|
-
return value ? value.toLowerCase() : "
|
|
368
|
+
return value ? value.toLowerCase() : "help";
|
|
229
369
|
default:
|
|
230
370
|
throw new Error(`Unknown command: ${value}`);
|
|
231
371
|
}
|
|
@@ -377,21 +517,97 @@ function commandUninstall(options) {
|
|
|
377
517
|
console.log(`Removed plugin link: ${pluginPath}`);
|
|
378
518
|
console.log(`Removed provider "${PROVIDER_ID}" from ${configPath}`);
|
|
379
519
|
}
|
|
380
|
-
function
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
520
|
+
function getStatusResult(configPath, pluginPath) {
|
|
521
|
+
let pluginType = "missing";
|
|
522
|
+
let pluginTarget;
|
|
523
|
+
if (existsSync(pluginPath)) {
|
|
524
|
+
const stat = lstatSync(pluginPath);
|
|
525
|
+
pluginType = stat.isSymbolicLink() ? "symlink" : "file";
|
|
526
|
+
if (pluginType === "symlink") {
|
|
527
|
+
try {
|
|
528
|
+
pluginTarget = readFileSync(pluginPath, "utf8");
|
|
529
|
+
} catch {
|
|
530
|
+
pluginTarget = undefined;
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
let providerEnabled = false;
|
|
535
|
+
let baseUrl = "http://127.0.0.1:32124/v1";
|
|
536
|
+
let modelCount = 0;
|
|
386
537
|
if (existsSync(configPath)) {
|
|
387
538
|
const config = readConfig(configPath);
|
|
388
|
-
|
|
389
|
-
|
|
539
|
+
const provider = config.provider?.["cursor-acp"];
|
|
540
|
+
providerEnabled = !!provider;
|
|
541
|
+
if (provider?.options?.baseURL) {
|
|
542
|
+
baseUrl = provider.options.baseURL;
|
|
543
|
+
}
|
|
544
|
+
modelCount = Object.keys(provider?.models || {}).length;
|
|
545
|
+
}
|
|
546
|
+
const opencodeDir = dirname(configPath);
|
|
547
|
+
const sdkPath = join(opencodeDir, "node_modules", "@ai-sdk", "openai-compatible");
|
|
548
|
+
const aiSdkInstalled = existsSync(sdkPath);
|
|
549
|
+
return {
|
|
550
|
+
plugin: {
|
|
551
|
+
path: pluginPath,
|
|
552
|
+
type: pluginType,
|
|
553
|
+
target: pluginTarget
|
|
554
|
+
},
|
|
555
|
+
provider: {
|
|
556
|
+
configPath,
|
|
557
|
+
name: "cursor-acp",
|
|
558
|
+
enabled: providerEnabled,
|
|
559
|
+
baseUrl,
|
|
560
|
+
modelCount
|
|
561
|
+
},
|
|
562
|
+
aiSdk: {
|
|
563
|
+
installed: aiSdkInstalled
|
|
564
|
+
}
|
|
565
|
+
};
|
|
566
|
+
}
|
|
567
|
+
function commandStatus(options) {
|
|
568
|
+
const { configPath, pluginPath } = resolvePaths(options);
|
|
569
|
+
const result = getStatusResult(configPath, pluginPath);
|
|
570
|
+
if (options.json) {
|
|
571
|
+
console.log(JSON.stringify(result, null, 2));
|
|
572
|
+
return;
|
|
573
|
+
}
|
|
574
|
+
console.log("");
|
|
575
|
+
console.log("Plugin");
|
|
576
|
+
console.log(` Path: ${result.plugin.path}`);
|
|
577
|
+
if (result.plugin.type === "symlink" && result.plugin.target) {
|
|
578
|
+
console.log(` Type: symlink → ${result.plugin.target}`);
|
|
579
|
+
} else if (result.plugin.type === "file") {
|
|
580
|
+
console.log(` Type: file (copy)`);
|
|
581
|
+
} else {
|
|
582
|
+
console.log(` Type: missing`);
|
|
583
|
+
}
|
|
584
|
+
console.log("");
|
|
585
|
+
console.log("Provider");
|
|
586
|
+
console.log(` Config: ${result.provider.configPath}`);
|
|
587
|
+
console.log(` Name: ${result.provider.name}`);
|
|
588
|
+
console.log(` Enabled: ${result.provider.enabled ? "yes" : "no"}`);
|
|
589
|
+
console.log(` Base URL: ${result.provider.baseUrl}`);
|
|
590
|
+
console.log(` Models: ${result.provider.modelCount}`);
|
|
591
|
+
console.log("");
|
|
592
|
+
console.log("AI SDK");
|
|
593
|
+
console.log(` @ai-sdk/openai-compatible: ${result.aiSdk.installed ? "installed" : "not installed"}`);
|
|
594
|
+
}
|
|
595
|
+
function commandDoctor(options) {
|
|
596
|
+
const { configPath, pluginPath } = resolvePaths(options);
|
|
597
|
+
const checks = runDoctorChecks(configPath, pluginPath);
|
|
598
|
+
console.log("");
|
|
599
|
+
for (const check of checks) {
|
|
600
|
+
const symbol = check.passed ? "✓" : check.warning ? "⚠" : "✗";
|
|
601
|
+
const color = check.passed ? "\x1B[32m" : check.warning ? "\x1B[33m" : "\x1B[31m";
|
|
602
|
+
console.log(` ${color}${symbol}\x1B[0m ${check.name}: ${check.message}`);
|
|
603
|
+
}
|
|
604
|
+
const failed = checks.filter((c) => !c.passed && !c.warning);
|
|
605
|
+
console.log("");
|
|
606
|
+
if (failed.length === 0) {
|
|
607
|
+
console.log("All checks passed!");
|
|
608
|
+
} else {
|
|
609
|
+
console.log(`${failed.length} check(s) failed. See messages above.`);
|
|
390
610
|
}
|
|
391
|
-
console.log(`Plugin file: ${pluginPath} (${pluginType})`);
|
|
392
|
-
console.log(`Provider in config: ${providerExists ? "yes" : "no"}`);
|
|
393
|
-
console.log(`Plugin enabled in config: ${pluginEnabled ? "yes" : "no"}`);
|
|
394
|
-
console.log(`Config path: ${configPath}`);
|
|
395
611
|
}
|
|
396
612
|
function main() {
|
|
397
613
|
let parsed;
|
|
@@ -418,6 +634,9 @@ function main() {
|
|
|
418
634
|
case "status":
|
|
419
635
|
commandStatus(parsed.options);
|
|
420
636
|
return;
|
|
637
|
+
case "doctor":
|
|
638
|
+
commandDoctor(parsed.options);
|
|
639
|
+
return;
|
|
421
640
|
case "help":
|
|
422
641
|
printHelp();
|
|
423
642
|
return;
|
|
@@ -429,3 +648,11 @@ function main() {
|
|
|
429
648
|
}
|
|
430
649
|
}
|
|
431
650
|
main();
|
|
651
|
+
export {
|
|
652
|
+
runDoctorChecks,
|
|
653
|
+
getStatusResult,
|
|
654
|
+
getBrandingHeader,
|
|
655
|
+
checkCursorAgentLogin,
|
|
656
|
+
checkCursorAgent,
|
|
657
|
+
checkBun
|
|
658
|
+
};
|