Turn any script into a managed menu bar switch on macOS.
English | 简体中文
SimpleToggle renders each of your automation scripts as a first-class control in the macOS menu bar — a switch, a long-running process, or a button. A few comment lines at the top of the script describe it; SimpleToggle handles the process lifecycle, status polling, parameter UI, persistence, scenes, automations, shortcuts and a full local API for AI agents.
Everyday macOS automations — keep the display awake, flip a proxy, show hidden files, flush DNS — each traditionally need their own single-purpose utility crowding the menu bar. Script-to-menu-bar tools exist (SwiftBar, xbar, Hammerspoon), but their model is script → printed output → display: on/off semantics, process lifecycle and state persistence are hand-rolled per plugin.
SimpleToggle's model is script → managed switch. The contract is the product.
| SwiftBar / xbar | Hammerspoon | SimpleToggle | |
|---|---|---|---|
| Mental model | print output → display | Lua automation framework | script → managed switch |
| On/off semantics | hand-rolled per plugin | hand-rolled in Lua | built in (toggle, daemon, button) |
| Process lifecycle | manual (pid files, pkill) | manual | process groups, SIGTERM → SIGKILL, orphan sweep |
| State persistence | manual | manual | restored at launch |
| Parameters | none | DIY | declarative: select, number with presets, text, key |
| Scenes, automations, shortcuts | — | DIY | built in |
| AI agents | — | — | local API, CLI, MCP server and skill, with approval |
- Three switch types —
toggle(on / off / status),daemon(the app holds the process and stops it), andaction(a button that runs once and shows progress and the result) - Panel — a glass flyout under the menu bar icon: list or grid, comfortable or compact, groups, search, inline parameters, a scene bar
- Workbench — a code-first editor with syntax highlighting and contract diagnostics; an inspector that edits the header (name, icon, type, parameters, menu bar) in sync with the code; test buttons with live output; run history; a library of ready-made scripts
- Scenes — one click to set several switches; click again to put everything back the way it was
- Automations — run switches or scenes at a time, when joining a network, on power changes, when an app launches or quits, when a display connects, at login / wake / unlock, when a file changes, or on a shortcut; optional day and time windows; your manual changes win for 10 minutes; rules that flip a switch back and forth are paused automatically
- Global shortcuts — for switches, scenes, the panel and the workbench, with conflict detection
- Activity — usage statistics (uses per day, a year-long heat map, time of week, sources, switches by use) and every run with its source (manual, shortcut, scene, automation, AI, CLI…), duration, exit code and output; filter, export, clear
- Menu bar — three icon styles, an optional count or countdown, and per-switch status items
- Appearance — light, dark or system; six palettes (Graphite by default, plus Signal, Paper, Peach, Phosphor and Ice), where one signal color marks what is on or running; glass or solid panel
- AI integration — local API, CLI, MCP server and agent skill; per-capability permissions for AI; script changes from AI are shown as a diff and written only after you approve; one-click setup for Claude Code, Cursor and Codex
- Deep links —
simpletoggle://for Shortcuts, Raycast and Alfred - Private by design — everything stays on your Mac; the only network request checks for updates
- English and Simplified Chinese, switchable at runtime
brew install --cask hanlunwang/tap/simpletoggleOr download the disk image from Releases. SimpleToggle is signed with a Developer ID, notarized, and updates itself (updates are verified with an Ed25519 signature before they are installed). Requires macOS 14 or later.
The Homebrew cask also links the simpletoggle command. On first launch a short walkthrough lets you pick switches from the library, choose a look and connect AI clients; every step can be skipped.
A switch is one executable script in ~/.config/simple-toggle/switches/ (changeable in Settings → Advanced), carrying metadata as # <switch.*> comments within its first 40 lines.
#!/bin/bash
# <switch.name> Keep Awake
# <switch.icon> lucide:coffee
# <switch.type> daemon
# <switch.description> Prevent sleep for a while
# <switch.param> key=mode type=select label=Mode default=d options="Display only=d|Display & system=dis"
# <switch.param> key=duration type=number label="Duration (min)" default=0 min=0 max=1440 presets="Unlimited=0|1 h=60|2 h=120"
# <switch.menubar> mode=add icon=lucide:coffee countdown=on
ARGS="-${SWITCH_MODE:-d}"
[ "${SWITCH_DURATION:-0}" -gt 0 ] && ARGS="$ARGS -t $((SWITCH_DURATION * 60))"
exec caffeinate $ARGS| Directive | Required | Description |
|---|---|---|
<switch.name> |
yes | Display name |
<switch.icon> |
no | lucide:<name> (any icon from lucide.dev), an emoji, or sf:<symbol> (mapped to Lucide) |
<switch.type> |
no | toggle (default), daemon or action |
<switch.description> |
no | One sentence for the library and tooltips |
<switch.param> |
no | One parameter per line; see below |
<switch.menubar> |
no | Menu bar presence while on; see below |
| Type | Invocation | Result |
|---|---|---|
toggle |
<script> on, <script> off, <script> status |
status prints on or off; polled every 5 s while the panel is open, 30 s in the background. Non-zero from status → unknown; from on/off → error |
daemon |
<script> run |
The app holds the process. Off sends SIGTERM to its process group (SIGKILL after 3 s). Exit 0 resets the switch; non-zero → error. Use exec |
action |
<script> (no arguments) |
Exit 0 is success; the last line of output is shown as the result |
Every run gets SIMPLETOGGLE_BIN (the app's binary), SIMPLETOGGLE_TRIGGER (who started it) and the parameters as SWITCH_<KEY>; Homebrew's bin directories are added to PATH. Scripts without a shebang run with the default shell (Settings → Advanced).
| Attribute | Applies to | Description |
|---|---|---|
key |
all | Identifier; injected as SWITCH_<KEY> (uppercased, non-alphanumerics → _) |
type |
all | select, number, text or key |
label |
all | Display label |
default |
all | Initial value; for select, the first option when omitted |
options |
select | label=value pairs separated by | |
presets |
number, text | Quick-pick buttons, same format |
min / max |
number | Inclusive bounds |
hint |
all | Placeholder or tooltip |
Quote values that contain spaces. Changing a parameter while a switch is on restarts it. type=key renders a key picker; scripts send the key with "$SIMPLETOGGLE_BIN" press "$SWITCH_KEY" (one-time Accessibility grant).
# <switch.menubar> mode=add icon=lucide:coffee countdown=on — mode=add shows a dedicated status item while on, mode=replace swaps the main icon; countdown=on needs a number parameter with key=duration in minutes (0 = unlimited).
Every script submission — workbench, CLI, API or MCP — is linted against the contract: errors are rejected, warnings are shown with their line numbers.
simpletoggle list # switches with state
simpletoggle on|off <id> # turn a switch on or off
simpletoggle run <id> # run an action and print its output
simpletoggle state <id> # on | off | error | unknown
simpletoggle params <id> # parameters and current values
simpletoggle set <id> k=v [...] # set parameters (restarts a running switch)
simpletoggle enable|disable <id> # show or hide in the panel
simpletoggle cat <id> # print the script
simpletoggle add <file.sh> # validate and install a new switch
simpletoggle put <id> <file.sh> # validate and replace a script
simpletoggle rm <id> # delete (moves to the Trash)
simpletoggle scenes # list scenes
simpletoggle scene <name> # activate a scene, or restore it when active (--activate / --restore)
simpletoggle log [id] [--failed] [--limit N] # recent runs
simpletoggle validate <file.sh> # offline contract lint
simpletoggle press <keyspec> # synthesize a key or mouse event
simpletoggle doctor # diagnose permissions and leftover processes
simpletoggle mcp # MCP server over stdio
simpletoggle version
--json gives machine-readable output. <id> is the script file name; .sh is optional.
-
One-click setup — Settings → AI & Integrations connects Claude Code (
claude mcp add), Cursor (~/.cursor/mcp.json) and Codex (~/.codex/config.toml), showing exactly what will be written first and backing up the file. Any other MCP client: add{ "mcpServers": { "simpletoggle": { "command": "/opt/homebrew/bin/simpletoggle", "args": ["mcp"] } } } -
MCP tools — list switches, set switches and parameters, run actions, read and write scripts (validated), activate scenes, read automations and run history, check pending changes.
-
Permissions — choose what AI agents may do: read, flip switches, write scripts, delete scripts (off by default). Script changes from AI wait for your approval and are shown as a diff in the workbench; you get a notification. These limits guard against agent mistakes; they are not a security boundary, since any program on your Mac can edit the script files.
-
Skill —
skills/simpletoggle/teaches agents the CLI and the authoring workflow (write →validate→add/put→ verify). Install it from Settings → AI & Integrations. -
Local API —
127.0.0.1:43737, loopback only; requests need the token from~/.config/simple-toggle/api.json(except ping and validate; the CLI and MCP read it)./v1covers switches and scripts;/v2adds actions, run history, server-sent events, scenes, automations and changes. Every request carries its source and shows up in Activity.
simpletoggle://toggle/keep-awake simpletoggle://on/wifi simpletoggle://off/wifi
simpletoggle://run/flush-dns simpletoggle://scene/Focus
simpletoggle://panel simpletoggle://workbench/scenes
simpletoggle://settings/ai
No accounts, no telemetry. Scripts, state and logs stay on your Mac (~/.config/simple-toggle/ and ~/Library/Application Support/SimpleToggle/). The only network request checks GitHub Releases for updates. Permissions are requested only when needed: Accessibility for key parameters, Location only if you want Wi-Fi names shown in rules, Apple Events when a script controls another app, Notifications when the first one is sent.
SimpleToggle 2 is written in Go with mygo (native GPU UI, no web view). The engine, API, CLI and MCP server live in internal/ and do not depend on the UI framework; app/ is the UI.
go test ./... # unit and end-to-end tests
go tool mygo dev # run the app with live reload
go tool mygo build # signed, notarized app and disk image (needs the signing identity in mygo.json)How the pieces fit together is described in docs/architecture.md.
Bug reports and pull requests are welcome; CI runs gofmt, go vet, the tests and the contract lint over every bundled script. To report a security issue, see SECURITY.md.
MIT. SimpleToggle bundles the Geist fonts (SIL Open Font License 1.1, app/fonts/OFL.txt) and Lucide icons (ISC, app/icons/LUCIDE_LICENSE), and is built with mygo and the MCP Go SDK.