agent-auth

Installation
SKILL.md
Contains Shell Commands

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)

  1. DON'T: scrape token values to copy-paste (cat ~/.config/.../config.json) — none exist until login. (Checking whether CI/BROWSER/the provider's *_TOKEN are set is fine — those suppress the popup; if one is actually set, clear it off the login command only with env -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 *_TOKEN is the strongest suppressor — the CLI short-circuits straight to token auth and skips the human browser flow entirely — so it must come off alongside CI/BROWSER. Apply the same env -u <PROVIDER_TOKEN> to the verify step too (env -u <PROVIDER_TOKEN> <cli> whoami): env -u is scoped to one command, so a session-wide token still gets read by a later whoami/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 (or unset it in the shell for the duration). Don't reach for CI= BROWSER= <cli> login: an empty BROWSER= 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 emptying BROWSER can 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.
  2. 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. bare vercel deploys, bare workos scaffolds). 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 the npx step entirely: install the documented brew/curl binary instead, but still --version-check that binary and watch for a stale older copy (brew render ships 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 the npx wrapper never fetched its native binary (npx -y @railway/cli does this → npm i -g <cli> --foreground-scripts, or brew). A blank whoami/logout means the same — NOT logged-out. Never sudo to install or fix a CLI — on shared Macs brew often 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 vendor curl … | bash that 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.
  3. 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 — but run_in_background is still better, since it lets you surface the device code before the human approves. Don't pass popup-suppressing flags (--no-open, BROWSER=none):
    npx -y <pkg>@latest login        # popup opens immediately; call returns on success (timeout ~300s)
    
    Backgrounded (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-level sleep is blocked here; /bin/sleep or a sleep inside a loop works (or the Monitor tool).
  4. 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 a secret=/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's localhost, 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 a user_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 you send it 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 an Unauthorized token. 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).
  5. Wait for the success line — or just the process exiting (some CLIs print none) — then verify (whoami/status). Don't blind-tail the 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 both 0 and 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. An Unauthorized/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.

Installs
3
First Seen
Jul 21, 2026