cursor-openai-byok 1.0.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.
@@ -0,0 +1,302 @@
1
+ "use strict";
2
+
3
+ const __bundlePath = require("path");
4
+ const __bundleRoot = __bundlePath.resolve(__dirname, "..");
5
+ const __bundleModules = {
6
+ "extension/extension.js": function(module, exports, require, __filename, __dirname) {
7
+ "use strict";
8
+
9
+ const vscode = require("vscode");
10
+ const fs = require("fs");
11
+ const path = require("path");
12
+ const childProcess = require("child_process");
13
+
14
+ let daemon = null;
15
+ let panel = null;
16
+
17
+ function activate(context) {
18
+ const root = path.resolve(__dirname, "..");
19
+ const paths = require(path.join(root, "src", "paths"));
20
+ const config = require(path.join(root, "src", "config"));
21
+ const cursorModels = require(path.join(root, "src", "cursor-models"));
22
+
23
+ config.writeDefaultConfig();
24
+ syncModels(config, cursorModels);
25
+ startServer(root, paths);
26
+
27
+ context.subscriptions.push(
28
+ vscode.commands.registerCommand("cursorOpenaiByok.openSettings", () => openSettings(context, root, paths, config, cursorModels)),
29
+ vscode.commands.registerCommand("cursorOpenaiByok.startServer", async () => {
30
+ const result = startServer(root, paths);
31
+ vscode.window.showInformationMessage(statusText(result));
32
+ syncModels(config, cursorModels);
33
+ refreshPanel(paths, config);
34
+ }),
35
+ vscode.commands.registerCommand("cursorOpenaiByok.stopServer", async () => {
36
+ const result = stopServer(paths);
37
+ vscode.window.showInformationMessage(statusText(result));
38
+ refreshPanel(paths, config);
39
+ }),
40
+ vscode.commands.registerCommand("cursorOpenaiByok.restartServer", async () => {
41
+ stopServer(paths);
42
+ const result = startServer(root, paths);
43
+ vscode.window.showInformationMessage(statusText(result));
44
+ syncModels(config, cursorModels);
45
+ refreshPanel(paths, config);
46
+ }),
47
+ vscode.commands.registerCommand("cursorOpenaiByok.showStatus", async () => {
48
+ const status = await getStatus(paths, config);
49
+ vscode.window.showInformationMessage(status.running ? `BYOK server running on ${status.url}` : "BYOK server stopped");
50
+ })
51
+ );
52
+ }
53
+
54
+ function deactivate() {
55
+ stopServer(require(path.join(path.resolve(__dirname, ".."), "src", "paths")));
56
+ }
57
+
58
+ function startServer(root, paths) {
59
+ const existing = readPid(paths);
60
+ if (existing && pidAlive(existing)) return { alreadyRunning: true, pid: existing };
61
+
62
+ const daemonScript = path.join(root, "src", "daemon-entry.js");
63
+ fs.mkdirSync(paths.configDir(), { recursive: true });
64
+ const out = fs.openSync(paths.logPath(), "a");
65
+ const err = fs.openSync(paths.logPath(), "a");
66
+ daemon = childProcess.spawn(process.execPath, [daemonScript], {
67
+ cwd: root,
68
+ detached: false,
69
+ stdio: ["ignore", out, err],
70
+ env: { ...process.env },
71
+ });
72
+ fs.writeFileSync(paths.pidPath(), String(daemon.pid));
73
+ daemon.once("exit", () => {
74
+ daemon = null;
75
+ try { fs.unlinkSync(paths.pidPath()); } catch {}
76
+ });
77
+ return { started: true, pid: daemon.pid };
78
+ }
79
+
80
+ function stopServer(paths) {
81
+ const pid = daemon && daemon.pid ? daemon.pid : readPid(paths);
82
+ if (!pid || !pidAlive(pid)) return { stopped: false };
83
+ try { process.kill(pid, "SIGTERM"); } catch {}
84
+ try { fs.unlinkSync(paths.pidPath()); } catch {}
85
+ daemon = null;
86
+ return { stopped: true, pid };
87
+ }
88
+
89
+ function readPid(paths) {
90
+ try {
91
+ const pid = Number(fs.readFileSync(paths.pidPath(), "utf8"));
92
+ return pid || null;
93
+ } catch {
94
+ return null;
95
+ }
96
+ }
97
+
98
+ function pidAlive(pid) {
99
+ try {
100
+ process.kill(pid, 0);
101
+ return true;
102
+ } catch {
103
+ return false;
104
+ }
105
+ }
106
+
107
+ async function getStatus(paths, config) {
108
+ let cfg;
109
+ try {
110
+ cfg = config.loadConfig();
111
+ } catch (err) {
112
+ return { running: false, configError: err.message, url: "http://127.0.0.1:39832" };
113
+ }
114
+ const host = cfg.server?.host || "127.0.0.1";
115
+ const port = cfg.server?.port || 39832;
116
+ const url = `http://${host}:${port}`;
117
+ try {
118
+ const res = await fetch(`${url}/health`);
119
+ const json = await res.json();
120
+ return { running: res.ok && json.ok, url, health: json, models: config.listModels(cfg), configPath: paths.configPath() };
121
+ } catch (err) {
122
+ return { running: false, url, error: err.message, models: config.listModels(cfg), configPath: paths.configPath() };
123
+ }
124
+ }
125
+
126
+ async function waitForHealth(paths, config, timeoutMs = 2000) {
127
+ const started = Date.now();
128
+ while (Date.now() - started < timeoutMs) {
129
+ const status = await getStatus(paths, config);
130
+ if (status.running) return true;
131
+ await new Promise((resolve) => setTimeout(resolve, 120));
132
+ }
133
+ return false;
134
+ }
135
+
136
+ function openSettings(context, root, paths, config, cursorModels) {
137
+ if (panel) {
138
+ panel.reveal(vscode.ViewColumn.One);
139
+ syncModels(config, cursorModels);
140
+ refreshPanel(paths, config);
141
+ return;
142
+ }
143
+ panel = vscode.window.createWebviewPanel(
144
+ "cursorOpenaiByokSettings",
145
+ "Cursor OpenAI BYOK",
146
+ vscode.ViewColumn.One,
147
+ { enableScripts: true }
148
+ );
149
+ panel.onDidDispose(() => { panel = null; }, null, context.subscriptions);
150
+ panel.webview.onDidReceiveMessage(async (message) => {
151
+ if (message.command === "refresh") return refreshPanel(paths, config);
152
+ if (message.command === "start") {
153
+ startServer(root, paths);
154
+ await waitForHealth(paths, config);
155
+ syncModels(config, cursorModels);
156
+ return refreshPanel(paths, config);
157
+ }
158
+ if (message.command === "stop") {
159
+ stopServer(paths);
160
+ return refreshPanel(paths, config);
161
+ }
162
+ if (message.command === "restart") {
163
+ stopServer(paths);
164
+ startServer(root, paths);
165
+ await waitForHealth(paths, config);
166
+ syncModels(config, cursorModels);
167
+ return refreshPanel(paths, config);
168
+ }
169
+ if (message.command === "openConfig") {
170
+ const doc = await vscode.workspace.openTextDocument(paths.configPath());
171
+ await vscode.window.showTextDocument(doc, vscode.ViewColumn.Beside);
172
+ return refreshPanel(paths, config);
173
+ }
174
+ if (message.command === "openLog") {
175
+ const doc = await vscode.workspace.openTextDocument(paths.logPath());
176
+ await vscode.window.showTextDocument(doc, vscode.ViewColumn.Beside);
177
+ }
178
+ });
179
+ panel.webview.html = renderHtml({ loading: true });
180
+ syncModels(config, cursorModels);
181
+ refreshPanel(paths, config);
182
+ }
183
+
184
+ function syncModels(config, cursorModels) {
185
+ try {
186
+ const cfg = config.loadConfig();
187
+ return cursorModels.syncCursorStorage(config.listModels(cfg));
188
+ } catch (err) {
189
+ console.warn("cursor-openai-byok model storage sync failed", err);
190
+ return { skipped: true, reason: err.message };
191
+ }
192
+ }
193
+
194
+ async function refreshPanel(paths, config) {
195
+ if (!panel) return;
196
+ const status = await getStatus(paths, config);
197
+ panel.webview.html = renderHtml(status);
198
+ }
199
+
200
+ function renderHtml(status) {
201
+ const nonce = String(Date.now());
202
+ const models = (status.models || []).map((m) => `<li><code>${escapeHtml(m.displayName)}</code> -> <code>${escapeHtml(m.apiModel)}</code> <span>${escapeHtml(m.providerName || m.providerId || "")}</span></li>`).join("");
203
+ return `<!doctype html>
204
+ <html>
205
+ <head>
206
+ <meta charset="utf-8">
207
+ <meta http-equiv="Content-Security-Policy" content="default-src 'none'; style-src 'unsafe-inline'; script-src 'nonce-${nonce}';">
208
+ <style>
209
+ body { font-family: var(--vscode-font-family); color: var(--vscode-foreground); padding: 20px; }
210
+ .row { margin: 12px 0; }
211
+ button { margin-right: 8px; padding: 6px 10px; }
212
+ code { color: var(--vscode-textLink-foreground); }
213
+ .ok { color: #4caf50; }
214
+ .bad { color: #f44336; }
215
+ .panel { border: 1px solid var(--vscode-panel-border); padding: 12px; margin-top: 12px; }
216
+ </style>
217
+ </head>
218
+ <body>
219
+ <h2>Cursor OpenAI BYOK</h2>
220
+ <div class="panel">
221
+ <div class="row">Server: <strong class="${status.running ? "ok" : "bad"}">${status.loading ? "loading" : status.running ? "running" : "stopped"}</strong></div>
222
+ <div class="row">URL: <code>${escapeHtml(status.url || "http://127.0.0.1:39832")}</code></div>
223
+ ${status.configError ? `<div class="row bad">Config error: ${escapeHtml(status.configError)}</div>` : ""}
224
+ ${status.error ? `<div class="row bad">Health error: ${escapeHtml(status.error)}</div>` : ""}
225
+ <div class="row">
226
+ <button data-command="refresh">Refresh</button>
227
+ <button data-command="start">Start</button>
228
+ <button data-command="stop">Stop</button>
229
+ <button data-command="restart">Restart</button>
230
+ <button data-command="openConfig">Open Config</button>
231
+ <button data-command="openLog">Open Log</button>
232
+ </div>
233
+ </div>
234
+ <div class="panel">
235
+ <h3>Configured Models</h3>
236
+ <ul>${models || "<li>No configured models</li>"}</ul>
237
+ </div>
238
+ <script nonce="${nonce}">
239
+ const vscode = acquireVsCodeApi();
240
+ document.querySelectorAll("button[data-command]").forEach((button) => {
241
+ button.addEventListener("click", () => vscode.postMessage({ command: button.dataset.command }));
242
+ });
243
+ </script>
244
+ </body>
245
+ </html>`;
246
+ }
247
+
248
+ function statusText(result) {
249
+ if (result.started) return `BYOK server started: pid ${result.pid}`;
250
+ if (result.alreadyRunning) return `BYOK server already running: pid ${result.pid}`;
251
+ if (result.stopped) return `BYOK server stopped: pid ${result.pid}`;
252
+ return "BYOK server is not running";
253
+ }
254
+
255
+ function escapeHtml(value) {
256
+ return String(value == null ? "" : value).replace(/[<>&'"]/g, (ch) => ({
257
+ "<": "&lt;",
258
+ ">": "&gt;",
259
+ "&": "&amp;",
260
+ "'": "&#39;",
261
+ "\"": "&quot;",
262
+ }[ch]));
263
+ }
264
+
265
+ module.exports = { activate, deactivate };
266
+
267
+ }
268
+ };
269
+ const __bundleCache = Object.create(null);
270
+ const __bundleNodeRequire = require;
271
+
272
+ function __bundleNormalize(id) {
273
+ return id.split(__bundlePath.sep).join("/");
274
+ }
275
+
276
+ function __bundleResolve(fromId, request) {
277
+ if (!request.startsWith(".")) return null;
278
+ const base = __bundlePath.resolve(__bundleRoot, __bundlePath.dirname(fromId), request);
279
+ for (const candidate of [base, base + ".js", __bundlePath.join(base, "index.js")]) {
280
+ const id = __bundleNormalize(__bundlePath.relative(__bundleRoot, candidate));
281
+ if (__bundleModules[id]) return id;
282
+ }
283
+ throw new Error("Cannot resolve " + request + " from " + fromId);
284
+ }
285
+
286
+ function __bundleRequire(id) {
287
+ if (__bundleCache[id]) return __bundleCache[id].exports;
288
+ const factory = __bundleModules[id];
289
+ if (!factory) return __bundleNodeRequire(id);
290
+ const module = { exports: {} };
291
+ __bundleCache[id] = module;
292
+ const filename = __bundlePath.join(__bundleRoot, id);
293
+ const dirname = __bundlePath.dirname(filename);
294
+ function localRequire(request) {
295
+ const localId = __bundleResolve(id, request);
296
+ return localId ? __bundleRequire(localId) : __bundleNodeRequire(request);
297
+ }
298
+ factory(module, module.exports, localRequire, filename, dirname);
299
+ return module.exports;
300
+ }
301
+
302
+ module.exports = __bundleRequire("extension/extension.js");
package/package.json ADDED
@@ -0,0 +1,77 @@
1
+ {
2
+ "name": "cursor-openai-byok",
3
+ "version": "1.0.2",
4
+ "description": "Local BYOK bridge for Cursor using OpenAI-compatible providers.",
5
+ "displayName": "Cursor OpenAI BYOK",
6
+ "publisher": "cursor-openai-byok",
7
+ "type": "commonjs",
8
+ "main": "./lib/extension.js",
9
+ "repository": {
10
+ "type": "git",
11
+ "url": "git+https://git.3weijia.com/base/ai/cursor-openai-byok.git"
12
+ },
13
+ "keywords": [
14
+ "cursor",
15
+ "byok",
16
+ "openai",
17
+ "openai-compatible",
18
+ "local-bridge"
19
+ ],
20
+ "files": [
21
+ "bin/",
22
+ "lib/",
23
+ "scripts/install.sh",
24
+ "scripts/uninstall.sh",
25
+ "scripts/install.ps1",
26
+ "scripts/uninstall.ps1",
27
+ "config.example.json",
28
+ "README.md",
29
+ "TROUBLESHOOTING.md"
30
+ ],
31
+ "publishConfig": {
32
+ "access": "public",
33
+ "registry": "https://registry.npmjs.org/"
34
+ },
35
+ "activationEvents": [
36
+ "*"
37
+ ],
38
+ "contributes": {
39
+ "commands": [
40
+ {
41
+ "command": "cursorOpenaiByok.openSettings",
42
+ "title": "Cursor OpenAI BYOK: Open Settings"
43
+ },
44
+ {
45
+ "command": "cursorOpenaiByok.startServer",
46
+ "title": "Cursor OpenAI BYOK: Start Server"
47
+ },
48
+ {
49
+ "command": "cursorOpenaiByok.stopServer",
50
+ "title": "Cursor OpenAI BYOK: Stop Server"
51
+ },
52
+ {
53
+ "command": "cursorOpenaiByok.restartServer",
54
+ "title": "Cursor OpenAI BYOK: Restart Server"
55
+ },
56
+ {
57
+ "command": "cursorOpenaiByok.showStatus",
58
+ "title": "Cursor OpenAI BYOK: Show Status"
59
+ }
60
+ ]
61
+ },
62
+ "bin": {
63
+ "cursor-openai-byok": "bin/cursor-openai-byok.js"
64
+ },
65
+ "scripts": {
66
+ "build:npm": "node scripts/build-npm.js",
67
+ "test": "node --test",
68
+ "doctor": "node bin/cursor-openai-byok.js doctor",
69
+ "prepack": "npm run build:npm",
70
+ "package": "npm run build:npm && node scripts/package.js"
71
+ },
72
+ "engines": {
73
+ "vscode": "^1.99.0",
74
+ "node": ">=20"
75
+ },
76
+ "license": "UNLICENSED"
77
+ }
@@ -0,0 +1,3 @@
1
+ $ErrorActionPreference = "Stop"
2
+ $Root = Split-Path -Parent (Split-Path -Parent $MyInvocation.MyCommand.Path)
3
+ node (Join-Path $Root "bin/cursor-openai-byok.js") install @args
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ DIR="$(cd "$(dirname "$0")/.." && pwd)"
4
+ node "$DIR/bin/cursor-openai-byok.js" install "$@"
@@ -0,0 +1,3 @@
1
+ $ErrorActionPreference = "Stop"
2
+ $Root = Split-Path -Parent (Split-Path -Parent $MyInvocation.MyCommand.Path)
3
+ node (Join-Path $Root "bin/cursor-openai-byok.js") uninstall @args
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ DIR="$(cd "$(dirname "$0")/.." && pwd)"
4
+ node "$DIR/bin/cursor-openai-byok.js" uninstall "$@"