Skip to content

feat(cli): enable auth by default and auto-authorize --open via OTP#124

Merged
antfu merged 2 commits into
mainfrom
feat/cli-open-auto-auth
Jul 22, 2026
Merged

feat(cli): enable auth by default and auto-authorize --open via OTP#124
antfu merged 2 commits into
mainfrom
feat/cli-open-auto-auth

Conversation

@antfubot

@antfubot antfubot commented Jul 22, 2026

Copy link
Copy Markdown
Collaborator

Summary

  • Every built-in plugin's standalone CLI now gates its dev server behind devframe's interactive OTP handshake by default. code-server, git, inspect, messages, terminals, and data-inspector previously opted out with auth: false/auth: options.auth ?? false; og and a11y already defaulted on. Each plugin keeps an auth option (and data-inspector's CLI a --no-auth flag) to opt back out for single-user/CI use.
  • To keep that gate from adding friction, --open now asks the resolved auth handler to rewrite the launched URL before opening it. createInteractiveAuth implements this by embedding the current OTP (via buildOtpAuthUrl), so the tab the CLI opens lands already authenticated instead of prompting for a code.
  • Adds DevframeAuthHandler.buildOpenUrl (optional) as the seam and wires it through maybeOpenBrowser in the dev adapter; a handler that omits it (e.g. a pre-shared-token scheme) leaves the opened URL unchanged.
  • Fixes an auth-bootstrap race exposed by turning auth on by default: connectDevframe()/getDevframeRpcClient() kicks off the handshake (stored token → URL OTP → native-prompt fallback) without waiting for it, so a caller's very first RPC calls (e.g. a component's onMount, fired the instant the client is available) could race that still-in-flight sequence — the socket was already open, so the transport sent them straight through, and the server rejected them with DF0036 even though the exact same call succeeds moments later. rpc.call/callOptional/callEvent now hold anything issued before the first bootstrap attempt settles and release it (or fail fast, if the handshake ultimately failed) once it does. This lives once in devframe's client RPC core, so every plugin's standalone/hosted SPA gets it automatically — no plugin-level changes needed.
  • Updates the standalone-CLI/security/definition/client docs and the devframe skill to describe the new defaults and the call-gating guarantee, plus the tsnapi API snapshots for the plugins whose public Options type gained auth?: boolean.

Verification

  • pnpm build && pnpm lint && pnpm typecheck all pass.
  • pnpm exec vitest run — 834/834 tests pass across every package/plugin, including:
    • two dev.test.ts cases asserting the OTP is embedded in the opened URL when auth is on, and that the plain origin opens when auth: false.
    • three new rpc-auth-gate.test.ts cases proving call/callOptional/callEvent hold until the auth bootstrap settles, then release (verified the tests fail without the fix and pass with it).

This PR was created with the help of an agent.

Every built-in plugin's standalone CLI now gates the dev server behind
devframe's interactive OTP handshake by default (code-server, git,
inspect, messages, terminals, data-inspector previously opted out;
og/a11y already defaulted on). Each plugin keeps an `auth` option to
opt back out for single-user/CI use.

To keep that gate from adding friction, `--open` now asks the resolved
auth handler to rewrite the launched URL — the interactive handler
embeds the current OTP (via buildOtpAuthUrl), so the opened tab
authenticates automatically instead of prompting for a code.

Adds DevframeAuthHandler.buildOpenUrl (optional) as the seam, wires it
through maybeOpenBrowser in the dev adapter, and updates docs/skill
guidance + tsnapi snapshots accordingly.

Co-authored-by: opencode <noreply@opencode.ai>
@netlify

netlify Bot commented Jul 22, 2026

Copy link
Copy Markdown

Deploy Preview for devfra ready!

Name Link
🔨 Latest commit c53bbc8
🔍 Latest deploy log https://app.netlify.com/projects/devfra/deploys/6a6067c809d2e20008ccf274
😎 Deploy Preview https://deploy-preview-124--devfra.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.
🤖 Make changes Run an agent on this branch

To edit notification comments on pull requests, go to your Netlify project configuration.

connectDevframe()/getDevframeRpcClient() kicks off the auth bootstrap
(stored token, then the URL's magic-link OTP, then a native-prompt
fallback) without waiting for it, so it can return the client right
away. A caller's very first RPC calls — e.g. a component's onMount,
fired the instant connectDevframe() resolves — raced that still
in-flight sequence: the socket was already open, so the transport sent
them straight through, and the server rejected them with DF0036
because trust hadn't landed yet, even though the exact same call would
succeed a moment later once the handshake completed.

rpc.call/callOptional/callEvent now hold anything issued before the
first bootstrap attempt settles and release it (or fail fast, if the
handshake ultimately failed) once it does. This lives once in
devframe's client RPC core, so every plugin's standalone/hosted SPA
gets it automatically — no plugin-level changes needed.

Co-authored-by: opencode <noreply@opencode.ai>
@antfu
antfu merged commit fd23f6b into main Jul 22, 2026
11 of 12 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.

2 participants