@magpie-community/opencode-cursor-auth 0.2.30 → 0.2.31

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.
Files changed (3) hide show
  1. package/README.md +15 -0
  2. package/index.mjs +42 -11
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -265,6 +265,21 @@ reaching its included allowance doesn't count the account as used up.
265
265
  On-demand also shows the team's spend when known, with its limit when it
266
266
  has one. A reached on-demand limit counts the account as used up.
267
267
 
268
+ ### Included usage only (optional)
269
+
270
+ In magpie, open the Cursor provider, turn on **Included usage only** under
271
+ **Plugin options**, and save (or set `"pluginOptions": {"cursor":
272
+ {"included_only": true}}` in magpie's settings.json). It is off by default,
273
+ applies to every Cursor account, and takes effect at each account's next
274
+ usage reading.
275
+
276
+ When on, Cursor Models and Other Models (an Enterprise account's allowance)
277
+ are what keep an account in routing: once one is used up, magpie moves its
278
+ models to the next account until the period resets, even when on-demand
279
+ could still pay for them. On-demand and Total are still shown but never
280
+ keep an account in routing. Use it when on-demand is a shared or paid pool
281
+ you don't want spent. When off, on-demand works as described above.
282
+
268
283
  Sign in to the Cursor account linked in Grok Bot. A linked SuperGrok
269
284
  subscription grants Bot access without a paid Cursor plan; see
270
285
  [Grok Bot plans and billing](https://cursor.com/help/grok-bot/plans).
package/index.mjs CHANGED
@@ -532,13 +532,21 @@ async function hardLimit(tok) {
532
532
  // dollars, thousands grouped: a team's pool runs to six figures ($451,200.00)
533
533
  const dollars = (n) => "$" + n.toLocaleString("en-US", { minimumFractionDigits: 2, maximumFractionDigits: 2 })
534
534
 
535
+ // INCLUDED_ONLY is the switch (magpie's plugin options, off by default)
536
+ // that keeps an account in routing only while its included usage lasts
537
+ // (magpie-community/plugins#485): on, the included pools (Cursor Models and
538
+ // Other Models; an Enterprise account's allowance) are what stops it, and
539
+ // On-demand and Total are shown only, a budget with room or none at all
540
+ // keeping nothing in routing. Off, on-demand that can still pay keeps it.
541
+ const INCLUDED_ONLY = "included_only"
542
+
535
543
  // enterpriseUsage is the allowance GetCurrentPeriodUsage leaves out on an
536
544
  // Enterprise account. That call answers with only the billing cycle there;
537
545
  // the website's usage-summary has individualUsage.overall, in cents, and
538
546
  // the account's membership. The CLI JWT is also the website's dashboard
539
547
  // session, as botUsage builds it. undefined when the summary says nothing
540
548
  // this can show, so a failed read stays an empty card, as before.
541
- async function enterpriseUsage(tok) {
549
+ async function enterpriseUsage(tok, includedOnly = false) {
542
550
  try {
543
551
  const subject = JSON.parse(Buffer.from(tok.split(".")[1], "base64url").toString()).sub
544
552
  if (typeof subject !== "string") return undefined
@@ -569,8 +577,9 @@ async function enterpriseUsage(tok) {
569
577
  const unlimited = od?.enabled === true && od.limit === null
570
578
  const fixed = od?.enabled === true && typeof od.limit === "number" && Number.isFinite(od.limit) && od.limit > 0 && spent
571
579
  // Only an explicitly unlimited budget or a measured budget with room
572
- // lets a spent included allowance stay available to magpie's routing.
573
- const aside = unlimited || (fixed && od.used < od.limit)
580
+ // lets a spent included allowance stay available to magpie's routing,
581
+ // and neither does with INCLUDED_ONLY on.
582
+ const aside = !includedOnly && (unlimited || (fixed && od.used < od.limit))
574
583
  const windows = [{
575
584
  name: "Enterprise",
576
585
  used: Math.max(0, Math.min(100, (100 * used) / limit)),
@@ -589,6 +598,7 @@ async function enterpriseUsage(tok) {
589
598
  limit: od.limit / 100,
590
599
  unit: "USD",
591
600
  ...resets,
601
+ ...(includedOnly ? { aside: true } : {}),
592
602
  })
593
603
  else if (unlimited && spent) windows.push({ name: "On-demand", used: 0, display: dollars(od.used / 100), ...resets, aside: true })
594
604
  return windows
@@ -603,9 +613,11 @@ async function enterpriseUsage(tok) {
603
613
  // Discord: a team that pays for more) is still served when the pools are
604
614
  // spent, so they are shown aside, and its On-demand spend is what can run
605
615
  // out: at its limit (a team member with none of their own: the team's
606
- // pool), or never when it has none. An Enterprise account's period has no
607
- // planUsage; its allowance comes from usage-summary instead.
608
- async function usage(tok, ids) {
616
+ // pool), or never when it has none. With includedOnly (INCLUDED_ONLY on)
617
+ // the pools are the allowance whatever on-demand allows, and On-demand is
618
+ // shown only. An Enterprise account's period has no planUsage; its
619
+ // allowance comes from usage-summary instead.
620
+ async function usage(tok, ids, includedOnly = false) {
609
621
  const [res, hard] = await Promise.all([
610
622
  fetch(API + "/aiserver.v1.DashboardService/GetCurrentPeriodUsage", {
611
623
  method: "POST",
@@ -620,7 +632,7 @@ async function usage(tok, ids) {
620
632
  const u = data?.planUsage
621
633
  if (!u) {
622
634
  // Prefer measured Enterprise usage before any reset-date-only fallback.
623
- const windows = await enterpriseUsage(tok)
635
+ const windows = await enterpriseUsage(tok, includedOnly)
624
636
  return windows ? { windows } : { windows: [] }
625
637
  }
626
638
  const ms = /^[+-]?\d+$/.test(data.billingCycleEnd ?? "") ? Number(data.billingCycleEnd) : 0
@@ -642,10 +654,10 @@ async function usage(tok, ids) {
642
654
  const pool = [...new Set(["auto", ...asked, ...(data.autoBucketModels ?? [])])].filter(inPool)
643
655
  const num = (v) => (typeof v === "number" ? v : 0)
644
656
  const od = onDemandOf(data, hard)
645
- const pooled = od.kind === "fixed" || od.kind === "unlimited" ? { aside: true } : {}
657
+ const pooled = !includedOnly && (od.kind === "fixed" || od.kind === "unlimited") ? { aside: true } : {}
646
658
  const onDemand =
647
659
  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 }]
660
+ ? [{ 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
661
  : od.kind === "unlimited"
650
662
  ? [{ name: "On-demand", used: 0, display: dollars(od.usedDollars), ...resets, aside: true }]
651
663
  : []
@@ -3172,7 +3184,16 @@ async function answerOf(auth, chat, signal, session = "") {
3172
3184
 
3173
3185
  // ---- the plugin ---------------------------------------------------------------------------
3174
3186
 
3175
- export async function CursorAuthPlugin() {
3187
+ export async function CursorAuthPlugin(input) {
3188
+ // includedOnly is the user's INCLUDED_ONLY as magpie has it now, read at
3189
+ // each usage reading so a change needs no restart; false outside magpie
3190
+ const includedOnly = () => {
3191
+ try {
3192
+ return input?.magpie?.option?.(ID, INCLUDED_ONLY) === true
3193
+ } catch {
3194
+ return false
3195
+ }
3196
+ }
3176
3197
  return {
3177
3198
  auth: {
3178
3199
  provider: ID,
@@ -3205,6 +3226,16 @@ export async function CursorAuthPlugin() {
3205
3226
  { type: "oauth", label: "cursor-agent's sign-in", authorize: cliSignIn },
3206
3227
  { type: "api", label: "Cursor API key (cursor.com/dashboard → Integrations)" },
3207
3228
  ],
3229
+ // magpie's own switches (input.magpie.option), read at each usage
3230
+ // reading
3231
+ options: [
3232
+ {
3233
+ key: INCLUDED_ONLY,
3234
+ label: "Included usage only",
3235
+ description:
3236
+ "Keeps an account in routing only while its included usage lasts: once Cursor Models or Other Models is used up, magpie moves those models to the next account until the period resets, even if on-demand could pay. On-demand and Total are still shown but never keep an account in routing.",
3237
+ },
3238
+ ],
3208
3239
  // magpie's: how much of the plan's included usage is gone (the plan
3209
3240
  // is the one the sign-in read). The built-in's read, clean or not,
3210
3241
  // neither marked the account nor cleared it.
@@ -3215,7 +3246,7 @@ export async function CursorAuthPlugin() {
3215
3246
  } catch (e) {
3216
3247
  return { error: e.message, windows: [], signIn: "kept" }
3217
3248
  }
3218
- const [u, plan, bot] = await Promise.all([usage(tok, Object.keys(provider?.models ?? {})), planOf(tok), botUsage(tok)])
3249
+ const [u, plan, bot] = await Promise.all([usage(tok, Object.keys(provider?.models ?? {}), includedOnly()), planOf(tok), botUsage(tok)])
3219
3250
  // Bot goes among magpie's three menu-bar rings: before Total, and
3220
3251
  // before an On-demand with no limit, which is only a dollar figure on
3221
3252
  // 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.30",
3
+ "version": "0.2.31",
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",