Skip to content

fix(cli): simplify the install command to npx zerion-cli init (WLT-2083) - #113

Open
zerts wants to merge 1 commit into
mainfrom
cli-simplify-install-command-wlt-2083
Open

fix(cli): simplify the install command to npx zerion-cli init (WLT-2083)#113
zerts wants to merge 1 commit into
mainfrom
cli-simplify-install-command-wlt-2083

Conversation

@zerts

@zerts zerts commented Aug 12, 2026

Copy link
Copy Markdown
Collaborator

Summary

The advertised onboarding one-liner was npx -y zerion-cli init -y --browser — five tokens with two different -ys, which reads like a typo. Worse, the flags actively made onboarding worse: init -y skipped real authentication in favour of "go copy a key from dashboard.zerion.io and run zerion config set apiKey", and --browser merely opened that dashboard for the copy/paste. The interactive default already did proper browser OAuth with loopback capture and auto-saved the key.

The install command is now:

npx zerion-cli init

What changed

  • Docs: npx zerion-cli init is the documented install command — README install section + command table, the usage JSON (zerion --help), init's own HELP, and the two partner skills that carried the long form (uniswap-lp, uniswap-x402). The ~15 agent-facing npm install -g zerion-cli prerequisite lines are deliberately untouched — a non-interactive global install is the right thing for an agent to run.
  • init -y now authenticates for real: runs authenticateWithBrowser (the same path as zerion login --browser, which works without a TTY because approval happens out-of-band) instead of degrading to print-and-paste. -y means "don't ask me questions", not "don't authenticate".
  • Unattended bail-out: when stdin isn't a TTY (CI, piped, container) the auth step prints API-key instructions and exits 0, rather than blocking up to 5 minutes on a loopback callback nobody will complete.
  • Auth failure doesn't fail the install: a denied / timed-out / state-mismatch login prints the manual fallback, still installs skills, and exits 0 with steps[].auth.ok = false in the JSON. An auth hiccup shouldn't undo a good CLI + skills install.
  • --no-open added for remote/headless hosts (prints the authorize URL instead of opening a browser), mirroring zerion login --no-open.
  • --browser is accepted as a no-op, so npx -y zerion-cli init -y --browser keeps working verbatim. No flag was removed.

Drive-by bug fix

-y never worked. parseFlags only understands --flags (cli/utils/common/flags.js:38), so a bare -y landed in the positional args and was silently dropped — meaning the command we documented as non-interactive was already showing the interactive auth picker. Caught by driving init -y under a pty while verifying this PR. The router now lifts -y into flags.yes alongside the existing -h/-v shorthands and filters it out of positionals, and init reports the resolved mode as nonInteractive in its JSON output.

Known issues left alone (pre-existing, out of scope)

  1. Per-command --help is unreachable. cli/router.js:187 handles flags.help before dispatch, so zerion init --help prints the global usage JSON and every command's own HELP object is dead code. Updated init's HELP anyway so it's correct when this is fixed.
  2. Shorthands after a bare boolean flag get swallowed. --dry-run -y assigns "-y" as --dry-run's value, because parseFlags treats any non--- token as a candidate value. Affects -h/-v equally, and -g (documented in zerion setup --help) is dropped entirely for the same reason as -y was. Fixing this means changing the shared parser for every command, so it wants its own ticket.

Test plan

  • npm ci && npm test395/395 pass (adds 4: the -y shorthand, the non-TTY -y bail-out, legacy --browser acceptance, and the usage JSON no longer advertising the long form)
  • Verified zerion init -y --no-open under a real pty: goes straight to browser OAuth and prints the authorize URL, no picker
  • Verified zerion init (no flags) under a pty: still shows the auth-method picker
  • Verified zerion init -y without a TTY: prints API-key instructions, exits 0, spawns no browser

Closes WLT-2083

🤖 Generated with Claude Code

The advertised one-liner was `npx -y zerion-cli init -y --browser` — two
different `-y`s, and the flags actively made onboarding worse: `init -y`
skipped real auth in favour of "go copy a key from the dashboard", and
`--browser` just opened that dashboard for the copy/paste. The interactive
default already did proper browser OAuth with loopback capture.

- document `npx zerion-cli init` as the install command (README, usage JSON,
  the two partner skills that carried the long form)
- `init -y` now runs the real browser login (same path as `login --browser`)
  instead of degrading to print-and-paste
- without a TTY (CI, piped, container) the auth step prints API-key
  instructions instead of blocking on a 5-minute loopback callback
- a denied/timed-out login prints the manual fallback and still installs
  skills, exit 0 — an auth hiccup shouldn't undo a good install
- add `--no-open` for remote/headless hosts, mirroring `zerion login`
- `--browser` is accepted as a no-op, so the old one-liner still works

Also fixes the `-y` shorthand, which `parseFlags` never saw (it only handles
`--flags`), so it was silently dropped — the command documented as
non-interactive still showed the auth picker. The router now lifts it like
`-h`/`-v`, and `init` reports the resolved mode as `nonInteractive`.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@linear-code

linear-code Bot commented Aug 12, 2026

Copy link
Copy Markdown

WLT-2083

@zerts

zerts commented Aug 12, 2026

Copy link
Copy Markdown
Collaborator Author

✅ Loop review — iteration 1/5

Verdict: approved

Severity Area Finding
minor behavior "--no-open still prints "Opening your browser to authorize…"" (cli/utils/api/oauth.js:152)
minor tests Legacy --browser test asserts a branch reached before --browser is read (cli/tests/unit/cli/commands/init.test.mjs:118)
minor code yes-branch OAuth block duplicates runInteractiveAuth's oauth branch verbatim (cli/commands/init.js:110)
minor behavior init --browser is a pure no-op while login --browser skips the picker (cli/commands/init.js:24)

Summary

Reviewed f35d52bd708ae5dfc8712069cdef167f5c8b92c7 (PR #113, cli-simplify-install-command-wlt-2083) against WLT-2083 and the PRD comment on the issue. No human review threads exist on the PR, so the Linear description + PRD are the whole spec.

Every PRD decision landed and I verified each one live under a real pty, driving the OAuth loopback callback myself to exercise the approve / deny / CSRF paths. npx zerion-cli init is now the documented command in all five places the PRD scoped (README install section, README command table, cli/router.js usage JSON, and both partner skills); no stale npx -y zerion-cli init -y --browser remains outside intentional test comments. init -y on a TTY goes straight to browser OAuth and saves the key — no picker, no copy/paste. Without a TTY it prints API-key instructions and exits 0 without spawning a browser. A denied or state-mismatched login prints the manual fallback, still runs the skills step, and exits 0 with steps[].auth.ok = false, exactly as specified. --no-open genuinely suppresses the browser launch (verified with a PATH shim), and the legacy init -y --browser one-liner reaches OAuth verbatim. The drive-by -y router fix is real and correctly scoped: flags.yes has only two consumers (init, setup), no command validates unknown flags, so lifting -y globally can't auto-confirm anything elsewhere. The 24 agent-facing npm install -g zerion-cli lines are untouched per the PRD's out-of-scope list. 395/395 unit tests pass.

Findings are all minor: one misleading log line (pre-existing, in a file the diff doesn't touch, but newly promoted by the README), one weak test assertion, one duplicated block, one flag-semantics divergence that the HELP text is honest about. Nothing warrants a fix iteration.

Findings

[minor/behavior] --no-open still prints "Opening your browser to authorize…"

cli/utils/api/oauth.js:152 logs Opening your browser to authorize with the Zerion dashboard: unconditionally, before the if (open) openBrowser(...) guard on line 155. With --no-open the CLI says it is opening a browser and then doesn't.

Repro (K-no-open.err in the scratch dir): zerion init --no-install --no-skills -y --no-open under a pty prints

  Opening your browser to authorize with the Zerion dashboard:
    https://dashboard.zerion.io/oauth/authorize?...
  Waiting for you to approve in the browser… (Ctrl-C to cancel)

and open.log (a PATH shim for open) stays empty, while the same run without --no-open records one OPEN_CALLED: line (K-open-default). Expected: something like "Open this URL to authorize…" when open is false. Functionally harmless — the URL is printed right beneath it, so a headless user can still copy it — and pre-existing (zerion login --no-open reads the same way). Flagged because this PR is about install-command copy quality and newly tells remote/headless users in README.md:21 to reach for --no-open.

[minor/tests] Legacy --browser test asserts a branch reached before --browser is read

cli/tests/unit/cli/commands/init.test.mjs:118 ("accepts the legacy --browser flag as a no-op") runs init --no-install --no-skills -y --browser under spawnSync and asserts auth.reason === "non_tty". The non-TTY bail-out at cli/commands/init.js:100 returns before yes or any --browser handling is consulted, so this assertion holds no matter what the flag does — it would still pass if the no-op acceptance regressed. It's an unfalsifiable test rather than a wrong one; the genuinely load-bearing behavior (-y --browser reaching OAuth) can't be reached from spawnSync, which the PR body acknowledges. I covered it live instead (I-legacy-browser: authorize URL emitted, code accepted, apiKey written to the temp config, exit 0).

[minor/code] yes-branch OAuth block duplicates runInteractiveAuth's oauth branch verbatim

cli/commands/init.js:110-124 is a line-for-line copy of cli/utils/api/interactive-auth.js:75-85 — same authenticateWithBrowser call, same setConfigValue("apiKey", apiKey), same " ✓ Authenticated — API key saved to config" string, same { ok: false, method, reason: err.code || "oauth_failed", message } shape. Two copies of the persist-and-report contract will drift; extracting a shared runBrowserAuth({ log, open, dashboardUrl }) from interactive-auth.js would leave init calling one function. No behavioral defect — I verified both paths produce identical config writes and identical JSON.

[minor/behavior] init --browser is a pure no-op while login --browser skips the picker

cli/commands/init.js:24 documents --browser as "No-op — browser auth is the default now", and flags.browser is never read. So zerion init --browser (without -y) still shows the auth-method picker, whereas zerion login --browser (cli/commands/login.js:41) skips it. Verified live: J-browser-then-y renders the three-option picker. The same flag name meaning "skip the picker" on one command and nothing on another is a small trap for anyone reading across the two help outputs; having init treat --browser like login does would remove the divergence. Not a regression — pre-PR, --browser was also ignored on the interactive path — and the HELP text states the no-op plainly, so this is a naming preference, not a bug.

Verification log

  • Tests: npm ci clean; npm test395 pass / 0 fail / 0 skipped (88 suites, 7.6s), matching the PR's claim. Integration suites (cli/tests/integration/) not run — they need live API credentials and none of them touch init or router.
  • Static checks: flags.yes consumers grepped repo-wide → only cli/commands/init.js:175 and cli/commands/setup.js:51, so the router's global -y lift cannot auto-confirm a trade or other destructive command. No unknown-flag validation anywhere, so the injected flags.yes is inert elsewhere. openBrowser import correctly dropped from init.js with no remaining callsite. Stale-doc sweep for npx -y zerion-cli init -y --browser → zero hits outside test comments; npm install -g zerion-cli → 24 hits, all untouched per the PRD's out-of-scope list; plugin/agent manifests (plugins/, .agents/, .claude-plugin/, .codex-plugin/, gemini-extension.json, CONTEXT.md) carry no install command. No debug code or dead constants left in the diff.
  • Animation: no animation code in diff.
  • Live: CLI, not a web app — no dev server or Playwright. Driven with a Python pty.openpty() harness (drive.py) so process.stdin.isTTY is true, HOME pointed at a throwaway dir per run so nothing touched the real ~/.zerion/config.json, ZERION_API_KEY="", and --no-install --no-skills to avoid npm -g / npx skills add. I scraped each run's authorize URL from stderr and drove the loopback callback myself with synthetic code/error/state values — no real dashboard approval, no network mutation. Scratch dir: /tmp/loop-review-oSQMVA (per-run .err/.out transcripts).
    • --help usage JSON → setup.init = npx zerion-cli init, plus new init -y and init --no-open entries; old init -y --browser key gone.
    • non-TTY init -yauth.reason: "non_tty", fallback instructions printed, exit 0, no browser spawned.
    • C-yes-approve: pty init -y --no-open → authorize URL, no picker; synthetic ?code=zk_live_test_key_123✓ Authenticated, auth: {ok:true, method:"oauth"}, key written to temp config, nonInteractive: true, exit 0. The core spec point.
    • D-interactive-picker: pty init (no -y) → three-option auth picker with browser pre-selected; Esc → user_cancelled, nonInteractive: false, exit 0. Interactive default preserved.
    • F-yes-denied: ?error=access_denied! Browser authorization failed, fallback printed, skills step still reached, exit 0, auth: {ok:false, reason:"oauth_denied"}. Matches the PRD's "auth failure doesn't fail the install" decision exactly.
    • G-state-mismatch: ?code=zk_evil&state=WRONG → callback 400, oauth_state_mismatch, no key persisted (config.json absent), exit 0. CSRF guard intact through the new path.
    • I-legacy-browser: init -y --browser --no-open → OAuth, key saved. Old one-liner works verbatim.
    • K-open-default / K-no-open: PATH shim for open → one OPEN_CALLED line by default, zero with --no-open.
    • setup skills -y --dry-run and setup -y skills --dry-run--yes present in the resolved command (the -y lift also fixes flag-before-positional ordering).
    • Known pre-existing parser hazard confirmed and not reported as a finding, since both the PRD and PR body explicitly scope it out: a bare boolean flag swallows a following shorthand, so setup skills --dry-run -y drops -y (H3, --yes absent) and init --browser -y leaves nonInteractive: false (J-browser-then-y). Neither is a regression — -y never worked in any order before this PR — and neither affects the documented argument orders.
  • Console/network: clean. No stack traces or unhandled rejections in any run; every invocation exited 0 as designed. The only outbound traffic was my own loopback callbacks to 127.0.0.1; the dashboard.zerion.io authorize URL was never fetched.

Posted by ai-runner.

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