clauddy 1.21.2 → 1.22.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 -1
- package/auth.js +29 -1
- package/main.js +23 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -46,7 +46,7 @@ Knowing you're at **82%** with **1h 12m** left on the window still leaves you do
|
|
|
46
46
|
- **`~35m left at this pace`** (in coral) — you'd run out before the window resets. Ease off, or wrap up.
|
|
47
47
|
- **`resets before you run out`** — the reset gets there first. Carry on.
|
|
48
48
|
|
|
49
|
-
The slope is fitted over your **session tokens** rather than the account %. The % is the number you care about, but it arrives as a whole number every
|
|
49
|
+
The slope is fitted over your **session tokens** rather than the account %. The % is the number you care about, but it arrives as a whole number every few minutes — over a short window the whole signal is a single `16 → 17` step, which throws the fitted pace off by multiples. Local-log tokens step too — one jump per assistant turn — but in increments some 10–20× finer, so the slope is far steadier; the account % then anchors it, converting tokens into % and re-calibrating on every poll.
|
|
50
50
|
|
|
51
51
|
It reads your **recent** pace, not the session average: go quiet for a few minutes and the projection eases off, which is the point.
|
|
52
52
|
|
package/auth.js
CHANGED
|
@@ -211,6 +211,24 @@ function scopedWeeks(j) {
|
|
|
211
211
|
return out
|
|
212
212
|
}
|
|
213
213
|
|
|
214
|
+
// What the endpoint says its own budget is. Undocumented, so we only ever read
|
|
215
|
+
// it — nothing depends on it yet. Knowing the real numbers is what tells us how
|
|
216
|
+
// close the activity poll's floor can get.
|
|
217
|
+
let rateLimit = null
|
|
218
|
+
function readRateLimit(res) {
|
|
219
|
+
const out = {}
|
|
220
|
+
try {
|
|
221
|
+
for (const [k, v] of res.headers) {
|
|
222
|
+
if (k.startsWith('anthropic-ratelimit-')) out[k.slice(20)] = v
|
|
223
|
+
}
|
|
224
|
+
} catch {
|
|
225
|
+
return // no headers to read — nothing here is worth failing a fetch over
|
|
226
|
+
}
|
|
227
|
+
if (!Object.keys(out).length) return
|
|
228
|
+
if (JSON.stringify(out) !== JSON.stringify(rateLimit)) console.log('usage rate limit', out)
|
|
229
|
+
rateLimit = out
|
|
230
|
+
}
|
|
231
|
+
|
|
214
232
|
// Step 3: fetch the authoritative usage
|
|
215
233
|
async function fetchUsage() {
|
|
216
234
|
const token = await validToken()
|
|
@@ -228,6 +246,7 @@ async function fetchUsage() {
|
|
|
228
246
|
status: res.status,
|
|
229
247
|
})
|
|
230
248
|
}
|
|
249
|
+
readRateLimit(res)
|
|
231
250
|
const j = await res.json()
|
|
232
251
|
return {
|
|
233
252
|
session: win(j.five_hour) || { pct: 0, resetMs: null },
|
|
@@ -261,4 +280,13 @@ async function fetchProfile() {
|
|
|
261
280
|
return profile
|
|
262
281
|
}
|
|
263
282
|
|
|
264
|
-
module.exports = {
|
|
283
|
+
module.exports = {
|
|
284
|
+
rateLimit: () => rateLimit,
|
|
285
|
+
begin,
|
|
286
|
+
complete,
|
|
287
|
+
fetchUsage,
|
|
288
|
+
fetchProfile,
|
|
289
|
+
clear,
|
|
290
|
+
isConnected,
|
|
291
|
+
setDataDir,
|
|
292
|
+
}
|
package/main.js
CHANGED
|
@@ -234,6 +234,7 @@ function applyAccount(acc) {
|
|
|
234
234
|
usage.setClaudeDir(acc.claudeDir)
|
|
235
235
|
armed = new Set()
|
|
236
236
|
lastSessionPct = null
|
|
237
|
+
lastSeenTokens = null // the new account's first read is a baseline, not a spend
|
|
237
238
|
loadAlertState()
|
|
238
239
|
}
|
|
239
240
|
|
|
@@ -402,6 +403,7 @@ function createWindow() {
|
|
|
402
403
|
const data = getUsage(config)
|
|
403
404
|
win.webContents.send('usage', data)
|
|
404
405
|
checkAlerts(config, data)
|
|
406
|
+
onLocalActivity(data)
|
|
405
407
|
} catch (err) {
|
|
406
408
|
win.webContents.send('usage-error', String(err))
|
|
407
409
|
}
|
|
@@ -651,11 +653,32 @@ function watchDebug() {
|
|
|
651
653
|
let usageTimer = null
|
|
652
654
|
let usageBackoff = 5 * 60 * 1000
|
|
653
655
|
let authFails = 0 // consecutive 401s — see pollUsage
|
|
656
|
+
let lastPollAt = 0
|
|
657
|
+
|
|
658
|
+
// The authoritative % only moves when tokens are actually spent, and the local
|
|
659
|
+
// logs show that within a tick. Polling off that beats a faster clock: it
|
|
660
|
+
// answers while the user is working and asks for nothing while they are not.
|
|
661
|
+
// The floor is what bounds the cost — never more than one call per 90s, well
|
|
662
|
+
// under what a fixed one-minute poll would spend.
|
|
663
|
+
const POLL_FLOOR_MS = 90 * 1000
|
|
664
|
+
let lastSeenTokens = null
|
|
665
|
+
function onLocalActivity(data) {
|
|
666
|
+
const t = data?.session?.tokens
|
|
667
|
+
if (typeof t !== 'number') return
|
|
668
|
+
const grew = lastSeenTokens != null && t > lastSeenTokens
|
|
669
|
+
lastSeenTokens = t
|
|
670
|
+
if (!grew || !auth.isConnected()) return
|
|
671
|
+
if (Date.now() - lastPollAt < POLL_FLOOR_MS) return
|
|
672
|
+
clearTimeout(usageTimer) // pollUsage schedules the next heartbeat itself
|
|
673
|
+
pollUsage()
|
|
674
|
+
}
|
|
675
|
+
|
|
654
676
|
function scheduleUsagePoll() {
|
|
655
677
|
clearTimeout(usageTimer)
|
|
656
678
|
if (auth.isConnected()) usageTimer = setTimeout(pollUsage, usageBackoff)
|
|
657
679
|
}
|
|
658
680
|
async function pollUsage() {
|
|
681
|
+
lastPollAt = Date.now()
|
|
659
682
|
try {
|
|
660
683
|
const u = await auth.fetchUsage()
|
|
661
684
|
usageBackoff = 5 * 60 * 1000
|