@goodandready/dsh-subscriptions 0.5.6 → 0.5.21
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/atomic-lock.js +35 -0
- package/lib/backoff.js +26 -0
- package/lib/client.js +211 -15
- package/lib/coalesce-stream.js +22 -0
- package/lib/code-assist.js +4 -4
- package/lib/cookie-bridge.js +8 -0
- package/lib/credit-detect.js +22 -0
- package/lib/encrypted-bundle.js +46 -0
- package/lib/import-auth.js +74 -7
- package/lib/importers/coding-agents.js +37 -0
- package/lib/index.js +35 -7
- package/lib/latency-heatmap.js +26 -0
- package/lib/latency-score.js +36 -0
- package/lib/multi-tenant.js +16 -0
- package/lib/network-benchmark.js +23 -0
- package/lib/pkce-store.js +28 -0
- package/lib/preflight-tokens.js +23 -0
- package/lib/proactive-refresh.js +37 -0
- package/lib/prompt-cache-warmer.js +44 -0
- package/lib/quarantine.js +38 -0
- package/lib/ratelimit-parser.js +24 -0
- package/lib/reconnect-stream.js +21 -0
- package/lib/reset-toast.js +38 -0
- package/lib/revocation.js +25 -0
- package/lib/rotate.js +70 -17
- package/lib/savings-calculator.js +14 -0
- package/lib/session-pin.js +29 -0
- package/lib/state-recovery.js +33 -0
- package/lib/stream-rotate.js +34 -11
- package/lib/telemetry.js +28 -0
- package/lib/token-speedometer.js +26 -0
- package/lib/vendors/antigravity.js +18 -5
- package/lib/vendors/claude-cli.js +24 -0
- package/lib/vendors/cody.js +72 -0
- package/lib/vendors/copilot.js +194 -0
- package/lib/vendors/cursor.js +1 -1
- package/lib/vendors/ernie.js +93 -0
- package/lib/vendors/glm.js +1 -1
- package/lib/vendors/index.js +26 -1
- package/lib/vendors/jetbrains.js +79 -0
- package/lib/vendors/kimi.js +1 -1
- package/lib/vendors/perplexity.js +79 -0
- package/lib/vendors/qwen.js +89 -0
- package/lib/vendors/replit.js +72 -0
- package/lib/vendors/spark.js +77 -0
- package/lib/wire.js +4 -2
- package/lib/zero-trace-logger.js +27 -0
- package/package.json +2 -5
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { open, unlink } from 'node:fs/promises'
|
|
2
|
+
|
|
3
|
+
export async function withFileLock(filePath, fn, timeoutMs = 5000) {
|
|
4
|
+
const lockPath = `${filePath}.lock`
|
|
5
|
+
const start = Date.now()
|
|
6
|
+
let handle = null
|
|
7
|
+
|
|
8
|
+
while (Date.now() - start < timeoutMs) {
|
|
9
|
+
try {
|
|
10
|
+
handle = await open(lockPath, 'wx')
|
|
11
|
+
break
|
|
12
|
+
} catch (err) {
|
|
13
|
+
if (err.code === 'EEXIST') {
|
|
14
|
+
await new Promise((r) => setTimeout(r, 40))
|
|
15
|
+
continue
|
|
16
|
+
}
|
|
17
|
+
throw err
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
if (!handle) {
|
|
22
|
+
throw new Error(`Failed to acquire lock on ${lockPath} after ${timeoutMs}ms`)
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
try {
|
|
26
|
+
return await fn()
|
|
27
|
+
} finally {
|
|
28
|
+
try {
|
|
29
|
+
await handle.close()
|
|
30
|
+
await unlink(lockPath)
|
|
31
|
+
} catch {
|
|
32
|
+
// ignore unlock errors
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
package/lib/backoff.js
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
export async function executeWithRetry(fn, {
|
|
2
|
+
maxRetries = 3,
|
|
3
|
+
initialDelayMs = 200,
|
|
4
|
+
maxDelayMs = 3000,
|
|
5
|
+
factor = 2,
|
|
6
|
+
isRetryable = (err) => true
|
|
7
|
+
} = {}) {
|
|
8
|
+
let attempt = 0
|
|
9
|
+
let delay = initialDelayMs
|
|
10
|
+
|
|
11
|
+
while (true) {
|
|
12
|
+
try {
|
|
13
|
+
return await fn()
|
|
14
|
+
} catch (err) {
|
|
15
|
+
attempt++
|
|
16
|
+
if (attempt > maxRetries || !isRetryable(err)) {
|
|
17
|
+
throw err
|
|
18
|
+
}
|
|
19
|
+
// Exponential backoff with random jitter: delay * (1 + random * 0.3)
|
|
20
|
+
const jitter = 1 + Math.random() * 0.3
|
|
21
|
+
const sleepMs = Math.min(maxDelayMs, Math.round(delay * jitter))
|
|
22
|
+
await new Promise((r) => setTimeout(r, sleepMs))
|
|
23
|
+
delay = Math.min(maxDelayMs, delay * factor)
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
}
|
package/lib/client.js
CHANGED
|
@@ -25,7 +25,12 @@ window.__ModuleLoader__.load({
|
|
|
25
25
|
'.dsub-chevOpen{transform:rotate(180deg)}' +
|
|
26
26
|
'.dsub-body{border-top:1px solid var(--dsw-alias-border-l2);margin:0 16px;padding-bottom:12px}' +
|
|
27
27
|
'.dsub-block{margin-top:10px}' +
|
|
28
|
-
'.dsub-row{display:flex;align-items:center;gap:8px;margin-top:8px}' +
|
|
28
|
+
'.dsub-row{display:flex;align-items:center;flex-wrap:wrap;gap:8px;margin-top:8px}' +
|
|
29
|
+
'.dsub-grow{flex:1 1 180px;min-width:140px}' +
|
|
30
|
+
'.dsub-helpBox{margin-top:8px;padding:12px 14px;border-radius:8px;background:var(--dsw-alias-bg-layer-1);border:1px solid var(--dsw-alias-border-l1);font-size:12px;line-height:1.6}' +
|
|
31
|
+
'.dsub-helpHead{display:flex;align-items:center;justify-content:space-between;font-weight:600;margin-bottom:8px;color:var(--dsw-alias-brand-primary)}' +
|
|
32
|
+
'.dsub-helpStep{margin-bottom:6px;color:var(--dsw-alias-label-primary)}' +
|
|
33
|
+
'.dsub-btnActive{background:var(--dsw-alias-brand-primary);color:#fff;border-color:var(--dsw-alias-brand-primary)}' +
|
|
29
34
|
'.dsub-h{font-size:13.5px;font-weight:600;color:var(--dsw-alias-label-primary)}' +
|
|
30
35
|
'.dsub-sub{font-size:12px;color:var(--dsw-alias-label-secondary)}' +
|
|
31
36
|
'.dsub-dim{font-size:11.5px;color:var(--dsw-alias-label-tertiary)}' +
|
|
@@ -120,6 +125,126 @@ const cssId = 'dsh-subscriptions/settings.module.css'
|
|
|
120
125
|
// пакет, не трогая код этого плагина. Английский — язык по умолчанию,
|
|
121
126
|
// на него же приходится откат, если перевода нет.
|
|
122
127
|
const NS = 'dsh-subscriptions'
|
|
128
|
+
|
|
129
|
+
const INSTRUCTIONS = {
|
|
130
|
+
codex: {
|
|
131
|
+
title: 'OpenAI / ChatGPT Codex',
|
|
132
|
+
stepsRu: [
|
|
133
|
+
'1. Авторизуйтесь через CLI на сервере: выполните "codex login" в терминале.',
|
|
134
|
+
'2. Нажмите кнопку "📥 Из CLI", чтобы сервер автоматически прочитал ~/.codex/auth.json.',
|
|
135
|
+
'3. Либо нажмите кнопку "Авторизация по коду" (Device Login) выше и введите код на auth.openai.com.',
|
|
136
|
+
],
|
|
137
|
+
stepsEn: [
|
|
138
|
+
'1. Log in via CLI on server: run "codex login" in terminal.',
|
|
139
|
+
'2. Click "📥 From CLI" to auto-import ~/.codex/auth.json.',
|
|
140
|
+
'3. Or click "Device Login" above and enter the code at auth.openai.com.',
|
|
141
|
+
],
|
|
142
|
+
link: 'https://chatgpt.com',
|
|
143
|
+
},
|
|
144
|
+
claude: {
|
|
145
|
+
title: 'Claude Code',
|
|
146
|
+
stepsRu: [
|
|
147
|
+
'1. Выполните "claude login" в консоли сервера.',
|
|
148
|
+
'2. Нажмите "📥 Из CLI" — плагин подтянет токен из ~/.claude/credentials.json.',
|
|
149
|
+
'3. Либо скопируйте свой Session Token / API-ключ из консоли Anthropic и нажмите "Сохранить".',
|
|
150
|
+
],
|
|
151
|
+
stepsEn: [
|
|
152
|
+
'1. Run "claude login" in server console.',
|
|
153
|
+
'2. Click "📥 From CLI" — plugin will import ~/.claude/credentials.json.',
|
|
154
|
+
'3. Or paste your Session Token / API Key from Anthropic Console and click "Save token".',
|
|
155
|
+
],
|
|
156
|
+
link: 'https://console.anthropic.com/settings/keys',
|
|
157
|
+
},
|
|
158
|
+
cursor: {
|
|
159
|
+
title: 'Cursor IDE',
|
|
160
|
+
stepsRu: [
|
|
161
|
+
'1. Авторизация через CLI: если Cursor установлен на сервере, нажмите "📥 Из CLI" (считывается из ~/.cursor/cli-config.json).',
|
|
162
|
+
'2. Вручную: откройте Настройки Cursor (кнопка "Подключить" или ссылка ниже), скопируйте WorkOS токен сессии или свой API ключ.',
|
|
163
|
+
'3. Вставьте его в поле ввода и нажмите кнопку "Сохранить".',
|
|
164
|
+
],
|
|
165
|
+
stepsEn: [
|
|
166
|
+
'1. Auto CLI: if Cursor is used on server, click "📥 From CLI" (reads from ~/.cursor/cli-config.json).',
|
|
167
|
+
'2. Manual: open Cursor Settings (Connect button or link below), copy your session token or API key.',
|
|
168
|
+
'3. Paste it into the input field and click "Save token".',
|
|
169
|
+
],
|
|
170
|
+
link: 'https://cursor.com/settings',
|
|
171
|
+
},
|
|
172
|
+
antigravity: {
|
|
173
|
+
title: 'Google Antigravity / Gemini',
|
|
174
|
+
stepsRu: [
|
|
175
|
+
'1. Самый простой способ: нажмите "📥 Из CLI" — на сервере уже есть авторизованная сессия (~/.gemini/antigravity-cli/antigravity-oauth-token).',
|
|
176
|
+
'2. Если токен обновлен в системе, нажатие "📥 Из CLI" мгновенно подхватит актуальный refresh_token.',
|
|
177
|
+
'3. Ручной ввод: вставьте JSON сессии или OAuth access_token/refresh_token в поле и нажмите "Сохранить".',
|
|
178
|
+
'Примечание: Кнопка "Подключить" для Google требует свой OAuth client_id в настройках плагина, поэтому для Antigravity рекомендуется использовать "📥 Из CLI"!',
|
|
179
|
+
],
|
|
180
|
+
stepsEn: [
|
|
181
|
+
'1. Easiest way: click "📥 From CLI" — server already has active token in ~/.gemini/antigravity-cli/antigravity-oauth-token.',
|
|
182
|
+
'2. Clicking "📥 From CLI" instantly loads fresh credentials.',
|
|
183
|
+
'3. Manual: paste session JSON or OAuth tokens and click "Save token".',
|
|
184
|
+
'Note: "Connect" button requires custom Google client_id in plugin settings; use "📥 From CLI" for seamless login!',
|
|
185
|
+
],
|
|
186
|
+
link: 'https://antigravity.google',
|
|
187
|
+
},
|
|
188
|
+
kimi: {
|
|
189
|
+
title: 'Moonshot Kimi',
|
|
190
|
+
stepsRu: [
|
|
191
|
+
'1. Откройте консоль разработчика Moonshot (ссылка ниже или по кнопке "Подключить").',
|
|
192
|
+
'2. Создайте и скопируйте свой API-ключ (sk-...).',
|
|
193
|
+
'3. Вставьте ключ в поле слота и нажмите "Сохранить".',
|
|
194
|
+
'4. Если у вас настроен Kimi Code CLI, можно также нажать "📥 Из CLI" (~/.kimi-code).',
|
|
195
|
+
],
|
|
196
|
+
stepsEn: [
|
|
197
|
+
'1. Open Moonshot developer console (link below or click Connect).',
|
|
198
|
+
'2. Generate and copy your API Key (sk-...).',
|
|
199
|
+
'3. Paste it into the input field and click "Save token".',
|
|
200
|
+
'4. Or click "📥 From CLI" if ~/.kimi-code is configured.',
|
|
201
|
+
],
|
|
202
|
+
link: 'https://platform.moonshot.cn/console/api-keys',
|
|
203
|
+
},
|
|
204
|
+
glm: {
|
|
205
|
+
title: 'Zhipu GLM (Z.AI)',
|
|
206
|
+
stepsRu: [
|
|
207
|
+
'1. Откройте Центр разработчика Zhipu AI (ссылка ниже или по кнопке "Подключить").',
|
|
208
|
+
'2. Скопируйте ваш API-ключ GLM.',
|
|
209
|
+
'3. Вставьте его в поле ввода и нажмите "Сохранить".',
|
|
210
|
+
'4. Если установлен ZCode CLI (~/.zcode), нажмите "📥 Из CLI" для авто-импорта.',
|
|
211
|
+
],
|
|
212
|
+
stepsEn: [
|
|
213
|
+
'1. Open Zhipu BigModel API Keys center (link below or click Connect).',
|
|
214
|
+
'2. Copy your GLM API Key.',
|
|
215
|
+
'3. Paste it into the input field and click "Save token".',
|
|
216
|
+
'4. Or click "📥 From CLI" if ZCode CLI is installed (~/.zcode).',
|
|
217
|
+
],
|
|
218
|
+
link: 'https://open.bigmodel.cn/usercenter/apikeys',
|
|
219
|
+
},
|
|
220
|
+
grok: {
|
|
221
|
+
title: 'xAI Grok',
|
|
222
|
+
stepsRu: [
|
|
223
|
+
'1. Выполните вход через grok-cli или hermes на сервере.',
|
|
224
|
+
'2. Нажмите "📥 Из CLI" (~/.grok/auth.json).',
|
|
225
|
+
'3. Либо вставьте xAI API-ключ в поле и нажмите "Сохранить".',
|
|
226
|
+
],
|
|
227
|
+
stepsEn: [
|
|
228
|
+
'1. Run grok-cli or hermes login on server.',
|
|
229
|
+
'2. Click "📥 From CLI" (~/.grok/auth.json).',
|
|
230
|
+
'3. Or paste your xAI API Key and click "Save token".',
|
|
231
|
+
],
|
|
232
|
+
link: 'https://x.ai',
|
|
233
|
+
},
|
|
234
|
+
kiro: {
|
|
235
|
+
title: 'AWS Kiro',
|
|
236
|
+
stepsRu: [
|
|
237
|
+
'1. Нажмите "📥 Из CLI" для импорта из ~/.kiro/credentials.json или ~/.aws/sso.',
|
|
238
|
+
'2. Либо вставьте KIRO_API_KEY в поле и нажмите "Сохранить".',
|
|
239
|
+
],
|
|
240
|
+
stepsEn: [
|
|
241
|
+
'1. Click "📥 From CLI" to import from ~/.kiro/credentials.json.',
|
|
242
|
+
'2. Or paste KIRO_API_KEY and click "Save token".',
|
|
243
|
+
],
|
|
244
|
+
link: 'https://aws.amazon.com',
|
|
245
|
+
},
|
|
246
|
+
}
|
|
247
|
+
|
|
123
248
|
const en = {
|
|
124
249
|
'notConnected': 'Not connected',
|
|
125
250
|
'verifyAccount': 'Verify account',
|
|
@@ -213,10 +338,15 @@ const cssId = 'dsh-subscriptions/settings.module.css'
|
|
|
213
338
|
'resetLabel': 'reset',
|
|
214
339
|
'check': 'Check',
|
|
215
340
|
'checking': 'Checking…',
|
|
216
|
-
|
|
217
|
-
'importTokenPlace': 'Paste
|
|
218
|
-
'importLocalCli': '
|
|
219
|
-
'
|
|
341
|
+
'importToken': 'Save token',
|
|
342
|
+
'importTokenPlace': 'Paste token or API key',
|
|
343
|
+
'importLocalCli': '📥 From CLI',
|
|
344
|
+
'importLocalCliTitle': 'Auto-import token from locally installed CLI on server',
|
|
345
|
+
'importLocalSuccess': 'Successfully imported token from local CLI!',
|
|
346
|
+
'howToGetToken': '❓ Instructions',
|
|
347
|
+
'howToGetTokenTitle': 'How to get token / API key for this provider',
|
|
348
|
+
'instructionsTitle': 'How to obtain token',
|
|
349
|
+
'close': 'Close',
|
|
220
350
|
'reconnectRequired': 'Reconnect required',
|
|
221
351
|
'manualTitle': 'If the browser did not come back',
|
|
222
352
|
'windowPrimary': 'Primary window',
|
|
@@ -225,10 +355,15 @@ const cssId = 'dsh-subscriptions/settings.module.css'
|
|
|
225
355
|
const ru = {
|
|
226
356
|
'check': 'Проверить',
|
|
227
357
|
'checking': 'Проверяю…',
|
|
228
|
-
|
|
229
|
-
'importTokenPlace': 'Вставить
|
|
230
|
-
'importLocalCli': '
|
|
231
|
-
'
|
|
358
|
+
'importToken': 'Сохранить',
|
|
359
|
+
'importTokenPlace': 'Вставить токен или API-ключ',
|
|
360
|
+
'importLocalCli': '📥 Из CLI',
|
|
361
|
+
'importLocalCliTitle': 'Автоматически считать токен из установленного на сервере CLI',
|
|
362
|
+
'importLocalSuccess': 'Токен успешно загружен из локального CLI!',
|
|
363
|
+
'howToGetToken': '❓ Инструкция',
|
|
364
|
+
'howToGetTokenTitle': 'Как получить токен или API-ключ для этого провайдера',
|
|
365
|
+
'instructionsTitle': 'Инструкция по получению токена',
|
|
366
|
+
'close': 'Закрыть',
|
|
232
367
|
'reconnectRequired': 'Нужно переподключить',
|
|
233
368
|
'manualTitle': 'Если браузер не вернулся сам',
|
|
234
369
|
'windowPrimary': 'Основное окно',
|
|
@@ -506,7 +641,9 @@ const cssId = 'dsh-subscriptions/settings.module.css'
|
|
|
506
641
|
const [checking, setChecking] = React.useState({})
|
|
507
642
|
const [saved, setSaved] = React.useState(false)
|
|
508
643
|
const [tokenDraft, setTokenDraft] = React.useState({})
|
|
644
|
+
const [openHelp, setOpenHelp] = React.useState({})
|
|
509
645
|
const [err, setErr] = React.useState('')
|
|
646
|
+
const [info, setInfo] = React.useState('')
|
|
510
647
|
|
|
511
648
|
const applyPayload = (data) => {
|
|
512
649
|
setDraft(JSON.parse(JSON.stringify((data && data.config) || {})))
|
|
@@ -679,6 +816,7 @@ const cssId = 'dsh-subscriptions/settings.module.css'
|
|
|
679
816
|
|
|
680
817
|
const importLocalCli = async (provider, index) => {
|
|
681
818
|
setErr('')
|
|
819
|
+
setInfo('')
|
|
682
820
|
const res = await fetch('/dsh-subscriptions/import-local', {
|
|
683
821
|
method: 'POST',
|
|
684
822
|
headers: { 'Content-Type': 'application/json' },
|
|
@@ -686,8 +824,10 @@ const cssId = 'dsh-subscriptions/settings.module.css'
|
|
|
686
824
|
})
|
|
687
825
|
const data = await res.json().catch(() => ({}))
|
|
688
826
|
if (!res.ok) throw new Error((data.error && data.error.message) || ('HTTP ' + res.status))
|
|
689
|
-
|
|
827
|
+
if (data.config) applyPayload(data)
|
|
828
|
+
else await reload()
|
|
690
829
|
setInfo(t('importLocalSuccess'))
|
|
830
|
+
setTimeout(() => setInfo(''), 3000)
|
|
691
831
|
}
|
|
692
832
|
|
|
693
833
|
const importToken = async (provider, index) => {
|
|
@@ -825,6 +965,21 @@ const cssId = 'dsh-subscriptions/settings.module.css'
|
|
|
825
965
|
onClick: () => complete(row.slot.provider, row.slot.index).catch((e) => setErr(cleanErrorMessage(e.message || e))),
|
|
826
966
|
}, t('submitCode')),
|
|
827
967
|
),
|
|
968
|
+
React.createElement('div', { className: 'dsub-row', style: { justifyContent: 'space-between' } },
|
|
969
|
+
React.createElement('button', {
|
|
970
|
+
type: 'button',
|
|
971
|
+
className: 'dsub-mini',
|
|
972
|
+
title: t('importLocalCliTitle'),
|
|
973
|
+
style: { display: 'inline-flex', alignItems: 'center', gap: 6, fontWeight: 600 },
|
|
974
|
+
onClick: () => importLocalCli(row.slot.provider, row.slot.index).catch((e) => setErr(cleanErrorMessage(e.message || e))),
|
|
975
|
+
}, t('importLocalCli')),
|
|
976
|
+
React.createElement('button', {
|
|
977
|
+
type: 'button',
|
|
978
|
+
className: 'dsub-mini' + (openHelp[key] ? ' dsub-btnActive' : ''),
|
|
979
|
+
title: t('howToGetTokenTitle'),
|
|
980
|
+
onClick: (e) => { if (e) { e.preventDefault(); e.stopPropagation(); } setOpenHelp((h) => Object.assign({}, h, { [key]: !h[key] })); },
|
|
981
|
+
}, t('howToGetToken')),
|
|
982
|
+
),
|
|
828
983
|
React.createElement('div', { className: 'dsub-row' },
|
|
829
984
|
React.createElement('input', {
|
|
830
985
|
className: 'dsub-grow',
|
|
@@ -834,14 +989,44 @@ const cssId = 'dsh-subscriptions/settings.module.css'
|
|
|
834
989
|
}),
|
|
835
990
|
React.createElement('button', {
|
|
836
991
|
type: 'button', className: 'dsub-mini',
|
|
992
|
+
title: t('importToken'),
|
|
837
993
|
onClick: () => importToken(row.slot.provider, row.slot.index).catch((e) => setErr(cleanErrorMessage(e.message || e))),
|
|
838
994
|
}, t('importToken')),
|
|
839
|
-
React.createElement('button', {
|
|
840
|
-
type: 'button', className: 'dsub-mini',
|
|
841
|
-
title: t('importLocalCli'),
|
|
842
|
-
onClick: () => importLocalCli(row.slot.provider, row.slot.index).catch((e) => setErr(cleanErrorMessage(e.message || e))),
|
|
843
|
-
}, '📥 CLI'),
|
|
844
995
|
),
|
|
996
|
+
(openHelp[key] ? (function renderInlineHelp(){
|
|
997
|
+
const prov = row.slot.provider;
|
|
998
|
+
const info = (typeof INSTRUCTIONS !== 'undefined' && INSTRUCTIONS[prov]) || {
|
|
999
|
+
title: prov,
|
|
1000
|
+
stepsRu: ['Вставьте токен или API-ключ в поле выше и нажмите "Сохранить".'],
|
|
1001
|
+
stepsEn: ['Paste your token or API key into the field above and click "Save token".'],
|
|
1002
|
+
};
|
|
1003
|
+
// Determine language safely from t('instructionsTitle')
|
|
1004
|
+
const isRu = t('instructionsTitle').indexOf('Инструкция') >= 0;
|
|
1005
|
+
const steps = isRu ? info.stepsRu : info.stepsEn;
|
|
1006
|
+
return React.createElement('div', { className: 'dsub-helpBox' },
|
|
1007
|
+
React.createElement('div', { className: 'dsub-helpHead' },
|
|
1008
|
+
React.createElement('span', null, 'ℹ️ ' + info.title + ' — ' + t('instructionsTitle')),
|
|
1009
|
+
React.createElement('button', {
|
|
1010
|
+
type: 'button', className: 'dsub-mini',
|
|
1011
|
+
style: { padding: '1px 6px', fontSize: 11 },
|
|
1012
|
+
onClick: (e) => {
|
|
1013
|
+
if (e) { e.preventDefault(); e.stopPropagation(); }
|
|
1014
|
+
setOpenHelp((h) => Object.assign({}, h, { [key]: false }));
|
|
1015
|
+
},
|
|
1016
|
+
}, '✕')
|
|
1017
|
+
),
|
|
1018
|
+
steps.map((st, i) => React.createElement('div', { key: i, className: 'dsub-helpStep' }, st)),
|
|
1019
|
+
info.link ? React.createElement('div', { style: { marginTop: 8 } },
|
|
1020
|
+
React.createElement('a', {
|
|
1021
|
+
href: info.link,
|
|
1022
|
+
target: '_blank',
|
|
1023
|
+
rel: 'noopener noreferrer',
|
|
1024
|
+
className: 'dsub-mini',
|
|
1025
|
+
style: { display: 'inline-flex', alignItems: 'center', gap: 4, textDecoration: 'none' },
|
|
1026
|
+
}, '🔗 ' + (isRu ? 'Открыть страницу провайдера' : 'Open provider console'))
|
|
1027
|
+
) : null
|
|
1028
|
+
);
|
|
1029
|
+
})() : null),
|
|
845
1030
|
React.createElement('div', { className: 'dsub-row' },
|
|
846
1031
|
React.createElement('input', {
|
|
847
1032
|
className: 'dsub-grow',
|
|
@@ -1005,6 +1190,7 @@ const cssId = 'dsh-subscriptions/settings.module.css'
|
|
|
1005
1190
|
onClick: () => save().catch((e) => setErr(cleanErrorMessage(e.message || e))),
|
|
1006
1191
|
}, t('save')),
|
|
1007
1192
|
saved ? React.createElement('span', { className: 'dsub-ok' }, t('saved')) : null,
|
|
1193
|
+
info ? React.createElement('span', { className: 'dsub-ok' }, info) : null,
|
|
1008
1194
|
err ? React.createElement('span', { className: 'dsub-bad' }, err) : null,
|
|
1009
1195
|
),
|
|
1010
1196
|
)
|
|
@@ -1211,6 +1397,16 @@ const cssId = 'dsh-subscriptions/settings.module.css'
|
|
|
1211
1397
|
if (p.includes('antigravity') || p.includes('google') || p.includes('gemini')) return { label: 'AGY', cls: 'dsub-brandAgy', icon: '◆' }
|
|
1212
1398
|
if (p.includes('kimi')) return { label: 'Kimi', cls: 'dsub-brandKimi', icon: '🌙' }
|
|
1213
1399
|
if (p.includes('glm') || p.includes('zcode')) return { label: 'GLM', cls: 'dsub-brandGlm', icon: '⚡' }
|
|
1400
|
+
if (p.includes('copilot') || p.includes('github')) return { label: 'Copilot', cls: 'dsub-brandCodex', icon: '🐙' }
|
|
1401
|
+
if (p.includes('cursor')) return { label: 'Cursor', cls: 'dsub-brandCodex', icon: '💻' }
|
|
1402
|
+
if (p.includes('kiro')) return { label: 'Kiro', cls: 'dsub-brandAgy', icon: '☁️' }
|
|
1403
|
+
if (p.includes('qwen')) return { label: 'Qwen', cls: 'dsub-brandKimi', icon: '🌐' }
|
|
1404
|
+
if (p.includes('ernie') || p.includes('baidu')) return { label: 'ERNIE', cls: 'dsub-brandGlm', icon: '🐻' }
|
|
1405
|
+
if (p.includes('spark') || p.includes('xfyun')) return { label: 'Spark', cls: 'dsub-brandGrok', icon: '✨' }
|
|
1406
|
+
if (p.includes('jetbrains')) return { label: 'JetBrains', cls: 'dsub-brandClaude', icon: '🚀' }
|
|
1407
|
+
if (p.includes('perplexity')) return { label: 'Perplexity', cls: 'dsub-brandClaude', icon: '🔮' }
|
|
1408
|
+
if (p.includes('replit')) return { label: 'Replit', cls: 'dsub-brandCodex', icon: '⚡' }
|
|
1409
|
+
if (p.includes('cody') || p.includes('sourcegraph')) return { label: 'Cody', cls: 'dsub-brandAgy', icon: '🔍' }
|
|
1214
1410
|
if (p.includes('ollama')) return { label: 'Ollama', cls: 'dsub-brandOllama', icon: '🦙' }
|
|
1215
1411
|
return { label: (p.charAt(0).toUpperCase() || 'P'), cls: 'dsub-brandCodex', icon: '●' }
|
|
1216
1412
|
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
export async function* coalesceStreamChunks(sourceStream, maxWaitMs = 40, maxChunkChars = 80) {
|
|
2
|
+
let buffer = ''
|
|
3
|
+
let lastFlush = Date.now()
|
|
4
|
+
|
|
5
|
+
for await (const chunk of sourceStream) {
|
|
6
|
+
const text = typeof chunk === 'string' ? chunk : (chunk && chunk.text) || ''
|
|
7
|
+
buffer += text
|
|
8
|
+
|
|
9
|
+
const now = Date.now()
|
|
10
|
+
if (buffer.length >= maxChunkChars || now - lastFlush >= maxWaitMs) {
|
|
11
|
+
if (buffer.length > 0) {
|
|
12
|
+
yield buffer
|
|
13
|
+
buffer = ''
|
|
14
|
+
lastFlush = now
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
if (buffer.length > 0) {
|
|
20
|
+
yield buffer
|
|
21
|
+
}
|
|
22
|
+
}
|
package/lib/code-assist.js
CHANGED
|
@@ -7,10 +7,10 @@ export const CODE_ASSIST_PROD = 'https://cloudcode-pa.googleapis.com/v1internal'
|
|
|
7
7
|
export const CODE_ASSIST_STREAM = 'https://daily-cloudcode-pa.googleapis.com/v1internal'
|
|
8
8
|
export const CODE_ASSIST = CODE_ASSIST_PROD
|
|
9
9
|
|
|
10
|
-
function antigravityPlatform() {
|
|
11
|
-
if (process.platform === 'win32') return '
|
|
12
|
-
if (process.platform === 'darwin') return '
|
|
13
|
-
return '
|
|
10
|
+
export function antigravityPlatform() {
|
|
11
|
+
if (process.platform === 'win32') return 'WINDOWS_AMD64'
|
|
12
|
+
if (process.platform === 'darwin') return process.arch === 'arm64' ? 'DARWIN_ARM64' : 'DARWIN_AMD64'
|
|
13
|
+
return process.arch === 'arm64' ? 'LINUX_ARM64' : 'LINUX_AMD64'
|
|
14
14
|
}
|
|
15
15
|
|
|
16
16
|
export function antigravityMetadata(projectId) {
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export function extractBearerFromCookie(cookieHeader) {
|
|
2
|
+
if (!cookieHeader || typeof cookieHeader !== 'string') return null
|
|
3
|
+
const match = cookieHeader.match(/(?:__Secure-next-auth\.session-token|session_token|auth_token)=([^;]+)/)
|
|
4
|
+
if (match && match[1]) {
|
|
5
|
+
return decodeURIComponent(match[1])
|
|
6
|
+
}
|
|
7
|
+
return null
|
|
8
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
export function detectCodexBillingType(rawBilling) {
|
|
2
|
+
if (!rawBilling || typeof rawBilling !== 'object') {
|
|
3
|
+
return { plan: 'Free / Unknown', isHardLimit: false, isPrepaid: false }
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
const planId = String(rawBilling.plan || rawBilling.subscription_type || '').toLowerCase()
|
|
7
|
+
const hasHardLimit = Boolean(rawBilling.hard_limit_reached || rawBilling.is_blocked)
|
|
8
|
+
const isPrepaid = Boolean(rawBilling.has_payment_method || rawBilling.is_prepaid)
|
|
9
|
+
|
|
10
|
+
let label = 'Free'
|
|
11
|
+
if (planId.includes('team')) label = 'Team'
|
|
12
|
+
else if (planId.includes('pro')) label = 'Pro'
|
|
13
|
+
else if (planId.includes('plus')) label = 'Plus'
|
|
14
|
+
else if (planId.includes('enterprise')) label = 'Enterprise'
|
|
15
|
+
|
|
16
|
+
return {
|
|
17
|
+
plan: label,
|
|
18
|
+
isHardLimit: hasHardLimit,
|
|
19
|
+
isPrepaid,
|
|
20
|
+
remainingCredits: rawBilling.remaining_credits != null ? Number(rawBilling.remaining_credits) : null
|
|
21
|
+
}
|
|
22
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { createCipheriv, createDecipheriv, randomBytes, pbkdf2Sync } from 'node:crypto'
|
|
2
|
+
|
|
3
|
+
const ALGORITHM = 'aes-256-gcm'
|
|
4
|
+
const SALT_LEN = 16
|
|
5
|
+
const IV_LEN = 12
|
|
6
|
+
const ITERATIONS = 100000
|
|
7
|
+
|
|
8
|
+
export function exportEncryptedBundle(data, password) {
|
|
9
|
+
const salt = randomBytes(SALT_LEN)
|
|
10
|
+
const key = pbkdf2Sync(password, salt, ITERATIONS, 32, 'sha256')
|
|
11
|
+
const iv = randomBytes(IV_LEN)
|
|
12
|
+
const cipher = createCipheriv(ALGORITHM, key, iv)
|
|
13
|
+
|
|
14
|
+
const plaintext = JSON.stringify(data)
|
|
15
|
+
let encrypted = cipher.update(plaintext, 'utf8', 'hex')
|
|
16
|
+
encrypted += cipher.final('hex')
|
|
17
|
+
const authTag = cipher.getAuthTag().toString('hex')
|
|
18
|
+
|
|
19
|
+
return {
|
|
20
|
+
version: 1,
|
|
21
|
+
algorithm: ALGORITHM,
|
|
22
|
+
salt: salt.toString('hex'),
|
|
23
|
+
iv: iv.toString('hex'),
|
|
24
|
+
authTag,
|
|
25
|
+
payload: encrypted
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function importEncryptedBundle(bundle, password) {
|
|
30
|
+
if (!bundle || bundle.version !== 1 || !bundle.payload) {
|
|
31
|
+
throw new Error('Invalid backup bundle format')
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const salt = Buffer.from(bundle.salt, 'hex')
|
|
35
|
+
const key = pbkdf2Sync(password, salt, ITERATIONS, 32, 'sha256')
|
|
36
|
+
const iv = Buffer.from(bundle.iv, 'hex')
|
|
37
|
+
const authTag = Buffer.from(bundle.authTag, 'hex')
|
|
38
|
+
|
|
39
|
+
const decipher = createDecipheriv(ALGORITHM, key, iv)
|
|
40
|
+
decipher.setAuthTag(authTag)
|
|
41
|
+
|
|
42
|
+
let decrypted = decipher.update(bundle.payload, 'hex', 'utf8')
|
|
43
|
+
decrypted += decipher.final('utf8')
|
|
44
|
+
|
|
45
|
+
return JSON.parse(decrypted)
|
|
46
|
+
}
|
package/lib/import-auth.js
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { readFile } from 'node:fs/promises'
|
|
2
2
|
import { homedir } from 'node:os'
|
|
3
3
|
import { join } from 'node:path'
|
|
4
|
+
import { readClaudeCredentials } from './vendors/claude-cli.js'
|
|
5
|
+
import { discoverCodingAgentConfigs } from './importers/coding-agents.js'
|
|
4
6
|
|
|
5
7
|
function homeFile(...parts) {
|
|
6
8
|
return join(homedir(), ...parts)
|
|
@@ -97,8 +99,8 @@ export async function discoverLocalCliSessions() {
|
|
|
97
99
|
}
|
|
98
100
|
}
|
|
99
101
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
+
// 6. Cursor Token (env, CLI config or IDE state)
|
|
103
|
+
const cursorCliConfig = await readJson(homeFile('.cursor', 'cli-config.json'))
|
|
102
104
|
if (process.env.CURSOR_ACCESS_TOKEN) {
|
|
103
105
|
detected.cursor = {
|
|
104
106
|
provider: 'cursor',
|
|
@@ -106,9 +108,15 @@ export async function discoverLocalCliSessions() {
|
|
|
106
108
|
email: 'Cursor IDE User',
|
|
107
109
|
hasRefreshToken: false,
|
|
108
110
|
}
|
|
111
|
+
} else if (cursorCliConfig && (cursorCliConfig.authInfo || cursorCliConfig.serverConfigCache)) {
|
|
112
|
+
detected.cursor = {
|
|
113
|
+
provider: 'cursor',
|
|
114
|
+
path: homeFile('.cursor', 'cli-config.json'),
|
|
115
|
+
email: (cursorCliConfig.authInfo && cursorCliConfig.authInfo.email) || 'Cursor CLI User',
|
|
116
|
+
hasRefreshToken: false,
|
|
117
|
+
}
|
|
109
118
|
}
|
|
110
119
|
|
|
111
|
-
|
|
112
120
|
// 7. AWS Kiro
|
|
113
121
|
const kiroPaths = [
|
|
114
122
|
homeFile('.kiro', 'credentials.json'),
|
|
@@ -135,6 +143,46 @@ export async function discoverLocalCliSessions() {
|
|
|
135
143
|
}
|
|
136
144
|
}
|
|
137
145
|
|
|
146
|
+
// 8. Claude CLI Direct Import
|
|
147
|
+
const claudeCreds = await readClaudeCredentials()
|
|
148
|
+
if (claudeCreds) {
|
|
149
|
+
detected['claude-cli'] = {
|
|
150
|
+
provider: 'claude',
|
|
151
|
+
path: homeFile('.claude', 'credentials.json'),
|
|
152
|
+
email: 'Claude Code CLI User',
|
|
153
|
+
hasRefreshToken: !!claudeCreds.refreshToken,
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// 9. GitHub Copilot token
|
|
158
|
+
if (process.env.GITHUB_COPILOT_TOKEN || process.env.GH_TOKEN) {
|
|
159
|
+
detected.copilot = {
|
|
160
|
+
provider: 'copilot',
|
|
161
|
+
path: 'GITHUB_COPILOT_TOKEN env',
|
|
162
|
+
email: 'GitHub Copilot User',
|
|
163
|
+
hasRefreshToken: false,
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// 10. Coding Agents (Aider / Roo-Code)
|
|
168
|
+
const agentConfigs = await discoverCodingAgentConfigs()
|
|
169
|
+
if (agentConfigs.aider) {
|
|
170
|
+
detected.aider = {
|
|
171
|
+
provider: 'aider',
|
|
172
|
+
path: agentConfigs.aider.path,
|
|
173
|
+
email: 'Aider Configured Agent',
|
|
174
|
+
hasRefreshToken: false,
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
if (agentConfigs.roocode) {
|
|
178
|
+
detected.roocode = {
|
|
179
|
+
provider: 'roocode',
|
|
180
|
+
path: agentConfigs.roocode.path,
|
|
181
|
+
email: 'Roo-Code Configured Agent',
|
|
182
|
+
hasRefreshToken: false,
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
138
186
|
return detected
|
|
139
187
|
}
|
|
140
188
|
|
|
@@ -143,6 +191,27 @@ export async function loadLocalCliBlob(provider) {
|
|
|
143
191
|
const info = discovered[provider]
|
|
144
192
|
if (!info) throw new Error(`no local CLI session found for ${provider}`)
|
|
145
193
|
|
|
194
|
+
if (provider === 'claude-cli' || (provider === 'claude' && info.provider === 'claude')) {
|
|
195
|
+
const creds = await readClaudeCredentials()
|
|
196
|
+
if (!creds) throw new Error('Claude CLI credentials not found')
|
|
197
|
+
return {
|
|
198
|
+
accessToken: creds.accessToken,
|
|
199
|
+
refreshToken: creds.refreshToken,
|
|
200
|
+
expiresAt: creds.expiresAt,
|
|
201
|
+
email: 'Claude Code CLI User'
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
if (provider === 'copilot') {
|
|
206
|
+
const tok = process.env.GITHUB_COPILOT_TOKEN || process.env.GH_TOKEN || ''
|
|
207
|
+
return {
|
|
208
|
+
accessToken: tok,
|
|
209
|
+
refreshToken: tok,
|
|
210
|
+
expiresAt: Date.now() + 30 * 86400 * 1000,
|
|
211
|
+
email: 'GitHub Copilot User'
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
146
215
|
const raw = await readJson(info.path)
|
|
147
216
|
if (!raw) throw new Error(`failed to read local CLI file: ${info.path}`)
|
|
148
217
|
|
|
@@ -197,18 +266,16 @@ export async function loadLocalCliBlob(provider) {
|
|
|
197
266
|
}
|
|
198
267
|
}
|
|
199
268
|
|
|
200
|
-
|
|
201
269
|
if (provider === 'cursor') {
|
|
202
|
-
const token = process.env.CURSOR_ACCESS_TOKEN || (raw && (raw.accessToken || raw.access_token)) || ''
|
|
270
|
+
const token = process.env.CURSOR_ACCESS_TOKEN || (raw && (raw.accessToken || raw.access_token || (raw.authInfo && raw.authInfo.authId))) || ''
|
|
203
271
|
return {
|
|
204
272
|
accessToken: token,
|
|
205
273
|
refreshToken: '',
|
|
206
274
|
expiresAt: Date.now() + 30 * 86400 * 1000,
|
|
207
|
-
email: 'Cursor User',
|
|
275
|
+
email: (raw && raw.authInfo && raw.authInfo.email) || 'Cursor User',
|
|
208
276
|
}
|
|
209
277
|
}
|
|
210
278
|
|
|
211
|
-
|
|
212
279
|
if (provider === 'kiro') {
|
|
213
280
|
const token = process.env.KIRO_API_KEY || (raw && (raw.accessToken || raw.access_token || raw.token)) || ''
|
|
214
281
|
return {
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { readFile } from 'node:fs/promises'
|
|
2
|
+
import { homedir } from 'node:os'
|
|
3
|
+
import { join } from 'node:path'
|
|
4
|
+
|
|
5
|
+
export async function discoverCodingAgentConfigs() {
|
|
6
|
+
const configs = {}
|
|
7
|
+
|
|
8
|
+
// 1. Aider (.aider.conf.yml)
|
|
9
|
+
try {
|
|
10
|
+
const p = join(homedir(), '.aider.conf.yml')
|
|
11
|
+
const text = await readFile(p, 'utf8')
|
|
12
|
+
configs.aider = {
|
|
13
|
+
name: 'Aider Config',
|
|
14
|
+
path: p,
|
|
15
|
+
found: true,
|
|
16
|
+
raw: text
|
|
17
|
+
}
|
|
18
|
+
} catch {
|
|
19
|
+
// not present
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// 2. Roo-Code / Roo-Cline settings
|
|
23
|
+
try {
|
|
24
|
+
const p = join(homedir(), '.roo-code', 'settings.json')
|
|
25
|
+
const text = await readFile(p, 'utf8')
|
|
26
|
+
configs.roocode = {
|
|
27
|
+
name: 'Roo-Code Config',
|
|
28
|
+
path: p,
|
|
29
|
+
found: true,
|
|
30
|
+
raw: text
|
|
31
|
+
}
|
|
32
|
+
} catch {
|
|
33
|
+
// not present
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
return configs
|
|
37
|
+
}
|