Skip to content

Commit a7ff3c9

Browse files
committed
feat: Multi account switching for codex
1 parent 653c905 commit a7ff3c9

12 files changed

Lines changed: 1689 additions & 104 deletions

File tree

docs/advanced.md

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,7 @@ Opens an interactive visualisation of which parts of the context are consuming t
117117

118118
## Session management
119119

120-
Sessions are stored as JSONL files under `~/.claude/projects/<sanitized-cwd>/<session-id>.jsonl`. Each line in the file is a JSON object representing a message or event in the conversation.
120+
Sessions are stored as JSONL files under `~/.claurst/projects/<base64url(project-root)>/<session-id>.jsonl`. Each line in the file is a JSON object representing a message or event in the conversation. The per-session metadata index lives at `~/.claurst/sessions/<id>.json`.
121121

122122
The transcript directory is derived from the working directory at session start. Worktrees and path sanitisation mean the per-project folder name is a normalised representation of the absolute path.
123123

@@ -508,11 +508,11 @@ SSH sessions work similarly: `claurst --ssh` enables a remote-accessible session
508508

509509
Claurst reads instruction files from the filesystem before every session and whenever a relevant file changes. The lookup order is:
510510

511-
1. **Managed**`/etc/claude-code/CLAUDE.md` (administrator-controlled, always loaded).
512-
2. **User**`~/.claude/CLAUDE.md` and `~/.claude/rules/*.md` (personal global instructions).
513-
3. **Project**`CLAUDE.md`, `.claude/CLAUDE.md`, and `.claude/rules/*.md` in each directory from the filesystem root down to the current working directory. Files are loaded from the root toward the CWD so that parent-directory rules are visible when processing child-directory rules.
511+
1. **Managed**`/etc/claude-code/AGENTS.md` and `/etc/claude-code/CLAUDE.md` (administrator-controlled, always loaded).
512+
2. **User**`~/.claurst/AGENTS.md` then `~/.claurst/CLAUDE.md` (personal global instructions).
513+
3. **Project**`AGENTS.md`, `CLAUDE.md`, `.claurst/AGENTS.md`, and `.claurst/CLAUDE.md` in each directory from the filesystem root down to the current working directory. Files are loaded from the root toward the CWD so that parent-directory rules are visible when processing child-directory rules.
514514

515-
`AGENTS.md` is treated equivalently to `CLAUDE.md` for compatibility with other AI coding tools.
515+
`AGENTS.md` is preferred (universal cross-tool standard); `CLAUDE.md` is loaded next at every scope for compatibility with other Claude tooling. If both exist at the same scope, both are loaded.
516516

517517
Files can `@include` other files using a frontmatter include directive. Included files are resolved relative to the including file.
518518

docs/auth.md

Lines changed: 210 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
# Claurst Authentication Guide
22

33
Claurst needs credentials to call the Anthropic API (or another provider's
4-
API). This document covers every supported authentication method, how tokens
5-
are stored, how to check and clear credentials, and how to authenticate with
6-
non-Anthropic providers.
4+
API). This document covers every supported authentication method, multi-account
5+
profile switching, how tokens are stored, how to check and clear credentials,
6+
and how to authenticate with non-Anthropic providers.
77

88
---
99

@@ -14,12 +14,19 @@ Claurst checks for credentials in the following priority order:
1414
1. `--api-key` flag (highest priority, session-only)
1515
2. `api_key` field in `~/.claurst/settings.json`
1616
3. `ANTHROPIC_API_KEY` environment variable
17-
4. Saved OAuth tokens in `~/.claurst/oauth_tokens.json`
17+
4. Tokens for the **active Anthropic profile** under
18+
`~/.claurst/accounts/anthropic/<id>/oauth_tokens.json`
19+
5. Legacy `~/.claurst/oauth_tokens.json` (auto-migrated to a profile on first
20+
read)
1821

1922
The first non-empty credential found is used. Provider-specific credentials
2023
(OpenAI, Google, etc.) follow the same pattern but use their own environment
2124
variables and provider config entries.
2225

26+
Codex (OpenAI ChatGPT subscription) accounts follow a parallel system —
27+
multiple profiles stored under `~/.claurst/accounts/codex/<id>/`, with the
28+
active profile selected via the account registry.
29+
2330
---
2431

2532
## Method 1: API Key
@@ -109,7 +116,8 @@ claurst auth login
109116
5. The browser redirects to `http://localhost:<port>/callback` with an
110117
authorization code.
111118
6. Claurst exchanges the code for tokens via the token endpoint.
112-
7. Tokens are saved to `~/.claurst/oauth_tokens.json`.
119+
7. Tokens are saved under `~/.claurst/accounts/anthropic/<profile-id>/oauth_tokens.json`
120+
and the profile is registered as **active** in `~/.claurst/accounts.json`.
113121

114122
This flow produces a Bearer token (`user:inference` scope) used directly for
115123
API calls.
@@ -121,10 +129,22 @@ claurst auth login --console
121129
```
122130

123131
This uses the Anthropic Console authorization endpoint. After token exchange,
124-
Claurst calls the Console API to create a new API key, stores it in
125-
`~/.claurst/oauth_tokens.json`, and uses it as a standard API key for
132+
Claurst calls the Console API to create a new API key, stores it in the
133+
active profile's `oauth_tokens.json`, and uses it as a standard API key for
126134
subsequent requests (not as a Bearer token).
127135

136+
### Naming the profile
137+
138+
Add `--label <name>` to give the new profile a human-friendly name (otherwise
139+
the id is derived from the JWT email's local-part). This becomes the id you
140+
use when running `claurst auth switch`:
141+
142+
```bash
143+
claurst auth login --label work
144+
claurst auth login --label personal
145+
claurst auth switch personal
146+
```
147+
128148
### Manual fallback
129149

130150
If the browser does not open automatically, Claurst prints the full
@@ -134,6 +154,131 @@ when prompted.
134154

135155
---
136156

157+
## Multi-Account Profiles
158+
159+
Claurst stores **multiple named accounts per provider** and lets you switch
160+
between them without re-logging-in. Supported providers today: **Anthropic**
161+
(Claude.ai / Console) and **Codex** (OpenAI ChatGPT subscription).
162+
163+
This is useful for separating work and personal accounts, juggling
164+
Pro/Max/Team plans, or testing against multiple organizations.
165+
166+
### On-disk layout
167+
168+
```
169+
~/.claurst/
170+
├── accounts.json # registry (active + metadata)
171+
└── accounts/
172+
├── anthropic/
173+
│ ├── work/oauth_tokens.json
174+
│ └── personal/oauth_tokens.json
175+
└── codex/
176+
└── work/codex_tokens.json
177+
```
178+
179+
`accounts.json` schema (excerpt):
180+
181+
```json
182+
{
183+
"version": 1,
184+
"providers": {
185+
"anthropic": {
186+
"active": "personal",
187+
"profiles": {
188+
"work": { "id": "work", "email": "kuber@company.example", "subscription_tier": "max", "added_at": "2026-05-25T19:00:00Z" },
189+
"personal": { "id": "personal", "email": "kuber@personal.example", "subscription_tier": "pro", "added_at": "2026-05-25T19:05:00Z" }
190+
}
191+
},
192+
"codex": {
193+
"active": "work",
194+
"profiles": { "work": { "id": "work", "email": "kuber@company.example" } }
195+
}
196+
}
197+
}
198+
```
199+
200+
### CLI
201+
202+
`claurst auth` and `claurst codex` are symmetric — same subcommands for both
203+
providers:
204+
205+
```bash
206+
# Add accounts (each login becomes its own profile)
207+
claurst auth login # Claude.ai (default)
208+
claurst auth login --console # Console / API-key flow
209+
claurst auth login --label work # name the profile
210+
claurst codex login # ChatGPT/Codex OAuth
211+
claurst codex login --label personal
212+
213+
# Inspect
214+
claurst auth status # show active Anthropic profile
215+
claurst auth list # all Anthropic profiles
216+
claurst codex list # all Codex profiles
217+
claurst accounts # both at once (use --json for JSON)
218+
219+
# Switch the active account
220+
claurst auth switch work
221+
claurst codex switch personal
222+
223+
# Remove a stored profile
224+
claurst auth remove work # delete profile + tokens dir
225+
claurst codex remove personal
226+
227+
# Logout (clears tokens for the active profile)
228+
claurst auth logout
229+
claurst codex logout
230+
```
231+
232+
`claurst auth status` and `claurst codex status` exit `0` when logged in and
233+
`1` otherwise, so they can drive scripts:
234+
235+
```bash
236+
if claurst codex status > /dev/null; then
237+
echo "Codex login present"
238+
fi
239+
```
240+
241+
### Slash commands
242+
243+
Inside the interactive REPL the same operations are available as slash
244+
commands — Anthropic is the default, pass `--codex` to target Codex:
245+
246+
```
247+
/login # OAuth login (Claude.ai)
248+
/login --console # API-key flow
249+
/login --codex # add a Codex account
250+
/login --label work # name the new profile
251+
/logout # clear active Anthropic credentials
252+
/logout --codex # clear active Codex credentials
253+
/logout --all # purge every stored Anthropic profile
254+
/accounts # list every stored account
255+
/switch personal # set active Anthropic to "personal"
256+
/switch --codex work # set active Codex to "work"
257+
```
258+
259+
`/accounts` lists every profile with a `*` next to the active one and shows
260+
email and subscription tier when known.
261+
262+
### Identity detection
263+
264+
When you log in, Claurst decodes the JWT id_token (or access token for Codex)
265+
to extract your email and provider-side account_id. If a stored profile
266+
already matches that identity, the existing profile is refreshed instead of
267+
a duplicate being created — re-logging-in the same account is idempotent.
268+
269+
### Backward compatibility
270+
271+
If you previously used Claurst (with the older single-file storage), your
272+
existing tokens are auto-migrated on first read:
273+
274+
- `~/.claurst/oauth_tokens.json``~/.claurst/accounts/anthropic/<derived>/oauth_tokens.json`
275+
- `~/.claurst/codex_tokens.json``~/.claurst/accounts/codex/<derived>/codex_tokens.json`
276+
277+
The legacy files are removed after a successful migration. No manual action
278+
needed.
279+
280+
---
281+
137282
## Method 3: Device Code Flow
138283

139284
The device code flow (RFC 8628) is designed for headless or server
@@ -153,12 +298,12 @@ ANTHROPIC_API_KEY="sk-ant-..." claurst --print "summarize the last 10 commits"
153298

154299
## Token Storage
155300

156-
### Anthropic OAuth tokens
301+
### Anthropic OAuth tokens (per profile)
157302

158-
Saved to:
303+
Each Anthropic account profile has its own file:
159304

160305
```
161-
~/.claurst/oauth_tokens.json
306+
~/.claurst/accounts/anthropic/<profile-id>/oauth_tokens.json
162307
```
163308

164309
The file contains the access token, optional refresh token, expiry timestamp,
@@ -175,12 +320,21 @@ granted scopes, and account email. Example structure:
175320
}
176321
```
177322

178-
The file is written with user-only permissions (`600` on Unix). Do not commit
179-
it to version control.
323+
The active profile pointer lives in `~/.claurst/accounts.json` (see
324+
[Multi-Account Profiles](#multi-account-profiles)). Files are written with
325+
user-only permissions (`600` on Unix). Do not commit them to version control.
326+
327+
### Codex tokens (per profile)
328+
329+
```
330+
~/.claurst/accounts/codex/<profile-id>/codex_tokens.json
331+
```
332+
333+
Contains the OpenAI access token, refresh token, account_id, and expiry.
180334

181335
### Provider credential store
182336

183-
API keys for non-Anthropic providers are stored in:
337+
API keys for non-Anthropic providers without dedicated OAuth flows are stored in:
184338

185339
```
186340
~/.claurst/auth.json
@@ -203,6 +357,10 @@ This file is keyed by provider ID and contains either an `api` credential
203357
}
204358
```
205359

360+
> **Note:** `~/.claurst/auth.json` is the multi-provider credential cache for
361+
> simple API-key providers. It is **distinct** from `~/.claurst/accounts.json`,
362+
> which is the multi-account registry for Anthropic/Codex OAuth profiles.
363+
206364
---
207365

208366
## Checking Authentication Status
@@ -252,35 +410,57 @@ fi
252410

253411
## Logging Out
254412

413+
By default, `logout` removes the **active** account's tokens and drops that
414+
profile from the registry; other stored profiles are untouched, so a stored
415+
secondary profile becomes the candidate for next selection.
416+
255417
```bash
418+
# Remove the active Anthropic profile
256419
claurst auth logout
420+
421+
# Remove the active Codex profile
422+
claurst codex logout
423+
424+
# Or from inside the REPL
425+
/logout
426+
/logout --codex
257427
```
258428

259-
This removes `~/.claurst/oauth_tokens.json`. API keys set via environment
260-
variables or `settings.json` are not affected; you must remove those manually.
429+
To purge every stored profile for a provider (and clear any API key in
430+
`settings.json`):
261431

262-
To fully clear all stored credentials:
432+
```
433+
/logout --all # Anthropic
434+
/logout --codex --all # Codex
435+
```
436+
437+
API keys set via environment variables are not affected by `logout`; remove
438+
them from your shell profile manually.
439+
440+
To delete a specific stored profile without making it active first:
263441

264442
```bash
265-
claurst auth logout
266-
rm -f ~/.claurst/auth.json
443+
claurst auth remove work
444+
claurst codex remove personal
267445
```
268446

269447
---
270448

271449
## Token Refresh
272450

273-
When Claurst loads OAuth tokens from `~/.claurst/oauth_tokens.json` and the
274-
access token is expired, it automatically attempts a silent refresh:
451+
When Claurst loads OAuth tokens for the active profile and the access token
452+
is expired, it automatically attempts a silent refresh:
275453

276-
1. A `POST` request is sent to the token endpoint with the stored refresh token.
277-
2. If successful, the new access token (and optionally a new refresh token) is
278-
written back to `~/.claurst/oauth_tokens.json`.
454+
1. A `POST` request is sent to the provider's token endpoint with the stored
455+
refresh token.
456+
2. If successful, the new access token (and optionally a new refresh token)
457+
is written back to the same per-profile token file.
279458
3. The refreshed token is used for the current session.
280459

281460
If the refresh fails (network error, expired refresh token, revoked grant),
282461
Claurst falls back to any configured API key. If no API key is available,
283-
authentication fails and you must run `claurst auth login` again.
462+
authentication fails and you must run `claurst auth login` (optionally with
463+
`--label <name>` to reuse a profile id) again.
284464

285465
---
286466

@@ -393,10 +573,15 @@ claurst --provider llamacpp --api-base http://localhost:8080
393573
- Restrict permissions on `~/.claurst/` to your user only:
394574
```bash
395575
chmod 700 ~/.claurst
396-
chmod 600 ~/.claurst/oauth_tokens.json
576+
chmod 700 ~/.claurst/accounts
577+
chmod 600 ~/.claurst/accounts.json
397578
chmod 600 ~/.claurst/auth.json
398579
chmod 600 ~/.claurst/settings.json
580+
find ~/.claurst/accounts -type f -name '*tokens.json' -exec chmod 600 {} +
399581
```
582+
Claurst already sets `0600` on `accounts.json` automatically on Unix; the
583+
command above is the belt-and-braces version that also covers the per-
584+
profile token files.
400585
- Do not commit `~/.claurst/` to version control.
401586
- Add `.claurst/` to your project's `.gitignore` to prevent accidentally
402587
committing project-level settings files that may contain keys.

0 commit comments

Comments
 (0)