Add PenpotUtils (utility functions the LLM can make use of)

This commit is contained in:
Dominik Jain 2025-09-28 17:18:02 +02:00 committed by Dominik Jain
parent afb00f6033
commit 865606b7b0
3 changed files with 90 additions and 15 deletions

View File

@ -2,29 +2,35 @@
# This file contains various prompts and instructions that can be used by the server
initial_instructions: |
You have access to Penpot tools in order to interact with Penpot design projects directly.
As a precondition, the user must connect a Penpot project to the MCP server using the Penpot MCP Plugin.
You have access to Penpot tools in order to interact with a Penpot design project directly.
As a precondition, the user must connect the Penpot design project to the MCP server using the Penpot MCP Plugin.
A Penpot project contains shapes and more general design elements (which we shall collectively refer to as "elements").
One of the key tools is the execute_code tool, which allows you to run JavaScript code using the Penpot Plugin API
One of your key tools is the `execute_code` tool, which allows you to run JavaScript code using the Penpot Plugin API
directly in the connected project.
When writing code, a key object is the `penpot` object (which is of type `Penpot`). It provides useful functionality:
To execute code correctly, you need to understand the Penpot Plugin API. You can retrieve API documentation via
the `penpot_api_info` tool.
When writing code, a key object is the `penpot` object (which is of type `Penpot`):
* `penpot.selection` provides the list of elements the user has selected in the Penpot UI.
If it is unclear which elements to work on, you can ask the user to select them for you.
* Generation of CSS content for elements:
generateStyle(shapes: Shape[], options?: {
type?: "css";
withPrelude?: boolean;
includeChildren?: boolean;
}): string;
* Generation of HTML/SVG content corresponding to elements:
generateMarkup(shapes: Shape[], options?: {
type?: "html" | "svg";
}): string;
* `penpot.root` provides the root element of the tree-structured design project, from which all shapes can be reached.
* Generation of CSS content for elements via `penpot.generateStyle`
* Generation of HTML/SVG content for elements via `penpot.generateMarkup`
For example, to generate CSS for the currently selected elements, you can execute this:
return penpot.generateStyle(penpot.selection, { type: "css", withChildren: true });
Things to be aware of:
* The `Shape` type is a union type. `ShapeBase` is a base type most shapes build upon.
Any given shape contains information on the concrete type via its `type` field.
Another useful object is the `penpotUtils` object (which not part of the official API). It provides:
* shapeStructure(shape: Shape, maxDepth: number | undefined = undefined): object
Generates an overview structure of the given shape,
providing the shape's id, name and type, and recursively the children's structure.
* findShapeById(id: string): Shape | null
* findShape(predicate: (shape: Shape) => boolean, root: Shape | null = penpot.root): Shape | null
You have hereby read the 'Penpot High-Level Overview' and need not use a tool to read it again.

View File

@ -0,0 +1,67 @@
import { Shape } from "@penpot/plugin-types";
export class PenpotUtils {
/**
* Generates an overview structure of the given shape,
* providing its id, name and type, and recursively its children's attributes.
* The `type` field indicates the type in the Penpot API.
*
* @param shape - The root shape to generate the structure from
* @param maxDepth - Optional maximum depth to traverse (leave undefined for unlimited)
* @returns An object representing the shape structure
*/
public static shapeStructure(shape: Shape, maxDepth: number | undefined = undefined): object {
let children = undefined;
if (maxDepth === undefined || maxDepth > 0) {
if ("children" in shape && shape.children) {
children = shape.children.map((child) =>
this.shapeStructure(child, maxDepth === undefined ? undefined : maxDepth - 1)
);
}
}
return {
id: shape.id,
name: shape.name,
type: shape.type,
children: children,
};
}
/**
* Finds the first shape that matches the given predicate in the given shape tree.
*
* @param predicate - A function that takes a shape and returns true if it matches the criteria
* @param root - The root shape to start the search from (defaults to penpot.root)
*/
public static findShape(predicate: (shape: Shape) => boolean, root: Shape | null = penpot.root): Shape | null {
let find = function (shape: Shape | null): Shape | null {
if (!shape) {
return null;
}
if (predicate(shape)) {
return shape;
}
if ("children" in shape && shape.children) {
for (let child of shape.children) {
let result = find(child);
if (result) {
return result;
}
}
}
return null;
};
return find(root);
}
/**
* Finds a shape by its unique ID.
*
* @param id - The unique ID of the shape to find
* @returns The shape with the matching ID, or null if not found
*/
public static findShapeById(id: string): Shape | null {
return this.findShape((shape) => shape.id === id);
}
}

View File

@ -1,5 +1,6 @@
import { Task, TaskHandler } from "../TaskHandler";
import { ExecuteCodeTaskParams, ExecuteCodeTaskResultData } from "../../../common/src";
import { PenpotUtils } from "../PenpotUtils.ts";
/**
* Console implementation that captures all log output for code execution.
@ -179,6 +180,7 @@ export class ExecuteCodeTaskHandler extends TaskHandler<ExecuteCodeTaskParams> {
penpot: penpot,
storage: {},
console: new ExecuteCodeTaskConsole(),
penpotUtils: PenpotUtils,
};
}