hatcheck 0.2.0__tar.gz → 0.3.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (40) hide show
  1. {hatcheck-0.2.0 → hatcheck-0.3.0}/Cargo.lock +1 -67
  2. {hatcheck-0.2.0 → hatcheck-0.3.0}/Cargo.toml +1 -1
  3. {hatcheck-0.2.0 → hatcheck-0.3.0}/PKG-INFO +239 -65
  4. {hatcheck-0.2.0 → hatcheck-0.3.0}/README.md +238 -64
  5. {hatcheck-0.2.0 → hatcheck-0.3.0}/crates/hatcheck/Cargo.toml +15 -1
  6. hatcheck-0.3.0/crates/hatcheck/LICENSE +21 -0
  7. hatcheck-0.3.0/crates/hatcheck/README.md +56 -0
  8. {hatcheck-0.2.0 → hatcheck-0.3.0}/crates/hatcheck/src/args.rs +22 -0
  9. {hatcheck-0.2.0 → hatcheck-0.3.0}/crates/hatcheck/src/best.rs +261 -36
  10. {hatcheck-0.2.0 → hatcheck-0.3.0}/crates/hatcheck/src/bind.rs +1 -1
  11. {hatcheck-0.2.0 → hatcheck-0.3.0}/crates/hatcheck/src/cli.rs +89 -16
  12. {hatcheck-0.2.0 → hatcheck-0.3.0}/crates/hatcheck/src/lib.rs +4 -0
  13. {hatcheck-0.2.0 → hatcheck-0.3.0}/crates/hatcheck/src/move_cmd.rs +168 -169
  14. {hatcheck-0.2.0 → hatcheck-0.3.0}/crates/hatcheck/src/ospath.rs +2 -1
  15. hatcheck-0.3.0/crates/hatcheck/src/relocate.rs +815 -0
  16. {hatcheck-0.2.0 → hatcheck-0.3.0}/crates/hatcheck/src/usage.rs +296 -117
  17. {hatcheck-0.2.0 → hatcheck-0.3.0}/LICENSE +0 -0
  18. {hatcheck-0.2.0 → hatcheck-0.3.0}/crates/hatcheck/src/bin/cl.rs +0 -0
  19. {hatcheck-0.2.0 → hatcheck-0.3.0}/crates/hatcheck/src/bin/claude-k.rs +0 -0
  20. {hatcheck-0.2.0 → hatcheck-0.3.0}/crates/hatcheck/src/bin/claude-z.rs +0 -0
  21. {hatcheck-0.2.0 → hatcheck-0.3.0}/crates/hatcheck/src/bin/cx.rs +0 -0
  22. {hatcheck-0.2.0 → hatcheck-0.3.0}/crates/hatcheck/src/bin/hat.rs +0 -0
  23. {hatcheck-0.2.0 → hatcheck-0.3.0}/crates/hatcheck/src/conv.rs +0 -0
  24. {hatcheck-0.2.0 → hatcheck-0.3.0}/crates/hatcheck/src/desktop.rs +0 -0
  25. {hatcheck-0.2.0 → hatcheck-0.3.0}/crates/hatcheck/src/facts.rs +0 -0
  26. {hatcheck-0.2.0 → hatcheck-0.3.0}/crates/hatcheck/src/find.rs +0 -0
  27. {hatcheck-0.2.0 → hatcheck-0.3.0}/crates/hatcheck/src/launch.rs +0 -0
  28. {hatcheck-0.2.0 → hatcheck-0.3.0}/crates/hatcheck/src/main.rs +0 -0
  29. {hatcheck-0.2.0 → hatcheck-0.3.0}/crates/hatcheck/src/open_cmd.rs +0 -0
  30. {hatcheck-0.2.0 → hatcheck-0.3.0}/crates/hatcheck/src/out.rs +0 -0
  31. {hatcheck-0.2.0 → hatcheck-0.3.0}/crates/hatcheck/src/paths.rs +0 -0
  32. {hatcheck-0.2.0 → hatcheck-0.3.0}/crates/hatcheck/src/provider.rs +0 -0
  33. {hatcheck-0.2.0 → hatcheck-0.3.0}/crates/hatcheck/src/py.rs +0 -0
  34. {hatcheck-0.2.0 → hatcheck-0.3.0}/crates/hatcheck/src/serve.rs +0 -0
  35. {hatcheck-0.2.0 → hatcheck-0.3.0}/crates/hatcheck/src/sessions.rs +0 -0
  36. {hatcheck-0.2.0 → hatcheck-0.3.0}/crates/hatcheck/src/show.rs +0 -0
  37. {hatcheck-0.2.0 → hatcheck-0.3.0}/crates/hatcheck/src/times.rs +0 -0
  38. {hatcheck-0.2.0 → hatcheck-0.3.0}/crates/hatcheck/src/where_cmd.rs +0 -0
  39. {hatcheck-0.2.0 → hatcheck-0.3.0}/crates/hatcheck/src/widths.rs +0 -0
  40. {hatcheck-0.2.0 → hatcheck-0.3.0}/pyproject.toml +0 -0
@@ -41,15 +41,6 @@ version = "2.13.2"
41
41
  source = "registry+https://github.com/rust-lang/crates.io-index"
42
42
  checksum = "3ded4057c258ba199e2d26386d3af3780957ecaee6c4ef4041c6b4b8b97c0b06"
43
43
 
44
- [[package]]
45
- name = "block-buffer"
46
- version = "0.10.4"
47
- source = "registry+https://github.com/rust-lang/crates.io-index"
48
- checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71"
49
- dependencies = [
50
- "generic-array",
51
- ]
52
-
53
44
  [[package]]
54
45
  name = "bumpalo"
55
46
  version = "3.20.3"
@@ -124,15 +115,6 @@ version = "0.8.7"
124
115
  source = "registry+https://github.com/rust-lang/crates.io-index"
125
116
  checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b"
126
117
 
127
- [[package]]
128
- name = "cpufeatures"
129
- version = "0.2.17"
130
- source = "registry+https://github.com/rust-lang/crates.io-index"
131
- checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280"
132
- dependencies = [
133
- "libc",
134
- ]
135
-
136
118
  [[package]]
137
119
  name = "crc32fast"
138
120
  version = "1.5.2"
@@ -142,32 +124,12 @@ dependencies = [
142
124
  "cfg-if",
143
125
  ]
144
126
 
145
- [[package]]
146
- name = "crypto-common"
147
- version = "0.1.7"
148
- source = "registry+https://github.com/rust-lang/crates.io-index"
149
- checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a"
150
- dependencies = [
151
- "generic-array",
152
- "typenum",
153
- ]
154
-
155
127
  [[package]]
156
128
  name = "deranged"
157
129
  version = "0.5.9"
158
130
  source = "registry+https://github.com/rust-lang/crates.io-index"
159
131
  checksum = "9e9de72ce2ad1f90dc62fa25f0f430ef85eb4b0d8fa0be4f30373bc40a21d28e"
160
132
 
161
- [[package]]
162
- name = "digest"
163
- version = "0.10.7"
164
- source = "registry+https://github.com/rust-lang/crates.io-index"
165
- checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292"
166
- dependencies = [
167
- "block-buffer",
168
- "crypto-common",
169
- ]
170
-
171
133
  [[package]]
172
134
  name = "displaydoc"
173
135
  version = "0.2.7"
@@ -269,16 +231,6 @@ dependencies = [
269
231
  "slab",
270
232
  ]
271
233
 
272
- [[package]]
273
- name = "generic-array"
274
- version = "0.14.7"
275
- source = "registry+https://github.com/rust-lang/crates.io-index"
276
- checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a"
277
- dependencies = [
278
- "typenum",
279
- "version_check",
280
- ]
281
-
282
234
  [[package]]
283
235
  name = "getrandom"
284
236
  version = "0.2.17"
@@ -310,7 +262,7 @@ checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a"
310
262
 
311
263
  [[package]]
312
264
  name = "hatcheck"
313
- version = "0.2.0"
265
+ version = "0.3.0"
314
266
  dependencies = [
315
267
  "base64 0.22.1",
316
268
  "chrono",
@@ -322,7 +274,6 @@ dependencies = [
322
274
  "png",
323
275
  "serde",
324
276
  "serde_json",
325
- "sha2",
326
277
  "terminal_size",
327
278
  "ureq",
328
279
  "windows-sys 0.59.0",
@@ -784,17 +735,6 @@ dependencies = [
784
735
  "zmij",
785
736
  ]
786
737
 
787
- [[package]]
788
- name = "sha2"
789
- version = "0.10.9"
790
- source = "registry+https://github.com/rust-lang/crates.io-index"
791
- checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283"
792
- dependencies = [
793
- "cfg-if",
794
- "cpufeatures",
795
- "digest",
796
- ]
797
-
798
738
  [[package]]
799
739
  name = "shlex"
800
740
  version = "2.0.1"
@@ -914,12 +854,6 @@ dependencies = [
914
854
  "zerovec",
915
855
  ]
916
856
 
917
- [[package]]
918
- name = "typenum"
919
- version = "1.20.1"
920
- source = "registry+https://github.com/rust-lang/crates.io-index"
921
- checksum = "b6f5e870be6c3b371b77fe0ee0bafb859fa4964b4404c27de1d380043c4dda20"
922
-
923
857
  [[package]]
924
858
  name = "unicode-ident"
925
859
  version = "1.0.27"
@@ -6,7 +6,7 @@ exclude = ["desktop/src-tauri"]
6
6
  resolver = "2"
7
7
 
8
8
  [workspace.package]
9
- version = "0.2.0"
9
+ version = "0.3.0"
10
10
  edition = "2021"
11
11
  license = "MIT"
12
12
  rust-version = "1.90"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: hatcheck
3
- Version: 0.2.0
3
+ Version: 0.3.0
4
4
  Classifier: Development Status :: 4 - Beta
5
5
  Classifier: Environment :: Console
6
6
  Classifier: Intended Audience :: Developers
@@ -52,12 +52,21 @@ keeping one profile per account: `cl` and `cx` run Claude Code and Codex with a
52
52
  `hatcheck` lists the recent sessions in all of them, with the command that resumes each one and
53
53
  what's left of each plan. `hatcheck usage` shows the plans, `hatcheck best` says which account to use
54
54
  now, `hatcheck where` finds a session, `hatcheck open` resumes one in a new terminal, and
55
- `hatcheck move` carries one over to another profile, or converts it between Claude Code and Codex.
56
- `hatcheck bind` ties a folder to a profile, so `cl` and `cx` pick it there, and `--json` gives the
57
- sessions and plans to scripts and status bars. `claude-z` and `claude-k` run Claude Code on Z.ai's
55
+ `hatcheck move` carries one over to another profile, or converts it between Claude Code and Codex,
56
+ and `hatcheck fork` brings one in there as a new session, keeping the original.
57
+ `hatcheck relocate` brings a moved project folder's sessions along, `hatcheck bind` ties a folder
58
+ to a profile, so `cl` and `cx` pick it there, and `--json` gives the sessions and plans to scripts
59
+ and status bars. `claude-z` and `claude-k` run Claude Code on Z.ai's
58
60
  GLM or Kimi's models instead, in any profile. See
59
61
  [Claude Code and Codex CLI profiles](#claude-code-and-codex-cli-profiles).
60
62
 
63
+ **Not a gateway.** Hatcheck is never between an agent and its model. It rewrites no agent's
64
+ config, holds no login, and runs nothing in the background. Tools such as
65
+ [magpie](https://github.com/yetone/magpie) put a local gateway in that path so any agent can use
66
+ any model and accounts can take over from each other when a quota runs out. If that is what you
67
+ want, use one of those. Hatcheck is for the opposite case: keeping a work account and a personal
68
+ account apart, and knowing which one each session ran on. See [Rules it keeps](#rules-it-keeps).
69
+
61
70
  | | macOS | Linux | Windows |
62
71
  |---------------------------------|:-----:|:-----:|:-------:|
63
72
  | Claude Desktop instances | yes | no official Desktop app | [planned](#roadmap) |
@@ -350,6 +359,11 @@ which is listed too. Codex files its sessions by the date they started, archived
350
359
  `hat` is the short name: `hat`, `hat usage`, `hat move …` and so on work the same. `hat check` is the name
351
360
  in two words, so it's `hat` again: `hat check` lists sessions and `hat check usage` is `hat usage`.
352
361
 
362
+ A subcommand can be cut to its first letters, after `hat` or `hatcheck`: `hat m …` is `hat move …`
363
+ and `hat f …` is `hat fork …`. Where two share a letter, it goes to the one used more: `b` is
364
+ `best`, `s` is `show` and `u` is `usage`, so `bind` and `unbind` take two letters, `bi` and `un`.
365
+ What it prints, and the commands it suggests, use the whole name.
366
+
353
367
  Lists the Claude Code sessions from every `~/.claude*` profile, and the Codex sessions from every
354
368
  `~/.codex*` home, that were active in the last 24 hours or are still open, grouped by folder and
355
369
  newest first. Run from a folder that has sessions, it lists
@@ -377,7 +391,7 @@ cd ~/notes
377
391
  9h Write release notes · main
378
392
  cl work --resume 7e8f9a0b-1c2d-4e3f-8a4b-5c6d7e8f9a0b
379
393
  Profiles: ~/.claude (1), ~/.claude-personal (1), ~/.claude-work (4)
380
- Usage from Claude Code's last snapshot (token expired; run claude in this profile to renew it): ~/.claude-personal 9h ago
394
+ Usage from Claude Code's last snapshot (Claude Code's /usage couldn't fetch new numbers; offline, or rate limited?): ~/.claude-personal 9h ago
381
395
  ```
382
396
 
383
397
  - Run the folder's `cd` line, then the session's command. Sessions still open in a terminal are
@@ -403,8 +417,11 @@ Usage from Claude Code's last snapshot (token expired; run claude in this profil
403
417
  like Desktop ones, and Codex's own approval reviews aren't listed.
404
418
  - Since the usage is a shell comment, pasting the whole line runs just the command (zsh needs
405
419
  `setopt interactivecomments`).
406
- - Usage is fetched live, one request per account. When that fails (expired token, rate limit), Claude
407
- Code's last saved snapshot is used instead and named at the bottom with its age.
420
+ - Usage is fetched live: Claude Code's `/usage` is run once per account, in its profile, and the
421
+ snapshot it saves is read (see [`hatcheck usage`](#hatcheck-usage-plan-usage-for-every-profile)
422
+ below); ChatGPT's usage endpoint is asked once per Codex account. When that gives nothing new
423
+ (offline, not signed in, rate limited), the last saved snapshot is used instead and named at the
424
+ bottom with its age.
408
425
  - A session that ran in a folder [bound](#hatcheck-bind-tie-a-folder-to-a-profile) to another
409
426
  profile is marked `⚠ folder bound to ~/.claude-work`, so it's easy to spot and `hatcheck move`.
410
427
  - What it reads from each session file is cached, in `sessions.json` in `~/Library/Caches/hatcheck`
@@ -436,7 +453,7 @@ Sorted by Fable: those with some left first, the soonest reset on top; then thos
436
453
  ~/.claude pro · live
437
454
  session (5h) 62% ██████▎░░░ █████▍░░░░ resets 5:01pm (in 2h41m)
438
455
  week (all models) 91% █████████▏ ████▍░░░░░ resets Sat 4:20pm (in 3d2h)
439
- ~/.claude-personal pro · cached 9h00m ago (token expired; run claude in this profile to renew it)
456
+ ~/.claude-personal pro · cached 9h00m ago (Claude Code's /usage couldn't fetch new numbers; offline, or rate limited?)
440
457
  session (5h) ? ·········· reset 10:20am, after this snapshot
441
458
  week (all models) 97% █████████▊ ███████▎░░ resets Mon 4:20pm (in 5d2h)
442
459
  ~/.codex-work ChatGPT plus · live
@@ -446,19 +463,26 @@ Sorted by Fable: those with some left first, the soonest reset on top; then thos
446
463
 
447
464
  - Accounts are sorted by their Fable limit. First come those with some Fable left, the soonest
448
465
  Fable reset on top: what's left there goes unused once it resets, so that's the one to use first.
449
- Next come those out of Fable, or out of the weekly limit for all models, which Fable counts against
450
- too, the soonest back on top. Then come accounts without a Fable limit, such as Pro plans, and
451
- Codex homes. A snapshot saved before its Fable limit reset counts as full until the reset after.
466
+ An account past 90% of its Fable limit goes behind the fresher ones, and past 98% behind those
467
+ too: nearly out, it's the one to use last. Next come those out of Fable, or out of the weekly
468
+ limit for all models, which Fable counts against too, the soonest back on top. Then come accounts
469
+ without a Fable limit, such as Pro plans, and Codex homes. A snapshot saved before its Fable limit
470
+ reset counts as full until the reset after.
452
471
  - Each limit has two bars: what's left of it, then how much of its window, 5 hours or a week, is
453
472
  left before it resets. In a terminal the second goes from blue with most of the window to go,
454
473
  through gold and orange, to red as the reset nears. When the first bar is shorter than the
455
474
  second, as for `Fable` here, the limit is being used up faster than its window runs out. A limit
456
475
  without a reset time, like an unused 5-hour session, has no time bar.
457
- - It reads each profile's OAuth token from the macOS Keychain, or from the profile's
458
- `.credentials.json` on Linux and Windows, and asks the same endpoint as Claude Code's `/usage`. The token is only read, never refreshed: a refresh rotates it and would sign Claude Code
459
- out of that profile.
460
- - When a token has expired or the endpoint rate-limits the account, Claude Code's last saved snapshot
461
- is shown with its age. Run `claude` (or `cl <name>`) in that profile to renew the token.
476
+ - A Claude Code account's numbers come from Claude Code itself: `claude -p /usage` is run in the
477
+ profile, with no tools, MCP servers, settings or transcript, which asks Anthropic with the
478
+ profile's own login and saves the answer in the profile's `.claude.json`; that snapshot is what's
479
+ shown. hatcheck never reads a login: the plan's name comes from the account details Claude Code
480
+ keeps beside the snapshot. The runs take about a second each, in parallel, from a folder of
481
+ hatcheck's own (`claude-usage` in its cache folder), so the one empty project folder Claude Code
482
+ makes for the folder it runs in is the same every time.
483
+ - When that gives nothing new (`claude` not on the `PATH`, the profile not signed in, offline, or
484
+ Anthropic rate-limiting the account), Claude Code's last saved snapshot is shown with its age and
485
+ the reason.
462
486
  - Profiles signed in to the same account are shown together. Per-model limits such as
463
487
  `week (Fable)` are named in violet, with their bar drawn in it, to tell them from `week (all
464
488
  models)`; the % keeps the severity's colour. Quota fields the server
@@ -476,18 +500,19 @@ Sorted by Fable: those with some left first, the soonest reset on top; then thos
476
500
 
477
501
  ### `hatcheck move`: carry on a session with another profile
478
502
 
479
- When one account runs low, continue a session on another profile, or in the other agent. It copies
480
- the session by default; `--no-keep` deletes the original once the copy is checked, which moves it.
503
+ When one account runs low, continue a session on another profile, or in the other agent. It moves
504
+ the session: the original is deleted once every copied file is checked against it, and an older
505
+ copy never replaces a newer one. `--keep` keeps the original, which copies it instead.
481
506
 
482
507
  ```
483
508
  $ hatcheck move 9a8b7c6d work
484
- Copied session 9a8b7c6d "Add rate limiting to the login endpoint" from ~/.claude to ~/.claude-work:
509
+ Moved session 9a8b7c6d "Add rate limiting to the login endpoint" from ~/.claude to ~/.claude-work:
485
510
  transcript 53.0 KB
486
511
  subagents and tool output 3 files
487
512
  /rewind file snapshots 4 files
488
513
  plan quiet-river-plan.md
489
514
  Paths in the conversation to these files now point to the copies.
490
- Kept the original in ~/.claude; --no-keep deletes it once the copy is checked.
515
+ Deleted the original from ~/.claude; the plan file stays, since sessions can share one.
491
516
  Resume it with:
492
517
  cd ~/code/api
493
518
  cl work --resume 9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d
@@ -495,9 +520,9 @@ Resume it now in ~/.claude-work? [y/N]
495
520
  ```
496
521
 
497
522
  - It copies the transcript, the folder of subagent transcripts and saved tool output, the `/rewind`
498
- file snapshots and the plan file, keeping their timestamps, and checks every copied file. Paths in
499
- the conversation that point at those files are changed to point at the copies, except inside
500
- Claude's thinking, whose signatures would stop matching.
523
+ file snapshots and the plan file, keeping their timestamps, and checks every copied file before it
524
+ deletes anything. Paths in the conversation that point at those files are changed to point at the
525
+ copies, except inside Claude's thinking, whose signatures would stop matching.
501
526
  - A session that's open in a terminal is refused: quit it first, so it stops changing.
502
527
  `hatcheck` marks those `● open`.
503
528
  - Resume it with the printed command, from the directory it ran in, or answer `y` when it asks and
@@ -506,38 +531,51 @@ Resume it now in ~/.claude-work? [y/N]
506
531
  permissions, MCP servers, plugins and project memory are the new profile's. If the new account
507
532
  rejects the signatures on earlier thinking, Claude Code drops those thinking blocks and carries
508
533
  on.
509
- - Plan files stay in the old profile even with `--no-keep`, since sessions can share one.
534
+ - The plan file stays in the old profile, since sessions can share one.
510
535
  - If the session's last reply came from Z.ai or Kimi, it also prints the `cl -z` or `cl -k` command
511
536
  that carries on there instead of on the new profile's Claude plan.
512
537
 
513
538
  #### When the profile already has a copy
514
539
 
515
- A session copied to another profile and carried on there can be moved back: the copy it left
516
- behind is an older version of it. The two copies are compared line by line:
540
+ A session copied to another profile with `--keep` and carried on there can be moved back: the copy
541
+ it left behind is an older version of it. The two copies are compared line by line:
517
542
 
518
543
  - **The copy there is older**, because the session went on from it: it's brought up to date, and
519
544
  each item shows `(updated an older copy)`. Nothing is lost, since the copy being moved starts with
520
545
  all of it.
521
- - **The copy there is newer**, because the session went on there: nothing is copied. With
522
- `--no-keep`, the older copy here is deleted.
523
- - **Both went on** after the copy was made: nothing is copied, since that would lose the prompts
546
+ - **The copy there is newer**, because the session went on there: it stops and changes nothing,
547
+ since putting this copy there would lose what came after. It prints the command that resumes the
548
+ session there, and the files of the older copy here, to delete if you don't want them.
549
+ - **Both went on** after the copy was made: nothing is moved, since that would lose the prompts
524
550
  only the copy there has. It says when they split and how many prompts each has had since.
525
- `--fork` brings this one in as a new session with its own ID and keeps both, as Claude Code's
526
- `claude --resume <id> --fork-session` and `codex fork` do. Or delete the copy there first.
551
+ [`hatcheck fork`](#hatcheck-fork-a-session-as-a-new-one-in-another-profile) brings this one in
552
+ as a new session with its own ID and keeps both. Or delete the copy there first.
553
+
554
+ ```
555
+ $ hatcheck move 9a8b7c6d work
556
+ ~/.claude-work already has session 9a8b7c6d "Add rate limiting to the login endpoint", with everything in this copy and more: the session went on there. This copy is older, so nothing was moved.
557
+ Resume it there with:
558
+ cd ~/code/api
559
+ cl work --resume 9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d
560
+ To drop the older copy in ~/.claude, delete:
561
+ ~/.claude/projects/-Users-me-code-api/9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d.jsonl
562
+ ~/.claude/projects/-Users-me-code-api/9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d
563
+ ~/.claude/file-history/9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d
564
+ ```
527
565
 
528
566
  ```
529
567
  $ hatcheck move 9a8b7c6d default
530
- Session 9a8b7c6d went on in both ~/.claude-work and ~/.claude after Oct 15 01:40: 3 new prompts in ~/.claude-work, 1 in ~/.claude. Copying this one over would lose what's only in ~/.claude, so nothing was copied.
568
+ Session 9a8b7c6d went on in both ~/.claude-work and ~/.claude after Oct 15 01:40: 3 new prompts in ~/.claude-work, 1 in ~/.claude. Moving this one over would lose what's only in ~/.claude, so nothing was moved.
531
569
  To keep both, bring this one in as a new session with its own ID:
532
- hatcheck move 9a8b7c6d default --fork
570
+ hatcheck fork 9a8b7c6d default
533
571
  Or delete the copy in ~/.claude first, to replace it:
534
572
  ~/.claude/projects/-Users-me-code-api/9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d.jsonl
573
+ ~/.claude/projects/-Users-me-code-api/9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d
574
+ ~/.claude/file-history/9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d
535
575
  ```
536
576
 
537
- A fork changes the session's ID in its records and gives it its own folders for subagents, saved
538
- tool output and `/rewind` snapshots. Its message IDs stay the same, as in Claude Code's own fork.
539
- Resuming a fork, a round trip and a copy that went on in both profiles were tested with the real
540
- `claude` (2.1.301).
577
+ A round trip and a copy that went on in both profiles were tested with the real `claude`
578
+ (2.1.301).
541
579
 
542
580
  When more than one profile has the session, you're asked which copy to take. The one used last
543
581
  comes first and is the default, and each other one says how it stands to it:
@@ -565,7 +603,7 @@ Converted Codex session 019a8c1e "Add retries to the uploader" from ~/.codex-wor
565
603
  tool calls 191, as text with their output shortened
566
604
  left out Codex's reasoning, images, goals and subagent sessions
567
605
  new session 6f6749d7-0f59-4683-997c-a3595421b93f (241.9 KB)
568
- The original stays in ~/.codex-work.
606
+ The original stays in ~/.codex-work, since a conversion leaves things out.
569
607
  Resume it with:
570
608
  cd ~/code/api
571
609
  cl work --resume 6f6749d7-0f59-4683-997c-a3595421b93f
@@ -573,9 +611,8 @@ Resume it now in ~/.claude-work? [y/N]
573
611
  ```
574
612
 
575
613
  - Between Codex homes signed in to the same ChatGPT account, the session file is copied as it is,
576
- with its ID and its name, and `--no-keep` works as for Claude Code. A copy already there is
577
- compared the same way, and `--fork` gives the copy a new ID in its file name, its
578
- `session_meta` and its events.
614
+ with its ID and its name, and the original is deleted once the copy is checked, as for Claude
615
+ Code. A copy already there is compared the same way.
579
616
  - Between Claude Code and Codex (`hatcheck move 3f1c2a9e codex:work` goes the other way), and
580
617
  between Codex homes on different accounts, the session is converted into a new one with a new ID,
581
618
  and the original is always kept. Codex encrypts its reasoning and its summaries of compacted
@@ -599,9 +636,87 @@ Resume it now in ~/.claude-work? [y/N]
599
636
  | `SESSION` | the session ID, or the start of it |
600
637
  | `PROFILE` | where it goes: `NAME` for `~/.claude-NAME`, `default` for `~/.claude`, `codex` for `~/.codex`, `codex:NAME` for `~/.codex-NAME`, or a path |
601
638
  | `--from PROFILE` | the profile or home to take it from, when more than one has it; without it you're asked, with the one used last as the default, and a script or `--json` gets that one |
602
- | `--no-keep` | delete the original once the copy is checked, which moves it (not when converting) |
603
- | `--fork` | copy it in as a new session with its own ID, leaving a copy already there as it is (a converted session always gets one) |
604
- | `--dry-run` | show what would be copied, converted and deleted, and change nothing |
639
+ | `--keep` | keep the original, which copies the session rather than moving it (a converted session's original is always kept) |
640
+ | `--dry-run` | show what would be moved, copied, converted and deleted, and change nothing |
641
+
642
+ `--no-keep`, which a move needed before it was the default, is still accepted, and so is `--fork`,
643
+ which does what [`hatcheck fork`](#hatcheck-fork-a-session-as-a-new-one-in-another-profile) does.
644
+
645
+ ### `hatcheck fork`: a session as a new one in another profile
646
+
647
+ Bring a session into another profile as a new one with its own ID, and keep the original, as
648
+ Claude Code's `claude --resume <id> --fork-session` and `codex fork` do within one profile. It's
649
+ how to keep both when a move stops because the session went on in both profiles.
650
+
651
+ ```
652
+ $ hatcheck fork 9a8b7c6d default
653
+ Forked session 9a8b7c6d "Add rate limiting to the login endpoint" from ~/.claude-work to ~/.claude as a new session:
654
+ transcript 61.2 KB
655
+ subagents and tool output 3 files
656
+ /rewind file snapshots 5 files
657
+ plan quiet-river-plan.md (already there)
658
+ new session 5b1e0c4a-7d2f-4e6b-9a3c-8f1d2e3c4b5a
659
+ Paths in the conversation to these files now point to the copies.
660
+ The copy of session 9a8b7c6d that was already in ~/.claude stays as it was.
661
+ The original stays in ~/.claude-work.
662
+ Resume it with:
663
+ cd ~/code/api
664
+ claude --resume 5b1e0c4a-7d2f-4e6b-9a3c-8f1d2e3c4b5a
665
+ Resume it now in ~/.claude? [y/N]
666
+ ```
667
+
668
+ - It copies what `move` copies and checks it the same way, and the copy gets the new ID in its
669
+ records, and its own folders for subagents, saved tool output and `/rewind` snapshots. Its
670
+ message IDs stay the same, as in Claude Code's own fork. A Codex session gets the new ID in its
671
+ file name, its `session_meta` and its events.
672
+ - A copy already in the profile stays as it is, so a fork never stops for one, and the original
673
+ always stays where it was. A plan file already there stays too, since the copy there uses it.
674
+ - To the other agent, or to a Codex home on another account, it converts the session, as `move`
675
+ does: a converted session is always a new one.
676
+ - Resuming a fork was tested with the real `claude` (2.1.301).
677
+
678
+ | Option | What it does |
679
+ |------------------|------------------------------------------------------------------------------|
680
+ | `SESSION` | the session ID, or the start of it |
681
+ | `PROFILE` | where it goes, as for [`move`](#hatcheck-move-carry-on-a-session-with-another-profile) |
682
+ | `--from PROFILE` | the profile or home to take it from, when more than one has it, as for `move` |
683
+ | `--dry-run` | show what would be copied or converted, and change nothing |
684
+
685
+ ### `hatcheck relocate`: a project folder that moved
686
+
687
+ Claude Code files a session under `projects/<the folder's name>` and records the folder in every
688
+ line, so once a project folder is renamed or moved, `claude --resume` from the new folder doesn't
689
+ list its sessions, and the commands `hatcheck` prints `cd` to a folder that's gone. `hatcheck
690
+ relocate` brings the sessions along, in every profile and home:
691
+
692
+ ```console
693
+ $ mv ~/code/api ~/code/api2
694
+ $ hatcheck relocate ~/code/api ~/code/api2
695
+ Relocated 4 sessions from ~/code/api to ~/code/api2:
696
+ ~/.claude-work 2 sessions: projects/-Users-me-code-api renamed projects/-Users-me-code-api2, with its memory and index
697
+ ~/.claude 1 session: projects/-Users-me-code-api renamed projects/-Users-me-code-api2
698
+ ~/.codex 1 session: the folder in the file rewritten
699
+ The folder binding of ~/code/api moved to ~/code/api2.
700
+ Resume them from ~/code/api2. To undo: hatcheck relocate ~/code/api2 ~/code/api
701
+ ```
702
+
703
+ - Sessions that ran in the folder, or in a folder under it, come along. Each profile's project
704
+ folder is renamed to the new name when every session in it moves, so its `memory/` and
705
+ `sessions-index.json` come with it, the index's paths updated; when some stay, the sessions are
706
+ moved into the new project folder one by one, with their subagent and tool-output folders. In
707
+ each transcript the lines' top-level `cwd` is rewritten to the new folder, and nothing else:
708
+ what was said about the old path stays as it was. A Codex session, filed by date, stays where
709
+ it is, with the folder in its `session_meta` and `turn_context` lines rewritten, so `codex
710
+ resume` lists it from the new folder. Timestamps are kept, and a folder binding
711
+ ([`hatcheck bind`](#hatcheck-bind-tie-a-folder-to-a-profile)) of the old folder moves to the new.
712
+ - A project folder whose name matches but whose sessions ran elsewhere (Claude Code names
713
+ `~/code/api-old` and `~/code/api/old` alike) is left alone and listed.
714
+ - It refuses, and changes nothing, when a session is open, was written in the last minute, is a
715
+ link rather than a file, is already filed under the new folder, or has a line naming the folder
716
+ that isn't JSON; and when NEW doesn't exist, is OLD, or is inside OLD. `--dry-run` shows what
717
+ would move. Running it the other way round, `hatcheck relocate NEW OLD`, undoes it, byte for
718
+ byte.
719
+ - `--json` prints the sessions moved and skipped, the binding and the undo command.
605
720
 
606
721
  ### `hatcheck open`: resume a session in a new terminal
607
722
 
@@ -635,27 +750,39 @@ cl work --resume 3f1c2a9e-8b7d-4c1e-9f2a-6d5e4c3b2a10
635
750
  ```console
636
751
  $ hatcheck best
637
752
  Claude Code, best first (sorted by Fable, as hatcheck usage sorts)
638
- 1. ~/.claude-work Fable 31% left, resets Tue 12:00pm · 5h 80% → cl work
639
- 2. ~/.claude-team Fable 100% left, resets Thu 1:00am · 5h 100% → cl team
640
- 3. ~/.claude-personal Fable 0% left, back Sat 4:59pm · 5h 100% → cl personal
641
- 4. ~/.claude week 64% · 5h 100% → claude
753
+ 1. ~/.claude-work Fable 31% left, resets Tue 12:00pm, 0.5%/h · 5h 80% → cl work
754
+ 2. ~/.claude-team Fable 100% left, resets Thu 1:00am, 0.9%/h · 5h 100% → cl team
755
+ 3. ~/.claude-personal Fable 0% left, back Sat 4:59pm · 5h 100% → cl personal
756
+ 4. ~/.claude week 64%, 1.3%/h · 5h 100% → claude
642
757
  Codex, best first
643
- 1. ~/.codex month 100% → codex
758
+ 1. ~/.codex month 100%, 0.2%/h → codex
644
759
  Advice only: nothing is started, moved or switched. hatcheck best --for <session> says where to carry a session.
645
760
  $ hatcheck best --for 9a8b7c6d
646
761
  Session 9a8b7c6d "Add rate limiting to the login endpoint" is in ~/.claude-personal: Fable 0% left, back Sat 4:59pm · 5h 100%.
647
- Carry it to ~/.claude-work: Fable 31% left, resets Tue 12:00pm · 5h 80%.
762
+ Carry it to ~/.claude-work: Fable 31% left, resets Tue 12:00pm, 0.5%/h · 5h 80%.
648
763
  hatcheck move 9a8b7c6d work --from personal
649
764
  It prints the command that resumes it there, and offers to run it.
765
+ $ hatcheck best --wait work && cl work
766
+ No room yet; waiting for ~/.claude-work until Tue 12:00pm (in 2d14h)…
650
767
  ```
651
768
 
652
769
  - Claude Code accounts come in the order `hatcheck usage` sorts them: those with Fable left first,
653
- the soonest Fable reset on top; then those out of Fable, or out of the weekly limit for all models,
654
- the soonest back on top; then accounts without a Fable limit, by the limit with the least left.
655
- Codex homes are ranked by the limit with the least left.
770
+ the soonest Fable reset on top, with those past 90% used behind the fresher ones and those past
771
+ 98% behind those; then those out of Fable, or out of the weekly limit for all models, the soonest
772
+ back on top; then accounts without a Fable limit, by the limit with the least left. Codex homes
773
+ are ranked by the limit with the least left.
774
+ - The Fable part ends with the account's pace: what's left, divided by the hours until it resets,
775
+ so what the account can spend per hour without running out before the reset. `0.5%/h` with 31%
776
+ left means about 62 hours to go. An account without a Fable limit shows the pace of its tightest
777
+ limit instead.
656
778
  - `--for <session>` picks the best other account of the session's agent that has room now, and
657
779
  prints the [`hatcheck move`](#hatcheck-move-carry-on-a-session-with-another-profile) command. If
658
780
  the session's own account is the best, it says so and prints the resume command.
781
+ - `--wait` sleeps until an account has room, then answers as above: any Claude Code account;
782
+ `--wait NAME` (or `default`, `codex`, `codex:NAME`) that one profile or home; with `--for`, an
783
+ account of the session's agent. It sleeps until the soonest reset the numbers name, says so on
784
+ stderr, then looks again; when nothing says when an account will have room, it stops with an
785
+ error instead of polling. For a script: `hatcheck best --wait work && cl work`.
659
786
  - The numbers are the ones `hatcheck usage` shows, live when it can be, with a snapshot's age
660
787
  otherwise; `--cached` skips the APIs.
661
788
  - **Advice only.** It runs when you ask, prints commands, and never starts, moves or switches
@@ -741,10 +868,11 @@ $ hatcheck --json
741
868
  "why": null,
742
869
  "limits": [
743
870
  {"name": "session (5h)", "short": "5h", "kind": "session", "used_percent": 20, "left_percent": 80,
744
- "resets_at": "2026-10-16T01:30:00Z", "window_seconds": 18000, "severity": "normal", "per_model": false}
871
+ "resets_at": "2026-10-16T01:30:00Z", "window_seconds": 18000, "severity": "normal", "per_model": false,
872
+ "pace": 28.42}
745
873
  ],
746
874
  "extra": null,
747
- "fable": {"left": true, "resets_at": "2026-10-20T16:00:00Z"}
875
+ "fable": {"left": true, "resets_at": "2026-10-20T16:00:00Z", "band": "fresh"}
748
876
  }
749
877
  ],
750
878
  "hidden": {"Desktop": 3},
@@ -759,12 +887,52 @@ $ hatcheck --json
759
887
  `--no-usage`). `hatcheck usage --json` gives every account, in its Fable order, with `sorted_by`.
760
888
  - **Stable:** absolute paths rather than `~`, times in UTC (ISO 8601), no colours. `version` goes up
761
889
  only when a field changes meaning or goes away; new fields can appear at any time.
762
- - `left_percent` is `null` for a snapshot's limit that has reset since it was saved. `fable` is
763
- `{"left": true, "resets_at": …}`, `{"left": false, "back_at": …}`, or `null` without a Fable limit.
764
- `source` is `live`, `cached` or `none`, with `why` when it isn't live.
890
+ - `left_percent` is `null` for a snapshot's limit that has reset since it was saved. `pace` is what's
891
+ left per hour until the reset (`left_percent` over the hours to go, at least one), `null` without
892
+ a reset ahead. `fable` is `{"left": true, "resets_at": …}`, `{"left": false, "back_at": …}`, or
893
+ `null` without a Fable limit; its `band` is `fresh`, `low` (past 90% used), `nearly out` (past 98%)
894
+ or `out`, the order `hatcheck usage` sorts by. `source` is `live`, `cached` or `none`, with `why`
895
+ when it isn't live.
765
896
  - Errors go to stderr with a non-zero exit, so stdout is always valid JSON. Email addresses only with
766
897
  `--email`.
767
898
 
899
+ ## Rules it keeps
900
+
901
+ Each rule names the code that keeps it, so it can be checked rather than taken on trust.
902
+
903
+ - **Not in the path.** Nothing of Hatcheck sits between an agent and its model. `cl` and `cx`
904
+ set `CLAUDE_CONFIG_DIR` or `CODEX_HOME` (and, for `cl -z` and `cl -k`, the provider variables
905
+ you set up) and run the agent in their place; nothing stays running
906
+ ([`launch.rs`](crates/hatcheck/src/launch.rs), `run`).
907
+ - **No agent's config is rewritten**, except when you ask: `hatcheck bind --hook` adds one
908
+ `SessionStart` hook to each profile's `settings.json` and each Codex home's `hooks.json`, and
909
+ `unbind --hook` removes only what it added ([`bind.rs`](crates/hatcheck/src/bind.rs),
910
+ `add_hooks` and `remove_hooks`).
911
+ - **No credentials.** It never asks for, stores, refreshes or moves a login. For Claude it
912
+ never reads one: Claude Code's own `/usage` is run in the profile and the snapshot it saves is
913
+ read ([`usage.rs`](crates/hatcheck/src/usage.rs), `run_claude_usage` and `claude_live`). For
914
+ Codex, the home's `auth.json` token is sent only to ChatGPT's usage endpoint, as it is, and
915
+ never refreshed (`read_codex_login` and `fetch_codex_usage` there).
916
+ - **Nothing leaves your computer** except those usage requests: Claude Code's to Anthropic, and
917
+ one to ChatGPT per Codex account. `CODEX_USAGE_URL` in `usage.rs` is the one address the
918
+ binary itself calls; `--cached` and `--no-usage` make none. The Z.ai and Kimi endpoints in
919
+ [`provider.rs`](crates/hatcheck/src/provider.rs) are handed to Claude Code, which calls them
920
+ only when you run `cl -z` or `cl -k`.
921
+ - **No automatic switching** between accounts. `hatcheck best` ranks and prints commands, and
922
+ nothing in the binary starts, moves or switches a session on its own
923
+ ([`best.rs`](crates/hatcheck/src/best.rs): advice only, `--wait` sleeps and then prints).
924
+ - **Nothing runs in the background.** There's no daemon. `hatcheck serve` is the desktop app's
925
+ engine and lives only while the app does, one command per line
926
+ ([`serve.rs`](crates/hatcheck/src/serve.rs)).
927
+ - **Sessions are only ever moved, copied or rewritten in ways you can undo.** `move` deletes the
928
+ original only once every copied file is checked against it, never puts an older copy over a
929
+ newer one, and is undone by moving the session back; `fork` keeps the original; `relocate`
930
+ prints the command that undoes it; and all three have `--dry-run`, which runs every check and
931
+ writes nothing ([`move_cmd.rs`](crates/hatcheck/src/move_cmd.rs),
932
+ [`relocate.rs`](crates/hatcheck/src/relocate.rs)).
933
+
934
+ Function names rather than line numbers, since lines move; `git grep` finds them.
935
+
768
936
  ## The desktop app
769
937
 
770
938
  A menu-bar app (system tray on Windows and Linux) built on the CLI. It shows what's left of every
@@ -773,9 +941,9 @@ which opens a terminal in the session's folder, and **Copy**. It also has button
773
941
  Desktop instances and notifies you when a limit runs low or resets.
774
942
 
775
943
  Its main window browses every session, with search, filters and a read-only preview of the
776
- conversation. From there you can resume a session, copy its command, or move it to another
777
- profile after a dry run. The window also lists profiles and homes, signs in to a new one in a
778
- terminal, and makes Desktop instances.
944
+ conversation. From there you can resume a session, copy its command, or move or fork it to
945
+ another profile after a dry run. The window also lists profiles and homes, signs in to a new one
946
+ in a terminal, and makes Desktop instances.
779
947
 
780
948
  It ships the CLI inside itself, so it needs no Python. It never handles a login, and live usage is
781
949
  off until you turn it on. See [desktop/README.md](desktop/README.md) to build it; releases come
@@ -787,7 +955,10 @@ from `.github/workflows/desktop.yml` for macOS, Windows and Linux. The plan behi
787
955
  - **`crates/hatcheck/`**: hatcheck in Rust, the commands and the launchers. Its `src/` has a file per
788
956
  subcommand (`sessions.rs`, `usage.rs`, `move_cmd.rs`, `open_cmd.rs`, `desktop.rs` and the rest),
789
957
  `launch.rs` for `cl`, `cx`, `claude-z` and `claude-k`, and `src/bin/` for those binaries and `hat`.
790
- It was ported from a Python version with the same output; see [docs/rust-port.md](docs/rust-port.md).
958
+ Its own README is the crate's page on crates.io. It was ported from a Python version with the
959
+ same output; see [docs/rust-port.md](docs/rust-port.md).
960
+ - **`CHANGELOG.md`**: what changed in each release of the CLI; a release's notes on GitHub are its
961
+ section there.
791
962
  - **`install.sh`** / **`install.ps1`**: build the commands from a clone and put them on your `PATH`
792
963
  (`~/.local/bin` by default).
793
964
  - **`pyproject.toml`**: the PyPI package, the binaries as wheels built with maturin.
@@ -799,6 +970,9 @@ from `.github/workflows/desktop.yml` for macOS, Windows and Linux. The plan behi
799
970
  `logo.svg` and `logo-dark.svg` (the mark), `logo-wordmark.svg` and `logo-wordmark-dark.svg`, `favicon.svg`.
800
971
  - **`docs/proposals/`**: the plans these came from: [the CLI features](docs/proposals/cli-features.md)
801
972
  and [the desktop app](docs/proposals/desktop-app.md).
973
+ - **`AGENTS.md`** and **`docs/lessons.md`**: notes for coding agents (`CLAUDE.md` imports them): how
974
+ to build and check, the rules the code keeps, and what earlier work got wrong, with the rule that
975
+ would have caught it.
802
976
  - **`tests/`**: the command tests, which run the binary against throwaway home folders:
803
977
  `python3 -m unittest discover -s tests`, which builds it with cargo first, and the crate's own
804
978
  tests with `cargo test -p hatcheck`. They never read your real profiles, Keychain or the network.
@@ -811,9 +985,9 @@ from `.github/workflows/desktop.yml` for macOS, Windows and Linux. The plan behi
811
985
  instance with its own icon, and a launcher that brings a running instance forward instead of
812
986
  opening a duplicate. It needs testing on a Windows machine first.
813
987
  - **Linux.** There's no official Claude Desktop for Linux, so only the CLI tools apply there.
814
- - **Releases:** the CLI's 0.1.0 is on PyPI, as Python; the next release ships the Rust binaries to
815
- PyPI, GitHub, the Homebrew tap and winget, then the app's installers, once the release workflows
816
- have their accounts and secrets (the workflows list them).
988
+ - **Releases:** the CLI is on GitHub, the Homebrew tap, PyPI and crates.io from 0.2.0
989
+ ([CHANGELOG.md](CHANGELOG.md)). Next are winget, then the app's installers, once its release
990
+ workflow has its signing and updater secrets (`desktop.yml` lists them).
817
991
  - **Signing:** a Developer ID for the macOS app, and a certificate for the Windows one.
818
992
 
819
993
  ## Credits