@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.
Files changed (80) hide show
  1. package/LICENSE +28 -0
  2. package/README.md +270 -0
  3. package/dist/cli/discover.js +527 -0
  4. package/dist/cli/mcptool.js +10339 -0
  5. package/dist/cli/opencode-cursor.js +2989 -0
  6. package/dist/index.js +20588 -0
  7. package/dist/plugin-entry.js +19848 -0
  8. package/package.json +82 -0
  9. package/scripts/cursor-agent-runner.mjs +272 -0
  10. package/scripts/sdk-runner.mjs +412 -0
  11. package/src/acp/metrics.ts +83 -0
  12. package/src/acp/sessions.ts +107 -0
  13. package/src/acp/tools.ts +209 -0
  14. package/src/auth.ts +175 -0
  15. package/src/cli/discover.ts +53 -0
  16. package/src/cli/mcptool.ts +133 -0
  17. package/src/cli/model-discovery.ts +71 -0
  18. package/src/cli/opencode-cursor.ts +1195 -0
  19. package/src/client/cursor-agent-child.ts +459 -0
  20. package/src/client/sdk-child.ts +550 -0
  21. package/src/client/simple.ts +293 -0
  22. package/src/commands/status.ts +39 -0
  23. package/src/index.ts +39 -0
  24. package/src/mcp/client-manager.ts +166 -0
  25. package/src/mcp/config.ts +169 -0
  26. package/src/mcp/tool-bridge.ts +133 -0
  27. package/src/models/config.ts +64 -0
  28. package/src/models/discovery.ts +105 -0
  29. package/src/models/index.ts +3 -0
  30. package/src/models/pricing.ts +196 -0
  31. package/src/models/sync.ts +247 -0
  32. package/src/models/types.ts +11 -0
  33. package/src/models/variants.ts +446 -0
  34. package/src/plugin-entry.ts +28 -0
  35. package/src/plugin-toggle.ts +81 -0
  36. package/src/plugin.ts +2802 -0
  37. package/src/provider/backend.ts +71 -0
  38. package/src/provider/boundary.ts +168 -0
  39. package/src/provider/passthrough-tracker.ts +38 -0
  40. package/src/provider/runtime-interception.ts +818 -0
  41. package/src/provider/tool-loop-guard.ts +644 -0
  42. package/src/provider/tool-schema-compat.ts +800 -0
  43. package/src/provider.ts +268 -0
  44. package/src/proxy/formatter.ts +60 -0
  45. package/src/proxy/handler.ts +29 -0
  46. package/src/proxy/incremental-prompt.ts +74 -0
  47. package/src/proxy/prompt-builder.ts +204 -0
  48. package/src/proxy/server.ts +207 -0
  49. package/src/proxy/session-resume.ts +312 -0
  50. package/src/proxy/tool-loop.ts +359 -0
  51. package/src/proxy/types.ts +13 -0
  52. package/src/services/toast-service.ts +81 -0
  53. package/src/streaming/ai-sdk-parts.ts +109 -0
  54. package/src/streaming/delta-tracker.ts +89 -0
  55. package/src/streaming/line-buffer.ts +44 -0
  56. package/src/streaming/openai-sse.ts +118 -0
  57. package/src/streaming/parser.ts +22 -0
  58. package/src/streaming/types.ts +158 -0
  59. package/src/tools/core/executor.ts +25 -0
  60. package/src/tools/core/registry.ts +27 -0
  61. package/src/tools/core/types.ts +31 -0
  62. package/src/tools/defaults.ts +954 -0
  63. package/src/tools/discovery.ts +140 -0
  64. package/src/tools/executors/cli.ts +59 -0
  65. package/src/tools/executors/local.ts +25 -0
  66. package/src/tools/executors/mcp.ts +39 -0
  67. package/src/tools/executors/sdk.ts +39 -0
  68. package/src/tools/index.ts +8 -0
  69. package/src/tools/registry.ts +34 -0
  70. package/src/tools/router.ts +123 -0
  71. package/src/tools/schema.ts +58 -0
  72. package/src/tools/skills/loader.ts +61 -0
  73. package/src/tools/skills/resolver.ts +21 -0
  74. package/src/tools/types.ts +29 -0
  75. package/src/types.ts +8 -0
  76. package/src/usage.ts +112 -0
  77. package/src/utils/binary.ts +71 -0
  78. package/src/utils/errors.ts +224 -0
  79. package/src/utils/logger.ts +191 -0
  80. package/src/utils/perf.ts +76 -0
package/package.json ADDED
@@ -0,0 +1,82 @@
1
+ {
2
+ "name": "@evanovation/open-cursor",
3
+ "version": "2.4.15",
4
+ "description": "No prompt limits. No broken streams. Full thinking + tool support. Your Cursor subscription, properly integrated.",
5
+ "type": "module",
6
+ "main": "dist/plugin-entry.js",
7
+ "module": "src/plugin-entry.ts",
8
+ "scripts": {
9
+ "build": "bun build ./src/index.ts ./src/plugin-entry.ts ./src/cli/discover.ts ./src/cli/opencode-cursor.ts ./src/cli/mcptool.ts --outdir ./dist --target node --external @opencode-ai/plugin --external zod",
10
+ "dev": "bun build ./src/index.ts ./src/plugin-entry.ts ./src/cli/discover.ts ./src/cli/opencode-cursor.ts ./src/cli/mcptool.ts --outdir ./dist --target node --watch --external @opencode-ai/plugin --external zod",
11
+ "test": "bun test",
12
+ "test:unit": "bun test tests/unit",
13
+ "test:integration": "bun test tests/integration",
14
+ "test:perf": "bun test tests/unit/perf.test.ts tests/unit/utils/logger.test.ts tests/unit/proxy/prompt-builder.test.ts tests/unit/proxy/session-resume.test.ts tests/unit/proxy/incremental-prompt.test.ts tests/unit/proxy/plugin-resume.test.ts tests/unit/cursor-agent-child.test.ts tests/unit/cursor-agent-runner.test.ts tests/unit/streaming/line-buffer.test.ts tests/unit/streaming/parser.test.ts tests/unit/streaming/delta-tracker.test.ts tests/unit/streaming/openai-sse.test.ts tests/integration/sdk-demux-roundtrip.test.ts",
15
+ "test:ci:unit": "bun test tests/tools/defaults.test.ts tests/tools/executor-chain.test.ts tests/tools/sdk-executor.test.ts tests/tools/mcp-executor.test.ts tests/tools/skills.test.ts tests/tools/registry.test.ts tests/unit/cli/opencode-cursor.test.ts tests/unit/cli/model-discovery.test.ts tests/unit/cursor-agent-child.test.ts tests/unit/cursor-agent-pool.test.ts tests/unit/cursor-agent-runner.test.ts tests/unit/errors.test.ts tests/unit/models/discovery.test.ts tests/unit/proxy/prompt-builder.test.ts tests/unit/proxy/tool-loop.test.ts tests/unit/proxy/session-resume.test.ts tests/unit/proxy/incremental-prompt.test.ts tests/unit/proxy/plugin-resume.test.ts tests/unit/provider-backend.test.ts tests/unit/provider-boundary.test.ts tests/unit/provider-runtime-interception.test.ts tests/unit/provider-tool-schema-compat.test.ts tests/unit/provider-tool-loop-guard.test.ts tests/unit/mcp/tool-bridge.test.ts tests/unit/sdk-child.test.ts tests/unit/sdk-runner.test.ts tests/unit/plugin.test.ts tests/unit/plugin-tools-hook.test.ts tests/unit/plugin-tool-resolution.test.ts tests/unit/plugin-config.test.ts tests/unit/plugin-stream-extraction.test.ts tests/unit/auth.test.ts tests/unit/streaming/line-buffer.test.ts tests/unit/streaming/parser.test.ts tests/unit/streaming/types.test.ts tests/unit/streaming/delta-tracker.test.ts tests/unit/streaming/openai-sse.test.ts tests/unit/streaming/ai-sdk-parts.test.ts tests/competitive/edge.test.ts",
16
+ "test:ci:integration": "bun test tests/integration/comprehensive.test.ts tests/integration/tools-router.integration.test.ts tests/integration/stream-router.integration.test.ts tests/integration/opencode-loop.integration.test.ts",
17
+ "verify:issue-92": "bash scripts/verify-issue-92.sh",
18
+ "check:pricing": "bun run scripts/check-cursor-pricing-coverage.ts",
19
+ "check:pricing:fixture": "bun run scripts/check-cursor-pricing-coverage.ts --models-file tests/fixtures/cursor-pricing-models.txt --skip-doc-fetch",
20
+ "discover": "bun run src/cli/discover.ts",
21
+ "prepublishOnly": "bun run build"
22
+ },
23
+ "bin": {
24
+ "open-cursor": "dist/cli/opencode-cursor.js",
25
+ "cursor-discover": "dist/cli/discover.js",
26
+ "mcptool": "dist/cli/mcptool.js"
27
+ },
28
+ "exports": {
29
+ ".": {
30
+ "bun": "./src/plugin-entry.ts",
31
+ "import": "./dist/plugin-entry.js",
32
+ "default": "./dist/plugin-entry.js"
33
+ },
34
+ "./lib": {
35
+ "bun": "./src/index.ts",
36
+ "import": "./dist/index.js",
37
+ "default": "./dist/index.js"
38
+ }
39
+ },
40
+ "files": [
41
+ "dist",
42
+ "src",
43
+ "scripts/sdk-runner.mjs",
44
+ "scripts/cursor-agent-runner.mjs"
45
+ ],
46
+ "dependencies": {
47
+ "@modelcontextprotocol/sdk": "^1.12.0",
48
+ "@opencode-ai/plugin": "1.1.53",
49
+ "@opencode-ai/sdk": "1.1.53",
50
+ "ai": "^6.0.55",
51
+ "strip-ansi": "^7.1.0"
52
+ },
53
+ "optionalDependencies": {
54
+ "@cursor/sdk": "^1.0.18"
55
+ },
56
+ "devDependencies": {
57
+ "@types/node": "^22.0.0",
58
+ "typescript": "^5.8.0"
59
+ },
60
+ "peerDependencies": {
61
+ "@opencode-ai/sdk": "^1.0.0",
62
+ "bun-types": "^1.1.0"
63
+ },
64
+ "license": "ISC",
65
+ "repository": {
66
+ "type": "git",
67
+ "url": "git+https://github.com/EvanNotFound/opencode-cursor.git"
68
+ },
69
+ "homepage": "https://github.com/EvanNotFound/opencode-cursor#readme",
70
+ "bugs": {
71
+ "url": "https://github.com/EvanNotFound/opencode-cursor/issues"
72
+ },
73
+ "keywords": [
74
+ "opencode",
75
+ "cursor",
76
+ "ai",
77
+ "streaming",
78
+ "plugin",
79
+ "cursor-agent"
80
+ ],
81
+ "author": "EvanNotFound"
82
+ }
@@ -0,0 +1,272 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * cursor-agent-runner.mjs
4
+ *
5
+ * Persistent Node runner for cursor-agent --print invocations.
6
+ * Reads NDJSON from stdin:
7
+ * {"id":"<string>","model":"...","cwd":"...","prompt":"...","resumeChatId?":"...","force?":bool,"cursorAgent?":"..."}
8
+ * Emits wrapped NDJSON to stdout:
9
+ * {"id":"<id>","event":{...StreamJsonEvent...}}
10
+ * {"id":"<id>","done":true,"exitCode":0|1}
11
+ *
12
+ * Requests are processed serially (one cursor-agent child at a time).
13
+ * Diagnostics go to stderr only.
14
+ */
15
+
16
+ import { spawn } from "node:child_process";
17
+ import { pathToFileURL } from "node:url";
18
+
19
+ const RESUME_CHAT_ID_SAFE_RE = /^[A-Za-z0-9][A-Za-z0-9_-]*$/;
20
+
21
+ const RUNNING_AS_MAIN = process.argv[1]
22
+ ? import.meta.url === pathToFileURL(process.argv[1]).href
23
+ : false;
24
+
25
+ const protocolWrite = process.stdout.write.bind(process.stdout);
26
+ if (RUNNING_AS_MAIN) {
27
+ process.stdout.write = (chunk, ...args) => process.stderr.write(chunk, ...args);
28
+ }
29
+
30
+ function writeProtocolLine(line) {
31
+ return protocolWrite(line);
32
+ }
33
+
34
+ function emitEvent(id, event) {
35
+ writeProtocolLine(JSON.stringify({ id, event }) + "\n");
36
+ }
37
+
38
+ function emitDone(id, exitCode) {
39
+ writeProtocolLine(JSON.stringify({ id, done: true, exitCode }) + "\n");
40
+ }
41
+
42
+ function emitErrorEvent(id, message) {
43
+ emitEvent(id, { type: "error", message });
44
+ }
45
+
46
+ function emitStderr(id, text) {
47
+ writeProtocolLine(JSON.stringify({ id, stderr: text }) + "\n");
48
+ }
49
+
50
+ // In-flight request tracking for cancellation. Requests are processed
51
+ // serially (one cursor-agent child at a time), so at most one child is
52
+ // active at any moment. Set in handleRequest on spawn, cleared on child
53
+ // close/error, and read by cancelRequest to kill the active child.
54
+ let currentRequest = null;
55
+ let currentChild = null;
56
+
57
+ async function handleRequest(request) {
58
+ const { id, model, cwd, prompt, resumeChatId, force, cursorAgent } = request;
59
+
60
+ if (!id || !model || !cwd || prompt == null) {
61
+ // Log only field presence and the request id; the request body includes
62
+ // the prompt, which must not be written to logs.
63
+ console.error(
64
+ `[cursor-agent-runner] Invalid request ${id || "<missing>"}: missing fields (model=${!!model} cwd=${!!cwd} prompt=${prompt != null})`,
65
+ );
66
+ emitErrorEvent(id || "unknown", "Missing required fields: id, model, cwd, prompt");
67
+ emitDone(id || "unknown", 1);
68
+ return;
69
+ }
70
+
71
+ const binary = typeof cursorAgent === "string" && cursorAgent.trim() ? cursorAgent.trim() : "cursor-agent";
72
+ const args = [
73
+ "--print",
74
+ "--output-format",
75
+ "stream-json",
76
+ "--stream-partial-output",
77
+ "--workspace",
78
+ cwd,
79
+ "--model",
80
+ model,
81
+ ];
82
+
83
+ if (typeof resumeChatId === "string" && resumeChatId.trim() && RESUME_CHAT_ID_SAFE_RE.test(resumeChatId.trim())) {
84
+ args.push("--resume", resumeChatId.trim());
85
+ }
86
+
87
+ if (force) {
88
+ args.push("--force");
89
+ }
90
+
91
+ console.error(`[cursor-agent-runner] Request ${id}: model=${model}, cwd=${cwd}, resume=${!!resumeChatId}`);
92
+
93
+ await new Promise((resolve) => {
94
+ const shell = process.platform === "win32";
95
+ const cmd = shell && binary.includes(" ") ? `"${binary}"` : binary;
96
+ const child = spawn(cmd, args, {
97
+ stdio: ["pipe", "pipe", "pipe"],
98
+ shell,
99
+ });
100
+ currentRequest = id;
101
+ currentChild = child;
102
+
103
+ let stderrText = "";
104
+ child.stderr?.on("data", (chunk) => {
105
+ const text = chunk.toString("utf8");
106
+ stderrText += text;
107
+ emitStderr(id, text);
108
+ });
109
+
110
+ child.stdin.write(typeof prompt === "string" ? prompt : String(prompt));
111
+ child.stdin.end();
112
+
113
+ let buffer = "";
114
+ child.stdout?.setEncoding("utf8");
115
+ child.stdout?.on("data", (chunk) => {
116
+ buffer += chunk;
117
+ const parts = buffer.split("\n");
118
+ buffer = parts.pop() ?? "";
119
+ for (const line of parts) {
120
+ if (!line.trim()) continue;
121
+ try {
122
+ const event = JSON.parse(line);
123
+ emitEvent(id, event);
124
+ } catch {
125
+ emitEvent(id, { type: "assistant", message: { role: "assistant", content: [{ type: "text", text: line }] } });
126
+ }
127
+ }
128
+ });
129
+
130
+ child.on("close", (code) => {
131
+ currentRequest = null;
132
+ currentChild = null;
133
+ if (buffer.trim()) {
134
+ try {
135
+ emitEvent(id, JSON.parse(buffer));
136
+ } catch {
137
+ // ignore trailing garbage
138
+ }
139
+ }
140
+ console.error(`[cursor-agent-runner] Request ${id} complete exitCode=${code ?? 1}`);
141
+ emitDone(id, code ?? 1);
142
+ resolve();
143
+ });
144
+
145
+ child.on("error", (err) => {
146
+ currentRequest = null;
147
+ currentChild = null;
148
+ console.error(`[cursor-agent-runner] Request ${id} spawn error: ${err.message}`);
149
+ emitErrorEvent(id, err.message);
150
+ if (stderrText.trim()) {
151
+ emitStderr(id, stderrText.trim());
152
+ }
153
+ emitDone(id, 1);
154
+ resolve();
155
+ });
156
+ });
157
+ }
158
+
159
+ async function main() {
160
+ console.error("[cursor-agent-runner] Waiting for requests on stdin...");
161
+
162
+ const queue = [];
163
+ let processing = false;
164
+
165
+ const pump = async () => {
166
+ if (processing) return;
167
+ processing = true;
168
+ try {
169
+ while (queue.length > 0) {
170
+ const request = queue.shift();
171
+ try {
172
+ await handleRequest(request);
173
+ } catch (err) {
174
+ const id = request?.id || "unknown";
175
+ const message = err instanceof Error ? err.message : String(err);
176
+ console.error(`[cursor-agent-runner] Unhandled error in request ${id}: ${message}`);
177
+ emitErrorEvent(id, message);
178
+ emitDone(id, 1);
179
+ }
180
+ }
181
+ } finally {
182
+ processing = false;
183
+ }
184
+ };
185
+
186
+ const enqueue = (request) => {
187
+ queue.push(request);
188
+ void pump();
189
+ };
190
+
191
+ // Cancel an in-flight or queued request. The pool child's kill() sends a
192
+ // {cancel: id} control line; without this, an aborted/intercepted
193
+ // cursor-agent request would run to completion and block the serial queue
194
+ // behind it. Killing the active child lets its close handler emit done
195
+ // promptly; a still-queued request is dropped with a synthetic done so the
196
+ // waiting caller resolves instead of hanging.
197
+ const cancelRequest = (id) => {
198
+ if (id && id === currentRequest && currentChild) {
199
+ try {
200
+ currentChild.kill("SIGKILL");
201
+ } catch (err) {
202
+ console.error(`[cursor-agent-runner] cancel kill failed for ${id}: ${err.message}`);
203
+ }
204
+ return;
205
+ }
206
+ const idx = queue.findIndex((r) => r && r.id === id);
207
+ if (idx >= 0) {
208
+ queue.splice(idx, 1);
209
+ console.error(`[cursor-agent-runner] Cancelled queued request ${id}`);
210
+ emitDone(id, 1);
211
+ }
212
+ // else: unknown or already completed — nothing to cancel.
213
+ };
214
+
215
+ // Parse one stdin line and route it: a {cancel: "<id>"} control message
216
+ // cancels a request; anything else is enqueued as a request.
217
+ const dispatchLine = (text) => {
218
+ let parsed;
219
+ try {
220
+ parsed = JSON.parse(text);
221
+ } catch (err) {
222
+ console.error(`[cursor-agent-runner] Failed to parse NDJSON line: ${err.message}`);
223
+ return;
224
+ }
225
+ if (parsed && typeof parsed.cancel === "string") {
226
+ cancelRequest(parsed.cancel);
227
+ return;
228
+ }
229
+ enqueue(parsed);
230
+ };
231
+
232
+ let buffer = "";
233
+ await new Promise((resolveEnd, rejectEnd) => {
234
+ process.stdin.setEncoding("utf8");
235
+ process.stdin.on("data", (chunk) => {
236
+ buffer += chunk;
237
+ const parts = buffer.split("\n");
238
+ buffer = parts.pop() ?? "";
239
+ for (const part of parts) {
240
+ if (!part.trim()) continue;
241
+ dispatchLine(part);
242
+ }
243
+ });
244
+ process.stdin.on("end", () => {
245
+ if (buffer.trim()) {
246
+ dispatchLine(buffer);
247
+ }
248
+ resolveEnd();
249
+ });
250
+ process.stdin.on("error", rejectEnd);
251
+ });
252
+
253
+ while (queue.length > 0 || processing) {
254
+ await pump();
255
+ if (queue.length > 0 || processing) {
256
+ await new Promise((r) => setTimeout(r, 10));
257
+ }
258
+ }
259
+
260
+ console.error("[cursor-agent-runner] Shutting down");
261
+ await new Promise((resolve) => protocolWrite("", resolve));
262
+ process.exit(0);
263
+ }
264
+
265
+ if (RUNNING_AS_MAIN) {
266
+ main().catch((err) => {
267
+ console.error(`[cursor-agent-runner] Fatal: ${err.message}`);
268
+ process.exit(1);
269
+ });
270
+ }
271
+
272
+ export { RESUME_CHAT_ID_SAFE_RE, emitDone, emitEvent, emitErrorEvent };