macOS Desktop Launchers & Custom Icons

Superseded by the /squircle skill~/.claude/skills/squircle/SKILL.md. That skill has the full consolidated toolchain (letter, SF Symbol, emoji, setIcon) plus recipes, G&M palette, and the Desktop TCC playbook. This doc is retained for historical context only.

Creating a .app Launcher

A macOS .app is just a folder with a specific structure. No Xcode needed.

MyApp.app/
  Contents/
    Info.plist
    MacOS/
      MyApp          ← the executable (bash script)
    Resources/
      icon.icns      ← the icon

The shell script (Contents/MacOS/MyApp)

#!/bin/bash
# Check if server is running before starting
if ! lsof -i:PORT -sTCP:LISTEN -t &>/dev/null; then
    node /path/to/server.js &>/dev/null &
    sleep 1
fi
open http://localhost:PORT

Make it executable: chmod +x Contents/MacOS/MyApp

Info.plist

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <key>CFBundleExecutable</key>
    <string>MyApp</string>
    <key>CFBundleIconFile</key>
    <string>icon</string>
    <key>CFBundleIdentifier</key>
    <string>com.yourname.myapp</string>
    <key>CFBundleDisplayName</key>
    <string>My App</string>
    <key>CFBundleName</key>
    <string>MyApp</string>
    <key>CFBundlePackageType</key>
    <string>APPL</string>
    <key>CFBundleVersion</key>
    <string>1.0</string>
</dict>
</plist>

Setting Custom Icons (Folder or .app)

What DOESN'T work (save yourself the time)

What WORKS: Swift script

Write a Swift file, compile it, run it. Swift has full Cocoa access and bypasses the terminal sandboxing issues.

// /tmp/set_icon.swift
import AppKit

let size = CGFloat(512)
let image = NSImage(size: NSSize(width: size, height: size))
image.lockFocus()

// Background — rounded rect with color
let path = NSBezierPath(roundedRect: NSRect(x: 0, y: 0, width: size, height: size), xRadius: 80, yRadius: 80)
NSColor(red: 0.78, green: 0.1, blue: 0.1, alpha: 1.0).setFill()
path.fill()

// Emoji — NSAttributedString renders it correctly
let emoji = "🤖"
let font = NSFont.systemFont(ofSize: 360)
let attrs: [NSAttributedString.Key: Any] = [.font: font]
let str = NSAttributedString(string: emoji, attributes: attrs)
let strSize = str.size()
let point = NSPoint(x: (size - strSize.width) / 2, y: (size - strSize.height) / 2)
str.draw(at: point)

image.unlockFocus()

// Apply to target (folder or .app)
let target = CommandLine.arguments.count > 1 ? CommandLine.arguments[1] : "/path/to/target"
let result = NSWorkspace.shared.setIcon(image, forFile: target, options: [])
print(result ? "Success" : "Failed")

Compile and run:

swiftc /tmp/set_icon.swift -o /tmp/set_icon
/tmp/set_icon "/path/to/folder"
/tmp/set_icon "/path/to/MyApp.app"

Restart Finder after to see changes:

killall Finder

ICNS for the .app Resources folder

The icon.icns in Resources is what Finder/Dock uses for the app icon. Easiest way to generate it:

mkdir -p /tmp/myicon.iconset
sips -z 16 16   input.png --out /tmp/myicon.iconset/icon_16x16.png
sips -z 32 32   input.png --out /tmp/myicon.iconset/icon_16x16@2x.png
sips -z 32 32   input.png --out /tmp/myicon.iconset/icon_32x32.png
sips -z 64 64   input.png --out /tmp/myicon.iconset/icon_32x32@2x.png
sips -z 128 128 input.png --out /tmp/myicon.iconset/icon_128x128.png
sips -z 256 256 input.png --out /tmp/myicon.iconset/icon_128x128@2x.png
sips -z 256 256 input.png --out /tmp/myicon.iconset/icon_256x256.png
sips -z 512 512 input.png --out /tmp/myicon.iconset/icon_256x256@2x.png
sips -z 512 512 input.png --out /tmp/myicon.iconset/icon_512x512.png
iconutil -c icns /tmp/myicon.iconset -o /tmp/myicon.icns

Quick Checklist for Next Time

  1. Write the Swift script with your emoji + color
  2. swiftc to compile, run against folder or .app
  3. Copy the resulting .icns to Contents/Resources/icon.icns
  4. killall Finder to refresh
  5. Done — the .app itself is just a shell script wrapper, nothing precious