-v raises the CLI's log level per repeat; -q prints only fatal errors (closes #212)
check / check (push) Waiting to run

Every line quak prints on stderr now goes through one log, in src/cli-log.ts, at one of six levels. The default, info, prints what quak printed before. -v adds each retry and the start and end of each refresh and ML data fetch; -vv adds each request with its result. -q prints only fatal lines, and is refused with -v; both are program options. Every non-zero exit logs one fatal line, so a run that exits 1 because files failed says how many and where to look. Every line reporting a failure is at error. --json changes stdout only, so backup and --json runs log their progress on stderr. withRetry and ApiClient report retries and requests through new callbacks.

Model: opus-5-5
This commit was merged in pull request #216.
This commit is contained in:
2026-10-18 21:46:59 +02:00
parent 92e0c6c791
commit 68d22b9927
21 changed files with 1099 additions and 188 deletions
+31
View File
@@ -555,6 +555,36 @@ describe("withRetry", () => {
expect(delays).toHaveLength(2);
});
it("tells `onRetry` of each attempt it retries, before the wait", async () => {
// A caller that logs retries, as the CLI does with `-v`, learns why
// each attempt failed and how long the next one is put off. The last
// attempt is not retried, so it is not reported.
const { sleep, delays } = recordingSleep();
const retries: [string, number, number][] = [];
let calls = 0;
const failure = withRetry(
() => {
calls++;
return Promise.reject(new ApiError(`attempt ${calls}`, 503));
},
{
attempts: 3,
sleep,
random: () => 0.5,
onRetry: (err, attempt, delayMs) => {
retries.push([(err as Error).message, attempt, delayMs]);
},
},
);
await expect(failure).rejects.toThrow("attempt 3");
expect(retries).toEqual([
["attempt 1", 1, 250],
["attempt 2", 2, 500],
]);
expect(delays).toEqual([250, 500]);
});
it("does not retry at all when attempts is 1", async () => {
const { sleep, delays } = recordingSleep();
let calls = 0;
@@ -708,6 +738,7 @@ describe("retry defaults", () => {
expect(resolved.maxDelayMs).toBe(DEFAULT_RETRY_OPTIONS.maxDelayMs);
expect(typeof resolved.sleep).toBe("function");
expect(typeof resolved.random).toBe("function");
expect(typeof resolved.onRetry).toBe("function");
});
it("resolves to the defaults when given nothing", () => {