A Go-based menu server with a native macOS menu bar wrapper.
# Build and run the macOS app (includes Go server)
make run
# Or just build without running
make app- A menu bar icon appears in your macOS menu bar (looks like a list icon)
- The Go server starts automatically in the background
- Click the menu bar icon to see your menu
- Click any menu item to trigger its server-side handler
momd/
├── cmd/momd/main.go # Go server entry point & menu definition
├── pkg/
│ ├── menu/ # Reusable menu package
│ ├── server/ # HTTP server package
│ ├── logger/ # Logging utilities
│ └── metric/ # Metrics utilities
├── macos/
│ ├── momd/
│ │ ├── main.swift # Swift app entry point
│ │ ├── AppDelegate.swift # Menu bar app logic
│ │ └── Info.plist # App metadata
│ ├── build/ # Build output
│ └── README.md # macOS-specific docs
└── Makefile # Build automation
Edit cmd/momd/main.go and modify the makeMenu() function:
func makeMenu() *menu.Menu {
return &menu.Menu{
Title: "My App",
Description: "My custom menu",
Items: []menu.Item{
{
Title: "Say Hello",
Description: "Prints a greeting (hover text)",
Type: menu.ItemTypeCallback, // Calls server
OnClick: "/hello",
Shortcut: "cmd+h", // ⌘H
Handler: myHandler(),
},
{
Title: "Open GitHub",
Description: "Opens GitHub in browser",
Type: menu.ItemTypeLink, // Opens URL
OnClick: "https://github.com",
Shortcut: "cmd+g", // ⌘G
},
{
Title: "Submenu",
Description: "A submenu example",
Items: []menu.Item{
{
Title: "Nested Item",
Description: "A nested callback",
Type: menu.ItemTypeCallback,
OnClick: "/submenu/nested",
Shortcut: "cmd+shift+n", // ⌘⇧N
Handler: myHandler(),
},
},
},
},
}
}There are two types of menu items:
-
menu.ItemTypeCallback: Calls back to the Go server when clicked- Requires a
HandlerandOnClickpath (e.g.,"/hello") - Server handles the request and returns a response
- Requires a
-
menu.ItemTypeLink: Opens a URL using the system default handler- Requires only
OnClickwith a full URL (e.g.,"https://github.com") - Opens in default browser, mail client, etc. depending on URL scheme
- No server-side handler needed
- Requires only
Title: The text displayed in the menu (required)Description: Tooltip text shown on hover (optional)Type: Eithermenu.ItemTypeCallbackormenu.ItemTypeLinkOnClick:- For callbacks: server path like
"/action" - For links: full URL like
"https://example.com"or"mailto:user@example.com"
- For callbacks: server path like
Shortcut: Keyboard shortcut (optional)- Format:
"cmd+key","cmd+shift+key", etc. - Modifiers:
cmd,ctrl,opt/option/alt,shift - Examples:
"cmd+1","cmd+shift+g","ctrl+opt+d"
- Format:
Handler: HTTP handler function (only for callback types)Items: Nested submenu items (optional)
make run # Build and run macOS app
make app # Build macOS app (builds Go server first)
make server # Run Go server directly (for testing)
make build # Build Go server binary only
make clean # Clean macOS and Go build artifacts
make test # Run tests
make help # Show all targetsThe macOS Swift app:
- Starts the Go server as a subprocess (
./momd -port 9876) - Makes HTTP GET to
http://localhost:9876/to fetch menu JSON - Builds a native NSMenu from the JSON structure
- Binds each menu item to make HTTP requests to their respective paths when clicked
The Go server:
- Defines the menu structure in code (in
main.go) - Serves the menu as JSON at the root endpoint (
/) - Handles menu item actions at their registered paths
- macOS 11.0+
- Swift (Xcode Command Line Tools)
- Go 1.21+
Run the test script to verify the app bundle:
./macos/test-app.shThis will check:
- App bundle structure
- Binary location and permissions
- Server functionality
Option 1: Console.app (Best for troubleshooting)
# Open Console app
open -a Console
# Search for "momd" to see all logsOption 2: Terminal log streaming (Real-time logs)
# Stream live logs from both Swift app and Go server
log stream --predicate 'subsystem == "com.mchmarny.momd"' --level info
# View recent logs (last 5 minutes)
log show --predicate 'subsystem == "com.mchmarny.momd"' --last 5m --info
# View with debug details
log stream --predicate 'subsystem == "com.mchmarny.momd"' --level debugOption 3: Run from Terminal (Development mode)
# Run app from terminal to see output directly
./macos/build/momd.app/Contents/MacOS/momdOption 4: Run Go server directly (Server development only)
# Run just the Go server for testing handlers
make server
# or
./bin/momd -port 9876What You'll See:
- Swift app logs: Server startup, menu building, user actions
- Go server logs: HTTP requests, handler execution (prefixed with
[Server])
Log Format:
2025-11-02 06:15:07.609652 Info momd: [com.mchmarny.momd:app] Invoking callback: /item1
2025-11-02 06:15:07.620297 Info momd: [com.mchmarny.momd:app] [Server] 2025/11/02 06:15:07 INFO handling method=GET url=/item1
Note:
- The Swift app uses
os_log(unified logging system) for all logs - Go server output (stdout) is captured via pipes and forwarded to
os_logwith[Server]prefix - Go server errors (stderr) are captured and logged with
[Server Error]prefix - All logs appear together in Console.app and
log streamwith proper timestamps
Test the Go server directly:
./bin/momd -port 9999
curl http://localhost:9999/┌────────────────────┐
│ macOS Menu Bar │
│ (Swift App) │
└────────┬───────────┘
│ HTTP
↓
┌────────────────────┐
│ Go Server │
│ (Port 9876) │
└────────────────────┘
The menu package (pkg/menu) is reusable - you can use it in any Go application to create menu-driven interfaces!
This is my personal project and it does not represent my employer. While I do my best to ensure that everything works, I take no responsibility for issues caused by this code.