@goodandready/dsh-subscriptions 0.2.5 → 0.2.6
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/lib/accounts.js +18 -1
- package/lib/blob.js +4 -0
- package/lib/client.js +19 -9
- package/lib/code-assist.js +4 -2
- package/lib/index.js +1 -0
- package/lib/usage.js +53 -1
- package/lib/vendors/claude.js +4 -2
- package/lib/vendors/codex.js +4 -2
- package/lib/vendors/grok.js +4 -2
- package/package.json +1 -1
package/lib/accounts.js
CHANGED
|
@@ -54,6 +54,7 @@ export function createAccountStore({ credentials, getConfig, fetchImpl }) {
|
|
|
54
54
|
const refreshLocks = new Map()
|
|
55
55
|
const refreshFailures = new Map()
|
|
56
56
|
const usage = new Map()
|
|
57
|
+
const windows = new Map()
|
|
57
58
|
const usageFetched = new Map()
|
|
58
59
|
const doFetch = fetchImpl || fetch
|
|
59
60
|
|
|
@@ -107,6 +108,7 @@ export function createAccountStore({ credentials, getConfig, fetchImpl }) {
|
|
|
107
108
|
const blob = parseBlob(await resolveRaw(ref))
|
|
108
109
|
base.label = blob.label || blob.email || ''
|
|
109
110
|
base.email = blob.email || ''
|
|
111
|
+
if (Array.isArray(blob.usage)) { base.usage = blob.usage; base.usageAt = blob.usageAt || 0 }
|
|
110
112
|
if (blob.validationUrl) base.validationUrl = blob.validationUrl
|
|
111
113
|
if (blob.validationMessage) base.validationMessage = blob.validationMessage
|
|
112
114
|
if (blob.accountNotice) base.accountNotice = blob.accountNotice
|
|
@@ -117,6 +119,7 @@ export function createAccountStore({ credentials, getConfig, fetchImpl }) {
|
|
|
117
119
|
...base,
|
|
118
120
|
cooldownUntil: cooldowns.get(ref) || 0,
|
|
119
121
|
quota: quotas.get(ref) || null,
|
|
122
|
+
usage: windows.get(ref) || base.usage || null,
|
|
120
123
|
usagePercent: usage.has(ref) ? usage.get(ref) : null,
|
|
121
124
|
refreshError: refreshFailures.get(ref)?.error || '',
|
|
122
125
|
validationUrl: base.validationUrl || '',
|
|
@@ -194,9 +197,23 @@ export function createAccountStore({ credentials, getConfig, fetchImpl }) {
|
|
|
194
197
|
const blob = await ensureFresh(provider, parseBlob(raw), slot.ref)
|
|
195
198
|
const snap = await vendor.usage(blob, cfg, doFetch)
|
|
196
199
|
usageFetched.set(slot.ref, Date.now())
|
|
197
|
-
if (snap
|
|
200
|
+
if (!snap) continue
|
|
201
|
+
if (Number.isFinite(Number(snap.usedPercent))) {
|
|
198
202
|
usage.set(slot.ref, Number(snap.usedPercent))
|
|
199
203
|
}
|
|
204
|
+
const list = Array.isArray(snap.windows) ? snap.windows : null
|
|
205
|
+
if (list) {
|
|
206
|
+
windows.set(slot.ref, list)
|
|
207
|
+
// persist last known windows so the card survives restarts
|
|
208
|
+
try {
|
|
209
|
+
const prev = Array.isArray(blob.usage) ? JSON.stringify(blob.usage) : ''
|
|
210
|
+
if (prev !== JSON.stringify(list)) {
|
|
211
|
+
await saveBlob(slot.ref, { ...blob, usage: list, usageAt: Date.now() })
|
|
212
|
+
} else if (!blob.usageAt) {
|
|
213
|
+
await saveBlob(slot.ref, { ...blob, usageAt: Date.now() })
|
|
214
|
+
}
|
|
215
|
+
} catch { /* persistence best-effort */ }
|
|
216
|
+
}
|
|
200
217
|
} catch {
|
|
201
218
|
usageFetched.set(slot.ref, Date.now())
|
|
202
219
|
}
|
package/lib/blob.js
CHANGED
|
@@ -16,6 +16,8 @@ export function serializeBlob(obj) {
|
|
|
16
16
|
email: asString(obj && obj.email),
|
|
17
17
|
accountId: asString(obj && obj.accountId),
|
|
18
18
|
projectId: asString(obj && obj.projectId),
|
|
19
|
+
...(Array.isArray(obj && obj.usage) ? { usage: obj.usage } : {}),
|
|
20
|
+
...((obj && obj.usageAt) ? { usageAt: Number(obj.usageAt) } : {}),
|
|
19
21
|
})
|
|
20
22
|
}
|
|
21
23
|
|
|
@@ -30,6 +32,8 @@ export function parseBlob(text) {
|
|
|
30
32
|
email: asString(obj.email),
|
|
31
33
|
accountId: asString(obj.accountId),
|
|
32
34
|
projectId: asString(obj.projectId),
|
|
35
|
+
...(Array.isArray(obj.usage) ? { usage: obj.usage } : {}),
|
|
36
|
+
...(obj.usageAt ? { usageAt: Number(obj.usageAt) } : {}),
|
|
33
37
|
}
|
|
34
38
|
}
|
|
35
39
|
|
package/lib/client.js
CHANGED
|
@@ -301,17 +301,27 @@ window.__ModuleLoader__.load({
|
|
|
301
301
|
t('verifySuffix'),
|
|
302
302
|
) : null,
|
|
303
303
|
(function(){
|
|
304
|
-
var
|
|
304
|
+
var parts=[]
|
|
305
|
+
var wins=account.usage
|
|
306
|
+
if(Array.isArray(wins)&&wins.length){
|
|
307
|
+
wins.forEach(function(w){
|
|
308
|
+
if(w&&w.usedPercent!=null)parts.push(t('limit')+' '+(w.ru||w.id)+': '+Math.round(w.usedPercent)+'/100%')
|
|
309
|
+
})
|
|
310
|
+
}
|
|
311
|
+
var q=account.quota
|
|
305
312
|
if(q){
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
if(q.
|
|
309
|
-
if(q.
|
|
310
|
-
if(q.
|
|
313
|
+
var qa=[]
|
|
314
|
+
if(q.remaining!=null&&q.limit!=null)qa.push(q.remaining+'/'+q.limit)
|
|
315
|
+
else if(q.remaining!=null)qa.push(String(q.remaining))
|
|
316
|
+
if(q.usedPercent!=null)qa.push(Math.round(q.usedPercent)+'%')
|
|
317
|
+
if(q.resetAt){var d=new Date(q.resetAt);qa.push(d.toLocaleTimeString())}
|
|
318
|
+
if(qa.length)parts.push(t('quota')+': '+qa.join(' / '))
|
|
311
319
|
}
|
|
312
|
-
if(
|
|
313
|
-
|
|
314
|
-
|
|
320
|
+
if(!wins&&!q&&account.usagePercent!=null)parts.push(Math.round(account.usagePercent)+'/100%')
|
|
321
|
+
var at=(q&&q.measuredAt)||(account.usageAt||0)
|
|
322
|
+
if(at){var m=Math.round((Date.now()-at)/60000);if(m>=1)parts.push(m+'m')}
|
|
323
|
+
if(!parts.length)return null
|
|
324
|
+
return React.createElement('span',{className:'dsub-sub'},parts.join(' \u00b7 '))
|
|
315
325
|
})(),
|
|
316
326
|
|
|
317
327
|
account.paidTierName ? React.createElement('span', { className: 'dsub-sub' }, t('plan') + ': ' + account.paidTierName) : null,
|
package/lib/code-assist.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { randomUUID } from 'node:crypto'
|
|
2
2
|
import { httpError, readJson } from './wire.js'
|
|
3
3
|
import { validationFromLoadCodeAssist, validationFromHttpError, noticeFromLoadCodeAssist, validationRequiredError } from './google-validation.js'
|
|
4
|
-
import { asUsageSnapshot, deepestUsedPercent } from './usage.js'
|
|
4
|
+
import { asUsageSnapshot, usageWindows, deepestUsedPercent } from './usage.js'
|
|
5
5
|
|
|
6
6
|
export const CODE_ASSIST_PROD = 'https://cloudcode-pa.googleapis.com/v1internal'
|
|
7
7
|
export const CODE_ASSIST_STREAM = 'https://daily-cloudcode-pa.googleapis.com/v1internal'
|
|
@@ -188,7 +188,9 @@ export async function fetchAvailableModels(fetchImpl, token, extraHeaders, fallb
|
|
|
188
188
|
export async function retrieveQuotaPercent(fetchImpl, token, extraHeaders) {
|
|
189
189
|
try {
|
|
190
190
|
const json = await postAssist(fetchImpl, 'retrieveUserQuota', token, {}, extraHeaders)
|
|
191
|
-
|
|
191
|
+
const snap = asUsageSnapshot(deepestUsedPercent(json))
|
|
192
|
+
if (snap) snap.windows = usageWindows(json)
|
|
193
|
+
return snap
|
|
192
194
|
} catch {
|
|
193
195
|
return null
|
|
194
196
|
}
|
package/lib/index.js
CHANGED
|
@@ -273,6 +273,7 @@ export function apply(ctx, config) {
|
|
|
273
273
|
cooldownUntil: info.cooldownUntil,
|
|
274
274
|
usagePercent: info.usagePercent,
|
|
275
275
|
quota: info.quota || null,
|
|
276
|
+
usage: info.usage || null,
|
|
276
277
|
refreshError: info.refreshError || '',
|
|
277
278
|
validationUrl: info.validationUrl || '',
|
|
278
279
|
validationMessage: info.validationMessage || '',
|
package/lib/usage.js
CHANGED
|
@@ -48,4 +48,56 @@ export function asUsageSnapshot(percent) {
|
|
|
48
48
|
const usedPercent = numberOrNull(percent)
|
|
49
49
|
if (usedPercent == null) return null
|
|
50
50
|
return { usedPercent }
|
|
51
|
-
}
|
|
51
|
+
}
|
|
52
|
+
// Named limit windows reported by vendors (five_hour, seven_day, primary...).
|
|
53
|
+
// Label map is a data table: extend it when a vendor adds a window name.
|
|
54
|
+
const WINDOW_LABELS = {
|
|
55
|
+
five_hour: { ru: "5ч", en: "5h" },
|
|
56
|
+
seven_day: { ru: "7д", en: "7d" },
|
|
57
|
+
seven_day_oauth_apps: { ru: "7д прил.", en: "7d apps" },
|
|
58
|
+
weekly_limit_7_days: { ru: "7д", en: "7d" },
|
|
59
|
+
primary: { ru: "осн.", en: "primary" },
|
|
60
|
+
secondary: { ru: "втор.", en: "secondary" },
|
|
61
|
+
monthly: { ru: "месяц", en: "month" },
|
|
62
|
+
credits: { ru: "кредиты", en: "credits" },
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function windowLabel(id) {
|
|
66
|
+
const known = WINDOW_LABELS[id]
|
|
67
|
+
return known || { ru: id, en: id }
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// Walk vendor usage JSON; every node carrying a utilization-style number
|
|
71
|
+
// becomes a named window keyed by its JSON key.
|
|
72
|
+
export function usageWindows(obj) {
|
|
73
|
+
const out = []
|
|
74
|
+
const seen = new Set()
|
|
75
|
+
function walk(value, key, depth) {
|
|
76
|
+
if (!value || typeof value !== "object" || depth > 6) return
|
|
77
|
+
const pct = numberOrNull(
|
|
78
|
+
value.utilization ?? value.used_percent ?? value.usedPercent ?? value.creditUsagePercent ?? value.used_percentage,
|
|
79
|
+
)
|
|
80
|
+
if (pct != null && key && !seen.has(key)) {
|
|
81
|
+
seen.add(key)
|
|
82
|
+
const label = windowLabel(key)
|
|
83
|
+
out.push({ id: key, ru: label.ru, en: label.en, usedPercent: pct })
|
|
84
|
+
}
|
|
85
|
+
const frac = numberOrNull(value.remainingFraction)
|
|
86
|
+
if (frac != null && key && !seen.has(key)) {
|
|
87
|
+
seen.add(key)
|
|
88
|
+
const label = windowLabel(key)
|
|
89
|
+
out.push({ id: key, ru: label.ru, en: label.en, usedPercent: (1 - frac) * 100 })
|
|
90
|
+
}
|
|
91
|
+
const ufrac = numberOrNull(value.usedFraction)
|
|
92
|
+
if (ufrac != null && key && !seen.has(key)) {
|
|
93
|
+
seen.add(key)
|
|
94
|
+
const label = windowLabel(key)
|
|
95
|
+
out.push({ id: key, ru: label.ru, en: label.en, usedPercent: ufrac * 100 })
|
|
96
|
+
}
|
|
97
|
+
for (const [k, child] of Object.entries(value)) {
|
|
98
|
+
if (child && typeof child === "object") walk(child, k, depth + 1)
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
walk(obj, "", 0)
|
|
102
|
+
return out.length ? out : null
|
|
103
|
+
}
|
package/lib/vendors/claude.js
CHANGED
|
@@ -2,7 +2,7 @@ import { buildAuthorizeUrl } from '../oauth.js'
|
|
|
2
2
|
import { anthropicPayload, modelCatalog } from '../messages.js'
|
|
3
3
|
import { jsonTokenRequest, anthropicStream, httpError, tokenBlobFromOAuth, readJson } from '../wire.js'
|
|
4
4
|
import { emailFromToken } from '../jwt.js'
|
|
5
|
-
import { asUsageSnapshot, deepestUsedPercent } from '../usage.js'
|
|
5
|
+
import { asUsageSnapshot, usageWindows, deepestUsedPercent } from '../usage.js'
|
|
6
6
|
|
|
7
7
|
export const id = 'claude'
|
|
8
8
|
|
|
@@ -104,7 +104,9 @@ export async function usage(blob, _cfg, fetchImpl) {
|
|
|
104
104
|
try {
|
|
105
105
|
const res = await (fetchImpl || fetch)(USAGE, { headers: oauthHeaders(blob.accessToken) })
|
|
106
106
|
const json = await readJson(res)
|
|
107
|
-
|
|
107
|
+
const snap = asUsageSnapshot(deepestUsedPercent(json))
|
|
108
|
+
if (snap) snap.windows = usageWindows(json)
|
|
109
|
+
return snap
|
|
108
110
|
} catch {
|
|
109
111
|
return null
|
|
110
112
|
}
|
package/lib/vendors/codex.js
CHANGED
|
@@ -3,7 +3,7 @@ import { buildAuthorizeUrl } from '../oauth.js'
|
|
|
3
3
|
import { codexResponsesBody, modelCatalog } from '../messages.js'
|
|
4
4
|
import { chatgptAccountId, emailFromToken } from '../jwt.js'
|
|
5
5
|
import { formTokenRequest, codexResponsesStream, readJson, tokenBlobFromOAuth, httpError } from '../wire.js'
|
|
6
|
-
import { asUsageSnapshot, deepestUsedPercent } from '../usage.js'
|
|
6
|
+
import { asUsageSnapshot, usageWindows, deepestUsedPercent } from '../usage.js'
|
|
7
7
|
|
|
8
8
|
export const id = 'codex'
|
|
9
9
|
|
|
@@ -150,7 +150,9 @@ export async function usage(blob, cfg, fetchImpl) {
|
|
|
150
150
|
headers: identityHeaders(blob, cfg, { Accept: 'application/json' }),
|
|
151
151
|
})
|
|
152
152
|
const json = await readJson(res)
|
|
153
|
-
|
|
153
|
+
const snap = asUsageSnapshot(deepestUsedPercent(json))
|
|
154
|
+
if (snap) snap.windows = usageWindows(json)
|
|
155
|
+
return snap
|
|
154
156
|
} catch {
|
|
155
157
|
return null
|
|
156
158
|
}
|
package/lib/vendors/grok.js
CHANGED
|
@@ -2,7 +2,7 @@ import { buildAuthorizeUrl } from '../oauth.js'
|
|
|
2
2
|
import { modelCatalog, codexResponsesBody } from '../messages.js'
|
|
3
3
|
import { formTokenRequest, codexResponsesStream, httpError, tokenBlobFromOAuth, readJson } from '../wire.js'
|
|
4
4
|
import { emailFromToken } from '../jwt.js'
|
|
5
|
-
import { asUsageSnapshot, grokBillingPercent } from '../usage.js'
|
|
5
|
+
import { asUsageSnapshot, usageWindows, grokBillingPercent } from '../usage.js'
|
|
6
6
|
|
|
7
7
|
export const id = 'grok'
|
|
8
8
|
|
|
@@ -201,7 +201,9 @@ export async function usage(blob, cfg, fetchImpl) {
|
|
|
201
201
|
headers: { ...identityHeaders(blob, cfg), Accept: 'application/json' },
|
|
202
202
|
})
|
|
203
203
|
const json = await readJson(res)
|
|
204
|
-
|
|
204
|
+
const snap = asUsageSnapshot(grokBillingPercent(json))
|
|
205
|
+
if (snap) snap.windows = usageWindows(json)
|
|
206
|
+
return snap
|
|
205
207
|
} catch {
|
|
206
208
|
return null
|
|
207
209
|
}
|
package/package.json
CHANGED