@goodandready/dsh-subscriptions 0.7.11 → 0.7.12
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/client.js +139 -53
- package/lib/http.js +2 -5
- package/lib/mask.js +1 -0
- package/lib/ollama-health.js +25 -0
- package/lib/routes/oauth.js +17 -0
- package/lib/routes/sources.js +2 -2
- package/lib/routes/status.js +5 -3
- package/lib/routes/vault.js +7 -3
- package/lib/vault-snapshots.js +2 -0
- package/lib/vault-sync.js +43 -6
- package/lib/vendors/grok.js +1 -0
- package/lib/vendors/index.js +1 -0
- package/package.json +2 -2
package/lib/client.js
CHANGED
|
@@ -390,6 +390,47 @@ window.__ModuleLoader__.load({
|
|
|
390
390
|
"cachePinnedBadge": "Cache Pinned",
|
|
391
391
|
"reasoningEffortTitle": "Reasoning Effort Control",
|
|
392
392
|
"reasoningEffortDesc": "Configure reasoning depth for models with extended thinking capabilities.",
|
|
393
|
+
"quotaWidget": "Quota widget",
|
|
394
|
+
"quotaWidgetOff": "Off",
|
|
395
|
+
"quotaWidgetDocked": "Docked",
|
|
396
|
+
"quotaWidgetFloating": "Floating (draggable)",
|
|
397
|
+
"quotaWidgetHint": "Floating: draggable; docked.",
|
|
398
|
+
"webhookUrl": "Webhook URL",
|
|
399
|
+
"webhookUrlHint": "Signed POST target for alerts.",
|
|
400
|
+
"webhookSecret": "Webhook secret",
|
|
401
|
+
"webhookFormat": "Webhook format",
|
|
402
|
+
"webhookFormatNative": "Native",
|
|
403
|
+
"webhookFormatNotify": "dsh-plugin-notify compatible",
|
|
404
|
+
"digestIntervalHours": "Digest interval (hours)",
|
|
405
|
+
"digestHint": "0 disables digests; needs a URL.",
|
|
406
|
+
"sendTest": "Send test",
|
|
407
|
+
"delivered": "delivered",
|
|
408
|
+
"vaultSnapshots": "Vault snapshots",
|
|
409
|
+
"vaultSnapshotsHint": "Encrypted slots; auto pre-pull.",
|
|
410
|
+
"createSnapshot": "Create snapshot",
|
|
411
|
+
"restoreSnapshot": "Restore",
|
|
412
|
+
"noSnapshots": "No snapshots yet.",
|
|
413
|
+
"created": "created",
|
|
414
|
+
"restoredSlots": "restored slots:",
|
|
415
|
+
"providerPacks": "Provider packs",
|
|
416
|
+
"providerPacksHint": "Add a Responses provider as JSON.",
|
|
417
|
+
"noPacks": "No packs yet.",
|
|
418
|
+
"validateAndSave": "Validate & save",
|
|
419
|
+
"exportPacks": "Export",
|
|
420
|
+
"exported": "exported",
|
|
421
|
+
"saved": "saved",
|
|
422
|
+
"sources": "Sources",
|
|
423
|
+
"sourcesHint": "Subscription → API key → Ollama.",
|
|
424
|
+
"keyRotationOffline": "key-rotation not installed/offline",
|
|
425
|
+
"keyRotationHealth": "key-rotation health",
|
|
426
|
+
"apiKeySource": "API key",
|
|
427
|
+
"oauthSource": "OAuth",
|
|
428
|
+
"ollamaSource": "Ollama",
|
|
429
|
+
"sourceNone": "none",
|
|
430
|
+
"openSettings": "Open settings",
|
|
431
|
+
"sourceAvailable": "available",
|
|
432
|
+
"sourceUnavailable": "unavailable",
|
|
433
|
+
"cooldownLabel": "cooldown",
|
|
393
434
|
"reasoningLevelOff": "Off"
|
|
394
435
|
};
|
|
395
436
|
|
|
@@ -704,6 +745,47 @@ window.__ModuleLoader__.load({
|
|
|
704
745
|
"cachePinnedBadge": "缓存粘性",
|
|
705
746
|
"reasoningEffortTitle": "推理强度控制",
|
|
706
747
|
"reasoningEffortDesc": "为具备扩展思考能力的模型配置推理深度与思考预算。",
|
|
748
|
+
"quotaWidget": "额度组件",
|
|
749
|
+
"quotaWidgetOff": "关闭",
|
|
750
|
+
"quotaWidgetDocked": "停靠",
|
|
751
|
+
"quotaWidgetFloating": "浮动(可拖动)",
|
|
752
|
+
"quotaWidgetHint": "浮动可拖动;停靠右下。",
|
|
753
|
+
"webhookUrl": "Webhook 地址",
|
|
754
|
+
"webhookUrlHint": "警报的签名 POST 目标。",
|
|
755
|
+
"webhookSecret": "Webhook 密钥",
|
|
756
|
+
"webhookFormat": "Webhook 格式",
|
|
757
|
+
"webhookFormatNative": "原生",
|
|
758
|
+
"webhookFormatNotify": "兼容 dsh-plugin-notify",
|
|
759
|
+
"digestIntervalHours": "摘要间隔(小时)",
|
|
760
|
+
"digestHint": "0 关闭摘要;需要地址。",
|
|
761
|
+
"sendTest": "发送测试",
|
|
762
|
+
"delivered": "已送达",
|
|
763
|
+
"vaultSnapshots": "Vault 快照",
|
|
764
|
+
"vaultSnapshotsHint": "加密 slots 快照。",
|
|
765
|
+
"createSnapshot": "创建快照",
|
|
766
|
+
"restoreSnapshot": "恢复",
|
|
767
|
+
"noSnapshots": "尚无快照。",
|
|
768
|
+
"created": "已创建",
|
|
769
|
+
"restoredSlots": "已恢复 slots:",
|
|
770
|
+
"providerPacks": "供应商包",
|
|
771
|
+
"providerPacksHint": "JSON 添加供应商。",
|
|
772
|
+
"noPacks": "尚无供应商包。",
|
|
773
|
+
"validateAndSave": "验证并保存",
|
|
774
|
+
"exportPacks": "导出",
|
|
775
|
+
"exported": "已导出",
|
|
776
|
+
"saved": "已保存",
|
|
777
|
+
"sources": "来源",
|
|
778
|
+
"sourcesHint": "订阅 → 密钥 → Ollama。",
|
|
779
|
+
"keyRotationOffline": "key-rotation 未安装或离线",
|
|
780
|
+
"keyRotationHealth": "key-rotation 健康度",
|
|
781
|
+
"apiKeySource": "API 密钥",
|
|
782
|
+
"oauthSource": "OAuth",
|
|
783
|
+
"ollamaSource": "Ollama",
|
|
784
|
+
"sourceNone": "无",
|
|
785
|
+
"openSettings": "打开设置",
|
|
786
|
+
"sourceAvailable": "可用",
|
|
787
|
+
"sourceUnavailable": "不可用",
|
|
788
|
+
"cooldownLabel": "冷却",
|
|
707
789
|
"reasoningLevelOff": "关闭"
|
|
708
790
|
};
|
|
709
791
|
// #51: live countdown to the quota window reset (account.quota.resetAt).
|
|
@@ -1829,21 +1911,21 @@ window.__ModuleLoader__.load({
|
|
|
1829
1911
|
),
|
|
1830
1912
|
),
|
|
1831
1913
|
h('div', { className: 'dsub-sub' }, t('composerQuotaDisplayHint')), h('div', { className: 'dsub-row' },
|
|
1832
|
-
h('span', { className: 'dsub-h' }, '
|
|
1914
|
+
h('span', { className: 'dsub-h' }, t('quotaWidget')),
|
|
1833
1915
|
h('select', {
|
|
1834
1916
|
className: 'dsub-select',
|
|
1835
1917
|
value: draft.quotaWidget || 'docked',
|
|
1836
1918
|
onChange: (e) => setDraft((d) => Object.assign({}, d, { quotaWidget: e.target.value })),
|
|
1837
1919
|
},
|
|
1838
|
-
h('option', { value: 'off' }, '
|
|
1839
|
-
h('option', { value: 'docked' }, '
|
|
1840
|
-
h('option', { value: 'floating' },
|
|
1920
|
+
h('option', { value: 'off' }, t('quotaWidgetOff')),
|
|
1921
|
+
h('option', { value: 'docked' }, t('quotaWidgetDocked')),
|
|
1922
|
+
h('option', { value: 'floating' }, t('quotaWidgetFloating')),
|
|
1841
1923
|
),
|
|
1842
1924
|
),
|
|
1843
|
-
h('div', { className: 'dsub-sub' }, '
|
|
1925
|
+
h('div', { className: 'dsub-sub' }, t('quotaWidgetHint')),
|
|
1844
1926
|
h(SourcesPanel, { t }),
|
|
1845
|
-
h(ProviderPacksPanel),
|
|
1846
|
-
h(VaultSnapshotsPanel),
|
|
1927
|
+
h(ProviderPacksPanel, { t }),
|
|
1928
|
+
h(VaultSnapshotsPanel, { t }),
|
|
1847
1929
|
|
|
1848
1930
|
h('div', { className: 'dsub-row' },
|
|
1849
1931
|
h('span', { className: 'dsub-h' }, t('composerQuota')),
|
|
@@ -1875,7 +1957,7 @@ window.__ModuleLoader__.load({
|
|
|
1875
1957
|
h('div', { className: 'dsub-sub' },
|
|
1876
1958
|
t('expiryNotifyDaysHint')),
|
|
1877
1959
|
h('div', { className: 'dsub-row' },
|
|
1878
|
-
h('span', { className: 'dsub-h' }, '
|
|
1960
|
+
h('span', { className: 'dsub-h' }, t('webhookUrl')),
|
|
1879
1961
|
h('input', {
|
|
1880
1962
|
type: 'url',
|
|
1881
1963
|
className: 'dsub-inp',
|
|
@@ -1884,20 +1966,20 @@ window.__ModuleLoader__.load({
|
|
|
1884
1966
|
onChange: (e) => setDraft((d) => Object.assign({}, d, { webhookUrl: e.target.value })),
|
|
1885
1967
|
}),
|
|
1886
1968
|
),
|
|
1887
|
-
h('div', { className: 'dsub-sub' }, '
|
|
1969
|
+
h('div', { className: 'dsub-sub' }, t('webhookUrlHint')),
|
|
1888
1970
|
h('div', { className: 'dsub-row' },
|
|
1889
|
-
h('span', { className: 'dsub-h' }, '
|
|
1971
|
+
h('span', { className: 'dsub-h' }, t('webhookFormat')),
|
|
1890
1972
|
h('select', {
|
|
1891
1973
|
className: 'dsub-select',
|
|
1892
1974
|
value: draft.webhookFormat || 'native',
|
|
1893
1975
|
onChange: (e) => setDraft((d) => Object.assign({}, d, { webhookFormat: e.target.value })),
|
|
1894
1976
|
},
|
|
1895
|
-
h('option', { value: 'native' }, '
|
|
1896
|
-
h('option', { value: 'notify' }, '
|
|
1977
|
+
h('option', { value: 'native' }, t('webhookFormatNative')),
|
|
1978
|
+
h('option', { value: 'notify' }, t('webhookFormatNotify')),
|
|
1897
1979
|
),
|
|
1898
1980
|
),
|
|
1899
1981
|
h('div', { className: 'dsub-row' },
|
|
1900
|
-
h('span', { className: 'dsub-h' }, '
|
|
1982
|
+
h('span', { className: 'dsub-h' }, t('webhookSecret')),
|
|
1901
1983
|
h('input', {
|
|
1902
1984
|
type: 'password',
|
|
1903
1985
|
className: 'dsub-inp',
|
|
@@ -1907,7 +1989,7 @@ window.__ModuleLoader__.load({
|
|
|
1907
1989
|
}),
|
|
1908
1990
|
),
|
|
1909
1991
|
h('div', { className: 'dsub-row' },
|
|
1910
|
-
h('span', { className: 'dsub-h' },
|
|
1992
|
+
h('span', { className: 'dsub-h' }, t('digestIntervalHours')),
|
|
1911
1993
|
h('input', {
|
|
1912
1994
|
type: 'number',
|
|
1913
1995
|
className: 'dsub-inp',
|
|
@@ -1918,8 +2000,8 @@ window.__ModuleLoader__.load({
|
|
|
1918
2000
|
onChange: (e) => setDraft((d) => Object.assign({}, d, { digestIntervalHours: Number(e.target.value) || 0 })),
|
|
1919
2001
|
}),
|
|
1920
2002
|
),
|
|
1921
|
-
h('div', { className: 'dsub-sub' }, '
|
|
1922
|
-
h(WebhookTestButton),
|
|
2003
|
+
h('div', { className: 'dsub-sub' }, t('digestHint')),
|
|
2004
|
+
h(WebhookTestButton, { t }),
|
|
1923
2005
|
),
|
|
1924
2006
|
h('details', { className: 'dsub-group', open: true },
|
|
1925
2007
|
h('summary', { className: 'dsub-groupHead' }, '🚦 ' + t('groupQueue')),
|
|
@@ -2060,19 +2142,19 @@ window.__ModuleLoader__.load({
|
|
|
2060
2142
|
h('div', { className: 'dsub-sub', style: { marginTop: '4px' } }, t('modelsCatalogDesc')),
|
|
2061
2143
|
h('div', { className: 'dsub-model-grid' },
|
|
2062
2144
|
(Array.isArray(modelsCatalog) && modelsCatalog.length ? modelsCatalog : [
|
|
2063
|
-
{ id: 'gpt-4o', contextLabel: '128k', capabilities: ['vision'], pool: { status: '
|
|
2064
|
-
{ id: 'o1 / o3-mini', contextLabel: '200k', capabilities: ['reasoning'], pool: { status: '
|
|
2065
|
-
{ id: 'claude-3-7-sonnet', contextLabel: '200k', capabilities: ['reasoning'], pool: { status: '
|
|
2066
|
-
{ id: 'claude-3-5-sonnet', contextLabel: '200k', capabilities: ['coding'], pool: { status: '
|
|
2067
|
-
{ id: 'grok-beta', contextLabel: '128k', capabilities: ['fast'], pool: { status: '
|
|
2068
|
-
{ id: 'gemini-2.5-pro', contextLabel: '1M', capabilities: ['vision'], pool: { status: '
|
|
2069
|
-
{ id: 'kimi-k1.5', contextLabel: '128k', capabilities: ['math'], pool: { status: '
|
|
2070
|
-
{ id: 'glm-4-plus', contextLabel: '128k', capabilities: ['chinese'], pool: { status: '
|
|
2145
|
+
{ id: 'gpt-4o', contextLabel: '128k', capabilities: ['vision'], pool: { status: t('sourceAvailable') } },
|
|
2146
|
+
{ id: 'o1 / o3-mini', contextLabel: '200k', capabilities: ['reasoning'], pool: { status: t('sourceAvailable') } },
|
|
2147
|
+
{ id: 'claude-3-7-sonnet', contextLabel: '200k', capabilities: ['reasoning'], pool: { status: t('sourceAvailable') } },
|
|
2148
|
+
{ id: 'claude-3-5-sonnet', contextLabel: '200k', capabilities: ['coding'], pool: { status: t('sourceAvailable') } },
|
|
2149
|
+
{ id: 'grok-beta', contextLabel: '128k', capabilities: ['fast'], pool: { status: t('sourceAvailable') } },
|
|
2150
|
+
{ id: 'gemini-2.5-pro', contextLabel: '1M', capabilities: ['vision'], pool: { status: t('sourceAvailable') } },
|
|
2151
|
+
{ id: 'kimi-k1.5', contextLabel: '128k', capabilities: ['math'], pool: { status: t('sourceAvailable') } },
|
|
2152
|
+
{ id: 'glm-4-plus', contextLabel: '128k', capabilities: ['chinese'], pool: { status: t('sourceAvailable') } },
|
|
2071
2153
|
]).map((m, idx) => {
|
|
2072
2154
|
const poolStatus = m.pool ? m.pool.status : 'unconfigured'
|
|
2073
2155
|
const statusClass = 'dsub-model-status-' + poolStatus
|
|
2074
2156
|
let poolLabel = ''
|
|
2075
|
-
if (poolStatus === '
|
|
2157
|
+
if (poolStatus === t('sourceAvailable')) {
|
|
2076
2158
|
poolLabel = '🟢 ' + (m.pool && m.pool.remainingPercent != null ? (m.pool.remainingPercent + '%') : t('modelPoolAvailable'))
|
|
2077
2159
|
} else if (poolStatus === 'degraded') {
|
|
2078
2160
|
poolLabel = '🟡 ' + t('modelPoolDegraded') + (m.pool ? ' (' + m.pool.healthyAccounts + '/' + m.pool.totalAccounts + ')' : '')
|
|
@@ -3703,7 +3785,8 @@ window.__ModuleLoader__.load({
|
|
|
3703
3785
|
)
|
|
3704
3786
|
}
|
|
3705
3787
|
|
|
3706
|
-
function WebhookTestButton() {
|
|
3788
|
+
function WebhookTestButton(props) {
|
|
3789
|
+
const t = (props && props.t) || ((k) => k)
|
|
3707
3790
|
const [state, setState] = React.useState({ busy: false, message: '', error: null })
|
|
3708
3791
|
const send = () => {
|
|
3709
3792
|
setState({ busy: true, message: '', error: null })
|
|
@@ -3711,19 +3794,20 @@ window.__ModuleLoader__.load({
|
|
|
3711
3794
|
.then(async (res) => {
|
|
3712
3795
|
const data = await res.json().catch(() => ({}))
|
|
3713
3796
|
if (!res.ok || !data.delivered) throw new Error((data.error && data.error.message) || ('HTTP ' + res.status))
|
|
3714
|
-
setState({ busy: false, message: 'delivered', error: null })
|
|
3797
|
+
setState({ busy: false, message: t('delivered'), error: null })
|
|
3715
3798
|
})
|
|
3716
3799
|
.catch((err) => setState({ busy: false, message: '', error: String(err && err.message || err) }))
|
|
3717
3800
|
}
|
|
3718
3801
|
return h('div', { className: 'dsub-row' },
|
|
3719
|
-
h('button', { type: 'button', className: 'dsub-mini', disabled: state.busy, onClick: send }, state.busy ? '…' : '
|
|
3802
|
+
h('button', { type: 'button', className: 'dsub-mini', disabled: state.busy, onClick: send }, state.busy ? '…' : t('sendTest')),
|
|
3720
3803
|
state.message ? h('span', { className: 'dsub-ok' }, state.message) : null,
|
|
3721
3804
|
state.error ? h('span', { className: 'dsub-bad' }, state.error) : null,
|
|
3722
3805
|
)
|
|
3723
3806
|
}
|
|
3724
3807
|
|
|
3725
3808
|
// #736: vault snapshot list with create/restore. Payload stays server-side.
|
|
3726
|
-
function VaultSnapshotsPanel() {
|
|
3809
|
+
function VaultSnapshotsPanel(props) {
|
|
3810
|
+
const t = (props && props.t) || ((k) => k)
|
|
3727
3811
|
const [state, setState] = React.useState({ snapshots: [], stats: null, message: '', error: null, busy: false })
|
|
3728
3812
|
const pull = () => {
|
|
3729
3813
|
fetch('/dsh-subscriptions/vault/snapshots', { cache: 'no-store' })
|
|
@@ -3744,31 +3828,32 @@ window.__ModuleLoader__.load({
|
|
|
3744
3828
|
const create = () => {
|
|
3745
3829
|
setState((st) => Object.assign({}, st, { busy: true, message: '', error: null }))
|
|
3746
3830
|
call('/dsh-subscriptions/vault/snapshots', { label: 'manual' })
|
|
3747
|
-
.then((data) => { setState((st) => Object.assign({}, st, { busy: false, message: 'created ' + data.snapshot.id })); pull() })
|
|
3831
|
+
.then((data) => { setState((st) => Object.assign({}, st, { busy: false, message: t('created') + ' ' + data.snapshot.id })); pull() })
|
|
3748
3832
|
.catch((err) => setState((st) => Object.assign({}, st, { busy: false, error: String(err && err.message || err) })))
|
|
3749
3833
|
}
|
|
3750
3834
|
const restore = (id) => {
|
|
3751
3835
|
setState((st) => Object.assign({}, st, { busy: true, message: '', error: null }))
|
|
3752
3836
|
call('/dsh-subscriptions/vault/snapshots/restore', { id })
|
|
3753
|
-
.then((data) => { setState((st) => Object.assign({}, st, { busy: false, message: '
|
|
3837
|
+
.then((data) => { setState((st) => Object.assign({}, st, { busy: false, message: t('restoredSlots') + ' ' + data.slotCount })); pull() })
|
|
3754
3838
|
.catch((err) => setState((st) => Object.assign({}, st, { busy: false, error: String(err && err.message || err) })))
|
|
3755
3839
|
}
|
|
3756
3840
|
return h('div', { className: 'dsub-section-card' },
|
|
3757
|
-
h('div', { className: 'dsub-section-title' }, '
|
|
3758
|
-
h('div', { className: 'dsub-section-desc' }, '
|
|
3841
|
+
h('div', { className: 'dsub-section-title' }, t('vaultSnapshots'), h('button', { type: 'button', className: 'dsub-mini', onClick: create, disabled: state.busy }, state.busy ? '…' : t('createSnapshot'))),
|
|
3842
|
+
h('div', { className: 'dsub-section-desc' }, t('vaultSnapshotsHint')),
|
|
3759
3843
|
state.stats ? h('div', { className: 'dsub-dim' }, state.stats.count + ' snapshots · ' + state.stats.bytes + ' bytes · ' + state.stats.path) : null,
|
|
3760
3844
|
state.snapshots.length ? state.snapshots.map((snap) => h('div', { className: 'dsub-row', key: snap.id },
|
|
3761
3845
|
h('span', { className: 'dsub-h' }, snap.id),
|
|
3762
3846
|
h('span', { className: 'dsub-dim' }, (snap.label || '-') + ' · ' + snap.slotCount + ' slots · ' + snap.createdAt),
|
|
3763
|
-
h('button', { type: 'button', className: 'dsub-mini', disabled: state.busy, onClick: () => restore(snap.id) }, '
|
|
3764
|
-
)) : h('div', { className: 'dsub-dim' }, '
|
|
3847
|
+
h('button', { type: 'button', className: 'dsub-mini', disabled: state.busy, onClick: () => restore(snap.id) }, t('restoreSnapshot')),
|
|
3848
|
+
)) : h('div', { className: 'dsub-dim' }, t('noSnapshots')),
|
|
3765
3849
|
state.message ? h('div', { className: 'dsub-ok' }, state.message) : null,
|
|
3766
3850
|
state.error ? h('div', { className: 'dsub-bad' }, state.error) : null,
|
|
3767
3851
|
)
|
|
3768
3852
|
}
|
|
3769
3853
|
|
|
3770
3854
|
// #724: provider pack manager. Displays ids/models only; headers are masked.
|
|
3771
|
-
function ProviderPacksPanel() {
|
|
3855
|
+
function ProviderPacksPanel(props) {
|
|
3856
|
+
const t = (props && props.t) || ((k) => k)
|
|
3772
3857
|
const [state, setState] = React.useState({ packs: [], text: '', message: '', error: null, busy: false })
|
|
3773
3858
|
const publicPack = (pack) => {
|
|
3774
3859
|
const copy = Object.assign({}, pack)
|
|
@@ -3812,18 +3897,18 @@ window.__ModuleLoader__.load({
|
|
|
3812
3897
|
body: JSON.stringify({ config: Object.assign({}, cfg.config, { providerPacks: packs }), revision: cfg.revision }),
|
|
3813
3898
|
})
|
|
3814
3899
|
if (!res.ok) throw new Error('HTTP ' + res.status)
|
|
3815
|
-
setState((st) => Object.assign({}, st, { packs, busy: false, message: 'saved ' + checked.id, text: '' }))
|
|
3900
|
+
setState((st) => Object.assign({}, st, { packs, busy: false, message: t('saved') + ' ' + checked.id, text: '' }))
|
|
3816
3901
|
})
|
|
3817
3902
|
.catch((err) => setState((st) => Object.assign({}, st, { busy: false, error: String(err && err.message || err) })))
|
|
3818
3903
|
}
|
|
3819
|
-
const exportPacks = () => setState((st) => Object.assign({}, st, { text: JSON.stringify(st.packs.map(publicPack), null, 2), message: 'exported' }))
|
|
3904
|
+
const exportPacks = () => setState((st) => Object.assign({}, st, { text: JSON.stringify(st.packs.map(publicPack), null, 2), message: t('exported') }))
|
|
3820
3905
|
return h('div', { className: 'dsub-section-card' },
|
|
3821
|
-
h('div', { className: 'dsub-section-title' }, '
|
|
3822
|
-
h('div', { className: 'dsub-section-desc' }, '
|
|
3906
|
+
h('div', { className: 'dsub-section-title' }, t('providerPacks'), h('button', { type: 'button', className: 'dsub-mini', onClick: pull }, '↻')),
|
|
3907
|
+
h('div', { className: 'dsub-section-desc' }, t('providerPacksHint')),
|
|
3823
3908
|
state.packs.length ? state.packs.map((pack) => h('div', { className: 'dsub-row', key: pack.id },
|
|
3824
3909
|
h('span', { className: 'dsub-h' }, pack.id),
|
|
3825
3910
|
h('span', { className: 'dsub-dim' }, (pack.displayName || '') + ' · ' + ((pack.models || []).length) + ' models'),
|
|
3826
|
-
)) : h('div', { className: 'dsub-dim' }, '
|
|
3911
|
+
)) : h('div', { className: 'dsub-dim' }, t('noPacks')),
|
|
3827
3912
|
h('textarea', {
|
|
3828
3913
|
className: 'dsub-inp',
|
|
3829
3914
|
style: { width: '100%', minHeight: 120, fontFamily: 'ui-monospace,monospace', padding: 8 },
|
|
@@ -3832,8 +3917,8 @@ window.__ModuleLoader__.load({
|
|
|
3832
3917
|
onChange: (e) => setState((st) => Object.assign({}, st, { text: e.target.value, message: '', error: null })),
|
|
3833
3918
|
}),
|
|
3834
3919
|
h('div', { className: 'dsub-row' },
|
|
3835
|
-
h('button', { type: 'button', className: 'dsub-mini', disabled: state.busy, onClick: save }, state.busy ? '…' : '
|
|
3836
|
-
h('button', { type: 'button', className: 'dsub-mini', onClick: exportPacks }, '
|
|
3920
|
+
h('button', { type: 'button', className: 'dsub-mini', disabled: state.busy, onClick: save }, state.busy ? '…' : t('validateAndSave')),
|
|
3921
|
+
h('button', { type: 'button', className: 'dsub-mini', onClick: exportPacks }, t('exportPacks')),
|
|
3837
3922
|
),
|
|
3838
3923
|
state.message ? h('div', { className: 'dsub-ok' }, state.message) : null,
|
|
3839
3924
|
state.error ? h('div', { className: 'dsub-bad' }, state.error) : null,
|
|
@@ -3841,7 +3926,8 @@ window.__ModuleLoader__.load({
|
|
|
3841
3926
|
}
|
|
3842
3927
|
|
|
3843
3928
|
// #713: read-only source chain panel. Never renders key values.
|
|
3844
|
-
function SourcesPanel() {
|
|
3929
|
+
function SourcesPanel(props) {
|
|
3930
|
+
const t = (props && props.t) || ((k) => k)
|
|
3845
3931
|
const [state, setState] = React.useState({ loading: true, data: null, error: null })
|
|
3846
3932
|
const pull = () => {
|
|
3847
3933
|
fetch('/dsh-subscriptions/sources', { cache: 'no-store' })
|
|
@@ -3854,16 +3940,16 @@ window.__ModuleLoader__.load({
|
|
|
3854
3940
|
const id = setInterval(pull, 30 * 1000)
|
|
3855
3941
|
return () => clearInterval(id)
|
|
3856
3942
|
}, [])
|
|
3857
|
-
const sourceLabel = (source) => source === 'api-key' ? '
|
|
3943
|
+
const sourceLabel = (source) => source === 'api-key' ? t('apiKeySource') : (source === 'oauth' ? t('oauthSource') : (source === 'ollama' ? t('ollamaSource') : t('sourceNone')))
|
|
3858
3944
|
return h('div', { className: 'dsub-section-card' },
|
|
3859
|
-
h('div', { className: 'dsub-section-title' }, '
|
|
3860
|
-
h('div', { className: 'dsub-section-desc' }, '
|
|
3945
|
+
h('div', { className: 'dsub-section-title' }, t('sources'), h('button', { type: 'button', className: 'dsub-mini', onClick: pull }, state.loading ? '…' : '↻')),
|
|
3946
|
+
h('div', { className: 'dsub-section-desc' }, t('sourcesHint')),
|
|
3861
3947
|
state.error ? h('div', { className: 'dsub-bad' }, state.error) : null,
|
|
3862
3948
|
state.data && state.data.keyRotation && !state.data.keyRotation.available
|
|
3863
|
-
? h('div', { className: 'dsub-dim' }, '
|
|
3949
|
+
? h('div', { className: 'dsub-dim' }, t('keyRotationOffline') + (state.data.keyRotation.reason ? ': ' + state.data.keyRotation.reason : ''))
|
|
3864
3950
|
: null,
|
|
3865
3951
|
state.data && state.data.keyRotation && state.data.keyRotation.available
|
|
3866
|
-
? h('div', { className: 'dsub-dim' }, '
|
|
3952
|
+
? h('div', { className: 'dsub-dim' }, t('keyRotationHealth') + ': ' + state.data.keyRotation.healthyKeys + '/' + state.data.keyRotation.totalKeys + ' healthy keys')
|
|
3867
3953
|
: null,
|
|
3868
3954
|
(state.data && state.data.providers ? state.data.providers : []).map((row) => h('div', { className: 'dsub-row', key: row.provider },
|
|
3869
3955
|
h('span', { className: 'dsub-h' }, row.provider),
|
|
@@ -3993,16 +4079,16 @@ window.__ModuleLoader__.load({
|
|
|
3993
4079
|
},
|
|
3994
4080
|
h('div', { className: 'dsub-h' }, String(state.active.provider || '').toUpperCase() + ' sources'),
|
|
3995
4081
|
(state.candidates || []).map((c, i) => {
|
|
3996
|
-
const label = c.source === 'api-key' ? '
|
|
4082
|
+
const label = c.source === 'api-key' ? t('apiKeySource') : (c.source === 'oauth' ? t('oauthSource') : t('ollamaSource'))
|
|
3997
4083
|
const cd = c.detail && Array.isArray(c.detail.keys) ? Math.max(0, ...c.detail.keys.map((k) => Number(k.cooldownMsLeft) || 0)) : 0
|
|
3998
4084
|
return h('div', { className: 'dsub-row', key: i },
|
|
3999
4085
|
h('span', { className: 'dsub-h' }, label),
|
|
4000
|
-
h('span', { className: c.available ? 'dsub-ok' : 'dsub-dim' }, c.available ? '
|
|
4001
|
-
cd > 0 ? h('span', { className: 'dsub-dim' }, '
|
|
4086
|
+
h('span', { className: c.available ? 'dsub-ok' : 'dsub-dim' }, c.available ? t('sourceAvailable') : t('sourceUnavailable')),
|
|
4087
|
+
cd > 0 ? h('span', { className: 'dsub-dim' }, t('cooldownLabel') + ' ' + Math.ceil(cd / 1000) + 's') : null,
|
|
4002
4088
|
c.detail && c.detail.healthyKeys != null ? h('span', { className: 'dsub-dim' }, c.detail.healthyKeys + '/' + c.detail.totalKeys + ' keys') : null,
|
|
4003
4089
|
)
|
|
4004
4090
|
}),
|
|
4005
|
-
h('button', { type: 'button', className: 'dsub-mini', onClick: () => { setOpen(false); openSettings() } }, '
|
|
4091
|
+
h('button', { type: 'button', className: 'dsub-mini', onClick: () => { setOpen(false); openSettings() } }, t('openSettings')),
|
|
4006
4092
|
) : null
|
|
4007
4093
|
return h(React.Fragment, null, h('div', {
|
|
4008
4094
|
className: 'dsub-qw' + (docked ? ' dsub-qwDocked' : '') + tone,
|
package/lib/http.js
CHANGED
|
@@ -104,14 +104,11 @@ function configuredTrustedHosts(targetCtx) {
|
|
|
104
104
|
}
|
|
105
105
|
|
|
106
106
|
/**
|
|
107
|
-
*
|
|
108
|
-
*
|
|
109
|
-
* @returns {boolean}/**
|
|
107
|
+
* Reject cross-site and untrusted writes and sensitive reads.
|
|
108
|
+
* Fail-closed: must be loopback, same-origin/same-site, or authentic host credentials (#374).
|
|
110
109
|
* @param {import('node:http').IncomingMessage} request
|
|
111
110
|
* @param {any} [targetCtx]
|
|
112
111
|
* @returns {boolean}
|
|
113
|
-
* Reject cross-site and untrusted writes and sensitive reads.
|
|
114
|
-
* Fail-closed: must be loopback, same-origin/same-site, or authentic host credentials (#374).
|
|
115
112
|
*/
|
|
116
113
|
export function isTrustedSettingsRequest(request, targetCtx) {
|
|
117
114
|
const headers = request?.headers || {}
|
package/lib/mask.js
CHANGED
|
@@ -20,6 +20,7 @@ export function maskText(value) {
|
|
|
20
20
|
return String(value || '').replace(EMAIL_RE, (m) => maskEmail(m))
|
|
21
21
|
}
|
|
22
22
|
|
|
23
|
+
/** @type {Array<[RegExp, string]>} */
|
|
23
24
|
const SECRET_PATTERNS = [
|
|
24
25
|
[/Bearer\s+[A-Za-z0-9._~+/-]+=*/gi, 'Bearer [REDACTED]'],
|
|
25
26
|
[/\b(?:sk|ghp|gho|ghu|ghs|ghr|github_pat|xox[baprs])[-_][A-Za-z0-9._-]{8,}/g, '[REDACTED]'],
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
// #755: health must probe the actual Ollama endpoint, not the fallback flag.
|
|
2
|
+
let cache = { base: '', at: 0, value: false }
|
|
3
|
+
|
|
4
|
+
export function clearOllamaHealthCache() {
|
|
5
|
+
cache = { base: '', at: 0, value: false }
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export async function probeOllama(baseUrl, { fetchImpl = fetch, timeoutMs = 800, ttlMs = 10000, now = Date.now } = {}) {
|
|
9
|
+
if (!baseUrl) return false
|
|
10
|
+
const base = String(baseUrl).replace(/\/$/, '')
|
|
11
|
+
if (cache.base === base && (now() - cache.at) < ttlMs) return cache.value
|
|
12
|
+
let value = false
|
|
13
|
+
try {
|
|
14
|
+
const init = { method: 'GET' }
|
|
15
|
+
if (timeoutMs > 0 && typeof AbortSignal !== 'undefined' && typeof AbortSignal.timeout === 'function') {
|
|
16
|
+
init.signal = AbortSignal.timeout(timeoutMs)
|
|
17
|
+
}
|
|
18
|
+
const res = await fetchImpl(base + '/api/tags', init)
|
|
19
|
+
value = Boolean(res && res.ok)
|
|
20
|
+
} catch {
|
|
21
|
+
value = false
|
|
22
|
+
}
|
|
23
|
+
cache = { base, at: now(), value }
|
|
24
|
+
return value
|
|
25
|
+
}
|
package/lib/routes/oauth.js
CHANGED
|
@@ -292,10 +292,27 @@ export function registerOauthRoutes(ctx, state) {
|
|
|
292
292
|
writeJson(res, 405, { ok: false, error: { code: 'method', message: 'GET only' } })
|
|
293
293
|
return
|
|
294
294
|
}
|
|
295
|
+
if (!isTrustedSettingsRequest(req, ctx)) {
|
|
296
|
+
writeJson(res, 403, { ok: false, error: { code: 'forbidden', message: 'same-origin or loopback only' } })
|
|
297
|
+
return
|
|
298
|
+
}
|
|
295
299
|
const q = queryOf(req)
|
|
296
300
|
const provider = q.get('provider') || ''
|
|
297
301
|
const redirectUri = q.get('redirectUri') || ''
|
|
298
302
|
const authUrl = q.get('authUrl') || ''
|
|
303
|
+
for (const [name, value] of [['redirectUri', redirectUri], ['authUrl', authUrl]]) {
|
|
304
|
+
if (!value) continue
|
|
305
|
+
try {
|
|
306
|
+
const parsed = new URL(value)
|
|
307
|
+
if (parsed.protocol !== 'http:' && parsed.protocol !== 'https:') {
|
|
308
|
+
writeJson(res, 400, { ok: false, error: { code: 'url', message: name + ' must use http(s)' } })
|
|
309
|
+
return
|
|
310
|
+
}
|
|
311
|
+
} catch {
|
|
312
|
+
writeJson(res, 400, { ok: false, error: { code: 'url', message: name + ' is invalid' } })
|
|
313
|
+
return
|
|
314
|
+
}
|
|
315
|
+
}
|
|
299
316
|
const host = (req.headers && req.headers.host) || ''
|
|
300
317
|
const user = process.env.USER || process.env.USERNAME || 'user'
|
|
301
318
|
const info = getHeadlessAuthInfo({ provider, redirectUri, authUrl, host, user })
|
package/lib/routes/sources.js
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { isTrustedSettingsRequest, safeJsonHandler, writeJson } from '../http.js'
|
|
2
2
|
import { buildSourceChain, readKeyRotationStatus, subscriptionCandidate } from '../key-rotation-bridge.js'
|
|
3
3
|
|
|
4
|
-
export async function collectSourceChain(state, port) {
|
|
4
|
+
export async function collectSourceChain(state, port, { accounts } = {}) {
|
|
5
5
|
const { accountsView, live } = state
|
|
6
6
|
let subscriptions = []
|
|
7
7
|
try {
|
|
8
|
-
const rows = await accountsView()
|
|
8
|
+
const rows = accounts || await accountsView()
|
|
9
9
|
subscriptions = (rows || []).map(subscriptionCandidate)
|
|
10
10
|
} catch { /* keep the chain available */ }
|
|
11
11
|
const keyRotation = port
|
package/lib/routes/status.js
CHANGED
|
@@ -27,6 +27,7 @@ import { PROVIDERS, displayName, droppedCredentialRefs, isProvider, oauthRef } f
|
|
|
27
27
|
import { getVendor } from '../vendors/index.js'
|
|
28
28
|
import { defaultErrorBuffer } from '../error-buffer.js'
|
|
29
29
|
import { assessHealth } from '../health-report.js'
|
|
30
|
+
import { probeOllama } from '../ollama-health.js'
|
|
30
31
|
import { defaultSnapshotStore } from '../vault-snapshots.js'
|
|
31
32
|
import { collectSourceChain } from './sources.js'
|
|
32
33
|
|
|
@@ -85,14 +86,15 @@ export function registerStatusRoutes(ctx, state) {
|
|
|
85
86
|
writeJson(res, 405, { ok: false, error: { code: 'method', message: 'GET only' } })
|
|
86
87
|
return
|
|
87
88
|
}
|
|
88
|
-
const
|
|
89
|
-
const
|
|
89
|
+
const logged = typeof store.loggedInProviders === 'function' ? await store.loggedInProviders().catch(() => []) : []
|
|
90
|
+
const accounts = logged.map((provider) => ({ provider, configured: true }))
|
|
91
|
+
const sources = await collectSourceChain(state, req.socket && req.socket.localPort, { accounts })
|
|
90
92
|
const health = assessHealth({
|
|
91
93
|
accounts,
|
|
92
94
|
sources,
|
|
93
95
|
snapshots: defaultSnapshotStore.stats(),
|
|
94
96
|
webhookUrl: live().webhookUrl,
|
|
95
|
-
ollama: live().
|
|
97
|
+
ollama: await probeOllama(live().ollamaBaseUrl),
|
|
96
98
|
})
|
|
97
99
|
writeJson(res, 200, { ok: true, status: health.status, version: pkgVersion || 'unknown', checks: health.checks })
|
|
98
100
|
}),
|
package/lib/routes/vault.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Config, plainConfig, revisionOf } from '../config-schema.js'
|
|
2
2
|
import { isTrustedSettingsRequest, readBody, safeJsonHandler, writeJson } from '../http.js'
|
|
3
|
-
import { capturePrePullSnapshot,
|
|
3
|
+
import { capturePrePullSnapshot, restoreSnapshotWithConfig, resolveVaultKey } from '../vault-sync.js'
|
|
4
4
|
import { defaultSnapshotStore } from '../vault-snapshots.js'
|
|
5
5
|
|
|
6
6
|
export function registerVaultRoutes(ctx, state) {
|
|
@@ -33,7 +33,11 @@ export function registerVaultRoutes(ctx, state) {
|
|
|
33
33
|
return
|
|
34
34
|
}
|
|
35
35
|
try {
|
|
36
|
-
const
|
|
36
|
+
const key = resolveVaultKey(live(), passphrase)
|
|
37
|
+
const snapshot = capturePrePullSnapshot(live().slots || [], key.passphrase, String(body.label || 'manual'), {
|
|
38
|
+
keyMode: key.keyMode,
|
|
39
|
+
deviceId: key.deviceId,
|
|
40
|
+
})
|
|
37
41
|
writeJson(res, 200, { ok: true, snapshot })
|
|
38
42
|
} catch (err) {
|
|
39
43
|
writeJson(res, 400, { ok: false, error: { code: 'vault', message: String(err && err.message || err) } })
|
|
@@ -74,7 +78,7 @@ export function registerVaultRoutes(ctx, state) {
|
|
|
74
78
|
return
|
|
75
79
|
}
|
|
76
80
|
try {
|
|
77
|
-
const restored =
|
|
81
|
+
const restored = restoreSnapshotWithConfig(snapshot, live())
|
|
78
82
|
const cfg = live()
|
|
79
83
|
const slots = stripLegacySlots(restored.slots || [])
|
|
80
84
|
await api.replace(plainConfig(Config({ ...cfg, slots })), revisionOf(api))
|
package/lib/vault-snapshots.js
CHANGED
package/lib/vault-sync.js
CHANGED
|
@@ -269,14 +269,44 @@ export function mergeVaultSlots(localSlots, remoteSlots) {
|
|
|
269
269
|
return { slots: out, conflicts }
|
|
270
270
|
}
|
|
271
271
|
|
|
272
|
-
export function createVaultSnapshot({ slots, blobs = {} }, passphrase, label = '') {
|
|
272
|
+
export function createVaultSnapshot({ slots, blobs = {} }, passphrase, label = '', meta = {}) {
|
|
273
273
|
if (!passphrase) throw new Error('passphrase is required for a vault snapshot')
|
|
274
274
|
const { vault, slotCount, blobCount } = exportVault({ slots: slots || [], blobs }, passphrase)
|
|
275
|
-
return { version: 2, label: String(label || ''), createdAt: new Date().toISOString(), vault, slotCount, blobCount }
|
|
275
|
+
return { version: 2, label: String(label || ''), createdAt: new Date().toISOString(), vault, slotCount, blobCount, ...meta }
|
|
276
276
|
}
|
|
277
277
|
|
|
278
|
-
export function capturePrePullSnapshot(slots, passphrase, label = 'pre-pull') {
|
|
279
|
-
return defaultSnapshotStore.add(createVaultSnapshot({ slots: slots || [], blobs: {} }, passphrase, label))
|
|
278
|
+
export function capturePrePullSnapshot(slots, passphrase, label = 'pre-pull', meta = {}) {
|
|
279
|
+
return defaultSnapshotStore.add(createVaultSnapshot({ slots: slots || [], blobs: {} }, passphrase, label, meta))
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
export function resolveVaultKey(cfg, passphrase) {
|
|
283
|
+
const perDevice = cfg && cfg.vaultPerDeviceKeys !== false
|
|
284
|
+
const device = resolveDeviceIdentity({ id: cfg && cfg.vaultDeviceId, label: cfg && cfg.vaultDeviceLabel })
|
|
285
|
+
return {
|
|
286
|
+
passphrase: perDevice ? devicePassphrase(passphrase, device.id) : passphrase,
|
|
287
|
+
keyMode: perDevice ? 'device' : 'base',
|
|
288
|
+
deviceId: perDevice ? device.id : '',
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
export function restoreSnapshotWithConfig(snapshot, cfg) {
|
|
293
|
+
const base = String((cfg && cfg.vaultSyncPassphrase) || '').trim()
|
|
294
|
+
if (!base) throw new Error('vaultSyncPassphrase is required to restore a snapshot')
|
|
295
|
+
const attempts = []
|
|
296
|
+
if (snapshot && snapshot.keyMode === 'device') {
|
|
297
|
+
attempts.push(devicePassphrase(base, snapshot.deviceId || resolveVaultKey(cfg, base).deviceId))
|
|
298
|
+
} else if (snapshot && snapshot.keyMode === 'base') {
|
|
299
|
+
attempts.push(base)
|
|
300
|
+
} else {
|
|
301
|
+
// Snapshots written before #749/#754 have no keyMode. Try the legacy
|
|
302
|
+
// base key first, then the current device key.
|
|
303
|
+
attempts.push(base, resolveVaultKey(cfg, base).passphrase)
|
|
304
|
+
}
|
|
305
|
+
let lastError = null
|
|
306
|
+
for (const pass of attempts) {
|
|
307
|
+
try { return restoreVaultSnapshot(snapshot, pass) } catch (err) { lastError = err }
|
|
308
|
+
}
|
|
309
|
+
throw lastError || new Error('unable to restore snapshot')
|
|
280
310
|
}
|
|
281
311
|
|
|
282
312
|
export function restoreVaultSnapshot(snapshot, passphrase) {
|
|
@@ -311,7 +341,7 @@ export async function syncVaultPush({ live, store, fetchFn = fetch }) {
|
|
|
311
341
|
|
|
312
342
|
const device = resolveDeviceIdentity({ id: cfg.vaultDeviceId, label: cfg.vaultDeviceLabel })
|
|
313
343
|
const perDevice = cfg.vaultPerDeviceKeys !== false
|
|
314
|
-
const activePass =
|
|
344
|
+
const activePass = resolveVaultKey(cfg, passphrase).passphrase
|
|
315
345
|
const { vault, slotCount, blobCount } = exportVault({ slots, blobs }, activePass)
|
|
316
346
|
const backend = cfg.vaultSyncBackend || 'webdav'
|
|
317
347
|
|
|
@@ -384,6 +414,7 @@ export async function syncVaultPull({ live, store, settingsApi, syncAdapter, ref
|
|
|
384
414
|
const device = resolveDeviceIdentity({ id: cfg.vaultDeviceId, label: cfg.vaultDeviceLabel })
|
|
385
415
|
const perDevice = cfg.vaultPerDeviceKeys !== false
|
|
386
416
|
let activePass = perDevice ? devicePassphrase(passphrase, device.id) : passphrase
|
|
417
|
+
let snapshotKeyMode = perDevice ? 'device' : 'base'
|
|
387
418
|
let usedLegacy = false
|
|
388
419
|
const load = async (deviceMode) => {
|
|
389
420
|
if (backend === 'webdav') {
|
|
@@ -423,10 +454,16 @@ export async function syncVaultPull({ live, store, settingsApi, syncAdapter, ref
|
|
|
423
454
|
if (!perDevice) throw err
|
|
424
455
|
encryptedData = await load(false)
|
|
425
456
|
activePass = passphrase
|
|
457
|
+
snapshotKeyMode = 'base'
|
|
426
458
|
usedLegacy = true
|
|
427
459
|
}
|
|
428
460
|
|
|
429
|
-
try {
|
|
461
|
+
try {
|
|
462
|
+
capturePrePullSnapshot(cfg.slots || [], activePass, 'pre-pull', {
|
|
463
|
+
keyMode: snapshotKeyMode,
|
|
464
|
+
deviceId: snapshotKeyMode === 'device' ? device.id : '',
|
|
465
|
+
})
|
|
466
|
+
} catch { /* snapshot is best-effort */ }
|
|
430
467
|
const result = importVault(encryptedData, activePass)
|
|
431
468
|
const providerFilter = new Set((cfg.vaultSyncProviders || []).map((p) => String(p)))
|
|
432
469
|
const remoteSlots = providerFilter.size
|
package/lib/vendors/grok.js
CHANGED
|
@@ -132,6 +132,7 @@ function streamHeaders(blob, config, extra) {
|
|
|
132
132
|
hostname = ''
|
|
133
133
|
}
|
|
134
134
|
if (!isTrustedAuthHost(hostname)) {
|
|
135
|
+
/** @type {Error & { code?: string }} */
|
|
135
136
|
const err = new Error(`Untrusted Grok baseUrl host: ${hostname || base}`)
|
|
136
137
|
err.code = 'GROK_UNTRUSTED_HOST'
|
|
137
138
|
throw err
|
package/lib/vendors/index.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@goodandready/dsh-subscriptions",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.12",
|
|
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",
|
|
@@ -39,7 +39,7 @@
|
|
|
39
39
|
"url": "https://github.com/GooDAnDReaDY/dsh-subscriptions/issues"
|
|
40
40
|
},
|
|
41
41
|
"scripts": {
|
|
42
|
-
"test": "eslint lib/ test/ && node --import ./test/_setup.mjs --test test/*.test.mjs",
|
|
42
|
+
"test": "eslint lib/ test/ && npm run typecheck && node --import ./test/_setup.mjs --test test/*.test.mjs",
|
|
43
43
|
"typecheck": "tsc -p tsconfig.checkjs.json"
|
|
44
44
|
},
|
|
45
45
|
"dsh": {
|