devframe 0.0.0 → 0.1.16

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 (73) hide show
  1. package/LICENSE.md +21 -0
  2. package/README.md +62 -0
  3. package/dist/_shared-CU6dE-VX.mjs +20 -0
  4. package/dist/adapters/build.d.mts +37 -0
  5. package/dist/adapters/build.mjs +61 -0
  6. package/dist/adapters/cli.d.mts +35 -0
  7. package/dist/adapters/cli.mjs +357 -0
  8. package/dist/adapters/embedded.d.mts +19 -0
  9. package/dist/adapters/embedded.mjs +15 -0
  10. package/dist/adapters/kit.d.mts +23 -0
  11. package/dist/adapters/kit.mjs +16 -0
  12. package/dist/adapters/mcp.d.mts +39 -0
  13. package/dist/adapters/mcp.mjs +278 -0
  14. package/dist/adapters/vite.d.mts +32 -0
  15. package/dist/adapters/vite.mjs +31 -0
  16. package/dist/client/index.d.mts +227 -0
  17. package/dist/client/index.mjs +2 -0
  18. package/dist/client-4WrEozlH.mjs +1561 -0
  19. package/dist/constants.d.mts +20 -0
  20. package/dist/constants.mjs +34 -0
  21. package/dist/context-BrePWeyd.mjs +6776 -0
  22. package/dist/define-Bb4zh-Dc.mjs +11 -0
  23. package/dist/devtool-OJ3QW0ns.d.mts +1125 -0
  24. package/dist/helpers/nuxt/index.d.mts +46 -0
  25. package/dist/helpers/nuxt/index.mjs +58 -0
  26. package/dist/helpers/nuxt/runtime/plugin.client.d.mts +8 -0
  27. package/dist/helpers/nuxt/runtime/plugin.client.mjs +12 -0
  28. package/dist/human-id-CHS0s28X.mjs +844 -0
  29. package/dist/immer-HjMAm3b6.mjs +894 -0
  30. package/dist/index-DvKDO5H8.d.mts +333 -0
  31. package/dist/index.d.mts +10 -0
  32. package/dist/index.mjs +2 -0
  33. package/dist/main-DpINGndA.mjs +601 -0
  34. package/dist/node/index.d.mts +365 -0
  35. package/dist/node/index.mjs +69 -0
  36. package/dist/open-BtOOEldu.mjs +533 -0
  37. package/dist/recipes/open-helpers.d.mts +104 -0
  38. package/dist/recipes/open-helpers.mjs +69 -0
  39. package/dist/rpc/client.d.mts +9 -0
  40. package/dist/rpc/client.mjs +13 -0
  41. package/dist/rpc/index.d.mts +2 -0
  42. package/dist/rpc/index.mjs +3 -0
  43. package/dist/rpc/server.d.mts +8 -0
  44. package/dist/rpc/server.mjs +10 -0
  45. package/dist/rpc/transports/ws-client.d.mts +2 -0
  46. package/dist/rpc/transports/ws-client.mjs +43 -0
  47. package/dist/rpc/transports/ws-server.d.mts +2 -0
  48. package/dist/rpc/transports/ws-server.mjs +58 -0
  49. package/dist/rpc-9FNa3Inb.mjs +474 -0
  50. package/dist/server-DrBxa6ZV.mjs +49 -0
  51. package/dist/src-BoIqXRc9.mjs +85 -0
  52. package/dist/static-dump-CQUC1aIW.mjs +82 -0
  53. package/dist/transports-BPUzHhI2.mjs +15 -0
  54. package/dist/types/index.d.mts +4 -0
  55. package/dist/types/index.mjs +6 -0
  56. package/dist/utils/events.d.mts +9 -0
  57. package/dist/utils/events.mjs +40 -0
  58. package/dist/utils/human-id.d.mts +10 -0
  59. package/dist/utils/human-id.mjs +3 -0
  60. package/dist/utils/nanoid.d.mts +4 -0
  61. package/dist/utils/nanoid.mjs +10 -0
  62. package/dist/utils/promise.d.mts +8 -0
  63. package/dist/utils/promise.mjs +15 -0
  64. package/dist/utils/shared-state.d.mts +2 -0
  65. package/dist/utils/shared-state.mjs +36 -0
  66. package/dist/utils/state.d.mts +49 -0
  67. package/dist/utils/state.mjs +26 -0
  68. package/dist/utils/when.d.mts +2 -0
  69. package/dist/utils/when.mjs +424 -0
  70. package/dist/when-CGLewRtm.d.mts +401 -0
  71. package/dist/ws-client-CklfxUHE.d.mts +17 -0
  72. package/dist/ws-server--IuUAaGi.d.mts +37 -0
  73. package/package.json +116 -8
package/LICENSE.md ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026-PRESENT Anthony Fu <https://github.com/antfu>
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,62 @@
1
+ # devframe
2
+
3
+ Framework-neutral foundation for building generic DevTools — RPC layer (birpc + valibot + WS presets), six hosts (RPC / docks / views / terminals / logs / commands / agent), and adapters under `devframe/adapters/*` (cli / build / spa / vite / kit / embedded / mcp). Part of the [Vite DevTools](https://devtools.vite.dev) monorepo.
4
+
5
+ ## Install
6
+
7
+ ```sh
8
+ pnpm add devframe
9
+ ```
10
+
11
+ ## Docs
12
+
13
+ See the [DevFrame documentation](https://devtools.vite.dev/devframe/) for the full guide and API reference.
14
+
15
+ ## Agent-Native (experimental)
16
+
17
+ > ⚠️ **Experimental.** The agent-native surface — the `agent` field on `defineRpcFunction`, `DevToolsAgentHost`, and the `devframe/adapters/mcp` adapter — is experimental and may change without a major version bump until it stabilizes.
18
+
19
+ DevFrame can expose a devtool's RPC functions, tools, and resources to coding agents over [MCP](https://modelcontextprotocol.io). Flag an RPC function with `agent: { description }` to surface it, then spin up an MCP server:
20
+
21
+ ```ts
22
+ import { defineDevtool, defineRpcFunction } from 'devframe'
23
+ import { createMcpServer } from 'devframe/adapters/mcp'
24
+
25
+ const getSummary = defineRpcFunction({
26
+ name: 'my-plugin:get-summary',
27
+ type: 'query',
28
+ agent: {
29
+ description: 'Return a short summary of the current build state.',
30
+ },
31
+ setup: ctx => ({ handler: async () => buildSummary() }),
32
+ })
33
+
34
+ const devtool = defineDevtool({
35
+ id: 'my-plugin',
36
+ setup(ctx) {
37
+ ctx.rpc.register(getSummary)
38
+ // Optional: register tools or resources directly.
39
+ ctx.agent.registerResource({
40
+ id: 'latest-build',
41
+ name: 'Latest build',
42
+ read: () => ({ text: renderMarkdown(latestBuild) }),
43
+ })
44
+ },
45
+ })
46
+
47
+ await createMcpServer(devtool, { transport: 'stdio' })
48
+ ```
49
+
50
+ Or via the CLI:
51
+
52
+ ```sh
53
+ devframe mcp
54
+ ```
55
+
56
+ `@modelcontextprotocol/sdk` is a peer dependency — add it to your package when you want to ship MCP support.
57
+
58
+ See the [Agent-Native guide](https://devtools.vite.dev/devframe/agent-native) for the full API, safety model, and Claude Desktop integration example.
59
+
60
+ ## License
61
+
62
+ [MIT](./LICENSE.md)
@@ -0,0 +1,20 @@
1
+ //#region src/adapters/_shared.ts
2
+ /**
3
+ * Resolve the mount base path for a devtool's SPA. Hosted adapters
4
+ * (`vite`, `kit`, `embedded`) default to `/.<id>/` so they don't
5
+ * collide with the host app; standalone adapters (`cli`, `spa`,
6
+ * `build`) default to `/` because they own the origin.
7
+ *
8
+ * The devtool author can override with `basePath` on the definition.
9
+ */
10
+ function resolveBasePath(def, kind) {
11
+ if (def.basePath) return normalizeBasePath(def.basePath);
12
+ return kind === "standalone" ? "/" : `/.${def.id}/`;
13
+ }
14
+ function normalizeBasePath(base) {
15
+ let out = base.startsWith("/") ? base : `/${base}`;
16
+ if (!out.endsWith("/")) out = `${out}/`;
17
+ return out.replace(/\/+/g, "/");
18
+ }
19
+ //#endregion
20
+ export { resolveBasePath as t };
@@ -0,0 +1,37 @@
1
+ import { r as DevtoolDefinition } from "../devtool-OJ3QW0ns.mjs";
2
+
3
+ //#region src/adapters/build.d.ts
4
+ interface CreateBuildOptions {
5
+ /** Output directory. Defaults to `dist-static`. */
6
+ outDir?: string;
7
+ /** Absolute URL base the output is served from (default: `/`). */
8
+ base?: string;
9
+ /**
10
+ * Override the SPA dist directory to copy into `outDir`. When omitted
11
+ * the adapter reads `devtool.cli?.distDir` — authors typically set this
12
+ * once on the definition itself.
13
+ */
14
+ distDir?: string;
15
+ /**
16
+ * Pretty-print RPC dump JSON files. Defaults to `false` so payload
17
+ * shards (which can be multiple MB for graph-heavy tools) ship
18
+ * minified. Set `true` when you need to diff / read the dumps by hand.
19
+ */
20
+ pretty?: boolean;
21
+ }
22
+ /**
23
+ * Produce a self-contained static deploy of a devtool:
24
+ *
25
+ * - Build a `mode: 'build'` context and run `devtool.setup(ctx)`.
26
+ * - Copy the author's SPA dist into `<outDir>/`.
27
+ * - Write `<outDir>/.connection.json` (`{ backend: 'static' }`) and the
28
+ * sharded RPC dump under `<outDir>/.rpc-dump/` so the deployed SPA
29
+ * discovers both via relative paths from `document.baseURI`.
30
+ * - When `def.spa` is configured, also write `<outDir>/spa-loader.json`
31
+ * describing the SPA's data-loader mode (`'query'` / `'upload'` /
32
+ * `'none'`). The output is mount-path agnostic — the same bundle
33
+ * works at `/`, `/devtools/`, or any base, no rewriting required.
34
+ */
35
+ declare function createBuild(d: DevtoolDefinition, options?: CreateBuildOptions): Promise<void>;
36
+ //#endregion
37
+ export { CreateBuildOptions, createBuild };
@@ -0,0 +1,61 @@
1
+ import { t as createHostContext } from "../context-BrePWeyd.mjs";
2
+ import { DEVTOOLS_CONNECTION_META_FILENAME, DEVTOOLS_RPC_DUMP_DIRNAME, DEVTOOLS_RPC_DUMP_MANIFEST_FILENAME } from "../constants.mjs";
3
+ import { n as createH3DevToolsHost, t as collectStaticRpcDump } from "../static-dump-CQUC1aIW.mjs";
4
+ import { t as resolveBasePath } from "../_shared-CU6dE-VX.mjs";
5
+ import { existsSync } from "node:fs";
6
+ import { dirname, resolve } from "pathe";
7
+ import process from "node:process";
8
+ import c from "ansis";
9
+ import fs$1 from "node:fs/promises";
10
+ //#region src/adapters/build.ts
11
+ /**
12
+ * Produce a self-contained static deploy of a devtool:
13
+ *
14
+ * - Build a `mode: 'build'` context and run `devtool.setup(ctx)`.
15
+ * - Copy the author's SPA dist into `<outDir>/`.
16
+ * - Write `<outDir>/.connection.json` (`{ backend: 'static' }`) and the
17
+ * sharded RPC dump under `<outDir>/.rpc-dump/` so the deployed SPA
18
+ * discovers both via relative paths from `document.baseURI`.
19
+ * - When `def.spa` is configured, also write `<outDir>/spa-loader.json`
20
+ * describing the SPA's data-loader mode (`'query'` / `'upload'` /
21
+ * `'none'`). The output is mount-path agnostic — the same bundle
22
+ * works at `/`, `/devtools/`, or any base, no rewriting required.
23
+ */
24
+ async function createBuild(d, options = {}) {
25
+ const outDir = resolve(options.outDir ?? "dist-static");
26
+ const distDir = options.distDir ?? d.cli?.distDir;
27
+ if (!distDir) throw new Error(`[devframe] createBuild: no distDir for "${d.id}". Set \`cli.distDir\` on the definition or pass it as an option.`);
28
+ if (existsSync(outDir)) await fs$1.rm(outDir, { recursive: true });
29
+ await fs$1.mkdir(outDir, { recursive: true });
30
+ console.log(c.cyan`[devframe] copying SPA from ${distDir} -> ${outDir}`);
31
+ await fs$1.cp(distDir, outDir, { recursive: true });
32
+ const ctx = await createHostContext({
33
+ cwd: process.cwd(),
34
+ mode: "build",
35
+ host: createH3DevToolsHost({ origin: "http://localhost" })
36
+ });
37
+ await d.setup(ctx);
38
+ await fs$1.mkdir(resolve(outDir, DEVTOOLS_RPC_DUMP_DIRNAME), { recursive: true });
39
+ await fs$1.writeFile(resolve(outDir, DEVTOOLS_CONNECTION_META_FILENAME), JSON.stringify({ backend: "static" }, null, 2), "utf-8");
40
+ console.log(c.cyan`[devframe] writing RPC dump to ${resolve(outDir, DEVTOOLS_RPC_DUMP_MANIFEST_FILENAME)}`);
41
+ const dump = await collectStaticRpcDump(ctx.rpc.definitions.values(), ctx);
42
+ const indent = options.pretty ? 2 : void 0;
43
+ for (const [filepath, data] of Object.entries(dump.files)) {
44
+ const fullpath = resolve(outDir, filepath);
45
+ await fs$1.mkdir(dirname(fullpath), { recursive: true });
46
+ await fs$1.writeFile(fullpath, JSON.stringify(data, null, indent), "utf-8");
47
+ }
48
+ await fs$1.writeFile(resolve(outDir, DEVTOOLS_RPC_DUMP_MANIFEST_FILENAME), JSON.stringify(dump.manifest, null, 2), "utf-8");
49
+ if (d.spa) {
50
+ const base = options.base ?? resolveBasePath(d, "standalone");
51
+ const spaLoader = {
52
+ version: 1,
53
+ mode: d.spa.loader ?? "none",
54
+ base
55
+ };
56
+ await fs$1.writeFile(resolve(outDir, "spa-loader.json"), JSON.stringify(spaLoader, null, 2), "utf-8");
57
+ }
58
+ console.log(c.green`[devframe] built "${d.id}" -> ${outDir}`);
59
+ }
60
+ //#endregion
61
+ export { createBuild };
@@ -0,0 +1,35 @@
1
+ import { r as DevtoolDefinition } from "../devtool-OJ3QW0ns.mjs";
2
+ import { App } from "h3";
3
+ import { CAC } from "cac";
4
+
5
+ //#region src/adapters/cli.d.ts
6
+ interface CreateCliOptions {
7
+ /** Default port for `dev` (default: 9999). */
8
+ defaultPort?: number;
9
+ /**
10
+ * Final CAC hook invoked after devframe's built-in subcommands and
11
+ * after the definition's `cli.configure`. Use this to add app-level
12
+ * flags and commands at the assembly stage.
13
+ */
14
+ configureCli?: (cli: CAC) => void;
15
+ /**
16
+ * Called once the dev server is listening. Use this to print a
17
+ * startup banner or trigger side-effects that depend on the live URL.
18
+ */
19
+ onReady?: (info: {
20
+ origin: string;
21
+ port: number;
22
+ app: App;
23
+ }) => void | Promise<void>;
24
+ }
25
+ interface CliHandle {
26
+ /**
27
+ * Raw CAC instance. Mutate before calling `parse()` for last-mile
28
+ * flag or command additions that don't fit `configureCli`.
29
+ */
30
+ cli: CAC;
31
+ parse: (argv?: string[]) => Promise<void>;
32
+ }
33
+ declare function createCli(d: DevtoolDefinition, options?: CreateCliOptions): CliHandle;
34
+ //#endregion
35
+ export { CliHandle, CreateCliOptions, createCli };
@@ -0,0 +1,357 @@
1
+ import { a as flagKeyToOption, o as isBooleanFlag, s as parseCliFlags } from "../src-BoIqXRc9.mjs";
2
+ import { t as createHostContext } from "../context-BrePWeyd.mjs";
3
+ import { DEVTOOLS_CONNECTION_META_FILENAME } from "../constants.mjs";
4
+ import { n as createH3DevToolsHost } from "../static-dump-CQUC1aIW.mjs";
5
+ import { t as startHttpAndWs } from "../server-DrBxa6ZV.mjs";
6
+ import { t as resolveBasePath } from "../_shared-CU6dE-VX.mjs";
7
+ import { createBuild } from "./build.mjs";
8
+ import { resolve } from "pathe";
9
+ import process$1 from "node:process";
10
+ import c from "ansis";
11
+ import { networkInterfaces } from "node:os";
12
+ import { createApp, eventHandler, fromNodeMiddleware } from "h3";
13
+ import { createServer } from "node:net";
14
+ import cac from "cac";
15
+ import sirv from "sirv";
16
+ //#region ../../../node_modules/.pnpm/get-port-please@3.2.0/node_modules/get-port-please/dist/index.mjs
17
+ const unsafePorts = /* @__PURE__ */ new Set([
18
+ 1,
19
+ 7,
20
+ 9,
21
+ 11,
22
+ 13,
23
+ 15,
24
+ 17,
25
+ 19,
26
+ 20,
27
+ 21,
28
+ 22,
29
+ 23,
30
+ 25,
31
+ 37,
32
+ 42,
33
+ 43,
34
+ 53,
35
+ 69,
36
+ 77,
37
+ 79,
38
+ 87,
39
+ 95,
40
+ 101,
41
+ 102,
42
+ 103,
43
+ 104,
44
+ 109,
45
+ 110,
46
+ 111,
47
+ 113,
48
+ 115,
49
+ 117,
50
+ 119,
51
+ 123,
52
+ 135,
53
+ 137,
54
+ 139,
55
+ 143,
56
+ 161,
57
+ 179,
58
+ 389,
59
+ 427,
60
+ 465,
61
+ 512,
62
+ 513,
63
+ 514,
64
+ 515,
65
+ 526,
66
+ 530,
67
+ 531,
68
+ 532,
69
+ 540,
70
+ 548,
71
+ 554,
72
+ 556,
73
+ 563,
74
+ 587,
75
+ 601,
76
+ 636,
77
+ 989,
78
+ 990,
79
+ 993,
80
+ 995,
81
+ 1719,
82
+ 1720,
83
+ 1723,
84
+ 2049,
85
+ 3659,
86
+ 4045,
87
+ 5060,
88
+ 5061,
89
+ 6e3,
90
+ 6566,
91
+ 6665,
92
+ 6666,
93
+ 6667,
94
+ 6668,
95
+ 6669,
96
+ 6697,
97
+ 10080
98
+ ]);
99
+ function isUnsafePort(port) {
100
+ return unsafePorts.has(port);
101
+ }
102
+ function isSafePort(port) {
103
+ return !isUnsafePort(port);
104
+ }
105
+ var GetPortError = class extends Error {
106
+ constructor(message, opts) {
107
+ super(message, opts);
108
+ this.message = message;
109
+ }
110
+ name = "GetPortError";
111
+ };
112
+ function _log(verbose, message) {
113
+ if (verbose) console.log(`[get-port] ${message}`);
114
+ }
115
+ function _generateRange(from, to) {
116
+ if (to < from) return [];
117
+ const r = [];
118
+ for (let index = from; index <= to; index++) r.push(index);
119
+ return r;
120
+ }
121
+ function _tryPort(port, host) {
122
+ return new Promise((resolve) => {
123
+ const server = createServer();
124
+ server.unref();
125
+ server.on("error", () => {
126
+ resolve(false);
127
+ });
128
+ server.listen({
129
+ port,
130
+ host
131
+ }, () => {
132
+ const { port: port2 } = server.address();
133
+ server.close(() => {
134
+ resolve(isSafePort(port2) && port2);
135
+ });
136
+ });
137
+ });
138
+ }
139
+ function _getLocalHosts(additional) {
140
+ const hosts = new Set(additional);
141
+ for (const _interface of Object.values(networkInterfaces())) for (const config of _interface || []) if (config.address && !config.internal && !config.address.startsWith("fe80::") && !config.address.startsWith("169.254")) hosts.add(config.address);
142
+ return [...hosts];
143
+ }
144
+ async function _findPort(ports, host) {
145
+ for (const port of ports) {
146
+ const r = await _tryPort(port, host);
147
+ if (r) return r;
148
+ }
149
+ }
150
+ function _fmtOnHost(hostname) {
151
+ return hostname ? `on host ${JSON.stringify(hostname)}` : "on any host";
152
+ }
153
+ const HOSTNAME_RE = /^(?!-)[\d.:A-Za-z-]{1,63}(?<!-)$/;
154
+ function _validateHostname(hostname, _public, verbose) {
155
+ if (hostname && !HOSTNAME_RE.test(hostname)) {
156
+ const fallbackHost = _public ? "0.0.0.0" : "127.0.0.1";
157
+ _log(verbose, `Invalid hostname: ${JSON.stringify(hostname)}. Using ${JSON.stringify(fallbackHost)} as fallback.`);
158
+ return fallbackHost;
159
+ }
160
+ return hostname;
161
+ }
162
+ async function getPort(_userOptions = {}) {
163
+ if (typeof _userOptions === "number" || typeof _userOptions === "string") _userOptions = { port: Number.parseInt(_userOptions + "") || 0 };
164
+ const _port = Number(_userOptions.port ?? process.env.PORT);
165
+ const _userSpecifiedAnyPort = Boolean(_userOptions.port || _userOptions.ports?.length || _userOptions.portRange?.length);
166
+ const options = {
167
+ random: _port === 0,
168
+ ports: [],
169
+ portRange: [],
170
+ alternativePortRange: _userSpecifiedAnyPort ? [] : [3e3, 3100],
171
+ verbose: false,
172
+ ..._userOptions,
173
+ port: _port,
174
+ host: _validateHostname(_userOptions.host ?? process.env.HOST, _userOptions.public, _userOptions.verbose)
175
+ };
176
+ if (options.random && !_userSpecifiedAnyPort) return getRandomPort(options.host);
177
+ const portsToCheck = [
178
+ options.port,
179
+ ...options.ports,
180
+ ..._generateRange(...options.portRange)
181
+ ].filter((port) => {
182
+ if (!port) return false;
183
+ if (!isSafePort(port)) {
184
+ _log(options.verbose, `Ignoring unsafe port: ${port}`);
185
+ return false;
186
+ }
187
+ return true;
188
+ });
189
+ if (portsToCheck.length === 0) portsToCheck.push(3e3);
190
+ let availablePort = await _findPort(portsToCheck, options.host);
191
+ if (!availablePort && options.alternativePortRange.length > 0) {
192
+ availablePort = await _findPort(_generateRange(...options.alternativePortRange), options.host);
193
+ if (portsToCheck.length > 0) {
194
+ let message = `Unable to find an available port (tried ${portsToCheck.join("-")} ${_fmtOnHost(options.host)}).`;
195
+ if (availablePort) message += ` Using alternative port ${availablePort}.`;
196
+ _log(options.verbose, message);
197
+ }
198
+ }
199
+ if (!availablePort && _userOptions.random !== false) {
200
+ availablePort = await getRandomPort(options.host);
201
+ if (availablePort) _log(options.verbose, `Using random port ${availablePort}`);
202
+ }
203
+ if (!availablePort) {
204
+ const triedRanges = [
205
+ options.port,
206
+ options.portRange.join("-"),
207
+ options.alternativePortRange.join("-")
208
+ ].filter(Boolean).join(", ");
209
+ throw new GetPortError(`Unable to find an available port ${_fmtOnHost(options.host)} (tried ${triedRanges})`);
210
+ }
211
+ return availablePort;
212
+ }
213
+ async function getRandomPort(host) {
214
+ const port = await checkPort(0, host);
215
+ if (port === false) throw new GetPortError(`Unable to find a random port ${_fmtOnHost(host)}`);
216
+ return port;
217
+ }
218
+ async function checkPort(port, host = process.env.HOST, verbose) {
219
+ if (!host) host = _getLocalHosts([void 0, "0.0.0.0"]);
220
+ if (!Array.isArray(host)) return _tryPort(port, host);
221
+ for (const _host of host) {
222
+ const _port = await _tryPort(port, _host);
223
+ if (_port === false) {
224
+ if (port < 1024 && verbose) _log(verbose, `Unable to listen to the privileged port ${port} ${_fmtOnHost(_host)}`);
225
+ return false;
226
+ }
227
+ if (port === 0 && _port !== 0) port = _port;
228
+ }
229
+ return port;
230
+ }
231
+ //#endregion
232
+ //#region src/adapters/cli.ts
233
+ function createCli(d, options = {}) {
234
+ const defaultPort = options.defaultPort ?? d.cli?.port ?? 9999;
235
+ const defaultHost = d.cli?.host ?? "localhost";
236
+ const cli = cac(d.cli?.command ?? d.id);
237
+ 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");
238
+ if (d.cli?.flags) for (const [key, schema] of Object.entries(d.cli.flags)) {
239
+ const optionName = flagKeyToOption(key);
240
+ const description = schema.description ?? "";
241
+ if (isBooleanFlag(schema)) devCommand.option(`--${optionName}`, description);
242
+ else devCommand.option(`--${optionName} <value>`, description);
243
+ }
244
+ devCommand.action(async (_args, rawFlags) => {
245
+ const flags = resolveTypedFlags(d, rawFlags);
246
+ const host = flags.host ?? defaultHost;
247
+ const portOptions = {
248
+ port: defaultPort,
249
+ host
250
+ };
251
+ if (d.cli?.portRange) portOptions.portRange = d.cli.portRange;
252
+ if (d.cli?.random) portOptions.random = d.cli.random;
253
+ await runDevServer(d, {
254
+ host,
255
+ port: flags.port ?? await getPort(portOptions),
256
+ flags
257
+ }, options);
258
+ });
259
+ cli.command("build", "Build a self-contained static deploy of the devtool").option("--out-dir <outDir>", "Output directory", { default: "dist-static" }).option("--base <base>", "URL base", { default: "/" }).option("--pretty", "Pretty-print dump JSON (larger on disk)").action(async (flags) => {
260
+ await createBuild(d, {
261
+ outDir: flags.outDir,
262
+ base: flags.base,
263
+ pretty: flags.pretty
264
+ });
265
+ });
266
+ cli.command("mcp", "Start an MCP server exposing agent-facing tools (stdio) [experimental]").action(async () => {
267
+ const { createMcpServer } = await import("./mcp.mjs");
268
+ await createMcpServer(d, {
269
+ transport: "stdio",
270
+ onReady: ({ transport }) => {
271
+ console.error(`[devframe] "${d.id}" MCP server ready (${transport})`);
272
+ }
273
+ });
274
+ });
275
+ d.cli?.configure?.(cli);
276
+ options.configureCli?.(cli);
277
+ cli.help();
278
+ cli.version("0.0.0");
279
+ return {
280
+ cli,
281
+ async parse(argv = process$1.argv) {
282
+ cli.parse(argv, { run: false });
283
+ await cli.runMatchedCommand();
284
+ }
285
+ };
286
+ }
287
+ function resolveTypedFlags(d, raw) {
288
+ if (!d.cli?.flags) return raw;
289
+ const { flags, issues } = parseCliFlags(d.cli.flags, raw);
290
+ if (issues?.length) {
291
+ for (const issue of issues) console.error(c.red`[devframe] invalid flag — ${issue}`);
292
+ process$1.exit(1);
293
+ }
294
+ return flags;
295
+ }
296
+ async function runDevServer(d, serverOptions, cliOptions) {
297
+ const distDir = d.cli?.distDir;
298
+ if (!distDir) throw new Error(`[devframe] dev: no cli.distDir for "${d.id}". Set \`cli.distDir\` on the definition.`);
299
+ const app = createApp();
300
+ const origin = `http://${serverOptions.host}:${serverOptions.port}`;
301
+ const basePath = resolveBasePath(d, "standalone");
302
+ const host = createH3DevToolsHost({
303
+ origin,
304
+ mount: (base, dir) => {
305
+ app.use(base, fromNodeMiddleware(sirv(dir, {
306
+ dev: true,
307
+ single: true
308
+ })));
309
+ }
310
+ });
311
+ const ctx = await createHostContext({
312
+ cwd: process$1.cwd(),
313
+ mode: "dev",
314
+ host
315
+ });
316
+ const setupInfo = { flags: serverOptions.flags };
317
+ await d.setup(ctx, setupInfo);
318
+ const connectionMetaPath = `${basePath}${DEVTOOLS_CONNECTION_META_FILENAME}`;
319
+ app.use(connectionMetaPath, eventHandler((event) => {
320
+ event.node.res.setHeader("Content-Type", "application/json");
321
+ return event.node.res.end(JSON.stringify({
322
+ backend: "websocket",
323
+ websocket: serverOptions.port
324
+ }));
325
+ }));
326
+ app.use(basePath, fromNodeMiddleware(sirv(resolve(distDir), {
327
+ dev: true,
328
+ single: true
329
+ })));
330
+ await startHttpAndWs({
331
+ context: ctx,
332
+ host: serverOptions.host,
333
+ port: serverOptions.port,
334
+ app,
335
+ auth: d.cli?.auth,
336
+ onReady: async (info) => {
337
+ await cliOptions.onReady?.(info);
338
+ await maybeOpenBrowser(d, serverOptions.flags, `${info.origin}${basePath}`);
339
+ }
340
+ });
341
+ }
342
+ async function maybeOpenBrowser(d, flags, origin) {
343
+ const cliOpen = d.cli?.open;
344
+ if (!(flags.open ?? (cliOpen !== void 0 && cliOpen !== false))) return;
345
+ const target = typeof flags.open === "string" ? resolveOpenTarget(origin, flags.open) : typeof cliOpen === "string" ? resolveOpenTarget(origin, cliOpen) : origin;
346
+ try {
347
+ const { default: open } = await import("../open-BtOOEldu.mjs");
348
+ await open(target);
349
+ } catch {}
350
+ }
351
+ function resolveOpenTarget(origin, target) {
352
+ if (/^https?:/.test(target)) return target;
353
+ if (target.startsWith("/")) return origin.replace(/\/$/, "") + target;
354
+ return origin.replace(/\/$/, "") + (target ? `/${target}` : "");
355
+ }
356
+ //#endregion
357
+ export { createCli };
@@ -0,0 +1,19 @@
1
+ import { d as DevToolsNodeContext, r as DevtoolDefinition } from "../devtool-OJ3QW0ns.mjs";
2
+
3
+ //#region src/adapters/embedded.d.ts
4
+ interface CreateEmbeddedOptions {
5
+ /** Target context the devtool is registered into. Required. */
6
+ ctx: DevToolsNodeContext;
7
+ }
8
+ /**
9
+ * Register a devtool into an already-running devframe/Kit context at
10
+ * runtime. Mirrors what the Vite plugin scan does for devtools passed
11
+ * as plugin options, but exposes the same flow to callers that need
12
+ * dynamic, post-startup registration.
13
+ *
14
+ * The host owns the mount path; when a hosted mount is needed the
15
+ * effective default follows the hosted rule of `def.basePath ?? '/.<id>/'`.
16
+ */
17
+ declare function createEmbedded(d: DevtoolDefinition, options: CreateEmbeddedOptions): Promise<void>;
18
+ //#endregion
19
+ export { CreateEmbeddedOptions, createEmbedded };
@@ -0,0 +1,15 @@
1
+ //#region src/adapters/embedded.ts
2
+ /**
3
+ * Register a devtool into an already-running devframe/Kit context at
4
+ * runtime. Mirrors what the Vite plugin scan does for devtools passed
5
+ * as plugin options, but exposes the same flow to callers that need
6
+ * dynamic, post-startup registration.
7
+ *
8
+ * The host owns the mount path; when a hosted mount is needed the
9
+ * effective default follows the hosted rule of `def.basePath ?? '/.<id>/'`.
10
+ */
11
+ async function createEmbedded(d, options) {
12
+ await d.setup(options.ctx);
13
+ }
14
+ //#endregion
15
+ export { createEmbedded };
@@ -0,0 +1,23 @@
1
+ import { r as DevtoolDefinition } from "../devtool-OJ3QW0ns.mjs";
2
+
3
+ //#region src/adapters/kit.d.ts
4
+ interface CreateKitPluginOptions {
5
+ /**
6
+ * Optional plugin name override. Defaults to `devframe:<devtool-id>`.
7
+ */
8
+ name?: string;
9
+ }
10
+ interface KitPlugin {
11
+ name: string;
12
+ devtools: {
13
+ setup: DevtoolDefinition['setup'];
14
+ capabilities?: DevtoolDefinition['capabilities'];
15
+ };
16
+ }
17
+ /**
18
+ * Produce a Vite plugin object that Kit's plugin-scan picks up via
19
+ * `Plugin.devtools`.
20
+ */
21
+ declare function createKitPlugin(d: DevtoolDefinition, options?: CreateKitPluginOptions): KitPlugin;
22
+ //#endregion
23
+ export { CreateKitPluginOptions, KitPlugin, createKitPlugin };
@@ -0,0 +1,16 @@
1
+ //#region src/adapters/kit.ts
2
+ /**
3
+ * Produce a Vite plugin object that Kit's plugin-scan picks up via
4
+ * `Plugin.devtools`.
5
+ */
6
+ function createKitPlugin(d, options = {}) {
7
+ return {
8
+ name: options.name ?? `devframe:${d.id}`,
9
+ devtools: {
10
+ setup: d.setup,
11
+ capabilities: d.capabilities
12
+ }
13
+ };
14
+ }
15
+ //#endregion
16
+ export { createKitPlugin };