Skip to content

feat(cli): say what a described change would do on a terminal - #98

Merged
thebrandonlucas merged 1 commit into
feat/cli-look-03-loginfrom
feat/cli-look-04-confirm
Oct 23, 2026
Merged

thebrandonlucas merged 1 commit into
feat/cli-look-03-loginfrom
feat/cli-look-04-confirm

Conversation

@thebrandonlucas

@thebrandonlucas thebrandonlucas commented Oct 22, 2026 •

Copy link
Copy Markdown
Contributor

Stack position: 4 of 13 in the look-and-feel stack. Base: feat/cli-look-03-login (#96).

Problem

Since #54, a change without --execute prints a description of its request and exits with code 6. On a terminal, that description is a debug-style field dump (run.reconcile, values_omitted, …) followed by a red error: Described this change without sending it…. It does not say plainly what would happen, and a normal preview looks like a failure.

Behavior

On a terminal with table output:

! Not sent: this would send a payment          ← yellow "!", action in bold
  amount     1000000 msats
  currency   btc
  max_fee    10000 msats
  type       bolt11
  wallet_id  33333333-3333-4333-8333-333333333333

dry_run  03684da9-…                            ← the full description, unchanged (stdout)
…
› Send it: add --execute, or set VOLTAGE_EXECUTE=1
Part Rule
Action payments send and receive say "send a payment" and "receive a payment". Other changes use the API's operation summary, lowercased, without a trailing period or " as a user" ("delete a wallet", "request a currency conversion quote").
Values The target resource, plus the body values the description already shows. Omitted (private) values and an id generated only for the description are left out. A send's data fields are listed as its own (amount, not data.amount). They use the same field value renderer as tables, so amounts read 1000000 msats.
Last line › Send it: … replaces the error: line on a terminal. Exit code 6 is unchanged.
Unchanged Redirected stderr keeps error: Described this change…; --json and NDJSON are unchanged; --quiet hides the summary but not the › Send it line.

There's no prompt: --execute is still the only approval, as #54 decided. The plan's "y/N" confirmation was written before #54 and was dropped.

Try it on staging

Setup: Try a pull request on staging (added by #97). These commands send nothing, so any wallet works; find a wallet ID with voltage wallets list --account me.

voltage wallets delete WALLET_ID --account me

Expect: ! Not sent: this would delete a wallet, then wallet_id WALLET_ID, a blank line, the full description, and › Send it: add --execute, or set VOLTAGE_EXECUTE=1. Exit code 6 (echo $?).

voltage payments receive --account me --wallet WALLET_ID --currency btc --kind bolt11 --amount 1000 --unit sats
voltage webhooks create --account me --name demo --url https://example.com/hook --event receive.completed

Expect: "this would receive a payment" with amount 1000000 msats, payment_kind bolt11, and wallet_id. Then "this would create a new webhook", where the URL and name are not shown because the description omits them.

voltage wallets delete WALLET_ID --account me --output table 2>&1 | cat
voltage wallets delete WALLET_ID --account me --json
voltage wallets delete WALLET_ID --account me -q

Expect: piped, the old error: Described this change without sending it… line and no summary. With --json, the JSON description and the JSON error, unchanged. With -q, no summary, but the › Send it line stays.

Validation

  • cargo fmt --check, cargo clippy --locked --all-targets -- -D warnings, cargo machete --with-metadata, cargo test --locked (147 unit and 125 integration tests), and python3 scripts/check-coverage.py all pass.
  • Unit tests cover the action phrase (aliases, summary, trailing period, " as a user"), and the facts: the target alone, a send's flattened data, omitted and null values dropped, a generated id dropped, and a chosen --id kept.
  • New PTY test: wallets delete without --execute on a terminal shows the summary block exactly, the details on stdout, the bold yellow !, a closing › Send it line, and no error:; exit code 6; no request sent. Piped stderr keeps error: Described this change and no summary.
  • CRAP: 0 of 678 measured functions at or above 20. The highest is 18.00 (src/cli.rs endpoint, untouched); api::execute is 15.00.

Docs

docs/concepts/safety-model.md and CHANGELOG.md.

@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 00bae89 (base #96 9296da7). The summary is well placed:

  • It runs only for human formats, and report_error handles the machine formats first, so --json and NDJSON are unchanged.
  • Exit code 6 is kept, and no request is sent.
  • The summary uses the same body that the description shows, so private and secret fields stay out unless --show-secrets is given.
  • Amounts keep their own currency.

Two fixes are needed.

P2: the summary can name a different wallet or webhook than the one the request targets. facts (src/dry_run.rs:174) labels the target with described.resource_id, which is body_id.or(invocation.resource_id) (src/input.rs). The request path, though, uses invocation.resource_id, and a raw --data body's id is accepted on any operation. Try voltage wallets update 1111… --org ORG --data @b.json --output table with {"id":"2222…","name":"x"} in b.json. The summary shows wallet_id 2222…, while the URL acts on 1111…. People read this summary to decide whether to add --execute, so it must name the target that the request hits. Use invocation.resource_id for the target fact, and add this case to facts_name_the_target_and_the_shown_values_only.

P2 (AGENTS.md: test every new early return): only the decorated terminal path and piped stderr are tested. Please also cover these:

  • --quiet hides the summary but keeps › Send it.
  • --json or --output ndjson with stderr on a terminal prints no summary and a JSON error (output.rs:320).

Nonblocking:

  • The Send it line hard-codes VOLTAGE_EXECUTE=1, while api.rs uses the EXECUTE_VARIABLE constant.
  • The description's "Try it" pipes stdout with 2>&1 | cat. A piped stdout selects JSON, so stderr gets the JSON error, not error: Described…. It needs --output table.
  • Arrays in the summary, such as webhook events, are shown as raw JSON.

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

Comment thread src/dry_run.rs Outdated
Comment thread src/output.rs
A change without --execute now starts, on a terminal with table output,
with "! Not sent: this would ..." and the target and values the description
shows, and ends with "Send it: add --execute, or set VOLTAGE_EXECUTE=1"
instead of an error line. The full description and exit code 6 are
unchanged, and redirected stderr keeps the error line.
@thebrandonlucas
thebrandonlucas force-pushed the feat/cli-look-04-confirm branch from 243fe63 to b17d0ca Compare October 23, 2026 12:38
@thebrandonlucas

Copy link
Copy Markdown
Contributor Author

Review-body notes, in b17d0ca:

  • Send it line: built from config::EXECUTE_VARIABLE.
  • Description's Try it: the piped example now passes --output table.
  • Arrays as raw JSON (webhook events): left as is. The summary uses the same field renderer as the details table below it, which also shows [{"receive":"completed"}]. A friendlier array layout would change both, so I'd do it separately.

@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 b17d0ca against feat/cli-look-03-login (#96 f9a1096). My findings are fixed:

  • The summary's target comes from invocation.resource_id, the path's target, so a raw --data body with another id no longer changes it, and a unit test covers that case.
  • New terminal tests cover --json, which prints only the JSON error with no summary and no Send it line, and --quiet, which hides the summary and keeps Send it.
  • The Send it line now uses EXECUTE_VARIABLE.

Validation on this head, in nix develop: cargo fmt --check, cargo clippy --locked --all-targets -- -D warnings, cargo machete --with-metadata, cargo test --locked (159 unit, 132 integration) and python3 scripts/check-coverage.py passed. CRAP: 708 measured functions, none at or above 20, highest wait_for_payment at 17.05. CRAP passed on its second run, after three pseudo-terminal login tests timed out in the instrumented suite on the first, the flakiness that #104 addresses. CI is green.

@thebrandonlucas
thebrandonlucas merged commit b2855cc into master Oct 23, 2026
9 of 14 checks passed
@thebrandonlucas
thebrandonlucas deleted the feat/cli-look-04-confirm 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