Loading source
Pulling the file list, source metadata, and syntax-aware rendering for this listing.
Source from repo
Generate Excalidraw .excalidraw JSON diagram files from natural language descriptions of processes and systems.
Files
Skill
Size
Entrypoint
Format
Open file
Syntax-highlighted preview of this file as included in the skill package.
scripts/add-arrow.py
1#!/usr/bin/env python32"""3Add arrows (connections) between elements in Excalidraw diagrams.45Usage:6python add-arrow.py <diagram_path> <from_x> <from_y> <to_x> <to_y> [OPTIONS]78Options:9--style {solid|dashed|dotted} Arrow line style (default: solid)10--color HEX Arrow color (default: #1e1e1e)11--label TEXT Add text label on the arrow12--use-edit-suffix Edit via .excalidraw.edit to avoid editor overwrite issues (enabled by default; use --no-use-edit-suffix to disable)1314Examples:15python add-arrow.py diagram.excalidraw 300 200 500 30016python add-arrow.py diagram.excalidraw 300 200 500 300 --label "HTTP"17python add-arrow.py diagram.excalidraw 300 200 500 300 --style dashed --color "#7950f2"18python add-arrow.py diagram.excalidraw 300 200 500 300 --use-edit-suffix19"""2021import json22import sys23import uuid24from pathlib import Path25from typing import Dict, Any262728def generate_unique_id() -> str:29"""Generate a unique ID for Excalidraw elements."""30return str(uuid.uuid4()).replace('-', '')[:16]313233def prepare_edit_path(diagram_path: Path, use_edit_suffix: bool) -> tuple[Path, Path | None]:34"""35Prepare a safe edit path to avoid editor overwrite issues.3637Returns:38(work_path, final_path)39- work_path: file path to read/write during edit40- final_path: file path to rename back to (or None if not used)41"""42if not use_edit_suffix:43return diagram_path, None4445if diagram_path.suffix != ".excalidraw":46return diagram_path, None4748edit_path = diagram_path.with_suffix(diagram_path.suffix + ".edit")4950if diagram_path.exists():51if edit_path.exists():52raise FileExistsError(f"Edit file already exists: {edit_path}")53diagram_path.rename(edit_path)5455return edit_path, diagram_path565758def finalize_edit_path(work_path: Path, final_path: Path | None) -> None:59"""Finalize edit by renaming .edit back to .excalidraw if needed."""60if final_path is None:61return6263if final_path.exists():64final_path.unlink()6566work_path.rename(final_path)676869def create_arrow(70from_x: float,71from_y: float,72to_x: float,73to_y: float,74style: str = "solid",75color: str = "#1e1e1e",76label: str = None77) -> list:78"""79Create an arrow element.8081Args:82from_x: Starting X coordinate83from_y: Starting Y coordinate84to_x: Ending X coordinate85to_y: Ending Y coordinate86style: Line style (solid, dashed, dotted)87color: Arrow color88label: Optional text label on the arrow8990Returns:91List of elements (arrow and optional label)92"""93elements = []9495# Arrow element96arrow = {97"id": generate_unique_id(),98"type": "arrow",99"x": from_x,100"y": from_y,101"width": to_x - from_x,102"height": to_y - from_y,103"angle": 0,104"strokeColor": color,105"backgroundColor": "transparent",106"fillStyle": "solid",107"strokeWidth": 2,108"strokeStyle": style,109"roughness": 1,110"opacity": 100,111"groupIds": [],112"frameId": None,113"index": "a0",114"roundness": {115"type": 2116},117"seed": 1000000000 + hash(f"{from_x}{from_y}{to_x}{to_y}") % 1000000000,118"version": 1,119"versionNonce": 2000000000 + hash(f"{from_x}{from_y}{to_x}{to_y}") % 1000000000,120"isDeleted": False,121"boundElements": [],122"updated": 1738195200000,123"link": None,124"locked": False,125"points": [126[0, 0],127[to_x - from_x, to_y - from_y]128],129"startBinding": None,130"endBinding": None,131"startArrowhead": None,132"endArrowhead": "arrow",133"lastCommittedPoint": None134}135elements.append(arrow)136137# Optional label138if label:139mid_x = (from_x + to_x) / 2 - (len(label) * 5)140mid_y = (from_y + to_y) / 2 - 10141142label_element = {143"id": generate_unique_id(),144"type": "text",145"x": mid_x,146"y": mid_y,147"width": len(label) * 10,148"height": 20,149"angle": 0,150"strokeColor": color,151"backgroundColor": "transparent",152"fillStyle": "solid",153"strokeWidth": 2,154"strokeStyle": "solid",155"roughness": 1,156"opacity": 100,157"groupIds": [],158"frameId": None,159"index": "a0",160"roundness": None,161"seed": 1000000000 + hash(label) % 1000000000,162"version": 1,163"versionNonce": 2000000000 + hash(label) % 1000000000,164"isDeleted": False,165"boundElements": [],166"updated": 1738195200000,167"link": None,168"locked": False,169"text": label,170"fontSize": 14,171"fontFamily": 5,172"textAlign": "center",173"verticalAlign": "top",174"containerId": None,175"originalText": label,176"autoResize": True,177"lineHeight": 1.25178}179elements.append(label_element)180181return elements182183184def add_arrow_to_diagram(185diagram_path: Path,186from_x: float,187from_y: float,188to_x: float,189to_y: float,190style: str = "solid",191color: str = "#1e1e1e",192label: str = None193) -> None:194"""195Add an arrow to an Excalidraw diagram.196197Args:198diagram_path: Path to the Excalidraw diagram file199from_x: Starting X coordinate200from_y: Starting Y coordinate201to_x: Ending X coordinate202to_y: Ending Y coordinate203style: Line style (solid, dashed, dotted)204color: Arrow color205label: Optional text label206"""207print(f"Creating arrow from ({from_x}, {from_y}) to ({to_x}, {to_y})")208arrow_elements = create_arrow(from_x, from_y, to_x, to_y, style, color, label)209210if label:211print(f" With label: '{label}'")212213# Load diagram214print(f"Loading diagram: {diagram_path}")215with open(diagram_path, 'r', encoding='utf-8') as f:216diagram = json.load(f)217218# Add arrow elements219if 'elements' not in diagram:220diagram['elements'] = []221222original_count = len(diagram['elements'])223diagram['elements'].extend(arrow_elements)224print(f" Added {len(arrow_elements)} elements (total: {original_count} -> {len(diagram['elements'])})")225226# Save diagram227print(f"Saving diagram")228with open(diagram_path, 'w', encoding='utf-8') as f:229json.dump(diagram, f, indent=2, ensure_ascii=False)230231print(f"✓ Successfully added arrow to diagram")232233234def main():235"""Main entry point."""236if len(sys.argv) < 6:237print("Usage: python add-arrow.py <diagram_path> <from_x> <from_y> <to_x> <to_y> [OPTIONS]")238print("\nOptions:")239print(" --style {solid|dashed|dotted} Line style (default: solid)")240print(" --color HEX Color (default: #1e1e1e)")241print(" --label TEXT Text label on arrow")242print(" --use-edit-suffix Edit via .excalidraw.edit to avoid editor overwrite issues (enabled by default; use --no-use-edit-suffix to disable)")243print("\nExamples:")244print(" python add-arrow.py diagram.excalidraw 300 200 500 300")245print(" python add-arrow.py diagram.excalidraw 300 200 500 300 --label 'HTTP'")246sys.exit(1)247248diagram_path = Path(sys.argv[1])249from_x = float(sys.argv[2])250from_y = float(sys.argv[3])251to_x = float(sys.argv[4])252to_y = float(sys.argv[5])253254# Parse optional arguments255style = "solid"256color = "#1e1e1e"257label = None258# Default: use edit suffix to avoid editor overwrite issues259use_edit_suffix = True260261i = 6262while i < len(sys.argv):263if sys.argv[i] == '--style':264if i + 1 < len(sys.argv):265style = sys.argv[i + 1]266if style not in ['solid', 'dashed', 'dotted']:267print(f"Error: Invalid style '{style}'. Must be: solid, dashed, or dotted")268sys.exit(1)269i += 2270else:271print("Error: --style requires an argument")272sys.exit(1)273elif sys.argv[i] == '--color':274if i + 1 < len(sys.argv):275color = sys.argv[i + 1]276i += 2277else:278print("Error: --color requires an argument")279sys.exit(1)280elif sys.argv[i] == '--label':281if i + 1 < len(sys.argv):282label = sys.argv[i + 1]283i += 2284else:285print("Error: --label requires a text argument")286sys.exit(1)287elif sys.argv[i] == '--use-edit-suffix':288use_edit_suffix = True289i += 1290elif sys.argv[i] == '--no-use-edit-suffix':291use_edit_suffix = False292i += 1293else:294print(f"Error: Unknown option: {sys.argv[i]}")295sys.exit(1)296297# Validate inputs298if not diagram_path.exists():299print(f"Error: Diagram file not found: {diagram_path}")300sys.exit(1)301302try:303work_path, final_path = prepare_edit_path(diagram_path, use_edit_suffix)304add_arrow_to_diagram(work_path, from_x, from_y, to_x, to_y, style, color, label)305finalize_edit_path(work_path, final_path)306except Exception as e:307print(f"Error: {e}")308sys.exit(1)309310311if __name__ == '__main__':312main()313