devframe 0.8.0 → 0.8.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (54) hide show
  1. package/dist/adapters/build.d.mts +1 -1
  2. package/dist/adapters/build.mjs +2 -2
  3. package/dist/adapters/cac.d.mts +1 -1
  4. package/dist/adapters/cac.mjs +1 -1
  5. package/dist/adapters/cli.d.mts +1 -1
  6. package/dist/adapters/cli.mjs +1 -1
  7. package/dist/adapters/dev.d.mts +15 -2
  8. package/dist/adapters/dev.mjs +1 -1
  9. package/dist/adapters/embedded.d.mts +1 -1
  10. package/dist/adapters/mcp.d.mts +11 -5
  11. package/dist/adapters/mcp.mjs +1 -1
  12. package/dist/{cac-B-R_tDGJ.mjs → cac-D4_9ok0z.mjs} +1 -1
  13. package/dist/cli/main.mjs +4 -3
  14. package/dist/client/index.d.mts +37 -8
  15. package/dist/client/index.mjs +41 -15
  16. package/dist/constants.d.mts +13 -6
  17. package/dist/constants.mjs +13 -6
  18. package/dist/{context-C9Cgm1hP.mjs → context-CBg8iRbQ.mjs} +3 -2
  19. package/dist/{context-7BbaIUSI.d.mts → context-DoSozfvx.d.mts} +1 -1
  20. package/dist/{revoke-BtQDKTp7.mjs → crypto-token-XCqTSMg9.mjs} +1 -35
  21. package/dist/{dev-D4M7Veo7.mjs → dev-Bc49u3qg.mjs} +7 -5
  22. package/dist/{devframe-Dsjn_Xtq.d.mts → devframe-mQ4qZAuo.d.mts} +10 -1
  23. package/dist/{diagnostics-B5-qHeqD.mjs → diagnostics-D7wM2cwM.mjs} +4 -0
  24. package/dist/{fetch-BZyK4v6W.mjs → fetch-BKaD0Vb3.mjs} +7 -6
  25. package/dist/helpers/vite.d.mts +15 -14
  26. package/dist/helpers/vite.mjs +8 -8
  27. package/dist/{host-h3-Kz7t5Xab.mjs → host-h3-D2hYZvDK.mjs} +2 -2
  28. package/dist/{http-BghOqfTD.mjs → http-C4T68YV6.mjs} +1 -1
  29. package/dist/{index-CeBbry0R.d.mts → index-b64Uuhy2.d.mts} +11 -6
  30. package/dist/index.d.mts +2 -2
  31. package/dist/{instance-registry-D6fxYu36.mjs → instance-registry-BnnA2Eoh.mjs} +1 -1
  32. package/dist/node/auth.d.mts +2 -2
  33. package/dist/node/auth.mjs +2 -2
  34. package/dist/node/hub-internals.d.mts +2 -2
  35. package/dist/node/hub-internals.mjs +1 -1
  36. package/dist/node/index.d.mts +23 -3
  37. package/dist/node/index.mjs +5 -5
  38. package/dist/recipes/common-rpc-functions.d.mts +1 -1
  39. package/dist/recipes/interactive-auth.d.mts +2 -2
  40. package/dist/recipes/interactive-auth.mjs +11 -3
  41. package/dist/revoke-DZbJ7Cl0.mjs +35 -0
  42. package/dist/rpc/transports/ws-server.d.mts +2 -2
  43. package/dist/rpc/transports/ws-server.mjs +75 -7
  44. package/dist/{server-CcAPxuoT.mjs → server-CD8XmBXl.mjs} +25 -6
  45. package/dist/{server-VLQJouOO.d.mts → server-UHWae43Q.d.mts} +21 -3
  46. package/dist/{state-WX3HT5a3.mjs → state-BeUHDDjk.mjs} +13 -6
  47. package/dist/{storage-Dzoc3NVC.mjs → storage-CTNVbuFB.mjs} +1 -1
  48. package/dist/types/index.d.mts +2 -2
  49. package/dist/utils/events.d.mts +1 -1
  50. package/dist/utils/shared-state.d.mts +1 -1
  51. package/dist/utils/streaming-channel.d.mts +1 -1
  52. package/dist/{ws-server-D_Vjtums.d.mts → ws-server-uKQcClcJ.d.mts} +36 -7
  53. package/package.json +2 -2
  54. package/skills/devframe/SKILL.md +3 -2
@@ -1,4 +1,4 @@
1
- import { r as DevframeDefinition } from "../devframe-Dsjn_Xtq.mjs";
1
+ import { r as DevframeDefinition } from "../devframe-mQ4qZAuo.mjs";
2
2
  //#region src/adapters/build.d.ts
3
3
  interface CreateBuildOptions {
4
4
  /** Output directory. Defaults to `dist-static`. */
@@ -3,8 +3,8 @@ import { n as colors } from "../diagnostics-reporter-CsIG85Q5.mjs";
3
3
  import { DEVFRAME_CONNECTION_META_FILENAME, DEVFRAME_RPC_DUMP_DIRNAME, DEVFRAME_RPC_DUMP_MANIFEST_FILENAME } from "../constants.mjs";
4
4
  import { n as strictJsonStringify } from "../serialization-C8Mnw9hK.mjs";
5
5
  import { n as structuredCloneStringify } from "../structured-clone-CbAV5rFI.mjs";
6
- import { n as createHostContext, t as createH3DevframeHost } from "../host-h3-Kz7t5Xab.mjs";
7
- import { t as diagnostics } from "../diagnostics-B5-qHeqD.mjs";
6
+ import { n as createHostContext, t as createH3DevframeHost } from "../host-h3-D2hYZvDK.mjs";
7
+ import { t as diagnostics } from "../diagnostics-D7wM2cwM.mjs";
8
8
  import { n as resolveBasePath } from "../_shared-bWRzeSa0.mjs";
9
9
  import { existsSync } from "node:fs";
10
10
  import { dirname, resolve } from "pathe";
@@ -1,4 +1,4 @@
1
- import { Ft as InferCliFlags, It as defineCliFlags, Lt as parseCliFlags, Pt as CliFlagsSchema, r as DevframeDefinition } from "../devframe-Dsjn_Xtq.mjs";
1
+ import { Ft as InferCliFlags, It as defineCliFlags, Lt as parseCliFlags, Pt as CliFlagsSchema, r as DevframeDefinition } from "../devframe-mQ4qZAuo.mjs";
2
2
  import { CAC } from "cac";
3
3
  import { H3 } from "h3";
4
4
  //#region src/adapters/cac.d.ts
@@ -1,2 +1,2 @@
1
- import { n as defineCliFlags, r as parseCliFlags, t as createCac } from "../cac-B-R_tDGJ.mjs";
1
+ import { n as defineCliFlags, r as parseCliFlags, t as createCac } from "../cac-D4_9ok0z.mjs";
2
2
  export { createCac, defineCliFlags, parseCliFlags };
@@ -1,4 +1,4 @@
1
- import { Ft as InferCliFlags, It as defineCliFlags, Lt as parseCliFlags, Pt as CliFlagsSchema } from "../devframe-Dsjn_Xtq.mjs";
1
+ import { Ft as InferCliFlags, It as defineCliFlags, Lt as parseCliFlags, Pt as CliFlagsSchema } from "../devframe-mQ4qZAuo.mjs";
2
2
  import { CacHandle, CreateCacOptions, createCac } from "./cac.mjs";
3
3
  //#region src/adapters/cli.d.ts
4
4
  /** @deprecated Use `createCac` from `devframe/adapters/cac` instead. */
@@ -1,4 +1,4 @@
1
- import { n as defineCliFlags, r as parseCliFlags, t as createCac } from "../cac-B-R_tDGJ.mjs";
1
+ import { n as defineCliFlags, r as parseCliFlags, t as createCac } from "../cac-D4_9ok0z.mjs";
2
2
  //#region src/adapters/cli.ts
3
3
  /** @deprecated Use `createCac` from `devframe/adapters/cac` instead. */
4
4
  const createCli = createCac;
@@ -1,5 +1,7 @@
1
- import { _ as ConnectionMeta, d as McpRouteOptions, p as DevframeAuthHandler, r as DevframeDefinition, u as DevframeWsOptions } from "../devframe-Dsjn_Xtq.mjs";
2
- import { n as StartedServer } from "../server-VLQJouOO.mjs";
1
+ import { W as DevframeNodeRpcSession, _ as ConnectionMeta, d as McpRouteOptions, p as DevframeAuthHandler, r as DevframeDefinition, u as DevframeWsOptions } from "../devframe-mQ4qZAuo.mjs";
2
+ import { n as DevframeNodeRpcSessionMeta } from "../ws-server-uKQcClcJ.mjs";
3
+ import { n as StartedServer } from "../server-UHWae43Q.mjs";
4
+ import { Peer } from "crossws";
3
5
  import { H3 } from "h3";
4
6
  //#region src/adapters/dev.d.ts
5
7
  interface CreateDevServerOptions {
@@ -68,6 +70,17 @@ interface CreateDevServerOptions {
68
70
  * {@link McpRouteOptions}.
69
71
  */
70
72
  mcp?: boolean | McpRouteOptions;
73
+ /**
74
+ * Called once per new WS connection, right after its session is created.
75
+ * Forwarded verbatim to the underlying `startHttpAndWs`.
76
+ */
77
+ onPeerConnect?: (peer: Peer, session: DevframeNodeRpcSession) => void;
78
+ /**
79
+ * Called once per closed WS connection, right after its session's
80
+ * disconnect bookkeeping runs. Forwarded verbatim to the underlying
81
+ * `startHttpAndWs`.
82
+ */
83
+ onPeerDisconnect?: (peer: Peer, meta: DevframeNodeRpcSessionMeta) => void;
71
84
  /**
72
85
  * Called once the WS server is bound. Devframe stays headless
73
86
  * otherwise — wire this if you want a startup banner.
@@ -1,2 +1,2 @@
1
- import { n as resolveDevServerPort, r as resolveMcpConnectionMeta, t as createDevServer } from "../dev-D4M7Veo7.mjs";
1
+ import { n as resolveDevServerPort, r as resolveMcpConnectionMeta, t as createDevServer } from "../dev-Bc49u3qg.mjs";
2
2
  export { createDevServer, resolveDevServerPort, resolveMcpConnectionMeta };
@@ -1,4 +1,4 @@
1
- import { b as DevframeNodeContext, r as DevframeDefinition } from "../devframe-Dsjn_Xtq.mjs";
1
+ import { b as DevframeNodeContext, r as DevframeDefinition } from "../devframe-mQ4qZAuo.mjs";
2
2
  //#region src/adapters/embedded.d.ts
3
3
  interface CreateEmbeddedOptions {
4
4
  /** Target context the devframe is registered into. Required. */
@@ -1,4 +1,4 @@
1
- import { b as DevframeNodeContext, r as DevframeDefinition } from "../devframe-Dsjn_Xtq.mjs";
1
+ import { b as DevframeNodeContext, r as DevframeDefinition } from "../devframe-mQ4qZAuo.mjs";
2
2
  import "@modelcontextprotocol/server";
3
3
  //#region src/adapters/mcp/build-server.d.ts
4
4
  interface CreateMcpServerOptions {
@@ -47,7 +47,12 @@ interface CreateMcpFetchHandlerOptions {
47
47
  exposeSharedState: boolean | ((key: string) => boolean);
48
48
  /**
49
49
  * Origin allow-list beyond the loopback default. `false` disables the
50
- * origin gate entirely. Default: loopback-only (mirrors the WS transport).
50
+ * origin gate entirely. Default: loopback-only.
51
+ *
52
+ * Unlike the WS transport, the MCP route does **not** allow `Origin`-less
53
+ * requests: a route-based endpoint is reachable by any local process, so a
54
+ * request must carry an `Origin` that passes the gate. Native clients
55
+ * (e.g. `devframe connect`) send their loopback origin explicitly.
51
56
  */
52
57
  allowedOrigins?: readonly string[] | false;
53
58
  }
@@ -71,9 +76,10 @@ interface McpFetchHandler {
71
76
  * and MCP server (built from the shared, live `ctx` via
72
77
  * `buildMcpServerFromContext`), correlated by the `Mcp-Session-Id` header: an
73
78
  * `initialize` POST spins up a session; later requests route to it; a `DELETE`
74
- * (or client disconnect) tears it down. The origin gate applies devframe's
75
- * loopback-default DNS-rebinding protection (identical semantics to the WS
76
- * upgrade's `isAllowedOrigin`).
79
+ * (or client disconnect) tears it down. The origin gate guards every request:
80
+ * loopback-default DNS-rebinding protection that — unlike the WS upgrade's
81
+ * `isAllowedOrigin` — also rejects `Origin`-less requests, so a route-based
82
+ * endpoint isn't reachable by an arbitrary local process.
77
83
  *
78
84
  * @experimental
79
85
  */
@@ -1,2 +1,2 @@
1
- import { n as createMcpServer, t as createMcpFetchHandler } from "../fetch-BZyK4v6W.mjs";
1
+ import { n as createMcpServer, t as createMcpFetchHandler } from "../fetch-BKaD0Vb3.mjs";
2
2
  export { createMcpFetchHandler, createMcpServer };
@@ -1,6 +1,6 @@
1
1
  import { n as colors } from "./diagnostics-reporter-CsIG85Q5.mjs";
2
2
  import { createBuild } from "./adapters/build.mjs";
3
- import { n as resolveDevServerPort, t as createDevServer } from "./dev-D4M7Veo7.mjs";
3
+ import { n as resolveDevServerPort, t as createDevServer } from "./dev-Bc49u3qg.mjs";
4
4
  import process from "node:process";
5
5
  import cac$1 from "cac";
6
6
  //#region src/adapters/flags.ts
package/dist/cli/main.mjs CHANGED
@@ -1,5 +1,5 @@
1
- import { t as diagnostics } from "../diagnostics-B5-qHeqD.mjs";
2
- import { n as probeDevframeOrigin, t as listLiveDevframeInstances } from "../instance-registry-D6fxYu36.mjs";
1
+ import { t as diagnostics } from "../diagnostics-D7wM2cwM.mjs";
2
+ import { n as probeDevframeOrigin, t as listLiveDevframeInstances } from "../instance-registry-BnnA2Eoh.mjs";
3
3
  import { t as toAgentToolName } from "../agent-tool-name-C3b5vEwJ.mjs";
4
4
  import { Diagnostic } from "nostics";
5
5
  import process from "node:process";
@@ -200,7 +200,8 @@ async function call(sdk, options, args) {
200
200
  });
201
201
  }
202
202
  async function withInstanceClient(sdk, url, fn) {
203
- const transport = new sdk.StreamableHTTPClientTransport(new URL(url));
203
+ const origin = new URL(url).origin;
204
+ const transport = new sdk.StreamableHTTPClientTransport(new URL(url), { requestInit: { headers: { origin } } });
204
205
  const client = new sdk.Client({
205
206
  name: "devframe-connect",
206
207
  version: "0.0.0"
@@ -1,4 +1,4 @@
1
- import { $ as DevframeRpcServerFunctions, F as ScopedSharedStates, I as SettingsForNamespace, J as RpcSharedStateHost, N as ScopedRpcFn, O as DevframeSettings, P as ScopedServerFunctions, Q as DevframeRpcClientFunctions, _ as ConnectionMeta, at as StreamReader, ht as SharedState, jt as EventEmitter, ot as StreamSink, q as RpcSharedStateGetOptions } from "../devframe-Dsjn_Xtq.mjs";
1
+ import { $ as DevframeRpcServerFunctions, F as ScopedSharedStates, I as SettingsForNamespace, J as RpcSharedStateHost, N as ScopedRpcFn, O as DevframeSettings, P as ScopedServerFunctions, Q as DevframeRpcClientFunctions, _ as ConnectionMeta, at as StreamReader, ht as SharedState, jt as EventEmitter, ot as StreamSink, q as RpcSharedStateGetOptions } from "../devframe-mQ4qZAuo.mjs";
2
2
  import { _ as RpcFunctionDefinition, w as RpcFunctionsCollector } from "../types-CnJSgRVa.mjs";
3
3
  import { E as RpcCacheOptions, T as RpcCacheManager } from "../index-Dbw1p5ch.mjs";
4
4
  import { t as WsRpcChannelOptions } from "../ws-client-B6Tnr0ic.mjs";
@@ -26,6 +26,12 @@ interface SetupDevframeConnectionOptions {
26
26
  /** Override the locally stored auth token. */
27
27
  authToken?: string;
28
28
  }
29
+ /**
30
+ * Allow an external viewer to connect by registering its browser origin with
31
+ * the Devframe host. Returns `false` if the host did not provide an origin
32
+ * registration token.
33
+ */
34
+ declare function registerDevframeViewerOrigin(connection: DevframeConnection, origin?: any): Promise<boolean>;
29
35
  /**
30
36
  * Return connection information previously prepared in this window or an
31
37
  * accessible parent window.
@@ -377,15 +383,16 @@ declare function getDevframeRpcClient(options?: DevframeRpcClientOptions): Promi
377
383
  //#endregion
378
384
  //#region src/client/otp.d.ts
379
385
  /**
380
- * Read a one-time authentication code (OTP) from the current page URL's query
381
- * string, without side effects. Returns `undefined` when the parameter is absent.
386
+ * Read a one-time authentication code (OTP) from the current page URL's
387
+ * fragment, without side effects. Returns `undefined` when the parameter is
388
+ * absent.
382
389
  */
383
390
  declare function readOtpFromUrl(param?: string): string | undefined;
384
391
  /**
385
- * Read the one-time code from the page URL and remove it from the address bar
386
- * (and the current history entry), so the single-use code isn't left in the
387
- * URL, browser history, or a `Referer`. Returns the code, or `undefined` when
388
- * absent.
392
+ * Read the one-time code from the page URL fragment and remove it from the
393
+ * address bar (and the current history entry), so the single-use code isn't
394
+ * left in the URL, browser history, or a `Referer`. Returns the code, or
395
+ * `undefined` when absent.
389
396
  */
390
397
  declare function consumeOtpFromUrl(param?: string): string | undefined;
391
398
  /**
@@ -402,6 +409,28 @@ declare function authenticateWithUrlOtp(rpc: Pick<DevframeRpcClient, 'isTrusted'
402
409
  param?: string;
403
410
  }): Promise<boolean>;
404
411
  //#endregion
412
+ //#region src/client/rpc-ws.d.ts
413
+ /** Minimal subset of `window.location` needed to resolve a WS URL. */
414
+ interface WsUrlLocation {
415
+ protocol: string;
416
+ host: string;
417
+ hostname: string;
418
+ href: string;
419
+ }
420
+ /**
421
+ * Resolve a {@link ConnectionMeta.websocket} descriptor into a concrete
422
+ * `ws(s)://` URL.
423
+ *
424
+ * The object / relative-path forms connect to the page's own origin (only the
425
+ * `http`→`ws` protocol swap is applied), resolving the path against where
426
+ * `__connection.json` was loaded. This is deliberately host-agnostic so the
427
+ * connection survives a reverse proxy that changes the domain or port — the
428
+ * client trusts its own location, never a server-baked hostname. An explicit
429
+ * `port`/`host` (or a full `ws(s)://` URL string) opts into a cross-origin
430
+ * endpoint, e.g. a side-car server on its own port.
431
+ */
432
+ declare function resolveWsUrl(websocket: ConnectionMeta['websocket'], metaBaseUrl: string, loc: WsUrlLocation): string;
433
+ //#endregion
405
434
  //#region src/client/settings.d.ts
406
435
  /**
407
436
  * Build the client-side `settings` surface for a scope namespace. Mirrors
@@ -412,4 +441,4 @@ declare function createClientSettings<T extends Record<string, any> = Record<str
412
441
  //#region src/client/index.d.ts
413
442
  declare const connectDevframe: typeof getDevframeRpcClient;
414
443
  //#endregion
415
- export { DevframeClientRpcHost, DevframeConnection, DevframeConnectionError, DevframeConnectionErrorKind, DevframeConnectionStatus, DevframeRpcClient, DevframeRpcClientCall, DevframeRpcClientCallEvent, DevframeRpcClientCallOptional, DevframeRpcClientMode, DevframeRpcClientOptions, DevframeRpcContext, DevframeScopedClientContext, DevframeScopedClientRpc, DevframeScopedClientStreamingHost, RpcClientEvents, RpcStreamingClientHost, SetupDevframeConnectionOptions, StreamingSubscribeOptions, authenticateWithUrlOtp, connectDevframe, consumeOtpFromUrl, createClientSettings, createRpcStreamingClientHost, createScopedClientContext, getDevframeConnection, getDevframeRpcClient, isCallableStatus, readOtpFromUrl, setupDevframeConnection };
444
+ export { DevframeClientRpcHost, DevframeConnection, DevframeConnectionError, DevframeConnectionErrorKind, DevframeConnectionStatus, DevframeRpcClient, DevframeRpcClientCall, DevframeRpcClientCallEvent, DevframeRpcClientCallOptional, DevframeRpcClientMode, DevframeRpcClientOptions, DevframeRpcContext, DevframeScopedClientContext, DevframeScopedClientRpc, DevframeScopedClientStreamingHost, RpcClientEvents, RpcStreamingClientHost, SetupDevframeConnectionOptions, StreamingSubscribeOptions, type WsUrlLocation, authenticateWithUrlOtp, connectDevframe, consumeOtpFromUrl, createClientSettings, createRpcStreamingClientHost, createScopedClientContext, getDevframeConnection, getDevframeRpcClient, isCallableStatus, readOtpFromUrl, registerDevframeViewerOrigin, resolveWsUrl, setupDevframeConnection };
@@ -20,11 +20,14 @@ const DEVFRAME_CONNECTION_META_FILENAME = "__connection.json";
20
20
  const DEVFRAME_CONNECTION_KEY = "__DEVFRAME_CONNECTION__";
21
21
  const DEVFRAME_RPC_DUMP_MANIFEST_FILENAME = "__rpc-dump/index.json";
22
22
  /**
23
- * Page-URL query parameter carrying a one-time authentication code (OTP) for
24
- * "magic link" auth. A host can print a link like `<origin>/?devframe_otp=<code>`;
25
- * the client reads the code, exchanges it for a token, and strips the parameter
26
- * from the URL. See `buildOtpAuthUrl` (node) and the `authenticateWithUrlOtp` /
27
- * `consumeOtpFromUrl` client utilities (or `connectDevframe`'s `otpParam`).
23
+ * Page-URL **fragment** parameter carrying a one-time authentication code (OTP)
24
+ * for "magic link" auth. A host can print a link like
25
+ * `<origin>/#devframe_otp=<code>`; the client reads the code, exchanges it for a
26
+ * token, and strips the parameter from the URL. The code rides the fragment
27
+ * (never the query string) so the browser never transmits it to the server,
28
+ * keeping it out of access logs and `Referer` headers. See `buildOtpAuthUrl`
29
+ * (node) and the `authenticateWithUrlOtp` / `consumeOtpFromUrl` client utilities
30
+ * (or `connectDevframe`'s `otpParam`).
28
31
  */
29
32
  const DEVFRAME_OTP_URL_PARAM = "devframe_otp";
30
33
  /**
@@ -35,6 +38,10 @@ const DEVFRAME_OTP_URL_PARAM = "devframe_otp";
35
38
  * trusted before its own `anonymous:devframe:auth` handshake call arrives.
36
39
  */
37
40
  const DEVFRAME_AUTH_TOKEN_QUERY_PARAM = "devframe_auth_token";
41
+ /** External viewer origin requested during connection bootstrap. */
42
+ const DEVFRAME_VIEWER_ORIGIN_QUERY_PARAM = "devframe_viewer_origin";
43
+ /** Token that authorizes an external viewer origin registration. */
44
+ const DEVFRAME_VIEWER_ORIGIN_TOKEN_QUERY_PARAM = "devframe_viewer_origin_token";
38
45
  //#endregion
39
46
  //#region src/utils/diagnostics-reporter.ts
40
47
  const formatAnsi = ansiFormatter(colors);
@@ -428,6 +435,21 @@ function storeAuthToken(token) {
428
435
  }
429
436
  //#endregion
430
437
  //#region src/client/connection.ts
438
+ /**
439
+ * Allow an external viewer to connect by registering its browser origin with
440
+ * the Devframe host. Returns `false` if the host did not provide an origin
441
+ * registration token.
442
+ */
443
+ async function registerDevframeViewerOrigin(connection, origin = globalThis.location?.origin) {
444
+ const token = connection.connectionMeta.viewerOriginToken;
445
+ if (!token || !origin) return false;
446
+ const url = new URL(connection.metaBaseUrl);
447
+ url.searchParams.set(DEVFRAME_VIEWER_ORIGIN_QUERY_PARAM, origin);
448
+ url.searchParams.set(DEVFRAME_VIEWER_ORIGIN_TOKEN_QUERY_PARAM, token);
449
+ const response = await fetch(url, { cache: "no-store" });
450
+ if (!response.ok) throw new Error(`Failed to register external viewer origin (${response.status}).`);
451
+ return true;
452
+ }
431
453
  function resolveMetaBaseUrl(baseURL) {
432
454
  const metaPath = withBase(DEVFRAME_CONNECTION_META_FILENAME, baseURL);
433
455
  try {
@@ -530,12 +552,14 @@ function isCallableStatus(status) {
530
552
  //#endregion
531
553
  //#region src/client/otp.ts
532
554
  /**
533
- * Read a one-time authentication code (OTP) from the current page URL's query
534
- * string, without side effects. Returns `undefined` when the parameter is absent.
555
+ * Read a one-time authentication code (OTP) from the current page URL's
556
+ * fragment, without side effects. Returns `undefined` when the parameter is
557
+ * absent.
535
558
  */
536
559
  function readOtpFromUrl(param = DEVFRAME_OTP_URL_PARAM) {
537
560
  try {
538
- return new URLSearchParams(globalThis.location?.search).get(param) || void 0;
561
+ const hash = globalThis.location?.hash?.replace(/^#/, "") ?? "";
562
+ return new URLSearchParams(hash).get(param) || void 0;
539
563
  } catch {
540
564
  return;
541
565
  }
@@ -543,16 +567,18 @@ function readOtpFromUrl(param = DEVFRAME_OTP_URL_PARAM) {
543
567
  function stripParamFromUrl(param) {
544
568
  try {
545
569
  const url = new URL(globalThis.location.href);
546
- if (!url.searchParams.has(param)) return;
547
- url.searchParams.delete(param);
570
+ const fragment = new URLSearchParams(url.hash.replace(/^#/, ""));
571
+ if (!fragment.has(param)) return;
572
+ fragment.delete(param);
573
+ url.hash = fragment.toString();
548
574
  globalThis.history?.replaceState(globalThis.history.state, "", url.href);
549
575
  } catch {}
550
576
  }
551
577
  /**
552
- * Read the one-time code from the page URL and remove it from the address bar
553
- * (and the current history entry), so the single-use code isn't left in the
554
- * URL, browser history, or a `Referer`. Returns the code, or `undefined` when
555
- * absent.
578
+ * Read the one-time code from the page URL fragment and remove it from the
579
+ * address bar (and the current history entry), so the single-use code isn't
580
+ * left in the URL, browser history, or a `Referer`. Returns the code, or
581
+ * `undefined` when absent.
556
582
  */
557
583
  function consumeOtpFromUrl(param = DEVFRAME_OTP_URL_PARAM) {
558
584
  const code = readOtpFromUrl(param);
@@ -2588,4 +2614,4 @@ async function getDevframeRpcClient(options = {}) {
2588
2614
  //#region src/client/index.ts
2589
2615
  const connectDevframe = getDevframeRpcClient;
2590
2616
  //#endregion
2591
- export { DevframeConnectionError, authenticateWithUrlOtp, connectDevframe, consumeOtpFromUrl, createClientSettings, createRpcStreamingClientHost, createScopedClientContext, getDevframeConnection, getDevframeRpcClient, isCallableStatus, readOtpFromUrl, setupDevframeConnection };
2617
+ export { DevframeConnectionError, authenticateWithUrlOtp, connectDevframe, consumeOtpFromUrl, createClientSettings, createRpcStreamingClientHost, createScopedClientContext, getDevframeConnection, getDevframeRpcClient, isCallableStatus, readOtpFromUrl, registerDevframeViewerOrigin, resolveWsUrl, setupDevframeConnection };
@@ -35,11 +35,14 @@ declare const DEVFRAME_RPC_DUMP_DIRNAME = "__rpc-dump";
35
35
  */
36
36
  declare const REMOTE_CONNECTION_KEY = "devframe-remote-connection";
37
37
  /**
38
- * Page-URL query parameter carrying a one-time authentication code (OTP) for
39
- * "magic link" auth. A host can print a link like `<origin>/?devframe_otp=<code>`;
40
- * the client reads the code, exchanges it for a token, and strips the parameter
41
- * from the URL. See `buildOtpAuthUrl` (node) and the `authenticateWithUrlOtp` /
42
- * `consumeOtpFromUrl` client utilities (or `connectDevframe`'s `otpParam`).
38
+ * Page-URL **fragment** parameter carrying a one-time authentication code (OTP)
39
+ * for "magic link" auth. A host can print a link like
40
+ * `<origin>/#devframe_otp=<code>`; the client reads the code, exchanges it for a
41
+ * token, and strips the parameter from the URL. The code rides the fragment
42
+ * (never the query string) so the browser never transmits it to the server,
43
+ * keeping it out of access logs and `Referer` headers. See `buildOtpAuthUrl`
44
+ * (node) and the `authenticateWithUrlOtp` / `consumeOtpFromUrl` client utilities
45
+ * (or `connectDevframe`'s `otpParam`).
43
46
  */
44
47
  declare const DEVFRAME_OTP_URL_PARAM = "devframe_otp";
45
48
  /**
@@ -50,6 +53,10 @@ declare const DEVFRAME_OTP_URL_PARAM = "devframe_otp";
50
53
  * trusted before its own `anonymous:devframe:auth` handshake call arrives.
51
54
  */
52
55
  declare const DEVFRAME_AUTH_TOKEN_QUERY_PARAM = "devframe_auth_token";
56
+ /** External viewer origin requested during connection bootstrap. */
57
+ declare const DEVFRAME_VIEWER_ORIGIN_QUERY_PARAM = "devframe_viewer_origin";
58
+ /** Token that authorizes an external viewer origin registration. */
59
+ declare const DEVFRAME_VIEWER_ORIGIN_TOKEN_QUERY_PARAM = "devframe_viewer_origin_token";
53
60
  /**
54
61
  * Prefix that marks an RPC method as callable before a connection is
55
62
  * trusted. This is the *only* rule the pre-trust gate applies — there is no
@@ -66,4 +73,4 @@ declare const ANONYMOUS_RPC_PREFIX = "anonymous:";
66
73
  */
67
74
  declare function isAnonymousRpcMethod(name: string): boolean;
68
75
  //#endregion
69
- export { ANONYMOUS_RPC_PREFIX, DEVFRAME_AUTH_TOKEN_QUERY_PARAM, DEVFRAME_CONNECTION_KEY, DEVFRAME_CONNECTION_META_FILENAME, DEVFRAME_DIRNAME, DEVFRAME_DOCK_IMPORTS_FILENAME, DEVFRAME_DOCK_IMPORTS_VIRTUAL_ID, DEVFRAME_MCP_ROUTE, DEVFRAME_MOUNT_PATH, DEVFRAME_MOUNT_PATH_NO_TRAILING_SLASH, DEVFRAME_OTP_URL_PARAM, DEVFRAME_RPC_DUMP_DIRNAME, DEVFRAME_RPC_DUMP_MANIFEST_FILENAME, DEVFRAME_WS_ROUTE, REMOTE_CONNECTION_KEY, isAnonymousRpcMethod };
76
+ export { ANONYMOUS_RPC_PREFIX, DEVFRAME_AUTH_TOKEN_QUERY_PARAM, DEVFRAME_CONNECTION_KEY, DEVFRAME_CONNECTION_META_FILENAME, DEVFRAME_DIRNAME, DEVFRAME_DOCK_IMPORTS_FILENAME, DEVFRAME_DOCK_IMPORTS_VIRTUAL_ID, DEVFRAME_MCP_ROUTE, DEVFRAME_MOUNT_PATH, DEVFRAME_MOUNT_PATH_NO_TRAILING_SLASH, DEVFRAME_OTP_URL_PARAM, DEVFRAME_RPC_DUMP_DIRNAME, DEVFRAME_RPC_DUMP_MANIFEST_FILENAME, DEVFRAME_VIEWER_ORIGIN_QUERY_PARAM, DEVFRAME_VIEWER_ORIGIN_TOKEN_QUERY_PARAM, DEVFRAME_WS_ROUTE, REMOTE_CONNECTION_KEY, isAnonymousRpcMethod };
@@ -35,11 +35,14 @@ const DEVFRAME_RPC_DUMP_DIRNAME = "__rpc-dump";
35
35
  */
36
36
  const REMOTE_CONNECTION_KEY = "devframe-remote-connection";
37
37
  /**
38
- * Page-URL query parameter carrying a one-time authentication code (OTP) for
39
- * "magic link" auth. A host can print a link like `<origin>/?devframe_otp=<code>`;
40
- * the client reads the code, exchanges it for a token, and strips the parameter
41
- * from the URL. See `buildOtpAuthUrl` (node) and the `authenticateWithUrlOtp` /
42
- * `consumeOtpFromUrl` client utilities (or `connectDevframe`'s `otpParam`).
38
+ * Page-URL **fragment** parameter carrying a one-time authentication code (OTP)
39
+ * for "magic link" auth. A host can print a link like
40
+ * `<origin>/#devframe_otp=<code>`; the client reads the code, exchanges it for a
41
+ * token, and strips the parameter from the URL. The code rides the fragment
42
+ * (never the query string) so the browser never transmits it to the server,
43
+ * keeping it out of access logs and `Referer` headers. See `buildOtpAuthUrl`
44
+ * (node) and the `authenticateWithUrlOtp` / `consumeOtpFromUrl` client utilities
45
+ * (or `connectDevframe`'s `otpParam`).
43
46
  */
44
47
  const DEVFRAME_OTP_URL_PARAM = "devframe_otp";
45
48
  /**
@@ -50,6 +53,10 @@ const DEVFRAME_OTP_URL_PARAM = "devframe_otp";
50
53
  * trusted before its own `anonymous:devframe:auth` handshake call arrives.
51
54
  */
52
55
  const DEVFRAME_AUTH_TOKEN_QUERY_PARAM = "devframe_auth_token";
56
+ /** External viewer origin requested during connection bootstrap. */
57
+ const DEVFRAME_VIEWER_ORIGIN_QUERY_PARAM = "devframe_viewer_origin";
58
+ /** Token that authorizes an external viewer origin registration. */
59
+ const DEVFRAME_VIEWER_ORIGIN_TOKEN_QUERY_PARAM = "devframe_viewer_origin_token";
53
60
  /**
54
61
  * Prefix that marks an RPC method as callable before a connection is
55
62
  * trusted. This is the *only* rule the pre-trust gate applies — there is no
@@ -68,4 +75,4 @@ function isAnonymousRpcMethod(name) {
68
75
  return name.startsWith(ANONYMOUS_RPC_PREFIX);
69
76
  }
70
77
  //#endregion
71
- export { ANONYMOUS_RPC_PREFIX, DEVFRAME_AUTH_TOKEN_QUERY_PARAM, DEVFRAME_CONNECTION_KEY, DEVFRAME_CONNECTION_META_FILENAME, DEVFRAME_DIRNAME, DEVFRAME_DOCK_IMPORTS_FILENAME, DEVFRAME_DOCK_IMPORTS_VIRTUAL_ID, DEVFRAME_MCP_ROUTE, DEVFRAME_MOUNT_PATH, DEVFRAME_MOUNT_PATH_NO_TRAILING_SLASH, DEVFRAME_OTP_URL_PARAM, DEVFRAME_RPC_DUMP_DIRNAME, DEVFRAME_RPC_DUMP_MANIFEST_FILENAME, DEVFRAME_WS_ROUTE, REMOTE_CONNECTION_KEY, isAnonymousRpcMethod };
78
+ export { ANONYMOUS_RPC_PREFIX, DEVFRAME_AUTH_TOKEN_QUERY_PARAM, DEVFRAME_CONNECTION_KEY, DEVFRAME_CONNECTION_META_FILENAME, DEVFRAME_DIRNAME, DEVFRAME_DOCK_IMPORTS_FILENAME, DEVFRAME_DOCK_IMPORTS_VIRTUAL_ID, DEVFRAME_MCP_ROUTE, DEVFRAME_MOUNT_PATH, DEVFRAME_MOUNT_PATH_NO_TRAILING_SLASH, DEVFRAME_OTP_URL_PARAM, DEVFRAME_RPC_DUMP_DIRNAME, DEVFRAME_RPC_DUMP_MANIFEST_FILENAME, DEVFRAME_VIEWER_ORIGIN_QUERY_PARAM, DEVFRAME_VIEWER_ORIGIN_TOKEN_QUERY_PARAM, DEVFRAME_WS_ROUTE, REMOTE_CONNECTION_KEY, isAnonymousRpcMethod };
@@ -1,5 +1,6 @@
1
- import { t as createStorage } from "./storage-Dzoc3NVC.mjs";
2
- import { i as randomToken, n as revokeAuthToken, t as revokeActiveConnectionsForToken } from "./revoke-BtQDKTp7.mjs";
1
+ import { n as randomToken } from "./crypto-token-XCqTSMg9.mjs";
2
+ import { t as createStorage } from "./storage-CTNVbuFB.mjs";
3
+ import { n as revokeAuthToken, t as revokeActiveConnectionsForToken } from "./revoke-DZbJ7Cl0.mjs";
3
4
  import { join } from "pathe";
4
5
  //#region src/node/hub-internals/context.ts
5
6
  const internalContextMap = /* @__PURE__ */ new WeakMap();
@@ -1,4 +1,4 @@
1
- import { b as DevframeNodeContext, ht as SharedState } from "./devframe-Dsjn_Xtq.mjs";
1
+ import { b as DevframeNodeContext, ht as SharedState } from "./devframe-mQ4qZAuo.mjs";
2
2
  //#region src/node/hub-internals/context.d.ts
3
3
  interface InternalAnonymousAuthStorage {
4
4
  trusted: Record<string, {
@@ -42,38 +42,4 @@ function timingSafeEqual(a, b) {
42
42
  return mismatch === 0;
43
43
  }
44
44
  //#endregion
45
- //#region src/node/auth/revoke.ts
46
- /**
47
- * Flip `isTrusted` to false on any live WS clients connected with `token`
48
- * and broadcast the `auth:revoked` event so they can react.
49
- *
50
- * Shared between persisted-auth revocation and remote-dock token revocation.
51
- */
52
- async function revokeActiveConnectionsForToken(context, token) {
53
- const rpcHost = context.rpc;
54
- if (!rpcHost?._rpcGroup) return;
55
- const affectedSessionIds = /* @__PURE__ */ new Set();
56
- for (const client of rpcHost._rpcGroup.clients) if (client.$meta.clientAuthToken === token) {
57
- affectedSessionIds.add(client.$meta.id);
58
- client.$meta.isTrusted = false;
59
- client.$meta.clientAuthToken = void 0;
60
- }
61
- if (affectedSessionIds.size === 0) return;
62
- await rpcHost.broadcast({
63
- method: "devframe:auth:revoked",
64
- args: [],
65
- filter: (client) => affectedSessionIds.has(client.$meta.id)
66
- });
67
- }
68
- /**
69
- * Revoke an auth token: remove from storage and notify all connected clients
70
- * using this token that they are no longer trusted.
71
- */
72
- async function revokeAuthToken(context, storage, token) {
73
- storage.mutate((state) => {
74
- delete state.trusted[token];
75
- });
76
- await revokeActiveConnectionsForToken(context, token);
77
- }
78
- //#endregion
79
- export { timingSafeEqual as a, randomToken as i, revokeAuthToken as n, randomDigits as r, revokeActiveConnectionsForToken as t };
45
+ export { randomToken as n, timingSafeEqual as r, randomDigits as t };
@@ -1,8 +1,8 @@
1
1
  import { DEVFRAME_CONNECTION_META_FILENAME } from "./constants.mjs";
2
- import { n as createHostContext, t as createH3DevframeHost } from "./host-h3-Kz7t5Xab.mjs";
3
- import { t as diagnostics } from "./diagnostics-B5-qHeqD.mjs";
4
- import { r as registerDevframeInstance } from "./instance-registry-D6fxYu36.mjs";
5
- import { i as normalizeHttpServerUrl, t as startHttpAndWs } from "./server-CcAPxuoT.mjs";
2
+ import { n as createHostContext, t as createH3DevframeHost } from "./host-h3-D2hYZvDK.mjs";
3
+ import { t as diagnostics } from "./diagnostics-D7wM2cwM.mjs";
4
+ import { r as registerDevframeInstance } from "./instance-registry-BnnA2Eoh.mjs";
5
+ import { i as normalizeHttpServerUrl, t as startHttpAndWs } from "./server-CD8XmBXl.mjs";
6
6
  import { n as resolveBasePath, t as normalizeBasePath } from "./_shared-bWRzeSa0.mjs";
7
7
  import { t as open } from "./open-Deb5xmIT.mjs";
8
8
  import { mountStaticHandler } from "./utils/serve-static.mjs";
@@ -295,7 +295,7 @@ async function createDevServer(def, options = {}) {
295
295
  const mcpPath = joinURL(basePath, mcpRoute);
296
296
  let mountMcpHttp;
297
297
  try {
298
- ({mountMcpHttp} = await import("./http-BghOqfTD.mjs"));
298
+ ({mountMcpHttp} = await import("./http-C4T68YV6.mjs"));
299
299
  } catch (error) {
300
300
  const reason = error instanceof Error ? error.message : String(error);
301
301
  throw diagnostics.DF0017({
@@ -339,6 +339,8 @@ async function createDevServer(def, options = {}) {
339
339
  path: bindPath,
340
340
  wsPort,
341
341
  auth: resolvedAuth,
342
+ onPeerConnect: options.onPeerConnect,
343
+ onPeerDisconnect: options.onPeerDisconnect,
342
344
  onReady: async (info) => {
343
345
  authHandler?.printBanner();
344
346
  await options.onReady?.(info);
@@ -1,6 +1,6 @@
1
1
  import { _ as RpcFunctionDefinition, v as RpcFunctionDefinitionAny } from "./types-CnJSgRVa.mjs";
2
2
  import { w as RpcFunctionsCollectorBase } from "./index-Dbw1p5ch.mjs";
3
- import { t as DevframeNodeRpcSessionMeta } from "./ws-server-D_Vjtums.mjs";
3
+ import { n as DevframeNodeRpcSessionMeta } from "./ws-server-uKQcClcJ.mjs";
4
4
  import { CAC } from "cac";
5
5
  import { StandardSchemaV1 } from "@standard-schema/spec";
6
6
  import { Peer } from "crossws";
@@ -1414,6 +1414,15 @@ interface ConnectionMeta {
1414
1414
  * > from a publicly reachable static `__connection.json`.
1415
1415
  */
1416
1416
  authToken?: string;
1417
+ /**
1418
+ * Session-scoped token that lets a trusted external viewer register its
1419
+ * browser origin before opening the WebSocket. The host includes it in the
1420
+ * connection metadata, which the viewer obtains through the host page.
1421
+ *
1422
+ * Treat this as a bearer credential. Keep connection metadata containing the
1423
+ * token same-origin until the requesting origin has been verified.
1424
+ */
1425
+ viewerOriginToken?: string;
1417
1426
  }
1418
1427
  //#endregion
1419
1428
  //#region src/types/utils.d.ts
@@ -87,6 +87,10 @@ const diagnostics = defineDiagnostics({
87
87
  DF0051: {
88
88
  why: (p) => `The devframe instance on port ${p.port} has no MCP endpoint.`,
89
89
  fix: "Restart the instance with the --mcp flag (or set `cli.mcp: true` on its definition) to expose its tools, then list instances again."
90
+ },
91
+ DF0052: {
92
+ why: (p) => `Failed to listen on ${p.host}:${p.port}: ${p.reason}`,
93
+ fix: "The port is likely already taken by another process (often a previous devframe instance). Free it, or pick another via `--port`, `cli.port` / `cli.portRange` on the definition, or `devMiddleware.port` on `viteDevBridge`. The original node error is available as `error.cause`."
90
94
  }
91
95
  }
92
96
  });
@@ -1,6 +1,6 @@
1
1
  import { isAllowedOrigin } from "./rpc/transports/ws-server.mjs";
2
- import { n as createHostContext } from "./host-h3-Kz7t5Xab.mjs";
3
- import { t as diagnostics } from "./diagnostics-B5-qHeqD.mjs";
2
+ import { n as createHostContext } from "./host-h3-D2hYZvDK.mjs";
3
+ import { t as diagnostics } from "./diagnostics-D7wM2cwM.mjs";
4
4
  import { t as toAgentToolName } from "./agent-tool-name-C3b5vEwJ.mjs";
5
5
  import { randomUUID } from "node:crypto";
6
6
  import { Diagnostic } from "nostics";
@@ -457,9 +457,10 @@ function parseResourceUri(uri) {
457
457
  * and MCP server (built from the shared, live `ctx` via
458
458
  * `buildMcpServerFromContext`), correlated by the `Mcp-Session-Id` header: an
459
459
  * `initialize` POST spins up a session; later requests route to it; a `DELETE`
460
- * (or client disconnect) tears it down. The origin gate applies devframe's
461
- * loopback-default DNS-rebinding protection (identical semantics to the WS
462
- * upgrade's `isAllowedOrigin`).
460
+ * (or client disconnect) tears it down. The origin gate guards every request:
461
+ * loopback-default DNS-rebinding protection that — unlike the WS upgrade's
462
+ * `isAllowedOrigin` — also rejects `Origin`-less requests, so a route-based
463
+ * endpoint isn't reachable by an arbitrary local process.
463
464
  *
464
465
  * @experimental
465
466
  */
@@ -503,7 +504,7 @@ function createMcpFetchHandler(ctx, options) {
503
504
  }
504
505
  async function handle(req) {
505
506
  const origin = req.headers.get("origin") ?? void 0;
506
- if (allowedOrigins !== false && !isAllowedOrigin(origin, allowedOrigins ?? [])) return new Response("Forbidden: origin not allowed", { status: 403 });
507
+ if (allowedOrigins !== false && (origin === void 0 || !isAllowedOrigin(origin, allowedOrigins ?? []))) return new Response("Forbidden: origin required", { status: 403 });
507
508
  const sessionId = req.headers.get("mcp-session-id") ?? void 0;
508
509
  let session = sessionId ? sessions.get(sessionId) : void 0;
509
510
  if (!session && req.method === "POST") {
@@ -1,4 +1,4 @@
1
- import { d as McpRouteOptions, p as DevframeAuthHandler, r as DevframeDefinition } from "../devframe-Dsjn_Xtq.mjs";
1
+ import { d as McpRouteOptions, p as DevframeAuthHandler, r as DevframeDefinition } from "../devframe-mQ4qZAuo.mjs";
2
2
  //#region src/helpers/vite.d.ts
3
3
  interface ViteDevBridgeOptions {
4
4
  /**
@@ -31,16 +31,17 @@ interface ViteDevBridgeOptions {
31
31
  flags?: Record<string, unknown>;
32
32
  };
33
33
  /**
34
- * Whether the bridged devframe runs its own auth gate. This is a **hosted**
35
- * adapter — the devframe shares the host app's origin and the host owns
36
- * authentication — so it defaults to `false`: the plugin's own gate never
37
- * fires and its `cli.auth` default is ignored (matching devframe's
38
- * hosted-deployment contract). Pass `true` to force devframe's interactive
39
- * OTP gate on, or a {@link DevframeAuthHandler} to install a custom scheme.
40
- * Only applies in bridge mode (`devMiddleware`); the static-mount mode
34
+ * Whether the bridged devframe runs its own auth gate. The side-car RPC
35
+ * server is reachable by anything that can open its socket, so it **gates by
36
+ * default**: when unset, authentication resolves through `createDevServer`
37
+ * (devframe's interactive OTP gate unless the definition's `cli.auth` opts
38
+ * out), and the side-car prints its code/link banner to stdout. Pass a
39
+ * {@link DevframeAuthHandler} to install a custom scheme, or `false` to opt
40
+ * out for a single-user localhost host that owns the trust boundary another
41
+ * way. Only applies in bridge mode (`devMiddleware`); the static-mount mode
41
42
  * starts no RPC server.
42
43
  *
43
- * @default false
44
+ * @default gated (devframe's interactive OTP, unless `cli.auth` opts out)
44
45
  */
45
46
  auth?: boolean | DevframeAuthHandler;
46
47
  /**
@@ -84,11 +85,11 @@ interface DevframeVitePlugin {
84
85
  * host-served SPA can discover the WS endpoint via
85
86
  * {@link connectDevframe}.
86
87
  *
87
- * As a hosted adapter the bridge defers authentication to the host: its
88
- * side-car RPC server runs with the plugin's own auth gate **off** by
89
- * default (ignoring `def.cli?.auth`), so a plugin mounted this way never
90
- * triggers its standalone OTP prompt. Opt back in per-mount with
91
- * `options.auth` (`true` for devframe's interactive gate, or a handler).
88
+ * The side-car RPC server **gates by default** (devframe's interactive OTP
89
+ * unless the definition's `cli.auth` opts out), printing its code/link banner
90
+ * to stdout, so a bridged devframe isn't silently reachable by anything that
91
+ * can open its socket. Pass `options.auth: false` to opt out for a single-user
92
+ * localhost host, or a {@link DevframeAuthHandler} for a custom scheme.
92
93
  *
93
94
  * Use bridge mode when integrating with frameworks that own the SPA
94
95
  * (Nuxt, Astro, SolidStart, plain Vite apps). For the all-in-one