A native macOS menubar app that puts Apple Intelligence at your fingertips — no API keys, no cloud, no Ollama. Powered entirely by on-device inference via apfel.
Requires macOS 26 Tahoe and Apple Silicon. Apple Intelligence must be enabled in System Settings.
grapfel sits in your menubar. Click the icon (or press ⌘⇧Space from anywhere) to open a lightweight chat window, type your request, hit Enter to send, and get a response from the on-device foundation model — instantly, privately, offline.
- no API keys — runs entirely on-device via Apple Intelligence
- no cloud — your prompts never leave your Mac
- no Ollama — uses Apple's own foundation model, not a third-party runtime
- fast — apfel keeps the model loaded; responses stream token-by-token in ~1 second
- persistent conversations — full multi-conversation sidebar; pick up exactly where you left off
- auto-installs apfel — grapfel detects missing dependencies and installs them for you on first launch
- configurable hotkey — record any key combo in Settings; grapfel registers it globally via Carbon
| Requirement | Details |
|---|---|
| macOS | 26 Tahoe or later |
| Hardware | Apple Silicon (M-series) |
| Apple Intelligence | Must be enabled in System Settings → Apple Intelligence & Siri |
| Homebrew | Required for apfel install (grapfel installs apfel automatically if Homebrew is present) |
Download the latest release from the Releases page.
- Download grapfel-1.0.0-macos26.zip from the Assets section.
- Unzip and move
grapfel.appto/Applications. - Launch. The ✦ icon appears in your menubar.
grapfel is signed with a Developer ID certificate and notarized by Apple — no Gatekeeper prompt, no quarantine step required. Future updates are delivered automatically via Sparkle.
On first launch, grapfel checks whether apfel is installed:
- If apfel is present: grapfel starts it immediately and you're ready to chat.
- If apfel is missing and Homebrew is installed: grapfel installs apfel automatically — progress streams in real time right in the app window.
- If Homebrew is not installed: grapfel shows a setup screen with a link to brew.sh and a Retry button.
Once apfel is installed, grapfel manages its lifecycle: starts it on launch, health-checks it on startup, restarts it on crash, and terminates it cleanly on quit.
| Action | How |
|---|---|
| Open / close | Click the ✦ menubar icon, or press ⌘⇧Space from anywhere |
| Send prompt | Press Enter |
| Insert newline | Press ⌘+Enter |
| New conversation | Click the ✏ icon in the header |
| Browse conversations | Click the ☰ icon to open the sidebar |
| Rename conversation | Double-click a conversation name in the sidebar |
| Delete conversation | Hover the conversation in the sidebar → click the trash icon |
| Copy response | Click Copy at the bottom of any assistant message |
| Copy as code block | Right-click Copy → "Copy as Code Block" |
| Copy as plain text | Right-click Copy → "Copy as Plain Text" |
| Export conversation | Sidebar → ··· menu → "Export as Markdown" |
| Export all conversations | Sidebar → ··· menu → "Export all as JSON" |
| Attach file | Click the paperclip button in the input area |
| Adjust generation options | Click the options disclosure group (temperature, max tokens, etc.) |
| Settings | Press ⌘, or click the gear icon |
| Change global hotkey | Settings → General → Global Shortcut → click to record |
| Update apfel | Banner appears automatically when an update is available → click Upgrade |
| Check for app updates | Right-click the ✦ menubar icon → "Check for Updates…" |
grapfel has a full multi-conversation sidebar. Each conversation is stored as a secure JSON file in ~/Library/Application Support/grapfel/conversations/ (permissions: 0600 per file, 0700 on the directory).
Conversations are auto-titled from the first message you send (trimmed to 40 characters at a word boundary). Switch between them instantly — the model gets the full context of whichever conversation is active.
Retention modes (Settings → Storage):
| Mode | Behavior |
|---|---|
| Session only | Nothing written to disk — conversations purge on quit |
| Last 50 turns | Keeps the most recent 50 user+assistant pairs per conversation |
| Unlimited | Retains all messages up to a 200-message cap |
| Option | Default | Notes |
|---|---|---|
| Temperature | 1.0 | Controls randomness (0.0–2.0) |
| Max tokens | 2048 | Maximum response length |
| Streaming | on | Token-by-token output as the model generates |
| JSON mode | off | Requests structured JSON output |
| System prompt | — | Sets the system role message sent before every turn |
Token usage (prompt / completion / total) is shown beneath each assistant response.
Permissive mode (disables content safety filtering) is a server-level flag — enable it in Settings → General. The server restarts automatically when toggled.
grapfel (menubar app, LSUIElement)
└── spawns apfel --serve on port 11434
└── wraps Apple's on-device foundation model
└── exposes OpenAI-compatible HTTP at 127.0.0.1:11434/v1
AppDelegate (@MainActor)
├── GrapfelPanel (NSPanel subclass, canBecomeKey=true, 621 pt wide)
│ └── NSHostingView → ContentView (direct contentView, not nested)
├── Carbon RegisterEventHotKey — configurable, no Input Monitoring permission
├── Outside-click monitor (NSEvent global) with activation-timing guards
└── Sparkle SPU updater
ApfelServerManager (actor) — subprocess lifecycle, health-check, crash-restart
HomebrewInstaller — brew detect, install apfel, upgrade apfel (streaming output)
ServerState (@Observable @MainActor) — .starting/.running/.binaryNotFound/.homebrewNotFound/.startFailed
ConversationStore (@Observable @MainActor)
├── UUID-named JSON files, 0o600 permissions, 0o700 directory
├── RetentionMode: .sessionOnly / .lastNTurns(50) / .unlimited(cap 200)
├── Auto-title (ConversationTitleFormatter, 40-char word-boundary)
└── Legacy migration: single conversation.json → UUID-named files
ChatViewModel (@Observable @MainActor)
├── displayedConversationID snapshot — prevents cross-conversation writes during streams
└── Streaming (SSE) enabled by default
Views (SwiftUI)
└── ContentView → SidebarView, HeaderBar, ConversationView, PromptInputView, OptionsPanel
All communication is over localhost — nothing touches the internet except Sparkle update checks (to GitHub Releases).
grapfel updates itself automatically via Sparkle. Right-click the ✦ menubar icon → Check for Updates… to check manually.
apfel — when a newer version is available in Homebrew, grapfel shows an update banner. Click Upgrade to install the update in-app (no terminal required).
- macOS 26 only — Apple Intelligence and the Liquid Glass material require the macOS 26 SDK.
- Apple Silicon only — Apple Intelligence is not available on Intel Macs.
- Menubar icon + panel (Liquid Glass, GrapfelPanel)
- apfel server lifecycle management (start, health-check, crash-restart)
- Prompt → response via OpenAI-compatible API
- Options panel (temperature, max tokens, system prompt, streaming, JSON mode)
- Enter to send, ⌘+Enter for newline
- Global hotkey — configurable in Settings, Carbon registration, no Input Monitoring permission
- Multi-turn conversation history with full context
- Persistent multi-conversation sidebar (UUID-named JSON, 0600/0700 permissions)
- Conversation auto-title (first message, 40-char word-boundary)
- Conversation export (Markdown per conversation, JSON archive for all)
- Retention modes: session-only, last-50-turns, unlimited
- Markdown rendering (fenced code blocks, bold, italic, inline code)
- Copy response (raw, code block, plain text)
- SSE streaming (token-by-token output)
- Sparkle auto-update for grapfel
- Developer ID signed + notarized
- Token usage display
- JSON mode and permissive mode
- Auto-install apfel on first launch (in-app, streaming progress)
- In-app apfel update (brew upgrade, no terminal required)
- Privacy manifest (PrivacyInfo.xcprivacy) + in-product privacy disclosure
- Full test suite (52 tests, Swift 6 strict concurrency)
- App icon + menubar template glyph
- apfel by Arthur-Ficial — the CLI/server that makes Apple Intelligence scriptable
- Built with SwiftUI + Swift 6 on macOS 26 Tahoe
MIT