macOS Desktop Launchers & Custom Icons
Superseded by the
/squircleskill —~/.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)
fileiconCLI — fails with "no write permissions" even when you own the folder. It's a macOS sandboxing issue with the terminal process, not a Unix permissions issue.- osascript
set icon of folder— Finder AppleScript returns -10006 error. NSWorkspace setIcon:forFile:via osascript — returnsfalse, same sandboxing block.- Pillow (Python) — can render a red background fine, but emoji from
Apple Color Emoji.ttcdoesn't render correctly. The folder ends up red with no visible emoji. - PyObjC — not available in the Homebrew Python environment.
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
- Write the Swift script with your emoji + color
swiftcto compile, run against folder or .app- Copy the resulting .icns to
Contents/Resources/icon.icns killall Finderto refresh- Done — the .app itself is just a shell script wrapper, nothing precious