Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 27 additions & 24 deletions .github/workflows/check-skill-sync.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,14 @@ on:
push:
branches: [main]
paths:
- "plugins/claude-code/skills/drawio/SKILL.md"
- "plugins/codex/drawio/skills/drawio/SKILL.md"
- "plugins/copilot/skills/drawio/SKILL.md"
- "plugins/claude-code/skills/drawio/**"
- "plugins/codex/drawio/skills/drawio/**"
- "plugins/copilot/skills/drawio/**"
pull_request:
paths:
- "plugins/claude-code/skills/drawio/SKILL.md"
- "plugins/codex/drawio/skills/drawio/SKILL.md"
- "plugins/copilot/skills/drawio/SKILL.md"
- "plugins/claude-code/skills/drawio/**"
- "plugins/codex/drawio/skills/drawio/**"
- "plugins/copilot/skills/drawio/**"
workflow_dispatch:

permissions:
Expand All @@ -24,27 +24,30 @@ jobs:
steps:
- uses: actions/checkout@v5

- name: Verify port SKILL.md copies are byte-identical to the Claude Code copy
- name: Verify port skill copies are byte-identical to the Claude Code copies
run: |
claude="plugins/claude-code/skills/drawio/SKILL.md"
status=0

for port in \
"plugins/codex/drawio/skills/drawio/SKILL.md" \
"plugins/copilot/skills/drawio/SKILL.md"; do

if [ ! -f "$port" ]; then
echo "$port does not exist (port not present) — nothing to check."
continue
fi

if cmp -s "$claude" "$port"; then
echo "OK: $port is byte-identical to $claude."
else
echo "::error file=$port::Must be byte-identical to $claude — mirror the edit to both copies."
diff -u "$claude" "$port" || true
status=1
fi
for file in "SKILL.md" "scripts/fix_edge_parents.py"; do
claude="plugins/claude-code/skills/drawio/$file"

for port in \
"plugins/codex/drawio/skills/drawio/$file" \
"plugins/copilot/skills/drawio/$file"; do

if [ ! -f "$port" ]; then
echo "$port does not exist (port not present) — nothing to check."
continue
fi

if cmp -s "$claude" "$port"; then
echo "OK: $port is byte-identical to $claude."
else
echo "::error file=$port::Must be byte-identical to $claude — mirror the edit to both copies."
diff -u "$claude" "$port" || true
status=1
fi
done
done

exit $status
41 changes: 41 additions & 0 deletions .github/workflows/test-scripts.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
name: Test Scripts

on:
push:
branches: [main]
paths:
- "plugins/tests/**"
- "plugins/claude-code/skills/drawio/scripts/**"
- "plugins/codex/drawio/skills/drawio/scripts/**"
- "plugins/copilot/skills/drawio/scripts/**"
- ".github/workflows/test-scripts.yml"
pull_request:
paths:
- "plugins/tests/**"
- "plugins/claude-code/skills/drawio/scripts/**"
- "plugins/codex/drawio/skills/drawio/scripts/**"
- "plugins/copilot/skills/drawio/scripts/**"
- ".github/workflows/test-scripts.yml"
workflow_dispatch:

permissions:
contents: read

jobs:
unittest:
runs-on: ubuntu-latest

strategy:
fail-fast: false
matrix:
python-version: ["3.9", "3.14"]

steps:
- uses: actions/checkout@v5

- uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}

- name: Run the test suite
run: python -m unittest discover -s plugins/tests -v
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,10 @@ pnpm-lock.yaml
# TypeScript cache
*.tsbuildinfo

# Python cache
__pycache__/
*.pyc

# Claude Code
.claude/
/test/
7 changes: 4 additions & 3 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,10 @@ The official draw.io MCP (Model Context Protocol) server that enables LLMs to op
- **`mcp-tool-server/`** — Original MCP tool server (stdio-based, opens browser). Published as `@drawio/mcp` on npm.
- **`project-instructions/`** — Claude Project instructions (no MCP required, no install).
- **`plugins/`** — Assistant-side plugins grouped by host, one subdirectory per AI assistant.
- **`plugins/claude-code/`** — Claude Code plugin: ships the `drawio` skill (generates native `.drawio` files, authored as Mermaid — converted + laid out by the desktop CLI — or as XML directly with optional ELK `--layout`; exports to PNG/SVG/PDF, or opens as a browser URL via `app.diagrams.net`). Mermaid conversion, ELK layout, and image export need draw.io Desktop; plain XML `.drawio`/`url` output does not. Installable via the repo-root marketplace or `claude --plugin-dir ./plugins/claude-code`. No MCP required.
- **`plugins/codex/drawio/`** — Codex CLI plugin: the Codex port of the Claude Code plugin, shipping the same `drawio` skill. `skills/drawio/SKILL.md` is byte-identical to the Claude plugin's copy (Codex uses the same `/drawio:drawio` invocation and fetches the same shared references from GitHub). Differs only in host wrapping: a `.codex-plugin/plugin.json` manifest with an `interface` block (official draw.io SVG logo, `brandColor`, default prompts). Nested under `codex/` because Codex requires the plugin root folder name to equal `plugin.json` `"name"` (`drawio`). No MCP required.
- **`plugins/copilot/`** — GitHub Copilot CLI plugin: the Copilot port of the Claude Code plugin, shipping the same `drawio` skill. `skills/drawio/SKILL.md` is byte-identical to the Claude plugin's copy (the in-skill `/drawio:drawio` example lines are model-facing; Copilot's user-facing command is plain `/drawio` since Copilot doesn't prefix plugin skills). Differs only in host wrapping: a root `plugin.json` manifest (Copilot's format, `skills` directory list); no folder-name rule, so `copilot/` is itself the plugin root. The same skill folder also works in other Copilot surfaces (VS Code agent mode, coding agent, code review) when copied to a repo's `.github/skills/`. No MCP required.
- **`plugins/claude-code/`** — Claude Code plugin: ships the `drawio` skill (generates native `.drawio` files, authored as Mermaid — converted + laid out by the desktop CLI — or as XML directly with optional ELK `--layout`; exports to PNG/SVG/PDF, or opens as a browser URL via `app.diagrams.net`). Mermaid conversion, ELK layout, and image export need draw.io Desktop; plain XML `.drawio`/`url` output does not. The skill also bundles `skills/drawio/scripts/fix_edge_parents.py`, a stdlib-only Python helper it runs on every generated `.drawio` (before any layout pass) to set each edge's `parent` to the nearest common ancestor of its `source` and `target` — the rule [`mxGraphModel.updateEdgeParent`](https://github.com/jgraph/drawio/blob/v31.1.8/src/main/webapp/mxgraph/src/model/mxGraphModel.js#L832-L885) applies (the script is a port of draw.io's bundled copy, and credits its copyright holders in its docstring), and what a layout pass needs in order to lay the diagram out correctly. Installable via the repo-root marketplace or `claude --plugin-dir ./plugins/claude-code`. No MCP required.
- **`plugins/codex/drawio/`** — Codex CLI plugin: the Codex port of the Claude Code plugin, shipping the same `drawio` skill. `skills/drawio/SKILL.md` and `skills/drawio/scripts/fix_edge_parents.py` are byte-identical to the Claude plugin's copies (Codex uses the same `/drawio:drawio` invocation and fetches the same shared references from GitHub). Differs only in host wrapping: a `.codex-plugin/plugin.json` manifest with an `interface` block (official draw.io SVG logo, `brandColor`, default prompts). Nested under `codex/` because Codex requires the plugin root folder name to equal `plugin.json` `"name"` (`drawio`). No MCP required.
- **`plugins/copilot/`** — GitHub Copilot CLI plugin: the Copilot port of the Claude Code plugin, shipping the same `drawio` skill. `skills/drawio/SKILL.md` and `skills/drawio/scripts/fix_edge_parents.py` are byte-identical to the Claude plugin's copies (the in-skill `/drawio:drawio` example lines are model-facing; Copilot's user-facing command is plain `/drawio` since Copilot doesn't prefix plugin skills). Differs only in host wrapping: a root `plugin.json` manifest (Copilot's format, `skills` directory list); no folder-name rule, so `copilot/` is itself the plugin root. The same skill folder also works in other Copilot surfaces (VS Code agent mode, coding agent, code review) when copied to a repo's `.github/skills/`. No MCP required.
- **`plugins/tests/`** — Python `unittest` suite for the scripts the plugins bundle (currently `fix_edge_parents.py`, tested through the Claude Code copy — the Codex and Copilot copies are byte-identical, which `check-skill-sync.yml` enforces). Run `python3 -m unittest discover -s plugins/tests` from the repo root; CI runs it via `.github/workflows/test-scripts.yml`. The JavaScript projects keep their own tests next to their source (`mcp-tool-server/test/`, `node --test`).
- **`shape-search/`** — Shape search index generator. Loads draw.io's `app.min.js` via jsdom to extract all shape styles and tags into `search-index.json`, which powers the `search_shapes` MCP tool. Re-run after updating `drawio-dev` to pick up new or changed shapes.

Most subdirectories have their own `CLAUDE.md` with implementation details.
Expand Down
7 changes: 6 additions & 1 deletion plugins/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,8 @@ plugins/
├── claude-code/ ← Claude Code plugin (plugin root)
├── codex/ ← Codex host group
│ └── drawio/ ← Codex plugin root (folder name == plugin.json "name")
└── copilot/ ← GitHub Copilot CLI plugin (plugin root)
├── copilot/ ← GitHub Copilot CLI plugin (plugin root)
└── tests/ ← tests for the scripts the plugins bundle
```

Codex normalizes a plugin's root folder name to match its `plugin.json` `"name"`, so the
Expand All @@ -50,6 +51,10 @@ Claude Code and Copilot have no such rule, so `claude-code/` and `copilot/` are
the plugin roots. If another assistant (Cursor, etc.) is added later, it follows the same
pattern in its own way.

[`tests/`](tests/README.md) is not a plugin: it holds the Python `unittest` suite for the
scripts the plugins bundle (`python3 -m unittest discover -s plugins/tests` from the repo
root).

The draw.io guidance itself — *how* to generate `.drawio` files, embed XML in PNG/SVG/PDF, and produce `app.diagrams.net` URLs — is shared. Only the wrapping (manifest format, file layout, invocation prefix) differs per host, and each host has its own plugin/skill model, so the wrapping is not assumed to be uniform.

The single source of truth for draw.io XML generation guidance lives at [`../shared/xml-reference.md`](../shared/xml-reference.md) — every plugin references that file rather than duplicating its contents.
Expand Down
Loading