-
Notifications
You must be signed in to change notification settings - Fork 0
feat(build-macos-apps): add macOS app building skills and commands #72
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,12 @@ | ||
| { | ||
| "name": "build-macos-apps", | ||
| "version": "1.0.0", | ||
| "description": "Build, debug, instrument, and implement macOS apps with SwiftUI and AppKit guidance, codesigning, packaging, and telemetry workflows.", | ||
| "author": { | ||
| "name": "duyet", | ||
| "url": "https://github.com/duyet" | ||
| }, | ||
| "homepage": "https://github.com/duyet/codex-claude-plugins", | ||
| "repository": "https://github.com/duyet/codex-claude-plugins", | ||
| "license": "MIT" | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,21 @@ | ||
| { | ||
| "name": "build-macos-apps", | ||
| "version": "1.0.0", | ||
| "description": "Build, debug, instrument, and implement macOS apps with SwiftUI and AppKit guidance, codesigning, packaging, and telemetry workflows.", | ||
| "author": { | ||
| "name": "duyet" | ||
| }, | ||
| "skills": "./skills/", | ||
| "commands": "./commands/", | ||
| "agents": "./agents/", | ||
| "interface": { | ||
| "displayName": "Build macOS Apps", | ||
| "shortDescription": "Build, debug, instrument, and implement macOS apps with SwiftUI and AppKit guidance", | ||
| "developerName": "duyet", | ||
| "category": "development", | ||
| "capabilities": ["Skill", "Command", "Agent"], | ||
| "links": { | ||
| "homepage": "https://github.com/duyet/codex-claude-plugins" | ||
| } | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,65 @@ | ||
| # Build macOS Apps Plugin | ||
|
|
||
| This plugin packages macOS-first development workflows. | ||
|
|
||
| It currently includes these skills: | ||
|
|
||
| - `build-run-debug` | ||
| - `test-triage` | ||
| - `signing-entitlements` | ||
| - `swiftpm-macos` | ||
| - `packaging-notarization` | ||
| - `swiftui-patterns` | ||
| - `liquid-glass` | ||
| - `window-management` | ||
| - `appkit-interop` | ||
| - `view-refactor` | ||
| - `telemetry` | ||
|
|
||
| ## What It Covers | ||
|
|
||
| - discovering local Xcode workspaces, projects, and Swift packages | ||
| - building and running macOS apps with shell-first desktop workflows | ||
| - creating one project-local `script/build_and_run.sh` entrypoint and wiring `.codex/environments/environment.toml` so the Codex app Run button works | ||
| - implementing native macOS SwiftUI scenes, menus, settings, toolbars, and multiwindow flows | ||
| - adopting modern macOS Liquid Glass and design-system guidance with standard SwiftUI structures, toolbars, search, controls, and custom glass surfaces | ||
| - tailoring SwiftUI windows with title/toolbar styling, material-backed container backgrounds, minimize/restoration behavior, default and ideal placement, borderless window style, and launch behavior | ||
| - bridging into AppKit for representables, responder-chain behavior, panels, and other desktop-only needs | ||
| - refactoring large macOS view files toward stable scene, selection, and command structure | ||
| - adding lightweight `Logger` / `os.Logger` instrumentation for windows, sidebars, menu commands, and menu bar actions | ||
| - reading and verifying runtime events with Console, `log stream`, and process logs | ||
| - triaging failing unit, integration, and UI-hosted macOS tests | ||
| - debugging launch failures, crashes, linker problems, and runtime regressions | ||
| - inspecting signing identities, entitlements, hardened runtime, and Gatekeeper issues | ||
| - preparing packaging and notarization workflows for distribution | ||
|
|
||
| ## Plugin Structure | ||
|
|
||
| ```text | ||
| build-macos-apps/ | ||
| ├── .claude-plugin/ # Claude manifest | ||
| ├── .codex-plugin/ # Codex manifest | ||
| ├── agents/ | ||
| │ └── openai.yaml # OpenAI surface metadata | ||
| ├── assets/ # Icons and SVGs | ||
| ├── commands/ # Slash commands | ||
| │ ├── build-and-run-macos-app.md | ||
| │ ├── fix-codesign-error.md | ||
| │ └── test-macos-app.md | ||
| └── skills/ # Skill payloads | ||
| ├── build-run-debug/ | ||
| ├── test-triage/ | ||
| ├── signing-entitlements/ | ||
| ├── swiftpm-macos/ | ||
| ├── packaging-notarization/ | ||
| ├── swiftui-patterns/ | ||
| ├── liquid-glass/ | ||
| ├── window-management/ | ||
| ├── appkit-interop/ | ||
| ├── view-refactor/ | ||
| └── telemetry/ | ||
| ``` | ||
|
|
||
| ## License | ||
|
|
||
| MIT |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,6 @@ | ||
| interface: | ||
| display_name: "Build macOS Apps" | ||
| short_description: "Build, debug, instrument, and implement macOS apps with SwiftUI and AppKit guidance" | ||
| icon_small: "./assets/build-macos-apps-small.svg" | ||
| icon_large: "./assets/app-icon.png" | ||
| default_prompt: "Build/refactor native macOS UI, inspect AppKit interop, add telemetry, or debug a macOS app." | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,30 @@ | ||
| # /build-and-run-macos-app | ||
|
|
||
| Create or update the project-local macOS `build_and_run.sh` script, wire the | ||
| Codex app Run button, then use that script as the default build/run entrypoint. | ||
|
|
||
| ## Arguments | ||
|
|
||
| - `scheme`: Xcode scheme name (optional) | ||
| - `workspace`: path to `.xcworkspace` (optional) | ||
| - `project`: path to `.xcodeproj` (optional) | ||
| - `product`: SwiftPM executable product name (optional) | ||
| - `mode`: `run`, `debug`, `logs`, `telemetry`, or `verify` (optional, default: `run`) | ||
| - `app_name`: process/app name to stop before relaunching (optional) | ||
|
|
||
| ## Workflow | ||
|
|
||
| 1. Detect whether the repo uses an Xcode workspace, Xcode project, or SwiftPM package. | ||
| 2. If the workspace is not inside git yet, run `git init` at the project root so Codex app git-backed features unlock. | ||
| 3. Create or update `script/build_and_run.sh` so it always stops the current app, builds the macOS target, and launches the fresh result. | ||
| 4. For SwiftPM, keep raw executable launch only for true CLI tools; for AppKit/SwiftUI GUI apps, create a project-local `.app` bundle and launch it with `/usr/bin/open -n`. | ||
| 5. Support optional script flags for `--debug`, `--logs`, `--telemetry`, and `--verify`. | ||
| 6. Follow the canonical bootstrap contract in `../skills/build-run-debug/references/run-button-bootstrap.md` for the exact script shape and `.codex/environments/environment.toml` format. | ||
| 7. Run the script in the requested mode and summarize any build, script, or launch failure. | ||
|
|
||
| ## Guardrails | ||
|
|
||
| - Do not initialize a nested git repo inside an existing parent checkout. | ||
| - Do not leave stale `Run` actions pointing at old script paths. | ||
| - Keep the no-flag script path simple: kill, build, run. | ||
| - Use `--debug`, `--logs`, `--telemetry`, or `--verify` only when the user asks for those modes. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,22 @@ | ||
| # /fix-codesign-error | ||
|
|
||
| Inspect a macOS signing or entitlement failure and explain the minimum fix path. | ||
|
|
||
| ## Arguments | ||
|
|
||
| - `app`: path to `.app` bundle or binary (optional) | ||
| - `identity`: signing identity hint (optional) | ||
| - `mode`: `inspect` or `repair-plan` (optional, default: `inspect`) | ||
|
|
||
| ## Workflow | ||
|
|
||
| 1. Inspect the app bundle, executable, signing info, and entitlements. | ||
| 2. Determine whether the problem is identity, provisioning, hardened runtime, sandboxing, or trust policy. | ||
| 3. Summarize the exact failure class in plain language. | ||
| 4. Provide the minimal repair sequence or validation command. | ||
|
|
||
| ## Guardrails | ||
|
|
||
| - Never invent entitlements; read them from the binary or source files. | ||
| - Distinguish local development signing problems from distribution or notarization failures. | ||
| - Prefer verifiable commands like `codesign -d`, `spctl`, and `plutil` over guesswork. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,23 @@ | ||
| # /test-macos-app | ||
|
|
||
| Run the smallest meaningful macOS test scope first and explain failures by category. | ||
|
|
||
| ## Arguments | ||
|
|
||
| - `scheme`: Xcode scheme name (optional) | ||
| - `target`: test target or product name (optional) | ||
| - `filter`: test filter expression (optional) | ||
| - `configuration`: `Debug` or `Release` (optional, default: `Debug`) | ||
|
|
||
| ## Workflow | ||
|
|
||
| 1. Detect whether the repo uses `xcodebuild test` or `swift test`. | ||
| 2. Prefer focused test execution when a target or filter is provided. | ||
| 3. Classify failures as compile, assertion, crash, env/setup, or flake. | ||
| 4. Summarize the top blocker and the narrowest sensible next step. | ||
|
|
||
| ## Guardrails | ||
|
|
||
| - Avoid rerunning the full suite if a focused rerun is possible. | ||
| - Distinguish build failures from actual failing tests. | ||
| - Note when host app setup or simulator-only test assumptions leak into a macOS run. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,69 @@ | ||
| --- | ||
| name: appkit-interop | ||
| description: Bridge macOS SwiftUI into AppKit narrowly. Use when implementing representables, reaching NSWindow or panels, handling menus, or using the responder chain. | ||
| --- | ||
|
|
||
| # AppKit Interop | ||
|
|
||
| ## Quick Start | ||
|
|
||
| Use this skill when SwiftUI is close but not quite enough for native macOS behavior. | ||
| Keep the bridge as small and explicit as possible. SwiftUI should usually remain | ||
| the source of truth, while AppKit handles the imperative edge. | ||
|
|
||
| ## Choose The Smallest Bridge | ||
|
|
||
| - Use pure SwiftUI when the required behavior already exists in scenes, toolbars, commands, inspectors, or standard controls. | ||
| - Use `NSViewRepresentable` when you need a specific AppKit view with lightweight lifecycle needs. | ||
| - Use `NSViewControllerRepresentable` when you need controller lifecycle, delegation, or presentation coordination. | ||
| - Use direct AppKit window or app hooks when you need `NSWindow`, responder-chain, menu validation, panels, or app-level behavior. | ||
|
|
||
| ## Workflow | ||
|
|
||
| 1. Name the capability gap precisely. | ||
| - Window behavior | ||
| - Text system behavior | ||
| - Menu validation | ||
| - Drag and drop | ||
| - File open/save panels | ||
| - First responder control | ||
|
|
||
| 2. Pick the smallest boundary that solves it. | ||
| - Avoid porting a whole screen to AppKit when one wrapped control or coordinator would do. | ||
|
|
||
| 3. Keep ownership explicit. | ||
| - SwiftUI owns value state, selection, and observable models. | ||
| - AppKit objects stay inside the representable, coordinator, or bridge object. | ||
|
|
||
| 4. Expose a narrow interface back to SwiftUI. | ||
| - Bindings for editable state | ||
| - Small callbacks for events | ||
| - Focused bridge services only when necessary | ||
|
|
||
| 5. Validate lifecycle assumptions. | ||
| - SwiftUI may recreate representables. | ||
| - Coordinators exist to hold delegate and target-action glue, not as a second app architecture. | ||
|
|
||
| ## References | ||
|
|
||
| - `references/representables.md`: choosing between view and view-controller wrappers, plus coordinator patterns. | ||
| - `references/window-panels.md`: window access, utility windows, and open/save panels. | ||
| - `references/responder-menus.md`: first responder, command routing, and menu validation. | ||
| - `references/drag-drop-pasteboard.md`: pasteboard, file URLs, and desktop drag/drop edges. | ||
|
|
||
| ## Guardrails | ||
|
|
||
| - Do not duplicate the source of truth between SwiftUI and AppKit. | ||
| - Do not let `Coordinator` become an unstructured dumping ground. | ||
| - Do not store long-lived `NSView` or `NSWindow` instances globally without a strong ownership reason. | ||
| - Prefer a tiny tested bridge over rewriting the feature in raw AppKit. | ||
| - If a pattern can remain entirely in `swiftui-patterns`, keep it there. | ||
|
|
||
| ## Output Expectations | ||
|
|
||
| Provide: | ||
|
|
||
| - the exact SwiftUI limitation being crossed | ||
| - the smallest recommended bridge type | ||
| - the data-flow boundary between SwiftUI and AppKit | ||
| - the lifecycle or validation risks to watch |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,23 @@ | ||
| # Drag, Drop, and Pasteboard | ||
|
|
||
| ## Intent | ||
|
|
||
| Use this when desktop drag/drop or pasteboard behavior exceeds what plain SwiftUI modifiers cover comfortably. | ||
|
|
||
| ## Good fits | ||
|
|
||
| - File URL dragging | ||
| - Pasteboard interoperability with other macOS apps | ||
| - Rich drag previews or AppKit-specific drop validation | ||
| - Legacy AppKit views with custom drag types | ||
|
|
||
| ## Core patterns | ||
|
|
||
| - Start with SwiftUI drag/drop APIs when they already cover the use case. | ||
| - Drop to AppKit when you need `NSPasteboard`, custom pasteboard types, or older AppKit delegate flows. | ||
| - Keep data conversion at the boundary instead of leaking AppKit types through the whole feature. | ||
|
|
||
| ## Pitfalls | ||
|
|
||
| - Do not move your whole list or canvas into AppKit just for one drop target. | ||
| - Keep file and pasteboard types explicit and validated. |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,56 @@ | ||||||||||||||||||||||||||||||||||||||||||||
| # Representables | ||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| ## Intent | ||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| Use this when wrapping an AppKit control or controller for a SwiftUI macOS app. | ||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| ## Choose the wrapper type | ||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| - Use `NSViewRepresentable` for a view-level bridge such as `NSTextView`, `NSScrollView`, or a custom AppKit control. | ||||||||||||||||||||||||||||||||||||||||||||
| - Use `NSViewControllerRepresentable` when you need controller lifecycle, delegate coordination, or AppKit presentation logic. | ||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| ## Skeleton | ||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| ```swift | ||||||||||||||||||||||||||||||||||||||||||||
| struct LegacyTextView: NSViewRepresentable { | ||||||||||||||||||||||||||||||||||||||||||||
| @Binding var text: String | ||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| func makeCoordinator() -> Coordinator { | ||||||||||||||||||||||||||||||||||||||||||||
| Coordinator(text: $text) | ||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| func makeNSView(context: Context) -> NSScrollView { | ||||||||||||||||||||||||||||||||||||||||||||
| let scrollView = NSScrollView() | ||||||||||||||||||||||||||||||||||||||||||||
| let textView = NSTextView() | ||||||||||||||||||||||||||||||||||||||||||||
| textView.delegate = context.coordinator | ||||||||||||||||||||||||||||||||||||||||||||
| scrollView.documentView = textView | ||||||||||||||||||||||||||||||||||||||||||||
| return scrollView | ||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+22
to
+28
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Programmatically setting up an
Suggested change
|
||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| func updateNSView(_ nsView: NSScrollView, context: Context) { | ||||||||||||||||||||||||||||||||||||||||||||
| guard let textView = nsView.documentView as? NSTextView else { return } | ||||||||||||||||||||||||||||||||||||||||||||
| if textView.string != text { | ||||||||||||||||||||||||||||||||||||||||||||
| textView.string = text | ||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| final class Coordinator: NSObject, NSTextViewDelegate { | ||||||||||||||||||||||||||||||||||||||||||||
| @Binding var text: String | ||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| init(text: Binding<String>) { | ||||||||||||||||||||||||||||||||||||||||||||
| _text = text | ||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| func textDidChange(_ notification: Notification) { | ||||||||||||||||||||||||||||||||||||||||||||
| guard let textView = notification.object as? NSTextView else { return } | ||||||||||||||||||||||||||||||||||||||||||||
| text = textView.string | ||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||
| ``` | ||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| ## Pitfalls | ||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| - Avoid infinite update loops by only pushing state into AppKit when values actually changed. | ||||||||||||||||||||||||||||||||||||||||||||
| - Keep delegates and target-action wiring in the coordinator. | ||||||||||||||||||||||||||||||||||||||||||||
| - If the wrapper grows into a full screen, re-evaluate the boundary. | ||||||||||||||||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,22 @@ | ||
| # Responder Chain and Menus | ||
|
|
||
| ## Intent | ||
|
|
||
| Use this when command handling depends on the active window, first responder, or AppKit menu validation. | ||
|
|
||
| ## Core patterns | ||
|
|
||
| - Start with SwiftUI `commands`, `FocusedValue`, and focused scene state. | ||
| - Use AppKit responder-chain hooks only when command routing or validation truly depends on the underlying responder system. | ||
| - Keep menu enablement rules close to the state they depend on. | ||
|
|
||
| ## Good fits for AppKit | ||
|
|
||
| - Validating whether a menu item should be enabled | ||
| - Routing actions through the current first responder | ||
| - Integrating with existing AppKit document or text behaviors | ||
|
|
||
| ## Pitfalls | ||
|
|
||
| - Do not recreate AppKit-style global command handling when SwiftUI focused values would work. | ||
| - Avoid scattering command logic between SwiftUI closures and AppKit selectors without a clear boundary. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,37 @@ | ||
| # Windows and Panels | ||
|
|
||
| ## Intent | ||
|
|
||
| Use this when SwiftUI scenes are not enough for the required macOS window or panel behavior. | ||
|
|
||
| ## Common cases | ||
|
|
||
| - Accessing the backing `NSWindow` | ||
| - Configuring titlebar or toolbar behavior | ||
| - Presenting `NSOpenPanel` or `NSSavePanel` | ||
| - Managing utility panels or floating windows | ||
|
|
||
| ## Core patterns | ||
|
|
||
| - Prefer SwiftUI `Window`, `WindowGroup`, and `openWindow` first. | ||
| - Use AppKit only for window features SwiftUI does not expose cleanly. | ||
| - Keep file open/save panels behind a small service or helper instead of scattering panel setup throughout the view tree. | ||
|
|
||
| ## Example: open panel | ||
|
|
||
| ```swift | ||
| @MainActor | ||
| func chooseFile() -> URL? { | ||
| let panel = NSOpenPanel() | ||
| panel.canChooseFiles = true | ||
| panel.canChooseDirectories = false | ||
| panel.allowsMultipleSelection = false | ||
| return panel.runModal() == .OK ? panel.url : nil | ||
| } | ||
| ``` | ||
|
|
||
| ## Pitfalls | ||
|
|
||
| - Do not let random views own long-lived `NSWindow` references. | ||
| - Keep floating panels and utility windows consistent with the scene model. | ||
| - If the behavior is really just settings or a secondary scene, go back to `swiftui-patterns`. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This metadata points at two plugin-local icon files, but the commit does not add an
assets/directory or either referenced file. Any surface that reads this metadata will show broken icons or fail asset resolution for the new plugin, so either include the assets or remove these references until they exist.Useful? React with 👍 / 👎.