Skip to content

Commit cef7951

Browse files
committed
feat(docs): update CLAUDE and README for new features and tools
- Added linting and formatting commands to CLAUDE.md. - Updated architecture section in CLAUDE.md to include new packages: pen-engine and pen-mcp. - Enhanced README with new sections on style guides, embeddable SDK, and design system kit. - Clarified prerequisites for building agent-native from source. - Improved descriptions of AI features and multi-agent support in the README.
1 parent e46a507 commit cef7951

7 files changed

Lines changed: 278 additions & 70 deletions

File tree

CLAUDE.md

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,13 +11,16 @@ Detailed module docs are in `packages/CLAUDE.md`, `apps/web/CLAUDE.md`, `apps/de
1111
- **Run all tests:** `bun --bun run test` (Vitest)
1212
- **Run a single test:** `bun --bun vitest run path/to/test.ts`
1313
- **Type check:** `npx tsc --noEmit`
14+
- **Lint:** `bun run lint` (oxlint)
15+
- **Format:** `bun run format` (oxfmt)
1416
- **Install dependencies:** `bun install`
1517
- **Bump version:** `bun run bump <version>` (syncs all package.json files)
1618
- **Electron dev:** `bun run electron:dev` (starts Vite + Electron together)
1719
- **Electron compile:** `bun run electron:compile` (esbuild electron/ to out/desktop/)
1820
- **Electron build:** `bun run electron:build` (full web build + compile + electron-builder package)
1921
- **CLI compile:** `bun run cli:compile` (esbuild CLI to apps/cli/dist/)
2022
- **CLI dev:** `bun run cli:dev` (run CLI from source via Bun)
23+
- **MCP dev:** `bun run mcp:dev` (run MCP server from source)
2124
- **Publish beta:** `bun run publish:beta [N]` (publish all npm packages with beta tag)
2225

2326
## Architecture
@@ -33,17 +36,20 @@ openpencil/
3336
├── packages/
3437
│ ├── pen-types/ Type definitions for PenDocument model
3538
│ ├── pen-core/ Document tree ops, layout engine, variables, boolean ops, clone utilities
39+
│ ├── pen-engine/ Headless design engine — framework-free document, selection, history, viewport
40+
│ ├── pen-react/ React UI SDK — DesignProvider, DesignCanvas, hooks, panels, toolbar
3641
│ ├── pen-codegen/ Multi-platform code generators
3742
│ ├── pen-figma/ Figma .fig file parser and converter
3843
│ ├── pen-renderer/ Standalone CanvasKit/Skia renderer
44+
│ ├── pen-mcp/ MCP server — tools, routes, document manager for external CLI integration
3945
│ ├── pen-sdk/ Umbrella SDK (re-exports all packages)
4046
│ ├── pen-ai-skills/ AI prompt skill engine (phase-driven prompt loading + design memory)
41-
│ └── agent/ Domain-agnostic AI agent SDK (Vercel AI SDK, multi-provider, agent teams)
47+
│ └── agent-native/ Native AI agent runtime (Zig NAPI, multi-provider, agent teams)
4248
├── scripts/ Build and publish scripts
4349
└── .githooks/ Pre-commit version sync from branch name
4450
```
4551

46-
**Key technologies:** React 19, CanvasKit/Skia WASM (canvas engine), Paper.js (boolean path operations), Zustand v5 (state management), TanStack Router (file-based routing), Tailwind CSS v4, shadcn/ui (UI primitives), Vite 7, Nitro (server), Electron 35 (desktop), Vercel AI SDK v6 (agent framework), i18next (15 locales), TypeScript (strict mode).
52+
**Key technologies:** React 19, CanvasKit/Skia WASM (canvas engine), Paper.js (boolean path operations), Zustand v5 (state management), TanStack Router (file-based routing), Tailwind CSS v4, shadcn/ui (UI primitives), Vite 7, Nitro (server), Electron 35 (desktop), Vercel AI SDK v6 (agent framework), i18next (15 locales), TypeScript (strict mode), oxlint/oxfmt (linting & formatting).
4753

4854
### Data Flow
4955

README.md

Lines changed: 51 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ Describe any UI in natural language. Watch it appear on the infinite canvas in r
4848

4949
### 🤖 Concurrent Agent Teams
5050

51-
The orchestrator decomposes complex pages into spatial sub-tasks. Multiple AI agents work on different sections simultaneously — hero, features, footer — all streaming in parallel.
51+
The orchestrator decomposes complex pages into spatial sub-tasks. Multiple AI agents work on different sections simultaneously — hero, features, footer — all streaming in parallel with per-member canvas indicators.
5252

5353
</td>
5454
</tr>
@@ -71,33 +71,60 @@ One-click install into Claude Code, Codex, Gemini, OpenCode, Kiro, or Copilot CL
7171
<tr>
7272
<td width="50%">
7373

74+
### 🎨 Style Guides
75+
76+
Built-in style guide library with tag-based fuzzy matching. Apply visual styles (glassmorphism, brutalist, retro, etc.) to AI-generated designs. MCP tools for external agent access.
77+
78+
</td>
79+
<td width="50%">
80+
7481
### 📦 Design-as-Code
7582

7683
`.op` files are JSON — human-readable, Git-friendly, diffable. Design variables generate CSS custom properties. Code export to React + Tailwind or HTML + CSS.
7784

7885
</td>
86+
</tr>
87+
<tr>
7988
<td width="50%">
8089

8190
### 🖥️ Runs Everywhere
8291

8392
Web app + native desktop on macOS, Windows, and Linux via Electron. Auto-updates from GitHub Releases. `.op` file association — double-click to open.
8493

8594
</td>
86-
</tr>
87-
<tr>
8895
<td width="50%">
8996

9097
### ⌨️ CLI — `op`
9198

9299
Control the design tool from your terminal. `op design`, `op insert`, `op export` — batch design DSL, node manipulation, code export. Pipe in from files or stdin. Works with desktop app or web server.
93100

94101
</td>
102+
</tr>
103+
<tr>
95104
<td width="50%">
96105

97106
### 🎯 Multi-Platform Code Export
98107

99108
Export to React + Tailwind, HTML + CSS, Vue, Svelte, Flutter, SwiftUI, Jetpack Compose, React Native — all from one `.op` file. Design variables become CSS custom properties.
100109

110+
</td>
111+
<td width="50%">
112+
113+
### 🧩 Embeddable SDK
114+
115+
`pen-engine` (headless) + `pen-react` (React UI SDK) — embed the design engine in your own app. DesignProvider, DesignCanvas, hooks, panels, and toolbar components out of the box.
116+
117+
</td>
118+
</tr>
119+
<tr>
120+
<td width="50%">
121+
122+
### 🛡️ Design System Kit
123+
124+
Manage reusable UIKits with style switching and component composition. Import/export kits from `.pen` files. Built-in registry with MCP tools for external access.
125+
126+
</td>
127+
<td width="50%">
101128
</td>
102129
</tr>
103130
</table>
@@ -142,7 +169,7 @@ Or run as a desktop app:
142169
bun run electron:dev
143170
```
144171

145-
> **Prerequisites:** [Bun](https://bun.sh/) >= 1.0 and [Node.js](https://nodejs.org/) >= 18
172+
> **Prerequisites:** [Bun](https://bun.sh/) >= 1.0 and [Node.js](https://nodejs.org/) >= 18. Optional: [Zig](https://ziglang.org/) >= 0.14 for building `agent-native` from source (a prebuilt binary will be downloaded automatically if Zig is not installed).
146173
147174
### Docker
148175

@@ -198,10 +225,13 @@ docker build --target full -t openpencil-full .
198225

199226
**Prompt to UI**
200227

201-
- **Text-to-design** — describe a page, get it generated on canvas in real-time with streaming animation
228+
- **Text-to-design** — describe a page, get it generated on canvas in real-time with SSE streaming animation
202229
- **Orchestrator** — decomposes complex pages into spatial sub-tasks for parallel generation
230+
- **Agent Teams** — concurrent team members with delegate tool, per-member canvas indicators, and fallback strategies
203231
- **Design modification** — select elements, then describe changes in natural language
204232
- **Vision input** — attach screenshots or mockups for reference-based design
233+
- **Style Guides** — apply visual styles (glassmorphism, brutalist, retro, etc.) via tag-based fuzzy matching
234+
- **Anti-slop** — cross-generation diversity tracking to avoid repetitive AI output
205235

206236
**Multi-Agent Support**
207237

@@ -220,11 +250,12 @@ docker build --target full -t openpencil-full .
220250

221251
**MCP Server**
222252

223-
- Built-in MCP server — one-click install into Claude Code / Codex / Gemini / OpenCode / Kiro / Copilot CLIs
253+
- Built-in MCP server (`pen-mcp` package) — one-click install into Claude Code / Codex / Gemini / OpenCode / Kiro / Copilot CLIs
224254
- Auto-detects Node.js — if not installed, falls back to HTTP transport and auto-starts the MCP HTTP server
225255
- Design automation from terminal: read, create, and modify `.op` files via any MCP-compatible agent
226256
- **Layered design workflow**`design_skeleton``design_content``design_refine` for higher-fidelity multi-section designs
227257
- **Segmented prompt retrieval** — load only the design knowledge you need (schema, layout, roles, icons, planning, etc.)
258+
- **Style guide tools**`get_style_guide_tags` and `get_style_guide` for applying visual styles via MCP
228259
- Multi-page support — create, rename, reorder, and duplicate pages via MCP tools
229260

230261
**Code Generation**
@@ -288,12 +319,14 @@ Supports three input methods: inline string, `@filepath` (read from file), or `-
288319
| --------------- | -------------------------------------------------------------------------------- |
289320
| **Frontend** | React 19 · TanStack Start · Tailwind CSS v4 · shadcn/ui · i18next |
290321
| **Canvas** | CanvasKit/Skia (WASM, GPU-accelerated) |
322+
| **Engine** | pen-engine (headless) · pen-react (React UI SDK) |
291323
| **State** | Zustand v5 |
292324
| **Server** | Nitro |
293325
| **Desktop** | Electron 35 |
294326
| **CLI** | `op` — terminal control, batch design DSL, code export |
295-
| **AI** | Vercel AI SDK v6 · Anthropic SDK · Claude Agent SDK · OpenCode SDK · Copilot SDK |
327+
| **AI** | agent-native (Zig NAPI) · Anthropic SDK · Claude Agent SDK · OpenCode SDK · Copilot SDK |
296328
| **Runtime** | Bun · Vite 7 |
329+
| **Lint** | oxlint · oxfmt |
297330
| **File format** | `.op` — JSON-based, human-readable, Git-friendly |
298331

299332
## Project Structure
@@ -308,7 +341,6 @@ openpencil/
308341
│ │ │ ├── services/ai/ AI chat, orchestrator, design generation, streaming
309342
│ │ │ ├── services/codegen/ Code generation service wrappers
310343
│ │ │ ├── stores/ Zustand — canvas, document, pages, history, AI
311-
│ │ │ ├── mcp/ MCP server tools for external CLI integration
312344
│ │ │ ├── hooks/ Keyboard shortcuts, file drop, Figma paste, MCP sync
313345
│ │ │ ├── i18n/ Internationalization — 15 locales
314346
│ │ │ └── uikit/ Reusable component kit system
@@ -327,12 +359,15 @@ openpencil/
327359
├── packages/
328360
│ ├── pen-types/ Type definitions for PenDocument model
329361
│ ├── pen-core/ Document tree ops, layout engine, variables
362+
│ ├── pen-engine/ Headless design engine — document, selection, history, viewport
363+
│ ├── pen-react/ React UI SDK — provider, canvas, hooks, panels, toolbar
330364
│ ├── pen-codegen/ Code generators (React, HTML, Vue, Flutter, ...)
331365
│ ├── pen-figma/ Figma .fig file parser and converter
332366
│ ├── pen-renderer/ Standalone CanvasKit/Skia renderer
367+
│ ├── pen-mcp/ MCP server — tools, routes, document manager
333368
│ ├── pen-sdk/ Umbrella SDK (re-exports all packages)
334369
│ ├── pen-ai-skills/ AI prompt skill engine (phase-driven prompt loading)
335-
│ └── agent/ AI agent SDK (Vercel AI SDK, multi-provider, agent teams)
370+
│ └── agent-native/ Native AI agent runtime (Zig NAPI, multi-provider, teams)
336371
└── .githooks/ Pre-commit version sync from branch name
337372
```
338373

@@ -361,11 +396,14 @@ bun --bun run dev # Dev server (port 3000)
361396
bun --bun run build # Production build
362397
bun --bun run test # Run tests (Vitest)
363398
npx tsc --noEmit # Type check
399+
bun run lint # Lint (oxlint)
400+
bun run format # Format (oxfmt)
364401
bun run bump <version> # Sync version across all package.json
365402
bun run electron:dev # Electron dev
366403
bun run electron:build # Electron package
367404
bun run cli:dev # Run CLI from source
368405
bun run cli:compile # Compile CLI to dist
406+
bun run mcp:dev # Run MCP server from source
369407
```
370408

371409
## Contributing
@@ -393,6 +431,10 @@ Contributions are welcome! See [CLAUDE.md](./CLAUDE.md) for architecture details
393431
- [x] CLI tool (`op`) for terminal control
394432
- [x] Built-in AI agent SDK with multi-provider support
395433
- [x] i18n — 15 languages
434+
- [x] Headless design engine (`pen-engine`) + React UI SDK (`pen-react`)
435+
- [x] Style Guides with tag-based matching and MCP tools
436+
- [x] Concurrent Agent Teams with delegate tool and canvas indicators
437+
- [x] Native agent runtime (`agent-native` — Zig NAPI)
396438
- [ ] Collaborative editing
397439
- [ ] Plugin system
398440

apps/web/server/api/ai/agent.ts

Lines changed: 82 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,9 @@ import {
1313
runTeam,
1414
addTeamMember,
1515
resolveTeamToolResult,
16+
teamRegisterDelegate,
17+
runTeamMember,
18+
destroyIterator,
1619
} from '@zseven-w/agent-native';
1720
import type { AuthLevel } from '../../../src/types/agent';
1821
import { agentSessions, cleanup, abortSession, type AgentSession } from '../../utils/agent-sessions';
@@ -167,10 +170,10 @@ export default defineEventHandler(async (event) => {
167170
}
168171
try {
169172
const resultJson = JSON.stringify(body.result);
170-
if (session.engine) {
171-
resolveToolResult(session.engine, body.toolCallId, resultJson);
172-
} else if (session.team) {
173+
if (session.team) {
173174
resolveTeamToolResult(session.team, body.toolCallId, resultJson);
175+
} else if (session.engine) {
176+
resolveToolResult(session.engine, body.toolCallId, resultJson);
174177
}
175178
} catch {
176179
return { ok: true, ignored: true };
@@ -232,16 +235,19 @@ export default defineEventHandler(async (event) => {
232235

233236
for (const m of body.members) {
234237
const memberProvider = createProviderHandle(m.providerType, m.apiKey, m.model, m.baseURL);
238+
// Members get an EMPTY tool registry — they output JSONL text directly,
239+
// not via tool calls. If we registered external tools (generate_design etc.),
240+
// the member would call them, but tool results can only be resolved on the
241+
// leader engine, not the member — causing the member to block forever.
235242
const memberTools = createToolRegistry();
236-
for (const def of body.toolDefs ?? []) {
237-
const params = def.parameters ? { ...def.parameters } : { type: 'object' };
238-
delete (params as any).$schema;
239-
registerToolSchema(memberTools, def.name, JSON.stringify(params));
240-
}
241243
addTeamMember(team, m.id, memberProvider, memberTools, m.systemPrompt ?? '', 20);
242244
memberHandles.push({ provider: memberProvider, tools: memberTools });
243245
}
244246

247+
// Register delegate tool in leader's registry so the LLM can call
248+
// delegate({member_id, task}) to dispatch work to members
249+
teamRegisterDelegate(team);
250+
245251
session = { team, provider, tools, memberHandles, createdAt: Date.now(), lastActivity: Date.now() };
246252
} else {
247253
// Single engine mode
@@ -294,6 +300,74 @@ export default defineEventHandler(async (event) => {
294300
let raw: string | null;
295301
while ((raw = await nextEvent(iter)) !== null) {
296302
session.lastActivity = Date.now();
303+
304+
// Intercept delegate tool_use in team mode — run member engine
305+
// instead of forwarding to client
306+
if (session.team) {
307+
try {
308+
const evt = JSON.parse(raw);
309+
if (evt.tool_use && evt.tool_use.name === 'delegate') {
310+
const toolUseId = evt.tool_use.id;
311+
let memberIdRaw: string | undefined;
312+
let taskRaw: string | undefined;
313+
314+
// Parse delegate args from input (may be JSON string or object)
315+
const inputData = evt.tool_use.input;
316+
if (typeof inputData === 'string') {
317+
try {
318+
const parsed = JSON.parse(inputData);
319+
memberIdRaw = parsed.member_id;
320+
taskRaw = parsed.task;
321+
} catch { /* fallback below */ }
322+
} else if (inputData && typeof inputData === 'object') {
323+
memberIdRaw = inputData.member_id;
324+
taskRaw = inputData.task;
325+
}
326+
327+
if (memberIdRaw && taskRaw) {
328+
// Emit member_start to client
329+
controller.enqueue(encoder.encode(
330+
`event: member_start\ndata: ${JSON.stringify({ type: 'member_start', memberId: memberIdRaw, task: taskRaw })}\n\n`,
331+
));
332+
333+
// Run member engine
334+
let memberResult = '';
335+
const memberIter = await runTeamMember(session.team, memberIdRaw, taskRaw);
336+
try {
337+
let memberRaw: string | null;
338+
while ((memberRaw = await nextEvent(memberIter)) !== null) {
339+
session.lastActivity = Date.now();
340+
// Forward member events to client (text, thinking, tool_call, etc.)
341+
controller.enqueue(encoder.encode(zigEventToSSE(memberRaw)));
342+
// Collect text for delegate tool result
343+
try {
344+
const mEvt = JSON.parse(memberRaw);
345+
if (mEvt.stream_event?.text && mEvt.stream_event.type === 'text_delta') {
346+
memberResult += mEvt.stream_event.text;
347+
}
348+
} catch { /* ignore parse errors */ }
349+
}
350+
} finally {
351+
destroyIterator(memberIter);
352+
}
353+
354+
// Emit member_end to client
355+
controller.enqueue(encoder.encode(
356+
`event: member_end\ndata: ${JSON.stringify({ type: 'member_end', memberId: memberIdRaw, result: '' })}\n\n`,
357+
));
358+
359+
// Resolve delegate tool result back to leader engine
360+
resolveTeamToolResult(
361+
session.team,
362+
toolUseId,
363+
JSON.stringify({ result: memberResult || 'Member completed task.' }),
364+
);
365+
continue; // skip normal forwarding for this event
366+
}
367+
}
368+
} catch { /* not JSON or not delegate — fall through to normal forwarding */ }
369+
}
370+
297371
controller.enqueue(encoder.encode(zigEventToSSE(raw)));
298372
}
299373
} catch (err: any) {

apps/web/server/api/ai/chat.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1022,7 +1022,11 @@ function streamViaBuiltin(body: ChatBody) {
10221022
} = await import('@zseven-w/agent-native');
10231023

10241024
const apiKey = body.builtinApiKey;
1025-
const model = body.model?.trim();
1025+
const rawModel = body.model?.trim() ?? '';
1026+
// Model string may be "builtin:<providerId>:<actualModel>" — extract the actual model name
1027+
const model = rawModel.startsWith('builtin:')
1028+
? rawModel.split(':').slice(2).join(':')
1029+
: rawModel;
10261030
if (!apiKey || !model) throw new Error('Builtin provider requires apiKey and model');
10271031

10281032
const builtinProvider =

0 commit comments

Comments
 (0)