claude-overnight 1.17.2 → 1.18.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/README.md +35 -0
- package/dist/cursor-models.d.ts +14 -0
- package/dist/cursor-models.js +50 -0
- package/dist/index.js +2 -1
- package/dist/models.d.ts +27 -0
- package/dist/models.js +60 -0
- package/dist/planner-query.d.ts +2 -3
- package/dist/planner-query.js +3 -31
- package/dist/planner.js +6 -4
- package/dist/providers.d.ts +3 -1
- package/dist/providers.js +161 -64
- package/dist/state.js +2 -1
- package/package.json +3 -2
- package/plugins/claude-overnight/.claude-plugin/plugin.json +2 -2
package/README.md
CHANGED
|
@@ -4,6 +4,8 @@
|
|
|
4
4
|
|
|
5
5
|
Your Max plan rate limits eat interactive coding time. One deep refactor and the 5-hour window is gone before lunch. `claude-overnight` runs background agent sessions up to the percentage cap you pick (90% is typical), leaving the rest free for your own Claude Code session. Hand it an objective and a session budget, walk away, review the diff when the run ends.
|
|
6
6
|
|
|
7
|
+
Cursor API Proxy supported -- route through Cursor's model gateway for Composer-powered execution on `auto`, `composer`, or `composer-2` models. See **Run via Cursor API Proxy** below.
|
|
8
|
+
|
|
7
9
|
Isolated by default. Every agent runs in its own git worktree on its own branch, so a misbehaving agent can't trash your working tree. You choose what agents can do before the run starts -- no surprise escalation mid-flight. Unmerged branches are preserved for manual review, never discarded. Built on the [Claude Agent SDK](https://www.npmjs.com/package/@anthropic-ai/claude-agent-sdk) -- not a Claude Code replacement, but a background lane that runs alongside it.
|
|
8
10
|
|
|
9
11
|
Different shape from hosted agent harnesses like [Claude Managed Agents](https://platform.claude.com/docs/en/managed-agents/overview): instead of one agent in one cloud container billed separately, you get many parallel sessions on your own machine, in your real repo, against your own Max plan (or API key). Works with Claude Opus, Sonnet, and Haiku -- or pair an Anthropic planner with a cheaper executor on Qwen, OpenRouter, or any Anthropic-compatible endpoint.
|
|
@@ -33,6 +35,39 @@ export ANTHROPIC_MODEL="qwen3.6-plus"
|
|
|
33
35
|
claude-overnight
|
|
34
36
|
```
|
|
35
37
|
|
|
38
|
+
## Run via Cursor API Proxy
|
|
39
|
+
|
|
40
|
+
Use Cursor's model gateway as an executor -- `auto` (delegates to best available), `composer`, or `composer-2` models. Runs locally through a proxy that speaks the Anthropic Messages API, so it's a drop-in replacement for any other provider.
|
|
41
|
+
|
|
42
|
+
1. **Install the Cursor CLI and proxy:**
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
curl https://cursor.com/install -fsS | bash
|
|
46
|
+
npm install -g cursor-api-proxy
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
2. **Get an API key.** Visit [cursor.com/dashboard/integrations](https://cursor.com/dashboard/integrations) and scroll to the "API Keys" section.
|
|
50
|
+
|
|
51
|
+
3. **Set up.** Run `claude-overnight` and when prompted to pick a model, choose **Cursor…**. It walks you through a one-time setup: CLI check, API key entry (persisted to `providers.json`), and proxy health check.
|
|
52
|
+
|
|
53
|
+
4. **Start the proxy** (in a separate terminal):
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
npx cursor-api-proxy
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
5. Pick your model (`auto`, `composer`, `composer-2`, etc.). The provider is saved and reappears in every future run.
|
|
60
|
+
|
|
61
|
+
Or configure the key manually:
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
export CURSOR_BRIDGE_API_KEY="sk-..."
|
|
65
|
+
npx cursor-api-proxy &
|
|
66
|
+
claude-overnight
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
**Tip:** run `claude-overnight` with the `--model=cursor-auto` flag in non-interactive mode to skip the picker. If the proxy isn't running at startup, a warning is shown but Anthropic providers remain available.
|
|
70
|
+
|
|
36
71
|
## Install
|
|
37
72
|
|
|
38
73
|
```bash
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export declare const CURSOR_PRIORITY_MODELS: Array<{
|
|
2
|
+
id: string;
|
|
3
|
+
label: string;
|
|
4
|
+
hint: string;
|
|
5
|
+
}>;
|
|
6
|
+
export declare const CURSOR_KNOWN_MODELS: Array<{
|
|
7
|
+
id: string;
|
|
8
|
+
label: string;
|
|
9
|
+
hint: string;
|
|
10
|
+
}>;
|
|
11
|
+
/** All known model IDs as a Set for quick membership checks. */
|
|
12
|
+
export declare const KNOWN_CURSOR_MODEL_IDS: Set<string>;
|
|
13
|
+
/** Display hint for a model ID — known ones get a hint, unknowns get a generic label. */
|
|
14
|
+
export declare function cursorModelHint(modelId: string): string;
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
// ── Cursor model constants ──
|
|
2
|
+
//
|
|
3
|
+
// Hardcoded model IDs returned by the Cursor API Proxy. These serve as a
|
|
4
|
+
// fallback when `agent --list-models` crashes (the bundled Node.js binary
|
|
5
|
+
// segfaults on some setups — the proxy inherits this bug).
|
|
6
|
+
//
|
|
7
|
+
// Update this list when Cursor adds/removes models. Run:
|
|
8
|
+
// node ~/.local/share/cursor-agent/versions/*/index.js --list-models
|
|
9
|
+
// to get the current list.
|
|
10
|
+
//
|
|
11
|
+
// The `priority` models always appear at the top of the picker in this order.
|
|
12
|
+
// `known` models appear after them. Anything the proxy returns dynamically
|
|
13
|
+
// that isn't in this list goes into a "more..." sub-menu.
|
|
14
|
+
import { CURSOR_MODEL_HINTS } from "./models.js";
|
|
15
|
+
export const CURSOR_PRIORITY_MODELS = [
|
|
16
|
+
{ id: "composer-2", label: "composer-2", hint: "Cursor Composer 2 — latest, strongest Cursor model" },
|
|
17
|
+
{ id: "composer-2-fast", label: "composer-2-fast", hint: "Cursor Composer 2 Fast — faster, cheaper variant" },
|
|
18
|
+
{ id: "auto", label: "auto", hint: "auto-delegates to the best available model" },
|
|
19
|
+
];
|
|
20
|
+
export const CURSOR_KNOWN_MODELS = [
|
|
21
|
+
{ id: "composer", label: "composer", hint: "Cursor Composer — previous generation" },
|
|
22
|
+
];
|
|
23
|
+
/** All known model IDs as a Set for quick membership checks. */
|
|
24
|
+
export const KNOWN_CURSOR_MODEL_IDS = new Set([
|
|
25
|
+
...CURSOR_PRIORITY_MODELS.map(m => m.id),
|
|
26
|
+
...CURSOR_KNOWN_MODELS.map(m => m.id),
|
|
27
|
+
]);
|
|
28
|
+
/** Display hint for a model ID — known ones get a hint, unknowns get a generic label. */
|
|
29
|
+
export function cursorModelHint(modelId) {
|
|
30
|
+
const m = modelId.toLowerCase();
|
|
31
|
+
for (const entry of [...CURSOR_PRIORITY_MODELS, ...CURSOR_KNOWN_MODELS]) {
|
|
32
|
+
if (entry.id === m)
|
|
33
|
+
return entry.hint;
|
|
34
|
+
}
|
|
35
|
+
if (m.startsWith("composer"))
|
|
36
|
+
return "Cursor Composer model";
|
|
37
|
+
if (m.includes("opus"))
|
|
38
|
+
return CURSOR_MODEL_HINTS.opus;
|
|
39
|
+
if (m.includes("sonnet"))
|
|
40
|
+
return CURSOR_MODEL_HINTS.sonnet;
|
|
41
|
+
if (m.includes("haiku"))
|
|
42
|
+
return CURSOR_MODEL_HINTS.haiku;
|
|
43
|
+
if (m.startsWith("gpt-5"))
|
|
44
|
+
return "GPT model via Cursor";
|
|
45
|
+
if (m.startsWith("gemini"))
|
|
46
|
+
return "Gemini model via Cursor";
|
|
47
|
+
if (m.startsWith("grok"))
|
|
48
|
+
return "Grok model via Cursor";
|
|
49
|
+
return "Cursor model";
|
|
50
|
+
}
|
package/dist/index.js
CHANGED
|
@@ -10,6 +10,7 @@ import { query } from "@anthropic-ai/claude-agent-sdk";
|
|
|
10
10
|
import { Swarm } from "./swarm.js";
|
|
11
11
|
import { planTasks, refinePlan, identifyThemes, buildThinkingTasks, orchestrate, salvageFromFile } from "./planner.js";
|
|
12
12
|
import { detectModelTier, setPlannerEnvResolver } from "./planner-query.js";
|
|
13
|
+
import { DEFAULT_MODEL } from "./models.js";
|
|
13
14
|
import { pickModel, loadProviders, preflightProvider, buildEnvResolver, healthCheckCursorProxy, PROXY_DEFAULT_URL, isCursorProxyProvider } from "./providers.js";
|
|
14
15
|
import { RunDisplay } from "./ui.js";
|
|
15
16
|
import { renderSummary } from "./render.js";
|
|
@@ -665,7 +666,7 @@ async function main() {
|
|
|
665
666
|
const defaultModel = activeProvider?.model
|
|
666
667
|
?? models[0]?.value
|
|
667
668
|
?? savedForCLI.find(p => p !== activeProvider)?.model
|
|
668
|
-
??
|
|
669
|
+
?? DEFAULT_MODEL;
|
|
669
670
|
workerModel = cliFlags.model ?? fileCfg?.model ?? defaultModel;
|
|
670
671
|
plannerModel = activeProvider?.model ?? models[0]?.value ?? workerModel;
|
|
671
672
|
// Auto-resolve a saved custom provider if --model matches its id or model id.
|
package/dist/models.d.ts
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
export declare const MODEL_TIER_OPUS = "opus";
|
|
2
|
+
export declare const MODEL_TIER_SONNET = "sonnet";
|
|
3
|
+
export declare const MODEL_TIER_HAIKU = "haiku";
|
|
4
|
+
export declare const MODEL_TIER_UNKNOWN = "unknown";
|
|
5
|
+
export type ModelTier = typeof MODEL_TIER_OPUS | typeof MODEL_TIER_SONNET | typeof MODEL_TIER_HAIKU | typeof MODEL_TIER_UNKNOWN;
|
|
6
|
+
export interface TierDetectionRule {
|
|
7
|
+
match: (model: string) => boolean;
|
|
8
|
+
tier: ModelTier;
|
|
9
|
+
}
|
|
10
|
+
export declare const TIER_DETECTION_RULES: TierDetectionRule[];
|
|
11
|
+
export declare function detectModelTier(model: string): ModelTier;
|
|
12
|
+
export declare const MODEL_CAPABILITY_DESCRIPTIONS: Record<ModelTier, string>;
|
|
13
|
+
export declare const UNKNOWN_MODEL_CAPABILITIES: Record<string, string>;
|
|
14
|
+
export declare function modelCapabilityBlock(model: string): string;
|
|
15
|
+
export declare const DEFAULT_MODEL = "claude-sonnet-4-6";
|
|
16
|
+
export declare const FALLBACK_MODEL = "claude-opus-4-6";
|
|
17
|
+
export declare const PLANNER_THRESHOLDS: {
|
|
18
|
+
opus: {
|
|
19
|
+
small: number;
|
|
20
|
+
medium: number;
|
|
21
|
+
};
|
|
22
|
+
default: {
|
|
23
|
+
small: number;
|
|
24
|
+
medium: number;
|
|
25
|
+
};
|
|
26
|
+
};
|
|
27
|
+
export declare const CURSOR_MODEL_HINTS: Record<string, string>;
|
package/dist/models.js
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
// ── Model tier constants ──
|
|
2
|
+
export const MODEL_TIER_OPUS = "opus";
|
|
3
|
+
export const MODEL_TIER_SONNET = "sonnet";
|
|
4
|
+
export const MODEL_TIER_HAIKU = "haiku";
|
|
5
|
+
export const MODEL_TIER_UNKNOWN = "unknown";
|
|
6
|
+
export const TIER_DETECTION_RULES = [
|
|
7
|
+
{ match: m => m === "default" || m.includes("opus"), tier: MODEL_TIER_OPUS },
|
|
8
|
+
{ match: m => m.includes("sonnet"), tier: MODEL_TIER_SONNET },
|
|
9
|
+
{ match: m => m.includes("haiku"), tier: MODEL_TIER_HAIKU },
|
|
10
|
+
{ match: m => m === "auto", tier: MODEL_TIER_UNKNOWN },
|
|
11
|
+
{ match: m => m.startsWith("composer"), tier: MODEL_TIER_SONNET },
|
|
12
|
+
{ match: m => m.startsWith("gpt-5") || m.startsWith("gemini") || m.startsWith("grok"), tier: MODEL_TIER_SONNET },
|
|
13
|
+
];
|
|
14
|
+
export function detectModelTier(model) {
|
|
15
|
+
const m = model.toLowerCase();
|
|
16
|
+
for (const rule of TIER_DETECTION_RULES) {
|
|
17
|
+
if (rule.match(m))
|
|
18
|
+
return rule.tier;
|
|
19
|
+
}
|
|
20
|
+
return MODEL_TIER_UNKNOWN;
|
|
21
|
+
}
|
|
22
|
+
// ── Capability descriptions ──
|
|
23
|
+
export const MODEL_CAPABILITY_DESCRIPTIONS = {
|
|
24
|
+
opus: "Each agent runs Claude Opus with 1M context -- a powerhouse. It can own entire epics, do deep codebase research, make architectural decisions, implement complex multi-file systems end-to-end, use browser tools for analysis, and deliver expert-level work. These agents can work for 30+ minutes on the most complex tasks. Do NOT waste them on trivial edits -- give them ownership and autonomy.",
|
|
25
|
+
sonnet: "Each agent runs Claude Sonnet -- capable of substantial implementation, refactoring, testing, and design work. Can work autonomously for 10-20 minutes on complex tasks. Give agents meaningful scope -- not just single-line edits.",
|
|
26
|
+
haiku: "Each agent runs Claude Haiku -- fast and efficient, best for focused, well-specified tasks. Be explicit about files, functions, and expected changes. Keep tasks scoped to a clear, concrete deliverable.",
|
|
27
|
+
unknown: "", // handled by UNKNOWN_MODEL_CAPABILITIES below
|
|
28
|
+
};
|
|
29
|
+
export const UNKNOWN_MODEL_CAPABILITIES = {
|
|
30
|
+
composer: "Each agent runs a Cursor Composer model with full codebase access. Capable of focused implementation work. Be explicit about files, functions, and expected changes.",
|
|
31
|
+
"gpt-5": "Each agent runs a GPT model via Cursor with full codebase access. Capable of focused implementation work. Be explicit about files, functions, and expected changes.",
|
|
32
|
+
gemini: "Each agent runs a Gemini model via Cursor with full codebase access. Be explicit about files, functions, and expected changes.",
|
|
33
|
+
grok: "Each agent runs a Grok model via Cursor with full codebase access. Be explicit about files, functions, and expected changes.",
|
|
34
|
+
};
|
|
35
|
+
export function modelCapabilityBlock(model) {
|
|
36
|
+
const tier = detectModelTier(model);
|
|
37
|
+
const cap = MODEL_CAPABILITY_DESCRIPTIONS[tier];
|
|
38
|
+
if (cap)
|
|
39
|
+
return cap;
|
|
40
|
+
const m = model.toLowerCase();
|
|
41
|
+
for (const [prefix, desc] of Object.entries(UNKNOWN_MODEL_CAPABILITIES)) {
|
|
42
|
+
if (m.startsWith(prefix))
|
|
43
|
+
return desc;
|
|
44
|
+
}
|
|
45
|
+
return `Each agent has full codebase access and can work autonomously.`;
|
|
46
|
+
}
|
|
47
|
+
// ── Default / fallback models ──
|
|
48
|
+
export const DEFAULT_MODEL = "claude-sonnet-4-6";
|
|
49
|
+
export const FALLBACK_MODEL = "claude-opus-4-6"; // used for planner + worker recovery
|
|
50
|
+
// ── Planner thresholds (opus-tuned vs default) ──
|
|
51
|
+
export const PLANNER_THRESHOLDS = {
|
|
52
|
+
opus: { small: 5, medium: 30 },
|
|
53
|
+
default: { small: 15, medium: 50 },
|
|
54
|
+
};
|
|
55
|
+
// ── Cursor model hints ──
|
|
56
|
+
export const CURSOR_MODEL_HINTS = {
|
|
57
|
+
opus: "Opus-tier model via Cursor",
|
|
58
|
+
sonnet: "Sonnet-tier model via Cursor",
|
|
59
|
+
haiku: "Haiku-tier model via Cursor (fast)",
|
|
60
|
+
};
|
package/dist/planner-query.d.ts
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import type { Task, PermMode, RateLimitWindow } from "./types.js";
|
|
2
|
+
import { detectModelTier, modelCapabilityBlock } from "./models.js";
|
|
3
|
+
export { detectModelTier, modelCapabilityBlock };
|
|
2
4
|
/**
|
|
3
5
|
* Logging callback used by planner/steering queries.
|
|
4
6
|
* `kind` distinguishes ephemeral status updates (heartbeat ticker) from
|
|
@@ -25,9 +27,6 @@ export interface PlannerOpts {
|
|
|
25
27
|
};
|
|
26
28
|
}
|
|
27
29
|
export declare function setPlannerEnvResolver(fn: ((model?: string) => Record<string, string> | undefined) | undefined): void;
|
|
28
|
-
export type ModelTier = "opus" | "sonnet" | "haiku" | "unknown";
|
|
29
|
-
export declare function detectModelTier(model: string): ModelTier;
|
|
30
|
-
export declare function modelCapabilityBlock(model: string): string;
|
|
31
30
|
export declare function getTotalPlannerCost(): number;
|
|
32
31
|
export declare function getPlannerRateLimitInfo(): PlannerRateLimitInfo;
|
|
33
32
|
export declare function runPlannerQuery(prompt: string, opts: PlannerOpts, onLog: PlannerLog): Promise<string>;
|
package/dist/planner-query.js
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import { query } from "@anthropic-ai/claude-agent-sdk";
|
|
2
2
|
import { readFileSync } from "fs";
|
|
3
3
|
import { NudgeError } from "./types.js";
|
|
4
|
+
import { detectModelTier, modelCapabilityBlock } from "./models.js";
|
|
5
|
+
// Re-export for consumers that import from planner-query (steering.ts, index.ts).
|
|
6
|
+
export { detectModelTier, modelCapabilityBlock };
|
|
4
7
|
// ── Shared env resolver (set once at run start, used by every planner query) ──
|
|
5
8
|
//
|
|
6
9
|
// Swarm and planner calls share a model→env map so a custom provider configured
|
|
@@ -10,37 +13,6 @@ let _envResolver;
|
|
|
10
13
|
export function setPlannerEnvResolver(fn) {
|
|
11
14
|
_envResolver = fn;
|
|
12
15
|
}
|
|
13
|
-
export function detectModelTier(model) {
|
|
14
|
-
const m = model.toLowerCase();
|
|
15
|
-
if (m === "default" || m.includes("opus"))
|
|
16
|
-
return "opus";
|
|
17
|
-
if (m.includes("sonnet"))
|
|
18
|
-
return "sonnet";
|
|
19
|
-
if (m.includes("haiku"))
|
|
20
|
-
return "haiku";
|
|
21
|
-
// Cursor API Proxy models
|
|
22
|
-
if (m === "auto")
|
|
23
|
-
return "unknown";
|
|
24
|
-
if (m.startsWith("composer"))
|
|
25
|
-
return "sonnet";
|
|
26
|
-
return "unknown";
|
|
27
|
-
}
|
|
28
|
-
export function modelCapabilityBlock(model) {
|
|
29
|
-
switch (detectModelTier(model)) {
|
|
30
|
-
case "opus":
|
|
31
|
-
return `Each agent runs Claude Opus with 1M context -- a powerhouse. It can own entire epics, do deep codebase research, make architectural decisions, implement complex multi-file systems end-to-end, use browser tools for analysis, and deliver expert-level work. These agents can work for 30+ minutes on the most complex tasks. Do NOT waste them on trivial edits -- give them ownership and autonomy.`;
|
|
32
|
-
case "sonnet":
|
|
33
|
-
return `Each agent runs Claude Sonnet -- capable of substantial implementation, refactoring, testing, and design work. Can work autonomously for 10-20 minutes on complex tasks. Give agents meaningful scope -- not just single-line edits.`;
|
|
34
|
-
case "haiku":
|
|
35
|
-
return `Each agent runs Claude Haiku -- fast and efficient, best for focused, well-specified tasks. Be explicit about files, functions, and expected changes. Keep each task scoped to a clear, concrete deliverable.`;
|
|
36
|
-
default:
|
|
37
|
-
// Cursor API Proxy or unknown model — generic but mention Cursor context
|
|
38
|
-
if (model.toLowerCase().startsWith("composer") || model.toLowerCase() === "auto") {
|
|
39
|
-
return `Each agent runs a Cursor model with full codebase access. Capable of focused implementation work. Be explicit about files, functions, and expected changes.`;
|
|
40
|
-
}
|
|
41
|
-
return `Each agent has full codebase access and can work autonomously.`;
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
16
|
// ── Rate limit tracking ──
|
|
45
17
|
const RATE_LIMIT_PATTERNS = ["rate", "limit", "overloaded", "429", "hit your limit", "too many"];
|
|
46
18
|
function isRateLimitError(err) {
|
package/dist/planner.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { readFileSync } from "fs";
|
|
2
|
-
import { runPlannerQuery, extractTaskJson, attemptJsonParse, postProcess
|
|
2
|
+
import { runPlannerQuery, extractTaskJson, attemptJsonParse, postProcess } from "./planner-query.js";
|
|
3
|
+
import { detectModelTier, modelCapabilityBlock, MODEL_TIER_HAIKU, MODEL_TIER_OPUS, PLANNER_THRESHOLDS } from "./models.js";
|
|
3
4
|
// Resilience: if the planner query throws but the agent already wrote valid
|
|
4
5
|
// tasks to `outFile` (via its Write tool), salvage them instead of discarding
|
|
5
6
|
// expensive work. Returns salvaged tasks on success, null if nothing usable on
|
|
@@ -62,7 +63,7 @@ function plannerPrompt(objective, workerModel, budget, concurrency, flexNote) {
|
|
|
62
63
|
? `\n- ${concurrency} agents run in parallel -- tasks that run concurrently must touch DIFFERENT files to avoid merge conflicts`
|
|
63
64
|
: "";
|
|
64
65
|
const flexLine = flexNote ? `\n\n${flexNote}` : "";
|
|
65
|
-
if (tier ===
|
|
66
|
+
if (tier === MODEL_TIER_HAIKU) {
|
|
66
67
|
return `You are a task coordinator for a parallel agent system. Analyze this codebase and break the following objective into independent tasks.
|
|
67
68
|
|
|
68
69
|
Objective: ${objective}
|
|
@@ -84,8 +85,9 @@ Respond with ONLY a JSON object (no markdown fences):
|
|
|
84
85
|
]
|
|
85
86
|
}`;
|
|
86
87
|
}
|
|
87
|
-
const
|
|
88
|
-
const
|
|
88
|
+
const thresholds = tier === MODEL_TIER_OPUS ? PLANNER_THRESHOLDS.opus : PLANNER_THRESHOLDS.default;
|
|
89
|
+
const smallThreshold = thresholds.small;
|
|
90
|
+
const mediumThreshold = thresholds.medium;
|
|
89
91
|
if (b <= smallThreshold) {
|
|
90
92
|
return `You are a task coordinator for a parallel agent system. Analyze this codebase and break the following objective into independent tasks.
|
|
91
93
|
|
package/dist/providers.d.ts
CHANGED
|
@@ -17,6 +17,8 @@ export interface ProviderConfig {
|
|
|
17
17
|
useJWT?: boolean;
|
|
18
18
|
/** When true, this provider routes through cursor-api-proxy (special env/health-check handling). */
|
|
19
19
|
cursorProxy?: boolean;
|
|
20
|
+
/** API key for cursor-api-proxy. Stored in providers.json (0600), used as fallback when CURSOR_BRIDGE_API_KEY env is not set. */
|
|
21
|
+
cursorApiKey?: string;
|
|
20
22
|
}
|
|
21
23
|
export declare function getStorePath(): string;
|
|
22
24
|
export declare function loadProviders(): ProviderConfig[];
|
|
@@ -65,7 +67,7 @@ export declare function healthCheckCursorProxy(baseUrl?: string): Promise<boolea
|
|
|
65
67
|
export declare function fetchCursorModels(baseUrl?: string): Promise<string[]>;
|
|
66
68
|
/**
|
|
67
69
|
* Interactive setup guide for cursor-api-proxy.
|
|
68
|
-
* Walks through CLI install,
|
|
70
|
+
* Walks through CLI install, API key config, and proxy start.
|
|
69
71
|
* Returns true when proxy is running and healthy.
|
|
70
72
|
*/
|
|
71
73
|
export declare function setupCursorProxy(): Promise<boolean>;
|
package/dist/providers.js
CHANGED
|
@@ -6,6 +6,8 @@ import chalk from "chalk";
|
|
|
6
6
|
import { query } from "@anthropic-ai/claude-agent-sdk";
|
|
7
7
|
import { ask, select, selectKey } from "./cli.js";
|
|
8
8
|
import { getBearerToken, clearTokenCache } from "./auth.js";
|
|
9
|
+
import { DEFAULT_MODEL } from "./models.js";
|
|
10
|
+
import { CURSOR_PRIORITY_MODELS, CURSOR_KNOWN_MODELS, KNOWN_CURSOR_MODEL_IDS, cursorModelHint, } from "./cursor-models.js";
|
|
9
11
|
// ── Store ──
|
|
10
12
|
const STORE_PATH = join(homedir(), ".claude", "claude-overnight", "providers.json");
|
|
11
13
|
export function getStorePath() { return STORE_PATH; }
|
|
@@ -64,9 +66,9 @@ export function envFor(p) {
|
|
|
64
66
|
if (v !== undefined)
|
|
65
67
|
base[k] = v;
|
|
66
68
|
if (p.cursorProxy) {
|
|
67
|
-
// cursor-api-proxy: routes through local proxy, no real API key needed
|
|
68
69
|
base.ANTHROPIC_BASE_URL = p.baseURL;
|
|
69
|
-
|
|
70
|
+
const key = process.env.CURSOR_BRIDGE_API_KEY || p.cursorApiKey;
|
|
71
|
+
base.ANTHROPIC_AUTH_TOKEN = key || "unused";
|
|
70
72
|
delete base.ANTHROPIC_API_KEY;
|
|
71
73
|
return base;
|
|
72
74
|
}
|
|
@@ -99,8 +101,8 @@ export async function pickModel(label, anthropicModels, currentModelId) {
|
|
|
99
101
|
// entry so the user isn't trapped if they cancel the Other… form.
|
|
100
102
|
if (anthropicModels.length === 0) {
|
|
101
103
|
items.push({
|
|
102
|
-
name:
|
|
103
|
-
value: { kind: "anthropic", model: { value:
|
|
104
|
+
name: DEFAULT_MODEL,
|
|
105
|
+
value: { kind: "anthropic", model: { value: DEFAULT_MODEL, displayName: DEFAULT_MODEL, description: "default (model list unavailable)" } },
|
|
104
106
|
hint: "default -- Anthropic model list unavailable",
|
|
105
107
|
});
|
|
106
108
|
}
|
|
@@ -291,24 +293,48 @@ export async function fetchCursorModels(baseUrl = PROXY_DEFAULT_URL) {
|
|
|
291
293
|
}
|
|
292
294
|
}
|
|
293
295
|
/**
|
|
294
|
-
*
|
|
296
|
+
* Try to fetch live Cursor model IDs. Falls back to an empty array — the
|
|
297
|
+
* caller merges with known constants, so the picker always has content.
|
|
298
|
+
*
|
|
299
|
+
* NOTE: `agent --list-models` segfaults with its bundled Node.js binary
|
|
300
|
+
* (exit 139). We work around this by running with the system `node` instead.
|
|
295
301
|
*/
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
302
|
+
async function fetchLiveCursorModels() {
|
|
303
|
+
// Try the proxy first (works when the bundled node doesn't crash)
|
|
304
|
+
const proxyModels = await fetchCursorModels();
|
|
305
|
+
if (proxyModels.length > 0)
|
|
306
|
+
return proxyModels;
|
|
307
|
+
// Fallback: run the cursor-agent CLI with system node
|
|
308
|
+
// Find the agent binary via command -v (alias-safe), then locate its index.js.
|
|
309
|
+
try {
|
|
310
|
+
// command -v handles symlinks and doesn't expand shell aliases
|
|
311
|
+
const agentPath = execSync("command -v agent 2>/dev/null || command -v cursor-agent 2>/dev/null", {
|
|
312
|
+
timeout: 3_000, encoding: "utf-8", shell: "bash",
|
|
313
|
+
}).trim();
|
|
314
|
+
if (!agentPath)
|
|
315
|
+
return [];
|
|
316
|
+
// Resolve the directory (realpath handles symlinks like agent → cursor-agent)
|
|
317
|
+
const dir = execSync(`dirname "$(realpath "${agentPath}")"`, {
|
|
318
|
+
timeout: 3_000, encoding: "utf-8", shell: "bash",
|
|
319
|
+
}).trim();
|
|
320
|
+
// The bundled index.js lives in the same directory as the agent script
|
|
321
|
+
const indexPath = `${dir}/index.js`;
|
|
322
|
+
const raw = execSync(`node "${indexPath}" --list-models 2>/dev/null`, {
|
|
323
|
+
timeout: 10_000, encoding: "utf-8",
|
|
324
|
+
});
|
|
325
|
+
// Strip ANSI escape codes (cursor uses \x1B[2K\r etc.)
|
|
326
|
+
const out = raw.replace(/\x1B\[[0-9;]*[a-zA-Z]/g, "");
|
|
327
|
+
// Parse lines like "composer-2-fast - Composer 2 Fast"
|
|
328
|
+
const ids = [];
|
|
329
|
+
for (const line of out.split("\n")) {
|
|
330
|
+
const match = line.match(/^([A-Za-z0-9][A-Za-z0-9._:/-]*)\s+-\s+/);
|
|
331
|
+
if (match)
|
|
332
|
+
ids.push(match[1]);
|
|
333
|
+
}
|
|
334
|
+
return ids;
|
|
335
|
+
}
|
|
336
|
+
catch { }
|
|
337
|
+
return [];
|
|
312
338
|
}
|
|
313
339
|
function setupSteps() {
|
|
314
340
|
return [
|
|
@@ -328,19 +354,14 @@ function setupSteps() {
|
|
|
328
354
|
successMsg: "Cursor CLI found",
|
|
329
355
|
},
|
|
330
356
|
{
|
|
331
|
-
label: "Cursor
|
|
357
|
+
label: "Cursor API key",
|
|
332
358
|
check: () => {
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
return out.toString().trim().length > 0;
|
|
336
|
-
}
|
|
337
|
-
catch {
|
|
338
|
-
return false;
|
|
339
|
-
}
|
|
359
|
+
const key = process.env.CURSOR_BRIDGE_API_KEY;
|
|
360
|
+
return !!key && key.trim().length > 0;
|
|
340
361
|
},
|
|
341
|
-
autoCmd: "
|
|
342
|
-
manualCmd: "
|
|
343
|
-
successMsg: "Cursor
|
|
362
|
+
autoCmd: "",
|
|
363
|
+
manualCmd: "",
|
|
364
|
+
successMsg: "Cursor API key configured",
|
|
344
365
|
},
|
|
345
366
|
{
|
|
346
367
|
label: "cursor-api-proxy server",
|
|
@@ -359,15 +380,51 @@ function setupSteps() {
|
|
|
359
380
|
},
|
|
360
381
|
];
|
|
361
382
|
}
|
|
383
|
+
const CURSOR_KEY_PROVIDER_ID = "cursor";
|
|
384
|
+
/** Persist the Cursor API key into providers.json. Updates any existing cursor proxy provider,
|
|
385
|
+
* or creates a sentinel entry so the key survives across sessions. */
|
|
386
|
+
function saveCursorApiKey(key) {
|
|
387
|
+
const existing = loadProviders().filter(p => p.cursorProxy);
|
|
388
|
+
if (existing.length > 0) {
|
|
389
|
+
const p = existing[0];
|
|
390
|
+
p.cursorApiKey = key;
|
|
391
|
+
saveProvider(p);
|
|
392
|
+
}
|
|
393
|
+
else {
|
|
394
|
+
const sentinel = {
|
|
395
|
+
id: CURSOR_KEY_PROVIDER_ID,
|
|
396
|
+
displayName: "Cursor (API key)",
|
|
397
|
+
baseURL: PROXY_DEFAULT_URL,
|
|
398
|
+
model: "auto",
|
|
399
|
+
cursorProxy: true,
|
|
400
|
+
cursorApiKey: key,
|
|
401
|
+
};
|
|
402
|
+
saveProvider(sentinel);
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
/** Prompt the user for a Cursor API key and persist it. Returns true if saved. */
|
|
406
|
+
async function promptAndSaveCursorKey() {
|
|
407
|
+
console.log(chalk.dim(` Get your API key from https://cursor.com/dashboard/integrations`));
|
|
408
|
+
console.log(chalk.dim(` (Scroll to the "API Keys" section at the bottom of the page)\n`));
|
|
409
|
+
const key = await ask(` ${chalk.cyan("API key")}: `);
|
|
410
|
+
if (key && key.trim()) {
|
|
411
|
+
const trimmed = key.trim();
|
|
412
|
+
process.env.CURSOR_BRIDGE_API_KEY = trimmed;
|
|
413
|
+
saveCursorApiKey(trimmed);
|
|
414
|
+
return true;
|
|
415
|
+
}
|
|
416
|
+
console.log(chalk.yellow(" No key provided — skipped"));
|
|
417
|
+
return false;
|
|
418
|
+
}
|
|
362
419
|
/**
|
|
363
420
|
* Interactive setup guide for cursor-api-proxy.
|
|
364
|
-
* Walks through CLI install,
|
|
421
|
+
* Walks through CLI install, API key config, and proxy start.
|
|
365
422
|
* Returns true when proxy is running and healthy.
|
|
366
423
|
*/
|
|
367
424
|
export async function setupCursorProxy() {
|
|
368
425
|
console.log(chalk.dim("\n Cursor API Proxy Setup"));
|
|
369
426
|
console.log(chalk.dim(" " + "─".repeat(40)));
|
|
370
|
-
console.log(chalk.dim(" We need three things: Cursor CLI,
|
|
427
|
+
console.log(chalk.dim(" We need three things: Cursor CLI, an API key, and the proxy server.\n"));
|
|
371
428
|
const steps = setupSteps();
|
|
372
429
|
for (const step of steps) {
|
|
373
430
|
if (step.check()) {
|
|
@@ -381,18 +438,10 @@ export async function setupCursorProxy() {
|
|
|
381
438
|
{ key: "s", desc: "kip (I'll handle it)" },
|
|
382
439
|
]);
|
|
383
440
|
if (choice === "a") {
|
|
384
|
-
if (step.label === "Cursor
|
|
385
|
-
|
|
386
|
-
console.log(chalk.dim(` Running: ${step.autoCmd}`));
|
|
387
|
-
console.log(chalk.dim(" (A browser window will open for login)\n"));
|
|
388
|
-
try {
|
|
389
|
-
execSync(step.autoCmd, { stdio: "inherit", timeout: 120_000 });
|
|
441
|
+
if (step.label === "Cursor API key") {
|
|
442
|
+
if (await promptAndSaveCursorKey()) {
|
|
390
443
|
console.log(chalk.green(` ✓ ${step.successMsg}`));
|
|
391
444
|
}
|
|
392
|
-
catch {
|
|
393
|
-
console.log(chalk.yellow(" Login failed — try manual mode"));
|
|
394
|
-
// Fall through to manual display
|
|
395
|
-
}
|
|
396
445
|
}
|
|
397
446
|
else if (step.label === "cursor-api-proxy server") {
|
|
398
447
|
// Don't auto-start the proxy server here — it blocks. Just verify it's installable.
|
|
@@ -428,10 +477,20 @@ export async function setupCursorProxy() {
|
|
|
428
477
|
}
|
|
429
478
|
}
|
|
430
479
|
else if (choice === "m") {
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
console.log(chalk.
|
|
480
|
+
if (step.label === "Cursor API key") {
|
|
481
|
+
console.log(chalk.cyan(`\n 1. Open: https://cursor.com/dashboard/integrations`));
|
|
482
|
+
console.log(chalk.cyan(` 2. Scroll to "API Keys" at the bottom of the page`));
|
|
483
|
+
console.log(chalk.cyan(` 3. Copy your API key and paste it below\n`));
|
|
484
|
+
if (await promptAndSaveCursorKey()) {
|
|
485
|
+
console.log(chalk.green(` ✓ ${step.successMsg}`));
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
else {
|
|
489
|
+
console.log(chalk.cyan(`\n Run this command:`));
|
|
490
|
+
console.log(chalk.white(` ${step.manualCmd}`));
|
|
491
|
+
if (step.label === "cursor-api-proxy server") {
|
|
492
|
+
console.log(chalk.yellow(` Then start the proxy: ${chalk.bold("npx cursor-api-proxy")}`));
|
|
493
|
+
}
|
|
435
494
|
}
|
|
436
495
|
console.log();
|
|
437
496
|
const done = await selectKey(` Done?`, [
|
|
@@ -457,7 +516,34 @@ export async function setupCursorProxy() {
|
|
|
457
516
|
console.log(chalk.yellow("\n Proxy not reachable yet. You can start it later and add it via 'Cursor' in the model picker."));
|
|
458
517
|
return false;
|
|
459
518
|
}
|
|
460
|
-
|
|
519
|
+
/**
|
|
520
|
+
* Build the full list of cursor model picker items. Priority models go first,
|
|
521
|
+
* then known models, then any extra live models we fetched. If there are more
|
|
522
|
+
* than a handful extras, they get a "more..." sub-menu.
|
|
523
|
+
*/
|
|
524
|
+
async function buildCursorPicker() {
|
|
525
|
+
const liveIds = await fetchLiveCursorModels();
|
|
526
|
+
const extra = new Set();
|
|
527
|
+
for (const id of liveIds) {
|
|
528
|
+
if (!KNOWN_CURSOR_MODEL_IDS.has(id))
|
|
529
|
+
extra.add(id);
|
|
530
|
+
}
|
|
531
|
+
const top = [
|
|
532
|
+
...CURSOR_PRIORITY_MODELS.map(m => ({ id: m.id, name: m.label, hint: m.hint })),
|
|
533
|
+
...CURSOR_KNOWN_MODELS.map(m => ({ id: m.id, name: m.label, hint: m.hint })),
|
|
534
|
+
];
|
|
535
|
+
// Only a few extras? show them inline. Otherwise defer to "more...".
|
|
536
|
+
const MORE_THRESHOLD = 6;
|
|
537
|
+
const more = [...extra].sort().map(id => ({
|
|
538
|
+
id,
|
|
539
|
+
name: id,
|
|
540
|
+
hint: cursorModelHint(id),
|
|
541
|
+
}));
|
|
542
|
+
if (more.length <= MORE_THRESHOLD) {
|
|
543
|
+
return { top: [...top, ...more], more: [] };
|
|
544
|
+
}
|
|
545
|
+
return { top, more };
|
|
546
|
+
}
|
|
461
547
|
async function pickCursorModel() {
|
|
462
548
|
console.log(chalk.dim("\n Cursor API Proxy Models"));
|
|
463
549
|
console.log(chalk.dim(" " + "─".repeat(40)));
|
|
@@ -489,28 +575,39 @@ async function pickCursorModel() {
|
|
|
489
575
|
return null;
|
|
490
576
|
}
|
|
491
577
|
}
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
578
|
+
const { top, more } = await buildCursorPicker();
|
|
579
|
+
// If there are more models available, add a "more…" entry
|
|
580
|
+
const items = top.map(m => ({
|
|
581
|
+
name: m.name,
|
|
582
|
+
value: m.id,
|
|
583
|
+
hint: m.hint,
|
|
584
|
+
}));
|
|
585
|
+
let hasMore = more.length > 0;
|
|
586
|
+
if (hasMore) {
|
|
587
|
+
items.push({ name: chalk.gray("more…"), value: "__more__", hint: `${more.length} additional models` });
|
|
497
588
|
}
|
|
498
|
-
const picked = await select(" Select a Cursor model:",
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
hint:
|
|
502
|
-
|
|
503
|
-
|
|
589
|
+
const picked = await select(" Select a Cursor model:", items, 0);
|
|
590
|
+
// Handle "more…" sub-menu
|
|
591
|
+
if (picked === "__more__") {
|
|
592
|
+
const moreItems = more.map(m => ({ name: m.name, value: m.id, hint: m.hint }));
|
|
593
|
+
const morePicked = await select(" More Cursor models:", moreItems, 0);
|
|
594
|
+
return saveCursorPick(morePicked);
|
|
595
|
+
}
|
|
596
|
+
return saveCursorPick(picked);
|
|
597
|
+
}
|
|
598
|
+
function saveCursorPick(modelId) {
|
|
599
|
+
const existingKey = loadProviders().find(p => p.id === CURSOR_KEY_PROVIDER_ID)?.cursorApiKey;
|
|
504
600
|
const provider = {
|
|
505
|
-
id: `cursor-${
|
|
506
|
-
displayName: `Cursor: ${
|
|
601
|
+
id: `cursor-${modelId}`,
|
|
602
|
+
displayName: `Cursor: ${modelId}`,
|
|
507
603
|
baseURL: PROXY_DEFAULT_URL,
|
|
508
|
-
model:
|
|
604
|
+
model: modelId,
|
|
509
605
|
cursorProxy: true,
|
|
606
|
+
...(existingKey ? { cursorApiKey: existingKey } : {}),
|
|
510
607
|
};
|
|
511
608
|
saveProvider(provider);
|
|
512
609
|
console.log(chalk.green(` ✓ Saved as provider: ${provider.displayName}`));
|
|
513
|
-
return { model:
|
|
610
|
+
return { model: modelId, providerId: provider.id, provider };
|
|
514
611
|
}
|
|
515
612
|
/**
|
|
516
613
|
* Build a single resolver that swarm.ts and planner-query.ts share. Maps a
|
package/dist/state.js
CHANGED
|
@@ -3,6 +3,7 @@ import { execSync } from "child_process";
|
|
|
3
3
|
import { join } from "path";
|
|
4
4
|
import chalk from "chalk";
|
|
5
5
|
import { forceMergeOverlay } from "./merge.js";
|
|
6
|
+
import { FALLBACK_MODEL } from "./models.js";
|
|
6
7
|
// ── File I/O helpers ──
|
|
7
8
|
export function readMdDir(dir) {
|
|
8
9
|
try {
|
|
@@ -267,7 +268,7 @@ export function backfillOrphanedPlans(rootDir, filterCwd) {
|
|
|
267
268
|
id: d,
|
|
268
269
|
objective: `(recovered pre-1.11.7 plan · ${taskCount} tasks)`,
|
|
269
270
|
budget: taskCount, remaining: taskCount,
|
|
270
|
-
workerModel:
|
|
271
|
+
workerModel: FALLBACK_MODEL, plannerModel: FALLBACK_MODEL,
|
|
271
272
|
concurrency: 5, permissionMode: "bypassPermissions",
|
|
272
273
|
flex: false, useWorktrees: true, mergeStrategy: "yolo",
|
|
273
274
|
allowExtraUsage: false,
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "claude-overnight",
|
|
3
|
-
"version": "1.
|
|
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.
|
|
3
|
+
"version": "1.18.0",
|
|
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": {
|
|
7
7
|
"claude-overnight": "dist/bin.js"
|
|
@@ -60,6 +60,7 @@
|
|
|
60
60
|
"code-automation",
|
|
61
61
|
"refactoring",
|
|
62
62
|
"migration",
|
|
63
|
+
"cursor",
|
|
63
64
|
"qwen",
|
|
64
65
|
"openrouter",
|
|
65
66
|
"iterative"
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "claude-overnight",
|
|
3
|
-
"version": "1.
|
|
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.",
|
|
3
|
+
"version": "1.18.0",
|
|
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"
|
|
7
7
|
},
|