Skip to content

Commit 6dca767

Browse files
dancinlifeclaude
andcommitted
docs: add CLAUDE.md (project entry + work rules) and ARCHITECTURE.json (design SSOT)
Completes the repo doc set alongside README.md/CHANGELOG.md per dancinlab convention: CLAUDE.md = entry point + work rules + hexa gotchas; ARCHITECTURE.json = the module/flow tree SSOT (commands · otp-flow · session-vault · roster-secrets · distribution · hexa-notes). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent 2064e3b commit 6dca767

2 files changed

Lines changed: 106 additions & 0 deletions

File tree

‎ARCHITECTURE.json‎

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
{
2+
"name": "quota — architecture SSOT",
3+
"summary": "Claude Code multi-account session vault + login-OTP fetcher. Compiled-hexa CLI; all I/O via exec() + json_parse. Credentials from the `secret` CLI only (never argv/logs/memory). One file: cli/quota.hexa.",
4+
"children": [
5+
{
6+
"name": "commands",
7+
"role": "the CLI surface (dispatch in fn main)",
8+
"children": [
9+
{ "name": "code <n|email|login-url>", "role": "newest login OTP for ONE account", "detail": "resolve target -> Postmark scan(email) -> print bare code to stdout, context to stderr. --wait <sec> polls 5s and accepts only mail arriving after start (no stale replay). --json = one object. exit 0 ok / 1 bad-arg / 3 none." },
10+
{ "name": "inbox", "role": "group-wide scan (who got mail + code)", "detail": "scan ALL roster emails (from_hint OFF) -> table of index/email/code/subject, newest first. --json = array." },
11+
{ "name": "list (alias ls)", "role": "roster from the secret store", "detail": "claude<n>.email keys -> index/email rows, with a [saved] mark when a vault slot exists. --json = array." },
12+
{ "name": "save <n>", "role": "snapshot the live Claude session -> vault slot n", "detail": "copy ~/.claude/.credentials.json -> ~/.quota/vault/<n>.credentials.json (chmod 600). Fails if no live session." },
13+
{ "name": "use <n>", "role": "restore a vault slot as the live session", "detail": "backs up the live cred to vault/.live-backup first (never destroys the current login), then copies slot n -> ~/.claude/.credentials.json." },
14+
{ "name": "sessions / rm <n>", "role": "list / delete vault slots", "detail": "sessions lists saved slots (--json); rm deletes one slot file." },
15+
{ "name": "--version / help", "role": "version + usage", "detail": "usage lists every verb + the secret keys consumed." }
16+
]
17+
},
18+
{
19+
"name": "otp-flow",
20+
"role": "login URL/account -> OTP (the core job)",
21+
"children": [
22+
{ "name": "resolve_target", "role": "login-url -> account", "detail": "http(s) arg -> extract login_hint=/email= query param if present; else treat the arg as an index/email verbatim." },
23+
{ "name": "postmark_cfg", "role": "token -> curl -K header file", "detail": "writes silent/show-error + `X-Postmark-Server-Token: <token>` + Accept:json into a temp -K file; the token is pulled from `secret get` INSIDE the shell substitution, so it never enters process memory or argv." },
24+
{ "name": "scan", "role": "3-state inbound scan -> rows", "detail": "curl /messages/inbound for status in (processed, scheduled, failed) -> json_parse -> dedup by MessageID -> match To against roster -> code = 6-8 digit run from Subject, else from the /details TextBody/HtmlBody. Newest-first (list order)." },
25+
{ "name": "from_hint", "role": "soft sender preference", "detail": "anthropic.com is a preference, NOT a hard filter: dedicated per-account mailboxes mean any OTP to the account is its login code. `code` prefers a hint match, then falls back to any coded message." }
26+
]
27+
},
28+
{
29+
"name": "session-vault",
30+
"role": "swap accounts when one hits its quota",
31+
"detail": "State under ~/.quota/ (vault/ credential snapshots + quota.log JSONL audit). The account-distinguishing credential is ~/.claude/.credentials.json (file-based, portable). `use` always backs up the live session before overwriting."
32+
},
33+
{
34+
"name": "roster-secrets",
35+
"role": "config = data, not code",
36+
"detail": "Postmark token key = postmark.dancinlab_server_token. Roster derived from secret keys claude<n>.email (n=1..20 probed). No account/provider literals in code — change the roster by changing the secret store. ~/.sidecar/accounts.json is NOT read (quota reads secret directly)."
37+
},
38+
{
39+
"name": "distribution",
40+
"role": "hx install + native binaries + CI/release",
41+
"children": [
42+
{ "name": "hx install quota", "role": "primary setup path", "detail": "dancinlab is in HX_ORGS default -> probe github.com/dancinlab/quota -> clone -> shim cli/quota.hexa (compile-then-exec, cached). Manifest [package].entry + [[bin]] in hexa.toml." },
43+
{ "name": "ci.yml", "role": "push/PR gate", "detail": "install hexa toolchain -> hexa parse entry+lib -> hexa run tests/smoke.hexa -> block bare top-level main() in changed .hexa. ubuntu + macos." },
44+
{ "name": "release.yml", "role": "v* tag -> verified release", "detail": "install-smoke (fresh `hx install .` on ubuntu+macos, quota --help dispatches) gates the GitHub Release; then build-binaries AOT-compiles quota-darwin-arm64 + quota-linux-x86_64 via `hexa build` and uploads them (allgreen-promote)." }
45+
]
46+
},
47+
{
48+
"name": "hexa-notes",
49+
"role": "language gotchas (compiled)",
50+
"detail": "Entry = fn main() with NO args param (fn main(args) won't compile); argv via args() then slice past the .hexa. int->string = str(x); JSON via json_parse()+j[\"key\"]+map_contains_key; no += (reassign). Heavy `hexa build` is guard-blocked on the dev mac -> build binaries on CI only."
51+
}
52+
]
53+
}

‎CLAUDE.md‎

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
# quota
2+
3+
Claude Code multi-account **session vault + login-OTP fetcher** — a single compiled-hexa CLI
4+
that (1) fetches the newest Claude login code for a roster account from the Postmark Inbound
5+
Messages API, and (2) saves/switches per-account `~/.claude/.credentials.json` sessions so you
6+
can hop accounts when one hits its quota. Credentials come from the `secret` CLI only — never
7+
in argv, logs, or process memory.
8+
9+
> 📍 SSOT pointers (this file = entry point + work rules only):
10+
> · **structure/design → [ARCHITECTURE.json](ARCHITECTURE.json)** — the module/flow tree is the single SSOT
11+
> · history → [CHANGELOG.md](CHANGELOG.md) (append · top entry = release notes)
12+
> · user guide → [README.md](README.md) (install + usage)
13+
14+
## Project
15+
hexa is a COMPILED language (`hexa run` = compile-then-exec, `hexa build` = AOT native binary).
16+
`quota` has no native HTTP/JSON lib, so all I/O is done by shelling out via `exec("…").trim()` +
17+
the `json_parse` builtin — the ecosystem-standard pattern. Distributed via `hx install quota`
18+
(clone + shim `cli/quota.hexa`) and as per-target native binaries attached to GitHub releases.
19+
20+
## Tree
21+
```
22+
quota/
23+
├─ cli/quota.hexa — the whole CLI (entry · dispatch + code/inbox/list + session vault)
24+
├─ tests/smoke.hexa — self-contained smoke (version/help/unknown · no secret/network)
25+
├─ hexa.toml — package manifest (hx install target · [package].entry = cli/quota.hexa)
26+
└─ .github/ — CI (parse + smoke) · release (hx-install smoke → GitHub Release + binaries)
27+
```
28+
29+
## Roster & secrets (data, not code)
30+
- Postmark server token: `secret get postmark.dancinlab_server_token` (curl `-K`, never in argv).
31+
- Account roster: derived from `secret` keys named `claude<n>.email` (n = 1..N). No account
32+
literals in code — change the roster by changing the secret store.
33+
- OTP sender domain (`anthropic.com`) is a SOFT preference, not a hard filter: each account has a
34+
dedicated mailbox, so any 6–8 digit code addressed to it is treated as its login code.
35+
36+
## Work rules (this repo)
37+
- do: any CLI change (new/renamed verb·flag·default) → update `cli/quota.hexa` help + `README.md`
38+
usage + `CHANGELOG.md` + `ARCHITECTURE.json` in lockstep (`help-lockstep` · `cycle-docs-pr`).
39+
- do: verify locally before pushing — `hexa parse cli/quota.hexa` + `hexa run tests/smoke.hexa`
40+
(exit 0). Heavy `hexa build` (native) is guard-blocked on the dev mac (freeze-defense) → let CI
41+
runners build the release binaries.
42+
- do: bump `hexa.toml` version + tag `vX.Y.Z` on verified main for a release (`release-tag-ci`);
43+
the release workflow gates the GitHub Release on a fresh `hx install` smoke (ubuntu + macos).
44+
- dont: put any credential value in code/argv/logs (`git-safety`) · hardcode account emails
45+
(roster is secret-store data) · claim hexa is "interpreted" (it is compiled).
46+
47+
## Gotchas
48+
- Entry MUST be `fn main()` with NO args param — `fn main(args)` is stale and won't compile on
49+
the current toolchain; get argv via the `args()` builtin, then slice past the `.hexa` path.
50+
- int→string is `str(x)` (not `.to_str()`); JSON via `json_parse()` + `j["key"]` +
51+
`map_contains_key`; there is no `+=` — reassign explicitly (`x = x + 1`).
52+
- `quota` collides with the system `/usr/bin/quota` (disk quota). `hx install` shims to
53+
`~/.hx/bin`, which must precede `/usr/bin` on PATH for ours to win.

0 commit comments

Comments
 (0)