feat: redesign mlxcel list output (modified, --json/-q/-v, styling)#143
Conversation
Build on #138 (which made `mlxcel list` the local-models inventory) to bring the listing UX in line with `ollama list` / `docker images`: a modified-time column, machine-readable and quiet output modes, NAME capping, sorting, and TTY-aware styling. Purely additive on top of #138 — it does not change which models are listed. Note on default layout: this changes the *default* `mlxcel list` table. The columns are now NAME / SIZE / MODIFIED (PATH is dropped from the default view and restored only under `-v/--verbose`), and the header is the compact `N model(s) · <total> · <root>` form with `$HOME` contracted to `~`. Scripts that scraped the old `REPO ID / SIZE / PATH` table or the `Downloaded models (...)` header line should move to `--json` (stable field names) or `-q` (repo-ids only); both are unstyled and pipe-safe. Data model: add `StoredModel.modified: Option<SystemTime>` (`src/downloader/store.rs`), sourced from the snapshot directory mtime at both `list_models_under` construction sites; `None` when the stat fails and rendered as `-`. Renderer (`src/commands/models.rs`): the rendering functions take `now` and `use_color` as injected parameters (never read the clock or `is_terminal()` internally) so golden tests stay deterministic and filesystem-free. A pure `humanize_relative(Duration)` helper maps elapsed time to compact labels (just now / N min / N hour(s) / N day(s) / N week(s) up to 4 weeks / N month(s) / N year(s)), correctly pluralized; a future mtime (clock skew) renders as "just now". NAME is capped at 40 chars and ellipsized with `…`. `--json` emits a dedicated `Serialize` struct with stable field names `{repo_id, size_bytes, path, modified}` (modified as Unix epoch seconds or null); `-q/--quiet` prints only repo-ids; `--sort name|size|modified` orders the rows (name asc, size largest-first, modified most-recent-first with None last). Styling dims only the MODIFIED cells and the header root (NAME stays plain) and is gated by a new stdout-oriented `should_color_stdout()` that mirrors the `NO_COLOR` + `is_terminal()` semantics of `should_show_progress()`; `--json`/`-q` are never styled. The empty-store hint from #138 (including the `mlxcel arch` redirect) is preserved. CLI (`src/main.rs`): extend `ListArgs` with `--json`, `-q/--quiet`, `-v/--verbose`, and `--sort` (clap `ValueEnum`, default name), with clap-enforced mutual exclusivity (`--json` conflicts with `--quiet`/`--verbose`; `--quiet` conflicts with `--verbose`; `--sort` is allowed with any). Tests: rework the `models.rs` golden tests for the new columns (default / json / quiet / verbose / empty), add `humanize_relative` boundary tests and a no-ANSI styling assertion, and add CLI-surface tests for the new flags' defaults, parsing, and conflicts.
Implementation Review SummaryIntent
Findings Addressed
Remaining Items
Verification against issue #141
Build verification (warm, narrow commands)
Verdict: the PR correctly and completely implements #141. |
Security & Performance Review — no blocking findingsReviewed the full change surface against the security/performance/code-quality checklists, with 1. Panic paths — clearNo reachable panic on user- or filesystem-influenced input.
2. Performance — clear
3. JSON correctness / injection — safeThe sole serialization site is 4. Terminal-escape consideration — pre-existing, NOT worsened (informational)The human table prints This PR does not worsen it:
Severity: informational / pre-existing (user owns their own model store; no new attack surface). If hardening is ever desired, a separate follow-up could sanitize control bytes in Other
LGTM from a security/performance standpoint. |
Add a CHANGELOG [Unreleased] entry describing the new NAME/SIZE/MODIFIED default table, the dropped PATH column (restored via -v/--verbose), humanized MODIFIED times from snapshot directory mtime, --json stable output, -q quiet mode, --sort, and TTY-aware styling with NO_COLOR support. Update the README "Manage downloaded models" section: replace the stale comment "List downloaded models with their on-disk size and path" with the accurate default (name, size, last-modified time) and add short example lines for --json, -q, and -v so readers can discover the new modes.
PR Finalization CompleteSummary
Changes made (commit 13513a4)CHANGELOG.md — added a new bullet under in , placed immediately after the #138 BREAKING entry and before . Describes: NAME/SIZE/MODIFIED default table, PATH moved behind -v/--verbose, humanized mtime from snapshot directory mtime, --json stable array output, -q quiet mode, --sort, and TTY-aware styling respecting NO_COLOR. README.md — replaced the stale comment on line 84 ("List downloaded models with their on-disk size and path") with the accurate default description, and added three example lines showing --json, -q, and -v modes. Confirmed no other README or docs prose describes the old REPO ID / SIZE / PATH table shape. All checks passing. Ready for merge. |
Summary
Improve the presentation/UX of
mlxcel list(the local-models inventory introduced by #138): aMODIFIEDtime column, machine-readable (--json) and quiet (-q) output, a-v/--verbosePATH restore,--sort, NAME capping/ellipsis, and TTY-aware styling that respectsNO_COLOR. Builds on #138 and is purely additive — it does not change which models are listed.Default-layout change (call-out)
This changes the default
mlxcel listtable. Columns are nowNAME / SIZE / MODIFIED(PATH is dropped from the default view, restored only under-v/--verbose), and the header becomes the compactN model(s) · <total> · <root>form with$HOMEcontracted to~. It is not a breaking flag removal, but scripts that scraped the oldREPO ID / SIZE / PATHtable or theDownloaded models (...)header should switch to--json(stable field names) or-q(repo-ids only) — both unstyled and pipe-safe.What changed
src/downloader/store.rs: addStoredModel.modified: Option<SystemTime>, populated from the snapshot directory mtime at bothlist_models_underconstruction sites (std::fs::metadata(path).modified().ok());Noneon stat failure.src/commands/models.rs: renderer redesign. Rendering functions take injectednow+use_color(never read the clock oris_terminal()internally) so golden tests are deterministic and filesystem-free. Adds purehumanize_relative(Duration)(just now / min / hour(s) / day(s) / week(s) up to 4 weeks / month(s) / year(s), pluralized; future mtime → "just now"), NAME cap atNAME_COL_MAX = 40with…ellipsis,--jsonvia a dedicatedSerializestruct{repo_id, size_bytes, path, modified}(modified = Unix epoch seconds or null),-qrepo-ids-only,--sort name|size|modified, and a stdout-orientedshould_color_stdout()mirroring theNO_COLOR+is_terminal()semantics ofshould_show_progress(). Styling dims only MODIFIED cells + the header root (NAME stays plain);--json/-qare never styled. The feat: Makemlxcel listshow local models by default; move the architecture catalog tomlxcel arch#138 empty-store hint (incl. themlxcel archredirect) is preserved.src/main.rs: extendListArgswith--json,-q/--quiet,-v/--verbose,--sort(clapValueEnum, defaultname); clap-enforced mutual exclusivity (--jsonvs--quiet/--verbose;--quietvs--verbose;--sortallowed with any). Dispatch threads the options through torun_list_local.src/commands/models.rs(tests) +src/main_tests.rs: reworked golden tests (default / json / quiet / verbose / empty),humanize_relativeboundary tests, a no-ANSI styling assertion + a color-on assertion, sort tests, and CLI-surface tests (flag defaults, parsing, conflicts, unknown---sortrejection).Test plan
cargo fmtcargo clippy --bin mlxcel --tests -- -D warnings(clean)cargo test --bin mlxcel(117 passed, 0 failed)-v(PATH restored),-q,--json(numeric/nullmodified),--sort size, empty-store hint,$HOME→~header contraction, and on a pty: dim escapes present with NAME plain,NO_COLOR=1+--jsonunstyled.Closes #141