claude-overnight 1.19.1 → 1.23.0
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/dist/_version.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const VERSION = "1.
|
|
1
|
+
export declare const VERSION = "1.23.0";
|
package/dist/_version.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
// Auto-generated by build — do not edit manually.
|
|
2
|
-
export const VERSION = "1.
|
|
2
|
+
export const VERSION = "1.23.0";
|
package/dist/providers.d.ts
CHANGED
|
@@ -58,6 +58,7 @@ export declare const PROXY_DEFAULT_URL = "http://127.0.0.1:8765";
|
|
|
58
58
|
export declare function isCursorProxyProvider(p: ProviderConfig): boolean;
|
|
59
59
|
/**
|
|
60
60
|
* Health check: GET /health on the proxy. Returns true if proxy is reachable.
|
|
61
|
+
* Passes the stored API key so the /health endpoint doesn't return 401.
|
|
61
62
|
*/
|
|
62
63
|
export declare function healthCheckCursorProxy(baseUrl?: string): Promise<boolean>;
|
|
63
64
|
/**
|
|
@@ -81,9 +82,12 @@ export declare function fetchCursorModels(baseUrl?: string): Promise<string[]>;
|
|
|
81
82
|
* - Proxy not running → spawns `npx cursor-api-proxy` detached, waits for health
|
|
82
83
|
* - Spawn fails (not installed) → returns false, caller falls back to manual instructions
|
|
83
84
|
*
|
|
85
|
+
* When `forceRestart` is true and a stale process is on the port, it will be
|
|
86
|
+
* killed and the proxy restarted.
|
|
87
|
+
*
|
|
84
88
|
* Returns true when the proxy is reachable at PROXY_DEFAULT_URL.
|
|
85
89
|
*/
|
|
86
|
-
export declare function ensureCursorProxyRunning(baseUrl?: string): Promise<boolean>;
|
|
90
|
+
export declare function ensureCursorProxyRunning(baseUrl?: string, forceRestart?: boolean): Promise<boolean>;
|
|
87
91
|
/**
|
|
88
92
|
* Full install + configure flow for cursor-api-proxy.
|
|
89
93
|
* Walks through CLI install, API key config, and proxy start.
|
package/dist/providers.js
CHANGED
|
@@ -262,13 +262,28 @@ export const PROXY_DEFAULT_URL = "http://127.0.0.1:8765";
|
|
|
262
262
|
export function isCursorProxyProvider(p) {
|
|
263
263
|
return p.cursorProxy === true || p.baseURL === PROXY_DEFAULT_URL;
|
|
264
264
|
}
|
|
265
|
+
/** Resolve the cursor-api-proxy API key from env or providers.json. */
|
|
266
|
+
function resolveCursorProxyKey() {
|
|
267
|
+
if (process.env.CURSOR_BRIDGE_API_KEY?.trim())
|
|
268
|
+
return process.env.CURSOR_BRIDGE_API_KEY.trim();
|
|
269
|
+
const saved = loadProviders().find(p => p.cursorProxy);
|
|
270
|
+
if (saved?.cursorApiKey?.trim())
|
|
271
|
+
return saved.cursorApiKey.trim();
|
|
272
|
+
return null;
|
|
273
|
+
}
|
|
274
|
+
/** Build fetch options with the cursor proxy auth header if a key is available. */
|
|
275
|
+
function cursorProxyFetchOpts() {
|
|
276
|
+
const key = resolveCursorProxyKey();
|
|
277
|
+
return key ? { headers: { Authorization: `Bearer ${key}` } } : {};
|
|
278
|
+
}
|
|
265
279
|
/**
|
|
266
280
|
* Health check: GET /health on the proxy. Returns true if proxy is reachable.
|
|
281
|
+
* Passes the stored API key so the /health endpoint doesn't return 401.
|
|
267
282
|
*/
|
|
268
283
|
export async function healthCheckCursorProxy(baseUrl = PROXY_DEFAULT_URL) {
|
|
269
284
|
const url = `${baseUrl.replace(/\/$/, "")}/health`;
|
|
270
285
|
try {
|
|
271
|
-
const res = await fetch(url, { method: "GET", signal: AbortSignal.timeout(3_000) });
|
|
286
|
+
const res = await fetch(url, { method: "GET", signal: AbortSignal.timeout(3_000), ...cursorProxyFetchOpts() });
|
|
272
287
|
return res.ok;
|
|
273
288
|
}
|
|
274
289
|
catch {
|
|
@@ -282,7 +297,7 @@ export async function healthCheckCursorProxy(baseUrl = PROXY_DEFAULT_URL) {
|
|
|
282
297
|
export async function fetchCursorModels(baseUrl = PROXY_DEFAULT_URL) {
|
|
283
298
|
const url = `${baseUrl.replace(/\/$/, "")}/v1/models`;
|
|
284
299
|
try {
|
|
285
|
-
const res = await fetch(url, { method: "GET", signal: AbortSignal.timeout(5_000) });
|
|
300
|
+
const res = await fetch(url, { method: "GET", signal: AbortSignal.timeout(5_000), ...cursorProxyFetchOpts() });
|
|
286
301
|
if (!res.ok)
|
|
287
302
|
return [];
|
|
288
303
|
const json = await res.json();
|
|
@@ -336,22 +351,52 @@ async function fetchLiveCursorModels() {
|
|
|
336
351
|
}
|
|
337
352
|
/**
|
|
338
353
|
* Verify something is actually cursor-api-proxy (not just any HTTP service on the port).
|
|
339
|
-
*
|
|
354
|
+
* Tries /health first (proxy identity), then falls back to /v1/models shape check.
|
|
355
|
+
* Returns true if it looks like the proxy.
|
|
340
356
|
*/
|
|
341
357
|
async function verifyCursorProxy(baseUrl = PROXY_DEFAULT_URL) {
|
|
342
|
-
const url =
|
|
358
|
+
const url = baseUrl.replace(/\/$/, "");
|
|
359
|
+
const opts = cursorProxyFetchOpts();
|
|
360
|
+
// /health is the most reliable proxy identity check — works even when
|
|
361
|
+
// /v1/models fails due to agent subprocess crash (macOS segfault).
|
|
343
362
|
try {
|
|
344
|
-
const res = await fetch(url
|
|
363
|
+
const res = await fetch(`${url}/health`, { method: "GET", signal: AbortSignal.timeout(3_000), ...opts });
|
|
364
|
+
if (res.ok)
|
|
365
|
+
return true;
|
|
366
|
+
}
|
|
367
|
+
catch { }
|
|
368
|
+
// Fallback: check /v1/models response shape
|
|
369
|
+
try {
|
|
370
|
+
const res = await fetch(`${url}/v1/models`, { method: "GET", signal: AbortSignal.timeout(3_000), ...opts });
|
|
345
371
|
if (!res.ok)
|
|
346
372
|
return false;
|
|
347
373
|
const json = await res.json();
|
|
348
|
-
// cursor-api-proxy returns { data: [{ id: "...", ... }] }
|
|
349
374
|
return Array.isArray(json?.data);
|
|
350
375
|
}
|
|
351
376
|
catch {
|
|
352
377
|
return false;
|
|
353
378
|
}
|
|
354
379
|
}
|
|
380
|
+
/**
|
|
381
|
+
* Kill whatever process is bound to the given port. Uses `lsof` on macOS /
|
|
382
|
+
* `fuser` on Linux. Returns the PID that was killed, or null if nothing found
|
|
383
|
+
* or permission denied.
|
|
384
|
+
*/
|
|
385
|
+
function killProcessOnPort(port, host = "127.0.0.1") {
|
|
386
|
+
try {
|
|
387
|
+
// macOS / BSD: lsof -ti :PORT gives just the PID
|
|
388
|
+
const pid = execSync(`lsof -ti :${port} 2>/dev/null`, {
|
|
389
|
+
timeout: 5_000, encoding: "utf-8",
|
|
390
|
+
}).trim().split("\n")[0];
|
|
391
|
+
if (!pid || !/^\d+$/.test(pid))
|
|
392
|
+
return null;
|
|
393
|
+
execSync(`kill -9 ${pid} 2>/dev/null`, { timeout: 5_000 });
|
|
394
|
+
return parseInt(pid, 10);
|
|
395
|
+
}
|
|
396
|
+
catch {
|
|
397
|
+
return null;
|
|
398
|
+
}
|
|
399
|
+
}
|
|
355
400
|
/**
|
|
356
401
|
* Check whether something is already listening on the proxy port.
|
|
357
402
|
* Returns true if any process bound the port (another instance, Cursor CLI, etc.).
|
|
@@ -437,6 +482,35 @@ function patchProxyEnvJs(proxyDir) {
|
|
|
437
482
|
}
|
|
438
483
|
return true;
|
|
439
484
|
}
|
|
485
|
+
/**
|
|
486
|
+
* Patch the proxy's token-cache.js to skip the macOS keychain read.
|
|
487
|
+
*
|
|
488
|
+
* The proxy calls `security find-generic-password -s "cursor-access-token" -w`
|
|
489
|
+
* after every agent run to cache tokens for its multi-account pool feature.
|
|
490
|
+
* This triggers a macOS keychain popup even though the key is not needed for
|
|
491
|
+
* auth (the cursor-agent subprocess handles its own auth). We neutralize it.
|
|
492
|
+
*
|
|
493
|
+
* Safe to call repeatedly — the patch is idempotent.
|
|
494
|
+
*/
|
|
495
|
+
function patchProxyTokenCacheJs(proxyDir) {
|
|
496
|
+
const tcJs = join(proxyDir, "dist", "lib", "token-cache.js");
|
|
497
|
+
if (!existsSync(tcJs))
|
|
498
|
+
return false;
|
|
499
|
+
const src = readFileSync(tcJs, "utf-8");
|
|
500
|
+
// Check if already patched
|
|
501
|
+
if (src.includes("/* claude-overnight patch: skip keychain */"))
|
|
502
|
+
return true;
|
|
503
|
+
const patch = `\n/* claude-overnight patch: skip keychain */\n` +
|
|
504
|
+
`return undefined;`;
|
|
505
|
+
// Replace the entire execSync chain inside readKeychainToken (multi-line)
|
|
506
|
+
const target = `const t = execSync('security find-generic-password -s "cursor-access-token" -w', { stdio: ["pipe", "pipe", "pipe"], timeout: 5000 })
|
|
507
|
+
.toString()
|
|
508
|
+
.trim();`;
|
|
509
|
+
if (!src.includes(target))
|
|
510
|
+
return false;
|
|
511
|
+
writeFileSync(tcJs, src.replace(target, patch + "\n// " + target.replace(/\n/g, "\n// ")), "utf-8");
|
|
512
|
+
return true;
|
|
513
|
+
}
|
|
440
514
|
/**
|
|
441
515
|
* Find the cursor-api-proxy package directory (npx cache or global install).
|
|
442
516
|
*/
|
|
@@ -482,14 +556,30 @@ function findProxyPackageDir() {
|
|
|
482
556
|
* - Proxy not running → spawns `npx cursor-api-proxy` detached, waits for health
|
|
483
557
|
* - Spawn fails (not installed) → returns false, caller falls back to manual instructions
|
|
484
558
|
*
|
|
559
|
+
* When `forceRestart` is true and a stale process is on the port, it will be
|
|
560
|
+
* killed and the proxy restarted.
|
|
561
|
+
*
|
|
485
562
|
* Returns true when the proxy is reachable at PROXY_DEFAULT_URL.
|
|
486
563
|
*/
|
|
487
|
-
export async function ensureCursorProxyRunning(baseUrl = PROXY_DEFAULT_URL) {
|
|
564
|
+
export async function ensureCursorProxyRunning(baseUrl = PROXY_DEFAULT_URL, forceRestart = false) {
|
|
488
565
|
const url = new URL(baseUrl);
|
|
489
566
|
const port = parseInt(url.port, 10) || 80;
|
|
567
|
+
// Always patch the npx cache on startup so proxy skips keychain reads.
|
|
568
|
+
// Idempotent — safe to call on every run.
|
|
569
|
+
const proxyDir = findProxyPackageDir();
|
|
570
|
+
if (proxyDir)
|
|
571
|
+
patchProxyTokenCacheJs(proxyDir);
|
|
490
572
|
// Already healthy?
|
|
491
|
-
if (await healthCheckCursorProxy(baseUrl))
|
|
573
|
+
if (await healthCheckCursorProxy(baseUrl)) {
|
|
574
|
+
// Proxy was running before the patch — restart it to load the patched token-cache.js.
|
|
575
|
+
console.log(chalk.dim(` Proxy already running — restarting to pick up keychain patch…`));
|
|
576
|
+
const killedPid = killProcessOnPort(port, url.hostname);
|
|
577
|
+
if (killedPid) {
|
|
578
|
+
await new Promise(r => setTimeout(r, 500));
|
|
579
|
+
return startProxyProcess(baseUrl, url, port);
|
|
580
|
+
}
|
|
492
581
|
return true;
|
|
582
|
+
}
|
|
493
583
|
// Something bound the port — verify it's actually the cursor proxy
|
|
494
584
|
if (await isPortInUse(port, url.hostname)) {
|
|
495
585
|
const isProxy = await verifyCursorProxy(baseUrl);
|
|
@@ -497,11 +587,25 @@ export async function ensureCursorProxyRunning(baseUrl = PROXY_DEFAULT_URL) {
|
|
|
497
587
|
console.log(chalk.dim(` Proxy verified at port ${port}`));
|
|
498
588
|
return true;
|
|
499
589
|
}
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
590
|
+
// Stale process on the port — kill it if forceRestart, or try automatically
|
|
591
|
+
if (!forceRestart) {
|
|
592
|
+
console.log(chalk.yellow(` ⚠ Something is on port ${port} but it's not cursor-api-proxy — killing stale process…`));
|
|
593
|
+
}
|
|
594
|
+
const killedPid = killProcessOnPort(port, url.hostname);
|
|
595
|
+
if (killedPid) {
|
|
596
|
+
console.log(chalk.green(` ✓ Killed stale process PID ${killedPid} on port ${port}`));
|
|
597
|
+
await new Promise(r => setTimeout(r, 500));
|
|
598
|
+
return startProxyProcess(baseUrl, url, port);
|
|
599
|
+
}
|
|
600
|
+
// Couldn't kill (permission denied, already gone) — try starting anyway
|
|
601
|
+
console.log(chalk.yellow(` ⚠ Couldn't kill process on port ${port} — attempting to start proxy anyway…`));
|
|
602
|
+
return startProxyProcess(baseUrl, url, port);
|
|
503
603
|
}
|
|
504
604
|
// Port is free — auto-start the proxy
|
|
605
|
+
return startProxyProcess(baseUrl, url, port);
|
|
606
|
+
}
|
|
607
|
+
/** Spawn the proxy process and wait for it to become healthy. */
|
|
608
|
+
async function startProxyProcess(baseUrl, url, port) {
|
|
505
609
|
console.log(chalk.yellow(`\n Proxy not running at ${baseUrl} — starting it for you…`));
|
|
506
610
|
// Resolve system node and agent index.js so the proxy doesn't use the
|
|
507
611
|
// agent's bundled node (segfaults with --list-models on macOS).
|
|
@@ -746,11 +850,11 @@ export async function setupCursorProxy() {
|
|
|
746
850
|
console.log(chalk.white(` ${chalk.bold("npx cursor-api-proxy")}`));
|
|
747
851
|
for (;;) {
|
|
748
852
|
const choice = await selectKey(` Proxy started?`, [
|
|
749
|
-
{ key: "r", desc: "etry" },
|
|
853
|
+
{ key: "r", desc: "etry (re-attempt auto-start + kill stale)" },
|
|
750
854
|
{ key: "c", desc: "ancel" },
|
|
751
855
|
]);
|
|
752
856
|
if (choice === "r") {
|
|
753
|
-
if (await
|
|
857
|
+
if (await ensureCursorProxyRunning(PROXY_DEFAULT_URL, true)) {
|
|
754
858
|
console.log(chalk.green("\n ✓ Proxy is running and healthy"));
|
|
755
859
|
return true;
|
|
756
860
|
}
|
|
@@ -802,36 +906,38 @@ async function pickCursorModel() {
|
|
|
802
906
|
clearInterval(spinner);
|
|
803
907
|
process.stdout.write("\x1B[2K\r");
|
|
804
908
|
if (!healthy) {
|
|
805
|
-
// Try to auto-start the proxy
|
|
909
|
+
// Try to auto-start the proxy (auto-kills stale processes)
|
|
806
910
|
const autoStarted = await ensureCursorProxyRunning();
|
|
807
911
|
if (autoStarted) {
|
|
808
912
|
// Proxy is up now — proceed to model list
|
|
809
913
|
}
|
|
810
914
|
else {
|
|
811
915
|
console.log(chalk.yellow(" Proxy is not running at " + PROXY_DEFAULT_URL));
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
916
|
+
for (;;) {
|
|
917
|
+
const choice = await selectKey(` How to proceed?`, [
|
|
918
|
+
{ key: "r", desc: "etry (re-attempt auto-start + kill stale)" },
|
|
919
|
+
{ key: "i", desc: "nstall + configure (CLI, API key, server)" },
|
|
920
|
+
{ key: "c", desc: "ancel" },
|
|
921
|
+
]);
|
|
922
|
+
if (choice === "r") {
|
|
923
|
+
if (await ensureCursorProxyRunning(PROXY_DEFAULT_URL, true)) {
|
|
924
|
+
console.log(chalk.green(" ✓ Proxy started"));
|
|
925
|
+
break;
|
|
926
|
+
}
|
|
927
|
+
console.log(chalk.yellow(` Still not reachable at ${PROXY_DEFAULT_URL}`));
|
|
928
|
+
}
|
|
929
|
+
else if (choice === "i") {
|
|
930
|
+
const ok = await setupCursorProxy();
|
|
931
|
+
if (!ok)
|
|
932
|
+
return null;
|
|
933
|
+
if (await healthCheckCursorProxy())
|
|
934
|
+
break;
|
|
820
935
|
return null;
|
|
821
|
-
}
|
|
822
|
-
else if (choice === "r") {
|
|
823
|
-
// User manually started it — one more health check
|
|
824
|
-
if (await healthCheckCursorProxy()) {
|
|
825
|
-
console.log(chalk.green(" ✓ Proxy detected"));
|
|
826
936
|
}
|
|
827
937
|
else {
|
|
828
|
-
console.log(chalk.yellow(" Still not reachable — try the install flow or cancel."));
|
|
829
938
|
return null;
|
|
830
939
|
}
|
|
831
940
|
}
|
|
832
|
-
else {
|
|
833
|
-
return null;
|
|
834
|
-
}
|
|
835
941
|
}
|
|
836
942
|
}
|
|
837
943
|
const { top, more } = await buildCursorPicker();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "claude-overnight",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.23.0",
|
|
4
4
|
"description": "Background lane for your Claude Max plan. Parallel Claude Agent SDK sessions in git worktrees with a usage cap that reserves headroom for your interactive Claude Code. Crash-safe resume. Provider-agnostic model catalog with capability-based planning.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "claude-overnight",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.23.0",
|
|
4
4
|
"description": "Claude Code skill for understanding, installing, and inspecting claude-overnight runs -- parallel Claude agents in git worktrees with thinking waves, multi-wave steering, and crash-safe resume. Supports Cursor API Proxy, Qwen, OpenRouter.",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Francesco Fornace"
|