@magpie-community/opencode-cursor-auth 0.2.22 → 0.2.23
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/index.mjs +69 -4
- package/package.json +1 -1
package/index.mjs
CHANGED
|
@@ -117,6 +117,68 @@ function authFile() {
|
|
|
117
117
|
return join(process.env.XDG_CONFIG_HOME || join(home, ".config"), "cursor", "auth.json")
|
|
118
118
|
}
|
|
119
119
|
|
|
120
|
+
// cliConfigFile is cursor-agent's cli-config.json, where it keeps the team
|
|
121
|
+
// it signed in to (authInfo.activeTeamId); its directory found as the CLI
|
|
122
|
+
// finds it, on every OS.
|
|
123
|
+
function cliConfigFile() {
|
|
124
|
+
const dir = process.env.CURSOR_CONFIG_DIR?.trim()
|
|
125
|
+
|| (process.env.XDG_CONFIG_HOME?.trim() ? join(process.env.XDG_CONFIG_HOME, "cursor") : join(homedir(), ".cursor"))
|
|
126
|
+
return join(dir, "cli-config.json")
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// tokenSub is a JWT's subject (the auth id, "auth0|user_…"); "" when it
|
|
130
|
+
// doesn't say.
|
|
131
|
+
function tokenSub(tok) {
|
|
132
|
+
const parts = String(tok ?? "").split(".")
|
|
133
|
+
if (parts.length !== 3) return ""
|
|
134
|
+
try {
|
|
135
|
+
const sub = JSON.parse(Buffer.from(parts[1], "base64url").toString("utf8"))?.sub
|
|
136
|
+
return typeof sub === "string" ? sub : ""
|
|
137
|
+
} catch {
|
|
138
|
+
return ""
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
const teamOf = (v) => (Number.isSafeInteger(v) && v > 0 ? v : undefined)
|
|
143
|
+
|
|
144
|
+
// activeTeamId is the team a request is billed to, as cursor-agent sends it
|
|
145
|
+
// (x-cursor-team-id): cli-config.json's authInfo.activeTeamId. Without it
|
|
146
|
+
// Cursor takes the token's default team, which in an organization may be one
|
|
147
|
+
// with on-demand spend turned off ("Team budget limit reached"). The config's
|
|
148
|
+
// team is only taken for the account cursor-agent signed in to: a token
|
|
149
|
+
// whose subject isn't its authInfo.authId, or a config with none, gets none.
|
|
150
|
+
// Read again after 10 s; cursor-agent rewrites the file in place, so a read
|
|
151
|
+
// that fails to parse keeps the last one, and only a missing file clears it.
|
|
152
|
+
const TEAM_TTL = 10_000
|
|
153
|
+
const teamRead = { at: 0, info: null }
|
|
154
|
+
async function activeTeamId(tok) {
|
|
155
|
+
if (Date.now() - teamRead.at >= TEAM_TTL) {
|
|
156
|
+
try {
|
|
157
|
+
teamRead.info = JSON.parse(await readFile(cliConfigFile(), "utf8"))?.authInfo ?? null
|
|
158
|
+
} catch (e) {
|
|
159
|
+
if (e?.code === "ENOENT") teamRead.info = null
|
|
160
|
+
}
|
|
161
|
+
teamRead.at = Date.now()
|
|
162
|
+
}
|
|
163
|
+
const info = teamRead.info
|
|
164
|
+
if (!info) return undefined
|
|
165
|
+
if (!info.authId || info.authId !== tokenSub(tok)) return undefined
|
|
166
|
+
return teamOf(info.activeTeamId)
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// teamHeader names that team in a request's headers, and teamBody in
|
|
170
|
+
// GetFilteredUsageEvents' body; neither says anything when there is none.
|
|
171
|
+
// GetHardLimit and GetCurrentPeriodUsage go out with "{}", as cursor-agent
|
|
172
|
+
// sends them: the team rides in the header only.
|
|
173
|
+
async function teamHeader(tok) {
|
|
174
|
+
const id = await activeTeamId(tok)
|
|
175
|
+
return id ? { "x-cursor-team-id": String(id) } : {}
|
|
176
|
+
}
|
|
177
|
+
async function teamBody(tok) {
|
|
178
|
+
const id = await activeTeamId(tok)
|
|
179
|
+
return id ? { teamId: id } : {}
|
|
180
|
+
}
|
|
181
|
+
|
|
120
182
|
// TOKEN_TTL is how long a token read from the Keychain is taken as it: a
|
|
121
183
|
// sign-in reads it afresh, one made in cursor-agent is seen within it.
|
|
122
184
|
// KEYCHAIN_DENIED is how long a read the user may have been asked about,
|
|
@@ -218,6 +280,7 @@ async function headersFor(tok) {
|
|
|
218
280
|
"x-cursor-client-version": await clientVersion(),
|
|
219
281
|
"x-cursor-client-type": "cli",
|
|
220
282
|
"x-ghost-mode": "true",
|
|
283
|
+
...await teamHeader(tok),
|
|
221
284
|
}
|
|
222
285
|
}
|
|
223
286
|
|
|
@@ -456,7 +519,7 @@ async function hardLimit(tok) {
|
|
|
456
519
|
try {
|
|
457
520
|
const res = await fetch(API + "/aiserver.v1.DashboardService/GetHardLimit", {
|
|
458
521
|
method: "POST",
|
|
459
|
-
headers: { Authorization: `Bearer ${tok}`, "Content-Type": "application/json", "Connect-Protocol-Version": "1" },
|
|
522
|
+
headers: { Authorization: `Bearer ${tok}`, "Content-Type": "application/json", "Connect-Protocol-Version": "1", ...await teamHeader(tok) },
|
|
460
523
|
body: "{}",
|
|
461
524
|
signal: AbortSignal.timeout(15_000),
|
|
462
525
|
})
|
|
@@ -481,7 +544,7 @@ async function usage(tok, ids) {
|
|
|
481
544
|
const [res, hard] = await Promise.all([
|
|
482
545
|
fetch(API + "/aiserver.v1.DashboardService/GetCurrentPeriodUsage", {
|
|
483
546
|
method: "POST",
|
|
484
|
-
headers: { Authorization: `Bearer ${tok}`, "Content-Type": "application/json", "Connect-Protocol-Version": "1" },
|
|
547
|
+
headers: { Authorization: `Bearer ${tok}`, "Content-Type": "application/json", "Connect-Protocol-Version": "1", ...await teamHeader(tok) },
|
|
485
548
|
body: "{}",
|
|
486
549
|
signal: AbortSignal.timeout(15_000),
|
|
487
550
|
}),
|
|
@@ -1515,7 +1578,7 @@ async function usageEvents(tok, conv, since, want, { poll = false, first = STEP_
|
|
|
1515
1578
|
const res = await fetch(API + "/aiserver.v1.DashboardService/GetFilteredUsageEvents", {
|
|
1516
1579
|
method: "POST",
|
|
1517
1580
|
headers: await headersFor(tok),
|
|
1518
|
-
body: JSON.stringify({
|
|
1581
|
+
body: JSON.stringify({ ...await teamBody(tok), startDate: String(since - 60_000), endDate: String(Date.now() + 60_000), page: 1, pageSize: 50 }),
|
|
1519
1582
|
signal: abortFetch && signal ? AbortSignal.any([signal, AbortSignal.timeout(STEP_WAIT.ask)]) : AbortSignal.timeout(STEP_WAIT.ask),
|
|
1520
1583
|
})
|
|
1521
1584
|
if (res.status >= 400 && res.status < 500 && res.status !== 429) {
|
|
@@ -2149,6 +2212,7 @@ async function runNative({ tok, base, id, tools, conv, convID, signal, maxMode,
|
|
|
2149
2212
|
"x-cursor-client-type": "cli", // else Cursor adds a prompt of its own
|
|
2150
2213
|
"x-ghost-mode": "true", // privacy mode: nothing kept for training
|
|
2151
2214
|
"x-request-id": randomUUID(),
|
|
2215
|
+
...await teamHeader(tok),
|
|
2152
2216
|
// none of Cursor's own tools, only the caller's
|
|
2153
2217
|
"x-cursor-agent-allowed-tools": "mcp_tool_call,get_mcp_tools_tool_call",
|
|
2154
2218
|
},
|
|
@@ -2237,6 +2301,7 @@ async function runOnce({ tok, base, id, tools, conv, convID, signal, maxMode, pa
|
|
|
2237
2301
|
"x-cursor-client-type": "cli", // else Cursor adds a prompt of its own
|
|
2238
2302
|
"x-ghost-mode": "true", // privacy mode: nothing kept for training
|
|
2239
2303
|
"x-request-id": randomUUID(),
|
|
2304
|
+
...await teamHeader(tok),
|
|
2240
2305
|
// none of Cursor's own tools, only the caller's
|
|
2241
2306
|
"x-cursor-agent-allowed-tools": "mcp_tool_call,get_mcp_tools_tool_call",
|
|
2242
2307
|
},
|
|
@@ -2887,4 +2952,4 @@ export async function CursorAuthPlugin() {
|
|
|
2887
2952
|
}
|
|
2888
2953
|
|
|
2889
2954
|
// for tests
|
|
2890
|
-
export const _internal = { keychain, keychainToken, nativeTransport, onDemandOf,resetH1: () => (h1Until = 0), onH1: () => Date.now() < h1Until, H2, H1, h2Blocked, open, openH1, openH2, STEP_WAIT, stepUsage, usageKey, 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 }
|
|
2955
|
+
export const _internal = { keychain, keychainToken, nativeTransport, onDemandOf,resetH1: () => (h1Until = 0), onH1: () => Date.now() < h1Until, resetTeam: () => { teamRead.at = 0; teamRead.info = null }, expireTeam: () => (teamRead.at = 0),H2, H1, h2Blocked, open, openH1, openH2, STEP_WAIT, stepUsage, usageKey, 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.23",
|
|
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",
|