@pen.dev/cli 0.3.3 → 0.3.5

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.
Files changed (70) hide show
  1. package/README.md +13 -6
  2. package/dist/anthropic-messages-xcJLXjiT.mjs +40 -0
  3. package/dist/azure-openai-responses-CoA9_5_J.mjs +2 -0
  4. package/dist/dist-Dmu1Pp1Y.mjs +1580 -0
  5. package/dist/{dist-BY6vOcMF.mjs → dist-L3CkgNK3.mjs} +2 -2
  6. package/dist/{error-body-D2Mrqb4g.mjs → error-body-BBlqjbe7.mjs} +1 -1
  7. package/dist/google-generative-ai-CFHCNDEQ.mjs +2 -0
  8. package/dist/google-shared-DolBdAGX.mjs +318 -0
  9. package/dist/google-vertex-tT_ymu8S.mjs +2 -0
  10. package/dist/index.mjs +2 -2
  11. package/dist/lib-EA7A-_Pc.mjs +9 -0
  12. package/dist/mistral-conversations-CsN5eF3B.mjs +5 -0
  13. package/dist/models-BBd5zwn6.mjs +2 -0
  14. package/dist/node_modules/@highagency/pencil-wasm/package.json +1 -1
  15. package/dist/node_modules/@highagency/pencil-wasm/pencil.d.ts +3 -0
  16. package/dist/node_modules/@highagency/pencil-wasm/pencil.js +1 -1
  17. package/dist/node_modules/@highagency/pencil-wasm/pencil.wasm +0 -0
  18. package/dist/openai-CbFQ5q5I.mjs +18 -0
  19. package/dist/openai-codex-responses-BIh-ZL5a.mjs +8 -0
  20. package/dist/openai-completions-CLJSzdfY.mjs +6 -0
  21. package/dist/openai-responses-DKbl253t.mjs +2 -0
  22. package/dist/openai-responses-shared-Brqyk2bc.mjs +11 -0
  23. package/dist/{openrouter-images-BbBWg9zF.mjs → openrouter-images-C8mOFqx9.mjs} +1 -1
  24. package/dist/out/mcp-server-darwin-arm64 +0 -0
  25. package/dist/out/mcp-server-darwin-x64 +0 -0
  26. package/dist/out/mcp-server-linux-arm64 +0 -0
  27. package/dist/out/mcp-server-linux-x64 +0 -0
  28. package/dist/out/mcp-server-windows-arm64.exe +0 -0
  29. package/dist/out/mcp-server-windows-x64.exe +0 -0
  30. package/dist/out/skills/pen-dev/SKILL.md +204 -0
  31. package/dist/out/skills/pen-dev/execute.md +364 -0
  32. package/dist/out/skills/pen-dev/guide/code.md +198 -0
  33. package/dist/out/skills/pen-dev/guide/components.md +45 -0
  34. package/dist/out/skills/pen-dev/guide/design-system.md +556 -0
  35. package/dist/out/skills/pen-dev/guide/landing-page.md +31 -0
  36. package/dist/out/skills/pen-dev/guide/mobile-app.md +31 -0
  37. package/dist/out/skills/pen-dev/guide/slides.md +222 -0
  38. package/dist/out/skills/pen-dev/guide/table.md +37 -0
  39. package/dist/out/skills/pen-dev/guide/tailwind.md +328 -0
  40. package/dist/out/skills/pen-dev/guide/web-app.md +245 -0
  41. package/dist/out/skills/pen-dev/pen-schema.md +202 -0
  42. package/dist/out/skills/pen-dev/scripts-and-shaders.md +94 -0
  43. package/dist/{pi-messages-CSXUHkjV.mjs → pi-messages-CucvyzBS.mjs} +2 -2
  44. package/dist/transform-messages-1ulTEFY1.mjs +2 -0
  45. package/dist/typescript-D0uAMumm.mjs +6 -0
  46. package/package.json +3 -3
  47. package/dist/anthropic-messages-Bf-fvkNu.mjs +0 -40
  48. package/dist/azure-openai-responses-CzgVqECr.mjs +0 -2
  49. package/dist/completionchunk-BQtPqA8U.mjs +0 -28
  50. package/dist/dist-BEo0Z8Vf.mjs +0 -1558
  51. package/dist/google-generative-ai-DSOqI0LF.mjs +0 -2
  52. package/dist/google-shared-CetPT_Vt.mjs +0 -318
  53. package/dist/google-vertex-CPtOc5oU.mjs +0 -2
  54. package/dist/mistral-conversations-CUsdbDQ8.mjs +0 -10
  55. package/dist/models-CwK5xZH9.mjs +0 -2
  56. package/dist/openai-Brr_V-tF.mjs +0 -17
  57. package/dist/openai-codex-responses-Cb5pfH5S.mjs +0 -8
  58. package/dist/openai-completions-BvTlcCeP.mjs +0 -6
  59. package/dist/openai-responses-IuaLTEHh.mjs +0 -2
  60. package/dist/openai-responses-shared-D4d0NbBE.mjs +0 -11
  61. package/dist/otel-CaADOqYZ.mjs +0 -4
  62. package/dist/transform-messages-CSBQpmXO.mjs +0 -2
  63. /package/dist/{deferred-tools-D8j2Ra6f.mjs → deferred-tools-CozAnCQL.mjs} +0 -0
  64. /package/dist/{github-copilot-headers-DZOfokGy.mjs → github-copilot-headers-DCj7hJoC.mjs} +0 -0
  65. /package/dist/{hash-Kp92CI9R.mjs → hash-DNYCELl4.mjs} +0 -0
  66. /package/dist/{headers-BvGum5dc.mjs → headers-DlpC0ues.mjs} +0 -0
  67. /package/dist/{openai-prompt-cache-t4kk9puG.mjs → openai-prompt-cache-tVHrB8oW.mjs} +0 -0
  68. /package/dist/{photon_rs-BySSRmP5.mjs → photon_rs-o0nbTM4F.mjs} +0 -0
  69. /package/dist/{provider-retry-BM3ArvaW.mjs → provider-retry-GaV_ioT7.mjs} +0 -0
  70. /package/dist/{sanitize-unicode-CHjuq5rK.mjs → sanitize-unicode-D-15laVS.mjs} +0 -0
@@ -0,0 +1,204 @@
1
+ ---
2
+ name: pen-dev
3
+ description: Create high-quality visual designs — websites, app screens, dashboards, slides, marketing materials, social media graphics on the pen.dev canvas. Use when the task involves generating designs on the canvas.
4
+ ---
5
+
6
+ # pen.dev
7
+
8
+ pen.dev is an infinite canvas design tool with nested object hierarchy.
9
+
10
+ ## Quick reference
11
+
12
+ | Topic | When to use | Reference |
13
+ | --- | --- | --- |
14
+ | `.pen schema` | Learn the .pen schema that is used to represent. Required to read. | [pen-schema.md](./pen-schema.md) |
15
+ | `execute` | Learn how to use the `execute` MCP tool to generating designs on the canvas. Required to read. | [execute.md](./execute.md) |
16
+ | `components` | Learn how to use components and instances in .pen files. | [components.md](./guide/components.md) |
17
+ | `scripts-and-shaders` | Learn how to use scripts and shaders on the pen.dev canvas. | [scripts-and-shaders.md](./scripts-and-shaders.md) |
18
+ | `code` | Generating code from .pen files. | [code.md](./guide/code.md) |
19
+ | `design-system` | Composing screens with design system components. | [design-system.md](./guide/design-system.md) |
20
+ | `landing-page` | Designing landing pages and promotional websites. | [landing-page.md](./guide/landing-page.md) |
21
+ | `mobile-app` | Designing mobile apps. | [mobile-app.md](./guide/mobile-app.md) |
22
+ | `slides` | Designing presentation slides. | [slides.md](./guide/slides.md) |
23
+ | `table` | Working with tables and dashboards. | [table.md](./guide/table.md) |
24
+ | `tailwind` | Tailwind CSS v4 implementation. | [tailwind.md](./guide/tailwind.md) |
25
+ | `web-app` | Designing web apps. | [web-app.md](./guide/web-app.md) |
26
+
27
+ ## General instructions
28
+
29
+ - Favor copying existing content and updating the copied content later, rather than generating new content.
30
+ - When creating new variables make sure you are not accidentally overwriting any existing design.
31
+ - User may ask for technical modifications like removing, moving, re-ordering, clearing, and copying objects/variables, or just ask questions. Only do what's requested and nothing more.
32
+ - pen.dev is a collaborative multiplayer environment: the document can change while you work, so the state you remember may be stale. If a node is missing or no longer matches what you expected, re-read instead of recreating it, and don't undo changes the user made in the meantime.
33
+ - When adding more content to a frame make sure the frame has the right layout, or is big enough to fit the new content. Resize the frame if necessary. There is no scrolling and the entire content should always be visible on the canvas.
34
+ - Place components at the top and your screens below, growing to the right and down.
35
+ - When creating new screens, represent each one as a top-level frame in `document`. Use `clip: true` on screen frames to prevent content overflow.
36
+ - Keep the document root clean: only page/screen frames, reusable component frames, and other major container frames belong directly under `document`. Never place text, icons, buttons, cards, rows, images, or decorative shapes directly in `document`.
37
+ - When the design has repeated UI, consider building those as reusable components first and then instancing them, so edits to the component propagate to every instance.
38
+ - Changes in the document are presented in real-time to the user. Make the changes in a logical order as a designer would.
39
+ - Minimize the time between the user requests and showing something on the screen. Users don't want to wait a long time before they see the progress.
40
+ - Use the canvas as part of your thinking. You don't need to preplan every little detail. Iteratively design using the document.
41
+ - Reasoning policy: keep your thinking brief. Do not plan the entire task up front in your reasoning. Think only about the immediate next step, make a tool call, and continue planning incrementally between tool calls.
42
+
43
+ - Do NOT use or think in CSS/HTML properties or behavior. pen.dev uses a custom format and has its own layout, rendering, and canvas behavior. pen.dev has similar concepts and naming, but it's not the same as CSS/HTML.
44
+ - If a property is not present in the .pen schema, it's not supported. Find a different way to achieve the same visual effect.
45
+ - Do NOT use: alignItems baseline/stretch, margin, percentage size. These values are not supported and will cause an error.
46
+
47
+ Verify each section immediately after you are done with it. Don't wait until the end of the whole generation.
48
+ Create a checklist to evaluate the design after you create it. Make sure to check for the following:
49
+ - Layout it not collapsed or broken.
50
+ - Content is not clipped outside the frame. Resize the container frames to fit the content if necessary.
51
+ - Color contrast on text, objects, and background is sufficient.
52
+ - Objects are properly aligned and spaced.
53
+ - Intention and understanding of the pen.dev schema matches the visuals.
54
+ After reviewing the design, do NOT delete it to make changes. If you want to fix it, always make direct updates to the existing objects.
55
+
56
+ ### Style
57
+
58
+ - Don't create repetitive styles and grids. Add some unique elements and layout to make the design feel more interesting.
59
+ - Avoid wrapping every element in its own box or card. This is a common AI habit that makes designs look generic. Use a container only when it has a real structural or functional purpose.
60
+ - Avoid excessive gradients, shadows, and rounded corners unless requested or when part of the brand identity. Be refined and intentional with effects and decorations
61
+ - All Google fonts are available.
62
+ - Never draw logos, illustrations, mascots, or other freeform artwork yourself out of paths and shapes - hand-built drawings always look bad. Whenever the user asks you to draw something, or a design needs a logo, illustration, or decorative graphic, use the `Generate` function with `type: "svg"` on a frame instead.
63
+
64
+ #### Style archetypes
65
+
66
+ The `get_style` MCP tool provides ready-made visual style archetypes with configurable fonts, colors, and imagery. Use one when the user has no specific brand or style direction. Load a style with `get_style({ name: "..." })`; if the style requires params, the tool returns the options to choose from — call it again with all params filled in.
67
+
68
+ Available styles:
69
+
70
+ - Aerial Gravitas
71
+ - Anchored Ribbon Grid
72
+ - Artisan Editorial
73
+ - Blueprint Technical
74
+ - Centered Device Cascade
75
+ - Centered Serif List
76
+ - Cinematic Alternating
77
+ - Cinematic Device Column
78
+ - Color Block Stack
79
+ - Dark Centered Platform
80
+ - Editorial Landscape Stack
81
+ - Editorial Scientific
82
+ - Gradient Prompt Stack
83
+ - Illustrated Ribbon Stack
84
+ - Illustrated Warm
85
+ - Inline Friendly
86
+ - Modular Bento Showcase
87
+ - Monumental Editorial
88
+ - Narrative Illustrated
89
+ - Product Data Grid
90
+ - Product Demo
91
+ - Saturated Code Bridge
92
+ - Soft Bento
93
+ - Spatial Plus
94
+ - Split Inverse Showcase
95
+ - Zigzag Bold Split
96
+
97
+ ### Objects
98
+
99
+ - All object coordinates are defined relative to the parent’s top-left corner.
100
+ - Use a coordinate system where `x` increases to the right and `y` increases downward.
101
+ - Objects rotate counter-clockwise around the top-left corner of their bounding box.
102
+ - Only `frame` and `group` types can have children. Shapes, text, and other nodes cannot have children.
103
+
104
+ - Properties do not cascade from parents to children. Every node is independent and must have all necessary properties specified.
105
+ - Exclude default property values unless they are overriding a non-default value inside an instance.
106
+
107
+ - Avoid duplicating the same dimension value across multiple sibling elements. If several children need to match their parent's width or height, use `fill_container` on each rather than hardcoding the parent's size repeatedly.
108
+ - Explicitly specify `width` and `height` for shapes and other nodes whose size is not otherwise determined by layout or text behavior.
109
+ - For layout-driven nodes, prefer `fit_content` and `fill_container` when appropriate instead of hardcoded numeric sizes.
110
+ - Set children to `fill_container` to distribute them evenly within their parent. Use the `gap` property on the parent to add gaps between children.
111
+
112
+ - Use `justifyContent: "center"` and `alignItems: "center"` on the parent to center its children both vertically and horizontally.
113
+ - For text, follow `textGrowth` rules: do not set `width` or `height` unless `textGrowth` requires them.
114
+ - Use `textAlign` or `textAlignVertical` to align the text within the text bounding box. `textAlign` has a visible effect when `textGrowth` is `fixed-width` or `fixed-width-height`. `textAlignVertical` has a visible effect only when `textGrowth` is `fixed-width-height`.
115
+ - Setting `textAlign` or `textAlignVertical` will not change the position of the text bounding box. Use flexbox layout to align the object.
116
+ - Use `textGrowth` to define text wrapping and bounding box sizing. When not specified, the default value is `"auto"`.
117
+ - Possible `textGrowth` values:
118
+ - `auto`: `width` and `height` are always derived from the text content, any `width` or `height` you set is ignored. Never does line wrapping, text will always be on a single line.
119
+ - `fixed-width`: the `width` node property MUST be specified, `height` is calculated from the text content. Does line wrapping based on the object's bounding box width.
120
+ - `fixed-width-height`: both `width` and `height` node property MUST be specified. Does line wrapping based on the object's bounding box width. Text content will vertically overflow.
121
+ - Only use `fixed-width-height` when you need to override the height of the text box. Prefer `fixed-width` with `fill_container` for text that needs to adapt to the parent container size.
122
+ - If you want to wrap lines, you HAVE TO set the `textGrowth` to either `fixed-width` or `fixed-width-height`.
123
+ - Never guess text dimensions, always rely on text wrapping and flexbox layout to size and position text. Any dimension guess for text will result in visual bugs.
124
+ - Use the `lineHeight` property on text as a ratio relative to the font size: `0.0` means 0%, and `1.0` means 100%. If not specified, the font’s default line height will be applied.
125
+
126
+ - Text has no `fill` by default and will be invisible. You MUST set the `fill` property on text objects to make them visible. Emoji requires `fill` as well.
127
+ - To reference a variable, use a string value with a `$` prefix (`fill: "$primary-color"`, `gap: "$spacing-small"`)
128
+ - `width` and `height` do not support percentage or viewport CSS values. Never use values like `"70%"`, `"100%"`, `"50vh"`, or `"calc(...)"` or even `value + "%"`. If you need proportion-based sizing that's not uniform from the layout, you need to use fixed pixel values.
129
+ - `fill` can be set on wrapping containers to add a background color, gradient, or an image.
130
+
131
+ ### Flexbox Layout
132
+
133
+ - `layout` and `padding` is only accessible on `frame` type. Do NOT set `layout` and `padding` on other types of nodes.
134
+ - **Prefer dynamic sizing over hardcoded values.** Use `fill_container` or `fit_content`, rather than repeating the parent's or children's pixel value. This makes designs more maintainable.
135
+ - Always prefer flexbox layout; only use `layout: "none"` when truly necessary.
136
+ - x and y properties are completely ignored when the node is in layout. Do NOT set x/y on a child unless the parent has layout: "none" or the node has layoutPosition: "absolute"
137
+ - Only use explicit numerical sizes in rare cases when it cannot be inferred from the layout.
138
+ - To align and distribute objects within a container with flexbox, wrap them in a parent object that has a `layout` property.
139
+ - Frames always default to `horizontal` direction and `fit_content` sizing.
140
+ - Padding affects ALL children uniformly - it creates space between the container's edges and its children.
141
+ - To offset an individual child in flexbox, wrap it in a flexbox frame with padding.
142
+ - Flexbox layout is single-axis only with no item wrapping. For grid-like layouts, manually create separate row frames.
143
+ - A parent cannot be sized by its children using `fit_content` if all direct children are sized by the parent using `fill_container`. This creates circular dependency. Don't rely on the fallback value to resolve circular dependency.
144
+
145
+ **Antipattern**
146
+ ```js
147
+ // WRONG: percentage values are not supported
148
+ Insert(parent,{type:"frame",width:"100%",height:`${100/count}%`})
149
+
150
+ // WRONG: padding on text is not supported, use a wrapping frame instead
151
+ Insert(parent,{type:"text",content:"text",fontSize:12,padding:12})
152
+
153
+ // WRONG: Collapses to 0 width. Parent defaults to fit_content. Child tries to fill it.
154
+ badParentId = Insert(screen, {type: "frame", layout: "vertical"});
155
+ Insert(badParentId, {type: "text", textGrowth: "fixed-width", width: "fill_container", content: "..."});
156
+ ```
157
+
158
+ #### Text Sizing
159
+
160
+ Text sizing depends on whether the parent or the text content controls the size.
161
+
162
+ **Parent defines size** - parent must have flexbox layout and determines the size. Use `textGrowth:"fixed-width"` + `fill_container` (headings, descriptions, paragraphs):
163
+
164
+ ```js
165
+ sectionId=Insert(parent,{type:"frame",name:"Header",layout:"vertical",width:400,gap:12,x:200,y:200})
166
+ Insert(sectionId,{type:"text",name:"Title",textGrowth:"fixed-width",width:"fill_container",fontFamily:"Inter",content:"Dashboard",fontSize:24,fill:"$text-primary"})
167
+ Insert(sectionId,{type:"text",name:"Subtitle",textGrowth:"fixed-width",width:"fill_container",fontFamily:"Inter",content:"Manage your account settings",fontSize:14,fill:"$text-secondary"})
168
+ ```
169
+
170
+ **Text defines size** - default `auto`, no width/height (button labels, tags, badges):
171
+
172
+ ```js
173
+ btnId=Insert(parent,{type:"frame",name:"Button",padding:12,gap:8})
174
+ Insert(btnId,{type:"text",name:"Label",fontFamily:"Inter",content:"Submit",fontSize:14,fill:"$text-primary"})
175
+ ```
176
+
177
+ **Antipattern** - using pixel dimensions when layout can handle it:
178
+
179
+ ```js
180
+ containerId=Insert(parent,{type:"frame",name:"Button",width:200,height:100,padding:12})
181
+ // WRONG: parent has layout, use fill_container instead of pixel width
182
+ Insert(containerId,{type:"text",content:"abc",textGrowth:"fixed-width",width:320,fontSize:14})
183
+ // WRONG: missing textGrowth after specifying width
184
+ Insert(containerId,{type:"text",content:"abc",width:100,fontSize:14})
185
+ ```
186
+
187
+ ### Using placeholders
188
+
189
+ - Any new, copied, or modified root frame MUST have `placeholder: true` for the entire duration of the work on it.
190
+ - Once you start working inside a placeholder node, finish it before unsetting the flag.
191
+ - Remove `placeholder: true` as soon as the frame is done - don't wait until all screens are finished.
192
+
193
+ ### SVG Path
194
+
195
+ - Always set an explicit `viewBox: [x, y, width, height]` on a path. It defines the region of the SVG coordinate space that maps onto the node's full box, and lets you keep the path's raw coordinates while still controlling placement via the node's `x`/`y`/`width`/`height`.
196
+ - The viewBox region is stretched to fill the node's width/height.
197
+
198
+ ### Graphs
199
+
200
+ - Always prefer bar charts and charts that can be built with simple layout configurations that the .pen format supports.
201
+ - Don't use absolutely positioned elements over the chart, as they won't align correctly.
202
+ - Don't manually match labels to bar positions and sizes. Rely on layout to position labels and bars correctly.
203
+ - When creating donut charts, always use `fill` color with `innerRadius` size to create the donut shape.
204
+ - Line charts cannot be easily built because the layout system cannot position individual points.
@@ -0,0 +1,364 @@
1
+ # Using the `execute` tool on the pen.dev canvas
2
+
3
+ - The `execute` tool executes a small javascript snippet to modify the document.
4
+ - Split work into multiple smaller `execute` calls focused on each section.
5
+
6
+ - In case of an error, all modifications and the created globals will be reverted.
7
+ - When an `execute` call fails, ALWAYS fix it with the `edits` parameter and the `editId` from the failure message - never resend the snippet. If the patched snippet fails again, keep fixing it with further `edits` under the same `editId`; `find` must then match the snippet as already patched.
8
+ - A list of warnings will be returned in the response message. Always Fix them in the next execute call.
9
+
10
+ - Use normal JavaScript to generate repeated design structure: arrays, `for...of` loops, computed values, conditionals, object spreads, helper functions, and template strings are all useful.
11
+ - Be smart about writing JavaScript to remove duplication and minimize the length of the generated code.
12
+ - Prefer loops/spreads/helpers over long handwritten repetitive code when creating nav items, table rows, cards, metrics, menus, or similar repeated UI.
13
+ - Do not include comments in the generated `execute` JavaScript snippet. Keep the input small.
14
+
15
+ - You MUST set the `name` property with a human readable name on every node and child node you add. This will make the document cleaner and easier to understand. A mapping from names to ids will be returned at the end of each `execute` call so you can reference the created nodes in the next calls.
16
+
17
+ - When creating style objects that are then spread when creating nodes. Make sure to include `type` in the style object.
18
+ - Never set `id` when creating, copying, or replacing nodes or components. pen.dev will always generate unique random IDs and override the input.
19
+
20
+ - Always prefer the returned node id in the `ref` property when creating instances.
21
+ - Use `Get` to read node data, including nodes created earlier in the same call.
22
+ - `Insert`/`Copy`/`Replace` return plain id strings. To access the children of a newly created node, read its subtree with `Get` first, e.g. `Get(rowId, {depth: 1}).children`.
23
+
24
+ - Each `execute` is executed in its own scope. Local variables and helper functions are NOT shared between `execute` calls. To persist values between calls, don't use `const` or `let` when declaring variables, use `myNodeId = Insert(...)`.
25
+
26
+ ## `execute` API
27
+
28
+ Only these functions are supported in `execute`. Use other tools for other operations.
29
+
30
+ ```ts
31
+ const document: string; // predefined root node
32
+
33
+ // Mutations
34
+ function Insert(parent: string, nodeData: Child): string; // returns the inserted node's id
35
+ function Copy(path: string, parent: string, copyNodeData?: Child): string; // returns the copied node's id (descendants get new ids - Get the copy to read them)
36
+ function Update(path: string, updateData: Child): void;
37
+ function Replace(path: string, nodeData: Child): string; // returns the replacement node's id
38
+ function Move(path: string, parent: string | undefined, index?: number): void;
39
+ function Delete(path: string): void;
40
+ function Generate(nodeId: string, type: "ai" | "svg" | "stock", prompt: string): void; // generate image or SVG
41
+ function SetVariables(variables: Record<string, VariableDefinition>, replace?: boolean): void;
42
+
43
+ // Reading
44
+ function Get(path: string, options?: GetOptions): Child; // one node, children nested
45
+ function Get<T>(path: string, visit: Visit<T>, options?: GetOptions): T[]; // visit a subtree
46
+ function Get<T>(visit: Visit<T>, options?: GetOptions): T[]; // visit the whole document
47
+ function GetVariables(): { variables: Record<string, VariableDefinition>; themes?: Record<string, string[]> };
48
+ function FindEmptySpace(input: { width: number; height: number; direction?: "top" | "right" | "bottom" | "left"; padding?: number; nodeId?: string }): { x: number; y: number; parentId?: string };
49
+ function Print(...values: unknown[]): void; // add a line to the execute response: strings raw, other values as JSON, joined with spaces
50
+ function TakeScreenshot(nodeIds: string[]): void;
51
+ function Export(nodeIds: string[], format: "png" | "jpeg" | "webp" | "pdf" | "html-tailwind" | "html-css", outputPath: string, options?: ExportOptions): void; // export nodes to image or HTML files
52
+
53
+ type Visit<T> = (node: Child, ctx: Ctx) => T | undefined; // returning undefined collects nothing for this node
54
+
55
+ interface ExportOptions {
56
+ scale?: number; // image scale factor, default 2
57
+ quality?: number; // JPEG/WEBP quality 1-100
58
+ includeHtmlScaffold?: boolean; // html: full document scaffold, default true
59
+ includeLayerNames?: boolean; // html: layer names as data attributes, default true
60
+ includeLayerIds?: boolean; // html: layer ids as data attributes, default false
61
+ }
62
+
63
+ interface GetOptions {
64
+ depth?: number; // 0 = the node itself; elided children appear as "..."
65
+ resolveVariables?: boolean; // computed values instead of $variable references
66
+ resolveInstances?: boolean; // expand component instances into full subtrees
67
+ includePathGeometry?: boolean;
68
+ }
69
+
70
+ interface Ctx {
71
+ node: Child; // the visited node (= visit's first argument)
72
+ parentCtx?: Ctx; // parent's context; undefined at the top - follow the chain for ancestors
73
+ depth: number;
74
+ index: number; // position among siblings
75
+ bounds: Rect; // resolved bounds in the parent's coordinate space (same space as node x/y)
76
+ problems?: "partially clipped" | "fully clipped"; // node sticks out of its parent
77
+ skipChildren(): void; // don't descend into this node's children
78
+ }
79
+ ```
80
+
81
+ `Get` arguments are recognized by type, so unused ones are simply omitted: `Get(screen, visit)`, `Get(visit)`, `Get(id, {depth: 1})` all work. There is deliberately no visitor-less whole-document read - it would dump the entire serialized document.
82
+
83
+ The `path` argument (used by `Get`, `Copy`, `Update`, `Replace`, `Move`, `Delete`) is a node ID, or a slash-separated path to a node nested inside a component instance (`instanceId/childId`). Slashes are only valid for component-instance nesting, not normal layer structure, and work for any nesting depth.
84
+
85
+ Targets - `path` and `Insert`'s `parent` - are always id/path strings: never pass a node object; when holding a node from `Get`, pass its `.id`. Returned ids concatenate directly: `cardId + "/childId"`, `{[metricLabelId]: {...}}`. To operate on a node you only know by name, find it with a visitor:
86
+
87
+ ```js
88
+ Get(n => n.name === "Primary Button" && Insert(n.id, {type: "text", name: "Button Label", content: "RESERVE NOW", fontFamily: "Inter", fontSize: 13, fill: "#111111"}))
89
+ ```
90
+
91
+ ### Insert
92
+
93
+ - Insert a new node at the end of the children array of the specified parent node.
94
+ - An insert can only be a single node, if you want to add children to it, use the returned id in the next Insert call.
95
+ - When working with components (reusable: true), insert their instances as refs with their properties overridden. Override descendant properties inline with the `descendants` map, or with subsequent Update operations.
96
+ - Use the Replace to override children inside a component instance, e.g. `Replace("myInstance/childId",{type:"text",...})`
97
+ - Returns the inserted node's id as a string.
98
+ - To access the children of a newly inserted node, read its subtree with `Get` first (it reflects nodes created earlier in the same call): `Get(rowId, {depth: 1}).children`.
99
+
100
+ ### Copy
101
+
102
+ - "path": The ID of the existing node to copy. If you want to customize some properties of the copied node, just add them next to the `path` property. If you want to customize nested nodes _under_ the copied one, use the same kind of `descendants` map that `ref` nodes use!
103
+
104
+ - When copying a node and modifying its descendants, you MUST use the "descendants" property in the Copy operation itself. DO NOT use separate Update operations for descendants of copied nodes, as this will fail due to ID mismatches. The copied node and its descendants receive new IDs, so Update operations referencing the original descendant IDs will fail.
105
+ - `descendants`: Optional, used for components. Keys may be node IDs/paths or unique descendant names inside the referenced component. If a name matches multiple descendants, use the node ID/path instead.
106
+
107
+ - Copying a reusable node creates a connected instance (a `ref` node).
108
+ - Returns the copied node's id as a string. The copy's descendants all get new ids - read them with `Get(copiedId)` if you need to modify them individually.
109
+
110
+ ### Update
111
+
112
+ - Update the properties of existing nodes, without listing their children.
113
+ - DO NOT use this to update the node's `children`, use Replace function for that.
114
+ - This function CANNOT change the `id`, `type` or `ref` properties of any node!
115
+ - `path`: The node to update.
116
+
117
+ - `updateData`: The node data to update
118
+
119
+ ### Replace
120
+
121
+ - Replace a node with a new node. All properties including the x/y are replaced.
122
+ - This tool is ideal for swapping out parts of a component instance with new nodes.
123
+ - Returns the replacement node's id as a string
124
+ - `path`: The path of the node which will be replaced
125
+ - `nodeData`: The properties of the new node
126
+
127
+ ### Move
128
+
129
+ - Move a node to a different location in the node tree in a .pen file.
130
+ - `path`: The node to move.
131
+ - `parent`: Optional. The new parent node. If omitted, the node stays under its current parent.
132
+ - `index`: Optional new position of the moved node among its siblings. If omitted, the node is placed at the end.
133
+
134
+ ### Delete
135
+
136
+ - Delete a node from a .pen file.
137
+ - `path`: The node to delete.
138
+ - Cannot delete descendants of component instances - emulate the deletion by overriding the descendant's `enabled` property with `false` instead.
139
+
140
+ ### SetVariables
141
+
142
+ - Define or update the variables and themes of the .pen file. Read existing variables with `Print(GetVariables())` first.
143
+ - `variables`: An object keyed by variable name. Each value MUST be an object with a `type` (`"color"`, `"number"`, or `"string"`) and a `value`. Passing a bare value like `"#A3B59A"` or `16` will fail.
144
+ - Variable names are arbitrary strings and MUST NOT begin with a dollar sign. The `$` prefix is only used when referencing a variable from a property (e.g. `fill: "$accent"`).
145
+ - `replace` (optional, default `false`): when `false`, the variables are merged into the existing definitions. Pass `true` to completely replace the document's existing variable definitions.
146
+ - Don't specify themes separately. If a variable uses theming, theme axes and values that aren't yet present in the document are registered automatically. For themed values, pass an array of `{value, theme}` entries.
147
+
148
+ ```js
149
+ SetVariables({
150
+ accent: {type:"color",value:"#A3B59A"},
151
+ "spacing-unit": {type:"number",value:16},
152
+ "font-heading": {type:"string",value:"Playfair Display"},
153
+ background: {type:"color",value:[
154
+ {value:"#F8F5F0",theme:{mode:"light"}},
155
+ {value:"#1A1A1A",theme:{mode:"dark"}}
156
+ ]}
157
+ })
158
+ ```
159
+
160
+ ### GetVariables
161
+
162
+ - Returns the variables and themes currently defined in the .pen file, including changes made earlier in the same `execute` call.
163
+ - Use the result to reference existing variables, to avoid overwriting them with `SetVariables`, or to create global CSS rules when generating code from a design.
164
+ - Combine with `Print` to read the variables in the tool response: `Print(GetVariables())`.
165
+
166
+ ### Get
167
+
168
+ - Reflects changes made earlier in the same `execute` call, so you can read back what you just created or modified.
169
+ - `path` is a node ID or instance path (with `resolveInstances`, expanded node ids are full `instanceId/childId` paths that `Update`/`Replace` accept).
170
+ - Without `visit`, the returned node is pure schema data that round-trips straight into `Insert`/`Copy`/`Update`/`Replace`.
171
+ - With `visit`, every read node is visited top-down. Only an `undefined` return collects nothing - a `false` from a `cond && Update(...)` visitor is collected, which is fine when the result is discarded. Skipped nodes' children are still visited unless you call `ctx.skipChildren()`.
172
+ - Use `ctx.bounds`/`ctx.problems` to verify layout instead of guessing; `bounds` compares directly against the node's `x`/`y` and feeds straight into `Update`.
173
+ - Use `visit` to `Print` one compact row per node you care about, to apply an operation to every matching node in one pass, or to collect nodes when you need the list itself (sorting, slicing, driving loops).
174
+ - Keep visitors compact with single-letter parameter names: `(n, c) => ({id: n.id, name: n.name, w: c.bounds.width})`.
175
+ - Don't store large `Get` results in globals; only ids and small values are worth persisting between calls.
176
+
177
+ ```js
178
+ Print(Get("Xk9f2", {depth: 3})) // read a node subtree
179
+ Get(n => n.reusable && Print(n.id, n.name)) // list all components
180
+ Get(screen, n => n.type === "text" && n.fontSize < 12 && Update(n.id, {fontSize: 12}), {resolveVariables: true}) // query and modify in one pass
181
+ Get(screen, (n, c) => Print(n.id, n.name, c.depth, c.bounds.width)) // compact overview of a subtree
182
+ Get(screen, (n, c) => c.problems && Print(n.name, "in", c.parentCtx?.node.name, ":", c.problems)) // layout problems check
183
+ Get(card, (n, c) => c.parentCtx && Math.abs(c.bounds.x + c.bounds.width / 2 - c.parentCtx.bounds.width / 2) > 1 && Print(n.name, "off-center"))
184
+ const texts = Get(screen, n => n.type === "text" ? n : undefined) // collect only when you need the list (sorting, slicing, driving loops)
185
+ ```
186
+
187
+ ### Print
188
+
189
+ - Adds one line to the `execute` response so you can read it after the call completes. Each argument is printed raw if it's a string and as JSON otherwise, joined with spaces. Print only JSON-compatible values.
190
+ - Keep responses small: for row-shaped data, print compact positional lines from a `Get` visitor instead of JSON objects - your own `Print` call documents what the columns mean. Reserve JSON for single structured values.
191
+ - Values printed by a failed `execute` call are not returned.
192
+
193
+ ```js
194
+ Get(root, n => Print(n.id, "=", n.name, n.reusable ? "✓" : "")) // one compact row per node
195
+ Get(screen, (n, c) => c.problems && Print(n.name, "|", c.parentCtx?.node.name, "|", c.problems))
196
+ Print(GetVariables())
197
+ Print(FindEmptySpace({width:1440,height:1024}))
198
+ ```
199
+
200
+ ### TakeScreenshot
201
+
202
+ - Renders the given nodes and attaches the screenshots as images to the `execute` response, one per node id. Returns nothing.
203
+ - `nodeIds`: the nodes to screenshot; use `"document"` to screenshot the entire document.
204
+ - The screenshots reflect the changes made earlier in the same `execute` call.
205
+ - Good practice: when an `execute` call finishes a whole section or design, end that same call with a `TakeScreenshot` of it. There is no need to finish the generation first and issue a separate tool call just to screenshot the result.
206
+ - Screenshots are expensive - use them sparingly. Take one only after a complete section is done, not in every `execute` call.
207
+ - Prefer the smallest meaningful node (a section frame, not the whole document) - large nodes use more tokens and obscure detail.
208
+ - Use `Get` with a visitor (`ctx.bounds` carries the resolved bounds) for structural/sizing checks; reach for a screenshot only when visual fidelity (color, type, alignment) is what you need to verify.
209
+ - Screenshots of a failed `execute` call are not returned.
210
+
211
+ ```js
212
+ heroId=Insert(pageId,{type:"frame",name:"Hero",layout:"vertical",gap:24,padding:64,width:"fill_container"})
213
+ Insert(heroId,{type:"text",name:"Headline",content:"Ship faster.",fontSize:64,fontWeight:"bold",fill:"$text-primary"})
214
+ Insert(heroId,{type:"text",name:"Subhead",content:"Design at the speed of thought.",fontSize:20,fill:"$text-secondary"})
215
+ TakeScreenshot([heroId])
216
+ ```
217
+
218
+ ### Export
219
+
220
+ - Exports nodes to files on disk. Returns nothing; the absolute paths of the written files are listed in the response.
221
+ - `outputPath`: for image formats a directory - each node is written as `<nodeId>.<ext>`; for HTML formats the path of the output file.
222
+ - Image formats (`png`, `jpeg`, `webp`, `pdf`): each node is exported as a separate file at 2x scale by default. For `pdf`, all nodes are combined into a single multi-page `export.pdf`.
223
+ - HTML formats (`html-tailwind`, `html-css`): all nodes are exported into one HTML file. Image assets are referenced with relative paths, never embedded.
224
+ - Use Export to deliver final assets or hand a design off to code - not to check your work; verify visuals with `TakeScreenshot` instead.
225
+
226
+ ```js
227
+ Export([heroId, pricingId], "png", "./exports")
228
+ Export([pageId], "html-tailwind", "./landing.html")
229
+ ```
230
+
231
+ ### Generate image or SVG
232
+
233
+ - IMPORTANT: There is NO `image` node type! Images are applied as FILLS to
234
+ existing nodes; SVGs are transformed into paths and added to a parent frame.
235
+ - For `"ai"` and `"svg"`, Generate is an async, non-blocking operation. It
236
+ returns nothing and finishes independently of the execute progress.
237
+ (`"stock"` is the exception: its fill is applied during the call itself.)
238
+ - The result lands in the document after the `execute` call that started it has
239
+ already returned. The target node stays empty or unfilled until then, and
240
+ screenshots taken right away will not show it. That is expected. Never
241
+ re-generate it, draw it by hand, or add children to a frame you generated an
242
+ SVG into.
243
+ - The frame an SVG is generated into keeps `placeholder: true` while the
244
+ drawing is in progress; the flag is cleared when the generation finishes.
245
+ Check on it with a cheap read in a later `execute` call - never with
246
+ screenshots: `Print(Get(logoFrameId, {depth: 0}).placeholder)`.
247
+ - Generations are slow: SVGs can take multiple minutes. While the placeholder
248
+ flag is still set, don't wait idly: continue with the rest of the design and
249
+ re-check only occasionally, e.g. after finishing another section - not after
250
+ every execute call.
251
+ - Only when no other work is left, poll with that tiny Get/Print call, leaving
252
+ a generous interval between checks (do other verification passes in
253
+ between). Never fire checks back-to-back.
254
+ - Screenshot the generated result only after the placeholder flag is cleared.
255
+ If the flag is cleared but the frame is still empty, the generation failed -
256
+ that is the one case where calling Generate again on the same node is
257
+ correct.
258
+
259
+ #### Generating an image (`type: "ai"` or `type: "stock"`)
260
+
261
+ - Never guess or invent URLs for image fills. Always use the Generate function
262
+ to get an image from a stock or AI service.
263
+ - To display an image: first Insert a frame or rectangle, then use Generate to
264
+ apply the image as a fill to that node.
265
+ - `nodeId`: The ID of the node to apply the image fill to.
266
+ - `type`: "ai" for AI-generated images, "stock" for stock photos from Unsplash.
267
+ - `prompt`:
268
+ - For "ai": a detailed descriptive prompt.
269
+ - For "stock": a 1-3 keyword search query following the Stock query rules
270
+ (simple, concrete, no use-case or abstract terms).
271
+ - `"stock"` fills are applied during the call; if no matching photo is found,
272
+ a warning is returned and the node is left without an image fill.
273
+
274
+ #### Generating an SVG (`type: "svg"`)
275
+
276
+ - Use SVG generation for ANY drawing task: logos, illustrations, mascots,
277
+ badges, decorative artwork, abstract patterns, or anything the user asks you
278
+ to "draw". This applies both when drawing is the request itself and when a
279
+ logo or illustration is one part of a larger design you are working on.
280
+ - Never create these manually by assembling `path`, `polygon`, or other shape
281
+ nodes - hand-built artwork always looks crude. Your only drawing tool for
282
+ freeform artwork is Generate.
283
+ - To display an SVG: first Insert a frame with the desired width and height
284
+ (no other element type is allowed), then use Generate. The resulting path elements are scaled to fit the frame's bounding box and inserted as its children.
285
+ - `nodeId`: The ID of the frame to insert the SVG into.
286
+ - `type`: "svg".
287
+ - `prompt`: a detailed descriptive prompt.
288
+
289
+ ### FindEmptySpace
290
+
291
+ - Finds an empty `width` x `height` area.
292
+ - When placing objects directly to document root and you don't have an exact position, use `FindEmptySpace` at the start of your `execute` to find an empty area for your content. Never overlap root objects.
293
+ - Don't just pick random coordinates unless you know exact position from the context or the user request.
294
+ - For multiple sequential screens, use the previous element's ID as the `nodeId` parameter in `FindEmptySpace`.
295
+ - `direction` (optional, default "right"): where to search, one of "top", "right", "bottom", "left".
296
+ - `padding` (optional, default 0): minimum distance from other elements.
297
+ - `nodeId` (optional): anchor node used for chaining multiple screens together.
298
+ - Returns `{x, y, parentId?}`. Insert or Copy into `parentId` (or `document` if absent) at the returned `x`/`y`.
299
+
300
+ ## Examples
301
+
302
+ Create the reusable `Metric` component (a vertical frame with a label and a value text), capturing the returned ids:
303
+
304
+ ```js
305
+ const pos=FindEmptySpace({width:240,height:96,direction:"top", padding:80})
306
+ metricCardId=Insert(document,{type:"frame",name:"Metric",x:pos.x,y:pos.y,reusable:true,layout:"vertical",gap:4,placeholder:true})
307
+ metricLabelId=Insert(metricCardId,{type:"text",name:"Label",fontFamily:"Inter",fontSize:13,fill:"$text-secondary",content:"Label"})
308
+ metricValueId=Insert(metricCardId,{type:"text",name:"Value",fontFamily:"Inter",fontSize:28,fill:"$text-primary",content:"0"})
309
+ Update(metricCardId,{placeholder:false})
310
+ ```
311
+
312
+ Insert a screen at the document root and fill it with a loop:
313
+
314
+ ```js
315
+ const pos=FindEmptySpace({width:1440,height:1024,padding:80})
316
+ pageId=Insert(document,{type:"frame",name:"Landing Page",x:pos.x,y:pos.y,layout:"vertical",width:1440,padding:40,gap:24,clip:true,placeholder:true})
317
+
318
+ const navItem={type:"text",fontFamily:"Inter",fontSize:14,fill:"$text-secondary"}
319
+ navId=Insert(pageId,{type:"frame",name:"Nav",gap:32,alignItems:"center",width:"fill_container"})
320
+ for (const label of ["Home","Our Story","Visit","Journal"]) {
321
+ Insert(navId,{...navItem,name:label,content:label})
322
+ }
323
+ ```
324
+
325
+ Instantiate the component in a loop (even in the same `execute`), keyed by the captured `metricLabelId`/`metricValueId` ids:
326
+
327
+ ```js
328
+ rowId=Insert(pageId,{type:"frame",name:"Metrics",gap:16,width:"fill_container"})
329
+ for (const [label,value] of [["Orders","1,284"],["Revenue","$48.2K"],["Customers","9,431"]]) {
330
+ Insert(rowId,{type:"ref",ref:metricCardId,name:label,width:"fill_container",descendants:{[metricLabelId]:{content:label},[metricValueId]:{content:value}}})
331
+ }
332
+
333
+ Update(pageId,{placeholder:false})
334
+ ```
335
+
336
+ Copy an existing screen, customize its descendants in the same `Copy` call, and delete a node:
337
+
338
+ ```js
339
+ const pos=FindEmptySpace({width:1440,height:1024,padding:80})
340
+ dashboardV2Id=Copy("Xk9f2",document,{name:"Dashboard V2",x:pos.x,y:pos.y,placeholder:true,descendants:{"Jd6Ru":{fill:"#0F172A"},"Pc2Ny/Gh9Kf":{content:"Reports"}}})
341
+ Update(dashboardV2Id,{placeholder:false})
342
+
343
+ Delete("Vn4kP")
344
+ ```
345
+
346
+ Define the design tokens first, then reference them with `$` when building. Themed values are passed as `{value, theme}` arrays (the `dark` axis value is registered on the fly), so `$bg`/`$text-primary` references resolve per theme automatically:
347
+
348
+ ```js
349
+ SetVariables({
350
+ "bg":{type:"color",value:[
351
+ {value:"#FAFAF7",theme:{mode:"light"}},
352
+ {value:"#141414",theme:{mode:"dark"}}
353
+ ]},
354
+ "text-primary":{type:"color",value:[
355
+ {value:"#1A1A1A",theme:{mode:"light"}},
356
+ {value:"#F5F5F5",theme:{mode:"dark"}}
357
+ ]},
358
+ "font-body":{type:"string",value:"Inter"},
359
+ "gap-md":{type:"number",value:16}
360
+ })
361
+
362
+ cardId=Insert(document,{type:"frame",name:"Card",layout:"vertical",fill:"$bg",padding:"$gap-md",gap:"$gap-md",x:80,y:80})
363
+ Insert(cardId,{type:"text",name:"Title",fontFamily:"$font-body",fontSize:20,fill:"$text-primary",content:"Tokens applied"})
364
+ ```