Skip to content

feat(cli): list wallets in every organization with --all-orgs - #74

Merged
thebrandonlucas merged 1 commit into
fix/cli-shortlist-env-flagsfrom
feat/cli-wallets-all-orgs
Oct 21, 2026
Merged

thebrandonlucas merged 1 commit into
fix/cli-shortlist-env-flagsfrom
feat/cli-wallets-all-orgs

Conversation

@thebrandonlucas

@thebrandonlucas thebrandonlucas commented Oct 21, 2026 •

Copy link
Copy Markdown
Contributor

Stack

One stack, merged bottom up:

  1. feat(cli): name the wallets that could pay a BOLT11 invoice #73: wallets that could pay a BOLT11 invoice (base master)
  2. fix(cli): name each candidate's environment when --env is not given #77: tee8z's review fixes for feat(cli): name the wallets that could pay a BOLT11 invoice #73 (base feat(cli): name the wallets that could pay a BOLT11 invoice #73)
  3. This PR: wallets list --all-orgs (base fix(cli): name each candidate's environment when --env is not given #77)
  4. feat(cli): offer a pinned profile after login on a terminal #75: offer a pinned profile after login (base feat(cli): list wallets in every organization with --all-orgs #74)

Problem

An agent that needs a wallet across organizations had to loop over organizations list and run wallets list --org once per organization (#34). The backend will not add a cross-organization endpoint (voltagecloud/backend#2296, closed: most users are in one organization, and the CLI can combine the results itself).

Behavior

voltage wallets list --all-orgs lists the wallets in every organization the browser login can see, as one list.

flowchart LR
  L[resolve login once] --> O[GET /users/current<br/>organizations]
  O --> P{for each organization,<br/>4 at a time}
  P --> X[token exchange<br/>audience = org]
  X --> W[GET /organizations/ID/wallets<br/>with the command's filters]
  W --> M[merge in organization order,<br/>add organization_id + organization_name]
Loading
  • Each wallet carries organization_id and organization_name. The table gets an ORGANIZATION NAME column, added to the preferred columns after name.
  • The command's filters and --query overrides (for example, --inactive true) apply in each organization. Profile and VOLTAGE_ORGANIZATION_ID scope is ignored, and --org or --env is a usage error (clap conflicts_with_all).
  • The picker asks nothing, because the command needs no organization.
  • An API key is refused with exit 3 before any request is sent. The hint says that a key belongs to one organization and suggests --org ID or --account NAME.
  • Progress: one Listing wallets in N organizations... line. The parallel exchanges and reads run quietly, so their progress lines cannot interleave.

Partial failure (decision): write the partial results, then fail. The wallets from the organizations that answered go to stdout as usual. The command then exits with the first failure's code (for example, 3 for a 403), with the message Could not list wallets in K of N organizations: NAME (ID), ... and error.detail.failed_organizations: [{organization_id, name, message, hint?}]. A script that checks the exit code never mistakes a partial list for a complete one, and a person still sees what could be listed. When every organization fails, nothing is written to stdout. If they all fail the same way (same kind and message), that error is returned unchanged with its own hint and detail. For example, a revoked login keeps credential_problem: login_expired and the voltage login --account NAME hint. Otherwise each organization is listed and the hint points to voltage auth status. Each failed_organizations entry also carries the failure's own detail.

The parallel exchanges go through Resolver::organization_token, which exchange now also uses. account_auth takes the Terminal, so --all-orgs passes a quiet one, with no duplicated auth-service setup.

Closes #34 (together with #73).

Checks

  • voltage-pr-review self-review against feat(cli): name the wallets that could pay a BOLT11 invoice #73's head, with the Rust style checklist.
  • Every new path is tested: merging and filters for two organizations, the human table column, partial failure (exit code, stdout, detail), total failure with mixed errors (nothing written, each organization named), a revoked login across every organization (the login_expired error itself, exit 3), no organizations (empty list, no API calls), API key refusal, --org/--env conflicts, and the picker asking nothing. Unit tests cover the organization parsing, the merge for pages and non-object items, and the error construction.
  • Gate in nix develop: cargo fmt --check, cargo clippy --locked --all-targets -- -D warnings, cargo machete --with-metadata, cargo test --locked, and python3 scripts/check-coverage.py all pass.
  • CRAP (outside nix), on the feat(cli): offer a pinned profile after login on a terminal #75 tip ff8b488: 0 violations. The highest score is 19.60 for wait_for_payment (unchanged). cli.rs endpoint rose from 17 to 18 at 100% coverage, because of the new flag branch.

The API lists wallets one organization at a time, and each organization
needs its own token, so `wallets list --all-orgs` lists the browser
login's organizations, then exchanges a token and reads the wallets for
each, four at a time. The results merge into one list, and each wallet
gains `organization_id` and `organization_name`, which the table shows.

An API key belongs to one organization, so it is refused with a hint,
and `--org` or `--env` cannot be combined with the flag. When some
organizations fail, the others' wallets are written and the command
then fails with the first failure's exit code, naming each failed
organization in `error.detail.failed_organizations`. When all fail,
nothing is written, and when they all fail the same way, as for a
revoked login, that error is reported with its own hint and detail.

Closes #34
tee8z
tee8z previously approved these changes Oct 21, 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 24cd775 against feat/cli-invoice-wallet-shortlist (#73 ed53d14). The changes requested on #73 still block this stack.

  • The parallel reads are safe: AuthService::exchange is a stateless token exchange that neither refreshes nor writes anything, and resolve has already refreshed the login once. buffered(4) keeps the organization order, so merge's zip pairs each result with its organization.
  • An API key is refused before any request. --org and --env conflict at parse time, and the picker asks nothing.
  • On partial failure, the wallets that were read are written, and then the first failure's kind sets the exit code. When every organization fails, nothing is written. When they all fail the same way, as with a revoked login, that error keeps its own hint and credential_problem detail.
  • Each organization's error has already been redacted by Api::send, so failed_organizations exposes no secret.

Nonblocking: COLUMNS puts organization_name in every table, not only this listing. That has no effect today, because no other listing returns the field.

Validation on this head, in nix develop: cargo fmt --check, cargo clippy --locked --all-targets -- -D warnings, cargo machete --with-metadata, cargo test --locked (129 unit, 112 integration) and python3 scripts/check-coverage.py passed. CRAP: 596 measured functions, none at or above 20, highest wait_for_payment at 19.60; 162 functions were unmeasured. CI is green.

@thebrandonlucas
thebrandonlucas force-pushed the feat/cli-wallets-all-orgs branch 2 times, most recently from b73454e to ce71c69 Compare October 21, 2026 19:31
@thebrandonlucas
thebrandonlucas removed this pull request from stack #76 October 21, 2026 19:32
@thebrandonlucas
thebrandonlucas changed the base branch from feat/cli-invoice-wallet-shortlist to fix/cli-shortlist-env-flags October 21, 2026 19:32
@thebrandonlucas
thebrandonlucas added this pull request to stack #78 October 21, 2026 19:32
@thebrandonlucas
thebrandonlucas merged commit c2fec30 into master Oct 21, 2026
8 checks passed
@thebrandonlucas
thebrandonlucas deleted the feat/cli-wallets-all-orgs branch October 21, 2026 21:49
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.

No direct way to find a wallet that can pay an invoice

2 participants