@goodandready/dsh-subscriptions 0.6.0 → 0.6.1
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/accounts.js +1 -1
- package/lib/adapter.js +2 -2
- package/lib/client.js +43 -167
- package/lib/crypto.js +4 -4
- package/lib/google-validation.js +1 -1
- package/lib/health.js +2 -2
- package/lib/history.js +5 -5
- package/lib/http.js +7 -0
- package/lib/images.js +21 -21
- package/lib/index.js +19 -19
- package/lib/loopback.js +10 -10
- package/lib/ratelimit.js +2 -2
- package/lib/refs.js +9 -1
- package/lib/routes/accounts.js +343 -0
- package/lib/routes/oauth.js +248 -0
- package/lib/routes/proxy.js +147 -0
- package/lib/routes/status.js +257 -0
- package/lib/routes.js +12 -916
- package/lib/subscriptions.js +1 -1
- package/lib/ui-styles.js +12 -12
- package/lib/usage.js +1 -1
- package/lib/vendor-factory.js +13 -13
- package/package.json +1 -1
- package/lib/ui-i18n.js +0 -256
package/README.md
CHANGED
|
@@ -231,6 +231,12 @@ dsh-subscriptions:
|
|
|
231
231
|
|
|
232
232
|
---
|
|
233
233
|
|
|
234
|
+
## 🌐 Localization
|
|
235
|
+
|
|
236
|
+
The plugin source language is English only. Russian and other translations are provided at runtime by separate language plugins (for example the russification plugin), which translate the registered locale keys - the package itself ships no bundled translations (Changed in 0.6.1).
|
|
237
|
+
|
|
238
|
+
---
|
|
239
|
+
|
|
234
240
|
## 📄 License
|
|
235
241
|
|
|
236
242
|
MIT © [GooDAnDReaDY](https://github.com/GooDAnDReaDY)
|
package/README.ru.md
CHANGED
|
@@ -204,6 +204,12 @@ dsh-subscriptions:
|
|
|
204
204
|
|
|
205
205
|
---
|
|
206
206
|
|
|
207
|
+
## 🌐 Локализация
|
|
208
|
+
|
|
209
|
+
Исходный язык плагина - только английский. Русский и другие переводы во время работы предоставляют отдельные языковые плагины (например, плагин русификации), переводящие зарегистрированные ключи локали, - сам пакет не содержит встроенных переводов (Changed in 0.6.1).
|
|
210
|
+
|
|
211
|
+
---
|
|
212
|
+
|
|
207
213
|
## 📄 Лицензия
|
|
208
214
|
|
|
209
215
|
MIT © [GooDAnDReaDY](https://github.com/GooDAnDReaDY)
|
package/README.zh.md
CHANGED
|
@@ -138,6 +138,12 @@ dsh plugin --profile web add @goodandready/dsh-subscriptions
|
|
|
138
138
|
|
|
139
139
|
---
|
|
140
140
|
|
|
141
|
+
## 🌐 本地化
|
|
142
|
+
|
|
143
|
+
插件源语言仅为英语。俄语及其他翻译由独立的语言插件(如俄化插件)在运行时对注册的本地化键进行翻译,包本身不内置翻译(Changed in 0.6.1)。
|
|
144
|
+
|
|
145
|
+
---
|
|
146
|
+
|
|
141
147
|
## 📄 开源协议
|
|
142
148
|
|
|
143
149
|
MIT © [GooDAnDReaDY](https://github.com/GooDAnDReaDY)
|
package/lib/accounts.js
CHANGED
|
@@ -327,7 +327,7 @@ export function createAccountStore({ credentials, getConfig, fetchImpl, fetchFor
|
|
|
327
327
|
recordExhaust(ref) { health.set(ref, recordExhaust(health.get(ref))) },
|
|
328
328
|
recordBroken(ref) { health.set(ref, recordBroken(health.get(ref))) },
|
|
329
329
|
recordSuccess(ref) {
|
|
330
|
-
//
|
|
330
|
+
// successful request clears the cooldown and puts the account back in rotation
|
|
331
331
|
cooldowns.delete(ref)
|
|
332
332
|
quarantines.delete(ref)
|
|
333
333
|
if (health.has(ref)) health.set(ref, null)
|
package/lib/adapter.js
CHANGED
|
@@ -147,9 +147,9 @@ export class SubscriptionAdapter extends LlmAdapter {
|
|
|
147
147
|
signal: opts.signal,
|
|
148
148
|
saveBlob: (next) => deps.saveBlob(account.ref, next),
|
|
149
149
|
})
|
|
150
|
-
//
|
|
150
|
+
// stream completed successfully: clear cooldown and health penalties
|
|
151
151
|
if (typeof deps.recordSuccess === 'function') deps.recordSuccess(account.ref)
|
|
152
|
-
// #65:
|
|
152
|
+
// #65: record in the request and cost history.
|
|
153
153
|
if (typeof deps.recordHistory === 'function') {
|
|
154
154
|
try {
|
|
155
155
|
deps.recordHistory({
|
package/lib/client.js
CHANGED
|
@@ -8,7 +8,7 @@ window.__ModuleLoader__.load({
|
|
|
8
8
|
try { return require('react-dom').createPortal } catch { return null }
|
|
9
9
|
})()
|
|
10
10
|
|
|
11
|
-
//
|
|
11
|
+
// Modular translator: shared by component subscriptions and helpers.
|
|
12
12
|
let t = (key) => key
|
|
13
13
|
const setT = (fn) => { t = fn }
|
|
14
14
|
|
|
@@ -87,9 +87,9 @@ window.__ModuleLoader__.load({
|
|
|
87
87
|
'.dsub-pill:active{transform:translateY(0)}' +
|
|
88
88
|
'.dsub-pillTag{padding:1px 5px;border-radius:4px;font-size:10.5px;font-weight:700;letter-spacing:.02em;background:var(--dsw-alias-bg-layer-1);color:var(--dsw-alias-label-secondary)}' +
|
|
89
89
|
'.dsub-led{width:7px;height:7px;border-radius:50%;flex:none;transition:all .2s ease}' +
|
|
90
|
-
'.dsub-ledOk{background:var(--dsw-alias-state-success-primary,#10b981);box-shadow:0 0 6px
|
|
91
|
-
'.dsub-ledWarn{background:var(--dsw-alias-state-warning-primary,#f59e0b);box-shadow:0 0 6px
|
|
92
|
-
'.dsub-ledBad{background:var(--dsw-alias-state-error-primary,#ef4444);box-shadow:0 0 6px
|
|
90
|
+
'.dsub-ledOk{background:var(--dsw-alias-state-success-primary,#10b981);box-shadow:0 0 6px color-mix(in srgb,var(--dsw-alias-state-success-primary) 70%,transparent)}' +
|
|
91
|
+
'.dsub-ledWarn{background:var(--dsw-alias-state-warning-primary,#f59e0b);box-shadow:0 0 6px color-mix(in srgb,var(--dsw-alias-state-warning-primary) 70%,transparent)}' +
|
|
92
|
+
'.dsub-ledBad{background:var(--dsw-alias-state-error-primary,#ef4444);box-shadow:0 0 6px color-mix(in srgb,var(--dsw-alias-state-error-primary) 70%,transparent)}' +
|
|
93
93
|
'.dsub-ledOff{background:var(--dsw-alias-label-tertiary,#6b7280)}' +
|
|
94
94
|
'.dsub-modalWrap{position:fixed;inset:0;z-index:10020;display:grid;place-items:center;background:rgba(0,0,0,.65);backdrop-filter:blur(10px);animation:dsubFadeIn .16s ease-out}' +
|
|
95
95
|
'.dsub-modal{width:min(520px,94vw);max-height:min(85vh,720px);overflow-y:auto;border:1px solid var(--dsw-alias-border-l1);border-radius:16px;background:var(--dsw-alias-bg-layer-3);color:var(--dsw-alias-label-primary);box-shadow:0 24px 64px rgba(0,0,0,.6),0 0 0 1px rgba(255,255,255,.07);padding:20px;animation:dsubScaleIn .18s cubic-bezier(.16,1,.3,1)}' +
|
|
@@ -108,7 +108,7 @@ window.__ModuleLoader__.load({
|
|
|
108
108
|
'.dsub-barTrack{height:6px;border-radius:3px;background:var(--dsw-alias-bg-layer-1);overflow:hidden}' +
|
|
109
109
|
'.dsub-barFillGrad{height:100%;border-radius:3px;transition:width .3s ease}' +
|
|
110
110
|
'.dsub-panel-box{margin-top:10px;padding:12px 14px;border-radius:10px;border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-2);display:flex;flex-direction:column;gap:8px}' +
|
|
111
|
-
'.dsub-panel-warn{border-color:
|
|
111
|
+
'.dsub-panel-warn{border-color:color-mix(in srgb,var(--dsw-alias-state-warning-primary) 40%,transparent);background:color-mix(in srgb,var(--dsw-alias-state-warning-primary) 5%,transparent)}' +
|
|
112
112
|
'.dsub-panel-title{font-size:13px;font-weight:600;color:var(--dsw-alias-label-primary);display:flex;align-items:center;justify-content:space-between}' +
|
|
113
113
|
'.dsub-panel-desc{font-size:12px;color:var(--dsw-alias-label-secondary);line-height:1.4}' +
|
|
114
114
|
'.dsub-usage-grid{display:flex;flex-direction:column;gap:8px;margin-top:4px}' +
|
|
@@ -117,14 +117,14 @@ window.__ModuleLoader__.load({
|
|
|
117
117
|
'.dsub-usage-track{width:100%;height:8px;border-radius:999px;background:var(--dsw-alias-bg-layer-3);border:1px solid var(--dsw-alias-border-l2);overflow:hidden}' +
|
|
118
118
|
'.dsub-usage-fill{height:100%;border-radius:999px;transition:width .3s ease}' +
|
|
119
119
|
'.dsub-usage-meta{display:flex;justify-content:space-between;align-items:center;font-size:11px;color:var(--dsw-alias-label-secondary)}' +
|
|
120
|
-
'.dsub-tag-ok{border-color:
|
|
121
|
-
'.dsub-tag-warn{border-color:
|
|
122
|
-
'.dsub-tag-bad{border-color:
|
|
120
|
+
'.dsub-tag-ok{border-color:color-mix(in srgb,var(--dsw-alias-state-success-primary) 30%,transparent);color:var(--dsw-alias-state-success-primary);background:color-mix(in srgb,var(--dsw-alias-state-success-primary) 8%,transparent);padding:1px 7px;border-radius:999px;font-size:11px;font-weight:600;border-style:solid;border-width:1px}' +
|
|
121
|
+
'.dsub-tag-warn{border-color:color-mix(in srgb,var(--dsw-alias-state-warning-primary) 40%,transparent);color:var(--dsw-alias-state-warning-primary);background:color-mix(in srgb,var(--dsw-alias-state-warning-primary) 8%,transparent);padding:1px 7px;border-radius:999px;font-size:11px;font-weight:600;border-style:solid;border-width:1px}' +
|
|
122
|
+
'.dsub-tag-bad{border-color:color-mix(in srgb,var(--dsw-alias-state-error-primary) 40%,transparent);color:var(--dsw-alias-state-error-primary);background:color-mix(in srgb,var(--dsw-alias-state-error-primary) 8%,transparent);padding:1px 7px;border-radius:999px;font-size:11px;font-weight:600;border-style:solid;border-width:1px}' +
|
|
123
123
|
'.dsub-slot-meta-row{display:flex;flex-wrap:wrap;align-items:center;gap:10px;margin-top:12px;padding-top:10px;border-top:1px solid var(--dsw-alias-border-l2);font-size:12px}' +
|
|
124
124
|
'.dsub-ref-tag{display:inline-flex;align-items:center;gap:5px;font-size:11.5px;color:var(--dsw-alias-label-secondary)}' +
|
|
125
125
|
'.dsub-ref-tag code{padding:2px 6px;border-radius:4px;background:var(--dsw-alias-bg-layer-1);border:1px solid var(--dsw-alias-border-l2);font-family:ui-monospace,monospace;color:var(--dsw-alias-label-primary);font-size:11px}' +
|
|
126
|
-
'.dsub-btn-danger{border-color:
|
|
127
|
-
'.dsub-btn-danger:hover:not(:disabled){background:
|
|
126
|
+
'.dsub-btn-danger{border-color:color-mix(in srgb,var(--dsw-alias-state-error-primary) 40%,transparent)!important;color:var(--dsw-alias-state-error-primary)!important}' +
|
|
127
|
+
'.dsub-btn-danger:hover:not(:disabled){background:color-mix(in srgb,var(--dsw-alias-state-error-primary) 12%,transparent)!important}' +
|
|
128
128
|
'.dsub-poolTitle{margin-top:16px;margin-bottom:8px;font-size:11.5px;font-weight:700;color:var(--dsw-alias-label-secondary);text-transform:uppercase;letter-spacing:.05em}' +
|
|
129
129
|
'.dsub-accountCard{display:flex;align-items:center;gap:12px;padding:10px 14px;border:1px solid var(--dsw-alias-border-l2);border-radius:10px;background:var(--dsw-alias-bg-layer-2);margin-top:6px;transition:border-color .15s,background .15s}' +
|
|
130
130
|
'.dsub-accountCard:hover{background:var(--dsw-alias-bg-layer-3);border-color:var(--dsw-alias-label-tertiary)}' +
|
|
@@ -142,9 +142,9 @@ window.__ModuleLoader__.load({
|
|
|
142
142
|
'.dsub-accNameRow{display:flex;align-items:center;justify-content:space-between;gap:8px}' +
|
|
143
143
|
'.dsub-accName{font-size:13px;font-weight:600}' +
|
|
144
144
|
'.dsub-accStatusTag{font-size:10.5px;font-weight:600;padding:1px 6px;border-radius:4px}' +
|
|
145
|
-
'.dsub-statusOk{background:
|
|
146
|
-
'.dsub-statusWarn{background:
|
|
147
|
-
'.dsub-statusBad{background:
|
|
145
|
+
'.dsub-statusOk{background:color-mix(in srgb,var(--dsw-alias-state-success-primary) 15%,transparent);color:var(--dsw-alias-state-success-primary)}' +
|
|
146
|
+
'.dsub-statusWarn{background:color-mix(in srgb,var(--dsw-alias-state-warning-primary) 15%,transparent);color:var(--dsw-alias-state-warning-primary)}' +
|
|
147
|
+
'.dsub-statusBad{background:color-mix(in srgb,var(--dsw-alias-state-error-primary) 15%,transparent);color:var(--dsw-alias-state-error-primary)}' +
|
|
148
148
|
'.dsub-statusOff{background:var(--dsw-alias-bg-layer-1);color:var(--dsw-alias-label-tertiary)}' +
|
|
149
149
|
'.dsub-modalFoot{display:flex;align-items:center;justify-content:space-between;margin-top:16px;padding-top:12px;border-top:1px solid var(--dsw-alias-border-l2)}' +
|
|
150
150
|
'.dsub-btnPrimary{display:inline-flex;align-items:center;gap:6px;padding:6px 12px;border-radius:8px;font-size:12px;font-weight:600;background:var(--dsw-alias-brand-primary,#3b82f6);color:#fff;border:0;cursor:pointer;transition:opacity .15s}' +
|
|
@@ -169,9 +169,9 @@ const cssId = 'dsh-subscriptions/settings.module.css'
|
|
|
169
169
|
document.head.appendChild(tag)
|
|
170
170
|
}
|
|
171
171
|
|
|
172
|
-
//
|
|
173
|
-
//
|
|
174
|
-
//
|
|
172
|
+
// Card strings live in the locale registry: a separate package translates
|
|
173
|
+
// them without touching this plugin's code. English is the default,
|
|
174
|
+
// and the fallback when no translation exists.
|
|
175
175
|
const NS = 'dsh-subscriptions'
|
|
176
176
|
|
|
177
177
|
const INSTRUCTIONS = {
|
|
@@ -421,130 +421,6 @@ const cssId = 'dsh-subscriptions/settings.module.css'
|
|
|
421
421
|
'windowPrimary': 'Primary window',
|
|
422
422
|
'windowSecondary': 'Secondary window',
|
|
423
423
|
}
|
|
424
|
-
const ru = {
|
|
425
|
-
'check': 'Проверить',
|
|
426
|
-
'checking': 'Проверяю…',
|
|
427
|
-
'importToken': 'Сохранить',
|
|
428
|
-
'importTokenPlace': 'Вставить токен или API-ключ',
|
|
429
|
-
'importLocalCli': '📥 Из CLI',
|
|
430
|
-
'importLocalCliTitle': 'Автоматически считать токен из установленного на сервере CLI',
|
|
431
|
-
'importLocalSuccess': 'Токен успешно загружен из локального CLI!',
|
|
432
|
-
'howToGetToken': '❓ Инструкция',
|
|
433
|
-
'howToGetTokenTitle': 'Как получить токен или API-ключ для этого провайдера',
|
|
434
|
-
'instructionsTitle': 'Инструкция по получению токена',
|
|
435
|
-
'close': 'Закрыть',
|
|
436
|
-
'reconnectRequired': 'Нужно переподключить',
|
|
437
|
-
'manualTitle': 'Если браузер не вернулся сам',
|
|
438
|
-
'windowPrimary': 'Основное окно',
|
|
439
|
-
'windowSecondary': 'Дополнительное окно',
|
|
440
|
-
'notConnected': 'Не подключено',
|
|
441
|
-
'verifyAccount': 'Требуется проверка',
|
|
442
|
-
'coolingDown': 'Пауза после лимита',
|
|
443
|
-
'usageFull': 'Лимит исчерпан',
|
|
444
|
-
'connected': 'Подключено',
|
|
445
|
-
'title': 'Подписки',
|
|
446
|
-
'cardIntro': 'Аккаунты подписок, ротация и лимиты.',
|
|
447
|
-
'show': 'Показать',
|
|
448
|
-
'hide': 'Скрыть',
|
|
449
|
-
'intro': 'Вход по обычной пользовательской подписке. Токены остаются в хранилище учётных данных харнесса, браузер их обратно не читает. Если после «Подключить» провайдер увёл на localhost или на свою страницу, вставьте сюда адрес перехода или код.',
|
|
450
|
-
'useOrigin': 'Использовать адрес этого веб-интерфейса как redirect_uri',
|
|
451
|
-
'useOriginHint': 'Включайте, только если зарегистрировали собственного клиента OAuth на этот адрес. Штатным консольным клиентам провайдеров нужен их опубликованный адрес возврата и вставка кода вручную.',
|
|
452
|
-
'privacyMask': 'Маскировать email и учётные записи',
|
|
453
|
-
'privacyMaskHint': 'Для демонстрации экрана: email отображается как j***n@example.com.',
|
|
454
|
-
'resetCredits': 'Карты сброса',
|
|
455
|
-
'resetCreditsHint': 'Карты сброса квоты ChatGPT: показывает, сколько доступно. Списание одной карты — осознанное действие с 5-секундным подтверждением.',
|
|
456
|
-
'resetAvailable': 'Доступно попыток сброса',
|
|
457
|
-
'resetExpires': 'ближайшая истекает',
|
|
458
|
-
'resetAck': 'Я понимаю, что будет списана 1 попытка',
|
|
459
|
-
'resetWait': 'кнопка активна через',
|
|
460
|
-
'resetReady': 'готово',
|
|
461
|
-
'resetGo': 'Сбросить',
|
|
462
|
-
'resetBusy': 'сбрасываю…',
|
|
463
|
-
'resetDone': 'квота сброшена',
|
|
464
|
-
'resetNothing': 'сервер сообщает: сброс не нужен (попытка не списана)',
|
|
465
|
-
'resetNoCredit': 'нет пригодной карты (не списано)',
|
|
466
|
-
'resetRedeemed': 'эта карта уже использована ранее',
|
|
467
|
-
'diagGenerate': 'Сгенерировать диагностический отчёт',
|
|
468
|
-
'diagCopy': 'Скопировать отчёт',
|
|
469
|
-
'diagIssues': 'Открыть трекер задач',
|
|
470
|
-
'diagHint': 'В отчёте нет токенов, email, адресов прокси и персональных данных.',
|
|
471
|
-
'subsPill': 'SUBS',
|
|
472
|
-
'subsModalTitle': 'Консоль подписок',
|
|
473
|
-
'subsActiveHero': 'Активная подписка',
|
|
474
|
-
'subsPoolTitle': 'Пул аккаунтов',
|
|
475
|
-
'subsRefresh': 'Обновить',
|
|
476
|
-
'subsOpenSettings': 'Все настройки →',
|
|
477
|
-
'subsFastMode': 'Режим Fast 1.5x',
|
|
478
|
-
'subsHealthy': 'Исправен',
|
|
479
|
-
'subsFree': 'свободно',
|
|
480
|
-
'subsUsed': 'израсходовано',
|
|
481
|
-
'subsModalClose': 'Закрыть',
|
|
482
|
-
'subsLogged': 'подключен',
|
|
483
|
-
'subsNotLogged': 'не подключен',
|
|
484
|
-
'subsCooldown': 'кулдаун',
|
|
485
|
-
'subsOpenSettings': 'Открыть настройки',
|
|
486
|
-
'hudTitle': 'Остаток подписки',
|
|
487
|
-
'settingsLoading': 'Загрузка настроек…',
|
|
488
|
-
'settingsUnavailable': 'Настройки недоступны',
|
|
489
|
-
'settingsRetry': 'Повторить',
|
|
490
|
-
'hudHint': 'перетащи · прилипает к краям · клик — обновить',
|
|
491
|
-
'fcCalibrating': 'калибровка…',
|
|
492
|
-
'fcIdle': 'нет расхода',
|
|
493
|
-
'fcHours': 'ч',
|
|
494
|
-
'fcMinutes': 'мин',
|
|
495
|
-
'reconnect': 'Переподключить',
|
|
496
|
-
'connect': 'Подключить',
|
|
497
|
-
'disconnect': 'Отключить',
|
|
498
|
-
'removeSlot': 'Убрать место',
|
|
499
|
-
'pastePlaceholder': 'Адрес перехода или код',
|
|
500
|
-
'submitCode': 'Отправить код',
|
|
501
|
-
'proxyPlaceholder': 'Прокси аккаунта (http://, https://, socks5://) - необязательно',
|
|
502
|
-
'proxyCheck': 'Проверить прокси',
|
|
503
|
-
'proxyOk': 'прокси ок',
|
|
504
|
-
'proxyFail': 'прокси недоступен',
|
|
505
|
-
'deviceLogin': 'Вход по коду',
|
|
506
|
-
'deviceHint': 'Headless: откройте ссылку, введите код, держите вкладку открытой.',
|
|
507
|
-
'deviceCopy': 'Скопировать код',
|
|
508
|
-
'devicePending': 'Ожидание подтверждения',
|
|
509
|
-
'deviceAuthorized': 'Вход выполнен',
|
|
510
|
-
'deviceExpired': 'Истёк - начните заново',
|
|
511
|
-
'verifyPrefix': 'Google требует однократной проверки аккаунта. ',
|
|
512
|
-
'verifyLink': 'Открыть страницу проверки',
|
|
513
|
-
'verifySuffix': ' затем подключитесь заново.',
|
|
514
|
-
'addAccount': '+ Добавить аккаунт',
|
|
515
|
-
'save': 'Сохранить',
|
|
516
|
-
'saved': 'Сохранено',
|
|
517
|
-
'loading': 'Загрузка…',
|
|
518
|
-
'accountLabel': 'Аккаунт',
|
|
519
|
-
'plan': 'Тариф',
|
|
520
|
-
'storedAs': 'Хранится как',
|
|
521
|
-
'switchLabel': 'Провайдер',
|
|
522
|
-
'chipActive': 'Подписки',
|
|
523
|
-
'expiryLabel': 'окончание',
|
|
524
|
-
'slashLogin': 'Вход в провайдера подписки',
|
|
525
|
-
'slashLogout': 'Выйти из провайдера подписки',
|
|
526
|
-
'slashStatus': 'Статус подписок',
|
|
527
|
-
'none': 'нет',
|
|
528
|
-
'forecast': '≈',
|
|
529
|
-
'resetCredits': 'Сброс лимитов ChatGPT',
|
|
530
|
-
'resetCreditsHint': 'Экстренный сброс 5-часового окна ChatGPT Plus/Pro. Требует 5 секунд подтверждения перед списанием.',
|
|
531
|
-
'resetCreditsAction': 'Сбросить лимит ChatGPT',
|
|
532
|
-
'resetAvailable': 'Доступно сбросов',
|
|
533
|
-
'resetExpires': 'срок действия',
|
|
534
|
-
'resetAck': 'Я понимаю, что одна попытка сброса будет списана',
|
|
535
|
-
'resetWait': 'подтверждение через',
|
|
536
|
-
'resetReady': 'готово к сбросу',
|
|
537
|
-
'resetGo': 'Сбросить лимит сейчас',
|
|
538
|
-
'resetBusy': 'Сброс…',
|
|
539
|
-
'resetDone': 'Лимит успешно сброшен',
|
|
540
|
-
'resetNothing': 'Лимит не требует сброса',
|
|
541
|
-
'resetNoCredit': 'Нет доступных попыток сброса',
|
|
542
|
-
'resetRedeemed': 'Попытка уже использована',
|
|
543
|
-
'windowPrimary': 'Окно 5 часов',
|
|
544
|
-
'windowSecondary': 'Окно 7 дней',
|
|
545
|
-
'usageLimitsTitle': 'Лимиты использования',
|
|
546
|
-
'slotDetailsTitle': 'Параметры слота',
|
|
547
|
-
}
|
|
548
424
|
|
|
549
425
|
// #51: live countdown to the quota window reset (account.quota.resetAt).
|
|
550
426
|
function cleanErrorMessage(raw) {
|
|
@@ -633,9 +509,9 @@ const cssId = 'dsh-subscriptions/settings.module.css'
|
|
|
633
509
|
return t('connected')
|
|
634
510
|
}
|
|
635
511
|
|
|
636
|
-
//
|
|
637
|
-
//
|
|
638
|
-
//
|
|
512
|
+
// The card in the Plugins tab draws its own header and collapse toggle:
|
|
513
|
+
// the core only provides the list frame.
|
|
514
|
+
// Core chevron icon. Without a guarded require, a missing IconChevronDownOutline14 could take down the whole client half.
|
|
639
515
|
let ChevronIcon = null
|
|
640
516
|
try {
|
|
641
517
|
const primitives = require('@deepseek-ai/dsh-client-ui-primitives')
|
|
@@ -653,7 +529,7 @@ const cssId = 'dsh-subscriptions/settings.module.css'
|
|
|
653
529
|
|
|
654
530
|
|
|
655
531
|
function SubsSection(props) {
|
|
656
|
-
//
|
|
532
|
+
// The translator comes from the slot because its registration declares locale.
|
|
657
533
|
const t = (props && props.t) || ((key) => key)
|
|
658
534
|
const [draft, setDraft] = React.useState(null)
|
|
659
535
|
const [accounts, setAccounts] = React.useState([])
|
|
@@ -852,7 +728,7 @@ const cssId = 'dsh-subscriptions/settings.module.css'
|
|
|
852
728
|
setPaste((p) => Object.assign({}, p, { [key]: '' }))
|
|
853
729
|
}
|
|
854
730
|
|
|
855
|
-
// #88:
|
|
731
|
+
// #88: probe the account proxy with a real request and measure latency.
|
|
856
732
|
const doProxyCheck = async (provider, index) => {
|
|
857
733
|
const key = provider + ':' + index
|
|
858
734
|
setChecking((c) => Object.assign({}, c, { ['proxy:' + key]: true }))
|
|
@@ -1331,8 +1207,8 @@ const cssId = 'dsh-subscriptions/settings.module.css'
|
|
|
1331
1207
|
)
|
|
1332
1208
|
}
|
|
1333
1209
|
|
|
1334
|
-
//
|
|
1335
|
-
//
|
|
1210
|
+
// The card in the Plugins tab follows the bash/agent-loop/web-search cards:
|
|
1211
|
+
// titled header with description and chevron, collapsible, bordered.
|
|
1336
1212
|
function PluginCard(props) {
|
|
1337
1213
|
const t = (props && props.t) || ((key) => key)
|
|
1338
1214
|
const [open, setOpen] = React.useState(false)
|
|
@@ -1358,14 +1234,14 @@ const cssId = 'dsh-subscriptions/settings.module.css'
|
|
|
1358
1234
|
}
|
|
1359
1235
|
|
|
1360
1236
|
function registerSettings(ctx) {
|
|
1361
|
-
//
|
|
1362
|
-
//
|
|
1363
|
-
//
|
|
1364
|
-
//
|
|
1365
|
-
// load plugins
|
|
1237
|
+
// Languages can come from more than this plugin: dictionary packages declare
|
|
1238
|
+
// Russian for foreign namespaces. The core throws on re-declaring the same
|
|
1239
|
+
// namespace+language pair, and an unguarded call used to take down the
|
|
1240
|
+
// whole plugin - in the UI it surfaced as Failed to
|
|
1241
|
+
// load plugins listing perfectly innocent neighbours.
|
|
1366
1242
|
//
|
|
1367
|
-
//
|
|
1368
|
-
//
|
|
1243
|
+
// So each language is declared separately and politely: if someone got
|
|
1244
|
+
// there first we yield, while our own English still lands.
|
|
1369
1245
|
const addLocale = (locale, dictionary) => {
|
|
1370
1246
|
try {
|
|
1371
1247
|
return ctx.locale.register(NS, locale, dictionary)
|
|
@@ -1374,14 +1250,14 @@ const cssId = 'dsh-subscriptions/settings.module.css'
|
|
|
1374
1250
|
}
|
|
1375
1251
|
}
|
|
1376
1252
|
ctx.effect(() => {
|
|
1377
|
-
const undo = [addLocale('en', en)
|
|
1253
|
+
const undo = [addLocale('en', en)]
|
|
1378
1254
|
return () => { for (const off of undo) off() }
|
|
1379
1255
|
}, 'dsh-subscriptions: словари')
|
|
1380
|
-
|
|
1256
|
+
// Labels outside a component take the translator bound to the namespace.
|
|
1381
1257
|
setT(ctx.locale.bind(NS))
|
|
1382
|
-
//
|
|
1383
|
-
//
|
|
1384
|
-
//
|
|
1258
|
+
// The canonical place is the Plugins tab: a card with its own header
|
|
1259
|
+
// and collapse instead of a row in the side list. The registration key must
|
|
1260
|
+
// equal the settings namespace, otherwise the tab silently hides the slot.
|
|
1385
1261
|
let moved = false
|
|
1386
1262
|
try {
|
|
1387
1263
|
moved = !!ctx.slots.inject('settings.plugin.item', () => ctx.slots.register(
|
|
@@ -1417,8 +1293,8 @@ const cssId = 'dsh-subscriptions/settings.module.css'
|
|
|
1417
1293
|
}
|
|
1418
1294
|
}
|
|
1419
1295
|
|
|
1420
|
-
//
|
|
1421
|
-
//
|
|
1296
|
+
// Hot provider switcher in the composer row: a click cycles
|
|
1297
|
+
// the active subscription provider for the next request.
|
|
1422
1298
|
let activeIdx = 0
|
|
1423
1299
|
let labelEl = null
|
|
1424
1300
|
const ORDER = ['codex', 'claude', 'grok', 'antigravity']
|
|
@@ -1443,8 +1319,8 @@ const cssId = 'dsh-subscriptions/settings.module.css'
|
|
|
1443
1319
|
} catch { return { logged: [], usage: 0, expiresAt: {}, labels: {}, expiryNotifyDays: 7, fastMode: false, composerQuota: 'off', active: null } }
|
|
1444
1320
|
}
|
|
1445
1321
|
|
|
1446
|
-
// #84:
|
|
1447
|
-
//
|
|
1322
|
+
// #84: compact quota indicator of the active subscription in the input area.
|
|
1323
|
+
// The runway forecast uses a rolling window of /status samples.
|
|
1448
1324
|
const fcSamples = { windows: {} }
|
|
1449
1325
|
|
|
1450
1326
|
function fcObserve(key, remaining, now) {
|
|
@@ -1528,8 +1404,8 @@ const cssId = 'dsh-subscriptions/settings.module.css'
|
|
|
1528
1404
|
return React.createElement('span', { className: 'dsub-cq', title: t('forecast') + ' · ' + state.active.provider }, value)
|
|
1529
1405
|
}
|
|
1530
1406
|
|
|
1531
|
-
// #83:
|
|
1532
|
-
//
|
|
1407
|
+
// #83: SUBS(N) pill in the session header with a pool health LED and
|
|
1408
|
+
// a modal accounts console (close: X button, Escape, outside click).
|
|
1533
1409
|
function brandBadge(prov) {
|
|
1534
1410
|
const p = String(prov || '').toLowerCase()
|
|
1535
1411
|
if (p.includes('codex') || p.includes('chatgpt') || p.includes('openai')) return { label: 'OpenAI', cls: 'dsub-brandCodex', icon: '⚡' }
|
|
@@ -1617,7 +1493,7 @@ const cssId = 'dsh-subscriptions/settings.module.css'
|
|
|
1617
1493
|
React.createElement('div', { className: 'dsub-heroTitle' },
|
|
1618
1494
|
React.createElement('span', { className: 'dsub-brandBadge ' + b.cls, style: { width: 24, height: 24, fontSize: 11 } }, b.icon),
|
|
1619
1495
|
React.createElement('span', null, (a.provider ? a.provider.toUpperCase() : 'LLM') + (a.index ? ' #' + a.index : '')),
|
|
1620
|
-
a.fastMode ? React.createElement('span', { className: 'dsub-pillTag', style: { background: '
|
|
1496
|
+
a.fastMode ? React.createElement('span', { className: 'dsub-pillTag', style: { background: 'color-mix(in srgb,var(--dsw-alias-state-warning-primary) 15%,transparent)', color: 'var(--dsw-alias-state-warning-primary)' } }, '⚡ FAST 1.5x') : null,
|
|
1621
1497
|
),
|
|
1622
1498
|
React.createElement('span', { className: 'dsub-heroModel' }, a.model || 'active model'),
|
|
1623
1499
|
),
|
|
@@ -1681,7 +1557,7 @@ const cssId = 'dsh-subscriptions/settings.module.css'
|
|
|
1681
1557
|
React.createElement('div', { className: 'dsub-accNameRow' },
|
|
1682
1558
|
React.createElement('div', { style: { display: 'flex', alignItems: 'center', gap: 6 } },
|
|
1683
1559
|
React.createElement('span', { className: 'dsub-accName' }, a.provider.toUpperCase() + (a.index ? ' #' + a.index : '')),
|
|
1684
|
-
isActiveThis ? React.createElement('span', { className: 'dsub-pillTag', style: { background: '
|
|
1560
|
+
isActiveThis ? React.createElement('span', { className: 'dsub-pillTag', style: { background: 'color-mix(in srgb,var(--dsw-alias-state-success-primary) 15%,transparent)', color: 'var(--dsw-alias-state-success-primary)' } }, 'ACTIVE') : null,
|
|
1685
1561
|
a.proxy ? React.createElement('span', { className: 'dsub-dim', title: a.proxy }, '🌐 proxy') : null,
|
|
1686
1562
|
),
|
|
1687
1563
|
React.createElement('span', { className: 'dsub-accStatusTag ' + statusClass }, statusText),
|
package/lib/crypto.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { scryptSync, createCipheriv, createDecipheriv, randomBytes } from "node:crypto"
|
|
2
2
|
|
|
3
|
-
// AES-256-GCM
|
|
4
|
-
//
|
|
3
|
+
// AES-256-GCM with a passphrase-derived key (scrypt, no separately stored salt -
|
|
4
|
+
// the salt is stored at the head of the block).
|
|
5
5
|
export function encryptWithPassphrase(plainText, passphrase) {
|
|
6
6
|
const salt = randomBytes(16)
|
|
7
7
|
const key = scryptSync(String(passphrase), salt, 32)
|
|
@@ -9,13 +9,13 @@ export function encryptWithPassphrase(plainText, passphrase) {
|
|
|
9
9
|
const cipher = createCipheriv("aes-256-gcm", key, iv)
|
|
10
10
|
const encrypted = Buffer.concat([cipher.update(String(plainText), "utf8"), cipher.final()])
|
|
11
11
|
const tag = cipher.getAuthTag()
|
|
12
|
-
//
|
|
12
|
+
// format: magic + salt + iv + tag + data
|
|
13
13
|
return "DSHE1:" + Buffer.concat([salt, iv, tag, encrypted]).toString("base64")
|
|
14
14
|
}
|
|
15
15
|
|
|
16
16
|
export function decryptWithPassphrase(payload, passphrase) {
|
|
17
17
|
const raw = String(payload || "")
|
|
18
|
-
if (!raw.startsWith("DSHE1:")) throw new Error("
|
|
18
|
+
if (!raw.startsWith("DSHE1:")) throw new Error("unknown export format")
|
|
19
19
|
const buf = Buffer.from(raw.slice(6), "base64")
|
|
20
20
|
const salt = buf.subarray(0, 16)
|
|
21
21
|
const iv = buf.subarray(16, 28)
|
package/lib/google-validation.js
CHANGED
|
@@ -121,7 +121,7 @@ export function googleLicenseMessage(status, bodyText) {
|
|
|
121
121
|
const api = parseGoogleApiError(bodyText)
|
|
122
122
|
const msg = String(api?.message || bodyText || '')
|
|
123
123
|
if (/do not have a valid license|#3501|PERMISSION_DENIED/i.test(msg)) {
|
|
124
|
-
return '
|
|
124
|
+
return 'Google account has no active Gemini Code Assist / Antigravity license (#3501), or no Google Cloud project is attached. Check the subscription at https://one.google.com or connect a different Google account.'
|
|
125
125
|
}
|
|
126
126
|
return ''
|
|
127
127
|
}
|
package/lib/health.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
//
|
|
2
|
-
//
|
|
1
|
+
// Per-account health score for smart rotation.
|
|
2
|
+
// Numbers mirror dsh-key-rotation pool.js: switch 5, exhaustion 10, bad key 15.
|
|
3
3
|
const PENALTY = { switch: 5, exhaust: 10, broken: 15 }
|
|
4
4
|
|
|
5
5
|
export function emptyHealth() {
|
package/lib/history.js
CHANGED
|
@@ -2,9 +2,9 @@ import { mkdirSync, readFileSync, writeFileSync } from 'node:fs'
|
|
|
2
2
|
import { join } from 'node:path'
|
|
3
3
|
import { homedir } from 'node:os'
|
|
4
4
|
|
|
5
|
-
// #65:
|
|
6
|
-
// ~/.dsh/storages/dsh-subscriptions/history.json (JSON
|
|
7
|
-
//
|
|
5
|
+
// #65: request and cost history. Stored in
|
|
6
|
+
// ~/.dsh/storages/dsh-subscriptions/history.json (JSON array, newest first).
|
|
7
|
+
// All IO is best-effort and synchronous: a write failure must never crash the harness.
|
|
8
8
|
|
|
9
9
|
export const DEFAULT_HISTORY_DIR = join(homedir(), '.dsh', 'storages', 'dsh-subscriptions')
|
|
10
10
|
|
|
@@ -42,7 +42,7 @@ export class HistoryStore {
|
|
|
42
42
|
} catch { /* best-effort */ }
|
|
43
43
|
}
|
|
44
44
|
|
|
45
|
-
/**
|
|
45
|
+
/** Add one entry. Returns the history length. */
|
|
46
46
|
add(entry) {
|
|
47
47
|
if (!entry || typeof entry !== 'object') return this.rows.length
|
|
48
48
|
this.rows.unshift({ ts: Date.now(), ...entry })
|
|
@@ -51,7 +51,7 @@ export class HistoryStore {
|
|
|
51
51
|
return this.rows.length
|
|
52
52
|
}
|
|
53
53
|
|
|
54
|
-
/**
|
|
54
|
+
/** Last N entries (newest first). */
|
|
55
55
|
recent(n) {
|
|
56
56
|
return this.rows.slice(0, n)
|
|
57
57
|
}
|
package/lib/http.js
CHANGED
package/lib/images.js
CHANGED
|
@@ -1,26 +1,26 @@
|
|
|
1
|
-
//
|
|
1
|
+
// Image generation on a subscription.
|
|
2
2
|
//
|
|
3
|
-
//
|
|
4
|
-
//
|
|
5
|
-
//
|
|
3
|
+
// This module only implements the protocol: where to go, what to send and how to read the response. Everything
|
|
4
|
+
// else - saving the file, attaching it to the conversation, the card - is the job of the generation
|
|
5
|
+
// plugin; this plugin only lends its account.
|
|
6
6
|
//
|
|
7
|
-
//
|
|
8
|
-
//
|
|
9
|
-
//
|
|
7
|
+
// The token never leaves the process: the plugin exposes an in-process service, not
|
|
8
|
+
// a network route. The harness on this machine is reachable without a password, and an endpoint
|
|
9
|
+
// handing out a live subscription token would be a wider hole than the ones we already closed.
|
|
10
10
|
|
|
11
|
-
/**
|
|
11
|
+
/** Where the ChatGPT subscription request goes. */
|
|
12
12
|
export const CODEX_URL = 'https://chatgpt.com/backend-api/codex/images/generations'
|
|
13
|
-
/**
|
|
13
|
+
/** The model served by this endpoint. */
|
|
14
14
|
export const CODEX_MODEL = 'gpt-image-2'
|
|
15
|
-
/**
|
|
15
|
+
/** Where the Grok subscription request goes. */
|
|
16
16
|
export const GROK_URL = 'https://api.x.ai/v1/images/generations'
|
|
17
|
-
/**
|
|
17
|
+
/** The model served by this endpoint. */
|
|
18
18
|
export const GROK_MODEL = 'grok-imagine-image-2.0'
|
|
19
19
|
|
|
20
|
-
/**
|
|
20
|
+
/** Sizes understood by ChatGPT. */
|
|
21
21
|
export const SIZES = ['1024x1024', '1024x1536', '1536x1024', 'auto']
|
|
22
22
|
|
|
23
|
-
/** Grok
|
|
23
|
+
/** Grok thinks in aspect ratios, not sizes. */
|
|
24
24
|
const GROK_ASPECT = {
|
|
25
25
|
'1024x1024': '1:1',
|
|
26
26
|
'1024x1536': '2:3',
|
|
@@ -30,7 +30,7 @@ const GROK_ASPECT = {
|
|
|
30
30
|
|
|
31
31
|
export function codexBody({ prompt, size, quality }) {
|
|
32
32
|
const text = String(prompt || '').trim()
|
|
33
|
-
if (!text) throw new Error('
|
|
33
|
+
if (!text) throw new Error('prompt must be a non-empty string')
|
|
34
34
|
return {
|
|
35
35
|
prompt: text,
|
|
36
36
|
model: CODEX_MODEL,
|
|
@@ -41,8 +41,8 @@ export function codexBody({ prompt, size, quality }) {
|
|
|
41
41
|
|
|
42
42
|
export function grokBody({ prompt, size, quality }) {
|
|
43
43
|
const text = String(prompt || '').trim()
|
|
44
|
-
if (!text) throw new Error('
|
|
45
|
-
//
|
|
44
|
+
if (!text) throw new Error('prompt must be a non-empty string')
|
|
45
|
+
// Grok has only two quality tiers: high is composed from medium.
|
|
46
46
|
const level = quality === 'low' ? 'low'
|
|
47
47
|
: (quality === 'medium' || quality === 'high') ? 'medium'
|
|
48
48
|
: undefined
|
|
@@ -55,7 +55,7 @@ export function grokBody({ prompt, size, quality }) {
|
|
|
55
55
|
}
|
|
56
56
|
}
|
|
57
57
|
|
|
58
|
-
/**
|
|
58
|
+
/** Response parsing: both sides reply in the same shape. */
|
|
59
59
|
export function parseImages(payload) {
|
|
60
60
|
const body = payload && typeof payload === 'object' ? payload : {}
|
|
61
61
|
const rows = Array.isArray(body.data) ? body.data : []
|
|
@@ -70,15 +70,15 @@ export function parseImages(payload) {
|
|
|
70
70
|
: {}),
|
|
71
71
|
})
|
|
72
72
|
}
|
|
73
|
-
if (!images.length) throw new Error('
|
|
73
|
+
if (!images.length) throw new Error('response contains no images')
|
|
74
74
|
return images
|
|
75
75
|
}
|
|
76
76
|
|
|
77
77
|
/**
|
|
78
|
-
*
|
|
78
|
+
* One request to the right endpoint with this provider's headers.
|
|
79
79
|
*
|
|
80
80
|
* @param options {{provider, prompt, size, quality, session, fetchImpl, signal}}
|
|
81
|
-
* session
|
|
81
|
+
* session - already refreshed token block: accessToken and, for ChatGPT, accountId.
|
|
82
82
|
*/
|
|
83
83
|
export async function generateOnce(options) {
|
|
84
84
|
const { provider, session, fetchImpl, signal } = options
|
|
@@ -88,7 +88,7 @@ export async function generateOnce(options) {
|
|
|
88
88
|
const headers = isCodex
|
|
89
89
|
? {
|
|
90
90
|
authorization: `Bearer ${session.accessToken}`,
|
|
91
|
-
// ChatGPT
|
|
91
|
+
// ChatGPT distinguishes accounts with a separate header; without it the request is rejected.
|
|
92
92
|
'chatgpt-account-id': session.accountId || '',
|
|
93
93
|
originator: 'codex_cli_rs',
|
|
94
94
|
'content-type': 'application/json',
|