Skip to content

feat(auth): brand the OAuth loopback callback pages + drop the Node floor to 22.12 [ENG-3406] - #8

Merged
Arlieeee merged 8 commits into
mainfrom
arliexubindu/eng-3406
Sep 22, 2026
Merged

Arlieeee merged 8 commits into
mainfrom
arliexubindu/eng-3406

Conversation

@Arlieeee

@Arlieeee Arlieeee commented Sep 22, 2026 •

Copy link
Copy Markdown
Contributor

1. Brand the OAuth loopback callback pages (ENG-3406)

The loopback callback used to return a 214-byte unstyled page. It now matches
the polished consent screens: Meshy wordmark, proper backdrop, distinct
success / canceled / failed states. Covered by tests/oauth.test.ts.

2. Drop the Node floor to 22.12 and stop naming the wrong npm package

engines.node was raised from >=20 to >=24 in 7edf056 as a side effect of a
dependency bump, not because any code needed Node 24. The only runtime gate in
the tree was doctor.ts's REQUIRED_NODE_MAJOR; @types/node@^22 typechecks
clean and the suite is 563/563 on Node 22. The real floor is commander@15's
>=22.12.0.

An overstated floor is not a warning, it is a silent downgrade. npm resolves an
unpinned install to the newest version whose engines the current runtime
satisfies — so npm i -g meshy-cli on Node 22 installed 0.1.3, the last
version declaring >=20, and printed nothing. Reproduced against the live
registry. Users read meshy --version as 0.1.3 and reported the CLI as stale;
agents read engines and reported it as incompatible.

Separately, the update notifier hardcoded npm i -g meshy-cli@latest. The same
tree is also published as @meshy-ai/cli, both declare the same meshy /
meshy-cli bins, and npm refuses to relink a bin owned by another package — so
an alias user who followed the hint got EEXIST: file already exists and no
upgrade. version.ts now exports PACKAGE_NAME from the same package.json
read, and the registry URL, upgrade command and notice text derive from it.

  • engines.node, .node-version, the CI check job and doctor's floor are all 22.12
  • tests/version.test.ts pins engines.node to the floor doctor enforces
  • smoke matrix is [22, 24, 26]: the floor, the current line, the next one
  • README documents the bin collision, how to switch, and the 0.1.3 trap
  • docs/skill-parity/decisions.md D-061 / D-062

Verification on Node 22.12.0 (the declared floor)

pnpm install --frozen-lockfile, typecheck, 563/563, build. Packed tarball
installed globally: no EBADENGINE, both bins start, doctor reports
local_ready: true / node=ok, doctor --check-api reaches the live API,
the no-credential path exits 3 with a valid error.code: "auth" envelope on
stdout, and the loopback OAuth server renders the new 16.8 KB branded cancel
page with the same v1 error envelope as the published build.

Version bumped to 0.3.2. Publishing is a separate manual release.yml run.

🤖 Generated with Claude Code

Arlieeee and others added 6 commits September 21, 2026 18:07
access_denied now renders its own "Connection canceled" page instead of the
generic error page, and that branch had no assertion. Also pin down that a
canceled page carries no error details — the cancel is deliberate, so there
is nothing to report back to the user.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The consent page lost its native ▶ disclosure marker, gained a card top
highlight and a tighter backdrop. The loopback page is meant to read as the
same surface, so it gets the same four changes: marker off with a CSS-drawn
chevron that turns on open, inset top highlight on the card, a narrower top
wash, and a radial grid mask centred on the card instead of a linear fade that
left a seam mid-page.

Still self-contained: the chevron is drawn with borders, so no icon font, no
extra markup and nothing new to fetch.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Follows the consent page back to its original backdrop: the wide top wash and
the plain top-to-bottom grid fade. The card-centred variant was tried and
rejected. The chevron disclosure and the card's top highlight stay.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…npm package

`engines.node` was raised from >=20 to >=24 in 7edf056 as a side effect of a
dependency bump, not because any code needed Node 24. The only runtime gate in
the tree was doctor.ts's REQUIRED_NODE_MAJOR; @types/node@^22 typechecks clean
and the suite is 563/563 on Node 22.22.0. The real floor is commander@15's
>=22.12.0.

An overstated floor is not a warning, it is a silent downgrade. npm resolves an
unpinned install to the newest version whose `engines` the current runtime
satisfies, so `npm i -g meshy-cli` on Node 22 installed 0.1.3 — the last version
declaring >=20 — without printing anything. Reproduced against the live
registry. Users then read `meshy --version` as 0.1.3 and reported the CLI as
stale; agents read `engines` and reported it as incompatible.

Separately, the update notifier hardcoded `npm i -g meshy-cli@latest`. The same
tree is also published as `@meshy-ai/cli`, both declare the same `meshy` /
`meshy-cli` bins, and npm refuses to relink a bin owned by another package — so
an alias user who followed the hint got `EEXIST: file already exists` and no
upgrade. version.ts now exports PACKAGE_NAME from the same package.json read,
and the registry URL, upgrade command and notice text all derive from it.

- engines.node, .node-version, the CI check job and doctor's floor are all 22.12
- tests/version.test.ts pins engines.node to the floor doctor enforces
- smoke matrix is [22, 24, 26]: the floor, the current line, the next one
- README documents the bin collision, how to switch, and the 0.1.3 trap
- decisions.md D-061 / D-062

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@Arlieeee Arlieeee changed the title feat(auth): brand the OAuth loopback callback pages [ENG-3406] feat(auth): brand the OAuth loopback callback pages + drop the Node floor to 22.12 [ENG-3406] Sep 22, 2026
Arlieeee and others added 2 commits September 22, 2026 19:36
…n when piped

`meshy balance` typed at a terminal answered with raw JSON braces. Surveyed the
CLIs on this machine: `gh release list`, `npm view`, `kubectl config
get-contexts` and `docker` all render a human shape by default and keep the
machine shape behind `--json` / `-o json` / `--format`. `aws` is the
counterexample, and it is configurable. A CLI whose default face is JSON braces
is the outlier.

This changes the default, not the contract. Untyped `--format` now resolves to
`pretty` when `process.stdout.isTTY` and `json` otherwise. Every agent, script,
pipe, redirect, command substitution and CI run reaches the CLI through
something that is not a TTY, so the bytes they read are byte-identical to
before; `--format json` and `--json` still force it, and SKILL.md already told
agents to pass it.

Two things had to be cleared first:

- commander carried `.default("json")` on the option, so "not typed" and
  `--format json` were indistinguishable. The default moved into
  `parseOutputFormat`, and `runtime.ts`'s duplicate `normalizeFormat` is deleted
  rather than taught the same rule a second time.
- legacy `-o <file>` renders through `emit()` with the same format, so a
  TTY-derived `pretty` would have silently landed in a file every caller reads
  back as JSON. `GlobalFlags.formatExplicit` records whether `--format` was
  really typed; untyped writes JSON to a file whatever the terminal shows.
  `--save-json` was never affected — it has its own writer.

Side effect, and the point: the update notifier's two channels finally separate.
`attachUpdateNotice` already skipped `pretty`, so a human gets one stderr line
instead of a `_notice` blob inside their output *and* the line; a pipe still
carries `_notice` in the JSON.

An empty collection now renders as `warnings: []` rather than a dangling key —
it is the default human face now, so the wart is worth the two lines.

Minor, not patch: the interactive default changed. Bumped 0.3.2 → 0.4.0.
decisions.md D-063 records the ceiling (renderPretty is a recursive key: value
dump; a real table renderer waits for a specific complaint).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…else

Follows the TTY-aware default: once `pretty` is what a person actually sees,
the output should look like the CLIs it sits next to. `src/internal/color.ts`,
~50 lines and no dependency — four SGR codes do not justify one.

The decision table, in order: FORCE_COLOR (on, unless `0`), NO_COLOR (off),
TERM=dumb (off), then whether the stream is a TTY. Both env vars are the
cross-ecosystem conventions and users expect them to work here.

Painted against the stream the text is going to, never a global flag:

- stdout, `pretty` only;
- stderr for the `error:` / `hint:` lines and the update hint, so
  `meshy ... | jq` still shows a red error while `2> log` stays clean;
- `json` / `ndjson` take the painter and ignore it — pinned by a test, because
  this is the one that would silently corrupt every agent reading stdout;
- anything written to a file renders unpainted: `--format pretty -o notes.txt`
  must not put control codes on disk. `render()` now defaults to the plain
  painter and only the two stdout call sites opt in.

Palette kept small: keys dim, `null` dim, and whole-value state words
(`ok`/`SUCCEEDED`/`true` green, `FAILED`/`error`/`false` red,
`PENDING`/`skipped`/`IN_PROGRESS` yellow). Matched on the entire value, case
insensitively, so a prompt reading "a failed robot" is never repainted.

Version stays 0.3.2: the machine contract is byte-identical, so this rides the
same patch as the Node floor fix. decisions.md D-064.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@Arlieeee
Arlieeee merged commit 1cb098a into main Sep 22, 2026
5 checks passed
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