claude-overnight 1.10.0 → 1.11.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/cli.js +2 -2
- package/dist/index.js +113 -9
- package/dist/render.js +2 -1
- package/dist/swarm.d.ts +2 -0
- package/dist/swarm.js +44 -11
- package/dist/ui.js +3 -0
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -6,8 +6,8 @@ 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"]);
|
|
10
|
-
const booleans = new Set(["--dry-run", "-h", "--help", "-v", "--version", "--no-flex", "--allow-extra-usage"]);
|
|
9
|
+
const known = new Set(["concurrency", "model", "timeout", "budget", "usage-cap", "extra-usage-budget", "merge", "perm"]);
|
|
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 = [];
|
|
13
13
|
for (let i = 0; i < argv.length; i++) {
|
package/dist/index.js
CHANGED
|
@@ -42,6 +42,11 @@ async function main() {
|
|
|
42
42
|
--extra-usage-budget=N Max $ for extra usage ${chalk.dim("(implies --allow-extra-usage)")}
|
|
43
43
|
--timeout=SECONDS Agent inactivity timeout ${chalk.dim("(default: 900s, nudges at timeout, kills at 2×)")}
|
|
44
44
|
--no-flex Disable adaptive multi-wave planning ${chalk.dim("(run all tasks in one shot)")}
|
|
45
|
+
--worktrees Force worktree isolation on ${chalk.dim("(default: auto-detect git repo)")}
|
|
46
|
+
--no-worktrees Disable worktree isolation ${chalk.dim("(all agents work in real cwd)")}
|
|
47
|
+
--merge=MODE Merge strategy: yolo or branch ${chalk.dim("(default: yolo)")}
|
|
48
|
+
--perm=MODE Permission mode: auto, bypassPermissions, default ${chalk.dim("(default: auto)")}
|
|
49
|
+
--yolo Shorthand for --perm=bypassPermissions --no-worktrees
|
|
45
50
|
|
|
46
51
|
${chalk.cyan("Defaults")} ${chalk.dim("(non-interactive)")}
|
|
47
52
|
model: first available concurrency: 5 worktrees: auto merge: yolo perms: auto
|
|
@@ -251,6 +256,9 @@ async function main() {
|
|
|
251
256
|
let usageCap;
|
|
252
257
|
let allowExtraUsage = false;
|
|
253
258
|
let extraUsageBudget;
|
|
259
|
+
let permissionMode = "auto";
|
|
260
|
+
let useWorktrees = false;
|
|
261
|
+
let mergeStrategy = "yolo";
|
|
254
262
|
if (resuming) {
|
|
255
263
|
workerModel = resumeState.workerModel;
|
|
256
264
|
plannerModel = resumeState.plannerModel;
|
|
@@ -260,6 +268,9 @@ async function main() {
|
|
|
260
268
|
usageCap = resumeState.usageCap;
|
|
261
269
|
allowExtraUsage = resumeState.allowExtraUsage ?? false;
|
|
262
270
|
extraUsageBudget = resumeState.extraUsageBudget;
|
|
271
|
+
permissionMode = resumeState.permissionMode;
|
|
272
|
+
useWorktrees = resumeState.useWorktrees;
|
|
273
|
+
mergeStrategy = resumeState.mergeStrategy;
|
|
263
274
|
}
|
|
264
275
|
else if (!nonInteractive) {
|
|
265
276
|
while (true) {
|
|
@@ -279,6 +290,17 @@ async function main() {
|
|
|
279
290
|
console.error(chalk.red(` Budget must be a positive number`));
|
|
280
291
|
process.exit(1);
|
|
281
292
|
}
|
|
293
|
+
// ③ Max concurrency (skip if --concurrency set)
|
|
294
|
+
if (cliFlags.concurrency) {
|
|
295
|
+
concurrency = parseInt(cliFlags.concurrency);
|
|
296
|
+
}
|
|
297
|
+
else {
|
|
298
|
+
const defaultC = Math.min(5, budget);
|
|
299
|
+
const concAns = await ask(`\n ${chalk.cyan("③")} ${chalk.dim("Max concurrency")} ${chalk.dim("[")}${chalk.white(String(defaultC))}${chalk.dim("]:")} `);
|
|
300
|
+
concurrency = parseInt(concAns) || defaultC;
|
|
301
|
+
if (concurrency < 1)
|
|
302
|
+
concurrency = 1;
|
|
303
|
+
}
|
|
282
304
|
let modelFrame = 0;
|
|
283
305
|
const modelSpinner = setInterval(() => {
|
|
284
306
|
process.stdout.write(`\x1B[2K\r ${chalk.cyan(BRAILLE[modelFrame++ % BRAILLE.length])} ${chalk.dim("loading models...")}`);
|
|
@@ -293,19 +315,19 @@ async function main() {
|
|
|
293
315
|
}
|
|
294
316
|
plannerModel = models[0]?.value || "claude-sonnet-4-6";
|
|
295
317
|
if (models.length > 0) {
|
|
296
|
-
workerModel = await select(`${chalk.cyan("
|
|
318
|
+
workerModel = await select(`${chalk.cyan("④")} Worker model:`, models.map(m => ({ name: m.displayName, value: m.value, hint: m.description })));
|
|
297
319
|
}
|
|
298
320
|
else {
|
|
299
|
-
const ans = await ask(` ${chalk.cyan("
|
|
321
|
+
const ans = await ask(` ${chalk.cyan("④")} ${chalk.dim("Worker model [claude-sonnet-4-6]:")} `);
|
|
300
322
|
workerModel = ans || "claude-sonnet-4-6";
|
|
301
323
|
}
|
|
302
|
-
usageCap = await select(`${chalk.cyan("
|
|
324
|
+
usageCap = await select(`${chalk.cyan("⑤")} Usage cap:`, [
|
|
303
325
|
{ name: "Unlimited", value: undefined, hint: "full capacity, wait through rate limits" },
|
|
304
326
|
{ name: "90%", value: 0.9, hint: "leave 10% for other work" },
|
|
305
327
|
{ name: "75%", value: 0.75, hint: "conservative, plenty of headroom" },
|
|
306
328
|
{ name: "50%", value: 0.5, hint: "use half, keep the rest" },
|
|
307
329
|
]);
|
|
308
|
-
const extraChoice = await select(`${chalk.cyan("
|
|
330
|
+
const extraChoice = await select(`${chalk.cyan("⑥")} Allow extra usage ${chalk.dim("(billed separately)")}:`, [
|
|
309
331
|
{ name: "No", value: "no", hint: "stop when plan limits are reached" },
|
|
310
332
|
{ name: "Yes, with $ limit", value: "budget", hint: "set a spending cap" },
|
|
311
333
|
{ name: "Yes, unlimited", value: "unlimited", hint: "keep going no matter what" },
|
|
@@ -319,7 +341,44 @@ async function main() {
|
|
|
319
341
|
}
|
|
320
342
|
else if (extraChoice === "unlimited")
|
|
321
343
|
allowExtraUsage = true;
|
|
322
|
-
|
|
344
|
+
// ⑦ Permission mode (skip if --yolo or --perm set)
|
|
345
|
+
const cliYolo = argv.includes("--yolo");
|
|
346
|
+
if (cliFlags.perm) {
|
|
347
|
+
permissionMode = cliFlags.perm;
|
|
348
|
+
}
|
|
349
|
+
else if (cliYolo) {
|
|
350
|
+
permissionMode = "bypassPermissions";
|
|
351
|
+
}
|
|
352
|
+
else {
|
|
353
|
+
permissionMode = await select(`${chalk.cyan("⑦")} Permissions:`, [
|
|
354
|
+
{ name: "Auto", value: "auto", hint: "accept low-risk, reject high-risk" },
|
|
355
|
+
{ name: "Bypass all", value: "bypassPermissions", hint: "agents can run anything (yolo)" },
|
|
356
|
+
{ name: "Prompt each", value: "default", hint: "ask for every dangerous op" },
|
|
357
|
+
]);
|
|
358
|
+
}
|
|
359
|
+
// ⑧ Worktrees + merge (skip if --yolo, --worktrees, --no-worktrees, or --merge set)
|
|
360
|
+
const gitRepo = isGitRepo(cwd);
|
|
361
|
+
if (cliYolo || argv.includes("--no-worktrees")) {
|
|
362
|
+
useWorktrees = false;
|
|
363
|
+
mergeStrategy = cliFlags.merge || "yolo";
|
|
364
|
+
}
|
|
365
|
+
else if (argv.includes("--worktrees")) {
|
|
366
|
+
useWorktrees = true;
|
|
367
|
+
mergeStrategy = cliFlags.merge || "yolo";
|
|
368
|
+
}
|
|
369
|
+
else if (gitRepo) {
|
|
370
|
+
const wtChoice = await select(`${chalk.cyan("⑧")} Git isolation:`, [
|
|
371
|
+
{ name: "Worktrees + yolo merge", value: "wt-yolo", hint: "isolate agents, merge into current branch" },
|
|
372
|
+
{ name: "Worktrees + new branch", value: "wt-branch", hint: "isolate agents, merge into a new branch" },
|
|
373
|
+
{ name: "No worktrees", value: "no-wt", hint: "all agents share the working directory" },
|
|
374
|
+
]);
|
|
375
|
+
useWorktrees = wtChoice !== "no-wt";
|
|
376
|
+
mergeStrategy = wtChoice === "wt-branch" ? "branch" : "yolo";
|
|
377
|
+
}
|
|
378
|
+
else {
|
|
379
|
+
useWorktrees = false;
|
|
380
|
+
mergeStrategy = "yolo";
|
|
381
|
+
}
|
|
323
382
|
const parts = [];
|
|
324
383
|
if (workerModel !== plannerModel)
|
|
325
384
|
parts.push(`${detectModelTier(workerModel)} → ${detectModelTier(plannerModel)}`);
|
|
@@ -331,6 +390,12 @@ async function main() {
|
|
|
331
390
|
if (usageCap != null)
|
|
332
391
|
parts.push(`cap ${Math.round(usageCap * 100)}%`);
|
|
333
392
|
parts.push(allowExtraUsage ? (extraUsageBudget ? `extra $${extraUsageBudget}` : "extra ∞") : "no extra");
|
|
393
|
+
if (permissionMode !== "auto")
|
|
394
|
+
parts.push(permissionMode === "bypassPermissions" ? "yolo" : "prompt");
|
|
395
|
+
if (useWorktrees)
|
|
396
|
+
parts.push(mergeStrategy === "branch" ? "wt→branch" : "wt→yolo");
|
|
397
|
+
else
|
|
398
|
+
parts.push("no wt");
|
|
334
399
|
if (completedRuns.length > 0)
|
|
335
400
|
parts.push(`${completedRuns.length} prior`);
|
|
336
401
|
const inner = parts.join(chalk.dim(" · "));
|
|
@@ -375,11 +440,28 @@ async function main() {
|
|
|
375
440
|
}
|
|
376
441
|
}
|
|
377
442
|
validateConcurrency(concurrency);
|
|
378
|
-
|
|
379
|
-
|
|
443
|
+
// Resolve permissionMode, useWorktrees, mergeStrategy for non-interactive + non-resume
|
|
444
|
+
if (!resuming && nonInteractive) {
|
|
445
|
+
const yolo = argv.includes("--yolo");
|
|
446
|
+
permissionMode = cliFlags.perm ? cliFlags.perm
|
|
447
|
+
: yolo ? "bypassPermissions"
|
|
448
|
+
: (fileCfg?.permissionMode ?? "auto");
|
|
449
|
+
if (!["auto", "bypassPermissions", "default"].includes(permissionMode)) {
|
|
450
|
+
console.error(chalk.red(` --perm must be auto, bypassPermissions, or default (got ${permissionMode})`));
|
|
451
|
+
process.exit(1);
|
|
452
|
+
}
|
|
453
|
+
useWorktrees = argv.includes("--no-worktrees") || yolo ? false
|
|
454
|
+
: argv.includes("--worktrees") ? true
|
|
455
|
+
: (fileCfg?.useWorktrees ?? isGitRepo(cwd));
|
|
456
|
+
mergeStrategy = cliFlags.merge ? cliFlags.merge
|
|
457
|
+
: (fileCfg?.mergeStrategy ?? "yolo");
|
|
458
|
+
if (!["yolo", "branch"].includes(mergeStrategy)) {
|
|
459
|
+
console.error(chalk.red(` --merge must be yolo or branch (got ${mergeStrategy})`));
|
|
460
|
+
process.exit(1);
|
|
461
|
+
}
|
|
462
|
+
}
|
|
380
463
|
if (useWorktrees)
|
|
381
464
|
validateGitRepo(cwd);
|
|
382
|
-
const mergeStrategy = resuming ? resumeState.mergeStrategy : (fileCfg?.mergeStrategy ?? "yolo");
|
|
383
465
|
if (nonInteractive) {
|
|
384
466
|
const capStr = usageCap != null ? ` cap=${Math.round(usageCap * 100)}%` : "";
|
|
385
467
|
const extraStr = allowExtraUsage ? (extraUsageBudget ? ` extra=$${extraUsageBudget}` : " extra=∞") : " extra=off";
|
|
@@ -416,7 +498,7 @@ async function main() {
|
|
|
416
498
|
for (let i = 0; i < themes.length; i++)
|
|
417
499
|
console.log(chalk.dim(` ${String(i + 1).padStart(3)}.`) + ` ${themes[i]}`);
|
|
418
500
|
console.log(chalk.dim(`\n ${thinkingCount} thinking agents → orchestrate → ${(budget ?? 10) - thinkingCount} execution sessions\n`));
|
|
419
|
-
const action = await selectKey(`${chalk.white(`${themes.length} themes`)} ${chalk.dim(`· ${thinkingCount} thinking · ${concurrency} concurrent`)}`, [{ key: "r", desc: "un" }, { key: "e", desc: "dit" }, { key: "q", desc: "uit" }]);
|
|
501
|
+
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" }]);
|
|
420
502
|
if (action === "r") {
|
|
421
503
|
reviewing = false;
|
|
422
504
|
break;
|
|
@@ -435,6 +517,28 @@ async function main() {
|
|
|
435
517
|
}
|
|
436
518
|
planRestore();
|
|
437
519
|
}
|
|
520
|
+
else if (action === "c") {
|
|
521
|
+
const question = await ask(`\n ${chalk.bold("Ask about the themes:")}\n ${chalk.cyan(">")} `);
|
|
522
|
+
if (!question)
|
|
523
|
+
continue;
|
|
524
|
+
process.stdout.write("\x1B[?25l");
|
|
525
|
+
try {
|
|
526
|
+
let answer = "";
|
|
527
|
+
for await (const msg of query({
|
|
528
|
+
prompt: `You're planning work for: "${objective}"\n\nThemes identified:\n${themes.map((t, i) => `${i + 1}. ${t}`).join("\n")}\n\nUser question: ${question}`,
|
|
529
|
+
options: { cwd, model: plannerModel, permissionMode, persistSession: false },
|
|
530
|
+
})) {
|
|
531
|
+
if (msg.type === "result" && msg.subtype === "success")
|
|
532
|
+
answer = msg.result || "";
|
|
533
|
+
}
|
|
534
|
+
planRestore();
|
|
535
|
+
if (answer)
|
|
536
|
+
console.log(chalk.dim(`\n ${answer.slice(0, 500)}\n`));
|
|
537
|
+
}
|
|
538
|
+
catch {
|
|
539
|
+
planRestore();
|
|
540
|
+
}
|
|
541
|
+
}
|
|
438
542
|
else {
|
|
439
543
|
console.log(chalk.dim("\n Aborted.\n"));
|
|
440
544
|
process.exit(0);
|
package/dist/render.js
CHANGED
|
@@ -183,7 +183,8 @@ export function renderFrame(swarm, showHotkeys, runInfo) {
|
|
|
183
183
|
if (showHotkeys) {
|
|
184
184
|
const pending = runInfo?.pendingSteer ?? 0;
|
|
185
185
|
const chip = pending > 0 ? chalk.cyan(` \u270E ${pending} steer queued`) : "";
|
|
186
|
-
|
|
186
|
+
const fixChip = swarm.failed > 0 && swarm.active > 0 ? chalk.yellow(" [f] fix") : "";
|
|
187
|
+
out.push(chalk.dim(" [b] budget [t] threshold [s] steer [?] ask [q] stop") + fixChip + chip);
|
|
187
188
|
}
|
|
188
189
|
out.push("");
|
|
189
190
|
return out.join("\n");
|
package/dist/swarm.d.ts
CHANGED
|
@@ -59,6 +59,8 @@ export declare class Swarm {
|
|
|
59
59
|
get pending(): number;
|
|
60
60
|
run(): Promise<void>;
|
|
61
61
|
abort(): void;
|
|
62
|
+
/** Re-queue all errored agents' tasks for retry within this wave. */
|
|
63
|
+
requeueFailed(): number;
|
|
62
64
|
logSequence: number;
|
|
63
65
|
log(agentId: number, text: string): void;
|
|
64
66
|
cleanup(): void;
|
package/dist/swarm.js
CHANGED
|
@@ -110,6 +110,21 @@ export class Swarm {
|
|
|
110
110
|
this.activeQueries.forEach(q => q.close());
|
|
111
111
|
this.activeQueries.clear();
|
|
112
112
|
}
|
|
113
|
+
/** Re-queue all errored agents' tasks for retry within this wave. */
|
|
114
|
+
requeueFailed() {
|
|
115
|
+
const errored = this.agents.filter(a => a.status === "error");
|
|
116
|
+
if (errored.length === 0)
|
|
117
|
+
return 0;
|
|
118
|
+
for (const a of errored) {
|
|
119
|
+
this.queue.push(a.task);
|
|
120
|
+
a.status = "pending";
|
|
121
|
+
a.error = undefined;
|
|
122
|
+
a.finishedAt = undefined;
|
|
123
|
+
}
|
|
124
|
+
this.failed -= errored.length;
|
|
125
|
+
this.log(-1, `Re-queued ${errored.length} failed task(s)`);
|
|
126
|
+
return errored.length;
|
|
127
|
+
}
|
|
113
128
|
logSequence = 0;
|
|
114
129
|
log(agentId, text) {
|
|
115
130
|
const entry = { time: Date.now(), agentId, text };
|
|
@@ -201,21 +216,39 @@ export class Swarm {
|
|
|
201
216
|
this.agents.push(agent);
|
|
202
217
|
let agentCwd = task.cwd || this.config.cwd;
|
|
203
218
|
if (this.config.useWorktrees && this.worktreeBase && !task.noWorktree) {
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
219
|
+
const branch = `swarm/task-${id}`;
|
|
220
|
+
const dir = join(this.worktreeBase, `agent-${id}`);
|
|
221
|
+
let worktreeOk = false;
|
|
222
|
+
for (let wt = 0; wt < 2 && !worktreeOk; wt++) {
|
|
223
|
+
try {
|
|
224
|
+
gitExec(`git worktree add -b "${branch}" "${dir}" HEAD`, this.config.cwd);
|
|
225
|
+
worktreeOk = true;
|
|
226
|
+
}
|
|
227
|
+
catch (e) {
|
|
228
|
+
if (wt === 0) {
|
|
229
|
+
this.log(id, `Worktree failed, cleaning up: ${e.message?.slice(0, 50)}`);
|
|
230
|
+
try {
|
|
231
|
+
gitExec(`git branch -D "${branch}"`, this.config.cwd);
|
|
232
|
+
}
|
|
233
|
+
catch { }
|
|
234
|
+
try {
|
|
235
|
+
rmSync(dir, { recursive: true, force: true });
|
|
236
|
+
}
|
|
237
|
+
catch { }
|
|
238
|
+
try {
|
|
239
|
+
gitExec("git worktree prune", this.config.cwd);
|
|
240
|
+
}
|
|
241
|
+
catch { }
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
if (worktreeOk) {
|
|
208
246
|
agentCwd = dir;
|
|
209
247
|
agent.branch = branch;
|
|
210
248
|
this.log(id, `Worktree: ${branch}`);
|
|
211
249
|
}
|
|
212
|
-
|
|
213
|
-
this.log(id, `Worktree failed
|
|
214
|
-
agent.status = "error";
|
|
215
|
-
agent.error = "worktree creation failed";
|
|
216
|
-
agent.finishedAt = Date.now();
|
|
217
|
-
this.failed++;
|
|
218
|
-
return;
|
|
250
|
+
else {
|
|
251
|
+
this.log(id, `Worktree failed after retry — running without isolation`);
|
|
219
252
|
}
|
|
220
253
|
}
|
|
221
254
|
this.log(id, `Starting: ${task.prompt.slice(0, 60)}`);
|
package/dist/ui.js
CHANGED
|
@@ -266,6 +266,9 @@ export class RunDisplay {
|
|
|
266
266
|
this.inputBuf = "";
|
|
267
267
|
}
|
|
268
268
|
}
|
|
269
|
+
else if ((s === "f" || s === "F") && this.swarm && this.swarm.failed > 0 && this.swarm.active > 0) {
|
|
270
|
+
this.swarm.requeueFailed();
|
|
271
|
+
}
|
|
269
272
|
else if ((s === "s" || s === "S") && this.onSteer) {
|
|
270
273
|
this.inputMode = "steer";
|
|
271
274
|
this.inputBuf = "";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "claude-overnight",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.11.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": {
|