@zseven-w/openpencil 0.7.1 → 0.7.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/openpencil-cli.cjs
CHANGED
|
@@ -11923,6 +11923,8 @@ function mapSingleFill(paint) {
|
|
|
11923
11923
|
type: "image",
|
|
11924
11924
|
url,
|
|
11925
11925
|
mode: mapScaleMode(paint.imageScaleMode),
|
|
11926
|
+
originalSize: normalizeOriginalSize(paint.originalImageWidth, paint.originalImageHeight),
|
|
11927
|
+
transform: normalizeImageTransform(paint.transform),
|
|
11926
11928
|
opacity: paint.opacity
|
|
11927
11929
|
};
|
|
11928
11930
|
}
|
|
@@ -11934,6 +11936,26 @@ function gradientAngleFromTransform(m) {
|
|
|
11934
11936
|
const mathAngle = Math.atan2(m.m10, m.m00) * (180 / Math.PI);
|
|
11935
11937
|
return Math.round(90 - mathAngle);
|
|
11936
11938
|
}
|
|
11939
|
+
function normalizeOriginalSize(width, height) {
|
|
11940
|
+
if (typeof width !== "number" || typeof height !== "number" || !Number.isFinite(width) || !Number.isFinite(height) || width <= 0 || height <= 0) {
|
|
11941
|
+
return void 0;
|
|
11942
|
+
}
|
|
11943
|
+
return { width, height };
|
|
11944
|
+
}
|
|
11945
|
+
function normalizeImageTransform(transform) {
|
|
11946
|
+
if (!transform) return void 0;
|
|
11947
|
+
if (Math.abs(transform.m00 - 1) <= IMAGE_TRANSFORM_EPSILON && Math.abs(transform.m01) <= IMAGE_TRANSFORM_EPSILON && Math.abs(transform.m02) <= IMAGE_TRANSFORM_EPSILON && Math.abs(transform.m10) <= IMAGE_TRANSFORM_EPSILON && Math.abs(transform.m11 - 1) <= IMAGE_TRANSFORM_EPSILON && Math.abs(transform.m12) <= IMAGE_TRANSFORM_EPSILON) {
|
|
11948
|
+
return void 0;
|
|
11949
|
+
}
|
|
11950
|
+
return {
|
|
11951
|
+
m00: transform.m00,
|
|
11952
|
+
m01: transform.m01,
|
|
11953
|
+
m02: transform.m02,
|
|
11954
|
+
m10: transform.m10,
|
|
11955
|
+
m11: transform.m11,
|
|
11956
|
+
m12: transform.m12
|
|
11957
|
+
};
|
|
11958
|
+
}
|
|
11937
11959
|
function mapScaleMode(mode) {
|
|
11938
11960
|
switch (mode) {
|
|
11939
11961
|
case "FIT":
|
|
@@ -11944,11 +11966,13 @@ function mapScaleMode(mode) {
|
|
|
11944
11966
|
return "fill";
|
|
11945
11967
|
}
|
|
11946
11968
|
}
|
|
11969
|
+
var IMAGE_TRANSFORM_EPSILON;
|
|
11947
11970
|
var init_figma_fill_mapper = __esm({
|
|
11948
11971
|
"packages/pen-figma/src/figma-fill-mapper.ts"() {
|
|
11949
11972
|
"use strict";
|
|
11950
11973
|
init_define_import_meta_env();
|
|
11951
11974
|
init_figma_color_utils();
|
|
11975
|
+
IMAGE_TRANSFORM_EPSILON = 1e-6;
|
|
11952
11976
|
}
|
|
11953
11977
|
});
|
|
11954
11978
|
|
|
@@ -13388,7 +13412,7 @@ var init_skill_bundle = __esm({
|
|
|
13388
13412
|
skill_bundle_default = {
|
|
13389
13413
|
version: "0.7.0",
|
|
13390
13414
|
files: {
|
|
13391
|
-
"skills/openpencil-design/SKILL.md": '---\nname: openpencil-design\ndescription: Use when designing UI with OpenPencil \u2014 creating layouts via op CLI, batch design DSL, or MCP tools. Covers PenNode schema, semantic roles, typography, color, spacing, and common component patterns.\n---\n\n# OpenPencil Design\n\nGenerate 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.\n\n## When to Use\n\n- Creating or modifying UI designs in `.op` files\n- Using the `op` CLI to script design operations\n- Designing via MCP tools (`batch_design`, `insert_node`, `design_skeleton`)\n- Need reference for PenNode schema, roles, or layout rules\n\n## Quick Reference \u2014 `op` CLI\n\n```bash\n# App control\nop start [--desktop|--web] # Launch app\nop stop # Stop running instance\nop status # Check if running\n\n# Document\nop open [file.op] # Open file or connect to live canvas\nop save <file.op> # Save current document\nop get [--depth N] [--pretty] # Get document tree\nop selection [--depth N] # Get current canvas selection\nop read-nodes [id...] [--depth N] [--vars] # Read node subtree(s) with optional variable resolution\nop layout [--parent P] [--depth N] # Snapshot layout tree with computed positions\nop find-space [--direction D] [--width N] [--height N] # Find empty space on canvas\n\n# Node operations\nop insert \'<json>\' [--parent P] # Insert node (--index N, --post-process)\nop update <id> \'<json>\' # Update node\nop delete <id> # Delete node\nop move <id> <parent> [index] # Move node\nop copy <id> <parent> # Deep-copy node\nop replace <id> \'<json>\' # Replace node\n\n# Batch design\nop design \'<dsl>\' # Batch design DSL (inline, @file, or stdin) [--canvas-width N]\n\n# Layered workflow\nop design:skeleton \'<json>\' # Create section structure\nop design:content <id> \'<json>\' # Populate section content\nop design:refine --root-id <id> # Validate + auto-fix (resolves icons) [--canvas-width N]\n\n# Import\nop import:svg <file.svg> [--parent P] # Import SVG as editable nodes\nop import:figma <file.fig> [--out out.op] # Convert Figma .fig to .op document\n\n# Pages\nop page list # List all pages\nop page add [--name N] # Add a new page\nop page remove <id> # Remove a page\nop page rename <id> \'<name>\' # Rename a page\nop page reorder <id> <index> # Move page to position\nop page duplicate <id> # Clone page with new IDs\n\n# Variables & Themes\nop vars / op vars:set \'<json>\' # Variables (--replace to replace all)\nop themes / op themes:set \'<json>\' # Themes (--replace to replace all)\nop theme:save <file.optheme> # Save current theme as preset file\nop theme:load <file.optheme> # Load a theme preset file\nop theme:list <directory> # List .optheme presets in directory\n\n# Codegen pipeline\nop codegen:plan \'<json>\' # Submit codegen plan (framework, rootIds, options)\nop codegen:submit \'<json>\' # Submit a code chunk for a node\nop codegen:assemble [--framework F] # Assemble all submitted chunks into final output\nop codegen:clean # Clear codegen state\n```\n\nGlobal flags: `--file <path>`, `--page <id>`, `--pretty`. Inputs: inline string, `@filepath`, or `-` (stdin).\n\n## Building Designs \u2014 Two Approaches\n\n### Approach 1: `op insert` (Recommended)\n\nThe 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.\n\n```bash\n# Create root frame, capture its ID\nROOT=$(op insert \'{"type":"frame","name":"Page","width":375,"height":812,"layout":"vertical"}\' \\\n | python3 -c "import sys,json; print(json.load(sys.stdin)[\'nodeId\'])")\n\n# Insert children using --parent\nop insert --parent "$ROOT" \'{"type":"text","content":"Hello","fontSize":28,"fontWeight":700}\'\n\n# Post-process: resolve icons, validate layout\nop design:refine --root-id "$ROOT"\n```\n\n### Approach 2: Batch Design DSL\n\nOne operation per line. Bind results with `name=` for later reference. Best for simple, flat structures.\n\n> **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`.\n\n```\nroot=I(null, { "type": "frame", "width": 1200, "layout": "vertical" })\nnav=I(root, { "type": "frame", "role": "navbar", "height": 72 })\nU(nav, { "fill": [{"type": "solid", "color": "#FFFFFF"}] })\ncard2=C(card1, grid, { "name": "Card 2" })\nM(sidebar, main, 0)\nD(old_section)\nR(old_btn, { "type": "rectangle", "role": "button" })\n```\n\n| Op | Syntax | Action |\n|----|--------|--------|\n| `I` | `name=I(parent, { node })` | Insert |\n| `U` | `U(ref, { updates })` | Update |\n| `C` | `name=C(source, parent, { overrides })` | Copy |\n| `R` | `name=R(ref, { node })` | Replace |\n| `M` | `M(ref, parent, index?)` | Move |\n| `D` | `D(ref)` | Delete |\n| `G` | `name=G(parent, "search", "query")` | Generate image via search |\n\n**DSL safe pattern** \u2014 always insert parent and children separately:\n\n```\nbtn=I(form, {"type":"rectangle","role":"button","width":"fill_container","height":50,"cornerRadius":12,"fill":[{"type":"solid","color":"#111111"}],"layout":"horizontal","justifyContent":"center","alignItems":"center"})\nI(btn, {"type":"text","content":"Submit","fontSize":16,"fontWeight":600,"fill":[{"type":"solid","color":"#FFFFFF"}]})\n```\n\n## PenNode Schema\n\n### Common Properties\n\n```json\n{\n "type": "frame|rectangle|text|ellipse|line|polygon|path|image|icon_font|group|ref",\n "name": "Display Name",\n "role": "semantic-role",\n "x": 0, "y": 0,\n "rotation": 0, "opacity": 1, "visible": true\n}\n```\n\n### Container Properties (frame, rectangle, group, ellipse)\n\n```json\n{\n "width": 400, // number | "fill_container" | "fit_content"\n "height": 300,\n "layout": "vertical", // "none" | "vertical" | "horizontal"\n "gap": 16,\n "padding": [16, 24], // number | [v, h] | [top, right, bottom, left]\n "justifyContent": "center", // "start" | "center" | "end" | "space_between" | "space_around"\n "alignItems": "center", // "start" | "center" | "end"\n "clipContent": true,\n "cornerRadius": 12, // number | [tl, tr, br, bl]\n "fill": [{ "type": "solid", "color": "#FFFFFF" }],\n "stroke": { "thickness": 1, "fill": [{ "type": "solid", "color": "#E5E7EB" }], "align": "inside", "dashPattern": [5, 3] },\n "effects": [{ "type": "shadow", "offsetX": 0, "offsetY": 4, "blur": 12, "spread": 0, "color": "rgba(0,0,0,0.08)" }],\n "children": []\n}\n```\n\n### Text\n\n```json\n{\n "type": "text",\n "content": "Hello", // string or StyledTextSegment[]\n "fontSize": 16, "fontFamily": "Inter", "fontWeight": 600,\n "textAlign": "center", // "left" | "center" | "right"\n "textGrowth": "fixed-width", // "auto" | "fixed-width" | "fixed-width-height"\n "lineHeight": 1.5, "letterSpacing": 0,\n "fill": [{ "type": "solid", "color": "#111111" }]\n}\n```\n\nRich text: `"content": [{ "text": "Bold ", "fontWeight": "bold" }, { "text": "normal" }]`\n\n### Path (Icons)\n\n```json\n{ "type": "path", "name": "HeartIcon", "width": 24, "height": 24,\n "fill": [{ "type": "solid", "color": "#111111" }] }\n```\n\nPascalCase + "Icon" suffix. Auto-resolved from Lucide set. Common: `SearchIcon`, `MenuIcon`, `HomeIcon`, `UserIcon`, `SettingsIcon`, `MailIcon`, `HeartIcon`, `StarIcon`, `CheckIcon`, `XIcon`, `ChevronRightIcon`, `ArrowRightIcon`, `ZapIcon`, `ShieldIcon`, `CodeIcon`, `LockIcon`, `SparklesIcon`, `PlayIcon`, `BellIcon`, `EyeIcon`, `DownloadIcon`, `PlusIcon`, `GlobeIcon`, `LayersIcon`.\n\n> **Icon rendering requires post-processing.** After inserting path nodes, you MUST run `op design:refine --root-id <id>` or use `op insert --post-process` to resolve icon names into actual SVG paths. Without this step, icons will exist in the tree but not render visually. Lucide icons use stroke rendering \u2014 the engine will clear `fill` and set `stroke` automatically during post-processing.\n\n### Image\n\n```json\n{ "type": "image", "src": "https://example.com/photo.jpg", "width": 400, "height": 300,\n "objectFit": "crop", "cornerRadius": 12 }\n```\n\nAI image placeholders (resolved by `design:refine`):\n\n```json\n{ "type": "image", "width": 400, "height": 300,\n "imagePrompt": "A modern office workspace with natural light",\n "imageSearchQuery": "modern office workspace" }\n```\n\nImage adjustments (all -100 to 100): `exposure`, `contrast`, `saturation`, `temperature`, `tint`, `highlights`, `shadows`.\n\n### Polygon\n\n```json\n{ "type": "polygon", "polygonCount": 6, "width": 80, "height": 80, "cornerRadius": 4,\n "fill": [{ "type": "solid", "color": "#6366F1" }] }\n```\n\n### Icon Font\n\n```json\n{ "type": "icon_font", "iconFontName": "lucide:home", "width": 24, "height": 24,\n "fill": [{ "type": "solid", "color": "#111111" }] }\n```\n\n### Line\n\n```json\n{ "type": "line", "x2": 200, "y2": 0,\n "stroke": { "thickness": 1, "fill": [{ "type": "solid", "color": "#E5E7EB" }] } }\n```\n\n### Fill Types\n\n```json\n{ "type": "solid", "color": "#3B82F6" }\n{ "type": "linear_gradient", "angle": 135,\n "stops": [{ "offset": 0, "color": "#6366F1" }, { "offset": 1, "color": "#8B5CF6" }] }\n{ "type": "radial_gradient", "cx": 0.5, "cy": 0.5, "radius": 0.5,\n "stops": [{ "offset": 0, "color": "#FFF" }, { "offset": 1, "color": "#000" }] }\n{ "type": "image", "url": "https://example.com/texture.jpg", "mode": "fill" }\n```\n\nImage fill modes: `fill`, `fit`, `crop`, `tile`, `stretch`. Image fill also supports adjustment filters (`exposure`, `contrast`, `saturation`, etc.).\n\n### Ref Node (Component Instance)\n\n```json\n{ "type": "ref", "ref": "reusable-frame-id",\n "descendants": { "child-id": { "content": "Override text" } } }\n```\n\nReferences a `frame` with `reusable: true`. Override specific descendant properties via `descendants`.\n\n### Design Variables\n\nReference with `$` prefix: `"color": "$primaryColor"`, `"gap": "$spacing"`.\n\n## Semantic Roles\n\nRoles declare intent \u2014 the engine applies smart defaults. Always prefer roles over manual styling.\n\n| Category | Roles |\n|----------|-------|\n| **Layout** | `section`, `row`, `column`, `centered-content`, `divider`, `spacer` |\n| **Navigation** | `navbar`, `nav-links`, `nav-link` |\n| **Interactive** | `button`, `icon-button`, `badge`, `tag`, `pill`, `input`, `form-input`, `search-bar` |\n| **Cards** | `card`, `feature-card`, `stat-card`, `pricing-card`, `image-card` |\n| **Content** | `hero`, `feature-grid`, `cta-section`, `footer`, `testimonial`, `stats-section` |\n| **Typography** | `heading`, `subheading`, `body-text`, `caption`, `label` |\n| **Media** | `avatar`, `icon`, `phone-mockup`, `screenshot-frame` |\n| **Table** | `table`, `table-row`, `table-header`, `table-cell` |\n| **Form** | `form-group` |\n\nKey defaults:\n- `navbar` \u2192 height: 56-72, horizontal, space_between, center-aligned\n- `button` \u2192 padding: [12, 24], cornerRadius: 8, centered\n- `card` \u2192 vertical, gap: 12, cornerRadius: 12, padding: 24\n- `heading` \u2192 lineHeight: 1.2, letterSpacing: -0.5\n- `body-text` \u2192 fill_container, textGrowth: fixed-width, lineHeight: 1.5\n\n## Layout Rules\n\n1. **NEVER set x/y on children inside layout containers** \u2014 engine positions them\n2. **Siblings must use same width strategy** \u2014 all `fill_container` or all fixed\n3. **NEVER `fill_container` inside `fit_content` parent** \u2014 circular dependency\n4. Cards in horizontal row: ALL `width: "fill_container"`, `height: "fill_container"`\n\n### Sizing Decision\n\n| Question | Answer |\n|----------|--------|\n| Stretch to fill? | `"fill_container"` |\n| Shrink to content? | `"fit_content"` |\n| Exact size? | number (px) |\n\n### Design Type Sizing\n\n| Type | Width | Height |\n|------|-------|--------|\n| Landing page | 1200 | 0 (auto) |\n| Mobile screen | 375 | 812 |\n| Dashboard | 1200 | 0 (auto) |\n\n## Design Principles\n\n### Typography\n\n```\nDisplay: 40-56px 700 letterSpacing: -1.5 lineHeight: 1.1 "Space Grotesk"\nHeading: 28-36px 700 letterSpacing: -0.5 lineHeight: 1.2 "Space Grotesk"\nSubheading: 20-24px 600 letterSpacing: -0.25 lineHeight: 1.3 "Space Grotesk"\nBody: 15-18px 400 letterSpacing: 0 lineHeight: 1.5 "Inter"\nCaption: 13-14px 400 letterSpacing: 0 lineHeight: 1.4 "Inter"\n```\n\nCJK: use `"Noto Sans SC/JP/KR"`, lineHeight >= 1.3, letterSpacing: 0 always.\n\n### Color\n\n```\nPrimary text: #111111 Secondary: #6B7280 Subtle: #9CA3AF\nBackground: #FFFFFF Surface: #F9FAFB Border: #E5E7EB\n```\n\nMax 2 saturated colors. WCAG AA: 4.5:1 body, 3:1 large. Dark bg: `#0F172A`, not `#000000`.\n\n### Spacing (8px grid)\n\n```\nRelated: 8-16px Components: 16-24px\nGroups: 24-32px Sections: 48-80px Page padding: 80px\n```\n\n### Shadows\n\n```json\n// Subtle (cards)\n{ "type": "shadow", "offsetY": 1, "blur": 3, "color": "rgba(0,0,0,0.05)" }\n// Medium (dropdowns)\n{ "type": "shadow", "offsetY": 4, "blur": 12, "color": "rgba(0,0,0,0.08)" }\n// Elevated (modals)\n{ "type": "shadow", "offsetY": 8, "blur": 24, "spread": -4, "color": "rgba(0,0,0,0.12)" }\n```\n\n### Copy Rules\n\nHeadlines: 2-6 words. Subtitles: max 15 words. Buttons: 1-3 words. No lorem ipsum. No emoji as icons.\n\n## Layered Workflow\n\nFor complex multi-section pages, use the three-step skeleton \u2192 content \u2192 refine flow:\n\n| Step | MCP Tool | CLI Equivalent |\n|------|----------|----------------|\n| 1. Create section structure | `design_skeleton` | `op design:skeleton \'<json>\'` |\n| 2. Populate each section | `design_content` (with `postProcess: true`) | `op design:content <section-id> \'<json>\'` |\n| 3. Validate + auto-fix | `design_refine` | `op design:refine --root-id <id>` |\n\n`design:refine` resolves icon names \u2192 SVG paths, fixes layout issues, and validates the tree. **Always run as the final step.**\n\n## Codegen Pipeline\n\nFor incremental, framework-aware code generation from the design tree:\n\n| Step | CLI Command | MCP Tool | Description |\n|------|------------|----------|-------------|\n| 1. Plan | `op codegen:plan \'<json>\'` | `codegen_plan` | Declare framework, root node IDs, and options |\n| 2. Submit | `op codegen:submit \'<json>\'` | `codegen_submit_chunk` | Submit generated code for individual nodes |\n| 3. Assemble | `op codegen:assemble --framework react` | `codegen_assemble` | Combine all chunks into the final output |\n| 4. Clean | `op codegen:clean` | `codegen_clean` | Clear server-side codegen state |\n\nThe plan JSON shape:\n```json\n{ "framework": "react", "rootIds": ["frame-1"], "options": { "tailwind": true } }\n```\n\nThe submit JSON shape:\n```json\n{ "nodeId": "card-1", "code": "<Card className=\\"...\\">...</Card>", "imports": ["Card"] }\n```\n\nSupported frameworks: `react`, `html`, `vue`, `svelte`, `flutter`, `swiftui`, `compose`, `rn` (React Native), `css`.\n\n## Multi-Page Documents\n\n```bash\nop page list # List all pages with IDs\nop page add --name "Settings" # Add a new page\nop page remove <page-id> # Remove a page\nop page rename <page-id> \'New Name\' # Rename a page\nop page reorder <page-id> 2 # Move page to index 2\nop page duplicate <page-id> # Clone page with new IDs\n```\n\nUse `--page <id>` on any command to target a specific page. Without it, commands operate on the first page.\n\n## Common Patterns\n\nPatterns below show `op insert --parent` commands. Each pattern is copy-paste ready.\n\n### Navbar\n\n```bash\nNAV=$(op insert --parent "$ROOT" \'{"type":"frame","role":"navbar","width":"fill_container","height":72,"layout":"horizontal","padding":[0,80],"justifyContent":"space_between","alignItems":"center","fill":[{"type":"solid","color":"#FFFFFF"}],"stroke":{"thickness":1,"fill":[{"type":"solid","color":"#F3F4F6"}]}}\' | ID)\nop insert --parent "$NAV" \'{"type":"text","content":"Brand","fontSize":20,"fontWeight":700,"fontFamily":"Space Grotesk"}\'\nLINKS=$(op insert --parent "$NAV" \'{"type":"frame","role":"nav-links","layout":"horizontal","gap":32,"width":"fit_content","height":"fit_content"}\' | ID)\nop insert --parent "$LINKS" \'{"type":"text","role":"nav-link","content":"Features","fontSize":15}\'\nop insert --parent "$LINKS" \'{"type":"text","role":"nav-link","content":"Pricing","fontSize":15}\'\nCTA=$(op insert --parent "$NAV" \'{"type":"rectangle","role":"button","padding":[10,24],"cornerRadius":8,"fill":[{"type":"solid","color":"#111111"}],"layout":"horizontal","justifyContent":"center","alignItems":"center"}\' | ID)\nop insert --parent "$CTA" \'{"type":"text","content":"Get Started","fontSize":14,"fontWeight":600,"fill":[{"type":"solid","color":"#FFFFFF"}]}\'\n```\n\n### Hero\n\n```bash\nHERO=$(op insert --parent "$ROOT" \'{"type":"frame","role":"hero","width":"fill_container","height":"fit_content","layout":"vertical","padding":[100,80],"gap":24,"alignItems":"center"}\' | ID)\nop insert --parent "$HERO" \'{"type":"text","role":"heading","content":"Build something great","fontSize":56,"fontWeight":700,"fontFamily":"Space Grotesk","textAlign":"center","letterSpacing":-1.5,"lineHeight":1.1,"textGrowth":"fixed-width","width":800}\'\nop insert --parent "$HERO" \'{"type":"text","role":"subheading","content":"The modern platform for teams who ship fast.","fontSize":18,"textAlign":"center","lineHeight":1.6,"textGrowth":"fixed-width","width":560,"fill":[{"type":"solid","color":"#6B7280"}]}\'\nBTNS=$(op insert --parent "$HERO" \'{"type":"frame","layout":"horizontal","gap":12,"width":"fit_content","height":"fit_content"}\' | ID)\nB1=$(op insert --parent "$BTNS" \'{"type":"rectangle","role":"button","padding":[14,32],"cornerRadius":10,"fill":[{"type":"solid","color":"#111111"}],"layout":"horizontal","justifyContent":"center","alignItems":"center"}\' | ID)\nop insert --parent "$B1" \'{"type":"text","content":"Start Free","fontSize":16,"fontWeight":600,"fill":[{"type":"solid","color":"#FFFFFF"}]}\'\nB2=$(op insert --parent "$BTNS" \'{"type":"rectangle","role":"button","padding":[14,32],"cornerRadius":10,"fill":[{"type":"solid","color":"#F3F4F6"}],"layout":"horizontal","justifyContent":"center","alignItems":"center"}\' | ID)\nop insert --parent "$B2" \'{"type":"text","content":"View Demo","fontSize":16,"fontWeight":600}\'\n```\n\n### Feature Card (in horizontal grid, ALL cards must use fill_container)\n\n```bash\nCARD=$(op insert --parent "$GRID" \'{"type":"rectangle","role":"feature-card","width":"fill_container","height":"fill_container","layout":"vertical","padding":28,"gap":16,"cornerRadius":16,"fill":[{"type":"solid","color":"#F9FAFB"}]}\' | ID)\nop insert --parent "$CARD" \'{"type":"path","name":"ZapIcon","width":24,"height":24,"fill":[{"type":"solid","color":"#111111"}]}\'\nop insert --parent "$CARD" \'{"type":"text","content":"Lightning Fast","fontSize":20,"fontWeight":600}\'\nop insert --parent "$CARD" \'{"type":"text","role":"body-text","content":"Sub-second builds with smart caching.","fontSize":15,"lineHeight":1.6,"fill":[{"type":"solid","color":"#6B7280"}]}\'\n```\n\n### Form Input\n\n```bash\nGRP=$(op insert --parent "$FORM" \'{"type":"frame","role":"form-group","layout":"vertical","gap":8,"width":"fill_container"}\' | ID)\nop insert --parent "$GRP" \'{"type":"text","role":"label","content":"Email","fontSize":14,"fontWeight":500}\'\nINP=$(op insert --parent "$GRP" \'{"type":"rectangle","role":"form-input","width":"fill_container","height":48,"cornerRadius":10,"layout":"horizontal","padding":[0,16],"gap":10,"alignItems":"center","fill":[{"type":"solid","color":"#F9FAFB"}],"stroke":{"thickness":1,"fill":[{"type":"solid","color":"#E5E7EB"}]}}\' | ID)\nop insert --parent "$INP" \'{"type":"path","name":"MailIcon","width":18,"height":18,"fill":[{"type":"solid","color":"#9CA3AF"}]}\'\nop insert --parent "$INP" \'{"type":"text","content":"you@example.com","fontSize":15,"fill":[{"type":"solid","color":"#9CA3AF"}]}\'\n```\n\n### Footer\n\n```bash\nFOOTER=$(op insert --parent "$ROOT" \'{"type":"frame","role":"footer","width":"fill_container","height":"fit_content","layout":"horizontal","padding":[48,80],"gap":80,"fill":[{"type":"solid","color":"#F9FAFB"}]}\' | ID)\nCOL1=$(op insert --parent "$FOOTER" \'{"type":"frame","layout":"vertical","gap":16,"width":240}\' | ID)\nop insert --parent "$COL1" \'{"type":"text","content":"Brand","fontSize":20,"fontWeight":700,"fontFamily":"Space Grotesk"}\'\nop insert --parent "$COL1" \'{"type":"text","content":"Building the future of design.","fontSize":14,"lineHeight":1.6,"fill":[{"type":"solid","color":"#6B7280"}]}\'\nCOL2=$(op insert --parent "$FOOTER" \'{"type":"frame","layout":"vertical","gap":12,"width":"fit_content"}\' | ID)\nop insert --parent "$COL2" \'{"type":"text","content":"Product","fontSize":14,"fontWeight":600}\'\nop insert --parent "$COL2" \'{"type":"text","content":"Features","fontSize":14,"fill":[{"type":"solid","color":"#6B7280"}]}\'\nop insert --parent "$COL2" \'{"type":"text","content":"Pricing","fontSize":14,"fill":[{"type":"solid","color":"#6B7280"}]}\'\n```\n\n## Common Mistakes\n\n| Mistake | Fix |\n|---------|-----|\n| Setting x/y inside layout container | Remove x/y \u2014 engine auto-positions |\n| Cards with different width strategies | All siblings: same sizing (`fill_container`) |\n| `fill_container` child in `fit_content` parent | Use fixed width or switch parent to `fill_container` |\n| Pure black text `#000000` | Use `#111111` or `#0F172A` |\n| Heavy drop shadows | Use subtle `rgba(0,0,0,0.05-0.12)` |\n| Emoji as icons | Use path nodes with icon names |\n| Lorem ipsum placeholder | Write realistic, concise copy |\n| Fixed height on text | Use `textGrowth: "fixed-width"` instead |\n| Space Grotesk for CJK | Use `"Noto Sans SC/JP/KR"` |\n| Negative letterSpacing on CJK | Always 0 for CJK text |\n| Missing post-process after insert | Run `op design:refine --root-id <id>` after building the tree |\n| Icons inserted but not visible | Path nodes need `design:refine` or `--post-process` to resolve SVG |\n| Using DSL `I()` with inline `children` | DSL parser fails on nested JSON \u2014 insert parent and children separately |\n| Missing `postProcess: true` in MCP | Always set for MCP tool calls |\n\n## Full Example \u2014 `op insert` Workflow (Recommended)\n\nBuild a complete mobile login page using `op insert --parent`. This is the most reliable approach.\n\n```bash\n#!/bin/bash\nset -e\nID() { python3 -c "import sys,json; print(json.load(sys.stdin)[\'nodeId\'])"; }\n\n# Root frame (mobile)\nROOT=$(op insert \'{"type":"frame","name":"Login","width":375,"height":812,"layout":"vertical","fill":[{"type":"solid","color":"#FFFFFF"}]}\' | ID)\n\n# Header\nTOP=$(op insert --parent "$ROOT" \'{"type":"frame","width":"fill_container","height":"fit_content","layout":"vertical","padding":[80,32,40,32],"gap":14,"alignItems":"center"}\' | ID)\nop insert --parent "$TOP" \'{"type":"path","name":"ShieldIcon","width":48,"height":48,"fill":[{"type":"solid","color":"#6366F1"}]}\'\nop insert --parent "$TOP" \'{"type":"text","content":"Welcome Back","fontSize":28,"fontWeight":700,"fontFamily":"Space Grotesk","letterSpacing":-0.5,"textAlign":"center"}\'\n\n# Form\nFORM=$(op insert --parent "$ROOT" \'{"type":"frame","width":"fill_container","height":"fit_content","layout":"vertical","padding":[0,32],"gap":20}\' | ID)\n\n# Email input\nGRP=$(op insert --parent "$FORM" \'{"type":"frame","role":"form-group","layout":"vertical","gap":8,"width":"fill_container"}\' | ID)\nop insert --parent "$GRP" \'{"type":"text","role":"label","content":"Email","fontSize":14,"fontWeight":500}\'\nINP=$(op insert --parent "$GRP" \'{"type":"rectangle","role":"form-input","width":"fill_container","height":48,"cornerRadius":10,"layout":"horizontal","padding":[0,16],"gap":10,"alignItems":"center","fill":[{"type":"solid","color":"#F9FAFB"}],"stroke":{"thickness":1,"fill":[{"type":"solid","color":"#E5E7EB"}]}}\' | ID)\nop insert --parent "$INP" \'{"type":"path","name":"MailIcon","width":18,"height":18,"fill":[{"type":"solid","color":"#9CA3AF"}]}\'\nop insert --parent "$INP" \'{"type":"text","content":"you@example.com","fontSize":15,"fill":[{"type":"solid","color":"#9CA3AF"}]}\'\n\n# Login button\nBTN=$(op insert --parent "$FORM" \'{"type":"rectangle","role":"button","width":"fill_container","height":50,"cornerRadius":12,"fill":[{"type":"solid","color":"#111111"}],"layout":"horizontal","justifyContent":"center","alignItems":"center"}\' | ID)\nop insert --parent "$BTN" \'{"type":"text","content":"Sign In","fontSize":16,"fontWeight":600,"fill":[{"type":"solid","color":"#FFFFFF"}]}\'\n\n# IMPORTANT: resolve icons + validate layout\nop design:refine --root-id "$ROOT"\n```\n\n## DSL Example \u2014 Landing Page\n\nDSL is suitable for simpler structures. **Avoid inline `children`** \u2014 insert parent and children as separate operations.\n\n```\nroot=I(null, {"type":"frame","name":"Landing","width":1200,"height":0,"layout":"vertical","fill":[{"type":"solid","color":"#FFFFFF"}]})\n\nnav=I(root, {"type":"frame","role":"navbar","width":"fill_container","height":72,"layout":"horizontal","padding":[0,80],"justifyContent":"space_between","alignItems":"center"})\nI(nav, {"type":"text","content":"Acme","fontSize":20,"fontWeight":700,"fontFamily":"Space Grotesk"})\nlinks=I(nav, {"type":"frame","role":"nav-links","layout":"horizontal","gap":32,"width":"fit_content","height":"fit_content"})\nI(links, {"type":"text","role":"nav-link","content":"Features","fontSize":15})\nI(links, {"type":"text","role":"nav-link","content":"Pricing","fontSize":15})\ncta=I(nav, {"type":"rectangle","role":"button","padding":[10,24],"cornerRadius":8,"fill":[{"type":"solid","color":"#111111"}],"layout":"horizontal","justifyContent":"center","alignItems":"center"})\nI(cta, {"type":"text","content":"Get Started","fontSize":14,"fontWeight":600,"fill":[{"type":"solid","color":"#FFFFFF"}]})\n\nhero=I(root, {"type":"frame","role":"hero","width":"fill_container","height":"fit_content","layout":"vertical","padding":[100,80],"gap":24,"alignItems":"center"})\nI(hero, {"type":"text","role":"heading","content":"Ship faster with Acme","fontSize":56,"fontWeight":700,"fontFamily":"Space Grotesk","textAlign":"center","letterSpacing":-1.5,"lineHeight":1.1,"textGrowth":"fixed-width","width":800})\nI(hero, {"type":"text","role":"subheading","content":"Turn ideas into production apps in minutes.","fontSize":18,"textAlign":"center","lineHeight":1.6,"textGrowth":"fixed-width","width":560,"fill":[{"type":"solid","color":"#6B7280"}]})\nbtns=I(hero, {"type":"frame","layout":"horizontal","gap":12,"width":"fit_content","height":"fit_content"})\nb1=I(btns, {"type":"rectangle","role":"button","padding":[14,32],"cornerRadius":10,"fill":[{"type":"solid","color":"#111111"}],"layout":"horizontal","justifyContent":"center","alignItems":"center"})\nI(b1, {"type":"text","content":"Start Free","fontSize":16,"fontWeight":600,"fill":[{"type":"solid","color":"#FFFFFF"}]})\nb2=I(btns, {"type":"rectangle","role":"button","padding":[14,32],"cornerRadius":10,"fill":[{"type":"solid","color":"#F3F4F6"}],"layout":"horizontal","justifyContent":"center","alignItems":"center"})\nI(b2, {"type":"text","content":"View Demo","fontSize":16,"fontWeight":600})\n\nfeat=I(root, {"type":"frame","role":"section","width":"fill_container","height":"fit_content","layout":"vertical","padding":[80,80],"gap":48,"alignItems":"center"})\nI(feat, {"type":"text","role":"heading","content":"Everything you need","fontSize":36,"fontWeight":700,"fontFamily":"Space Grotesk","textAlign":"center","letterSpacing":-0.5})\ngrid=I(feat, {"type":"frame","role":"feature-grid","width":"fill_container","layout":"horizontal","gap":24})\nc1=I(grid, {"type":"rectangle","role":"feature-card","width":"fill_container","height":"fill_container","layout":"vertical","padding":28,"gap":16,"cornerRadius":16,"fill":[{"type":"solid","color":"#F9FAFB"}]})\nI(c1, {"type":"path","name":"ZapIcon","width":24,"height":24,"fill":[{"type":"solid","color":"#111111"}]})\nI(c1, {"type":"text","content":"Lightning Fast","fontSize":20,"fontWeight":600})\nI(c1, {"type":"text","role":"body-text","content":"Sub-second builds with smart caching.","fontSize":15,"lineHeight":1.6,"fill":[{"type":"solid","color":"#6B7280"}]})\nc2=C(c1, grid, {})\nU(c2+"/0", {"name":"ShieldIcon"})\nU(c2+"/1", {"content":"Enterprise Security"})\nU(c2+"/2", {"content":"SOC 2 certified with end-to-end encryption."})\nc3=C(c1, grid, {})\nU(c3+"/0", {"name":"GitBranchIcon"})\nU(c3+"/1", {"content":"Git-Native Workflow"})\nU(c3+"/2", {"content":"Preview deploys on every push with instant rollback."})\n```\n',
|
|
13415
|
+
"skills/openpencil-design/SKILL.md": '---\nname: openpencil-design\ndescription: Use when designing UI with OpenPencil \u2014 creating layouts via op CLI, batch design DSL, or MCP tools. Covers PenNode schema, semantic roles, typography, color, spacing, and common component patterns.\n---\n\n# OpenPencil Design\n\nGenerate 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.\n\n## When to Use\n\n- Creating or modifying UI designs in `.op` files\n- Using the `op` CLI to script design operations\n- Designing via MCP tools (`batch_design`, `insert_node`, `design_skeleton`)\n- Need reference for PenNode schema, roles, or layout rules\n\n## Quick Reference \u2014 `op` CLI\n\n```bash\n# App control\nop start [--desktop|--web] # Launch app\nop stop # Stop running instance\nop status # Check if running\n\n# Document\nop open [file.op] # Open file or connect to live canvas\nop save <file.op> # Save current document\nop get [--depth N] [--pretty] # Get document tree\nop selection [--depth N] # Get current canvas selection\nop read-nodes [id...] [--depth N] [--vars] # Read node subtree(s) with optional variable resolution\nop layout [--parent P] [--depth N] # Snapshot layout tree with computed positions\nop find-space [--direction D] [--width N] [--height N] # Find empty space on canvas\n\n# Node operations\nop insert \'<json>\' [--parent P] # Insert node (--index N, --post-process)\nop update <id> \'<json>\' # Update node\nop delete <id> # Delete node\nop move <id> <parent> [index] # Move node\nop copy <id> <parent> # Deep-copy node\nop replace <id> \'<json>\' # Replace node\n\n# Batch design\nop design \'<dsl>\' # Batch design DSL (inline, @file, or stdin) [--canvas-width N]\n\n# Layered workflow\nop design:skeleton \'<json>\' # Create section structure\nop design:content <id> \'<json>\' # Populate section content\nop design:refine --root-id <id> # Validate + auto-fix (resolves icons) [--canvas-width N]\n\n# Import\nop import:svg <file.svg> [--parent P] # Import SVG as editable nodes\nop import:figma <file.fig> [--out out.op] # Convert Figma .fig to .op document\n\n# Pages\nop page list # List all pages\nop page add [--name N] # Add a new page\nop page remove <id> # Remove a page\nop page rename <id> \'<name>\' # Rename a page\nop page reorder <id> <index> # Move page to position\nop page duplicate <id> # Clone page with new IDs\n\n# Variables & Themes\nop vars / op vars:set \'<json>\' # Variables (--replace to replace all)\nop themes / op themes:set \'<json>\' # Themes (--replace to replace all)\nop theme:save <file.optheme> # Save current theme as preset file\nop theme:load <file.optheme> # Load a theme preset file\nop theme:list <directory> # List .optheme presets in directory\n\n# Codegen pipeline\nop codegen:plan \'<json>\' # Submit codegen plan (framework, rootIds, options)\nop codegen:submit \'<json>\' # Submit a code chunk for a node\nop codegen:assemble [--framework F] # Assemble all submitted chunks into final output\nop codegen:clean # Clear codegen state\n```\n\nGlobal flags: `--file <path>`, `--page <id>`, `--pretty`. Inputs: inline string, `@filepath`, or `-` (stdin).\n\n## Building Designs \u2014 Two Approaches\n\n### Approach 1: `op insert` (Recommended)\n\nThe 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.\n\n```bash\n# Create root frame, capture its ID\nROOT=$(op insert \'{"type":"frame","name":"Page","width":375,"height":812,"layout":"vertical"}\' \\\n | python3 -c "import sys,json; print(json.load(sys.stdin)[\'nodeId\'])")\n\n# Insert children using --parent\nop insert --parent "$ROOT" \'{"type":"text","content":"Hello","fontSize":28,"fontWeight":700}\'\n\n# Post-process: resolve icons, validate layout\nop design:refine --root-id "$ROOT"\n```\n\n### Approach 2: Batch Design DSL\n\nOne operation per line. Bind results with `name=` for later reference. Best for simple, flat structures.\n\n> **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`.\n\n```\nroot=I(null, { "type": "frame", "width": 1200, "layout": "vertical" })\nnav=I(root, { "type": "frame", "role": "navbar", "height": 72 })\nU(nav, { "fill": [{"type": "solid", "color": "#FFFFFF"}] })\ncard2=C(card1, grid, { "name": "Card 2" })\nM(sidebar, main, 0)\nD(old_section)\nR(old_btn, { "type": "rectangle", "role": "button" })\n```\n\n| Op | Syntax | Action |\n|----|--------|--------|\n| `I` | `name=I(parent, { node })` | Insert |\n| `U` | `U(ref, { updates })` | Update |\n| `C` | `name=C(source, parent, { overrides })` | Copy |\n| `R` | `name=R(ref, { node })` | Replace |\n| `M` | `M(ref, parent, index?)` | Move |\n| `D` | `D(ref)` | Delete |\n| `G` | `name=G(parent, "search", "query")` | Generate image via search |\n\n**DSL safe pattern** \u2014 always insert parent and children separately:\n\n```\nbtn=I(form, {"type":"rectangle","role":"button","width":"fill_container","height":50,"cornerRadius":12,"fill":[{"type":"solid","color":"#111111"}],"layout":"horizontal","justifyContent":"center","alignItems":"center"})\nI(btn, {"type":"text","content":"Submit","fontSize":16,"fontWeight":600,"fill":[{"type":"solid","color":"#FFFFFF"}]})\n```\n\n## STRICT JSON Rules\n\nWhen emitting PenNode JSON (via `op insert`, `op design`, `batch_design`, `insert_node`), you MUST produce strictly valid JSON. Common mistakes that break parsing:\n\n- **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 \u2014 double-check.\n- **Every key MUST be a double-quoted non-empty string.**\n- **`fill` is ALWAYS an array**: `"fill": [{"type": "solid", "color": "#hex"}]`. Shorthand like `"fill": "#hex"` works but the array form is the canonical shape.\n- **`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).\n- **NO trailing commas** before `}` or `]`.\n- **NO comments** inside JSON (`//` or `/* */`).\n- Use **straight double quotes** `"`, not smart/curly quotes.\n- **`content` for text, NOT `text`**: `{"type": "text", "content": "Hello"}`.\n- **`iconFontName` for icons, NOT `iconName` or `icon`**: `{"type": "icon_font", "iconFontName": "lock"}`.\n- Before finalizing the JSON, mentally verify: every key has a value, every value has a key, all brackets balance.\n\n## PenNode Schema\n\n### Common Properties\n\n```json\n{\n "type": "frame|rectangle|text|ellipse|line|polygon|path|image|icon_font|group|ref",\n "name": "Display Name",\n "role": "semantic-role",\n "x": 0, "y": 0,\n "rotation": 0, "opacity": 1, "visible": true\n}\n```\n\n### Container Properties (frame, rectangle, group, ellipse)\n\n```json\n{\n "width": 400, // number | "fill_container" | "fit_content"\n "height": 300,\n "layout": "vertical", // "none" | "vertical" | "horizontal"\n "gap": 16,\n "padding": [16, 24], // number | [v, h] | [top, right, bottom, left]\n "justifyContent": "center", // "start" | "center" | "end" | "space_between" | "space_around"\n "alignItems": "center", // "start" | "center" | "end"\n "clipContent": true,\n "cornerRadius": 12, // number | [tl, tr, br, bl]\n "fill": [{ "type": "solid", "color": "#FFFFFF" }],\n "stroke": { "thickness": 1, "fill": [{ "type": "solid", "color": "#E5E7EB" }], "align": "inside", "dashPattern": [5, 3] },\n "effects": [{ "type": "shadow", "offsetX": 0, "offsetY": 4, "blur": 12, "spread": 0, "color": "rgba(0,0,0,0.08)" }],\n "children": []\n}\n```\n\n### Text\n\n```json\n{\n "type": "text",\n "content": "Hello", // string or StyledTextSegment[]\n "fontSize": 16, "fontFamily": "Inter", "fontWeight": 600,\n "textAlign": "center", // "left" | "center" | "right"\n "textGrowth": "fixed-width", // "auto" | "fixed-width" | "fixed-width-height"\n "lineHeight": 1.5, "letterSpacing": 0,\n "fill": [{ "type": "solid", "color": "#111111" }]\n}\n```\n\nRich text: `"content": [{ "text": "Bold ", "fontWeight": "bold" }, { "text": "normal" }]`\n\n### Icons \u2014 Two Options\n\n#### Option A: `icon_font` (RECOMMENDED \u2014 renders directly, no post-processing needed)\n\n```json\n{ "type": "icon_font", "name": "Lock Icon", "iconFontName": "lock",\n "width": 20, "height": 20,\n "fill": [{ "type": "solid", "color": "#6B7280" }] }\n```\n\n**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`.\n\nWorks in ALL contexts: CLI, MCP tools, or direct `.op` files \u2014 no `design:refine` required.\n\n#### Option B: `path` (requires post-processing)\n\n```json\n{ "type": "path", "name": "HeartIcon", "width": 24, "height": 24,\n "fill": [{ "type": "solid", "color": "#111111" }] }\n```\n\nPascalCase + "Icon" suffix. Auto-resolved from Lucide set during post-processing.\n\n> **Path icons need post-processing.** After inserting path nodes, run `op design:refine --root-id <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 \u2014 **prefer `icon_font` in MCP contexts.**\n\n### Image\n\n```json\n{ "type": "image", "src": "https://example.com/photo.jpg", "width": 400, "height": 300,\n "objectFit": "crop", "cornerRadius": 12 }\n```\n\nAI image placeholders (resolved by `design:refine`):\n\n```json\n{ "type": "image", "width": 400, "height": 300,\n "imagePrompt": "A modern office workspace with natural light",\n "imageSearchQuery": "modern office workspace" }\n```\n\nImage adjustments (all -100 to 100): `exposure`, `contrast`, `saturation`, `temperature`, `tint`, `highlights`, `shadows`.\n\n### Polygon\n\n```json\n{ "type": "polygon", "polygonCount": 6, "width": 80, "height": 80, "cornerRadius": 4,\n "fill": [{ "type": "solid", "color": "#6366F1" }] }\n```\n\n### Icon Font\n\n```json\n{ "type": "icon_font", "iconFontName": "lucide:home", "width": 24, "height": 24,\n "fill": [{ "type": "solid", "color": "#111111" }] }\n```\n\n### Line\n\n```json\n{ "type": "line", "x2": 200, "y2": 0,\n "stroke": { "thickness": 1, "fill": [{ "type": "solid", "color": "#E5E7EB" }] } }\n```\n\n### Fill Types\n\n```json\n{ "type": "solid", "color": "#3B82F6" }\n{ "type": "linear_gradient", "angle": 135,\n "stops": [{ "offset": 0, "color": "#6366F1" }, { "offset": 1, "color": "#8B5CF6" }] }\n{ "type": "radial_gradient", "cx": 0.5, "cy": 0.5, "radius": 0.5,\n "stops": [{ "offset": 0, "color": "#FFF" }, { "offset": 1, "color": "#000" }] }\n{ "type": "image", "url": "https://example.com/texture.jpg", "mode": "fill" }\n```\n\nImage fill modes: `fill`, `fit`, `crop`, `tile`, `stretch`. Image fill also supports adjustment filters (`exposure`, `contrast`, `saturation`, etc.).\n\n### Ref Node (Component Instance)\n\n```json\n{ "type": "ref", "ref": "reusable-frame-id",\n "descendants": { "child-id": { "content": "Override text" } } }\n```\n\nReferences a `frame` with `reusable: true`. Override specific descendant properties via `descendants`.\n\n### Design Variables\n\nReference with `$` prefix: `"color": "$primaryColor"`, `"gap": "$spacing"`.\n\n## Semantic Roles\n\nRoles declare intent \u2014 the engine applies smart defaults. Always prefer roles over manual styling.\n\n| Category | Roles |\n|----------|-------|\n| **Layout** | `section`, `row`, `column`, `centered-content`, `divider`, `spacer` |\n| **Navigation** | `navbar`, `nav-links`, `nav-link` |\n| **Interactive** | `button`, `icon-button`, `badge`, `tag`, `pill`, `input`, `form-input`, `search-bar` |\n| **Cards** | `card`, `feature-card`, `stat-card`, `pricing-card`, `image-card` |\n| **Content** | `hero`, `feature-grid`, `cta-section`, `footer`, `testimonial`, `stats-section` |\n| **Typography** | `heading`, `subheading`, `body-text`, `caption`, `label` |\n| **Media** | `avatar`, `icon`, `phone-mockup`, `screenshot-frame` |\n| **Table** | `table`, `table-row`, `table-header`, `table-cell` |\n| **Form** | `form-group` |\n\nKey defaults:\n- `navbar` \u2192 height: 56-72, horizontal, space_between, center-aligned\n- `button` \u2192 padding: [12, 24], cornerRadius: 8, centered\n- `card` \u2192 vertical, gap: 12, cornerRadius: 12, padding: 24\n- `heading` \u2192 lineHeight: 1.2, letterSpacing: -0.5\n- `body-text` \u2192 fill_container, textGrowth: fixed-width, lineHeight: 1.5\n\n## Layout Rules\n\n1. **NEVER set x/y on children inside layout containers** \u2014 engine positions them\n2. **Siblings must use same width strategy** \u2014 all `fill_container` or all fixed\n3. **NEVER `fill_container` inside `fit_content` parent** \u2014 circular dependency\n4. Cards in horizontal row: ALL `width: "fill_container"`, `height: "fill_container"`\n\n### Sizing Decision\n\n| Question | Answer |\n|----------|--------|\n| Stretch to fill? | `"fill_container"` |\n| Shrink to content? | `"fit_content"` |\n| Exact size? | number (px) |\n\n### Design Type Sizing\n\n| Type | Width | Height |\n|------|-------|--------|\n| Landing page | 1200 | 0 (auto) |\n| Mobile screen | 375 | 812 |\n| Dashboard | 1200 | 0 (auto) |\n\n## Design Principles\n\n### Typography\n\n```\nDisplay: 40-56px 700 letterSpacing: -1.5 lineHeight: 1.1 "Space Grotesk"\nHeading: 28-36px 700 letterSpacing: -0.5 lineHeight: 1.2 "Space Grotesk"\nSubheading: 20-24px 600 letterSpacing: -0.25 lineHeight: 1.3 "Space Grotesk"\nBody: 15-18px 400 letterSpacing: 0 lineHeight: 1.5 "Inter"\nCaption: 13-14px 400 letterSpacing: 0 lineHeight: 1.4 "Inter"\n```\n\nCJK: use `"Noto Sans SC/JP/KR"`, lineHeight >= 1.3, letterSpacing: 0 always.\n\n### Color\n\n```\nPrimary text: #111111 Secondary: #6B7280 Subtle: #9CA3AF\nBackground: #FFFFFF Surface: #F9FAFB Border: #E5E7EB\n```\n\nMax 2 saturated colors. WCAG AA: 4.5:1 body, 3:1 large. Dark bg: `#0F172A`, not `#000000`.\n\n### Spacing (8px grid)\n\n```\nRelated: 8-16px Components: 16-24px\nGroups: 24-32px Sections: 48-80px Page padding: 80px\n```\n\n### Shadows\n\n```json\n// Subtle (cards)\n{ "type": "shadow", "offsetY": 1, "blur": 3, "color": "rgba(0,0,0,0.05)" }\n// Medium (dropdowns)\n{ "type": "shadow", "offsetY": 4, "blur": 12, "color": "rgba(0,0,0,0.08)" }\n// Elevated (modals)\n{ "type": "shadow", "offsetY": 8, "blur": 24, "spread": -4, "color": "rgba(0,0,0,0.12)" }\n```\n\n### Copy Rules\n\nHeadlines: 2-6 words. Subtitles: max 15 words. Buttons: 1-3 words. No lorem ipsum. No emoji as icons.\n\n## Layered Workflow\n\nFor complex multi-section pages, use the three-step skeleton \u2192 content \u2192 refine flow:\n\n| Step | MCP Tool | CLI Equivalent |\n|------|----------|----------------|\n| 1. Create section structure | `design_skeleton` | `op design:skeleton \'<json>\'` |\n| 2. Populate each section | `design_content` (with `postProcess: true`) | `op design:content <section-id> \'<json>\'` |\n| 3. Validate + auto-fix | `design_refine` | `op design:refine --root-id <id>` |\n\n`design:refine` resolves icon names \u2192 SVG paths, fixes layout issues, and validates the tree. **Always run as the final step.**\n\n## Codegen Pipeline\n\nFor incremental, framework-aware code generation from the design tree:\n\n| Step | CLI Command | MCP Tool | Description |\n|------|------------|----------|-------------|\n| 1. Plan | `op codegen:plan \'<json>\'` | `codegen_plan` | Declare framework, root node IDs, and options |\n| 2. Submit | `op codegen:submit \'<json>\'` | `codegen_submit_chunk` | Submit generated code for individual nodes |\n| 3. Assemble | `op codegen:assemble --framework react` | `codegen_assemble` | Combine all chunks into the final output |\n| 4. Clean | `op codegen:clean` | `codegen_clean` | Clear server-side codegen state |\n\nThe plan JSON shape:\n```json\n{ "framework": "react", "rootIds": ["frame-1"], "options": { "tailwind": true } }\n```\n\nThe submit JSON shape:\n```json\n{ "nodeId": "card-1", "code": "<Card className=\\"...\\">...</Card>", "imports": ["Card"] }\n```\n\nSupported frameworks: `react`, `html`, `vue`, `svelte`, `flutter`, `swiftui`, `compose`, `rn` (React Native), `css`.\n\n## Multi-Page Documents\n\n```bash\nop page list # List all pages with IDs\nop page add --name "Settings" # Add a new page\nop page remove <page-id> # Remove a page\nop page rename <page-id> \'New Name\' # Rename a page\nop page reorder <page-id> 2 # Move page to index 2\nop page duplicate <page-id> # Clone page with new IDs\n```\n\nUse `--page <id>` on any command to target a specific page. Without it, commands operate on the first page.\n\n## Common Patterns\n\nPatterns below show `op insert --parent` commands. Each pattern is copy-paste ready.\n\n### Navbar\n\n```bash\nNAV=$(op insert --parent "$ROOT" \'{"type":"frame","role":"navbar","width":"fill_container","height":72,"layout":"horizontal","padding":[0,80],"justifyContent":"space_between","alignItems":"center","fill":[{"type":"solid","color":"#FFFFFF"}],"stroke":{"thickness":1,"fill":[{"type":"solid","color":"#F3F4F6"}]}}\' | ID)\nop insert --parent "$NAV" \'{"type":"text","content":"Brand","fontSize":20,"fontWeight":700,"fontFamily":"Space Grotesk"}\'\nLINKS=$(op insert --parent "$NAV" \'{"type":"frame","role":"nav-links","layout":"horizontal","gap":32,"width":"fit_content","height":"fit_content"}\' | ID)\nop insert --parent "$LINKS" \'{"type":"text","role":"nav-link","content":"Features","fontSize":15}\'\nop insert --parent "$LINKS" \'{"type":"text","role":"nav-link","content":"Pricing","fontSize":15}\'\nCTA=$(op insert --parent "$NAV" \'{"type":"rectangle","role":"button","padding":[10,24],"cornerRadius":8,"fill":[{"type":"solid","color":"#111111"}],"layout":"horizontal","justifyContent":"center","alignItems":"center"}\' | ID)\nop insert --parent "$CTA" \'{"type":"text","content":"Get Started","fontSize":14,"fontWeight":600,"fill":[{"type":"solid","color":"#FFFFFF"}]}\'\n```\n\n### Hero\n\n```bash\nHERO=$(op insert --parent "$ROOT" \'{"type":"frame","role":"hero","width":"fill_container","height":"fit_content","layout":"vertical","padding":[100,80],"gap":24,"alignItems":"center"}\' | ID)\nop insert --parent "$HERO" \'{"type":"text","role":"heading","content":"Build something great","fontSize":56,"fontWeight":700,"fontFamily":"Space Grotesk","textAlign":"center","letterSpacing":-1.5,"lineHeight":1.1,"textGrowth":"fixed-width","width":800}\'\nop insert --parent "$HERO" \'{"type":"text","role":"subheading","content":"The modern platform for teams who ship fast.","fontSize":18,"textAlign":"center","lineHeight":1.6,"textGrowth":"fixed-width","width":560,"fill":[{"type":"solid","color":"#6B7280"}]}\'\nBTNS=$(op insert --parent "$HERO" \'{"type":"frame","layout":"horizontal","gap":12,"width":"fit_content","height":"fit_content"}\' | ID)\nB1=$(op insert --parent "$BTNS" \'{"type":"rectangle","role":"button","padding":[14,32],"cornerRadius":10,"fill":[{"type":"solid","color":"#111111"}],"layout":"horizontal","justifyContent":"center","alignItems":"center"}\' | ID)\nop insert --parent "$B1" \'{"type":"text","content":"Start Free","fontSize":16,"fontWeight":600,"fill":[{"type":"solid","color":"#FFFFFF"}]}\'\nB2=$(op insert --parent "$BTNS" \'{"type":"rectangle","role":"button","padding":[14,32],"cornerRadius":10,"fill":[{"type":"solid","color":"#F3F4F6"}],"layout":"horizontal","justifyContent":"center","alignItems":"center"}\' | ID)\nop insert --parent "$B2" \'{"type":"text","content":"View Demo","fontSize":16,"fontWeight":600}\'\n```\n\n### Feature Card (in horizontal grid, ALL cards must use fill_container)\n\n```bash\nCARD=$(op insert --parent "$GRID" \'{"type":"rectangle","role":"feature-card","width":"fill_container","height":"fill_container","layout":"vertical","padding":28,"gap":16,"cornerRadius":16,"fill":[{"type":"solid","color":"#F9FAFB"}]}\' | ID)\nop insert --parent "$CARD" \'{"type":"path","name":"ZapIcon","width":24,"height":24,"fill":[{"type":"solid","color":"#111111"}]}\'\nop insert --parent "$CARD" \'{"type":"text","content":"Lightning Fast","fontSize":20,"fontWeight":600}\'\nop insert --parent "$CARD" \'{"type":"text","role":"body-text","content":"Sub-second builds with smart caching.","fontSize":15,"lineHeight":1.6,"fill":[{"type":"solid","color":"#6B7280"}]}\'\n```\n\n### Form Input\n\n```bash\nGRP=$(op insert --parent "$FORM" \'{"type":"frame","role":"form-group","layout":"vertical","gap":8,"width":"fill_container"}\' | ID)\nop insert --parent "$GRP" \'{"type":"text","role":"label","content":"Email","fontSize":14,"fontWeight":500}\'\nINP=$(op insert --parent "$GRP" \'{"type":"rectangle","role":"form-input","width":"fill_container","height":48,"cornerRadius":10,"layout":"horizontal","padding":[0,16],"gap":10,"alignItems":"center","fill":[{"type":"solid","color":"#F9FAFB"}],"stroke":{"thickness":1,"fill":[{"type":"solid","color":"#E5E7EB"}]}}\' | ID)\nop insert --parent "$INP" \'{"type":"path","name":"MailIcon","width":18,"height":18,"fill":[{"type":"solid","color":"#9CA3AF"}]}\'\nop insert --parent "$INP" \'{"type":"text","content":"you@example.com","fontSize":15,"fill":[{"type":"solid","color":"#9CA3AF"}]}\'\n```\n\n### Footer\n\n```bash\nFOOTER=$(op insert --parent "$ROOT" \'{"type":"frame","role":"footer","width":"fill_container","height":"fit_content","layout":"horizontal","padding":[48,80],"gap":80,"fill":[{"type":"solid","color":"#F9FAFB"}]}\' | ID)\nCOL1=$(op insert --parent "$FOOTER" \'{"type":"frame","layout":"vertical","gap":16,"width":240}\' | ID)\nop insert --parent "$COL1" \'{"type":"text","content":"Brand","fontSize":20,"fontWeight":700,"fontFamily":"Space Grotesk"}\'\nop insert --parent "$COL1" \'{"type":"text","content":"Building the future of design.","fontSize":14,"lineHeight":1.6,"fill":[{"type":"solid","color":"#6B7280"}]}\'\nCOL2=$(op insert --parent "$FOOTER" \'{"type":"frame","layout":"vertical","gap":12,"width":"fit_content"}\' | ID)\nop insert --parent "$COL2" \'{"type":"text","content":"Product","fontSize":14,"fontWeight":600}\'\nop insert --parent "$COL2" \'{"type":"text","content":"Features","fontSize":14,"fill":[{"type":"solid","color":"#6B7280"}]}\'\nop insert --parent "$COL2" \'{"type":"text","content":"Pricing","fontSize":14,"fill":[{"type":"solid","color":"#6B7280"}]}\'\n```\n\n## Common Mistakes\n\n| Mistake | Fix |\n|---------|-----|\n| Setting x/y inside layout container | Remove x/y \u2014 engine auto-positions |\n| Cards with different width strategies | All siblings: same sizing (`fill_container`) |\n| `fill_container` child in `fit_content` parent | Use fixed width or switch parent to `fill_container` |\n| Pure black text `#000000` | Use `#111111` or `#0F172A` |\n| Heavy drop shadows | Use subtle `rgba(0,0,0,0.05-0.12)` |\n| Emoji as icons | Use path nodes with icon names |\n| Lorem ipsum placeholder | Write realistic, concise copy |\n| Fixed height on text | Use `textGrowth: "fixed-width"` instead |\n| Space Grotesk for CJK | Use `"Noto Sans SC/JP/KR"` |\n| Negative letterSpacing on CJK | Always 0 for CJK text |\n| Missing post-process after insert | Run `op design:refine --root-id <id>` after building the tree |\n| Icons inserted but not visible | Path nodes need `design:refine` or `--post-process` to resolve SVG |\n| Using DSL `I()` with inline `children` | DSL parser fails on nested JSON \u2014 insert parent and children separately |\n| Missing `postProcess: true` in MCP | Always set for MCP tool calls |\n\n## Full Example \u2014 `op insert` Workflow (Recommended)\n\nBuild a complete mobile login page using `op insert --parent`. This is the most reliable approach.\n\n```bash\n#!/bin/bash\nset -e\nID() { python3 -c "import sys,json; print(json.load(sys.stdin)[\'nodeId\'])"; }\n\n# Root frame (mobile)\nROOT=$(op insert \'{"type":"frame","name":"Login","width":375,"height":812,"layout":"vertical","fill":[{"type":"solid","color":"#FFFFFF"}]}\' | ID)\n\n# Header\nTOP=$(op insert --parent "$ROOT" \'{"type":"frame","width":"fill_container","height":"fit_content","layout":"vertical","padding":[80,32,40,32],"gap":14,"alignItems":"center"}\' | ID)\nop insert --parent "$TOP" \'{"type":"path","name":"ShieldIcon","width":48,"height":48,"fill":[{"type":"solid","color":"#6366F1"}]}\'\nop insert --parent "$TOP" \'{"type":"text","content":"Welcome Back","fontSize":28,"fontWeight":700,"fontFamily":"Space Grotesk","letterSpacing":-0.5,"textAlign":"center"}\'\n\n# Form\nFORM=$(op insert --parent "$ROOT" \'{"type":"frame","width":"fill_container","height":"fit_content","layout":"vertical","padding":[0,32],"gap":20}\' | ID)\n\n# Email input\nGRP=$(op insert --parent "$FORM" \'{"type":"frame","role":"form-group","layout":"vertical","gap":8,"width":"fill_container"}\' | ID)\nop insert --parent "$GRP" \'{"type":"text","role":"label","content":"Email","fontSize":14,"fontWeight":500}\'\nINP=$(op insert --parent "$GRP" \'{"type":"rectangle","role":"form-input","width":"fill_container","height":48,"cornerRadius":10,"layout":"horizontal","padding":[0,16],"gap":10,"alignItems":"center","fill":[{"type":"solid","color":"#F9FAFB"}],"stroke":{"thickness":1,"fill":[{"type":"solid","color":"#E5E7EB"}]}}\' | ID)\nop insert --parent "$INP" \'{"type":"path","name":"MailIcon","width":18,"height":18,"fill":[{"type":"solid","color":"#9CA3AF"}]}\'\nop insert --parent "$INP" \'{"type":"text","content":"you@example.com","fontSize":15,"fill":[{"type":"solid","color":"#9CA3AF"}]}\'\n\n# Login button\nBTN=$(op insert --parent "$FORM" \'{"type":"rectangle","role":"button","width":"fill_container","height":50,"cornerRadius":12,"fill":[{"type":"solid","color":"#111111"}],"layout":"horizontal","justifyContent":"center","alignItems":"center"}\' | ID)\nop insert --parent "$BTN" \'{"type":"text","content":"Sign In","fontSize":16,"fontWeight":600,"fill":[{"type":"solid","color":"#FFFFFF"}]}\'\n\n# IMPORTANT: resolve icons + validate layout\nop design:refine --root-id "$ROOT"\n```\n\n## DSL Example \u2014 Landing Page\n\nDSL is suitable for simpler structures. **Avoid inline `children`** \u2014 insert parent and children as separate operations.\n\n```\nroot=I(null, {"type":"frame","name":"Landing","width":1200,"height":0,"layout":"vertical","fill":[{"type":"solid","color":"#FFFFFF"}]})\n\nnav=I(root, {"type":"frame","role":"navbar","width":"fill_container","height":72,"layout":"horizontal","padding":[0,80],"justifyContent":"space_between","alignItems":"center"})\nI(nav, {"type":"text","content":"Acme","fontSize":20,"fontWeight":700,"fontFamily":"Space Grotesk"})\nlinks=I(nav, {"type":"frame","role":"nav-links","layout":"horizontal","gap":32,"width":"fit_content","height":"fit_content"})\nI(links, {"type":"text","role":"nav-link","content":"Features","fontSize":15})\nI(links, {"type":"text","role":"nav-link","content":"Pricing","fontSize":15})\ncta=I(nav, {"type":"rectangle","role":"button","padding":[10,24],"cornerRadius":8,"fill":[{"type":"solid","color":"#111111"}],"layout":"horizontal","justifyContent":"center","alignItems":"center"})\nI(cta, {"type":"text","content":"Get Started","fontSize":14,"fontWeight":600,"fill":[{"type":"solid","color":"#FFFFFF"}]})\n\nhero=I(root, {"type":"frame","role":"hero","width":"fill_container","height":"fit_content","layout":"vertical","padding":[100,80],"gap":24,"alignItems":"center"})\nI(hero, {"type":"text","role":"heading","content":"Ship faster with Acme","fontSize":56,"fontWeight":700,"fontFamily":"Space Grotesk","textAlign":"center","letterSpacing":-1.5,"lineHeight":1.1,"textGrowth":"fixed-width","width":800})\nI(hero, {"type":"text","role":"subheading","content":"Turn ideas into production apps in minutes.","fontSize":18,"textAlign":"center","lineHeight":1.6,"textGrowth":"fixed-width","width":560,"fill":[{"type":"solid","color":"#6B7280"}]})\nbtns=I(hero, {"type":"frame","layout":"horizontal","gap":12,"width":"fit_content","height":"fit_content"})\nb1=I(btns, {"type":"rectangle","role":"button","padding":[14,32],"cornerRadius":10,"fill":[{"type":"solid","color":"#111111"}],"layout":"horizontal","justifyContent":"center","alignItems":"center"})\nI(b1, {"type":"text","content":"Start Free","fontSize":16,"fontWeight":600,"fill":[{"type":"solid","color":"#FFFFFF"}]})\nb2=I(btns, {"type":"rectangle","role":"button","padding":[14,32],"cornerRadius":10,"fill":[{"type":"solid","color":"#F3F4F6"}],"layout":"horizontal","justifyContent":"center","alignItems":"center"})\nI(b2, {"type":"text","content":"View Demo","fontSize":16,"fontWeight":600})\n\nfeat=I(root, {"type":"frame","role":"section","width":"fill_container","height":"fit_content","layout":"vertical","padding":[80,80],"gap":48,"alignItems":"center"})\nI(feat, {"type":"text","role":"heading","content":"Everything you need","fontSize":36,"fontWeight":700,"fontFamily":"Space Grotesk","textAlign":"center","letterSpacing":-0.5})\ngrid=I(feat, {"type":"frame","role":"feature-grid","width":"fill_container","layout":"horizontal","gap":24})\nc1=I(grid, {"type":"rectangle","role":"feature-card","width":"fill_container","height":"fill_container","layout":"vertical","padding":28,"gap":16,"cornerRadius":16,"fill":[{"type":"solid","color":"#F9FAFB"}]})\nI(c1, {"type":"path","name":"ZapIcon","width":24,"height":24,"fill":[{"type":"solid","color":"#111111"}]})\nI(c1, {"type":"text","content":"Lightning Fast","fontSize":20,"fontWeight":600})\nI(c1, {"type":"text","role":"body-text","content":"Sub-second builds with smart caching.","fontSize":15,"lineHeight":1.6,"fill":[{"type":"solid","color":"#6B7280"}]})\nc2=C(c1, grid, {})\nU(c2+"/0", {"name":"ShieldIcon"})\nU(c2+"/1", {"content":"Enterprise Security"})\nU(c2+"/2", {"content":"SOC 2 certified with end-to-end encryption."})\nc3=C(c1, grid, {})\nU(c3+"/0", {"name":"GitBranchIcon"})\nU(c3+"/1", {"content":"Git-Native Workflow"})\nU(c3+"/2", {"content":"Preview deploys on every push with instant rollback."})\n```\n',
|
|
13392
13416
|
".claude-plugin/plugin.json": '{\n "name": "openpencil-skill",\n "description": "Design skill for OpenPencil \u2014 op CLI, batch DSL, MCP tools, PenNode schema, and UI design best practices",\n "version": "0.7.0",\n "author": {\n "name": "ZSeven-W",\n "email": "xkayshen@gmail.com"\n },\n "homepage": "https://github.com/zseven-w/openpencil-skill",\n "repository": "https://github.com/zseven-w/openpencil-skill",\n "license": "MIT",\n "keywords": [\n "design",\n "ui",\n "vector",\n "openpencil",\n "cli",\n "mcp",\n "dsl"\n ]\n}\n',
|
|
13393
13417
|
".claude-plugin/marketplace.json": '{\n "name": "openpencil-skill",\n "description": "Design skill for OpenPencil \u2014 op CLI, batch DSL, MCP tools, and UI best practices",\n "owner": {\n "name": "ZSeven-W",\n "email": "xkayshen@gmail.com"\n },\n "plugins": [\n {\n "name": "openpencil-skill",\n "description": "Design skill for OpenPencil \u2014 op CLI, batch DSL, MCP tools, PenNode schema, and UI design best practices",\n "version": "0.7.0",\n "source": "./",\n "author": {\n "name": "ZSeven-W",\n "email": "xkayshen@gmail.com"\n }\n }\n ]\n}\n',
|
|
13394
13418
|
".cursor-plugin/plugin.json": '{\n "name": "openpencil-skill",\n "displayName": "OpenPencil Design",\n "description": "Design skill for OpenPencil \u2014 op CLI, batch DSL, MCP tools, PenNode schema, and UI design best practices",\n "version": "0.7.0",\n "author": {\n "name": "ZSeven-W",\n "email": "xkayshen@gmail.com"\n },\n "homepage": "https://github.com/zseven-w/openpencil-skill",\n "repository": "https://github.com/zseven-w/openpencil-skill",\n "license": "MIT",\n "keywords": [\n "design",\n "ui",\n "vector",\n "openpencil",\n "cli",\n "mcp",\n "dsl"\n ],\n "skills": "./skills/"\n}\n',
|
|
@@ -13751,7 +13775,7 @@ init_define_import_meta_env();
|
|
|
13751
13775
|
// apps/cli/package.json
|
|
13752
13776
|
var package_default = {
|
|
13753
13777
|
name: "@zseven-w/openpencil",
|
|
13754
|
-
version: "0.7.
|
|
13778
|
+
version: "0.7.2",
|
|
13755
13779
|
description: "CLI for OpenPencil \u2014 control the design tool from your terminal",
|
|
13756
13780
|
homepage: "https://github.com/ZSeven-W/openpencil/tree/main/apps/cli",
|
|
13757
13781
|
bugs: {
|
|
@@ -13778,8 +13802,8 @@ var package_default = {
|
|
|
13778
13802
|
compile: "cd ../.. && bun run cli:compile"
|
|
13779
13803
|
},
|
|
13780
13804
|
dependencies: {
|
|
13781
|
-
"@zseven-w/pen-figma": "0.7.
|
|
13782
|
-
"@zseven-w/pen-mcp": "0.7.
|
|
13805
|
+
"@zseven-w/pen-figma": "0.7.2",
|
|
13806
|
+
"@zseven-w/pen-mcp": "0.7.2"
|
|
13783
13807
|
}
|
|
13784
13808
|
};
|
|
13785
13809
|
|