@goodandready/dsh-subscriptions 0.6.22 → 0.6.24
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/lib/accounts-view.js +84 -0
- package/lib/accounts.js +5 -2
- package/lib/adapter-manager.js +300 -0
- package/lib/adapter.js +7 -7
- package/lib/alerts.js +2 -2
- package/lib/backoff.js +1 -1
- package/lib/client.js +89 -75
- package/lib/code-assist.js +1 -1
- package/lib/config-schema.js +48 -6
- package/lib/diagnostics.js +60 -0
- package/lib/history.js +21 -1
- package/lib/http.js +58 -11
- package/lib/index.js +143 -543
- package/lib/mask.js +1 -1
- package/lib/ollama.js +4 -4
- package/lib/routes/accounts.js +5 -1
- package/lib/routes/proxy.js +1 -1
- package/lib/routes/status.js +42 -7
- package/lib/routes.js +0 -1
- package/lib/session-pin.js +0 -8
- package/lib/subscriptions.js +5 -6
- package/lib/vault.js +2 -2
- package/lib/vendor-factory.js +0 -1
- package/lib/vendors/antigravity.js +1 -1
- package/lib/vendors/codex.js +8 -7
- package/lib/vendors/grok.js +1 -1
- package/lib/vendors/index.js +0 -1
- package/lib/vendors/qwen.js +1 -1
- package/lib/vendors/spark.js +1 -1
- package/package.json +3 -1
package/lib/client.js
CHANGED
|
@@ -4,9 +4,6 @@ window.__ModuleLoader__.load({
|
|
|
4
4
|
var module = { exports: {} }
|
|
5
5
|
var exports = module.exports
|
|
6
6
|
const React = require('react')
|
|
7
|
-
const createPortal = (function () {
|
|
8
|
-
try { return require('react-dom').createPortal } catch { return null }
|
|
9
|
-
})()
|
|
10
7
|
|
|
11
8
|
// Modular translator: shared by component subscriptions and helpers.
|
|
12
9
|
let t = (key) => key
|
|
@@ -21,15 +18,15 @@ window.__ModuleLoader__.load({
|
|
|
21
18
|
return { hasError: true, error }
|
|
22
19
|
}
|
|
23
20
|
componentDidCatch(error, info) {
|
|
24
|
-
try { console.error("[dsh-subscriptions UI error]", error, info) } catch
|
|
21
|
+
try { console.error("[dsh-subscriptions UI error]", error, info) } catch { /* fallback if console fails */ }
|
|
25
22
|
}
|
|
26
23
|
render() {
|
|
27
24
|
if (this.state.hasError) {
|
|
28
25
|
return React.createElement(
|
|
29
26
|
"div",
|
|
30
|
-
{ style: { padding: "12px", border: "1px solid var(--dsw-alias-state-error-primary
|
|
31
|
-
React.createElement("div", { style: { fontWeight: 600, color: "var(--dsw-alias-state-error-primary
|
|
32
|
-
React.createElement("div", { style: { color: "var(--dsw-alias-label-secondary
|
|
27
|
+
{ style: { padding: "12px", border: "1px solid var(--dsw-alias-state-error-primary)", borderRadius: "8px", background: "var(--dsw-alias-bg-layer-2)", margin: "8px 0", fontSize: "13px" } },
|
|
28
|
+
React.createElement("div", { style: { fontWeight: 600, color: "var(--dsw-alias-state-error-primary)", marginBottom: "4px" } }, "Subscriptions UI error"),
|
|
29
|
+
React.createElement("div", { style: { color: "var(--dsw-alias-label-secondary)", fontSize: "12px", marginBottom: "8px" } }, String(this.state.error && this.state.error.message || this.state.error || "Unknown error")),
|
|
33
30
|
React.createElement("button", {
|
|
34
31
|
className: "dsub-mini",
|
|
35
32
|
onClick: () => this.setState({ hasError: false, error: null })
|
|
@@ -63,7 +60,7 @@ window.__ModuleLoader__.load({
|
|
|
63
60
|
'.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}' +
|
|
64
61
|
'.dsub-helpHead{display:flex;align-items:center;justify-content:space-between;font-weight:600;margin-bottom:8px;color:var(--dsw-alias-brand-primary)}' +
|
|
65
62
|
'.dsub-helpStep{margin-bottom:6px;color:var(--dsw-alias-label-primary)}' +
|
|
66
|
-
'.dsub-btnActive{background:var(--dsw-alias-brand-primary);color:var(--dsw-alias-text-contrast
|
|
63
|
+
'.dsub-btnActive{background:var(--dsw-alias-brand-primary);color:var(--dsw-alias-text-contrast);border-color:var(--dsw-alias-brand-primary)}' +
|
|
67
64
|
'.dsub-h{font-size:13.5px;font-weight:600;color:var(--dsw-alias-label-primary)}' +
|
|
68
65
|
'.dsub-sub{font-size:12px;color:var(--dsw-alias-label-secondary)}' +
|
|
69
66
|
'.dsub-dim{font-size:11.5px;color:var(--dsw-alias-label-tertiary)}' +
|
|
@@ -77,7 +74,7 @@ window.__ModuleLoader__.load({
|
|
|
77
74
|
'.dsub-inp{height:30px;border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-3);color:var(--dsw-alias-label-primary);border-radius:6px;padding:0 8px;font-size:12px;min-width:180px}' +
|
|
78
75
|
'.dsub-inp:focus{outline:none;border-color:var(--dsw-alias-brand-primary)}' +
|
|
79
76
|
'.dsub-box{border:1px solid var(--dsw-alias-border-l2);border-radius:8px;padding:10px;margin-top:8px;background:var(--dsw-alias-bg-layer-2)}' +
|
|
80
|
-
'.dsub-cooldown{display:inline-block;padding:2px 6px;border-radius:4px;background:var(--dsw-alias-state-warning-primary);color:var(--dsw-alias-bg-layer-1
|
|
77
|
+
'.dsub-cooldown{display:inline-block;padding:2px 6px;border-radius:4px;background:var(--dsw-alias-state-warning-primary);color:var(--dsw-alias-bg-layer-1);font-size:11px;font-weight:600}' +
|
|
81
78
|
'.dsub-bar{position:relative;height:6px;border-radius:3px;background:var(--dsw-alias-bg-layer-1);overflow:hidden;flex:1;min-width:80px}' +
|
|
82
79
|
'.dsub-barFill{height:100%;border-radius:3px;background:var(--dsw-alias-state-success-primary);transition:width .2s}' +
|
|
83
80
|
'.dsub-barWarn .dsub-barFill{background:var(--dsw-alias-state-warning-primary)}' +
|
|
@@ -91,10 +88,10 @@ window.__ModuleLoader__.load({
|
|
|
91
88
|
'.dsub-pill:active{transform:translateY(0)}' +
|
|
92
89
|
'.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)}' +
|
|
93
90
|
'.dsub-led{width:7px;height:7px;border-radius:50%;flex:none;transition:all .2s ease}' +
|
|
94
|
-
'.dsub-ledOk{background:var(--dsw-alias-state-success-primary
|
|
95
|
-
'.dsub-ledWarn{background:var(--dsw-alias-state-warning-primary
|
|
96
|
-
'.dsub-ledBad{background:var(--dsw-alias-state-error-primary
|
|
97
|
-
'.dsub-ledOff{background:var(--dsw-alias-label-tertiary
|
|
91
|
+
'.dsub-ledOk{background:var(--dsw-alias-state-success-primary);box-shadow:0 0 6px color-mix(in srgb,var(--dsw-alias-state-success-primary) 70%,transparent)}' +
|
|
92
|
+
'.dsub-ledWarn{background:var(--dsw-alias-state-warning-primary);box-shadow:0 0 6px color-mix(in srgb,var(--dsw-alias-state-warning-primary) 70%,transparent)}' +
|
|
93
|
+
'.dsub-ledBad{background:var(--dsw-alias-state-error-primary);box-shadow:0 0 6px color-mix(in srgb,var(--dsw-alias-state-error-primary) 70%,transparent)}' +
|
|
94
|
+
'.dsub-ledOff{background:var(--dsw-alias-label-tertiary)}' +
|
|
98
95
|
'.dsub-modalWrap{position:fixed;inset:0;z-index:10020;display:grid;place-items:center;background:color-mix(in srgb, black 65%, transparent);backdrop-filter:blur(10px);animation:dsubFadeIn .16s ease-out}' +
|
|
99
96
|
'.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 color-mix(in srgb, black 60%, transparent),0 0 0 1px color-mix(in srgb, white 7%, transparent);padding:20px;animation:dsubScaleIn .18s cubic-bezier(.16,1,.3,1)}' +
|
|
100
97
|
'@keyframes dsubFadeIn{from{opacity:0}to{opacity:1}}' +
|
|
@@ -160,11 +157,11 @@ window.__ModuleLoader__.load({
|
|
|
160
157
|
'.dsub-brandBadge{width:32px;height:32px;border-radius:8px;display:grid;place-items:center;font-size:12px;font-weight:700;flex-shrink:0}' +
|
|
161
158
|
'.dsub-brandCodex{background:color-mix(in srgb,var(--dsw-alias-state-success-primary) 15%,transparent);color:var(--dsw-alias-state-success-primary);border:1px solid color-mix(in srgb,var(--dsw-alias-state-success-primary) 30%,transparent)}' +
|
|
162
159
|
'.dsub-brandClaude{background:color-mix(in srgb,var(--dsw-alias-state-warning-primary) 15%,transparent);color:var(--dsw-alias-state-warning-primary);border:1px solid color-mix(in srgb,var(--dsw-alias-state-warning-primary) 30%,transparent)}' +
|
|
163
|
-
'.dsub-brandGrok{background:color-mix(in srgb
|
|
160
|
+
'.dsub-brandGrok{background:color-mix(in srgb,var(--dsw-alias-accent-primary, var(--dsw-alias-brand-primary)) 15%,transparent);color:var(--dsw-alias-accent-primary, var(--dsw-alias-brand-primary));border:1px solid color-mix(in srgb,var(--dsw-alias-accent-primary, var(--dsw-alias-brand-primary)) 30%,transparent)}' +
|
|
164
161
|
'.dsub-brandAgy{background:color-mix(in srgb,var(--dsw-alias-brand-primary) 15%,transparent);color:var(--dsw-alias-brand-primary);border:1px solid color-mix(in srgb,var(--dsw-alias-brand-primary) 30%,transparent)}' +
|
|
165
162
|
'.dsub-brandOllama{background:color-mix(in srgb,var(--dsw-alias-label-tertiary) 15%,transparent);color:var(--dsw-alias-label-secondary);border:1px solid color-mix(in srgb,var(--dsw-alias-label-tertiary) 30%,transparent)}' +
|
|
166
|
-
'.dsub-brandKimi{background:color-mix(in srgb
|
|
167
|
-
'.dsub-brandGlm{background:color-mix(in srgb
|
|
163
|
+
'.dsub-brandKimi{background:color-mix(in srgb,var(--dsw-alias-state-warning-primary) 15%,transparent);color:var(--dsw-alias-state-warning-primary);border:1px solid color-mix(in srgb,var(--dsw-alias-state-warning-primary) 30%,transparent)}' +
|
|
164
|
+
'.dsub-brandGlm{background:color-mix(in srgb,var(--dsw-alias-state-info-primary, var(--dsw-alias-brand-primary)) 15%,transparent);color:var(--dsw-alias-state-info-primary, var(--dsw-alias-brand-primary));border:1px solid color-mix(in srgb,var(--dsw-alias-state-info-primary, var(--dsw-alias-brand-primary)) 30%,transparent)}' +
|
|
168
165
|
'.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:color-mix(in srgb,var(--dsw-alias-brand-primary) 15%,transparent);color:var(--dsw-alias-brand-primary);border:1px solid color-mix(in srgb,var(--dsw-alias-brand-primary) 30%,transparent);margin-left:6px}' +
|
|
169
166
|
|
|
170
167
|
'.dsub-accInfo{flex:1;min-width:0;display:flex;flex-direction:column;gap:3px}' +
|
|
@@ -176,7 +173,7 @@ window.__ModuleLoader__.load({
|
|
|
176
173
|
'.dsub-statusBad{background:color-mix(in srgb,var(--dsw-alias-state-error-primary) 15%,transparent);color:var(--dsw-alias-state-error-primary)}' +
|
|
177
174
|
'.dsub-statusOff{background:var(--dsw-alias-bg-layer-1);color:var(--dsw-alias-label-tertiary)}' +
|
|
178
175
|
'.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)}' +
|
|
179
|
-
'.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
|
|
176
|
+
'.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);color:var(--dsw-alias-text-contrast);border:0;cursor:pointer;transition:opacity .15s}' +
|
|
180
177
|
'.dsub-btnPrimary:hover{opacity:.9}' +
|
|
181
178
|
'.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}' +
|
|
182
179
|
'.dsub-btnSec:hover{background:var(--dsw-alias-bg-layer-2);color:var(--dsw-alias-label-primary);border-color:var(--dsw-alias-label-tertiary)}' +
|
|
@@ -696,7 +693,7 @@ const cssId = 'dsh-subscriptions/settings.module.css'
|
|
|
696
693
|
if (parsed.error && parsed.error.message) text = parsed.error.message
|
|
697
694
|
else if (parsed.message) text = parsed.message
|
|
698
695
|
else if (parsed.code) text = parsed.code
|
|
699
|
-
} catch
|
|
696
|
+
} catch { /* ignore parse error */ }
|
|
700
697
|
}
|
|
701
698
|
var firstLine = text.split('\n')[0].trim()
|
|
702
699
|
if (firstLine.length > 160) return firstLine.slice(0, 157) + '...'
|
|
@@ -767,7 +764,7 @@ const cssId = 'dsh-subscriptions/settings.module.css'
|
|
|
767
764
|
function badgeFor(account, t) {
|
|
768
765
|
if (!account || !account.configured) return t('notConnected')
|
|
769
766
|
if (account.validationUrl) return t('verifyAccount')
|
|
770
|
-
//
|
|
767
|
+
// Issue 303: a quarantined account says why it is parked.
|
|
771
768
|
if (account.quarantineReason && account.quarantineUntil && account.quarantineUntil > Date.now()) {
|
|
772
769
|
return account.quarantineReason === 'VERIFY' ? t('verifyAccount') : String(account.quarantineReason)
|
|
773
770
|
}
|
|
@@ -783,7 +780,7 @@ const cssId = 'dsh-subscriptions/settings.module.css'
|
|
|
783
780
|
try {
|
|
784
781
|
const primitives = require('@deepseek-ai/dsh-client-ui-primitives')
|
|
785
782
|
ChevronIcon = primitives && primitives.IconChevronDownOutline14
|
|
786
|
-
} catch
|
|
783
|
+
} catch {
|
|
787
784
|
ChevronIcon = null
|
|
788
785
|
}
|
|
789
786
|
function FallbackChevron(props) {
|
|
@@ -863,7 +860,7 @@ const cssId = 'dsh-subscriptions/settings.module.css'
|
|
|
863
860
|
.then((d) => {
|
|
864
861
|
const txt = d && d.ok ? JSON.stringify(d.report, null, 2) : ('error: ' + ((d && d.error && d.error.message) || 'unknown'))
|
|
865
862
|
setDiag(txt)
|
|
866
|
-
try { navigator.clipboard.writeText(txt) } catch
|
|
863
|
+
try { navigator.clipboard.writeText(txt) } catch { /* clipboard write disallowed */ }
|
|
867
864
|
})
|
|
868
865
|
.catch((e) => setDiag('error: ' + String(e && e.message || e)))
|
|
869
866
|
}
|
|
@@ -937,8 +934,10 @@ const cssId = 'dsh-subscriptions/settings.module.css'
|
|
|
937
934
|
const [openHelp, setOpenHelp] = React.useState({})
|
|
938
935
|
const [err, setErr] = React.useState('')
|
|
939
936
|
const [info, setInfo] = React.useState('')
|
|
937
|
+
const [revision, setRevision] = React.useState(null)
|
|
940
938
|
|
|
941
939
|
const applyPayload = (data) => {
|
|
940
|
+
if (data && data.revision != null) setRevision(data.revision)
|
|
942
941
|
setDraft(JSON.parse(JSON.stringify((data && data.config) || {})))
|
|
943
942
|
setAccounts((data && data.accounts) || [])
|
|
944
943
|
setProviders((data && data.providers) || [])
|
|
@@ -998,13 +997,20 @@ const cssId = 'dsh-subscriptions/settings.module.css'
|
|
|
998
997
|
|
|
999
998
|
const save = async () => {
|
|
1000
999
|
setErr(''); setSaved(false)
|
|
1000
|
+
const payload = revision != null ? Object.assign({}, draft, { revision }) : draft
|
|
1001
1001
|
const res = await fetch('/dsh-subscriptions/config', {
|
|
1002
1002
|
method: 'PUT',
|
|
1003
1003
|
headers: { 'Content-Type': 'application/json' },
|
|
1004
|
-
body: JSON.stringify(
|
|
1004
|
+
body: JSON.stringify(payload),
|
|
1005
1005
|
})
|
|
1006
1006
|
const data = await res.json().catch(() => ({}))
|
|
1007
|
-
if (!res.ok)
|
|
1007
|
+
if (!res.ok) {
|
|
1008
|
+
if (res.status === 409) {
|
|
1009
|
+
reload().catch(() => {})
|
|
1010
|
+
throw new Error((data.error && data.error.message) || 'Configuration modified concurrently; reloaded.')
|
|
1011
|
+
}
|
|
1012
|
+
throw new Error((data.error && data.error.message) || ('HTTP ' + res.status))
|
|
1013
|
+
}
|
|
1008
1014
|
applyPayload(data)
|
|
1009
1015
|
setSaved(true); setTimeout(() => setSaved(false), 2000)
|
|
1010
1016
|
}
|
|
@@ -1251,7 +1257,7 @@ const cssId = 'dsh-subscriptions/settings.module.css'
|
|
|
1251
1257
|
React.createElement('div', { style: { padding: '8px 0', overflowX: 'auto' } },
|
|
1252
1258
|
React.createElement('table', { style: { width: '100%', fontSize: '12px', borderCollapse: 'collapse' } },
|
|
1253
1259
|
React.createElement('thead', null,
|
|
1254
|
-
React.createElement('tr', { style: { borderBottom: '1px solid var(--dsw-alias-border-primary
|
|
1260
|
+
React.createElement('tr', { style: { borderBottom: '1px solid var(--dsw-alias-border-primary)', textAlign: 'left', opacity: 0.7 } },
|
|
1255
1261
|
React.createElement('th', { style: { padding: '6px' } }, t('healthAccount')),
|
|
1256
1262
|
React.createElement('th', { style: { padding: '6px' } }, t('healthScore')),
|
|
1257
1263
|
React.createElement('th', { style: { padding: '6px' } }, t('healthLatency')),
|
|
@@ -1262,11 +1268,11 @@ const cssId = 'dsh-subscriptions/settings.module.css'
|
|
|
1262
1268
|
React.createElement('tbody', null,
|
|
1263
1269
|
accounts.map((acc, idx) => {
|
|
1264
1270
|
const sc = acc.healthScore != null ? acc.healthScore : 100
|
|
1265
|
-
const healthColor = sc >= 80 ? '
|
|
1271
|
+
const healthColor = sc >= 80 ? 'var(--dsw-alias-state-success-primary)' : (sc >= 50 ? 'var(--dsw-alias-state-warning-primary)' : 'var(--dsw-alias-state-error-primary)')
|
|
1266
1272
|
const isQ = acc.status === 'quarantine' || (acc.quarantineUntil && Number(acc.quarantineUntil) > Date.now())
|
|
1267
1273
|
const statusLabel = isQ ? ('⛔ ' + t('healthQuarantine')) : (acc.status === 'probing' ? ('🟡 ' + t('healthWarmup')) : (acc.configured ? ('🟢 ' + t('healthActive')) : ('⚪ ' + t('healthIdle'))))
|
|
1268
1274
|
const pct = acc.usagePercent != null ? Math.round(acc.usagePercent) : null
|
|
1269
|
-
return React.createElement('tr', { key: (acc.ref || acc.provider + acc.index || idx), style: { borderBottom: '1px solid var(--dsw-alias-border-secondary
|
|
1275
|
+
return React.createElement('tr', { key: (acc.ref || acc.provider + acc.index || idx), style: { borderBottom: '1px solid var(--dsw-alias-border-secondary)' } },
|
|
1270
1276
|
React.createElement('td', { style: { padding: '6px', fontWeight: 600 } }, (acc.provider ? acc.provider.toUpperCase() : 'LLM') + ' #' + (acc.index || 1) + (acc.label ? ' (' + acc.label + ')' : '')),
|
|
1271
1277
|
React.createElement('td', { style: { padding: '6px', color: healthColor } }, sc + '%'),
|
|
1272
1278
|
React.createElement('td', { style: { padding: '6px' } }, acc.latencyMs ? acc.latencyMs + ' ms' : '—'),
|
|
@@ -1481,7 +1487,7 @@ const cssId = 'dsh-subscriptions/settings.module.css'
|
|
|
1481
1487
|
diag ? React.createElement('div', { className: 'dsub-row' },
|
|
1482
1488
|
React.createElement('button', {
|
|
1483
1489
|
type: 'button', className: 'dsub-mini',
|
|
1484
|
-
onClick: () => { try { navigator.clipboard.writeText(diag) } catch
|
|
1490
|
+
onClick: () => { try { navigator.clipboard.writeText(diag) } catch { /* clipboard write error */ } },
|
|
1485
1491
|
}, t('diagCopy')),
|
|
1486
1492
|
React.createElement('pre', { className: 'dsub-diag' }, diag),
|
|
1487
1493
|
) : null,
|
|
@@ -1650,7 +1656,7 @@ const cssId = 'dsh-subscriptions/settings.module.css'
|
|
|
1650
1656
|
React.createElement('div', { className: 'dsub-row' },
|
|
1651
1657
|
React.createElement('button', {
|
|
1652
1658
|
type: 'button', className: 'dsub-mini',
|
|
1653
|
-
onClick: () => { try { navigator.clipboard.writeText(d.userCode) } catch
|
|
1659
|
+
onClick: () => { try { navigator.clipboard.writeText(d.userCode) } catch { /* clipboard write error */ } },
|
|
1654
1660
|
}, t('deviceCopy')),
|
|
1655
1661
|
React.createElement('a', { href: d.authUrl, target: '_blank', rel: 'noopener noreferrer', className: 'dsub-mini' }, t('verifyLink')),
|
|
1656
1662
|
),
|
|
@@ -1876,7 +1882,7 @@ const cssId = 'dsh-subscriptions/settings.module.css'
|
|
|
1876
1882
|
const addLocale = (locale, dictionary) => {
|
|
1877
1883
|
try {
|
|
1878
1884
|
return ctx.locale.register(NS, locale, dictionary)
|
|
1879
|
-
} catch
|
|
1885
|
+
} catch {
|
|
1880
1886
|
return () => {}
|
|
1881
1887
|
}
|
|
1882
1888
|
}
|
|
@@ -1889,51 +1895,59 @@ const cssId = 'dsh-subscriptions/settings.module.css'
|
|
|
1889
1895
|
// The canonical place is the Plugins tab: a card with its own header
|
|
1890
1896
|
// and collapse instead of a row in the side list. The registration key must
|
|
1891
1897
|
// equal the settings namespace, otherwise the tab silently hides the slot.
|
|
1892
|
-
|
|
1893
|
-
|
|
1894
|
-
|
|
1895
|
-
|
|
1896
|
-
|
|
1897
|
-
|
|
1898
|
-
|
|
1899
|
-
|
|
1900
|
-
|
|
1901
|
-
|
|
1902
|
-
|
|
1903
|
-
|
|
1904
|
-
|
|
1905
|
-
|
|
1906
|
-
|
|
1907
|
-
|
|
1908
|
-
|
|
1909
|
-
|
|
1910
|
-
|
|
1911
|
-
|
|
1912
|
-
|
|
1913
|
-
|
|
1914
|
-
|
|
1915
|
-
|
|
1916
|
-
|
|
1917
|
-
|
|
1918
|
-
|
|
1919
|
-
|
|
1920
|
-
|
|
1921
|
-
|
|
1922
|
-
|
|
1923
|
-
|
|
1924
|
-
|
|
1925
|
-
|
|
1926
|
-
|
|
1927
|
-
|
|
1928
|
-
|
|
1929
|
-
|
|
1898
|
+
ctx.effect(() => {
|
|
1899
|
+
const cleanups = []
|
|
1900
|
+
try {
|
|
1901
|
+
if (ctx.slots && typeof ctx.slots.inject === 'function') {
|
|
1902
|
+
const un1 = ctx.slots.inject('plugins.item', () => ctx.slots.register(
|
|
1903
|
+
{
|
|
1904
|
+
name: 'plugins.item',
|
|
1905
|
+
id: ROW_ID,
|
|
1906
|
+
order: 60,
|
|
1907
|
+
label: () => t('title') || 'Subscriptions',
|
|
1908
|
+
locale: NS,
|
|
1909
|
+
inject: () => ({ ctx: ctx }),
|
|
1910
|
+
},
|
|
1911
|
+
PluginCard,
|
|
1912
|
+
))
|
|
1913
|
+
if (typeof un1 === 'function') cleanups.push(un1)
|
|
1914
|
+
|
|
1915
|
+
const un2 = ctx.slots.inject('plugins.row.config', () => ctx.slots.register(
|
|
1916
|
+
{
|
|
1917
|
+
name: 'plugins.row.config',
|
|
1918
|
+
key: ROW_CONFIG_KEY,
|
|
1919
|
+
locale: NS,
|
|
1920
|
+
inject: () => ({ ctx: ctx }),
|
|
1921
|
+
},
|
|
1922
|
+
PluginCard,
|
|
1923
|
+
))
|
|
1924
|
+
if (typeof un2 === 'function') cleanups.push(un2)
|
|
1925
|
+
|
|
1926
|
+
const un3 = ctx.slots.inject('settings.plugin.item', () => ctx.slots.register(
|
|
1927
|
+
{
|
|
1928
|
+
name: 'settings.plugin.item',
|
|
1929
|
+
key: NS,
|
|
1930
|
+
locale: NS,
|
|
1931
|
+
inject: () => ({ ctx: ctx }),
|
|
1932
|
+
},
|
|
1933
|
+
PluginCard,
|
|
1934
|
+
))
|
|
1935
|
+
if (typeof un3 === 'function') cleanups.push(un3)
|
|
1936
|
+
}
|
|
1937
|
+
} catch {
|
|
1938
|
+
// Slot system not available — expected on headless / non-web profiles.
|
|
1939
|
+
}
|
|
1940
|
+
return () => {
|
|
1941
|
+
for (const off of cleanups) {
|
|
1942
|
+
try { off() } catch { /* best-effort cleanup */ }
|
|
1943
|
+
}
|
|
1944
|
+
}
|
|
1945
|
+
}, 'dsh-subscriptions: settings slots')
|
|
1930
1946
|
}
|
|
1931
1947
|
|
|
1932
1948
|
// Hot provider switcher in the composer row: a click cycles
|
|
1933
1949
|
// the active subscription provider for the next request.
|
|
1934
|
-
|
|
1935
|
-
let labelEl = null
|
|
1936
|
-
const ORDER = ['codex', 'claude', 'grok', 'antigravity']
|
|
1950
|
+
const ORDER = ['codex', 'claude', 'grok', 'antigravity']
|
|
1937
1951
|
|
|
1938
1952
|
async function refreshLoggedIn() {
|
|
1939
1953
|
try {
|
|
@@ -1970,7 +1984,7 @@ const cssId = 'dsh-subscriptions/settings.module.css'
|
|
|
1970
1984
|
windows[key] = { samples: samples.filter((x) => x.at >= now - 24 * 60 * 60 * 1000).slice(-192) }
|
|
1971
1985
|
}
|
|
1972
1986
|
|
|
1973
|
-
function fcEstimate(key, remaining
|
|
1987
|
+
function fcEstimate(key, remaining) {
|
|
1974
1988
|
const rec = fcSamples.windows[key]
|
|
1975
1989
|
if (!rec) return { status: 'calibrating' }
|
|
1976
1990
|
const samples = rec.samples
|
|
@@ -2144,7 +2158,7 @@ const cssId = 'dsh-subscriptions/settings.module.css'
|
|
|
2144
2158
|
className: 'dsub-barFillGrad',
|
|
2145
2159
|
style: {
|
|
2146
2160
|
width: Math.min(100, Math.max(0, primPct)) + '%',
|
|
2147
|
-
background: primPct >= 90 ? 'var(--dsw-alias-state-error-primary
|
|
2161
|
+
background: primPct >= 90 ? 'var(--dsw-alias-state-error-primary)' : (primPct >= 50 ? 'var(--dsw-alias-state-warning-primary)' : 'var(--dsw-alias-state-success-primary)'),
|
|
2148
2162
|
},
|
|
2149
2163
|
}),
|
|
2150
2164
|
),
|
|
@@ -2271,7 +2285,7 @@ const cssId = 'dsh-subscriptions/settings.module.css'
|
|
|
2271
2285
|
try {
|
|
2272
2286
|
const btn = Array.from(document.querySelectorAll('button')).find((b) => (b.textContent || '').includes(t('title')))
|
|
2273
2287
|
if (btn) btn.click()
|
|
2274
|
-
} catch
|
|
2288
|
+
} catch { /* best-effort */ }
|
|
2275
2289
|
},
|
|
2276
2290
|
}, t('subsOpenSettings')),
|
|
2277
2291
|
),
|
|
@@ -2290,7 +2304,7 @@ const cssId = 'dsh-subscriptions/settings.module.css'
|
|
|
2290
2304
|
order: 15,
|
|
2291
2305
|
locale: NS,
|
|
2292
2306
|
},
|
|
2293
|
-
(
|
|
2307
|
+
() => React.createElement(ErrorBoundary, null, React.createElement(SubsPill, { t })),
|
|
2294
2308
|
),
|
|
2295
2309
|
)
|
|
2296
2310
|
}, 'dsh-subscriptions: subs pill')
|
|
@@ -2306,7 +2320,7 @@ const cssId = 'dsh-subscriptions/settings.module.css'
|
|
|
2306
2320
|
order: 5,
|
|
2307
2321
|
locale: NS,
|
|
2308
2322
|
},
|
|
2309
|
-
(
|
|
2323
|
+
() => React.createElement(ErrorBoundary, null, React.createElement(ComposerQuota, { t })),
|
|
2310
2324
|
),
|
|
2311
2325
|
)
|
|
2312
2326
|
}, 'dsh-subscriptions: composer quota')
|
package/lib/code-assist.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { randomUUID } from 'node:crypto'
|
|
2
2
|
import { httpError, readJson } from './wire.js'
|
|
3
|
-
import { validationFromLoadCodeAssist,
|
|
3
|
+
import { validationFromLoadCodeAssist, noticeFromLoadCodeAssist, validationRequiredError } from './google-validation.js'
|
|
4
4
|
import { asUsageSnapshot, usageWindows, deepestUsedPercent } from './usage.js'
|
|
5
5
|
|
|
6
6
|
export const CODE_ASSIST_PROD = 'https://cloudcode-pa.googleapis.com/v1internal'
|
package/lib/config-schema.js
CHANGED
|
@@ -56,6 +56,8 @@ export const Config = z.object({
|
|
|
56
56
|
.description('#84 Composer quota indicator mode: off, percent, bar or forecast (predictive runway from a sliding window).'),
|
|
57
57
|
codexRedirectUri: z.string().default(''),
|
|
58
58
|
codexBaseUrl: z.string().default(''),
|
|
59
|
+
codexClientVersion: z.string().default('')
|
|
60
|
+
.description('Codex CLI identity version for /models catalog gating (e.g. 0.157.1). Empty uses the built-in default.'),
|
|
59
61
|
claudeClientId: z.string().default(''),
|
|
60
62
|
claudeRedirectUri: z.string().default(''),
|
|
61
63
|
grokClientId: z.string().default(''),
|
|
@@ -64,10 +66,8 @@ export const Config = z.object({
|
|
|
64
66
|
grokClientVersion: z.string().default('')
|
|
65
67
|
.description('Grok CLI identity version header. Empty uses the built-in default.'),
|
|
66
68
|
antigravityClientId: z.string().default(''),
|
|
67
|
-
antigravityClientSecret: z.string().default('')
|
|
68
|
-
.description('Google Cloud OAuth Client Secret for Antigravity.'),
|
|
69
69
|
antigravityRedirectUri: z.string().default(''),
|
|
70
|
-
|
|
70
|
+
cascadingFallback: z.boolean().default(false)
|
|
71
71
|
.description('#349 Opt-in cross-vendor fallback when all accounts of a provider are exhausted or rate limited.'),
|
|
72
72
|
cascadingChain: z.any().default({})
|
|
73
73
|
.description('#349 Custom cross-vendor fallback chains (e.g. { claude: ["copilot", "cursor"] }).'),
|
|
@@ -82,11 +82,53 @@ export const Config = z.object({
|
|
|
82
82
|
})
|
|
83
83
|
|
|
84
84
|
export function publicConfig(cfg) {
|
|
85
|
+
if (!cfg || typeof cfg !== "object") return cfg
|
|
85
86
|
const clone = { ...cfg }
|
|
86
|
-
// #242: redact
|
|
87
|
-
// Secrets must never leave the server; the UI does not need them.
|
|
87
|
+
// #242, #252: redact secret fields before exposing config via GET /config.
|
|
88
88
|
for (const key of Object.keys(clone)) {
|
|
89
|
-
if (key.endsWith(
|
|
89
|
+
if (key.endsWith("ClientSecret") || key.endsWith("Secret")) {
|
|
90
|
+
clone[key] = clone[key] ? "••••••" : ""
|
|
91
|
+
}
|
|
90
92
|
}
|
|
93
|
+
|
|
94
|
+
// Redact credentials in slots (proxyUrl with user:pass)
|
|
95
|
+
if (Array.isArray(clone.slots)) {
|
|
96
|
+
clone.slots = clone.slots.map((s) => {
|
|
97
|
+
if (!s || typeof s !== "object" || !s.proxyUrl) return s
|
|
98
|
+
try {
|
|
99
|
+
const u = new URL(s.proxyUrl)
|
|
100
|
+
if (u.password || u.username) {
|
|
101
|
+
const auth = u.username ? (u.password ? `${u.username}:${u.password}` : u.username) : `:${u.password}`
|
|
102
|
+
const safeAuth = u.username ? `${u.username}:••••••` : `:••••••`
|
|
103
|
+
return { ...s, proxyUrl: s.proxyUrl.replace(auth + "@", safeAuth + "@") }
|
|
104
|
+
}
|
|
105
|
+
} catch { /* ignore non-standard url */ }
|
|
106
|
+
return s
|
|
107
|
+
})
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// Redact credentials in customVendors (apiKey, token, secret, Authorization headers)
|
|
111
|
+
if (Array.isArray(clone.customVendors)) {
|
|
112
|
+
clone.customVendors = clone.customVendors.map((cv) => {
|
|
113
|
+
if (!cv || typeof cv !== "object") return cv
|
|
114
|
+
const copy = { ...cv }
|
|
115
|
+
if (copy.apiKey) copy.apiKey = "••••••"
|
|
116
|
+
if (copy.token) copy.token = "••••••"
|
|
117
|
+
if (copy.secret) copy.secret = "••••••"
|
|
118
|
+
if (copy.clientSecret) copy.clientSecret = "••••••"
|
|
119
|
+
if (copy.headers && typeof copy.headers === "object") {
|
|
120
|
+
const h = { ...copy.headers }
|
|
121
|
+
for (const hk of Object.keys(h)) {
|
|
122
|
+
const lower = hk.toLowerCase()
|
|
123
|
+
if (lower === "authorization" || lower.includes("key") || lower.includes("token") || lower.includes("secret")) {
|
|
124
|
+
h[hk] = "••••••"
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
copy.headers = h
|
|
128
|
+
}
|
|
129
|
+
return copy
|
|
130
|
+
})
|
|
131
|
+
}
|
|
132
|
+
|
|
91
133
|
return clone
|
|
92
134
|
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { maskText } from './mask.js'
|
|
2
|
+
import { normalizeSlots } from './accounts.js'
|
|
3
|
+
import { PROVIDERS } from './refs.js'
|
|
4
|
+
|
|
5
|
+
export function scrubReport(v) {
|
|
6
|
+
if (typeof v === 'string') return maskText(v)
|
|
7
|
+
if (Array.isArray(v)) return v.map(scrubReport)
|
|
8
|
+
if (v && typeof v === 'object') {
|
|
9
|
+
const o = {}
|
|
10
|
+
for (const k of Object.keys(v)) o[k] = scrubReport(v[k])
|
|
11
|
+
return o
|
|
12
|
+
}
|
|
13
|
+
return v
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export async function createDiagnosticsReport({ live, store, history, NS, pkgVersion }) {
|
|
17
|
+
const cfg = live()
|
|
18
|
+
const slots = normalizeSlots(cfg.slots)
|
|
19
|
+
const mk = () => ({ loggedIn: false, slots: 0, configured: 0, cooldown: 0, proxy: 0, maxUsagePercent: null })
|
|
20
|
+
const providers = {}
|
|
21
|
+
for (const p of PROVIDERS) providers[p] = mk()
|
|
22
|
+
const logged = await store.loggedInProviders()
|
|
23
|
+
for (const slot of slots) {
|
|
24
|
+
const pv = providers[slot.provider] || (providers[slot.provider] = mk())
|
|
25
|
+
pv.slots++
|
|
26
|
+
if (slot.proxyUrl) pv.proxy++
|
|
27
|
+
try {
|
|
28
|
+
const info = await store.describeRef(slot.ref)
|
|
29
|
+
if (info.configured) pv.configured++
|
|
30
|
+
if (info.cooldownUntil && info.cooldownUntil > Date.now()) pv.cooldown++
|
|
31
|
+
if (info.usagePercent != null) pv.maxUsagePercent = Math.max(pv.maxUsagePercent || 0, Math.round(info.usagePercent))
|
|
32
|
+
} catch { /* best-effort */ }
|
|
33
|
+
}
|
|
34
|
+
for (const p of Object.keys(providers)) providers[p].loggedIn = logged.includes(p)
|
|
35
|
+
const rows = history.all()
|
|
36
|
+
const byStatus = {}
|
|
37
|
+
for (const r of rows) {
|
|
38
|
+
const k = (r.provider || '?') + ':' + (r.status || '?')
|
|
39
|
+
byStatus[k] = (byStatus[k] || 0) + 1
|
|
40
|
+
}
|
|
41
|
+
const lastErrors = rows.filter((r) => r.status && r.status >= 400).slice(0, 10)
|
|
42
|
+
.map((r) => ({ ts: r.ts, provider: r.provider, status: r.status, kind: r.kind || 'request', ms: r.ms || null }))
|
|
43
|
+
return scrubReport({
|
|
44
|
+
generatedAt: new Date().toISOString(),
|
|
45
|
+
plugin: NS + (pkgVersion ? ' v' + pkgVersion : ''),
|
|
46
|
+
runtime: { node: process.version, platform: process.platform, arch: process.arch, uptimeSec: Math.round(process.uptime()) },
|
|
47
|
+
providers,
|
|
48
|
+
requests: { total: rows.length, byStatus, lastErrors },
|
|
49
|
+
settings: {
|
|
50
|
+
cooldownMs: cfg.cooldownMs,
|
|
51
|
+
switchAtRemaining: cfg.switchAtRemaining,
|
|
52
|
+
probeIntervalMin: cfg.probeIntervalMin,
|
|
53
|
+
useWebCallback: !!cfg.useWebCallback,
|
|
54
|
+
autoLoopback: !!cfg.autoLoopback,
|
|
55
|
+
privacyMask: !!cfg.privacyMask,
|
|
56
|
+
customVendors: Array.isArray(cfg.customVendors) ? cfg.customVendors.length : 0,
|
|
57
|
+
proxySlots: slots.filter((s) => s.proxyUrl).length,
|
|
58
|
+
},
|
|
59
|
+
})
|
|
60
|
+
}
|
package/lib/history.js
CHANGED
|
@@ -9,10 +9,11 @@ import { homedir } from 'node:os'
|
|
|
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, debounceMs = 0) {
|
|
12
|
+
constructor(dir = DEFAULT_HISTORY_DIR, ttlMs = 7 * 24 * 60 * 60 * 1000, debounceMs = 0, maxEntries = 1000) {
|
|
13
13
|
this.dir = dir
|
|
14
14
|
this.ttlMs = ttlMs
|
|
15
15
|
this.debounceMs = debounceMs
|
|
16
|
+
this.maxEntries = maxEntries
|
|
16
17
|
this.path = join(dir, 'history.json')
|
|
17
18
|
this.rows = []
|
|
18
19
|
this._persistTimer = null
|
|
@@ -39,6 +40,9 @@ export class HistoryStore {
|
|
|
39
40
|
const cutoff = Date.now() - this.ttlMs
|
|
40
41
|
const before = this.rows.length
|
|
41
42
|
this.rows = this.rows.filter((r) => r && r.ts && r.ts >= cutoff)
|
|
43
|
+
if (this.rows.length > this.maxEntries) {
|
|
44
|
+
this.rows = this.rows.slice(0, this.maxEntries)
|
|
45
|
+
}
|
|
42
46
|
if (this.rows.length !== before) this._persist()
|
|
43
47
|
}
|
|
44
48
|
|
|
@@ -71,12 +75,28 @@ export class HistoryStore {
|
|
|
71
75
|
add(entry) {
|
|
72
76
|
if (!entry || typeof entry !== 'object') return this.rows.length
|
|
73
77
|
this.rows.unshift({ ts: Date.now(), ...entry })
|
|
78
|
+
if (this.rows.length > this.maxEntries) {
|
|
79
|
+
this.rows = this.rows.slice(0, this.maxEntries)
|
|
80
|
+
}
|
|
74
81
|
this._prune()
|
|
75
82
|
this._persist()
|
|
76
83
|
return this.rows.length
|
|
77
84
|
}
|
|
78
85
|
|
|
79
86
|
/** Last N entries (newest first). */
|
|
87
|
+
dispose() {
|
|
88
|
+
this.flush()
|
|
89
|
+
if (this._persistTimer) {
|
|
90
|
+
clearTimeout(this._persistTimer)
|
|
91
|
+
this._persistTimer = null
|
|
92
|
+
}
|
|
93
|
+
if (this._exitHandler && typeof process !== "undefined" && typeof process.removeListener === "function") {
|
|
94
|
+
process.removeListener("beforeExit", this._exitHandler)
|
|
95
|
+
this._exitHandler = null
|
|
96
|
+
}
|
|
97
|
+
this.rows = []
|
|
98
|
+
}
|
|
99
|
+
|
|
80
100
|
recent(n) {
|
|
81
101
|
return this.rows.slice(0, n)
|
|
82
102
|
}
|