#!/usr/bin/swift // 🤖 Regenerates a Mac dev-alt icon set from the canonical app icon: the // SAME purple "11" tile, hue-rotated so a dev build is unmistakable in the // Dock without inventing a new mark (CLAUDE.md: never hand-roll a // substitute glyph). Two variants: "dev" (orange, the default) and "live" // (green — the convention for a dev build aimed at PRODUCTION home, so two // running alts with different aims never share an icon). Run from the repo // root: // // swift tools/make-dev-icon.swift # AppIconDev (orange) // swift tools/make-dev-icon.swift live # AppIconDevLive (green) // // Committed output; re-run only if the canonical icon changes. import AppKit import CoreImage // Purple sits ~253°; orange ~30° (-220°), green ~130° (-123°). White and // near-white (the "11") are hue-less, so they ride through untouched. let variants: [String: (set: String, angle: Double)] = [ "dev": ("AppIconDev", -220.0), "live": ("AppIconDevLive", -123.0), ] let name = CommandLine.arguments.count > 1 ? CommandLine.arguments[1] : "dev" guard let variant = variants[name] else { fatalError("unknown variant \(name) — expected: \(variants.keys.sorted().joined(separator: ", "))") } let src = "ios/LChatMac/Assets.xcassets/AppIcon.appiconset/icon_1024.png" let outDir = "ios/LChatMac/Assets.xcassets/\(variant.set).appiconset" guard let image = CIImage(contentsOf: URL(fileURLWithPath: src)) else { fatalError("can't read \(src) — run from the repo root") } let hue = CIFilter(name: "CIHueAdjust", parameters: [ kCIInputImageKey: image, kCIInputAngleKey: NSNumber(value: variant.angle * .pi / 180.0), ])! guard let tinted = hue.outputImage else { fatalError("hue adjust failed") } let ctx = CIContext() guard let full = ctx.createCGImage(tinted, from: tinted.extent) else { fatalError("render failed") } try? FileManager.default.createDirectory(atPath: outDir, withIntermediateDirectories: true) for size in [16, 32, 64, 128, 256, 512, 1024] { let rep = NSBitmapImageRep(bitmapDataPlanes: nil, pixelsWide: size, pixelsHigh: size, bitsPerSample: 8, samplesPerPixel: 4, hasAlpha: true, isPlanar: false, colorSpaceName: .deviceRGB, bytesPerRow: 0, bitsPerPixel: 0)! let nsCtx = NSGraphicsContext(bitmapImageRep: rep)! NSGraphicsContext.saveGraphicsState() NSGraphicsContext.current = nsCtx nsCtx.cgContext.interpolationQuality = .high nsCtx.cgContext.draw(full, in: CGRect(x: 0, y: 0, width: size, height: size)) NSGraphicsContext.restoreGraphicsState() let png = rep.representation(using: .png, properties: [:])! try! png.write(to: URL(fileURLWithPath: "\(outDir)/icon_\(size).png")) print("wrote \(outDir)/icon_\(size).png") } let contents = """ { "images" : [ { "idiom" : "mac", "size" : "16x16", "scale" : "1x", "filename" : "icon_16.png" }, { "idiom" : "mac", "size" : "16x16", "scale" : "2x", "filename" : "icon_32.png" }, { "idiom" : "mac", "size" : "32x32", "scale" : "1x", "filename" : "icon_32.png" }, { "idiom" : "mac", "size" : "32x32", "scale" : "2x", "filename" : "icon_64.png" }, { "idiom" : "mac", "size" : "128x128", "scale" : "1x", "filename" : "icon_128.png" }, { "idiom" : "mac", "size" : "128x128", "scale" : "2x", "filename" : "icon_256.png" }, { "idiom" : "mac", "size" : "256x256", "scale" : "1x", "filename" : "icon_256.png" }, { "idiom" : "mac", "size" : "256x256", "scale" : "2x", "filename" : "icon_512.png" }, { "idiom" : "mac", "size" : "512x512", "scale" : "1x", "filename" : "icon_512.png" }, { "idiom" : "mac", "size" : "512x512", "scale" : "2x", "filename" : "icon_1024.png" } ], "info" : { "version" : 1, "author" : "xcode" } } """ try! contents.write(toFile: "\(outDir)/Contents.json", atomically: true, encoding: .utf8) print("wrote \(outDir)/Contents.json")