@goodandready/dsh-clinebot 0.4.1 → 0.4.3

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 CHANGED
@@ -5,6 +5,20 @@ 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.4.3] - 2026-09-24
9
+
10
+ ### Fixed
11
+ - **DSH Settings Reload & DataCloneError Normalization (#96)**: Extended `plainConfig` in `lib/config.js` to recursively resolve functional getters (`() => current`), nested volatile getter objects, and arrays before passing configuration to `structuredClone`, completely eliminating `DataCloneError: () => current could not be cloned` during Cordis plugin reload and settings updates.
12
+ - **Config Schema Non-Volatile Alignment (#96)**: Removed the `.volatile()` marker from `planSyncedAt`, ensuring that internal synchronization timestamps are preserved as runtime state and not serialized into user settings forms.
13
+ - **Boot and Reload Regression Test (#96)**: Added regression tests covering deep functional getter resolution in `plainConfig` and verifying host boot/reload stability with profile-shaped `dynamicModels`.
14
+
15
+ ## [0.4.2] - 2026-09-24
16
+
17
+ ### Fixed
18
+ - **Slash Command Plain Text Formatting (#107)**: Eliminated raw markdown formatting (`###`, `**`, backticks) in `/cline` command responses, preventing unrendered markdown clutter in DSH chat `<pre>` blocks.
19
+ - **Single-Line Collapsed Summary (#107)**: Added a concise first-line summary strictly `<= 120` characters across all subcommands (`quota`, `models`, `accounts`, `stats`, `test`), displaying cleanly in the collapsed command card without premature line breaks or truncation ellipses.
20
+ - **Active Key Name Resolution (#107)**: Fixed `resolveActiveAccountKey` to consistently set both `apiKeyEnv` and `envName` for pooled and default accounts, preventing `Active Key: undefined` in command output and status payloads.
21
+
8
22
  ## [0.4.1] - 2026-09-24
9
23
 
10
24
  ### Fixed
package/lib/config.js CHANGED
@@ -52,7 +52,7 @@ export const Config = z.object({
52
52
  })).default([])
53
53
  .description('Models automatically discovered from the official ClinePass subscription plan.'),
54
54
  planSyncedAt: z.number().default(0)
55
- .description('Timestamp when models were last synchronized with active subscription plan.').volatile(),
55
+ .description('Timestamp when models were last synchronized with active subscription plan.'),
56
56
  timeoutMs: z.number().default(DEFAULT_TIMEOUT_MS)
57
57
  .description('HTTP probe timeout in milliseconds.').volatile(),
58
58
  smokeTimeoutMs: z.number().default(DEFAULT_SMOKE_TIMEOUT_MS)
@@ -73,15 +73,20 @@ function isVolatileRef(value) {
73
73
  return !!value && typeof value === 'object' && !Array.isArray(value) && typeof value.get === 'function'
74
74
  }
75
75
 
76
- // DSH stores each volatile field as { get }. structuredClone cannot copy that
77
- // getter, so callers need a plain snapshot before validation.
76
+ // DSH stores each volatile field as { get } or getter functions. structuredClone
77
+ // cannot copy functions or getters, so callers need a plain snapshot before validation.
78
78
  export function plainConfig(cfg) {
79
+ while (typeof cfg === 'function') {
80
+ cfg = cfg()
81
+ }
79
82
  if (isVolatileRef(cfg)) return plainConfig(cfg.get())
80
83
  if (!cfg || typeof cfg !== 'object') return cfg
84
+ if (Array.isArray(cfg)) {
85
+ return cfg.map((item) => plainConfig(item))
86
+ }
81
87
  const out = {}
82
88
  for (const key of Object.keys(cfg)) {
83
- const value = cfg[key]
84
- out[key] = isVolatileRef(value) ? value.get() : value
89
+ out[key] = plainConfig(cfg[key])
85
90
  }
86
91
  return out
87
92
  }
@@ -18,6 +18,7 @@ import {
18
18
  sessionStats,
19
19
  getLastRotation,
20
20
  usageCache,
21
+ DEFAULT_API_KEY_ENV,
21
22
  } from './cline-client.js'
22
23
 
23
24
  export function resolvePathWithHome(p) {
@@ -58,14 +59,21 @@ export async function resolveActiveAccountKey(ctx, cfg, pool = null) {
58
59
  const accountPool = pool || await resolveAccountPool(ctx, cfg)
59
60
  const configured = accountPool.filter((acc) => acc.present && acc.value)
60
61
  if (!configured.length) {
61
- return { envName: publicConfig(cfg).apiKeyEnv, value: '', source: 'none', id: 'default' }
62
+ const fallbackEnv = publicConfig(cfg).apiKeyEnv || DEFAULT_API_KEY_ENV
63
+ return { envName: fallbackEnv, apiKeyEnv: fallbackEnv, value: '', source: 'none', id: 'default' }
62
64
  }
63
65
  const pub = publicConfig(cfg)
66
+ let chosen = configured[0]
64
67
  if (pub.activeAccount) {
65
68
  const pinned = configured.find((acc) => acc.apiKeyEnv === pub.activeAccount || acc.id === pub.activeAccount)
66
- if (pinned) return pinned
69
+ if (pinned) chosen = pinned
70
+ }
71
+ const keyEnv = chosen.apiKeyEnv || chosen.envName || DEFAULT_API_KEY_ENV
72
+ return {
73
+ ...chosen,
74
+ envName: keyEnv,
75
+ apiKeyEnv: keyEnv,
67
76
  }
68
- return configured[0]
69
77
  }
70
78
 
71
79
  export async function buildStatus(ctx, cfg) {
@@ -36,23 +36,27 @@ export function registerSlashCommand(ctx, { live, getSettingsApi, syncProviderSt
36
36
  if (subcmd === 'models') {
37
37
  const allModels = getAllModels(pub.dynamicModels)
38
38
  const disabledSet = new Set(pub.disabledModels || [])
39
+ const activeCount = allModels.filter((m) => !disabledSet.has(m.id)).length
39
40
  const syncStatus = pub.planSynced
40
- ? `✅ Verified with plan (${pub.planSyncedAt ? new Date(pub.planSyncedAt).toLocaleDateString() : 'synced'})`
41
- : '⚠️ Not verified with plan (fallback catalog)'
41
+ ? `Verified (${pub.planSyncedAt ? new Date(pub.planSyncedAt).toLocaleDateString([], { day: '2-digit', month: '2-digit', year: 'numeric' }) : 'synced'})`
42
+ : 'Unverified fallback'
43
+ const summary = `ClinePass Models · ${activeCount}/${allModels.length} active · ${pub.planSynced ? 'Verified with plan' : 'Fallback catalog'}`
42
44
  const lines = [
43
- '### 🎯 ClinePass Models Catalog',
44
- `* **Plan Sync**: ${syncStatus}`,
45
- `* **Total models**: ${allModels.length} (${allModels.filter((m) => !disabledSet.has(m.id)).length} active)`,
45
+ summary,
46
+ '',
47
+ `Plan Sync: ${syncStatus}`,
48
+ `Active Models: ${activeCount} of ${allModels.length}`,
46
49
  ]
47
50
  if (pub.defaultModelWarning) {
48
- lines.push(`* ⚠️ **Warning**: ${pub.defaultModelWarning}`)
51
+ lines.push(`Warning: ${pub.defaultModelWarning}`)
49
52
  }
50
53
  lines.push('')
51
54
  for (const m of allModels) {
52
- const active = !disabledSet.has(m.id) ? '✅' : '❌'
53
- const isVis = isVisionModel(m.id, pub.dynamicModels) ? '📷 Vision' : '📝 Text'
54
- const efforts = Array.isArray(m.reasoningEfforts) ? `🧠 [${m.reasoningEfforts.join(', ')}]` : ''
55
- lines.push(`* ${active} **${m.name}** (\`${m.id}\`) — ${isVis} · ${formatModelContext(m.contextLength)} ${efforts}`)
55
+ const active = !disabledSet.has(m.id) ? '[OK]' : '[--]'
56
+ const isVis = isVisionModel(m.id, pub.dynamicModels) ? 'Vision' : 'Text'
57
+ const efforts = Array.isArray(m.reasoningEfforts) ? ` · Reasoning: ${m.reasoningEfforts.join(', ')}` : ''
58
+ lines.push(` ${active} ${m.name} (${m.id})`)
59
+ lines.push(` ${isVis} · ${formatModelContext(m.contextLength)}${efforts}`)
56
60
  }
57
61
  return lines.join('\n')
58
62
  }
@@ -60,31 +64,37 @@ export function registerSlashCommand(ctx, { live, getSettingsApi, syncProviderSt
60
64
  // 2. Subcommand /cline accounts
61
65
  if (subcmd === 'accounts') {
62
66
  const pool = await resolveAccountPool(ctx, live())
67
+ const activeKey = await resolveActiveAccountKey(ctx, live(), pool)
68
+ const activeName = activeKey.apiKeyEnv || activeKey.envName || pub.apiKeyEnv
69
+ const configuredCount = pool.filter((a) => a.present).length
70
+ const summary = `ClinePass Accounts · ${configuredCount}/${pool.length} configured · Active: ${activeName}`
63
71
  const lines = [
64
- '### 🔑 ClinePass Accounts Pool',
65
- `* **Active Account**: \`${pub.activeAccount || pub.apiKeyEnv}\``,
72
+ summary,
73
+ '',
74
+ `Active Account: ${activeName}`,
66
75
  '',
67
76
  ]
68
77
  for (const acc of pool) {
69
- const pinBadge = acc.isPinned ? '📌 [Pinned]' : ''
70
- const statusBadge = acc.present ? '✅ Configured' : '⚠️ Missing Key'
78
+ const pinStr = acc.isPinned ? ' [Pinned]' : ''
79
+ const statusStr = acc.present ? 'Configured' : 'Missing Key'
71
80
  const cacheKey = `cline:usage:${(acc.value || '').slice(-8)}`
72
81
  const cached = usageCache.get(cacheKey)?.data
73
- const usageInfo = cached?.windows?.fiveHour ? ` · ⏱ ${cached.windows.fiveHour.percentUsed}% used` : ''
74
- lines.push(`* **${acc.label}** (\`${acc.apiKeyEnv}\`): ${statusBadge} ${pinBadge}${usageInfo}`)
82
+ const usageInfo = cached?.windows?.fiveHour ? ` · 5h ${cached.windows.fiveHour.percentUsed}% used` : ''
83
+ lines.push(` ${acc.label} (${acc.apiKeyEnv})`)
84
+ lines.push(` Status: ${statusStr}${pinStr}${usageInfo}`)
75
85
  }
76
86
  const lastRot = getLastRotation()
77
87
  if (lastRot) {
78
- lines.push('', `* **Last Failover**: \`${lastRot.from}\` → \`${lastRot.to}\` (${lastRot.reason})`)
88
+ lines.push('', `Last Failover: ${lastRot.from} -> ${lastRot.to} (${lastRot.reason})`)
79
89
  }
80
- lines.push('', 'Switch active account: `/cline switch <env_variable_name>`')
90
+ lines.push('', 'Switch active account: /cline switch <KEY_NAME>')
81
91
  return lines.join('\n')
82
92
  }
83
93
 
84
94
  // 3. Subcommand /cline switch <account>
85
95
  if (subcmd === 'switch') {
86
96
  if (!param) {
87
- return '⚠️ Please specify account: `/cline switch <CLINEBOT_API_KEY_2>`'
97
+ return 'Please specify account: /cline switch <KEY_NAME>'
88
98
  }
89
99
  clearUsageCache()
90
100
  clearProbeCache()
@@ -93,9 +103,9 @@ export function registerSlashCommand(ctx, { live, getSettingsApi, syncProviderSt
93
103
  const next = Config({ ...plainConfig(live()), activeAccount: param })
94
104
  await settingsApi.replace(next)
95
105
  await syncProviderState(next)
96
- return `✅ Active account switched to \`${param}\``
106
+ return `Active account switched to ${param}`
97
107
  }
98
- return `⚠️ Could not apply setting (settings service unavailable).`
108
+ return 'Could not apply setting (settings service unavailable).'
99
109
  }
100
110
 
101
111
  // 4. Subcommand /cline rotate (smart failover next)
@@ -103,42 +113,44 @@ export function registerSlashCommand(ctx, { live, getSettingsApi, syncProviderSt
103
113
  const res = await rotateToNextAccount(ctx, live(), 'slash_command', getSettingsApi())
104
114
  if (res.rotated) {
105
115
  await syncProviderState(live())
106
- return `🔄 **Account Rotated**: switched from \`${res.previousAccount}\` to \`${res.activeAccount}\`. DSH provider updated!`
116
+ return `Account rotated: switched from ${res.previousAccount} to ${res.activeAccount}. DSH provider updated.`
107
117
  }
108
- return `⚠️ Rotation skipped: ${res.message || 'no alternative configured accounts in pool'}.`
118
+ return `Rotation skipped: ${res.message || 'no alternative configured accounts in pool'}.`
109
119
  }
110
120
 
111
121
  // 5. Subcommand /cline ping (fresh host reachability probe)
112
122
  if (subcmd === 'ping') {
113
123
  const health = await probeHealth(pub.baseUrl, { bypassCache: true, timeoutMs: 5000 })
114
124
  if (health.ok) {
115
- return `🏓 **Cline API Pong**: \`${pub.baseUrl}\` is reachable (latency: **${health.latencyMs} ms**, HTTP ${health.status})`
125
+ return `Cline API Pong: ${pub.baseUrl} is reachable (latency: ${health.latencyMs} ms, HTTP ${health.status})`
116
126
  }
117
- return `❌ **Cline API Ping Failed**: ${health.error || 'Host unreachable'}`
127
+ return `Cline API Ping Failed: ${health.error || 'Host unreachable'}`
118
128
  }
119
129
 
120
130
  // 6. Subcommand /cline stats (real DSH stream metrics)
121
131
  if (subcmd === 'stats' || subcmd === 'telemetry') {
132
+ const summary = `ClineBot Telemetry · ${sessionStats.successfulRequests}/${sessionStats.totalRequests} reqs · ~${sessionStats.totalTokensEst} tokens`
122
133
  const lines = [
123
- '### 📈 DSH ClineBot Stream Telemetry',
124
- `* **Requests**: ${sessionStats.successfulRequests} successful / ${sessionStats.totalRequests} total${sessionStats.abortedRequests ? ` (${sessionStats.abortedRequests} aborted)` : ''}${sessionStats.failedRequests ? ` (${sessionStats.failedRequests} failed)` : ''}`,
125
- `* **Estimated Tokens**: ~${sessionStats.totalTokensEst} total (~${sessionStats.promptTokensEst} prompt, ~${sessionStats.completionTokensEst} completion)`,
126
- `* **Last Stream Latency**: ${sessionStats.lastLatencyMs !== null ? `${sessionStats.lastLatencyMs} ms` : '—'}`,
127
- `* **Last Request At**: ${sessionStats.lastRequestAt ? new Date(sessionStats.lastRequestAt).toLocaleTimeString() : '—'}`,
134
+ summary,
135
+ '',
136
+ `Requests: ${sessionStats.successfulRequests} successful / ${sessionStats.totalRequests} total${sessionStats.abortedRequests ? ` (${sessionStats.abortedRequests} aborted)` : ''}${sessionStats.failedRequests ? ` (${sessionStats.failedRequests} failed)` : ''}`,
137
+ `Tokens: ~${sessionStats.totalTokensEst} total (~${sessionStats.promptTokensEst} prompt, ~${sessionStats.completionTokensEst} completion)`,
138
+ `Last Latency: ${sessionStats.lastLatencyMs !== null ? `${sessionStats.lastLatencyMs} ms` : '—'}`,
139
+ `Last Request: ${sessionStats.lastRequestAt ? new Date(sessionStats.lastRequestAt).toLocaleTimeString() : '—'}`,
128
140
  ]
129
141
  if (sessionStats.lastError) {
130
- lines.push(`* **Last Stream Error**: ${sessionStats.lastError}`)
142
+ lines.push(`Last Error: ${sessionStats.lastError}`)
131
143
  }
132
144
  if (sessionStats.byModel && Object.keys(sessionStats.byModel).length) {
133
- lines.push('', '**By Model:**')
145
+ lines.push('', 'By Model:')
134
146
  for (const [mId, mStats] of Object.entries(sessionStats.byModel)) {
135
- lines.push(`* \`${mId}\`: ${mStats.requests} reqs, ~${mStats.promptTokens + mStats.completionTokens} tokens`)
147
+ lines.push(` ${mId}: ${mStats.requests} reqs, ~${mStats.promptTokens + mStats.completionTokens} tokens`)
136
148
  }
137
149
  }
138
150
  if (sessionStats.lastSmoke) {
139
151
  const smoke = sessionStats.lastSmoke
140
152
  const smokeTime = new Date(smoke.at).toLocaleTimeString()
141
- lines.push('', `* **Last Smoke Test**: ${smoke.ok ? '✅ Passed' : '❌ Failed'} (${smoke.latencyMs} ms at ${smokeTime}${smoke.model ? `, model: \`${smoke.model}\`` : ''})`)
153
+ lines.push('', `Last Smoke Test: ${smoke.ok ? 'Passed' : 'Failed'} (${smoke.latencyMs} ms at ${smokeTime}${smoke.model ? `, model: ${smoke.model}` : ''})`)
142
154
  }
143
155
  return lines.join('\n')
144
156
  }
@@ -146,11 +158,11 @@ export function registerSlashCommand(ctx, { live, getSettingsApi, syncProviderSt
146
158
  // 7. Subcommand /cline test [model] / smoke
147
159
  if (subcmd === 'test' || subcmd === 'smoke') {
148
160
  if (param && !isSupportedModel(param, pub.dynamicModels)) {
149
- return `⚠️ **ClineBot**: Model \`${param}\` is not recognized. Use \`/cline models\` to see available models.`
161
+ return `ClineBot: Model "${param}" is not recognized. Use /cline models to see available models.`
150
162
  }
151
163
  const activeKey = await resolveActiveAccountKey(ctx, live())
152
164
  if (!activeKey.value) {
153
- return '⚠️ **ClineBot**: API key is not configured. Open **Settings → Plugins → ClineBot**.'
165
+ return 'ClineBot: API key is not configured. Open Settings -> Plugins -> ClineBot.'
154
166
  }
155
167
  const modelToTest = param || pub.defaultModel || DEFAULT_MODEL_ID
156
168
  const outcome = await smokeChat(pub.baseUrl, activeKey.value, {
@@ -169,25 +181,28 @@ export function registerSlashCommand(ctx, { live, getSettingsApi, syncProviderSt
169
181
  const failover = await rotateToNextAccount(ctx, live(), 'smoke_429', getSettingsApi())
170
182
  if (failover.rotated) {
171
183
  await syncProviderState(live())
172
- failoverNotice = `\n🔄 **Auto-failover**: HTTP 429 detected! Active account automatically rotated to \`${failover.activeAccount}\`.`
184
+ failoverNotice = `\nAuto-failover: HTTP 429 detected! Active account automatically rotated to ${failover.activeAccount}.`
173
185
  }
174
186
  }
175
187
 
176
188
  if (outcome.ok) {
189
+ const summary = `Smoke Test Passed · ${outcome.model} · ${outcome.latencyMs} ms`
177
190
  return [
178
- `### 🟢 Smoke Test Passed: \`${outcome.model}\``,
179
- `* **Latency**: ${outcome.latencyMs} ms`,
180
- `* **Tokens**: prompt: ${outcome.promptTokens}, completion: ${outcome.completionTokens} (total: ${outcome.totalTokens})`,
181
- `* **Preview**: _"${outcome.preview}"_`,
191
+ summary,
192
+ '',
193
+ `Model: ${outcome.model}`,
194
+ `Latency: ${outcome.latencyMs} ms`,
195
+ `Tokens: prompt: ${outcome.promptTokens}, completion: ${outcome.completionTokens} (total: ${outcome.totalTokens})`,
196
+ `Preview: "${outcome.preview}"`,
182
197
  ].join('\n')
183
198
  }
184
- return `❌ **Smoke Test Failed**: ${outcome.error} (HTTP ${outcome.status || 'timeout'})${failoverNotice}`
199
+ return `Smoke Test Failed: ${outcome.error} (HTTP ${outcome.status || 'timeout'})${failoverNotice}`
185
200
  }
186
201
 
187
- // 7. Subcommand /cline quota or /cline balance
202
+ // 8. Subcommand /cline quota or /cline balance
188
203
  const activeKey = await resolveActiveAccountKey(ctx, live())
189
204
  if (!activeKey.value) {
190
- return '⚠️ **ClineBot**: API key is not configured. Open **Settings → Plugins → ClineBot**.'
205
+ return 'ClineBot: API key is not configured. Open Settings -> Plugins -> ClineBot.'
191
206
  }
192
207
 
193
208
  const [health, usage] = await Promise.all([
@@ -197,41 +212,55 @@ export function registerSlashCommand(ctx, { live, getSettingsApi, syncProviderSt
197
212
 
198
213
  const fiveHour = usage?.windows?.fiveHour
199
214
  const weekly = usage?.windows?.weekly
200
- const reset5h = fiveHour?.resetsAt ? new Date(fiveHour.resetsAt).toLocaleTimeString() : 'N/A'
201
- const resetWk = weekly?.resetsAt ? new Date(weekly.resetsAt).toLocaleDateString() : 'N/A'
215
+ const reset5h = fiveHour?.resetsAt ? new Date(fiveHour.resetsAt).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' }) : 'N/A'
216
+ const resetWk = weekly?.resetsAt ? new Date(weekly.resetsAt).toLocaleDateString([], { day: '2-digit', month: '2-digit' }) : 'N/A'
217
+ const resetWkFull = weekly?.resetsAt ? new Date(weekly.resetsAt).toLocaleDateString([], { day: '2-digit', month: '2-digit', year: 'numeric' }) : 'N/A'
202
218
 
219
+ const planRaw = usage?.plan || 'ClinePass'
220
+ const planShort = planRaw.replace(/\s*\(\$\d+\.\d+\/mo\)/i, '').trim()
221
+ const wkPct = typeof weekly?.percentUsed === 'number' ? `${weekly.percentUsed}%` : 'N/A'
222
+ const fhPct = typeof fiveHour?.percentUsed === 'number' ? `${fiveHour.percentUsed}%` : 'N/A'
223
+ const pingStr = health.ok ? `${health.latencyMs} ms` : 'offline'
224
+ const keyName = activeKey.apiKeyEnv || activeKey.envName || pub.apiKeyEnv
225
+
226
+ // Line 1: Short summary for collapsed command row (strictly <= 120 chars)
227
+ const summary = `${planShort} · Weekly ${wkPct}${weekly?.resetsAt ? ` (reset ${resetWk})` : ''} · 5h ${fhPct} · Ping ${pingStr}`
228
+
229
+ const pad = (str, len = 15) => (str + ':').padEnd(len, ' ')
203
230
  const lines = [
204
- `### 🤖 ClinePass Status (${usage?.plan || 'ClinePass'})`,
205
- `* **Host Ping**: ${health.ok ? `✅ ${health.latencyMs} ms` : '❌ Unreachable'}`,
206
- `* **Active Key**: \`${activeKey.envName}\` (${activeKey.source})`,
207
- `* **Default Model**: \`${pub.defaultModel}\``,
231
+ summary,
232
+ '',
233
+ `${pad('Plan')}${planRaw}`,
234
+ `${pad('Active Key')}${keyName} (${activeKey.source})`,
235
+ `${pad('Default Model')}${pub.defaultModel}`,
236
+ `${pad('Host Ping')}${health.ok ? `${health.latencyMs} ms (reachable)` : 'Unreachable'}`,
208
237
  ]
238
+ if (usage?.user?.email) {
239
+ lines.splice(3, 0, `${pad('Account')}${usage.user.email}`)
240
+ }
209
241
  if (pub.defaultModelWarning) {
210
- lines.push(`* ⚠️ **Warning**: ${pub.defaultModelWarning}`)
242
+ lines.push(`${pad('Warning')}${pub.defaultModelWarning}`)
211
243
  }
244
+
212
245
  lines.push(
213
246
  '',
214
- `**⏱ 5-Hour Rolling Limit**: ${formatProgressBar(fiveHour?.percentUsed)} (reset: ${reset5h})`,
215
- `**📅 Weekly Window**: ${formatProgressBar(weekly?.percentUsed)} (reset: ${resetWk})`,
247
+ `${pad('5-Hour Quota')}${formatProgressBar(fiveHour?.percentUsed)} (resets: ${reset5h})`,
248
+ `${pad('Weekly Quota')}${formatProgressBar(weekly?.percentUsed)} (resets: ${resetWkFull})`,
216
249
  )
217
250
 
218
251
  if (fiveHour?.percentUsed >= 95) {
219
- lines.push('', '🚨 **CRITICAL LIMIT**: 5-hour quota is 95%+ consumed! New requests may be throttled until reset.')
252
+ lines.push('', 'CRITICAL: 5-hour quota is 95%+ consumed! New requests may be throttled until reset.')
220
253
  } else if (fiveHour?.percentUsed >= 80) {
221
- lines.push('', `⚠️ **Warning**: 5-hour quota is ${fiveHour.percentUsed}% consumed.`)
254
+ lines.push('', `Warning: 5-hour quota is ${fiveHour.percentUsed}% consumed.`)
222
255
  }
223
256
 
224
257
  if (sessionStats.totalRequests > 0) {
225
- lines.push('', `**📊 Session Telemetry**: ${sessionStats.successfulRequests}/${sessionStats.totalRequests} successful requests, ~${sessionStats.totalTokensEst} total tokens`)
226
- }
227
-
228
- if (usage?.user?.email) {
229
- lines.push(`* **Account**: \`${usage.user.email}\``)
258
+ lines.push('', `Session Stats: ${sessionStats.successfulRequests}/${sessionStats.totalRequests} successful requests, ~${sessionStats.totalTokensEst} total tokens`)
230
259
  }
231
260
 
232
261
  return lines.join('\n')
233
262
  } catch (err) {
234
- return `❌ **/cline error**: ${String(err?.message || err)}`
263
+ return `ClineBot error: ${String(err?.message || err)}`
235
264
  }
236
265
  }
237
266
 
@@ -250,8 +279,8 @@ export function registerSlashCommand(ctx, { live, getSettingsApi, syncProviderSt
250
279
  } catch (err) {
251
280
  return {
252
281
  kind: 'error',
253
- text: `❌ /cline error: ${String(err?.message || err)}`,
254
- toString() { return `❌ /cline error: ${String(err?.message || err)}` },
282
+ text: `ClineBot error: ${String(err?.message || err)}`,
283
+ toString() { return `ClineBot error: ${String(err?.message || err)}` },
255
284
  }
256
285
  }
257
286
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@goodandready/dsh-clinebot",
3
- "version": "0.4.1",
3
+ "version": "0.4.3",
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",