tokenmaxxing 0.3.0 → 0.3.1
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/package.json +1 -1
- package/src/lib/usage.ts +29 -11
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "tokenmaxxing",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.1",
|
|
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/lib/usage.ts
CHANGED
|
@@ -86,12 +86,16 @@ function zonedWallToEpoch(y: number, mon: number, day: number, hour: number, min
|
|
|
86
86
|
|
|
87
87
|
/**
|
|
88
88
|
* Parse a `/usage` reset clock like `Jul 11 at 12pm (Asia/Seoul)` or
|
|
89
|
-
* `Jul
|
|
90
|
-
*
|
|
91
|
-
*
|
|
89
|
+
* `Jul 10, 3:30pm (Asia/Seoul)` to epoch ms. The day-time glue is not stable
|
|
90
|
+
* across claude installs (observed on 2.1.206: ` at ` on macOS, `, ` on Linux);
|
|
91
|
+
* exactly those two glues are accepted, same-line only, so a third drift shows
|
|
92
|
+
* up as an unparsed clock (and the usage.reset_clock_unparsed log) instead of a
|
|
93
|
+
* guessed instant. The text carries no year, so we pick the year whose resulting
|
|
94
|
+
* instant is nearest `now` (resets are always days away, so the correct year
|
|
95
|
+
* wins by ~360 days). Returns null if unparseable.
|
|
92
96
|
*/
|
|
93
97
|
export function parseResetClock(clock: string, now = Date.now()): number | null {
|
|
94
|
-
const m = clock.match(/\b([A-Za-z]{3,9})\s+(\d{1,2})\
|
|
98
|
+
const m = clock.match(/\b([A-Za-z]{3,9})\s+(\d{1,2})(?:[^\S\n]+at[^\S\n]+|,[^\S\n]*)(\d{1,2})(?::(\d{2}))?\s*([ap])m\s*\(([^)]+)\)/i);
|
|
95
99
|
if (!m) return null;
|
|
96
100
|
const mon = MONTHS[m[1]!.slice(0, 3).toLowerCase()];
|
|
97
101
|
if (mon === undefined) return null;
|
|
@@ -123,11 +127,15 @@ export function parseResetClock(clock: string, now = Date.now()): number | null
|
|
|
123
127
|
*/
|
|
124
128
|
export function parseUsageTextFull(text: string, now = Date.now()): FullUsage | null {
|
|
125
129
|
if (!text) return null;
|
|
126
|
-
// The reset clock is required in full (month day at h[:mm]am/pm (tz))
|
|
127
|
-
// optional group, so the lazy bridge is forced to find it when
|
|
128
|
-
// group cleanly skips a line that has no clock
|
|
129
|
-
//
|
|
130
|
-
|
|
130
|
+
// The reset clock is required in full (month day[, | at ]h[:mm]am/pm (tz))
|
|
131
|
+
// inside its optional group, so the lazy bridge is forced to find it when
|
|
132
|
+
// present yet the group cleanly skips a line that has no clock. The bridge
|
|
133
|
+
// refuses to cross another "Current" so a dateless clock ("resets Jul 8.")
|
|
134
|
+
// can never steal the NEXT entry's clock or swallow that entry. The day-time
|
|
135
|
+
// glue is not stable across installs (observed on 2.1.206: ` at ` on macOS,
|
|
136
|
+
// `, ` on Linux); exactly those two are accepted, same-line only, so a third
|
|
137
|
+
// drift surfaces in the usage.reset_clock_unparsed log instead of misparsing.
|
|
138
|
+
const re = /current (session|week \(([^)]+)\)):\s*(\d+)\s*%(?:(?:(?!current)[^\n])*?\bresets\s+([A-Z][a-z]{2,8}\s+\d{1,2}(?:[^\S\n]+at[^\S\n]+|,[^\S\n]*)\d{1,2}(?::\d{2})?\s*[ap]m\s*\([^)]+\)))?/gi;
|
|
131
139
|
let session: UsageWindow | null = null;
|
|
132
140
|
let weekAll: UsageWindow | null = null;
|
|
133
141
|
const perModel: Record<string, UsageWindow> = {};
|
|
@@ -190,8 +198,18 @@ async function probeUsageOnce(env: Record<string, string>, now: number): Promise
|
|
|
190
198
|
}
|
|
191
199
|
|
|
192
200
|
const j = z.object({ result: z.string() }).safeParse((() => { try { return JSON.parse(out); } catch { return null; } })());
|
|
193
|
-
const
|
|
194
|
-
|
|
201
|
+
const text = j.success ? j.data.result : out;
|
|
202
|
+
const full = parseUsageTextFull(text, now);
|
|
203
|
+
if (!full) {
|
|
204
|
+
log("usage.probe_unparsed", { sample: text.trim().slice(0, 200) });
|
|
205
|
+
} else {
|
|
206
|
+
const clockLine = text.split("\n").find((l) => /^current /i.test(l) && /\bresets\b/i.test(l));
|
|
207
|
+
if (clockLine && [full.session, full.weekAll, ...Object.values(full.perModel)].every((w) => w.resetsAt === null)) {
|
|
208
|
+
// Percentages parsed but every reset clock was dropped: the clock format
|
|
209
|
+
// drifted again. Log the line so the next drift is visible in the log.
|
|
210
|
+
log("usage.reset_clock_unparsed", { sample: clockLine.slice(0, 120) });
|
|
211
|
+
}
|
|
212
|
+
}
|
|
195
213
|
return full;
|
|
196
214
|
}
|
|
197
215
|
|