pitboard 0.5.0

Park and restore your own Claude Code and Codex logins on one machine, and see what each one has left.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
# pitboard

Switch between your own Claude Code and Codex logins, and see how much each one has left.
[usepitboard.com](https://usepitboard.com)

[![CI](https://github.com/datlechin/pitboard/actions/workflows/ci.yml/badge.svg)](https://github.com/datlechin/pitboard/actions/workflows/ci.yml)
[![crates.io](https://img.shields.io/crates/v/pitboard.svg)](https://crates.io/crates/pitboard)
[![Licence](https://img.shields.io/badge/licence-Apache--2.0-blue.svg)](LICENSE)

<img src=".github/media/menu.png" alt="The pitboard menu, listing Claude Code and Codex accounts with a check mark on each one in use and their five hour and weekly limits under their names" width="300">

If you have more than one Claude or ChatGPT subscription, changing accounts in Claude Code
or OpenAI's Codex CLI normally means signing out and back in through a browser. pitboard
keeps each login you are not using and puts the one you ask for where the tool reads it.
Your history, sessions, settings and projects stay where they are. Only the account
changes.

Status: pre-release. macOS is tested. On Linux it builds and the tests pass. Claude Code's
shipping build has no keyring backend outside macOS and Windows, so on Linux its login is
a plaintext file at mode 0600. Codex's default store is a file on every platform, and it is
the only Codex store pitboard supports; this was read from Codex's source and a macOS build.
pitboard's parked logins are 0600 files too. What has not happened is a run on a Linux
machine with a real signed-in Claude Code or Codex.

## Install

```sh
brew install datlechin/tap/pitboard            # the command line, macOS and Linux
brew install --cask datlechin/tap/pitboard-app # the menu bar app, macOS 14 or later
```

The app includes the command line and keeps both up to date, so install one or the other.
Neither needs Rust. Without Homebrew:

- `cargo binstall pitboard` fetches the release's command line for your machine.
- The [latest release]https://github.com/datlechin/pitboard/releases/latest has the
  command line for macOS and Linux, x86_64 and aarch64, and the app. Check a download with
  `gh attestation verify <file> --repo datlechin/pitboard`.
- `cargo install --locked pitboard` builds it from source. Without `--locked`, cargo
  ignores the `Cargo.lock` pitboard was released with and may pick newer dependencies.

If you installed the app with the old cask, `datlechin/tap/pitboard`, that name is the
command line now and Homebrew replaces your app with it once. Install the app again as
`pitboard-app`, as [Upgrading from 0.3.0](CHANGELOG.md#upgrading-from-030) shows.

Removing it: run `pitboard uninstall` first. It deletes every parked login it wrote,
pitboard's own files and the daily renewal schedule, and leaves the account you are signed
in to signed in. Then remove pitboard the way you installed it.

## Set up

Add the account you are signed in as, then the others:

```sh
pitboard enroll personal                # the Claude Code account signed in now
pitboard enroll work --sign-in          # another Claude Code account
pitboard enroll codex/personal          # the Codex account signed in now
pitboard enroll codex/work --sign-in    # another Codex account
```

`--sign-in` runs the tool's own sign-in in a separate directory (for Codex, `codex login`
with a private `CODEX_HOME`), so the login you are using now stays put, unless you sign in
to that same account: then the new login takes its place. Do not add an account with Claude
Code's `/login` or with `codex login`. Both replace the login in use, and pitboard cannot
keep a login it did not see leave. `codex login` also revokes the login it replaces.

A label belongs to a tool: `codex/work` is a Codex account, `claude/work` a Claude Code one.
A bare name for a new account means Claude Code. In `use`, `forget` and `rename` a bare
`work` is enough while only one tool has an account called `work`; if two do, pitboard lists
both and you type the full label, such as `codex/work`. `enroll` is different: a bare name
there always means Claude Code, so a Codex account is always `codex/<label>` when you enrol
it or sign in to it again.

## Daily use

```sh
pitboard                  # who is signed in to each tool, and what each account has left
pitboard use claude/work  # switch Claude Code
pitboard use codex/work   # switch Codex
pitboard status --offline # the last numbers measured, without asking anyone
```

```text
● personal  me@example.com  signed in
    5h    ██████░░░░   59%  resets in 1h 10m
    week  ███████░░░   73%  resets in 5d 18h
          about 48m left at this rate

○ work      me@company.com  ready · good for 26d 4h
    5h    █░░░░░░░░░   12%  resets in 3h 02m
    week  ████░░░░░░   40%  resets in 2d 4h
          resets in 3h 02m
```

The last line of each account is the one that answers the question. 73% of a weekly limit
means nothing without knowing whether it was 40% this morning, so pitboard keeps what each
limit has been doing and works out how long the account lasts: until its tightest limit
fills at the rate it has been filling, or until that limit resets, whichever comes first.
It says nothing at all until there is enough to go on, because a wrong answer here tells
you to switch when you need not.

Usage comes from Anthropic for Claude Code accounts and from OpenAI for Codex accounts, for
all accounts at once. A number is asked for again once the tightest limit it describes could
have moved by a percentage point, which for a five-hour window is three minutes, so running
`pitboard` twice in a row costs one set of requests and the app and the command line share
one between them. `pitboard status --fresh` asks anyway. If a parked
login has expired, pitboard renews it first. If the service cannot be reached, or asks for
less traffic, you get the last numbers pitboard saw, and when it saw them.

A Claude Code session that is already open picks up the switch within about 33 seconds. You
do not have to restart it. A running `codex` does not pick it up at all: restart it (see
[Codex](#codex)).

Each account holds one parked login. Switching to an account uses that login up, and
switching away parks a fresh one. A parked login is renewed whenever you run `pitboard`,
and otherwise not, so one you leave alone for weeks expires and needs a browser sign-in.
`pitboard schedule install` hands that to your computer's own scheduler instead. If one
expires or goes missing, sign in to that account again. The rest of what pitboard knows about it stays:

```sh
pitboard enroll work --sign-in
```

For a Codex account, name the tool: `pitboard enroll codex/work --sign-in`.

Signing in again to the account you are using puts the new login in use in place of the old
one, the way the tool's own sign-in would, and parks nothing. A running `codex` keeps the old
login until you restart it. If pitboard cannot tell whose login the tool is using, because
the service does not answer or the login cannot be read, it parks the new login instead of
writing over one it cannot name, and says so.

Other commands:

- `pitboard rename wrong right` fixes a label without signing in again. A rename stays
  inside its tool: `pitboard rename codex/work job` gives you `codex/job`.
- `pitboard forget <label>` drops an account, as in `pitboard forget codex/old`.
- `pitboard doctor` checks what pitboard depends on in Claude Code, and every parked login
  of both tools.
- `pitboard log` shows what pitboard has changed, and when.
- `pitboard abandon` gives up on an interrupted switch that cannot be finished, keeping
  every login. Only needed when recovery cannot reach Anthropic.
- `pitboard repair` asks the keychain what parked logins are on this machine and accounts
  for every one: given back to the account it belongs to, or reported and left alone.
  Only needed if pitboard's own files were lost or restored from a backup. On macOS a login
  it gives back that this pitboard did not write may be another pitboard's, so `forget` and
  `uninstall` leave it where it is.
- `pitboard adopt` takes over a `~/.pitboard` that came from another computer, keeping the
  accounts and dropping the logins they came with. Each then needs one
  `pitboard enroll <label> --sign-in`.
- `pitboard renew` renews every parked login that is due, and nothing else.
- `pitboard schedule install` asks this computer's own scheduler to run that daily, so
  parked logins stay alive while you are away. Opt-in; `pitboard schedule status` says
  whether it is on and `pitboard schedule uninstall` takes it away.
- `pitboard uninstall` deletes every parked login it wrote, the daily renewal schedule and
  pitboard's own files. On macOS it leaves one that `pitboard repair` gave back and this
  pitboard did not write, and says how many it left.

## Codex

pitboard leaves Codex alone until you enrol a Codex account. Before that, `pitboard` shows
no Codex row, reads nothing of Codex's and asks OpenAI nothing.

Codex works like Claude Code in pitboard, except for these:

- A running `codex` never notices a switch. Restart it. After a switch, on the command line
  or in the menu bar app, pitboard counts the `codex` processes running and tells you to
  quit them.
- Quit those sessions; do not type `/logout` in one. Signing out there revokes the old
  account's login at OpenAI, and that is the login pitboard has just parked. The account
  would then need a browser sign-in.
- pitboard works with Codex's default store, the file `~/.codex/auth.json` (or
  `$CODEX_HOME/auth.json`). If `config.toml` sets `cli_auth_credentials_store` to `keyring`
  or `auto`, pitboard refuses and says why: those keychain items belong to Codex, and every
  read by another program would ask you for permission. It also refuses `ephemeral`, which
  keeps the login in memory only, so there is nothing to park.
- Only a ChatGPT sign-in can be switched. A Codex signed in with an API key has no account
  login to park.
- The account is read from the login's own ID token, with no request. Two people in one
  ChatGPT Team or Business workspace are two accounts.
- On macOS a Codex park is the whole `auth.json`, which is too big for `security`'s standard
  input, so it goes on the argument line. pitboard says so after a switch or a `--sign-in`
  enrolment, but not when it renews a parked login. See the question about the keychain
  below.

## Status line

`pitboard statusline` prints one line with the account in use and what every account has
left:

```text
personal 59%·73%  work 12%·40%
```

It reads what Claude Code passes on stdin plus pitboard's own files, and lists Claude Code
accounts only, since Claude Code is what runs it. It does not use the network and does not
touch a login. Add it to `~/.claude/settings.json`:

```json
{
  "statusLine": {
    "type": "command",
    "command": "pitboard statusline",
    "refreshInterval": 10
  }
}
```

With `refreshInterval`, Claude Code also runs it every 10 seconds, so an idle session picks
up the numbers your busy sessions recorded.

To combine it with a status line of your own, pipe the same input to it:
`echo "$input" | pitboard statusline`.

## Menu bar app

The menu bar shows the account in use and its tightest limit. Its menu lists every account
under its tool, checks the one in use, and says under each what its limits stand at.
Choosing another account switches to it. When the account in use runs out, the app tells
you once, and the menu offers the account of the same tool with the most left.

With accounts of both tools, the menu bar follows the signed-in account closest to running
out, whichever tool it is for. A Codex switch has no countdown: the app says that running
`codex` sessions keep the old account until you quit them and start them again, and how
many pitboard found running, if any.

<img src=".github/media/window.png" alt="pitboard's window, listing each account with a bar for each of its limits, when each resets, and a note on the last Codex switch above them" width="760">

pitboard's window has the rest: every account with its limits drawn out, everything the app
has to tell you in full, the activity log, and what `pitboard doctor` finds about your Mac.
Add Account signs in through the tool's own sign-in in your browser, and asks which tool
when both are installed. Each account's menu renames it, signs in to it again, copies its
address or forgets it. The app calls the same core as the command line rather than running
`pitboard` for each answer.

The command line comes inside the app, for `pitboard repair`, scripts and the status line,
and updates with it. The `pitboard-app` cask puts it on your `PATH`. Without the cask, and
with no other `pitboard` installed, Settings, Command Line, "Install Command Line Tool…"
links `/usr/local/bin/pitboard` to it, after asking for an administrator's password.

Usage is read when you open the menu, if the last read is a minute old, and every few
minutes while the app runs. "Open pitboard at login", what the menu bar shows, and daily
renewal are in Settings. Daily renewal runs the command line inside the app, so move a
downloaded app to Applications before turning it on. A copy from a release keeps itself up
to date. One you build yourself does not, because it carries no update key. Building it
needs Xcode, and Rust with both of the Mac's targets, since the app and the command line
inside it are built for Apple silicon and Intel alike:

```sh
rustup target add aarch64-apple-darwin x86_64-apple-darwin
./apple/scripts/build-app.sh
cp -R apple/build/Pitboard.app /Applications/
```

To work on the app, run `./apple/scripts/build-xcframework.sh` first, which builds the core
and its Swift bindings, neither of them committed, then open `apple/Pitboard.xcodeproj`.
`swift test --package-path apple` runs the unit tests. The UI tests run from Xcode, or with
`xcodebuild test -project apple/Pitboard.xcodeproj -scheme Pitboard -destination
'platform=macOS'`, each in a fixture that never touches your accounts.
[CONTRIBUTING.md](CONTRIBUTING.md#the-app) has the rest.

## Scripting

Every command that reports a result takes `--json` and prints the same envelope, including
on failure and for a mistyped command line: `{v, command, ok, data, warnings, error}`.
Error codes are stable. When a failure came from asking Anthropic or OpenAI, `error.cause`
says what went wrong underneath and whether asking again is worth anything:
`{"code": "rate_limited", "worth_retrying": true}`. `completions` and `manpage` write a generated file to stdout, so
they have no JSON form and refuse the flag rather than ignore it.
Exit codes: 0 done, 1 not done, 2 command line wrong, 3 a login or a tool's files are in a
state pitboard will not act on.

`use` names the tool in `provider`. `adoption` says whether sessions already running follow
on their own (`{"follows": "polling", "within_seconds": 33}`) or need a restart
(`{"follows": "restart", "program": "codex"}`); in the second case
`adoption_ceiling_seconds` is null.

Shell completions: `pitboard completions zsh` (or `bash`, `fish`, `elvish`, `powershell`).

Documentation: [docs.usepitboard.com](https://docs.usepitboard.com).

## What it will not do

These are rules, not gaps:

- No switching by itself on any server signal.
- No pooling or proxying of requests, and no `ANTHROPIC_BASE_URL` interception.
- No failover when an account is on hold.
- No renewing of the login you are signed in as. That is the tool's own job. pitboard
  renews only a login it parked itself.
- No export, import or sync of parked logins between machines.

The last one is a safety rule, not a missing feature. Each machine has to sign in to each
account itself. If a machine presents a refresh token another machine has already rotated,
Claude Code drops the login on both. If a `~/.pitboard` does arrive from another computer,
for instance through Migration Assistant, `pitboard adopt` keeps the accounts and drops the
logins rather than leaving you with a tool that refuses to do anything.

## What a switch costs you

None of these are pitboard's to fix, so they are listed plainly:

- Remote Control turns off for a conversation that was started under a different account.
  Claude Code does that when it sees the owner change.
- The prompt cache belongs to one account, so the first message after a switch rebuilds it.
  Measured on this project, that costs about the same as leaving a session idle for an
  hour, which a five-hour limit usually means has happened anyway.
- Usage for a parked account is read with its parked login, so work done on other machines
  counts too.
- A running `codex` keeps using the account it started with until you restart it.

## What it talks to

For Claude Code accounts, Anthropic:

- `api.anthropic.com/api/oauth/profile`: which account a login belongs to.
- `api.anthropic.com/api/oauth/usage`: what an account has left.
- `platform.claude.com/v1/oauth/token`: renewing a login pitboard parked, with Claude Code's
  own client id.

For Codex accounts, OpenAI:

- `chatgpt.com/backend-api/wham/usage`: what an account has left, and before a switch,
  whether OpenAI still accepts the incoming login. Codex makes the same read, and it spends
  no quota.
- `auth.openai.com/oauth/token`: renewing a login pitboard parked, with Codex's own public
  client id.

Which account a Codex login belongs to is read from the login itself, with no request.
pitboard has no telemetry and no server of its own. A copy of the app from a release also
checks `github.com/datlechin/pitboard` for updates.

## Questions people ask first

**Where do my tokens go?** Only to the service that issued them: Anthropic for Claude Code,
OpenAI for Codex, at the addresses listed above. Parked logins stay on the machine that made
them, in the keychain on macOS.

**Is this allowed?** pitboard only moves logins you already hold, between its own store and
the place each tool reads. It does not share an account between people, pool requests, or
touch an account you do not own. Whether several subscriptions suit what you are doing is
between you and Anthropic's or OpenAI's terms.

**What if it dies halfway through a switch?** It writes down what it is about to do before
it does it, including a fingerprint of the login on each side. The next command reads which
one is in place and finishes or undoes the switch from that, with no network needed. Only
when the login in place is neither, which is what the tool refreshing a token in those few
seconds looks like, does it need to work out whose login it is. A Codex login names its own
account, so that needs no network. A Claude Code login needs Anthropic to say, and when
pitboard cannot ask, it changes nothing and keeps the record for a later run. `pitboard
doctor` reports the state, and `pitboard log` is the record of every change it has made.

**My login is too big for the keychain, what now?** On macOS, `security` reads only about
two kilobytes of a command from standard input. A Claude Code login goes over that when it
holds MCP server tokens, and a Codex park always does, since it is the whole `auth.json`.
Past that there is one route left, passing it as an argument, where another process running
as you could read it while the call lasts. Claude Code does the same for its own login on
every token refresh. pitboard does it too, and says so after a switch or a `--sign-in`
enrolment that does it. It does not say so when it renews a parked login, and on macOS
every Codex park renewal goes that way. `PITBOARD_NO_ARGV=1` refuses instead, which on
macOS means no Codex account can be parked. Set it before parking any Codex account: with a
Codex park already in the keychain, the next renewal exchanges the refresh token and then
cannot store the result, and that account's parked login is lost. `pitboard doctor` shows
the size of the Claude Code login.

**What about Gemini CLI?** Not supported. Since 18 June 2026 Google no longer offers Gemini
CLI's "Login with Google" to individual, Google AI Pro and Google AI Ultra accounts
([notice](https://developers.google.com/gemini-code-assist/docs/deprecations/code-assist-individuals)).

**Why trust the download?** On macOS the app and the command line are signed with a
Developer ID and notarised by Apple, and the app's update feed is signed too. Homebrew
installs those same files, checked against the release's own `SHA256SUMS`. Every release
attests what it published: each tarball, the app, `SHA256SUMS`, a bill of materials beside
each artefact, and `appcast.xml`, which is the file that decides what an installed copy
runs next. The attestation names the workflow and the commit that produced the file:

```sh
gh attestation verify Pitboard-v0.3.0-macos.zip --repo datlechin/pitboard
gh attestation verify pitboard-v0.3.0-aarch64-apple-darwin.tar.gz --repo datlechin/pitboard
gh attestation verify SHA256SUMS --repo datlechin/pitboard
gh attestation verify appcast.xml --repo datlechin/pitboard
```

`SHA256SUMS` is worth attesting rather than only reading: on its own it is evidence against
a download that went wrong, not against anyone who could change the release.

Or build it yourself: `cargo install --locked pitboard`.

## How it works

Parked logins live in the keychain on macOS and in files under `~/.pitboard/vault/` on
Linux. pitboard will not move a login it cannot identify.

For Claude Code, pitboard asks Anthropic which account a login belongs to instead of
trusting Claude Code's config file, which can be a day behind the login it describes. On
macOS, Claude Code keeps its login in a keychain item that only `/usr/bin/security` is
trusted to read. pitboard reads and writes it with the same tool. Calling the Security
framework from another process changes that item's access list for good, and after that
every read Claude Code makes takes one to three seconds instead of a few milliseconds.
pitboard takes the same lock Claude Code takes around credential writes, and never writes
Claude Code's plaintext fallback file for you.

For Codex, the login is the file `~/.codex/auth.json`. Its ID token names the account, so
identifying it needs no request. `codex login` and `codex logout` revoke the stored refresh
token at OpenAI, so a Codex login is moved, never copied: the outgoing one goes into
pitboard's store and is read back before the incoming one is written. Codex takes no lock on
that file, so pitboard reads it back after a switch rather than trusting its own write.

See [SECURITY.md](SECURITY.md) for where your credentials live and what pitboard protects
against.

## Licence

Apache-2.0. Not affiliated with Anthropic or OpenAI. See [NOTICE](NOTICE).