# Claude Desktop accounts — a Claude Code mod
Switch which account the **Claude Desktop** app is signed in as, from a Claude
Code session in the app's Code tab (or a terminal). It shows each account's
quota beside it and drives `ai-usagebar account switch --desktop`.
## Requirements
- macOS with the Claude Desktop app.
- Claude Code **2.1.287+**, the first version that loads mods.
- `ai-usagebar` installed on `PATH` or in `~/.cargo/bin` (Homebrew's
`/opt/homebrew/bin` and `/usr/local/bin` are also checked).
- At least one Desktop account saved:
```bash
ai-usagebar account add work --desktop ai-usagebar account add personal --desktop ```
## Install
1. Get this folder on your Mac, for example with `git clone https://github.com/akitaonrails/ai-usagebar ~/ai-usagebar`.
2. Point Claude Code at it in `~/.claude/settings.json`. Several folders are separated with `:`.
```json
{
"env": {
"CLAUDE_CODE_PLUGIN_DIRS": "/Users/you/ai-usagebar/claude-code-mod",
"CLAUDE_CODE_PLUGIN_DIR_WATCH": "1"
}
}
```
`CLAUDE_CODE_PLUGIN_DIR_WATCH=1` is optional. It makes Desktop sessions
reload the mod when the folder changes, for example after a `git pull`.
3. Quit and reopen the Claude app. Both variables are read when a session starts.
In a terminal, `claude --plugin-dir ~/ai-usagebar/claude-code-mod` loads it for one session.
## Use
`/claude-account` opens the **Claude accounts** pane:
- **The list:** every saved Desktop account, with its e-mail, the active one marked, and a **Switch** button on each of the others.
- **Quota:** under each account, its quota windows (`5h`, `7d`, model weeklies) as a bar and a percentage, colored by how much is used, with the time to reset. They come from `ai-usagebar usage --json` and load after the list.
- **Switching:**
1. **Switch** first runs `account switch --desktop --dry-run` and shows the plan.
2. **Switch and reopen** then does it. Claude quits, the saved login and local history move to that account, a rollback archive is written, and the app reopens.
3. Every open session restarts, the one that pressed the button included.
The status line shows the active Desktop account (`Desktop: work`).
## How the switch outlives Claude
Sessions in the desktop app are children of Claude.app, so anything they start
dies when the app quits, and a switch run that way would never reopen it. The
mod hands the switch to launchd instead:
```
/bin/launchctl submit -l com.akitaonrails.ai-usagebar.desktop-switch.<ms> \
-o ~/Library/Logs/ai-usagebar-desktop-switch.log -e <same> -- \
/bin/sh -c '<JOB_SCRIPT>' sh <job> <ai-usagebar> account switch --desktop --yes -- <label>
```
- **Outside Claude:** the job belongs to launchd in your login session, outside Claude's process tree and process group.
- **The label is safe:** it is its own argument, never spliced into shell text, and only labels from `account status --json` are offered.
- **It runs once:** `launchctl submit` would restart a job that fails, so the wrapper removes its own job when the switch ends.
- **Logs:** output goes to `~/Library/Logs/ai-usagebar-desktop-switch.log`, with an `== exit N` line after each switch.
- **First run:** macOS may ask once to let the switch control Claude. If you deny it, `ai-usagebar` quits the app with signals instead.
`ai-usagebar` is looked up in `$PATH`, then `~/.cargo/bin`, `/opt/homebrew/bin`,
and `/usr/local/bin`, and the absolute path is passed to launchd, since
launchd's `PATH` is minimal. The AI Usage.app bundle's `ai-usagebar-tray`
binary is deliberately not used: the mod drives the supported `ai-usagebar`
CLI only, and says so when it is not installed rather than showing an empty
quota.
## Limits
- A switch runs without a terminal. When an account deleted a routine that
another account still has, `ai-usagebar` keeps every copy rather than asking.
Run `ai-usagebar account switch <label> --desktop` in a terminal to decide.
- Adding an account is interactive, so it stays a terminal command
(`ai-usagebar account add <label> --desktop`).
## Develop
```bash
claude plugin validate claude-code-mod
claude plugin test claude-code-mod
npx -p typescript@5 tsc -p claude-code-mod --noEmit # once Claude has laid .claude-plugin/types/
```
The tests mock `$.process`. Nothing in them runs `ai-usagebar` or `launchctl`.