devframe 0.9.1 → 0.9.3

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 (76) hide show
  1. package/dist/{_shared-DqbKCAew.d.mts → _shared-BPovvJW8.d.mts} +1 -1
  2. package/dist/adapters/build.d.mts +11 -2
  3. package/dist/adapters/build.mjs +46 -9
  4. package/dist/adapters/cac.d.mts +1 -1
  5. package/dist/adapters/cac.mjs +1 -1
  6. package/dist/adapters/dev.d.mts +4 -4
  7. package/dist/adapters/dev.mjs +1 -1
  8. package/dist/adapters/embedded.d.mts +1 -1
  9. package/dist/adapters/embedded.mjs +2 -1
  10. package/dist/adapters/initiate.d.mts +3 -3
  11. package/dist/adapters/initiate.mjs +1 -1
  12. package/dist/adapters/mcp.d.mts +1 -1
  13. package/dist/adapters/mcp.mjs +1 -1
  14. package/dist/cli/main.mjs +2 -2
  15. package/dist/client/index.d.mts +5 -5
  16. package/dist/client/index.mjs +2 -2
  17. package/dist/{context-CXhjygJU.mjs → context-BUJGvlje.mjs} +1 -1
  18. package/dist/{context-dqyXS6o3.d.mts → context-CO7eZHJy.d.mts} +1 -1
  19. package/dist/{context-Bdkakk8S.mjs → context-CQW0lSw8.mjs} +40 -20
  20. package/dist/{devframe-BlLEZR-x.d.mts → context-_i51nYOs.d.mts} +1033 -970
  21. package/dist/{dev-C8VHtuPP.mjs → dev-lpIvwagf.mjs} +10 -9
  22. package/dist/{diagnostics-Di8ytitn.mjs → diagnostics-BXWW3VK-.mjs} +3 -3
  23. package/dist/hash-DdPnc4k3.mjs +360 -0
  24. package/dist/{host-agent-BDdcOXhG.mjs → host-agent-B84av916.mjs} +1 -1
  25. package/dist/{http-BbHCU5Zs.mjs → http-BKiqHcXX.mjs} +6 -5
  26. package/dist/{index-BKFT9-jA.d.mts → index-CEvd-YXD.d.mts} +2 -2
  27. package/dist/{index-tdrk1bzV.d.mts → index-DOg4S5wk.d.mts} +1 -1
  28. package/dist/index.d.mts +6 -6
  29. package/dist/{instance-registry-CHunhMi5.mjs → instance-registry-BjlMQbFB.mjs} +1 -1
  30. package/dist/{instance-shell-DbDoozNM.mjs → instance-shell-B-uevnK-.mjs} +4 -4
  31. package/dist/{instance-shell-0iw-cQ5V.d.mts → instance-shell-BHvBIGP4.d.mts} +3 -3
  32. package/dist/internal/index.d.mts +8 -8
  33. package/dist/internal/index.mjs +5 -5
  34. package/dist/node/auth.d.mts +2 -2
  35. package/dist/node/hub-internals.d.mts +2 -2
  36. package/dist/node/hub-internals.mjs +1 -1
  37. package/dist/node/index.d.mts +12 -3
  38. package/dist/node/index.mjs +2 -2
  39. package/dist/recipes/common-rpc-functions.d.mts +13 -3
  40. package/dist/recipes/common-rpc-functions.mjs +10 -0
  41. package/dist/recipes/interactive-auth.d.mts +2 -2
  42. package/dist/recipes/interactive-auth.mjs +1 -1
  43. package/dist/{remote-assets-DRfY2RjX.mjs → remote-assets-DezQmPQU.mjs} +13 -3
  44. package/dist/rpc/dump.d.mts +1 -1
  45. package/dist/rpc/dump.mjs +1 -1
  46. package/dist/rpc/index.d.mts +2 -2
  47. package/dist/rpc/index.mjs +1 -1
  48. package/dist/rpc/transports/sse-client.d.mts +1 -1
  49. package/dist/rpc/transports/sse-server.d.mts +2 -2
  50. package/dist/rpc/transports/ws-bun.d.mts +2 -2
  51. package/dist/rpc/transports/ws-client.d.mts +1 -1
  52. package/dist/rpc/transports/ws-deno.d.mts +26 -0
  53. package/dist/rpc/transports/ws-deno.mjs +30 -0
  54. package/dist/rpc/transports/ws-server.d.mts +1 -1
  55. package/dist/{rpc-core-CR8o34O9.d.mts → rpc-core-CDZoGYiD.d.mts} +3 -3
  56. package/dist/{rpc-core-Dru9uoM0.mjs → rpc-core-DlHd276T.mjs} +3 -10
  57. package/dist/{shared-state-fRRbRUtD.mjs → shared-state-DoXutg_U.mjs} +1 -1
  58. package/dist/{sse-client-CFBOIaUi.d.mts → sse-client-CdGD_B8_.d.mts} +1 -1
  59. package/dist/{static-BPZ1_OPA.mjs → static-DV_qUSRS.mjs} +1 -1
  60. package/dist/{storage-D4a99Ung.mjs → storage-BXNVhKyR.mjs} +1 -1
  61. package/dist/types/index.d.mts +5 -5
  62. package/dist/{types-Djq7CBXh.d.mts → types-BBLeGfpt.d.mts} +1 -1
  63. package/dist/utils/events.d.mts +1 -1
  64. package/dist/utils/hash.mjs +1 -1
  65. package/dist/utils/remote-assets.d.mts +7 -1
  66. package/dist/utils/remote-assets.mjs +1 -1
  67. package/dist/utils/shared-state.d.mts +1 -1
  68. package/dist/utils/shared-state.mjs +1 -1
  69. package/dist/utils/streaming-channel.d.mts +1 -1
  70. package/dist/validation-YrZH6atx.mjs +311 -0
  71. package/dist/{ws-client-DhjNyQo3.d.mts → ws-client-Bo2t6hES.d.mts} +1 -1
  72. package/dist/{ws-server-D1d3QM9f.d.mts → ws-server-DLtEoLdR.d.mts} +1 -1
  73. package/package.json +4 -3
  74. package/skills/devframe/SKILL.md +1 -1
  75. package/dist/hash-KtDZYXDN.mjs +0 -266
  76. package/dist/validation-CpXFB6Dz.mjs +0 -266
@@ -1,50 +1,11 @@
1
- import { d as RpcFunctionsCollectorBase } from "./index-tdrk1bzV.mjs";
2
- import { _ as RpcFunctionDefinition, v as RpcFunctionDefinitionAny } from "./types-Djq7CBXh.mjs";
3
- import { d as DevframeRpcConnection, u as DevframeNodeRpcSessionMeta } from "./ws-server-D1d3QM9f.mjs";
4
- import { m as defineDiagnostics } from "./nostics-SPGDgHEP.mjs";
1
+ import { d as RpcFunctionsCollectorBase } from "./index-DOg4S5wk.mjs";
2
+ import { _ as RpcFunctionDefinition, v as RpcFunctionDefinitionAny } from "./types-BBLeGfpt.mjs";
3
+ import { d as DevframeRpcConnection, u as DevframeNodeRpcSessionMeta } from "./ws-server-DLtEoLdR.mjs";
5
4
  import { a as RemoteAssetsStore, o as StaticAssetsSource } from "./remote-assets-Bg4gCUZ_.mjs";
6
- import { CAC } from "cac";
7
- import { StandardSchemaV1 } from "@standard-schema/spec";
5
+ import { m as defineDiagnostics } from "./nostics-SPGDgHEP.mjs";
8
6
  import { BirpcReturn } from "birpc";
9
- //#region src/adapters/flags.d.ts
10
- /**
11
- * Schema map for typed CLI flags. Keys are flag names in camelCase —
12
- * this matches CAC's parsed-flag output ( `--no-open` → `noOpen` ). Each
13
- * value is any [Standard Schema](https://standardschema.dev/) validator
14
- * (valibot, zod, arktype, devframe's built-in `s`, …) used to both (a)
15
- * derive the CAC option type when the flag is registered and (b) validate
16
- * the parsed value before it's forwarded to `setup(ctx, { flags })`.
17
- */
18
- type CliFlagsSchema = Record<string, StandardSchemaV1>;
19
- /**
20
- * Identity helper that preserves the literal schema-map type — use this
21
- * so `InferCliFlags<typeof myFlags>` resolves to the right object shape.
22
- *
23
- * ```ts
24
- * const appFlags = defineCliFlags({
25
- * depth: v.pipe(v.number(), v.integer()),
26
- * config: v.optional(v.string()),
27
- * })
28
- *
29
- * defineDevframe({
30
- * cli: { flags: appFlags },
31
- * setup(ctx, info) {
32
- * const flags = info.flags as InferCliFlags<typeof appFlags>
33
- * flags.depth // number
34
- * flags.config // string | undefined
35
- * },
36
- * })
37
- * ```
38
- */
39
- declare function defineCliFlags<T extends CliFlagsSchema>(flags: T): T;
40
- /** Extract the parsed-output type from a {@link CliFlagsSchema}. */
41
- type InferCliFlags<T extends CliFlagsSchema> = { [K in keyof T]: StandardSchemaV1.InferOutput<T[K]>; };
42
- /** Validate the raw cac-parsed bag against a {@link CliFlagsSchema}. */
43
- declare function parseCliFlags(schema: CliFlagsSchema, raw: Record<string, unknown>): {
44
- flags: Record<string, unknown>;
45
- issues?: string[];
46
- };
47
- //#endregion
7
+ import { StandardSchemaV1 } from "@standard-schema/spec";
8
+ import { CAC } from "cac";
48
9
  //#region src/types/events.d.ts
49
10
  interface EventsMap {
50
11
  [event: string]: any;
@@ -313,235 +274,144 @@ interface DevframeAgentHost {
313
274
  getResource: (id: string) => AgentResource | undefined;
314
275
  }
315
276
  //#endregion
316
- //#region src/utils/shared-state.d.ts
317
- type ImmutablePrimitive = undefined | null | boolean | string | number | Function;
318
- type Immutable<T> = T extends ImmutablePrimitive ? T : T extends Array<infer U> ? ImmutableArray<U> : T extends Map<infer K, infer V> ? ImmutableMap<K, V> : T extends Set<infer M> ? ImmutableSet<M> : ImmutableObject<T>;
319
- type ImmutableArray<T> = ReadonlyArray<Immutable<T>>;
320
- type ImmutableMap<K, V> = ReadonlyMap<Immutable<K>, Immutable<V>>;
321
- type ImmutableSet<T> = ReadonlySet<Immutable<T>>;
322
- type ImmutableObject<T> = { readonly [K in keyof T]: Immutable<T[K]>; };
277
+ //#region src/adapters/flags.d.ts
323
278
  /**
324
- * Serializable patch describing a single mutation to a `SharedState`.
325
- * Structurally compatible with JSON-Patch and is safe to send over RPC.
279
+ * Schema map for typed CLI flags. Keys are flag names in camelCase —
280
+ * this matches CAC's parsed-flag output ( `--no-open` → `noOpen` ). Each
281
+ * value is any [Standard Schema](https://standardschema.dev/) validator
282
+ * (valibot, zod, arktype, devframe's built-in `s`, …) used to both (a)
283
+ * derive the CAC option type when the flag is registered and (b) validate
284
+ * the parsed value before it's forwarded to `setup(ctx, { flags })`.
326
285
  */
327
- interface SharedStatePatch {
328
- op: 'add' | 'remove' | 'replace';
329
- path: readonly (string | number)[];
330
- value?: unknown;
331
- }
286
+ type CliFlagsSchema = Record<string, StandardSchemaV1>;
332
287
  /**
333
- * State host that is immutable by default with explicit mutate.
288
+ * Identity helper that preserves the literal schema-map type — use this
289
+ * so `InferCliFlags<typeof myFlags>` resolves to the right object shape.
290
+ *
291
+ * ```ts
292
+ * const appFlags = defineCliFlags({
293
+ * depth: v.pipe(v.number(), v.integer()),
294
+ * config: v.optional(v.string()),
295
+ * })
296
+ *
297
+ * defineDevframe({
298
+ * cli: { flags: appFlags },
299
+ * setup(ctx, info) {
300
+ * const flags = info.flags as InferCliFlags<typeof appFlags>
301
+ * flags.depth // number
302
+ * flags.config // string | undefined
303
+ * },
304
+ * })
305
+ * ```
334
306
  */
335
- interface SharedState<T> {
336
- /**
337
- * Get the current state. Immutable.
338
- */
339
- value: () => Immutable<T>;
340
- /**
341
- * Subscribe to state changes.
342
- */
343
- on: EventEmitter<SharedStateEvents<T>>['on'];
307
+ declare function defineCliFlags<T extends CliFlagsSchema>(flags: T): T;
308
+ /** Extract the parsed-output type from a {@link CliFlagsSchema}. */
309
+ type InferCliFlags<T extends CliFlagsSchema> = { [K in keyof T]: StandardSchemaV1.InferOutput<T[K]>; };
310
+ /** Validate the raw cac-parsed bag against a {@link CliFlagsSchema}. */
311
+ declare function parseCliFlags(schema: CliFlagsSchema, raw: Record<string, unknown>): {
312
+ flags: Record<string, unknown>;
313
+ issues?: string[];
314
+ };
315
+ //#endregion
316
+ //#region src/node/auth/handler.d.ts
317
+ /**
318
+ * A ready-made pre-auth RPC handler, as produced by
319
+ * `devframe/recipes/interactive-auth`'s `createInteractiveAuth`. Bundles
320
+ * everything a host adapter needs to wire an authenticated server:
321
+ * the handshake RPC functions, the resolver gate, the connect-time trust
322
+ * hook, and the startup banner.
323
+ *
324
+ * `initDevframe` / `initHub` accept one of these directly via their `auth` option —
325
+ * see {@link https://devfra.me | devframe}'s server docs — or a host can
326
+ * wire the four pieces itself against a lower-level transport.
327
+ */
328
+ interface DevframeAuthHandler {
344
329
  /**
345
- * Mutate the state.
330
+ * `anonymous:devframe:auth` + `anonymous:devframe:auth:exchange` (the
331
+ * handshake) and `devframe:auth:revoke` (self-revoke) — register these on
332
+ * the RPC host (e.g. `rpcHost.register(fn)` for each).
346
333
  */
347
- mutate: (fn: (state: T) => void, syncId?: string) => void;
334
+ rpcFunctions: RpcFunctionDefinitionAny[];
348
335
  /**
349
- * Apply patches to the state.
336
+ * Resolver gate: whether `methodName` is callable given `session`'s
337
+ * current trust state. Defaults to allowing any `anonymous:`-prefixed
338
+ * method (see `isAnonymousRpcMethod`) plus anything once the session is
339
+ * trusted.
350
340
  */
351
- patch: (patches: SharedStatePatch[], syncId?: string) => void;
341
+ authorize: (methodName: string, session: DevframeNodeRpcSession) => boolean;
352
342
  /**
353
- * Sync IDs that have been applied to the state.
343
+ * Connect-time trust: reads a bearer token off the connection's initial
344
+ * request (a static/pre-shared token from `clientAuthTokens`, or a token
345
+ * minted by the code exchange) and, when valid, marks the session
346
+ * trusted immediately — before the client's own handshake call.
354
347
  */
355
- syncIds: Set<string>;
356
- }
357
- interface SharedStateEvents<T> {
358
- updated: (fullState: T, patches: SharedStatePatch[] | undefined, syncId: string) => void;
359
- }
360
- interface SharedStateOptions<T> {
348
+ onConnect: (connection: DevframeRpcConnection, session: DevframeNodeRpcSession) => void;
361
349
  /**
362
- * Initial state.
350
+ * Print the current one-time code and its magic-link URL. Devframe stays
351
+ * headless — call this yourself once the server is listening. Safe to
352
+ * call repeatedly; it only prints once per code.
363
353
  */
364
- initialValue: T;
354
+ printBanner: () => void;
365
355
  /**
366
- * Enable patches.
367
- *
368
- * @default false
356
+ * Rewrite a URL the CLI is about to open in the browser (`--open`) so the
357
+ * tab lands already authenticated — e.g. appending the current OTP as a
358
+ * query param. Called with the fully-resolved target URL; return it
359
+ * unchanged to opt out. Optional: a handler that doesn't need this (e.g.
360
+ * one gated by a pre-shared bearer token instead of an interactive code)
361
+ * can omit it and the URL opens as-is.
369
362
  */
370
- enablePatches?: boolean;
363
+ buildOpenUrl?: (url: string) => string;
371
364
  }
372
- declare function createSharedState<T extends object>(options: SharedStateOptions<T>): SharedState<T>;
373
365
  //#endregion
374
- //#region src/utils/streaming-channel.d.ts
366
+ //#region src/types/diagnostics.d.ts
375
367
  /**
376
- * Serialized error shape sent over the wire when a stream ends with a failure.
377
- * Stays JSON-safe so the strict-JSON encoder can carry it without coercion.
368
+ * The shared diagnostics lookup exposed by the host. A `Proxy` that resolves
369
+ * any registered code name to its `nostics` handle (a callable that builds
370
+ * a diagnostic and routes it through registered reporters). Typed loosely
371
+ * because it spans heterogeneous definitions registered by different
372
+ * integrations.
378
373
  */
379
- interface StreamErrorPayload {
380
- name: string;
381
- message: string;
382
- }
374
+ type DevframeDiagnosticsLogger = Record<string, any>;
383
375
  /**
384
- * Single buffered chunk in the server-side ring buffer.
376
+ * Host for structured diagnostics — a thin layer over `nostics` that lets
377
+ * integrations register their own coded errors/warnings into a shared
378
+ * registry without taking a direct dependency on `nostics`.
385
379
  *
386
- * Sequence numbers start at 1 and increment per write. Subscribers track
387
- * `lastSeenSeq` and ask for `afterSeq` on resubscribe so the server can
388
- * replay any chunks the client missed during a brief disconnect.
380
+ * Typical usage from a plugin's `setup(ctx)`:
381
+ *
382
+ * ```ts
383
+ * const myDiagnostics = ctx.diagnostics.defineDiagnostics({
384
+ * docsBase: 'https://example.com/errors',
385
+ * codes: {
386
+ * MYP0001: { why: 'Something went wrong' },
387
+ * },
388
+ * })
389
+ * ctx.diagnostics.register(myDiagnostics)
390
+ *
391
+ * // Through the shared lookup (loose typing):
392
+ * throw ctx.diagnostics.logger.MYP0001()
393
+ *
394
+ * // Or directly on the typed handle returned from `defineDiagnostics`:
395
+ * throw myDiagnostics.MYP0001()
396
+ * ```
389
397
  */
390
- interface BufferedChunk<T> {
391
- seq: number;
392
- chunk: T;
393
- }
394
- interface StreamSinkEvents<T> {
395
- /** Fired for each `write()`. The RPC layer subscribes and broadcasts. */
396
- chunk: (seq: number, chunk: T) => void;
397
- /** Terminal — fired exactly once per sink lifetime. */
398
- end: (error?: StreamErrorPayload) => void;
399
- }
400
- interface CreateStreamSinkOptions {
401
- id?: string;
398
+ interface DevframeDiagnosticsHost {
402
399
  /**
403
- * Size of the per-stream ring buffer kept for replay-on-resubscribe.
404
- * `0` (default) disables replay.
400
+ * Proxy-backed lookup of every registered diagnostic handle by code name.
401
+ * Resolves to a `nostics` `DiagnosticHandle` — a callable that builds a
402
+ * diagnostic and routes it through registered reporters; prefix with
403
+ * `throw` to raise. Loosely typed — for autocompletion, keep a reference
404
+ * to the typed result of `defineDiagnostics()` instead.
405
405
  */
406
- replayWindow?: number;
407
- }
408
- /**
409
- * Server-side producer handle. Two equivalent surfaces share one piece of
410
- * state: the imperative `write/error/close` triple, and a `WritableStream<T>`
411
- * for `pipeTo`-style consumption.
412
- */
413
- interface StreamSink<T> {
414
- /** Stable id used by clients to subscribe. */
415
- readonly id: string;
406
+ readonly logger: DevframeDiagnosticsLogger;
416
407
  /**
417
- * Aborts when the consumer cancels (server-side) or when the transport
418
- * loses every subscriber. Producers should poll `signal.aborted` and exit
419
- * cleanly.
408
+ * Register additional diagnostic definitions with this host. After
409
+ * registration, codes from the new definition are reachable via
410
+ * `host.logger.CODE`. Plugins that want shared output formatting should
411
+ * build their diagnostics via `host.defineDiagnostics()` first — that
412
+ * factory pre-wires the host's ANSI console reporter.
420
413
  */
421
- readonly signal: AbortSignal;
422
- /** `true` after `close()` / `error()`. Further writes throw. */
423
- readonly closed: boolean;
424
- /** Last allocated sequence number. `0` until the first write. */
425
- readonly lastSeq: number;
426
- write: (chunk: T) => void;
427
- error: (reason: unknown) => void;
428
- close: () => void;
429
- /** External-cancel path. Aborts the signal so handlers can short-circuit. */
430
- abort: (reason?: unknown) => void;
431
- /** `WritableStream<T>` adapter — same in-memory state as the imperative API. */
432
- readonly writable: WritableStream<T>;
433
- /**
434
- * Internal — RPC layer subscribes to receive chunk/end notifications.
435
- * Not part of the public contract; do not call directly.
436
- *
437
- * @internal
438
- */
439
- readonly events: EventEmitter<StreamSinkEvents<T>>;
440
- /**
441
- * Internal — replay buffer. RPC layer reads on (re)subscribe to feed
442
- * missed chunks before going live.
443
- *
444
- * @internal
445
- */
446
- readonly buffer: ReadonlyArray<BufferedChunk<T>>;
447
- }
448
- interface CreateStreamReaderOptions {
449
- id?: string;
450
- /**
451
- * Maximum number of buffered chunks held client-side while the consumer
452
- * isn't draining. On overflow, the oldest chunk is dropped.
453
- */
454
- highWaterMark?: number;
455
- /**
456
- * Called when the chunk queue overflows the high-water mark. The RPC
457
- * layer wires this to a coded warning; the primitive itself is
458
- * RPC-agnostic.
459
- */
460
- onOverflow?: (dropped: number) => void;
461
- /** Called when the consumer cancels — the RPC layer sends `:cancel` upstream. */
462
- onCancel?: () => void;
463
- }
464
- /**
465
- * Client-side consumer handle. Both an `AsyncIterable<T>` (for `for await`)
466
- * and exposes `readable: ReadableStream<T>` (for `pipeTo`). Pick one — they
467
- * share a single internal queue, so concurrent draining will race.
468
- */
469
- interface StreamReader<T> extends AsyncIterable<T> {
470
- readonly id: string;
471
- readonly cancelled: boolean;
472
- readonly done: boolean;
473
- /** Highest `seq` observed. Used for replay on reconnect. */
474
- readonly lastSeenSeq: number;
475
- /** `ReadableStream<T>` adapter for `pipeTo`-style consumption. */
476
- readonly readable: ReadableStream<T>;
477
- cancel: () => void;
478
- /** @internal */
479
- _push: (seq: number, chunk: T) => void;
480
- /** @internal */
481
- _end: (error?: StreamErrorPayload) => void;
482
- }
483
- /**
484
- * Build a server-side stream sink. RPC-agnostic — the RPC host wires
485
- * `events.on('chunk' | 'end')` to broadcast, and reads `buffer` to replay
486
- * for late or reconnecting subscribers.
487
- */
488
- declare function createStreamSink<T>(options?: CreateStreamSinkOptions): StreamSink<T>;
489
- /**
490
- * Build a client-side stream reader. RPC-agnostic — the RPC host calls
491
- * `_push(seq, chunk)` on each incoming chunk and `_end(error?)` on the
492
- * terminal frame. Consumers iterate with `for await` or pipe `readable`.
493
- */
494
- declare function createStreamReader<T>(options?: CreateStreamReaderOptions): StreamReader<T>;
495
- //#endregion
496
- //#region src/types/diagnostics.d.ts
497
- /**
498
- * The shared diagnostics lookup exposed by the host. A `Proxy` that resolves
499
- * any registered code name to its `nostics` handle (a callable that builds
500
- * a diagnostic and routes it through registered reporters). Typed loosely
501
- * because it spans heterogeneous definitions registered by different
502
- * integrations.
503
- */
504
- type DevframeDiagnosticsLogger = Record<string, any>;
505
- /**
506
- * Host for structured diagnostics — a thin layer over `nostics` that lets
507
- * integrations register their own coded errors/warnings into a shared
508
- * registry without taking a direct dependency on `nostics`.
509
- *
510
- * Typical usage from a plugin's `setup(ctx)`:
511
- *
512
- * ```ts
513
- * const myDiagnostics = ctx.diagnostics.defineDiagnostics({
514
- * docsBase: 'https://example.com/errors',
515
- * codes: {
516
- * MYP0001: { why: 'Something went wrong' },
517
- * },
518
- * })
519
- * ctx.diagnostics.register(myDiagnostics)
520
- *
521
- * // Through the shared lookup (loose typing):
522
- * throw ctx.diagnostics.logger.MYP0001()
523
- *
524
- * // Or directly on the typed handle returned from `defineDiagnostics`:
525
- * throw myDiagnostics.MYP0001()
526
- * ```
527
- */
528
- interface DevframeDiagnosticsHost {
529
- /**
530
- * Proxy-backed lookup of every registered diagnostic handle by code name.
531
- * Resolves to a `nostics` `DiagnosticHandle` — a callable that builds a
532
- * diagnostic and routes it through registered reporters; prefix with
533
- * `throw` to raise. Loosely typed — for autocompletion, keep a reference
534
- * to the typed result of `defineDiagnostics()` instead.
535
- */
536
- readonly logger: DevframeDiagnosticsLogger;
537
- /**
538
- * Register additional diagnostic definitions with this host. After
539
- * registration, codes from the new definition are reachable via
540
- * `host.logger.CODE`. Plugins that want shared output formatting should
541
- * build their diagnostics via `host.defineDiagnostics()` first — that
542
- * factory pre-wires the host's ANSI console reporter.
543
- */
544
- register: (definitions: Record<string, unknown>) => void;
414
+ register: (definitions: Record<string, unknown>) => void;
545
415
  /**
546
416
  * Build a typed diagnostics object with the host's ANSI console reporter
547
417
  * pre-wired. The same `devframe/utils/nostics` `defineDiagnostics` every
@@ -620,123 +490,314 @@ interface DevframeHost {
620
490
  */
621
491
  type DevframeStorageScope = 'workspace' | 'project' | 'global';
622
492
  //#endregion
623
- //#region src/types/views.d.ts
624
- interface DevframeViewHost {
493
+ //#region src/types/rpc-augments.d.ts
494
+ /**
495
+ * To be extended
496
+ */
497
+ interface DevframeRpcClientFunctions {
625
498
  /**
499
+ * Server→client notification that this connection's auth token has been
500
+ * revoked. The client drops to untrusted on receipt. Broadcast by
501
+ * `revokeActiveConnectionsForToken`.
502
+ *
626
503
  * @internal
627
504
  */
628
- buildStaticDirs: {
629
- baseUrl: string;
630
- source: StaticAssetsSource;
631
- }[];
505
+ 'devframe:auth:revoked': () => Promise<void>;
632
506
  /**
633
- * Helper to host static files
634
- * - In `dev` mode, it will register middleware to `viteServer.middlewares` to host the static files
635
- * - In `build` mode, it will copy the static files to the dist directory
507
+ * Streaming chunk pushed from server to subscribed clients. Wired by
508
+ * `RpcStreamingHost`; do not register manually.
636
509
  *
637
- * Accepts a local dist directory, or a {@link StaticAssetsSource} remote
638
- * declaration served through devframe's caching CDN back-proxy.
510
+ * @internal
639
511
  */
640
- hostStatic: (baseUrl: string, source: StaticAssetsSource) => void;
641
- }
642
- //#endregion
643
- //#region src/types/scope.d.ts
644
- type AnyRpcFn = (...args: any[]) => any;
645
- /**
646
- * Resolve a scoped bare name `T` back to its fully-qualified entry in
647
- * `Registry` (`<NS>:<T>`). Indexing the plain registry behind a `keyof`
648
- * guard keeps TypeScript from rejecting a generic index into the
649
- * key-remapped `Scoped*Functions<NS>` maps (TS2536) once a plugin augments
650
- * the registry, while preserving each name's argument/return typing.
651
- */
652
- type ScopedRpcFn<Registry, NS extends string, T extends string> = `${NS}:${T}` extends keyof Registry ? Extract<Registry[`${NS}:${T}`], AnyRpcFn> : AnyRpcFn;
653
- /**
654
- * Augmentable registry mapping a scope namespace to the shape of its
655
- * persisted settings. Tools augment it so `ctx.scope('my-plugin')`
656
- * gets a fully-typed `settings.global` / `settings.project`:
657
- *
658
- * ```ts
659
- * declare module 'devframe' {
660
- * interface DevframeSettingsRegistry {
661
- * 'my-plugin': { theme: 'light' | 'dark', recentFiles: string[] }
662
- * }
663
- * }
664
- * ```
665
- */
666
- interface DevframeSettingsRegistry {}
667
- /**
668
- * Resolve the settings shape for a namespace from
669
- * {@link DevframeSettingsRegistry}, falling back to an open record when
670
- * the namespace hasn't been augmented.
671
- */
672
- type SettingsForNamespace<NS extends string> = NS extends keyof DevframeSettingsRegistry ? DevframeSettingsRegistry[NS] extends Record<string, any> ? DevframeSettingsRegistry[NS] : Record<string, any> : Record<string, any>;
673
- /**
674
- * A persisted key-value settings store for one scope (`global` or
675
- * `project`). Backed by a file on the node side and by the shared-state
676
- * sync protocol on the client, so a `set` on either side propagates to
677
- * every connected peer and survives restarts.
678
- *
679
- * Every method is async because the underlying shared state is resolved
680
- * lazily on first access.
681
- */
682
- interface DevframeSettingsStore<T extends Record<string, any> = Record<string, any>> {
683
- /** Read a single setting. Resolves to `undefined` when unset. */
684
- get: <K extends keyof T>(key: K) => Promise<T[K] | undefined>;
685
- /** Write a single setting. */
686
- set: <K extends keyof T>(key: K, value: T[K]) => Promise<void>;
687
- /** Remove a single setting. */
688
- delete: <K extends keyof T>(key: K) => Promise<void>;
689
- /** Read the whole settings object (immutable snapshot). */
690
- all: () => Promise<Readonly<T>>;
512
+ 'devframe:streaming:chunk': (channel: string, id: string, seq: number, chunk: any) => Promise<void>;
691
513
  /**
692
- * Subscribe to settings changes. Resolves to an unsubscribe function.
693
- * Fires on every local or remote mutation with the full snapshot.
514
+ * Streaming terminator pushed from server to subscribed clients. Wired by
515
+ * `RpcStreamingHost`; do not register manually.
516
+ *
517
+ * @internal
694
518
  */
695
- onChange: (fn: (value: Readonly<T>) => void) => Promise<() => void>;
696
- }
697
- /**
698
- * The two settings scopes available on a scoped context.
699
- *
700
- * - `project` — per-workspace state, persisted under the host's
701
- * `workspace` storage dir. Project-local settings.
702
- * - `global` — per-user state, persisted under the host's `global`
703
- * storage dir. Machine-wide preferences.
704
- */
705
- interface DevframeSettings<T extends Record<string, any> = Record<string, any>> {
706
- global: DevframeSettingsStore<T>;
707
- project: DevframeSettingsStore<T>;
708
- }
709
- /**
710
- * Map the server-side RPC registry down to the bare names owned by a
711
- * namespace, so a scoped `call('get-cwd')` is typed without the
712
- * `my-plugin:` prefix.
713
- */
714
- type ScopedServerFunctions<NS extends string> = { [K in keyof DevframeRpcServerFunctions as K extends `${NS}:${infer R}` ? R : never]: DevframeRpcServerFunctions[K]; };
715
- /**
716
- * Map the client-side RPC registry down to the bare names owned by a
717
- * namespace, used by scoped `broadcast`.
718
- */
719
- type ScopedClientFunctions<NS extends string> = { [K in keyof DevframeRpcClientFunctions as K extends `${NS}:${infer R}` ? R : never]: DevframeRpcClientFunctions[K]; };
720
- /**
721
- * Map the shared-state registry down to the bare keys owned by a
722
- * namespace, so scoped `sharedState('messages')` is typed without the
723
- * `my-plugin:` prefix.
724
- */
725
- type ScopedSharedStates<NS extends string> = { [K in keyof DevframeRpcSharedStates as K extends `${NS}:${infer R}` ? R : never]: DevframeRpcSharedStates[K]; };
726
- /**
727
- * Broadcast options for a scoped client method (bare name).
728
- */
729
- interface ScopedBroadcastOptions<METHOD, Args extends any[]> {
730
- method: METHOD;
731
- args: Args;
732
- optional?: boolean;
733
- event?: boolean;
519
+ 'devframe:streaming:end': (channel: string, id: string, error?: {
520
+ name: string;
521
+ message: string;
522
+ }) => Promise<void>;
523
+ /**
524
+ * Server→client cancel for an in-flight upload. Wired by
525
+ * `RpcStreamingHost`; do not register manually.
526
+ *
527
+ * @internal
528
+ */
529
+ 'devframe:streaming:upload-cancel': (channel: string, id: string) => Promise<void>;
530
+ /**
531
+ * Full shared-state snapshot pushed from server to subscribed clients.
532
+ * Wired by `RpcSharedStateHost`; do not register manually.
533
+ *
534
+ * @internal
535
+ */
536
+ 'devframe:rpc:client-state:updated': (key: string, fullState: any, syncId: string) => Promise<void>;
537
+ /**
538
+ * Incremental shared-state patch pushed from server to subscribed clients.
539
+ * Wired by `RpcSharedStateHost`; do not register manually.
540
+ *
541
+ * @internal
542
+ */
543
+ 'devframe:rpc:client-state:patch': (key: string, patches: any[], syncId: string) => Promise<void>;
734
544
  }
735
545
  /**
736
- * Node-side streaming host scoped to a namespace. Channel names are
737
- * auto-prefixed with `<namespace>:`.
546
+ * To be extended
738
547
  */
739
- interface DevframeScopedStreamingHost {
548
+ interface DevframeRpcServerFunctions {
549
+ /**
550
+ * Authenticate a connection with a previously-issued bearer token; resolves
551
+ * whether the connection is now trusted. The interactive handler is provided
552
+ * by the host adapter (e.g. Vite DevTools); the standalone server registers
553
+ * an auto-trust noop when `auth: false`.
554
+ *
555
+ * Named with the `anonymous:` prefix (see `isAnonymousRpcMethod`) so it is
556
+ * reachable before the connection is trusted — this is the *only* rule the
557
+ * pre-trust gate applies, no per-method allowlist.
558
+ *
559
+ * @internal
560
+ */
561
+ 'anonymous:devframe:auth': (params: {
562
+ authToken: string;
563
+ ua: string;
564
+ origin: string;
565
+ }) => Promise<{
566
+ isTrusted: boolean;
567
+ }>;
568
+ /**
569
+ * Exchange a one-time authentication code (shown by the dev server) for a fresh,
570
+ * node-issued bearer token, returning the token on success or `null`. The
571
+ * handler is provided by the host adapter on top of `exchangeTempAuthCode`.
572
+ *
573
+ * Named with the `anonymous:` prefix (see `isAnonymousRpcMethod`) so it is
574
+ * reachable before the connection is trusted.
575
+ *
576
+ * @internal
577
+ */
578
+ 'anonymous:devframe:auth:exchange': (params: {
579
+ code: string;
580
+ ua: string;
581
+ origin: string;
582
+ }) => Promise<{
583
+ authToken: string | null;
584
+ }>;
585
+ /**
586
+ * Self-revoke: the caller asks the server to revoke its own bearer token
587
+ * (if any) and drop to untrusted. Requires an already-trusted caller, so
588
+ * unlike the two handshake methods above it does **not** carry the
589
+ * `anonymous:` prefix. Registered by `recipes/interactive-auth`.
590
+ *
591
+ * @internal
592
+ */
593
+ 'devframe:auth:revoke': () => Promise<void>;
594
+ /**
595
+ * Subscribe a client to a shared-state key. Wired by
596
+ * `RpcSharedStateHost`; do not register manually.
597
+ *
598
+ * @internal
599
+ */
600
+ 'devframe:rpc:server-state:subscribe': (key: string) => Promise<void>;
601
+ /**
602
+ * Read the current value for a shared-state key. Wired by
603
+ * `RpcSharedStateHost`; do not register manually.
604
+ *
605
+ * @internal
606
+ */
607
+ 'devframe:rpc:server-state:get': (key: string) => Promise<any>;
608
+ /**
609
+ * Replace a shared-state value (from the client). Wired by
610
+ * `RpcSharedStateHost`; do not register manually.
611
+ *
612
+ * @internal
613
+ */
614
+ 'devframe:rpc:server-state:set': (key: string, value: any, syncId: string) => Promise<void>;
615
+ /**
616
+ * Apply a patch to a shared-state value (from the client). Wired by
617
+ * `RpcSharedStateHost`; do not register manually.
618
+ *
619
+ * @internal
620
+ */
621
+ 'devframe:rpc:server-state:patch': (key: string, patches: any[], syncId: string) => Promise<void>;
622
+ /**
623
+ * Client→server streaming subscription with optional replay cursor.
624
+ * Wired by `RpcStreamingHost`; do not register manually.
625
+ *
626
+ * @internal
627
+ */
628
+ 'devframe:streaming:subscribe': (channel: string, id: string, opts?: {
629
+ afterSeq?: number;
630
+ }) => Promise<void>;
631
+ /**
632
+ * Client→server streaming unsubscribe. Wired by `RpcStreamingHost`;
633
+ * do not register manually.
634
+ *
635
+ * @internal
636
+ */
637
+ 'devframe:streaming:unsubscribe': (channel: string, id: string) => Promise<void>;
638
+ /**
639
+ * Client→server streaming cancellation request. Wired by
640
+ * `RpcStreamingHost`; do not register manually.
641
+ *
642
+ * @internal
643
+ */
644
+ 'devframe:streaming:cancel': (channel: string, id: string) => Promise<void>;
645
+ /**
646
+ * Client→server upload chunk. Wired by `RpcStreamingHost`; do not
647
+ * register manually.
648
+ *
649
+ * @internal
650
+ */
651
+ 'devframe:streaming:upload-chunk': (channel: string, id: string, seq: number, chunk: any) => Promise<void>;
652
+ /**
653
+ * Client→server upload terminator. Wired by `RpcStreamingHost`; do not
654
+ * register manually.
655
+ *
656
+ * @internal
657
+ */
658
+ 'devframe:streaming:upload-end': (channel: string, id: string, error?: {
659
+ name: string;
660
+ message: string;
661
+ }) => Promise<void>;
662
+ }
663
+ /**
664
+ * To be extended
665
+ */
666
+ interface DevframeRpcSharedStates {
667
+ /**
668
+ * Wire-service advertisements — package name → `{ package, version,
669
+ * scope, meta }` for every installed {@link import('./services').DevframeServiceDefinition}.
670
+ * Written by the node services host at the `ready()` barrier; clients read
671
+ * it through `client.services` (or subscribe to it directly for
672
+ * reactivity). Read-only from the browser.
673
+ */
674
+ 'devframe:services': DevframeServicesState;
675
+ }
676
+ //#endregion
677
+ //#region src/types/views.d.ts
678
+ interface DevframeViewHost {
679
+ /**
680
+ * @internal
681
+ */
682
+ buildStaticDirs: {
683
+ baseUrl: string;
684
+ source: StaticAssetsSource;
685
+ }[];
686
+ /**
687
+ * Helper to host static files
688
+ * - In `dev` mode, it will register middleware to `viteServer.middlewares` to host the static files
689
+ * - In `build` mode, it will copy the static files to the dist directory
690
+ *
691
+ * Accepts a local dist directory, or a {@link StaticAssetsSource} remote
692
+ * declaration served through devframe's caching CDN back-proxy.
693
+ *
694
+ * `defaultResolveFrom` overrides, for this call only, the context's own
695
+ * `importMetaUrl` as the default `resolveFrom` for a remote source that
696
+ * doesn't set one. A shared host that mounts assets on behalf of another
697
+ * devframe (a hub installing a plugin) passes that plugin's `importMetaUrl`
698
+ * so the assets resolve against the plugin's dependency graph rather than
699
+ * the host's.
700
+ */
701
+ hostStatic: (baseUrl: string, source: StaticAssetsSource, defaultResolveFrom?: string | null) => void;
702
+ }
703
+ //#endregion
704
+ //#region src/types/scope.d.ts
705
+ type AnyRpcFn = (...args: any[]) => any;
706
+ /**
707
+ * Resolve a scoped bare name `T` back to its fully-qualified entry in
708
+ * `Registry` (`<NS>:<T>`). Indexing the plain registry behind a `keyof`
709
+ * guard keeps TypeScript from rejecting a generic index into the
710
+ * key-remapped `Scoped*Functions<NS>` maps (TS2536) once a plugin augments
711
+ * the registry, while preserving each name's argument/return typing.
712
+ */
713
+ type ScopedRpcFn<Registry, NS extends string, T extends string> = `${NS}:${T}` extends keyof Registry ? Extract<Registry[`${NS}:${T}`], AnyRpcFn> : AnyRpcFn;
714
+ /**
715
+ * Augmentable registry mapping a scope namespace to the shape of its
716
+ * persisted settings. Tools augment it so `ctx.scope('my-plugin')`
717
+ * gets a fully-typed `settings.global` / `settings.project`:
718
+ *
719
+ * ```ts
720
+ * declare module 'devframe' {
721
+ * interface DevframeSettingsRegistry {
722
+ * 'my-plugin': { theme: 'light' | 'dark', recentFiles: string[] }
723
+ * }
724
+ * }
725
+ * ```
726
+ */
727
+ interface DevframeSettingsRegistry {}
728
+ /**
729
+ * Resolve the settings shape for a namespace from
730
+ * {@link DevframeSettingsRegistry}, falling back to an open record when
731
+ * the namespace hasn't been augmented.
732
+ */
733
+ type SettingsForNamespace<NS extends string> = NS extends keyof DevframeSettingsRegistry ? DevframeSettingsRegistry[NS] extends Record<string, any> ? DevframeSettingsRegistry[NS] : Record<string, any> : Record<string, any>;
734
+ /**
735
+ * A persisted key-value settings store for one scope (`global` or
736
+ * `project`). Backed by a file on the node side and by the shared-state
737
+ * sync protocol on the client, so a `set` on either side propagates to
738
+ * every connected peer and survives restarts.
739
+ *
740
+ * Every method is async because the underlying shared state is resolved
741
+ * lazily on first access.
742
+ */
743
+ interface DevframeSettingsStore<T extends Record<string, any> = Record<string, any>> {
744
+ /** Read a single setting. Resolves to `undefined` when unset. */
745
+ get: <K extends keyof T>(key: K) => Promise<T[K] | undefined>;
746
+ /** Write a single setting. */
747
+ set: <K extends keyof T>(key: K, value: T[K]) => Promise<void>;
748
+ /** Remove a single setting. */
749
+ delete: <K extends keyof T>(key: K) => Promise<void>;
750
+ /** Read the whole settings object (immutable snapshot). */
751
+ all: () => Promise<Readonly<T>>;
752
+ /**
753
+ * Subscribe to settings changes. Resolves to an unsubscribe function.
754
+ * Fires on every local or remote mutation with the full snapshot.
755
+ */
756
+ onChange: (fn: (value: Readonly<T>) => void) => Promise<() => void>;
757
+ }
758
+ /**
759
+ * The two settings scopes available on a scoped context.
760
+ *
761
+ * - `project` — per-workspace state, persisted under the host's
762
+ * `workspace` storage dir. Project-local settings.
763
+ * - `global` — per-user state, persisted under the host's `global`
764
+ * storage dir. Machine-wide preferences.
765
+ */
766
+ interface DevframeSettings<T extends Record<string, any> = Record<string, any>> {
767
+ global: DevframeSettingsStore<T>;
768
+ project: DevframeSettingsStore<T>;
769
+ }
770
+ /**
771
+ * Map the server-side RPC registry down to the bare names owned by a
772
+ * namespace, so a scoped `call('get-cwd')` is typed without the
773
+ * `my-plugin:` prefix.
774
+ */
775
+ type ScopedServerFunctions<NS extends string> = { [K in keyof DevframeRpcServerFunctions as K extends `${NS}:${infer R}` ? R : never]: DevframeRpcServerFunctions[K]; };
776
+ /**
777
+ * Map the client-side RPC registry down to the bare names owned by a
778
+ * namespace, used by scoped `broadcast`.
779
+ */
780
+ type ScopedClientFunctions<NS extends string> = { [K in keyof DevframeRpcClientFunctions as K extends `${NS}:${infer R}` ? R : never]: DevframeRpcClientFunctions[K]; };
781
+ /**
782
+ * Map the shared-state registry down to the bare keys owned by a
783
+ * namespace, so scoped `sharedState('messages')` is typed without the
784
+ * `my-plugin:` prefix.
785
+ */
786
+ type ScopedSharedStates<NS extends string> = { [K in keyof DevframeRpcSharedStates as K extends `${NS}:${infer R}` ? R : never]: DevframeRpcSharedStates[K]; };
787
+ /**
788
+ * Broadcast options for a scoped client method (bare name).
789
+ */
790
+ interface ScopedBroadcastOptions<METHOD, Args extends any[]> {
791
+ method: METHOD;
792
+ args: Args;
793
+ optional?: boolean;
794
+ event?: boolean;
795
+ }
796
+ /**
797
+ * Node-side streaming host scoped to a namespace. Channel names are
798
+ * auto-prefixed with `<namespace>:`.
799
+ */
800
+ interface DevframeScopedStreamingHost {
740
801
  /**
741
802
  * Register a streaming channel. The bare `name` is auto-prefixed with
742
803
  * the scope namespace (`<namespace>:<name>`); pass an already-qualified
@@ -981,310 +1042,701 @@ interface DevframeServicesHost {
981
1042
  /** Ids of every currently-provided service. */
982
1043
  keys: () => string[];
983
1044
  /**
984
- * Install a **wire service** (see {@link DevframeServiceDefinition}).
985
- * Before {@link DevframeServicesHost.ready} fires, installs are queued —
986
- * option sets from every installer accumulate and each service is
987
- * constructed **once** at the barrier with the merged options; the
988
- * returned promise resolves with the service's node API then (or
989
- * `undefined` when an optional descriptor's package can't be imported).
990
- * After the barrier, installs construct immediately; installing an
991
- * already-installed package returns the existing API (a warning is
992
- * emitted when the late install carried options, since they're ignored).
1045
+ * Install a **wire service** (see {@link DevframeServiceDefinition}). The
1046
+ * common path is declarative — list services on `DevframeDefinition.services`
1047
+ * (or `initHub({ services })`) and the adapter installs them for you before
1048
+ * `setup` runs. Call `install()` directly only for the dynamic escape hatch:
1049
+ * a service configured at runtime from data unknown until then.
1050
+ *
1051
+ * Before the pre-setup ready fires, installs are queued and their option
1052
+ * sets deep-merged, constructing each service **once**. After it, an install
1053
+ * constructs immediately; installing an already-installed package returns
1054
+ * the existing API (a warning — `DF0066` — when the late install carried
1055
+ * options, since they're ignored). The returned promise resolves with the
1056
+ * node API (or `undefined` when an optional descriptor's package can't be
1057
+ * imported).
993
1058
  *
994
1059
  * `resolveFrom` is where a descriptor's package resolves **from**: a path
995
- * or file URL (e.g. `import.meta.url`), or an npm package name — typically
996
- * the declaring plugin's `packageName`, so its declared services resolve
997
- * against the plugin's own dependencies. Falls back to the context's
998
- * `workspaceRoot`.
1060
+ * or file URL (e.g. the declaring devframe's `importMetaUrl`), or an npm
1061
+ * package name, so its declared services resolve against the declarer's own
1062
+ * dependencies. Falls back to the context's `workspaceRoot` then `cwd`.
999
1063
  */
1000
1064
  install: <API = unknown, Options = any>(input: DevframeServiceInput<API, Options>, options?: {
1001
1065
  resolveFrom?: string | null;
1002
1066
  }) => Promise<API | undefined>;
1003
1067
  /**
1004
- * Fire the collect-then-setup barrier: resolve every queued descriptor
1005
- * (importing its package), merge option sets per service, construct each
1006
- * service once, `provide()` its node API under the package name, and
1007
- * advertise it to clients via the `devframe:services` shared state.
1008
- * Idempotent — adapters call it once after every devframe's `setup` has
1009
- * run; a repeat returns the same promise. Rejects when a `required`
1010
- * service fails to import or misses its version range.
1068
+ * Construct every queued service — importing descriptor packages, merging
1069
+ * option sets, `provide()`-ing each node API under its package name, and
1070
+ * advertising it to clients via the `devframe:services` shared state.
1071
+ * Idempotent. **Internal**: the adapters call it once, before running any
1072
+ * `setup`, so services are ready for `setup` to consume; application code
1073
+ * uses declarative `services` (or `install()` for the dynamic case) and
1074
+ * never calls this. Rejects when a `required` service fails to import or
1075
+ * misses its version range.
1011
1076
  */
1012
1077
  ready: () => Promise<void>;
1013
1078
  }
1014
1079
  //#endregion
1015
- //#region src/types/rpc-augments.d.ts
1080
+ //#region src/types/devframe.d.ts
1016
1081
  /**
1017
- * To be extended
1082
+ * Classification of how a devframe is being deployed. Hosted adapters
1083
+ * (`vite`, `embedded`) share their origin with a host app and must
1084
+ * namespace their mount path under `/__<id>/`. Standalone adapters
1085
+ * (`cli`, `build`) own the origin and default to `/`.
1018
1086
  */
1019
- interface DevframeRpcClientFunctions {
1020
- /**
1021
- * Server→client notification that this connection's auth token has been
1022
- * revoked. The client drops to untrusted on receipt. Broadcast by
1023
- * `revokeActiveConnectionsForToken`.
1024
- *
1025
- * @internal
1026
- */
1027
- 'devframe:auth:revoked': () => Promise<void>;
1087
+ type DevframeDeploymentKind = 'standalone' | 'hosted';
1088
+ /**
1089
+ * How a hub deduplicates devframes that share an `id` when more than one
1090
+ * is mounted onto the same hub. See {@link DevframeDefinition.duplicationStrategy}.
1091
+ *
1092
+ * - `'warn'` (default) — keep the first registration, drop later
1093
+ * duplicates, and emit a warning diagnostic (`DF8105`).
1094
+ * - `'silent'` — drop later duplicates without warning.
1095
+ * - `'throw'` — throw when a duplicate is mounted.
1096
+ * - `'duplicate'` — let every instance coexist under a disambiguated
1097
+ * dock id.
1098
+ */
1099
+ type DevframeDuplicationStrategy = 'warn' | 'silent' | 'throw' | 'duplicate';
1100
+ /**
1101
+ * Controls where the browser opens the RPC WebSocket — advertised in
1102
+ * `__connection.json` and used to bind the dev server. The three shapes map
1103
+ * to the three connection scenarios; precedence is `url` > `port` > `route`:
1104
+ *
1105
+ * 1. **Same server, different route** (default) — leave `port`/`url` unset.
1106
+ * The socket shares the HTTP server's port and binds to `route`
1107
+ * (`__ws`). The client connects to its own origin, so the link
1108
+ * survives a reverse proxy that rewrites the host/port/subpath.
1109
+ *
1110
+ * 2. **Different port** — set `port`. The socket binds on its own port on the
1111
+ * same host; the client targets `ws(s)://<page-host>:<port>/<route>`.
1112
+ *
1113
+ * 3. **Remote, different origin** — set `url` to a full `ws://`/`wss://`
1114
+ * endpoint (e.g. a public tunnel or relay). The client uses it verbatim.
1115
+ */
1116
+ interface DevframeWsOptions {
1028
1117
  /**
1029
- * Streaming chunk pushed from server to subscribed clients. Wired by
1030
- * `RpcStreamingHost`; do not register manually.
1031
- *
1032
- * @internal
1118
+ * Upgrade route segment the socket binds to and is advertised at, relative
1119
+ * to the SPA base. Default: `__ws`.
1033
1120
  */
1034
- 'devframe:streaming:chunk': (channel: string, id: string, seq: number, chunk: any) => Promise<void>;
1121
+ route?: string;
1035
1122
  /**
1036
- * Streaming terminator pushed from server to subscribed clients. Wired by
1037
- * `RpcStreamingHost`; do not register manually.
1038
- *
1039
- * @internal
1123
+ * Bind the socket on its own port instead of sharing the HTTP port. The
1124
+ * browser connects to this port on the page's hostname. Implies
1125
+ * {@link DevframeWsOptions.sidecar}.
1040
1126
  */
1041
- 'devframe:streaming:end': (channel: string, id: string, error?: {
1042
- name: string;
1043
- message: string;
1044
- }) => Promise<void>;
1127
+ port?: number;
1045
1128
  /**
1046
- * Server→client cancel for an in-flight upload. Wired by
1047
- * `RpcStreamingHost`; do not register manually.
1048
- *
1049
- * @internal
1129
+ * Start a side-car WebSocket server on a free port, for hosts whose request
1130
+ * handlers can't accept upgrades (Next.js route handlers, Nitro, Rsbuild).
1131
+ * The resolved port is advertised in `__connection.json`, so the browser
1132
+ * finds it without any further wiring. Set `port` instead to pin it.
1050
1133
  */
1051
- 'devframe:streaming:upload-cancel': (channel: string, id: string) => Promise<void>;
1134
+ sidecar?: boolean;
1052
1135
  /**
1053
- * Full shared-state snapshot pushed from server to subscribed clients.
1054
- * Wired by `RpcSharedStateHost`; do not register manually.
1055
- *
1056
- * @internal
1136
+ * Advertise a fixed, fully-qualified endpoint on another origin (a full
1137
+ * `ws://`/`wss://` URL). Takes precedence over `port`/`route` in the meta.
1057
1138
  */
1058
- 'devframe:rpc:client-state:updated': (key: string, fullState: any, syncId: string) => Promise<void>;
1139
+ url?: string;
1140
+ }
1141
+ /**
1142
+ * Controls the SSE RPC endpoint — the WebSocket-free transport for hosts
1143
+ * and proxies where the upgrade isn't available. It rides whatever HTTP
1144
+ * surface serves the instance (the same one serving `__connection.json`),
1145
+ * so a relative `route` always resolves; there is no port plumbing of its
1146
+ * own. Enabled by default alongside the WebSocket; pass `sse: false` to
1147
+ * disable, or `ws: false` to run SSE-only (`backend: 'sse'`).
1148
+ */
1149
+ interface DevframeSseOptions {
1059
1150
  /**
1060
- * Incremental shared-state patch pushed from server to subscribed clients.
1061
- * Wired by `RpcSharedStateHost`; do not register manually.
1062
- *
1063
- * @internal
1151
+ * Route segment the SSE endpoint binds to and is advertised at, relative
1152
+ * to the SPA base. Default: `__sse`.
1064
1153
  */
1065
- 'devframe:rpc:client-state:patch': (key: string, patches: any[], syncId: string) => Promise<void>;
1154
+ route?: string;
1066
1155
  }
1067
1156
  /**
1068
- * To be extended
1157
+ * Configuration for the route-based MCP server mounted alongside the dev
1158
+ * server (opt-in via {@link DevframeCliOptions.mcp}). The endpoint speaks
1159
+ * the MCP Streamable-HTTP transport over the same origin as the SPA,
1160
+ * exposing the definition's `ctx.agent` tools + shared-state resources to
1161
+ * external MCP clients connected to the *running* server.
1069
1162
  */
1070
- interface DevframeRpcServerFunctions {
1163
+ interface McpRouteOptions {
1071
1164
  /**
1072
- * Authenticate a connection with a previously-issued bearer token; resolves
1073
- * whether the connection is now trusted. The interactive handler is provided
1074
- * by the host adapter (e.g. Vite DevTools); the standalone server registers
1075
- * an auto-trust noop when `auth: false`.
1076
- *
1077
- * Named with the `anonymous:` prefix (see `isAnonymousRpcMethod`) so it is
1078
- * reachable before the connection is trusted — this is the *only* rule the
1079
- * pre-trust gate applies, no per-method allowlist.
1080
- *
1081
- * @internal
1165
+ * Route segment the MCP endpoint binds to, relative to the SPA base.
1166
+ * Default: `__mcp` (i.e. `/__mcp` standalone, `/__<id>/__mcp` hosted).
1082
1167
  */
1083
- 'anonymous:devframe:auth': (params: {
1084
- authToken: string;
1085
- ua: string;
1086
- origin: string;
1087
- }) => Promise<{
1088
- isTrusted: boolean;
1089
- }>;
1168
+ path?: string;
1090
1169
  /**
1091
- * Exchange a one-time authentication code (shown by the dev server) for a fresh,
1092
- * node-issued bearer token, returning the token on success or `null`. The
1093
- * handler is provided by the host adapter on top of `exchangeTempAuthCode`.
1094
- *
1095
- * Named with the `anonymous:` prefix (see `isAnonymousRpcMethod`) so it is
1096
- * reachable before the connection is trusted.
1170
+ * Extra `Origin` header values to accept beyond the loopback default
1171
+ * (`localhost`/`127.0.0.1`/`::1` and any `Origin`-less native client).
1172
+ * Add your LAN/tunnel origin here when reaching the endpoint from another
1173
+ * host, mirroring the WS transport's origin gate. Pass `false` to disable
1174
+ * origin checking entirely (not recommended). Default: loopback-only.
1097
1175
  *
1098
- * @internal
1176
+ * This is the endpoint's DNS-rebinding protection — the shared
1177
+ * `isAllowedOrigin` gate the WS upgrade already uses, applied as external
1178
+ * middleware (the approach the MCP SDK now recommends over its own
1179
+ * deprecated `allowedHosts`/`allowedOrigins` transport flags).
1099
1180
  */
1100
- 'anonymous:devframe:auth:exchange': (params: {
1101
- code: string;
1102
- ua: string;
1103
- origin: string;
1104
- }) => Promise<{
1105
- authToken: string | null;
1106
- }>;
1181
+ allowedOrigins?: readonly string[] | false;
1182
+ }
1183
+ interface DevframeCliOptions {
1184
+ /** Binary name; default: the devframe's `id`. */
1185
+ command?: string;
1186
+ /** Preferred port for the dev server (default 9999). */
1187
+ port?: number;
1188
+ /** Port scan range, forwarded to `get-port-please`. */
1189
+ portRange?: [number, number];
1190
+ /** Prefer a random open port. */
1191
+ random?: boolean;
1192
+ /** Default host to bind to; `--host` overrides. */
1193
+ host?: string;
1107
1194
  /**
1108
- * Self-revoke: the caller asks the server to revoke its own bearer token
1109
- * (if any) and drop to untrusted. Requires an already-trusted caller, so
1110
- * unlike the two handshake methods above it does **not** carry the
1111
- * `anonymous:` prefix. Registered by `recipes/interactive-auth`.
1112
- *
1113
- * @internal
1195
+ * Auto-open the browser when the dev server starts.
1196
+ * `true` opens the resolved origin; a string opens that relative path.
1197
+ * The `--open` / `--no-open` flags override this.
1114
1198
  */
1115
- 'devframe:auth:revoke': () => Promise<void>;
1199
+ open?: boolean | string;
1116
1200
  /**
1117
- * Subscribe a client to a shared-state key. Wired by
1118
- * `RpcSharedStateHost`; do not register manually.
1201
+ * Authentication for the standalone dev server.
1119
1202
  *
1120
- * @internal
1121
- */
1122
- 'devframe:rpc:server-state:subscribe': (key: string) => Promise<void>;
1123
- /**
1124
- * Read the current value for a shared-state key. Wired by
1125
- * `RpcSharedStateHost`; do not register manually.
1203
+ * - `undefined` / `true` — the standalone adapters (`cli` /
1204
+ * served `build`) auto-wire devframe's interactive OTP auth
1205
+ * (`createInteractiveAuth`): an untrusted client can only reach
1206
+ * `anonymous:` methods until it exchanges the printed one-time code.
1207
+ * The adapter prints the code + magic-link banner once the server is
1208
+ * listening.
1209
+ * - `false` — no gate, for trusted single-user localhost tools where an
1210
+ * auth round-trip only gets in the way (the built-in plugins set this).
1211
+ * The `--no-auth` CLI flag maps here for one-off runs.
1212
+ * - A {@link DevframeAuthHandler} — a custom handler (e.g. a tuned
1213
+ * `createInteractiveAuth`, or an entirely different scheme) passed
1214
+ * straight through to the RPC transport binding.
1126
1215
  *
1127
- * @internal
1128
- */
1129
- 'devframe:rpc:server-state:get': (key: string) => Promise<any>;
1130
- /**
1131
- * Replace a shared-state value (from the client). Wired by
1132
- * `RpcSharedStateHost`; do not register manually.
1216
+ * Hosted adapters (`vite`, `embedded`) ignore this and defer to the host's
1217
+ * auth; `@vitejs/devtools` honors the equivalent `devtools.clientAuth`.
1133
1218
  *
1134
- * @internal
1219
+ * @default true
1135
1220
  */
1136
- 'devframe:rpc:server-state:set': (key: string, value: any, syncId: string) => Promise<void>;
1221
+ auth?: boolean | DevframeAuthHandler;
1137
1222
  /**
1138
- * Apply a patch to a shared-state value (from the client). Wired by
1139
- * `RpcSharedStateHost`; do not register manually.
1223
+ * Expose a route-based MCP server alongside the dev server, speaking the
1224
+ * MCP Streamable-HTTP transport at `/__mcp` (relative to the base path).
1225
+ * It surfaces the same `ctx.agent` tools + shared-state resources as the
1226
+ * stdio `mcp` command, but against the live, running server.
1140
1227
  *
1141
- * @internal
1142
- */
1143
- 'devframe:rpc:server-state:patch': (key: string, patches: any[], syncId: string) => Promise<void>;
1144
- /**
1145
- * Client→server streaming subscription with optional replay cursor.
1146
- * Wired by `RpcStreamingHost`; do not register manually.
1228
+ * - `false` / omitted (default) — no MCP route is mounted.
1229
+ * - `true` — mount at the default `__mcp` route with the loopback-only
1230
+ * origin gate.
1231
+ * - {@link McpRouteOptions} — customise the route path / allowed origins.
1147
1232
  *
1148
- * @internal
1233
+ * The `--mcp` / `--no-mcp` CLI flags override this per run.
1149
1234
  */
1150
- 'devframe:streaming:subscribe': (channel: string, id: string, opts?: {
1151
- afterSeq?: number;
1152
- }) => Promise<void>;
1235
+ mcp?: boolean | McpRouteOptions;
1153
1236
  /**
1154
- * Client→server streaming unsubscribe. Wired by `RpcStreamingHost`;
1155
- * do not register manually.
1156
- *
1157
- * @internal
1237
+ * Author's SPA dist — served as the devframe's UI. A local directory, or
1238
+ * a {@link StaticAssetsSource} remote declaration (`{ package, version }`)
1239
+ * served through devframe's caching CDN back-proxy so the assets need not
1240
+ * ship inside the node package.
1158
1241
  */
1159
- 'devframe:streaming:unsubscribe': (channel: string, id: string) => Promise<void>;
1242
+ distDir?: StaticAssetsSource;
1160
1243
  /**
1161
- * Client→server streaming cancellation request. Wired by
1162
- * `RpcStreamingHost`; do not register manually.
1163
- *
1164
- * @internal
1244
+ * How the browser reaches the RPC WebSocket. Defaults to sharing the HTTP
1245
+ * port on the `__ws` route. See {@link DevframeWsOptions} for the
1246
+ * different-port and remote-origin variants. Pass `false` to serve no
1247
+ * WebSocket at all — clients connect over SSE instead (`backend: 'sse'`).
1165
1248
  */
1166
- 'devframe:streaming:cancel': (channel: string, id: string) => Promise<void>;
1249
+ ws?: DevframeWsOptions | false;
1167
1250
  /**
1168
- * Client→server upload chunk. Wired by `RpcStreamingHost`; do not
1169
- * register manually.
1170
- *
1171
- * @internal
1251
+ * How the browser reaches the SSE RPC endpoint — enabled by default
1252
+ * alongside the WebSocket as the more portable transport. Pass `false`
1253
+ * to disable, or a {@link DevframeSseOptions} to change its route.
1172
1254
  */
1173
- 'devframe:streaming:upload-chunk': (channel: string, id: string, seq: number, chunk: any) => Promise<void>;
1255
+ sse?: boolean | DevframeSseOptions;
1174
1256
  /**
1175
- * Client→server upload terminator. Wired by `RpcStreamingHost`; do not
1176
- * register manually.
1177
- *
1178
- * @internal
1179
- */
1180
- 'devframe:streaming:upload-end': (channel: string, id: string, error?: {
1181
- name: string;
1182
- message: string;
1183
- }) => Promise<void>;
1257
+ * Capability-side CAC hook. Called with the CAC instance after the
1258
+ * adapter registers its built-in commands (`build` / `mcp`)
1259
+ * but before `createCac`'s own `configureCli` caller. Use this to
1260
+ * contribute tool-specific flags and subcommands from the definition
1261
+ * itself.
1262
+ */
1263
+ configure?: (cli: CAC) => void;
1264
+ /**
1265
+ * Typed CLI flags for the default `dev` command, backed by any
1266
+ * [Standard Schema](https://standardschema.dev/) validator (valibot,
1267
+ * zod, arktype, or devframe's built-in `s`). The adapter registers
1268
+ * matching `--kebab-key` options on CAC, validates the parsed values,
1269
+ * and forwards the typed bag to `setup(ctx, { flags })`.
1270
+ *
1271
+ * Use {@link defineCliFlags} to preserve the literal schema-map
1272
+ * shape, and {@link InferCliFlags} to recover the typed output at the
1273
+ * call site:
1274
+ *
1275
+ * ```ts
1276
+ * const appFlags = defineCliFlags({
1277
+ * depth: v.pipe(v.number(), v.integer()),
1278
+ * config: v.optional(v.string()),
1279
+ * })
1280
+ *
1281
+ * defineDevframe({
1282
+ * cli: { flags: appFlags },
1283
+ * setup(ctx, info) {
1284
+ * const flags = info.flags as InferCliFlags<typeof appFlags>
1285
+ * },
1286
+ * })
1287
+ * ```
1288
+ */
1289
+ flags?: CliFlagsSchema;
1184
1290
  }
1185
1291
  /**
1186
- * To be extended
1292
+ * Default dock attributes for the iframe entry a hub synthesizes when it
1293
+ * mounts this devframe. Framework-neutral metadata only — the hub layer
1294
+ * (`ctx.install`) merges these beneath its per-mount `dock` overrides,
1295
+ * which in turn sit beneath the locked, derived `id` / `type` / `url`.
1296
+ *
1297
+ * Every field is optional. `title` / `icon` default to the definition's
1298
+ * `name` / `icon` when omitted here; the rest are unset by default.
1299
+ * Standalone adapters (`cli` / `build`) ignore this entirely.
1187
1300
  */
1188
- interface DevframeRpcSharedStates {
1301
+ interface DevframeDockDefaults {
1302
+ /** Dock entry title. Defaults to the definition's `name`. */
1303
+ title?: string;
1304
+ /** Dock entry icon. Defaults to the definition's `icon`. */
1305
+ icon?: string | {
1306
+ light: string;
1307
+ dark: string;
1308
+ };
1189
1309
  /**
1190
- * Wire-service advertisements — package name → `{ package, version,
1191
- * scope, meta }` for every installed {@link import('./services').DevframeServiceDefinition}.
1192
- * Written by the node services host at the `ready()` barrier; clients read
1193
- * it through `client.services` (or subscribe to it directly for
1194
- * reactivity). Read-only from the browser.
1310
+ * Sort weight within the dock; higher sorts earlier.
1311
+ * @default 0
1195
1312
  */
1196
- 'devframe:services': DevframeServicesState;
1197
- }
1198
- //#endregion
1199
- //#region src/types/rpc.d.ts
1200
- interface DevframeNodeRpcSession {
1201
- meta: DevframeNodeRpcSessionMeta;
1202
- rpc: BirpcReturn<DevframeRpcClientFunctions, DevframeRpcServerFunctions, false>;
1313
+ defaultOrder?: number;
1314
+ /**
1315
+ * Category the entry groups under in the dock.
1316
+ * @default 'default'
1317
+ */
1318
+ category?: string;
1319
+ /**
1320
+ * Conditional-visibility expression (same syntax as command `when`
1321
+ * clauses). Set to `'false'` to hide the entry unconditionally.
1322
+ */
1323
+ when?: string;
1324
+ /**
1325
+ * Render-only visibility expression, same syntax as {@link when}. Hides the
1326
+ * entry's own dock-bar button when it evaluates to `false` while leaving it
1327
+ * registered and reachable (activation, RPC lookups, etc.) — unlike `when`,
1328
+ * which is the general relevance switch for the entry as a whole.
1329
+ */
1330
+ visibility?: string;
1331
+ /** Badge text rendered on the dock icon (e.g. an unread count). */
1332
+ badge?: string;
1333
+ /** Id of the dock group this entry collapses under, if any. */
1334
+ groupId?: string;
1203
1335
  }
1204
- interface RpcBroadcastOptions<METHOD, Args extends any[]> {
1205
- method: METHOD;
1206
- args: Args;
1207
- optional?: boolean;
1208
- event?: boolean;
1209
- filter?: (client: BirpcReturn<DevframeRpcClientFunctions, DevframeRpcServerFunctions, false>) => boolean | void;
1336
+ /**
1337
+ * Runtime information threaded into `setup(ctx, info)`. Adapters
1338
+ * populate the fields that make sense for their deployment. In
1339
+ * particular, `createCac` fills `flags` with the parsed CAC bag.
1340
+ */
1341
+ interface DevframeSetupInfo {
1342
+ /** Parsed CLI flags, populated by the CLI adapter. */
1343
+ flags?: Record<string, unknown>;
1210
1344
  }
1211
- type RpcFunctionsHost = RpcFunctionsCollectorBase<DevframeRpcServerFunctions, DevframeNodeContext> & {
1345
+ interface DevframeDefinition {
1346
+ id: string;
1347
+ name: string;
1348
+ /** Semver of the tool, surfaced in hub UIs and diagnostics. */
1349
+ version: string;
1350
+ /** npm package name the devframe ships in (e.g. `@scope/my-tool`). */
1351
+ packageName: string;
1212
1352
  /**
1213
- * Invoke a locally registered server RPC function directly.
1353
+ * `import.meta.url` of the module that defines this devframe. **Always
1354
+ * provide it** (`importMetaUrl: import.meta.url`): it is the resolution base
1355
+ * for the tool's own dependency graph, which lets the host resolve
1356
+ * everything against the plugin's own installed packages rather than the
1357
+ * consuming app's.
1358
+ *
1359
+ * - **Remote assets** — becomes the default `resolveFrom` for any remote
1360
+ * {@link StaticAssetsSource} the devframe hosts (its `cli.distDir`, and
1361
+ * every `ctx.views.hostStatic` call) that doesn't set one explicitly, so a
1362
+ * locally installed copy of the assets package is served with zero
1363
+ * network. A per-asset `resolveFrom` still wins, and an explicit
1364
+ * `resolveFrom: null` still opts out.
1365
+ * - **Service dependencies** — becomes the base the host resolves declared
1366
+ * {@link DevframeServiceInput | services} from, so a plugin can ship a
1367
+ * service package as its own dependency instead of asking users to install
1368
+ * it.
1369
+ *
1370
+ * Optional for backward compatibility; omitting it falls back to
1371
+ * runtime-directory resolution and disables the zero-network installed-copy
1372
+ * fast paths above.
1373
+ */
1374
+ importMetaUrl?: string;
1375
+ /** Project homepage or documentation URL. */
1376
+ homepage: string;
1377
+ /** One-line summary of what the tool does. */
1378
+ description: string;
1379
+ icon?: string | {
1380
+ light: string;
1381
+ dark: string;
1382
+ };
1383
+ /**
1384
+ * Default dock attributes applied when a hub mounts this devframe as an
1385
+ * iframe dock entry. Consulted only by the hub install path (`ctx.install`),
1386
+ * which merge these beneath the per-mount `dock` overrides; standalone
1387
+ * adapters (`cli` / `build`) ignore it.
1214
1388
  *
1215
- * This bypasses transport and is useful for server-side cross-function calls.
1389
+ * @see {@link DevframeDockDefaults}
1216
1390
  */
1217
- invokeLocal: <T extends keyof DevframeRpcServerFunctions, Args extends Parameters<DevframeRpcServerFunctions[T]>>(method: T, ...args: Args) => Promise<Awaited<ReturnType<DevframeRpcServerFunctions[T]>>>;
1391
+ dock?: DevframeDockDefaults;
1218
1392
  /**
1219
- * Broadcast a message to all connected clients
1393
+ * Mount path override. Defaults depend on the adapter:
1394
+ * `/` for standalone (`cli` / `build`), `/__<id>/` for hosted
1395
+ * (`vite` / `embedded`).
1220
1396
  */
1221
- broadcast: <T extends keyof DevframeRpcClientFunctions, Args extends Parameters<DevframeRpcClientFunctions[T]>>(options: RpcBroadcastOptions<T, Args>) => Promise<void>;
1397
+ basePath?: string;
1222
1398
  /**
1223
- * Get the current RPC client
1399
+ * How a hub reacts when another devframe sharing this one's `id` is
1400
+ * mounted onto the same hub. Consulted only by hub adapters
1401
+ * (`ctx.install`); standalone adapters (`cli` / `build`)
1402
+ * ignore it.
1224
1403
  *
1225
- * Available in RPC functions to get the current RPC client
1404
+ * @default 'warn'
1226
1405
  */
1227
- getCurrentRpcSession: () => DevframeNodeRpcSession | undefined;
1406
+ duplicationStrategy?: DevframeDuplicationStrategy;
1228
1407
  /**
1229
- * The shared state host
1408
+ * Declares which runtimes meaningfully support this devframe. Adapters
1409
+ * act on a `false` before doing any work:
1410
+ *
1411
+ * - `build: false` — `createCac` skips registering the `build` subcommand,
1412
+ * and `createBuild` refuses (throws `DF0042`) unless `{ force: true }`.
1413
+ * Useful for a devframe whose value is inherently live (e.g. it manages
1414
+ * real files on disk), so a static export would only ever produce a
1415
+ * broken, write-less shell of the tool.
1416
+ * - `dev: false` — `createDevServer` refuses (throws `DF0058`) unless
1417
+ * `{ force: true }`. Useful for a devframe that only makes sense as a
1418
+ * static export (e.g. a report generator with nothing to serve live).
1230
1419
  */
1231
- sharedState: RpcSharedStateHost;
1420
+ capabilities?: {
1421
+ dev?: boolean;
1422
+ build?: boolean;
1423
+ };
1232
1424
  /**
1233
- * The streaming channel host. Provides per-channel `start()` /
1234
- * `pipeFrom()` producers; clients consume via `rpc.streaming.subscribe()`.
1235
- *
1236
- * @see RpcStreamingHost
1425
+ * Wire services this devframe consumes (see `DevframeServiceDefinition`).
1426
+ * Each entry is either a declarative descriptor
1427
+ * (`{ package, version?, required?, options? }` — the host imports the
1428
+ * package's default-export factory, resolving it against **this plugin's
1429
+ * own dependencies**) or a ready `DevframeServiceDefinition` (the factory
1430
+ * was already called). The adapter queues these before `setup(ctx)` runs
1431
+ * and constructs each service once at the `ctx.services.ready()` barrier,
1432
+ * merging option sets across every declarer. Missing services are skipped
1433
+ * unless marked `required` — clients feature-detect via
1434
+ * `client.services.has(pkg)` and degrade.
1237
1435
  */
1238
- streaming: RpcStreamingHost;
1436
+ services?: DevframeServiceInput[];
1437
+ /** RPC-level configuration for this devframe (see {@link DevframeRpcOptions}). */
1438
+ rpc?: DevframeRpcOptions;
1439
+ /** Server-side setup — the primary entrypoint. Runs in every runtime. */
1440
+ setup: (ctx: DevframeNodeContext, info?: DevframeSetupInfo) => void | Promise<void>;
1441
+ cli?: DevframeCliOptions;
1442
+ }
1443
+ interface DevframeRpcOptions {
1444
+ /**
1445
+ * Opt an RPC function into the static-build snapshot **without owning its
1446
+ * definition** — the mechanism a devframe uses to bake a wire service's
1447
+ * RPC (e.g. `@devframes/service-git`'s `status`/`log`/`show`) into its
1448
+ * `build` export, since the service itself defines no `dump`/`snapshot`.
1449
+ *
1450
+ * Each entry is either a bare method id (bakes the no-argument call, like
1451
+ * `snapshot: true`) or `{ method, inputs }` where `inputs` is the list of
1452
+ * argument-tuples to bake — or an async provider given the node context
1453
+ * (so it can enumerate at build time, e.g. read commit hashes via the
1454
+ * service's node API). `createBuild` resolves these after setup and
1455
+ * executes the target's own handler per tuple; the first tuple's result
1456
+ * becomes the fallback so any call variant resolves to a baked value.
1457
+ */
1458
+ snapshot?: DevframeSnapshotRpcEntry[];
1459
+ }
1460
+ /** Argument-tuples to bake for a {@link DevframeSnapshotRpcEntry}, or a provider that computes them at build time. */
1461
+ type DevframeSnapshotRpcInputs = readonly (readonly unknown[])[] | ((ctx: DevframeNodeContext) => readonly (readonly unknown[])[] | Promise<readonly (readonly unknown[])[]>);
1462
+ /**
1463
+ * One {@link DevframeRpcOptions.snapshot} entry: a bare method id (bakes
1464
+ * the no-argument call) or a method plus the argument-tuples to bake.
1465
+ */
1466
+ type DevframeSnapshotRpcEntry = string | {
1467
+ method: string;
1468
+ inputs: DevframeSnapshotRpcInputs;
1239
1469
  };
1240
- interface RpcSharedStateGetOptions<T> {
1241
- sharedState?: SharedState<T>;
1242
- initialValue?: T;
1470
+ //#endregion
1471
+ //#region src/utils/shared-state.d.ts
1472
+ type ImmutablePrimitive = undefined | null | boolean | string | number | Function;
1473
+ type Immutable<T> = T extends ImmutablePrimitive ? T : T extends Array<infer U> ? ImmutableArray<U> : T extends Map<infer K, infer V> ? ImmutableMap<K, V> : T extends Set<infer M> ? ImmutableSet<M> : ImmutableObject<T>;
1474
+ type ImmutableArray<T> = ReadonlyArray<Immutable<T>>;
1475
+ type ImmutableMap<K, V> = ReadonlyMap<Immutable<K>, Immutable<V>>;
1476
+ type ImmutableSet<T> = ReadonlySet<Immutable<T>>;
1477
+ type ImmutableObject<T> = { readonly [K in keyof T]: Immutable<T[K]>; };
1478
+ /**
1479
+ * Serializable patch describing a single mutation to a `SharedState`.
1480
+ * Structurally compatible with JSON-Patch and is safe to send over RPC.
1481
+ */
1482
+ interface SharedStatePatch {
1483
+ op: 'add' | 'remove' | 'replace';
1484
+ path: readonly (string | number)[];
1485
+ value?: unknown;
1243
1486
  }
1244
- interface RpcSharedStateHost {
1245
- get: <T extends object = any>(key: string, options?: RpcSharedStateGetOptions<T>) => Promise<SharedState<T>>;
1246
- keys: () => string[];
1487
+ /**
1488
+ * State host that is immutable by default with explicit mutate.
1489
+ */
1490
+ interface SharedState<T> {
1247
1491
  /**
1248
- * Subscribe to new shared-state keys becoming available. Fires when
1249
- * `get(key, ...)` creates a fresh entry (not on subsequent gets).
1250
- * Useful for protocol adapters (e.g. MCP) that surface shared state
1251
- * as dynamic resources.
1492
+ * Get the current state. Immutable.
1252
1493
  */
1253
- onKeyAdded: (fn: (key: string) => void) => () => void;
1494
+ value: () => Immutable<T>;
1254
1495
  /**
1255
- * Unregister a shared state and drop its broadcast listeners. Returns
1256
- * `true` when a state was removed, `false` when the key was unknown.
1257
- * Used by short-lived states (e.g. a disposed JSON-render view) to avoid
1258
- * leaking listeners and lingering entries for the context lifetime.
1496
+ * Subscribe to state changes.
1259
1497
  */
1260
- delete: (key: string) => boolean;
1261
- }
1262
- /**
1263
- * Options for `RpcStreamingHost.create()`.
1264
- */
1265
- interface RpcStreamingChannelOptions {
1498
+ on: EventEmitter<SharedStateEvents<T>>['on'];
1266
1499
  /**
1267
- * Size of the per-stream ring buffer kept on the server for
1268
- * replay-on-resubscribe. `0` (default) disables replay; on reconnect
1269
- * the consumer only sees chunks that arrive after subscribing.
1270
- *
1271
- * The buffer is per stream id, not per channel — each `channel.start()`
1272
- * gets its own.
1500
+ * Mutate the state.
1273
1501
  */
1274
- replayWindow?: number;
1502
+ mutate: (fn: (state: T) => void, syncId?: string) => void;
1275
1503
  /**
1276
- * Milliseconds a closed stream is retained on the server after its
1277
- * last subscriber leaves (or if no subscriber ever arrived). During
1278
- * this window, late subscribers can still join and replay the buffer
1279
- * + receive the `end` frame.
1280
- *
1281
- * Defaults to `30_000` (30 s) when `replayWindow > 0`, else `0`
1282
- * (immediate free). Set to `0` to opt out, or higher for longer
1283
- * post-mortem replay.
1504
+ * Apply patches to the state.
1284
1505
  */
1285
- closedStreamRetention?: number;
1506
+ patch: (patches: SharedStatePatch[], syncId?: string) => void;
1507
+ /**
1508
+ * Sync IDs that have been applied to the state.
1509
+ */
1510
+ syncIds: Set<string>;
1286
1511
  }
1287
- /**
1512
+ interface SharedStateEvents<T> {
1513
+ updated: (fullState: T, patches: SharedStatePatch[] | undefined, syncId: string) => void;
1514
+ }
1515
+ interface SharedStateOptions<T> {
1516
+ /**
1517
+ * Initial state.
1518
+ */
1519
+ initialValue: T;
1520
+ /**
1521
+ * Enable patches.
1522
+ *
1523
+ * @default false
1524
+ */
1525
+ enablePatches?: boolean;
1526
+ }
1527
+ declare function createSharedState<T extends object>(options: SharedStateOptions<T>): SharedState<T>;
1528
+ //#endregion
1529
+ //#region src/utils/streaming-channel.d.ts
1530
+ /**
1531
+ * Serialized error shape sent over the wire when a stream ends with a failure.
1532
+ * Stays JSON-safe so the strict-JSON encoder can carry it without coercion.
1533
+ */
1534
+ interface StreamErrorPayload {
1535
+ name: string;
1536
+ message: string;
1537
+ }
1538
+ /**
1539
+ * Single buffered chunk in the server-side ring buffer.
1540
+ *
1541
+ * Sequence numbers start at 1 and increment per write. Subscribers track
1542
+ * `lastSeenSeq` and ask for `afterSeq` on resubscribe so the server can
1543
+ * replay any chunks the client missed during a brief disconnect.
1544
+ */
1545
+ interface BufferedChunk<T> {
1546
+ seq: number;
1547
+ chunk: T;
1548
+ }
1549
+ interface StreamSinkEvents<T> {
1550
+ /** Fired for each `write()`. The RPC layer subscribes and broadcasts. */
1551
+ chunk: (seq: number, chunk: T) => void;
1552
+ /** Terminal — fired exactly once per sink lifetime. */
1553
+ end: (error?: StreamErrorPayload) => void;
1554
+ }
1555
+ interface CreateStreamSinkOptions {
1556
+ id?: string;
1557
+ /**
1558
+ * Size of the per-stream ring buffer kept for replay-on-resubscribe.
1559
+ * `0` (default) disables replay.
1560
+ */
1561
+ replayWindow?: number;
1562
+ }
1563
+ /**
1564
+ * Server-side producer handle. Two equivalent surfaces share one piece of
1565
+ * state: the imperative `write/error/close` triple, and a `WritableStream<T>`
1566
+ * for `pipeTo`-style consumption.
1567
+ */
1568
+ interface StreamSink<T> {
1569
+ /** Stable id used by clients to subscribe. */
1570
+ readonly id: string;
1571
+ /**
1572
+ * Aborts when the consumer cancels (server-side) or when the transport
1573
+ * loses every subscriber. Producers should poll `signal.aborted` and exit
1574
+ * cleanly.
1575
+ */
1576
+ readonly signal: AbortSignal;
1577
+ /** `true` after `close()` / `error()`. Further writes throw. */
1578
+ readonly closed: boolean;
1579
+ /** Last allocated sequence number. `0` until the first write. */
1580
+ readonly lastSeq: number;
1581
+ write: (chunk: T) => void;
1582
+ error: (reason: unknown) => void;
1583
+ close: () => void;
1584
+ /** External-cancel path. Aborts the signal so handlers can short-circuit. */
1585
+ abort: (reason?: unknown) => void;
1586
+ /** `WritableStream<T>` adapter — same in-memory state as the imperative API. */
1587
+ readonly writable: WritableStream<T>;
1588
+ /**
1589
+ * Internal — RPC layer subscribes to receive chunk/end notifications.
1590
+ * Not part of the public contract; do not call directly.
1591
+ *
1592
+ * @internal
1593
+ */
1594
+ readonly events: EventEmitter<StreamSinkEvents<T>>;
1595
+ /**
1596
+ * Internal — replay buffer. RPC layer reads on (re)subscribe to feed
1597
+ * missed chunks before going live.
1598
+ *
1599
+ * @internal
1600
+ */
1601
+ readonly buffer: ReadonlyArray<BufferedChunk<T>>;
1602
+ }
1603
+ interface CreateStreamReaderOptions {
1604
+ id?: string;
1605
+ /**
1606
+ * Maximum number of buffered chunks held client-side while the consumer
1607
+ * isn't draining. On overflow, the oldest chunk is dropped.
1608
+ */
1609
+ highWaterMark?: number;
1610
+ /**
1611
+ * Called when the chunk queue overflows the high-water mark. The RPC
1612
+ * layer wires this to a coded warning; the primitive itself is
1613
+ * RPC-agnostic.
1614
+ */
1615
+ onOverflow?: (dropped: number) => void;
1616
+ /** Called when the consumer cancels — the RPC layer sends `:cancel` upstream. */
1617
+ onCancel?: () => void;
1618
+ }
1619
+ /**
1620
+ * Client-side consumer handle. Both an `AsyncIterable<T>` (for `for await`)
1621
+ * and exposes `readable: ReadableStream<T>` (for `pipeTo`). Pick one — they
1622
+ * share a single internal queue, so concurrent draining will race.
1623
+ */
1624
+ interface StreamReader<T> extends AsyncIterable<T> {
1625
+ readonly id: string;
1626
+ readonly cancelled: boolean;
1627
+ readonly done: boolean;
1628
+ /** Highest `seq` observed. Used for replay on reconnect. */
1629
+ readonly lastSeenSeq: number;
1630
+ /** `ReadableStream<T>` adapter for `pipeTo`-style consumption. */
1631
+ readonly readable: ReadableStream<T>;
1632
+ cancel: () => void;
1633
+ /** @internal */
1634
+ _push: (seq: number, chunk: T) => void;
1635
+ /** @internal */
1636
+ _end: (error?: StreamErrorPayload) => void;
1637
+ }
1638
+ /**
1639
+ * Build a server-side stream sink. RPC-agnostic — the RPC host wires
1640
+ * `events.on('chunk' | 'end')` to broadcast, and reads `buffer` to replay
1641
+ * for late or reconnecting subscribers.
1642
+ */
1643
+ declare function createStreamSink<T>(options?: CreateStreamSinkOptions): StreamSink<T>;
1644
+ /**
1645
+ * Build a client-side stream reader. RPC-agnostic — the RPC host calls
1646
+ * `_push(seq, chunk)` on each incoming chunk and `_end(error?)` on the
1647
+ * terminal frame. Consumers iterate with `for await` or pipe `readable`.
1648
+ */
1649
+ declare function createStreamReader<T>(options?: CreateStreamReaderOptions): StreamReader<T>;
1650
+ //#endregion
1651
+ //#region src/types/rpc.d.ts
1652
+ interface DevframeNodeRpcSession {
1653
+ meta: DevframeNodeRpcSessionMeta;
1654
+ rpc: BirpcReturn<DevframeRpcClientFunctions, DevframeRpcServerFunctions, false>;
1655
+ }
1656
+ interface RpcBroadcastOptions<METHOD, Args extends any[]> {
1657
+ method: METHOD;
1658
+ args: Args;
1659
+ optional?: boolean;
1660
+ event?: boolean;
1661
+ filter?: (client: BirpcReturn<DevframeRpcClientFunctions, DevframeRpcServerFunctions, false>) => boolean | void;
1662
+ }
1663
+ type RpcFunctionsHost = RpcFunctionsCollectorBase<DevframeRpcServerFunctions, DevframeNodeContext> & {
1664
+ /**
1665
+ * Invoke a locally registered server RPC function directly.
1666
+ *
1667
+ * This bypasses transport and is useful for server-side cross-function calls.
1668
+ */
1669
+ invokeLocal: <T extends keyof DevframeRpcServerFunctions, Args extends Parameters<DevframeRpcServerFunctions[T]>>(method: T, ...args: Args) => Promise<Awaited<ReturnType<DevframeRpcServerFunctions[T]>>>;
1670
+ /**
1671
+ * Broadcast a message to all connected clients
1672
+ */
1673
+ broadcast: <T extends keyof DevframeRpcClientFunctions, Args extends Parameters<DevframeRpcClientFunctions[T]>>(options: RpcBroadcastOptions<T, Args>) => Promise<void>;
1674
+ /**
1675
+ * Get the current RPC client
1676
+ *
1677
+ * Available in RPC functions to get the current RPC client
1678
+ */
1679
+ getCurrentRpcSession: () => DevframeNodeRpcSession | undefined;
1680
+ /**
1681
+ * The shared state host
1682
+ */
1683
+ sharedState: RpcSharedStateHost;
1684
+ /**
1685
+ * The streaming channel host. Provides per-channel `start()` /
1686
+ * `pipeFrom()` producers; clients consume via `rpc.streaming.subscribe()`.
1687
+ *
1688
+ * @see RpcStreamingHost
1689
+ */
1690
+ streaming: RpcStreamingHost;
1691
+ };
1692
+ interface RpcSharedStateGetOptions<T> {
1693
+ sharedState?: SharedState<T>;
1694
+ initialValue?: T;
1695
+ }
1696
+ interface RpcSharedStateHost {
1697
+ get: <T extends object = any>(key: string, options?: RpcSharedStateGetOptions<T>) => Promise<SharedState<T>>;
1698
+ keys: () => string[];
1699
+ /**
1700
+ * Subscribe to new shared-state keys becoming available. Fires when
1701
+ * `get(key, ...)` creates a fresh entry (not on subsequent gets).
1702
+ * Useful for protocol adapters (e.g. MCP) that surface shared state
1703
+ * as dynamic resources.
1704
+ */
1705
+ onKeyAdded: (fn: (key: string) => void) => () => void;
1706
+ /**
1707
+ * Unregister a shared state and drop its broadcast listeners. Returns
1708
+ * `true` when a state was removed, `false` when the key was unknown.
1709
+ * Used by short-lived states (e.g. a disposed JSON-render view) to avoid
1710
+ * leaking listeners and lingering entries for the context lifetime.
1711
+ */
1712
+ delete: (key: string) => boolean;
1713
+ }
1714
+ /**
1715
+ * Options for `RpcStreamingHost.create()`.
1716
+ */
1717
+ interface RpcStreamingChannelOptions {
1718
+ /**
1719
+ * Size of the per-stream ring buffer kept on the server for
1720
+ * replay-on-resubscribe. `0` (default) disables replay; on reconnect
1721
+ * the consumer only sees chunks that arrive after subscribing.
1722
+ *
1723
+ * The buffer is per stream id, not per channel — each `channel.start()`
1724
+ * gets its own.
1725
+ */
1726
+ replayWindow?: number;
1727
+ /**
1728
+ * Milliseconds a closed stream is retained on the server after its
1729
+ * last subscriber leaves (or if no subscriber ever arrived). During
1730
+ * this window, late subscribers can still join and replay the buffer
1731
+ * + receive the `end` frame.
1732
+ *
1733
+ * Defaults to `30_000` (30 s) when `replayWindow > 0`, else `0`
1734
+ * (immediate free). Set to `0` to opt out, or higher for longer
1735
+ * post-mortem replay.
1736
+ */
1737
+ closedStreamRetention?: number;
1738
+ }
1739
+ /**
1288
1740
  * Channel handle returned by `ctx.rpc.streaming.create(name, opts)`. A
1289
1741
  * channel owns a wire namespace; calling `start()` produces individual
1290
1742
  * streams keyed by id.
@@ -1610,393 +2062,4 @@ interface ConnectionMeta {
1610
2062
  */
1611
2063
  interface DevframeConnectionConfigsRegistry {}
1612
2064
  //#endregion
1613
- //#region src/node/auth/handler.d.ts
1614
- /**
1615
- * A ready-made pre-auth RPC handler, as produced by
1616
- * `devframe/recipes/interactive-auth`'s `createInteractiveAuth`. Bundles
1617
- * everything a host adapter needs to wire an authenticated server:
1618
- * the handshake RPC functions, the resolver gate, the connect-time trust
1619
- * hook, and the startup banner.
1620
- *
1621
- * `initDevframe` / `initHub` accept one of these directly via their `auth` option —
1622
- * see {@link https://devfra.me | devframe}'s server docs — or a host can
1623
- * wire the four pieces itself against a lower-level transport.
1624
- */
1625
- interface DevframeAuthHandler {
1626
- /**
1627
- * `anonymous:devframe:auth` + `anonymous:devframe:auth:exchange` (the
1628
- * handshake) and `devframe:auth:revoke` (self-revoke) — register these on
1629
- * the RPC host (e.g. `rpcHost.register(fn)` for each).
1630
- */
1631
- rpcFunctions: RpcFunctionDefinitionAny[];
1632
- /**
1633
- * Resolver gate: whether `methodName` is callable given `session`'s
1634
- * current trust state. Defaults to allowing any `anonymous:`-prefixed
1635
- * method (see `isAnonymousRpcMethod`) plus anything once the session is
1636
- * trusted.
1637
- */
1638
- authorize: (methodName: string, session: DevframeNodeRpcSession) => boolean;
1639
- /**
1640
- * Connect-time trust: reads a bearer token off the connection's initial
1641
- * request (a static/pre-shared token from `clientAuthTokens`, or a token
1642
- * minted by the code exchange) and, when valid, marks the session
1643
- * trusted immediately — before the client's own handshake call.
1644
- */
1645
- onConnect: (connection: DevframeRpcConnection, session: DevframeNodeRpcSession) => void;
1646
- /**
1647
- * Print the current one-time code and its magic-link URL. Devframe stays
1648
- * headless — call this yourself once the server is listening. Safe to
1649
- * call repeatedly; it only prints once per code.
1650
- */
1651
- printBanner: () => void;
1652
- /**
1653
- * Rewrite a URL the CLI is about to open in the browser (`--open`) so the
1654
- * tab lands already authenticated — e.g. appending the current OTP as a
1655
- * query param. Called with the fully-resolved target URL; return it
1656
- * unchanged to opt out. Optional: a handler that doesn't need this (e.g.
1657
- * one gated by a pre-shared bearer token instead of an interactive code)
1658
- * can omit it and the URL opens as-is.
1659
- */
1660
- buildOpenUrl?: (url: string) => string;
1661
- }
1662
- //#endregion
1663
- //#region src/types/devframe.d.ts
1664
- /**
1665
- * Classification of how a devframe is being deployed. Hosted adapters
1666
- * (`vite`, `embedded`) share their origin with a host app and must
1667
- * namespace their mount path under `/__<id>/`. Standalone adapters
1668
- * (`cli`, `build`) own the origin and default to `/`.
1669
- */
1670
- type DevframeDeploymentKind = 'standalone' | 'hosted';
1671
- /**
1672
- * How a hub deduplicates devframes that share an `id` when more than one
1673
- * is mounted onto the same hub. See {@link DevframeDefinition.duplicationStrategy}.
1674
- *
1675
- * - `'warn'` (default) — keep the first registration, drop later
1676
- * duplicates, and emit a warning diagnostic (`DF8105`).
1677
- * - `'silent'` — drop later duplicates without warning.
1678
- * - `'throw'` — throw when a duplicate is mounted.
1679
- * - `'duplicate'` — let every instance coexist under a disambiguated
1680
- * dock id.
1681
- */
1682
- type DevframeDuplicationStrategy = 'warn' | 'silent' | 'throw' | 'duplicate';
1683
- /**
1684
- * Controls where the browser opens the RPC WebSocket — advertised in
1685
- * `__connection.json` and used to bind the dev server. The three shapes map
1686
- * to the three connection scenarios; precedence is `url` > `port` > `route`:
1687
- *
1688
- * 1. **Same server, different route** (default) — leave `port`/`url` unset.
1689
- * The socket shares the HTTP server's port and binds to `route`
1690
- * (`__ws`). The client connects to its own origin, so the link
1691
- * survives a reverse proxy that rewrites the host/port/subpath.
1692
- *
1693
- * 2. **Different port** — set `port`. The socket binds on its own port on the
1694
- * same host; the client targets `ws(s)://<page-host>:<port>/<route>`.
1695
- *
1696
- * 3. **Remote, different origin** — set `url` to a full `ws://`/`wss://`
1697
- * endpoint (e.g. a public tunnel or relay). The client uses it verbatim.
1698
- */
1699
- interface DevframeWsOptions {
1700
- /**
1701
- * Upgrade route segment the socket binds to and is advertised at, relative
1702
- * to the SPA base. Default: `__ws`.
1703
- */
1704
- route?: string;
1705
- /**
1706
- * Bind the socket on its own port instead of sharing the HTTP port. The
1707
- * browser connects to this port on the page's hostname. Implies
1708
- * {@link DevframeWsOptions.sidecar}.
1709
- */
1710
- port?: number;
1711
- /**
1712
- * Start a side-car WebSocket server on a free port, for hosts whose request
1713
- * handlers can't accept upgrades (Next.js route handlers, Nitro, Rsbuild).
1714
- * The resolved port is advertised in `__connection.json`, so the browser
1715
- * finds it without any further wiring. Set `port` instead to pin it.
1716
- */
1717
- sidecar?: boolean;
1718
- /**
1719
- * Advertise a fixed, fully-qualified endpoint on another origin (a full
1720
- * `ws://`/`wss://` URL). Takes precedence over `port`/`route` in the meta.
1721
- */
1722
- url?: string;
1723
- }
1724
- /**
1725
- * Controls the SSE RPC endpoint — the WebSocket-free transport for hosts
1726
- * and proxies where the upgrade isn't available. It rides whatever HTTP
1727
- * surface serves the instance (the same one serving `__connection.json`),
1728
- * so a relative `route` always resolves; there is no port plumbing of its
1729
- * own. Enabled by default alongside the WebSocket; pass `sse: false` to
1730
- * disable, or `ws: false` to run SSE-only (`backend: 'sse'`).
1731
- */
1732
- interface DevframeSseOptions {
1733
- /**
1734
- * Route segment the SSE endpoint binds to and is advertised at, relative
1735
- * to the SPA base. Default: `__sse`.
1736
- */
1737
- route?: string;
1738
- }
1739
- /**
1740
- * Configuration for the route-based MCP server mounted alongside the dev
1741
- * server (opt-in via {@link DevframeCliOptions.mcp}). The endpoint speaks
1742
- * the MCP Streamable-HTTP transport over the same origin as the SPA,
1743
- * exposing the definition's `ctx.agent` tools + shared-state resources to
1744
- * external MCP clients connected to the *running* server.
1745
- */
1746
- interface McpRouteOptions {
1747
- /**
1748
- * Route segment the MCP endpoint binds to, relative to the SPA base.
1749
- * Default: `__mcp` (i.e. `/__mcp` standalone, `/__<id>/__mcp` hosted).
1750
- */
1751
- path?: string;
1752
- /**
1753
- * Extra `Origin` header values to accept beyond the loopback default
1754
- * (`localhost`/`127.0.0.1`/`::1` and any `Origin`-less native client).
1755
- * Add your LAN/tunnel origin here when reaching the endpoint from another
1756
- * host, mirroring the WS transport's origin gate. Pass `false` to disable
1757
- * origin checking entirely (not recommended). Default: loopback-only.
1758
- *
1759
- * This is the endpoint's DNS-rebinding protection — the shared
1760
- * `isAllowedOrigin` gate the WS upgrade already uses, applied as external
1761
- * middleware (the approach the MCP SDK now recommends over its own
1762
- * deprecated `allowedHosts`/`allowedOrigins` transport flags).
1763
- */
1764
- allowedOrigins?: readonly string[] | false;
1765
- }
1766
- interface DevframeCliOptions {
1767
- /** Binary name; default: the devframe's `id`. */
1768
- command?: string;
1769
- /** Preferred port for the dev server (default 9999). */
1770
- port?: number;
1771
- /** Port scan range, forwarded to `get-port-please`. */
1772
- portRange?: [number, number];
1773
- /** Prefer a random open port. */
1774
- random?: boolean;
1775
- /** Default host to bind to; `--host` overrides. */
1776
- host?: string;
1777
- /**
1778
- * Auto-open the browser when the dev server starts.
1779
- * `true` opens the resolved origin; a string opens that relative path.
1780
- * The `--open` / `--no-open` flags override this.
1781
- */
1782
- open?: boolean | string;
1783
- /**
1784
- * Authentication for the standalone dev server.
1785
- *
1786
- * - `undefined` / `true` — the standalone adapters (`cli` /
1787
- * served `build`) auto-wire devframe's interactive OTP auth
1788
- * (`createInteractiveAuth`): an untrusted client can only reach
1789
- * `anonymous:` methods until it exchanges the printed one-time code.
1790
- * The adapter prints the code + magic-link banner once the server is
1791
- * listening.
1792
- * - `false` — no gate, for trusted single-user localhost tools where an
1793
- * auth round-trip only gets in the way (the built-in plugins set this).
1794
- * The `--no-auth` CLI flag maps here for one-off runs.
1795
- * - A {@link DevframeAuthHandler} — a custom handler (e.g. a tuned
1796
- * `createInteractiveAuth`, or an entirely different scheme) passed
1797
- * straight through to the RPC transport binding.
1798
- *
1799
- * Hosted adapters (`vite`, `embedded`) ignore this and defer to the host's
1800
- * auth; `@vitejs/devtools` honors the equivalent `devtools.clientAuth`.
1801
- *
1802
- * @default true
1803
- */
1804
- auth?: boolean | DevframeAuthHandler;
1805
- /**
1806
- * Expose a route-based MCP server alongside the dev server, speaking the
1807
- * MCP Streamable-HTTP transport at `/__mcp` (relative to the base path).
1808
- * It surfaces the same `ctx.agent` tools + shared-state resources as the
1809
- * stdio `mcp` command, but against the live, running server.
1810
- *
1811
- * - `false` / omitted (default) — no MCP route is mounted.
1812
- * - `true` — mount at the default `__mcp` route with the loopback-only
1813
- * origin gate.
1814
- * - {@link McpRouteOptions} — customise the route path / allowed origins.
1815
- *
1816
- * The `--mcp` / `--no-mcp` CLI flags override this per run.
1817
- */
1818
- mcp?: boolean | McpRouteOptions;
1819
- /**
1820
- * Author's SPA dist — served as the devframe's UI. A local directory, or
1821
- * a {@link StaticAssetsSource} remote declaration (`{ package, version }`)
1822
- * served through devframe's caching CDN back-proxy so the assets need not
1823
- * ship inside the node package.
1824
- */
1825
- distDir?: StaticAssetsSource;
1826
- /**
1827
- * How the browser reaches the RPC WebSocket. Defaults to sharing the HTTP
1828
- * port on the `__ws` route. See {@link DevframeWsOptions} for the
1829
- * different-port and remote-origin variants. Pass `false` to serve no
1830
- * WebSocket at all — clients connect over SSE instead (`backend: 'sse'`).
1831
- */
1832
- ws?: DevframeWsOptions | false;
1833
- /**
1834
- * How the browser reaches the SSE RPC endpoint — enabled by default
1835
- * alongside the WebSocket as the more portable transport. Pass `false`
1836
- * to disable, or a {@link DevframeSseOptions} to change its route.
1837
- */
1838
- sse?: boolean | DevframeSseOptions;
1839
- /**
1840
- * Capability-side CAC hook. Called with the CAC instance after the
1841
- * adapter registers its built-in commands (`build` / `mcp`)
1842
- * but before `createCac`'s own `configureCli` caller. Use this to
1843
- * contribute tool-specific flags and subcommands from the definition
1844
- * itself.
1845
- */
1846
- configure?: (cli: CAC) => void;
1847
- /**
1848
- * Typed CLI flags for the default `dev` command, backed by any
1849
- * [Standard Schema](https://standardschema.dev/) validator (valibot,
1850
- * zod, arktype, or devframe's built-in `s`). The adapter registers
1851
- * matching `--kebab-key` options on CAC, validates the parsed values,
1852
- * and forwards the typed bag to `setup(ctx, { flags })`.
1853
- *
1854
- * Use {@link defineCliFlags} to preserve the literal schema-map
1855
- * shape, and {@link InferCliFlags} to recover the typed output at the
1856
- * call site:
1857
- *
1858
- * ```ts
1859
- * const appFlags = defineCliFlags({
1860
- * depth: v.pipe(v.number(), v.integer()),
1861
- * config: v.optional(v.string()),
1862
- * })
1863
- *
1864
- * defineDevframe({
1865
- * cli: { flags: appFlags },
1866
- * setup(ctx, info) {
1867
- * const flags = info.flags as InferCliFlags<typeof appFlags>
1868
- * },
1869
- * })
1870
- * ```
1871
- */
1872
- flags?: CliFlagsSchema;
1873
- }
1874
- /**
1875
- * Default dock attributes for the iframe entry a hub synthesizes when it
1876
- * mounts this devframe. Framework-neutral metadata only — the hub layer
1877
- * (`ctx.install`) merges these beneath its per-mount `dock` overrides,
1878
- * which in turn sit beneath the locked, derived `id` / `type` / `url`.
1879
- *
1880
- * Every field is optional. `title` / `icon` default to the definition's
1881
- * `name` / `icon` when omitted here; the rest are unset by default.
1882
- * Standalone adapters (`cli` / `build`) ignore this entirely.
1883
- */
1884
- interface DevframeDockDefaults {
1885
- /** Dock entry title. Defaults to the definition's `name`. */
1886
- title?: string;
1887
- /** Dock entry icon. Defaults to the definition's `icon`. */
1888
- icon?: string | {
1889
- light: string;
1890
- dark: string;
1891
- };
1892
- /**
1893
- * Sort weight within the dock; higher sorts earlier.
1894
- * @default 0
1895
- */
1896
- defaultOrder?: number;
1897
- /**
1898
- * Category the entry groups under in the dock.
1899
- * @default 'default'
1900
- */
1901
- category?: string;
1902
- /**
1903
- * Conditional-visibility expression (same syntax as command `when`
1904
- * clauses). Set to `'false'` to hide the entry unconditionally.
1905
- */
1906
- when?: string;
1907
- /**
1908
- * Render-only visibility expression, same syntax as {@link when}. Hides the
1909
- * entry's own dock-bar button when it evaluates to `false` while leaving it
1910
- * registered and reachable (activation, RPC lookups, etc.) — unlike `when`,
1911
- * which is the general relevance switch for the entry as a whole.
1912
- */
1913
- visibility?: string;
1914
- /** Badge text rendered on the dock icon (e.g. an unread count). */
1915
- badge?: string;
1916
- /** Id of the dock group this entry collapses under, if any. */
1917
- groupId?: string;
1918
- }
1919
- /**
1920
- * Runtime information threaded into `setup(ctx, info)`. Adapters
1921
- * populate the fields that make sense for their deployment. In
1922
- * particular, `createCac` fills `flags` with the parsed CAC bag.
1923
- */
1924
- interface DevframeSetupInfo {
1925
- /** Parsed CLI flags, populated by the CLI adapter. */
1926
- flags?: Record<string, unknown>;
1927
- }
1928
- interface DevframeDefinition {
1929
- id: string;
1930
- name: string;
1931
- /** Semver of the tool, surfaced in hub UIs and diagnostics. */
1932
- version: string;
1933
- /** npm package name the devframe ships in (e.g. `@scope/my-tool`). */
1934
- packageName: string;
1935
- /** Project homepage or documentation URL. */
1936
- homepage: string;
1937
- /** One-line summary of what the tool does. */
1938
- description: string;
1939
- icon?: string | {
1940
- light: string;
1941
- dark: string;
1942
- };
1943
- /**
1944
- * Default dock attributes applied when a hub mounts this devframe as an
1945
- * iframe dock entry. Consulted only by the hub install path (`ctx.install`),
1946
- * which merge these beneath the per-mount `dock` overrides; standalone
1947
- * adapters (`cli` / `build`) ignore it.
1948
- *
1949
- * @see {@link DevframeDockDefaults}
1950
- */
1951
- dock?: DevframeDockDefaults;
1952
- /**
1953
- * Mount path override. Defaults depend on the adapter:
1954
- * `/` for standalone (`cli` / `build`), `/__<id>/` for hosted
1955
- * (`vite` / `embedded`).
1956
- */
1957
- basePath?: string;
1958
- /**
1959
- * How a hub reacts when another devframe sharing this one's `id` is
1960
- * mounted onto the same hub. Consulted only by hub adapters
1961
- * (`ctx.install`); standalone adapters (`cli` / `build`)
1962
- * ignore it.
1963
- *
1964
- * @default 'warn'
1965
- */
1966
- duplicationStrategy?: DevframeDuplicationStrategy;
1967
- /**
1968
- * Declares which runtimes meaningfully support this devframe. Adapters
1969
- * act on a `false` before doing any work:
1970
- *
1971
- * - `build: false` — `createCac` skips registering the `build` subcommand,
1972
- * and `createBuild` refuses (throws `DF0042`) unless `{ force: true }`.
1973
- * Useful for a devframe whose value is inherently live (e.g. it manages
1974
- * real files on disk), so a static export would only ever produce a
1975
- * broken, write-less shell of the tool.
1976
- * - `dev: false` — `createDevServer` refuses (throws `DF0058`) unless
1977
- * `{ force: true }`. Useful for a devframe that only makes sense as a
1978
- * static export (e.g. a report generator with nothing to serve live).
1979
- */
1980
- capabilities?: {
1981
- dev?: boolean;
1982
- build?: boolean;
1983
- };
1984
- /**
1985
- * Wire services this devframe consumes (see `DevframeServiceDefinition`).
1986
- * Each entry is either a declarative descriptor
1987
- * (`{ package, version?, required?, options? }` — the host imports the
1988
- * package's default-export factory, resolving it against **this plugin's
1989
- * own dependencies**) or a ready `DevframeServiceDefinition` (the factory
1990
- * was already called). The adapter queues these before `setup(ctx)` runs
1991
- * and constructs each service once at the `ctx.services.ready()` barrier,
1992
- * merging option sets across every declarer. Missing services are skipped
1993
- * unless marked `required` — clients feature-detect via
1994
- * `client.services.has(pkg)` and degrade.
1995
- */
1996
- services?: DevframeServiceInput[];
1997
- /** Server-side setup — the primary entrypoint. Runs in every runtime. */
1998
- setup: (ctx: DevframeNodeContext, info?: DevframeSetupInfo) => void | Promise<void>;
1999
- cli?: DevframeCliOptions;
2000
- }
2001
- //#endregion
2002
- export { DevframeStorageScope as $, DevframeServiceId as A, DevframeAgentHost as At, DevframeScopedNodeRpc as B, RpcStreamingChannelOptions as C, AgentResource as Ct, DevframeRpcSharedStates as D, AgentToolInput as Dt, DevframeRpcServerFunctions as E, AgentTool as Et, DevframeServicesHost as F, CliFlagsSchema as Ft, ScopedBroadcastOptions as G, DevframeSettings as H, DevframeServicesRegistry as I, InferCliFlags as It, ScopedServerFunctions as J, ScopedClientFunctions as K, DevframeServicesScopeRegistry as L, defineCliFlags as Lt, DevframeServiceMeta as M, EventEmitter as Mt, DevframeServiceOf as N, EventUnsubscribe as Nt, DevframeServiceDefinition as O, AgentToolProvider as Ot, DevframeServiceScopeOf as P, EventsMap as Pt, DevframeHost as Q, DevframeServicesState as R, parseCliFlags as Rt, RpcStreamingChannel as S, AgentManifest as St, DevframeRpcClientFunctions as T, AgentResourceInput as Tt, DevframeSettingsRegistry as U, DevframeScopedStreamingHost as V, DevframeSettingsStore as W, SettingsForNamespace as X, ScopedSharedStates as Y, DevframeViewHost as Z, DevframeNodeRpcSession as _, SharedStateEvents as _t, DevframeDuplicationStrategy as a, StreamErrorPayload as at, RpcSharedStateGetOptions as b, createSharedState as bt, DevframeWsOptions as c, StreamSinkEvents as ct, ConnectionMeta as d, Immutable as dt, DevframeDiagnosticsHost as et, ConnectionMetaSse as f, ImmutableArray as ft, DevframeNodeContext as g, SharedState as gt, DevframeConnectionConfigsRegistry as h, ImmutableSet as ht, DevframeDockDefaults as i, CreateStreamSinkOptions as it, DevframeServiceInput as j, DevframeAgentHostEvents as jt, DevframeServiceDescriptor as k, AgentToolProviderHandle as kt, McpRouteOptions as l, createStreamReader as lt, DevframeCapabilities as m, ImmutableObject as mt, DevframeDefinition as n, BufferedChunk as nt, DevframeSetupInfo as o, StreamReader as ot, ConnectionMetaWebsocket as p, ImmutableMap as pt, ScopedRpcFn as q, DevframeDeploymentKind as r, CreateStreamReaderOptions as rt, DevframeSseOptions as s, StreamSink as st, DevframeCliOptions as t, DevframeDiagnosticsLogger as tt, DevframeAuthHandler as u, createStreamSink as ut, RpcBroadcastOptions as v, SharedStateOptions as vt, RpcStreamingHost as w, AgentResourceContent as wt, RpcSharedStateHost as x, AgentHandle as xt, RpcFunctionsHost as y, SharedStatePatch as yt, DevframeScopedNodeContext as z };
2065
+ export { DevframeServicesHost as $, SharedStateEvents as A, AgentResource as At, DevframeSetupInfo as B, EventUnsubscribe as Bt, createStreamSink as C, DevframeAuthHandler as Ct, ImmutableObject as D, parseCliFlags as Dt, ImmutableMap as E, defineCliFlags as Et, DevframeDefinition as F, AgentToolProvider as Ft, McpRouteOptions as G, DevframeSnapshotRpcInputs as H, DevframeDeploymentKind as I, AgentToolProviderHandle as It, DevframeServiceId as J, DevframeServiceDefinition as K, DevframeDockDefaults as L, DevframeAgentHost as Lt, SharedStatePatch as M, AgentResourceInput as Mt, createSharedState as N, AgentTool as Nt, ImmutableSet as O, AgentHandle as Ot, DevframeCliOptions as P, AgentToolInput as Pt, DevframeServiceScopeOf as Q, DevframeDuplicationStrategy as R, DevframeAgentHostEvents as Rt, createStreamReader as S, DevframeDiagnosticsLogger as St, ImmutableArray as T, InferCliFlags as Tt, DevframeSseOptions as U, DevframeSnapshotRpcEntry as V, EventsMap as Vt, DevframeWsOptions as W, DevframeServiceMeta as X, DevframeServiceInput as Y, DevframeServiceOf as Z, CreateStreamSinkOptions as _, DevframeRpcServerFunctions as _t, DevframeConnectionConfigsRegistry as a, DevframeScopedStreamingHost as at, StreamSink as b, DevframeStorageScope as bt, RpcBroadcastOptions as c, DevframeSettingsStore as ct, RpcSharedStateHost as d, ScopedRpcFn as dt, DevframeServicesRegistry as et, RpcStreamingChannel as f, ScopedServerFunctions as ft, CreateStreamReaderOptions as g, DevframeRpcClientFunctions as gt, BufferedChunk as h, DevframeViewHost as ht, DevframeCapabilities as i, DevframeScopedNodeRpc as it, SharedStateOptions as j, AgentResourceContent as jt, SharedState as k, AgentManifest as kt, RpcFunctionsHost as l, ScopedBroadcastOptions as lt, RpcStreamingHost as m, SettingsForNamespace as mt, ConnectionMetaSse as n, DevframeServicesState as nt, DevframeNodeContext as o, DevframeSettings as ot, RpcStreamingChannelOptions as p, ScopedSharedStates as pt, DevframeServiceDescriptor as q, ConnectionMetaWebsocket as r, DevframeScopedNodeContext as rt, DevframeNodeRpcSession as s, DevframeSettingsRegistry as st, ConnectionMeta as t, DevframeServicesScopeRegistry as tt, RpcSharedStateGetOptions as u, ScopedClientFunctions as ut, StreamErrorPayload as v, DevframeRpcSharedStates as vt, Immutable as w, CliFlagsSchema as wt, StreamSinkEvents as x, DevframeDiagnosticsHost as xt, StreamReader as y, DevframeHost as yt, DevframeRpcOptions as z, EventEmitter as zt };