@goodandready/dsh-subscriptions 0.6.50 → 0.6.51
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/blob.js +35 -2
- package/lib/import-auth.js +31 -3
- package/lib/jwt.js +33 -0
- package/lib/vendors/antigravity.js +15 -3
- package/package.json +1 -1
package/lib/blob.js
CHANGED
|
@@ -2,6 +2,33 @@ function asString(value) {
|
|
|
2
2
|
return value == null ? "" : String(value)
|
|
3
3
|
}
|
|
4
4
|
|
|
5
|
+
export function normalizeExpiresAt(raw) {
|
|
6
|
+
if (raw == null || raw === "") return 0
|
|
7
|
+
if (typeof raw === "number") {
|
|
8
|
+
if (!Number.isFinite(raw) || raw <= 0) return 0
|
|
9
|
+
return raw < 1e11 ? Math.floor(raw * 1000) : Math.floor(raw)
|
|
10
|
+
}
|
|
11
|
+
if (typeof raw === "string") {
|
|
12
|
+
const trimmed = raw.trim()
|
|
13
|
+
if (!trimmed) return 0
|
|
14
|
+
if (/^\d+(\.\d+)?$/.test(trimmed)) {
|
|
15
|
+
const num = Number(trimmed)
|
|
16
|
+
if (!Number.isFinite(num) || num <= 0) return 0
|
|
17
|
+
return num < 1e11 ? Math.floor(num * 1000) : Math.floor(num)
|
|
18
|
+
}
|
|
19
|
+
const parsed = Date.parse(trimmed)
|
|
20
|
+
if (Number.isFinite(parsed) && parsed > 0) {
|
|
21
|
+
return parsed
|
|
22
|
+
}
|
|
23
|
+
return 0
|
|
24
|
+
}
|
|
25
|
+
if (raw instanceof Date) {
|
|
26
|
+
const t = raw.getTime()
|
|
27
|
+
return Number.isFinite(t) && t > 0 ? t : 0
|
|
28
|
+
}
|
|
29
|
+
return 0
|
|
30
|
+
}
|
|
31
|
+
|
|
5
32
|
export function serializeBlob(obj) {
|
|
6
33
|
if (!obj || typeof obj !== "object") throw new Error("invalid oauth blob")
|
|
7
34
|
const accessToken = asString(obj.accessToken)
|
|
@@ -13,11 +40,14 @@ export function serializeBlob(obj) {
|
|
|
13
40
|
...obj,
|
|
14
41
|
accessToken,
|
|
15
42
|
refreshToken,
|
|
16
|
-
expiresAt:
|
|
43
|
+
expiresAt: normalizeExpiresAt(obj.expiresAt),
|
|
17
44
|
label: asString(obj.label),
|
|
18
45
|
email: asString(obj.email),
|
|
19
46
|
accountId: asString(obj.accountId),
|
|
20
47
|
projectId: asString(obj.projectId),
|
|
48
|
+
...(obj.clientId ? { clientId: asString(obj.clientId) } : {}),
|
|
49
|
+
...(obj.clientSecret ? { clientSecret: asString(obj.clientSecret) } : {}),
|
|
50
|
+
...(obj.idToken ? { idToken: asString(obj.idToken) } : {}),
|
|
21
51
|
...(Array.isArray(obj.usage) ? { usage: obj.usage } : {}),
|
|
22
52
|
...(obj.usageAt ? { usageAt: Number(obj.usageAt) } : {}),
|
|
23
53
|
})
|
|
@@ -30,11 +60,14 @@ export function parseBlob(text) {
|
|
|
30
60
|
...obj,
|
|
31
61
|
accessToken: asString(obj.accessToken),
|
|
32
62
|
refreshToken: asString(obj.refreshToken),
|
|
33
|
-
expiresAt:
|
|
63
|
+
expiresAt: normalizeExpiresAt(obj.expiresAt),
|
|
34
64
|
label: asString(obj.label),
|
|
35
65
|
email: asString(obj.email),
|
|
36
66
|
accountId: asString(obj.accountId),
|
|
37
67
|
projectId: asString(obj.projectId),
|
|
68
|
+
...(obj.clientId ? { clientId: asString(obj.clientId) } : {}),
|
|
69
|
+
...(obj.clientSecret ? { clientSecret: asString(obj.clientSecret) } : {}),
|
|
70
|
+
...(obj.idToken ? { idToken: asString(obj.idToken) } : {}),
|
|
38
71
|
...(Array.isArray(obj.usage) ? { usage: obj.usage } : {}),
|
|
39
72
|
...(obj.usageAt ? { usageAt: Number(obj.usageAt) } : {}),
|
|
40
73
|
}
|
package/lib/import-auth.js
CHANGED
|
@@ -2,6 +2,8 @@ import { readFile } from 'node:fs/promises'
|
|
|
2
2
|
import { homedir } from 'node:os'
|
|
3
3
|
import { join } from 'node:path'
|
|
4
4
|
import { readClaudeCredentials } from './vendors/claude-cli.js'
|
|
5
|
+
import { normalizeExpiresAt } from './blob.js'
|
|
6
|
+
import { isGoogleClientId, extractGoogleClientIdFromJwt, emailFromToken } from './jwt.js'
|
|
5
7
|
|
|
6
8
|
// Test seam: a fixture root overrides the real home for the duration of a
|
|
7
9
|
// public call. Undefined (the default) keeps production behaviour.
|
|
@@ -67,17 +69,20 @@ async function discoverLocalCliSessionsInner() {
|
|
|
67
69
|
// 3. Antigravity / Gemini CLI
|
|
68
70
|
const agyPaths = [
|
|
69
71
|
homeFile('.gemini', 'antigravity-cli', 'antigravity-oauth-token'),
|
|
72
|
+
homeFile('.gemini', 'oauth_creds.json'),
|
|
70
73
|
homeFile('.cli-proxy-api', 'antigravity.json'),
|
|
71
74
|
]
|
|
72
75
|
for (const p of agyPaths) {
|
|
73
76
|
const agy = await readJson(p)
|
|
74
77
|
const tok = agy && agy.token ? agy.token : agy
|
|
75
78
|
if (tok && (tok.access_token || tok.accessToken)) {
|
|
79
|
+
const idToken = agy?.id_token || agy?.idToken || tok?.id_token || tok?.idToken || ''
|
|
80
|
+
const email = agy?.email || tok?.account || (idToken ? emailFromToken(idToken) : '') || ''
|
|
76
81
|
detected.antigravity = {
|
|
77
82
|
provider: 'antigravity',
|
|
78
83
|
source: 'file',
|
|
79
84
|
path: p,
|
|
80
|
-
email:
|
|
85
|
+
email: email || 'Antigravity User',
|
|
81
86
|
hasRefreshToken: !!(tok.refresh_token || tok.refreshToken),
|
|
82
87
|
}
|
|
83
88
|
break
|
|
@@ -331,12 +336,35 @@ export async function loadLocalCliBlob(provider, options = {}) {
|
|
|
331
336
|
|
|
332
337
|
if (canonical === 'antigravity') {
|
|
333
338
|
const tok = raw.token || raw
|
|
339
|
+
const idToken = raw.id_token || raw.idToken || tok.id_token || tok.idToken || ''
|
|
340
|
+
|
|
341
|
+
const directClientId = raw.client_id || raw.clientId || tok.client_id || tok.clientId ||
|
|
342
|
+
raw.installed?.client_id || raw.web?.client_id || ''
|
|
343
|
+
let clientId = ''
|
|
344
|
+
if (typeof directClientId === 'string' && isGoogleClientId(directClientId)) {
|
|
345
|
+
clientId = directClientId.trim()
|
|
346
|
+
} else if (idToken) {
|
|
347
|
+
clientId = extractGoogleClientIdFromJwt(idToken)
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
const directSecret = raw.client_secret || raw.clientSecret || tok.client_secret || tok.clientSecret ||
|
|
351
|
+
raw.installed?.client_secret || raw.web?.client_secret || ''
|
|
352
|
+
const clientSecret = typeof directSecret === 'string' ? directSecret.trim() : ''
|
|
353
|
+
|
|
354
|
+
const rawExpiry = tok.expiry ?? tok.expires_at ?? tok.expiresAt ?? tok.expiry_date ?? raw.expiry_date ?? raw.expiry ?? (tok.expires_in ? Date.now() + Number(tok.expires_in) * 1000 : 0)
|
|
355
|
+
const expiresAt = normalizeExpiresAt(rawExpiry)
|
|
356
|
+
|
|
357
|
+
const email = raw.email || tok.account || (idToken ? emailFromToken(idToken) : '') || ''
|
|
358
|
+
|
|
334
359
|
return {
|
|
335
360
|
accessToken: tok.access_token || tok.accessToken || '',
|
|
336
361
|
refreshToken: tok.refresh_token || tok.refreshToken || '',
|
|
337
|
-
expiresAt
|
|
338
|
-
email
|
|
362
|
+
expiresAt,
|
|
363
|
+
email,
|
|
339
364
|
projectId: raw.project_id || raw.projectId || tok.project_id || '',
|
|
365
|
+
...(clientId ? { clientId } : {}),
|
|
366
|
+
...(clientSecret ? { clientSecret } : {}),
|
|
367
|
+
...(idToken ? { idToken } : {}),
|
|
340
368
|
}
|
|
341
369
|
}
|
|
342
370
|
|
package/lib/jwt.js
CHANGED
|
@@ -26,3 +26,36 @@ export function emailFromToken(token) {
|
|
|
26
26
|
if (!payload) return ''
|
|
27
27
|
return String(payload.email || payload.preferred_username || '')
|
|
28
28
|
}
|
|
29
|
+
|
|
30
|
+
export function isGoogleClientId(str) {
|
|
31
|
+
if (typeof str !== 'string') return false
|
|
32
|
+
const trimmed = str.trim()
|
|
33
|
+
return /^[0-9]+-[a-zA-Z0-9_-]+\.apps\.googleusercontent\.com$/.test(trimmed)
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export function extractGoogleClientIdFromJwt(token) {
|
|
37
|
+
const payload = decodeJwtPayload(token)
|
|
38
|
+
if (!payload || typeof payload !== 'object') return ''
|
|
39
|
+
|
|
40
|
+
const azp = typeof payload.azp === 'string' ? payload.azp.trim() : ''
|
|
41
|
+
const rawAud = payload.aud
|
|
42
|
+
|
|
43
|
+
const audiences = Array.isArray(rawAud)
|
|
44
|
+
? rawAud.filter(a => typeof a === 'string').map(a => a.trim())
|
|
45
|
+
: (typeof rawAud === 'string' && rawAud.trim() ? [rawAud.trim()] : [])
|
|
46
|
+
|
|
47
|
+
if (azp) {
|
|
48
|
+
if (!isGoogleClientId(azp)) return ''
|
|
49
|
+
if (audiences.length > 0 && !audiences.includes(azp)) {
|
|
50
|
+
return ''
|
|
51
|
+
}
|
|
52
|
+
return azp
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const validAuds = audiences.filter(isGoogleClientId)
|
|
56
|
+
if (validAuds.length === 1 && (audiences.length === 1 || audiences.every(a => a === validAuds[0]))) {
|
|
57
|
+
return validAuds[0]
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
return ''
|
|
61
|
+
}
|
|
@@ -109,15 +109,25 @@ export async function exchangeCode(cfg, pkce, code, fetchImpl) {
|
|
|
109
109
|
redirect_uri: cfg.redirectUri,
|
|
110
110
|
code_verifier: pkce.verifier,
|
|
111
111
|
}, fetchImpl)
|
|
112
|
-
const blob = tokenBlobFromOAuth(json
|
|
112
|
+
const blob = tokenBlobFromOAuth(json, {
|
|
113
|
+
clientId: cfg.clientId,
|
|
114
|
+
...(cfg.clientSecret ? { clientSecret: cfg.clientSecret } : {}),
|
|
115
|
+
})
|
|
113
116
|
const labeled = { ...blob, email: blob.email || emailFromToken(blob.accessToken), label: blob.label || 'Antigravity' }
|
|
114
117
|
return withProject(labeled, fetchImpl)
|
|
115
118
|
}
|
|
116
119
|
|
|
117
120
|
export async function refresh(cfg, blob, fetchImpl) {
|
|
121
|
+
const clientId = (cfg?.clientId && String(cfg.clientId).trim()) || (blob?.clientId && String(blob.clientId).trim()) || ''
|
|
122
|
+
const clientSecret = (cfg?.clientSecret && String(cfg.clientSecret).trim()) || (blob?.clientSecret && String(blob.clientSecret).trim()) || ''
|
|
123
|
+
|
|
124
|
+
if (!clientId) {
|
|
125
|
+
throw new Error('Google OAuth refresh requires a Client ID. Configure antigravityClientId in plugin settings or re-import credentials with valid OAuth client metadata.')
|
|
126
|
+
}
|
|
127
|
+
|
|
118
128
|
const json = await formTokenRequest(TOKEN, {
|
|
119
|
-
client_id:
|
|
120
|
-
client_secret:
|
|
129
|
+
client_id: clientId,
|
|
130
|
+
...(clientSecret ? { client_secret: clientSecret } : {}),
|
|
121
131
|
grant_type: 'refresh_token',
|
|
122
132
|
refresh_token: blob.refreshToken,
|
|
123
133
|
}, fetchImpl)
|
|
@@ -128,6 +138,8 @@ export async function refresh(cfg, blob, fetchImpl) {
|
|
|
128
138
|
paidTierId: blob.paidTierId,
|
|
129
139
|
paidTierName: blob.paidTierName,
|
|
130
140
|
sessionId: blob.sessionId,
|
|
141
|
+
clientId,
|
|
142
|
+
...(clientSecret ? { clientSecret } : {}),
|
|
131
143
|
})
|
|
132
144
|
}
|
|
133
145
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@goodandready/dsh-subscriptions",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.51",
|
|
4
4
|
"description": "Use ChatGPT Codex, Claude, Grok, Antigravity, Kimi, GLM, Cursor, Kiro, Copilot, Qwen, ERNIE, Spark, JetBrains, Perplexity, Replit, and Cody subscriptions as DeepSeek Harness LLM providers via OAuth.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|