claude-overnight 1.8.3 → 1.9.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.d.ts ADDED
@@ -0,0 +1,38 @@
1
+ import type { ModelInfo } from "@anthropic-ai/claude-agent-sdk";
2
+ import type { Task, PermMode, MergeStrategy } from "./types.js";
3
+ export declare function parseCliFlags(argv: string[]): {
4
+ flags: Record<string, string>;
5
+ positional: string[];
6
+ };
7
+ export declare function isAuthError(err: unknown): boolean;
8
+ export declare function fetchModels(timeoutMs?: number): Promise<ModelInfo[]>;
9
+ export declare function ask(question: string): Promise<string>;
10
+ export declare function select<T>(label: string, items: {
11
+ name: string;
12
+ value: T;
13
+ hint?: string;
14
+ }[], defaultIdx?: number): Promise<T>;
15
+ export declare function selectKey(label: string, options: {
16
+ key: string;
17
+ desc: string;
18
+ }[]): Promise<string>;
19
+ export interface FileArgs {
20
+ tasks: Task[];
21
+ objective?: string;
22
+ concurrency?: number;
23
+ model?: string;
24
+ permissionMode?: PermMode;
25
+ cwd?: string;
26
+ allowedTools?: string[];
27
+ useWorktrees?: boolean;
28
+ mergeStrategy?: MergeStrategy;
29
+ usageCap?: number;
30
+ flexiblePlan?: boolean;
31
+ }
32
+ export declare function loadTaskFile(file: string): FileArgs;
33
+ export declare function validateConcurrency(value: unknown): asserts value is number;
34
+ export declare function isGitRepo(cwd: string): boolean;
35
+ export declare function validateGitRepo(cwd: string): void;
36
+ export declare function showPlan(tasks: Task[]): void;
37
+ export declare const BRAILLE: readonly ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
38
+ export declare function makeProgressLog(): (text: string) => void;
package/dist/cli.js ADDED
@@ -0,0 +1,273 @@
1
+ import { readFileSync } from "fs";
2
+ import { resolve } from "path";
3
+ import { createInterface } from "readline";
4
+ import chalk from "chalk";
5
+ import { query } from "@anthropic-ai/claude-agent-sdk";
6
+ // ── CLI flag parsing ──
7
+ export function parseCliFlags(argv) {
8
+ const known = new Set(["concurrency", "model", "timeout", "budget", "usage-cap", "extra-usage-budget"]);
9
+ const booleans = new Set(["--dry-run", "-h", "--help", "-v", "--version", "--no-flex", "--allow-extra-usage"]);
10
+ const flags = {};
11
+ const positional = [];
12
+ for (let i = 0; i < argv.length; i++) {
13
+ const arg = argv[i];
14
+ if (booleans.has(arg))
15
+ continue;
16
+ const eq = arg.match(/^--(\w[\w-]*)=(.+)$/);
17
+ if (eq && known.has(eq[1])) {
18
+ flags[eq[1]] = eq[2];
19
+ continue;
20
+ }
21
+ const bare = arg.match(/^--(\w[\w-]*)$/);
22
+ if (bare && known.has(bare[1]) && i + 1 < argv.length && !argv[i + 1].startsWith("-")) {
23
+ flags[bare[1]] = argv[++i];
24
+ continue;
25
+ }
26
+ if (!arg.startsWith("--"))
27
+ positional.push(arg);
28
+ }
29
+ return { flags, positional };
30
+ }
31
+ // ── Auth error detection ──
32
+ const AUTH_PATTERNS = ["unauthorized", "forbidden", "invalid_api_key", "authentication"];
33
+ export function isAuthError(err) {
34
+ const msg = err instanceof Error ? err.message : String(err);
35
+ return AUTH_PATTERNS.some((p) => msg.toLowerCase().includes(p));
36
+ }
37
+ // ── Fetch models via SDK ──
38
+ export async function fetchModels(timeoutMs = 10_000) {
39
+ let q;
40
+ let timer;
41
+ try {
42
+ q = query({ prompt: "", options: { persistSession: false } });
43
+ const models = await Promise.race([
44
+ q.supportedModels(),
45
+ new Promise((_, reject) => {
46
+ timer = setTimeout(() => reject(new Error("model_fetch_timeout")), timeoutMs);
47
+ }),
48
+ ]);
49
+ clearTimeout(timer);
50
+ q.close();
51
+ return models;
52
+ }
53
+ catch (err) {
54
+ clearTimeout(timer);
55
+ q?.close();
56
+ if (err.message === "model_fetch_timeout") {
57
+ console.warn(chalk.yellow("\n Model fetch timed out — continuing with defaults"));
58
+ }
59
+ else if (isAuthError(err)) {
60
+ console.error(chalk.red("\n Authentication failed — check your API key or run: claude auth\n"));
61
+ process.exit(1);
62
+ }
63
+ else {
64
+ console.warn(chalk.yellow(`\n Could not fetch models: ${String(err.message || err).slice(0, 80)} — continuing with defaults`));
65
+ }
66
+ return [];
67
+ }
68
+ }
69
+ // ── Interactive primitives ──
70
+ export function ask(question) {
71
+ const rl = createInterface({ input: process.stdin, output: process.stdout });
72
+ return new Promise((res) => {
73
+ rl.question(question, (answer) => { rl.close(); res(answer.trim()); });
74
+ });
75
+ }
76
+ export async function select(label, items, defaultIdx = 0) {
77
+ const { stdin, stdout } = process;
78
+ let idx = defaultIdx;
79
+ const draw = (first = false) => {
80
+ if (!first)
81
+ stdout.write(`\x1B[${items.length}A`);
82
+ for (let i = 0; i < items.length; i++) {
83
+ const sel = i === idx;
84
+ const radio = sel ? chalk.cyan(" ● ") : chalk.dim(" ○ ");
85
+ const name = sel ? chalk.white(items[i].name) : chalk.dim(items[i].name);
86
+ const hint = items[i].hint ? chalk.dim(` · ${items[i].hint}`) : "";
87
+ stdout.write(`\x1B[2K${radio}${name}${hint}\n`);
88
+ }
89
+ };
90
+ stdout.write(`\n ${chalk.bold(label)}\n`);
91
+ draw(true);
92
+ return new Promise((resolve) => {
93
+ stdin.setRawMode(true);
94
+ stdin.resume();
95
+ const done = (val) => {
96
+ stdin.setRawMode(false);
97
+ stdin.removeListener("data", handler);
98
+ stdin.pause();
99
+ resolve(val);
100
+ };
101
+ const handler = (buf) => {
102
+ const s = buf.toString();
103
+ if (s === "\x1B[A") {
104
+ idx = (idx - 1 + items.length) % items.length;
105
+ draw();
106
+ }
107
+ else if (s === "\x1B[B") {
108
+ idx = (idx + 1) % items.length;
109
+ draw();
110
+ }
111
+ else if (s === "\r")
112
+ done(items[idx].value);
113
+ else if (s === "\x03") {
114
+ stdin.setRawMode(false);
115
+ process.exit(0);
116
+ }
117
+ else if (/^[1-9]$/.test(s)) {
118
+ const n = parseInt(s) - 1;
119
+ if (n < items.length) {
120
+ idx = n;
121
+ draw();
122
+ done(items[idx].value);
123
+ }
124
+ }
125
+ };
126
+ stdin.on("data", handler);
127
+ });
128
+ }
129
+ export async function selectKey(label, options) {
130
+ const { stdin, stdout } = process;
131
+ const keys = options.map((o) => o.key.toLowerCase());
132
+ const optStr = options.map((o) => `${chalk.cyan.bold(o.key.toUpperCase())}${chalk.dim(o.desc)}`).join(chalk.dim(" │ "));
133
+ stdout.write(`\n ${label}\n ${optStr}\n `);
134
+ return new Promise((resolve) => {
135
+ stdin.setRawMode(true);
136
+ stdin.resume();
137
+ const handler = (buf) => {
138
+ const s = buf.toString().toLowerCase();
139
+ if (s === "\x03") {
140
+ stdin.setRawMode(false);
141
+ process.exit(0);
142
+ }
143
+ if (s === "\r") {
144
+ stdin.setRawMode(false);
145
+ stdin.removeListener("data", handler);
146
+ stdin.pause();
147
+ resolve(keys[0]);
148
+ return;
149
+ }
150
+ if (keys.includes(s)) {
151
+ stdin.setRawMode(false);
152
+ stdin.removeListener("data", handler);
153
+ stdin.pause();
154
+ resolve(s);
155
+ }
156
+ };
157
+ stdin.on("data", handler);
158
+ });
159
+ }
160
+ const KNOWN_TASK_FILE_KEYS = new Set([
161
+ "tasks", "objective", "concurrency", "cwd", "model", "permissionMode", "allowedTools", "worktrees", "mergeStrategy", "usageCap", "flexiblePlan",
162
+ ]);
163
+ export function loadTaskFile(file) {
164
+ const path = resolve(file);
165
+ let raw;
166
+ try {
167
+ raw = readFileSync(path, "utf-8");
168
+ }
169
+ catch {
170
+ throw new Error(`Cannot read task file: ${path}`);
171
+ }
172
+ let json;
173
+ try {
174
+ json = JSON.parse(raw);
175
+ }
176
+ catch {
177
+ throw new Error(`Task file is not valid JSON: ${path}`);
178
+ }
179
+ const parsed = Array.isArray(json) ? { tasks: json } : json;
180
+ if (!Array.isArray(json) && typeof json === "object" && json !== null) {
181
+ const unknown = Object.keys(json).filter((k) => !KNOWN_TASK_FILE_KEYS.has(k));
182
+ if (unknown.length > 0) {
183
+ throw new Error(`Unknown key${unknown.length > 1 ? "s" : ""} in task file: ${unknown.join(", ")}. Allowed: ${[...KNOWN_TASK_FILE_KEYS].join(", ")}`);
184
+ }
185
+ }
186
+ if (!Array.isArray(parsed.tasks))
187
+ throw new Error(`Task file must contain a "tasks" array (got ${typeof parsed.tasks})`);
188
+ const tasks = [];
189
+ for (let i = 0; i < parsed.tasks.length; i++) {
190
+ const t = parsed.tasks[i];
191
+ const id = String(tasks.length);
192
+ if (typeof t === "string") {
193
+ if (!t.trim())
194
+ throw new Error(`Task ${i} is an empty string`);
195
+ tasks.push({ id, prompt: t });
196
+ }
197
+ else if (typeof t === "object" && t !== null) {
198
+ if (typeof t.prompt !== "string" || !t.prompt.trim())
199
+ throw new Error(`Task ${i} is missing a "prompt" string`);
200
+ tasks.push({ id, prompt: t.prompt, cwd: t.cwd ? resolve(t.cwd) : undefined, model: t.model });
201
+ }
202
+ else {
203
+ throw new Error(`Task ${i} must be a string or object with a "prompt" field (got ${typeof t})`);
204
+ }
205
+ }
206
+ if (parsed.concurrency !== undefined)
207
+ validateConcurrency(parsed.concurrency);
208
+ const usageCap = parsed.usageCap;
209
+ if (usageCap != null && (typeof usageCap !== "number" || usageCap < 0 || usageCap > 100)) {
210
+ throw new Error(`usageCap must be a number between 0 and 100 (got ${JSON.stringify(usageCap)})`);
211
+ }
212
+ if (parsed.flexiblePlan && typeof parsed.objective !== "string") {
213
+ throw new Error(`flexiblePlan requires an "objective" string in the task file`);
214
+ }
215
+ return {
216
+ tasks,
217
+ objective: typeof parsed.objective === "string" ? parsed.objective : undefined,
218
+ concurrency: parsed.concurrency,
219
+ model: parsed.model,
220
+ cwd: parsed.cwd ? resolve(parsed.cwd) : undefined,
221
+ permissionMode: parsed.permissionMode,
222
+ allowedTools: parsed.allowedTools,
223
+ useWorktrees: parsed.worktrees,
224
+ mergeStrategy: parsed.mergeStrategy,
225
+ usageCap,
226
+ flexiblePlan: parsed.flexiblePlan,
227
+ };
228
+ }
229
+ // ── Validation helpers ──
230
+ export function validateConcurrency(value) {
231
+ if (typeof value !== "number" || !Number.isInteger(value) || value < 1) {
232
+ throw new Error(`Concurrency must be a positive integer (got ${JSON.stringify(value)})`);
233
+ }
234
+ }
235
+ export function isGitRepo(cwd) {
236
+ const { execSync } = require("child_process");
237
+ try {
238
+ execSync("git rev-parse --git-dir", { cwd, encoding: "utf-8", stdio: "pipe" });
239
+ return true;
240
+ }
241
+ catch {
242
+ return false;
243
+ }
244
+ }
245
+ export function validateGitRepo(cwd) {
246
+ if (!isGitRepo(cwd)) {
247
+ throw new Error(`Worktrees require a git repository, but ${cwd} is not inside one.\n` +
248
+ ` Run: cd ${cwd} && git init\n` +
249
+ ` Or set "worktrees": false in your task file.`);
250
+ }
251
+ }
252
+ // ── Display helpers ──
253
+ export function showPlan(tasks) {
254
+ const w = Math.max((process.stdout.columns ?? 80) - 6, 40);
255
+ const ruleLen = Math.min(w, 70);
256
+ console.log(chalk.dim(` ─── ${tasks.length} tasks ${"─".repeat(Math.max(0, ruleLen - String(tasks.length).length - 10))}`));
257
+ for (const t of tasks) {
258
+ const num = chalk.dim(String(Number(t.id) + 1).padStart(4) + ".");
259
+ console.log(`${num} ${t.prompt.slice(0, w)}`);
260
+ }
261
+ console.log(chalk.dim(` ${"─".repeat(ruleLen)}\n`));
262
+ }
263
+ export const BRAILLE = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
264
+ export function makeProgressLog() {
265
+ let frame = 0;
266
+ return (text) => {
267
+ const spin = chalk.cyan(BRAILLE[frame++ % BRAILLE.length]);
268
+ const maxW = (process.stdout.columns ?? 80) - 6;
269
+ const clean = text.replace(/\n/g, " ");
270
+ const line = clean.length > maxW ? clean.slice(0, maxW - 1) + "\u2026" : clean;
271
+ process.stdout.write(`\x1B[2K\r ${spin} ${chalk.dim(line)}`);
272
+ };
273
+ }