claude-overnight 1.13.1 → 1.14.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -21,6 +21,133 @@ function countTasksInFile(path) {
21
21
  return 0;
22
22
  }
23
23
  }
24
+ async function promptResumeOverrides(state, cliFlags, argv, noTTY, runDir) {
25
+ // ── Apply CLI flag overrides first ──
26
+ if (cliFlags.model)
27
+ state.workerModel = cliFlags.model;
28
+ if (cliFlags.concurrency) {
29
+ const n = parseInt(cliFlags.concurrency);
30
+ if (n >= 1)
31
+ state.concurrency = n;
32
+ }
33
+ if (cliFlags.budget) {
34
+ const n = parseInt(cliFlags.budget);
35
+ if (n > 0) {
36
+ state.remaining = n;
37
+ state.budget = state.accCompleted + state.accFailed + n;
38
+ }
39
+ }
40
+ if (cliFlags["usage-cap"] != null) {
41
+ const v = parseFloat(cliFlags["usage-cap"]);
42
+ if (!isNaN(v) && v >= 0 && v <= 100)
43
+ state.usageCap = v > 0 ? v / 100 : undefined;
44
+ }
45
+ if (cliFlags["extra-usage-budget"] != null) {
46
+ const v = parseFloat(cliFlags["extra-usage-budget"]);
47
+ if (!isNaN(v) && v > 0) {
48
+ state.extraUsageBudget = v;
49
+ state.allowExtraUsage = true;
50
+ }
51
+ }
52
+ if (argv.includes("--allow-extra-usage"))
53
+ state.allowExtraUsage = true;
54
+ if (cliFlags.perm)
55
+ state.permissionMode = cliFlags.perm;
56
+ if (noTTY) {
57
+ try {
58
+ saveRunState(runDir, state);
59
+ }
60
+ catch { }
61
+ return;
62
+ }
63
+ // ── Interactive review ──
64
+ const fmtSummary = () => {
65
+ const tier = detectModelTier(state.workerModel);
66
+ const remaining = Math.max(1, state.remaining);
67
+ const capStr = state.usageCap != null ? `${Math.round(state.usageCap * 100)}%` : "unlimited";
68
+ const extraStr = state.allowExtraUsage
69
+ ? (state.extraUsageBudget ? `$${state.extraUsageBudget}` : "unlimited")
70
+ : "off";
71
+ console.log();
72
+ console.log(` ${chalk.dim("Resume settings")}`);
73
+ console.log(` ${chalk.dim("─".repeat(40))}`);
74
+ console.log(` ${chalk.dim("model ")}${chalk.white(state.workerModel)} ${chalk.dim(`(${tier})`)}`);
75
+ console.log(` ${chalk.dim("remaining ")}${chalk.white(String(remaining))} ${chalk.dim("sessions")}`);
76
+ console.log(` ${chalk.dim("concur ")}${chalk.white(String(state.concurrency))}`);
77
+ console.log(` ${chalk.dim("usage cap ")}${chalk.white(capStr)}`);
78
+ console.log(` ${chalk.dim("extra ")}${chalk.white(extraStr)}`);
79
+ };
80
+ fmtSummary();
81
+ const action = await selectKey("", [
82
+ { key: "r", desc: "esume" },
83
+ { key: "e", desc: "dit" },
84
+ { key: "q", desc: "uit" },
85
+ ]);
86
+ if (action === "q")
87
+ process.exit(0);
88
+ if (action === "r")
89
+ return;
90
+ // ── Edit walk ──
91
+ const models = await fetchModels(5_000);
92
+ if (models.length > 0) {
93
+ const currentIdx = Math.max(0, models.findIndex(m => m.value === state.workerModel));
94
+ state.workerModel = await select(`${chalk.cyan("①")} Worker model:`, models.map(m => ({ name: m.displayName, value: m.value, hint: m.description })), currentIdx);
95
+ }
96
+ else {
97
+ const ans = await ask(`\n ${chalk.cyan("①")} Worker model ${chalk.dim(`[${state.workerModel}]:`)} `);
98
+ if (ans.trim())
99
+ state.workerModel = ans.trim();
100
+ }
101
+ const remAns = await ask(`\n ${chalk.cyan("②")} Remaining sessions ${chalk.dim(`[${state.remaining}]:`)} `);
102
+ const parsedRem = parseInt(remAns);
103
+ if (!isNaN(parsedRem) && parsedRem > 0) {
104
+ state.remaining = parsedRem;
105
+ state.budget = state.accCompleted + state.accFailed + parsedRem;
106
+ }
107
+ const concAns = await ask(`\n ${chalk.cyan("③")} Concurrency ${chalk.dim(`[${state.concurrency}]:`)} `);
108
+ const parsedConc = parseInt(concAns);
109
+ if (!isNaN(parsedConc) && parsedConc >= 1)
110
+ state.concurrency = parsedConc;
111
+ const currentCap = state.usageCap != null ? String(Math.round(state.usageCap * 100)) : "off";
112
+ const capAns = await ask(`\n ${chalk.cyan("④")} Usage cap % ${chalk.dim(`[${currentCap}]`)} ${chalk.dim("(0 = off):")} `);
113
+ if (capAns.trim()) {
114
+ const v = parseFloat(capAns);
115
+ if (!isNaN(v) && v >= 0 && v <= 100)
116
+ state.usageCap = v > 0 ? v / 100 : undefined;
117
+ }
118
+ const currentExtra = state.allowExtraUsage
119
+ ? (state.extraUsageBudget ? `$${state.extraUsageBudget}` : "unlimited")
120
+ : "off";
121
+ const extraChoice = await select(`${chalk.cyan("⑤")} Extra usage ${chalk.dim(`[current: ${currentExtra}]`)}:`, [
122
+ { name: "Keep current", value: "keep" },
123
+ { name: "Off", value: "off", hint: "stop at plan limit" },
124
+ { name: "With $ cap", value: "budget", hint: "set a spending cap" },
125
+ { name: "Unlimited", value: "unlimited", hint: "no cap, billed as overage" },
126
+ ]);
127
+ if (extraChoice === "off") {
128
+ state.allowExtraUsage = false;
129
+ state.extraUsageBudget = undefined;
130
+ }
131
+ else if (extraChoice === "budget") {
132
+ const bAns = await ask(` ${chalk.dim("Max extra $:")} `);
133
+ const bVal = parseFloat(bAns);
134
+ if (!isNaN(bVal) && bVal > 0) {
135
+ state.extraUsageBudget = bVal;
136
+ state.allowExtraUsage = true;
137
+ }
138
+ }
139
+ else if (extraChoice === "unlimited") {
140
+ state.allowExtraUsage = true;
141
+ state.extraUsageBudget = undefined;
142
+ }
143
+ try {
144
+ saveRunState(runDir, state);
145
+ }
146
+ catch { }
147
+ console.log(chalk.green("\n ✓ Settings updated"));
148
+ fmtSummary();
149
+ console.log();
150
+ }
24
151
  async function main() {
25
152
  const argv = process.argv.slice(2);
26
153
  if (argv.includes("-v") || argv.includes("--version")) {
@@ -310,6 +437,7 @@ async function main() {
310
437
  }
311
438
  catch { }
312
439
  }
440
+ await promptResumeOverrides(resumeState, cliFlags, argv, noTTY, resumeRunDir);
313
441
  }
314
442
  }
315
443
  // ── Config resolution ──
package/dist/render.js CHANGED
@@ -196,7 +196,7 @@ export function renderFrame(swarm, showHotkeys, runInfo) {
196
196
  const chip = pending > 0 ? chalk.cyan(` \u270E ${pending} steer queued`) : "";
197
197
  const fixChip = swarm.failed > 0 && swarm.active > 0 ? chalk.yellow(" [f] fix") : "";
198
198
  const pauseLabel = swarm.paused ? "[p] resume" : "[p] pause";
199
- out.push(chalk.dim(` [b] budget [t] threshold [c] conc ${pauseLabel} [s] steer [?] ask [q] stop`) + fixChip + chip);
199
+ out.push(chalk.dim(` [b] budget [t] cap [c] conc [e] extra ${pauseLabel} [s] steer [?] ask [q] stop`) + fixChip + chip);
200
200
  if (swarm.blocked > 0 && swarm.blocked === swarm.active) {
201
201
  out.push(chalk.yellow(` all workers rate-limited — press [c] to reduce concurrency, [p] to pause, [q] to quit`));
202
202
  }
package/dist/run.js CHANGED
@@ -25,7 +25,10 @@ export async function executeRun(cfg) {
25
25
  let currentSwarm;
26
26
  let remaining;
27
27
  let currentTasks;
28
- const liveConfig = { remaining: 0, usageCap, concurrency, paused: false, dirty: false };
28
+ const liveConfig = {
29
+ remaining: 0, usageCap, concurrency, paused: false, dirty: false,
30
+ extraUsageBudget: cfg.extraUsageBudget,
31
+ };
29
32
  let waveNum;
30
33
  const waveHistory = [];
31
34
  let accCost, accCompleted, accFailed, accTools;
@@ -341,6 +344,7 @@ export async function executeRun(cfg) {
341
344
  if (liveConfig.dirty) {
342
345
  remaining = liveConfig.remaining;
343
346
  usageCap = liveConfig.usageCap;
347
+ cfg.extraUsageBudget = liveConfig.extraUsageBudget;
344
348
  liveConfig.dirty = false;
345
349
  }
346
350
  liveConfig.remaining = remaining;
package/dist/swarm.d.ts CHANGED
@@ -65,7 +65,7 @@ export declare class Swarm {
65
65
  readonly model: string | undefined;
66
66
  usageCap: number | undefined;
67
67
  readonly allowExtraUsage: boolean;
68
- readonly extraUsageBudget: number | undefined;
68
+ extraUsageBudget: number | undefined;
69
69
  readonly baseCostUsd: number;
70
70
  mergeBranch?: string;
71
71
  constructor(config: SwarmConfig);
@@ -76,6 +76,8 @@ export declare class Swarm {
76
76
  setConcurrency(n: number): void;
77
77
  /** Freeze/resume dispatch without killing the run. Paused workers block at the top of their loop. */
78
78
  setPaused(b: boolean): void;
79
+ /** Live-adjust the overage spend cap. `undefined` = unlimited. If already over the new cap, stop dispatch. */
80
+ setExtraUsageBudget(n: number | undefined): void;
79
81
  run(): Promise<void>;
80
82
  abort(): void;
81
83
  /** Re-queue all errored agents' tasks for retry within this wave. */
package/dist/swarm.js CHANGED
@@ -109,6 +109,18 @@ export class Swarm {
109
109
  this.paused = b;
110
110
  this.log(-1, b ? "Dispatch paused" : "Dispatch resumed");
111
111
  }
112
+ /** Live-adjust the overage spend cap. `undefined` = unlimited. If already over the new cap, stop dispatch. */
113
+ setExtraUsageBudget(n) {
114
+ if (this.extraUsageBudget === n)
115
+ return;
116
+ const prev = this.extraUsageBudget;
117
+ this.extraUsageBudget = n;
118
+ const fmt = (v) => v != null ? `$${v}` : "unlimited";
119
+ this.log(-1, `Extra usage budget: ${fmt(prev)} → ${fmt(n)}`);
120
+ if (n != null && this.isUsingOverage && this.overageCostUsd >= n) {
121
+ this.capForOverage(`Extra usage budget $${n} exceeded ($${this.overageCostUsd.toFixed(2)} spent) — stopping dispatch`);
122
+ }
123
+ }
112
124
  async run() {
113
125
  try {
114
126
  if (this.config.useWorktrees) {
package/dist/ui.d.ts CHANGED
@@ -33,6 +33,8 @@ export interface LiveConfig {
33
33
  concurrency: number;
34
34
  paused: boolean;
35
35
  dirty: boolean;
36
+ /** Overage spend cap ($) — undefined = unlimited. Synced from the [e] hotkey. */
37
+ extraUsageBudget?: number;
36
38
  }
37
39
  /** State of an in-flight or recently-completed ask side query. */
38
40
  export interface AskState {
package/dist/ui.js CHANGED
@@ -164,6 +164,9 @@ export class RunDisplay {
164
164
  if (this.inputMode === "concurrency") {
165
165
  return `\n ${chalk.cyan(">")} New concurrency (min 1): ${rendered}\u2588`;
166
166
  }
167
+ if (this.inputMode === "extra") {
168
+ return `\n ${chalk.cyan(">")} Extra usage $ cap (0 = stop on overage): ${rendered}\u2588`;
169
+ }
167
170
  if (this.inputMode === "steer") {
168
171
  return `\n ${chalk.cyan(">")} ${chalk.bold("Steer next wave")} ${chalk.dim("(Enter to queue, Esc to cancel)")}\n ${rendered}\u2588`;
169
172
  }
@@ -229,7 +232,7 @@ export class RunDisplay {
229
232
  }
230
233
  /** Handle a pasted block. Returns true if the frame needs a redraw. */
231
234
  handlePaste(text) {
232
- if (this.inputMode === "budget" || this.inputMode === "threshold" || this.inputMode === "concurrency") {
235
+ if (this.inputMode === "budget" || this.inputMode === "threshold" || this.inputMode === "concurrency" || this.inputMode === "extra") {
233
236
  const clean = text.replace(/[^0-9.]/g, "");
234
237
  if (clean)
235
238
  appendCharToSegments(this.inputSegs, clean);
@@ -246,7 +249,7 @@ export class RunDisplay {
246
249
  /** Handle a typed (non-pasted) chunk. Returns true if the frame needs a redraw. */
247
250
  handleTyped(s) {
248
251
  const lc = this.liveConfig;
249
- if (this.inputMode === "budget" || this.inputMode === "threshold" || this.inputMode === "concurrency") {
252
+ if (this.inputMode === "budget" || this.inputMode === "threshold" || this.inputMode === "concurrency" || this.inputMode === "extra") {
250
253
  let dirty = false;
251
254
  for (const ch of s) {
252
255
  if (ch === "\r" || ch === "\n") {
@@ -270,6 +273,11 @@ export class RunDisplay {
270
273
  lc.dirty = true;
271
274
  this.swarm?.setConcurrency(n);
272
275
  }
276
+ else if (this.inputMode === "extra" && !isNaN(val) && val >= 0) {
277
+ lc.extraUsageBudget = val;
278
+ lc.dirty = true;
279
+ this.swarm?.setExtraUsageBudget(val);
280
+ }
273
281
  this.inputMode = "none";
274
282
  this.inputSegs = [];
275
283
  return true;
@@ -357,6 +365,14 @@ export class RunDisplay {
357
365
  }
358
366
  return false;
359
367
  }
368
+ if (s === "e" || s === "E") {
369
+ if (this.swarm) {
370
+ this.inputMode = "extra";
371
+ this.inputSegs = [];
372
+ return true;
373
+ }
374
+ return false;
375
+ }
360
376
  if (s === "p" || s === "P") {
361
377
  if (this.swarm) {
362
378
  const next = !this.swarm.paused;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-overnight",
3
- "version": "1.13.1",
3
+ "version": "1.14.0",
4
4
  "description": "Run 10, 100, or 1000 Claude agents overnight. Parallel autonomous AI coding with thinking waves, iterative quality steering, crash recovery, and rate limit handling. Built on the Claude Agent SDK.",
5
5
  "type": "module",
6
6
  "bin": {