cursor-composer-in-claude 0.7.3
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 +303 -0
- package/dist/cli/accounts.d.ts +18 -0
- package/dist/cli/accounts.js +149 -0
- package/dist/cli/accounts.js.map +1 -0
- package/dist/cli/args.d.ts +14 -0
- package/dist/cli/args.js +93 -0
- package/dist/cli/args.js.map +1 -0
- package/dist/cli/constants.d.ts +1 -0
- package/dist/cli/constants.js +4 -0
- package/dist/cli/constants.js.map +1 -0
- package/dist/cli/login.d.ts +1 -0
- package/dist/cli/login.js +143 -0
- package/dist/cli/login.js.map +1 -0
- package/dist/cli/reset-hwid.d.ts +24 -0
- package/dist/cli/reset-hwid.js +286 -0
- package/dist/cli/reset-hwid.js.map +1 -0
- package/dist/cli/usage.d.ts +27 -0
- package/dist/cli/usage.js +177 -0
- package/dist/cli/usage.js.map +1 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +58 -0
- package/dist/cli.js.map +1 -0
- package/dist/client.d.ts +95 -0
- package/dist/client.js +316 -0
- package/dist/client.js.map +1 -0
- package/dist/lib/account-pool.d.ts +35 -0
- package/dist/lib/account-pool.js +143 -0
- package/dist/lib/account-pool.js.map +1 -0
- package/dist/lib/acp-client.d.ts +53 -0
- package/dist/lib/acp-client.js +517 -0
- package/dist/lib/acp-client.js.map +1 -0
- package/dist/lib/agent-cmd-args.d.ts +9 -0
- package/dist/lib/agent-cmd-args.js +29 -0
- package/dist/lib/agent-cmd-args.js.map +1 -0
- package/dist/lib/agent-runner.d.ts +12 -0
- package/dist/lib/agent-runner.js +144 -0
- package/dist/lib/agent-runner.js.map +1 -0
- package/dist/lib/anthropic.d.ts +26 -0
- package/dist/lib/anthropic.js +70 -0
- package/dist/lib/anthropic.js.map +1 -0
- package/dist/lib/cli-stream-parser.d.ts +8 -0
- package/dist/lib/cli-stream-parser.js +46 -0
- package/dist/lib/cli-stream-parser.js.map +1 -0
- package/dist/lib/config.d.ts +52 -0
- package/dist/lib/config.js +47 -0
- package/dist/lib/config.js.map +1 -0
- package/dist/lib/cursor-cli.d.ts +9 -0
- package/dist/lib/cursor-cli.js +30 -0
- package/dist/lib/cursor-cli.js.map +1 -0
- package/dist/lib/env.d.ts +54 -0
- package/dist/lib/env.js +247 -0
- package/dist/lib/env.js.map +1 -0
- package/dist/lib/handlers/anthropic-messages.d.ts +9 -0
- package/dist/lib/handlers/anthropic-messages.js +281 -0
- package/dist/lib/handlers/anthropic-messages.js.map +1 -0
- package/dist/lib/handlers/chat-completions.d.ts +9 -0
- package/dist/lib/handlers/chat-completions.js +275 -0
- package/dist/lib/handlers/chat-completions.js.map +1 -0
- package/dist/lib/handlers/health.d.ts +7 -0
- package/dist/lib/handlers/health.js +15 -0
- package/dist/lib/handlers/health.js.map +1 -0
- package/dist/lib/handlers/models.d.ts +15 -0
- package/dist/lib/handlers/models.js +44 -0
- package/dist/lib/handlers/models.js.map +1 -0
- package/dist/lib/http.d.ts +5 -0
- package/dist/lib/http.js +36 -0
- package/dist/lib/http.js.map +1 -0
- package/dist/lib/max-mode-preflight.d.ts +5 -0
- package/dist/lib/max-mode-preflight.js +62 -0
- package/dist/lib/max-mode-preflight.js.map +1 -0
- package/dist/lib/model-map.d.ts +17 -0
- package/dist/lib/model-map.js +62 -0
- package/dist/lib/model-map.js.map +1 -0
- package/dist/lib/openai.d.ts +17 -0
- package/dist/lib/openai.js +111 -0
- package/dist/lib/openai.js.map +1 -0
- package/dist/lib/process.d.ts +32 -0
- package/dist/lib/process.js +174 -0
- package/dist/lib/process.js.map +1 -0
- package/dist/lib/request-listener.d.ts +7 -0
- package/dist/lib/request-listener.js +99 -0
- package/dist/lib/request-listener.js.map +1 -0
- package/dist/lib/request-log.d.ts +16 -0
- package/dist/lib/request-log.js +147 -0
- package/dist/lib/request-log.js.map +1 -0
- package/dist/lib/resolve-model.d.ts +8 -0
- package/dist/lib/resolve-model.js +18 -0
- package/dist/lib/resolve-model.js.map +1 -0
- package/dist/lib/sanitize.d.ts +21 -0
- package/dist/lib/sanitize.js +79 -0
- package/dist/lib/sanitize.js.map +1 -0
- package/dist/lib/server.d.ts +13 -0
- package/dist/lib/server.js +118 -0
- package/dist/lib/server.js.map +1 -0
- package/dist/lib/token-cache.d.ts +10 -0
- package/dist/lib/token-cache.js +35 -0
- package/dist/lib/token-cache.js.map +1 -0
- package/dist/lib/win-cmdline-limit.d.ts +32 -0
- package/dist/lib/win-cmdline-limit.js +92 -0
- package/dist/lib/win-cmdline-limit.js.map +1 -0
- package/dist/lib/workspace.d.ts +19 -0
- package/dist/lib/workspace.js +77 -0
- package/dist/lib/workspace.js.map +1 -0
- package/package.json +51 -0
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
import * as https from "node:https";
|
|
2
|
+
export { TOKEN_FILE, readCachedToken, writeCachedToken, readKeychainToken, } from "../lib/token-cache.js";
|
|
3
|
+
// ---------------------------------------------------------------------------
|
|
4
|
+
// JWT helpers (no external dependencies)
|
|
5
|
+
// ---------------------------------------------------------------------------
|
|
6
|
+
export function decodeJwtPayload(token) {
|
|
7
|
+
try {
|
|
8
|
+
const parts = token.split(".");
|
|
9
|
+
if (parts.length < 2)
|
|
10
|
+
return {};
|
|
11
|
+
const padded = parts[1].replace(/-/g, "+").replace(/_/g, "/");
|
|
12
|
+
return JSON.parse(Buffer.from(padded, "base64").toString("utf-8"));
|
|
13
|
+
}
|
|
14
|
+
catch {
|
|
15
|
+
return {};
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
/** Extract the auth0 user sub from a Cursor access token (e.g. "auth0|user_01KK…"). */
|
|
19
|
+
export function tokenSub(token) {
|
|
20
|
+
const p = decodeJwtPayload(token);
|
|
21
|
+
return typeof p.sub === "string" ? p.sub : undefined;
|
|
22
|
+
}
|
|
23
|
+
// ---------------------------------------------------------------------------
|
|
24
|
+
// Cursor API
|
|
25
|
+
// ---------------------------------------------------------------------------
|
|
26
|
+
const API_BASE = "https://api2.cursor.sh";
|
|
27
|
+
function apiGet(path, token) {
|
|
28
|
+
return new Promise((resolve, reject) => {
|
|
29
|
+
const opts = {
|
|
30
|
+
hostname: "api2.cursor.sh",
|
|
31
|
+
path,
|
|
32
|
+
method: "GET",
|
|
33
|
+
headers: { Authorization: `Bearer ${token}` },
|
|
34
|
+
};
|
|
35
|
+
const req = https.request(opts, (res) => {
|
|
36
|
+
let data = "";
|
|
37
|
+
res.on("data", (c) => (data += c));
|
|
38
|
+
res.on("end", () => {
|
|
39
|
+
try {
|
|
40
|
+
resolve(JSON.parse(data));
|
|
41
|
+
}
|
|
42
|
+
catch {
|
|
43
|
+
resolve(null);
|
|
44
|
+
}
|
|
45
|
+
});
|
|
46
|
+
});
|
|
47
|
+
req.on("error", reject);
|
|
48
|
+
req.setTimeout(8000, () => {
|
|
49
|
+
req.destroy(new Error("timeout"));
|
|
50
|
+
});
|
|
51
|
+
req.end();
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
export async function fetchAccountUsage(token) {
|
|
55
|
+
try {
|
|
56
|
+
const raw = (await apiGet("/auth/usage", token));
|
|
57
|
+
if (!raw || typeof raw !== "object")
|
|
58
|
+
return null;
|
|
59
|
+
const { startOfMonth, ...rest } = raw;
|
|
60
|
+
return {
|
|
61
|
+
startOfMonth: typeof startOfMonth === "string" ? startOfMonth : "",
|
|
62
|
+
models: rest,
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
catch {
|
|
66
|
+
return null;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
/** Human-readable plan name + limits for display. */
|
|
70
|
+
export function describePlan(profile) {
|
|
71
|
+
const { membershipType, subscriptionStatus, daysRemainingOnTrial } = profile;
|
|
72
|
+
switch (membershipType) {
|
|
73
|
+
case "free_trial": {
|
|
74
|
+
const days = daysRemainingOnTrial ?? 0;
|
|
75
|
+
return `Pro Trial (${days}d left) — unlimited fast requests`;
|
|
76
|
+
}
|
|
77
|
+
case "pro":
|
|
78
|
+
case "pro_plus":
|
|
79
|
+
case "ultra":
|
|
80
|
+
return `${membershipType === "pro" ? "Pro" : membershipType === "pro_plus" ? "Pro+" : "Ultra"} — extended limits`;
|
|
81
|
+
case "free":
|
|
82
|
+
case "hobby":
|
|
83
|
+
return "Hobby (free) — limited agent requests";
|
|
84
|
+
default:
|
|
85
|
+
return `${membershipType} · ${subscriptionStatus}`;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
export async function fetchStripeProfile(token) {
|
|
89
|
+
try {
|
|
90
|
+
const raw = (await apiGet("/auth/full_stripe_profile", token));
|
|
91
|
+
if (!raw || typeof raw !== "object")
|
|
92
|
+
return null;
|
|
93
|
+
return {
|
|
94
|
+
membershipType: String(raw.membershipType ?? ""),
|
|
95
|
+
subscriptionStatus: String(raw.subscriptionStatus ?? ""),
|
|
96
|
+
daysRemainingOnTrial: typeof raw.daysRemainingOnTrial === "number"
|
|
97
|
+
? raw.daysRemainingOnTrial
|
|
98
|
+
: null,
|
|
99
|
+
isTeamMember: Boolean(raw.isTeamMember),
|
|
100
|
+
isYearlyPlan: Boolean(raw.isYearlyPlan),
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
catch {
|
|
104
|
+
return null;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
// ---------------------------------------------------------------------------
|
|
108
|
+
// Summary helpers
|
|
109
|
+
// ---------------------------------------------------------------------------
|
|
110
|
+
// Human-readable labels for Cursor's internal model/pool keys.
|
|
111
|
+
// Keys are actual identifiers from the agent binary (2026.03.20 build).
|
|
112
|
+
const MODEL_LABELS = {
|
|
113
|
+
// ── Usage pool keys (what the /auth/usage API actually returns) ──
|
|
114
|
+
"gpt-4": "Fast Premium Requests", // main premium pool (all models)
|
|
115
|
+
// ── Claude Sonnet ──
|
|
116
|
+
"claude-sonnet-4-6": "Claude Sonnet 4.6",
|
|
117
|
+
"claude-sonnet-4-5-20250929-v1": "Claude Sonnet 4.5",
|
|
118
|
+
"claude-sonnet-4-20250514-v1": "Claude Sonnet 4",
|
|
119
|
+
// ── Claude Opus ──
|
|
120
|
+
"claude-opus-4-6-v1": "Claude Opus 4.6",
|
|
121
|
+
"claude-opus-4-5-20251101-v1": "Claude Opus 4.5",
|
|
122
|
+
"claude-opus-4-1-20250805-v1": "Claude Opus 4.1",
|
|
123
|
+
"claude-opus-4-20250514-v1": "Claude Opus 4",
|
|
124
|
+
// ── Claude Haiku ──
|
|
125
|
+
"claude-haiku-4-5-20251001-v1": "Claude Haiku 4.5",
|
|
126
|
+
"claude-3-5-haiku-20241022-v1": "Claude 3.5 Haiku",
|
|
127
|
+
// ── GPT / OpenAI ──
|
|
128
|
+
"gpt-5": "GPT-5",
|
|
129
|
+
"gpt-4o": "GPT-4o",
|
|
130
|
+
o1: "o1",
|
|
131
|
+
"o3-mini": "o3-mini",
|
|
132
|
+
// ── Cursor-native ──
|
|
133
|
+
"cursor-small": "Cursor Small (free)",
|
|
134
|
+
};
|
|
135
|
+
function modelLabel(key) {
|
|
136
|
+
return MODEL_LABELS[key] ?? key;
|
|
137
|
+
}
|
|
138
|
+
export function formatUsageSummary(usage) {
|
|
139
|
+
const lines = [];
|
|
140
|
+
const start = usage.startOfMonth
|
|
141
|
+
? new Date(usage.startOfMonth).toLocaleDateString()
|
|
142
|
+
: "?";
|
|
143
|
+
lines.push(` 📅 Billing period from ${start}`);
|
|
144
|
+
const entries = Object.entries(usage.models);
|
|
145
|
+
if (entries.length === 0) {
|
|
146
|
+
lines.push(` 🔢 No requests this billing period`);
|
|
147
|
+
return lines;
|
|
148
|
+
}
|
|
149
|
+
// Sort: entries with limits first, then by usage descending
|
|
150
|
+
const sorted = entries.sort(([, a], [, b]) => {
|
|
151
|
+
if ((a.maxRequestUsage !== null) !== (b.maxRequestUsage !== null))
|
|
152
|
+
return a.maxRequestUsage !== null ? -1 : 1;
|
|
153
|
+
return b.numRequests - a.numRequests;
|
|
154
|
+
});
|
|
155
|
+
for (const [key, v] of sorted) {
|
|
156
|
+
const used = v.numRequests;
|
|
157
|
+
const max = v.maxRequestUsage;
|
|
158
|
+
const label = modelLabel(key);
|
|
159
|
+
if (max !== null && max > 0) {
|
|
160
|
+
const pct = Math.round((used / max) * 100);
|
|
161
|
+
const bar = makeBar(used, max, 12);
|
|
162
|
+
lines.push(` 🔢 ${label}: ${used}/${max} (${pct}%) [${bar}]`);
|
|
163
|
+
}
|
|
164
|
+
else if (used > 0) {
|
|
165
|
+
lines.push(` 🔢 ${label}: ${used} requests`);
|
|
166
|
+
}
|
|
167
|
+
else {
|
|
168
|
+
lines.push(` 🔢 ${label}: 0 requests (unlimited)`);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
return lines;
|
|
172
|
+
}
|
|
173
|
+
function makeBar(used, max, width) {
|
|
174
|
+
const fill = Math.round((used / max) * width);
|
|
175
|
+
return "█".repeat(fill) + "░".repeat(width - fill);
|
|
176
|
+
}
|
|
177
|
+
//# sourceMappingURL=usage.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"usage.js","sourceRoot":"","sources":["../../src/cli/usage.ts"],"names":[],"mappings":"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"}
|
package/dist/cli.d.ts
ADDED
package/dist/cli.js
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import fs from "node:fs";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { fileURLToPath } from "node:url";
|
|
5
|
+
import { loadBridgeConfig } from "./lib/config.js";
|
|
6
|
+
import { startBridgeServer, setupGracefulShutdown } from "./lib/server.js";
|
|
7
|
+
import { parseArgs, printHelp } from "./cli/args.js";
|
|
8
|
+
import { handleAccountsList, handleLogout } from "./cli/accounts.js";
|
|
9
|
+
import { handleLogin } from "./cli/login.js";
|
|
10
|
+
import { handleResetHwid } from "./cli/reset-hwid.js";
|
|
11
|
+
// Re-export parseArgs so src/cli.test.ts can import it without a separate path
|
|
12
|
+
export { parseArgs } from "./cli/args.js";
|
|
13
|
+
// ---------------------------------------------------------------------------
|
|
14
|
+
// Package metadata
|
|
15
|
+
// ---------------------------------------------------------------------------
|
|
16
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
17
|
+
const __dirname = path.dirname(__filename);
|
|
18
|
+
const pkgPath = path.join(__dirname, "..", "package.json");
|
|
19
|
+
const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf-8"));
|
|
20
|
+
// ---------------------------------------------------------------------------
|
|
21
|
+
// Entry point
|
|
22
|
+
// ---------------------------------------------------------------------------
|
|
23
|
+
async function main() {
|
|
24
|
+
const args = parseArgs(process.argv.slice(2));
|
|
25
|
+
if (args.help) {
|
|
26
|
+
printHelp(pkg.version);
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
if (args.login) {
|
|
30
|
+
await handleLogin(args.accountName, args.proxies);
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
if (args.logout) {
|
|
34
|
+
await handleLogout(args.accountName);
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
if (args.accountsList) {
|
|
38
|
+
await handleAccountsList();
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
if (args.resetHwid) {
|
|
42
|
+
await handleResetHwid({ deepClean: args.deepClean, dryRun: args.dryRun });
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
const config = loadBridgeConfig({ tailscale: args.tailscale });
|
|
46
|
+
const servers = startBridgeServer({ version: pkg.version, config });
|
|
47
|
+
setupGracefulShutdown(servers);
|
|
48
|
+
}
|
|
49
|
+
const realArgv1 = process.argv[1] ? fs.realpathSync(process.argv[1]) : "";
|
|
50
|
+
const isMainModule = realArgv1 === fs.realpathSync(__filename);
|
|
51
|
+
if (isMainModule) {
|
|
52
|
+
main().catch((err) => {
|
|
53
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
54
|
+
console.error(`Error: ${msg}`);
|
|
55
|
+
process.exit(1);
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
//# sourceMappingURL=cli.js.map
|
package/dist/cli.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"}
|
package/dist/client.d.ts
ADDED
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SDK for calling cursor-api-proxy from another project.
|
|
3
|
+
*
|
|
4
|
+
* When startProxy is true (default), the SDK will start the proxy in the
|
|
5
|
+
* background if it is not already reachable. Prerequisites: Cursor agent CLI
|
|
6
|
+
* must be installed and set up separately (see README).
|
|
7
|
+
*/
|
|
8
|
+
export type CursorProxyClientOptions = {
|
|
9
|
+
/** Proxy base URL (e.g. http://127.0.0.1:8765). Default: env CURSOR_PROXY_URL or http://127.0.0.1:8765 */
|
|
10
|
+
baseUrl?: string;
|
|
11
|
+
/** Optional API key; if the proxy is started with CURSOR_BRIDGE_API_KEY, pass it here. */
|
|
12
|
+
apiKey?: string;
|
|
13
|
+
/**
|
|
14
|
+
* When true (default), start the proxy in the background if it is not reachable.
|
|
15
|
+
* Only applies when using the default base URL. Set to false if you run the proxy yourself.
|
|
16
|
+
*/
|
|
17
|
+
startProxy?: boolean;
|
|
18
|
+
};
|
|
19
|
+
/**
|
|
20
|
+
* Ensures the proxy is running at the given base URL. If the URL is the default
|
|
21
|
+
* and the proxy is not reachable, starts it in the background (Node.js only).
|
|
22
|
+
* Resolves when /health returns 200 or rejects on timeout.
|
|
23
|
+
*/
|
|
24
|
+
export declare function ensureProxyRunning(options?: {
|
|
25
|
+
baseUrl?: string;
|
|
26
|
+
timeoutMs?: number;
|
|
27
|
+
}): Promise<string>;
|
|
28
|
+
export declare function stopManagedProxy(options?: {
|
|
29
|
+
timeoutMs?: number;
|
|
30
|
+
}): Promise<boolean>;
|
|
31
|
+
/**
|
|
32
|
+
* Options suitable for the OpenAI SDK constructor.
|
|
33
|
+
* Use: new OpenAI(getOpenAIOptions())
|
|
34
|
+
* For auto-starting the proxy first, use getOpenAIOptionsAsync() and await it.
|
|
35
|
+
*/
|
|
36
|
+
export declare function getOpenAIOptions(options?: CursorProxyClientOptions): {
|
|
37
|
+
baseURL: string;
|
|
38
|
+
apiKey: string;
|
|
39
|
+
};
|
|
40
|
+
/**
|
|
41
|
+
* Like getOpenAIOptions but ensures the proxy is running first (starts it in the background if needed).
|
|
42
|
+
* Use: new OpenAI(await getOpenAIOptionsAsync())
|
|
43
|
+
*/
|
|
44
|
+
export declare function getOpenAIOptionsAsync(options?: CursorProxyClientOptions & {
|
|
45
|
+
timeoutMs?: number;
|
|
46
|
+
}): Promise<{
|
|
47
|
+
baseURL: string;
|
|
48
|
+
apiKey: string;
|
|
49
|
+
}>;
|
|
50
|
+
/**
|
|
51
|
+
* Minimal client to call the proxy HTTP API.
|
|
52
|
+
* When startProxy is true (default), the proxy is started in the background on first request if not reachable.
|
|
53
|
+
*/
|
|
54
|
+
export declare function createCursorProxyClient(options?: CursorProxyClientOptions): {
|
|
55
|
+
/** Base URL of the proxy (no /v1 suffix) */
|
|
56
|
+
baseUrl: string;
|
|
57
|
+
/** Headers to send (Content-Type and optional Authorization) */
|
|
58
|
+
headers: Record<string, string>;
|
|
59
|
+
/** Get options for the OpenAI SDK constructor */
|
|
60
|
+
getOpenAIOptions: () => {
|
|
61
|
+
baseURL: string;
|
|
62
|
+
apiKey: string;
|
|
63
|
+
};
|
|
64
|
+
/**
|
|
65
|
+
* POST to a path (e.g. /v1/chat/completions). Body is JSON-serialized.
|
|
66
|
+
*/
|
|
67
|
+
request<T = unknown>(path: string, body: unknown): Promise<{
|
|
68
|
+
data: T;
|
|
69
|
+
ok: boolean;
|
|
70
|
+
status: number;
|
|
71
|
+
}>;
|
|
72
|
+
/** OpenAI-style chat completions (non-streaming). */
|
|
73
|
+
chatCompletionsCreate(params: {
|
|
74
|
+
model?: string;
|
|
75
|
+
messages: Array<{
|
|
76
|
+
role: string;
|
|
77
|
+
content: string;
|
|
78
|
+
}>;
|
|
79
|
+
stream?: false;
|
|
80
|
+
}): Promise<{
|
|
81
|
+
choices?: Array<{
|
|
82
|
+
message?: {
|
|
83
|
+
content?: string;
|
|
84
|
+
};
|
|
85
|
+
}>;
|
|
86
|
+
error?: {
|
|
87
|
+
message?: string;
|
|
88
|
+
};
|
|
89
|
+
}>;
|
|
90
|
+
/**
|
|
91
|
+
* Use for streaming: returns a fetch Response so you can read the body stream.
|
|
92
|
+
* Ensures the proxy is running first when startProxy is true.
|
|
93
|
+
*/
|
|
94
|
+
fetch(path: string, init?: RequestInit): Promise<Response>;
|
|
95
|
+
};
|
package/dist/client.js
ADDED
|
@@ -0,0 +1,316 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SDK for calling cursor-api-proxy from another project.
|
|
3
|
+
*
|
|
4
|
+
* When startProxy is true (default), the SDK will start the proxy in the
|
|
5
|
+
* background if it is not already reachable. Prerequisites: Cursor agent CLI
|
|
6
|
+
* must be installed and set up separately (see README).
|
|
7
|
+
*/
|
|
8
|
+
const DEFAULT_BASE_URL = "http://127.0.0.1:8765";
|
|
9
|
+
const HEALTH_PATH = "/health";
|
|
10
|
+
const PROXY_START_TIMEOUT_MS = 15_000;
|
|
11
|
+
const PROXY_POLL_MS = 200;
|
|
12
|
+
const PROXY_STOP_TIMEOUT_MS = 5_000;
|
|
13
|
+
const SHUTDOWN_SIGNALS = ["SIGINT", "SIGTERM", "SIGHUP", "SIGBREAK"];
|
|
14
|
+
let _proxyProcess = null;
|
|
15
|
+
let _managedProxyStartupPromise = null;
|
|
16
|
+
let _managedProxyStartedBySdk = false;
|
|
17
|
+
let _shutdownHandlersInstalled = false;
|
|
18
|
+
let _signalCleanupInProgress = false;
|
|
19
|
+
function killManagedProxySync() {
|
|
20
|
+
const child = _proxyProcess;
|
|
21
|
+
if (!child || !_managedProxyStartedBySdk)
|
|
22
|
+
return;
|
|
23
|
+
if (child.exitCode == null) {
|
|
24
|
+
try {
|
|
25
|
+
child.kill("SIGTERM");
|
|
26
|
+
}
|
|
27
|
+
catch { }
|
|
28
|
+
}
|
|
29
|
+
_proxyProcess = null;
|
|
30
|
+
_managedProxyStartedBySdk = false;
|
|
31
|
+
_managedProxyStartupPromise = null;
|
|
32
|
+
}
|
|
33
|
+
function installShutdownHandlers() {
|
|
34
|
+
if (_shutdownHandlersInstalled ||
|
|
35
|
+
typeof process === "undefined" ||
|
|
36
|
+
!process?.on) {
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
_shutdownHandlersInstalled = true;
|
|
40
|
+
process.on("exit", () => {
|
|
41
|
+
killManagedProxySync();
|
|
42
|
+
});
|
|
43
|
+
const handleSignal = async (signal) => {
|
|
44
|
+
if (_signalCleanupInProgress) {
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
_signalCleanupInProgress = true;
|
|
48
|
+
try {
|
|
49
|
+
await stopManagedProxy();
|
|
50
|
+
}
|
|
51
|
+
finally {
|
|
52
|
+
for (const value of SHUTDOWN_SIGNALS) {
|
|
53
|
+
process.removeListener(value, signalHandlers[value]);
|
|
54
|
+
}
|
|
55
|
+
_signalCleanupInProgress = false;
|
|
56
|
+
try {
|
|
57
|
+
process.kill(process.pid, signal);
|
|
58
|
+
}
|
|
59
|
+
catch {
|
|
60
|
+
process.exit(1);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
};
|
|
64
|
+
const signalHandlers = {
|
|
65
|
+
SIGINT: () => {
|
|
66
|
+
void handleSignal("SIGINT");
|
|
67
|
+
},
|
|
68
|
+
SIGTERM: () => {
|
|
69
|
+
void handleSignal("SIGTERM");
|
|
70
|
+
},
|
|
71
|
+
SIGHUP: () => {
|
|
72
|
+
void handleSignal("SIGHUP");
|
|
73
|
+
},
|
|
74
|
+
SIGBREAK: () => {
|
|
75
|
+
void handleSignal("SIGBREAK");
|
|
76
|
+
},
|
|
77
|
+
};
|
|
78
|
+
for (const signal of SHUTDOWN_SIGNALS) {
|
|
79
|
+
process.on(signal, signalHandlers[signal]);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
function isDefaultBaseUrl(baseUrl) {
|
|
83
|
+
const u = baseUrl.replace(/\/$/, "");
|
|
84
|
+
return u === DEFAULT_BASE_URL || u === "http://127.0.0.1:8765" || u === "http://localhost:8765";
|
|
85
|
+
}
|
|
86
|
+
async function pingHealth(baseUrl) {
|
|
87
|
+
const url = `${baseUrl.replace(/\/$/, "")}${HEALTH_PATH}`;
|
|
88
|
+
try {
|
|
89
|
+
const res = await fetch(url, { method: "GET" });
|
|
90
|
+
return res.ok;
|
|
91
|
+
}
|
|
92
|
+
catch {
|
|
93
|
+
return false;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Ensures the proxy is running at the given base URL. If the URL is the default
|
|
98
|
+
* and the proxy is not reachable, starts it in the background (Node.js only).
|
|
99
|
+
* Resolves when /health returns 200 or rejects on timeout.
|
|
100
|
+
*/
|
|
101
|
+
export async function ensureProxyRunning(options = {}) {
|
|
102
|
+
const baseUrl = options.baseUrl ??
|
|
103
|
+
((typeof process !== "undefined" && process.env?.CURSOR_PROXY_URL) ||
|
|
104
|
+
DEFAULT_BASE_URL);
|
|
105
|
+
const root = baseUrl.replace(/\/$/, "");
|
|
106
|
+
const timeoutMs = options.timeoutMs ?? PROXY_START_TIMEOUT_MS;
|
|
107
|
+
if (await pingHealth(root)) {
|
|
108
|
+
return root;
|
|
109
|
+
}
|
|
110
|
+
if (!isDefaultBaseUrl(root)) {
|
|
111
|
+
throw new Error(`cursor-api-proxy is not reachable at ${root}. Start it manually (e.g. npx cursor-api-proxy) or use the default URL for auto-start.`);
|
|
112
|
+
}
|
|
113
|
+
const isNode = typeof process !== "undefined" &&
|
|
114
|
+
process.versions?.node &&
|
|
115
|
+
typeof globalThis.fetch !== "undefined";
|
|
116
|
+
if (!isNode) {
|
|
117
|
+
throw new Error("cursor-api-proxy is not reachable. Start it manually (e.g. npx cursor-api-proxy). Auto-start is only available in Node.js.");
|
|
118
|
+
}
|
|
119
|
+
if (_managedProxyStartupPromise) {
|
|
120
|
+
return _managedProxyStartupPromise;
|
|
121
|
+
}
|
|
122
|
+
const startupPromise = (async () => {
|
|
123
|
+
const { spawn } = await import("node:child_process");
|
|
124
|
+
const pathMod = await import("node:path");
|
|
125
|
+
const path = pathMod.default;
|
|
126
|
+
const { fileURLToPath } = await import("node:url");
|
|
127
|
+
const clientDir = path.dirname(fileURLToPath(import.meta.url));
|
|
128
|
+
const cliPath = path.join(clientDir, "cli.js");
|
|
129
|
+
const child = spawn(process.execPath, [cliPath], {
|
|
130
|
+
stdio: "ignore",
|
|
131
|
+
detached: false,
|
|
132
|
+
cwd: process.cwd(),
|
|
133
|
+
env: process.env,
|
|
134
|
+
});
|
|
135
|
+
child.unref();
|
|
136
|
+
installShutdownHandlers();
|
|
137
|
+
_proxyProcess = child;
|
|
138
|
+
_managedProxyStartedBySdk = true;
|
|
139
|
+
let exitCode = null;
|
|
140
|
+
child.on("error", () => {
|
|
141
|
+
_proxyProcess = null;
|
|
142
|
+
_managedProxyStartedBySdk = false;
|
|
143
|
+
});
|
|
144
|
+
child.on("exit", (code) => {
|
|
145
|
+
exitCode = code ?? null;
|
|
146
|
+
_proxyProcess = null;
|
|
147
|
+
_managedProxyStartedBySdk = false;
|
|
148
|
+
});
|
|
149
|
+
const deadline = Date.now() + timeoutMs;
|
|
150
|
+
while (Date.now() < deadline) {
|
|
151
|
+
await new Promise((r) => setTimeout(r, PROXY_POLL_MS));
|
|
152
|
+
if (await pingHealth(root)) {
|
|
153
|
+
return root;
|
|
154
|
+
}
|
|
155
|
+
if (exitCode != null) {
|
|
156
|
+
_proxyProcess = null;
|
|
157
|
+
_managedProxyStartedBySdk = false;
|
|
158
|
+
throw new Error(`cursor-api-proxy process exited with code ${exitCode} before becoming ready. Ensure Cursor CLI is installed (agent login).`);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
if (_proxyProcess) {
|
|
162
|
+
_proxyProcess.kill();
|
|
163
|
+
_proxyProcess = null;
|
|
164
|
+
}
|
|
165
|
+
_managedProxyStartedBySdk = false;
|
|
166
|
+
throw new Error(`cursor-api-proxy did not become ready within ${timeoutMs}ms. Ensure Cursor CLI is installed (agent login).`);
|
|
167
|
+
})();
|
|
168
|
+
_managedProxyStartupPromise = startupPromise;
|
|
169
|
+
try {
|
|
170
|
+
return await startupPromise;
|
|
171
|
+
}
|
|
172
|
+
finally {
|
|
173
|
+
if (_managedProxyStartupPromise === startupPromise) {
|
|
174
|
+
_managedProxyStartupPromise = null;
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
export async function stopManagedProxy(options = {}) {
|
|
179
|
+
const child = _proxyProcess;
|
|
180
|
+
if (!child || !_managedProxyStartedBySdk) {
|
|
181
|
+
return false;
|
|
182
|
+
}
|
|
183
|
+
const timeoutMs = options.timeoutMs ?? PROXY_STOP_TIMEOUT_MS;
|
|
184
|
+
_managedProxyStartupPromise = null;
|
|
185
|
+
if (child.exitCode != null) {
|
|
186
|
+
_proxyProcess = null;
|
|
187
|
+
_managedProxyStartedBySdk = false;
|
|
188
|
+
return true;
|
|
189
|
+
}
|
|
190
|
+
const exitPromise = new Promise((resolve) => {
|
|
191
|
+
child.once("exit", () => resolve());
|
|
192
|
+
child.once("error", () => resolve());
|
|
193
|
+
});
|
|
194
|
+
child.kill("SIGTERM");
|
|
195
|
+
const exited = await Promise.race([
|
|
196
|
+
exitPromise.then(() => true),
|
|
197
|
+
new Promise((resolve) => {
|
|
198
|
+
setTimeout(() => resolve(false), timeoutMs);
|
|
199
|
+
}),
|
|
200
|
+
]);
|
|
201
|
+
if (!exited && child.exitCode == null) {
|
|
202
|
+
child.kill("SIGKILL");
|
|
203
|
+
await exitPromise;
|
|
204
|
+
}
|
|
205
|
+
_proxyProcess = null;
|
|
206
|
+
_managedProxyStartedBySdk = false;
|
|
207
|
+
return true;
|
|
208
|
+
}
|
|
209
|
+
/**
|
|
210
|
+
* Options suitable for the OpenAI SDK constructor.
|
|
211
|
+
* Use: new OpenAI(getOpenAIOptions())
|
|
212
|
+
* For auto-starting the proxy first, use getOpenAIOptionsAsync() and await it.
|
|
213
|
+
*/
|
|
214
|
+
export function getOpenAIOptions(options = {}) {
|
|
215
|
+
const baseUrl = options.baseUrl ??
|
|
216
|
+
((typeof process !== "undefined" && process.env?.CURSOR_PROXY_URL) ||
|
|
217
|
+
DEFAULT_BASE_URL);
|
|
218
|
+
const baseURL = baseUrl.endsWith("/v1") ? baseUrl : `${baseUrl.replace(/\/$/, "")}/v1`;
|
|
219
|
+
const apiKey = options.apiKey ??
|
|
220
|
+
((typeof process !== "undefined" && process.env?.CURSOR_BRIDGE_API_KEY) ||
|
|
221
|
+
"unused");
|
|
222
|
+
return { baseURL, apiKey };
|
|
223
|
+
}
|
|
224
|
+
/**
|
|
225
|
+
* Like getOpenAIOptions but ensures the proxy is running first (starts it in the background if needed).
|
|
226
|
+
* Use: new OpenAI(await getOpenAIOptionsAsync())
|
|
227
|
+
*/
|
|
228
|
+
export async function getOpenAIOptionsAsync(options = {}) {
|
|
229
|
+
const startProxy = options.startProxy !== false;
|
|
230
|
+
const baseUrl = options.baseUrl ??
|
|
231
|
+
((typeof process !== "undefined" && process.env?.CURSOR_PROXY_URL) ||
|
|
232
|
+
DEFAULT_BASE_URL);
|
|
233
|
+
const root = baseUrl.replace(/\/$/, "");
|
|
234
|
+
if (startProxy && isDefaultBaseUrl(root)) {
|
|
235
|
+
await ensureProxyRunning({ baseUrl: root, timeoutMs: options.timeoutMs });
|
|
236
|
+
}
|
|
237
|
+
return getOpenAIOptions(options);
|
|
238
|
+
}
|
|
239
|
+
/**
|
|
240
|
+
* Minimal client to call the proxy HTTP API.
|
|
241
|
+
* When startProxy is true (default), the proxy is started in the background on first request if not reachable.
|
|
242
|
+
*/
|
|
243
|
+
export function createCursorProxyClient(options = {}) {
|
|
244
|
+
const startProxy = options.startProxy !== false;
|
|
245
|
+
const baseUrl = options.baseUrl ??
|
|
246
|
+
((typeof process !== "undefined" && process.env?.CURSOR_PROXY_URL) ||
|
|
247
|
+
DEFAULT_BASE_URL);
|
|
248
|
+
const root = baseUrl.replace(/\/$/, "");
|
|
249
|
+
const apiKeyRaw = options.apiKey ??
|
|
250
|
+
(typeof process !== "undefined" ? process.env?.CURSOR_BRIDGE_API_KEY : undefined);
|
|
251
|
+
const apiKey = typeof apiKeyRaw === "string" ? apiKeyRaw : undefined;
|
|
252
|
+
const headers = {
|
|
253
|
+
"Content-Type": "application/json",
|
|
254
|
+
...(apiKey ? { Authorization: `Bearer ${apiKey}` } : {}),
|
|
255
|
+
};
|
|
256
|
+
async function ensureThenRequest(path, init) {
|
|
257
|
+
const url = path.startsWith("http")
|
|
258
|
+
? path
|
|
259
|
+
: `${root}${path.startsWith("/") ? "" : "/"}${path}`;
|
|
260
|
+
if (startProxy && isDefaultBaseUrl(root)) {
|
|
261
|
+
await ensureProxyRunning({ baseUrl: root });
|
|
262
|
+
}
|
|
263
|
+
return fetch(url, init);
|
|
264
|
+
}
|
|
265
|
+
return {
|
|
266
|
+
/** Base URL of the proxy (no /v1 suffix) */
|
|
267
|
+
baseUrl: root,
|
|
268
|
+
/** Headers to send (Content-Type and optional Authorization) */
|
|
269
|
+
headers,
|
|
270
|
+
/** Get options for the OpenAI SDK constructor */
|
|
271
|
+
getOpenAIOptions: () => getOpenAIOptions({ baseUrl: root, apiKey: apiKey ?? "unused" }),
|
|
272
|
+
/**
|
|
273
|
+
* POST to a path (e.g. /v1/chat/completions). Body is JSON-serialized.
|
|
274
|
+
*/
|
|
275
|
+
async request(path, body) {
|
|
276
|
+
const url = path.startsWith("http")
|
|
277
|
+
? path
|
|
278
|
+
: `${root}${path.startsWith("/") ? "" : "/"}${path}`;
|
|
279
|
+
const res = await ensureThenRequest(url, {
|
|
280
|
+
method: "POST",
|
|
281
|
+
headers,
|
|
282
|
+
body: JSON.stringify(body),
|
|
283
|
+
});
|
|
284
|
+
const data = (await res.json().catch(() => ({})));
|
|
285
|
+
return { data, ok: res.ok, status: res.status };
|
|
286
|
+
},
|
|
287
|
+
/** OpenAI-style chat completions (non-streaming). */
|
|
288
|
+
async chatCompletionsCreate(params) {
|
|
289
|
+
const { data, ok, status } = await this.request("/v1/chat/completions", {
|
|
290
|
+
model: params.model ?? "default",
|
|
291
|
+
messages: params.messages,
|
|
292
|
+
stream: false,
|
|
293
|
+
});
|
|
294
|
+
if (!ok) {
|
|
295
|
+
const err = data?.error?.message ?? JSON.stringify(data);
|
|
296
|
+
throw new Error(`cursor-api-proxy error (${status}): ${err}`);
|
|
297
|
+
}
|
|
298
|
+
return data;
|
|
299
|
+
},
|
|
300
|
+
/**
|
|
301
|
+
* Use for streaming: returns a fetch Response so you can read the body stream.
|
|
302
|
+
* Ensures the proxy is running first when startProxy is true.
|
|
303
|
+
*/
|
|
304
|
+
async fetch(path, init = {}) {
|
|
305
|
+
const url = path.startsWith("http")
|
|
306
|
+
? path
|
|
307
|
+
: `${root}${path.startsWith("/") ? "" : "/"}${path}`;
|
|
308
|
+
const merged = new Headers(init.headers);
|
|
309
|
+
merged.set("Content-Type", "application/json");
|
|
310
|
+
if (apiKey)
|
|
311
|
+
merged.set("Authorization", `Bearer ${apiKey}`);
|
|
312
|
+
return ensureThenRequest(url, { ...init, headers: merged });
|
|
313
|
+
},
|
|
314
|
+
};
|
|
315
|
+
}
|
|
316
|
+
//# sourceMappingURL=client.js.map
|