AgentStack
Browse Sign in
Browse Why AgentStack Sell Docs
Sign in
SKILL verified MIT Self-run

Openpencil Design

skill-zseven-w-openpencil-skill-openpencil-design · by ZSeven-W

Use when designing UI with OpenPencil — creating layouts via op CLI, batch design DSL, or MCP tools. Covers PenNode schema, semantic roles, typography, color, spacing, and common component patterns.

No reviews yet
0 installs
3 views
0.0% view→install

Install

$ agentstack add skill-zseven-w-openpencil-skill-openpencil-design

✓ scanned · ✓ verified, works with Claude Code, Cursor, and more.

Security review

✓ Passed

No issues found. Passed automated security review. · v0.1.0 How review works →

  • Prompt-injection patterns
  • Secret / credential exfiltration
  • Dangerous shell & filesystem operations
  • Untrusted network calls
  • Known-malicious package signatures

What it can access

  • Network access No
  • Filesystem access No
  • Shell / process execution No
  • Environment & secrets No
  • Dynamic code execution No

From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.

View the full security report →

Verified badge

Passed review? Show it. Paste this badge into your README, it links to the public security report.

AgentStack Verified badge Links to your public security report.
[![AgentStack Verified](https://agentstack.voostack.com/badges/verified.svg)](https://agentstack.voostack.com/security/report/skill-zseven-w-openpencil-skill-openpencil-design)

Reliability & compatibility

Security review passed
0 installs to date
no reviews yet
3mo ago

Declared compatibility

Claude CodeClaude Desktop

Compatibility is declared by the source manifest. End-to-end runtime verification is coming, see below.

Preview Execution monitoring

We're building live execution health for every listing: tool-call success rate, median latency, uptime, and last-checked timestamps, measured, not self-reported. It isn't live yet, so we don't show numbers we can't stand behind.

How agent discovery & health will work →
Are you the author of Openpencil Design? Claim this listing to set pricing, connect Stripe payouts, and keep 70% of every sale.
Sign up to claim

About

OpenPencil Design

Generate production-quality vector designs by writing PenNode JSON trees. Use the op CLI or MCP tools to create, read, update, and delete nodes on the OpenPencil canvas.

When to Use

  • Creating or modifying UI designs in .op files
  • Using the op CLI to script design operations
  • Designing via MCP tools (batch_design, insert_node, design_skeleton)
  • Need reference for PenNode schema, roles, or layout rules

Quick Reference — op CLI

# App control
op start [--desktop|--web]           # Launch app
op stop                              # Stop running instance
op status                            # Check if running

# Document
op open [file.op]                    # Open file or connect to live canvas
op save                     # Save current document
op get [--depth N] [--pretty]        # Get document tree
op selection [--depth N]             # Get current canvas selection
op read-nodes [id...] [--depth N] [--vars]  # Read node subtree(s) with optional variable resolution
op layout [--parent P] [--depth N]   # Snapshot layout tree with computed positions
op find-space [--direction D] [--width N] [--height N]  # Find empty space on canvas

# Node operations
op insert '' [--parent P]     # Insert node (--index N, --post-process)
op update  ''              # Update node
op delete                        # Delete node
op move   [index]        # Move node
op copy                  # Deep-copy node
op replace  ''             # Replace node

# Batch design
op design ''                    # Batch design DSL (inline, @file, or stdin) [--canvas-width N]

# Layered workflow
op design:skeleton ''          # Create section structure
op design:content  ''      # Populate section content
op design:refine --root-id       # Validate + auto-fix (resolves icons) [--canvas-width N]

# Import
op import:svg  [--parent P]       # Import SVG as editable nodes
op import:figma  [--out out.op]   # Convert Figma .fig to .op document

# Pages
op page list                         # List all pages
op page add [--name N]               # Add a new page
op page remove                   # Remove a page
op page rename  ''         # Rename a page
op page reorder           # Move page to position
op page duplicate                # Clone page with new IDs

# Variables & Themes
op vars / op vars:set ''       # Variables (--replace to replace all)
op themes / op themes:set ''   # Themes (--replace to replace all)
op theme:save          # Save current theme as preset file
op theme:load          # Load a theme preset file
op theme:list             # List .optheme presets in directory

# Codegen pipeline
op codegen:plan ''             # Submit codegen plan (framework, rootIds, options)
op codegen:submit ''           # Submit a code chunk for a node
op codegen:assemble [--framework F]  # Assemble all submitted chunks into final output
op codegen:clean                     # Clear codegen state

Global flags: --file , --page , --pretty. Inputs: inline string, @filepath, or - (stdin).

Building Designs — Two Approaches

Approach 1: op insert (Recommended)

The most reliable way to build designs. Use --parent to specify the parent node. Capture the returned nodeId to reference later. Always finish with design:refine to resolve icons and validate layout.

# Create root frame, capture its ID
ROOT=$(op insert '{"type":"frame","name":"Page","width":375,"height":812,"layout":"vertical"}' \
  | python3 -c "import sys,json; print(json.load(sys.stdin)['nodeId'])")

# Insert children using --parent
op insert --parent "$ROOT" '{"type":"text","content":"Hello","fontSize":28,"fontWeight":700}'

# Post-process: resolve icons, validate layout
op design:refine --root-id "$ROOT"

Approach 2: Batch Design DSL

One operation per line. Bind results with name= for later reference. Best for simple, flat structures.

> Limitation: The DSL parser cannot handle deeply nested JSON (e.g., children arrays with nested objects, or multiple levels of array nesting). Keep each I() call to a single level of nesting. For complex nodes with children, use separate I() calls for parent and children, or use op insert --parent.

root=I(null, { "type": "frame", "width": 1200, "layout": "vertical" })
nav=I(root, { "type": "frame", "role": "navbar", "height": 72 })
U(nav, { "fill": [{"type": "solid", "color": "#FFFFFF"}] })
card2=C(card1, grid, { "name": "Card 2" })
M(sidebar, main, 0)
D(old_section)
R(old_btn, { "type": "rectangle", "role": "button" })

| Op | Syntax | Action | |----|--------|--------| | I | name=I(parent, { node }) | Insert | | U | U(ref, { updates }) | Update | | C | name=C(source, parent, { overrides }) | Copy | | R | name=R(ref, { node }) | Replace | | M | M(ref, parent, index?) | Move | | D | D(ref) | Delete | | G | name=G(parent, "search", "query") | Generate image via search |

DSL safe pattern — always insert parent and children separately:

btn=I(form, {"type":"rectangle","role":"button","width":"fill_container","height":50,"cornerRadius":12,"fill":[{"type":"solid","color":"#111111"}],"layout":"horizontal","justifyContent":"center","alignItems":"center"})
I(btn, {"type":"text","content":"Submit","fontSize":16,"fontWeight":600,"fill":[{"type":"solid","color":"#FFFFFF"}]})

STRICT JSON Rules

When emitting PenNode JSON (via op insert, op design, batch_design, insert_node), you MUST produce strictly valid JSON. Common mistakes that break parsing:

  • Every property MUST have both a key and a value. NEVER emit ": 50 or : 50 without a key name. This often happens when you truncate/reformat — double-check.
  • Every key MUST be a double-quoted non-empty string.
  • fill is ALWAYS an array: "fill": [{"type": "solid", "color": "#hex"}]. Shorthand like "fill": "#hex" works but the array form is the canonical shape.
  • stroke is an object with a fill array: "stroke": {"thickness": 1, "fill": [{"type": "solid", "color": "#hex"}]}. NEVER "stroke": {"thickness": 1, "color": "#hex"} or "stroke": "#hex" (parser auto-converts these but the correct shape is preferred).
  • NO trailing commas before } or ].
  • NO comments inside JSON (// or /* */).
  • Use straight double quotes ", not smart/curly quotes.
  • content for text, NOT text: {"type": "text", "content": "Hello"}.
  • iconFontName for icons, NOT iconName or icon: {"type": "icon_font", "iconFontName": "lock"}.
  • Before finalizing the JSON, mentally verify: every key has a value, every value has a key, all brackets balance.

PenNode Schema

Common Properties

{
  "type": "frame|rectangle|text|ellipse|line|polygon|path|image|icon_font|group|ref",
  "name": "Display Name",
  "role": "semantic-role",
  "x": 0, "y": 0,
  "rotation": 0, "opacity": 1, "visible": true
}

Container Properties (frame, rectangle, group, ellipse)

{
  "width": 400,              // number | "fill_container" | "fit_content"
  "height": 300,
  "layout": "vertical",      // "none" | "vertical" | "horizontal"
  "gap": 16,
  "padding": [16, 24],       // number | [v, h] | [top, right, bottom, left]
  "justifyContent": "center", // "start" | "center" | "end" | "space_between" | "space_around"
  "alignItems": "center",    // "start" | "center" | "end"
  "clipContent": true,
  "cornerRadius": 12,        // number | [tl, tr, br, bl]
  "fill": [{ "type": "solid", "color": "#FFFFFF" }],
  "stroke": { "thickness": 1, "fill": [{ "type": "solid", "color": "#E5E7EB" }], "align": "inside", "dashPattern": [5, 3] },
  "effects": [{ "type": "shadow", "offsetX": 0, "offsetY": 4, "blur": 12, "spread": 0, "color": "rgba(0,0,0,0.08)" }],
  "children": []
}

Text

{
  "type": "text",
  "content": "Hello",          // string or StyledTextSegment[]
  "fontSize": 16, "fontFamily": "Inter", "fontWeight": 600,
  "textAlign": "center",       // "left" | "center" | "right"
  "textGrowth": "fixed-width", // "auto" | "fixed-width" | "fixed-width-height"
  "lineHeight": 1.5, "letterSpacing": 0,
  "fill": [{ "type": "solid", "color": "#111111" }]
}

Rich text: "content": [{ "text": "Bold ", "fontWeight": "bold" }, { "text": "normal" }]

Icons — Two Options

Option A: icon_font (RECOMMENDED — renders directly, no post-processing needed)
{ "type": "icon_font", "name": "Lock Icon", "iconFontName": "lock",
  "width": 20, "height": 20,
  "fill": [{ "type": "solid", "color": "#6B7280" }] }

Field is iconFontName (NOT iconName, NOT icon). Values are lowercase kebab-case Lucide names: mail, lock, eye, eye-off, chrome, apple, message-circle, x, arrow-right, search, heart, star, check, plus, bell, home, user, settings, chevron-right, download, globe, layers, zap, shield, play.

Works in ALL contexts: CLI, MCP tools, or direct .op files — no design:refine required.

Option B: path (requires post-processing)
{ "type": "path", "name": "HeartIcon", "width": 24, "height": 24,
  "fill": [{ "type": "solid", "color": "#111111" }] }

PascalCase + "Icon" suffix. Auto-resolved from Lucide set during post-processing.

> Path icons need post-processing. After inserting path nodes, run op design:refine --root-id or use op insert --post-process. Without this, path icons won't render visually. The standalone MCP server (used by ACP agents) does NOT have hook implementations registered, so path icons will NOT resolve there — prefer icon_font in MCP contexts.

Image

{ "type": "image", "src": "https://example.com/photo.jpg", "width": 400, "height": 300,
  "objectFit": "crop", "cornerRadius": 12 }

AI image placeholders (resolved by design:refine):

{ "type": "image", "width": 400, "height": 300,
  "imagePrompt": "A modern office workspace with natural light",
  "imageSearchQuery": "modern office workspace" }

Image adjustments (all -100 to 100): exposure, contrast, saturation, temperature, tint, highlights, shadows.

Polygon

{ "type": "polygon", "polygonCount": 6, "width": 80, "height": 80, "cornerRadius": 4,
  "fill": [{ "type": "solid", "color": "#6366F1" }] }

Icon Font

{ "type": "icon_font", "iconFontName": "lucide:home", "width": 24, "height": 24,
  "fill": [{ "type": "solid", "color": "#111111" }] }

Line

{ "type": "line", "x2": 200, "y2": 0,
  "stroke": { "thickness": 1, "fill": [{ "type": "solid", "color": "#E5E7EB" }] } }

Fill Types

{ "type": "solid", "color": "#3B82F6" }
{ "type": "linear_gradient", "angle": 135,
  "stops": [{ "offset": 0, "color": "#6366F1" }, { "offset": 1, "color": "#8B5CF6" }] }
{ "type": "radial_gradient", "cx": 0.5, "cy": 0.5, "radius": 0.5,
  "stops": [{ "offset": 0, "color": "#FFF" }, { "offset": 1, "color": "#000" }] }
{ "type": "image", "url": "https://example.com/texture.jpg", "mode": "fill" }

Image fill modes: fill, fit, crop, tile, stretch. Image fill also supports adjustment filters (exposure, contrast, saturation, etc.).

Ref Node (Component Instance)

{ "type": "ref", "ref": "reusable-frame-id",
  "descendants": { "child-id": { "content": "Override text" } } }

References a frame with reusable: true. Override specific descendant properties via descendants.

Design Variables

Reference with $ prefix: "color": "$primaryColor", "gap": "$spacing".

Semantic Roles

Roles declare intent — the engine applies smart defaults. Always prefer roles over manual styling.

| Category | Roles | |----------|-------| | Layout | section, row, column, centered-content, divider, spacer | | Navigation | navbar, nav-links, nav-link | | Interactive | button, icon-button, badge, tag, pill, input, form-input, search-bar | | Cards | card, feature-card, stat-card, pricing-card, image-card | | Content | hero, feature-grid, cta-section, footer, testimonial, stats-section | | Typography | heading, subheading, body-text, caption, label | | Media | avatar, icon, phone-mockup, screenshot-frame | | Table | table, table-row, table-header, table-cell | | Form | form-group |

Key defaults:

  • navbar → height: 56-72, horizontal, space_between, center-aligned
  • button → padding: [12, 24], cornerRadius: 8, centered
  • card → vertical, gap: 12, cornerRadius: 12, padding: 24
  • heading → lineHeight: 1.2, letterSpacing: -0.5
  • body-text → fill_container, textGrowth: fixed-width, lineHeight: 1.5

Layout Rules

  1. NEVER set x/y on children inside layout containers — engine positions them
  2. Siblings must use same width strategy — all fill_container or all fixed
  3. NEVER fill_container inside fit_content parent — circular dependency
  4. Cards in horizontal row: ALL width: "fill_container", height: "fill_container"

Sizing Decision

| Question | Answer | |----------|--------| | Stretch to fill? | "fill_container" | | Shrink to content? | "fit_content" | | Exact size? | number (px) |

Design Type Sizing

| Type | Width | Height | |------|-------|--------| | Landing page | 1200 | 0 (auto) | | Mobile screen | 375 | 812 | | Dashboard | 1200 | 0 (auto) |

Design Principles

Typography

Display:    40-56px  700  letterSpacing: -1.5  lineHeight: 1.1   "Space Grotesk"
Heading:    28-36px  700  letterSpacing: -0.5  lineHeight: 1.2   "Space Grotesk"
Subheading: 20-24px  600  letterSpacing: -0.25 lineHeight: 1.3   "Space Grotesk"
Body:       15-18px  400  letterSpacing: 0     lineHeight: 1.5   "Inter"
Caption:    13-14px  400  letterSpacing: 0     lineHeight: 1.4   "Inter"

CJK: use "Noto Sans SC/JP/KR", lineHeight >= 1.3, letterSpacing: 0 always.

Color

Primary text:   #111111       Secondary: #6B7280     Subtle: #9CA3AF
Background:     #FFFFFF       Surface:   #F9FAFB     Border: #E5E7EB

Max 2 saturated colors. WCAG AA: 4.5:1 body, 3:1 large. Dark bg: #0F172A, not #000000.

Spacing (8px grid)

Related:    8-16px     Components: 16-24px
Groups:     24-32px    Sections:   48-80px    Page padding: 80px

Shadows

// Subtle (cards)
{ "type": "shadow", "offsetY": 1, "blur": 3, "color": "rgba(0,0,0,0.05)" }
// Medium (dropdowns)
{ "type": "shadow", "offsetY": 4, "blur": 12, "color": "rgba(0,0,0,0.08)" }
// Elevated (modals)
{ "type": "shadow", "offsetY": 8, "blur": 24, "spread": -4, "color": "rgba(0,0,0,0.12)" }

Copy Rules

Headlines: 2-6 words. Subtitles: max 15 words. Buttons: 1-3 words. No lorem ipsum. No emoji as icons.

Layered Workflow

For complex multi-section pages, use the three-step skeleton → content → refine flow:

| Step | MCP Tool | CLI Equivalent | |------|----------|----------------| | 1. Create section structure | design_skeleton | op design:skeleton '' | | 2. Populate each section | design_content (with postProcess: true) | op design:content '' | | 3. Validate + auto-fix | design_refine | op design:refine --root-id |

design:refine resolves icon names → SVG paths, fixes layout issues, and validates the tree. Always run as the final step.

Codegen Pipeline

For incremental, framework-aware code generation from the design tree:

| Step | CLI Command | MCP Tool | Description | |------|------------|----------|-------------| | 1. Plan | op codegen:plan '' | codegen_plan | Declare framework, root node IDs, and options | | 2. Submit | op codegen:submit '' | codegen_submit_chunk | Submit generated code for individual nodes | | 3. Assemble | op codegen:assemble --framework react | codegen_assemble | Combine all chunks into the final output | | 4. Clean | op codegen:clean | codegen_clean | Clear server-side codegen state |

The plan JSON shape:

{ "framework": "react", "root

…

## Source & license

This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.

- **Author:** [ZSeven-W](https://github.com/ZSeven-W)
- **Source:** [ZSeven-W/openpencil-skill](https://github.com/ZSeven-W/openpencil-skill)
- **License:** MIT

Install and usage instructions live in the source repository linked above.

Reviews

No reviews yet, be the first.

Versions

  • v0.1.0 Imported from the upstream source.