@goodandready/dsh-clinebot 0.3.15 → 0.3.17
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 +22 -0
- package/lib/access.js +2 -1
- package/lib/client.js +6 -4
- package/lib/cline-client.js +41 -9
- package/lib/config.js +2 -2
- package/lib/index.js +8 -1
- package/lib/models.js +15 -71
- package/lib/provider-sync.js +4 -4
- package/lib/routes/accounts.js +6 -12
- package/lib/routes/auth.js +4 -8
- package/lib/routes/models.js +4 -8
- package/lib/routes/settings.js +3 -5
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,28 @@ 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.17] - 2026-09-21
|
|
9
|
+
|
|
10
|
+
### Fixed
|
|
11
|
+
- **Cache Resilience (#50)**: Fixed permanent `isRevalidating` flag lock in `usageCache` and `probeCache` upon background SWR revalidation failures. The flag is now guaranteed to reset in `finally` and `catch` blocks.
|
|
12
|
+
- **Immediate Abort Handling (#52)**: Eliminated artificial 250ms delay and futile retry upon `AbortError` or `signal.aborted` in `fetchUsageLimits`.
|
|
13
|
+
- **Lifecycle Timer Cleanup (#53)**: Wrapped `autoDiscover` timer in `ctx.effect` with cleanup handler to avoid orphaned background callbacks upon plugin context unload.
|
|
14
|
+
- **UI Version Hardcode Removal (#56)**: Replaced hardcoded legacy `'0.3.12'` string in initial React state with dynamic retrieval from `/update` endpoint and conditional badge rendering.
|
|
15
|
+
|
|
16
|
+
### Performance
|
|
17
|
+
- **Single Account Pool Resolution (#51)**: Eliminated duplicate sequential IPC queries to DSH Credentials by passing the pre-resolved account pool into `resolveActiveAccountKey` within `buildStatus`.
|
|
18
|
+
- **Bounded Cache with Auto-Eviction (#57)**: Enforced `MAX_CACHE_ENTRIES = 50` and automatic eviction of expired entries in `usageCache` and `probeCache` to prevent long-term memory leaks.
|
|
19
|
+
|
|
20
|
+
### Refactored
|
|
21
|
+
- **Active Route Guard (#54)**: Converted `assertTrustedSettingsRequest` into an active route guard writing `403 Forbidden` and applied it across all write endpoints in `lib/routes/`.
|
|
22
|
+
- **Dead Export Wiring (#55)**: Wired `getDefaultModelIds` helper in `lib/config.js` to compute `allDefaultIds`, clearing preflight dead-export warnings.
|
|
23
|
+
|
|
24
|
+
## [0.3.16] - 2026-09-20
|
|
25
|
+
|
|
26
|
+
### Fixed
|
|
27
|
+
- **Strict Active Subscription Plan Model Filtering (#48)**: Correctly parsed `plan.features.included` array from `GET /users/me/plan` to discover exactly the 11 active ClinePass subscription models. Removed non-subscription models from the default `CLINE_MODELS` list (eliminating out-of-plan failures and preventing 400+ models from cluttering the DSH picker).
|
|
28
|
+
- **Array Parsing in `parsePlanIncludedModels`**: Added support for both string and array inputs (extracting feature entries matching `/includes\s+/i`).
|
|
29
|
+
|
|
8
30
|
## [0.3.15] - 2026-09-19
|
|
9
31
|
|
|
10
32
|
### Fixed
|
package/lib/access.js
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
import { isTrustedSettingsRequest } from './http.js'
|
|
1
|
+
import { isTrustedSettingsRequest, writeJson } from './http.js'
|
|
2
2
|
|
|
3
3
|
export { isTrustedSettingsRequest }
|
|
4
4
|
|
|
5
5
|
export function assertTrustedSettingsRequest(req, res) {
|
|
6
6
|
if (!isTrustedSettingsRequest(req)) {
|
|
7
|
+
writeJson(res, 403, { ok: false, error: 'Forbidden' })
|
|
7
8
|
return false
|
|
8
9
|
}
|
|
9
10
|
return true
|
package/lib/client.js
CHANGED
|
@@ -402,9 +402,11 @@ function UpdateBanner({ updateState, handleTriggerUpdate, t }) {
|
|
|
402
402
|
React.createElement(
|
|
403
403
|
'div',
|
|
404
404
|
{ style: { display: 'flex', alignItems: 'center', gap: '8px', fontSize: '13px' } },
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
405
|
+
updateState.currentVersion
|
|
406
|
+
? React.createElement('span', { style: { color: 'var(--dsw-alias-label-secondary)', fontWeight: 500 } },
|
|
407
|
+
`v${updateState.currentVersion}`
|
|
408
|
+
)
|
|
409
|
+
: null,
|
|
408
410
|
updateState.checking
|
|
409
411
|
? React.createElement('span', { style: { color: 'var(--dsw-alias-label-tertiary)', fontSize: '12px' } },
|
|
410
412
|
t('update.checking')
|
|
@@ -882,7 +884,7 @@ function SettingsPage(props) {
|
|
|
882
884
|
const [updateState, setUpdateState] = React.useState({
|
|
883
885
|
checking: false,
|
|
884
886
|
updating: false,
|
|
885
|
-
currentVersion: '
|
|
887
|
+
currentVersion: '',
|
|
886
888
|
latestVersion: '',
|
|
887
889
|
updateAvailable: false,
|
|
888
890
|
canAutoUpdate: true,
|
package/lib/cline-client.js
CHANGED
|
@@ -132,9 +132,26 @@ function abortAfter(ms) {
|
|
|
132
132
|
}
|
|
133
133
|
|
|
134
134
|
// In-memory caches for quota queries and host health probes
|
|
135
|
+
export const MAX_CACHE_ENTRIES = 50
|
|
135
136
|
export const usageCache = new Map()
|
|
136
137
|
export const probeCache = new Map()
|
|
137
138
|
|
|
139
|
+
export function setBoundedCache(map, key, value, maxEntries = MAX_CACHE_ENTRIES) {
|
|
140
|
+
if (map.size >= maxEntries) {
|
|
141
|
+
const now = Date.now()
|
|
142
|
+
for (const [k, v] of map.entries()) {
|
|
143
|
+
if (v.expiresAt && v.expiresAt < now) {
|
|
144
|
+
map.delete(k)
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
if (map.size >= maxEntries) {
|
|
148
|
+
const oldestKey = map.keys().next().value
|
|
149
|
+
if (oldestKey) map.delete(oldestKey)
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
map.set(key, value)
|
|
153
|
+
}
|
|
154
|
+
|
|
138
155
|
export function clearProbeCache() {
|
|
139
156
|
probeCache.clear()
|
|
140
157
|
}
|
|
@@ -212,8 +229,10 @@ export async function fetchUsageLimits(baseUrl, apiKey, {
|
|
|
212
229
|
keepalive: true,
|
|
213
230
|
})
|
|
214
231
|
} catch (netErr) {
|
|
232
|
+
if (signal?.aborted || netErr?.name === 'AbortError') throw netErr
|
|
215
233
|
// Quick 1-retry fallback on transient network drop
|
|
216
234
|
await new Promise((r) => setTimeout(r, 250))
|
|
235
|
+
if (signal?.aborted || netErr?.name === 'AbortError') throw netErr
|
|
217
236
|
limitsRes = await fetchImpl(`${base}/users/me/plan/usage-limits`, {
|
|
218
237
|
method: 'GET',
|
|
219
238
|
headers,
|
|
@@ -277,12 +296,13 @@ export async function fetchUsageLimits(baseUrl, apiKey, {
|
|
|
277
296
|
|
|
278
297
|
if (planRes?.ok) {
|
|
279
298
|
const planData = await planRes.json().catch(() => ({}))
|
|
280
|
-
const plan = planData?.data || planData
|
|
281
|
-
if (plan?.title || plan?.name) {
|
|
282
|
-
planDisplayName = `${plan.title || plan.name} ($${((plan.priceInCents || 999) / 100).toFixed(2)}/mo)`
|
|
299
|
+
const plan = planData?.data?.plan || planData?.data || planData?.plan || planData
|
|
300
|
+
if (plan?.displayName || plan?.title || plan?.name) {
|
|
301
|
+
planDisplayName = `${plan.displayName || plan.title || plan.name} ($${((plan.pricePerSeatCents || plan.priceInCents || 999) / 100).toFixed(2)}/mo)`
|
|
283
302
|
}
|
|
284
|
-
|
|
285
|
-
|
|
303
|
+
const featuresIncluded = plan?.features?.included || plan?.includedModels
|
|
304
|
+
if (featuresIncluded) {
|
|
305
|
+
dynamicModels = parsePlanIncludedModels(featuresIncluded)
|
|
286
306
|
}
|
|
287
307
|
}
|
|
288
308
|
} catch {
|
|
@@ -298,7 +318,7 @@ export async function fetchUsageLimits(baseUrl, apiKey, {
|
|
|
298
318
|
checkedAt: Date.now(),
|
|
299
319
|
}
|
|
300
320
|
|
|
301
|
-
usageCache
|
|
321
|
+
setBoundedCache(usageCache, cacheKey, { data: result, expiresAt: Date.now() + ttlMs, isRevalidating: false })
|
|
302
322
|
return result
|
|
303
323
|
} catch (err) {
|
|
304
324
|
const outcome = {
|
|
@@ -307,10 +327,15 @@ export async function fetchUsageLimits(baseUrl, apiKey, {
|
|
|
307
327
|
checkedAt: Date.now(),
|
|
308
328
|
}
|
|
309
329
|
if (!usageCache.has(cacheKey)) {
|
|
310
|
-
usageCache
|
|
330
|
+
setBoundedCache(usageCache, cacheKey, { data: outcome, expiresAt: Date.now() + 5000, isRevalidating: false })
|
|
331
|
+
} else {
|
|
332
|
+
const existing = usageCache.get(cacheKey)
|
|
333
|
+
if (existing) existing.isRevalidating = false
|
|
311
334
|
}
|
|
312
335
|
return outcome
|
|
313
336
|
} finally {
|
|
337
|
+
const existing = usageCache.get(cacheKey)
|
|
338
|
+
if (existing) existing.isRevalidating = false
|
|
314
339
|
cancel()
|
|
315
340
|
}
|
|
316
341
|
}
|
|
@@ -361,7 +386,7 @@ export async function probeHealth(baseUrl, {
|
|
|
361
386
|
error: reachable ? null : `HTTP status ${res.status}`,
|
|
362
387
|
checkedAt: Date.now(),
|
|
363
388
|
}
|
|
364
|
-
probeCache
|
|
389
|
+
setBoundedCache(probeCache, cacheKey, { data: outcome, expiresAt: Date.now() + ttlMs, isRevalidating: false })
|
|
365
390
|
return outcome
|
|
366
391
|
} catch (err) {
|
|
367
392
|
const latencyMs = Date.now() - start
|
|
@@ -371,9 +396,16 @@ export async function probeHealth(baseUrl, {
|
|
|
371
396
|
error: String(err?.message || err),
|
|
372
397
|
checkedAt: Date.now(),
|
|
373
398
|
}
|
|
374
|
-
probeCache.
|
|
399
|
+
if (!probeCache.has(cacheKey)) {
|
|
400
|
+
setBoundedCache(probeCache, cacheKey, { data: outcome, expiresAt: Date.now() + 5000, isRevalidating: false })
|
|
401
|
+
} else {
|
|
402
|
+
const existing = probeCache.get(cacheKey)
|
|
403
|
+
if (existing) existing.isRevalidating = false
|
|
404
|
+
}
|
|
375
405
|
return outcome
|
|
376
406
|
} finally {
|
|
407
|
+
const existing = probeCache.get(cacheKey)
|
|
408
|
+
if (existing) existing.isRevalidating = false
|
|
377
409
|
cancel()
|
|
378
410
|
}
|
|
379
411
|
}
|
package/lib/config.js
CHANGED
|
@@ -9,6 +9,7 @@ import {
|
|
|
9
9
|
import {
|
|
10
10
|
DEFAULT_MODEL_ID,
|
|
11
11
|
getAllModels,
|
|
12
|
+
getDefaultModelIds,
|
|
12
13
|
getActiveModelIds,
|
|
13
14
|
} from './models.js'
|
|
14
15
|
|
|
@@ -56,8 +57,7 @@ export const Config = z.object({
|
|
|
56
57
|
|
|
57
58
|
export function publicConfig(cfg) {
|
|
58
59
|
const dynamic = Array.isArray(cfg?.dynamicModels) ? cfg.dynamicModels : []
|
|
59
|
-
const
|
|
60
|
-
const allDefaultIds = allModels.map((m) => m.id)
|
|
60
|
+
const allDefaultIds = getDefaultModelIds(dynamic)
|
|
61
61
|
|
|
62
62
|
let disabledList = Array.isArray(cfg?.disabledModels) ? cfg.disabledModels : []
|
|
63
63
|
if (!Array.isArray(cfg?.disabledModels) || (cfg.disabledModels.length === 0 && Array.isArray(cfg?.enabledModels) && cfg.enabledModels.length > 0)) {
|
package/lib/index.js
CHANGED
|
@@ -83,7 +83,14 @@ export function apply(ctx, config) {
|
|
|
83
83
|
}
|
|
84
84
|
|
|
85
85
|
syncProviderState(live())
|
|
86
|
-
|
|
86
|
+
if (typeof ctx.effect === 'function') {
|
|
87
|
+
ctx.effect(() => {
|
|
88
|
+
const timer = setTimeout(triggerAutoDiscover, 500)
|
|
89
|
+
return () => clearTimeout(timer)
|
|
90
|
+
}, 'dsh-clinebot: auto-discover')
|
|
91
|
+
} else {
|
|
92
|
+
setTimeout(triggerAutoDiscover, 500)
|
|
93
|
+
}
|
|
87
94
|
|
|
88
95
|
if (ctx.webServer?.register) {
|
|
89
96
|
const unregisterUpdater = registerPluginUpdater(ctx, {
|
package/lib/models.js
CHANGED
|
@@ -136,83 +136,27 @@ export const CLINE_MODELS = Object.freeze([
|
|
|
136
136
|
recommended: false,
|
|
137
137
|
isCustom: false,
|
|
138
138
|
},
|
|
139
|
-
{
|
|
140
|
-
id: 'cline-pass/claude-3-7-sonnet',
|
|
141
|
-
name: 'Claude 3.7 Sonnet',
|
|
142
|
-
description: 'Hybrid reasoning and standard generation model with high coding proficiency.',
|
|
143
|
-
contextLength: 200000,
|
|
144
|
-
maxTokens: 8192,
|
|
145
|
-
input: ['text', 'image'],
|
|
146
|
-
category: 'coding',
|
|
147
|
-
recommended: true,
|
|
148
|
-
isCustom: false,
|
|
149
|
-
reasoningEfforts: ['low', 'medium', 'high'],
|
|
150
|
-
},
|
|
151
|
-
{
|
|
152
|
-
id: 'cline-pass/gpt-4.5-preview',
|
|
153
|
-
name: 'GPT-4.5 Preview',
|
|
154
|
-
description: 'Advanced flagship frontier model with deep world knowledge and intuition.',
|
|
155
|
-
contextLength: 128000,
|
|
156
|
-
maxTokens: 16384,
|
|
157
|
-
input: ['text', 'image'],
|
|
158
|
-
category: 'general',
|
|
159
|
-
recommended: true,
|
|
160
|
-
isCustom: false,
|
|
161
|
-
},
|
|
162
|
-
{
|
|
163
|
-
id: 'cline-pass/o3-mini',
|
|
164
|
-
name: 'o3-mini',
|
|
165
|
-
description: 'Fast, cost-effective reasoning model specialized for STEM and coding.',
|
|
166
|
-
contextLength: 200000,
|
|
167
|
-
maxTokens: 65536,
|
|
168
|
-
input: ['text'],
|
|
169
|
-
category: 'reasoning',
|
|
170
|
-
recommended: true,
|
|
171
|
-
isCustom: false,
|
|
172
|
-
reasoningEfforts: ['low', 'medium', 'high'],
|
|
173
|
-
},
|
|
174
|
-
{
|
|
175
|
-
id: 'cline-pass/gemini-2.5-pro',
|
|
176
|
-
name: 'Gemini 2.5 Pro',
|
|
177
|
-
description: 'State-of-the-art multimodal reasoning model with extended context.',
|
|
178
|
-
contextLength: 1000000,
|
|
179
|
-
maxTokens: 8192,
|
|
180
|
-
input: ['text', 'image'],
|
|
181
|
-
category: 'multimodal',
|
|
182
|
-
recommended: true,
|
|
183
|
-
isCustom: false,
|
|
184
|
-
reasoningEfforts: ['low', 'medium', 'high'],
|
|
185
|
-
},
|
|
186
|
-
{
|
|
187
|
-
id: 'cline-pass/gemini-2.5-flash',
|
|
188
|
-
name: 'Gemini 2.5 Flash',
|
|
189
|
-
description: 'Ultra-fast multimodal model optimized for real-time agent workflows.',
|
|
190
|
-
contextLength: 1000000,
|
|
191
|
-
maxTokens: 8192,
|
|
192
|
-
input: ['text', 'image'],
|
|
193
|
-
category: 'general',
|
|
194
|
-
recommended: false,
|
|
195
|
-
isCustom: false,
|
|
196
|
-
},
|
|
197
|
-
{
|
|
198
|
-
id: 'cline-pass/qwen-2.5-coder-32b',
|
|
199
|
-
name: 'Qwen 2.5 Coder 32B',
|
|
200
|
-
description: 'Open-weights powerhouse for code generation, refactoring and bug fixing.',
|
|
201
|
-
contextLength: 131072,
|
|
202
|
-
maxTokens: 8192,
|
|
203
|
-
input: ['text'],
|
|
204
|
-
category: 'coding',
|
|
205
|
-
recommended: false,
|
|
206
|
-
isCustom: false,
|
|
207
|
-
},
|
|
208
139
|
])
|
|
209
140
|
|
|
210
141
|
/**
|
|
211
142
|
* Parse human-readable included models string from ClinePass plan features.
|
|
212
143
|
* Example: "Includes Kimi K3, GLM 5.2, Kimi K2.6, Kimi K2.7 Code, Mimo v2.5, Mimo v2.5 Pro, Minimax M3, Qwen3.7 Plus, Qwen3.7 Max, DeepSeek V4 Pro, and DeepSeek V4 Flash"
|
|
213
144
|
*/
|
|
214
|
-
export function parsePlanIncludedModels(
|
|
215
|
-
if (!
|
|
145
|
+
export function parsePlanIncludedModels(includedInput) {
|
|
146
|
+
if (!includedInput) return []
|
|
147
|
+
let includedText = ''
|
|
148
|
+
if (Array.isArray(includedInput)) {
|
|
149
|
+
const foundStr = includedInput.find((item) => typeof item === 'string' && /includes\s+/i.test(item))
|
|
150
|
+
if (foundStr) {
|
|
151
|
+
includedText = foundStr
|
|
152
|
+
} else {
|
|
153
|
+
includedText = includedInput.filter((x) => typeof x === 'string').join(', ')
|
|
154
|
+
}
|
|
155
|
+
} else if (typeof includedInput === 'string') {
|
|
156
|
+
includedText = includedInput
|
|
157
|
+
} else {
|
|
158
|
+
return []
|
|
159
|
+
}
|
|
216
160
|
const clean = includedText
|
|
217
161
|
.replace(/^includes\s+/i, '')
|
|
218
162
|
.replace(/\band\b/gi, ',')
|
package/lib/provider-sync.js
CHANGED
|
@@ -37,9 +37,9 @@ export async function checkRegisteredInPiAi(ctx) {
|
|
|
37
37
|
}
|
|
38
38
|
}
|
|
39
39
|
|
|
40
|
-
export async function resolveActiveAccountKey(ctx, cfg) {
|
|
41
|
-
const
|
|
42
|
-
const configured =
|
|
40
|
+
export async function resolveActiveAccountKey(ctx, cfg, pool = null) {
|
|
41
|
+
const accountPool = pool || await resolveAccountPool(ctx, cfg)
|
|
42
|
+
const configured = accountPool.filter((acc) => acc.present && acc.value)
|
|
43
43
|
if (!configured.length) {
|
|
44
44
|
return { envName: publicConfig(cfg).apiKeyEnv, value: '', source: 'none', id: 'default' }
|
|
45
45
|
}
|
|
@@ -62,7 +62,7 @@ export async function buildStatus(ctx, cfg) {
|
|
|
62
62
|
probeHealth(pub.baseUrl, { timeoutMs: probeTimeout }),
|
|
63
63
|
])
|
|
64
64
|
|
|
65
|
-
const activeAcc = await resolveActiveAccountKey(ctx, cfg)
|
|
65
|
+
const activeAcc = await resolveActiveAccountKey(ctx, cfg, pool)
|
|
66
66
|
const allModels = getAllModels(pub.dynamicModels)
|
|
67
67
|
|
|
68
68
|
let usage = null
|
package/lib/routes/accounts.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { writeJson, readBody } from '../http.js'
|
|
2
|
-
import { isTrustedSettingsRequest } from '../access.js'
|
|
2
|
+
import { isTrustedSettingsRequest, assertTrustedSettingsRequest } from '../access.js'
|
|
3
3
|
import { publicConfig, Config } from '../config.js'
|
|
4
4
|
import { upsertPiAiProvider, removePiAiProvider } from '../provider-sync.js'
|
|
5
5
|
import { clearUsageCache, clearProbeCache } from '../cline-client.js'
|
|
@@ -11,13 +11,11 @@ export function registerAccountsRoutes(ctx, { live, getSettingsApi, syncProvider
|
|
|
11
11
|
path: '/dsh-clinebot/accounts/active',
|
|
12
12
|
handler: async (req, res) => {
|
|
13
13
|
if (req.method !== 'POST') return writeJson(res, 405, { ok: false, error: 'POST only' })
|
|
14
|
-
if (!
|
|
15
|
-
return writeJson(res, 403, { ok: false, error: 'Forbidden' })
|
|
16
|
-
}
|
|
14
|
+
if (!assertTrustedSettingsRequest(req, res)) return
|
|
17
15
|
try {
|
|
18
16
|
const bodyBuf = await readBody(req)
|
|
19
17
|
let body = {}
|
|
20
|
-
try { body = JSON.parse(bodyBuf.toString('utf8')) } catch {}
|
|
18
|
+
try { body = JSON.parse(bodyBuf.toString('utf8')) } catch { body = {} }
|
|
21
19
|
const account = String(body.account || '').trim()
|
|
22
20
|
|
|
23
21
|
clearUsageCache()
|
|
@@ -43,13 +41,11 @@ export function registerAccountsRoutes(ctx, { live, getSettingsApi, syncProvider
|
|
|
43
41
|
path: '/dsh-clinebot/register',
|
|
44
42
|
handler: async (req, res) => {
|
|
45
43
|
if (req.method !== 'POST') return writeJson(res, 405, { ok: false, error: 'POST only' })
|
|
46
|
-
if (!
|
|
47
|
-
return writeJson(res, 403, { ok: false, error: 'Forbidden' })
|
|
48
|
-
}
|
|
44
|
+
if (!assertTrustedSettingsRequest(req, res)) return
|
|
49
45
|
try {
|
|
50
46
|
const bodyBuf = await readBody(req)
|
|
51
47
|
let body = {}
|
|
52
|
-
try { body = JSON.parse(bodyBuf.toString('utf8')) } catch {}
|
|
48
|
+
try { body = JSON.parse(bodyBuf.toString('utf8')) } catch { body = {} }
|
|
53
49
|
const activeModels = body.models || publicConfig(live()).enabledModels
|
|
54
50
|
const result = await upsertPiAiProvider(ctx, live(), activeModels)
|
|
55
51
|
writeJson(res, 200, { ok: true, provider: result })
|
|
@@ -65,9 +61,7 @@ export function registerAccountsRoutes(ctx, { live, getSettingsApi, syncProvider
|
|
|
65
61
|
path: '/dsh-clinebot/unregister',
|
|
66
62
|
handler: async (req, res) => {
|
|
67
63
|
if (req.method !== 'POST') return writeJson(res, 405, { ok: false, error: 'POST only' })
|
|
68
|
-
if (!
|
|
69
|
-
return writeJson(res, 403, { ok: false, error: 'Forbidden' })
|
|
70
|
-
}
|
|
64
|
+
if (!assertTrustedSettingsRequest(req, res)) return
|
|
71
65
|
try {
|
|
72
66
|
await removePiAiProvider(ctx)
|
|
73
67
|
writeJson(res, 200, { ok: true })
|
package/lib/routes/auth.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { writeJson, readBody } from '../http.js'
|
|
2
|
-
import { isTrustedSettingsRequest } from '../access.js'
|
|
2
|
+
import { isTrustedSettingsRequest, assertTrustedSettingsRequest } from '../access.js'
|
|
3
3
|
import { publicConfig } from '../config.js'
|
|
4
4
|
import { resolveActiveAccountKey } from '../provider-sync.js'
|
|
5
5
|
import { smokeChat, recordSessionRequest, rotateToNextAccount, DEFAULT_MODEL_ID } from '../cline-client.js'
|
|
@@ -13,9 +13,7 @@ export function registerAuthRoutes(ctx, { live, getSettingsApi, syncProviderStat
|
|
|
13
13
|
path: '/dsh-clinebot/auth/begin',
|
|
14
14
|
handler: async (req, res) => {
|
|
15
15
|
if (req.method !== 'POST') return writeJson(res, 405, { ok: false, error: 'POST only' })
|
|
16
|
-
if (!
|
|
17
|
-
return writeJson(res, 403, { ok: false, error: 'Forbidden' })
|
|
18
|
-
}
|
|
16
|
+
if (!assertTrustedSettingsRequest(req, res)) return
|
|
19
17
|
try {
|
|
20
18
|
const authUrl = 'https://app.cline.bot'
|
|
21
19
|
authSession = {
|
|
@@ -54,13 +52,11 @@ export function registerAuthRoutes(ctx, { live, getSettingsApi, syncProviderStat
|
|
|
54
52
|
path: '/dsh-clinebot/smoke',
|
|
55
53
|
handler: async (req, res) => {
|
|
56
54
|
if (req.method !== 'POST') return writeJson(res, 405, { ok: false, error: 'POST only' })
|
|
57
|
-
if (!
|
|
58
|
-
return writeJson(res, 403, { ok: false, error: 'Forbidden' })
|
|
59
|
-
}
|
|
55
|
+
if (!assertTrustedSettingsRequest(req, res)) return
|
|
60
56
|
try {
|
|
61
57
|
const bodyBuf = await readBody(req)
|
|
62
58
|
let body = {}
|
|
63
|
-
try { body = JSON.parse(bodyBuf.toString('utf8')) } catch {}
|
|
59
|
+
try { body = JSON.parse(bodyBuf.toString('utf8')) } catch { body = {} }
|
|
64
60
|
|
|
65
61
|
const pub = publicConfig(live())
|
|
66
62
|
const activeKey = await resolveActiveAccountKey(ctx, live())
|
package/lib/routes/models.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { writeJson, readBody } from '../http.js'
|
|
2
|
-
import { isTrustedSettingsRequest } from '../access.js'
|
|
2
|
+
import { isTrustedSettingsRequest, assertTrustedSettingsRequest } from '../access.js'
|
|
3
3
|
import { publicConfig, Config } from '../config.js'
|
|
4
4
|
import { resolveActiveAccountKey, resolvePathWithHome } from '../provider-sync.js'
|
|
5
5
|
import { getAllModels, saveModelsDiskCache } from '../models.js'
|
|
@@ -11,9 +11,7 @@ export function registerModelsRoutes(ctx, { live, getSettingsApi, syncProviderSt
|
|
|
11
11
|
kind: 'exact',
|
|
12
12
|
path: '/dsh-clinebot/models/sync',
|
|
13
13
|
handler: async (req, res) => {
|
|
14
|
-
if (!
|
|
15
|
-
return writeJson(res, 403, { ok: false, error: 'Forbidden' })
|
|
16
|
-
}
|
|
14
|
+
if (!assertTrustedSettingsRequest(req, res)) return
|
|
17
15
|
if (req.method !== 'POST') {
|
|
18
16
|
return writeJson(res, 405, { ok: false, error: 'POST only' })
|
|
19
17
|
}
|
|
@@ -69,13 +67,11 @@ export function registerModelsRoutes(ctx, { live, getSettingsApi, syncProviderSt
|
|
|
69
67
|
path: '/dsh-clinebot/models/toggle',
|
|
70
68
|
handler: async (req, res) => {
|
|
71
69
|
if (req.method !== 'POST') return writeJson(res, 405, { ok: false, error: 'POST only' })
|
|
72
|
-
if (!
|
|
73
|
-
return writeJson(res, 403, { ok: false, error: 'Forbidden' })
|
|
74
|
-
}
|
|
70
|
+
if (!assertTrustedSettingsRequest(req, res)) return
|
|
75
71
|
try {
|
|
76
72
|
const bodyBuf = await readBody(req)
|
|
77
73
|
let body = {}
|
|
78
|
-
try { body = JSON.parse(bodyBuf.toString('utf8')) } catch {}
|
|
74
|
+
try { body = JSON.parse(bodyBuf.toString('utf8')) } catch { body = {} }
|
|
79
75
|
|
|
80
76
|
const settingsApi = getSettingsApi()
|
|
81
77
|
if (settingsApi?.replace) {
|
package/lib/routes/settings.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { writeJson, readBody } from '../http.js'
|
|
2
|
-
import { isTrustedSettingsRequest } from '../access.js'
|
|
2
|
+
import { isTrustedSettingsRequest, assertTrustedSettingsRequest } from '../access.js'
|
|
3
3
|
import { publicConfig, Config } from '../config.js'
|
|
4
4
|
import { buildStatus } from '../provider-sync.js'
|
|
5
5
|
import { saveCredentialKey, smokeChat, DEFAULT_API_KEY_ENV } from '../cline-client.js'
|
|
@@ -62,13 +62,11 @@ export function registerSettingsRoutes(ctx, { live, getSettingsApi, syncProvider
|
|
|
62
62
|
path: '/dsh-clinebot/save-key',
|
|
63
63
|
handler: async (req, res) => {
|
|
64
64
|
if (req.method !== 'POST') return writeJson(res, 405, { ok: false, error: 'POST only' })
|
|
65
|
-
if (!
|
|
66
|
-
return writeJson(res, 403, { ok: false, error: 'Forbidden' })
|
|
67
|
-
}
|
|
65
|
+
if (!assertTrustedSettingsRequest(req, res)) return
|
|
68
66
|
try {
|
|
69
67
|
const bodyBuf = await readBody(req)
|
|
70
68
|
let body = {}
|
|
71
|
-
try { body = JSON.parse(bodyBuf.toString('utf8')) } catch {}
|
|
69
|
+
try { body = JSON.parse(bodyBuf.toString('utf8')) } catch { body = {} }
|
|
72
70
|
|
|
73
71
|
const apiKey = String(body.apiKey || '').trim()
|
|
74
72
|
if (!apiKey) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@goodandready/dsh-clinebot",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.17",
|
|
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",
|