-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
+40 -8
View File
@@ -2,8 +2,9 @@
import { stdout, stderr } from "node:process";
import { input, password } from "@inquirer/prompts";
import { Command } from "commander";
import { Command, Option } from "commander";
import envPaths from "env-paths";
import { logLevel, logRequests, stderrLog, type Log } from "../src/cli-log.js";
import { init } from "../src/crypto/index.js";
import {
type CliContext,
@@ -29,6 +30,9 @@ const paths = envPaths("quak", { suffix: "" });
const program = new Command();
// Program options, so they are taken before or after any command's name.
// `-v` counts its repeats, the way commander's documentation counts a repeated
// option.
program
.name("quak")
.description("CLI for the Ente end-to-end encrypted photo service")
@@ -37,21 +41,46 @@ program
"--cache-dir <path>",
"Directory for the local metadata/content cache " +
"(default: the per-user cache directory)",
);
)
.option(
"-v, --verbose",
"Log more on stderr: debug lines with -v, trace lines too with -vv",
(_value: string, previous: number) => previous + 1,
0,
)
.addOption(
new Option(
"-q, --quiet",
"Print nothing on stderr but the error a failing command exits with",
).conflicts("verbose"),
)
.configureHelp({ showGlobalOptions: true });
interface ProgramOptions {
cacheDir?: string;
verbose: number;
quiet?: boolean;
}
// The log for this run, at the level `-v` and `-q` ask for.
const log = (): Log => {
const { verbose, quiet } = program.opts<ProgramOptions>();
return stderrLog(stderr, logLevel(verbose, quiet ?? false));
};
const context = (): CliContext => ({
stdout,
stderr,
log: log(),
sessionDir: paths.data,
cacheDir: program.opts<{ cacheDir?: string }>().cacheDir,
loadSession,
login: (opts) => Client.login(opts),
cacheDir: program.opts<ProgramOptions>().cacheDir,
loadSession: (path) => loadSession(path, logRequests(log())),
login: (opts) => Client.login({ ...opts, apiOptions: logRequests(log()) }),
prompt: (message) => input({ message }),
promptSecret: (message) => password({ message, mask: true }),
});
const run = (command: Promise<number>): Promise<void> =>
runCommand(command, stdout, stderr, (code) => process.exit(code));
runCommand(command, stdout, stderr, log(), (code) => process.exit(code));
program
.command("login")
@@ -143,7 +172,10 @@ program
{
...context(),
loadSession: (path) =>
loadSession(path, { retry: UNATTENDED_RETRY_OPTIONS }),
loadSession(
path,
logRequests(log(), UNATTENDED_RETRY_OPTIONS),
),
},
dir,
opts,