claude-overnight 1.16.4 → 1.16.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.
package/README.md CHANGED
@@ -1,10 +1,12 @@
1
1
  # claude-overnight
2
2
 
3
- **Run 10, 100, or 1000 Claude agents overnight.** A local multi-session orchestrator for the [Claude Agent SDK](https://www.npmjs.com/package/@anthropic-ai/claude-agent-sdk) — parallel Claude agent sessions in isolated git worktrees, spend caps, rate-limit handling, and crash-safe resume across days. Press Run and go to sleep.
3
+ **A background lane for your Claude Max plan.** Runs a capped swarm of Claude Agent SDK sessions in isolated git worktrees stops at a usage cap you set, so your interactive Claude Code always has headroom. Rate-limited? It waits. Crash? It resumes with full context.
4
4
 
5
- Local-first, git-native, budget-first. Describe what to build, set a spend cap, press Run. The tool plans with a thinking wave of architect sessions, breaks the objective into concrete tasks, launches parallel agent sessions in isolated git worktrees, iterates toward quality with a planner/executor/reflection loop, handles rate limits automatically, and resumes cleanly across crashes, rate-limit windows, and laptop sleeps. You wake up to merged commits.
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
- Different shape from hosted single-session agent harnesses like [Claude Managed Agents](https://platform.claude.com/docs/en/managed-agents/overview): instead of one agent in one cloud container, you get many parallel agent sessions running on your own machine, in your real repo, coordinated by multi-wave steering. Works with Claude Opus, Sonnet, and Haiku or pair an Anthropic planner with a cheaper executor on Qwen, OpenRouter, or any Anthropic-compatible endpoint via the `Other…` picker.
7
+ 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
+
9
+ 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.
8
10
 
9
11
  ## Install
10
12
 
@@ -53,27 +55,22 @@ claude-overnight
53
55
 
54
56
  ◆ Thinking: 5 agents exploring... ← architects analyze your codebase
55
57
  ◆ Orchestrating plan... ← synthesizes 50 concrete tasks
56
- ◆ Wave 1 · 50 tasks · $4.20 spent ← fully autonomous from here
58
+ ◆ Wave 1 · 50 tasks · $4.20 spent ← runs unattended from here
57
59
  ↑ 1.2M in ↓ 340K out $4.20 / $4.24 total
58
60
  ◆ Assessing... how close to amazing?
59
61
  ◆ Wave 2 · 30 tasks · $18.50 spent ← improvements from assessment
60
62
  ◆ Reflection: 2 agents reviewing ← deep quality audit
61
63
  ◆ Wave 3 · 20 tasks · $31.00 spent ← fixes from review findings
62
- ◆ Assessing... ✓ Vision met
64
+ ◆ Assessing... ✓ Done
63
65
  ```
64
66
 
65
- You interact once (objective, budget, model, review themes), then everything runs autonomously — thinking, planning, executing, reflecting, steering. Rate-limited? It waits and retries. Crash? Resume where you left off. Capped at usage limit? Pick up next time with full context preserved.
66
-
67
- ## How is this different?
68
-
69
- Claude already has several ways to run agents. `claude-overnight` fills a specific niche:
67
+ You interact once (objective, budget, model, review themes), then the rest runs unattended — thinking, planning, executing, reflecting, steering. Rate-limited? It waits and retries. Crash? Resume where you left off. Capped at usage limit? Pick up next time with full context preserved.
70
68
 
71
- - **Claude Code** — interactive pair programming in your terminal. One agent, one conversation, you drive. `claude-overnight` is the inverse: many agents, no driver, you walk away and come back to merged commits.
72
- - **[Claude Managed Agents](https://platform.claude.com/docs/en/managed-agents/overview)** — a hosted single-session agent harness. One agent, one cloud container, stateful conversation. `claude-overnight` is a local multi-session *orchestrator* built on the [Claude Agent SDK](https://www.npmjs.com/package/@anthropic-ai/claude-agent-sdk): many parallel sessions on your machine, in your real repo, with spend caps and multi-day crash-safe resume.
73
- - **[Claude Agent SDK](https://www.npmjs.com/package/@anthropic-ai/claude-agent-sdk)** — primitives for building your own agent. `claude-overnight` is one specific thing built on top of it: an overnight swarm orchestrator you didn't have to write.
74
- - **IDE copilots (Cursor, Copilot, Cline, etc.)** — synchronous assistants that complete while you're at the keyboard. `claude-overnight` is asynchronous: you hand off an objective and a budget, close the laptop lid, and review a branch in the morning.
69
+ ## How it differs
75
70
 
76
- If you want to hand an objective and a spend cap to Claude and wake up to shipped work on your real repo, this is the shape.
71
+ - vs **Claude Code**: many agents, no driver, capped so your Claude Code session keeps its headroom
72
+ - vs **[Managed Agents](https://platform.claude.com/docs/en/managed-agents/overview)**: on your machine, against your Max plan, in your real git history — not a cloud container billed separately
73
+ - vs **Cursor / Copilot / Cline**: asynchronous, off the keyboard
77
74
 
78
75
  ## Use cases
79
76
 
@@ -86,17 +83,17 @@ If you want to hand an objective and a spend cap to Claude and wake up to shippe
86
83
  - **Quality audits** — reflection waves surface architectural issues and code smells.
87
84
  - **Long research runs** — architect sessions explore a large codebase before any code lands.
88
85
 
89
- Typical shape: one objective + a $20–$200 spend cap + sleep.
86
+ Typical shape: one objective + a $20–$200 spend cap + walk away.
90
87
 
91
88
  ## How it works
92
89
 
93
- ### 1. Thinking wave — parallel architect sessions
90
+ ### 1. Thinking phase — parallel architect sessions
94
91
 
95
92
  For budgets > 15, the tool launches **architect agents** that explore your codebase before any code is written. Each one gets a different research angle (architecture, data models, APIs, testing, etc.) and writes a structured design document. The number scales with budget: 5 for budget=50, 10 for budget=2000.
96
93
 
97
94
  ### 2. Task orchestration
98
95
 
99
- An orchestrator session reads all design documents and synthesizes concrete execution tasks — grounded in real files and patterns the architects found. No guesswork. The task plan is also written to a file for resilience — if orchestration is interrupted, partial results survive.
96
+ An orchestrator session reads all design documents and synthesizes concrete execution tasks — grounded in real files and patterns the architects found. The task plan is also written to a file for resilience — if orchestration is interrupted, partial results survive.
100
97
 
101
98
  ### 3. Parallel execution waves
102
99
 
@@ -110,7 +107,7 @@ After each wave, steering assesses: "how good is this?" — not "what's missing?
110
107
 
111
108
  ### 4. Goal refinement and steering
112
109
 
113
- The tool starts with your broad objective but evolves its definition of "amazing" as it learns your codebase. Steering refines the goal after each wave. Late waves are informed by early discoveries.
110
+ The tool starts with your broad objective but refines its definition of quality as it learns your codebase. Steering updates the goal after each wave. Late waves are informed by early discoveries.
114
111
 
115
112
  ### 5. Three-layer context memory
116
113
 
@@ -118,7 +115,7 @@ Long runs stay sharp because steering maintains three layers of memory:
118
115
 
119
116
  - **Status** — a living project snapshot, updated every wave. Compressed, never truncated.
120
117
  - **Milestones** — strategic snapshots archived every ~5 waves. Long-term memory.
121
- - **Goal** — the evolving north star. What "amazing" means for this codebase.
118
+ - **Goal** — the evolving north star. What quality means for this codebase.
122
119
 
123
120
  ## Run history, resume, and knowledge carryforward
124
121
 
package/dist/cli.js CHANGED
@@ -144,6 +144,9 @@ export function backspaceSegments(segs) {
144
144
  return;
145
145
  }
146
146
  }
147
+ function stripAnsi(s) {
148
+ return s.replace(/\x1B\[[0-9;]*[a-zA-Z]/g, "");
149
+ }
147
150
  // ── Interactive primitives ──
148
151
  /**
149
152
  * Read a line from the user with bracketed-paste awareness.
@@ -158,12 +161,22 @@ export function ask(question) {
158
161
  }
159
162
  return new Promise((resolve) => {
160
163
  const segs = [];
161
- // DEC save/restore cursor + clear-to-end-of-screen so redraws don't pile
162
- // up when the input wraps past the terminal width onto additional rows.
164
+ const tail = question.split("\n").pop() ?? "";
165
+ const tailVisibleLen = stripAnsi(tail).length;
166
+ let prevWrapRows = 0;
167
+ // Only rewrite the input line (and any wrapped continuation rows). The
168
+ // question header above is never touched, so redraws can't stack copies
169
+ // even if the initial write scrolled the viewport.
163
170
  const redraw = () => {
164
- stdout.write("\x1B8\x1B[J" + question + renderSegments(segs));
171
+ const cols = stdout.columns || 80;
172
+ if (prevWrapRows > 0)
173
+ stdout.write(`\x1B[${prevWrapRows}A`);
174
+ stdout.write("\r\x1B[J");
175
+ const rendered = renderSegments(segs);
176
+ stdout.write(tail + rendered);
177
+ const visible = tailVisibleLen + stripAnsi(rendered).length;
178
+ prevWrapRows = visible > 0 ? Math.floor((visible - 1) / cols) : 0;
165
179
  };
166
- stdout.write("\x1B7");
167
180
  stdout.write(question);
168
181
  stdout.write("\x1B[?2004h");
169
182
  try {
@@ -188,7 +201,8 @@ export function ask(question) {
188
201
  redraw();
189
202
  continue;
190
203
  }
191
- for (const ch of seg.text) {
204
+ for (let ci = 0; ci < seg.text.length; ci++) {
205
+ const ch = seg.text[ci];
192
206
  if (ch === "\r" || ch === "\n") {
193
207
  stdout.write("\n");
194
208
  cleanup();
@@ -202,11 +216,20 @@ export function ask(question) {
202
216
  }
203
217
  if (ch === "\x7F" || ch === "\b") {
204
218
  backspaceSegments(segs);
219
+ redraw();
220
+ continue;
221
+ }
222
+ // Skip ESC and any bytes that are part of an ANSI escape sequence
223
+ // (arrow keys, function keys, etc. arrive as \x1B [ ... letter)
224
+ if (ch === "\x1B") {
205
225
  continue;
206
226
  }
207
227
  const code = ch.charCodeAt(0);
208
- if (ch !== "\x1B" && code >= 0x20)
209
- appendCharToSegments(segs, ch);
228
+ if (code < 0x20)
229
+ continue; // control chars
230
+ if (code >= 0x7F && code < 0xA0)
231
+ continue; // DEL + C1 controls
232
+ appendCharToSegments(segs, ch);
210
233
  }
211
234
  redraw();
212
235
  }
@@ -241,15 +264,10 @@ export async function select(label, items, defaultIdx = 0) {
241
264
  };
242
265
  const handler = (buf) => {
243
266
  const s = buf.toString();
244
- if (s === "\x1B[A") {
245
- idx = (idx - 1 + items.length) % items.length;
246
- draw();
247
- }
248
- else if (s === "\x1B[B") {
249
- idx = (idx + 1) % items.length;
250
- draw();
251
- }
252
- else if (s === "\r")
267
+ // Ignore ANSI escape sequences (arrow keys etc.)
268
+ if (s[0] === "\x1B")
269
+ return;
270
+ if (s === "\r")
253
271
  done(items[idx].value);
254
272
  else if (s === "\x03") {
255
273
  stdin.setRawMode(false);
@@ -277,6 +295,9 @@ export async function selectKey(label, options) {
277
295
  stdin.resume();
278
296
  const handler = (buf) => {
279
297
  const s = buf.toString().toLowerCase();
298
+ // Ignore ANSI escape sequences
299
+ if (s[0] === "\x1B")
300
+ return;
280
301
  if (s === "\x03") {
281
302
  stdin.setRawMode(false);
282
303
  process.exit(0);
@@ -288,7 +309,7 @@ export async function selectKey(label, options) {
288
309
  resolve(keys[0]);
289
310
  return;
290
311
  }
291
- if (keys.includes(s)) {
312
+ if (s.length === 1 && keys.includes(s)) {
292
313
  stdin.setRawMode(false);
293
314
  stdin.removeListener("data", handler);
294
315
  stdin.pause();
package/dist/index.js CHANGED
@@ -166,7 +166,7 @@ async function main() {
166
166
  }
167
167
  if (argv.includes("-h") || argv.includes("--help")) {
168
168
  console.log(`
169
- ${chalk.bold("🌙 claude-overnight")} ${chalk.dim("— fire off Claude agents, come back to shipped work")}
169
+ ${chalk.bold("🌙 claude-overnight")} ${chalk.dim("— background lane for your Claude Max plan")}
170
170
  ${chalk.dim("─".repeat(60))}
171
171
 
172
172
  ${chalk.cyan("Usage")}
package/dist/ui.js CHANGED
@@ -282,7 +282,13 @@ export class RunDisplay {
282
282
  this.inputSegs = [];
283
283
  return true;
284
284
  }
285
- if (ch === "\x1B" || ch === "\x03") {
285
+ if (ch === "\x03") {
286
+ this.inputMode = "none";
287
+ this.inputSegs = [];
288
+ return true;
289
+ }
290
+ // ESC cancels input mode
291
+ if (ch === "\x1B") {
286
292
  this.inputMode = "none";
287
293
  this.inputSegs = [];
288
294
  return true;
@@ -301,7 +307,8 @@ export class RunDisplay {
301
307
  }
302
308
  if (this.inputMode === "steer" || this.inputMode === "ask") {
303
309
  let dirty = false;
304
- for (const ch of s) {
310
+ for (let ci = 0; ci < s.length; ci++) {
311
+ const ch = s[ci];
305
312
  if (ch === "\r" || ch === "\n") {
306
313
  const text = segmentsToString(this.inputSegs).trim();
307
314
  const wasAsk = this.inputMode === "ask";
@@ -320,10 +327,18 @@ export class RunDisplay {
320
327
  this.inputSegs = [];
321
328
  return true;
322
329
  }
323
- // Ignore raw ESC onlylet ANSI sequences (arrows etc.) fall through
324
- if (ch === "\x1B" && s.length === 1) {
330
+ // ESC cancelsconsume this byte and any following ANSI sequence bytes
331
+ if (ch === "\x1B") {
325
332
  this.inputMode = "none";
326
333
  this.inputSegs = [];
334
+ // Skip any remaining ANSI sequence bytes (e.g. [A for arrow keys)
335
+ while (ci + 1 < s.length) {
336
+ const next = s[ci + 1];
337
+ const nc = next.charCodeAt(0);
338
+ ci++;
339
+ if ((nc >= 0x40 && nc <= 0x7E) || nc === 0x7F)
340
+ break; // final byte
341
+ }
327
342
  return true;
328
343
  }
329
344
  if (ch === "\x7F" || ch === "\b") {
@@ -332,6 +347,10 @@ export class RunDisplay {
332
347
  continue;
333
348
  }
334
349
  const code = ch.charCodeAt(0);
350
+ if (code < 0x20)
351
+ continue; // control chars
352
+ if (code >= 0x7F && code < 0xA0)
353
+ continue; // DEL + C1 controls
335
354
  if (code >= 0x20 && code <= 0x7E && segmentsToString(this.inputSegs).length < MAX_INPUT_LEN) {
336
355
  appendCharToSegments(this.inputSegs, ch);
337
356
  dirty = true;
@@ -339,17 +358,26 @@ export class RunDisplay {
339
358
  }
340
359
  return dirty;
341
360
  }
342
- // Hotkey mode
343
- if (s === "\x1B" && this.askState && !this.askState.streaming) {
361
+ // Hotkey mode — only accept single printable ASCII characters
362
+ // Skip ESC and ANSI sequences entirely
363
+ if (s.length > 1 && (s[0] === "\x1B" || s.charCodeAt(0) < 0x20))
364
+ return false;
365
+ if (s.length !== 1)
366
+ return false;
367
+ const key = s[0];
368
+ const code = key.charCodeAt(0);
369
+ if (code < 0x20 || code > 0x7E)
370
+ return false;
371
+ if (key === "\x1B" && this.askState && !this.askState.streaming) {
344
372
  this.askState = undefined;
345
373
  return false;
346
374
  }
347
- if (s === "b" || s === "B") {
375
+ if (key === "b" || key === "B") {
348
376
  this.inputMode = "budget";
349
377
  this.inputSegs = [];
350
378
  return true;
351
379
  }
352
- if (s === "t" || s === "T") {
380
+ if (key === "t" || key === "T") {
353
381
  if (this.swarm) {
354
382
  this.inputMode = "threshold";
355
383
  this.inputSegs = [];
@@ -357,7 +385,7 @@ export class RunDisplay {
357
385
  }
358
386
  return false;
359
387
  }
360
- if (s === "c" || s === "C") {
388
+ if (key === "c" || key === "C") {
361
389
  if (this.swarm) {
362
390
  this.inputMode = "concurrency";
363
391
  this.inputSegs = [];
@@ -365,7 +393,7 @@ export class RunDisplay {
365
393
  }
366
394
  return false;
367
395
  }
368
- if (s === "e" || s === "E") {
396
+ if (key === "e" || key === "E") {
369
397
  if (this.swarm) {
370
398
  this.inputMode = "extra";
371
399
  this.inputSegs = [];
@@ -373,7 +401,7 @@ export class RunDisplay {
373
401
  }
374
402
  return false;
375
403
  }
376
- if (s === "p" || s === "P") {
404
+ if (key === "p" || key === "P") {
377
405
  if (this.swarm) {
378
406
  const next = !this.swarm.paused;
379
407
  this.swarm.setPaused(next);
@@ -383,20 +411,20 @@ export class RunDisplay {
383
411
  }
384
412
  return false;
385
413
  }
386
- if ((s === "f" || s === "F") && this.swarm && this.swarm.failed > 0 && this.swarm.active > 0) {
414
+ if ((key === "f" || key === "F") && this.swarm && this.swarm.failed > 0 && this.swarm.active > 0) {
387
415
  this.swarm.requeueFailed();
388
416
  return false;
389
417
  }
390
- if ((s === "r" || s === "R") && this.swarm && this.swarm.rateLimitPaused > 0) {
418
+ if ((key === "r" || key === "R") && this.swarm && this.swarm.rateLimitPaused > 0) {
391
419
  this.swarm.retryRateLimitNow();
392
420
  return true;
393
421
  }
394
- if ((s === "s" || s === "S") && this.onSteer) {
422
+ if ((key === "s" || key === "S") && this.onSteer) {
395
423
  this.inputMode = "steer";
396
424
  this.inputSegs = [];
397
425
  return true;
398
426
  }
399
- if (s === "?" && this.onAsk && this.swarm && !this.askBusy) {
427
+ if (key === "?" && this.onAsk && this.swarm && !this.askBusy) {
400
428
  if (this.askState && !this.askState.streaming) {
401
429
  this.askState = undefined;
402
430
  return false;
@@ -405,7 +433,7 @@ export class RunDisplay {
405
433
  this.inputSegs = [];
406
434
  return true;
407
435
  }
408
- if (s === "q" || s === "Q" || s === "\x03") {
436
+ if (key === "q" || key === "Q" || key === "\x03") {
409
437
  if (this.swarm) {
410
438
  if (this.swarm.aborted)
411
439
  process.exit(0);
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "claude-overnight",
3
- "version": "1.16.4",
4
- "description": "Local multi-session orchestrator for the Claude Agent SDK. Runs parallel Claude agents in git worktrees overnight spend caps, rate-limit handling, crash-safe resume, multi-wave steering. Opus/Sonnet/Haiku + Qwen/OpenRouter. A local alternative to hosted agent harnesses.",
3
+ "version": "1.16.7",
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. Opus/Sonnet/Haiku + Qwen/OpenRouter.",
5
5
  "type": "module",
6
6
  "bin": {
7
7
  "claude-overnight": "dist/bin.js"