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 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("")} Worker model:`, models.map(m => ({ name: m.displayName, value: m.value, hint: m.description })));
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("")} ${chalk.dim("Worker model [claude-sonnet-4-6]:")} `);
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("")} Usage cap:`, [
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("")} Allow extra usage ${chalk.dim("(billed separately)")}:`, [
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
- concurrency = Math.min(5, budget);
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
- const permissionMode = resuming ? resumeState.permissionMode : (fileCfg?.permissionMode ?? "auto");
379
- const useWorktrees = resuming ? resumeState.useWorktrees : (fileCfg?.useWorktrees ?? isGitRepo(cwd));
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
- out.push(chalk.dim(" [b] budget [t] threshold [s] steer [?] ask [q] stop") + chip);
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
- try {
205
- const branch = `swarm/task-${id}`;
206
- const dir = join(this.worktreeBase, `agent-${id}`);
207
- gitExec(`git worktree add -b "${branch}" "${dir}" HEAD`, this.config.cwd);
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
- catch (e) {
213
- this.log(id, `Worktree failed: ${e.message?.slice(0, 60)}`);
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.10.0",
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": {