镜像站点 · 本页由第三方 GitHub 只读镜像提供,非 GitHub 官方站点,不接受任何登录或凭据输入。前往 github.com
Skip to content

feat(cli): pick a missing organization, environment, or wallet - #22

Merged
thebrandonlucas merged 1 commit into
docs/help-examples-executefrom
feat/cli-interactive-pickers
Oct 20, 2026
Merged

thebrandonlucas merged 1 commit into
docs/help-examples-executefrom
feat/cli-interactive-pickers

Conversation

@thebrandonlucas

@thebrandonlucas thebrandonlucas commented Oct 9, 2026 •

Copy link
Copy Markdown
Contributor

Stack: master ← #20 ← #22 ← #57 ← #24 ← #19 ← #26 ← #27 ← #28 ← #29 ← #30 ← #55. Rebased onto master (#53, #56) on 2026-10-19.

Summary

On a terminal, a command that lacks the organization, environment, or wallet it needs now asks for it instead of failing. Implements DEV-2903 (first part).

$ voltage payments send --org ORG_ID --env ENV_ID --currency btc --invoice lnbc1…
? Wallet: pay
> Payroll  cccccccc-cccc-4ccc-8ccc-cccccccccccc
Next time, pass --wallet cccccccc-… or save a profile with voltage profiles create.
dry_run  …                       (still only described: --execute is the approval)
Command Asks for
API commands Each scope in the path: organization, environment, wallet
Friendly payments send / receive Wallet
Friendly wallets create Environment
environments list Organization
profiles create Organization and environment
  • Choices come from the same sources as organizations list, environments list, and wallets list (in the selected environment). Each shows as name UUID. Typing filters by any part of the name or ID.
  • Never asks when stdin or stderr is not a terminal, when stdout is piped without --output table, or with --no-input, --json, --output json|ndjson, or --dry-run. Those runs fail with the same error as before.
  • Esc leaves the scope unset, so the command reports what is missing. An empty list says so. Ctrl-C exits with 130.
  • API keys cannot list organizations or environments. With a key, only a wallet is offered, and a missing organization still fails with --org is required (exit 2).
  • The picker resolves the credential once and exchanges at most one organization token, then the command runs as before.

Changes after review

  • api::wallets builds its path and query from the registry operation GetAllOrganizationsWalletsAsUser, through a new Request::scoped. More than one --env now fails with the same error as wallets list. Before, the error was discarded and the picker listed every environment's wallets.
  • pick::needs matches every LocalCommand and ProfileCommand variant, so a new local command must decide whether it needs a scope.
  • The mapping from a picker answer to a choice, Esc, Ctrl-C, or an I/O error (exit 4) is a small function with a unit test.
  • The guide lists piped stdout among the cases that never ask.
  • The integration tests for failed listings, Esc at the organization and environment pickers, and a real SIGINT are in test(cli): cover the picker's failed listings, Esc, and signals #57, to keep this PR under 1,000 lines.

Review exceptions

  • Dependency: inquire =0.9.4, default features off plus crossterm. It draws the picker through crossterm, which feat(cli): fit human tables to the terminal and scrub all API text #15 already added, so the CLI keeps one terminal backend. It enables crossterm's events feature, which brings mio, signal-hook, signal-hook-mio, and derive_more. New crates from inquire itself: inquire and dyn-clone. The fuzzy feature is left off: its default substring filter matches names and IDs, and dropping fuzzy avoids fuzzy-matcher and thread_local.
  • Crate-visible items: terminal::Choice and Terminal::pick (prompts belong to terminal), api::wallets (HTTP belongs to api), input::Request::scoped, and auth::exchange and auth::list (split out of resolve_organization and discover so the picker resolves once). Nothing is added to the library's public API.
  • Getter: Choice::name(). A Choice scrubs its API-supplied name in Choice::new, and private fields keep unscrubbed text from reaching the picker.
  • Guard: PickerScreen turns bracketed paste off and shows the cursor on drop. A signal that ends the command while the picker is open skips inquire's own cleanup. TerminalModes restores the terminal attributes, as for the hidden key prompt.

Deferred

  • Picking a positional target, such as wallets get with no ID. This changes clap's required argument.
  • auth import-key (organization and one environment) and webhook path scopes do not ask yet.
  • Scope-free validation still runs after the pickers. payments send --unit sats without --amount asks first, then fails.

Tests

  • Unit tests: which commands ask for which scope, how listings become scrubbed, sorted choices, and how a picker answer maps to a result.
  • Pseudo-terminal integration tests: a typed filter and Enter picks a wallet and the change is still only described (exit 6); Esc at the wallet picker gives the original error (exit 2); typed Ctrl-C exits 130; terminal modes are restored each time; --json, --no-input, and --dry-run neither ask nor list; an API key without an organization keeps --org is required; a login picks an organization and then an environment; an empty wallet list says so.

Checks

On 9f29f81:

  • cargo fmt --check, cargo clippy --locked --all-targets -- -D warnings, cargo machete --with-metadata: pass.
  • cargo test --locked: 93 unit and 65 integration tests pass. VOLTAGE_BLESS=1 cargo test --locked leaves the tree clean.
  • python3 scripts/check-coverage.py: pass.
  • CRAP (scripts/crap-report.sh): no function at or above 20. Highest is api::wait_for_payment at 19.66, unchanged from or lower than master (19.66).
  • Skill review: voltage-pr-review and the voltage-backend-engineering Rust checklist, against the stack base. Findings are fixed in this commit.

Not run: the staging and live tutorials, which need credentials.

tee8z
tee8z previously approved these changes Oct 9, 2026
Base automatically changed from docs/readme-landing to docs/help-examples-execute October 13, 2026 16:38
@thebrandonlucas
thebrandonlucas force-pushed the feat/cli-interactive-pickers branch from 9540228 to 6c29564 Compare October 13, 2026 16:40
@thebrandonlucas
thebrandonlucas force-pushed the feat/cli-interactive-pickers branch from 6c29564 to 33efa27 Compare October 13, 2026 16:44
@thebrandonlucas
thebrandonlucas force-pushed the feat/cli-interactive-pickers branch from 33efa27 to 069b2d5 Compare October 13, 2026 18:56
@thebrandonlucas
thebrandonlucas added this pull request to stack #25 October 13, 2026 18:56
@thebrandonlucas
thebrandonlucas force-pushed the feat/cli-interactive-pickers branch 3 times, most recently from 1a065cb to 37e9bc5 Compare October 19, 2026 18:25
@thebrandonlucas
thebrandonlucas force-pushed the feat/cli-interactive-pickers branch from 37e9bc5 to f5b085e Compare October 19, 2026 18:39
@thebrandonlucas
thebrandonlucas force-pushed the feat/cli-interactive-pickers branch from f5b085e to 7e7814c Compare October 19, 2026 19:19
Comment thread tests/cli_integration.rs Resolved

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

Review against docs/quality.md, with the CRAP report (scripts/crap-report.sh) run on this head.

ask fails the CRAP budget: 28.30 (cyclomatic 13, 55.1% coverage). It is the only function over 20 in the crate. The branching is fine: a straight sequence of early returns with nesting depth 1. The problem is that no test runs the organization and environment pickers with a browser login (src/pick.rs:123-151). The quality guide says to add meaningful tests rather than split functions, so these suggestions add tests:

  • Login, organization then environment: profiles create picks both. environments list picks only the organization.
  • API key with --org but no --env: shows the environments notice.
  • Empty wallet list: "No wallets to choose from."

To drive two pickers in one run, on_a_terminal now takes (prompt, keys) steps instead of Option<prompt> plus keys. The suggestions also drop the empty-list check in Terminal::pick, which can never run because choose returns first.

Apply them all as one batch (Files changed → "Add suggestion to batch" on each). The call-site suggestions only compile with the new on_a_terminal signature.

With all suggestions applied, I ran:

  • cargo fmt --check and cargo clippy --locked --all-targets -- -D warnings: clean.
  • cargo test --locked: 92 unit and 65 integration tests pass.
  • CRAP report: ask is 13.01 (95.9% coverage), fill 11.00 (100%), and no function violates the budget.
  • PR diff against docs/help-examples-execute: 876 changed lines, under the 1,000-line limit.

Not suggested (outside this diff): on_a_terminal repeats most of interrupt_the_hidden_key_prompt (tests/cli_integration.rs:1734): the terminal setup, the unsafe pre_exec block, the output reader, and the kill-on-timeout cleanup. GitHub can't suggest changes to lines this PR doesn't touch. A follow-up could move that helper onto the same spawn and cleanup code so there is one unsafe block.

Comment thread src/terminal.rs Resolved
Comment thread tests/cli_integration.rs Resolved
Comment thread tests/cli_integration.rs Resolved
Comment thread tests/cli_integration.rs Resolved
Comment thread tests/cli_integration.rs Resolved
Comment thread tests/cli_integration.rs Resolved
Comment thread tests/cli_integration.rs Resolved
Comment thread tests/cli_integration.rs Resolved
Comment thread tests/cli_integration.rs Resolved
Comment thread tests/cli_integration.rs Resolved
@thebrandonlucas
thebrandonlucas force-pushed the feat/cli-interactive-pickers branch from 7e7814c to 2a34199 Compare October 20, 2026 00:20
On a terminal, a command that lacks the organization, environment, or
wallet it needs now asks for it instead of failing. A picker, filtered
by typing part of a name or ID, lists each choice as "name  UUID" from the same sources as
organizations list, environments list, and wallets list, in the
environment when one is selected. Afterwards a notice names the flags
that select the same scope.

API commands ask for the scope their path needs. Friendly payment
creation asks for a wallet, and friendly wallet creation for an
environment. environments list asks for an organization, and profiles
create for an organization and environment. A positional target is
still typed.

Nothing is asked when stdin or stderr is not a terminal, with
--no-input, with JSON or NDJSON output, or with --dry-run, so scripts
see the same errors as before. Esc leaves the scope unchanged and the
command reports what is missing; Ctrl-C exits with code 130. An API key
cannot list organizations or environments, so with a key only a wallet is
offered and a missing organization fails as before. Picking
never sends a change, which still needs --execute. API names are
scrubbed before the picker draws them.

The picker resolves the credential once and exchanges at most one
organization token. inquire draws it through crossterm, which it now runs
with its event features. Pseudo-terminal tests cover a pick, Esc, Ctrl-C,
the no-prompt flags, an empty wallet list, an API key without an
organization or environment, and a login that picks an organization and
then an environment.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

human review wanted CI passes; ready for a human reviewer

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants