What #
node dist/index.js --login <handle> can now be completed on a headless
machine over SSH. After printing the authorize URL, the flow also accepts
the OAuth callback URL pasted into the terminal (copied from the browser's
address bar on whatever machine ran the browser), racing the existing
loopback listener — whichever arrives first wins; the flow settles exactly
once.
- Auto-enabled on interactive terminals (TTY), local or SSH
- Forced with
--pasteorLEXIDRAW_MCP_LOGIN_PASTE=1|true|yes(scripted drivers over open pipes; host-spawned pipes without the flag keep byte-identical behavior) - Pasted
stateis checked againstFileStateStorebefore acceptance, mirroring the loopback handler's fail-fast;client.callbackremains the security authority for the exchange - EOF ("stdin closed") degrades gracefully back to loopback + timeout
LEXIDRAW_MCP_LOGIN_TIMEOUT_MSoverrides the 5-minute wait (positive integer, ≤ 2147483647); five invalid pastes abort the flow- Denial redirects (
error=access_denied, nocode) surface a clear "Authorization failed" error
Why #
Over SSH the browser runs on the user's laptop; the authorization server
redirects the laptop's browser to the laptop's 127.0.0.1:<port>, where
nothing listens. The user sees "connection refused" — but the address bar
holds the full callback URL. The pasted code is exchanged server-to-server
from the headless machine, so no local browser is needed.
Tests #
26 new tests in tests/cli.test.ts: race in both directions, per-branch
hints and re-prompting, invalid-paste cap, denial, EOF fallback, timeout
override + ConfigError paths, parsePastedCallback units, and
makeStdinPasteReader lifecycle over a PassThrough. Full suite 138 passed;
tsc --noEmit clean; npm run build succeeds.
Docs #
README (headless/SSH paragraph + env table rows) and CLAUDE.md (loopback auth bullet) updated. README hunks also fold in the pending Lexidraw rename.