Pure — no I/O — so a unit test on this function alone proves the
refusal decision (CI, no --key → refuse) without a subprocess.
It does not by itself prove run() consults it before constructing
a Dio; that ordering is run()'s first statement, before either
_loginWithKey or _loginWithPassword — the only two places a Dio
gets built — are ever reached.
Pure — the no-orphan / self-match decision, no I/O. Null means "print
nothing": either no cli:<hostname> key exists at all, or the one
that does is the very key just pasted, so nothing is orphaned.
Pure — email and keyName only, no apiKey/rawKey parameter, so
the signature itself is why this can't print a raw key. The "never
writes the raw key to stdout" claim rests on that plus run()
reaching this only via AppLogger.success(successMessage(...)) in
_loginWithKey/_loginWithPassword above — never a raw string
interpolating rawKey/result.apiKey — both verified by reading,
not by this function's own tests, which pin its formatting instead.