@goodandready/dsh-clinebot 0.5.33 → 0.6.1
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 +32 -0
- package/README.md +63 -36
- package/README.ru.md +3 -1
- package/README.zh.md +3 -1
- package/lib/adapter-messages.js +237 -0
- package/lib/adapter-stream.js +292 -0
- package/lib/adapter.js +616 -0
- package/lib/client.js +2634 -922
- package/lib/cline-client.js +15 -6
- package/lib/config.js +266 -233
- package/lib/credential-refs.js +104 -84
- package/lib/index.js +569 -502
- package/lib/model-prices.js +117 -0
- package/lib/provider-sync.js +404 -387
- package/lib/proxy-token.js +186 -186
- package/lib/routes/accounts.js +523 -403
- package/lib/routes/models.js +2 -1
- package/lib/routes/settings.js +81 -2
- package/lib/services.js +55 -0
- package/lib/stats-storage.js +378 -165
- package/lib/transport/cline-transport.js +314 -0
- package/lib/tui-settings.js +158 -0
- package/package.json +113 -77
package/lib/routes/models.js
CHANGED
|
@@ -234,7 +234,8 @@ export function registerModelsRoutes(ctx, { live, getSettingsApi, syncProviderSt
|
|
|
234
234
|
timeoutMs: pub.timeoutMs,
|
|
235
235
|
bypassCache: true,
|
|
236
236
|
})
|
|
237
|
-
|
|
237
|
+
const accountLabel = activeKey.label || activeKey.apiKeyEnv || activeKey.id || ''
|
|
238
|
+
writeJson(res, usageData.ok ? 200 : 502, { ...(publicUsage(usageData) || {}), account: accountLabel })
|
|
238
239
|
} catch (err) {
|
|
239
240
|
writeJson(res, 500, { ok: false, error: String(err?.message || err) })
|
|
240
241
|
}
|
package/lib/routes/settings.js
CHANGED
|
@@ -3,7 +3,8 @@ import { assertTrustedSettingsRequest } from '../access.js'
|
|
|
3
3
|
import { publicConfig, plainConfig, 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'
|
|
6
|
-
import { getStatsSummary, resetStats, exportStatsAsCsv } from '../stats-storage.js'
|
|
6
|
+
import { getStatsSummary, getSessionStats, resetStats, exportStatsAsCsv, getAnalytics } from '../stats-storage.js'
|
|
7
|
+
import { priceTable } from '../model-prices.js'
|
|
7
8
|
|
|
8
9
|
export function registerSettingsRoutes(ctx, { live, getSettingsApi, syncProviderState, triggerAutoDiscover }) {
|
|
9
10
|
// 1. GET /dsh-clinebot/status
|
|
@@ -196,6 +197,24 @@ export function registerSettingsRoutes(ctx, { live, getSettingsApi, syncProvider
|
|
|
196
197
|
},
|
|
197
198
|
}), 'dsh-clinebot: /stats')
|
|
198
199
|
|
|
200
|
+
// 4b. GET /dsh-clinebot/stats/session — ClineBot-only aggregate for one DSH session
|
|
201
|
+
ctx.effect(() => ctx.webServer.register({
|
|
202
|
+
kind: 'exact',
|
|
203
|
+
path: '/dsh-clinebot/stats/session',
|
|
204
|
+
handler: async (req, res) => {
|
|
205
|
+
if (req.method !== 'GET') return writeJson(res, 405, { ok: false, error: 'GET only' })
|
|
206
|
+
if (!assertTrustedSettingsRequest(req, res, ctx)) return
|
|
207
|
+
try {
|
|
208
|
+
const url = new URL(req.url || '/', 'http://localhost')
|
|
209
|
+
const sessionId = String(url.searchParams.get('sessionId') || '').trim().slice(0, 200)
|
|
210
|
+
if (!sessionId) return writeJson(res, 400, { ok: false, error: 'sessionId required' })
|
|
211
|
+
writeJson(res, 200, { ok: true, session: getSessionStats(sessionId) })
|
|
212
|
+
} catch (err) {
|
|
213
|
+
writeJson(res, 500, { ok: false, error: String(err?.message || err) })
|
|
214
|
+
}
|
|
215
|
+
},
|
|
216
|
+
}), 'dsh-clinebot: /stats/session')
|
|
217
|
+
|
|
199
218
|
// 5. POST /dsh-clinebot/stats/reset — reset persistent stats (Issue #129)
|
|
200
219
|
ctx.effect(() => ctx.webServer.register({
|
|
201
220
|
kind: 'exact',
|
|
@@ -221,7 +240,13 @@ export function registerSettingsRoutes(ctx, { live, getSettingsApi, syncProvider
|
|
|
221
240
|
if (req.method !== 'GET') return writeJson(res, 405, { ok: false, error: 'GET only' })
|
|
222
241
|
if (!assertTrustedSettingsRequest(req, res, ctx)) return
|
|
223
242
|
try {
|
|
224
|
-
|
|
243
|
+
let detailed = false
|
|
244
|
+
try {
|
|
245
|
+
detailed = new URL(req.url, 'http://127.0.0.1').searchParams.get('detailed') === '1'
|
|
246
|
+
} catch {
|
|
247
|
+
detailed = false
|
|
248
|
+
}
|
|
249
|
+
const csv = exportStatsAsCsv(detailed)
|
|
225
250
|
res.statusCode = 200
|
|
226
251
|
res.setHeader('Content-Type', 'text/csv; charset=utf-8')
|
|
227
252
|
res.setHeader('Content-Disposition', 'attachment; filename="clinebot-stats.csv"')
|
|
@@ -231,4 +256,58 @@ export function registerSettingsRoutes(ctx, { live, getSettingsApi, syncProvider
|
|
|
231
256
|
}
|
|
232
257
|
},
|
|
233
258
|
}), 'dsh-clinebot: /stats/export')
|
|
259
|
+
|
|
260
|
+
// 7. GET /dsh-clinebot/analytics — aggregated session/day/model/account report
|
|
261
|
+
ctx.effect(() => ctx.webServer.register({
|
|
262
|
+
kind: 'exact',
|
|
263
|
+
path: '/dsh-clinebot/analytics',
|
|
264
|
+
handler: async (req, res) => {
|
|
265
|
+
if (req.method !== 'GET') return writeJson(res, 405, { ok: false, error: 'GET only' })
|
|
266
|
+
if (!assertTrustedSettingsRequest(req, res, ctx)) return
|
|
267
|
+
try {
|
|
268
|
+
let query = {}
|
|
269
|
+
try {
|
|
270
|
+
query = Object.fromEntries(new URL(req.url, 'http://127.0.0.1').searchParams.entries())
|
|
271
|
+
} catch {}
|
|
272
|
+
const parseTime = (value) => {
|
|
273
|
+
if (value === undefined || value === '') return undefined
|
|
274
|
+
const numeric = Number(value)
|
|
275
|
+
return Number.isFinite(numeric) ? numeric : Date.parse(value)
|
|
276
|
+
}
|
|
277
|
+
const analytics = getAnalytics({
|
|
278
|
+
from: parseTime(query.from),
|
|
279
|
+
to: parseTime(query.to),
|
|
280
|
+
groupBy: query.groupBy,
|
|
281
|
+
limit: query.limit,
|
|
282
|
+
model: query.model,
|
|
283
|
+
account: query.account,
|
|
284
|
+
heatmapDays: query.heatmapDays,
|
|
285
|
+
heatmapModel: query.heatmapModel,
|
|
286
|
+
})
|
|
287
|
+
writeJson(res, 200, { ok: true, ...analytics })
|
|
288
|
+
} catch (err) {
|
|
289
|
+
writeJson(res, 500, { ok: false, error: String(err?.message || err) })
|
|
290
|
+
}
|
|
291
|
+
},
|
|
292
|
+
}), 'dsh-clinebot: /analytics')
|
|
293
|
+
|
|
294
|
+
// 8. GET /dsh-clinebot/prices — local cost-estimate table
|
|
295
|
+
ctx.effect(() => ctx.webServer.register({
|
|
296
|
+
kind: 'exact',
|
|
297
|
+
path: '/dsh-clinebot/prices',
|
|
298
|
+
handler: async (req, res) => {
|
|
299
|
+
if (req.method !== 'GET') return writeJson(res, 405, { ok: false, error: 'GET only' })
|
|
300
|
+
if (!assertTrustedSettingsRequest(req, res, ctx)) return
|
|
301
|
+
try {
|
|
302
|
+
const pub = publicConfig(live())
|
|
303
|
+
writeJson(res, 200, {
|
|
304
|
+
ok: true,
|
|
305
|
+
...priceTable(pub.priceOverrides || {}, pub.enabledModels || []),
|
|
306
|
+
updatedAt: new Date().toISOString(),
|
|
307
|
+
})
|
|
308
|
+
} catch (err) {
|
|
309
|
+
writeJson(res, 500, { ok: false, error: String(err?.message || err) })
|
|
310
|
+
}
|
|
311
|
+
},
|
|
312
|
+
}), 'dsh-clinebot: /prices')
|
|
234
313
|
}
|
package/lib/services.js
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Optional-service helpers and one-time legacy lifecycle migration.
|
|
3
|
+
*
|
|
4
|
+
* The plugin no longer hard-injects `webServer`/`credentials`, so every read
|
|
5
|
+
* must survive Cordis's strict-proxy guard and a headless profile that mounts
|
|
6
|
+
* neither service.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { LLM_PI_AI_NS } from './config.js'
|
|
10
|
+
import { deleteCredentialKey } from './credential-refs.js'
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Read an optional service without tripping Cordis's strict-proxy guard.
|
|
14
|
+
* @param {object | undefined} ctx
|
|
15
|
+
* @param {string} name
|
|
16
|
+
* @returns {unknown}
|
|
17
|
+
*/
|
|
18
|
+
export function service(ctx, name) {
|
|
19
|
+
try {
|
|
20
|
+
const viaGet = typeof ctx?.get === 'function' ? ctx.get(name) : undefined
|
|
21
|
+
if (viaGet) return viaGet
|
|
22
|
+
} catch {
|
|
23
|
+
/* strict context refuses get() for a service the fiber does not inject */
|
|
24
|
+
}
|
|
25
|
+
try {
|
|
26
|
+
return ctx ? ctx[name] : undefined
|
|
27
|
+
} catch {
|
|
28
|
+
return undefined
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Remove the legacy `llm-pi-ai.providers.clinebot` entry and its local proxy
|
|
34
|
+
* token once the native adapter owns the route. Idempotent and best-effort:
|
|
35
|
+
* a failed cleanup is logged, never fatal.
|
|
36
|
+
* @param {object} ctx
|
|
37
|
+
*/
|
|
38
|
+
export async function migrateLegacyPiAiProvider(ctx) {
|
|
39
|
+
try {
|
|
40
|
+
const svc = service(ctx, 'settings')
|
|
41
|
+
if (!svc || typeof svc.describe !== 'function' || typeof svc.mutate !== 'function') return
|
|
42
|
+
const descriptor = svc.describe().find((entry) => entry?.ns === LLM_PI_AI_NS)
|
|
43
|
+
if (descriptor?.value?.providers?.clinebot) {
|
|
44
|
+
await svc.mutate(LLM_PI_AI_NS, [{ op: 'remove', path: ['providers', 'clinebot'] }])
|
|
45
|
+
ctx?.logger?.info?.('[dsh-clinebot] Removed legacy llm-pi-ai clinebot provider after adapter migration')
|
|
46
|
+
}
|
|
47
|
+
} catch (error) {
|
|
48
|
+
ctx?.logger?.warn?.('[dsh-clinebot] Legacy llm-pi-ai provider cleanup skipped: ' + (error?.message || error))
|
|
49
|
+
}
|
|
50
|
+
try {
|
|
51
|
+
await deleteCredentialKey(ctx, 'CLINEBOT_LOCAL_PROXY_TOKEN')
|
|
52
|
+
} catch {
|
|
53
|
+
/* the credential may never have existed */
|
|
54
|
+
}
|
|
55
|
+
}
|