@goodandready/dsh-clinebot 0.3.10 β 0.3.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/CHANGELOG.md +16 -0
- package/lib/access.js +10 -0
- package/lib/account-pool.js +131 -0
- package/lib/client.js +1119 -1025
- package/lib/cline-client.js +1 -128
- package/lib/config.js +84 -0
- package/lib/index.js +36 -864
- package/lib/provider-sync.js +216 -0
- package/lib/routes/accounts.js +79 -0
- package/lib/routes/auth.js +102 -0
- package/lib/routes/models.js +126 -0
- package/lib/routes/settings.js +102 -0
- package/lib/slash-command.js +198 -0
- package/package.json +3 -2
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
import { publicConfig, Config } from './config.js'
|
|
2
|
+
import { resolveActiveAccountKey, formatProgressBar } from './provider-sync.js'
|
|
3
|
+
import {
|
|
4
|
+
getAllModels,
|
|
5
|
+
isVisionModel,
|
|
6
|
+
isSupportedModel,
|
|
7
|
+
formatModelContext,
|
|
8
|
+
DEFAULT_MODEL_ID,
|
|
9
|
+
} from './models.js'
|
|
10
|
+
import {
|
|
11
|
+
resolveAccountPool,
|
|
12
|
+
rotateToNextAccount,
|
|
13
|
+
probeHealth,
|
|
14
|
+
smokeChat,
|
|
15
|
+
fetchUsageLimits,
|
|
16
|
+
recordSessionRequest,
|
|
17
|
+
clearUsageCache,
|
|
18
|
+
clearProbeCache,
|
|
19
|
+
usageCache,
|
|
20
|
+
sessionStats,
|
|
21
|
+
} from './cline-client.js'
|
|
22
|
+
|
|
23
|
+
export function registerSlashCommand(ctx, { live, getSettingsApi, syncProviderState }) {
|
|
24
|
+
ctx.inject(['commands'], (cmdCtx) => {
|
|
25
|
+
const commands = cmdCtx.commands
|
|
26
|
+
if (typeof commands?.register !== 'function') return
|
|
27
|
+
|
|
28
|
+
const unregister = commands.register({
|
|
29
|
+
name: 'cline',
|
|
30
|
+
description: 'Check ClinePass subscription quota, models, accounts and test connectivity (/cline [quota|models|accounts|switch <name>|test [model]|ping|rotate])',
|
|
31
|
+
execute: async (rawArgs) => {
|
|
32
|
+
const pub = publicConfig(live())
|
|
33
|
+
const subcmd = String(rawArgs || '').trim().toLowerCase().split(/\s+/)[0] || 'quota'
|
|
34
|
+
const param = String(rawArgs || '').trim().split(/\s+/)[1] || ''
|
|
35
|
+
|
|
36
|
+
// 1. Subcommand /cline models
|
|
37
|
+
if (subcmd === 'models') {
|
|
38
|
+
const allModels = getAllModels(pub.dynamicModels)
|
|
39
|
+
const disabledSet = new Set(pub.disabledModels || [])
|
|
40
|
+
const lines = [
|
|
41
|
+
'### π― ClinePass Models Catalog',
|
|
42
|
+
`* **ΠΡΠ΅Π³ΠΎ ΠΌΠΎΠ΄Π΅Π»Π΅ΠΉ**: ${allModels.length} (${allModels.filter((m) => !disabledSet.has(m.id)).length} Π°ΠΊΡΠΈΠ²Π½ΠΎ)`,
|
|
43
|
+
'',
|
|
44
|
+
]
|
|
45
|
+
for (const m of allModels) {
|
|
46
|
+
const active = !disabledSet.has(m.id) ? 'β
' : 'β'
|
|
47
|
+
const isVis = isVisionModel(m.id, pub.dynamicModels) ? 'π· Vision' : 'π Text'
|
|
48
|
+
const efforts = Array.isArray(m.reasoningEfforts) ? `π§ [${m.reasoningEfforts.join(', ')}]` : ''
|
|
49
|
+
lines.push(`* ${active} **${m.name}** (\`${m.id}\`) β ${isVis} Β· ${formatModelContext(m.contextLength)} ${efforts}`)
|
|
50
|
+
}
|
|
51
|
+
return lines.join('\n')
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// 2. Subcommand /cline accounts
|
|
55
|
+
if (subcmd === 'accounts') {
|
|
56
|
+
const pool = await resolveAccountPool(ctx, live())
|
|
57
|
+
const lines = [
|
|
58
|
+
'### π ClinePass Accounts Pool',
|
|
59
|
+
`* **Active Account**: \`${pub.activeAccount || pub.apiKeyEnv}\``,
|
|
60
|
+
'',
|
|
61
|
+
]
|
|
62
|
+
for (const acc of pool) {
|
|
63
|
+
const pinBadge = acc.isPinned ? 'π [Pinned]' : ''
|
|
64
|
+
const statusBadge = acc.present ? 'β
Configured' : 'β οΈ Missing Key'
|
|
65
|
+
const cacheKey = `cline:usage:${(acc.value || '').slice(-8)}`
|
|
66
|
+
const cached = usageCache.get(cacheKey)?.data
|
|
67
|
+
const usageInfo = cached?.windows?.fiveHour ? ` Β· β± ${cached.windows.fiveHour.percentUsed}% used` : ''
|
|
68
|
+
lines.push(`* **${acc.label}** (\`${acc.apiKeyEnv}\`): ${statusBadge} ${pinBadge}${usageInfo}`)
|
|
69
|
+
}
|
|
70
|
+
lines.push('', 'Switch active account: `/cline switch <env_variable_name>`')
|
|
71
|
+
return lines.join('\n')
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// 3. Subcommand /cline switch <account>
|
|
75
|
+
if (subcmd === 'switch') {
|
|
76
|
+
if (!param) {
|
|
77
|
+
return 'β οΈ Please specify account: `/cline switch <CLINEBOT_API_KEY_2>`'
|
|
78
|
+
}
|
|
79
|
+
clearUsageCache()
|
|
80
|
+
clearProbeCache()
|
|
81
|
+
const settingsApi = getSettingsApi()
|
|
82
|
+
if (settingsApi?.replace) {
|
|
83
|
+
const next = Config({ ...live(), activeAccount: param })
|
|
84
|
+
await settingsApi.replace(next)
|
|
85
|
+
await syncProviderState(next)
|
|
86
|
+
return `β
Active account switched to \`${param}\``
|
|
87
|
+
}
|
|
88
|
+
return `β οΈ Could not apply setting (settings service unavailable).`
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// 4. Subcommand /cline rotate (smart failover next)
|
|
92
|
+
if (subcmd === 'rotate') {
|
|
93
|
+
const res = await rotateToNextAccount(ctx, live(), 'slash_command', getSettingsApi())
|
|
94
|
+
if (res.rotated) {
|
|
95
|
+
await syncProviderState(live())
|
|
96
|
+
return `π **Account Rotated**: switched from \`${res.previousAccount}\` to \`${res.activeAccount}\`. DSH provider updated!`
|
|
97
|
+
}
|
|
98
|
+
return `β οΈ Rotation skipped: ${res.message || 'no alternative configured accounts in pool'}.`
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// 5. Subcommand /cline ping (fresh host reachability probe)
|
|
102
|
+
if (subcmd === 'ping') {
|
|
103
|
+
const health = await probeHealth(pub.baseUrl, { bypassCache: true, timeoutMs: 5000 })
|
|
104
|
+
if (health.ok) {
|
|
105
|
+
return `π **Cline API Pong**: \`${pub.baseUrl}\` is reachable (latency: **${health.latencyMs} ms**, HTTP ${health.status})`
|
|
106
|
+
}
|
|
107
|
+
return `β **Cline API Ping Failed**: ${health.error || 'Host unreachable'}`
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// 6. Subcommand /cline test [model] / smoke
|
|
111
|
+
if (subcmd === 'test' || subcmd === 'smoke') {
|
|
112
|
+
if (param && !isSupportedModel(param, pub.dynamicModels)) {
|
|
113
|
+
return `β οΈ **ClineBot**: Model \`${param}\` is not recognized. Use \`/cline models\` to see available models.`
|
|
114
|
+
}
|
|
115
|
+
const activeKey = await resolveActiveAccountKey(ctx, live())
|
|
116
|
+
if (!activeKey.value) {
|
|
117
|
+
return 'β οΈ **ClineBot**: API key is not configured. Open **Settings β Plugins β ClineBot**.'
|
|
118
|
+
}
|
|
119
|
+
const modelToTest = param || pub.defaultModel || DEFAULT_MODEL_ID
|
|
120
|
+
const outcome = await smokeChat(pub.baseUrl, activeKey.value, {
|
|
121
|
+
model: modelToTest,
|
|
122
|
+
timeoutMs: pub.smokeTimeoutMs,
|
|
123
|
+
})
|
|
124
|
+
recordSessionRequest({
|
|
125
|
+
latencyMs: outcome.latencyMs,
|
|
126
|
+
ok: outcome.ok,
|
|
127
|
+
error: outcome.error,
|
|
128
|
+
promptTokens: outcome.promptTokens || 5,
|
|
129
|
+
completionTokens: outcome.completionTokens || 10,
|
|
130
|
+
})
|
|
131
|
+
|
|
132
|
+
let failoverNotice = ''
|
|
133
|
+
if (outcome.status === 429) {
|
|
134
|
+
const failover = await rotateToNextAccount(ctx, live(), 'smoke_429', getSettingsApi())
|
|
135
|
+
if (failover.rotated) {
|
|
136
|
+
await syncProviderState(live())
|
|
137
|
+
failoverNotice = `\nπ **Auto-failover**: HTTP 429 detected! Active account automatically rotated to \`${failover.activeAccount}\`.`
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
if (outcome.ok) {
|
|
142
|
+
return [
|
|
143
|
+
`### π’ Smoke Test Passed: \`${outcome.model}\``,
|
|
144
|
+
`* **Latency**: ${outcome.latencyMs} ms`,
|
|
145
|
+
`* **Tokens**: prompt: ${outcome.promptTokens}, completion: ${outcome.completionTokens} (total: ${outcome.totalTokens})`,
|
|
146
|
+
`* **Preview**: _"${outcome.preview}"_`,
|
|
147
|
+
].join('\n')
|
|
148
|
+
}
|
|
149
|
+
return `β **Smoke Test Failed**: ${outcome.error} (HTTP ${outcome.status || 'timeout'})${failoverNotice}`
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// 7. Subcommand /cline quota or /cline balance
|
|
153
|
+
const activeKey = await resolveActiveAccountKey(ctx, live())
|
|
154
|
+
if (!activeKey.value) {
|
|
155
|
+
return 'β οΈ **ClineBot**: API key is not configured. Open **Settings β Plugins β ClineBot**.'
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
const [health, usage] = await Promise.all([
|
|
159
|
+
probeHealth(pub.baseUrl, { timeoutMs: 5000 }),
|
|
160
|
+
fetchUsageLimits(pub.baseUrl, activeKey.value, { timeoutMs: 8000 }),
|
|
161
|
+
])
|
|
162
|
+
|
|
163
|
+
const fiveHour = usage?.windows?.fiveHour
|
|
164
|
+
const weekly = usage?.windows?.weekly
|
|
165
|
+
const reset5h = fiveHour?.resetsAt ? new Date(fiveHour.resetsAt).toLocaleTimeString() : 'N/A'
|
|
166
|
+
const resetWk = weekly?.resetsAt ? new Date(weekly.resetsAt).toLocaleDateString() : 'N/A'
|
|
167
|
+
|
|
168
|
+
const lines = [
|
|
169
|
+
`### π€ ClinePass Status (${usage?.plan || 'ClinePass'})`,
|
|
170
|
+
`* **Host Ping**: ${health.ok ? `β
${health.latencyMs} ms` : 'β Unreachable'}`,
|
|
171
|
+
`* **Active Key**: \`${activeKey.envName}\` (${activeKey.source})`,
|
|
172
|
+
`* **Default Model**: \`${pub.defaultModel}\``,
|
|
173
|
+
'',
|
|
174
|
+
`**β± 5-Hour Rolling Limit**: ${formatProgressBar(fiveHour?.percentUsed)} (reset: ${reset5h})`,
|
|
175
|
+
`**π
Weekly Window**: ${formatProgressBar(weekly?.percentUsed)} (reset: ${resetWk})`,
|
|
176
|
+
]
|
|
177
|
+
|
|
178
|
+
if (fiveHour?.percentUsed >= 95) {
|
|
179
|
+
lines.push('', 'π¨ **CRITICAL LIMIT**: 5-hour quota is 95%+ consumed! New requests may be throttled until reset.')
|
|
180
|
+
} else if (fiveHour?.percentUsed >= 80) {
|
|
181
|
+
lines.push('', `β οΈ **Warning**: 5-hour quota is ${fiveHour.percentUsed}% consumed.`)
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
if (sessionStats.totalRequests > 0) {
|
|
185
|
+
lines.push('', `**π Session Telemetry**: ${sessionStats.successfulRequests}/${sessionStats.totalRequests} successful requests, ~${sessionStats.totalTokensEst} total tokens`)
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
if (usage?.user?.email) {
|
|
189
|
+
lines.push(`* **Account**: \`${usage.user.email}\``)
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
return lines.join('\n')
|
|
193
|
+
},
|
|
194
|
+
})
|
|
195
|
+
|
|
196
|
+
ctx.effect(() => () => unregister?.(), 'dsh-clinebot: slash-command')
|
|
197
|
+
})
|
|
198
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@goodandready/dsh-clinebot",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.12",
|
|
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",
|
|
@@ -37,7 +37,8 @@
|
|
|
37
37
|
"url": "git+https://github.com/GooDAnDReaDY/dsh-clinebot.git"
|
|
38
38
|
},
|
|
39
39
|
"scripts": {
|
|
40
|
-
"
|
|
40
|
+
"build:client": "node scripts/build-client.js",
|
|
41
|
+
"test": "node scripts/build-client.js && node --test test/*.test.js"
|
|
41
42
|
},
|
|
42
43
|
"engines": {
|
|
43
44
|
"node": ">=20"
|