@goodandready/dsh-subscriptions 0.6.0 → 0.6.2
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 +12 -1
- package/README.ru.md +11 -0
- package/README.zh.md +6 -0
- package/lib/accounts.js +1 -1
- package/lib/adapter.js +2 -2
- package/lib/client.js +220 -189
- 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 +5 -5
- package/lib/ui-i18n.js +0 -256
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)}' +
|
|
@@ -103,12 +103,16 @@ window.__ModuleLoader__.load({
|
|
|
103
103
|
'.dsub-heroHead{display:flex;align-items:center;justify-content:space-between;margin-bottom:10px}' +
|
|
104
104
|
'.dsub-heroTitle{font-size:14px;font-weight:700;display:flex;align-items:center;gap:6px}' +
|
|
105
105
|
'.dsub-heroModel{font-size:11.5px;color:var(--dsw-alias-label-secondary);font-family:ui-monospace,monospace}' +
|
|
106
|
+
'.dsub-select{appearance:none;height:30px;border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-3);color:var(--dsw-alias-label-primary);border-radius:6px;padding:0 24px 0 8px;font-size:12px;cursor:pointer}' +
|
|
107
|
+
'.dsub-details{margin-top:12px;border:1px solid var(--dsw-alias-border-l2);border-radius:8px;padding:8px 12px;background:var(--dsw-alias-bg-layer-2)}' +
|
|
108
|
+
'.dsub-summary{cursor:pointer;font-weight:600;font-size:12.5px;color:var(--dsw-alias-label-secondary);user-select:none}' +
|
|
109
|
+
'.dsub-summary:hover{color:var(--dsw-alias-label-primary)}' +
|
|
106
110
|
'.dsub-heroBars{display:flex;flex-direction:column;gap:8px;margin-top:10px}' +
|
|
107
111
|
'.dsub-barLabelRow{display:flex;justify-content:space-between;font-size:11.5px;color:var(--dsw-alias-label-secondary);margin-bottom:3px}' +
|
|
108
112
|
'.dsub-barTrack{height:6px;border-radius:3px;background:var(--dsw-alias-bg-layer-1);overflow:hidden}' +
|
|
109
113
|
'.dsub-barFillGrad{height:100%;border-radius:3px;transition:width .3s ease}' +
|
|
110
114
|
'.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:
|
|
115
|
+
'.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
116
|
'.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
117
|
'.dsub-panel-desc{font-size:12px;color:var(--dsw-alias-label-secondary);line-height:1.4}' +
|
|
114
118
|
'.dsub-usage-grid{display:flex;flex-direction:column;gap:8px;margin-top:4px}' +
|
|
@@ -117,14 +121,14 @@ window.__ModuleLoader__.load({
|
|
|
117
121
|
'.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
122
|
'.dsub-usage-fill{height:100%;border-radius:999px;transition:width .3s ease}' +
|
|
119
123
|
'.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:
|
|
124
|
+
'.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}' +
|
|
125
|
+
'.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}' +
|
|
126
|
+
'.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
127
|
'.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
128
|
'.dsub-ref-tag{display:inline-flex;align-items:center;gap:5px;font-size:11.5px;color:var(--dsw-alias-label-secondary)}' +
|
|
125
129
|
'.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:
|
|
130
|
+
'.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}' +
|
|
131
|
+
'.dsub-btn-danger:hover:not(:disabled){background:color-mix(in srgb,var(--dsw-alias-state-error-primary) 12%,transparent)!important}' +
|
|
128
132
|
'.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
133
|
'.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
134
|
'.dsub-accountCard:hover{background:var(--dsw-alias-bg-layer-3);border-color:var(--dsw-alias-label-tertiary)}' +
|
|
@@ -142,9 +146,9 @@ window.__ModuleLoader__.load({
|
|
|
142
146
|
'.dsub-accNameRow{display:flex;align-items:center;justify-content:space-between;gap:8px}' +
|
|
143
147
|
'.dsub-accName{font-size:13px;font-weight:600}' +
|
|
144
148
|
'.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:
|
|
149
|
+
'.dsub-statusOk{background:color-mix(in srgb,var(--dsw-alias-state-success-primary) 15%,transparent);color:var(--dsw-alias-state-success-primary)}' +
|
|
150
|
+
'.dsub-statusWarn{background:color-mix(in srgb,var(--dsw-alias-state-warning-primary) 15%,transparent);color:var(--dsw-alias-state-warning-primary)}' +
|
|
151
|
+
'.dsub-statusBad{background:color-mix(in srgb,var(--dsw-alias-state-error-primary) 15%,transparent);color:var(--dsw-alias-state-error-primary)}' +
|
|
148
152
|
'.dsub-statusOff{background:var(--dsw-alias-bg-layer-1);color:var(--dsw-alias-label-tertiary)}' +
|
|
149
153
|
'.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
154
|
'.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 +173,9 @@ const cssId = 'dsh-subscriptions/settings.module.css'
|
|
|
169
173
|
document.head.appendChild(tag)
|
|
170
174
|
}
|
|
171
175
|
|
|
172
|
-
//
|
|
173
|
-
//
|
|
174
|
-
//
|
|
176
|
+
// Card strings live in the locale registry: a separate package translates
|
|
177
|
+
// them without touching this plugin's code. English is the default,
|
|
178
|
+
// and the fallback when no translation exists.
|
|
175
179
|
const NS = 'dsh-subscriptions'
|
|
176
180
|
|
|
177
181
|
const INSTRUCTIONS = {
|
|
@@ -308,6 +312,37 @@ const cssId = 'dsh-subscriptions/settings.module.css'
|
|
|
308
312
|
'useOriginHint': 'Leave off unless you registered your own OAuth client for this origin. Vendor CLI clients usually require their published redirect URI plus the paste step.',
|
|
309
313
|
'privacyMask': 'Mask emails and account identifiers',
|
|
310
314
|
'privacyMaskHint': 'For demos and screen sharing: emails show as j***n@example.com.',
|
|
315
|
+
'autoLoopback': 'Auto Loopback',
|
|
316
|
+
'autoLoopbackHint': 'Listen on local callback port to catch OAuth tokens automatically without manual copy-paste.',
|
|
317
|
+
'hideDeprecated': 'Hide deprecated models',
|
|
318
|
+
'hideDeprecatedHint': 'Hide older or superseded model variants from model dropdowns.',
|
|
319
|
+
'composerQuota': 'Composer quota indicator',
|
|
320
|
+
'composerQuotaHint': 'Display subscription usage indicator directly in the composer toolbar.',
|
|
321
|
+
'composerQuotaOff': 'Disabled',
|
|
322
|
+
'composerQuotaPercent': 'Percentage',
|
|
323
|
+
'composerQuotaBar': 'Progress Bar',
|
|
324
|
+
'composerQuotaForecast': 'Remaining Forecast',
|
|
325
|
+
'expiryNotifyDays': 'Expiry warning threshold (days)',
|
|
326
|
+
'expiryNotifyDaysHint': 'Notify when subscription renewal or token expiration is within this many days (0 to disable).',
|
|
327
|
+
'codexFastMode': 'Codex fast mode (1.5x)',
|
|
328
|
+
'codexFastModeHint': 'Enable high-throughput mode for OpenAI Codex models if supported by subscription.',
|
|
329
|
+
'codexVerbosity': 'Codex streaming verbosity',
|
|
330
|
+
'codexVerbosityHint': 'Control streaming chunking detail level from the Codex provider.',
|
|
331
|
+
'codexVerbosityDefault': 'Default',
|
|
332
|
+
'codexVerbosityLow': 'Low',
|
|
333
|
+
'codexVerbosityMedium': 'Medium',
|
|
334
|
+
'codexVerbosityHigh': 'High',
|
|
335
|
+
'ollamaFallback': 'Ollama local fallback',
|
|
336
|
+
'ollamaFallbackHint': 'Automatically route queries to a local Ollama instance when cloud quota is exhausted.',
|
|
337
|
+
'ollamaBaseUrl': 'Ollama Base URL',
|
|
338
|
+
'ollamaFallbackModel': 'Ollama Fallback Model',
|
|
339
|
+
'advancedSettings': 'Advanced Settings & Monitoring',
|
|
340
|
+
'cooldownMs': 'Failure cooldown (minutes)',
|
|
341
|
+
'cooldownMsHint': 'How long to pause a failing or rate-limited account before retrying.',
|
|
342
|
+
'probeIntervalMin': 'Health probe interval (minutes)',
|
|
343
|
+
'probeIntervalMinHint': 'Interval for background checks to refresh account status and quota balance.',
|
|
344
|
+
'notifyLimits': 'Notify on rate limits',
|
|
345
|
+
'notifyLimitsHint': 'Show notification alerts when account usage reaches quota ceiling.',
|
|
311
346
|
'resetCredits': 'Reset credits',
|
|
312
347
|
'resetCreditsHint': 'ChatGPT reset cards: emergency reset for the 5-hour window. Requires a 5s confirmation.',
|
|
313
348
|
'resetCreditsAction': 'Reset ChatGPT Limit',
|
|
@@ -421,130 +456,6 @@ const cssId = 'dsh-subscriptions/settings.module.css'
|
|
|
421
456
|
'windowPrimary': 'Primary window',
|
|
422
457
|
'windowSecondary': 'Secondary window',
|
|
423
458
|
}
|
|
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
459
|
|
|
549
460
|
// #51: live countdown to the quota window reset (account.quota.resetAt).
|
|
550
461
|
function cleanErrorMessage(raw) {
|
|
@@ -633,9 +544,9 @@ const cssId = 'dsh-subscriptions/settings.module.css'
|
|
|
633
544
|
return t('connected')
|
|
634
545
|
}
|
|
635
546
|
|
|
636
|
-
//
|
|
637
|
-
//
|
|
638
|
-
//
|
|
547
|
+
// The card in the Plugins tab draws its own header and collapse toggle:
|
|
548
|
+
// the core only provides the list frame.
|
|
549
|
+
// Core chevron icon. Without a guarded require, a missing IconChevronDownOutline14 could take down the whole client half.
|
|
639
550
|
let ChevronIcon = null
|
|
640
551
|
try {
|
|
641
552
|
const primitives = require('@deepseek-ai/dsh-client-ui-primitives')
|
|
@@ -653,7 +564,7 @@ const cssId = 'dsh-subscriptions/settings.module.css'
|
|
|
653
564
|
|
|
654
565
|
|
|
655
566
|
function SubsSection(props) {
|
|
656
|
-
//
|
|
567
|
+
// The translator comes from the slot because its registration declares locale.
|
|
657
568
|
const t = (props && props.t) || ((key) => key)
|
|
658
569
|
const [draft, setDraft] = React.useState(null)
|
|
659
570
|
const [accounts, setAccounts] = React.useState([])
|
|
@@ -852,7 +763,7 @@ const cssId = 'dsh-subscriptions/settings.module.css'
|
|
|
852
763
|
setPaste((p) => Object.assign({}, p, { [key]: '' }))
|
|
853
764
|
}
|
|
854
765
|
|
|
855
|
-
// #88:
|
|
766
|
+
// #88: probe the account proxy with a real request and measure latency.
|
|
856
767
|
const doProxyCheck = async (provider, index) => {
|
|
857
768
|
const key = provider + ':' + index
|
|
858
769
|
setChecking((c) => Object.assign({}, c, { ['proxy:' + key]: true }))
|
|
@@ -971,6 +882,146 @@ const cssId = 'dsh-subscriptions/settings.module.css'
|
|
|
971
882
|
),
|
|
972
883
|
React.createElement('div', { className: 'dsub-sub' },
|
|
973
884
|
t('privacyMaskHint')),
|
|
885
|
+
React.createElement('label', { className: 'dsub-row' },
|
|
886
|
+
React.createElement('input', {
|
|
887
|
+
type: 'checkbox',
|
|
888
|
+
checked: !!draft.autoLoopback,
|
|
889
|
+
onChange: (e) => setDraft((d) => Object.assign({}, d, { autoLoopback: e.target.checked })),
|
|
890
|
+
}),
|
|
891
|
+
React.createElement('span', null, t('autoLoopback')),
|
|
892
|
+
),
|
|
893
|
+
React.createElement('div', { className: 'dsub-sub' },
|
|
894
|
+
t('autoLoopbackHint')),
|
|
895
|
+
React.createElement('label', { className: 'dsub-row' },
|
|
896
|
+
React.createElement('input', {
|
|
897
|
+
type: 'checkbox',
|
|
898
|
+
checked: !!draft.hideDeprecatedModels,
|
|
899
|
+
onChange: (e) => setDraft((d) => Object.assign({}, d, { hideDeprecatedModels: e.target.checked })),
|
|
900
|
+
}),
|
|
901
|
+
React.createElement('span', null, t('hideDeprecated')),
|
|
902
|
+
),
|
|
903
|
+
React.createElement('div', { className: 'dsub-sub' },
|
|
904
|
+
t('hideDeprecatedHint')),
|
|
905
|
+
React.createElement('div', { className: 'dsub-row' },
|
|
906
|
+
React.createElement('span', { className: 'dsub-h' }, t('composerQuota')),
|
|
907
|
+
React.createElement('select', {
|
|
908
|
+
className: 'dsub-select',
|
|
909
|
+
value: draft.composerQuota || 'percent',
|
|
910
|
+
onChange: (e) => setDraft((d) => Object.assign({}, d, { composerQuota: e.target.value })),
|
|
911
|
+
},
|
|
912
|
+
React.createElement('option', { value: 'off' }, t('composerQuotaOff')),
|
|
913
|
+
React.createElement('option', { value: 'percent' }, t('composerQuotaPercent')),
|
|
914
|
+
React.createElement('option', { value: 'bar' }, t('composerQuotaBar')),
|
|
915
|
+
React.createElement('option', { value: 'forecast' }, t('composerQuotaForecast')),
|
|
916
|
+
),
|
|
917
|
+
),
|
|
918
|
+
React.createElement('div', { className: 'dsub-sub' },
|
|
919
|
+
t('composerQuotaHint')),
|
|
920
|
+
React.createElement('div', { className: 'dsub-row' },
|
|
921
|
+
React.createElement('span', { className: 'dsub-h' }, t('expiryNotifyDays')),
|
|
922
|
+
React.createElement('input', {
|
|
923
|
+
type: 'number',
|
|
924
|
+
className: 'dsub-inp',
|
|
925
|
+
style: { width: '80px', minWidth: '80px' },
|
|
926
|
+
min: 0,
|
|
927
|
+
max: 90,
|
|
928
|
+
value: draft.expiryNotifyDays != null ? draft.expiryNotifyDays : 7,
|
|
929
|
+
onChange: (e) => setDraft((d) => Object.assign({}, d, { expiryNotifyDays: Number(e.target.value) || 0 })),
|
|
930
|
+
}),
|
|
931
|
+
),
|
|
932
|
+
React.createElement('div', { className: 'dsub-sub' },
|
|
933
|
+
t('expiryNotifyDaysHint')),
|
|
934
|
+
React.createElement('label', { className: 'dsub-row' },
|
|
935
|
+
React.createElement('input', {
|
|
936
|
+
type: 'checkbox',
|
|
937
|
+
checked: !!draft.codexFastMode,
|
|
938
|
+
onChange: (e) => setDraft((d) => Object.assign({}, d, { codexFastMode: e.target.checked })),
|
|
939
|
+
}),
|
|
940
|
+
React.createElement('span', null, t('codexFastMode')),
|
|
941
|
+
),
|
|
942
|
+
React.createElement('div', { className: 'dsub-sub' },
|
|
943
|
+
t('codexFastModeHint')),
|
|
944
|
+
React.createElement('div', { className: 'dsub-row' },
|
|
945
|
+
React.createElement('span', { className: 'dsub-h' }, t('codexVerbosity')),
|
|
946
|
+
React.createElement('select', {
|
|
947
|
+
className: 'dsub-select',
|
|
948
|
+
value: draft.codexVerbosity || 'default',
|
|
949
|
+
onChange: (e) => setDraft((d) => Object.assign({}, d, { codexVerbosity: e.target.value })),
|
|
950
|
+
},
|
|
951
|
+
React.createElement('option', { value: 'default' }, t('codexVerbosityDefault')),
|
|
952
|
+
React.createElement('option', { value: 'low' }, t('codexVerbosityLow')),
|
|
953
|
+
React.createElement('option', { value: 'medium' }, t('codexVerbosityMedium')),
|
|
954
|
+
React.createElement('option', { value: 'high' }, t('codexVerbosityHigh')),
|
|
955
|
+
),
|
|
956
|
+
),
|
|
957
|
+
React.createElement('div', { className: 'dsub-sub' },
|
|
958
|
+
t('codexVerbosityHint')),
|
|
959
|
+
React.createElement('label', { className: 'dsub-row' },
|
|
960
|
+
React.createElement('input', {
|
|
961
|
+
type: 'checkbox',
|
|
962
|
+
checked: !!draft.ollamaFallback,
|
|
963
|
+
onChange: (e) => setDraft((d) => Object.assign({}, d, { ollamaFallback: e.target.checked })),
|
|
964
|
+
}),
|
|
965
|
+
React.createElement('span', null, t('ollamaFallback')),
|
|
966
|
+
),
|
|
967
|
+
React.createElement('div', { className: 'dsub-sub' },
|
|
968
|
+
t('ollamaFallbackHint')),
|
|
969
|
+
draft.ollamaFallback ? React.createElement('div', { className: 'dsub-row' },
|
|
970
|
+
React.createElement('input', {
|
|
971
|
+
type: 'text',
|
|
972
|
+
className: 'dsub-inp dsub-grow',
|
|
973
|
+
placeholder: 'http://localhost:11434',
|
|
974
|
+
value: draft.ollamaBaseUrl || '',
|
|
975
|
+
onChange: (e) => setDraft((d) => Object.assign({}, d, { ollamaBaseUrl: e.target.value })),
|
|
976
|
+
}),
|
|
977
|
+
React.createElement('input', {
|
|
978
|
+
type: 'text',
|
|
979
|
+
className: 'dsub-inp dsub-grow',
|
|
980
|
+
placeholder: 'llama3:latest',
|
|
981
|
+
value: draft.ollamaFallbackModel || '',
|
|
982
|
+
onChange: (e) => setDraft((d) => Object.assign({}, d, { ollamaFallbackModel: e.target.value })),
|
|
983
|
+
}),
|
|
984
|
+
) : null,
|
|
985
|
+
React.createElement('details', { className: 'dsub-details' },
|
|
986
|
+
React.createElement('summary', { className: 'dsub-summary' }, t('advancedSettings')),
|
|
987
|
+
React.createElement('div', { className: 'dsub-block' },
|
|
988
|
+
React.createElement('div', { className: 'dsub-row' },
|
|
989
|
+
React.createElement('span', { className: 'dsub-h' }, t('cooldownMs')),
|
|
990
|
+
React.createElement('input', {
|
|
991
|
+
type: 'number',
|
|
992
|
+
className: 'dsub-inp',
|
|
993
|
+
style: { width: '80px', minWidth: '80px' },
|
|
994
|
+
min: 1,
|
|
995
|
+
max: 120,
|
|
996
|
+
value: Math.round((draft.cooldownMs || 600000) / 60000),
|
|
997
|
+
onChange: (e) => setDraft((d) => Object.assign({}, d, { cooldownMs: (Number(e.target.value) || 10) * 60000 })),
|
|
998
|
+
}),
|
|
999
|
+
),
|
|
1000
|
+
React.createElement('div', { className: 'dsub-sub' }, t('cooldownMsHint')),
|
|
1001
|
+
React.createElement('div', { className: 'dsub-row' },
|
|
1002
|
+
React.createElement('span', { className: 'dsub-h' }, t('probeIntervalMin')),
|
|
1003
|
+
React.createElement('input', {
|
|
1004
|
+
type: 'number',
|
|
1005
|
+
className: 'dsub-inp',
|
|
1006
|
+
style: { width: '80px', minWidth: '80px' },
|
|
1007
|
+
min: 1,
|
|
1008
|
+
max: 1440,
|
|
1009
|
+
value: draft.probeIntervalMin != null ? draft.probeIntervalMin : 15,
|
|
1010
|
+
onChange: (e) => setDraft((d) => Object.assign({}, d, { probeIntervalMin: Number(e.target.value) || 15 })),
|
|
1011
|
+
}),
|
|
1012
|
+
),
|
|
1013
|
+
React.createElement('div', { className: 'dsub-sub' }, t('probeIntervalMinHint')),
|
|
1014
|
+
React.createElement('label', { className: 'dsub-row' },
|
|
1015
|
+
React.createElement('input', {
|
|
1016
|
+
type: 'checkbox',
|
|
1017
|
+
checked: draft.notifyLimits !== false,
|
|
1018
|
+
onChange: (e) => setDraft((d) => Object.assign({}, d, { notifyLimits: e.target.checked })),
|
|
1019
|
+
}),
|
|
1020
|
+
React.createElement('span', null, t('notifyLimits')),
|
|
1021
|
+
),
|
|
1022
|
+
React.createElement('div', { className: 'dsub-sub' }, t('notifyLimitsHint')),
|
|
1023
|
+
),
|
|
1024
|
+
),
|
|
974
1025
|
),
|
|
975
1026
|
React.createElement('div', { className: 'dsub-block' },
|
|
976
1027
|
React.createElement('div', { className: 'dsub-row' },
|
|
@@ -1331,8 +1382,8 @@ const cssId = 'dsh-subscriptions/settings.module.css'
|
|
|
1331
1382
|
)
|
|
1332
1383
|
}
|
|
1333
1384
|
|
|
1334
|
-
//
|
|
1335
|
-
//
|
|
1385
|
+
// The card in the Plugins tab follows the bash/agent-loop/web-search cards:
|
|
1386
|
+
// titled header with description and chevron, collapsible, bordered.
|
|
1336
1387
|
function PluginCard(props) {
|
|
1337
1388
|
const t = (props && props.t) || ((key) => key)
|
|
1338
1389
|
const [open, setOpen] = React.useState(false)
|
|
@@ -1358,14 +1409,14 @@ const cssId = 'dsh-subscriptions/settings.module.css'
|
|
|
1358
1409
|
}
|
|
1359
1410
|
|
|
1360
1411
|
function registerSettings(ctx) {
|
|
1361
|
-
//
|
|
1362
|
-
//
|
|
1363
|
-
//
|
|
1364
|
-
//
|
|
1365
|
-
// load plugins
|
|
1412
|
+
// Languages can come from more than this plugin: dictionary packages declare
|
|
1413
|
+
// Russian for foreign namespaces. The core throws on re-declaring the same
|
|
1414
|
+
// namespace+language pair, and an unguarded call used to take down the
|
|
1415
|
+
// whole plugin - in the UI it surfaced as Failed to
|
|
1416
|
+
// load plugins listing perfectly innocent neighbours.
|
|
1366
1417
|
//
|
|
1367
|
-
//
|
|
1368
|
-
//
|
|
1418
|
+
// So each language is declared separately and politely: if someone got
|
|
1419
|
+
// there first we yield, while our own English still lands.
|
|
1369
1420
|
const addLocale = (locale, dictionary) => {
|
|
1370
1421
|
try {
|
|
1371
1422
|
return ctx.locale.register(NS, locale, dictionary)
|
|
@@ -1374,17 +1425,16 @@ const cssId = 'dsh-subscriptions/settings.module.css'
|
|
|
1374
1425
|
}
|
|
1375
1426
|
}
|
|
1376
1427
|
ctx.effect(() => {
|
|
1377
|
-
const undo = [addLocale('en', en)
|
|
1428
|
+
const undo = [addLocale('en', en)]
|
|
1378
1429
|
return () => { for (const off of undo) off() }
|
|
1379
1430
|
}, 'dsh-subscriptions: словари')
|
|
1380
|
-
|
|
1431
|
+
// Labels outside a component take the translator bound to the namespace.
|
|
1381
1432
|
setT(ctx.locale.bind(NS))
|
|
1382
|
-
//
|
|
1383
|
-
//
|
|
1384
|
-
//
|
|
1385
|
-
let moved = false
|
|
1433
|
+
// The canonical place is the Plugins tab: a card with its own header
|
|
1434
|
+
// and collapse instead of a row in the side list. The registration key must
|
|
1435
|
+
// equal the settings namespace, otherwise the tab silently hides the slot.
|
|
1386
1436
|
try {
|
|
1387
|
-
|
|
1437
|
+
ctx.slots.inject('settings.plugin.item', () => ctx.slots.register(
|
|
1388
1438
|
{
|
|
1389
1439
|
name: 'settings.plugin.item',
|
|
1390
1440
|
key: NS,
|
|
@@ -1393,32 +1443,13 @@ const cssId = 'dsh-subscriptions/settings.module.css'
|
|
|
1393
1443
|
},
|
|
1394
1444
|
PluginCard,
|
|
1395
1445
|
))
|
|
1396
|
-
} catch { moved = false }
|
|
1397
|
-
if (moved) return
|
|
1398
|
-
// #244: Fallback for harness cores before settings.plugin.item slot was
|
|
1399
|
-
// introduced (< 0.1.2-rc.1). Once all production profiles run a core
|
|
1400
|
-
// that has settings.plugin.item, this block can be safely removed.
|
|
1401
|
-
// Tracked in Gitea #244 — do not remove without verifying minimum core.
|
|
1402
|
-
try {
|
|
1403
|
-
ctx.slots.inject('settings.section', () => ctx.slots.register(
|
|
1404
|
-
{
|
|
1405
|
-
name: 'settings.section',
|
|
1406
|
-
id: '@goodandready/dsh-subscriptions',
|
|
1407
|
-
order: 28,
|
|
1408
|
-
locale: NS,
|
|
1409
|
-
label: () => t('title'),
|
|
1410
|
-
inject: () => ({ ctx: ctx }),
|
|
1411
|
-
},
|
|
1412
|
-
SubsSection,
|
|
1413
|
-
))
|
|
1414
1446
|
} catch (e) {
|
|
1415
|
-
//
|
|
1416
|
-
// This is expected on headless / non-web profiles.
|
|
1447
|
+
// Slot system not available — expected on headless / non-web profiles.
|
|
1417
1448
|
}
|
|
1418
1449
|
}
|
|
1419
1450
|
|
|
1420
|
-
//
|
|
1421
|
-
//
|
|
1451
|
+
// Hot provider switcher in the composer row: a click cycles
|
|
1452
|
+
// the active subscription provider for the next request.
|
|
1422
1453
|
let activeIdx = 0
|
|
1423
1454
|
let labelEl = null
|
|
1424
1455
|
const ORDER = ['codex', 'claude', 'grok', 'antigravity']
|
|
@@ -1443,8 +1474,8 @@ const cssId = 'dsh-subscriptions/settings.module.css'
|
|
|
1443
1474
|
} catch { return { logged: [], usage: 0, expiresAt: {}, labels: {}, expiryNotifyDays: 7, fastMode: false, composerQuota: 'off', active: null } }
|
|
1444
1475
|
}
|
|
1445
1476
|
|
|
1446
|
-
// #84:
|
|
1447
|
-
//
|
|
1477
|
+
// #84: compact quota indicator of the active subscription in the input area.
|
|
1478
|
+
// The runway forecast uses a rolling window of /status samples.
|
|
1448
1479
|
const fcSamples = { windows: {} }
|
|
1449
1480
|
|
|
1450
1481
|
function fcObserve(key, remaining, now) {
|
|
@@ -1528,8 +1559,8 @@ const cssId = 'dsh-subscriptions/settings.module.css'
|
|
|
1528
1559
|
return React.createElement('span', { className: 'dsub-cq', title: t('forecast') + ' · ' + state.active.provider }, value)
|
|
1529
1560
|
}
|
|
1530
1561
|
|
|
1531
|
-
// #83:
|
|
1532
|
-
//
|
|
1562
|
+
// #83: SUBS(N) pill in the session header with a pool health LED and
|
|
1563
|
+
// a modal accounts console (close: X button, Escape, outside click).
|
|
1533
1564
|
function brandBadge(prov) {
|
|
1534
1565
|
const p = String(prov || '').toLowerCase()
|
|
1535
1566
|
if (p.includes('codex') || p.includes('chatgpt') || p.includes('openai')) return { label: 'OpenAI', cls: 'dsub-brandCodex', icon: '⚡' }
|
|
@@ -1617,7 +1648,7 @@ const cssId = 'dsh-subscriptions/settings.module.css'
|
|
|
1617
1648
|
React.createElement('div', { className: 'dsub-heroTitle' },
|
|
1618
1649
|
React.createElement('span', { className: 'dsub-brandBadge ' + b.cls, style: { width: 24, height: 24, fontSize: 11 } }, b.icon),
|
|
1619
1650
|
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: '
|
|
1651
|
+
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
1652
|
),
|
|
1622
1653
|
React.createElement('span', { className: 'dsub-heroModel' }, a.model || 'active model'),
|
|
1623
1654
|
),
|
|
@@ -1681,7 +1712,7 @@ const cssId = 'dsh-subscriptions/settings.module.css'
|
|
|
1681
1712
|
React.createElement('div', { className: 'dsub-accNameRow' },
|
|
1682
1713
|
React.createElement('div', { style: { display: 'flex', alignItems: 'center', gap: 6 } },
|
|
1683
1714
|
React.createElement('span', { className: 'dsub-accName' }, a.provider.toUpperCase() + (a.index ? ' #' + a.index : '')),
|
|
1684
|
-
isActiveThis ? React.createElement('span', { className: 'dsub-pillTag', style: { background: '
|
|
1715
|
+
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
1716
|
a.proxy ? React.createElement('span', { className: 'dsub-dim', title: a.proxy }, '🌐 proxy') : null,
|
|
1686
1717
|
),
|
|
1687
1718
|
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() {
|