Build a devtool once. Mount it anywhere.

A framework-neutral foundation for devtools. One definition becomes a Web Standard handler you can mount into any host, ship as a CLI or static report, and expose to coding agents.
import { defineDevframe } from 'devframe'
import { initDevframe } from 'devframe/initiate'

const myDevframe = defineDevframe({
  id: 'my-tool',
  name: 'My Tool',
  rpc: [getModules],
  view: { type: 'spa', distDir: './dist/client' },
})

// one Request → Response handler, any host
const { handler } = initDevframe(myDevframe, {
  base: '/__my-tool/',
})

// Foundation //

One definition, every entry point

defineDevframe() describes a tool once. initDevframe() turns it into a Web Standard Request → Response handler — and adapters reshape that same definition into whatever your package ships.

One Standard Handler
Mount the same handler into Hono, Nitro, Next.js, SvelteKit, Vite, Rsbuild, Deno, or Bun.
Adapters as Conveniences
The same definition also becomes a standalone CLI, a dev server, a static report, an MCP server, or a Vite DevTools dock.
Type-safe RPC & Shared State
Bidirectional calls built on birpc, validated against any Standard Schema validator, plus observable patch-synced state that survives reconnects.
Visual and Agentic
Expose the same internal state to a web UI and to coding agents over MCP — one source of truth, two interfaces.
From One Devframe to a Hub
@devframes/hub composes many devframes behind one handler with docks, commands, terminals, and messages.
Built-in Plugins, Any Framework
Official plugins span Vue, Svelte, Solid, and React — devframe owns the protocol and leaves the UI framework to the author.

// Portability //

The same handler, mounted natively

A devframe's boundary is simply the Web Standard Request and Response. Any framework that speaks that — or connect-style middleware — mounts the same tool and inherits the whole ecosystem. Only the host-facing glue changes. See all adapters.

server.ts
import { Hono } from 'hono'
import { devtools } from './devtools'

const app = new Hono()

app.all(`${devtools.base}*`, c => devtools.handler(c.req.raw))

// Adapters //

Package it the way your tool ships

The handler is the smallest common denominator. Higher-level adapters package that same definition into familiar forms — pick the entry points your package needs. Browse the adapters.

cli.ts
import { createCac } from 'devframe/adapters/cac'
import myDevframe from './my-tool'

createCac(myDevframe).parse()

// Interfaces //

One capability, two interfaces

RPC functions stay private by default and opt into agent exposure explicitly. The MCP adapter translates those functions, resources, and selected shared state into an agent-consumable surface — the presentation changes, the source of truth stays the same.

rpc.ts
import { defineRpcFunction } from 'devframe'

export const inspectBuild = defineRpcFunction({
  name: 'inspect-build',
  type: 'query',
  handler: () => readBuildGraph(),
})

// Hub //

From one devframe to a devtools host

When several devtools run at once, discovery becomes the problem. @devframes/hub is a headless composition layer: many devframes register docks, commands, terminals, and shared state, and appear through one consistent entry. Learn about the hub.

hub.ts
import { initHub } from '@devframes/hub/initiate'
import { createTerminalsDevframe } from '@devframes/plugin-terminals'

const hub = initHub({
  base: '/__devframes/',
  devframes: [
    createTerminalsDevframe(),
    // ...more devframes
  ],
  ui: await import('@devframes/hub-ui').then(m => m.createUi()),
})

Ship your devtool everywhere

Start from one DevframeDefinition and pick the entry points your package ships — hosted, standalone, embedded, or agentic.