OmniGraffle
Written by the repository maintainer, 2026. Public — MIT License (see LICENSE.txt).
Work with OmniGraffle documents (.graffle, .gtemplate, .gstencil) safely and correctly: inspect
them, convert other formats into them, edit or repair them, and export them back out. OmniGraffle
files are not plain text and not one fixed binary shape — the same .graffle extension can hide a
zip archive, a Finder-style package directory, a gzip-compressed plist, a raw XML plist, or a binary
plist. Treat every document as unknown until detected, and prefer OmniGraffle itself to write, import,
render, and validate the result rather than hand-editing serialized data.
Before doing anything: restate scope
State the source path and the requested output path (or, for pure inspection, just the source path).
Classify the request into one of: inspect, modify, create, import (into OmniGraffle), export (out of
OmniGraffle), repair, or validate. This classification decides which section below applies and what
"done" means.
Non-negotiable safety rules
- Never overwrite the source file. Always write to a new output path or a timestamped backup
before touching anything. If the user gave an output path, use it; if they didn't and this is a
modification of an existing document, create
<name>_<YYYYMMDD-HHMMSS>.graffle next to the
original rather than replacing it, and say so.
- Never upload a customer or user document to any online converter or web service. All
conversion happens locally through OmniGraffle itself,
plutil, plistlib, or the bundled
scripts.
- Never treat a binary
.graffle as ordinary text. Don't cat, sed, or grep -text a raw
document. Detect its variant first (scripts/grafflectl detect) and use the matching reader.
- Never do a blind search-and-replace inside
data.plist. Plists are typed, nested structures;
a text-level replace can corrupt a value's type, truncate binary data, or land inside the wrong
key entirely. Load it structurally (plistlib, or plutil -convert) before changing anything.
- Never regenerate a whole document from a simplified JSON summary. A JSON inventory built for
Claude to read is deliberately lossy (see
references/file-formats.md). Writing a document back
from that summary silently drops unknown keys, embedded assets, styles, and metadata. Only the
typed-JSON round trip described in references/file-formats.md is safe to write back from, and
even then only as a controlled fallback (see Rule 7).
- Preserve everything not explicitly being changed — unknown plist keys, binary values, embedded
assets, styles, layers, IDs, and relationships. When in doubt, copy the input forward unchanged
rather than reconstructing it.
- Prefer OmniGraffle itself as the writer, importer, renderer, and validator. Direct plist
patching (
references/file-formats.md, references/validation.md) is a controlled fallback for
the narrow case where native automation genuinely cannot expose the needed property — not a
default path.
- Work in a temporary directory for extraction and intermediate files (
mktemp -d). Don't leave
scratch files next to the user's documents.
- Do not claim a creation, edit, or conversion is complete until the output has been saved, closed,
and reopened in OmniGraffle without error, per
references/validation.md. Inspection-only tasks
don't require this (OmniGraffle doesn't need to be running to read a file), but anything that
writes a document does.
- If OmniGraffle is unavailable, or its installed license can't write the required format, report
the blocker plainly rather than silently falling back to something weaker. Say what's missing
and what would unblock it.
Step 1: Environment discovery
Run this once per session (skip if already done this session):
scripts/grafflectl doctor
This confirms: macOS, OmniGraffle's install path/version/build/bundle identifier, whether its
scripting dictionary exposes the operations this skill needs, Python 3 + plistlib, the standard
CLI tools (file, unzip, gzip, ditto, plutil, osascript, sdef, open), and temp-directory
write access. Read references/automation.md for what the doctor output means and how to react to a
missing capability (e.g. OmniGraffle not found, or an older version whose dictionary lacks an
operation this task needs).
Locate OmniGraffle by bundle identifier, not by a hardcoded application name — a user may have
OmniGraffle 6, 7, or a renamed/relocated copy. scripts/omnigraffle_bridge.applescript application-info does this and records path, version, build, bundle ID, and (via sdef) which
scripting commands are actually available. Never call an AppleScript or Omni Automation command that
sdef didn't confirm exists.
Step 2: Detect and inspect the input
For any existing .graffle/.gtemplate/.gstencil, or any SVG/PDF/Visio file being converted in:
scripts/grafflectl detect "<input>"
scripts/grafflectl inspect "<input>"
detect identifies which of the five OmniGraffle variants (or which foreign format) this file is,
using magic bytes and file/unzip -l/plutil — never by guessing from the extension. inspect
prints a short structural summary (canvas count, layer count, top-level graphic counts, embedded
asset count, document/app version) without dumping the full plist into context. Full detail lives in
references/file-formats.md.
Do not put the raw plist or the raw SVG source into your own context as a first move. Build a
concise inventory instead:
scripts/grafflectl inventory "<input>" /tmp/<workdir>/inventory.json
Then read the resulting JSON (it's small) rather than the source document. The inventory covers
document/application versions; canvas names, IDs, sizes, order; layer names, order, visibility,
locking, printability; the graphic hierarchy, classes, names, and user data; text; bounds and
rotation; shape types; groups and nested groups; lines and endpoints; connections and magnets; fill,
stroke, font, and text properties; image references; embedded asset filenames; notes/metadata; and
cross-references. See references/file-formats.md for what each of these looks like inside the plist
and how the script builds them defensively (OmniGraffle's plist schema varies slightly by document
and app version — the script introspects rather than assuming a fixed shape, and so should any manual
follow-up).
Step 3: Choose the method — native automation first
| Task |
Preferred method |
Fallback |
| Create a new document, add/edit canvases, layers, shapes, lines, text, groups, styles |
OmniGraffle native object model via AppleScript / Omni Automation JavaScript (scripts/omnigraffle_bridge.applescript) |
Direct plist patch only if the property truly isn't exposed |
Import SVG → native .graffle |
OmniGraffle's own SVG open/import |
Never hand-roll an SVG→plist translator |
Export .graffle → PDF/PNG/SVG |
OmniGraffle's own export |
— |
| Repair a damaged document |
Open in OmniGraffle if it opens at all; otherwise the plist-patch fallback in references/validation.md |
— |
| Read-only inspection |
scripts/graffle_plist.py (no OmniGraffle required) |
— |
Read references/automation.md before calling the bridge: it explains how the bridge determines
which AppleScript verbs and which Omni Automation JavaScript entry points are actually available for
the installed version, and how to fall back correctly when one path isn't exposed. Never hard-code an
application name, file-type identifier, or scripting command the installed dictionary hasn't
confirmed.
Step 4: Do the work
- SVG → OmniGraffle: follow
references/conversion-workflows.md in full — it covers SVG
inventory (viewBox, paths, groups, text, fonts, transforms, gradients, clipping, masks, markers,
embedded/external images), resource resolution, the native import call, whether the result stayed
vector-editable or got flattened into one object, and the visual diff pass against the source.
- Export: use
scripts/omnigraffle_bridge.applescript export-pdf|export-png|export-svg. Confirm
canvas count and dimensions match expectations before declaring the export done.
- Direct plist patching (fallback only): follow the procedure in
references/file-formats.md
("Direct plist patching") exactly — extract to a temp workspace, parse with plistlib, apply a
narrow deterministic change, preserve every untouched key and asset, re-serialize, rebuild any zip
archive with files at the archive root (no extra enclosing directory), then hand the provisional
result back to OmniGraffle for open → save → close → reopen → render before trusting it.
Step 5: Validate
Every task that produced or changed a document goes through references/validation.md's checklist
before you report success: structural validity (archive integrity, data.plist parses, every
referenced asset exists), OmniGraffle opens it without error, saves it, closes it, reopens the saved
copy, and every relevant canvas renders. For conversions specifically, check for missing graphics,
displaced objects, changed colors, broken gradients, wrong transparency or clipping, font
substitution, altered text, wrong line endings, and unexpected flattening — and fix confirmed
differences, then re-run the save/close/reopen/render cycle.
If something in this checklist can't be completed (OmniGraffle isn't installed, a needed operation
isn't in the scripting dictionary, a feature genuinely doesn't survive import), that is a reported
limitation, not a silent gap.
Step 6: Report
Close with a completion report — see references/validation.md for the exact fields (source path,
output path, backup path, OmniGraffle version used, writing/import method, whether objects stayed
editable, canvas/layer/graphic/asset counts, structural validation result, reopen test result, render
validation result, confirmed limitations). Don't claim something was copied, matched, converted, or
validated unless it actually happened in this session.
Reference map
| File |
Read it when |
references/file-formats.md |
Detecting a variant, understanding data.plist structure, building an inventory, or doing a plist-patch fallback |
references/automation.md |
Calling the AppleScript/Omni Automation bridge, or reacting to a missing capability from doctor |
references/conversion-workflows.md |
Converting SVG (or another format) into .graffle, or exporting out |
references/validation.md |
Before reporting any create/edit/import/export/repair as complete |
Scripts
| Script |
Purpose |
scripts/grafflectl |
Single entry point: detect, inspect, extract, inventory, import-svg, render, export, validate, compare, doctor |
scripts/graffle_plist.py |
Format-agnostic plist operations: detect, inspect, extract, inventory, plist↔typed-JSON, validate, compare-structure. Works without OmniGraffle installed. |
scripts/omnigraffle_bridge.applescript |
Native automation: application-info, open, create, save-copy, close, reopen, import-svg, combine-svgs, export-pdf/png/svg, inventory-native, validate-open |
grafflectl shells out to both of the above — call grafflectl for everyday use, and drop to the
underlying scripts directly only when a task needs an operation grafflectl doesn't wrap yet.
1---2name: omnigraffle3description: Use whenever an OmniGraffle document or .graffle, .gtemplate, or .gstencil file is involved, including reading, inspecting, extracting, creating, editing, repairing, rendering, validating, or converting SVG, PDF, Visio, and other supported formats to or from OmniGraffle.4license: MIT License. Public. See LICENSE.txt.5---67# OmniGraffle89Written by the repository maintainer, 2026. Public — MIT License (see `LICENSE.txt`).1011Work with OmniGraffle documents (`.graffle`, `.gtemplate`, `.gstencil`) safely and correctly: inspect12them, convert other formats into them, edit or repair them, and export them back out. OmniGraffle13files are not plain text and not one fixed binary shape — the same `.graffle` extension can hide a14zip archive, a Finder-style package directory, a gzip-compressed plist, a raw XML plist, or a binary15plist. Treat every document as unknown until detected, and prefer OmniGraffle itself to write, import,16render, and validate the result rather than hand-editing serialized data.1718## Before doing anything: restate scope1920State the source path and the requested output path (or, for pure inspection, just the source path).21Classify the request into one of: inspect, modify, create, import (into OmniGraffle), export (out of22OmniGraffle), repair, or validate. This classification decides which section below applies and what23"done" means.2425## Non-negotiable safety rules26271. **Never overwrite the source file.** Always write to a new output path or a timestamped backup28 before touching anything. If the user gave an output path, use it; if they didn't and this is a29 modification of an existing document, create `<name>_<YYYYMMDD-HHMMSS>.graffle` next to the30 original rather than replacing it, and say so.312. **Never upload a customer or user document to any online converter or web service.** All32 conversion happens locally through OmniGraffle itself, `plutil`, `plistlib`, or the bundled33 scripts.343. **Never treat a binary `.graffle` as ordinary text.** Don't `cat`, `sed`, or `grep -text` a raw35 document. Detect its variant first (`scripts/grafflectl detect`) and use the matching reader.364. **Never do a blind search-and-replace inside `data.plist`.** Plists are typed, nested structures;37 a text-level replace can corrupt a value's type, truncate binary data, or land inside the wrong38 key entirely. Load it structurally (`plistlib`, or `plutil -convert`) before changing anything.395. **Never regenerate a whole document from a simplified JSON summary.** A JSON inventory built for40 Claude to read is deliberately lossy (see `references/file-formats.md`). Writing a document back41 from that summary silently drops unknown keys, embedded assets, styles, and metadata. Only the42 typed-JSON round trip described in `references/file-formats.md` is safe to write back from, and43 even then only as a controlled fallback (see Rule 7).446. **Preserve everything not explicitly being changed** — unknown plist keys, binary values, embedded45 assets, styles, layers, IDs, and relationships. When in doubt, copy the input forward unchanged46 rather than reconstructing it.477. **Prefer OmniGraffle itself as the writer, importer, renderer, and validator.** Direct plist48 patching (`references/file-formats.md`, `references/validation.md`) is a controlled fallback for49 the narrow case where native automation genuinely cannot expose the needed property — not a50 default path.518. **Work in a temporary directory** for extraction and intermediate files (`mktemp -d`). Don't leave52 scratch files next to the user's documents.539. **Do not claim a creation, edit, or conversion is complete until the output has been saved, closed,54 and reopened in OmniGraffle without error**, per `references/validation.md`. Inspection-only tasks55 don't require this (OmniGraffle doesn't need to be running to read a file), but anything that56 writes a document does.5710. **If OmniGraffle is unavailable, or its installed license can't write the required format, report58 the blocker plainly** rather than silently falling back to something weaker. Say what's missing59 and what would unblock it.6061## Step 1: Environment discovery6263Run this once per session (skip if already done this session):6465```bash66scripts/grafflectl doctor67```6869This confirms: macOS, OmniGraffle's install path/version/build/bundle identifier, whether its70scripting dictionary exposes the operations this skill needs, Python 3 + `plistlib`, the standard71CLI tools (`file`, `unzip`, `gzip`, `ditto`, `plutil`, `osascript`, `sdef`, `open`), and temp-directory72write access. Read `references/automation.md` for what the doctor output means and how to react to a73missing capability (e.g. OmniGraffle not found, or an older version whose dictionary lacks an74operation this task needs).7576Locate OmniGraffle by bundle identifier, not by a hardcoded application name — a user may have77OmniGraffle 6, 7, or a renamed/relocated copy. `scripts/omnigraffle_bridge.applescript78application-info` does this and records path, version, build, bundle ID, and (via `sdef`) which79scripting commands are actually available. Never call an AppleScript or Omni Automation command that80`sdef` didn't confirm exists.8182## Step 2: Detect and inspect the input8384For any existing `.graffle`/`.gtemplate`/`.gstencil`, or any SVG/PDF/Visio file being converted in:8586```bash87scripts/grafflectl detect "<input>"88scripts/grafflectl inspect "<input>"89```9091`detect` identifies which of the five OmniGraffle variants (or which foreign format) this file is,92using magic bytes and `file`/`unzip -l`/`plutil` — never by guessing from the extension. `inspect`93prints a short structural summary (canvas count, layer count, top-level graphic counts, embedded94asset count, document/app version) without dumping the full plist into context. Full detail lives in95`references/file-formats.md`.9697Do **not** put the raw plist or the raw SVG source into your own context as a first move. Build a98concise inventory instead:99100```bash101scripts/grafflectl inventory "<input>" /tmp/<workdir>/inventory.json102```103104Then read the resulting JSON (it's small) rather than the source document. The inventory covers105document/application versions; canvas names, IDs, sizes, order; layer names, order, visibility,106locking, printability; the graphic hierarchy, classes, names, and user data; text; bounds and107rotation; shape types; groups and nested groups; lines and endpoints; connections and magnets; fill,108stroke, font, and text properties; image references; embedded asset filenames; notes/metadata; and109cross-references. See `references/file-formats.md` for what each of these looks like inside the plist110and how the script builds them defensively (OmniGraffle's plist schema varies slightly by document111and app version — the script introspects rather than assuming a fixed shape, and so should any manual112follow-up).113114## Step 3: Choose the method — native automation first115116| Task | Preferred method | Fallback |117|---|---|---|118| Create a new document, add/edit canvases, layers, shapes, lines, text, groups, styles | OmniGraffle native object model via AppleScript / Omni Automation JavaScript (`scripts/omnigraffle_bridge.applescript`) | Direct plist patch only if the property truly isn't exposed |119| Import SVG → native `.graffle` | OmniGraffle's own SVG open/import | Never hand-roll an SVG→plist translator |120| Export `.graffle` → PDF/PNG/SVG | OmniGraffle's own export | — |121| Repair a damaged document | Open in OmniGraffle if it opens at all; otherwise the plist-patch fallback in `references/validation.md` | — |122| Read-only inspection | `scripts/graffle_plist.py` (no OmniGraffle required) | — |123124Read `references/automation.md` before calling the bridge: it explains how the bridge determines125which AppleScript verbs and which Omni Automation JavaScript entry points are actually available for126the installed version, and how to fall back correctly when one path isn't exposed. Never hard-code an127application name, file-type identifier, or scripting command the installed dictionary hasn't128confirmed.129130## Step 4: Do the work131132- **SVG → OmniGraffle**: follow `references/conversion-workflows.md` in full — it covers SVG133 inventory (viewBox, paths, groups, text, fonts, transforms, gradients, clipping, masks, markers,134 embedded/external images), resource resolution, the native import call, whether the result stayed135 vector-editable or got flattened into one object, and the visual diff pass against the source.136- **Export**: use `scripts/omnigraffle_bridge.applescript export-pdf|export-png|export-svg`. Confirm137 canvas count and dimensions match expectations before declaring the export done.138- **Direct plist patching (fallback only)**: follow the procedure in `references/file-formats.md`139 ("Direct plist patching") exactly — extract to a temp workspace, parse with `plistlib`, apply a140 narrow deterministic change, preserve every untouched key and asset, re-serialize, rebuild any zip141 archive with files at the archive root (no extra enclosing directory), then hand the provisional142 result back to OmniGraffle for open → save → close → reopen → render before trusting it.143144## Step 5: Validate145146Every task that produced or changed a document goes through `references/validation.md`'s checklist147before you report success: structural validity (archive integrity, `data.plist` parses, every148referenced asset exists), OmniGraffle opens it without error, saves it, closes it, reopens the saved149copy, and every relevant canvas renders. For conversions specifically, check for missing graphics,150displaced objects, changed colors, broken gradients, wrong transparency or clipping, font151substitution, altered text, wrong line endings, and unexpected flattening — and fix confirmed152differences, then re-run the save/close/reopen/render cycle.153154If something in this checklist can't be completed (OmniGraffle isn't installed, a needed operation155isn't in the scripting dictionary, a feature genuinely doesn't survive import), that is a reported156limitation, not a silent gap.157158## Step 6: Report159160Close with a completion report — see `references/validation.md` for the exact fields (source path,161output path, backup path, OmniGraffle version used, writing/import method, whether objects stayed162editable, canvas/layer/graphic/asset counts, structural validation result, reopen test result, render163validation result, confirmed limitations). Don't claim something was copied, matched, converted, or164validated unless it actually happened in this session.165166## Reference map167168| File | Read it when |169|---|---|170| `references/file-formats.md` | Detecting a variant, understanding `data.plist` structure, building an inventory, or doing a plist-patch fallback |171| `references/automation.md` | Calling the AppleScript/Omni Automation bridge, or reacting to a missing capability from `doctor` |172| `references/conversion-workflows.md` | Converting SVG (or another format) into `.graffle`, or exporting out |173| `references/validation.md` | Before reporting any create/edit/import/export/repair as complete |174175## Scripts176177| Script | Purpose |178|---|---|179| `scripts/grafflectl` | Single entry point: `detect`, `inspect`, `extract`, `inventory`, `import-svg`, `render`, `export`, `validate`, `compare`, `doctor` |180| `scripts/graffle_plist.py` | Format-agnostic plist operations: detect, inspect, extract, inventory, plist↔typed-JSON, validate, compare-structure. Works without OmniGraffle installed. |181| `scripts/omnigraffle_bridge.applescript` | Native automation: application-info, open, create, save-copy, close, reopen, import-svg, combine-svgs, export-pdf/png/svg, inventory-native, validate-open |182183`grafflectl` shells out to both of the above — call `grafflectl` for everyday use, and drop to the184underlying scripts directly only when a task needs an operation `grafflectl` doesn't wrap yet.