@magpie-community/opencode-cursor-auth 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/README.md +7 -0
- package/index.mjs +71 -11
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -131,6 +131,13 @@ Accounts with a Grok Bot allowance also show its usage and reset time;
|
|
|
131
131
|
active trials are labelled separately. A spent Bot allowance doesn't stop
|
|
132
132
|
Cursor requests.
|
|
133
133
|
|
|
134
|
+
An account that may spend on-demand once its included usage is gone (a
|
|
135
|
+
team or personal spend limit, as `cursor-agent`'s usage view shows it) also
|
|
136
|
+
shows On-demand: its spend, and its limit when it has one. Cursor keeps
|
|
137
|
+
serving such an account past the included usage, so magpie doesn't count
|
|
138
|
+
it used up when Cursor Models or Other Models reach 100%; it does when the
|
|
139
|
+
on-demand spend reaches its limit.
|
|
140
|
+
|
|
134
141
|
Sign in to the Cursor account linked in Grok Bot. A linked SuperGrok
|
|
135
142
|
subscription grants Bot access without a paid Cursor plan; see
|
|
136
143
|
[Grok Bot plans and billing](https://cursor.com/help/grok-bot/plans).
|
package/index.mjs
CHANGED
|
@@ -380,16 +380,67 @@ function firstParty(model) {
|
|
|
380
380
|
return ["grok-4.5", "grok-4.6", "grok-4.7"].some((b) => model === b || model.startsWith(b + "-"))
|
|
381
381
|
}
|
|
382
382
|
|
|
383
|
+
// onDemandOf is what Cursor still serves once the included usage is spent,
|
|
384
|
+
// as cursor-agent's own usage view tells it (its On-demand line): from the
|
|
385
|
+
// period's spendLimitUsage (cents) and, where that sets no limit of the
|
|
386
|
+
// user's, GetHardLimit's reply (dollars; undefined when it couldn't be
|
|
387
|
+
// read). kind is "fixed" (up to limitDollars), "unlimited", "disabled" or
|
|
388
|
+
// "unavailable" (not known).
|
|
389
|
+
function onDemandOf(data, hard) {
|
|
390
|
+
const s = data?.spendLimitUsage
|
|
391
|
+
const usedDollars = Number(s?.individualUsed ?? 0) / 100
|
|
392
|
+
const own =
|
|
393
|
+
s?.individualLimit === undefined
|
|
394
|
+
? undefined
|
|
395
|
+
: Number(s.individualLimit) > 0
|
|
396
|
+
? { kind: "fixed", usedDollars, limitDollars: Number(s.individualLimit) / 100 }
|
|
397
|
+
: { kind: "disabled", usedDollars }
|
|
398
|
+
if (s?.limitType === "team") {
|
|
399
|
+
if (own) return own
|
|
400
|
+
if (hard) return hard.noUsageBasedAllowed || !(Number(hard.hardLimit) > 0) ? { kind: "disabled", usedDollars } : { kind: "unlimited", usedDollars }
|
|
401
|
+
return Number(s.pooledLimit ?? 0) > 0 ? { kind: "unlimited", usedDollars } : { kind: "unavailable", usedDollars }
|
|
402
|
+
}
|
|
403
|
+
if (!hard) return own ?? { kind: "unavailable", usedDollars }
|
|
404
|
+
if (hard.noUsageBasedAllowed) return { kind: "disabled", usedDollars }
|
|
405
|
+
const limit = Number(hard.hardLimit ?? 0)
|
|
406
|
+
if (limit >= 2147483647) return { kind: "unlimited", usedDollars }
|
|
407
|
+
return limit > 0 ? { kind: "fixed", usedDollars, limitDollars: limit } : { kind: "disabled", usedDollars }
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
// hardLimit is GetHardLimit's reply, undefined when it couldn't be read.
|
|
411
|
+
async function hardLimit(tok) {
|
|
412
|
+
try {
|
|
413
|
+
const res = await fetch(API + "/aiserver.v1.DashboardService/GetHardLimit", {
|
|
414
|
+
method: "POST",
|
|
415
|
+
headers: { Authorization: `Bearer ${tok}`, "Content-Type": "application/json", "Connect-Protocol-Version": "1" },
|
|
416
|
+
body: "{}",
|
|
417
|
+
signal: AbortSignal.timeout(15_000),
|
|
418
|
+
})
|
|
419
|
+
return res.ok ? ((await res.json()) ?? undefined) : undefined
|
|
420
|
+
} catch {
|
|
421
|
+
return undefined
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
const dollars = (n) => "$" + n.toFixed(2)
|
|
426
|
+
|
|
383
427
|
// usage is the account's windows this billing period; an enterprise plan
|
|
384
428
|
// reports spend instead, and gets none. ids are the models it can be
|
|
385
|
-
// asked for, each counted by the pool Cursor bills it to.
|
|
429
|
+
// asked for, each counted by the pool Cursor bills it to. An account that
|
|
430
|
+
// may spend on-demand once its included usage is gone (fottencity on
|
|
431
|
+
// Discord: a team that pays for more) is still served when the pools are
|
|
432
|
+
// spent, so they are shown aside, and its On-demand spend is what can run
|
|
433
|
+
// out: at its limit, or never when it has none.
|
|
386
434
|
async function usage(tok, ids) {
|
|
387
|
-
const res = await
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
435
|
+
const [res, hard] = await Promise.all([
|
|
436
|
+
fetch(API + "/aiserver.v1.DashboardService/GetCurrentPeriodUsage", {
|
|
437
|
+
method: "POST",
|
|
438
|
+
headers: { Authorization: `Bearer ${tok}`, "Content-Type": "application/json", "Connect-Protocol-Version": "1" },
|
|
439
|
+
body: "{}",
|
|
440
|
+
signal: AbortSignal.timeout(15_000),
|
|
441
|
+
}),
|
|
442
|
+
hardLimit(tok),
|
|
443
|
+
])
|
|
393
444
|
if (!res.ok) return { error: statusText(res.status), windows: [] }
|
|
394
445
|
const data = await res.json()
|
|
395
446
|
const u = data?.planUsage
|
|
@@ -412,11 +463,20 @@ async function usage(tok, ids) {
|
|
|
412
463
|
const asked = ids.flatMap((id) => [id, id.replace(/@[^@]*$/, "")])
|
|
413
464
|
const pool = [...new Set(["auto", ...asked, ...(data.autoBucketModels ?? [])])].filter(inPool)
|
|
414
465
|
const num = (v) => (typeof v === "number" ? v : 0)
|
|
466
|
+
const od = onDemandOf(data, hard)
|
|
467
|
+
const pooled = od.kind === "fixed" || od.kind === "unlimited" ? { aside: true } : {}
|
|
468
|
+
const onDemand =
|
|
469
|
+
od.kind === "fixed"
|
|
470
|
+
? [{ name: "On-demand", used: Math.max(0, Math.min(100, (100 * od.usedDollars) / od.limitDollars)), display: `${dollars(od.usedDollars)} / ${dollars(od.limitDollars)}`, ...resets }]
|
|
471
|
+
: od.kind === "unlimited"
|
|
472
|
+
? [{ name: "On-demand", used: 0, display: dollars(od.usedDollars), ...resets, aside: true }]
|
|
473
|
+
: []
|
|
415
474
|
// the two pools fit the line; the total goes in its tooltip
|
|
416
475
|
return {
|
|
417
476
|
windows: [
|
|
418
|
-
{ name: "Cursor Models", used: num(u.autoPercentUsed), ...resets, models: pool },
|
|
419
|
-
{ name: "Other Models", used: num(u.apiPercentUsed), ...resets, notModels: pool },
|
|
477
|
+
{ name: "Cursor Models", used: num(u.autoPercentUsed), ...resets, models: pool, ...pooled },
|
|
478
|
+
{ name: "Other Models", used: num(u.apiPercentUsed), ...resets, notModels: pool, ...pooled },
|
|
479
|
+
...onDemand,
|
|
420
480
|
{ name: "Total", used: num(u.totalPercentUsed), ...resets, aside: true },
|
|
421
481
|
],
|
|
422
482
|
}
|
|
@@ -2197,7 +2257,7 @@ export async function CursorAuthPlugin() {
|
|
|
2197
2257
|
}
|
|
2198
2258
|
const [u, plan, bot] = await Promise.all([usage(tok, Object.keys(provider?.models ?? {})), planOf(tok), botUsage(tok)])
|
|
2199
2259
|
// Put Bot before Total for magpie's three menu-bar rings.
|
|
2200
|
-
const windows = [...u.windows.filter((w) =>
|
|
2260
|
+
const windows = [...u.windows.filter((w) => w.name !== "Total"), ...bot, ...u.windows.filter((w) => w.name === "Total")]
|
|
2201
2261
|
return { ...u, windows, ...(plan ? { plan } : {}), signIn: "kept" }
|
|
2202
2262
|
},
|
|
2203
2263
|
},
|
|
@@ -2240,4 +2300,4 @@ export async function CursorAuthPlugin() {
|
|
|
2240
2300
|
}
|
|
2241
2301
|
|
|
2242
2302
|
// for tests
|
|
2243
|
-
export const _internal = { resetH1: () => (h1Until = 0), onH1: () => Date.now() < h1Until, H2, H1, h2Blocked, open, openH1, STEP_WAIT, stepUsage, ledger, owed, flights, misses, unreadable, catalog, usable, request, modelsOf, tokensOf, maxRequired, conversationID, usageOf, sessionOf, SESSION, errorResponse, kept, poolBase, conversation, buildRun, decode, exec, fields, pb, pbValue, pbAny, frame, frames, failure, toolsOf, expiry }
|
|
2303
|
+
export const _internal = { onDemandOf,resetH1: () => (h1Until = 0), onH1: () => Date.now() < h1Until, H2, H1, h2Blocked, open, openH1, STEP_WAIT, stepUsage, ledger, owed, flights, misses, unreadable, catalog, usable, request, modelsOf, tokensOf, maxRequired, conversationID, usageOf, sessionOf, SESSION, errorResponse, kept, poolBase, conversation, buildRun, decode, exec, fields, pb, pbValue, pbAny, frame, frames, failure, toolsOf, expiry }
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@magpie-community/opencode-cursor-auth",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.6",
|
|
4
4
|
"description": "Cursor subscriptions (Pro, Pro+, Ultra, Teams) in OpenCode and magpie, through the API cursor-agent talks to",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./index.mjs",
|