claude-overnight 1.24.5 → 1.24.7

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.
@@ -1 +1 @@
1
- export declare const VERSION = "1.24.5";
1
+ export declare const VERSION = "1.24.7";
package/dist/_version.js CHANGED
@@ -1,2 +1,2 @@
1
1
  // Auto-generated by build — do not edit manually.
2
- export const VERSION = "1.24.5";
2
+ export const VERSION = "1.24.7";
package/dist/index.js CHANGED
@@ -9,7 +9,7 @@ import { Swarm } from "./swarm.js";
9
9
  import { planTasks, refinePlan, identifyThemes, buildThinkingTasks, orchestrate, salvageFromFile } from "./planner.js";
10
10
  import { modelDisplayName, formatContextWindow, DEFAULT_MODEL } from "./models.js";
11
11
  import { setPlannerEnvResolver } from "./planner-query.js";
12
- import { pickModel, loadProviders, preflightProvider, buildEnvResolver, healthCheckCursorProxy, PROXY_DEFAULT_URL, isCursorProxyProvider, ensureCursorProxyRunning } from "./providers.js";
12
+ import { pickModel, loadProviders, preflightProvider, buildEnvResolver, healthCheckCursorProxy, PROXY_DEFAULT_URL, isCursorProxyProvider, ensureCursorProxyRunning, bundledComposerProxyShellCommand, } from "./providers.js";
13
13
  import { RunDisplay } from "./ui.js";
14
14
  import { renderSummary } from "./render.js";
15
15
  import { executeRun } from "./run.js";
@@ -223,7 +223,10 @@ async function main() {
223
223
  const proxyUp = await healthCheckCursorProxy();
224
224
  if (!proxyUp) {
225
225
  console.warn(chalk.yellow(`\n ⚠ ${savedCursorProviders.length} Cursor provider(s) saved but proxy is not running at ${PROXY_DEFAULT_URL}`));
226
- console.warn(chalk.yellow(` Start it: npm exec cursor-composer-in-claude (from this tool's install directory)`));
226
+ {
227
+ const cmd = bundledComposerProxyShellCommand();
228
+ console.warn(chalk.yellow(cmd ? ` Start bundled proxy: ${cmd}` : ` Run npm install where claude-overnight is installed, then retry`));
229
+ }
227
230
  console.warn(chalk.dim(` (Continuing — you can still use Anthropic models)\n`));
228
231
  }
229
232
  }
@@ -776,7 +779,10 @@ async function main() {
776
779
  if (!result.ok) {
777
780
  console.error(chalk.red(` ✗ ${role} preflight failed: ${chalk.dim(result.error)}`));
778
781
  if (isCursorProxyProvider(provider)) {
779
- console.error(chalk.yellow(` The proxy at ${PROXY_DEFAULT_URL} may have crashed or timed out (e.g. keychain/UI). Retry, or start the same version as this install: npm exec cursor-composer-in-claude (from the claude-overnight folder, not bare npx — avoids a stale global cache).`));
782
+ {
783
+ const cmd = bundledComposerProxyShellCommand();
784
+ console.error(chalk.yellow(` The proxy at ${PROXY_DEFAULT_URL} may have crashed or timed out (e.g. keychain/UI). Retry, or start the bundled proxy: ${cmd ?? "npm install in the claude-overnight package, then re-run"}`));
785
+ }
780
786
  }
781
787
  else {
782
788
  console.error(chalk.red(` Fix the provider at ~/.claude/claude-overnight/providers.json and retry.`));
@@ -1,4 +1,8 @@
1
1
  import type { ModelInfo } from "@anthropic-ai/claude-agent-sdk";
2
+ /**
3
+ * Shell command to run the same bundled proxy CLI we spawn in-process (never `npx`/global).
4
+ */
5
+ export declare function bundledComposerProxyShellCommand(): string | null;
2
6
  /**
3
7
  * A non-Anthropic model provider reachable via an Anthropic-compatible endpoint
4
8
  * (e.g. DashScope for Qwen, OpenRouter, a local proxy). Stored user-level so a
@@ -80,8 +84,8 @@ export declare function fetchCursorModels(baseUrl?: string): Promise<string[]>;
80
84
  * - Proxy not running → spawns detached, waits for health
81
85
  * - Spawn fails → returns false, caller falls back to manual instructions
82
86
  *
83
- * When `forceRestart` is true and a stale process is on the port, it will be
84
- * killed and the proxy restarted.
87
+ * When `forceRestart` is true, any listener on the port is killed and the
88
+ * bundled proxy is spawned (same as a version mismatch).
85
89
  *
86
90
  * Returns true when the proxy is reachable at PROXY_DEFAULT_URL.
87
91
  */
@@ -90,7 +94,7 @@ export declare function ensureCursorProxyRunning(baseUrl?: string, forceRestart?
90
94
  * Full install + configure flow for cursor-composer-in-claude.
91
95
  * Walks through CLI install, API key config, and proxy start.
92
96
  * Only needed when the quick auto-start (`ensureCursorProxyRunning`) fails —
93
- * e.g. npx can't find the package or the user has no API key yet.
97
+ * e.g. dependencies not installed or the user has no API key yet.
94
98
  * Returns true when proxy is running and healthy.
95
99
  */
96
100
  export declare function setupCursorProxy(): Promise<boolean>;
package/dist/providers.js CHANGED
@@ -2,6 +2,7 @@ import { readFileSync, writeFileSync, mkdirSync, existsSync, chmodSync, realpath
2
2
  import { createRequire } from "node:module";
3
3
  import { homedir } from "os";
4
4
  import { join, dirname } from "path";
5
+ import { fileURLToPath } from "node:url";
5
6
  import { execSync, spawn } from "child_process";
6
7
  import chalk from "chalk";
7
8
  import { query } from "@anthropic-ai/claude-agent-sdk";
@@ -35,6 +36,19 @@ function getEmbeddedComposerProxyVersion() {
35
36
  return null;
36
37
  }
37
38
  }
39
+ /** Directory containing this package's `package.json` (works for global and local installs). */
40
+ function getClaudeOvernightInstallRoot() {
41
+ return dirname(dirname(fileURLToPath(import.meta.url)));
42
+ }
43
+ /**
44
+ * Shell command to run the same bundled proxy CLI we spawn in-process (never `npx`/global).
45
+ */
46
+ export function bundledComposerProxyShellCommand() {
47
+ const cli = resolveCursorComposerCli();
48
+ if (!cli)
49
+ return null;
50
+ return `node "${cli}"`;
51
+ }
38
52
  // ── Store ──
39
53
  const STORE_PATH = join(homedir(), ".claude", "claude-overnight", "providers.json");
40
54
  export function getStorePath() { return STORE_PATH; }
@@ -355,26 +369,38 @@ async function getCursorProxyHealthInfo(baseUrl = PROXY_DEFAULT_URL) {
355
369
  }
356
370
  }
357
371
  /**
358
- * If something is listening and reports a different package version than our
359
- * embedded `cursor-composer-in-claude`, kill the port and start the bundled CLI.
360
- * Stops `npx` leaving an ancient proxy alive while preflight times out behind keychain/UI.
372
+ * If something is listening and we cannot prove it is this install's bundled
373
+ * `cursor-composer-in-claude` (version mismatch or missing `/health.version`),
374
+ * kill the listener and start the bundled CLI. Avoids stale global/`npx` proxies.
361
375
  */
362
376
  async function maybeRestartStaleProxy(baseUrl, url, port) {
363
377
  const embedded = getEmbeddedComposerProxyVersion();
364
378
  const info = await getCursorProxyHealthInfo(baseUrl);
365
379
  const runningV = info?.version;
366
- if (embedded && runningV && embedded !== runningV) {
367
- console.log(chalk.yellow(` ⚠ Running proxy is v${runningV} but claude-overnight depends on cursor-composer-in-claude v${embedded} — restarting…`));
368
- killProcessOnPort(port, url.hostname);
369
- await new Promise(r => setTimeout(r, 500));
370
- return startProxyProcess(baseUrl, url, port);
380
+ if (!embedded) {
381
+ console.log(chalk.dim(JSON.stringify({
382
+ claudeOvernight: VERSION,
383
+ cursorComposerExpected: null,
384
+ cursorComposerRunning: runningV ?? "unknown",
385
+ })));
386
+ return true;
371
387
  }
372
- console.log(chalk.dim(JSON.stringify({
373
- claudeOvernight: VERSION,
374
- cursorComposerExpected: embedded,
375
- cursorComposerRunning: runningV ?? "unknown",
376
- })));
377
- return true;
388
+ const trusted = Boolean(runningV && runningV === embedded);
389
+ if (trusted) {
390
+ console.log(chalk.dim(JSON.stringify({
391
+ claudeOvernight: VERSION,
392
+ cursorComposerExpected: embedded,
393
+ cursorComposerRunning: runningV,
394
+ })));
395
+ return true;
396
+ }
397
+ const reason = !runningV
398
+ ? `proxy does not report a version in /health — replacing with bundled v${embedded}`
399
+ : `running proxy is v${runningV} but this install bundles cursor-composer-in-claude v${embedded}`;
400
+ console.log(chalk.yellow(` ⚠ ${reason} — restarting…`));
401
+ killProcessOnPort(port, url.hostname);
402
+ await new Promise(r => setTimeout(r, 500));
403
+ return startProxyProcess(baseUrl, url, port);
378
404
  }
379
405
  /**
380
406
  * Fetch available Cursor models via GET /v1/models on the proxy.
@@ -456,22 +482,23 @@ async function verifyCursorProxy(baseUrl = PROXY_DEFAULT_URL) {
456
482
  const res = await fetch(`${url}/v1/models`, { method: "GET", signal: AbortSignal.timeout(3_000), ...opts });
457
483
  if (!res.ok)
458
484
  return false;
459
- const json = await res.json();
460
- return Array.isArray(json?.data);
485
+ const json = (await res.json());
486
+ return Array.isArray(json["data"]);
461
487
  }
462
488
  catch {
463
489
  return false;
464
490
  }
465
491
  }
466
492
  /**
467
- * Kill whatever process is bound to the given port. Uses `lsof` on macOS /
468
- * `fuser` on Linux. Returns the PID that was killed, or null if nothing found
469
- * or permission denied.
493
+ * Kill whatever process is listening on the given port.
494
+ * Uses `lsof` with TCP LISTEN only plain `lsof -ti :PORT` also matches
495
+ * *clients* whose remote peer is that port, so the first PID can be the
496
+ * caller (e.g. claude-overnight) and `kill -9` would suicide the CLI.
470
497
  */
471
498
  function killProcessOnPort(port, host = "127.0.0.1") {
472
499
  try {
473
- // macOS / BSD: lsof -ti :PORT gives just the PID
474
- const pid = execSync(`lsof -ti :${port} 2>/dev/null`, {
500
+ // `-sTCP:LISTEN` is required: `lsof -ti :PORT` includes ESTABLISHED clients to localhost:PORT.
501
+ const pid = execSync(`lsof -nP -iTCP:${port} -sTCP:LISTEN -t 2>/dev/null`, {
475
502
  timeout: 5_000, encoding: "utf-8",
476
503
  }).trim().split("\n")[0];
477
504
  if (!pid || !/^\d+$/.test(pid))
@@ -511,14 +538,20 @@ async function isPortInUse(port, host = "127.0.0.1") {
511
538
  * - Proxy not running → spawns detached, waits for health
512
539
  * - Spawn fails → returns false, caller falls back to manual instructions
513
540
  *
514
- * When `forceRestart` is true and a stale process is on the port, it will be
515
- * killed and the proxy restarted.
541
+ * When `forceRestart` is true, any listener on the port is killed and the
542
+ * bundled proxy is spawned (same as a version mismatch).
516
543
  *
517
544
  * Returns true when the proxy is reachable at PROXY_DEFAULT_URL.
518
545
  */
519
546
  export async function ensureCursorProxyRunning(baseUrl = PROXY_DEFAULT_URL, forceRestart = false) {
520
547
  const url = new URL(baseUrl);
521
548
  const port = parseInt(url.port, 10) || 80;
549
+ if (forceRestart && resolveCursorComposerCli()) {
550
+ console.log(chalk.dim(` Replacing listener on port ${port} with bundled cursor-composer-in-claude…`));
551
+ killProcessOnPort(port, url.hostname);
552
+ await new Promise(r => setTimeout(r, 500));
553
+ return startProxyProcess(baseUrl, url, port);
554
+ }
522
555
  // Already healthy?
523
556
  if (await healthCheckCursorProxy(baseUrl)) {
524
557
  return await maybeRestartStaleProxy(baseUrl, url, port);
@@ -584,12 +617,21 @@ async function startProxyProcess(baseUrl, url, port) {
584
617
  catch {
585
618
  cliResolved = composerCli;
586
619
  }
620
+ const bridgeKey = apiKeyEnv || apiKeyStored || "unused";
587
621
  const proxyEnv = {
588
622
  ...Object.fromEntries(Object.entries(process.env).filter(([, v]) => v !== undefined)),
589
623
  CI: "true",
590
- CURSOR_BRIDGE_API_KEY: apiKeyEnv || apiKeyStored || "unused",
624
+ CURSOR_BRIDGE_API_KEY: bridgeKey,
591
625
  CURSOR_SKIP_KEYCHAIN: "1",
592
626
  };
627
+ // cursor-composer-in-claude passes CURSOR_API_KEY / CURSOR_AUTH_TOKEN to the agent only from
628
+ // these vars — not from CURSOR_BRIDGE_API_KEY. Without them the Cursor CLI falls back to
629
+ // login/keychain (macOS dialogs, "cursor-user", hangs under preflight).
630
+ const explicitAgentKey = process.env.CURSOR_API_KEY?.trim() || process.env.CURSOR_AUTH_TOKEN?.trim();
631
+ if (!explicitAgentKey && bridgeKey !== "unused") {
632
+ proxyEnv.CURSOR_API_KEY = bridgeKey;
633
+ proxyEnv.CURSOR_AUTH_TOKEN = bridgeKey;
634
+ }
593
635
  if (sysNode && agentJs) {
594
636
  proxyEnv.CURSOR_AGENT_NODE = sysNode;
595
637
  proxyEnv.CURSOR_AGENT_SCRIPT = agentJs;
@@ -602,8 +644,13 @@ async function startProxyProcess(baseUrl, url, port) {
602
644
  cliPath: cliResolved,
603
645
  nodeExec: process.execPath,
604
646
  apiKey: keySource,
647
+ agentCursorKey: explicitAgentKey ? "env CURSOR_API_KEY or CURSOR_AUTH_TOKEN" : (bridgeKey === "unused" ? "none" : "mirrored from bridge key"),
605
648
  agentPaths: sysNode && agentJs ? { node: sysNode, script: agentJs } : undefined,
606
- childEnv: { CI: proxyEnv.CI, CURSOR_SKIP_KEYCHAIN: proxyEnv.CURSOR_SKIP_KEYCHAIN },
649
+ childEnv: {
650
+ CI: proxyEnv.CI,
651
+ CURSOR_SKIP_KEYCHAIN: proxyEnv.CURSOR_SKIP_KEYCHAIN,
652
+ CURSOR_API_KEY: proxyEnv.CURSOR_API_KEY ? "(set)" : "(unset)",
653
+ },
607
654
  },
608
655
  })));
609
656
  try {
@@ -638,6 +685,18 @@ async function startProxyProcess(baseUrl, url, port) {
638
685
  return false;
639
686
  }
640
687
  }
688
+ function tryBundledComposerHelp() {
689
+ const cli = resolveCursorComposerCli();
690
+ if (!cli)
691
+ return false;
692
+ try {
693
+ execSync(`node "${cli}" --help`, { stdio: "pipe", timeout: 10_000 });
694
+ return true;
695
+ }
696
+ catch {
697
+ return false;
698
+ }
699
+ }
641
700
  function setupSteps() {
642
701
  return [
643
702
  {
@@ -666,19 +725,11 @@ function setupSteps() {
666
725
  successMsg: "Cursor API key configured",
667
726
  },
668
727
  {
669
- label: "cursor-composer-in-claude server",
670
- check: () => {
671
- try {
672
- execSync("npx cursor-composer-in-claude --help", { stdio: "pipe", timeout: 10_000 });
673
- return true;
674
- }
675
- catch {
676
- return false;
677
- }
678
- },
679
- autoCmd: "npx cursor-composer-in-claude",
680
- manualCmd: "npx cursor-composer-in-claude",
681
- successMsg: "cursor-composer-in-claude available",
728
+ label: "cursor-composer-in-claude (bundled dependency)",
729
+ check: () => tryBundledComposerHelp(),
730
+ autoCmd: "npm install",
731
+ manualCmd: "npm install",
732
+ successMsg: "Bundled proxy package installed",
682
733
  },
683
734
  ];
684
735
  }
@@ -722,7 +773,7 @@ async function promptAndSaveCursorKey() {
722
773
  * Full install + configure flow for cursor-composer-in-claude.
723
774
  * Walks through CLI install, API key config, and proxy start.
724
775
  * Only needed when the quick auto-start (`ensureCursorProxyRunning`) fails —
725
- * e.g. npx can't find the package or the user has no API key yet.
776
+ * e.g. dependencies not installed or the user has no API key yet.
726
777
  * Returns true when proxy is running and healthy.
727
778
  */
728
779
  export async function setupCursorProxy() {
@@ -777,40 +828,41 @@ export async function setupCursorProxy() {
777
828
  console.log(chalk.yellow(" No API key — the proxy won't authenticate without one."));
778
829
  }
779
830
  }
780
- // ── Step 3: Proxy server auto-start if needed ──
831
+ // ── Step 3: Bundled proxy dependency ──
781
832
  const proxyStep = steps[2];
833
+ const installRoot = getClaudeOvernightInstallRoot();
782
834
  if (proxyStep.check()) {
783
835
  console.log(chalk.green(` ✓ ${proxyStep.successMsg}`));
784
836
  }
785
837
  else {
786
- console.log(chalk.yellow(`\n ${proxyStep.label} not installed`));
787
- const choice = await selectKey(` Install and start it?`, [
788
- { key: "a", desc: "uto (install + start)" },
838
+ console.log(chalk.yellow(`\n ${proxyStep.label} missing under node_modules`));
839
+ const choice = await selectKey(` Run npm install in this claude-overnight install?`, [
840
+ { key: "a", desc: "uto (npm install)" },
789
841
  { key: "m", desc: "anual (show commands)" },
790
842
  { key: "s", desc: "kip (I'll handle it)" },
791
843
  ]);
792
844
  if (choice === "a") {
793
- console.log(chalk.dim(` Checking install…`));
845
+ console.log(chalk.dim(` Running: npm install in ${installRoot}`));
794
846
  try {
795
- execSync("npx cursor-composer-in-claude --help", { stdio: "pipe", timeout: 15_000 });
796
- console.log(chalk.green(` ✓ cursor-composer-in-claude is installed`));
847
+ execSync("npm install", { cwd: installRoot, stdio: "inherit", timeout: 180_000 });
797
848
  }
798
849
  catch {
799
- console.log(chalk.dim(` Installing…`));
800
- try {
801
- execSync("npm install -g cursor-composer-in-claude", { stdio: "inherit", timeout: 120_000 });
802
- console.log(chalk.green(` ✓ Installed`));
803
- }
804
- catch {
805
- console.log(chalk.yellow(" Install failed — try manual: npm install -g cursor-composer-in-claude"));
806
- return false;
807
- }
850
+ console.log(chalk.yellow(" npm install failed."));
851
+ return false;
852
+ }
853
+ if (!tryBundledComposerHelp()) {
854
+ console.log(chalk.yellow(" cursor-composer-in-claude still missing after npm install."));
855
+ return false;
808
856
  }
857
+ console.log(chalk.green(` ✓ ${proxyStep.successMsg}`));
809
858
  }
810
859
  else if (choice === "m") {
811
- console.log(chalk.cyan(`\n Install: ${chalk.bold("npm install -g cursor-composer-in-claude")}`));
812
- console.log(chalk.cyan(` Start: ${chalk.bold("npx cursor-composer-in-claude")}\n`));
813
- const ok = await selectKey(` Started it?`, [
860
+ console.log(chalk.cyan(`\n From ${chalk.bold(installRoot)}:`));
861
+ console.log(chalk.white(` ${chalk.bold("npm install")}`));
862
+ const cmd = bundledComposerProxyShellCommand();
863
+ if (cmd)
864
+ console.log(chalk.white(` ${chalk.bold(cmd)}\n`));
865
+ const ok = await selectKey(` Done?`, [
814
866
  { key: "r", desc: "eady" },
815
867
  { key: "c", desc: "ancel" },
816
868
  ]);
@@ -822,12 +874,14 @@ export async function setupCursorProxy() {
822
874
  return false;
823
875
  }
824
876
  }
825
- // Auto-start the proxy (detached background process)
877
+ // Auto-start the proxy (detached only the bundled CLI)
826
878
  if (await ensureCursorProxyRunning())
827
879
  return true;
828
- // Auto-start failed or not responding — offer manual fallback
829
- console.log(chalk.yellow(`\n Couldn't start the proxy automatically. Start it manually:`));
830
- console.log(chalk.white(` ${chalk.bold("npx cursor-composer-in-claude")}`));
880
+ const manual = bundledComposerProxyShellCommand();
881
+ console.log(chalk.yellow(`\n Couldn't start the proxy automatically.`));
882
+ console.log(chalk.cyan(` Ensure dependencies: ${chalk.bold(`cd "${installRoot}" && npm install`)}`));
883
+ if (manual)
884
+ console.log(chalk.cyan(` Start bundled proxy: ${chalk.bold(manual)}`));
831
885
  for (;;) {
832
886
  const choice = await selectKey(` Proxy started?`, [
833
887
  { key: "r", desc: "etry (re-attempt auto-start + kill stale)" },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-overnight",
3
- "version": "1.24.5",
3
+ "version": "1.24.7",
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": {
@@ -16,7 +16,7 @@
16
16
  "dependencies": {
17
17
  "@anthropic-ai/claude-agent-sdk": "^0.2.92",
18
18
  "chalk": "^5.4.1",
19
- "cursor-composer-in-claude": "0.7.7",
19
+ "cursor-composer-in-claude": "0.7.8",
20
20
  "jsonwebtoken": "^9.0.2"
21
21
  },
22
22
  "devDependencies": {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-overnight",
3
- "version": "1.24.5",
3
+ "version": "1.24.7",
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"