@playwo/opencode-cursor-oauth 0.0.0-dev.b8e6dd72a8b6 → 0.0.9
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 +1 -1
- package/dist/index.js +2 -52
- package/dist/models.d.ts +0 -3
- package/dist/models.js +31 -58
- package/dist/proxy.d.ts +0 -1
- package/dist/proxy.js +8 -116
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -44,7 +44,7 @@ OpenAI-compatible proxy on demand and routes requests through Cursor's gRPC API.
|
|
|
44
44
|
## How it works
|
|
45
45
|
|
|
46
46
|
1. OAuth — browser-based login to Cursor via PKCE.
|
|
47
|
-
2. Model discovery — queries Cursor's gRPC API for all available models
|
|
47
|
+
2. Model discovery — queries Cursor's gRPC API for all available models.
|
|
48
48
|
3. Local proxy — translates `POST /v1/chat/completions` into Cursor's
|
|
49
49
|
protobuf/Connect protocol.
|
|
50
50
|
4. Native tool routing — rejects Cursor's built-in filesystem/shell tools and
|
package/dist/index.js
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
import { generateCursorAuthParams, getTokenExpiry, pollCursorAuth, refreshCursorToken, } from "./auth";
|
|
2
2
|
import { getCursorModels } from "./models";
|
|
3
|
-
import { startProxy
|
|
3
|
+
import { startProxy } from "./proxy";
|
|
4
4
|
const CURSOR_PROVIDER_ID = "cursor";
|
|
5
|
-
let lastModelDiscoveryError = null;
|
|
6
5
|
/**
|
|
7
6
|
* OpenCode plugin that provides Cursor authentication and model access.
|
|
8
7
|
* Register in opencode.json: { "plugin": ["opencode-cursor-oauth"] }
|
|
@@ -30,25 +29,7 @@ export const CursorAuthPlugin = async (input) => {
|
|
|
30
29
|
});
|
|
31
30
|
accessToken = refreshed.access;
|
|
32
31
|
}
|
|
33
|
-
|
|
34
|
-
try {
|
|
35
|
-
models = await getCursorModels(accessToken);
|
|
36
|
-
lastModelDiscoveryError = null;
|
|
37
|
-
}
|
|
38
|
-
catch (error) {
|
|
39
|
-
const message = error instanceof Error
|
|
40
|
-
? error.message
|
|
41
|
-
: "Cursor model discovery failed.";
|
|
42
|
-
stopProxy();
|
|
43
|
-
if (provider) {
|
|
44
|
-
provider.models = {};
|
|
45
|
-
}
|
|
46
|
-
if (message !== lastModelDiscoveryError) {
|
|
47
|
-
lastModelDiscoveryError = message;
|
|
48
|
-
await showDiscoveryFailureToast(input, message);
|
|
49
|
-
}
|
|
50
|
-
return buildDisabledProviderConfig(message);
|
|
51
|
-
}
|
|
32
|
+
const models = await getCursorModels(accessToken);
|
|
52
33
|
const port = await startProxy(async () => {
|
|
53
34
|
const currentAuth = await getAuth();
|
|
54
35
|
if (currentAuth.type !== "oauth") {
|
|
@@ -164,37 +145,6 @@ function buildCursorProviderModels(models, port) {
|
|
|
164
145
|
},
|
|
165
146
|
]));
|
|
166
147
|
}
|
|
167
|
-
async function showDiscoveryFailureToast(input, message) {
|
|
168
|
-
try {
|
|
169
|
-
await input.client.tui.showToast({
|
|
170
|
-
body: {
|
|
171
|
-
title: "Cursor plugin disabled",
|
|
172
|
-
message,
|
|
173
|
-
variant: "error",
|
|
174
|
-
duration: 8_000,
|
|
175
|
-
},
|
|
176
|
-
});
|
|
177
|
-
}
|
|
178
|
-
catch { }
|
|
179
|
-
}
|
|
180
|
-
function buildDisabledProviderConfig(message) {
|
|
181
|
-
return {
|
|
182
|
-
baseURL: "http://127.0.0.1/cursor-disabled/v1",
|
|
183
|
-
apiKey: "cursor-disabled",
|
|
184
|
-
async fetch() {
|
|
185
|
-
return new Response(JSON.stringify({
|
|
186
|
-
error: {
|
|
187
|
-
message,
|
|
188
|
-
type: "server_error",
|
|
189
|
-
code: "cursor_model_discovery_failed",
|
|
190
|
-
},
|
|
191
|
-
}), {
|
|
192
|
-
status: 503,
|
|
193
|
-
headers: { "Content-Type": "application/json" },
|
|
194
|
-
});
|
|
195
|
-
},
|
|
196
|
-
};
|
|
197
|
-
}
|
|
198
148
|
// $/M token rates from cursor.com/docs/models-and-pricing
|
|
199
149
|
const MODEL_COST_TABLE = {
|
|
200
150
|
// Anthropic
|
package/dist/models.d.ts
CHANGED
|
@@ -5,9 +5,6 @@ export interface CursorModel {
|
|
|
5
5
|
contextWindow: number;
|
|
6
6
|
maxTokens: number;
|
|
7
7
|
}
|
|
8
|
-
export declare class CursorModelDiscoveryError extends Error {
|
|
9
|
-
constructor(message: string);
|
|
10
|
-
}
|
|
11
8
|
export declare function getCursorModels(apiKey: string): Promise<CursorModel[]>;
|
|
12
9
|
/** @internal Test-only. */
|
|
13
10
|
export declare function clearModelCache(): void;
|
package/dist/models.js
CHANGED
|
@@ -1,9 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cursor model discovery via GetUsableModels.
|
|
3
|
+
* Uses the H2 bridge for transport. Falls back to a hardcoded list
|
|
4
|
+
* when discovery fails.
|
|
5
|
+
*/
|
|
1
6
|
import { create, fromBinary, toBinary } from "@bufbuild/protobuf";
|
|
2
7
|
import { z } from "zod";
|
|
3
8
|
import { callCursorUnaryRpc } from "./proxy";
|
|
4
9
|
import { GetUsableModelsRequestSchema, GetUsableModelsResponseSchema, } from "./proto/agent_pb";
|
|
5
10
|
const GET_USABLE_MODELS_PATH = "/agent.v1.AgentService/GetUsableModels";
|
|
6
|
-
const MODEL_DISCOVERY_TIMEOUT_MS = 5_000;
|
|
7
11
|
const DEFAULT_CONTEXT_WINDOW = 200_000;
|
|
8
12
|
const DEFAULT_MAX_TOKENS = 64_000;
|
|
9
13
|
const CursorModelDetailsSchema = z.object({
|
|
@@ -18,12 +22,24 @@ const CursorModelDetailsSchema = z.object({
|
|
|
18
22
|
.transform((aliases) => (aliases ?? []).filter((alias) => typeof alias === "string")),
|
|
19
23
|
thinkingDetails: z.unknown().optional(),
|
|
20
24
|
});
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
}
|
|
25
|
+
const FALLBACK_MODELS = [
|
|
26
|
+
// Composer models
|
|
27
|
+
{ id: "composer-1", name: "Composer 1", reasoning: true, contextWindow: 200_000, maxTokens: 64_000 },
|
|
28
|
+
{ id: "composer-1.5", name: "Composer 1.5", reasoning: true, contextWindow: 200_000, maxTokens: 64_000 },
|
|
29
|
+
// Claude models
|
|
30
|
+
{ id: "claude-4.6-opus-high", name: "Claude 4.6 Opus", reasoning: true, contextWindow: 200_000, maxTokens: 128_000 },
|
|
31
|
+
{ id: "claude-4.6-sonnet-medium", name: "Claude 4.6 Sonnet", reasoning: true, contextWindow: 200_000, maxTokens: 64_000 },
|
|
32
|
+
{ id: "claude-4.5-sonnet", name: "Claude 4.5 Sonnet", reasoning: true, contextWindow: 200_000, maxTokens: 64_000 },
|
|
33
|
+
// GPT models
|
|
34
|
+
{ id: "gpt-5.4-medium", name: "GPT-5.4", reasoning: true, contextWindow: 272_000, maxTokens: 128_000 },
|
|
35
|
+
{ id: "gpt-5.2", name: "GPT-5.2", reasoning: true, contextWindow: 400_000, maxTokens: 128_000 },
|
|
36
|
+
{ id: "gpt-5.2-codex", name: "GPT-5.2 Codex", reasoning: true, contextWindow: 400_000, maxTokens: 128_000 },
|
|
37
|
+
{ id: "gpt-5.3-codex", name: "GPT-5.3 Codex", reasoning: true, contextWindow: 400_000, maxTokens: 128_000 },
|
|
38
|
+
{ id: "gpt-5.3-codex-spark-preview", name: "GPT-5.3 Codex Spark", reasoning: true, contextWindow: 128_000, maxTokens: 128_000 },
|
|
39
|
+
// Other models
|
|
40
|
+
{ id: "gemini-3.1-pro", name: "Gemini 3.1 Pro", reasoning: true, contextWindow: 1_000_000, maxTokens: 64_000 },
|
|
41
|
+
{ id: "grok-code-fast-1", name: "Grok Code Fast 1", reasoning: false, contextWindow: 128_000, maxTokens: 64_000 },
|
|
42
|
+
];
|
|
27
43
|
async function fetchCursorUsableModels(apiKey) {
|
|
28
44
|
try {
|
|
29
45
|
const requestPayload = create(GetUsableModelsRequestSchema, {});
|
|
@@ -32,31 +48,18 @@ async function fetchCursorUsableModels(apiKey) {
|
|
|
32
48
|
accessToken: apiKey,
|
|
33
49
|
rpcPath: GET_USABLE_MODELS_PATH,
|
|
34
50
|
requestBody,
|
|
35
|
-
timeoutMs: MODEL_DISCOVERY_TIMEOUT_MS,
|
|
36
51
|
});
|
|
37
|
-
if (response.timedOut) {
|
|
38
|
-
|
|
39
|
-
}
|
|
40
|
-
if (response.exitCode !== 0) {
|
|
41
|
-
throw new CursorModelDiscoveryError(buildDiscoveryHttpError(response.exitCode, response.body));
|
|
42
|
-
}
|
|
43
|
-
if (response.body.length === 0) {
|
|
44
|
-
throw new CursorModelDiscoveryError("Cursor model discovery returned an empty response.");
|
|
52
|
+
if (response.timedOut || response.exitCode !== 0 || response.body.length === 0) {
|
|
53
|
+
return null;
|
|
45
54
|
}
|
|
46
55
|
const decoded = decodeGetUsableModelsResponse(response.body);
|
|
47
|
-
if (!decoded)
|
|
48
|
-
|
|
49
|
-
}
|
|
56
|
+
if (!decoded)
|
|
57
|
+
return null;
|
|
50
58
|
const models = normalizeCursorModels(decoded.models);
|
|
51
|
-
|
|
52
|
-
throw new CursorModelDiscoveryError("Cursor model discovery returned no usable models.");
|
|
53
|
-
}
|
|
54
|
-
return models;
|
|
59
|
+
return models.length > 0 ? models : null;
|
|
55
60
|
}
|
|
56
|
-
catch
|
|
57
|
-
|
|
58
|
-
throw error;
|
|
59
|
-
throw new CursorModelDiscoveryError("Cursor model discovery failed.");
|
|
61
|
+
catch {
|
|
62
|
+
return null;
|
|
60
63
|
}
|
|
61
64
|
}
|
|
62
65
|
let cachedModels = null;
|
|
@@ -64,43 +67,13 @@ export async function getCursorModels(apiKey) {
|
|
|
64
67
|
if (cachedModels)
|
|
65
68
|
return cachedModels;
|
|
66
69
|
const discovered = await fetchCursorUsableModels(apiKey);
|
|
67
|
-
cachedModels = discovered;
|
|
70
|
+
cachedModels = discovered && discovered.length > 0 ? discovered : FALLBACK_MODELS;
|
|
68
71
|
return cachedModels;
|
|
69
72
|
}
|
|
70
73
|
/** @internal Test-only. */
|
|
71
74
|
export function clearModelCache() {
|
|
72
75
|
cachedModels = null;
|
|
73
76
|
}
|
|
74
|
-
function buildDiscoveryHttpError(exitCode, body) {
|
|
75
|
-
const detail = extractDiscoveryErrorDetail(body);
|
|
76
|
-
const protocolHint = exitCode === 464
|
|
77
|
-
? " Likely protocol mismatch: Cursor appears to expect an HTTP/2 Connect unary request."
|
|
78
|
-
: "";
|
|
79
|
-
if (!detail) {
|
|
80
|
-
return `Cursor model discovery failed with HTTP ${exitCode}.${protocolHint}`;
|
|
81
|
-
}
|
|
82
|
-
return `Cursor model discovery failed with HTTP ${exitCode}: ${detail}.${protocolHint}`;
|
|
83
|
-
}
|
|
84
|
-
function extractDiscoveryErrorDetail(body) {
|
|
85
|
-
if (body.length === 0)
|
|
86
|
-
return null;
|
|
87
|
-
const text = new TextDecoder().decode(body).trim();
|
|
88
|
-
if (!text)
|
|
89
|
-
return null;
|
|
90
|
-
try {
|
|
91
|
-
const parsed = JSON.parse(text);
|
|
92
|
-
const code = typeof parsed.code === "string" ? parsed.code : undefined;
|
|
93
|
-
const message = typeof parsed.message === "string" ? parsed.message : undefined;
|
|
94
|
-
if (message && code)
|
|
95
|
-
return `${message} (${code})`;
|
|
96
|
-
if (message)
|
|
97
|
-
return message;
|
|
98
|
-
if (code)
|
|
99
|
-
return code;
|
|
100
|
-
}
|
|
101
|
-
catch { }
|
|
102
|
-
return text.length > 200 ? `${text.slice(0, 197)}...` : text;
|
|
103
|
-
}
|
|
104
77
|
function decodeGetUsableModelsResponse(payload) {
|
|
105
78
|
try {
|
|
106
79
|
return fromBinary(GetUsableModelsResponseSchema, payload);
|
package/dist/proxy.d.ts
CHANGED
package/dist/proxy.js
CHANGED
|
@@ -16,10 +16,8 @@ import { create, fromBinary, fromJson, toBinary, toJson } from "@bufbuild/protob
|
|
|
16
16
|
import { ValueSchema } from "@bufbuild/protobuf/wkt";
|
|
17
17
|
import { AgentClientMessageSchema, AgentRunRequestSchema, AgentServerMessageSchema, BidiRequestIdSchema, ClientHeartbeatSchema, ConversationActionSchema, ConversationStateStructureSchema, ConversationStepSchema, AgentConversationTurnStructureSchema, ConversationTurnStructureSchema, AssistantMessageSchema, BackgroundShellSpawnResultSchema, DeleteResultSchema, DeleteRejectedSchema, DiagnosticsResultSchema, ExecClientMessageSchema, FetchErrorSchema, FetchResultSchema, GetBlobResultSchema, GrepErrorSchema, GrepResultSchema, KvClientMessageSchema, LsRejectedSchema, LsResultSchema, McpErrorSchema, McpResultSchema, McpSuccessSchema, McpTextContentSchema, McpToolDefinitionSchema, McpToolResultContentItemSchema, ModelDetailsSchema, ReadRejectedSchema, ReadResultSchema, RequestContextResultSchema, RequestContextSchema, RequestContextSuccessSchema, SetBlobResultSchema, ShellRejectedSchema, ShellResultSchema, UserMessageActionSchema, UserMessageSchema, WriteRejectedSchema, WriteResultSchema, WriteShellStdinErrorSchema, WriteShellStdinResultSchema, } from "./proto/agent_pb";
|
|
18
18
|
import { createHash } from "node:crypto";
|
|
19
|
-
import { connect as connectHttp2 } from "node:http2";
|
|
20
19
|
const CURSOR_API_URL = process.env.CURSOR_API_URL ?? "https://api2.cursor.sh";
|
|
21
20
|
const CURSOR_CLIENT_VERSION = "cli-2026.01.09-231024f";
|
|
22
|
-
const CURSOR_CONNECT_PROTOCOL_VERSION = "1";
|
|
23
21
|
const CONNECT_END_STREAM_FLAG = 0b00000010;
|
|
24
22
|
const SSE_HEADERS = {
|
|
25
23
|
"Content-Type": "text/event-stream",
|
|
@@ -49,19 +47,18 @@ function frameConnectMessage(data, flags = 0) {
|
|
|
49
47
|
return frame;
|
|
50
48
|
}
|
|
51
49
|
function buildCursorHeaders(options, contentType, extra = {}) {
|
|
52
|
-
const headers = new Headers(
|
|
53
|
-
return headers;
|
|
54
|
-
}
|
|
55
|
-
function buildCursorHeaderValues(options, contentType, extra = {}) {
|
|
56
|
-
return {
|
|
50
|
+
const headers = new Headers({
|
|
57
51
|
authorization: `Bearer ${options.accessToken}`,
|
|
58
52
|
"content-type": contentType,
|
|
59
53
|
"x-ghost-mode": "true",
|
|
60
54
|
"x-cursor-client-version": CURSOR_CLIENT_VERSION,
|
|
61
55
|
"x-cursor-client-type": "cli",
|
|
62
56
|
"x-request-id": crypto.randomUUID(),
|
|
63
|
-
|
|
64
|
-
|
|
57
|
+
});
|
|
58
|
+
for (const [key, value] of Object.entries(extra)) {
|
|
59
|
+
headers.set(key, value);
|
|
60
|
+
}
|
|
61
|
+
return headers;
|
|
65
62
|
}
|
|
66
63
|
function encodeVarint(value) {
|
|
67
64
|
if (!Number.isSafeInteger(value) || value < 0) {
|
|
@@ -239,17 +236,6 @@ async function createCursorSession(options) {
|
|
|
239
236
|
};
|
|
240
237
|
}
|
|
241
238
|
export async function callCursorUnaryRpc(options) {
|
|
242
|
-
const target = new URL(options.rpcPath, options.url ?? CURSOR_API_URL);
|
|
243
|
-
const transport = options.transport ?? "auto";
|
|
244
|
-
if (transport === "http2" || (transport === "auto" && target.protocol === "https:")) {
|
|
245
|
-
const http2Result = await callCursorUnaryRpcOverHttp2(options, target);
|
|
246
|
-
if (transport === "http2" || http2Result.timedOut || http2Result.exitCode !== 1) {
|
|
247
|
-
return http2Result;
|
|
248
|
-
}
|
|
249
|
-
}
|
|
250
|
-
return callCursorUnaryRpcOverFetch(options, target);
|
|
251
|
-
}
|
|
252
|
-
async function callCursorUnaryRpcOverFetch(options, target) {
|
|
253
239
|
let timedOut = false;
|
|
254
240
|
const timeoutMs = options.timeoutMs ?? 5_000;
|
|
255
241
|
const controller = new AbortController();
|
|
@@ -260,13 +246,9 @@ async function callCursorUnaryRpcOverFetch(options, target) {
|
|
|
260
246
|
}, timeoutMs)
|
|
261
247
|
: undefined;
|
|
262
248
|
try {
|
|
263
|
-
const response = await fetch(
|
|
249
|
+
const response = await fetch(new URL(options.rpcPath, options.url ?? CURSOR_API_URL), {
|
|
264
250
|
method: "POST",
|
|
265
|
-
headers: buildCursorHeaders(options, "application/proto",
|
|
266
|
-
accept: "application/proto, application/json",
|
|
267
|
-
"connect-protocol-version": CURSOR_CONNECT_PROTOCOL_VERSION,
|
|
268
|
-
"connect-timeout-ms": String(timeoutMs),
|
|
269
|
-
}),
|
|
251
|
+
headers: buildCursorHeaders(options, "application/proto"),
|
|
270
252
|
body: toFetchBody(options.requestBody),
|
|
271
253
|
signal: controller.signal,
|
|
272
254
|
});
|
|
@@ -289,96 +271,6 @@ async function callCursorUnaryRpcOverFetch(options, target) {
|
|
|
289
271
|
clearTimeout(timeout);
|
|
290
272
|
}
|
|
291
273
|
}
|
|
292
|
-
async function callCursorUnaryRpcOverHttp2(options, target) {
|
|
293
|
-
const timeoutMs = options.timeoutMs ?? 5_000;
|
|
294
|
-
const authority = `${target.protocol}//${target.host}`;
|
|
295
|
-
return new Promise((resolve) => {
|
|
296
|
-
let settled = false;
|
|
297
|
-
let timedOut = false;
|
|
298
|
-
let session;
|
|
299
|
-
let stream;
|
|
300
|
-
const finish = (result) => {
|
|
301
|
-
if (settled)
|
|
302
|
-
return;
|
|
303
|
-
settled = true;
|
|
304
|
-
if (timeout)
|
|
305
|
-
clearTimeout(timeout);
|
|
306
|
-
try {
|
|
307
|
-
stream?.close();
|
|
308
|
-
}
|
|
309
|
-
catch { }
|
|
310
|
-
try {
|
|
311
|
-
session?.close();
|
|
312
|
-
}
|
|
313
|
-
catch { }
|
|
314
|
-
resolve(result);
|
|
315
|
-
};
|
|
316
|
-
const timeout = timeoutMs > 0
|
|
317
|
-
? setTimeout(() => {
|
|
318
|
-
timedOut = true;
|
|
319
|
-
finish({
|
|
320
|
-
body: new Uint8Array(),
|
|
321
|
-
exitCode: 124,
|
|
322
|
-
timedOut: true,
|
|
323
|
-
});
|
|
324
|
-
}, timeoutMs)
|
|
325
|
-
: undefined;
|
|
326
|
-
try {
|
|
327
|
-
session = connectHttp2(authority);
|
|
328
|
-
session.once("error", () => {
|
|
329
|
-
finish({
|
|
330
|
-
body: new Uint8Array(),
|
|
331
|
-
exitCode: timedOut ? 124 : 1,
|
|
332
|
-
timedOut,
|
|
333
|
-
});
|
|
334
|
-
});
|
|
335
|
-
const headers = {
|
|
336
|
-
":method": "POST",
|
|
337
|
-
":path": `${target.pathname}${target.search}`,
|
|
338
|
-
...buildCursorHeaderValues(options, "application/proto", {
|
|
339
|
-
accept: "application/proto, application/json",
|
|
340
|
-
"connect-protocol-version": CURSOR_CONNECT_PROTOCOL_VERSION,
|
|
341
|
-
"connect-timeout-ms": String(timeoutMs),
|
|
342
|
-
}),
|
|
343
|
-
};
|
|
344
|
-
stream = session.request(headers);
|
|
345
|
-
let statusCode = 0;
|
|
346
|
-
const chunks = [];
|
|
347
|
-
stream.once("response", (responseHeaders) => {
|
|
348
|
-
const statusHeader = responseHeaders[":status"];
|
|
349
|
-
statusCode = typeof statusHeader === "number"
|
|
350
|
-
? statusHeader
|
|
351
|
-
: Number(statusHeader ?? 0);
|
|
352
|
-
});
|
|
353
|
-
stream.on("data", (chunk) => {
|
|
354
|
-
chunks.push(Buffer.from(chunk));
|
|
355
|
-
});
|
|
356
|
-
stream.once("end", () => {
|
|
357
|
-
const body = new Uint8Array(Buffer.concat(chunks));
|
|
358
|
-
finish({
|
|
359
|
-
body,
|
|
360
|
-
exitCode: statusCode >= 200 && statusCode < 300 ? 0 : (statusCode || 1),
|
|
361
|
-
timedOut,
|
|
362
|
-
});
|
|
363
|
-
});
|
|
364
|
-
stream.once("error", () => {
|
|
365
|
-
finish({
|
|
366
|
-
body: new Uint8Array(),
|
|
367
|
-
exitCode: timedOut ? 124 : 1,
|
|
368
|
-
timedOut,
|
|
369
|
-
});
|
|
370
|
-
});
|
|
371
|
-
stream.end(Buffer.from(options.requestBody));
|
|
372
|
-
}
|
|
373
|
-
catch {
|
|
374
|
-
finish({
|
|
375
|
-
body: new Uint8Array(),
|
|
376
|
-
exitCode: timedOut ? 124 : 1,
|
|
377
|
-
timedOut,
|
|
378
|
-
});
|
|
379
|
-
}
|
|
380
|
-
});
|
|
381
|
-
}
|
|
382
274
|
let proxyServer;
|
|
383
275
|
let proxyPort;
|
|
384
276
|
let proxyAccessTokenProvider;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@playwo/opencode-cursor-oauth",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.9",
|
|
4
4
|
"description": "OpenCode plugin that connects Cursor's API to OpenCode via OAuth, model discovery, and a local OpenAI-compatible proxy.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|