@evanovation/open-cursor 2.4.15
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 +270 -0
- package/dist/cli/discover.js +527 -0
- package/dist/cli/mcptool.js +10339 -0
- package/dist/cli/opencode-cursor.js +2989 -0
- package/dist/index.js +20588 -0
- package/dist/plugin-entry.js +19848 -0
- package/package.json +82 -0
- package/scripts/cursor-agent-runner.mjs +272 -0
- package/scripts/sdk-runner.mjs +412 -0
- package/src/acp/metrics.ts +83 -0
- package/src/acp/sessions.ts +107 -0
- package/src/acp/tools.ts +209 -0
- package/src/auth.ts +175 -0
- package/src/cli/discover.ts +53 -0
- package/src/cli/mcptool.ts +133 -0
- package/src/cli/model-discovery.ts +71 -0
- package/src/cli/opencode-cursor.ts +1195 -0
- package/src/client/cursor-agent-child.ts +459 -0
- package/src/client/sdk-child.ts +550 -0
- package/src/client/simple.ts +293 -0
- package/src/commands/status.ts +39 -0
- package/src/index.ts +39 -0
- package/src/mcp/client-manager.ts +166 -0
- package/src/mcp/config.ts +169 -0
- package/src/mcp/tool-bridge.ts +133 -0
- package/src/models/config.ts +64 -0
- package/src/models/discovery.ts +105 -0
- package/src/models/index.ts +3 -0
- package/src/models/pricing.ts +196 -0
- package/src/models/sync.ts +247 -0
- package/src/models/types.ts +11 -0
- package/src/models/variants.ts +446 -0
- package/src/plugin-entry.ts +28 -0
- package/src/plugin-toggle.ts +81 -0
- package/src/plugin.ts +2802 -0
- package/src/provider/backend.ts +71 -0
- package/src/provider/boundary.ts +168 -0
- package/src/provider/passthrough-tracker.ts +38 -0
- package/src/provider/runtime-interception.ts +818 -0
- package/src/provider/tool-loop-guard.ts +644 -0
- package/src/provider/tool-schema-compat.ts +800 -0
- package/src/provider.ts +268 -0
- package/src/proxy/formatter.ts +60 -0
- package/src/proxy/handler.ts +29 -0
- package/src/proxy/incremental-prompt.ts +74 -0
- package/src/proxy/prompt-builder.ts +204 -0
- package/src/proxy/server.ts +207 -0
- package/src/proxy/session-resume.ts +312 -0
- package/src/proxy/tool-loop.ts +359 -0
- package/src/proxy/types.ts +13 -0
- package/src/services/toast-service.ts +81 -0
- package/src/streaming/ai-sdk-parts.ts +109 -0
- package/src/streaming/delta-tracker.ts +89 -0
- package/src/streaming/line-buffer.ts +44 -0
- package/src/streaming/openai-sse.ts +118 -0
- package/src/streaming/parser.ts +22 -0
- package/src/streaming/types.ts +158 -0
- package/src/tools/core/executor.ts +25 -0
- package/src/tools/core/registry.ts +27 -0
- package/src/tools/core/types.ts +31 -0
- package/src/tools/defaults.ts +954 -0
- package/src/tools/discovery.ts +140 -0
- package/src/tools/executors/cli.ts +59 -0
- package/src/tools/executors/local.ts +25 -0
- package/src/tools/executors/mcp.ts +39 -0
- package/src/tools/executors/sdk.ts +39 -0
- package/src/tools/index.ts +8 -0
- package/src/tools/registry.ts +34 -0
- package/src/tools/router.ts +123 -0
- package/src/tools/schema.ts +58 -0
- package/src/tools/skills/loader.ts +61 -0
- package/src/tools/skills/resolver.ts +21 -0
- package/src/tools/types.ts +29 -0
- package/src/types.ts +8 -0
- package/src/usage.ts +112 -0
- package/src/utils/binary.ts +71 -0
- package/src/utils/errors.ts +224 -0
- package/src/utils/logger.ts +191 -0
- package/src/utils/perf.ts +76 -0
|
@@ -0,0 +1,527 @@
|
|
|
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
|
+
function __accessProp(key) {
|
|
9
|
+
return this[key];
|
|
10
|
+
}
|
|
11
|
+
var __toESMCache_node;
|
|
12
|
+
var __toESMCache_esm;
|
|
13
|
+
var __toESM = (mod, isNodeMode, target) => {
|
|
14
|
+
var canCache = mod != null && typeof mod === "object";
|
|
15
|
+
if (canCache) {
|
|
16
|
+
var cache = isNodeMode ? __toESMCache_node ??= new WeakMap : __toESMCache_esm ??= new WeakMap;
|
|
17
|
+
var cached = cache.get(mod);
|
|
18
|
+
if (cached)
|
|
19
|
+
return cached;
|
|
20
|
+
}
|
|
21
|
+
target = mod != null ? __create(__getProtoOf(mod)) : {};
|
|
22
|
+
const to = isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target;
|
|
23
|
+
for (let key of __getOwnPropNames(mod))
|
|
24
|
+
if (!__hasOwnProp.call(to, key))
|
|
25
|
+
__defProp(to, key, {
|
|
26
|
+
get: __accessProp.bind(mod, key),
|
|
27
|
+
enumerable: true
|
|
28
|
+
});
|
|
29
|
+
if (canCache)
|
|
30
|
+
cache.set(mod, to);
|
|
31
|
+
return to;
|
|
32
|
+
};
|
|
33
|
+
var __commonJS = (cb, mod) => () => (mod || cb((mod = { exports: {} }).exports, mod), mod.exports);
|
|
34
|
+
var __returnValue = (v) => v;
|
|
35
|
+
function __exportSetter(name, newValue) {
|
|
36
|
+
this[name] = __returnValue.bind(null, newValue);
|
|
37
|
+
}
|
|
38
|
+
var __export = (target, all) => {
|
|
39
|
+
for (var name in all)
|
|
40
|
+
__defProp(target, name, {
|
|
41
|
+
get: all[name],
|
|
42
|
+
enumerable: true,
|
|
43
|
+
configurable: true,
|
|
44
|
+
set: __exportSetter.bind(all, name)
|
|
45
|
+
});
|
|
46
|
+
};
|
|
47
|
+
var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
|
|
48
|
+
var __require = /* @__PURE__ */ createRequire(import.meta.url);
|
|
49
|
+
|
|
50
|
+
// src/utils/errors.ts
|
|
51
|
+
function stripAnsi(str) {
|
|
52
|
+
if (typeof str !== "string")
|
|
53
|
+
return String(str ?? "");
|
|
54
|
+
return str.replace(/\x1b\[[0-9;]*[a-zA-Z]/g, "");
|
|
55
|
+
}
|
|
56
|
+
function parseAgentError(stderr) {
|
|
57
|
+
const input = typeof stderr === "string" ? stderr : String(stderr ?? "");
|
|
58
|
+
const clean = stripAnsi(input).trim();
|
|
59
|
+
if (clean.includes("usage limit") || clean.includes("hit your usage limit")) {
|
|
60
|
+
const savingsMatch = clean.match(/saved \$(\d+(?:\.\d+)?)/i);
|
|
61
|
+
const resetMatch = clean.match(/reset[^0-9]*(\d{1,2}\/\d{1,2}\/\d{4})/i);
|
|
62
|
+
const modelMatch = clean.match(/continue with (\w+)/i);
|
|
63
|
+
const details = {};
|
|
64
|
+
if (savingsMatch)
|
|
65
|
+
details.savings = `$${savingsMatch[1]}`;
|
|
66
|
+
if (resetMatch)
|
|
67
|
+
details.resetDate = resetMatch[1];
|
|
68
|
+
if (modelMatch)
|
|
69
|
+
details.affectedModel = modelMatch[1];
|
|
70
|
+
return {
|
|
71
|
+
type: "quota",
|
|
72
|
+
recoverable: false,
|
|
73
|
+
message: clean,
|
|
74
|
+
userMessage: "You've hit your Cursor usage limit",
|
|
75
|
+
details,
|
|
76
|
+
suggestion: "Switch to a different model or set a Spend Limit in Cursor settings"
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
if (clean.includes("not logged in") || clean.includes("auth") || clean.includes("unauthorized")) {
|
|
80
|
+
return {
|
|
81
|
+
type: "auth",
|
|
82
|
+
recoverable: false,
|
|
83
|
+
message: clean,
|
|
84
|
+
userMessage: "Not authenticated with Cursor",
|
|
85
|
+
details: {},
|
|
86
|
+
suggestion: "Run: opencode auth login → Other → cursor-acp, or: cursor-agent login"
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
if (clean.includes("ECONNREFUSED") || clean.includes("network") || clean.includes("fetch failed")) {
|
|
90
|
+
return {
|
|
91
|
+
type: "network",
|
|
92
|
+
recoverable: true,
|
|
93
|
+
message: clean,
|
|
94
|
+
userMessage: "Connection to Cursor failed",
|
|
95
|
+
details: {},
|
|
96
|
+
suggestion: "Check your internet connection and try again"
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
if (clean.includes("model not found") || clean.includes("invalid model") || clean.includes("Cannot use this model")) {
|
|
100
|
+
const modelMatch = clean.match(/Cannot use this model: ([^.]+)/);
|
|
101
|
+
const availableMatch = clean.match(/Available models: (.+)/);
|
|
102
|
+
const details = {};
|
|
103
|
+
if (modelMatch)
|
|
104
|
+
details.requested = modelMatch[1];
|
|
105
|
+
if (availableMatch)
|
|
106
|
+
details.available = availableMatch[1].split(", ").slice(0, 5).join(", ") + "...";
|
|
107
|
+
return {
|
|
108
|
+
type: "model",
|
|
109
|
+
recoverable: false,
|
|
110
|
+
message: clean,
|
|
111
|
+
userMessage: modelMatch ? `Model '${modelMatch[1]}' not available` : "Requested model not available",
|
|
112
|
+
details,
|
|
113
|
+
suggestion: "Use cursor-acp/auto or check available models with: cursor-agent models"
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
const recoverable = clean.includes("timeout") || clean.includes("ETIMEDOUT");
|
|
117
|
+
return {
|
|
118
|
+
type: "unknown",
|
|
119
|
+
recoverable,
|
|
120
|
+
message: clean,
|
|
121
|
+
userMessage: clean.substring(0, 200) || "An error occurred",
|
|
122
|
+
details: {}
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
function isTransientContinuation(tail) {
|
|
126
|
+
const trimmed = tail.trim();
|
|
127
|
+
if (!trimmed)
|
|
128
|
+
return false;
|
|
129
|
+
const stripped = trimmed.replace(/^[\s:;,.]+/, "");
|
|
130
|
+
const causalMatch = stripped.match(/^(?:because of|due to)\s+(.+)/i);
|
|
131
|
+
if (causalMatch) {
|
|
132
|
+
const cause = causalMatch[1];
|
|
133
|
+
if (SESSION_SPECIFIC_CAUSE_WORDS.test(cause)) {
|
|
134
|
+
return false;
|
|
135
|
+
}
|
|
136
|
+
if (TRANSIENT_CAUSE_WORDS.test(cause)) {
|
|
137
|
+
return true;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
const firstSegment = stripped.split(/[,;]/)[0]?.trim() ?? "";
|
|
141
|
+
if (firstSegment) {
|
|
142
|
+
if (SESSION_SPECIFIC_CAUSE_WORDS.test(firstSegment)) {
|
|
143
|
+
return false;
|
|
144
|
+
}
|
|
145
|
+
if (TRANSIENT_CAUSE_WORDS.test(firstSegment)) {
|
|
146
|
+
return true;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
if (SESSION_SPECIFIC_CAUSE_WORDS.test(stripped)) {
|
|
150
|
+
return false;
|
|
151
|
+
}
|
|
152
|
+
if (TRANSIENT_CAUSE_WORDS.test(stripped)) {
|
|
153
|
+
return true;
|
|
154
|
+
}
|
|
155
|
+
return TRANSIENT_CONTINUATION_PATTERN.test(tail);
|
|
156
|
+
}
|
|
157
|
+
function isResumeSpecificFailure(stderr) {
|
|
158
|
+
const text = typeof stderr === "string" ? stderr : String(stderr ?? "");
|
|
159
|
+
const clean = stripAnsi(text);
|
|
160
|
+
for (const pattern of RESUME_FAILURE_PATTERNS) {
|
|
161
|
+
const match = clean.match(pattern);
|
|
162
|
+
if (!match)
|
|
163
|
+
continue;
|
|
164
|
+
const tail = clean.slice(match.index + match[0].length);
|
|
165
|
+
if (!isTransientContinuation(tail)) {
|
|
166
|
+
return true;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
return false;
|
|
170
|
+
}
|
|
171
|
+
function formatErrorForUser(error) {
|
|
172
|
+
let output = `cursor-acp error: ${error.userMessage || error.message || "Unknown error"}`;
|
|
173
|
+
const details = error.details || {};
|
|
174
|
+
if (Object.keys(details).length > 0) {
|
|
175
|
+
const detailParts = Object.entries(details).map(([k, v]) => `${k}: ${v}`).join(" | ");
|
|
176
|
+
output += `
|
|
177
|
+
${detailParts}`;
|
|
178
|
+
}
|
|
179
|
+
if (error.suggestion) {
|
|
180
|
+
output += `
|
|
181
|
+
Suggestion: ${error.suggestion}`;
|
|
182
|
+
}
|
|
183
|
+
return output;
|
|
184
|
+
}
|
|
185
|
+
var RESUME_FAILURE_PATTERNS, TRANSIENT_CONTINUATION_PATTERN, TRANSIENT_CAUSE_WORDS, SESSION_SPECIFIC_CAUSE_WORDS;
|
|
186
|
+
var init_errors = __esm(() => {
|
|
187
|
+
RESUME_FAILURE_PATTERNS = [
|
|
188
|
+
/\bsession\s+(?:has\s+(?:been\s+)?|is\s+|was\s+)?(?:not\s+found|expired|deleted|missing|no\s+longer\s+exists)/i,
|
|
189
|
+
/\bchat\s+(?:has\s+(?:been\s+)?|is\s+|was\s+)?(?:not\s+found|expired|deleted|missing|no\s+longer\s+exists)/i,
|
|
190
|
+
/\bconversation\s+(?:has\s+(?:been\s+)?|is\s+|was\s+)?(?:not\s+found|expired|deleted|missing|no\s+longer\s+exists)/i,
|
|
191
|
+
/\bthread\s+(?:has\s+(?:been\s+)?|is\s+|was\s+)?(?:not\s+found|expired|deleted|missing|no\s+longer\s+exists)/i,
|
|
192
|
+
/\bresume\s+(?:failed|error|invalid|aborted)(?:\s+(?:session|chat|conversation|thread))?/i,
|
|
193
|
+
/\bfailed\s+to\s+resume(?:\s+(?:session|chat|conversation|thread))?/i,
|
|
194
|
+
/\bcould\s+not\s+resume(?:\s+(?:session|chat|conversation|thread))?/i,
|
|
195
|
+
/\bno\s+active\s+session/i,
|
|
196
|
+
/\bno\s+such\s+session/i,
|
|
197
|
+
/\bno\s+such\s+chat/i,
|
|
198
|
+
/\binvalid\s+(?:session|chat|conversation|thread)(?:\s+id)?/i,
|
|
199
|
+
/\b(?:session|chat|conversation|thread)\s+invalid(?:\s+id)?/i,
|
|
200
|
+
/\b(?:session|chat|conversation|thread)\s+id\s+(?:is\s+)?(?:invalid|not\s+found|expired|missing)/i,
|
|
201
|
+
/\b(?:session|chat|conversation|thread)\s+(?:isn['’]t|wasn['’]t)\s+found/i,
|
|
202
|
+
/\b(?:session|chat|conversation|thread)\s+(?:can(?:not|\s+not)|could\s+not)\s+(?:be\s+)?resumed/i,
|
|
203
|
+
/\bunable\s+to\s+resume\b/i,
|
|
204
|
+
/\bcan(?:not|\s+not)\s+resume\b/i
|
|
205
|
+
];
|
|
206
|
+
TRANSIENT_CONTINUATION_PATTERN = /^\s*[:;]?\s*(?:token|credential|credentials|auth|secret|password|format|network|quota|usage|limit|api|key|request(?:_|-|\s+)?id?|due\s+to\s+(?:network|auth|quota)|because\s+of\s+(?:network|auth|quota)|caused\s+by\s+(?:network|auth|quota))/i;
|
|
207
|
+
TRANSIENT_CAUSE_WORDS = /\b(?:auth(?:enticat(?:e|ion|ed))?|re-auth(?:enticate)?|token(?:\s+rotation)?|credential|password|secret|network|connection|internet|offline|quota|usage(?:\s+limit)?|api[\s-]?key|fetch\s+failed|econnrefused|timeout|timed\s+out)\b/i;
|
|
208
|
+
SESSION_SPECIFIC_CAUSE_WORDS = /\b(?:inactiv(?:ity|e)|idle|policy|retention|archiv(?:e|ed)|purged|deleted|removed|expired)\b/i;
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
// src/utils/logger.ts
|
|
212
|
+
import * as fs from "node:fs";
|
|
213
|
+
import * as path from "node:path";
|
|
214
|
+
import * as os from "node:os";
|
|
215
|
+
function getConfiguredLevel() {
|
|
216
|
+
const env = process.env.CURSOR_ACP_LOG_LEVEL?.toLowerCase();
|
|
217
|
+
if (env && env in LEVEL_PRIORITY) {
|
|
218
|
+
return env;
|
|
219
|
+
}
|
|
220
|
+
return "info";
|
|
221
|
+
}
|
|
222
|
+
function isSilent() {
|
|
223
|
+
return process.env.CURSOR_ACP_LOG_SILENT === "1" || process.env.CURSOR_ACP_LOG_SILENT === "true";
|
|
224
|
+
}
|
|
225
|
+
function shouldLog(level) {
|
|
226
|
+
if (isSilent())
|
|
227
|
+
return false;
|
|
228
|
+
const configured = getConfiguredLevel();
|
|
229
|
+
return LEVEL_PRIORITY[level] >= LEVEL_PRIORITY[configured];
|
|
230
|
+
}
|
|
231
|
+
function formatMessage(level, component, message, data) {
|
|
232
|
+
const prefix = `[cursor-acp:${component}]`;
|
|
233
|
+
const levelTag = level.toUpperCase().padEnd(5);
|
|
234
|
+
let formatted = `${prefix} ${levelTag} ${message}`;
|
|
235
|
+
if (data !== undefined) {
|
|
236
|
+
if (typeof data === "object") {
|
|
237
|
+
formatted += ` ${JSON.stringify(data)}`;
|
|
238
|
+
} else {
|
|
239
|
+
formatted += ` ${data}`;
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
return formatted;
|
|
243
|
+
}
|
|
244
|
+
function isConsoleEnabled() {
|
|
245
|
+
const consoleEnv = process.env.CURSOR_ACP_LOG_CONSOLE;
|
|
246
|
+
return consoleEnv === "1" || consoleEnv === "true";
|
|
247
|
+
}
|
|
248
|
+
function getLogDir() {
|
|
249
|
+
const override = process.env.CURSOR_ACP_LOG_DIR?.trim();
|
|
250
|
+
return override || path.join(os.homedir(), ".opencode-cursor");
|
|
251
|
+
}
|
|
252
|
+
function getLogFile() {
|
|
253
|
+
return path.join(getLogDir(), "plugin.log");
|
|
254
|
+
}
|
|
255
|
+
function ensureLogDir() {
|
|
256
|
+
if (logDirEnsured)
|
|
257
|
+
return;
|
|
258
|
+
try {
|
|
259
|
+
const logDir = getLogDir();
|
|
260
|
+
if (!fs.existsSync(logDir)) {
|
|
261
|
+
fs.mkdirSync(logDir, { recursive: true });
|
|
262
|
+
}
|
|
263
|
+
logDirEnsured = true;
|
|
264
|
+
} catch {
|
|
265
|
+
logFileError = true;
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
function openLogStream() {
|
|
269
|
+
if (logStream || logFileError)
|
|
270
|
+
return;
|
|
271
|
+
ensureLogDir();
|
|
272
|
+
if (logFileError)
|
|
273
|
+
return;
|
|
274
|
+
try {
|
|
275
|
+
try {
|
|
276
|
+
logBytesWritten = fs.statSync(getLogFile()).size;
|
|
277
|
+
} catch {
|
|
278
|
+
logBytesWritten = 0;
|
|
279
|
+
}
|
|
280
|
+
logStream = fs.createWriteStream(getLogFile(), { flags: "a" });
|
|
281
|
+
logStream.on("error", () => {
|
|
282
|
+
if (!logFileError) {
|
|
283
|
+
logFileError = true;
|
|
284
|
+
console.error(`[cursor-acp] Failed to write logs. Using: ${getLogFile()}`);
|
|
285
|
+
}
|
|
286
|
+
logStream = null;
|
|
287
|
+
});
|
|
288
|
+
} catch {
|
|
289
|
+
logFileError = true;
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
function rotateIfNeeded() {
|
|
293
|
+
if (logBytesWritten < MAX_LOG_SIZE)
|
|
294
|
+
return;
|
|
295
|
+
try {
|
|
296
|
+
if (logStream) {
|
|
297
|
+
logStream.end();
|
|
298
|
+
logStream = null;
|
|
299
|
+
}
|
|
300
|
+
const logFile = getLogFile();
|
|
301
|
+
fs.renameSync(logFile, logFile + ".1");
|
|
302
|
+
logBytesWritten = 0;
|
|
303
|
+
openLogStream();
|
|
304
|
+
} catch {
|
|
305
|
+
if (!logFileError && !logStream) {
|
|
306
|
+
openLogStream();
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
function writeToFile(message) {
|
|
311
|
+
if (logFileError)
|
|
312
|
+
return;
|
|
313
|
+
if (!logStream)
|
|
314
|
+
openLogStream();
|
|
315
|
+
if (logFileError || !logStream)
|
|
316
|
+
return;
|
|
317
|
+
rotateIfNeeded();
|
|
318
|
+
if (logFileError || !logStream)
|
|
319
|
+
return;
|
|
320
|
+
const timestamp = new Date().toISOString();
|
|
321
|
+
const line = `${timestamp} ${message}
|
|
322
|
+
`;
|
|
323
|
+
logStream.write(line);
|
|
324
|
+
logBytesWritten += Buffer.byteLength(line);
|
|
325
|
+
}
|
|
326
|
+
function createLogger(component) {
|
|
327
|
+
return {
|
|
328
|
+
isDebugEnabled: () => shouldLog("debug"),
|
|
329
|
+
debug: (message, data) => {
|
|
330
|
+
if (!shouldLog("debug"))
|
|
331
|
+
return;
|
|
332
|
+
const formatted = formatMessage("debug", component, message, data);
|
|
333
|
+
writeToFile(formatted);
|
|
334
|
+
if (isConsoleEnabled())
|
|
335
|
+
console.error(formatted);
|
|
336
|
+
},
|
|
337
|
+
info: (message, data) => {
|
|
338
|
+
if (!shouldLog("info"))
|
|
339
|
+
return;
|
|
340
|
+
const formatted = formatMessage("info", component, message, data);
|
|
341
|
+
writeToFile(formatted);
|
|
342
|
+
if (isConsoleEnabled())
|
|
343
|
+
console.error(formatted);
|
|
344
|
+
},
|
|
345
|
+
warn: (message, data) => {
|
|
346
|
+
if (!shouldLog("warn"))
|
|
347
|
+
return;
|
|
348
|
+
const formatted = formatMessage("warn", component, message, data);
|
|
349
|
+
writeToFile(formatted);
|
|
350
|
+
if (isConsoleEnabled())
|
|
351
|
+
console.error(formatted);
|
|
352
|
+
},
|
|
353
|
+
error: (message, data) => {
|
|
354
|
+
if (!shouldLog("error"))
|
|
355
|
+
return;
|
|
356
|
+
const formatted = formatMessage("error", component, message, data);
|
|
357
|
+
writeToFile(formatted);
|
|
358
|
+
if (isConsoleEnabled())
|
|
359
|
+
console.error(formatted);
|
|
360
|
+
}
|
|
361
|
+
};
|
|
362
|
+
}
|
|
363
|
+
var MAX_LOG_SIZE, LEVEL_PRIORITY, logDirEnsured = false, logFileError = false, logStream = null, logBytesWritten = 0;
|
|
364
|
+
var init_logger = __esm(() => {
|
|
365
|
+
MAX_LOG_SIZE = 5 * 1024 * 1024;
|
|
366
|
+
LEVEL_PRIORITY = {
|
|
367
|
+
debug: 0,
|
|
368
|
+
info: 1,
|
|
369
|
+
warn: 2,
|
|
370
|
+
error: 3
|
|
371
|
+
};
|
|
372
|
+
});
|
|
373
|
+
|
|
374
|
+
// src/utils/binary.ts
|
|
375
|
+
import { existsSync as fsExistsSync } from "fs";
|
|
376
|
+
import * as pathModule from "path";
|
|
377
|
+
import { homedir as osHomedir } from "os";
|
|
378
|
+
function resolveCursorAgentBinary(deps = {}) {
|
|
379
|
+
const platform = deps.platform ?? process.platform;
|
|
380
|
+
const env = deps.env ?? process.env;
|
|
381
|
+
const checkExists = deps.existsSync ?? fsExistsSync;
|
|
382
|
+
const home = (deps.homedir ?? osHomedir)();
|
|
383
|
+
const envOverride = env.CURSOR_AGENT_EXECUTABLE;
|
|
384
|
+
if (envOverride && envOverride.length > 0) {
|
|
385
|
+
return envOverride;
|
|
386
|
+
}
|
|
387
|
+
if (platform === "win32") {
|
|
388
|
+
const pathJoin = pathModule.win32.join;
|
|
389
|
+
const localAppData = env.LOCALAPPDATA ?? pathJoin(home, "AppData", "Local");
|
|
390
|
+
const knownPath = pathJoin(localAppData, "cursor-agent", "cursor-agent.cmd");
|
|
391
|
+
if (checkExists(knownPath)) {
|
|
392
|
+
return knownPath;
|
|
393
|
+
}
|
|
394
|
+
log.warn("cursor-agent not found at known Windows path, falling back to PATH", { checkedPath: knownPath });
|
|
395
|
+
return "cursor-agent.cmd";
|
|
396
|
+
}
|
|
397
|
+
const knownPaths = [
|
|
398
|
+
pathModule.join(home, ".cursor-agent", "cursor-agent"),
|
|
399
|
+
"/usr/local/bin/cursor-agent"
|
|
400
|
+
];
|
|
401
|
+
for (const p of knownPaths) {
|
|
402
|
+
if (checkExists(p)) {
|
|
403
|
+
return p;
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
log.warn("cursor-agent not found at known paths, falling back to PATH", { checkedPaths: knownPaths });
|
|
407
|
+
return "cursor-agent";
|
|
408
|
+
}
|
|
409
|
+
function formatShellCommandForPlatform(command, platform = process.platform) {
|
|
410
|
+
if (platform !== "win32") {
|
|
411
|
+
return command;
|
|
412
|
+
}
|
|
413
|
+
if (command.startsWith('"') && command.endsWith('"')) {
|
|
414
|
+
return command;
|
|
415
|
+
}
|
|
416
|
+
return `"${command}"`;
|
|
417
|
+
}
|
|
418
|
+
var log;
|
|
419
|
+
var init_binary = __esm(() => {
|
|
420
|
+
init_logger();
|
|
421
|
+
log = createLogger("binary");
|
|
422
|
+
});
|
|
423
|
+
|
|
424
|
+
// src/cli/model-discovery.ts
|
|
425
|
+
import { execFileSync } from "child_process";
|
|
426
|
+
function parseCursorModelsOutput(output) {
|
|
427
|
+
const clean = stripAnsi(output);
|
|
428
|
+
const models = [];
|
|
429
|
+
const seen = new Set;
|
|
430
|
+
for (const line of clean.split(`
|
|
431
|
+
`)) {
|
|
432
|
+
const trimmed = line.trim();
|
|
433
|
+
if (!trimmed)
|
|
434
|
+
continue;
|
|
435
|
+
const match = trimmed.match(/^([a-zA-Z0-9._-]+)\s+-\s+(.+?)(?:\s+\((?:current|default)\))*\s*$/);
|
|
436
|
+
if (!match)
|
|
437
|
+
continue;
|
|
438
|
+
const id = match[1];
|
|
439
|
+
if (seen.has(id))
|
|
440
|
+
continue;
|
|
441
|
+
seen.add(id);
|
|
442
|
+
models.push({ id, name: match[2].trim() });
|
|
443
|
+
}
|
|
444
|
+
return models;
|
|
445
|
+
}
|
|
446
|
+
function discoverModelsFromCursorAgent() {
|
|
447
|
+
const raw = execFileSync(resolveCursorAgentBinary(), ["models"], {
|
|
448
|
+
encoding: "utf8",
|
|
449
|
+
...process.platform !== "win32" && { killSignal: "SIGTERM" },
|
|
450
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
451
|
+
timeout: MODEL_DISCOVERY_TIMEOUT_MS
|
|
452
|
+
});
|
|
453
|
+
const models = parseCursorModelsOutput(raw);
|
|
454
|
+
if (models.length === 0) {
|
|
455
|
+
throw new Error("No models parsed from cursor-agent output");
|
|
456
|
+
}
|
|
457
|
+
return models;
|
|
458
|
+
}
|
|
459
|
+
function fallbackModels() {
|
|
460
|
+
return [
|
|
461
|
+
{ id: "auto", name: "Auto" },
|
|
462
|
+
{ id: "composer-1.5", name: "Composer 1.5" },
|
|
463
|
+
{ id: "composer-1", name: "Composer 1" },
|
|
464
|
+
{ id: "opus-4.6-thinking", name: "Claude 4.6 Opus (Thinking)" },
|
|
465
|
+
{ id: "opus-4.6", name: "Claude 4.6 Opus" },
|
|
466
|
+
{ id: "sonnet-4.6", name: "Claude 4.6 Sonnet" },
|
|
467
|
+
{ id: "sonnet-4.6-thinking", name: "Claude 4.6 Sonnet (Thinking)" },
|
|
468
|
+
{ id: "opus-4.5", name: "Claude 4.5 Opus" },
|
|
469
|
+
{ id: "opus-4.5-thinking", name: "Claude 4.5 Opus (Thinking)" },
|
|
470
|
+
{ id: "sonnet-4.5", name: "Claude 4.5 Sonnet" },
|
|
471
|
+
{ id: "sonnet-4.5-thinking", name: "Claude 4.5 Sonnet (Thinking)" },
|
|
472
|
+
{ id: "gpt-5.4-high", name: "GPT-5.4 High" },
|
|
473
|
+
{ id: "gpt-5.4-medium", name: "GPT-5.4" },
|
|
474
|
+
{ id: "gpt-5.3-codex", name: "GPT-5.3 Codex" },
|
|
475
|
+
{ id: "gpt-5.2", name: "GPT-5.2" },
|
|
476
|
+
{ id: "gemini-3.1-pro", name: "Gemini 3.1 Pro" },
|
|
477
|
+
{ id: "gemini-3-pro", name: "Gemini 3 Pro" },
|
|
478
|
+
{ id: "gemini-3-flash", name: "Gemini 3 Flash" },
|
|
479
|
+
{ id: "grok", name: "Grok" },
|
|
480
|
+
{ id: "kimi-k2.5", name: "Kimi K2.5" }
|
|
481
|
+
];
|
|
482
|
+
}
|
|
483
|
+
var MODEL_DISCOVERY_TIMEOUT_MS = 5000;
|
|
484
|
+
var init_model_discovery = __esm(() => {
|
|
485
|
+
init_errors();
|
|
486
|
+
init_binary();
|
|
487
|
+
});
|
|
488
|
+
|
|
489
|
+
// src/cli/discover.ts
|
|
490
|
+
init_model_discovery();
|
|
491
|
+
import { readFileSync, writeFileSync, existsSync as existsSync2 } from "fs";
|
|
492
|
+
import { join as join3 } from "path";
|
|
493
|
+
import { homedir as homedir2 } from "os";
|
|
494
|
+
async function main() {
|
|
495
|
+
console.log("Discovering Cursor models...");
|
|
496
|
+
let models = fallbackModels();
|
|
497
|
+
try {
|
|
498
|
+
models = discoverModelsFromCursorAgent();
|
|
499
|
+
} catch (error) {
|
|
500
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
501
|
+
console.warn(`Warning: cursor-agent model discovery failed, using fallback list (${message})`);
|
|
502
|
+
}
|
|
503
|
+
console.log(`Found ${models.length} models:`);
|
|
504
|
+
for (const model of models) {
|
|
505
|
+
console.log(` - ${model.id}: ${model.name}`);
|
|
506
|
+
}
|
|
507
|
+
const configPath = join3(homedir2(), ".config/opencode/opencode.json");
|
|
508
|
+
if (!existsSync2(configPath)) {
|
|
509
|
+
console.error(`Config not found: ${configPath}`);
|
|
510
|
+
process.exit(1);
|
|
511
|
+
}
|
|
512
|
+
const existingConfig = JSON.parse(readFileSync(configPath, "utf-8"));
|
|
513
|
+
if (existingConfig.provider?.["cursor-acp"]) {
|
|
514
|
+
const formatted = Object.fromEntries(models.map((model) => [model.id, { name: model.name }]));
|
|
515
|
+
existingConfig.provider["cursor-acp"].models = {
|
|
516
|
+
...existingConfig.provider["cursor-acp"].models,
|
|
517
|
+
...formatted
|
|
518
|
+
};
|
|
519
|
+
writeFileSync(configPath, JSON.stringify(existingConfig, null, 2));
|
|
520
|
+
console.log(`Updated ${configPath}`);
|
|
521
|
+
} else {
|
|
522
|
+
console.error("cursor-acp provider not found in config");
|
|
523
|
+
process.exit(1);
|
|
524
|
+
}
|
|
525
|
+
console.log("Done!");
|
|
526
|
+
}
|
|
527
|
+
main().catch(console.error);
|