@goodandready/dsh-subscriptions 0.6.5 → 0.6.6
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 +7 -0
- package/README.ru.md +7 -0
- package/lib/client.js +143 -3
- package/lib/history.js +78 -2
- package/lib/routes/status.js +125 -7
- package/package.json +6 -6
- package/lib/ratelimit-parser.js +0 -0
- package/lib/ui-styles.js +0 -119
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
|
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
|
## 📦 Быстрая установка
|
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 = () =>
|
|
667
|
-
|
|
668
|
-
|
|
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
|
|
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
|
}
|
package/lib/routes/status.js
CHANGED
|
@@ -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
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@goodandready/dsh-subscriptions",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.6",
|
|
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
|
+
}
|
package/lib/ratelimit-parser.js
DELETED
|
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'
|