Skip to content

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 ​

ts
import { defineDevframe, defineRpcFunction } from 'devframe'
import * as v from 'valibot'

export default defineDevframe({
  id: 'my-devframe',
  name: 'My Devframe',
  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 ​

FieldTypeDescription
idstringRequired. Unique, namespaced identifier (kebab-case). Used as a prefix for RPC names, dock IDs, and MCP tool names.
namestringRequired. Display name shown in the dock and agent manifests.
iconstring | { light, dark }Optional Iconify name or URL; supports light/dark pairs.
versionstringOptional version string surfaced to clients.
basePathstringOptional mount path override. Defaults depend on the adapter: / for standalone (cli / spa / build), /.<id>/ for hosted (vite / embedded).
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.
cliDevframeCliOptionsDefaults for the CLI adapter. See CLI options below.
spaDevframeSpaOptionsDefaults for the SPA adapter (base, loader).

Runtime flags ​

The ctx.mode field is either 'dev' or 'build'. Use it to gate work that should only run in one runtime:

ts
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 DevToolsNodeContext:

ts
interface DevToolsNodeContext {
  readonly cwd: string
  readonly workspaceRoot: string
  readonly mode: 'dev' | 'build'

  host: DevToolsHost // runtime abstraction (mountStatic / resolveOrigin / getStorageDir)
  rpc: RpcFunctionsHost // register + broadcast + sharedState
  views: DevToolsViewHost // static file hosting (`hostStatic`)
  diagnostics: DevToolsDiagnosticsHost
  agent: DevToolsAgentHost // 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:

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.

ts
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:

ts
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`.
  },
})
FieldTypeDescription
commandstringBinary name surfaced in --help. Default: the definition's id.
distDirstringSPA dist directory. Required for dev / build / spa.
portnumberPreferred port for the dev server.
portRange[number, number]Port scan range, passed through to get-port-please.
randombooleanPrefer a random open port.
hoststringDefault bind host.
openboolean | stringtrue opens the origin, a string opens a specific path, false disables. Matches the --open / --no-open flags.
authbooleanDisable the WS trust flow when the tool is localhost-only and single-user. Default true.
configure(cli: CAC) => voidContribute 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 ​

ts
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:

ts
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
  • vite adapter — mount your devframe into Vite DevTools or another compatible host

Released under the MIT License.