Devframe Definition
Every Devframe tool starts with a single defineDevframe call. The returned DevframeDefinition is a portable value that any of the adapters can consume — the same definition runs under createCli, createBuild, createMcpServer, the vite adapter's createPluginFromDevframe, and so on.
Minimal definition
import { defineDevframe, defineRpcFunction } from 'devframe'
import * as v from 'valibot'
export default defineDevframe({
id: 'my-devframe',
name: 'My Devframe',
version: '1.0.0',
packageName: 'my-devframe',
homepage: 'https://github.com/me/my-devframe',
description: 'A one-line summary of what the tool does.',
icon: 'ph:gauge-duotone',
setup(ctx) {
// Register your RPC functions, shared state, etc. here.
ctx.rpc.register(defineRpcFunction({
name: 'my-devframe:hello',
type: 'static',
jsonSerializable: true,
handler: () => ({ message: 'hello' }),
}))
},
})Host adapters (such as the vite adapter for Vite DevTools) derive their mount entry from id, name, icon, and basePath automatically.
Definition fields
| Field | Type | Description |
|---|---|---|
id | string | Required. Unique, namespaced identifier (kebab-case). Used as a prefix for RPC names, dock IDs, and MCP tool names. |
name | string | Required. Display name shown in the dock and agent manifests. |
version | string | Required. Semver of the tool, surfaced in hub UIs and diagnostics. |
packageName | string | Required. npm package name the devframe ships in (e.g. @scope/my-tool). |
homepage | string | Required. Project homepage or documentation URL. |
description | string | Required. One-line summary of what the tool does. |
icon | string | { light, dark } | Optional Iconify name or URL; supports light/dark pairs. |
basePath | string | Optional mount path override. Defaults depend on the adapter: / for standalone (cli / spa / build), /.<id>/ for hosted (vite / embedded). |
duplicationStrategy | 'warn' | 'silent' | 'throw' | 'duplicate' | How a hub reacts when another devframe sharing this id is mounted onto the same hub. Defaults to 'warn'. See Hub. Hub adapters consult it; standalone adapters ignore it. |
capabilities | { dev?, build?, spa? } | Per-runtime feature flags. A boolean applies to the runtime as a whole; an object enables individual features. |
setup | (ctx, info?) => void | Promise<void> | Required. Server-side entry point. Runs in every runtime. The optional second argument carries runtime metadata — most notably the parsed CLI flags when running under createCli. |
setupBrowser | (ctx) => void | Promise<void> | Browser-only entry used by the SPA adapter. |
cli | DevframeCliOptions | Defaults for the CLI adapter. See CLI options below. |
spa | DevframeSpaOptions | Defaults for the SPA adapter (base, loader). |
Sourcing metadata from package.json
Keep version, packageName, homepage, and description in sync with the package you publish by importing them straight from its package.json. Note that the package's name field maps to packageName — the devframe name is a separate display label.
import pkg from '../package.json' with { type: 'json' }
export default defineDevframe({
id: 'my-devframe',
name: 'My Devframe', // display label
version: pkg.version,
packageName: pkg.name,
homepage: pkg.homepage,
description: pkg.description,
setup(ctx) { /* … */ },
})The default import with a with { type: 'json' } attribute resolves under both bundlers and Node's native TypeScript execution. Bundlers also support the destructured import { version } from '../package.json' form when the devframe is always bundled before it runs.
Runtime flags
The ctx.mode field is either 'dev' or 'build'. Use it to gate work that should only run in one runtime:
defineDevframe({
id: 'my-devframe',
name: 'My Devframe',
setup(ctx) {
if (ctx.mode === 'build') {
// Static-only work — baked into the RPC dump.
}
else {
// Dev-mode wiring, file watchers, etc.
}
},
})The CLI dev server sets mode: 'dev'; createBuild sets mode: 'build'.
The setup context
setup(ctx) receives a DevframeNodeContext:
interface DevframeNodeContext {
readonly cwd: string
readonly workspaceRoot: string
readonly mode: 'dev' | 'build'
host: DevframeHost // runtime abstraction (mountStatic / resolveOrigin / getStorageDir)
rpc: RpcFunctionsHost // register + broadcast + sharedState
views: DevframeViewHost // static file hosting (`hostStatic`)
diagnostics: DevframeDiagnosticsHost
agent: DevframeAgentHost // experimental
}Host adapters can augment ctx with additional surfaces. For example, the vite adapter exposes Vite DevTools' dock, command, message, and terminal hosts via an optional setup hook on createPluginFromDevframe — consult the host's docs for those extras.
Each devframe-level host has a dedicated page:
- RPC —
ctx.rpc - Shared State —
ctx.rpc.sharedState - Diagnostics —
ctx.diagnostics - Agent-Native —
ctx.agent
Browser setup
The SPA adapter supports a setupBrowser(ctx) hook that runs inside the deployed client bundle. Use it for tools that perform their own in-browser work — parsing a dropped file, calling public APIs from the client, etc.
defineDevframe({
id: 'my-devframe',
name: 'My Devframe',
setup(ctx) { /* server-side */ },
setupBrowser(ctx) {
// `ctx.rpc` is the write-disabled static client in SPA mode.
},
})Deployed SPAs that use setupBrowser ship their own client entry that registers the handlers.
CLI options
cli configures the CLI adapter's defaults and plugs additional flags/commands into the CAC instance:
defineDevframe({
id: 'my-devframe',
name: 'My Devframe',
cli: {
command: 'my-devframe', // binary name; default: the `id`
distDir: './client/dist', // required for dev / build / spa
port: 9876, // preferred port; default: 9999
portRange: [9876, 10000], // forwarded to get-port-please
random: false, // forwarded to get-port-please
host: 'localhost', // default host; --host overrides
open: true, // auto-open the browser on dev start
auth: false, // skip the trust handshake (single-user localhost)
configure(cli) { // contribute capability flags/commands
cli
.option('--my-flag <value>', 'Tool-specific flag')
},
},
setup(ctx, { flags }) {
// `flags` carries the parsed cac bag — contains built-in flags
// (`--port`, `--host`, `--open`, `--no-open`) and anything you added
// in `configure`.
},
})| Field | Type | Description |
|---|---|---|
command | string | Binary name surfaced in --help. Default: the definition's id. |
distDir | string | SPA dist directory. Required for dev / build / spa. |
port | number | Preferred port for the dev server. |
portRange | [number, number] | Port scan range, passed through to get-port-please. |
random | boolean | Prefer a random open port. |
host | string | Default bind host. |
open | boolean | string | true opens the origin, a string opens a specific path, false disables. Matches the --open / --no-open flags. |
auth | boolean | Disable the WS trust flow when the tool is localhost-only and single-user. Default true. |
configure | (cli: CAC) => void | Contribute capability flags/commands. Runs before createCli's configureCli option so the final tool author always has the last word. |
setup(ctx, info) receives info.flags populated from both devframe's built-in flags and any you declared via configure — saves duplicating flag parsing.
SPA options
defineDevframe({
id: 'my-devframe',
spa: {
base: '/',
loader: 'query', // 'query' | 'upload' | 'none'
},
})See Adapters for how each adapter consumes these.
Multiple runtimes, one definition
The definition is a plain value, so wire it into multiple adapters from the same file:
import { createPluginFromDevframe } from '@vitejs/devtools-kit/node'
import { createBuild } from 'devframe/adapters/build'
import { createCli } from 'devframe/adapters/cli'
const devframe = defineDevframe({ id: 'my-devframe', name: 'My Devframe', setup() {} })
// 1. Standalone CLI:
await createCli(devframe).parse()
// 2. Offline snapshot:
await createBuild(devframe, { outDir: 'dist-static' })
// 3. Mount into a host (Vite DevTools shown — other hosts can implement equivalents):
export const myPlugin = () => createPluginFromDevframe(devframe)What's next
- Adapters — pick a deployment target
- RPC — register server functions
viteadapter — mount your devframe into Vite DevTools or another compatible host