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 +17 -20
- package/dist/cli.js +38 -17
- package/dist/index.js +1 -1
- package/dist/ui.js +44 -16
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
# claude-overnight
|
|
2
2
|
|
|
3
|
-
**
|
|
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
|
-
|
|
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
|
-
|
|
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 ←
|
|
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... ✓
|
|
64
|
+
◆ Assessing... ✓ Done
|
|
63
65
|
```
|
|
64
66
|
|
|
65
|
-
You interact once (objective, budget, model, review themes), then
|
|
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
|
-
|
|
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
|
-
|
|
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 +
|
|
86
|
+
Typical shape: one objective + a $20–$200 spend cap + walk away.
|
|
90
87
|
|
|
91
88
|
## How it works
|
|
92
89
|
|
|
93
|
-
### 1. Thinking
|
|
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.
|
|
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
|
|
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
|
|
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
|
-
|
|
162
|
-
|
|
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.
|
|
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 (
|
|
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 (
|
|
209
|
-
|
|
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
|
-
|
|
245
|
-
|
|
246
|
-
|
|
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("—
|
|
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 === "\
|
|
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 (
|
|
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
|
-
//
|
|
324
|
-
if (ch === "\x1B"
|
|
330
|
+
// ESC cancels — consume 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
|
-
|
|
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 (
|
|
375
|
+
if (key === "b" || key === "B") {
|
|
348
376
|
this.inputMode = "budget";
|
|
349
377
|
this.inputSegs = [];
|
|
350
378
|
return true;
|
|
351
379
|
}
|
|
352
|
-
if (
|
|
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 (
|
|
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 (
|
|
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 (
|
|
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 ((
|
|
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 ((
|
|
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 ((
|
|
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 (
|
|
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 (
|
|
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
|
-
"description": "
|
|
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"
|