Get Your Agent to Help
Install the macOS design skill:
npx skills add ehmo/platform-design-skills@macos-design-guidelines
Then ask: "help me build a SwiftUI MenuBarExtra app that shows my launchd job status by calling a CLI"
Create the Project
mkdir -p ~/Code/jobs-menubar && cd ~/Code/jobs-menubar
Create Package.swift:
// swift-tools-version: 5.9
import PackageDescription
let package = Package(
name: "AIJobsMenubar",
platforms: [.macOS(.v14)],
targets: [.executableTarget(name: "AIJobsMenubar", path: "Sources")]
)
The App
Sources/AIJobsMenubarApp.swift — MenuBarExtra with .window style gives you a popover:
import SwiftUI
@main
struct AIJobsMenubarApp: App {
var body: some Scene {
MenuBarExtra("AI Jobs", systemImage: "gearshape.2") {
JobsView()
}
.menuBarExtraStyle(.window)
}
}
The View
Sources/JobsView.swift — shell out to the CLI, parse JSON, render:
import SwiftUI
struct Job: Codable, Identifiable {
let name, label, scope, schedule, pid, exitCode: String
let loaded, disabled: Bool
var id: String { label }
var statusColor: Color {
if !loaded { return .yellow }
if exitCode != "0" && exitCode != "-" { return .red }
return .green
}
}
struct CLIResponse: Codable {
let ok: Bool
let result: StatusResult
}
struct StatusResult: Codable {
let total, loaded: Int
let jobs: [Job]
}
@Observable class JobsModel {
var jobs: [Job] = []
// IMPORTANT: absolute path to bun
let bun = "\(NSHomeDirectory())/.bun/bin/bun"
let cli: String
init() {
// Update this path to your project
cli = "\(NSHomeDirectory())/Code/badass-courses/james-long-ai-job-scheduling/src/cli.ts"
}
func refresh() {
let process = Process()
process.executableURL = URL(fileURLWithPath: bun)
process.arguments = ["run", cli, "status"]
let pipe = Pipe()
process.standardOutput = pipe
try? process.run()
process.waitUntilExit()
let data = pipe.fileHandleForReading.readDataToEndOfFile()
if let response = try? JSONDecoder().decode(CLIResponse.self, from: data) {
jobs = response.result.jobs
}
}
func kick(_ job: Job) {
let p = Process()
p.executableURL = URL(fileURLWithPath: bun)
p.arguments = ["run", cli, "kick", job.name]
try? p.run(); p.waitUntilExit()
refresh()
}
}
struct JobsView: View {
@State private var model = JobsModel()
var body: some View {
VStack(alignment: .leading, spacing: 8) {
ForEach(model.jobs) { job in
HStack {
Circle().fill(job.statusColor).frame(width: 8, height: 8)
Text(job.name).font(.system(.body, design: .monospaced))
Spacer()
Text(job.schedule).font(.caption).foregroundStyle(.secondary)
}
.contentShape(Rectangle())
.onTapGesture { model.kick(job) }
}
Divider()
HStack {
Button("Refresh") { model.refresh() }
Spacer()
}.buttonStyle(.plain).font(.caption)
}
.padding()
.frame(width: 350)
.onAppear { model.refresh() }
}
}
Build & Run
swift build && swift run
# Menubar icon appears
Why This Branch
You're building a real Mac app. It lives in the menubar. It's native. Click a job to kick it. This is the most ambitious branch — your coding agent can scaffold it, but you're writing Swift.
Companion Notes
Branch: SwiftUI Menubar App
A native macOS menubar app that shows job status at a glance. Click the icon → see all jobs. The most ambitious branch — you're building a real Mac app.
Architecture
Menubar icon (SwiftUI) → shells out to `jobs status` (JSON)
→ parses response
→ renders in a popover
Setup
mkdir -p ~/Code/ai-jobs-menubar
cd ~/Code/ai-jobs-menubar
Create Package.swift:
// swift-tools-version: 5.9
import PackageDescription
let package = Package(
name: "AIJobsMenubar",
platforms: [.macOS(.v14)],
targets: [
.executableTarget(name: "AIJobsMenubar", path: "Sources")
]
)
The App
Create Sources/AIJobsMenubarApp.swift:
import SwiftUI
@main
struct AIJobsMenubarApp: App {
var body: some Scene {
MenuBarExtra("AI Jobs", systemImage: "gearshape.2") {
JobsMenuView()
}
.menuBarExtraStyle(.window)
}
}
The View
Create Sources/JobsMenuView.swift:
import SwiftUI
struct Job: Codable, Identifiable {
let name: String
let label: String
let scope: String
let schedule: String
let loaded: Bool
let pid: String
let exitCode: String
let disabled: Bool
var id: String { label }
var statusColor: Color {
if disabled { return .gray }
if !loaded { return .yellow }
if exitCode != "0" && exitCode != "-" { return .red }
return .green
}
var statusText: String {
if disabled { return "Disabled" }
if !loaded { return "Unloaded" }
if exitCode != "0" && exitCode != "-" { return "Error (\(exitCode))" }
if pid != "-" { return "Running" }
return "Idle"
}
}
struct CLIResponse: Codable {
let ok: Bool
let result: StatusResult
}
struct StatusResult: Codable {
let total: Int
let loaded: Int
let jobs: [Job]
}
@Observable
class JobsModel {
var jobs: [Job] = []
var lastUpdate: Date?
var error: String?
// IMPORTANT: absolute path to bun — SwiftUI apps have minimal PATH
let bunPath = "\(NSHomeDirectory())/.bun/bin/bun"
let cliPath: String
init() {
// Adjust this path to your project
cliPath = "\(NSHomeDirectory())/Code/badass-courses/james-long-ai-job-scheduling/src/cli.ts"
}
func refresh() {
let process = Process()
process.executableURL = URL(fileURLWithPath: bunPath)
process.arguments = ["run", cliPath, "status"]
let pipe = Pipe()
process.standardOutput = pipe
do {
try process.run()
process.waitUntilExit()
let data = pipe.fileHandleForReading.readDataToEndOfFile()
let response = try JSONDecoder().decode(CLIResponse.self, from: data)
jobs = response.result.jobs
lastUpdate = Date()
error = nil
} catch {
self.error = error.localizedDescription
}
}
func kick(_ job: Job) {
let process = Process()
process.executableURL = URL(fileURLWithPath: bunPath)
process.arguments = ["run", cliPath, "kick", job.name]
try? process.run()
process.waitUntilExit()
refresh()
}
func sync() {
let process = Process()
process.executableURL = URL(fileURLWithPath: bunPath)
process.arguments = ["run", cliPath, "sync"]
try? process.run()
process.waitUntilExit()
refresh()
}
}
struct JobsMenuView: View {
@State private var model = JobsModel()
var body: some View {
VStack(alignment: .leading, spacing: 8) {
ForEach(model.jobs) { job in
HStack {
Circle()
.fill(job.statusColor)
.frame(width: 8, height: 8)
Text(job.name)
.font(.system(.body, design: .monospaced))
Spacer()
Text(job.schedule)
.font(.caption)
.foregroundStyle(.secondary)
Text(job.statusText)
.font(.caption)
.foregroundStyle(.secondary)
}
.contentShape(Rectangle())
.onTapGesture { model.kick(job) }
}
Divider()
HStack {
Button("Sync") { model.sync() }
Button("Refresh") { model.refresh() }
Spacer()
if let date = model.lastUpdate {
Text(date, style: .time)
.font(.caption2)
.foregroundStyle(.tertiary)
}
}
.buttonStyle(.plain)
.font(.caption)
if let error = model.error {
Text(error)
.font(.caption)
.foregroundStyle(.red)
}
}
.padding()
.frame(width: 400)
.onAppear { model.refresh() }
}
}
Build & Run
cd ~/Code/ai-jobs-menubar
swift build
swift run
# → Menubar icon appears
To run at login, add the built binary to System Settings → Login Items.
What Makes This Special
- Native macOS — feels like a real system tool
- Menubar = always visible, never in the way
- Click a job to kick it
- The JSON CLI is the entire backend — SwiftUI is just rendering
What to Add
- Auto-refresh timer (poll every 30s)
- Color the menubar icon based on health (green/yellow/red)
- Notification on job failure (use
UNUserNotificationCenter) - Log viewer in a separate window