agent-auth
This skill contains shell command directives (!`command`) that may execute system commands. Review carefully before installing.
Logging a human into a CLI
Goal: make the OAuth browser popup open automatically, let the human click Approve, and let the CLI store the token itself. You don't copy tokens; you don't drive the browser.
This is automatic browser login — for the case where there's no token yet and a real human is here to sign in. That's the whole premise. Assume a human is at the keyboard: they'll click Approve in the browser, confirm any device code (against what the CLI printed), and can relay a code or error back to you as text — so when you're stuck or need a code, ask them. You make the popup appear and react to the CLI; they approve. Some CLIs now detect an agent and try to refuse the browser, demanding a token instead (Supabase --agent, the JSON/"agent mode" trend) — override that (--agent no, etc.) to get the human's browser flow. A *_TOKEN/--token env is only the last-resort fallback when there's genuinely no human/browser; never the preferred route here — except 🔑 API-key-only CLIs (Clerk) that have no browser login at all, where the key is the auth (see the per-provider table for which).
Fast-path (do this every time)
- DON'T: scrape token values to copy-paste (
cat ~/.config/.../config.json) — none exist until login. (Checking whetherCI/BROWSER/the provider's*_TOKENare set is fine — those suppress the popup; if one is actually set, clear it off the login command only withenv -u CI -u BROWSER -u <PROVIDER_TOKEN> <cli> login, which truly unsets them. Include the provider's own token var (<PROVIDER_TOKEN>= the last column of the providers table —VERCEL_TOKEN,NETLIFY_AUTH_TOKEN,SANITY_AUTH_TOKEN, …): a set*_TOKENis the strongest suppressor — the CLI short-circuits straight to token auth and skips the human browser flow entirely — so it must come off alongsideCI/BROWSER. Apply the sameenv -u <PROVIDER_TOKEN>to the verify step too (env -u <PROVIDER_TOKEN> <cli> whoami):env -uis scoped to one command, so a session-wide token still gets read by a laterwhoami/status, which would then report the env token's identity and pass even if the browser login stored nothing — a false "logged in." Keep the token cleared across the whole login-and-verify sequence (orunsetit in the shell for the duration). Don't reach forCI= BROWSER= <cli> login: an emptyBROWSER=is itself a popup-suppressor on some CLIs, so you'd re-introduce the very thing you're clearing — and only bother clearing at all if it's genuinely set, since needlessly emptyingBROWSERcan suppress an otherwise-fine auto-open.) DON'T drive the browser (xdotool, screenshot the X root, CDP, browser-agent logs). DON'T heavy-install an SDK (npm i <pkg>, 1000+ pkgs / ~2 min) just to authenticate. DON'T reach for a provider's MCP auth tool (e.g.mcp__sanity__authenticate) — drive the CLI's own login. - Get the CLI light & current:
npx -y <cli-package>@latest --version(always pair it with--version/a subcommand — never run the bare package: some default binaries do work, e.g. bareverceldeploys, bareworkosscaffolds). This also dodges a stale global/brew binary already on PATH, which can have a broken login (an old Homebrew Railway v4.x fails its callback). Binary-only CLIs (flyctl, render, daytona, auth0 — no npm package) skip thenpxstep entirely: install the documentedbrew/curlbinary instead, but still--version-check that binary and watch for a stale older copy (brewrenderships v1.x while v2.x exists). Sanity-check<cli> --version 2>&1 | grep -v 'npm warn'— it MUST print a real version. Blank/error = a stale binary shadowing it (brew upgrade/full path) or thenpxwrapper never fetched its native binary (npx -y @railway/clidoes this →npm i -g <cli> --foreground-scripts, or brew). A blankwhoami/logoutmeans the same — NOT logged-out. Neversudoto install or fix a CLI — on shared Macsbrewoften fails (/opt/homebrew/Cellar is not writable) and sudo isn't available; fall back to a user-level install (the CLI's GitHub-release Darwin tarball run from the scratchpad, or a vendorcurl … | bashthat lands in~/), then invoke it by full path — a~/.<tool>install isn't on a bash call's PATH. Don't retry the same install with sudo. - Run the default login ONCE and keep that one process alive until the human approves. The login starts a local callback server (e.g.
localhost:4321); if its process dies — or another app already holds that port — the human's click 404s with the other app's error (check it's free first:lsof -i :<port>). So run it foreground with a long timeout, or as a persistent background job (run_in_background,nohup … & disown) — for a localhost-callback CLI never use a bare… &that ends with the shell call (that kills the callback server). Device-code / remote-poll CLIs (Render, Fly, Netlify) have no local server, so a bare&is harmless there — butrun_in_backgroundis still better, since it lets you surface the device code before the human approves. Don't pass popup-suppressing flags (--no-open,BROWSER=none):
Backgrounded (npx -y <pkg>@latest login # popup opens immediately; call returns on success (timeout ~300s)run_in_background) returns immediately with no inline output — poll its output file (bounded loop) for the URL then the success line / process-exit. Don't add your own>redirect: the harness already captures stdout to the task output file, so a redirect leaves that file empty and you'll poll the wrong place and see nothing. Only a bare top-levelsleepis blocked here;/bin/sleepor asleepinside a loop works (or the Monitor tool). - The popup opens in the human's browser automatically. Tell them: "approve the login in the window that just opened." If a CLI prints a URL but doesn't open it itself (Stripe's
--non-interactive, 🔗 print-URL rows),open '<url>'(macOS) /xdg-open '<url>'(Linux) it yourself on a local host so the human still gets the auto-open — don't just paste it and make them open it. (Open only the human-facing auth URL — never asecret=/poll URL; see the Stripe row.) If it didn't open (headless), surface the URL — except for localhost-callback CLIs (Sanity:4321, wrangler:8976, …): on a remote/headless host the human's browser can't reach this box'slocalhost, so the pasted URL never completes the callback. There, skip the popup and use the token/env path instead. For device-code flows (Vercel, Stripe, Auth0, Render, WorkOS, Convex) the CLI prints auser_code— read it from the output and echo it to the human so they can confirm the match before approving. Spell out the human's exact action — never leave them guessing. Say "approve in the window that just opened," and if a code is shown, state which kind it is: a visual match to confirm against the terminal then approve in the browser (device-code flows — Vercel/Stripe/WorkOS/Render: nothing to send back), or a paste-back where the browser prints a code the human must enter into the CLI (Supabase: "Enter this verification code in Supabase CLI") — there you ask the human to relay it and yousendit into the session. Silently polling while the human wonders what to do is a failure. Tell them to sign into the right account in the browser first — a popup approved under the wrong account still "logs in" but stores anUnauthorizedtoken. Send this as you launch the login command — don't block waiting for a reply (most of the time they're already on the right account): localhost-callback / poll flows (wrangler, Netlify) can complete the instant they approve, before a later message reaches them, so prime them up front rather than warning after. When you ask for a relayed code, ask for "the code, or any error text on the page" — they may be staring at an error, and you can't see screenshots (if they send an image, say so and ask them to type it). - Wait for the success line — or just the process exiting (some CLIs print none) — then verify (whoami/status). Don't blind-
tailthe log — the identity line is usually first (grep for it); spinner/device-code logs flood with ANSI frames, so trust the verify command's identity output, not a log grep. Don't trust the verify exit code alone either — some (netlify status) return an unreliable exit code outside a linked project even when logged in (seen both0and non-zero); parse stdout for the identity block. Verify the identity, not just exit-0 — and don't say "logged in" until the verify command prints it. AnUnauthorized/empty verify right after a success line almost always means the human approved in the wrong account (or the provider hit a token cap), NOT a stale token — ask which; don't dive into a keychain/config diagnostic loop.
Run the login once and wait — don't fire it repeatedly. It opens a local callback server and blocks until the human approves; spawning extra logins because you didn't see instant success just leaves competing servers behind. One login, keep it alive, wait for the success line.