Skip to content

feat(cli): pick the wallet, organization, or environment to show on a terminal - #126

Merged
thebrandonlucas merged 1 commit into
brandon/dev-2904-voltage-cli-replace-hand-rolled-terminal-code-with-crosstermfrom
brandon/dev-2903-voltage-cli-interactive-mode-for-navigating-ids-and-teams
Nov 5, 2026
Merged

thebrandonlucas merged 1 commit into
brandon/dev-2904-voltage-cli-replace-hand-rolled-terminal-code-with-crosstermfrom
brandon/dev-2903-voltage-cli-interactive-mode-for-navigating-ids-and-teams

Conversation

@thebrandonlucas

@thebrandonlucas thebrandonlucas commented Oct 27, 2026 •

Copy link
Copy Markdown
Contributor

Fixes DEV-2903

Problem

#22 made a missing --org, --env, or --wallet open a picker, but only when a command could not run without it. You still could not browse: voltage wallets get without an ID failed with wallets get needs a wallet ID … voltage wallets list shows the IDs, and there was no way to look at one organization or environment at all. You still had to copy UUIDs out of a list by hand.

Behavior

On a terminal, a read that names one resource but has no ID now picks it with the existing picker, then shows it:

Command, on a terminal Before After
wallets get, wallets ledger list, wallets policies get, wallets payments summary without WALLET_ID/--wallet exit 2, needs a wallet ID asks for the org if missing, then Wallet:, then shows that wallet
organizations get (new) did not exist shows the org that --org or the profile selects, or asks Organization:
environments get (new) did not exist shows the env that --env or the profile selects, or asks Organization:/Environment:
wallets update/delete without an ID exit 2 unchanged: exit 2 before any prompt or request

After a pick, the CLI prints the usual Next time, pass --wallet ID … notice. Esc ends with the same error as before.

Nothing changes for scripts (DEV-2871). Off a terminal, with --json, --output json|ndjson, or --no-input, nothing is asked. A wallet read without an ID still fails with exit 2 and the same message and hint, before any credential is resolved or request is sent. organizations get and environments get without a selection report the usual --org is required and Exactly one --env is required errors.

Why this design

  • The gap was in get. Every command that needs scope already picks it, and the one command that failed for a missing ID with a "go copy it from the list" hint was a wallet read. So I extended that rather than making list interactive or adding a copy-to-clipboard step. A list stays a list, which is safe to pipe.
  • Reads only. A change (update, delete) still has to name its wallet. That keeps the existing rule that a delete is never aimed by ambient state, and keeps a picker out of the path of a change.
  • organizations get and environments get take no positional ID. They show the organization or environment the scope selects, so --org, --env, and profiles work as they do in every other command. The pickers then come for free from pick::needs. The auth service has no single-resource route, so get finds the item in the same listing that list uses, and reports Organization ID is not visible to this login when it is not there.
  • A wallet read never takes its wallet from VOLTAGE_WALLET_ID. Same rule as feat(cli): pick a missing organization, environment, or wallet #22's --wallet change. The picked wallet goes into the command's own ID, not the scope (pick::Picked.wallet).

Code

  • cli.rs: a wallet operation without an ID now parses to resource_id: None instead of failing in clap validation. The error moves to cli::missing_wallet, which pick raises. ListCommand becomes DiscoveryCommand { List, Get }, with help examples for the new commands and for wallets get.
  • pick.rs: wallet_target decides between picking and stopping (stopping for a change, off a terminal, or for JSON). ask now returns Picked { flags, wallet }, so fill can put the wallet in the scope or in the command's own ID.
  • startup.rs: discover serves list and get. auth::Resolver::discover names the subcommand in its API-key error (organizations get needs a browser login …).
  • Docs: guides/login-and-profiles.md (pickers browse), guides/wallets.md, generated commands.md, and a CHANGELOG line.

Checks

  • Branch updated with a merge of master (CHANGELOG conflict: kept both lines). On the merged head: cargo fmt --check, cargo clippy --locked --all-targets -- -D warnings, cargo machete --with-metadata, cargo test --locked (200 unit, 152 integration), python3 scripts/check-coverage.py: all pass.
  • New tests:
    • On a PTY: pick a wallet and show it; Esc; --json and --no-input (no prompt, no request); wallets delete (no prompt); pick an org for organizations get; pick an env for environments get; --json with no env (no prompt, no request).
    • Off a terminal: wallets ledger list with no ID; organizations get with a visible org, an org that is not visible, and no org; the API-key error for organizations get.
    • Unit: wallet_target for each read, change, and terminal/script case, and the new needs.
  • CRAP (scripts/crap-report.sh, on the merged head): 0 of 851 measured functions at or above 20. Highest overall 18.10 (api.rs wait_for_payment, unchanged). Highest changed: pick::ask 14.00 and pick::fill 13.00, both 100% covered; auth::Resolver::discover 8, startup::discover 7, pick::wallet_target 5. Line coverage 97.97%.
  • Diff reviewed against origin/master with voltage-pr-review and the Rust style checklist.
  • Not run: staging. The steps below are for the reviewer.

Try it on staging

alias voltage="$PWD/scripts/voltage-staging"
voltage --build
voltage login --account me --credential-store file
voltage organizations get --account me

Expect: an Organization: picker. Type to filter, press Enter. The organization's fields follow, and then Next time, pass --org ORG_ID …. With only one organization, it is taken without asking.

voltage environments get --account me --org ORG_ID
voltage wallets get --account me --org ORG_ID

Expect: an Environment: picker, then the environment. Then a Wallet: picker (name CURRENCY UUID), then the wallet card with balance and activity, as for wallets get WALLET_ID.

voltage wallets get --account me --org ORG_ID --json; echo $?
voltage wallets get --account me --org ORG_ID | cat; echo $?
voltage wallets delete --account me --org ORG_ID; echo $?

Expect: no picker in any of these. wallets get needs a wallet ID (or wallets delete needs a wallet ID), with the hint, and exit code 2.

Merge order with #127

#127 now sits directly below this PR in stack #135, so the picker's pseudo-terminal tests (pick, Esc) run against #127's new terminal guard; the whole suite passes on this head. Merging the layers below also gave DiscoveryCommand::List #120's --all (and updated #120's parse test to DiscoveryCommand::List) and kept #129's context --format env dispatch beside this PR's discover.

Open questions

  • Should changes (wallets update/delete) pick too? They are describe-only without --execute, but I kept them explicit to stay conservative.
  • Other single-resource reads (payments get, webhooks get, quotes get) could pick the same way. I left them out: their lists can be long and paginated, and they have no names to filter on.

Stack

Stack #135, layer 6 of 9 (bottom targets master):

# PR Change
1 #97 Staging setup and a Try it section for each PR
2 #120 --all on organizations, environments, and profiles list
3 #128 Warn when a profile or flag overrides a set VOLTAGE_* variable
4 #129 voltage context --format env
5 #127 Replace hand-rolled terminal code with crossterm
6 #126 (this PR) Pick the wallet, organization, or environment to show
7 #130 Prefilled GitHub issue links from voltage feedback and panics
8 #123 voltage environments create
9 #131 Replace flake.nix with a Kaifile

The layers are chained with merge commits, because history is not rewritten. Use squash and merge, bottom first.

@thebrandonlucas
thebrandonlucas marked this pull request as draft October 27, 2026 14:35
@thebrandonlucas
thebrandonlucas marked this pull request as ready for review November 4, 2026 16:10
@thebrandonlucas
thebrandonlucas requested a review from tee8z November 4, 2026 16:10
thebrandonlucas added a commit that referenced this pull request Nov 4, 2026
…-the-cli' into brandon/dev-2953-voltage-cli-add-environments-create

Resolve environments create against the layers below it:

- EnvironmentCommand flattens DiscoveryCommand (list, get) from #126 and
  adds create, so `environments list` keeps the --all that #120 added and
  `environments get` keeps the picker.
- Resolver::discover keeps #126's DiscoveryCommand argument and uses this
  branch's requires_login and login_organization helpers.
- pick asks for an environment only for `environments get`, and #120's
  parse test matches EnvironmentCommand::Discovery(DiscoveryCommand::List).
- CHANGELOG, docs/commands.md, the login guide, and check-coverage.py keep
  both the get and create entries.
@thebrandonlucas
thebrandonlucas changed the base branch from master to brandon/dev-2904-voltage-cli-replace-hand-rolled-terminal-code-with-crossterm November 4, 2026 18:33
tee8z
tee8z previously approved these changes Nov 4, 2026

@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 at 51e7afb. Moving the missing-wallet error from parsing to pick::fill is safe: Session::open calls fill on every path, and wallet_target raises missing_wallet off a terminal, for JSON, and for every non-GET wallet operation, before any credential is resolved or request is sent. The picked wallet goes to resource_id, never the scope, so VOLTAGE_WALLET_ID still cannot aim a wallet command. organizations get and environments get reuse pick::needs, and the not-visible path is tested.

Non-blocking: discover finds the item by comparing id.to_string() with the API string. That depends on the API returning lowercase UUIDs. Parsing item["id"] as a Uuid would not.

CRAP: not run on this head yet; I will add the result when it finishes. CI is green.

@tee8z

tee8z commented Nov 4, 2026

Copy link
Copy Markdown
Contributor

CRAP on 51e7afb: 875 of 1093 functions measured, 0 violations. Highest: 18.10 wait_for_payment (unchanged), then 18.00 endpoint (unchanged).

@thebrandonlucas

Copy link
Copy Markdown
Contributor Author

Took the non-blocking suggestion in 8d9618b. discover now parses item["id"] as a Uuid before it compares. organizations_get_shows_the_selected_organization_and_never_asks_a_script now lists an ID in capitals and gets it by its lowercase form. That test fails without the change. CRAP on 8d9618b: 0 violations. The push may have dismissed your approval.

@thebrandonlucas
thebrandonlucas requested a review from tee8z November 5, 2026 12:55
… terminal

On a terminal, a wallet read without WALLET_ID or --wallet (wallets get,
wallets ledger list, wallets policies get, wallets payments summary) opens
the wallet picker and shows the wallet picked. New organizations get and
environments get show the organization or environment the scope selects,
and pick it on a terminal when none is selected.

Wallet changes still name their wallet. Off a terminal, with --json,
--output json|ndjson, or --no-input, nothing is asked and a missing ID
fails before any request, as before.
@thebrandonlucas
thebrandonlucas force-pushed the brandon/dev-2903-voltage-cli-interactive-mode-for-navigating-ids-and-teams branch from ce12d7a to 49590cc Compare November 5, 2026 15:09

@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.

Reviewed 49590cc against #127 (5f64b27). No blocking findings in this layer.

Wallet reads can pick their explicit target; wallet mutations still fail before discovery when the target is absent. The PTY tests exercise wallet/org/env selection, Esc, and the machine-output/no-input paths.

Local gate passed: formatting, Clippy with warnings denied, dependency hygiene, full tests (210 unit + 156 integration), and contract/docs generation checks. Required CRAP check passed: 877/1097 functions measured, 220 unmeasured, no scores >=20; highest 18.10 (wait_for_payment).

Limits: Linux mock-service/PTY validation; no live staging requests or manual Windows/macOS run. Approval applies to this incremental stack layer.

@thebrandonlucas
thebrandonlucas merged commit 0023b68 into master Nov 5, 2026
8 of 14 checks passed
@thebrandonlucas
thebrandonlucas deleted the brandon/dev-2903-voltage-cli-interactive-mode-for-navigating-ids-and-teams branch November 5, 2026 18:07
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