@magpie-community/opencode-cursor-auth 0.2.30 → 0.2.32
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 +20 -0
- package/index.mjs +73 -17
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -240,6 +240,11 @@ go without the list for 5 minutes instead of each waiting for it again.
|
|
|
240
240
|
## Usage
|
|
241
241
|
|
|
242
242
|
magpie shows Cursor Models, Other Models and Total for the billing period.
|
|
243
|
+
A plan of Cursor's merged billing (`GetPlanInfo`'s `isMerged`, the newer
|
|
244
|
+
plans) has no Other Models pool: its included usage is one allowance every
|
|
245
|
+
model draws on, as the dashboard's "Weekly usage limit" shows it. magpie
|
|
246
|
+
shows that as one window, Weekly (Monthly on a monthly plan), for every
|
|
247
|
+
model.
|
|
243
248
|
Accounts with a Grok Bot allowance also show its usage and reset time;
|
|
244
249
|
active trials are labelled separately. A spent Bot allowance doesn't stop
|
|
245
250
|
Cursor requests.
|
|
@@ -265,6 +270,21 @@ reaching its included allowance doesn't count the account as used up.
|
|
|
265
270
|
On-demand also shows the team's spend when known, with its limit when it
|
|
266
271
|
has one. A reached on-demand limit counts the account as used up.
|
|
267
272
|
|
|
273
|
+
### Included usage only (optional)
|
|
274
|
+
|
|
275
|
+
In magpie, open the Cursor provider, turn on **Included usage only** under
|
|
276
|
+
**Plugin options**, and save (or set `"pluginOptions": {"cursor":
|
|
277
|
+
{"included_only": true}}` in magpie's settings.json). It is off by default,
|
|
278
|
+
applies to every Cursor account, and takes effect at each account's next
|
|
279
|
+
usage reading.
|
|
280
|
+
|
|
281
|
+
When on, Cursor Models and Other Models (a merged plan's Weekly, an
|
|
282
|
+
Enterprise account's allowance) are what keep an account in routing: once one is used up, magpie moves its
|
|
283
|
+
models to the next account until the period resets, even when on-demand
|
|
284
|
+
could still pay for them. On-demand and Total are still shown but never
|
|
285
|
+
keep an account in routing. Use it when on-demand is a shared or paid pool
|
|
286
|
+
you don't want spent. When off, on-demand works as described above.
|
|
287
|
+
|
|
268
288
|
Sign in to the Cursor account linked in Grok Bot. A linked SuperGrok
|
|
269
289
|
subscription grants Bot access without a paid Cursor plan; see
|
|
270
290
|
[Grok Bot plans and billing](https://cursor.com/help/grok-bot/plans).
|
package/index.mjs
CHANGED
|
@@ -316,18 +316,27 @@ async function whoIs(tok) {
|
|
|
316
316
|
|
|
317
317
|
// plans are the plan each token's account is on, as `cursor-agent about`
|
|
318
318
|
// names it (GetPlanInfo's planName, magpie's card title), asked again after
|
|
319
|
-
// an hour.
|
|
319
|
+
// an hour. merged is GetPlanInfo's isMerged: a plan of Cursor's merged
|
|
320
|
+
// (xAI) billing, whose included usage is one allowance for every model,
|
|
321
|
+
// most of them a week long (includedUsagePeriod), and has no Other Models
|
|
322
|
+
// pool (majiang_ai on X). The dashboard's usage page shows such a plan one
|
|
323
|
+
// meter, the period's totalPercentUsed. period is "weekly", "monthly" or
|
|
324
|
+
// "" (not said). A reply that couldn't be read keeps the last one, else
|
|
325
|
+
// says nothing: undefined merged, which the pools are kept for.
|
|
320
326
|
const plans = new Map()
|
|
321
327
|
const PLAN_TTL = 3600_000
|
|
328
|
+
const PERIODS = { INCLUDED_USAGE_PERIOD_WEEKLY: "weekly", INCLUDED_USAGE_PERIOD_MONTHLY: "monthly", 1: "monthly", 2: "weekly" }
|
|
322
329
|
async function planOf(tok) {
|
|
323
330
|
const had = plans.get(tok)
|
|
324
|
-
if (had && Date.now() - had.at < PLAN_TTL) return had
|
|
331
|
+
if (had && Date.now() - had.at < PLAN_TTL) return had
|
|
325
332
|
try {
|
|
326
|
-
const
|
|
327
|
-
|
|
328
|
-
|
|
333
|
+
const info = await unary(API, "aiserver.v1.DashboardService/GetPlanInfo", tok)
|
|
334
|
+
const plan = String(info?.planInfo?.planName ?? "").trim()
|
|
335
|
+
const got = { plan: plan || had?.plan || "", merged: info?.isMerged === true, period: PERIODS[info?.planInfo?.includedUsagePeriod] ?? "", at: Date.now() }
|
|
336
|
+
if (plan || got.merged) plans.set(tok, got)
|
|
337
|
+
return got
|
|
329
338
|
} catch {
|
|
330
|
-
return had
|
|
339
|
+
return had ?? { plan: "" }
|
|
331
340
|
}
|
|
332
341
|
}
|
|
333
342
|
|
|
@@ -532,13 +541,21 @@ async function hardLimit(tok) {
|
|
|
532
541
|
// dollars, thousands grouped: a team's pool runs to six figures ($451,200.00)
|
|
533
542
|
const dollars = (n) => "$" + n.toLocaleString("en-US", { minimumFractionDigits: 2, maximumFractionDigits: 2 })
|
|
534
543
|
|
|
544
|
+
// INCLUDED_ONLY is the switch (magpie's plugin options, off by default)
|
|
545
|
+
// that keeps an account in routing only while its included usage lasts
|
|
546
|
+
// (magpie-community/plugins#485): on, the included pools (Cursor Models and
|
|
547
|
+
// Other Models; an Enterprise account's allowance) are what stops it, and
|
|
548
|
+
// On-demand and Total are shown only, a budget with room or none at all
|
|
549
|
+
// keeping nothing in routing. Off, on-demand that can still pay keeps it.
|
|
550
|
+
const INCLUDED_ONLY = "included_only"
|
|
551
|
+
|
|
535
552
|
// enterpriseUsage is the allowance GetCurrentPeriodUsage leaves out on an
|
|
536
553
|
// Enterprise account. That call answers with only the billing cycle there;
|
|
537
554
|
// the website's usage-summary has individualUsage.overall, in cents, and
|
|
538
555
|
// the account's membership. The CLI JWT is also the website's dashboard
|
|
539
556
|
// session, as botUsage builds it. undefined when the summary says nothing
|
|
540
557
|
// this can show, so a failed read stays an empty card, as before.
|
|
541
|
-
async function enterpriseUsage(tok) {
|
|
558
|
+
async function enterpriseUsage(tok, includedOnly = false) {
|
|
542
559
|
try {
|
|
543
560
|
const subject = JSON.parse(Buffer.from(tok.split(".")[1], "base64url").toString()).sub
|
|
544
561
|
if (typeof subject !== "string") return undefined
|
|
@@ -569,8 +586,9 @@ async function enterpriseUsage(tok) {
|
|
|
569
586
|
const unlimited = od?.enabled === true && od.limit === null
|
|
570
587
|
const fixed = od?.enabled === true && typeof od.limit === "number" && Number.isFinite(od.limit) && od.limit > 0 && spent
|
|
571
588
|
// Only an explicitly unlimited budget or a measured budget with room
|
|
572
|
-
// lets a spent included allowance stay available to magpie's routing
|
|
573
|
-
|
|
589
|
+
// lets a spent included allowance stay available to magpie's routing,
|
|
590
|
+
// and neither does with INCLUDED_ONLY on.
|
|
591
|
+
const aside = !includedOnly && (unlimited || (fixed && od.used < od.limit))
|
|
574
592
|
const windows = [{
|
|
575
593
|
name: "Enterprise",
|
|
576
594
|
used: Math.max(0, Math.min(100, (100 * used) / limit)),
|
|
@@ -589,6 +607,7 @@ async function enterpriseUsage(tok) {
|
|
|
589
607
|
limit: od.limit / 100,
|
|
590
608
|
unit: "USD",
|
|
591
609
|
...resets,
|
|
610
|
+
...(includedOnly ? { aside: true } : {}),
|
|
592
611
|
})
|
|
593
612
|
else if (unlimited && spent) windows.push({ name: "On-demand", used: 0, display: dollars(od.used / 100), ...resets, aside: true })
|
|
594
613
|
return windows
|
|
@@ -603,9 +622,13 @@ async function enterpriseUsage(tok) {
|
|
|
603
622
|
// Discord: a team that pays for more) is still served when the pools are
|
|
604
623
|
// spent, so they are shown aside, and its On-demand spend is what can run
|
|
605
624
|
// out: at its limit (a team member with none of their own: the team's
|
|
606
|
-
// pool), or never when it has none.
|
|
607
|
-
//
|
|
608
|
-
|
|
625
|
+
// pool), or never when it has none. With includedOnly (INCLUDED_ONLY on)
|
|
626
|
+
// the pools are the allowance whatever on-demand allows, and On-demand is
|
|
627
|
+
// shown only. An Enterprise account's period has no planUsage; its
|
|
628
|
+
// allowance comes from usage-summary instead.
|
|
629
|
+
// plan is planOf's answer, asked alongside (a promise): a merged plan's
|
|
630
|
+
// included usage is one window, Weekly on a weekly plan, for every model.
|
|
631
|
+
async function usage(tok, ids, includedOnly = false, plan = undefined) {
|
|
609
632
|
const [res, hard] = await Promise.all([
|
|
610
633
|
fetch(API + "/aiserver.v1.DashboardService/GetCurrentPeriodUsage", {
|
|
611
634
|
method: "POST",
|
|
@@ -620,7 +643,7 @@ async function usage(tok, ids) {
|
|
|
620
643
|
const u = data?.planUsage
|
|
621
644
|
if (!u) {
|
|
622
645
|
// Prefer measured Enterprise usage before any reset-date-only fallback.
|
|
623
|
-
const windows = await enterpriseUsage(tok)
|
|
646
|
+
const windows = await enterpriseUsage(tok, includedOnly)
|
|
624
647
|
return windows ? { windows } : { windows: [] }
|
|
625
648
|
}
|
|
626
649
|
const ms = /^[+-]?\d+$/.test(data.billingCycleEnd ?? "") ? Number(data.billingCycleEnd) : 0
|
|
@@ -642,13 +665,26 @@ async function usage(tok, ids) {
|
|
|
642
665
|
const pool = [...new Set(["auto", ...asked, ...(data.autoBucketModels ?? [])])].filter(inPool)
|
|
643
666
|
const num = (v) => (typeof v === "number" ? v : 0)
|
|
644
667
|
const od = onDemandOf(data, hard)
|
|
645
|
-
const pooled = od.kind === "fixed" || od.kind === "unlimited" ? { aside: true } : {}
|
|
668
|
+
const pooled = !includedOnly && (od.kind === "fixed" || od.kind === "unlimited") ? { aside: true } : {}
|
|
646
669
|
const onDemand =
|
|
647
670
|
od.kind === "fixed"
|
|
648
|
-
? [{ name: "On-demand", used: Math.max(0, Math.min(100, (100 * od.usedDollars) / od.limitDollars)), display: `${dollars(od.usedDollars)} / ${dollars(od.limitDollars)}` + (od.mineDollars === undefined ? "" : ` (you: ${dollars(od.mineDollars)})`), ...resets }]
|
|
671
|
+
? [{ name: "On-demand", used: Math.max(0, Math.min(100, (100 * od.usedDollars) / od.limitDollars)), display: `${dollars(od.usedDollars)} / ${dollars(od.limitDollars)}` + (od.mineDollars === undefined ? "" : ` (you: ${dollars(od.mineDollars)})`), ...resets, ...(includedOnly ? { aside: true } : {}) }]
|
|
649
672
|
: od.kind === "unlimited"
|
|
650
673
|
? [{ name: "On-demand", used: 0, display: dollars(od.usedDollars), ...resets, aside: true }]
|
|
651
674
|
: []
|
|
675
|
+
// A merged plan has no pools: its included usage is one allowance every
|
|
676
|
+
// model draws on, which the dashboard shows as the period's
|
|
677
|
+
// totalPercentUsed, autoPercentUsed the same and apiPercentUsed 0 always.
|
|
678
|
+
const info = await plan
|
|
679
|
+
if (info?.merged === true) {
|
|
680
|
+
const weekly = info.period === "weekly"
|
|
681
|
+
return {
|
|
682
|
+
windows: [
|
|
683
|
+
{ name: weekly ? "Weekly" : info.period === "monthly" ? "Monthly" : "Included", used: num(u.totalPercentUsed), ...resets, ...(weekly ? { span: 604800 } : {}), ...pooled },
|
|
684
|
+
...onDemand,
|
|
685
|
+
],
|
|
686
|
+
}
|
|
687
|
+
}
|
|
652
688
|
// the two pools fit the line; the total goes in its tooltip
|
|
653
689
|
return {
|
|
654
690
|
windows: [
|
|
@@ -3172,7 +3208,16 @@ async function answerOf(auth, chat, signal, session = "") {
|
|
|
3172
3208
|
|
|
3173
3209
|
// ---- the plugin ---------------------------------------------------------------------------
|
|
3174
3210
|
|
|
3175
|
-
export async function CursorAuthPlugin() {
|
|
3211
|
+
export async function CursorAuthPlugin(input) {
|
|
3212
|
+
// includedOnly is the user's INCLUDED_ONLY as magpie has it now, read at
|
|
3213
|
+
// each usage reading so a change needs no restart; false outside magpie
|
|
3214
|
+
const includedOnly = () => {
|
|
3215
|
+
try {
|
|
3216
|
+
return input?.magpie?.option?.(ID, INCLUDED_ONLY) === true
|
|
3217
|
+
} catch {
|
|
3218
|
+
return false
|
|
3219
|
+
}
|
|
3220
|
+
}
|
|
3176
3221
|
return {
|
|
3177
3222
|
auth: {
|
|
3178
3223
|
provider: ID,
|
|
@@ -3205,6 +3250,16 @@ export async function CursorAuthPlugin() {
|
|
|
3205
3250
|
{ type: "oauth", label: "cursor-agent's sign-in", authorize: cliSignIn },
|
|
3206
3251
|
{ type: "api", label: "Cursor API key (cursor.com/dashboard → Integrations)" },
|
|
3207
3252
|
],
|
|
3253
|
+
// magpie's own switches (input.magpie.option), read at each usage
|
|
3254
|
+
// reading
|
|
3255
|
+
options: [
|
|
3256
|
+
{
|
|
3257
|
+
key: INCLUDED_ONLY,
|
|
3258
|
+
label: "Included usage only",
|
|
3259
|
+
description:
|
|
3260
|
+
"Keeps an account in routing only while its included usage lasts: once an included pool (Cursor Models, Other Models, a newer plan's Weekly) is used up, magpie moves its models to the next account until it resets, even if on-demand could pay. On-demand and Total never keep an account in routing.",
|
|
3261
|
+
},
|
|
3262
|
+
],
|
|
3208
3263
|
// magpie's: how much of the plan's included usage is gone (the plan
|
|
3209
3264
|
// is the one the sign-in read). The built-in's read, clean or not,
|
|
3210
3265
|
// neither marked the account nor cleared it.
|
|
@@ -3215,7 +3270,8 @@ export async function CursorAuthPlugin() {
|
|
|
3215
3270
|
} catch (e) {
|
|
3216
3271
|
return { error: e.message, windows: [], signIn: "kept" }
|
|
3217
3272
|
}
|
|
3218
|
-
const
|
|
3273
|
+
const info = planOf(tok)
|
|
3274
|
+
const [u, { plan }, bot] = await Promise.all([usage(tok, Object.keys(provider?.models ?? {}), includedOnly(), info), info, botUsage(tok)])
|
|
3219
3275
|
// Bot goes among magpie's three menu-bar rings: before Total, and
|
|
3220
3276
|
// before an On-demand with no limit, which is only a dollar figure on
|
|
3221
3277
|
// a ring that never fills (magpie #2213, Morganwan90). An On-demand
|
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.32",
|
|
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",
|