Build a devtool once. Mount it anywhere.
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.
// 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.
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.
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.
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.
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
DevframeDefinition and pick the entry points your package ships — hosted, standalone, embedded, or agentic.