diff --git a/.codespellrc b/.codespellrc index 7f9b3fa8..39d8ab1d 100644 --- a/.codespellrc +++ b/.codespellrc @@ -11,6 +11,7 @@ # categor - TypeScript template literal in website/src/scripts/pages/skills.ts:70 (categor${...length > 1 ? "ies" : "y"}) # aline - proper name (Aline Ávila, contributor) # ative - part of "Declarative Agents" in TypeSpec M365 Copilot documentation (collections/typespec-m365-copilot.collection.md) -ignore-words-list = numer,wit,aks,edn,ser,ois,gir,rouge,categor,aline,ative,afterall,deques +# dateA, dateB - variable names used in sorting comparison functions +ignore-words-list = numer,wit,aks,edn,ser,ois,gir,rouge,categor,aline,ative,afterall,deques,dateA,dateB # Skip certain files and directories skip = .git,node_modules,package-lock.json,*.lock,website/build,website/.docusaurus diff --git a/.github/workflows/deploy-website.yml b/.github/workflows/deploy-website.yml index e7f7e81d..aca15150 100644 --- a/.github/workflows/deploy-website.yml +++ b/.github/workflows/deploy-website.yml @@ -40,6 +40,8 @@ jobs: steps: - name: Checkout uses: actions/checkout@v4 + with: + fetch-depth: 0 # Full history needed for git-based last updated dates - name: Setup Node.js uses: actions/setup-node@v4 diff --git a/agents/dotnet-upgrade.agent.md b/agents/dotnet-upgrade.agent.md index 7e7bd5e6..083a2f13 100644 --- a/agents/dotnet-upgrade.agent.md +++ b/agents/dotnet-upgrade.agent.md @@ -122,7 +122,7 @@ dotnet msbuild .csproj /t:GenerateRestoreGraphFile /p:RestoreGraphO ## Classification Rules - `TargetFramework` starts with `netcoreapp`, `net5.0+`, `net6.0+`, etc. → **Modern .NET** - `netstandard*` → **.NET Standard** (migrate to current .NET version) -- `net4*` → **.NET Framework** (migrate via intermediate step to .NET 6+) +- `net4*` → **.NET Framework** (migrate via intermediate step to .NET 8+) --- diff --git a/cookbook/copilot-sdk/python/pr-visualization.md b/cookbook/copilot-sdk/python/pr-visualization.md index 3d8b7969..0419aed1 100644 --- a/cookbook/copilot-sdk/python/pr-visualization.md +++ b/cookbook/copilot-sdk/python/pr-visualization.md @@ -20,7 +20,7 @@ You want to understand how long PRs have been open in a repository. This tool de ## Prerequisites ```bash -pip install copilot-sdk +pip install github-copilot-sdk ``` ## Usage diff --git a/docs/README.prompts.md b/docs/README.prompts.md index 6a828118..816a758b 100644 --- a/docs/README.prompts.md +++ b/docs/README.prompts.md @@ -98,7 +98,7 @@ Ready-to-use prompt templates for specific development scenarios and tasks, defi | [Microsoft 365 Declarative Agents Development Kit](../prompts/declarative-agents.prompt.md)
[![Install in VS Code](https://img.shields.io/badge/VS_Code-Install-0098FF?style=flat-square&logo=visualstudiocode&logoColor=white)](https://aka.ms/awesome-copilot/install/prompt?url=vscode%3Achat-prompt%2Finstall%3Furl%3Dhttps%3A%2F%2Fraw.githubusercontent.com%2Fgithub%2Fawesome-copilot%2Fmain%2Fprompts%2Fdeclarative-agents.prompt.md)
[![Install in VS Code Insiders](https://img.shields.io/badge/VS_Code_Insiders-Install-24bfa5?style=flat-square&logo=visualstudiocode&logoColor=white)](https://aka.ms/awesome-copilot/install/prompt?url=vscode-insiders%3Achat-prompt%2Finstall%3Furl%3Dhttps%3A%2F%2Fraw.githubusercontent.com%2Fgithub%2Fawesome-copilot%2Fmain%2Fprompts%2Fdeclarative-agents.prompt.md) | Complete development kit for Microsoft 365 Copilot declarative agents with three comprehensive workflows (basic, advanced, validation), TypeSpec support, and Microsoft 365 Agents Toolkit integration | | [Migration and Code Evolution Instructions Generator](../prompts/generate-custom-instructions-from-codebase.prompt.md)
[![Install in VS Code](https://img.shields.io/badge/VS_Code-Install-0098FF?style=flat-square&logo=visualstudiocode&logoColor=white)](https://aka.ms/awesome-copilot/install/prompt?url=vscode%3Achat-prompt%2Finstall%3Furl%3Dhttps%3A%2F%2Fraw.githubusercontent.com%2Fgithub%2Fawesome-copilot%2Fmain%2Fprompts%2Fgenerate-custom-instructions-from-codebase.prompt.md)
[![Install in VS Code Insiders](https://img.shields.io/badge/VS_Code_Insiders-Install-24bfa5?style=flat-square&logo=visualstudiocode&logoColor=white)](https://aka.ms/awesome-copilot/install/prompt?url=vscode-insiders%3Achat-prompt%2Finstall%3Furl%3Dhttps%3A%2F%2Fraw.githubusercontent.com%2Fgithub%2Fawesome-copilot%2Fmain%2Fprompts%2Fgenerate-custom-instructions-from-codebase.prompt.md) | Migration and code evolution instructions generator for GitHub Copilot. Analyzes differences between two project versions (branches, commits, or releases) to create precise instructions allowing Copilot to maintain consistency during technology migrations, major refactoring, or framework version upgrades. | | [MkDocs AI Translator](../prompts/mkdocs-translations.prompt.md)
[![Install in VS Code](https://img.shields.io/badge/VS_Code-Install-0098FF?style=flat-square&logo=visualstudiocode&logoColor=white)](https://aka.ms/awesome-copilot/install/prompt?url=vscode%3Achat-prompt%2Finstall%3Furl%3Dhttps%3A%2F%2Fraw.githubusercontent.com%2Fgithub%2Fawesome-copilot%2Fmain%2Fprompts%2Fmkdocs-translations.prompt.md)
[![Install in VS Code Insiders](https://img.shields.io/badge/VS_Code_Insiders-Install-24bfa5?style=flat-square&logo=visualstudiocode&logoColor=white)](https://aka.ms/awesome-copilot/install/prompt?url=vscode-insiders%3Achat-prompt%2Finstall%3Furl%3Dhttps%3A%2F%2Fraw.githubusercontent.com%2Fgithub%2Fawesome-copilot%2Fmain%2Fprompts%2Fmkdocs-translations.prompt.md) | Generate a language translation for a mkdocs documentation stack. | -| [MSTest Best Practices](../prompts/csharp-mstest.prompt.md)
[![Install in VS Code](https://img.shields.io/badge/VS_Code-Install-0098FF?style=flat-square&logo=visualstudiocode&logoColor=white)](https://aka.ms/awesome-copilot/install/prompt?url=vscode%3Achat-prompt%2Finstall%3Furl%3Dhttps%3A%2F%2Fraw.githubusercontent.com%2Fgithub%2Fawesome-copilot%2Fmain%2Fprompts%2Fcsharp-mstest.prompt.md)
[![Install in VS Code Insiders](https://img.shields.io/badge/VS_Code_Insiders-Install-24bfa5?style=flat-square&logo=visualstudiocode&logoColor=white)](https://aka.ms/awesome-copilot/install/prompt?url=vscode-insiders%3Achat-prompt%2Finstall%3Furl%3Dhttps%3A%2F%2Fraw.githubusercontent.com%2Fgithub%2Fawesome-copilot%2Fmain%2Fprompts%2Fcsharp-mstest.prompt.md) | Get best practices for MSTest unit testing, including data-driven tests | +| [MSTest Best Practices (MSTest 3.x/4.x)](../prompts/csharp-mstest.prompt.md)
[![Install in VS Code](https://img.shields.io/badge/VS_Code-Install-0098FF?style=flat-square&logo=visualstudiocode&logoColor=white)](https://aka.ms/awesome-copilot/install/prompt?url=vscode%3Achat-prompt%2Finstall%3Furl%3Dhttps%3A%2F%2Fraw.githubusercontent.com%2Fgithub%2Fawesome-copilot%2Fmain%2Fprompts%2Fcsharp-mstest.prompt.md)
[![Install in VS Code Insiders](https://img.shields.io/badge/VS_Code_Insiders-Install-24bfa5?style=flat-square&logo=visualstudiocode&logoColor=white)](https://aka.ms/awesome-copilot/install/prompt?url=vscode-insiders%3Achat-prompt%2Finstall%3Furl%3Dhttps%3A%2F%2Fraw.githubusercontent.com%2Fgithub%2Fawesome-copilot%2Fmain%2Fprompts%2Fcsharp-mstest.prompt.md) | Get best practices for MSTest 3.x/4.x unit testing, including modern assertion APIs and data-driven tests | | [Multi Stage Dockerfile](../prompts/multi-stage-dockerfile.prompt.md)
[![Install in VS Code](https://img.shields.io/badge/VS_Code-Install-0098FF?style=flat-square&logo=visualstudiocode&logoColor=white)](https://aka.ms/awesome-copilot/install/prompt?url=vscode%3Achat-prompt%2Finstall%3Furl%3Dhttps%3A%2F%2Fraw.githubusercontent.com%2Fgithub%2Fawesome-copilot%2Fmain%2Fprompts%2Fmulti-stage-dockerfile.prompt.md)
[![Install in VS Code Insiders](https://img.shields.io/badge/VS_Code_Insiders-Install-24bfa5?style=flat-square&logo=visualstudiocode&logoColor=white)](https://aka.ms/awesome-copilot/install/prompt?url=vscode-insiders%3Achat-prompt%2Finstall%3Furl%3Dhttps%3A%2F%2Fraw.githubusercontent.com%2Fgithub%2Fawesome-copilot%2Fmain%2Fprompts%2Fmulti-stage-dockerfile.prompt.md) | Create optimized multi-stage Dockerfiles for any language or framework | | [My Issues](../prompts/my-issues.prompt.md)
[![Install in VS Code](https://img.shields.io/badge/VS_Code-Install-0098FF?style=flat-square&logo=visualstudiocode&logoColor=white)](https://aka.ms/awesome-copilot/install/prompt?url=vscode%3Achat-prompt%2Finstall%3Furl%3Dhttps%3A%2F%2Fraw.githubusercontent.com%2Fgithub%2Fawesome-copilot%2Fmain%2Fprompts%2Fmy-issues.prompt.md)
[![Install in VS Code Insiders](https://img.shields.io/badge/VS_Code_Insiders-Install-24bfa5?style=flat-square&logo=visualstudiocode&logoColor=white)](https://aka.ms/awesome-copilot/install/prompt?url=vscode-insiders%3Achat-prompt%2Finstall%3Furl%3Dhttps%3A%2F%2Fraw.githubusercontent.com%2Fgithub%2Fawesome-copilot%2Fmain%2Fprompts%2Fmy-issues.prompt.md) | List my issues in the current repository | | [My Pull Requests](../prompts/my-pull-requests.prompt.md)
[![Install in VS Code](https://img.shields.io/badge/VS_Code-Install-0098FF?style=flat-square&logo=visualstudiocode&logoColor=white)](https://aka.ms/awesome-copilot/install/prompt?url=vscode%3Achat-prompt%2Finstall%3Furl%3Dhttps%3A%2F%2Fraw.githubusercontent.com%2Fgithub%2Fawesome-copilot%2Fmain%2Fprompts%2Fmy-pull-requests.prompt.md)
[![Install in VS Code Insiders](https://img.shields.io/badge/VS_Code_Insiders-Install-24bfa5?style=flat-square&logo=visualstudiocode&logoColor=white)](https://aka.ms/awesome-copilot/install/prompt?url=vscode-insiders%3Achat-prompt%2Finstall%3Furl%3Dhttps%3A%2F%2Fraw.githubusercontent.com%2Fgithub%2Fawesome-copilot%2Fmain%2Fprompts%2Fmy-pull-requests.prompt.md) | List my pull requests in the current repository | diff --git a/docs/README.skills.md b/docs/README.skills.md index f2be3d47..f743490c 100644 --- a/docs/README.skills.md +++ b/docs/README.skills.md @@ -31,6 +31,7 @@ Skills differ from other primitives by supporting bundled assets (scripts, code | [azure-static-web-apps](../skills/azure-static-web-apps/SKILL.md) | Helps create, configure, and deploy Azure Static Web Apps using the SWA CLI. Use when deploying static sites to Azure, setting up SWA local development, configuring staticwebapp.config.json, adding Azure Functions APIs to SWA, or setting up GitHub Actions CI/CD for Static Web Apps. | None | | [chrome-devtools](../skills/chrome-devtools/SKILL.md) | Expert-level browser automation, debugging, and performance analysis using Chrome DevTools MCP. Use for interacting with web pages, capturing screenshots, analyzing network traffic, and profiling performance. | None | | [copilot-sdk](../skills/copilot-sdk/SKILL.md) | Build agentic applications with GitHub Copilot SDK. Use when embedding AI agents in apps, creating custom tools, implementing streaming responses, managing sessions, connecting to MCP servers, or creating custom agents. Triggers on Copilot SDK, GitHub SDK, agentic app, embed Copilot, programmable agent, MCP server, custom agent. | None | +| [excalidraw-diagram-generator](../skills/excalidraw-diagram-generator/SKILL.md) | Generate Excalidraw diagrams from natural language descriptions. Use when asked to "create a diagram", "make a flowchart", "visualize a process", "draw a system architecture", "create a mind map", or "generate an Excalidraw file". Supports flowcharts, relationship diagrams, mind maps, and system architecture diagrams. Outputs .excalidraw JSON files that can be opened directly in Excalidraw. | `references/element-types.md`
`references/excalidraw-schema.md`
`scripts/.gitignore`
`scripts/README.md`
`scripts/add-arrow.py`
`scripts/add-icon-to-diagram.py`
`scripts/split-excalidraw-library.py`
`templates/business-flow-swimlane-template.excalidraw`
`templates/class-diagram-template.excalidraw`
`templates/data-flow-diagram-template.excalidraw`
`templates/er-diagram-template.excalidraw`
`templates/flowchart-template.excalidraw`
`templates/mindmap-template.excalidraw`
`templates/relationship-template.excalidraw`
`templates/sequence-diagram-template.excalidraw` | | [gh-cli](../skills/gh-cli/SKILL.md) | GitHub CLI (gh) comprehensive reference for repositories, issues, pull requests, Actions, projects, releases, gists, codespaces, organizations, extensions, and all GitHub operations from the command line. | None | | [git-commit](../skills/git-commit/SKILL.md) | Execute git commit with conventional commit message analysis, intelligent staging, and message generation. Use when user asks to commit changes, create a git commit, or mentions "/commit". Supports: (1) Auto-detecting type and scope from changes, (2) Generating conventional commit messages from diff, (3) Interactive commit with optional type/scope/description overrides, (4) Intelligent file staging for logical grouping | None | | [github-issues](../skills/github-issues/SKILL.md) | Create, update, and manage GitHub issues using MCP tools. Use this skill when users want to create bug reports, feature requests, or task issues, update existing issues, add labels/assignees/milestones, or manage issue workflows. Triggers on requests like "create an issue", "file a bug", "request a feature", "update issue X", or any GitHub issue management task. | `references/templates.md` | @@ -43,7 +44,9 @@ Skills differ from other primitives by supporting bundled assets (scripts, code | [microsoft-code-reference](../skills/microsoft-code-reference/SKILL.md) | Look up Microsoft API references, find working code samples, and verify SDK code is correct. Use when working with Azure SDKs, .NET libraries, or Microsoft APIs—to find the right method, check parameters, get working examples, or troubleshoot errors. Catches hallucinated methods, wrong signatures, and deprecated patterns by querying official docs. | None | | [microsoft-docs](../skills/microsoft-docs/SKILL.md) | Query official Microsoft documentation to understand concepts, find tutorials, and learn how services work. Use for Azure, .NET, Microsoft 365, Windows, Power Platform, and all Microsoft technologies. Get accurate, current information from learn.microsoft.com and other official Microsoft websites—architecture overviews, quickstarts, configuration guides, limits, and best practices. | None | | [nuget-manager](../skills/nuget-manager/SKILL.md) | Manage NuGet packages in .NET projects/solutions. Use this skill when adding, removing, or updating NuGet package versions. It enforces using `dotnet` CLI for package management and provides strict procedures for direct file edits only when updating versions. | None | +| [penpot-uiux-design](../skills/penpot-uiux-design/SKILL.md) | Comprehensive guide for creating professional UI/UX designs in Penpot using MCP tools. Use this skill when: (1) Creating new UI/UX designs for web, mobile, or desktop applications, (2) Building design systems with components and tokens, (3) Designing dashboards, forms, navigation, or landing pages, (4) Applying accessibility standards and best practices, (5) Following platform guidelines (iOS, Android, Material Design), (6) Reviewing or improving existing Penpot designs for usability. Triggers: "design a UI", "create interface", "build layout", "design dashboard", "create form", "design landing page", "make it accessible", "design system", "component library". | `references/accessibility.md`
`references/component-patterns.md`
`references/platform-guidelines.md`
`references/setup-troubleshooting.md` | | [plantuml-ascii](../skills/plantuml-ascii/SKILL.md) | Generate ASCII art diagrams using PlantUML text mode. Use when user asks to create ASCII diagrams, text-based diagrams, terminal-friendly diagrams, or mentions plantuml ascii, text diagram, ascii art diagram. Supports: Converting PlantUML diagrams to ASCII art, Creating sequence diagrams, class diagrams, flowcharts in ASCII format, Generating Unicode-enhanced ASCII art with -utxt flag | None | +| [powerbi-modeling](../skills/powerbi-modeling/SKILL.md) | Power BI semantic modeling assistant for building optimized data models. Use when working with Power BI semantic models, creating measures, designing star schemas, configuring relationships, implementing RLS, or optimizing model performance. Triggers on queries about DAX calculations, table relationships, dimension/fact table design, naming conventions, model documentation, cardinality, cross-filter direction, calculation groups, and data model best practices. Always connects to the active model first using power-bi-modeling MCP tools to understand the data structure before providing guidance. | `references/MEASURES-DAX.md`
`references/PERFORMANCE.md`
`references/RELATIONSHIPS.md`
`references/RLS.md`
`references/STAR-SCHEMA.md` | | [prd](../skills/prd/SKILL.md) | Generate high-quality Product Requirements Documents (PRDs) for software systems and AI-powered features. Includes executive summaries, user stories, technical specifications, and risk analysis. | None | | [refactor](../skills/refactor/SKILL.md) | Surgical code refactoring to improve maintainability without changing behavior. Covers extracting functions, renaming variables, breaking down god functions, improving type safety, eliminating code smells, and applying design patterns. Less drastic than repo-rebuilder; use for gradual improvements. | None | | [scoutqa-test](../skills/scoutqa-test/SKILL.md) | This skill should be used when the user asks to "test this website", "run exploratory testing", "check for accessibility issues", "verify the login flow works", "find bugs on this page", or requests automated QA testing. Triggers on web application testing scenarios including smoke tests, accessibility audits, e-commerce flows, and user flow validation using ScoutQA CLI. IMPORTANT: Use this skill proactively after implementing web application features to verify they work correctly - don't wait for the user to ask for testing. | None | diff --git a/eng/generate-website-data.mjs b/eng/generate-website-data.mjs index 4a9b895c..48bed4b4 100644 --- a/eng/generate-website-data.mjs +++ b/eng/generate-website-data.mjs @@ -24,6 +24,7 @@ import { parseSkillMetadata, parseYamlFile, } from "./yaml-parser.mjs"; +import { getGitFileDates } from "./utils/git-dates.mjs"; const __filename = fileURLToPath(import.meta.url); @@ -64,7 +65,7 @@ function extractTitle(filePath, frontmatter) { /** * Generate agents metadata */ -function generateAgentsData() { +function generateAgentsData(gitDates) { const agents = []; const files = fs .readdirSync(AGENTS_DIR) @@ -105,6 +106,7 @@ function generateAgentsData() { : [], path: relativePath, filename: file, + lastUpdated: gitDates.get(relativePath) || null, }); } @@ -123,7 +125,7 @@ function generateAgentsData() { /** * Generate prompts metadata */ -function generatePromptsData() { +function generatePromptsData(gitDates) { const prompts = []; const files = fs .readdirSync(PROMPTS_DIR) @@ -151,6 +153,7 @@ function generatePromptsData() { tools: tools, path: relativePath, filename: file, + lastUpdated: gitDates.get(relativePath) || null, }); } @@ -206,7 +209,7 @@ function extractExtensionFromPattern(pattern) { /** * Generate instructions metadata */ -function generateInstructionsData() { +function generateInstructionsData(gitDates) { const instructions = []; const files = fs .readdirSync(INSTRUCTIONS_DIR) @@ -253,6 +256,7 @@ function generateInstructionsData() { extensions: [...new Set(extensions)], path: relativePath, filename: file, + lastUpdated: gitDates.get(relativePath) || null, }); } @@ -316,7 +320,7 @@ function categorizeSkill(name, description) { /** * Generate skills metadata */ -function generateSkillsData() { +function generateSkillsData(gitDates) { const skills = []; if (!fs.existsSync(SKILLS_DIR)) { @@ -343,6 +347,9 @@ function generateSkillsData() { // Get all files in the skill folder recursively const files = getSkillFiles(skillPath, relativePath); + // Get last updated from SKILL.md file + const skillFilePath = `${relativePath}/SKILL.md`; + skills.push({ id: folder, name: metadata.name, @@ -356,8 +363,9 @@ function generateSkillsData() { assetCount: metadata.assets.length, category: category, path: relativePath, - skillFile: `${relativePath}/SKILL.md`, + skillFile: skillFilePath, files: files, + lastUpdated: gitDates.get(skillFilePath) || null, }); } } @@ -406,7 +414,7 @@ function getSkillFiles(skillPath, relativePath) { /** * Generate collections metadata */ -function generateCollectionsData() { +function generateCollectionsData(gitDates) { const collections = []; if (!fs.existsSync(COLLECTIONS_DIR)) { @@ -447,6 +455,7 @@ function generateCollectionsData() { })), path: relativePath, filename: file, + lastUpdated: gitDates.get(relativePath) || null, }); } } @@ -542,6 +551,7 @@ function generateSearchIndex( title: agent.title, description: agent.description, path: agent.path, + lastUpdated: agent.lastUpdated, searchText: `${agent.title} ${agent.description} ${agent.tools.join( " " )}`.toLowerCase(), @@ -555,6 +565,7 @@ function generateSearchIndex( title: prompt.title, description: prompt.description, path: prompt.path, + lastUpdated: prompt.lastUpdated, searchText: `${prompt.title} ${prompt.description}`.toLowerCase(), }); } @@ -566,6 +577,7 @@ function generateSearchIndex( title: instruction.title, description: instruction.description, path: instruction.path, + lastUpdated: instruction.lastUpdated, searchText: `${instruction.title} ${instruction.description} ${ instruction.applyTo || "" }`.toLowerCase(), @@ -579,6 +591,7 @@ function generateSearchIndex( title: skill.title, description: skill.description, path: skill.skillFile, + lastUpdated: skill.lastUpdated, searchText: `${skill.title} ${skill.description}`.toLowerCase(), }); } @@ -591,6 +604,7 @@ function generateSearchIndex( description: collection.description, path: collection.path, tags: collection.tags, + lastUpdated: collection.lastUpdated, searchText: `${collection.name} ${ collection.description } ${collection.tags.join(" ")}`.toLowerCase(), @@ -703,32 +717,40 @@ async function main() { ensureDataDir(); + // Load git dates for all resource files (single efficient git command) + console.log("Loading git history for last updated dates..."); + const gitDates = getGitFileDates( + ["agents/", "prompts/", "instructions/", "skills/", "collections/"], + ROOT_FOLDER + ); + console.log(`✓ Loaded dates for ${gitDates.size} files\n`); + // Generate all data - const agentsData = generateAgentsData(); + const agentsData = generateAgentsData(gitDates); const agents = agentsData.items; console.log( `✓ Generated ${agents.length} agents (${agentsData.filters.models.length} models, ${agentsData.filters.tools.length} tools)` ); - const promptsData = generatePromptsData(); + const promptsData = generatePromptsData(gitDates); const prompts = promptsData.items; console.log( `✓ Generated ${prompts.length} prompts (${promptsData.filters.tools.length} tools)` ); - const instructionsData = generateInstructionsData(); + const instructionsData = generateInstructionsData(gitDates); const instructions = instructionsData.items; console.log( `✓ Generated ${instructions.length} instructions (${instructionsData.filters.extensions.length} extensions)` ); - const skillsData = generateSkillsData(); + const skillsData = generateSkillsData(gitDates); const skills = skillsData.items; console.log( `✓ Generated ${skills.length} skills (${skillsData.filters.categories.length} categories)` ); - const collectionsData = generateCollectionsData(); + const collectionsData = generateCollectionsData(gitDates); const collections = collectionsData.items; console.log( `✓ Generated ${collections.length} collections (${collectionsData.filters.tags.length} tags)` diff --git a/eng/utils/git-dates.mjs b/eng/utils/git-dates.mjs new file mode 100644 index 00000000..2f359699 --- /dev/null +++ b/eng/utils/git-dates.mjs @@ -0,0 +1,103 @@ +#!/usr/bin/env node + +/** + * Utility to extract last modification dates from git history. + * Uses a single git log command for efficiency. + */ + +import { execSync } from "child_process"; +import path from "path"; + +/** + * Get the last modification date for all tracked files in specified directories. + * Returns a Map of file path -> ISO date string. + * + * @param {string[]} directories - Array of directory paths to scan + * @param {string} rootDir - Root directory for relative paths + * @returns {Map} Map of relative file path to ISO date string + */ +export function getGitFileDates(directories, rootDir) { + const fileDates = new Map(); + + try { + // Get git log with file names for all specified directories + // Format: ISO date, then file names that were modified in that commit + const gitArgs = [ + "--no-pager", + "log", + "--format=%aI", // Author date in ISO 8601 format + "--name-only", + "--diff-filter=ACMR", // Added, Copied, Modified, Renamed + "--", + ...directories, + ]; + + const output = execSync(`git ${gitArgs.join(" ")}`, { + encoding: "utf8", + cwd: rootDir, + stdio: ["pipe", "pipe", "pipe"], + }); + + // Parse the output: alternating date lines and file name lines + // Format is: + // 2026-01-15T10:30:00+00:00 + // + // file1.md + // file2.md + // + // 2026-01-14T09:00:00+00:00 + // ... + + let currentDate = null; + const lines = output.split("\n"); + + for (const line of lines) { + const trimmed = line.trim(); + + if (!trimmed) { + continue; + } + + // Check if this is a date line (ISO 8601 format) + if (/^\d{4}-\d{2}-\d{2}T/.test(trimmed)) { + currentDate = trimmed; + } else if (currentDate && trimmed) { + // This is a file path - only set if we haven't seen this file yet + // (first occurrence is the most recent modification) + if (!fileDates.has(trimmed)) { + fileDates.set(trimmed, currentDate); + } + } + } + } catch (error) { + // Git command failed - might not be a git repo or no history + console.warn("Warning: Could not get git dates:", error.message); + } + + return fileDates; +} + +/** + * Get the last modification date for a single file. + * + * @param {string} filePath - Path to the file (relative to git root) + * @param {string} rootDir - Root directory + * @returns {string|null} ISO date string or null if not found + */ +export function getGitFileDate(filePath, rootDir) { + try { + const output = execSync( + `git --no-pager log -1 --format="%aI" -- "${filePath}"`, + { + encoding: "utf8", + cwd: rootDir, + stdio: ["pipe", "pipe", "pipe"], + } + ); + + const date = output.trim(); + return date || null; + } catch (error) { + return null; + } +} diff --git a/instructions/copilot-sdk-python.instructions.md b/instructions/copilot-sdk-python.instructions.md index 8bc46757..ae8a23d5 100644 --- a/instructions/copilot-sdk-python.instructions.md +++ b/instructions/copilot-sdk-python.instructions.md @@ -18,11 +18,11 @@ name: "GitHub Copilot SDK Python Instructions" Always install via pip: ```bash -pip install copilot-sdk +pip install github-copilot-sdk # or with poetry -poetry add copilot-sdk +poetry add github-copilot-sdk # or with uv -uv add copilot-sdk +uv add github-copilot-sdk ``` ## Client Initialization diff --git a/instructions/dotnet-upgrade.instructions.md b/instructions/dotnet-upgrade.instructions.md index 7c8b733b..f2a146d9 100644 --- a/instructions/dotnet-upgrade.instructions.md +++ b/instructions/dotnet-upgrade.instructions.md @@ -23,9 +23,9 @@ Follow the steps **sequentially** and **do not attempt to upgrade all projects a - Note the current target and SDK. 2. **Select Target Version** - - **.NET (Core/Modern)**: Upgrade to the latest LTS (e.g., `net8.0`). - - **.NET Standard**: Prefer migrating to **.NET 6+** if possible. If staying, target `netstandard2.1`. - - **.NET Framework**: Upgrade to at least **4.8**, or migrate to .NET 6+ if feasible. + - **.NET (Core/Modern)**: Upgrade to the latest LTS (e.g., `net10.0`). + - **.NET Standard**: Prefer migrating to **.NET 8+** if possible. If staying, target `netstandard2.1`. + - **.NET Framework**: Upgrade to at least **4.8**, or migrate to .NET 8+ if feasible. 3. **Review Release Notes & Breaking Changes** - [.NET Core/.NET Upgrade Docs](https://learn.microsoft.com/dotnet/core/whats-new/) @@ -76,7 +76,7 @@ For each project: ``` 2. Check for: - - `TargetFramework` → Change to the desired version (e.g., `net8.0`). + - `TargetFramework` → Change to the desired version (e.g., `net10.0`). - `PackageReference` → Verify if each NuGet package supports the new framework. - Run: ```bash @@ -148,7 +148,7 @@ BlobServiceClient client = new BlobServiceClient(connectionString); - Common issues: - Deprecated APIs → Replace with supported alternatives. - Package incompatibility → Find updated NuGet or migrate to Microsoft-supported library. - - Configuration differences (e.g., `Startup.cs` → `Program.cs` in .NET 6+). + - Configuration differences (e.g., `Startup.cs` → `Program.cs` in .NET 8+). --- @@ -239,9 +239,9 @@ Use this table as a sample to track the progress of the upgrade across all proje | Project Name | Target Framework | Dependencies Updated | Builds Successfully | Tests Passing | Deployment Verified | Notes | |--------------|------------------|-----------------------|---------------------|---------------|---------------------|-------| -| Project A | ☐ net8.0 | ☐ | ☐ | ☐ | ☐ | | -| Project B | ☐ net8.0 | ☐ | ☐ | ☐ | ☐ | | -| Project C | ☐ net8.0 | ☐ | ☐ | ☐ | ☐ | | +| Project A | ☐ net10.0 | ☐ | ☐ | ☐ | ☐ | | +| Project B | ☐ net10.0 | ☐ | ☐ | ☐ | ☐ | | +| Project C | ☐ net10.0 | ☐ | ☐ | ☐ | ☐ | | > ✅ Mark each column as you complete the step for every project. @@ -272,7 +272,7 @@ For organizations with multiple repositories: ## 🔑 Notes & Best Practices - **Prefer Migration to Modern .NET** - If on .NET Framework or .NET Standard, evaluate moving to .NET 6/8 for long-term support. + If on .NET Framework or .NET Standard, evaluate moving to .NET 8/10 for long-term support. - **Automate Tests Early** CI/CD should block merges if tests fail. - **Incremental Upgrades** diff --git a/prompts/conventional-commit.prompt.md b/prompts/conventional-commit.prompt.md index c517d438..01cf2b5a 100644 --- a/prompts/conventional-commit.prompt.md +++ b/prompts/conventional-commit.prompt.md @@ -1,6 +1,6 @@ --- description: 'Prompt and workflow for generating conventional commit messages using a structured XML format. Guides users to create standardized, descriptive commit messages in line with the Conventional Commits specification, including instructions, examples, and validation.' -tools: ['runCommands/runInTerminal', 'runCommands/getTerminalOutput'] +tools: ['execute/runInTerminal', 'execute/getTerminalOutput'] --- ### Instructions diff --git a/prompts/csharp-mstest.prompt.md b/prompts/csharp-mstest.prompt.md index ae4fc938..9a27bda8 100644 --- a/prompts/csharp-mstest.prompt.md +++ b/prompts/csharp-mstest.prompt.md @@ -1,67 +1,479 @@ --- agent: 'agent' tools: ['changes', 'search/codebase', 'edit/editFiles', 'problems', 'search'] -description: 'Get best practices for MSTest unit testing, including data-driven tests' +description: 'Get best practices for MSTest 3.x/4.x unit testing, including modern assertion APIs and data-driven tests' --- -# MSTest Best Practices +# MSTest Best Practices (MSTest 3.x/4.x) -Your goal is to help me write effective unit tests with MSTest, covering both standard and data-driven testing approaches. +Your goal is to help me write effective unit tests with modern MSTest, using current APIs and best practices. ## Project Setup - Use a separate test project with naming convention `[ProjectName].Tests` -- Reference MSTest package -- Create test classes that match the classes being tested (e.g., `CalculatorTests` for `Calculator`) -- Use .NET SDK test commands: `dotnet test` for running tests +- Reference MSTest 3.x+ NuGet packages (includes analyzers) +- Consider using MSTest.Sdk for simplified project setup +- Run tests with `dotnet test` -## Test Structure +## Test Class Structure - Use `[TestClass]` attribute for test classes -- Use `[TestMethod]` attribute for test methods -- Follow the Arrange-Act-Assert (AAA) pattern -- Name tests using the pattern `MethodName_Scenario_ExpectedBehavior` -- Use `[TestInitialize]` and `[TestCleanup]` for per-test setup and teardown -- Use `[ClassInitialize]` and `[ClassCleanup]` for per-class setup and teardown -- Use `[AssemblyInitialize]` and `[AssemblyCleanup]` for assembly-level setup and teardown +- **Seal test classes by default** for performance and design clarity +- Use `[TestMethod]` for test methods (prefer over `[DataTestMethod]`) +- Follow Arrange-Act-Assert (AAA) pattern +- Name tests using pattern `MethodName_Scenario_ExpectedBehavior` -## Standard Tests +```csharp +[TestClass] +public sealed class CalculatorTests +{ + [TestMethod] + public void Add_TwoPositiveNumbers_ReturnsSum() + { + // Arrange + var calculator = new Calculator(); -- Keep tests focused on a single behavior -- Avoid testing multiple behaviors in one test method -- Use clear assertions that express intent -- Include only the assertions needed to verify the test case -- Make tests independent and idempotent (can run in any order) -- Avoid test interdependencies + // Act + var result = calculator.Add(2, 3); + + // Assert + Assert.AreEqual(5, result); + } +} +``` + +## Test Lifecycle + +- **Prefer constructors over `[TestInitialize]`** - enables `readonly` fields and follows standard C# patterns +- Use `[TestCleanup]` for cleanup that must run even if test fails +- Combine constructor with async `[TestInitialize]` when async setup is needed + +```csharp +[TestClass] +public sealed class ServiceTests +{ + private readonly MyService _service; // readonly enabled by constructor + + public ServiceTests() + { + _service = new MyService(); + } + + [TestInitialize] + public async Task InitAsync() + { + // Use for async initialization only + await _service.WarmupAsync(); + } + + [TestCleanup] + public void Cleanup() => _service.Reset(); +} +``` + +### Execution Order + +1. **Assembly Initialization** - `[AssemblyInitialize]` (once per test assembly) +2. **Class Initialization** - `[ClassInitialize]` (once per test class) +3. **Test Initialization** (for every test method): + 1. Constructor + 2. Set `TestContext` property + 3. `[TestInitialize]` +4. **Test Execution** - test method runs +5. **Test Cleanup** (for every test method): + 1. `[TestCleanup]` + 2. `DisposeAsync` (if implemented) + 3. `Dispose` (if implemented) +6. **Class Cleanup** - `[ClassCleanup]` (once per test class) +7. **Assembly Cleanup** - `[AssemblyCleanup]` (once per test assembly) + +## Modern Assertion APIs + +MSTest provides three assertion classes: `Assert`, `StringAssert`, and `CollectionAssert`. + +### Assert Class - Core Assertions + +```csharp +// Equality +Assert.AreEqual(expected, actual); +Assert.AreNotEqual(notExpected, actual); +Assert.AreSame(expectedObject, actualObject); // Reference equality +Assert.AreNotSame(notExpectedObject, actualObject); + +// Null checks +Assert.IsNull(value); +Assert.IsNotNull(value); + +// Boolean +Assert.IsTrue(condition); +Assert.IsFalse(condition); + +// Fail/Inconclusive +Assert.Fail("Test failed due to..."); +Assert.Inconclusive("Test cannot be completed because..."); +``` + +### Exception Testing (Prefer over `[ExpectedException]`) + +```csharp +// Assert.Throws - matches TException or derived types +var ex = Assert.Throws(() => Method(null)); +Assert.AreEqual("Value cannot be null.", ex.Message); + +// Assert.ThrowsExactly - matches exact type only +var ex = Assert.ThrowsExactly(() => Method()); + +// Async versions +var ex = await Assert.ThrowsAsync(async () => await client.GetAsync(url)); +var ex = await Assert.ThrowsExactlyAsync(async () => await Method()); +``` + +### Collection Assertions (Assert class) + +```csharp +Assert.Contains(expectedItem, collection); +Assert.DoesNotContain(unexpectedItem, collection); +Assert.ContainsSingle(collection); // exactly one element +Assert.HasCount(5, collection); +Assert.IsEmpty(collection); +Assert.IsNotEmpty(collection); +``` + +### String Assertions (Assert class) + +```csharp +Assert.Contains("expected", actualString); +Assert.StartsWith("prefix", actualString); +Assert.EndsWith("suffix", actualString); +Assert.DoesNotStartWith("prefix", actualString); +Assert.DoesNotEndWith("suffix", actualString); +Assert.MatchesRegex(@"\d{3}-\d{4}", phoneNumber); +Assert.DoesNotMatchRegex(@"\d+", textOnly); +``` + +### Comparison Assertions + +```csharp +Assert.IsGreaterThan(lowerBound, actual); +Assert.IsGreaterThanOrEqualTo(lowerBound, actual); +Assert.IsLessThan(upperBound, actual); +Assert.IsLessThanOrEqualTo(upperBound, actual); +Assert.IsInRange(actual, low, high); +Assert.IsPositive(number); +Assert.IsNegative(number); +``` + +### Type Assertions + +```csharp +// MSTest 3.x - uses out parameter +Assert.IsInstanceOfType(obj, out var typed); +typed.DoSomething(); + +// MSTest 4.x - returns typed result directly +var typed = Assert.IsInstanceOfType(obj); +typed.DoSomething(); + +Assert.IsNotInstanceOfType(obj); +``` + +### Assert.That (MSTest 4.0+) + +```csharp +Assert.That(result.Count > 0); // Auto-captures expression in failure message +``` + +### StringAssert Class + +> **Note:** Prefer `Assert` class equivalents when available (e.g., `Assert.Contains("expected", actual)` over `StringAssert.Contains(actual, "expected")`). + +```csharp +StringAssert.Contains(actualString, "expected"); +StringAssert.StartsWith(actualString, "prefix"); +StringAssert.EndsWith(actualString, "suffix"); +StringAssert.Matches(actualString, new Regex(@"\d{3}-\d{4}")); +StringAssert.DoesNotMatch(actualString, new Regex(@"\d+")); +``` + +### CollectionAssert Class + +> **Note:** Prefer `Assert` class equivalents when available (e.g., `Assert.Contains`). + +```csharp +// Containment +CollectionAssert.Contains(collection, expectedItem); +CollectionAssert.DoesNotContain(collection, unexpectedItem); + +// Equality (same elements, same order) +CollectionAssert.AreEqual(expectedCollection, actualCollection); +CollectionAssert.AreNotEqual(unexpectedCollection, actualCollection); + +// Equivalence (same elements, any order) +CollectionAssert.AreEquivalent(expectedCollection, actualCollection); +CollectionAssert.AreNotEquivalent(unexpectedCollection, actualCollection); + +// Subset checks +CollectionAssert.IsSubsetOf(subset, superset); +CollectionAssert.IsNotSubsetOf(notSubset, collection); + +// Element validation +CollectionAssert.AllItemsAreInstancesOfType(collection, typeof(MyClass)); +CollectionAssert.AllItemsAreNotNull(collection); +CollectionAssert.AllItemsAreUnique(collection); +``` ## Data-Driven Tests -- Use `[TestMethod]` combined with data source attributes -- Use `[DataRow]` for inline test data -- Use `[DynamicData]` for programmatically generated test data -- Use `[TestProperty]` to add metadata to tests -- Use meaningful parameter names in data-driven tests +### DataRow -## Assertions +```csharp +[TestMethod] +[DataRow(1, 2, 3)] +[DataRow(0, 0, 0, DisplayName = "Zeros")] +[DataRow(-1, 1, 0, IgnoreMessage = "Known issue #123")] // MSTest 3.8+ +public void Add_ReturnsSum(int a, int b, int expected) +{ + Assert.AreEqual(expected, Calculator.Add(a, b)); +} +``` -- Use `Assert.AreEqual` for value equality -- Use `Assert.AreSame` for reference equality -- Use `Assert.IsTrue`/`Assert.IsFalse` for boolean conditions -- Use `CollectionAssert` for collection comparisons -- Use `StringAssert` for string-specific assertions -- Use `Assert.Throws` to test exceptions -- Ensure assertions are simple in nature and have a message provided for clarity on failure +### DynamicData -## Mocking and Isolation +The data source can return any of the following types: -- Consider using Moq or NSubstitute alongside MSTest -- Mock dependencies to isolate units under test -- Use interfaces to facilitate mocking -- Consider using a DI container for complex test setups +- `IEnumerable<(T1, T2, ...)>` (ValueTuple) - **preferred**, provides type safety (MSTest 3.7+) +- `IEnumerable>` - provides type safety +- `IEnumerable` - provides type safety plus control over test metadata (display name, categories) +- `IEnumerable` - **least preferred**, no type safety + +> **Note:** When creating new test data methods, prefer `ValueTuple` or `TestDataRow` over `IEnumerable`. The `object[]` approach provides no compile-time type checking and can lead to runtime errors from type mismatches. + +```csharp +[TestMethod] +[DynamicData(nameof(TestData))] +public void DynamicTest(int a, int b, int expected) +{ + Assert.AreEqual(expected, Calculator.Add(a, b)); +} + +// ValueTuple - preferred (MSTest 3.7+) +public static IEnumerable<(int a, int b, int expected)> TestData => +[ + (1, 2, 3), + (0, 0, 0), +]; + +// TestDataRow - when you need custom display names or metadata +public static IEnumerable> TestDataWithMetadata => +[ + new((1, 2, 3)) { DisplayName = "Positive numbers" }, + new((0, 0, 0)) { DisplayName = "Zeros" }, + new((-1, 1, 0)) { DisplayName = "Mixed signs", IgnoreMessage = "Known issue #123" }, +]; + +// IEnumerable - avoid for new code (no type safety) +public static IEnumerable LegacyTestData => +[ + [1, 2, 3], + [0, 0, 0], +]; +``` + +## TestContext + +The `TestContext` class provides test run information, cancellation support, and output methods. +See [TestContext documentation](https://learn.microsoft.com/dotnet/core/testing/unit-testing-mstest-writing-tests-testcontext) for complete reference. + +### Accessing TestContext + +```csharp +// Property (MSTest suppresses CS8618 - don't use nullable or = null!) +public TestContext TestContext { get; set; } + +// Constructor injection (MSTest 3.6+) - preferred for immutability +[TestClass] +public sealed class MyTests +{ + private readonly TestContext _testContext; + + public MyTests(TestContext testContext) + { + _testContext = testContext; + } +} + +// Static methods receive it as parameter +[ClassInitialize] +public static void ClassInit(TestContext context) { } + +// Optional for cleanup methods (MSTest 3.6+) +[ClassCleanup] +public static void ClassCleanup(TestContext context) { } + +[AssemblyCleanup] +public static void AssemblyCleanup(TestContext context) { } +``` + +### Cancellation Token + +Always use `TestContext.CancellationToken` for cooperative cancellation with `[Timeout]`: + +```csharp +[TestMethod] +[Timeout(5000)] +public async Task LongRunningTest() +{ + await _httpClient.GetAsync(url, TestContext.CancellationToken); +} +``` + +### Test Run Properties + +```csharp +TestContext.TestName // Current test method name +TestContext.TestDisplayName // Display name (3.7+) +TestContext.CurrentTestOutcome // Pass/Fail/InProgress +TestContext.TestData // Parameterized test data (3.7+, in TestInitialize/Cleanup) +TestContext.TestException // Exception if test failed (3.7+, in TestCleanup) +TestContext.DeploymentDirectory // Directory with deployment items +``` + +### Output and Result Files + +```csharp +// Write to test output (useful for debugging) +TestContext.WriteLine("Processing item {0}", itemId); + +// Attach files to test results (logs, screenshots) +TestContext.AddResultFile(screenshotPath); + +// Store/retrieve data across test methods +TestContext.Properties["SharedKey"] = computedValue; +``` + +## Advanced Features + +### Retry for Flaky Tests (MSTest 3.9+) + +```csharp +[TestMethod] +[Retry(3)] +public void FlakyTest() { } +``` + +### Conditional Execution (MSTest 3.10+) + +Skip or run tests based on OS or CI environment: + +```csharp +// OS-specific tests +[TestMethod] +[OSCondition(OperatingSystems.Windows)] +public void WindowsOnlyTest() { } + +[TestMethod] +[OSCondition(OperatingSystems.Linux | OperatingSystems.MacOS)] +public void UnixOnlyTest() { } + +[TestMethod] +[OSCondition(ConditionMode.Exclude, OperatingSystems.Windows)] +public void SkipOnWindowsTest() { } + +// CI environment tests +[TestMethod] +[CICondition] // Runs only in CI (default: ConditionMode.Include) +public void CIOnlyTest() { } + +[TestMethod] +[CICondition(ConditionMode.Exclude)] // Skips in CI, runs locally +public void LocalOnlyTest() { } +``` + +### Parallelization + +```csharp +// Assembly level +[assembly: Parallelize(Workers = 4, Scope = ExecutionScope.MethodLevel)] + +// Disable for specific class +[TestClass] +[DoNotParallelize] +public sealed class SequentialTests { } +``` + +### Work Item Traceability (MSTest 3.8+) + +Link tests to work items for traceability in test reports: + +```csharp +// Azure DevOps work items +[TestMethod] +[WorkItem(12345)] // Links to work item #12345 +public void Feature_Scenario_ExpectedBehavior() { } + +// Multiple work items +[TestMethod] +[WorkItem(12345)] +[WorkItem(67890)] +public void Feature_CoversMultipleRequirements() { } + +// GitHub issues (MSTest 3.8+) +[TestMethod] +[GitHubWorkItem("https://github.com/owner/repo/issues/42")] +public void BugFix_Issue42_IsResolved() { } +``` + +Work item associations appear in test results and can be used for: +- Tracing test coverage to requirements +- Linking bug fixes to regression tests +- Generating traceability reports in CI/CD pipelines + +## Common Mistakes to Avoid + +```csharp +// ❌ Wrong argument order +Assert.AreEqual(actual, expected); +// ✅ Correct +Assert.AreEqual(expected, actual); + +// ❌ Using ExpectedException (obsolete) +[ExpectedException(typeof(ArgumentException))] +// ✅ Use Assert.Throws +Assert.Throws(() => Method()); + +// ❌ Using LINQ Single() - unclear exception +var item = items.Single(); +// ✅ Use ContainsSingle - better failure message +var item = Assert.ContainsSingle(items); + +// ❌ Hard cast - unclear exception +var handler = (MyHandler)result; +// ✅ Type assertion - shows actual type on failure +var handler = Assert.IsInstanceOfType(result); + +// ❌ Ignoring cancellation token +await client.GetAsync(url, CancellationToken.None); +// ✅ Flow test cancellation +await client.GetAsync(url, TestContext.CancellationToken); + +// ❌ Making TestContext nullable - leads to unnecessary null checks +public TestContext? TestContext { get; set; } +// ❌ Using null! - MSTest already suppresses CS8618 for this property +public TestContext TestContext { get; set; } = null!; +// ✅ Declare without nullable or initializer - MSTest handles the warning +public TestContext TestContext { get; set; } +``` ## Test Organization - Group tests by feature or component -- Use test categories with `[TestCategory("Category")]` -- Use test priorities with `[Priority(1)]` for critical tests -- Use `[Owner("DeveloperName")]` to indicate ownership +- Use `[TestCategory("Category")]` for filtering +- Use `[TestProperty("Name", "Value")]` for custom metadata (e.g., `[TestProperty("Bug", "12345")]`) +- Use `[Priority(1)]` for critical tests +- Enable relevant MSTest analyzers (MSTEST0020 for constructor preference) + +## Mocking and Isolation + +- Use Moq or NSubstitute for mocking dependencies +- Use interfaces to facilitate mocking +- Mock dependencies to isolate units under test diff --git a/skills/excalidraw-diagram-generator/SKILL.md b/skills/excalidraw-diagram-generator/SKILL.md new file mode 100644 index 00000000..e33fd902 --- /dev/null +++ b/skills/excalidraw-diagram-generator/SKILL.md @@ -0,0 +1,613 @@ +--- +name: excalidraw-diagram-generator +description: 'Generate Excalidraw diagrams from natural language descriptions. Use when asked to "create a diagram", "make a flowchart", "visualize a process", "draw a system architecture", "create a mind map", or "generate an Excalidraw file". Supports flowcharts, relationship diagrams, mind maps, and system architecture diagrams. Outputs .excalidraw JSON files that can be opened directly in Excalidraw.' +--- + +# Excalidraw Diagram Generator + +A skill for generating Excalidraw-format diagrams from natural language descriptions. This skill helps create visual representations of processes, systems, relationships, and ideas without manual drawing. + +## When to Use This Skill + +Use this skill when users request: + +- "Create a diagram showing..." +- "Make a flowchart for..." +- "Visualize the process of..." +- "Draw the system architecture of..." +- "Generate a mind map about..." +- "Create an Excalidraw file for..." +- "Show the relationship between..." +- "Diagram the workflow of..." + +**Supported diagram types:** +- 📊 **Flowcharts**: Sequential processes, workflows, decision trees +- 🔗 **Relationship Diagrams**: Entity relationships, system components, dependencies +- 🧠 **Mind Maps**: Concept hierarchies, brainstorming results, topic organization +- 🏗️ **Architecture Diagrams**: System design, module interactions, data flow +- 📈 **Data Flow Diagrams (DFD)**: Data flow visualization, data transformation processes +- 🏊 **Business Flow (Swimlane)**: Cross-functional workflows, actor-based process flows +- 📦 **Class Diagrams**: Object-oriented design, class structures and relationships +- 🔄 **Sequence Diagrams**: Object interactions over time, message flows +- 🗃️ **ER Diagrams**: Database entity relationships, data models + +## Prerequisites + +- Clear description of what should be visualized +- Identification of key entities, steps, or concepts +- Understanding of relationships or flow between elements + +## Step-by-Step Workflow + +### Step 1: Understand the Request + +Analyze the user's description to determine: +1. **Diagram type** (flowchart, relationship, mind map, architecture) +2. **Key elements** (entities, steps, concepts) +3. **Relationships** (flow, connections, hierarchy) +4. **Complexity** (number of elements) + +### Step 2: Choose the Appropriate Diagram Type + +| User Intent | Diagram Type | Example Keywords | +|-------------|--------------|------------------| +| Process flow, steps, procedures | **Flowchart** | "workflow", "process", "steps", "procedure" | +| Connections, dependencies, associations | **Relationship Diagram** | "relationship", "connections", "dependencies", "structure" | +| Concept hierarchy, brainstorming | **Mind Map** | "mind map", "concepts", "ideas", "breakdown" | +| System design, components | **Architecture Diagram** | "architecture", "system", "components", "modules" | +| Data flow, transformation processes | **Data Flow Diagram (DFD)** | "data flow", "data processing", "data transformation" | +| Cross-functional processes, actor responsibilities | **Business Flow (Swimlane)** | "business process", "swimlane", "actors", "responsibilities" | +| Object-oriented design, class structures | **Class Diagram** | "class", "inheritance", "OOP", "object model" | +| Interaction sequences, message flows | **Sequence Diagram** | "sequence", "interaction", "messages", "timeline" | +| Database design, entity relationships | **ER Diagram** | "database", "entity", "relationship", "data model" | + +### Step 3: Extract Structured Information + +**For Flowcharts:** +- List of sequential steps +- Decision points (if any) +- Start and end points + +**For Relationship Diagrams:** +- Entities/nodes (name + optional description) +- Relationships between entities (from → to, with label) + +**For Mind Maps:** +- Central topic +- Main branches (3-6 recommended) +- Sub-topics for each branch (optional) + +**For Data Flow Diagrams (DFD):** +- Data sources and destinations (external entities) +- Processes (data transformations) +- Data stores (databases, files) +- Data flows (arrows showing data movement from left-to-right or from top-left to bottom-right) +- **Important**: Do not represent process order, only data flow + +**For Business Flow (Swimlane):** +- Actors/roles (departments, systems, people) - displayed as header columns +- Process lanes (vertical lanes under each actor) +- Process boxes (activities within each lane) +- Flow arrows (connecting process boxes, including cross-lane handoffs) + +**For Class Diagrams:** +- Classes with names +- Attributes with visibility (+, -, #) +- Methods with visibility and parameters +- Relationships: inheritance (solid line + white triangle), implementation (dashed line + white triangle), association (solid line), dependency (dashed line), aggregation (solid line + white diamond), composition (solid line + filled diamond) +- Multiplicity notations (1, 0..1, 1..*, *) + +**For Sequence Diagrams:** +- Objects/actors (arranged horizontally at top) +- Lifelines (vertical lines from each object) +- Messages (horizontal arrows between lifelines) +- Synchronous messages (solid arrow), asynchronous messages (dashed arrow) +- Return values (dashed arrows) +- Activation boxes (rectangles on lifelines during execution) +- Time flows from top to bottom + +**For ER Diagrams:** +- Entities (rectangles with entity names) +- Attributes (listed inside entities) +- Primary keys (underlined or marked with PK) +- Foreign keys (marked with FK) +- Relationships (lines connecting entities) +- Cardinality: 1:1 (one-to-one), 1:N (one-to-many), N:M (many-to-many) +- Junction/associative entities for many-to-many relationships (dashed rectangles) + +### Step 4: Generate the Excalidraw JSON + +Create the `.excalidraw` file with appropriate elements: + +**Available element types:** +- `rectangle`: Boxes for entities, steps, concepts +- `ellipse`: Alternative shapes for emphasis +- `diamond`: Decision points +- `arrow`: Directional connections +- `text`: Labels and annotations + +**Key properties to set:** +- **Position**: `x`, `y` coordinates +- **Size**: `width`, `height` +- **Style**: `strokeColor`, `backgroundColor`, `fillStyle` +- **Font**: `fontFamily: 5` (Excalifont - **required for all text elements**) +- **Text**: Embedded text for labels +- **Connections**: `points` array for arrows + +**Important**: All text elements must use `fontFamily: 5` (Excalifont) for consistent visual appearance. + +### Step 5: Format the Output + +Structure the complete Excalidraw file: + +```json +{ + "type": "excalidraw", + "version": 2, + "source": "https://excalidraw.com", + "elements": [ + // Array of diagram elements + ], + "appState": { + "viewBackgroundColor": "#ffffff", + "gridSize": 20 + }, + "files": {} +} +``` + +### Step 6: Save and Provide Instructions + +1. Save as `.excalidraw` +2. Inform user how to open: + - Visit https://excalidraw.com + - Click "Open" or drag-and-drop the file + - Or use Excalidraw VS Code extension + +## Best Practices + +### Element Count Guidelines + +| Diagram Type | Recommended Count | Maximum | +|--------------|-------------------|---------| +| Flowchart steps | 3-10 | 15 | +| Relationship entities | 3-8 | 12 | +| Mind map branches | 4-6 | 8 | +| Mind map sub-topics per branch | 2-4 | 6 | + +### Layout Tips + +1. **Start positions**: Center important elements, use consistent spacing +2. **Spacing**: + - Horizontal gap: 200-300px between elements + - Vertical gap: 100-150px between rows +3. **Colors**: Use consistent color scheme + - Primary elements: Light blue (`#a5d8ff`) + - Secondary elements: Light green (`#b2f2bb`) + - Important/Central: Yellow (`#ffd43b`) + - Alerts/Warnings: Light red (`#ffc9c9`) +4. **Text sizing**: 16-24px for readability +5. **Font**: Always use `fontFamily: 5` (Excalifont) for all text elements +6. **Arrow style**: Use straight arrows for simple flows, curved for complex relationships + +### Complexity Management + +**If user request has too many elements:** +- Suggest breaking into multiple diagrams +- Focus on main elements first +- Offer to create detailed sub-diagrams + +**Example response:** +``` +"Your request includes 15 components. For clarity, I recommend: +1. High-level architecture diagram (6 main components) +2. Detailed diagram for each subsystem + +Would you like me to start with the high-level view?" +``` + +## Example Prompts and Responses + +### Example 1: Simple Flowchart + +**User:** "Create a flowchart for user registration" + +**Agent generates:** +1. Extract steps: "Enter email" → "Verify email" → "Set password" → "Complete" +2. Create flowchart with 4 rectangles + 3 arrows +3. Save as `user-registration-flow.excalidraw` + +### Example 2: Relationship Diagram + +**User:** "Diagram the relationship between User, Post, and Comment entities" + +**Agent generates:** +1. Entities: User, Post, Comment +2. Relationships: User → Post ("creates"), User → Comment ("writes"), Post → Comment ("contains") +3. Save as `user-content-relationships.excalidraw` + +### Example 3: Mind Map + +**User:** "Mind map about machine learning concepts" + +**Agent generates:** +1. Center: "Machine Learning" +2. Branches: Supervised Learning, Unsupervised Learning, Reinforcement Learning, Deep Learning +3. Sub-topics under each branch +4. Save as `machine-learning-mindmap.excalidraw` + +## Troubleshooting + +| Issue | Solution | +|-------|----------| +| Elements overlap | Increase spacing between coordinates | +| Text doesn't fit in boxes | Increase box width or reduce font size | +| Too many elements | Break into multiple diagrams | +| Unclear layout | Use grid layout (rows/columns) or radial layout (mind maps) | +| Colors inconsistent | Define color palette upfront based on element types | + +## Advanced Techniques + +### Grid Layout (for Relationship Diagrams) +```javascript +const columns = Math.ceil(Math.sqrt(entityCount)); +const x = startX + (index % columns) * horizontalGap; +const y = startY + Math.floor(index / columns) * verticalGap; +``` + +### Radial Layout (for Mind Maps) +```javascript +const angle = (2 * Math.PI * index) / branchCount; +const x = centerX + radius * Math.cos(angle); +const y = centerY + radius * Math.sin(angle); +``` + +### Auto-generated IDs +Use timestamp + random string for unique IDs: +```javascript +const id = Date.now().toString(36) + Math.random().toString(36).substr(2); +``` + +## Output Format + +Always provide: +1. ✅ Complete `.excalidraw` JSON file +2. 📊 Summary of what was created +3. 📝 Element count +4. 💡 Instructions for opening/editing + +**Example summary:** +``` +Created: user-workflow.excalidraw +Type: Flowchart +Elements: 7 rectangles, 6 arrows, 1 title text +Total: 14 elements + +To view: +1. Visit https://excalidraw.com +2. Drag and drop user-workflow.excalidraw +3. Or use File → Open in Excalidraw VS Code extension +``` + +## Validation Checklist + +Before delivering the diagram: +- [ ] All elements have unique IDs +- [ ] Coordinates prevent overlapping +- [ ] Text is readable (font size 16+) +- [ ] **All text elements use `fontFamily: 5` (Excalifont)** +- [ ] Arrows connect logically +- [ ] Colors follow consistent scheme +- [ ] File is valid JSON +- [ ] Element count is reasonable (<20 for clarity) + +## Icon Libraries (Optional Enhancement) + +For specialized diagrams (e.g., AWS/GCP/Azure architecture diagrams), you can use pre-made icon libraries from Excalidraw. This provides professional, standardized icons instead of basic shapes. + +### When User Requests Icons + +**If user asks for AWS/cloud architecture diagrams or mentions wanting to use specific icons:** + +1. **Check if library exists**: Look for `libraries//reference.md` +2. **If library exists**: Proceed to use icons (see AI Assistant Workflow below) +3. **If library does NOT exist**: Respond with setup instructions: + + ``` + To use [AWS/GCP/Azure/etc.] architecture icons, please follow these steps: + + 1. Visit https://libraries.excalidraw.com/ + 2. Search for "[AWS Architecture Icons/etc.]" and download the .excalidrawlib file + 3. Create directory: skills/excalidraw-diagram-generator/libraries/[icon-set-name]/ + 4. Place the downloaded file in that directory + 5. Run the splitter script: + python skills/excalidraw-diagram-generator/scripts/split-excalidraw-library.py skills/excalidraw-diagram-generator/libraries/[icon-set-name]/ + + This will split the library into individual icon files for efficient use. + After setup is complete, I can create your diagram using the actual AWS/cloud icons. + + Alternatively, I can create the diagram now using simple shapes (rectangles, ellipses) + which you can later replace with icons manually in Excalidraw. + ``` + +### User Setup Instructions (Detailed) + +**Step 1: Create Library Directory** +```bash +mkdir -p skills/excalidraw-diagram-generator/libraries/aws-architecture-icons +``` + +**Step 2: Download Library** +- Visit: https://libraries.excalidraw.com/ +- Search for your desired icon set (e.g., "AWS Architecture Icons") +- Click download to get the `.excalidrawlib` file +- Example categories (availability varies; confirm on the site): + - Cloud service icons + - UI/Material icons + - Flowchart symbols + +**Step 3: Place Library File** +- Rename the downloaded file to match the directory name (e.g., `aws-architecture-icons.excalidrawlib`) +- Move it to the directory created in Step 1 + +**Step 4: Run Splitter Script** +```bash +python skills/excalidraw-diagram-generator/scripts/split-excalidraw-library.py skills/excalidraw-diagram-generator/libraries/aws-architecture-icons/ +``` + +**Step 5: Verify Setup** +After running the script, verify the following structure exists: +``` +skills/excalidraw-diagram-generator/libraries/aws-architecture-icons/ + aws-architecture-icons.excalidrawlib (original) + reference.md (generated - icon lookup table) + icons/ (generated - individual icon files) + API-Gateway.json + CloudFront.json + EC2.json + Lambda.json + RDS.json + S3.json + ... +``` + +### AI Assistant Workflow + +**When icon libraries are available in `libraries/`:** + +**RECOMMENDED APPROACH: Use Python Scripts (Efficient & Reliable)** + +The repository includes Python scripts that handle icon integration automatically: + +1. **Create base diagram structure**: + - Create `.excalidraw` file with basic layout (title, boxes, regions) + - This establishes the canvas and overall structure + +2. **Add icons using Python script**: + ```bash + python skills/excalidraw-diagram-generator/scripts/add-icon-to-diagram.py \ + [--label "Text"] [--library-path PATH] + ``` + - Edit via `.excalidraw.edit` is enabled by default to avoid overwrite issues; pass `--no-use-edit-suffix` to disable. + + **Examples**: + ```bash + # Add EC2 icon at position (400, 300) with label + python scripts/add-icon-to-diagram.py diagram.excalidraw EC2 400 300 --label "Web Server" + + # Add VPC icon at position (200, 150) + python scripts/add-icon-to-diagram.py diagram.excalidraw VPC 200 150 + + # Add icon from different library + python scripts/add-icon-to-diagram.py diagram.excalidraw Compute-Engine 500 200 \ + --library-path libraries/gcp-icons --label "API Server" + ``` + +3. **Add connecting arrows**: + ```bash + python skills/excalidraw-diagram-generator/scripts/add-arrow.py \ + [--label "Text"] [--style solid|dashed|dotted] [--color HEX] + ``` + - Edit via `.excalidraw.edit` is enabled by default to avoid overwrite issues; pass `--no-use-edit-suffix` to disable. + + **Examples**: + ```bash + # Simple arrow from (300, 250) to (500, 300) + python scripts/add-arrow.py diagram.excalidraw 300 250 500 300 + + # Arrow with label + python scripts/add-arrow.py diagram.excalidraw 300 250 500 300 --label "HTTPS" + + # Dashed arrow with custom color + python scripts/add-arrow.py diagram.excalidraw 400 350 600 400 --style dashed --color "#7950f2" + ``` + +4. **Workflow summary**: + ```bash + # Step 1: Create base diagram with title and structure + # (Create .excalidraw file with initial elements) + + # Step 2: Add icons with labels + python scripts/add-icon-to-diagram.py my-diagram.excalidraw "Internet-gateway" 200 150 --label "Internet Gateway" + python scripts/add-icon-to-diagram.py my-diagram.excalidraw VPC 250 250 + python scripts/add-icon-to-diagram.py my-diagram.excalidraw ELB 350 300 --label "Load Balancer" + python scripts/add-icon-to-diagram.py my-diagram.excalidraw EC2 450 350 --label "EC2 Instance" + python scripts/add-icon-to-diagram.py my-diagram.excalidraw RDS 550 400 --label "Database" + + # Step 3: Add connecting arrows + python scripts/add-arrow.py my-diagram.excalidraw 250 200 300 250 # Internet → VPC + python scripts/add-arrow.py my-diagram.excalidraw 300 300 400 300 # VPC → ELB + python scripts/add-arrow.py my-diagram.excalidraw 400 330 500 350 # ELB → EC2 + python scripts/add-arrow.py my-diagram.excalidraw 500 380 600 400 # EC2 → RDS + ``` + +**Benefits of Python Script Approach**: +- ✅ **No token consumption**: Icon JSON data (200-1000 lines each) never enters AI context +- ✅ **Accurate transformations**: Coordinate calculations handled deterministically +- ✅ **ID management**: Automatic UUID generation prevents conflicts +- ✅ **Reliable**: No risk of coordinate miscalculation or ID collision +- ✅ **Fast**: Direct file manipulation, no parsing overhead +- ✅ **Reusable**: Works with any Excalidraw library you provide + +**ALTERNATIVE: Manual Icon Integration (Not Recommended)** + +Only use this if Python scripts are unavailable: + +1. **Check for libraries**: + ``` + List directory: skills/excalidraw-diagram-generator/libraries/ + Look for subdirectories containing reference.md files + ``` + +2. **Read reference.md**: + ``` + Open: libraries//reference.md + This is lightweight (typically <300 lines) and lists all available icons + ``` + +3. **Find relevant icons**: + ``` + Search the reference.md table for icon names matching diagram needs + Example: For AWS diagram with EC2, S3, Lambda → Find "EC2", "S3", "Lambda" in table + ``` + +4. **Load specific icon data** (WARNING: Large files): + ``` + Read ONLY the needed icon files: + - libraries/aws-architecture-icons/icons/EC2.json (200-300 lines) + - libraries/aws-architecture-icons/icons/S3.json (200-300 lines) + - libraries/aws-architecture-icons/icons/Lambda.json (200-300 lines) + Note: Each icon file is 200-1000 lines - this consumes significant tokens + ``` + +5. **Extract and transform elements**: + ``` + Each icon JSON contains an "elements" array + Calculate bounding box (min_x, min_y, max_x, max_y) + Apply offset to all x/y coordinates + Generate new unique IDs for all elements + Update groupIds references + Copy transformed elements into your diagram + ``` + +6. **Position icons and add connections**: + ``` + Adjust x/y coordinates to position icons correctly in the diagram + Update IDs to ensure uniqueness across diagram + Add connecting arrows and labels as needed + ``` + +**Manual Integration Challenges**: +- ⚠️ High token consumption (200-1000 lines per icon × number of icons) +- ⚠️ Complex coordinate transformation calculations +- ⚠️ Risk of ID collision if not handled carefully +- ⚠️ Time-consuming for diagrams with many icons + +### Example: Creating AWS Diagram with Icons + +**Request**: "Create an AWS architecture diagram with Internet Gateway, VPC, ELB, EC2, and RDS" + +**Recommended Workflow (using Python scripts)**: +**Request**: "Create an AWS architecture diagram with Internet Gateway, VPC, ELB, EC2, and RDS" + +**Recommended Workflow (using Python scripts)**: + +```bash +# Step 1: Create base diagram file with title +# Create my-aws-diagram.excalidraw with basic structure (title, etc.) + +# Step 2: Check icon availability +# Read: libraries/aws-architecture-icons/reference.md +# Confirm icons exist: Internet-gateway, VPC, ELB, EC2, RDS + +# Step 3: Add icons with Python script +python scripts/add-icon-to-diagram.py my-aws-diagram.excalidraw "Internet-gateway" 150 100 --label "Internet Gateway" +python scripts/add-icon-to-diagram.py my-aws-diagram.excalidraw VPC 200 200 +python scripts/add-icon-to-diagram.py my-aws-diagram.excalidraw ELB 350 250 --label "Load Balancer" +python scripts/add-icon-to-diagram.py my-aws-diagram.excalidraw EC2 500 300 --label "Web Server" +python scripts/add-icon-to-diagram.py my-aws-diagram.excalidraw RDS 650 350 --label "Database" + +# Step 4: Add connecting arrows +python scripts/add-arrow.py my-aws-diagram.excalidraw 200 150 250 200 # Internet → VPC +python scripts/add-arrow.py my-aws-diagram.excalidraw 265 230 350 250 # VPC → ELB +python scripts/add-arrow.py my-aws-diagram.excalidraw 415 280 500 300 # ELB → EC2 +python scripts/add-arrow.py my-aws-diagram.excalidraw 565 330 650 350 --label "SQL" --style dashed + +# Result: Complete diagram with professional AWS icons, labels, and connections +``` + +**Benefits**: +- No manual coordinate calculation +- No token consumption for icon data +- Deterministic, reliable results +- Easy to iterate and adjust positions + +**Alternative Workflow (manual, if scripts unavailable)**: +1. Check: `libraries/aws-architecture-icons/reference.md` exists → Yes +2. Read reference.md → Find entries for Internet-gateway, VPC, ELB, EC2, RDS +3. Load: + - `icons/Internet-gateway.json` (298 lines) + - `icons/VPC.json` (550 lines) + - `icons/ELB.json` (363 lines) + - `icons/EC2.json` (231 lines) + - `icons/RDS.json` (similar size) + **Total: ~2000+ lines of JSON to process** +4. Extract elements from each JSON +5. Calculate bounding boxes and offsets for each icon +6. Transform all coordinates (x, y) for positioning +7. Generate unique IDs for all elements +8. Add arrows showing data flow +9. Add text labels +10. Generate final `.excalidraw` file + +**Challenges with manual approach**: +- High token consumption (~2000-5000 lines) +- Complex coordinate math +- Risk of ID conflicts + +### Supported Icon Libraries (Examples — verify availability) + +- This workflow works with any valid `.excalidrawlib` file you provide. +- Examples of library categories you may find on https://libraries.excalidraw.com/: + - Cloud service icons + - Kubernetes / infrastructure icons + - UI / Material icons + - Flowchart / diagram symbols + - Network diagram icons +- Availability and naming can change; verify exact library names on the site before use. + +### Fallback: No Icons Available + +**If no icon libraries are set up:** +- Create diagrams using basic shapes (rectangles, ellipses, arrows) +- Use color coding and text labels to distinguish components +- Inform user they can add icons later or set up libraries for future diagrams +- The diagram will still be functional and clear, just less visually polished + +## References + +See bundled references for: +- `references/excalidraw-schema.md` - Complete Excalidraw JSON schema +- `references/element-types.md` - Detailed element type specifications +- `templates/flowchart-template.json` - Basic flowchart starter +- `templates/relationship-template.json` - Relationship diagram starter +- `templates/mindmap-template.json` - Mind map starter +- `scripts/split-excalidraw-library.py` - Tool to split `.excalidrawlib` files +- `scripts/README.md` - Documentation for library tools +- `scripts/.gitignore` - Prevents local Python artifacts from being committed + +## Limitations + +- Complex curves are simplified to straight/basic curved lines +- Hand-drawn roughness is set to default (1) +- No embedded images support in auto-generation +- Maximum recommended elements: 20 per diagram +- No automatic collision detection (use spacing guidelines) + +## Future Enhancements + +Potential improvements: +- Auto-layout optimization algorithms +- Import from Mermaid/PlantUML syntax +- Template library expansion +- Interactive editing after generation diff --git a/skills/excalidraw-diagram-generator/references/element-types.md b/skills/excalidraw-diagram-generator/references/element-types.md new file mode 100644 index 00000000..3d85f8b2 --- /dev/null +++ b/skills/excalidraw-diagram-generator/references/element-types.md @@ -0,0 +1,497 @@ +# Excalidraw Element Types Guide + +Detailed specifications for each Excalidraw element type with visual examples and use cases. + +## Element Type Overview + +| Type | Visual | Primary Use | Text Support | +|------|--------|-------------|--------------| +| `rectangle` | □ | Boxes, containers, process steps | ✅ Yes | +| `ellipse` | ○ | Emphasis, terminals, states | ✅ Yes | +| `diamond` | ◇ | Decision points, choices | ✅ Yes | +| `arrow` | → | Directional flow, relationships | ❌ No (use separate text) | +| `line` | — | Connections, dividers | ❌ No | +| `text` | A | Labels, annotations, titles | ✅ (Its purpose) | + +--- + +## Rectangle + +**Best for:** Process steps, entities, data stores, components + +### Properties + +```typescript +{ + type: "rectangle", + roundness: { type: 3 }, // Rounded corners + text: "Step Name", // Optional embedded text + fontSize: 20, + textAlign: "center", + verticalAlign: "middle" +} +``` + +### Use Cases + +| Scenario | Configuration | +|----------|---------------| +| **Process step** | Green background (`#b2f2bb`), centered text | +| **Entity/Object** | Blue background (`#a5d8ff`), medium size | +| **System component** | Light color, descriptive text | +| **Data store** | Gray/white, database-like label | + +### Size Guidelines + +| Content | Width | Height | +|---------|-------|--------| +| Single word | 120-150px | 60-80px | +| Short phrase (2-4 words) | 180-220px | 80-100px | +| Sentence | 250-300px | 100-120px | + +### Example + +```json +{ + "type": "rectangle", + "x": 100, + "y": 100, + "width": 200, + "height": 80, + "backgroundColor": "#b2f2bb", + "text": "Validate Input", + "fontSize": 20, + "textAlign": "center", + "verticalAlign": "middle", + "roundness": { "type": 3 } +} +``` + +--- + +## Ellipse + +**Best for:** Start/end points, states, emphasis circles + +### Properties + +```typescript +{ + type: "ellipse", + text: "Start", + fontSize: 18, + textAlign: "center", + verticalAlign: "middle" +} +``` + +### Use Cases + +| Scenario | Configuration | +|----------|---------------| +| **Flow start** | Light green, "Start" text | +| **Flow end** | Light red, "End" text | +| **State** | Soft color, state name | +| **Highlight** | Bright color, emphasis text | + +### Size Guidelines + +For circular shapes, use `width === height`: + +| Content | Diameter | +|---------|----------| +| Icon/Symbol | 60-80px | +| Short text | 100-120px | +| Longer text | 150-180px | + +### Example + +```json +{ + "type": "ellipse", + "x": 100, + "y": 100, + "width": 120, + "height": 120, + "backgroundColor": "#d0f0c0", + "text": "Start", + "fontSize": 18, + "textAlign": "center", + "verticalAlign": "middle" +} +``` + +--- + +## Diamond + +**Best for:** Decision points, conditional branches + +### Properties + +```typescript +{ + type: "diamond", + text: "Valid?", + fontSize: 18, + textAlign: "center", + verticalAlign": "middle" +} +``` + +### Use Cases + +| Scenario | Text Example | +|----------|--------------| +| **Yes/No decision** | "Is Valid?", "Exists?" | +| **Multiple choice** | "Type?", "Status?" | +| **Conditional** | "Score > 50?" | + +### Size Guidelines + +Diamonds need more space than rectangles for the same text: + +| Content | Width | Height | +|---------|-------|--------| +| Yes/No | 120-140px | 120-140px | +| Short question | 160-180px | 160-180px | +| Longer question | 200-220px | 200-220px | + +### Example + +```json +{ + "type": "diamond", + "x": 100, + "y": 100, + "width": 150, + "height": 150, + "backgroundColor": "#ffe4a3", + "text": "Valid?", + "fontSize": 18, + "textAlign": "center", + "verticalAlign": "middle" +} +``` + +--- + +## Arrow + +**Best for:** Flow direction, relationships, dependencies + +### Properties + +```typescript +{ + type: "arrow", + points: [[0, 0], [endX, endY]], // Relative coordinates + roundness: { type: 2 }, // Curved + startBinding: null, // Or { elementId, focus, gap } + endBinding: null +} +``` + +### Arrow Directions + +#### Horizontal (Left to Right) + +```json +{ + "x": 100, + "y": 150, + "width": 200, + "height": 0, + "points": [[0, 0], [200, 0]] +} +``` + +#### Vertical (Top to Bottom) + +```json +{ + "x": 200, + "y": 100, + "width": 0, + "height": 150, + "points": [[0, 0], [0, 150]] +} +``` + +#### Diagonal + +```json +{ + "x": 100, + "y": 100, + "width": 200, + "height": 150, + "points": [[0, 0], [200, 150]] +} +``` + +### Arrow Styles + +| Style | `strokeStyle` | `strokeWidth` | Use Case | +|-------|---------------|---------------|----------| +| **Normal flow** | `"solid"` | 2 | Standard connections | +| **Optional/Weak** | `"dashed"` | 2 | Optional paths | +| **Important** | `"solid"` | 3-4 | Emphasized flow | +| **Dotted** | `"dotted"` | 2 | Indirect relationships | + +### Adding Arrow Labels + +Use separate text elements positioned near arrow midpoint: + +```json +[ + { + "type": "arrow", + "id": "arrow1", + "x": 100, + "y": 150, + "points": [[0, 0], [200, 0]] + }, + { + "type": "text", + "x": 180, // Near midpoint + "y": 130, // Above arrow + "text": "sends", + "fontSize": 14 + } +] +``` + +--- + +## Line + +**Best for:** Non-directional connections, dividers, borders + +### Properties + +```typescript +{ + type: "line", + points: [[0, 0], [x2, y2], [x3, y3], ...], + roundness: null // Or { type: 2 } for curved +} +``` + +### Use Cases + +| Scenario | Configuration | +|----------|---------------| +| **Divider** | Horizontal, thin stroke | +| **Border** | Closed path (polygon) | +| **Connection** | Multi-point path | +| **Underline** | Short horizontal line | + +### Multi-Point Line Example + +```json +{ + "type": "line", + "x": 100, + "y": 100, + "points": [ + [0, 0], + [100, 50], + [200, 0] + ] +} +``` + +--- + +## Text + +**Best for:** Labels, titles, annotations, standalone text + +### Properties + +```typescript +{ + type: "text", + text: "Label text", + fontSize: 20, + fontFamily: 1, // 1=Virgil, 2=Helvetica, 3=Cascadia + textAlign: "left", + verticalAlign: "top" +} +``` + +### Font Sizes by Purpose + +| Purpose | Font Size | +|---------|-----------| +| **Main title** | 28-36 | +| **Section header** | 24-28 | +| **Element label** | 18-22 | +| **Annotation** | 14-16 | +| **Small note** | 12-14 | + +### Width/Height Calculation + +```javascript +// Approximate width +const width = text.length * fontSize * 0.6; + +// Approximate height (single line) +const height = fontSize * 1.2; + +// Multi-line +const lines = text.split('\n').length; +const height = fontSize * 1.2 * lines; +``` + +### Text Positioning + +| Position | textAlign | verticalAlign | Use Case | +|----------|-----------|---------------|----------| +| **Top-left** | `"left"` | `"top"` | Default labels | +| **Centered** | `"center"` | `"middle"` | Titles | +| **Bottom-right** | `"right"` | `"bottom"` | Footnotes | + +### Example: Title + +```json +{ + "type": "text", + "x": 100, + "y": 50, + "width": 400, + "height": 40, + "text": "System Architecture", + "fontSize": 32, + "fontFamily": 2, + "textAlign": "center", + "verticalAlign": "top" +} +``` + +### Example: Annotation + +```json +{ + "type": "text", + "x": 150, + "y": 200, + "width": 100, + "height": 20, + "text": "User input", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "top" +} +``` + +--- + +## Combining Elements + +### Pattern: Labeled Box + +```json +[ + { + "type": "rectangle", + "id": "box1", + "x": 100, + "y": 100, + "width": 200, + "height": 100, + "text": "Component", + "textAlign": "center", + "verticalAlign": "middle" + } +] +``` + +### Pattern: Connected Boxes + +```json +[ + { + "type": "rectangle", + "id": "box1", + "x": 100, + "y": 100, + "width": 150, + "height": 80, + "text": "Step 1" + }, + { + "type": "arrow", + "id": "arrow1", + "x": 250, + "y": 140, + "points": [[0, 0], [100, 0]] + }, + { + "type": "rectangle", + "id": "box2", + "x": 350, + "y": 100, + "width": 150, + "height": 80, + "text": "Step 2" + } +] +``` + +### Pattern: Decision Tree + +```json +[ + { + "type": "diamond", + "id": "decision", + "x": 100, + "y": 100, + "width": 140, + "height": 140, + "text": "Valid?" + }, + { + "type": "arrow", + "id": "yes-arrow", + "x": 240, + "y": 170, + "points": [[0, 0], [60, 0]] + }, + { + "type": "text", + "id": "yes-label", + "x": 250, + "y": 150, + "text": "Yes", + "fontSize": 14 + }, + { + "type": "rectangle", + "id": "yes-box", + "x": 300, + "y": 140, + "width": 120, + "height": 60, + "text": "Process" + } +] +``` + +--- + +## Summary + +| When you need... | Use this element | +|------------------|------------------| +| Process box | `rectangle` with text | +| Decision point | `diamond` with question | +| Flow direction | `arrow` | +| Start/End | `ellipse` | +| Title/Header | `text` (large font) | +| Annotation | `text` (small font) | +| Non-directional link | `line` | +| Divider | `line` (horizontal) | diff --git a/skills/excalidraw-diagram-generator/references/excalidraw-schema.md b/skills/excalidraw-diagram-generator/references/excalidraw-schema.md new file mode 100644 index 00000000..bfdac6cf --- /dev/null +++ b/skills/excalidraw-diagram-generator/references/excalidraw-schema.md @@ -0,0 +1,350 @@ +# Excalidraw JSON Schema Reference + +This document describes the structure of Excalidraw `.excalidraw` files for diagram generation. + +## Top-Level Structure + +```typescript +interface ExcalidrawFile { + type: "excalidraw"; + version: number; // Always 2 + source: string; // "https://excalidraw.com" + elements: ExcalidrawElement[]; + appState: AppState; + files: Record; // Usually empty {} +} +``` + +## AppState + +```typescript +interface AppState { + viewBackgroundColor: string; // Hex color, e.g., "#ffffff" + gridSize: number; // Typically 20 +} +``` + +## ExcalidrawElement Base Properties + +All elements share these common properties: + +```typescript +interface BaseElement { + id: string; // Unique identifier + type: ElementType; // See Element Types below + x: number; // X coordinate (pixels from top-left) + y: number; // Y coordinate (pixels from top-left) + width: number; // Width in pixels + height: number; // Height in pixels + angle: number; // Rotation angle in radians (usually 0) + strokeColor: string; // Hex color, e.g., "#1e1e1e" + backgroundColor: string; // Hex color or "transparent" + fillStyle: "solid" | "hachure" | "cross-hatch"; + strokeWidth: number; // 1-4 typically + strokeStyle: "solid" | "dashed" | "dotted"; + roughness: number; // 0-2, controls hand-drawn effect (1 = default) + opacity: number; // 0-100 + groupIds: string[]; // IDs of groups this element belongs to + frameId: null; // Usually null + index: string; // Stacking order identifier + roundness: Roundness | null; + seed: number; // Random seed for deterministic rendering + version: number; // Element version (increment on edit) + versionNonce: number; // Random number changed on edit + isDeleted: boolean; // Should be false + boundElements: any; // Usually null + updated: number; // Timestamp in milliseconds + link: null; // External link (usually null) + locked: boolean; // Whether element is locked +} +``` + +## Element Types + +### Rectangle + +```typescript +interface RectangleElement extends BaseElement { + type: "rectangle"; + roundness: { type: 3 }; // 3 = rounded corners + text?: string; // Optional text inside + fontSize?: number; // Font size (16-32 typical) + fontFamily?: number; // 1 = Virgil, 2 = Helvetica, 3 = Cascadia + textAlign?: "left" | "center" | "right"; + verticalAlign?: "top" | "middle" | "bottom"; +} +``` + +**Example:** +```json +{ + "id": "rect1", + "type": "rectangle", + "x": 100, + "y": 100, + "width": 200, + "height": 100, + "strokeColor": "#1e1e1e", + "backgroundColor": "#a5d8ff", + "text": "My Box", + "fontSize": 20, + "textAlign": "center", + "verticalAlign": "middle", + "roundness": { "type": 3 } +} +``` + +### Ellipse + +```typescript +interface EllipseElement extends BaseElement { + type: "ellipse"; + text?: string; + fontSize?: number; + fontFamily?: number; + textAlign?: "left" | "center" | "right"; + verticalAlign?: "top" | "middle" | "bottom"; +} +``` + +### Diamond + +```typescript +interface DiamondElement extends BaseElement { + type: "diamond"; + text?: string; + fontSize?: number; + fontFamily?: number; + textAlign?: "left" | "center" | "right"; + verticalAlign?: "top" | "middle" | "bottom"; +} +``` + +### Arrow + +```typescript +interface ArrowElement extends BaseElement { + type: "arrow"; + points: [number, number][]; // Array of [x, y] coordinates relative to element + startBinding: Binding | null; + endBinding: Binding | null; + roundness: { type: 2 }; // 2 = curved arrow +} +``` + +**Example:** +```json +{ + "id": "arrow1", + "type": "arrow", + "x": 100, + "y": 100, + "width": 200, + "height": 0, + "points": [ + [0, 0], + [200, 0] + ], + "roundness": { "type": 2 }, + "startBinding": null, + "endBinding": null +} +``` + +**Points explanation:** +- First point `[0, 0]` is relative to `(x, y)` +- Subsequent points are relative to the first point +- For straight horizontal arrow: `[[0, 0], [width, 0]]` +- For straight vertical arrow: `[[0, 0], [0, height]]` + +### Line + +```typescript +interface LineElement extends BaseElement { + type: "line"; + points: [number, number][]; + startBinding: Binding | null; + endBinding: Binding | null; + roundness: { type: 2 } | null; +} +``` + +### Text + +```typescript +interface TextElement extends BaseElement { + type: "text"; + text: string; + fontSize: number; + fontFamily: number; // 1-3 + textAlign: "left" | "center" | "right"; + verticalAlign: "top" | "middle" | "bottom"; + roundness: null; // Text has no roundness +} +``` + +**Example:** +```json +{ + "id": "text1", + "type": "text", + "x": 100, + "y": 100, + "width": 150, + "height": 25, + "text": "Hello World", + "fontSize": 20, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "top", + "roundness": null +} +``` + +**Width/Height calculation:** +- Width ≈ `text.length * fontSize * 0.6` +- Height ≈ `fontSize * 1.2 * numberOfLines` + +## Bindings + +Bindings connect arrows to shapes: + +```typescript +interface Binding { + elementId: string; // ID of bound element + focus: number; // -1 to 1, position along edge + gap: number; // Distance from element edge +} +``` + +## Common Colors + +| Color Name | Hex Code | Use Case | +|------------|----------|----------| +| Black | `#1e1e1e` | Default stroke | +| Light Blue | `#a5d8ff` | Primary entities | +| Light Green | `#b2f2bb` | Process steps | +| Yellow | `#ffd43b` | Important/Central | +| Light Red | `#ffc9c9` | Warnings/Errors | +| Cyan | `#96f2d7` | Secondary items | +| Transparent | `transparent` | No fill | +| White | `#ffffff` | Background | + +## ID Generation + +IDs should be unique strings. Common patterns: + +```javascript +// Timestamp-based +const id = Date.now().toString(36) + Math.random().toString(36).substr(2); + +// Sequential +const id = "element-" + counter++; + +// Descriptive +const id = "step-1", "entity-user", "arrow-1-to-2"; +``` + +## Seed Generation + +Seeds are used for deterministic randomness in hand-drawn effect: + +```javascript +const seed = Math.floor(Math.random() * 2147483647); +``` + +## Version and VersionNonce + +```javascript +const version = 1; // Increment when element is edited +const versionNonce = Math.floor(Math.random() * 2147483647); +``` + +## Coordinate System + +- Origin `(0, 0)` is top-left corner +- X increases to the right +- Y increases downward +- All units are in pixels + +## Recommended Spacing + +| Context | Spacing | +|---------|---------| +| Horizontal gap between elements | 200-300px | +| Vertical gap between rows | 100-150px | +| Minimum margin from edge | 50px | +| Arrow-to-box clearance | 20-30px | + +## Font Families + +| ID | Name | Description | +|----|------|-------------| +| 1 | Virgil | Hand-drawn style (default) | +| 2 | Helvetica | Clean sans-serif | +| 3 | Cascadia | Monospace | + +## Validation Rules + +✅ **Required:** +- All IDs must be unique +- `type` must match actual element type +- `version` must be an integer ≥ 1 +- `opacity` must be 0-100 + +⚠️ **Recommended:** +- Keep `roughness` at 1 for consistency +- Use `strokeWidth` of 2 for clarity +- Set `isDeleted` to `false` +- Set `locked` to `false` +- Keep `frameId`, `boundElements`, `link` as `null` + +## Complete Minimal Example + +```json +{ + "type": "excalidraw", + "version": 2, + "source": "https://excalidraw.com", + "elements": [ + { + "id": "box1", + "type": "rectangle", + "x": 100, + "y": 100, + "width": 200, + "height": 100, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "#a5d8ff", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "a0", + "roundness": { "type": 3 }, + "seed": 1234567890, + "version": 1, + "versionNonce": 987654321, + "isDeleted": false, + "boundElements": null, + "updated": 1706659200000, + "link": null, + "locked": false, + "text": "Hello", + "fontSize": 20, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle" + } + ], + "appState": { + "viewBackgroundColor": "#ffffff", + "gridSize": 20 + }, + "files": {} +} +``` diff --git a/skills/excalidraw-diagram-generator/scripts/.gitignore b/skills/excalidraw-diagram-generator/scripts/.gitignore new file mode 100644 index 00000000..bcc50b67 --- /dev/null +++ b/skills/excalidraw-diagram-generator/scripts/.gitignore @@ -0,0 +1,34 @@ +# Avoid accidentally committing local Python artifacts produced when running these scripts. + +# Byte-compiled / cache files +__pycache__/ +*.py[cod] +*$py.class + +# Virtual environments (people often create these next to scripts) +.venv/ +venv/ +env/ +ENV/ + +# Tool caches +.pytest_cache/ +.mypy_cache/ +.ruff_cache/ +.tox/ +.nox/ + +# Coverage outputs +.coverage +.coverage.* +htmlcov/ + +# Packaging/build outputs (rare here, but harmless) +build/ +dist/ +*.egg-info/ +.eggs/ + +# OS cruft +.DS_Store +Thumbs.db diff --git a/skills/excalidraw-diagram-generator/scripts/README.md b/skills/excalidraw-diagram-generator/scripts/README.md new file mode 100644 index 00000000..df810f1a --- /dev/null +++ b/skills/excalidraw-diagram-generator/scripts/README.md @@ -0,0 +1,193 @@ +# Excalidraw Library Tools + +This directory contains scripts for working with Excalidraw libraries. + +## split-excalidraw-library.py + +Splits an Excalidraw library file (`*.excalidrawlib`) into individual icon JSON files for efficient token usage by AI assistants. + +### Prerequisites + +- Python 3.6 or higher +- No additional dependencies required (uses only standard library) + +### Usage + +```bash +python split-excalidraw-library.py +``` + +### Step-by-Step Workflow + +1. **Create library directory**: + ```bash + mkdir -p skills/excalidraw-diagram-generator/libraries/aws-architecture-icons + ``` + +2. **Download and place library file**: + - Visit: https://libraries.excalidraw.com/ + - Search for "AWS Architecture Icons" and download the `.excalidrawlib` file + - Rename it to match the directory name: `aws-architecture-icons.excalidrawlib` + - Place it in the directory created in step 1 + +3. **Run the script**: + ```bash + python skills/excalidraw-diagram-generator/scripts/split-excalidraw-library.py skills/excalidraw-diagram-generator/libraries/aws-architecture-icons/ + ``` + +### Output Structure + +The script creates the following structure in the library directory: + +``` +skills/excalidraw-diagram-generator/libraries/aws-architecture-icons/ + aws-architecture-icons.excalidrawlib # Original file (kept) + reference.md # Generated: Quick reference table + icons/ # Generated: Individual icon files + API-Gateway.json + CloudFront.json + EC2.json + S3.json + ... +``` + +### What the Script Does + +1. **Reads** the `.excalidrawlib` file +2. **Extracts** each icon from the `libraryItems` array +3. **Sanitizes** icon names to create valid filenames (spaces → hyphens, removes special characters) +4. **Saves** each icon as a separate JSON file in the `icons/` directory +5. **Generates** a `reference.md` file with a table mapping icon names to filenames + +### Benefits + +- **Token Efficiency**: AI can first read the lightweight `reference.md` to find relevant icons, then load only the specific icon files needed +- **Organization**: Icons are organized in a clear directory structure +- **Extensibility**: Users can add multiple library sets side-by-side + +### Recommended Workflow + +1. Download desired Excalidraw libraries from https://libraries.excalidraw.com/ +2. Run this script on each library file +3. Move the generated folders to `../libraries/` +4. The AI assistant will use `reference.md` files to locate and use icons efficiently + +### Library Sources (Examples — verify availability) + +- Examples found on https://libraries.excalidraw.com/ may include cloud/service icon sets. +- Availability changes over time; verify the exact library names on the site before use. +- This script works with any valid `.excalidrawlib` file you provide. + +### Troubleshooting + +**Error: File not found** +- Check that the file path is correct +- Make sure the file has a `.excalidrawlib` extension + +**Error: Invalid library file format** +- Ensure the file is a valid Excalidraw library file +- Check that it contains a `libraryItems` array + +### License Considerations + +When using third-party icon libraries: +- **AWS Architecture Icons**: Subject to AWS Content License +- **GCP Icons**: Subject to Google's terms +- **Other libraries**: Check each library's license + +This script is for personal/organizational use. Redistribution of split icon files should comply with the original library's license terms. + +## add-icon-to-diagram.py + +Adds a specific icon from a split Excalidraw library into an existing `.excalidraw` diagram. The script handles coordinate translation and ID collision avoidance, and can optionally add a label under the icon. + +### Prerequisites + +- Python 3.6 or higher +- A diagram file (`.excalidraw`) +- A split icon library directory (created by `split-excalidraw-library.py`) + +### Usage + +```bash +python add-icon-to-diagram.py [OPTIONS] +``` + +**Options** +- `--library-path PATH` : Path to the icon library directory (default: `aws-architecture-icons`) +- `--label TEXT` : Add a text label below the icon +-- `--use-edit-suffix` : Edit via `.excalidraw.edit` to avoid editor overwrite issues (enabled by default; pass `--no-use-edit-suffix` to disable) + +### Examples + +```bash +# Add EC2 icon at position (400, 300) +python add-icon-to-diagram.py diagram.excalidraw EC2 400 300 + +# Add VPC icon with label +python add-icon-to-diagram.py diagram.excalidraw VPC 200 150 --label "VPC" + +# Safe edit mode is enabled by default (avoids editor overwrite issues) +# Use `--no-use-edit-suffix` to disable +python add-icon-to-diagram.py diagram.excalidraw EC2 500 300 + +# Add icon from another library +python add-icon-to-diagram.py diagram.excalidraw Compute-Engine 500 200 \ + --library-path libraries/gcp-icons --label "API Server" +``` + +### What the Script Does + +1. **Loads** the icon JSON from the library’s `icons/` directory +2. **Calculates** the icon’s bounding box +3. **Offsets** all coordinates to the target position +4. **Generates** unique IDs for all elements and groups +5. **Appends** the transformed elements to the diagram +6. **(Optional)** Adds a label beneath the icon + +--- + +## add-arrow.py + +Adds a straight arrow between two points in an existing `.excalidraw` diagram. Supports optional labels and line styles. + +### Prerequisites + +- Python 3.6 or higher +- A diagram file (`.excalidraw`) + +### Usage + +```bash +python add-arrow.py [OPTIONS] +``` + +**Options** +- `--style {solid|dashed|dotted}` : Line style (default: `solid`) +- `--color HEX` : Arrow color (default: `#1e1e1e`) +- `--label TEXT` : Add a text label on the arrow +-- `--use-edit-suffix` : Edit via `.excalidraw.edit` to avoid editor overwrite issues (enabled by default; pass `--no-use-edit-suffix` to disable) + +### Examples + +```bash +# Simple arrow +python add-arrow.py diagram.excalidraw 300 200 500 300 + +# Arrow with label +python add-arrow.py diagram.excalidraw 300 200 500 300 --label "HTTPS" + +# Dashed arrow with custom color +python add-arrow.py diagram.excalidraw 400 350 600 400 --style dashed --color "#7950f2" + +# Safe edit mode is enabled by default (avoids editor overwrite issues) +# Use `--no-use-edit-suffix` to disable +python add-arrow.py diagram.excalidraw 300 200 500 300 +``` + +### What the Script Does + +1. **Creates** an arrow element from the given coordinates +2. **(Optional)** Adds a label near the arrow midpoint +3. **Appends** elements to the diagram +4. **Saves** the updated file diff --git a/skills/excalidraw-diagram-generator/scripts/add-arrow.py b/skills/excalidraw-diagram-generator/scripts/add-arrow.py new file mode 100644 index 00000000..169f09ff --- /dev/null +++ b/skills/excalidraw-diagram-generator/scripts/add-arrow.py @@ -0,0 +1,312 @@ +#!/usr/bin/env python3 +""" +Add arrows (connections) between elements in Excalidraw diagrams. + +Usage: + python add-arrow.py [OPTIONS] + +Options: + --style {solid|dashed|dotted} Arrow line style (default: solid) + --color HEX Arrow color (default: #1e1e1e) + --label TEXT Add text label on the arrow + --use-edit-suffix Edit via .excalidraw.edit to avoid editor overwrite issues (enabled by default; use --no-use-edit-suffix to disable) + +Examples: + python add-arrow.py diagram.excalidraw 300 200 500 300 + python add-arrow.py diagram.excalidraw 300 200 500 300 --label "HTTP" + python add-arrow.py diagram.excalidraw 300 200 500 300 --style dashed --color "#7950f2" + python add-arrow.py diagram.excalidraw 300 200 500 300 --use-edit-suffix +""" + +import json +import sys +import uuid +from pathlib import Path +from typing import Dict, Any + + +def generate_unique_id() -> str: + """Generate a unique ID for Excalidraw elements.""" + return str(uuid.uuid4()).replace('-', '')[:16] + + +def prepare_edit_path(diagram_path: Path, use_edit_suffix: bool) -> tuple[Path, Path | None]: + """ + Prepare a safe edit path to avoid editor overwrite issues. + + Returns: + (work_path, final_path) + - work_path: file path to read/write during edit + - final_path: file path to rename back to (or None if not used) + """ + if not use_edit_suffix: + return diagram_path, None + + if diagram_path.suffix != ".excalidraw": + return diagram_path, None + + edit_path = diagram_path.with_suffix(diagram_path.suffix + ".edit") + + if diagram_path.exists(): + if edit_path.exists(): + raise FileExistsError(f"Edit file already exists: {edit_path}") + diagram_path.rename(edit_path) + + return edit_path, diagram_path + + +def finalize_edit_path(work_path: Path, final_path: Path | None) -> None: + """Finalize edit by renaming .edit back to .excalidraw if needed.""" + if final_path is None: + return + + if final_path.exists(): + final_path.unlink() + + work_path.rename(final_path) + + +def create_arrow( + from_x: float, + from_y: float, + to_x: float, + to_y: float, + style: str = "solid", + color: str = "#1e1e1e", + label: str = None +) -> list: + """ + Create an arrow element. + + Args: + from_x: Starting X coordinate + from_y: Starting Y coordinate + to_x: Ending X coordinate + to_y: Ending Y coordinate + style: Line style (solid, dashed, dotted) + color: Arrow color + label: Optional text label on the arrow + + Returns: + List of elements (arrow and optional label) + """ + elements = [] + + # Arrow element + arrow = { + "id": generate_unique_id(), + "type": "arrow", + "x": from_x, + "y": from_y, + "width": to_x - from_x, + "height": to_y - from_y, + "angle": 0, + "strokeColor": color, + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": style, + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": None, + "index": "a0", + "roundness": { + "type": 2 + }, + "seed": 1000000000 + hash(f"{from_x}{from_y}{to_x}{to_y}") % 1000000000, + "version": 1, + "versionNonce": 2000000000 + hash(f"{from_x}{from_y}{to_x}{to_y}") % 1000000000, + "isDeleted": False, + "boundElements": [], + "updated": 1738195200000, + "link": None, + "locked": False, + "points": [ + [0, 0], + [to_x - from_x, to_y - from_y] + ], + "startBinding": None, + "endBinding": None, + "startArrowhead": None, + "endArrowhead": "arrow", + "lastCommittedPoint": None + } + elements.append(arrow) + + # Optional label + if label: + mid_x = (from_x + to_x) / 2 - (len(label) * 5) + mid_y = (from_y + to_y) / 2 - 10 + + label_element = { + "id": generate_unique_id(), + "type": "text", + "x": mid_x, + "y": mid_y, + "width": len(label) * 10, + "height": 20, + "angle": 0, + "strokeColor": color, + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": None, + "index": "a0", + "roundness": None, + "seed": 1000000000 + hash(label) % 1000000000, + "version": 1, + "versionNonce": 2000000000 + hash(label) % 1000000000, + "isDeleted": False, + "boundElements": [], + "updated": 1738195200000, + "link": None, + "locked": False, + "text": label, + "fontSize": 14, + "fontFamily": 5, + "textAlign": "center", + "verticalAlign": "top", + "containerId": None, + "originalText": label, + "autoResize": True, + "lineHeight": 1.25 + } + elements.append(label_element) + + return elements + + +def add_arrow_to_diagram( + diagram_path: Path, + from_x: float, + from_y: float, + to_x: float, + to_y: float, + style: str = "solid", + color: str = "#1e1e1e", + label: str = None +) -> None: + """ + Add an arrow to an Excalidraw diagram. + + Args: + diagram_path: Path to the Excalidraw diagram file + from_x: Starting X coordinate + from_y: Starting Y coordinate + to_x: Ending X coordinate + to_y: Ending Y coordinate + style: Line style (solid, dashed, dotted) + color: Arrow color + label: Optional text label + """ + print(f"Creating arrow from ({from_x}, {from_y}) to ({to_x}, {to_y})") + arrow_elements = create_arrow(from_x, from_y, to_x, to_y, style, color, label) + + if label: + print(f" With label: '{label}'") + + # Load diagram + print(f"Loading diagram: {diagram_path}") + with open(diagram_path, 'r', encoding='utf-8') as f: + diagram = json.load(f) + + # Add arrow elements + if 'elements' not in diagram: + diagram['elements'] = [] + + original_count = len(diagram['elements']) + diagram['elements'].extend(arrow_elements) + print(f" Added {len(arrow_elements)} elements (total: {original_count} -> {len(diagram['elements'])})") + + # Save diagram + print(f"Saving diagram") + with open(diagram_path, 'w', encoding='utf-8') as f: + json.dump(diagram, f, indent=2, ensure_ascii=False) + + print(f"✓ Successfully added arrow to diagram") + + +def main(): + """Main entry point.""" + if len(sys.argv) < 6: + print("Usage: python add-arrow.py [OPTIONS]") + print("\nOptions:") + print(" --style {solid|dashed|dotted} Line style (default: solid)") + print(" --color HEX Color (default: #1e1e1e)") + print(" --label TEXT Text label on arrow") + print(" --use-edit-suffix Edit via .excalidraw.edit to avoid editor overwrite issues (enabled by default; use --no-use-edit-suffix to disable)") + print("\nExamples:") + print(" python add-arrow.py diagram.excalidraw 300 200 500 300") + print(" python add-arrow.py diagram.excalidraw 300 200 500 300 --label 'HTTP'") + sys.exit(1) + + diagram_path = Path(sys.argv[1]) + from_x = float(sys.argv[2]) + from_y = float(sys.argv[3]) + to_x = float(sys.argv[4]) + to_y = float(sys.argv[5]) + + # Parse optional arguments + style = "solid" + color = "#1e1e1e" + label = None + # Default: use edit suffix to avoid editor overwrite issues + use_edit_suffix = True + + i = 6 + while i < len(sys.argv): + if sys.argv[i] == '--style': + if i + 1 < len(sys.argv): + style = sys.argv[i + 1] + if style not in ['solid', 'dashed', 'dotted']: + print(f"Error: Invalid style '{style}'. Must be: solid, dashed, or dotted") + sys.exit(1) + i += 2 + else: + print("Error: --style requires an argument") + sys.exit(1) + elif sys.argv[i] == '--color': + if i + 1 < len(sys.argv): + color = sys.argv[i + 1] + i += 2 + else: + print("Error: --color requires an argument") + sys.exit(1) + elif sys.argv[i] == '--label': + if i + 1 < len(sys.argv): + label = sys.argv[i + 1] + i += 2 + else: + print("Error: --label requires a text argument") + sys.exit(1) + elif sys.argv[i] == '--use-edit-suffix': + use_edit_suffix = True + i += 1 + elif sys.argv[i] == '--no-use-edit-suffix': + use_edit_suffix = False + i += 1 + else: + print(f"Error: Unknown option: {sys.argv[i]}") + sys.exit(1) + + # Validate inputs + if not diagram_path.exists(): + print(f"Error: Diagram file not found: {diagram_path}") + sys.exit(1) + + try: + work_path, final_path = prepare_edit_path(diagram_path, use_edit_suffix) + add_arrow_to_diagram(work_path, from_x, from_y, to_x, to_y, style, color, label) + finalize_edit_path(work_path, final_path) + except Exception as e: + print(f"Error: {e}") + sys.exit(1) + + +if __name__ == '__main__': + main() diff --git a/skills/excalidraw-diagram-generator/scripts/add-icon-to-diagram.py b/skills/excalidraw-diagram-generator/scripts/add-icon-to-diagram.py new file mode 100644 index 00000000..f1035254 --- /dev/null +++ b/skills/excalidraw-diagram-generator/scripts/add-icon-to-diagram.py @@ -0,0 +1,404 @@ +#!/usr/bin/env python3 +""" +Add icons from Excalidraw libraries to diagrams. + +This script reads an icon JSON file from an Excalidraw library, transforms its coordinates +to a target position, generates unique IDs, and adds it to an existing Excalidraw diagram. +Works with any Excalidraw library (AWS, GCP, Azure, Kubernetes, etc.). + +Usage: + python add-icon-to-diagram.py [OPTIONS] + +Options: + --library-path PATH Path to the icon library directory (default: aws-architecture-icons) + --label TEXT Add a text label below the icon + --use-edit-suffix Edit via .excalidraw.edit to avoid editor overwrite issues (enabled by default; use --no-use-edit-suffix to disable) + +Examples: + python add-icon-to-diagram.py diagram.excalidraw EC2 500 300 + python add-icon-to-diagram.py diagram.excalidraw EC2 500 300 --label "Web Server" + python add-icon-to-diagram.py diagram.excalidraw VPC 200 150 --library-path libraries/gcp-icons + python add-icon-to-diagram.py diagram.excalidraw EC2 500 300 --use-edit-suffix +""" + +import json +import sys +import uuid +from pathlib import Path +from typing import Dict, List, Any, Tuple + + +def generate_unique_id() -> str: + """Generate a unique ID for Excalidraw elements.""" + return str(uuid.uuid4()).replace('-', '')[:16] + + +def calculate_bounding_box(elements: List[Dict[str, Any]]) -> Tuple[float, float, float, float]: + """Calculate the bounding box (min_x, min_y, max_x, max_y) of icon elements.""" + if not elements: + return (0, 0, 0, 0) + + min_x = float('inf') + min_y = float('inf') + max_x = float('-inf') + max_y = float('-inf') + + for element in elements: + if 'x' in element and 'y' in element: + x = element['x'] + y = element['y'] + width = element.get('width', 0) + height = element.get('height', 0) + + min_x = min(min_x, x) + min_y = min(min_y, y) + max_x = max(max_x, x + width) + max_y = max(max_y, y + height) + + return (min_x, min_y, max_x, max_y) + + +def transform_icon_elements( + elements: List[Dict[str, Any]], + target_x: float, + target_y: float +) -> List[Dict[str, Any]]: + """ + Transform icon elements to target coordinates with unique IDs. + + Args: + elements: Icon elements from JSON file + target_x: Target X coordinate (top-left position) + target_y: Target Y coordinate (top-left position) + + Returns: + Transformed elements with new coordinates and IDs + """ + if not elements: + return [] + + # Calculate bounding box + min_x, min_y, max_x, max_y = calculate_bounding_box(elements) + + # Calculate offset + offset_x = target_x - min_x + offset_y = target_y - min_y + + # Create ID mapping: old_id -> new_id + id_mapping = {} + for element in elements: + if 'id' in element: + old_id = element['id'] + id_mapping[old_id] = generate_unique_id() + + # Create group ID mapping + group_id_mapping = {} + for element in elements: + if 'groupIds' in element: + for old_group_id in element['groupIds']: + if old_group_id not in group_id_mapping: + group_id_mapping[old_group_id] = generate_unique_id() + + # Transform elements + transformed = [] + for element in elements: + new_element = element.copy() + + # Update coordinates + if 'x' in new_element: + new_element['x'] = new_element['x'] + offset_x + if 'y' in new_element: + new_element['y'] = new_element['y'] + offset_y + + # Update ID + if 'id' in new_element: + new_element['id'] = id_mapping[new_element['id']] + + # Update group IDs + if 'groupIds' in new_element: + new_element['groupIds'] = [ + group_id_mapping[gid] for gid in new_element['groupIds'] + ] + + # Update binding references if they exist + if 'startBinding' in new_element and new_element['startBinding']: + if 'elementId' in new_element['startBinding']: + old_id = new_element['startBinding']['elementId'] + if old_id in id_mapping: + new_element['startBinding']['elementId'] = id_mapping[old_id] + + if 'endBinding' in new_element and new_element['endBinding']: + if 'elementId' in new_element['endBinding']: + old_id = new_element['endBinding']['elementId'] + if old_id in id_mapping: + new_element['endBinding']['elementId'] = id_mapping[old_id] + + # Update containerId if it exists + if 'containerId' in new_element and new_element['containerId']: + old_id = new_element['containerId'] + if old_id in id_mapping: + new_element['containerId'] = id_mapping[old_id] + + # Update boundElements if they exist + if 'boundElements' in new_element and new_element['boundElements']: + new_bound_elements = [] + for bound_elem in new_element['boundElements']: + if isinstance(bound_elem, dict) and 'id' in bound_elem: + old_id = bound_elem['id'] + if old_id in id_mapping: + bound_elem['id'] = id_mapping[old_id] + new_bound_elements.append(bound_elem) + new_element['boundElements'] = new_bound_elements + + transformed.append(new_element) + + return transformed + + +def load_icon(icon_name: str, library_path: Path) -> List[Dict[str, Any]]: + """ + Load icon elements from library. + + Args: + icon_name: Name of the icon (e.g., "EC2", "VPC") + library_path: Path to the icon library directory + + Returns: + List of icon elements + """ + icon_file = library_path / "icons" / f"{icon_name}.json" + + if not icon_file.exists(): + raise FileNotFoundError(f"Icon file not found: {icon_file}") + + with open(icon_file, 'r', encoding='utf-8') as f: + icon_data = json.load(f) + + return icon_data.get('elements', []) + + +def prepare_edit_path(diagram_path: Path, use_edit_suffix: bool) -> tuple[Path, Path | None]: + """ + Prepare a safe edit path to avoid editor overwrite issues. + + Returns: + (work_path, final_path) + - work_path: file path to read/write during edit + - final_path: file path to rename back to (or None if not used) + """ + if not use_edit_suffix: + return diagram_path, None + + if diagram_path.suffix != ".excalidraw": + return diagram_path, None + + edit_path = diagram_path.with_suffix(diagram_path.suffix + ".edit") + + if diagram_path.exists(): + if edit_path.exists(): + raise FileExistsError(f"Edit file already exists: {edit_path}") + diagram_path.rename(edit_path) + + return edit_path, diagram_path + + +def finalize_edit_path(work_path: Path, final_path: Path | None) -> None: + """Finalize edit by renaming .edit back to .excalidraw if needed.""" + if final_path is None: + return + + if final_path.exists(): + final_path.unlink() + + work_path.rename(final_path) + + +def create_text_label(text: str, x: float, y: float) -> Dict[str, Any]: + """ + Create a text label element. + + Args: + text: Label text + x: X coordinate + y: Y coordinate + + Returns: + Text element dictionary + """ + return { + "id": generate_unique_id(), + "type": "text", + "x": x, + "y": y, + "width": len(text) * 10, # Approximate width + "height": 20, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": None, + "index": "a0", + "roundness": None, + "seed": 1000000000 + hash(text) % 1000000000, + "version": 1, + "versionNonce": 2000000000 + hash(text) % 1000000000, + "isDeleted": False, + "boundElements": [], + "updated": 1738195200000, + "link": None, + "locked": False, + "text": text, + "fontSize": 16, + "fontFamily": 5, # Excalifont + "textAlign": "center", + "verticalAlign": "top", + "containerId": None, + "originalText": text, + "autoResize": True, + "lineHeight": 1.25 + } + + +def add_icon_to_diagram( + diagram_path: Path, + icon_name: str, + x: float, + y: float, + library_path: Path, + label: str = None +) -> None: + """ + Add an icon to an Excalidraw diagram. + + Args: + diagram_path: Path to the Excalidraw diagram file + icon_name: Name of the icon to add + x: Target X coordinate + y: Target Y coordinate + library_path: Path to the icon library directory + label: Optional text label to add below the icon + """ + # Load icon elements + print(f"Loading icon: {icon_name}") + icon_elements = load_icon(icon_name, library_path) + print(f" Loaded {len(icon_elements)} elements") + + # Transform icon elements + print(f"Transforming to position ({x}, {y})") + transformed_elements = transform_icon_elements(icon_elements, x, y) + + # Calculate icon bounding box for label positioning + if label and transformed_elements: + min_x, min_y, max_x, max_y = calculate_bounding_box(transformed_elements) + icon_width = max_x - min_x + icon_height = max_y - min_y + + # Position label below icon, centered + label_x = min_x + (icon_width / 2) - (len(label) * 5) + label_y = max_y + 10 + + label_element = create_text_label(label, label_x, label_y) + transformed_elements.append(label_element) + print(f" Added label: '{label}'") + + # Load diagram + print(f"Loading diagram: {diagram_path}") + with open(diagram_path, 'r', encoding='utf-8') as f: + diagram = json.load(f) + + # Add transformed elements + if 'elements' not in diagram: + diagram['elements'] = [] + + original_count = len(diagram['elements']) + diagram['elements'].extend(transformed_elements) + print(f" Added {len(transformed_elements)} elements (total: {original_count} -> {len(diagram['elements'])})") + + # Save diagram + print(f"Saving diagram") + with open(diagram_path, 'w', encoding='utf-8') as f: + json.dump(diagram, f, indent=2, ensure_ascii=False) + + print(f"✓ Successfully added '{icon_name}' icon to diagram") + + +def main(): + """Main entry point.""" + if len(sys.argv) < 5: + print("Usage: python add-icon-to-diagram.py [OPTIONS]") + print("\nOptions:") + print(" --library-path PATH Path to icon library directory") + print(" --label TEXT Add text label below icon") + print(" --use-edit-suffix Edit via .excalidraw.edit to avoid editor overwrite issues (enabled by default; use --no-use-edit-suffix to disable)") + print("\nExamples:") + print(" python add-icon-to-diagram.py diagram.excalidraw EC2 500 300") + print(" python add-icon-to-diagram.py diagram.excalidraw EC2 500 300 --label 'Web Server'") + sys.exit(1) + + diagram_path = Path(sys.argv[1]) + icon_name = sys.argv[2] + x = float(sys.argv[3]) + y = float(sys.argv[4]) + + # Default library path + script_dir = Path(__file__).parent + default_library_path = script_dir.parent / "libraries" / "aws-architecture-icons" + + # Parse optional arguments + library_path = default_library_path + label = None + # Default: use edit suffix to avoid editor overwrite issues + use_edit_suffix = True + + i = 5 + while i < len(sys.argv): + if sys.argv[i] == '--library-path': + if i + 1 < len(sys.argv): + library_path = Path(sys.argv[i + 1]) + i += 2 + else: + print("Error: --library-path requires a path argument") + sys.exit(1) + elif sys.argv[i] == '--label': + if i + 1 < len(sys.argv): + label = sys.argv[i + 1] + i += 2 + else: + print("Error: --label requires a text argument") + sys.exit(1) + elif sys.argv[i] == '--use-edit-suffix': + use_edit_suffix = True + i += 1 + elif sys.argv[i] == '--no-use-edit-suffix': + use_edit_suffix = False + i += 1 + else: + print(f"Error: Unknown option: {sys.argv[i]}") + sys.exit(1) + + # Validate inputs + if not diagram_path.exists(): + print(f"Error: Diagram file not found: {diagram_path}") + sys.exit(1) + + if not library_path.exists(): + print(f"Error: Library path not found: {library_path}") + sys.exit(1) + + try: + work_path, final_path = prepare_edit_path(diagram_path, use_edit_suffix) + add_icon_to_diagram(work_path, icon_name, x, y, library_path, label) + finalize_edit_path(work_path, final_path) + except Exception as e: + print(f"Error: {e}") + sys.exit(1) + + +if __name__ == '__main__': + main() + diff --git a/skills/excalidraw-diagram-generator/scripts/split-excalidraw-library.py b/skills/excalidraw-diagram-generator/scripts/split-excalidraw-library.py new file mode 100644 index 00000000..ec903dd2 --- /dev/null +++ b/skills/excalidraw-diagram-generator/scripts/split-excalidraw-library.py @@ -0,0 +1,183 @@ +#!/usr/bin/env python3 +""" +Excalidraw Library Splitter + +This script splits an Excalidraw library file (*.excalidrawlib) into individual +icon JSON files and generates a reference.md file for easy lookup. + +The script expects the following structure: + skills/excalidraw-diagram-generator/libraries/{icon-set-name}/ + {icon-set-name}.excalidrawlib (place this file first) + +Usage: + python split-excalidraw-library.py + +Example: + python split-excalidraw-library.py skills/excalidraw-diagram-generator/libraries/aws-architecture-icons/ +""" + +import json +import os +import re +import sys +from pathlib import Path + + +def sanitize_filename(name: str) -> str: + """ + Sanitize icon name to create a valid filename. + + Args: + name: Original icon name + + Returns: + Sanitized filename safe for all platforms + """ + # Replace spaces with hyphens + filename = name.replace(' ', '-') + + # Remove or replace special characters + filename = re.sub(r'[^\w\-.]', '', filename) + + # Remove multiple consecutive hyphens + filename = re.sub(r'-+', '-', filename) + + # Remove leading/trailing hyphens + filename = filename.strip('-') + + return filename + + +def find_library_file(directory: Path) -> Path: + """ + Find the .excalidrawlib file in the given directory. + + Args: + directory: Directory to search + + Returns: + Path to the library file + + Raises: + SystemExit: If no library file or multiple library files found + """ + library_files = list(directory.glob('*.excalidrawlib')) + + if len(library_files) == 0: + print(f"Error: No .excalidrawlib file found in {directory}") + print(f"Please place a .excalidrawlib file in {directory} first.") + sys.exit(1) + + if len(library_files) > 1: + print(f"Error: Multiple .excalidrawlib files found in {directory}") + print(f"Please keep only one library file in {directory}.") + sys.exit(1) + + return library_files[0] + + +def split_library(library_dir: str) -> None: + """ + Split an Excalidraw library file into individual icon files. + + Args: + library_dir: Path to the directory containing the .excalidrawlib file + """ + library_dir = Path(library_dir) + + if not library_dir.exists(): + print(f"Error: Directory not found: {library_dir}") + sys.exit(1) + + if not library_dir.is_dir(): + print(f"Error: Path is not a directory: {library_dir}") + sys.exit(1) + + # Find the library file + library_path = find_library_file(library_dir) + print(f"Found library: {library_path.name}") + + # Load library file + print(f"Loading library data...") + with open(library_path, 'r', encoding='utf-8') as f: + library_data = json.load(f) + + # Validate library structure + if 'libraryItems' not in library_data: + print("Error: Invalid library file format (missing 'libraryItems')") + sys.exit(1) + + # Create icons directory + icons_dir = library_dir / 'icons' + icons_dir.mkdir(exist_ok=True) + print(f"Output directory: {library_dir}") + + # Process each library item (icon) + library_items = library_data['libraryItems'] + icon_list = [] + + print(f"Processing {len(library_items)} icons...") + + for item in library_items: + # Get icon name + icon_name = item.get('name', 'Unnamed') + + # Create sanitized filename + filename = sanitize_filename(icon_name) + '.json' + + # Save icon data + icon_path = icons_dir / filename + with open(icon_path, 'w', encoding='utf-8') as f: + json.dump(item, f, ensure_ascii=False, indent=2) + + # Add to reference list + icon_list.append({ + 'name': icon_name, + 'filename': filename + }) + + print(f" ✓ {icon_name} → {filename}") + + # Sort icon list by name + icon_list.sort(key=lambda x: x['name']) + + # Generate reference.md + library_name = library_path.stem + reference_path = library_dir / 'reference.md' + with open(reference_path, 'w', encoding='utf-8') as f: + f.write(f"# {library_name} Reference\n\n") + f.write(f"This directory contains {len(icon_list)} icons extracted from `{library_path.name}`.\n\n") + f.write("## Available Icons\n\n") + f.write("| Icon Name | Filename |\n") + f.write("|-----------|----------|\n") + + for icon in icon_list: + f.write(f"| {icon['name']} | `icons/{icon['filename']}` |\n") + + f.write("\n## Usage\n\n") + f.write("Each icon JSON file contains the complete `elements` array needed to render that icon in Excalidraw.\n") + f.write("You can copy the elements from these files into your Excalidraw diagrams.\n") + + print(f"\n✅ Successfully split library into {len(icon_list)} icons") + print(f"📄 Reference file created: {reference_path}") + print(f"📁 Icons directory: {icons_dir}") + + +def main(): + """Main entry point.""" + if hasattr(sys.stdout, "reconfigure"): + # Ensure consistent UTF-8 output on Windows consoles. + sys.stdout.reconfigure(encoding="utf-8") + if len(sys.argv) != 2: + print("Usage: python split-excalidraw-library.py ") + print("\nExample:") + print(" python split-excalidraw-library.py skills/excalidraw-diagram-generator/libraries/aws-architecture-icons/") + print("\nNote: The directory should contain a .excalidrawlib file.") + sys.exit(1) + + library_dir = sys.argv[1] + split_library(library_dir) + + +if __name__ == '__main__': + main() diff --git a/skills/excalidraw-diagram-generator/templates/business-flow-swimlane-template.excalidraw b/skills/excalidraw-diagram-generator/templates/business-flow-swimlane-template.excalidraw new file mode 100644 index 00000000..0d0c26b8 --- /dev/null +++ b/skills/excalidraw-diagram-generator/templates/business-flow-swimlane-template.excalidraw @@ -0,0 +1,334 @@ +{ + "type": "excalidraw", + "version": 2, + "source": "https://excalidraw.com", + "elements": [ + { + "id": "title", + "type": "text", + "x": 200, + "y": 50, + "width": 300, + "height": 30, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "a0", + "roundness": null, + "seed": 2001001001, + "version": 1, + "versionNonce": 3002002001, + "isDeleted": false, + "boundElements": null, + "updated": 1706659200000, + "link": null, + "locked": false, + "text": "Business Process Flow", + "fontSize": 24, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "top" + }, + { + "id": "lane-header-1", + "type": "rectangle", + "x": 100, + "y": 120, + "width": 200, + "height": 50, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "#e7f5ff", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "a1", + "roundness": null, + "seed": 2001001002, + "version": 1, + "versionNonce": 3002002002, + "isDeleted": false, + "boundElements": null, + "updated": 1706659200000, + "link": null, + "locked": false, + "text": "Customer", + "fontSize": 18, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle" + }, + { + "id": "lane-1", + "type": "rectangle", + "x": 100, + "y": 170, + "width": 200, + "height": 250, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "a2", + "roundness": null, + "seed": 2001001003, + "version": 1, + "versionNonce": 3002002003, + "isDeleted": false, + "boundElements": null, + "updated": 1706659200000, + "link": null, + "locked": false + }, + { + "id": "process-1", + "type": "rectangle", + "x": 130, + "y": 200, + "width": 140, + "height": 70, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "#b2f2bb", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "a3", + "roundness": { "type": 3 }, + "seed": 2001001004, + "version": 1, + "versionNonce": 3002002004, + "isDeleted": false, + "boundElements": null, + "updated": 1706659200000, + "link": null, + "locked": false, + "text": "Submit\nRequest", + "fontSize": 16, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle" + }, + { + "id": "lane-header-2", + "type": "rectangle", + "x": 300, + "y": 120, + "width": 200, + "height": 50, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "#fff3bf", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "a4", + "roundness": null, + "seed": 2001001005, + "version": 1, + "versionNonce": 3002002005, + "isDeleted": false, + "boundElements": null, + "updated": 1706659200000, + "link": null, + "locked": false, + "text": "Sales Team", + "fontSize": 18, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle" + }, + { + "id": "lane-2", + "type": "rectangle", + "x": 300, + "y": 170, + "width": 200, + "height": 250, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "a5", + "roundness": null, + "seed": 2001001006, + "version": 1, + "versionNonce": 3002002006, + "isDeleted": false, + "boundElements": null, + "updated": 1706659200000, + "link": null, + "locked": false + }, + { + "id": "process-2", + "type": "rectangle", + "x": 330, + "y": 200, + "width": 140, + "height": 70, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "#ffd43b", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "a6", + "roundness": { "type": 3 }, + "seed": 2001001007, + "version": 1, + "versionNonce": 3002002007, + "isDeleted": false, + "boundElements": null, + "updated": 1706659200000, + "link": null, + "locked": false, + "text": "Review\nRequest", + "fontSize": 16, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle" + }, + { + "id": "cross-lane-arrow", + "type": "arrow", + "x": 270, + "y": 235, + "width": 60, + "height": 0, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "a7", + "roundness": { "type": 2 }, + "seed": 2001001008, + "version": 1, + "versionNonce": 3002002008, + "isDeleted": false, + "boundElements": null, + "updated": 1706659200000, + "link": null, + "locked": false, + "points": [ + [0, 0], + [60, 0] + ], + "startBinding": null, + "endBinding": null + }, + { + "id": "process-3", + "type": "rectangle", + "x": 330, + "y": 310, + "width": 140, + "height": 70, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "#ffd43b", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "a8", + "roundness": { "type": 3 }, + "seed": 2001001009, + "version": 1, + "versionNonce": 3002002009, + "isDeleted": false, + "boundElements": null, + "updated": 1706659200000, + "link": null, + "locked": false, + "text": "Approve", + "fontSize": 16, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle" + }, + { + "id": "within-lane-arrow", + "type": "arrow", + "x": 400, + "y": 270, + "width": 0, + "height": 40, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "a9", + "roundness": { "type": 2 }, + "seed": 2001001010, + "version": 1, + "versionNonce": 3002002010, + "isDeleted": false, + "boundElements": null, + "updated": 1706659200000, + "link": null, + "locked": false, + "points": [ + [0, 0], + [0, 40] + ], + "startBinding": null, + "endBinding": null + } + ], + "appState": { + "viewBackgroundColor": "#ffffff", + "gridSize": 20 + }, + "files": {} +} diff --git a/skills/excalidraw-diagram-generator/templates/class-diagram-template.excalidraw b/skills/excalidraw-diagram-generator/templates/class-diagram-template.excalidraw new file mode 100644 index 00000000..aae28dfb --- /dev/null +++ b/skills/excalidraw-diagram-generator/templates/class-diagram-template.excalidraw @@ -0,0 +1,558 @@ +{ + "type": "excalidraw", + "version": 2, + "source": "https://marketplace.visualstudio.com/items?itemName=pomdtr.excalidraw-editor", + "elements": [ + { + "id": "class-1", + "type": "rectangle", + "x": 100, + "y": 100, + "width": 200, + "height": 180, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "#e7f5ff", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "a0", + "roundness": null, + "seed": 3001001001, + "version": 1, + "versionNonce": 4002002001, + "isDeleted": false, + "boundElements": [], + "updated": 1706659200000, + "link": null, + "locked": false + }, + { + "id": "class-name-1", + "type": "text", + "x": 150, + "y": 110, + "width": 100, + "height": 25, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "a1", + "roundness": null, + "seed": 3001001002, + "version": 1, + "versionNonce": 4002002002, + "isDeleted": false, + "boundElements": [], + "updated": 1706659200000, + "link": null, + "locked": false, + "text": "User", + "fontSize": 20, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "top", + "containerId": null, + "originalText": "User", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "id": "separator-1", + "type": "line", + "x": 100, + "y": 145, + "width": 200, + "height": 0, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "a2", + "roundness": null, + "seed": 3001001003, + "version": 1, + "versionNonce": 4002002003, + "isDeleted": false, + "boundElements": [], + "updated": 1706659200000, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 200, + 0 + ] + ], + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null + }, + { + "id": "attributes-1", + "type": "text", + "x": 110, + "y": 155, + "width": 180, + "height": 50, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "a3", + "roundness": null, + "seed": 3001001004, + "version": 1, + "versionNonce": 4002002004, + "isDeleted": false, + "boundElements": [], + "updated": 1706659200000, + "link": null, + "locked": false, + "text": "- id: number\n- name: string\n- email: string", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "- id: number\n- name: string\n- email: string", + "autoResize": true, + "lineHeight": 1.1904761904761905 + }, + { + "id": "separator-2", + "type": "line", + "x": 100, + "y": 215, + "width": 200, + "height": 0, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "a4", + "roundness": null, + "seed": 3001001005, + "version": 1, + "versionNonce": 4002002005, + "isDeleted": false, + "boundElements": [], + "updated": 1706659200000, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 200, + 0 + ] + ], + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null + }, + { + "id": "methods-1", + "type": "text", + "x": 110, + "y": 225, + "width": 180, + "height": 45, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "a5", + "roundness": null, + "seed": 3001001006, + "version": 3, + "versionNonce": 1660402375, + "isDeleted": false, + "boundElements": [], + "updated": 1769755991910, + "link": null, + "locked": false, + "text": "+ login(): void\n+ logout(): void\n+ updateProfile(): void", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "+ login(): void\n+ logout(): void\n+ updateProfile(): void", + "autoResize": true, + "lineHeight": 1.0714285714285714 + }, + { + "id": "class-2", + "type": "rectangle", + "x": 400, + "y": 100, + "width": 200, + "height": 180, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "#fff3bf", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "a6", + "roundness": null, + "seed": 3001001007, + "version": 1, + "versionNonce": 4002002007, + "isDeleted": false, + "boundElements": [], + "updated": 1706659200000, + "link": null, + "locked": false + }, + { + "id": "class-name-2", + "type": "text", + "x": 430, + "y": 110, + "width": 140, + "height": 25, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "a7", + "roundness": null, + "seed": 3001001008, + "version": 1, + "versionNonce": 4002002008, + "isDeleted": false, + "boundElements": [], + "updated": 1706659200000, + "link": null, + "locked": false, + "text": "AdminUser", + "fontSize": 20, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "top", + "containerId": null, + "originalText": "AdminUser", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "id": "separator-3", + "type": "line", + "x": 400, + "y": 145, + "width": 200, + "height": 0, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "a8", + "roundness": null, + "seed": 3001001009, + "version": 1, + "versionNonce": 4002002009, + "isDeleted": false, + "boundElements": [], + "updated": 1706659200000, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 200, + 0 + ] + ], + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null + }, + { + "id": "attributes-2", + "type": "text", + "x": 410, + "y": 155, + "width": 180, + "height": 35, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "a9", + "roundness": null, + "seed": 3001001010, + "version": 1, + "versionNonce": 4002002010, + "isDeleted": false, + "boundElements": [], + "updated": 1706659200000, + "link": null, + "locked": false, + "text": "- role: string\n- permissions: string[]", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "- role: string\n- permissions: string[]", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "id": "separator-4", + "type": "line", + "x": 400, + "y": 200, + "width": 200, + "height": 0, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "aA", + "roundness": null, + "seed": 3001001011, + "version": 2, + "versionNonce": 873024679, + "isDeleted": false, + "boundElements": [], + "updated": 1769755880046, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 200, + 0 + ] + ], + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null + }, + { + "id": "methods-2", + "type": "text", + "x": 410, + "y": 210, + "width": 180, + "height": 60, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "aB", + "roundness": null, + "seed": 3001001012, + "version": 2, + "versionNonce": 1702655305, + "isDeleted": false, + "boundElements": [], + "updated": 1769755880046, + "link": null, + "locked": false, + "text": "+ manageUsers(): void\n+ assignRole(): void\n+ revokePermission(): void", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "+ manageUsers(): void\n+ assignRole(): void\n+ revokePermission(): void", + "autoResize": true, + "lineHeight": 1.4285714285714286 + }, + { + "id": "inheritance-line", + "type": "line", + "x": 400, + "y": 190, + "width": 100, + "height": 0, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "aC", + "roundness": null, + "seed": 3001001013, + "version": 18, + "versionNonce": 1139021225, + "isDeleted": false, + "boundElements": [], + "updated": 1769755989350, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + -100, + 0 + ] + ], + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null + }, + { + "id": "inheritance-triangle", + "type": "line", + "x": 314.1999816894531, + "y": 181.5, + "width": 15, + "height": 15, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "aD", + "roundness": null, + "seed": 3001001014, + "version": 21, + "versionNonce": 1468657767, + "isDeleted": false, + "boundElements": [], + "updated": 1769756005117, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + -15, + 15 + ], + [ + 0, + 15 + ], + [ + 0, + 0 + ] + ], + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null + } + ], + "appState": { + "gridSize": 20, + "gridStep": 5, + "gridModeEnabled": false, + "viewBackgroundColor": "#ffffff" + }, + "files": {} +} \ No newline at end of file diff --git a/skills/excalidraw-diagram-generator/templates/data-flow-diagram-template.excalidraw b/skills/excalidraw-diagram-generator/templates/data-flow-diagram-template.excalidraw new file mode 100644 index 00000000..baea839e --- /dev/null +++ b/skills/excalidraw-diagram-generator/templates/data-flow-diagram-template.excalidraw @@ -0,0 +1,279 @@ +{ + "type": "excalidraw", + "version": 2, + "source": "https://excalidraw.com", + "elements": [ + { + "id": "external-entity-1", + "type": "rectangle", + "x": 100, + "y": 200, + "width": 120, + "height": 80, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "#ffc9c9", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "a0", + "roundness": { "type": 3 }, + "seed": 1001001001, + "version": 1, + "versionNonce": 2002002002, + "isDeleted": false, + "boundElements": null, + "updated": 1706659200000, + "link": null, + "locked": false, + "text": "User", + "fontSize": 18, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle" + }, + { + "id": "data-flow-1", + "type": "arrow", + "x": 220, + "y": 240, + "width": 80, + "height": 0, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "a1", + "roundness": { "type": 2 }, + "seed": 1001001002, + "version": 1, + "versionNonce": 2002002003, + "isDeleted": false, + "boundElements": null, + "updated": 1706659200000, + "link": null, + "locked": false, + "points": [ + [0, 0], + [80, 0] + ], + "startBinding": null, + "endBinding": null + }, + { + "id": "flow-label-1", + "type": "text", + "x": 230, + "y": 220, + "width": 80, + "height": 20, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "a2", + "roundness": null, + "seed": 1001001003, + "version": 1, + "versionNonce": 2002002004, + "isDeleted": false, + "boundElements": null, + "updated": 1706659200000, + "link": null, + "locked": false, + "text": "input data", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "top" + }, + { + "id": "process-1", + "type": "ellipse", + "x": 300, + "y": 200, + "width": 120, + "height": 80, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "#a5d8ff", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "a3", + "roundness": null, + "seed": 1001001004, + "version": 1, + "versionNonce": 2002002005, + "isDeleted": false, + "boundElements": null, + "updated": 1706659200000, + "link": null, + "locked": false, + "text": "Process\nData", + "fontSize": 16, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle" + }, + { + "id": "data-flow-2", + "type": "arrow", + "x": 420, + "y": 240, + "width": 80, + "height": 0, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "a4", + "roundness": { "type": 2 }, + "seed": 1001001005, + "version": 1, + "versionNonce": 2002002006, + "isDeleted": false, + "boundElements": null, + "updated": 1706659200000, + "link": null, + "locked": false, + "points": [ + [0, 0], + [80, 0] + ], + "startBinding": null, + "endBinding": null + }, + { + "id": "flow-label-2", + "type": "text", + "x": 425, + "y": 220, + "width": 100, + "height": 20, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "a5", + "roundness": null, + "seed": 1001001006, + "version": 1, + "versionNonce": 2002002007, + "isDeleted": false, + "boundElements": null, + "updated": 1706659200000, + "link": null, + "locked": false, + "text": "processed data", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "top" + }, + { + "id": "data-store-1", + "type": "rectangle", + "x": 500, + "y": 200, + "width": 150, + "height": 80, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "#96f2d7", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "a6", + "roundness": null, + "seed": 1001001007, + "version": 1, + "versionNonce": 2002002008, + "isDeleted": false, + "boundElements": null, + "updated": 1706659200000, + "link": null, + "locked": false, + "text": "Data Store\n(Database)", + "fontSize": 16, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle" + }, + { + "id": "data-store-line", + "type": "line", + "x": 500, + "y": 225, + "width": 150, + "height": 0, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "a7", + "roundness": null, + "seed": 1001001008, + "version": 1, + "versionNonce": 2002002009, + "isDeleted": false, + "boundElements": null, + "updated": 1706659200000, + "link": null, + "locked": false, + "points": [ + [0, 0], + [150, 0] + ], + "startBinding": null, + "endBinding": null + } + ], + "appState": { + "viewBackgroundColor": "#ffffff", + "gridSize": 20 + }, + "files": {} +} diff --git a/skills/excalidraw-diagram-generator/templates/er-diagram-template.excalidraw b/skills/excalidraw-diagram-generator/templates/er-diagram-template.excalidraw new file mode 100644 index 00000000..a023522c --- /dev/null +++ b/skills/excalidraw-diagram-generator/templates/er-diagram-template.excalidraw @@ -0,0 +1,662 @@ +{ + "type": "excalidraw", + "version": 2, + "source": "https://excalidraw.com", + "elements": [ + { + "id": "entity-1", + "type": "rectangle", + "x": 100, + "y": 150, + "width": 180, + "height": 150, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "#e7f5ff", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "a0", + "roundness": null, + "seed": 5001001001, + "version": 1, + "versionNonce": 6002002001, + "isDeleted": false, + "boundElements": null, + "updated": 1706659200000, + "link": null, + "locked": false + }, + { + "id": "entity-name-1", + "type": "text", + "x": 150, + "y": 160, + "width": 80, + "height": 25, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "a1", + "roundness": null, + "seed": 5001001002, + "version": 1, + "versionNonce": 6002002002, + "isDeleted": false, + "boundElements": null, + "updated": 1706659200000, + "link": null, + "locked": false, + "text": "User", + "fontSize": 20, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "top" + }, + { + "id": "entity-separator-1", + "type": "line", + "x": 100, + "y": 195, + "width": 180, + "height": 0, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "a2", + "roundness": null, + "seed": 5001001003, + "version": 1, + "versionNonce": 6002002003, + "isDeleted": false, + "boundElements": null, + "updated": 1706659200000, + "link": null, + "locked": false, + "points": [ + [0, 0], + [180, 0] + ], + "startBinding": null, + "endBinding": null + }, + { + "id": "attributes-1", + "type": "text", + "x": 110, + "y": 205, + "width": 160, + "height": 80, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "a3", + "roundness": null, + "seed": 5001001004, + "version": 1, + "versionNonce": 6002002004, + "isDeleted": false, + "boundElements": null, + "updated": 1706659200000, + "link": null, + "locked": false, + "text": "PK: user_id\nname\nemail\ncreated_at", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "top" + }, + { + "id": "entity-2", + "type": "rectangle", + "x": 450, + "y": 150, + "width": 180, + "height": 150, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "#fff3bf", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "a4", + "roundness": null, + "seed": 5001001005, + "version": 1, + "versionNonce": 6002002005, + "isDeleted": false, + "boundElements": null, + "updated": 1706659200000, + "link": null, + "locked": false + }, + { + "id": "entity-name-2", + "type": "text", + "x": 500, + "y": 160, + "width": 80, + "height": 25, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "a5", + "roundness": null, + "seed": 5001001006, + "version": 1, + "versionNonce": 6002002006, + "isDeleted": false, + "boundElements": null, + "updated": 1706659200000, + "link": null, + "locked": false, + "text": "Order", + "fontSize": 20, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "top" + }, + { + "id": "entity-separator-2", + "type": "line", + "x": 450, + "y": 195, + "width": 180, + "height": 0, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "a6", + "roundness": null, + "seed": 5001001007, + "version": 1, + "versionNonce": 6002002007, + "isDeleted": false, + "boundElements": null, + "updated": 1706659200000, + "link": null, + "locked": false, + "points": [ + [0, 0], + [180, 0] + ], + "startBinding": null, + "endBinding": null + }, + { + "id": "attributes-2", + "type": "text", + "x": 460, + "y": 205, + "width": 160, + "height": 80, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "a7", + "roundness": null, + "seed": 5001001008, + "version": 1, + "versionNonce": 6002002008, + "isDeleted": false, + "boundElements": null, + "updated": 1706659200000, + "link": null, + "locked": false, + "text": "PK: order_id\nFK: user_id\ntotal_amount\norder_date", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "top" + }, + { + "id": "relationship-line", + "type": "line", + "x": 280, + "y": 225, + "width": 170, + "height": 0, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "a8", + "roundness": null, + "seed": 5001001009, + "version": 1, + "versionNonce": 6002002009, + "isDeleted": false, + "boundElements": null, + "updated": 1706659200000, + "link": null, + "locked": false, + "points": [ + [0, 0], + [170, 0] + ], + "startBinding": null, + "endBinding": null + }, + { + "id": "cardinality-1", + "type": "text", + "x": 290, + "y": 205, + "width": 20, + "height": 20, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "a9", + "roundness": null, + "seed": 5001001010, + "version": 1, + "versionNonce": 6002002010, + "isDeleted": false, + "boundElements": null, + "updated": 1706659200000, + "link": null, + "locked": false, + "text": "1", + "fontSize": 16, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "top" + }, + { + "id": "cardinality-2", + "type": "text", + "x": 420, + "y": 205, + "width": 20, + "height": 20, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "a10", + "roundness": null, + "seed": 5001001011, + "version": 1, + "versionNonce": 6002002011, + "isDeleted": false, + "boundElements": null, + "updated": 1706659200000, + "link": null, + "locked": false, + "text": "N", + "fontSize": 16, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "top" + }, + { + "id": "relationship-label", + "type": "text", + "x": 330, + "y": 200, + "width": 80, + "height": 20, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "a11", + "roundness": null, + "seed": 5001001012, + "version": 1, + "versionNonce": 6002002012, + "isDeleted": false, + "boundElements": null, + "updated": 1706659200000, + "link": null, + "locked": false, + "text": "places", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "top" + }, + { + "id": "entity-3", + "type": "rectangle", + "x": 450, + "y": 380, + "width": 180, + "height": 120, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "#d0f0c0", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "a12", + "roundness": null, + "seed": 5001001013, + "version": 1, + "versionNonce": 6002002013, + "isDeleted": false, + "boundElements": null, + "updated": 1706659200000, + "link": null, + "locked": false + }, + { + "id": "entity-name-3", + "type": "text", + "x": 480, + "y": 390, + "width": 120, + "height": 25, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "a13", + "roundness": null, + "seed": 5001001014, + "version": 1, + "versionNonce": 6002002014, + "isDeleted": false, + "boundElements": null, + "updated": 1706659200000, + "link": null, + "locked": false, + "text": "Product", + "fontSize": 20, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "top" + }, + { + "id": "entity-separator-3", + "type": "line", + "x": 450, + "y": 425, + "width": 180, + "height": 0, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "a14", + "roundness": null, + "seed": 5001001015, + "version": 1, + "versionNonce": 6002002015, + "isDeleted": false, + "boundElements": null, + "updated": 1706659200000, + "link": null, + "locked": false, + "points": [ + [0, 0], + [180, 0] + ], + "startBinding": null, + "endBinding": null + }, + { + "id": "attributes-3", + "type": "text", + "x": 460, + "y": 435, + "width": 160, + "height": 50, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "a15", + "roundness": null, + "seed": 5001001016, + "version": 1, + "versionNonce": 6002002016, + "isDeleted": false, + "boundElements": null, + "updated": 1706659200000, + "link": null, + "locked": false, + "text": "PK: product_id\nname\nprice", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "top" + }, + { + "id": "relationship-line-2", + "type": "line", + "x": 540, + "y": 300, + "width": 0, + "height": 80, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "a16", + "roundness": null, + "seed": 5001001017, + "version": 1, + "versionNonce": 6002002017, + "isDeleted": false, + "boundElements": null, + "updated": 1706659200000, + "link": null, + "locked": false, + "points": [ + [0, 0], + [0, 80] + ], + "startBinding": null, + "endBinding": null + }, + { + "id": "cardinality-3", + "type": "text", + "x": 550, + "y": 310, + "width": 20, + "height": 20, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "a17", + "roundness": null, + "seed": 5001001018, + "version": 1, + "versionNonce": 6002002018, + "isDeleted": false, + "boundElements": null, + "updated": 1706659200000, + "link": null, + "locked": false, + "text": "N", + "fontSize": 16, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "top" + }, + { + "id": "cardinality-4", + "type": "text", + "x": 550, + "y": 350, + "width": 20, + "height": 20, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "a18", + "roundness": null, + "seed": 5001001019, + "version": 1, + "versionNonce": 6002002019, + "isDeleted": false, + "boundElements": null, + "updated": 1706659200000, + "link": null, + "locked": false, + "text": "M", + "fontSize": 16, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "top" + }, + { + "id": "relationship-label-2", + "type": "text", + "x": 490, + "y": 330, + "width": 80, + "height": 20, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "a19", + "roundness": null, + "seed": 5001001020, + "version": 1, + "versionNonce": 6002002020, + "isDeleted": false, + "boundElements": null, + "updated": 1706659200000, + "link": null, + "locked": false, + "text": "contains", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "top" + } + ], + "appState": { + "viewBackgroundColor": "#ffffff", + "gridSize": 20 + }, + "files": {} +} diff --git a/skills/excalidraw-diagram-generator/templates/flowchart-template.excalidraw b/skills/excalidraw-diagram-generator/templates/flowchart-template.excalidraw new file mode 100644 index 00000000..965a3f9c --- /dev/null +++ b/skills/excalidraw-diagram-generator/templates/flowchart-template.excalidraw @@ -0,0 +1,179 @@ +{ + "type": "excalidraw", + "version": 2, + "source": "https://excalidraw.com", + "elements": [ + { + "id": "step1", + "type": "rectangle", + "x": 400, + "y": 200, + "width": 200, + "height": 80, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "#b2f2bb", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "a0", + "roundness": { "type": 3 }, + "seed": 1234567890, + "version": 1, + "versionNonce": 987654321, + "isDeleted": false, + "boundElements": null, + "updated": 1706659200000, + "link": null, + "locked": false, + "text": "Step 1", + "fontSize": 20, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle" + }, + { + "id": "arrow1", + "type": "arrow", + "x": 500, + "y": 280, + "width": 0, + "height": 100, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "a1", + "roundness": { "type": 2 }, + "seed": 1234567891, + "version": 1, + "versionNonce": 987654322, + "isDeleted": false, + "boundElements": null, + "updated": 1706659200000, + "link": null, + "locked": false, + "points": [ + [0, 0], + [0, 100] + ], + "startBinding": null, + "endBinding": null + }, + { + "id": "step2", + "type": "rectangle", + "x": 400, + "y": 380, + "width": 200, + "height": 80, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "#b2f2bb", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "a2", + "roundness": { "type": 3 }, + "seed": 1234567892, + "version": 1, + "versionNonce": 987654323, + "isDeleted": false, + "boundElements": null, + "updated": 1706659200000, + "link": null, + "locked": false, + "text": "Step 2", + "fontSize": 20, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle" + }, + { + "id": "arrow2", + "type": "arrow", + "x": 500, + "y": 460, + "width": 0, + "height": 100, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "a3", + "roundness": { "type": 2 }, + "seed": 1234567893, + "version": 1, + "versionNonce": 987654324, + "isDeleted": false, + "boundElements": null, + "updated": 1706659200000, + "link": null, + "locked": false, + "points": [ + [0, 0], + [0, 100] + ], + "startBinding": null, + "endBinding": null + }, + { + "id": "step3", + "type": "rectangle", + "x": 400, + "y": 560, + "width": 200, + "height": 80, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "#b2f2bb", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "a4", + "roundness": { "type": 3 }, + "seed": 1234567894, + "version": 1, + "versionNonce": 987654325, + "isDeleted": false, + "boundElements": null, + "updated": 1706659200000, + "link": null, + "locked": false, + "text": "Step 3", + "fontSize": 20, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle" + } + ], + "appState": { + "viewBackgroundColor": "#ffffff", + "gridSize": 20 + }, + "files": {} +} diff --git a/skills/excalidraw-diagram-generator/templates/mindmap-template.excalidraw b/skills/excalidraw-diagram-generator/templates/mindmap-template.excalidraw new file mode 100644 index 00000000..53e382c4 --- /dev/null +++ b/skills/excalidraw-diagram-generator/templates/mindmap-template.excalidraw @@ -0,0 +1,244 @@ +{ + "type": "excalidraw", + "version": 2, + "source": "https://marketplace.visualstudio.com/items?itemName=pomdtr.excalidraw-editor", + "elements": [ + { + "id": "center", + "type": "rectangle", + "x": 500, + "y": 350, + "width": 200, + "height": 100, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "#ffd43b", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "a0", + "roundness": { + "type": 3 + }, + "seed": 3333333333, + "version": 3, + "versionNonce": 641024845, + "isDeleted": false, + "boundElements": [ + { + "id": "arrow1", + "type": "arrow" + }, + { + "id": "arrow2", + "type": "arrow" + } + ], + "updated": 1769755916717, + "link": null, + "locked": false, + "text": "Central Topic", + "fontSize": 20, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle" + }, + { + "id": "branch1", + "type": "rectangle", + "x": 250, + "y": 150, + "width": 150, + "height": 80, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "#96f2d7", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "a1", + "roundness": { + "type": 3 + }, + "seed": 3333333334, + "version": 2, + "versionNonce": 2040232045, + "isDeleted": false, + "boundElements": [ + { + "id": "arrow1", + "type": "arrow" + } + ], + "updated": 1769755912840, + "link": null, + "locked": false, + "text": "Branch 1", + "fontSize": 18, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle" + }, + { + "id": "arrow1", + "type": "arrow", + "x": 600, + "y": 350, + "width": 246.39999389648438, + "height": 111.20001220703125, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "a2", + "roundness": { + "type": 2 + }, + "seed": 3333333335, + "version": 23, + "versionNonce": 308894189, + "isDeleted": false, + "boundElements": [], + "updated": 1769755914127, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + -246.39999389648438, + -111.20001220703125 + ] + ], + "startBinding": { + "elementId": "center", + "focus": 0.5255972360761778, + "gap": 1 + }, + "endBinding": { + "elementId": "branch1", + "focus": 0.48604063201707415, + "gap": 8.79998779296875 + }, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow" + }, + { + "id": "branch2", + "type": "rectangle", + "x": 750, + "y": 150, + "width": 150, + "height": 80, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "#96f2d7", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "a3", + "roundness": { + "type": 3 + }, + "seed": 3333333336, + "version": 2, + "versionNonce": 1459929741, + "isDeleted": false, + "boundElements": [ + { + "id": "arrow2", + "type": "arrow" + } + ], + "updated": 1769755916716, + "link": null, + "locked": false, + "text": "Branch 2", + "fontSize": 18, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle" + }, + { + "id": "arrow2", + "type": "arrow", + "x": 600, + "y": 350, + "width": 216, + "height": 112.80001831054688, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "a4", + "roundness": { + "type": 2 + }, + "seed": 3333333337, + "version": 41, + "versionNonce": 1447859213, + "isDeleted": false, + "boundElements": [], + "updated": 1769756030188, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 216, + -112.80001831054688 + ] + ], + "startBinding": { + "elementId": "center", + "focus": -0.48913039421990545, + "gap": 1 + }, + "endBinding": { + "elementId": "branch2", + "focus": -0.5368418212214556, + "gap": 7.199981689453125 + }, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow" + } + ], + "appState": { + "gridSize": 20, + "gridStep": 5, + "gridModeEnabled": false, + "viewBackgroundColor": "#ffffff" + }, + "files": {} +} \ No newline at end of file diff --git a/skills/excalidraw-diagram-generator/templates/relationship-template.excalidraw b/skills/excalidraw-diagram-generator/templates/relationship-template.excalidraw new file mode 100644 index 00000000..b2ea0b6a --- /dev/null +++ b/skills/excalidraw-diagram-generator/templates/relationship-template.excalidraw @@ -0,0 +1,145 @@ +{ + "type": "excalidraw", + "version": 2, + "source": "https://excalidraw.com", + "elements": [ + { + "id": "entity1", + "type": "rectangle", + "x": 300, + "y": 300, + "width": 180, + "height": 100, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "#a5d8ff", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "a0", + "roundness": { "type": 3 }, + "seed": 1111111111, + "version": 1, + "versionNonce": 2222222222, + "isDeleted": false, + "boundElements": null, + "updated": 1706659200000, + "link": null, + "locked": false, + "text": "Entity A", + "fontSize": 20, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle" + }, + { + "id": "entity2", + "type": "rectangle", + "x": 600, + "y": 300, + "width": 180, + "height": 100, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "#a5d8ff", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "a1", + "roundness": { "type": 3 }, + "seed": 1111111112, + "version": 1, + "versionNonce": 2222222223, + "isDeleted": false, + "boundElements": null, + "updated": 1706659200000, + "link": null, + "locked": false, + "text": "Entity B", + "fontSize": 20, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle" + }, + { + "id": "relationship", + "type": "arrow", + "x": 480, + "y": 350, + "width": 120, + "height": 0, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "a2", + "roundness": { "type": 2 }, + "seed": 1111111113, + "version": 1, + "versionNonce": 2222222224, + "isDeleted": false, + "boundElements": null, + "updated": 1706659200000, + "link": null, + "locked": false, + "points": [ + [0, 0], + [120, 0] + ], + "startBinding": null, + "endBinding": null + }, + { + "id": "label", + "type": "text", + "x": 510, + "y": 325, + "width": 60, + "height": 24, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "a3", + "roundness": null, + "seed": 1111111114, + "version": 1, + "versionNonce": 2222222225, + "isDeleted": false, + "boundElements": null, + "updated": 1706659200000, + "link": null, + "locked": false, + "text": "relates to", + "fontSize": 16, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "top" + } + ], + "appState": { + "viewBackgroundColor": "#ffffff", + "gridSize": 20 + }, + "files": {} +} diff --git a/skills/excalidraw-diagram-generator/templates/sequence-diagram-template.excalidraw b/skills/excalidraw-diagram-generator/templates/sequence-diagram-template.excalidraw new file mode 100644 index 00000000..6602ae26 --- /dev/null +++ b/skills/excalidraw-diagram-generator/templates/sequence-diagram-template.excalidraw @@ -0,0 +1,509 @@ +{ + "type": "excalidraw", + "version": 2, + "source": "https://excalidraw.com", + "elements": [ + { + "id": "object-1", + "type": "rectangle", + "x": 150, + "y": 100, + "width": 120, + "height": 50, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "#e7f5ff", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "a0", + "roundness": null, + "seed": 4001001001, + "version": 1, + "versionNonce": 5002002001, + "isDeleted": false, + "boundElements": null, + "updated": 1706659200000, + "link": null, + "locked": false, + "text": "Client", + "fontSize": 18, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle" + }, + { + "id": "lifeline-1", + "type": "line", + "x": 210, + "y": 150, + "width": 0, + "height": 300, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "dashed", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "a1", + "roundness": null, + "seed": 4001001002, + "version": 1, + "versionNonce": 5002002002, + "isDeleted": false, + "boundElements": null, + "updated": 1706659200000, + "link": null, + "locked": false, + "points": [ + [0, 0], + [0, 300] + ], + "startBinding": null, + "endBinding": null + }, + { + "id": "object-2", + "type": "rectangle", + "x": 350, + "y": 100, + "width": 120, + "height": 50, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "#fff3bf", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "a2", + "roundness": null, + "seed": 4001001003, + "version": 1, + "versionNonce": 5002002003, + "isDeleted": false, + "boundElements": null, + "updated": 1706659200000, + "link": null, + "locked": false, + "text": "Server", + "fontSize": 18, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle" + }, + { + "id": "lifeline-2", + "type": "line", + "x": 410, + "y": 150, + "width": 0, + "height": 300, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "dashed", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "a3", + "roundness": null, + "seed": 4001001004, + "version": 1, + "versionNonce": 5002002004, + "isDeleted": false, + "boundElements": null, + "updated": 1706659200000, + "link": null, + "locked": false, + "points": [ + [0, 0], + [0, 300] + ], + "startBinding": null, + "endBinding": null + }, + { + "id": "object-3", + "type": "rectangle", + "x": 550, + "y": 100, + "width": 120, + "height": 50, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "#d0f0c0", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "a4", + "roundness": null, + "seed": 4001001005, + "version": 1, + "versionNonce": 5002002005, + "isDeleted": false, + "boundElements": null, + "updated": 1706659200000, + "link": null, + "locked": false, + "text": "Database", + "fontSize": 18, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle" + }, + { + "id": "lifeline-3", + "type": "line", + "x": 610, + "y": 150, + "width": 0, + "height": 300, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "dashed", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "a5", + "roundness": null, + "seed": 4001001006, + "version": 1, + "versionNonce": 5002002006, + "isDeleted": false, + "boundElements": null, + "updated": 1706659200000, + "link": null, + "locked": false, + "points": [ + [0, 0], + [0, 300] + ], + "startBinding": null, + "endBinding": null + }, + { + "id": "message-1", + "type": "arrow", + "x": 210, + "y": 200, + "width": 200, + "height": 0, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "a6", + "roundness": { "type": 2 }, + "seed": 4001001007, + "version": 1, + "versionNonce": 5002002007, + "isDeleted": false, + "boundElements": null, + "updated": 1706659200000, + "link": null, + "locked": false, + "points": [ + [0, 0], + [200, 0] + ], + "startBinding": null, + "endBinding": null + }, + { + "id": "message-label-1", + "type": "text", + "x": 250, + "y": 180, + "width": 120, + "height": 20, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "a7", + "roundness": null, + "seed": 4001001008, + "version": 1, + "versionNonce": 5002002008, + "isDeleted": false, + "boundElements": null, + "updated": 1706659200000, + "link": null, + "locked": false, + "text": "1: request()", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "top" + }, + { + "id": "activation-1", + "type": "rectangle", + "x": 405, + "y": 200, + "width": 10, + "height": 80, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "#ffd43b", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "a8", + "roundness": null, + "seed": 4001001009, + "version": 1, + "versionNonce": 5002002009, + "isDeleted": false, + "boundElements": null, + "updated": 1706659200000, + "link": null, + "locked": false + }, + { + "id": "message-2", + "type": "arrow", + "x": 415, + "y": 230, + "width": 195, + "height": 0, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "a9", + "roundness": { "type": 2 }, + "seed": 4001001010, + "version": 1, + "versionNonce": 5002002010, + "isDeleted": false, + "boundElements": null, + "updated": 1706659200000, + "link": null, + "locked": false, + "points": [ + [0, 0], + [195, 0] + ], + "startBinding": null, + "endBinding": null + }, + { + "id": "message-label-2", + "type": "text", + "x": 450, + "y": 210, + "width": 120, + "height": 20, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "a10", + "roundness": null, + "seed": 4001001011, + "version": 1, + "versionNonce": 5002002011, + "isDeleted": false, + "boundElements": null, + "updated": 1706659200000, + "link": null, + "locked": false, + "text": "2: query()", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "top" + }, + { + "id": "return-message-1", + "type": "arrow", + "x": 610, + "y": 250, + "width": 195, + "height": 0, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "dashed", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "a11", + "roundness": { "type": 2 }, + "seed": 4001001012, + "version": 1, + "versionNonce": 5002002012, + "isDeleted": false, + "boundElements": null, + "updated": 1706659200000, + "link": null, + "locked": false, + "points": [ + [0, 0], + [-195, 0] + ], + "startBinding": null, + "endBinding": null + }, + { + "id": "return-label-1", + "type": "text", + "x": 450, + "y": 255, + "width": 120, + "height": 20, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "a12", + "roundness": null, + "seed": 4001001013, + "version": 1, + "versionNonce": 5002002013, + "isDeleted": false, + "boundElements": null, + "updated": 1706659200000, + "link": null, + "locked": false, + "text": "3: result", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "top" + }, + { + "id": "return-message-2", + "type": "arrow", + "x": 410, + "y": 280, + "width": 200, + "height": 0, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "dashed", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "a13", + "roundness": { "type": 2 }, + "seed": 4001001014, + "version": 1, + "versionNonce": 5002002014, + "isDeleted": false, + "boundElements": null, + "updated": 1706659200000, + "link": null, + "locked": false, + "points": [ + [0, 0], + [-200, 0] + ], + "startBinding": null, + "endBinding": null + }, + { + "id": "return-label-2", + "type": "text", + "x": 250, + "y": 285, + "width": 120, + "height": 20, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "a14", + "roundness": null, + "seed": 4001001015, + "version": 1, + "versionNonce": 5002002015, + "isDeleted": false, + "boundElements": null, + "updated": 1706659200000, + "link": null, + "locked": false, + "text": "4: response", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "top" + } + ], + "appState": { + "viewBackgroundColor": "#ffffff", + "gridSize": 20 + }, + "files": {} +} diff --git a/skills/penpot-uiux-design/SKILL.md b/skills/penpot-uiux-design/SKILL.md new file mode 100644 index 00000000..b8f589e4 --- /dev/null +++ b/skills/penpot-uiux-design/SKILL.md @@ -0,0 +1,342 @@ +--- +name: penpot-uiux-design +description: 'Comprehensive guide for creating professional UI/UX designs in Penpot using MCP tools. Use this skill when: (1) Creating new UI/UX designs for web, mobile, or desktop applications, (2) Building design systems with components and tokens, (3) Designing dashboards, forms, navigation, or landing pages, (4) Applying accessibility standards and best practices, (5) Following platform guidelines (iOS, Android, Material Design), (6) Reviewing or improving existing Penpot designs for usability. Triggers: "design a UI", "create interface", "build layout", "design dashboard", "create form", "design landing page", "make it accessible", "design system", "component library".' +--- + +# Penpot UI/UX Design Guide + +Create professional, user-centered designs in Penpot using the `penpot/penpot-mcp` MCP server and proven UI/UX principles. + +## Available MCP Tools + +| Tool | Purpose | +| ---- | ------- | +| `mcp__penpot__execute_code` | Run JavaScript in Penpot plugin context to create/modify designs | +| `mcp__penpot__export_shape` | Export shapes as PNG/SVG for visual inspection | +| `mcp__penpot__import_image` | Import images (icons, photos, logos) into designs | +| `mcp__penpot__penpot_api_info` | Retrieve Penpot API documentation | + +## MCP Server Setup + +The Penpot MCP tools require the `penpot/penpot-mcp` server running locally. For detailed installation and troubleshooting, see [setup-troubleshooting.md](references/setup-troubleshooting.md). + +### Before Setup: Check If Already Running + +**Always check if the MCP server is already available before attempting setup:** + +1. **Try calling a tool first**: Attempt `mcp__penpot__penpot_api_info` - if it succeeds, the server is running and connected. No setup needed. + +2. **If the tool fails**, ask the user: + > "The Penpot MCP server doesn't appear to be connected. Is the server already installed and running? If so, I can help troubleshoot. If not, I can guide you through the setup." + +3. **Only proceed with setup instructions if the user confirms the server is not installed.** + +### Quick Start (Only If Not Installed) + +```bash +# Clone and install +git clone https://github.com/penpot/penpot-mcp.git +cd penpot-mcp +npm install + +# Build and start servers +npm run bootstrap +``` + +Then in Penpot: +1. Open a design file +2. Go to **Plugins** → **Load plugin from URL** +3. Enter: `http://localhost:4400/manifest.json` +4. Click **"Connect to MCP server"** in the plugin UI + +### VS Code Configuration + +Add to `settings.json`: +```json +{ + "mcp": { + "servers": { + "penpot": { + "url": "http://localhost:4401/sse" + } + } + } +} +``` + +### Troubleshooting (If Server Is Installed But Not Working) + +| Issue | Solution | +| ----- | -------- | +| Plugin won't connect | Check servers are running (`npm run start:all` in penpot-mcp dir) | +| Browser blocks localhost | Allow local network access prompt, or disable Brave Shield, or try Firefox | +| Tools not appearing in client | Restart VS Code/Claude completely after config changes | +| Tool execution fails/times out | Ensure Penpot plugin UI is open and shows "Connected" | +| "WebSocket connection failed" | Check firewall allows ports 4400, 4401, 4402 | + +## Quick Reference + +| Task | Reference File | +| ---- | -------------- | +| MCP server installation & troubleshooting | [setup-troubleshooting.md](references/setup-troubleshooting.md) | +| Component specs (buttons, forms, nav) | [component-patterns.md](references/component-patterns.md) | +| Accessibility (contrast, touch targets) | [accessibility.md](references/accessibility.md) | +| Screen sizes & platform specs | [platform-guidelines.md](references/platform-guidelines.md) | + +## Core Design Principles + +### The Golden Rules + +1. **Clarity over cleverness**: Every element must have a purpose +2. **Consistency builds trust**: Reuse patterns, colors, and components +3. **User goals first**: Design for tasks, not features +4. **Accessibility is not optional**: Design for everyone +5. **Test with real users**: Validate assumptions early + +### Visual Hierarchy (Priority Order) + +1. **Size**: Larger = more important +2. **Color/Contrast**: High contrast draws attention +3. **Position**: Top-left (LTR) gets seen first +4. **Whitespace**: Isolation emphasizes importance +5. **Typography weight**: Bold stands out + +## Design Workflow + +1. **Check for design system first**: Ask user if they have existing tokens/specs, or discover from current Penpot file +2. **Understand the page**: Call `mcp__penpot__execute_code` with `penpotUtils.shapeStructure()` to see hierarchy +3. **Find elements**: Use `penpotUtils.findShapes()` to locate elements by type or name +4. **Create/modify**: Use `penpot.createBoard()`, `penpot.createRectangle()`, `penpot.createText()` etc. +5. **Apply layout**: Use `addFlexLayout()` for responsive containers +6. **Validate**: Call `mcp__penpot__export_shape` to visually check your work + +## Design System Handling + +**Before creating designs, determine if the user has an existing design system:** + +1. **Ask the user**: "Do you have a design system or brand guidelines to follow?" +2. **Discover from Penpot**: Check for existing components, colors, and patterns + +```javascript +// Discover existing design patterns in current file +const allShapes = penpotUtils.findShapes(() => true, penpot.root); + +// Find existing colors in use +const colors = new Set(); +allShapes.forEach(s => { + if (s.fills) s.fills.forEach(f => colors.add(f.fillColor)); +}); + +// Find existing text styles (font sizes, weights) +const textStyles = allShapes + .filter(s => s.type === 'text') + .map(s => ({ fontSize: s.fontSize, fontWeight: s.fontWeight })); + +// Find existing components +const components = penpot.library.local.components; + +return { colors: [...colors], textStyles, componentCount: components.length }; +``` + +**If user HAS a design system:** + +- Use their specified colors, spacing, typography +- Match their existing component patterns +- Follow their naming conventions + +**If user has NO design system:** + +- Use the default tokens below as a starting point +- Offer to help establish consistent patterns +- Reference specs in [component-patterns.md](references/component-patterns.md) + +## Key Penpot API Gotchas + +- `width`/`height` are READ-ONLY → use `shape.resize(w, h)` +- `parentX`/`parentY` are READ-ONLY → use `penpotUtils.setParentXY(shape, x, y)` +- Use `insertChild(index, shape)` for z-ordering (not `appendChild`) +- Flex children array order is REVERSED for `dir="column"` or `dir="row"` +- After `text.resize()`, reset `growType` to `"auto-width"` or `"auto-height"` + +## Positioning New Boards + +**Always check existing boards before creating new ones** to avoid overlap: + +```javascript +// Find all existing boards and calculate next position +const boards = penpotUtils.findShapes(s => s.type === 'board', penpot.root); +let nextX = 0; +const gap = 100; // Space between boards + +if (boards.length > 0) { + // Find rightmost board edge + boards.forEach(b => { + const rightEdge = b.x + b.width; + if (rightEdge + gap > nextX) { + nextX = rightEdge + gap; + } + }); +} + +// Create new board at calculated position +const newBoard = penpot.createBoard(); +newBoard.x = nextX; +newBoard.y = 0; +newBoard.resize(375, 812); +``` + +**Board spacing guidelines:** + +- Use 100px gap between related screens (same flow) +- Use 200px+ gap between different sections/flows +- Align boards vertically (same y) for visual organization +- Group related screens horizontally in user flow order + +## Default Design Tokens + +**Use these defaults only when user has no design system. Always prefer user's tokens if available.** + +### Spacing Scale (8px base) + +| Token | Value | Usage | +| ----- | ----- | ----- | +| `spacing-xs` | 4px | Tight inline elements | +| `spacing-sm` | 8px | Related elements | +| `spacing-md` | 16px | Default padding | +| `spacing-lg` | 24px | Section spacing | +| `spacing-xl` | 32px | Major sections | +| `spacing-2xl` | 48px | Page-level spacing | + +### Typography Scale + +| Level | Size | Weight | Usage | +| ----- | ---- | ------ | ----- | +| Display | 48-64px | Bold | Hero headlines | +| H1 | 32-40px | Bold | Page titles | +| H2 | 24-28px | Semibold | Section headers | +| H3 | 20-22px | Semibold | Subsections | +| Body | 16px | Regular | Main content | +| Small | 14px | Regular | Secondary text | +| Caption | 12px | Regular | Labels, hints | + +### Color Usage + +| Purpose | Recommendation | +| ------- | -------------- | +| Primary | Main brand color, CTAs | +| Secondary | Supporting actions | +| Success | #22C55E range (confirmations) | +| Warning | #F59E0B range (caution) | +| Error | #EF4444 range (errors) | +| Neutral | Gray scale for text/borders | + +## Common Layouts + +### Mobile Screen (375×812) + +```text +┌─────────────────────────────┐ +│ Status Bar (44px) │ +├─────────────────────────────┤ +│ Header/Nav (56px) │ +├─────────────────────────────┤ +│ │ +│ Content Area │ +│ (Scrollable) │ +│ Padding: 16px horizontal │ +│ │ +├─────────────────────────────┤ +│ Bottom Nav/CTA (84px) │ +└─────────────────────────────┘ + +``` + +### Desktop Dashboard (1440×900) + +```text +┌──────┬──────────────────────────────────┐ +│ │ Header (64px) │ +│ Side │──────────────────────────────────│ +│ bar │ Page Title + Actions │ +│ │──────────────────────────────────│ +│ 240 │ Content Grid │ +│ px │ ┌─────┐ ┌─────┐ ┌─────┐ ┌─────┐ │ +│ │ │Card │ │Card │ │Card │ │Card │ │ +│ │ └─────┘ └─────┘ └─────┘ └─────┘ │ +│ │ │ +└──────┴──────────────────────────────────┘ + +``` + +## Component Checklist + +### Buttons + +- [ ] Clear, action-oriented label (2-3 words) +- [ ] Minimum touch target: 44×44px +- [ ] Visual states: default, hover, active, disabled, loading +- [ ] Sufficient contrast (3:1 against background) +- [ ] Consistent border radius across app + +### Forms + +- [ ] Labels above inputs (not just placeholders) +- [ ] Required field indicators +- [ ] Error messages adjacent to fields +- [ ] Logical tab order +- [ ] Input types match content (email, tel, etc.) + +### Navigation + +- [ ] Current location clearly indicated +- [ ] Consistent position across screens +- [ ] Maximum 7±2 top-level items +- [ ] Touch-friendly on mobile (48px targets) + +## Accessibility Quick Checks + +1. **Color contrast**: Text 4.5:1, Large text 3:1 +2. **Touch targets**: Minimum 44×44px +3. **Focus states**: Visible keyboard focus indicators +4. **Alt text**: Meaningful descriptions for images +5. **Hierarchy**: Proper heading levels (H1→H2→H3) +6. **Color independence**: Never rely solely on color + +## Design Review Checklist + +Before finalizing any design: + +- [ ] Visual hierarchy is clear +- [ ] Consistent spacing and alignment +- [ ] Typography is readable (16px+ body text) +- [ ] Color contrast meets WCAG AA +- [ ] Interactive elements are obvious +- [ ] Mobile-friendly touch targets +- [ ] Loading/empty/error states considered +- [ ] Consistent with design system + +## Validating Designs + +Use these validation approaches with `mcp__penpot__execute_code`: + +| Check | Method | +| ----- | ------ | +| Elements outside bounds | `penpotUtils.analyzeDescendants()` with `isContainedIn()` | +| Text too small (<12px) | `penpotUtils.findShapes()` filtering by `fontSize` | +| Missing contrast | Call `mcp__penpot__export_shape` and visually inspect | +| Hierarchy structure | `penpotUtils.shapeStructure()` to review nesting | + +### Export CSS + +Use `penpot.generateStyle(selection, { type: 'css', includeChildren: true })` via `mcp__penpot__execute_code` to extract CSS from designs. + +## Tips for Great Designs + +1. **Start with content**: Real content reveals layout needs +2. **Design mobile-first**: Constraints breed creativity +3. **Use a grid**: 8px base grid keeps things aligned +4. **Limit colors**: 1 primary + 1 secondary + neutrals +5. **Limit fonts**: 1-2 typefaces maximum +6. **Embrace whitespace**: Breathing room improves comprehension +7. **Be consistent**: Same action = same appearance everywhere +8. **Provide feedback**: Every action needs a response diff --git a/skills/penpot-uiux-design/references/accessibility.md b/skills/penpot-uiux-design/references/accessibility.md new file mode 100644 index 00000000..2482b9da --- /dev/null +++ b/skills/penpot-uiux-design/references/accessibility.md @@ -0,0 +1,329 @@ +# Accessibility Guidelines Reference (WCAG) + +## Quick Compliance Checklist + +### Level AA Requirements (Minimum Standard) + +- [ ] Color contrast 4.5:1 for normal text +- [ ] Color contrast 3:1 for large text (18px+ or 14px bold) +- [ ] Touch targets minimum 44×44px +- [ ] All functionality available via keyboard +- [ ] Visible focus indicators +- [ ] No content flashes more than 3 times/second +- [ ] Page has descriptive title +- [ ] Link purpose clear from text +- [ ] Form inputs have labels +- [ ] Error messages are descriptive + +--- + +## Color and Contrast + +### Contrast Ratios + +| Element | Minimum Ratio | Enhanced (AAA) | +| ------- | ------------- | -------------- | +| Body text | 4.5:1 | 7:1 | +| Large text (18px+) | 3:1 | 4.5:1 | +| UI components | 3:1 | - | +| Graphical objects | 3:1 | - | + +### Color Independence + +Never use color as the only means of conveying information: + +```text +✗ Error fields shown only in red +✓ Error fields with red border + error icon + text message + +✗ Required fields marked only with red asterisk +✓ Required fields labeled "(required)" or with icon + tooltip + +✗ Status shown only by color dots +✓ Status with color + icon + label text + +``` + +### Accessible Color Combinations + +**Safe text colors on backgrounds:** + +| Background | Text Color | Contrast | +| ---------- | ---------- | -------- | +| White (#FFFFFF) | Dark gray (#1F2937) | 15.5:1 ✓ | +| Light gray (#F3F4F6) | Dark gray (#374151) | 10.9:1 ✓ | +| Primary blue (#2563EB) | White (#FFFFFF) | 4.6:1 ✓ | +| Dark (#111827) | White (#FFFFFF) | 18.1:1 ✓ | + +**Colors to avoid for text:** + +- Yellow on white (insufficient contrast) +- Light gray on white +- Orange on white (marginal at best) + +--- + +## Keyboard Navigation + +### Requirements + +1. **All interactive elements** must be reachable via Tab key +2. **Logical tab order** following visual layout +3. **No keyboard traps** (user can always Tab away) +4. **Focus visible** at all times during keyboard navigation +5. **Skip links** to bypass repetitive navigation + +### Focus Indicators + +```css +/* Example focus styles */ +:focus { + outline: 2px solid #2563EB; + outline-offset: 2px; +} + +:focus:not(:focus-visible) { + outline: none; /* Hide for mouse users */ +} + +:focus-visible { + outline: 2px solid #2563EB; + outline-offset: 2px; +} + +``` + +### Keyboard Shortcuts + +| Key | Expected Behavior | +| --- | ----------------- | +| Tab | Move to next interactive element | +| Shift+Tab | Move to previous element | +| Enter | Activate button/link | +| Space | Activate button, toggle checkbox | +| Escape | Close modal/dropdown | +| Arrow keys | Navigate within components | + +--- + +## Screen Reader Support + +### Semantic HTML Elements + +Use appropriate elements for their purpose: + +| Purpose | Element | Not This | +| ------- | ------- | -------- | +| Navigation | `