feat(cli)(#32): canonical UX — --json, --help, asset-pair input, auto-help on errors#33
Merged
Merged
Conversation
3 tasks
…-help on errors Issue #32 — sphere-cli UX consistency. No more legacy: every command takes the same input shape, defaults to human-friendly output, and prints the full help block (never a one-line "Usage:" stub) on errors. Five-pass implementation (legacy-cli.ts): Pass A — Output formatting - New formatOutput(payload, shape, label?) helper with a typed OutputShape union and per-shape renderers (identity, invoice-terms, invoice-status, transfer-result, swap-status, etc.). - Global --json flag (jsonMode) opts back into JSON; default is a human-readable labelled block. - Replaced 22 console.log(JSON.stringify(...)) sites in the dispatch body. File-write/POST-body JSON.stringify calls remain (wire format). Pass B — Canonical asset input - Dropped parseAssetArg (quoted compound form, "10 UCT"). - New consumeAssetPair(args, startIdx) reads two argv tokens. - invoice-create, invoice-return, swap-propose all take --asset <amount> <coin>, --offer <amount> <coin>, --want <amount> <coin>. - invoice-create now supports multiple --asset flags for multi-asset invoices. Pass C — --help works everywhere - Early-dispatch shim in main() catches --help/-h before the switch, handling sub-subcommand keys ("wallet create", "daemon start") via compound lookup with fallback to top-level. - src/index.ts: legacy subcommands disable commander's built-in --help (helpOption(false)) so the flag reaches our dispatcher. - "help" added to LEGACY_NAMESPACES so `sphere help <cmd>` routes through the COMMAND_HELP registry. - Universal flags (--json, --help/-h) added to printCommandHelp output. Pass D — Completion + docs - Added COMMAND_HELP entries for `completions` and `help` so the drift-guard test passes. - README: new "UX — canonical conventions" + "Shell completion" sections documenting the canonical syntax and three install paths (no-sudo, system-wide, per-user zsh fpath). Pass E — Auto-help on invalid input - Single failWithHelp(cmdName, errorMsg) helper prints "Error: <msg>" followed by the full COMMAND_HELP block to stderr, then exit(1). - Replaced ~40 `console.error('Usage: …'); process.exit(1)` sites. - New helper resolveInvoiceId() collapses the duplicated invoice-prefix lookup boilerplate across ~10 invoice-* commands. Plus a new src/legacy/legacy-cli-ux.test.ts that statically guards the invariants (no stray JSON.stringify, no "Usage:" stubs, no parseAssetArg calls, completion ↔ COMMAND_HELP alignment) so future drift surfaces as a test failure. Verified: typecheck clean, 113/113 tests pass (incl. 7 new UX guard tests), build clean, smoke-tested `sphere invoice create --help`, `sphere swap propose --help`, `sphere help send`, and the missing-args auto-help path on `sphere payments send`.
c2c8771 to
310c981
Compare
This was referenced Jun 5, 2026
Open
vrogojin
added a commit
that referenced
this pull request
Jun 7, 2026
Closes follow-ups #1 and #2 from issue #40. Item #1 — `sphere invoice return <id>` (bulk) shows actual refunded amount instead of `amount: "0"`. The bug: `getInvoiceStatus()` ran AFTER `returnAllInvoicePayments()`, by which point the SDK had drained `senderBalances[].netBalance` to 0 (refunds had been issued), so every row in the rendered output showed 0. Fix: snapshot status BEFORE the SDK call, mirror the SDK's iteration filters so `plan[i] ↔ results[i]`. Static-analysis pin in `legacy-cli-ux.test.ts` asserts the call ordering doesn't regress (first `await getInvoiceStatus(` must precede first `await returnAllInvoicePayments(` inside the invoice-return case). Item #2 — integration test backlog from PR #33 canonical-UX cutover. Down from 63 → 3 failures (remaining 3 are unrelated regressions, called out below). • `expectUsageHint(out, cmdName, positional?)` helper in `test/integration/helpers.ts`: tolerates the new `Usage: npm run cli -- <cmd>` prefix so the next help-format change is a one-line fix. All ~13 call sites updated. • JSON-shape stdout captures (`/"directAddress": "..."/` etc.) updated to canonical-UX labelled-block form (`/directAddress\s*:\s*.../`) across 9 test files. • `--asset "1000000 UCT"` quoted single-string form (rejected by the canonical asset-pair guard) replaced with two-positional form in cli-invoice/cli-swap. • `expect.toMatch(/Asset not found/)` → case-insensitive — the error string is lowercased by `failWithHelp` now. Same for `No invoice found matching prefix:`. • `crypto decrypt` now accepts BOTH bare base64 (the new canonical-UX `crypto encrypt` output) and JSON-quoted base64 (legacy `--json` callers). Restores the `encrypt foo pw | decrypt - pw` roundtrip. • Added `swap-ping` HELP_TEXT block and completion entry (the `failWithHelp('swap-ping', ...)` calls were already there; only the help registration was missing). Moved swap-ping into `SWAP_SUBCOMMANDS` and dropped the obsolete "no help available (HELP_TEXT gap pin)" test. Items #3 (`--asset-index` / `--target-index`) and #4 (NFT-only `invoice pay`) deferred per the issue's suggested order. Out of scope, remaining integration failures (3): • `cli-multiaddress`: `tokens/` subdir ENOENT after `payments switch 1` — likely Profile (OrbitDB) cutover relocating the per-address token store off `.sphere-cli/tokens/`. Separate bug; not Usage/UX cutover. • `cli-wallet-lifecycle`: `sphere clear --yes` succeeds, but `sphere status` afterwards auto-generates a new wallet and reports it instead of "No wallet found". Real CLI/SDK regression; needs a separate ticket. • `cli-wallet-lifecycle`: `init --nametag` flake — testnet nametag mint sometimes doesn't complete inside the timeout. Locally green when the mint succeeds; the regex now correctly matches the canonical UX `nametag : @<name>` form. Unit tests (127) all green. Type-check + lint clean. Co-authored-by: Vladimir Rogojin <vrogojin@blockyinnovations.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #32.
Issue #32 asked for sphere-cli UX consistency across five concrete goals.
This PR delivers all five passes as a clean break — no backwards-compat
shims for the legacy quoted-asset form or for the JSON-by-default output.
What changed
Pass A — output formatting
formatOutput(payload, shape, label?)helper with a typedOutputShapeunion and per-shape renderers (identity, invoice-terms,invoice-status, transfer-result, swap-status, swap-ping, auto-return,
invoice-transfers, generic, …).
--jsonflag opts back into JSON; default is a human-readablelabelled block.
console.log(JSON.stringify(...))sites. File-write /POST-body
JSON.stringifycalls remain (wire format).Pass B — canonical asset input
parseAssetArg(quoted compound form,"10 UCT").consumeAssetPair(args, startIdx)reads two argv tokens.invoice-create,invoice-return,swap-proposenow take--asset <amount> <coin>,--offer <amount> <coin>,--want <amount> <coin>.invoice-createsupports multiple--assetflags for multi-assetinvoices.
Pass C —
--helpworks for every command/subcommandmain()catches--help/-hbefore theswitch, handling sub-subcommand keys (
wallet create,daemon start)via compound lookup with fallback to top-level.
src/index.ts: legacy subcommands disable commander's built-in--help(helpOption(false)) so the flag reaches our dispatcher.helpadded toLEGACY_NAMESPACESsosphere help <cmd>routesthrough the
COMMAND_HELPregistry.--json,--help/-h) shown in every help block.Pass D — completion + docs
COMMAND_HELPentries forcompletionsandhelpso thedrift-guard test passes.
sections documenting the canonical syntax and three install paths
(no-sudo, system-wide, per-user zsh fpath).
Pass E — auto-help on invalid input
failWithHelp(cmdName, errorMsg)helper printsError: <msg>followed by the fullCOMMAND_HELPblock to stderr,then
exit(1).console.error('Usage: …'); process.exit(1)sites.resolveInvoiceId()helper collapses the duplicatedinvoice-prefix lookup boilerplate across ~10 invoice-* commands.
Static UX guard
New
src/legacy/legacy-cli-ux.test.tsenforces the invariants sofuture drift surfaces as a test failure:
console.log(JSON.stringify(...))in the dispatch body.console.error('Usage: …'); process.exit(…)legacy stubs.parseAssetArg(...)call sites.COMMAND_HELPkey is reachable through the completion list.formatOutput,failWithHelp,consumeAssetPair) exist.--jsonand--helpshims are wired inmain().Verification
Smoke-tested in dev:
sphere invoice create --help→ prints the new canonical help blocksphere swap propose --help→ canonical--offer <amount> <coin>formsphere help send→ routes throughCOMMAND_HELP['send']sphere payments send(no args) →Error: …+ full help block + exit 1sphere wallet initintegration test compatibility: human renderkeeps
l1Address/directAddress/chainPubkeyas labels so theexisting
toMatch(/l1Address/)assertion still passes.Breaking changes (intentional — "no more legacy")
--asset "10 UCT") is no longeraccepted. Use
--asset 10 UCT(two positional tokens).CLI stdout must pass
--json.Both are explicitly called out in the new "UX — canonical conventions"
README section so the migration path is one place.
Out of scope (per the issue)
legacy-cli.tsmonolith — that's its own refactor;this PR lands within the existing file structure as the issue
permits.