diff --git a/alias.ts b/alias.ts
index 9b95034f..e284552c 100644
--- a/alias.ts
+++ b/alias.ts
@@ -108,6 +108,12 @@ export const alias = {
'@devframes/plugin-messages/cli': p('messages/src/cli.ts'),
'@devframes/plugin-messages/vite': p('messages/src/vite.ts'),
'@devframes/plugin-messages': p('messages/src/index.ts'),
+ '@devframes/plugin-assets/client': p('assets/src/client/index.ts'),
+ '@devframes/plugin-assets/node': p('assets/src/node/index.ts'),
+ '@devframes/plugin-assets/rpc': p('assets/src/rpc/index.ts'),
+ '@devframes/plugin-assets/cli': p('assets/src/cli.ts'),
+ '@devframes/plugin-assets/vite': p('assets/src/vite.ts'),
+ '@devframes/plugin-assets': p('assets/src/index.ts'),
}
// update tsconfig.base.json — CSS aliases exist for Vite resolution only;
diff --git a/docs/.vitepress/config.ts b/docs/.vitepress/config.ts
index 738d1a5d..b3ce436a 100644
--- a/docs/.vitepress/config.ts
+++ b/docs/.vitepress/config.ts
@@ -71,6 +71,7 @@ function pluginsItems(prefix: string) {
{ text: 'Git', link: `${prefix}/plugins/git` },
{ text: 'Terminals', link: `${prefix}/plugins/terminals` },
{ text: 'Code Server', link: `${prefix}/plugins/code-server` },
+ { text: 'Assets', link: `${prefix}/plugins/assets` },
] satisfies DefaultTheme.NavItemWithLink[]
}
diff --git a/docs/errors/DF0042.md b/docs/errors/DF0042.md
new file mode 100644
index 00000000..96c364c4
--- /dev/null
+++ b/docs/errors/DF0042.md
@@ -0,0 +1,32 @@
+---
+outline: deep
+---
+
+# DF0042: Static Build Disabled By The Definition
+
+## Message
+
+> "`{id}`" declares `capabilities.build: false` — its static export is not meaningful (writes are excluded and any live-served data won't be there).
+
+## Cause
+
+`createBuild` runs unconditionally when called directly, but a definition can opt out of static export via `capabilities.build: false` — typically because the devframe is inherently live (it manages real files on disk, spawns a process, etc.) and a `mode: 'build'` export would only ever produce a broken, write-less shell of the tool. `createCac` already skips registering the `build` subcommand for such a definition; this diagnostic covers the remaining path — a caller invoking `createBuild()` directly, bypassing the CLI.
+
+## Example
+
+```ts
+// ✗ Bad — builds a devframe that opted out of static export
+await createBuild(assetsDevframe) // throws DF0042
+
+// ✓ Good — the degraded export is still useful to you
+await createBuild(assetsDevframe, { force: true })
+```
+
+## Fix
+
+- Pass `{ force: true }` to `createBuild()` if the degraded export is still useful to you.
+- Otherwise, drop `capabilities.build: false` on the definition if a static export should be supported after all.
+
+## Source
+
+- [`packages/devframe/src/adapters/build.ts`](https://github.com/devframes/devframe/blob/main/packages/devframe/src/adapters/build.ts) — `createBuild()` throws this when `capabilities.build` is `false` and `force` isn't set.
diff --git a/docs/plugins/assets.md b/docs/plugins/assets.md
new file mode 100644
index 00000000..a13699de
--- /dev/null
+++ b/docs/plugins/assets.md
@@ -0,0 +1,90 @@
+---
+outline: deep
+---
+
+# Assets
+
+Browse, preview, upload, rename, and delete the files in a directory, built as a **Vue** SPA on `@antfu/design` — a framework-neutral port of Nuxt DevTools' Assets tab.
+
+Package: `@devframes/plugin-assets` · framework: **Vue + @antfu/design**
+
+## What it does
+
+Search by name and filter by type from an inline chip row, switch between a thumbnail grid (grouped by folder) and a file tree, and open a resizable right-hand details panel with a live preview (image, video, audio, font, or text), file metadata, and ready-to-copy usage snippets (``, CSS `background-image`, `@font-face`, a download link). Upload files with the toolbar button (native file picker) or by dropping them anywhere on the frame, and select multiple assets to delete them together. A live file watcher keeps every connected client's listing in sync with changes made outside the UI.
+
+The standalone server requires devframe's trust handshake by default because it can read, write, and delete real files. Uploads, renames, deletes, and folder creation are enabled by default — pass `{ write: false }` (or `--read-only` on the standalone CLI) for a browse-only deployment.
+
+## Standalone
+
+```sh
+pnpx @devframes/plugin-assets # manages /public
+pnpx @devframes/plugin-assets --read-only # disable upload / rename / delete / mkdir
+```
+
+## Mount into a Vite host
+
+```ts
+// vite.config.ts
+import { assetsVitePlugin } from '@devframes/plugin-assets/vite'
+import { defineConfig } from 'vite'
+
+export default defineConfig({
+ plugins: [
+ assetsVitePlugin(),
+ ],
+})
+```
+
+## Programmatic
+
+`createAssetsDevframe(options)` returns a definition you can deploy through any adapter:
+
+```ts
+import { createAssetsDevframe } from '@devframes/plugin-assets'
+
+export default createAssetsDevframe({
+ dir: 'static', // defaults to `/public`
+ baseURL: '/', // the URL the host serves `dir` at
+ write: true,
+ uploadExtensions: ['png', 'jpg', 'svg', 'webp'], // defaults to Nuxt DevTools' own allow-list, or '*' for any
+})
+```
+
+| Option | Default | Description |
+|--------|---------|-------------|
+| `dir` | `/public` | Directory this devframe manages. |
+| `baseURL` | `/` | URL base the host serves `dir` at — each asset's `publicPath` is `baseURL` + its path. Match a non-root deployment base (e.g. Nuxt's `app.baseURL`). |
+| `write` | `true` | Enable upload, rename, delete, and folder creation from the UI. |
+| `uploadExtensions` | Nuxt DevTools' allow-list | Extensions `upload` accepts, or `'*'` for any. |
+| `serveStatic` | `false` | Serve the directory's bytes from this devframe itself. Left off when mounted into a host that already serves `public/`; the standalone CLI turns it on. |
+| `build` | `false` | Register the `build` CLI subcommand. See [why it's off by default](#static-export) below. |
+
+## How previews are served
+
+Asset previews (``, `