@goodandready/dsh-subscriptions 0.5.32 → 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 CHANGED
@@ -68,7 +68,7 @@ graph LR
68
68
 
69
69
  ## ✨ Key Features & Capabilities
70
70
 
71
- ### 1. 🌐 4 Supported Built-in Subscription Vendors
71
+ ### 1. 🌐 16 Supported Built-in Subscription Vendors
72
72
 
73
73
  | Vendor Key | Subscription Tier | Protocol & Features |
74
74
  |---|---|---|
@@ -76,6 +76,18 @@ graph LR
76
76
  | `claude` | Claude Pro / Max | Native Claude Messages protocol, usage tracking (`/v1/messages`, `/api/oauth/...`) |
77
77
  | `grok` | xAI / X Premium | Real-time reasoning responses, billing checks & social search |
78
78
  | `antigravity` | Google Cloud Code Assist | Antigravity engine (`/v1/loadCodeAssist`, `/v1/streamGenerateContent`) |
79
+ | `kimi` | Moonshot Kimi | OAuth Device Flow login (`auth.kimi.com`), OpenAI-compatible chat |
80
+ | `glm` | Z.ai GLM Coding Plan | GLM coding endpoint (`api.z.ai`), live quota monitor (`/api/monitor/usage/quota/limit`) |
81
+ | `cursor` | Cursor | Cursor backend (`api2.cursor.sh`), billing-period usage dashboard parsing |
82
+ | `kiro` | AWS Kiro | Kiro desktop OAuth (`app.kiro.dev`), streaming code assistance |
83
+ | `copilot` | GitHub Copilot | GitHub Device Flow login (`github.com/login/device`), Copilot chat completions |
84
+ | `qwen` | Alibaba Qwen (DashScope) | OpenAI-compatible endpoint (`dashscope.aliyuncs.com/compatible-mode/v1`), API key auth |
85
+ | `ernie` | Baidu ERNIE (Qianfan) | OAuth2 token refresh via API Key + Secret Key, Wenxinworkshop chat |
86
+ | `spark` | iFlytek Spark | OpenAI-compatible Spark HTTP API (`spark-api-open.xf-yun.com/v1`) |
87
+ | `jetbrains` | JetBrains AI Assistant | JetBrains AI relay (`api.jetbrains.ai`) |
88
+ | `perplexity` | Perplexity Pro | Sonar model catalog (`api.perplexity.ai`) |
89
+ | `replit` | Replit Core | Replit AI API (`replit.com/api/v1/ai`), connect-token auth |
90
+ | `cody` | Sourcegraph Cody Pro | Sourcegraph API (`sourcegraph.com/.api`), access-token auth |
79
91
 
80
92
  *Custom vendors can also be dynamically registered via the `createVendorFromProfile` factory.*
81
93
 
@@ -219,6 +231,12 @@ dsh-subscriptions:
219
231
 
220
232
  ---
221
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
+
222
240
  ## 📄 License
223
241
 
224
242
  MIT © [GooDAnDReaDY](https://github.com/GooDAnDReaDY)
package/README.ru.md CHANGED
@@ -68,7 +68,7 @@ graph LR
68
68
 
69
69
  ## ✨ Ключевые возможности
70
70
 
71
- ### 1. 🌐 4 Поддерживаемых встроенных вендора подписок
71
+ ### 1. 🌐 16 Поддерживаемых встроенных вендоров подписок
72
72
 
73
73
  | Ключ вендора | Тариф подписки | Протокол и возможности |
74
74
  |---|---|---|
@@ -76,6 +76,18 @@ graph LR
76
76
  | `claude` | Claude Pro / Max | Нативный протокол Claude Messages, трекинг расхода (`/v1/messages`, `/api/oauth/...`) |
77
77
  | `grok` | xAI / X Premium | Ответы с рассуждениями, проверка баланса и поиск в соцсети |
78
78
  | `antigravity` | Google Cloud Code Assist | Движок Antigravity (`/v1/loadCodeAssist`, `/v1/streamGenerateContent`) |
79
+ | `kimi` | Moonshot Kimi | Вход по OAuth Device Flow (`auth.kimi.com`), OpenAI-совместимый чат |
80
+ | `glm` | Z.ai GLM Coding Plan | Кодинг-эндпоинт GLM (`api.z.ai`), живой монитор квоты (`/api/monitor/usage/quota/limit`) |
81
+ | `cursor` | Cursor | Бэкенд Cursor (`api2.cursor.sh`), разбор дашборда расхода за период |
82
+ | `kiro` | AWS Kiro | Десктопный OAuth Kiro (`app.kiro.dev`), стриминг кода |
83
+ | `copilot` | GitHub Copilot | Вход по GitHub Device Flow (`github.com/login/device`), chat completions Copilot |
84
+ | `qwen` | Alibaba Qwen (DashScope) | OpenAI-совместимый эндпоинт (`dashscope.aliyuncs.com/compatible-mode/v1`), вход по API-ключу |
85
+ | `ernie` | Baidu ERNIE (Qianfan) | Продление OAuth2-токена по связке API Key + Secret Key, чат Wenxinworkshop |
86
+ | `spark` | iFlytek Spark | OpenAI-совместимый HTTP API Spark (`spark-api-open.xf-yun.com/v1`) |
87
+ | `jetbrains` | JetBrains AI Assistant | Релей JetBrains AI (`api.jetbrains.ai`) |
88
+ | `perplexity` | Perplexity Pro | Каталог моделей Sonar (`api.perplexity.ai`) |
89
+ | `replit` | Replit Core | Replit AI API (`replit.com/api/v1/ai`), вход по connect-токену |
90
+ | `cody` | Sourcegraph Cody Pro | Sourcegraph API (`sourcegraph.com/.api`), вход по access-токену |
79
91
 
80
92
  *Также поддерживается регистрация кастомных вендоров через фабрику профилей `createVendorFromProfile`.*
81
93
 
@@ -192,6 +204,12 @@ dsh-subscriptions:
192
204
 
193
205
  ---
194
206
 
207
+ ## 🌐 Локализация
208
+
209
+ Исходный язык плагина - только английский. Русский и другие переводы во время работы предоставляют отдельные языковые плагины (например, плагин русификации), переводящие зарегистрированные ключи локали, - сам пакет не содержит встроенных переводов (Changed in 0.6.1).
210
+
211
+ ---
212
+
195
213
  ## 📄 Лицензия
196
214
 
197
215
  MIT © [GooDAnDReaDY](https://github.com/GooDAnDReaDY)
package/README.zh.md CHANGED
@@ -68,13 +68,25 @@ graph LR
68
68
 
69
69
  ## ✨ 核心功能
70
70
 
71
- ### 支持的内置订阅供应商
71
+ ### 支持的 16 个内置订阅供应商
72
72
  | 供应商 | 订阅级别 | 协议 |
73
73
  |---|---|---|
74
74
  | `codex` | ChatGPT Plus / Pro | 流式响应、工具调用、图像生成 |
75
75
  | `claude` | Claude Pro / Max | 原生 Messages 协议、用量跟踪 |
76
76
  | `grok` | xAI / X Premium | 实时推理、计费检查 |
77
77
  | `antigravity` | Google Cloud Code Assist | 流式生成 |
78
+ | `kimi` | Moonshot Kimi | OAuth Device Flow 登录(`auth.kimi.com`)、OpenAI 兼容聊天 |
79
+ | `glm` | Z.ai GLM Coding Plan | GLM 编码端点(`api.z.ai`)、实时配额监控 |
80
+ | `cursor` | Cursor | Cursor 后端(`api2.cursor.sh`)、用量面板解析 |
81
+ | `kiro` | AWS Kiro | Kiro 桌面 OAuth(`app.kiro.dev`)、流式输出 |
82
+ | `copilot` | GitHub Copilot | GitHub Device Flow 登录、Copilot 聊天补全 |
83
+ | `qwen` | 阿里 Qwen(DashScope) | OpenAI 兼容端点、API Key 认证 |
84
+ | `ernie` | 百度 ERNIE(千帆) | OAuth2 令牌刷新(API Key + Secret Key) |
85
+ | `spark` | 讯飞星火 | OpenAI 兼容 HTTP API |
86
+ | `jetbrains` | JetBrains AI Assistant | JetBrains AI 中继(`api.jetbrains.ai`) |
87
+ | `perplexity` | Perplexity Pro | Sonar 模型目录(`api.perplexity.ai`) |
88
+ | `replit` | Replit Core | Replit AI API、connect-token 认证 |
89
+ | `cody` | Sourcegraph Cody Pro | Sourcegraph API、access-token 认证 |
78
90
 
79
91
  ### 多账号轮换与限流保护
80
92
  * 每个供应商可挂多个账号(如 `CODEX_OAUTH_1`、`CODEX_OAUTH_2`)。
@@ -108,6 +120,14 @@ graph LR
108
120
  * **匿名诊断报告**:设置卡片内一键生成(插件/运行时版本、系统、各供应商健康状态、HTTP 状态聚合、最近错误与耗时、非敏感配置)并自动复制到剪贴板;令牌、邮箱、凭据名与代理地址严格排除。
109
121
  * 同区块提供问题追踪器链接,便于提交 issue。
110
122
 
123
+ ### HTTP API 路由(`v0.4.9`)
124
+ | 路由 | 方法 | 用途 |
125
+ |---|---|---|
126
+ | `/dsh-subscriptions/diagnostics` | GET | 匿名诊断报告(无密钥、无令牌、无代理地址) |
127
+ | `/dsh-subscriptions/proxy-check` | POST | 检测槽位代理到供应商基础地址的延迟 |
128
+ | `/dsh-subscriptions/oauth/device/start` | POST | 发起 Codex 设备码登录(返回用户码与验证地址) |
129
+ | `/dsh-subscriptions/oauth/device/poll` | POST | 轮询设备码授权状态 |
130
+
111
131
  ---
112
132
 
113
133
  ## 📦 安装指南
@@ -118,6 +138,12 @@ dsh plugin --profile web add @goodandready/dsh-subscriptions
118
138
 
119
139
  ---
120
140
 
141
+ ## 🌐 本地化
142
+
143
+ 插件源语言仅为英语。俄语及其他翻译由独立的语言插件(如俄化插件)在运行时对注册的本地化键进行翻译,包本身不内置翻译(Changed in 0.6.1)。
144
+
145
+ ---
146
+
121
147
  ## 📄 开源协议
122
148
 
123
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 rgba(16,185,129,.7)}' +
91
- '.dsub-ledWarn{background:var(--dsw-alias-state-warning-primary,#f59e0b);box-shadow:0 0 6px rgba(245,158,11,.7)}' +
92
- '.dsub-ledBad{background:var(--dsw-alias-state-error-primary,#ef4444);box-shadow:0 0 6px rgba(239,68,68,.7)}' +
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:rgba(245,158,11,0.4);background:rgba(245,158,11,0.05)}' +
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:rgba(16,185,129,0.3);color:var(--dsw-alias-state-success-primary);background:rgba(16,185,129,0.08);padding:1px 7px;border-radius:999px;font-size:11px;font-weight:600;border-style:solid;border-width:1px}' +
121
- '.dsub-tag-warn{border-color:rgba(245,158,11,0.4);color:var(--dsw-alias-state-warning-primary);background:rgba(245,158,11,0.08);padding:1px 7px;border-radius:999px;font-size:11px;font-weight:600;border-style:solid;border-width:1px}' +
122
- '.dsub-tag-bad{border-color:rgba(239,68,68,0.4);color:var(--dsw-alias-state-error-primary);background:rgba(239,68,68,0.08);padding:1px 7px;border-radius:999px;font-size:11px;font-weight:600;border-style:solid;border-width:1px}' +
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:rgba(239,68,68,0.4)!important;color:var(--dsw-alias-state-error-primary)!important}' +
127
- '.dsub-btn-danger:hover:not(:disabled){background:rgba(239,68,68,0.12)!important}' +
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:rgba(16,185,129,.15);color:#34d399}' +
146
- '.dsub-statusWarn{background:rgba(245,158,11,.15);color:#fbbf24}' +
147
- '.dsub-statusBad{background:rgba(239,68,68,.15);color:#f87171}' +
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}' +
@@ -165,12 +165,13 @@ const cssId = 'dsh-subscriptions/settings.module.css'
165
165
  tag.textContent = CSS
166
166
  tag.setAttribute('data-plugin', 'dsh-subscriptions')
167
167
  tag.dataset.pluginCss = cssId
168
+ tag.setAttribute('data-dsh-plugin', 'dsh-subscriptions')
168
169
  document.head.appendChild(tag)
169
170
  }
170
171
 
171
- // Строки карточки живут в реестре локалей: так их переводит отдельный
172
- // пакет, не трогая код этого плагина. Английский язык по умолчанию,
173
- // на него же приходится откат, если перевода нет.
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.
174
175
  const NS = 'dsh-subscriptions'
175
176
 
176
177
  const INSTRUCTIONS = {
@@ -420,130 +421,6 @@ const cssId = 'dsh-subscriptions/settings.module.css'
420
421
  'windowPrimary': 'Primary window',
421
422
  'windowSecondary': 'Secondary window',
422
423
  }
423
- const ru = {
424
- 'check': 'Проверить',
425
- 'checking': 'Проверяю…',
426
- 'importToken': 'Сохранить',
427
- 'importTokenPlace': 'Вставить токен или API-ключ',
428
- 'importLocalCli': '📥 Из CLI',
429
- 'importLocalCliTitle': 'Автоматически считать токен из установленного на сервере CLI',
430
- 'importLocalSuccess': 'Токен успешно загружен из локального CLI!',
431
- 'howToGetToken': '❓ Инструкция',
432
- 'howToGetTokenTitle': 'Как получить токен или API-ключ для этого провайдера',
433
- 'instructionsTitle': 'Инструкция по получению токена',
434
- 'close': 'Закрыть',
435
- 'reconnectRequired': 'Нужно переподключить',
436
- 'manualTitle': 'Если браузер не вернулся сам',
437
- 'windowPrimary': 'Основное окно',
438
- 'windowSecondary': 'Дополнительное окно',
439
- 'notConnected': 'Не подключено',
440
- 'verifyAccount': 'Требуется проверка',
441
- 'coolingDown': 'Пауза после лимита',
442
- 'usageFull': 'Лимит исчерпан',
443
- 'connected': 'Подключено',
444
- 'title': 'Подписки',
445
- 'cardIntro': 'Аккаунты подписок, ротация и лимиты.',
446
- 'show': 'Показать',
447
- 'hide': 'Скрыть',
448
- 'intro': 'Вход по обычной пользовательской подписке. Токены остаются в хранилище учётных данных харнесса, браузер их обратно не читает. Если после «Подключить» провайдер увёл на localhost или на свою страницу, вставьте сюда адрес перехода или код.',
449
- 'useOrigin': 'Использовать адрес этого веб-интерфейса как redirect_uri',
450
- 'useOriginHint': 'Включайте, только если зарегистрировали собственного клиента OAuth на этот адрес. Штатным консольным клиентам провайдеров нужен их опубликованный адрес возврата и вставка кода вручную.',
451
- 'privacyMask': 'Маскировать email и учётные записи',
452
- 'privacyMaskHint': 'Для демонстрации экрана: email отображается как j***n@example.com.',
453
- 'resetCredits': 'Карты сброса',
454
- 'resetCreditsHint': 'Карты сброса квоты ChatGPT: показывает, сколько доступно. Списание одной карты — осознанное действие с 5-секундным подтверждением.',
455
- 'resetAvailable': 'Доступно попыток сброса',
456
- 'resetExpires': 'ближайшая истекает',
457
- 'resetAck': 'Я понимаю, что будет списана 1 попытка',
458
- 'resetWait': 'кнопка активна через',
459
- 'resetReady': 'готово',
460
- 'resetGo': 'Сбросить',
461
- 'resetBusy': 'сбрасываю…',
462
- 'resetDone': 'квота сброшена',
463
- 'resetNothing': 'сервер сообщает: сброс не нужен (попытка не списана)',
464
- 'resetNoCredit': 'нет пригодной карты (не списано)',
465
- 'resetRedeemed': 'эта карта уже использована ранее',
466
- 'diagGenerate': 'Сгенерировать диагностический отчёт',
467
- 'diagCopy': 'Скопировать отчёт',
468
- 'diagIssues': 'Открыть трекер задач',
469
- 'diagHint': 'В отчёте нет токенов, email, адресов прокси и персональных данных.',
470
- 'subsPill': 'SUBS',
471
- 'subsModalTitle': 'Консоль подписок',
472
- 'subsActiveHero': 'Активная подписка',
473
- 'subsPoolTitle': 'Пул аккаунтов',
474
- 'subsRefresh': 'Обновить',
475
- 'subsOpenSettings': 'Все настройки →',
476
- 'subsFastMode': 'Режим Fast 1.5x',
477
- 'subsHealthy': 'Исправен',
478
- 'subsFree': 'свободно',
479
- 'subsUsed': 'израсходовано',
480
- 'subsModalClose': 'Закрыть',
481
- 'subsLogged': 'подключен',
482
- 'subsNotLogged': 'не подключен',
483
- 'subsCooldown': 'кулдаун',
484
- 'subsOpenSettings': 'Открыть настройки',
485
- 'hudTitle': 'Остаток подписки',
486
- 'settingsLoading': 'Загрузка настроек…',
487
- 'settingsUnavailable': 'Настройки недоступны',
488
- 'settingsRetry': 'Повторить',
489
- 'hudHint': 'перетащи · прилипает к краям · клик — обновить',
490
- 'fcCalibrating': 'калибровка…',
491
- 'fcIdle': 'нет расхода',
492
- 'fcHours': 'ч',
493
- 'fcMinutes': 'мин',
494
- 'reconnect': 'Переподключить',
495
- 'connect': 'Подключить',
496
- 'disconnect': 'Отключить',
497
- 'removeSlot': 'Убрать место',
498
- 'pastePlaceholder': 'Адрес перехода или код',
499
- 'submitCode': 'Отправить код',
500
- 'proxyPlaceholder': 'Прокси аккаунта (http://, https://, socks5://) - необязательно',
501
- 'proxyCheck': 'Проверить прокси',
502
- 'proxyOk': 'прокси ок',
503
- 'proxyFail': 'прокси недоступен',
504
- 'deviceLogin': 'Вход по коду',
505
- 'deviceHint': 'Headless: откройте ссылку, введите код, держите вкладку открытой.',
506
- 'deviceCopy': 'Скопировать код',
507
- 'devicePending': 'Ожидание подтверждения',
508
- 'deviceAuthorized': 'Вход выполнен',
509
- 'deviceExpired': 'Истёк - начните заново',
510
- 'verifyPrefix': 'Google требует однократной проверки аккаунта. ',
511
- 'verifyLink': 'Открыть страницу проверки',
512
- 'verifySuffix': ' затем подключитесь заново.',
513
- 'addAccount': '+ Добавить аккаунт',
514
- 'save': 'Сохранить',
515
- 'saved': 'Сохранено',
516
- 'loading': 'Загрузка…',
517
- 'accountLabel': 'Аккаунт',
518
- 'plan': 'Тариф',
519
- 'storedAs': 'Хранится как',
520
- 'switchLabel': 'Провайдер',
521
- 'chipActive': 'Подписки',
522
- 'expiryLabel': 'окончание',
523
- 'slashLogin': 'Вход в провайдера подписки',
524
- 'slashLogout': 'Выйти из провайдера подписки',
525
- 'slashStatus': 'Статус подписок',
526
- 'none': 'нет',
527
- 'forecast': '≈',
528
- 'resetCredits': 'Сброс лимитов ChatGPT',
529
- 'resetCreditsHint': 'Экстренный сброс 5-часового окна ChatGPT Plus/Pro. Требует 5 секунд подтверждения перед списанием.',
530
- 'resetCreditsAction': 'Сбросить лимит ChatGPT',
531
- 'resetAvailable': 'Доступно сбросов',
532
- 'resetExpires': 'срок действия',
533
- 'resetAck': 'Я понимаю, что одна попытка сброса будет списана',
534
- 'resetWait': 'подтверждение через',
535
- 'resetReady': 'готово к сбросу',
536
- 'resetGo': 'Сбросить лимит сейчас',
537
- 'resetBusy': 'Сброс…',
538
- 'resetDone': 'Лимит успешно сброшен',
539
- 'resetNothing': 'Лимит не требует сброса',
540
- 'resetNoCredit': 'Нет доступных попыток сброса',
541
- 'resetRedeemed': 'Попытка уже использована',
542
- 'windowPrimary': 'Окно 5 часов',
543
- 'windowSecondary': 'Окно 7 дней',
544
- 'usageLimitsTitle': 'Лимиты использования',
545
- 'slotDetailsTitle': 'Параметры слота',
546
- }
547
424
 
548
425
  // #51: live countdown to the quota window reset (account.quota.resetAt).
549
426
  function cleanErrorMessage(raw) {
@@ -632,9 +509,9 @@ const cssId = 'dsh-subscriptions/settings.module.css'
632
509
  return t('connected')
633
510
  }
634
511
 
635
- // Карточка во вкладке «Плагины» рисует свой заголовок и сворачивание:
636
- // ядро даёт только рамку списка.
637
- // Ядровый значок раскрытия. Без защищённого require вместо IconChevronDownOutline14 может упасть вся клиентская половина.
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.
638
515
  let ChevronIcon = null
639
516
  try {
640
517
  const primitives = require('@deepseek-ai/dsh-client-ui-primitives')
@@ -652,7 +529,7 @@ const cssId = 'dsh-subscriptions/settings.module.css'
652
529
 
653
530
 
654
531
  function SubsSection(props) {
655
- // Переводчик приходит от слота, потому что в его записи указан locale.
532
+ // The translator comes from the slot because its registration declares locale.
656
533
  const t = (props && props.t) || ((key) => key)
657
534
  const [draft, setDraft] = React.useState(null)
658
535
  const [accounts, setAccounts] = React.useState([])
@@ -851,7 +728,7 @@ const cssId = 'dsh-subscriptions/settings.module.css'
851
728
  setPaste((p) => Object.assign({}, p, { [key]: '' }))
852
729
  }
853
730
 
854
- // #88: проверить прокси аккаунта реальным запросом с замером задержки.
731
+ // #88: probe the account proxy with a real request and measure latency.
855
732
  const doProxyCheck = async (provider, index) => {
856
733
  const key = provider + ':' + index
857
734
  setChecking((c) => Object.assign({}, c, { ['proxy:' + key]: true }))
@@ -1330,8 +1207,8 @@ const cssId = 'dsh-subscriptions/settings.module.css'
1330
1207
  )
1331
1208
  }
1332
1209
 
1333
- // Карточка во вкладке «Плагины» как карточки bash/agent-loop/web-search:
1334
- // заголовок с именем, описанием и шевроном, сворачивание, каёмка.
1210
+ // The card in the Plugins tab follows the bash/agent-loop/web-search cards:
1211
+ // titled header with description and chevron, collapsible, bordered.
1335
1212
  function PluginCard(props) {
1336
1213
  const t = (props && props.t) || ((key) => key)
1337
1214
  const [open, setOpen] = React.useState(false)
@@ -1357,14 +1234,14 @@ const cssId = 'dsh-subscriptions/settings.module.css'
1357
1234
  }
1358
1235
 
1359
1236
  function registerSettings(ctx) {
1360
- // Язык может принести не только плагин: словарные пакеты объявляют
1361
- // русский для чужих пространств. Ядро на повторное объявление той же
1362
- // пары «пространство + язык» бросает исключение, и незащищённый вызов
1363
- // уносил с собой весь плагин в интерфейсе это выглядело как «Failed to
1364
- // 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.
1365
1242
  //
1366
- // Поэтому каждый язык объявляется отдельно и по-хорошему: заняли до нас
1367
- // уступаем, свой английский при этом всё равно встаёт на место.
1243
+ // So each language is declared separately and politely: if someone got
1244
+ // there first we yield, while our own English still lands.
1368
1245
  const addLocale = (locale, dictionary) => {
1369
1246
  try {
1370
1247
  return ctx.locale.register(NS, locale, dictionary)
@@ -1373,14 +1250,14 @@ const cssId = 'dsh-subscriptions/settings.module.css'
1373
1250
  }
1374
1251
  }
1375
1252
  ctx.effect(() => {
1376
- const undo = [addLocale('en', en), addLocale('ru', ru)]
1253
+ const undo = [addLocale('en', en)]
1377
1254
  return () => { for (const off of undo) off() }
1378
1255
  }, 'dsh-subscriptions: словари')
1379
- // Подписи вне компонента берут переводчик, привязанный к namespace.
1256
+ // Labels outside a component take the translator bound to the namespace.
1380
1257
  setT(ctx.locale.bind(NS))
1381
- // Штатное место вкладка «Плагины»: карточка со своим заголовком и
1382
- // сворачиванием вместо строки в боковом списке. Ключ регистрации обязан
1383
- // равняться пространству настроек, иначе вкладка молча не покажет слот.
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.
1384
1261
  let moved = false
1385
1262
  try {
1386
1263
  moved = !!ctx.slots.inject('settings.plugin.item', () => ctx.slots.register(
@@ -1416,8 +1293,8 @@ const cssId = 'dsh-subscriptions/settings.module.css'
1416
1293
  }
1417
1294
  }
1418
1295
 
1419
- // Горячий переключатель провайдера в строке композера: клик циклично
1420
- // меняет активного провайдера подписки для следующего запроса.
1296
+ // Hot provider switcher in the composer row: a click cycles
1297
+ // the active subscription provider for the next request.
1421
1298
  let activeIdx = 0
1422
1299
  let labelEl = null
1423
1300
  const ORDER = ['codex', 'claude', 'grok', 'antigravity']
@@ -1442,8 +1319,8 @@ const cssId = 'dsh-subscriptions/settings.module.css'
1442
1319
  } catch { return { logged: [], usage: 0, expiresAt: {}, labels: {}, expiryNotifyDays: 7, fastMode: false, composerQuota: 'off', active: null } }
1443
1320
  }
1444
1321
 
1445
- // #84: компактный индикатор квоты активной подписки в области ввода.
1446
- // Прогноз (runway) считается по скользящему окну сэмплов /status.
1322
+ // #84: compact quota indicator of the active subscription in the input area.
1323
+ // The runway forecast uses a rolling window of /status samples.
1447
1324
  const fcSamples = { windows: {} }
1448
1325
 
1449
1326
  function fcObserve(key, remaining, now) {
@@ -1527,8 +1404,8 @@ const cssId = 'dsh-subscriptions/settings.module.css'
1527
1404
  return React.createElement('span', { className: 'dsub-cq', title: t('forecast') + ' · ' + state.active.provider }, value)
1528
1405
  }
1529
1406
 
1530
- // #83: пилл SUBS(N) в шапке сессии со светодиодом здоровья пула и
1531
- // модальной консолью аккаунтов (закрытие: крестик, Escape, клик мимо).
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).
1532
1409
  function brandBadge(prov) {
1533
1410
  const p = String(prov || '').toLowerCase()
1534
1411
  if (p.includes('codex') || p.includes('chatgpt') || p.includes('openai')) return { label: 'OpenAI', cls: 'dsub-brandCodex', icon: '⚡' }
@@ -1616,7 +1493,7 @@ const cssId = 'dsh-subscriptions/settings.module.css'
1616
1493
  React.createElement('div', { className: 'dsub-heroTitle' },
1617
1494
  React.createElement('span', { className: 'dsub-brandBadge ' + b.cls, style: { width: 24, height: 24, fontSize: 11 } }, b.icon),
1618
1495
  React.createElement('span', null, (a.provider ? a.provider.toUpperCase() : 'LLM') + (a.index ? ' #' + a.index : '')),
1619
- a.fastMode ? React.createElement('span', { className: 'dsub-pillTag', style: { background: 'rgba(245,158,11,.15)', color: '#fbbf24' } }, '⚡ FAST 1.5x') : null,
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,
1620
1497
  ),
1621
1498
  React.createElement('span', { className: 'dsub-heroModel' }, a.model || 'active model'),
1622
1499
  ),
@@ -1680,7 +1557,7 @@ const cssId = 'dsh-subscriptions/settings.module.css'
1680
1557
  React.createElement('div', { className: 'dsub-accNameRow' },
1681
1558
  React.createElement('div', { style: { display: 'flex', alignItems: 'center', gap: 6 } },
1682
1559
  React.createElement('span', { className: 'dsub-accName' }, a.provider.toUpperCase() + (a.index ? ' #' + a.index : '')),
1683
- isActiveThis ? React.createElement('span', { className: 'dsub-pillTag', style: { background: 'rgba(16,185,129,.15)', color: '#34d399' } }, 'ACTIVE') : null,
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,
1684
1561
  a.proxy ? React.createElement('span', { className: 'dsub-dim', title: a.proxy }, '🌐 proxy') : null,
1685
1562
  ),
1686
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 с ключом из passphrase (scrypt, без хранения соли отдельно
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
- // формат: magic + salt + iv + tag + data
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)
@@ -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 'У аккаунта Google нет активной лицензии Gemini Code Assist / Antigravity (#3501), либо не привязан проект Google Cloud. Проверьте подписку на https://one.google.com или подключите другой Google аккаунт.'
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
- // Health-счёт аккаунта для умной ротации.
2
- // Числа как в dsh-key-rotation pool.js: свитч 5, исчерпание 10, битый ключ 15.
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
- // Все IO best-effort и синхронные: сбой записи не должен ронять харнесс.
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
- /** Последние N записей (новые сверху). */
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
@@ -80,3 +80,10 @@ export function safeJsonHandler(fn) {
80
80
  }
81
81
  }
82
82
  }
83
+
84
+ export function escapeHtml(text) {
85
+ return String(text)
86
+ .replace(/&/g, '&')
87
+ .replace(/</g, '&lt;')
88
+ .replace(/>/g, '&gt;')
89
+ }