feat(cli): terminal art for login/logout/whoami - #3556
Conversation
Terminal UI for `dimos login`, `dimos logout` and `dimos whoami` is plain
`typer.echo` output. It works, but the UX could be considerably better —
especially during the browser-approval wait, which is the longest thing the
CLI asks anyone to sit through.
Adds `dimos/cli/art.py`, a pure rendering module where every function returns
`list[str]`. `cloud.py` swaps seven prints for seven calls to it. No new
dependencies: `terminaltexteffects` was already one, and this drops the `rich`
imports from `cloud.py`.
No behaviour change — `_post`, `_store`, `_forget`, `api_key`, the polling
loop and every exit code are byte-identical, and the seven existing tests pass
unmodified.
- one card carries the whole wait: URL, device code, and a last line that
ticks. `Live.pause()` sleeps the server's interval but wakes every 100ms
to animate, so the spinner turns without changing the poll cadence.
- colour is state: grey until something happens, red when a code is
refused, and the brand gradient once you are through.
- signing in reveals the wordmark and lands on a single card — account and
key at the top, then a short About DimOS with the community link and
capability areas. `dimos --help` remains the command list.
- `whoami` stays a single line. It is a lookup people script against, not a
moment, and its output format is unchanged.
Putting text in a fixed-width box adds two failure modes plain `echo` does not
have, and the tests pin both down:
- nothing may exceed the terminal width. The layout drops the art, then the
border, then the two-column labels; thresholds are derived from the copy
rather than picked, so the device code and verification URL survive down
to 30 columns — the width of the URL itself.
- no art reaches non-terminal output. Off a TTY the cards render as plain
lines, so CI logs and journald get the words alone.
Codecov Report❌ Patch coverage is
@@ Coverage Diff @@
## main #3556 +/- ##
========================================
Coverage 76.13% 76.13%
========================================
Files 1228 1230 +2
Lines 119172 119485 +313
Branches 10684 10721 +37
========================================
+ Hits 90726 90972 +246
- Misses 25345 25402 +57
- Partials 3101 3111 +10
Flags with carried forward coverage won't be shown. Click here to find out more.
... and 4 files with indirect coverage changes 🚀 New features to boost your workflow:
|
Greptile SummaryThis change adds adaptive terminal artwork and uses the new rendering helpers for cloud authentication output. Error output can still contain ANSI color sequences when stdout is interactive but stderr is redirected, which leaves escape codes in logs and error pipelines. Confidence Score: 4/5Not safe to merge as-is because redirected stderr can receive terminal escape sequences instead of plain text. The mixed-output-stream behavior was exercised directly with interactive stdout and redirected stderr, and the captured stderr output retained ANSI escape codes. Files Needing Attention: dimos/cli/art.py needs to determine terminal capability from the stream selected by show(..., err=True).
What T-Rex did
|
| out = "\n".join(rows if enabled() else [_ESC.sub("", r) for r in rows]) | ||
| print(out, file=sys.stderr if err else sys.stdout) |
There was a problem hiding this comment.
Error output uses stdout terminal state
show(..., err=True) decides whether to preserve ANSI sequences through enabled(), which checks sys.stdout even though this call writes to sys.stderr. When stdout remains interactive but stderr is redirected to a log or error pipeline, color escapes are retained in that redirected output. Select the destination stream before checking terminal capability so redirected stderr receives plain text.
Artifacts
Narrow runnable test source for stdout-TTY and redirected-stderr behavior
- Authored Python probe that replaces stdout and stderr with streams reporting True and False from isatty(), invokes both show destinations with ANSI rows, and asserts their captured contents. The test directly exercises the claimed path.
Captured full source of the narrow destination-stream test
- Captured output of `cat trex-artifacts/art-show-destination-stream-test.py`, including command, working directory, exit code, and the full authored test source. The evidence records the exact runnable probe.
Runtime output showing ANSI escapes retained on redirected stderr
- Captured execution of the narrow probe with stdout reporting TTY and stderr reporting non-TTY; the captured stderr representation contains `\x1b[31m` and `\x1b[0m`. The takeaway is that show(err=True) leaks ANSI escapes to redirected stderr.
|
|
||
| # Sampled down the centre of the portal in the brand asset: pale yellow at the | ||
| # lintel, through cream and mint, to salmon at the threshold. | ||
| PORTAL: list[RGB] = [ |
There was a problem hiding this comment.
we have standardized theme in cli/theme and dimos/dimos/cli/dimos.tcss would be nice to unify more instead introduce a third place with colors
|
Personally, I dislike animations. On This may be a bit controversial, but I think animations in TUIs are perceived as a software project not having its priorities right (like the infamous Ryan Dahl rant about unicode characters in terminals and the rest: https://tinyclouds.org/rant/ ) |
Contribution path
Problem
Terminal UI for
dimos login,dimos logoutanddimos whoamiis plaintyper.echooutput. It works, but the UX could be better.Demo
dimos login→whoami→logout→ a refused code.Solution
New
dimos/cli/art.py: pure rendering, every function returnslist[str].cloud.pyswaps seven prints for seven calls to it.No behaviour change —
_post,_store,_forget,api_key, the polling loop and every exit code are byte-identical, and the seven existing tests pass unmodified.No new libraries —
terminaltexteffectswas already one; this drops therichimports fromcloud.py.Four things to understand:
Live— the login card is redrawn in place each poll.pause()sleeps the server'sintervalbut wakes every 100ms to animate, so the spinner turns without changing the poll cadence.card()— the width ladder. Drops the art, then the border, then the two-column labels until it fits. Thresholds are derived from the copy, so the device code and URL survive to 30 columns.enabled()—isatty() and not NO_COLOR. When false there is no art, just the words, so nothing decorative reaches CI logs.Signing in lands on a single card: account and key at the top, then a short About DimOS with the community link and capability areas.
How to Test
40 tests pass (33 new, 7 existing unmodified). The width sweep covers 30–120 columns and asserts the device code and URL are never truncated.
AI assistance
Claude Code with Opus 5, heavily involved — design exploration and implementation.
Checklist