Skip to content

feat(cli): errors name a next step and per-command help uses one heading style - #239

Merged
duyetbot merged 1 commit into
mainfrom
feat/ux-fx-style
Oct 28, 2026
Merged

duyetbot merged 1 commit into
mainfrom
feat/ux-fx-style

Conversation

@duyetbot

Copy link
Copy Markdown
Member

Owner request 2026-10-29: make anyr's terminal UX follow Vercel's fx (https://github.com/vercel-labs/fx). Companion to the README PR #238.

What fx does that anyr did not

  • Per-command help uses one layout: Usage:, Options:, Examples:. anyr mixed that with gh-style ALL CAPS (USAGE, FLAGS, AVAILABLE COMMANDS) in auth, login, logout, status, switch, token, decision, onboard.
  • Errors are one short line that names the failing subject, then the next command. anyr's no-key error was a 170 character sentence with four alternatives, and unknown-subcommand errors had no error: prefix and the fix glued on after a period.

Changes

  • src/help.rs, src/onboard.rs: per-command headings become Usage:, Commands:, Options:, Examples:, Aliases:, Settings:, Modes:. The root --help and the help --all map keep their layout (the cli-regression skill parses it).
  • src/exit.rs: a one-line message ending in . Run: x, . Try: x or . Use: x prints as the message plus a hint: line, the shape anyr bogus already used. One-line usage errors now get the error: prefix. Usage lines (Usage: ...) and multi-line help text print unchanged. --json mode gets the same split into message and hint.
  • src/key.rs: the no-key error is not signed in, no AnyRouter key found plus a hint with anyr login (and --device over SSH) or ANYROUTER_API_KEY. Exit code stays 4.
  • tests/cli.rs: the missing-key assertion follows the new wording.

Not changed: the interactive screens (config --pick, models --pick, launcher). They already render plain text from the top left with a thin line, which is the owner rule and matches fx's inline style. No command or flag was removed or renamed.

Before and after

NO_COLOR=1, empty HOME, no key, </dev/null. Output cut at 16 lines.

@@ -20,30 +20,33 @@
 $ anyr auth --help
 Authenticate with AnyRouter.
 
-USAGE
+Usage:
   anyr auth <command> [flags]
 
-AVAILABLE COMMANDS
+Commands:
   login:       Log in to an AnyRouter account
   logout:      Log out of an AnyRouter account
   status:      View authentication status
   switch:      Switch the active account
   token:       Print the API key
 
-FLAGS
+Options:
   -h, --help   Show help for command
 (exit 0)
 
 $ anyr chat hi
-error: No AnyRouter config and no key. Pass --key sk-ar-... or set ANYROUTER_API_KEY, or use --device/--device-code for headless environments, or run in an interactive terminal to log in.
+error: not signed in, no AnyRouter key found
+hint: run `anyr login` (add --device over SSH), or set ANYROUTER_API_KEY
 (exit 4)
 
 $ anyr models use nope
-Unknown model "nope". Run: anyr models
+Unknown model "nope"
+hint: run `anyr models`
 (exit 2)
 
 $ anyr keys nope
-Unknown keys subcommand "nope". Try: list | create | show | update | audit | revoke
+Unknown keys subcommand "nope"
+hint: one of list | create | show | update | audit | revoke
 (exit 2)
 
 $ anyr artifact publish /nonexistent

Interactive flow anyr config --dump-tui is byte-identical before and after (plain, top-left):

Config
account  (not signed in)
credits  -
file     <config path>
[general]  claude  codex  grok  opencode  pi  pool  qwen  goose  aider  crush  …
────────────────────────────────────────────────────────────
  ACCOUNT
❯ account         (not signed in)
  ...
tab  agent    ↑↓  move    ↵  edit    x  reset    q  close

Verified

cargo test --locked --all-targets all green (594 unit tests plus every integration test), cargo clippy --locked --all-targets clean, cargo fmt --check clean. New unit tests in src/exit.rs cover the hint split and the prefix rule.

🤖 Generated with Claude Code

…ing style

Co-authored-by: Duyet Le <me@duyet.net>
Co-authored-by: duyetbot <bot@duyet.net>
@github-actions

Copy link
Copy Markdown
Contributor

anyr size and startup

Startup is wall time for a cold anyr --version / anyr --help (median of 21 runs). Size is the stripped release binary, or the .wasm for the browser demo.

Size budget: anyr-linux-x86_64 must stay ≤ 4.0 MiB (4194304 bytes) stripped.

Asset Kind Size --version median --help median
anyr-darwin-arm64 native 2.4 MiB 3.8 ms 4.01 ms
anyr-darwin-x86_64 native 2.9 MiB 34.45 ms 35.02 ms
anyr-linux-arm64 native 2.9 MiB 0.97 ms 1.07 ms
anyr-linux-x86_64 native 3.4 MiB 0.79 ms 0.9 ms
anyr-windows-x86_64.exe native 2.9 MiB 10.46 ms 10.62 ms
anyr.wasm wasm 252.0 KiB — —

Budget check: ok

raw timings
[
  {
    "asset": "anyr-darwin-arm64",
    "kind": "native",
    "path": "/Users/runner/work/cli/cli/anyr-darwin-arm64",
    "bytes": 2553800,
    "size": "2.4 MiB",
    "version": "0.1.36 (built 2026-10-28 17:38:57)",
    "target": "aarch64-apple-darwin",
    "os": "macos-latest",
    "startup_version": {
      "n": 21,
      "min_ms": 3.44,
      "median_ms": 3.8,
      "p95_ms": 4.7,
      "mean_ms": 3.92
    },
    "startup_help": {
      "n": 21,
      "min_ms": 3.11,
      "median_ms": 4.01,
      "p95_ms": 6.38,
      "mean_ms": 4.54
    }
  },
  {
    "asset": "anyr-darwin-x86_64",
    "kind": "native",
    "path": "/Users/runner/work/cli/cli/anyr-darwin-x86_64",
    "bytes": 3037656,
    "size": "2.9 MiB",
    "version": "0.1.36 (built 2026-10-28 17:39:12)",
    "target": "x86_64-apple-darwin",
    "os": "macos-latest",
    "startup_version": {
      "n": 21,
      "min_ms": 32.04,
      "median_ms": 34.45,
      "p95_ms": 38.15,
      "mean_ms": 35.14
    },
    "startup_help": {
      "n": 21,
      "min_ms": 31.78,
      "median_ms": 35.02,
      "p95_ms": 37.82,
      "mean_ms": 35.78
    }
  },
  {
    "asset": "anyr-linux-arm64",
    "kind": "native",
    "path": "/home/runner/work/cli/cli/anyr-linux-arm64",
    "bytes": 3020048,
    "size": "2.9 MiB",
    "version": "0.1.36 (built 2026-10-28 17:38:54)",
    "target": "aarch64-unknown-linux-gnu",
    "os": "ubuntu-24.04-arm",
    "startup_version": {
      "n": 21,
      "min_ms": 0.87,
      "median_ms": 0.97,
      "p95_ms": 1.06,
      "mean_ms": 0.98
    },
    "startup_help": {
      "n": 21,
      "min_ms": 0.98,
      "median_ms": 1.07,
      "p95_ms": 1.19,
      "mean_ms": 1.09
    }
  },
  {
    "asset": "anyr-linux-x86_64",
    "kind": "native",
    "path": "/home/runner/work/cli/cli/anyr-linux-x86_64",
    "bytes": 3521992,
    "size": "3.4 MiB",
    "version": "0.1.36 (built 2026-10-28 17:38:55)",
    "target": "x86_64-unknown-linux-gnu",
    "os": "ubuntu-latest",
    "startup_version": {
      "n": 21,
      "min_ms": 0.75,
      "median_ms": 0.79,
      "p95_ms": 1.03,
      "mean_ms": 0.86
    },
    "startup_help": {
      "n": 21,
      "min_ms": 0.85,
      "median_ms": 0.9,
      "p95_ms": 1.06,
      "mean_ms": 0.92
    }
  },
  {
    "asset": "anyr-windows-x86_64.exe",
    "kind": "native",
    "path": "D:\\a\\cli\\cli\\anyr-windows-x86_64.exe",
    "bytes": 2995712,
    "size": "2.9 MiB",
    "version": "0.1.36 (built 2026-10-28T17:39:56Z)",
    "target": "x86_64-pc-windows-msvc",
    "os": "windows-latest",
    "startup_version": {
      "n": 21,
      "min_ms": 10.08,
      "median_ms": 10.46,
      "p95_ms": 10.94,
      "mean_ms": 10.49
    },
    "startup_help": {
      "n": 21,
      "min_ms": 9.83,
      "median_ms": 10.62,
      "p95_ms": 12.08,
      "mean_ms": 10.82
    }
  },
  {
    "asset": "anyr.wasm",
    "kind": "wasm",
    "path": "/home/runner/work/cli/cli/target/wasm-pkg/anyr_cli_bg.wasm",
    "bytes": 258081,
    "size": "252.0 KiB",
    "version": "wasm",
    "target": "wasm32-unknown-unknown",
    "os": ""
  }
]

@duyetbot
duyetbot merged commit 3481824 into main Oct 28, 2026
10 checks passed
@duyetbot
duyetbot deleted the feat/ux-fx-style branch October 28, 2026 17:41
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants