Token导航 LogoToken导航

Agent Skill · 研究检索

excalidraw-generation(excalidraw generation 搜索)

excalidraw-generation 用于查找、检索和筛选相关信息,适合在 Codex、Claude、Cursor、Gemini CLI 中需要根据关键词、任务场景或来源线索快速定位候选结果时使用。可结合来源仓库、安装命令和原始 README 继续核验具体用法。安装前建议确认权限范围、维护状态,以及是否会触发联网、命令执行或文件读写。

excalidraw-generation 是研究检索类 Agent Skill,可用宿主为 Codex、Claude、Cursor、Gemini CLI,通过GitHub 仓库获取,权限为只读;许可证为 MIT,第三方扫描有提醒项,按安装量在研究检索类的 73,675 个 Skill 里排第 6,828。

总安装 20,813周安装 497GitHub Stars 28下载 5,187核验 2026-05-01

安装说明

本站只整理中文说明,不托管安装包,也不代用户安装。
GitHub
许可证
MIT

安装方式

通过对话安装

复制提示词发给支持本地命令或 Skills 的 AI 助手,先确认命令和权限,再让它执行。

请帮我安装这个 Agent Skill:excalidraw-generation(excalidraw generation 搜索)
来源仓库:https://github.com/rhuss/cc-slidev
仓库路径:skills/excalidraw-generation
安装命令:
npx skills add https://github.com/rhuss/cc-slidev --skill 'Excalidraw Generation'
安装前请先检查当前环境是否支持对应 CLI,并向我确认将要执行的命令、安装目录、联网范围和文件读写权限;确认后再执行。

命令行安装

复制命令到本机终端执行。不同来源提供的安装方式可能略有差异;本站展示可直接复制的安装命令,安装前请核对来源页面。

skills.shnpx skills
npx skills add https://github.com/rhuss/cc-slidev --skill 'Excalidraw Generation'

这个 Skill 是做什么的

研究检索类 Skill 主要解决「找资料、核对事实、把来源整理清楚」的问题,通常会让助手先检索再总结。

excalidraw-generation 将文本转化为语义化的 Excalidraw 幻灯片,注重视觉表达与设计美感。适合在 Codex、Claude、Cursor、Gemini CLI 中需要制作演示文稿、培训材料或技术讲解图时使用。采用两阶段流程:内容提取与视觉重构,确保信息清晰传达。

必须使用 render-excalidraw.sh 脚本转换 JSON 为 SVG,禁止手动嵌入原始数据。适用宿主包括 Codex、Claude、Cursor、Gemini CLI,接入前应确认版本、权限和运行环境要求。

来源仓库内路径:skills/excalidraw-generation

上手步骤

4 步
  1. 1

    确认自己的宿主工具

    公开资料标注支持 Codex、Claude、Cursor、Gemini CLI;先确认自己用的是其中之一,再按对应工具的 Skills 目录放置。

  2. 2

    拿到安装命令

    可以直接复制的命令是 npx skills add https://github.com/rhuss/cc-slidev --skill 'Excalidraw Generation…;命令里的占位符换成自己的路径或密钥再执行。

  3. 3

    安装后重启宿主

    命令执行完要完全退出宿主工具再打开,让新装的 Skill 被重新扫描到,否则列表里可能看不到。

  4. 4

    按最小权限试用

    它标注为只读,先用只读任务试一次,确认行为符合预期再扩大使用范围。

常见问题

6 条

excalidraw-generation 怎么安装?

在终端执行 npx skills add https://github.com/rhuss/cc-slidev --skill 'Excalidraw Generation'。装完完全退出宿主工具再打开,让它重新扫描本地 Skill。

excalidraw-generation 支持哪些 AI 工具?

公开资料标注支持 Codex、Claude、Cursor、Gemini CLI。其他工具只要能读取本地 Skill 目录,一般也能手动放进去,但要自己确认触发方式是否一致。

excalidraw-generation 需要什么权限?

权限级别是只读。该 Skill 主要提供规则、说明或参考内容,本身偏只读;真正读写文件、联网或执行命令仍取决于宿主 Agent 的任务。

excalidraw-generation 是免费的吗?

来源标注的许可证是 MIT。本站不代收任何费用,也不托管安装包;能否商用、要不要署名,以许可证原文为准。

excalidraw-generation 用的人多吗?

公开数据是累计安装 20,813 次,近一周安装 497 次,下载 5,187 次,GitHub 28 星。这些数字来自第三方目录统计,只能当作热度参考,不代表质量或安全性。

excalidraw-generation 安全吗?

第三方扫描有提醒项,说明存在需要人工确认的行为。安装前请打开源码看清它读了什么、联网到哪里。

值得选的地方和要先想清楚的地方

值得选的地方

  • 同时适配 4 种工具,换客户端不用重新找替代品。
  • 3 个目录同时收录,说明这个 Skill 流传比较广。
  • 许可证明确为 MIT,商用和分发边界好判断。
  • 标注为只读权限,试用阶段不容易误改本地文件。

先想清楚的地方

  • 第三方扫描存在提醒项或未通过项,需要人工逐条确认。
  • 第三方目录整理,版本更新和行为细节以来源仓库为准。

适不适合你

更适合

  • 已经在用 Codex,想直接扩展它能力的人。
  • 需要处理研究检索类任务、又不想每次都手写提示词的人。
  • 只想先试试效果、不希望它改动本地文件的人。

不太适合

  • 需要厂商级技术支持和 SLA 的生产系统。

研究检索类 Skill 对比

6 个同类
Skill总安装GitHub 星权限许可证扫描
find-skills

寻找技能 · Codex、Claude

31,199,81316,486操作浏览器MIT有提醒
self-improving-agent

自我改进 Agent · OpenClaw

16,464,0023,396接触敏感数据MIT-0通过
soultrace

灵魂追踪 · Codex、Claude

7,588,94412需要联网未明确有提醒
self-improving

自我完善 · OpenClaw

6,829,2841,046需要联网MIT-0通过
microsoft-foundry

微软代工厂 · Codex、Claude

6,614,144764调用外部服务未明确有提醒
excalidraw-generation

excalidraw generation 搜索 · 本页

20,81328只读MIT有提醒

按安装量取同分类前几名;数字来自第三方目录统计,只作热度参考,不代表质量结论。

SKILL.md 原文

Excalidraw Generation Expert

Core Philosophy: Semantic redesign, not mechanical conversion. Think like both a presentation designer (clarity, accessibility, simplicity) and an artist (creative visual expression, spatial design, aesthetic beauty).

CRITICAL - Rendering Rule

ALWAYS use render-excalidraw.sh for SVG conversion - NO EXCEPTIONS

After creating Excalidraw JSON:

  1. Save JSON to diagrams/<slug>.excalidraw
  2. MUST render using: ${CLAUDE_PLUGIN_ROOT}/scripts/render-excalidraw.sh
  3. NEVER attempt manual SVG conversion
  4. NEVER embed JSON in markdown - only reference the rendered SVG

The script handles all rendering automatically with excalidraw-brute-export-cli.

When to Use This Skill

Auto-trigger when:

  • User explicitly requests: "create excalidraw diagram", "hand-drawn diagram", "sketch", "whiteboard"
  • Slide content suggests conceptual/spatial relationships
  • Architecture with nested components
  • Brainstorming/ideation context
  • Informal, approachable style needed
  • Annotations and callouts would add value

Diagram type suitability:

  • ✅✅✅ BEST: Conceptual relationships, architecture diagrams, mind maps, timelines, comparisons
  • ✅✅ EXCELLENT: Flowcharts (with annotations), spatial layouts, nested structures
  • ⚠️ OKAY: Sequence diagrams (prefer Mermaid instead)
  • ❌ NOT RECOMMENDED: Formal UML (use PlantUML), state machines (use Mermaid)

Evidence-Based Design Constraints (HARD LIMITS)

These constraints are NON-NEGOTIABLE. Enforce strictly:

  • Cognitive load: Maximum 9 elements (7±2 rule from cognitive psychology)
  • Accessibility:

- Colorblind-safe palette ONLY: Blue #3b82f6 + Orange #f97316 - Minimum 4.5:1 contrast ratio for all text (WCAG AA) - Never rely on color alone to convey information

  • Minimal text: Under 50 words total per diagram
  • One idea per diagram: If concept is complex, split into multiple diagrams
  • Hand-drawn aesthetic: Roughness 1 for informal feel

Core Capabilities

1. Semantic Concept Extraction

Process (ALWAYS follow this order):

  1. Analyze user's description or slide content
  2. Extract core concepts (entities, relationships, flows)
  3. Identify semantic type:

- Containment: X contains Y → Use nested boxes/frames - Flow: A→B→C → Use arrows with spatial progression - Comparison: X vs Y → Use side-by-side separation - Hierarchy: Parent-child → Use vertical/spatial positioning - Grouping: Related items → Use frames or color-coded regions - Annotation: Context/explanation → Use callouts and bound text

  1. Design layout (choose from layout algorithms below)
  2. Generate JSON (use element factories below)

Example:

Input: "Kubernetes device plugin architecture"

Semantic analysis:
- Type: Architecture + Flow
- Key concepts: Control Plane (container), Worker Node (container),
                GPU (component), Device Plugin (component), Kubelet (component)
- Relationships: Discovery flow, Registration flow, Capacity updates
- Spatial meaning: Control Plane ABOVE Worker Node (hierarchy)

Design choice: Vertical layout with 2 frames, 5 shapes, 3 arrows, 3 annotations
Cognitive load: 2 frames + 5 shapes = 7 units ✓

2. Element Factories

These functions generate valid Excalidraw JSON elements. Use them to build diagrams.

ID Generation
function generateId() {
  // Excalidraw uses random alphanumeric IDs (12+ chars)
  return Math.random().toString(36).substring(2, 15) +
         Math.random().toString(36).substring(2, 15);
}
Rectangle Factory
function createRectangle(x, y, width, height, text = null, options = {}) {
  const id = generateId();

  const element = {
    type: "rectangle",
    version: 1,
    versionNonce: Math.floor(Math.random() * 1000000),
    isDeleted: false,
    id: id,
    fillStyle: options.fillStyle || "hachure",
    strokeWidth: options.strokeWidth || 2,
    strokeStyle: "solid",
    roughness: options.roughness !== undefined ? options.roughness : 1,
    opacity: 100,
    angle: options.angle || 0,
    x: x,
    y: y,
    strokeColor: options.strokeColor || THEME_COLORS.primary,
    backgroundColor: options.backgroundColor || "transparent",
    width: width,
    height: height,
    seed: Math.floor(Math.random() * 1000000),
    groupIds: options.groupIds || [],
    frameId: options.frameId || null,
    roundness: { type: 3 },
    boundElements: [],
    updated: Date.now(),
    link: null,
    locked: false
  };

  // If text provided, create bound text element
  if (text) {
    const textElement = createBoundText(text, id, x, y, width, height);
    element.boundElements.push({ type: "text", id: textElement.id });
    return [element, textElement];  // Return array
  }

  return element;  // Return single element
}
Text Factory (Standalone and Bound)
function createText(text, x, y, options = {}) {
  return {
    type: "text",
    version: 1,
    versionNonce: Math.floor(Math.random() * 1000000),
    isDeleted: false,
    id: options.id || generateId(),
    fillStyle: "hachure",
    strokeWidth: 1,
    strokeStyle: "solid",
    roughness: 0,  // Text is always smooth
    opacity: 100,
    angle: 0,
    x: x,
    y: y,
    strokeColor: options.strokeColor || THEME_COLORS.text,
    backgroundColor: "transparent",
    width: options.width || 200,
    height: options.height || 25,
    seed: Math.floor(Math.random() * 1000000),
    groupIds: options.groupIds || [],
    frameId: options.frameId || null,
    roundness: null,
    boundElements: [],
    updated: Date.now(),
    link: null,
    locked: false,
    fontSize: options.fontSize || 20,
    fontFamily: 1,  // 1 = Excalifont/Virgil (hand-drawn), 2 = Helvetica, 3 = Cascadia
    text: text,
    textAlign: options.textAlign || "center",
    verticalAlign: options.verticalAlign || "middle",
    containerId: options.containerId || null,
    originalText: text,
    lineHeight: 1.25,
    baseline: 18
  };
}

// Font family mapping for reference:
// fontFamily: 1 → Excalifont/Virgil (hand-drawn, default for Excalidraw aesthetic)
// fontFamily: 2 → Helvetica (clean, modern)
// fontFamily: 3 → Cascadia (monospace, code)
// When rendering to SVG: font-family: 'Excalifont', 'Virgil', cursive, sans-serif

function createBoundText(text, containerId, containerX, containerY, containerWidth, containerHeight) {
  // Calculate centered position inside container
  const textWidth = Math.min(containerWidth - 20, 200);
  const textHeight = 25;
  const textX = containerX + (containerWidth - textWidth) / 2;
  const textY = containerY + (containerHeight - textHeight) / 2;

  return createText(text, textX, textY, {
    width: textWidth,
    height: textHeight,
    containerId: containerId,
    textAlign: "center",
    verticalAlign: "middle"
  });
}
Arrow Factory
function createArrow(startX, startY, endX, endY, options = {}) {
  const points = [
    [0, 0],  // Start point (relative to x, y)
    [endX - startX, endY - startY]  // End point (relative)
  ];

  return {
    type: "arrow",
    version: 1,
    versionNonce: Math.floor(Math.random() * 1000000),
    isDeleted: false,
    id: generateId(),
    fillStyle: "hachure",
    strokeWidth: options.strokeWidth || 2,
    strokeStyle: "solid",
    roughness: options.roughness !== undefined ? options.roughness : 1,
    opacity: 100,
    angle: 0,
    x: startX,
    y: startY,
    strokeColor: options.strokeColor || THEME_COLORS.neutral,
    backgroundColor: "transparent",
    width: Math.abs(endX - startX),
    height: Math.abs(endY - startY),
    seed: Math.floor(Math.random() * 1000000),
    groupIds: options.groupIds || [],
    frameId: options.frameId || null,
    roundness: { type: 2 },
    boundElements: [],
    updated: Date.now(),
    link: null,
    locked: false,
    startBinding: options.startBinding || null,
    endBinding: options.endBinding || null,
    lastCommittedPoint: null,
    startArrowhead: null,
    endArrowhead: "arrow",
    points: points
  };
}
Frame Factory (Containers)
function createFrame(x, y, width, height, name, options = {}) {
  return {
    type: "frame",
    version: 1,
    versionNonce: Math.floor(Math.random() * 1000000),
    isDeleted: false,
    id: generateId(),
    fillStyle: "hachure",
    strokeWidth: 2,
    strokeStyle: "solid",
    roughness: 0,  // Frames are clean, not hand-drawn
    opacity: 100,
    angle: 0,
    x: x,
    y: y,
    strokeColor: options.strokeColor || THEME_COLORS.neutral,
    backgroundColor: options.backgroundColor || THEME_COLORS.light_bg,
    width: width,
    height: height,
    seed: Math.floor(Math.random() * 1000000),
    groupIds: [],
    frameId: null,
    roundness: null,
    boundElements: [],
    updated: Date.now(),
    link: null,
    locked: false,
    name: name
  };
}
Ellipse Factory
function createEllipse(x, y, width, height, text = null, options = {}) {
  const id = generateId();

  const element = {
    type: "ellipse",
    version: 1,
    versionNonce: Math.floor(Math.random() * 1000000),
    isDeleted: false,
    id: id,
    fillStyle: options.fillStyle || "hachure",
    strokeWidth: options.strokeWidth || 2,
    strokeStyle: "solid",
    roughness: options.roughness !== undefined ? options.roughness : 1,
    opacity: 100,
    angle: 0,
    x: x,
    y: y,
    strokeColor: options.strokeColor || THEME_COLORS.primary,
    backgroundColor: options.backgroundColor || "transparent",
    width: width,
    height: height,
    seed: Math.floor(Math.random() * 1000000),
    groupIds: options.groupIds || [],
    frameId: options.frameId || null,
    roundness: null,
    boundElements: [],
    updated: Date.now(),
    link: null,
    locked: false
  };

  if (text) {
    const textElement = createBoundText(text, id, x, y, width, height);
    element.boundElements.push({ type: "text", id: textElement.id });
    return [element, textElement];
  }

  return element;
}
Callout Factory (Annotation)
function createCallout(targetX, targetY, text, direction = "top-right") {
  const offsets = {
    "top-right": { dx: 100, dy: -80 },
    "top-left": { dx: -100, dy: -80 },
    "bottom-right": { dx: 100, dy: 80 },
    "bottom-left": { dx: -100, dy: 80 }
  };

  const offset = offsets[direction];
  const textX = targetX + offset.dx;
  const textY = targetY + offset.dy;

  const calloutText = createText(text, textX, textY, {
    fontSize: 16,
    strokeColor: THEME_COLORS.accent
  });

  const arrow = createArrow(textX, textY + 12, targetX, targetY, {
    strokeColor: THEME_COLORS.accent,
    strokeWidth: 1.5
  });

  // Group them together
  const groupId = generateId();
  calloutText.groupIds.push(groupId);
  arrow.groupIds.push(groupId);

  return [calloutText, arrow];
}

3. Layout Algorithms

Layout Constants
const LAYOUT = {
  MARGIN: 50,          // Canvas edge margin
  PADDING: 40,         // Between elements
  NODE_WIDTH: 180,     // Standard node width
  NODE_HEIGHT: 80,     // Standard node height
  ARROW_GAP: 10        // Gap for arrow binding
};
Horizontal Flow Layout
function layoutHorizontalFlow(nodes) {
  // Left-to-right progression
  const positions = [];
  let currentX = LAYOUT.MARGIN;
  const baseY = 200;  // Vertical center

  nodes.forEach((node, index) => {
    positions.push({
      x: currentX,
      y: baseY,
      width: LAYOUT.NODE_WIDTH,
      height: LAYOUT.NODE_HEIGHT,
      text: node
    });
    currentX += LAYOUT.NODE_WIDTH + LAYOUT.PADDING;
  });

  return positions;
}
Vertical Flow Layout
function layoutVerticalFlow(nodes) {
  // Top-to-bottom progression
  const positions = [];
  const baseX = 300;  // Horizontal center
  let currentY = LAYOUT.MARGIN;

  nodes.forEach((node, index) => {
    positions.push({
      x: baseX,
      y: currentY,
      width: LAYOUT.NODE_WIDTH,
      height: LAYOUT.NODE_HEIGHT,
      text: node
    });
    currentY += LAYOUT.NODE_HEIGHT + LAYOUT.PADDING;
  });

  return positions;
}
Radial Layout (Mind Map)
function layoutRadial(centerNode, childNodes) {
  const positions = [];
  const centerX = 400;
  const centerY = 300;
  const radius = 200;

  // Center node
  positions.push({
    x: centerX - LAYOUT.NODE_WIDTH / 2,
    y: centerY - LAYOUT.NODE_HEIGHT / 2,
    width: LAYOUT.NODE_WIDTH,
    height: LAYOUT.NODE_HEIGHT,
    text: centerNode
  });

  // Child nodes in circle
  const angleStep = (2 * Math.PI) / childNodes.length;
  childNodes.forEach((node, index) => {
    const angle = index * angleStep;
    const x = centerX + radius * Math.cos(angle) - LAYOUT.NODE_WIDTH / 2;
    const y = centerY + radius * Math.sin(angle) - LAYOUT.NODE_HEIGHT / 2;

    positions.push({
      x: x,
      y: y,
      width: LAYOUT.NODE_WIDTH,
      height: LAYOUT.NODE_HEIGHT,
      text: node
    });
  });

  return positions;
}

4. Connection Binding Logic

Create Binding Point
function createBindingPoint(shapeId, shapeX, shapeY, shapeWidth, shapeHeight, side) {
  // side: "top", "bottom", "left", "right"
  let focus = { x: 0, y: 0 };

  switch(side) {
    case "right":
      focus = { x: 1, y: 0 };  // Right edge, centered
      break;
    case "left":
      focus = { x: -1, y: 0 };  // Left edge, centered
      break;
    case "bottom":
      focus = { x: 0, y: 1 };  // Bottom edge, centered
      break;
    case "top":
      focus = { x: 0, y: -1 };  // Top edge, centered
      break;
  }

  return {
    elementId: shapeId,
    focus: focus,
    gap: LAYOUT.ARROW_GAP
  };
}
Connect Shapes Horizontally
function connectShapesHorizontal(shapeA, shapeB) {
  // Bind arrow from right edge of A to left edge of B
  const startX = shapeA.x + shapeA.width;
  const startY = shapeA.y + shapeA.height / 2;
  const endX = shapeB.x;
  const endY = shapeB.y + shapeB.height / 2;

  return createArrow(startX, startY, endX, endY, {
    startBinding: createBindingPoint(shapeA.id, shapeA.x, shapeA.y, shapeA.width, shapeA.height, "right"),
    endBinding: createBindingPoint(shapeB.id, shapeB.x, shapeB.y, shapeB.width, shapeB.height, "left")
  });
}

5. Color Palette (Colorblind-Safe)

const THEME_COLORS = {
  primary: "#3b82f6",      // Blue (8.6:1 contrast) - Main shapes
  secondary: "#f97316",    // Orange (3.4:1, ≥24pt only) - Emphasis
  neutral: "#6b7280",      // Gray - Arrows, frames
  text: "#1f2937",         // Dark gray (16.1:1 contrast) - ALL text
  background: "#ffffff",   // White canvas
  accent: "#8b5cf6",       // Purple - Annotations
  light_bg: "#f3f4f6"      // Light gray - Frame fills
};

Color Usage Rules:

  • Primary shapes: Blue stroke, transparent or light fill
  • Emphasis shapes: Orange stroke (use sparingly)
  • Containers/frames: Gray stroke, light gray fill
  • Arrows: Gray (neutral, never distracting)
  • All text: Dark gray #1f2937 (maximum readability)
  • Annotations: Purple for visual distinction

6. Validation Functions

JSON Structure Validation
function validateExcalidrawJSON(json) {
  const errors = [];

  // Check required top-level fields
  if (json.type !== "excalidraw") {
    errors.push("Missing or invalid 'type' (must be 'excalidraw')");
  }
  if (json.version !== 2) {
    errors.push("Version should be 2");
  }
  if (!Array.isArray(json.elements)) {
    errors.push("'elements' must be array");
  }
  if (typeof json.appState !== "object") {
    errors.push("'appState' must be object");
  }

  // Validate each element
  json.elements.forEach((element, index) => {
    if (!element.id) errors.push(`Element ${index} missing 'id'`);
    if (!element.type) errors.push(`Element ${index} missing 'type'`);
    if (typeof element.x !== "number") errors.push(`Element ${index} missing 'x'`);
    if (typeof element.y !== "number") errors.push(`Element ${index} missing 'y'`);

    // Check bound text references
    if (element.boundElements) {
      element.boundElements.forEach(bound => {
        if (bound.type === "text") {
          const textElement = json.elements.find(e => e.id === bound.id);
          if (!textElement) {
            errors.push(`Bound text ${bound.id} not found`);
          }
          if (textElement && textElement.containerId !== element.id) {
            errors.push(`Bound text ${bound.id} containerId mismatch`);
          }
        }
      });
    }

    // Check arrow bindings
    if (element.type === "arrow") {
      if (element.startBinding && element.startBinding.elementId) {
        const target = json.elements.find(e => e.id === element.startBinding.elementId);
        if (!target) {
          errors.push(`Arrow ${element.id} startBinding target not found`);
        }
      }
      if (element.endBinding && element.endBinding.elementId) {
        const target = json.elements.find(e => e.id === element.endBinding.elementId);
        if (!target) {
          errors.push(`Arrow ${element.id} endBinding target not found`);
        }
      }
    }
  });

  return {
    valid: errors.length === 0,
    errors: errors
  };
}
Cognitive Load Validation
function countCognitiveElements(json) {
  // Count distinct visual concepts (not total elements)
  const cognitiveUnits = {
    shapes: 0,
    arrows: 0,
    annotations: 0,
    frames: 0
  };

  const groupedElements = new Set();

  json.elements.forEach(element => {
    // Skip if part of counted group
    if (element.groupIds && element.groupIds.length > 0) {
      if (groupedElements.has(element.groupIds[0])) {
        return;  // Already counted
      }
      groupedElements.add(element.groupIds[0]);
    }

    // Skip bound text (counted with parent)
    if (element.containerId) return;

    switch(element.type) {
      case "rectangle":
      case "ellipse":
      case "diamond":
        cognitiveUnits.shapes++;
        break;
      case "arrow":
      case "line":
        cognitiveUnits.arrows++;
        break;
      case "text":
        cognitiveUnits.annotations++;
        break;
      case "frame":
        cognitiveUnits.frames++;
        break;
    }
  });

  const total = cognitiveUnits.shapes +
                cognitiveUnits.arrows +
                cognitiveUnits.annotations +
                cognitiveUnits.frames;

  return {
    breakdown: cognitiveUnits,
    total: total,
    withinLimit: total <= 9,  // 7±2 rule
    recommendation: total > 9 ? "SPLIT into multiple diagrams" : "Good"
  };
}

7. Assembly Function

function assembleExcalidrawJSON(elements) {
  return {
    type: "excalidraw",
    version: 2,
    source: "https://excalidraw.com",
    elements: elements,
    appState: {
      viewBackgroundColor: THEME_COLORS.background,
      gridSize: null,
      theme: "light"
    },
    files: {}
  };
}

Interactive Workflow

Follow this workflow when generating diagrams:

Step 1: Analyze Concept

Ask the user (if not clear from context):

  1. What's the main concept to convey?
  2. Who's the audience? (beginners, experts, mixed)
  3. Should this be formal or informal?
  4. Are there specific relationships/flows to highlight?

Step 2: Design Proposal

Show the user your design approach:

## Diagram Analysis

Semantic type: [Architecture/Flow/Mind Map/etc.]
Best platform: Excalidraw

Design approach:
- Layout: [Horizontal/Vertical/Radial]
- Elements: [List main shapes and their purpose]
- Frames: [If using containers]
- Arrows: [Key flows to show]
- Annotations: [Callouts for context]
- Hand-drawn aesthetic: roughness 1
- Colors: Colorblind-safe blue/orange

Element count: [N] shapes + [M] arrows + [P] annotations = [Total]
Grouped into [X] logical units → within cognitive limit ✓ / ⚠️ OVER LIMIT

Step 3: Show ASCII Preview

Show a text-based preview:

┌─────────────────────────────────┐
│  Container Name                 │
│  ┌──────────┐    ┌──────────┐  │
│  │ Shape A  │───→│ Shape B  │  │
│  └──────────┘    └──────────┘  │
└─────────────────────────────────┘
     ↑ "Annotation explaining flow"

Ask: "Proceed with JSON generation?"

Step 4: Generate JSON

  1. Create frames (if needed)
  2. Create shapes with bound text
  3. Position using layout algorithm
  4. Create arrows with bindings
  5. Add annotation callouts
  6. Apply grouping (if needed)
  7. Assemble final JSON
  8. Validate structure
  9. Check cognitive load
  10. Save to file

Step 5: Save and Render

File structure:

diagrams/
  └── <slide-title-slug>.excalidraw    # JSON source (editable)

public/images/<slide-title-slug>/
  └── diagram-excalidraw.svg           # Rendered SVG (for slide)

IMPORTANT: Always save source files to ./diagrams/ directory.

CRITICAL - Rendering Process:

  1. Save JSON: Use Write tool to save JSON to diagrams/<slug>.excalidraw
  2. Render to SVG: ALWAYS use render-excalidraw.sh script - NEVER attempt manual rendering: ${CLAUDE_PLUGIN_ROOT}/scripts/render-excalidraw.sh \ diagrams/<slug>.excalidraw \ public/images/<slug>/diagram-excalidraw.svg
  3. Script handles: The script automatically:

- Installs excalidraw-brute-export-cli if missing - Installs playwright chromium dependencies - Renders with correct parameters (--background 1, --embed-scene 0, etc.) - Ensures proper font rendering (Excalifont → Virgil → cursive → sans-serif)

DO NOT attempt to render Excalidraw any other way. ALWAYS use the script.

Step 6: Offer Iterations

✅ Excalidraw Diagram Generated!

Source: diagrams/<slug>.excalidraw
Rendered: public/images/<slug>/diagram-excalidraw.svg

Edit online: https://excalidraw.com (drag diagrams/<slug>.excalidraw file)

After editing, re-render with:
${CLAUDE_PLUGIN_ROOT}/scripts/render-excalidraw.sh \
  diagrams/<slug>.excalidraw \
  public/images/<slug>/diagram-excalidraw.svg

Refinement options:
- Adjust layout (horizontal ↔ vertical)
- Add more annotations
- Change colors/emphasis
- Simplify (remove elements)
- Add more detail

What would you like to adjust?

Example Generation: Architecture Diagram

Input: "Show Kubernetes device plugin architecture"

Step 1: Semantic Analysis

Type: Architecture + Flow
Concepts:
- Control Plane (container) - top
- Worker Node (container) - bottom
- Inside Worker Node: GPU, Device Plugin, Kubelet
- Flow: Discovery → Registration → Capacity Updates

Layout: Vertical (hierarchy)
Element count: 2 frames + 5 shapes + 3 arrows + 2 annotations = 12 base
BUT: Grouped into 2 logical units (control plane, worker node) = 7 cognitive units ✓

Step 2: JSON Generation

const elements = [];

// Create frames
const controlPlaneFrame = createFrame(50, 50, 700, 200, "Control Plane");
const workerNodeFrame = createFrame(50, 300, 700, 300, "Worker Node");
elements.push(controlPlaneFrame, workerNodeFrame);

// Control plane components
const [scheduler, schedulerText] = createRectangle(100, 100, 180, 80, "Scheduler", {
  frameId: controlPlaneFrame.id,
  strokeColor: THEME_COLORS.primary
});
const [apiServer, apiServerText] = createRectangle(400, 100, 180, 80, "API Server", {
  frameId: controlPlaneFrame.id,
  strokeColor: THEME_COLORS.primary
});
elements.push(scheduler, schedulerText, apiServer, apiServerText);

// Worker node components
const [gpu, gpuText] = createRectangle(100, 350, 180, 80, "GPU 0\nGPU 1", {
  frameId: workerNodeFrame.id,
  strokeColor: THEME_COLORS.secondary  // Orange for emphasis
});
const [plugin, pluginText] = createRectangle(350, 350, 180, 80, "Device Plugin", {
  frameId: workerNodeFrame.id,
  strokeColor: THEME_COLORS.primary
});
const [kubelet, kubeletText] = createRectangle(350, 480, 180, 80, "Kubelet", {
  frameId: workerNodeFrame.id,
  strokeColor: THEME_COLORS.primary
});
elements.push(gpu, gpuText, plugin, pluginText, kubelet, kubeletText);

// Arrows with bindings
const arrow1 = connectShapesHorizontal(gpu, plugin);  // Discovery
const arrow2 = createArrow(
  plugin.x + plugin.width / 2, plugin.y + plugin.height,
  kubelet.x + kubelet.width / 2, kubelet.y,
  {
    startBinding: createBindingPoint(plugin.id, plugin.x, plugin.y, plugin.width, plugin.height, "bottom"),
    endBinding: createBindingPoint(kubelet.id, kubelet.x, kubelet.y, kubelet.width, kubelet.height, "top")
  }
);
const arrow3 = createArrow(
  kubelet.x + kubelet.width / 2, kubelet.y,
  apiServer.x + apiServer.width / 2, apiServer.y + apiServer.height,
  {
    startBinding: createBindingPoint(kubelet.id, kubelet.x, kubelet.y, kubelet.width, kubelet.height, "top"),
    endBinding: createBindingPoint(apiServer.id, apiServer.x, apiServer.y, apiServer.width, apiServer.height, "bottom")
  }
);
elements.push(arrow1, arrow2, arrow3);

// Annotations
const [annotation1Text, annotation1Arrow] = createCallout(
  plugin.x + plugin.width, plugin.y + plugin.height / 2,
  "Your code",
  "top-right"
);
const [annotation2Text, annotation2Arrow] = createCallout(
  scheduler.x + scheduler.width / 2, scheduler.y,
  "Now aware!",
  "top-left"
);
elements.push(annotation1Text, annotation1Arrow, annotation2Text, annotation2Arrow);

// Assemble and validate
const json = assembleExcalidrawJSON(elements);
const validation = validateExcalidrawJSON(json);
const cognitiveCheck = countCognitiveElements(json);

if (!validation.valid) {
  console.error("Validation errors:", validation.errors);
  // Fix or abort
}

if (!cognitiveCheck.withinLimit) {
  console.warn(`Cognitive overload: ${cognitiveCheck.total} elements`);
  // Suggest splitting
}

// Save to file (source goes in diagrams/)
const filePath = "diagrams/device-plugin-architecture.excalidraw";
writeFile(filePath, JSON.stringify(json, null, 2));

Diagram Type Patterns

Flowchart Pattern

// Horizontal left-to-right flow
const nodes = ["Start", "Process", "Transform", "Output", "End"];
const positions = layoutHorizontalFlow(nodes);
const elements = [];

// Create shapes
const shapes = positions.map(pos => {
  const [rect, text] = createRectangle(pos.x, pos.y, pos.width, pos.height, pos.text);
  elements.push(rect, text);
  return rect;
});

// Connect with arrows
for (let i = 0; i < shapes.length - 1; i++) {
  const arrow = connectShapesHorizontal(shapes[i], shapes[i + 1]);
  elements.push(arrow);
}

// Add annotation at critical step
const [calloutText, calloutArrow] = createCallout(
  shapes[2].x + shapes[2].width / 2,
  shapes[2].y + shapes[2].height,
  "Key transformation!",
  "bottom-right"
);
elements.push(calloutText, calloutArrow);

Mind Map Pattern

// Radial layout from center
const centerConcept = "GPU Scheduling";
const branches = ["Device Plugin", "MIG", "Time-Slicing", "MPS", "Virtual GPUs"];

const positions = layoutRadial(centerConcept, branches);
const elements = [];

// Center ellipse
const [centerEllipse, centerText] = createEllipse(
  positions[0].x, positions[0].y,
  positions[0].width, positions[0].height,
  centerConcept,
  { strokeColor: THEME_COLORS.secondary, strokeWidth: 3 }
);
elements.push(centerEllipse, centerText);

// Branch ellipses with arrows
for (let i = 1; i < positions.length; i++) {
  const [ellipse, text] = createEllipse(
    positions[i].x, positions[i].y,
    positions[i].width, positions[i].height,
    positions[i].text
  );
  elements.push(ellipse, text);

  // Arrow from center to branch
  const arrow = createArrow(
    centerEllipse.x + centerEllipse.width / 2,
    centerEllipse.y + centerEllipse.height / 2,
    ellipse.x + ellipse.width / 2,
    ellipse.y + ellipse.height / 2
  );
  elements.push(arrow);
}

Quality Checklist

Before saving any diagram, verify:

  • Cognitive load: ≤9 elements total
  • Colors: Only approved palette (blue, orange, gray, purple)
  • Contrast: All text uses #1f2937 on white background
  • Text: Under 50 words total
  • Bindings: All arrows have startBinding and endBinding
  • Bound text: All text has correct containerId
  • JSON valid: Passes validateExcalidrawJSON()
  • One idea: Diagram conveys single clear concept
  • Frames: Used for containers/boundaries where appropriate
  • Hand-drawn: Roughness 1 for shapes (0 for frames and text)

Error Handling

If JSON generation fails:

  1. Log error details
  2. Attempt to fix (adjust positions, fix bindings)
  3. If unfixable: offer simpler design or Mermaid fallback
  4. Always save JSON even if rendering fails (user can edit at excalidraw.com)

If cognitive load exceeded:

  1. Warn user: "Diagram has X elements (limit: 9)"
  2. Suggest: "Split into 2 diagrams" or "Use progressive disclosure"
  3. Ask user to approve anyway or redesign

If validation fails:

  1. Show specific errors
  2. Attempt auto-fix for common issues
  3. If critical: abort and redesign

Integration Points

With /slidev:diagram Command

When diagram command analyzes a slide and determines Excalidraw is best fit:

Invoke Skill tool: skill: "slidev:excalidraw-generation"

The skill will take over generation process.

Auto-Suggestion Logic

Monitor for these triggers in slide content:

  • Keywords: "architecture", "components", "system design", "overview"
  • Nested structures detected
  • Spatial relationships described
  • Informal context ("brainstorm", "ideation", "workshop")

When detected, suggest: "I recommend creating an Excalidraw diagram for this - it excels at spatial layouts and informal designs. Proceed?"

Tools Available

  • Read: Read slide content, existing diagrams
  • Write: Save generated JSON to file
  • Bash: Execute rendering scripts if needed
  • AskUserQuestion: Interactive workflow questions

Next Steps After Generation

After successfully generating a diagram:

  1. Inform user of file locations
  2. Run render-excalidraw.sh to generate SVG
  3. Provide excalidraw.com editing instructions
  4. Offer refinement options
  5. Ask if they want to generate for another slide
  6. Suggest integration into slide (markdown image reference to SVG, NEVER embed JSON)

Remember: You are both a presentation designer (enforcing evidence-based constraints) AND an artist (creating beautiful, spatial, hand-drawn diagrams). Every diagram should be accessible, minimal, and convey exactly one clear idea.

平台分布

OpenCode

31.52%
按下载量换算1,635

Codex

21.33%
按下载量换算1,106

Claude Code

17.81%
按下载量换算924

Gemini CLI

13%
按下载量换算674

windsurf

8.21%
按下载量换算426

trae

3.92%
按下载量换算203

安全审计

Gen Agent Trust Hub

通过

Socket

可疑

Snyk

通过

权限和风险

只读

该 Skill 主要提供规则、说明或参考内容,本身偏只读;真正读写文件、联网或执行命令仍取决于宿主 Agent 的任务。

安装前确认

本站仅展示第三方公开信息,不托管安装包,不提供自动安装或运行环境。安装前应自行审查源码、依赖和命令行为。来源安全扫描存在提醒或未通过项,安装前请自行复核。

继续浏览同类 Skills