Ghost OS is open source and we welcome contributions.
The most impactful contribution is a new recipe. Pick an app, figure out the workflow, save it as a recipe JSON, test it 3+ times, and submit a PR.
Good recipe candidates:
- Slack: send a message, reply to a thread
- Google Calendar: create an event
- Finder: organize files, create folders
- System Settings: toggle settings
- Any web app: login, fill forms, extract data
Ghost OS should work with every app. Test it with apps you use daily and report what works and what doesn't. File issues with:
- Which app and version
- What tool you called
- What happened vs what you expected
- The output from
ghost doctor
Check the issues page. Issues labeled good first issue are a great starting point.
git clone https://github.com/ghostwright/ghost-os.git
cd ghost-os
swift buildRequirements:
- macOS 14+
- Swift 6.2+ (install via swiftly)
- Accessibility permission for your terminal app
- Screen Recording permission (optional, for screenshots)
The project depends on AXorcist which is referenced as a local package at ../AXorcist. Clone it alongside ghost-os:
your-workspace/
├── AXorcist/ # git clone https://github.com/steipete/AXorcist
└── ghost-os/ # this repo
Sources/
├── GhostOS/ # Library (the MCP server logic)
│ ├── MCP/ # MCPServer, MCPTools, MCPDispatch
│ ├── Perception/ # ghost_context, ghost_find, ghost_read, etc.
│ ├── Actions/ # ghost_click, ghost_type, ghost_hotkey, etc.
│ ├── Recipes/ # RecipeEngine, RecipeStore, RecipeTypes
│ ├── Screenshot/ # ScreenCaptureKit wrapper
│ └── Common/ # Logger, Types, LocatorBuilder
└── ghost/ # CLI (thin entry point)
├── main.swift # ghost mcp, setup, doctor, status
├── SetupWizard.swift # Interactive first-run setup
└── Doctor.swift # Diagnostic tool
Recipes are JSON files stored in ~/.ghost-os/recipes/. Here's the structure:
{
"schema_version": 2,
"name": "my-recipe",
"description": "What this recipe does",
"app": "Google Chrome",
"params": {
"query": {
"type": "string",
"description": "What to search for",
"required": true
}
},
"preconditions": {
"app_running": "Google Chrome",
"url_contains": "example.com"
},
"steps": [
{
"id": 1,
"action": "click",
"target": {
"criteria": [{"attribute": "AXRole", "value": "AXButton"}],
"computedNameContains": "Search"
},
"wait_after": {
"condition": "elementExists",
"value": "Results",
"timeout": 5
},
"note": "Click the search button"
}
],
"on_failure": "stop"
}Actions: click, type, press, hotkey, focus, scroll, wait
Wait conditions: elementExists, elementGone, urlContains, titleContains, urlChanged, titleChanged, delay
Tips:
- Use
computedNameContainsfor fuzzy matching ("Compose" matches "Compose" button) - Add
criteriawithAXRoleto narrow matches (e.g., only buttons) - Always include
"criteria": []even if empty (required by the Locator decoder) - Use
wait_afterinstead of fixed delays - Test your recipe at least 3 times before submitting
- Swift 6.2 with strict concurrency
- All logging to stderr (stdout is the MCP protocol channel)
- No force unwraps except in tests
- Functions over 80 lines get split
- Errors tell the agent what to do next, not just what went wrong
- Concise but informative
- Anyone reading the git log should understand what changed
- No AI attribution lines