@goodandready/dsh-subscriptions 0.6.5 → 0.6.7

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
@@ -186,6 +186,13 @@ const res = await ctx.subscriptions.request('codex', '/backend-api/codex/images/
186
186
  * **Frosted Panel**: Hovering reveals a `backdrop-filter: blur(28px)` panel listing every account with usage bars. Clicking the bubble refreshes quota data; data also refreshes every 60 s.
187
187
  * **Settings Card Polish (`v0.4.18`)**: Chevron switched to the core `IconChevronDownOutline14` primitive; explicit settings snapshot states (loading / unavailable + Retry) guard against phantom input.
188
188
 
189
+ ### 16. ⚡ ClineBot-Inspired UI & Hardened Telemetry (`v0.6.6`)
190
+ * **Live Status Badge Bar**: Instant latency check to the host (`Host online (XX ms)`), real-time connected account indicators, and pool size at a glance.
191
+ * **One-Click Smoke Test (Ping)**: Test live connectivity to the active upstream subscription provider and measure real round-trip latency.
192
+ * **Session Telemetry Dashboard**: Visual stat cards displaying successful/total requests, average latency, session success rate, and last request activity.
193
+ * **Curated Models Catalog**: Quick preview of supported model families with context windows and capability tags (Vision, Reasoning, Hybrid).
194
+ * **Non-Blocking Storage**: `HistoryStore` debounces disk persistence asynchronously to prevent blocking the Node.js event loop during high-throughput streaming.
195
+
189
196
  ---
190
197
 
191
198
  ## 📦 Quick Installation
@@ -236,6 +243,15 @@ dsh-subscriptions:
236
243
 
237
244
  ---
238
245
 
246
+ ## 🧠 Claude Adaptive Thinking & Effort (Added in v0.6.7)
247
+
248
+ Supports Anthropic's adaptive thinking (`thinking: { type: "adaptive" }`) and reasoning effort steering (`output_config: { effort }`):
249
+ - **Model Support**: Automatically gates effort levels for Opus 4.6+, Opus 4.7+, Opus 5 (`low`, `medium`, `high`, `xhigh`, `max`) and Sonnet 4.6+, Sonnet 5 (`low`, `medium`, `high`).
250
+ - **Safe Fallback**: Models that do not support adaptive thinking (Haiku, Fable, 4.5 and earlier) reject the parameter cleanly and leave requests unaugmented to prevent 400 Bad Request API errors.
251
+ - **Dynamic Catalog**: The provider model catalog exports matching `reasoning.efforts` options to the DeepSeek Harness interface.
252
+
253
+ ---
254
+
239
255
  ## 🌐 Localization
240
256
 
241
257
  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).
package/README.ru.md CHANGED
@@ -162,6 +162,13 @@ const res = await ctx.subscriptions.request('codex', '/backend-api/codex/images/
162
162
  * **Reasoning vs Standard**: 429 на reasoning-модели (claude `*thinking*`, grok `*reasoning*`, все codex-модели) охлаждает только семейство reasoning этого аккаунта — standard-модели того же аккаунта продолжают работать. Legacy-кулдауны (от старых версий) по-прежнему блокируют весь аккаунт до истечения.
163
163
  * **Скрыть устаревшие модели (`hideDeprecatedModels`)**: убирает из нативного селектора id с `test`/`preview`/`dev`/`alpha`/`beta`/`legacy` (действует и на живые каталоги, и на статичный фолбэк).
164
164
 
165
+ ### 12. ⚡ Дизайн в стиле ClineBot и неблокирующая телеметрия (`v0.6.6`)
166
+ * **Строка статуса с бейджами**: Мгновенный замер задержки хоста (`Host online (XX ms)`), количество активных подключений и размер пула.
167
+ * **Интерактивный Smoke Test (Ping)**: Проверка связи с провайдером в один клик с измерением реального времени отклика в миллисекундах.
168
+ * **Панель телеметрии сессии**: 4 карточки метрик (успешных/всего запросов, средняя задержка, процент успешности, последняя активность).
169
+ * **Каталог моделей подписок**: Компактный список поддерживаемых моделей с размером контекста (128k, 200k, 1M) и тегами возможностей.
170
+ * **Асинхронная история**: Неблокирующий дебаунсинг записи `HistoryStore`, исключающий просадки event loop при интенсивном стриминге.
171
+
165
172
  ---
166
173
 
167
174
  ## 📦 Быстрая установка
@@ -209,6 +216,15 @@ dsh-subscriptions:
209
216
 
210
217
  ---
211
218
 
219
+ ## 🧠 Адаптивное мышление Claude и уровни усилия (Добавлено в v0.6.7)
220
+
221
+ Поддержка адаптивного мышления Anthropic (`thinking: { type: "adaptive" }`) и управления глубиной рассуждений (`output_config: { effort }`):
222
+ - **Поддержка моделей**: Автоматическое определение уровней усилия для Opus 4.6+, Opus 4.7+, Opus 5 (`low`, `medium`, `high`, `xhigh`, `max`) и Sonnet 4.6+, Sonnet 5 (`low`, `medium`, `high`).
223
+ - **Безопасный фоллбэк**: Модели без поддержки адаптивного рассуждения (Haiku, Fable, 4.5 и старше) не перегружаются параметрами во избежание ошибок Anthropic API (400 Bad Request).
224
+ - **Динамический каталог**: В каталоге моделей регистрируется секция `reasoning.efforts`, доступная для выбора в интерфейсе DeepSeek Harness.
225
+
226
+ ---
227
+
212
228
  ## 🌐 Локализация
213
229
 
214
230
  Исходный язык плагина - только английский. Русский и другие переводы во время работы предоставляют отдельные языковые плагины (например, плагин русификации), переводящие зарегистрированные ключи локали, - сам пакет не содержит встроенных переводов (Changed in 0.6.1).
package/README.zh.md CHANGED
@@ -138,6 +138,15 @@ dsh plugin --profile web add @goodandready/dsh-subscriptions
138
138
 
139
139
  ---
140
140
 
141
+ ## 🧠 Claude 自适应思考与推理深度控制 (v0.6.7 新增)
142
+
143
+ 支持 Anthropic 自适应思考 (`thinking: { type: "adaptive" }`) 及推理深度调节 (`output_config: { effort }`):
144
+ - **模型支持**: 针对 Opus 4.6+、Opus 4.7+、Opus 5 (`low`, `medium`, `high`, `xhigh`, `max`) 及 Sonnet 4.6+、Sonnet 5 (`low`, `medium`, `high`) 自动提供对应推理级别。
145
+ - **安全降级**: 不支持自适应思考的模型(如 Haiku、Fable、4.5 及更早版本)保持原始请求,防止出现 400 Bad Request 错误。
146
+ - **动态目录**: 模型列表自动暴露 `reasoning.efforts`,供 DeepSeek Harness 界面选择。
147
+
148
+ ---
149
+
141
150
  ## 🌐 本地化
142
151
 
143
152
  插件源语言仅为英语。俄语及其他翻译由独立的语言插件(如俄化插件)在运行时对注册的本地化键进行翻译,包本身不内置翻译(Changed in 0.6.1)。
package/lib/client.js CHANGED
@@ -111,6 +111,27 @@ window.__ModuleLoader__.load({
111
111
  '.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)}' +
112
112
  '.dsub-summary{cursor:pointer;font-weight:600;font-size:12.5px;color:var(--dsw-alias-label-secondary);user-select:none}' +
113
113
  '.dsub-summary:hover{color:var(--dsw-alias-label-primary)}' +
114
+ '.dsub-header-bar{display:flex;align-items:center;justify-content:space-between;flex-wrap:wrap;gap:8px;padding:8px 0 12px;margin-bottom:8px;border-bottom:1px solid var(--dsw-alias-border-l2)}' +
115
+ '.dsub-badge{font-size:12px;padding:3px 10px;border-radius:999px;border:1px solid var(--dsw-alias-border-l2);display:inline-flex;align-items:center;gap:5px;font-weight:500}' +
116
+ '.dsub-badge-ok{border-color:var(--dsw-alias-state-success-primary);color:var(--dsw-alias-state-success-primary);background:rgba(16,185,129,0.08)}' +
117
+ '.dsub-badge-warn{border-color:var(--dsw-alias-state-warning-primary);color:var(--dsw-alias-state-warning-primary);background:rgba(245,158,11,0.08)}' +
118
+ '.dsub-badge-bad{border-color:var(--dsw-alias-state-error-primary);color:var(--dsw-alias-state-error-primary);background:rgba(239,68,68,0.08)}' +
119
+ '.dsub-badge-neutral{border-color:var(--dsw-alias-border-l2);color:var(--dsw-alias-label-secondary);background:var(--dsw-alias-bg-layer-2)}' +
120
+ '.dsub-section-card{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-3);border-radius:12px;padding:14px 16px;display:flex;flex-direction:column;gap:10px;margin-top:12px}' +
121
+ '.dsub-section-title{font-size:14.5px;font-weight:600;color:var(--dsw-alias-label-primary);display:flex;align-items:center;justify-content:space-between;gap:8px}' +
122
+ '.dsub-section-desc{font-size:12px;color:var(--dsw-alias-label-secondary);margin-top:-4px;line-height:1.4}' +
123
+ '.dsub-grid-2{display:grid;grid-template-columns:repeat(auto-fit, minmax(200px, 1fr));gap:10px}' +
124
+ '.dsub-stat-box{padding:10px 12px;border:1px solid var(--dsw-alias-border-l2);border-radius:8px;background:var(--dsw-alias-bg-layer-2);display:flex;flex-direction:column;gap:3px}' +
125
+ '.dsub-stat-val{font-size:16.5px;font-weight:700;color:var(--dsw-alias-label-primary)}' +
126
+ '.dsub-stat-lbl{font-size:11.5px;color:var(--dsw-alias-label-secondary)}' +
127
+ '.dsub-alert-ok{padding:8px 12px;border-radius:8px;background:rgba(16,185,129,0.09);color:var(--dsw-alias-state-success-primary);font-size:12.5px;border:1px solid rgba(16,185,129,0.25)}' +
128
+ '.dsub-alert-bad{padding:8px 12px;border-radius:8px;background:rgba(239,68,68,0.09);color:var(--dsw-alias-state-error-primary);font-size:12.5px;border:1px solid rgba(239,68,68,0.25)}' +
129
+ '.dsub-btn-primary{background:var(--dsw-alias-label-primary) !important;color:var(--dsw-alias-bg-layer-3) !important;border-color:transparent !important;font-weight:600}' +
130
+ '.dsub-btn-primary:hover:not(:disabled){opacity:0.9}' +
131
+ '.dsub-model-grid{display:flex;flex-wrap:wrap;gap:6px;margin-top:6px}' +
132
+ '.dsub-model-chip{display:inline-flex;align-items:center;gap:6px;padding:3px 8px;border-radius:6px;background:var(--dsw-alias-bg-layer-1);border:1px solid var(--dsw-alias-border-l2);font-size:11.5px;color:var(--dsw-alias-label-primary)}' +
133
+ '.dsub-model-chip code{font-family:ui-monospace,monospace;font-weight:600}' +
134
+ '.dsub-model-tag{font-size:10px;padding:1px 4px;border-radius:3px;background:var(--dsw-alias-bg-layer-2);color:var(--dsw-alias-label-secondary);font-weight:600}' +
114
135
  '.dsub-heroBars{display:flex;flex-direction:column;gap:8px;margin-top:10px}' +
115
136
  '.dsub-barLabelRow{display:flex;justify-content:space-between;font-size:11.5px;color:var(--dsw-alias-label-secondary);margin-bottom:3px}' +
116
137
  '.dsub-barTrack{height:6px;border-radius:3px;background:var(--dsw-alias-bg-layer-1);overflow:hidden}' +
@@ -353,6 +374,21 @@ const cssId = 'dsh-subscriptions/settings.module.css'
353
374
  'probeIntervalMinHint': 'Interval for background checks to refresh account status and quota balance.',
354
375
  'notifyLimits': 'Notify on rate limits',
355
376
  'notifyLimitsHint': 'Show notification alerts when account usage reaches quota ceiling.',
377
+ 'hostOnline': 'Host online',
378
+ 'hostOffline': 'Host unreachable',
379
+ 'statsTitle': 'Session Telemetry',
380
+ 'statsRequests': 'Successful / Total Requests',
381
+ 'statsLatency': 'Avg Response Latency',
382
+ 'statsSuccessRate': 'Session Success Rate',
383
+ 'statsLastActivity': 'Last Activity',
384
+ 'smokeTitle': 'Connection Smoke Test (Ping)',
385
+ 'smokeDesc': 'Test live upstream connectivity and measure round-trip latency to the subscription provider.',
386
+ 'runSmoke': '⚡ Run Smoke Test (Ping)',
387
+ 'smokeTesting': 'Pinging…',
388
+ 'smokeOk': 'Smoke test passed',
389
+ 'smokeFail': 'Smoke test failed',
390
+ 'modelsCatalogTitle': 'Curated Subscription Models',
391
+ 'modelsCatalogDesc': 'Available model catalog across active subscriptions in DeepSeek Harness.',
356
392
  'resetCredits': 'Reset credits',
357
393
  'resetCreditsHint': 'ChatGPT reset cards: emergency reset for the 5-hour window. Requires a 5s confirmation.',
358
394
  'resetCreditsAction': 'Reset ChatGPT Limit',
@@ -650,6 +686,32 @@ const cssId = 'dsh-subscriptions/settings.module.css'
650
686
  .catch((e) => setDiag('error: ' + String(e && e.message || e)))
651
687
  }
652
688
  const [checkRes, setCheckRes] = React.useState({})
689
+ const [hostPing, setHostPing] = React.useState({ ok: true, latencyMs: null })
690
+ const [telemetry, setTelemetry] = React.useState(null)
691
+ const [smoke, setSmoke] = React.useState({ testing: false, result: null })
692
+
693
+ const reloadTelemetry = () => {
694
+ fetch('/dsh-subscriptions/telemetry', { cache: 'no-store' })
695
+ .then((r) => r.json())
696
+ .then((d) => { if (d && d.ok && d.telemetry) setTelemetry(d.telemetry) })
697
+ .catch(() => {})
698
+ }
699
+
700
+ const runSmokeTest = async (provider, index) => {
701
+ setSmoke({ testing: true, result: null })
702
+ try {
703
+ const res = await fetch('/dsh-subscriptions/smoke', {
704
+ method: 'POST',
705
+ headers: { 'Content-Type': 'application/json' },
706
+ body: JSON.stringify({ provider, index }),
707
+ })
708
+ const d = await res.json()
709
+ setSmoke({ testing: false, result: d })
710
+ reloadTelemetry()
711
+ } catch (e) {
712
+ setSmoke({ testing: false, result: { ok: false, error: { message: String(e && e.message || e) } } })
713
+ }
714
+ }
653
715
  const [checking, setChecking] = React.useState({})
654
716
  const [saved, setSaved] = React.useState(false)
655
717
  const [tokenDraft, setTokenDraft] = React.useState({})
@@ -663,9 +725,21 @@ const cssId = 'dsh-subscriptions/settings.module.css'
663
725
  setProviders((data && data.providers) || [])
664
726
  }
665
727
 
666
- const reload = () => fetch('/dsh-subscriptions/config', { cache: 'no-store' })
667
- .then((res) => res.json())
668
- .then(applyPayload)
728
+ const reload = () => {
729
+ const t0 = Date.now()
730
+ return fetch('/dsh-subscriptions/config', { cache: 'no-store' })
731
+ .then((res) => res.json())
732
+ .then((data) => {
733
+ setHostPing({ ok: true, latencyMs: Date.now() - t0 })
734
+ applyPayload(data)
735
+ reloadTelemetry()
736
+ return data
737
+ })
738
+ .catch((err) => {
739
+ setHostPing({ ok: false, latencyMs: null })
740
+ throw err
741
+ })
742
+ }
669
743
 
670
744
  React.useEffect(() => {
671
745
  let alive = true
@@ -871,7 +945,59 @@ const cssId = 'dsh-subscriptions/settings.module.css'
871
945
  { id: 'antigravity', name: 'Antigravity' },
872
946
  ]
873
947
 
948
+ const connectedCount = accounts.filter((a) => a.configured).length
949
+
874
950
  return React.createElement('div', { className: 'dsub-wrap' },
951
+ React.createElement('div', { className: 'dsub-header-bar' },
952
+ React.createElement('div', { className: 'dsub-row', style: { marginTop: 0 } },
953
+ React.createElement('span', {
954
+ className: 'dsub-badge ' + (hostPing.ok ? 'dsub-badge-ok' : 'dsub-badge-bad')
955
+ }, hostPing.ok
956
+ ? (hostPing.latencyMs != null ? 'Host online (' + hostPing.latencyMs + ' ms)' : t('hostOnline'))
957
+ : t('hostOffline')
958
+ ),
959
+ React.createElement('span', {
960
+ className: 'dsub-badge ' + (connectedCount > 0 ? 'dsub-badge-ok' : 'dsub-badge-warn')
961
+ }, connectedCount > 0
962
+ ? (connectedCount + ' ' + t('subsLogged'))
963
+ : t('subsNotLogged')
964
+ ),
965
+ React.createElement('span', {
966
+ className: 'dsub-badge dsub-badge-neutral'
967
+ }, 'Pool: ' + accounts.length + ' accounts'),
968
+ ),
969
+ React.createElement('button', {
970
+ type: 'button',
971
+ className: 'dsub-mini dsub-btn-primary',
972
+ disabled: smoke.testing,
973
+ onClick: () => runSmokeTest(),
974
+ }, smoke.testing ? t('smokeTesting') : t('runSmoke')),
975
+ ),
976
+ smoke.result ? React.createElement('div', {
977
+ className: smoke.result.ok ? 'dsub-alert-ok' : 'dsub-alert-bad',
978
+ style: { marginBottom: '12px' }
979
+ }, smoke.result.ok
980
+ ? ('🟢 ' + t('smokeOk') + ': ' + (smoke.result.provider || 'Provider').toUpperCase() + ' · ' + (smoke.result.latencyMs != null ? smoke.result.latencyMs + ' ms' : 'OK') + (smoke.result.label ? ' (' + smoke.result.label + ')' : ''))
981
+ : ('❌ ' + t('smokeFail') + ': ' + (smoke.result.error && (smoke.result.error.message || smoke.result.error.code) || 'Unknown error'))
982
+ ) : null,
983
+ telemetry && telemetry.totalRequests > 0 ? React.createElement('div', { className: 'dsub-grid-2', style: { marginBottom: '14px' } },
984
+ React.createElement('div', { className: 'dsub-stat-box' },
985
+ React.createElement('div', { className: 'dsub-stat-val' }, telemetry.successRequests + ' / ' + telemetry.totalRequests),
986
+ React.createElement('div', { className: 'dsub-stat-lbl' }, t('statsRequests')),
987
+ ),
988
+ React.createElement('div', { className: 'dsub-stat-box' },
989
+ React.createElement('div', { className: 'dsub-stat-val' }, (telemetry.avgLatencyMs || 0) + ' ms'),
990
+ React.createElement('div', { className: 'dsub-stat-lbl' }, t('statsLatency')),
991
+ ),
992
+ React.createElement('div', { className: 'dsub-stat-box' },
993
+ React.createElement('div', { className: 'dsub-stat-val' }, (telemetry.successRate || 100) + '%'),
994
+ React.createElement('div', { className: 'dsub-stat-lbl' }, t('statsSuccessRate')),
995
+ ),
996
+ React.createElement('div', { className: 'dsub-stat-box' },
997
+ React.createElement('div', { className: 'dsub-stat-val' }, telemetry.lastRequestAt ? Math.round((Date.now() - telemetry.lastRequestAt) / 60000) + 'm ago' : '—'),
998
+ React.createElement('div', { className: 'dsub-stat-lbl' }, t('statsLastActivity')),
999
+ ),
1000
+ ) : null,
875
1001
  React.createElement('div', { className: 'dsub-block' },
876
1002
  React.createElement('div', { className: 'dsub-h' }, t('title')),
877
1003
  React.createElement('div', { className: 'dsub-sub' },
@@ -1011,6 +1137,20 @@ const cssId = 'dsh-subscriptions/settings.module.css'
1011
1137
  }),
1012
1138
  ) : null,
1013
1139
  ),
1140
+ React.createElement('details', { className: 'dsub-group' },
1141
+ React.createElement('summary', { className: 'dsub-groupHead' }, '🎯 ' + t('modelsCatalogTitle')),
1142
+ React.createElement('div', { className: 'dsub-sub', style: { marginTop: '4px' } }, t('modelsCatalogDesc')),
1143
+ React.createElement('div', { className: 'dsub-model-grid' },
1144
+ React.createElement('div', { className: 'dsub-model-chip' }, React.createElement('code', null, 'gpt-4o'), React.createElement('span', { className: 'dsub-model-tag' }, '128k · Vision')),
1145
+ React.createElement('div', { className: 'dsub-model-chip' }, React.createElement('code', null, 'o1 / o3-mini'), React.createElement('span', { className: 'dsub-model-tag' }, '200k · Reasoning')),
1146
+ React.createElement('div', { className: 'dsub-model-chip' }, React.createElement('code', null, 'claude-3-7-sonnet'), React.createElement('span', { className: 'dsub-model-tag' }, '200k · Hybrid')),
1147
+ React.createElement('div', { className: 'dsub-model-chip' }, React.createElement('code', null, 'claude-3-5-sonnet'), React.createElement('span', { className: 'dsub-model-tag' }, '200k · Coding')),
1148
+ React.createElement('div', { className: 'dsub-model-chip' }, React.createElement('code', null, 'grok-beta'), React.createElement('span', { className: 'dsub-model-tag' }, '128k · Speed')),
1149
+ React.createElement('div', { className: 'dsub-model-chip' }, React.createElement('code', null, 'gemini-2.5-pro'), React.createElement('span', { className: 'dsub-model-tag' }, '1M · Context')),
1150
+ React.createElement('div', { className: 'dsub-model-chip' }, React.createElement('code', null, 'kimi-k1.5'), React.createElement('span', { className: 'dsub-model-tag' }, '128k · Math')),
1151
+ React.createElement('div', { className: 'dsub-model-chip' }, React.createElement('code', null, 'glm-4-plus'), React.createElement('span', { className: 'dsub-model-tag' }, '128k · Chinese')),
1152
+ ),
1153
+ ),
1014
1154
  React.createElement('details', { className: 'dsub-details' },
1015
1155
  React.createElement('summary', { className: 'dsub-summary' }, t('advancedSettings')),
1016
1156
  React.createElement('div', { className: 'dsub-block' },
package/lib/history.js CHANGED
@@ -4,17 +4,24 @@ import { homedir } from 'node:os'
4
4
 
5
5
  // #65: request and cost history. Stored in
6
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.
7
+ // All IO is best-effort: 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
 
11
11
  export class HistoryStore {
12
- constructor(dir = DEFAULT_HISTORY_DIR, ttlMs = 7 * 24 * 60 * 60 * 1000) {
12
+ constructor(dir = DEFAULT_HISTORY_DIR, ttlMs = 7 * 24 * 60 * 60 * 1000, debounceMs = 0) {
13
13
  this.dir = dir
14
14
  this.ttlMs = ttlMs
15
+ this.debounceMs = debounceMs
15
16
  this.path = join(dir, 'history.json')
16
17
  this.rows = []
18
+ this._persistTimer = null
17
19
  this._load()
20
+
21
+ if (typeof process !== 'undefined' && typeof process.on === 'function') {
22
+ this._exitHandler = () => this.flush()
23
+ process.on('beforeExit', this._exitHandler)
24
+ }
18
25
  }
19
26
 
20
27
  _load() {
@@ -36,6 +43,24 @@ export class HistoryStore {
36
43
  }
37
44
 
38
45
  _persist() {
46
+ if (this.debounceMs > 0) {
47
+ if (this._persistTimer) return
48
+ this._persistTimer = setTimeout(() => {
49
+ this._persistTimer = null
50
+ this.flush()
51
+ }, this.debounceMs)
52
+ if (typeof this._persistTimer.unref === 'function') this._persistTimer.unref()
53
+ return
54
+ }
55
+ this.flush()
56
+ }
57
+
58
+ /** Write pending updates to disk immediately. */
59
+ flush() {
60
+ if (this._persistTimer) {
61
+ clearTimeout(this._persistTimer)
62
+ this._persistTimer = null
63
+ }
39
64
  try {
40
65
  mkdirSync(this.dir, { recursive: true })
41
66
  writeFileSync(this.path, JSON.stringify(this.rows))
@@ -63,4 +88,55 @@ export class HistoryStore {
63
88
  size() {
64
89
  return this.rows.length
65
90
  }
91
+
92
+ /** Summarize session and 24-hour telemetry for UI stat cards. */
93
+ telemetrySummary() {
94
+ const total = this.rows.length
95
+ if (total === 0) {
96
+ return {
97
+ totalRequests: 0,
98
+ requests24h: 0,
99
+ successRequests: 0,
100
+ errorRequests: 0,
101
+ successRate: 100,
102
+ avgLatencyMs: 0,
103
+ lastLatencyMs: 0,
104
+ lastRequestAt: null,
105
+ lastProvider: null,
106
+ lastModel: null,
107
+ }
108
+ }
109
+ let success = 0
110
+ let error = 0
111
+ let totalMs = 0
112
+ let latencyCount = 0
113
+ const now = Date.now()
114
+ const last24h = now - 24 * 60 * 60 * 1000
115
+ let count24h = 0
116
+
117
+ for (const r of this.rows) {
118
+ if (r.ts >= last24h) count24h++
119
+ const st = Number(r.status || 0)
120
+ if (st >= 200 && st < 400) success++
121
+ else if (st >= 400) error++
122
+ if (typeof r.ms === 'number' && r.ms > 0) {
123
+ totalMs += r.ms
124
+ latencyCount++
125
+ }
126
+ }
127
+
128
+ const first = this.rows[0] // newest first
129
+ return {
130
+ totalRequests: total,
131
+ requests24h: count24h,
132
+ successRequests: success,
133
+ errorRequests: error,
134
+ successRate: total > 0 ? Math.round((success / total) * 100) : 100,
135
+ avgLatencyMs: latencyCount > 0 ? Math.round(totalMs / latencyCount) : 0,
136
+ lastLatencyMs: (first && typeof first.ms === 'number') ? first.ms : 0,
137
+ lastRequestAt: (first && first.ts) || null,
138
+ lastProvider: (first && first.provider) || null,
139
+ lastModel: (first && first.model) || null,
140
+ }
141
+ }
66
142
  }
@@ -32,7 +32,7 @@ export function registerStatusRoutes(ctx, state) {
32
32
  ctx.effect(() => ctx.webServer.register({
33
33
  kind: 'exact',
34
34
  path: '/dsh-subscriptions/config',
35
- handler: async (req, res) => {
35
+ handler: safeJsonHandler(async (req, res) => {
36
36
  if (req.method === 'GET') {
37
37
  writeJson(res, 200, await configResponse())
38
38
  return
@@ -67,10 +67,7 @@ export function registerStatusRoutes(ctx, state) {
67
67
  } catch (e) {
68
68
  writeJson(res, 400, { ok: false, error: { code: 'save', message: String(e && e.message || e) } })
69
69
  }
70
- },
71
-
72
-
73
-
70
+ }),
74
71
  }), 'dsh-subscriptions: /config')
75
72
 
76
73
 
@@ -239,7 +236,7 @@ export function registerStatusRoutes(ctx, state) {
239
236
  ctx.effect(() => ctx.webServer.register({
240
237
  kind: 'exact',
241
238
  path: '/dsh-subscriptions/history',
242
- handler: async (req, res) => {
239
+ handler: safeJsonHandler(async (req, res) => {
243
240
  if (req.method !== 'GET') {
244
241
  writeJson(res, 405, { ok: false, error: { code: 'method', message: 'GET only' } })
245
242
  return
@@ -251,6 +248,127 @@ export function registerStatusRoutes(ctx, state) {
251
248
  }
252
249
  const limit = Math.min(Number(queryOf(req).get('limit') || '10'), 100)
253
250
  writeJson(res, 200, { ok: true, total: history.size(), items: history.recent(limit) })
254
- },
251
+ }),
255
252
  }), 'dsh-subscriptions: /history')
253
+
254
+ // Telemetry summary route for UI stat cards (requests, latency, success rate)
255
+ ctx.effect(() => ctx.webServer.register({
256
+ kind: 'exact',
257
+ path: '/dsh-subscriptions/telemetry',
258
+ handler: safeJsonHandler(async (req, res) => {
259
+ if (req.method !== 'GET') {
260
+ writeJson(res, 405, { ok: false, error: { code: 'method', message: 'GET only' } })
261
+ return
262
+ }
263
+ const summary = typeof history.telemetrySummary === 'function'
264
+ ? history.telemetrySummary()
265
+ : { totalRequests: history.size(), requests24h: 0, successRequests: 0, errorRequests: 0, successRate: 100, avgLatencyMs: 0 }
266
+ writeJson(res, 200, { ok: true, telemetry: summary })
267
+ }),
268
+ }), 'dsh-subscriptions: /telemetry')
269
+
270
+ // Live smoke ping test for active subscription providers
271
+ ctx.effect(() => ctx.webServer.register({
272
+ kind: 'exact',
273
+ path: '/dsh-subscriptions/smoke',
274
+ handler: safeJsonHandler(async (req, res) => {
275
+ if (req.method !== 'POST') {
276
+ writeJson(res, 405, { ok: false, error: { code: 'method', message: 'POST only' } })
277
+ return
278
+ }
279
+ let payload = {}
280
+ try { payload = JSON.parse((await readBody(req, 64 * 1024)).toString('utf8') || '{}') } catch {}
281
+
282
+ let provider = payload.provider
283
+ let index = payload.index || 1
284
+
285
+ if (!provider) {
286
+ const accounts = await accountsView()
287
+ const connected = accounts.find((a) => a.configured)
288
+ if (connected) {
289
+ provider = connected.provider
290
+ index = connected.index || 1
291
+ }
292
+ }
293
+
294
+ if (!provider || !isProvider(provider)) {
295
+ writeJson(res, 400, { ok: false, error: { code: 'no_connected_accounts', message: 'No connected subscription account found to test' } })
296
+ return
297
+ }
298
+
299
+ const ref = oauthRef(provider, index)
300
+ const info = await store.describeRef(ref)
301
+ if (!info.configured) {
302
+ writeJson(res, 400, { ok: false, provider, index, ref, error: { code: 'not_connected', message: 'Account is not connected' } })
303
+ return
304
+ }
305
+
306
+ const t0 = Date.now()
307
+ let blob
308
+ try {
309
+ blob = await store.loadBlob(ref)
310
+ blob = await store.ensureFresh(provider, blob, ref)
311
+ } catch (e) {
312
+ const latencyMs = Date.now() - t0
313
+ history.add({ provider, ref, path: "/smoke", status: 401, ms: latencyMs })
314
+ writeJson(res, 502, { ok: false, provider, index, ref, latencyMs, error: { code: 'auth_failed', message: String(e && e.message || e) } })
315
+ return
316
+ }
317
+
318
+ const vendor = getVendor(provider)
319
+ if (typeof vendor.check === 'function') {
320
+ let capturedQuota = null
321
+ const probeFetch = async (url, init) => {
322
+ const res2 = await fetch(url, init)
323
+ try {
324
+ const snap = quotaSnapshot(provider, res2.headers, null, Date.now())
325
+ if (snap) { capturedQuota = snap; store.rememberQuota(ref, snap) }
326
+ } catch {}
327
+ return res2
328
+ }
329
+ try {
330
+ await vendor.check(blob, vendorConfig(provider, live()), probeFetch)
331
+ const latencyMs = Date.now() - t0
332
+ history.add({ provider, ref, path: "/smoke", status: 200, ms: latencyMs })
333
+ writeJson(res, 200, {
334
+ ok: true,
335
+ provider,
336
+ index,
337
+ ref,
338
+ latencyMs,
339
+ label: pmL(blob.label),
340
+ email: pmE(blob.email),
341
+ quota: capturedQuota || info.quota || null,
342
+ usagePercent: info.usagePercent ?? null,
343
+ })
344
+ return
345
+ } catch (e) {
346
+ const latencyMs = Date.now() - t0
347
+ history.add({ provider, ref, path: "/smoke", status: 502, ms: latencyMs })
348
+ writeJson(res, 502, {
349
+ ok: false,
350
+ provider,
351
+ index,
352
+ ref,
353
+ latencyMs,
354
+ error: { code: e && e.code ? e.code : 'VENDOR', message: String(e && e.message || e).slice(0, 300) },
355
+ })
356
+ return
357
+ }
358
+ }
359
+
360
+ const latencyMs = Date.now() - t0
361
+ history.add({ provider, ref, path: "/smoke", status: 200, ms: latencyMs })
362
+ writeJson(res, 200, {
363
+ ok: true,
364
+ provider,
365
+ index,
366
+ ref,
367
+ latencyMs,
368
+ label: pmL(blob.label),
369
+ email: pmE(blob.email),
370
+ quota: info.quota || null,
371
+ })
372
+ }),
373
+ }), 'dsh-subscriptions: /smoke')
256
374
  }
@@ -19,6 +19,38 @@ export function providerInfo() {
19
19
  return { id, name: 'Claude' }
20
20
  }
21
21
 
22
+ // Thinking-effort support follows Anthropic's current semantics:
23
+ // - Adaptive thinking (`thinking: { type: 'adaptive' }`) is the recommended /
24
+ // only on-mode for current generations; depth and token spend are steered by
25
+ // `output_config: { effort }`.
26
+ // - Effort is accepted on Opus 4.6+ and Sonnet 4.6+; `xhigh` was added on
27
+ // Opus 4.7; `max` is Opus-tier only.
28
+ // - Sonnet 4.5 / Haiku / Fable families reject effort (400), and models older
29
+ // than the adaptive generation use the legacy budget_tokens knob, so no
30
+ // effort control is advertised there. Model ids with a major version only
31
+ // (e.g. `claude-opus-5`) are treated as the latest of their family.
32
+ const BASE_EFFORT_LEVELS = ['low', 'medium', 'high']
33
+
34
+ export function effortLevelsForModel(modelId) {
35
+ const m = /^claude-(opus|sonnet|haiku|fable)(?:-(\d+))?(?:-(\d+))?/.exec(String(modelId || ''))
36
+ if (!m) return null
37
+ const [, family, majorRaw, minorRaw] = m
38
+ if (family === 'haiku' || family === 'fable') return null
39
+ const major = majorRaw ? Number(majorRaw) : null
40
+ const minor = minorRaw ? Number(minorRaw) : null
41
+ const atLeast = (wantMajor, wantMinor) => (
42
+ major === null || major > wantMajor || (major === wantMajor && (minor === null || minor >= wantMinor))
43
+ )
44
+ // Adaptive thinking / effort exist from Opus 4.6 and Sonnet 4.6 onward.
45
+ if (!atLeast(4, 6)) return null
46
+ const levels = [...BASE_EFFORT_LEVELS]
47
+ if (family === 'opus') {
48
+ if (atLeast(4, 7)) levels.push('xhigh')
49
+ levels.push('max')
50
+ }
51
+ return levels
52
+ }
53
+
22
54
  export function defaults() {
23
55
  return {
24
56
  clientId: '9d1c250a-e61b-44d9-88ed-5944d1962f5e',
@@ -88,7 +120,27 @@ export async function refresh(cfg, blob, fetchImpl) {
88
120
  }
89
121
 
90
122
  export async function listModels(blob, cfg) {
91
- return modelCatalog(id, cfg.models || defaults().models)
123
+ const raw = cfg.models && cfg.models.length ? cfg.models : defaults().models
124
+ const entries = raw.map((entry) => {
125
+ const model = typeof entry === 'string' ? { id: entry, name: entry } : { ...entry }
126
+ const levels = effortLevelsForModel(model.id || model.name)
127
+ if (!levels) return model
128
+ return { ...model, reasoning: { efforts: levels.map((level) => ({ id: level, name: level })) } }
129
+ })
130
+ return modelCatalog(id, entries)
131
+ }
132
+
133
+ // Advertise adaptive thinking + effort only for models whose capability table
134
+ // declares the chosen level; anything else (unsupported family, 'off', no
135
+ // selection) leaves the request untouched so behavior stays opt-in.
136
+ export function applyClaudeThinking(payload, options) {
137
+ const effort = options && String(options.reasoningEffort || '')
138
+ if (!effort || effort === 'off') return payload
139
+ const levels = effortLevelsForModel(payload && payload.model)
140
+ if (!levels || !levels.includes(effort)) return payload
141
+ payload.thinking = { type: 'adaptive' }
142
+ payload.output_config = { effort }
143
+ return payload
92
144
  }
93
145
 
94
146
 
@@ -114,6 +166,7 @@ export async function usage(blob, _cfg, fetchImpl) {
114
166
 
115
167
  export async function* streamOnce({ blob, options, fetchImpl, headers, config, signal }) {
116
168
  const payload = anthropicPayload(options, config.systemPrefix || IDENTITY)
169
+ applyClaudeThinking(payload, options)
117
170
  const res = await fetchImpl(API, {
118
171
  method: 'POST',
119
172
  headers: {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@goodandready/dsh-subscriptions",
3
- "version": "0.6.5",
3
+ "version": "0.6.7",
4
4
  "description": "Use ChatGPT Codex, Claude, Grok, Antigravity, Kimi, GLM, Cursor, Kiro, Copilot, Qwen, ERNIE, Spark, JetBrains, Perplexity, Replit, and Cody subscriptions as DeepSeek Harness LLM providers via OAuth.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -34,10 +34,6 @@
34
34
  "bugs": {
35
35
  "url": "https://github.com/GooDAnDReaDY/dsh-subscriptions/issues"
36
36
  },
37
- "scripts": {
38
- "test": "eslint lib/ && node --test test/*.test.mjs",
39
- "typecheck": "tsc -p tsconfig.checkjs.json"
40
- },
41
37
  "dsh": {
42
38
  "bundle": {
43
39
  "patch": "./cordis.patch.yml"
@@ -66,5 +62,9 @@
66
62
  "@deepseek-ai/dsh-llm": "0.1.0-rc.8",
67
63
  "@types/node": "^22.20.2",
68
64
  "typescript": "^7.0.2"
65
+ },
66
+ "scripts": {
67
+ "test": "eslint lib/ && node --test test/*.test.mjs",
68
+ "typecheck": "tsc -p tsconfig.checkjs.json"
69
69
  }
70
- }
70
+ }
File without changes
package/lib/ui-styles.js DELETED
@@ -1,119 +0,0 @@
1
- export const CSS =
2
- '.dsub-card{list-style:none;border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-3);border-radius:12px;transition:border-color .16s,background .16s}' +
3
- '.dsub-card:hover{border-color:var(--dsw-alias-label-dimmed)}' +
4
- '.dsub-cardOpen{background:var(--dsw-alias-bg-layer-2);border-color:var(--dsw-alias-label-dimmed)}' +
5
- '.dsub-header{appearance:none;width:100%;font:inherit;color:inherit;text-align:left;cursor:pointer;background:0 0;border:0;border-radius:12px;align-items:center;gap:12px;padding:18px 20px;display:flex}' +
6
- '.dsub-header:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:-2px}' +
7
- '.dsub-headText{flex-direction:column;flex:1;gap:4px;min-width:0;display:flex}' +
8
- '.dsub-name{color:var(--dsw-alias-label-primary);font-size:16px;font-weight:600;line-height:1.4}' +
9
- '.dsub-description{color:var(--dsw-alias-label-secondary);font-size:13px}' +
10
- '.dsub-chev{color:var(--dsw-alias-label-tertiary);flex:none;transition:transform .16s}' +
11
- '.dsub-chevOpen{transform:rotate(180deg)}' +
12
- '.dsub-body{border-top:1px solid var(--dsw-alias-border-l2);margin:0 20px;padding-bottom:12px}' +
13
- '.dsub-block{margin-top:10px}' +
14
- '.dsub-row{display:flex;align-items:center;flex-wrap:wrap;gap:8px;margin-top:8px}' +
15
- '.dsub-grow{flex:1 1 180px;min-width:140px}' +
16
- '.dsub-helpBox{margin-top:8px;padding:12px 14px;border-radius:8px;background:var(--dsw-alias-bg-layer-1);border:1px solid var(--dsw-alias-border-l1);font-size:12px;line-height:1.6}' +
17
- '.dsub-helpHead{display:flex;align-items:center;justify-content:space-between;font-weight:600;margin-bottom:8px;color:var(--dsw-alias-brand-primary)}' +
18
- '.dsub-helpStep{margin-bottom:6px;color:var(--dsw-alias-label-primary)}' +
19
- '.dsub-btnActive{background:var(--dsw-alias-brand-primary);color:#fff;border-color:var(--dsw-alias-brand-primary)}' +
20
- '.dsub-h{font-size:13.5px;font-weight:600;color:var(--dsw-alias-label-primary)}' +
21
- '.dsub-sub{font-size:12px;color:var(--dsw-alias-label-secondary)}' +
22
- '.dsub-dim{font-size:11.5px;color:var(--dsw-alias-label-tertiary)}' +
23
- '.dsub-mini{appearance:none;font:inherit;font-size:12px;cursor:pointer;border:1px solid var(--dsw-alias-border-l2);border-radius:6px;padding:3px 8px;background:var(--dsw-alias-bg-layer-2);color:var(--dsw-alias-label-primary);transition:all .15s}' +
24
- '.dsub-mini:hover{background:var(--dsw-alias-bg-layer-1);border-color:var(--dsw-alias-label-dimmed)}' +
25
- '.dsub-ok{font-size:12px;color:var(--dsw-alias-state-success-primary)}' +
26
- '.dsub-bad{font-size:12px;color:var(--dsw-alias-state-error-primary);overflow-wrap:anywhere;word-break:break-word;max-width:100%}' +
27
- '.dsub-hint{overflow-wrap:anywhere;word-break:break-word;max-width:100%}' +
28
- '.dsub-btn{appearance:none;font:inherit;cursor:pointer;border:1px solid var(--dsw-alias-border-l2);border-radius:8px;padding:7px 14px;font-size:13px;font-weight:500;background:var(--dsw-alias-bg-layer-2);color:var(--dsw-alias-label-primary);transition:all .15s}' +
29
- '.dsub-btn:hover{background:var(--dsw-alias-bg-layer-1);border-color:var(--dsw-alias-label-dimmed)}' +
30
- '.dsub-inp{height:36px;border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-2);color:var(--dsw-alias-label-primary);border-radius:8px;padding:0 12px;font-size:13px;min-width:180px}' +
31
- '.dsub-inp:focus{outline:none;border-color:var(--dsw-alias-brand-primary)}' +
32
- '.dsub-box{border:1px solid var(--dsw-alias-border-l2);border-radius:8px;padding:12px 14px;margin-top:8px;background:var(--dsw-alias-bg-layer-2)}' +
33
- '.dsub-cooldown{display:inline-block;padding:2px 6px;border-radius:4px;background:var(--dsw-alias-state-warning-primary);color:#000;font-size:11px;font-weight:600}' +
34
- '.dsub-bar{position:relative;height:10px;border-radius:999px;background:var(--dsw-alias-bg-layer-1);overflow:hidden;flex:1;min-width:80px}' +
35
- '.dsub-barFill{height:100%;border-radius:3px;background:var(--dsw-alias-state-success-primary);transition:width .2s}' +
36
- '.dsub-barWarn .dsub-barFill{background:var(--dsw-alias-state-warning-primary)}' +
37
- '.dsub-barFull .dsub-barFill{background:var(--dsw-alias-state-error-primary)}' +
38
- '.dsub-barRow{display:flex;align-items:center;gap:8px;font-size:12px;color:var(--dsw-alias-label-secondary)}' +
39
- '.dsub-manual{display:flex;flex-direction:column;gap:6px;margin-top:4px;padding-top:10px;border-top:1px solid var(--dsw-alias-border-l2)}' +
40
- '.dsub-verify{font-size:12px;color:var(--dsw-alias-state-warning-primary)}' +
41
- '.dsub-verify a{color:var(--dsw-alias-brand-primary)}' +
42
- '.dsub-pill{display:inline-flex;align-items:center;gap:7px;height:28px;padding:0 11px;border:1px solid var(--dsw-alias-border-l2);border-radius:999px;background:var(--dsw-alias-bg-layer-2);color:var(--dsw-alias-label-primary);font-size:12px;font-weight:600;cursor:pointer;box-shadow:0 1px 3px rgba(0,0,0,.15),inset 0 1px 0 rgba(255,255,255,.06);transition:all .16s ease;user-select:none}' +
43
- '.dsub-pill:hover{background:var(--dsw-alias-bg-layer-3);border-color:var(--dsw-alias-label-tertiary);transform:translateY(-1px);box-shadow:0 3px 8px rgba(0,0,0,.25),inset 0 1px 0 rgba(255,255,255,.1)}' +
44
- '.dsub-pill:active{transform:translateY(0)}' +
45
- '.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)}' +
46
- '.dsub-led{width:7px;height:7px;border-radius:50%;flex:none;transition:all .2s ease}' +
47
- '.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)}' +
48
- '.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)}' +
49
- '.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)}' +
50
- '.dsub-ledOff{background:var(--dsw-alias-label-tertiary,#6b7280)}' +
51
- '.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}' +
52
- '.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)}' +
53
- '@keyframes dsubFadeIn{from{opacity:0}to{opacity:1}}' +
54
- '@keyframes dsubScaleIn{from{opacity:0;transform:scale(.96)}to{opacity:1;transform:scale(1)}}' +
55
- '.dsub-modalHead{display:flex;align-items:center;justify-content:space-between;gap:12px;padding-bottom:14px;border-bottom:1px solid var(--dsw-alias-border-l2)}' +
56
- '.dsub-modalTitleWrap{display:flex;align-items:center;gap:10px}' +
57
- '.dsub-modalIcon{width:30px;height:30px;border-radius:8px;background:var(--dsw-alias-bg-layer-1);display:grid;place-items:center;color:var(--dsw-alias-brand-primary);font-size:14px}' +
58
- '.dsub-modalBadge{font-size:11px;font-weight:600;padding:2px 7px;border-radius:10px;background:var(--dsw-alias-bg-layer-1);color:var(--dsw-alias-label-secondary)}' +
59
- '.dsub-heroCard{margin-top:14px;padding:14px 16px;border-radius:12px;background:linear-gradient(135deg,var(--dsw-alias-bg-layer-2) 0%,var(--dsw-alias-bg-layer-3) 100%);border:1px solid var(--dsw-alias-border-l1);box-shadow:inset 0 1px 0 rgba(255,255,255,.05)}' +
60
- '.dsub-heroHead{display:flex;align-items:center;justify-content:space-between;margin-bottom:10px}' +
61
- '.dsub-heroTitle{font-size:14px;font-weight:700;display:flex;align-items:center;gap:6px}' +
62
- '.dsub-heroModel{font-size:11.5px;color:var(--dsw-alias-label-secondary);font-family:ui-monospace,monospace}' +
63
- '.dsub-heroBars{display:flex;flex-direction:column;gap:8px;margin-top:10px}' +
64
- '.dsub-barLabelRow{display:flex;justify-content:space-between;font-size:11.5px;color:var(--dsw-alias-label-secondary);margin-bottom:3px}' +
65
- '.dsub-barTrack{height:10px;border-radius:999px;background:var(--dsw-alias-bg-layer-1);overflow:hidden}' +
66
- '.dsub-barFillGrad{height:100%;border-radius:3px;transition:width .3s ease}' +
67
- '.dsub-panel-box{margin-top:10px;padding:14px 16px;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}' +
68
- '.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)}' +
69
- '.dsub-panel-title{font-size:16px;font-weight:600;color:var(--dsw-alias-label-primary);display:flex;align-items:center;justify-content:space-between}' +
70
- '.dsub-panel-desc{font-size:12px;color:var(--dsw-alias-label-secondary);line-height:1.4}' +
71
- '.dsub-usage-grid{display:flex;flex-direction:column;gap:8px;margin-top:4px}' +
72
- '.dsub-usage-card{display:flex;flex-direction:column;gap:5px;padding:8px 10px;border:1px solid var(--dsw-alias-border-l2);border-radius:8px;background:var(--dsw-alias-bg-layer-1)}' +
73
- '.dsub-usage-head{display:flex;justify-content:space-between;align-items:center;font-size:12px;font-weight:500;color:var(--dsw-alias-label-primary)}' +
74
- '.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}' +
75
- '.dsub-usage-fill{height:100%;border-radius:999px;transition:width .3s ease}' +
76
- '.dsub-usage-meta{display:flex;justify-content:space-between;align-items:center;font-size:11px;color:var(--dsw-alias-label-secondary)}' +
77
- '.dsub-tag-ok{border-color:var(--dsw-alias-state-success-primary);color:var(--dsw-alias-state-success-primary);background:color-mix(in srgb,var(--dsw-alias-state-success-primary) 8%,transparent);padding:3px 10px;border-radius:999px;font-size:12px;font-weight:600;border-style:solid;border-width:1px}' +
78
- '.dsub-tag-warn{border-color:var(--dsw-alias-state-warning-primary);color:var(--dsw-alias-state-warning-primary);background:color-mix(in srgb,var(--dsw-alias-state-warning-primary) 8%,transparent);padding:3px 10px;border-radius:999px;font-size:12px;font-weight:600;border-style:solid;border-width:1px}' +
79
- '.dsub-tag-bad{border-color:var(--dsw-alias-state-error-primary);color:var(--dsw-alias-state-error-primary);background:color-mix(in srgb,var(--dsw-alias-state-error-primary) 8%,transparent);padding:3px 10px;border-radius:999px;font-size:12px;font-weight:600;border-style:solid;border-width:1px}' +
80
- '.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}' +
81
- '.dsub-ref-tag{display:inline-flex;align-items:center;gap:5px;font-size:11.5px;color:var(--dsw-alias-label-secondary)}' +
82
- '.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}' +
83
- '.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}' +
84
- '.dsub-btn-danger:hover:not(:disabled){background:color-mix(in srgb,var(--dsw-alias-state-error-primary) 12%,transparent)!important}' +
85
- '.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}' +
86
- '.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}' +
87
- '.dsub-accountCard:hover{background:var(--dsw-alias-bg-layer-3);border-color:var(--dsw-alias-label-tertiary)}' +
88
- '.dsub-brandBadge{width:32px;height:32px;border-radius:8px;display:grid;place-items:center;font-size:12px;font-weight:700;flex-shrink:0}' +
89
- '.dsub-brandCodex{background:rgba(16,185,129,.15);color:#10b981;border:1px solid rgba(16,185,129,.3)}' +
90
- '.dsub-brandClaude{background:rgba(245,158,11,.15);color:#f59e0b;border:1px solid rgba(245,158,11,.3)}' +
91
- '.dsub-brandGrok{background:rgba(168,85,247,.15);color:#c084fc;border:1px solid rgba(168,85,247,.3)}' +
92
- '.dsub-brandAgy{background:rgba(59,130,246,.15);color:#60a5fa;border:1px solid rgba(59,130,246,.3)}' +
93
- '.dsub-brandOllama{background:rgba(107,114,128,.15);color:#9ca3af;border:1px solid rgba(107,114,128,.3)}' +
94
- '.dsub-brandKimi{background:rgba(236,72,153,.15);color:#f472b6;border:1px solid rgba(236,72,153,.3)}' +
95
- '.dsub-brandGlm{background:rgba(20,184,166,.15);color:#2dd4bf;border:1px solid rgba(20,184,166,.3)}' +
96
- '.dsub-planBadge{display:inline-flex;align-items:center;padding:1px 6px;border-radius:4px;font-size:10px;font-weight:600;text-transform:uppercase;letter-spacing:0.5px;background:rgba(99,102,241,.15);color:#818cf8;border:1px solid rgba(99,102,241,.3);margin-left:6px}' +
97
-
98
- '.dsub-accInfo{flex:1;min-width:0;display:flex;flex-direction:column;gap:3px}' +
99
- '.dsub-accNameRow{display:flex;align-items:center;justify-content:space-between;gap:8px}' +
100
- '.dsub-accName{font-size:13px;font-weight:600}' +
101
- '.dsub-accStatusTag{font-size:10.5px;font-weight:600;padding:1px 6px;border-radius:4px}' +
102
- '.dsub-statusOk{background:color-mix(in srgb,var(--dsw-alias-state-success-primary) 15%,transparent);color:var(--dsw-alias-state-success-primary)}' +
103
- '.dsub-statusWarn{background:color-mix(in srgb,var(--dsw-alias-state-warning-primary) 15%,transparent);color:var(--dsw-alias-state-warning-primary)}' +
104
- '.dsub-statusBad{background:color-mix(in srgb,var(--dsw-alias-state-error-primary) 15%,transparent);color:var(--dsw-alias-state-error-primary)}' +
105
- '.dsub-statusOff{background:var(--dsw-alias-bg-layer-1);color:var(--dsw-alias-label-tertiary)}' +
106
- '.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)}' +
107
- '.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}' +
108
- '.dsub-btnPrimary:hover{opacity:.9}' +
109
- '.dsub-btnSec{display:inline-flex;align-items:center;gap:5px;padding:5px 10px;border-radius:7px;font-size:11.5px;font-weight:500;background:var(--dsw-alias-bg-layer-1);color:var(--dsw-alias-label-secondary);border:1px solid var(--dsw-alias-border-l2);cursor:pointer;text-decoration:none;transition:all .15s}' +
110
- '.dsub-btnSec:hover{background:var(--dsw-alias-bg-layer-2);color:var(--dsw-alias-label-primary);border-color:var(--dsw-alias-label-tertiary)}' +
111
- '.dsub-cq{display:inline-flex;align-items:center;gap:6px;font-size:12px;color:var(--dsw-alias-label-secondary);padding:0 6px}' +
112
- '.dsub-cqBar{position:relative;width:40px;height:5px;border-radius:3px;background:var(--dsw-alias-bg-layer-1);overflow:hidden}' +
113
- '.dsub-cqBarFill{height:100%;border-radius:3px;background:var(--dsw-alias-state-success-primary)}' +
114
- '.dsub-cqBarWarn .dsub-cqBarFill{background:var(--dsw-alias-state-warning-primary)}' +
115
- '.dsub-cqBarFull .dsub-cqBarFill{background:var(--dsw-alias-state-error-primary)}' +
116
- '.dsub-cqB{font-variant-numeric:tabular-nums;font-weight:600;color:var(--dsw-alias-label-primary)}' +
117
- '.dsub-diag{font:11px/1.5 ui-monospace,SFMono-Regular,monospace;max-height:260px;overflow:auto;white-space:pre-wrap;word-break:break-word;border:1px solid var(--dsw-alias-border-l2);border-radius:8px;padding:10px;background:var(--dsw-alias-bg-layer-2);color:var(--dsw-alias-label-secondary)}'
118
-
119
- const cssId = 'dsh-subscriptions/settings.module.css'