feat(cli): add --client/-c filter flag, deprecate per-client booleans#464
Merged
feat(cli): add --client/-c filter flag, deprecate per-client booleans#464
Conversation
Contributor
|
The latest updates on your projects. Learn more about Vercel for GitHub. |
This comment was marked as resolved.
This comment was marked as resolved.
This comment was marked as resolved.
This comment was marked as resolved.
Replace 19 individual boolean flags (--opencode, --claude, --codex, ...) with a single repeatable, comma-separated --client/-c flag backed by a clap ValueEnum. Cleans up --help (19 lines collapse to 1) and stops flag-list growth as new clients are added. Legacy boolean flags remain functional but are hidden from --help and will be removed in the next major release; using them prints a one-line deprecation warning to stderr only when stderr is a TTY (so JSON pipelines and tests stay clean). The Synthetic meta-client is exposed as a first-class --client value without changing tokscale-core: ClientFilter mirrors ClientId on the CLI side, leaving the core crate's clap-free layering and synthetic post-processing semantics intact. Updates filtering docs in README.md, README.ko.md, README.ja.md, and README.zh-cn.md.
…ients setting Replace the split TUI state (HashSet<ClientId> + bool include_synthetic) with a single HashSet<ClientFilter>, so the source picker, in-memory app state, disk cache key, and CLI flag parser all share one representation. The Synthetic meta-client now lives as a regular set member instead of a parallel boolean. Cache schema unchanged on disk: load_cache and save_cached_data still serialize (enabled_clients: Vec<String>, include_synthetic: bool) so caches written by older releases keep loading without a migration step. The projection happens at the read/write boundary. Add ClientFilter::to_client_id / from_client_id / from_filter_str helpers for boundary code that still needs Vec<ClientId> (the loader API) or parses canonical id strings (settings.json). Reorder ClientFilter variants to mirror ClientId::ALL declaration order so --help possible values, the TUI picker rows, and value_variants() iteration all agree on a single chronological ordering, with Synthetic appended at the end. Add a defaultClients setting (top-level in settings.json) that applies when the user passes neither --client/-c nor a legacy boolean flag. CLI flags always override defaults completely — no merging — so 'tokscale --client codex' is not surprised by extra entries from settings. Unknown ids in defaultClients are silently dropped to keep stale config from breaking tokscale. Refactor source_picker.rs to drop its local SourceOption enum and walk ClientFilter::value_variants() directly. Hotkey for Synthetic stays 'x' to preserve current display; it collides with Mux which also binds 'x' in client_ui — pre-existing bug noted with a TODO, scoped out of this change. Update README.md / README.ko.md / README.ja.md / README.zh-cn.md Configuration sections to document defaultClients.
…ADME examples
Two related issues caught in code review:
1. The no-filter TUI default and the `submit` warm-cache filter set
drifted apart. TUI launch with no `--client` flag enabled every
ClientFilter including Synthetic; `run_warm_tui_cache()` warmed the
cache with Synthetic excluded. Result: every TUI launch after
`submit` was a stale-cache hit instead of fresh, defeating the
warming. Pre-refactor behavior was 'every ClientId, include_synthetic
= false'; this restores it.
Fix: introduce `ClientFilter::default_set()` as the single source of
truth for 'no filter' semantics. Both code paths (App constructor in
tui/app.rs, the cache lookup in tui/mod.rs, and run_warm_tui_cache in
main.rs) now consult it. Drop the now-unused `clap::ValueEnum`
imports in tui/app.rs and tui/mod.rs.
Synthetic detection has always been opt-in because it post-processes
other clients' sessions to re-attribute messages — flipping it on
silently for everyone is a real behavior change.
Add unit tests:
- `test_client_filter_default_set_excludes_synthetic` — guards the
contract directly.
- `test_app_no_filter_default_matches_default_set` — guards that
App's constructor stays in lockstep.
2. Replace stale legacy-flag examples that still appeared in README
sections unrelated to the 'Filtering by Platform' deprecation block:
`tokscale models --week --claude`, `tokscale submit --opencode
--claude ...`, `tokscale graph --opencode --claude`. After the major
bump that removes the deprecated flags these examples become outright
wrong; even before that they contradict the canonical interface.
Updated in all four READMEs (en/ko/ja/zh-cn).
Test plan: 369 unit + 83 integration = 452 pass, 0 fail.
… tests hermetic Two issues caught in PR code review: 1. **`run_warm_tui_cache` ignored `defaultClients`** (devin-ai-integration P2) `run_warm_tui_cache` always used `ClientFilter::default_set()` (every real client, Synthetic excluded), regardless of the user's `defaultClients` setting. `tui::run` on a no-flag launch DOES honor `defaultClients` via `build_client_filter` upstream. Result: a user with `defaultClients = ["opencode", "claude"]` got a warm cache storing all 18 clients while the next `tokscale` launch wanted only the 2 configured ones — guaranteed cache miss, defeating the warming. Fix: extract `resolve_default_tui_filter_set` mirroring the no-flag-launch resolution chain (defaultClients → fall back to default_set). Both `run_warm_tui_cache` and the documentation comment now consult it. 2. **`build_client_filter` tests were non-hermetic** (cubic-dev-ai P2) The 5 unit tests for `build_client_filter` were calling the wrapper that loads `~/.config/tokscale/settings.json`. They passed for me (no `defaultClients` set) but a developer with their own `defaultClients` would get unrelated filter ids appended to every assertion. Pure variant `build_client_filter_with_defaults` already existed; these tests now use it with `&[]` for hermetic input. Adds: - `resolve_default_tui_filter_set_with(&[String])` — pure variant of the resolver, unit-testable. - 4 new tests: configured defaults override, empty fallback, unknown id drop, all-unknown fallback to `default_set`, synthetic supported via `defaultClients` for opt-in power users. Test plan: 374 unit + 83 integration = 457 pass, 0 fail.
…system
- pass an empty defaults slice when resolving the submit subcommand's client
filter so 'tokscale submit' falls back to default_submit_clients() instead
of silently uploading the user's defaultClients subset
- add Goose to ClientFilter, hidden legacy flag, default_set membership, and
filter round-trip helpers so the new --client/-c flag covers every
supported client
- accept malformed elements in defaultClients without invalidating the rest
of settings.json; non-string entries are dropped silently
Synthetic hotkey collision with Mux ('x') is left as-is (pre-existing behavior
documented in source_picker.rs); fixing it requires a wider audit of TUI
hotkey display and is scoped out of this commit.
1ccb7dc to
757f2a3
Compare
This was referenced Apr 26, 2026
junhoyeo
added a commit
that referenced
this pull request
Apr 26, 2026
…nd client filter UX (#467) Pre-release correctness and security fixes across the four PRs landed this cycle (#464, #454, #359, #355). ## Antigravity Trust boundary around the local language-server RPC was too loose: - Bound RPC body sizes at 16 MiB (Content-Length, chunked, and read-to-end paths) - Verify process identity by checking the executable path (`lsof` on macOS, `/proc/<pid>/exe` on Linux); accept paths containing either `antigravity` or `language_server` since some Antigravity-flavored servers launch from generic `language_server` binaries with `--app_data_dir antigravity` - Probe candidate endpoints with a real RPC call and JSON-shape check (probe body capped at 4 KiB) instead of trusting any 200 response; consume HTTP headers before reading body so the cap applies to the JSON body alone - Lock concurrent syncs on a per-cache PID lock file with bounded retry (3 attempts) instead of unbounded recursion. Eviction is gated on PID liveness only — long-running syncs no longer get stomped on by age-based timeouts. - Enforce manifest version on load: future versions abort, older versions start fresh - Recover corrupted manifests by moving them aside as `manifest.json.corrupt-<ts>` instead of failing every subsequent sync ## Codebuff Parser correctness around silent data loss: - Accumulate run-state usage across the full reverse `messageHistory` walk instead of returning on the first signal-bearing entry. Previously a newest assistant entry carrying only a model id would short-circuit the walk and silently drop real token counts on earlier entries. - Include the source-array ordinal in the fallback dedup key so two id-less assistant messages with identical session/timestamp/model/tokens no longer collapse into a single record - Reject non-positive numeric timestamps in the shared `parse_timestamp_value`/`parse_timestamp_str` helpers so messages with `timestamp: 0` or negative epochs fall through to chat-id / file-mtime fallback chain ## TUI + parsing User-visible client-filter UX: - Move `SYNTHETIC_HOTKEY` from `'x'` to `'n'`. `'x'` collided with Mux's hotkey, and the dispatch order made the displayed `[x]` for Synthetic purely cosmetic - Enable `ignore_case` on `--client/-c` so `OPENCODE`, `Codebuff`, and `antigravity` all parse as the same canonical filter ## Test results - `cargo test -p tokscale-cli` — 415 unit + 83 integration, all pass - `cargo test -p tokscale-core` — 566 unit + 10 codebuff + 3 hermes, all pass - `cargo check --workspace` — clean Each fix has a regression test where applicable.
9 tasks
junhoyeo
pushed a commit
that referenced
this pull request
Apr 26, 2026
…ale on macOS (#468) Resolves a documented-vs-actual path mismatch on macOS. `Settings::config_path()` and `star_cache_path()` were calling `dirs::config_dir()`, which returns `~/Library/Application Support/` on macOS, while `auth.rs`, `cursor.rs`, `antigravity.rs`, and all four READMEs document `~/.config/tokscale/`. macOS users editing the documented `settings.json` were touching a file tokscale never read — and the new `defaultClients` setting from #464 was silently no-op for them. - New `crates/tokscale-cli/src/paths.rs::get_config_dir()` is now the single source of truth for the config dir. Resolution: `TOKSCALE_CONFIG_DIR` env override → macOS/Linux `$HOME/.config/tokscale` → Windows `dirs::config_dir().join("tokscale")` → `./.tokscale` last-ditch fallback. - `Settings::load()` falls back to the legacy `~/Library/Application Support/tokscale/settings.json` on macOS only, so existing users keep their theme, scanner, and defaultClients across the upgrade. `save()` always writes the new canonical path. - `TOKSCALE_CONFIG_DIR` env var added (mirrors `TOKSCALE_HEADLESS_DIR` semantics), documented in all four READMEs. - Corrects the Windows-Specific Configuration sections: settings.json lives at `%APPDATA%\tokscale\` (the actual `dirs::config_dir()` default on Windows), not `%USERPROFILE%\.config\tokscale\`. Adds the missing `credentials.json` row. - Out of scope: `auth.rs`, `cursor.rs`, `antigravity.rs` already hardcode `~/.config/tokscale/` correctly. Cache and headless dirs were left alone.
junhoyeo
added a commit
that referenced
this pull request
Apr 27, 2026
… cache eagerly, add `--write-cache` opt-in for light mode (#473) * refactor(paths): hoist path helpers to tokscale-core and add get_cache_dir Tokscale-core's caches (source-message bincode, pricing JSON, the OpenCode migration record) all need the same config dir resolution that `Settings::load()` and `load_star_cache()` use, but the helpers lived only in tokscale-cli. Move them to a new `tokscale_core::paths` module and re-export them from the CLI side so existing call sites stay untouched. Adds two new helpers: - `get_cache_dir()` returns `<config_dir>/cache`. The next commit consolidates every cache (TUI display data, source-message bincode, pricing JSON, opencode-migration.json, Wrapped fonts/images) under this single subdirectory so an isolated profile (`TOKSCALE_CONFIG_DIR=...`) covers everything in one shot. - `legacy_dirs_cache_dir()` and `legacy_dot_cache_tokscale_dir()` resolve the historic `dirs::cache_dir()/tokscale` and `~/.cache/tokscale` locations respectively. Both probes return None when `TOKSCALE_CONFIG_DIR` is set so legacy data does not leak into isolated profiles. Both are needed on macOS because the historic split put the TUI display cache under `~/.cache/tokscale` and the source message / pricing caches under `~/Library/Caches/tokscale`. No behavior change yet: all current callers still resolve the same paths they did before. The next commit switches the cache modules over and adds the legacy fallback chain. * feat(cache): consolidate all caches under ~/.config/tokscale/cache with legacy fallback Tokscale state was scattered across up to four directories per platform. On macOS specifically the source-message bincode and pricing JSON lived under ~/Library/Caches/tokscale while the TUI display cache lived under ~/.cache/tokscale, and Wrapped image/font caches were in a third place. The new defaultClients setting from #464 made this split visible: users who set TOKSCALE_CONFIG_DIR for an isolated profile got a hermetic config root but the caches still leaked from the host paths. Consolidates every cache under <config_dir>/cache so: - TOKSCALE_CONFIG_DIR controls everything in one shot (hermeticity) - rm -rf ~/.config/tokscale/cache wipes only regenerable state - The macOS-vs-Linux directory split is gone Per-file moves: - tui-data-cache.json: ~/.cache/tokscale/ -> <config_dir>/cache/ - source-message-cache.bin + .lock: dirs::cache_dir()/tokscale/ -> <config_dir>/cache/ - pricing-litellm.json + pricing-openrouter.json: same as above - opencode-migration.json: same as above - fonts/, images/ (Wrapped): ~/.cache/tokscale/<dir>/ -> <config_dir>/cache/<dir>/ Read-side migration is a one-time fallback per file: try the canonical path first, then probe the legacy locations (gated off when TOKSCALE_CONFIG_DIR is set). Writes always land at the new path. Legacy files are left in place so a downgrade keeps working — no copy, no delete. Schemas stay at TUI=6, core=7. Path moves alone don't warrant a bump. Closes #470 * feat(tui): render disk cache regardless of age and always background-refresh The 5-minute cache staleness threshold previously gated the render decision on TUI launch: a Stale cache rendered with a background refresh, but a hard Miss (cache older than its hard-expiry window, schema drift, or filter-set mismatch) blocked the user behind a full re-aggregation pass. Schema drift and filter mismatch still produce a Miss because rendering wrong-shape or wrong-filter data is worse than showing nothing — but the time-based staleness check no longer affects the render decision. The Fresh-vs-Stale distinction stays in CacheResult for future status-bar use (data age metadata), but the call site treats both as renderable. needs_background_load becomes unconditionally true on TUI launch so cached data always gets refreshed. Closes #471 * feat(cli,settings): add --write-cache flag and light.writeCache setting Adds an opt-in mechanism to warm the TUI cache from a --light run. Previously tokscale --light was strictly read-only — no way to opportunistically refresh the TUI cache from a CLI report even though the underlying UsageData was already computed. Daily-cron users would see a cold TUI on the next interactive launch. CLI flags: - --write-cache (requires --light): overwrite the TUI cache atomically after the report renders - --no-write-cache (requires --light, conflicts with --write-cache): skip the cache write even if the setting opts in Settings file: - settings.json grows a light section with writeCache: false default - #[serde(default)] keeps existing settings.json files loading unchanged Resolution: --no-write-cache > --write-cache > settings.light.writeCache > false. Cache write uses the existing atomic temp-file rename, so a process crash mid-write never loses the cache. Closes #472 * docs: document unified cache layout, --write-cache flag, and light.writeCache setting - Cache layout: all regenerable caches now live under <config_dir>/cache. Documented for both posix and Windows in all four language READMEs. - TOKSCALE_CONFIG_DIR row: clarifies the override now covers caches too. - light.writeCache row: new opt-in setting documented in the configuration table. - Cache directory layout subsection: explains what lives where and notes the directory is safe to delete (caches regenerate). * style: auto-fix lint issues [skip ci] * fix(cli,paths): refuse light cache write when filters scope it and reject empty TOKSCALE_CONFIG_DIR The TUI cache key is `(enabled_clients, group_by)` only — it does NOT include `--since`, `--until`, `--year`, or `--home`. `write_light_cache` previously forwarded all of those into the DataLoader, so a `tokscale --since 2025-01-01 --light --write-cache` invocation built a date-filtered or home-scoped slice and saved it under the unfiltered key. Subsequent `tokscale tui` launches would hit that cache and render the filtered slice as if it were the default report — silent data correctness loss flagged by both Devin and Codex review. `write_light_cache` now refuses the write when any of those filters is present and prints an eprintln explaining why. The CLI report still prints normally; only the cache-warm side-effect is skipped. Separately, `get_config_dir` previously treated an empty `TOKSCALE_CONFIG_DIR=""` as a valid override and returned `PathBuf::from("")`, while `is_config_dir_overridden` treated empty as unset. After cache consolidation that mismatch could send writes to relative paths like `./cache/...` even though the legacy fallback logic still behaved as if no override was active. Resolver now agrees with the predicate: empty string falls through to the platform default. * fix(antigravity): route cache discovery through config dir * fix(cli): forward light write-cache flags from models * fix(cache): create message-cache lock dir with ensure_cache_dir * test(cli): isolate wrapped and override-cache path fixtures * test(core): make env-mutating cache path tests panic-safe * fix(cli): treat write_light_cache as best-effort so scan failures don't tank exit codes The cache-warm step ran AFTER the report had already been flushed to stdout, but propagated DataLoader errors via `?`. A scan failure (e.g. a corrupt session file in one client's directory) would surface as a non-zero exit code on a CLI invocation the user already saw print correctly, breaking scripts and CI pipelines that key off exit status. Changes write_light_cache return type from `Result<()>` to `()` and swallows loader errors via `if let Ok(data) = loader.load(...)`, matching the pattern in run_warm_tui_cache. The call site no longer needs `?`. The eprintln-on-skip behavior (when --since/--until/--year/ --home are set) is unchanged. Also fixes a stray clippy carryover in paths::tests where an empty TOKSCALE_CONFIG_DIR fallback assertion compared a PathBuf against a \&str literal. * fix(clients): make PathRoot::Config match get_config_dir on Windows Antigravity's scanner uses PathRoot::Config to resolve where to look for synced sessions. Without this fix the resolver hardcoded `{home_dir}/.config/tokscale` as the non-Linux fallback, while get_antigravity_cache_dir() (the writer side) routes through paths::get_config_dir() which calls dirs::config_dir() on Windows. The two diverged on Windows: tokscale antigravity sync wrote to %APPDATA%\\tokscale\\antigravity-cache\\ while the scanner read from %USERPROFILE%\\.config\\tokscale\\antigravity-cache\\sessions\\, so synced data silently never appeared in reports. PathRoot::Config now mirrors get_config_dir's platform branches: TOKSCALE_CONFIG_DIR override > Linux XDG_CONFIG_HOME > Windows dirs::config_dir() > generic `{home}/.config/tokscale`. macOS still falls through to `{home}/.config/tokscale` because get_config_dir() deliberately overrides dirs::config_dir() there (which would otherwise return ~/Library/Application Support/). New regression test test_path_root_config_uses_dirs_config_dir_on_windows locks the writer/scanner agreement on Windows. * test(cli): pin XDG_CONFIG_HOME in offline test command and seed pricing cache at canonical path CI runners can have XDG_CONFIG_HOME set globally outside the sandboxed HOME, so the post-#470 cache resolver leaks the binary's read+write to the host's config dir. Three integration tests (test_monthly_json_offline_*, test_submit_offline_*) flaked on Linux runners because the cost expectations assumed the sandboxed pricing cache (or absence thereof), but the binary was finding pricing data via the host's $XDG_CONFIG_HOME/tokscale/cache/ — or worse, would write to it. offline_cmd_with_home now sets XDG_CONFIG_HOME alongside the existing XDG_CACHE_HOME and XDG_DATA_HOME pins, mirroring cmd_with_home and keeping the binary's cache resolution fully inside the temp dir. write_pricing_cache also seeds the canonical .config/tokscale/cache/ directory so tests that exercise the new path work even when legacy fallback is suppressed (e.g. via TOKSCALE_CONFIG_DIR override). * test(core): pin XDG_CONFIG_HOME in cache fallback tests so CI stays hermetic CI runners that set XDG_CONFIG_HOME globally were leaking the binary's canonical cache root outside the test's sandboxed HOME, so SourceMessageCache::load read from the host's $XDG_CONFIG_HOME/tokscale/cache/source-message-cache.bin (or wrote there) instead of the temp dir. Three message_cache tests (test_source_message_cache_round_trip, load_falls_back_to_legacy_dirs_cache_path, load_falls_back_to_legacy_dot_cache_path) and one pricing/cache test (load_falls_back_to_legacy_dirs_cache_path) flaked on Linux runners as a result. Adds sandbox_cache_env / restore_cache_env helpers to message_cache so the round-trip test pins HOME + XDG_CONFIG_HOME + XDG_CACHE_HOME together. The two legacy-fallback tests gain explicit XDG_CONFIG_HOME pinning to match. The pricing test does the same. All 4 now pass on Linux CI. --------- Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
--opencode,--claude,--codex, ...) with a single repeatable, comma-separated--client/-cflag backed by a clapValueEnum.defaultClientssetting in~/.config/tokscale/settings.jsonfor personalized filter defaults.HashSet<ClientFilter>(Synthetic is now a regular set member instead of a parallelbool include_synthetic).--helpand slated for removal in the next major (see feat(cli)!: remove deprecated per-client boolean flags #465). Using one prints a TTY-gated stderr deprecation warning.Why
Adding a new agent integration meant editing four places (struct field,
build_client_filterarray, help string, tests) and added one more line to a--helpblock that was already 19 lines long. Discoverability was suffering — users couldn't easily scan all supported clients, and the help output kept growing with every new integration.Before / After
Before —
tokscale --help:After:
Usage
Configuration
New top-level
defaultClientsfield in~/.config/tokscale/settings.json:{ "colorPalette": "blue", "defaultClients": ["opencode", "claude"] }--client/-cand no legacy flag.Design Notes
ClientFilterlives intokscale-cli, nottokscale-coreThis keeps the core crate free of CLI-parsing dependencies. The
Syntheticmeta-client (which has no scan path of its own and is detected by post-processing other agents' sessions) is a first-class--clientvalue without changing core invariants.ClientFilter::as_filter_strmirrorsClientId::as_strexactly, with a unit test enforcing round-trip parity.Resolution order in
build_client_filter--client/-cvalues (preserves user-typed order)defaultClientsfrom settings.json — only when steps 1 and 2 produced nothingCLI flags always win. Mixing canonical and legacy forms doesn't double-list.
Cache schema preserved
load_cacheandsave_cached_datastill serialize(enabled_clients: Vec<String>, include_synthetic: bool)on disk. Projection between the unifiedHashSet<ClientFilter>and this legacy shape happens at the read/write boundary. Caches written by older releases keep loading.TTY-gated deprecation warning
The legacy-flag warning fires only when stderr is a TTY, so
tokscale --json | jqandassert_cmd-driven tests don't pick up unexpected stderr noise.Migration
tokscale --opencodetokscale --client opencodetokscale --opencode --claudetokscale --client opencode,claudetokscale --synthetictokscale --client syntheticTest Plan
cargo build -p tokscale-cli— cleancargo test -p tokscale-cli --bin tokscale— 374 pass, 0 fail (includes new tests forClientFilterround-trip, declaration-order invariant,default_setsemantics,defaultClientsresolution, hermeticbuild_client_filtertests, and TUI default-state regression)cargo test -p tokscale-cli --test cli_tests— 83 pass, 0 fail (existing integration tests using legacy flags continue to work)tokscale --client opencode,claude --json✓tokscale -c opencode -c claude --json✓tokscale --client invalidclient→ clap error with full possible-values list ✓tokscale --opencode --json→ still works ✓defaultClientsin settings.json honored when no flag passed ✓tokscale submitproduces fresh cache hit on next launch ✓Review history
All review-bot threads addressed and resolved:
ClientFilter::default_set()excluding Synthetic, plus a regression test asserting the no-filterAppdefault stays in lockstep.run_warm_tui_cacheignoreddefaultClients(Devin BUG follow-up) — fixed by extractingresolve_default_tui_filter_set()mirroring the no-flag-launch resolution chain. Warm cache now matches what the next no-flag TUI launch wants.build_client_filtertests (cubic-dev-ai P2) — fixed by switching all 5 unit tests tobuild_client_filter_with_defaults(flags, &[]).