diff --git a/packages/domscribe-relay/src/mcp/tools/dormant-status.tool.spec.ts b/packages/domscribe-relay/src/mcp/tools/dormant-status.tool.spec.ts index 4e07de2..5790050 100644 --- a/packages/domscribe-relay/src/mcp/tools/dormant-status.tool.spec.ts +++ b/packages/domscribe-relay/src/mcp/tools/dormant-status.tool.spec.ts @@ -18,7 +18,7 @@ describe('DormantStatusTool', () => { expect(structured['cwd']).toBe(cwd); }); - it('should return guidance with setup instructions', async () => { + it('should return guidance explaining dormant state', async () => { // Arrange const tool = new DormantStatusTool('/tmp/test'); @@ -28,10 +28,23 @@ describe('DormantStatusTool', () => { // Assert const structured = result.structuredContent as Record; expect(structured['guidance']).toEqual(expect.any(String)); - expect(structured['guidance']).toContain('npx domscribe init'); expect(structured['guidance']).toContain('.domscribe'); }); + it('should return actionable next steps for the agent', async () => { + // Arrange + const tool = new DormantStatusTool('/tmp/test'); + + // Act + const result: CallToolResult = await tool.toolCallback({}); + + // Assert + const structured = result.structuredContent as Record; + expect(structured['nextSteps']).toEqual(expect.any(String)); + expect(structured['nextSteps']).toContain('package.json'); + expect(structured['nextSteps']).toContain('@domscribe'); + }); + it('should return text content matching structured content', async () => { // Arrange const tool = new DormantStatusTool('/tmp/test'); diff --git a/packages/domscribe-relay/src/mcp/tools/dormant-status.tool.ts b/packages/domscribe-relay/src/mcp/tools/dormant-status.tool.ts index eb5a7ff..dab48b4 100644 --- a/packages/domscribe-relay/src/mcp/tools/dormant-status.tool.ts +++ b/packages/domscribe-relay/src/mcp/tools/dormant-status.tool.ts @@ -22,7 +22,10 @@ const DormantStatusToolOutputSchema = McpToolOutputSchema.extend({ cwd: z .string() .describe('The working directory where the MCP server was started'), - guidance: z.string().describe('Instructions for setting up Domscribe'), + guidance: z.string().describe('Explanation of why Domscribe is not active'), + nextSteps: z + .string() + .describe('Actionable steps for the agent to set up Domscribe'), }); type DormantStatusToolOutput = z.infer; @@ -48,7 +51,16 @@ export class DormantStatusTool implements McpToolDefinition< guidance: 'Domscribe is not active in this workspace. ' + 'No .domscribe/ directory was found at or above the current working directory. ' + - 'To set up Domscribe, run `npx domscribe init` in your project root.', + 'The .domscribe/ directory is created automatically when the dev server starts ' + + 'with the Domscribe bundler plugin configured.', + nextSteps: + 'To set up Domscribe: ' + + '(1) Read package.json to detect the framework (next/nuxt/react/vue + vite/webpack), ' + + '(2) detect the package manager from lockfiles (pnpm-lock.yaml/yarn.lock/bun.lock), ' + + '(3) install the appropriate @domscribe/* package as a dev dependency, ' + + '(4) edit the bundler config file to add the Domscribe plugin, ' + + '(5) add .domscribe to .gitignore, ' + + '(6) ask the user to start their dev server.', }; return { diff --git a/packages/domscribe-relay/src/skills/domscribe/SKILL.md b/packages/domscribe-relay/src/skills/domscribe/SKILL.md index 28d5f9d..53398d3 100644 --- a/packages/domscribe-relay/src/skills/domscribe/SKILL.md +++ b/packages/domscribe-relay/src/skills/domscribe/SKILL.md @@ -1,7 +1,7 @@ --- name: domscribe -description: Work with Domscribe — the pixel-to-code bridge. Use when editing or modifying UI components (React, Vue, Next.js, Nuxt), implementing features from captured UI annotations, querying runtime context for source locations, exploring component structure, or when user mentions annotations, queued tasks, UI changes, props, state, DOM, or asks about how elements render at runtime. -allowed-tools: Read, Edit, Write, mcp__domscribe__*, domscribe.*, mcp.domscribe.*, process_next, check_status, explore_component, find_annotations +description: Work with Domscribe — the pixel-to-code bridge. Use when setting up, initializing, or configuring Domscribe for a project, OR when editing or modifying UI components (React, Vue, Next.js, Nuxt), implementing features from captured UI annotations, querying runtime context for source locations, exploring component structure, or when user mentions annotations, queued tasks, UI changes, props, state, DOM, or asks about how elements render at runtime. +allowed-tools: Read, Edit, Write, Bash, Glob, mcp__domscribe__*, domscribe.*, mcp.domscribe.*, process_next, check_status, explore_component, find_annotations --- # Domscribe @@ -11,6 +11,71 @@ Domscribe bridges running UI and source code. It maps every rendered element to - **UI → Code**: User clicks an element in the browser, Domscribe captures it as an annotation with source location, runtime context, and user intent. You claim and implement it. - **Code → UI**: You're editing a source file and want to know what an element looks like at runtime. Query by file and line to get live props, state, and DOM snapshot. +## Setup / Initialization + +If `domscribe.status` returns `active: false`, Domscribe is not yet configured in this workspace. The `.domscribe/` directory is created automatically when the dev server starts with the Domscribe bundler plugin configured. Follow this procedure to set it up: + +1. **Confirm dormant state** — call `domscribe.status`. If `active: false`, proceed. Note the `cwd` in the response. +2. **Detect framework** — read `package.json` (at `cwd`, or at the `appRoot` from `domscribe.config.json` if it exists) and match dependencies against the table below. +3. **Detect package manager** — check which lockfile exists at the project root. +4. **Install the package** — run the appropriate install command via Bash (e.g., `pnpm add -D @domscribe/next`). +5. **Edit the bundler config** — read the config file and apply the integration pattern. Load `references/config-patterns.md` for the exact import, transformation, and example for each framework. +6. **Update `.gitignore`** — if `.domscribe` is not already listed, append a `# Domscribe artifacts` comment and `.domscribe` entry. +7. **Inform the user** — tell them to start (or restart) their dev server. Domscribe activates automatically on first run. + +### Framework Detection + +Check `dependencies` and `devDependencies` in `package.json`. Match top-down (first match wins): + +| Dependency | Framework | +| ------------------- | ------------- | +| `next` | next | +| `nuxt` | nuxt | +| `react` + `vite` | react-vite | +| `react` (no `vite`) | react-webpack | +| `vue` + `vite` | vue-vite | +| `vue` (no `vite`) | vue-webpack | +| `vite` only | other-vite | +| `webpack` only | other-webpack | + +### Package Mapping + +| Framework | Package | Config file | +| ------------- | ---------------------- | ------------------- | +| next | `@domscribe/next` | `next.config.ts` | +| nuxt | `@domscribe/nuxt` | `nuxt.config.ts` | +| react-vite | `@domscribe/react` | `vite.config.ts` | +| react-webpack | `@domscribe/react` | `webpack.config.js` | +| vue-vite | `@domscribe/vue` | `vite.config.ts` | +| vue-webpack | `@domscribe/vue` | `webpack.config.js` | +| other-vite | `@domscribe/transform` | `vite.config.ts` | +| other-webpack | `@domscribe/transform` | `webpack.config.js` | + +### Package Manager Detection + +| Lockfile | Package manager | +| ------------------------ | --------------- | +| `pnpm-lock.yaml` | pnpm | +| `yarn.lock` | yarn | +| `bun.lock` / `bun.lockb` | bun | +| (none) | npm | + +Install command pattern: ` add -D ` (pnpm/yarn/bun) or `npm install -D `. + +### Monorepo Projects + +If `domscribe.config.json` exists at the project root with an `appRoot` field, the frontend app lives in that subdirectory. Install packages and find the bundler config relative to `appRoot`. + +If the project appears to be a monorepo (e.g., `apps/`, `packages/` directories, workspace config in `package.json`) but no `domscribe.config.json` exists, ask the user which directory contains the frontend app. Then write `domscribe.config.json` at the project root: + +```json +{ "appRoot": "apps/web" } +``` + +### After Setup + +The MCP server starts in dormant mode when no `.domscribe/` directory exists. After the user starts their dev server, the bundler plugin creates `.domscribe/` automatically. The MCP server will need to restart to detect the new workspace and transition to active mode with the full tool suite. + ## Editing Components (Code → UI) **Why query runtime state?** Source code alone doesn't tell you what props a component actually received, whether a conditional branch rendered, what CSS classes were applied, or what text the user sees. `domscribe.query.bySource` gives you the live truth from the browser. diff --git a/packages/domscribe-relay/src/skills/domscribe/references/config-patterns.md b/packages/domscribe-relay/src/skills/domscribe/references/config-patterns.md new file mode 100644 index 0000000..f9909e3 --- /dev/null +++ b/packages/domscribe-relay/src/skills/domscribe/references/config-patterns.md @@ -0,0 +1,210 @@ +# Domscribe Config Patterns + +How to edit each framework's bundler config to integrate Domscribe. The 8 framework combinations reduce to 4 distinct integration patterns. + +--- + +## Next.js — HOF Wrapper + +**Import to add:** + +```typescript +import { withDomscribe } from '@domscribe/next'; +``` + +**How to edit:** + +Wrap the existing default export with `withDomscribe()()`. This is a higher-order function that returns a config transformer. + +- If the file has `export default ` — change to `export default withDomscribe()()` +- If the file has `const config = ...; export default config` — change to `export default withDomscribe()(config)` +- If the file already uses another wrapper (e.g., `withMDX`), compose them: `export default withDomscribe()(withMDX(config))` + +**Full example (`next.config.ts`):** + +```typescript +import type { NextConfig } from 'next'; +import { withDomscribe } from '@domscribe/next'; + +const nextConfig: NextConfig = {}; + +export default withDomscribe()(nextConfig); +``` + +--- + +## Nuxt — Module Registration + +**How to edit:** + +Add `'@domscribe/nuxt'` as a string to the `modules` array inside `defineNuxtConfig()`. No import needed — Nuxt resolves the module by package name. + +- If no `modules` key exists in the config object, add it: `modules: ['@domscribe/nuxt']` +- If `modules` exists, append `'@domscribe/nuxt'` to the array + +**Full example (`nuxt.config.ts`):** + +```typescript +export default defineNuxtConfig({ + modules: ['@domscribe/nuxt'], +}); +``` + +--- + +## Vite Plugin (React / Vue / Other) + +Three variants — same pattern, different import path: + +| Framework | Import path | +| ---------- | ----------------------------------- | +| react-vite | `@domscribe/react/vite` | +| vue-vite | `@domscribe/vue/vite` | +| other-vite | `@domscribe/transform/plugins/vite` | + +**Import to add** (use the path from the table above): + +```typescript +import { domscribe } from '@domscribe/react/vite'; +``` + +**How to edit:** + +Add `domscribe()` to the `plugins` array inside `defineConfig()`. Place it after the framework plugin (e.g., after `react()` or `vue()`). + +- If `plugins` exists, append `domscribe()` to the array +- If `plugins` doesn't exist, add `plugins: [domscribe()]` + +**Full example — React + Vite (`vite.config.ts`):** + +```typescript +import { defineConfig } from 'vite'; +import react from '@vitejs/plugin-react'; +import { domscribe } from '@domscribe/react/vite'; + +export default defineConfig({ + plugins: [react(), domscribe()], +}); +``` + +**Full example — Vue + Vite (`vite.config.ts`):** + +```typescript +import { defineConfig } from 'vite'; +import vue from '@vitejs/plugin-vue'; +import { domscribe } from '@domscribe/vue/vite'; + +export default defineConfig({ + plugins: [vue(), domscribe()], +}); +``` + +**Full example — Other + Vite (`vite.config.ts`):** + +```typescript +import { defineConfig } from 'vite'; +import { domscribe } from '@domscribe/transform/plugins/vite'; + +export default defineConfig({ + plugins: [domscribe()], +}); +``` + +--- + +## Webpack Plugin + Loader (React / Vue / Other) + +Three variants — same pattern, different import path: + +| Framework | Import path | +| ------------- | -------------------------------------- | +| react-webpack | `@domscribe/react/webpack` | +| vue-webpack | `@domscribe/vue/webpack` | +| other-webpack | `@domscribe/transform/plugins/webpack` | + +**Two edits required:** + +### Edit 1 — Add the transform loader rule + +Add a pre-enforce loader rule to `module.rules`. This must run before other loaders: + +```javascript +{ + test: /\.[jt]sx?$/, + exclude: /node_modules/, + enforce: 'pre', + use: [ + { + loader: '@domscribe/transform/webpack-loader', + options: { enabled: process.env.NODE_ENV !== 'production' }, + }, + ], +} +``` + +### Edit 2 — Add the webpack plugin + +Add the plugin instance to the `plugins` array. Use the import path from the table above: + +```javascript +const { DomscribeWebpackPlugin } = require('@domscribe/react/webpack'); + +// In the plugins array: +new DomscribeWebpackPlugin({ + enabled: process.env.NODE_ENV !== 'production', + overlay: true, +}); +``` + +**Full example — React + Webpack (`webpack.config.js`):** + +```javascript +const { DomscribeWebpackPlugin } = require('@domscribe/react/webpack'); + +const isDevelopment = process.env.NODE_ENV !== 'production'; + +module.exports = { + module: { + rules: [ + { + test: /\.[jt]sx?$/, + exclude: /node_modules/, + enforce: 'pre', + use: [ + { + loader: '@domscribe/transform/webpack-loader', + options: { enabled: isDevelopment }, + }, + ], + }, + // ... existing loaders + ], + }, + plugins: [ + new DomscribeWebpackPlugin({ + enabled: isDevelopment, + overlay: true, + }), + ], +}; +``` + +--- + +## Package Manager Install Commands + +| Lockfile | Package manager | Install command | +| ------------------------ | --------------- | ---------------- | +| `pnpm-lock.yaml` | pnpm | `pnpm add -D` | +| `yarn.lock` | yarn | `yarn add -D` | +| `bun.lock` / `bun.lockb` | bun | `bun add -D` | +| (none) | npm | `npm install -D` | + +## Gitignore + +If `.domscribe` is not already listed in `.gitignore`, append: + +``` +# Domscribe artifacts +.domscribe +```