tokenmaxxing 0.1.0 → 0.2.0

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.
package/DESIGN.md ADDED
@@ -0,0 +1,125 @@
1
+ # tokenmaxxing - design
2
+
3
+ Automatic Claude Code account switching. You run `claude` exactly as always; when the active account crosses **95%** usage, tokenmaxxing swaps to a fresh account and - at the next safe turn boundary - **restarts your session resumed on it, automatically**. Works across many concurrent sessions at once.
4
+
5
+ > Scope: **Claude Code only, macOS first.** Codex and other CLIs deferred (see `.memory/cc-codex-auth-mechanics.md`).
6
+ >
7
+ > Status: **implemented** (v0.1.0, 2026-07-09). TypeScript on Bun → single binary; Zod validates every external-boundary payload, JSON config, es-toolkit for utilities, `flock(2)` via `bun:ffi`. All load-bearing external facts were adversarially verified against the `2.1.204` binary + docs (OAuth token endpoint is `platform.claude.com/v1/oauth/token`, client_id `9d1c250a-…`, JSON body). What the acceptance gate actually shows is in §9.
8
+
9
+ ---
10
+
11
+ ## 1. Why there is a thin supervisor (and why that's the whole trick)
12
+
13
+ A **running** `claude` cannot adopt a swapped credential mid-flight - binary-verified: it holds its OAuth token in memory and a 429 (the limit event) does **not** invalidate it, so it never re-reads the keychain on the event we care about. A keychain swap is only picked up by a **fresh** `claude` process.
14
+
15
+ The clean way to exploit that is not to fight the live process but to **replace it at a salvageable moment**: after a turn completes, the conversation is fully written to the transcript JSONL and `claude` is idle at the prompt - killing it there loses nothing, and a `claude --resume <session-id>` comes back cold on the new account and continues exactly where it left off. **This is why we swap at 95%: the 5% headroom is the budget to reach a clean turn boundary and respawn before the account actually hits the wall.**
16
+
17
+ A hook can't do the respawn - when `claude` exits, the shell owns the terminal. So tokenmaxxing installs a **supervisor** (aliased to `claude`) that owns the process lifecycle:
18
+
19
+ ```
20
+ supervisor (you type `claude`) → real claude (in a PTY) → Stop hook
21
+ ▲_______________ relaunch --resume <sid> ______________|
22
+ ```
23
+
24
+ It is a process/PTY manager only - spawn, forward the terminal, wait, restore terminal, relaunch. It never proxies API traffic or handles tokens. Everything else about `claude` is unchanged.
25
+
26
+ ---
27
+
28
+ ## 2. What tokenmaxxing installs
29
+
30
+ - A `claude` **supervisor** on your PATH ahead of the real binary (`~/.config/tokenmaxxing/bin/claude`), or a shell function - you invoke it identically.
31
+ - Three `~/.claude/settings.json` entries (merged, preserving anything you already have): a transparent `statusLine` shim, a `Stop` hook, a `SessionStart` hook.
32
+ - **`~/.config/tokenmaxxing/`** - the single home for config and state:
33
+ - `config.json` - threshold, account order/policy.
34
+ - `accounts.json` - non-secret index `{email, organizationUuid, accountUuid, lastUsage, resetsAt, needs_reauth}`.
35
+ - `usage.json` - live usage, written by the statusLine shim.
36
+ - `respawn/<session-id>` - per-session respawn markers (the hook→supervisor signal).
37
+ - `bin/claude` - the supervisor.
38
+ - Per-account **credentials** follow the platform's Claude Code store: macOS = login-keychain items `tokenmaxxing-cred-<accountUuid[:8]>` (never plaintext on disk); Linux = 0600 files `creds/tokenmaxxing-cred-<accountUuid[:8]>.json` (the same plaintext model claude itself uses - its Linux build has no keyring path at all, binary-verified 2.1.205). One `credstore` facade dispatches on a `{kind: keychain|file}` target; call sites never branch on platform.
39
+
40
+ No background daemon - it's event-driven (statusline pushes usage; hooks + supervisor react). The `claude` binary and `~/.claude` layout are untouched.
41
+
42
+ ---
43
+
44
+ ## 3. How a switch happens
45
+
46
+ ### 3.1 Usage feed (free, push-based)
47
+ The Stop hook's stdin has no usage data, but the **statusLine does** (`rate_limits.{five_hour,seven_day}.{used_percentage,resets_at}`, after every turn, 300ms debounce, zero cost). tokenmaxxing's statusLine shim tees that to `usage.json` (write-on-change, O(ms)) and passes your real statusline through unchanged. Cold-start fallback if `usage.json` is absent: `TOKENMAXXING_PROBE=1 claude -p '/usage'`, with `[ -n "$TOKENMAXXING_PROBE" ] && exit 0` as the hook's first line to stop the nested process recursing (hooks fire in `-p` too). The probe scrubs every ambient credential override claude reads before the keychain (`CLAUDE_CODE_OAUTH_TOKEN`, `CLAUDE_SECURESTORAGE_CONFIG_DIR`, etc.) so it can only meter the credential in the keychain item, and retries the transient empty-footer case (claude prints local stats with no percentages when its own usage fetch throttles).
48
+
49
+ ### 3.2 Detect + swap + signal (Stop hook, per turn)
50
+ 1. Read `usage.json`; `exit 0` fast if both windows `< 95%` (metered per `organizationUuid`).
51
+ 2. Else take a `flock` on `~/.config/tokenmaxxing/lock`, re-check under it (parallel sessions race - first winner already swapped), pick the best parked account (lowest 7-day, not rate-limited, soonest `resets_at` tiebreak), and **swap the credential** (§3.4).
52
+ 3. Write `respawn/<session_id>` (atomic temp+rename) and `SIGTERM` the parent `claude` (`kill -TERM $PPID`). The turn is already committed, so this is a clean stop.
53
+
54
+ ### 3.3 Respawn (supervisor)
55
+ The supervisor's `claude` call returns; it sees `respawn/<sid>`, deletes it, resets the terminal, prints `↻ switched to <account>`, and relaunches `claude --resume <sid>`. The resumed process reads the keychain cold → runs on the new account, same conversation. The `SessionStart` hook (source `resume`) re-checks the account before the first turn as a backstop.
56
+
57
+ ### 3.4 Swap sequence (under the lock)
58
+ 1. **Harvest the live credential into its TRUE owner's backup** - read the current `Claude Code-credentials` blob and resolve which account it actually belongs to via the roles endpoint (`GET /api/oauth/claude_cli/roles`), NOT the `accounts.json` active label. The label drifts from the live blob (a kill mid-swap, a manual `/login`), and harvesting by label once overwrote another account's backup and destroyed its only credential. Mandatory anyway: Claude rotates the refresh token in place, so older backups are dead. Refuse the swap if the live credential belongs to no pooled account.
59
+ 2. **Refresh B** - OAuth refresh-grant with B's parked refresh token → fresh access token; persist the rotated refresh token. On `invalid_grant`, mark B `needs_reauth`, notify, try the next account.
60
+ 3. **Install B** - `security add-generic-password -U … 'Claude Code-credentials' …` with B's fresh (non-expired) `claudeAiOauth` JSON.
61
+ 4. **Swap identity + mark B active** - atomically rewrite only the `oauthAccount` object in `~/.claude.json` (temp+rename) to B's, and write `activeAccountUuid = B` in the SAME critical section, so a crash can't leave the installed credential and the active label pointing at different accounts.
62
+ 5. Do steps 1, 3, 4 inside Claude's own `~/.claude.lock` so the writes can't collide with a token refresh.
63
+
64
+ ### 3.5 Multiple concurrent sessions
65
+ Each terminal ran the supervisor, so each has its own child `claude`, its own `--session-id`, and its own respawn marker. When the shared account hits 95%, the first Stop hook to win the `flock` performs the one swap; **every** session's Stop hook writes its own respawn marker and SIGTERMs its own `claude`; **every** supervisor independently relaunches `claude --resume <its-own-sid>`. All of them come back on the new account, each continuing its own conversation. (They share one credential, so they always move together - consistent with "one current account, many windows.")
66
+
67
+ ---
68
+
69
+ ## 4. Onboarding (no `adopt`)
70
+
71
+ - **`tokenmaxxing init` imports the account you're already on - automatically, no prompts, no re-login.** It reads the live `Claude Code-credentials` keychain blob plus the `oauthAccount` object in `~/.claude.json` (email, `organizationUuid`, `accountUuid`, plan tier) and writes them as **account #1** into tokenmaxxing's store (`tokenmaxxing-cred-<accountUuid[:8]>` + an `accounts.json` index entry). Nothing about your current session changes - that account stays active; it's now just also a registered pool member. After this one command you already have a working (single-account) pool. `init` also installs the supervisor + the three settings entries.
72
+ - If the current auth is API-key mode (`ANTHROPIC_API_KEY`/`apiKeyHelper`) rather than a subscription `/login`, there's no quota-poolable subscription credential to import - `init` says so and points you to `/login` first (per-token API billing isn't what tokenmaxxing pools).
73
+ - **`tokenmaxxing add`** - registers *additional* accounts: logs one in via a throwaway `CLAUDE_CONFIG_DIR=~/.config/tokenmaxxing/onboard` (your primary login untouched), harvests it into the store, deletes the temp dir + its namespaced item. This is the **only** time `CLAUDE_CONFIG_DIR` is ever used.
74
+ - Both commands exercise `security` reads/writes interactively (where a macOS keychain ACL prompt is acceptable), so the first access never happens cold inside a headless hook.
75
+
76
+ ---
77
+
78
+ ## 5. Rotation policy
79
+ Trigger at `five_hour >= 95%` OR `seven_day >= 95%`, per org. "Exhausted" is a **timestamped state** (`resets_at`), not a flag - an account is a candidate again after it resets. Optional projected threshold (`95 − EMA(per-turn Δ%)`) so a single large turn can't blow past 100% before the next Stop hook.
80
+
81
+ **Model-aware trigger.** Claude subscriptions also enforce a **per-model weekly cap** - the capable models (Fable, Opus) have a tighter weekly limit that binds *before* the aggregate (e.g. 80% week-Fable at only 50% week-all-models). This cap isn't in statusLine stdin, so when the active model is in `policy.switchModels` we read it from `claude -p '/usage'` (free, 0 tokens, TTL-cached) and add `week(<activeModel>) >= threshold` to the trigger. A Fable session switches on the Fable cap; a Sonnet session rides the aggregate.
82
+
83
+ ---
84
+
85
+ ## 6. Honest papercuts
86
+ - **Respawn hiccup.** At the swap turn you see `claude` restart (~1–2s) and resume. Anything you typed in the split second before respawn is lost - mitigate by respawning fast and showing a clear "switching" state; the supervisor resets terminal mode so nothing is left garbled.
87
+ - **One cold turn.** Prompt cache is org-scoped: the first turn after resuming on B re-uploads context once (bigger on long transcripts).
88
+ - **Single-turn overshoot.** If one turn jumps from <95% straight past the wall, that turn can end rate-limited before the Stop hook swaps; the respawn then still recovers it. Projected threshold reduces this.
89
+ - **Shared blast radius.** All default-profile sessions share one keychain item, so a swap moves them all (each via its own respawn). The `flock` + re-check is mandatory or racing hooks burn two accounts at once.
90
+ - **Refresh-token rotation / parked-token rot.** Step 1 re-harvest is mandatory; a parked refresh token can be invalidated by logging in elsewhere → picker must catch `invalid_grant`, mark `needs_reauth`, fall through.
91
+ - **statusLine fragility.** The shim is the most visible surface - a bug flickers or breaks your real status line. Keep it O(ms), write-on-change.
92
+ - **Keychain blob size & ps-safety.** The live `Claude Code-credentials` item also holds per-MCP-server OAuth state, so it can exceed `security -i`'s ~4KB interactive line buffer (verified on a real machine - a 4.3KB blob truncated). tokenmaxxing therefore stores parked backups as **`claudeAiOauth`-only** (small → always the ps-safe stdin write) and, on a swap, **merges** the fresh `claudeAiOauth` into the *current* live blob so MCP tokens survive the switch - using the argv write path (secret briefly visible in `ps` on your own machine) only for that one oversized live write.
93
+ - **settings.json is user-owned.** Install by merge; ship `tokenmaxxing doctor` to verify the supervisor + 3 entries survive a `/config` edit or update.
94
+
95
+ ---
96
+
97
+ ## 7. Scope
98
+ **v1:** `tokenmaxxing init` / `add` / `ls` / `status` / `doctor`; the supervisor; statusLine shim + Stop/SessionStart hooks; 95% swap with `flock` + reset-aware picker; platform credential store (macOS keychain / Linux 0600 files, one facade); auto-respawn across concurrent sessions. macOS + Linux.
99
+
100
+ **v2:** projected-threshold pre-emption; a `UserPromptSubmit` guard that respawns *before* a turn starts when already over; Windows.
101
+
102
+ **Later:** Codex as a second pool; tool-agnostic picker.
103
+
104
+ **Non-goals:** an API/MITM proxy; reimplementing OAuth beyond the single refresh-grant call in the swap.
105
+
106
+ ---
107
+
108
+ ## 8. Stack
109
+ TypeScript on Bun, `bun build --compile` → the `claude` supervisor, the statusLine shim, and the hook as fast-starting single binaries (the Stop path runs every turn, so start-up latency matters). npm name `tokenmaxxing` owned. The supervisor needs a real PTY layer (spawn claude on a pty, forward resize/signals, restore mode between runs).
110
+
111
+ ---
112
+
113
+ ## 9. What the acceptance gate showed (2026-07-09)
114
+
115
+ Unit suite: **32 pass**. Hermetic swap+concurrency+model-aware E2E: **all pass**. CLI init/doctor/uninstall through the compiled binary: **pass**.
116
+
117
+ 1. **Transcript continuity across a process boundary - ✅ proven on real claude/real account.** `claude --session-id X -p …` committed a clean 12-line transcript; `claude --resume X -p …` recalled the earlier turn's codeword. The supervisor's kill→restore-termios→respawn→`--resume` loop is proven with a mock claude. The one step not run live is the abrupt SIGTERM of an *idle interactive* real claude (structurally safe - the transcript is fully committed+fsynced before idle and nothing writes while idle).
118
+ 2. **Terminal restoration - ✅ mechanism proven.** The supervisor saves `stty -g` and restores it between kill and respawn; the path executes in the mock-supervisor run. Visual raw-mode/​resize confirmation wants a live interactive terminal.
119
+ 3. **SessionStart swap - ✅ swap logic proven; ⚠️ "adopted by first turn" needs a 2nd real account.** The hook's decision+swap path is covered by the E2E; the claude-internal timing (SessionStart before first `getToken`) needs a second subscription to observe end-to-end.
120
+ 4. **Keychain writes from a headless hook - ✅ proven.** The swap E2E's 4 concurrent subprocesses each wrote the live item via `security -i` (secret on stdin, never argv) with no prompt/hang; `init` parked backups headlessly too. (Residual: the live `Claude Code-credentials` ACL for the *fresh resumed claude* is mitigated by onboarding touching `security` interactively.)
121
+ 5. **Concurrent flocked swap - ✅ proven.** 4 concurrent processes racing the trigger → exactly **one** `flock`ed swap, one OAuth refresh, all end on B. Per-supervisor respawn is proven with the mock; N *real* supervisors respawning together needs real accounts + PTYs.
122
+ 6. **Model-aware trigger - ✅ proven.** Fable at 96% per-model cap (aggregate only 30/50) → swaps; Sonnet at 96% → no swap.
123
+
124
+ Owed before fully trusting in the wild: the live interactive-PTY SIGTERM/terminal test, and the two items that require a **second real subscription account** (SessionStart adoption, N real supervisors).
125
+ ```
package/README.md ADDED
@@ -0,0 +1,90 @@
1
+ # tokenmaxxing
2
+
3
+ **Automatic Claude Code account switching.** Run `claude` exactly as you always do; when the active account crosses its usage limit, tokenmaxxing swaps to a fresh account and - at the next safe turn boundary - restarts your session *resumed on it*, automatically. Works across many concurrent sessions.
4
+
5
+ > **Scope:** Claude Code only, macOS (Apple Silicon) and Linux (x64/arm64). It pools **subscription** accounts (Pro/Max), not API keys.
6
+
7
+ ```
8
+ $ claude
9
+ …you work normally…
10
+ ↻ tokenmaxxing: switched to work@acme.com - resuming…
11
+ …same conversation, fresh quota…
12
+ ```
13
+
14
+ ## Why
15
+
16
+ A running `claude` holds its OAuth token in memory and a 429 does **not** make it re-read the credential store - so you can't hot-swap a live session. tokenmaxxing instead swaps the credential and **respawns** `claude --resume <id>` at a committed turn boundary (the transcript is already on disk, so nothing is lost). A thin `claude` supervisor on your PATH owns that respawn; everything else about `claude` is unchanged - all flags, MCP, hooks, and skills pass through.
17
+
18
+ ## Install
19
+
20
+ Requires [Bun](https://bun.sh) and Claude Code, on macOS or Linux.
21
+
22
+ ```sh
23
+ git clone https://github.com/anaclumos/tokenmaxxing && cd tokenmaxxing
24
+ bun install
25
+ bun run build # → dist/tokenmaxxing (single binary)
26
+ ./dist/tokenmaxxing init
27
+ ```
28
+
29
+ `init` imports the account you're already on, installs the `claude` supervisor + three `settings.json` entries (a statusLine shim, a Stop hook, a SessionStart hook), and adds the supervisor's bin dir to PATH in your shell rc (idempotent; it must sit ahead of the real `claude` to intercept it). Restart your shell, then add more accounts and go:
30
+
31
+ ```sh
32
+ tokenmaxxing add # logs one in, in isolation, and pools it
33
+ claude # use claude as always
34
+ ```
35
+
36
+ ## Commands
37
+
38
+ | command | what it does |
39
+ |---|---|
40
+ | `tokenmaxxing init` | import the current account + install supervisor & hooks |
41
+ | `tokenmaxxing add` | register an additional account (isolated login, harvested into the pool) |
42
+ | `tokenmaxxing ls` | list pooled accounts |
43
+ | `tokenmaxxing status` | accounts with 5h / weekly usage bars, active + exhausted-until-reset |
44
+ | `tokenmaxxing doctor` | verify the supervisor + settings entries survived |
45
+ | `tokenmaxxing rename <sel> <label>` · `rm <sel>` | manage the pool |
46
+ | `tokenmaxxing uninstall` | remove supervisor + settings entries (accounts/credentials kept) |
47
+
48
+ ## How switching decides
49
+
50
+ Switching triggers at **95%** (configurable) on any of:
51
+
52
+ - **Session** (5-hour) or **week (all models)** - the aggregate windows, fed free/push-based by a statusLine shim.
53
+ - **Per-model weekly cap** - the capable models (Fable, Opus) have their own tighter weekly limit that binds *before* the aggregate. tokenmaxxing reads it from `claude -p '/usage'` (free, 0 tokens, TTL-cached) whenever the active model is one of `policy.switchModels`, so a Fable session switches on the Fable cap while a Sonnet session rides the aggregate.
54
+
55
+ The 5% headroom is deliberate: it's the budget to reach a clean turn boundary and respawn before the wall.
56
+
57
+ ## Configuration
58
+
59
+ `~/.config/tokenmaxxing/config.json` (every field optional):
60
+
61
+ ```json
62
+ {
63
+ "threshold": 95,
64
+ "policy": {
65
+ "projectionMargin": 0,
66
+ "switchModels": ["fable", "opus"],
67
+ "usagePollTtlMs": 90000
68
+ }
69
+ }
70
+ ```
71
+
72
+ `projectionMargin` subtracts an EMA of per-turn Δ% for pre-emption; `switchModels` names the models whose per-model cap triggers a switch; `usagePollTtlMs` is how long a `/usage` per-model poll stays fresh.
73
+
74
+ State lives entirely in `~/.config/tokenmaxxing/`. Per-account credentials follow the platform's Claude Code store: the login keychain on macOS (`tokenmaxxing-cred-<uuid8>` items, never plaintext on disk), 0600 files under `~/.config/tokenmaxxing/creds/` on Linux (the same plaintext model claude itself uses for `~/.claude/.credentials.json`).
75
+
76
+ ## Honest limitations
77
+
78
+ - **One cold turn.** The first turn after resuming on a new account re-uploads context once (prompt cache is org-scoped).
79
+ - **Respawn hiccup.** At the swap you see `claude` restart (~1–2s); anything typed in that split second is lost.
80
+ - **Shared blast radius.** All default-profile sessions share one live credential, so a swap moves them all together (each respawns its own session). A `flock` + re-check keeps racing hooks from burning two accounts.
81
+ - **Keychain ACL (macOS).** `init`/`add` touch the keychain interactively so the first `security` access isn't cold inside a headless hook.
82
+ - **Plaintext credentials (Linux).** Claude Code itself stores Linux credentials as a 0600 plaintext file; tokenmaxxing's parked copies follow the same model.
83
+
84
+ ## How it's built
85
+
86
+ TypeScript on Bun, compiled to a single binary with `bun build --compile`. [Zod](https://zod.dev) validates every external-boundary payload (credential blobs, hook/statusLine stdin, OAuth responses, config), [es-toolkit](https://es-toolkit.dev) for utilities. The supervisor is process/terminal-only - it never proxies API traffic or touches tokens in flight. Cross-process coordination uses `flock(2)` via `bun:ffi` (macOS has no `flock(1)`; one codepath serves both platforms). Credential I/O goes through one platform-selected store: `security(1)` generic-passwords on macOS, atomic 0600 file writes on Linux.
87
+
88
+ ## License
89
+
90
+ MIT
Binary file
package/package.json CHANGED
@@ -1,19 +1,45 @@
1
1
  {
2
2
  "name": "tokenmaxxing",
3
- "version": "0.1.0",
4
- "description": "tokenmaxxing CLI",
3
+ "version": "0.2.0",
4
+ "description": "Automatic Claude Code account switching — pool multiple accounts and hot-swap when quota fills, resuming your session on the fresh account.",
5
5
  "type": "module",
6
+ "license": "MIT",
7
+ "author": "anaclumos",
8
+ "homepage": "https://github.com/anaclumos/tokenmaxxing",
9
+ "repository": {
10
+ "type": "git",
11
+ "url": "git+https://github.com/anaclumos/tokenmaxxing.git"
12
+ },
6
13
  "bin": {
7
- "tokenmaxxing": "./dist/index.js"
14
+ "tokenmaxxing": "./dist/tokenmaxxing"
8
15
  },
9
16
  "files": [
10
- "dist"
17
+ "dist/tokenmaxxing",
18
+ "README.md",
19
+ "DESIGN.md"
11
20
  ],
21
+ "engines": {
22
+ "bun": ">=1.1.0"
23
+ },
24
+ "os": ["darwin", "linux"],
25
+ "cpu": ["arm64", "x64"],
12
26
  "scripts": {
13
- "build": "bun build ./src/index.ts --outdir ./dist --target node",
14
- "dev": "bun run ./src/index.ts"
27
+ "dev": "bun run src/main.ts",
28
+ "build": "bun build --compile --minify --sourcemap src/main.ts --outfile dist/tokenmaxxing",
29
+ "build:dev": "bun build --compile src/main.ts --outfile dist/tokenmaxxing",
30
+ "build:linux-x64": "bun build --compile --minify --sourcemap --target=bun-linux-x64 src/main.ts --outfile dist/tokenmaxxing-linux-x64",
31
+ "build:linux-arm64": "bun build --compile --minify --sourcemap --target=bun-linux-arm64 src/main.ts --outfile dist/tokenmaxxing-linux-arm64",
32
+ "test": "bun test",
33
+ "typecheck": "tsc --noEmit",
34
+ "prepublishOnly": "bun run typecheck && bun run test && bun run build"
15
35
  },
16
36
  "devDependencies": {
17
- "@types/bun": "latest"
37
+ "@types/bun": "latest",
38
+ "typescript": "^5.6.0"
39
+ },
40
+ "dependencies": {
41
+ "es-toolkit": "^1.49.0",
42
+ "ky": "^2.0.2",
43
+ "zod": "^4.4.3"
18
44
  }
19
45
  }
package/dist/index.js DELETED
@@ -1,38 +0,0 @@
1
- #!/usr/bin/env node
2
-
3
- // src/index.ts
4
- var args = process.argv.slice(2);
5
- var command = args[0];
6
- function printHelp() {
7
- console.log(`
8
- tokenmaxxing - CLI
9
-
10
- Usage:
11
- tokenmaxxing <command>
12
-
13
- Commands:
14
- help Show this help message
15
- version Show version
16
-
17
- Options:
18
- --help, -h Show help
19
- --version, -v Show version
20
- `);
21
- }
22
- function printVersion() {
23
- console.log("tokenmaxxing v0.1.0");
24
- }
25
- switch (command) {
26
- case "version":
27
- case "--version":
28
- case "-v":
29
- printVersion();
30
- break;
31
- case "help":
32
- case "--help":
33
- case "-h":
34
- case undefined:
35
- default:
36
- printHelp();
37
- break;
38
- }