@goodandready/dsh-subscriptions 0.6.72 → 0.6.82
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/README.zh.md +7 -0
- package/lib/accounts-view.js +97 -5
- package/lib/accounts.js +137 -11
- package/lib/adapter.js +3 -0
- package/lib/alerts.js +35 -0
- package/lib/analytics.js +31 -2
- package/lib/billing.js +99 -0
- package/lib/blob.js +18 -0
- package/lib/capabilities.js +208 -0
- package/lib/client.js +344 -17
- package/lib/config-schema.js +46 -4
- package/lib/diagnostic-wizard.js +2 -5
- package/lib/diagnostics.js +2 -2
- package/lib/error-buffer.js +2 -2
- package/lib/forecast.js +56 -0
- package/lib/headless-auth.js +379 -0
- package/lib/history.js +36 -0
- package/lib/http.js +26 -4
- package/lib/index.js +98 -11
- package/lib/jwt.js +84 -17
- package/lib/loopback.js +3 -2
- package/lib/mask.js +16 -0
- package/lib/oauth-pending.js +12 -0
- package/lib/pricing.js +89 -0
- package/lib/refs.js +2 -0
- package/lib/reset-clock.js +55 -0
- package/lib/rotate.js +18 -4
- package/lib/routes/accounts.js +131 -0
- package/lib/routes/oauth.js +44 -2
- package/lib/routes/proxy.js +14 -0
- package/lib/scope-inspector.js +51 -0
- package/lib/stream-rotate.js +31 -10
- package/lib/updater.js +21 -0
- package/lib/vault-sync.js +412 -0
- package/lib/vault.js +1 -1
- package/lib/vendor-factory.js +6 -2
- package/lib/vendors/antigravity.js +11 -2
- package/lib/vendors/claude.js +56 -8
- package/lib/vendors/codex.js +34 -6
- package/lib/vendors/copilot-cli.js +8 -1
- package/lib/vendors/ernie.js +9 -2
- package/lib/vendors/grok.js +66 -6
- package/lib/vendors/jetbrains.js +133 -13
- package/lib/vendors/perplexity.js +148 -17
- package/package.json +1 -1
package/lib/client.js
CHANGED
|
@@ -154,6 +154,24 @@ window.__ModuleLoader__.load({
|
|
|
154
154
|
"recentErrorsDesc": "Last 20 runtime failures across model streams and providers (sanitized).",
|
|
155
155
|
"recentErrorsClear": "Clear Log",
|
|
156
156
|
"recentErrorsEmpty": "No recent errors recorded",
|
|
157
|
+
"atRestEncrypted": "Encrypted at Rest (AES-256-GCM)",
|
|
158
|
+
"atRestEncryptedTitle": "Credentials are protected at rest with master key encryption.",
|
|
159
|
+
"maxConcurrencySlotTitle": "Max parallel streams for this account slot (default: 1)",
|
|
160
|
+
"concurrencyQueueHint": "Active and queued parallel requests for this account",
|
|
161
|
+
"activeStreams": "active",
|
|
162
|
+
"queuedStreams": "queued",
|
|
163
|
+
"pacingRunwayHint": "Estimated time before quota exhausts at current pace",
|
|
164
|
+
"runwayPrefix": "Runway",
|
|
165
|
+
"billingAlertHint": "Billing status notice from provider",
|
|
166
|
+
"renewalHint": "Subscription billing cycle renewal date",
|
|
167
|
+
"valueDeliveredHint": "Equivalent official API cost of consumed tokens vs subscription price",
|
|
168
|
+
"capabilitiesHint": "Subscription capabilities, context window and model features",
|
|
169
|
+
"orPasteVaultPayload": "or paste vault payload below",
|
|
170
|
+
"workspaceTitle": "Organization / Workspace",
|
|
171
|
+
"workspaceSelect": "Select active organization/workspace",
|
|
172
|
+
"personalWorkspace": "Personal Default",
|
|
173
|
+
"windowResetsAt": "Window resets at",
|
|
174
|
+
"quotaRecovery": "Quota recovery",
|
|
157
175
|
"toastCooldownRecovered": "is now available (cooldown expired)",
|
|
158
176
|
"modelsCatalogTitle": "Curated Subscription Models",
|
|
159
177
|
"modelsCatalogDesc": "Available model catalog across active subscriptions in DeepSeek Harness.",
|
|
@@ -265,6 +283,11 @@ window.__ModuleLoader__.load({
|
|
|
265
283
|
"instructionsTitle": "How to obtain token",
|
|
266
284
|
"close": "Close",
|
|
267
285
|
"reconnectRequired": "Reconnect required",
|
|
286
|
+
"reconnectNow": "Re-authenticate now",
|
|
287
|
+
"scopeWarning": "OAuth scope warning",
|
|
288
|
+
"reconnectScope": "Re-grant scopes",
|
|
289
|
+
"sessionRevokedBadge": "Revoked / Expired",
|
|
290
|
+
"sessionRevokedWarning": "Session expired / revoked — re-authorization required",
|
|
268
291
|
"manualTitle": "If the browser did not come back",
|
|
269
292
|
"updateChecking": "Checking updates…",
|
|
270
293
|
"updateUpToDate": "Up to date",
|
|
@@ -309,6 +332,19 @@ window.__ModuleLoader__.load({
|
|
|
309
332
|
"toastReleased": "Account active and healthy",
|
|
310
333
|
"toastQuotaWarning": "Quota threshold exceeded",
|
|
311
334
|
"enableToasts": "In-app toast alerts",
|
|
335
|
+
"headlessTip": "Headless / Remote Server?",
|
|
336
|
+
"headlessSshCommand": "SSH Port Forward Command:",
|
|
337
|
+
"headlessCopySsh": "Copy SSH Command",
|
|
338
|
+
"headlessCopied": "Copied!",
|
|
339
|
+
"headlessScanQr": "Scan with Mobile to Authenticate:",
|
|
340
|
+
"vaultSyncTitle": "Automated Remote Vault Sync",
|
|
341
|
+
"vaultSyncPush": "Sync Now (Push)",
|
|
342
|
+
"vaultSyncPull": "Sync Now (Pull)",
|
|
343
|
+
"vaultSyncPushOk": "Vault successfully pushed to remote storage",
|
|
344
|
+
"vaultSyncPullOk": "Vault successfully pulled and merged from remote storage",
|
|
345
|
+
"vaultSyncBackend": "Backend:",
|
|
346
|
+
"vaultSyncLastPush": "Last Push:",
|
|
347
|
+
"vaultSyncLastPull": "Last Pull:",
|
|
312
348
|
"groupQueue": "Concurrency & Request Queue",
|
|
313
349
|
"enableQueue": "Enable request queue",
|
|
314
350
|
"enableQueueHint": "Queue excess requests when account hits concurrency limit instead of failing with 429",
|
|
@@ -450,6 +486,24 @@ window.__ModuleLoader__.load({
|
|
|
450
486
|
"recentErrorsDesc": "最近 20 条流式传输与 API 调用失败记录(已脱敏)。",
|
|
451
487
|
"recentErrorsClear": "清空记录",
|
|
452
488
|
"recentErrorsEmpty": "暂无错误记录",
|
|
489
|
+
"atRestEncrypted": "静态存储加密 (AES-256-GCM)",
|
|
490
|
+
"atRestEncryptedTitle": "凭据已通过主密钥进行静态 AES-256-GCM 加密保护。",
|
|
491
|
+
"maxConcurrencySlotTitle": "当前账户槽位最大并发请求数(默认:1)",
|
|
492
|
+
"concurrencyQueueHint": "当前账户槽位进行中与排队中的并发请求",
|
|
493
|
+
"activeStreams": "活跃",
|
|
494
|
+
"queuedStreams": "排队中",
|
|
495
|
+
"pacingRunwayHint": "当前调用速度下的预计剩余可用时间",
|
|
496
|
+
"runwayPrefix": "可用时长",
|
|
497
|
+
"billingAlertHint": "服务商账单状态提示",
|
|
498
|
+
"renewalHint": "订阅账单周期续费日期",
|
|
499
|
+
"valueDeliveredHint": "消耗 Token 对应的官方 API 价值对比订阅费用",
|
|
500
|
+
"capabilitiesHint": "订阅账号模型上下文窗口与特权特性",
|
|
501
|
+
"orPasteVaultPayload": "或在下方粘贴保险库文本",
|
|
502
|
+
"workspaceTitle": "工作区与组织",
|
|
503
|
+
"workspaceSelect": "选择生效的工作区或组织",
|
|
504
|
+
"personalWorkspace": "个人默认",
|
|
505
|
+
"windowResetsAt": "额度重置于",
|
|
506
|
+
"quotaRecovery": "额度恢复",
|
|
453
507
|
"toastCooldownRecovered": "已解除冷却恢复可用",
|
|
454
508
|
"modelsCatalogTitle": "精选订阅模型",
|
|
455
509
|
"modelsCatalogDesc": "当前 DeepSeek Harness 活跃订阅下可用模型一览。",
|
|
@@ -561,6 +615,11 @@ window.__ModuleLoader__.load({
|
|
|
561
615
|
"instructionsTitle": "获取令牌指引",
|
|
562
616
|
"close": "关闭",
|
|
563
617
|
"reconnectRequired": "需要重新连接",
|
|
618
|
+
"reconnectNow": "立即重新认证",
|
|
619
|
+
"scopeWarning": "OAuth权限范围警告",
|
|
620
|
+
"reconnectScope": "重新授权权限",
|
|
621
|
+
"sessionRevokedBadge": "已失效 / 过期",
|
|
622
|
+
"sessionRevokedWarning": "会话已过期或失效 — 需要重新授权",
|
|
564
623
|
"manualTitle": "若浏览器未能自动跳转回调",
|
|
565
624
|
"updateChecking": "检查更新中…",
|
|
566
625
|
"updateUpToDate": "已是最新版本",
|
|
@@ -602,6 +661,19 @@ window.__ModuleLoader__.load({
|
|
|
602
661
|
"releaseQuarantineTitle": "立即解除账户隔离状态",
|
|
603
662
|
"resetsIn": "重置倒计时",
|
|
604
663
|
"toastQuarantined": "账户进入隔离期",
|
|
664
|
+
"headlessTip": "无头环境 / 远程服务器?",
|
|
665
|
+
"headlessSshCommand": "SSH 端口转发命令:",
|
|
666
|
+
"headlessCopySsh": "复制 SSH 命令",
|
|
667
|
+
"headlessCopied": "已复制!",
|
|
668
|
+
"headlessScanQr": "使用手机扫描以登录:",
|
|
669
|
+
"vaultSyncTitle": "远程加密保管库自动同步",
|
|
670
|
+
"vaultSyncPush": "立即同步 (推送)",
|
|
671
|
+
"vaultSyncPull": "立即同步 (拉取)",
|
|
672
|
+
"vaultSyncPushOk": "保管库已成功推送到远程存储",
|
|
673
|
+
"vaultSyncPullOk": "已从远程存储拉取并合并保管库",
|
|
674
|
+
"vaultSyncBackend": "存储后端:",
|
|
675
|
+
"vaultSyncLastPush": "上次推送:",
|
|
676
|
+
"vaultSyncLastPull": "上次拉取:",
|
|
605
677
|
"toastReleased": "账户已解除隔离恢复使用",
|
|
606
678
|
"toastQuotaWarning": "额度预警",
|
|
607
679
|
"enableToasts": "应用内弹窗提醒",
|
|
@@ -678,6 +750,11 @@ window.__ModuleLoader__.load({
|
|
|
678
750
|
else if (p.indexOf('team') >= 0) label = 'Team'
|
|
679
751
|
else if (p.indexOf('plus') >= 0) label = 'Plus'
|
|
680
752
|
else if (p.indexOf('enterp') >= 0) label = 'Enterprise'
|
|
753
|
+
} else if (provider === 'claude') {
|
|
754
|
+
if (p.indexOf('enterp') >= 0) label = 'Enterprise'
|
|
755
|
+
else if (p.indexOf('team') >= 0) label = 'Team'
|
|
756
|
+
else if (p.indexOf('max') >= 0) label = 'Max'
|
|
757
|
+
else if (p.indexOf('pro') >= 0) label = 'Pro'
|
|
681
758
|
} else if (provider === 'grok') {
|
|
682
759
|
if (p.indexOf('super') >= 0) label = 'SuperGrok'
|
|
683
760
|
else if (p.indexOf('plus') >= 0) label = 'X Premium+'
|
|
@@ -689,6 +766,10 @@ window.__ModuleLoader__.load({
|
|
|
689
766
|
label = 'Coding Plan'
|
|
690
767
|
} else if (provider === 'glm') {
|
|
691
768
|
label = '150% Boost'
|
|
769
|
+
} else if (provider === 'perplexity') {
|
|
770
|
+
label = 'Pro'
|
|
771
|
+
} else if (provider === 'jetbrains') {
|
|
772
|
+
label = 'AI Assistant'
|
|
692
773
|
}
|
|
693
774
|
return label ? h('span', { className: 'dsub-planBadge' }, label) : null
|
|
694
775
|
}
|
|
@@ -718,22 +799,27 @@ window.__ModuleLoader__.load({
|
|
|
718
799
|
const ms = (props.resetAt || 0) - now
|
|
719
800
|
if (ms <= 0) return null
|
|
720
801
|
const total = Math.floor(ms / 1000)
|
|
802
|
+
const d = new Date(props.resetAt)
|
|
803
|
+
const pad = (n) => String(n).padStart(2, '0')
|
|
804
|
+
const timeStr = pad(d.getHours()) + ':' + pad(d.getMinutes())
|
|
805
|
+
const relStr = formatRelativeReset(props.resetAt, t('lang'), now)
|
|
721
806
|
if (total > 3600) {
|
|
722
807
|
return h('span', { className: 'dsub-sub', style: { fontVariantNumeric: 'tabular-nums' } },
|
|
723
|
-
t('
|
|
808
|
+
'⏳ ' + t('windowResetsAt') + ' ' + timeStr + ' (' + relStr + ')')
|
|
724
809
|
}
|
|
725
810
|
const m = Math.floor(total / 60)
|
|
726
811
|
const sec = total % 60
|
|
727
|
-
const pad = (n) => String(n).padStart(2, '0')
|
|
728
812
|
return h('span', { className: 'dsub-sub', style: { fontVariantNumeric: 'tabular-nums' } },
|
|
729
|
-
t('
|
|
813
|
+
'⏳ ' + t('windowResetsAt') + ' ' + timeStr + ' (' + relStr + ' · ' + pad(m) + ':' + pad(sec) + ')')
|
|
730
814
|
}
|
|
731
815
|
|
|
732
816
|
function badgeFor(account, t) {
|
|
733
817
|
if (!account || !account.configured) return t('notConnected')
|
|
818
|
+
if (account.isRevoked) return t('sessionRevokedBadge')
|
|
734
819
|
if (account.validationUrl) return t('verifyAccount')
|
|
735
820
|
// Issue 303: a quarantined account says why it is parked.
|
|
736
821
|
if (account.quarantineReason && account.quarantineUntil && account.quarantineUntil > Date.now()) {
|
|
822
|
+
if (account.quarantineReason === 'SESSION_REVOKED' || account.quarantineReason === 'REVOKED') return t('sessionRevokedBadge')
|
|
737
823
|
return account.quarantineReason === 'VERIFY' ? t('verifyAccount') : String(account.quarantineReason)
|
|
738
824
|
}
|
|
739
825
|
if (account.cooldownUntil && account.cooldownUntil > Date.now()) {
|
|
@@ -885,6 +971,53 @@ window.__ModuleLoader__.load({
|
|
|
885
971
|
const [smoke, setSmoke] = React.useState({ testing: false, result: null })
|
|
886
972
|
const [updater, setUpdater] = React.useState({ checking: false, updating: false, status: null, message: '' })
|
|
887
973
|
const [backupModal, setBackupModal] = React.useState(null)
|
|
974
|
+
const [vaultSyncInfo, setVaultSyncInfo] = React.useState(null)
|
|
975
|
+
const [vaultSyncLoading, setVaultSyncLoading] = React.useState(false)
|
|
976
|
+
const [headlessInfo, setHeadlessInfo] = React.useState({})
|
|
977
|
+
const [copiedSsh, setCopiedSsh] = React.useState({})
|
|
978
|
+
|
|
979
|
+
const loadVaultSyncStatus = () => {
|
|
980
|
+
fetch('/dsh-subscriptions/vault/sync/status')
|
|
981
|
+
.then((r) => r.json())
|
|
982
|
+
.then((d) => { if (d && d.ok) setVaultSyncInfo(d.status) })
|
|
983
|
+
.catch(() => {})
|
|
984
|
+
}
|
|
985
|
+
|
|
986
|
+
const doVaultSyncPush = async () => {
|
|
987
|
+
setBackupErr(''); setBackupInfo(''); setVaultSyncLoading(true)
|
|
988
|
+
try {
|
|
989
|
+
const res = await fetch('/dsh-subscriptions/vault/sync/push', {
|
|
990
|
+
method: 'POST',
|
|
991
|
+
headers: { 'Content-Type': 'application/json' },
|
|
992
|
+
})
|
|
993
|
+
const d = await res.json().catch(() => ({}))
|
|
994
|
+
if (!res.ok || !d.ok) throw new Error((d.error && d.error.message) || ('HTTP ' + res.status))
|
|
995
|
+
setBackupInfo(t('vaultSyncPushOk'))
|
|
996
|
+
if (d.status) setVaultSyncInfo(d.status)
|
|
997
|
+
} catch (e) {
|
|
998
|
+
setBackupErr(cleanErrorMessage(e.message || e))
|
|
999
|
+
}
|
|
1000
|
+
setVaultSyncLoading(false)
|
|
1001
|
+
}
|
|
1002
|
+
|
|
1003
|
+
const doVaultSyncPull = async () => {
|
|
1004
|
+
setBackupErr(''); setBackupInfo(''); setVaultSyncLoading(true)
|
|
1005
|
+
try {
|
|
1006
|
+
const res = await fetch('/dsh-subscriptions/vault/sync/pull', {
|
|
1007
|
+
method: 'POST',
|
|
1008
|
+
headers: { 'Content-Type': 'application/json' },
|
|
1009
|
+
})
|
|
1010
|
+
const d = await res.json().catch(() => ({}))
|
|
1011
|
+
if (!res.ok || !d.ok) throw new Error((d.error && d.error.message) || ('HTTP ' + res.status))
|
|
1012
|
+
setBackupInfo(t('vaultSyncPullOk') + (d.result ? ' (' + d.result.slotCount + ' slots)' : ''))
|
|
1013
|
+
if (d.status) setVaultSyncInfo(d.status)
|
|
1014
|
+
if (d.accounts) setAccounts(d.accounts)
|
|
1015
|
+
await reload()
|
|
1016
|
+
} catch (e) {
|
|
1017
|
+
setBackupErr(cleanErrorMessage(e.message || e))
|
|
1018
|
+
}
|
|
1019
|
+
setVaultSyncLoading(false)
|
|
1020
|
+
}
|
|
888
1021
|
const [backupPass, setBackupPass] = React.useState('')
|
|
889
1022
|
const [backupData, setBackupData] = React.useState('')
|
|
890
1023
|
const [backupErr, setBackupErr] = React.useState('')
|
|
@@ -959,7 +1092,7 @@ window.__ModuleLoader__.load({
|
|
|
959
1092
|
const url = URL.createObjectURL(blob)
|
|
960
1093
|
const a = document.createElement('a')
|
|
961
1094
|
a.href = url
|
|
962
|
-
a.download = 'dsh-subscriptions-
|
|
1095
|
+
a.download = 'dsh-subscriptions-vault.enc'
|
|
963
1096
|
document.body.appendChild(a)
|
|
964
1097
|
a.click()
|
|
965
1098
|
document.body.removeChild(a)
|
|
@@ -1176,6 +1309,8 @@ window.__ModuleLoader__.load({
|
|
|
1176
1309
|
const res = await fetch('/dsh-subscriptions/oauth/start?provider=' + encodeURIComponent(provider) + '&index=' + encodeURIComponent(index), { cache: 'no-store' })
|
|
1177
1310
|
const data = await res.json().catch(() => ({}))
|
|
1178
1311
|
if (!res.ok) throw new Error((data.error && data.error.message) || ('HTTP ' + res.status))
|
|
1312
|
+
const key = provider + ':' + index
|
|
1313
|
+
if (data.headless) setHeadlessInfo((m) => Object.assign({}, m, { [key]: data.headless }))
|
|
1179
1314
|
if (data.url) window.open(data.url, '_blank', 'noopener')
|
|
1180
1315
|
}
|
|
1181
1316
|
|
|
@@ -1191,6 +1326,7 @@ window.__ModuleLoader__.load({
|
|
|
1191
1326
|
})
|
|
1192
1327
|
const data = await res.json().catch(() => ({}))
|
|
1193
1328
|
if (!res.ok) throw new Error((data.error && data.error.message) || ('HTTP ' + res.status))
|
|
1329
|
+
if (data.headless) setHeadlessInfo((m) => Object.assign({}, m, { [key]: data.headless }))
|
|
1194
1330
|
setDevice((m) => Object.assign({}, m, { [key]: { state: data.state, userCode: data.userCode, authUrl: data.authUrl, intervalMs: data.intervalMs || 5000, status: 'pending' } }))
|
|
1195
1331
|
}
|
|
1196
1332
|
|
|
@@ -1353,6 +1489,22 @@ window.__ModuleLoader__.load({
|
|
|
1353
1489
|
setTokenDraft((d) => Object.assign({}, d, { [provider + ':' + index]: '' }))
|
|
1354
1490
|
}
|
|
1355
1491
|
|
|
1492
|
+
const switchOrg = async (provider, index, organizationId) => {
|
|
1493
|
+
setErr('')
|
|
1494
|
+
try {
|
|
1495
|
+
const res = await fetch('/dsh-subscriptions/accounts/switch-organization', {
|
|
1496
|
+
method: 'POST',
|
|
1497
|
+
headers: { 'Content-Type': 'application/json' },
|
|
1498
|
+
body: JSON.stringify({ provider, index, organizationId }),
|
|
1499
|
+
})
|
|
1500
|
+
const data = await res.json().catch(() => ({}))
|
|
1501
|
+
if (!res.ok) throw new Error((data.error && data.error.message) || ('HTTP ' + res.status))
|
|
1502
|
+
if (data.accounts) setAccounts(data.accounts)
|
|
1503
|
+
} catch (e) {
|
|
1504
|
+
setErr(cleanErrorMessage(e.message || e))
|
|
1505
|
+
}
|
|
1506
|
+
}
|
|
1507
|
+
|
|
1356
1508
|
const addSlot = (provider) => {
|
|
1357
1509
|
const used = slots.filter((s) => s.provider === provider).map((s) => s.index)
|
|
1358
1510
|
let index = 1
|
|
@@ -1958,8 +2110,8 @@ window.__ModuleLoader__.load({
|
|
|
1958
2110
|
h('button', { type: 'button', className: 'dsub-mini', onClick: () => runWizard(null, null, null, false) }, '🩺 ' + t('wizardAll')),
|
|
1959
2111
|
h('button', { type: 'button', className: 'dsub-mini', onClick: genDiag }, t('diagGenerate')),
|
|
1960
2112
|
h('button', { type: 'button', className: 'dsub-mini', onClick: runAnalyzeSession }, analyzingCache ? '…' : t('cacheAnalyze')),
|
|
1961
|
-
h('button', { type: 'button', className: 'dsub-mini', onClick: () => { setBackupModal('export'); setBackupErr(''); setBackupInfo('') } }, '🔒 ' + t('backupExport')),
|
|
1962
|
-
h('button', { type: 'button', className: 'dsub-mini', onClick: () => { setBackupModal('import'); setBackupErr(''); setBackupInfo('') } }, '📥 ' + t('backupImport')),
|
|
2113
|
+
h('button', { type: 'button', className: 'dsub-mini', onClick: () => { setBackupModal('export'); setBackupErr(''); setBackupInfo(''); loadVaultSyncStatus() } }, '🔒 ' + t('backupExport')),
|
|
2114
|
+
h('button', { type: 'button', className: 'dsub-mini', onClick: () => { setBackupModal('import'); setBackupErr(''); setBackupInfo(''); loadVaultSyncStatus() } }, '📥 ' + t('backupImport')),
|
|
1963
2115
|
h('a', { href: 'https://github.com/GooDAnDReaDY/dsh-subscriptions/issues', target: '_blank', rel: 'noopener noreferrer', className: 'dsub-mini' }, t('diagIssues')),
|
|
1964
2116
|
),
|
|
1965
2117
|
h('div', { className: 'dsub-sub' }, t('diagHint')),
|
|
@@ -2086,6 +2238,24 @@ window.__ModuleLoader__.load({
|
|
|
2086
2238
|
backupModal === 'export' ? t('backupExportHint') : t('backupImportHint')
|
|
2087
2239
|
),
|
|
2088
2240
|
h('div', { style: { display: 'flex', flexDirection: 'column', gap: '8px', marginTop: '8px' } },
|
|
2241
|
+
backupModal === 'import' ? h('div', { className: 'dsub-row', style: { gap: '8px', alignItems: 'center' } },
|
|
2242
|
+
h('input', {
|
|
2243
|
+
type: 'file',
|
|
2244
|
+
accept: '.enc,.txt',
|
|
2245
|
+
className: 'dsub-mini',
|
|
2246
|
+
style: { maxWidth: '220px' },
|
|
2247
|
+
onChange: (e) => {
|
|
2248
|
+
const f = e.target.files && e.target.files[0]
|
|
2249
|
+
if (!f) return
|
|
2250
|
+
const reader = new window.FileReader()
|
|
2251
|
+
reader.onload = (evt) => {
|
|
2252
|
+
if (evt.target && evt.target.result) setBackupData(String(evt.target.result))
|
|
2253
|
+
}
|
|
2254
|
+
reader.readAsText(f)
|
|
2255
|
+
},
|
|
2256
|
+
}),
|
|
2257
|
+
h('span', { className: 'dsub-sub' }, t('orPasteVaultPayload'))
|
|
2258
|
+
) : null,
|
|
2089
2259
|
backupModal === 'import' ? h('textarea', {
|
|
2090
2260
|
className: 'dsub-diag',
|
|
2091
2261
|
style: { width: '100%', height: '80px', fontFamily: 'monospace', fontSize: '11px' },
|
|
@@ -2127,7 +2297,34 @@ window.__ModuleLoader__.load({
|
|
|
2127
2297
|
onClick: downloadBackupFile,
|
|
2128
2298
|
}, t('backupDownload')),
|
|
2129
2299
|
h('pre', { className: 'dsub-diag', style: { maxHeight: '60px', overflow: 'hidden', textOverflow: 'ellipsis' } }, backupData)
|
|
2130
|
-
) : null
|
|
2300
|
+
) : null,
|
|
2301
|
+
h('div', {
|
|
2302
|
+
className: 'dsub-panel-box',
|
|
2303
|
+
style: { marginTop: 10, padding: '10px 12px', borderTop: '1px solid var(--dsw-alias-border-l2)' }
|
|
2304
|
+
},
|
|
2305
|
+
h('div', { className: 'dsub-panel-title' },
|
|
2306
|
+
h('span', null, '☁️ ' + t('vaultSyncTitle')),
|
|
2307
|
+
h('div', { style: { display: 'flex', gap: 6 } },
|
|
2308
|
+
h('button', {
|
|
2309
|
+
type: 'button',
|
|
2310
|
+
className: 'dsub-mini',
|
|
2311
|
+
disabled: vaultSyncLoading,
|
|
2312
|
+
onClick: doVaultSyncPush,
|
|
2313
|
+
}, vaultSyncLoading ? '…' : t('vaultSyncPush')),
|
|
2314
|
+
h('button', {
|
|
2315
|
+
type: 'button',
|
|
2316
|
+
className: 'dsub-mini',
|
|
2317
|
+
disabled: vaultSyncLoading,
|
|
2318
|
+
onClick: doVaultSyncPull,
|
|
2319
|
+
}, vaultSyncLoading ? '…' : t('vaultSyncPull'))
|
|
2320
|
+
)
|
|
2321
|
+
),
|
|
2322
|
+
vaultSyncInfo ? h('div', { className: 'dsub-sub', style: { marginTop: 4 } },
|
|
2323
|
+
t('vaultSyncBackend') + ' ' + (vaultSyncInfo.backend || 'webdav') + ' · ' +
|
|
2324
|
+
t('vaultSyncLastPush') + ' ' + (vaultSyncInfo.lastPushAt ? new Date(vaultSyncInfo.lastPushAt).toLocaleTimeString() : 'never') + ' · ' +
|
|
2325
|
+
t('vaultSyncLastPull') + ' ' + (vaultSyncInfo.lastPullAt ? new Date(vaultSyncInfo.lastPullAt).toLocaleTimeString() : 'never')
|
|
2326
|
+
) : null
|
|
2327
|
+
)
|
|
2131
2328
|
)
|
|
2132
2329
|
) : null,
|
|
2133
2330
|
),
|
|
@@ -2141,20 +2338,30 @@ window.__ModuleLoader__.load({
|
|
|
2141
2338
|
const account = accountOf(row.slot.provider, row.slot.index)
|
|
2142
2339
|
const key = row.slot.provider + ':' + row.slot.index
|
|
2143
2340
|
const on = !!account.configured
|
|
2341
|
+
const avatarEl = (account.avatarUrl ? h('img', {
|
|
2342
|
+
src: account.avatarUrl,
|
|
2343
|
+
alt: account.email || account.label || 'Avatar',
|
|
2344
|
+
className: 'dsub-avatar',
|
|
2345
|
+
onError: (e) => { e.target.style.display = 'none' },
|
|
2346
|
+
}) : (account.email ? h('span', {
|
|
2347
|
+
className: 'dsub-avatar dsub-avatar-initials',
|
|
2348
|
+
title: account.email,
|
|
2349
|
+
}, (account.email.charAt(0) || '?').toUpperCase()) : null))
|
|
2144
2350
|
return h('div', { className: 'dsub-card', key: key },
|
|
2145
|
-
h('div', { className: 'dsub-row' },
|
|
2351
|
+
h('div', { className: 'dsub-row', style: { alignItems: 'center' } },
|
|
2352
|
+
avatarEl,
|
|
2146
2353
|
h('input', {
|
|
2147
2354
|
className: 'dsub-grow',
|
|
2148
2355
|
value: row.slot.label || '',
|
|
2149
|
-
placeholder: account.label || (t('accountLabel') + ' ' + row.slot.index),
|
|
2356
|
+
placeholder: account.email || account.label || (t('accountLabel') + ' ' + row.slot.index),
|
|
2150
2357
|
onChange: (e) => {
|
|
2151
2358
|
const next = slots.slice()
|
|
2152
2359
|
next[row.i] = Object.assign({}, next[row.i], { label: e.target.value })
|
|
2153
2360
|
setSlots(next)
|
|
2154
2361
|
},
|
|
2155
2362
|
}),
|
|
2156
|
-
h('span', { className: 'dsub-badge' + (account.validationUrl ? ' dsub-badge-warn' : (on ? ' dsub-badge-on' : '')) }, badgeFor(account, t)),
|
|
2157
|
-
normalizePlanBadge(row.slot.provider, account.plan || (account.quota && account.quota.plan)),
|
|
2363
|
+
h('span', { className: 'dsub-badge' + (account.isRevoked ? ' dsub-badge-bad' : (account.validationUrl ? ' dsub-badge-warn' : (on ? ' dsub-badge-on' : ''))) }, badgeFor(account, t)),
|
|
2364
|
+
normalizePlanBadge(row.slot.provider, account.plan || account.paidTierName || (account.quota && account.quota.plan)),
|
|
2158
2365
|
h('button', {
|
|
2159
2366
|
type: 'button', className: 'dsub-mini',
|
|
2160
2367
|
onClick: () => connect(row.slot.provider, row.slot.index).catch((e) => setErr(cleanErrorMessage(e.message || e))),
|
|
@@ -2233,8 +2440,8 @@ window.__ModuleLoader__.load({
|
|
|
2233
2440
|
h('span', { className: 'dsub-sub', style: { color: 'var(--dsw-alias-state-error-primary)', fontVariantNumeric: 'tabular-nums' } },
|
|
2234
2441
|
'⛔ ' + (account.quarantineReason || 'QUARANTINE') + ' · ' + t('resetsIn') + ' ' + formatRelativeReset(account.quarantineUntil, t('lang'), Date.now())
|
|
2235
2442
|
),
|
|
2236
|
-
) : (account && account.quota && account.quota.resetAt && Number(account.quota.resetAt) > Date.now()) ? h('div', { className: 'dsub-row', style: { marginTop: 4 } },
|
|
2237
|
-
h(ResetCountdown, { resetAt: account.quota.resetAt }),
|
|
2443
|
+
) : (account && (account.resetAt || (account.quota && account.quota.resetAt)) && Number(account.resetAt || account.quota.resetAt) > Date.now()) ? h('div', { className: 'dsub-row', style: { marginTop: 4 } },
|
|
2444
|
+
h(ResetCountdown, { resetAt: account.resetAt || account.quota.resetAt }),
|
|
2238
2445
|
) : null,
|
|
2239
2446
|
h('div', { className: 'dsub-manual' },
|
|
2240
2447
|
h('span', { className: 'dsub-sub' }, t('manualTitle')),
|
|
@@ -2250,6 +2457,36 @@ window.__ModuleLoader__.load({
|
|
|
2250
2457
|
onClick: () => complete(row.slot.provider, row.slot.index).catch((e) => setErr(cleanErrorMessage(e.message || e))),
|
|
2251
2458
|
}, t('submitCode')),
|
|
2252
2459
|
),
|
|
2460
|
+
(headlessInfo[key] ? h('div', {
|
|
2461
|
+
className: 'dsub-panel-box',
|
|
2462
|
+
style: { marginTop: 6, padding: '8px 12px', background: 'var(--dsw-alias-bg-layer-2)' }
|
|
2463
|
+
},
|
|
2464
|
+
h('div', { style: { fontWeight: 600, fontSize: '11px', display: 'flex', justifyContent: 'space-between', alignItems: 'center' } },
|
|
2465
|
+
h('span', null, '🌐 ' + t('headlessTip')),
|
|
2466
|
+
h('button', {
|
|
2467
|
+
type: 'button',
|
|
2468
|
+
className: 'dsub-mini',
|
|
2469
|
+
onClick: () => {
|
|
2470
|
+
try {
|
|
2471
|
+
navigator.clipboard.writeText(headlessInfo[key].sshTunnelCommand)
|
|
2472
|
+
setCopiedSsh((m) => Object.assign({}, m, { [key]: true }))
|
|
2473
|
+
setTimeout(() => setCopiedSsh((m) => Object.assign({}, m, { [key]: false })), 2000)
|
|
2474
|
+
} catch { /* ignore */ }
|
|
2475
|
+
}
|
|
2476
|
+
}, copiedSsh[key] ? ('✓ ' + t('headlessCopied')) : ('📋 ' + t('headlessCopySsh')))
|
|
2477
|
+
),
|
|
2478
|
+
h('div', { style: { fontFamily: 'monospace', fontSize: '11px', margin: '4px 0', wordBreak: 'break-all' } },
|
|
2479
|
+
headlessInfo[key].sshTunnelCommand
|
|
2480
|
+
),
|
|
2481
|
+
headlessInfo[key].svgDataUrl ? h('div', { style: { marginTop: 6, textAlign: 'center' } },
|
|
2482
|
+
h('div', { className: 'dsub-sub', style: { marginBottom: 4 } }, t('headlessScanQr')),
|
|
2483
|
+
h('img', {
|
|
2484
|
+
src: headlessInfo[key].svgDataUrl,
|
|
2485
|
+
alt: 'QR Code',
|
|
2486
|
+
style: { width: '130px', height: '130px', borderRadius: '4px', background: '#fff', padding: '4px' }
|
|
2487
|
+
})
|
|
2488
|
+
) : null
|
|
2489
|
+
) : null),
|
|
2253
2490
|
h('div', { className: 'dsub-row', style: { justifyContent: 'space-between' } },
|
|
2254
2491
|
h('button', {
|
|
2255
2492
|
type: 'button',
|
|
@@ -2348,6 +2585,26 @@ window.__ModuleLoader__.load({
|
|
|
2348
2585
|
disabled: checking['proxy:' + key],
|
|
2349
2586
|
onClick: () => doProxyCheck(row.slot.provider, row.slot.index).catch((e) => setErr(cleanErrorMessage(e.message || e))),
|
|
2350
2587
|
}, checking['proxy:' + key] ? '\u2026' : t('proxyCheck')),
|
|
2588
|
+
h('span', {
|
|
2589
|
+
style: { fontSize: '11px', color: 'var(--dsw-alias-label-secondary)', display: 'inline-flex', alignItems: 'center', gap: '3px' },
|
|
2590
|
+
title: t('maxConcurrencySlotTitle')
|
|
2591
|
+
},
|
|
2592
|
+
'⚡',
|
|
2593
|
+
h('input', {
|
|
2594
|
+
type: 'number',
|
|
2595
|
+
min: 1,
|
|
2596
|
+
max: 20,
|
|
2597
|
+
style: { width: '44px', padding: '1px 4px', fontSize: '11px', textAlign: 'center' },
|
|
2598
|
+
value: row.slot.maxConcurrency !== undefined ? row.slot.maxConcurrency : 1,
|
|
2599
|
+
title: t('maxConcurrencySlotTitle'),
|
|
2600
|
+
onChange: (e) => {
|
|
2601
|
+
const next = slots.slice()
|
|
2602
|
+
const val = parseInt(e.target.value, 10)
|
|
2603
|
+
next[row.i] = Object.assign({}, next[row.i], { maxConcurrency: isNaN(val) ? 1 : Math.max(1, val) })
|
|
2604
|
+
setSlots(next)
|
|
2605
|
+
},
|
|
2606
|
+
})
|
|
2607
|
+
),
|
|
2351
2608
|
),
|
|
2352
2609
|
(function(){
|
|
2353
2610
|
var r = proxyRes[key]
|
|
@@ -2439,7 +2696,30 @@ window.__ModuleLoader__.load({
|
|
|
2439
2696
|
)
|
|
2440
2697
|
})() : null),
|
|
2441
2698
|
account.accountNotice ? h('div', { className: 'dsub-verify' }, account.accountNotice) : null,
|
|
2442
|
-
account.
|
|
2699
|
+
account.scopeWarning ? h('div', { className: 'dsub-scope-banner' },
|
|
2700
|
+
h('span', { style: { color: 'var(--dsw-alias-state-warn-primary)' } }, '⚠️ ' + t('scopeWarning') + ': ' + account.scopeWarning),
|
|
2701
|
+
h('button', {
|
|
2702
|
+
type: 'button',
|
|
2703
|
+
className: 'dsub-mini',
|
|
2704
|
+
onClick: () => connect(row.slot.provider, row.slot.index).catch((e) => setErr(cleanErrorMessage(e.message || e))),
|
|
2705
|
+
}, '🔄 ' + t('reconnectScope'))
|
|
2706
|
+
) : null,
|
|
2707
|
+
(account.isRevoked || account.authWarning) ? h('div', { className: 'dsub-bad dsub-row', style: { alignItems: 'center', justifyContent: 'space-between', gap: '8px', marginBottom: '6px' } },
|
|
2708
|
+
h('span', null, '⛔ ' + (account.authWarning || t('sessionRevokedWarning'))),
|
|
2709
|
+
h('button', {
|
|
2710
|
+
type: 'button',
|
|
2711
|
+
className: 'dsub-mini dsub-btn-primary',
|
|
2712
|
+
onClick: () => connect(row.slot.provider, row.slot.index).catch((e) => setErr(cleanErrorMessage(e.message || e))),
|
|
2713
|
+
}, '🔄 ' + t('reconnectNow'))
|
|
2714
|
+
) : null,
|
|
2715
|
+
account.refreshError ? h('div', { className: 'dsub-bad dsub-row', style: { alignItems: 'center', justifyContent: 'space-between', gap: '8px' } },
|
|
2716
|
+
h('span', null, t('reconnectRequired') + ': ' + account.refreshError),
|
|
2717
|
+
h('button', {
|
|
2718
|
+
type: 'button',
|
|
2719
|
+
className: 'dsub-mini dsub-btn-primary',
|
|
2720
|
+
onClick: () => connect(row.slot.provider, row.slot.index).catch((e) => setErr(cleanErrorMessage(e.message || e))),
|
|
2721
|
+
}, '🔄 ' + t('reconnectNow'))
|
|
2722
|
+
) : null,
|
|
2443
2723
|
(function(){ var r=checkRes[key]; if(!r) return null; var txt=r.ok ? ('ok ' + (r.email||'')) : ('fail ' + (r.error && r.error.message || '')); var cls=r.ok ? 'dsub-ok' : 'dsub-bad'; var q=r.quota; if(q && q.remaining!=null) txt += ' quota:'+q.remaining+(q.limit!=null?'/'+q.limit:''); return h('div', {className: cls}, txt) })(),
|
|
2444
2724
|
account.validationUrl ? h('div', { className: 'dsub-verify' },
|
|
2445
2725
|
t('verifyPrefix'),
|
|
@@ -2523,10 +2803,25 @@ window.__ModuleLoader__.load({
|
|
|
2523
2803
|
h('div', { className: 'dsub-usage-grid' }, items)
|
|
2524
2804
|
)
|
|
2525
2805
|
})(),
|
|
2526
|
-
((account.paidTierName || account.ref || (telemetry && telemetry.accounts && telemetry.accounts[account.ref])) ? h('div', { className: 'dsub-slot-meta-row' },
|
|
2527
|
-
account.
|
|
2806
|
+
((account.email || account.plan || account.paidTierName || account.ref || (telemetry && telemetry.accounts && telemetry.accounts[account.ref])) ? h('div', { className: 'dsub-slot-meta-row' },
|
|
2807
|
+
account.email ? h('span', { className: 'dsub-sub' },
|
|
2808
|
+
h('strong', null, '👤 '),
|
|
2809
|
+
account.email
|
|
2810
|
+
) : null,
|
|
2811
|
+
(account.plan || account.paidTierName) ? h('span', { className: 'dsub-sub' },
|
|
2528
2812
|
h('strong', null, t('plan') + ': '),
|
|
2529
|
-
account.paidTierName
|
|
2813
|
+
account.plan || account.paidTierName
|
|
2814
|
+
) : null,
|
|
2815
|
+
(account.organizations && account.organizations.length > 0) ? h('span', { className: 'dsub-sub', style: { display: 'inline-flex', alignItems: 'center', gap: 4 } },
|
|
2816
|
+
h('strong', null, '🏢 ' + t('workspaceTitle') + ':'),
|
|
2817
|
+
h('select', {
|
|
2818
|
+
className: 'dsub-select',
|
|
2819
|
+
style: { padding: '1px 6px', fontSize: '12px' },
|
|
2820
|
+
value: account.selectedOrgId || '',
|
|
2821
|
+
onChange: (e) => switchOrg(account.provider, account.index, e.target.value),
|
|
2822
|
+
},
|
|
2823
|
+
account.organizations.map((org) => h('option', { key: org.id, value: org.id }, org.name + (org.role ? ' (' + org.role + ')' : '')))
|
|
2824
|
+
)
|
|
2530
2825
|
) : null,
|
|
2531
2826
|
(function(){
|
|
2532
2827
|
const accTelem = telemetry && telemetry.accounts && telemetry.accounts[account.ref]
|
|
@@ -2541,6 +2836,38 @@ window.__ModuleLoader__.load({
|
|
|
2541
2836
|
'⚡ ' + parts.join(' · ')
|
|
2542
2837
|
)
|
|
2543
2838
|
})(),
|
|
2839
|
+
(account.activeStreams > 0 || account.queuedStreams > 0) ? h('span', { className: 'dsub-ref-tag', title: t('concurrencyQueueHint') },
|
|
2840
|
+
account.activeStreams > 0 ? ('⚡ ' + account.activeStreams + ' ' + t('activeStreams')) : null,
|
|
2841
|
+
(account.activeStreams > 0 && account.queuedStreams > 0) ? ' · ' : null,
|
|
2842
|
+
account.queuedStreams > 0 ? ('⏳ ' + account.queuedStreams + ' ' + t('queuedStreams')) : null
|
|
2843
|
+
) : null,
|
|
2844
|
+
account.runwayText ? h('span', {
|
|
2845
|
+
className: 'dsub-ref-tag',
|
|
2846
|
+
style: {
|
|
2847
|
+
color: account.pacingStatus === 'critical' ? 'var(--dsw-alias-state-error-primary)' : (account.pacingStatus === 'elevated' ? 'var(--dsw-alias-state-warn-primary)' : 'var(--dsw-alias-state-success-primary)'),
|
|
2848
|
+
},
|
|
2849
|
+
title: t('pacingRunwayHint')
|
|
2850
|
+
}, '⏱️ ' + t('runwayPrefix') + ': ' + account.runwayText) : null,
|
|
2851
|
+
(account.valueDelivered && account.valueDelivered.equivalentCost > 0) ? h('span', {
|
|
2852
|
+
className: 'dsub-speed-tag',
|
|
2853
|
+
title: t('valueDeliveredHint')
|
|
2854
|
+
}, '📊 ' + account.valueDelivered.formattedCost + ' (' + account.valueDelivered.roiLabel + ' ROI)') : null,
|
|
2855
|
+
(account.billing && account.billing.badgeText) ? h('span', {
|
|
2856
|
+
className: 'dsub-ref-tag',
|
|
2857
|
+
style: { color: 'var(--dsw-alias-state-error-primary)', fontWeight: 600 },
|
|
2858
|
+
title: account.billing.warning || t('billingAlertHint')
|
|
2859
|
+
}, '⚠️ ' + account.billing.badgeText) : null,
|
|
2860
|
+
(!account.billing?.badgeText && account.renewalCountdown) ? h('span', {
|
|
2861
|
+
className: 'dsub-ref-tag',
|
|
2862
|
+
title: t('renewalHint')
|
|
2863
|
+
}, '📅 ' + account.renewalCountdown) : null,
|
|
2864
|
+
(account.capabilities && Array.isArray(account.capabilities.tags) && account.capabilities.tags.length > 0) ? h('span', {
|
|
2865
|
+
className: 'dsub-ref-tag',
|
|
2866
|
+
title: t('capabilitiesHint')
|
|
2867
|
+
}, '⚡ ' + account.capabilities.tags.join(' · ')) : null,
|
|
2868
|
+
account.isEncryptedAtRest ? h('span', { className: 'dsub-ref-tag', title: t('atRestEncryptedTitle') },
|
|
2869
|
+
'🔒 ' + t('atRestEncrypted')
|
|
2870
|
+
) : null,
|
|
2544
2871
|
account.ref ? h('span', { className: 'dsub-ref-tag' },
|
|
2545
2872
|
h('span', null, t('storedAs')),
|
|
2546
2873
|
h('code', null, account.ref)
|
package/lib/config-schema.js
CHANGED
|
@@ -20,6 +20,8 @@ export const Slot = z.object({
|
|
|
20
20
|
.description('#67 Optional subscription expiry timestamp (ms). When set and within expiryNotifyDays, the header chip shows the account and expiry date.'),
|
|
21
21
|
proxyUrl: z.string().default('')
|
|
22
22
|
.description('#88 Per-account proxy URL (http://, https://, socks5://). All requests for this account route through it. Empty = direct connection.'),
|
|
23
|
+
maxConcurrency: z.number().default(1)
|
|
24
|
+
.description('#555 Per-slot max concurrent requests before queuing. Defaults to 1 for subscription safety.'),
|
|
23
25
|
})
|
|
24
26
|
|
|
25
27
|
export const defaultSlots = PROVIDERS.map((provider) => ({ provider, index: 1, label: '' }))
|
|
@@ -86,6 +88,10 @@ export const Config = z.object({
|
|
|
86
88
|
antigravityClientSecret: z.string().role('secret').default('')
|
|
87
89
|
.description('Google OAuth client secret for custom confidential clients or CLI tokens requiring secret refresh.'),
|
|
88
90
|
antigravityRedirectUri: z.string().volatile().default(''),
|
|
91
|
+
perplexityClientId: z.string().volatile().default(''),
|
|
92
|
+
perplexityRedirectUri: z.string().volatile().default(''),
|
|
93
|
+
jetbrainsClientId: z.string().volatile().default(''),
|
|
94
|
+
jetbrainsRedirectUri: z.string().volatile().default(''),
|
|
89
95
|
antigravityModels: z.array(z.any()).volatile().default([])
|
|
90
96
|
.description('Custom model catalog for Antigravity.'),
|
|
91
97
|
codexOriginator: z.string().volatile().default('')
|
|
@@ -130,12 +136,48 @@ export const Config = z.object({
|
|
|
130
136
|
.description('#485 Safety margin percent: avoid draining accounts below this threshold when alternatives exist.'),
|
|
131
137
|
enableQueue: z.boolean().volatile().default(true)
|
|
132
138
|
.description('#518 Bounded concurrency queue to prevent bursts of 429 errors.'),
|
|
133
|
-
accountMaxConcurrency: z.number().volatile().default(
|
|
134
|
-
.description('#518
|
|
139
|
+
accountMaxConcurrency: z.number().volatile().default(1)
|
|
140
|
+
.description('#518 #555 Default max concurrent requests per account before queuing (1 for OAuth subscription safety).'),
|
|
135
141
|
maxQueueSize: z.number().volatile().default(20)
|
|
136
142
|
.description('#518 Max requests waiting in queue before reject.'),
|
|
137
143
|
queueTimeoutMs: z.number().volatile().default(30000)
|
|
138
144
|
.description('#518 Max queue wait time in ms before timeout.'),
|
|
145
|
+
masterKey: z.string().role('secret').default('')
|
|
146
|
+
.description('#550 Optional master passphrase for encrypting OAuth credentials at rest (defaults to DSH_SUBSCRIPTIONS_MASTER_KEY env).'),
|
|
147
|
+
vaultSyncEnabled: z.boolean().volatile().default(false)
|
|
148
|
+
.description('#562 Enable automated encrypted vault sync to remote storage.'),
|
|
149
|
+
vaultSyncBackend: z.string().volatile().default('webdav')
|
|
150
|
+
.description('#562 Remote storage backend: webdav, gist, or s3.'),
|
|
151
|
+
vaultSyncPassphrase: z.string().role('secret').default('')
|
|
152
|
+
.description('#562 Passphrase used to encrypt/decrypt the remote vault archive.'),
|
|
153
|
+
vaultSyncIntervalHours: z.number().volatile().default(24)
|
|
154
|
+
.description('#562 Automated periodic sync interval in hours (0 disables periodic pull).'),
|
|
155
|
+
vaultSyncAutoPush: z.boolean().volatile().default(true)
|
|
156
|
+
.description('#562 Automatically push encrypted vault after successful OAuth login/refresh.'),
|
|
157
|
+
vaultSyncWebdavUrl: z.string().volatile().default('')
|
|
158
|
+
.description('#562 WebDAV target URL.'),
|
|
159
|
+
vaultSyncWebdavUsername: z.string().volatile().default('')
|
|
160
|
+
.description('#562 WebDAV username.'),
|
|
161
|
+
vaultSyncWebdavPassword: z.string().role('secret').default('')
|
|
162
|
+
.description('#562 WebDAV password or app token.'),
|
|
163
|
+
vaultSyncGistId: z.string().volatile().default('')
|
|
164
|
+
.description('#562 GitHub or Gitea Gist ID.'),
|
|
165
|
+
vaultSyncGistToken: z.string().role('secret').default('')
|
|
166
|
+
.description('#562 Personal access token with gist permission.'),
|
|
167
|
+
vaultSyncGistBaseUrl: z.string().volatile().default('https://api.github.com')
|
|
168
|
+
.description('#562 Gist API base URL.'),
|
|
169
|
+
vaultSyncS3Endpoint: z.string().volatile().default('')
|
|
170
|
+
.description('#562 S3 endpoint URL.'),
|
|
171
|
+
vaultSyncS3Bucket: z.string().volatile().default('')
|
|
172
|
+
.description('#562 S3 bucket name.'),
|
|
173
|
+
vaultSyncS3Key: z.string().volatile().default('dsh-subscriptions-vault.enc')
|
|
174
|
+
.description('#562 S3 object key.'),
|
|
175
|
+
vaultSyncS3Region: z.string().volatile().default('us-east-1')
|
|
176
|
+
.description('#562 S3 region.'),
|
|
177
|
+
vaultSyncS3AccessKey: z.string().volatile().default('')
|
|
178
|
+
.description('#562 S3 Access Key ID.'),
|
|
179
|
+
vaultSyncS3SecretKey: z.string().role('secret').default('')
|
|
180
|
+
.description('#562 S3 Secret Access Key.'),
|
|
139
181
|
customVendors: z.array(z.any()).volatile().default([])
|
|
140
182
|
.description('Declarative OpenAI-Responses-compatible providers. See README.'),
|
|
141
183
|
})
|
|
@@ -145,7 +187,7 @@ export function publicConfig(cfg) {
|
|
|
145
187
|
const clone = { ...cfg }
|
|
146
188
|
// #242, #252: redact secret fields before exposing config via GET /config.
|
|
147
189
|
for (const key of Object.keys(clone)) {
|
|
148
|
-
if (key.endsWith("ClientSecret") || key.endsWith("Secret")) {
|
|
190
|
+
if (key.endsWith("ClientSecret") || key.endsWith("Secret") || key.endsWith("SecretKey") || key.endsWith("Password") || key.endsWith("Token") || key.endsWith("Passphrase") || key === "masterKey") {
|
|
149
191
|
clone[key] = clone[key] ? "••••••" : ""
|
|
150
192
|
}
|
|
151
193
|
}
|
|
@@ -199,7 +241,7 @@ export function restoreMaskedSecrets(incoming, existing) {
|
|
|
199
241
|
const out = { ...incoming }
|
|
200
242
|
|
|
201
243
|
for (const key of Object.keys(out)) {
|
|
202
|
-
if ((key.endsWith("ClientSecret") || key.endsWith("Secret")) && typeof out[key] === "string") {
|
|
244
|
+
if ((key.endsWith("ClientSecret") || key.endsWith("Secret") || key.endsWith("SecretKey") || key.endsWith("Password") || key.endsWith("Token") || key.endsWith("Passphrase") || key === "masterKey") && typeof out[key] === "string") {
|
|
203
245
|
if (out[key].includes("••••")) {
|
|
204
246
|
out[key] = existing[key] || ""
|
|
205
247
|
}
|