diff --git a/README.md b/README.md index 3002854..8d3e49c 100644 --- a/README.md +++ b/README.md @@ -12,36 +12,20 @@ Penpot's MCP Server is unlike any other you've seen. You get design-to- design, ## Architecture -The Penpot MCP server exposes tools to AI clients (LLMs), which support the retrieval +The **Penpot MCP Server** exposes tools to AI clients (LLMs), which support the retrieval of design data as well as the modification and creation of design elements. -The MCP server communicates with Penpot via a dedicated Penpot MCP plugin, -which connects to the MCP server via WebSocket. +The MCP server communicates with Penpot via the dedicated **Penpot MCP Plugin**, +which connects to the MCP server via WebSocket. +This enables the LLM to carry out tasks in the context of a design file by +executing code that leverages the Penpot Plugin API. +The LLM is free to write and execute arbitrary code snippets +within the Penpot Plugin environment to accomplish its tasks. ![Architecture](resources/architecture.png) -This repository is a monorepo containing four main components: - -1. **Common Types** (`common/`): - - Shared TypeScript definitions for request/response protocol - - Ensures type safety across server and plugin components - -2. **Penpot MCP Server** (`mcp-server/`): - - Provides MCP tools to LLMs for Penpot interaction - - Runs a WebSocket server accepting connections from the Penpot MCP plugin - - Implements request/response correlation with unique task IDs - - Handles task timeouts and proper error reporting - -3. **Penpot MCP Plugin** (`penpot-plugin/`): - - Connects to the MCP server via WebSocket - - Executes tasks in Penpot using the Plugin API - - Sends structured responses back to the server# - -4. **Helper Scripts** (`python-scripts/`): - - Python scripts that prepare data for the MCP server (development use) - -The core components are written in TypeScript, rendering interactions with the -Penpot Plugin API both natural and type-safe. - +This repository thus contains not only the MCP server implementation itself +but also the supporting Penpot MCP Plugin +(see section [Repository Structure](#repository-structure) below). ## Demonstration @@ -180,3 +164,35 @@ of the prompt input area. To add the Penpot MCP server to a Claude Code project, issue the command claude mcp add penpot -t http http://localhost:4401/mcp + +## Repository Structure + +This repository is a monorepo containing four main components: + +1. **Common Types** (`common/`): + - Shared TypeScript definitions for request/response protocol + - Ensures type safety across server and plugin components + +2. **Penpot MCP Server** (`mcp-server/`): + - Provides MCP tools to LLMs for Penpot interaction + - Runs a WebSocket server accepting connections from the Penpot MCP plugin + - Implements request/response correlation with unique task IDs + - Handles task timeouts and proper error reporting + +3. **Penpot MCP Plugin** (`penpot-plugin/`): + - Connects to the MCP server via WebSocket + - Executes tasks in Penpot using the Plugin API + - Sends structured responses back to the server# + +4. **Helper Scripts** (`python-scripts/`): + - Python scripts that prepare data for the MCP server (development use) + +The core components are written in TypeScript, rendering interactions with the +Penpot Plugin API both natural and type-safe. + +## Beyond Local Execution + +The above instructions describe how to run the MCP server and plugin server locally. +We are working on enabling remote deployments of the MCP server, particularly +in [multi-user mode](docs/multi-user-mode.md), where multiple Penpot users will +be able to connect to the same MCP server instance. diff --git a/docs/multi-user-mode.md b/docs/multi-user-mode.md new file mode 100644 index 0000000..b4471d1 --- /dev/null +++ b/docs/multi-user-mode.md @@ -0,0 +1,41 @@ +# Multi-User Mode + +> [!WARNING] +> Multi-user mode is under development and not yet fully integrated. +> This information is provided for testing purposes only. + +The Penpot MCP server supports a multi-user mode, allowing multiple Penpot users +to connect to the same MCP server instance simultaneously. +This supports remote deployments of the MCP server, without requiring each user +to run their own server instance. + +## Limitations + +Multi-user mode has the limitation that tools which read from or write to +the local file system are not supported, as the server cannot access +the client's file system. This affects the import and export tools. + +## Running Components in Multi-User Mode + +To run the MCP server and the Penpot MCP plugin in multi-user mode (for testing), +you can use the following command: + +```shell +npm run bootstrap:multi-user +``` + +This will: +* launch the MCP server in multi-user mode (adding the `--multi-user` flag), +* build and launch the Penpot MCP plugin server in multi-user mode. + +See the package.json scripts for both `mcp-server` and `penpot-plugin` for details. + +In multi-user mode, users are required to be authenticated via a token. + +* This token is provided in the URL used to connect to the MCP server, + e.g. `http://localhost:4401/mcp?userToken=USER_TOKEN`. +* The same token must be provided when connecting the Penpot MCP plugin + to the MCP server. + In the future, the token will, most likely be generated by Penpot and + provided to the plugin automatically. + :warning: For now, it is hard-coded in the plugin's source code for testing purposes. diff --git a/mcp-server/data/api_types.yml b/mcp-server/data/api_types.yml index 780d405..4e31600 100644 --- a/mcp-server/data/api_types.yml +++ b/mcp-server/data/api_types.yml @@ -63,7 +63,7 @@ Penpot: generateFontFaces(shapes: Shape[]): Promise; openViewer(): void; createPage(): Page; - openPage(page: Page): void; + openPage(page: Page, newWindow?: boolean): void; alignHorizontal(shapes: Shape[], direction: "center" | "left" | "right"): void; alignVertical(shapes: Shape[], direction: "center" | "top" | "bottom"): void; distributeHorizontal(shapes: Shape[]): void; @@ -768,7 +768,7 @@ Penpot: Returns Page openPage: |- ``` - openPage(page): void + openPage(page, newWindow?): void ``` * Changes the current open page to given page. Requires `content:read` permission. @@ -777,6 +777,9 @@ Penpot: + page: Page the page to open + + newWindow: boolean + + if true opens the page in a new window Returns void @@ -986,7 +989,7 @@ Blur: } ``` - Referenced by: Board, Boolean, Ellipse, Group, Image, Path, Rectangle, ShapeBase, SvgRaw, Text + Referenced by: Board, Boolean, Ellipse, Group, Image, Path, Rectangle, ShapeBase, SvgRaw, Text, VariantContainer members: Properties: id: |- @@ -1042,6 +1045,7 @@ Board: addGridLayout(): GridLayout; addRulerGuide(orientation: RulerGuideOrientation, value: number): RulerGuide; removeRulerGuide(guide: RulerGuide): void; + isVariantContainer(): boolean; getPluginData(key: string): string; setPluginData(key: string, value: string): void; getPluginDataKeys(): string[]; @@ -1051,6 +1055,7 @@ Board: id: string; name: string; parent: null | Shape; + parentIndex: number; x: number; y: number; width: number; @@ -1109,6 +1114,7 @@ Board: strokes: Stroke[]; layoutChild?: LayoutChildProperties; layoutCell?: LayoutChildProperties; + setParentIndex(index: number): void; isComponentInstance(): boolean; isComponentMainInstance(): boolean; isComponentCopyInstance(): boolean; @@ -1119,11 +1125,19 @@ Board: componentHead(): null | Shape; component(): null | LibraryComponent; detach(): void; + swapComponent(component: LibraryComponent): void; + switchVariant(pos: number, value: string): void; + combineAsVariants(ids: string[]): void; + isVariantHead(): boolean; resize(width: number, height: number): void; rotate(angle: number, center?: null | { x: number; y: number; }): void; + bringToFront(): void; + bringForward(): void; + sendToBack(): void; + sendBackward(): void; export(config: Export): Promise; interactions: Interaction[]; addInteraction(trigger: Trigger, action: Action, delay?: number): Interaction; @@ -1137,6 +1151,7 @@ Board: * ShapeBase + Board + - VariantContainer Referenced by: CloseOverlay, CommentThread, Context, ContextTypesUtils, Flow, NavigateTo, OpenOverlay, OverlayAction, Page, Penpot, RulerGuide, Shape, ToggleOverlay members: @@ -1209,6 +1224,14 @@ Board: ``` The children shapes contained within the board. + When writing into this property, you can only reorder the shapes, not + changing the structure. If the new shapes don't match the current shapes + it will give a validation error. + + Example + ``` + board.children = board.children.reverse(); + ``` id: |- ``` id: string @@ -1228,6 +1251,12 @@ Board: The parent shape. If the shape is the first level the parent will be the root shape. For the root shape the parent is null + parentIndex: |- + ``` + parentIndex: number + ``` + + Returns the index of the current shape in the parent x: |- ``` x: number @@ -1551,6 +1580,14 @@ Board: + guide: RulerGuide Returns void + isVariantContainer: |- + ``` + isVariantContainer(): boolean + ``` + + * Returns boolean + + Returns true when the current board is a VariantContainer getPluginData: |- ``` getPluginData(key): string @@ -1674,6 +1711,22 @@ Board: ``` const sharedKeys = shape.getSharedPluginDataKeys('exampleNamespace');console.log(sharedKeys); ``` + setParentIndex: |- + ``` + setParentIndex(index): void + ``` + + * Changes the index inside the parent of the current shape. + This method will shift the indexes of the shapes around that position to + match the index. + If the index is greater than the number of elements it will positioned last. + + Parameters + + index: number + + the new index for the shape to be in + + Returns void isComponentInstance: |- ``` isComponentInstance(): boolean @@ -1759,6 +1812,57 @@ Board: shape as a "basic shape" Returns void + swapComponent: |- + ``` + swapComponent(component): void + ``` + + * TODO + + Parameters + + component: LibraryComponent + + Returns void + switchVariant: |- + ``` + switchVariant(pos, value): void + ``` + + * Switch a VariantComponent copy to the nearest one that has the specified property value + + Parameters + + pos: number + + The position of the poroperty to update + + value: string + + The new value of the property + + Returns void + combineAsVariants: |- + ``` + combineAsVariants(ids): void + ``` + + * Combine several standard Components into a VariantComponent. Similar to doing it with the contextual menu + on the Penpot interface. + The current shape must be a component main instance. + + Parameters + + ids: string[] + + A list of ids of the main instances of the components to combine with this one. + + Returns void + isVariantHead: |- + ``` + isVariantHead(): boolean + ``` + + * Returns boolean + + Returns true when the current shape is the head of a components tree nested structure, + and that component is a VariantComponent resize: |- ``` resize(width, height): void @@ -1804,6 +1908,1040 @@ Board: ``` shape.rotate(45); ``` + bringToFront: |- + ``` + bringToFront(): void + ``` + + * Moves the current shape to the front of its siblings + + Returns void + bringForward: |- + ``` + bringForward(): void + ``` + + * Moves the current shape one position forward in its list of siblings + + Returns void + sendToBack: |- + ``` + sendToBack(): void + ``` + + * Moves the current shape to the back of its siblings + + Returns void + sendBackward: |- + ``` + sendBackward(): void + ``` + + * Moves the current shape one position backwards in its list of siblings + + Returns void + export: |- + ``` + export(config): Promise + ``` + + * Generates an export from the current shape. + + Parameters + + config: Export + + Returns Promise + + Example + ``` + shape.export({ type: 'png', scale: 2 }); + ``` + addInteraction: |- + ``` + addInteraction(trigger, action, delay?): Interaction + ``` + + * Adds a new interaction to the shape. + + Parameters + + trigger: Trigger + + defines the conditions under which the action will be triggered + + action: Action + + defines what will be executed when the trigger happens + + delay: number + + for the type of trigger `after-delay` will specify the time after triggered. Ignored otherwise. + + Returns Interaction + + Example + ``` + shape.addInteraction('click', { type: 'navigate-to', destination: anotherBoard }); + ``` + removeInteraction: |- + ``` + removeInteraction(interaction): void + ``` + + * Removes the interaction from the shape. + + Parameters + + interaction: Interaction + + is the interaction to remove from the shape + + Returns void + + Example + ``` + shape.removeInteraction(interaction); + ``` + clone: |- + ``` + clone(): Shape + ``` + + * Creates a clone of the shape. + + Returns Shape + + Returns a new instance of the shape with identical properties. + remove: |- + ``` + remove(): void + ``` + + * Removes the shape from its parent. + + Returns void +VariantContainer: + overview: |- + Interface VariantContainer + ========================== + + Represents a VariantContainer in Penpot + This interface extends `Board` and includes properties and methods specific to VariantContainer. + + ``` + interface VariantContainer { + type: "board"; + clipContent: boolean; + showInViewMode: boolean; + grid?: GridLayout; + flex?: FlexLayout; + guides: Guide[]; + rulerGuides: RulerGuide[]; + horizontalSizing?: "auto" | "fix"; + verticalSizing?: "auto" | "fix"; + fills: Fill[]; + children: Shape[]; + appendChild(child: Shape): void; + insertChild(index: number, child: Shape): void; + addFlexLayout(): FlexLayout; + addGridLayout(): GridLayout; + addRulerGuide(orientation: RulerGuideOrientation, value: number): RulerGuide; + removeRulerGuide(guide: RulerGuide): void; + isVariantContainer(): boolean; + variants: null | Variants; + getPluginData(key: string): string; + setPluginData(key: string, value: string): void; + getPluginDataKeys(): string[]; + getSharedPluginData(namespace: string, key: string): string; + setSharedPluginData(namespace: string, key: string, value: string): void; + getSharedPluginDataKeys(namespace: string): string[]; + id: string; + name: string; + parent: null | Shape; + parentIndex: number; + x: number; + y: number; + width: number; + height: number; + bounds: Bounds; + center: Point; + blocked: boolean; + hidden: boolean; + visible: boolean; + proportionLock: boolean; + constraintsHorizontal: + | "center" + | "left" + | "right" + | "leftright" + | "scale"; + constraintsVertical: + | "center" + | "top" + | "bottom" + | "scale" + | "topbottom"; + borderRadius: number; + borderRadiusTopLeft: number; + borderRadiusTopRight: number; + borderRadiusBottomRight: number; + borderRadiusBottomLeft: number; + opacity: number; + blendMode: + | "difference" + | "normal" + | "darken" + | "multiply" + | "color-burn" + | "lighten" + | "screen" + | "color-dodge" + | "overlay" + | "soft-light" + | "hard-light" + | "exclusion" + | "hue" + | "saturation" + | "color" + | "luminosity"; + shadows: Shadow[]; + blur?: Blur; + exports: Export[]; + boardX: number; + boardY: number; + parentX: number; + parentY: number; + flipX: boolean; + flipY: boolean; + rotation: number; + strokes: Stroke[]; + layoutChild?: LayoutChildProperties; + layoutCell?: LayoutChildProperties; + setParentIndex(index: number): void; + isComponentInstance(): boolean; + isComponentMainInstance(): boolean; + isComponentCopyInstance(): boolean; + isComponentRoot(): boolean; + isComponentHead(): boolean; + componentRefShape(): null | Shape; + componentRoot(): null | Shape; + componentHead(): null | Shape; + component(): null | LibraryComponent; + detach(): void; + swapComponent(component: LibraryComponent): void; + switchVariant(pos: number, value: string): void; + combineAsVariants(ids: string[]): void; + isVariantHead(): boolean; + resize(width: number, height: number): void; + rotate(angle: number, center?: null | { + x: number; + y: number; + }): void; + bringToFront(): void; + bringForward(): void; + sendToBack(): void; + sendBackward(): void; + export(config: Export): Promise; + interactions: Interaction[]; + addInteraction(trigger: Trigger, action: Action, delay?: number): Interaction; + removeInteraction(interaction: Interaction): void; + clone(): Shape; + remove(): void; + } + ``` + + Hierarchy (view full) + + * Board + + VariantContainer + + Referenced by: ContextTypesUtils + members: + Properties: + type: |- + ``` + type + ``` + + The type of the shape, which is always 'board' for boards. + clipContent: |- + ``` + clipContent: boolean + ``` + + When true the board will clip the children inside + showInViewMode: |- + ``` + showInViewMode: boolean + ``` + + WHen true the board will be displayed in the view mode + grid: |- + ``` + grid?: GridLayout + ``` + + The grid layout configuration of the board, if applicable. + flex: |- + ``` + flex?: FlexLayout + ``` + + The flex layout configuration of the board, if applicable. + guides: |- + ``` + guides: Guide[] + ``` + + The guides associated with the board. + rulerGuides: |- + ``` + rulerGuides: RulerGuide[] + ``` + + The ruler guides attached to the board + horizontalSizing: |- + ``` + horizontalSizing?: "auto" | "fix" + ``` + + The horizontal sizing behavior of the board. + verticalSizing: |- + ``` + verticalSizing?: "auto" | "fix" + ``` + + The vertical sizing behavior of the board. + fills: |- + ``` + fills: Fill[] + ``` + + The fills applied to the shape. + children: |- + ``` + children: Shape[] + ``` + + The children shapes contained within the board. + When writing into this property, you can only reorder the shapes, not + changing the structure. If the new shapes don't match the current shapes + it will give a validation error. + + Example + ``` + board.children = board.children.reverse(); + ``` + variants: |- + ``` + variants: null | Variants + ``` + + Access to the Variant interface, for attributes and actions over the full Variant (not only this VariantContainer) + id: |- + ``` + id: string + ``` + + The unique identifier of the shape. + name: |- + ``` + name: string + ``` + + The name of the shape. + parent: |- + ``` + parent: null | Shape + ``` + + The parent shape. If the shape is the first level the parent will be the root shape. + For the root shape the parent is null + parentIndex: |- + ``` + parentIndex: number + ``` + + Returns the index of the current shape in the parent + x: |- + ``` + x: number + ``` + + The x-coordinate of the shape's position. + y: |- + ``` + y: number + ``` + + The y-coordinate of the shape's position. + width: |- + ``` + width: number + ``` + + The width of the shape. + height: |- + ``` + height: number + ``` + + The height of the shape. + bounds: |- + ``` + bounds: Bounds + ``` + + Returns + + Returns the bounding box surrounding the current shape + center: |- + ``` + center: Point + ``` + + Returns + + Returns the geometric center of the shape + blocked: |- + ``` + blocked: boolean + ``` + + Indicates whether the shape is blocked. + hidden: |- + ``` + hidden: boolean + ``` + + Indicates whether the shape is hidden. + visible: |- + ``` + visible: boolean + ``` + + Indicates whether the shape is visible. + proportionLock: |- + ``` + proportionLock: boolean + ``` + + Indicates whether the shape has proportion lock enabled. + constraintsHorizontal: |- + ``` + constraintsHorizontal: + | "center" + | "left" + | "right" + | "leftright" + | "scale" + ``` + + The horizontal constraints applied to the shape. + constraintsVertical: |- + ``` + constraintsVertical: + | "center" + | "top" + | "bottom" + | "scale" + | "topbottom" + ``` + + The vertical constraints applied to the shape. + borderRadius: |- + ``` + borderRadius: number + ``` + + The border radius of the shape. + borderRadiusTopLeft: |- + ``` + borderRadiusTopLeft: number + ``` + + The border radius of the top-left corner of the shape. + borderRadiusTopRight: |- + ``` + borderRadiusTopRight: number + ``` + + The border radius of the top-right corner of the shape. + borderRadiusBottomRight: |- + ``` + borderRadiusBottomRight: number + ``` + + The border radius of the bottom-right corner of the shape. + borderRadiusBottomLeft: |- + ``` + borderRadiusBottomLeft: number + ``` + + The border radius of the bottom-left corner of the shape. + opacity: |- + ``` + opacity: number + ``` + + The opacity of the shape. + blendMode: |- + ``` + blendMode: + | "difference" + | "normal" + | "darken" + | "multiply" + | "color-burn" + | "lighten" + | "screen" + | "color-dodge" + | "overlay" + | "soft-light" + | "hard-light" + | "exclusion" + | "hue" + | "saturation" + | "color" + | "luminosity" + ``` + + The blend mode applied to the shape. + shadows: |- + ``` + shadows: Shadow[] + ``` + + The shadows applied to the shape. + blur: |- + ``` + blur?: Blur + ``` + + The blur effect applied to the shape. + exports: |- + ``` + exports: Export[] + ``` + + The export settings of the shape. + boardX: |- + ``` + boardX: number + ``` + + The x-coordinate of the shape relative to its board. + boardY: |- + ``` + boardY: number + ``` + + The y-coordinate of the shape relative to its board. + parentX: |- + ``` + parentX: number + ``` + + The x-coordinate of the shape relative to its parent. + parentY: |- + ``` + parentY: number + ``` + + The y-coordinate of the shape relative to its parent. + flipX: |- + ``` + flipX: boolean + ``` + + Indicates whether the shape is flipped horizontally. + flipY: |- + ``` + flipY: boolean + ``` + + Indicates whether the shape is flipped vertically. + rotation: |- + ``` + rotation: number + ``` + + Returns + + Returns the rotation in degrees of the shape with respect to it's center. + strokes: |- + ``` + strokes: Stroke[] + ``` + + The strokes applied to the shape. + layoutChild: |- + ``` + layoutChild?: LayoutChildProperties + ``` + + Layout properties for children of the shape. + layoutCell: |- + ``` + layoutCell?: LayoutChildProperties + ``` + + Layout properties for cells in a grid layout. + interactions: |- + ``` + interactions: Interaction[] + ``` + + The interactions for the current shape. + Methods: + appendChild: |- + ``` + appendChild(child): void + ``` + + * Appends a child shape to the board. + + Parameters + + child: Shape + + The child shape to append. + + Returns void + + Example + ``` + board.appendChild(childShape); + ``` + insertChild: |- + ``` + insertChild(index, child): void + ``` + + * Inserts a child shape at the specified index within the board. + + Parameters + + index: number + + The index at which to insert the child shape. + + child: Shape + + The child shape to insert. + + Returns void + + Example + ``` + board.insertChild(0, childShape); + ``` + addFlexLayout: |- + ``` + addFlexLayout(): FlexLayout + ``` + + * Adds a flex layout configuration to the board (so it's necessary to create a board first of all). + + Returns FlexLayout + + Returns the flex layout configuration added to the board. + + Example + ``` + const board = penpot.createBoard();const flex = board.addFlexLayout();// You can change the flex properties as follows.flex.dir = "column";flex.wrap = "wrap";flex.alignItems = "center";flex.justifyContent = "center";flex.horizontalSizing = "fill";flex.verticalSizing = "fill"; + ``` + addGridLayout: |- + ``` + addGridLayout(): GridLayout + ``` + + * Adds a grid layout configuration to the board (so it's necessary to create a board first of all). You can add rows and columns, check addRow/addColumn. + + Returns GridLayout + + Returns the grid layout configuration added to the board. + + Example + ``` + const board = penpot.createBoard();const grid = board.addGridLayout();// You can change the grid properties as follows.grid.alignItems = "center";grid.justifyItems = "start";grid.rowGap = 10;grid.columnGap = 10;grid.verticalPadding = 5;grid.horizontalPadding = 5 + ``` + addRulerGuide: |- + ``` + addRulerGuide(orientation, value): RulerGuide + ``` + + * Creates a new ruler guide. + + Parameters + + orientation: RulerGuideOrientation + + value: number + + Returns RulerGuide + removeRulerGuide: |- + ``` + removeRulerGuide(guide): void + ``` + + * Removes the `guide` from the current page. + + Parameters + + guide: RulerGuide + + Returns void + isVariantContainer: |- + ``` + isVariantContainer(): boolean + ``` + + * Returns boolean + + Returns true when the current board is a VariantContainer + getPluginData: |- + ``` + getPluginData(key): string + ``` + + * Retrieves the data for our own plugin, given a specific key. + + Parameters + + key: string + + The key for which to retrieve the data. + + Returns string + + Returns the data associated with the key as a string. + + Example + ``` + const data = shape.getPluginData('exampleKey');console.log(data); + ``` + setPluginData: |- + ``` + setPluginData(key, value): void + ``` + + * Sets the plugin-specific data for the given key. + + Parameters + + key: string + + The key for which to set the data. + + value: string + + The data to set for the key. + + Returns void + + Example + ``` + shape.setPluginData('exampleKey', 'exampleValue'); + ``` + getPluginDataKeys: |- + ``` + getPluginDataKeys(): string[] + ``` + + * Retrieves all the keys for the plugin-specific data. + + Returns string[] + + Returns an array of strings representing all the keys. + + Example + ``` + const keys = shape.getPluginDataKeys();console.log(keys); + ``` + getSharedPluginData: |- + ``` + getSharedPluginData(namespace, key): string + ``` + + * If we know the namespace of an external plugin, this is the way to get their data. + + Parameters + + namespace: string + + The namespace for the shared data. + + key: string + + The key for which to retrieve the data. + + Returns string + + Returns the shared data associated with the key as a string. + + Example + ``` + const sharedData = shape.getSharedPluginData('exampleNamespace', 'exampleKey');console.log(sharedData); + ``` + setSharedPluginData: |- + ``` + setSharedPluginData(namespace, key, value): void + ``` + + * Sets the shared plugin-specific data for the given namespace and key. + + Parameters + + namespace: string + + The namespace for the shared data. + + key: string + + The key for which to set the data. + + value: string + + The data to set for the key. + + Returns void + + Example + ``` + shape.setSharedPluginData('exampleNamespace', 'exampleKey', 'exampleValue'); + ``` + getSharedPluginDataKeys: |- + ``` + getSharedPluginDataKeys(namespace): string[] + ``` + + * Retrieves all the keys for the shared plugin-specific data in the given namespace. + + Parameters + + namespace: string + + The namespace for the shared data. + + Returns string[] + + Returns an array of strings representing all the keys in the namespace. + + Example + ``` + const sharedKeys = shape.getSharedPluginDataKeys('exampleNamespace');console.log(sharedKeys); + ``` + setParentIndex: |- + ``` + setParentIndex(index): void + ``` + + * Changes the index inside the parent of the current shape. + This method will shift the indexes of the shapes around that position to + match the index. + If the index is greater than the number of elements it will positioned last. + + Parameters + + index: number + + the new index for the shape to be in + + Returns void + isComponentInstance: |- + ``` + isComponentInstance(): boolean + ``` + + * Returns boolean + + Returns true if the current shape is inside a component instance + isComponentMainInstance: |- + ``` + isComponentMainInstance(): boolean + ``` + + * Returns boolean + + Returns true if the current shape is inside a component **main** instance + isComponentCopyInstance: |- + ``` + isComponentCopyInstance(): boolean + ``` + + * Returns boolean + + Returns true if the current shape is inside a component **copy** instance + isComponentRoot: |- + ``` + isComponentRoot(): boolean + ``` + + * Returns boolean + + Returns true when the current shape is the root of a component tree + isComponentHead: |- + ``` + isComponentHead(): boolean + ``` + + * Returns boolean + + Returns true when the current shape is the head of a components tree nested structure + componentRefShape: |- + ``` + componentRefShape(): null | Shape + ``` + + * Returns null | Shape + + Returns the equivalent shape in the component main instance. If the current shape is inside a + main instance will return `null`; + componentRoot: |- + ``` + componentRoot(): null | Shape + ``` + + * Returns null | Shape + + Returns the root of the component tree structure for the current shape. If the current shape + is already a root will return itself. + componentHead: |- + ``` + componentHead(): null | Shape + ``` + + * Returns null | Shape + + Returns the head of the component tree structure for the current shape. If the current shape + is already a head will return itself. + component: |- + ``` + component(): null | LibraryComponent + ``` + + * Returns null | LibraryComponent + + If the shape is a component instance, returns the reference to the component associated + otherwise will return null + detach: |- + ``` + detach(): void + ``` + + * If the current shape is a component it will remove the component information and leave the + shape as a "basic shape" + + Returns void + swapComponent: |- + ``` + swapComponent(component): void + ``` + + * TODO + + Parameters + + component: LibraryComponent + + Returns void + switchVariant: |- + ``` + switchVariant(pos, value): void + ``` + + * Switch a VariantComponent copy to the nearest one that has the specified property value + + Parameters + + pos: number + + The position of the poroperty to update + + value: string + + The new value of the property + + Returns void + combineAsVariants: |- + ``` + combineAsVariants(ids): void + ``` + + * Combine several standard Components into a VariantComponent. Similar to doing it with the contextual menu + on the Penpot interface. + The current shape must be a component main instance. + + Parameters + + ids: string[] + + A list of ids of the main instances of the components to combine with this one. + + Returns void + isVariantHead: |- + ``` + isVariantHead(): boolean + ``` + + * Returns boolean + + Returns true when the current shape is the head of a components tree nested structure, + and that component is a VariantComponent + resize: |- + ``` + resize(width, height): void + ``` + + * Resizes the shape to the specified width and height. + + Parameters + + width: number + + The new width of the shape. + + height: number + + The new height of the shape. + + Returns void + + Example + ``` + shape.resize(200, 100); + ``` + rotate: |- + ``` + rotate(angle, center?): void + ``` + + * Rotates the shape in relation with the given center. + + Parameters + + angle: number + + Angle in degrees to rotate. + + center: null | { + x: number; + y: number; + } + + Center of the transform rotation. If not send will use the geometri center of the shapes. + + Returns void + + Example + ``` + shape.rotate(45); + ``` + bringToFront: |- + ``` + bringToFront(): void + ``` + + * Moves the current shape to the front of its siblings + + Returns void + bringForward: |- + ``` + bringForward(): void + ``` + + * Moves the current shape one position forward in its list of siblings + + Returns void + sendToBack: |- + ``` + sendToBack(): void + ``` + + * Moves the current shape to the back of its siblings + + Returns void + sendBackward: |- + ``` + sendBackward(): void + ``` + + * Moves the current shape one position backwards in its list of siblings + + Returns void export: |- ``` export(config): Promise @@ -1892,7 +3030,9 @@ Boolean: interface Boolean { type: "boolean"; toD(): string; - content: PathCommand[]; + content: string; + d: string; + commands: PathCommand[]; fills: Fill[]; children: Shape[]; appendChild(child: Shape): void; @@ -1906,6 +3046,7 @@ Boolean: id: string; name: string; parent: null | Shape; + parentIndex: number; x: number; y: number; width: number; @@ -1964,6 +3105,7 @@ Boolean: strokes: Stroke[]; layoutChild?: LayoutChildProperties; layoutCell?: LayoutChildProperties; + setParentIndex(index: number): void; isComponentInstance(): boolean; isComponentMainInstance(): boolean; isComponentCopyInstance(): boolean; @@ -1974,11 +3116,19 @@ Boolean: componentHead(): null | Shape; component(): null | LibraryComponent; detach(): void; + swapComponent(component: LibraryComponent): void; + switchVariant(pos: number, value: string): void; + combineAsVariants(ids: string[]): void; + isVariantHead(): boolean; resize(width: number, height: number): void; rotate(angle: number, center?: null | { x: number; y: number; }): void; + bringToFront(): void; + bringForward(): void; + sendToBack(): void; + sendBackward(): void; export(config: Export): Promise; interactions: Interaction[]; addInteraction(trigger: Trigger, action: Action, delay?: number): Interaction; @@ -2004,7 +3154,23 @@ Boolean: The type of the shape, which is always 'bool' for boolean operation shapes. content: |- ``` - content: PathCommand[] + content: string + ``` + + The content of the boolean shape, defined as the path string. + + Deprecated + + Use either `d` or `commands`. + d: |- + ``` + d: string + ``` + + The content of the boolean shape, defined as the path string. + commands: |- + ``` + commands: PathCommand[] ``` The content of the boolean shape, defined as an array of path commands. @@ -2041,6 +3207,12 @@ Boolean: The parent shape. If the shape is the first level the parent will be the root shape. For the root shape the parent is null + parentIndex: |- + ``` + parentIndex: number + ``` + + Returns the index of the current shape in the parent x: |- ``` x: number @@ -2282,6 +3454,10 @@ Boolean: Returns string Returns the path data (d attribute) as a string. + + Deprecated + + Use the `d` attribute appendChild: |- ``` appendChild(child): void @@ -2444,6 +3620,22 @@ Boolean: ``` const sharedKeys = shape.getSharedPluginDataKeys('exampleNamespace');console.log(sharedKeys); ``` + setParentIndex: |- + ``` + setParentIndex(index): void + ``` + + * Changes the index inside the parent of the current shape. + This method will shift the indexes of the shapes around that position to + match the index. + If the index is greater than the number of elements it will positioned last. + + Parameters + + index: number + + the new index for the shape to be in + + Returns void isComponentInstance: |- ``` isComponentInstance(): boolean @@ -2529,6 +3721,57 @@ Boolean: shape as a "basic shape" Returns void + swapComponent: |- + ``` + swapComponent(component): void + ``` + + * TODO + + Parameters + + component: LibraryComponent + + Returns void + switchVariant: |- + ``` + switchVariant(pos, value): void + ``` + + * Switch a VariantComponent copy to the nearest one that has the specified property value + + Parameters + + pos: number + + The position of the poroperty to update + + value: string + + The new value of the property + + Returns void + combineAsVariants: |- + ``` + combineAsVariants(ids): void + ``` + + * Combine several standard Components into a VariantComponent. Similar to doing it with the contextual menu + on the Penpot interface. + The current shape must be a component main instance. + + Parameters + + ids: string[] + + A list of ids of the main instances of the components to combine with this one. + + Returns void + isVariantHead: |- + ``` + isVariantHead(): boolean + ``` + + * Returns boolean + + Returns true when the current shape is the head of a components tree nested structure, + and that component is a VariantComponent resize: |- ``` resize(width, height): void @@ -2574,6 +3817,38 @@ Boolean: ``` shape.rotate(45); ``` + bringToFront: |- + ``` + bringToFront(): void + ``` + + * Moves the current shape to the front of its siblings + + Returns void + bringForward: |- + ``` + bringForward(): void + ``` + + * Moves the current shape one position forward in its list of siblings + + Returns void + sendToBack: |- + ``` + sendToBack(): void + ``` + + * Moves the current shape to the back of its siblings + + Returns void + sendBackward: |- + ``` + sendBackward(): void + ``` + + * Moves the current shape one position backwards in its list of siblings + + Returns void export: |- ``` export(config): Promise @@ -3239,7 +4514,7 @@ Context: removeListener(listenerId: symbol): void; openViewer(): void; createPage(): Page; - openPage(page: Page): void; + openPage(page: Page, newWindow?: boolean): void; alignHorizontal(shapes: Shape[], direction: "center" | "left" | "right"): void; alignVertical(shapes: Shape[], direction: "center" | "top" | "bottom"): void; distributeHorizontal(shapes: Shape[]): void; @@ -3771,7 +5046,7 @@ Context: Returns Page openPage: |- ``` - openPage(page): void + openPage(page, newWindow?): void ``` * Changes the current open page to given page. Requires `content:read` permission. @@ -3780,6 +5055,9 @@ Context: + page: Page the page to open + + newWindow: boolean + + if true opens the page in a new window Returns void @@ -3932,6 +5210,8 @@ ContextTypesUtils: isText(shape: Shape): shape is Text; isEllipse(shape: Shape): shape is Ellipse; isSVG(shape: Shape): shape is SvgRaw; + isVariantContainer(shape: Shape): shape is VariantContainer; + isVariantComponent(component: LibraryComponent): component is LibraryVariantComponent; } ``` @@ -4073,6 +5353,36 @@ ContextTypesUtils: Returns shape is SvgRaw Returns true if the shape is a SvgRaw, otherwise false. + isVariantContainer: |- + ``` + isVariantContainer(shape): shape is VariantContainer + ``` + + * Checks if the given shape is a variant container. + + Parameters + + shape: Shape + + The shape to check. + + Returns shape is VariantContainer + + Returns true if the shape is a variant container, otherwise false. + isVariantComponent: |- + ``` + isVariantComponent(component): component is LibraryVariantComponent + ``` + + * Checks if the given component is a VariantComponent. + + Parameters + + component: LibraryComponent + + The component to check. + + Returns component is LibraryVariantComponent + + Returns true if the component is a VariantComponent, otherwise false. ContextUtils: overview: |- Interface ContextUtils @@ -4182,6 +5492,7 @@ Ellipse: id: string; name: string; parent: null | Shape; + parentIndex: number; x: number; y: number; width: number; @@ -4240,6 +5551,7 @@ Ellipse: strokes: Stroke[]; layoutChild?: LayoutChildProperties; layoutCell?: LayoutChildProperties; + setParentIndex(index: number): void; isComponentInstance(): boolean; isComponentMainInstance(): boolean; isComponentCopyInstance(): boolean; @@ -4250,11 +5562,19 @@ Ellipse: componentHead(): null | Shape; component(): null | LibraryComponent; detach(): void; + swapComponent(component: LibraryComponent): void; + switchVariant(pos: number, value: string): void; + combineAsVariants(ids: string[]): void; + isVariantHead(): boolean; resize(width: number, height: number): void; rotate(angle: number, center?: null | { x: number; y: number; }): void; + bringToFront(): void; + bringForward(): void; + sendToBack(): void; + sendBackward(): void; export(config: Export): Promise; interactions: Interaction[]; addInteraction(trigger: Trigger, action: Action, delay?: number): Interaction; @@ -4303,6 +5623,12 @@ Ellipse: The parent shape. If the shape is the first level the parent will be the root shape. For the root shape the parent is null + parentIndex: |- + ``` + parentIndex: number + ``` + + Returns the index of the current shape in the parent x: |- ``` x: number @@ -4657,6 +5983,22 @@ Ellipse: ``` const sharedKeys = shape.getSharedPluginDataKeys('exampleNamespace');console.log(sharedKeys); ``` + setParentIndex: |- + ``` + setParentIndex(index): void + ``` + + * Changes the index inside the parent of the current shape. + This method will shift the indexes of the shapes around that position to + match the index. + If the index is greater than the number of elements it will positioned last. + + Parameters + + index: number + + the new index for the shape to be in + + Returns void isComponentInstance: |- ``` isComponentInstance(): boolean @@ -4742,6 +6084,57 @@ Ellipse: shape as a "basic shape" Returns void + swapComponent: |- + ``` + swapComponent(component): void + ``` + + * TODO + + Parameters + + component: LibraryComponent + + Returns void + switchVariant: |- + ``` + switchVariant(pos, value): void + ``` + + * Switch a VariantComponent copy to the nearest one that has the specified property value + + Parameters + + pos: number + + The position of the poroperty to update + + value: string + + The new value of the property + + Returns void + combineAsVariants: |- + ``` + combineAsVariants(ids): void + ``` + + * Combine several standard Components into a VariantComponent. Similar to doing it with the contextual menu + on the Penpot interface. + The current shape must be a component main instance. + + Parameters + + ids: string[] + + A list of ids of the main instances of the components to combine with this one. + + Returns void + isVariantHead: |- + ``` + isVariantHead(): boolean + ``` + + * Returns boolean + + Returns true when the current shape is the head of a components tree nested structure, + and that component is a VariantComponent resize: |- ``` resize(width, height): void @@ -4787,6 +6180,38 @@ Ellipse: ``` shape.rotate(45); ``` + bringToFront: |- + ``` + bringToFront(): void + ``` + + * Moves the current shape to the front of its siblings + + Returns void + bringForward: |- + ``` + bringForward(): void + ``` + + * Moves the current shape one position forward in its list of siblings + + Returns void + sendToBack: |- + ``` + sendToBack(): void + ``` + + * Moves the current shape to the back of its siblings + + Returns void + sendBackward: |- + ``` + sendBackward(): void + ``` + + * Moves the current shape one position backwards in its list of siblings + + Returns void export: |- ``` export(config): Promise @@ -4956,7 +6381,7 @@ Export: } ``` - Referenced by: Board, Boolean, Ellipse, Group, Image, Path, Rectangle, ShapeBase, SvgRaw, Text + Referenced by: Board, Boolean, Ellipse, Group, Image, Path, Rectangle, ShapeBase, SvgRaw, Text, VariantContainer members: Properties: type: |- @@ -5298,7 +6723,7 @@ Fill: } ``` - Referenced by: Board, Boolean, Ellipse, Group, Image, LibraryColor, Path, Rectangle, ShapeBase, SvgRaw, Text, TextRange + Referenced by: Board, Boolean, Ellipse, Group, Image, LibraryColor, Path, Rectangle, ShapeBase, SvgRaw, Text, TextRange, VariantContainer members: Properties: fillColor: |- @@ -5401,7 +6826,7 @@ FlexLayout: * CommonLayout + FlexLayout - Referenced by: Board + Referenced by: Board, VariantContainer members: Properties: alignItems: |- @@ -5976,7 +7401,7 @@ GridLayout: * CommonLayout + GridLayout - Referenced by: Board + Referenced by: Board, VariantContainer members: Properties: alignItems: |- @@ -6379,6 +7804,7 @@ Group: id: string; name: string; parent: null | Shape; + parentIndex: number; x: number; y: number; width: number; @@ -6438,6 +7864,7 @@ Group: strokes: Stroke[]; layoutChild?: LayoutChildProperties; layoutCell?: LayoutChildProperties; + setParentIndex(index: number): void; isComponentInstance(): boolean; isComponentMainInstance(): boolean; isComponentCopyInstance(): boolean; @@ -6448,11 +7875,19 @@ Group: componentHead(): null | Shape; component(): null | LibraryComponent; detach(): void; + swapComponent(component: LibraryComponent): void; + switchVariant(pos: number, value: string): void; + combineAsVariants(ids: string[]): void; + isVariantHead(): boolean; resize(width: number, height: number): void; rotate(angle: number, center?: null | { x: number; y: number; }): void; + bringToFront(): void; + bringForward(): void; + sendToBack(): void; + sendBackward(): void; export(config: Export): Promise; interactions: Interaction[]; addInteraction(trigger: Trigger, action: Action, delay?: number): Interaction; @@ -6501,6 +7936,12 @@ Group: The parent shape. If the shape is the first level the parent will be the root shape. For the root shape the parent is null + parentIndex: |- + ``` + parentIndex: number + ``` + + Returns the index of the current shape in the parent x: |- ``` x: number @@ -6925,6 +8366,22 @@ Group: ``` const sharedKeys = shape.getSharedPluginDataKeys('exampleNamespace');console.log(sharedKeys); ``` + setParentIndex: |- + ``` + setParentIndex(index): void + ``` + + * Changes the index inside the parent of the current shape. + This method will shift the indexes of the shapes around that position to + match the index. + If the index is greater than the number of elements it will positioned last. + + Parameters + + index: number + + the new index for the shape to be in + + Returns void isComponentInstance: |- ``` isComponentInstance(): boolean @@ -7010,6 +8467,57 @@ Group: shape as a "basic shape" Returns void + swapComponent: |- + ``` + swapComponent(component): void + ``` + + * TODO + + Parameters + + component: LibraryComponent + + Returns void + switchVariant: |- + ``` + switchVariant(pos, value): void + ``` + + * Switch a VariantComponent copy to the nearest one that has the specified property value + + Parameters + + pos: number + + The position of the poroperty to update + + value: string + + The new value of the property + + Returns void + combineAsVariants: |- + ``` + combineAsVariants(ids): void + ``` + + * Combine several standard Components into a VariantComponent. Similar to doing it with the contextual menu + on the Penpot interface. + The current shape must be a component main instance. + + Parameters + + ids: string[] + + A list of ids of the main instances of the components to combine with this one. + + Returns void + isVariantHead: |- + ``` + isVariantHead(): boolean + ``` + + * Returns boolean + + Returns true when the current shape is the head of a components tree nested structure, + and that component is a VariantComponent resize: |- ``` resize(width, height): void @@ -7055,6 +8563,38 @@ Group: ``` shape.rotate(45); ``` + bringToFront: |- + ``` + bringToFront(): void + ``` + + * Moves the current shape to the front of its siblings + + Returns void + bringForward: |- + ``` + bringForward(): void + ``` + + * Moves the current shape one position forward in its list of siblings + + Returns void + sendToBack: |- + ``` + sendToBack(): void + ``` + + * Moves the current shape to the back of its siblings + + Returns void + sendBackward: |- + ``` + sendBackward(): void + ``` + + * Moves the current shape one position backwards in its list of siblings + + Returns void export: |- ``` export(config): Promise @@ -7423,6 +8963,7 @@ Image: id: string; name: string; parent: null | Shape; + parentIndex: number; x: number; y: number; width: number; @@ -7481,6 +9022,7 @@ Image: strokes: Stroke[]; layoutChild?: LayoutChildProperties; layoutCell?: LayoutChildProperties; + setParentIndex(index: number): void; isComponentInstance(): boolean; isComponentMainInstance(): boolean; isComponentCopyInstance(): boolean; @@ -7491,11 +9033,19 @@ Image: componentHead(): null | Shape; component(): null | LibraryComponent; detach(): void; + swapComponent(component: LibraryComponent): void; + switchVariant(pos: number, value: string): void; + combineAsVariants(ids: string[]): void; + isVariantHead(): boolean; resize(width: number, height: number): void; rotate(angle: number, center?: null | { x: number; y: number; }): void; + bringToFront(): void; + bringForward(): void; + sendToBack(): void; + sendBackward(): void; export(config: Export): Promise; interactions: Interaction[]; addInteraction(trigger: Trigger, action: Action, delay?: number): Interaction; @@ -7544,6 +9094,12 @@ Image: The parent shape. If the shape is the first level the parent will be the root shape. For the root shape the parent is null + parentIndex: |- + ``` + parentIndex: number + ``` + + Returns the index of the current shape in the parent x: |- ``` x: number @@ -7898,6 +9454,22 @@ Image: ``` const sharedKeys = shape.getSharedPluginDataKeys('exampleNamespace');console.log(sharedKeys); ``` + setParentIndex: |- + ``` + setParentIndex(index): void + ``` + + * Changes the index inside the parent of the current shape. + This method will shift the indexes of the shapes around that position to + match the index. + If the index is greater than the number of elements it will positioned last. + + Parameters + + index: number + + the new index for the shape to be in + + Returns void isComponentInstance: |- ``` isComponentInstance(): boolean @@ -7983,6 +9555,57 @@ Image: shape as a "basic shape" Returns void + swapComponent: |- + ``` + swapComponent(component): void + ``` + + * TODO + + Parameters + + component: LibraryComponent + + Returns void + switchVariant: |- + ``` + switchVariant(pos, value): void + ``` + + * Switch a VariantComponent copy to the nearest one that has the specified property value + + Parameters + + pos: number + + The position of the poroperty to update + + value: string + + The new value of the property + + Returns void + combineAsVariants: |- + ``` + combineAsVariants(ids): void + ``` + + * Combine several standard Components into a VariantComponent. Similar to doing it with the contextual menu + on the Penpot interface. + The current shape must be a component main instance. + + Parameters + + ids: string[] + + A list of ids of the main instances of the components to combine with this one. + + Returns void + isVariantHead: |- + ``` + isVariantHead(): boolean + ``` + + * Returns boolean + + Returns true when the current shape is the head of a components tree nested structure, + and that component is a VariantComponent resize: |- ``` resize(width, height): void @@ -8028,6 +9651,38 @@ Image: ``` shape.rotate(45); ``` + bringToFront: |- + ``` + bringToFront(): void + ``` + + * Moves the current shape to the front of its siblings + + Returns void + bringForward: |- + ``` + bringForward(): void + ``` + + * Moves the current shape one position forward in its list of siblings + + Returns void + sendToBack: |- + ``` + sendToBack(): void + ``` + + * Moves the current shape to the back of its siblings + + Returns void + sendBackward: |- + ``` + sendBackward(): void + ``` + + * Moves the current shape one position backwards in its list of siblings + + Returns void export: |- ``` export(config): Promise @@ -8121,7 +9776,7 @@ Interaction: } ``` - Referenced by: Board, Boolean, Ellipse, Group, Image, Path, Rectangle, ShapeBase, SvgRaw, Text + Referenced by: Board, Boolean, Ellipse, Group, Image, Path, Rectangle, ShapeBase, SvgRaw, Text, VariantContainer members: Properties: shape: |- @@ -8250,7 +9905,7 @@ LayoutChildProperties: } ``` - Referenced by: Board, Boolean, Ellipse, Group, Image, Path, Rectangle, ShapeBase, SvgRaw, Text + Referenced by: Board, Boolean, Ellipse, Group, Image, Path, Rectangle, ShapeBase, SvgRaw, Text, VariantContainer members: Properties: absolute: |- @@ -8873,6 +10528,8 @@ LibraryComponent: interface LibraryComponent { instance(): Shape; mainInstance(): Shape; + isVariant(): boolean; + transformInVariant(): void; id: string; libraryId: string; name: string; @@ -8890,8 +10547,9 @@ LibraryComponent: * LibraryElement + LibraryComponent + - LibraryVariantComponent - Referenced by: Board, Boolean, Ellipse, Group, Image, Library, Path, Rectangle, ShapeBase, SvgRaw, Text + Referenced by: Board, Boolean, ContextTypesUtils, Ellipse, Group, Image, Library, Path, Rectangle, ShapeBase, SvgRaw, Text, VariantContainer, Variants members: Properties: id: |- @@ -8942,6 +10600,296 @@ LibraryComponent: * Returns Shape Returns the reference to the main component shape. + isVariant: |- + ``` + isVariant(): boolean + ``` + + * Returns boolean + + true when this component is a VariantComponent + transformInVariant: |- + ``` + transformInVariant(): void + ``` + + * Creates a new Variant from this standard Component. It creates a VariantContainer, transform this Component into a VariantComponent, duplicates it, and creates a + set of properties based on the component name and path. + Similar to doing it with the contextual menu or the shortcut on the Penpot interface + + Returns void + getPluginData: |- + ``` + getPluginData(key): string + ``` + + * Retrieves the data for our own plugin, given a specific key. + + Parameters + + key: string + + The key for which to retrieve the data. + + Returns string + + Returns the data associated with the key as a string. + + Example + ``` + const data = shape.getPluginData('exampleKey');console.log(data); + ``` + setPluginData: |- + ``` + setPluginData(key, value): void + ``` + + * Sets the plugin-specific data for the given key. + + Parameters + + key: string + + The key for which to set the data. + + value: string + + The data to set for the key. + + Returns void + + Example + ``` + shape.setPluginData('exampleKey', 'exampleValue'); + ``` + getPluginDataKeys: |- + ``` + getPluginDataKeys(): string[] + ``` + + * Retrieves all the keys for the plugin-specific data. + + Returns string[] + + Returns an array of strings representing all the keys. + + Example + ``` + const keys = shape.getPluginDataKeys();console.log(keys); + ``` + getSharedPluginData: |- + ``` + getSharedPluginData(namespace, key): string + ``` + + * If we know the namespace of an external plugin, this is the way to get their data. + + Parameters + + namespace: string + + The namespace for the shared data. + + key: string + + The key for which to retrieve the data. + + Returns string + + Returns the shared data associated with the key as a string. + + Example + ``` + const sharedData = shape.getSharedPluginData('exampleNamespace', 'exampleKey');console.log(sharedData); + ``` + setSharedPluginData: |- + ``` + setSharedPluginData(namespace, key, value): void + ``` + + * Sets the shared plugin-specific data for the given namespace and key. + + Parameters + + namespace: string + + The namespace for the shared data. + + key: string + + The key for which to set the data. + + value: string + + The data to set for the key. + + Returns void + + Example + ``` + shape.setSharedPluginData('exampleNamespace', 'exampleKey', 'exampleValue'); + ``` + getSharedPluginDataKeys: |- + ``` + getSharedPluginDataKeys(namespace): string[] + ``` + + * Retrieves all the keys for the shared plugin-specific data in the given namespace. + + Parameters + + namespace: string + + The namespace for the shared data. + + Returns string[] + + Returns an array of strings representing all the keys in the namespace. + + Example + ``` + const sharedKeys = shape.getSharedPluginDataKeys('exampleNamespace');console.log(sharedKeys); + ``` +LibraryVariantComponent: + overview: |- + Interface LibraryVariantComponent + ================================= + + Represents a component element from a library in Penpot. + This interface extends `LibraryElement` and includes properties specific to component elements. + + ``` + interface LibraryVariantComponent { + instance(): Shape; + mainInstance(): Shape; + isVariant(): boolean; + transformInVariant(): void; + variants: null | Variants; + variantProps: { + [property: string]: string; + }; + variantError: string; + addVariant(): void; + setVariantProperty(pos: number, value: string): void; + id: string; + libraryId: string; + name: string; + path: string; + getPluginData(key: string): string; + setPluginData(key: string, value: string): void; + getPluginDataKeys(): string[]; + getSharedPluginData(namespace: string, key: string): string; + setSharedPluginData(namespace: string, key: string, value: string): void; + getSharedPluginDataKeys(namespace: string): string[]; + } + ``` + + Hierarchy (view full) + + * LibraryComponent + + LibraryVariantComponent + + Referenced by: ContextTypesUtils + members: + Properties: + variants: |- + ``` + variants: null | Variants + ``` + + Access to the Variant interface, for attributes and actions over the full Variant (not only this VariantComponent) + variantProps: |- + ``` + variantProps: { + [property: string]: string; + } + ``` + + A list of the variants props of this VariantComponent. Each property have a key and a value + variantError: |- + ``` + variantError: string + ``` + + If this VariantComponent has an invalid name, that does't follow the structure [property]=[value], [property]=[value] + this field stores that invalid name + id: |- + ``` + id: string + ``` + + The unique identifier of the library element. + libraryId: |- + ``` + libraryId: string + ``` + + The unique identifier of the library to which the element belongs. + name: |- + ``` + name: string + ``` + + The name of the library element. + path: |- + ``` + path: string + ``` + + The path of the library element. + Methods: + instance: |- + ``` + instance(): Shape + ``` + + * Creates an instance of the component. + + Returns Shape + + Returns a `Shape` object representing the instance of the component. + + Example + ``` + const componentInstance = libraryComponent.instance(); + ``` + mainInstance: |- + ``` + mainInstance(): Shape + ``` + + * Returns Shape + + Returns the reference to the main component shape. + isVariant: |- + ``` + isVariant(): boolean + ``` + + * Returns boolean + + true when this component is a VariantComponent + transformInVariant: |- + ``` + transformInVariant(): void + ``` + + * Creates a new Variant from this standard Component. It creates a VariantContainer, transform this Component into a VariantComponent, duplicates it, and creates a + set of properties based on the component name and path. + Similar to doing it with the contextual menu or the shortcut on the Penpot interface + + Returns void + addVariant: |- + ``` + addVariant(): void + ``` + + * Creates a duplicate of the current VariantComponent on its Variant + + Returns void + setVariantProperty: |- + ``` + setVariantProperty(pos, value): void + ``` + + * Sets the value of the variant property on the indicated position + + Parameters + + pos: number + + value: string + + Returns void getPluginData: |- ``` getPluginData(key): string @@ -10305,7 +12253,9 @@ Path: interface Path { type: "path"; toD(): string; - content: PathCommand[]; + content: string; + d: string; + commands: PathCommand[]; fills: Fill[]; getPluginData(key: string): string; setPluginData(key: string, value: string): void; @@ -10316,6 +12266,7 @@ Path: id: string; name: string; parent: null | Shape; + parentIndex: number; x: number; y: number; width: number; @@ -10374,6 +12325,7 @@ Path: strokes: Stroke[]; layoutChild?: LayoutChildProperties; layoutCell?: LayoutChildProperties; + setParentIndex(index: number): void; isComponentInstance(): boolean; isComponentMainInstance(): boolean; isComponentCopyInstance(): boolean; @@ -10384,11 +12336,19 @@ Path: componentHead(): null | Shape; component(): null | LibraryComponent; detach(): void; + swapComponent(component: LibraryComponent): void; + switchVariant(pos: number, value: string): void; + combineAsVariants(ids: string[]): void; + isVariantHead(): boolean; resize(width: number, height: number): void; rotate(angle: number, center?: null | { x: number; y: number; }): void; + bringToFront(): void; + bringForward(): void; + sendToBack(): void; + sendBackward(): void; export(config: Export): Promise; interactions: Interaction[]; addInteraction(trigger: Trigger, action: Action, delay?: number): Interaction; @@ -10414,10 +12374,26 @@ Path: The type of the shape, which is always 'path' for path shapes. content: |- ``` - content: PathCommand[] + content: string ``` - The content of the path shape, defined as an array of path commands. + The content of the boolean shape, defined as the path string. + + Deprecated + + Use either `d` or `commands`. + d: |- + ``` + d: string + ``` + + The content of the boolean shape, defined as the path string. + commands: |- + ``` + commands: PathCommand[] + ``` + + The content of the boolean shape, defined as an array of path commands. fills: |- ``` fills: Fill[] @@ -10445,6 +12421,12 @@ Path: The parent shape. If the shape is the first level the parent will be the root shape. For the root shape the parent is null + parentIndex: |- + ``` + parentIndex: number + ``` + + Returns the index of the current shape in the parent x: |- ``` x: number @@ -10686,6 +12668,10 @@ Path: Returns string Returns the path data (d attribute) as a string. + + Deprecated + + Use the `d` attribute getPluginData: |- ``` getPluginData(key): string @@ -10809,6 +12795,22 @@ Path: ``` const sharedKeys = shape.getSharedPluginDataKeys('exampleNamespace');console.log(sharedKeys); ``` + setParentIndex: |- + ``` + setParentIndex(index): void + ``` + + * Changes the index inside the parent of the current shape. + This method will shift the indexes of the shapes around that position to + match the index. + If the index is greater than the number of elements it will positioned last. + + Parameters + + index: number + + the new index for the shape to be in + + Returns void isComponentInstance: |- ``` isComponentInstance(): boolean @@ -10894,6 +12896,57 @@ Path: shape as a "basic shape" Returns void + swapComponent: |- + ``` + swapComponent(component): void + ``` + + * TODO + + Parameters + + component: LibraryComponent + + Returns void + switchVariant: |- + ``` + switchVariant(pos, value): void + ``` + + * Switch a VariantComponent copy to the nearest one that has the specified property value + + Parameters + + pos: number + + The position of the poroperty to update + + value: string + + The new value of the property + + Returns void + combineAsVariants: |- + ``` + combineAsVariants(ids): void + ``` + + * Combine several standard Components into a VariantComponent. Similar to doing it with the contextual menu + on the Penpot interface. + The current shape must be a component main instance. + + Parameters + + ids: string[] + + A list of ids of the main instances of the components to combine with this one. + + Returns void + isVariantHead: |- + ``` + isVariantHead(): boolean + ``` + + * Returns boolean + + Returns true when the current shape is the head of a components tree nested structure, + and that component is a VariantComponent resize: |- ``` resize(width, height): void @@ -10939,6 +12992,38 @@ Path: ``` shape.rotate(45); ``` + bringToFront: |- + ``` + bringToFront(): void + ``` + + * Moves the current shape to the front of its siblings + + Returns void + bringForward: |- + ``` + bringForward(): void + ``` + + * Moves the current shape one position forward in its list of siblings + + Returns void + sendToBack: |- + ``` + sendToBack(): void + ``` + + * Moves the current shape to the back of its siblings + + Returns void + sendBackward: |- + ``` + sendBackward(): void + ``` + + * Moves the current shape one position backwards in its list of siblings + + Returns void export: |- ``` export(config): Promise @@ -11418,6 +13503,7 @@ Rectangle: id: string; name: string; parent: null | Shape; + parentIndex: number; x: number; y: number; width: number; @@ -11476,6 +13562,7 @@ Rectangle: strokes: Stroke[]; layoutChild?: LayoutChildProperties; layoutCell?: LayoutChildProperties; + setParentIndex(index: number): void; isComponentInstance(): boolean; isComponentMainInstance(): boolean; isComponentCopyInstance(): boolean; @@ -11486,11 +13573,19 @@ Rectangle: componentHead(): null | Shape; component(): null | LibraryComponent; detach(): void; + swapComponent(component: LibraryComponent): void; + switchVariant(pos: number, value: string): void; + combineAsVariants(ids: string[]): void; + isVariantHead(): boolean; resize(width: number, height: number): void; rotate(angle: number, center?: null | { x: number; y: number; }): void; + bringToFront(): void; + bringForward(): void; + sendToBack(): void; + sendBackward(): void; export(config: Export): Promise; interactions: Interaction[]; addInteraction(trigger: Trigger, action: Action, delay?: number): Interaction; @@ -11541,6 +13636,12 @@ Rectangle: The parent shape. If the shape is the first level the parent will be the root shape. For the root shape the parent is null + parentIndex: |- + ``` + parentIndex: number + ``` + + Returns the index of the current shape in the parent x: |- ``` x: number @@ -11895,6 +13996,22 @@ Rectangle: ``` const sharedKeys = shape.getSharedPluginDataKeys('exampleNamespace');console.log(sharedKeys); ``` + setParentIndex: |- + ``` + setParentIndex(index): void + ``` + + * Changes the index inside the parent of the current shape. + This method will shift the indexes of the shapes around that position to + match the index. + If the index is greater than the number of elements it will positioned last. + + Parameters + + index: number + + the new index for the shape to be in + + Returns void isComponentInstance: |- ``` isComponentInstance(): boolean @@ -11980,6 +14097,57 @@ Rectangle: shape as a "basic shape" Returns void + swapComponent: |- + ``` + swapComponent(component): void + ``` + + * TODO + + Parameters + + component: LibraryComponent + + Returns void + switchVariant: |- + ``` + switchVariant(pos, value): void + ``` + + * Switch a VariantComponent copy to the nearest one that has the specified property value + + Parameters + + pos: number + + The position of the poroperty to update + + value: string + + The new value of the property + + Returns void + combineAsVariants: |- + ``` + combineAsVariants(ids): void + ``` + + * Combine several standard Components into a VariantComponent. Similar to doing it with the contextual menu + on the Penpot interface. + The current shape must be a component main instance. + + Parameters + + ids: string[] + + A list of ids of the main instances of the components to combine with this one. + + Returns void + isVariantHead: |- + ``` + isVariantHead(): boolean + ``` + + * Returns boolean + + Returns true when the current shape is the head of a components tree nested structure, + and that component is a VariantComponent resize: |- ``` resize(width, height): void @@ -12025,6 +14193,38 @@ Rectangle: ``` shape.rotate(45); ``` + bringToFront: |- + ``` + bringToFront(): void + ``` + + * Moves the current shape to the front of its siblings + + Returns void + bringForward: |- + ``` + bringForward(): void + ``` + + * Moves the current shape one position forward in its list of siblings + + Returns void + sendToBack: |- + ``` + sendToBack(): void + ``` + + * Moves the current shape to the back of its siblings + + Returns void + sendBackward: |- + ``` + sendBackward(): void + ``` + + * Moves the current shape one position backwards in its list of siblings + + Returns void export: |- ``` export(config): Promise @@ -12117,7 +14317,7 @@ RulerGuide: } ``` - Referenced by: Board, Page + Referenced by: Board, Page, VariantContainer members: Properties: orientation: |- @@ -12160,7 +14360,7 @@ Shadow: } ``` - Referenced by: Board, Boolean, Ellipse, Group, Image, Path, Rectangle, ShapeBase, SvgRaw, Text + Referenced by: Board, Boolean, Ellipse, Group, Image, Path, Rectangle, ShapeBase, SvgRaw, Text, VariantContainer members: Properties: id: |- @@ -12234,6 +14434,7 @@ ShapeBase: id: string; name: string; parent: null | Shape; + parentIndex: number; x: number; y: number; width: number; @@ -12293,6 +14494,7 @@ ShapeBase: strokes: Stroke[]; layoutChild?: LayoutChildProperties; layoutCell?: LayoutChildProperties; + setParentIndex(index: number): void; isComponentInstance(): boolean; isComponentMainInstance(): boolean; isComponentCopyInstance(): boolean; @@ -12303,11 +14505,19 @@ ShapeBase: componentHead(): null | Shape; component(): null | LibraryComponent; detach(): void; + swapComponent(component: LibraryComponent): void; + switchVariant(pos: number, value: string): void; + combineAsVariants(ids: string[]): void; + isVariantHead(): boolean; resize(width: number, height: number): void; rotate(angle: number, center?: null | { x: number; y: number; }): void; + bringToFront(): void; + bringForward(): void; + sendToBack(): void; + sendBackward(): void; export(config: Export): Promise; interactions: Interaction[]; addInteraction(trigger: Trigger, action: Action, delay?: number): Interaction; @@ -12351,6 +14561,12 @@ ShapeBase: The parent shape. If the shape is the first level the parent will be the root shape. For the root shape the parent is null + parentIndex: |- + ``` + parentIndex: number + ``` + + Returns the index of the current shape in the parent x: |- ``` x: number @@ -12711,6 +14927,22 @@ ShapeBase: ``` const sharedKeys = shape.getSharedPluginDataKeys('exampleNamespace');console.log(sharedKeys); ``` + setParentIndex: |- + ``` + setParentIndex(index): void + ``` + + * Changes the index inside the parent of the current shape. + This method will shift the indexes of the shapes around that position to + match the index. + If the index is greater than the number of elements it will positioned last. + + Parameters + + index: number + + the new index for the shape to be in + + Returns void isComponentInstance: |- ``` isComponentInstance(): boolean @@ -12796,6 +15028,57 @@ ShapeBase: shape as a "basic shape" Returns void + swapComponent: |- + ``` + swapComponent(component): void + ``` + + * TODO + + Parameters + + component: LibraryComponent + + Returns void + switchVariant: |- + ``` + switchVariant(pos, value): void + ``` + + * Switch a VariantComponent copy to the nearest one that has the specified property value + + Parameters + + pos: number + + The position of the poroperty to update + + value: string + + The new value of the property + + Returns void + combineAsVariants: |- + ``` + combineAsVariants(ids): void + ``` + + * Combine several standard Components into a VariantComponent. Similar to doing it with the contextual menu + on the Penpot interface. + The current shape must be a component main instance. + + Parameters + + ids: string[] + + A list of ids of the main instances of the components to combine with this one. + + Returns void + isVariantHead: |- + ``` + isVariantHead(): boolean + ``` + + * Returns boolean + + Returns true when the current shape is the head of a components tree nested structure, + and that component is a VariantComponent resize: |- ``` resize(width, height): void @@ -12841,6 +15124,38 @@ ShapeBase: ``` shape.rotate(45); ``` + bringToFront: |- + ``` + bringToFront(): void + ``` + + * Moves the current shape to the front of its siblings + + Returns void + bringForward: |- + ``` + bringForward(): void + ``` + + * Moves the current shape one position forward in its list of siblings + + Returns void + sendToBack: |- + ``` + sendToBack(): void + ``` + + * Moves the current shape to the back of its siblings + + Returns void + sendBackward: |- + ``` + sendBackward(): void + ``` + + * Moves the current shape one position backwards in its list of siblings + + Returns void export: |- ``` export(config): Promise @@ -13022,7 +15337,7 @@ Stroke: } ``` - Referenced by: Board, Boolean, Ellipse, Group, Image, LibraryColor, Path, Rectangle, ShapeBase, SvgRaw, Text + Referenced by: Board, Boolean, Ellipse, Group, Image, LibraryColor, Path, Rectangle, ShapeBase, SvgRaw, Text, VariantContainer members: Properties: strokeColor: |- @@ -13111,6 +15426,7 @@ SvgRaw: id: string; name: string; parent: null | Shape; + parentIndex: number; x: number; y: number; width: number; @@ -13170,6 +15486,7 @@ SvgRaw: strokes: Stroke[]; layoutChild?: LayoutChildProperties; layoutCell?: LayoutChildProperties; + setParentIndex(index: number): void; isComponentInstance(): boolean; isComponentMainInstance(): boolean; isComponentCopyInstance(): boolean; @@ -13180,11 +15497,19 @@ SvgRaw: componentHead(): null | Shape; component(): null | LibraryComponent; detach(): void; + swapComponent(component: LibraryComponent): void; + switchVariant(pos: number, value: string): void; + combineAsVariants(ids: string[]): void; + isVariantHead(): boolean; resize(width: number, height: number): void; rotate(angle: number, center?: null | { x: number; y: number; }): void; + bringToFront(): void; + bringForward(): void; + sendToBack(): void; + sendBackward(): void; export(config: Export): Promise; interactions: Interaction[]; addInteraction(trigger: Trigger, action: Action, delay?: number): Interaction; @@ -13222,6 +15547,12 @@ SvgRaw: The parent shape. If the shape is the first level the parent will be the root shape. For the root shape the parent is null + parentIndex: |- + ``` + parentIndex: number + ``` + + Returns the index of the current shape in the parent x: |- ``` x: number @@ -13586,6 +15917,22 @@ SvgRaw: ``` const sharedKeys = shape.getSharedPluginDataKeys('exampleNamespace');console.log(sharedKeys); ``` + setParentIndex: |- + ``` + setParentIndex(index): void + ``` + + * Changes the index inside the parent of the current shape. + This method will shift the indexes of the shapes around that position to + match the index. + If the index is greater than the number of elements it will positioned last. + + Parameters + + index: number + + the new index for the shape to be in + + Returns void isComponentInstance: |- ``` isComponentInstance(): boolean @@ -13671,6 +16018,57 @@ SvgRaw: shape as a "basic shape" Returns void + swapComponent: |- + ``` + swapComponent(component): void + ``` + + * TODO + + Parameters + + component: LibraryComponent + + Returns void + switchVariant: |- + ``` + switchVariant(pos, value): void + ``` + + * Switch a VariantComponent copy to the nearest one that has the specified property value + + Parameters + + pos: number + + The position of the poroperty to update + + value: string + + The new value of the property + + Returns void + combineAsVariants: |- + ``` + combineAsVariants(ids): void + ``` + + * Combine several standard Components into a VariantComponent. Similar to doing it with the contextual menu + on the Penpot interface. + The current shape must be a component main instance. + + Parameters + + ids: string[] + + A list of ids of the main instances of the components to combine with this one. + + Returns void + isVariantHead: |- + ``` + isVariantHead(): boolean + ``` + + * Returns boolean + + Returns true when the current shape is the head of a components tree nested structure, + and that component is a VariantComponent resize: |- ``` resize(width, height): void @@ -13716,6 +16114,38 @@ SvgRaw: ``` shape.rotate(45); ``` + bringToFront: |- + ``` + bringToFront(): void + ``` + + * Moves the current shape to the front of its siblings + + Returns void + bringForward: |- + ``` + bringForward(): void + ``` + + * Moves the current shape one position forward in its list of siblings + + Returns void + sendToBack: |- + ``` + sendToBack(): void + ``` + + * Moves the current shape to the back of its siblings + + Returns void + sendBackward: |- + ``` + sendBackward(): void + ``` + + * Moves the current shape one position backwards in its list of siblings + + Returns void export: |- ``` export(config): Promise @@ -13811,6 +16241,7 @@ Text: id: string; name: string; parent: null | Shape; + parentIndex: number; x: number; y: number; width: number; @@ -13870,6 +16301,7 @@ Text: strokes: Stroke[]; layoutChild?: LayoutChildProperties; layoutCell?: LayoutChildProperties; + setParentIndex(index: number): void; isComponentInstance(): boolean; isComponentMainInstance(): boolean; isComponentCopyInstance(): boolean; @@ -13880,11 +16312,19 @@ Text: componentHead(): null | Shape; component(): null | LibraryComponent; detach(): void; + swapComponent(component: LibraryComponent): void; + switchVariant(pos: number, value: string): void; + combineAsVariants(ids: string[]): void; + isVariantHead(): boolean; resize(width: number, height: number): void; rotate(angle: number, center?: null | { x: number; y: number; }): void; + bringToFront(): void; + bringForward(): void; + sendToBack(): void; + sendBackward(): void; export(config: Export): Promise; interactions: Interaction[]; addInteraction(trigger: Trigger, action: Action, delay?: number): Interaction; @@ -13966,6 +16406,12 @@ Text: The parent shape. If the shape is the first level the parent will be the root shape. For the root shape the parent is null + parentIndex: |- + ``` + parentIndex: number + ``` + + Returns the index of the current shape in the parent x: |- ``` x: number @@ -14454,6 +16900,22 @@ Text: ``` const sharedKeys = shape.getSharedPluginDataKeys('exampleNamespace');console.log(sharedKeys); ``` + setParentIndex: |- + ``` + setParentIndex(index): void + ``` + + * Changes the index inside the parent of the current shape. + This method will shift the indexes of the shapes around that position to + match the index. + If the index is greater than the number of elements it will positioned last. + + Parameters + + index: number + + the new index for the shape to be in + + Returns void isComponentInstance: |- ``` isComponentInstance(): boolean @@ -14539,6 +17001,57 @@ Text: shape as a "basic shape" Returns void + swapComponent: |- + ``` + swapComponent(component): void + ``` + + * TODO + + Parameters + + component: LibraryComponent + + Returns void + switchVariant: |- + ``` + switchVariant(pos, value): void + ``` + + * Switch a VariantComponent copy to the nearest one that has the specified property value + + Parameters + + pos: number + + The position of the poroperty to update + + value: string + + The new value of the property + + Returns void + combineAsVariants: |- + ``` + combineAsVariants(ids): void + ``` + + * Combine several standard Components into a VariantComponent. Similar to doing it with the contextual menu + on the Penpot interface. + The current shape must be a component main instance. + + Parameters + + ids: string[] + + A list of ids of the main instances of the components to combine with this one. + + Returns void + isVariantHead: |- + ``` + isVariantHead(): boolean + ``` + + * Returns boolean + + Returns true when the current shape is the head of a components tree nested structure, + and that component is a VariantComponent resize: |- ``` resize(width, height): void @@ -14584,6 +17097,38 @@ Text: ``` shape.rotate(45); ``` + bringToFront: |- + ``` + bringToFront(): void + ``` + + * Moves the current shape to the front of its siblings + + Returns void + bringForward: |- + ``` + bringForward(): void + ``` + + * Moves the current shape one position forward in its list of siblings + + Returns void + sendToBack: |- + ``` + sendToBack(): void + ``` + + * Moves the current shape to the back of its siblings + + Returns void + sendBackward: |- + ``` + sendBackward(): void + ``` + + * Moves the current shape one position backwards in its list of siblings + + Returns void export: |- ``` export(config): Promise @@ -15123,6 +17668,116 @@ User: ``` const sessionId = user.sessionId;console.log(sessionId); ``` +Variants: + overview: |- + Interface Variants + ================== + + TODO + + ``` + interface Variants { + id: string; + libraryId: string; + properties: string[]; + currentValues(property: string): string[]; + removeProperty(pos: number): void; + renameProperty(pos: number, name: string): void; + variantComponents(): LibraryComponent[]; + addVariant(): void; + addProperty(): void; + } + ``` + + Referenced by: LibraryVariantComponent, VariantContainer + members: + Properties: + id: |- + ``` + id: string + ``` + + The unique identifier of the variant element. It is the id of the VariantContainer, and all the VariantComponents + that belong to this variant have an attribute variantId which this is as value. + libraryId: |- + ``` + libraryId: string + ``` + + The unique identifier of the library to which the variant belongs. + properties: |- + ``` + properties: string[] + ``` + + A list with the names of the properties of the Variant + Methods: + currentValues: |- + ``` + currentValues(property): string[] + ``` + + * A list of all the values of a property along all the variantComponents of this Variant + + Parameters + + property: string + + The name of the property + + Returns string[] + removeProperty: |- + ``` + removeProperty(pos): void + ``` + + * Remove a property of the Variant + + Parameters + + pos: number + + The position of the property to remove + + Returns void + renameProperty: |- + ``` + renameProperty(pos, name): void + ``` + + * Rename a property of the Variant + + Parameters + + pos: number + + The position of the property to rename + + name: string + + The new name of the property + + Returns void + variantComponents: |- + ``` + variantComponents(): LibraryComponent[] + ``` + + * List all the VariantComponents on this Variant. + + Returns LibraryComponent[] + addVariant: |- + ``` + addVariant(): void + ``` + + * Creates a duplicate of the main VariantComponent of this Variant + + Returns void + addProperty: |- + ``` + addProperty(): void + ``` + + * Adds a new property to this Variant + + Returns void Viewport: overview: |- Interface Viewport @@ -15210,7 +17865,7 @@ Action: Type for all the possible types of actions in an interaction. - Referenced by: Board, Boolean, Ellipse, Group, Image, Interaction, Path, Rectangle, ShapeBase, SvgRaw, Text + Referenced by: Board, Boolean, Ellipse, Group, Image, Interaction, Path, Rectangle, ShapeBase, SvgRaw, Text, VariantContainer members: {} Animation: overview: |- @@ -15280,7 +17935,7 @@ Bounds: const bounds = { x: 50, y: 50, width: 200, height: 100 };console.log(bounds); ``` - Referenced by: Board, Boolean, Ellipse, Group, Image, Path, Rectangle, ShapeBase, SvgRaw, Text, Viewport + Referenced by: Board, Boolean, Ellipse, Group, Image, Path, Rectangle, ShapeBase, SvgRaw, Text, VariantContainer, Viewport members: {} Gradient: overview: |- @@ -15352,7 +18007,7 @@ Guide: Represents a board guide in Penpot. This type can be one of several specific board guide types: column, row, or square. - Referenced by: Board + Referenced by: Board, VariantContainer members: {} ImageData: overview: |- @@ -15367,6 +18022,7 @@ ImageData: mtype?: string; id: string; keepAspectRatio?: boolean; + data(): Promise; } ``` @@ -15394,6 +18050,15 @@ ImageData: Whether to keep the aspect ratio of the image when resizing. Defaults to false if omitted. + * data:function + + ``` + data(): Promise + ``` + + + Returns the imaged data as a byte array. + + Returns Promise Referenced by: Color, Context, Fill, LibraryColor, Penpot members: {} @@ -15486,7 +18151,7 @@ Point: Point represents a point in 2D space, typically with x and y coordinates. - Referenced by: Board, Boolean, CommentThread, Ellipse, Group, Image, OpenOverlay, OverlayAction, Page, Path, Rectangle, ShapeBase, SvgRaw, Text, ToggleOverlay, Viewport + Referenced by: Board, Boolean, CommentThread, Ellipse, Group, Image, OpenOverlay, OverlayAction, Page, Path, Rectangle, ShapeBase, SvgRaw, Text, ToggleOverlay, VariantContainer, Viewport members: {} RulerGuideOrientation: overview: |- @@ -15497,7 +18162,7 @@ RulerGuideOrientation: RulerGuideOrientation: "horizontal" | "vertical" ``` - Referenced by: Board, Page, RulerGuide + Referenced by: Board, Page, RulerGuide, VariantContainer members: {} Shape: overview: |- @@ -15525,7 +18190,7 @@ Shape: let shape: Shape;if (penpot.utils.types.isRectangle(shape)) { console.log(shape.type);} ``` - Referenced by: Board, Boolean, Context, ContextGeometryUtils, ContextTypesUtils, Ellipse, EventsMap, FlexLayout, GridLayout, Group, Image, Interaction, Library, LibraryComponent, LibraryTypography, OpenOverlay, OverlayAction, Page, Path, Penpot, Rectangle, ShapeBase, SvgRaw, Text, ToggleOverlay, Viewport + Referenced by: Board, Boolean, Context, ContextGeometryUtils, ContextTypesUtils, Ellipse, EventsMap, FlexLayout, GridLayout, Group, Image, Interaction, Library, LibraryComponent, LibraryTypography, LibraryVariantComponent, OpenOverlay, OverlayAction, Page, Path, Penpot, Rectangle, ShapeBase, SvgRaw, Text, ToggleOverlay, VariantContainer, Viewport members: {} StrokeCap: overview: |- @@ -15599,5 +18264,5 @@ Trigger: * `mouse-leave` triggers when the user moves the mouse outside the shape. * `after-delay` triggers after the `delay` time has passed even if no interaction from the user happens. - Referenced by: Board, Boolean, Ellipse, Group, Image, Interaction, Path, Rectangle, ShapeBase, SvgRaw, Text + Referenced by: Board, Boolean, Ellipse, Group, Image, Interaction, Path, Rectangle, ShapeBase, SvgRaw, Text, VariantContainer members: {} diff --git a/mcp-server/data/prompts.yml b/mcp-server/data/prompts.yml index 976e93b..360642b 100644 --- a/mcp-server/data/prompts.yml +++ b/mcp-server/data/prompts.yml @@ -29,9 +29,11 @@ initial_instructions: | * When a shape is a child of a parent shape, the property `parent` refers to the parent shape, and the read-only properties `parentX` and `parentY` (as well as `boardX` and `boardY`) provide the position of the shape relative to its parent (containing board). To position a shape within its parent, set the absolute `x` and `y` properties accordingly. - * The z-order of shapes is, by default, determined by the order in the `children` array of the parent shape. + * The z-order of shapes is determined by the order in the `children` array of the parent shape. Therefore, when creating shapes that should be on top of each other, add them to the parent in the correct order (i.e. add background shapes first, then foreground shapes later). + To modify z-order after creation, use the following methods on shapes: `bringToFront()`, `sendToBack()`, `bringForward()`, `sendBackward()`, + and, for precise control, `setParentIndex(index)` (0-based). # Executing Code diff --git a/mcp-server/package-lock.json b/mcp-server/package-lock.json index f4cc319..8215eb1 100644 --- a/mcp-server/package-lock.json +++ b/mcp-server/package-lock.json @@ -51,11 +51,329 @@ "typescript": "^5.0.0" } }, + "../common/node_modules/penpot-mcp": { + "resolved": "..", + "link": true + }, + "../common/node_modules/typescript": { + "version": "5.9.2", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "../node_modules/@babel/runtime": { + "version": "7.28.4", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "../node_modules/ansi-regex": { + "version": "5.0.1", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "../node_modules/ansi-styles": { + "version": "4.3.0", + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "../node_modules/chalk": { + "version": "4.1.2", + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "../node_modules/chalk/node_modules/supports-color": { + "version": "7.2.0", + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "../node_modules/cliui": { + "version": "8.0.1", + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "../node_modules/color-convert": { + "version": "2.0.1", + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "../node_modules/color-name": { + "version": "1.1.4", + "license": "MIT" + }, + "../node_modules/concurrently": { + "version": "8.2.2", + "license": "MIT", + "dependencies": { + "chalk": "^4.1.2", + "date-fns": "^2.30.0", + "lodash": "^4.17.21", + "rxjs": "^7.8.1", + "shell-quote": "^1.8.1", + "spawn-command": "0.0.2", + "supports-color": "^8.1.1", + "tree-kill": "^1.2.2", + "yargs": "^17.7.2" + }, + "bin": { + "conc": "dist/bin/concurrently.js", + "concurrently": "dist/bin/concurrently.js" + }, + "engines": { + "node": "^14.13.0 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/open-cli-tools/concurrently?sponsor=1" + } + }, + "../node_modules/date-fns": { + "version": "2.30.0", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.21.0" + }, + "engines": { + "node": ">=0.11" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/date-fns" + } + }, + "../node_modules/emoji-regex": { + "version": "8.0.0", + "license": "MIT" + }, + "../node_modules/escalade": { + "version": "3.2.0", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "../node_modules/get-caller-file": { + "version": "2.0.5", + "license": "ISC", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "../node_modules/get-them-args": { + "version": "1.3.2", + "license": "MIT" + }, + "../node_modules/has-flag": { + "version": "4.0.0", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "../node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "../node_modules/kill-port": { + "version": "2.0.1", + "license": "MIT", + "dependencies": { + "get-them-args": "1.3.2", + "shell-exec": "1.0.2" + }, + "bin": { + "kill-port": "cli.js" + } + }, + "../node_modules/lodash": { + "version": "4.17.21", + "license": "MIT" + }, + "../node_modules/prettier": { + "version": "3.6.2", + "dev": true, + "license": "MIT", + "bin": { + "prettier": "bin/prettier.cjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, + "../node_modules/require-directory": { + "version": "2.1.1", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "../node_modules/rxjs": { + "version": "7.8.2", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.1.0" + } + }, + "../node_modules/shell-exec": { + "version": "1.0.2", + "license": "MIT" + }, + "../node_modules/shell-quote": { + "version": "1.8.3", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "../node_modules/spawn-command": { + "version": "0.0.2" + }, + "../node_modules/string-width": { + "version": "4.2.3", + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "../node_modules/strip-ansi": { + "version": "6.0.1", + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "../node_modules/supports-color": { + "version": "8.1.1", + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "../node_modules/tree-kill": { + "version": "1.2.2", + "license": "MIT", + "bin": { + "tree-kill": "cli.js" + } + }, + "../node_modules/tslib": { + "version": "2.8.1", + "license": "0BSD" + }, + "../node_modules/wrap-ansi": { + "version": "7.0.0", + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "../node_modules/y18n": { + "version": "5.0.8", + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "../node_modules/yargs": { + "version": "17.7.2", + "license": "MIT", + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "../node_modules/yargs-parser": { + "version": "21.1.1", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, "node_modules/@cspotcode/source-map-support": { "version": "0.8.1", - "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", - "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", "dev": true, + "license": "MIT", "dependencies": { "@jridgewell/trace-mapping": "0.3.9" }, @@ -63,384 +381,8 @@ "node": ">=12" } }, - "node_modules/@esbuild/aix-ppc64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.19.12.tgz", - "integrity": "sha512-bmoCYyWdEL3wDQIVbcyzRyeKLgk2WtWLTWz1ZIAZF/EGbNOwSA6ew3PftJ1PqMiOOGu0OyFMzG53L0zqIpPeNA==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "aix" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/android-arm": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.19.12.tgz", - "integrity": "sha512-qg/Lj1mu3CdQlDEEiWrlC4eaPZ1KztwGJ9B6J+/6G+/4ewxJg7gqj8eVYWvao1bXrqGiW2rsBZFSX3q2lcW05w==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/android-arm64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.19.12.tgz", - "integrity": "sha512-P0UVNGIienjZv3f5zq0DP3Nt2IE/3plFzuaS96vihvD0Hd6H/q4WXUGpCxD/E8YrSXfNyRPbpTq+T8ZQioSuPA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/android-x64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.19.12.tgz", - "integrity": "sha512-3k7ZoUW6Q6YqhdhIaq/WZ7HwBpnFBlW905Fa4s4qWJyiNOgT1dOqDiVAQFwBH7gBRZr17gLrlFCRzF6jFh7Kew==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/darwin-arm64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.19.12.tgz", - "integrity": "sha512-B6IeSgZgtEzGC42jsI+YYu9Z3HKRxp8ZT3cqhvliEHovq8HSX2YX8lNocDn79gCKJXOSaEot9MVYky7AKjCs8g==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/darwin-x64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.19.12.tgz", - "integrity": "sha512-hKoVkKzFiToTgn+41qGhsUJXFlIjxI/jSYeZf3ugemDYZldIXIxhvwN6erJGlX4t5h417iFuheZ7l+YVn05N3A==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/freebsd-arm64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.19.12.tgz", - "integrity": "sha512-4aRvFIXmwAcDBw9AueDQ2YnGmz5L6obe5kmPT8Vd+/+x/JMVKCgdcRwH6APrbpNXsPz+K653Qg8HB/oXvXVukA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/freebsd-x64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.19.12.tgz", - "integrity": "sha512-EYoXZ4d8xtBoVN7CEwWY2IN4ho76xjYXqSXMNccFSx2lgqOG/1TBPW0yPx1bJZk94qu3tX0fycJeeQsKovA8gg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-arm": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.19.12.tgz", - "integrity": "sha512-J5jPms//KhSNv+LO1S1TX1UWp1ucM6N6XuL6ITdKWElCu8wXP72l9MM0zDTzzeikVyqFE6U8YAV9/tFyj0ti+w==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-arm64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.19.12.tgz", - "integrity": "sha512-EoTjyYyLuVPfdPLsGVVVC8a0p1BFFvtpQDB/YLEhaXyf/5bczaGeN15QkR+O4S5LeJ92Tqotve7i1jn35qwvdA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-ia32": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.19.12.tgz", - "integrity": "sha512-Thsa42rrP1+UIGaWz47uydHSBOgTUnwBwNq59khgIwktK6x60Hivfbux9iNR0eHCHzOLjLMLfUMLCypBkZXMHA==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-loong64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.19.12.tgz", - "integrity": "sha512-LiXdXA0s3IqRRjm6rV6XaWATScKAXjI4R4LoDlvO7+yQqFdlr1Bax62sRwkVvRIrwXxvtYEHHI4dm50jAXkuAA==", - "cpu": [ - "loong64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-mips64el": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.19.12.tgz", - "integrity": "sha512-fEnAuj5VGTanfJ07ff0gOA6IPsvrVHLVb6Lyd1g2/ed67oU1eFzL0r9WL7ZzscD+/N6i3dWumGE1Un4f7Amf+w==", - "cpu": [ - "mips64el" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-ppc64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.19.12.tgz", - "integrity": "sha512-nYJA2/QPimDQOh1rKWedNOe3Gfc8PabU7HT3iXWtNUbRzXS9+vgB0Fjaqr//XNbd82mCxHzik2qotuI89cfixg==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-riscv64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.19.12.tgz", - "integrity": "sha512-2MueBrlPQCw5dVJJpQdUYgeqIzDQgw3QtiAHUC4RBz9FXPrskyyU3VI1hw7C0BSKB9OduwSJ79FTCqtGMWqJHg==", - "cpu": [ - "riscv64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-s390x": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.19.12.tgz", - "integrity": "sha512-+Pil1Nv3Umes4m3AZKqA2anfhJiVmNCYkPchwFJNEJN5QxmTs1uzyy4TvmDrCRNT2ApwSari7ZIgrPeUx4UZDg==", - "cpu": [ - "s390x" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-x64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.19.12.tgz", - "integrity": "sha512-B71g1QpxfwBvNrfyJdVDexenDIt1CiDN1TIXLbhOw0KhJzE78KIFGX6OJ9MrtC0oOqMWf+0xop4qEU8JrJTwCg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/netbsd-x64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.19.12.tgz", - "integrity": "sha512-3ltjQ7n1owJgFbuC61Oj++XhtzmymoCihNFgT84UAmJnxJfm4sYCiSLTXZtE00VWYpPMYc+ZQmB6xbSdVh0JWA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/openbsd-x64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.19.12.tgz", - "integrity": "sha512-RbrfTB9SWsr0kWmb9srfF+L933uMDdu9BIzdA7os2t0TXhCRjrQyCeOt6wVxr79CKD4c+p+YhCj31HBkYcXebw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/sunos-x64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.19.12.tgz", - "integrity": "sha512-HKjJwRrW8uWtCQnQOz9qcU3mUZhTUQvi56Q8DPTLLB+DawoiQdjsYq+j+D3s9I8VFtDr+F9CjgXKKC4ss89IeA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "sunos" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/win32-arm64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.19.12.tgz", - "integrity": "sha512-URgtR1dJnmGvX864pn1B2YUYNzjmXkuJOIqG2HdU62MVS4EHpU2946OZoTMnRUHklGtJdJZ33QfzdjGACXhn1A==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/win32-ia32": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.19.12.tgz", - "integrity": "sha512-+ZOE6pUkMOJfmxmBZElNOx72NKpIa/HFOMGzu8fqzQJ5kgf6aTGrcJaFsNiVMH4JKpMipyK+7k0n2UXN7a8YKQ==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=12" - } - }, "node_modules/@esbuild/win32-x64": { "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.19.12.tgz", - "integrity": "sha512-T1QyPSDCyMXaO3pzBkF96E8xMkiRYbUEZADd29SyPGabqxMViNoii+NcK7eWJAEoU6RZyEm5lVSIjTmcdoB9HA==", "cpu": [ "x64" ], @@ -456,24 +398,21 @@ }, "node_modules/@jridgewell/resolve-uri": { "version": "3.1.2", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", - "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", "dev": true, + "license": "MIT", "engines": { "node": ">=6.0.0" } }, "node_modules/@jridgewell/sourcemap-codec": { "version": "1.5.5", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", - "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/@jridgewell/trace-mapping": { "version": "0.3.9", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", - "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", "dev": true, + "license": "MIT", "dependencies": { "@jridgewell/resolve-uri": "^3.0.3", "@jridgewell/sourcemap-codec": "^1.4.10" @@ -481,8 +420,6 @@ }, "node_modules/@modelcontextprotocol/sdk": { "version": "1.17.5", - "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.17.5.tgz", - "integrity": "sha512-QakrKIGniGuRVfWBdMsDea/dx1PNE739QJ7gCM41s9q+qaCYTHCdsIBXQVVXry3mfWAiaM9kT22Hyz53Uw8mfg==", "license": "MIT", "dependencies": { "ajv": "^6.12.6", @@ -504,8 +441,6 @@ }, "node_modules/@modelcontextprotocol/sdk/node_modules/accepts": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz", - "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==", "license": "MIT", "dependencies": { "mime-types": "^3.0.0", @@ -517,8 +452,6 @@ }, "node_modules/@modelcontextprotocol/sdk/node_modules/body-parser": { "version": "2.2.0", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.0.tgz", - "integrity": "sha512-02qvAaxv8tp7fBa/mw1ga98OGm+eCbqzJOKoRt70sLmfEEi+jyBYVTDGfCL/k06/4EMk/z01gCe7HoCH/f2LTg==", "license": "MIT", "dependencies": { "bytes": "^3.1.2", @@ -537,8 +470,6 @@ }, "node_modules/@modelcontextprotocol/sdk/node_modules/content-disposition": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.0.0.tgz", - "integrity": "sha512-Au9nRL8VNUut/XSzbQA38+M78dzP4D+eqg3gfJHMIHHYa3bg067xj1KxMUWj+VULbiZMowKngFFbKczUrNJ1mg==", "license": "MIT", "dependencies": { "safe-buffer": "5.2.1" @@ -549,8 +480,6 @@ }, "node_modules/@modelcontextprotocol/sdk/node_modules/cookie-signature": { "version": "1.2.2", - "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz", - "integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==", "license": "MIT", "engines": { "node": ">=6.6.0" @@ -558,8 +487,6 @@ }, "node_modules/@modelcontextprotocol/sdk/node_modules/debug": { "version": "4.4.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", - "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", "license": "MIT", "dependencies": { "ms": "^2.1.3" @@ -575,8 +502,6 @@ }, "node_modules/@modelcontextprotocol/sdk/node_modules/express": { "version": "5.1.0", - "resolved": "https://registry.npmjs.org/express/-/express-5.1.0.tgz", - "integrity": "sha512-DT9ck5YIRU+8GYzzU5kT3eHGA5iL+1Zd0EutOmTE9Dtk+Tvuzd23VBU+ec7HPNSTxXYO55gPV/hq4pSBJDjFpA==", "license": "MIT", "dependencies": { "accepts": "^2.0.0", @@ -617,8 +542,6 @@ }, "node_modules/@modelcontextprotocol/sdk/node_modules/finalhandler": { "version": "2.1.0", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.0.tgz", - "integrity": "sha512-/t88Ty3d5JWQbWYgaOGCCYfXRwV1+be02WqYYlL6h0lEiUAMPM8o8qKGO01YIkOHzka2up08wvgYD0mDiI+q3Q==", "license": "MIT", "dependencies": { "debug": "^4.4.0", @@ -634,8 +557,6 @@ }, "node_modules/@modelcontextprotocol/sdk/node_modules/fresh": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz", - "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==", "license": "MIT", "engines": { "node": ">= 0.8" @@ -643,8 +564,6 @@ }, "node_modules/@modelcontextprotocol/sdk/node_modules/iconv-lite": { "version": "0.6.3", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", - "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", "license": "MIT", "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" @@ -655,8 +574,6 @@ }, "node_modules/@modelcontextprotocol/sdk/node_modules/media-typer": { "version": "1.1.0", - "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz", - "integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==", "license": "MIT", "engines": { "node": ">= 0.8" @@ -664,8 +581,6 @@ }, "node_modules/@modelcontextprotocol/sdk/node_modules/merge-descriptors": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz", - "integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==", "license": "MIT", "engines": { "node": ">=18" @@ -676,8 +591,6 @@ }, "node_modules/@modelcontextprotocol/sdk/node_modules/mime-db": { "version": "1.54.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", - "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", "license": "MIT", "engines": { "node": ">= 0.6" @@ -685,8 +598,6 @@ }, "node_modules/@modelcontextprotocol/sdk/node_modules/mime-types": { "version": "3.0.1", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.1.tgz", - "integrity": "sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA==", "license": "MIT", "dependencies": { "mime-db": "^1.54.0" @@ -697,14 +608,10 @@ }, "node_modules/@modelcontextprotocol/sdk/node_modules/ms": { "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", "license": "MIT" }, "node_modules/@modelcontextprotocol/sdk/node_modules/negotiator": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz", - "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==", "license": "MIT", "engines": { "node": ">= 0.6" @@ -712,8 +619,6 @@ }, "node_modules/@modelcontextprotocol/sdk/node_modules/qs": { "version": "6.14.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.0.tgz", - "integrity": "sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==", "license": "BSD-3-Clause", "dependencies": { "side-channel": "^1.1.0" @@ -727,8 +632,6 @@ }, "node_modules/@modelcontextprotocol/sdk/node_modules/send": { "version": "1.2.0", - "resolved": "https://registry.npmjs.org/send/-/send-1.2.0.tgz", - "integrity": "sha512-uaW0WwXKpL9blXE2o0bRhoL2EGXIrZxQ2ZQ4mgcfoBxdFmQold+qWsD2jLrfZ0trjKL6vOw0j//eAwcALFjKSw==", "license": "MIT", "dependencies": { "debug": "^4.3.5", @@ -749,8 +652,6 @@ }, "node_modules/@modelcontextprotocol/sdk/node_modules/serve-static": { "version": "2.2.0", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.0.tgz", - "integrity": "sha512-61g9pCh0Vnh7IutZjtLGGpTA355+OPn2TyDv/6ivP2h/AdAVX9azsoxmg2/M6nZeQZNYBEwIcsne1mJd9oQItQ==", "license": "MIT", "dependencies": { "encodeurl": "^2.0.0", @@ -764,8 +665,6 @@ }, "node_modules/@modelcontextprotocol/sdk/node_modules/type-is": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.0.1.tgz", - "integrity": "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==", "license": "MIT", "dependencies": { "content-type": "^1.0.5", @@ -782,32 +681,26 @@ }, "node_modules/@tsconfig/node10": { "version": "1.0.11", - "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.11.tgz", - "integrity": "sha512-DcRjDCujK/kCk/cUe8Xz8ZSpm8mS3mNNpta+jGCA6USEDfktlNvm1+IuZ9eTcDbNk41BHwpHHeW+N1lKCz4zOw==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/@tsconfig/node12": { "version": "1.0.11", - "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz", - "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/@tsconfig/node14": { "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz", - "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/@tsconfig/node16": { "version": "1.0.4", - "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz", - "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/@types/body-parser": { "version": "1.19.6", - "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.6.tgz", - "integrity": "sha512-HLFeCYgz89uk22N5Qg3dvGvsv46B8GLvKKo1zKG4NybA8U2DiEO3w9lqGg29t/tfLRJpJ6iQxnVw4OnB7MoM9g==", "dev": true, "license": "MIT", "dependencies": { @@ -817,8 +710,6 @@ }, "node_modules/@types/connect": { "version": "3.4.38", - "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz", - "integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==", "dev": true, "license": "MIT", "dependencies": { @@ -827,8 +718,6 @@ }, "node_modules/@types/express": { "version": "4.17.23", - "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.23.tgz", - "integrity": "sha512-Crp6WY9aTYP3qPi2wGDo9iUe/rceX01UMhnF1jmwDcKCFM6cx7YhGP/Mpr3y9AASpfHixIG0E6azCcL5OcDHsQ==", "dev": true, "license": "MIT", "dependencies": { @@ -840,8 +729,6 @@ }, "node_modules/@types/express-serve-static-core": { "version": "4.19.6", - "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.19.6.tgz", - "integrity": "sha512-N4LZ2xG7DatVqhCZzOGb1Yi5lMbXSZcmdLDe9EzSndPV2HpWYWzRbaerl2n27irrm94EPpprqa8KpskPT085+A==", "dev": true, "license": "MIT", "dependencies": { @@ -853,52 +740,39 @@ }, "node_modules/@types/http-errors": { "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.5.tgz", - "integrity": "sha512-r8Tayk8HJnX0FztbZN7oVqGccWgw98T/0neJphO91KkmOzug1KkofZURD4UaD5uH8AqcFLfdPErnBod0u71/qg==", "dev": true, "license": "MIT" }, "node_modules/@types/js-yaml": { "version": "4.0.9", - "resolved": "https://registry.npmjs.org/@types/js-yaml/-/js-yaml-4.0.9.tgz", - "integrity": "sha512-k4MGaQl5TGo/iipqb2UDG2UwjXziSWkh0uysQelTlJpX1qGlpUZYm8PnO4DxG1qBomtJUdYJ6qR6xdIah10JLg==", "dev": true, "license": "MIT" }, "node_modules/@types/mime": { "version": "1.3.5", - "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz", - "integrity": "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==", "dev": true, "license": "MIT" }, "node_modules/@types/node": { "version": "20.19.13", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.13.tgz", - "integrity": "sha512-yCAeZl7a0DxgNVteXFHt9+uyFbqXGy/ShC4BlcHkoE0AfGXYv/BUiplV72DjMYXHDBXFjhvr6DD1NiRVfB4j8g==", "dev": true, + "license": "MIT", "dependencies": { "undici-types": "~6.21.0" } }, "node_modules/@types/qs": { "version": "6.14.0", - "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.14.0.tgz", - "integrity": "sha512-eOunJqu0K1923aExK6y8p6fsihYEn/BYuQ4g0CxAAgFc4b/ZLN4CrsRZ55srTdqoiLzU2B2evC+apEIxprEzkQ==", "dev": true, "license": "MIT" }, "node_modules/@types/range-parser": { "version": "1.2.7", - "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.7.tgz", - "integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==", "dev": true, "license": "MIT" }, "node_modules/@types/send": { "version": "0.17.5", - "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.5.tgz", - "integrity": "sha512-z6F2D3cOStZvuk2SaP6YrwkNO65iTZcwA2ZkSABegdkAh/lf+Aa/YQndZVfmEXT5vgAp6zv06VQ3ejSVjAny4w==", "dev": true, "license": "MIT", "dependencies": { @@ -908,8 +782,6 @@ }, "node_modules/@types/serve-static": { "version": "1.15.8", - "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.8.tgz", - "integrity": "sha512-roei0UY3LhpOJvjbIP6ZZFngyLKl5dskOtDhxY5THRSpO+ZI+nzJ+m5yUMzGrp89YRa7lvknKkMYjqQFGwA7Sg==", "dev": true, "license": "MIT", "dependencies": { @@ -920,14 +792,10 @@ }, "node_modules/@types/validator": { "version": "13.15.3", - "resolved": "https://registry.npmjs.org/@types/validator/-/validator-13.15.3.tgz", - "integrity": "sha512-7bcUmDyS6PN3EuD9SlGGOxM77F8WLVsrwkxyWxKnxzmXoequ6c7741QBrANq6htVRGOITJ7z72mTP6Z4XyuG+Q==", "license": "MIT" }, "node_modules/@types/ws": { "version": "8.18.1", - "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.18.1.tgz", - "integrity": "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==", "dev": true, "license": "MIT", "dependencies": { @@ -936,8 +804,6 @@ }, "node_modules/accepts": { "version": "1.3.8", - "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", - "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", "license": "MIT", "dependencies": { "mime-types": "~2.1.34", @@ -949,9 +815,8 @@ }, "node_modules/acorn": { "version": "8.15.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", - "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "dev": true, + "license": "MIT", "bin": { "acorn": "bin/acorn" }, @@ -961,9 +826,8 @@ }, "node_modules/acorn-walk": { "version": "8.3.4", - "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.4.tgz", - "integrity": "sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==", "dev": true, + "license": "MIT", "dependencies": { "acorn": "^8.11.0" }, @@ -973,8 +837,6 @@ }, "node_modules/ajv": { "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", "license": "MIT", "dependencies": { "fast-deep-equal": "^3.1.1", @@ -989,26 +851,19 @@ }, "node_modules/arg": { "version": "4.1.3", - "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", - "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/argparse": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", "license": "Python-2.0" }, "node_modules/array-flatten": { "version": "1.1.1", - "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", - "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==", "license": "MIT" }, "node_modules/atomic-sleep": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/atomic-sleep/-/atomic-sleep-1.0.0.tgz", - "integrity": "sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ==", "license": "MIT", "engines": { "node": ">=8.0.0" @@ -1016,8 +871,6 @@ }, "node_modules/body-parser": { "version": "1.20.3", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz", - "integrity": "sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==", "license": "MIT", "dependencies": { "bytes": "3.1.2", @@ -1040,8 +893,6 @@ }, "node_modules/body-parser/node_modules/iconv-lite": { "version": "0.4.24", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", - "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", "license": "MIT", "dependencies": { "safer-buffer": ">= 2.1.2 < 3" @@ -1052,8 +903,6 @@ }, "node_modules/body-parser/node_modules/raw-body": { "version": "2.5.2", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz", - "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==", "license": "MIT", "dependencies": { "bytes": "3.1.2", @@ -1067,16 +916,13 @@ }, "node_modules/bytes": { "version": "3.1.2", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", - "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "license": "MIT", "engines": { "node": ">= 0.8" } }, "node_modules/call-bind-apply-helpers": { "version": "1.0.2", - "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", - "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", "license": "MIT", "dependencies": { "es-errors": "^1.3.0", @@ -1088,8 +934,6 @@ }, "node_modules/call-bound": { "version": "1.0.4", - "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", - "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", "license": "MIT", "dependencies": { "call-bind-apply-helpers": "^1.0.2", @@ -1104,14 +948,10 @@ }, "node_modules/class-transformer": { "version": "0.5.1", - "resolved": "https://registry.npmjs.org/class-transformer/-/class-transformer-0.5.1.tgz", - "integrity": "sha512-SQa1Ws6hUbfC98vKGxZH3KFY0Y1lm5Zm0SY8XX9zbK7FJCyVEac3ATW0RIpwzW+oOfmHE5PMPufDG9hCfoEOMw==", "license": "MIT" }, "node_modules/class-validator": { "version": "0.14.2", - "resolved": "https://registry.npmjs.org/class-validator/-/class-validator-0.14.2.tgz", - "integrity": "sha512-3kMVRF2io8N8pY1IFIXlho9r8IPUUIfHe2hYVtiebvAzU2XeQFXTv+XI4WX+TnXmtwXMDcjngcpkiPM0O9PvLw==", "license": "MIT", "dependencies": { "@types/validator": "^13.11.8", @@ -1121,14 +961,10 @@ }, "node_modules/colorette": { "version": "2.0.20", - "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz", - "integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==", "license": "MIT" }, "node_modules/content-disposition": { "version": "0.5.4", - "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", - "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", "license": "MIT", "dependencies": { "safe-buffer": "5.2.1" @@ -1139,16 +975,13 @@ }, "node_modules/content-type": { "version": "1.0.5", - "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", - "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "license": "MIT", "engines": { "node": ">= 0.6" } }, "node_modules/cookie": { "version": "0.7.1", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.1.tgz", - "integrity": "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==", "license": "MIT", "engines": { "node": ">= 0.6" @@ -1156,14 +989,10 @@ }, "node_modules/cookie-signature": { "version": "1.0.6", - "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", - "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==", "license": "MIT" }, "node_modules/cors": { "version": "2.8.5", - "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", - "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", "license": "MIT", "dependencies": { "object-assign": "^4", @@ -1175,14 +1004,11 @@ }, "node_modules/create-require": { "version": "1.1.1", - "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", - "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/cross-spawn": { "version": "7.0.6", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", - "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", "license": "MIT", "dependencies": { "path-key": "^3.1.0", @@ -1195,8 +1021,6 @@ }, "node_modules/dateformat": { "version": "4.6.3", - "resolved": "https://registry.npmjs.org/dateformat/-/dateformat-4.6.3.tgz", - "integrity": "sha512-2P0p0pFGzHS5EMnhdxQi7aJN+iMheud0UhG4dlE1DLAlvL8JHjJJTX/CSm4JXwV0Ka5nGk3zC5mcb5bUQUxxMA==", "license": "MIT", "engines": { "node": "*" @@ -1204,8 +1028,6 @@ }, "node_modules/debug": { "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", "license": "MIT", "dependencies": { "ms": "2.0.0" @@ -1213,16 +1035,13 @@ }, "node_modules/depd": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", - "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "license": "MIT", "engines": { "node": ">= 0.8" } }, "node_modules/destroy": { "version": "1.2.0", - "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", - "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", "license": "MIT", "engines": { "node": ">= 0.8", @@ -1231,17 +1050,14 @@ }, "node_modules/diff": { "version": "4.0.2", - "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", - "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", "dev": true, + "license": "BSD-3-Clause", "engines": { "node": ">=0.3.1" } }, "node_modules/dunder-proto": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", - "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", "license": "MIT", "dependencies": { "call-bind-apply-helpers": "^1.0.1", @@ -1254,14 +1070,10 @@ }, "node_modules/ee-first": { "version": "1.1.1", - "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", - "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", "license": "MIT" }, "node_modules/encodeurl": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", - "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", "license": "MIT", "engines": { "node": ">= 0.8" @@ -1269,8 +1081,6 @@ }, "node_modules/end-of-stream": { "version": "1.4.5", - "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz", - "integrity": "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==", "license": "MIT", "dependencies": { "once": "^1.4.0" @@ -1278,8 +1088,6 @@ }, "node_modules/es-define-property": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", - "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", "license": "MIT", "engines": { "node": ">= 0.4" @@ -1287,8 +1095,6 @@ }, "node_modules/es-errors": { "version": "1.3.0", - "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", - "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", "license": "MIT", "engines": { "node": ">= 0.4" @@ -1296,8 +1102,6 @@ }, "node_modules/es-object-atoms": { "version": "1.1.1", - "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", - "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", "license": "MIT", "dependencies": { "es-errors": "^1.3.0" @@ -1308,8 +1112,6 @@ }, "node_modules/esbuild": { "version": "0.19.12", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.19.12.tgz", - "integrity": "sha512-aARqgq8roFBj054KvQr5f1sFu0D65G+miZRCuJyJ0G13Zwx7vRar5Zhn2tkQNzIXcBrNVsv/8stehpj+GAjgbg==", "dev": true, "hasInstallScript": true, "license": "MIT", @@ -1347,14 +1149,10 @@ }, "node_modules/escape-html": { "version": "1.0.3", - "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", - "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", "license": "MIT" }, "node_modules/etag": { "version": "1.8.1", - "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", - "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", "license": "MIT", "engines": { "node": ">= 0.6" @@ -1362,8 +1160,6 @@ }, "node_modules/eventsource": { "version": "3.0.7", - "resolved": "https://registry.npmjs.org/eventsource/-/eventsource-3.0.7.tgz", - "integrity": "sha512-CRT1WTyuQoD771GW56XEZFQ/ZoSfWid1alKGDYMmkt2yl8UXrVR4pspqWNEcqKvVIzg6PAltWjxcSSPrboA4iA==", "license": "MIT", "dependencies": { "eventsource-parser": "^3.0.1" @@ -1374,8 +1170,6 @@ }, "node_modules/eventsource-parser": { "version": "3.0.6", - "resolved": "https://registry.npmjs.org/eventsource-parser/-/eventsource-parser-3.0.6.tgz", - "integrity": "sha512-Vo1ab+QXPzZ4tCa8SwIHJFaSzy4R6SHf7BY79rFBDf0idraZWAkYrDjDj8uWaSm3S2TK+hJ7/t1CEmZ7jXw+pg==", "license": "MIT", "engines": { "node": ">=18.0.0" @@ -1383,8 +1177,6 @@ }, "node_modules/express": { "version": "4.21.2", - "resolved": "https://registry.npmjs.org/express/-/express-4.21.2.tgz", - "integrity": "sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==", "license": "MIT", "dependencies": { "accepts": "~1.3.8", @@ -1429,8 +1221,6 @@ }, "node_modules/express-rate-limit": { "version": "7.5.1", - "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-7.5.1.tgz", - "integrity": "sha512-7iN8iPMDzOMHPUYllBEsQdWVB6fPDMPqwjBaFrgr4Jgr/+okjvzAy+UHlYYL/Vs0OsOrMkwS6PJDkFlJwoxUnw==", "license": "MIT", "engines": { "node": ">= 16" @@ -1444,26 +1234,18 @@ }, "node_modules/fast-copy": { "version": "3.0.2", - "resolved": "https://registry.npmjs.org/fast-copy/-/fast-copy-3.0.2.tgz", - "integrity": "sha512-dl0O9Vhju8IrcLndv2eU4ldt1ftXMqqfgN4H1cpmGV7P6jeB9FwpN9a2c8DPGE1Ys88rNUJVYDHq73CGAGOPfQ==", "license": "MIT" }, "node_modules/fast-deep-equal": { "version": "3.1.3", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", "license": "MIT" }, "node_modules/fast-json-stable-stringify": { "version": "2.1.0", - "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", - "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", "license": "MIT" }, "node_modules/fast-redact": { "version": "3.5.0", - "resolved": "https://registry.npmjs.org/fast-redact/-/fast-redact-3.5.0.tgz", - "integrity": "sha512-dwsoQlS7h9hMeYUq1W++23NDcBLV4KqONnITDV9DjfS3q1SgDGVrBdvvTLUotWtPSD7asWDV9/CmsZPy8Hf70A==", "license": "MIT", "engines": { "node": ">=6" @@ -1471,14 +1253,10 @@ }, "node_modules/fast-safe-stringify": { "version": "2.1.1", - "resolved": "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.1.1.tgz", - "integrity": "sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==", "license": "MIT" }, "node_modules/finalhandler": { "version": "1.3.1", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz", - "integrity": "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==", "license": "MIT", "dependencies": { "debug": "2.6.9", @@ -1495,8 +1273,6 @@ }, "node_modules/forwarded": { "version": "0.2.0", - "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", - "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", "license": "MIT", "engines": { "node": ">= 0.6" @@ -1504,8 +1280,6 @@ }, "node_modules/fresh": { "version": "0.5.2", - "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", - "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", "license": "MIT", "engines": { "node": ">= 0.6" @@ -1513,8 +1287,6 @@ }, "node_modules/function-bind": { "version": "1.1.2", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", - "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", "license": "MIT", "funding": { "url": "https://github.com/sponsors/ljharb" @@ -1522,8 +1294,6 @@ }, "node_modules/get-intrinsic": { "version": "1.3.0", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", - "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", "license": "MIT", "dependencies": { "call-bind-apply-helpers": "^1.0.2", @@ -1546,8 +1316,6 @@ }, "node_modules/get-proto": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", - "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", "license": "MIT", "dependencies": { "dunder-proto": "^1.0.1", @@ -1559,8 +1327,6 @@ }, "node_modules/gopd": { "version": "1.2.0", - "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", - "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", "license": "MIT", "engines": { "node": ">= 0.4" @@ -1571,8 +1337,6 @@ }, "node_modules/has-symbols": { "version": "1.1.0", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", - "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", "license": "MIT", "engines": { "node": ">= 0.4" @@ -1583,8 +1347,6 @@ }, "node_modules/hasown": { "version": "2.0.2", - "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", - "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", "license": "MIT", "dependencies": { "function-bind": "^1.1.2" @@ -1595,14 +1357,11 @@ }, "node_modules/help-me": { "version": "5.0.0", - "resolved": "https://registry.npmjs.org/help-me/-/help-me-5.0.0.tgz", - "integrity": "sha512-7xgomUX6ADmcYzFik0HzAxh/73YlKR9bmFzf51CZwR+b6YtzU2m0u49hQCqV6SvlqIqsaxovfwdvbnsw3b/zpg==", "license": "MIT" }, "node_modules/http-errors": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", - "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", + "license": "MIT", "dependencies": { "depd": "2.0.0", "inherits": "2.0.4", @@ -1616,8 +1375,7 @@ }, "node_modules/iconv-lite": { "version": "0.7.0", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.0.tgz", - "integrity": "sha512-cf6L2Ds3h57VVmkZe+Pn+5APsT7FpqJtEhhieDCvrE2MK5Qk9MyffgQyuxQTm6BChfeZNtcOLHp9IcWRVcIcBQ==", + "license": "MIT", "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" }, @@ -1631,13 +1389,10 @@ }, "node_modules/inherits": { "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + "license": "ISC" }, "node_modules/ipaddr.js": { "version": "1.9.1", - "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", - "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", "license": "MIT", "engines": { "node": ">= 0.10" @@ -1645,20 +1400,14 @@ }, "node_modules/is-promise": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz", - "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==", "license": "MIT" }, "node_modules/isexe": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", "license": "ISC" }, "node_modules/joycon": { "version": "3.1.1", - "resolved": "https://registry.npmjs.org/joycon/-/joycon-3.1.1.tgz", - "integrity": "sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw==", "license": "MIT", "engines": { "node": ">=10" @@ -1666,8 +1415,6 @@ }, "node_modules/js-yaml": { "version": "4.1.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", "license": "MIT", "dependencies": { "argparse": "^2.0.1" @@ -1678,26 +1425,19 @@ }, "node_modules/json-schema-traverse": { "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", "license": "MIT" }, "node_modules/libphonenumber-js": { "version": "1.12.15", - "resolved": "https://registry.npmjs.org/libphonenumber-js/-/libphonenumber-js-1.12.15.tgz", - "integrity": "sha512-TMDCtIhWUDHh91wRC+wFuGlIzKdPzaTUHHVrIZ3vPUEoNaXFLrsIQ1ZpAeZeXApIF6rvDksMTvjrIQlLKaYxqQ==", "license": "MIT" }, "node_modules/make-error": { "version": "1.3.6", - "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", - "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", - "dev": true + "dev": true, + "license": "ISC" }, "node_modules/math-intrinsics": { "version": "1.1.0", - "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", - "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", "license": "MIT", "engines": { "node": ">= 0.4" @@ -1705,8 +1445,6 @@ }, "node_modules/media-typer": { "version": "0.3.0", - "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", - "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", "license": "MIT", "engines": { "node": ">= 0.6" @@ -1714,8 +1452,6 @@ }, "node_modules/merge-descriptors": { "version": "1.0.3", - "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz", - "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==", "license": "MIT", "funding": { "url": "https://github.com/sponsors/sindresorhus" @@ -1723,8 +1459,6 @@ }, "node_modules/methods": { "version": "1.1.2", - "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", - "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", "license": "MIT", "engines": { "node": ">= 0.6" @@ -1732,8 +1466,6 @@ }, "node_modules/mime": { "version": "1.6.0", - "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", - "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", "license": "MIT", "bin": { "mime": "cli.js" @@ -1744,8 +1476,6 @@ }, "node_modules/mime-db": { "version": "1.52.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", - "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", "license": "MIT", "engines": { "node": ">= 0.6" @@ -1753,8 +1483,6 @@ }, "node_modules/mime-types": { "version": "2.1.35", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", - "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", "license": "MIT", "dependencies": { "mime-db": "1.52.0" @@ -1765,8 +1493,6 @@ }, "node_modules/minimist": { "version": "1.2.8", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", - "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", "license": "MIT", "funding": { "url": "https://github.com/sponsors/ljharb" @@ -1774,14 +1500,10 @@ }, "node_modules/ms": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", "license": "MIT" }, "node_modules/negotiator": { "version": "0.6.3", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", - "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", "license": "MIT", "engines": { "node": ">= 0.6" @@ -1789,8 +1511,6 @@ }, "node_modules/object-assign": { "version": "4.1.1", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", "license": "MIT", "engines": { "node": ">=0.10.0" @@ -1798,8 +1518,6 @@ }, "node_modules/object-inspect": { "version": "1.13.4", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", - "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", "license": "MIT", "engines": { "node": ">= 0.4" @@ -1810,8 +1528,6 @@ }, "node_modules/on-exit-leak-free": { "version": "2.1.2", - "resolved": "https://registry.npmjs.org/on-exit-leak-free/-/on-exit-leak-free-2.1.2.tgz", - "integrity": "sha512-0eJJY6hXLGf1udHwfNftBqH+g73EU4B504nZeKpz1sYRKafAghwxEJunB2O7rDZkL4PGfsMVnTXZ2EjibbqcsA==", "license": "MIT", "engines": { "node": ">=14.0.0" @@ -1819,8 +1535,6 @@ }, "node_modules/on-finished": { "version": "2.4.1", - "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", - "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", "license": "MIT", "dependencies": { "ee-first": "1.1.1" @@ -1831,8 +1545,6 @@ }, "node_modules/once": { "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", "license": "ISC", "dependencies": { "wrappy": "1" @@ -1840,8 +1552,6 @@ }, "node_modules/parseurl": { "version": "1.3.3", - "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", - "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", "license": "MIT", "engines": { "node": ">= 0.8" @@ -1849,8 +1559,6 @@ }, "node_modules/path-key": { "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", "license": "MIT", "engines": { "node": ">=8" @@ -1858,8 +1566,6 @@ }, "node_modules/path-to-regexp": { "version": "0.1.12", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz", - "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==", "license": "MIT" }, "node_modules/penpot-mcp": { @@ -1868,8 +1574,6 @@ }, "node_modules/pino": { "version": "9.10.0", - "resolved": "https://registry.npmjs.org/pino/-/pino-9.10.0.tgz", - "integrity": "sha512-VOFxoNnxICtxaN8S3E73pR66c5MTFC+rwRcNRyHV/bV/c90dXvJqMfjkeRFsGBDXmlUN3LccJQPqGIufnaJePA==", "license": "MIT", "dependencies": { "atomic-sleep": "^1.0.0", @@ -1890,8 +1594,6 @@ }, "node_modules/pino-abstract-transport": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/pino-abstract-transport/-/pino-abstract-transport-2.0.0.tgz", - "integrity": "sha512-F63x5tizV6WCh4R6RHyi2Ml+M70DNRXt/+HANowMflpgGFMAym/VKm6G7ZOQRjqN7XbGxK1Lg9t6ZrtzOaivMw==", "license": "MIT", "dependencies": { "split2": "^4.0.0" @@ -1899,8 +1601,6 @@ }, "node_modules/pino-pretty": { "version": "13.1.1", - "resolved": "https://registry.npmjs.org/pino-pretty/-/pino-pretty-13.1.1.tgz", - "integrity": "sha512-TNNEOg0eA0u+/WuqH0MH0Xui7uqVk9D74ESOpjtebSQYbNWJk/dIxCXIxFsNfeN53JmtWqYHP2OrIZjT/CBEnA==", "license": "MIT", "dependencies": { "colorette": "^2.0.7", @@ -1923,14 +1623,10 @@ }, "node_modules/pino-std-serializers": { "version": "7.0.0", - "resolved": "https://registry.npmjs.org/pino-std-serializers/-/pino-std-serializers-7.0.0.tgz", - "integrity": "sha512-e906FRY0+tV27iq4juKzSYPbUj2do2X2JX4EzSca1631EB2QJQUqGbDuERal7LCtOpxl6x3+nvo9NPZcmjkiFA==", "license": "MIT" }, "node_modules/pkce-challenge": { "version": "5.0.0", - "resolved": "https://registry.npmjs.org/pkce-challenge/-/pkce-challenge-5.0.0.tgz", - "integrity": "sha512-ueGLflrrnvwB3xuo/uGob5pd5FN7l0MsLf0Z87o/UQmRtwjvfylfc9MurIxRAWywCYTgrvpXBcqjV4OfCYGCIQ==", "license": "MIT", "engines": { "node": ">=16.20.0" @@ -1938,8 +1634,6 @@ }, "node_modules/process-warning": { "version": "5.0.0", - "resolved": "https://registry.npmjs.org/process-warning/-/process-warning-5.0.0.tgz", - "integrity": "sha512-a39t9ApHNx2L4+HBnQKqxxHNs1r7KF+Intd8Q/g1bUh6q0WIp9voPXJ/x0j+ZL45KF1pJd9+q2jLIRMfvEshkA==", "funding": [ { "type": "github", @@ -1954,8 +1648,6 @@ }, "node_modules/proxy-addr": { "version": "2.0.7", - "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", - "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", "license": "MIT", "dependencies": { "forwarded": "0.2.0", @@ -1967,8 +1659,6 @@ }, "node_modules/pump": { "version": "3.0.3", - "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.3.tgz", - "integrity": "sha512-todwxLMY7/heScKmntwQG8CXVkWUOdYxIvY2s0VWAAMh/nd8SoYiRaKjlr7+iCs984f2P8zvrfWcDDYVb73NfA==", "license": "MIT", "dependencies": { "end-of-stream": "^1.1.0", @@ -1977,8 +1667,6 @@ }, "node_modules/punycode": { "version": "2.3.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", - "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", "license": "MIT", "engines": { "node": ">=6" @@ -1986,8 +1674,6 @@ }, "node_modules/qs": { "version": "6.13.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz", - "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==", "license": "BSD-3-Clause", "dependencies": { "side-channel": "^1.0.6" @@ -2001,14 +1687,10 @@ }, "node_modules/quick-format-unescaped": { "version": "4.0.4", - "resolved": "https://registry.npmjs.org/quick-format-unescaped/-/quick-format-unescaped-4.0.4.tgz", - "integrity": "sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg==", "license": "MIT" }, "node_modules/range-parser": { "version": "1.2.1", - "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", - "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", "license": "MIT", "engines": { "node": ">= 0.6" @@ -2016,8 +1698,7 @@ }, "node_modules/raw-body": { "version": "3.0.1", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.1.tgz", - "integrity": "sha512-9G8cA+tuMS75+6G/TzW8OtLzmBDMo8p1JRxN5AZ+LAp8uxGA8V8GZm4GQ4/N5QNQEnLmg6SS7wyuSmbKepiKqA==", + "license": "MIT", "dependencies": { "bytes": "3.1.2", "http-errors": "2.0.0", @@ -2030,8 +1711,6 @@ }, "node_modules/real-require": { "version": "0.2.0", - "resolved": "https://registry.npmjs.org/real-require/-/real-require-0.2.0.tgz", - "integrity": "sha512-57frrGM/OCTLqLOAh0mhVA9VBMHd+9U7Zb2THMGdBUoZVOtGbJzjxsYGDJ3A9AYYCP4hn6y1TVbaOfzWtm5GFg==", "license": "MIT", "engines": { "node": ">= 12.13.0" @@ -2039,14 +1718,10 @@ }, "node_modules/reflect-metadata": { "version": "0.1.14", - "resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.1.14.tgz", - "integrity": "sha512-ZhYeb6nRaXCfhnndflDK8qI6ZQ/YcWZCISRAWICW9XYqMUwjZM9Z0DveWX/ABN01oxSHwVxKQmxeYZSsm0jh5A==", "license": "Apache-2.0" }, "node_modules/router": { "version": "2.2.0", - "resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz", - "integrity": "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==", "license": "MIT", "dependencies": { "debug": "^4.4.0", @@ -2061,8 +1736,6 @@ }, "node_modules/router/node_modules/debug": { "version": "4.4.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", - "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", "license": "MIT", "dependencies": { "ms": "^2.1.3" @@ -2078,14 +1751,10 @@ }, "node_modules/router/node_modules/ms": { "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", "license": "MIT" }, "node_modules/router/node_modules/path-to-regexp": { "version": "8.3.0", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.3.0.tgz", - "integrity": "sha512-7jdwVIRtsP8MYpdXSwOS0YdD0Du+qOoF/AEPIt88PcCFrZCzx41oxku1jD88hZBwbNUIEfpqvuhjFaMAqMTWnA==", "license": "MIT", "funding": { "type": "opencollective", @@ -2094,8 +1763,6 @@ }, "node_modules/safe-buffer": { "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", "funding": [ { "type": "github", @@ -2114,8 +1781,6 @@ }, "node_modules/safe-stable-stringify": { "version": "2.5.0", - "resolved": "https://registry.npmjs.org/safe-stable-stringify/-/safe-stable-stringify-2.5.0.tgz", - "integrity": "sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA==", "license": "MIT", "engines": { "node": ">=10" @@ -2123,13 +1788,10 @@ }, "node_modules/safer-buffer": { "version": "2.1.2", - "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + "license": "MIT" }, "node_modules/secure-json-parse": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/secure-json-parse/-/secure-json-parse-4.0.0.tgz", - "integrity": "sha512-dxtLJO6sc35jWidmLxo7ij+Eg48PM/kleBsxpC8QJE0qJICe+KawkDQmvCMZUr9u7WKVHgMW6vy3fQ7zMiFZMA==", "funding": [ { "type": "github", @@ -2144,8 +1806,6 @@ }, "node_modules/send": { "version": "0.19.0", - "resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz", - "integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==", "license": "MIT", "dependencies": { "debug": "2.6.9", @@ -2168,8 +1828,6 @@ }, "node_modules/send/node_modules/encodeurl": { "version": "1.0.2", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", - "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", "license": "MIT", "engines": { "node": ">= 0.8" @@ -2177,14 +1835,10 @@ }, "node_modules/send/node_modules/ms": { "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", "license": "MIT" }, "node_modules/serve-static": { "version": "1.16.2", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz", - "integrity": "sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==", "license": "MIT", "dependencies": { "encodeurl": "~2.0.0", @@ -2198,13 +1852,10 @@ }, "node_modules/setprototypeof": { "version": "1.2.0", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", - "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" + "license": "ISC" }, "node_modules/shebang-command": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", "license": "MIT", "dependencies": { "shebang-regex": "^3.0.0" @@ -2215,8 +1866,6 @@ }, "node_modules/shebang-regex": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", "license": "MIT", "engines": { "node": ">=8" @@ -2224,8 +1873,6 @@ }, "node_modules/side-channel": { "version": "1.1.0", - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", - "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", "license": "MIT", "dependencies": { "es-errors": "^1.3.0", @@ -2243,8 +1890,6 @@ }, "node_modules/side-channel-list": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", - "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", "license": "MIT", "dependencies": { "es-errors": "^1.3.0", @@ -2259,8 +1904,6 @@ }, "node_modules/side-channel-map": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", - "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", "license": "MIT", "dependencies": { "call-bound": "^1.0.2", @@ -2277,8 +1920,6 @@ }, "node_modules/side-channel-weakmap": { "version": "1.0.2", - "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", - "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", "license": "MIT", "dependencies": { "call-bound": "^1.0.2", @@ -2296,8 +1937,6 @@ }, "node_modules/sonic-boom": { "version": "4.2.0", - "resolved": "https://registry.npmjs.org/sonic-boom/-/sonic-boom-4.2.0.tgz", - "integrity": "sha512-INb7TM37/mAcsGmc9hyyI6+QR3rR1zVRu36B0NeGXKnOOLiZOfER5SA+N7X7k3yUYRzLWafduTDvJAfDswwEww==", "license": "MIT", "dependencies": { "atomic-sleep": "^1.0.0" @@ -2305,8 +1944,6 @@ }, "node_modules/split2": { "version": "4.2.0", - "resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz", - "integrity": "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==", "license": "ISC", "engines": { "node": ">= 10.x" @@ -2314,16 +1951,13 @@ }, "node_modules/statuses": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", - "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "license": "MIT", "engines": { "node": ">= 0.8" } }, "node_modules/strip-json-comments": { "version": "5.0.3", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-5.0.3.tgz", - "integrity": "sha512-1tB5mhVo7U+ETBKNf92xT4hrQa3pm0MZ0PQvuDnWgAAGHDsfp4lPSpiS6psrSiet87wyGPh9ft6wmhOMQ0hDiw==", "license": "MIT", "engines": { "node": ">=14.16" @@ -2334,8 +1968,6 @@ }, "node_modules/thread-stream": { "version": "3.1.0", - "resolved": "https://registry.npmjs.org/thread-stream/-/thread-stream-3.1.0.tgz", - "integrity": "sha512-OqyPZ9u96VohAyMfJykzmivOrY2wfMSf3C5TtFJVgN+Hm6aj+voFhlK+kZEIv2FBh1X6Xp3DlnCOfEQ3B2J86A==", "license": "MIT", "dependencies": { "real-require": "^0.2.0" @@ -2343,17 +1975,15 @@ }, "node_modules/toidentifier": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", - "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "license": "MIT", "engines": { "node": ">=0.6" } }, "node_modules/ts-node": { "version": "10.9.2", - "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz", - "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==", "dev": true, + "license": "MIT", "dependencies": { "@cspotcode/source-map-support": "^0.8.0", "@tsconfig/node10": "^1.0.7", @@ -2394,8 +2024,6 @@ }, "node_modules/type-is": { "version": "1.6.18", - "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", - "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", "license": "MIT", "dependencies": { "media-typer": "0.3.0", @@ -2407,9 +2035,8 @@ }, "node_modules/typescript": { "version": "5.9.2", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.2.tgz", - "integrity": "sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A==", "dev": true, + "license": "Apache-2.0", "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -2420,22 +2047,18 @@ }, "node_modules/undici-types": { "version": "6.21.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", - "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/unpipe": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", - "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "license": "MIT", "engines": { "node": ">= 0.8" } }, "node_modules/uri-js": { "version": "4.4.1", - "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", - "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", "license": "BSD-2-Clause", "dependencies": { "punycode": "^2.1.0" @@ -2443,8 +2066,6 @@ }, "node_modules/utils-merge": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", - "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", "license": "MIT", "engines": { "node": ">= 0.4.0" @@ -2452,14 +2073,11 @@ }, "node_modules/v8-compile-cache-lib": { "version": "3.0.1", - "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", - "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/validator": { "version": "13.15.15", - "resolved": "https://registry.npmjs.org/validator/-/validator-13.15.15.tgz", - "integrity": "sha512-BgWVbCI72aIQy937xbawcs+hrVaN/CZ2UwutgaJ36hGqRrLNM+f5LUT/YPRbo8IV/ASeFzXszezV+y2+rq3l8A==", "license": "MIT", "engines": { "node": ">= 0.10" @@ -2467,8 +2085,6 @@ }, "node_modules/vary": { "version": "1.1.2", - "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", - "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", "license": "MIT", "engines": { "node": ">= 0.8" @@ -2476,8 +2092,6 @@ }, "node_modules/which": { "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", "license": "ISC", "dependencies": { "isexe": "^2.0.0" @@ -2491,14 +2105,10 @@ }, "node_modules/wrappy": { "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", "license": "ISC" }, "node_modules/ws": { "version": "8.18.3", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.3.tgz", - "integrity": "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==", "license": "MIT", "engines": { "node": ">=10.0.0" @@ -2518,25 +2128,21 @@ }, "node_modules/yn": { "version": "3.1.1", - "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", - "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", "dev": true, + "license": "MIT", "engines": { "node": ">=6" } }, "node_modules/zod": { "version": "3.25.76", - "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", - "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", + "license": "MIT", "funding": { "url": "https://github.com/sponsors/colinhacks" } }, "node_modules/zod-to-json-schema": { "version": "3.24.6", - "resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.24.6.tgz", - "integrity": "sha512-h/z3PKvcTcTetyjl1fkj79MHNEjm+HpD6NXheWjzOekY7kV+lwDYnHw+ivHkijnCSMz1yJaWBD9vu/Fcmk+vEg==", "license": "ISC", "peerDependencies": { "zod": "^3.24.1" diff --git a/mcp-server/package.json b/mcp-server/package.json index 5f73363..19c0886 100644 --- a/mcp-server/package.json +++ b/mcp-server/package.json @@ -10,7 +10,9 @@ "build:types": "tsc --emitDeclarationOnly --outDir dist", "build:full": "npm run build && npm run build:types", "start": "node dist/index.js", - "dev": "node --loader ts-node/esm src/index.ts" + "start:multi-user": "node dist/index.js --multi-user", + "dev": "node --loader ts-node/esm src/index.ts", + "dev:multi-user": "node --loader ts-node/esm src/index.ts --multi-user" }, "keywords": [ "mcp", diff --git a/mcp-server/src/PenpotMcpServer.ts b/mcp-server/src/PenpotMcpServer.ts index b726960..eb2c367 100644 --- a/mcp-server/src/PenpotMcpServer.ts +++ b/mcp-server/src/PenpotMcpServer.ts @@ -1,4 +1,5 @@ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; +import { AsyncLocalStorage } from "async_hooks"; import { SSEServerTransport } from "@modelcontextprotocol/sdk/server/sse.js"; import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js"; import { ExecuteCodeTool } from "./tools/ExecuteCodeTool"; @@ -13,6 +14,13 @@ import { ImportImageTool } from "./tools/ImportImageTool"; import { ReplServer } from "./ReplServer"; import { ApiDocs } from "./ApiDocs"; +/** + * Session context for request-scoped data. + */ +export interface SessionContext { + userToken?: string; +} + export class PenpotMcpServer { private readonly logger = createLogger("PenpotMcpServer"); private readonly server: McpServer; @@ -23,15 +31,21 @@ export class PenpotMcpServer { private readonly replServer: ReplServer; private apiDocs: ApiDocs; + /** + * Manages session-specific context, particularly user tokens for each request. + */ + private readonly sessionContext = new AsyncLocalStorage(); + private readonly transports = { streamable: {} as Record, - sse: {} as Record, + sse: {} as Record, }; constructor( private port: number = 4401, private webSocketPort: number = 4402, - replPort: number = 4403 + replPort: number = 4403, + private isMultiUser: boolean = false ) { this.configLoader = new ConfigurationLoader(); this.apiDocs = new ApiDocs(); @@ -47,26 +61,55 @@ export class PenpotMcpServer { ); this.tools = new Map>(); - this.pluginBridge = new PluginBridge(webSocketPort); + this.pluginBridge = new PluginBridge(this, webSocketPort); this.replServer = new ReplServer(this.pluginBridge, replPort); this.registerTools(); } + /** + * Indicates whether the server is running in multi-user mode, + * where user tokens are required for authentication. + */ + public isMultiUserMode(): boolean { + return this.isMultiUser; + } + + /** + * Indicates whether file system access is enabled for MCP tools. + * Access is enabled only in single-user mode, where the file system is assumed + * to belong to the user running the server locally. + */ + public isFileSystemAccessEnabled(): boolean { + return !this.isMultiUserMode(); + } + public getInitialInstructions(): string { let instructions = this.configLoader.getInitialInstructions(); instructions = instructions.replace("$api_types", this.apiDocs.getTypeNames().join(", ")); return instructions; } + /** + * Retrieves the current session context. + * + * @returns The session context for the current request, or undefined if not in a request context + */ + public getSessionContext(): SessionContext | undefined { + return this.sessionContext.getStore(); + } + private registerTools(): void { + // Create relevant tool instances (depending on file system access) const toolInstances: Tool[] = [ new ExecuteCodeTool(this), new HighLevelOverviewTool(this), new PenpotApiInfoTool(this, this.apiDocs), - new ExportShapeTool(this), - new ImportImageTool(this), + new ExportShapeTool(this), // tool adapts to file system access internally ]; + if (this.isFileSystemAccessEnabled()) { + toolInstances.push(new ImportImageTool(this)); + } for (const tool of toolInstances) { const toolName = tool.getToolName(); @@ -88,51 +131,70 @@ export class PenpotMcpServer { } private setupHttpEndpoints(): void { + /** + * Modern Streamable HTTP connection endpoint + */ this.app.all("/mcp", async (req: any, res: any) => { - const { randomUUID } = await import("node:crypto"); + const userToken = req.query.userToken as string | undefined; - const sessionId = req.headers["mcp-session-id"] as string | undefined; - let transport: StreamableHTTPServerTransport; + await this.sessionContext.run({ userToken }, async () => { + const { randomUUID } = await import("node:crypto"); - if (sessionId && this.transports.streamable[sessionId]) { - transport = this.transports.streamable[sessionId]; - } else { - transport = new StreamableHTTPServerTransport({ - sessionIdGenerator: () => randomUUID(), - onsessioninitialized: (id: string) => { - this.transports.streamable[id] = transport; - }, + const sessionId = req.headers["mcp-session-id"] as string | undefined; + let transport: StreamableHTTPServerTransport; + + if (sessionId && this.transports.streamable[sessionId]) { + transport = this.transports.streamable[sessionId]; + } else { + transport = new StreamableHTTPServerTransport({ + sessionIdGenerator: () => randomUUID(), + onsessioninitialized: (id: string) => { + this.transports.streamable[id] = transport; + }, + }); + + transport.onclose = () => { + if (transport.sessionId) { + delete this.transports.streamable[transport.sessionId]; + } + }; + + await this.server.connect(transport); + } + + await transport.handleRequest(req, res, req.body); + }); + }); + + /** + * Legacy SSE connection endpoint + */ + this.app.get("/sse", async (req: any, res: any) => { + const userToken = req.query.userToken as string | undefined; + + await this.sessionContext.run({ userToken }, async () => { + const transport = new SSEServerTransport("/messages", res); + this.transports.sse[transport.sessionId] = { transport, userToken }; + + res.on("close", () => { + delete this.transports.sse[transport.sessionId]; }); - transport.onclose = () => { - if (transport.sessionId) { - delete this.transports.streamable[transport.sessionId]; - } - }; - await this.server.connect(transport); - } - - await transport.handleRequest(req, res, req.body); - }); - - this.app.get("/sse", async (_req: any, res: any) => { - const transport = new SSEServerTransport("/messages", res); - this.transports.sse[transport.sessionId] = transport; - - res.on("close", () => { - delete this.transports.sse[transport.sessionId]; }); - - await this.server.connect(transport); }); + /** + * SSE message POST endpoint (using previously established session) + */ this.app.post("/messages", async (req: any, res: any) => { const sessionId = req.query.sessionId as string; - const transport = this.transports.sse[sessionId]; + const session = this.transports.sse[sessionId]; - if (transport) { - await transport.handlePostMessage(req, res, req.body); + if (session) { + await this.sessionContext.run({ userToken: session.userToken }, async () => { + await session.transport.handlePostMessage(req, res, req.body); + }); } else { res.status(400).send("No transport found for sessionId"); } diff --git a/mcp-server/src/PluginBridge.ts b/mcp-server/src/PluginBridge.ts index 1d872a0..92aa667 100644 --- a/mcp-server/src/PluginBridge.ts +++ b/mcp-server/src/PluginBridge.ts @@ -1,19 +1,29 @@ import { WebSocket, WebSocketServer } from "ws"; +import * as http from "http"; import { PluginTask } from "./PluginTask"; import { PluginTaskResponse, PluginTaskResult } from "@penpot-mcp/common"; import { createLogger } from "./logger"; +import type { PenpotMcpServer } from "./PenpotMcpServer"; + +interface ClientConnection { + socket: WebSocket; + userToken: string | null; +} /** - * Provides the connection to the Penpot MCP Plugin via WebSocket + * Manages WebSocket connections to Penpot plugin instances and handles plugin tasks + * over these connections. */ export class PluginBridge { private readonly logger = createLogger("PluginBridge"); private readonly wsServer: WebSocketServer; - private readonly connectedClients: Set = new Set(); + private readonly connectedClients: Map = new Map(); + private readonly clientsByToken: Map = new Map(); private readonly pendingTasks: Map> = new Map(); private readonly taskTimeouts: Map = new Map(); constructor( + private mcpServer: PenpotMcpServer, private port: number, private taskTimeoutSecs: number = 30 ) { @@ -25,12 +35,39 @@ export class PluginBridge { * Sets up WebSocket connection handlers for plugin communication. * * Manages client connections and provides bidirectional communication - * channel between the MCP server and Penpot plugin instances. + * channel between the MCP mcpServer and Penpot plugin instances. */ private setupWebSocketHandlers(): void { - this.wsServer.on("connection", (ws: WebSocket) => { - this.logger.info("New WebSocket connection established"); - this.connectedClients.add(ws); + this.wsServer.on("connection", (ws: WebSocket, request: http.IncomingMessage) => { + // extract userToken from query parameters + const url = new URL(request.url!, `ws://${request.headers.host}`); + const userToken = url.searchParams.get("userToken"); + + // require userToken if running in multi-user mode + if (this.mcpServer.isMultiUserMode() && !userToken) { + this.logger.warn("Connection attempt without userToken in multi-user mode - rejecting"); + ws.close(1008, "Missing userToken parameter"); + return; + } + + if (userToken) { + this.logger.info("New WebSocket connection established (authenticated)"); + } else { + this.logger.info("New WebSocket connection established (unauthenticated)"); + } + + // register the client connection with both indexes + const connection: ClientConnection = { socket: ws, userToken }; + this.connectedClients.set(ws, connection); + if (userToken) { + // ensure only one connection per userToken + if (this.clientsByToken.has(userToken)) { + this.logger.warn("Duplicate connection for given user token; rejecting new connection"); + ws.close(1008, "Duplicate connection for given user token; close previous connection first."); + } + + this.clientsByToken.set(userToken, connection); + } ws.on("message", (data: Buffer) => { this.logger.info("Received WebSocket message: %s", data.toString()); @@ -44,16 +81,24 @@ export class PluginBridge { ws.on("close", () => { this.logger.info("WebSocket connection closed"); + const connection = this.connectedClients.get(ws); this.connectedClients.delete(ws); + if (connection?.userToken) { + this.clientsByToken.delete(connection.userToken); + } }); ws.on("error", (error) => { this.logger.error(error, "WebSocket connection error"); + const connection = this.connectedClients.get(ws); this.connectedClients.delete(ws); + if (connection?.userToken) { + this.clientsByToken.delete(connection.userToken); + } }); }); - this.logger.info("WebSocket server started on port %d", this.port); + this.logger.info("WebSocket mcpServer started on port %d", this.port); } /** @@ -90,6 +135,50 @@ export class PluginBridge { this.logger.info(`Task ${response.id} completed: success=${response.success}`); } + /** + * Determines the client connection to use for executing a task. + * + * In single-user mode, returns the single connected client. + * In multi-user mode, returns the client matching the session's userToken. + * + * @returns The client connection to use + * @throws Error if no suitable connection is found or if configuration is invalid + */ + private getClientConnection(): ClientConnection { + if (this.mcpServer.isMultiUserMode()) { + const sessionContext = this.mcpServer.getSessionContext(); + if (!sessionContext?.userToken) { + throw new Error("No userToken found in session context. Multi-user mode requires authentication."); + } + + const connection = this.clientsByToken.get(sessionContext.userToken); + if (!connection) { + throw new Error( + `No plugin instance connected for user token. Please ensure the plugin is running and connected with the correct token.` + ); + } + + return connection; + } else { + // single-user mode: return the single connected client + if (this.connectedClients.size === 0) { + throw new Error( + `No Penpot plugin instances are currently connected. Please ensure the plugin is running and connected.` + ); + } + if (this.connectedClients.size > 1) { + throw new Error( + `Multiple (${this.connectedClients.size}) Penpot MCP Plugin instances are connected. ` + + `Ask the user to ensure that only one instance is connected at a time.` + ); + } + + // return the first (and only) connection + const connection = this.connectedClients.values().next().value; + return connection; + } + } + /** * Executes a plugin task by sending it to connected clients. * @@ -102,44 +191,22 @@ export class PluginBridge { public async executePluginTask>( task: PluginTask ): Promise { - // Check for a single connected client - if (this.connectedClients.size === 0) { - throw new Error( - `No Penpot plugin instances are currently connected. Please ensure the plugin is running and connected.` - ); - } - if (this.connectedClients.size > 1) { - throw new Error( - `Multiple (${this.connectedClients.size}) Penpot MCP Plugin instances are connected. ` + - `Ask the user to ensure that only one instance is connected at a time.` - ); - } + // get the appropriate client connection based on mode + const connection = this.getClientConnection(); - // Register the task for result correlation + // register the task for result correlation this.pendingTasks.set(task.id, task); - // Send task to all connected clients + // send task to the selected client const requestMessage = JSON.stringify(task.toRequest()); - let sentCount = 0; - this.connectedClients.forEach((client) => { - if (client.readyState === 1) { - // WebSocket.OPEN - client.send(requestMessage); - sentCount++; - } - }); - - if (sentCount === 0) { - // Clean up the pending task and timeout since we couldn't send it + if (connection.socket.readyState !== 1) { + // WebSocket is not open this.pendingTasks.delete(task.id); - const timeoutHandle = this.taskTimeouts.get(task.id); - if (timeoutHandle) { - clearTimeout(timeoutHandle); - this.taskTimeouts.delete(task.id); - } - throw new Error(`All connected plugin instances appear to be disconnected. Task could not be sent.`); + throw new Error(`Plugin instance is disconnected. Task could not be sent.`); } + connection.socket.send(requestMessage); + // Set up a timeout to reject the task if no response is received const timeoutHandle = setTimeout(() => { const pendingTask = this.pendingTasks.get(task.id); @@ -153,7 +220,7 @@ export class PluginBridge { }, this.taskTimeoutSecs * 1000); this.taskTimeouts.set(task.id, timeoutHandle); - this.logger.info(`Sent task ${task.id} to ${sentCount} connected client`); + this.logger.info(`Sent task ${task.id} to connected client`); return await task.getResultPromise(); } diff --git a/mcp-server/src/Tool.ts b/mcp-server/src/Tool.ts index 4a8fc2e..90c5a41 100644 --- a/mcp-server/src/Tool.ts +++ b/mcp-server/src/Tool.ts @@ -1,7 +1,7 @@ import { z } from "zod"; import "reflect-metadata"; import { TextResponse, ToolResponse } from "./ToolResponse"; -import type { PenpotMcpServer } from "./PenpotMcpServer"; +import type { PenpotMcpServer, SessionContext } from "./PenpotMcpServer"; import { createLogger } from "./logger"; /** @@ -38,6 +38,10 @@ export abstract class Tool { let argsInstance: TArgs = args as TArgs; this.logger.info("Executing tool: %s; arguments: %s", this.getToolName(), this.formatArgs(argsInstance)); + // TODO: Remove; testing only + const sessionContext = this.mcpServer.getSessionContext(); + this.logger.info("Session context: %s", sessionContext ? JSON.stringify(sessionContext) : "none"); + // execute the actual tool logic let result = await this.executeCore(argsInstance); @@ -89,6 +93,15 @@ export abstract class Tool { return formatted.length > 0 ? "\n" + formatted.join("\n") : "{}"; } + /** + * Retrieves the current session context. + * + * @returns The session context for the current request, or undefined if not in a request context + */ + protected getSessionContext(): SessionContext | undefined { + return this.mcpServer.getSessionContext(); + } + public getInputSchema() { return this.inputSchema; } diff --git a/mcp-server/src/index.ts b/mcp-server/src/index.ts index d9f2810..5eea0ad 100644 --- a/mcp-server/src/index.ts +++ b/mcp-server/src/index.ts @@ -22,6 +22,7 @@ async function main(): Promise { try { const args = process.argv.slice(2); let port = 4401; // default port + let multiUser = false; // default to single-user mode // parse command line arguments for (let i = 0; i < args.length; i++) { @@ -42,18 +43,21 @@ async function main(): Promise { if (i + 1 < args.length) { process.env.LOG_DIR = args[i + 1]; } + } else if (args[i] === "--multi-user") { + multiUser = true; } else if (args[i] === "--help" || args[i] === "-h") { logger.info("Usage: node dist/index.js [options]"); logger.info("Options:"); logger.info(" --port, -p Port number for the HTTP/SSE server (default: 4401)"); logger.info(" --log-level, -l Log level: trace, debug, info, warn, error (default: info)"); logger.info(" --log-dir Directory for log files (default: mcp-server/logs)"); + logger.info(" --multi-user Enable multi-user mode (default: single-user)"); logger.info(" --help, -h Show this help message"); process.exit(0); } } - const server = new PenpotMcpServer(port); + const server = new PenpotMcpServer(port, undefined, undefined, multiUser); await server.start(); // keep the process alive diff --git a/mcp-server/src/tools/ExportShapeTool.ts b/mcp-server/src/tools/ExportShapeTool.ts index 1315614..ae4577c 100644 --- a/mcp-server/src/tools/ExportShapeTool.ts +++ b/mcp-server/src/tools/ExportShapeTool.ts @@ -45,7 +45,13 @@ export class ExportShapeTool extends Tool { * @param mcpServer - The MCP server instance */ constructor(mcpServer: PenpotMcpServer) { - super(mcpServer, ExportShapeArgs.schema); + let schema: any = ExportShapeArgs.schema; + if (!mcpServer.isFileSystemAccessEnabled()) { + // remove filePath key from schema + schema = { ...schema }; + delete schema.filePath; + } + super(mcpServer, schema); } public getToolName(): string { @@ -53,11 +59,11 @@ export class ExportShapeTool extends Tool { } public getToolDescription(): string { - return ( + let description = "Exports a shape from the Penpot design to a PNG or SVG image, " + - "such that you can get an impression of what the shape looks like.\n" + - "Alternatively, you can save it to a file." - ); + "such that you can get an impression of what the shape looks like."; + if (this.mcpServer.isFileSystemAccessEnabled()) description += "\nAlternatively, you can save it to a file."; + return description; } protected async executeCore(args: ExportShapeArgs): Promise { @@ -89,6 +95,10 @@ export class ExportShapeTool extends Tool { return TextResponse.fromData(imageData); } } else { + // make sure file system access is enabled + if (!this.mcpServer.isFileSystemAccessEnabled()) { + throw new Error("File system access is not enabled on the MCP server!"); + } // save image to file if (args.format === "png") { FileUtils.writeBinaryFile(args.filePath, PNGImageContent.byteData(imageData)); diff --git a/package.json b/package.json index a092ac2..14406b3 100644 --- a/package.json +++ b/package.json @@ -5,8 +5,11 @@ "scripts": { "install:all": "concurrently --names \"COMMON,MCP-SERVER,PLUGIN\" --prefix-colors \"green,cyan,magenta\" \"npm --prefix common install\" \"npm --prefix mcp-server install\" \"npm --prefix penpot-plugin install\"", "build:all": "concurrently --names \"COMMON,MCP-SERVER,PLUGIN\" --prefix-colors \"green,cyan,magenta\" --success first \"npm --prefix common install && npm --prefix common run build\" \"npm --prefix mcp-server run build\" \"npm --prefix penpot-plugin run build\"", + "build:all-multi-user": "concurrently --names \"COMMON,MCP-SERVER,PLUGIN\" --prefix-colors \"green,cyan,magenta\" --success first \"npm --prefix common install && npm --prefix common run build\" \"npm --prefix mcp-server run build\" \"npm --prefix penpot-plugin run build:multi-user\"", "start:all": "concurrently --names \"MCP-SERVER,PLUGIN-SERVER\" --prefix-colors \"cyan,magenta\" --kill-others-on-fail \"npm --prefix mcp-server start\" \"npm --prefix penpot-plugin run dev\"", + "start:all-multi-user": "concurrently --names \"MCP-SERVER,PLUGIN-SERVER\" --prefix-colors \"cyan,magenta\" --kill-others-on-fail \"npm --prefix mcp-server run start:multi-user\" \"npm --prefix penpot-plugin run dev:multi-user\"", "bootstrap": "npm run install:all && npm run build:all && npm run start:all", + "bootstrap:multi-user": "npm run install:all && npm run build:all-multi-user && npm run start:all-multi-user", "format": "prettier --write .", "format:check": "prettier --check ." }, diff --git a/penpot-plugin/package-lock.json b/penpot-plugin/package-lock.json index 5fcc2fa..384ee05 100644 --- a/penpot-plugin/package-lock.json +++ b/penpot-plugin/package-lock.json @@ -1,12 +1,12 @@ { - "name": "penpot-plugin-starter-template", - "version": "0.0.0", + "name": "penpot-mcp-plugin", + "version": "1.0.0", "lockfileVersion": 3, "requires": true, "packages": { "": { - "name": "penpot-plugin-starter-template", - "version": "0.0.0", + "name": "penpot-mcp-plugin", + "version": "1.0.0", "dependencies": { "@penpot-mcp/common": "file:../common", "@penpot/plugin-styles": "1.3.2", @@ -14,6 +14,7 @@ "penpot-mcp": "file:.." }, "devDependencies": { + "cross-env": "^7.0.3", "typescript": "^5.8.3", "vite": "^7.0.5", "vite-live-preview": "^0.3.2" @@ -816,6 +817,40 @@ "node": ">=18" } }, + "node_modules/cross-env": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-env/-/cross-env-7.0.3.tgz", + "integrity": "sha512-+/HKd6EgcQCJGh2PSjZuUitQBQynKor4wrFbRg4DtAgS1aWO+gU52xpH7M9ScGgXSYmAVS9bIJ8EzuaGw0oNAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "cross-spawn": "^7.0.1" + }, + "bin": { + "cross-env": "src/bin/cross-env.js", + "cross-env-shell": "src/bin/cross-env-shell.js" + }, + "engines": { + "node": ">=10.14", + "npm": ">=6", + "yarn": ">=1" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, "node_modules/debug": { "version": "4.3.6", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.6.tgz", @@ -914,6 +949,13 @@ "node": "^8.16.0 || ^10.6.0 || >=11.0.0" } }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, + "license": "ISC" + }, "node_modules/ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", @@ -950,6 +992,16 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/penpot-mcp": { "resolved": "..", "link": true @@ -1039,6 +1091,29 @@ "fsevents": "~2.3.2" } }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/source-map-js": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", @@ -1184,6 +1259,22 @@ "vite": ">=5.2.13" } }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, "node_modules/ws": { "version": "8.18.0", "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz", diff --git a/penpot-plugin/package.json b/penpot-plugin/package.json index 2176c51..cc817e9 100644 --- a/penpot-plugin/package.json +++ b/penpot-plugin/package.json @@ -1,11 +1,13 @@ { - "name": "penpot-plugin-starter-template", + "name": "penpot-mcp-plugin", "private": true, - "version": "0.0.0", + "version": "1.0.0", "type": "module", "scripts": { "dev": "vite build --watch", - "build": "tsc && vite build" + "dev:multi-user": "cross-env MULTI_USER_MODE=true vite build --watch", + "build": "tsc && vite build", + "build:multi-user": "tsc && cross-env MULTI_USER_MODE=true vite build" }, "dependencies": { "@penpot-mcp/common": "file:../common", @@ -14,6 +16,7 @@ "penpot-mcp": "file:.." }, "devDependencies": { + "cross-env": "^7.0.3", "typescript": "^5.8.3", "vite": "^7.0.5", "vite-live-preview": "^0.3.2" diff --git a/penpot-plugin/src/main.ts b/penpot-plugin/src/main.ts index 2a44977..7a31b0d 100644 --- a/penpot-plugin/src/main.ts +++ b/penpot-plugin/src/main.ts @@ -4,16 +4,25 @@ import "./style.css"; const searchParams = new URLSearchParams(window.location.search); document.body.dataset.theme = searchParams.get("theme") ?? "light"; +// Determine whether multi-user mode is enabled based on URL parameters +const isMultiUserMode = searchParams.get("multiUser") === "true"; +console.log("Penpot MCP multi-user mode:", isMultiUserMode); + // WebSocket connection management let ws: WebSocket | null = null; const statusElement = document.getElementById("connection-status"); /** * Updates the connection status display element. + * + * @param status - the base status text to display + * @param isConnectedState - whether the connection is in a connected state (affects color) + * @param message - optional additional message to append to the status */ -function updateConnectionStatus(status: string, isConnectedState: boolean): void { +function updateConnectionStatus(status: string, isConnectedState: boolean, message?: string): void { if (statusElement) { - statusElement.textContent = status; + const displayText = message ? `${status}: ${message}` : status; + statusElement.textContent = displayText; statusElement.style.color = isConnectedState ? "var(--accent-primary)" : "var(--error-700)"; } } @@ -42,9 +51,13 @@ function connectToMcpServer(): void { } try { - // Use environment variable for MCP WebSocket URL, fallback to localhost for local development - const mcpWsUrl = import.meta.env.PENPOT_MCP_WEBSOCKET_URL || "ws://localhost:4402"; - ws = new WebSocket(mcpWsUrl); + let wsUrl = import.meta.env.PENPOT_MCP_WEBSOCKET_URL || "ws://localhost:4402"; + if (isMultiUserMode) { + // TODO obtain proper userToken from penpot + const userToken = "dummyToken"; + wsUrl += `?userToken=${encodeURIComponent(userToken)}`; + } + ws = new WebSocket(wsUrl); updateConnectionStatus("Connecting...", false); ws.onopen = () => { @@ -63,19 +76,22 @@ function connectToMcpServer(): void { } }; - ws.onclose = () => { + ws.onclose = (event: CloseEvent) => { console.log("Disconnected from MCP server"); - updateConnectionStatus("Disconnected", false); + const message = event.reason || undefined; + updateConnectionStatus("Disconnected", false, message); ws = null; }; ws.onerror = (error) => { console.error("WebSocket error:", error); + // note: WebSocket error events typically don't contain detailed error messages updateConnectionStatus("Connection error", false); }; } catch (error) { console.error("Failed to connect to MCP server:", error); - updateConnectionStatus("Connection failed", false); + const message = error instanceof Error ? error.message : undefined; + updateConnectionStatus("Connection failed", false, message); } } diff --git a/penpot-plugin/src/plugin.ts b/penpot-plugin/src/plugin.ts index 9cdeaa5..e6a1fad 100644 --- a/penpot-plugin/src/plugin.ts +++ b/penpot-plugin/src/plugin.ts @@ -6,10 +6,16 @@ import { Task, TaskHandler } from "./TaskHandler"; */ const taskHandlers: TaskHandler[] = [new ExecuteCodeTaskHandler()]; -penpot.ui.open("Penpot MCP Plugin", `?theme=${penpot.theme}`, { width: 158, height: 200 }); +// Determine whether multi-user mode is enabled based on build-time configuration +declare const IS_MULTI_USER_MODE: boolean; +const isMultiUserMode = typeof IS_MULTI_USER_MODE !== "undefined" ? IS_MULTI_USER_MODE : false; -// Handle both legacy string messages and new request-based messages +// Open the plugin UI (main.ts) +penpot.ui.open("Penpot MCP Plugin", `?theme=${penpot.theme}&multiUser=${isMultiUserMode}`, { width: 158, height: 200 }); + +// Handle messages penpot.ui.onMessage((message) => { + // Handle plugin task requests if (typeof message === "object" && message.task && message.id) { handlePluginTaskRequest(message).catch((error) => { console.error("Error in handlePluginTaskRequest:", error); @@ -53,7 +59,7 @@ async function handlePluginTaskRequest(request: { id: string; task: string; para } } -// Update the theme in the iframe +// Handle theme change in the iframe penpot.on("themechange", (theme) => { penpot.ui.sendMessage({ source: "penpot", diff --git a/penpot-plugin/vite.config.ts b/penpot-plugin/vite.config.ts index 92af45e..191827c 100644 --- a/penpot-plugin/vite.config.ts +++ b/penpot-plugin/vite.config.ts @@ -1,6 +1,10 @@ import { defineConfig } from "vite"; import livePreview from "vite-live-preview"; +// Debug: Log the environment variable +console.log("MULTI_USER_MODE env:", process.env.MULTI_USER_MODE); +console.log("Will define IS_MULTI_USER_MODE as:", JSON.stringify(process.env.MULTI_USER_MODE === "true")); + export default defineConfig({ plugins: [ livePreview({ @@ -30,4 +34,7 @@ export default defineConfig({ ? process.env.PENPOT_MCP_PLUGIN_SERVER_ALLOWED_HOSTS.split(",").map((h) => h.trim()) : [], }, + define: { + IS_MULTI_USER_MODE: JSON.stringify(process.env.MULTI_USER_MODE === "true"), + }, });