-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
the line a failing command exits with, and is refused with -v. Both are
program options. withRetry reports retries through a new onRetry, and
ApiClient reports requests through a new onRequest; the CLI logs what
they report. --json now changes stdout only, so backup and --json runs
log their progress on stderr.

Model: opus-5-5
This commit is contained in:
2026-10-18 18:36:33 +00:00
parent 92e0c6c791
commit f9969b2525
17 changed files with 876 additions and 155 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", () => {