Skip to content
FacensPublic

About

A macOS menu-bar app that starts a terminal coding-agent session in the folder, on the account, at the model you mean. Apple Silicon only.

Resources

Code of conduct

Contributing

Security policy

Stars

2 stars

Watchers

0 watching

Forks

The AgentMenu icon: a prompt caret, a command line under it, and a dot

AgentMenu

Start a terminal coding-agent session in the folder you mean, on the account you mean, at the model and effort you mean.
Two clicks from the menu bar. No config file to edit, no session to correct after it starts.

macOS 14 or later · Apple Silicon · Download the latest release · GPL-3.0-or-later

The AgentMenu popover: a Work/Personal profile switch, a rate-limit readout with a 5-hour and a weekly bar, the front Finder window as a launch target, and three project rows each showing the model and effort it would launch with

A ⚠︎ on a row marks a target that bypasses the agent's own permission prompts.

Changing model or effort inside a running agent session costs tokens and attention: the session is already loaded when the correction happens. AgentMenu moves that choice to launch time, where it belongs.

Install

Apple Silicon only. The build is arm64 and does not run on an Intel Mac.

  1. Download AgentMenu-<version>.zip from the Releases page.
  2. Unzip it.
  3. Move AgentMenu.app to /Applications.
  4. Open it.

The app is signed with a Developer ID and notarized, so it opens with no warning — no right-click dance, no trip through System Settings.

Betas. Releases marked Pre-release on the Releases page are betas — signed and notarized the same way as a final release, just published first, on the beta update channel. A final release supersedes the beta it followed. Install one by hand if you want a change early; once in-app updates ship, a Settings toggle will opt a copy into betas instead.

What it does

  • Launch targets: folders you configure, the folder of the front Finder window, and $HOME (which is there on first run, before you configure anything).
  • A preset per entry: agent, terminal, account profile, model, effort, permission mode and advisor state. An entry overrides only the fields it cares about; the rest inherit from the global default. One folder can have several entries — the same project on the work account and on the personal one, or on opus and on sonnet — and the duplicate button makes the second one from the first, so only the value you want to change is left to change.
  • A one-shot override: expand a row, change model or effort, launch. The saved preset is untouched unless you explicitly save it to the folder or to the global default.
  • Two accounts, kept apart: the dropdown is separated by profile, so a work folder cannot be launched on the personal account by mis-click.
  • Rate-limit readout: 5-hour and weekly consumption per account, with how old the reading is — never presented as current when it isn't. The menu-bar icon turns into a meter when an account is near its limit.
  • Agents and terminals are data. Each is a manifest file; adding one is a file you write, not a fork you maintain. See docs/adding-an-agent.md and docs/adding-a-terminal.md.
  • A launch is a full command, with the agent binary's absolute path and an environment prefix. It never depends on a shell alias or a shell function, so it works regardless of how your shell is set up.
  • It writes into an agent's configuration only what it names, only when you ask, and removes it on request. Today that is one thing: the status-line bridge, which you install deliberately from Settings → Accounts. Installing writes agentmenu-statusline.sh into that account's configuration directory and sets one key, statusLine.command, in its settings.json; the dialog names both before anything is written, and every other key in the file is left as it is. Once installed, the bridge writes two more files beside them, tb-rate-snapshot.json and tb-rate-history.jsonl. AgentMenu otherwise reads settings.json once, to seed its defaults. Sessions are read, never written: the session registry (sessions/<pid>.json) and the transcripts (projects/) that Claude Code keeps are opened read-only, and AgentMenu adds nothing to either. Removing the bridge is the Remove button next to Install in Settings → Accounts, and it takes all of it back out: the dialog names the three files and the key, then it deletes the files and sets statusLine.command back to the status line you had before (or removes statusLine if you had none). If you changed statusLine.command after installing, it refuses and touches nothing.
  • Sessions, not just launches. A second tab lists the agent sessions running on this Mac, what each one is doing, and which ones are waiting for you. See Sessions.

Agents and terminals

Status
Claude Code Verified end to end (against 2.1.266). The default.
Codex, OpenCode Manifests are present, disabled and marked unverified: the flags were not executed, so the app will not pretend otherwise. Enable one in Settings once you have checked it.
iTerm2 Verified. Preferred when it is installed.
Terminal.app Verified. The fallback every Mac has, so a first run always has somewhere to launch.
Ghostty Manifest is present, disabled and marked unverified — same rule as the agents above.

Every one of these is a small TOML file under Resources/, and a file with the same id in ~/.config/agentmenu/agents/ or ~/.config/agentmenu/terminals/ overrides it on your machine alone.

First run

With no ~/.config/agentmenu/config.toml, AgentMenu writes one that already works before it asks you anything: the agent whose binary it can find, the terminal that is installed, the account profiles whose configuration directories exist, and $HOME as a launch target. Then the popover shows a setup card with the two decisions it cannot make for you — which agents to use, and which project folders — and detects the git checkouts under the usual code directories to make the second one a matter of ticking boxes.

The card stays in the popover until at least one real project folder is configured. Everything else — renaming accounts, more folders, the status-line bridge — lives in Settings.

The rate-limit readout

Claude Code hands rate-limit data to exactly one place: the standard input of your statusLine command. There is no claude usage subcommand, and hooks receive nothing. So AgentMenu reads a small snapshot file that a status-line bridge writes.

Install it from Settings → Accounts, per account. It writes the bridge script into that profile's configuration directory and points statusLine at it. If you already have a status-line command, the bridge calls yours and passes its stdin through, so your status line keeps working — installing is additive, not a replacement. Remove in the same place undoes it.

Two consequences worth knowing:

  • The reading is only as fresh as your last session — the snapshot updates while an agent session runs, throttled to once a minute. After an idle evening it is hours old, and AgentMenu shows that age rather than pretending.
  • With no snapshot for an account, the readout is hidden entirely.

Sessions

The popover has two tabs, Launch and Sessions, and it always opens on Launch. Sessions lists every Claude Code session running on this Mac, grouped by folder, each marked Working, Needs you, Your turn or Unknown. Click a row and its Terminal or iTerm2 tab comes to the front. Quit, rename and reopen are in the row and header menus, and a closed session can be reopened later.

Sessions launched from AgentMenu can keep running when the window closes. That is on by default and is a preset field you can turn off per folder or for one launch. It runs the agent under a small tmux that ships inside the app and is separate from any tmux of yours.

AgentMenu learns about sessions by reading the files Claude Code already keeps. It does not write to them. Other agents are listed with process-level status only: running, or not.

docs/sessions.md covers the tab, keep running (including a Shift+Enter limit in Terminal.app), restore, notifications, the macOS permissions involved, and where AgentMenu keeps its own state.

🔎 "I installed it and nothing appeared"

It is almost certainly your menu-bar manager, not the install. Ice, Bartender, Hidden Bar and friends park a new status item in their hidden section, and the standard macOS positioning key does not override that — we verified it: the item lands off-screen at x ≈ -4000 and reports itself as present the whole time.

Open your menu-bar manager and unhide AgentMenu (in Ice: reveal the hidden section and drag the AgentMenu item out of it). The app is running; the icon is just parked.

There is no command-line tool

Earlier versions of this README documented agentmenu as a typed command — resolving a folder's account from a shell script, installing the status-line bridge. That is reversed: nothing puts agentmenu on your PATH, and there is no supported way to type it yourself.

The CLI at Contents/Resources/bin/agentmenu, inside the app bundle, still exists, but it is internal. The app invokes it itself — to install or remove the status-line bridge — and the bridge script it writes invokes it again on every refresh.

Build from source

Xcode is not required. Command Line Tools plus SwiftPM is the whole toolchain.

git clone https://github.com/Facens/agentmenu.git
cd agentmenu
make bundle          # -> dist/AgentMenu.app
make test            # the AgentMenuKit suite
open dist/AgentMenu.app

make bundle signs with the maintainer's Developer ID Application certificate when it is in the keychain. On any other machine — yours, most likely — there is no certificate, so it signs ad hoc instead and says so with a banner. An ad-hoc build runs on the machine that built it; it will not pass Gatekeeper anywhere else and cannot be notarized.

The test suite is a plain executable target, not an XCTest bundle: neither XCTest nor swift-testing exists in a Command Line Tools install, and requiring Xcode to run the tests would make "no Xcode" true only for the maintainer.

Test surface

A release is exercised on a clean machine before it ships, by a script that clicks the app rather than calling into it. A script cannot see what the app decided, only what it drew — so the app can be asked, at launch, to keep a journal of what it detected, what it is showing and what you chose. It is a read-only record: it performs no action and changes nothing about how the app behaves.

It is off, and only you can turn it on. There is one switch, a preference key, and it names a file:

defaults write dev.facens.agentmenu harnessJournal journal.ndjson   # on
defaults delete dev.facens.agentmenu harnessJournal                 # off

The value is a file name, not a path. AgentMenu writes it in one fixed place — ~/Library/Application Support/dev.facens.agentmenu/harness/ — and a value containing / or .. is refused outright: nothing is written anywhere, and one line in the app's log is the only trace. The file is created at mode 0600, is never written through a symbolic link, and is only ever appended to.

Each line is one JSON object: a sequence number, a timestamp, the schema version, the build, and an event with its data. The events are the app's own decisions — harness started, detecting started, detecting finished, setup shown, setup finished, launch requested, launch result, bridge installed, save failed — and the values are the ones the popover was already showing you: the agents found on this machine, the folders suggested, the binary a launch resolved to, the folder it would run in, the model and effort. For a launch it records the names of the environment variables the command sets and never their values, and never the argument vector. No file contents, no credentials, nothing you could not read off the screen.

Three more things worth knowing before you switch it on:

  • It stops growing. The journal is capped at 1 MiB; past that, the oldest lines are dropped to make room for the newest. A single value longer than 512 characters is shortened, so one long error message cannot push a run's own history out of the file.
  • It cleans up after itself. Launch AgentMenu with the key unset and any journal left in that directory is deleted. Files in there that are not journals are left alone.
  • It is readable by anything running as you. The directory carries no secret and confinement is not the point — any process that could read it could also have set the key in the first place. The point is that the app writes nothing outside that one directory.

Nothing listens. The journal opens no socket, no port and no network traffic; it is a file, and the only way to read it is to read it. The one socket AgentMenu does create is separate from the journal: the bundled tmux that keeps sessions running talks over a Unix socket file under ~/Library/Application Support/dev.facens.agentmenu/host/, in a directory you own. It is a local path, not a port, and nothing on the network can reach it. See docs/sessions.md.

Every control this exercises carries a stable accessibility identifier. The harness drives the built app by AXIdentifier alone — never a coordinate, never a label — so the same identifiers a screen reader would see are also what the script clicks. They're part of the app's contract with that script, not incidental UI detail; see CONTRIBUTING.md for the rule.

Every release carries the redacted result of a real run. Before a release is presented as current, this exact asset — the same zip you'd download — is installed and driven through every scenario on a vanilla macOS VM that never saw this machine before, and the release carries the result: report.public.json, attached to it on GitHub. It holds a verdict, a list of finding codes, the asset's SHA-256, which build of the golden image it ran against (the macOS and Claude Code versions, and when it was built), the scenario names, and a run id — and nothing else. No value in it may contain a /, so no screenshot, no host path and no hostname ever reaches it, and a finding is always one of a fixed, published set of codes, never free text.

Contributing

The cheapest useful contribution is a manifest for an agent or a terminal you have actually run, and it needs no licence grant at all. Code contributions do carry one; CONTRIBUTING.md says what and why, upfront.

Licence

GPL-3.0-or-later. See LICENSE.

A paid version may exist later. If it does, it will be code that is never published, not a relicensing of this repository — what is GPL here stays GPL. Contributions carry a grant that permits that; it is stated upfront in CONTRIBUTING.md rather than announced afterwards. The cheapest useful contribution — an agent or terminal manifest — needs no grant at all.

About

A macOS menu-bar app that starts a terminal coding-agent session in the folder, on the account, at the model you mean. Apple Silicon only.

Resources

Code of conduct

Contributing

Security policy

Stars

2 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages