A macOS menu bar app + CLI + MCP for managing local LLM models (GGUF and Apple MLX/oMLX/MTPLX/DS4/Mlx-Serve/Splash/sushi) — plus an optional cloud rail, OrcaRouter (200+ hosted models: DeepSeek, GLM, Kimi, Qwen, Claude, GPT…), when you want hosted models too. Local-first: nothing changes until you enable it.
Made this initially for my wife, so that it would be easy for her to switch models instead of typing on the CLI, then I expanded it with more features for my lazy self. I use Deepseek-v4-flash and GLM 5.2 to assist me to code this app. Added the MCP so that my Hermes Agent could load and unload models as and when it is needed.
Beta — v0.9.13b. This is an early release of LM Switcher. The core model management paths (discover / load / unload — GGUF, MLX, oMLX, MTPLX, DS4, mlx-serve, Splash and sushi backends) are stable, but expect rough edges; see the changelog for what landed so far.
The engine is chosen when a model is discovered — file format plus the folder it lives in. There is no
engine picker: move a model into another engine's root (or hot-load it) to change engines, and one tree has one
owner, so nothing lists twice. The interactive version ships inside the app (Help → Which engine runs your
model?); source: docs/engine-map.html — the SVG above is generated from it by
scripts/export-engine-map-svg.py. The optional OrcaRouter cloud rail is drawn at the bottom — it’s outside this discovery flow entirely (enable it in Settings; see Optional cloud provider — OrcaRouter).
LM Switcher is a native macOS app that lives in your menu bar (next to the clock). It lets you:
- 🔍 Discover GGUF and MLX/oMLX/MTPLX/DS4/mlx-serve/Splash/sushi models in a directory of your choice (recursive scan)
▶️ Load any model with one click (spawns the right backend:llama-serverormlx_lm.server)- ⏹ Unload any model independently — no more "kill the wrong process" surprises
- 🔄 Run multiple models simultaneously, each on its own port (e.g. one chat model on :8080, an embedder on :8081)
- 🎨 Auto-pair
mmproj-*.ggufprojection files with their vision-capable base models (with fallback matching for QAT/generic naming) - 🧠 MTP exclusion —
mtp-*.ggufencoder files are excluded from the model list; loaded automatically by llama-server - ⚡ DFlash drafter support — auto-detects
dflash-*.ggufcompanions (e.g. Muse Glimmer) and attaches them via--spec-type draft-dflash, with adaptive disengage (--spec-draft-p-min 0.4); DFlash toggle in Settings → Local → Inference - 📝 Chat Template Override — configure a custom
.jinjaor.jsontemplate for agentic harnesses (opencode, pi) - 🤖 Gemma 4 ready — auto-applies
--reasoning off --reasoning-format noneso OpenAI-compatible clients (opencode, pi, OpenClaw) don't break onreasoning_content(per-model overridable — Muse Glimmer needs it OFF) - ⚙️ Tune per-model port, context size, and extra args via a settings window
- 🔌 Sync with externally-launched servers (started by the companion CLI or by hand)
- 📁 Show up in Launchpad, Spotlight, and
~/Applications/like a real Mac app - ☁️ Optional cloud rail — flip on OrcaRouter in Settings → OrcaRouter for 200+ hosted models (sign-in keeps the key in the Keychain; local scanning, launching and the MCP surface are untouched)
A companion shell command llama provides the same functionality from your terminal, and the two tools share state automatically.
Every engine serves an OpenAI-compatible API on loopback (127.0.0.1). Five engines ship their own web UI (llama.cpp,
MTPLX, Splash, mlx-serve, sushi); for the rest — and as one minimal client for all of them — the repo carries a single-file chat page:
docs/chat.html (installed to ~/AI/tools/lm-switcher-chat.html). Open it, pick the engine
port, and it lists whatever that server has loaded (it auto-detects the model id from /v1/models).
# grab it and open it (no build, no server — a single HTML file)
curl -fsSL -o chat.html https://raw.githubusercontent.com/kruzif-x/lm-switcher/main/docs/chat.html && open chat.htmlThe app's Help tab covers the same ground — Help → 8. Browser access (chat in a web page) — including a button that opens the page in your default browser, plus the CORS caveat explained below.
| Engine | Its own browser UI | docs/chat.html can call it |
|---|---|---|
| llama.cpp (GGUF) | http://127.0.0.1:<port>/ — built-in chat |
✅ (Access-Control-Allow-Origin: null) |
| MTPLX | http://127.0.0.1:8085/ — chat; /dashboard/ live stats; /docs API |
❌ rejects cross-origin (403) → use its own UI |
| Splash | http://127.0.0.1:8095/ — built-in chat UI |
❌ rejects cross-origin (403) → use its own UI |
| mlx-serve | http://127.0.0.1:11234/ — landing page with a chat box |
✅ (Access-Control-Allow-Origin: *) |
| sushi | http://127.0.0.1:12345/ — built-in chat page |
✅ (Access-Control-Allow-Origin: *) |
| oMLX | — (no landing page) | ✅ (*) |
| DS4 (DwarfStar) | — | ✅ (launchers pass --cors) |
MLX (mlx_lm.server) |
— | not tested |
Example — Qwen3.8-27B via Splash (dense 27B, DFlash2 speculative decoding, ~70 t/s on M2 Max):
- Load it: click the model in the LM Switcher menu bar (or
llama load Qwen3.8-27B-Splash). Warm load ≈ 20 s. - Chat in a browser at http://127.0.0.1:8095/ — the engine's own UI, always works.
- API clients:
http://127.0.0.1:8095/v1(OpenAI-compatible, no auth — loopback only), model idincoai/Qwen3.8-27B-Splash, context 262144. docs/chat.html?port=8095also works for everything except the HTTP call itself — Splash refuses cross-origin requests, and the page says so and points at :8095.
Example — engines with no UI (DS4, oMLX): load the model, then open docs/chat.html?port=8090 (DS4) or
?port=8000 (oMLX). Handy params: ?port=, ?q=<prompt> (prefills and sends — bookmarkable), and the page
remembers the last port. If a fetch fails the page tells you why: nothing listening vs. a server that blocks
cross-origin calls.
LM Switcher stays local-first. If you also want cloud models — DeepSeek, GLM, Kimi, Qwen, Claude, GPT, Gemini and more, 200+ of them behind one OpenAI-compatible endpoint, billed at provider rates with no token markup — OrcaRouter is the one we use.
Sign up via our referral link (LM Switcher earns 5% of what referred workspaces spend — it funds development):
https://www.orcarouter.ai/ref/ref_a1a2c3f77b1a87bde5f8
Ready-to-copy provider entries (Codex CLI format + a generic OpenAI-compatible snippet): integrations/orcarouter.toml.
In the app: open Settings → OrcaRouter and click Sign in with OrcaRouter — approve in your browser and the key is stored in the macOS Keychain automatically (no copy-paste; new users can sign up right in that flow, and the referral is credited automatically). Manual paste is still supported. Then hit Test connection to verify the key and fetch the catalog, and pick a Default model (orcarouter/auto routes for you). Once enabled, an OrcaRouter row appears at the top of the menu bar list for quick load/copy.
One key, every tool. OrcaRouter speaks plain OpenAI-compatible HTTP, so the same key works in any client — including the tools this project runs beside:
| Tool | Where the provider block goes |
|---|---|
| Hermes Agent | ~/.hermes/config.yaml → custom_providers: entry (name: OrcaRouter, base_url: https://api.orcarouter.ai/v1, key_env: ORCA_KEY) + ORCA_KEY=… in ~/.hermes/.env |
| pi | ~/.pi/agent/models.json → provider block with "api": "openai-completions", "apiKey": "${ORCA_KEY}" |
| opencode | ~/.config/opencode/opencode.json → @ai-sdk/openai-compatible provider with "apiKey": "{env:ORCA_KEY}" |
| Codex CLI | integrations/orcarouter.toml in this repo |
| dsh (DeepSeek CLI) | add a custom OpenAI-compatible provider in its Settings → Models |
Shared catalog across all of them: orcarouter/auto, deepseek/deepseek-v4.1-flash, z-ai/glm-5.3-flash, moonshotai/kimi-k3, … The app's Copy key button moves the key from the macOS Keychain to your clipboard to bootstrap any of these.
Agents (MCP) & OrcaRouter: LM Switcher's MCP agent surface manages local models only — OrcaRouter is deliberately not exposed to agents (no cloud models in agent listings, settings are read-only for agents, and the key never leaves the Keychain). Agents that want cloud models use their own provider configuration (e.g. the Hermes block above).
Vision-capable GGUF models (e.g. Gemma 3, Gemma 4) need a separate mmproj (multimodal projection) file to process images. LM Switcher automatically finds and attaches the right --mmproj flag when loading a model.
How it works:
-
Name-based matching — strips quantization suffixes from the model name (e.g.
gemma-4-12B-it-Q4_K_M→gemma-4-12B-it), then looks formmproj-gemma-4-12B-it-*.ggufin the same directory. -
Fallback matching — if name-based matching fails, scans the directory for any
mmproj-*.gguffile. This handles QAT (Quantization-Aware Training) models where the mmproj might be namedmmproj-BF16.ggufinstead of following the standard naming convention.
# Standard naming — name-based match works:
~/models/gguf/
├── gemma-4-12B-it-Q4_K_M.gguf ← main model
└── mmproj-gemma-4-12B-it-Q8_0.gguf ← matched by name
# QAT naming — fallback catches it:
~/models/gguf/gemma-4-12B-it-qat-GGUF/
├── gemma-4-12B-it-qat-UD-Q4_K_XL.gguf ← main model (non-standard name)
└── mmproj-BF16.gguf ← matched by fallback
The same findCompanion(for:prefix:) helper is used for both Swift and bash, ensuring consistent behavior across the app and CLI.
MTP (Multi-Token Prediction) is a technique where a model predicts multiple tokens per forward pass, speeding up inference. Some model distributions (e.g. unsloth QAT builds) include a separate mtp-*.gguf file alongside the main model.
These files are not standalone models — they're encoder heads that llama-server loads automatically from the model's metadata when the --mmproj is attached. If they appeared in the model list, users would try to load them directly and get confusing errors.
What LM Switcher does:
- Excludes
mtp-*.gguffiles from the model list (files starting withmtp-prefix) - Does NOT exclude files containing
-mtp-as an infix (e.g.gemma-3-mtp-Q4_K_M.gguf) — those are standalone models
# Excluded from model list (not standalone):
mtp-gemma-4-12B-it.gguf ← MTP head, loaded automatically
mtp-Q4_K_M.gguf ← MTP head, loaded automatically
# Included in model list (standalone model):
gemma-3-mtp-Q4_K_M.gguf ← a model with "mtp" in its name
MTP is ON by default — the app auto-detects whether a model supports it (companion mtp-*.gguf file or built-in MTP), and silently skips it for models that don't.
If you want to experiment with MTP on your own hardware, toggle "Enable MTP" in Settings → Local. This passes --spec-type draft-mtp to llama-server. It persists across restarts via UserDefaults.
The companion llama CLI also supports this via LLAMA_ENABLE_MTP=true.
DFlash is a block-diffusion speculative-decoding technique where a small
companion network (the "drafter") proposes entire blocks of 16 tokens in a
single forward pass; the main model verifies them in parallel. Meta's Muse
Glimmer ships a drafter as dflash-kquant.gguf next to the main model file.
- Auto-detect: a
dflash-*.ggufin the same directory as a GGUF model is attached automatically with--spec-type draft-dflash --spec-draft-model <file> --spec-draft-n-max 15 --spec-draft-p-min 0.4. - Exclusion:
dflash-*.gguffiles never appear in the model list (menu bar,llama list, MCP) — same treatment asmtp-*. - DFlash toggle (Settings → Local → Inference): ON by default, silently skipped when no companion exists. Also available as a per-model override.
- Sampling interaction: while a DFlash drafter is attached, the app
omits
--top-p/--top-k/--repeat-penalty— they skew the distribution the drafter verifies against and collapse block acceptance (measured on Muse Glimmer: 63% → 13%, 25 → 7.5 t/s). Temperature remains user-controlled; note the drafter's full speedup only materializes at low temperature (greedy). - Adaptive disengage:
--spec-draft-p-min 0.4makes llama.cpp stop drafting when greedy acceptance drops below 0.4 (Muse's long reasoning phase on open-ended prompts), avoiding a net slowdown (7.5 t/s with a stuck drafter vs 10 t/s baseline). - Muse Glimmer specifics: use f16/f16 KV cache per-model (q8_0/q4_0 costs ~23% on this arch) and turn per-model Suppress reasoning OFF (it garbles Muse's thinking output).
llama-server includes built-in chat templates for each model family, but agentic coding harnesses (opencode, pi, OpenClaw, etc.) need custom templates to handle tool-calling correctly.
Why the standard template breaks for agentic use:
The standard Gemma 4 chat template has four edge cases that only surface during multi-turn tool calling:
- Broken tool call arguments — when a harness sends
argumentsas a JSON string (common with Vercel AI SDK), the standard template wraps it in extra braces, producing invalid output likecall:fn{{"city":"Tokyo"}} - Dropped reasoning — after 3-4 rounds, the model's prior reasoning is stripped from history, causing tool calls to collapse to
arguments: {} - Thinking disabled —
enable_thinkingdefaults to false in the standard template, and OpenAI-compatible adapters drop unknown fields, so thinking never activates - Null corruption —
nullvalues in tool parameters render as the Python string"None"instead of JSONnull
Custom .jinja templates (like gemma4_chat_template.jinja) fix these issues but may cause tokenization errors if used with llama-server's built-in tokenizer for regular chat.
How to use it:
- Menu bar app: Open Settings → Local → Chat Template Override → browse to your
.jinjafile - CLI:
LLAMA_CHAT_TEMPLATE=~/models/gemma4_chat_template.jinja llama load gemma-4-12B-it-qat-UD-Q4_K_XL - Leave empty to use the model's built-in template (works for regular chat, web UI, Hermes, etc.)
# When chat template is NOT set:
llama-server -m model.gguf --mmproj mmproj.gguf
# → uses built-in GGUF template (correct for regular use)
# When chat template IS set:
llama-server -m model.gguf --mmproj mmproj.gguf --chat-template gemma4_chat_template.jinja
# → uses custom template (correct for agentic harnesses)
For a deep dive into how llama-server loads Gemma 4 12B and Gemma 4
12B QAT — including every flag passed, how the SigLIP vision encoder
(mmproj) is loaded, how Multi-Token Prediction (MTP) heads are
discovered and loaded from model metadata, QAT vs standard differences,
and why a custom Jinja template is only required for agentic use cases —
see:
docs/GEMMA4_LOADING_DETAILS.md
Highlights:
- The full annotated
llama-servercommand line for both variants - Why
--reasoning off --reasoning-format noneis required (Gemma 4 emits areasoning_contentfield that crashes OpenAI-compatible clients; LM Switcher applies these flags automatically) - Decision table: when to use the built-in chat template vs a custom
.jinja(Gemma 4 is the only common model that needs an override) - Manual verification steps (server logs,
/v1/models, GGUF metadata inspection)
cd ~/Projects/LLM-Switcher
./scripts/install.shThis will:
- Compile
LlamaMenubarApp.swiftto a binary - Wrap it in
~/Applications/LM Switcher.app - Compile the
lm-switcher-mcpagent-access server to~/bin(a build failure here only warns — it never blocks the app install) - Install the
llamaCLI to~/bin/llama - Install a LaunchAgent so the app starts at login
- Register the app with Launch Services (so it shows in Launchpad/Spotlight)
From the menu bar:
- Click the ⚡ icon in your menu bar
- Click any model to load it; hover a row for quick copy / pin / unload buttons, or right-click for the full action list (Switch to, etc.)
- With 2+ models running, checkboxes appear for bulk "Unload selected"
- "⏹ Unload all" stops everything at once — no confirmation dialog; a 5-second Undo appears instead if that was a mistake
From the CLI:
llama list # list all discovered models
llama load gemma # load one (fuzzy match)
llama load cerebras omnicoder # load multiple
llama switch gemma # unload all, load gemma
llama unload cerebras # unload just cerebras
llama unload all # unload everything
llama status # show running models + ports
llama ctx gemma 8192 # set per-model ctx (applies next load)
llama port cerebras 9000 # set per-model port (0 = auto)
llama menubar # launch the menu bar app
llama help # full usageRecommended model directory layout (configurable in Settings → Models Directory):
~/models/
├── gguf/ # scanned recursively
│ ├── gemma-4-12B-it-Q4_K_M.gguf
│ ├── mmproj-gemma-4-12B-it-Q8_0.gguf # auto-paired with gemma
│ ├── cerebras_Qwen3-Coder-25B-A3B-Q4_K_M.gguf
│ ├── omnicoder-9b-q4_k_m.gguf
│ └── gemma-4-12B-it-qat-GGUF/ # QAT model with companion files
│ ├── gemma-4-12B-it-qat-UD-Q4_K_XL.gguf
│ ├── mmproj-BF16.gguf # auto-paired via fallback
│ ├── mtp-gemma-4-12B-it.gguf # excluded, loaded automatically
│ └── gemma4_chat_template.jinja # custom template (optional)
└── mlx/ # one dir per model
└── llama-3.2-3b/
├── config.json
└── model.safetensors
The app automatically excludes these files from the model list (they're not standalone chat models):
mmproj-*.gguf— vision projection, auto-paired with vision-capable base modelsmtp-*.gguf— multi-token prediction head, loaded automatically by llama-serverdflash-*.gguf— DFlash block-diffusion drafter, attached automatically via--spec-type draft-dflashmodernbert-embed-*.gguf— embedding model used by the gbrain system
Open Settings from the menu bar dropdown (⚙ Settings…).
Local tab (card-based):
- Models — models directory, default port, context size, idle-unload timer, extra args
- Backends —
llama-server/mlx_lm.serverpaths, chat template override - Inference — flash attention, thinking mode, reasoning suppression, MTP, DFlash, mlock, no-mmap
- Agent access (MCP) — allow agent control, allow swap for agent loads, notify on agent actions (see Agent control (MCP))
- Advanced (collapsed) — KV cache type, sampling, CPU threads/batch size, MLX max KV size
Per-Model tab — a sidebar of every discovered model (running state + an override-count badge) next to a detail pane for the selected model. Flip "Override Local Settings" to edit that model's port, context, sampling, KV cache, thinking, reasoning suppression, MTP, DFlash, and extra args independently; each field is tagged OVERRIDE or GLOBAL so it's obvious at a glance what's actually customized versus just inherited. Load / Unload / Switch buttons live right in the header.
CLI environment variables:
LLAMA_MODELS_DIR=~/models # models directory (default: the app's Models Directory setting, then ~/models)
LLAMA_PORT=8080 # default port
LLAMA_CTX_SIZE=4096 # default context size
LLAMA_EXTRA_ARGS="" # extra args for llama-server
LLAMA_SERVER=/opt/homebrew/bin/llama-server
MLX_SERVER=~/Library/Python/3.14/bin/mlx_lm.server
LLAMA_CHAT_TEMPLATE="" # optional chat template overrideLM Switcher ships a standalone MCP server, lm-switcher-mcp (installed
to ~/bin), that lets agents — Hermes, opencode, anything
speaking MCP over stdio — manage your local models. It is OFF by
default: enable Agent access (MCP) in Settings → Local (Inference
card, below MTP) before registering.
# Hermes (available to every project)
hermes mcp add lm-switcher --command ~/bin/lm-switcher-mcp# Hermes — add under mcp_servers: in ~/.hermes/config.yaml,
# then restart the gateway
mcp_servers:
lm-switcher:
command: /Users/you/bin/lm-switcher-mcp # absolute path
args: []
timeout: 300 # loads block until healthy (default 180 s)Any other MCP client (opencode, pi, …): configure a local/stdio
server whose command is ~/bin/lm-switcher-mcp with no arguments — the
server speaks standard JSON-RPC 2.0 over stdio, newline-delimited. For
opencode that looks like:
"mcp": { "lm-switcher": { "type": "local", "command": ["~/bin/lm-switcher-mcp"] } }| Tool | What it does |
|---|---|
status |
Fresh snapshot: running models, ports, RAM/pressure/swap |
list_models |
All discovered models with size, max context, pin state |
load_model |
Load and block until the endpoint is healthy |
unload_model |
Unload one model (pinned models are refused) |
unload_all |
Unload everything except pinned models |
switch_model |
Load new, verify healthy, then unload the rest |
get_settings |
Read-only settings dump |
Safety rails:
- Swap guard — loads that would exceed free RAM are refused with the largest context size that would fit, unless you enable Allow swap for agent loads.
- Pinning — right-click a running model → Pin. Agents cannot unload it; your own Unload buttons still can.
- Notifications — a macOS notification is posted whenever an agent loads or unloads a model (toggleable).
- Read-only settings — agents can never change ports, context sizes,
or sampling. Per-call
ctx_size/portrequests are ephemeral. - Turning the toggle OFF blocks every tool call instantly but never unloads running models.
~/Projects/LLM-Switcher/
├── src/
│ ├── LlamaMenubarApp.swift # @main entry point + settings window host
│ ├── ServerManager.swift # the brain: discovery, process lifecycle, state
│ ├── MenuView.swift # menu bar dropdown UI
│ ├── SettingsView.swift # Local / Per-Model / OrcaRouter / Help / About settings window
│ ├── DomainTypes.swift # ModelEntry, ModelState, AppSettings, ModelBackend
│ └── llama # the CLI (bash)
├── scripts/
│ ├── install.sh # build & install
│ ├── uninstall.sh # remove everything
│ └── make_icon.py # generate the app icon
├── assets/
│ ├── AppIcon.icns # compiled icon (1024x1024)
│ └── icon.iconset/ # source PNGs at all sizes
└── docs/
├── ARCHITECTURE.md # how the pieces fit together
├── GEMMA4_LOADING_DETAILS.md # Gemma 4 loading deep-dive
└── STATE.md # where state lives
Documentation:
| Doc | What it covers |
|---|---|
| ARCHITECTURE.md | Component diagram, how the Swift app and CLI talk to each other |
| GEMMA4_LOADING_DETAILS.md | Gemma 4 loading deep-dive: every llama-server flag, mmproj encoder, MTP heads, chat templates |
| STATE.md | Where state lives on disk, PID files, UserDefaults, auto-start |
graph TD
A["⚡ Menu bar app (Swift)"] <-->|"UserDefaults\n(shared domain)"| B["🖥 llama CLI (bash)"]
A -->|"spawns & monitors"| C["llama-server :8080\n(Model A)"]
A -->|"spawns & monitors"| D["llama-server :8081\n(Model B)"]
B -->|"spawns & monitors"| C
B -->|"spawns & monitors"| D
C -->|"PID file"| E[("~/.local/share/\nllama-menubar/pids/")]
D -->|"PID file"| E
A -->|"reads"| E
B -->|"reads"| E
A -->|"ps scan\n(external process detection)"| F["Any running\nllama-server"]
B -->|"ps scan"| F
style A fill:#1a1a2e,color:#4ade80,stroke:#4ade80
style B fill:#1a1a2e,color:#22d3ee,stroke:#22d3ee
style C fill:#1e3a5f,color:#93c5fd,stroke:#3b82f6
style D fill:#1e3a5f,color:#93c5fd,stroke:#3b82f6
style E fill:#27272a,color:#a1a1aa,stroke:#52525b
style F fill:#27272a,color:#a1a1aa,stroke:#52525b
Both the menu bar app and the CLI can:
- Start/stop server processes (each model = one process on its own port)
- Read settings from
~/Library/Preferences/local.llama-menubar.plistvia thedefaultscommand - See each other's processes by parsing
psoutput and matching model paths
The Swift app requires macOS 26+ on Apple Silicon. The build pins its deployment target explicitly (-target arm64-apple-macos26.0 in scripts/install.sh) — the binary was always effectively macOS 26+ (it inherits the host OS version otherwise), and it uses MenuBarExtra, @Observable and the Settings scene.
# Manual compile of the full module (no bundle)
swiftc -parse-as-library -O \
-framework SwiftUI -framework AppKit \
src/*.swift \
-o lm-switcher
# Full build + install (recommended)
./scripts/install.shThe -parse-as-library flag is required because we use @main; without it the compiler treats the file as a top-level script and rejects @main.
Because that's the lightest-weight way to keep a tool always available on macOS without:
- Taking up dock space
- Showing up in Cmd-Tab
- Needing a main window
- Feeling heavy when you just want to toggle a model
LSUIElement = true in Info.plist is what makes the app a "true" menu bar app — invisible in the dock and app switcher.
Current: v0.9.13b — beta. OrcaRouter optional cloud provider (dedicated Settings tab, API key in the macOS Keychain, live "Test connection"), sushi backend (beamivalice/sushi, the mlx-serve fork with EXL3/affine packs — one shared server, models load on demand), Splash backend (paperniuk/splash M1/M2 fork), menu bar + Per-Model redesign, MCP agent access, idle auto-unload, eight engines (llama.cpp, MLX, oMLX, MTPLX, DS4, mlx-serve, Splash, sushi), and an in-app engine-selection map. (Matches CFBundleShortVersionString in scripts/install.sh.)
See CHANGELOG.md for full version history.
Personal use. Modify and redistribute freely.
LM Switcher is free and open source. If it earns its menu-bar corner, a small sponsorship keeps the coffee flowing — Sponsor on GitHub.
