Skip to content

feat(cli): greet bare voltage and name next steps on a terminal - #112

Merged
thebrandonlucas merged 1 commit into
feat/cli-look-12-watchfrom
feat/cli-look-13-greeting
Oct 23, 2026
Merged

thebrandonlucas merged 1 commit into
feat/cli-look-12-watchfrom
feat/cli-look-13-greeting

Conversation

@thebrandonlucas

@thebrandonlucas thebrandonlucas commented Oct 23, 2026 •

Copy link
Copy Markdown
Contributor

Stack position: 13 of 13, the last in the look-and-feel stack. Base: feat/cli-look-12-watch (#111).

Problem

Bare voltage dumped 67 lines of usage to stderr and failed with exit code 2, which is a cold first contact. After a step that leaves you somewhere new (an empty list, a fresh profile), the CLI did not say what to do next.

Behavior

voltage with no command, on a terminal, perhaps with global flags such as --profile or --account:

    __              ____
   / /   _  _____  / / /____ ____ ____
  / /_  | |/ / _ \/ / __/ _ `/ _ `/ -_)
 /_  /  |___/\___/_/\__/\_,_/\_, /\__/
   //                       /___/

✓ Signed in as you@example.com (account: me)
Profile       staging
Organization  ORG_ID

Try
› voltage wallets list
› voltage payments list --watch
› voltage --help
  • The greeting reads only the configuration (the same data as voltage context), never the network, and exits 0.
  • It shows ! Not signed in and suggests voltage login first when no credential is selected, or ✓ Using the API key in VOLTAGE_API_KEY for an ambient key.
  • Profile, organization, and wallet appear only when set.
  • Off a terminal, or with --json / --output json|ndjson, nothing changes: usage on stderr, exit code 2.

Next steps on a terminal (--quiet hides them; redirected output never has them):

After Line
An empty wallets list › Create a wallet: voltage wallets create --help
An empty payments list › Receive a payment: voltage payments receive --help
An empty webhooks list › Create a webhook: voltage webhooks create --help
profiles create NAME › Use it: voltage wallets list --profile NAME (shell-quoted)

The suggestions point at --help instead of guessing placeholder flags. Creating a wallet, for example, needs a credit line and network that only the person knows.

Design notes

  • GlobalFlags::without_command detects "no command": clap's missing-subcommand errors, both for no arguments at all and for flags without a command. It then reads the same flags by parsing them in front of context, so the greeting honors --profile, --account, --config-dir, and the staging wrapper's flags. greeting::wanted adds the TTY and machine-output checks, and startup::run greets before normal parsing.
  • greeting::lines is pure over voltage context's JSON plus the account's saved email, so it's tested directly.
  • empty_list_next_step maps the three list operations to their hint, and Call::suggest writes it.
  • The test helper strip_styles now strips any CSI sequence, not only SGR. It panicked on a status line's trailing ESC[2K.

Try it on staging

Setup: Try a pull request on staging (added by #97). Read-only, except profiles create, which saves a local profile.

voltage
voltage --account me

Expect: the bolt wordmark, ✓ Signed in as …, the organization from your .env, then Try and three › commands. echo $? prints 0.

voltage 2>&1 | cat
voltage --json

Expect: the old usage error both times, exit code 2.

voltage webhooks list --account me

Expect: if you have no webhooks, No results. followed by › Create a webhook: voltage webhooks create --help.

voltage profiles create try-it --account me --org ORG_ID --env ENV_ID
voltage profiles delete try-it

Expect: › Use it: voltage wallets list --profile try-it after the create. The delete removes the profile again. Get ORG_ID and ENV_ID from voltage context --account me.

Validation

  • cargo fmt --check, cargo clippy --locked --all-targets -- -D warnings, cargo machete --with-metadata, cargo test --locked (176 unit and 136 integration tests), and python3 scripts/check-coverage.py all pass.
  • Unit tests:
    • Greeting lines for a signed-in person with and without a saved email, not signed in (voltage login first), and an ambient API key.
    • Every suggested command parses.
    • without_command reads bare and --profile flags, keeps --json, and rejects a named command, --help, and an unknown flag.
    • Each empty-list suggestion is a real command's help.
  • Integration tests:
    • Greeting, all streams on a PTY: the wordmark, the API-key line, and suggestions, with no request sent. Piped bare voltage still exits 2 with the usage error.
    • Hints on a PTY: an empty wallets list gets the hint, a non-empty one doesn't, and profiles create gets its › Use it line.
  • Checked by hand on staging, directly and with --account me.
  • CRAP: 0 of 789 measured functions at or above 20. The highest is 19.00 (src/cli.rs endpoint, unchanged from feat(cli): follow payments live with payments list --watch #111). greeting.rs is at most 7.0.

Docs

docs/getting-started.md, docs/reference/output.md, and CHANGELOG.md.

@thebrandonlucas
thebrandonlucas added this pull request to stack #92 October 23, 2026 02:54
@thebrandonlucas
thebrandonlucas marked this pull request as ready for review October 23, 2026 02:58
@thebrandonlucas
thebrandonlucas force-pushed the feat/cli-look-13-greeting branch from ebf8217 to 5bcf94b Compare October 23, 2026 03:30
@thebrandonlucas
thebrandonlucas force-pushed the feat/cli-look-13-greeting branch from 5bcf94b to d819684 Compare October 23, 2026 12:38

@tee8z tee8z left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Requesting changes on d819684 (base #111). Off a terminal, or with --json or -o json|ndjson, bare voltage still prints usage to stderr and exits 2. --help, --version and named commands are unchanged. The greeting makes no network request and shows no secret, and its text is scrubbed.

P2: the greeting says "Not signed in" and suggests voltage login for every credential problem. signed_in (src/greeting.rs:50) is false whenever credential_problem is set, and the next step is then always voltage login (:84). But CredentialProblem also covers these:

  • MultipleCredentials: two saved logins and no default, common with staging and production. The next step is --account NAME or a profile.
  • EmptyApiKey: a blank VOLTAGE_API_KEY still overrides saved logins, so the fix is to unset it, and voltage login cannot help.
  • BoundElsewhere and AuthUrlMismatch.

Key the line and the next step off the problem, as voltage context does, and add a test for at least MultipleCredentials and EmptyApiKey.

Nonblocking:

  • The › Use it: hint after profiles create checks only decorates(), not out.is_human(), so it prints with --json and with redirected stdout, although output.md says redirected output never has hints. It also prints before the result, and it drops a --config-dir that was passed.
  • An empty payments list --statuses failed suggests "Receive a payment", whatever filters were given.
  • greeting::show failing, for example voltage --profile missing on a terminal, is untested.

Gate and CRAP on this head are running, and I will add the results.

Comment thread src/greeting.rs Outdated
@tee8z

tee8z commented Oct 23, 2026

Copy link
Copy Markdown
Contributor

Gate results for d819684, which my change request said I would add. In nix develop: cargo fmt --check, cargo clippy --locked --all-targets -- -D warnings, cargo machete --with-metadata, cargo test --locked (192 unit, 145 integration) and python3 scripts/check-coverage.py passed. CRAP: 825 measured functions, none at or above 20, highest endpoint at 18.00.

@thebrandonlucas
thebrandonlucas force-pushed the feat/cli-look-13-greeting branch from d819684 to d3c90f4 Compare October 23, 2026 14:09
@thebrandonlucas

Copy link
Copy Markdown
Contributor Author

Review-body items, in d3c90f4:

  • P2, credential problems: see the thread.
  • › Use it: hint: Envelope gains a next step that Output writes after the result. It appears only with human output on interactive stdout and stderr, and --quiet hides it. The hint keeps --config-dir when one was passed. The same Output::suggest now writes the empty-list hints. The test checks the order, the --config-dir, --json, --quiet, and redirected stdout.
  • Empty list with filters: the empty-list hint appears only when the list had no filters and no --query.
  • greeting::show failing: new test a_greeting_that_cannot_read_its_scope_reports_the_error runs voltage --profile missing on a terminal: exit 2, the error, no greeting.

@thebrandonlucas
thebrandonlucas force-pushed the feat/cli-look-13-greeting branch from d3c90f4 to e1d6055 Compare October 23, 2026 14:20
voltage with no command, on a terminal, shows the wordmark, who is signed
in (from the configuration, without a network request), the saved
profile, organization, and wallet, and three commands to try, and exits 0.
Off a terminal or with --json it stays a usage error with exit code 2. On
a terminal, an empty wallets, payments, or webhooks list ends with a line
naming the command that makes the first one, and profiles create names a
command that uses the new profile.
@thebrandonlucas
thebrandonlucas force-pushed the feat/cli-look-13-greeting branch from e1d6055 to 05d5fd4 Compare October 23, 2026 14:31

@tee8z tee8z left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approved 05d5fd4 against feat/cli-look-12-watch (#111 5c45231). My findings are fixed:

  • P2, credential problems: the greeting runs auth::select, the function commands and voltage context use, which reads only the configuration and environment. On a problem it leads with CredentialProblem::error's message, and its first suggestion is that problem's own hint. Unit tests cover NoCredential, EmptyApiKey (unset the variable) and MultipleCredentials (--account NAME or --profile NAME). With no problem and no account, the credential can only be VOLTAGE_API_KEY, so that line is accurate.
  • › Use it: hint: it is now a NextStep that Output::suggest writes after the result. It needs human output on interactive stdout and stderr, with no --output-file and no --quiet, and it keeps --config-dir. The integration test covers the order, --json, --quiet and redirected stdout.
  • Empty filtered list: the empty-list hint needs no filters and no --query, and a unit test checks that each suggested command parses.
  • greeting::show failing: a_greeting_that_cannot_read_its_scope_reports_the_error covers it.

Validation on this head, in nix develop: cargo fmt --check, cargo clippy --locked --all-targets -- -D warnings, cargo machete --with-metadata, cargo test --locked (197 unit, 149 integration) and python3 scripts/check-coverage.py passed. CI is green. I did not run the CRAP report for this re-review.

@thebrandonlucas
thebrandonlucas merged commit 6610d0f into master Oct 23, 2026
8 of 14 checks passed
@thebrandonlucas
thebrandonlucas deleted the feat/cli-look-13-greeting branch October 23, 2026 15:47
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