cli: maturity polish — color, error translation, tabwriter consistency

Adds three small but high-leverage presentation tweaks for v0.1:

1. internal/cli/style is a new ~70 LOC package with Pass/Fail/Warn/
   Dim/Bold helpers. Each is TTY-gated and obeys NO_COLOR. No
   external dep. Wired into the doctor PASS/FAIL/WARN status, the
   "banger:" error prefix on stderr, and the dim 'ready in <elapsed>'
   line.
2. internal/cli/errors translates rpc.ErrorResponse into user-facing
   text. operation_failed becomes invisible (the message wins);
   not_found, already_exists, bad_request, bad_version, unauthorized,
   unknown_method get short labels; unknown codes pass through. The
   daemon-attached op_id lands in dim parens — paste into
   journalctl --grep to find the daemon log line that produced the
   failure.
3. Tabwriter config converges on (0, 8, 2, ' ', 0) across every
   list/table command. The vm prune confirmation table picked up the
   right config; system install + system status switched from bare
   "key: value\n" lines to tabular form. printVMSpecLine drops its
   Unicode middle dot for an ASCII '|' so terminals without UTF-8
   render cleanly.

Tests cover translateRPCError for every code, style helpers no-op
on non-TTY and under NO_COLOR. Smoke status greps switch from
"key: value" to "key   value" to match the new format.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Thales Maciel 2026-04-26 22:27:07 -03:00
parent e47b8146dc
commit 71a332a6a1
No known key found for this signature in database
GPG key ID: 33112E6833C34679
11 changed files with 358 additions and 28 deletions

View file

@ -1790,10 +1790,14 @@ func TestDaemonStatusIncludesLogPathWhenStopped(t *testing.T) {
}
output := stdout.String()
// Output is tabwriter-formatted (key TAB value, padded). Assert
// the key and value land on the same line rather than pinning a
// specific separator.
for _, want := range []string{
"service: bangerd.service",
"socket: /run/banger/bangerd.sock",
"log: journalctl -u bangerd.service",
"service",
"bangerd.service",
"/run/banger/bangerd.sock",
"journalctl -u bangerd.service",
} {
if !strings.Contains(output, want) {
t.Fatalf("output = %q, want %q", output, want)
@ -1825,13 +1829,14 @@ func TestDaemonStatusIncludesDaemonBuildInfoWhenRunning(t *testing.T) {
output := stdout.String()
for _, want := range []string{
"service: bangerd.service",
"socket: /run/banger/bangerd.sock",
"log: journalctl -u bangerd.service",
"pid: 42",
"version: v1.2.3",
"commit: abc123",
"built_at: 2026-03-22T12:00:00Z",
"service",
"bangerd.service",
"/run/banger/bangerd.sock",
"journalctl -u bangerd.service",
"42",
"v1.2.3",
"abc123",
"2026-03-22T12:00:00Z",
} {
if !strings.Contains(output, want) {
t.Fatalf("output = %q, want %q", output, want)