diff --git a/mcp/.gitignore b/mcp/.gitignore new file mode 100644 index 0000000000..8a245a5dca --- /dev/null +++ b/mcp/.gitignore @@ -0,0 +1,11 @@ +.idea +node_modules +dist +*.bak +*.orig +temp +*.tsbuildinfo + +# Log files +logs/ +*.log diff --git a/mcp/.prettierignore b/mcp/.prettierignore new file mode 100644 index 0000000000..00e05b35e2 --- /dev/null +++ b/mcp/.prettierignore @@ -0,0 +1,7 @@ +*.md +*.json +python-scripts/ +.serena/ + +# auto-generated files +mcp-server/data/api_types.yml diff --git a/mcp/.prettierrc b/mcp/.prettierrc new file mode 100644 index 0000000000..3d58be61d9 --- /dev/null +++ b/mcp/.prettierrc @@ -0,0 +1,20 @@ +{ + "tabWidth": 4, + "overrides": [ + { + "files": "*.yml", + "options": { + "tabWidth": 2 + } + } + ], + "useTabs": false, + "semi": true, + "singleQuote": false, + "quoteProps": "as-needed", + "trailingComma": "es5", + "bracketSpacing": true, + "arrowParens": "always", + "printWidth": 120, + "endOfLine": "auto" +} diff --git a/mcp/.serena/.gitignore b/mcp/.serena/.gitignore new file mode 100644 index 0000000000..14d86ad623 --- /dev/null +++ b/mcp/.serena/.gitignore @@ -0,0 +1 @@ +/cache diff --git a/mcp/.serena/memories/code_style_conventions.md b/mcp/.serena/memories/code_style_conventions.md new file mode 100644 index 0000000000..cc9b1c07ae --- /dev/null +++ b/mcp/.serena/memories/code_style_conventions.md @@ -0,0 +1,25 @@ +# Code Style and Conventions + +## General Principles +- **Object-Oriented Design**: VERY IMPORTANT: Use idiomatic, object-oriented style with explicit abstractions +- **Strategy Pattern**: Prefer explicitly typed interfaces over bare functions for non-trivial functionality +- **Clean Architecture**: Tools implement a common interface for consistent registration and execution + +## TypeScript Configuration +- **Strict Mode**: All strict TypeScript options enabled +- **Target**: ES2022 +- **Module System**: CommonJS +- **Declaration Files**: Generated with source maps + +## Naming Conventions +- **Classes**: PascalCase (e.g., `ExeceuteCodeTool`, `PenpotMcpServer`) +- **Interfaces**: PascalCase (e.g., `Tool`) +- **Methods**: camelCase (e.g., `execute`, `registerTools`) +- **Constants**: camelCase for readonly properties (e.g., `definition`) +- **Files**: PascalCase for classes (e.g., `ExecuteCodeTool.ts`) + +## Documentation Style +- **JSDoc**: Use comprehensive JSDoc comments for classes, methods, and interfaces +- **Description Format**: Initial elliptical phrase that defines *what* it is, followed by details +- **Comment Style**: VERY IMPORTANT: Start with lowercase for comments of code blocks (unless lengthy explanation with multiple sentences) + diff --git a/mcp/.serena/memories/project_overview.md b/mcp/.serena/memories/project_overview.md new file mode 100644 index 0000000000..528976b077 --- /dev/null +++ b/mcp/.serena/memories/project_overview.md @@ -0,0 +1,91 @@ +# Penpot MCP Project Overview - Updated + +## Purpose +This project is a Model Context Protocol (MCP) server for Penpot integration. It provides a TypeScript-based server that can be used to extend Penpot's functionality through custom tools with bidirectional WebSocket communication. + +## Tech Stack +- **Language**: TypeScript +- **Runtime**: Node.js +- **Framework**: MCP SDK (@modelcontextprotocol/sdk) +- **Build Tool**: TypeScript Compiler (tsc) + esbuild +- **Package Manager**: pnpm +- **WebSocket**: ws library for real-time communication + +## Project Structure +``` +penpot-mcp/ +├── common/ # Shared type definitions +│ ├── src/ +│ │ ├── index.ts # Exports for shared types +│ │ └── types.ts # PluginTaskResult, request/response interfaces +│ └── package.json # @penpot-mcp/common package +├── mcp-server/ # Main MCP server implementation +│ ├── src/ +│ │ ├── index.ts # Main server entry point +│ │ ├── PenpotMcpServer.ts # Enhanced with request/response correlation +│ │ ├── PluginTask.ts # Now supports result promises +│ │ ├── tasks/ # PluginTask implementations +│ │ └── tools/ # Tool implementations +│ └── package.json # Includes @penpot-mcp/common dependency +├── penpot-plugin/ # Penpot plugin with response capability +│ ├── src/ +│ │ ├── main.ts # Enhanced WebSocket handling with response forwarding +│ │ └── plugin.ts # Now sends task responses back to server +│ └── package.json # Includes @penpot-mcp/common dependency +└── prepare-api-docs # Python project for the generation of API docs +``` + +## Key Tasks + +### Adding a new Tool + +1. Implement the tool class in `mcp-server/src/tools/` following the `Tool` interface. + IMPORTANT: Do not catch any exceptions in the `executeCore` method. Let them propagate to be handled centrally. +2. Register the tool in `PenpotMcpServer`. + +Look at `PrintTextTool` as an example. + +Many tools are linked to tasks that are handled in the plugin, i.e. they have an associated `PluginTask` implementation in `mcp-server/src/tasks/`. + +### Adding a new PluginTask + +1. Implement the input data interface for the task in `common/src/types.ts`. +2. Implement the `PluginTask` class in `mcp-server/src/tasks/`. +3. Implement the corresponding task handler class in the plugin (`penpot-plugin/src/task-handlers/`). + * In the success case, call `task.sendSuccess`. + * In the failure case, just throw an exception, which will be handled centrally! + * Look at `PrintTextTaskHandler` as an example. +4. Register the task handler in `penpot-plugin/src/plugin.ts` in the `taskHandlers` list. + + +## Key Components + +### Enhanced WebSocket Protocol +- **Request Format**: `{id: string, task: string, params: any}` +- **Response Format**: `{id: string, result: {success: boolean, error?: string, data?: any}}` +- **Request/Response Correlation**: Using unique UUIDs for task tracking +- **Timeout Handling**: 30-second timeout with automatic cleanup +- **Type Safety**: Shared definitions via @penpot-mcp/common package + +### Core Classes +- **PenpotMcpServer**: Enhanced with pending task tracking and response handling +- **PluginTask**: Now creates result promises that resolve when plugin responds +- **Tool implementations**: Now properly await task completion and report results +- **Plugin handlers**: Send structured responses back to server + +### New Features +1. **Bidirectional Communication**: Plugin now responds with success/failure status +2. **Task Result Promises**: Every executePluginTask() sets and returns a promise +3. **Error Reporting**: Failed tasks properly report error messages to tools +4. **Shared Type Safety**: Common package ensures consistency across projects +5. **Timeout Protection**: Tasks don't hang indefinitely (30s limit) +6. **Request Correlation**: Unique IDs match requests to responses + +## Task Flow + +``` +LLM Tool Call → MCP Server → WebSocket (Request) → Plugin → Penpot API + ↑ ↓ + Tool Response ← MCP Server ← WebSocket (Response) ← Plugin Result +``` + diff --git a/mcp/.serena/memories/suggested_commands.md b/mcp/.serena/memories/suggested_commands.md new file mode 100644 index 0000000000..34f92fbae7 --- /dev/null +++ b/mcp/.serena/memories/suggested_commands.md @@ -0,0 +1,70 @@ +# Suggested Commands + +## Development Commands +```bash +# Navigate to MCP server directory +cd penpot/mcp/server + +# Install dependencies +pnpm install + +# Build the TypeScript project +pnpm run build + +# Start the server (production) +pnpm run start + +# Start the server in development mode +npm run start:dev +``` + +## Testing and Development +```bash +# Run TypeScript compiler in watch mode +pnpx tsc --watch + +# Check TypeScript compilation without emitting files +pnpx tsc --noEmit +``` + +## Windows-Specific Commands +```cmd +# Directory navigation +cd penpot/mcp/server +dir # List directory contents +type package.json # Display file contents + +# Git operations +git status +git add . +git commit -m "message" +git push + +# File operations +copy src\file.ts backup\file.ts # Copy files +del dist\* # Delete files +mkdir new-directory # Create directory +rmdir /s directory # Remove directory recursively +``` + +## Project Structure Navigation +```bash +# Key directories +cd penpot/mcp/server/src # Source code +cd penpot/mcp/server/src/tools # Tool implementations +cd penpot/mcp/server/src/interfaces # Type definitions +cd penpot/mcp/server/dist # Compiled output +``` + +## Common Utilities +```cmd +# Search for text in files +findstr /s /i "HelloWorld" *.ts + +# Find files by name +dir /s /b *Tool.ts + +# Process management +tasklist | findstr node # Find Node.js processes +taskkill /f /im node.exe # Kill Node.js processes +``` diff --git a/mcp/.serena/memories/task_completion_guidelines.md b/mcp/.serena/memories/task_completion_guidelines.md new file mode 100644 index 0000000000..678fd83e55 --- /dev/null +++ b/mcp/.serena/memories/task_completion_guidelines.md @@ -0,0 +1,56 @@ +# Task Completion Guidelines + +## After Making Code Changes + +### 1. Build and Test +```bash +cd mcp-server +npm run build:full # or npm run build for faster bundling only +``` + +### 2. Verify TypeScript Compilation +```bash +npx tsc --noEmit +``` + +### 3. Test the Server +```bash +# Start in development mode to test changes +npm run dev +``` + +### 4. Code Quality Checks +- Ensure all code follows the established conventions +- Verify JSDoc comments are complete and accurate +- Check that error handling is appropriate +- Use clean imports WITHOUT file extensions (esbuild handles resolution) +- Validate that tool interfaces are properly implemented + +### 5. Integration Testing +- Test tool registration in the main server +- Verify MCP protocol compliance +- Ensure tool definitions match implementation + +## Before Committing Changes +1. **Build Successfully**: `npm run build:full` completes without errors +2. **No TypeScript Errors**: `npx tsc --noEmit` passes +3. **Documentation Updated**: JSDoc comments reflect changes +4. **Tool Registry Updated**: New tools added to `registerTools()` method +5. **Interface Compliance**: All tools implement the `Tool` interface correctly + +## File Organization +- Place new tools in `src/tools/` directory +- Update main server registration in `src/index.ts` +- Follow existing naming conventions + +## Common Patterns +- All tools must implement the `Tool` interface +- Use readonly properties for tool definitions +- Include comprehensive error handling +- Follow the established documentation style +- Import WITHOUT file extensions (esbuild resolves them automatically) + +## Build System +- Uses esbuild for fast bundling and TypeScript for declarations +- Import statements should omit file extensions entirely +- IDE refactoring is safe - no extension-related build failures \ No newline at end of file diff --git a/mcp/.serena/project.yml b/mcp/.serena/project.yml new file mode 100644 index 0000000000..c9ed0f7330 --- /dev/null +++ b/mcp/.serena/project.yml @@ -0,0 +1,130 @@ + + +# whether to use the project's gitignore file to ignore files +# Added on 2025-04-07 +ignore_all_files_in_gitignore: true + +# list of additional paths to ignore +# same syntax as gitignore, so you can use * and ** +# Was previously called `ignored_dirs`, please update your config if you are using that. +# Added (renamed) on 2025-04-07 +ignored_paths: [] + +# whether the project is in read-only mode +# If set to true, all editing tools will be disabled and attempts to use them will result in an error +# Added on 2025-04-18 +read_only: false + + + +# list of tool names to exclude. We recommend not excluding any tools, see the readme for more details. +# Below is the complete list of tools for convenience. +# To make sure you have the latest list of tools, and to view their descriptions, +# execute `uv run scripts/print_tool_overview.py`. +# +# * `activate_project`: Activates a project by name. +# * `check_onboarding_performed`: Checks whether project onboarding was already performed. +# * `create_text_file`: Creates/overwrites a file in the project directory. +# * `delete_lines`: Deletes a range of lines within a file. +# * `delete_memory`: Deletes a memory from Serena's project-specific memory store. +# * `execute_shell_command`: Executes a shell command. +# * `find_referencing_code_snippets`: Finds code snippets in which the symbol at the given location is referenced. +# * `find_referencing_symbols`: Finds symbols that reference the symbol at the given location (optionally filtered by type). +# * `find_symbol`: Performs a global (or local) search for symbols with/containing a given name/substring (optionally filtered by type). +# * `get_current_config`: Prints the current configuration of the agent, including the active and available projects, tools, contexts, and modes. +# * `get_symbols_overview`: Gets an overview of the top-level symbols defined in a given file. +# * `initial_instructions`: Gets the initial instructions for the current project. +# Should only be used in settings where the system prompt cannot be set, +# e.g. in clients you have no control over, like Claude Desktop. +# * `insert_after_symbol`: Inserts content after the end of the definition of a given symbol. +# * `insert_at_line`: Inserts content at a given line in a file. +# * `insert_before_symbol`: Inserts content before the beginning of the definition of a given symbol. +# * `list_dir`: Lists files and directories in the given directory (optionally with recursion). +# * `list_memories`: Lists memories in Serena's project-specific memory store. +# * `onboarding`: Performs onboarding (identifying the project structure and essential tasks, e.g. for testing or building). +# * `prepare_for_new_conversation`: Provides instructions for preparing for a new conversation (in order to continue with the necessary context). +# * `read_file`: Reads a file within the project directory. +# * `read_memory`: Reads the memory with the given name from Serena's project-specific memory store. +# * `remove_project`: Removes a project from the Serena configuration. +# * `replace_lines`: Replaces a range of lines within a file with new content. +# * `replace_symbol_body`: Replaces the full definition of a symbol. +# * `restart_language_server`: Restarts the language server, may be necessary when edits not through Serena happen. +# * `search_for_pattern`: Performs a search for a pattern in the project. +# * `summarize_changes`: Provides instructions for summarizing the changes made to the codebase. +# * `switch_modes`: Activates modes by providing a list of their names +# * `think_about_collected_information`: Thinking tool for pondering the completeness of collected information. +# * `think_about_task_adherence`: Thinking tool for determining whether the agent is still on track with the current task. +# * `think_about_whether_you_are_done`: Thinking tool for determining whether the task is truly completed. +# * `write_memory`: Writes a named memory (for future reference) to Serena's project-specific memory store. +excluded_tools: [] + +# initial prompt for the project. It will always be given to the LLM upon activating the project +# (contrary to the memories, which are loaded on demand). +initial_prompt: | + IMPORTANT: You use an idiomatic, object-oriented style. + In particular, this implies that, for any non-trivial interfaces, you use interfaces that expect explicitly typed abstractions + rather than mere functions (i.e. use the strategy pattern, for example). + + Comments: + When describing parameters, methods/functions and classes, you use a precise style, where the initial (elliptical) phrase + clearly defines *what* it is. Any details then follow in subsequent sentences. + + When describing what blocks of code do, you also use an elliptical style and start with a lower-case letter unless + the comment is a lengthy explanation with at least two sentences (in which case you start with a capital letter, as is + required for sentences). +# the name by which the project can be referenced within Serena +project_name: "penpot-mcp" + +# list of mode names to that are always to be included in the set of active modes +# The full set of modes to be activated is base_modes + default_modes. +# If the setting is undefined, the base_modes from the global configuration (serena_config.yml) apply. +# Otherwise, this setting overrides the global configuration. +# Set this to [] to disable base modes for this project. +# Set this to a list of mode names to always include the respective modes for this project. +base_modes: + +# list of mode names that are to be activated by default. +# The full set of modes to be activated is base_modes + default_modes. +# If the setting is undefined, the default_modes from the global configuration (serena_config.yml) apply. +# Otherwise, this overrides the setting from the global configuration (serena_config.yml). +# This setting can, in turn, be overridden by CLI parameters (--mode). +default_modes: + +# list of tools to include that would otherwise be disabled (particularly optional tools that are disabled by default) +included_optional_tools: [] + +# fixed set of tools to use as the base tool set (if non-empty), replacing Serena's default set of tools. +# This cannot be combined with non-empty excluded_tools or included_optional_tools. +fixed_tools: [] + +# the encoding used by text files in the project +# For a list of possible encodings, see https://docs.python.org/3.11/library/codecs.html#standard-encodings +encoding: utf-8 + + + +# list of languages for which language servers are started; choose from: +# al bash clojure cpp csharp +# csharp_omnisharp dart elixir elm erlang +# fortran fsharp go groovy haskell +# java julia kotlin lua markdown +# matlab nix pascal perl php +# powershell python python_jedi r rego +# ruby ruby_solargraph rust scala swift +# terraform toml typescript typescript_vts vue +# yaml zig +# (This list may be outdated. For the current list, see values of Language enum here: +# https://github.com/oraios/serena/blob/main/src/solidlsp/ls_config.py +# For some languages, there are alternative language servers, e.g. csharp_omnisharp, ruby_solargraph.) +# Note: +# - For C, use cpp +# - For JavaScript, use typescript +# - For Free Pascal/Lazarus, use pascal +# Special requirements: +# Some languages require additional setup/installations. +# See here for details: https://oraios.github.io/serena/01-about/020_programming-languages.html#language-servers +# When using multiple languages, the first language server that supports a given file will be used for that file. +# The first language is the default language and the respective language server will be used as a fallback. +# Note that when using the JetBrains backend, language servers are not used and this list is correspondingly ignored. +languages: +- typescript diff --git a/mcp/README.md b/mcp/README.md new file mode 100644 index 0000000000..199281cd07 --- /dev/null +++ b/mcp/README.md @@ -0,0 +1,247 @@ +![mcp-server-cover-github-1](https://github.com/user-attachments/assets/dcd14e63-fecd-424f-9a50-c1b1eafe2a4f) + +# Penpot's Official MCP Server + +Penpot integrates a LLM layer built on the Model Context Protocol (MCP) via Penpot's Plugin API to interact with a Penpot design file. Penpot's MCP server enables LLMs to perfom data queries, transformation and creation operations. + +Penpot's MCP Server is unlike any other you've seen. You get design-to- design, code-to-design and design-code supercharged workflows. + + +[![Penpot MCP video playlist](https://github.com/user-attachments/assets/204f1d99-ce51-41dd-a5dd-1ef739f8f089)](https://www.youtube.com/playlist?list=PLgcCPfOv5v57SKMuw1NmS0-lkAXevpn10) + + +## Architecture + +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 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 thus contains not only the MCP server implementation itself +but also the supporting Penpot MCP Plugin +(see section [Repository Structure](#repository-structure) below). + +## Demonstration + +[![Video](https://v32155.1blu.de/penpot/PenpotFest2025_thumbnail.png)](https://v32155.1blu.de/penpot/PenpotFest2025.mp4) + + +## Usage + +To use the Penpot MCP server, you must + * run the MCP server and connect your AI client to it, + * run the web server providing the Penpot MCP plugin, and + * open the Penpot MCP plugin in Penpot and connect it to the MCP server. + +Follow the steps below to enable the integration. + + +### Prerequisites + +The project requires [Node.js](https://nodejs.org/) (tested with v22). +Following the installation of Node.js, the tools `npm` and `npx` should be +available in your terminal. + +You should probably be using penpot devenv, where all this dependencies are +already present and correctly setup. + + +### 1. Build & Launch the MCP Server and the Plugin Server + +If it's your first execution, install the required dependencies: +```shell +cd mcp/ +./scripts/setup +``` + +Then build all components and start the two servers: +```shell +pnpm run bootstrap +``` + +This bootstrap command will: + + * install dependencies for all components (`pnpm -r run install`) + * build all components (`pnpm -r run build`) + * start all components (`pnpm -r --parallel run start`) + + +### 2. Load the Plugin in Penpot and Establish the Connection + +> [!NOTE] +> **Browser Connectivity Restrictions** +> +> Starting with Chromium version 142, the private network access (PNA) restrictions have been hardened, +> and when connecting to `localhost` from a web application served from a different origin +> (such as https://design.penpot.app), the connection must explicitly be allowed. +> +> Most Chromium-based browsers (e.g. Chrome, Vivaldi) will display a popup requesting permission +> to access the local network. Be sure to approve the request to allow the connection. +> +> Some browsers take additional security measures, and you may need to disable them. +> For example, in Brave, disable the "Shield" for the Penpot website to allow local network access. +> +> If your browser refuses to connect to the locally served plugin, check its configuration or +> try a different browser (e.g. Firefox) that does not enforce these restrictions. + +1. Open Penpot in your browser +2. Navigate to a design file +3. Open the Plugins menu +4. Load the plugin using the development URL (`http://localhost:4400/manifest.json` by default) +5. Open the plugin UI +6. In the plugin UI, click "Connect to MCP server". + The connection status should change from "Not connected" to "Connected to MCP server". + (Check the browser's developer console for WebSocket connection logs. + Check the MCP server terminal for WebSocket connection messages.) + +> [!IMPORTANT] +> Do not close the plugin's UI while using the MCP server, as this will close the connection. + +### 3. Connect an MCP Client + +By default, the server runs on port 4401 and provides: + +- **Modern Streamable HTTP endpoint**: `http://localhost:4401/mcp` +- **Legacy SSE endpoint**: `http://localhost:4401/sse` + +These endpoints can be used directly by MCP clients that support them. +Simply configure the client to connect the MCP server by providing the respective URL. + +When using a client that only supports stdio transport, +a proxy like `mcp-remote` is required. + +#### Using a Proxy for stdio Transport + +NOTE: only relevant if you are executing this outside of devenv + +The `mcp-remote` package can proxy stdio transport to HTTP/SSE, +allowing clients that support only stdio to connect to the MCP server indirectly. + +1. Install `mcp-remote` globally if you haven't already: + + npm install -g mcp-remote + +2. Use `mcp-remote` to provide the launch command for your MCP client: + + npx -y mcp-remote http://localhost:4401/sse --allow-http + +#### Example: Claude Desktop + +For Windows and macOS, there is the official [Claude Desktop app](https://claude.ai/download), which you can use as an MCP client. +For Linux, there is an [unofficial community version](https://github.com/aaddrick/claude-desktop-debian). + +Since Claude Desktop natively supports only stdio transport, you will need to use a proxy like `mcp-remote`. +Install it as described above. + +To add the server to Claude Desktop's configuration, locate the configuration file (or find it via Menu / File / Settings / Developer): + +- **Windows**: `%APPDATA%/Claude/claude_desktop_config.json` +- **macOS**: `~/Library/Application Support/Claude/claude_desktop_config.json` +- **Linux**: `~/.config/Claude/claude_desktop_config.json` + +Add a `penpot` entry under `mcpServers` with the following content: + +```json +{ + "mcpServers": { + "penpot": { + "command": "npx", + "args": ["-y", "mcp-remote", "http://localhost:4401/sse", "--allow-http"] + } + } +} +``` + +After updating the configuration file, restart Claude Desktop completely for the changes to take effect. + +> [!IMPORTANT] +> Be sure to fully quit the app for the changes to take effect; closing the window is *not* sufficient. +> To fully terminate the app, choose Menu / File / Quit. + +After the restart, you should see the MCP server listed when clicking on the "Search and tools" icon at the bottom +of the prompt input area. + +#### Example: Claude Code + +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. + +## Configuration + +The Penpot MCP server can be configured using environment variables. All configuration +options use the `PENPOT_MCP_` prefix for consistency. + +### Server Configuration + +| Environment Variable | Description | Default | +|------------------------------------|----------------------------------------------------------------------------|--------------| +| `PENPOT_MCP_SERVER_LISTEN_ADDRESS` | Address on which the MCP server listens (binds to) | `localhost` | +| `PENPOT_MCP_SERVER_PORT` | Port for the HTTP/SSE server | `4401` | +| `PENPOT_MCP_WEBSOCKET_PORT` | Port for the WebSocket server (plugin connection) | `4402` | +| `PENPOT_MCP_REPL_PORT` | Port for the REPL server (development/debugging) | `4403` | +| `PENPOT_MCP_SERVER_ADDRESS` | Hostname or IP address via which clients can reach the MCP server | `localhost` | +| `PENPOT_MCP_REMOTE_MODE` | Enable remote mode (disables file system access). Set to `true` to enable. | `false` | + +### Logging Configuration + +| Environment Variable | Description | Default | +|------------------------|------------------------------------------------------|----------| +| `PENPOT_MCP_LOG_LEVEL` | Log level: `trace`, `debug`, `info`, `warn`, `error` | `info` | +| `PENPOT_MCP_LOG_DIR` | Directory for log files | `logs` | + +### Plugin Server Configuration + +| Environment Variable | Description | Default | +|-------------------------------------------|-----------------------------------------------------------------------------------------|--------------| +| `PENPOT_MCP_PLUGIN_SERVER_LISTEN_ADDRESS` | Address on which the plugin web server listens (single address or comma-separated list) | (local only) | + +## 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. + +To run the server remotely (even for a single user), +you may set the following environment variables to configure the two servers +(MCP server & plugin server) appropriately: + * `PENPOT_MCP_REMOTE_MODE=true`: This ensures that the MCP server is operating + in remote mode, with local file system access disabled. + * `PENPOT_MCP_SERVER_LISTEN_ADDRESS` and `PENPOT_MCP_PLUGIN_SERVER_LISTEN_ADDRESS`: + Set these according to your requirements for remote connectivity. + To bind all interfaces, use `0.0.0.0` (use caution in untrusted networks). + * `PENPOT_MCP_SERVER_ADDRESS=`: This sets the hostname or IP address + where the MCP server can be reached. The Penpot MCP Plugin uses this to construct + the WebSocket URL as `ws://:` (default port: `4402`). diff --git a/mcp/docs/multi-user-mode.md b/mcp/docs/multi-user-mode.md new file mode 100644 index 0000000000..b4471d18b9 --- /dev/null +++ b/mcp/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/package.json b/mcp/package.json new file mode 100644 index 0000000000..ff96a6be16 --- /dev/null +++ b/mcp/package.json @@ -0,0 +1,25 @@ +{ + "name": "mcp-meta", + "version": "1.0.0", + "description": "", + "scripts": { + "build": "pnpm -r run build", + "build:multi-user": "pnpm -r run build:multi-user", + "start": "pnpm -r --parallel run start", + "start:multi-user": "pnpm -r --parallel --filter \"./packages/*\" run start:multi-user", + "bootstrap": "pnpm -r install && pnpm run build && pnpm run start", + "bootstrap:multi-user": "npm run install && npm run build:multi-user && pnpm run start:multi-user", + "fmt": "prettier --write packages/", + "fmt:check": "prettier --check packages/" + }, + "repository": { + "type": "git", + "url": "https://github.com/penpot/penpot.git" + }, + "packageManager": "pnpm@10.28.2+sha512.41872f037ad22f7348e3b1debbaf7e867cfd448f2726d9cf74c08f19507c31d2c8e7a11525b983febc2df640b5438dee6023ebb1f84ed43cc2d654d2bc326264", + "private": true, + "devDependencies": { + "concurrently": "^9.2.1", + "prettier": "^3.0.0" + } +} diff --git a/mcp/packages/common/package.json b/mcp/packages/common/package.json new file mode 100644 index 0000000000..6c014b34ac --- /dev/null +++ b/mcp/packages/common/package.json @@ -0,0 +1,20 @@ +{ + "name": "mcp-common", + "version": "1.0.0", + "description": "Shared type definitions and interfaces for Penpot MCP", + "main": "dist/index.js", + "types": "dist/index.d.ts", + "packageManager": "pnpm@10.28.2+sha512.41872f037ad22f7348e3b1debbaf7e867cfd448f2726d9cf74c08f19507c31d2c8e7a11525b983febc2df640b5438dee6023ebb1f84ed43cc2d654d2bc326264", + "scripts": { + "build": "tsc --build --clean && tsc --build", + "watch": "tsc --watch", + "types:check": "tsc --noEmit", + "clean": "rm -rf dist/" + }, + "devDependencies": { + "typescript": "^5.0.0" + }, + "files": [ + "dist/**/*" + ] +} diff --git a/mcp/packages/common/src/index.ts b/mcp/packages/common/src/index.ts new file mode 100644 index 0000000000..eea524d655 --- /dev/null +++ b/mcp/packages/common/src/index.ts @@ -0,0 +1 @@ +export * from "./types"; diff --git a/mcp/packages/common/src/types.ts b/mcp/packages/common/src/types.ts new file mode 100644 index 0000000000..36e52cc531 --- /dev/null +++ b/mcp/packages/common/src/types.ts @@ -0,0 +1,85 @@ +/** + * Result of a plugin task execution. + * + * Contains the outcome status of a task and any additional result data. + */ +export interface PluginTaskResult { + /** + * Optional result data from the task execution. + */ + data?: T; +} + +/** + * Request message sent from server to plugin. + * + * Contains a unique identifier, task name, and parameters for execution. + */ +export interface PluginTaskRequest { + /** + * Unique identifier for request/response correlation. + */ + id: string; + + /** + * The name of the task to execute. + */ + task: string; + + /** + * The parameters for task execution. + */ + params: any; +} + +/** + * Response message sent from plugin back to server. + * + * Contains the original request ID and the execution result. + */ +export interface PluginTaskResponse { + /** + * Unique identifier matching the original request. + */ + id: string; + + /** + * Whether the task completed successfully. + */ + success: boolean; + + /** + * Optional error message if the task failed. + */ + error?: string; + + /** + * The result of the task execution. + */ + data?: T; +} + +/** + * Parameters for the executeCode task. + */ +export interface ExecuteCodeTaskParams { + /** + * The JavaScript code to be executed. + */ + code: string; +} + +/** + * Result data for the executeCode task. + */ +export interface ExecuteCodeTaskResultData { + /** + * The result of the executed code, if any. + */ + result: T; + + /** + * Captured console output during code execution. + */ + log: string; +} diff --git a/mcp/packages/common/tsconfig.json b/mcp/packages/common/tsconfig.json new file mode 100644 index 0000000000..89b057e684 --- /dev/null +++ b/mcp/packages/common/tsconfig.json @@ -0,0 +1,19 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "commonjs", + "lib": ["ES2022"], + "outDir": "./dist", + "rootDir": "./src", + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true, + "declaration": true, + "declarationMap": true, + "sourceMap": true, + "composite": true + }, + "include": ["src/**/*"], + "exclude": ["node_modules", "dist"] +} diff --git a/mcp/packages/plugin/.gitignore b/mcp/packages/plugin/.gitignore new file mode 100644 index 0000000000..a547bf36d8 --- /dev/null +++ b/mcp/packages/plugin/.gitignore @@ -0,0 +1,24 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +dist +dist-ssr +*.local + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? diff --git a/mcp/packages/plugin/README.md b/mcp/packages/plugin/README.md new file mode 100644 index 0000000000..1a194fa19f --- /dev/null +++ b/mcp/packages/plugin/README.md @@ -0,0 +1,21 @@ +# Penpot MCP Plugin + +This project contains a Penpot plugin that accompanies the Penpot MCP server. +It connects to the MCP server via WebSocket, subsequently allowing the MCP +server to execute tasks in Penpot using the Plugin API. + +## Setup + +1. Install Dependencies + + pnpm install + +2. Build the Project + + pnpm run build + +3. Start a Local Development Server + + pnpm run start + + This will start a local development server at `http://localhost:4400`. diff --git a/mcp/packages/plugin/index.html b/mcp/packages/plugin/index.html new file mode 100644 index 0000000000..b2c08b5dae --- /dev/null +++ b/mcp/packages/plugin/index.html @@ -0,0 +1,15 @@ + + + + + + Penpot plugin example + + + + +
Not connected
+ + + + diff --git a/mcp/packages/plugin/package.json b/mcp/packages/plugin/package.json new file mode 100644 index 0000000000..2fca8aeaae --- /dev/null +++ b/mcp/packages/plugin/package.json @@ -0,0 +1,24 @@ +{ + "name": "mcp-plugin", + "private": true, + "version": "1.0.0", + "type": "module", + "scripts": { + "start": "vite build --watch --config vite.config.ts", + "start:multi-user": "cross-env MULTI_USER_MODE=true vite build --watch --config vite.config.ts", + "build": "tsc && vite build --config vite.release.config.ts", + "build:multi-user": "tsc && cross-env MULTI_USER_MODE=true vite build --config vite.release.config.ts", + "types:check": "tsc --noEmit", + "clean": "rm -rf dist/" + }, + "dependencies": { + "@penpot/plugin-styles": "1.4.1", + "@penpot/plugin-types": "1.4.1" + }, + "devDependencies": { + "cross-env": "^7.0.3", + "typescript": "^5.8.3", + "vite": "^7.0.8", + "vite-live-preview": "^0.3.2" + } +} diff --git a/mcp/packages/plugin/public/manifest.json b/mcp/packages/plugin/public/manifest.json new file mode 100644 index 0000000000..506021c29e --- /dev/null +++ b/mcp/packages/plugin/public/manifest.json @@ -0,0 +1,6 @@ +{ + "name": "Penpot MCP Plugin", + "code": "plugin.js", + "description": "This plugin enables interaction with the Penpot MCP server", + "permissions": ["content:read", "content:write", "library:read", "library:write", "comment:read", "comment:write"] +} diff --git a/mcp/packages/plugin/src/PenpotUtils.ts b/mcp/packages/plugin/src/PenpotUtils.ts new file mode 100644 index 0000000000..d5044e658a --- /dev/null +++ b/mcp/packages/plugin/src/PenpotUtils.ts @@ -0,0 +1,425 @@ +import { Board, Fill, FlexLayout, GridLayout, Page, Rectangle, Shape } from "@penpot/plugin-types"; + +export class PenpotUtils { + /** + * Generates an overview structure of the given shape, + * providing its id, name and type, and recursively its children's attributes. + * The `type` field indicates the type in the Penpot API. + * If the shape has a layout system (flex or grid), includes layout information. + * + * @param shape - The root shape to generate the structure from + * @param maxDepth - Optional maximum depth to traverse (leave undefined for unlimited) + * @returns An object representing the shape structure + */ + public static shapeStructure(shape: Shape, maxDepth: number | undefined = undefined): object { + let children = undefined; + if (maxDepth === undefined || maxDepth > 0) { + if ("children" in shape && shape.children) { + children = shape.children.map((child) => + this.shapeStructure(child, maxDepth === undefined ? undefined : maxDepth - 1) + ); + } + } + + const result: any = { + id: shape.id, + name: shape.name, + type: shape.type, + children: children, + }; + + // add layout information if present + if ("flex" in shape && shape.flex) { + const flex: FlexLayout = shape.flex; + result.layout = { + type: "flex", + dir: flex.dir, + rowGap: flex.rowGap, + columnGap: flex.columnGap, + }; + } else if ("grid" in shape && shape.grid) { + const grid: GridLayout = shape.grid; + result.layout = { + type: "grid", + rows: grid.rows, + columns: grid.columns, + rowGap: grid.rowGap, + columnGap: grid.columnGap, + }; + } + + return result; + } + + /** + * Finds all shapes that matches the given predicate in the given shape tree. + * + * @param predicate - A function that takes a shape and returns true if it matches the criteria + * @param root - The root shape to start the search from (defaults to penpot.root) + */ + public static findShapes(predicate: (shape: Shape) => boolean, root: Shape | null = penpot.root): Shape[] { + let result = new Array(); + + let find = function (shape: Shape | null) { + if (!shape) { + return; + } + if (predicate(shape)) { + result.push(shape); + } + if ("children" in shape && shape.children) { + for (let child of shape.children) { + find(child); + } + } + }; + + find(root); + return result; + } + + /** + * Finds the first shape that matches the given predicate in the given shape tree. + * + * @param predicate - A function that takes a shape and returns true if it matches the criteria + * @param root - The root shape to start the search from (if null, searches all pages) + */ + public static findShape(predicate: (shape: Shape) => boolean, root: Shape | null = null): Shape | null { + let find = function (shape: Shape | null): Shape | null { + if (!shape) { + return null; + } + if (predicate(shape)) { + return shape; + } + if ("children" in shape && shape.children) { + for (let child of shape.children) { + let result = find(child); + if (result) { + return result; + } + } + } + return null; + }; + + if (root === null) { + const pages = penpot.currentFile?.pages; + if (pages) { + for (let page of pages) { + let result = find(page.root); + if (result) { + return result; + } + } + } + return null; + } else { + return find(root); + } + } + + /** + * Finds a shape by its unique ID. + * + * @param id - The unique ID of the shape to find + * @returns The shape with the matching ID, or null if not found + */ + public static findShapeById(id: string): Shape | null { + return this.findShape((shape) => shape.id === id); + } + + public static findPage(predicate: (page: Page) => boolean): Page | null { + let page = penpot.currentFile!.pages.find(predicate); + return page || null; + } + + public static getPages(): { id: string; name: string }[] { + return penpot.currentFile!.pages.map((page) => ({ id: page.id, name: page.name })); + } + + public static getPageById(id: string): Page | null { + return this.findPage((page) => page.id === id); + } + + public static getPageByName(name: string): Page | null { + return this.findPage((page) => page.name.toLowerCase() === name.toLowerCase()); + } + + public static getPageForShape(shape: Shape): Page | null { + for (const page of penpot.currentFile!.pages) { + if (page.getShapeById(shape.id)) { + return page; + } + } + return null; + } + + public static generateCss(shape: Shape): string { + const page = this.getPageForShape(shape); + if (!page) { + throw new Error("Shape is not part of any page"); + } + penpot.openPage(page); + return penpot.generateStyle([shape], { type: "css", includeChildren: true }); + } + + /** + * Checks if a child shape is fully contained within its parent's bounds. + * Visual containment means all edges of the child are within the parent's bounding box. + * + * @param child - The child shape to check + * @param parent - The parent shape to check against + * @returns true if child is fully contained within parent bounds, false otherwise + */ + public static isContainedIn(child: Shape, parent: Shape): boolean { + return ( + child.x >= parent.x && + child.y >= parent.y && + child.x + child.width <= parent.x + parent.width && + child.y + child.height <= parent.y + parent.height + ); + } + + /** + * Sets the position of a shape relative to its parent's position. + * This is a convenience method since parentX and parentY are read-only properties. + * + * @param shape - The shape to position + * @param parentX - The desired X position relative to the parent + * @param parentY - The desired Y position relative to the parent + * @throws Error if the shape has no parent + */ + public static setParentXY(shape: Shape, parentX: number, parentY: number): void { + if (!shape.parent) { + throw new Error("Shape has no parent - cannot set parent-relative position"); + } + shape.x = shape.parent.x + parentX; + shape.y = shape.parent.y + parentY; + } + + /** + * Adds a flex layout to a container while preserving the visual order of existing children. + * Without this, adding a flex layout can arbitrarily reorder children. + * + * The method sorts children by their current position (x for "row", y for "column") before + * adding the layout, then reorders them to maintain that visual sequence. + * + * @param container - The container (board) to add the flex layout to + * @param dir - The layout direction: "row" for horizontal, "column" for vertical + * @returns The created FlexLayout instance + */ + public static addFlexLayout(container: Board, dir: "column" | "row"): FlexLayout { + // obtain children sorted by position (ascending) + const children = "children" in container && container.children ? [...container.children] : []; + const sortedChildren = children.sort((a, b) => (dir === "row" ? a.x - b.x : a.y - b.y)); + + // add the flex layout + const flexLayout = container.addFlexLayout(); + flexLayout.dir = dir; + + // reorder children to preserve visual order; since the children array is reversed + // relative to visual order for dir="column" or dir="row", we insert each child at + // index 0 in sorted order, which places the first (smallest position) at the highest + // index, making it appear first visually + for (const child of sortedChildren) { + child.setParentIndex(0); + } + + return flexLayout; + } + + /** + * Analyzes all descendants of a shape by applying an evaluator function to each. + * Only descendants for which the evaluator returns a non-null/non-undefined value are included in the result. + * This is a general-purpose utility for validation, analysis, or collecting corrector functions. + * + * @param root - The root shape whose descendants to analyze + * @param evaluator - Function called for each descendant with (root, descendant); return null/undefined to skip + * @param maxDepth - Optional maximum depth to traverse (undefined for unlimited) + * @returns Array of objects containing the shape and the evaluator's result + */ + public static analyzeDescendants( + root: Shape, + evaluator: (root: Shape, descendant: Shape) => T | null | undefined, + maxDepth: number | undefined = undefined + ): Array<{ shape: Shape; result: NonNullable }> { + const results: Array<{ shape: Shape; result: NonNullable }> = []; + + const traverse = (shape: Shape, currentDepth: number): void => { + const result = evaluator(root, shape); + if (result !== null && result !== undefined) { + results.push({ shape, result: result as NonNullable }); + } + + if (maxDepth === undefined || currentDepth < maxDepth) { + if ("children" in shape && shape.children) { + for (const child of shape.children) { + traverse(child, currentDepth + 1); + } + } + } + }; + + // Start traversal with root's children (not root itself) + if ("children" in root && root.children) { + for (const child of root.children) { + traverse(child, 1); + } + } + + return results; + } + + /** + * Decodes a base64 string to a Uint8Array. + * This is required because the Penpot plugin environment does not provide the atob function. + * + * @param base64 - The base64-encoded string to decode + * @returns The decoded data as a Uint8Array + */ + public static atob(base64: string): Uint8Array { + const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + const lookup = new Uint8Array(256); + for (let i = 0; i < chars.length; i++) { + lookup[chars.charCodeAt(i)] = i; + } + + let bufferLength = base64.length * 0.75; + if (base64[base64.length - 1] === "=") { + bufferLength--; + if (base64[base64.length - 2] === "=") { + bufferLength--; + } + } + + const bytes = new Uint8Array(bufferLength); + let p = 0; + for (let i = 0; i < base64.length; i += 4) { + const encoded1 = lookup[base64.charCodeAt(i)]; + const encoded2 = lookup[base64.charCodeAt(i + 1)]; + const encoded3 = lookup[base64.charCodeAt(i + 2)]; + const encoded4 = lookup[base64.charCodeAt(i + 3)]; + + bytes[p++] = (encoded1 << 2) | (encoded2 >> 4); + bytes[p++] = ((encoded2 & 15) << 4) | (encoded3 >> 2); + bytes[p++] = ((encoded3 & 3) << 6) | (encoded4 & 63); + } + + return bytes; + } + + /** + * Imports an image from base64 data into the Penpot design as a Rectangle shape filled with the image. + * The rectangle has the image's original proportions by default. + * Optionally accepts position (x, y) and dimensions (width, height) parameters. + * If only one dimension is provided, the other is calculated to maintain the image's aspect ratio. + * + * This function is used internally by the ImportImageTool in the MCP server. + * + * @param base64 - The base64-encoded image data + * @param mimeType - The MIME type of the image (e.g., "image/png") + * @param name - The name to assign to the newly created rectangle shape + * @param x - The x-coordinate for positioning the rectangle (optional) + * @param y - The y-coordinate for positioning the rectangle (optional) + * @param width - The desired width of the rectangle (optional) + * @param height - The desired height of the rectangle (optional) + */ + public static async importImage( + base64: string, + mimeType: string, + name: string, + x: number | undefined, + y: number | undefined, + width: number | undefined, + height: number | undefined + ): Promise { + // convert base64 to Uint8Array + const bytes = PenpotUtils.atob(base64); + + // upload the image data to Penpot + const imageData = await penpot.uploadMediaData(name, bytes, mimeType); + + // create a rectangle shape + const rect = penpot.createRectangle(); + rect.name = name; + + // calculate dimensions + let rectWidth, rectHeight; + const hasWidth = width !== undefined; + const hasHeight = height !== undefined; + + if (hasWidth && hasHeight) { + // both width and height provided - use them directly + rectWidth = width; + rectHeight = height; + } else if (hasWidth) { + // only width provided - maintain aspect ratio + rectWidth = width; + rectHeight = rectWidth * (imageData.height / imageData.width); + } else if (hasHeight) { + // only height provided - maintain aspect ratio + rectHeight = height; + rectWidth = rectHeight * (imageData.width / imageData.height); + } else { + // neither provided - use original dimensions + rectWidth = imageData.width; + rectHeight = imageData.height; + } + + // set rectangle dimensions + rect.resize(rectWidth, rectHeight); + + // set position if provided + if (x !== undefined) { + rect.x = x; + } + if (y !== undefined) { + rect.y = y; + } + + // apply the image as a fill + rect.fills = [{ fillOpacity: 1, fillImage: imageData }]; + + return rect; + } + + /** + * Exports the given shape (or its fill) to BASE64 image data. + * + * This function is used internally by the ExportImageTool in the MCP server. + * + * @param shape - The shape whose image data to export + * @param mode - Either "shape" (to export the entire shape, including descendants) or "fill" + * to export the shape's raw fill image data + * @param asSVG - Whether to export as SVG rather than as a pixel image (only supported for mode "shape") + * @returns A byte array containing the exported image data. + * - For mode="shape", it will be PNG or SVG data depending on the value of `asSVG`. + * - For mode="fill", it will be whatever format the fill image is stored in. + */ + public static async exportImage(shape: Shape, mode: "shape" | "fill", asSVG: boolean): Promise { + switch (mode) { + case "shape": + return shape.export({ type: asSVG ? "svg" : "png" }); + case "fill": + if (asSVG) { + throw new Error("Image fills cannot be exported as SVG"); + } + // check whether the shape has the `fills` member + if (!("fills" in shape)) { + throw new Error("Shape with `fills` member is required for fill export mode"); + } + // find first fill that has fillImage + const fills: Fill[] = (shape as any).fills; + for (const fill of fills) { + if (fill.fillImage) { + const imageData = fill.fillImage; + return imageData.data(); + } + } + throw new Error("No fill with image data found in the shape"); + default: + throw new Error(`Unsupported export mode: ${mode}`); + } + } +} diff --git a/mcp/packages/plugin/src/TaskHandler.ts b/mcp/packages/plugin/src/TaskHandler.ts new file mode 100644 index 0000000000..b09a6c0c17 --- /dev/null +++ b/mcp/packages/plugin/src/TaskHandler.ts @@ -0,0 +1,77 @@ +/** + * Represents a task received from the MCP server in the Penpot MCP plugin + */ +export class Task { + public isResponseSent: boolean = false; + + /** + * @param requestId Unique identifier for the task request + * @param taskType The type of the task to execute + * @param params Task parameters/arguments + */ + constructor( + public requestId: string, + public taskType: string, + public params: TParams + ) {} + + /** + * Sends a task response back to the MCP server. + */ + protected sendResponse(success: boolean, data: any = undefined, error: any = undefined): void { + if (this.isResponseSent) { + console.error("Response already sent for task:", this.requestId); + return; + } + + const response = { + type: "task-response", + response: { + id: this.requestId, + success: success, + data: data, + error: error, + }, + }; + + // Send to main.ts which will forward to MCP server via WebSocket + penpot.ui.sendMessage(response); + console.log("Sent task response:", response); + this.isResponseSent = true; + } + + public sendSuccess(data: any = undefined): void { + this.sendResponse(true, data); + } + + public sendError(error: string): void { + this.sendResponse(false, undefined, error); + } +} + +/** + * Abstract base class for task handlers in the Penpot MCP plugin. + * + * @template TParams - The type of parameters this handler expects + */ +export abstract class TaskHandler { + /** The task type this handler is responsible for */ + abstract readonly taskType: string; + + /** + * Checks if this handler can process the given task. + * + * @param task - The task identifier to check + * @returns True if this handler applies to the given task + */ + isApplicableTo(task: Task): boolean { + return this.taskType === task.taskType; + } + + /** + * Handles the task with the provided parameters. + * + * @param task - The task to be handled + */ + abstract handle(task: Task): Promise; +} diff --git a/mcp/packages/plugin/src/main.ts b/mcp/packages/plugin/src/main.ts new file mode 100644 index 0000000000..18877d35dd --- /dev/null +++ b/mcp/packages/plugin/src/main.ts @@ -0,0 +1,110 @@ +import "./style.css"; + +// get the current theme from the URL +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, message?: string): void { + if (statusElement) { + const displayText = message ? `${status}: ${message}` : status; + statusElement.textContent = displayText; + statusElement.style.color = isConnectedState ? "var(--accent-primary)" : "var(--error-700)"; + } +} + +/** + * Sends a task response back to the MCP server via WebSocket. + * + * @param response - The response containing task ID and result + */ +function sendTaskResponse(response: any): void { + if (ws && ws.readyState === WebSocket.OPEN) { + ws.send(JSON.stringify(response)); + console.log("Sent response to MCP server:", response); + } else { + console.error("WebSocket not connected, cannot send response"); + } +} + +/** + * Establishes a WebSocket connection to the MCP server. + */ +function connectToMcpServer(): void { + if (ws?.readyState === WebSocket.OPEN) { + updateConnectionStatus("Already connected", true); + return; + } + + try { + let wsUrl = PENPOT_MCP_WEBSOCKET_URL; + if (isMultiUserMode) { + // TODO obtain proper userToken from penpot + const userToken = "dummyToken"; + wsUrl += `?userToken=${encodeURIComponent(userToken)}`; + } + ws = new WebSocket(wsUrl); + updateConnectionStatus("Connecting...", false); + + ws.onopen = () => { + console.log("Connected to MCP server"); + updateConnectionStatus("Connected to MCP server", true); + }; + + ws.onmessage = (event) => { + console.log("Received from MCP server:", event.data); + try { + const request = JSON.parse(event.data); + // Forward the task request to the plugin for execution + parent.postMessage(request, "*"); + } catch (error) { + console.error("Failed to parse WebSocket message:", error); + } + }; + + ws.onclose = (event: CloseEvent) => { + console.log("Disconnected from MCP server"); + 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); + const message = error instanceof Error ? error.message : undefined; + updateConnectionStatus("Connection failed", false, message); + } +} + +document.querySelector("[data-handler='connect-mcp']")?.addEventListener("click", () => { + connectToMcpServer(); +}); + +// Listen plugin.ts messages +window.addEventListener("message", (event) => { + if (event.data.source === "penpot") { + document.body.dataset.theme = event.data.theme; + } else if (event.data.type === "task-response") { + // Forward task response back to MCP server + sendTaskResponse(event.data.response); + } +}); diff --git a/mcp/packages/plugin/src/plugin.ts b/mcp/packages/plugin/src/plugin.ts new file mode 100644 index 0000000000..e6a1fad33e --- /dev/null +++ b/mcp/packages/plugin/src/plugin.ts @@ -0,0 +1,69 @@ +import { ExecuteCodeTaskHandler } from "./task-handlers/ExecuteCodeTaskHandler"; +import { Task, TaskHandler } from "./TaskHandler"; + +/** + * Registry of all available task handlers. + */ +const taskHandlers: TaskHandler[] = [new ExecuteCodeTaskHandler()]; + +// 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; + +// 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); + }); + } +}); + +/** + * Handles plugin task requests received from the MCP server via WebSocket. + * + * @param request - The task request containing ID, task type and parameters + */ +async function handlePluginTaskRequest(request: { id: string; task: string; params: any }): Promise { + console.log("Executing plugin task:", request.task, request.params); + const task = new Task(request.id, request.task, request.params); + + // Find the appropriate handler + const handler = taskHandlers.find((h) => h.isApplicableTo(task)); + + if (handler) { + try { + // Cast the params to the expected type and handle the task + console.log("Processing task with handler:", handler); + await handler.handle(task); + + // check whether a response was sent and send a generic success if not + if (!task.isResponseSent) { + console.warn("Handler did not send a response, sending generic success."); + task.sendSuccess("Task completed without a specific response."); + } + + console.log("Task handled successfully:", task); + } catch (error) { + console.error("Error handling task:", error); + const errorMessage = error instanceof Error ? error.message : "Unknown error"; + task.sendError(`Error handling task: ${errorMessage}`); + } + } else { + console.error("Unknown plugin task:", request.task); + task.sendError(`Unknown task type: ${request.task}`); + } +} + +// Handle theme change in the iframe +penpot.on("themechange", (theme) => { + penpot.ui.sendMessage({ + source: "penpot", + type: "themechange", + theme, + }); +}); diff --git a/mcp/packages/plugin/src/style.css b/mcp/packages/plugin/src/style.css new file mode 100644 index 0000000000..030f2204e9 --- /dev/null +++ b/mcp/packages/plugin/src/style.css @@ -0,0 +1,10 @@ +@import "@penpot/plugin-styles/styles.css"; + +body { + line-height: 1.5; + padding: 10px; +} + +p { + margin-block-end: 0.75rem; +} diff --git a/mcp/packages/plugin/src/task-handlers/ExecuteCodeTaskHandler.ts b/mcp/packages/plugin/src/task-handlers/ExecuteCodeTaskHandler.ts new file mode 100644 index 0000000000..86fc62710f --- /dev/null +++ b/mcp/packages/plugin/src/task-handlers/ExecuteCodeTaskHandler.ts @@ -0,0 +1,212 @@ +import { Task, TaskHandler } from "../TaskHandler"; +import { ExecuteCodeTaskParams, ExecuteCodeTaskResultData } from "../../../common/src"; +import { PenpotUtils } from "../PenpotUtils.ts"; + +/** + * Console implementation that captures all log output for code execution. + * + * Provides the same interface as the native console object but appends + * all output to an internal log string that can be retrieved. + */ +class ExecuteCodeTaskConsole { + /** + * Accumulated log output from all console method calls. + */ + private logOutput: string = ""; + + /** + * Resets the accumulated log output to empty string. + * Should be called before each code execution to start with clean logs. + */ + resetLog(): void { + this.logOutput = ""; + } + + /** + * Gets the accumulated log output from all console method calls. + * @returns The complete log output as a string + */ + getLog(): string { + return this.logOutput; + } + + /** + * Appends a formatted message to the log output. + * @param level - Log level prefix (e.g., "LOG", "WARN", "ERROR") + * @param args - Arguments to log, will be stringified and joined + */ + private appendToLog(level: string, ...args: any[]): void { + const message = args + .map((arg) => (typeof arg === "object" ? JSON.stringify(arg, null, 2) : String(arg))) + .join(" "); + this.logOutput += `[${level}] ${message}\n`; + } + + /** + * Logs a message to the captured output. + */ + log(...args: any[]): void { + this.appendToLog("LOG", ...args); + } + + /** + * Logs a warning message to the captured output. + */ + warn(...args: any[]): void { + this.appendToLog("WARN", ...args); + } + + /** + * Logs an error message to the captured output. + */ + error(...args: any[]): void { + this.appendToLog("ERROR", ...args); + } + + /** + * Logs an informational message to the captured output. + */ + info(...args: any[]): void { + this.appendToLog("INFO", ...args); + } + + /** + * Logs a debug message to the captured output. + */ + debug(...args: any[]): void { + this.appendToLog("DEBUG", ...args); + } + + /** + * Logs a message with trace information to the captured output. + */ + trace(...args: any[]): void { + this.appendToLog("TRACE", ...args); + } + + /** + * Logs a table to the captured output (simplified as JSON). + */ + table(data: any): void { + this.appendToLog("TABLE", data); + } + + /** + * Starts a timer (simplified implementation that just logs). + */ + time(label?: string): void { + this.appendToLog("TIME", `Timer started: ${label || "default"}`); + } + + /** + * Ends a timer (simplified implementation that just logs). + */ + timeEnd(label?: string): void { + this.appendToLog("TIME_END", `Timer ended: ${label || "default"}`); + } + + /** + * Logs messages in a group (simplified to just log the label). + */ + group(label?: string): void { + this.appendToLog("GROUP", label || ""); + } + + /** + * Logs messages in a collapsed group (simplified to just log the label). + */ + groupCollapsed(label?: string): void { + this.appendToLog("GROUP_COLLAPSED", label || ""); + } + + /** + * Ends the current group (simplified implementation). + */ + groupEnd(): void { + this.appendToLog("GROUP_END", ""); + } + + /** + * Clears the console (no-op in this implementation since we want to capture logs). + */ + clear(): void { + // intentionally empty - we don't want to clear captured logs + } + + /** + * Counts occurrences of calls with the same label (simplified implementation). + */ + count(label?: string): void { + this.appendToLog("COUNT", label || "default"); + } + + /** + * Resets the count for a label (simplified implementation). + */ + countReset(label?: string): void { + this.appendToLog("COUNT_RESET", label || "default"); + } + + /** + * Logs an assertion (simplified to just log if condition is false). + */ + assert(condition: boolean, ...args: any[]): void { + if (!condition) { + this.appendToLog("ASSERT", ...args); + } + } +} + +/** + * Task handler for executing JavaScript code in the plugin context. + * + * Maintains a persistent context object that preserves state between code executions + * and captures all console output during execution. + */ +export class ExecuteCodeTaskHandler extends TaskHandler { + readonly taskType = "executeCode"; + + /** + * Persistent context object that maintains state between code executions. + * Contains the penpot API, storage object, and custom console implementation. + */ + private readonly context: any; + + constructor() { + super(); + + // initialize context, making penpot, penpotUtils, storage and the custom console available + this.context = { + penpot: penpot, + storage: {}, + console: new ExecuteCodeTaskConsole(), + penpotUtils: PenpotUtils, + }; + } + + async handle(task: Task): Promise { + if (!task.params.code) { + task.sendError("executeCode task requires 'code' parameter"); + return; + } + + this.context.console.resetLog(); + + const context = this.context; + const code = task.params.code; + + let result: any = await (async (ctx) => { + const fn = new Function(...Object.keys(ctx), `return (async () => { ${code} })();`); + return fn(...Object.values(ctx)); + })(context); + + console.log("Code execution result:", result); + + // return result and captured log + let resultData: ExecuteCodeTaskResultData = { + result: result, + log: this.context.console.getLog(), + }; + task.sendSuccess(resultData); + } +} diff --git a/mcp/packages/plugin/src/vite-env.d.ts b/mcp/packages/plugin/src/vite-env.d.ts new file mode 100644 index 0000000000..ddbf746e04 --- /dev/null +++ b/mcp/packages/plugin/src/vite-env.d.ts @@ -0,0 +1,4 @@ +/// + +declare const IS_MULTI_USER_MODE: boolean; +declare const PENPOT_MCP_WEBSOCKET_URL: string; diff --git a/mcp/packages/plugin/tsconfig.json b/mcp/packages/plugin/tsconfig.json new file mode 100644 index 0000000000..0db6d8a2c6 --- /dev/null +++ b/mcp/packages/plugin/tsconfig.json @@ -0,0 +1,24 @@ +{ + "compilerOptions": { + "target": "ES2020", + "useDefineForClassFields": true, + "module": "ESNext", + "lib": ["ES2020", "DOM", "DOM.Iterable"], + "skipLibCheck": true, + "typeRoots": ["./node_modules/@types", "./node_modules/@penpot"], + "types": ["plugin-types"], + /* Bundler mode */ + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + + /* Linting */ + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true + }, + "include": ["src"] +} diff --git a/mcp/packages/plugin/vite.config.ts b/mcp/packages/plugin/vite.config.ts new file mode 100644 index 0000000000..82613250ff --- /dev/null +++ b/mcp/packages/plugin/vite.config.ts @@ -0,0 +1,43 @@ +import { defineConfig } from "vite"; +import livePreview from "vite-live-preview"; + +// Debug: Log the environment variables +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")); + +let WS_URI = "http://localhost:4402"; +console.log("Will define PENPOT_MCP_WEBSOCKET_URL as:", JSON.stringify(WS_URI)); + +export default defineConfig({ + plugins: [ + livePreview({ + reload: true, + config: { + build: { + sourcemap: true, + }, + }, + }), + ], + build: { + rollupOptions: { + input: { + plugin: "src/plugin.ts", + index: "./index.html", + }, + output: { + entryFileNames: "[name].js", + }, + }, + }, + preview: { + host: "0.0.0.0", + port: 4400, + cors: true, + allowedHosts: [], + }, + define: { + IS_MULTI_USER_MODE: JSON.stringify(process.env.MULTI_USER_MODE === "true"), + PENPOT_MCP_WEBSOCKET_URL: JSON.stringify(WS_URI), + }, +}); diff --git a/mcp/packages/plugin/vite.release.config.ts b/mcp/packages/plugin/vite.release.config.ts new file mode 100644 index 0000000000..462fc9f598 --- /dev/null +++ b/mcp/packages/plugin/vite.release.config.ts @@ -0,0 +1,10 @@ +import { defineConfig, mergeConfig } from "vite"; +import baseConfig from "./vite.config"; + +export default mergeConfig( + baseConfig, + defineConfig({ + base: "./", + plugins: [], + }) +); diff --git a/mcp/packages/server/.gitignore b/mcp/packages/server/.gitignore new file mode 100644 index 0000000000..e69de29bb2 diff --git a/mcp/packages/server/README.md b/mcp/packages/server/README.md new file mode 100644 index 0000000000..78be5e1219 --- /dev/null +++ b/mcp/packages/server/README.md @@ -0,0 +1,24 @@ +# Penpot MCP Server + +A Model Context Protocol (MCP) server that provides Penpot integration +capabilities for AI clients supporting the model context protocol (MCP). + +## Setup + +1. Install Dependencies + + pnpm install + +2. Build the Project + + pnpm run build + +3. Run the Server + + pnpm run start + + +## Penpot Plugin API REPL + +The MCP server includes a REPL interface for testing Penpot Plugin API calls. +To use it, connect to the URL reported at startup. diff --git a/mcp/packages/server/data/api_types.yml b/mcp/packages/server/data/api_types.yml new file mode 100644 index 0000000000..d7094c06b0 --- /dev/null +++ b/mcp/packages/server/data/api_types.yml @@ -0,0 +1,18268 @@ +Penpot: + overview: |- + Interface Penpot + ================ + + These are methods and properties available on the `penpot` global object. + + ``` + interface Penpot { + ui: { + open: ((name: string, url: string, options?: { + width: number; + height: number; + }) => void); + size: null | { + width: number; + height: number; + }; + resize: ((width: number, height: number) => void); + sendMessage: ((message: unknown) => void); + onMessage: ((callback: ((message: T) => void)) => void); + }; + utils: ContextUtils; + closePlugin: (() => void); + on(type: T, callback: ((event: EventsMap[T]) => void), props?: { + [key: string]: unknown; + }): symbol; + off(listenerId: symbol): void; + root: null | Shape; + currentFile: null | File; + currentPage: null | Page; + viewport: Viewport; + history: HistoryContext; + library: LibraryContext; + fonts: FontsContext; + currentUser: User; + activeUsers: ActiveUser[]; + theme: Theme; + localStorage: LocalStorage; + selection: Shape[]; + shapesColors(shapes: Shape[]): (Color & ColorShapeInfo)[]; + replaceColor(shapes: Shape[], oldColor: Color, newColor: Color): void; + uploadMediaUrl(name: string, url: string): Promise; + uploadMediaData(name: string, data: Uint8Array, mimeType: string): Promise; + group(shapes: Shape[]): null | Group; + ungroup(group: Group, ...other: Group[]): void; + createRectangle(): Rectangle; + createBoard(): Board; + createEllipse(): Ellipse; + createPath(): Path; + createBoolean(boolType: BooleanType, shapes: Shape[]): null | Boolean; + createShapeFromSvg(svgString: string): null | Group; + createShapeFromSvgWithImages(svgString: string): Promise; + createText(text: string): null | Text; + generateMarkup(shapes: Shape[], options?: { + type?: "html" | "svg"; + }): string; + generateStyle(shapes: Shape[], options?: { + type?: "css"; + withPrelude?: boolean; + includeChildren?: boolean; + }): string; + generateFontFaces(shapes: Shape[]): Promise; + openViewer(): void; + createPage(): Page; + 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; + distributeVertical(shapes: Shape[]): void; + flatten(shapes: Shape[]): Path[]; + } + ``` + + Hierarchy + + * Omit + + Penpot + members: + Properties: + ui: |- + ``` + ui: { + open: ((name: string, url: string, options?: { + width: number; + height: number; + }) => void); + size: null | { + width: number; + height: number; + }; + resize: ((width: number, height: number) => void); + sendMessage: ((message: unknown) => void); + onMessage: ((callback: ((message: T) => void)) => void); + } + ``` + + Type declaration + + * open: ((name: string, url: string, options?: { width: number; height: number; }) => void) + + Opens the plugin UI. It is possible to develop a plugin without interface (see Palette color example) but if you need, the way to open this UI is using `penpot.ui.open`. + There is a minimum and maximum size for this modal and a default size but it's possible to customize it anyway with the options parameter. + + Example + ``` + penpot.ui.open('Plugin name', 'url', {width: 150, height: 300}); + ``` + + + ``` + (name, url, options?): void + ``` + + - Parameters + * name: string + + title of the plugin, it'll be displayed on the top of the modal + * url: string + + of the plugin + * options: { + width: number; + height: number; + } + + height and width of the modal. + + + width: number + + height: number + + Returns void + * size: null | { width: number; height: number; } + * resize: ((width: number, height: number) => void) + + Resizes the plugin UI. + + Example + ``` + penpot.ui.resize(300, 400); + ``` + + + ``` + (width, height): void + ``` + + - Parameters + * width: number + + The width of the modal. + * height: number + + The height of the modal. + + Returns void + * sendMessage: ((message: unknown) => void) + + Sends a message to the plugin UI. + + Example + ``` + this.sendMessage({ type: 'example-type', content: 'data we want to share' }); + ``` + + + ``` + (message): void + ``` + + - Parameters + * message: unknown + + content usually is an object + + Returns void + * onMessage: ((callback: ((message: T) => void)) => void) + + This is usually used in the `plugin.ts` file in order to handle the data sent by our plugin + + Example + ``` + penpot.ui.onMessage((message) => {if(message.type === 'example-type' { ...do something })}); + ``` + + + ``` + (callback): void + ``` + + - Type Parameters + * T + + Parameters + * callback: ((message: T) => void) + + A function that will be called whenever a message is received. + The function receives a single argument, `message`, which is of type `T`. + + + ``` + (message): void + ``` + + - Parameters + * message: T + + Returns void + + Returns void + utils: |- + ``` + utils: ContextUtils + ``` + + Provides access to utility functions and context-specific operations. + closePlugin: |- + ``` + closePlugin: (() => void) + ``` + + Closes the plugin. When this method is called the UI will be closed. + + Example + ``` + penpot.closePlugin(); + ``` + root: |- + ``` + readonly root: null | Shape + ``` + + The root shape in the current Penpot context. Requires `content:read` permission. + + Example + ``` + const rootShape = context.root;console.log(rootShape); + ``` + currentFile: |- + ``` + readonly currentFile: null | File + ``` + + Retrieves file data from the current Penpot context. Requires `content:read` permission. + + Returns + + Returns the file data or `null` if no file is available. + + Example + ``` + const fileData = context.currentFile;console.log(fileData); + ``` + currentPage: |- + ``` + readonly currentPage: null | Page + ``` + + The current page in the Penpot context. Requires `content:read` permission. + + Example + ``` + const currentPage = context.currentPage;console.log(currentPage); + ``` + viewport: |- + ``` + readonly viewport: Viewport + ``` + + The viewport settings in the Penpot context. + + Example + ``` + const viewportSettings = context.viewport;console.log(viewportSettings); + ``` + history: |- + ``` + readonly history: HistoryContext + ``` + + Context encapsulating the history operations + + Example + ``` + const historyContext = context.history;console.log(historyContext); + ``` + library: |- + ``` + readonly library: LibraryContext + ``` + + The library context in the Penpot context, including both local and connected libraries. Requires `library:read` permission. + + Example + ``` + const libraryContext = context.library;console.log(libraryContext); + ``` + fonts: |- + ``` + readonly fonts: FontsContext + ``` + + The fonts context in the Penpot context, providing methods to manage fonts. Requires `content:read` permission. + + Example + ``` + const fontsContext = context.fonts;console.log(fontsContext); + ``` + currentUser: |- + ``` + readonly currentUser: User + ``` + + The current user in the Penpot context. Requires `user:read` permission. + + Example + ``` + const currentUser = context.currentUser;console.log(currentUser); + ``` + activeUsers: |- + ``` + readonly activeUsers: ActiveUser[] + ``` + + An array of active users in the Penpot context. Requires `user:read` permission. + + Example + ``` + const activeUsers = context.activeUsers;console.log(activeUsers); + ``` + theme: |- + ``` + readonly theme: Theme + ``` + + The current theme (light or dark) in Penpot. + + Example + ``` + const currentTheme = context.theme;console.log(currentTheme); + ``` + localStorage: |- + ``` + readonly localStorage: LocalStorage + ``` + + Access to the localStorage proxy + selection: |- + ``` + selection: Shape[] + ``` + + The currently selected shapes in Penpot. Requires `content:read` permission. + + Example + ``` + const selectedShapes = context.selection;console.log(selectedShapes); + ``` + Methods: + on: |- + ``` + on(type, callback, props?): symbol + ``` + + * Adds an event listener for the specified event type. + Subscribing to events requires `content:read` permission. + + The following are the possible event types: + + + pagechange: event emitted when the current page changes. The callback will receive the new page. + + shapechange: event emitted when the shape changes. This event requires to send inside the `props` object the shape + that will be observed. For example: + ``` + // Observe the current selected shapepenpot.on('shapechange', (shape) => console.log(shape.name), { shapeId: penpot.selection[0].id }); + ``` + + + selectionchange: event emitted when the current selection changes. The callback will receive the list of ids for the new selection + + themechange: event emitted when the user changes its theme. The callback will receive the new theme (currently: either `dark` or `light`) + + documentsaved: event emitted after the document is saved in the backend. + + Type Parameters + + T extends keyof EventsMap + + Parameters + + type: T + + The event type to listen for. + + callback: ((event: EventsMap[T]) => void) + + The callback function to execute when the event is triggered. + + - ``` + (event): void + ``` + + * Parameters + + event: EventsMap[T] + + Returns void + + props: { + [key: string]: unknown; + } + + The properties for the current event handler. Only makes sense for specific events. + + - [key: string]: unknown + + Returns symbol + + the listener id that can be used to call `off` and cancel the listener + + Example + ``` + penpot.on('pagechange', () => {...do something}). + ``` + off: |- + ``` + off(listenerId): void + ``` + + * Removes an event listener for the specified event type. + + Parameters + + listenerId: symbol + + the id returned by the `on` method when the callback was set + + Returns void + + Example + ``` + const listenerId = penpot.on('contentsave', () => console.log("Changed"));penpot.off(listenerId); + ``` + shapesColors: |- + ``` + shapesColors(shapes): (Color & ColorShapeInfo)[] + ``` + + * Retrieves colors applied to the given shapes in Penpot. Requires `content:read` permission. + + Parameters + + shapes: Shape[] + + Returns (Color & ColorShapeInfo)[] + + Returns an array of colors and their shape information. + + Example + ``` + const colors = context.shapesColors(shapes);console.log(colors); + ``` + replaceColor: |- + ``` + replaceColor(shapes, oldColor, newColor): void + ``` + + * Replaces a specified old color with a new color in the given shapes. Requires `content:write` permission. + + Parameters + + shapes: Shape[] + + oldColor: Color + + newColor: Color + + Returns void + + Example + ``` + context.replaceColor(shapes, oldColor, newColor); + ``` + uploadMediaUrl: |- + ``` + uploadMediaUrl(name, url): Promise + ``` + + * Uploads media to Penpot and retrieves its image data. Requires `content:write` permission. + + Parameters + + name: string + + The name of the media. + + url: string + + The URL of the media to be uploaded. + + Returns Promise + + Returns a promise that resolves to the image data of the uploaded media. + + Example + ``` + const imageData = await context.uploadMediaUrl('example', 'https://example.com/image.jpg');console.log(imageData);// to insert the image in a shape we can doconst board = penpot.createBoard();const shape = penpot.createRectangle();board.appendChild(shape);shape.fills = [{ fillOpacity: 1, fillImage: imageData }]; + ``` + uploadMediaData: |- + ``` + uploadMediaData(name, data, mimeType): Promise + ``` + + * Uploads media to penpot and retrieves the image data. Requires `content:write` permission. + + Parameters + + name: string + + The name of the media. + + data: Uint8Array + + The image content data + + mimeType: string + + Returns Promise + + Returns a promise that resolves to the image data of the uploaded media. + + Example + ``` + const imageData = await context.uploadMediaData('example', imageData, 'image/jpeg');console.log(imageData); + ``` + group: |- + ``` + group(shapes): null | Group + ``` + + * Groups the specified shapes. Requires `content:write` permission. + + Parameters + + shapes: Shape[] + + An array of shapes to group. + + Returns null | Group + + Returns the newly created group or `null` if the group could not be created. + + Example + ``` + const penpotShapesArray = penpot.selection;penpot.group(penpotShapesArray); + ``` + ungroup: |- + ``` + ungroup(group, ...other): void + ``` + + * Ungroups the specified group. Requires `content:write` permission. + + Parameters + + group: Group + + The group to ungroup. + + ...other: Group[] + + Additional groups to ungroup. + + Returns void + + Example + ``` + const penpotShapesArray = penpot.selection;// We need to make sure that something is selected, and if the selected shape is a group,if (selected.length && penpot.utils.types.isGroup(penpotShapesArray[0])) { penpot.group(penpotShapesArray[0]);} + ``` + createRectangle: |- + ``` + createRectangle(): Rectangle + ``` + + * Use this method to create the shape of a rectangle. Requires `content:write` permission. + + Returns Rectangle + + Example + ``` + const shape = penpot.createRectangle();// just change the values like thisshape.name = "Example rectangle";// for solid colorshape.fills = [{ fillColor: "#7EFFF5" }];// for linear gradient colorshape.fills = [{ fillColorGradient: { "type": "linear", "startX": 0.5, "startY": 0, "endX": 0.5, "endY": 1, "width": 1, "stops": [ { "color": "#003ae9", "opacity": 1, "offset": 0 }, { "color": "#003ae9", "opacity": 0, "offset": 1 } ] }}];// for a image fillconst imageData = await context.uploadMediaUrl('example', 'https://example.com/image.jpg');shape.fills = [{ fillOpacity: 1, fillImage: imageData }];shape.borderRadius = 8;shape.strokes = [ { strokeColor: "#2e3434", strokeStyle: "solid", strokeWidth: 2, strokeAlignment: "center", },]; + ``` + createBoard: |- + ``` + createBoard(): Board + ``` + + * Use this method to create a board. This is the first step before anything else, the container. Requires `content:write` permission. + Then you can add a gridlayout, flexlayout or add a shape inside the board. + Just a heads-up: board is a board in Penpot UI. + + Returns Board + + Example + ``` + const board = penpot.createBoard();// to add grid layoutboard.addGridLayout();// to add flex layoutboard.addFlexLayout();// to create a shape inside the boardconst shape = penpot.createRectangle();board.appendChild(shape); + ``` + createEllipse: |- + ``` + createEllipse(): Ellipse + ``` + + * Use this method to create the shape of an ellipse. Requires `content:write` permission. + + Returns Ellipse + + Example + ``` + const shape = penpot.createEllipse();// just change the values like thisshape.name = "Example ellipse";// for solid colorshape.fills = [{ fillColor: "#7EFFF5" }];// for linear gradient colorshape.fills = [{ fillColorGradient: { "type": "linear", "startX": 0.5, "startY": 0, "endX": 0.5, "endY": 1, "width": 1, "stops": [ { "color": "#003ae9", "opacity": 1, "offset": 0 }, { "color": "#003ae9", "opacity": 0, "offset": 1 } ] }}];// for an image fillconst imageData = await context.uploadMediaUrl('example', 'https://example.com/image.jpg');shape.fills = [{ fillOpacity: 1, fillImage: imageData }];shape.strokes = [ { strokeColor: "#2e3434", strokeStyle: "solid", strokeWidth: 2, strokeAlignment: "center", },]; + ``` + createPath: |- + ``` + createPath(): Path + ``` + + * Use this method to create a path. Requires `content:write` permission. + + Returns Path + + Example + ``` + const path = penpot.createPath();path.name = "My path";// for solid colorpath.fills = [{ fillColor: "#7EFFF5" }]; + ``` + createBoolean: |- + ``` + createBoolean(boolType, shapes): null | Boolean + ``` + + * Creates a Boolean shape based on the specified boolean operation and shapes. Requires `content:write` permission. + + Parameters + + boolType: BooleanType + + The type of boolean operation ('union', 'difference', 'exclude', 'intersection'). + + shapes: Shape[] + + An array of shapes to perform the boolean operation on. + + Returns null | Boolean + + Returns the newly created Boolean shape resulting from the boolean operation. + + Example + ``` + const booleanShape = context.createBoolean('union', [shape1, shape2]); + ``` + createShapeFromSvg: |- + ``` + createShapeFromSvg(svgString): null | Group + ``` + + * Creates a Group from an SVG string. Requires `content:write` permission. + + Parameters + + svgString: string + + The SVG string representing the shapes to be converted into a group. + + Returns null | Group + + Returns the newly created Group containing the shapes from the SVG. + + Example + ``` + const svgGroup = context.createShapeFromSvg('...'); + ``` + createShapeFromSvgWithImages: |- + ``` + createShapeFromSvgWithImages(svgString): Promise + ``` + + * Creates a Group from an SVG string. The SVG can have images and the method returns + a Promise because the shape will be available after all images are uploaded. + Requires `content:write` permission. + + Parameters + + svgString: string + + The SVG string representing the shapes to be converted into a group. + + Returns Promise + + Returns a promise with the newly created Group containing the shapes from the SVG. + + Example + ``` + const svgGroup = await context.createShapeFromSvgWithImages('...'); + ``` + createText: |- + ``` + createText(text): null | Text + ``` + + * Creates a Text shape with the specified text content. Requires `content:write` permission. + + Parameters + + text: string + + The text content for the Text shape. + + Returns null | Text + + Returns the new created shape, if the shape wasn't created can return null. + + Example + ``` + const board = penpot.createBoard();let text;text = penpot.createText();// just change the values like thistext.growType = 'auto-height';text.fontFamily = 'Work Sans';text.fontSize = '12';text.fills = [{fillColor: '#9f05ff', fillOpacity: 1}];text.strokes = [{strokeOpacity: 1, strokeStyle: 'solid', strokeWidth: 2, strokeColor: '#deabff', strokeAlignment: 'outer'}];board.appendChild(text); + ``` + generateMarkup: |- + ``` + generateMarkup(shapes, options?): string + ``` + + * Generates markup for the given shapes. Requires `content:read` permission + + Parameters + + shapes: Shape[] + + options: { + type?: "html" | "svg"; + } + - Optionaltype?: "html" | "svg" + + Returns string + + Example + ``` + const markup = context.generateMarkup(shapes, { type: 'html' });console.log(markup); + ``` + generateStyle: |- + ``` + generateStyle(shapes, options?): string + ``` + + * Generates styles for the given shapes. Requires `content:read` permission + + Parameters + + shapes: Shape[] + + options: { + type?: "css"; + withPrelude?: boolean; + includeChildren?: boolean; + } + - Optionaltype?: "css" + - OptionalwithPrelude?: boolean + - OptionalincludeChildren?: boolean + + Returns string + + Example + ``` + const styles = context.generateStyle(shapes, { type: 'css' });console.log(styles); + ``` + generateFontFaces: |- + ``` + generateFontFaces(shapes): Promise + ``` + + * Generates the fontfaces styles necessaries to render the shapes. + Requires `content:read` permission + + Parameters + + shapes: Shape[] + + Returns Promise + + Example + ``` + const fontfaces = context.generateFontFaces(penpot.selection);console.log(fontfaces); + ``` + openViewer: |- + ``` + openViewer(): void + ``` + + * Opens the viewer section. Requires `content:read` permission. + + Returns void + createPage: |- + ``` + createPage(): Page + ``` + + * Creates a new page. Requires `content:write` permission. + + Returns Page + openPage: |- + ``` + openPage(page, newWindow?): void + ``` + + * Changes the current open page to given page. Requires `content:read` permission. + + Parameters + + page: Page + + the page to open + + newWindow: boolean + + if true opens the page in a new window + + Returns void + + Example + ``` + context.openPage(page); + ``` + alignHorizontal: |- + ``` + alignHorizontal(shapes, direction): void + ``` + + * Aligning will move all the selected layers to a position relative to one + of them in the horizontal direction. + + Parameters + + shapes: Shape[] + + to align + + direction: "center" | "left" | "right" + + where the shapes will be aligned + + Returns void + alignVertical: |- + ``` + alignVertical(shapes, direction): void + ``` + + * Aligning will move all the selected layers to a position relative to one + of them in the vertical direction. + + Parameters + + shapes: Shape[] + + to align + + direction: "center" | "top" | "bottom" + + where the shapes will be aligned + + Returns void + distributeHorizontal: |- + ``` + distributeHorizontal(shapes): void + ``` + + * Distributing objects to position them horizontally with equal distances between them. + + Parameters + + shapes: Shape[] + + to distribute + + Returns void + distributeVertical: |- + ``` + distributeVertical(shapes): void + ``` + + * Distributing objects to position them vertically with equal distances between them. + + Parameters + + shapes: Shape[] + + to distribute + + Returns void + flatten: |- + ``` + flatten(shapes): Path[] + ``` + + * Converts the shapes into Paths. If the shapes are complex will put together + all its paths into one. + + Parameters + + shapes: Shape[] + + to flatten + + Returns Path[] +ActiveUser: + overview: |- + Interface ActiveUser + ==================== + + Represents an active user in Penpot, extending the `User` interface. + This interface includes additional properties specific to active users. + + ``` + interface ActiveUser { + position?: { + x: number; + y: number; + }; + zoom?: number; + id: string; + name?: string; + avatarUrl?: string; + color: string; + sessionId?: string; + } + ``` + + Hierarchy (view full) + + * User + + ActiveUser + + Referenced by: Context, Penpot + members: + Properties: + position: |- + ``` + position?: { + x: number; + y: number; + } + ``` + + The position of the active user. + + Example + ``` + const userPosition = activeUser.position;console.log(userPosition); + ``` + zoom: |- + ``` + readonly zoom?: number + ``` + + The zoom level of the active user. + + Example + ``` + const userZoom = activeUser.zoom;console.log(userZoom); + ``` + id: |- + ``` + readonly id: string + ``` + + The unique identifier of the user. + + Example + ``` + const userId = user.id;console.log(userId); + ``` + name: |- + ``` + readonly name?: string + ``` + + The name of the user. + + Example + ``` + const userName = user.name;console.log(userName); + ``` + avatarUrl: |- + ``` + readonly avatarUrl?: string + ``` + + The URL of the user's avatar image. + + Example + ``` + const avatarUrl = user.avatarUrl;console.log(avatarUrl); + ``` + color: |- + ``` + readonly color: string + ``` + + The color associated with the user. + + Example + ``` + const userColor = user.color;console.log(userColor); + ``` + sessionId: |- + ``` + readonly sessionId?: string + ``` + + The session ID of the user. + + Example + ``` + const sessionId = user.sessionId;console.log(sessionId); + ``` +Blur: + overview: |- + Interface Blur + ============== + + Represents blur properties in Penpot. + This interface includes properties for defining the type and intensity of a blur effect, along with its visibility. + + ``` + interface Blur { + id?: string; + type?: "layer-blur"; + value?: number; + hidden?: boolean; + } + ``` + + Referenced by: Board, Boolean, Ellipse, Group, Image, Path, Rectangle, ShapeBase, SvgRaw, Text, VariantContainer + members: + Properties: + id: |- + ``` + id?: string + ``` + + The optional unique identifier for the blur effect. + type: |- + ``` + type + ``` + + The optional type of the blur effect. + Currently, only 'layer-blur' is supported. + value: |- + ``` + value?: number + ``` + + The optional intensity value of the blur effect. + hidden: |- + ``` + hidden?: boolean + ``` + + Specifies whether the blur effect is hidden. + Defaults to false if omitted. +Board: + overview: |- + Interface Board + =============== + + Represents a board in Penpot. + This interface extends `ShapeBase` and includes properties and methods specific to board. + + ``` + interface Board { + 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; + 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) + + * ShapeBase + + Board + - VariantContainer + + Referenced by: CloseOverlay, CommentThread, Context, ContextTypesUtils, Flow, NavigateTo, OpenOverlay, OverlayAction, Page, Penpot, RulerGuide, Shape, ToggleOverlay + members: + Properties: + type: |- + ``` + readonly 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: |- + ``` + readonly grid?: GridLayout + ``` + + The grid layout configuration of the board, if applicable. + flex: |- + ``` + readonly flex?: FlexLayout + ``` + + The flex layout configuration of the board, if applicable. + guides: |- + ``` + guides: Guide[] + ``` + + The guides associated with the board. + rulerGuides: |- + ``` + readonly 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. + + Overrides ShapeBase.fills + 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(); + ``` + id: |- + ``` + readonly id: string + ``` + + The unique identifier of the shape. + name: |- + ``` + name: string + ``` + + The name of the shape. + parent: |- + ``` + readonly 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: |- + ``` + readonly 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: |- + ``` + readonly width: number + ``` + + The width of the shape. + height: |- + ``` + readonly height: number + ``` + + The height of the shape. + bounds: |- + ``` + readonly bounds: Bounds + ``` + + Returns + + Returns the bounding box surrounding the current shape + center: |- + ``` + readonly 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: |- + ``` + readonly layoutChild?: LayoutChildProperties + ``` + + Layout properties for children of the shape. + layoutCell: |- + ``` + readonly layoutCell?: LayoutChildProperties + ``` + + Layout properties for cells in a grid layout. + interactions: |- + ``` + readonly 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 + ``` + + * 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: |- + ``` + readonly 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: |- + ``` + readonly grid?: GridLayout + ``` + + The grid layout configuration of the board, if applicable. + flex: |- + ``` + readonly flex?: FlexLayout + ``` + + The flex layout configuration of the board, if applicable. + guides: |- + ``` + guides: Guide[] + ``` + + The guides associated with the board. + rulerGuides: |- + ``` + readonly 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: |- + ``` + readonly variants: null | Variants + ``` + + Access to the Variant interface, for attributes and actions over the full Variant (not only this VariantContainer) + id: |- + ``` + readonly id: string + ``` + + The unique identifier of the shape. + name: |- + ``` + name: string + ``` + + The name of the shape. + parent: |- + ``` + readonly 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: |- + ``` + readonly 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: |- + ``` + readonly width: number + ``` + + The width of the shape. + height: |- + ``` + readonly height: number + ``` + + The height of the shape. + bounds: |- + ``` + readonly bounds: Bounds + ``` + + Returns + + Returns the bounding box surrounding the current shape + center: |- + ``` + readonly 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: |- + ``` + readonly layoutChild?: LayoutChildProperties + ``` + + Layout properties for children of the shape. + layoutCell: |- + ``` + readonly layoutCell?: LayoutChildProperties + ``` + + Layout properties for cells in a grid layout. + interactions: |- + ``` + readonly 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 + ``` + + * 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 +Boolean: + overview: |- + Interface Boolean + ================= + + Represents a boolean operation shape in Penpot. + This interface extends `ShapeBase` and includes properties and methods specific to boolean operations. + + ``` + interface Boolean { + type: "boolean"; + toD(): string; + content: string; + d: string; + commands: PathCommand[]; + fills: Fill[]; + children: Shape[]; + appendChild(child: Shape): void; + insertChild(index: number, child: Shape): void; + 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) + + * ShapeBase + + Boolean + + Referenced by: Context, ContextTypesUtils, Penpot, Shape + members: + Properties: + type: |- + ``` + readonly type + ``` + + The type of the shape, which is always 'bool' for boolean operation shapes. + content: |- + ``` + 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. + fills: |- + ``` + fills: Fill[] + ``` + + The fills applied to the shape. + + Overrides ShapeBase.fills + children: |- + ``` + readonly children: Shape[] + ``` + + The children shapes contained within the boolean shape. + id: |- + ``` + readonly id: string + ``` + + The unique identifier of the shape. + name: |- + ``` + name: string + ``` + + The name of the shape. + parent: |- + ``` + readonly 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: |- + ``` + readonly 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: |- + ``` + readonly width: number + ``` + + The width of the shape. + height: |- + ``` + readonly height: number + ``` + + The height of the shape. + bounds: |- + ``` + readonly bounds: Bounds + ``` + + Returns + + Returns the bounding box surrounding the current shape + center: |- + ``` + readonly 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: |- + ``` + readonly layoutChild?: LayoutChildProperties + ``` + + Layout properties for children of the shape. + layoutCell: |- + ``` + readonly layoutCell?: LayoutChildProperties + ``` + + Layout properties for cells in a grid layout. + interactions: |- + ``` + readonly interactions: Interaction[] + ``` + + The interactions for the current shape. + Methods: + toD: |- + ``` + toD(): string + ``` + + * Converts the boolean shape to its path data representation. + + Returns string + + Returns the path data (d attribute) as a string. + + Deprecated + + Use the `d` attribute + appendChild: |- + ``` + appendChild(child): void + ``` + + * Appends a child shape to the boolean shape. + + Parameters + + child: Shape + + The child shape to append. + + Returns void + + Example + ``` + boolShape.appendChild(childShape); + ``` + insertChild: |- + ``` + insertChild(index, child): void + ``` + + * Inserts a child shape at the specified index within the boolean shape. + + Parameters + + index: number + + The index at which to insert the child shape. + + child: Shape + + The child shape to insert. + + Returns void + + Example + ``` + boolShape.insertChild(0, childShape); + ``` + 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 + ``` + + * 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 +CloseOverlay: + overview: |- + Interface CloseOverlay + ====================== + + This action will close a targeted board that is opened as an overlay. + + ``` + interface CloseOverlay { + type: "close-overlay"; + destination?: Board; + animation: Animation; + } + ``` + + Referenced by: Action + members: + Properties: + type: |- + ``` + readonly type + ``` + + The action type + destination: |- + ``` + readonly destination?: Board + ``` + + The overlay to be closed with this action. + animation: |- + ``` + readonly animation: Animation + ``` + + Animation displayed with this interaction. +Color: + overview: |- + Interface Color + =============== + + Represents color properties in Penpot. + This interface includes properties for defining solid colors, gradients, and image fills, along with metadata. + + ``` + interface Color { + id?: string; + fileId?: string; + name?: string; + path?: string; + color?: string; + opacity?: number; + refId?: string; + refFile?: string; + gradient?: Gradient; + image?: ImageData; + } + ``` + + Referenced by: Context, Penpot, Shadow + members: + Properties: + id: |- + ``` + id?: string + ``` + + The optional reference ID for an external color definition. + fileId: |- + ``` + fileId?: string + ``` + + The optional reference to an external file for the color definition. + name: |- + ``` + name?: string + ``` + + The optional name of the color. + path: |- + ``` + path?: string + ``` + + The optional path or category to which this color belongs. + color: |- + ``` + color?: string + ``` + + The optional solid color, represented as a string (e.g., '#FF5733'). + opacity: |- + ``` + opacity?: number + ``` + + The optional opacity level of the color, ranging from 0 (fully transparent) to 1 (fully opaque). + Defaults to 1 if omitted. + refId: |- + ``` + refId?: string + ``` + + The optional reference ID for an external color definition. + + Deprecated + + Use `id` instead + refFile: |- + ``` + refFile?: string + ``` + + The optional reference to an external file for the color definition. + + Deprecated + + Use `fileId` + gradient: |- + ``` + gradient?: Gradient + ``` + + The optional gradient fill defined by a Gradient object. + image: |- + ``` + image?: ImageData + ``` + + The optional image fill defined by an ImageData object. +ColorShapeInfo: + overview: |- + Interface ColorShapeInfo + ======================== + + Additional color information for the methods to extract colors from a list of shapes. + + ``` + interface ColorShapeInfo { + shapesInfo: ColorShapeInfoEntry[]; + } + ``` + + Referenced by: Context, Penpot + members: + Properties: + shapesInfo: |- + ``` + readonly shapesInfo: ColorShapeInfoEntry[] + ``` + + List of shapes with additional information +ColorShapeInfoEntry: + overview: |- + Interface ColorShapeInfoEntry + ============================= + + Entry for the color shape additional information. + + ``` + interface ColorShapeInfoEntry { + property: string; + index?: number; + shapeId: string; + } + ``` + + Referenced by: ColorShapeInfo + members: + Properties: + property: |- + ``` + readonly property: string + ``` + + Property that has the color (example: fill, stroke...) + index: |- + ``` + readonly index?: number + ``` + + For properties that are indexes (such as fill) represent the index + of the color inside that property. + shapeId: |- + ``` + readonly shapeId: string + ``` + + Identifier of the shape that contains the color +Comment: + overview: |- + Interface Comment + ================= + + Comments allow the team to have one priceless conversation getting and + providing feedback right over the designs and prototypes. + + ``` + interface Comment { + user: User; + date: Date; + content: string; + remove(): void; + } + ``` + + Referenced by: CommentThread + members: + Properties: + user: |- + ``` + readonly user: User + ``` + + The `user` that has created the comment. + date: |- + ``` + readonly date: Date + ``` + + The `date` the comment has been created. + content: |- + ``` + content: string + ``` + + The `content` for the commentary. The owner can modify the comment. + Methods: + remove: |- + ``` + remove(): void + ``` + + * Remove the current comment from its comment thread. Only the owner can remove their comments. + Requires the `comment:write` permission. + + Returns void +CommentThread: + overview: |- + Interface CommentThread + ======================= + + Represents a list of comments one after the other. Usually these threads + are conversations the users have in Penpot. + + ``` + interface CommentThread { + seqNumber: number; + board?: Board; + owner?: User; + position: Point; + resolved: boolean; + findComments(): Promise; + reply(content: string): Promise; + remove(): void; + } + ``` + + Referenced by: Page + members: + Properties: + seqNumber: |- + ``` + readonly seqNumber: number + ``` + + This is the number that is displayed on the workspace. Is an increasing + sequence for each comment. + board: |- + ``` + readonly board?: Board + ``` + + If the thread is attached to a `board` this will have that board + reference. + owner: |- + ``` + readonly owner?: User + ``` + + Owner of the comment thread + position: |- + ``` + position: Point + ``` + + The `position` in absolute coordinates in the canvas. + resolved: |- + ``` + resolved: boolean + ``` + + Whether the thread has been marked as `resolved` or not. + Methods: + findComments: |- + ``` + findComments(): Promise + ``` + + * List of `comments` ordered by creation date. + Requires the `comment:read` or `comment:write` permission. + + Returns Promise + reply: |- + ``` + reply(content): Promise + ``` + + * Creates a new comment after the last one in the thread. The current user will + be used as the creation user. + Requires the `comment:write` permission. + + Parameters + + content: string + + Returns Promise + remove: |- + ``` + remove(): void + ``` + + * Removes the current comment thread. Only the user that created the thread can + remove it. + Requires the `comment:write` permission. + + Returns void +CommonLayout: + overview: |- + Interface CommonLayout + ====================== + + CommonLayout represents a common layout interface in the Penpot application. + It includes various properties for alignment, spacing, padding, and sizing, as well as a method to remove the layout. + + ``` + interface CommonLayout { + alignItems?: + | "center" + | "start" + | "end" + | "stretch"; + alignContent?: + | "center" + | "start" + | "end" + | "stretch" + | "space-between" + | "space-around" + | "space-evenly"; + justifyItems?: + | "center" + | "start" + | "end" + | "stretch"; + justifyContent?: + | "center" + | "start" + | "end" + | "stretch" + | "space-between" + | "space-around" + | "space-evenly"; + rowGap: number; + columnGap: number; + verticalPadding: number; + horizontalPadding: number; + topPadding: number; + rightPadding: number; + bottomPadding: number; + leftPadding: number; + horizontalSizing: "fill" | "auto" | "fit-content"; + verticalSizing: "fill" | "auto" | "fit-content"; + remove(): void; + } + ``` + + Hierarchy (view full) + + * CommonLayout + + FlexLayout + + GridLayout + members: + Properties: + alignItems: |- + ``` + alignItems?: + | "center" + | "start" + | "end" + | "stretch" + ``` + + The `alignItems` property specifies the default alignment for items inside the container. + It can be one of the following values: + + * 'start': Items are aligned at the start. + * 'end': Items are aligned at the end. + * 'center': Items are centered. + * 'stretch': Items are stretched to fill the container. + alignContent: |- + ``` + alignContent?: + | "center" + | "start" + | "end" + | "stretch" + | "space-between" + | "space-around" + | "space-evenly" + ``` + + The `alignContent` property specifies how the content is aligned within the container when there is extra space. + It can be one of the following values: + + * 'start': Content is aligned at the start. + * 'end': Content is aligned at the end. + * 'center': Content is centered. + * 'space-between': Content is distributed with space between. + * 'space-around': Content is distributed with space around. + * 'space-evenly': Content is distributed with even space around. + * 'stretch': Content is stretched to fill the container. + justifyItems: |- + ``` + justifyItems?: + | "center" + | "start" + | "end" + | "stretch" + ``` + + The `justifyItems` property specifies the default justification for items inside the container. + It can be one of the following values: + + * 'start': Items are justified at the start. + * 'end': Items are justified at the end. + * 'center': Items are centered. + * 'stretch': Items are stretched to fill the container. + justifyContent: |- + ``` + justifyContent?: + | "center" + | "start" + | "end" + | "stretch" + | "space-between" + | "space-around" + | "space-evenly" + ``` + + The `justifyContent` property specifies how the content is justified within the container when there is extra space. + It can be one of the following values: + + * 'start': Content is justified at the start. + * 'center': Content is centered. + * 'end': Content is justified at the end. + * 'space-between': Content is distributed with space between. + * 'space-around': Content is distributed with space around. + * 'space-evenly': Content is distributed with even space around. + * 'stretch': Content is stretched to fill the container. + rowGap: |- + ``` + rowGap: number + ``` + + The `rowGap` property specifies the gap between rows in the layout. + columnGap: |- + ``` + columnGap: number + ``` + + The `columnGap` property specifies the gap between columns in the layout. + verticalPadding: |- + ``` + verticalPadding: number + ``` + + The `verticalPadding` property specifies the vertical padding inside the container. + horizontalPadding: |- + ``` + horizontalPadding: number + ``` + + The `horizontalPadding` property specifies the horizontal padding inside the container. + topPadding: |- + ``` + topPadding: number + ``` + + The `topPadding` property specifies the padding at the top of the container. + rightPadding: |- + ``` + rightPadding: number + ``` + + The `rightPadding` property specifies the padding at the right of the container. + bottomPadding: |- + ``` + bottomPadding: number + ``` + + The `bottomPadding` property specifies the padding at the bottom of the container. + leftPadding: |- + ``` + leftPadding: number + ``` + + The `leftPadding` property specifies the padding at the left of the container. + horizontalSizing: |- + ``` + horizontalSizing: "fill" | "auto" | "fit-content" + ``` + + The `horizontalSizing` property specifies the horizontal sizing behavior of the container. + It can be one of the following values: + + * 'fit-content': The container fits the content. + * 'fill': The container fills the available space. + * 'auto': The container size is determined automatically. + verticalSizing: |- + ``` + verticalSizing: "fill" | "auto" | "fit-content" + ``` + + The `verticalSizing` property specifies the vertical sizing behavior of the container. + It can be one of the following values: + + * 'fit-content': The container fits the content. + * 'fill': The container fills the available space. + * 'auto': The container size is determined automatically. + Methods: + remove: |- + ``` + remove(): void + ``` + + * The `remove` method removes the layout. + + Returns void +Context: + overview: |- + Interface Context + ================= + + Represents the context of Penpot, providing access to various Penpot functionalities and data. + + ``` + interface Context { + root: null | Shape; + currentFile: null | File; + currentPage: null | Page; + viewport: Viewport; + history: HistoryContext; + library: LibraryContext; + fonts: FontsContext; + currentUser: User; + activeUsers: ActiveUser[]; + theme: Theme; + localStorage: LocalStorage; + selection: Shape[]; + shapesColors(shapes: Shape[]): (Color & ColorShapeInfo)[]; + replaceColor(shapes: Shape[], oldColor: Color, newColor: Color): void; + uploadMediaUrl(name: string, url: string): Promise; + uploadMediaData(name: string, data: Uint8Array, mimeType: string): Promise; + group(shapes: Shape[]): null | Group; + ungroup(group: Group, ...other: Group[]): void; + createRectangle(): Rectangle; + createBoard(): Board; + createEllipse(): Ellipse; + createPath(): Path; + createBoolean(boolType: BooleanType, shapes: Shape[]): null | Boolean; + createShapeFromSvg(svgString: string): null | Group; + createShapeFromSvgWithImages(svgString: string): Promise; + createText(text: string): null | Text; + generateMarkup(shapes: Shape[], options?: { + type?: "html" | "svg"; + }): string; + generateStyle(shapes: Shape[], options?: { + type?: "css"; + withPrelude?: boolean; + includeChildren?: boolean; + }): string; + generateFontFaces(shapes: Shape[]): Promise; + addListener(type: T, callback: ((event: EventsMap[T]) => void), props?: { + [key: string]: unknown; + }): symbol; + removeListener(listenerId: symbol): void; + openViewer(): void; + createPage(): Page; + 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; + distributeVertical(shapes: Shape[]): void; + flatten(shapes: Shape[]): Path[]; + } + ``` + members: + Properties: + root: |- + ``` + readonly root: null | Shape + ``` + + The root shape in the current Penpot context. Requires `content:read` permission. + + Example + ``` + const rootShape = context.root;console.log(rootShape); + ``` + currentFile: |- + ``` + readonly currentFile: null | File + ``` + + Retrieves file data from the current Penpot context. Requires `content:read` permission. + + Returns + + Returns the file data or `null` if no file is available. + + Example + ``` + const fileData = context.currentFile;console.log(fileData); + ``` + currentPage: |- + ``` + readonly currentPage: null | Page + ``` + + The current page in the Penpot context. Requires `content:read` permission. + + Example + ``` + const currentPage = context.currentPage;console.log(currentPage); + ``` + viewport: |- + ``` + readonly viewport: Viewport + ``` + + The viewport settings in the Penpot context. + + Example + ``` + const viewportSettings = context.viewport;console.log(viewportSettings); + ``` + history: |- + ``` + readonly history: HistoryContext + ``` + + Context encapsulating the history operations + + Example + ``` + const historyContext = context.history;console.log(historyContext); + ``` + library: |- + ``` + readonly library: LibraryContext + ``` + + The library context in the Penpot context, including both local and connected libraries. Requires `library:read` permission. + + Example + ``` + const libraryContext = context.library;console.log(libraryContext); + ``` + fonts: |- + ``` + readonly fonts: FontsContext + ``` + + The fonts context in the Penpot context, providing methods to manage fonts. Requires `content:read` permission. + + Example + ``` + const fontsContext = context.fonts;console.log(fontsContext); + ``` + currentUser: |- + ``` + readonly currentUser: User + ``` + + The current user in the Penpot context. Requires `user:read` permission. + + Example + ``` + const currentUser = context.currentUser;console.log(currentUser); + ``` + activeUsers: |- + ``` + readonly activeUsers: ActiveUser[] + ``` + + An array of active users in the Penpot context. Requires `user:read` permission. + + Example + ``` + const activeUsers = context.activeUsers;console.log(activeUsers); + ``` + theme: |- + ``` + readonly theme: Theme + ``` + + The current theme (light or dark) in Penpot. + + Example + ``` + const currentTheme = context.theme;console.log(currentTheme); + ``` + localStorage: |- + ``` + readonly localStorage: LocalStorage + ``` + + Access to the localStorage proxy + selection: |- + ``` + selection: Shape[] + ``` + + The currently selected shapes in Penpot. Requires `content:read` permission. + + Example + ``` + const selectedShapes = context.selection;console.log(selectedShapes); + ``` + Methods: + shapesColors: |- + ``` + shapesColors(shapes): (Color & ColorShapeInfo)[] + ``` + + * Retrieves colors applied to the given shapes in Penpot. Requires `content:read` permission. + + Parameters + + shapes: Shape[] + + Returns (Color & ColorShapeInfo)[] + + Returns an array of colors and their shape information. + + Example + ``` + const colors = context.shapesColors(shapes);console.log(colors); + ``` + replaceColor: |- + ``` + replaceColor(shapes, oldColor, newColor): void + ``` + + * Replaces a specified old color with a new color in the given shapes. Requires `content:write` permission. + + Parameters + + shapes: Shape[] + + oldColor: Color + + newColor: Color + + Returns void + + Example + ``` + context.replaceColor(shapes, oldColor, newColor); + ``` + uploadMediaUrl: |- + ``` + uploadMediaUrl(name, url): Promise + ``` + + * Uploads media to Penpot and retrieves its image data. Requires `content:write` permission. + + Parameters + + name: string + + The name of the media. + + url: string + + The URL of the media to be uploaded. + + Returns Promise + + Returns a promise that resolves to the image data of the uploaded media. + + Example + ``` + const imageData = await context.uploadMediaUrl('example', 'https://example.com/image.jpg');console.log(imageData);// to insert the image in a shape we can doconst board = penpot.createBoard();const shape = penpot.createRectangle();board.appendChild(shape);shape.fills = [{ fillOpacity: 1, fillImage: imageData }]; + ``` + uploadMediaData: |- + ``` + uploadMediaData(name, data, mimeType): Promise + ``` + + * Uploads media to penpot and retrieves the image data. Requires `content:write` permission. + + Parameters + + name: string + + The name of the media. + + data: Uint8Array + + The image content data + + mimeType: string + + Returns Promise + + Returns a promise that resolves to the image data of the uploaded media. + + Example + ``` + const imageData = await context.uploadMediaData('example', imageData, 'image/jpeg');console.log(imageData); + ``` + group: |- + ``` + group(shapes): null | Group + ``` + + * Groups the specified shapes. Requires `content:write` permission. + + Parameters + + shapes: Shape[] + + An array of shapes to group. + + Returns null | Group + + Returns the newly created group or `null` if the group could not be created. + + Example + ``` + const penpotShapesArray = penpot.selection;penpot.group(penpotShapesArray); + ``` + ungroup: |- + ``` + ungroup(group, ...other): void + ``` + + * Ungroups the specified group. Requires `content:write` permission. + + Parameters + + group: Group + + The group to ungroup. + + ...other: Group[] + + Additional groups to ungroup. + + Returns void + + Example + ``` + const penpotShapesArray = penpot.selection;// We need to make sure that something is selected, and if the selected shape is a group,if (selected.length && penpot.utils.types.isGroup(penpotShapesArray[0])) { penpot.group(penpotShapesArray[0]);} + ``` + createRectangle: |- + ``` + createRectangle(): Rectangle + ``` + + * Use this method to create the shape of a rectangle. Requires `content:write` permission. + + Returns Rectangle + + Example + ``` + const shape = penpot.createRectangle();// just change the values like thisshape.name = "Example rectangle";// for solid colorshape.fills = [{ fillColor: "#7EFFF5" }];// for linear gradient colorshape.fills = [{ fillColorGradient: { "type": "linear", "startX": 0.5, "startY": 0, "endX": 0.5, "endY": 1, "width": 1, "stops": [ { "color": "#003ae9", "opacity": 1, "offset": 0 }, { "color": "#003ae9", "opacity": 0, "offset": 1 } ] }}];// for a image fillconst imageData = await context.uploadMediaUrl('example', 'https://example.com/image.jpg');shape.fills = [{ fillOpacity: 1, fillImage: imageData }];shape.borderRadius = 8;shape.strokes = [ { strokeColor: "#2e3434", strokeStyle: "solid", strokeWidth: 2, strokeAlignment: "center", },]; + ``` + createBoard: |- + ``` + createBoard(): Board + ``` + + * Use this method to create a board. This is the first step before anything else, the container. Requires `content:write` permission. + Then you can add a gridlayout, flexlayout or add a shape inside the board. + Just a heads-up: board is a board in Penpot UI. + + Returns Board + + Example + ``` + const board = penpot.createBoard();// to add grid layoutboard.addGridLayout();// to add flex layoutboard.addFlexLayout();// to create a shape inside the boardconst shape = penpot.createRectangle();board.appendChild(shape); + ``` + createEllipse: |- + ``` + createEllipse(): Ellipse + ``` + + * Use this method to create the shape of an ellipse. Requires `content:write` permission. + + Returns Ellipse + + Example + ``` + const shape = penpot.createEllipse();// just change the values like thisshape.name = "Example ellipse";// for solid colorshape.fills = [{ fillColor: "#7EFFF5" }];// for linear gradient colorshape.fills = [{ fillColorGradient: { "type": "linear", "startX": 0.5, "startY": 0, "endX": 0.5, "endY": 1, "width": 1, "stops": [ { "color": "#003ae9", "opacity": 1, "offset": 0 }, { "color": "#003ae9", "opacity": 0, "offset": 1 } ] }}];// for an image fillconst imageData = await context.uploadMediaUrl('example', 'https://example.com/image.jpg');shape.fills = [{ fillOpacity: 1, fillImage: imageData }];shape.strokes = [ { strokeColor: "#2e3434", strokeStyle: "solid", strokeWidth: 2, strokeAlignment: "center", },]; + ``` + createPath: |- + ``` + createPath(): Path + ``` + + * Use this method to create a path. Requires `content:write` permission. + + Returns Path + + Example + ``` + const path = penpot.createPath();path.name = "My path";// for solid colorpath.fills = [{ fillColor: "#7EFFF5" }]; + ``` + createBoolean: |- + ``` + createBoolean(boolType, shapes): null | Boolean + ``` + + * Creates a Boolean shape based on the specified boolean operation and shapes. Requires `content:write` permission. + + Parameters + + boolType: BooleanType + + The type of boolean operation ('union', 'difference', 'exclude', 'intersection'). + + shapes: Shape[] + + An array of shapes to perform the boolean operation on. + + Returns null | Boolean + + Returns the newly created Boolean shape resulting from the boolean operation. + + Example + ``` + const booleanShape = context.createBoolean('union', [shape1, shape2]); + ``` + createShapeFromSvg: |- + ``` + createShapeFromSvg(svgString): null | Group + ``` + + * Creates a Group from an SVG string. Requires `content:write` permission. + + Parameters + + svgString: string + + The SVG string representing the shapes to be converted into a group. + + Returns null | Group + + Returns the newly created Group containing the shapes from the SVG. + + Example + ``` + const svgGroup = context.createShapeFromSvg('...'); + ``` + createShapeFromSvgWithImages: |- + ``` + createShapeFromSvgWithImages(svgString): Promise + ``` + + * Creates a Group from an SVG string. The SVG can have images and the method returns + a Promise because the shape will be available after all images are uploaded. + Requires `content:write` permission. + + Parameters + + svgString: string + + The SVG string representing the shapes to be converted into a group. + + Returns Promise + + Returns a promise with the newly created Group containing the shapes from the SVG. + + Example + ``` + const svgGroup = await context.createShapeFromSvgWithImages('...'); + ``` + createText: |- + ``` + createText(text): null | Text + ``` + + * Creates a Text shape with the specified text content. Requires `content:write` permission. + + Parameters + + text: string + + The text content for the Text shape. + + Returns null | Text + + Returns the new created shape, if the shape wasn't created can return null. + + Example + ``` + const board = penpot.createBoard();let text;text = penpot.createText();// just change the values like thistext.growType = 'auto-height';text.fontFamily = 'Work Sans';text.fontSize = '12';text.fills = [{fillColor: '#9f05ff', fillOpacity: 1}];text.strokes = [{strokeOpacity: 1, strokeStyle: 'solid', strokeWidth: 2, strokeColor: '#deabff', strokeAlignment: 'outer'}];board.appendChild(text); + ``` + generateMarkup: |- + ``` + generateMarkup(shapes, options?): string + ``` + + * Generates markup for the given shapes. Requires `content:read` permission + + Parameters + + shapes: Shape[] + + options: { + type?: "html" | "svg"; + } + - Optionaltype?: "html" | "svg" + + Returns string + + Example + ``` + const markup = context.generateMarkup(shapes, { type: 'html' });console.log(markup); + ``` + generateStyle: |- + ``` + generateStyle(shapes, options?): string + ``` + + * Generates styles for the given shapes. Requires `content:read` permission + + Parameters + + shapes: Shape[] + + options: { + type?: "css"; + withPrelude?: boolean; + includeChildren?: boolean; + } + - Optionaltype?: "css" + - OptionalwithPrelude?: boolean + - OptionalincludeChildren?: boolean + + Returns string + + Example + ``` + const styles = context.generateStyle(shapes, { type: 'css' });console.log(styles); + ``` + generateFontFaces: |- + ``` + generateFontFaces(shapes): Promise + ``` + + * Generates the fontfaces styles necessaries to render the shapes. + Requires `content:read` permission + + Parameters + + shapes: Shape[] + + Returns Promise + + Example + ``` + const fontfaces = context.generateFontFaces(penpot.selection);console.log(fontfaces); + ``` + addListener: |- + ``` + addListener(type, callback, props?): symbol + ``` + + * Adds the current callback as an event listener + + Type Parameters + + T extends keyof EventsMap + + Parameters + + type: T + + callback: ((event: EventsMap[T]) => void) + - ``` + (event): void + ``` + + * Parameters + + event: EventsMap[T] + + Returns void + + props: { + [key: string]: unknown; + } + - [key: string]: unknown + + Returns symbol + + Example + ``` + const listenerId = context.addListener('selectionchange', (event) => { console.log(event);}); + ``` + removeListener: |- + ``` + removeListener(listenerId): void + ``` + + * Removes the listenerId from the list of listeners + + Parameters + + listenerId: symbol + + Returns void + + Example + ``` + context.removeListener(listenerId); + ``` + openViewer: |- + ``` + openViewer(): void + ``` + + * Opens the viewer section. Requires `content:read` permission. + + Returns void + createPage: |- + ``` + createPage(): Page + ``` + + * Creates a new page. Requires `content:write` permission. + + Returns Page + openPage: |- + ``` + openPage(page, newWindow?): void + ``` + + * Changes the current open page to given page. Requires `content:read` permission. + + Parameters + + page: Page + + the page to open + + newWindow: boolean + + if true opens the page in a new window + + Returns void + + Example + ``` + context.openPage(page); + ``` + alignHorizontal: |- + ``` + alignHorizontal(shapes, direction): void + ``` + + * Aligning will move all the selected layers to a position relative to one + of them in the horizontal direction. + + Parameters + + shapes: Shape[] + + to align + + direction: "center" | "left" | "right" + + where the shapes will be aligned + + Returns void + alignVertical: |- + ``` + alignVertical(shapes, direction): void + ``` + + * Aligning will move all the selected layers to a position relative to one + of them in the vertical direction. + + Parameters + + shapes: Shape[] + + to align + + direction: "center" | "top" | "bottom" + + where the shapes will be aligned + + Returns void + distributeHorizontal: |- + ``` + distributeHorizontal(shapes): void + ``` + + * Distributing objects to position them horizontally with equal distances between them. + + Parameters + + shapes: Shape[] + + to distribute + + Returns void + distributeVertical: |- + ``` + distributeVertical(shapes): void + ``` + + * Distributing objects to position them vertically with equal distances between them. + + Parameters + + shapes: Shape[] + + to distribute + + Returns void + flatten: |- + ``` + flatten(shapes): Path[] + ``` + + * Converts the shapes into Paths. If the shapes are complex will put together + all its paths into one. + + Parameters + + shapes: Shape[] + + to flatten + + Returns Path[] +ContextGeometryUtils: + overview: |- + Interface ContextGeometryUtils + ============================== + + Utility methods for geometric calculations in Penpot. + + Example + ``` + const centerPoint = geometryUtils.center(shapes);console.log(centerPoint); + ``` + + ``` + interface ContextGeometryUtils { + center(shapes: Shape[]): null | { + x: number; + y: number; + }; + } + ``` + + Referenced by: ContextUtils + members: + Methods: + center: |- + ``` + center(shapes): null | { + x: number; + y: number; + } + ``` + + * Calculates the center point of a given array of shapes. + This method computes the geometric center (centroid) of the bounding boxes of the provided shapes. + + Parameters + + shapes: Shape[] + + The array of shapes to calculate the center for. + + Returns null | { x: number; y: number; } + + Returns the center point as an object with `x` and `y` coordinates, or null if the array is empty. + + Example + ``` + const centerPoint = geometryUtils.center(shapes);console.log(centerPoint); + ``` +ContextTypesUtils: + overview: |- + Interface ContextTypesUtils + =========================== + + Utility methods for determining the types of Penpot shapes. + + Example + ``` + const isBoard = typesUtils.isBoard(shape);console.log(isBoard); + ``` + + ``` + interface ContextTypesUtils { + isBoard(shape: Shape): shape is Board; + isGroup(shape: Shape): shape is Group; + isMask(shape: Shape): shape is Group; + isBool(shape: Shape): shape is Boolean; + isRectangle(shape: Shape): shape is Rectangle; + isPath(shape: Shape): shape is Path; + 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; + } + ``` + + Referenced by: ContextUtils + members: + Methods: + isBoard: |- + ``` + isBoard(shape): shape is Board + ``` + + * Checks if the given shape is a board. + + Parameters + + shape: Shape + + The shape to check. + + Returns shape is Board + + Returns true if the shape is a board, otherwise false. + isGroup: |- + ``` + isGroup(shape): shape is Group + ``` + + * Checks if the given shape is a group. + + Parameters + + shape: Shape + + The shape to check. + + Returns shape is Group + + Returns true if the shape is a Group, otherwise false. + isMask: |- + ``` + isMask(shape): shape is Group + ``` + + * Checks if the given shape is a mask. + + Parameters + + shape: Shape + + The shape to check. + + Returns shape is Group + + Returns true if the shape is a Group (acting as a mask), otherwise false. + isBool: |- + ``` + isBool(shape): shape is Boolean + ``` + + * Checks if the given shape is a boolean operation. + + Parameters + + shape: Shape + + The shape to check. + + Returns shape is Boolean + + Returns true if the shape is a Bool, otherwise false. + isRectangle: |- + ``` + isRectangle(shape): shape is Rectangle + ``` + + * Checks if the given shape is a rectangle. + + Parameters + + shape: Shape + + The shape to check. + + Returns shape is Rectangle + + Returns true if the shape is a Rectangle, otherwise false. + isPath: |- + ``` + isPath(shape): shape is Path + ``` + + * Checks if the given shape is a path. + + Parameters + + shape: Shape + + The shape to check. + + Returns shape is Path + + Returns true if the shape is a Path, otherwise false. + isText: |- + ``` + isText(shape): shape is Text + ``` + + * Checks if the given shape is a text element. + + Parameters + + shape: Shape + + The shape to check. + + Returns shape is Text + + Returns true if the shape is a Text, otherwise false. + isEllipse: |- + ``` + isEllipse(shape): shape is Ellipse + ``` + + * Checks if the given shape is an ellipse. + + Parameters + + shape: Shape + + The shape to check. + + Returns shape is Ellipse + + Returns true if the shape is an Ellipse, otherwise false. + isSVG: |- + ``` + isSVG(shape): shape is SvgRaw + ``` + + * Checks if the given shape is an SVG. + + Parameters + + shape: Shape + + The shape to check. + + 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 + ====================== + + Utility methods for various operations in Penpot. + + ``` + interface ContextUtils { + geometry: ContextGeometryUtils; + types: ContextTypesUtils; + } + ``` + + Referenced by: Penpot + members: + Properties: + geometry: |- + ``` + readonly geometry: ContextGeometryUtils + ``` + + Geometry utility methods for Penpot. + Provides methods for geometric calculations, such as finding the center of a group of shapes. + + Example + ``` + const centerPoint = penpot.utils.geometry.center(shapes);console.log(centerPoint); + ``` + types: |- + ``` + readonly types: ContextTypesUtils + ``` + + Type utility methods for Penpot. + Provides methods for determining the types of various shapes in Penpot. + + Example + ``` + const isBoard = utils.types.isBoard(shape);console.log(isBoard); + ``` +Dissolve: + overview: |- + Interface Dissolve + ================== + + Dissolve animation + + ``` + interface Dissolve { + type: "dissolve"; + duration: number; + easing?: + | "linear" + | "ease" + | "ease-in" + | "ease-out" + | "ease-in-out"; + } + ``` + + Referenced by: Animation + members: + Properties: + type: |- + ``` + readonly type + ``` + + Type of the animation + duration: |- + ``` + readonly duration: number + ``` + + Duration of the animation effect + easing: |- + ``` + readonly easing?: + | "linear" + | "ease" + | "ease-in" + | "ease-out" + | "ease-in-out" + ``` + + Function that the dissolve effect will follow for the interpolation. + Defaults to `linear`. +Ellipse: + overview: |- + Interface Ellipse + ================= + + Represents an ellipse shape in Penpot. + This interface extends `ShapeBase` and includes properties specific to ellipses. + + ``` + interface Ellipse { + type: "ellipse"; + fills: Fill[]; + 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) + + * ShapeBase + + Ellipse + + Referenced by: Context, ContextTypesUtils, Penpot, Shape + members: + Properties: + type: |- + ``` + type + ``` + fills: |- + ``` + fills: Fill[] + ``` + + The fills applied to the shape. + + Overrides ShapeBase.fills + id: |- + ``` + readonly id: string + ``` + + The unique identifier of the shape. + name: |- + ``` + name: string + ``` + + The name of the shape. + parent: |- + ``` + readonly 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: |- + ``` + readonly 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: |- + ``` + readonly width: number + ``` + + The width of the shape. + height: |- + ``` + readonly height: number + ``` + + The height of the shape. + bounds: |- + ``` + readonly bounds: Bounds + ``` + + Returns + + Returns the bounding box surrounding the current shape + center: |- + ``` + readonly 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: |- + ``` + readonly layoutChild?: LayoutChildProperties + ``` + + Layout properties for children of the shape. + layoutCell: |- + ``` + readonly layoutCell?: LayoutChildProperties + ``` + + Layout properties for cells in a grid layout. + interactions: |- + ``` + readonly interactions: Interaction[] + ``` + + The interactions for the current shape. + Methods: + 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 + ``` + + * 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 +EventsMap: + overview: |- + Interface EventsMap + =================== + + Represents a mapping of events to their corresponding types in Penpot. + This interface provides information about various events that can be triggered in the application. + + Example + ``` + penpot.on('pagechange', (event) => { console.log(event);}); + ``` + + ``` + interface EventsMap { + pagechange: Page; + filechange: File; + selectionchange: string[]; + themechange: Theme; + finish: string; + shapechange: Shape; + contentsave: void; + } + ``` + + Referenced by: Context, Penpot + members: + Properties: + pagechange: |- + ``` + pagechange: Page + ``` + + The `pagechange` event is triggered when the active page in the project is changed. + filechange: |- + ``` + filechange: File + ``` + + The `filechange` event is triggered when there are changes in the current file. + selectionchange: |- + ``` + selectionchange: string[] + ``` + + The `selectionchange` event is triggered when the selection of elements changes. + This event passes a list of identifiers of the selected elements. + themechange: |- + ``` + themechange: Theme + ``` + + The `themechange` event is triggered when the application theme is changed. + finish: |- + ``` + finish: string + ``` + + The `finish` event is triggered when some operation is finished. + shapechange: |- + ``` + shapechange: Shape + ``` + + This event will trigger whenever the shape in the props change. It's mandatory to send + with the props an object like `{ shapeId: '' }` + contentsave: |- + ``` + contentsave: void + ``` + + The `contentsave` event will trigger when the content file changes. +Export: + overview: |- + Interface Export + ================ + + Represents export settings in Penpot. + This interface includes properties for defining export configurations. + + ``` + interface Export { + type: + | "svg" + | "png" + | "jpeg" + | "pdf"; + scale?: number; + suffix?: string; + skipChildren?: boolean; + } + ``` + + Referenced by: Board, Boolean, Ellipse, Group, Image, Path, Rectangle, ShapeBase, SvgRaw, Text, VariantContainer + members: + Properties: + type: |- + ``` + type: + | "svg" + | "png" + | "jpeg" + | "pdf" + ``` + + Type of the file to export. Can be one of the following values: png, jpeg, svg, pdf + scale: |- + ``` + scale?: number + ``` + + For bitmap formats represent the scale of the original size to resize the export + suffix: |- + ``` + suffix?: string + ``` + + Suffix that will be appended to the resulting exported file + skipChildren: |- + ``` + skipChildren?: boolean + ``` + + If true will ignore the children when exporting the shape +File: + overview: |- + Interface File + ============== + + File represents a file in the Penpot application. + It includes properties for the file's identifier, name, and revision number. + + ``` + interface File { + id: string; + name: string; + revn: number; + pages: Page[]; + export(exportType: "penpot" | "zip", libraryExportType?: "all" | "merge" | "detach"): Promise; + findVersions(criteria?: { + createdBy: User; + }): Promise; + saveVersion(label: string): Promise; + 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) + + * PluginData + + File + + Referenced by: Context, EventsMap, Penpot + members: + Properties: + id: |- + ``` + readonly id: string + ``` + + The `id` property is a unique identifier for the file. + name: |- + ``` + name: string + ``` + + The `name` for the file + revn: |- + ``` + revn: number + ``` + + The `revn` will change for every document update + pages: |- + ``` + pages: Page[] + ``` + + List all the pages for the current file + Methods: + export: |- + ``` + export(exportType, libraryExportType?): Promise + ``` + + * Parameters + + exportType: "penpot" | "zip" + + libraryExportType: "all" | "merge" | "detach" + + Returns Promise + findVersions: |- + ``` + findVersions(criteria?): Promise + ``` + + * Retrieves the versions for the file. + + Parameters + + criteria: { + createdBy: User; + } + - createdBy: User + + Returns Promise + saveVersion: |- + ``` + saveVersion(label): Promise + ``` + + * Saves the current version into the versions history. + Requires the `content:write` permission. + + Parameters + + label: string + + Returns Promise + 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); + ``` +FileVersion: + overview: |- + Interface FileVersion + ===================== + + Type defining the file version properties. + + ``` + interface FileVersion { + label: string; + createdBy?: User; + createdAt: Date; + isAutosave: boolean; + restore(): void; + remove(): Promise; + pin(): Promise; + } + ``` + + Referenced by: File, FileVersion + members: + Properties: + label: |- + ``` + label: string + ``` + + The current label to identify the version. + createdBy: |- + ``` + readonly createdBy?: User + ``` + + The user that created the version. If not present, the + version is an autosave. + createdAt: |- + ``` + readonly createdAt: Date + ``` + + The date when the version was created. + isAutosave: |- + ``` + readonly isAutosave: boolean + ``` + + If the current version has been generated automatically. + Methods: + restore: |- + ``` + restore(): void + ``` + + * Returns void + remove: |- + ``` + remove(): Promise + ``` + + * Remove the current version. + Requires the `content:write` permission. + + Returns Promise + pin: |- + ``` + pin(): Promise + ``` + + * Converts an autosave version into a permanent version. + Requires the `content:write` permission. + + Returns Promise +Fill: + overview: |- + Interface Fill + ============== + + Represents fill properties in Penpot. You can add a fill to any shape except for groups. + This interface includes properties for defining solid color fills, gradient fills, and image fills. + + ``` + interface Fill { + fillColor?: string; + fillOpacity?: number; + fillColorGradient?: Gradient; + fillColorRefFile?: string; + fillColorRefId?: string; + fillImage?: ImageData; + } + ``` + + Referenced by: Board, Boolean, Ellipse, Group, Image, LibraryColor, Path, Rectangle, ShapeBase, SvgRaw, Text, TextRange, VariantContainer + members: + Properties: + fillColor: |- + ``` + fillColor?: string + ``` + + The optional solid fill color, represented as a string (e.g., '#FF5733'). + fillOpacity: |- + ``` + fillOpacity?: number + ``` + + The optional opacity level of the solid fill color, ranging from 0 (fully transparent) to 1 (fully opaque). + Defaults to 1 if omitted. + fillColorGradient: |- + ``` + fillColorGradient?: Gradient + ``` + + The optional gradient fill defined by a Gradient object. + fillColorRefFile: |- + ``` + fillColorRefFile?: string + ``` + + The optional reference to an external file for the fill color. + fillColorRefId: |- + ``` + fillColorRefId?: string + ``` + + The optional reference ID within the external file for the fill color. + fillImage: |- + ``` + fillImage?: ImageData + ``` + + The optional image fill defined by an ImageData object. +FlexLayout: + overview: |- + Interface FlexLayout + ==================== + + Represents a flexible layout configuration in Penpot. + This interface extends `CommonLayout` and includes properties for defining the direction, + wrapping behavior, and child management of a flex layout. + + ``` + interface FlexLayout { + alignItems?: + | "center" + | "start" + | "end" + | "stretch"; + alignContent?: + | "center" + | "start" + | "end" + | "stretch" + | "space-between" + | "space-around" + | "space-evenly"; + justifyItems?: + | "center" + | "start" + | "end" + | "stretch"; + justifyContent?: + | "center" + | "start" + | "end" + | "stretch" + | "space-between" + | "space-around" + | "space-evenly"; + rowGap: number; + columnGap: number; + verticalPadding: number; + horizontalPadding: number; + topPadding: number; + rightPadding: number; + bottomPadding: number; + leftPadding: number; + horizontalSizing: "fill" | "auto" | "fit-content"; + verticalSizing: "fill" | "auto" | "fit-content"; + remove(): void; + dir: + | "row" + | "row-reverse" + | "column" + | "column-reverse"; + wrap?: "wrap" | "nowrap"; + appendChild(child: Shape): void; + } + ``` + + Hierarchy (view full) + + * CommonLayout + + FlexLayout + + Referenced by: Board, VariantContainer + members: + Properties: + alignItems: |- + ``` + alignItems?: + | "center" + | "start" + | "end" + | "stretch" + ``` + + The `alignItems` property specifies the default alignment for items inside the container. + It can be one of the following values: + + * 'start': Items are aligned at the start. + * 'end': Items are aligned at the end. + * 'center': Items are centered. + * 'stretch': Items are stretched to fill the container. + alignContent: |- + ``` + alignContent?: + | "center" + | "start" + | "end" + | "stretch" + | "space-between" + | "space-around" + | "space-evenly" + ``` + + The `alignContent` property specifies how the content is aligned within the container when there is extra space. + It can be one of the following values: + + * 'start': Content is aligned at the start. + * 'end': Content is aligned at the end. + * 'center': Content is centered. + * 'space-between': Content is distributed with space between. + * 'space-around': Content is distributed with space around. + * 'space-evenly': Content is distributed with even space around. + * 'stretch': Content is stretched to fill the container. + justifyItems: |- + ``` + justifyItems?: + | "center" + | "start" + | "end" + | "stretch" + ``` + + The `justifyItems` property specifies the default justification for items inside the container. + It can be one of the following values: + + * 'start': Items are justified at the start. + * 'end': Items are justified at the end. + * 'center': Items are centered. + * 'stretch': Items are stretched to fill the container. + justifyContent: |- + ``` + justifyContent?: + | "center" + | "start" + | "end" + | "stretch" + | "space-between" + | "space-around" + | "space-evenly" + ``` + + The `justifyContent` property specifies how the content is justified within the container when there is extra space. + It can be one of the following values: + + * 'start': Content is justified at the start. + * 'center': Content is centered. + * 'end': Content is justified at the end. + * 'space-between': Content is distributed with space between. + * 'space-around': Content is distributed with space around. + * 'space-evenly': Content is distributed with even space around. + * 'stretch': Content is stretched to fill the container. + rowGap: |- + ``` + rowGap: number + ``` + + The `rowGap` property specifies the gap between rows in the layout. + columnGap: |- + ``` + columnGap: number + ``` + + The `columnGap` property specifies the gap between columns in the layout. + verticalPadding: |- + ``` + verticalPadding: number + ``` + + The `verticalPadding` property specifies the vertical padding inside the container. + horizontalPadding: |- + ``` + horizontalPadding: number + ``` + + The `horizontalPadding` property specifies the horizontal padding inside the container. + topPadding: |- + ``` + topPadding: number + ``` + + The `topPadding` property specifies the padding at the top of the container. + rightPadding: |- + ``` + rightPadding: number + ``` + + The `rightPadding` property specifies the padding at the right of the container. + bottomPadding: |- + ``` + bottomPadding: number + ``` + + The `bottomPadding` property specifies the padding at the bottom of the container. + leftPadding: |- + ``` + leftPadding: number + ``` + + The `leftPadding` property specifies the padding at the left of the container. + horizontalSizing: |- + ``` + horizontalSizing: "fill" | "auto" | "fit-content" + ``` + + The `horizontalSizing` property specifies the horizontal sizing behavior of the container. + It can be one of the following values: + + * 'fit-content': The container fits the content. + * 'fill': The container fills the available space. + * 'auto': The container size is determined automatically. + verticalSizing: |- + ``` + verticalSizing: "fill" | "auto" | "fit-content" + ``` + + The `verticalSizing` property specifies the vertical sizing behavior of the container. + It can be one of the following values: + + * 'fit-content': The container fits the content. + * 'fill': The container fills the available space. + * 'auto': The container size is determined automatically. + dir: |- + ``` + dir: + | "row" + | "row-reverse" + | "column" + | "column-reverse" + ``` + + The direction of the flex layout. + + * 'row': Main axis is horizontal, from left to right. + * 'row-reverse': Main axis is horizontal, from right to left. + * 'column': Main axis is vertical, from top to bottom. + * 'column-reverse': Main axis is vertical, from bottom to top. + wrap: |- + ``` + wrap?: "wrap" | "nowrap" + ``` + + The optional wrapping behavior of the flex layout. + + * 'wrap': Child elements will wrap onto multiple lines. + * 'nowrap': Child elements will not wrap. + Methods: + remove: |- + ``` + remove(): void + ``` + + * The `remove` method removes the layout. + + Returns void + appendChild: |- + ``` + appendChild(child): void + ``` + + * Appends a child element to the flex layout. + + Parameters + + child: Shape + + The child element to be appended, of type `Shape`. + + Returns void + + Example + ``` + flexLayout.appendChild(childShape); + ``` +Flow: + overview: |- + Interface Flow + ============== + + Defines an interaction flow inside penpot. A flow is defined by a starting board for an interaction. + + ``` + interface Flow { + page: Page; + name: string; + startingBoard: Board; + remove(): void; + } + ``` + + Referenced by: Page + members: + Properties: + page: |- + ``` + readonly page: Page + ``` + + The page in which the flow is defined + name: |- + ``` + name: string + ``` + + The name for the current flow + startingBoard: |- + ``` + startingBoard: Board + ``` + + The starting board for this interaction flow + Methods: + remove: |- + ``` + remove(): void + ``` + + * Removes the flow from the page + + Returns void +Font: + overview: |- + Interface Font + ============== + + Represents a font in Penpot, which includes details about the font family, variants, and styling options. + This interface provides properties and methods for describing and applying fonts within Penpot. + + ``` + interface Font { + name: string; + fontId: string; + fontFamily: string; + fontStyle?: null | "normal" | "italic"; + fontVariantId: string; + fontWeight: string; + variants: FontVariant[]; + applyToText(text: Text, variant?: FontVariant): void; + applyToRange(range: TextRange, variant?: FontVariant): void; + } + ``` + + Referenced by: FontsContext, LibraryTypography + members: + Properties: + name: |- + ``` + name: string + ``` + + This property holds the human-readable name of the font. + fontId: |- + ``` + fontId: string + ``` + + The unique identifier of the font. + fontFamily: |- + ``` + fontFamily: string + ``` + + The font family of the font. + fontStyle: |- + ``` + fontStyle?: null | "normal" | "italic" + ``` + + The default font style of the font. + fontVariantId: |- + ``` + fontVariantId: string + ``` + + The default font variant ID of the font. + fontWeight: |- + ``` + fontWeight: string + ``` + + The default font weight of the font. + variants: |- + ``` + variants: FontVariant[] + ``` + + An array of font variants available for the font. + Methods: + applyToText: |- + ``` + applyToText(text, variant?): void + ``` + + * Applies the font styles to a text shape. + + Parameters + + text: Text + + The text shape to apply the font styles to. + + variant: FontVariant + + Optional. The specific font variant to apply. If not provided, applies the default variant. + + Returns void + + Example + ``` + font.applyToText(textShape, fontVariant); + ``` + applyToRange: |- + ``` + applyToRange(range, variant?): void + ``` + + * Applies the font styles to a text range within a text shape. + + Parameters + + range: TextRange + + The text range to apply the font styles to. + + variant: FontVariant + + Optional. The specific font variant to apply. If not provided, applies the default variant. + + Returns void + + Example + ``` + font.applyToRange(textRange, fontVariant); + ``` +FontVariant: + overview: |- + Interface FontVariant + ===================== + + Represents a font variant in Penpot, which defines a specific style variation of a font. + This interface provides properties for describing the characteristics of a font variant. + + ``` + interface FontVariant { + name: string; + fontVariantId: string; + fontWeight: string; + fontStyle: "normal" | "italic"; + } + ``` + + Referenced by: Font, LibraryTypography + members: + Properties: + name: |- + ``` + name: string + ``` + + The name of the font variant. + fontVariantId: |- + ``` + fontVariantId: string + ``` + + The unique identifier of the font variant. + fontWeight: |- + ``` + fontWeight: string + ``` + + The font weight of the font variant. + fontStyle: |- + ``` + fontStyle: "normal" | "italic" + ``` + + The font style of the font variant. +FontsContext: + overview: |- + Interface FontsContext + ====================== + + Represents the context for managing fonts in Penpot. + This interface provides methods to interact with fonts, such as retrieving fonts by ID or name. + + ``` + interface FontsContext { + all: Font[]; + findById(id: string): null | Font; + findByName(name: string): null | Font; + findAllById(id: string): Font[]; + findAllByName(name: string): Font[]; + } + ``` + + Referenced by: Context, Penpot + members: + Properties: + all: |- + ``` + all: Font[] + ``` + + An array containing all available fonts. + Methods: + findById: |- + ``` + findById(id): null | Font + ``` + + * Finds a font by its unique identifier. + + Parameters + + id: string + + The ID of the font to find. + + Returns null | Font + + Returns the `Font` object if found, otherwise `null`. + + Example + ``` + const font = fontsContext.findById('font-id');if (font) { console.log(font.name);} + ``` + findByName: |- + ``` + findByName(name): null | Font + ``` + + * Finds a font by its name. + + Parameters + + name: string + + The name of the font to find. + + Returns null | Font + + Returns the `Font` object if found, otherwise `null`. + + Example + ``` + const font = fontsContext.findByName('font-name');if (font) { console.log(font.name);} + ``` + findAllById: |- + ``` + findAllById(id): Font[] + ``` + + * Finds all fonts matching a specific ID. + + Parameters + + id: string + + The ID to match against. + + Returns Font[] + + Returns an array of `Font` objects matching the provided ID. + + Example + ``` + const fonts = fontsContext.findAllById('font-id');console.log(fonts); + ``` + findAllByName: |- + ``` + findAllByName(name): Font[] + ``` + + * Finds all fonts matching a specific name. + + Parameters + + name: string + + The name to match against. + + Returns Font[] + + Returns an array of `Font` objects matching the provided name. + + Example + ``` + const fonts = fontsContext.findAllByName('font-name');console.log(fonts); + ``` +GridLayout: + overview: |- + Interface GridLayout + ==================== + + GridLayout represents a grid layout in the Penpot application, extending the common layout interface. + It includes properties and methods to manage rows, columns, and child elements within the grid. + + ``` + interface GridLayout { + alignItems?: + | "center" + | "start" + | "end" + | "stretch"; + alignContent?: + | "center" + | "start" + | "end" + | "stretch" + | "space-between" + | "space-around" + | "space-evenly"; + justifyItems?: + | "center" + | "start" + | "end" + | "stretch"; + justifyContent?: + | "center" + | "start" + | "end" + | "stretch" + | "space-between" + | "space-around" + | "space-evenly"; + rowGap: number; + columnGap: number; + verticalPadding: number; + horizontalPadding: number; + topPadding: number; + rightPadding: number; + bottomPadding: number; + leftPadding: number; + horizontalSizing: "fill" | "auto" | "fit-content"; + verticalSizing: "fill" | "auto" | "fit-content"; + remove(): void; + dir: "row" | "column"; + rows: Track[]; + columns: Track[]; + addRow(type: TrackType, value?: number): void; + addRowAtIndex(index: number, type: TrackType, value?: number): void; + addColumn(type: TrackType, value?: number): void; + addColumnAtIndex(index: number, type: TrackType, value: number): void; + removeRow(index: number): void; + removeColumn(index: number): void; + setColumn(index: number, type: TrackType, value?: number): void; + setRow(index: number, type: TrackType, value?: number): void; + appendChild(child: Shape, row: number, column: number): void; + } + ``` + + Hierarchy (view full) + + * CommonLayout + + GridLayout + + Referenced by: Board, VariantContainer + members: + Properties: + alignItems: |- + ``` + alignItems?: + | "center" + | "start" + | "end" + | "stretch" + ``` + + The `alignItems` property specifies the default alignment for items inside the container. + It can be one of the following values: + + * 'start': Items are aligned at the start. + * 'end': Items are aligned at the end. + * 'center': Items are centered. + * 'stretch': Items are stretched to fill the container. + alignContent: |- + ``` + alignContent?: + | "center" + | "start" + | "end" + | "stretch" + | "space-between" + | "space-around" + | "space-evenly" + ``` + + The `alignContent` property specifies how the content is aligned within the container when there is extra space. + It can be one of the following values: + + * 'start': Content is aligned at the start. + * 'end': Content is aligned at the end. + * 'center': Content is centered. + * 'space-between': Content is distributed with space between. + * 'space-around': Content is distributed with space around. + * 'space-evenly': Content is distributed with even space around. + * 'stretch': Content is stretched to fill the container. + justifyItems: |- + ``` + justifyItems?: + | "center" + | "start" + | "end" + | "stretch" + ``` + + The `justifyItems` property specifies the default justification for items inside the container. + It can be one of the following values: + + * 'start': Items are justified at the start. + * 'end': Items are justified at the end. + * 'center': Items are centered. + * 'stretch': Items are stretched to fill the container. + justifyContent: |- + ``` + justifyContent?: + | "center" + | "start" + | "end" + | "stretch" + | "space-between" + | "space-around" + | "space-evenly" + ``` + + The `justifyContent` property specifies how the content is justified within the container when there is extra space. + It can be one of the following values: + + * 'start': Content is justified at the start. + * 'center': Content is centered. + * 'end': Content is justified at the end. + * 'space-between': Content is distributed with space between. + * 'space-around': Content is distributed with space around. + * 'space-evenly': Content is distributed with even space around. + * 'stretch': Content is stretched to fill the container. + rowGap: |- + ``` + rowGap: number + ``` + + The `rowGap` property specifies the gap between rows in the layout. + columnGap: |- + ``` + columnGap: number + ``` + + The `columnGap` property specifies the gap between columns in the layout. + verticalPadding: |- + ``` + verticalPadding: number + ``` + + The `verticalPadding` property specifies the vertical padding inside the container. + horizontalPadding: |- + ``` + horizontalPadding: number + ``` + + The `horizontalPadding` property specifies the horizontal padding inside the container. + topPadding: |- + ``` + topPadding: number + ``` + + The `topPadding` property specifies the padding at the top of the container. + rightPadding: |- + ``` + rightPadding: number + ``` + + The `rightPadding` property specifies the padding at the right of the container. + bottomPadding: |- + ``` + bottomPadding: number + ``` + + The `bottomPadding` property specifies the padding at the bottom of the container. + leftPadding: |- + ``` + leftPadding: number + ``` + + The `leftPadding` property specifies the padding at the left of the container. + horizontalSizing: |- + ``` + horizontalSizing: "fill" | "auto" | "fit-content" + ``` + + The `horizontalSizing` property specifies the horizontal sizing behavior of the container. + It can be one of the following values: + + * 'fit-content': The container fits the content. + * 'fill': The container fills the available space. + * 'auto': The container size is determined automatically. + verticalSizing: |- + ``` + verticalSizing: "fill" | "auto" | "fit-content" + ``` + + The `verticalSizing` property specifies the vertical sizing behavior of the container. + It can be one of the following values: + + * 'fit-content': The container fits the content. + * 'fill': The container fills the available space. + * 'auto': The container size is determined automatically. + dir: |- + ``` + dir: "row" | "column" + ``` + + The `dir` property specifies the primary direction of the grid layout. + It can be either 'column' or 'row'. + rows: |- + ``` + readonly rows: Track[] + ``` + + The `rows` property represents the collection of rows in the grid. + This property is read-only. + columns: |- + ``` + readonly columns: Track[] + ``` + + The `columns` property represents the collection of columns in the grid. + This property is read-only. + Methods: + remove: |- + ``` + remove(): void + ``` + + * The `remove` method removes the layout. + + Returns void + addRow: |- + ``` + addRow(type, value?): void + ``` + + * Adds a new row to the grid. + + Parameters + + type: TrackType + + The type of the row to add. + + value: number + + The value associated with the row type (optional). + + Returns void + + Example + ``` + const board = penpot.createBoard();const grid = board.addGridLayout();grid.addRow("flex", 1); + ``` + addRowAtIndex: |- + ``` + addRowAtIndex(index, type, value?): void + ``` + + * Adds a new row to the grid at the specified index. + + Parameters + + index: number + + The index at which to add the row. + + type: TrackType + + The type of the row to add. + + value: number + + The value associated with the row type (optional). + + Returns void + + Example + ``` + gridLayout.addRowAtIndex(0, 'fixed', 100); + ``` + addColumn: |- + ``` + addColumn(type, value?): void + ``` + + * Adds a new column to the grid. + + Parameters + + type: TrackType + + The type of the column to add. + + value: number + + The value associated with the column type (optional). + + Returns void + + Example + ``` + const board = penpot.createBoard();const grid = board.addGridLayout();grid.addColumn('percent', 50); + ``` + addColumnAtIndex: |- + ``` + addColumnAtIndex(index, type, value): void + ``` + + * Adds a new column to the grid at the specified index. + + Parameters + + index: number + + The index at which to add the column. + + type: TrackType + + The type of the column to add. + + value: number + + The value associated with the column type. + + Returns void + + Example + ``` + gridLayout.addColumnAtIndex(1, 'auto'); + ``` + removeRow: |- + ``` + removeRow(index): void + ``` + + * Removes a row from the grid at the specified index. + + Parameters + + index: number + + The index of the row to remove. + + Returns void + + Example + ``` + gridLayout.removeRow(2); + ``` + removeColumn: |- + ``` + removeColumn(index): void + ``` + + * Removes a column from the grid at the specified index. + + Parameters + + index: number + + The index of the column to remove. + + Returns void + + Example + ``` + gridLayout.removeColumn(3); + ``` + setColumn: |- + ``` + setColumn(index, type, value?): void + ``` + + * Sets the properties of a column at the specified index. + + Parameters + + index: number + + The index of the column to set. + + type: TrackType + + The type of the column. + + value: number + + The value associated with the column type (optional). + + Returns void + + Example + ``` + gridLayout.setColumn(0, 'fixed', 200); + ``` + setRow: |- + ``` + setRow(index, type, value?): void + ``` + + * Sets the properties of a row at the specified index. + + Parameters + + index: number + + The index of the row to set. + + type: TrackType + + The type of the row. + + value: number + + The value associated with the row type (optional). + + Returns void + + Example + ``` + gridLayout.setRow(1, 'flex'); + ``` + appendChild: |- + ``` + appendChild(child, row, column): void + ``` + + * Appends a child element to the grid at the specified row and column. + + Parameters + + child: Shape + + The child element to append. + + row: number + + The row index where the child will be placed. + + column: number + + The column index where the child will be placed. + + Returns void + + Example + ``` + gridLayout.appendChild(childShape, 0, 1); + ``` +Group: + overview: |- + Interface Group + =============== + + Represents a group of shapes in Penpot. + This interface extends `ShapeBase` and includes properties and methods specific to groups. + + ``` + interface Group { + type: "group"; + children: Shape[]; + appendChild(child: Shape): void; + insertChild(index: number, child: Shape): void; + isMask(): boolean; + makeMask(): void; + removeMask(): void; + 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; + fills: Fill[] | "mixed"; + 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) + + * ShapeBase + + Group + + Referenced by: Context, ContextTypesUtils, Penpot, Shape + members: + Properties: + type: |- + ``` + readonly type + ``` + + The type of the shape, which is always 'group' for groups. + children: |- + ``` + readonly children: Shape[] + ``` + + The children shapes contained within the group. + id: |- + ``` + readonly id: string + ``` + + The unique identifier of the shape. + name: |- + ``` + name: string + ``` + + The name of the shape. + parent: |- + ``` + readonly 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: |- + ``` + readonly 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: |- + ``` + readonly width: number + ``` + + The width of the shape. + height: |- + ``` + readonly height: number + ``` + + The height of the shape. + bounds: |- + ``` + readonly bounds: Bounds + ``` + + Returns + + Returns the bounding box surrounding the current shape + center: |- + ``` + readonly 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. + fills: |- + ``` + fills: Fill[] | "mixed" + ``` + + The fills applied to the shape. + strokes: |- + ``` + strokes: Stroke[] + ``` + + The strokes applied to the shape. + layoutChild: |- + ``` + readonly layoutChild?: LayoutChildProperties + ``` + + Layout properties for children of the shape. + layoutCell: |- + ``` + readonly layoutCell?: LayoutChildProperties + ``` + + Layout properties for cells in a grid layout. + interactions: |- + ``` + readonly interactions: Interaction[] + ``` + + The interactions for the current shape. + Methods: + appendChild: |- + ``` + appendChild(child): void + ``` + + * Appends a child shape to the group. + + Parameters + + child: Shape + + The child shape to append. + + Returns void + + Example + ``` + group.appendChild(childShape); + ``` + insertChild: |- + ``` + insertChild(index, child): void + ``` + + * Inserts a child shape at the specified index within the group. + + Parameters + + index: number + + The index at which to insert the child shape. + + child: Shape + + The child shape to insert. + + Returns void + + Example + ``` + group.insertChild(0, childShape); + ``` + isMask: |- + ``` + isMask(): boolean + ``` + + * Checks if the group is currently a mask. + A mask defines a clipping path for its child shapes. + + Returns boolean + makeMask: |- + ``` + makeMask(): void + ``` + + * Converts the group into a mask. + + Returns void + removeMask: |- + ``` + removeMask(): void + ``` + + * Removes the mask from the group. + + 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); + ``` + 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 + ``` + + * 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 +GuideColumn: + overview: |- + Interface GuideColumn + ===================== + + Represents a goard guide for columns in Penpot. + This interface includes properties for defining the type, visibility, and parameters of column guides within a board. + + ``` + interface GuideColumn { + type: "column"; + display: boolean; + params: GuideColumnParams; + } + ``` + + Referenced by: Guide + members: + Properties: + type: |- + ``` + type + ``` + + The type of the guide, which is always 'column' for column guides. + display: |- + ``` + display: boolean + ``` + + Specifies whether the column guide is displayed. + params: |- + ``` + params: GuideColumnParams + ``` + + The parameters defining the appearance and layout of the column guides. +GuideColumnParams: + overview: |- + Interface GuideColumnParams + =========================== + + Represents parameters for board guide columns in Penpot. + This interface includes properties for defining the appearance and layout of column guides within a board. + + ``` + interface GuideColumnParams { + color: { + color: string; + opacity: number; + }; + type?: + | "center" + | "left" + | "right" + | "stretch"; + size?: number; + margin?: number; + itemLength?: number; + gutter?: number; + } + ``` + + Referenced by: GuideColumn, GuideRow + members: + Properties: + color: |- + ``` + color: { + color: string; + opacity: number; + } + ``` + + The color configuration for the column guides. + type: |- + ``` + type?: + | "center" + | "left" + | "right" + | "stretch" + ``` + + The optional alignment type of the column guides. + + * 'stretch': Columns stretch to fit the available space. + * 'left': Columns align to the left. + * 'center': Columns align to the center. + * 'right': Columns align to the right. + size: |- + ``` + size?: number + ``` + + The optional size of each column. + margin: |- + ``` + margin?: number + ``` + + The optional margin between the columns and the board edges. + itemLength: |- + ``` + itemLength?: number + ``` + + The optional length of each item within the columns. + gutter: |- + ``` + gutter?: number + ``` + + The optional gutter width between columns. +GuideRow: + overview: |- + Interface GuideRow + ================== + + Represents a board guide for rows in Penpot. + This interface includes properties for defining the type, visibility, and parameters of row guides within a board. + + ``` + interface GuideRow { + type: "row"; + display: boolean; + params: GuideColumnParams; + } + ``` + + Referenced by: Guide + members: + Properties: + type: |- + ``` + type + ``` + + The type of the guide, which is always 'row' for row guides. + display: |- + ``` + display: boolean + ``` + + Specifies whether the row guide is displayed. + params: |- + ``` + params: GuideColumnParams + ``` + + The parameters defining the appearance and layout of the row guides. + Note: This reuses the same parameter structure as column guides. +GuideSquare: + overview: |- + Interface GuideSquare + ===================== + + Represents a board guide for squares in Penpot. + This interface includes properties for defining the type, visibility, and parameters of square guides within a board. + + ``` + interface GuideSquare { + type: "square"; + display: boolean; + params: GuideSquareParams; + } + ``` + + Referenced by: Guide + members: + Properties: + type: |- + ``` + type + ``` + + The type of the guide, which is always 'square' for square guides. + display: |- + ``` + display: boolean + ``` + + Specifies whether the square guide is displayed. + params: |- + ``` + params: GuideSquareParams + ``` + + The parameters defining the appearance and layout of the square guides. +GuideSquareParams: + overview: |- + Interface GuideSquareParams + =========================== + + Represents parameters for board guide squares in Penpot. + This interface includes properties for defining the appearance and size of square guides within a board. + + ``` + interface GuideSquareParams { + color: { + color: string; + opacity: number; + }; + size?: number; + } + ``` + + Referenced by: GuideSquare + members: + Properties: + color: |- + ``` + color: { + color: string; + opacity: number; + } + ``` + + The color configuration for the square guides. + size: |- + ``` + size?: number + ``` + + The optional size of each square guide. +HistoryContext: + overview: |- + Interface HistoryContext + ======================== + + This object allows to access to some history functions + + ``` + interface HistoryContext { + undoBlockBegin(): Symbol; + undoBlockFinish(blockId: Symbol): void; + } + ``` + + Referenced by: Context, Penpot + members: + Methods: + undoBlockBegin: |- + ``` + undoBlockBegin(): Symbol + ``` + + * Starts an undo block. All operations done inside this block will be undone together until + a call to `undoBlockFinish` is called. + + Returns Symbol + + the block identifier + undoBlockFinish: |- + ``` + undoBlockFinish(blockId): void + ``` + + * Ends the undo block started with `undoBlockBegin` + + Parameters + + blockId: Symbol + + is the id returned by `undoBlockBegin` + + Returns void + + Example + ``` + historyContext.undoBlockFinish(blockId); + ``` +Image: + overview: |- + Interface Image + =============== + + Represents an image shape in Penpot. + This interface extends `ShapeBase` and includes properties specific to image shapes. + + ``` + interface Image { + type: "image"; + fills: Fill[]; + 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) + + * ShapeBase + + Image + + Referenced by: Shape + members: + Properties: + type: |- + ``` + type + ``` + fills: |- + ``` + fills: Fill[] + ``` + + The fills applied to the shape. + + Overrides ShapeBase.fills + id: |- + ``` + readonly id: string + ``` + + The unique identifier of the shape. + name: |- + ``` + name: string + ``` + + The name of the shape. + parent: |- + ``` + readonly 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: |- + ``` + readonly 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: |- + ``` + readonly width: number + ``` + + The width of the shape. + height: |- + ``` + readonly height: number + ``` + + The height of the shape. + bounds: |- + ``` + readonly bounds: Bounds + ``` + + Returns + + Returns the bounding box surrounding the current shape + center: |- + ``` + readonly 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: |- + ``` + readonly layoutChild?: LayoutChildProperties + ``` + + Layout properties for children of the shape. + layoutCell: |- + ``` + readonly layoutCell?: LayoutChildProperties + ``` + + Layout properties for cells in a grid layout. + interactions: |- + ``` + readonly interactions: Interaction[] + ``` + + The interactions for the current shape. + Methods: + 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 + ``` + + * 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 +Interaction: + overview: |- + Interface Interaction + ===================== + + Penpot allows you to prototype interactions by connecting boards, which can act as screens. + + ``` + interface Interaction { + shape?: Shape; + trigger: Trigger; + delay?: null | number; + action: Action; + remove(): void; + } + ``` + + Referenced by: Board, Boolean, Ellipse, Group, Image, Path, Rectangle, ShapeBase, SvgRaw, Text, VariantContainer + members: + Properties: + shape: |- + ``` + readonly shape?: Shape + ``` + + The shape that owns the interaction + trigger: |- + ``` + trigger: Trigger + ``` + + The user action that will start the interaction. + delay: |- + ``` + delay?: null | number + ``` + + Time in **milliseconds** after the action will happen. Only applies to `after-delay` triggers. + action: |- + ``` + action: Action + ``` + + The action that will execute after the trigger happens. + Methods: + remove: |- + ``` + remove(): void + ``` + + * Removes the interaction + + Returns void +LayoutCellProperties: + overview: |- + Interface LayoutCellProperties + ============================== + + Properties for defining the layout of a cell in Penpot. + + ``` + interface LayoutCellProperties { + row?: number; + rowSpan?: number; + column?: number; + columnSpan?: number; + areaName?: string; + position?: "area" | "auto" | "manual"; + } + ``` + members: + Properties: + row: |- + ``` + row?: number + ``` + + The row index of the cell. + This value is optional and indicates the starting row of the cell. + rowSpan: |- + ``` + rowSpan?: number + ``` + + The number of rows the cell should span. + This value is optional and determines the vertical span of the cell. + column: |- + ``` + column?: number + ``` + + The column index of the cell. + This value is optional and indicates the starting column of the cell. + columnSpan: |- + ``` + columnSpan?: number + ``` + + The number of columns the cell should span. + This value is optional and determines the horizontal span of the cell. + areaName: |- + ``` + areaName?: string + ``` + + The name of the grid area that this cell belongs to. + This value is optional and can be used to define named grid areas. + position: |- + ``` + position?: "area" | "auto" | "manual" + ``` + + The positioning mode of the cell. + This value can be 'auto', 'manual', or 'area' and determines how the cell is positioned within the layout. +LayoutChildProperties: + overview: |- + Interface LayoutChildProperties + =============================== + + Properties for defining the layout of a child element in Penpot. + + ``` + interface LayoutChildProperties { + absolute: boolean; + zIndex: number; + horizontalSizing: "fill" | "auto" | "fix"; + verticalSizing: "fill" | "auto" | "fix"; + alignSelf: + | "center" + | "auto" + | "start" + | "end" + | "stretch"; + horizontalMargin: number; + verticalMargin: number; + topMargin: number; + rightMargin: number; + bottomMargin: number; + leftMargin: number; + maxWidth: null | number; + maxHeight: null | number; + minWidth: null | number; + minHeight: null | number; + } + ``` + + Referenced by: Board, Boolean, Ellipse, Group, Image, Path, Rectangle, ShapeBase, SvgRaw, Text, VariantContainer + members: + Properties: + absolute: |- + ``` + absolute: boolean + ``` + + Specifies whether the child element is positioned absolutely. + When set to true, the element is taken out of the normal document flow and positioned relative to its nearest positioned ancestor. + zIndex: |- + ``` + zIndex: number + ``` + + Defines the stack order of the child element + Elements with a higher zIndex will be displayed in front of those with a lower zIndex. + horizontalSizing: |- + ``` + horizontalSizing: "fill" | "auto" | "fix" + ``` + + Determines the horizontal sizing behavior of the child element + + * 'auto': The width is determined by the content. + * 'fill': The element takes up the available width. + * 'fix': The width is fixed. + verticalSizing: |- + ``` + verticalSizing: "fill" | "auto" | "fix" + ``` + + Determines the vertical sizing behavior of the child element. + + * 'auto': The height is determined by the content. + * 'fill': The element takes up the available height. + * 'fix': The height is fixed. + alignSelf: |- + ``` + alignSelf: + | "center" + | "auto" + | "start" + | "end" + | "stretch" + ``` + + Aligns the child element within its container. + + * 'auto': Default alignment. + * 'start': Aligns the element at the start of the container. + * 'center': Centers the element within the container. + * 'end': Aligns the element at the end of the container. + * 'stretch': Stretches the element to fill the container. + horizontalMargin: |- + ``` + horizontalMargin: number + ``` + + Sets the horizontal margin of the child element. + This is the space on the left and right sides of the element. + verticalMargin: |- + ``` + verticalMargin: number + ``` + + Sets the vertical margin of the child element. + This is the space on the top and bottom sides of the element. + topMargin: |- + ``` + topMargin: number + ``` + + Sets the top margin of the child element. + This is the space above the element. + rightMargin: |- + ``` + rightMargin: number + ``` + + Sets the right margin of the child element. + This is the space to the right of the element. + bottomMargin: |- + ``` + bottomMargin: number + ``` + + Sets the bottom margin of the child element. + This is the space below the element. + leftMargin: |- + ``` + leftMargin: number + ``` + + Sets the left margin of the child element. + This is the space to the left of the element. + maxWidth: |- + ``` + maxWidth: null | number + ``` + + Defines the maximum width of the child element. + If set to null, there is no maximum width constraint. + maxHeight: |- + ``` + maxHeight: null | number + ``` + + Defines the maximum height of the child element. + If set to null, there is no maximum height constraint. + minWidth: |- + ``` + minWidth: null | number + ``` + + Defines the minimum width of the child element. + If set to null, there is no minimum width constraint. + minHeight: |- + ``` + minHeight: null | number + ``` + + Defines the minimum height of the child element. + If set to null, there is no minimum height constraint. +Library: + overview: |- + Interface Library + ================= + + Represents a library in Penpot, containing colors, typographies, and components. + + ``` + interface Library { + id: string; + name: string; + colors: LibraryColor[]; + typographies: LibraryTypography[]; + components: LibraryComponent[]; + createColor(): LibraryColor; + createTypography(): LibraryTypography; + createComponent(shapes: Shape[]): LibraryComponent; + 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) + + * PluginData + + Library + + Referenced by: LibraryContext + members: + Properties: + id: |- + ``` + readonly id: string + ``` + + The unique identifier of the library. + name: |- + ``` + readonly name: string + ``` + + The name of the library. + colors: |- + ``` + readonly colors: LibraryColor[] + ``` + + An array of color elements in the library. + + Example + ``` + console.log(penpot.library.local.colors); + ``` + typographies: |- + ``` + readonly typographies: LibraryTypography[] + ``` + + An array of typography elements in the library. + components: |- + ``` + readonly components: LibraryComponent[] + ``` + + An array of component elements in the library. + + Example + ``` + console.log(penpot.library.local.components + ``` + Methods: + createColor: |- + ``` + createColor(): LibraryColor + ``` + + * Creates a new color element in the library. + + Returns LibraryColor + + Returns a new `LibraryColor` object representing the created color element. + + Example + ``` + const newColor = penpot.library.local.createColor();console.log(newColor); + ``` + createTypography: |- + ``` + createTypography(): LibraryTypography + ``` + + * Creates a new typography element in the library. + + Returns LibraryTypography + + Returns a new `LibraryTypography` object representing the created typography element. + + Example + ``` + const newTypography = library.createTypography(); + ``` + createComponent: |- + ``` + createComponent(shapes): LibraryComponent + ``` + + * Creates a new component element in the library using the provided shapes. + + Parameters + + shapes: Shape[] + + An array of `Shape` objects representing the shapes to be included in the component. + + Returns LibraryComponent + + Returns a new `LibraryComponent` object representing the created component element. + + Example + ``` + const newComponent = penpot.library.local.createComponent([shape1, shape2]); + ``` + 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); + ``` +LibraryColor: + overview: |- + Interface LibraryColor + ====================== + + Represents a color element from a library in Penpot. + This interface extends `LibraryElement` and includes properties specific to color elements. + + ``` + interface LibraryColor { + color?: string; + opacity?: number; + gradient?: Gradient; + image?: ImageData; + asFill(): Fill; + asStroke(): Stroke; + 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) + + * LibraryElement + + LibraryColor + + Referenced by: Library + members: + Properties: + color: |- + ``` + color?: string + ``` + + The color value of the library color. + opacity: |- + ``` + opacity?: number + ``` + + The opacity value of the library color. + gradient: |- + ``` + gradient?: Gradient + ``` + + The gradient value of the library color, if it's a gradient. + image: |- + ``` + image?: ImageData + ``` + + The image data of the library color, if it's an image fill. + id: |- + ``` + readonly id: string + ``` + + The unique identifier of the library element. + libraryId: |- + ``` + readonly 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: + asFill: |- + ``` + asFill(): Fill + ``` + + * Converts the library color into a fill object. + + Returns Fill + + Returns a `Fill` object representing the color as a fill. + + Example + ``` + const fill = libraryColor.asFill(); + ``` + asStroke: |- + ``` + asStroke(): Stroke + ``` + + * Converts the library color into a stroke object. + + Returns Stroke + + Returns a `Stroke` object representing the color as a stroke. + + Example + ``` + const stroke = libraryColor.asStroke(); + ``` + 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); + ``` +LibraryComponent: + overview: |- + Interface LibraryComponent + ========================== + + Represents a component element from a library in Penpot. + This interface extends `LibraryElement` and includes properties specific to component elements. + + ``` + interface LibraryComponent { + instance(): Shape; + mainInstance(): Shape; + isVariant(): boolean; + transformInVariant(): 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) + + * LibraryElement + + LibraryComponent + - LibraryVariantComponent + + Referenced by: Board, Boolean, ContextTypesUtils, Ellipse, Group, Image, Library, Path, Rectangle, ShapeBase, SvgRaw, Text, VariantContainer, Variants + members: + Properties: + id: |- + ``` + readonly id: string + ``` + + The unique identifier of the library element. + libraryId: |- + ``` + readonly 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 + 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: |- + ``` + readonly variants: null | Variants + ``` + + Access to the Variant interface, for attributes and actions over the full Variant (not only this VariantComponent) + variantProps: |- + ``` + readonly 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: |- + ``` + readonly id: string + ``` + + The unique identifier of the library element. + libraryId: |- + ``` + readonly 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 + ``` + + * 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); + ``` +LibraryElement: + overview: |- + Interface LibraryElement + ======================== + + Represents an element in a Penpot library. + This interface provides information about a specific element in a library. + + ``` + interface LibraryElement { + 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) + + * PluginData + + LibraryElement + - LibraryColor + - LibraryComponent + - LibraryTypography + members: + Properties: + id: |- + ``` + readonly id: string + ``` + + The unique identifier of the library element. + libraryId: |- + ``` + readonly 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: + 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); + ``` +LibrarySummary: + overview: |- + Interface LibrarySummary + ======================== + + Represents a summary of a Penpot library. + This interface provides properties for summarizing various aspects of a Penpot library. + + ``` + interface LibrarySummary { + id: string; + name: string; + numColors: number; + numComponents: number; + numTypographies: number; + } + ``` + + Referenced by: LibraryContext + members: + Properties: + id: |- + ``` + readonly id: string + ``` + + The unique identifier of the library. + name: |- + ``` + readonly name: string + ``` + + The name of the library. + numColors: |- + ``` + readonly numColors: number + ``` + + The number of colors in the library. + numComponents: |- + ``` + readonly numComponents: number + ``` + + The number of components in the library. + numTypographies: |- + ``` + readonly numTypographies: number + ``` + + The number of typographies in the library. +LibraryTypography: + overview: |- + Interface LibraryTypography + =========================== + + Represents a typography element from a library in Penpot. + This interface extends `LibraryElement` and includes properties specific to typography elements. + + ``` + interface LibraryTypography { + id: string; + libraryId: string; + name: string; + path: string; + fontId: string; + fontFamily: string; + fontVariantId: string; + fontSize: string; + fontWeight: string; + fontStyle?: null | "normal" | "italic"; + lineHeight: string; + letterSpacing: string; + textTransform?: + | null + | "uppercase" + | "capitalize" + | "lowercase"; + applyToText(shape: Shape): void; + applyToTextRange(range: TextRange): void; + setFont(font: Font, variant?: FontVariant): void; + 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) + + * LibraryElement + + LibraryTypography + + Referenced by: Library, Text, TextRange + members: + Properties: + id: |- + ``` + readonly id: string + ``` + + The unique identifier of the library element. + libraryId: |- + ``` + readonly 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. + fontId: |- + ``` + fontId: string + ``` + + The unique identifier of the font used in the typography element. + fontFamily: |- + ``` + fontFamily: string + ``` + + The font family of the typography element. + fontVariantId: |- + ``` + fontVariantId: string + ``` + + The unique identifier of the font variant used in the typography element. + fontSize: |- + ``` + fontSize: string + ``` + + The font size of the typography element. + fontWeight: |- + ``` + fontWeight: string + ``` + + The font weight of the typography element. + fontStyle: |- + ``` + fontStyle?: null | "normal" | "italic" + ``` + + The font style of the typography element. + lineHeight: |- + ``` + lineHeight: string + ``` + + The line height of the typography element. + letterSpacing: |- + ``` + letterSpacing: string + ``` + + The letter spacing of the typography element. + textTransform: |- + ``` + textTransform?: + | null + | "uppercase" + | "capitalize" + | "lowercase" + ``` + + The text transform applied to the typography element. + Methods: + applyToText: |- + ``` + applyToText(shape): void + ``` + + * Applies the typography styles to a text shape. + + Parameters + + shape: Shape + + The text shape to apply the typography styles to. + + Returns void + + Example + ``` + typographyElement.applyToText(textShape); + ``` + applyToTextRange: |- + ``` + applyToTextRange(range): void + ``` + + * Applies the typography styles to a range of text within a text shape. + + Parameters + + range: TextRange + + Represents a range of text within a Text shape. This interface provides properties for styling and formatting text ranges. + + Returns void + + Example + ``` + typographyElement.applyToTextRange(textShape); + ``` + setFont: |- + ``` + setFont(font, variant?): void + ``` + + * Sets the font and optionally its variant for the typography element. + + Parameters + + font: Font + + The font to set. + + variant: FontVariant + + The font variant to set (optional). + + Returns void + + Example + ``` + typographyElement.setFont(newFont, newVariant); + ``` + 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); + ``` +LocalStorage: + overview: |- + Interface LocalStorage + ====================== + + Proxy for the local storage. Only elements owned by the plugin + can be stored and accessed. + Warning: other plugins won't be able to access this information but + the user could potentialy access the data through the browser information. + + ``` + interface LocalStorage { + getItem(key: string): string; + setItem(key: string, value: unknown): void; + removeItem(key: string): void; + getKeys(): string[]; + } + ``` + + Referenced by: Context, Penpot + members: + Methods: + getItem: |- + ``` + getItem(key): string + ``` + + * Retrieve the element with the given key + Requires the `allow:localstorage` permission. + + Parameters + + key: string + + Returns string + setItem: |- + ``` + setItem(key, value): void + ``` + + * Set the data given the key. If the value already existed it + will be overriden. The value will be stored in a string representation. + Requires the `allow:localstorage` permission. + + Parameters + + key: string + + value: unknown + + Returns void + removeItem: |- + ``` + removeItem(key): void + ``` + + * Remove the value stored in the key. + Requires the `allow:localstorage` permission. + + Parameters + + key: string + + Returns void + getKeys: |- + ``` + getKeys(): string[] + ``` + + * Return all the keys for the data stored by the plugin. + Requires the `allow:localstorage` permission. + + Returns string[] +NavigateTo: + overview: |- + Interface NavigateTo + ==================== + + It takes the user from one board to the destination set in the interaction. + + ``` + interface NavigateTo { + type: "navigate-to"; + destination: Board; + preserveScrollPosition?: boolean; + animation?: Animation; + } + ``` + + Referenced by: Action + members: + Properties: + type: |- + ``` + readonly type + ``` + + Type of action + destination: |- + ``` + readonly destination: Board + ``` + + Board to which the action targets + preserveScrollPosition: |- + ``` + readonly preserveScrollPosition?: boolean + ``` + + When true the scroll will be preserved. + animation: |- + ``` + readonly animation?: Animation + ``` + + Animation displayed with this interaction. +OpenOverlay: + overview: |- + Interface OpenOverlay + ===================== + + It opens a board right over the current board. + + ``` + interface OpenOverlay { + type: "open-overlay"; + destination: Board; + relativeTo?: Shape; + position?: + | "center" + | "manual" + | "top-left" + | "top-right" + | "top-center" + | "bottom-left" + | "bottom-right" + | "bottom-center"; + manualPositionLocation?: Point; + closeWhenClickOutside?: boolean; + addBackgroundOverlay?: boolean; + animation?: Animation; + } + ``` + + Hierarchy (view full) + + * OverlayAction + + OpenOverlay + + Referenced by: Action + members: + Properties: + type: |- + ``` + readonly type + ``` + + The action type + destination: |- + ``` + readonly destination: Board + ``` + + Overlay board that will be opened. + relativeTo: |- + ``` + readonly relativeTo?: Shape + ``` + + Base shape to which the overlay will be positioned taking constraints into account. + position: |- + ``` + readonly position?: + | "center" + | "manual" + | "top-left" + | "top-right" + | "top-center" + | "bottom-left" + | "bottom-right" + | "bottom-center" + ``` + + Positioning of the overlay. + manualPositionLocation: |- + ``` + readonly manualPositionLocation?: Point + ``` + + For `position = 'manual'` the location of the overlay. + closeWhenClickOutside: |- + ``` + readonly closeWhenClickOutside?: boolean + ``` + + When true the overlay will be closed when clicking outside + addBackgroundOverlay: |- + ``` + readonly addBackgroundOverlay?: boolean + ``` + + When true a background will be added to the overlay. + animation: |- + ``` + readonly animation?: Animation + ``` + + Animation displayed with this interaction. +OpenUrl: + overview: |- + Interface OpenUrl + ================= + + This action opens an URL in a new tab. + + ``` + interface OpenUrl { + type: "open-url"; + url: string; + } + ``` + + Referenced by: Action + members: + Properties: + type: |- + ``` + readonly type + ``` + + The action type + url: |- + ``` + readonly url: string + ``` + + The URL to open when the action is executed +OverlayAction: + overview: |- + Interface OverlayAction + ======================= + + Base type for the actions "open-overlay" and "toggle-overlay" that share most of their properties + + ``` + interface OverlayAction { + destination: Board; + relativeTo?: Shape; + position?: + | "center" + | "manual" + | "top-left" + | "top-right" + | "top-center" + | "bottom-left" + | "bottom-right" + | "bottom-center"; + manualPositionLocation?: Point; + closeWhenClickOutside?: boolean; + addBackgroundOverlay?: boolean; + animation?: Animation; + } + ``` + + Hierarchy (view full) + + * OverlayAction + + OpenOverlay + + ToggleOverlay + members: + Properties: + destination: |- + ``` + readonly destination: Board + ``` + + Overlay board that will be opened. + relativeTo: |- + ``` + readonly relativeTo?: Shape + ``` + + Base shape to which the overlay will be positioned taking constraints into account. + position: |- + ``` + readonly position?: + | "center" + | "manual" + | "top-left" + | "top-right" + | "top-center" + | "bottom-left" + | "bottom-right" + | "bottom-center" + ``` + + Positioning of the overlay. + manualPositionLocation: |- + ``` + readonly manualPositionLocation?: Point + ``` + + For `position = 'manual'` the location of the overlay. + closeWhenClickOutside: |- + ``` + readonly closeWhenClickOutside?: boolean + ``` + + When true the overlay will be closed when clicking outside + addBackgroundOverlay: |- + ``` + readonly addBackgroundOverlay?: boolean + ``` + + When true a background will be added to the overlay. + animation: |- + ``` + readonly animation?: Animation + ``` + + Animation displayed with this interaction. +Page: + overview: |- + Interface Page + ============== + + Page represents a page in the Penpot application. + It includes properties for the page's identifier and name, as well as methods for managing shapes on the page. + + ``` + interface Page { + id: string; + name: string; + rulerGuides: RulerGuide[]; + root: Shape; + getShapeById(id: string): null | Shape; + findShapes(criteria?: { + name?: string; + nameLike?: string; + type?: + | "boolean" + | "path" + | "ellipse" + | "image" + | "text" + | "group" + | "board" + | "rectangle" + | "svg-raw"; + }): Shape[]; + flows: Flow[]; + createFlow(name: string, board: Board): Flow; + removeFlow(flow: Flow): void; + addRulerGuide(orientation: RulerGuideOrientation, value: number, board?: Board): RulerGuide; + removeRulerGuide(guide: RulerGuide): void; + addCommentThread(content: string, position: Point): Promise; + removeCommentThread(commentThread: CommentThread): Promise; + findCommentThreads(criteria?: { + onlyYours: boolean; + showResolved: boolean; + }): Promise; + 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) + + * PluginData + + Page + + Referenced by: Context, EventsMap, File, Flow, Penpot + members: + Properties: + id: |- + ``` + readonly id: string + ``` + + The `id` property is a unique identifier for the page. + name: |- + ``` + name: string + ``` + + The `name` property is the name of the page. + rulerGuides: |- + ``` + readonly rulerGuides: RulerGuide[] + ``` + + The ruler guides attached to the board + root: |- + ``` + root: Shape + ``` + + The root shape of the current page. Will be the parent shape of all the shapes inside the document. + Requires `content:read` permission. + flows: |- + ``` + readonly flows: Flow[] + ``` + + The interaction flows defined for the page. + Methods: + getShapeById: |- + ``` + getShapeById(id): null | Shape + ``` + + * Retrieves a shape by its unique identifier. + + Parameters + + id: string + + The unique identifier of the shape. + + Returns null | Shape + + Example + ``` + const shape = penpot.currentPage.getShapeById('shapeId'); + ``` + findShapes: |- + ``` + findShapes(criteria?): Shape[] + ``` + + * Finds all shapes on the page. + Optionaly it gets a criteria object to search for specific criteria + + Parameters + + criteria: { + name?: string; + nameLike?: string; + type?: + | "boolean" + | "path" + | "ellipse" + | "image" + | "text" + | "group" + | "board" + | "rectangle" + | "svg-raw"; + } + - Optionalname?: string + - OptionalnameLike?: string + - Optionaltype?: | "boolean" | "path" | "ellipse" | "image" | "text" | "group" | "board" | "rectangle" | "svg-raw" + + Returns Shape[] + + Example + ``` + const shapes = penpot.currentPage.findShapes({ name: 'exampleName' }); + ``` + createFlow: |- + ``` + createFlow(name, board): Flow + ``` + + * Creates a new flow in the page. + + Parameters + + name: string + + the name identifying the flow + + board: Board + + the starting board for the current flow + + Returns Flow + + Example + ``` + const flow = penpot.currentPage.createFlow('exampleFlow', board); + ``` + removeFlow: |- + ``` + removeFlow(flow): void + ``` + + * Removes the flow from the page + + Parameters + + flow: Flow + + the flow to be removed from the page + + Returns void + addRulerGuide: |- + ``` + addRulerGuide(orientation, value, board?): RulerGuide + ``` + + * Creates a new ruler guide. + + Parameters + + orientation: RulerGuideOrientation + + value: number + + board: Board + + Returns RulerGuide + removeRulerGuide: |- + ``` + removeRulerGuide(guide): void + ``` + + * Removes the `guide` from the current page. + + Parameters + + guide: RulerGuide + + Returns void + addCommentThread: |- + ``` + addCommentThread(content, position): Promise + ``` + + * Creates a new comment thread in the `position`. Optionaly adds + it into the `board`. + Returns the thread created. + Requires the `comment:write` permission. + + Parameters + + content: string + + position: Point + + Returns Promise + removeCommentThread: |- + ``` + removeCommentThread(commentThread): Promise + ``` + + * Removes the comment thread. + Requires the `comment:write` permission. + + Parameters + + commentThread: CommentThread + + Returns Promise + findCommentThreads: |- + ``` + findCommentThreads(criteria?): Promise + ``` + + * Find all the comments that match the criteria. + + + `onlyYours`: if `true` will return the threads where the current + user has engaged. + + `showResolved`: by default resolved comments will be hidden. If `true` + the resolved will be returned. + Requires the `comment:read` or `comment:write` permission. + + Parameters + + criteria: { + onlyYours: boolean; + showResolved: boolean; + } + - onlyYours: boolean + - showResolved: boolean + + Returns Promise + 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); + ``` +Path: + overview: |- + Interface Path + ============== + + Represents a path shape in Penpot. + This interface extends `ShapeBase` and includes properties and methods specific to paths. + + ``` + interface Path { + type: "path"; + toD(): string; + content: string; + d: string; + commands: PathCommand[]; + fills: Fill[]; + 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) + + * ShapeBase + + Path + + Referenced by: Context, ContextTypesUtils, Penpot, Shape + members: + Properties: + type: |- + ``` + readonly type + ``` + + The type of the shape, which is always 'path' for path shapes. + content: |- + ``` + 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. + fills: |- + ``` + fills: Fill[] + ``` + + The fills applied to the shape. + + Overrides ShapeBase.fills + id: |- + ``` + readonly id: string + ``` + + The unique identifier of the shape. + name: |- + ``` + name: string + ``` + + The name of the shape. + parent: |- + ``` + readonly 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: |- + ``` + readonly 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: |- + ``` + readonly width: number + ``` + + The width of the shape. + height: |- + ``` + readonly height: number + ``` + + The height of the shape. + bounds: |- + ``` + readonly bounds: Bounds + ``` + + Returns + + Returns the bounding box surrounding the current shape + center: |- + ``` + readonly 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: |- + ``` + readonly layoutChild?: LayoutChildProperties + ``` + + Layout properties for children of the shape. + layoutCell: |- + ``` + readonly layoutCell?: LayoutChildProperties + ``` + + Layout properties for cells in a grid layout. + interactions: |- + ``` + readonly interactions: Interaction[] + ``` + + The interactions for the current shape. + Methods: + toD: |- + ``` + toD(): string + ``` + + * Converts the path shape to its path data representation. + + Returns string + + Returns the path data (d attribute) as a string. + + Deprecated + + Use the `d` attribute + 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 + ``` + + * 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 +PathCommand: + overview: |- + Interface PathCommand + ===================== + + Represents a path command in Penpot. + This interface includes a property for defining the type of command. + + ``` + interface PathCommand { + command: + | "M" + | "move-to" + | "Z" + | "close-path" + | "L" + | "line-to" + | "H" + | "line-to-horizontal" + | "V" + | "line-to-vertical" + | "C" + | "curve-to" + | "S" + | "smooth-curve-to" + | "Q" + | "quadratic-bezier-curve-to" + | "T" + | "smooth-quadratic-bezier-curve-to" + | "A" + | "elliptical-arc"; + params?: { + x?: number; + y?: number; + c1x?: number; + c1y?: number; + c2x?: number; + c2y?: number; + rx?: number; + ry?: number; + xAxisRotation?: number; + largeArcFlag?: boolean; + sweepFlag?: boolean; + }; + } + ``` + + Referenced by: Boolean, Path + members: + Properties: + command: |- + ``` + command: + | "M" + | "move-to" + | "Z" + | "close-path" + | "L" + | "line-to" + | "H" + | "line-to-horizontal" + | "V" + | "line-to-vertical" + | "C" + | "curve-to" + | "S" + | "smooth-curve-to" + | "Q" + | "quadratic-bezier-curve-to" + | "T" + | "smooth-quadratic-bezier-curve-to" + | "A" + | "elliptical-arc" + ``` + + The type of path command. + Possible values include: + + * 'M' or 'move-to': Move to a new point. + * 'Z' or 'close-path': Close the current path. + * 'L' or 'line-to': Draw a straight line to a new point. + * 'H' or 'line-to-horizontal': Draw a horizontal line to a new point. + * 'V' or 'line-to-vertical': Draw a vertical line to a new point. + * 'C' or 'curve-to': Draw a cubic Bezier curve to a new point. + * 'S' or 'smooth-curve-to': Draw a smooth cubic Bezier curve to a new point. + * 'Q' or 'quadratic-bezier-curve-to': Draw a quadratic Bezier curve to a new point. + * 'T' or 'smooth-quadratic-bezier-curve-to': Draw a smooth quadratic Bezier curve to a new point. + * 'A' or 'elliptical-arc': Draw an elliptical arc to a new point. + + Example + ``` + const pathCommand: PathCommand = { command: 'M', params: { x: 0, y: 0 } }; + ``` + params: |- + ``` + params?: { + x?: number; + y?: number; + c1x?: number; + c1y?: number; + c2x?: number; + c2y?: number; + rx?: number; + ry?: number; + xAxisRotation?: number; + largeArcFlag?: boolean; + sweepFlag?: boolean; + } + ``` + + Optional parameters associated with the path command. + + Type declaration + + * Optionalx?: number + + The x-coordinate of the point (or endpoint). + * Optionaly?: number + + The y-coordinate of the point (or endpoint). + * Optionalc1x?: number + + The x-coordinate of the first control point for curves. + * Optionalc1y?: number + + The y-coordinate of the first control point for curves. + * Optionalc2x?: number + + The x-coordinate of the second control point for curves. + * Optionalc2y?: number + + The y-coordinate of the second control point for curves. + * Optionalrx?: number + + The radius of the ellipse's x-axis. + * Optionalry?: number + + The radius of the ellipse's y-axis. + * OptionalxAxisRotation?: number + + The rotation angle of the ellipse's x-axis. + * OptionallargeArcFlag?: boolean + + A flag indicating whether to use the larger arc. + * OptionalsweepFlag?: boolean + + A flag indicating the direction of the arc. +PluginData: + overview: |- + Interface PluginData + ==================== + + Provides methods for managing plugin-specific data associated with a Penpot shape. + + ``` + interface PluginData { + 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) + + * PluginData + + File + + Library + + LibraryElement + + Page + + ShapeBase + members: + Methods: + 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); + ``` +PreviousScreen: + overview: |- + Interface PreviousScreen + ======================== + + It takes back to the last board shown. + + ``` + interface PreviousScreen { + type: "previous-screen"; + } + ``` + + Referenced by: Action + members: + Properties: + type: |- + ``` + readonly type + ``` + + The action type +Push: + overview: |- + Interface Push + ============== + + Push animation + + ``` + interface Push { + type: "push"; + direction: + | "left" + | "right" + | "up" + | "down"; + duration: number; + easing?: + | "linear" + | "ease" + | "ease-in" + | "ease-out" + | "ease-in-out"; + } + ``` + + Referenced by: Animation + members: + Properties: + type: |- + ``` + readonly type + ``` + + Type of the animation + direction: |- + ``` + readonly direction: + | "left" + | "right" + | "up" + | "down" + ``` + + Direction for the push animation + duration: |- + ``` + readonly duration: number + ``` + + Duration of the animation effect + easing: |- + ``` + readonly easing?: + | "linear" + | "ease" + | "ease-in" + | "ease-out" + | "ease-in-out" + ``` + + Function that the dissolve effect will follow for the interpolation. + Defaults to `linear` +Rectangle: + overview: |- + Interface Rectangle + =================== + + Represents a rectangle shape in Penpot. + This interface extends `ShapeBase` and includes properties specific to rectangles. + + ``` + interface Rectangle { + 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[]; + type: "rectangle"; + fills: Fill[]; + 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) + + * ShapeBase + + Rectangle + + Referenced by: Context, ContextTypesUtils, Penpot, Shape + members: + Properties: + type: |- + ``` + readonly type + ``` + + The type of the shape, which is always 'rect' for rectangle shapes. + fills: |- + ``` + fills: Fill[] + ``` + + The fills applied to the shape. + + Overrides ShapeBase.fills + id: |- + ``` + readonly id: string + ``` + + The unique identifier of the shape. + name: |- + ``` + name: string + ``` + + The name of the shape. + parent: |- + ``` + readonly 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: |- + ``` + readonly 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: |- + ``` + readonly width: number + ``` + + The width of the shape. + height: |- + ``` + readonly height: number + ``` + + The height of the shape. + bounds: |- + ``` + readonly bounds: Bounds + ``` + + Returns + + Returns the bounding box surrounding the current shape + center: |- + ``` + readonly 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: |- + ``` + readonly layoutChild?: LayoutChildProperties + ``` + + Layout properties for children of the shape. + layoutCell: |- + ``` + readonly layoutCell?: LayoutChildProperties + ``` + + Layout properties for cells in a grid layout. + interactions: |- + ``` + readonly interactions: Interaction[] + ``` + + The interactions for the current shape. + Methods: + 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 + ``` + + * 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 +RulerGuide: + overview: |- + Interface RulerGuide + ==================== + + Represents a ruler guide. These are horizontal or vertical lines that can be + used to position elements in the UI. + + ``` + interface RulerGuide { + orientation: RulerGuideOrientation; + position: number; + board?: Board; + } + ``` + + Referenced by: Board, Page, VariantContainer + members: + Properties: + orientation: |- + ``` + readonly orientation: RulerGuideOrientation + ``` + + `orientation` indicates whether the ruler is either `horizontal` or `vertical` + position: |- + ``` + position: number + ``` + + `position` is the position in the axis in absolute positioning. If this is a board + guide will return the positioning relative to the board. + board: |- + ``` + board?: Board + ``` + + If the guide is attached to a board this will retrieve the board shape +Shadow: + overview: |- + Interface Shadow + ================ + + Represents shadow properties in Penpot. + This interface includes properties for defining drop shadows and inner shadows, along with their visual attributes. + + ``` + interface Shadow { + id?: string; + style?: "drop-shadow" | "inner-shadow"; + offsetX?: number; + offsetY?: number; + blur?: number; + spread?: number; + hidden?: boolean; + color?: Color; + } + ``` + + Referenced by: Board, Boolean, Ellipse, Group, Image, Path, Rectangle, ShapeBase, SvgRaw, Text, VariantContainer + members: + Properties: + id: |- + ``` + id?: string + ``` + + The optional unique identifier for the shadow. + style: |- + ``` + style?: "drop-shadow" | "inner-shadow" + ``` + + The optional style of the shadow. + + * 'drop-shadow': A shadow cast outside the element. + * 'inner-shadow': A shadow cast inside the element. + offsetX: |- + ``` + offsetX?: number + ``` + + The optional X-axis offset of the shadow. + offsetY: |- + ``` + offsetY?: number + ``` + + The optional Y-axis offset of the shadow. + blur: |- + ``` + blur?: number + ``` + + The optional blur radius of the shadow. + spread: |- + ``` + spread?: number + ``` + + The optional spread radius of the shadow. + hidden: |- + ``` + hidden?: boolean + ``` + + Specifies whether the shadow is hidden. + Defaults to false if omitted. + color: |- + ``` + color?: Color + ``` + + The optional color of the shadow, defined by a Color object. +ShapeBase: + overview: |- + Interface ShapeBase + =================== + + Represents the base properties and methods of a shape in Penpot. + This interface provides common properties and methods shared by all shapes. + + ``` + interface ShapeBase { + 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; + fills: Fill[] | "mixed"; + 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) + + * PluginData + + ShapeBase + - Board + - Boolean + - Ellipse + - Group + - Image + - Path + - Rectangle + - SvgRaw + - Text + members: + Properties: + id: |- + ``` + readonly id: string + ``` + + The unique identifier of the shape. + name: |- + ``` + name: string + ``` + + The name of the shape. + parent: |- + ``` + readonly 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: |- + ``` + readonly 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: |- + ``` + readonly width: number + ``` + + The width of the shape. + height: |- + ``` + readonly height: number + ``` + + The height of the shape. + bounds: |- + ``` + readonly bounds: Bounds + ``` + + Returns + + Returns the bounding box surrounding the current shape + center: |- + ``` + readonly 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. + fills: |- + ``` + fills: Fill[] | "mixed" + ``` + + The fills applied to the shape. + strokes: |- + ``` + strokes: Stroke[] + ``` + + The strokes applied to the shape. + layoutChild: |- + ``` + readonly layoutChild?: LayoutChildProperties + ``` + + Layout properties for children of the shape. + layoutCell: |- + ``` + readonly layoutCell?: LayoutChildProperties + ``` + + Layout properties for cells in a grid layout. + interactions: |- + ``` + readonly interactions: Interaction[] + ``` + + The interactions for the current shape. + Methods: + 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 + ``` + + * 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 +Slide: + overview: |- + Interface Slide + =============== + + Slide animation + + ``` + interface Slide { + type: "slide"; + way: "in" | "out"; + direction: + | "left" + | "right" + | "up" + | "down"; + duration: number; + offsetEffect?: boolean; + easing?: + | "linear" + | "ease" + | "ease-in" + | "ease-out" + | "ease-in-out"; + } + ``` + + Referenced by: Animation + members: + Properties: + type: |- + ``` + readonly type + ``` + + Type of the animation. + way: |- + ``` + readonly way: "in" | "out" + ``` + + Indicate if the slide will be either in-to-out `in` or out-to-in `out`. + direction: |- + ``` + readonly direction: + | "left" + | "right" + | "up" + | "down" + ``` + + Direction for the slide animation. + duration: |- + ``` + readonly duration: number + ``` + + Duration of the animation effect. + offsetEffect: |- + ``` + readonly offsetEffect?: boolean + ``` + + If `true` the offset effect will be used. + easing: |- + ``` + readonly easing?: + | "linear" + | "ease" + | "ease-in" + | "ease-out" + | "ease-in-out" + ``` + + Function that the dissolve effect will follow for the interpolation. + Defaults to `linear`. +Stroke: + overview: |- + Interface Stroke + ================ + + Represents stroke properties in Penpot. You can add a stroke to any shape except for groups. + This interface includes properties for defining the color, style, width, alignment, and caps of a stroke. + + ``` + interface Stroke { + strokeColor?: string; + strokeColorRefFile?: string; + strokeColorRefId?: string; + strokeOpacity?: number; + strokeStyle?: + | "svg" + | "none" + | "mixed" + | "solid" + | "dotted" + | "dashed"; + strokeWidth?: number; + strokeAlignment?: "center" | "inner" | "outer"; + strokeCapStart?: StrokeCap; + strokeCapEnd?: StrokeCap; + strokeColorGradient?: Gradient; + } + ``` + + Referenced by: Board, Boolean, Ellipse, Group, Image, LibraryColor, Path, Rectangle, ShapeBase, SvgRaw, Text, VariantContainer + members: + Properties: + strokeColor: |- + ``` + strokeColor?: string + ``` + + The optional color of the stroke, represented as a string (e.g., '#FF5733'). + strokeColorRefFile: |- + ``` + strokeColorRefFile?: string + ``` + + The optional reference to an external file for the stroke color. + strokeColorRefId: |- + ``` + strokeColorRefId?: string + ``` + + The optional reference ID within the external file for the stroke color. + strokeOpacity: |- + ``` + strokeOpacity?: number + ``` + + The optional opacity level of the stroke color, ranging from 0 (fully transparent) to 1 (fully opaque). + Defaults to 1 if omitted. + strokeStyle: |- + ``` + strokeStyle?: + | "svg" + | "none" + | "mixed" + | "solid" + | "dotted" + | "dashed" + ``` + + The optional style of the stroke. + strokeWidth: |- + ``` + strokeWidth?: number + ``` + + The optional width of the stroke. + strokeAlignment: |- + ``` + strokeAlignment?: "center" | "inner" | "outer" + ``` + + The optional alignment of the stroke relative to the shape's boundary. + strokeCapStart: |- + ``` + strokeCapStart?: StrokeCap + ``` + + The optional cap style for the start of the stroke. + strokeCapEnd: |- + ``` + strokeCapEnd?: StrokeCap + ``` + + The optional cap style for the end of the stroke. + strokeColorGradient: |- + ``` + strokeColorGradient?: Gradient + ``` + + The optional gradient stroke defined by a Gradient object. +SvgRaw: + overview: |- + Interface SvgRaw + ================ + + Represents an SVG raw shape in Penpot. + This interface extends `ShapeBase` and includes properties specific to raw SVG shapes. + + ``` + interface SvgRaw { + 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; + fills: Fill[] | "mixed"; + 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; + type: "svg-raw"; + } + ``` + + Hierarchy (view full) + + * ShapeBase + + SvgRaw + + Referenced by: ContextTypesUtils, Shape + members: + Properties: + id: |- + ``` + readonly id: string + ``` + + The unique identifier of the shape. + name: |- + ``` + name: string + ``` + + The name of the shape. + parent: |- + ``` + readonly 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: |- + ``` + readonly 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: |- + ``` + readonly width: number + ``` + + The width of the shape. + height: |- + ``` + readonly height: number + ``` + + The height of the shape. + bounds: |- + ``` + readonly bounds: Bounds + ``` + + Returns + + Returns the bounding box surrounding the current shape + center: |- + ``` + readonly 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. + fills: |- + ``` + fills: Fill[] | "mixed" + ``` + + The fills applied to the shape. + strokes: |- + ``` + strokes: Stroke[] + ``` + + The strokes applied to the shape. + layoutChild: |- + ``` + readonly layoutChild?: LayoutChildProperties + ``` + + Layout properties for children of the shape. + layoutCell: |- + ``` + readonly layoutCell?: LayoutChildProperties + ``` + + Layout properties for cells in a grid layout. + interactions: |- + ``` + readonly interactions: Interaction[] + ``` + + The interactions for the current shape. + type: |- + ``` + type + ``` + Methods: + 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 + ``` + + * 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 +Text: + overview: |- + Interface Text + ============== + + Text represents a text element in the Penpot application, extending the base shape interface. + It includes various properties to define the text content and its styling attributes. + + ``` + interface Text { + 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; + fills: Fill[] | "mixed"; + 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; + type: "text"; + characters: string; + growType: "fixed" | "auto-width" | "auto-height"; + fontId: string; + fontFamily: string; + fontVariantId: string; + fontSize: string; + fontWeight: string; + fontStyle: + | null + | "normal" + | "italic" + | "mixed"; + lineHeight: string; + letterSpacing: string; + textTransform: + | null + | "mixed" + | "uppercase" + | "capitalize" + | "lowercase"; + textDecoration: + | null + | "mixed" + | "underline" + | "line-through"; + direction: + | null + | "mixed" + | "ltr" + | "rtl"; + align: + | null + | "center" + | "left" + | "right" + | "mixed" + | "justify"; + verticalAlign: + | null + | "center" + | "top" + | "bottom"; + getRange(start: number, end: number): TextRange; + applyTypography(typography: LibraryTypography): void; + } + ``` + + Hierarchy (view full) + + * ShapeBase + + Text + + Referenced by: Context, ContextTypesUtils, Font, Penpot, Shape, TextRange + members: + Properties: + id: |- + ``` + readonly id: string + ``` + + The unique identifier of the shape. + name: |- + ``` + name: string + ``` + + The name of the shape. + parent: |- + ``` + readonly 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: |- + ``` + readonly 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: |- + ``` + readonly width: number + ``` + + The width of the shape. + height: |- + ``` + readonly height: number + ``` + + The height of the shape. + bounds: |- + ``` + readonly bounds: Bounds + ``` + + Returns + + Returns the bounding box surrounding the current shape + center: |- + ``` + readonly 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. + fills: |- + ``` + fills: Fill[] | "mixed" + ``` + + The fills applied to the shape. + strokes: |- + ``` + strokes: Stroke[] + ``` + + The strokes applied to the shape. + layoutChild: |- + ``` + readonly layoutChild?: LayoutChildProperties + ``` + + Layout properties for children of the shape. + layoutCell: |- + ``` + readonly layoutCell?: LayoutChildProperties + ``` + + Layout properties for cells in a grid layout. + interactions: |- + ``` + readonly interactions: Interaction[] + ``` + + The interactions for the current shape. + type: |- + ``` + readonly type + ``` + + The type of the shape, which is always 'text' for text shapes. + characters: |- + ``` + characters: string + ``` + + The characters contained within the text shape. + growType: |- + ``` + growType: "fixed" | "auto-width" | "auto-height" + ``` + + The grow type of the text shape, defining how the text box adjusts its size. + Possible values are: + + * 'fixed': Fixed size. + * 'auto-width': Adjusts width automatically. + * 'auto-height': Adjusts height automatically. + fontId: |- + ``` + fontId: string + ``` + + The font ID used in the text shape, or 'mixed' if multiple fonts are used. + fontFamily: |- + ``` + fontFamily: string + ``` + + The font family used in the text shape, or 'mixed' if multiple font families are used. + fontVariantId: |- + ``` + fontVariantId: string + ``` + + The font variant ID used in the text shape, or 'mixed' if multiple font variants are used. + fontSize: |- + ``` + fontSize: string + ``` + + The font size used in the text shape, or 'mixed' if multiple font sizes are used. + fontWeight: |- + ``` + fontWeight: string + ``` + + The font weight used in the text shape, or 'mixed' if multiple font weights are used. + fontStyle: |- + ``` + fontStyle: + | null + | "normal" + | "italic" + | "mixed" + ``` + + The font style used in the text shape, or 'mixed' if multiple font styles are used. + lineHeight: |- + ``` + lineHeight: string + ``` + + The line height used in the text shape, or 'mixed' if multiple line heights are used. + letterSpacing: |- + ``` + letterSpacing: string + ``` + + The letter spacing used in the text shape, or 'mixed' if multiple letter spacings are used. + textTransform: |- + ``` + textTransform: + | null + | "mixed" + | "uppercase" + | "capitalize" + | "lowercase" + ``` + + The text transform applied to the text shape, or 'mixed' if multiple text transforms are used. + textDecoration: |- + ``` + textDecoration: + | null + | "mixed" + | "underline" + | "line-through" + ``` + + The text decoration applied to the text shape, or 'mixed' if multiple text decorations are used. + direction: |- + ``` + direction: + | null + | "mixed" + | "ltr" + | "rtl" + ``` + + The text direction for the text shape, or 'mixed' if multiple directions are used. + align: |- + ``` + align: + | null + | "center" + | "left" + | "right" + | "mixed" + | "justify" + ``` + + The horizontal alignment of the text shape. It can be a specific alignment or 'mixed' if multiple alignments are used. + verticalAlign: |- + ``` + verticalAlign: + | null + | "center" + | "top" + | "bottom" + ``` + + The vertical alignment of the text shape. It can be a specific alignment or 'mixed' if multiple alignments are used. + Methods: + 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 + ``` + + * 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 + getRange: |- + ``` + getRange(start, end): TextRange + ``` + + * Gets a text range within the text shape. + + Parameters + + start: number + + The start index of the text range. + + end: number + + The end index of the text range. + + Returns TextRange + + Returns a TextRange object representing the specified text range. + + Example + ``` + const textRange = textShape.getRange(0, 10);console.log(textRange.characters); + ``` + applyTypography: |- + ``` + applyTypography(typography): void + ``` + + * Applies a typography style to the text shape. + + Parameters + + typography: LibraryTypography + + The typography style to apply. + + Returns void + + Remarks + + This method sets various typography properties for the text shape according to the given typography style. + + Example + ``` + textShape.applyTypography(typography); + ``` +TextRange: + overview: |- + Interface TextRange + =================== + + Represents a range of text within a Text shape. + This interface provides properties for styling and formatting text ranges. + + ``` + interface TextRange { + shape: Text; + characters: string; + fontId: string; + fontFamily: string; + fontVariantId: string; + fontSize: string; + fontWeight: string; + fontStyle: + | null + | "normal" + | "italic" + | "mixed"; + lineHeight: string; + letterSpacing: string; + textTransform: + | null + | "none" + | "mixed" + | "uppercase" + | "capitalize" + | "lowercase"; + textDecoration: + | null + | "none" + | "mixed" + | "underline" + | "line-through"; + direction: + | null + | "mixed" + | "ltr" + | "rtl"; + fills: Fill[] | "mixed"; + align: + | null + | "center" + | "left" + | "right" + | "mixed" + | "justify"; + verticalAlign: + | null + | "center" + | "top" + | "bottom" + | "mixed"; + applyTypography(typography: LibraryTypography): void; + } + ``` + + Referenced by: Font, LibraryTypography, Text + members: + Properties: + shape: |- + ``` + readonly shape: Text + ``` + + The Text shape to which this text range belongs. + characters: |- + ``` + readonly characters: string + ``` + + The characters associated with the current text range. + fontId: |- + ``` + fontId: string + ``` + + The font ID of the text range. It can be a specific font ID or 'mixed' if multiple fonts are used. + fontFamily: |- + ``` + fontFamily: string + ``` + + The font family of the text range. It can be a specific font family or 'mixed' if multiple font families are used. + fontVariantId: |- + ``` + fontVariantId: string + ``` + + The font variant ID of the text range. It can be a specific font variant ID or 'mixed' if multiple font variants are used. + fontSize: |- + ``` + fontSize: string + ``` + + The font size of the text range. It can be a specific font size or 'mixed' if multiple font sizes are used. + fontWeight: |- + ``` + fontWeight: string + ``` + + The font weight of the text range. It can be a specific font weight or 'mixed' if multiple font weights are used. + fontStyle: |- + ``` + fontStyle: + | null + | "normal" + | "italic" + | "mixed" + ``` + + The font style of the text range. It can be a specific font style or 'mixed' if multiple font styles are used. + lineHeight: |- + ``` + lineHeight: string + ``` + + The line height of the text range. It can be a specific line height or 'mixed' if multiple line heights are used. + letterSpacing: |- + ``` + letterSpacing: string + ``` + + The letter spacing of the text range. It can be a specific letter spacing or 'mixed' if multiple letter spacings are used. + textTransform: |- + ``` + textTransform: + | null + | "none" + | "mixed" + | "uppercase" + | "capitalize" + | "lowercase" + ``` + + The text transform applied to the text range. It can be a specific text transform or 'mixed' if multiple text transforms are used. + textDecoration: |- + ``` + textDecoration: + | null + | "none" + | "mixed" + | "underline" + | "line-through" + ``` + + The text decoration applied to the text range. It can be a specific text decoration or 'mixed' if multiple text decorations are used. + direction: |- + ``` + direction: + | null + | "mixed" + | "ltr" + | "rtl" + ``` + + The text direction for the text range. It can be a specific direction or 'mixed' if multiple directions are used. + fills: |- + ``` + fills: Fill[] | "mixed" + ``` + + The fill styles applied to the text range. + align: |- + ``` + align: + | null + | "center" + | "left" + | "right" + | "mixed" + | "justify" + ``` + + The horizontal alignment of the text range. It can be a specific alignment or 'mixed' if multiple alignments are used. + verticalAlign: |- + ``` + verticalAlign: + | null + | "center" + | "top" + | "bottom" + | "mixed" + ``` + + The vertical alignment of the text range. It can be a specific alignment or 'mixed' if multiple alignments are used. + Methods: + applyTypography: |- + ``` + applyTypography(typography): void + ``` + + * Applies a typography style to the text range. + This method sets various typography properties for the text range according to the given typography style. + + Parameters + + typography: LibraryTypography + + The typography style to apply. + + Returns void + + Example + ``` + textRange.applyTypography(typography); + ``` +ToggleOverlay: + overview: |- + Interface ToggleOverlay + ======================= + + It opens an overlay if it is not already opened or closes it if it is already opened. + + ``` + interface ToggleOverlay { + destination: Board; + relativeTo?: Shape; + position?: + | "center" + | "manual" + | "top-left" + | "top-right" + | "top-center" + | "bottom-left" + | "bottom-right" + | "bottom-center"; + manualPositionLocation?: Point; + closeWhenClickOutside?: boolean; + addBackgroundOverlay?: boolean; + animation?: Animation; + type: "toggle-overlay"; + } + ``` + + Hierarchy (view full) + + * OverlayAction + + ToggleOverlay + + Referenced by: Action + members: + Properties: + destination: |- + ``` + readonly destination: Board + ``` + + Overlay board that will be opened. + relativeTo: |- + ``` + readonly relativeTo?: Shape + ``` + + Base shape to which the overlay will be positioned taking constraints into account. + position: |- + ``` + readonly position?: + | "center" + | "manual" + | "top-left" + | "top-right" + | "top-center" + | "bottom-left" + | "bottom-right" + | "bottom-center" + ``` + + Positioning of the overlay. + manualPositionLocation: |- + ``` + readonly manualPositionLocation?: Point + ``` + + For `position = 'manual'` the location of the overlay. + closeWhenClickOutside: |- + ``` + readonly closeWhenClickOutside?: boolean + ``` + + When true the overlay will be closed when clicking outside + addBackgroundOverlay: |- + ``` + readonly addBackgroundOverlay?: boolean + ``` + + When true a background will be added to the overlay. + animation: |- + ``` + readonly animation?: Animation + ``` + + Animation displayed with this interaction. + type: |- + ``` + readonly type + ``` + + The action type +Track: + overview: |- + Interface Track + =============== + + Represents a track configuration in Penpot. + This interface includes properties for defining the type and value of a track used in layout configurations. + + ``` + interface Track { + type: TrackType; + value: null | number; + } + ``` + + Referenced by: GridLayout + members: + Properties: + type: |- + ``` + type: TrackType + ``` + + The type of the track. + This can be one of the following values: + + * 'flex': A flexible track type. + * 'fixed': A fixed track type. + * 'percent': A track type defined by a percentage. + * 'auto': An automatic track type. + value: |- + ``` + value: null | number + ``` + + The value of the track. + This can be a number representing the size of the track, or null if not applicable. +User: + overview: |- + Interface User + ============== + + Represents a user in Penpot. + + ``` + interface User { + id: string; + name?: string; + avatarUrl?: string; + color: string; + sessionId?: string; + } + ``` + + Hierarchy (view full) + + * User + + ActiveUser + + Referenced by: Comment, CommentThread, Context, File, FileVersion, Penpot + members: + Properties: + id: |- + ``` + readonly id: string + ``` + + The unique identifier of the user. + + Example + ``` + const userId = user.id;console.log(userId); + ``` + name: |- + ``` + readonly name?: string + ``` + + The name of the user. + + Example + ``` + const userName = user.name;console.log(userName); + ``` + avatarUrl: |- + ``` + readonly avatarUrl?: string + ``` + + The URL of the user's avatar image. + + Example + ``` + const avatarUrl = user.avatarUrl;console.log(avatarUrl); + ``` + color: |- + ``` + readonly color: string + ``` + + The color associated with the user. + + Example + ``` + const userColor = user.color;console.log(userColor); + ``` + sessionId: |- + ``` + readonly sessionId?: string + ``` + + The session ID of the user. + + Example + ``` + 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: |- + ``` + readonly 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: |- + ``` + readonly 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 + ================== + + Viewport represents the viewport in the Penpot application. + It includes the center point, zoom level, and the bounds of the viewport. + + ``` + interface Viewport { + center: Point; + zoom: number; + bounds: Bounds; + zoomReset(): void; + zoomToFitAll(): void; + zoomIntoView(shapes: Shape[]): void; + } + ``` + + Referenced by: Context, Penpot + members: + Properties: + center: |- + ``` + center: Point + ``` + + the `center` point of the current viewport. If changed will change the + viewport position. + zoom: |- + ``` + zoom: number + ``` + + the `zoom` level as a number where `1` represents 100%. + bounds: |- + ``` + readonly bounds: Bounds + ``` + + the `bounds` are the current coordinates of the viewport. + Methods: + zoomReset: |- + ``` + zoomReset(): void + ``` + + * Resets the zoom level. + + Returns void + zoomToFitAll: |- + ``` + zoomToFitAll(): void + ``` + + * Changes the viewport and zoom so can fit all the current shapes in the page. + + Returns void + zoomIntoView: |- + ``` + zoomIntoView(shapes): void + ``` + + * Changes the viewport and zoom so all the `shapes` in the argument are + visible. + + Parameters + + shapes: Shape[] + + Returns void +Action: + overview: |- + Type Alias Action + ================= + + ``` + Action: + | NavigateTo + | OpenOverlay + | ToggleOverlay + | CloseOverlay + | PreviousScreen + | OpenUrl + ``` + + Type for all the possible types of actions in an interaction. + + Referenced by: Board, Boolean, Ellipse, Group, Image, Interaction, Path, Rectangle, ShapeBase, SvgRaw, Text, VariantContainer + members: {} +Animation: + overview: |- + Type Alias Animation + ==================== + + ``` + Animation: Dissolve | Slide | Push + ``` + + Type of all the animations that can be added to an interaction. + + Referenced by: CloseOverlay, NavigateTo, OpenOverlay, OverlayAction, ToggleOverlay + members: {} +BooleanType: + overview: |- + Type Alias BooleanType + ====================== + + ``` + BooleanType: + | "union" + | "difference" + | "exclude" + | "intersection" + ``` + + Represents the boolean operation types available in Penpot. + These types define how shapes can be combined or modified using boolean operations. + + Referenced by: Context, Penpot + members: {} +Bounds: + overview: |- + Type Alias Bounds + ================= + + ``` + Bounds: { + x: number; + y: number; + width: number; + height: number; + } + ``` + + Bounds represents the boundaries of a rectangular area, + defined by the coordinates of the top-left corner and the dimensions of the rectangle. + + Type declaration + + * x: number + + Top-left x position of the rectangular area defined + * y: number + + Top-left y position of the rectangular area defined + * width: number + + Width of the represented area + * height: number + + Height of the represented area + + Example + ``` + 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, VariantContainer, Viewport + members: {} +Gradient: + overview: |- + Type Alias Gradient + =================== + + ``` + Gradient: { + type: "linear" | "radial"; + startX: number; + startY: number; + endX: number; + endY: number; + width: number; + stops: { + color: string; + opacity?: number; + offset: number; + }[]; + } + ``` + + Represents a gradient configuration in Penpot. + A gradient can be either linear or radial and includes properties to define its shape, position, and color stops. + + Type declaration + + * type: "linear" | "radial" + + Specifies the type of gradient. + + + 'linear': A gradient that transitions colors along a straight line. + + 'radial': A gradient that transitions colors radiating outward from a central point. + + Example + ``` + const gradient: Gradient = { type: 'linear', startX: 0, startY: 0, endX: 100, endY: 100, width: 100, stops: [{ color: '#FF5733', offset: 0 }] }; + ``` + * startX: number + + The X-coordinate of the starting point of the gradient. + * startY: number + + The Y-coordinate of the starting point of the gradient. + * endX: number + + The X-coordinate of the ending point of the gradient. + * endY: number + + The Y-coordinate of the ending point of the gradient. + * width: number + + The width of the gradient. For radial gradients, this could be interpreted as the radius. + * stops: { color: string; opacity?: number; offset: number; }[] + + An array of color stops that define the gradient. + + Referenced by: Color, Fill, LibraryColor, Stroke + members: {} +Guide: + overview: |- + Type Alias Guide + ================ + + ``` + Guide: GuideColumn | GuideRow | GuideSquare + ``` + + Represents a board guide in Penpot. + This type can be one of several specific board guide types: column, row, or square. + + Referenced by: Board, VariantContainer + members: {} +ImageData: + overview: |- + Type Alias ImageData + ==================== + + ``` + ImageData: { + name?: string; + width: number; + height: number; + mtype?: string; + id: string; + keepAspectRatio?: boolean; + data(): Promise; + } + ``` + + Represents image data in Penpot. + This includes properties for defining the image's dimensions, metadata, and aspect ratio handling. + + Type declaration + + * Optionalname?: string + + The optional name of the image. + * width: number + + The width of the image. + * height: number + + The height of the image. + * Optionalmtype?: string + + The optional media type of the image (e.g., 'image/png', 'image/jpeg'). + * id: string + + The unique identifier for the image. + * OptionalkeepAspectRatio?: boolean + + 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: {} +LibraryContext: + overview: |- + Type Alias LibraryContext + ========================= + + ``` + LibraryContext: { + local: Library; + connected: Library[]; + availableLibraries(): Promise; + connectLibrary(libraryId: string): Promise; + } + ``` + + Represents the context of Penpot libraries, including both local and connected libraries. + This type contains references to the local library and an array of connected libraries. + + Type declaration + + * Readonlylocal: Library + + The local library in the Penpot context. + + Example + ``` + const localLibrary = libraryContext.local; + ``` + * Readonlyconnected: Library[] + + An array of connected libraries in the Penpot context. + + Example + ``` + const connectedLibraries = libraryContext.connected; + ``` + * availableLibraries:function + + ``` + availableLibraries(): Promise + ``` + + + Retrieves a summary of available libraries that can be connected to. + + Returns Promise + + Returns a promise that resolves to an array of `LibrarySummary` objects representing available libraries. + + Example + ``` + const availableLibraries = await libraryContext.availableLibraries(); + ``` + * connectLibrary:function + + ``` + connectLibrary(libraryId): Promise + ``` + + + Connects to a specific library identified by its ID. + + Parameters + - libraryId: string + + The ID of the library to connect to. + + Returns Promise + + Returns a promise that resolves to the `Library` object representing the connected library. + + Example + ``` + const connectedLibrary = await libraryContext.connectLibrary('library-id'); + ``` + + Referenced by: Context, Penpot + members: {} +Point: + overview: |- + Type Alias Point + ================ + + ``` + Point: { + x: number; + y: number; + } + ``` + + 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, VariantContainer, Viewport + members: {} +RulerGuideOrientation: + overview: |- + Type Alias RulerGuideOrientation + ================================ + + ``` + RulerGuideOrientation: "horizontal" | "vertical" + ``` + + Referenced by: Board, Page, RulerGuide, VariantContainer + members: {} +Shape: + overview: |- + Type Alias Shape + ================ + + ``` + Shape: + | Board + | Group + | Boolean + | Rectangle + | Path + | Text + | Ellipse + | SvgRaw + | Image + ``` + + Shape represents a union of various shape types used in the Penpot project. + This type allows for different shapes to be handled under a single type umbrella. + + Example + ``` + 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, LibraryVariantComponent, OpenOverlay, OverlayAction, Page, Path, Penpot, Rectangle, ShapeBase, SvgRaw, Text, ToggleOverlay, VariantContainer, Viewport + members: {} +StrokeCap: + overview: |- + Type Alias StrokeCap + ==================== + + ``` + StrokeCap: + | "round" + | "square" + | "line-arrow" + | "triangle-arrow" + | "square-marker" + | "circle-marker" + | "diamond-marker" + ``` + + Represents the cap style of a stroke in Penpot. + This type defines various styles for the ends of a stroke. + + Referenced by: Stroke + members: {} +Theme: + overview: |- + Type Alias Theme + ================ + + ``` + Theme: "light" | "dark" + ``` + + This type specifies the possible themes: 'light' or 'dark'. + + Referenced by: Context, EventsMap, Penpot + members: {} +TrackType: + overview: |- + Type Alias TrackType + ==================== + + ``` + TrackType: + | "flex" + | "fixed" + | "percent" + | "auto" + ``` + + Represents the type of track in Penpot. + This type defines various track types that can be used in layout configurations. + + Referenced by: GridLayout, Track + members: {} +Trigger: + overview: |- + Type Alias Trigger + ================== + + ``` + Trigger: + | "click" + | "mouse-enter" + | "mouse-leave" + | "after-delay" + ``` + + Types of triggers defined: + + * `click` triggers when the user uses the mouse to click on a shape + * `mouse-enter` triggers when the user moves the mouse inside the shape (even if no mouse button is pressed) + * `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, VariantContainer + members: {} diff --git a/mcp/packages/server/data/prompts.yml b/mcp/packages/server/data/prompts.yml new file mode 100644 index 0000000000..dabed7a590 --- /dev/null +++ b/mcp/packages/server/data/prompts.yml @@ -0,0 +1,267 @@ +# Prompts configuration for Penpot MCP Server +# This file contains various prompts and instructions that can be used by the server + +initial_instructions: | + You have access to Penpot tools in order to interact with a Penpot design project directly. + As a precondition, the user must connect the Penpot design project to the MCP server using the Penpot MCP Plugin. + + IMPORTANT: When transferring styles from a Penpot design to code, make sure that you strictly adhere to the design. + NEVER make assumptions about missing values and don't get overly creative (e.g. don't pick your own colours and stick to + non-creative defaults such as white/black if you are lacking information). + + # Executing Code + + One of your key tools is the `execute_code` tool, which allows you to run JavaScript code using the Penpot Plugin API + directly in the connected project. + + VERY IMPORTANT: When writing code, NEVER LOG INFORMATION YOU ARE ALSO RETURNING. It would duplicate the information you receive! + + To execute code correctly, you need to understand the Penpot Plugin API. You can retrieve API documentation via + the `penpot_api_info` tool. + + This is the full list of types/interfaces in the Penpot API: $api_types + + You use the `storage` object extensively to store data and utility functions you define across tool calls. + This allows you to inspect intermediate results while still being able to build on them in subsequent code executions. + + # The Structure of Penpot Designs + + A Penpot design ultimately consists of shapes. + The type `Shape` is a union type, which encompasses both containers and low-level shapes. + Shapes in a Penpot design are organized hierarchically. + At the top level, a design project contains one or more `Page` objects. + Each `Page` contains a tree of elements. For a given instance `page`, its root shape is `page.root`. + A Page is frequently structured into boards. A `Board` is a high-level grouping element. + A `Group` is a more low-level grouping element used to organize low-level shapes into a logical unit. + Actual low-level shape types are `Rectangle`, `Path`, `Text`, `Ellipse`, `Image`, `Boolean`, and `SvgRaw`. + `ShapeBase` is a base type most shapes build upon. + + # Core Shape Properties and Methods + + **Type**: + Any given shape contains information on the concrete type via its `type` field. + + **Position and Dimensions**: + * The location properties `x` and `y` refer to the top left corner of a shape's bounding box in the absolute (Page) coordinate system. + These are writable - set them directly to position shapes. + * `parentX` and `parentY` (as well as `boardX` and `boardY`) are READ-ONLY computed properties showing position relative to parent/board. + To position relative to parent, use `penpotUtils.setParentXY(shape, parentX, parentY)` or manually set `shape.x = parent.x + parentX`. + * `width` and `height` are READ-ONLY. Use `resize(width, height)` method to change dimensions. + * `bounds` is a READ-ONLY property. Use `x`, `y` with `resize()` to modify shape bounds. + + **Other Writable Properties**: + * `name` - Shape name + * `fills`, `strokes` - Styling properties + * `rotation`, `opacity`, `blocked`, `hidden`, `visible` + + **Z-Order**: + * 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). + CRITICAL: NEVER use the broken function `appendChild` to achieve this, ALWAYS use `parent.insertChild(parent.children.length, shape)` + * To modify z-order after creation, use these methods: `bringToFront()`, `sendToBack()`, `bringForward()`, `sendBackward()`, + and, for precise control, `setParentIndex(index)` (0-based). + + **Modification Methods**: + * `resize(width, height)` - Change dimensions (required for width/height since they're read-only) + * `rotate(angle, center?)` - Rotate shape + * `remove()` - Permanently destroy the shape (use only for deletion, NOT for reparenting) + + **Hierarchical Structure**: + * `parent` - The parent shape (null for root shapes) + Note: Hierarchical nesting does not necessarily imply visual containment + * CRITICAL: To add children to a parent shape (e.g. a `Board`): + - ALWAYS use `parent.insertChild(index, shape)` to add a child, e.g. `parent.insertChild(parent.children.length, shape)` to append + - NEVER use `parent.appendChild(shape)` as it is BROKEN and will not insert in a predictable place (except in flex layout boards) + * Reparenting: `newParent.appendChild(shape)` or `newParent.insertChild(index, shape)` will move a shape to new parent + - Automatically removes the shape from its old parent + - Absolute x/y positions are preserved (use `penpotUtils.setParentXY` to adjust relative position) + + # Images + + The `Image` type is a legacy type. Images are now typically embedded in a `Fill`, with `fillImage` set to an + `ImageData` object, i.e. the `fills` property of of a shape (e.g. a `Rectangle`) will contain a fill where `fillImage` is set. + Use the `export_shape` and `import_image` tools to export and import images. + + # Layout Systems + + Boards can have layout systems that automatically control the positioning and spacing of their children: + + * If a board has a layout system, then child positions are controlled by the layout system. + For every child, key properties of the child within the layout are stored in `child.layoutChild: LayoutChildProperties`: + - `absolute: boolean` - if true, child position is not controlled by layout system. x/y will set *relative* position within parent! + - margins (`topMargin`, `rightMargin`, `bottomMargin`, `leftMargin` or combined `verticalMargin`, `horizontalMargin`) + - sizing (`verticalSizing`, `horizontalSizing`: "fill" | "auto" | "fix") + - min/max sizes (`minWidth`, `maxWidth`, `minHeight`, `maxHeight`) + - `zIndex: number` (higher numbers on top) + + * **Flex Layout**: A flexbox-style layout system + - Properties: `dir`, `rowGap`, `columnGap`, `alignItems`, `justifyContent`; + - `dir`: "row" | "column" | "row-reverse" | "column-reverse" + - Padding: `topPadding`, `rightPadding`, `bottomPadding`, `leftPadding`, or combined `verticalPadding`, `horizontalPadding` + - To modify spacing: adjust `rowGap` and `columnGap` properties, not individual child positions. + Optionally, adjust indivudual child margins via `child.layoutChild`. + - When a board has flex layout, + - child positions are controlled by the layout system, not by individual x/y coordinates (unless `child.layoutChild.absolute` is true); + appending or inserting children automatically positions them according to the layout rules. + - CRITICAL: For for dir="column" or dir="row", the order of the `children` array is reversed relative to the visual order! + Therefore, the element that appears first in the array, appears visually at the end (bottom/right) and vice versa. + ALWAYS BEAR IN MIND THAT THE CHILDREN ARRAY ORDER IS REVERSED FOR dir="column" OR dir="row"! + - CRITICAL: The FlexLayout method `board.flex.appendChild` is BROKEN. To append children to a flex layout board such that + they appear visually at the end, ALWAYS use the Board's method `board.appendChild(shape)`; it will insert at the front + of the `children` array for dir="column" or dir="row", which is what you want. So call it in the order of visual appearance. + To insert at a specific index, use `board.insertChild(index, shape)`, bearing in mind the reversed order for dir="column" + or dir="row". + - Add to a board with `board.addFlexLayout(): FlexLayout`; instance then accessible via `board.flex`. + IMPORTANT: When adding a flex layout to a container that already has children, + use `penpotUtils.addFlexLayout(container, dir)` instead! This preserves the existing visual order of children. + Otherwise, children will be arbitrarily reordered when the children order suddenly determines the display order. + - Check with: `if (board.flex) { ... }` + + * **Grid Layout**: A CSS grid-style layout system + - Add to a board with `board.addGridLayout(): GridLayout`; instance then accessibly via `board.grid`; + Check with: `if (board.grid) { ... }` + - Properties: `rows`, `columns`, `rowGap`, `columnGap` + - Children are positioned via 1-based row/column indices + - Add to grid via `board.flex.appendChild(shape, row, column)` + - Modify grid positioning after the fact via `shape.layoutCell: LayoutCellProperties` + + * When working with boards: + - ALWAYS check if the board has a layout system before attempting to reposition children + - Modify layout properties (gaps, padding) instead of trying to set child x/y positions directly + - Layout systems override manual positioning of children + + # Text Elements + + The rendered content of `Text` element is given by the `characters` property. + + To change the size of the text, change the `fontSize` property; applying `resize()` does NOT change the font size, + it only changes the formal bounding box; if the text does not fit it, it will overflow. + The bounding box is sized automatically as long as the `growType` property is set to "auto-width" or "auto-height". + `resize` always sets `growType` to "fixed", so ALWAYS set it back to "auto-*" if you want automatic sizing - otherwise the bounding box will be meaningless, with the text overflowing! + The auto-sizing is not immediate; sleep for a short time (100ms) if you want to read the updated bounding box. + + # The `penpot` and `penpotUtils` Objects, Exploring Designs + + A key object to use in your code is the `penpot` object (which is of type `Penpot`): + * `penpot.selection` provides the list of shapes the user has selected in the Penpot UI. + If it is unclear which elements to work on, you can ask the user to select them for you. + ALWAYS immediately copy the selected shape(s) into `storage`! Do not assume that the selection remains unchanged. + * `penpot.root` provides the root shape of the currently active page. + * Generation of CSS content for elements via `penpot.generateStyle` + * Generation of HTML/SVG content for elements via `penpot.generateMarkup` + + For example, to generate CSS for the currently selected elements, you can execute this: + return penpot.generateStyle(penpot.selection, { type: "css", withChildren: true }); + + CRITICAL: The `penpotUtils` object provides essential utilities - USE THESE INSTEAD OF WRITING YOUR OWN: + * getPages(): { id: string; name: string }[] + * getPageById(id: string): Page | null + * getPageByName(name: string): Page | null + * shapeStructure(shape: Shape, maxDepth: number | undefined = undefined): { id, name, type, children?, layout? } + Generates an overview structure of the given shape. + - children: recursive, limited by maxDepth + - layout: present if shape has flex/grid layout, contains { type: "flex" | "grid", ... } + * findShapeById(id: string): Shape | null + * findShape(predicate: (shape: Shape) => boolean, root: Shape | null = null): Shape | null + If no root is provided, search globally (in all pages). + * findShapes(predicate: (shape: Shape) => boolean, root: Shape | null = null): Shape[] + * isContainedIn(shape: Shape, container: Shape): boolean + Returns true iff shape is fully within the container's geometric bounds. + Note that a shape's bounds may not always reflect its actual visual content - descendants can overflow; check using analyzeDescendants (see below). + * setParentXY(shape: Shape, parentX: number, parentY: number): void + Sets shape position relative to its parent (since parentX/parentY are read-only) + * analyzeDescendants(root: Shape, evaluator: (root: Shape, descendant: Shape) => T | null | undefined, maxDepth?: number): Array<{ shape: Shape, result: T }> + General-purpose utility for analyzing/validating descendants + Calls evaluator on each descendant; collects non-null/undefined results + Powerful pattern: evaluator can return corrector functions or diagnostic data + + General pointers for working with Penpot designs: + * Prefer `penpotUtils` helper functions — avoid reimplementing shape searching. + * To get an overview of a single page, use `penpotUtils.shapeStructure(page.root, 3)`. + Note that `penpot.root` refers to the current page only. When working across pages, first determine the relevant page(s). + * Use `penpotUtils.findShapes()` or `penpotUtils.findShape()` with predicates to locate elements efficiently. + + Common tasks - Quick Reference (ALWAYS use penpotUtils for these): + * Find all images: + const images = penpotUtils.findShapes( + shape => shape.type === 'image' || shape.fills?.some(fill => fill.fillImage), + penpot.root + ); + * Find text elements: + const texts = penpotUtils.findShapes(shape => shape.type === 'text', penpot.root); + * Find (the first) shape with a given name: + const shape = penpotUtils.findShape(shape => shape.name === 'MyShape'); + * Get structure of current selection: + const structure = penpotUtils.shapeStructure(penpot.selection[0]); + * Find shapes in current selection/board: + const shapes = penpotUtils.findShapes(predicate, penpot.selection[0] || penpot.root); + * Validate/analyze descendants (returning corrector functions): + const fixes = penpotUtils.analyzeDescendants(board, (root, shape) => { + const xMod = shape.parentX % 4; + if (xMod !== 0) { + return () => penpotUtils.setParentXY(shape, Math.round(shape.parentX / 4) * 4, shape.parentY); + } + }); + fixes.forEach(f => f.result()); // Apply all fixes + * Find containment violations: + const violations = penpotUtils.analyzeDescendants(board, (root, shape) => { + return !penpotUtils.isContainedIn(shape, root) ? 'outside-bounds' : null; + }); + Always validate against the root container that is supposed to contain the shapes. + + # Visual Inspection of Designs + + For many tasks, it can be critical to visually inspect the design. Remember to use the `export_shape` tool for this purpose! + + # Revising Designs + + * Before applying design changes, ask: "Would a designer consider this appropriate?" + * When dealing with containment issues, ask: Is the parent too small OR is the child too large? + Container sizes are usually intentional, check content first. + * Check for reasonable font sizes and typefaces + * The use of flex layouts is encouraged for cases where elements are arranged in rows or columns with consistent spacing/positioning. + Consider converting boards to flex layout when appropriate. + + # Asset Libraries + + Libraries in Penpot are collections of reusable design assets (components, colors, and typographies) that can be shared across files. + They enable design systems and consistent styling across projects. + Each Penpot file has its own local library and can connect to external shared libraries. + + Accessing libraries: via `penpot.library` (type: `LibraryContext`): + * `penpot.library.local` (type: `Library`) - The current file's own library + * `penpot.library.connected` (type: `Library[]`) - Array of already-connected external libraries + * `penpot.library.availableLibraries()` (returns: `Promise`) - Libraries available to connect + * `penpot.library.connectLibrary(libraryId: string)` (returns: `Promise`) - Connect a new library + + Each `Library` object has: + * `id: string` + * `name: string` + * `components: LibraryComponent[]` - Array of components + * `colors: LibraryColor[]` - Array of colors + * `typographies: LibraryTypography[]` - Array of typographies + + Using library components: + * find a component in the library by name: + const component: LibraryComponent = library.components.find(comp => comp.name.includes('Button')); + * create a new instance of the component on the current page: + const instance: Shape = component.instance(); + This returns a `Shape` (often a `Board` containing child elements). + After instantiation, modify the instance's properties as desired. + * get the reference to the main component shape: + const mainShape: Shape = component.mainInstance(); + + Adding assets to a library: + * const newColor: LibraryColor = penpot.library.local.createColor(); + newColor.name = 'Brand Primary'; + newColor.color = '#0066FF'; + * const newTypo: LibraryTypography = penpot.library.local.createTypography(); + newTypo.name = 'Heading Large'; + // Set typography properties... + * const shapes: Shape[] = [shape1, shape2]; // shapes to include + const newComponent: LibraryComponent = penpot.library.local.createComponent(shapes); + newComponent.name = 'My Button'; + + -- + You have hereby read the 'Penpot High-Level Overview' and need not use a tool to read it again. diff --git a/mcp/packages/server/package.json b/mcp/packages/server/package.json new file mode 100644 index 0000000000..09e47a4dce --- /dev/null +++ b/mcp/packages/server/package.json @@ -0,0 +1,54 @@ +{ + "name": "mcp-server", + "version": "1.0.0", + "description": "MCP server for Penpot integration", + "type": "module", + "main": "dist/index.js", + "scripts": { + "build:server": "esbuild src/index.ts --bundle --platform=node --target=node18 --format=esm --outfile=dist/index.js --external:@modelcontextprotocol/* --external:ws --external:express --external:class-transformer --external:class-validator --external:reflect-metadata --external:pino --external:pino-pretty --external:js-yaml --external:sharp", + "build": "pnpm run build:server && cp -r src/static dist/static && cp -r data dist/data", + "build:multi-user": "pnpm run build", + "build:types": "tsc --emitDeclarationOnly --outDir dist", + "start": "node dist/index.js", + "start:multi-user": "node dist/index.js --multi-user", + "start:dev": "node --import ts-node/register src/index.ts", + "start:dev:multi-user": "node --loader ts-node/esm src/index.ts --multi-user", + "types:check": "tsc --noEmit", + "clean": "rm -rf dist/" + }, + "keywords": [ + "mcp", + "penpot", + "server" + ], + "author": "", + "license": "MIT", + "packageManager": "pnpm@10.28.2+sha512.41872f037ad22f7348e3b1debbaf7e867cfd448f2726d9cf74c08f19507c31d2c8e7a11525b983febc2df640b5438dee6023ebb1f84ed43cc2d654d2bc326264", + "dependencies": { + "@modelcontextprotocol/sdk": "^1.24.0", + "class-transformer": "^0.5.1", + "class-validator": "^0.14.3", + "express": "^5.1.0", + "js-yaml": "^4.1.1", + "penpot-mcp": "file:..", + "pino": "^9.10.0", + "pino-pretty": "^13.1.1", + "reflect-metadata": "^0.1.13", + "sharp": "^0.34.5", + "ws": "^8.18.0", + "zod": "^4.3.6" + }, + "devDependencies": { + "@penpot/mcp-common": "workspace:../common", + "@types/express": "^4.17.0", + "@types/js-yaml": "^4.0.9", + "@types/node": "^20.0.0", + "@types/ws": "^8.5.10", + "esbuild": "^0.25.0", + "ts-node": "^10.9.2", + "typescript": "^5.0.0" + }, + "ts-node": { + "esm": true + } +} diff --git a/mcp/packages/server/pnpm-lock.yaml b/mcp/packages/server/pnpm-lock.yaml new file mode 100644 index 0000000000..b8229de386 --- /dev/null +++ b/mcp/packages/server/pnpm-lock.yaml @@ -0,0 +1,2840 @@ +lockfileVersion: "9.0" + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +importers: + .: + dependencies: + "@modelcontextprotocol/sdk": + specifier: ^1.24.0 + version: 1.25.3(hono@4.11.7)(zod@4.3.6) + "@penpot-mcp/common": + specifier: file:../common + version: file:../common + class-transformer: + specifier: ^0.5.1 + version: 0.5.1 + class-validator: + specifier: ^0.14.3 + version: 0.14.3 + express: + specifier: ^5.1.0 + version: 5.2.1 + js-yaml: + specifier: ^4.1.1 + version: 4.1.1 + penpot-mcp: + specifier: file:.. + version: file:.. + pino: + specifier: ^9.10.0 + version: 9.14.0 + pino-pretty: + specifier: ^13.1.1 + version: 13.1.3 + reflect-metadata: + specifier: ^0.1.13 + version: 0.1.14 + sharp: + specifier: ^0.34.5 + version: 0.34.5 + ws: + specifier: ^8.18.0 + version: 8.19.0 + devDependencies: + "@types/express": + specifier: ^4.17.0 + version: 4.17.25 + "@types/js-yaml": + specifier: ^4.0.9 + version: 4.0.9 + "@types/node": + specifier: ^20.0.0 + version: 20.19.30 + "@types/ws": + specifier: ^8.5.10 + version: 8.18.1 + esbuild: + specifier: ^0.25.0 + version: 0.25.12 + ts-node: + specifier: ^10.9.0 + version: 10.9.2(@types/node@20.19.30)(typescript@5.9.3) + typescript: + specifier: ^5.0.0 + version: 5.9.3 + +packages: + "@babel/runtime@7.28.6": + resolution: + { + integrity: sha512-05WQkdpL9COIMz4LjTxGpPNCdlpyimKppYNoJ5Di5EUObifl8t4tuLuUBBZEpoLYOmfvIWrsp9fCl0HoPRVTdA==, + } + engines: { node: ">=6.9.0" } + + "@cspotcode/source-map-support@0.8.1": + resolution: + { + integrity: sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==, + } + engines: { node: ">=12" } + + "@emnapi/runtime@1.8.1": + resolution: + { + integrity: sha512-mehfKSMWjjNol8659Z8KxEMrdSJDDot5SXMq00dM8BN4o+CLNXQ0xH2V7EchNHV4RmbZLmmPdEaXZc5H2FXmDg==, + } + + "@esbuild/aix-ppc64@0.25.12": + resolution: + { + integrity: sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==, + } + engines: { node: ">=18" } + cpu: [ppc64] + os: [aix] + + "@esbuild/android-arm64@0.25.12": + resolution: + { + integrity: sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==, + } + engines: { node: ">=18" } + cpu: [arm64] + os: [android] + + "@esbuild/android-arm@0.25.12": + resolution: + { + integrity: sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==, + } + engines: { node: ">=18" } + cpu: [arm] + os: [android] + + "@esbuild/android-x64@0.25.12": + resolution: + { + integrity: sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==, + } + engines: { node: ">=18" } + cpu: [x64] + os: [android] + + "@esbuild/darwin-arm64@0.25.12": + resolution: + { + integrity: sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==, + } + engines: { node: ">=18" } + cpu: [arm64] + os: [darwin] + + "@esbuild/darwin-x64@0.25.12": + resolution: + { + integrity: sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==, + } + engines: { node: ">=18" } + cpu: [x64] + os: [darwin] + + "@esbuild/freebsd-arm64@0.25.12": + resolution: + { + integrity: sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==, + } + engines: { node: ">=18" } + cpu: [arm64] + os: [freebsd] + + "@esbuild/freebsd-x64@0.25.12": + resolution: + { + integrity: sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==, + } + engines: { node: ">=18" } + cpu: [x64] + os: [freebsd] + + "@esbuild/linux-arm64@0.25.12": + resolution: + { + integrity: sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==, + } + engines: { node: ">=18" } + cpu: [arm64] + os: [linux] + + "@esbuild/linux-arm@0.25.12": + resolution: + { + integrity: sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==, + } + engines: { node: ">=18" } + cpu: [arm] + os: [linux] + + "@esbuild/linux-ia32@0.25.12": + resolution: + { + integrity: sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==, + } + engines: { node: ">=18" } + cpu: [ia32] + os: [linux] + + "@esbuild/linux-loong64@0.25.12": + resolution: + { + integrity: sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==, + } + engines: { node: ">=18" } + cpu: [loong64] + os: [linux] + + "@esbuild/linux-mips64el@0.25.12": + resolution: + { + integrity: sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==, + } + engines: { node: ">=18" } + cpu: [mips64el] + os: [linux] + + "@esbuild/linux-ppc64@0.25.12": + resolution: + { + integrity: sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==, + } + engines: { node: ">=18" } + cpu: [ppc64] + os: [linux] + + "@esbuild/linux-riscv64@0.25.12": + resolution: + { + integrity: sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==, + } + engines: { node: ">=18" } + cpu: [riscv64] + os: [linux] + + "@esbuild/linux-s390x@0.25.12": + resolution: + { + integrity: sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==, + } + engines: { node: ">=18" } + cpu: [s390x] + os: [linux] + + "@esbuild/linux-x64@0.25.12": + resolution: + { + integrity: sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==, + } + engines: { node: ">=18" } + cpu: [x64] + os: [linux] + + "@esbuild/netbsd-arm64@0.25.12": + resolution: + { + integrity: sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==, + } + engines: { node: ">=18" } + cpu: [arm64] + os: [netbsd] + + "@esbuild/netbsd-x64@0.25.12": + resolution: + { + integrity: sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==, + } + engines: { node: ">=18" } + cpu: [x64] + os: [netbsd] + + "@esbuild/openbsd-arm64@0.25.12": + resolution: + { + integrity: sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==, + } + engines: { node: ">=18" } + cpu: [arm64] + os: [openbsd] + + "@esbuild/openbsd-x64@0.25.12": + resolution: + { + integrity: sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==, + } + engines: { node: ">=18" } + cpu: [x64] + os: [openbsd] + + "@esbuild/openharmony-arm64@0.25.12": + resolution: + { + integrity: sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==, + } + engines: { node: ">=18" } + cpu: [arm64] + os: [openharmony] + + "@esbuild/sunos-x64@0.25.12": + resolution: + { + integrity: sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==, + } + engines: { node: ">=18" } + cpu: [x64] + os: [sunos] + + "@esbuild/win32-arm64@0.25.12": + resolution: + { + integrity: sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==, + } + engines: { node: ">=18" } + cpu: [arm64] + os: [win32] + + "@esbuild/win32-ia32@0.25.12": + resolution: + { + integrity: sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==, + } + engines: { node: ">=18" } + cpu: [ia32] + os: [win32] + + "@esbuild/win32-x64@0.25.12": + resolution: + { + integrity: sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==, + } + engines: { node: ">=18" } + cpu: [x64] + os: [win32] + + "@hono/node-server@1.19.9": + resolution: + { + integrity: sha512-vHL6w3ecZsky+8P5MD+eFfaGTyCeOHUIFYMGpQGbrBTSmNNoxv0if69rEZ5giu36weC5saFuznL411gRX7bJDw==, + } + engines: { node: ">=18.14.1" } + peerDependencies: + hono: ^4 + + "@img/colour@1.0.0": + resolution: + { + integrity: sha512-A5P/LfWGFSl6nsckYtjw9da+19jB8hkJ6ACTGcDfEJ0aE+l2n2El7dsVM7UVHZQ9s2lmYMWlrS21YLy2IR1LUw==, + } + engines: { node: ">=18" } + + "@img/sharp-darwin-arm64@0.34.5": + resolution: + { + integrity: sha512-imtQ3WMJXbMY4fxb/Ndp6HBTNVtWCUI0WdobyheGf5+ad6xX8VIDO8u2xE4qc/fr08CKG/7dDseFtn6M6g/r3w==, + } + engines: { node: ^18.17.0 || ^20.3.0 || >=21.0.0 } + cpu: [arm64] + os: [darwin] + + "@img/sharp-darwin-x64@0.34.5": + resolution: + { + integrity: sha512-YNEFAF/4KQ/PeW0N+r+aVVsoIY0/qxxikF2SWdp+NRkmMB7y9LBZAVqQ4yhGCm/H3H270OSykqmQMKLBhBJDEw==, + } + engines: { node: ^18.17.0 || ^20.3.0 || >=21.0.0 } + cpu: [x64] + os: [darwin] + + "@img/sharp-libvips-darwin-arm64@1.2.4": + resolution: + { + integrity: sha512-zqjjo7RatFfFoP0MkQ51jfuFZBnVE2pRiaydKJ1G/rHZvnsrHAOcQALIi9sA5co5xenQdTugCvtb1cuf78Vf4g==, + } + cpu: [arm64] + os: [darwin] + + "@img/sharp-libvips-darwin-x64@1.2.4": + resolution: + { + integrity: sha512-1IOd5xfVhlGwX+zXv2N93k0yMONvUlANylbJw1eTah8K/Jtpi15KC+WSiaX/nBmbm2HxRM1gZ0nSdjSsrZbGKg==, + } + cpu: [x64] + os: [darwin] + + "@img/sharp-libvips-linux-arm64@1.2.4": + resolution: + { + integrity: sha512-excjX8DfsIcJ10x1Kzr4RcWe1edC9PquDRRPx3YVCvQv+U5p7Yin2s32ftzikXojb1PIFc/9Mt28/y+iRklkrw==, + } + cpu: [arm64] + os: [linux] + + "@img/sharp-libvips-linux-arm@1.2.4": + resolution: + { + integrity: sha512-bFI7xcKFELdiNCVov8e44Ia4u2byA+l3XtsAj+Q8tfCwO6BQ8iDojYdvoPMqsKDkuoOo+X6HZA0s0q11ANMQ8A==, + } + cpu: [arm] + os: [linux] + + "@img/sharp-libvips-linux-ppc64@1.2.4": + resolution: + { + integrity: sha512-FMuvGijLDYG6lW+b/UvyilUWu5Ayu+3r2d1S8notiGCIyYU/76eig1UfMmkZ7vwgOrzKzlQbFSuQfgm7GYUPpA==, + } + cpu: [ppc64] + os: [linux] + + "@img/sharp-libvips-linux-riscv64@1.2.4": + resolution: + { + integrity: sha512-oVDbcR4zUC0ce82teubSm+x6ETixtKZBh/qbREIOcI3cULzDyb18Sr/Wcyx7NRQeQzOiHTNbZFF1UwPS2scyGA==, + } + cpu: [riscv64] + os: [linux] + + "@img/sharp-libvips-linux-s390x@1.2.4": + resolution: + { + integrity: sha512-qmp9VrzgPgMoGZyPvrQHqk02uyjA0/QrTO26Tqk6l4ZV0MPWIW6LTkqOIov+J1yEu7MbFQaDpwdwJKhbJvuRxQ==, + } + cpu: [s390x] + os: [linux] + + "@img/sharp-libvips-linux-x64@1.2.4": + resolution: + { + integrity: sha512-tJxiiLsmHc9Ax1bz3oaOYBURTXGIRDODBqhveVHonrHJ9/+k89qbLl0bcJns+e4t4rvaNBxaEZsFtSfAdquPrw==, + } + cpu: [x64] + os: [linux] + + "@img/sharp-libvips-linuxmusl-arm64@1.2.4": + resolution: + { + integrity: sha512-FVQHuwx1IIuNow9QAbYUzJ+En8KcVm9Lk5+uGUQJHaZmMECZmOlix9HnH7n1TRkXMS0pGxIJokIVB9SuqZGGXw==, + } + cpu: [arm64] + os: [linux] + + "@img/sharp-libvips-linuxmusl-x64@1.2.4": + resolution: + { + integrity: sha512-+LpyBk7L44ZIXwz/VYfglaX/okxezESc6UxDSoyo2Ks6Jxc4Y7sGjpgU9s4PMgqgjj1gZCylTieNamqA1MF7Dg==, + } + cpu: [x64] + os: [linux] + + "@img/sharp-linux-arm64@0.34.5": + resolution: + { + integrity: sha512-bKQzaJRY/bkPOXyKx5EVup7qkaojECG6NLYswgktOZjaXecSAeCWiZwwiFf3/Y+O1HrauiE3FVsGxFg8c24rZg==, + } + engines: { node: ^18.17.0 || ^20.3.0 || >=21.0.0 } + cpu: [arm64] + os: [linux] + + "@img/sharp-linux-arm@0.34.5": + resolution: + { + integrity: sha512-9dLqsvwtg1uuXBGZKsxem9595+ujv0sJ6Vi8wcTANSFpwV/GONat5eCkzQo/1O6zRIkh0m/8+5BjrRr7jDUSZw==, + } + engines: { node: ^18.17.0 || ^20.3.0 || >=21.0.0 } + cpu: [arm] + os: [linux] + + "@img/sharp-linux-ppc64@0.34.5": + resolution: + { + integrity: sha512-7zznwNaqW6YtsfrGGDA6BRkISKAAE1Jo0QdpNYXNMHu2+0dTrPflTLNkpc8l7MUP5M16ZJcUvysVWWrMefZquA==, + } + engines: { node: ^18.17.0 || ^20.3.0 || >=21.0.0 } + cpu: [ppc64] + os: [linux] + + "@img/sharp-linux-riscv64@0.34.5": + resolution: + { + integrity: sha512-51gJuLPTKa7piYPaVs8GmByo7/U7/7TZOq+cnXJIHZKavIRHAP77e3N2HEl3dgiqdD/w0yUfiJnII77PuDDFdw==, + } + engines: { node: ^18.17.0 || ^20.3.0 || >=21.0.0 } + cpu: [riscv64] + os: [linux] + + "@img/sharp-linux-s390x@0.34.5": + resolution: + { + integrity: sha512-nQtCk0PdKfho3eC5MrbQoigJ2gd1CgddUMkabUj+rBevs8tZ2cULOx46E7oyX+04WGfABgIwmMC0VqieTiR4jg==, + } + engines: { node: ^18.17.0 || ^20.3.0 || >=21.0.0 } + cpu: [s390x] + os: [linux] + + "@img/sharp-linux-x64@0.34.5": + resolution: + { + integrity: sha512-MEzd8HPKxVxVenwAa+JRPwEC7QFjoPWuS5NZnBt6B3pu7EG2Ge0id1oLHZpPJdn3OQK+BQDiw9zStiHBTJQQQQ==, + } + engines: { node: ^18.17.0 || ^20.3.0 || >=21.0.0 } + cpu: [x64] + os: [linux] + + "@img/sharp-linuxmusl-arm64@0.34.5": + resolution: + { + integrity: sha512-fprJR6GtRsMt6Kyfq44IsChVZeGN97gTD331weR1ex1c1rypDEABN6Tm2xa1wE6lYb5DdEnk03NZPqA7Id21yg==, + } + engines: { node: ^18.17.0 || ^20.3.0 || >=21.0.0 } + cpu: [arm64] + os: [linux] + + "@img/sharp-linuxmusl-x64@0.34.5": + resolution: + { + integrity: sha512-Jg8wNT1MUzIvhBFxViqrEhWDGzqymo3sV7z7ZsaWbZNDLXRJZoRGrjulp60YYtV4wfY8VIKcWidjojlLcWrd8Q==, + } + engines: { node: ^18.17.0 || ^20.3.0 || >=21.0.0 } + cpu: [x64] + os: [linux] + + "@img/sharp-wasm32@0.34.5": + resolution: + { + integrity: sha512-OdWTEiVkY2PHwqkbBI8frFxQQFekHaSSkUIJkwzclWZe64O1X4UlUjqqqLaPbUpMOQk6FBu/HtlGXNblIs0huw==, + } + engines: { node: ^18.17.0 || ^20.3.0 || >=21.0.0 } + cpu: [wasm32] + + "@img/sharp-win32-arm64@0.34.5": + resolution: + { + integrity: sha512-WQ3AgWCWYSb2yt+IG8mnC6Jdk9Whs7O0gxphblsLvdhSpSTtmu69ZG1Gkb6NuvxsNACwiPV6cNSZNzt0KPsw7g==, + } + engines: { node: ^18.17.0 || ^20.3.0 || >=21.0.0 } + cpu: [arm64] + os: [win32] + + "@img/sharp-win32-ia32@0.34.5": + resolution: + { + integrity: sha512-FV9m/7NmeCmSHDD5j4+4pNI8Cp3aW+JvLoXcTUo0IqyjSfAZJ8dIUmijx1qaJsIiU+Hosw6xM5KijAWRJCSgNg==, + } + engines: { node: ^18.17.0 || ^20.3.0 || >=21.0.0 } + cpu: [ia32] + os: [win32] + + "@img/sharp-win32-x64@0.34.5": + resolution: + { + integrity: sha512-+29YMsqY2/9eFEiW93eqWnuLcWcufowXewwSNIT6UwZdUUCrM3oFjMWH/Z6/TMmb4hlFenmfAVbpWeup2jryCw==, + } + engines: { node: ^18.17.0 || ^20.3.0 || >=21.0.0 } + cpu: [x64] + os: [win32] + + "@jridgewell/resolve-uri@3.1.2": + resolution: + { + integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==, + } + engines: { node: ">=6.0.0" } + + "@jridgewell/sourcemap-codec@1.5.5": + resolution: + { + integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==, + } + + "@jridgewell/trace-mapping@0.3.9": + resolution: + { + integrity: sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==, + } + + "@modelcontextprotocol/sdk@1.25.3": + resolution: + { + integrity: sha512-vsAMBMERybvYgKbg/l4L1rhS7VXV1c0CtyJg72vwxONVX0l4ZfKVAnZEWTQixJGTzKnELjQ59e4NbdFDALRiAQ==, + } + engines: { node: ">=18" } + peerDependencies: + "@cfworker/json-schema": ^4.1.1 + zod: ^3.25 || ^4.0 + peerDependenciesMeta: + "@cfworker/json-schema": + optional: true + + "@penpot-mcp/common@file:../common": + resolution: { directory: ../common, type: directory } + + "@pinojs/redact@0.4.0": + resolution: + { + integrity: sha512-k2ENnmBugE/rzQfEcdWHcCY+/FM3VLzH9cYEsbdsoqrvzAKRhUZeRNhAZvB8OitQJ1TBed3yqWtdjzS6wJKBwg==, + } + + "@tsconfig/node10@1.0.12": + resolution: + { + integrity: sha512-UCYBaeFvM11aU2y3YPZ//O5Rhj+xKyzy7mvcIoAjASbigy8mHMryP5cK7dgjlz2hWxh1g5pLw084E0a/wlUSFQ==, + } + + "@tsconfig/node12@1.0.11": + resolution: + { + integrity: sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==, + } + + "@tsconfig/node14@1.0.3": + resolution: + { + integrity: sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==, + } + + "@tsconfig/node16@1.0.4": + resolution: + { + integrity: sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==, + } + + "@types/body-parser@1.19.6": + resolution: + { + integrity: sha512-HLFeCYgz89uk22N5Qg3dvGvsv46B8GLvKKo1zKG4NybA8U2DiEO3w9lqGg29t/tfLRJpJ6iQxnVw4OnB7MoM9g==, + } + + "@types/connect@3.4.38": + resolution: + { + integrity: sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==, + } + + "@types/express-serve-static-core@4.19.8": + resolution: + { + integrity: sha512-02S5fmqeoKzVZCHPZid4b8JH2eM5HzQLZWN2FohQEy/0eXTq8VXZfSN6Pcr3F6N9R/vNrj7cpgbhjie6m/1tCA==, + } + + "@types/express@4.17.25": + resolution: + { + integrity: sha512-dVd04UKsfpINUnK0yBoYHDF3xu7xVH4BuDotC/xGuycx4CgbP48X/KF/586bcObxT0HENHXEU8Nqtu6NR+eKhw==, + } + + "@types/http-errors@2.0.5": + resolution: + { + integrity: sha512-r8Tayk8HJnX0FztbZN7oVqGccWgw98T/0neJphO91KkmOzug1KkofZURD4UaD5uH8AqcFLfdPErnBod0u71/qg==, + } + + "@types/js-yaml@4.0.9": + resolution: + { + integrity: sha512-k4MGaQl5TGo/iipqb2UDG2UwjXziSWkh0uysQelTlJpX1qGlpUZYm8PnO4DxG1qBomtJUdYJ6qR6xdIah10JLg==, + } + + "@types/mime@1.3.5": + resolution: + { + integrity: sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==, + } + + "@types/node@20.19.30": + resolution: + { + integrity: sha512-WJtwWJu7UdlvzEAUm484QNg5eAoq5QR08KDNx7g45Usrs2NtOPiX8ugDqmKdXkyL03rBqU5dYNYVQetEpBHq2g==, + } + + "@types/qs@6.14.0": + resolution: + { + integrity: sha512-eOunJqu0K1923aExK6y8p6fsihYEn/BYuQ4g0CxAAgFc4b/ZLN4CrsRZ55srTdqoiLzU2B2evC+apEIxprEzkQ==, + } + + "@types/range-parser@1.2.7": + resolution: + { + integrity: sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==, + } + + "@types/send@0.17.6": + resolution: + { + integrity: sha512-Uqt8rPBE8SY0RK8JB1EzVOIZ32uqy8HwdxCnoCOsYrvnswqmFZ/k+9Ikidlk/ImhsdvBsloHbAlewb2IEBV/Og==, + } + + "@types/send@1.2.1": + resolution: + { + integrity: sha512-arsCikDvlU99zl1g69TcAB3mzZPpxgw0UQnaHeC1Nwb015xp8bknZv5rIfri9xTOcMuaVgvabfIRA7PSZVuZIQ==, + } + + "@types/serve-static@1.15.10": + resolution: + { + integrity: sha512-tRs1dB+g8Itk72rlSI2ZrW6vZg0YrLI81iQSTkMmOqnqCaNr/8Ek4VwWcN5vZgCYWbg/JJSGBlUaYGAOP73qBw==, + } + + "@types/validator@13.15.10": + resolution: + { + integrity: sha512-T8L6i7wCuyoK8A/ZeLYt1+q0ty3Zb9+qbSSvrIVitzT3YjZqkTZ40IbRsPanlB4h1QB3JVL1SYCdR6ngtFYcuA==, + } + + "@types/ws@8.18.1": + resolution: + { + integrity: sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==, + } + + accepts@2.0.0: + resolution: + { + integrity: sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==, + } + engines: { node: ">= 0.6" } + + acorn-walk@8.3.4: + resolution: + { + integrity: sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==, + } + engines: { node: ">=0.4.0" } + + acorn@8.15.0: + resolution: + { + integrity: sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==, + } + engines: { node: ">=0.4.0" } + hasBin: true + + ajv-formats@3.0.1: + resolution: + { + integrity: sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==, + } + peerDependencies: + ajv: ^8.0.0 + peerDependenciesMeta: + ajv: + optional: true + + ajv@8.17.1: + resolution: + { + integrity: sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==, + } + + ansi-regex@5.0.1: + resolution: + { + integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==, + } + engines: { node: ">=8" } + + ansi-styles@4.3.0: + resolution: + { + integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==, + } + engines: { node: ">=8" } + + arg@4.1.3: + resolution: + { + integrity: sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==, + } + + argparse@2.0.1: + resolution: + { + integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==, + } + + atomic-sleep@1.0.0: + resolution: + { + integrity: sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ==, + } + engines: { node: ">=8.0.0" } + + body-parser@2.2.2: + resolution: + { + integrity: sha512-oP5VkATKlNwcgvxi0vM0p/D3n2C3EReYVX+DNYs5TjZFn/oQt2j+4sVJtSMr18pdRr8wjTcBl6LoV+FUwzPmNA==, + } + engines: { node: ">=18" } + + bytes@3.1.2: + resolution: + { + integrity: sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==, + } + engines: { node: ">= 0.8" } + + call-bind-apply-helpers@1.0.2: + resolution: + { + integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==, + } + engines: { node: ">= 0.4" } + + call-bound@1.0.4: + resolution: + { + integrity: sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==, + } + engines: { node: ">= 0.4" } + + chalk@4.1.2: + resolution: + { + integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==, + } + engines: { node: ">=10" } + + class-transformer@0.5.1: + resolution: + { + integrity: sha512-SQa1Ws6hUbfC98vKGxZH3KFY0Y1lm5Zm0SY8XX9zbK7FJCyVEac3ATW0RIpwzW+oOfmHE5PMPufDG9hCfoEOMw==, + } + + class-validator@0.14.3: + resolution: + { + integrity: sha512-rXXekcjofVN1LTOSw+u4u9WXVEUvNBVjORW154q/IdmYWy1nMbOU9aNtZB0t8m+FJQ9q91jlr2f9CwwUFdFMRA==, + } + + cliui@8.0.1: + resolution: + { + integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==, + } + engines: { node: ">=12" } + + color-convert@2.0.1: + resolution: + { + integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==, + } + engines: { node: ">=7.0.0" } + + color-name@1.1.4: + resolution: + { + integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==, + } + + colorette@2.0.20: + resolution: + { + integrity: sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==, + } + + concurrently@8.2.2: + resolution: + { + integrity: sha512-1dP4gpXFhei8IOtlXRE/T/4H88ElHgTiUzh71YUmtjTEHMSRS2Z/fgOxHSxxusGHogsRfxNq1vyAwxSC+EVyDg==, + } + engines: { node: ^14.13.0 || >=16.0.0 } + hasBin: true + + content-disposition@1.0.1: + resolution: + { + integrity: sha512-oIXISMynqSqm241k6kcQ5UwttDILMK4BiurCfGEREw6+X9jkkpEe5T9FZaApyLGGOnFuyMWZpdolTXMtvEJ08Q==, + } + engines: { node: ">=18" } + + content-type@1.0.5: + resolution: + { + integrity: sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==, + } + engines: { node: ">= 0.6" } + + cookie-signature@1.2.2: + resolution: + { + integrity: sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==, + } + engines: { node: ">=6.6.0" } + + cookie@0.7.2: + resolution: + { + integrity: sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==, + } + engines: { node: ">= 0.6" } + + cors@2.8.6: + resolution: + { + integrity: sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw==, + } + engines: { node: ">= 0.10" } + + create-require@1.1.1: + resolution: + { + integrity: sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==, + } + + cross-spawn@7.0.6: + resolution: + { + integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==, + } + engines: { node: ">= 8" } + + date-fns@2.30.0: + resolution: + { + integrity: sha512-fnULvOpxnC5/Vg3NCiWelDsLiUc9bRwAPs/+LfTLNvetFCtCTN+yQz15C/fs4AwX1R9K5GLtLfn8QW+dWisaAw==, + } + engines: { node: ">=0.11" } + + dateformat@4.6.3: + resolution: + { + integrity: sha512-2P0p0pFGzHS5EMnhdxQi7aJN+iMheud0UhG4dlE1DLAlvL8JHjJJTX/CSm4JXwV0Ka5nGk3zC5mcb5bUQUxxMA==, + } + + debug@4.4.3: + resolution: + { + integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==, + } + engines: { node: ">=6.0" } + peerDependencies: + supports-color: "*" + peerDependenciesMeta: + supports-color: + optional: true + + depd@2.0.0: + resolution: + { + integrity: sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==, + } + engines: { node: ">= 0.8" } + + detect-libc@2.1.2: + resolution: + { + integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==, + } + engines: { node: ">=8" } + + diff@4.0.4: + resolution: + { + integrity: sha512-X07nttJQkwkfKfvTPG/KSnE2OMdcUCao6+eXF3wmnIQRn2aPAHH3VxDbDOdegkd6JbPsXqShpvEOHfAT+nCNwQ==, + } + engines: { node: ">=0.3.1" } + + dunder-proto@1.0.1: + resolution: + { + integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==, + } + engines: { node: ">= 0.4" } + + ee-first@1.1.1: + resolution: + { + integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==, + } + + emoji-regex@8.0.0: + resolution: + { + integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==, + } + + encodeurl@2.0.0: + resolution: + { + integrity: sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==, + } + engines: { node: ">= 0.8" } + + end-of-stream@1.4.5: + resolution: + { + integrity: sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==, + } + + es-define-property@1.0.1: + resolution: + { + integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==, + } + engines: { node: ">= 0.4" } + + es-errors@1.3.0: + resolution: + { + integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==, + } + engines: { node: ">= 0.4" } + + es-object-atoms@1.1.1: + resolution: + { + integrity: sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==, + } + engines: { node: ">= 0.4" } + + esbuild@0.25.12: + resolution: + { + integrity: sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==, + } + engines: { node: ">=18" } + hasBin: true + + escalade@3.2.0: + resolution: + { + integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==, + } + engines: { node: ">=6" } + + escape-html@1.0.3: + resolution: + { + integrity: sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==, + } + + etag@1.8.1: + resolution: + { + integrity: sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==, + } + engines: { node: ">= 0.6" } + + eventsource-parser@3.0.6: + resolution: + { + integrity: sha512-Vo1ab+QXPzZ4tCa8SwIHJFaSzy4R6SHf7BY79rFBDf0idraZWAkYrDjDj8uWaSm3S2TK+hJ7/t1CEmZ7jXw+pg==, + } + engines: { node: ">=18.0.0" } + + eventsource@3.0.7: + resolution: + { + integrity: sha512-CRT1WTyuQoD771GW56XEZFQ/ZoSfWid1alKGDYMmkt2yl8UXrVR4pspqWNEcqKvVIzg6PAltWjxcSSPrboA4iA==, + } + engines: { node: ">=18.0.0" } + + express-rate-limit@7.5.1: + resolution: + { + integrity: sha512-7iN8iPMDzOMHPUYllBEsQdWVB6fPDMPqwjBaFrgr4Jgr/+okjvzAy+UHlYYL/Vs0OsOrMkwS6PJDkFlJwoxUnw==, + } + engines: { node: ">= 16" } + peerDependencies: + express: ">= 4.11" + + express@5.2.1: + resolution: + { + integrity: sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==, + } + engines: { node: ">= 18" } + + fast-copy@4.0.2: + resolution: + { + integrity: sha512-ybA6PDXIXOXivLJK/z9e+Otk7ve13I4ckBvGO5I2RRmBU1gMHLVDJYEuJYhGwez7YNlYji2M2DvVU+a9mSFDlw==, + } + + fast-deep-equal@3.1.3: + resolution: + { + integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==, + } + + fast-safe-stringify@2.1.1: + resolution: + { + integrity: sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==, + } + + fast-uri@3.1.0: + resolution: + { + integrity: sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==, + } + + finalhandler@2.1.1: + resolution: + { + integrity: sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA==, + } + engines: { node: ">= 18.0.0" } + + forwarded@0.2.0: + resolution: + { + integrity: sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==, + } + engines: { node: ">= 0.6" } + + fresh@2.0.0: + resolution: + { + integrity: sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==, + } + engines: { node: ">= 0.8" } + + function-bind@1.1.2: + resolution: + { + integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==, + } + + get-caller-file@2.0.5: + resolution: + { + integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==, + } + engines: { node: 6.* || 8.* || >= 10.* } + + get-intrinsic@1.3.0: + resolution: + { + integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==, + } + engines: { node: ">= 0.4" } + + get-proto@1.0.1: + resolution: + { + integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==, + } + engines: { node: ">= 0.4" } + + get-them-args@1.3.2: + resolution: + { + integrity: sha512-LRn8Jlk+DwZE4GTlDbT3Hikd1wSHgLMme/+7ddlqKd7ldwR6LjJgTVWzBnR01wnYGe4KgrXjg287RaI22UHmAw==, + } + + gopd@1.2.0: + resolution: + { + integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==, + } + engines: { node: ">= 0.4" } + + has-flag@4.0.0: + resolution: + { + integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==, + } + engines: { node: ">=8" } + + has-symbols@1.1.0: + resolution: + { + integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==, + } + engines: { node: ">= 0.4" } + + hasown@2.0.2: + resolution: + { + integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==, + } + engines: { node: ">= 0.4" } + + help-me@5.0.0: + resolution: + { + integrity: sha512-7xgomUX6ADmcYzFik0HzAxh/73YlKR9bmFzf51CZwR+b6YtzU2m0u49hQCqV6SvlqIqsaxovfwdvbnsw3b/zpg==, + } + + hono@4.11.7: + resolution: + { + integrity: sha512-l7qMiNee7t82bH3SeyUCt9UF15EVmaBvsppY2zQtrbIhl/yzBTny+YUxsVjSjQ6gaqaeVtZmGocom8TzBlA4Yw==, + } + engines: { node: ">=16.9.0" } + + http-errors@2.0.1: + resolution: + { + integrity: sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==, + } + engines: { node: ">= 0.8" } + + iconv-lite@0.7.2: + resolution: + { + integrity: sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==, + } + engines: { node: ">=0.10.0" } + + inherits@2.0.4: + resolution: + { + integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==, + } + + ipaddr.js@1.9.1: + resolution: + { + integrity: sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==, + } + engines: { node: ">= 0.10" } + + is-fullwidth-code-point@3.0.0: + resolution: + { + integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==, + } + engines: { node: ">=8" } + + is-promise@4.0.0: + resolution: + { + integrity: sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==, + } + + isexe@2.0.0: + resolution: + { + integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==, + } + + jose@6.1.3: + resolution: + { + integrity: sha512-0TpaTfihd4QMNwrz/ob2Bp7X04yuxJkjRGi4aKmOqwhov54i6u79oCv7T+C7lo70MKH6BesI3vscD1yb/yzKXQ==, + } + + joycon@3.1.1: + resolution: + { + integrity: sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw==, + } + engines: { node: ">=10" } + + js-yaml@4.1.1: + resolution: + { + integrity: sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==, + } + hasBin: true + + json-schema-traverse@1.0.0: + resolution: + { + integrity: sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==, + } + + json-schema-typed@8.0.2: + resolution: + { + integrity: sha512-fQhoXdcvc3V28x7C7BMs4P5+kNlgUURe2jmUT1T//oBRMDrqy1QPelJimwZGo7Hg9VPV3EQV5Bnq4hbFy2vetA==, + } + + kill-port@2.0.1: + resolution: + { + integrity: sha512-e0SVOV5jFo0mx8r7bS29maVWp17qGqLBZ5ricNSajON6//kmb7qqqNnml4twNE8Dtj97UQD+gNFOaipS/q1zzQ==, + } + hasBin: true + + libphonenumber-js@1.12.35: + resolution: + { + integrity: sha512-T/Cz6iLcsZdb5jDncDcUNhSAJ0VlSC9TnsqtBNdpkaAmy24/R1RhErtNWVWBrcUZKs9hSgaVsBkc7HxYnazIfw==, + } + + lodash@4.17.23: + resolution: + { + integrity: sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w==, + } + + make-error@1.3.6: + resolution: + { + integrity: sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==, + } + + math-intrinsics@1.1.0: + resolution: + { + integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==, + } + engines: { node: ">= 0.4" } + + media-typer@1.1.0: + resolution: + { + integrity: sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==, + } + engines: { node: ">= 0.8" } + + merge-descriptors@2.0.0: + resolution: + { + integrity: sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==, + } + engines: { node: ">=18" } + + mime-db@1.54.0: + resolution: + { + integrity: sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==, + } + engines: { node: ">= 0.6" } + + mime-types@3.0.2: + resolution: + { + integrity: sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==, + } + engines: { node: ">=18" } + + minimist@1.2.8: + resolution: + { + integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==, + } + + ms@2.1.3: + resolution: + { + integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==, + } + + negotiator@1.0.0: + resolution: + { + integrity: sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==, + } + engines: { node: ">= 0.6" } + + object-assign@4.1.1: + resolution: + { + integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==, + } + engines: { node: ">=0.10.0" } + + object-inspect@1.13.4: + resolution: + { + integrity: sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==, + } + engines: { node: ">= 0.4" } + + on-exit-leak-free@2.1.2: + resolution: + { + integrity: sha512-0eJJY6hXLGf1udHwfNftBqH+g73EU4B504nZeKpz1sYRKafAghwxEJunB2O7rDZkL4PGfsMVnTXZ2EjibbqcsA==, + } + engines: { node: ">=14.0.0" } + + on-finished@2.4.1: + resolution: + { + integrity: sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==, + } + engines: { node: ">= 0.8" } + + once@1.4.0: + resolution: + { + integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==, + } + + parseurl@1.3.3: + resolution: + { + integrity: sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==, + } + engines: { node: ">= 0.8" } + + path-key@3.1.1: + resolution: + { + integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==, + } + engines: { node: ">=8" } + + path-to-regexp@8.3.0: + resolution: + { + integrity: sha512-7jdwVIRtsP8MYpdXSwOS0YdD0Du+qOoF/AEPIt88PcCFrZCzx41oxku1jD88hZBwbNUIEfpqvuhjFaMAqMTWnA==, + } + + penpot-mcp@file:..: + resolution: { directory: .., type: directory } + + pino-abstract-transport@2.0.0: + resolution: + { + integrity: sha512-F63x5tizV6WCh4R6RHyi2Ml+M70DNRXt/+HANowMflpgGFMAym/VKm6G7ZOQRjqN7XbGxK1Lg9t6ZrtzOaivMw==, + } + + pino-abstract-transport@3.0.0: + resolution: + { + integrity: sha512-wlfUczU+n7Hy/Ha5j9a/gZNy7We5+cXp8YL+X+PG8S0KXxw7n/JXA3c46Y0zQznIJ83URJiwy7Lh56WLokNuxg==, + } + + pino-pretty@13.1.3: + resolution: + { + integrity: sha512-ttXRkkOz6WWC95KeY9+xxWL6AtImwbyMHrL1mSwqwW9u+vLp/WIElvHvCSDg0xO/Dzrggz1zv3rN5ovTRVowKg==, + } + hasBin: true + + pino-std-serializers@7.1.0: + resolution: + { + integrity: sha512-BndPH67/JxGExRgiX1dX0w1FvZck5Wa4aal9198SrRhZjH3GxKQUKIBnYJTdj2HDN3UQAS06HlfcSbQj2OHmaw==, + } + + pino@9.14.0: + resolution: + { + integrity: sha512-8OEwKp5juEvb/MjpIc4hjqfgCNysrS94RIOMXYvpYCdm/jglrKEiAYmiumbmGhCvs+IcInsphYDFwqrjr7398w==, + } + hasBin: true + + pkce-challenge@5.0.1: + resolution: + { + integrity: sha512-wQ0b/W4Fr01qtpHlqSqspcj3EhBvimsdh0KlHhH8HRZnMsEa0ea2fTULOXOS9ccQr3om+GcGRk4e+isrZWV8qQ==, + } + engines: { node: ">=16.20.0" } + + process-warning@5.0.0: + resolution: + { + integrity: sha512-a39t9ApHNx2L4+HBnQKqxxHNs1r7KF+Intd8Q/g1bUh6q0WIp9voPXJ/x0j+ZL45KF1pJd9+q2jLIRMfvEshkA==, + } + + proxy-addr@2.0.7: + resolution: + { + integrity: sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==, + } + engines: { node: ">= 0.10" } + + pump@3.0.3: + resolution: + { + integrity: sha512-todwxLMY7/heScKmntwQG8CXVkWUOdYxIvY2s0VWAAMh/nd8SoYiRaKjlr7+iCs984f2P8zvrfWcDDYVb73NfA==, + } + + qs@6.14.1: + resolution: + { + integrity: sha512-4EK3+xJl8Ts67nLYNwqw/dsFVnCf+qR7RgXSK9jEEm9unao3njwMDdmsdvoKBKHzxd7tCYz5e5M+SnMjdtXGQQ==, + } + engines: { node: ">=0.6" } + + quick-format-unescaped@4.0.4: + resolution: + { + integrity: sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg==, + } + + range-parser@1.2.1: + resolution: + { + integrity: sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==, + } + engines: { node: ">= 0.6" } + + raw-body@3.0.2: + resolution: + { + integrity: sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==, + } + engines: { node: ">= 0.10" } + + real-require@0.2.0: + resolution: + { + integrity: sha512-57frrGM/OCTLqLOAh0mhVA9VBMHd+9U7Zb2THMGdBUoZVOtGbJzjxsYGDJ3A9AYYCP4hn6y1TVbaOfzWtm5GFg==, + } + engines: { node: ">= 12.13.0" } + + reflect-metadata@0.1.14: + resolution: + { + integrity: sha512-ZhYeb6nRaXCfhnndflDK8qI6ZQ/YcWZCISRAWICW9XYqMUwjZM9Z0DveWX/ABN01oxSHwVxKQmxeYZSsm0jh5A==, + } + + require-directory@2.1.1: + resolution: + { + integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==, + } + engines: { node: ">=0.10.0" } + + require-from-string@2.0.2: + resolution: + { + integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==, + } + engines: { node: ">=0.10.0" } + + router@2.2.0: + resolution: + { + integrity: sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==, + } + engines: { node: ">= 18" } + + rxjs@7.8.2: + resolution: + { + integrity: sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==, + } + + safe-stable-stringify@2.5.0: + resolution: + { + integrity: sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA==, + } + engines: { node: ">=10" } + + safer-buffer@2.1.2: + resolution: + { + integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==, + } + + secure-json-parse@4.1.0: + resolution: + { + integrity: sha512-l4KnYfEyqYJxDwlNVyRfO2E4NTHfMKAWdUuA8J0yve2Dz/E/PdBepY03RvyJpssIpRFwJoCD55wA+mEDs6ByWA==, + } + + semver@7.7.3: + resolution: + { + integrity: sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==, + } + engines: { node: ">=10" } + hasBin: true + + send@1.2.1: + resolution: + { + integrity: sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ==, + } + engines: { node: ">= 18" } + + serve-static@2.2.1: + resolution: + { + integrity: sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw==, + } + engines: { node: ">= 18" } + + setprototypeof@1.2.0: + resolution: + { + integrity: sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==, + } + + sharp@0.34.5: + resolution: + { + integrity: sha512-Ou9I5Ft9WNcCbXrU9cMgPBcCK8LiwLqcbywW3t4oDV37n1pzpuNLsYiAV8eODnjbtQlSDwZ2cUEeQz4E54Hltg==, + } + engines: { node: ^18.17.0 || ^20.3.0 || >=21.0.0 } + + shebang-command@2.0.0: + resolution: + { + integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==, + } + engines: { node: ">=8" } + + shebang-regex@3.0.0: + resolution: + { + integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==, + } + engines: { node: ">=8" } + + shell-exec@1.0.2: + resolution: + { + integrity: sha512-jyVd+kU2X+mWKMmGhx4fpWbPsjvD53k9ivqetutVW/BQ+WIZoDoP4d8vUMGezV6saZsiNoW2f9GIhg9Dondohg==, + } + + shell-quote@1.8.3: + resolution: + { + integrity: sha512-ObmnIF4hXNg1BqhnHmgbDETF8dLPCggZWBjkQfhZpbszZnYur5DUljTcCHii5LC3J5E0yeO/1LIMyH+UvHQgyw==, + } + engines: { node: ">= 0.4" } + + side-channel-list@1.0.0: + resolution: + { + integrity: sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==, + } + engines: { node: ">= 0.4" } + + side-channel-map@1.0.1: + resolution: + { + integrity: sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==, + } + engines: { node: ">= 0.4" } + + side-channel-weakmap@1.0.2: + resolution: + { + integrity: sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==, + } + engines: { node: ">= 0.4" } + + side-channel@1.1.0: + resolution: + { + integrity: sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==, + } + engines: { node: ">= 0.4" } + + sonic-boom@4.2.0: + resolution: + { + integrity: sha512-INb7TM37/mAcsGmc9hyyI6+QR3rR1zVRu36B0NeGXKnOOLiZOfER5SA+N7X7k3yUYRzLWafduTDvJAfDswwEww==, + } + + spawn-command@0.0.2: + resolution: + { + integrity: sha512-zC8zGoGkmc8J9ndvml8Xksr1Amk9qBujgbF0JAIWO7kXr43w0h/0GJNM/Vustixu+YE8N/MTrQ7N31FvHUACxQ==, + } + + split2@4.2.0: + resolution: + { + integrity: sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==, + } + engines: { node: ">= 10.x" } + + statuses@2.0.2: + resolution: + { + integrity: sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==, + } + engines: { node: ">= 0.8" } + + string-width@4.2.3: + resolution: + { + integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==, + } + engines: { node: ">=8" } + + strip-ansi@6.0.1: + resolution: + { + integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==, + } + engines: { node: ">=8" } + + strip-json-comments@5.0.3: + resolution: + { + integrity: sha512-1tB5mhVo7U+ETBKNf92xT4hrQa3pm0MZ0PQvuDnWgAAGHDsfp4lPSpiS6psrSiet87wyGPh9ft6wmhOMQ0hDiw==, + } + engines: { node: ">=14.16" } + + supports-color@7.2.0: + resolution: + { + integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==, + } + engines: { node: ">=8" } + + supports-color@8.1.1: + resolution: + { + integrity: sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==, + } + engines: { node: ">=10" } + + thread-stream@3.1.0: + resolution: + { + integrity: sha512-OqyPZ9u96VohAyMfJykzmivOrY2wfMSf3C5TtFJVgN+Hm6aj+voFhlK+kZEIv2FBh1X6Xp3DlnCOfEQ3B2J86A==, + } + + toidentifier@1.0.1: + resolution: + { + integrity: sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==, + } + engines: { node: ">=0.6" } + + tree-kill@1.2.2: + resolution: + { + integrity: sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==, + } + hasBin: true + + ts-node@10.9.2: + resolution: + { + integrity: sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==, + } + hasBin: true + peerDependencies: + "@swc/core": ">=1.2.50" + "@swc/wasm": ">=1.2.50" + "@types/node": "*" + typescript: ">=2.7" + peerDependenciesMeta: + "@swc/core": + optional: true + "@swc/wasm": + optional: true + + tslib@2.8.1: + resolution: + { + integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==, + } + + type-is@2.0.1: + resolution: + { + integrity: sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==, + } + engines: { node: ">= 0.6" } + + typescript@5.9.3: + resolution: + { + integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==, + } + engines: { node: ">=14.17" } + hasBin: true + + undici-types@6.21.0: + resolution: + { + integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==, + } + + unpipe@1.0.0: + resolution: + { + integrity: sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==, + } + engines: { node: ">= 0.8" } + + v8-compile-cache-lib@3.0.1: + resolution: + { + integrity: sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==, + } + + validator@13.15.26: + resolution: + { + integrity: sha512-spH26xU080ydGggxRyR1Yhcbgx+j3y5jbNXk/8L+iRvdIEQ4uTRH2Sgf2dokud6Q4oAtsbNvJ1Ft+9xmm6IZcA==, + } + engines: { node: ">= 0.10" } + + vary@1.1.2: + resolution: + { + integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==, + } + engines: { node: ">= 0.8" } + + which@2.0.2: + resolution: + { + integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==, + } + engines: { node: ">= 8" } + hasBin: true + + wrap-ansi@7.0.0: + resolution: + { + integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==, + } + engines: { node: ">=10" } + + wrappy@1.0.2: + resolution: + { + integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==, + } + + ws@8.19.0: + resolution: + { + integrity: sha512-blAT2mjOEIi0ZzruJfIhb3nps74PRWTCz1IjglWEEpQl5XS/UNama6u2/rjFkDDouqr4L67ry+1aGIALViWjDg==, + } + engines: { node: ">=10.0.0" } + peerDependencies: + bufferutil: ^4.0.1 + utf-8-validate: ">=5.0.2" + peerDependenciesMeta: + bufferutil: + optional: true + utf-8-validate: + optional: true + + y18n@5.0.8: + resolution: + { + integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==, + } + engines: { node: ">=10" } + + yargs-parser@21.1.1: + resolution: + { + integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==, + } + engines: { node: ">=12" } + + yargs@17.7.2: + resolution: + { + integrity: sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==, + } + engines: { node: ">=12" } + + yn@3.1.1: + resolution: + { + integrity: sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==, + } + engines: { node: ">=6" } + + zod-to-json-schema@3.25.1: + resolution: + { + integrity: sha512-pM/SU9d3YAggzi6MtR4h7ruuQlqKtad8e9S0fmxcMi+ueAK5Korys/aWcV9LIIHTVbj01NdzxcnXSN+O74ZIVA==, + } + peerDependencies: + zod: ^3.25 || ^4 + + zod@4.3.6: + resolution: + { + integrity: sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==, + } + +snapshots: + "@babel/runtime@7.28.6": {} + + "@cspotcode/source-map-support@0.8.1": + dependencies: + "@jridgewell/trace-mapping": 0.3.9 + + "@emnapi/runtime@1.8.1": + dependencies: + tslib: 2.8.1 + optional: true + + "@esbuild/aix-ppc64@0.25.12": + optional: true + + "@esbuild/android-arm64@0.25.12": + optional: true + + "@esbuild/android-arm@0.25.12": + optional: true + + "@esbuild/android-x64@0.25.12": + optional: true + + "@esbuild/darwin-arm64@0.25.12": + optional: true + + "@esbuild/darwin-x64@0.25.12": + optional: true + + "@esbuild/freebsd-arm64@0.25.12": + optional: true + + "@esbuild/freebsd-x64@0.25.12": + optional: true + + "@esbuild/linux-arm64@0.25.12": + optional: true + + "@esbuild/linux-arm@0.25.12": + optional: true + + "@esbuild/linux-ia32@0.25.12": + optional: true + + "@esbuild/linux-loong64@0.25.12": + optional: true + + "@esbuild/linux-mips64el@0.25.12": + optional: true + + "@esbuild/linux-ppc64@0.25.12": + optional: true + + "@esbuild/linux-riscv64@0.25.12": + optional: true + + "@esbuild/linux-s390x@0.25.12": + optional: true + + "@esbuild/linux-x64@0.25.12": + optional: true + + "@esbuild/netbsd-arm64@0.25.12": + optional: true + + "@esbuild/netbsd-x64@0.25.12": + optional: true + + "@esbuild/openbsd-arm64@0.25.12": + optional: true + + "@esbuild/openbsd-x64@0.25.12": + optional: true + + "@esbuild/openharmony-arm64@0.25.12": + optional: true + + "@esbuild/sunos-x64@0.25.12": + optional: true + + "@esbuild/win32-arm64@0.25.12": + optional: true + + "@esbuild/win32-ia32@0.25.12": + optional: true + + "@esbuild/win32-x64@0.25.12": + optional: true + + "@hono/node-server@1.19.9(hono@4.11.7)": + dependencies: + hono: 4.11.7 + + "@img/colour@1.0.0": {} + + "@img/sharp-darwin-arm64@0.34.5": + optionalDependencies: + "@img/sharp-libvips-darwin-arm64": 1.2.4 + optional: true + + "@img/sharp-darwin-x64@0.34.5": + optionalDependencies: + "@img/sharp-libvips-darwin-x64": 1.2.4 + optional: true + + "@img/sharp-libvips-darwin-arm64@1.2.4": + optional: true + + "@img/sharp-libvips-darwin-x64@1.2.4": + optional: true + + "@img/sharp-libvips-linux-arm64@1.2.4": + optional: true + + "@img/sharp-libvips-linux-arm@1.2.4": + optional: true + + "@img/sharp-libvips-linux-ppc64@1.2.4": + optional: true + + "@img/sharp-libvips-linux-riscv64@1.2.4": + optional: true + + "@img/sharp-libvips-linux-s390x@1.2.4": + optional: true + + "@img/sharp-libvips-linux-x64@1.2.4": + optional: true + + "@img/sharp-libvips-linuxmusl-arm64@1.2.4": + optional: true + + "@img/sharp-libvips-linuxmusl-x64@1.2.4": + optional: true + + "@img/sharp-linux-arm64@0.34.5": + optionalDependencies: + "@img/sharp-libvips-linux-arm64": 1.2.4 + optional: true + + "@img/sharp-linux-arm@0.34.5": + optionalDependencies: + "@img/sharp-libvips-linux-arm": 1.2.4 + optional: true + + "@img/sharp-linux-ppc64@0.34.5": + optionalDependencies: + "@img/sharp-libvips-linux-ppc64": 1.2.4 + optional: true + + "@img/sharp-linux-riscv64@0.34.5": + optionalDependencies: + "@img/sharp-libvips-linux-riscv64": 1.2.4 + optional: true + + "@img/sharp-linux-s390x@0.34.5": + optionalDependencies: + "@img/sharp-libvips-linux-s390x": 1.2.4 + optional: true + + "@img/sharp-linux-x64@0.34.5": + optionalDependencies: + "@img/sharp-libvips-linux-x64": 1.2.4 + optional: true + + "@img/sharp-linuxmusl-arm64@0.34.5": + optionalDependencies: + "@img/sharp-libvips-linuxmusl-arm64": 1.2.4 + optional: true + + "@img/sharp-linuxmusl-x64@0.34.5": + optionalDependencies: + "@img/sharp-libvips-linuxmusl-x64": 1.2.4 + optional: true + + "@img/sharp-wasm32@0.34.5": + dependencies: + "@emnapi/runtime": 1.8.1 + optional: true + + "@img/sharp-win32-arm64@0.34.5": + optional: true + + "@img/sharp-win32-ia32@0.34.5": + optional: true + + "@img/sharp-win32-x64@0.34.5": + optional: true + + "@jridgewell/resolve-uri@3.1.2": {} + + "@jridgewell/sourcemap-codec@1.5.5": {} + + "@jridgewell/trace-mapping@0.3.9": + dependencies: + "@jridgewell/resolve-uri": 3.1.2 + "@jridgewell/sourcemap-codec": 1.5.5 + + "@modelcontextprotocol/sdk@1.25.3(hono@4.11.7)(zod@4.3.6)": + dependencies: + "@hono/node-server": 1.19.9(hono@4.11.7) + ajv: 8.17.1 + ajv-formats: 3.0.1(ajv@8.17.1) + content-type: 1.0.5 + cors: 2.8.6 + cross-spawn: 7.0.6 + eventsource: 3.0.7 + eventsource-parser: 3.0.6 + express: 5.2.1 + express-rate-limit: 7.5.1(express@5.2.1) + jose: 6.1.3 + json-schema-typed: 8.0.2 + pkce-challenge: 5.0.1 + raw-body: 3.0.2 + zod: 4.3.6 + zod-to-json-schema: 3.25.1(zod@4.3.6) + transitivePeerDependencies: + - hono + - supports-color + + "@penpot-mcp/common@file:../common": + dependencies: + penpot-mcp: file:.. + + "@pinojs/redact@0.4.0": {} + + "@tsconfig/node10@1.0.12": {} + + "@tsconfig/node12@1.0.11": {} + + "@tsconfig/node14@1.0.3": {} + + "@tsconfig/node16@1.0.4": {} + + "@types/body-parser@1.19.6": + dependencies: + "@types/connect": 3.4.38 + "@types/node": 20.19.30 + + "@types/connect@3.4.38": + dependencies: + "@types/node": 20.19.30 + + "@types/express-serve-static-core@4.19.8": + dependencies: + "@types/node": 20.19.30 + "@types/qs": 6.14.0 + "@types/range-parser": 1.2.7 + "@types/send": 1.2.1 + + "@types/express@4.17.25": + dependencies: + "@types/body-parser": 1.19.6 + "@types/express-serve-static-core": 4.19.8 + "@types/qs": 6.14.0 + "@types/serve-static": 1.15.10 + + "@types/http-errors@2.0.5": {} + + "@types/js-yaml@4.0.9": {} + + "@types/mime@1.3.5": {} + + "@types/node@20.19.30": + dependencies: + undici-types: 6.21.0 + + "@types/qs@6.14.0": {} + + "@types/range-parser@1.2.7": {} + + "@types/send@0.17.6": + dependencies: + "@types/mime": 1.3.5 + "@types/node": 20.19.30 + + "@types/send@1.2.1": + dependencies: + "@types/node": 20.19.30 + + "@types/serve-static@1.15.10": + dependencies: + "@types/http-errors": 2.0.5 + "@types/node": 20.19.30 + "@types/send": 0.17.6 + + "@types/validator@13.15.10": {} + + "@types/ws@8.18.1": + dependencies: + "@types/node": 20.19.30 + + accepts@2.0.0: + dependencies: + mime-types: 3.0.2 + negotiator: 1.0.0 + + acorn-walk@8.3.4: + dependencies: + acorn: 8.15.0 + + acorn@8.15.0: {} + + ajv-formats@3.0.1(ajv@8.17.1): + optionalDependencies: + ajv: 8.17.1 + + ajv@8.17.1: + dependencies: + fast-deep-equal: 3.1.3 + fast-uri: 3.1.0 + json-schema-traverse: 1.0.0 + require-from-string: 2.0.2 + + ansi-regex@5.0.1: {} + + ansi-styles@4.3.0: + dependencies: + color-convert: 2.0.1 + + arg@4.1.3: {} + + argparse@2.0.1: {} + + atomic-sleep@1.0.0: {} + + body-parser@2.2.2: + dependencies: + bytes: 3.1.2 + content-type: 1.0.5 + debug: 4.4.3 + http-errors: 2.0.1 + iconv-lite: 0.7.2 + on-finished: 2.4.1 + qs: 6.14.1 + raw-body: 3.0.2 + type-is: 2.0.1 + transitivePeerDependencies: + - supports-color + + bytes@3.1.2: {} + + call-bind-apply-helpers@1.0.2: + dependencies: + es-errors: 1.3.0 + function-bind: 1.1.2 + + call-bound@1.0.4: + dependencies: + call-bind-apply-helpers: 1.0.2 + get-intrinsic: 1.3.0 + + chalk@4.1.2: + dependencies: + ansi-styles: 4.3.0 + supports-color: 7.2.0 + + class-transformer@0.5.1: {} + + class-validator@0.14.3: + dependencies: + "@types/validator": 13.15.10 + libphonenumber-js: 1.12.35 + validator: 13.15.26 + + cliui@8.0.1: + dependencies: + string-width: 4.2.3 + strip-ansi: 6.0.1 + wrap-ansi: 7.0.0 + + color-convert@2.0.1: + dependencies: + color-name: 1.1.4 + + color-name@1.1.4: {} + + colorette@2.0.20: {} + + concurrently@8.2.2: + dependencies: + chalk: 4.1.2 + date-fns: 2.30.0 + lodash: 4.17.23 + rxjs: 7.8.2 + shell-quote: 1.8.3 + spawn-command: 0.0.2 + supports-color: 8.1.1 + tree-kill: 1.2.2 + yargs: 17.7.2 + + content-disposition@1.0.1: {} + + content-type@1.0.5: {} + + cookie-signature@1.2.2: {} + + cookie@0.7.2: {} + + cors@2.8.6: + dependencies: + object-assign: 4.1.1 + vary: 1.1.2 + + create-require@1.1.1: {} + + cross-spawn@7.0.6: + dependencies: + path-key: 3.1.1 + shebang-command: 2.0.0 + which: 2.0.2 + + date-fns@2.30.0: + dependencies: + "@babel/runtime": 7.28.6 + + dateformat@4.6.3: {} + + debug@4.4.3: + dependencies: + ms: 2.1.3 + + depd@2.0.0: {} + + detect-libc@2.1.2: {} + + diff@4.0.4: {} + + dunder-proto@1.0.1: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-errors: 1.3.0 + gopd: 1.2.0 + + ee-first@1.1.1: {} + + emoji-regex@8.0.0: {} + + encodeurl@2.0.0: {} + + end-of-stream@1.4.5: + dependencies: + once: 1.4.0 + + es-define-property@1.0.1: {} + + es-errors@1.3.0: {} + + es-object-atoms@1.1.1: + dependencies: + es-errors: 1.3.0 + + esbuild@0.25.12: + optionalDependencies: + "@esbuild/aix-ppc64": 0.25.12 + "@esbuild/android-arm": 0.25.12 + "@esbuild/android-arm64": 0.25.12 + "@esbuild/android-x64": 0.25.12 + "@esbuild/darwin-arm64": 0.25.12 + "@esbuild/darwin-x64": 0.25.12 + "@esbuild/freebsd-arm64": 0.25.12 + "@esbuild/freebsd-x64": 0.25.12 + "@esbuild/linux-arm": 0.25.12 + "@esbuild/linux-arm64": 0.25.12 + "@esbuild/linux-ia32": 0.25.12 + "@esbuild/linux-loong64": 0.25.12 + "@esbuild/linux-mips64el": 0.25.12 + "@esbuild/linux-ppc64": 0.25.12 + "@esbuild/linux-riscv64": 0.25.12 + "@esbuild/linux-s390x": 0.25.12 + "@esbuild/linux-x64": 0.25.12 + "@esbuild/netbsd-arm64": 0.25.12 + "@esbuild/netbsd-x64": 0.25.12 + "@esbuild/openbsd-arm64": 0.25.12 + "@esbuild/openbsd-x64": 0.25.12 + "@esbuild/openharmony-arm64": 0.25.12 + "@esbuild/sunos-x64": 0.25.12 + "@esbuild/win32-arm64": 0.25.12 + "@esbuild/win32-ia32": 0.25.12 + "@esbuild/win32-x64": 0.25.12 + + escalade@3.2.0: {} + + escape-html@1.0.3: {} + + etag@1.8.1: {} + + eventsource-parser@3.0.6: {} + + eventsource@3.0.7: + dependencies: + eventsource-parser: 3.0.6 + + express-rate-limit@7.5.1(express@5.2.1): + dependencies: + express: 5.2.1 + + express@5.2.1: + dependencies: + accepts: 2.0.0 + body-parser: 2.2.2 + content-disposition: 1.0.1 + content-type: 1.0.5 + cookie: 0.7.2 + cookie-signature: 1.2.2 + debug: 4.4.3 + depd: 2.0.0 + encodeurl: 2.0.0 + escape-html: 1.0.3 + etag: 1.8.1 + finalhandler: 2.1.1 + fresh: 2.0.0 + http-errors: 2.0.1 + merge-descriptors: 2.0.0 + mime-types: 3.0.2 + on-finished: 2.4.1 + once: 1.4.0 + parseurl: 1.3.3 + proxy-addr: 2.0.7 + qs: 6.14.1 + range-parser: 1.2.1 + router: 2.2.0 + send: 1.2.1 + serve-static: 2.2.1 + statuses: 2.0.2 + type-is: 2.0.1 + vary: 1.1.2 + transitivePeerDependencies: + - supports-color + + fast-copy@4.0.2: {} + + fast-deep-equal@3.1.3: {} + + fast-safe-stringify@2.1.1: {} + + fast-uri@3.1.0: {} + + finalhandler@2.1.1: + dependencies: + debug: 4.4.3 + encodeurl: 2.0.0 + escape-html: 1.0.3 + on-finished: 2.4.1 + parseurl: 1.3.3 + statuses: 2.0.2 + transitivePeerDependencies: + - supports-color + + forwarded@0.2.0: {} + + fresh@2.0.0: {} + + function-bind@1.1.2: {} + + get-caller-file@2.0.5: {} + + get-intrinsic@1.3.0: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-define-property: 1.0.1 + es-errors: 1.3.0 + es-object-atoms: 1.1.1 + function-bind: 1.1.2 + get-proto: 1.0.1 + gopd: 1.2.0 + has-symbols: 1.1.0 + hasown: 2.0.2 + math-intrinsics: 1.1.0 + + get-proto@1.0.1: + dependencies: + dunder-proto: 1.0.1 + es-object-atoms: 1.1.1 + + get-them-args@1.3.2: {} + + gopd@1.2.0: {} + + has-flag@4.0.0: {} + + has-symbols@1.1.0: {} + + hasown@2.0.2: + dependencies: + function-bind: 1.1.2 + + help-me@5.0.0: {} + + hono@4.11.7: {} + + http-errors@2.0.1: + dependencies: + depd: 2.0.0 + inherits: 2.0.4 + setprototypeof: 1.2.0 + statuses: 2.0.2 + toidentifier: 1.0.1 + + iconv-lite@0.7.2: + dependencies: + safer-buffer: 2.1.2 + + inherits@2.0.4: {} + + ipaddr.js@1.9.1: {} + + is-fullwidth-code-point@3.0.0: {} + + is-promise@4.0.0: {} + + isexe@2.0.0: {} + + jose@6.1.3: {} + + joycon@3.1.1: {} + + js-yaml@4.1.1: + dependencies: + argparse: 2.0.1 + + json-schema-traverse@1.0.0: {} + + json-schema-typed@8.0.2: {} + + kill-port@2.0.1: + dependencies: + get-them-args: 1.3.2 + shell-exec: 1.0.2 + + libphonenumber-js@1.12.35: {} + + lodash@4.17.23: {} + + make-error@1.3.6: {} + + math-intrinsics@1.1.0: {} + + media-typer@1.1.0: {} + + merge-descriptors@2.0.0: {} + + mime-db@1.54.0: {} + + mime-types@3.0.2: + dependencies: + mime-db: 1.54.0 + + minimist@1.2.8: {} + + ms@2.1.3: {} + + negotiator@1.0.0: {} + + object-assign@4.1.1: {} + + object-inspect@1.13.4: {} + + on-exit-leak-free@2.1.2: {} + + on-finished@2.4.1: + dependencies: + ee-first: 1.1.1 + + once@1.4.0: + dependencies: + wrappy: 1.0.2 + + parseurl@1.3.3: {} + + path-key@3.1.1: {} + + path-to-regexp@8.3.0: {} + + penpot-mcp@file:..: + dependencies: + concurrently: 8.2.2 + kill-port: 2.0.1 + + pino-abstract-transport@2.0.0: + dependencies: + split2: 4.2.0 + + pino-abstract-transport@3.0.0: + dependencies: + split2: 4.2.0 + + pino-pretty@13.1.3: + dependencies: + colorette: 2.0.20 + dateformat: 4.6.3 + fast-copy: 4.0.2 + fast-safe-stringify: 2.1.1 + help-me: 5.0.0 + joycon: 3.1.1 + minimist: 1.2.8 + on-exit-leak-free: 2.1.2 + pino-abstract-transport: 3.0.0 + pump: 3.0.3 + secure-json-parse: 4.1.0 + sonic-boom: 4.2.0 + strip-json-comments: 5.0.3 + + pino-std-serializers@7.1.0: {} + + pino@9.14.0: + dependencies: + "@pinojs/redact": 0.4.0 + atomic-sleep: 1.0.0 + on-exit-leak-free: 2.1.2 + pino-abstract-transport: 2.0.0 + pino-std-serializers: 7.1.0 + process-warning: 5.0.0 + quick-format-unescaped: 4.0.4 + real-require: 0.2.0 + safe-stable-stringify: 2.5.0 + sonic-boom: 4.2.0 + thread-stream: 3.1.0 + + pkce-challenge@5.0.1: {} + + process-warning@5.0.0: {} + + proxy-addr@2.0.7: + dependencies: + forwarded: 0.2.0 + ipaddr.js: 1.9.1 + + pump@3.0.3: + dependencies: + end-of-stream: 1.4.5 + once: 1.4.0 + + qs@6.14.1: + dependencies: + side-channel: 1.1.0 + + quick-format-unescaped@4.0.4: {} + + range-parser@1.2.1: {} + + raw-body@3.0.2: + dependencies: + bytes: 3.1.2 + http-errors: 2.0.1 + iconv-lite: 0.7.2 + unpipe: 1.0.0 + + real-require@0.2.0: {} + + reflect-metadata@0.1.14: {} + + require-directory@2.1.1: {} + + require-from-string@2.0.2: {} + + router@2.2.0: + dependencies: + debug: 4.4.3 + depd: 2.0.0 + is-promise: 4.0.0 + parseurl: 1.3.3 + path-to-regexp: 8.3.0 + transitivePeerDependencies: + - supports-color + + rxjs@7.8.2: + dependencies: + tslib: 2.8.1 + + safe-stable-stringify@2.5.0: {} + + safer-buffer@2.1.2: {} + + secure-json-parse@4.1.0: {} + + semver@7.7.3: {} + + send@1.2.1: + dependencies: + debug: 4.4.3 + encodeurl: 2.0.0 + escape-html: 1.0.3 + etag: 1.8.1 + fresh: 2.0.0 + http-errors: 2.0.1 + mime-types: 3.0.2 + ms: 2.1.3 + on-finished: 2.4.1 + range-parser: 1.2.1 + statuses: 2.0.2 + transitivePeerDependencies: + - supports-color + + serve-static@2.2.1: + dependencies: + encodeurl: 2.0.0 + escape-html: 1.0.3 + parseurl: 1.3.3 + send: 1.2.1 + transitivePeerDependencies: + - supports-color + + setprototypeof@1.2.0: {} + + sharp@0.34.5: + dependencies: + "@img/colour": 1.0.0 + detect-libc: 2.1.2 + semver: 7.7.3 + optionalDependencies: + "@img/sharp-darwin-arm64": 0.34.5 + "@img/sharp-darwin-x64": 0.34.5 + "@img/sharp-libvips-darwin-arm64": 1.2.4 + "@img/sharp-libvips-darwin-x64": 1.2.4 + "@img/sharp-libvips-linux-arm": 1.2.4 + "@img/sharp-libvips-linux-arm64": 1.2.4 + "@img/sharp-libvips-linux-ppc64": 1.2.4 + "@img/sharp-libvips-linux-riscv64": 1.2.4 + "@img/sharp-libvips-linux-s390x": 1.2.4 + "@img/sharp-libvips-linux-x64": 1.2.4 + "@img/sharp-libvips-linuxmusl-arm64": 1.2.4 + "@img/sharp-libvips-linuxmusl-x64": 1.2.4 + "@img/sharp-linux-arm": 0.34.5 + "@img/sharp-linux-arm64": 0.34.5 + "@img/sharp-linux-ppc64": 0.34.5 + "@img/sharp-linux-riscv64": 0.34.5 + "@img/sharp-linux-s390x": 0.34.5 + "@img/sharp-linux-x64": 0.34.5 + "@img/sharp-linuxmusl-arm64": 0.34.5 + "@img/sharp-linuxmusl-x64": 0.34.5 + "@img/sharp-wasm32": 0.34.5 + "@img/sharp-win32-arm64": 0.34.5 + "@img/sharp-win32-ia32": 0.34.5 + "@img/sharp-win32-x64": 0.34.5 + + shebang-command@2.0.0: + dependencies: + shebang-regex: 3.0.0 + + shebang-regex@3.0.0: {} + + shell-exec@1.0.2: {} + + shell-quote@1.8.3: {} + + side-channel-list@1.0.0: + dependencies: + es-errors: 1.3.0 + object-inspect: 1.13.4 + + side-channel-map@1.0.1: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + object-inspect: 1.13.4 + + side-channel-weakmap@1.0.2: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + object-inspect: 1.13.4 + side-channel-map: 1.0.1 + + side-channel@1.1.0: + dependencies: + es-errors: 1.3.0 + object-inspect: 1.13.4 + side-channel-list: 1.0.0 + side-channel-map: 1.0.1 + side-channel-weakmap: 1.0.2 + + sonic-boom@4.2.0: + dependencies: + atomic-sleep: 1.0.0 + + spawn-command@0.0.2: {} + + split2@4.2.0: {} + + statuses@2.0.2: {} + + string-width@4.2.3: + dependencies: + emoji-regex: 8.0.0 + is-fullwidth-code-point: 3.0.0 + strip-ansi: 6.0.1 + + strip-ansi@6.0.1: + dependencies: + ansi-regex: 5.0.1 + + strip-json-comments@5.0.3: {} + + supports-color@7.2.0: + dependencies: + has-flag: 4.0.0 + + supports-color@8.1.1: + dependencies: + has-flag: 4.0.0 + + thread-stream@3.1.0: + dependencies: + real-require: 0.2.0 + + toidentifier@1.0.1: {} + + tree-kill@1.2.2: {} + + ts-node@10.9.2(@types/node@20.19.30)(typescript@5.9.3): + dependencies: + "@cspotcode/source-map-support": 0.8.1 + "@tsconfig/node10": 1.0.12 + "@tsconfig/node12": 1.0.11 + "@tsconfig/node14": 1.0.3 + "@tsconfig/node16": 1.0.4 + "@types/node": 20.19.30 + acorn: 8.15.0 + acorn-walk: 8.3.4 + arg: 4.1.3 + create-require: 1.1.1 + diff: 4.0.4 + make-error: 1.3.6 + typescript: 5.9.3 + v8-compile-cache-lib: 3.0.1 + yn: 3.1.1 + + tslib@2.8.1: {} + + type-is@2.0.1: + dependencies: + content-type: 1.0.5 + media-typer: 1.1.0 + mime-types: 3.0.2 + + typescript@5.9.3: {} + + undici-types@6.21.0: {} + + unpipe@1.0.0: {} + + v8-compile-cache-lib@3.0.1: {} + + validator@13.15.26: {} + + vary@1.1.2: {} + + which@2.0.2: + dependencies: + isexe: 2.0.0 + + wrap-ansi@7.0.0: + dependencies: + ansi-styles: 4.3.0 + string-width: 4.2.3 + strip-ansi: 6.0.1 + + wrappy@1.0.2: {} + + ws@8.19.0: {} + + y18n@5.0.8: {} + + yargs-parser@21.1.1: {} + + yargs@17.7.2: + dependencies: + cliui: 8.0.1 + escalade: 3.2.0 + get-caller-file: 2.0.5 + require-directory: 2.1.1 + string-width: 4.2.3 + y18n: 5.0.8 + yargs-parser: 21.1.1 + + yn@3.1.1: {} + + zod-to-json-schema@3.25.1(zod@4.3.6): + dependencies: + zod: 4.3.6 + + zod@4.3.6: {} diff --git a/mcp/packages/server/src/ApiDocs.ts b/mcp/packages/server/src/ApiDocs.ts new file mode 100644 index 0000000000..c6374ea3f3 --- /dev/null +++ b/mcp/packages/server/src/ApiDocs.ts @@ -0,0 +1,128 @@ +import * as yaml from "js-yaml"; +import * as fs from "fs"; +import * as path from "path"; + +/** + * Represents a single type/interface defined in the Penpot API + */ +export class ApiType { + private readonly name: string; + private readonly overview: string; + private readonly members: Record>; + private cachedFullText: string | null = null; + + constructor(name: string, overview: string, members: Record>) { + this.name = name; + this.overview = overview; + this.members = members; + } + + /** + * Returns the original name of this API type. + */ + getName(): string { + return this.name; + } + + /** + * Returns the overview text of this API type (which all signature/type declarations) + */ + getOverviewText() { + return this.overview; + } + + /** + * Creates a single markdown text document from all parts of this API type. + * + * The full text is cached within the object for performance. + */ + getFullText(): string { + if (this.cachedFullText === null) { + let text = this.overview; + + for (const [memberType, memberEntries] of Object.entries(this.members)) { + text += `\n\n## ${memberType}\n`; + + for (const [memberName, memberDescription] of Object.entries(memberEntries)) { + text += `\n### ${memberName}\n\n${memberDescription}`; + } + } + + this.cachedFullText = text; + } + + return this.cachedFullText; + } + + /** + * Returns the description of the member with the given name. + * + * The member type doesn't matter for the search, as member names are unique + * across all member types within a single API type. + */ + getMember(memberName: string): string | null { + for (const memberEntries of Object.values(this.members)) { + if (memberName in memberEntries) { + return memberEntries[memberName]; + } + } + return null; + } +} + +/** + * Loads and manages API documentation from YAML files. + * + * This class provides case-insensitive access to API type documentation + * loaded from the data/api_types.yml file. + */ +export class ApiDocs { + private readonly apiTypes: Map = new Map(); + + /** + * Creates a new ApiDocs instance and loads the API types from the YAML file. + */ + constructor() { + this.loadApiTypes(); + } + + /** + * Loads API types from the data/api_types.yml file. + */ + private loadApiTypes(): void { + const yamlPath = path.join(process.cwd(), "data", "api_types.yml"); + const yamlContent = fs.readFileSync(yamlPath, "utf8"); + const data = yaml.load(yamlContent) as Record; + + for (const [typeName, typeData] of Object.entries(data)) { + const overview = typeData.overview || ""; + const members = typeData.members || {}; + + const apiType = new ApiType(typeName, overview, members); + + // store with lower-case key for case-insensitive retrieval + this.apiTypes.set(typeName.toLowerCase(), apiType); + } + } + + /** + * Retrieves an API type by name (case-insensitive). + */ + getType(typeName: string): ApiType | null { + return this.apiTypes.get(typeName.toLowerCase()) || null; + } + + /** + * Returns all available type names. + */ + getTypeNames(): string[] { + return Array.from(this.apiTypes.values()).map((type) => type.getName()); + } + + /** + * Returns the number of loaded API types. + */ + getTypeCount(): number { + return this.apiTypes.size; + } +} diff --git a/mcp/packages/server/src/ConfigurationLoader.ts b/mcp/packages/server/src/ConfigurationLoader.ts new file mode 100644 index 0000000000..0a24d88210 --- /dev/null +++ b/mcp/packages/server/src/ConfigurationLoader.ts @@ -0,0 +1,85 @@ +import { readFileSync, existsSync } from "fs"; +import { join, dirname } from "path"; +import { fileURLToPath } from "url"; +import yaml from "js-yaml"; +import { createLogger } from "./logger.js"; + +/** + * Interface defining the structure of the prompts configuration file. + */ +export interface PromptsConfig { + /** Initial instructions displayed when the server starts or connects to a client */ + initial_instructions: string; + [key: string]: any; // Allow for future extension with additional prompt types +} + +/** + * Configuration loader for prompts and server settings. + * + * Handles loading and parsing of YAML configuration files, + * providing type-safe access to configuration values with + * appropriate fallbacks for missing files or values. + */ +export class ConfigurationLoader { + private readonly logger = createLogger("ConfigurationLoader"); + private readonly baseDir: string; + private promptsConfig: PromptsConfig | null = null; + + /** + * Creates a new configuration loader instance. + * + * @param baseDir - Base directory for resolving configuration file paths + */ + constructor(baseDir: string) { + this.baseDir = baseDir; + } + + /** + * Loads the prompts configuration from the YAML file. + * + * Reads and parses the prompts.yml file, providing cached access + * to configuration values on subsequent calls. + * + * @returns The parsed prompts configuration object + */ + public getPromptsConfig(): PromptsConfig { + if (this.promptsConfig !== null) { + return this.promptsConfig; + } + + const promptsPath = join(this.baseDir, "data", "prompts.yml"); + + if (!existsSync(promptsPath)) { + throw new Error(`Prompts configuration file not found at ${promptsPath}, using defaults`); + } + + const fileContent = readFileSync(promptsPath, "utf8"); + const parsedConfig = yaml.load(fileContent) as PromptsConfig; + + this.promptsConfig = parsedConfig || {}; + this.logger.info(`Loaded prompts configuration from ${promptsPath}`); + + return this.promptsConfig; + } + + /** + * Gets the initial instructions for the MCP server. + * + * @returns The initial instructions string, or undefined if not configured + */ + public getInitialInstructions(): string { + const config = this.getPromptsConfig(); + return config.initial_instructions; + } + + /** + * Reloads the configuration from disk. + * + * Forces a fresh read of the configuration file on the next access, + * useful for development or when configuration files are updated at runtime. + */ + public reloadConfiguration(): void { + this.promptsConfig = null; + this.logger.info("Configuration cache cleared, will reload on next access"); + } +} diff --git a/mcp/packages/server/src/PenpotMcpServer.ts b/mcp/packages/server/src/PenpotMcpServer.ts new file mode 100644 index 0000000000..1797fdec23 --- /dev/null +++ b/mcp/packages/server/src/PenpotMcpServer.ts @@ -0,0 +1,262 @@ +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"; +import { PluginBridge } from "./PluginBridge"; +import { ConfigurationLoader } from "./ConfigurationLoader"; +import { createLogger } from "./logger"; +import { Tool } from "./Tool"; +import { HighLevelOverviewTool } from "./tools/HighLevelOverviewTool"; +import { PenpotApiInfoTool } from "./tools/PenpotApiInfoTool"; +import { ExportShapeTool } from "./tools/ExportShapeTool"; +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; + private readonly tools: Map>; + public readonly configLoader: ConfigurationLoader; + private app: any; + public readonly pluginBridge: PluginBridge; + 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, + }; + + private readonly port: number; + private readonly webSocketPort: number; + private readonly replPort: number; + private readonly listenAddress: string; + /** + * the address (domain name or IP address) via which clients can reach the MCP server + */ + public readonly serverAddress: string; + + constructor(private isMultiUser: boolean = false) { + // read port configuration from environment variables + this.port = parseInt(process.env.PENPOT_MCP_SERVER_PORT ?? "4401", 10); + this.webSocketPort = parseInt(process.env.PENPOT_MCP_WEBSOCKET_PORT ?? "4402", 10); + this.replPort = parseInt(process.env.PENPOT_MCP_REPL_PORT ?? "4403", 10); + this.listenAddress = process.env.PENPOT_MCP_SERVER_LISTEN_ADDRESS ?? "localhost"; + this.serverAddress = process.env.PENPOT_MCP_SERVER_ADDRESS ?? "localhost"; + + this.configLoader = new ConfigurationLoader(process.cwd()); + this.apiDocs = new ApiDocs(); + + this.server = new McpServer( + { + name: "penpot-mcp-server", + version: "1.0.0", + }, + { + instructions: this.getInitialInstructions(), + } + ); + + this.tools = new Map>(); + this.pluginBridge = new PluginBridge(this, this.webSocketPort); + this.replServer = new ReplServer(this.pluginBridge, this.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 the server is running in remote mode. + * + * In remote mode, the server is not assumed to be accessed only by a local user on the same machine, + * with corresponding limitations being enforced. + * Remote mode can be explicitly enabled by setting the environment variable PENPOT_MCP_REMOTE_MODE + * to "true". Enabling multi-user mode forces remote mode, regardless of the value of the environment + * variable. + */ + public isRemoteMode(): boolean { + const isRemoteModeRequested: boolean = process.env.PENPOT_MCP_REMOTE_MODE === "true"; + return this.isMultiUserMode() || isRemoteModeRequested; + } + + /** + * Indicates whether file system access is enabled for MCP tools. + * Access is enabled only in local mode, where the file system is assumed + * to belong to the user running the server locally. + */ + public isFileSystemAccessEnabled(): boolean { + return !this.isRemoteMode(); + } + + 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), // tool adapts to file system access internally + ]; + if (this.isFileSystemAccessEnabled()) { + toolInstances.push(new ImportImageTool(this)); + } + + for (const tool of toolInstances) { + const toolName = tool.getToolName(); + this.tools.set(toolName, tool); + + // Register each tool with McpServer + this.logger.info(`Registering tool: ${toolName}`); + this.server.registerTool( + toolName, + { + description: tool.getToolDescription(), + inputSchema: tool.getInputSchema(), + }, + async (args) => { + return tool.execute(args); + } + ); + } + } + + private setupHttpEndpoints(): void { + /** + * Modern Streamable HTTP connection endpoint + */ + this.app.all("/mcp", async (req: any, res: any) => { + const userToken = req.query.userToken as string | undefined; + + await this.sessionContext.run({ userToken }, async () => { + const { randomUUID } = await import("node:crypto"); + + 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]; + }); + + 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 session = this.transports.sse[sessionId]; + + 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"); + } + }); + } + + async start(): Promise { + const { default: express } = await import("express"); + this.app = express(); + this.app.use(express.json()); + + this.setupHttpEndpoints(); + + return new Promise((resolve) => { + this.app.listen(this.port, this.listenAddress, async () => { + this.logger.info(`Multi-user mode: ${this.isMultiUserMode()}`); + this.logger.info(`Remote mode: ${this.isRemoteMode()}`); + this.logger.info(`Modern Streamable HTTP endpoint: http://${this.serverAddress}:${this.port}/mcp`); + this.logger.info(`Legacy SSE endpoint: http://${this.serverAddress}:${this.port}/sse`); + this.logger.info(`WebSocket server URL: ws://${this.serverAddress}:${this.webSocketPort}`); + + // start the REPL server + await this.replServer.start(); + + resolve(); + }); + }); + } + + /** + * Stops the MCP server and associated services. + * + * Gracefully shuts down the REPL server and other components. + */ + public async stop(): Promise { + this.logger.info("Stopping Penpot MCP Server..."); + await this.replServer.stop(); + this.logger.info("Penpot MCP Server stopped"); + } +} diff --git a/mcp/packages/server/src/PluginBridge.ts b/mcp/packages/server/src/PluginBridge.ts new file mode 100644 index 0000000000..413ec2fa70 --- /dev/null +++ b/mcp/packages/server/src/PluginBridge.ts @@ -0,0 +1,227 @@ +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; +} + +/** + * 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: Map = new Map(); + private readonly clientsByToken: Map = new Map(); + private readonly pendingTasks: Map> = new Map(); + private readonly taskTimeouts: Map = new Map(); + + constructor( + public readonly mcpServer: PenpotMcpServer, + private port: number, + private taskTimeoutSecs: number = 30 + ) { + this.wsServer = new WebSocketServer({ port: port }); + this.setupWebSocketHandlers(); + } + + /** + * Sets up WebSocket connection handlers for plugin communication. + * + * Manages client connections and provides bidirectional communication + * channel between the MCP mcpServer and Penpot plugin instances. + */ + private setupWebSocketHandlers(): void { + 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 (token provided)"); + } else { + this.logger.info("New WebSocket connection established"); + } + + // 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.debug("Received WebSocket message: %s", data.toString()); + try { + const response: PluginTaskResponse = JSON.parse(data.toString()); + this.handlePluginTaskResponse(response); + } catch (error) { + this.logger.error(error, "Failure while processing WebSocket message"); + } + }); + + 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 mcpServer started on port %d", this.port); + } + + /** + * Handles responses from the plugin for completed tasks. + * + * Finds the pending task by ID and resolves or rejects its promise + * based on the execution result. + * + * @param response - The plugin task response containing ID and result + */ + private handlePluginTaskResponse(response: PluginTaskResponse): void { + const task = this.pendingTasks.get(response.id); + if (!task) { + this.logger.info(`Received response for unknown task ID: ${response.id}`); + return; + } + + // Clear the timeout and remove the task from pending tasks + const timeoutHandle = this.taskTimeouts.get(response.id); + if (timeoutHandle) { + clearTimeout(timeoutHandle); + this.taskTimeouts.delete(response.id); + } + this.pendingTasks.delete(response.id); + + // Resolve or reject the task's promise based on the result + if (response.success) { + task.resolveWithResult({ data: response.data }); + } else { + const error = new Error(response.error || "Task execution failed (details not provided)"); + task.rejectWithError(error); + } + + 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. + * + * Registers the task for result correlation and returns a promise + * that resolves when the plugin responds with the execution result. + * + * @param task - The plugin task to execute + * @throws Error if no plugin instances are connected or available + */ + public async executePluginTask>( + task: PluginTask + ): Promise { + // get the appropriate client connection based on mode + const connection = this.getClientConnection(); + + // register the task for result correlation + this.pendingTasks.set(task.id, task); + + // send task to the selected client + const requestMessage = JSON.stringify(task.toRequest()); + if (connection.socket.readyState !== 1) { + // WebSocket is not open + this.pendingTasks.delete(task.id); + 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); + if (pendingTask) { + this.pendingTasks.delete(task.id); + this.taskTimeouts.delete(task.id); + pendingTask.rejectWithError( + new Error(`Task ${task.id} timed out after ${this.taskTimeoutSecs} seconds`) + ); + } + }, this.taskTimeoutSecs * 1000); + + this.taskTimeouts.set(task.id, timeoutHandle); + this.logger.info(`Sent task ${task.id} to connected client`); + + return await task.getResultPromise(); + } +} diff --git a/mcp/packages/server/src/PluginTask.ts b/mcp/packages/server/src/PluginTask.ts new file mode 100644 index 0000000000..8600cac22b --- /dev/null +++ b/mcp/packages/server/src/PluginTask.ts @@ -0,0 +1,122 @@ +/** + * Base class for plugin tasks that are sent over WebSocket. + * + * Each task defines a specific operation for the plugin to execute + * along with strongly-typed parameters. + * + * @template TParams - The strongly-typed parameters for this task + */ +import { PluginTaskRequest, PluginTaskResult } from "@penpot/mcp-common"; +import { randomUUID } from "crypto"; + +/** + * Base class for plugin tasks that are sent over WebSocket. + * + * Each task defines a specific operation for the plugin to execute + * along with strongly-typed parameters and request/response correlation. + * + * @template TParams - The strongly-typed parameters for this task + * @template TResult - The expected result type from task execution + */ +export abstract class PluginTask = PluginTaskResult> { + /** + * Unique identifier for request/response correlation. + */ + public readonly id: string; + + /** + * The name of the task to execute on the plugin side. + */ + public readonly task: string; + + /** + * The parameters for this task execution. + */ + public readonly params: TParams; + + /** + * Promise that resolves when the task execution completes. + */ + private readonly result: Promise; + + /** + * Resolver function for the result promise. + */ + private resolveResult?: (result: TResult) => void; + + /** + * Rejector function for the result promise. + */ + private rejectResult?: (error: Error) => void; + + /** + * Creates a new plugin task instance. + * + * @param task - The name of the task to execute + * @param params - The parameters for task execution + */ + constructor(task: string, params: TParams) { + this.id = randomUUID(); + this.task = task; + this.params = params; + this.result = new Promise((resolve, reject) => { + this.resolveResult = resolve; + this.rejectResult = reject; + }); + } + + /** + * Gets the result promise for this task. + * + * @returns Promise that resolves when the task execution completes + */ + getResultPromise(): Promise { + if (!this.result) { + throw new Error("Result promise not initialized"); + } + return this.result; + } + + /** + * Resolves the task with the given result. + * + * This method should be called when a task response is received + * from the plugin with matching ID. + * + * @param result - The task execution result + */ + resolveWithResult(result: TResult): void { + if (!this.resolveResult) { + throw new Error("Result promise not initialized"); + } + this.resolveResult(result); + } + + /** + * Rejects the task with the given error. + * + * This method should be called when task execution fails + * or times out. + * + * @param error - The error that occurred during task execution + */ + rejectWithError(error: Error): void { + if (!this.rejectResult) { + throw new Error("Result promise not initialized"); + } + this.rejectResult(error); + } + + /** + * Serializes the task to a request message for WebSocket transmission. + * + * @returns The request message containing ID, task name, and parameters + */ + toRequest(): PluginTaskRequest { + return { + id: this.id, + task: this.task, + params: this.params, + }; + } +} diff --git a/mcp/packages/server/src/ReplServer.ts b/mcp/packages/server/src/ReplServer.ts new file mode 100644 index 0000000000..978f0e2ea4 --- /dev/null +++ b/mcp/packages/server/src/ReplServer.ts @@ -0,0 +1,112 @@ +import express from "express"; +import path from "path"; +import { fileURLToPath } from "url"; +import { PluginBridge } from "./PluginBridge"; +import { ExecuteCodePluginTask } from "./tasks/ExecuteCodePluginTask"; +import { createLogger } from "./logger"; + +/** + * Web-based REPL server for executing code through the PluginBridge. + * + * Provides a REPL-style HTML interface that allows users to input + * JavaScript code and execute it via ExecuteCodePluginTask instances. + * The interface maintains command history, displays logs in <pre> tags, + * and shows results in visually separated blocks. + */ +export class ReplServer { + private readonly logger = createLogger("ReplServer"); + private readonly app: express.Application; + private readonly port: number; + private server: any; + + constructor( + private readonly pluginBridge: PluginBridge, + port: number = 4403 + ) { + this.port = port; + this.app = express(); + this.setupMiddleware(); + this.setupRoutes(); + } + + /** + * Sets up Express middleware for request parsing and static content. + */ + private setupMiddleware(): void { + this.app.use(express.json()); + } + + /** + * Sets up HTTP routes for the REPL interface and API endpoints. + */ + private setupRoutes(): void { + // serve the main REPL interface + this.app.get("/", (req, res) => { + const __filename = fileURLToPath(import.meta.url); + const __dirname = path.dirname(__filename); + const htmlPath = path.join(__dirname, "static", "repl.html"); + res.sendFile(htmlPath); + }); + + // API endpoint for executing code + this.app.post("/execute", async (req, res) => { + try { + const { code } = req.body; + + if (!code || typeof code !== "string") { + return res.status(400).json({ + error: "Code parameter is required and must be a string", + }); + } + + const task = new ExecuteCodePluginTask({ code }); + const result = await this.pluginBridge.executePluginTask(task); + + // extract the result member from ExecuteCodeTaskResultData + const executeResult = result.data?.result; + + res.json({ + success: true, + result: executeResult, + log: result.data?.log || "", + }); + } catch (error) { + this.logger.error(error, "Failed to execute code in REPL"); + res.status(500).json({ + error: error instanceof Error ? error.message : "Unknown error occurred", + }); + } + }); + } + + /** + * Starts the REPL web server. + * + * Begins listening on the configured port and logs server startup information. + */ + public async start(): Promise { + return new Promise((resolve) => { + this.server = this.app.listen(this.port, () => { + this.logger.info(`REPL server started on port ${this.port}`); + this.logger.info( + `REPL interface URL: http://${this.pluginBridge.mcpServer.serverAddress}:${this.port}` + ); + resolve(); + }); + }); + } + + /** + * Stops the REPL web server. + */ + public async stop(): Promise { + if (this.server) { + return new Promise((resolve) => { + this.server.close(() => { + this.logger.info("REPL server stopped"); + resolve(); + }); + }); + } + } +} diff --git a/mcp/packages/server/src/Tool.ts b/mcp/packages/server/src/Tool.ts new file mode 100644 index 0000000000..65cfe539bd --- /dev/null +++ b/mcp/packages/server/src/Tool.ts @@ -0,0 +1,121 @@ +import { z } from "zod"; +import "reflect-metadata"; +import { TextResponse, ToolResponse } from "./ToolResponse"; +import type { PenpotMcpServer, SessionContext } from "./PenpotMcpServer"; +import { createLogger } from "./logger"; + +/** + * An empty arguments class for tools that do not require any parameters. + */ +export class EmptyToolArgs { + static schema = {}; +} + +/** + * Base class for type-safe tools with automatic schema generation and validation. + * + * This class provides type safety through automatic validation and strongly-typed + * protected methods. All tools should extend this class. + * + * @template TArgs - The strongly-typed arguments class for this tool + */ +export abstract class Tool { + private readonly logger = createLogger("Tool"); + + protected constructor( + protected mcpServer: PenpotMcpServer, + private inputSchema: z.ZodRawShape + ) {} + + /** + * Executes the tool with automatic validation and type safety. + * + * This method handles the unknown args from the MCP protocol, + * delegating to the type-safe implementation. + */ + async execute(args: unknown): Promise { + try { + let argsInstance: TArgs = args as TArgs; + this.logger.info("Executing tool: %s; arguments: %s", this.getToolName(), this.formatArgs(argsInstance)); + + // execute the actual tool logic + let result = await this.executeCore(argsInstance); + + this.logger.info("Tool execution completed: %s", this.getToolName()); + return result; + } catch (error) { + this.logger.error(error); + return new TextResponse(`Tool execution failed: ${String(error)}`); + } + } + + /** + * Formats tool arguments for readable logging. + * + * Multi-line strings are preserved with proper indentation. + */ + protected formatArgs(args: TArgs): string { + const formatted: string[] = []; + + for (const [key, value] of Object.entries(args)) { + if (typeof value === "string" && value.includes("\n")) { + // multi-line string - preserve formatting with indentation + const indentedValue = value + .split("\n") + .map((line, index) => (index === 0 ? line : " " + line)) + .join("\n"); + formatted.push(` ${key}: ${indentedValue}`); + } else if (typeof value === "string") { + // single-line string + formatted.push(` ${key}: "${value}"`); + } else if (value === null || value === undefined) { + formatted.push(` ${key}: ${value}`); + } else { + // other types (numbers, booleans, objects, arrays) + const stringified = JSON.stringify(value, null, 2); + if (stringified.includes("\n")) { + // multi-line JSON - indent it + const indented = stringified + .split("\n") + .map((line, index) => (index === 0 ? line : " " + line)) + .join("\n"); + formatted.push(` ${key}: ${indented}`); + } else { + formatted.push(` ${key}: ${stringified}`); + } + } + } + + 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; + } + + /** + * Returns the tool's unique name. + */ + public abstract getToolName(): string; + + /** + * Returns the tool's description. + */ + public abstract getToolDescription(): string; + + /** + * Executes the tool's core logic. + * + * @param args - The (typed) tool arguments + */ + protected abstract executeCore(args: TArgs): Promise; +} diff --git a/mcp/packages/server/src/ToolResponse.ts b/mcp/packages/server/src/ToolResponse.ts new file mode 100644 index 0000000000..6055f24ccf --- /dev/null +++ b/mcp/packages/server/src/ToolResponse.ts @@ -0,0 +1,97 @@ +import type { CallToolResult } from "@modelcontextprotocol/sdk/types.js"; + +type CallToolContent = CallToolResult["content"][number]; +type TextItem = Extract; +type ImageItem = Extract; + +export class TextContent implements TextItem { + [x: string]: unknown; + readonly type = "text" as const; + constructor(public text: string) {} + + /** + * @param data - Text data as string or as object (from JSON representation where indices are mapped to character codes) + */ + public static textData(data: string | object): string { + if (typeof data === "object") { + // convert object containing character codes (as obtained from JSON conversion of string) back to string + return String.fromCharCode(...(Object.values(data) as number[])); + } else { + return data; + } + } +} + +export class ImageContent implements ImageItem { + [x: string]: unknown; + readonly type = "image" as const; + + /** + * @param data - Base64-encoded image data + * @param mimeType - MIME type of the image (e.g., "image/png") + */ + constructor( + public data: string, + public mimeType: string + ) {} + + /** + * Utility function for ensuring a consistent Uint8Array representation of byte data. + * Input can be either a Uint8Array or an object (as obtained from JSON conversion of Uint8Array + * from the plugin). + * + * @param data - data as Uint8Array or as object (from JSON conversion of Uint8Array) + * @return data as Uint8Array + */ + public static byteData(data: Uint8Array | object): Uint8Array { + if (typeof data === "object") { + // convert object (as obtained from JSON conversion of Uint8Array) back to Uint8Array + return new Uint8Array(Object.values(data) as number[]); + } else { + return data; + } + } +} + +export class PNGImageContent extends ImageContent { + /** + * @param data - PNG image data as Uint8Array or as object (from JSON conversion of Uint8Array) + */ + constructor(data: Uint8Array | object) { + let array = ImageContent.byteData(data); + super(Buffer.from(array).toString("base64"), "image/png"); + } +} + +export class ToolResponse implements CallToolResult { + [x: string]: unknown; + content: CallToolContent[]; // <- IMPORTANT: protocol’s union + constructor(content: CallToolContent[]) { + this.content = content; + } +} + +export class TextResponse extends ToolResponse { + constructor(text: string) { + super([new TextContent(text)]); + } + + /** + * Creates a TextResponse from text data given as string or as object (from JSON representation where indices are mapped to + * character codes). + * + * @param data - Text data as string or as object (from JSON representation where indices are mapped to character codes) + */ + public static fromData(data: string | object): TextResponse { + return new TextResponse(TextContent.textData(data)); + } +} + +export class PNGResponse extends ToolResponse { + /** + * @param data - PNG image data as Uint8Array or as object (from JSON conversion of Uint8Array) + */ + constructor(data: Uint8Array | object) { + super([new PNGImageContent(data)]); + } +} diff --git a/mcp/packages/server/src/index.ts b/mcp/packages/server/src/index.ts new file mode 100644 index 0000000000..356c2cea81 --- /dev/null +++ b/mcp/packages/server/src/index.ts @@ -0,0 +1,67 @@ +#!/usr/bin/env node + +import { PenpotMcpServer } from "./PenpotMcpServer"; +import { createLogger, logFilePath } from "./logger"; + +/** + * Entry point for Penpot MCP Server + * + * Creates and starts the MCP server instance, handling any startup errors + * gracefully and ensuring proper process termination. + * + * Configuration via environment variables (see README). + */ +async function main(): Promise { + const logger = createLogger("main"); + + // log the file path early so it appears before any potential errors + logger.info(`Logging to file: ${logFilePath}`); + + try { + const args = process.argv.slice(2); + let multiUser = false; // default to single-user mode + + // parse command line arguments + for (let i = 0; i < args.length; i++) { + 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(" --multi-user Enable multi-user mode (default: single-user)"); + logger.info(" --help, -h Show this help message"); + logger.info(""); + logger.info("Note that configuration is mostly handled through environment variables."); + logger.info("Refer to the README for more information."); + process.exit(0); + } + } + + const server = new PenpotMcpServer(multiUser); + await server.start(); + + // keep the process alive + process.on("SIGINT", async () => { + logger.info("Received SIGINT, shutting down gracefully..."); + await server.stop(); + process.exit(0); + }); + + process.on("SIGTERM", async () => { + logger.info("Received SIGTERM, shutting down gracefully..."); + await server.stop(); + process.exit(0); + }); + } catch (error) { + logger.error(error, "Failed to start MCP server"); + process.exit(1); + } +} + +// Start the server if this file is run directly +if (import.meta.url.endsWith(process.argv[1]) || process.argv[1].endsWith("index.js")) { + main().catch((error) => { + createLogger("main").error(error, "Unhandled error in main"); + process.exit(1); + }); +} diff --git a/mcp/packages/server/src/logger.ts b/mcp/packages/server/src/logger.ts new file mode 100644 index 0000000000..1e8f96e5ec --- /dev/null +++ b/mcp/packages/server/src/logger.ts @@ -0,0 +1,80 @@ +import pino from "pino"; +import { join, resolve } from "path"; + +/** + * Configuration for log file location and level. + */ +const LOG_DIR = process.env.PENPOT_MCP_LOG_DIR || "logs"; +const LOG_LEVEL = process.env.PENPOT_MCP_LOG_LEVEL || "info"; + +/** + * Generates a timestamped log file name. + * + * @returns Log file name + */ +function generateLogFileName(): string { + const now = new Date(); + const year = now.getFullYear(); + const month = String(now.getMonth() + 1).padStart(2, "0"); + const day = String(now.getDate()).padStart(2, "0"); + const hours = String(now.getHours()).padStart(2, "0"); + const minutes = String(now.getMinutes()).padStart(2, "0"); + const seconds = String(now.getSeconds()).padStart(2, "0"); + return `penpot-mcp-${year}${month}${day}-${hours}${minutes}${seconds}.log`; +} + +/** + * Absolute path to the log file being written. + */ +export const logFilePath = resolve(join(LOG_DIR, generateLogFileName())); + +/** + * Logger instance configured for both console and file output with metadata. + * + * Both console and file output use pretty formatting for human readability. + * Console output includes colors, while file output is plain text. + */ +export const logger = pino({ + level: LOG_LEVEL, + timestamp: pino.stdTimeFunctions.isoTime, + transport: { + targets: [ + { + // console transport with pretty formatting + target: "pino-pretty", + level: LOG_LEVEL, + options: { + colorize: true, + translateTime: "SYS:yyyy-mm-dd HH:MM:ss.l", + ignore: "pid,hostname", + messageFormat: "{msg}", + levelFirst: true, + }, + }, + { + // file transport with pretty formatting (same as console) + target: "pino-pretty", + level: LOG_LEVEL, + options: { + destination: logFilePath, + colorize: false, + translateTime: "SYS:yyyy-mm-dd HH:MM:ss.l", + ignore: "pid,hostname", + messageFormat: "{msg}", + levelFirst: true, + mkdir: true, + }, + }, + ], + }, +}); + +/** + * Creates a child logger with the specified name/origin. + * + * @param name - The name/origin identifier for the logger + * @returns Child logger instance with the specified name + */ +export function createLogger(name: string) { + return logger.child({ name }); +} diff --git a/mcp/packages/server/src/static/repl.html b/mcp/packages/server/src/static/repl.html new file mode 100644 index 0000000000..9fafa13bc0 --- /dev/null +++ b/mcp/packages/server/src/static/repl.html @@ -0,0 +1,554 @@ + + + + + + Penpot API REPL + + + + +
+ +
+ +
Ctrl+Enter to execute • Arrow up/down for command history
+ + + + diff --git a/mcp/packages/server/src/tasks/ExecuteCodePluginTask.ts b/mcp/packages/server/src/tasks/ExecuteCodePluginTask.ts new file mode 100644 index 0000000000..130e69c2ec --- /dev/null +++ b/mcp/packages/server/src/tasks/ExecuteCodePluginTask.ts @@ -0,0 +1,22 @@ +import { PluginTask } from "../PluginTask"; +import { ExecuteCodeTaskParams, ExecuteCodeTaskResultData, PluginTaskResult } from "@penpot/mcp-common"; + +/** + * Task for executing JavaScript code in the plugin context. + * + * This task instructs the plugin to execute arbitrary JavaScript code + * and return the result of execution. + */ +export class ExecuteCodePluginTask extends PluginTask< + ExecuteCodeTaskParams, + PluginTaskResult> +> { + /** + * Creates a new execute code task. + * + * @param params - The parameters containing the code to execute + */ + constructor(params: ExecuteCodeTaskParams) { + super("executeCode", params); + } +} diff --git a/mcp/packages/server/src/tools/ExecuteCodeTool.ts b/mcp/packages/server/src/tools/ExecuteCodeTool.ts new file mode 100644 index 0000000000..adcb6339fa --- /dev/null +++ b/mcp/packages/server/src/tools/ExecuteCodeTool.ts @@ -0,0 +1,77 @@ +import { z } from "zod"; +import { Tool } from "../Tool"; +import type { ToolResponse } from "../ToolResponse"; +import { TextResponse } from "../ToolResponse"; +import "reflect-metadata"; +import { PenpotMcpServer } from "../PenpotMcpServer"; +import { ExecuteCodePluginTask } from "../tasks/ExecuteCodePluginTask"; +import { ExecuteCodeTaskParams } from "@penpot/mcp-common"; + +/** + * Arguments class for ExecuteCodeTool + */ +export class ExecuteCodeArgs { + static schema = { + code: z + .string() + .min(1, "Code cannot be empty") + .describe("The JavaScript code to execute in the plugin context."), + }; + + /** + * The JavaScript code to execute in the plugin context. + */ + code!: string; +} + +/** + * Tool for executing JavaScript code in the Penpot plugin context + */ +export class ExecuteCodeTool extends Tool { + /** + * Creates a new ExecuteCode tool instance. + * + * @param mcpServer - The MCP server instance + */ + constructor(mcpServer: PenpotMcpServer) { + super(mcpServer, ExecuteCodeArgs.schema); + } + + public getToolName(): string { + return "execute_code"; + } + + public getToolDescription(): string { + return ( + "Executes JavaScript code in the Penpot plugin context.\n" + + "IMPORTANT: Before using this tool, make sure you have read the 'Penpot High-Level Overview' and know " + + "which Penpot API functionality is necessary and how to use it.\n" + + "You have access two main objects: `penpot` (the Penpot API, of type `Penpot`), `penpotUtils`, " + + "and `storage`.\n" + + "`storage` is an object in which arbitrary data can be stored, simply by adding a new attribute; " + + "stored attributes can be referenced in future calls to this tool, so any intermediate results that " + + "could come in handy later should be stored in `storage` instead of just a fleeting variable; " + + "you can also store functions and thus build up a library).\n" + + "Think of the code being executed as the body of a function: " + + "The tool call returns whatever you return in the applicable `return` statement, if any.\n" + + "If an exception occurs, the exception's message will be returned to you.\n" + + "Any output that you generate via the `console` object will be returned to you separately; so you may use it" + + "to track what your code is doing, but you should *only* do so only if there is an ACTUAL NEED for this! " + + "VERY IMPORTANT: Don't use logging prematurely! NEVER log the data you are returning, as you will otherwise receive it twice!\n" + + "VERY IMPORTANT: In general, try a simple approach first, and only if it fails, try more complex code that involves " + + "handling different cases (in particular error cases) and that applies logging." + ); + } + + protected async executeCore(args: ExecuteCodeArgs): Promise { + const taskParams: ExecuteCodeTaskParams = { code: args.code }; + const task = new ExecuteCodePluginTask(taskParams); + const result = await this.mcpServer.pluginBridge.executePluginTask(task); + + if (result.data !== undefined) { + return new TextResponse(JSON.stringify(result.data, null, 2)); + } else { + return new TextResponse("Code executed successfully with no return value."); + } + } +} diff --git a/mcp/packages/server/src/tools/ExportShapeTool.ts b/mcp/packages/server/src/tools/ExportShapeTool.ts new file mode 100644 index 0000000000..d032b0b770 --- /dev/null +++ b/mcp/packages/server/src/tools/ExportShapeTool.ts @@ -0,0 +1,147 @@ +import { z } from "zod"; +import { Tool } from "../Tool"; +import { ImageContent, PNGImageContent, PNGResponse, TextContent, TextResponse, ToolResponse } from "../ToolResponse"; +import "reflect-metadata"; +import { PenpotMcpServer } from "../PenpotMcpServer"; +import { ExecuteCodePluginTask } from "../tasks/ExecuteCodePluginTask"; +import { FileUtils } from "../utils/FileUtils"; +import sharp from "sharp"; + +/** + * Arguments class for ExportShapeTool + */ +export class ExportShapeArgs { + static schema = { + shapeId: z + .string() + .min(1, "shapeId cannot be empty") + .describe( + "Identifier of the shape to export. Use the special identifier 'selection' to " + + "export the first shape currently selected by the user." + ), + format: z.enum(["svg", "png"]).default("png").describe("The output format, either 'png' (default) or 'svg'."), + mode: z + .enum(["shape", "fill"]) + .default("shape") + .describe( + "The export mode: either 'shape' (full shape as it appears in the design, including descendants; the default) or " + + "'fill' (export the raw image that is used as a fill for the shape; PNG format only)" + ), + filePath: z + .string() + .optional() + .describe( + "Optional file path to save the exported image to. If not provided, " + + "the image data is returned directly for you to see." + ), + }; + + shapeId!: string; + + format: "svg" | "png" = "png"; + + mode: "shape" | "fill" = "shape"; + + filePath?: string; +} + +/** + * Tool for executing JavaScript code in the Penpot plugin context + */ +export class ExportShapeTool extends Tool { + /** + * Creates a new ExecuteCode tool instance. + * + * @param mcpServer - The MCP server instance + */ + constructor(mcpServer: PenpotMcpServer) { + let schema: any = ExportShapeArgs.schema; + if (!mcpServer.isFileSystemAccessEnabled()) { + // remove filePath key from schema + schema = { ...schema }; + delete schema.filePath; + } + super(mcpServer, schema); + } + + public getToolName(): string { + return "export_shape"; + } + + public getToolDescription(): string { + let description = + "Exports a shape (or a shape's image fill) from the Penpot design to a PNG or SVG image, " + + "such that you can get an impression of what it looks like. "; + if (this.mcpServer.isFileSystemAccessEnabled()) { + description += "\nAlternatively, you can save it to a file."; + } + return description; + } + + protected async executeCore(args: ExportShapeArgs): Promise { + // check arguments + if (args.filePath) { + FileUtils.checkPathIsAbsolute(args.filePath); + } + + // create code for exporting the shape + let shapeCode: string; + if (args.shapeId === "selection") { + shapeCode = `penpot.selection[0]`; + } else { + shapeCode = `penpotUtils.findShapeById("${args.shapeId}")`; + } + const asSvg = args.format === "svg"; + const code = `return penpotUtils.exportImage(${shapeCode}, "${args.mode}", ${asSvg});`; + + // execute the code and obtain the image data + const task = new ExecuteCodePluginTask({ code: code }); + const result = await this.mcpServer.pluginBridge.executePluginTask(task); + const imageData = result.data!.result; + + // handle output and return response + if (!args.filePath) { + // return image data directly (for the LLM to "see" it) + if (args.format === "png") { + return new PNGResponse(await this.toPngImageBytes(imageData)); + } else { + return TextResponse.fromData(imageData); + } + } else { + // save to file requested: 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 to file + if (args.format === "png") { + FileUtils.writeBinaryFile(args.filePath, await this.toPngImageBytes(imageData)); + } else { + FileUtils.writeTextFile(args.filePath, TextContent.textData(imageData)); + } + return new TextResponse(`The shape has been exported to ${args.filePath}`); + } + } + + /** + * Converts image data to PNG format if necessary. + * + * @param data - The original image data as Uint8Array or as object (from JSON conversion of Uint8Array) + * @return The image data as PNG bytes + */ + private async toPngImageBytes(data: Uint8Array | object): Promise { + const originalBytes = ImageContent.byteData(data); + + // use sharp to detect format and convert to PNG if necessary + const image = sharp(originalBytes); + const metadata = await image.metadata(); + + // if already PNG, return as-is to avoid unnecessary re-encoding + if (metadata.format === "png") { + return originalBytes; + } + + // convert to PNG + const pngBuffer = await image.png().toBuffer(); + return new Uint8Array(pngBuffer); + } +} diff --git a/mcp/packages/server/src/tools/HighLevelOverviewTool.ts b/mcp/packages/server/src/tools/HighLevelOverviewTool.ts new file mode 100644 index 0000000000..ada8829771 --- /dev/null +++ b/mcp/packages/server/src/tools/HighLevelOverviewTool.ts @@ -0,0 +1,26 @@ +import { EmptyToolArgs, Tool } from "../Tool"; +import "reflect-metadata"; +import type { ToolResponse } from "../ToolResponse"; +import { TextResponse } from "../ToolResponse"; +import { PenpotMcpServer } from "../PenpotMcpServer"; + +export class HighLevelOverviewTool extends Tool { + constructor(mcpServer: PenpotMcpServer) { + super(mcpServer, EmptyToolArgs.schema); + } + + public getToolName(): string { + return "high_level_overview"; + } + + public getToolDescription(): string { + return ( + "Returns basic high-level instructions on the usage of Penpot-related tools and the Penpot API. " + + "If you have already read the 'Penpot High-Level Overview', you must not call this tool." + ); + } + + protected async executeCore(args: EmptyToolArgs): Promise { + return new TextResponse(this.mcpServer.getInitialInstructions()); + } +} diff --git a/mcp/packages/server/src/tools/ImportImageTool.ts b/mcp/packages/server/src/tools/ImportImageTool.ts new file mode 100644 index 0000000000..7581aaf525 --- /dev/null +++ b/mcp/packages/server/src/tools/ImportImageTool.ts @@ -0,0 +1,123 @@ +import { z } from "zod"; +import { Tool } from "../Tool"; +import { TextResponse, ToolResponse } from "../ToolResponse"; +import "reflect-metadata"; +import { PenpotMcpServer } from "../PenpotMcpServer"; +import { ExecuteCodePluginTask } from "../tasks/ExecuteCodePluginTask"; +import { FileUtils } from "../utils/FileUtils"; +import * as fs from "fs"; +import * as path from "path"; + +/** + * Arguments class for ImportImageTool + */ +export class ImportImageArgs { + static schema = { + filePath: z.string().min(1, "filePath cannot be empty").describe("Absolute path to the image file to import."), + x: z.number().optional().describe("Optional X coordinate for the rectangle's position."), + y: z.number().optional().describe("Optional Y coordinate for the rectangle's position."), + width: z + .number() + .positive("width must be positive") + .optional() + .describe( + "Optional width for the rectangle. If only width is provided, height is calculated to maintain aspect ratio." + ), + height: z + .number() + .positive("height must be positive") + .optional() + .describe( + "Optional height for the rectangle. If only height is provided, width is calculated to maintain aspect ratio." + ), + }; + + filePath!: string; + + x?: number; + + y?: number; + + width?: number; + + height?: number; +} + +/** + * Tool for importing a raster image from the local file system into Penpot + */ +export class ImportImageTool extends Tool { + /** + * Maps file extensions to MIME types. + */ + protected static readonly MIME_TYPES: { [key: string]: string } = { + ".jpg": "image/jpeg", + ".jpeg": "image/jpeg", + ".png": "image/png", + ".gif": "image/gif", + ".webp": "image/webp", + }; + + /** + * Creates a new ImportImage tool instance. + * + * @param mcpServer - The MCP server instance + */ + constructor(mcpServer: PenpotMcpServer) { + super(mcpServer, ImportImageArgs.schema); + } + + public getToolName(): string { + return "import_image"; + } + + public getToolDescription(): string { + return ( + "Imports a pixel image from the local file system into Penpot by creating a Rectangle instance " + + "that uses the image as a fill. The rectangle has the image's original proportions by default. " + + "Optionally accepts position (x, y) and dimensions (width, height) parameters. " + + "If only one dimension is provided, the other is calculated to maintain the image's aspect ratio. " + + "Supported formats: JPEG, PNG, GIF, WEBP." + ); + } + + protected async executeCore(args: ImportImageArgs): Promise { + // check that file path is absolute + FileUtils.checkPathIsAbsolute(args.filePath); + + // check that file exists + if (!fs.existsSync(args.filePath)) { + throw new Error(`File not found: ${args.filePath}`); + } + + // read the file as binary data + const fileData = fs.readFileSync(args.filePath); + const base64Data = fileData.toString("base64"); + + // determine mime type from file extension + const ext = path.extname(args.filePath).toLowerCase(); + const mimeType = ImportImageTool.MIME_TYPES[ext]; + if (!mimeType) { + const supportedExtensions = Object.keys(ImportImageTool.MIME_TYPES).join(", "); + throw new Error( + `Unsupported image format: ${ext}. Supported formats (file extensions): ${supportedExtensions}` + ); + } + + // generate and execute JavaScript code to import the image + const fileName = path.basename(args.filePath); + const escapedBase64 = base64Data.replace(/\\/g, "\\\\").replace(/'/g, "\\'"); + const escapedFileName = fileName.replace(/\\/g, "\\\\").replace(/'/g, "\\'"); + const code = ` + const rectangle = await penpotUtils.importImage( + '${escapedBase64}', '${mimeType}', '${escapedFileName}', + ${args.x ?? "undefined"}, ${args.y ?? "undefined"}, + ${args.width ?? "undefined"}, ${args.height ?? "undefined"}); + return { shapeId: rectangle.id }; + `; + const task = new ExecuteCodePluginTask({ code: code }); + const executionResult = await this.mcpServer.pluginBridge.executePluginTask(task); + + return new TextResponse(JSON.stringify(executionResult.data?.result, null, 2)); + } +} diff --git a/mcp/packages/server/src/tools/PenpotApiInfoTool.ts b/mcp/packages/server/src/tools/PenpotApiInfoTool.ts new file mode 100644 index 0000000000..935f4c3702 --- /dev/null +++ b/mcp/packages/server/src/tools/PenpotApiInfoTool.ts @@ -0,0 +1,88 @@ +import { z } from "zod"; +import { Tool } from "../Tool"; +import "reflect-metadata"; +import type { ToolResponse } from "../ToolResponse"; +import { TextResponse } from "../ToolResponse"; +import { PenpotMcpServer } from "../PenpotMcpServer"; +import { ApiDocs } from "../ApiDocs"; + +/** + * Arguments class for the PenpotApiInfoTool + */ +export class PenpotApiInfoArgs { + static schema = { + type: z.string().min(1, "Type name cannot be empty"), + member: z.string().optional(), + }; + + /** + * The API type name to retrieve information for. + */ + type!: string; + + /** + * The specific member name to retrieve (optional). + */ + member?: string; +} + +/** + * Tool for retrieving Penpot API documentation information. + * + * This tool provides access to API type documentation loaded from YAML files, + * allowing retrieval of either full type documentation or specific member details. + */ +export class PenpotApiInfoTool extends Tool { + private static readonly MAX_FULL_TEXT_CHARS = 2000; + private readonly apiDocs: ApiDocs; + + /** + * Creates a new PenpotApiInfo tool instance. + * + * @param mcpServer - The MCP server instance + */ + constructor(mcpServer: PenpotMcpServer, apiDocs: ApiDocs) { + super(mcpServer, PenpotApiInfoArgs.schema); + this.apiDocs = apiDocs; + } + + public getToolName(): string { + return "penpot_api_info"; + } + + public getToolDescription(): string { + return ( + "Retrieves Penpot API documentation for types and their members." + + "Be sure to read the 'Penpot High-Level Overview' first." + ); + } + + protected async executeCore(args: PenpotApiInfoArgs): Promise { + const apiType = this.apiDocs.getType(args.type); + + if (!apiType) { + throw new Error(`API type "${args.type}" not found`); + } + + if (args.member) { + // return specific member documentation + const memberDoc = apiType.getMember(args.member); + if (!memberDoc) { + throw new Error(`Member "${args.member}" not found in type "${args.type}"`); + } + return new TextResponse(memberDoc); + } else { + // return full text or overview based on length + const fullText = apiType.getFullText(); + if (fullText.length <= PenpotApiInfoTool.MAX_FULL_TEXT_CHARS) { + return new TextResponse(fullText); + } else { + return new TextResponse( + apiType.getOverviewText() + + "\n\nMember details not provided (too long). " + + "Call this tool with a member name for more information." + ); + } + } + } +} diff --git a/mcp/packages/server/src/utils/FileUtils.ts b/mcp/packages/server/src/utils/FileUtils.ts new file mode 100644 index 0000000000..8da7c7d6d6 --- /dev/null +++ b/mcp/packages/server/src/utils/FileUtils.ts @@ -0,0 +1,44 @@ +import * as path from "path"; +import * as fs from "fs"; + +export class FileUtils { + /** + * Checks whether the given file path is absolute and raises an error if not. + * + * @param filePath - The file path to check + */ + public static checkPathIsAbsolute(filePath: string): void { + if (!path.isAbsolute(filePath)) { + throw new Error(`The specified file path must be absolute: ${filePath}`); + } + } + + public static createParentDirectories(filePath: string): void { + const dir = path.dirname(filePath); + if (!fs.existsSync(dir)) { + fs.mkdirSync(dir, { recursive: true }); + } + } + + /** + * Writes binary data to a file at the specified path, creating the parent directories if necessary. + * + * @param filePath - The absolute path to the file where data should be written + * @param bytes - The binary data to write to the file + */ + public static writeBinaryFile(filePath: string, bytes: Uint8Array): void { + this.createParentDirectories(filePath); + fs.writeFileSync(filePath, Buffer.from(bytes)); + } + + /** + * Writes text data to a file at the specified path, creating the parent directories if necessary. + * + * @param filePath - The absolute path to the file where data should be written + * @param text - The text data to write to the file + */ + public static writeTextFile(filePath: string, text: string): void { + this.createParentDirectories(filePath); + fs.writeFileSync(filePath, text, { encoding: "utf-8" }); + } +} diff --git a/mcp/packages/server/tsconfig.json b/mcp/packages/server/tsconfig.json new file mode 100644 index 0000000000..f61bd28a33 --- /dev/null +++ b/mcp/packages/server/tsconfig.json @@ -0,0 +1,22 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "ESNext", + "moduleResolution": "node", + "lib": ["ES2022"], + "outDir": "./dist", + "rootDir": "./src", + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true, + "declaration": true, + "declarationMap": true, + "sourceMap": true, + "resolveJsonModule": true, + "experimentalDecorators": true, + "emitDecoratorMetadata": true + }, + "include": ["src/**/*"], + "exclude": ["node_modules", "dist"] +} diff --git a/mcp/pnpm-lock.yaml b/mcp/pnpm-lock.yaml new file mode 100644 index 0000000000..15088c9791 --- /dev/null +++ b/mcp/pnpm-lock.yaml @@ -0,0 +1,2851 @@ +lockfileVersion: '9.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +importers: + + .: + devDependencies: + concurrently: + specifier: ^9.2.1 + version: 9.2.1 + prettier: + specifier: ^3.0.0 + version: 3.8.1 + + packages/common: + devDependencies: + typescript: + specifier: ^5.0.0 + version: 5.9.3 + + packages/plugin: + dependencies: + '@penpot/plugin-styles': + specifier: 1.4.1 + version: 1.4.1 + '@penpot/plugin-types': + specifier: 1.4.1 + version: 1.4.1 + devDependencies: + cross-env: + specifier: ^7.0.3 + version: 7.0.3 + typescript: + specifier: ^5.8.3 + version: 5.9.3 + vite: + specifier: ^7.0.8 + version: 7.3.1(@types/node@20.19.30) + vite-live-preview: + specifier: ^0.3.2 + version: 0.3.2(vite@7.3.1(@types/node@20.19.30)) + + packages/server: + dependencies: + '@modelcontextprotocol/sdk': + specifier: ^1.24.0 + version: 1.25.3(hono@4.11.7)(zod@4.3.6) + class-transformer: + specifier: ^0.5.1 + version: 0.5.1 + class-validator: + specifier: ^0.14.3 + version: 0.14.3 + express: + specifier: ^5.1.0 + version: 5.2.1 + js-yaml: + specifier: ^4.1.1 + version: 4.1.1 + penpot-mcp: + specifier: file:.. + version: packages@file:packages + pino: + specifier: ^9.10.0 + version: 9.14.0 + pino-pretty: + specifier: ^13.1.1 + version: 13.1.3 + reflect-metadata: + specifier: ^0.1.13 + version: 0.1.14 + sharp: + specifier: ^0.34.5 + version: 0.34.5 + ws: + specifier: ^8.18.0 + version: 8.19.0 + zod: + specifier: ^4.3.6 + version: 4.3.6 + devDependencies: + '@penpot/mcp-common': + specifier: workspace:../common + version: link:../common + '@types/express': + specifier: ^4.17.0 + version: 4.17.25 + '@types/js-yaml': + specifier: ^4.0.9 + version: 4.0.9 + '@types/node': + specifier: ^20.0.0 + version: 20.19.30 + '@types/ws': + specifier: ^8.5.10 + version: 8.18.1 + esbuild: + specifier: ^0.25.0 + version: 0.25.12 + ts-node: + specifier: ^10.9.2 + version: 10.9.2(@types/node@20.19.30)(typescript@5.9.3) + typescript: + specifier: ^5.0.0 + version: 5.9.3 + +packages: + + '@commander-js/extra-typings@12.1.0': + resolution: {integrity: sha512-wf/lwQvWAA0goIghcb91dQYpkLBcyhOhQNqG/VgWhnKzgt+UOMvra7EX/2fv70arm5RW+PUHoQHHDa6/p77Eqg==} + peerDependencies: + commander: ~12.1.0 + + '@cspotcode/source-map-support@0.8.1': + resolution: {integrity: sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==} + engines: {node: '>=12'} + + '@emnapi/runtime@1.8.1': + resolution: {integrity: sha512-mehfKSMWjjNol8659Z8KxEMrdSJDDot5SXMq00dM8BN4o+CLNXQ0xH2V7EchNHV4RmbZLmmPdEaXZc5H2FXmDg==} + + '@esbuild/aix-ppc64@0.25.12': + resolution: {integrity: sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [aix] + + '@esbuild/aix-ppc64@0.27.2': + resolution: {integrity: sha512-GZMB+a0mOMZs4MpDbj8RJp4cw+w1WV5NYD6xzgvzUJ5Ek2jerwfO2eADyI6ExDSUED+1X8aMbegahsJi+8mgpw==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [aix] + + '@esbuild/android-arm64@0.25.12': + resolution: {integrity: sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [android] + + '@esbuild/android-arm64@0.27.2': + resolution: {integrity: sha512-pvz8ZZ7ot/RBphf8fv60ljmaoydPU12VuXHImtAs0XhLLw+EXBi2BLe3OYSBslR4rryHvweW5gmkKFwTiFy6KA==} + engines: {node: '>=18'} + cpu: [arm64] + os: [android] + + '@esbuild/android-arm@0.25.12': + resolution: {integrity: sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==} + engines: {node: '>=18'} + cpu: [arm] + os: [android] + + '@esbuild/android-arm@0.27.2': + resolution: {integrity: sha512-DVNI8jlPa7Ujbr1yjU2PfUSRtAUZPG9I1RwW4F4xFB1Imiu2on0ADiI/c3td+KmDtVKNbi+nffGDQMfcIMkwIA==} + engines: {node: '>=18'} + cpu: [arm] + os: [android] + + '@esbuild/android-x64@0.25.12': + resolution: {integrity: sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==} + engines: {node: '>=18'} + cpu: [x64] + os: [android] + + '@esbuild/android-x64@0.27.2': + resolution: {integrity: sha512-z8Ank4Byh4TJJOh4wpz8g2vDy75zFL0TlZlkUkEwYXuPSgX8yzep596n6mT7905kA9uHZsf/o2OJZubl2l3M7A==} + engines: {node: '>=18'} + cpu: [x64] + os: [android] + + '@esbuild/darwin-arm64@0.25.12': + resolution: {integrity: sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [darwin] + + '@esbuild/darwin-arm64@0.27.2': + resolution: {integrity: sha512-davCD2Zc80nzDVRwXTcQP/28fiJbcOwvdolL0sOiOsbwBa72kegmVU0Wrh1MYrbuCL98Omp5dVhQFWRKR2ZAlg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [darwin] + + '@esbuild/darwin-x64@0.25.12': + resolution: {integrity: sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==} + engines: {node: '>=18'} + cpu: [x64] + os: [darwin] + + '@esbuild/darwin-x64@0.27.2': + resolution: {integrity: sha512-ZxtijOmlQCBWGwbVmwOF/UCzuGIbUkqB1faQRf5akQmxRJ1ujusWsb3CVfk/9iZKr2L5SMU5wPBi1UWbvL+VQA==} + engines: {node: '>=18'} + cpu: [x64] + os: [darwin] + + '@esbuild/freebsd-arm64@0.25.12': + resolution: {integrity: sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [freebsd] + + '@esbuild/freebsd-arm64@0.27.2': + resolution: {integrity: sha512-lS/9CN+rgqQ9czogxlMcBMGd+l8Q3Nj1MFQwBZJyoEKI50XGxwuzznYdwcav6lpOGv5BqaZXqvBSiB/kJ5op+g==} + engines: {node: '>=18'} + cpu: [arm64] + os: [freebsd] + + '@esbuild/freebsd-x64@0.25.12': + resolution: {integrity: sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [freebsd] + + '@esbuild/freebsd-x64@0.27.2': + resolution: {integrity: sha512-tAfqtNYb4YgPnJlEFu4c212HYjQWSO/w/h/lQaBK7RbwGIkBOuNKQI9tqWzx7Wtp7bTPaGC6MJvWI608P3wXYA==} + engines: {node: '>=18'} + cpu: [x64] + os: [freebsd] + + '@esbuild/linux-arm64@0.25.12': + resolution: {integrity: sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==} + engines: {node: '>=18'} + cpu: [arm64] + os: [linux] + + '@esbuild/linux-arm64@0.27.2': + resolution: {integrity: sha512-hYxN8pr66NsCCiRFkHUAsxylNOcAQaxSSkHMMjcpx0si13t1LHFphxJZUiGwojB1a/Hd5OiPIqDdXONia6bhTw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [linux] + + '@esbuild/linux-arm@0.25.12': + resolution: {integrity: sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==} + engines: {node: '>=18'} + cpu: [arm] + os: [linux] + + '@esbuild/linux-arm@0.27.2': + resolution: {integrity: sha512-vWfq4GaIMP9AIe4yj1ZUW18RDhx6EPQKjwe7n8BbIecFtCQG4CfHGaHuh7fdfq+y3LIA2vGS/o9ZBGVxIDi9hw==} + engines: {node: '>=18'} + cpu: [arm] + os: [linux] + + '@esbuild/linux-ia32@0.25.12': + resolution: {integrity: sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==} + engines: {node: '>=18'} + cpu: [ia32] + os: [linux] + + '@esbuild/linux-ia32@0.27.2': + resolution: {integrity: sha512-MJt5BRRSScPDwG2hLelYhAAKh9imjHK5+NE/tvnRLbIqUWa+0E9N4WNMjmp/kXXPHZGqPLxggwVhz7QP8CTR8w==} + engines: {node: '>=18'} + cpu: [ia32] + os: [linux] + + '@esbuild/linux-loong64@0.25.12': + resolution: {integrity: sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==} + engines: {node: '>=18'} + cpu: [loong64] + os: [linux] + + '@esbuild/linux-loong64@0.27.2': + resolution: {integrity: sha512-lugyF1atnAT463aO6KPshVCJK5NgRnU4yb3FUumyVz+cGvZbontBgzeGFO1nF+dPueHD367a2ZXe1NtUkAjOtg==} + engines: {node: '>=18'} + cpu: [loong64] + os: [linux] + + '@esbuild/linux-mips64el@0.25.12': + resolution: {integrity: sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==} + engines: {node: '>=18'} + cpu: [mips64el] + os: [linux] + + '@esbuild/linux-mips64el@0.27.2': + resolution: {integrity: sha512-nlP2I6ArEBewvJ2gjrrkESEZkB5mIoaTswuqNFRv/WYd+ATtUpe9Y09RnJvgvdag7he0OWgEZWhviS1OTOKixw==} + engines: {node: '>=18'} + cpu: [mips64el] + os: [linux] + + '@esbuild/linux-ppc64@0.25.12': + resolution: {integrity: sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [linux] + + '@esbuild/linux-ppc64@0.27.2': + resolution: {integrity: sha512-C92gnpey7tUQONqg1n6dKVbx3vphKtTHJaNG2Ok9lGwbZil6DrfyecMsp9CrmXGQJmZ7iiVXvvZH6Ml5hL6XdQ==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [linux] + + '@esbuild/linux-riscv64@0.25.12': + resolution: {integrity: sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==} + engines: {node: '>=18'} + cpu: [riscv64] + os: [linux] + + '@esbuild/linux-riscv64@0.27.2': + resolution: {integrity: sha512-B5BOmojNtUyN8AXlK0QJyvjEZkWwy/FKvakkTDCziX95AowLZKR6aCDhG7LeF7uMCXEJqwa8Bejz5LTPYm8AvA==} + engines: {node: '>=18'} + cpu: [riscv64] + os: [linux] + + '@esbuild/linux-s390x@0.25.12': + resolution: {integrity: sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==} + engines: {node: '>=18'} + cpu: [s390x] + os: [linux] + + '@esbuild/linux-s390x@0.27.2': + resolution: {integrity: sha512-p4bm9+wsPwup5Z8f4EpfN63qNagQ47Ua2znaqGH6bqLlmJ4bx97Y9JdqxgGZ6Y8xVTixUnEkoKSHcpRlDnNr5w==} + engines: {node: '>=18'} + cpu: [s390x] + os: [linux] + + '@esbuild/linux-x64@0.25.12': + resolution: {integrity: sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==} + engines: {node: '>=18'} + cpu: [x64] + os: [linux] + + '@esbuild/linux-x64@0.27.2': + resolution: {integrity: sha512-uwp2Tip5aPmH+NRUwTcfLb+W32WXjpFejTIOWZFw/v7/KnpCDKG66u4DLcurQpiYTiYwQ9B7KOeMJvLCu/OvbA==} + engines: {node: '>=18'} + cpu: [x64] + os: [linux] + + '@esbuild/netbsd-arm64@0.25.12': + resolution: {integrity: sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [netbsd] + + '@esbuild/netbsd-arm64@0.27.2': + resolution: {integrity: sha512-Kj6DiBlwXrPsCRDeRvGAUb/LNrBASrfqAIok+xB0LxK8CHqxZ037viF13ugfsIpePH93mX7xfJp97cyDuTZ3cw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [netbsd] + + '@esbuild/netbsd-x64@0.25.12': + resolution: {integrity: sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [netbsd] + + '@esbuild/netbsd-x64@0.27.2': + resolution: {integrity: sha512-HwGDZ0VLVBY3Y+Nw0JexZy9o/nUAWq9MlV7cahpaXKW6TOzfVno3y3/M8Ga8u8Yr7GldLOov27xiCnqRZf0tCA==} + engines: {node: '>=18'} + cpu: [x64] + os: [netbsd] + + '@esbuild/openbsd-arm64@0.25.12': + resolution: {integrity: sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openbsd] + + '@esbuild/openbsd-arm64@0.27.2': + resolution: {integrity: sha512-DNIHH2BPQ5551A7oSHD0CKbwIA/Ox7+78/AWkbS5QoRzaqlev2uFayfSxq68EkonB+IKjiuxBFoV8ESJy8bOHA==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openbsd] + + '@esbuild/openbsd-x64@0.25.12': + resolution: {integrity: sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==} + engines: {node: '>=18'} + cpu: [x64] + os: [openbsd] + + '@esbuild/openbsd-x64@0.27.2': + resolution: {integrity: sha512-/it7w9Nb7+0KFIzjalNJVR5bOzA9Vay+yIPLVHfIQYG/j+j9VTH84aNB8ExGKPU4AzfaEvN9/V4HV+F+vo8OEg==} + engines: {node: '>=18'} + cpu: [x64] + os: [openbsd] + + '@esbuild/openharmony-arm64@0.25.12': + resolution: {integrity: sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openharmony] + + '@esbuild/openharmony-arm64@0.27.2': + resolution: {integrity: sha512-LRBbCmiU51IXfeXk59csuX/aSaToeG7w48nMwA6049Y4J4+VbWALAuXcs+qcD04rHDuSCSRKdmY63sruDS5qag==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openharmony] + + '@esbuild/sunos-x64@0.25.12': + resolution: {integrity: sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==} + engines: {node: '>=18'} + cpu: [x64] + os: [sunos] + + '@esbuild/sunos-x64@0.27.2': + resolution: {integrity: sha512-kMtx1yqJHTmqaqHPAzKCAkDaKsffmXkPHThSfRwZGyuqyIeBvf08KSsYXl+abf5HDAPMJIPnbBfXvP2ZC2TfHg==} + engines: {node: '>=18'} + cpu: [x64] + os: [sunos] + + '@esbuild/win32-arm64@0.25.12': + resolution: {integrity: sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [win32] + + '@esbuild/win32-arm64@0.27.2': + resolution: {integrity: sha512-Yaf78O/B3Kkh+nKABUF++bvJv5Ijoy9AN1ww904rOXZFLWVc5OLOfL56W+C8F9xn5JQZa3UX6m+IktJnIb1Jjg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [win32] + + '@esbuild/win32-ia32@0.25.12': + resolution: {integrity: sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==} + engines: {node: '>=18'} + cpu: [ia32] + os: [win32] + + '@esbuild/win32-ia32@0.27.2': + resolution: {integrity: sha512-Iuws0kxo4yusk7sw70Xa2E2imZU5HoixzxfGCdxwBdhiDgt9vX9VUCBhqcwY7/uh//78A1hMkkROMJq9l27oLQ==} + engines: {node: '>=18'} + cpu: [ia32] + os: [win32] + + '@esbuild/win32-x64@0.25.12': + resolution: {integrity: sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==} + engines: {node: '>=18'} + cpu: [x64] + os: [win32] + + '@esbuild/win32-x64@0.27.2': + resolution: {integrity: sha512-sRdU18mcKf7F+YgheI/zGf5alZatMUTKj/jNS6l744f9u3WFu4v7twcUI9vu4mknF4Y9aDlblIie0IM+5xxaqQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [win32] + + '@hono/node-server@1.19.9': + resolution: {integrity: sha512-vHL6w3ecZsky+8P5MD+eFfaGTyCeOHUIFYMGpQGbrBTSmNNoxv0if69rEZ5giu36weC5saFuznL411gRX7bJDw==} + engines: {node: '>=18.14.1'} + peerDependencies: + hono: ^4 + + '@img/colour@1.0.0': + resolution: {integrity: sha512-A5P/LfWGFSl6nsckYtjw9da+19jB8hkJ6ACTGcDfEJ0aE+l2n2El7dsVM7UVHZQ9s2lmYMWlrS21YLy2IR1LUw==} + engines: {node: '>=18'} + + '@img/sharp-darwin-arm64@0.34.5': + resolution: {integrity: sha512-imtQ3WMJXbMY4fxb/Ndp6HBTNVtWCUI0WdobyheGf5+ad6xX8VIDO8u2xE4qc/fr08CKG/7dDseFtn6M6g/r3w==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm64] + os: [darwin] + + '@img/sharp-darwin-x64@0.34.5': + resolution: {integrity: sha512-YNEFAF/4KQ/PeW0N+r+aVVsoIY0/qxxikF2SWdp+NRkmMB7y9LBZAVqQ4yhGCm/H3H270OSykqmQMKLBhBJDEw==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [x64] + os: [darwin] + + '@img/sharp-libvips-darwin-arm64@1.2.4': + resolution: {integrity: sha512-zqjjo7RatFfFoP0MkQ51jfuFZBnVE2pRiaydKJ1G/rHZvnsrHAOcQALIi9sA5co5xenQdTugCvtb1cuf78Vf4g==} + cpu: [arm64] + os: [darwin] + + '@img/sharp-libvips-darwin-x64@1.2.4': + resolution: {integrity: sha512-1IOd5xfVhlGwX+zXv2N93k0yMONvUlANylbJw1eTah8K/Jtpi15KC+WSiaX/nBmbm2HxRM1gZ0nSdjSsrZbGKg==} + cpu: [x64] + os: [darwin] + + '@img/sharp-libvips-linux-arm64@1.2.4': + resolution: {integrity: sha512-excjX8DfsIcJ10x1Kzr4RcWe1edC9PquDRRPx3YVCvQv+U5p7Yin2s32ftzikXojb1PIFc/9Mt28/y+iRklkrw==} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@img/sharp-libvips-linux-arm@1.2.4': + resolution: {integrity: sha512-bFI7xcKFELdiNCVov8e44Ia4u2byA+l3XtsAj+Q8tfCwO6BQ8iDojYdvoPMqsKDkuoOo+X6HZA0s0q11ANMQ8A==} + cpu: [arm] + os: [linux] + libc: [glibc] + + '@img/sharp-libvips-linux-ppc64@1.2.4': + resolution: {integrity: sha512-FMuvGijLDYG6lW+b/UvyilUWu5Ayu+3r2d1S8notiGCIyYU/76eig1UfMmkZ7vwgOrzKzlQbFSuQfgm7GYUPpA==} + cpu: [ppc64] + os: [linux] + libc: [glibc] + + '@img/sharp-libvips-linux-riscv64@1.2.4': + resolution: {integrity: sha512-oVDbcR4zUC0ce82teubSm+x6ETixtKZBh/qbREIOcI3cULzDyb18Sr/Wcyx7NRQeQzOiHTNbZFF1UwPS2scyGA==} + cpu: [riscv64] + os: [linux] + libc: [glibc] + + '@img/sharp-libvips-linux-s390x@1.2.4': + resolution: {integrity: sha512-qmp9VrzgPgMoGZyPvrQHqk02uyjA0/QrTO26Tqk6l4ZV0MPWIW6LTkqOIov+J1yEu7MbFQaDpwdwJKhbJvuRxQ==} + cpu: [s390x] + os: [linux] + libc: [glibc] + + '@img/sharp-libvips-linux-x64@1.2.4': + resolution: {integrity: sha512-tJxiiLsmHc9Ax1bz3oaOYBURTXGIRDODBqhveVHonrHJ9/+k89qbLl0bcJns+e4t4rvaNBxaEZsFtSfAdquPrw==} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@img/sharp-libvips-linuxmusl-arm64@1.2.4': + resolution: {integrity: sha512-FVQHuwx1IIuNow9QAbYUzJ+En8KcVm9Lk5+uGUQJHaZmMECZmOlix9HnH7n1TRkXMS0pGxIJokIVB9SuqZGGXw==} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@img/sharp-libvips-linuxmusl-x64@1.2.4': + resolution: {integrity: sha512-+LpyBk7L44ZIXwz/VYfglaX/okxezESc6UxDSoyo2Ks6Jxc4Y7sGjpgU9s4PMgqgjj1gZCylTieNamqA1MF7Dg==} + cpu: [x64] + os: [linux] + libc: [musl] + + '@img/sharp-linux-arm64@0.34.5': + resolution: {integrity: sha512-bKQzaJRY/bkPOXyKx5EVup7qkaojECG6NLYswgktOZjaXecSAeCWiZwwiFf3/Y+O1HrauiE3FVsGxFg8c24rZg==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@img/sharp-linux-arm@0.34.5': + resolution: {integrity: sha512-9dLqsvwtg1uuXBGZKsxem9595+ujv0sJ6Vi8wcTANSFpwV/GONat5eCkzQo/1O6zRIkh0m/8+5BjrRr7jDUSZw==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm] + os: [linux] + libc: [glibc] + + '@img/sharp-linux-ppc64@0.34.5': + resolution: {integrity: sha512-7zznwNaqW6YtsfrGGDA6BRkISKAAE1Jo0QdpNYXNMHu2+0dTrPflTLNkpc8l7MUP5M16ZJcUvysVWWrMefZquA==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [ppc64] + os: [linux] + libc: [glibc] + + '@img/sharp-linux-riscv64@0.34.5': + resolution: {integrity: sha512-51gJuLPTKa7piYPaVs8GmByo7/U7/7TZOq+cnXJIHZKavIRHAP77e3N2HEl3dgiqdD/w0yUfiJnII77PuDDFdw==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [riscv64] + os: [linux] + libc: [glibc] + + '@img/sharp-linux-s390x@0.34.5': + resolution: {integrity: sha512-nQtCk0PdKfho3eC5MrbQoigJ2gd1CgddUMkabUj+rBevs8tZ2cULOx46E7oyX+04WGfABgIwmMC0VqieTiR4jg==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [s390x] + os: [linux] + libc: [glibc] + + '@img/sharp-linux-x64@0.34.5': + resolution: {integrity: sha512-MEzd8HPKxVxVenwAa+JRPwEC7QFjoPWuS5NZnBt6B3pu7EG2Ge0id1oLHZpPJdn3OQK+BQDiw9zStiHBTJQQQQ==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@img/sharp-linuxmusl-arm64@0.34.5': + resolution: {integrity: sha512-fprJR6GtRsMt6Kyfq44IsChVZeGN97gTD331weR1ex1c1rypDEABN6Tm2xa1wE6lYb5DdEnk03NZPqA7Id21yg==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@img/sharp-linuxmusl-x64@0.34.5': + resolution: {integrity: sha512-Jg8wNT1MUzIvhBFxViqrEhWDGzqymo3sV7z7ZsaWbZNDLXRJZoRGrjulp60YYtV4wfY8VIKcWidjojlLcWrd8Q==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [x64] + os: [linux] + libc: [musl] + + '@img/sharp-wasm32@0.34.5': + resolution: {integrity: sha512-OdWTEiVkY2PHwqkbBI8frFxQQFekHaSSkUIJkwzclWZe64O1X4UlUjqqqLaPbUpMOQk6FBu/HtlGXNblIs0huw==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [wasm32] + + '@img/sharp-win32-arm64@0.34.5': + resolution: {integrity: sha512-WQ3AgWCWYSb2yt+IG8mnC6Jdk9Whs7O0gxphblsLvdhSpSTtmu69ZG1Gkb6NuvxsNACwiPV6cNSZNzt0KPsw7g==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm64] + os: [win32] + + '@img/sharp-win32-ia32@0.34.5': + resolution: {integrity: sha512-FV9m/7NmeCmSHDD5j4+4pNI8Cp3aW+JvLoXcTUo0IqyjSfAZJ8dIUmijx1qaJsIiU+Hosw6xM5KijAWRJCSgNg==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [ia32] + os: [win32] + + '@img/sharp-win32-x64@0.34.5': + resolution: {integrity: sha512-+29YMsqY2/9eFEiW93eqWnuLcWcufowXewwSNIT6UwZdUUCrM3oFjMWH/Z6/TMmb4hlFenmfAVbpWeup2jryCw==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [x64] + os: [win32] + + '@jridgewell/resolve-uri@3.1.2': + resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} + engines: {node: '>=6.0.0'} + + '@jridgewell/sourcemap-codec@1.5.5': + resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==} + + '@jridgewell/trace-mapping@0.3.9': + resolution: {integrity: sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==} + + '@modelcontextprotocol/sdk@1.25.3': + resolution: {integrity: sha512-vsAMBMERybvYgKbg/l4L1rhS7VXV1c0CtyJg72vwxONVX0l4ZfKVAnZEWTQixJGTzKnELjQ59e4NbdFDALRiAQ==} + engines: {node: '>=18'} + peerDependencies: + '@cfworker/json-schema': ^4.1.1 + zod: ^3.25 || ^4.0 + peerDependenciesMeta: + '@cfworker/json-schema': + optional: true + + '@penpot/plugin-styles@1.4.1': + resolution: {integrity: sha512-6TuJqKQsq1Xmhn2A02R+kCOzIzIdqgFg5z6ncLH2PlAflKIX6aYsGiOF7yFx4RYgCegRVMFPnVis6/hwO+YGQg==} + + '@penpot/plugin-types@1.4.1': + resolution: {integrity: sha512-pHE2B3GI8M5JR03S/NdBoN+z6e1R1IEh3vpFbLG9LN0EZpQE6nEbmCo5jWAWI73Jqlg6CHG/RWVJNmWECnkDTA==} + + '@pinojs/redact@0.4.0': + resolution: {integrity: sha512-k2ENnmBugE/rzQfEcdWHcCY+/FM3VLzH9cYEsbdsoqrvzAKRhUZeRNhAZvB8OitQJ1TBed3yqWtdjzS6wJKBwg==} + + '@rollup/rollup-android-arm-eabi@4.57.0': + resolution: {integrity: sha512-tPgXB6cDTndIe1ah7u6amCI1T0SsnlOuKgg10Xh3uizJk4e5M1JGaUMk7J4ciuAUcFpbOiNhm2XIjP9ON0dUqA==} + cpu: [arm] + os: [android] + + '@rollup/rollup-android-arm64@4.57.0': + resolution: {integrity: sha512-sa4LyseLLXr1onr97StkU1Nb7fWcg6niokTwEVNOO7awaKaoRObQ54+V/hrF/BP1noMEaaAW6Fg2d/CfLiq3Mg==} + cpu: [arm64] + os: [android] + + '@rollup/rollup-darwin-arm64@4.57.0': + resolution: {integrity: sha512-/NNIj9A7yLjKdmkx5dC2XQ9DmjIECpGpwHoGmA5E1AhU0fuICSqSWScPhN1yLCkEdkCwJIDu2xIeLPs60MNIVg==} + cpu: [arm64] + os: [darwin] + + '@rollup/rollup-darwin-x64@4.57.0': + resolution: {integrity: sha512-xoh8abqgPrPYPr7pTYipqnUi1V3em56JzE/HgDgitTqZBZ3yKCWI+7KUkceM6tNweyUKYru1UMi7FC060RyKwA==} + cpu: [x64] + os: [darwin] + + '@rollup/rollup-freebsd-arm64@4.57.0': + resolution: {integrity: sha512-PCkMh7fNahWSbA0OTUQ2OpYHpjZZr0hPr8lId8twD7a7SeWrvT3xJVyza+dQwXSSq4yEQTMoXgNOfMCsn8584g==} + cpu: [arm64] + os: [freebsd] + + '@rollup/rollup-freebsd-x64@4.57.0': + resolution: {integrity: sha512-1j3stGx+qbhXql4OCDZhnK7b01s6rBKNybfsX+TNrEe9JNq4DLi1yGiR1xW+nL+FNVvI4D02PUnl6gJ/2y6WJA==} + cpu: [x64] + os: [freebsd] + + '@rollup/rollup-linux-arm-gnueabihf@4.57.0': + resolution: {integrity: sha512-eyrr5W08Ms9uM0mLcKfM/Uzx7hjhz2bcjv8P2uynfj0yU8GGPdz8iYrBPhiLOZqahoAMB8ZiolRZPbbU2MAi6Q==} + cpu: [arm] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-arm-musleabihf@4.57.0': + resolution: {integrity: sha512-Xds90ITXJCNyX9pDhqf85MKWUI4lqjiPAipJ8OLp8xqI2Ehk+TCVhF9rvOoN8xTbcafow3QOThkNnrM33uCFQA==} + cpu: [arm] + os: [linux] + libc: [musl] + + '@rollup/rollup-linux-arm64-gnu@4.57.0': + resolution: {integrity: sha512-Xws2KA4CLvZmXjy46SQaXSejuKPhwVdaNinldoYfqruZBaJHqVo6hnRa8SDo9z7PBW5x84SH64+izmldCgbezw==} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-arm64-musl@4.57.0': + resolution: {integrity: sha512-hrKXKbX5FdaRJj7lTMusmvKbhMJSGWJ+w++4KmjiDhpTgNlhYobMvKfDoIWecy4O60K6yA4SnztGuNTQF+Lplw==} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@rollup/rollup-linux-loong64-gnu@4.57.0': + resolution: {integrity: sha512-6A+nccfSDGKsPm00d3xKcrsBcbqzCTAukjwWK6rbuAnB2bHaL3r9720HBVZ/no7+FhZLz/U3GwwZZEh6tOSI8Q==} + cpu: [loong64] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-loong64-musl@4.57.0': + resolution: {integrity: sha512-4P1VyYUe6XAJtQH1Hh99THxr0GKMMwIXsRNOceLrJnaHTDgk1FTcTimDgneRJPvB3LqDQxUmroBclQ1S0cIJwQ==} + cpu: [loong64] + os: [linux] + libc: [musl] + + '@rollup/rollup-linux-ppc64-gnu@4.57.0': + resolution: {integrity: sha512-8Vv6pLuIZCMcgXre6c3nOPhE0gjz1+nZP6T+hwWjr7sVH8k0jRkH+XnfjjOTglyMBdSKBPPz54/y1gToSKwrSQ==} + cpu: [ppc64] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-ppc64-musl@4.57.0': + resolution: {integrity: sha512-r1te1M0Sm2TBVD/RxBPC6RZVwNqUTwJTA7w+C/IW5v9Ssu6xmxWEi+iJQlpBhtUiT1raJ5b48pI8tBvEjEFnFA==} + cpu: [ppc64] + os: [linux] + libc: [musl] + + '@rollup/rollup-linux-riscv64-gnu@4.57.0': + resolution: {integrity: sha512-say0uMU/RaPm3CDQLxUUTF2oNWL8ysvHkAjcCzV2znxBr23kFfaxocS9qJm+NdkRhF8wtdEEAJuYcLPhSPbjuQ==} + cpu: [riscv64] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-riscv64-musl@4.57.0': + resolution: {integrity: sha512-/MU7/HizQGsnBREtRpcSbSV1zfkoxSTR7wLsRmBPQ8FwUj5sykrP1MyJTvsxP5KBq9SyE6kH8UQQQwa0ASeoQQ==} + cpu: [riscv64] + os: [linux] + libc: [musl] + + '@rollup/rollup-linux-s390x-gnu@4.57.0': + resolution: {integrity: sha512-Q9eh+gUGILIHEaJf66aF6a414jQbDnn29zeu0eX3dHMuysnhTvsUvZTCAyZ6tJhUjnvzBKE4FtuaYxutxRZpOg==} + cpu: [s390x] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-x64-gnu@4.57.0': + resolution: {integrity: sha512-OR5p5yG5OKSxHReWmwvM0P+VTPMwoBS45PXTMYaskKQqybkS3Kmugq1W+YbNWArF8/s7jQScgzXUhArzEQ7x0A==} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-x64-musl@4.57.0': + resolution: {integrity: sha512-XeatKzo4lHDsVEbm1XDHZlhYZZSQYym6dg2X/Ko0kSFgio+KXLsxwJQprnR48GvdIKDOpqWqssC3iBCjoMcMpw==} + cpu: [x64] + os: [linux] + libc: [musl] + + '@rollup/rollup-openbsd-x64@4.57.0': + resolution: {integrity: sha512-Lu71y78F5qOfYmubYLHPcJm74GZLU6UJ4THkf/a1K7Tz2ycwC2VUbsqbJAXaR6Bx70SRdlVrt2+n5l7F0agTUw==} + cpu: [x64] + os: [openbsd] + + '@rollup/rollup-openharmony-arm64@4.57.0': + resolution: {integrity: sha512-v5xwKDWcu7qhAEcsUubiav7r+48Uk/ENWdr82MBZZRIm7zThSxCIVDfb3ZeRRq9yqk+oIzMdDo6fCcA5DHfMyA==} + cpu: [arm64] + os: [openharmony] + + '@rollup/rollup-win32-arm64-msvc@4.57.0': + resolution: {integrity: sha512-XnaaaSMGSI6Wk8F4KK3QP7GfuuhjGchElsVerCplUuxRIzdvZ7hRBpLR0omCmw+kI2RFJB80nenhOoGXlJ5TfQ==} + cpu: [arm64] + os: [win32] + + '@rollup/rollup-win32-ia32-msvc@4.57.0': + resolution: {integrity: sha512-3K1lP+3BXY4t4VihLw5MEg6IZD3ojSYzqzBG571W3kNQe4G4CcFpSUQVgurYgib5d+YaCjeFow8QivWp8vuSvA==} + cpu: [ia32] + os: [win32] + + '@rollup/rollup-win32-x64-gnu@4.57.0': + resolution: {integrity: sha512-MDk610P/vJGc5L5ImE4k5s+GZT3en0KoK1MKPXCRgzmksAMk79j4h3k1IerxTNqwDLxsGxStEZVBqG0gIqZqoA==} + cpu: [x64] + os: [win32] + + '@rollup/rollup-win32-x64-msvc@4.57.0': + resolution: {integrity: sha512-Zv7v6q6aV+VslnpwzqKAmrk5JdVkLUzok2208ZXGipjb+msxBr/fJPZyeEXiFgH7k62Ak0SLIfxQRZQvTuf7rQ==} + cpu: [x64] + os: [win32] + + '@tsconfig/node10@1.0.12': + resolution: {integrity: sha512-UCYBaeFvM11aU2y3YPZ//O5Rhj+xKyzy7mvcIoAjASbigy8mHMryP5cK7dgjlz2hWxh1g5pLw084E0a/wlUSFQ==} + + '@tsconfig/node12@1.0.11': + resolution: {integrity: sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==} + + '@tsconfig/node14@1.0.3': + resolution: {integrity: sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==} + + '@tsconfig/node16@1.0.4': + resolution: {integrity: sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==} + + '@types/ansi-html@0.0.0': + resolution: {integrity: sha512-PEBpUlteD0VW02udY7UjjgjxHwVXmkdanhmRIMkzatGmORJGjzqKylrXVxz1G5xRTEECMxIkwTHpPmZ9Jb7ANQ==} + + '@types/body-parser@1.19.6': + resolution: {integrity: sha512-HLFeCYgz89uk22N5Qg3dvGvsv46B8GLvKKo1zKG4NybA8U2DiEO3w9lqGg29t/tfLRJpJ6iQxnVw4OnB7MoM9g==} + + '@types/connect@3.4.38': + resolution: {integrity: sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==} + + '@types/debug@4.1.12': + resolution: {integrity: sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==} + + '@types/estree@1.0.8': + resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==} + + '@types/express-serve-static-core@4.19.8': + resolution: {integrity: sha512-02S5fmqeoKzVZCHPZid4b8JH2eM5HzQLZWN2FohQEy/0eXTq8VXZfSN6Pcr3F6N9R/vNrj7cpgbhjie6m/1tCA==} + + '@types/express@4.17.25': + resolution: {integrity: sha512-dVd04UKsfpINUnK0yBoYHDF3xu7xVH4BuDotC/xGuycx4CgbP48X/KF/586bcObxT0HENHXEU8Nqtu6NR+eKhw==} + + '@types/http-errors@2.0.5': + resolution: {integrity: sha512-r8Tayk8HJnX0FztbZN7oVqGccWgw98T/0neJphO91KkmOzug1KkofZURD4UaD5uH8AqcFLfdPErnBod0u71/qg==} + + '@types/js-yaml@4.0.9': + resolution: {integrity: sha512-k4MGaQl5TGo/iipqb2UDG2UwjXziSWkh0uysQelTlJpX1qGlpUZYm8PnO4DxG1qBomtJUdYJ6qR6xdIah10JLg==} + + '@types/mime@1.3.5': + resolution: {integrity: sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==} + + '@types/ms@2.1.0': + resolution: {integrity: sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==} + + '@types/node@20.19.30': + resolution: {integrity: sha512-WJtwWJu7UdlvzEAUm484QNg5eAoq5QR08KDNx7g45Usrs2NtOPiX8ugDqmKdXkyL03rBqU5dYNYVQetEpBHq2g==} + + '@types/qs@6.14.0': + resolution: {integrity: sha512-eOunJqu0K1923aExK6y8p6fsihYEn/BYuQ4g0CxAAgFc4b/ZLN4CrsRZ55srTdqoiLzU2B2evC+apEIxprEzkQ==} + + '@types/range-parser@1.2.7': + resolution: {integrity: sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==} + + '@types/send@0.17.6': + resolution: {integrity: sha512-Uqt8rPBE8SY0RK8JB1EzVOIZ32uqy8HwdxCnoCOsYrvnswqmFZ/k+9Ikidlk/ImhsdvBsloHbAlewb2IEBV/Og==} + + '@types/send@1.2.1': + resolution: {integrity: sha512-arsCikDvlU99zl1g69TcAB3mzZPpxgw0UQnaHeC1Nwb015xp8bknZv5rIfri9xTOcMuaVgvabfIRA7PSZVuZIQ==} + + '@types/serve-static@1.15.10': + resolution: {integrity: sha512-tRs1dB+g8Itk72rlSI2ZrW6vZg0YrLI81iQSTkMmOqnqCaNr/8Ek4VwWcN5vZgCYWbg/JJSGBlUaYGAOP73qBw==} + + '@types/validator@13.15.10': + resolution: {integrity: sha512-T8L6i7wCuyoK8A/ZeLYt1+q0ty3Zb9+qbSSvrIVitzT3YjZqkTZ40IbRsPanlB4h1QB3JVL1SYCdR6ngtFYcuA==} + + '@types/ws@8.18.1': + resolution: {integrity: sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==} + + accepts@2.0.0: + resolution: {integrity: sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==} + engines: {node: '>= 0.6'} + + acorn-walk@8.3.4: + resolution: {integrity: sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==} + engines: {node: '>=0.4.0'} + + acorn@8.15.0: + resolution: {integrity: sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==} + engines: {node: '>=0.4.0'} + hasBin: true + + ajv-formats@3.0.1: + resolution: {integrity: sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==} + peerDependencies: + ajv: ^8.0.0 + peerDependenciesMeta: + ajv: + optional: true + + ajv@8.17.1: + resolution: {integrity: sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==} + + ansi-html@0.0.9: + resolution: {integrity: sha512-ozbS3LuenHVxNRh/wdnN16QapUHzauqSomAl1jwwJRRsGwFwtj644lIhxfWu0Fy0acCij2+AEgHvjscq3dlVXg==} + engines: {'0': node >= 0.8.0} + hasBin: true + + ansi-regex@5.0.1: + resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} + engines: {node: '>=8'} + + ansi-styles@4.3.0: + resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} + engines: {node: '>=8'} + + arg@4.1.3: + resolution: {integrity: sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==} + + argparse@2.0.1: + resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} + + atomic-sleep@1.0.0: + resolution: {integrity: sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ==} + engines: {node: '>=8.0.0'} + + body-parser@2.2.2: + resolution: {integrity: sha512-oP5VkATKlNwcgvxi0vM0p/D3n2C3EReYVX+DNYs5TjZFn/oQt2j+4sVJtSMr18pdRr8wjTcBl6LoV+FUwzPmNA==} + engines: {node: '>=18'} + + bytes@3.1.2: + resolution: {integrity: sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==} + engines: {node: '>= 0.8'} + + call-bind-apply-helpers@1.0.2: + resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==} + engines: {node: '>= 0.4'} + + call-bound@1.0.4: + resolution: {integrity: sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==} + engines: {node: '>= 0.4'} + + chalk@4.1.2: + resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} + engines: {node: '>=10'} + + chalk@5.6.2: + resolution: {integrity: sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==} + engines: {node: ^12.17.0 || ^14.13 || >=16.0.0} + + class-transformer@0.5.1: + resolution: {integrity: sha512-SQa1Ws6hUbfC98vKGxZH3KFY0Y1lm5Zm0SY8XX9zbK7FJCyVEac3ATW0RIpwzW+oOfmHE5PMPufDG9hCfoEOMw==} + + class-validator@0.14.3: + resolution: {integrity: sha512-rXXekcjofVN1LTOSw+u4u9WXVEUvNBVjORW154q/IdmYWy1nMbOU9aNtZB0t8m+FJQ9q91jlr2f9CwwUFdFMRA==} + + cliui@8.0.1: + resolution: {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==} + engines: {node: '>=12'} + + color-convert@2.0.1: + resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} + engines: {node: '>=7.0.0'} + + color-name@1.1.4: + resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} + + colorette@2.0.20: + resolution: {integrity: sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==} + + commander@12.1.0: + resolution: {integrity: sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==} + engines: {node: '>=18'} + + concurrently@9.2.1: + resolution: {integrity: sha512-fsfrO0MxV64Znoy8/l1vVIjjHa29SZyyqPgQBwhiDcaW8wJc2W3XWVOGx4M3oJBnv/zdUZIIp1gDeS98GzP8Ng==} + engines: {node: '>=18'} + hasBin: true + + content-disposition@1.0.1: + resolution: {integrity: sha512-oIXISMynqSqm241k6kcQ5UwttDILMK4BiurCfGEREw6+X9jkkpEe5T9FZaApyLGGOnFuyMWZpdolTXMtvEJ08Q==} + engines: {node: '>=18'} + + content-type@1.0.5: + resolution: {integrity: sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==} + engines: {node: '>= 0.6'} + + cookie-signature@1.2.2: + resolution: {integrity: sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==} + engines: {node: '>=6.6.0'} + + cookie@0.7.2: + resolution: {integrity: sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==} + engines: {node: '>= 0.6'} + + cors@2.8.6: + resolution: {integrity: sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw==} + engines: {node: '>= 0.10'} + + create-require@1.1.1: + resolution: {integrity: sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==} + + cross-env@7.0.3: + resolution: {integrity: sha512-+/HKd6EgcQCJGh2PSjZuUitQBQynKor4wrFbRg4DtAgS1aWO+gU52xpH7M9ScGgXSYmAVS9bIJ8EzuaGw0oNAw==} + engines: {node: '>=10.14', npm: '>=6', yarn: '>=1'} + hasBin: true + + cross-spawn@7.0.6: + resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} + engines: {node: '>= 8'} + + dateformat@4.6.3: + resolution: {integrity: sha512-2P0p0pFGzHS5EMnhdxQi7aJN+iMheud0UhG4dlE1DLAlvL8JHjJJTX/CSm4JXwV0Ka5nGk3zC5mcb5bUQUxxMA==} + + debug@4.4.3: + resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + + depd@2.0.0: + resolution: {integrity: sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==} + engines: {node: '>= 0.8'} + + detect-libc@2.1.2: + resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==} + engines: {node: '>=8'} + + diff@4.0.4: + resolution: {integrity: sha512-X07nttJQkwkfKfvTPG/KSnE2OMdcUCao6+eXF3wmnIQRn2aPAHH3VxDbDOdegkd6JbPsXqShpvEOHfAT+nCNwQ==} + engines: {node: '>=0.3.1'} + + dunder-proto@1.0.1: + resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} + engines: {node: '>= 0.4'} + + ee-first@1.1.1: + resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==} + + emoji-regex@8.0.0: + resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} + + encodeurl@2.0.0: + resolution: {integrity: sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==} + engines: {node: '>= 0.8'} + + end-of-stream@1.4.5: + resolution: {integrity: sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==} + + es-define-property@1.0.1: + resolution: {integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==} + engines: {node: '>= 0.4'} + + es-errors@1.3.0: + resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==} + engines: {node: '>= 0.4'} + + es-object-atoms@1.1.1: + resolution: {integrity: sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==} + engines: {node: '>= 0.4'} + + esbuild@0.25.12: + resolution: {integrity: sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==} + engines: {node: '>=18'} + hasBin: true + + esbuild@0.27.2: + resolution: {integrity: sha512-HyNQImnsOC7X9PMNaCIeAm4ISCQXs5a5YasTXVliKv4uuBo1dKrG0A+uQS8M5eXjVMnLg3WgXaKvprHlFJQffw==} + engines: {node: '>=18'} + hasBin: true + + escalade@3.2.0: + resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} + engines: {node: '>=6'} + + escape-goat@4.0.0: + resolution: {integrity: sha512-2Sd4ShcWxbx6OY1IHyla/CVNwvg7XwZVoXZHcSu9w9SReNP1EzzD5T8NWKIR38fIqEns9kDWKUQTXXAmlDrdPg==} + engines: {node: '>=12'} + + escape-html@1.0.3: + resolution: {integrity: sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==} + + etag@1.8.1: + resolution: {integrity: sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==} + engines: {node: '>= 0.6'} + + eventsource-parser@3.0.6: + resolution: {integrity: sha512-Vo1ab+QXPzZ4tCa8SwIHJFaSzy4R6SHf7BY79rFBDf0idraZWAkYrDjDj8uWaSm3S2TK+hJ7/t1CEmZ7jXw+pg==} + engines: {node: '>=18.0.0'} + + eventsource@3.0.7: + resolution: {integrity: sha512-CRT1WTyuQoD771GW56XEZFQ/ZoSfWid1alKGDYMmkt2yl8UXrVR4pspqWNEcqKvVIzg6PAltWjxcSSPrboA4iA==} + engines: {node: '>=18.0.0'} + + express-rate-limit@7.5.1: + resolution: {integrity: sha512-7iN8iPMDzOMHPUYllBEsQdWVB6fPDMPqwjBaFrgr4Jgr/+okjvzAy+UHlYYL/Vs0OsOrMkwS6PJDkFlJwoxUnw==} + engines: {node: '>= 16'} + peerDependencies: + express: '>= 4.11' + + express@5.2.1: + resolution: {integrity: sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==} + engines: {node: '>= 18'} + + fast-copy@4.0.2: + resolution: {integrity: sha512-ybA6PDXIXOXivLJK/z9e+Otk7ve13I4ckBvGO5I2RRmBU1gMHLVDJYEuJYhGwez7YNlYji2M2DvVU+a9mSFDlw==} + + fast-deep-equal@3.1.3: + resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} + + fast-safe-stringify@2.1.1: + resolution: {integrity: sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==} + + fast-uri@3.1.0: + resolution: {integrity: sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==} + + fdir@6.5.0: + resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==} + engines: {node: '>=12.0.0'} + peerDependencies: + picomatch: ^3 || ^4 + peerDependenciesMeta: + picomatch: + optional: true + + finalhandler@2.1.1: + resolution: {integrity: sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA==} + engines: {node: '>= 18.0.0'} + + forwarded@0.2.0: + resolution: {integrity: sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==} + engines: {node: '>= 0.6'} + + fresh@2.0.0: + resolution: {integrity: sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==} + engines: {node: '>= 0.8'} + + fsevents@2.3.3: + resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + + function-bind@1.1.2: + resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} + + get-caller-file@2.0.5: + resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==} + engines: {node: 6.* || 8.* || >= 10.*} + + get-intrinsic@1.3.0: + resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==} + engines: {node: '>= 0.4'} + + get-proto@1.0.1: + resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==} + engines: {node: '>= 0.4'} + + gopd@1.2.0: + resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==} + engines: {node: '>= 0.4'} + + has-flag@4.0.0: + resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} + engines: {node: '>=8'} + + has-symbols@1.1.0: + resolution: {integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==} + engines: {node: '>= 0.4'} + + hasown@2.0.2: + resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} + engines: {node: '>= 0.4'} + + help-me@5.0.0: + resolution: {integrity: sha512-7xgomUX6ADmcYzFik0HzAxh/73YlKR9bmFzf51CZwR+b6YtzU2m0u49hQCqV6SvlqIqsaxovfwdvbnsw3b/zpg==} + + hono@4.11.7: + resolution: {integrity: sha512-l7qMiNee7t82bH3SeyUCt9UF15EVmaBvsppY2zQtrbIhl/yzBTny+YUxsVjSjQ6gaqaeVtZmGocom8TzBlA4Yw==} + engines: {node: '>=16.9.0'} + + http-errors@2.0.1: + resolution: {integrity: sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==} + engines: {node: '>= 0.8'} + + iconv-lite@0.7.2: + resolution: {integrity: sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==} + engines: {node: '>=0.10.0'} + + inherits@2.0.4: + resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} + + ipaddr.js@1.9.1: + resolution: {integrity: sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==} + engines: {node: '>= 0.10'} + + is-fullwidth-code-point@3.0.0: + resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} + engines: {node: '>=8'} + + is-promise@4.0.0: + resolution: {integrity: sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==} + + isexe@2.0.0: + resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} + + jose@6.1.3: + resolution: {integrity: sha512-0TpaTfihd4QMNwrz/ob2Bp7X04yuxJkjRGi4aKmOqwhov54i6u79oCv7T+C7lo70MKH6BesI3vscD1yb/yzKXQ==} + + joycon@3.1.1: + resolution: {integrity: sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw==} + engines: {node: '>=10'} + + js-yaml@4.1.1: + resolution: {integrity: sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==} + hasBin: true + + json-schema-traverse@1.0.0: + resolution: {integrity: sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==} + + json-schema-typed@8.0.2: + resolution: {integrity: sha512-fQhoXdcvc3V28x7C7BMs4P5+kNlgUURe2jmUT1T//oBRMDrqy1QPelJimwZGo7Hg9VPV3EQV5Bnq4hbFy2vetA==} + + libphonenumber-js@1.12.35: + resolution: {integrity: sha512-T/Cz6iLcsZdb5jDncDcUNhSAJ0VlSC9TnsqtBNdpkaAmy24/R1RhErtNWVWBrcUZKs9hSgaVsBkc7HxYnazIfw==} + + make-error@1.3.6: + resolution: {integrity: sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==} + + math-intrinsics@1.1.0: + resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} + engines: {node: '>= 0.4'} + + media-typer@1.1.0: + resolution: {integrity: sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==} + engines: {node: '>= 0.8'} + + merge-descriptors@2.0.0: + resolution: {integrity: sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==} + engines: {node: '>=18'} + + mime-db@1.54.0: + resolution: {integrity: sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==} + engines: {node: '>= 0.6'} + + mime-types@3.0.2: + resolution: {integrity: sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==} + engines: {node: '>=18'} + + minimist@1.2.8: + resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} + + ms@2.1.3: + resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + + nanoid@3.3.11: + resolution: {integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==} + engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} + hasBin: true + + negotiator@1.0.0: + resolution: {integrity: sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==} + engines: {node: '>= 0.6'} + + object-assign@4.1.1: + resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} + engines: {node: '>=0.10.0'} + + object-inspect@1.13.4: + resolution: {integrity: sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==} + engines: {node: '>= 0.4'} + + on-exit-leak-free@2.1.2: + resolution: {integrity: sha512-0eJJY6hXLGf1udHwfNftBqH+g73EU4B504nZeKpz1sYRKafAghwxEJunB2O7rDZkL4PGfsMVnTXZ2EjibbqcsA==} + engines: {node: '>=14.0.0'} + + on-finished@2.4.1: + resolution: {integrity: sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==} + engines: {node: '>= 0.8'} + + once@1.4.0: + resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} + + p-defer@4.0.1: + resolution: {integrity: sha512-Mr5KC5efvAK5VUptYEIopP1bakB85k2IWXaRC0rsh1uwn1L6M0LVml8OIQ4Gudg4oyZakf7FmeRLkMMtZW1i5A==} + engines: {node: '>=12'} + + packages@file:packages: + resolution: {directory: packages, type: directory} + + parseurl@1.3.3: + resolution: {integrity: sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==} + engines: {node: '>= 0.8'} + + path-key@3.1.1: + resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} + engines: {node: '>=8'} + + path-to-regexp@8.3.0: + resolution: {integrity: sha512-7jdwVIRtsP8MYpdXSwOS0YdD0Du+qOoF/AEPIt88PcCFrZCzx41oxku1jD88hZBwbNUIEfpqvuhjFaMAqMTWnA==} + + picocolors@1.1.1: + resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} + + picomatch@4.0.3: + resolution: {integrity: sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==} + engines: {node: '>=12'} + + pino-abstract-transport@2.0.0: + resolution: {integrity: sha512-F63x5tizV6WCh4R6RHyi2Ml+M70DNRXt/+HANowMflpgGFMAym/VKm6G7ZOQRjqN7XbGxK1Lg9t6ZrtzOaivMw==} + + pino-abstract-transport@3.0.0: + resolution: {integrity: sha512-wlfUczU+n7Hy/Ha5j9a/gZNy7We5+cXp8YL+X+PG8S0KXxw7n/JXA3c46Y0zQznIJ83URJiwy7Lh56WLokNuxg==} + + pino-pretty@13.1.3: + resolution: {integrity: sha512-ttXRkkOz6WWC95KeY9+xxWL6AtImwbyMHrL1mSwqwW9u+vLp/WIElvHvCSDg0xO/Dzrggz1zv3rN5ovTRVowKg==} + hasBin: true + + pino-std-serializers@7.1.0: + resolution: {integrity: sha512-BndPH67/JxGExRgiX1dX0w1FvZck5Wa4aal9198SrRhZjH3GxKQUKIBnYJTdj2HDN3UQAS06HlfcSbQj2OHmaw==} + + pino@9.14.0: + resolution: {integrity: sha512-8OEwKp5juEvb/MjpIc4hjqfgCNysrS94RIOMXYvpYCdm/jglrKEiAYmiumbmGhCvs+IcInsphYDFwqrjr7398w==} + hasBin: true + + pkce-challenge@5.0.1: + resolution: {integrity: sha512-wQ0b/W4Fr01qtpHlqSqspcj3EhBvimsdh0KlHhH8HRZnMsEa0ea2fTULOXOS9ccQr3om+GcGRk4e+isrZWV8qQ==} + engines: {node: '>=16.20.0'} + + postcss@8.5.6: + resolution: {integrity: sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==} + engines: {node: ^10 || ^12 || >=14} + + prettier@3.8.1: + resolution: {integrity: sha512-UOnG6LftzbdaHZcKoPFtOcCKztrQ57WkHDeRD9t/PTQtmT0NHSeWWepj6pS0z/N7+08BHFDQVUrfmfMRcZwbMg==} + engines: {node: '>=14'} + hasBin: true + + process-warning@5.0.0: + resolution: {integrity: sha512-a39t9ApHNx2L4+HBnQKqxxHNs1r7KF+Intd8Q/g1bUh6q0WIp9voPXJ/x0j+ZL45KF1pJd9+q2jLIRMfvEshkA==} + + proxy-addr@2.0.7: + resolution: {integrity: sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==} + engines: {node: '>= 0.10'} + + pump@3.0.3: + resolution: {integrity: sha512-todwxLMY7/heScKmntwQG8CXVkWUOdYxIvY2s0VWAAMh/nd8SoYiRaKjlr7+iCs984f2P8zvrfWcDDYVb73NfA==} + + qs@6.14.1: + resolution: {integrity: sha512-4EK3+xJl8Ts67nLYNwqw/dsFVnCf+qR7RgXSK9jEEm9unao3njwMDdmsdvoKBKHzxd7tCYz5e5M+SnMjdtXGQQ==} + engines: {node: '>=0.6'} + + quick-format-unescaped@4.0.4: + resolution: {integrity: sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg==} + + range-parser@1.2.1: + resolution: {integrity: sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==} + engines: {node: '>= 0.6'} + + raw-body@3.0.2: + resolution: {integrity: sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==} + engines: {node: '>= 0.10'} + + real-require@0.2.0: + resolution: {integrity: sha512-57frrGM/OCTLqLOAh0mhVA9VBMHd+9U7Zb2THMGdBUoZVOtGbJzjxsYGDJ3A9AYYCP4hn6y1TVbaOfzWtm5GFg==} + engines: {node: '>= 12.13.0'} + + reflect-metadata@0.1.14: + resolution: {integrity: sha512-ZhYeb6nRaXCfhnndflDK8qI6ZQ/YcWZCISRAWICW9XYqMUwjZM9Z0DveWX/ABN01oxSHwVxKQmxeYZSsm0jh5A==} + + require-directory@2.1.1: + resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==} + engines: {node: '>=0.10.0'} + + require-from-string@2.0.2: + resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==} + engines: {node: '>=0.10.0'} + + rollup@4.57.0: + resolution: {integrity: sha512-e5lPJi/aui4TO1LpAXIRLySmwXSE8k3b9zoGfd42p67wzxog4WHjiZF3M2uheQih4DGyc25QEV4yRBbpueNiUA==} + engines: {node: '>=18.0.0', npm: '>=8.0.0'} + hasBin: true + + router@2.2.0: + resolution: {integrity: sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==} + engines: {node: '>= 18'} + + rxjs@7.8.2: + resolution: {integrity: sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==} + + safe-stable-stringify@2.5.0: + resolution: {integrity: sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA==} + engines: {node: '>=10'} + + safer-buffer@2.1.2: + resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} + + secure-json-parse@4.1.0: + resolution: {integrity: sha512-l4KnYfEyqYJxDwlNVyRfO2E4NTHfMKAWdUuA8J0yve2Dz/E/PdBepY03RvyJpssIpRFwJoCD55wA+mEDs6ByWA==} + + semver@7.7.3: + resolution: {integrity: sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==} + engines: {node: '>=10'} + hasBin: true + + send@1.2.1: + resolution: {integrity: sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ==} + engines: {node: '>= 18'} + + serve-static@2.2.1: + resolution: {integrity: sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw==} + engines: {node: '>= 18'} + + setprototypeof@1.2.0: + resolution: {integrity: sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==} + + sharp@0.34.5: + resolution: {integrity: sha512-Ou9I5Ft9WNcCbXrU9cMgPBcCK8LiwLqcbywW3t4oDV37n1pzpuNLsYiAV8eODnjbtQlSDwZ2cUEeQz4E54Hltg==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + + shebang-command@2.0.0: + resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} + engines: {node: '>=8'} + + shebang-regex@3.0.0: + resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} + engines: {node: '>=8'} + + shell-quote@1.8.3: + resolution: {integrity: sha512-ObmnIF4hXNg1BqhnHmgbDETF8dLPCggZWBjkQfhZpbszZnYur5DUljTcCHii5LC3J5E0yeO/1LIMyH+UvHQgyw==} + engines: {node: '>= 0.4'} + + side-channel-list@1.0.0: + resolution: {integrity: sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==} + engines: {node: '>= 0.4'} + + side-channel-map@1.0.1: + resolution: {integrity: sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==} + engines: {node: '>= 0.4'} + + side-channel-weakmap@1.0.2: + resolution: {integrity: sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==} + engines: {node: '>= 0.4'} + + side-channel@1.1.0: + resolution: {integrity: sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==} + engines: {node: '>= 0.4'} + + sonic-boom@4.2.0: + resolution: {integrity: sha512-INb7TM37/mAcsGmc9hyyI6+QR3rR1zVRu36B0NeGXKnOOLiZOfER5SA+N7X7k3yUYRzLWafduTDvJAfDswwEww==} + + source-map-js@1.2.1: + resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} + engines: {node: '>=0.10.0'} + + split2@4.2.0: + resolution: {integrity: sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==} + engines: {node: '>= 10.x'} + + statuses@2.0.2: + resolution: {integrity: sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==} + engines: {node: '>= 0.8'} + + string-width@4.2.3: + resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} + engines: {node: '>=8'} + + strip-ansi@6.0.1: + resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} + engines: {node: '>=8'} + + strip-json-comments@5.0.3: + resolution: {integrity: sha512-1tB5mhVo7U+ETBKNf92xT4hrQa3pm0MZ0PQvuDnWgAAGHDsfp4lPSpiS6psrSiet87wyGPh9ft6wmhOMQ0hDiw==} + engines: {node: '>=14.16'} + + supports-color@7.2.0: + resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} + engines: {node: '>=8'} + + supports-color@8.1.1: + resolution: {integrity: sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==} + engines: {node: '>=10'} + + thread-stream@3.1.0: + resolution: {integrity: sha512-OqyPZ9u96VohAyMfJykzmivOrY2wfMSf3C5TtFJVgN+Hm6aj+voFhlK+kZEIv2FBh1X6Xp3DlnCOfEQ3B2J86A==} + + tinyglobby@0.2.15: + resolution: {integrity: sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==} + engines: {node: '>=12.0.0'} + + toidentifier@1.0.1: + resolution: {integrity: sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==} + engines: {node: '>=0.6'} + + tree-kill@1.2.2: + resolution: {integrity: sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==} + hasBin: true + + ts-node@10.9.2: + resolution: {integrity: sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==} + hasBin: true + peerDependencies: + '@swc/core': '>=1.2.50' + '@swc/wasm': '>=1.2.50' + '@types/node': '*' + typescript: '>=2.7' + peerDependenciesMeta: + '@swc/core': + optional: true + '@swc/wasm': + optional: true + + tslib@2.8.1: + resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} + + type-is@2.0.1: + resolution: {integrity: sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==} + engines: {node: '>= 0.6'} + + typescript@5.9.3: + resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==} + engines: {node: '>=14.17'} + hasBin: true + + undici-types@6.21.0: + resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==} + + unpipe@1.0.0: + resolution: {integrity: sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==} + engines: {node: '>= 0.8'} + + v8-compile-cache-lib@3.0.1: + resolution: {integrity: sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==} + + validator@13.15.26: + resolution: {integrity: sha512-spH26xU080ydGggxRyR1Yhcbgx+j3y5jbNXk/8L+iRvdIEQ4uTRH2Sgf2dokud6Q4oAtsbNvJ1Ft+9xmm6IZcA==} + engines: {node: '>= 0.10'} + + vary@1.1.2: + resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==} + engines: {node: '>= 0.8'} + + vite-live-preview@0.3.2: + resolution: {integrity: sha512-NrmGaAc85qvkx/+6FluiTo9rLnoY+/NOYnuUvcW5Yb5tSJzUxuloXYrCSS1dtxQB9YKUbpQ95JCb0GRuF//JEQ==} + hasBin: true + peerDependencies: + vite: '>=5.2.13' + + vite@7.3.1: + resolution: {integrity: sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA==} + engines: {node: ^20.19.0 || >=22.12.0} + hasBin: true + peerDependencies: + '@types/node': ^20.19.0 || >=22.12.0 + jiti: '>=1.21.0' + less: ^4.0.0 + lightningcss: ^1.21.0 + sass: ^1.70.0 + sass-embedded: ^1.70.0 + stylus: '>=0.54.8' + sugarss: ^5.0.0 + terser: ^5.16.0 + tsx: ^4.8.1 + yaml: ^2.4.2 + peerDependenciesMeta: + '@types/node': + optional: true + jiti: + optional: true + less: + optional: true + lightningcss: + optional: true + sass: + optional: true + sass-embedded: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + tsx: + optional: true + yaml: + optional: true + + which@2.0.2: + resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} + engines: {node: '>= 8'} + hasBin: true + + wrap-ansi@7.0.0: + resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} + engines: {node: '>=10'} + + wrappy@1.0.2: + resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} + + ws@8.19.0: + resolution: {integrity: sha512-blAT2mjOEIi0ZzruJfIhb3nps74PRWTCz1IjglWEEpQl5XS/UNama6u2/rjFkDDouqr4L67ry+1aGIALViWjDg==} + engines: {node: '>=10.0.0'} + peerDependencies: + bufferutil: ^4.0.1 + utf-8-validate: '>=5.0.2' + peerDependenciesMeta: + bufferutil: + optional: true + utf-8-validate: + optional: true + + y18n@5.0.8: + resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==} + engines: {node: '>=10'} + + yargs-parser@21.1.1: + resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==} + engines: {node: '>=12'} + + yargs@17.7.2: + resolution: {integrity: sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==} + engines: {node: '>=12'} + + yn@3.1.1: + resolution: {integrity: sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==} + engines: {node: '>=6'} + + zod-to-json-schema@3.25.1: + resolution: {integrity: sha512-pM/SU9d3YAggzi6MtR4h7ruuQlqKtad8e9S0fmxcMi+ueAK5Korys/aWcV9LIIHTVbj01NdzxcnXSN+O74ZIVA==} + peerDependencies: + zod: ^3.25 || ^4 + + zod@4.3.6: + resolution: {integrity: sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==} + +snapshots: + + '@commander-js/extra-typings@12.1.0(commander@12.1.0)': + dependencies: + commander: 12.1.0 + + '@cspotcode/source-map-support@0.8.1': + dependencies: + '@jridgewell/trace-mapping': 0.3.9 + + '@emnapi/runtime@1.8.1': + dependencies: + tslib: 2.8.1 + optional: true + + '@esbuild/aix-ppc64@0.25.12': + optional: true + + '@esbuild/aix-ppc64@0.27.2': + optional: true + + '@esbuild/android-arm64@0.25.12': + optional: true + + '@esbuild/android-arm64@0.27.2': + optional: true + + '@esbuild/android-arm@0.25.12': + optional: true + + '@esbuild/android-arm@0.27.2': + optional: true + + '@esbuild/android-x64@0.25.12': + optional: true + + '@esbuild/android-x64@0.27.2': + optional: true + + '@esbuild/darwin-arm64@0.25.12': + optional: true + + '@esbuild/darwin-arm64@0.27.2': + optional: true + + '@esbuild/darwin-x64@0.25.12': + optional: true + + '@esbuild/darwin-x64@0.27.2': + optional: true + + '@esbuild/freebsd-arm64@0.25.12': + optional: true + + '@esbuild/freebsd-arm64@0.27.2': + optional: true + + '@esbuild/freebsd-x64@0.25.12': + optional: true + + '@esbuild/freebsd-x64@0.27.2': + optional: true + + '@esbuild/linux-arm64@0.25.12': + optional: true + + '@esbuild/linux-arm64@0.27.2': + optional: true + + '@esbuild/linux-arm@0.25.12': + optional: true + + '@esbuild/linux-arm@0.27.2': + optional: true + + '@esbuild/linux-ia32@0.25.12': + optional: true + + '@esbuild/linux-ia32@0.27.2': + optional: true + + '@esbuild/linux-loong64@0.25.12': + optional: true + + '@esbuild/linux-loong64@0.27.2': + optional: true + + '@esbuild/linux-mips64el@0.25.12': + optional: true + + '@esbuild/linux-mips64el@0.27.2': + optional: true + + '@esbuild/linux-ppc64@0.25.12': + optional: true + + '@esbuild/linux-ppc64@0.27.2': + optional: true + + '@esbuild/linux-riscv64@0.25.12': + optional: true + + '@esbuild/linux-riscv64@0.27.2': + optional: true + + '@esbuild/linux-s390x@0.25.12': + optional: true + + '@esbuild/linux-s390x@0.27.2': + optional: true + + '@esbuild/linux-x64@0.25.12': + optional: true + + '@esbuild/linux-x64@0.27.2': + optional: true + + '@esbuild/netbsd-arm64@0.25.12': + optional: true + + '@esbuild/netbsd-arm64@0.27.2': + optional: true + + '@esbuild/netbsd-x64@0.25.12': + optional: true + + '@esbuild/netbsd-x64@0.27.2': + optional: true + + '@esbuild/openbsd-arm64@0.25.12': + optional: true + + '@esbuild/openbsd-arm64@0.27.2': + optional: true + + '@esbuild/openbsd-x64@0.25.12': + optional: true + + '@esbuild/openbsd-x64@0.27.2': + optional: true + + '@esbuild/openharmony-arm64@0.25.12': + optional: true + + '@esbuild/openharmony-arm64@0.27.2': + optional: true + + '@esbuild/sunos-x64@0.25.12': + optional: true + + '@esbuild/sunos-x64@0.27.2': + optional: true + + '@esbuild/win32-arm64@0.25.12': + optional: true + + '@esbuild/win32-arm64@0.27.2': + optional: true + + '@esbuild/win32-ia32@0.25.12': + optional: true + + '@esbuild/win32-ia32@0.27.2': + optional: true + + '@esbuild/win32-x64@0.25.12': + optional: true + + '@esbuild/win32-x64@0.27.2': + optional: true + + '@hono/node-server@1.19.9(hono@4.11.7)': + dependencies: + hono: 4.11.7 + + '@img/colour@1.0.0': {} + + '@img/sharp-darwin-arm64@0.34.5': + optionalDependencies: + '@img/sharp-libvips-darwin-arm64': 1.2.4 + optional: true + + '@img/sharp-darwin-x64@0.34.5': + optionalDependencies: + '@img/sharp-libvips-darwin-x64': 1.2.4 + optional: true + + '@img/sharp-libvips-darwin-arm64@1.2.4': + optional: true + + '@img/sharp-libvips-darwin-x64@1.2.4': + optional: true + + '@img/sharp-libvips-linux-arm64@1.2.4': + optional: true + + '@img/sharp-libvips-linux-arm@1.2.4': + optional: true + + '@img/sharp-libvips-linux-ppc64@1.2.4': + optional: true + + '@img/sharp-libvips-linux-riscv64@1.2.4': + optional: true + + '@img/sharp-libvips-linux-s390x@1.2.4': + optional: true + + '@img/sharp-libvips-linux-x64@1.2.4': + optional: true + + '@img/sharp-libvips-linuxmusl-arm64@1.2.4': + optional: true + + '@img/sharp-libvips-linuxmusl-x64@1.2.4': + optional: true + + '@img/sharp-linux-arm64@0.34.5': + optionalDependencies: + '@img/sharp-libvips-linux-arm64': 1.2.4 + optional: true + + '@img/sharp-linux-arm@0.34.5': + optionalDependencies: + '@img/sharp-libvips-linux-arm': 1.2.4 + optional: true + + '@img/sharp-linux-ppc64@0.34.5': + optionalDependencies: + '@img/sharp-libvips-linux-ppc64': 1.2.4 + optional: true + + '@img/sharp-linux-riscv64@0.34.5': + optionalDependencies: + '@img/sharp-libvips-linux-riscv64': 1.2.4 + optional: true + + '@img/sharp-linux-s390x@0.34.5': + optionalDependencies: + '@img/sharp-libvips-linux-s390x': 1.2.4 + optional: true + + '@img/sharp-linux-x64@0.34.5': + optionalDependencies: + '@img/sharp-libvips-linux-x64': 1.2.4 + optional: true + + '@img/sharp-linuxmusl-arm64@0.34.5': + optionalDependencies: + '@img/sharp-libvips-linuxmusl-arm64': 1.2.4 + optional: true + + '@img/sharp-linuxmusl-x64@0.34.5': + optionalDependencies: + '@img/sharp-libvips-linuxmusl-x64': 1.2.4 + optional: true + + '@img/sharp-wasm32@0.34.5': + dependencies: + '@emnapi/runtime': 1.8.1 + optional: true + + '@img/sharp-win32-arm64@0.34.5': + optional: true + + '@img/sharp-win32-ia32@0.34.5': + optional: true + + '@img/sharp-win32-x64@0.34.5': + optional: true + + '@jridgewell/resolve-uri@3.1.2': {} + + '@jridgewell/sourcemap-codec@1.5.5': {} + + '@jridgewell/trace-mapping@0.3.9': + dependencies: + '@jridgewell/resolve-uri': 3.1.2 + '@jridgewell/sourcemap-codec': 1.5.5 + + '@modelcontextprotocol/sdk@1.25.3(hono@4.11.7)(zod@4.3.6)': + dependencies: + '@hono/node-server': 1.19.9(hono@4.11.7) + ajv: 8.17.1 + ajv-formats: 3.0.1(ajv@8.17.1) + content-type: 1.0.5 + cors: 2.8.6 + cross-spawn: 7.0.6 + eventsource: 3.0.7 + eventsource-parser: 3.0.6 + express: 5.2.1 + express-rate-limit: 7.5.1(express@5.2.1) + jose: 6.1.3 + json-schema-typed: 8.0.2 + pkce-challenge: 5.0.1 + raw-body: 3.0.2 + zod: 4.3.6 + zod-to-json-schema: 3.25.1(zod@4.3.6) + transitivePeerDependencies: + - hono + - supports-color + + '@penpot/plugin-styles@1.4.1': {} + + '@penpot/plugin-types@1.4.1': {} + + '@pinojs/redact@0.4.0': {} + + '@rollup/rollup-android-arm-eabi@4.57.0': + optional: true + + '@rollup/rollup-android-arm64@4.57.0': + optional: true + + '@rollup/rollup-darwin-arm64@4.57.0': + optional: true + + '@rollup/rollup-darwin-x64@4.57.0': + optional: true + + '@rollup/rollup-freebsd-arm64@4.57.0': + optional: true + + '@rollup/rollup-freebsd-x64@4.57.0': + optional: true + + '@rollup/rollup-linux-arm-gnueabihf@4.57.0': + optional: true + + '@rollup/rollup-linux-arm-musleabihf@4.57.0': + optional: true + + '@rollup/rollup-linux-arm64-gnu@4.57.0': + optional: true + + '@rollup/rollup-linux-arm64-musl@4.57.0': + optional: true + + '@rollup/rollup-linux-loong64-gnu@4.57.0': + optional: true + + '@rollup/rollup-linux-loong64-musl@4.57.0': + optional: true + + '@rollup/rollup-linux-ppc64-gnu@4.57.0': + optional: true + + '@rollup/rollup-linux-ppc64-musl@4.57.0': + optional: true + + '@rollup/rollup-linux-riscv64-gnu@4.57.0': + optional: true + + '@rollup/rollup-linux-riscv64-musl@4.57.0': + optional: true + + '@rollup/rollup-linux-s390x-gnu@4.57.0': + optional: true + + '@rollup/rollup-linux-x64-gnu@4.57.0': + optional: true + + '@rollup/rollup-linux-x64-musl@4.57.0': + optional: true + + '@rollup/rollup-openbsd-x64@4.57.0': + optional: true + + '@rollup/rollup-openharmony-arm64@4.57.0': + optional: true + + '@rollup/rollup-win32-arm64-msvc@4.57.0': + optional: true + + '@rollup/rollup-win32-ia32-msvc@4.57.0': + optional: true + + '@rollup/rollup-win32-x64-gnu@4.57.0': + optional: true + + '@rollup/rollup-win32-x64-msvc@4.57.0': + optional: true + + '@tsconfig/node10@1.0.12': {} + + '@tsconfig/node12@1.0.11': {} + + '@tsconfig/node14@1.0.3': {} + + '@tsconfig/node16@1.0.4': {} + + '@types/ansi-html@0.0.0': {} + + '@types/body-parser@1.19.6': + dependencies: + '@types/connect': 3.4.38 + '@types/node': 20.19.30 + + '@types/connect@3.4.38': + dependencies: + '@types/node': 20.19.30 + + '@types/debug@4.1.12': + dependencies: + '@types/ms': 2.1.0 + + '@types/estree@1.0.8': {} + + '@types/express-serve-static-core@4.19.8': + dependencies: + '@types/node': 20.19.30 + '@types/qs': 6.14.0 + '@types/range-parser': 1.2.7 + '@types/send': 1.2.1 + + '@types/express@4.17.25': + dependencies: + '@types/body-parser': 1.19.6 + '@types/express-serve-static-core': 4.19.8 + '@types/qs': 6.14.0 + '@types/serve-static': 1.15.10 + + '@types/http-errors@2.0.5': {} + + '@types/js-yaml@4.0.9': {} + + '@types/mime@1.3.5': {} + + '@types/ms@2.1.0': {} + + '@types/node@20.19.30': + dependencies: + undici-types: 6.21.0 + + '@types/qs@6.14.0': {} + + '@types/range-parser@1.2.7': {} + + '@types/send@0.17.6': + dependencies: + '@types/mime': 1.3.5 + '@types/node': 20.19.30 + + '@types/send@1.2.1': + dependencies: + '@types/node': 20.19.30 + + '@types/serve-static@1.15.10': + dependencies: + '@types/http-errors': 2.0.5 + '@types/node': 20.19.30 + '@types/send': 0.17.6 + + '@types/validator@13.15.10': {} + + '@types/ws@8.18.1': + dependencies: + '@types/node': 20.19.30 + + accepts@2.0.0: + dependencies: + mime-types: 3.0.2 + negotiator: 1.0.0 + + acorn-walk@8.3.4: + dependencies: + acorn: 8.15.0 + + acorn@8.15.0: {} + + ajv-formats@3.0.1(ajv@8.17.1): + optionalDependencies: + ajv: 8.17.1 + + ajv@8.17.1: + dependencies: + fast-deep-equal: 3.1.3 + fast-uri: 3.1.0 + json-schema-traverse: 1.0.0 + require-from-string: 2.0.2 + + ansi-html@0.0.9: {} + + ansi-regex@5.0.1: {} + + ansi-styles@4.3.0: + dependencies: + color-convert: 2.0.1 + + arg@4.1.3: {} + + argparse@2.0.1: {} + + atomic-sleep@1.0.0: {} + + body-parser@2.2.2: + dependencies: + bytes: 3.1.2 + content-type: 1.0.5 + debug: 4.4.3 + http-errors: 2.0.1 + iconv-lite: 0.7.2 + on-finished: 2.4.1 + qs: 6.14.1 + raw-body: 3.0.2 + type-is: 2.0.1 + transitivePeerDependencies: + - supports-color + + bytes@3.1.2: {} + + call-bind-apply-helpers@1.0.2: + dependencies: + es-errors: 1.3.0 + function-bind: 1.1.2 + + call-bound@1.0.4: + dependencies: + call-bind-apply-helpers: 1.0.2 + get-intrinsic: 1.3.0 + + chalk@4.1.2: + dependencies: + ansi-styles: 4.3.0 + supports-color: 7.2.0 + + chalk@5.6.2: {} + + class-transformer@0.5.1: {} + + class-validator@0.14.3: + dependencies: + '@types/validator': 13.15.10 + libphonenumber-js: 1.12.35 + validator: 13.15.26 + + cliui@8.0.1: + dependencies: + string-width: 4.2.3 + strip-ansi: 6.0.1 + wrap-ansi: 7.0.0 + + color-convert@2.0.1: + dependencies: + color-name: 1.1.4 + + color-name@1.1.4: {} + + colorette@2.0.20: {} + + commander@12.1.0: {} + + concurrently@9.2.1: + dependencies: + chalk: 4.1.2 + rxjs: 7.8.2 + shell-quote: 1.8.3 + supports-color: 8.1.1 + tree-kill: 1.2.2 + yargs: 17.7.2 + + content-disposition@1.0.1: {} + + content-type@1.0.5: {} + + cookie-signature@1.2.2: {} + + cookie@0.7.2: {} + + cors@2.8.6: + dependencies: + object-assign: 4.1.1 + vary: 1.1.2 + + create-require@1.1.1: {} + + cross-env@7.0.3: + dependencies: + cross-spawn: 7.0.6 + + cross-spawn@7.0.6: + dependencies: + path-key: 3.1.1 + shebang-command: 2.0.0 + which: 2.0.2 + + dateformat@4.6.3: {} + + debug@4.4.3: + dependencies: + ms: 2.1.3 + + depd@2.0.0: {} + + detect-libc@2.1.2: {} + + diff@4.0.4: {} + + dunder-proto@1.0.1: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-errors: 1.3.0 + gopd: 1.2.0 + + ee-first@1.1.1: {} + + emoji-regex@8.0.0: {} + + encodeurl@2.0.0: {} + + end-of-stream@1.4.5: + dependencies: + once: 1.4.0 + + es-define-property@1.0.1: {} + + es-errors@1.3.0: {} + + es-object-atoms@1.1.1: + dependencies: + es-errors: 1.3.0 + + esbuild@0.25.12: + optionalDependencies: + '@esbuild/aix-ppc64': 0.25.12 + '@esbuild/android-arm': 0.25.12 + '@esbuild/android-arm64': 0.25.12 + '@esbuild/android-x64': 0.25.12 + '@esbuild/darwin-arm64': 0.25.12 + '@esbuild/darwin-x64': 0.25.12 + '@esbuild/freebsd-arm64': 0.25.12 + '@esbuild/freebsd-x64': 0.25.12 + '@esbuild/linux-arm': 0.25.12 + '@esbuild/linux-arm64': 0.25.12 + '@esbuild/linux-ia32': 0.25.12 + '@esbuild/linux-loong64': 0.25.12 + '@esbuild/linux-mips64el': 0.25.12 + '@esbuild/linux-ppc64': 0.25.12 + '@esbuild/linux-riscv64': 0.25.12 + '@esbuild/linux-s390x': 0.25.12 + '@esbuild/linux-x64': 0.25.12 + '@esbuild/netbsd-arm64': 0.25.12 + '@esbuild/netbsd-x64': 0.25.12 + '@esbuild/openbsd-arm64': 0.25.12 + '@esbuild/openbsd-x64': 0.25.12 + '@esbuild/openharmony-arm64': 0.25.12 + '@esbuild/sunos-x64': 0.25.12 + '@esbuild/win32-arm64': 0.25.12 + '@esbuild/win32-ia32': 0.25.12 + '@esbuild/win32-x64': 0.25.12 + + esbuild@0.27.2: + optionalDependencies: + '@esbuild/aix-ppc64': 0.27.2 + '@esbuild/android-arm': 0.27.2 + '@esbuild/android-arm64': 0.27.2 + '@esbuild/android-x64': 0.27.2 + '@esbuild/darwin-arm64': 0.27.2 + '@esbuild/darwin-x64': 0.27.2 + '@esbuild/freebsd-arm64': 0.27.2 + '@esbuild/freebsd-x64': 0.27.2 + '@esbuild/linux-arm': 0.27.2 + '@esbuild/linux-arm64': 0.27.2 + '@esbuild/linux-ia32': 0.27.2 + '@esbuild/linux-loong64': 0.27.2 + '@esbuild/linux-mips64el': 0.27.2 + '@esbuild/linux-ppc64': 0.27.2 + '@esbuild/linux-riscv64': 0.27.2 + '@esbuild/linux-s390x': 0.27.2 + '@esbuild/linux-x64': 0.27.2 + '@esbuild/netbsd-arm64': 0.27.2 + '@esbuild/netbsd-x64': 0.27.2 + '@esbuild/openbsd-arm64': 0.27.2 + '@esbuild/openbsd-x64': 0.27.2 + '@esbuild/openharmony-arm64': 0.27.2 + '@esbuild/sunos-x64': 0.27.2 + '@esbuild/win32-arm64': 0.27.2 + '@esbuild/win32-ia32': 0.27.2 + '@esbuild/win32-x64': 0.27.2 + + escalade@3.2.0: {} + + escape-goat@4.0.0: {} + + escape-html@1.0.3: {} + + etag@1.8.1: {} + + eventsource-parser@3.0.6: {} + + eventsource@3.0.7: + dependencies: + eventsource-parser: 3.0.6 + + express-rate-limit@7.5.1(express@5.2.1): + dependencies: + express: 5.2.1 + + express@5.2.1: + dependencies: + accepts: 2.0.0 + body-parser: 2.2.2 + content-disposition: 1.0.1 + content-type: 1.0.5 + cookie: 0.7.2 + cookie-signature: 1.2.2 + debug: 4.4.3 + depd: 2.0.0 + encodeurl: 2.0.0 + escape-html: 1.0.3 + etag: 1.8.1 + finalhandler: 2.1.1 + fresh: 2.0.0 + http-errors: 2.0.1 + merge-descriptors: 2.0.0 + mime-types: 3.0.2 + on-finished: 2.4.1 + once: 1.4.0 + parseurl: 1.3.3 + proxy-addr: 2.0.7 + qs: 6.14.1 + range-parser: 1.2.1 + router: 2.2.0 + send: 1.2.1 + serve-static: 2.2.1 + statuses: 2.0.2 + type-is: 2.0.1 + vary: 1.1.2 + transitivePeerDependencies: + - supports-color + + fast-copy@4.0.2: {} + + fast-deep-equal@3.1.3: {} + + fast-safe-stringify@2.1.1: {} + + fast-uri@3.1.0: {} + + fdir@6.5.0(picomatch@4.0.3): + optionalDependencies: + picomatch: 4.0.3 + + finalhandler@2.1.1: + dependencies: + debug: 4.4.3 + encodeurl: 2.0.0 + escape-html: 1.0.3 + on-finished: 2.4.1 + parseurl: 1.3.3 + statuses: 2.0.2 + transitivePeerDependencies: + - supports-color + + forwarded@0.2.0: {} + + fresh@2.0.0: {} + + fsevents@2.3.3: + optional: true + + function-bind@1.1.2: {} + + get-caller-file@2.0.5: {} + + get-intrinsic@1.3.0: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-define-property: 1.0.1 + es-errors: 1.3.0 + es-object-atoms: 1.1.1 + function-bind: 1.1.2 + get-proto: 1.0.1 + gopd: 1.2.0 + has-symbols: 1.1.0 + hasown: 2.0.2 + math-intrinsics: 1.1.0 + + get-proto@1.0.1: + dependencies: + dunder-proto: 1.0.1 + es-object-atoms: 1.1.1 + + gopd@1.2.0: {} + + has-flag@4.0.0: {} + + has-symbols@1.1.0: {} + + hasown@2.0.2: + dependencies: + function-bind: 1.1.2 + + help-me@5.0.0: {} + + hono@4.11.7: {} + + http-errors@2.0.1: + dependencies: + depd: 2.0.0 + inherits: 2.0.4 + setprototypeof: 1.2.0 + statuses: 2.0.2 + toidentifier: 1.0.1 + + iconv-lite@0.7.2: + dependencies: + safer-buffer: 2.1.2 + + inherits@2.0.4: {} + + ipaddr.js@1.9.1: {} + + is-fullwidth-code-point@3.0.0: {} + + is-promise@4.0.0: {} + + isexe@2.0.0: {} + + jose@6.1.3: {} + + joycon@3.1.1: {} + + js-yaml@4.1.1: + dependencies: + argparse: 2.0.1 + + json-schema-traverse@1.0.0: {} + + json-schema-typed@8.0.2: {} + + libphonenumber-js@1.12.35: {} + + make-error@1.3.6: {} + + math-intrinsics@1.1.0: {} + + media-typer@1.1.0: {} + + merge-descriptors@2.0.0: {} + + mime-db@1.54.0: {} + + mime-types@3.0.2: + dependencies: + mime-db: 1.54.0 + + minimist@1.2.8: {} + + ms@2.1.3: {} + + nanoid@3.3.11: {} + + negotiator@1.0.0: {} + + object-assign@4.1.1: {} + + object-inspect@1.13.4: {} + + on-exit-leak-free@2.1.2: {} + + on-finished@2.4.1: + dependencies: + ee-first: 1.1.1 + + once@1.4.0: + dependencies: + wrappy: 1.0.2 + + p-defer@4.0.1: {} + + packages@file:packages: {} + + parseurl@1.3.3: {} + + path-key@3.1.1: {} + + path-to-regexp@8.3.0: {} + + picocolors@1.1.1: {} + + picomatch@4.0.3: {} + + pino-abstract-transport@2.0.0: + dependencies: + split2: 4.2.0 + + pino-abstract-transport@3.0.0: + dependencies: + split2: 4.2.0 + + pino-pretty@13.1.3: + dependencies: + colorette: 2.0.20 + dateformat: 4.6.3 + fast-copy: 4.0.2 + fast-safe-stringify: 2.1.1 + help-me: 5.0.0 + joycon: 3.1.1 + minimist: 1.2.8 + on-exit-leak-free: 2.1.2 + pino-abstract-transport: 3.0.0 + pump: 3.0.3 + secure-json-parse: 4.1.0 + sonic-boom: 4.2.0 + strip-json-comments: 5.0.3 + + pino-std-serializers@7.1.0: {} + + pino@9.14.0: + dependencies: + '@pinojs/redact': 0.4.0 + atomic-sleep: 1.0.0 + on-exit-leak-free: 2.1.2 + pino-abstract-transport: 2.0.0 + pino-std-serializers: 7.1.0 + process-warning: 5.0.0 + quick-format-unescaped: 4.0.4 + real-require: 0.2.0 + safe-stable-stringify: 2.5.0 + sonic-boom: 4.2.0 + thread-stream: 3.1.0 + + pkce-challenge@5.0.1: {} + + postcss@8.5.6: + dependencies: + nanoid: 3.3.11 + picocolors: 1.1.1 + source-map-js: 1.2.1 + + prettier@3.8.1: {} + + process-warning@5.0.0: {} + + proxy-addr@2.0.7: + dependencies: + forwarded: 0.2.0 + ipaddr.js: 1.9.1 + + pump@3.0.3: + dependencies: + end-of-stream: 1.4.5 + once: 1.4.0 + + qs@6.14.1: + dependencies: + side-channel: 1.1.0 + + quick-format-unescaped@4.0.4: {} + + range-parser@1.2.1: {} + + raw-body@3.0.2: + dependencies: + bytes: 3.1.2 + http-errors: 2.0.1 + iconv-lite: 0.7.2 + unpipe: 1.0.0 + + real-require@0.2.0: {} + + reflect-metadata@0.1.14: {} + + require-directory@2.1.1: {} + + require-from-string@2.0.2: {} + + rollup@4.57.0: + dependencies: + '@types/estree': 1.0.8 + optionalDependencies: + '@rollup/rollup-android-arm-eabi': 4.57.0 + '@rollup/rollup-android-arm64': 4.57.0 + '@rollup/rollup-darwin-arm64': 4.57.0 + '@rollup/rollup-darwin-x64': 4.57.0 + '@rollup/rollup-freebsd-arm64': 4.57.0 + '@rollup/rollup-freebsd-x64': 4.57.0 + '@rollup/rollup-linux-arm-gnueabihf': 4.57.0 + '@rollup/rollup-linux-arm-musleabihf': 4.57.0 + '@rollup/rollup-linux-arm64-gnu': 4.57.0 + '@rollup/rollup-linux-arm64-musl': 4.57.0 + '@rollup/rollup-linux-loong64-gnu': 4.57.0 + '@rollup/rollup-linux-loong64-musl': 4.57.0 + '@rollup/rollup-linux-ppc64-gnu': 4.57.0 + '@rollup/rollup-linux-ppc64-musl': 4.57.0 + '@rollup/rollup-linux-riscv64-gnu': 4.57.0 + '@rollup/rollup-linux-riscv64-musl': 4.57.0 + '@rollup/rollup-linux-s390x-gnu': 4.57.0 + '@rollup/rollup-linux-x64-gnu': 4.57.0 + '@rollup/rollup-linux-x64-musl': 4.57.0 + '@rollup/rollup-openbsd-x64': 4.57.0 + '@rollup/rollup-openharmony-arm64': 4.57.0 + '@rollup/rollup-win32-arm64-msvc': 4.57.0 + '@rollup/rollup-win32-ia32-msvc': 4.57.0 + '@rollup/rollup-win32-x64-gnu': 4.57.0 + '@rollup/rollup-win32-x64-msvc': 4.57.0 + fsevents: 2.3.3 + + router@2.2.0: + dependencies: + debug: 4.4.3 + depd: 2.0.0 + is-promise: 4.0.0 + parseurl: 1.3.3 + path-to-regexp: 8.3.0 + transitivePeerDependencies: + - supports-color + + rxjs@7.8.2: + dependencies: + tslib: 2.8.1 + + safe-stable-stringify@2.5.0: {} + + safer-buffer@2.1.2: {} + + secure-json-parse@4.1.0: {} + + semver@7.7.3: {} + + send@1.2.1: + dependencies: + debug: 4.4.3 + encodeurl: 2.0.0 + escape-html: 1.0.3 + etag: 1.8.1 + fresh: 2.0.0 + http-errors: 2.0.1 + mime-types: 3.0.2 + ms: 2.1.3 + on-finished: 2.4.1 + range-parser: 1.2.1 + statuses: 2.0.2 + transitivePeerDependencies: + - supports-color + + serve-static@2.2.1: + dependencies: + encodeurl: 2.0.0 + escape-html: 1.0.3 + parseurl: 1.3.3 + send: 1.2.1 + transitivePeerDependencies: + - supports-color + + setprototypeof@1.2.0: {} + + sharp@0.34.5: + dependencies: + '@img/colour': 1.0.0 + detect-libc: 2.1.2 + semver: 7.7.3 + optionalDependencies: + '@img/sharp-darwin-arm64': 0.34.5 + '@img/sharp-darwin-x64': 0.34.5 + '@img/sharp-libvips-darwin-arm64': 1.2.4 + '@img/sharp-libvips-darwin-x64': 1.2.4 + '@img/sharp-libvips-linux-arm': 1.2.4 + '@img/sharp-libvips-linux-arm64': 1.2.4 + '@img/sharp-libvips-linux-ppc64': 1.2.4 + '@img/sharp-libvips-linux-riscv64': 1.2.4 + '@img/sharp-libvips-linux-s390x': 1.2.4 + '@img/sharp-libvips-linux-x64': 1.2.4 + '@img/sharp-libvips-linuxmusl-arm64': 1.2.4 + '@img/sharp-libvips-linuxmusl-x64': 1.2.4 + '@img/sharp-linux-arm': 0.34.5 + '@img/sharp-linux-arm64': 0.34.5 + '@img/sharp-linux-ppc64': 0.34.5 + '@img/sharp-linux-riscv64': 0.34.5 + '@img/sharp-linux-s390x': 0.34.5 + '@img/sharp-linux-x64': 0.34.5 + '@img/sharp-linuxmusl-arm64': 0.34.5 + '@img/sharp-linuxmusl-x64': 0.34.5 + '@img/sharp-wasm32': 0.34.5 + '@img/sharp-win32-arm64': 0.34.5 + '@img/sharp-win32-ia32': 0.34.5 + '@img/sharp-win32-x64': 0.34.5 + + shebang-command@2.0.0: + dependencies: + shebang-regex: 3.0.0 + + shebang-regex@3.0.0: {} + + shell-quote@1.8.3: {} + + side-channel-list@1.0.0: + dependencies: + es-errors: 1.3.0 + object-inspect: 1.13.4 + + side-channel-map@1.0.1: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + object-inspect: 1.13.4 + + side-channel-weakmap@1.0.2: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + object-inspect: 1.13.4 + side-channel-map: 1.0.1 + + side-channel@1.1.0: + dependencies: + es-errors: 1.3.0 + object-inspect: 1.13.4 + side-channel-list: 1.0.0 + side-channel-map: 1.0.1 + side-channel-weakmap: 1.0.2 + + sonic-boom@4.2.0: + dependencies: + atomic-sleep: 1.0.0 + + source-map-js@1.2.1: {} + + split2@4.2.0: {} + + statuses@2.0.2: {} + + string-width@4.2.3: + dependencies: + emoji-regex: 8.0.0 + is-fullwidth-code-point: 3.0.0 + strip-ansi: 6.0.1 + + strip-ansi@6.0.1: + dependencies: + ansi-regex: 5.0.1 + + strip-json-comments@5.0.3: {} + + supports-color@7.2.0: + dependencies: + has-flag: 4.0.0 + + supports-color@8.1.1: + dependencies: + has-flag: 4.0.0 + + thread-stream@3.1.0: + dependencies: + real-require: 0.2.0 + + tinyglobby@0.2.15: + dependencies: + fdir: 6.5.0(picomatch@4.0.3) + picomatch: 4.0.3 + + toidentifier@1.0.1: {} + + tree-kill@1.2.2: {} + + ts-node@10.9.2(@types/node@20.19.30)(typescript@5.9.3): + dependencies: + '@cspotcode/source-map-support': 0.8.1 + '@tsconfig/node10': 1.0.12 + '@tsconfig/node12': 1.0.11 + '@tsconfig/node14': 1.0.3 + '@tsconfig/node16': 1.0.4 + '@types/node': 20.19.30 + acorn: 8.15.0 + acorn-walk: 8.3.4 + arg: 4.1.3 + create-require: 1.1.1 + diff: 4.0.4 + make-error: 1.3.6 + typescript: 5.9.3 + v8-compile-cache-lib: 3.0.1 + yn: 3.1.1 + + tslib@2.8.1: {} + + type-is@2.0.1: + dependencies: + content-type: 1.0.5 + media-typer: 1.1.0 + mime-types: 3.0.2 + + typescript@5.9.3: {} + + undici-types@6.21.0: {} + + unpipe@1.0.0: {} + + v8-compile-cache-lib@3.0.1: {} + + validator@13.15.26: {} + + vary@1.1.2: {} + + vite-live-preview@0.3.2(vite@7.3.1(@types/node@20.19.30)): + dependencies: + '@commander-js/extra-typings': 12.1.0(commander@12.1.0) + '@types/ansi-html': 0.0.0 + '@types/debug': 4.1.12 + '@types/ws': 8.18.1 + ansi-html: 0.0.9 + chalk: 5.6.2 + commander: 12.1.0 + debug: 4.4.3 + escape-goat: 4.0.0 + p-defer: 4.0.1 + vite: 7.3.1(@types/node@20.19.30) + ws: 8.19.0 + transitivePeerDependencies: + - bufferutil + - supports-color + - utf-8-validate + + vite@7.3.1(@types/node@20.19.30): + dependencies: + esbuild: 0.27.2 + fdir: 6.5.0(picomatch@4.0.3) + picomatch: 4.0.3 + postcss: 8.5.6 + rollup: 4.57.0 + tinyglobby: 0.2.15 + optionalDependencies: + '@types/node': 20.19.30 + fsevents: 2.3.3 + + which@2.0.2: + dependencies: + isexe: 2.0.0 + + wrap-ansi@7.0.0: + dependencies: + ansi-styles: 4.3.0 + string-width: 4.2.3 + strip-ansi: 6.0.1 + + wrappy@1.0.2: {} + + ws@8.19.0: {} + + y18n@5.0.8: {} + + yargs-parser@21.1.1: {} + + yargs@17.7.2: + dependencies: + cliui: 8.0.1 + escalade: 3.2.0 + get-caller-file: 2.0.5 + require-directory: 2.1.1 + string-width: 4.2.3 + y18n: 5.0.8 + yargs-parser: 21.1.1 + + yn@3.1.1: {} + + zod-to-json-schema@3.25.1(zod@4.3.6): + dependencies: + zod: 4.3.6 + + zod@4.3.6: {} diff --git a/mcp/pnpm-workspace.yaml b/mcp/pnpm-workspace.yaml new file mode 100644 index 0000000000..9bc2f715c9 --- /dev/null +++ b/mcp/pnpm-workspace.yaml @@ -0,0 +1,6 @@ +linkWorkspacePackages: true + +packages: + - "./packages/common" + - "./packages/server" + - "./packages/plugin" diff --git a/mcp/resources/architecture.png b/mcp/resources/architecture.png new file mode 100644 index 0000000000..f8305fd993 Binary files /dev/null and b/mcp/resources/architecture.png differ diff --git a/mcp/scripts/build b/mcp/scripts/build new file mode 100755 index 0000000000..2a69fe9260 --- /dev/null +++ b/mcp/scripts/build @@ -0,0 +1,50 @@ +#!/usr/bin/env bash +# NOTE: this script should be called from the parent directory to +# properly work + +set -ex + +corepack enable; +corepack install; + +# Ensure clean working directory +rm -rf dist; +rm -rf node_modules; +rm -rf packages/server/dist; +rm -rf packages/server/node_modules; + +pushd ../plugins +pnpm install +pnpm run build:doc +popd + +rsync -avr ../plugins/dist/doc/ ./types-generator/doc/ + +pushd types-generator +set +e; +pixi install; +pnpx concurrently --kill-others-on-fail -k \ + "caddy file-server --root doc --listen :9090" \ + "pixi run python prepare_api_docs.py http://localhost:9090"; +rm -rf doc; +popd + +set -e; + +pnpm -r --filter "!mcp-plugin" install; +pnpm -r --filter "mcp-server" run build:multi-user; + +rsync -avr packages/server/dist/ ./dist/; + +cp packages/server/package.json ./dist/; +cp packages/server/pnpm-lock.yaml ./dist/; + +cat <=7.5.0 + constrains: + - openmp_impl 9999 + license: BSD-3-Clause + license_family: BSD + purls: [] + size: 23621 + timestamp: 1650670423406 +- conda: https://conda.anaconda.org/conda-forge/noarch/beautifulsoup4-4.13.5-pyha770c72_0.conda + sha256: d2124c0ea13527c7f54582269b3ae19541141a3740d6d779e7aa95aa82eaf561 + md5: de0fd9702fd4c1186e930b8c35af6b6b + depends: + - python >=3.10 + - soupsieve >=1.2 + - typing-extensions + license: MIT + license_family: MIT + purls: + - pkg:pypi/beautifulsoup4?source=compressed-mapping + size: 88278 + timestamp: 1756094375546 +- conda: https://conda.anaconda.org/conda-forge/noarch/beautifulsoup4-4.14.2-pyha770c72_0.conda + sha256: b949bd0121bb1eabc282c4de0551cc162b621582ee12b415e6f8297398e3b3b4 + md5: 749ebebabc2cae99b2e5b3edd04c6ca2 + depends: + - python >=3.10 + - soupsieve >=1.2 + - typing-extensions + license: MIT + license_family: MIT + purls: + - pkg:pypi/beautifulsoup4?source=hash-mapping + size: 89146 + timestamp: 1759146127397 +- conda: https://conda.anaconda.org/conda-forge/linux-64/brotli-python-1.2.0-py311h7c6b74e_0.conda + sha256: 5e6858dae1935793a7fa7f46d8975b0596b546c28586cb463dd2fdeba3bcc193 + md5: 645bc783bc723d67a294a51bc860762d + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + - libstdcxx >=14 + - python >=3.11,<3.12.0a0 + - python_abi 3.11.* *_cp311 + constrains: + - libbrotlicommon 1.2.0 h09219d5_0 + license: MIT + license_family: MIT + purls: + - pkg:pypi/brotli?source=hash-mapping + size: 368532 + timestamp: 1761592301216 +- conda: https://conda.anaconda.org/conda-forge/win-64/brotli-python-1.1.0-py311h3e6a449_4.conda + sha256: d524edc172239fec70ad946e3b2fa1b9d7eea145ad80e9e66da25a4d815770ea + md5: 21d3a7fa95d27938158009cd08e461f2 + depends: + - python >=3.11,<3.12.0a0 + - python_abi 3.11.* *_cp311 + - ucrt >=10.0.20348.0 + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + constrains: + - libbrotlicommon 1.1.0 hfd05255_4 + license: MIT + license_family: MIT + purls: + - pkg:pypi/brotli?source=hash-mapping + size: 323289 + timestamp: 1756600106141 +- conda: https://conda.anaconda.org/conda-forge/linux-64/bzip2-1.0.8-hda65f42_8.conda + sha256: c30daba32ddebbb7ded490f0e371eae90f51e72db620554089103b4a6934b0d5 + md5: 51a19bba1b8ebfb60df25cde030b7ebc + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + license: bzip2-1.0.6 + license_family: BSD + purls: [] + size: 260341 + timestamp: 1757437258798 +- conda: https://conda.anaconda.org/conda-forge/win-64/bzip2-1.0.8-h0ad9c76_8.conda + sha256: d882712855624641f48aa9dc3f5feea2ed6b4e6004585d3616386a18186fe692 + md5: 1077e9333c41ff0be8edd1a5ec0ddace + depends: + - ucrt >=10.0.20348.0 + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + license: bzip2-1.0.6 + license_family: BSD + purls: [] + size: 55977 + timestamp: 1757437738856 +- conda: https://conda.anaconda.org/conda-forge/noarch/ca-certificates-2025.11.12-hbd8a1cb_0.conda + sha256: b986ba796d42c9d3265602bc038f6f5264095702dd546c14bc684e60c385e773 + md5: f0991f0f84902f6b6009b4d2350a83aa + depends: + - __unix + license: ISC + purls: [] + size: 152432 + timestamp: 1762967197890 +- conda: https://conda.anaconda.org/conda-forge/noarch/ca-certificates-2025.8.3-h4c7d964_0.conda + sha256: 3b82f62baad3fd33827b01b0426e8203a2786c8f452f633740868296bcbe8485 + md5: c9e0c0f82f6e63323827db462b40ede8 + depends: + - __win + license: ISC + purls: [] + size: 154489 + timestamp: 1754210967212 +- conda: https://conda.anaconda.org/conda-forge/noarch/certifi-2025.11.12-pyhd8ed1ab_0.conda + sha256: 083a2bdad892ccf02b352ecab38ee86c3e610ba9a4b11b073ea769d55a115d32 + md5: 96a02a5c1a65470a7e4eedb644c872fd + depends: + - python >=3.10 + license: ISC + purls: + - pkg:pypi/certifi?source=compressed-mapping + size: 157131 + timestamp: 1762976260320 +- conda: https://conda.anaconda.org/conda-forge/noarch/certifi-2025.8.3-pyhd8ed1ab_0.conda + sha256: a1ad5b0a2a242f439608f22a538d2175cac4444b7b3f4e2b8c090ac337aaea40 + md5: 11f59985f49df4620890f3e746ed7102 + depends: + - python >=3.9 + license: ISC + purls: + - pkg:pypi/certifi?source=compressed-mapping + size: 158692 + timestamp: 1754231530168 +- conda: https://conda.anaconda.org/conda-forge/linux-64/cffi-2.0.0-py311h03d9500_1.conda + sha256: 3ad13377356c86d3a945ae30e9b8c8734300925ef81a3cb0a9db0d755afbe7bb + md5: 3912e4373de46adafd8f1e97e4bd166b + depends: + - __glibc >=2.17,<3.0.a0 + - libffi >=3.5.2,<3.6.0a0 + - libgcc >=14 + - pycparser + - python >=3.11,<3.12.0a0 + - python_abi 3.11.* *_cp311 + license: MIT + license_family: MIT + purls: + - pkg:pypi/cffi?source=hash-mapping + size: 303338 + timestamp: 1761202960110 +- conda: https://conda.anaconda.org/conda-forge/win-64/cffi-2.0.0-py311h3485c13_0.conda + sha256: 12f5d72b95dbd417367895a92c35922b24bb016d1497f24f3a243224ec6cb81b + md5: 573fd072e80c1a334e19a1f95024d94d + depends: + - pycparser + - python >=3.11,<3.12.0a0 + - python_abi 3.11.* *_cp311 + - ucrt >=10.0.20348.0 + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + license: MIT + license_family: MIT + purls: + - pkg:pypi/cffi?source=compressed-mapping + size: 298353 + timestamp: 1758716437777 +- conda: https://conda.anaconda.org/conda-forge/noarch/charset-normalizer-3.4.3-pyhd8ed1ab_0.conda + sha256: 838d5a011f0e7422be6427becba3de743c78f3874ad2743c341accbba9bb2624 + md5: 7e7d5ef1b9ed630e4a1c358d6bc62284 + depends: + - python >=3.9 + license: MIT + license_family: MIT + purls: + - pkg:pypi/charset-normalizer?source=hash-mapping + size: 51033 + timestamp: 1754767444665 +- conda: https://conda.anaconda.org/conda-forge/noarch/charset-normalizer-3.4.4-pyhd8ed1ab_0.conda + sha256: b32f8362e885f1b8417bac2b3da4db7323faa12d5db62b7fd6691c02d60d6f59 + md5: a22d1fd9bf98827e280a02875d9a007a + depends: + - python >=3.10 + license: MIT + license_family: MIT + purls: + - pkg:pypi/charset-normalizer?source=hash-mapping + size: 50965 + timestamp: 1760437331772 +- conda: https://conda.anaconda.org/conda-forge/noarch/h2-4.3.0-pyhcf101f3_0.conda + sha256: 84c64443368f84b600bfecc529a1194a3b14c3656ee2e832d15a20e0329b6da3 + md5: 164fc43f0b53b6e3a7bc7dce5e4f1dc9 + depends: + - python >=3.10 + - hyperframe >=6.1,<7 + - hpack >=4.1,<5 + - python + license: MIT + license_family: MIT + purls: + - pkg:pypi/h2?source=compressed-mapping + size: 95967 + timestamp: 1756364871835 +- conda: https://conda.anaconda.org/conda-forge/noarch/hpack-4.1.0-pyhd8ed1ab_0.conda + sha256: 6ad78a180576c706aabeb5b4c8ceb97c0cb25f1e112d76495bff23e3779948ba + md5: 0a802cb9888dd14eeefc611f05c40b6e + depends: + - python >=3.9 + license: MIT + license_family: MIT + purls: + - pkg:pypi/hpack?source=hash-mapping + size: 30731 + timestamp: 1737618390337 +- conda: https://conda.anaconda.org/conda-forge/noarch/hyperframe-6.1.0-pyhd8ed1ab_0.conda + sha256: 77af6f5fe8b62ca07d09ac60127a30d9069fdc3c68d6b256754d0ffb1f7779f8 + md5: 8e6923fc12f1fe8f8c4e5c9f343256ac + depends: + - python >=3.9 + license: MIT + license_family: MIT + purls: + - pkg:pypi/hyperframe?source=hash-mapping + size: 17397 + timestamp: 1737618427549 +- conda: https://conda.anaconda.org/conda-forge/linux-64/icu-75.1-he02047a_0.conda + sha256: 71e750d509f5fa3421087ba88ef9a7b9be11c53174af3aa4d06aff4c18b38e8e + md5: 8b189310083baabfb622af68fd9d3ae3 + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc-ng >=12 + - libstdcxx-ng >=12 + license: MIT + license_family: MIT + purls: [] + size: 12129203 + timestamp: 1720853576813 +- conda: https://conda.anaconda.org/conda-forge/noarch/idna-3.10-pyhd8ed1ab_1.conda + sha256: d7a472c9fd479e2e8dcb83fb8d433fce971ea369d704ece380e876f9c3494e87 + md5: 39a4f67be3286c86d696df570b1201b7 + depends: + - python >=3.9 + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/idna?source=hash-mapping + size: 49765 + timestamp: 1733211921194 +- conda: https://conda.anaconda.org/conda-forge/noarch/idna-3.11-pyhd8ed1ab_0.conda + sha256: ae89d0299ada2a3162c2614a9d26557a92aa6a77120ce142f8e0109bbf0342b0 + md5: 53abe63df7e10a6ba605dc5f9f961d36 + depends: + - python >=3.10 + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/idna?source=hash-mapping + size: 50721 + timestamp: 1760286526795 +- conda: https://conda.anaconda.org/conda-forge/linux-64/ld_impl_linux-64-2.45-h1aa0949_0.conda + sha256: 32321d38b8785ef8ddcfef652ee370acee8d944681014d47797a18637ff16854 + md5: 1450224b3e7d17dfeb985364b77a4d47 + depends: + - __glibc >=2.17,<3.0.a0 + - zstd >=1.5.7,<1.6.0a0 + constrains: + - binutils_impl_linux-64 2.45 + license: GPL-3.0-only + license_family: GPL + purls: [] + size: 753744 + timestamp: 1763060439129 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libexpat-2.7.1-hecca717_0.conda + sha256: da2080da8f0288b95dd86765c801c6e166c4619b910b11f9a8446fb852438dc2 + md5: 4211416ecba1866fab0c6470986c22d6 + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + constrains: + - expat 2.7.1.* + license: MIT + license_family: MIT + purls: [] + size: 74811 + timestamp: 1752719572741 +- conda: https://conda.anaconda.org/conda-forge/win-64/libexpat-2.7.1-hac47afa_0.conda + sha256: 8432ca842bdf8073ccecf016ccc9140c41c7114dc4ec77ca754551c01f780845 + md5: 3608ffde260281fa641e70d6e34b1b96 + depends: + - ucrt >=10.0.20348.0 + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + constrains: + - expat 2.7.1.* + license: MIT + license_family: MIT + purls: [] + size: 141322 + timestamp: 1752719767870 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libffi-3.5.2-h9ec8514_0.conda + sha256: 25cbdfa65580cfab1b8d15ee90b4c9f1e0d72128f1661449c9a999d341377d54 + md5: 35f29eec58405aaf55e01cb470d8c26a + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + license: MIT + license_family: MIT + purls: [] + size: 57821 + timestamp: 1760295480630 +- conda: https://conda.anaconda.org/conda-forge/win-64/libffi-3.4.6-h537db12_1.conda + sha256: d3b0b8812eab553d3464bbd68204f007f1ebadf96ce30eb0cbc5159f72e353f5 + md5: 85d8fa5e55ed8f93f874b3b23ed54ec6 + depends: + - ucrt >=10.0.20348.0 + - vc >=14.2,<15 + - vc14_runtime >=14.29.30139 + license: MIT + license_family: MIT + purls: [] + size: 44978 + timestamp: 1743435053850 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-15.2.0-h767d61c_7.conda + sha256: 08f9b87578ab981c7713e4e6a7d935e40766e10691732bba376d4964562bcb45 + md5: c0374badb3a5d4b1372db28d19462c53 + depends: + - __glibc >=2.17,<3.0.a0 + - _openmp_mutex >=4.5 + constrains: + - libgomp 15.2.0 h767d61c_7 + - libgcc-ng ==15.2.0=*_7 + license: GPL-3.0-only WITH GCC-exception-3.1 + license_family: GPL + purls: [] + size: 822552 + timestamp: 1759968052178 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-ng-15.2.0-h69a702a_7.conda + sha256: 2045066dd8e6e58aaf5ae2b722fb6dfdbb57c862b5f34ac7bfb58c40ef39b6ad + md5: 280ea6eee9e2ddefde25ff799c4f0363 + depends: + - libgcc 15.2.0 h767d61c_7 + license: GPL-3.0-only WITH GCC-exception-3.1 + license_family: GPL + purls: [] + size: 29313 + timestamp: 1759968065504 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libgomp-15.2.0-h767d61c_7.conda + sha256: e9fb1c258c8e66ee278397b5822692527c5f5786d372fe7a869b900853f3f5ca + md5: f7b4d76975aac7e5d9e6ad13845f92fe + depends: + - __glibc >=2.17,<3.0.a0 + license: GPL-3.0-only WITH GCC-exception-3.1 + license_family: GPL + purls: [] + size: 447919 + timestamp: 1759967942498 +- conda: https://conda.anaconda.org/conda-forge/linux-64/liblzma-5.8.1-hb9d3cd8_2.conda + sha256: f2591c0069447bbe28d4d696b7fcb0c5bd0b4ac582769b89addbcf26fb3430d8 + md5: 1a580f7796c7bf6393fddb8bbbde58dc + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=13 + constrains: + - xz 5.8.1.* + license: 0BSD + purls: [] + size: 112894 + timestamp: 1749230047870 +- conda: https://conda.anaconda.org/conda-forge/win-64/liblzma-5.8.1-h2466b09_2.conda + sha256: 55764956eb9179b98de7cc0e55696f2eff8f7b83fc3ebff5e696ca358bca28cc + md5: c15148b2e18da456f5108ccb5e411446 + depends: + - ucrt >=10.0.20348.0 + - vc >=14.2,<15 + - vc14_runtime >=14.29.30139 + constrains: + - xz 5.8.1.* + license: 0BSD + purls: [] + size: 104935 + timestamp: 1749230611612 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libnsl-2.0.1-hb9d3cd8_1.conda + sha256: 927fe72b054277cde6cb82597d0fcf6baf127dcbce2e0a9d8925a68f1265eef5 + md5: d864d34357c3b65a4b731f78c0801dc4 + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=13 + license: LGPL-2.1-only + license_family: GPL + purls: [] + size: 33731 + timestamp: 1750274110928 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libsqlite-3.51.0-hee844dc_0.conda + sha256: 4c992dcd0e34b68f843e75406f7f303b1b97c248d18f3c7c330bdc0bc26ae0b3 + md5: 729a572a3ebb8c43933b30edcc628ceb + depends: + - __glibc >=2.17,<3.0.a0 + - icu >=75.1,<76.0a0 + - libgcc >=14 + - libzlib >=1.3.1,<2.0a0 + license: blessing + purls: [] + size: 945576 + timestamp: 1762299687230 +- conda: https://conda.anaconda.org/conda-forge/win-64/libsqlite-3.50.4-hf5d6505_0.conda + sha256: 5dc4f07b2d6270ac0c874caec53c6984caaaa84bc0d3eb593b0edf3dc8492efa + md5: ccb20d946040f86f0c05b644d5eadeca + depends: + - ucrt >=10.0.20348.0 + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + license: blessing + purls: [] + size: 1288499 + timestamp: 1753948889360 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-15.2.0-h8f9b012_7.conda + sha256: 1b981647d9775e1cdeb2fab0a4dd9cd75a6b0de2963f6c3953dbd712f78334b3 + md5: 5b767048b1b3ee9a954b06f4084f93dc + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc 15.2.0 h767d61c_7 + constrains: + - libstdcxx-ng ==15.2.0=*_7 + license: GPL-3.0-only WITH GCC-exception-3.1 + license_family: GPL + purls: [] + size: 3898269 + timestamp: 1759968103436 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-ng-15.2.0-h4852527_7.conda + sha256: 024fd46ac3ea8032a5ec3ea7b91c4c235701a8bf0e6520fe5e6539992a6bd05f + md5: f627678cf829bd70bccf141a19c3ad3e + depends: + - libstdcxx 15.2.0 h8f9b012_7 + license: GPL-3.0-only WITH GCC-exception-3.1 + license_family: GPL + purls: [] + size: 29343 + timestamp: 1759968157195 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libuuid-2.41.2-he9a06e4_0.conda + sha256: e5ec6d2ad7eef538ddcb9ea62ad4346fde70a4736342c4ad87bd713641eb9808 + md5: 80c07c68d2f6870250959dcc95b209d1 + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + license: BSD-3-Clause + license_family: BSD + purls: [] + size: 37135 + timestamp: 1758626800002 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libxcrypt-4.4.36-hd590300_1.conda + sha256: 6ae68e0b86423ef188196fff6207ed0c8195dd84273cb5623b85aa08033a410c + md5: 5aa797f8787fe7a17d1b0821485b5adc + depends: + - libgcc-ng >=12 + license: LGPL-2.1-or-later + purls: [] + size: 100393 + timestamp: 1702724383534 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libzlib-1.3.1-hb9d3cd8_2.conda + sha256: d4bfe88d7cb447768e31650f06257995601f89076080e76df55e3112d4e47dc4 + md5: edb0dca6bc32e4f4789199455a1dbeb8 + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=13 + constrains: + - zlib 1.3.1 *_2 + license: Zlib + license_family: Other + purls: [] + size: 60963 + timestamp: 1727963148474 +- conda: https://conda.anaconda.org/conda-forge/win-64/libzlib-1.3.1-h2466b09_2.conda + sha256: ba945c6493449bed0e6e29883c4943817f7c79cbff52b83360f7b341277c6402 + md5: 41fbfac52c601159df6c01f875de31b9 + depends: + - ucrt >=10.0.20348.0 + - vc >=14.2,<15 + - vc14_runtime >=14.29.30139 + constrains: + - zlib 1.3.1 *_2 + license: Zlib + license_family: Other + purls: [] + size: 55476 + timestamp: 1727963768015 +- conda: https://conda.anaconda.org/conda-forge/noarch/markdownify-1.1.0-pyhd8ed1ab_1.conda + sha256: 127f040a4ec203d0e9af708600113fcb7641948f0f0f790c6906738397efa947 + md5: 4792499afa7a0fdfcff1533cab5564b3 + depends: + - beautifulsoup4 >=4.9,<5 + - python >=3.8 + - six >=1.15,<2 + license: MIT + license_family: MIT + purls: + - pkg:pypi/markdownify?source=hash-mapping + size: 19359 + timestamp: 1756304996346 +- conda: https://conda.anaconda.org/conda-forge/noarch/markdownify-1.2.0-pyhcf101f3_0.conda + sha256: cb4991ecdb0e08f88ba9438ccd55c577e1d62e9184b5b552b517038774dccee0 + md5: 2b07b14f9a78dec85e80ae4a2fa3d964 + depends: + - python >=3.10 + - beautifulsoup4 >=4.9,<5 + - six >=1.15,<2 + - python + license: MIT + license_family: MIT + purls: + - pkg:pypi/markdownify?source=hash-mapping + size: 21734 + timestamp: 1760956933495 +- conda: https://conda.anaconda.org/conda-forge/linux-64/ncurses-6.5-h2d0b736_3.conda + sha256: 3fde293232fa3fca98635e1167de6b7c7fda83caf24b9d6c91ec9eefb4f4d586 + md5: 47e340acb35de30501a76c7c799c41d7 + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=13 + license: X11 AND BSD-3-Clause + purls: [] + size: 891641 + timestamp: 1738195959188 +- conda: https://conda.anaconda.org/conda-forge/linux-64/openssl-3.6.0-h26f9b46_0.conda + sha256: a47271202f4518a484956968335b2521409c8173e123ab381e775c358c67fe6d + md5: 9ee58d5c534af06558933af3c845a780 + depends: + - __glibc >=2.17,<3.0.a0 + - ca-certificates + - libgcc >=14 + license: Apache-2.0 + license_family: Apache + purls: [] + size: 3165399 + timestamp: 1762839186699 +- conda: https://conda.anaconda.org/conda-forge/win-64/openssl-3.5.3-h725018a_1.conda + sha256: 72dc204b0d59a7262bc77ca0e86cba11cbc6706cb9b4d6656fe7fab9593347c9 + md5: c84884e2c1f899de9a895a1f0b7c9cd8 + depends: + - ca-certificates + - ucrt >=10.0.20348.0 + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + license: Apache-2.0 + license_family: Apache + purls: [] + size: 9276051 + timestamp: 1758599639304 +- conda: https://conda.anaconda.org/conda-forge/noarch/pixi-pycharm-0.0.9-unix_hf108a03_0.conda + sha256: adb49cb011bc758a18d7729431d393c96b1686e9cb8b2b0a76f158a20a590743 + md5: 178205e98910428bf7411888adf63033 + depends: + - __unix + - python >=3.8 + license: BSD-3-Clause + license_family: BSD + purls: [] + size: 6681 + timestamp: 1758810290525 +- conda: https://conda.anaconda.org/conda-forge/noarch/pixi-pycharm-0.0.9-win_hba80fca_0.conda + sha256: 22c6fedb771249e8b9723a916a3dc558b74681d3d8c2f118667cba81851f2002 + md5: de239ce0ba6c1b8d4bdd791650d8067c + depends: + - __win + - python >=3.8 + license: BSD-3-Clause + purls: [] + size: 6687 + timestamp: 1758810287418 +- conda: https://conda.anaconda.org/conda-forge/noarch/pycparser-2.22-pyh29332c3_1.conda + sha256: 79db7928d13fab2d892592223d7570f5061c192f27b9febd1a418427b719acc6 + md5: 12c566707c80111f9799308d9e265aef + depends: + - python >=3.9 + - python + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/pycparser?source=hash-mapping + size: 110100 + timestamp: 1733195786147 +- conda: https://conda.anaconda.org/conda-forge/noarch/pysocks-1.7.1-pyh09c184e_7.conda + sha256: d016e04b0e12063fbee4a2d5fbb9b39a8d191b5a0042f0b8459188aedeabb0ca + md5: e2fd202833c4a981ce8a65974fe4abd1 + depends: + - __win + - python >=3.9 + - win_inet_pton + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/pysocks?source=hash-mapping + size: 21784 + timestamp: 1733217448189 +- conda: https://conda.anaconda.org/conda-forge/noarch/pysocks-1.7.1-pyha55dd90_7.conda + sha256: ba3b032fa52709ce0d9fd388f63d330a026754587a2f461117cac9ab73d8d0d8 + md5: 461219d1a5bd61342293efa2c0c90eac + depends: + - __unix + - python >=3.9 + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/pysocks?source=hash-mapping + size: 21085 + timestamp: 1733217331982 +- conda: https://conda.anaconda.org/conda-forge/linux-64/python-3.11.14-hd63d673_2_cpython.conda + build_number: 2 + sha256: 5b872f7747891e50e990a96d2b235236a5c66cc9f8c9dcb7149aee674ea8145a + md5: c4202a55b4486314fbb8c11bc43a29a0 + depends: + - __glibc >=2.17,<3.0.a0 + - bzip2 >=1.0.8,<2.0a0 + - ld_impl_linux-64 >=2.36.1 + - libexpat >=2.7.1,<3.0a0 + - libffi >=3.5.2,<3.6.0a0 + - libgcc >=14 + - liblzma >=5.8.1,<6.0a0 + - libnsl >=2.0.1,<2.1.0a0 + - libsqlite >=3.50.4,<4.0a0 + - libuuid >=2.41.2,<3.0a0 + - libxcrypt >=4.4.36 + - libzlib >=1.3.1,<2.0a0 + - ncurses >=6.5,<7.0a0 + - openssl >=3.5.4,<4.0a0 + - readline >=8.2,<9.0a0 + - tk >=8.6.13,<8.7.0a0 + - tzdata + constrains: + - python_abi 3.11.* *_cp311 + license: Python-2.0 + purls: [] + size: 30874708 + timestamp: 1761174520369 +- conda: https://conda.anaconda.org/conda-forge/win-64/python-3.11.13-h3f84c4b_0_cpython.conda + sha256: 723dbca1384f30bd2070f77dd83eefd0e8d7e4dda96ac3332fbf8fe5573a8abb + md5: bedbb6f7bb654839719cd528f9b298ad + depends: + - bzip2 >=1.0.8,<2.0a0 + - libexpat >=2.7.0,<3.0a0 + - libffi >=3.4.6,<3.5.0a0 + - liblzma >=5.8.1,<6.0a0 + - libsqlite >=3.50.0,<4.0a0 + - libzlib >=1.3.1,<2.0a0 + - openssl >=3.5.0,<4.0a0 + - tk >=8.6.13,<8.7.0a0 + - tzdata + - ucrt >=10.0.20348.0 + - vc >=14.2,<15 + - vc14_runtime >=14.29.30139 + constrains: + - python_abi 3.11.* *_cp311 + license: Python-2.0 + purls: [] + size: 18242669 + timestamp: 1749048351218 +- conda: https://conda.anaconda.org/conda-forge/noarch/python_abi-3.11-8_cp311.conda + build_number: 8 + sha256: fddf123692aa4b1fc48f0471e346400d9852d96eeed77dbfdd746fa50a8ff894 + md5: 8fcb6b0e2161850556231336dae58358 + constrains: + - python 3.11.* *_cpython + license: BSD-3-Clause + license_family: BSD + purls: [] + size: 7003 + timestamp: 1752805919375 +- conda: https://conda.anaconda.org/conda-forge/linux-64/readline-8.2-h8c095d6_2.conda + sha256: 2d6d0c026902561ed77cd646b5021aef2d4db22e57a5b0178dfc669231e06d2c + md5: 283b96675859b20a825f8fa30f311446 + depends: + - libgcc >=13 + - ncurses >=6.5,<7.0a0 + license: GPL-3.0-only + license_family: GPL + purls: [] + size: 282480 + timestamp: 1740379431762 +- conda: https://conda.anaconda.org/conda-forge/noarch/requests-2.32.5-pyhd8ed1ab_0.conda + sha256: 8dc54e94721e9ab545d7234aa5192b74102263d3e704e6d0c8aa7008f2da2a7b + md5: db0c6b99149880c8ba515cf4abe93ee4 + depends: + - certifi >=2017.4.17 + - charset-normalizer >=2,<4 + - idna >=2.5,<4 + - python >=3.9 + - urllib3 >=1.21.1,<3 + constrains: + - chardet >=3.0.2,<6 + license: Apache-2.0 + license_family: APACHE + purls: + - pkg:pypi/requests?source=hash-mapping + size: 59263 + timestamp: 1755614348400 +- conda: https://conda.anaconda.org/conda-forge/linux-64/ruamel.yaml-0.18.16-py311h49ec1c0_0.conda + sha256: 3ad29409ab3742a4cab9e42fc936a2678249c97bbe014905d215afab3aaf13bb + md5: 945689ec8e69e9f254eceef82c9f932f + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + - python >=3.11,<3.12.0a0 + - python_abi 3.11.* *_cp311 + - ruamel.yaml.clib >=0.1.2 + license: MIT + license_family: MIT + purls: + - pkg:pypi/ruamel-yaml?source=hash-mapping + size: 276028 + timestamp: 1761160704044 +- conda: https://conda.anaconda.org/conda-forge/win-64/ruamel.yaml-0.18.15-py311h3485c13_1.conda + sha256: 4a222db2ec50db5a11ace74090045170a611b24f82d80535e8d98bf478c32cc2 + md5: be5d5993e755c3edc3ef860da01c67b4 + depends: + - python >=3.11,<3.12.0a0 + - python_abi 3.11.* *_cp311 + - ruamel.yaml.clib >=0.1.2 + - ucrt >=10.0.20348.0 + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + license: MIT + license_family: MIT + purls: + - pkg:pypi/ruamel-yaml?source=hash-mapping + size: 274899 + timestamp: 1756839144620 +- conda: https://conda.anaconda.org/conda-forge/linux-64/ruamel.yaml.clib-0.2.14-py311h49ec1c0_0.conda + sha256: c17b1dc975839a35efc434d3391b8aaa1977a541641d8729c883d5106fb64b97 + md5: 4942e6597e5fc980020b648353c711d4 + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + - python >=3.11,<3.12.0a0 + - python_abi 3.11.* *_cp311 + license: MIT + license_family: MIT + purls: + - pkg:pypi/ruamel-yaml-clib?source=hash-mapping + size: 141908 + timestamp: 1760564334661 +- conda: https://conda.anaconda.org/conda-forge/win-64/ruamel.yaml.clib-0.2.12-py311h3485c13_1.conda + sha256: ad383a91985153438817e6b241c9f151692e01ef257279f83dec55f8d024e213 + md5: 713991ee78f7fbbcecfe03c7226dec24 + depends: + - python >=3.11,<3.12.0a0 + - python_abi 3.11.* *_cp311 + - ucrt >=10.0.20348.0 + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + license: MIT + license_family: MIT + purls: + - pkg:pypi/ruamel-yaml-clib?source=hash-mapping + size: 107842 + timestamp: 1756829092915 +- pypi: https://files.pythonhosted.org/packages/ad/d5/62a0e693230bace8e9a767d6d187a4d9421a7c6ee4b48551f8ff7bd1629a/sensai_utils-1.5.0-py3-none-any.whl + name: sensai-utils + version: 1.5.0 + sha256: c23c51d23d353e7a9b77c70aa8ba7f2d0a8fe4e67ee5bc1ef41c69dba5e4befb + requires_dist: + - typing-extensions>=4.6 +- pypi: https://files.pythonhosted.org/packages/3f/85/5a634f5bae4e73c9d11dd7d613338c0414d07455a1a211175bcc52e66aec/sensai_utils-1.6.0-py3-none-any.whl + name: sensai-utils + version: 1.6.0 + sha256: 3298d4d21bdf7a1b91873213614c748ceb17014c7d72c9492bd5bf82ba893f9d + requires_dist: + - typing-extensions>=4.6 +- conda: https://conda.anaconda.org/conda-forge/noarch/six-1.17.0-pyhe01879c_1.conda + sha256: 458227f759d5e3fcec5d9b7acce54e10c9e1f4f4b7ec978f3bfd54ce4ee9853d + md5: 3339e3b65d58accf4ca4fb8748ab16b3 + depends: + - python >=3.9 + - python + license: MIT + license_family: MIT + purls: + - pkg:pypi/six?source=hash-mapping + size: 18455 + timestamp: 1753199211006 +- conda: https://conda.anaconda.org/conda-forge/noarch/soupsieve-2.8-pyhd8ed1ab_0.conda + sha256: c978576cf9366ba576349b93be1cfd9311c00537622a2f9e14ba2b90c97cae9c + md5: 18c019ccf43769d211f2cf78e9ad46c2 + depends: + - python >=3.10 + license: MIT + license_family: MIT + purls: + - pkg:pypi/soupsieve?source=hash-mapping + size: 37803 + timestamp: 1756330614547 +- conda: https://conda.anaconda.org/conda-forge/linux-64/tk-8.6.13-noxft_ha0e22de_103.conda + sha256: 1544760538a40bcd8ace2b1d8ebe3eb5807ac268641f8acdc18c69c5ebfeaf64 + md5: 86bc20552bf46075e3d92b67f089172d + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=13 + - libzlib >=1.3.1,<2.0a0 + constrains: + - xorg-libx11 >=1.8.12,<2.0a0 + license: TCL + license_family: BSD + purls: [] + size: 3284905 + timestamp: 1763054914403 +- conda: https://conda.anaconda.org/conda-forge/win-64/tk-8.6.13-h2c6b04d_2.conda + sha256: e3614b0eb4abcc70d98eae159db59d9b4059ed743ef402081151a948dce95896 + md5: ebd0e761de9aa879a51d22cc721bd095 + depends: + - ucrt >=10.0.20348.0 + - vc >=14.2,<15 + - vc14_runtime >=14.29.30139 + license: TCL + license_family: BSD + purls: [] + size: 3466348 + timestamp: 1748388121356 +- conda: https://conda.anaconda.org/conda-forge/noarch/typing-extensions-4.15.0-h396c80c_0.conda + sha256: 7c2df5721c742c2a47b2c8f960e718c930031663ac1174da67c1ed5999f7938c + md5: edd329d7d3a4ab45dcf905899a7a6115 + depends: + - typing_extensions ==4.15.0 pyhcf101f3_0 + license: PSF-2.0 + license_family: PSF + purls: [] + size: 91383 + timestamp: 1756220668932 +- conda: https://conda.anaconda.org/conda-forge/noarch/typing_extensions-4.15.0-pyhcf101f3_0.conda + sha256: 032271135bca55aeb156cee361c81350c6f3fb203f57d024d7e5a1fc9ef18731 + md5: 0caa1af407ecff61170c9437a808404d + depends: + - python >=3.10 + - python + license: PSF-2.0 + license_family: PSF + purls: + - pkg:pypi/typing-extensions?source=hash-mapping + size: 51692 + timestamp: 1756220668932 +- conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-2025b-h78e105d_0.conda + sha256: 5aaa366385d716557e365f0a4e9c3fca43ba196872abbbe3d56bb610d131e192 + md5: 4222072737ccff51314b5ece9c7d6f5a + license: LicenseRef-Public-Domain + purls: [] + size: 122968 + timestamp: 1742727099393 +- conda: https://conda.anaconda.org/conda-forge/win-64/ucrt-10.0.26100.0-h57928b3_0.conda + sha256: 3005729dce6f3d3f5ec91dfc49fc75a0095f9cd23bab49efb899657297ac91a5 + md5: 71b24316859acd00bdb8b38f5e2ce328 + constrains: + - vc14_runtime >=14.29.30037 + - vs2015_runtime >=14.29.30037 + license: LicenseRef-MicrosoftWindowsSDK10 + purls: [] + size: 694692 + timestamp: 1756385147981 +- conda: https://conda.anaconda.org/conda-forge/noarch/urllib3-2.5.0-pyhd8ed1ab_0.conda + sha256: 4fb9789154bd666ca74e428d973df81087a697dbb987775bc3198d2215f240f8 + md5: 436c165519e140cb08d246a4472a9d6a + depends: + - brotli-python >=1.0.9 + - h2 >=4,<5 + - pysocks >=1.5.6,<2.0,!=1.5.7 + - python >=3.9 + - zstandard >=0.18.0 + license: MIT + license_family: MIT + purls: + - pkg:pypi/urllib3?source=hash-mapping + size: 101735 + timestamp: 1750271478254 +- conda: https://conda.anaconda.org/conda-forge/win-64/vc-14.3-h41ae7f8_31.conda + sha256: cb357591d069a1e6cb74199a8a43a7e3611f72a6caed9faa49dbb3d7a0a98e0b + md5: 28f4ca1e0337d0f27afb8602663c5723 + depends: + - vc14_runtime >=14.44.35208 + track_features: + - vc14 + license: BSD-3-Clause + license_family: BSD + purls: [] + size: 18249 + timestamp: 1753739241465 +- conda: https://conda.anaconda.org/conda-forge/win-64/vc14_runtime-14.44.35208-h818238b_31.conda + sha256: af4b4b354b87a9a8d05b8064ff1ea0b47083274f7c30b4eb96bc2312c9b5f08f + md5: 603e41da40a765fd47995faa021da946 + depends: + - ucrt >=10.0.20348.0 + - vcomp14 14.44.35208 h818238b_31 + constrains: + - vs2015_runtime 14.44.35208.* *_31 + license: LicenseRef-MicrosoftVisualCpp2015-2022Runtime + license_family: Proprietary + purls: [] + size: 682424 + timestamp: 1753739239305 +- conda: https://conda.anaconda.org/conda-forge/win-64/vcomp14-14.44.35208-h818238b_31.conda + sha256: 67b317b64f47635415776718d25170a9a6f9a1218c0f5a6202bfd687e07b6ea4 + md5: a6b1d5c1fc3cb89f88f7179ee6a9afe3 + depends: + - ucrt >=10.0.20348.0 + constrains: + - vs2015_runtime 14.44.35208.* *_31 + license: LicenseRef-MicrosoftVisualCpp2015-2022Runtime + license_family: Proprietary + purls: [] + size: 113963 + timestamp: 1753739198723 +- conda: https://conda.anaconda.org/conda-forge/noarch/win_inet_pton-1.1.0-pyh7428d3b_8.conda + sha256: 93807369ab91f230cf9e6e2a237eaa812492fe00face5b38068735858fba954f + md5: 46e441ba871f524e2b067929da3051c2 + depends: + - __win + - python >=3.9 + license: LicenseRef-Public-Domain + purls: + - pkg:pypi/win-inet-pton?source=hash-mapping + size: 9555 + timestamp: 1733130678956 +- conda: https://conda.anaconda.org/conda-forge/linux-64/zstandard-0.25.0-py311haee01d2_1.conda + sha256: d534a6518c2d8eccfa6579d75f665261484f0f2f7377b50402446a9433d46234 + md5: ca45bfd4871af957aaa5035593d5efd2 + depends: + - python + - cffi >=1.11 + - zstd >=1.5.7,<1.5.8.0a0 + - libgcc >=14 + - __glibc >=2.17,<3.0.a0 + - zstd >=1.5.7,<1.6.0a0 + - python_abi 3.11.* *_cp311 + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/zstandard?source=hash-mapping + size: 466893 + timestamp: 1762512695614 +- conda: https://conda.anaconda.org/conda-forge/win-64/zstandard-0.25.0-py311hf893f09_0.conda + sha256: 3b66d3cb738a9993e8432d1a03402d207128166c4ef0c928e712958e51aff325 + md5: d26077d20b4bba54f4c718ed1313805f + depends: + - python + - cffi >=1.11 + - zstd >=1.5.7,<1.5.8.0a0 + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + - ucrt >=10.0.20348.0 + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + - ucrt >=10.0.20348.0 + - zstd >=1.5.7,<1.6.0a0 + - python_abi 3.11.* *_cp311 + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/zstandard?source=hash-mapping + size: 375866 + timestamp: 1757930134099 +- conda: https://conda.anaconda.org/conda-forge/linux-64/zstd-1.5.7-hb8e6e7a_2.conda + sha256: a4166e3d8ff4e35932510aaff7aa90772f84b4d07e9f6f83c614cba7ceefe0eb + md5: 6432cb5d4ac0046c3ac0a8a0f95842f9 + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=13 + - libstdcxx >=13 + - libzlib >=1.3.1,<2.0a0 + license: BSD-3-Clause + license_family: BSD + purls: [] + size: 567578 + timestamp: 1742433379869 +- conda: https://conda.anaconda.org/conda-forge/win-64/zstd-1.5.7-hbeecb71_2.conda + sha256: bc64864377d809b904e877a98d0584f43836c9f2ef27d3d2a1421fa6eae7ca04 + md5: 21f56217d6125fb30c3c3f10c786d751 + depends: + - libzlib >=1.3.1,<2.0a0 + - ucrt >=10.0.20348.0 + - vc >=14.2,<15 + - vc14_runtime >=14.29.30139 + license: BSD-3-Clause + license_family: BSD + purls: [] + size: 354697 + timestamp: 1742433568506 diff --git a/mcp/types-generator/pixi.toml b/mcp/types-generator/pixi.toml new file mode 100644 index 0000000000..d3fa902d5b --- /dev/null +++ b/mcp/types-generator/pixi.toml @@ -0,0 +1,20 @@ +[workspace] +authors = ["Oraios AI "] +channels = ["conda-forge"] +description = "Scripts supporting the development of the Penpot MCP server" +name = "penpot-mcp-scripts" +platforms = ["win-64","linux-64"] +version = "0.1.0" + +[tasks] + +[dependencies] +python = "3.11.*" +pixi-pycharm = ">=0.0.9,<0.0.10" +beautifulsoup4 = ">=4.13.5,<5" +markdownify = ">=1.1.0,<2" +requests = ">=2.32.5,<3" +"ruamel.yaml" = ">=0.18.15,<0.19" + +[pypi-dependencies] +sensai-utils = ">=1.5.0, <2" diff --git a/mcp/types-generator/prepare_api_docs.py b/mcp/types-generator/prepare_api_docs.py new file mode 100644 index 0000000000..f5f4c72212 --- /dev/null +++ b/mcp/types-generator/prepare_api_docs.py @@ -0,0 +1,269 @@ +import collections +import dataclasses +import os +from dataclasses import dataclass +from io import StringIO +from pathlib import Path + +import requests +from bs4 import BeautifulSoup, Tag +from markdownify import MarkdownConverter +from ruamel.yaml import YAML +from ruamel.yaml.scalarstring import LiteralScalarString +from sensai.util import logging +import sys + +log = logging.getLogger(__name__) + +class PenpotAPIContentMarkdownConverter(MarkdownConverter): + """ + Markdown converter for Penpot API docs, specifically for the .col-content element + (and sub-elements thereof) + """ + def process_tag(self, node, parent_tags=None): + soup = BeautifulSoup(str(node), "html.parser") + + # skip breadcrumbs + if "class" in node.attrs and "tsd-breadcrumb" in node.attrs["class"]: + return "" + + # convert h5 and h4 to plain text + if node.name in ["h5", "h4"]: + return soup.get_text() + + text = soup.get_text() + + # convert tsd-tag code elements (containing e.g. "Readonly" and "Optional" designations) + # If we encounter them at this level, we just remove them, as they are redundant. + # The significant such tags are handled in the tsd-signature processing below. + if node.name == "code" and "class" in node.attrs and "tsd-tag" in node.attrs["class"]: + return "" + + # skip buttons (e.g. "Copy") + if node.name == "button": + return "" + + # skip links to definitions in
  • elements + if node.name == "li" and text.startswith("Defined in"): + return "" + + # for links, just return the text + if node.name == "a": + return text + + # skip inheritance information + if node.name == "p" and text.startswith("Inherited from"): + return "" + + # remove index with links + if "class" in node.attrs and "tsd-index-content" in node.attrs["class"]: + return "" + + # convert
     blocks to markdown code blocks
    +        if node.name == "pre":
    +            for button in soup.find_all("button"):
    +                button.decompose()
    +            return f"\n```\n{soup.get_text().strip()}\n```\n\n"
    +
    +        # convert tsd-signature elements to code blocks, converting 
    to newlines + if "class" in node.attrs and "tsd-signature" in node.attrs["class"]: + # convert
    to newlines + for br in soup.find_all("br"): + br.replace_with("\n") + # process tsd-tags (keeping only "readonly"; optional is redundant, as it is indicated via "?") + for tag in soup.find_all(attrs={"class": "tsd-tag"}): + tag_lower = tag.get_text().strip().lower() + if tag_lower in ["readonly"]: + tag.replace_with(f"{tag_lower} ") + else: + tag.decompose() + # return as code block + return f"\n```\n{soup.get_text()}\n```\n\n" + + # other cases: use the default processing + return super().process_tag(node, parent_tags=parent_tags) + + +@dataclass +class TypeInfo: + overview: str + """ + the main type information, which contains all the declarations/signatures but no descriptions + """ + members: dict[str, dict[str, str]] + """ + mapping from member type (e.g. "Properties", "Methods") to a mapping of member name to markdown description + """ + + def add_referencing_types(self, referencing_types: set[str]): + if referencing_types: + self.overview += "\n\nReferenced by: " + ", ".join(sorted(referencing_types)) + + def __repr__(self) -> str: + num_members = {k: len(v) for k, v in self.members.items()} + return f"TypeInfo(overview_length={len(self.overview)}, num_members={num_members})" + + +class YamlConverter: + """Converts dictionaries to YAML with all strings in block literal style""" + + def __init__(self): + self.yaml = YAML() + self.yaml.preserve_quotes = True + self.yaml.width = 4096 # Prevent line wrapping + + def _convert_strings_to_block(self, obj): + if isinstance(obj, dict): + return {k: self._convert_strings_to_block(v) for k, v in obj.items()} + elif isinstance(obj, list): + return [self._convert_strings_to_block(item) for item in obj] + elif isinstance(obj, str): + return LiteralScalarString(obj) + else: + return obj + + def to_yaml(self, data): + processed_data = self._convert_strings_to_block(data) + stream = StringIO() + self.yaml.dump(processed_data, stream) + return stream.getvalue() + + def to_file(self, data, filepath): + processed_data = self._convert_strings_to_block(data) + with open(filepath, 'w', encoding='utf-8') as f: + self.yaml.dump(processed_data, f) + + +class PenpotAPIDocsProcessor: + def __init__(self, url=None): + self.md_converter = PenpotAPIContentMarkdownConverter() + self.base_url = url + self.types: dict[str, TypeInfo] = {} + self.type_referenced_by: dict[str, set[str]] = collections.defaultdict(set) + + def run(self, target_dir: str): + os.makedirs(target_dir, exist_ok=True) + + # find links to all interfaces and types + modules_page = self._fetch("modules.html") + soup = BeautifulSoup(modules_page, "html.parser") + content = soup.find(attrs={"class": "col-content"}) + links = content.find_all("a", href=True) + + # process each link, converting interface and type pages to markdown + for link in links: + href = link['href'] + if href.startswith("interfaces/") or href.startswith("types/"): + type_name = href.split("/")[-1].replace(".html", "") + log.info("Processing page: %s", type_name) + type_info = self.process_page(href, type_name) + print(f"Adding '{type_name}' with {type_info}") + self.types[type_name] = type_info + + # add type reference information + for type_name, type_info in self.types.items(): + referencing_types = self.type_referenced_by.get(type_name, set()) + type_info.add_referencing_types(referencing_types) + + # save to yaml + yaml_path = os.path.join(target_dir, "api_types.yml") + log.info("Writing API type information to %s", yaml_path) + data_dict = {k: dataclasses.asdict(v) for k, v in self.types.items()} + YamlConverter().to_file(data_dict, yaml_path) + + def _fetch(self, rel_url: str) -> bytes: + response = requests.get(f"{self.base_url}/{rel_url}") + if response.status_code != 200: + raise Exception(f"Failed to retrieve page: {response.status_code}") + html_content = response.content + return html_content + + def _html_to_markdown(self, html_content: str) -> str: + md = self.md_converter.convert(html_content) + md = md.replace("\xa0", " ") # replace non-breaking spaces + return md.strip() + + def process_page(self, rel_url: str, type_name: str) -> TypeInfo: + html_content = self._fetch(rel_url) + soup = BeautifulSoup(html_content, "html.parser") + + content = soup.find(attrs={"class": "col-content"}) + # full_text = self._html_to_markdown(str(content)) + + # extract individual members + members = {} + member_group_tags = [] + for el in content.children: + if isinstance(el, Tag): + if "class" in el.attrs and "tsd-member-group" in el.attrs["class"]: + member_group_tags.append(el) + members_type = el.find("h2").get_text().strip() + members_in_group = {} + members[members_type] = members_in_group + for member_tag in el.find_all(attrs={"class": "tsd-member"}): + member_anchor = member_tag.find("a", attrs={"class": "tsd-anchor"}, recursive=False) + member_name = member_anchor.attrs["id"] + member_heading = member_tag.find("h3") + # extract tsd-tag info (e.g., "Readonly") from the heading and reinsert it into the signature, + # where we want to see it. The heading is removed, as it is redundant. + if member_heading: + tags_in_heading = member_heading.find_all(attrs={"class": "tsd-tag"}) + if tags_in_heading: + signature_tag = member_tag.find(attrs={"class": "tsd-signature"}) + if signature_tag: + for tag in reversed(tags_in_heading): + signature_tag.insert(0, tag) + member_heading.decompose() + # convert to markdown + tag_text = str(member_tag) + members_in_group[member_name] = self._html_to_markdown(tag_text) + + # record references to other types in signature + signature = content.find("div", attrs={"class": "tsd-signature"}) + for link_to_type in signature.find_all("a", attrs={"class": "tsd-signature-type"}): + referenced_type_name = link_to_type.get_text().strip() + self.type_referenced_by[referenced_type_name].add(type_name) + + # remove the member groups from the soup + for tag in member_group_tags: + tag.decompose() + + # overview is what remains in content after removing member groups + overview = self._html_to_markdown(str(content)) + + return TypeInfo( + overview=overview, + members=members + ) + + +DEFAULT_API_DOCS_URL = "http://localhost:9090" + +def main(): + target_dir = Path(__file__).parent.parent / "packages" / "server" / "data" + url = sys.argv[1] if len(sys.argv) > 1 else DEFAULT_API_DOCS_URL + + print("Fetching plugin data from: {}".format(url)) + PenpotAPIDocsProcessor(url).run(target_dir=str(target_dir)) + + +def debug_type_conversion(rel_url: str): + """ + This function is for debugging purposes only. + It processes a single type page and prints the converted markdown to the console. + + :param rel_url: relative URL of the type page (e.g., "interfaces/ShapeBase") + """ + type_name = rel_url.split("/")[-1] + processor = PenpotAPIDocsProcessor() + type_info = processor.process_page(rel_url, type_name) + print(f"--- overview ---\n{type_info.overview}\n") + for member_type, members in type_info.members.items(): + print(f"\n{member_type}:") + for member_name, member_md in members.items(): + print(f"--- {member_name} ---\n{member_md}\n") + + +if __name__ == '__main__': + # debug_type_conversion("interfaces/LayoutChildProperties") + logging.run_main(main) diff --git a/package.json b/package.json index ccfb8377a6..0a6d43e4f6 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,7 @@ "license": "MPL-2.0", "author": "Kaleidos INC", "private": true, - "packageManager": "pnpm@10.26.2+sha512.0e308ff2005fc7410366f154f625f6631ab2b16b1d2e70238444dd6ae9d630a8482d92a451144debc492416896ed16f7b114a86ec68b8404b2443869e68ffda6", + "packageManager": "pnpm@10.28.2+sha512.41872f037ad22f7348e3b1debbaf7e867cfd448f2726d9cf74c08f19507c31d2c8e7a11525b983febc2df640b5438dee6023ebb1f84ed43cc2d654d2bc326264", "repository": { "type": "git", "url": "https://github.com/penpot/penpot"