tokenmaxxing 0.11.0 → 0.12.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/README.md +1 -0
- package/package.json +1 -1
- package/src/cli/status.ts +5 -1
- package/src/cli/watch.ts +57 -0
- package/src/main.ts +3 -0
package/README.md
CHANGED
|
@@ -40,6 +40,7 @@ claude # use claude as always
|
|
|
40
40
|
| `tokenmaxxing ls` | list pooled accounts |
|
|
41
41
|
| `tokenmaxxing status` | accounts with 5h / weekly usage bars, active + exhausted-until-reset |
|
|
42
42
|
| `tokenmaxxing status --force` | additionally ping every account (one tiny haiku request each) so all 5h session timers start now, then sample fresh |
|
|
43
|
+
| `tokenmaxxing watch [seconds]` | live status: re-render every N seconds (default 120, floor 30; never pings) |
|
|
43
44
|
| `tokenmaxxing doctor` | verify the supervisor + settings entries survived |
|
|
44
45
|
| `tokenmaxxing rename <sel> <label>` · `rm <sel>` | manage the pool |
|
|
45
46
|
| `tokenmaxxing uninstall` | remove supervisor + settings entries (accounts/credentials kept) |
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "tokenmaxxing",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.12.0",
|
|
4
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
6
|
"license": "MIT",
|
package/src/cli/status.ts
CHANGED
|
@@ -22,7 +22,10 @@ import { bar, c, fmtAgo, fmtReset } from "./render.ts";
|
|
|
22
22
|
import type { FullUsage } from "../lib/usage.ts";
|
|
23
23
|
import type { UsageWindow } from "../lib/types.ts";
|
|
24
24
|
|
|
25
|
-
|
|
25
|
+
/** `preRender` runs after sampling, right before the first output line: `watch`
|
|
26
|
+
* keeps the previous frame on screen through the multi-second sample and
|
|
27
|
+
* clears only when the fresh frame is ready to paint (watch(1) semantics). */
|
|
28
|
+
export async function cmdStatus(force = false, preRender?: () => void): Promise<number> {
|
|
26
29
|
let idx = loadAccounts();
|
|
27
30
|
const cfg = loadConfig();
|
|
28
31
|
const now = Date.now();
|
|
@@ -89,6 +92,7 @@ export async function cmdStatus(force = false): Promise<number> {
|
|
|
89
92
|
saveAccounts(idx);
|
|
90
93
|
});
|
|
91
94
|
|
|
95
|
+
preRender?.();
|
|
92
96
|
console.log(c.dim(`threshold 5h ${cfg.thresholds.session}% · week ${cfg.thresholds.weekly}% · ${idx.accounts.length} account(s)`));
|
|
93
97
|
console.log();
|
|
94
98
|
|
package/src/cli/watch.ts
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
// `tokenmaxxing watch [seconds]`: native live status (issue #3) - re-render
|
|
2
|
+
// `status` on an interval instead of `watch -n 120 'tokenmaxxing status'`.
|
|
3
|
+
// Plain `status` per tick, never `--force`: a ping meters real quota and starts
|
|
4
|
+
// 5h session windows, so watching must stay free; each refresh costs only the
|
|
5
|
+
// parked accounts' `/usage` probes the one-shot status already does.
|
|
6
|
+
|
|
7
|
+
import { delay } from "es-toolkit";
|
|
8
|
+
import { loadAccounts } from "../lib/state.ts";
|
|
9
|
+
import { cmdStatus } from "./status.ts";
|
|
10
|
+
import { c } from "./render.ts";
|
|
11
|
+
|
|
12
|
+
const DEFAULT_INTERVAL_S = 120;
|
|
13
|
+
/** Floor: each tick spawns a `/usage` probe per parked account (seconds each,
|
|
14
|
+
* under the flock); anything faster than this just queues probes. */
|
|
15
|
+
const MIN_INTERVAL_S = 30;
|
|
16
|
+
/** Cap: past ~24.8 days the setTimeout ms overflow collapses the sleep to ~1ms
|
|
17
|
+
* and the loop runs hot; a day is already beyond any sane watch cadence. */
|
|
18
|
+
const MAX_INTERVAL_S = 86_400;
|
|
19
|
+
|
|
20
|
+
/** Seconds between refreshes, clamped; null when the argument is not a positive number. */
|
|
21
|
+
export function resolveWatchInterval(arg?: string): number | null {
|
|
22
|
+
if (arg === undefined) return DEFAULT_INTERVAL_S;
|
|
23
|
+
const n = Number(arg);
|
|
24
|
+
if (!Number.isFinite(n) || n <= 0) return null;
|
|
25
|
+
return Math.min(Math.max(n, MIN_INTERVAL_S), MAX_INTERVAL_S);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/** Home + clear screen + clear scrollback. Written only once the fresh frame is
|
|
29
|
+
* ready to paint (cmdStatus preRender), so the previous frame stays readable
|
|
30
|
+
* through the multi-second sample instead of blanking - watch(1) semantics. */
|
|
31
|
+
const CLEAR = "\x1b[H\x1b[2J\x1b[3J";
|
|
32
|
+
|
|
33
|
+
export async function cmdWatch(intervalArg?: string): Promise<number> {
|
|
34
|
+
const intervalS = resolveWatchInterval(intervalArg);
|
|
35
|
+
if (intervalS === null) {
|
|
36
|
+
console.error(c.red(`watch interval must be a positive number of seconds, got: ${intervalArg}`));
|
|
37
|
+
return 2;
|
|
38
|
+
}
|
|
39
|
+
if (loadAccounts().accounts.length === 0) return cmdStatus();
|
|
40
|
+
|
|
41
|
+
const paintHeader = () => {
|
|
42
|
+
process.stdout.write(process.stdout.isTTY ? CLEAR : "\n");
|
|
43
|
+
console.log(c.dim(`watch · every ${intervalS}s · ${new Date().toLocaleTimeString()} · ctrl-c to quit`));
|
|
44
|
+
};
|
|
45
|
+
while (true) {
|
|
46
|
+
// One failed tick (a mid-write ~/.claude.json read, a transient probe or
|
|
47
|
+
// lock error) must not kill an hours-long monitor: report it and keep the
|
|
48
|
+
// cadence, exactly like watch(1) showing a failing command's output.
|
|
49
|
+
try {
|
|
50
|
+
await cmdStatus(false, paintHeader);
|
|
51
|
+
} catch (e) {
|
|
52
|
+
paintHeader();
|
|
53
|
+
console.error(c.red(`status failed this tick: ${String((e as Error).message ?? e)}`));
|
|
54
|
+
}
|
|
55
|
+
await delay(intervalS * 1000);
|
|
56
|
+
}
|
|
57
|
+
}
|
package/src/main.ts
CHANGED
|
@@ -12,6 +12,7 @@ import { cmdInit } from "./cli/init.ts";
|
|
|
12
12
|
import { cmdAdd } from "./cli/add.ts";
|
|
13
13
|
import { cmdLs } from "./cli/ls.ts";
|
|
14
14
|
import { cmdStatus } from "./cli/status.ts";
|
|
15
|
+
import { cmdWatch } from "./cli/watch.ts";
|
|
15
16
|
import { cmdDoctor } from "./cli/doctor.ts";
|
|
16
17
|
import { cmdRm } from "./cli/rm.ts";
|
|
17
18
|
import { cmdRename } from "./cli/rename.ts";
|
|
@@ -31,6 +32,7 @@ function printHelp(): void {
|
|
|
31
32
|
${c.cyan("tokenmaxxing ls")} list pooled accounts
|
|
32
33
|
${c.cyan("tokenmaxxing status")} accounts with 5h / weekly / per-model usage bars
|
|
33
34
|
${c.cyan("tokenmaxxing status --force")} ping every account (one tiny haiku request each) so all 5h session timers start now, then sample fresh; ${c.cyan("xx --force")} works too
|
|
35
|
+
${c.cyan("tokenmaxxing watch")} [seconds] live status: re-render every N seconds (default 120, never pings)
|
|
34
36
|
${c.cyan("tokenmaxxing doctor")} verify the install is intact
|
|
35
37
|
${c.cyan("tokenmaxxing rename")} <sel> <label>
|
|
36
38
|
${c.cyan("tokenmaxxing rm")} <sel>
|
|
@@ -65,6 +67,7 @@ async function main(): Promise<number> {
|
|
|
65
67
|
case "add": return cmdAdd();
|
|
66
68
|
case "ls": return cmdLs();
|
|
67
69
|
case "status": return cmdStatus(args.includes("--force"));
|
|
70
|
+
case "watch": return cmdWatch(args[1]);
|
|
68
71
|
case "doctor": return cmdDoctor();
|
|
69
72
|
case "rm": return cmdRm(args[1]);
|
|
70
73
|
case "rename": return cmdRename(args[1], args[2]);
|