devframe 0.9.9 → 0.9.10

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 (69) hide show
  1. package/dist/{_shared-CK3czkH4.d.mts → _shared-CCzf9Hub.d.mts} +26 -7
  2. package/dist/{_shared-C08SdQRl.mjs → _shared-U8D-xzrp.mjs} +41 -5
  3. package/dist/adapters/build.d.mts +1 -1
  4. package/dist/adapters/build.mjs +13 -26
  5. package/dist/adapters/cac.d.mts +6 -10
  6. package/dist/adapters/cac.mjs +3 -4
  7. package/dist/adapters/dev.d.mts +7 -7
  8. package/dist/adapters/dev.mjs +2 -2
  9. package/dist/adapters/embedded.d.mts +1 -1
  10. package/dist/adapters/initiate.d.mts +5 -4
  11. package/dist/adapters/initiate.mjs +1 -1
  12. package/dist/adapters/mcp.d.mts +1 -1
  13. package/dist/adapters/mcp.mjs +3 -3
  14. package/dist/cli/main.mjs +2 -2
  15. package/dist/client/index.d.mts +1 -1
  16. package/dist/client/index.mjs +7 -1
  17. package/dist/{context-CwPUinjn.mjs → context-B29Oh0oA.mjs} +47 -12
  18. package/dist/{context-rgBSPFHS.d.mts → context-BJiKJqyW.d.mts} +1 -1
  19. package/dist/{context-4AIVRLYF.d.mts → context-CHW4BZOj.d.mts} +51 -18
  20. package/dist/{context-DIUvJW4D.mjs → context-jXO5vzjH.mjs} +1 -1
  21. package/dist/{dev-Eqq5PX0k.mjs → dev-CRYHx7iG.mjs} +49 -35
  22. package/dist/{diagnostics-BMfN8mb1.mjs → diagnostics-DZA6KQ_Z.mjs} +3 -3
  23. package/dist/{services-install-P_DV7VYL.mjs → host-agent-CKqSTEX9.mjs} +8 -155
  24. package/dist/in-page-channel/index.d.mts +1 -1
  25. package/dist/{index-pKl67H6w.d.mts → index-CNC390b_.d.mts} +2 -2
  26. package/dist/index.d.mts +2 -2
  27. package/dist/{instance-registry-C7-41RSp.mjs → instance-registry-DCq762YJ.mjs} +2 -2
  28. package/dist/{instance-shell-7Z52PE4_.d.mts → instance-shell-DZipaP9k.d.mts} +2 -2
  29. package/dist/{instance-shell-cdVETpfz.mjs → instance-shell-DvYCwrDy.mjs} +5 -18
  30. package/dist/internal/index.d.mts +11 -9
  31. package/dist/internal/index.mjs +9 -9
  32. package/dist/node/auth.d.mts +2 -2
  33. package/dist/node/auth.mjs +1 -1
  34. package/dist/node/hub-internals.d.mts +2 -2
  35. package/dist/node/hub-internals.mjs +2 -2
  36. package/dist/node/index.d.mts +1 -1
  37. package/dist/node/index.mjs +2 -2
  38. package/dist/recipes/common-rpc-functions.d.mts +1 -1
  39. package/dist/recipes/interactive-auth.d.mts +2 -2
  40. package/dist/recipes/interactive-auth.mjs +2 -2
  41. package/dist/{remote-assets-BIFWOlBW.mjs → remote-assets-DMFzLSoH.mjs} +3 -3
  42. package/dist/rpc/dump.d.mts +17 -1
  43. package/dist/rpc/dump.mjs +2 -2
  44. package/dist/rpc/index.mjs +2 -3
  45. package/dist/rpc/transports/sse-client.mjs +1 -1
  46. package/dist/rpc/transports/sse-server.mjs +1 -1
  47. package/dist/rpc/transports/ws-bun.d.mts +1 -1
  48. package/dist/rpc/transports/ws-client.mjs +1 -1
  49. package/dist/rpc/transports/ws-deno.d.mts +1 -1
  50. package/dist/rpc/transports/ws-server.mjs +1 -1
  51. package/dist/{rpc-core-DVQ7xi89.mjs → rpc-core-CvkU23bQ.mjs} +1 -1
  52. package/dist/{rpc-core-Ceu6AUz8.d.mts → rpc-core-DsyDViQy.d.mts} +2 -2
  53. package/dist/{serialization-DTZnPQ1-.mjs → serialization-DFg5sUt6.mjs} +39 -2
  54. package/dist/services-install-Ben4yh52.mjs +154 -0
  55. package/dist/{state-CeQQWzM-.mjs → state-Mwt6--el.mjs} +13 -5
  56. package/dist/{storage-Dccf_IyD.mjs → storage-B70AXK64.mjs} +2 -2
  57. package/dist/types/index.d.mts +2 -2
  58. package/dist/utils/events.d.mts +1 -1
  59. package/dist/utils/remote-assets.mjs +1 -1
  60. package/dist/utils/serve-static.mjs +1 -1
  61. package/dist/utils/shared-state.d.mts +1 -1
  62. package/dist/utils/streaming-channel.d.mts +1 -1
  63. package/dist/{validation-BOy8IoFZ.mjs → validation-CfV1s0SF.mjs} +1 -1
  64. package/dist/{wire-codec-dcCeXcSo.mjs → wire-codec-BHGt0sSs.mjs} +1 -1
  65. package/dist/{static-DZ8P2Kn7.mjs → write-BuLCrn9h.mjs} +25 -4
  66. package/package.json +2 -6
  67. package/skills/devframe/SKILL.md +5 -3
  68. package/dist/diagnostics-D0EVm5M6.mjs +0 -39
  69. package/dist/{host-h3-BJ4siyz7.mjs → host-h3-BROI6SQ4.mjs} +1 -1
@@ -1,4 +1,4 @@
1
- import { F as DevframeDefinition, G as McpAuthorization, I as DevframeDeploymentKind, K as McpRouteOptions, t as ConnectionMeta } from "./context-4AIVRLYF.mjs";
1
+ import { F as DevframeDefinition, G as McpAuthorization, I as DevframeDeploymentKind, q as McpSetting, t as ConnectionMeta, zt as DevframeAgentHost } from "./context-CHW4BZOj.mjs";
2
2
  //#region src/adapters/_shared.d.ts
3
3
  /**
4
4
  * Resolve the mount base path for a devframe's SPA. Hosted adapters
@@ -38,20 +38,39 @@ interface ResolvedMcpConfig {
38
38
  authorization: McpAuthorization;
39
39
  }
40
40
  /**
41
- * Normalize the `mcp` option (`boolean | McpRouteOptions`) into a
42
- * fully-resolved config, or `undefined` when the MCP route is disabled.
41
+ * Normalize an *explicit* `mcp` setting into a fully-resolved config, or
42
+ * `undefined` when the MCP route is disabled. `'auto'` also resolves to
43
+ * `undefined` here: whether it mounts depends on the live agent surface,
44
+ * which only the mounting adapter can consult (through
45
+ * {@link loadAutoMcpAdapter}) - static resolvers like
46
+ * `resolveMcpConnectionMeta` treat it as unadvertisable.
43
47
  *
44
48
  * An enabled route trusts same-machine callers by default: the authorization
45
49
  * resolves to origin-only (`false`) unless the object config opts into a
46
50
  * bearer/callback identity check. An empty-string bearer is treated as no
47
51
  * bearer (origin-only) rather than a usable credential.
48
52
  */
49
- declare function resolveMcpConfig(mcp: boolean | McpRouteOptions | undefined): ResolvedMcpConfig | undefined;
53
+ declare function resolveMcpConfig(mcp: McpSetting | undefined): ResolvedMcpConfig | undefined;
54
+ /**
55
+ * Resolve the `mcp: 'auto'` default at mount time: import the MCP adapter
56
+ * when the devframe's agent surface is non-empty, or return `undefined`
57
+ * (mount nothing) when the surface is empty - the zero-cost path, loading
58
+ * no MCP code at all. The adapter (and the MCP SDK behind it) loads through
59
+ * `importRuntimeModule`, so it never enters a consumer's bundle graph.
60
+ *
61
+ * Generic like `importRuntimeModule`: the caller names the module type
62
+ * (`typeof import('devframe/adapters/mcp')`) so this shared helper carries
63
+ * no type-level dependency on the MCP adapter.
64
+ */
65
+ declare function loadAutoMcpAdapter<T>(agent: Pick<DevframeAgentHost, 'hasSurface'>): Promise<T | undefined>;
50
66
  /**
51
67
  * Resolve the `mcp` entry a `__connection.json` should advertise for a dev
52
68
  * server started with the given `mcp` option (falling back to `def.cli?.mcp`,
53
69
  * exactly like `createDevServer`), or `undefined` when the route is
54
- * disabled.
70
+ * disabled. `'auto'` (the omitted default) resolves at mount time against
71
+ * the live agent surface, so hand-rolled meta advertises it only for an
72
+ * explicit setting; the adapters advertise the actually-mounted route
73
+ * themselves.
55
74
  *
56
75
  * Hosted bridges that hand-roll their connection meta pass the side-car
57
76
  * `port`: the advertised path becomes absolute (the side-car mounts at `/`)
@@ -59,6 +78,6 @@ declare function resolveMcpConfig(mcp: boolean | McpRouteOptions | undefined): R
59
78
  * stays relative, resolved against `__connection.json`'s own location (the
60
79
  * same-server default).
61
80
  */
62
- declare function resolveMcpConnectionMeta(def: DevframeDefinition, mcp: boolean | McpRouteOptions | undefined, port?: number): ConnectionMeta['mcp'];
81
+ declare function resolveMcpConnectionMeta(def: DevframeDefinition, mcp: McpSetting | undefined, port?: number): ConnectionMeta['mcp'];
63
82
  //#endregion
64
- export { resolveDevServerPort as a, resolveBasePath as i, ResolvedMcpConfig as n, resolveMcpConfig as o, normalizeBasePath as r, resolveMcpConnectionMeta as s, ResolveDevServerPortOptions as t };
83
+ export { resolveBasePath as a, resolveMcpConnectionMeta as c, normalizeBasePath as i, ResolvedMcpConfig as n, resolveDevServerPort as o, loadAutoMcpAdapter as r, resolveMcpConfig as s, ResolveDevServerPortOptions as t };
@@ -1,6 +1,20 @@
1
1
  import "./constants.mjs";
2
+ import { r as importServicePackage } from "./services-install-Ben4yh52.mjs";
2
3
  import { n as getPort } from "./dist-CZXfGEkd.mjs";
3
4
  import { cleanDoubleSlashes, withLeadingSlash, withTrailingSlash, withoutLeadingSlash } from "ufo";
5
+ //#region src/node/import-runtime-module.ts
6
+ /**
7
+ * Resolve and import a package at runtime without adding it to a consumer's
8
+ * bundle graph. First-party adapters use this for modules whose code is
9
+ * needed only when the matching feature is enabled (optional peers, and the
10
+ * MCP adapter with the SDK behind it).
11
+ *
12
+ * @internal
13
+ */
14
+ async function importRuntimeModule(specifier) {
15
+ return await importServicePackage(specifier, [import.meta.url]);
16
+ }
17
+ //#endregion
4
18
  //#region src/adapters/_shared.ts
5
19
  const DEFAULT_PORT = 9999;
6
20
  /**
@@ -36,8 +50,12 @@ async function resolveDevServerPort(def, options = {}) {
36
50
  return getPort(portOptions);
37
51
  }
38
52
  /**
39
- * Normalize the `mcp` option (`boolean | McpRouteOptions`) into a
40
- * fully-resolved config, or `undefined` when the MCP route is disabled.
53
+ * Normalize an *explicit* `mcp` setting into a fully-resolved config, or
54
+ * `undefined` when the MCP route is disabled. `'auto'` also resolves to
55
+ * `undefined` here: whether it mounts depends on the live agent surface,
56
+ * which only the mounting adapter can consult (through
57
+ * {@link loadAutoMcpAdapter}) - static resolvers like
58
+ * `resolveMcpConnectionMeta` treat it as unadvertisable.
41
59
  *
42
60
  * An enabled route trusts same-machine callers by default: the authorization
43
61
  * resolves to origin-only (`false`) unless the object config opts into a
@@ -45,7 +63,7 @@ async function resolveDevServerPort(def, options = {}) {
45
63
  * bearer (origin-only) rather than a usable credential.
46
64
  */
47
65
  function resolveMcpConfig(mcp) {
48
- if (!mcp) return void 0;
66
+ if (!mcp || mcp === "auto") return void 0;
49
67
  if (mcp === true) return { authorization: false };
50
68
  const authorization = typeof mcp.authorization === "string" && mcp.authorization.length === 0 ? false : mcp.authorization ?? false;
51
69
  return {
@@ -55,10 +73,28 @@ function resolveMcpConfig(mcp) {
55
73
  };
56
74
  }
57
75
  /**
76
+ * Resolve the `mcp: 'auto'` default at mount time: import the MCP adapter
77
+ * when the devframe's agent surface is non-empty, or return `undefined`
78
+ * (mount nothing) when the surface is empty - the zero-cost path, loading
79
+ * no MCP code at all. The adapter (and the MCP SDK behind it) loads through
80
+ * `importRuntimeModule`, so it never enters a consumer's bundle graph.
81
+ *
82
+ * Generic like `importRuntimeModule`: the caller names the module type
83
+ * (`typeof import('devframe/adapters/mcp')`) so this shared helper carries
84
+ * no type-level dependency on the MCP adapter.
85
+ */
86
+ async function loadAutoMcpAdapter(agent) {
87
+ if (!agent.hasSurface()) return void 0;
88
+ return await importRuntimeModule("devframe/adapters/mcp");
89
+ }
90
+ /**
58
91
  * Resolve the `mcp` entry a `__connection.json` should advertise for a dev
59
92
  * server started with the given `mcp` option (falling back to `def.cli?.mcp`,
60
93
  * exactly like `createDevServer`), or `undefined` when the route is
61
- * disabled.
94
+ * disabled. `'auto'` (the omitted default) resolves at mount time against
95
+ * the live agent surface, so hand-rolled meta advertises it only for an
96
+ * explicit setting; the adapters advertise the actually-mounted route
97
+ * themselves.
62
98
  *
63
99
  * Hosted bridges that hand-roll their connection meta pass the side-car
64
100
  * `port`: the advertised path becomes absolute (the side-car mounts at `/`)
@@ -76,4 +112,4 @@ function resolveMcpConnectionMeta(def, mcp, port) {
76
112
  } : { path: route };
77
113
  }
78
114
  //#endregion
79
- export { resolveMcpConnectionMeta as a, resolveMcpConfig as i, resolveBasePath as n, resolveDevServerPort as r, normalizeBasePath as t };
115
+ export { resolveMcpConfig as a, resolveDevServerPort as i, normalizeBasePath as n, resolveMcpConnectionMeta as o, resolveBasePath as r, importRuntimeModule as s, loadAutoMcpAdapter as t };
@@ -1,4 +1,4 @@
1
- import { F as DevframeDefinition, V as DevframeSnapshotRpcEntry, o as DevframeNodeContext } from "../context-4AIVRLYF.mjs";
1
+ import { F as DevframeDefinition, V as DevframeSnapshotRpcEntry, o as DevframeNodeContext } from "../context-CHW4BZOj.mjs";
2
2
  import { o as StaticAssetsSource } from "../remote-assets-eBvSKZMS.mjs";
3
3
  //#region src/adapters/build.d.ts
4
4
  interface CreateBuildOptions {
@@ -1,17 +1,15 @@
1
1
  import { s as colors } from "../nostics-D0PvLZsn.mjs";
2
- import { n as strictJsonStringify } from "../serialization-DTZnPQ1-.mjs";
3
2
  import { t as resolveClientAssets } from "../client-assets-DNdhagGm.mjs";
4
- import { DEVFRAME_CONNECTION_META_FILENAME, DEVFRAME_RPC_DUMP_DIRNAME, DEVFRAME_RPC_DUMP_MANIFEST_FILENAME } from "../constants.mjs";
5
- import { t as collectStaticRpcDump } from "../static-DZ8P2Kn7.mjs";
6
- import { n as structuredCloneStringify } from "../structured-clone-CbAV5rFI.mjs";
7
- import { t as diagnostics } from "../diagnostics-BMfN8mb1.mjs";
8
- import { t as createHostContext } from "../context-CwPUinjn.mjs";
9
- import { t as resolveStaticAssetsSource } from "../remote-assets-BIFWOlBW.mjs";
10
- import { t as createH3DevframeHost } from "../host-h3-BJ4siyz7.mjs";
3
+ import { DEVFRAME_CONNECTION_META_FILENAME, DEVFRAME_RPC_DUMP_MANIFEST_FILENAME } from "../constants.mjs";
4
+ import { n as collectStaticRpcDump, t as writeStaticRpcDump } from "../write-BuLCrn9h.mjs";
5
+ import { t as diagnostics } from "../diagnostics-DZA6KQ_Z.mjs";
6
+ import { t as createHostContext } from "../context-B29Oh0oA.mjs";
7
+ import { t as resolveStaticAssetsSource } from "../remote-assets-DMFzLSoH.mjs";
8
+ import { t as createH3DevframeHost } from "../host-h3-BROI6SQ4.mjs";
9
+ import fs from "node:fs/promises";
10
+ import { resolve } from "pathe";
11
11
  import process from "node:process";
12
- import { dirname, resolve } from "pathe";
13
12
  import { existsSync } from "node:fs";
14
- import fs$1 from "node:fs/promises";
15
13
  //#region src/adapters/build.ts
16
14
  /**
17
15
  * Produce a self-contained static deploy of a devframe:
@@ -29,8 +27,8 @@ async function createBuild(d, options = {}) {
29
27
  const outDir = resolve(options.outDir ?? "dist-static");
30
28
  const distSource = options.distDir ?? resolveClientAssets(d);
31
29
  if (!distSource) throw new Error(`[devframe] createBuild: no client assets for "${d.id}". Set \`clientAssets\` on the definition or pass it as an option.`);
32
- if (existsSync(outDir)) await fs$1.rm(outDir, { recursive: true });
33
- await fs$1.mkdir(outDir, { recursive: true });
30
+ if (existsSync(outDir)) await fs.rm(outDir, { recursive: true });
31
+ await fs.mkdir(outDir, { recursive: true });
34
32
  const host = createH3DevframeHost({
35
33
  origin: "http://localhost",
36
34
  appName: d.id
@@ -46,19 +44,17 @@ async function createBuild(d, options = {}) {
46
44
  await ctx.services.ready();
47
45
  await d.setup(ctx);
48
46
  applySnapshotRpc(ctx, d.rpc?.snapshot);
49
- await fs$1.mkdir(resolve(outDir, DEVFRAME_RPC_DUMP_DIRNAME), { recursive: true });
50
47
  await writeConnectionMeta(ctx, outDir);
51
48
  console.log(colors.cyan`[devframe] writing RPC dump to ${resolve(outDir, DEVFRAME_RPC_DUMP_MANIFEST_FILENAME)}`);
52
49
  const dump = await collectStaticRpcDump(ctx.rpc.definitions.values(), ctx);
53
- await writeDumpFiles(dump, outDir, options.pretty ? 2 : void 0);
54
- await fs$1.writeFile(resolve(outDir, DEVFRAME_RPC_DUMP_MANIFEST_FILENAME), JSON.stringify(dump.manifest, null, 2), "utf-8");
50
+ await writeStaticRpcDump(dump, outDir, { pretty: options.pretty });
55
51
  console.log(colors.green`[devframe] built "${d.id}" -> ${outDir}`);
56
52
  }
57
53
  /** Copy a local SPA dist, or materialize a remote assets source, into `outDir`. */
58
54
  async function materializeSpa(resolved, outDir) {
59
55
  if (typeof resolved === "string") {
60
56
  console.log(colors.cyan`[devframe] copying SPA from ${resolved} -> ${outDir}`);
61
- await fs$1.cp(resolved, outDir, { recursive: true });
57
+ await fs.cp(resolved, outDir, { recursive: true });
62
58
  return;
63
59
  }
64
60
  console.log(colors.cyan`[devframe] materializing SPA from ${resolved.assets.package}@${resolved.assets.version} -> ${outDir}`);
@@ -68,20 +64,11 @@ async function materializeSpa(resolved, outDir) {
68
64
  async function writeConnectionMeta(ctx, outDir) {
69
65
  const jsonSerializableMethods = [];
70
66
  for (const def of ctx.rpc.definitions.values()) if (def.jsonSerializable === true) jsonSerializableMethods.push(def.name);
71
- await fs$1.writeFile(resolve(outDir, DEVFRAME_CONNECTION_META_FILENAME), JSON.stringify({
67
+ await fs.writeFile(resolve(outDir, DEVFRAME_CONNECTION_META_FILENAME), JSON.stringify({
72
68
  backend: "static",
73
69
  jsonSerializableMethods
74
70
  }, null, 2), "utf-8");
75
71
  }
76
- /** Encode and write each sharded RPC dump file under `outDir`. */
77
- async function writeDumpFiles(dump, outDir, indent) {
78
- for (const [filepath, file] of Object.entries(dump.files)) {
79
- const fullpath = resolve(outDir, filepath);
80
- await fs$1.mkdir(dirname(fullpath), { recursive: true });
81
- const text = file.serialization === "structured-clone" ? structuredCloneStringify(file.data) : strictJsonStringify(file.data, file.fnName);
82
- await fs$1.writeFile(fullpath, file.serialization === "json" && indent != null ? JSON.stringify(JSON.parse(text), null, indent) : text, "utf-8");
83
- }
84
- }
85
72
  /**
86
73
  * Attach a `dump` to each {@link DevframeRpcOptions.snapshot} target so the
87
74
  * static collector bakes it, even though the (service-owned) definition
@@ -1,4 +1,4 @@
1
- import { Dt as defineCliFlags, Et as InferCliFlags, F as DevframeDefinition, K as McpRouteOptions, Ot as parseCliFlags, Tt as CliFlagsSchema } from "../context-4AIVRLYF.mjs";
1
+ import { Dt as InferCliFlags, Et as CliFlagsSchema, F as DevframeDefinition, Ot as defineCliFlags, kt as parseCliFlags, q as McpSetting } from "../context-CHW4BZOj.mjs";
2
2
  import { CAC } from "cac";
3
3
  import { H3 } from "h3";
4
4
  //#region src/adapters/cac.d.ts
@@ -9,17 +9,13 @@ interface CreateCacOptions {
9
9
  * Expose a route-based MCP server alongside the dev server, speaking the
10
10
  * MCP Streamable-HTTP transport at `<base>__mcp`. Whether to expose MCP is
11
11
  * a hosting decision made at the CLI assembly stage, so it lives here rather
12
- * than on the definition.
12
+ * than on the definition. When unset, falls back to the definition's
13
+ * deprecated `cli.mcp`, then to the `'auto'` default (mount once the agent
14
+ * surface is non-empty). See {@link McpSetting}.
13
15
  *
14
- * - `false` / omitted (default): no MCP route is mounted.
15
- * - `true`: mount at the default `__mcp` route with the loopback origin gate.
16
- * - {@link McpRouteOptions}: customise the route path, origin allow-list, and
17
- * opt into an identity check.
18
- *
19
- * The `--mcp` / `--no-mcp` flags override this per run. Falls back to the
20
- * definition's deprecated `cli.mcp` when unset.
16
+ * The `--mcp` / `--no-mcp` flags override this per run.
21
17
  */
22
- mcp?: boolean | McpRouteOptions;
18
+ mcp?: McpSetting;
23
19
  /**
24
20
  * Final CAC hook invoked after devframe's built-in subcommands and
25
21
  * after the definition's `cli.configure`. Use this to add app-level
@@ -1,8 +1,7 @@
1
1
  import { s as colors } from "../nostics-D0PvLZsn.mjs";
2
- import { r as resolveDevServerPort } from "../_shared-C08SdQRl.mjs";
3
- import { a as importRuntimeModule } from "../instance-shell-cdVETpfz.mjs";
2
+ import { i as resolveDevServerPort, s as importRuntimeModule } from "../_shared-U8D-xzrp.mjs";
4
3
  import { createBuild } from "./build.mjs";
5
- import { t as createDevServer } from "../dev-Eqq5PX0k.mjs";
4
+ import { t as createDevServer } from "../dev-CRYHx7iG.mjs";
6
5
  import process from "node:process";
7
6
  import cac$1 from "cac";
8
7
  //#region src/adapters/flags.ts
@@ -96,7 +95,7 @@ function createCac(d, options = {}) {
96
95
  const defaultHost = d.cli?.host ?? "localhost";
97
96
  const command = d.cli?.command ?? d.id;
98
97
  const cli = cac$1(command);
99
- const devCommand = cli.command("[...args]", "Start a local dev server").option("--port <port>", "Port to listen on").option("--host <host>", "Host to bind to", { default: defaultHost }).option("--open", "Open the browser on start").option("--no-open", "Do not open the browser").option("--no-auth", "Disable the interactive authentication gate").option("--mcp", "Expose an MCP server over HTTP at /__mcp (use --no-mcp to disable)");
98
+ const devCommand = cli.command("[...args]", "Start a local dev server").option("--port <port>", "Port to listen on").option("--host <host>", "Host to bind to", { default: defaultHost }).option("--open", "Open the browser on start").option("--no-open", "Do not open the browser").option("--no-auth", "Disable the interactive authentication gate").option("--mcp", "Force the MCP route on (use --no-mcp to disable; default mounts it once agent tools exist)");
100
99
  if (d.cli?.flags) for (const [key, schema] of Object.entries(d.cli.flags)) {
101
100
  const optionName = flagKeyToOption(key);
102
101
  const description = schema.description ?? "";
@@ -1,8 +1,8 @@
1
1
  import { c as DevframeNodeRpcSessionMeta, l as DevframeRpcConnection, n as WsOriginRegistry } from "../ws-server-D2nv68Hg.mjs";
2
- import { F as DevframeDefinition, K as McpRouteOptions, U as DevframeSseOptions, W as DevframeWsOptions, s as DevframeNodeRpcSession, wt as DevframeAuthHandler } from "../context-4AIVRLYF.mjs";
2
+ import { F as DevframeDefinition, Tt as DevframeAuthHandler, U as DevframeSseOptions, W as DevframeWsOptions, q as McpSetting, s as DevframeNodeRpcSession } from "../context-CHW4BZOj.mjs";
3
3
  import { o as StaticAssetsSource } from "../remote-assets-eBvSKZMS.mjs";
4
- import { c as StartedServer } from "../instance-shell-7Z52PE4_.mjs";
5
- import { a as resolveDevServerPort, s as resolveMcpConnectionMeta, t as ResolveDevServerPortOptions } from "../_shared-CK3czkH4.mjs";
4
+ import { c as StartedServer } from "../instance-shell-DZipaP9k.mjs";
5
+ import { c as resolveMcpConnectionMeta, o as resolveDevServerPort, t as ResolveDevServerPortOptions } from "../_shared-CCzf9Hub.mjs";
6
6
  import { H3 } from "h3";
7
7
  //#region src/adapters/dev.d.ts
8
8
  interface CreateDevServerOptions {
@@ -81,11 +81,11 @@ interface CreateDevServerOptions {
81
81
  auth?: boolean | DevframeAuthHandler;
82
82
  /**
83
83
  * Expose a route-based MCP server on the dev server (Streamable-HTTP).
84
- * Overrides `def.cli?.mcp`; `undefined` falls through to it. `false`
85
- * disables the route regardless of the definition default. See
86
- * {@link McpRouteOptions}.
84
+ * Overrides `def.cli?.mcp`; `undefined` falls through to it, then to the
85
+ * `'auto'` default (mount once the agent surface is non-empty). `false`
86
+ * disables the route regardless. See {@link McpSetting}.
87
87
  */
88
- mcp?: boolean | McpRouteOptions;
88
+ mcp?: McpSetting;
89
89
  /**
90
90
  * Called once per new RPC connection, right after its session is created.
91
91
  * Forwarded verbatim to the underlying transport binding.
@@ -1,3 +1,3 @@
1
- import { a as resolveMcpConnectionMeta, r as resolveDevServerPort } from "../_shared-C08SdQRl.mjs";
2
- import { t as createDevServer } from "../dev-Eqq5PX0k.mjs";
1
+ import { i as resolveDevServerPort, o as resolveMcpConnectionMeta } from "../_shared-U8D-xzrp.mjs";
2
+ import { t as createDevServer } from "../dev-CRYHx7iG.mjs";
3
3
  export { createDevServer, resolveDevServerPort, resolveMcpConnectionMeta };
@@ -1,4 +1,4 @@
1
- import { F as DevframeDefinition, o as DevframeNodeContext } from "../context-4AIVRLYF.mjs";
1
+ import { F as DevframeDefinition, o as DevframeNodeContext } from "../context-CHW4BZOj.mjs";
2
2
  //#region src/adapters/embedded.d.ts
3
3
  interface CreateEmbeddedOptions {
4
4
  /** Target context the devframe is registered into. Required. */
@@ -1,7 +1,7 @@
1
1
  import { c as DevframeNodeRpcSessionMeta, l as DevframeRpcConnection, n as WsOriginRegistry } from "../ws-server-D2nv68Hg.mjs";
2
- import { F as DevframeDefinition, K as McpRouteOptions, U as DevframeSseOptions, W as DevframeWsOptions, o as DevframeNodeContext, s as DevframeNodeRpcSession, t as ConnectionMeta, wt as DevframeAuthHandler, xt as DevframeStorageScope } from "../context-4AIVRLYF.mjs";
2
+ import { F as DevframeDefinition, St as DevframeStorageScope, Tt as DevframeAuthHandler, U as DevframeSseOptions, W as DevframeWsOptions, o as DevframeNodeContext, q as McpSetting, s as DevframeNodeRpcSession, t as ConnectionMeta } from "../context-CHW4BZOj.mjs";
3
3
  import { o as StaticAssetsSource } from "../remote-assets-eBvSKZMS.mjs";
4
- import { c as StartedServer, f as DevframeInstanceRecord } from "../instance-shell-7Z52PE4_.mjs";
4
+ import { c as StartedServer, f as DevframeInstanceRecord } from "../instance-shell-DZipaP9k.mjs";
5
5
  import { Buffer } from "node:buffer";
6
6
  import { IncomingMessage, Server, ServerResponse } from "node:http";
7
7
  import { Duplex } from "node:stream";
@@ -73,9 +73,10 @@ interface InitDevframeOptions {
73
73
  /**
74
74
  * Expose a route-based MCP server (Streamable-HTTP) at `<base>__mcp` and
75
75
  * advertise it in `__connection.json`. Overrides `def.cli?.mcp`;
76
- * `undefined` falls through to it. See {@link McpRouteOptions}.
76
+ * `undefined` falls through to it, then to the `'auto'` default (mount
77
+ * once the agent surface is non-empty). See {@link McpSetting}.
77
78
  */
78
- mcp?: boolean | McpRouteOptions;
79
+ mcp?: McpSetting;
79
80
  /**
80
81
  * Public origin the host app is reachable at (e.g. `http://localhost:3000`),
81
82
  * or a getter for hosts that resolve it late. Backs the auth banner's magic
@@ -1,2 +1,2 @@
1
- import { n as getInstanceInternals, r as initDevframe } from "../dev-Eqq5PX0k.mjs";
1
+ import { n as getInstanceInternals, r as initDevframe } from "../dev-CRYHx7iG.mjs";
2
2
  export { getInstanceInternals, initDevframe };
@@ -1,4 +1,4 @@
1
- import { F as DevframeDefinition, G as McpAuthorization, o as DevframeNodeContext } from "../context-4AIVRLYF.mjs";
1
+ import { F as DevframeDefinition, G as McpAuthorization, o as DevframeNodeContext } from "../context-CHW4BZOj.mjs";
2
2
  import { H3 } from "h3";
3
3
  import "@modelcontextprotocol/server";
4
4
  //#region src/adapters/mcp/build-server.d.ts
@@ -2,11 +2,11 @@ import { t as Diagnostic } from "../nostics-D0PvLZsn.mjs";
2
2
  import { t as DEVFRAME_EVENTS } from "../events-BV4Dj59a.mjs";
3
3
  import { t as isAllowedOrigin } from "../origin-DZit9g29.mjs";
4
4
  import { r as timingSafeEqual } from "../crypto-token-TVbGGFuj.mjs";
5
- import { t as diagnostics } from "../diagnostics-BMfN8mb1.mjs";
6
- import { t as createHostContext } from "../context-CwPUinjn.mjs";
5
+ import { t as diagnostics } from "../diagnostics-DZA6KQ_Z.mjs";
6
+ import { t as createHostContext } from "../context-B29Oh0oA.mjs";
7
7
  import { t as toAgentToolName } from "../agent-tool-name-DGzeuWcO.mjs";
8
- import process from "node:process";
9
8
  import { join } from "pathe";
9
+ import process from "node:process";
10
10
  import { homedir } from "node:os";
11
11
  import { defineHandler } from "h3";
12
12
  import { Server, createMcpHandler } from "@modelcontextprotocol/server";
package/dist/cli/main.mjs CHANGED
@@ -1,6 +1,6 @@
1
1
  import { t as Diagnostic } from "../nostics-D0PvLZsn.mjs";
2
- import { t as diagnostics } from "../diagnostics-BMfN8mb1.mjs";
3
- import { n as listLiveDevframeInstances, r as probeDevframeOrigin } from "../instance-registry-C7-41RSp.mjs";
2
+ import { t as diagnostics } from "../diagnostics-DZA6KQ_Z.mjs";
3
+ import { n as listLiveDevframeInstances, r as probeDevframeOrigin } from "../instance-registry-DCq762YJ.mjs";
4
4
  import { t as toAgentToolName } from "../agent-tool-name-DGzeuWcO.mjs";
5
5
  import process from "node:process";
6
6
  import { joinURL } from "ufo";
@@ -1,6 +1,6 @@
1
1
  import { f as RpcCacheManager, p as RpcCacheOptions } from "../index-pIVt92jP.mjs";
2
2
  import { _ as RpcFunctionDefinition, w as RpcFunctionsCollector } from "../types-Dq4i156u.mjs";
3
- import { $ as DevframeServiceScopeOf, Bt as EventEmitter, Z as DevframeServiceMeta, _t as DevframeRpcClientFunctions, b as StreamSink, d as RpcSharedStateHost, ft as ScopedRpcFn, ht as SettingsForNamespace, k as SharedState, mt as ScopedSharedStates, pt as ScopedServerFunctions, rt as DevframeServicesState, st as DevframeSettings, t as ConnectionMeta, u as RpcSharedStateGetOptions, vt as DevframeRpcServerFunctions, y as StreamReader } from "../context-4AIVRLYF.mjs";
3
+ import { Q as DevframeServiceMeta, Vt as EventEmitter, b as StreamSink, ct as DevframeSettings, d as RpcSharedStateHost, et as DevframeServiceScopeOf, gt as SettingsForNamespace, ht as ScopedSharedStates, it as DevframeServicesState, k as SharedState, mt as ScopedServerFunctions, pt as ScopedRpcFn, t as ConnectionMeta, u as RpcSharedStateGetOptions, vt as DevframeRpcClientFunctions, y as StreamReader, yt as DevframeRpcServerFunctions } from "../context-CHW4BZOj.mjs";
4
4
  import { t as SseRpcChannelOptions } from "../sse-client-BQ4jS3eO.mjs";
5
5
  import { t as WsRpcChannelOptions } from "../ws-client-Xpxt0oPA.mjs";
6
6
  import { BirpcOptions, BirpcReturn } from "birpc";
@@ -533,9 +533,15 @@ async function setupDevframeConnection(options = {}) {
533
533
  const response = await fetch(metaPath);
534
534
  if (!response.ok) throw new Error(`Failed to fetch connection meta from ${metaUrl}: ${response.status}`);
535
535
  const connectionMeta = await response.json();
536
+ const loadedFrom = response.url || metaUrl;
536
537
  const connection = {
537
538
  connectionMeta,
538
- metaBaseUrl: response.url || metaUrl,
539
+ /**
540
+ * A served `baseUrl` re-points relative resolution (RPC dump shards,
541
+ * transport paths) at the meta that owns them: a static hub build's
542
+ * per-frame meta directs each frame SPA at the hub's own dump.
543
+ */
544
+ metaBaseUrl: connectionMeta.baseUrl ? new URL(connectionMeta.baseUrl, loadedFrom).href : loadedFrom,
539
545
  authToken: readStoredAuthToken(options.authToken ?? connectionMeta.authToken)
540
546
  };
541
547
  storeConnection(connection);
@@ -1,16 +1,17 @@
1
1
  import { i as defineDiagnostics } from "./nostics-D0PvLZsn.mjs";
2
- import { t as diagnostics } from "./diagnostics-D0EVm5M6.mjs";
2
+ import { r as diagnostics } from "./serialization-DFg5sUt6.mjs";
3
3
  import { RpcFunctionsCollectorBase } from "./rpc/index.mjs";
4
4
  import { defineRpcFunction } from "./index.mjs";
5
5
  import { t as DEVFRAME_EVENTS } from "./events-BV4Dj59a.mjs";
6
6
  import { DEVFRAME_SERVICES_STATE_KEY } from "./constants.mjs";
7
- import { t as diagnostics$1 } from "./diagnostics-BMfN8mb1.mjs";
8
- import { a as DevframeAgentHost, i as satisfiesVersionRange, n as expandResolveFrom, r as importServicePackage, s as createEventEmitter, t as deepMergeOptionSets } from "./services-install-P_DV7VYL.mjs";
9
- import { n as createDebug, t as resolveStaticAssetsSource } from "./remote-assets-BIFWOlBW.mjs";
10
- import { t as createStorage } from "./storage-Dccf_IyD.mjs";
7
+ import { t as diagnostics$1 } from "./diagnostics-DZA6KQ_Z.mjs";
8
+ import { r as createEventEmitter, t as DevframeAgentHost } from "./host-agent-CKqSTEX9.mjs";
9
+ import { n as createDebug, t as resolveStaticAssetsSource } from "./remote-assets-DMFzLSoH.mjs";
10
+ import { i as satisfiesVersionRange, n as expandResolveFrom, r as importServicePackage, t as deepMergeOptionSets } from "./services-install-Ben4yh52.mjs";
11
+ import { t as createStorage } from "./storage-B70AXK64.mjs";
12
+ import { join } from "pathe";
11
13
  import { createSharedState } from "devframe/utils/shared-state";
12
14
  import process from "node:process";
13
- import { join } from "pathe";
14
15
  import { existsSync } from "node:fs";
15
16
  //#region src/node/host-diagnostics.ts
16
17
  var DevframeDiagnosticsHost = class {
@@ -129,9 +130,14 @@ function createRpcSharedStateServerHost(rpc) {
129
130
  },
130
131
  /**
131
132
  * Pre-compute snapshots for the build-mode static dump so the SPA
132
- * can read them without a live server.
133
+ * can read them without a live server. The `undefined` fallback makes
134
+ * a key the build never created resolve like the live handler does
135
+ * (returning `undefined`) instead of failing the dump lookup.
133
136
  */
134
- dump: () => ({ inputs: host.keys().map((key) => [key]) })
137
+ dump: () => ({
138
+ inputs: host.keys().map((key) => [key]),
139
+ fallback: void 0
140
+ })
135
141
  });
136
142
  rpc.register({
137
143
  name: "devframe:rpc:server-state:set",
@@ -777,6 +783,17 @@ var RpcFunctionsHostImpl = class extends RpcFunctionsCollectorBase {
777
783
  //#endregion
778
784
  //#region src/node/host-services.ts
779
785
  const debug = createDebug("devframe:services");
786
+ /**
787
+ * Per-RPC-host registry of the services already installed against it, keyed
788
+ * by the RPC host object. A single wire service can be declared from more
789
+ * than one devframe context that shares one RPC host (e.g. a kit that mounts
790
+ * a definition alongside another context, so both iterate `def.services`).
791
+ * The per-instance `installed` guard can't see across those sibling hosts, so
792
+ * the second context would re-run the service factory and re-register its RPC,
793
+ * hitting DF0021. Sharing the registry by RPC host makes the first install win
794
+ * across every context on that host.
795
+ */
796
+ const installedByRpcHost = /* @__PURE__ */ new WeakMap();
780
797
  function isServiceDefinition(input) {
781
798
  return typeof input.setup === "function";
782
799
  }
@@ -825,11 +842,27 @@ var DevframeServicesHostImpl = class {
825
842
  services = /* @__PURE__ */ new Map();
826
843
  listeners = /* @__PURE__ */ new Map();
827
844
  pending = /* @__PURE__ */ new Map();
828
- installed = /* @__PURE__ */ new Map();
845
+ localInstalled = /* @__PURE__ */ new Map();
829
846
  readyPromise;
830
847
  constructor(context) {
831
848
  this.context = context;
832
849
  }
850
+ /**
851
+ * The install-dedup registry, shared across every services host that shares
852
+ * this host's RPC host so the first install of a package wins across sibling
853
+ * contexts. Falls back to a per-instance map when there is no RPC host (a
854
+ * context-less host only exercises the in-process `provide`/`get` tier).
855
+ */
856
+ get installed() {
857
+ const rpc = this.context?.rpc;
858
+ if (!rpc) return this.localInstalled;
859
+ let registry = installedByRpcHost.get(rpc);
860
+ if (!registry) {
861
+ registry = /* @__PURE__ */ new Map();
862
+ installedByRpcHost.set(rpc, registry);
863
+ }
864
+ return registry;
865
+ }
833
866
  provide(id, service) {
834
867
  const key = id;
835
868
  if (this.services.has(key)) throw diagnostics$1.DF0037({ id: key });
@@ -907,8 +940,10 @@ var DevframeServicesHostImpl = class {
907
940
  }
908
941
  async installPackage(pkg, entries) {
909
942
  if (this.installed.has(pkg)) {
910
- diagnostics$1.DF0066({ package: pkg });
911
- return this.installed.get(pkg);
943
+ const api = this.installed.get(pkg);
944
+ if (this.services.has(pkg)) diagnostics$1.DF0066({ package: pkg });
945
+ else this.provide(pkg, api);
946
+ return api;
912
947
  }
913
948
  let def = entries.filter((entry) => isServiceDefinition(entry.input))[0]?.input;
914
949
  if (!def) {
@@ -924,7 +959,7 @@ var DevframeServicesHostImpl = class {
924
959
  debug("installing service %s@%s (scope %s)", def.package, def.version, def.scope);
925
960
  const scoped = this.context.scope(def.scope);
926
961
  const api = await def.setup(scoped, options === void 0 ? {} : { options });
927
- this.installed.set(def.package, api);
962
+ this.installed.set(pkg, api);
928
963
  this.provide(def.package, api);
929
964
  await this.advertise(def);
930
965
  return api;
@@ -1,4 +1,4 @@
1
- import { k as SharedState, o as DevframeNodeContext } from "./context-4AIVRLYF.mjs";
1
+ import { k as SharedState, o as DevframeNodeContext } from "./context-CHW4BZOj.mjs";
2
2
  //#region src/node/hub-internals/context.d.ts
3
3
  interface InternalAnonymousAuthStorage {
4
4
  trusted: Record<string, {