From appkit
Build and run workflow for native macOS AppKit apps — project creation with Tuist, the build-and-run.sh helper, xcodebuild, launching the .app, reading stdout/crash logs, error diagnosis, and prerequisites. Use when building, running, or fixing build errors in an AppKit / Swift / Cocoa project.
How this skill is triggered — by the user, by Claude, or both
Slash command
/appkit:appkit-dev-workflowThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
A native AppKit GUI app must be a **`.app` bundle** with an `Info.plist` — you cannot just `swift run` an executable and get a proper menu bar, Dock icon, window activation, or resource bundle. The supported, agent-friendly scaffold is an **Xcode project generated from a declarative `Project.swift` via [Tuist](https://docs.tuist.dev)** (`brew install tuist`). This is the AppKit analog of `dotne...
A native AppKit GUI app must be a .app bundle with an Info.plist — you cannot just swift run an executable and get a proper menu bar, Dock icon, window activation, or resource bundle. The supported, agent-friendly scaffold is an Xcode project generated from a declarative Project.swift via Tuist (brew install tuist). This is the AppKit analog of dotnet new: write source + Project.swift, run tuist generate, build with xcodebuild.
New app — write a Project.swift (start from templates/Project.swift in this skill) plus minimal sources, then generate:
tuist generate --no-open # produces MyApp.xcodeproj + MyApp.xcworkspace from Project.swift
Canonical no-storyboard entry point (Sources/main.swift) — robust, avoids nib/storyboard requirements:
import AppKit
MainActor.assumeIsolated {
let app = NSApplication.shared
let delegate = AppDelegate()
app.delegate = delegate
app.setActivationPolicy(.regular) // shows in Dock + gets a menu bar
app.activate() // NSApp.activate (macOS 14+); foregrounds the app
app.run()
}
// Sources/AppDelegate.swift
import AppKit
@MainActor
final class AppDelegate: NSObject, NSApplicationDelegate {
private var windowController: MainWindowController?
func applicationDidFinishLaunching(_ notification: Notification) {
let wc = MainWindowController()
wc.showWindow(nil)
windowController = wc
}
func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool { true }
}
Build the window/content in an NSWindowController + NSViewController (see the appkit-design skill for layout, controls, sizing, and Liquid Glass).
Existing app — read the project to understand its shape before changing anything:
*.xcodeproj / *.xcworkspace, or Project.swift (Tuist) / project.yml (XcodeGen) / Package.swift (SwiftPM).MACOSX_DEPLOYMENT_TARGET) and Swift language mode.Package.resolved, or CocoaPods/Carthage if present).For a small dependency graph, declare SwiftPM packages inline in Project.swift under packages: and reference them per-target via .package(product:), then regenerate:
let project = Project(
name: "MyApp",
packages: [
.remote(url: "https://github.com/sparkle-project/Sparkle",
requirement: .upToNextMajor(from: "2.0.0")),
],
targets: [
.target(name: "MyApp", /* … */ dependencies: [.package(product: "Sparkle")]),
]
)
For a larger graph, prefer a dedicated Tuist/Package.swift that lists the dependencies, then run tuist install and reference each with .external(name: "Sparkle") in the target. Pin to a major (.upToNextMajor(from:)) rather than an exact version so you pick up compatible fixes. Run tuist generate after editing Project.swift (build-and-run.sh does this for you, and runs tuist install first when a Tuist/Package.swift is present).
Use the build-and-run.sh script included with this skill — it does the whole loop:
./build-and-run.sh
What it does automatically:
xcode-select -p) and the license is accepted.tuist generate if a Project.swift exists and the generated project is missing or stale (and tuist install first if a Tuist/Package.swift declares dependencies).xcodebuild into a predictable ./build DerivedData path..app from xcodebuild -showBuildSettings.open (returns immediately; app runs detached).Reading output. A normal open MyApp.app returns immediately and detaches, so you won't see the app's stdout. To watch logs and crashes (the analog of "debug output"):
./build-and-run.sh --logs # runs MyApp.app/Contents/MacOS/MyApp in the foreground, streaming stdout/stderr
Run this in the background (Bash tool run_in_background: true) so it doesn't block your turn while the app is open; then read the streamed output. Alternatively, for already-running apps, tail the unified log:
log stream --level debug --predicate 'process == "MyApp"'
Options:
./build-and-run.sh # generate (if needed), build, launch with open
./build-and-run.sh --scheme MyApp # explicit scheme
./build-and-run.sh --logs # build, then run the inner binary streaming stdout (use background)
./build-and-run.sh --skip-run # build only (safe to run in foreground)
./build-and-run.sh --configuration Release # override Debug
If the build fails: read ALL errors (Swift emits the full set in one pass — no need to guess), batch-fix them, then re-run build-and-run.sh.
If the app crashes on launch: run with --logs and read the stderr/exception, or check the latest crash report:
ls -t ~/Library/Logs/DiagnosticReports/MyApp-*.ips | head -1 | xargs cat
| Error / symptom | Fix |
|---|---|
xcode-select: error: tool 'xcodebuild' requires Xcode | Point at full Xcode: sudo xcode-select -s /Applications/Xcode.app; accept license: sudo xcodebuild -license accept |
tuist: command not found | brew install tuist (or run /appkit-setup) |
No such module 'AppKit' on a SwiftPM executable | You're building a bare executable, not an app bundle — scaffold an Xcode project (Tuist) instead |
Cannot find 'X' in scope / no such module | Add the import, or add the SwiftPM package to Project.swift (or Tuist/Package.swift) and regenerate |
'NSGlassEffectView' is only available in macOS 26 or newer | Wrap usage in if #available(macOS 26, *) or raise the deployment target to 26.0 |
| Window opens then app immediately quits | Missing/short-lived window controller reference — retain it on the AppDelegate; ensure app.run() is called |
| App launches but no menu bar / not in Dock | setActivationPolicy(.regular) not set, or running the raw binary without a bundle — launch the .app with open |
Call to main actor-isolated ... in a synchronous nonisolated context | Mark the type/method @MainActor, or hop with await MainActor.run { } |
| Blank/black window | Content view not added or zero-size constraints — verify contentView/constraints; check translatesAutoresizingMaskIntoConstraints = false |
code signing is required for product type 'Application' (on device-style targets) | For local Debug, set CODE_SIGN_IDENTITY=- (ad-hoc) or "Sign to Run Locally" / automatic signing |
The app is damaged after copying a build | Quarantine/unsigned — for local runs xattr -cr MyApp.app; for distribution see appkit-packaging |
| Requirement | Minimum | Recommended | Install |
|---|---|---|---|
| macOS | matches your deployment target | macOS 26 Tahoe (to build for Tahoe) | — |
| Xcode | 26 (for the macOS 26 SDK) | latest 26.x (Swift 6.3+) | Mac App Store, or xcodes install |
| Command Line Tools / license | accepted | accepted | sudo xcodebuild -license accept |
| Tuist | 4.x | latest | brew install tuist |
| Homebrew | — | latest | https://brew.sh |
If any of these are missing when you try to use them — xcodebuild/tuist not found, the Xcode license unaccepted, no Xcode selected — do not try to install Xcode yourself or work around it. Stop and tell the user the prerequisite is missing and ask them to run /appkit-setup (a user-invoked skill that installs and verifies everything). Then retry the failed command.
swift build executable as a GUI app — AppKit apps need a .app bundle with Info.plist (use Tuist + xcodebuild).LSUIElement/agent activation just to silence a "no window" bug — fix the window controller lifetime..sync to the main queue, semaphore waits on main) — it deadlocks the UI.build-and-run.sh resolve BUILT_PRODUCTS_DIR.build-and-run.sh — included with this skill; generates, builds, and launches automatically.templates/Project.swift — a minimal Tuist manifest for a programmatic AppKit app + UI test target, targeting macOS 26.npx claudepluginhub markmals/mac-dev-skills --plugin appkitProvides behavioral guidelines to reduce common LLM coding mistakes, focusing on simplicity, surgical changes, assumption surfacing, and verifiable success criteria.
Searches, retrieves, and installs Agent Skills from prompts.chat registry using MCP tools like search_skills and get_skill. Activates for finding skills, browsing catalogs, or extending Claude.