diff --git a/README.md b/README.md
index 80d596d..1c4cbb9 100644
--- a/README.md
+++ b/README.md
@@ -6,63 +6,21 @@
Devframe
-[![npm version][npm-version-src]][npm-version-href]
-[![npm downloads][npm-downloads-src]][npm-downloads-href]
-[![bundle][bundle-src]][bundle-href]
-[![JSDocs][jsdocs-src]][jsdocs-href]
-[![License][license-src]][license-href]
-
-Framework-neutral foundation for building generic DevTools. Describe one devframe — its RPC, its data, its SPA, its CLI shape — and deploy the same definition through any of seven adapters.
-
-Documentation: [https://devfra.me/](https://devfra.me/).
-
-## Install
-
-```sh
-pnpm add devframe
-```
-
-## Hello, Devframe
-
-```ts
-import { defineDevframe, defineRpcFunction } from 'devframe'
-import { createCli } from 'devframe/adapters/cli'
-
-const devframe = defineDevframe({
- id: 'my-devframe',
- name: 'My Devframe',
- setup(ctx) {
- ctx.rpc.register(defineRpcFunction({
- name: 'my-devframe:hello',
- type: 'static',
- jsonSerializable: true,
- handler: () => ({ message: 'hello' }),
- }))
- },
-})
-
-await createCli(devframe).parse()
-```
-
-## Adapters
-
-| Adapter | Use case |
-|---------|----------|
-| `cli` | Standalone CLI tool with `dev` / `build` / `mcp` subcommands. |
-| `build` | Generates a static, self-contained SPA snapshot. |
-| `vite` | Mounts the devframe into Vite DevTools (or any compatible host) via `@vitejs/devtools-kit`. |
-| `embedded` | Overlays inside another devframe's UI. |
-| `mcp` | Surfaces the devframe's RPC to coding agents over MCP. |
+
+
+
+
+
+
+
-## Repo layout
+
+Framework-neutral foundation for building generic DevTools.
+
-| Path | Description |
-|------|-------------|
-| [`packages/devframe`](./packages/devframe) | The published [`devframe`](https://www.npmjs.com/package/devframe) npm package. |
-| [`packages/nuxt`](./packages/nuxt) | The [`@devframes/nuxt`](https://www.npmjs.com/package/@devframes/nuxt) Nuxt module adapter. |
-| [`docs`](./docs) | VitePress documentation site, deployed at https://devfra.me/. |
-| [`examples`](./examples) | End-to-end demos: [`devframe-counter`](./examples/devframe-counter), [`devframe-files-inspector`](./examples/devframe-files-inspector), and [`devframe-streaming-chat`](./examples/devframe-streaming-chat). |
-| [`tests`](./tests) | Public-API snapshot tests via [`tsnapi`](https://github.com/posva/tsnapi). |
+
+Documentation: https://devfra.me/
+
## Sponsors
@@ -75,16 +33,3 @@ await createCli(devframe).parse()
## License
[MIT](./LICENSE.md) License © [Anthony Fu](https://github.com/antfu)
-
-
-
-[npm-version-src]: https://img.shields.io/npm/v/devframe?style=flat&colorA=080f12&colorB=517158
-[npm-version-href]: https://npmx.dev/package/devframe
-[npm-downloads-src]: https://img.shields.io/npm/dm/devframe?style=flat&colorA=080f12&colorB=517158
-[npm-downloads-href]: https://npmx.dev/package/devframe
-[bundle-src]: https://img.shields.io/bundlephobia/minzip/devframe?style=flat&colorA=080f12&colorB=517158&label=minzip
-[bundle-href]: https://bundlephobia.com/result?p=devframe
-[license-src]: https://img.shields.io/github/license/devframes/devframe.svg?style=flat&colorA=080f12&colorB=517158
-[license-href]: https://github.com/devframes/devframe/blob/main/LICENSE.md
-[jsdocs-src]: https://img.shields.io/badge/jsdocs-reference-080f12?style=flat&colorA=080f12&colorB=517158
-[jsdocs-href]: https://www.jsdocs.io/package/devframe
diff --git a/docs/.vitepress/config.ts b/docs/.vitepress/config.ts
index 0cf177b..23a0f11 100644
--- a/docs/.vitepress/config.ts
+++ b/docs/.vitepress/config.ts
@@ -3,9 +3,13 @@ import { fileURLToPath } from 'node:url'
import { globSync } from 'tinyglobby'
import { defineConfig } from 'vitepress'
import { withMermaid } from 'vitepress-plugin-mermaid'
+import pkg from '../../packages/devframe/package.json' with { type: 'json' }
const errorsDir = fileURLToPath(new URL('../errors/', import.meta.url))
+const repo = 'https://github.com/devframes/devframe'
+const brandColor = '#517158'
+
function listErrorCodes(prefix: string): string[] {
return globSync(`${prefix}*.md`, { cwd: errorsDir })
.map(f => f.replace(/\.md$/, ''))
@@ -16,26 +20,53 @@ function guideItems(prefix: string): DefaultTheme.NavItemWithLink[] {
return [
{ text: 'Introduction', link: `${prefix}/guide/` },
{ text: 'Devframe Definition', link: `${prefix}/guide/devframe-definition` },
- { text: 'Adapters', link: `${prefix}/guide/adapters` },
{ text: 'RPC', link: `${prefix}/guide/rpc` },
{ text: 'Shared State', link: `${prefix}/guide/shared-state` },
{ text: 'Streaming', link: `${prefix}/guide/streaming` },
{ text: 'When Clauses', link: `${prefix}/guide/when-clauses` },
{ text: 'Structured Diagnostics', link: `${prefix}/guide/diagnostics` },
- { text: 'Utilities', link: `${prefix}/guide/utilities` },
{ text: 'Client', link: `${prefix}/guide/client` },
{ text: 'Standalone CLI', link: `${prefix}/guide/standalone-cli` },
- { text: 'Nuxt Helper', link: `${prefix}/guide/nuxt` },
{ text: 'Agent-Native (experimental)', link: `${prefix}/guide/agent-native` },
]
}
+function adaptersItems(prefix: string): DefaultTheme.NavItemWithLink[] {
+ return [
+ { text: 'Overview', link: `${prefix}/adapters/` },
+ { text: 'CLI', link: `${prefix}/adapters/cli` },
+ { text: 'Dev', link: `${prefix}/adapters/dev` },
+ { text: 'Build', link: `${prefix}/adapters/build` },
+ { text: 'Vite', link: `${prefix}/adapters/vite` },
+ { text: 'Embedded', link: `${prefix}/adapters/embedded` },
+ { text: 'MCP', link: `${prefix}/adapters/mcp` },
+ ]
+}
+
+function helpersItems(prefix: string): DefaultTheme.NavItemWithLink[] {
+ return [
+ { text: 'Overview', link: `${prefix}/helpers/` },
+ { text: 'Utilities', link: `${prefix}/helpers/utilities` },
+ { text: 'Vite Bridge', link: `${prefix}/helpers/vite-bridge` },
+ { text: 'Nuxt Module', link: `${prefix}/helpers/nuxt` },
+ { text: 'Open Helpers', link: `${prefix}/helpers/open-helpers` },
+ ]
+}
+
export function devframeSidebar(prefix = ''): DefaultTheme.SidebarItem[] {
return [
{
text: 'Guide',
items: guideItems(prefix),
},
+ {
+ text: 'Adapters',
+ items: adaptersItems(prefix),
+ },
+ {
+ text: 'Helpers',
+ items: helpersItems(prefix),
+ },
{
text: 'Error Reference',
link: `${prefix}/errors/`,
@@ -48,10 +79,19 @@ export function devframeSidebar(prefix = ''): DefaultTheme.SidebarItem[] {
]
}
-export function devframeNav(prefix = ''): DefaultTheme.NavItemWithLink[] {
+export function devframeNav(prefix = ''): DefaultTheme.NavItem[] {
return [
- ...guideItems(prefix),
- { text: 'Error Reference', link: `${prefix}/errors/` },
+ { text: 'Guide', items: guideItems(prefix) },
+ { text: 'Adapters', items: adaptersItems(prefix) },
+ { text: 'Helpers', items: helpersItems(prefix) },
+ { text: 'Errors', link: `${prefix}/errors/` },
+ {
+ text: `v${pkg.version}`,
+ items: [
+ { text: 'Release Notes', link: `${repo}/releases` },
+ { text: 'Contributing', link: `${repo}/blob/main/CONTRIBUTING.md` },
+ ],
+ },
]
}
@@ -60,22 +100,22 @@ export default withMermaid(defineConfig({
description: 'Framework-neutral foundation for building generic DevTools — RPC layer, hosts, and adapters.',
head: [
['link', { rel: 'icon', type: 'image/svg+xml', href: '/logo.svg' }],
+ ['link', { rel: 'apple-touch-icon', href: '/logo.svg' }],
+ ['link', { rel: 'mask-icon', href: '/logo.svg', color: brandColor }],
+ ['meta', { name: 'theme-color', content: brandColor }],
],
themeConfig: {
logo: { light: '/logo.svg', dark: '/logo.svg' },
- nav: [
- { text: 'Guide', items: guideItems('') },
- { text: 'Error Reference', link: '/errors/' },
- ],
+ nav: devframeNav(),
sidebar: devframeSidebar(),
search: {
provider: 'local',
},
socialLinks: [
- { icon: 'github', link: 'https://github.com/devframes/devframe' },
+ { icon: 'github', link: repo },
],
editLink: {
- pattern: 'https://github.com/devframes/devframe/edit/main/docs/:path',
+ pattern: `${repo}/edit/main/docs/:path`,
text: 'Suggest changes to this page',
},
footer: {
diff --git a/docs/adapters/build.md b/docs/adapters/build.md
new file mode 100644
index 0000000..0e63cca
--- /dev/null
+++ b/docs/adapters/build.md
@@ -0,0 +1,41 @@
+---
+outline: deep
+---
+
+# Build
+
+Produces a self-contained static deploy of a devframe:
+
+1. Copies the author's SPA dist (`cli.distDir` or `options.distDir`) into ``.
+2. Runs `setup(ctx)` with `mode: 'build'`.
+3. Collects RPC dumps for every `'static'` function and any `'query'` function with `dump.inputs` / `snapshot: true`.
+4. Writes `/__connection.json` (`{ backend: 'static' }`) and sharded dump files under `/__rpc-dump/` — both at the SPA root so the deployed client discovers them via relative paths from `document.baseURI`.
+5. When `def.spa` is set, also writes `/spa-loader.json` describing how the SPA hydrates its data.
+
+```ts
+import { createBuild } from 'devframe/adapters/build'
+import devframe from './devframe'
+
+await createBuild(devframe, {
+ outDir: 'dist-static',
+ base: '/',
+})
+```
+
+| Option | Default | Description |
+|--------|---------|-------------|
+| `outDir` | `dist-static` | Output directory. Cleared on each build. |
+| `base` | `/` | Absolute URL base the output is served from. |
+| `distDir` | `def.cli?.distDir` | Override the SPA dist directory. |
+
+The resulting directory hosts on any static web server (`serve`, nginx, GitHub Pages, …). The client auto-detects `static` mode by resolving `./__connection.json` against `document.baseURI` and runs in read-only form.
+
+`createBuild` copies the SPA verbatim, so deploying under a custom URL base just means building the SPA with relative asset paths (`vite.base: './'`) — the client discovers the effective base at runtime.
+
+When `def.spa` is set on the definition, `createBuild` also writes `spa-loader.json` next to `index.html` describing how the deployed SPA sources its data:
+
+- `'none'` — use the baked RPC dump only (read-only static view).
+- `'query'` — hydrate from URL search params.
+- `'upload'` — accept a drag-and-drop file.
+
+Deployed SPAs that use `setupBrowser` ship their own client entry that registers the handlers.
diff --git a/docs/adapters/cli.md b/docs/adapters/cli.md
new file mode 100644
index 0000000..e320d90
--- /dev/null
+++ b/docs/adapters/cli.md
@@ -0,0 +1,110 @@
+---
+outline: deep
+---
+
+# CLI
+
+The CLI adapter wraps a `DevframeDefinition` in a `cac`-powered command-line interface. From one entry it spins up an `h3` dev server with WebSocket RPC, builds static snapshots, builds SPA bundles, or starts an MCP server.
+
+```ts
+import { defineDevframe } from 'devframe'
+import { createCli } from 'devframe/adapters/cli'
+
+const devframe = defineDevframe({
+ id: 'my-devframe',
+ name: 'My Devframe',
+ cli: { distDir: './client/dist' },
+ setup(ctx) { /* register docks, RPC, etc. */ },
+})
+
+await createCli(devframe).parse()
+```
+
+Running the resulting binary:
+
+```sh
+my-devframe # dev server at http://localhost:9999/
+my-devframe --port 8080
+my-devframe build --out-dir dist-static
+my-devframe build --out-dir dist-static --base /devtools/
+my-devframe mcp # stdio MCP server (experimental)
+```
+
+Standalone CLI serves the SPA at `/` by default. The `/__devtools/` prefix is for *hosted* adapters where devframe mounts alongside an existing app — see [Mount paths](./#mount-paths).
+
+## Options
+
+`createCli(def, options?)` accepts:
+
+| Option | Default | Description |
+|--------|---------|-------------|
+| `defaultPort` | `9999` (or `def.cli?.port`) | Port used by the dev command when `--port` isn't provided. |
+| `configureCli` | — | `(cli: CAC) => void` — final hook to add commands/flags at the assembly stage, after the definition's `cli.configure` runs. |
+| `onReady` | — | `(info: { origin, port, app }) => void \| Promise` — called once the dev server is listening. Use this to print your own startup banner. |
+
+`createCli` returns a `CliHandle`:
+
+```ts
+interface CliHandle {
+ cli: CAC // raw cac instance — mutate before calling parse()
+ parse: (argv?: string[]) => Promise
+}
+```
+
+The `cli` property lets the caller add ad-hoc commands and flags right before `parse()` when a `configureCli` callback is inconvenient.
+
+## Definition-level `cli` fields
+
+```ts
+defineDevframe({
+ id: 'my-devframe',
+ cli: {
+ command: 'my-devframe', // binary name; default: the id
+ distDir: './client/dist', // required for dev/build/spa
+ port: 7777, // preferred port
+ portRange: [7777, 9000], // passed through to get-port-please
+ random: false, // passed through to get-port-please
+ host: '127.0.0.1', // 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('--config ', 'Custom config file')
+ .option('--no-files', 'Skip file matching')
+ },
+ },
+ setup(ctx, { flags }) {
+ // `flags` is the parsed cac flag bag — includes both devframe's
+ // built-ins (`--port`, `--host`, `--open`) and anything declared in
+ // `cli.configure` or `configureCli`.
+ },
+})
+```
+
+`distDir` is the only required field; everything else has sensible defaults. The `configure` hook runs *before* the `configureCli` option passed to `createCli`, so the final tool author always has the last word on flags.
+
+## Headless logging
+
+Devframe leaves startup output to the application. Wire `onReady` to print your own banner:
+
+```ts
+await createCli(devframe, {
+ onReady({ origin }) {
+ console.log(`ESLint Config Inspector ready at ${origin}`)
+ },
+}).parse()
+```
+
+Structured diagnostics (via `logs-sdk`) continue to surface through their normal reporters.
+
+## Use your own CLI framework
+
+To integrate devframe into an existing commander / yargs program — or to expose a different command structure than `createCli`'s `dev` / `build` / `mcp` triplet — drop down to the peer factories. Same `DevframeDefinition`, different shell:
+
+| Building block | Entry | Purpose |
+|----------------|-------|---------|
+| [`createDevServer(def, opts?)`](./dev) | `devframe/adapters/dev` | h3 + WebSocket RPC + SPA mount |
+| [`createBuild(def, opts?)`](./build) | `devframe/adapters/build` | Static deploy |
+| [`createMcpServer(def, opts?)`](./mcp) | `devframe/adapters/mcp` | stdio MCP server |
+| `parseCliFlags(schema, raw)` | `devframe/adapters/cli` | Validate a flag bag against a `CliFlagsSchema` |
+
+See the [Standalone CLI guide](/guide/standalone-cli#use-your-own-cli-framework) for a worked commander example.
diff --git a/docs/adapters/dev.md b/docs/adapters/dev.md
new file mode 100644
index 0000000..12124dc
--- /dev/null
+++ b/docs/adapters/dev.md
@@ -0,0 +1,49 @@
+---
+outline: deep
+---
+
+# Dev
+
+The `dev` adapter is the building block `createCli` uses internally — h3 + WebSocket RPC + the author's SPA mounted at the resolved base path. Reach for it directly to mount the dev server inside an existing CLI program (commander, yargs, hand-rolled CAC) or to attach custom middleware to the underlying h3 app.
+
+```ts
+import { createDevServer } from 'devframe/adapters/dev'
+import devframe from './devframe'
+
+const handle = await createDevServer(devframe, {
+ port: 7777,
+ onReady: ({ origin }) => console.log(`Ready at ${origin}`),
+})
+
+// graceful shutdown — SIGINT, hot reload, test teardown
+process.on('SIGINT', () => handle.close().then(() => process.exit(0)))
+```
+
+`createDevServer` returns the underlying `StartedServer` (origin, port, h3 app, WS server, RPC group, `close()`) so callers can integrate it into their own process lifecycle.
+
+| Option | Default | Description |
+|--------|---------|-------------|
+| `host` | `def.cli?.host ?? 'localhost'` | Bind host. |
+| `port` | resolved via `resolveDevServerPort` | Port to listen on. |
+| `flags` | `{}` | Parsed flag bag forwarded to `setup(ctx, { flags })`. |
+| `distDir` | `def.cli?.distDir` | Required — throws when neither is set. |
+| `basePath` | `resolveBasePath(def, 'standalone')` | Mount path override. |
+| `app` | fresh h3 app | Pre-configured h3 app to mount onto (custom middleware, auth, extra static assets). |
+| `openBrowser` | resolves from `flags.open` / `def.cli?.open` | Explicit on/off override. `false` disables; a string opens that relative path. |
+| `onReady` | — | Callback when the WS server is bound. |
+
+## Port resolution
+
+`resolveDevServerPort(def, opts?)` resolves a port up-front (to print or log it) before the server starts:
+
+```ts
+import { resolveDevServerPort } from 'devframe/adapters/dev'
+
+const port = await resolveDevServerPort(devframe, { host: '127.0.0.1' })
+// honors def.cli?.port / portRange / random
+```
+
+| Option | Default | Description |
+|--------|---------|-------------|
+| `host` | `def.cli?.host ?? 'localhost'` | Bind host (passed to `get-port-please` for in-use detection). |
+| `defaultPort` | `def.cli?.port ?? 9999` | Override the preferred port. |
diff --git a/docs/adapters/embedded.md b/docs/adapters/embedded.md
new file mode 100644
index 0000000..5d2ae0b
--- /dev/null
+++ b/docs/adapters/embedded.md
@@ -0,0 +1,20 @@
+---
+outline: deep
+---
+
+# Embedded
+
+Register a devframe into an already-running context at runtime. Mirrors the [`vite`](./vite) adapter's plugin-scan, but for callers that need dynamic, post-startup registration. The host decides the mount path; `embedded` is a hosted adapter and inherits the `/__/` default when one is needed.
+
+```ts
+import { createEmbedded } from 'devframe/adapters/embedded'
+import devframe from './devframe'
+
+await createEmbedded(devframe, { ctx: existingCtx })
+```
+
+| Option | Required | Description |
+|--------|----------|-------------|
+| `ctx` | ✓ | Target `DevToolsNodeContext` the devframe is registered into. |
+
+Useful when a host loads devframes based on runtime conditions (feature flags, user opt-in, dynamic discovery) rather than static config.
diff --git a/docs/adapters/index.md b/docs/adapters/index.md
new file mode 100644
index 0000000..958c4a5
--- /dev/null
+++ b/docs/adapters/index.md
@@ -0,0 +1,41 @@
+---
+outline: deep
+---
+
+# Adapters
+
+An adapter takes a `DevframeDefinition` and deploys it into a specific runtime — a standalone CLI, a Vite plugin, a static snapshot, an embedded host, or an MCP server. Each adapter ships at its own entry point (`devframe/adapters/`); the bundler pulls in only the ones you use.
+
+Every adapter factory has the shape `createXxx(devframeDef, options?)`.
+
+## Comparison
+
+| Adapter | Entry | Factory | Best for |
+|---------|-------|---------|----------|
+| [`cli`](./cli) | `devframe/adapters/cli` | `createCli(def, options?)` | Standalone tools run via `node ./my-tool.js` |
+| [`dev`](./dev) | `devframe/adapters/dev` | `createDevServer(def, options?)` | Run the dev server programmatically — drive it from any CLI framework |
+| [`build`](./build) | `devframe/adapters/build` | `createBuild(def, options?)` | Offline reports, CI artifacts, deployable SPA snapshots |
+| [`vite`](./vite) | `@vitejs/devtools-kit/node` | `createPluginFromDevframe(def, options?)` | Mount the definition into Vite DevTools (or any compatible host) |
+| [`embedded`](./embedded) | `devframe/adapters/embedded` | `createEmbedded(def, { ctx })` | Runtime registration into an already-running host |
+| [`mcp`](./mcp) | `devframe/adapters/mcp` | `createMcpServer(def, options?)` | Exposing a devframe to coding agents |
+
+## Mount paths
+
+A devframe's SPA basePath depends on which adapter is running it:
+
+| Adapter kind | Default basePath | Reason |
+|--------------|------------------|--------|
+| `cli`, `spa`, `build` (standalone) | `/` | The devframe owns the origin. |
+| `vite`, `embedded` (hosted) | `/__/` | The devframe shares the origin with a host app and namespaces itself. |
+
+Override either side explicitly with `DevframeDefinition.basePath`:
+
+```ts
+defineDevframe({
+ id: 'my-devframe',
+ basePath: '/devframes/', // force this base regardless of adapter
+ setup(ctx) { /* … */ },
+})
+```
+
+SPA authors should build with relative asset paths (`vite.base: './'`); the client resolves its connection descriptor relative to the page at runtime. See [Client](/guide/client#runtime-basepath-discovery) for the discovery rules.
diff --git a/docs/adapters/mcp.md b/docs/adapters/mcp.md
new file mode 100644
index 0000000..212112c
--- /dev/null
+++ b/docs/adapters/mcp.md
@@ -0,0 +1,21 @@
+---
+outline: deep
+---
+
+# MCP
+
+> [!WARNING] Experimental
+> The agent-native surface is experimental and may change without a major version bump.
+
+Translates a devframe's agent host into a [Model Context Protocol](https://modelcontextprotocol.io) server so coding agents (Claude Desktop, Cursor, Zed, Claude Code) can call flagged RPCs and read exposed resources.
+
+```ts
+import { createMcpServer } from 'devframe/adapters/mcp'
+import devframe from './devframe'
+
+await createMcpServer(devframe, { transport: 'stdio' })
+```
+
+`@modelcontextprotocol/sdk` is a peer dependency — install it when shipping MCP support. The current transport is `stdio`.
+
+See the [Agent-Native](/guide/agent-native) page for the full API, safety model, and Claude Desktop integration example.
diff --git a/docs/adapters/vite.md b/docs/adapters/vite.md
new file mode 100644
index 0000000..504ec04
--- /dev/null
+++ b/docs/adapters/vite.md
@@ -0,0 +1,25 @@
+---
+outline: deep
+---
+
+# Vite
+
+The Vite-DevTools adapter — wraps a `DevframeDefinition` so Vite DevTools' kit plugin-scan picks it up. The factory lives in `@vitejs/devtools-kit/node` so devframe itself stays free of any Vite or `@vitejs/*` dependency. The pattern (`definition → host plugin → mount`) is general; other hosts can implement equivalent bridges.
+
+```ts
+import { createPluginFromDevframe } from '@vitejs/devtools-kit/node'
+import devframe from './devframe'
+
+export default function myVitePlugin() {
+ return createPluginFromDevframe(devframe)
+}
+```
+
+The returned object has the shape `{ name, devtools: { setup, capabilities } }`. Use this adapter when your devframe should live inside the Vite DevTools dock alongside other integrations. The kit synthesises an iframe dock entry from the definition's `id` / `name` / `icon` / `basePath`; for richer host-side behaviour (extra terminals, commands, dock overrides) pass `options.setup`. See the [DevTools Kit → DevTools Plugin](https://devtools.vite.dev/kit/devtools-plugin) page for the Vite-specific guide.
+
+| Option | Default | Description |
+|--------|---------|-------------|
+| `name` | `devframe:` | Override the Vite plugin name. |
+| `base` | `def.basePath ?? /.${id}/` | Mount path override. |
+| `dock` | `{}` | Overrides for the synthesized iframe dock entry (category, icon, when). |
+| `setup` | — | Additional host-only setup hook; receives the kit-augmented context (Vite DevTools' `docks`, `terminals`, `messages`, `commands`). |
diff --git a/docs/guide/adapters.md b/docs/guide/adapters.md
deleted file mode 100644
index 6d637a5..0000000
--- a/docs/guide/adapters.md
+++ /dev/null
@@ -1,315 +0,0 @@
----
-outline: deep
----
-
-# Adapters
-
-An adapter takes a `DevframeDefinition` and deploys it into a specific runtime — a standalone CLI, a Vite plugin, a static snapshot, an SPA, a Kit plugin, an embedded host, or an MCP server. Each adapter ships at its own entry point (`devframe/adapters/`); the bundler pulls in only the ones you use.
-
-Every adapter factory has the shape `createXxx(devframeDef, options?)`.
-
-## Comparison
-
-| Adapter | Entry | Factory | Best for |
-|---------|-------|---------|----------|
-| [`cli`](#cli) | `devframe/adapters/cli` | `createCli(def, options?)` | Standalone tools run via `node ./my-tool.js` |
-| [`dev`](#dev) | `devframe/adapters/dev` | `createDevServer(def, options?)` | Run the dev server programmatically — drive it from any CLI framework |
-| [`build`](#build) | `devframe/adapters/build` | `createBuild(def, options?)` | Offline reports, CI artifacts, deployable SPA snapshots |
-| [`vite`](#vite) | `@vitejs/devtools-kit/node` | `createPluginFromDevframe(def, options?)` | Mount the definition into Vite DevTools (or any compatible host) |
-| [`embedded`](#embedded) | `devframe/adapters/embedded` | `createEmbedded(def, { ctx })` | Runtime registration into an already-running host |
-| [`mcp`](#mcp) | `devframe/adapters/mcp` | `createMcpServer(def, options?)` | Exposing a devframe to coding agents |
-
-## CLI
-
-The CLI adapter wraps a `DevframeDefinition` in a `cac`-powered command-line interface. From one entry it spins up an `h3` dev server with WebSocket RPC, builds static snapshots, builds SPA bundles, or starts an MCP server.
-
-```ts
-import { defineDevframe } from 'devframe'
-import { createCli } from 'devframe/adapters/cli'
-
-const devframe = defineDevframe({
- id: 'my-devframe',
- name: 'My Devframe',
- cli: { distDir: './client/dist' },
- setup(ctx) { /* register docks, RPC, etc. */ },
-})
-
-await createCli(devframe).parse()
-```
-
-Running the resulting binary:
-
-```sh
-my-devframe # dev server at http://localhost:9999/
-my-devframe --port 8080
-my-devframe build --out-dir dist-static
-my-devframe build --out-dir dist-static --base /devtools/
-my-devframe mcp # stdio MCP server (experimental)
-```
-
-Standalone CLI serves the SPA at `/` by default. The `/__devtools/` prefix is for *hosted* adapters where devframe mounts alongside an existing app — see [Mount paths](#mount-paths).
-
-### Options
-
-`createCli(def, options?)` accepts:
-
-| Option | Default | Description |
-|--------|---------|-------------|
-| `defaultPort` | `9999` (or `def.cli?.port`) | Port used by the dev command when `--port` isn't provided. |
-| `configureCli` | — | `(cli: CAC) => void` — final hook to add commands/flags at the assembly stage, after the definition's `cli.configure` runs. |
-| `onReady` | — | `(info: { origin, port, app }) => void \| Promise` — called once the dev server is listening. Use this to print your own startup banner. |
-
-`createCli` returns a `CliHandle`:
-
-```ts
-interface CliHandle {
- cli: CAC // raw cac instance — mutate before calling parse()
- parse: (argv?: string[]) => Promise
-}
-```
-
-The `cli` property lets the caller add ad-hoc commands and flags right before `parse()` when a `configureCli` callback is inconvenient.
-
-### Definition-level `cli` fields
-
-```ts
-defineDevframe({
- id: 'my-devframe',
- cli: {
- command: 'my-devframe', // binary name; default: the id
- distDir: './client/dist', // required for dev/build/spa
- port: 7777, // preferred port
- portRange: [7777, 9000], // passed through to get-port-please
- random: false, // passed through to get-port-please
- host: '127.0.0.1', // 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('--config ', 'Custom config file')
- .option('--no-files', 'Skip file matching')
- },
- },
- setup(ctx, { flags }) {
- // `flags` is the parsed cac flag bag — includes both devframe's
- // built-ins (`--port`, `--host`, `--open`) and anything declared in
- // `cli.configure` or `configureCli`.
- },
-})
-```
-
-`distDir` is the only required field; everything else has sensible defaults. The `configure` hook runs *before* the `configureCli` option passed to `createCli`, so the final tool author always has the last word on flags.
-
-### Headless logging
-
-Devframe leaves startup output to the application. Wire `onReady` to print your own banner:
-
-```ts
-await createCli(devframe, {
- onReady({ origin }) {
- console.log(`ESLint Config Inspector ready at ${origin}`)
- },
-}).parse()
-```
-
-Structured diagnostics (via `logs-sdk`) continue to surface through their normal reporters.
-
-### Use your own CLI framework
-
-To integrate devframe into an existing commander / yargs program — or to expose a different command structure than `createCli`'s `dev` / `build` / `mcp` triplet — drop down to the peer factories. Same `DevframeDefinition`, different shell:
-
-| Building block | Entry | Purpose |
-|----------------|-------|---------|
-| [`createDevServer(def, opts?)`](#dev) | `devframe/adapters/dev` | h3 + WebSocket RPC + SPA mount |
-| [`createBuild(def, opts?)`](#build) | `devframe/adapters/build` | Static deploy |
-| [`createMcpServer(def, opts?)`](#mcp) | `devframe/adapters/mcp` | stdio MCP server |
-| `parseCliFlags(schema, raw)` | `devframe/adapters/cli` | Validate a flag bag against a `CliFlagsSchema` |
-
-See the [Standalone CLI guide](./standalone-cli#use-your-own-cli-framework) for a worked commander example.
-
-## Dev
-
-The `dev` adapter is the building block `createCli` uses internally — h3 + WebSocket RPC + the author's SPA mounted at the resolved base path. Reach for it directly to mount the dev server inside an existing CLI program (commander, yargs, hand-rolled CAC) or to attach custom middleware to the underlying h3 app.
-
-```ts
-import { createDevServer } from 'devframe/adapters/dev'
-import devframe from './devframe'
-
-const handle = await createDevServer(devframe, {
- port: 7777,
- onReady: ({ origin }) => console.log(`Ready at ${origin}`),
-})
-
-// graceful shutdown — SIGINT, hot reload, test teardown
-process.on('SIGINT', () => handle.close().then(() => process.exit(0)))
-```
-
-`createDevServer` returns the underlying `StartedServer` (origin, port, h3 app, WS server, RPC group, `close()`) so callers can integrate it into their own process lifecycle.
-
-| Option | Default | Description |
-|--------|---------|-------------|
-| `host` | `def.cli?.host ?? 'localhost'` | Bind host. |
-| `port` | resolved via `resolveDevServerPort` | Port to listen on. |
-| `flags` | `{}` | Parsed flag bag forwarded to `setup(ctx, { flags })`. |
-| `distDir` | `def.cli?.distDir` | Required — throws when neither is set. |
-| `basePath` | `resolveBasePath(def, 'standalone')` | Mount path override. |
-| `app` | fresh h3 app | Pre-configured h3 app to mount onto (custom middleware, auth, extra static assets). |
-| `openBrowser` | resolves from `flags.open` / `def.cli?.open` | Explicit on/off override. `false` disables; a string opens that relative path. |
-| `onReady` | — | Callback when the WS server is bound. |
-
-### Port resolution
-
-`resolveDevServerPort(def, opts?)` resolves a port up-front (to print or log it) before the server starts:
-
-```ts
-import { resolveDevServerPort } from 'devframe/adapters/dev'
-
-const port = await resolveDevServerPort(devframe, { host: '127.0.0.1' })
-// honors def.cli?.port / portRange / random
-```
-
-| Option | Default | Description |
-|--------|---------|-------------|
-| `host` | `def.cli?.host ?? 'localhost'` | Bind host (passed to `get-port-please` for in-use detection). |
-| `defaultPort` | `def.cli?.port ?? 9999` | Override the preferred port. |
-
-## Mount paths
-
-A devframe's SPA basePath depends on which adapter is running it:
-
-| Adapter kind | Default basePath | Reason |
-|--------------|------------------|--------|
-| `cli`, `spa`, `build` (standalone) | `/` | The devframe owns the origin. |
-| `vite`, `embedded` (hosted) | `/__/` | The devframe shares the origin with a host app and namespaces itself. |
-
-Override either side explicitly with `DevframeDefinition.basePath`:
-
-```ts
-defineDevframe({
- id: 'my-devframe',
- basePath: '/devframes/', // force this base regardless of adapter
- setup(ctx) { /* … */ },
-})
-```
-
-SPA authors should build with relative asset paths (`vite.base: './'`); the client resolves its connection descriptor relative to the page at runtime. See [Client](./client#runtime-basepath-discovery) for the discovery rules.
-
-## Build
-
-Produces a self-contained static deploy of a devframe:
-
-1. Copies the author's SPA dist (`cli.distDir` or `options.distDir`) into ``.
-2. Runs `setup(ctx)` with `mode: 'build'`.
-3. Collects RPC dumps for every `'static'` function and any `'query'` function with `dump.inputs` / `snapshot: true`.
-4. Writes `/__connection.json` (`{ backend: 'static' }`) and sharded dump files under `/__rpc-dump/` — both at the SPA root so the deployed client discovers them via relative paths from `document.baseURI`.
-5. When `def.spa` is set, also writes `/spa-loader.json` describing how the SPA hydrates its data.
-
-```ts
-import { createBuild } from 'devframe/adapters/build'
-import devframe from './devframe'
-
-await createBuild(devframe, {
- outDir: 'dist-static',
- base: '/',
-})
-```
-
-| Option | Default | Description |
-|--------|---------|-------------|
-| `outDir` | `dist-static` | Output directory. Cleared on each build. |
-| `base` | `/` | Absolute URL base the output is served from. |
-| `distDir` | `def.cli?.distDir` | Override the SPA dist directory. |
-
-The resulting directory hosts on any static web server (`serve`, nginx, GitHub Pages, …). The client auto-detects `static` mode by resolving `./__connection.json` against `document.baseURI` and runs in read-only form.
-
-`createBuild` copies the SPA verbatim, so deploying under a custom URL base just means building the SPA with relative asset paths (`vite.base: './'`) — the client discovers the effective base at runtime.
-
-When `def.spa` is set on the definition, `createBuild` also writes `spa-loader.json` next to `index.html` describing how the deployed SPA sources its data:
-
-- `'none'` — use the baked RPC dump only (read-only static view).
-- `'query'` — hydrate from URL search params.
-- `'upload'` — accept a drag-and-drop file.
-
-Deployed SPAs that use `setupBrowser` ship their own client entry that registers the handlers.
-
-## Vite
-
-The Vite-DevTools adapter — wraps a `DevframeDefinition` so Vite DevTools' kit plugin-scan picks it up. The factory lives in `@vitejs/devtools-kit/node` so devframe itself stays free of any Vite or `@vitejs/*` dependency. The pattern (`definition → host plugin → mount`) is general; other hosts can implement equivalent bridges.
-
-```ts
-import { createPluginFromDevframe } from '@vitejs/devtools-kit/node'
-import devframe from './devframe'
-
-export default function myVitePlugin() {
- return createPluginFromDevframe(devframe)
-}
-```
-
-The returned object has the shape `{ name, devtools: { setup, capabilities } }`. Use this adapter when your devframe should live inside the Vite DevTools dock alongside other integrations. The kit synthesises an iframe dock entry from the definition's `id` / `name` / `icon` / `basePath`; for richer host-side behaviour (extra terminals, commands, dock overrides) pass `options.setup`. See the [DevTools Kit → DevTools Plugin](https://devtools.vite.dev/kit/devtools-plugin) page for the Vite-specific guide.
-
-| Option | Default | Description |
-|--------|---------|-------------|
-| `name` | `devframe:` | Override the Vite plugin name. |
-| `base` | `def.basePath ?? /.${id}/` | Mount path override. |
-| `dock` | `{}` | Overrides for the synthesized iframe dock entry (category, icon, when). |
-| `setup` | — | Additional host-only setup hook; receives the kit-augmented context (Vite DevTools' `docks`, `terminals`, `messages`, `commands`). |
-
-## Embedded
-
-Register a devframe into an already-running context at runtime. Mirrors the `vite` adapter's plugin-scan, but for callers that need dynamic, post-startup registration. The host decides the mount path; `embedded` is a hosted adapter and inherits the `/__/` default when one is needed.
-
-```ts
-import { createEmbedded } from 'devframe/adapters/embedded'
-import devframe from './devframe'
-
-await createEmbedded(devframe, { ctx: existingCtx })
-```
-
-| Option | Required | Description |
-|--------|----------|-------------|
-| `ctx` | ✓ | Target `DevToolsNodeContext` the devframe is registered into. |
-
-Useful when a host loads devframes based on runtime conditions (feature flags, user opt-in, dynamic discovery) rather than static config.
-
-## MCP
-
-> [!WARNING] Experimental
-> The agent-native surface is experimental and may change without a major version bump.
-
-Translates a devframe's agent host into a [Model Context Protocol](https://modelcontextprotocol.io) server so coding agents (Claude Desktop, Cursor, Zed, Claude Code) can call flagged RPCs and read exposed resources.
-
-```ts
-import { createMcpServer } from 'devframe/adapters/mcp'
-import devframe from './devframe'
-
-await createMcpServer(devframe, { transport: 'stdio' })
-```
-
-`@modelcontextprotocol/sdk` is a peer dependency — install it when shipping MCP support. The current transport is `stdio`.
-
-See the [Agent-Native](./agent-native) page for the full API, safety model, and Claude Desktop integration example.
-
-## Helpers
-
-These are not adapters — they're small utilities for integrating devframe into specific runtimes outside the adapter list.
-
-### `devframe/helpers/vite`
-
-A thin Vite plugin for mounting a devframe inside an existing Vite dev server, used by `@devframes/nuxt` and available for any Vite-based host (Astro, SolidStart, plain Vite apps). Two modes:
-
-- **Static mount** (default) — mounts `def.cli.distDir` at `options.base` (`/__/` by default). No RPC server.
-- **Bridge mode** (`devMiddleware: true | {…}`) — skips the static mount; the host app owns the SPA. Devframe spawns a separate RPC + WS server and registers Vite middleware at `__connection.json` so the host-served SPA can discover the WS endpoint.
-
-```ts
-import { viteDevBridge } from 'devframe/helpers/vite'
-import { defineConfig } from 'vite'
-import devframe from './devframe'
-
-export default defineConfig({
- plugins: [viteDevBridge(devframe)],
-})
-```
-
-| Option | Default | Description |
-|--------|---------|-------------|
-| `base` | `def.basePath ?? '/__/'` | Mount path inside the Vite dev server. |
-| `devMiddleware` | `false` | `true` or `{ port?, host?, flags? }` to enable bridge mode. |
diff --git a/docs/guide/devframe-definition.md b/docs/guide/devframe-definition.md
index 5459504..27cddc8 100644
--- a/docs/guide/devframe-definition.md
+++ b/docs/guide/devframe-definition.md
@@ -4,7 +4,7 @@ outline: deep
# Devframe Definition
-Every Devframe tool starts with a single `defineDevframe` call. The returned `DevframeDefinition` is a portable value that any of the [adapters](./adapters) can consume — the same definition runs under `createCli`, `createBuild`, `createMcpServer`, the `vite` adapter's `createPluginFromDevframe`, and so on.
+Every Devframe tool starts with a single `defineDevframe` call. The returned `DevframeDefinition` is a portable value that any of the [adapters](/adapters/) can consume — the same definition runs under `createCli`, `createBuild`, `createMcpServer`, the `vite` adapter's `createPluginFromDevframe`, and so on.
## Minimal definition
@@ -28,7 +28,7 @@ export default defineDevframe({
})
```
-Host adapters (such as the [`vite` adapter](./adapters#vite) for Vite DevTools) derive their mount entry from `id`, `name`, `icon`, and `basePath` automatically.
+Host adapters (such as the [`vite` adapter](/adapters/vite) for Vite DevTools) derive their mount entry from `id`, `name`, `icon`, and `basePath` automatically.
## Definition fields
@@ -84,7 +84,7 @@ interface DevToolsNodeContext {
}
```
-Host adapters can augment `ctx` with additional surfaces. For example, the [`vite` adapter](./adapters#vite) exposes Vite DevTools' dock, command, message, and terminal hosts via an optional `setup` hook on `createPluginFromDevframe` — consult the host's docs for those extras.
+Host adapters can augment `ctx` with additional surfaces. For example, the [`vite` adapter](/adapters/vite) 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](./rpc) — `ctx.rpc`
@@ -165,7 +165,7 @@ defineDevframe({
})
```
-See [Adapters](./adapters) for how each adapter consumes these.
+See [Adapters](/adapters/) for how each adapter consumes these.
## Multiple runtimes, one definition
@@ -190,6 +190,6 @@ export const myPlugin = () => createPluginFromDevframe(devframe)
## What's next
-- [Adapters](./adapters) — pick a deployment target
+- [Adapters](/adapters/) — pick a deployment target
- [RPC](./rpc) — register server functions
-- [`vite` adapter](./adapters#vite) — mount your devframe into Vite DevTools or another compatible host
+- [`vite` adapter](/adapters/vite) — mount your devframe into Vite DevTools or another compatible host
diff --git a/docs/guide/index.md b/docs/guide/index.md
index d7464d9..791522e 100644
--- a/docs/guide/index.md
+++ b/docs/guide/index.md
@@ -6,7 +6,7 @@ outline: deep
**Devframe is an asset: define your devtool once, serve it anywhere.** You describe a single tool — its RPC surface, its data model, its SPA, its CLI shape — and the same definition deploys through any of the runtime adapters: a standalone CLI, a self-contained static report, an embedded SPA, an MCP server, and more. Devframe is framework- and build-tool-agnostic — it has no Vite dependency and no opinion on what UI framework your SPA uses.
-[Vite DevTools](https://devtools.vite.dev/) is built on top of devframe. If you need an integrated multi-tool host (docks, command palette, terminals, cross-tool toasts), mount your devframe into Vite DevTools via the [`vite` adapter](./adapters#vite) — or build your own host adapter targeting any environment you like.
+[Vite DevTools](https://devtools.vite.dev/) is built on top of devframe. If you need an integrated multi-tool host (docks, command palette, terminals, cross-tool toasts), mount your devframe into Vite DevTools via the [`vite` adapter](/adapters/vite) — or build your own host adapter targeting any environment you like.
> [!WARNING] Experimental
> The Devframe API is still in development and may change between versions. The agent-native surface (`agent` on `defineRpcFunction`, `ctx.agent`, and the MCP adapter) is additionally flagged as experimental.
@@ -32,40 +32,10 @@ Devframe keeps its surface focused on one tool, so the same definition stays por
| **[Diagnostics](./diagnostics)** | Coded warnings/errors via `logs-sdk` — registered into the host logger so adapters and consumers share the same surface. |
| **[Streaming](./streaming)** | One-way (RPC streaming) and two-way (uploads) channel primitives for long-running data. |
| **[When Clauses](./when-clauses)** | VS Code-style conditional expressions for docks, commands, and custom UI. |
-| **[Utilities](./utilities)** | Bundled helpers under `devframe/utils/*` — terminal colors, hashing, editor launch, structured-clone serialization, and more. |
+| **[Utilities](/helpers/utilities)** | Bundled helpers under `devframe/utils/*` — terminal colors, hashing, editor launch, structured-clone serialization, and more. |
| **[Client](./client)** | Browser-side RPC client (`connectDevframe`) with auto-auth and WebSocket / static modes. |
| **[Agent-Native](./agent-native)** | Opt-in exposure of your tool's surface to coding agents over MCP. |
-## Architecture
-
-```mermaid
-flowchart TB
- Definition["DevframeDefinition
(defineDevframe)"]
- Definition --> Adapters
-
- subgraph Adapters["Adapters (choose one per deployment)"]
- CLI["cli"]
- Vite["vite"]
- Build["build"]
- Embedded["embedded"]
- MCP["mcp"]
- end
-
- Adapters --> Ctx["DevToolsNodeContext"]
-
- subgraph Ctx["DevToolsNodeContext"]
- direction TB
- RPC["rpc"]
- Views["views (hostStatic)"]
- Diagnostics["diagnostics"]
- Agent["agent"]
- end
-
- Ctx <-->|WebSocket or static| Client["DevToolsRpcClient
(browser)"]
-```
-
-Hosts (Vite DevTools is one) can wrap the same definition with their own adapter to augment `ctx` with extras like docks, terminals, and a command palette.
-
## Install
```sh
@@ -102,7 +72,7 @@ const devframe = defineDevframe({
await createCli(devframe).parse()
```
-The same definition can also be deployed through any of the other adapters — for example, mounted into Vite DevTools via the [`vite` adapter](./adapters#vite).
+The same definition can also be deployed through any of the other adapters — for example, mounted into Vite DevTools via the [`vite` adapter](/adapters/vite).
Run it:
@@ -126,15 +96,29 @@ Devframe deploys the same `DevframeDefinition` through one of these adapters:
| `embedded` | `createEmbedded(d, { ctx })` | Runtime registration into an existing host |
| `mcp` | `createMcpServer(d, opts)` | Model Context Protocol server |
-See [Adapters](./adapters) for the full reference.
+See [Adapters](/adapters/) for the full reference.
## Framework- and build-tool-agnostic
-Devframe has zero dependencies on Vite or any `@vitejs/*` package — the same definition runs in any Node environment, with any UI framework, against any build tool. Vite DevTools is one host built on top of devframe; mount your definition there with the [`vite` adapter](./adapters#vite), or write adapters for any other host.
+Devframe has zero dependencies on Vite or any `@vitejs/*` package — the same definition runs in any Node environment, with any UI framework, against any build tool. Vite DevTools is one host built on top of devframe; mount your definition there with the [`vite` adapter](/adapters/vite), or write adapters for any other host.
## What's next
- [Devframe Definition](./devframe-definition) — understand `defineDevframe` and the `DevToolsNodeContext`
-- [Adapters](./adapters) — pick the right deployment target for your tool
+- [Adapters](/adapters/) — pick the right deployment target for your tool
- [RPC](./rpc) — define type-safe server functions your client can call
- [Agent-Native](./agent-native) — expose your devframe to Claude Desktop, Cursor, or any MCP client
+
+## Built with Devframe
+
+Real-world devtools shipping on Devframe:
+
+- [**Vite DevTools**](https://devtools.vite.dev/) — the host that bundles multiple devframes into one UI (docks, command palette, terminals). Mount your own definition into it via the [`vite` adapter](/adapters/vite).
+- [**ESLint Config Inspector**](https://github.com/eslint/config-inspector) — official ESLint tool for inspecting flat configs.
+- [**node-modules-inspector**](https://github.com/antfu/node-modules-inspector) — interactive visualizer for your `node_modules` dependency graph.
+
+End-to-end examples in this repo, exercising the full adapter surface:
+
+- [**devframe-counter**](https://github.com/devframes/devframe/tree/main/examples/devframe-counter) — smallest possible demo, exercises all adapters.
+- [**devframe-files-inspector**](https://github.com/devframes/devframe/tree/main/examples/devframe-files-inspector) — lists files in cwd via RPC; exercises CLI dev/build/spa surfaces.
+- [**devframe-streaming-chat**](https://github.com/devframes/devframe/tree/main/examples/devframe-streaming-chat) — streams synthetic chat tokens from server to client via `ctx.rpc.streaming`.
diff --git a/docs/guide/standalone-cli.md b/docs/guide/standalone-cli.md
index e808efd..08db130 100644
--- a/docs/guide/standalone-cli.md
+++ b/docs/guide/standalone-cli.md
@@ -98,7 +98,7 @@ export default defineNuxtConfig({
})
```
-Build with `nuxt build` and point `cli.distDir` at `./dist/public`. The SPA discovers its effective base at runtime — no `--base` rewrite needed. See the [Nuxt helper docs](./nuxt) for the full reference.
+Build with `nuxt build` and point `cli.distDir` at `./dist/public`. The SPA discovers its effective base at runtime — no `--base` rewrite needed. See the [Nuxt helper docs](/helpers/nuxt) for the full reference.
## Connecting from the client
@@ -157,21 +157,7 @@ The adapter derives each flag's CAC option from its schema — booleans become `
## Open helpers
-For the two actions every CLI devtool needs — open a file in the editor, reveal a path in the OS file explorer — use the prebuilt recipes instead of re-implementing them:
-
-```ts
-import { openHelpers } from 'devframe/recipes/open-helpers'
-
-defineDevframe({
- id: 'my-tool',
- name: 'My Tool',
- setup(ctx) {
- openHelpers.forEach(fn => ctx.rpc.register(fn))
- },
-})
-```
-
-This registers `devframe:open-in-editor` and `devframe:open-in-finder`. Both helpers reuse the bundled [`launchEditor`](./utilities#devframe-utils-launch-editor) and [`open`](./utilities#devframe-utils-open) utilities, so there's nothing extra to install.
+For the two actions every CLI devtool needs — open a file in the editor, reveal a path in the OS file explorer — use the prebuilt recipes from `devframe/recipes/open-helpers` instead of re-implementing them. See [Helpers → Open Helpers](/helpers/open-helpers) for the full reference.
## Snapshot queries for static builds
@@ -320,7 +306,7 @@ For typed flag schemas, `parseCliFlags(schema, rawBag)` (from `devframe/adapters
## See also
- [Devframe Definition](./devframe-definition) — field reference
-- [Adapters → CLI](./adapters#cli) — full CLI adapter reference including `configureCli` and mount-path rules
-- [Adapters → Dev](./adapters#dev) — `createDevServer` reference for bring-your-own-CLI integration
+- [Adapters → CLI](/adapters/cli) — full CLI adapter reference including `configureCli` and mount-path rules
+- [Adapters → Dev](/adapters/dev) — `createDevServer` reference for bring-your-own-CLI integration
- [Client](./client) — `connectDevframe`, shared state, caching
- [Agent-Native](./agent-native) — exposing your tool to Claude Desktop, Cursor, etc.
diff --git a/docs/helpers/index.md b/docs/helpers/index.md
new file mode 100644
index 0000000..819ad21
--- /dev/null
+++ b/docs/helpers/index.md
@@ -0,0 +1,16 @@
+---
+outline: deep
+---
+
+# Helpers
+
+Helpers are the optional, opt-in surface around the core `defineDevframe` API: small wrappers for runtime integration, prebuilt RPC recipes, and a curated set of low-level utilities. None of them are required to ship a devframe — reach for them when they match the shape of what you're building.
+
+| Helper | Entry | What it does |
+|--------|-------|--------------|
+| [Utilities](./utilities) | `devframe/utils/*` | Bundled small utilities — terminal colors, hashing, editor launch, structured-clone serialization, and more. |
+| [Vite Bridge](./vite-bridge) | `devframe/helpers/vite` | Vite plugin for mounting a devframe inside any Vite-based host (Astro, SolidStart, plain Vite). |
+| [Nuxt Module](./nuxt) | `@devframes/nuxt` | Nuxt module that wires a Nuxt SPA as a devframe client and serves the dev-time RPC bridge. |
+| [Open Helpers](./open-helpers) | `devframe/recipes/open-helpers` | Prebuilt RPC actions for "open in editor" and "reveal in Finder". |
+
+Helpers vs. [adapters](/adapters/): an adapter takes a `DevframeDefinition` and deploys it as a runnable surface (CLI, dev server, static build, MCP server). A helper is a smaller piece — a Vite plugin, a Nuxt module, a recipe, a utility function — that you compose alongside an adapter.
diff --git a/docs/guide/nuxt.md b/docs/helpers/nuxt.md
similarity index 94%
rename from docs/guide/nuxt.md
rename to docs/helpers/nuxt.md
index 7c2cd12..fb484d8 100644
--- a/docs/guide/nuxt.md
+++ b/docs/helpers/nuxt.md
@@ -9,7 +9,7 @@ The `@devframes/nuxt` module wires a Nuxt-built SPA as a devframe client, and op
It handles the four things every Nuxt-powered standalone devtool needs:
1. **Base-agnostic assets.** Sets `app.baseURL: './'` and `vite.base: './'` so the same production build works at `/`, `/tool/`, and any other deployment path without build-time URL rewriting.
-2. **Runtime RPC connection.** Adds a client plugin that calls [`connectDevframe()`](./client) once on page load and provides the result as `$rpc` on the Nuxt app.
+2. **Runtime RPC connection.** Adds a client plugin that calls [`connectDevframe()`](/guide/client) once on page load and provides the result as `$rpc` on the Nuxt app.
3. **Dev-time RPC bridge.** When you pass `devframe`, `nuxt dev` spins up a separate WebSocket RPC server and serves `__connection.json` so the SPA can reach it — no hand-rolled Vite plugin required.
4. **TypeScript augmentation.** `useNuxtApp().$rpc` is typed as `DevToolsRpcClient` out of the box.
@@ -130,6 +130,6 @@ At runtime the built SPA fetches `./__connection.json` (resolved against `docume
## See also
-- [Standalone CLI recipe](./standalone-cli) — end-to-end walk-through
-- [Client](./client) — `connectDevframe` reference
-- [Adapters](./adapters) — CLI / Vite / Build / SPA / Kit / Embedded / MCP
+- [Standalone CLI recipe](/guide/standalone-cli) — end-to-end walk-through
+- [Client](/guide/client) — `connectDevframe` reference
+- [Adapters](/adapters/) — CLI / Vite / Build / Embedded / MCP
diff --git a/docs/helpers/open-helpers.md b/docs/helpers/open-helpers.md
new file mode 100644
index 0000000..9e7b60b
--- /dev/null
+++ b/docs/helpers/open-helpers.md
@@ -0,0 +1,56 @@
+---
+outline: deep
+---
+
+# Open Helpers
+
+Prebuilt RPC actions for the two file-system actions every CLI devtool needs — opening a file in the editor, revealing a path in the OS file explorer. Use the recipe instead of re-implementing them so every devframe converges on the same registered names and payload shape.
+
+```ts
+import { openHelpers } from 'devframe/recipes/open-helpers'
+
+defineDevframe({
+ id: 'my-tool',
+ name: 'My Tool',
+ setup(ctx) {
+ openHelpers.forEach(fn => ctx.rpc.register(fn))
+ },
+})
+```
+
+## Exports
+
+| Export | Registered name | Type | Args | Purpose |
+|--------|------------------|------|------|---------|
+| `openInEditor` | `devframe:open-in-editor` | `action` | `[filename: string]` | Open the file in the user's editor via [`launchEditor`](./utilities#devframe-utils-launch-editor). Accepts `file`, `file:line`, or `file:line:column`. |
+| `openInFinder` | `devframe:open-in-finder` | `action` | `[path: string]` | Reveal the path in the OS file explorer via [`open`](./utilities#devframe-utils-open). |
+| `openHelpers` | — | `readonly [openInEditor, openInFinder]` | — | Convenience array for batch registration. |
+
+Both functions are `action`-type RPCs returning `void` and use `valibot` schemas (`v.string()`) for their single argument.
+
+## Pick and choose
+
+Register only the helper you need rather than the whole array:
+
+```ts
+import { openInEditor } from 'devframe/recipes/open-helpers'
+
+defineDevframe({
+ id: 'my-tool',
+ setup(ctx) {
+ ctx.rpc.register(openInEditor)
+ },
+})
+```
+
+## On the client
+
+The SPA calls these like any other RPC:
+
+```ts
+const rpc = await connectDevframe()
+await rpc.call('devframe:open-in-editor', 'src/main.ts:42:7')
+await rpc.call('devframe:open-in-finder', '/abs/path/to/dir')
+```
+
+`launchEditor`'s editor auto-detection reads the `LAUNCH_EDITOR` environment variable on the server side — there is no client-side configuration.
diff --git a/docs/guide/utilities.md b/docs/helpers/utilities.md
similarity index 96%
rename from docs/guide/utilities.md
rename to docs/helpers/utilities.md
index 69d177d..0d27ab9 100644
--- a/docs/guide/utilities.md
+++ b/docs/helpers/utilities.md
@@ -46,7 +46,7 @@ launchEditor('src/main.ts:42:7')
launchEditor('src/main.ts:42:7', 'code')
```
-The auto-detection reads the `LAUNCH_EDITOR` environment variable and falls back to common defaults. Most devframes consume this through the prebuilt `openInEditor` recipe — see [Open helpers](./standalone-cli#open-helpers).
+The auto-detection reads the `LAUNCH_EDITOR` environment variable and falls back to common defaults. Most devframes consume this through the prebuilt `openInEditor` recipe — see [Open helpers](./open-helpers).
### `devframe/utils/hash`
@@ -120,7 +120,7 @@ off()
### `devframe/utils/shared-state`
-Underlying immutable state container used by `ctx.rpc.sharedState`. Most devframes interact with it indirectly — see [Shared State](./shared-state). Available directly when you need a state hub outside the RPC host.
+Underlying immutable state container used by `ctx.rpc.sharedState`. Most devframes interact with it indirectly — see [Shared State](/guide/shared-state). Available directly when you need a state hub outside the RPC host.
```ts
import { createSharedState } from 'devframe/utils/shared-state'
@@ -134,11 +134,11 @@ state.value() // { count: 1 }
### `devframe/utils/streaming-channel`
-Low-level sink/reader primitives for streamed RPC payloads. Most devframes consume these through `ctx.rpc.streaming` — see [Streaming](./streaming).
+Low-level sink/reader primitives for streamed RPC payloads. Most devframes consume these through `ctx.rpc.streaming` — see [Streaming](/guide/streaming).
### `devframe/utils/when`
-Statically-validated when-clause expressions for conditional UI visibility. The runtime + types ship from here; the consumer fields (`when` on docks and commands) are kit-side. See [When Clauses](./when-clauses).
+Statically-validated when-clause expressions for conditional UI visibility. The runtime + types ship from here; the consumer fields (`when` on docks and commands) are kit-side. See [When Clauses](/guide/when-clauses).
## Why a `utils/*` subpath
diff --git a/docs/helpers/vite-bridge.md b/docs/helpers/vite-bridge.md
new file mode 100644
index 0000000..3a8bfb2
--- /dev/null
+++ b/docs/helpers/vite-bridge.md
@@ -0,0 +1,33 @@
+---
+outline: deep
+---
+
+# Vite Bridge
+
+A thin Vite plugin for mounting a devframe inside an existing Vite dev server. Used by [`@devframes/nuxt`](./nuxt) and available for any Vite-based host (Astro, SolidStart, plain Vite apps).
+
+This sits below the [`vite` adapter](/adapters/vite) on the abstraction ladder: the adapter targets the full Vite DevTools dock; the bridge is the lower-level Vite plugin you reach for when you want a devframe to ride along with an existing app's dev server without the DevTools dock.
+
+```ts
+import { viteDevBridge } from 'devframe/helpers/vite'
+import { defineConfig } from 'vite'
+import devframe from './devframe'
+
+export default defineConfig({
+ plugins: [viteDevBridge(devframe)],
+})
+```
+
+## Modes
+
+- **Static mount** (default) — mounts `def.cli.distDir` at `options.base` (`/__/` by default). No RPC server. Useful when you only need the SPA bundle served from a known path.
+- **Bridge mode** (`devMiddleware: true | {…}`) — skips the static mount; the host app owns the SPA. Devframe spawns a separate RPC + WS server and registers Vite middleware at `__connection.json` so the host-served SPA can discover the WS endpoint.
+
+## Options
+
+| Option | Default | Description |
+|--------|---------|-------------|
+| `base` | `def.basePath ?? '/__/'` | Mount path inside the Vite dev server. |
+| `devMiddleware` | `false` | `true` or `{ port?, host?, flags? }` to enable bridge mode. |
+
+When `devMiddleware` is an object, the inner fields mirror [`createDevServer`](/adapters/dev) — `port` pins the WS server port, `host` sets the bind host, and `flags` is forwarded to `def.setup(ctx, { flags })`.
diff --git a/packages/devframe/README.md b/packages/devframe/README.md
index 067293b..6665550 100644
--- a/packages/devframe/README.md
+++ b/packages/devframe/README.md
@@ -1,84 +1,28 @@
# devframe
-Framework- and build-tool-agnostic foundation for building generic DevTools. Define your devtool once — its RPC, its data, its SPA, its CLI shape — and deploy the same definition anywhere through a set of pluggable adapters.
+[![npm version][npm-version-src]][npm-version-href]
+[![npm downloads][npm-downloads-src]][npm-downloads-href]
+[![bundle][bundle-src]][bundle-href]
+[![JSDocs][jsdocs-src]][jsdocs-href]
+[![License][license-src]][license-href]
-Full documentation: [https://devfra.me/](https://devfra.me/).
+Framework-neutral foundation for building generic DevTools.
-## Install
+Documentation: [https://devfra.me/](https://devfra.me/).
-```sh
-pnpm add devframe
-```
-
-## Hello, Devframe
-
-```ts
-import { defineDevframe, defineRpcFunction } from 'devframe'
-import { createCli } from 'devframe/adapters/cli'
-
-const devframe = defineDevframe({
- id: 'my-devframe',
- name: 'My Devframe',
- setup(ctx) {
- ctx.rpc.register(defineRpcFunction({
- name: 'my-devframe:hello',
- type: 'static',
- jsonSerializable: true,
- handler: () => ({ message: 'hello' }),
- }))
- },
-})
-
-await createCli(devframe).parse()
-```
-
-## Adapters
-
-| Adapter | Use case |
-|---------|----------|
-| `cli` | Standalone CLI tool with `dev` / `build` / `mcp` subcommands. |
-| `build` | Generates a static, self-contained SPA snapshot. |
-| `vite` | Mounts the devframe into Vite DevTools (or any compatible host) via `@vitejs/devtools-kit`. |
-| `embedded` | Overlays inside another devtool's UI. |
-| `mcp` | Surfaces the devframe's RPC to coding agents over MCP. |
-
-## Agent-Native (experimental)
-
-> [!WARNING]
-> The agent-native surface — the `agent` field on `defineRpcFunction`, `DevToolsAgentHost`, and the `devframe/adapters/mcp` adapter — may change without a major version bump until it stabilizes.
-
-Devframe surfaces a devframe's RPC functions, tools, and resources to coding agents over [MCP](https://modelcontextprotocol.io). Flag an RPC function with `agent: { description }` to expose it, then spin up an MCP server:
-
-```ts
-import { defineDevframe, defineRpcFunction } from 'devframe'
-import { createMcpServer } from 'devframe/adapters/mcp'
-
-const getSummary = defineRpcFunction({
- name: 'my-plugin:get-summary',
- type: 'query',
- agent: {
- description: 'Return a short summary of the current build state.',
- },
- setup: ctx => ({ handler: async () => buildSummary() }),
-})
-
-const devframe = defineDevframe({
- id: 'my-plugin',
- setup(ctx) {
- ctx.rpc.register(getSummary)
- ctx.agent.registerResource({
- id: 'latest-build',
- name: 'Latest build',
- read: () => ({ text: renderMarkdown(latestBuild) }),
- })
- },
-})
-
-await createMcpServer(devframe, { transport: 'stdio' })
-```
+## License
-Or via the CLI: `devframe mcp`. `@modelcontextprotocol/sdk` is a peer dependency — add it when you want MCP support. See the [Agent-Native guide](https://devfra.me/guide/agent-native) for the full API and Claude Desktop integration example.
+[MIT](../../LICENSE.md) License © [Anthony Fu](https://github.com/antfu)
-## License
+
-[MIT](./LICENSE.md)
+[npm-version-src]: https://img.shields.io/npm/v/devframe?style=flat&colorA=080f12&colorB=517158
+[npm-version-href]: https://npmx.dev/package/devframe
+[npm-downloads-src]: https://img.shields.io/npm/dm/devframe?style=flat&colorA=080f12&colorB=517158
+[npm-downloads-href]: https://npmx.dev/package/devframe
+[bundle-src]: https://img.shields.io/bundlephobia/minzip/devframe?style=flat&colorA=080f12&colorB=517158&label=minzip
+[bundle-href]: https://bundlephobia.com/result?p=devframe
+[license-src]: https://img.shields.io/github/license/devframes/devframe.svg?style=flat&colorA=080f12&colorB=517158
+[license-href]: https://github.com/devframes/devframe/blob/main/LICENSE.md
+[jsdocs-src]: https://img.shields.io/badge/jsdocs-reference-080f12?style=flat&colorA=080f12&colorB=517158
+[jsdocs-href]: https://www.jsdocs.io/package/devframe