AgentStack
Browse Sign in
Browse Why AgentStack Sell Docs
Sign in
SKILL verified Apache-2.0 Self-run

Compose App Icon

skill-giginet-apple-icon-composer-skill-compose-app-icon · by giginet

Author and validate Apple Icon Composer `.icon` packages. Use this when the user asks to generate an app icon, scaffold a `.icon` from parameters, set up light/dark/tinted appearance variants (specializations), change any field of an existing `icon.json` (fills, blend modes, shadows, translucency, LiquidGlass Mode/Specular/Blur, layer layouts, asset filenames), or validate/diagnose a `.icon` pack…

No reviews yet
0 installs
37 views
0.0% view→install

Install

$ agentstack add skill-giginet-apple-icon-composer-skill-compose-app-icon

✓ scanned · ✓ verified, works with Claude Code, Cursor, and more.

Security review

✓ Passed

No issues found. Passed automated security review. · v0.1.0 How review works →

  • Prompt-injection patterns
  • Secret / credential exfiltration
  • Dangerous shell & filesystem operations
  • Untrusted network calls
  • Known-malicious package signatures

What it can access

  • Network access No
  • Filesystem access No
  • Shell / process execution No
  • Environment & secrets No
  • Dynamic code execution No

From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.

View the full security report →

Verified badge

Passed review? Show it. Paste this badge into your README, it links to the public security report.

AgentStack Verified badge Links to your public security report.
[![AgentStack Verified](https://agentstack.voostack.com/badges/verified.svg)](https://agentstack.voostack.com/security/report/skill-giginet-apple-icon-composer-skill-compose-app-icon)

Reliability & compatibility

Security review passed
0 installs to date
no reviews yet
3mo ago

Declared compatibility

Claude CodeClaude Desktop

Compatibility is declared by the source manifest. End-to-end runtime verification is coming, see below.

Preview Execution monitoring

We're building live execution health for every listing: tool-call success rate, median latency, uptime, and last-checked timestamps, measured, not self-reported. It isn't live yet, so we don't show numbers we can't stand behind.

How agent discovery & health will work →
Are you the author of Compose App Icon? Claim this listing to set pricing, connect Stripe payouts, and keep 70% of every sale.
Sign up to claim

About

Apple Icon Composer .icon packages

A .icon is a directory (macOS document package) containing a declarative icon.json and an Assets/ folder. This skill authors new packages, edits existing ones in place, and validates them against the bundled JSON Schema. All three workflows share one bundled uv project.

Preflight: confirm uv is installed

Before running any commands, execute which uv. If it exits non-zero (no uv on PATH), stop and report the error to the user — this skill requires uv. Do not fall back to a system python3; the bundled pyproject.toml pins requires-python = ">=3.9" and dependency versions via uv.lock.

Running the bundled CLIs

The two Python CLIs (create_icon.py, validate_icon.py), the icon-schema.json they validate against, and the pyproject.toml / uv.lock that pin their dependencies all live together in this skill's scripts/ directory, which is a self-contained uv project. Run every command from inside it:

# Claude Code / Codex plugin install:
cd "${CLAUDE_PLUGIN_ROOT}/skills/compose-app-icon/scripts"
# gh skill install: cd into the scripts/ directory next to this SKILL.md instead.

uv sync                                  # once, to populate .venv from uv.lock
uv run python create_icon.py   ...       # author a new .icon
uv run python validate_icon.py ...       # validate a .icon or icon.json

The rest of this document shows commands as uv run python .py ... — always run them from that scripts/ directory.

Creating a new .icon

uv run python create_icon.py \
    --output /path/to/Foo.icon \
    --icon /path/to/icon.json \
    --asset star.png=/path/to/star.png \
    --asset ring.png=/path/to/ring.png

Flags:

| Flag | Meaning | |---|---| | --output PATH | Target .icon directory (must end with .icon). | | --icon PATH | icon.json document, or - to read the JSON from stdin. | | --asset NAME=PATH | Register one image asset. Repeat for each image-name referenced in the document. NAME is the filename inside Assets/; PATH is the source file on disk. | | --force | Overwrite --output if it already exists. | | --no-validate | Skip JSON Schema validation (rarely what you want). |

create_icon.py validates against icon-schema.json before writing and checks every referenced image-name against the supplied --asset map.

Editing an existing .icon

There is no update subcommand. Because icon.json is just JSON and the schema is well-defined, edit the file in place with the Edit tool and then re-validate:

  1. Read the current .icon/icon.json to see what's there.
  2. Use the Edit tool to change exactly the field(s) the user asked about — a color string, a blend-mode enum, a position.scale, a specialization entry, etc. Refer to the schema sections below for allowed values.
  3. If an asset image needs to change, overwrite the file in .icon/Assets/ (same filename → no image-name edit needed; new filename → update every image-name / image-name-specializations.value that referenced the old name and place the new asset in Assets/).
  4. Re-validate the whole package so both the schema and the asset-reference cross-check pass:

``bash uv run python validate_icon.py /path/to/Foo.icon ``

Prefer minimal, targeted edits — keep keys in their existing order, don't reformat the file, and only add a -specializations array when the user actually wants a per-appearance override. The create_icon.py CLI's output format (sort_keys=True, 2-space indent) is the target style if the file is being re-written wholesale.

Validating a .icon or icon.json

uv run python validate_icon.py /path/to/Foo.icon
# or, for a bare document:
uv run python validate_icon.py /path/to/icon.json

validate_icon.py:

  1. Parses icon.json and checks it against icon-schema.json using jsonschema (Draft 2020-12).
  2. Reports every schema violation with a JSON pointer and the validator's message.
  3. When pointed at a .icon directory, cross-checks every image-name and image-name-specializations.value against the files in Assets/, reporting both missing and orphaned files.

| Flag | Meaning | |---|---| | (positional) | Either a .icon directory or an icon.json file. | | --skip-assets | Do not cross-check image-name references against Assets/. |

Exit codes: 0 = valid, 1 = schema or asset violation, 2 = bad input path.

Reading the output

Valid result:

VALID: /path/to/Foo.icon/icon.json

A non-fatal warning may follow when some files in Assets/ are not referenced by any layer:

warning: 2 unused asset(s) in Assets/: old-dark.png, old-light.png

Schema errors look like:

INVALID: /path/to/Foo.icon/icon.json (3 error(s))
  at /groups/0/layers/0
    Additional properties are not allowed ('fil' was unexpected)
  at /groups/0/shadow/kind
    'Natural' is not one of ['neutral', 'layer-color', 'none']
  at /groups/0/layers/1
    {'image-name-specializations': ...} is not valid under any of the given schemas

Missing asset errors look like:

INVALID: /path/to/Foo.icon has 1 missing asset(s)
  Assets/symbol-dark.png is referenced but not on disk

Common failure patterns

Every message below is produced by jsonschema and maps back to a specific rule in icon-schema.json.

  • Additional properties are not allowed — an unrecognized key (typo, wrong case, or a UI label written as JSON). First suspects: shadow.kind set to "Natural"/"Chromatic"/"Off" (use "neutral"/"layer-color"/"none"); a -specialization (singular) array (the key is always -specializations plural); typos like "ligthing".
  • 'X' is not one of [...] — enum mismatch; see the enum table below.
  • is not valid under any of the given schemas — a fill object, specialization value, or image-name choice failed every oneOf/anyOf branch. A fill must have exactly one of solid/automatic-gradient/linear-gradient; a fill-specializations entry's value may be a fill object or the literal string "automatic"; a layer must contain either image-name (string) or image-name-specializations (array).
  • 'X' is a required property — a required field is missing: groups and supported-platforms at top level; name on every layer; shadow.kind/shadow.opacity/translucency.enabled/translucency.value when the parent object is present.

Ground-truth check & rendering with ictool (macOS + Xcode only)

validate_icon.py checks the document against the JSON Schema, but the schema cannot model every constraint Icon Composer enforces at load time. When Xcode is installed, ictool — the command-line tool bundled inside Icon Composer.app — gives the authoritative answer by rendering the document the same way the app opens it, and as a bonus exports preview PNGs per platform / appearance.

This is optional and macOS-only: it is unavailable on agent hosts without Xcode, so always run validate_icon.py first as the portable check, then use ictool as a final confirmation and to produce previews when it's present.

Locating ictool

xcode-select -p prints the active Xcode's Developer directory (e.g. /Applications/Xcode.app/Contents/Developer); ictool lives one level up under Applications/Icon Composer.app:

ICTOOL="$(dirname "$(xcode-select -p)")/Applications/Icon Composer.app/Contents/Executables/ictool"
[ -x "$ICTOOL" ] || { echo "ictool not found — Xcode 26+ with Icon Composer required"; }
"$ICTOOL" --version          # {"bundle-version": "98", "short-bundle-version": "1.5"}

Rendering a rendition (and validating by side effect)

"$ICTOOL" /path/to/Foo.icon \
    --export-image --output-file /tmp/foo.png \
    --platform iOS --rendition Default --width 1024 --height 1024 --scale 2

| Flag | Meaning | |---|---| | --export-image | The only operation; renders the document to --output-file (PNG). | | --output-file PATH | Where to write the rendered PNG. | | --platform | iOS, macOS, or watchOS. | | --rendition | Default, Dark, TintedLight, TintedDark, ClearLight, ClearDark. | | --width / --height / --scale | Output size in points × scale (e.g. 1024 1024 2 → 2048×2048 px). | | --light-angle | (optional) lighting angle. | | --tint-color / --tint-strength | (optional) tint for the Tinted* renditions; each takes a single value, e.g. --tint-color 0.25 --tint-strength 0.75. |

There is no separate validate subcommand — validation is a side effect of rendering:

  • Success → exit 0, prints {}, and writes the PNG. Icon Composer can open the document.
  • Failure → non-zero exit and The data couldn't be read because it is missing. (or a more specific message). Icon Composer would refuse to open it, even if validate_icon.py said VALID.

So after authoring or editing, render the Default rendition (and Dark / a Tinted* one if the icon uses specializations) to confirm the package actually opens and to eyeball the result. This is exactly what catches engine-level issues the schema can't — for example a position with scale but no translation-in-points, which renders fine only once both keys are present.

Canvas and asset sizing

Icon Composer's design canvas is 1024 × 1024 points. Image assets should be 1024 × 1024 PNG (or SVG) with the visible content centered; position.translation-in-points operates in this 1024-point coordinate system, so [0, 0] means no offset from the canvas center. Smaller assets render at their native size and look visually smaller than the canvas.

icon.json — top-level shape

{
  "color-space-for-untagged-svg-colors": "display-p3",   // optional: "srgb" | "display-p3"
  "fill": { ... },                                        // OR fill-specializations (background)
  "fill-specializations": [ ... ],                        // per-appearance background fill
  "groups": [ ... ],                                      // REQUIRED: ordered layer groups
  "supported-platforms": { "squares": "shared" }          // REQUIRED
}

Groups — one record per LiquidGlass-rendered bundle

A group shares the same LiquidGlass rendering pipeline across its layers and carries these properties (each with an optional sibling -specializations):

| JSON key | Type | UI label | Notes | |---|---|---|---| | lighting | "individual" \| "combined" | Mode | How light interacts per-layer or across the group. | | specular | boolean | Specular | Highlight on/off. | | blur | number 0–1 | Blur | Background blur amount. | | translucency | { enabled: bool, value: number } | Translucency | | | shadow | { kind: string, opacity: number } | Shadow | See the UI ↔ JSON table below. | | position | { scale: number, translation-in-points: [x, y] } | Composition.Layout | Omit when identity; otherwise include both keys (see gotchas). |

Layers — image-backed records inside a group

| JSON key | Type | Category | Notes | |---|---|---|---| | name | string | — | Required display name. | | image-name | string | Composition.Layout | Filename in Assets/. Required unless image-name-specializations is present. | | image-name-specializations | array | Composition.Layout | Per-appearance filenames. | | fill | fill object | Color | See Fill below. | | fill-specializations | array | Color | | | blend-mode | string | Color | Enum: normal, darken, multiply, plus-darker, lighten, screen, plus-lighter, overlay, soft-light, hard-light. | | blend-mode-specializations | array | Color | | | opacity | number 0–1 | Color | | | opacity-specializations | array | Color | | | glass | boolean | Effects | LiquidGlass on/off for this layer (not the same as group-level specular). | | glass-specializations | array | Effects | | | hidden | boolean | Composition.Visible | | | hidden-specializations | array | Composition.Visible | | | position | position object | Composition.Layout | | | position-specializations | array | Composition.Layout | |

Fill — three alternative shapes

{ "solid":              "extended-srgb:1.0,1.0,1.0,1.0" }
{ "automatic-gradient": "extended-srgb:0.0,0.5,1.0,1.0" }
{ "linear-gradient":   ["extended-srgb:...", "extended-srgb:..."] }

Color strings are :,,.... Common spaces: extended-srgb, display-p3, extended-gray.

Specializations — per-appearance overrides

Icon Composer supports three appearances: light (default), dark, tinted. Any specializable property X has an optional sibling array X-specializations:

"fill-specializations": [
  { "value": { "automatic-gradient": "extended-srgb:0,0.53,1,1" } }, // omitting appearance = default/light
  { "appearance": "dark",   "value": { "linear-gradient": [ "...", "..." ] } },
  { "appearance": "tinted", "value": "automatic" }                    // inherit default
]
  • Omitting appearance usually targets light, but "light" may also be set explicitly.
  • value may be the literal string "automatic" to inherit the default appearance's value.

Specializations exist for exactly these properties:

  • Color: fill, blend-mode, opacity
  • LiquidGlass (group): lighting, specular, blur, translucency, shadow (plus the nested keys translucency.enabled / translucency.value / shadow.kind / shadow.opacity)
  • Effects (layer): glass
  • Composition.Visible (layer): hidden
  • Composition.Layout (layer & group): image-name, position

UI ↔ JSON label mapping

Several Icon Composer UI labels differ from the JSON keys they write.

| UI | JSON | |---|---| | Shadow: Natural | shadow.kind: "neutral" | | Shadow: Chromatic | shadow.kind: "layer-color" | | Shadow: Off | shadow.kind: "none" | | Blend Mode: Plus Darker | "plus-darker" | | Blend Mode: Plus Lighter | "plus-lighter" | | Blend Mode: Soft / Hard Light | "soft-light" / "hard-light" | | LiquidGlass: Mode | lighting |

Blend modes are otherwise the UI label lower-cased and kebab-cased.

Minimal example

# Compose the icon document
cat > /tmp/icon.json <<'JSON'
{
  "fill": { "automatic-gradient": "extended-srgb:0.20,0.50,1.00,1.00" },
  "groups": [{
    "layers": [
      { "name": "symbol", "image-name": "symbol.png", "glass": true }
    ],
    "shadow": { "kind": "neutral", "opacity": 0.5 },
    "translucency": { "enabled": true, "value": 0.5 }
  }],
  "supported-platforms": { "squares": "shared" }
}
JSON

uv run python create_icon.py \
    --output /tmp/Hello.icon \
    --icon /tmp/icon.json \
    --asset symbol.png=/path/to/symbol-1024.png

Dark-mode specialization example

// icon.json snippet: same layer, ring is cream in light, gradient in dark
{
  "name": "ring",
  "image-name": "ring.png",
  "fill-specializations": [
    { "value": { "solid": "extended-srgb:1.00,0.95,0.70,1.00" } },
    { "appearance": "dark",
      "value": { "linear-gradient": [
        "extended-srgb:0.95,0.30,0.60,1",
        "extended-srgb:0.40,0.20,0.80,1"
      ] } }
  ]
}

Shortcut: list every asset referenced by a document

jq -r '
  [
    .groups[].layers[]
    | (."image-name"? // empty),
      (."image-name-specializations"? // [] | .[] | .value)
  ]
  | unique[]
' /path/to/icon.json

Use this to build the --asset flags for create_icon.py when retrofitting an existing document.

Gotchas

  • Use JSON values, not UI labels ("neutral" not "Natural", "layer-color" not "Chromatic").
  • A position object must carry both scale and translation-in-points. A scale-only position validates against older schemas but Icon Composer 1.5 refuses to open the package (The document … could not be opened. The data is missing.); always pair scale with translation-in-points (use [0, 0] when there is no offset

Source & license

This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.

Install and usage instructions live in the source repository linked above.

Reviews

No reviews yet, be the first.

Versions

  • v0.1.0 Imported from the upstream source.