@goodandready/dsh-clinebot 0.3.9 → 0.3.10
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/CHANGELOG.md +19 -0
- package/lib/client.js +44 -18
- package/lib/cline-client.js +30 -3
- package/lib/http.js +62 -2
- package/lib/index.js +10 -0
- package/lib/updater.js +32 -1
- package/package.json +3 -2
- package/docs/README.ru.md +0 -199
- package/docs/README.zh.md +0 -201
- package/docs/design/DESIGN.md +0 -88
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,25 @@ All notable changes to `@goodandready/dsh-clinebot` will be documented in this f
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [0.3.10] - 2026-09-16
|
|
9
|
+
|
|
10
|
+
### Changed
|
|
11
|
+
- **UI Color Contrast & Adaptive Theme Compliance** (Gitea Issue #27): Replaced all hardcoded `rgba(...)` background tints and borders with CSS `color-mix(in srgb, var(--dsw-alias-state-...) X%, transparent)` for full legibility across Light and Dark DSH themes.
|
|
12
|
+
- **Kernel Chevron Icon Probe & Fallback** (Gitea Issue #28): Added dynamic safe probe for kernel `IconChevronDownOutline14` from `@deepseek-ai/dsh-client-ui-primitives` with pixel-perfect FallbackChevron and `.cb-chevron` / `.cb-chevron-open` rotation classes.
|
|
13
|
+
- **Single-Bundle Runtime Contract Documentation** (Gitea Issue #29): Formalized client single-bundle architecture and decoupled backend design in `docs/design/DESIGN.md`.
|
|
14
|
+
- **Packaging Sanitization & Denylist Enforcement** (Gitea Issues #25, #26, #30): Purged internal workflow files (`AGENTS.md`, `index.md`, `deploy.sh`, `release-notes.md`) from git tracking and added them to `.gitignore`. Removed redundant duplicate READMEs in `docs/` and outdated root `.tgz` artifacts, reducing unpacked package size to 200 KiB.
|
|
15
|
+
- **Client Module Injection Contract Clarification** (Gitea Issue #32): Documented that `dsh.client.inject: []` in `package.json` is architectural canon for plugins consuming core services (`slots`, `locale`, `settingsScope`) via `exports.inject` rather than require-table imports.
|
|
16
|
+
- **Immediate Quota & Probe Invalidation on Account Switch** (Gitea Issue #31): Connected `clearUsageCache()` and `clearProbeCache()` to account switching routes (`/accounts/active`, `/cline switch`) and `rotateToNextAccount()`, preventing quota telemetry from lagging or sticking to former accounts.
|
|
17
|
+
- **Model Validation in Slash Command** (Gitea Issue #31): Integrated `isSupportedModel()` in `/cline test [model]` to validate target models upfront before issuing upstream requests.
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
### Fixed
|
|
21
|
+
- **LLM Provider Schema Alignment (`reasoningEfforts`)** (GitHub Issue #1, Gitea Issue #35): Fixed Cordis loader validation failure (`$.providers.clinebot.models[0].reasoningEfforts expected false | { [key]: string } but got ["low","medium","high"]`). Properly map array reasoning efforts into a validated object record `{ [effort]: effort }` or `false`, resolving startup provider crash.
|
|
22
|
+
- **Client Localization Fallback Crash** (Gitea Issue #34): Fixed `ReferenceError: ru is not defined` in `lib/client.js` fallback registration path when `ctx.effect` is absent. Removed direct reference to deleted `ru` dictionary.
|
|
23
|
+
- **Settings Persistence Error Visibility** (Gitea Issue #33): Replaced empty/silent `catch {}` blocks around settings persistence in `lib/client.js` and `lib/cline-client.js` with structured warning logs (`ctx.logger.warn`) and user-facing error banners (`setErr`).
|
|
24
|
+
- **Semantic Version Prerelease Comparison in Host Updater** (Gitea Issue #23): Enhanced `isNewerVersion()` in `lib/updater.js` to strictly follow SemVer 2.0.0 rules for pre-release tags, ensuring pre-releases and release candidates update seamlessly.
|
|
25
|
+
- **Hardened Write-Route Security Validation** (Gitea Issue #22): Strengthened `isTrustedSettingsRequest()` in `lib/http.js` to rigorously validate `Origin`, `Host`, `X-Forwarded-Host`, `Referer`, and loopback remote addresses against CSRF, while maintaining full support for reverse proxies and local LAN environments.
|
|
26
|
+
|
|
8
27
|
## [0.3.9] - 2026-09-15
|
|
9
28
|
|
|
10
29
|
### Added
|
package/lib/client.js
CHANGED
|
@@ -208,7 +208,14 @@ const en = {
|
|
|
208
208
|
})
|
|
209
209
|
)
|
|
210
210
|
}
|
|
211
|
-
|
|
211
|
+
let ChevronIcon = null
|
|
212
|
+
try {
|
|
213
|
+
const primitives = require('@deepseek-ai/dsh-client-ui-primitives')
|
|
214
|
+
ChevronIcon = primitives && (primitives.IconChevronDownOutline14 || primitives.IconChevronDownOutline)
|
|
215
|
+
} catch (_) {
|
|
216
|
+
ChevronIcon = null
|
|
217
|
+
}
|
|
218
|
+
const Chevron = ChevronIcon || FallbackChevron
|
|
212
219
|
|
|
213
220
|
function ensureCss() {
|
|
214
221
|
if (typeof document === 'undefined') return
|
|
@@ -218,6 +225,8 @@ const en = {
|
|
|
218
225
|
style.dataset.dshPlugin = NS
|
|
219
226
|
style.textContent = `
|
|
220
227
|
.cb-page{display:flex;flex-direction:column;gap:20px;padding:8px 0 32px;max-width:960px}
|
|
228
|
+
.cb-chevron{display:inline-flex;align-items:center;justify-content:center;transition:transform .16s ease;color:var(--dsw-alias-label-tertiary)}
|
|
229
|
+
.cb-chevron-open{transform:rotate(180deg)}
|
|
221
230
|
.cb-header{display:flex;flex-direction:column;gap:8px;padding-bottom:16px;border-bottom:1px solid var(--dsw-alias-border-l2)}
|
|
222
231
|
.cb-page-title{font-size:22px;font-weight:700;color:var(--dsw-alias-label-primary);display:flex;align-items:center;gap:10px}
|
|
223
232
|
.cb-page-sub{font-size:14px;color:var(--dsw-alias-label-secondary);line-height:1.5}
|
|
@@ -230,9 +239,9 @@ const en = {
|
|
|
230
239
|
.cb-grid-2{display:grid;grid-template-columns:repeat(auto-fit, minmax(280px, 1fr));gap:14px}
|
|
231
240
|
|
|
232
241
|
.cb-badge{font-size:12px;padding:3px 10px;border-radius:999px;border:1px solid var(--dsw-alias-border-l2);display:inline-flex;align-items:center;gap:5px;font-weight:500}
|
|
233
|
-
.cb-badge-ok{border-color:var(--dsw-alias-state-success-primary);color:var(--dsw-alias-state-success-primary);background:
|
|
234
|
-
.cb-badge-warn{border-color:var(--dsw-alias-state-warning-primary);color:var(--dsw-alias-state-warning-primary);background:
|
|
235
|
-
.cb-badge-bad{border-color:var(--dsw-alias-state-error-primary);color:var(--dsw-alias-state-error-primary);background:
|
|
242
|
+
.cb-badge-ok{border-color:var(--dsw-alias-state-success-primary);color:var(--dsw-alias-state-success-primary);background:color-mix(in srgb, var(--dsw-alias-state-success-primary) 8%, transparent)}
|
|
243
|
+
.cb-badge-warn{border-color:var(--dsw-alias-state-warning-primary);color:var(--dsw-alias-state-warning-primary);background:color-mix(in srgb, var(--dsw-alias-state-warning-primary) 8%, transparent)}
|
|
244
|
+
.cb-badge-bad{border-color:var(--dsw-alias-state-error-primary);color:var(--dsw-alias-state-error-primary);background:color-mix(in srgb, var(--dsw-alias-state-error-primary) 8%, transparent)}
|
|
236
245
|
|
|
237
246
|
.cb-input{height:36px;border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-2);color:var(--dsw-alias-label-primary);border-radius:8px;padding:0 12px;font-size:13px;width:100%;box-sizing:border-box}
|
|
238
247
|
.cb-input:focus{outline:none;border-color:var(--dsw-alias-state-brand-primary)}
|
|
@@ -242,8 +251,8 @@ const en = {
|
|
|
242
251
|
.cb-btn:hover:not(:disabled){background:var(--dsw-alias-bg-layer-4, var(--dsw-alias-bg-layer-2));border-color:var(--dsw-alias-label-dimmed, var(--dsw-alias-border-l2))}
|
|
243
252
|
.cb-btn-primary{background:var(--dsw-alias-label-primary);color:var(--dsw-alias-bg-layer-3);border-color:transparent}
|
|
244
253
|
.cb-btn-primary:hover:not(:disabled){background:var(--dsw-alias-label-primary) !important;color:var(--dsw-alias-bg-layer-3) !important;opacity:0.88;visibility:visible !important}
|
|
245
|
-
.cb-btn-danger{color:var(--dsw-alias-state-error-primary);border-color:
|
|
246
|
-
.cb-btn-danger:hover:not(:disabled){background:
|
|
254
|
+
.cb-btn-danger{color:var(--dsw-alias-state-error-primary);border-color:color-mix(in srgb, var(--dsw-alias-state-error-primary) 30%, transparent)}
|
|
255
|
+
.cb-btn-danger:hover:not(:disabled){background:color-mix(in srgb, var(--dsw-alias-state-error-primary) 12%, transparent) !important;border-color:color-mix(in srgb, var(--dsw-alias-state-error-primary) 50%, transparent)}
|
|
247
256
|
.cb-btn-disabled{opacity:0.5;cursor:not-allowed}
|
|
248
257
|
|
|
249
258
|
.cb-bar-container{display:flex;flex-direction:column;gap:6px;padding:12px 14px;border:1px solid var(--dsw-alias-border-l2);border-radius:8px;background:var(--dsw-alias-bg-layer-2)}
|
|
@@ -257,11 +266,11 @@ const en = {
|
|
|
257
266
|
.cb-table td{padding:10px;border-bottom:1px solid var(--dsw-alias-border-l2);font-size:13px;color:var(--dsw-alias-label-primary)}
|
|
258
267
|
.cb-table tr:hover{background:var(--dsw-alias-bg-layer-2)}
|
|
259
268
|
|
|
260
|
-
.cb-alert-ok{padding:10px 14px;border-radius:8px;background:
|
|
261
|
-
.cb-alert-bad{padding:10px 14px;border-radius:8px;background:
|
|
262
|
-
.cb-alert-err{padding:10px 14px;border-radius:8px;background:
|
|
263
|
-
.cb-banner-warning{padding:12px 16px;border-radius:8px;background:
|
|
264
|
-
.cb-banner-exhausted{padding:12px 16px;border-radius:8px;background:
|
|
269
|
+
.cb-alert-ok{padding:10px 14px;border-radius:8px;background:color-mix(in srgb, var(--dsw-alias-state-success-primary) 10%, transparent);color:var(--dsw-alias-state-success-primary);font-size:13px}
|
|
270
|
+
.cb-alert-bad{padding:10px 14px;border-radius:8px;background:color-mix(in srgb, var(--dsw-alias-state-error-primary) 10%, transparent);color:var(--dsw-alias-state-error-primary);font-size:13px}
|
|
271
|
+
.cb-alert-err{padding:10px 14px;border-radius:8px;background:color-mix(in srgb, var(--dsw-alias-state-error-primary) 10%, transparent);color:var(--dsw-alias-state-error-primary);font-size:13px}
|
|
272
|
+
.cb-banner-warning{padding:12px 16px;border-radius:8px;background:color-mix(in srgb, var(--dsw-alias-state-warning-primary) 12%, transparent);border:1px solid var(--dsw-alias-state-warning-primary);color:var(--dsw-alias-state-warning-primary);font-size:13px;display:flex;align-items:center;gap:10px;font-weight:500}
|
|
273
|
+
.cb-banner-exhausted{padding:12px 16px;border-radius:8px;background:color-mix(in srgb, var(--dsw-alias-state-error-primary) 12%, transparent);border:1px solid var(--dsw-alias-state-error-primary);color:var(--dsw-alias-state-error-primary);font-size:13px;display:flex;align-items:center;gap:10px;font-weight:600}
|
|
265
274
|
.cb-stat-box{padding:12px 14px;border:1px solid var(--dsw-alias-border-l2);border-radius:8px;background:var(--dsw-alias-bg-layer-2);display:flex;flex-direction:column;gap:4px}
|
|
266
275
|
.cb-stat-val{font-size:18px;font-weight:700;color:var(--dsw-alias-label-primary)}
|
|
267
276
|
.cb-stat-lbl{font-size:12px;color:var(--dsw-alias-label-secondary)}
|
|
@@ -587,7 +596,11 @@ const en = {
|
|
|
587
596
|
// Pin active account
|
|
588
597
|
async function handlePinAccount(accountEnv) {
|
|
589
598
|
if (scope && snapshotStatus === "ready") {
|
|
590
|
-
try {
|
|
599
|
+
try {
|
|
600
|
+
await scope.set("activeAccount", accountEnv)
|
|
601
|
+
} catch (e) {
|
|
602
|
+
console.warn('[dsh-clinebot] Failed to set activeAccount on scope:', e)
|
|
603
|
+
}
|
|
591
604
|
}
|
|
592
605
|
setBusy(`pin-${accountEnv}`)
|
|
593
606
|
setErr('')
|
|
@@ -620,15 +633,26 @@ const en = {
|
|
|
620
633
|
if (debounceTimerRef.current) clearTimeout(debounceTimerRef.current)
|
|
621
634
|
debounceTimerRef.current = setTimeout(async () => {
|
|
622
635
|
if (scope && snapshotStatus === 'ready') {
|
|
623
|
-
try {
|
|
636
|
+
try {
|
|
637
|
+
await scope.set('disabledModels', nextDisabled)
|
|
638
|
+
} catch (e) {
|
|
639
|
+
console.warn('[dsh-clinebot] Failed to set disabledModels on scope:', e)
|
|
640
|
+
}
|
|
624
641
|
}
|
|
625
642
|
try {
|
|
626
|
-
await fetch(`${ROUTE_PREFIX}/models/toggle`, {
|
|
643
|
+
const res = await fetch(`${ROUTE_PREFIX}/models/toggle`, {
|
|
627
644
|
method: 'POST',
|
|
628
645
|
headers: { 'Content-Type': 'application/json' },
|
|
629
646
|
body: JSON.stringify({ disabledModels: nextDisabled }),
|
|
630
647
|
})
|
|
631
|
-
|
|
648
|
+
const data = await res.json().catch(() => ({}))
|
|
649
|
+
if (!data.ok) {
|
|
650
|
+
setErr(data.error || 'Failed to persist disabled models')
|
|
651
|
+
}
|
|
652
|
+
} catch (e) {
|
|
653
|
+
console.warn('[dsh-clinebot] Failed to persist disabled models:', e)
|
|
654
|
+
setErr(String(e.message || e))
|
|
655
|
+
}
|
|
632
656
|
}, 280)
|
|
633
657
|
}
|
|
634
658
|
|
|
@@ -1219,7 +1243,7 @@ const en = {
|
|
|
1219
1243
|
React.createElement('div', { style: { fontWeight: 600, fontSize: '15px' } }, t('title')),
|
|
1220
1244
|
React.createElement('div', { style: { fontSize: '13px', color: 'var(--dsw-alias-label-secondary)' } }, t('subtitle'))
|
|
1221
1245
|
),
|
|
1222
|
-
React.createElement('span', {
|
|
1246
|
+
React.createElement('span', { className: 'cb-chevron' + (open ? ' cb-chevron-open' : '') },
|
|
1223
1247
|
React.createElement(Chevron)
|
|
1224
1248
|
)
|
|
1225
1249
|
),
|
|
@@ -1255,7 +1279,9 @@ const en = {
|
|
|
1255
1279
|
try {
|
|
1256
1280
|
const s = (ctx?.get && ctx.get('lanSettings')) || ctx?.settingsScope
|
|
1257
1281
|
s?.describe?.()?.load?.()
|
|
1258
|
-
} catch (
|
|
1282
|
+
} catch (e) {
|
|
1283
|
+
console.debug?.('[dsh-clinebot] Polling settings mirror:', e)
|
|
1284
|
+
}
|
|
1259
1285
|
}, 1000)
|
|
1260
1286
|
return () => clearInterval(timer)
|
|
1261
1287
|
}
|
|
@@ -1276,7 +1302,7 @@ const en = {
|
|
|
1276
1302
|
}, 'dsh-clinebot: dictionaries')
|
|
1277
1303
|
} else {
|
|
1278
1304
|
addLocale('en', en)
|
|
1279
|
-
addLocale('
|
|
1305
|
+
addLocale('zh', zh)
|
|
1280
1306
|
}
|
|
1281
1307
|
}
|
|
1282
1308
|
|
package/lib/cline-client.js
CHANGED
|
@@ -493,8 +493,27 @@ export function buildPiAiProvider({
|
|
|
493
493
|
input: hasImage ? ['text', 'image'] : ['text'],
|
|
494
494
|
provider: PROVIDER_ID,
|
|
495
495
|
}
|
|
496
|
+
const validEfforts = ['off', 'minimal', 'low', 'medium', 'high', 'xhigh', 'max']
|
|
496
497
|
if (Array.isArray(item.reasoningEfforts) && item.reasoningEfforts.length) {
|
|
497
|
-
|
|
498
|
+
const efforts = {}
|
|
499
|
+
for (const effort of item.reasoningEfforts) {
|
|
500
|
+
const key = String(effort).trim().toLowerCase()
|
|
501
|
+
if (validEfforts.includes(key)) {
|
|
502
|
+
efforts[key] = key
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
res.reasoningEfforts = Object.keys(efforts).length ? efforts : false
|
|
506
|
+
} else if (item.reasoningEfforts && typeof item.reasoningEfforts === 'object' && !Array.isArray(item.reasoningEfforts)) {
|
|
507
|
+
const efforts = {}
|
|
508
|
+
for (const [k, v] of Object.entries(item.reasoningEfforts)) {
|
|
509
|
+
const key = String(k).trim().toLowerCase()
|
|
510
|
+
if (validEfforts.includes(key)) {
|
|
511
|
+
efforts[key] = typeof v === 'string' && v.trim() ? v.trim() : key
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
res.reasoningEfforts = Object.keys(efforts).length ? efforts : false
|
|
515
|
+
} else {
|
|
516
|
+
res.reasoningEfforts = false
|
|
498
517
|
}
|
|
499
518
|
return res
|
|
500
519
|
})
|
|
@@ -634,7 +653,9 @@ export async function rotateToNextAccount(ctx, cfg, reason = 'rate_limit', setti
|
|
|
634
653
|
const next = { ...cfg, activeAccount: nextAcc.apiKeyEnv }
|
|
635
654
|
await settingsApi.replace(next)
|
|
636
655
|
updated = true
|
|
637
|
-
} catch {
|
|
656
|
+
} catch (err) {
|
|
657
|
+
ctx?.logger?.warn?.('[dsh-clinebot] Failed to persist rotated activeAccount via settingsApi: ' + (err?.message || err))
|
|
658
|
+
}
|
|
638
659
|
} else {
|
|
639
660
|
const settings = (ctx?.get && ctx.get('settings')) || ctx?.settings
|
|
640
661
|
if (settings?.mutate) {
|
|
@@ -643,10 +664,16 @@ export async function rotateToNextAccount(ctx, cfg, reason = 'rate_limit', setti
|
|
|
643
664
|
{ op: 'set', path: ['activeAccount'], value: nextAcc.apiKeyEnv },
|
|
644
665
|
])
|
|
645
666
|
updated = true
|
|
646
|
-
} catch {
|
|
667
|
+
} catch (err) {
|
|
668
|
+
ctx?.logger?.warn?.('[dsh-clinebot] Failed to persist rotated activeAccount via settings.mutate: ' + (err?.message || err))
|
|
669
|
+
}
|
|
647
670
|
}
|
|
648
671
|
}
|
|
649
672
|
|
|
673
|
+
// Reset cached quota and host probes so new account immediately revalidates
|
|
674
|
+
clearUsageCache()
|
|
675
|
+
clearProbeCache()
|
|
676
|
+
|
|
650
677
|
return {
|
|
651
678
|
rotated: true,
|
|
652
679
|
previousAccount: active,
|
package/lib/http.js
CHANGED
|
@@ -28,7 +28,67 @@ export function readBody(req, maxBytes = 256 * 1024) {
|
|
|
28
28
|
})
|
|
29
29
|
}
|
|
30
30
|
|
|
31
|
-
|
|
31
|
+
function header(request, name) {
|
|
32
|
+
const value = request?.headers?.[name.toLowerCase()]
|
|
33
|
+
return Array.isArray(value) ? value[0] : value
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function isLoopbackAddress(value) {
|
|
37
|
+
const address = String(value || '').toLowerCase().replace(/^\[|\]$/g, '')
|
|
38
|
+
return address === 'localhost' || address === 'localhost.' || address === '::1'
|
|
39
|
+
|| address.startsWith('127.')
|
|
40
|
+
|| address.startsWith('::ffff:127.')
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/** Reject cross-site writes while allowing local, LAN, and reverse-proxy UIs. */
|
|
32
44
|
export function isTrustedSettingsRequest(request) {
|
|
33
|
-
|
|
45
|
+
const secFetchSite = header(request, 'sec-fetch-site')
|
|
46
|
+
if (secFetchSite === 'cross-site') {
|
|
47
|
+
return false
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const host = header(request, 'x-forwarded-host') || header(request, 'host')
|
|
51
|
+
const origin = header(request, 'origin')
|
|
52
|
+
if (origin) {
|
|
53
|
+
try {
|
|
54
|
+
const url = new URL(origin)
|
|
55
|
+
if (host && url.host.toLowerCase() === host.toLowerCase()) {
|
|
56
|
+
return true
|
|
57
|
+
}
|
|
58
|
+
if (isLoopbackAddress(url.hostname) && isLoopbackAddress(request?.socket?.remoteAddress)) {
|
|
59
|
+
return true
|
|
60
|
+
}
|
|
61
|
+
return false
|
|
62
|
+
} catch {
|
|
63
|
+
return false
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const referer = header(request, 'referer')
|
|
68
|
+
if (referer) {
|
|
69
|
+
try {
|
|
70
|
+
const url = new URL(referer)
|
|
71
|
+
if (host && url.host.toLowerCase() === host.toLowerCase()) {
|
|
72
|
+
return true
|
|
73
|
+
}
|
|
74
|
+
if (isLoopbackAddress(url.hostname) && isLoopbackAddress(request?.socket?.remoteAddress)) {
|
|
75
|
+
return true
|
|
76
|
+
}
|
|
77
|
+
return false
|
|
78
|
+
} catch {
|
|
79
|
+
return false
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// Requests without origin/referer (e.g. curl or internal requests): allow if loopback
|
|
84
|
+
if (isLoopbackAddress(request?.socket?.remoteAddress)) {
|
|
85
|
+
return true
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// If sec-fetch-site is explicitly same-origin or same-site, allow
|
|
89
|
+
if (secFetchSite === 'same-origin' || secFetchSite === 'same-site') {
|
|
90
|
+
return true
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
return false
|
|
34
94
|
}
|
package/lib/index.js
CHANGED
|
@@ -10,6 +10,7 @@ import {
|
|
|
10
10
|
getAllModels,
|
|
11
11
|
getDefaultModelIds,
|
|
12
12
|
getActiveModelIds,
|
|
13
|
+
isSupportedModel,
|
|
13
14
|
parsePlanIncludedModels,
|
|
14
15
|
saveModelsDiskCache,
|
|
15
16
|
loadModelsDiskCache,
|
|
@@ -33,6 +34,8 @@ import {
|
|
|
33
34
|
sessionStats,
|
|
34
35
|
recordSessionRequest,
|
|
35
36
|
resetSessionStats,
|
|
37
|
+
clearUsageCache,
|
|
38
|
+
clearProbeCache,
|
|
36
39
|
usageCache,
|
|
37
40
|
} from './cline-client.js'
|
|
38
41
|
|
|
@@ -705,6 +708,8 @@ export function apply(ctx, config) {
|
|
|
705
708
|
try { body = JSON.parse(bodyBuf.toString('utf8')) } catch {}
|
|
706
709
|
const account = String(body.account || '').trim()
|
|
707
710
|
|
|
711
|
+
clearUsageCache()
|
|
712
|
+
clearProbeCache()
|
|
708
713
|
if (settingsApi?.replace) {
|
|
709
714
|
const next = Config({ ...live(), activeAccount: account })
|
|
710
715
|
await settingsApi.replace(next)
|
|
@@ -818,6 +823,8 @@ export function apply(ctx, config) {
|
|
|
818
823
|
if (!param) {
|
|
819
824
|
return '⚠️ Please specify account: `/cline switch <CLINEBOT_API_KEY_2>`'
|
|
820
825
|
}
|
|
826
|
+
clearUsageCache()
|
|
827
|
+
clearProbeCache()
|
|
821
828
|
if (settingsApi?.replace) {
|
|
822
829
|
const next = Config({ ...live(), activeAccount: param })
|
|
823
830
|
await settingsApi.replace(next)
|
|
@@ -848,6 +855,9 @@ export function apply(ctx, config) {
|
|
|
848
855
|
|
|
849
856
|
// 6. Subcommand /cline test [model] / smoke
|
|
850
857
|
if (subcmd === 'test' || subcmd === 'smoke') {
|
|
858
|
+
if (param && !isSupportedModel(param, pub.dynamicModels)) {
|
|
859
|
+
return `⚠️ **ClineBot**: Model \`${param}\` is not recognized. Use \`/cline models\` to see available models.`
|
|
860
|
+
}
|
|
851
861
|
const activeKey = await resolveActiveAccountKey(ctx, live())
|
|
852
862
|
if (!activeKey.value) {
|
|
853
863
|
return '⚠️ **ClineBot**: API key is not configured. Open **Settings → Plugins → ClineBot**.'
|
package/lib/updater.js
CHANGED
|
@@ -104,6 +104,37 @@ function parseSemver(value) {
|
|
|
104
104
|
}
|
|
105
105
|
}
|
|
106
106
|
|
|
107
|
+
function comparePrerelease(candidateParts, currentParts) {
|
|
108
|
+
// A normal version with no pre-release tag is newer than a pre-release version
|
|
109
|
+
if (candidateParts.length === 0 && currentParts.length > 0) return 1
|
|
110
|
+
if (candidateParts.length > 0 && currentParts.length === 0) return -1
|
|
111
|
+
if (candidateParts.length === 0 && currentParts.length === 0) return 0
|
|
112
|
+
|
|
113
|
+
const len = Math.max(candidateParts.length, currentParts.length)
|
|
114
|
+
for (let i = 0; i < len; i += 1) {
|
|
115
|
+
const a = candidateParts[i]
|
|
116
|
+
const b = currentParts[i]
|
|
117
|
+
if (a === undefined) return -1
|
|
118
|
+
if (b === undefined) return 1
|
|
119
|
+
if (a === b) continue
|
|
120
|
+
|
|
121
|
+
const aNum = /^\d+$/.test(a) ? Number(a) : undefined
|
|
122
|
+
const bNum = /^\d+$/.test(b) ? Number(b) : undefined
|
|
123
|
+
|
|
124
|
+
if (aNum !== undefined && bNum !== undefined) {
|
|
125
|
+
return aNum > bNum ? 1 : -1
|
|
126
|
+
}
|
|
127
|
+
if (aNum !== undefined && bNum === undefined) {
|
|
128
|
+
return -1
|
|
129
|
+
}
|
|
130
|
+
if (aNum === undefined && bNum !== undefined) {
|
|
131
|
+
return 1
|
|
132
|
+
}
|
|
133
|
+
return a.localeCompare(b) > 0 ? 1 : -1
|
|
134
|
+
}
|
|
135
|
+
return 0
|
|
136
|
+
}
|
|
137
|
+
|
|
107
138
|
export function isNewerVersion(currentValue, candidateValue) {
|
|
108
139
|
const current = parseSemver(currentValue)
|
|
109
140
|
const candidate = parseSemver(candidateValue)
|
|
@@ -111,7 +142,7 @@ export function isNewerVersion(currentValue, candidateValue) {
|
|
|
111
142
|
for (let index = 0; index < 3; index += 1) {
|
|
112
143
|
if (candidate.core[index] !== current.core[index]) return candidate.core[index] > current.core[index]
|
|
113
144
|
}
|
|
114
|
-
return
|
|
145
|
+
return comparePrerelease(candidate.prerelease, current.prerelease) > 0
|
|
115
146
|
}
|
|
116
147
|
|
|
117
148
|
async function fetchLatestVersion(packageName, registry) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@goodandready/dsh-clinebot",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.10",
|
|
4
4
|
"description": "DeepSeek Harness companion for ClineBot / ClinePass: dynamic subscription models sync, quota exhaustion warnings, session metrics, dedicated settings page, live usage limits, and /cline slash-command.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -15,7 +15,8 @@
|
|
|
15
15
|
"lib/",
|
|
16
16
|
"cordis.patch.yml",
|
|
17
17
|
"README.md",
|
|
18
|
-
"
|
|
18
|
+
"README.ru.md",
|
|
19
|
+
"README.zh.md",
|
|
19
20
|
"CHANGELOG.md",
|
|
20
21
|
"LICENSE"
|
|
21
22
|
],
|
package/docs/README.ru.md
DELETED
|
@@ -1,199 +0,0 @@
|
|
|
1
|
-
# 📦 @goodandready/dsh-clinebot
|
|
2
|
-
|
|
3
|
-
<div align="center">
|
|
4
|
-
|
|
5
|
-
<h3>Нативное подключение провайдера ClineBot / ClinePass для DeepSeek Harness</h3>
|
|
6
|
-
|
|
7
|
-
<p align="center">
|
|
8
|
-
<a href="https://www.npmjs.com/package/@goodandready/dsh-clinebot"><img src="https://img.shields.io/npm/v/@goodandready/dsh-clinebot.svg?style=for-the-badge&color=6366f1&labelColor=1e1b4b" alt="npm version"></a>
|
|
9
|
-
<a href="../LICENSE"><img src="https://img.shields.io/github/license/GooDAnDReaDY/dsh-clinebot.svg?style=for-the-badge&color=10b981&labelColor=064e3b" alt="license"></a>
|
|
10
|
-
<a href="https://github.com/topics/dsh-plugin"><img src="https://img.shields.io/badge/DSH-Plugin-8b5cf6.svg?style=for-the-badge&labelColor=2e1065" alt="DSH Plugin"></a>
|
|
11
|
-
<a href="https://nodejs.org"><img src="https://img.shields.io/badge/Node-20%2B-f59e0b.svg?style=for-the-badge&labelColor=451a03" alt="Node version"></a>
|
|
12
|
-
</p>
|
|
13
|
-
|
|
14
|
-
<!-- Обязательная кнопка перехода на витрину всех проектов -->
|
|
15
|
-
<p align="center">
|
|
16
|
-
<a href="https://goodandready.app/"><img src="https://img.shields.io/badge/Все_проекты_автора-goodandready.app-ff4500.svg?style=for-the-badge&logo=rocket&logoColor=white&labelColor=1a1a2e" alt="Все проекты автора"></a>
|
|
17
|
-
</p>
|
|
18
|
-
|
|
19
|
-
<p align="center">
|
|
20
|
-
<a href="README.md"><b>🇬🇧 English</b></a> •
|
|
21
|
-
<a href="README.ru.md"><b>🇷🇺 Русский</b></a> •
|
|
22
|
-
<a href="README.zh.md"><b>🇨🇳 中文说明</b></a>
|
|
23
|
-
</p>
|
|
24
|
-
|
|
25
|
-
<table align="center">
|
|
26
|
-
<tr>
|
|
27
|
-
<td align="center">
|
|
28
|
-
⭐ <strong>Если вам нравится этот плагин, поставьте ему звезду на GitHub</strong> — это покажет мне, что плагин вам полезен, и будет мотивировать меня развивать его дальше.
|
|
29
|
-
<br><br>
|
|
30
|
-
🐛 <strong>Если вы нашли баг или хотите предложить новый функционал</strong>, создайте issue на GitHub на любом языке — я рассмотрю ваше предложение и реализую полезные идеи в одной из следующих версий плагина.
|
|
31
|
-
</td>
|
|
32
|
-
</tr>
|
|
33
|
-
</table>
|
|
34
|
-
|
|
35
|
-
</div>
|
|
36
|
-
|
|
37
|
-
---
|
|
38
|
-
|
|
39
|
-
## ⚡ Обзор и решаемая проблема
|
|
40
|
-
|
|
41
|
-
**ClinePass** (`https://cline.bot`) — сервис единой фиксированной подписки (\$9.99/мес), предоставляющий разработчикам повышенные лимиты (в 2–5 раз выше стандартных) на передовые open-weights модели программирования и рассуждений через единый OpenAI-совместимый интерфейс (`https://api.cline.bot/api/v1`).
|
|
42
|
-
|
|
43
|
-
Интеграция ClinePass в DeepSeek Harness (DSH) напрямую сопряжена со следующими сложностями:
|
|
44
|
-
1. **Отсутствие эндпоинта `/v1/models`**: запрос `GET /v1/models` к `api.cline.bot` возвращает `404 Not Found`, из-за чего динамический поиск моделей в DSH падает или оставляет список пустым.
|
|
45
|
-
2. **Специфический формат идентификаторов**: моделям требуется обязательный префикс `cline-pass/` (например, `cline-pass/deepseek-v4-flash`, `cline-pass/kimi-k3`).
|
|
46
|
-
3. **Отслеживание лимитов**: 5-часовые и недельные скользящие окна расхода токенов требуют прозрачной визуализации в интерфейсе.
|
|
47
|
-
4. **Безопасность ключей**: хранение API-токенов в открытых конфигурациях небезопасно.
|
|
48
|
-
|
|
49
|
-
Плагин **`@goodandready/dsh-clinebot`** решает эти задачи «из коробки»:
|
|
50
|
-
* 🚀 **Обновление в один клик**: Обновление плагина прямо из интерфейса DSH или через защищённый loopback-эндпоинт `/dsh-clinebot/update`.
|
|
51
|
-
* ⚡ **SWR-кэширование квот и здоровья**: Мгновенный ответ (<2 мс) на запросы статуса с фоновым обновлением данных без блокировки UI.
|
|
52
|
-
* 🔀 **Smart Quota-Aware Failover**: Интеллектуальная ротация аккаунтов с обходом исчерпанных лимитов и авто-восстановлением при наступлении `resetsAt`.
|
|
53
|
-
* 🖥️ **Отдельная страница в Настройках**: собственная полноэкранная страница в меню Настроек DSH (`Настройки → ClineBot`).
|
|
54
|
-
* 🔄 **Динамическая синхронизация моделей подписки**: автоматическое получение реального списка моделей из `GET /api/v1/users/me/plan` и мгновенное обновление провайдера DSH в один клик.
|
|
55
|
-
* ⚠️ **Предупреждения об исчерпании квоты**: баннеры предупреждения при достижении 80% (внимание) и 95% (исчерпано) 5-часового лимита с таймером сброса.
|
|
56
|
-
* 📈 **Метрики сессии**: учет количества запросов, расчетных токенов (Prompt / Completion), задержки и времени последнего вызова.
|
|
57
|
-
* 📊 **Дашборд лимитов подписки (Usage)**: наглядные прогресс-бары расхода 5-часового и недельного скользящего окна из официального API `GET /users/me/plan/usage-limits`.
|
|
58
|
-
* 🔑 **Сохранение ключа прямо из UI**: поле ввода ключа с маскировкой; сохранение напрямую в системный сервис `credentials` (`~/.dsh/.credentials.yaml`) без ручной правки файлов на сервере.
|
|
59
|
-
* 🎯 **Управление моделями в пикере**: включение/выключение отображения конкретных моделей в диалогах чата.
|
|
60
|
-
* 💬 **Слэш-команда `/cline` в чате**: просмотр остатка квот, предупреждений, статистики сессии, задержки и активной модели прямо из чата.
|
|
61
|
-
|
|
62
|
-
---
|
|
63
|
-
|
|
64
|
-
## 🏛️ Архитектура
|
|
65
|
-
|
|
66
|
-
```mermaid
|
|
67
|
-
graph LR
|
|
68
|
-
subgraph UI [Интерфейс DSH]
|
|
69
|
-
Page["Отдельная страница (Настройки -> ClineBot)"]
|
|
70
|
-
QuotaBar["Прогресс-бары 5h и недельного лимита + Баннер предупреждений"]
|
|
71
|
-
KeyInput["Ввод и сохранение API-ключа"]
|
|
72
|
-
ModelPick["Динамическая синхронизация моделей и пикер"]
|
|
73
|
-
StatsCard["Метрики и статистика текущей сессии"]
|
|
74
|
-
end
|
|
75
|
-
|
|
76
|
-
subgraph PluginHost [Хост-часть dsh-clinebot]
|
|
77
|
-
HttpEndpoints["API: /api/plugins/dsh-clinebot/*"]
|
|
78
|
-
ClientCore["lib/cline-client.js"]
|
|
79
|
-
ModelCatalog["lib/models.js (Встроенные + Динамические из плана)"]
|
|
80
|
-
SlashCmd["Слэш-команда: /cline"]
|
|
81
|
-
end
|
|
82
|
-
|
|
83
|
-
subgraph DSHCore [Сервисы DSH]
|
|
84
|
-
Credentials["Сервис credentials (~/.dsh/.credentials.yaml)"]
|
|
85
|
-
PiAi["Настройки: llm-pi-ai.providers.clinebot"]
|
|
86
|
-
end
|
|
87
|
-
|
|
88
|
-
subgraph Upstream [Сервер Cline]
|
|
89
|
-
ClinePass["api.cline.bot/api/v1/chat/completions"]
|
|
90
|
-
ClineQuota["api.cline.bot/api/v1/users/me/plan/usage-limits"]
|
|
91
|
-
ClinePlan["api.cline.bot/api/v1/users/me/plan"]
|
|
92
|
-
end
|
|
93
|
-
|
|
94
|
-
Page -->|GET /status & /usage| HttpEndpoints
|
|
95
|
-
KeyInput -->|POST /save-key| HttpEndpoints
|
|
96
|
-
ModelPick -->|POST /models/sync| HttpEndpoints
|
|
97
|
-
HttpEndpoints --> Credentials
|
|
98
|
-
HttpEndpoints --> ClientCore
|
|
99
|
-
ClientCore --> ModelCatalog
|
|
100
|
-
HttpEndpoints -->|Атомарная мутация| PiAi
|
|
101
|
-
ClientCore -->|Чат| ClinePass
|
|
102
|
-
ClientCore -->|Квоты| ClineQuota
|
|
103
|
-
ClientCore -->|Тарифный план| ClinePlan
|
|
104
|
-
```
|
|
105
|
-
|
|
106
|
-
---
|
|
107
|
-
|
|
108
|
-
## ✨ Структура модулей и возможности
|
|
109
|
-
|
|
110
|
-
* **`lib/models.js`**: каталог встроенных моделей ClinePass (11 моделей) и парсер моделей подписки (`parsePlanIncludedModels`, `getAllModels`, `getDynamicModels`).
|
|
111
|
-
* **`lib/cline-client.js`**:
|
|
112
|
-
* `fetchUsageLimits`: параллельный опрос `GET /users/me/plan/usage-limits`, `GET /users/me/plan` и `GET /users/me` с кэшированием в памяти.
|
|
113
|
-
* `sessionStats` / `recordSessionRequest`: счетчики сессии (запросы, токены, задержка, время).
|
|
114
|
-
* `saveCredentialKey`: атомарная запись ключей в `~/.dsh/.credentials.yaml`.
|
|
115
|
-
* `smokeChat`: замер задержки и тестовый пинг с фиксацией статистики.
|
|
116
|
-
* `buildPiAiProvider`: генерация конфигурации провайдера для `llm-pi-ai` (`api: 'openai-completions'`).
|
|
117
|
-
* **`lib/index.js`**: сервис Cordis, регистрация системных маршрутов (включая `/dsh-clinebot/models/sync`), расчет порогов предупреждения квоты и слэш-команды `/cline`.
|
|
118
|
-
* **`lib/client.js`**: полнофункциональный раздел настроек (`settings.section`, order 28) с баннерами предупреждений, карточкой статистики сессии, кнопкой синхронизации моделей плана и аккордеоном плагина (`settings.plugin.item`).
|
|
119
|
-
|
|
120
|
-
---
|
|
121
|
-
|
|
122
|
-
## 📦 Установка
|
|
123
|
-
|
|
124
|
-
```bash
|
|
125
|
-
dsh plugin --profile web add @goodandready/dsh-clinebot
|
|
126
|
-
```
|
|
127
|
-
|
|
128
|
-
Перезапустите экземпляр DeepSeek Harness и обновите вкладку в браузере.
|
|
129
|
-
|
|
130
|
-
---
|
|
131
|
-
|
|
132
|
-
## 💬 Слэш-команда `/cline` в чате
|
|
133
|
-
|
|
134
|
-
В любой сессии чата введите команду `/cline` для проверки остатка лимитов, предупреждений и статистики:
|
|
135
|
-
|
|
136
|
-
```text
|
|
137
|
-
### 🤖 ClinePass Status (ClinePass ($9.99/mo))
|
|
138
|
-
* Пинг хоста: ✅ 210 мс
|
|
139
|
-
* Активный ключ: CLINEBOT_API_KEY (credentials)
|
|
140
|
-
* Модель по умолчанию: `cline-pass/deepseek-v4-flash`
|
|
141
|
-
|
|
142
|
-
⏱ 5-часовое окно: [████░░░░░░] 42% (сброс: 18:00)
|
|
143
|
-
📅 Недельное окно: [██████░░░░] 60% (сброс: 08.09)
|
|
144
|
-
* Аккаунт: `developer@example.com`
|
|
145
|
-
|
|
146
|
-
📈 Статистика текущей сессии:
|
|
147
|
-
* Запросов: 14 вызовов
|
|
148
|
-
* Токены: ~8,450 (Промпт: 6,100 | Ответ: 2,350)
|
|
149
|
-
* Задержка последнего ответа: 210 мс
|
|
150
|
-
```
|
|
151
|
-
|
|
152
|
-
---
|
|
153
|
-
|
|
154
|
-
## ⚙️ Таблица конфигурации (`settings.yaml`)
|
|
155
|
-
|
|
156
|
-
```yaml
|
|
157
|
-
dsh-clinebot:
|
|
158
|
-
enabled: true
|
|
159
|
-
baseUrl: https://api.cline.bot/api/v1
|
|
160
|
-
apiKeyEnv: CLINEBOT_API_KEY
|
|
161
|
-
defaultModel: cline-pass/deepseek-v4-flash
|
|
162
|
-
timeoutMs: 15000
|
|
163
|
-
smokeTimeoutMs: 25000
|
|
164
|
-
enabledModels:
|
|
165
|
-
- cline-pass/deepseek-v4-flash
|
|
166
|
-
- cline-pass/deepseek-v4-pro
|
|
167
|
-
- cline-pass/kimi-k3
|
|
168
|
-
- cline-pass/qwen3.7-max
|
|
169
|
-
dynamicModels: []
|
|
170
|
-
```
|
|
171
|
-
|
|
172
|
-
### Параметры конфигурации
|
|
173
|
-
|
|
174
|
-
| Параметр | Тип | По умолчанию | Описание |
|
|
175
|
-
|:---|:---|:---|:---|
|
|
176
|
-
| `enabled` | `boolean` | `true` | Включение моста провайдера ClineBot |
|
|
177
|
-
| `baseUrl` | `string` | `"https://api.cline.bot/api/v1"` | Базовый URL OpenAI-совместимого API ClinePass |
|
|
178
|
-
| `apiKeyEnv` | `string` | `"CLINEBOT_API_KEY"` | Имя переменной / ключа в хранилище credentials |
|
|
179
|
-
| `defaultModel` | `string` | `"cline-pass/deepseek-v4-flash"` | Модель, выбираемая по умолчанию |
|
|
180
|
-
| `timeoutMs` | `number` | `15000` | Таймаут HTTP-запросов (мс) |
|
|
181
|
-
| `smokeTimeoutMs` | `number` | `25000` | Таймаут тестового пинга (мс) |
|
|
182
|
-
| `enabledModels` | `array` | `[...]` | Список моделей, активных в селекторе чата |
|
|
183
|
-
| `dynamicModels` | `array` | `[]` | Динамические модели, автоматически синхронизированные из тарифа |
|
|
184
|
-
|
|
185
|
-
---
|
|
186
|
-
|
|
187
|
-
## 🧪 Тестирование
|
|
188
|
-
|
|
189
|
-
Запуск автоматического набора тестов:
|
|
190
|
-
|
|
191
|
-
```bash
|
|
192
|
-
npm test
|
|
193
|
-
```
|
|
194
|
-
|
|
195
|
-
---
|
|
196
|
-
|
|
197
|
-
## 📄 Лицензия
|
|
198
|
-
|
|
199
|
-
MIT © [GooDAnDReaDY](https://github.com/GooDAnDReaDY)
|
package/docs/README.zh.md
DELETED
|
@@ -1,201 +0,0 @@
|
|
|
1
|
-
# 📦 @goodandready/dsh-clinebot
|
|
2
|
-
|
|
3
|
-
<div align="center">
|
|
4
|
-
|
|
5
|
-
<h3>适用于 DeepSeek Harness 的 ClineBot / ClinePass 原生模型提供商伴侣插件</h3>
|
|
6
|
-
|
|
7
|
-
<p align="center">
|
|
8
|
-
<a href="https://www.npmjs.com/package/@goodandready/dsh-clinebot"><img src="https://img.shields.io/npm/v/@goodandready/dsh-clinebot.svg?style=for-the-badge&color=6366f1&labelColor=1e1b4b" alt="npm version"></a>
|
|
9
|
-
<a href="../LICENSE"><img src="https://img.shields.io/github/license/GooDAnDReaDY/dsh-clinebot.svg?style=for-the-badge&color=10b981&labelColor=064e3b" alt="license"></a>
|
|
10
|
-
<a href="https://github.com/topics/dsh-plugin"><img src="https://img.shields.io/badge/DSH-Plugin-8b5cf6.svg?style=for-the-badge&labelColor=2e1065" alt="DSH Plugin"></a>
|
|
11
|
-
<a href="https://nodejs.org"><img src="https://img.shields.io/badge/Node-20%2B-f59e0b.svg?style=for-the-badge&labelColor=451a03" alt="Node version"></a>
|
|
12
|
-
</p>
|
|
13
|
-
|
|
14
|
-
<!-- 作者所有项目展示页面链接 -->
|
|
15
|
-
<p align="center">
|
|
16
|
-
<a href="https://goodandready.app/"><img src="https://img.shields.io/badge/作者所有开源项目-goodandready.app-ff4500.svg?style=for-the-badge&logo=rocket&logoColor=white&labelColor=1a1a2e" alt="所有项目"></a>
|
|
17
|
-
</p>
|
|
18
|
-
|
|
19
|
-
<p align="center">
|
|
20
|
-
<a href="README.md"><b>🇬🇧 English</b></a> •
|
|
21
|
-
<a href="README.ru.md"><b>🇷🇺 Русский</b></a> •
|
|
22
|
-
<a href="README.zh.md"><b>🇨🇳 中文说明</b></a>
|
|
23
|
-
</p>
|
|
24
|
-
|
|
25
|
-
<table align="center">
|
|
26
|
-
<tr>
|
|
27
|
-
<td align="center">
|
|
28
|
-
⭐ <strong>如果您喜欢这个插件,请在 GitHub 上为它点亮 Star</strong> — 这能让我知道插件对您有用,并鼓励我继续开发和维护它。
|
|
29
|
-
<br><br>
|
|
30
|
-
🐛 <strong>如果您发现 Bug 或希望增加功能</strong>,请使用任意语言在 GitHub 上提交 Issue — 我会评估您的建议,并在后续版本中实现有价值的改进。
|
|
31
|
-
</td>
|
|
32
|
-
</tr>
|
|
33
|
-
</table>
|
|
34
|
-
|
|
35
|
-
</div>
|
|
36
|
-
|
|
37
|
-
---
|
|
38
|
-
|
|
39
|
-
## ⚡ 概述与解决的核心痛点
|
|
40
|
-
|
|
41
|
-
**ClinePass** (`https://cline.bot`) 是一项固定月费(\$9.99/月)的高性价比订阅服务,为开发者提供主流开源代码模型与推理模型 2–5 倍的高并发调用限额,统一通过 OpenAI 兼容接口 (`https://api.cline.bot/api/v1`) 提供服务。
|
|
42
|
-
|
|
43
|
-
在将 ClinePass 接入 DeepSeek Harness (DSH) 时存在以下挑战:
|
|
44
|
-
1. **缺失 `/v1/models` 接口**:`api.cline.bot` 的 `GET /v1/models` 会直接返回 `404 Not Found`,导致动态模型同步失败或模型列表为空。
|
|
45
|
-
2. **专属模型前缀**:所有模型 ID 均需前缀 `cline-pass/`(如 `cline-pass/deepseek-v4-flash`, `cline-pass/kimi-k3`)。
|
|
46
|
-
3. **用量额度监控**:5 小时滑动窗口与每周限额需要清晰直观的可视化进度监控。
|
|
47
|
-
4. **安全凭据隔离**:禁止在明文配置中直接填写密钥。
|
|
48
|
-
|
|
49
|
-
**`@goodandready/dsh-clinebot`** 完美解决以上痛点:
|
|
50
|
-
* 🚀 **应用内一键更新**:直接在 DSH 界面检查并升级插件,或通过受保护的 `/dsh-clinebot/update` 进行本地安全更新。
|
|
51
|
-
* ⚡ **SWR 配额与健康状态缓存**:状态查询毫秒级响应(<2ms),并在后台静默更新,不阻塞前端渲染。
|
|
52
|
-
* 🔀 **智能配额故障转移 (Smart Failover)**:多账号池自动轮询,避开耗尽账号并在 `resetsAt` 到达后自动恢复。
|
|
53
|
-
* 🖥️ **专属设置大页**:在 DSH 设置中提供全宽独立页面(`设置 → ClineBot`)。
|
|
54
|
-
* 🔄 **订阅模型动态同步**:从官方 `GET /api/v1/users/me/plan` 自动提取真实包含模型,一键原子级同步至 DSH 提供商配置,无需等待插件更新。
|
|
55
|
-
* ⚠️ **额度耗尽实时预警**:当 5 小时滑动窗口达到 80%(警告黄色)和 95%(即将耗尽红色)时展示醒目预警横幅与重置倒计时。
|
|
56
|
-
* 📈 **会话统计与指标看板**:实时追踪请求调用次数、预估 Token(Prompt / Completion)、最近延迟及最后调用时间。
|
|
57
|
-
* 📊 **实时用量仪表盘**:调用官方 `GET /users/me/plan/usage-limits` API,实时渲染 5 小时与每周额度进度条及重置倒计时。
|
|
58
|
-
* 🔑 **界面直存密钥**:在 UI 中直接粘贴 API 密钥,通过 `ctx.credentials.set()` 自动安全保存至 `~/.dsh/.credentials.yaml`。
|
|
59
|
-
* 🎯 **模型选择器管理**:支持勾选开启/关闭特定模型在聊天选择器中的显示。
|
|
60
|
-
* 💬 **聊天斜杠指令 `/cline`**:在任意聊天框快速查询当前配额、预警横幅、会话指标统计、网络延迟与活跃模型。
|
|
61
|
-
|
|
62
|
-
---
|
|
63
|
-
|
|
64
|
-
## 🏛️ 架构设计
|
|
65
|
-
|
|
66
|
-
```mermaid
|
|
67
|
-
graph LR
|
|
68
|
-
subgraph UI [DSH Web 前端界面]
|
|
69
|
-
Page["独立配置页 (设置 -> ClineBot)"]
|
|
70
|
-
QuotaBar["5小时与每周额度进度条 + 额度预警横幅"]
|
|
71
|
-
KeyInput["API 密钥直填与安全保存"]
|
|
72
|
-
ModelPick["模型动态同步与选择器管控"]
|
|
73
|
-
StatsCard["会话指标监控看板"]
|
|
74
|
-
end
|
|
75
|
-
|
|
76
|
-
subgraph PluginHost [dsh-clinebot 宿主运行环境]
|
|
77
|
-
HttpEndpoints["API 路由: /api/plugins/dsh-clinebot/*"]
|
|
78
|
-
ClientCore["lib/cline-client.js"]
|
|
79
|
-
ModelCatalog["lib/models.js (内置精选 + 动态订阅解析)"]
|
|
80
|
-
SlashCmd["斜杠指令: /cline"]
|
|
81
|
-
end
|
|
82
|
-
|
|
83
|
-
subgraph DSHCore [DSH 核心系统服务]
|
|
84
|
-
Credentials["凭据存储服务 (~/.dsh/.credentials.yaml)"]
|
|
85
|
-
PiAi["模型注册: llm-pi-ai.providers.clinebot"]
|
|
86
|
-
end
|
|
87
|
-
|
|
88
|
-
subgraph Upstream [Cline 官方云端]
|
|
89
|
-
ClinePass["api.cline.bot/api/v1/chat/completions"]
|
|
90
|
-
ClineQuota["api.cline.bot/api/v1/users/me/plan/usage-limits"]
|
|
91
|
-
ClinePlan["api.cline.bot/api/v1/users/me/plan"]
|
|
92
|
-
end
|
|
93
|
-
|
|
94
|
-
Page -->|GET /status & /usage| HttpEndpoints
|
|
95
|
-
KeyInput -->|POST /save-key| HttpEndpoints
|
|
96
|
-
ModelPick -->|POST /models/sync| HttpEndpoints
|
|
97
|
-
HttpEndpoints --> Credentials
|
|
98
|
-
HttpEndpoints --> ClientCore
|
|
99
|
-
ClientCore --> ModelCatalog
|
|
100
|
-
HttpEndpoints -->|原子级写入| PiAi
|
|
101
|
-
ClientCore -->|模型对话| ClinePass
|
|
102
|
-
ClientCore -->|额度查询| ClineQuota
|
|
103
|
-
ClientCore -->|套餐信息| ClinePlan
|
|
104
|
-
```
|
|
105
|
-
|
|
106
|
-
---
|
|
107
|
-
|
|
108
|
-
## ✨ 核心模块与功能
|
|
109
|
-
|
|
110
|
-
* **`lib/models.js`**:管理 11 款官方精选内置模型以及套餐模型动态解析器(`parsePlanIncludedModels`, `getAllModels`, `getDynamicModels`)。
|
|
111
|
-
* **`lib/cline-client.js`**:
|
|
112
|
-
* `fetchUsageLimits`:高效并发轮询 `GET /users/me/plan/usage-limits`、`GET /users/me/plan` 与 `GET /users/me` 并进行内存缓存。
|
|
113
|
-
* `sessionStats` / `recordSessionRequest`:内存级会话度量记录器(请求次数、Token 估算、延迟、时间戳)。
|
|
114
|
-
* `saveCredentialKey`:将密钥安全写入 `~/.dsh/.credentials.yaml`。
|
|
115
|
-
* `smokeChat`:毫秒级网络探活与非流式延迟测试,并记录会话指标。
|
|
116
|
-
* `buildPiAiProvider`:构建 DSH `llm-pi-ai` 兼容的服务商定义 (`api: 'openai-completions'`)。
|
|
117
|
-
* **`lib/index.js`**:Cordis 插件主生命周期服务,注册后端 REST API 路由(含 `/dsh-clinebot/models/sync`)、额度预警计算与 `/cline` 聊天斜杠指令。
|
|
118
|
-
* **`lib/client.js`**:前端设置面板(`settings.section` 序号 28),内含预警横幅、会话指标卡片、一键模型同步按钮及插件折叠卡片。
|
|
119
|
-
|
|
120
|
-
---
|
|
121
|
-
|
|
122
|
-
## 📦 快速安装
|
|
123
|
-
|
|
124
|
-
在 DeepSeek Harness Web 配置中安装:
|
|
125
|
-
|
|
126
|
-
```bash
|
|
127
|
-
dsh plugin --profile web add @goodandready/dsh-clinebot
|
|
128
|
-
```
|
|
129
|
-
|
|
130
|
-
重启 DeepSeek Harness 实例并刷新浏览器页面。
|
|
131
|
-
|
|
132
|
-
---
|
|
133
|
-
|
|
134
|
-
## 💬 聊天斜杠指令 `/cline`
|
|
135
|
-
|
|
136
|
-
在任何聊天会话中输入 `/cline` 即可即时检查配额、预警状态与会话指标:
|
|
137
|
-
|
|
138
|
-
```text
|
|
139
|
-
### 🤖 ClinePass Status (ClinePass ($9.99/mo))
|
|
140
|
-
* 响应延迟: ✅ 210 ms
|
|
141
|
-
* 活跃密钥: CLINEBOT_API_KEY (credentials)
|
|
142
|
-
* 默认模型: `cline-pass/deepseek-v4-flash`
|
|
143
|
-
|
|
144
|
-
⏱ 5 小时窗口: [████░░░░░░] 42% (重置时间: 18:00)
|
|
145
|
-
📅 每周窗口: [██████░░░░] 60% (重置时间: 09月08日)
|
|
146
|
-
* 绑定账号: `developer@example.com`
|
|
147
|
-
|
|
148
|
-
📈 当前会话统计:
|
|
149
|
-
* 请求次数: 14 次
|
|
150
|
-
* Token 估算: ~8,450 (Prompt: 6,100 | Completion: 2,350)
|
|
151
|
-
* 最近延迟: 210 ms
|
|
152
|
-
```
|
|
153
|
-
|
|
154
|
-
---
|
|
155
|
-
|
|
156
|
-
## ⚙️ 配置项参考 (`settings.yaml`)
|
|
157
|
-
|
|
158
|
-
```yaml
|
|
159
|
-
dsh-clinebot:
|
|
160
|
-
enabled: true
|
|
161
|
-
baseUrl: https://api.cline.bot/api/v1
|
|
162
|
-
apiKeyEnv: CLINEBOT_API_KEY
|
|
163
|
-
defaultModel: cline-pass/deepseek-v4-flash
|
|
164
|
-
timeoutMs: 15000
|
|
165
|
-
smokeTimeoutMs: 25000
|
|
166
|
-
enabledModels:
|
|
167
|
-
- cline-pass/deepseek-v4-flash
|
|
168
|
-
- cline-pass/deepseek-v4-pro
|
|
169
|
-
- cline-pass/kimi-k3
|
|
170
|
-
- cline-pass/qwen3.7-max
|
|
171
|
-
dynamicModels: []
|
|
172
|
-
```
|
|
173
|
-
|
|
174
|
-
### 配置参数说明
|
|
175
|
-
|
|
176
|
-
| 参数项 | 类型 | 默认值 | 说明 |
|
|
177
|
-
|:---|:---|:---|:---|
|
|
178
|
-
| `enabled` | `boolean` | `true` | 是否启用 ClineBot 桥接插件 |
|
|
179
|
-
| `baseUrl` | `string` | `"https://api.cline.bot/api/v1"` | ClinePass OpenAI 兼容接口地址 |
|
|
180
|
-
| `apiKeyEnv` | `string` | `"CLINEBOT_API_KEY"` | 凭据管理系统中的密钥名称 |
|
|
181
|
-
| `defaultModel` | `string` | `"cline-pass/deepseek-v4-flash"` | 默认选中的模型 ID |
|
|
182
|
-
| `timeoutMs` | `number` | `15000` | HTTP 请求超时时间(毫秒) |
|
|
183
|
-
| `smokeTimeoutMs` | `number` | `25000` | 探活测试超时时间(毫秒) |
|
|
184
|
-
| `enabledModels` | `array` | `[...]` | 允许在聊天下拉框中显示的可用模型列表 |
|
|
185
|
-
| `dynamicModels` | `array` | `[]` | 从官方套餐中自动同步的动态模型列表 |
|
|
186
|
-
|
|
187
|
-
---
|
|
188
|
-
|
|
189
|
-
## 🧪 测试
|
|
190
|
-
|
|
191
|
-
运行自动化测试套件:
|
|
192
|
-
|
|
193
|
-
```bash
|
|
194
|
-
npm test
|
|
195
|
-
```
|
|
196
|
-
|
|
197
|
-
---
|
|
198
|
-
|
|
199
|
-
## 📄 许可证
|
|
200
|
-
|
|
201
|
-
MIT © [GooDAnDReaDY](https://github.com/GooDAnDReaDY)
|
package/docs/design/DESIGN.md
DELETED
|
@@ -1,88 +0,0 @@
|
|
|
1
|
-
# Design Contract: `@goodandready/dsh-clinebot`
|
|
2
|
-
|
|
3
|
-
## 1. Executive Summary
|
|
4
|
-
`@goodandready/dsh-clinebot` is a companion plugin for DeepSeek Harness (DSH) enabling native integration of the **ClineBot / ClinePass** subscription provider. Because ClinePass is an OpenAI-compatible endpoint whose `GET /v1/models` returns `404 Not Found`, dynamic discovery is impossible. This plugin acts as the bridge: delivering a curated catalog of open-weights models, securely resolving credentials, exposing health and smoke tests, and mutating the DSH `llm-pi-ai` provider registry.
|
|
5
|
-
|
|
6
|
-
## 2. Architecture & Cordis Lifecycles
|
|
7
|
-
The plugin consists of two runtime boundaries conforming to DSH authoring standards:
|
|
8
|
-
|
|
9
|
-
### 2.1 Host Runtime (`lib/index.js`, `lib/cline-client.js`, `lib/models.js`, `lib/http.js`)
|
|
10
|
-
* **Cordis Service Registration**: Declares `inject = ['settings', 'webServer', 'credentials']` and registers the settings namespace dynamically via `ctx.inject(['settings'], (sctx) => { sctx.settings.register(NS, Config, { base: config }) })`. This guarantees that the configuration schema, default values, and reactive watchers are declared and available to the host and client settingsScope without timing issues.
|
|
11
|
-
* **Safe Service Resolution**: Service lookups utilize defensive proxy resolution `(ctx?.get && ctx.get('credentials')) || ctx?.credentials` to prevent `undefined` properties on Cordis proxies.
|
|
12
|
-
* **Credential Isolation**: The plugin NEVER stores plain API keys in its configuration. The setting `apiKeyEnv` holds the credential identifier (default: `CLINEBOT_API_KEY`), resolved via `ctx.get('credentials').resolve()` or `process.env`.
|
|
13
|
-
* **State Synchronization & Auto-Registration**: Mutates the core `llm-pi-ai` settings space (`op: 'set', path: ['providers', 'clinebot']`) declaratively and automatically when enabled or key is saved.
|
|
14
|
-
* **Auto-Discovery & `disabledModels`**: Features automatic background polling of subscription plan models (`GET /api/v1/users/me/plan`). User preferences are tracked via `disabledModels: []`, ensuring newly added plan models appear enabled by default in the DSH chat picker without manual re-synchronization.
|
|
15
|
-
|
|
16
|
-
### 2.2 Client Runtime (`lib/client.js`)
|
|
17
|
-
* Self-registering module via `window.__ModuleLoader__.load({ id: '@goodandready/dsh-clinebot', factory })`.
|
|
18
|
-
* Injects `['slots', 'locale', 'settingsScope']`.
|
|
19
|
-
* Slots strictly and exclusively into `settings.plugin.item` (`key: NS`, `locale: NS`). Standalone top-level `settings.section` registration is omitted to maintain clean primary navigation in DSH and prevent side-list pollution.
|
|
20
|
-
* Uses `refreshMirrorUntilVisible(ctx)` to invalidate and re-read the client settings mirror until the namespace is reported ready by the host.
|
|
21
|
-
* Registers localized `en` and `zh` dictionaries with duplicate-safe guards (`ctx.locale.register()`), while Russian translation is modularly supplied by `dsh-russian-lang`.
|
|
22
|
-
* Reactive binding via `((ctx?.get && ctx.get('lanSettings')) || ctx?.settingsScope).bind({ namespace: NS })` with `useSyncExternalStore` guarding against `unavailable` / `loading` snapshot states. Form mutations write directly to `scope.set()`.
|
|
23
|
-
* Uses native design tokens (`--dsw-alias-...`) with full dark/light theme support.
|
|
24
|
-
* Injects isolated style tag tagged with `data-dsh-plugin="dsh-clinebot"`.
|
|
25
|
-
|
|
26
|
-
```mermaid
|
|
27
|
-
graph LR
|
|
28
|
-
subgraph Client [DSH Web Interface]
|
|
29
|
-
UI[Settings Card: ClineBot]
|
|
30
|
-
SmokeBtn[Smoke Test Button]
|
|
31
|
-
RegBtn[Register in DSH Models]
|
|
32
|
-
end
|
|
33
|
-
|
|
34
|
-
subgraph Host [DSH Node.js Runtime]
|
|
35
|
-
API["HTTP API: /api/plugins/dsh-clinebot/*"]
|
|
36
|
-
ClientHelper["lib/cline-client.js"]
|
|
37
|
-
Catalog["lib/models.js (Static 11 Models)"]
|
|
38
|
-
CredService[DSH Credentials Service]
|
|
39
|
-
PiAiSettings["DSH Settings: llm-pi-ai"]
|
|
40
|
-
end
|
|
41
|
-
|
|
42
|
-
subgraph Remote [Cline Service]
|
|
43
|
-
ClineAPI["api.cline.bot/api/v1"]
|
|
44
|
-
end
|
|
45
|
-
|
|
46
|
-
UI -->|GET /status| API
|
|
47
|
-
SmokeBtn -->|POST /smoke| API
|
|
48
|
-
RegBtn -->|POST /register| API
|
|
49
|
-
API --> CredService
|
|
50
|
-
API --> ClientHelper
|
|
51
|
-
ClientHelper --> Catalog
|
|
52
|
-
API -->|Mutate| PiAiSettings
|
|
53
|
-
ClientHelper -->|POST /chat/completions| ClineAPI
|
|
54
|
-
```
|
|
55
|
-
|
|
56
|
-
## 3. UI/UX Contract
|
|
57
|
-
* **Badges**:
|
|
58
|
-
* Host connectivity: `Host online (<ms>)` (green) / `Host unreachable` (red).
|
|
59
|
-
* Credential presence: `Key ✓ (credentials|env)` (green) / `Key missing` (amber).
|
|
60
|
-
* Registration status: `DSH Registered` (green) / `Not Registered` (amber).
|
|
61
|
-
* **Model Picker**: Interactive checklist of all 11 official models with multi-select and vision capability indicators.
|
|
62
|
-
* **Non-destructive actions**: Unregister cleanly removes the provider entry from DSH without touching other providers or configurations.
|
|
63
|
-
|
|
64
|
-
## 4. Security & Isolation
|
|
65
|
-
* CSRF / Cross-site protection: All mutating routes (`/register`, `/unregister`, `/smoke`, `/models`, `/accounts/active`, `/auth/begin`) validate `isTrustedSettingsRequest(req)` (`Sec-Fetch-Site !== 'cross-site'`).
|
|
66
|
-
* Body size limits: Request payloads are strictly capped at 256 KB.
|
|
67
|
-
* Sensitive credential data is never returned across the HTTP API (only `{ present: boolean, source: string, envName: string }`).
|
|
68
|
-
|
|
69
|
-
## 5. Multi-Account Pool & Resilient Execution (v0.3.3)
|
|
70
|
-
* **Account Pool**: The plugin supports multiple accounts (`accounts: [{ label, apiKeyEnv }]`, `activeAccount`). `resolveActiveAccountKey()` automatically selects the configured active account or falls back to primary `apiKeyEnv`. Account switching (`POST /dsh-clinebot/accounts/active` and `/cline switch <label>`) triggers instant re-registration in `llm-pi-ai` without service restart.
|
|
71
|
-
* **Resilient Retry Policy**: HTTP calls to ClinePass utilize `retryWithBackoff()` with exponential delays and jitter to automatically absorb transient 429 rate-limiting events and upstream 5xx errors.
|
|
72
|
-
* **Reasoning Effort Support**: Models declaring `reasoningEfforts: ['low', 'medium', 'high', 'max']` expose native thinking controls within the DSH model picker, accompanied by UI badges (`🧠 Reasoning`).
|
|
73
|
-
* **Offline Cold-Start Cache**: Discovered plan models are serialized locally to `modelsCachePath` (`~/.dsh/clinebot-models-cache.json`), ensuring models remain immediately available on cold boot even if the upstream network or Cline API is temporarily unavailable.
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
## 6. Performance, Resilience & Telemetry (v0.3.8)
|
|
77
|
-
* **Stale-While-Revalidate (SWR) Network Probing**: `probeHealth()` utilizes an in-memory SWR cache (`probeCache`) with 25s TTL. Repeated `/status` queries return instantaneously (<1ms latency) with fresh host availability, asynchronously refreshing network latency in the background without blocking the client UI thread.
|
|
78
|
-
* **HTTP Keep-Alive Connection Reuse**: Outbound fetch calls to `api.cline.bot` enforce persistent connection keepalive (`keepalive: true`), eliminating recurrent TLS handshake and TCP connection establishment latency.
|
|
79
|
-
* **Auto-Failover Account Rotation**: When an active account encounters HTTP 429 (Rate Limit) or 100% quota depletion, `rotateToNextAccount()` automatically selects the next configured account in the pool, applies the update to DSH settings, and re-synchronizes credentials in `llm-pi-ai` in real time.
|
|
80
|
-
* **Accurate Token Telemetry**: Real usage metadata (`prompt_tokens`, `completion_tokens`, `total_tokens`) is parsed directly from chat completion responses and tracked in session telemetry (`sessionStats`).
|
|
81
|
-
* **Expanded Slash Commands**: Slash command `/cline` supports `/cline test [model]` (smoke test with latency, response and token metrics), `/cline ping` (real-time host connectivity test), and `/cline rotate` (round-robin active account rotation).
|
|
82
|
-
* **Debounced Model Selection**: Model exclusion checkboxes in `lib/client.js` utilize immediate optimistic UI rendering paired with a 280ms debounced persistence layer, ensuring smooth interaction without request thrashing.
|
|
83
|
-
|
|
84
|
-
## 7. Stability, SWR Quotas, Smart Failover & One-Click In-App Updater (v0.3.9)
|
|
85
|
-
* **SWR Quota Caching (`fetchUsageLimits`)**: In-memory Stale-While-Revalidate caching for Cline usage limits (20s TTL). Returns quota statistics (<2ms) immediately on `/status` requests while refreshing quota windows in the background.
|
|
86
|
-
* **Smart Quota-Aware Failover**: Account failover evaluates cached rolling limits (5-hour window), automatically prioritizing accounts with the lowest `percentUsed` and respecting `resetsAt` timestamps for automatic account recovery.
|
|
87
|
-
* **One-Click In-App Updater (`/dsh-clinebot/update`)**: Integrates host-side one-click updater (`lib/updater.js`) with security verification (`isTrustedUpdateRequest`: loopback validation, same-origin checks, `x-dsh-plugin-update: 1` header). Allows seamless in-place updates from DSH UI.
|
|
88
|
-
* **Canonical DSH Localization Standard**: Source code complies with canonical DSH standards (English base canon, complete Chinese `zh` locale registration in client, external Russian translation provided by `dsh-russian-lang`). All slash command responses and system logs are localized to English.
|