Skip to content

feat: zerion login and logout commands (PKCE browser flow)#17

Open
graysonhyc wants to merge 13 commits intomainfrom
feat/login-cmd
Open

feat: zerion login and logout commands (PKCE browser flow)#17
graysonhyc wants to merge 13 commits intomainfrom
feat/login-cmd

Conversation

@graysonhyc
Copy link
Copy Markdown
Collaborator

@graysonhyc graysonhyc commented Apr 24, 2026

Summary

Adds zerion login / zerion logout and routes zerion init through the new browser PKCE flow — users no longer have to copy an API key from the dashboard by hand.

  • login (default) — opens dashboard.zerion.io/cli-auth with a PKCE code_challenge, polls a backend status endpoint, and auto-saves the returned API key. Interactive picker offers browser-PKCE or paste-a-key.
  • login --api-key <key> — non-interactive save (for scripts / CI).
  • logout — clears saved apiKey and any agentTokens, warns if ZERION_API_KEY is still set in the shell.
  • init — now uses browserLogin() directly instead of prompting for a paste. --browser flag dropped (browser is the only interactive path); --yes skips auth and points users at zerion login for later.
  • Inline offer — after wallet create / wallet import, if no API key is configured, the CLI offers to run login inline. Failures don't kill the wallet flow.

What's in the PR

  • cli/utils/auth/pkce.js — session ID (32B hex), verifier (32B base64url), SHA-256 challenge.
  • cli/utils/auth/browser-flow.js — opens WEB_URL/cli-auth?code_challenge=…#session_id=…, polls CLI_STATUS_URL every 2s (5-min timeout). 202 = pending, 401 = rejected, 200 { apiKey } = done. Accepts an opener for tests.
  • cli/commands/login.js — banner, welcome menu, success block with team/API/masked key/config path. Reusable from inline flows via quiet: true (rethrows instead of process.exit).
  • cli/commands/logout.js — single-path cleanup via unsetConfigValue, env-var hint.
  • cli/commands/init.js — now calls browserLogin(); dropped local openBrowser() / readSecret() paste path and --browser flag.
  • cli/utils/wallet/offer-login.js — post-wallet-setup login offer; suppresses banner on chained inline flows; survives login failure so wallet setup still completes.
  • cli/commands/wallet/create.js, cli/commands/wallet/import.js — wired into the offer.
  • cli/utils/common/constants.jsWEB_URL and CLI_STATUS_URL, both env-overridable for staging/tests.
  • cli/router.js, cli/zerion.js — help output + command registration.
  • README.md — new auth section leading with zerion login; copy-paste flow demoted to the non-interactive fallback (--api-key / ZERION_API_KEY). Setup table now lists login / logout. Quickstart simplified to npx -y zerion-cli init (no --browser).
  • skills/zerion/SKILL.md — auth section now leads with zerion login / logout; env-var path is the non-interactive fallback for CI/headless agents.
  • package.json — adds open dependency.
  • package-lock.json — regenerated with optional deps so npm ci is happy in CI.
  • tests/auth/{pkce,browser-flow}.test.mjs — PKCE fixture hash + mocked polling (202→200 and 401 rejection paths). Browser flow accepts injected opener so tests don't launch real browser tabs.

Edge cases handled

  • Key format — validator accepts both zk_… (real dashboard format, e.g. zk_dev_… / zk_prod_…) and legacy zk-…. Previously rejected valid keys.
  • Non-TTY stdin — interactive login and init detect !process.stdin.isTTY and surface a structured error/skip pointing at --api-key / ZERION_API_KEY instead of hanging on readline.
  • Masked input — manual-paste path uses readSecret({mask: true}) so the API key no longer echoes into terminal scrollback.
  • Flag collision--api-key alongside --browser prints a note that --api-key wins.
  • Env-var persistencelogout prints a hint to unset ZERION_API_KEY when the env var is still set, since it overrides the saved config.
  • Already logged in — prints a masked preview and exits unless --force is passed.
  • Inline login failure — when login is invoked from within wallet create / import, a failed PKCE flow (timeout, network) does not abort wallet setup; the outer flow continues and the user can retry login later.
  • Banner suppression on chained flows — inline login skips the welcome banner so the wallet-setup output stays clean.
  • Test isolationbrowserLogin() accepts an opener arg, so unit tests don't spawn real browser tabs in CI.

Out of scope

No changes to x402, MPP, Solana support, analytics commands, auth.js, client.js, or any other existing code paths. This PR only adds the login/logout surface, routes init through it, and updates docs.

Test plan

  • npm test — 101 unit tests pass, 0 fail
  • npm ci succeeds locally (lockfile in sync; CI green)
  • zerion login interactive picker (option 1 → browser PKCE, option 2 → masked paste)
  • zerion login --api-key zk_dev_… non-interactive
  • zerion login on already-logged-in config → exits cleanly, suggests --force
  • zerion init (no flags) → routes through browser PKCE
  • zerion init -y → skips auth with non_interactive reason
  • zerion init with ZERION_API_KEY set → already-authenticated path
  • zerion logout with and without ZERION_API_KEY set in shell
  • zerion wallet create / import with no API key → offers inline login; failure doesn't abort wallet setup
  • End-to-end against staging dashboard (requires backend /api/cli/status endpoint)

🤖 Generated with Claude Code

graysonhyc and others added 13 commits April 22, 2026 17:04
Adds a PKCE-based browser login flow so users no longer have to paste
an API key from the dashboard. `zerion login --browser` opens the
dashboard, polls a backend status endpoint with the code_verifier,
and auto-saves the returned key. Also supports `--api-key` for direct
save and an interactive default. `zerion logout` clears the saved key
and any agent tokens without touching other config.

- cli/lib/auth/{pkce,browser-flow}.js: PKCE primitives + polling loop
- cli/commands/{login,logout}.js: new commands, registered in zerion.js
- cli/lib/util/constants.js: WEB_URL and CLI_STATUS_URL (env-overridable)
- tests/auth/{pkce,browser-flow}.test.mjs: unit tests incl. fixture hash
  and mocked fetch polling (202 -> 200 and 401 rejection paths)
- package.json: adds `open` dep; test glob now includes subdirs
- README: new Quickstart Authentication section

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Show banner, welcome menu, masked key + config path on login success
- Clear agentTokens on logout via unsetConfigValue and show config path
- Add resolveX402(flags) helper; use across analytics commands
- Remove @x402/svm dep; x402 now EVM/Base only
- README: collapse x402 docs to single WALLET_PRIVATE_KEY example

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Prior commit (d45e339) mixed x402 simplification into a login-focused PR.
Revert those x402/analytics/package changes and keep scope on login UX.

Also address real edge cases in login/logout:

- Accept real dashboard key prefixes. Validator required 'zk-' but
  dashboard issues 'zk_dev_…' / 'zk_prod_…'. Now accepts 'zk_' or 'zk-'.
- Mask API key input on manual-paste path (readSecret mask=true) so the
  key no longer echoes to the terminal scrollback.
- Guard interactive login against non-TTY stdin. CI/pipes now fail with
  'no_tty' pointing at --browser / --api-key / ZERION_API_KEY instead
  of hanging forever on readline.
- Warn on login flag collision (--api-key alongside --browser).
- logout surfaces a lingering ZERION_API_KEY env var so users aren't
  silently still authed via the shell environment.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
# Conflicts:
#	package-lock.json
#	package.json
… tabs

The real `open()` call was fire-and-forget, but on macOS
`open https://mock.invalid` succeeds and opens a blank tab anyway —
running `npm test` left several stale tabs in the user's browser.

Accept an `opener` argument (defaults to the `open` package) and pass
a no-op in tests.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Wallet setup shouldn't end at a just-created vault — the CLI is useless
without an API key. After `wallet create` and `wallet import` succeed,
if `getApiKey()` returns nothing, prompt the user to run `zerion login`
right there. Accepting opens the browser-based PKCE flow and auto-saves
the key; declining leaves a hint pointing at `zerion login` /
ZERION_API_KEY and continues to the agent-token offer.

- cli/lib/wallet/offer-login.js — new helper, skips silently when an
  API key already exists (config or env var), and in non-TTY contexts
  leaves a stderr note instead of blocking on readline.
- wallet create / import — call offerLogin() before offerAgentToken so
  the user lands in a usable state by the end of setup.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The banner ("zerion cli vX.Y.Z / Wallet analysis & autonomous trading…")
is useful on standalone `zerion login`, but noisy when `wallet create`
or `wallet import` chain into login after the wallet output.

Add a `quiet` flag to loginCmd that skips banner() on both the browser
and interactive paths, and pass it from offerLogin().

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
When offerLogin() runs inside `wallet create`/`wallet import` and the
user times out (5 min) or denies in the browser, loginCmd was calling
process.exit(1). The wallet was already saved to disk but the outer
flow — including the agent-token offer — died with a non-zero exit.

Pass `quiet: true` through to runBrowser(); on failure it rethrows
instead of exiting. offerLogin() now catches, prints a short skip
notice pointing at `zerion login`, and continues to agent-token setup.

Top-level `zerion login` behavior is unchanged (still exits non-zero
on failure).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
# Conflicts:
#	README.md
#	cli/README.md
#	cli/commands/wallet/create.js
#	cli/commands/wallet/import.js
#	cli/utils/wallet/offer-login.js
#	cli/zerion.js
#	package-lock.json
# Conflicts:
#	package-lock.json
npm ci requires utf-8-validate@5.0.10 entry that prior --ignore-scripts
install had skipped.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
zerion init now calls browserLogin() instead of prompting the user to
paste an API key. The browser flow opens dashboard.zerion.io with a
PKCE challenge and auto-saves the key once the user clicks Authorize.

README and the zerion base skill lead with `zerion login` / `zerion
logout`; copy-paste guidance is demoted to the non-interactive
fallback (`--api-key` / `ZERION_API_KEY`) for CI and scripts.

Drops the now-redundant `--browser` flag from `init` (browser is the
only interactive path) and the local openBrowser() / readSecret()
helpers.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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.

1 participant