Skip to content

Commit 071d23e

Browse files
authored
feat(devframe): framework-neutral devtools foundation + agent-native MCP (#297)
1 parent 3e6eb98 commit 071d23e

370 files changed

Lines changed: 11948 additions & 2229 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

AGENTS.md

Lines changed: 26 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,9 @@ Monorepo (`pnpm` workspaces + `turbo`). ESM TypeScript; bundled with `tsdown`. P
88

99
| Package | npm | Description |
1010
|---------|-----|-------------|
11-
| `packages/core` | `@vitejs/devtools` | Vite plugin, CLI, runtime hosts (docks, views, terminals), WS RPC server, standalone/webcomponents client |
12-
| `packages/kit` | `@vitejs/devtools-kit` | Public types/utilities for integration authors (`defineRpcFunction`, shared state, events, client helpers) |
13-
| `packages/rpc` | `@vitejs/devtools-rpc` | Typed RPC wrapper over `birpc` with WS presets |
11+
| `packages/devframe` | `devframe` | Framework-neutral foundation — RPC layer (birpc + valibot + WS presets), host classes, createHostContext, six adapters at `devframe/adapters/*` (cli/build/spa/vite/kit/embedded), connectDevtool client |
12+
| `packages/core` | `@vitejs/devtools` | Vite plugin, CLI, standalone/webcomponents client. Wraps devframe's createHostContext with the Vite plugin scan |
13+
| `packages/kit` | `@vitejs/devtools-kit` | Vite-specific superset of devframe — adds PluginWithDevTools, ViteDevToolsNodeContext, and re-exports devframe's public types |
1414
| `packages/ui` | `@vitejs/devtools-ui` | Shared UI components, composables, and UnoCSS preset (`presetDevToolsUI`). Private, not published |
1515
| `packages/rolldown` | `@vitejs/devtools-rolldown` | Nuxt UI for Rolldown build data. Serves at `/.devtools-rolldown/` |
1616
| `packages/vite` | `@vitejs/devtools-vite` | Nuxt UI for Vite DevTools (WIP). Serves at `/.devtools-vite/` |
@@ -23,20 +23,25 @@ Other top-level directories:
2323

2424
```mermaid
2525
flowchart TD
26-
core["core"] --> kit & rpc
26+
kit --> devframe
27+
core --> kit
2728
core --> rolldown & vite & self-inspect
28-
rolldown --> kit & rpc & ui
29-
vite --> kit & rpc & ui
30-
self-inspect --> kit & rpc
29+
rolldown --> kit & ui
30+
vite --> kit & ui
31+
self-inspect --> kit
3132
webext --> core
3233
```
3334

35+
## Dep Boundary
36+
37+
`packages/devframe` is the lowest-level package in this monorepo and is positioned to be extracted into its own repo. It MUST NOT import from `vite` or any `@vitejs/*` package — not as a `dependencies` entry, not as an inlined dep, not as a source import. `packages/kit` and above build on top of devframe, never the reverse.
38+
3439
## Architecture
3540

3641
- **Entry**: `createDevToolsContext` (`packages/core/src/node/context.ts`) builds `DevToolsNodeContext` with hosts for RPC, docks, views, terminals. Invokes `plugin.devtools.setup` hooks.
3742
- **Node context**: server-side (cwd, vite config, mode, hosts, auth storage at `node_modules/.vite/devtools/auth.json`).
3843
- **Client context**: webcomponents/Nuxt UI state (`packages/core/src/client/webcomponents/state/*`) — dock entries, panels, RPC client. Two modes: `embedded` (overlay in host app) and `standalone` (independent page).
39-
- **WS server** (`packages/core/src/node/ws.ts`): RPC via `@vitejs/devtools-rpc/presets/ws`. Auth skipped in build mode or when `devtools.clientAuth` is `false`.
44+
- **WS server** (`packages/core/src/node/ws.ts`): RPC via `devframe/rpc/transports/ws-server`. Auth skipped in build mode or when `devtools.clientAuth` is `false`.
4045
- **Nuxt UI plugins** (rolldown, vite, self-inspect): each registers RPC functions and hosts static Nuxt SPA at its own base path.
4146

4247
## Development
@@ -57,11 +62,21 @@ pnpm -C docs run docs # docs dev server
5762

5863
- Use workspace aliases from `alias.ts`.
5964
- RPC functions must use `defineRpcFunction` from kit; always namespace IDs (`my-plugin:fn-name`).
60-
- Shared state via `@vitejs/devtools-kit/utils/shared-state`; keep values serializable.
65+
- Shared state via `devframe/utils/shared-state`; keep values serializable.
6166
- Nuxt UI base paths: `/.devtools-rolldown/`, `/.devtools-vite/`, `/.devtools-self-inspect/`.
6267
- Shared UI components/preset in `packages/ui`; use `presetDevToolsUI` from `@vitejs/devtools-ui/unocss`.
6368
- Currently focused on Rolldown build-mode analysis; dev-mode support is deferred.
6469

70+
### Devframe design principles
71+
72+
These apply to everything inside `packages/devframe` and to how host packages (`kit`, `core`, etc.) layer on top. When in doubt, err on the side of "devframe provides hooks, the app decides UX".
73+
74+
- **Headless by default.** No default startup banners, no opinionated logging to stdout, no default styling. Provide hooks (`onReady`, `cli.configure`, etc.); let the application print its own branding. Structured diagnostics via `logs-sdk` are fine — ad-hoc `console.log`s baked into adapters are not.
75+
- **File watching is the app's job, not devframe's.** Don't add a generic watcher primitive. Authors wire chokidar / fs.watch / watchman themselves and signal change via `ctx.rpc.sharedState.set(...)` or event-type RPCs. devframe stays out of the filesystem-observation business.
76+
- **Mount path depends on adapter context.** Given `id: 'foo'`, the default mount path is `/.foo/` for *hosted* adapters (`vite`, `kit`, `embedded`) and `/` for *standalone* adapters (`cli`, `spa`, `build`). Authors override via `DevtoolDefinition.basePath`. Don't hardcode `DEVTOOLS_MOUNT_PATH` in adapter code paths that may run standalone.
77+
- **SPAs own their basePath at runtime.** Build SPAs with relative asset paths (`vite.base: './'`); discover the effective base in the browser from the executing script's location / `document.baseURI`. `createBuild` / `createSpa` copy SPA output verbatim — no HTML rewriting, no build-time `--base` injection. The client (`connectDevtool`) resolves `.connection.json` relative to the runtime base automatically.
78+
- **CLI flags compose from both sides.** The `cac` instance backing `createCli` is exposed both to the `DevtoolDefinition` (`cli.configure(cli)`) — for capabilities contributed by the tool itself — and to the `createCli` caller — for flags added at the final assembly stage. Parsed flag values are forwarded to `setup(ctx, { flags })`. Never hardcode domain-specific flags into `createCli`.
79+
6580
## Structured Diagnostics (Error Codes)
6681

6782
All node-side warnings and errors use structured diagnostics via [`logs-sdk`](https://github.com/vercel-labs/logs-sdk). Never use raw `console.warn`, `console.error`, or `throw new Error` with ad-hoc messages in node-side code — always define a coded diagnostic.
@@ -70,7 +85,8 @@ All node-side warnings and errors use structured diagnostics via [`logs-sdk`](ht
7085

7186
| Prefix | Package(s) | Diagnostics file |
7287
|--------|-----------|-----------------|
73-
| `DTK` | `packages/rpc`, `packages/core` | `packages/rpc/src/diagnostics.ts`, `packages/core/src/node/diagnostics.ts` |
88+
| `DF` | `packages/devframe` | `packages/devframe/src/node/diagnostics.ts`, `packages/devframe/src/rpc/diagnostics.ts` |
89+
| `DTK` | `packages/core` (Vite-specific remainder) | `packages/core/src/node/diagnostics.ts` |
7490
| `RDDT` | `packages/rolldown` | `packages/rolldown/src/node/diagnostics.ts` |
7591
| `VDT` | `packages/vite` (reserved) ||
7692

alias.ts

Lines changed: 28 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,36 @@ import { join, relative } from 'pathe'
44

55
const root = fileURLToPath(new URL('.', import.meta.url))
66
const r = (path: string) => fileURLToPath(new URL(`./packages/${path}`, import.meta.url))
7+
const df = (path: string) => fileURLToPath(new URL(`./devframe/packages/${path}`, import.meta.url))
78

89
export const alias = {
9-
'@vitejs/devtools-rpc/presets/ws/server': r('rpc/src/presets/ws/server.ts'),
10-
'@vitejs/devtools-rpc/presets/ws/client': r('rpc/src/presets/ws/client.ts'),
11-
'@vitejs/devtools-rpc/presets': r('rpc/src/presets/index.ts'),
12-
'@vitejs/devtools-rpc/client': r('rpc/src/client.ts'),
13-
'@vitejs/devtools-rpc/server': r('rpc/src/server.ts'),
14-
'@vitejs/devtools-rpc': r('rpc/src'),
10+
'devframe/rpc/transports/ws-server': df('devframe/src/rpc/transports/ws-server.ts'),
11+
'devframe/rpc/transports/ws-client': df('devframe/src/rpc/transports/ws-client.ts'),
12+
'devframe/rpc/client': df('devframe/src/rpc/client.ts'),
13+
'devframe/rpc/server': df('devframe/src/rpc/server.ts'),
14+
'devframe/rpc': df('devframe/src/rpc'),
15+
'devframe/types': df('devframe/src/types/index.ts'),
16+
'devframe/node': df('devframe/src/node/index.ts'),
17+
'devframe/constants': df('devframe/src/constants.ts'),
18+
'devframe/utils/events': df('devframe/src/utils/events.ts'),
19+
'devframe/utils/human-id': df('devframe/src/utils/human-id.ts'),
20+
'devframe/utils/nanoid': df('devframe/src/utils/nanoid.ts'),
21+
'devframe/utils/promise': df('devframe/src/utils/promise.ts'),
22+
'devframe/utils/shared-state': df('devframe/src/utils/shared-state.ts'),
23+
'devframe/utils/state': df('devframe/src/utils/state.ts'),
24+
'devframe/utils/when': df('devframe/src/utils/when.ts'),
25+
'devframe/adapters/cli': df('devframe/src/adapters/cli.ts'),
26+
'devframe/adapters/build': df('devframe/src/adapters/build.ts'),
27+
'devframe/adapters/vite': df('devframe/src/adapters/vite.ts'),
28+
'devframe/adapters/kit': df('devframe/src/adapters/kit.ts'),
29+
'devframe/adapters/embedded': df('devframe/src/adapters/embedded.ts'),
30+
'devframe/adapters/mcp': df('devframe/src/adapters/mcp.ts'),
31+
'devframe/helpers/nuxt/runtime/plugin.client': df('devframe/src/helpers/nuxt/runtime/plugin.client.ts'),
32+
'devframe/helpers/nuxt': df('devframe/src/helpers/nuxt/index.ts'),
33+
'devframe/recipes/open-helpers': df('devframe/src/recipes/open-helpers.ts'),
34+
'devframe/client': df('devframe/src/client/index.ts'),
35+
'devframe': df('devframe/src'),
36+
'@vitejs/devtools-kit/node': r('kit/src/node/index.ts'),
1537
'@vitejs/devtools-kit/client': r('kit/src/client/index.ts'),
1638
'@vitejs/devtools-kit/constants': r('kit/src/constants.ts'),
1739
'@vitejs/devtools-kit/utils/events': r('kit/src/utils/events.ts'),

devframe/README.md

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
# DevFrame
2+
3+
Framework-neutral foundation for building generic DevTools — an RPC layer (birpc + valibot + WS presets), runtime hosts (RPC / docks / views / terminals / logs / commands / agent), and adapters that deploy a single devtool definition to seven targets: `cli`, `build`, `vite`, `kit`, `embedded`, `mcp`, plus the `spa` mode.
4+
5+
## Layout
6+
7+
This directory is staged for extraction into a standalone repository. Until then it lives inside the [`vitejs/devtools`](https://github.com/vitejs/devtools) monorepo.
8+
9+
| Path | Description |
10+
|------|-------------|
11+
| [`packages/devframe`](./packages/devframe) | The published [`devframe`](https://www.npmjs.com/package/devframe) npm package. |
12+
| [`docs`](./docs) | VitePress documentation site, deployed at https://devtools.vite.dev/devframe/. |
13+
| [`examples`](./examples) | End-to-end demos: [`devframe-counter`](./examples/devframe-counter) (smallest cross-adapter demo) and [`devframe-files-inspector`](./examples/devframe-files-inspector) (CLI dev/build/spa + Vite DevTools dock). |
14+
| [`tests`](./tests) | Public-API snapshot tests via [`tsnapi`](https://github.com/posva/tsnapi). |
15+
16+
## Install
17+
18+
```sh
19+
pnpm add devframe
20+
```
21+
22+
## Documentation
23+
24+
See [https://devtools.vite.dev/devframe/](https://devtools.vite.dev/devframe/) for the full guide and API reference.
25+
26+
## Adapters
27+
28+
| Adapter | Use case |
29+
|---------|----------|
30+
| `cli` | Standalone CLI tool with `dev` / `build` / `mcp` subcommands. |
31+
| `build` | Generates a static, self-contained SPA snapshot. |
32+
| `vite` | Runs as a Vite plugin alongside the host app's dev server. |
33+
| `kit` | Plugs into the DevTools Kit aggregator. |
34+
| `embedded` | Mounts as an overlay inside another devtool's UI. |
35+
| `mcp` | Exposes the devtool's RPC surface to coding agents over MCP. |
36+
37+
## License
38+
39+
[MIT](./packages/devframe/LICENSE.md)

devframe/docs/.vitepress/config.ts

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
import { defineConfig } from 'vitepress'
2+
import { withMermaid } from 'vitepress-plugin-mermaid'
3+
import devframeSidebar from './sidebar'
4+
5+
export default withMermaid(defineConfig({
6+
title: 'DevFrame',
7+
description: 'Framework-neutral foundation for building generic DevTools — RPC layer, hosts, and adapters.',
8+
themeConfig: {
9+
nav: [
10+
{ text: 'Guide', link: '/guide/' },
11+
{ text: 'Errors', link: '/errors/' },
12+
],
13+
sidebar: devframeSidebar(),
14+
search: {
15+
provider: 'local',
16+
},
17+
socialLinks: [
18+
{ icon: 'github', link: 'https://github.com/vitejs/devtools' },
19+
],
20+
editLink: {
21+
pattern: 'https://github.com/vitejs/devtools/edit/main/devframe/docs/:path',
22+
text: 'Suggest changes to this page',
23+
},
24+
footer: {
25+
message: 'Released under the MIT License.',
26+
copyright: 'Copyright © 2025-present VoidZero Inc. & Vite Contributors',
27+
},
28+
lastUpdated: {
29+
text: 'Last updated',
30+
},
31+
},
32+
mermaid: {
33+
theme: 'base',
34+
flowchart: {
35+
curve: 'basis',
36+
padding: 20,
37+
nodeSpacing: 50,
38+
rankSpacing: 60,
39+
useMaxWidth: true,
40+
},
41+
sequence: {
42+
actorMargin: 80,
43+
boxMargin: 10,
44+
boxTextMargin: 5,
45+
noteMargin: 10,
46+
messageMargin: 40,
47+
useMaxWidth: true,
48+
},
49+
},
50+
}))
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import type { DefaultTheme } from 'vitepress'
2+
3+
export default function devframeSidebar(prefix = ''): DefaultTheme.SidebarItem[] {
4+
return [
5+
{
6+
text: 'Guide',
7+
items: [
8+
{ text: 'Introduction', link: `${prefix}/guide/` },
9+
{ text: 'Devtool Definition', link: `${prefix}/guide/devtool-definition` },
10+
{ text: 'Adapters', link: `${prefix}/guide/adapters` },
11+
{ text: 'RPC', link: `${prefix}/guide/rpc` },
12+
{ text: 'Shared State', link: `${prefix}/guide/shared-state` },
13+
{ text: 'Dock System', link: `${prefix}/guide/dock-system` },
14+
{ text: 'Commands', link: `${prefix}/guide/commands` },
15+
{ text: 'When Clauses', link: `${prefix}/guide/when-clauses` },
16+
{ text: 'Logs & Notifications', link: `${prefix}/guide/logs` },
17+
{ text: 'Terminals', link: `${prefix}/guide/terminals` },
18+
{ text: 'Client', link: `${prefix}/guide/client` },
19+
{ text: 'Standalone CLI', link: `${prefix}/guide/standalone-cli` },
20+
{ text: 'Nuxt Helper', link: `${prefix}/guide/nuxt` },
21+
{ text: 'Agent-Native (experimental)', link: `${prefix}/guide/agent-native` },
22+
],
23+
},
24+
{
25+
text: 'Error Reference',
26+
link: `${prefix}/errors/`,
27+
collapsed: true,
28+
items: Array.from({ length: 17 }, (_, i) => {
29+
const code = `DF${String(i + 1).padStart(4, '0')}`
30+
return { text: code, link: `${prefix}/errors/${code}` }
31+
}),
32+
},
33+
]
34+
}

devframe/docs/errors/DF0001.md

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
---
2+
outline: deep
3+
---
4+
5+
# DF0001: Dock Already Registered
6+
7+
> Package: `devframe`
8+
9+
## Message
10+
11+
> Dock with id "`{id}`" is already registered
12+
13+
## Cause
14+
15+
`DevToolsDockHost.register()` rejects duplicate ids by default.
16+
17+
## Fix
18+
19+
Pass `force: true` as the second argument to intentionally overwrite, or choose a unique dock id.
20+
21+
## Source
22+
23+
`packages/devframe/src/node/host-docks.ts`
24+
25+
## Migrated from
26+
27+
Previously documented as `DTK0015` under `@vitejs/devtools`.

devframe/docs/errors/DF0002.md

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
---
2+
outline: deep
3+
---
4+
5+
# DF0002: Cannot Change Dock ID
6+
7+
> Package: `devframe`
8+
9+
## Message
10+
11+
> Cannot change the id of a dock. Use register() to add new docks.
12+
13+
## Cause
14+
15+
`DevToolsDockHost.update()` rejects id changes because docks are keyed by id.
16+
17+
## Fix
18+
19+
Keep the `id` on `update(patch)` identical to the registered value, or call `register()` with a new id (and optionally `force: true`).
20+
21+
## Source
22+
23+
`packages/devframe/src/node/host-docks.ts`
24+
25+
## Migrated from
26+
27+
Previously documented as `DTK0016` under `@vitejs/devtools`.

devframe/docs/errors/DF0003.md

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
---
2+
outline: deep
3+
---
4+
5+
# DF0003: Dock Not Registered
6+
7+
> Package: `devframe`
8+
9+
## Message
10+
11+
> Dock with id "`{id}`" is not registered. Use register() to add new docks.
12+
13+
## Cause
14+
15+
`DevToolsDockHost.update()` was called with an id that has no matching registration.
16+
17+
## Fix
18+
19+
Call `register()` first, or check the id against `dockHost.values()` before updating.
20+
21+
## Source
22+
23+
`packages/devframe/src/node/host-docks.ts`
24+
25+
## Migrated from
26+
27+
Previously documented as `DTK0017` under `@vitejs/devtools`.

devframe/docs/errors/DF0004.md

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
---
2+
outline: deep
3+
---
4+
5+
# DF0004: Terminal Session Already Registered
6+
7+
> Package: `devframe`
8+
9+
## Message
10+
11+
> Terminal session with id "`{id}`" already registered
12+
13+
## Cause
14+
15+
`DevToolsTerminalHost.register()` already has a session under that id.
16+
17+
## Fix
18+
19+
Use a unique session id or call `unregister(id)` first.
20+
21+
## Source
22+
23+
`packages/devframe/src/node/host-terminals.ts`
24+
25+
## Migrated from
26+
27+
Previously documented as `DTK0018` under `@vitejs/devtools`.

devframe/docs/errors/DF0005.md

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
---
2+
outline: deep
3+
---
4+
5+
# DF0005: Terminal Session Not Registered
6+
7+
> Package: `devframe`
8+
9+
## Message
10+
11+
> Terminal session with id "`{id}`" not registered
12+
13+
## Cause
14+
15+
A terminal operation (update, read, close) referenced an id that the host does not know about.
16+
17+
## Fix
18+
19+
Ensure the session was created via `register()` before calling update/read/close operations on it.
20+
21+
## Source
22+
23+
`packages/devframe/src/node/host-terminals.ts`
24+
25+
## Migrated from
26+
27+
Previously documented as `DTK0019` under `@vitejs/devtools`.

0 commit comments

Comments
 (0)