# Omnigraffle

> 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.

- Skill: `smartaicoach/omnigraffle` (Agent Skill, multi-file: 9 files)
- Install (CLI): `npx skillmds@latest add smartaicoach/omnigraffle`
- Raw SKILL.md: https://api.skillmd.com/api/skills/smartaicoach/omnigraffle/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Docs & Writing
- License: MIT License. Public. See LICENSE.txt.
- Author: smartaicoach (https://skillmd.com/u/smartaicoach)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/smartaicoach/omnigraffle

---


# 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

1. **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.
2. **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.
3. **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.
4. **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.
5. **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).
6. **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.
7. **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.
8. **Work in a temporary directory** for extraction and intermediate files (`mktemp -d`). Don't leave
   scratch files next to the user's documents.
9. **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.
10. **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):

```bash
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:

```bash
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:

```bash
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.

