claude-overnight 1.25.42 → 1.25.45

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.
Files changed (161) hide show
  1. package/dist/bin.js +1 -1
  2. package/dist/{cli.d.ts → cli/cli.d.ts} +14 -4
  3. package/dist/{cli.js → cli/cli.js} +20 -6
  4. package/dist/cli/help.d.ts +2 -0
  5. package/dist/cli/help.js +44 -0
  6. package/dist/cli/plan-phase.d.ts +38 -0
  7. package/dist/cli/plan-phase.js +290 -0
  8. package/dist/cli/preflight.d.ts +15 -0
  9. package/dist/cli/preflight.js +139 -0
  10. package/dist/cli/resume.d.ts +27 -0
  11. package/dist/cli/resume.js +312 -0
  12. package/dist/{settings.d.ts → cli/settings.d.ts} +1 -2
  13. package/dist/{settings.js → cli/settings.js} +2 -13
  14. package/dist/core/_version.d.ts +1 -0
  15. package/dist/{_version.js → core/_version.js} +1 -1
  16. package/dist/core/auth.d.ts +20 -0
  17. package/dist/core/auth.js +20 -0
  18. package/dist/core/jwt-signer.d.ts +59 -0
  19. package/dist/core/jwt-signer.js +122 -0
  20. package/dist/core/key-vault.d.ts +11 -0
  21. package/dist/core/key-vault.js +63 -0
  22. package/dist/core/rate-limiter.d.ts +38 -0
  23. package/dist/core/rate-limiter.js +99 -0
  24. package/dist/core/secret-manager.d.ts +8 -0
  25. package/dist/core/secret-manager.js +51 -0
  26. package/dist/core/token-cache.d.ts +56 -0
  27. package/dist/core/token-cache.js +125 -0
  28. package/dist/core/token-manager.d.ts +32 -0
  29. package/dist/core/token-manager.js +127 -0
  30. package/dist/{transcripts.d.ts → core/transcripts.d.ts} +1 -1
  31. package/dist/{transcripts.js → core/transcripts.js} +10 -2
  32. package/dist/{types.d.ts → core/types.d.ts} +2 -13
  33. package/dist/index.js +51 -776
  34. package/dist/middleware/index.d.ts +1 -0
  35. package/dist/middleware/index.js +1 -0
  36. package/dist/middleware/rate-limit.d.ts +49 -0
  37. package/dist/middleware/rate-limit.js +65 -0
  38. package/dist/planner/coach/coach.d.ts +15 -0
  39. package/dist/planner/coach/coach.js +207 -0
  40. package/dist/planner/coach/context.d.ts +23 -0
  41. package/dist/planner/coach/context.js +154 -0
  42. package/dist/planner/coach/schema.d.ts +111 -0
  43. package/dist/planner/coach/schema.js +101 -0
  44. package/dist/planner/coach/settings.d.ts +8 -0
  45. package/dist/planner/coach/settings.js +24 -0
  46. package/dist/planner/json.d.ts +4 -0
  47. package/dist/planner/json.js +106 -0
  48. package/dist/{planner.d.ts → planner/planner.d.ts} +5 -5
  49. package/dist/{planner.js → planner/planner.js} +35 -19
  50. package/dist/planner/postprocess.d.ts +2 -0
  51. package/dist/planner/postprocess.js +106 -0
  52. package/dist/planner/query.d.ts +28 -0
  53. package/dist/{planner-query.js → planner/query.js} +47 -317
  54. package/dist/planner/steering.d.ts +52 -0
  55. package/dist/{steering.js → planner/steering.js} +119 -49
  56. package/dist/planner/throttle.d.ts +48 -0
  57. package/dist/planner/throttle.js +87 -0
  58. package/dist/providers/cursor-env.d.ts +53 -0
  59. package/dist/providers/cursor-env.js +283 -0
  60. package/dist/providers/cursor-picker.d.ts +7 -0
  61. package/dist/providers/cursor-picker.js +306 -0
  62. package/dist/providers/cursor-proxy.d.ts +26 -0
  63. package/dist/providers/cursor-proxy.js +459 -0
  64. package/dist/providers/index.d.ts +70 -0
  65. package/dist/providers/index.js +324 -0
  66. package/dist/run/budget.d.ts +6 -0
  67. package/dist/run/budget.js +33 -0
  68. package/dist/run/health.d.ts +5 -0
  69. package/dist/run/health.js +71 -0
  70. package/dist/run/review.d.ts +24 -0
  71. package/dist/run/review.js +32 -0
  72. package/dist/{run.d.ts → run/run.d.ts} +2 -2
  73. package/dist/run/run.js +576 -0
  74. package/dist/run/summary.d.ts +38 -0
  75. package/dist/run/summary.js +174 -0
  76. package/dist/run/throttle.d.ts +21 -0
  77. package/dist/run/throttle.js +48 -0
  78. package/dist/run/wave-loop.d.ts +71 -0
  79. package/dist/run/wave-loop.js +443 -0
  80. package/dist/{state.d.ts → state/state.d.ts} +2 -2
  81. package/dist/{state.js → state/state.js} +10 -6
  82. package/dist/swarm/agent-run.d.ts +34 -0
  83. package/dist/swarm/agent-run.js +465 -0
  84. package/dist/swarm/config.d.ts +31 -0
  85. package/dist/swarm/config.js +35 -0
  86. package/dist/swarm/errors.d.ts +6 -0
  87. package/dist/swarm/errors.js +40 -0
  88. package/dist/swarm/merge-autocommit.d.ts +1 -0
  89. package/dist/swarm/merge-autocommit.js +93 -0
  90. package/dist/swarm/merge-helpers.d.ts +15 -0
  91. package/dist/swarm/merge-helpers.js +175 -0
  92. package/dist/{merge.d.ts → swarm/merge.d.ts} +4 -17
  93. package/dist/swarm/merge.js +180 -0
  94. package/dist/swarm/message-handler.d.ts +35 -0
  95. package/dist/swarm/message-handler.js +195 -0
  96. package/dist/{swarm.d.ts → swarm/swarm.d.ts} +35 -50
  97. package/dist/swarm/swarm.js +470 -0
  98. package/dist/ui/bars.d.ts +12 -0
  99. package/dist/ui/bars.js +183 -0
  100. package/dist/ui/footer-state.d.ts +12 -0
  101. package/dist/ui/footer-state.js +93 -0
  102. package/dist/ui/footer.d.ts +11 -0
  103. package/dist/ui/footer.js +40 -0
  104. package/dist/ui/header.d.ts +14 -0
  105. package/dist/ui/header.js +65 -0
  106. package/dist/ui/input.d.ts +10 -0
  107. package/dist/ui/input.js +246 -0
  108. package/dist/ui/overlay.d.ts +11 -0
  109. package/dist/ui/overlay.js +42 -0
  110. package/dist/ui/primitives.d.ts +33 -0
  111. package/dist/ui/primitives.js +111 -0
  112. package/dist/ui/run-body.d.ts +6 -0
  113. package/dist/ui/run-body.js +169 -0
  114. package/dist/ui/settings.d.ts +13 -0
  115. package/dist/ui/settings.js +112 -0
  116. package/dist/ui/shell.d.ts +8 -0
  117. package/dist/ui/shell.js +34 -0
  118. package/dist/ui/steering-body.d.ts +13 -0
  119. package/dist/ui/steering-body.js +101 -0
  120. package/dist/ui/store.d.ts +66 -0
  121. package/dist/ui/store.js +44 -0
  122. package/dist/ui/summary.d.ts +2 -0
  123. package/dist/ui/summary.js +71 -0
  124. package/dist/ui/types.d.ts +50 -0
  125. package/dist/ui/types.js +4 -0
  126. package/dist/ui/ui.d.ts +46 -0
  127. package/dist/ui/ui.js +260 -0
  128. package/docs/CURSOR_PROXY.md +63 -0
  129. package/package.json +7 -4
  130. package/plugins/claude-overnight/.claude-plugin/plugin.json +1 -1
  131. package/plugins/lsp-first/.claude-plugin/plugin.json +18 -0
  132. package/plugins/lsp-first/README.md +38 -0
  133. package/plugins/lsp-first/hooks/lsp-first-guard.js +112 -0
  134. package/dist/_version.d.ts +0 -1
  135. package/dist/auth.d.ts +0 -19
  136. package/dist/auth.js +0 -82
  137. package/dist/coach.d.ts +0 -49
  138. package/dist/coach.js +0 -498
  139. package/dist/interactive-panel.d.ts +0 -42
  140. package/dist/interactive-panel.js +0 -169
  141. package/dist/merge.js +0 -442
  142. package/dist/planner-query.d.ts +0 -60
  143. package/dist/providers.d.ts +0 -159
  144. package/dist/providers.js +0 -1437
  145. package/dist/render.d.ts +0 -79
  146. package/dist/render.js +0 -768
  147. package/dist/run.js +0 -1136
  148. package/dist/steering.d.ts +0 -3
  149. package/dist/swarm.js +0 -1059
  150. package/dist/test-coach.d.ts +0 -1
  151. package/dist/ui.d.ts +0 -162
  152. package/dist/ui.js +0 -1011
  153. /package/dist/{cursor-models.d.ts → core/cursor-models.d.ts} +0 -0
  154. /package/dist/{cursor-models.js → core/cursor-models.js} +0 -0
  155. /package/dist/{models.d.ts → core/models.d.ts} +0 -0
  156. /package/dist/{models.js → core/models.js} +0 -0
  157. /package/dist/{proxy-port.d.ts → core/proxy-port.d.ts} +0 -0
  158. /package/dist/{proxy-port.js → core/proxy-port.js} +0 -0
  159. /package/dist/{turns.d.ts → core/turns.d.ts} +0 -0
  160. /package/dist/{turns.js → core/turns.js} +0 -0
  161. /package/dist/{types.js → core/types.js} +0 -0
package/dist/bin.js CHANGED
@@ -30,7 +30,7 @@ if (process.stdout.isTTY && !quiet && !process.env.CLAUDE_OVERNIGHT_UPDATED) {
30
30
  writeFileSync(tsFile, String(Date.now())); // stamp first so failures don't re-trigger
31
31
  const { execFileSync, spawnSync } = await import("node:child_process");
32
32
  const latest = execFileSync("npm", ["show", "claude-overnight", "version"], { encoding: "utf-8", timeout: 6000 }).trim();
33
- const { VERSION } = await import("./_version.js");
33
+ const { VERSION } = await import("./core/_version.js");
34
34
  if (latest !== VERSION) {
35
35
  process.stdout.write(`\r\x1b[2K 🌙 claude-overnight \x1b[33m${VERSION} → ${latest}\x1b[0m updating…\n`);
36
36
  execFileSync("npm", ["i", "-g", `claude-overnight@${latest}`], { stdio: "inherit", timeout: 60000 });
@@ -1,10 +1,10 @@
1
1
  import type { ModelInfo } from "@anthropic-ai/claude-agent-sdk";
2
- import type { Task, PermMode, MergeStrategy } from "./types.js";
2
+ import type { Task, MergeStrategy } from "../core/types.js";
3
3
  export declare function parseCliFlags(argv: string[]): {
4
4
  flags: Record<string, string>;
5
5
  positional: string[];
6
6
  };
7
- import { isJWTAuthError } from "./auth.js";
7
+ import { isJWTAuthError } from "../core/auth.js";
8
8
  /** @deprecated Use isJWTAuthError from auth.ts instead. */
9
9
  export declare const isAuthError: typeof isJWTAuthError;
10
10
  export { isJWTAuthError };
@@ -51,7 +51,6 @@ export interface FileArgs {
51
51
  objective?: string;
52
52
  concurrency?: number;
53
53
  model?: string;
54
- permissionMode?: PermMode;
55
54
  cwd?: string;
56
55
  allowedTools?: string[];
57
56
  beforeWave?: string | string[];
@@ -68,4 +67,15 @@ export declare function isGitRepo(cwd: string): boolean;
68
67
  export declare function validateGitRepo(cwd: string): void;
69
68
  export declare function showPlan(tasks: Task[]): void;
70
69
  export declare const BRAILLE: readonly ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
71
- export declare function makeProgressLog(): (text: string) => void;
70
+ /** Dual-mode progress renderer.
71
+ *
72
+ * - `status` (default): transient single-line ticker — clears itself each frame.
73
+ * Use for elapsed-time / cost / rolling tail of model text.
74
+ * - `event`: permanent log line — scrolls up, ticker redraws underneath.
75
+ * Use for tool calls and notable state changes.
76
+ *
77
+ * The two modes cooperate: an event clears the current ticker, writes the
78
+ * event on its own line, and the next status tick redraws the ticker below.
79
+ * That gives the user a visible history of what the planner did, with a live
80
+ * "now" indicator that always stays pinned at the bottom. */
81
+ export declare function makeProgressLog(): (text: string, kind?: "status" | "event") => void;
@@ -6,7 +6,7 @@ import chalk from "chalk";
6
6
  import { query } from "@anthropic-ai/claude-agent-sdk";
7
7
  // ── CLI flag parsing ──
8
8
  export function parseCliFlags(argv) {
9
- const known = new Set(["concurrency", "model", "timeout", "budget", "usage-cap", "extra-usage-budget", "merge", "perm"]);
9
+ const known = new Set(["concurrency", "model", "timeout", "budget", "usage-cap", "extra-usage-budget", "merge"]);
10
10
  const booleans = new Set(["--dry-run", "-h", "--help", "-v", "--version", "--no-flex", "--allow-extra-usage", "--worktrees", "--no-worktrees", "--yolo"]);
11
11
  const flags = {};
12
12
  const positional = [];
@@ -30,7 +30,7 @@ export function parseCliFlags(argv) {
30
30
  return { flags, positional };
31
31
  }
32
32
  // ── Auth error detection (re-exported from auth module for backward compatibility) ──
33
- import { isJWTAuthError } from "./auth.js";
33
+ import { isJWTAuthError } from "../core/auth.js";
34
34
  /** @deprecated Use isJWTAuthError from auth.ts instead. */
35
35
  export const isAuthError = isJWTAuthError;
36
36
  export { isJWTAuthError };
@@ -332,7 +332,7 @@ export async function selectKey(label, options) {
332
332
  });
333
333
  }
334
334
  const KNOWN_TASK_FILE_KEYS = new Set([
335
- "tasks", "objective", "concurrency", "cwd", "model", "permissionMode", "allowedTools", "beforeWave", "afterWave", "afterRun", "worktrees", "mergeStrategy", "usageCap", "flexiblePlan",
335
+ "tasks", "objective", "concurrency", "cwd", "model", "allowedTools", "beforeWave", "afterWave", "afterRun", "worktrees", "mergeStrategy", "usageCap", "flexiblePlan",
336
336
  ]);
337
337
  export function loadTaskFile(file) {
338
338
  const path = resolve(file);
@@ -392,7 +392,6 @@ export function loadTaskFile(file) {
392
392
  concurrency: parsed.concurrency,
393
393
  model: parsed.model,
394
394
  cwd: parsed.cwd ? resolve(parsed.cwd) : undefined,
395
- permissionMode: parsed.permissionMode,
396
395
  allowedTools: parsed.allowedTools,
397
396
  beforeWave: parsed.beforeWave,
398
397
  afterWave: parsed.afterWave,
@@ -437,13 +436,28 @@ export function showPlan(tasks) {
437
436
  console.log(chalk.dim(` ${"─".repeat(ruleLen)}\n`));
438
437
  }
439
438
  export const BRAILLE = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
439
+ /** Dual-mode progress renderer.
440
+ *
441
+ * - `status` (default): transient single-line ticker — clears itself each frame.
442
+ * Use for elapsed-time / cost / rolling tail of model text.
443
+ * - `event`: permanent log line — scrolls up, ticker redraws underneath.
444
+ * Use for tool calls and notable state changes.
445
+ *
446
+ * The two modes cooperate: an event clears the current ticker, writes the
447
+ * event on its own line, and the next status tick redraws the ticker below.
448
+ * That gives the user a visible history of what the planner did, with a live
449
+ * "now" indicator that always stays pinned at the bottom. */
440
450
  export function makeProgressLog() {
441
451
  let frame = 0;
442
- return (text) => {
443
- const spin = chalk.cyan(BRAILLE[frame++ % BRAILLE.length]);
452
+ return (text, kind = "status") => {
444
453
  const maxW = (process.stdout.columns ?? 80) - 6;
445
454
  const clean = text.replace(/\n/g, " ");
446
455
  const line = clean.length > maxW ? clean.slice(0, maxW - 1) + "\u2026" : clean;
456
+ if (kind === "event") {
457
+ process.stdout.write(`\x1B[2K\r ${chalk.cyan("›")} ${chalk.dim(line)}\n`);
458
+ return;
459
+ }
460
+ const spin = chalk.cyan(BRAILLE[frame++ % BRAILLE.length]);
447
461
  process.stdout.write(`\x1B[2K\r ${spin} ${chalk.dim(line)}`);
448
462
  };
449
463
  }
@@ -0,0 +1,2 @@
1
+ export declare function printVersion(): void;
2
+ export declare function printHelp(): void;
@@ -0,0 +1,44 @@
1
+ import { readFileSync } from "fs";
2
+ import { dirname, join } from "path";
3
+ import { fileURLToPath } from "url";
4
+ import chalk from "chalk";
5
+ import { VERSION } from "../core/_version.js";
6
+ export function printVersion() {
7
+ const __dirname = dirname(fileURLToPath(import.meta.url));
8
+ const pkg = JSON.parse(readFileSync(join(__dirname, "..", "..", "package.json"), "utf-8"));
9
+ console.log(`claude-overnight v${pkg.version}`);
10
+ }
11
+ export function printHelp() {
12
+ console.log(`
13
+ ${chalk.bold("🌙 claude-overnight")} ${chalk.dim("v" + VERSION + " -- background lane for your Claude Max plan")}
14
+ ${chalk.dim("─".repeat(60))}
15
+
16
+ ${chalk.cyan("Usage")}
17
+ claude-overnight ${chalk.dim("interactive mode")}
18
+ claude-overnight tasks.json ${chalk.dim("task file mode")}
19
+ claude-overnight "fix auth" "add tests" ${chalk.dim("inline tasks")}
20
+
21
+ ${chalk.cyan("Flags")}
22
+ -h, --help Show this help
23
+ -v, --version Print version
24
+ --dry-run Show planned tasks without running them
25
+ --budget=N Target number of agent runs ${chalk.dim("(default: 10)")}
26
+ --concurrency=N Max parallel agents ${chalk.dim("(default: 5)")}
27
+ --model=NAME Worker model override ${chalk.dim("(interactive mode picks planner + worker separately -- supports 'Other…' for Qwen / OpenRouter / etc.)")}
28
+ --fast-model=NAME Fast worker model for quick tasks ${chalk.dim("(optional -- checked by next wave's workers)")}
29
+ --usage-cap=N Stop at N% utilization ${chalk.dim("(e.g. 90 to save 10% for other work)")}
30
+ --allow-extra-usage Allow extra/overage usage ${chalk.dim("(default: stop when plan limits hit)")}
31
+ --extra-usage-budget=N Max $ for extra usage ${chalk.dim("(implies --allow-extra-usage)")}
32
+ --timeout=SECONDS Agent inactivity timeout ${chalk.dim("(default: 900s, nudges at timeout, kills at 2×)")}
33
+ --no-flex Disable adaptive multi-wave planning ${chalk.dim("(run all tasks in one shot)")}
34
+ --worktrees Force worktree isolation on ${chalk.dim("(default: auto-detect git repo)")}
35
+ --no-worktrees Disable worktree isolation ${chalk.dim("(all agents work in real cwd)")}
36
+ --merge=MODE Merge strategy: yolo or branch ${chalk.dim("(default: yolo)")}
37
+ --yolo Shorthand for --no-worktrees
38
+ --no-coach Skip the setup coach ${chalk.dim("(raw objective, no preflight rewrite)")}
39
+ --coach-model Re-pick coach model ${chalk.dim("(overrides saved choice)")}
40
+
41
+ ${chalk.cyan("Defaults")} ${chalk.dim("(non-interactive)")}
42
+ model: first available concurrency: 5 worktrees: auto merge: yolo
43
+ `);
44
+ }
@@ -0,0 +1,38 @@
1
+ import type { ProviderConfig, EnvResolver } from "../providers/index.js";
2
+ import type { Task, MergeStrategy, WaveSummary } from "../core/types.js";
3
+ export interface PlanPhaseInput {
4
+ objective: string | undefined;
5
+ noTTY: boolean;
6
+ flex: boolean;
7
+ budget: number | undefined;
8
+ concurrency: number;
9
+ cwd: string;
10
+ plannerModel: string;
11
+ workerModel: string;
12
+ fastModel: string | undefined;
13
+ plannerProvider: ProviderConfig | undefined;
14
+ workerProvider: ProviderConfig | undefined;
15
+ fastProvider: ProviderConfig | undefined;
16
+ usageCap: number | undefined;
17
+ allowExtraUsage: boolean;
18
+ extraUsageBudget: number | undefined;
19
+ useWorktrees: boolean;
20
+ mergeStrategy: MergeStrategy;
21
+ agentTimeoutMs: number | undefined;
22
+ runDir: string;
23
+ designDir: string;
24
+ previousKnowledge: string;
25
+ envForModel: EnvResolver;
26
+ coachedOriginal: string | undefined;
27
+ coachedAt: number | undefined;
28
+ }
29
+ export interface PlanPhaseResult {
30
+ tasks: Task[];
31
+ thinkingHistory?: WaveSummary;
32
+ thinkingUsed: number;
33
+ thinkingCost: number;
34
+ thinkingIn: number;
35
+ thinkingOut: number;
36
+ thinkingTools: number;
37
+ }
38
+ export declare function runPlanPhase(input: PlanPhaseInput): Promise<PlanPhaseResult>;
@@ -0,0 +1,290 @@
1
+ import { readFileSync, readdirSync, mkdirSync, writeFileSync } from "fs";
2
+ import { join } from "path";
3
+ import chalk from "chalk";
4
+ import { query } from "@anthropic-ai/claude-agent-sdk";
5
+ import { Swarm } from "../swarm/swarm.js";
6
+ import { identifyThemes, buildThinkingTasks, orchestrate, planTasks, refinePlan } from "../planner/planner.js";
7
+ import { RunDisplay } from "../ui/ui.js";
8
+ import { renderSummary } from "../ui/summary.js";
9
+ import { isCursorProxyProvider } from "../providers/index.js";
10
+ import { readMdDir, saveRunState } from "../state/state.js";
11
+ import { selectKey, ask, showPlan, makeProgressLog, isJWTAuthError } from "./cli.js";
12
+ export async function runPlanPhase(input) {
13
+ const { objective, noTTY, flex, budget, concurrency, cwd, plannerModel, workerModel, fastModel, plannerProvider, workerProvider, fastProvider, usageCap, allowExtraUsage, extraUsageBudget, useWorktrees, mergeStrategy, agentTimeoutMs, runDir, designDir, previousKnowledge, envForModel, coachedOriginal, coachedAt, } = input;
14
+ let tasks = [];
15
+ let thinkingHistory;
16
+ let thinkingUsed = 0, thinkingCost = 0, thinkingIn = 0, thinkingOut = 0, thinkingTools = 0;
17
+ // Persist an early planning-phase state so the run is visible to the resume
18
+ // picker even if orchestrate dies before executeRun gets a chance to run.
19
+ // Without this, a crashed plan phase leaves no run.json and the run vanishes
20
+ // from findIncompleteRuns -- you pay for orchestration and can't see it.
21
+ if (objective) {
22
+ try {
23
+ saveRunState(runDir, {
24
+ id: runDir.split(/[/\\]/).pop() ?? "",
25
+ objective, budget: budget ?? 10, remaining: budget ?? 10,
26
+ workerModel, plannerModel, fastModel,
27
+ workerProviderId: workerProvider?.id, plannerProviderId: plannerProvider?.id,
28
+ fastProviderId: fastProvider?.id,
29
+ concurrency,
30
+ usageCap, allowExtraUsage, extraUsageBudget,
31
+ flex, useWorktrees, mergeStrategy,
32
+ waveNum: 0, currentTasks: [],
33
+ accCost: 0, accCompleted: 0, accFailed: 0,
34
+ accIn: 0, accOut: 0, accTools: 0,
35
+ branches: [],
36
+ phase: "planning",
37
+ startedAt: new Date().toISOString(),
38
+ cwd,
39
+ });
40
+ }
41
+ catch { }
42
+ }
43
+ if (noTTY) {
44
+ console.error(chalk.red(" No tasks provided and stdin is not a TTY."));
45
+ process.exit(1);
46
+ }
47
+ process.stdout.write("\x1B[?25l");
48
+ const planRestore = () => process.stdout.write("\x1B[?25h");
49
+ const useThinking = flex && (budget ?? 10) > concurrency * 3;
50
+ const thinkingCount = useThinking ? Math.min(Math.max(concurrency, Math.ceil((budget ?? 10) * 0.005)), 10) : 0;
51
+ try {
52
+ if (useThinking) {
53
+ // Persist themes as a Markdown doc so a planning-phase crash leaves a
54
+ // readable record (and a future resume can skip identifyThemes).
55
+ const saveThemesMd = (list) => {
56
+ try {
57
+ writeFileSync(join(runDir, "themes.md"), `# Themes\n\n**Objective:** ${objective}\n\n${list.map((t, i) => `${i + 1}. ${t}`).join("\n")}\n`, "utf-8");
58
+ }
59
+ catch { }
60
+ };
61
+ let themes = await identifyThemes(objective, thinkingCount, cwd, plannerModel, makeProgressLog(), "themes");
62
+ saveThemesMd(themes);
63
+ process.stdout.write(`\x1B[2K\r ${chalk.green(`✓ ${themes.length} themes`)}\n\n`);
64
+ planRestore();
65
+ let reviewing = true;
66
+ while (reviewing) {
67
+ for (let i = 0; i < themes.length; i++)
68
+ console.log(chalk.dim(` ${String(i + 1).padStart(3)}.`) + ` ${themes[i]}`);
69
+ console.log(chalk.dim(`\n ${thinkingCount} thinking agents → orchestrate → ${(budget ?? 10) - thinkingCount} execution sessions\n`));
70
+ const action = await selectKey(`${chalk.white(`${themes.length} themes`)} ${chalk.dim(`· ${thinkingCount} thinking · ${concurrency} concurrent`)}`, [{ key: "r", desc: "un" }, { key: "e", desc: "dit" }, { key: "c", desc: "hat" }, { key: "q", desc: "uit" }]);
71
+ if (action === "r") {
72
+ reviewing = false;
73
+ break;
74
+ }
75
+ if (action === "e") {
76
+ const feedback = await ask(`\n ${chalk.bold("What should change?")}\n ${chalk.cyan(">")} `);
77
+ if (!feedback)
78
+ continue;
79
+ process.stdout.write("\x1B[?25l");
80
+ try {
81
+ themes = await identifyThemes(`${objective}\n\nUser feedback: ${feedback}`, thinkingCount, cwd, plannerModel, makeProgressLog(), "themes-refine");
82
+ saveThemesMd(themes);
83
+ process.stdout.write(`\x1B[2K\r ${chalk.green(`✓ ${themes.length} themes`)}\n\n`);
84
+ }
85
+ catch (err) {
86
+ console.error(chalk.red(`\n Re-planning failed: ${err.message}\n`));
87
+ }
88
+ planRestore();
89
+ }
90
+ else if (action === "c") {
91
+ const question = await ask(`\n ${chalk.bold("Ask about the themes:")}\n ${chalk.cyan(">")} `);
92
+ if (!question)
93
+ continue;
94
+ process.stdout.write("\x1B[?25l");
95
+ try {
96
+ let answer = "";
97
+ const plannerEnv = envForModel(plannerModel);
98
+ for await (const msg of query({
99
+ prompt: `You're planning work for: "${objective}"\n\nThemes identified:\n${themes.map((t, i) => `${i + 1}. ${t}`).join("\n")}\n\nUser question: ${question}`,
100
+ options: { cwd, model: plannerModel, permissionMode: "bypassPermissions", allowDangerouslySkipPermissions: true, persistSession: false, ...(plannerEnv && { env: plannerEnv }) },
101
+ })) {
102
+ if (msg.type === "result" && msg.subtype === "success")
103
+ answer = msg.result || "";
104
+ }
105
+ planRestore();
106
+ if (answer)
107
+ console.log(chalk.dim(`\n ${answer.slice(0, 500)}\n`));
108
+ }
109
+ catch {
110
+ planRestore();
111
+ }
112
+ }
113
+ else {
114
+ console.log(chalk.dim("\n Aborted.\n"));
115
+ process.exit(0);
116
+ }
117
+ }
118
+ process.stdout.write("\x1B[?25l");
119
+ mkdirSync(designDir, { recursive: true });
120
+ const existingDesigns = readMdDir(designDir);
121
+ if (existingDesigns) {
122
+ const designFiles = readdirSync(designDir).filter(f => f.endsWith(".md")).sort();
123
+ console.log(chalk.green(`\n ✓ Reusing ${designFiles.length} design docs`) + chalk.dim(` (from prior attempt)`));
124
+ for (const f of designFiles) {
125
+ try {
126
+ const firstLine = readFileSync(join(designDir, f), "utf-8").split("\n")[0].replace(/^#+\s*/, "").trim();
127
+ if (firstLine)
128
+ console.log(chalk.dim(` ${firstLine.slice(0, 80)}`));
129
+ }
130
+ catch { }
131
+ }
132
+ console.log("");
133
+ }
134
+ else {
135
+ const researchModel = fastModel ? workerModel : plannerModel;
136
+ const thinkingTasks = buildThinkingTasks(objective, themes, designDir, researchModel, previousKnowledge || undefined);
137
+ console.log(chalk.cyan(`\n ◆ Thinking: ${thinkingTasks.length} agents exploring...\n`));
138
+ const thinkingSwarm = new Swarm({
139
+ tasks: thinkingTasks, concurrency, cwd, model: researchModel,
140
+ useWorktrees: false, mergeStrategy: "yolo", agentTimeoutMs, usageCap, allowExtraUsage, extraUsageBudget,
141
+ envForModel,
142
+ cursorProxy: [plannerProvider, workerProvider, fastProvider].some(p => p && isCursorProxyProvider(p)),
143
+ });
144
+ const thinkRunInfo = { accIn: 0, accOut: 0, accCost: 0, accCompleted: 0, accFailed: 0, sessionsBudget: budget ?? 10, waveNum: -1, remaining: budget ?? 10, model: researchModel, startedAt: Date.now() };
145
+ const thinkDisplay = new RunDisplay(thinkRunInfo, { remaining: 0, usageCap, concurrency, paused: false, dirty: false });
146
+ thinkDisplay.setWave(thinkingSwarm);
147
+ thinkDisplay.start();
148
+ // Save thinking-wave state on every exit path (normal, abort, double-q).
149
+ const saveThinkingState = () => {
150
+ thinkingUsed = thinkingSwarm.completed + thinkingSwarm.failed;
151
+ thinkingCost = thinkingSwarm.totalCostUsd;
152
+ thinkingIn = thinkingSwarm.totalInputTokens;
153
+ thinkingOut = thinkingSwarm.totalOutputTokens;
154
+ thinkingTools = thinkingSwarm.agents.reduce((sum, a) => sum + a.toolCalls, 0);
155
+ try {
156
+ saveRunState(runDir, {
157
+ id: runDir.split(/[/\\]/).pop() ?? "",
158
+ objective: objective, budget: budget ?? 10, remaining: (budget ?? 10) - thinkingUsed,
159
+ workerModel, plannerModel, fastModel,
160
+ workerProviderId: workerProvider?.id, plannerProviderId: plannerProvider?.id,
161
+ fastProviderId: fastProvider?.id,
162
+ concurrency,
163
+ usageCap, allowExtraUsage, extraUsageBudget,
164
+ flex, useWorktrees, mergeStrategy,
165
+ waveNum: 0, currentTasks: [],
166
+ accCost: thinkingCost, accCompleted: thinkingUsed, accFailed: 0,
167
+ accIn: thinkingIn, accOut: thinkingOut, accTools: thinkingTools,
168
+ branches: [],
169
+ phase: "planning",
170
+ startedAt: new Date().toISOString(),
171
+ cwd,
172
+ coachedObjective: coachedOriginal,
173
+ coachedAt,
174
+ });
175
+ }
176
+ catch { }
177
+ };
178
+ // Catch double-q / hard exit during thinking wave
179
+ const exitHandler = () => { try {
180
+ saveThinkingState();
181
+ }
182
+ catch { } };
183
+ process.on("exit", exitHandler);
184
+ try {
185
+ await thinkingSwarm.run();
186
+ }
187
+ finally {
188
+ thinkDisplay.pause();
189
+ console.log(renderSummary(thinkingSwarm));
190
+ thinkDisplay.stop();
191
+ saveThinkingState();
192
+ process.removeListener("exit", exitHandler);
193
+ }
194
+ thinkingHistory = { wave: -1, tasks: thinkingSwarm.agents.map(a => ({ prompt: a.task.prompt.slice(0, 200), status: a.status, filesChanged: a.filesChanged, error: a.error })) };
195
+ if (thinkingSwarm.rateLimitResetsAt) {
196
+ const waitMs = thinkingSwarm.rateLimitResetsAt - Date.now();
197
+ if (waitMs > 0) {
198
+ console.log(chalk.dim(` Waiting ${Math.ceil(waitMs / 1000)}s for rate limit reset...`));
199
+ await new Promise(r => setTimeout(r, waitMs + 2000));
200
+ }
201
+ }
202
+ }
203
+ const designs = readMdDir(designDir);
204
+ const taskFile = join(runDir, "tasks.json");
205
+ if (designs) {
206
+ const orchBudget = Math.min(50, Math.max(concurrency, Math.ceil(((budget ?? 10) - thinkingUsed) * 0.5)));
207
+ const flexNote = `This is wave 1 of an adaptive multi-wave run (total budget: ${(budget ?? 10) - thinkingUsed}). Plan the highest-impact foundational work first. Future waves will iterate based on what's learned.`;
208
+ console.log(chalk.cyan(`\n ◆ Orchestrating plan...\n`));
209
+ tasks = await orchestrate(objective, designs, cwd, plannerModel, workerModel, orchBudget, concurrency, makeProgressLog(), flexNote, taskFile);
210
+ process.stdout.write(`\x1B[2K\r ${chalk.green(`✓ ${tasks.length} tasks`)}\n\n`);
211
+ }
212
+ else {
213
+ console.log(chalk.yellow(`\n No design docs -- falling back to direct planning\n`));
214
+ const waveBudget = Math.min(50, Math.max(concurrency, Math.ceil(((budget ?? 10) - thinkingUsed) * 0.5)));
215
+ tasks = await planTasks(objective, cwd, plannerModel, workerModel, waveBudget, concurrency, makeProgressLog(), undefined, taskFile);
216
+ process.stdout.write(`\x1B[2K\r ${chalk.green(`✓ ${tasks.length} tasks`)}\n\n`);
217
+ }
218
+ }
219
+ else {
220
+ const waveBudget = flex ? Math.min(50, Math.max(concurrency, Math.ceil((budget ?? 10) * 0.5))) : budget;
221
+ const flexNote = flex ? `This is wave 1 of an adaptive multi-wave run (total budget: ${budget}). Plan the highest-impact foundational work first. Future waves will iterate, polish, and expand based on what's learned.` : undefined;
222
+ console.log(chalk.cyan(`\n ◆ Planning${flex ? " wave 1" : ""}...\n`));
223
+ tasks = await planTasks(objective, cwd, plannerModel, workerModel, waveBudget, concurrency, makeProgressLog(), flexNote);
224
+ process.stdout.write(`\x1B[2K\r ${chalk.green(`✓ ${tasks.length} tasks`)}${flex ? chalk.dim(` · wave 1`) : ""}\n\n`);
225
+ planRestore();
226
+ let reviewing = true;
227
+ while (reviewing) {
228
+ showPlan(tasks);
229
+ const action = await selectKey(`${chalk.white(`${tasks.length} tasks`)} ${chalk.dim(`· ${concurrency} concurrent`)}`, [{ key: "r", desc: "un" }, { key: "e", desc: "dit" }, { key: "c", desc: "hat" }, { key: "q", desc: "uit" }]);
230
+ switch (action) {
231
+ case "r":
232
+ reviewing = false;
233
+ break;
234
+ case "e": {
235
+ const feedback = await ask(`\n ${chalk.bold("What should change?")}\n ${chalk.cyan(">")} `);
236
+ if (!feedback)
237
+ break;
238
+ console.log(chalk.cyan("\n ◆ Re-planning...\n"));
239
+ process.stdout.write("\x1B[?25l");
240
+ try {
241
+ tasks = await refinePlan(objective, tasks, feedback, cwd, plannerModel, workerModel, budget, concurrency, makeProgressLog());
242
+ process.stdout.write(`\x1B[2K\r ${chalk.green(`✓ ${tasks.length} tasks`)}\n\n`);
243
+ }
244
+ catch (err) {
245
+ console.error(chalk.red(`\n Re-planning failed: ${err.message}\n`));
246
+ }
247
+ planRestore();
248
+ break;
249
+ }
250
+ case "c": {
251
+ const question = await ask(`\n ${chalk.bold("Ask about the plan:")}\n ${chalk.cyan(">")} `);
252
+ if (!question)
253
+ break;
254
+ process.stdout.write("\x1B[?25l");
255
+ try {
256
+ let answer = "";
257
+ const plannerEnv = envForModel(plannerModel);
258
+ for await (const msg of query({
259
+ prompt: `You planned these tasks for the objective "${objective}":\n${tasks.map((t, i) => `${i + 1}. ${t.prompt}`).join("\n")}\n\nUser question: ${question}`,
260
+ options: { cwd, model: plannerModel, permissionMode: "bypassPermissions", allowDangerouslySkipPermissions: true, persistSession: false, ...(plannerEnv && { env: plannerEnv }) },
261
+ })) {
262
+ if (msg.type === "result" && msg.subtype === "success")
263
+ answer = msg.result || "";
264
+ }
265
+ planRestore();
266
+ if (answer)
267
+ console.log(chalk.dim(`\n ${answer.slice(0, 500)}\n`));
268
+ }
269
+ catch {
270
+ planRestore();
271
+ }
272
+ break;
273
+ }
274
+ case "q":
275
+ console.log(chalk.dim("\n Aborted.\n"));
276
+ process.exit(0);
277
+ }
278
+ }
279
+ }
280
+ }
281
+ catch (err) {
282
+ planRestore();
283
+ if (isJWTAuthError(err))
284
+ console.error(chalk.red(`\n Authentication failed -- check your API key or run: claude auth\n`));
285
+ else
286
+ console.error(chalk.red(`\n Planning failed: ${err.message}\n`));
287
+ process.exit(1);
288
+ }
289
+ return { tasks, thinkingHistory, thinkingUsed, thinkingCost, thinkingIn, thinkingOut, thinkingTools };
290
+ }
@@ -0,0 +1,15 @@
1
+ import type { ProviderConfig } from "../providers/index.js";
2
+ export interface PreflightInput {
3
+ plannerModel: string;
4
+ plannerProvider?: ProviderConfig | undefined;
5
+ workerModel: string;
6
+ workerProvider?: ProviderConfig | undefined;
7
+ fastModel?: string | undefined;
8
+ fastProvider?: ProviderConfig | undefined;
9
+ cwd: string;
10
+ }
11
+ export interface PreflightResult {
12
+ /** true when the fast provider failed preflight and the caller should drop it */
13
+ fastDegraded: boolean;
14
+ }
15
+ export declare function runProviderPreflight(input: PreflightInput): Promise<PreflightResult>;
@@ -0,0 +1,139 @@
1
+ import chalk from "chalk";
2
+ import { getProxyPort, buildProxyUrl } from "../core/proxy-port.js";
3
+ import { setPlannerEnvResolver } from "../planner/query.js";
4
+ import { preflightProvider, isCursorProxyProvider, readCursorProxyLogTail, ensureCursorProxyRunning, bundledComposerProxyShellCommand, hasCursorAgentToken, PROXY_DEFAULT_URL, buildEnvResolver, } from "../providers/index.js";
5
+ export async function runProviderPreflight(input) {
6
+ const { plannerModel, plannerProvider, workerModel, workerProvider, fastProvider, cwd } = input;
7
+ const seen = new Set();
8
+ const all = [
9
+ ["planner", plannerProvider],
10
+ ["worker", workerProvider],
11
+ ["fast", fastProvider],
12
+ ];
13
+ const pending = [];
14
+ const cursorProxies = [];
15
+ for (const [role, p] of all) {
16
+ if (p && !seen.has(p.id)) {
17
+ seen.add(p.id);
18
+ pending.push([role, p]);
19
+ if (isCursorProxyProvider(p))
20
+ cursorProxies.push(p);
21
+ }
22
+ }
23
+ // Auto-start cursor proxy before pinging (restarts when a token exists so stale listeners get CURSOR_API_KEY).
24
+ if (cursorProxies.length > 0) {
25
+ const resolvedPort = getProxyPort(cwd);
26
+ const resolvedUrl = buildProxyUrl(resolvedPort);
27
+ await ensureCursorProxyRunning(resolvedUrl);
28
+ // Sync providers to the resolved port (may differ from default if per-project port was picked)
29
+ for (const p of cursorProxies) {
30
+ if (!p.baseURL || p.baseURL === PROXY_DEFAULT_URL) {
31
+ p.baseURL = resolvedUrl;
32
+ }
33
+ }
34
+ if (resolvedUrl !== PROXY_DEFAULT_URL) {
35
+ console.log(chalk.dim(` Proxy port: ${resolvedPort}`));
36
+ }
37
+ if (!hasCursorAgentToken()) {
38
+ console.error(chalk.red(` ✗ Cursor models require a User API key — add it via ${chalk.bold("Cursor…")} setup, or set ` +
39
+ `${chalk.bold("CURSOR_API_KEY")} / ${chalk.bold("CURSOR_BRIDGE_API_KEY")}, or ${chalk.bold("cursorApiKey")} in providers.json.`));
40
+ console.error(chalk.dim(` Without it the Cursor CLI falls back to macOS Keychain (\`cursor-user\`).`));
41
+ process.exit(1);
42
+ }
43
+ }
44
+ process.stdout.write(` ${chalk.dim(`◆ Pinging ${pending.map(([r, p]) => `${r} (${p.displayName})`).join(", ")}…`)}\n`);
45
+ // Preflight strategy: all providers run fully in parallel. Cursor proxy
46
+ // providers used to race on the shared `~/.cursor/cli-config.json`, but the
47
+ // proxy now uses an account pool (`CURSOR_CONFIG_DIRS`) — each parallel
48
+ // cursor-agent subprocess gets its own config dir, eliminating the race.
49
+ // See ensureCursorAccountPool() in providers.ts.
50
+ //
51
+ // Single in-place status line collapses N parallel progress streams (one
52
+ // per provider) into one tty line updated via `\r` + ANSI clear. Keeps the
53
+ // "window head" calm instead of appending 3 lines per 3s tick.
54
+ const statuses = new Map();
55
+ const isTTY = process.stdout.isTTY;
56
+ let statusLineActive = false;
57
+ const renderStatus = () => {
58
+ if (!isTTY)
59
+ return;
60
+ const parts = [...statuses.entries()].map(([r, s]) => `${r} ${chalk.dim(s)}`);
61
+ process.stdout.write(`\x1B[2K\r`);
62
+ if (parts.length === 0) {
63
+ statusLineActive = false;
64
+ return;
65
+ }
66
+ process.stdout.write(chalk.dim(" " + parts.join(" · ")));
67
+ statusLineActive = true;
68
+ };
69
+ const clearStatusLine = () => {
70
+ if (isTTY && statusLineActive) {
71
+ process.stdout.write(`\x1B[2K\r`);
72
+ statusLineActive = false;
73
+ }
74
+ };
75
+ /** Cursor agent cold start + thinking-variant model latency can exceed 20s, and the cursor
76
+ * preflight now also runs a write-capability probe (see probeCursorWriteCapability) that
77
+ * asks cursor to Bash a marker file — so the total budget must cover auth ping + write turn. */
78
+ const preflightMs = (p) => isCursorProxyProvider(p) ? 90_000 : 20_000;
79
+ // Cursor's composer-2 pipeline intermittently stalls for 100s+ on a write-tool turn
80
+ // even though the tool succeeded (proxy logs it as "SLOW response"). A single retry
81
+ // almost always clears it — so we retry once on timeout-style failures for cursor
82
+ // proxy providers before giving up.
83
+ const isTimeoutError = (err) => /^timeout after /.test(err) || /: timeout after /.test(err);
84
+ const runPreflight = async (role, p) => {
85
+ statuses.set(role, "connecting…");
86
+ renderStatus();
87
+ let result = await preflightProvider(p, cwd, preflightMs(p), {
88
+ onProgress: (msg) => { statuses.set(role, msg); renderStatus(); },
89
+ });
90
+ if (!result.ok && isCursorProxyProvider(p) && isTimeoutError(result.error)) {
91
+ statuses.set(role, "retrying after timeout…");
92
+ renderStatus();
93
+ result = await preflightProvider(p, cwd, preflightMs(p), {
94
+ onProgress: (msg) => { statuses.set(role, `retry: ${msg}`); renderStatus(); },
95
+ });
96
+ }
97
+ statuses.delete(role);
98
+ renderStatus();
99
+ return { role, provider: p, result };
100
+ };
101
+ const results = await Promise.all(pending.map(([role, p]) => runPreflight(role, p)));
102
+ clearStatusLine();
103
+ let fastDegraded = false;
104
+ for (const { role, provider, result } of results) {
105
+ if (!result.ok) {
106
+ const degradable = role === "fast";
107
+ const prefix = degradable ? chalk.yellow(` ⚠ ${role} preflight failed`) : chalk.red(` ✗ ${role} preflight failed`);
108
+ console.error(`${prefix}: ${chalk.dim(result.error)}`);
109
+ if (isCursorProxyProvider(provider)) {
110
+ const tail = readCursorProxyLogTail(25);
111
+ if (tail) {
112
+ console.error(chalk.yellow(` ── proxy log tail (agent stderr + sessions) ──`));
113
+ for (const line of tail.split("\n"))
114
+ console.error(chalk.dim(` ${line}`));
115
+ }
116
+ const cmd = bundledComposerProxyShellCommand();
117
+ const proxyUrl = provider.baseURL || PROXY_DEFAULT_URL;
118
+ console.error(chalk.yellow(` The proxy at ${proxyUrl} 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"}`));
119
+ }
120
+ else if (!degradable) {
121
+ console.error(chalk.red(` Fix the provider at ~/.claude/claude-overnight/providers.json and retry.`));
122
+ }
123
+ if (degradable) {
124
+ console.error(chalk.yellow(` Continuing without the fast worker — fast-eligible tasks will run on the main worker model instead.`));
125
+ console.error("");
126
+ fastDegraded = true;
127
+ continue;
128
+ }
129
+ console.error("");
130
+ process.exit(1);
131
+ }
132
+ console.log(` ${chalk.green(`✓ ${role} ready`)} ${chalk.dim(`· ${provider.displayName} · ${provider.model}`)}`);
133
+ }
134
+ if (fastDegraded) {
135
+ const rebuilt = buildEnvResolver({ plannerModel, plannerProvider, workerModel, workerProvider, fastModel: undefined, fastProvider: undefined });
136
+ setPlannerEnvResolver(rebuilt);
137
+ }
138
+ return { fastDegraded };
139
+ }