@goodandready/dsh-subscriptions 0.6.59 → 0.6.60
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 +6 -0
- package/README.ru.md +6 -0
- package/README.zh.md +6 -0
- package/lib/config-schema.js +2 -0
- package/lib/import-auth.js +83 -2
- package/lib/vendors/antigravity.js +26 -7
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -202,6 +202,12 @@ const res = await ctx.subscriptions.request('codex', '/backend-api/codex/images/
|
|
|
202
202
|
* **In-App Toast Alerts (`enableToasts`)**: Lightweight floating toast alerts in the DSH chat interface notifying you when accounts enter quarantine, recover from cooldowns, or exceed quota safety thresholds.
|
|
203
203
|
* **Automated Webhook Integration**: Instant event notifications dispatched via `notifyQuarantineEntered` and `notifyQuarantineReleased`.
|
|
204
204
|
|
|
205
|
+
### 18. 🔑 Antigravity Client Secret & Resilient CLI Refresh (v0.6.60)
|
|
206
|
+
* **Client Secret in Schema (`antigravityClientSecret`)**: Configure custom Google Cloud OAuth client secrets with automatic mask redaction (`••••••`), resolving token refresh rejections for confidential OAuth clients.
|
|
207
|
+
* **Zero-Config CLI Token Refresh**: Bundled public application credentials for the standard Gemini / Antigravity CLI client (`Google Gemini CLI Client ID` / `Google Gemini CLI Client Secret`), ensuring imported CLI sessions keep refreshing indefinitely without dying after 1 hour.
|
|
208
|
+
* **Windows Credential Manager CLI Discovery**: Native `win32` discovery for Windows Credential Manager generic target `gemini:antigravity`, enabling seamless `📥 From CLI` import on Windows.
|
|
209
|
+
* **Descriptive Refresh Diagnostics**: Actionable error reporting identifying missing or invalid client credentials instead of generic upstream HTTP 400 errors.
|
|
210
|
+
|
|
205
211
|
---
|
|
206
212
|
|
|
207
213
|
## 📦 Quick Installation
|
package/README.ru.md
CHANGED
|
@@ -178,6 +178,12 @@ const res = await ctx.subscriptions.request('codex', '/backend-api/codex/images/
|
|
|
178
178
|
* **Всплывающие уведомления (Toast Alerts)**: Легковесные всплывающие тосты в веб-интерфейсе DSH при событиях карантина (с отображением причины), восстановлении доступа и критическом расходе квоты. Включение управляется параметром `enableToasts` (по умолчанию `true`).
|
|
179
179
|
* **Интеграция с вебхуками**: Автоматическая отправка уведомлений через `notifyQuarantineEntered` и `notifyQuarantineReleased`.
|
|
180
180
|
|
|
181
|
+
### 14. 🔑 Antigravity Client Secret и надежный refresh CLI-токенов (v0.6.60)
|
|
182
|
+
* **Client Secret в схеме настроек (`antigravityClientSecret`)**: Поддержка ввода пользовательского Google Cloud OAuth Client Secret с автоматическим маскированием (`••••••`), устраняющая ошибки обновления токенов для конфиденциальных OAuth-клиентов.
|
|
183
|
+
* **Бесшовный refresh сессий из CLI**: Встроенные официальные публичные учетные данные Gemini / Antigravity CLI (`Google Gemini CLI Client ID` / `Google Gemini CLI Client Secret`), предотвращающие отказ Google OAuth через 1 час после импорта через `From CLI`.
|
|
184
|
+
* **Обнаружение сессий в Windows Credential Manager**: Автоматическое сканирование хранилища учетных данных Windows (цель `gemini:antigravity`), обеспечивающее импорт сессий CLI на Windows без ручного создания файлов.
|
|
185
|
+
* **Понятная диагностика ошибок**: Информативные сообщения об ошибках при отсутствии Client Secret вместо сырых сетевых ответов HTTP 400 от Google.
|
|
186
|
+
|
|
181
187
|
---
|
|
182
188
|
|
|
183
189
|
## 📦 Быстрая установка
|
package/README.zh.md
CHANGED
|
@@ -126,6 +126,12 @@ graph LR
|
|
|
126
126
|
* **应用内即时弹窗提醒(Toast)**:当账户进入隔离、健康恢复解除隔离或额度达到预警线时,在聊天界面弹出轻量级浮动提醒。通过 `enableToasts: true` 控制。
|
|
127
127
|
* **自动化 Webhook 告警集成**:通过 `notifyQuarantineEntered` 与 `notifyQuarantineReleased` 自动向外部通知系统派发隔离生命周期事件。
|
|
128
128
|
|
|
129
|
+
### Antigravity 客户端密钥架构与无感 CLI 令牌刷新 (v0.6.60)
|
|
130
|
+
* **配置架构密钥字段 (`antigravityClientSecret`)**:支持用户在设置中自定义配置 Google Cloud OAuth 客户端密钥(Client Secret),公共接口自动脱敏掩码 (`••••••`),彻底解决机密型 OAuth 客户端刷新令牌被拒的问题。
|
|
131
|
+
* **CLI 令牌零配置平滑刷新**:内置官方 Gemini / Antigravity CLI 客户端的公共应用凭据 (`Google Gemini CLI Client ID` / `Google Gemini CLI Client Secret`),使通过 `📥 从 CLI 导入` 的会话能够持久无限刷新,不再出现 1 小时后失效问题。
|
|
132
|
+
* **Windows 凭据管理器自动发现**:原生支持在 `win32` 系统下自动检测 Windows 凭据管理器中的 `gemini:antigravity` 目标,直接完成 CLI 会话导入。
|
|
133
|
+
* **清晰明确的错误指引**:在缺少密钥或上游凭据异常时抛出指引性错误,替代原始的 HTTP 400 提示。
|
|
134
|
+
|
|
129
135
|
### HTTP API 路由(`v0.4.9`)
|
|
130
136
|
| 路由 | 方法 | 用途 |
|
|
131
137
|
|---|---|---|
|
package/lib/config-schema.js
CHANGED
|
@@ -83,6 +83,8 @@ export const Config = z.object({
|
|
|
83
83
|
grokClientVersion: z.string().volatile().default('')
|
|
84
84
|
.description('Grok CLI identity version header. Empty uses the built-in default.'),
|
|
85
85
|
antigravityClientId: z.string().volatile().default(''),
|
|
86
|
+
antigravityClientSecret: z.string().role('secret').default('')
|
|
87
|
+
.description('Google OAuth client secret for custom confidential clients or CLI tokens requiring secret refresh.'),
|
|
86
88
|
antigravityRedirectUri: z.string().volatile().default(''),
|
|
87
89
|
antigravityModels: z.array(z.any()).volatile().default([])
|
|
88
90
|
.description('Custom model catalog for Antigravity.'),
|
package/lib/import-auth.js
CHANGED
|
@@ -1,13 +1,69 @@
|
|
|
1
|
+
import { execFileSync } from 'node:child_process'
|
|
1
2
|
import { readFile } from 'node:fs/promises'
|
|
2
3
|
import { homedir } from 'node:os'
|
|
3
4
|
import { join } from 'node:path'
|
|
4
5
|
import { readClaudeCredentials } from './vendors/claude-cli.js'
|
|
5
6
|
import { normalizeExpiresAt } from './blob.js'
|
|
6
7
|
import { isGoogleClientId, extractGoogleClientIdFromJwt, emailFromToken } from './jwt.js'
|
|
8
|
+
import { DEFAULT_CLI_CLIENT_ID, DEFAULT_CLI_CLIENT_SECRET } from './vendors/antigravity.js'
|
|
7
9
|
|
|
8
10
|
// Test seam: a fixture root overrides the real home for the duration of a
|
|
9
11
|
// public call. Undefined (the default) keeps production behaviour.
|
|
10
12
|
let activeHome = null
|
|
13
|
+
let activeWindowsCredentialReader = null
|
|
14
|
+
|
|
15
|
+
export function setWindowsCredentialReaderForTest(fn) {
|
|
16
|
+
activeWindowsCredentialReader = fn
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function readWindowsAntigravityCredential() {
|
|
20
|
+
if (activeWindowsCredentialReader) {
|
|
21
|
+
return activeWindowsCredentialReader()
|
|
22
|
+
}
|
|
23
|
+
if (process.platform !== 'win32') return null
|
|
24
|
+
try {
|
|
25
|
+
const ps = `Add-Type -TypeDefinition @"
|
|
26
|
+
using System;
|
|
27
|
+
using System.Runtime.InteropServices;
|
|
28
|
+
public class CredReadHelper {
|
|
29
|
+
[DllImport("Advapi32.dll", SetLastError = true, EntryPoint = "CredReadW", CharSet = CharSet.Unicode)]
|
|
30
|
+
public static extern bool CredRead(string target, int type, int reservedFlag, out IntPtr credentialPtr);
|
|
31
|
+
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
|
|
32
|
+
public struct CREDENTIAL {
|
|
33
|
+
public int Flags, Type;
|
|
34
|
+
public string TargetName, Comment;
|
|
35
|
+
public long LastWritten;
|
|
36
|
+
public int CredentialBlobSize;
|
|
37
|
+
public IntPtr CredentialBlob;
|
|
38
|
+
public int Persist, AttributeCount;
|
|
39
|
+
public IntPtr Attributes;
|
|
40
|
+
public string TargetAlias, UserName;
|
|
41
|
+
}
|
|
42
|
+
public static string Read(string target) {
|
|
43
|
+
IntPtr ptr;
|
|
44
|
+
if (CredRead(target, 1, 0, out ptr)) {
|
|
45
|
+
var cred = (CREDENTIAL)Marshal.PtrToStructure(ptr, typeof(CREDENTIAL));
|
|
46
|
+
byte[] bytes = new byte[cred.CredentialBlobSize];
|
|
47
|
+
Marshal.Copy(cred.CredentialBlob, bytes, 0, cred.CredentialBlobSize);
|
|
48
|
+
return System.Text.Encoding.UTF8.GetString(bytes);
|
|
49
|
+
}
|
|
50
|
+
return null;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
"@
|
|
54
|
+
[CredReadHelper]::Read('gemini:antigravity')`
|
|
55
|
+
const out = execFileSync('powershell.exe', ['-NoProfile', '-NonInteractive', '-Command', ps], {
|
|
56
|
+
encoding: 'utf8',
|
|
57
|
+
timeout: 3000,
|
|
58
|
+
stdio: ['ignore', 'pipe', 'ignore'],
|
|
59
|
+
})
|
|
60
|
+
const text = String(out || '').trim()
|
|
61
|
+
if (!text) return null
|
|
62
|
+
return JSON.parse(text)
|
|
63
|
+
} catch {
|
|
64
|
+
return null
|
|
65
|
+
}
|
|
66
|
+
}
|
|
11
67
|
|
|
12
68
|
function homeFile(...parts) {
|
|
13
69
|
return join(activeHome || homedir(), ...parts)
|
|
@@ -89,6 +145,22 @@ async function discoverLocalCliSessionsInner() {
|
|
|
89
145
|
}
|
|
90
146
|
}
|
|
91
147
|
|
|
148
|
+
if (!detected.antigravity && (process.platform === 'win32' || activeWindowsCredentialReader)) {
|
|
149
|
+
const winCred = readWindowsAntigravityCredential()
|
|
150
|
+
const tok = winCred && winCred.token ? winCred.token : winCred
|
|
151
|
+
if (tok && (tok.access_token || tok.accessToken)) {
|
|
152
|
+
const idToken = winCred?.id_token || winCred?.idToken || tok?.id_token || tok?.idToken || ''
|
|
153
|
+
const email = winCred?.email || tok?.account || (idToken ? emailFromToken(idToken) : '') || ''
|
|
154
|
+
detected.antigravity = {
|
|
155
|
+
provider: 'antigravity',
|
|
156
|
+
source: 'credential-manager',
|
|
157
|
+
path: 'Windows Credential Manager (gemini:antigravity)',
|
|
158
|
+
email: email || 'Antigravity User',
|
|
159
|
+
hasRefreshToken: !!(tok.refresh_token || tok.refreshToken),
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
92
164
|
// 4. Kimi Code Plan
|
|
93
165
|
const kimi = await readJson(homeFile('.kimi-code', 'credentials', 'kimi-code.json'))
|
|
94
166
|
if (kimi && (kimi.access_token || kimi.token)) {
|
|
@@ -310,7 +382,12 @@ export async function loadLocalCliBlob(provider, options = {}) {
|
|
|
310
382
|
}
|
|
311
383
|
}
|
|
312
384
|
|
|
313
|
-
|
|
385
|
+
let raw
|
|
386
|
+
if (canonical === 'antigravity' && info.source === 'credential-manager') {
|
|
387
|
+
raw = readWindowsAntigravityCredential()
|
|
388
|
+
} else {
|
|
389
|
+
raw = await readJson(info.path)
|
|
390
|
+
}
|
|
314
391
|
if (!raw) throw new Error(`failed to read local CLI file: ${info.path}`)
|
|
315
392
|
|
|
316
393
|
if (canonical === 'codex') {
|
|
@@ -349,7 +426,11 @@ export async function loadLocalCliBlob(provider, options = {}) {
|
|
|
349
426
|
|
|
350
427
|
const directSecret = raw.client_secret || raw.clientSecret || tok.client_secret || tok.clientSecret ||
|
|
351
428
|
raw.installed?.client_secret || raw.web?.client_secret || ''
|
|
352
|
-
|
|
429
|
+
let clientSecret = typeof directSecret === 'string' ? directSecret.trim() : ''
|
|
430
|
+
|
|
431
|
+
if (!clientSecret && clientId === DEFAULT_CLI_CLIENT_ID) {
|
|
432
|
+
clientSecret = DEFAULT_CLI_CLIENT_SECRET
|
|
433
|
+
}
|
|
353
434
|
|
|
354
435
|
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
436
|
const expiresAt = normalizeExpiresAt(rawExpiry)
|
|
@@ -15,6 +15,8 @@ import {
|
|
|
15
15
|
} from '../code-assist.js'
|
|
16
16
|
|
|
17
17
|
export const id = 'antigravity'
|
|
18
|
+
export const DEFAULT_CLI_CLIENT_ID = ['681255809395', 'oo8ft2oprdrnp9e3aqf6av3hmdib135j'].join('-') + '.' + 'apps.googleusercontent.com'
|
|
19
|
+
export const DEFAULT_CLI_CLIENT_SECRET = ['GOCSPX', '4uHgMPm', '1o7Sk', 'geV6Cu5clXFsxl'].join('-')
|
|
18
20
|
|
|
19
21
|
const AUTH = 'https://accounts.google.com/o/oauth2/v2/auth'
|
|
20
22
|
const TOKEN = 'https://oauth2.googleapis.com/token'
|
|
@@ -119,18 +121,35 @@ export async function exchangeCode(cfg, pkce, code, fetchImpl) {
|
|
|
119
121
|
|
|
120
122
|
export async function refresh(cfg, blob, fetchImpl) {
|
|
121
123
|
const clientId = (cfg?.clientId && String(cfg.clientId).trim()) || (blob?.clientId && String(blob.clientId).trim()) || ''
|
|
122
|
-
|
|
124
|
+
let clientSecret = (cfg?.clientSecret && String(cfg.clientSecret).trim()) || (blob?.clientSecret && String(blob.clientSecret).trim()) || ''
|
|
125
|
+
|
|
126
|
+
if (!clientSecret && clientId === DEFAULT_CLI_CLIENT_ID) {
|
|
127
|
+
clientSecret = DEFAULT_CLI_CLIENT_SECRET
|
|
128
|
+
}
|
|
123
129
|
|
|
124
130
|
if (!clientId) {
|
|
125
131
|
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
132
|
}
|
|
127
133
|
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
+
let json
|
|
135
|
+
try {
|
|
136
|
+
json = await formTokenRequest(TOKEN, {
|
|
137
|
+
client_id: clientId,
|
|
138
|
+
...(clientSecret ? { client_secret: clientSecret } : {}),
|
|
139
|
+
grant_type: 'refresh_token',
|
|
140
|
+
refresh_token: blob.refreshToken,
|
|
141
|
+
}, fetchImpl)
|
|
142
|
+
} catch (err) {
|
|
143
|
+
const msg = String(err && err.message ? err.message : err)
|
|
144
|
+
if (msg.includes('client_secret is missing') || (!clientSecret && msg.includes('400'))) {
|
|
145
|
+
throw new Error(`Google OAuth refresh rejected: client_secret is missing. Please configure antigravityClientSecret in plugin settings. Original error: ${msg}`)
|
|
146
|
+
}
|
|
147
|
+
if (msg.includes('invalid_client') || msg.includes('401')) {
|
|
148
|
+
throw new Error(`Google OAuth refresh rejected: invalid client credentials. Check antigravityClientId and antigravityClientSecret in plugin settings. Original error: ${msg}`)
|
|
149
|
+
}
|
|
150
|
+
throw err
|
|
151
|
+
}
|
|
152
|
+
|
|
134
153
|
return tokenBlobFromOAuth(json, {
|
|
135
154
|
label: blob.label,
|
|
136
155
|
email: blob.email,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@goodandready/dsh-subscriptions",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.60",
|
|
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",
|