claude_profile 1.2.0

Claude Code account credential management and token status
Documentation
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
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
# Commands :: Account

Account management commands: list, save, use, delete, limits, and relogin.

---

### Command :: 3. `.accounts`

List all saved accounts (identity view) or run per-account mutations (`assign::1`, `unclaim::1`). Without `name::`: shows all accounts; with `name::EMAIL`: shows that account only. Column visibility controlled via `cols::` (modifies from default identity set: Account, Owner, Active, Current, Sub, Tier, Expires, Email). When data-source params are active (`refresh::1`, `touch::1`), fetches live quota using the same pipeline as `.usage` — defaults to local-only read with no HTTP fetch.

-- **Parameters:** [`name::`](../param/001_name.md) *(optional)*, [`cols::`](../param/033_cols.md), [`assign::`](../param/057_assign.md), [`unclaim::`](../param/056_unclaim.md), [`force::`](../param/058_force.md), [`for::`](../param/053_for.md), [`dry::`](../param/004_dry.md), [`set_model::`](../param/054_set_model.md), [`refresh::`](../param/019_refresh.md), [`touch::`](../param/034_touch.md), [`imodel::`](../param/035_imodel.md), [`effort::`](../param/036_effort.md), [`sort::`](../param/025_sort.md), [`desc::`](../param/026_desc.md), [`prefer::`](../param/027_prefer.md), [`count::`](../param/037_count.md), [`offset::`](../param/038_offset.md), [`only_active::`](../param/039_only_active.md), [`only_next::`](../param/040_only_next.md), [`min_5h::`](../param/041_min_5h.md), [`min_7d::`](../param/042_min_7d.md), [`only_valid::`](../param/043_only_valid.md), [`exclude_exhausted::`](../param/044_exclude_exhausted.md), [`get::`](../param/045_get.md), [`abs::`](../param/046_abs.md), [`no_color::`](../param/047_no_color.md), [`live::`](../param/020_live.md), [`interval::`](../param/021_interval.md), [`jitter::`](../param/022_jitter.md), [`format::`](../param/002_format.md), [`trace::`](../param/023_trace.md)
-- **Exit:** 0 (success) | 1 (usage: invalid `name::` chars, legacy field-toggle param used, unknown `cols::` id, `for::` missing `@`, G8 ownership violation on `unclaim::1`) | 2 (runtime: account not found or credential store unreadable)

**Syntax:**

```bash
clp .accounts
clp .accounts name::alice@acme.com
clp .accounts alice@acme.com                         # positional: bare name at any position
clp .accounts car                                     # prefix: first saved account starting with "car"
clp .accounts cols::+host,-tier                      # add host column, remove tier column
clp .accounts cols::-owner                            # hide owner column
clp .accounts unclaim::1 name::alice@acme.com        # clear ownership (G8 gate)
clp .accounts unclaim::1 name::alice@acme.com force::1  # bypass G8
clp .accounts assign::1 name::alice@acme.com         # write active-account marker
clp .accounts assign::1                               # emit live usage block (no name::)
clp .accounts refresh::1                              # fetch live quota (HTTP)
clp .accounts refresh::1 sort::renew                 # sorted by renewal, live data
clp .accounts format::json
clp .accounts format::table
```

| Parameter | Type | Default | Purpose |
|-----------|------|---------|---------|
| `name::` | [`AccountName`](../type/001_account_name.md) | *(omit to list all)* | Show or operate on a single named account; prefix resolution supported |
| `cols::` | `string` | `""` | Column visibility modifiers: comma-separated `+col_id` / `-col_id` relative to identity default set (`account`, `owner`, `active`, `current`, `sub`, `tier`, `expires`, `email`); opt-in: `display_name`, `host`, `role`, `billing`, `model`, `uuid`, `capabilities`, `org_uuid`, `org_name` |
| `unclaim::` | `bool` | `0` | Release ownership of `name::` account; writes `owner: ""` to `{name}.json`; G8 gate runs before write even when `dry::1`; when `name::` absent, batch-unclaims filtered set |
| `assign::` | `bool` | `0` | Write per-machine active-account marker for `name::` account; when `name::` absent, emits live usage block |
| `force::` | `bool` | `0` | Bypass G8 ownership gate on `unclaim::1`; allows any identity to release ownership; ignored without `unclaim::1` |
| `for::` | `string` (`USER@MACHINE`) | current `$USER@hostname` | Target machine identity for `assign::1`; split on first `@`; both components required when provided |
| `dry::` | `bool` | `0` | Preview mutations without writing; G8 gate still runs on `unclaim::1 dry::1` |
| `set_model::` | `enum` | *(omit)* | Write session model to `settings.json`: `opus`, `sonnet`, `haiku`, `default` |
| `refresh::` | `bool` | **`0`** | Attempt OAuth token refresh via subprocess (default `0`; differs from `.usage` default of `1`) |
| `touch::` | `bool` | **`0`** | Activate idle 5h session windows via subprocess (default `0`; differs from `.usage` default of `1`) |
| `imodel::` | `enum` | `auto` | Subprocess model: `auto`, `sonnet`, `opus`, `haiku`, `keep` |
| `effort::` | `enum` | `auto` | Subprocess effort: `auto`, `low`, `normal`, `high`, `max` |
| `sort::` | `enum` | **`name`** | Row ordering and footer recommendation: `name` (default for `.accounts`), `renew`, `renews` |
| `desc::` | `bool` | `0` | Sort direction: 0 = ascending, 1 = descending |
| `prefer::` | `enum` | `any` | Weekly quota column for sort heuristics: `any`, `opus`, `sonnet` |
| `count::` | `u64` | `0` | Max rows to display (0 = all) |
| `offset::` | `u64` | `0` | Skip first N rows |
| `only_active::` | `bool` | `0` | Show only the per-machine active account |
| `only_next::` | `bool` | `0` | Show only the recommended next account |
| `min_5h::` | `f64` | `0` | Hide accounts with `5h Left` below this percentage |
| `min_7d::` | `f64` | `0` | Hide accounts with `7d Left` below this percentage |
| `only_valid::` | `bool` | `0` | Hide 🔴 (invalid/expired) rows |
| `exclude_exhausted::` | `bool` | `0` | Hide 🟡 and 🔴 rows |
| `get::` | `string` | `""` | Extract bare field value for first row |
| `abs::` | `bool` | `0` | Show absolute token counts instead of percentages |
| `no_color::` | `bool` | `0` | Strip emoji and ANSI colors |
| `live::` | `bool` | `0` | Continuous monitor mode |
| `interval::` | `u64` | `30` | Seconds between live refresh cycles (≥ 30) |
| `jitter::` | `u64` | `0` | Max random seconds added to interval |
| `format::` | [`OutputFormat`](../type/002_output_format.md) | `text` | Output format: `text`, `json`, `table` |
| `trace::` | `bool` | `0` | Print `[trace]` lines to stderr |

**Algorithm (5 steps):**
1. Resolve credential store; graceful degradation on unavailability (returns `(no accounts configured)` with exit 0)
2. List all accounts; resolve and filter by `name::` when provided
3. **Mutation dispatch:** `assign::1` → write per-machine marker or emit usage block (when `name::` absent); `unclaim::1` → evaluate G8 gate then write `owner: ""` or batch-unclaim filtered set; legacy field-toggle param present → exit 1 with `cols::` migration hint
4. Parse `cols::` modifiers; read `owner` from `{name}.json` per account (when `cols.owner`); detect current account via token comparison (when `cols.current`)
5. Apply sort/filter; render in `format::`

**Examples:**

```bash
clp .accounts
# alice@acme.com
#   Owner:   user1@w003
#   Active:  yes
#   Current: no
#   Sub:     max
#   Tier:    default_claude_max_20x
#   Expires: in 2h 11m
#   Email:   alice@acme.com

clp .accounts format::table
# Accounts
#
#    Account         Owner        Active  Sub   Tier                     Expires
# -  --------------  -----------  ------  ----  -----------------------  ---------
# ✓  alice@acme.com  user1@w003   yes     max   default_claude_max_20x   in 2h 11m

clp .accounts unclaim::1 name::alice@acme.com
# unclaimed alice@acme.com

clp .accounts assign::1 name::alice@acme.com for::bob@laptop
# Assigned alice@acme.com for bob@laptop  →  _active_laptop_bob
```

**Notes:**
- `cols::` replaces the 15 former field-toggle params (`active::`, `current::`, `sub::`, `tier::`, `expires::`, `email::`, `display_name::`, `host::`, `role::`, `billing::`, `model::`, `uuid::`, `capabilities::`, `org_uuid::`, `org_name::`). Using any exits 1 with a `cols::` migration hint.
- Owner column is in the identity default set — shows `USER@MACHINE` when owned, `—` when unowned. Hide with `cols::-owner`.
- `format::json` always includes all fields regardless of `cols::`.
- `format::table` columns: flag, Account, Owner (when enabled), Active, Sub, Tier, Expires.
- Data-source params (`refresh::`, `touch::`) default to `0` — `.accounts` is local-only by default; set to `1` to activate the same live pipeline as `.usage`.
- `assign::` and `unclaim::` are also available on `.usage` (same behavior). See [Feature 037](../../feature/037_accounts_usage_param_unification.md).
- G8 ownership gate evaluates BEFORE `dry::1` on `unclaim::1` — a non-owner gets exit 1 even in dry-run mode.
- `current::` field (in text mode) shows `Current: yes` for the account whose `accessToken` matches `~/.claude/.credentials.json`. See [feature/016_current_account_awareness.md](../../feature/016_current_account_awareness.md).

### Referenced Features

| # | Feature | Role |
|---|---------|------|
| 1 | [Account List](../../feature/003_account_list.md) | Account enumeration and per-account block rendering |
| 2 | [Rich Account Metadata](../../feature/014_rich_account_metadata.md) | Extended metadata fields |
| 3 | [Name Shortcut Syntax](../../feature/015_name_shortcut_syntax.md) | Prefix and positional `name::` resolution |
| 4 | [Current Account Awareness](../../feature/016_current_account_awareness.md) | Token-based current account detection (`cols.current`) |
| 5 | [Extended Snapshot Fields](../../feature/021_extended_snapshot_fields.md) | Opt-in snapshot fields via `cols::+uuid` / `+capabilities` |
| 6 | [Org Identity Snapshot](../../feature/022_org_identity_snapshot.md) | Org fields via `cols::+org_uuid` / `+org_name` |
| 7 | [Host Metadata](../../feature/029_account_host_metadata.md) | `cols::+host` / `+role` from saved snapshot |
| 8 | [Account Ownership](../../feature/036_account_ownership.md) | G8 gate for `unclaim::1`; `force::` bypass |
| 9 | [Accounts/Usage Param Unification](../../feature/037_accounts_usage_param_unification.md) | 32-param unified interface; `cols::` replacing field toggles; mutation params |

### Referenced User Stories

| # | User Story | Persona |
|---|------------|---------|
| 1 | [Account Rotation](../user_story/001_account_rotation.md) | Inspect accounts before and after rotation |
| 2 | [Account Onboarding](../user_story/002_onboarding.md) | Verify saved account metadata during onboarding |

---

### Command :: 4. `.account.save`

Copies `~/.claude/.credentials.json` to `{credential_store}/{name}.credentials.json` and merges identity, model, roles, and profile metadata into the unified `{name}.json`. Machine-global state (`commands.*`, `mcpServers`, `projects`) is not captured. Use this to preserve account identity before switching.

-- **Parameters:** [`name::`](../param/001_name.md), [`dry::`](../param/004_dry.md), [`host::`](../param/048_host.md), [`role::`](../param/052_role.md), [`trace::`](../param/023_trace.md)
-- **Exit:** 0 (success) | 1 (usage: invalid name or no active account set) | 2 (runtime: credentials unreadable)

**Syntax:**

```bash
clp .account.save                                      # infer name from oauthAccount.emailAddress in ~/.claude.json (falls back to _active_{hostname}_{user})
clp .account.save name::alice@acme.com                # explicit name
clp .account.save name::alice@acme.com dry::1
clp .account.save host::workstation                   # store host label in {name}.json
clp .account.save role::work                          # store role label in {name}.json
clp .account.save host::workstation role::personal    # both metadata fields
```

| Parameter | Type | Default | Purpose |
|-----------|------|---------|---------|
| `name::` | [`AccountName`](../type/001_account_name.md) | `auto` (inferred from `oauthAccount.emailAddress` in `~/.claude.json`; falls back to per-machine active marker — see [Feature 025](../../feature/025_per_machine_active_marker.md); exits 1 if neither source present) | Account email to save as |
| `dry::` | `bool` | `0` | Preview action without executing |
| `host::` | `string` | `""` (auto-detected hostname) | Machine/host label stored in `{name}.json` (see [feature/029](../../feature/029_account_host_metadata.md)) |
| `role::` | `string` | `""` | User-defined role label stored in `{name}.json` (see [param 052](../param/052_role.md)) |
| `trace::` | `bool` | `0` | Print `[trace]` lines to stderr for credential read and file write steps |

**Algorithm (5 steps):**
1. Resolve `name::`: read `oauthAccount.emailAddress` from `~/.claude.json`; fall back to `_active_{hostname}_{user}` marker; exit 1 if neither present
2. `(when dry::0)` Copy `~/.claude/.credentials.json` → `{name}.credentials.json` (atomic write)
3. `(when dry::0)` Read `~/.claude.json` + `~/.claude/settings.json` + call `GET /api/oauth/claude_cli/roles` (best-effort); merge all into unified `{name}.json` (preserves `_renewal_at` and other keys)
4. `(when dry::0)` Write host and role into `{name}.json`: `host::` (auto-captured `$USER@$HOSTNAME` when omitted); `role::` via read-merge; `owner` field preserved unchanged via read-merge — `account_save_routine()` passes `owner: None` to `save()` (ownership-neutral)
5. `(when dry::0)` Write `_active_{hostname}_{user}` = `{name}` (per-machine active marker)

**Examples:**

```bash
clp .account.save
# saved current credentials as 'alice@acme.com'   (inferred from oauthAccount.emailAddress)

clp .account.save name::alice@acme.com dry::1
# [dry-run] would save current credentials as 'alice@acme.com'

clp .account.save host::workstation role::work
# saved current credentials as 'alice@acme.com'   (host='workstation', role='work')
```

**Notes:**
- Also writes `{credential_store}/_active_{hostname}_{user}` = `{name}` on every successful save (per-machine active marker via `active_marker_filename()`).
- Also calls endpoint 005 (`GET /api/oauth/claude_cli/roles`) and merges result into `{name}.json` (best-effort: failure is silently skipped).
- **Metadata refresh:** Re-running `.account.save` for an existing name refreshes the unified `{name}.json` and re-fetches endpoint 005 — this is the canonical way to refresh cached org identity without re-login. `{name}.json` is updated via read-merge (not full overwrite): the `oauthAccount` key is replaced but all other keys (e.g., `_renewal_at` set by `.account.renewal`) are preserved.
- **Ownership-neutral save:** `.account.save` never writes to the `owner` field — `account_save_routine()` passes `owner: None` to `save()`, preserving any existing `owner` via read-merge. Background refresh callers also pass `owner: None`. To release ownership, use `clp .accounts unclaim::1 name::EMAIL`. See [Feature 036](../../feature/036_account_ownership.md).

### Referenced Features

| # | Feature | Role |
|---|---------|------|
| 1 | [Store Init](../../feature/001_store_init.md) | Credential store initialization before save |
| 2 | [Save Account](../../feature/002_account_save.md) | Core save algorithm and file layout |
| 3 | [Persistent Storage](../../feature/010_persistent_storage.md) | Unified `{name}.json` merge semantics |
| 4 | [Per-Machine Active Marker](../../feature/025_per_machine_active_marker.md) | `_active_{hostname}_{user}` marker written on save |
| 5 | [Host Metadata](../../feature/029_account_host_metadata.md) | `host::` and `role::` metadata stored in `{name}.json` |
| 6 | [Account Ownership](../../feature/036_account_ownership.md) | Ownership model — `.account.save` is ownership-neutral (passes `owner: None`); `.accounts unclaim::1` clears ownership; `.account.assign` is marker-only |

### Referenced User Stories

| # | User Story | Persona |
|---|------------|---------|
| 1 | [Account Onboarding](../user_story/002_onboarding.md) | Saving credentials during initial account setup |

---

### Command :: 5. `.account.use`

Atomically overwrites `~/.claude/.credentials.json` with the named account's credentials (write-then-rename), updates the active marker (`_active_{hostname}_{user}`), and best-effort patches `~/.claude.json["oauthAccount"]` from the saved snapshot — preserving all machine-global keys untouched. When `touch::1` (default), fetches quota for the target account and spawns an isolated subprocess to activate its idle 5h session window if `five_hour.resets_at` is absent.

-- **Parameters:** [`name::`](../param/001_name.md) **(required)**, [`dry::`](../param/004_dry.md), [`touch::`](../param/034_touch.md), [`refresh::`](../param/019_refresh.md), [`imodel::`](../param/035_imodel.md), [`effort::`](../param/036_effort.md), [`trace::`](../param/023_trace.md), [`set_model::`](../param/054_set_model.md)
-- **Exit:** 0 (success) | 1 (usage: invalid name or invalid `imodel::`/`effort::`/`trace::`/`set_model::` value) | 2 (runtime: account not found or HOME unset) | 3 (account credentials expired — `touch::1` + fetch failed + `expiresAt` in the past, AND refresh failed or `refresh::0`)

**Syntax:**

```bash
clp .account.use name::alice@home.com
clp .account.use alice@home.com               # positional: bare name at any position
clp .account.use dry::1 alice@home.com        # reversed: arg order does not matter
clp .account.use car                           # prefix: first saved account starting with "car"
clp .account.use name::alice@home.com dry::1
clp .account.use name::alice@home.com touch::0
clp .account.use name::alice@home.com refresh::0
clp .account.use name::alice@home.com imodel::opus effort::max
clp .account.use name::alice@home.com trace::1
clp .account.use name::alice@home.com set_model::opus
clp .account.use name::alice@home.com set_model::default
```

| Parameter | Type | Default | Purpose |
|-----------|------|---------|---------|
| `name::` | [`AccountName`](../type/001_account_name.md) | **(required)** | Account email to switch to |
| `dry::` | `bool` | `0` | Preview action without executing |
| `touch::` | `bool` | `1` | Activate idle 5h session window via subprocess after switch |
| `refresh::` | `bool` | `1` | Attempt OAuth token refresh when locally expired before refusing with exit 3 |
| `imodel::` | `enum` | `auto` | Model for post-switch subprocess: `auto` (haiku — sufficient for keep-alive), `sonnet`, `opus`, `haiku`, `keep` |
| `effort::` | `enum` | `auto` | Effort for post-switch subprocess: `auto` (`low` for any model; no flag for haiku/keep), `low`, `normal`, `high`, `max` |
| `trace::` | `bool` | `0` | Print `[trace] account.use` lines to stderr: credential read, quota fetch, idle check, model resolution, subprocess dispatch |
| `set_model::` | `enum` | *(omit)* | Explicitly write session model to `settings.json`: `opus` (`claude-opus-4-6`), `sonnet` (`claude-sonnet-4-6`), `haiku` (`claude-haiku-4-5-20251001`), `default` (removes override); takes precedence over automatic `apply_model_override()` |

**Algorithm (7 steps):**
1. Resolve `name::` via `AccountSelector`; load `{name}.credentials.json`
2. `(when dry::0)` Atomically overwrite `~/.claude/.credentials.json` via write-then-rename
3. `(when dry::0)` Write `_active_{hostname}_{user}` = `{name}` (active marker)
4. `(when dry::0)` Best-effort patch `~/.claude.json["oauthAccount"]` from saved snapshot (preserves machine-global keys)
5. `(when touch::1)` Fetch quota via `GET /api/oauth/usage`; `(when refresh::1 + locally expired)` call `refresh_account_token()` first; evaluate idle: `five_hour.resets_at` absent → idle
6. `(when touch::1 + idle)` Resolve model+effort via `resolve_model()`/`resolve_effort()`; spawn isolated subprocess via `run_isolated()`
7. Session-model override: `(when set_model:: provided)` write requested model via `set_session_model()`; `(otherwise, when target was already active + valid quota)` write resolved model via `apply_model_override()`

**Examples:**

```bash
clp .account.use name::alice@home.com
# switched to 'alice@home.com'   (idle account: subprocess spawned to activate 5h session)

clp .account.use name::alice@home.com touch::0
# switched to 'alice@home.com'   (pure credential rotation — no subprocess)

clp .account.use name::alice@home.com dry::1
# [dry-run] would switch to 'alice@home.com'

clp .account.use name::alice@home.com trace::1
# [trace] account.use  alice@home.com  reading /...alice@home.com.credentials.json
# [trace] account.use  alice@home.com  reading: OK
# [trace] account.use  alice@home.com  quota fetch: OK
# [trace] account.use  alice@home.com  subprocess: scheduled (idle check removed)
# [trace] account.use  alice@home.com  model: claude-opus-4-6  effort: low
# [trace] account.use  alice@home.com  subprocess: spawned
# switched to 'alice@home.com'
```

**Notes:**
- `touch::1` (default): fetches quota for the target account; when fetch succeeds, always spawns `run_isolated(["--print", "."])` with resolved model/effort (subprocess is idempotent; Fix(BUG-285): idle check removed). Quota fetch failure checks `expiresAt` — if locally expired and `refresh::1` (default), attempts token refresh and re-probes touch context on success; exits 3 if refresh fails. If locally expired and `refresh::0`, exits 3 immediately. If `expiresAt` is absent or not yet expired, skips touch silently and the switch completes.
- `touch::0`: pure credential rotation — no quota fetch, no subprocess, no expiry check. Pre-Feature-027 behavior.
- `imodel::` and `effort::` follow the same resolution logic as `.usage` (Feature 026): `imodel::auto` always selects Haiku (sufficient for keep-alive pings); `resolve_effort()` maps Haiku and keep → no `--effort` flag, other models → `low`. See [feature/026](../../feature/026_subprocess_model_effort.md).
- `set_model::`: when provided, `set_session_model()` writes the requested model to `settings.json` last (after any `apply_post_switch_touch()` or `apply_model_override()`), ensuring it takes precedence. `default` removes the `model` key entirely.
- `trace::1` only produces output when `touch::1`; with `touch::0` there are no fetch operations to trace.
- See [feature/027_account_use_post_switch_touch.md](../../feature/027_account_use_post_switch_touch.md) for full execution sequence and acceptance criteria.

### Referenced Features

| # | Feature | Role |
|---|---------|------|
| 1 | [Switch Account](../../feature/004_account_use.md) | Atomic credential rotation and active marker update |
| 2 | [Token Refresh](../../feature/017_token_refresh.md) | Pre-switch refresh on locally-expired token |
| 3 | [Session Touch](../../feature/024_session_touch.md) | Idle 5h window activation after switch |
| 4 | [Subprocess Model/Effort](../../feature/026_subprocess_model_effort.md) | Model and effort selection for post-switch subprocess |
| 5 | [Post-Switch Touch](../../feature/027_account_use_post_switch_touch.md) | Full execution sequence with touch and model override |

### Referenced User Stories

| # | User Story | Persona |
|---|------------|---------|
| 1 | [Account Rotation](../user_story/001_account_rotation.md) | Primary command for switching to a named account |

---

### Command :: 6. `.account.delete`

Removes `{credential_store}/{name}.credentials.json` and `{name}.json` from the credential store, plus any legacy satellite files from pre-consolidation layout.

-- **Parameters:** [`name::`](../param/001_name.md) **(required)**, [`dry::`](../param/004_dry.md), [`trace::`](../param/023_trace.md)
-- **Exit:** 0 (success) | 1 (usage: invalid name) | 2 (runtime: account not found)

**Syntax:**

```bash
clp .account.delete name::alice@oldco.com
clp .account.delete alice@oldco.com          # positional: bare name at any position
clp .account.delete dry::1 alice@oldco.com   # reversed: arg order does not matter
clp .account.delete car                      # prefix
clp .account.delete name::alice@oldco.com dry::1
```

| Parameter | Type | Default | Purpose |
|-----------|------|---------|---------|
| `name::` | [`AccountName`](../type/001_account_name.md) | **(required)** | Account email to delete |
| `dry::` | `bool` | `0` | Preview action without executing |
| `trace::` | `bool` | `0` | Print `[trace]` lines to stderr for each file removal step |

**Algorithm (4 steps):**
1. Resolve `name::` via `AccountSelector`; validate account exists in credential store
2. `(when dry::0)` Delete `{name}.credentials.json`
3. `(when dry::0)` Best-effort delete `{name}.json` + legacy files (`.claude.json`, `.settings.json`, `.roles.json`, `.profile.json`; skip missing)
4. `(when dry::0 + deleted account = active)` Delete `_active_{hostname}_{user}` marker

**Examples:**

```bash
clp .account.delete name::alice@oldco.com
# deleted account 'alice@oldco.com'

clp .account.delete name::alice@oldco.com dry::1
# [dry-run] would delete account 'alice@oldco.com'
```

**Notes:**
- Metadata file (`{name}.json`) and legacy satellite files are removed best-effort: missing files are silently skipped.
- Deleting the active account also removes the active marker (`_active_{hostname}_{user}`).

### Referenced Features

| # | Feature | Role |
|---|---------|------|
| 1 | [Delete Account](../../feature/005_account_delete.md) | File removal sequence and legacy satellite cleanup |

### Referenced User Stories

| # | User Story | Persona |
|---|------------|---------|
| 1 | [Account Onboarding](../user_story/002_onboarding.md) | Removing stale accounts during account management |

---

### Command :: 11. `.account.limits`

Show rate-limit utilization for the active or named account. Displays session (5h) usage, weekly all-model (7d) usage, and rate-limit status with percentage consumed and reset times.

-- **Parameters:** [`name::`](../param/001_name.md) *(optional)*, [`format::`](../param/002_format.md), [`trace::`](../param/023_trace.md)
-- **Exit:** 0 (success) | 1 (usage: invalid `name::` chars) | 2 (runtime: account not found, data unavailable, HOME unset)

**Syntax:**

```bash
clp .account.limits
clp .account.limits name::alice@acme.com
clp .account.limits alice@acme.com            # positional: bare name at any position
clp .account.limits format::json alice@acme.com  # reversed: arg order does not matter
clp .account.limits car                   # prefix
clp .account.limits format::json
```

| Parameter | Type | Default | Purpose |
|-----------|------|---------|---------|
| `name::` | [`AccountName`](../type/001_account_name.md) | *(omit for active)* | Query a named account instead of the active account |
| `format::` | [`OutputFormat`](../type/002_output_format.md) | `text` | Output format |
| `trace::` | `bool` | `0` | Print `[trace]` lines to stderr for credential store read and API call |

**Algorithm (3 steps):**
1. Resolve `name::` (omit → active account from `_active_{hostname}_{user}` marker); load credentials
2. Fetch rate-limit headers via `fetch_rate_limits()` (`anthropic-ratelimit-unified-*` response headers)
3. Render session (5h), weekly all-model (7d), and weekly sonnet utilization in requested `format::`

**Examples:**

```bash
clp .account.limits
# Session (5h):   62%  resets in 1h 48m
# Weekly (all):   41%  resets in 3d 12h
# Weekly (sonnet): 38%  resets in 3d 12h

clp .account.limits format::json
# {"session_pct":62,"session_reset_secs":6480,"weekly_all_pct":41,"weekly_all_reset_secs":302400,"weekly_sonnet_pct":38,"weekly_sonnet_reset_secs":302400}
```

**Notes:**
- Data source: `anthropic-ratelimit-unified-*` response headers; transport: `claude_quota::fetch_rate_limits()`. See [feature/013_account_limits.md](../../feature/013_account_limits.md).

### Referenced Features

| # | Feature | Role |
|---|---------|------|
| 1 | [Account Limits](../../feature/013_account_limits.md) | Rate-limit header parsing and utilization rendering |

### Referenced User Stories

| # | User Story | Persona |
|---|------------|---------|
| 1 | [Multi-Account Quota Monitoring](../user_story/003_quota_monitoring.md) | Per-account rate-limit utilization check |

---

### Command :: 12. `.account.relogin`

Force browser-based re-authentication for a named account whose `refreshToken` is expired or revoked. This is the recovery path when `refresh::1` silently fails (trace shows `run_isolated: OK credentials=None` — Claude starts but performs no OAuth refresh because the refresh token itself is dead).

-- **Parameters:** [`name::`](../param/001_name.md) *(optional, defaults to active)*, [`dry::`](../param/004_dry.md), [`trace::`](../param/023_trace.md)
-- **Exit:** 0 (success: credentials refreshed and saved) | 1 (usage: invalid name value) | 2 (runtime: name omitted and no active account; account not found; or Claude spawn failed) | 3 (timeout or login abandoned: claude exited without updating credentials)

**Syntax:**

```bash
clp .account.relogin                   # default: active account
clp .account.relogin name::carol@example.com
clp .account.relogin carol@example.com          # positional: bare name at any position
clp .account.relogin dry::1 carol@example.com   # reversed: arg order does not matter
clp .account.relogin car               # prefix
clp .account.relogin name::carol@example.com dry::1
clp .account.relogin dry::1            # dry-run for active account
```

| Parameter | Type | Default | Purpose |
|-----------|------|---------|---------|
| `name::` | [`AccountName`](../type/001_account_name.md) | *(active account)* | Account to re-authenticate; omit to use the currently active account |
| `dry::` | `bool` | `0` | Preview the steps without executing |
| `trace::` | `bool` | `0` | Print `[trace]` lines to stderr for each step: store read, switch, spawn, credential change, save, restore |

**Algorithm (6 steps):**
1. Resolve `name::` via [`AccountSelector`](../type/004_account_selector.md) → validate account exists in credential store
2. Snapshot the current active account name (for restoration after login)
3. `switch_account(name)` — makes the named account active in `~/.claude/`
4. Spawn `claude` with inherited TTY (stdin/stdout/stderr connected — NOT isolated subprocess) — Claude detects empty or invalid credentials and opens the browser login page
5. Wait for `claude` to exit; if `~/.claude/.credentials.json` changed → `account::save(name)` propagates fresh credentials to credential store
6. `switch_account(original_active)` — restore the prior active account

**Examples:**

```bash
clp .account.relogin dry::1
# [dry-run] would re-authenticate 'alice@example.com' via browser login

clp .account.relogin name::carol@example.com
# re-authenticated 'carol@example.com' — credentials saved

clp .account.relogin name::carol@example.com dry::1
# [dry-run] would re-authenticate 'carol@example.com' via browser login
```

**Notes:**
- Requires a TTY — `clp .account.relogin` in a piped non-TTY context will fail at step 4 (Claude cannot open a browser or display the login prompt).
- The `claude` subprocess runs with the full inherited environment; no credential isolation (contrast with `refresh::1` which uses an isolated subprocess).
- If `claude` exits without updating `~/.claude/.credentials.json`, the command exits 3. The active account is still restored (step 6 runs regardless of outcome).
- Use this when `clp .usage refresh::1 trace::1` shows `run_isolated: OK credentials=None` for an account — that trace indicates a dead refresh token requiring full browser re-auth.

### Referenced Features

| # | Feature | Role |
|---|---------|------|
| 1 | [Auto Rotate](../../feature/008_auto_rotate.md) | Relogin as recovery path for dead refresh tokens |

### Referenced User Stories

| # | User Story | Persona |
|---|------------|---------|
| 1 | [Account Onboarding](../user_story/002_onboarding.md) | Re-authenticating an account with expired refresh token |

---

### Command :: 13. `.account.rotate` *(deprecated — Feature 038)*

**DEPRECATED** — hidden redirector; always exits 1. Use `.usage rotate::1` instead.

```bash
clp .usage rotate::1
clp .usage rotate::1 sort::renews
clp .usage rotate::1 dry::1
```

See [feature/038_usage_strategy_rotate.md](../../feature/038_usage_strategy_rotate.md) for full behavioral specification.

---

### Command :: 14. `.account.renewal`

Set, preview, or clear the billing renewal timestamp override (`_renewal_at`) stored in `{name}.json`. When set, the `.usage` `~Renews` column shows an exact duration (`in Xh Ym`) instead of the estimated `~`-prefixed value derived from `org_created_at`. Supports single account, comma-separated list, or `name::all` to update every saved account in one operation.

-- **Parameters:** [`name::`](../param/001_name.md) **(required)**, [`at::`](../param/049_at.md), [`from_now::`](../param/050_from_now.md), [`clear::`](../param/051_clear.md), [`dry::`](../param/004_dry.md), [`trace::`](../param/023_trace.md)
-- **Exit:** 0 (success) | 1 (usage: no operation provided, conflicting params, or invalid format) | 2 (runtime: account not found or credential store unreadable)

**Syntax:**

```bash
clp .account.renewal name::alice@acme.com at::2026-06-29T21:00:00Z
clp .account.renewal name::alice@acme.com from_now::+1h30m
clp .account.renewal name::alice@acme.com from_now::-30m
clp .account.renewal name::alice@acme.com clear::1
clp .account.renewal name::all from_now::+0m
clp .account.renewal name::alice@acme.com,bob@acme.com at::2026-06-29T21:00:00Z
clp .account.renewal name::alice@acme.com at::2026-06-29T21:00:00Z dry::1
```

| Parameter | Type | Default | Purpose |
|-----------|------|---------|---------|
| `name::` | [`AccountName`](../type/001_account_name.md) or `all` or comma-list | **(required)** | Target account(s): single email/prefix, comma-separated list, or `all` for every saved account |
| `at::` | `string` | *(omit)* | Absolute ISO-8601 UTC renewal timestamp (e.g., `2026-06-29T21:00:00Z`); mutually exclusive with `from_now::` and `clear::` |
| `from_now::` | `string` | *(omit)* | Signed duration delta from now (e.g., `+3h30m`, `-30m`, `+0m`); mutually exclusive with `at::` and `clear::` |
| `clear::` | `bool` | `0` | Remove `_renewal_at` from `{name}.json`; mutually exclusive with `at::` and `from_now::` |
| `dry::` | `bool` | `0` | Preview operation without writing files |
| `trace::` | `bool` | `0` | Print `[trace]` lines to stderr for each file read and write step |

**Algorithm (4 steps):**
1. Resolve target account list from `name::`: single email/prefix, comma-separated list, or `all` (every saved account)
2. For each target: read `{name}.json`
3. Compute new `_renewal_at` value from `at::` (absolute ISO-8601), `from_now::` (signed delta), or `clear::1` (remove key)
4. `(when dry::0)` Write `{name}.json` with updated `_renewal_at` key per account

**Examples:**

```bash
clp .account.renewal name::alice@acme.com at::2026-06-29T21:00:00Z
# renewal set for 'alice@acme.com': 2026-06-29T21:00:00Z  (in 30d 14h)

clp .account.renewal name::all from_now::+0m
# renewal set for 'alice@acme.com': 2026-05-29T18:34:22Z  (now)
# renewal set for 'bob@acme.com':   2026-05-29T18:34:22Z  (now)
# renewal set for 'carol@acme.com': 2026-05-29T18:34:22Z  (now)

clp .account.renewal name::alice@acme.com clear::1
# renewal cleared for 'alice@acme.com'  (~Renews will show estimate from org_created_at)

clp .account.renewal name::alice@acme.com at::2026-06-29T21:00:00Z dry::1
# [dry-run] would set renewal for 'alice@acme.com': 2026-06-29T21:00:00Z  (in 30d 14h)
```

**Notes:**
- `_renewal_at` is stored as a top-level key in `{name}.json` alongside `oauthAccount`. It is preserved when `clp .account.save` re-saves that account (read-merge).
- Past `_renewal_at` values are auto-advanced monthly by `.usage` at render time — no need to re-set after each billing cycle.
- `from_now::+0m` sets the override to the current time, which immediately enters the monthly auto-advance cycle.
- `name::all` targets every account in the credential store at the time of execution.
- See [feature/030_account_renewal_override.md](../../feature/030_account_renewal_override.md) for full semantics, `~Renews` rendering rules, and acceptance criteria.

### Referenced Features

| # | Feature | Role |
|---|---------|------|
| 1 | [Account Renewal Override](../../feature/030_account_renewal_override.md) | `_renewal_at` storage, monthly auto-advance, and `~Renews` rendering |

### Referenced User Stories

| # | User Story | Persona |
|---|------------|---------|
| 1 | [Account Onboarding](../user_story/002_onboarding.md) | Set accurate billing renewal dates during account setup |

---

### Command :: 15. `.account.inspect`

Unified live account diagnostic — identity, subscription, org, and quota utilization for one account. Calls endpoints 002 (`GET /api/oauth/account`), 005 (`GET /api/oauth/claude_cli/roles`), and 001 (`GET /api/oauth/usage`) and renders identity fields (tagged_id, uuid, email, name), ALL membership entries with a selection-priority indicator, capabilities, rate-limit tier, and 5h/7d/Sonnet quota utilization with reset countdowns. Primary use case: diagnosing account state and remaining quota (see BUG-237 / feature 031).

-- **Parameters:** [`name::`](../param/001_name.md), [`refresh::`](../param/019_refresh.md), [`trace::`](../param/023_trace.md), [`format::`](../param/002_format.md)
-- **Exit:** 0 (success) | 1 (usage: invalid param) | 2 (runtime: account not found or credential store unreadable)

**Syntax:**

```bash
clp .account.inspect                    # default: active account
clp .account.inspect name::alice@acme.com
clp .account.inspect alice             # prefix
clp .account.inspect refresh::0        # skip token refresh on expired credentials
clp .account.inspect format::json
clp .account.inspect trace::1          # show [trace] endpoint calls to stderr
```

| Parameter | Type | Default | Purpose |
|-----------|------|---------|---------|
| `name::` | [`AccountName`](../type/001_account_name.md) | *(active account)* | Account to inspect; omit to use the currently active account |
| `refresh::` | `bool` | `1` | Attempt OAuth token refresh via isolated subprocess when `expiresAt` is locally expired, before endpoint calls |
| `trace::` | `bool` | `0` | Print `[trace]` lines to stderr for each endpoint call: URL, HTTP status, field extraction summary |
| `format::` | [`OutputFormat`](../type/002_output_format.md) | `text` | Output format: `text` (default) or `json` |

**Algorithm (5 steps):**
1. Resolve `name::` (omit → active account from `_active_{hostname}_{user}` marker); load credentials
2. `(when refresh::1 + locally expired)` Call `refresh_account_token()` to obtain a fresh token
3. Call endpoint 002 (`GET /api/oauth/account`), endpoint 005 (`GET /api/oauth/claude_cli/roles`), and endpoint 001 (`GET /api/oauth/usage`) — each independently; failure falls back to local snapshots with `(snapshot)` suffix per field (quota fields show `N/A` with no snapshot fallback)
4. Apply membership selection priority: `billing_type=stripe_subscription + claude_max` > `billing_type=stripe_subscription` > `memberships[0]`
5. Render all fields in requested `format::`

**Output (text):**

```
Account:         alice@acme.com
Name:            Alice (Alice)
Email:           alice@acme.com
Status:          🟢 valid (expires in 3h 52m)
Tagged ID:       user_01abc...def
UUID:            aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee

Memberships:     2
  [0]  billing_type=none              has_max=false  capabilities=[chat]
  [1]  billing_type=stripe_subscription  has_max=true   capabilities=[claude_max, chat]  ← selected

Billing:         stripe_subscription
Has Max:         yes
Capabilities:    [claude_max, chat]
Tier:            default_claude_max_20x

Session (5h):    45% consumed, resets in 12m
Weekly (7d):     33% consumed, resets in 1d 5h
Sonnet (7d):     53% consumed, resets in 1d 5h

Org:             alice@acme.com's Organization
Org UUID:        aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee
Org Role:        admin
Workspace UUID:  (none)
Workspace:       (none)
```

**Membership selection priority:**

| Priority | Criteria |
|----------|----------|
| 1 (highest) | `billing_type == "stripe_subscription"` AND capabilities contain `"claude_max"` |
| 2 | `billing_type == "stripe_subscription"` (any capabilities) |
| 3 (fallback) | `memberships[0]` |

The selected membership is marked `← selected` when there are multiple memberships; the `Billing:` and `Has Max:` fields reflect the selected membership.

**Examples:**

```bash
clp .account.inspect
# Account:     alice@acme.com
# Status:      🟢 valid (expires in 3h 52m)
# ...

clp .account.inspect name::i5@wbox.pro
# Account:     i5@wbox.pro
# Memberships: 2
#   [0]  billing_type=none              has_max=false  ...
#   [1]  billing_type=stripe_subscription  has_max=true  ...  ← selected
# Billing:     stripe_subscription

clp .account.inspect format::json | jq '.memberships | length'
# 2
```

**Notes:**
- Endpoints 002, 005, and 001 are called independently. A failure on one endpoint falls back to the local snapshot from `{name}.json` with a `(snapshot)` suffix per field; quota fields (endpoint 001) have no snapshot fallback and are omitted when unavailable.
- `refresh::1` (default) behaves identically to `.usage`'s `refresh::1`: calls `refresh_account_token()` once when `expiresAt` is locally expired; retries endpoint calls with the fresh token.
- See [feature/031_account_inspect.md](../../feature/031_account_inspect.md) for full design, graceful fallback semantics, and all acceptance criteria.

### Referenced Features

| # | Feature | Role |
|---|---------|------|
| 1 | [Account Inspect](../../feature/031_account_inspect.md) | Unified account diagnostic — identity, subscription, org, and quota utilization |

### Referenced User Stories

| # | User Story | Persona |
|---|------------|---------|
| 1 | [Credential Diagnostics](../user_story/005_credential_diagnostics.md) | Live multi-endpoint inspection for subscription diagnosis |

---

### Command :: 16. `.account.assign` *(removed — Feature 037)*

**Fully removed.** Use `.accounts assign::1 name::X` instead.

```bash
clp .accounts assign::1 name::alice@corp.com
clp .accounts assign::1 name::alice@corp.com for::bob@laptop
clp .accounts assign::1                        # live usage block (no name::)
```

---

### Command :: 17. `.account.unclaim` *(removed — Feature 037)*

**Fully removed.** Use `.accounts unclaim::1 name::X` instead.

```bash
clp .accounts unclaim::1 name::alice@acme.com
clp .accounts unclaim::1 name::alice@acme.com dry::1
clp .accounts unclaim::1 name::alice@acme.com force::1  # bypass G8
clp .accounts unclaim::1                                 # batch-unclaim filtered set
```