@goodandready/dsh-clinebot 0.6.4 → 0.6.5

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,15 @@ 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.6.5] - 2026-10-21
9
+
10
+ ### Added
11
+ - **Grouped model table**: Settings now separates `Included in plan`, `Other models` and `Custom models` with per-group counts.
12
+ - **Extended `/cline`**: `/cline cost` (`/cline budget`) reports estimated UTC day/month spend and guard state; `/cline usage` falls through to the quota report, which now includes the monthly window.
13
+
14
+ ### Changed
15
+ - Headless/web fixture profiles no longer carry the removed `transport` and `proxyMode` fields.
16
+
8
17
  ## [0.6.4] - 2026-10-21
9
18
 
10
19
  ### Added
package/README.md CHANGED
@@ -4,7 +4,7 @@
4
4
 
5
5
  <h3>Native ClineBot / ClinePass Provider Companion for DeepSeek Harness</h3>
6
6
 
7
- <p><b>0.6.4</b> adds P50/P95 latency, clickable heatmap detail, JSON export and configurable retention on top of quota forecasts and spend guards. Chat runs on the native <code>ctx.llm</code> adapter and requires DSH <code>0.2.1-rc.2</code>; the legacy <code>llm-pi-ai</code>, loopback proxy and <code>llm/stream</code> path are removed.</p>
7
+ <p><b>0.6.5</b> groups the model table by plan availability and adds <code>/cline cost</code> / monthly quota reporting on top of the analytics work. Chat runs on the native <code>ctx.llm</code> adapter and requires DSH <code>0.2.1-rc.2</code>; the legacy <code>llm-pi-ai</code>, loopback proxy and <code>llm/stream</code> path are removed.</p>
8
8
 
9
9
  <p align="center">
10
10
  <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>
package/README.ru.md CHANGED
@@ -4,7 +4,7 @@
4
4
 
5
5
  <h3>Нативное подключение провайдера ClineBot / ClinePass для DeepSeek Harness</h3>
6
6
 
7
- <p><b>0.6.4</b> добавляет P50/P95 задержки, кликабельный heatmap, JSON-экспорт и настраиваемый retention поверх прогноза квоты и лимитов трат. Чат работает через нативный адаптер <code>ctx.llm</code>, требуется DSH <code>0.2.1-rc.2</code>; старые <code>llm-pi-ai</code>, loopback-прокси и <code>llm/stream</code> удалены.</p>
7
+ <p><b>0.6.5</b> группирует таблицу моделей по доступности в тарифе и добавляет <code>/cline cost</code> / отчёт по месячной квоте поверх аналитики. Чат работает через нативный адаптер <code>ctx.llm</code>, требуется DSH <code>0.2.1-rc.2</code>; старые <code>llm-pi-ai</code>, loopback-прокси и <code>llm/stream</code> удалены.</p>
8
8
 
9
9
  <p align="center">
10
10
  <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>
package/README.zh.md CHANGED
@@ -4,7 +4,7 @@
4
4
 
5
5
  <h3>适用于 DeepSeek Harness 的 ClineBot / ClinePass 原生模型提供商伴侣插件</h3>
6
6
 
7
- <p><b>0.6.4</b> 添加 P50/P95 延迟、可点击热力图、JSON 导出和可配置保留期,并保留配额预测与花费上限。聊天使用原生 <code>ctx.llm</code> 适配器,需要 DSH <code>0.2.1-rc.2</code>;旧的 <code>llm-pi-ai</code>、回环代理和 <code>llm/stream</code> 已删除。</p>
7
+ <p><b>0.6.5</b> 按套餐可用性对模型表分组,并在分析功能之上添加 <code>/cline cost</code> / 每月配额报告。聊天使用原生 <code>ctx.llm</code> 适配器,需要 DSH <code>0.2.1-rc.2</code>;旧的 <code>llm-pi-ai</code>、回环代理和 <code>llm/stream</code> 已删除。</p>
8
8
 
9
9
  <p align="center">
10
10
  <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>
package/lib/client.js CHANGED
@@ -140,6 +140,7 @@ const en = {
140
140
  'models.visible_models_hint': 'Choose which models appear in the DSH chat selector (empty means all supported models).',
141
141
  'models.in_plan': 'In Plan',
142
142
  'models.out_of_plan': 'Out of Plan',
143
+ 'models.group_plan': 'Included in plan', 'models.group_other': 'Other models', 'models.group_custom': 'Custom models',
143
144
  'models.plan_only': 'Plan Only',
144
145
  'models.show_all': 'Show All',
145
146
  'models.clean_stale': 'Clean Stale ({count})',
@@ -425,6 +426,7 @@ const zh = {
425
426
  'models.visible_models_hint': '选择在 DSH 聊天选择器中显示的模型(为空时显示全部可用模型)。',
426
427
  'models.in_plan': '套餐内',
427
428
  'models.out_of_plan': '套餐外',
429
+ 'models.group_plan': '套餐内模型', 'models.group_other': '其他模型', 'models.group_custom': '自定义模型',
428
430
  'models.plan_only': '仅套餐模型',
429
431
  'models.show_all': '显示全部',
430
432
  'models.clean_stale': '清理过期 ({count})',
@@ -1085,6 +1087,7 @@ function ensureCss() {
1085
1087
  .cb-table{font-size:12px}
1086
1088
  .cb-table th{padding:5px 8px;border-bottom:1px solid rgba(127,127,127,.18);font-size:11px;font-weight:600;opacity:.8}
1087
1089
  .cb-table td{padding:5px 8px;border-bottom:1px solid rgba(127,127,127,.18);font-size:12px}
1090
+ .cb-model-group td{padding:8px 8px 4px;color:var(--dsw-alias-label-secondary);font-size:11px;font-weight:650;background:rgba(127,127,127,.08)}
1088
1091
  .cb-progress-track,.cb-bar-track{height:8px;border-radius:999px;background:rgba(127,127,127,.2)}
1089
1092
  .cb-progress-bar-fill,.cb-bar-fill,.cb-footFill,.cb-meter-fill{background:#4fbf86}
1090
1093
  .cb-progress-bar-warn{background:#d9a24a}
@@ -3037,7 +3040,6 @@ function ModelsSection({
3037
3040
  const [editingModelId, setEditingModelId] = React.useState(null)
3038
3041
  const [customCtxVal, setCustomCtxVal] = React.useState('')
3039
3042
  const [customMaxVal, setCustomMaxVal] = React.useState('')
3040
-
3041
3043
  function formatCtx(num) {
3042
3044
  num = Number(num) || 200000
3043
3045
  if (num >= 1000000) {
@@ -3050,7 +3052,6 @@ function ModelsSection({
3050
3052
  }
3051
3053
  return String(num)
3052
3054
  }
3053
-
3054
3055
  const filteredModels = modelsList.filter((m) => {
3055
3056
  if (viewFilter === 'vision') {
3056
3057
  const hasVision = Boolean(m.input?.includes('image') || m.input?.includes('vision'))
@@ -3071,10 +3072,13 @@ function ModelsSection({
3071
3072
  }
3072
3073
  return true
3073
3074
  })
3074
-
3075
- return React.createElement(
3076
- 'div',
3077
- { className: 'cb-section-card' },
3075
+ const groupFor = (m) => (m.isCustom ? 'custom' : (m.inPlan ? 'plan' : 'other'))
3076
+ const orderedModels = [...filteredModels].sort((a, b) => {
3077
+ const order = { plan: 0, other: 1, custom: 2 }
3078
+ return order[groupFor(a)] - order[groupFor(b)]
3079
+ })
3080
+ let lastGroup = ''
3081
+ return React.createElement('div', { className: 'cb-section-card' },
3078
3082
  defaultModelWarning ? React.createElement('div', { className: 'cb-alert-bad cb-inline-alert', style: { marginBottom: '12px' } }, React.createElement(CbIcon, { name: 'warning', size: 14 }), ` ${defaultModelWarning}`) : null,
3079
3083
  React.createElement(VisibleModelsSub, {
3080
3084
  modelsList,
@@ -3138,8 +3142,6 @@ function ModelsSection({
3138
3142
  )
3139
3143
  ),
3140
3144
  React.createElement('div', { className: 'cb-section-desc' }, t('models.desc')),
3141
-
3142
- // Search and display filter bar
3143
3145
  React.createElement(
3144
3146
  'div',
3145
3147
  {
@@ -3214,7 +3216,6 @@ function ModelsSection({
3214
3216
  )
3215
3217
  )
3216
3218
  ),
3217
-
3218
3219
  React.createElement(
3219
3220
  'table',
3220
3221
  { className: 'cb-table' },
@@ -3234,7 +3235,10 @@ function ModelsSection({
3234
3235
  React.createElement(
3235
3236
  'tbody',
3236
3237
  null,
3237
- filteredModels.map((m) => {
3238
+ orderedModels.map((m) => {
3239
+ const groupKey = groupFor(m)
3240
+ const showGroup = groupKey !== lastGroup
3241
+ lastGroup = groupKey
3238
3242
  const isEnabled = !disabledSet.has(m.id)
3239
3243
  const defaultCtx = m.defaultContextLength || m.clineContextLength || 200000
3240
3244
  const origCtx = m.originalContextLength || 200000
@@ -3243,10 +3247,7 @@ function ModelsSection({
3243
3247
  const origProv = m.originalProvider || 'Original'
3244
3248
  const isOverridden = Boolean(m.isContextOverridden)
3245
3249
  const isOriginal = isOverridden && (m.contextLength === origCtx && (m.maxTokens || defaultMax) === origMax)
3246
-
3247
- return React.createElement(
3248
- 'tr',
3249
- { key: m.id },
3250
+ const row = React.createElement('tr', { key: m.id },
3250
3251
  React.createElement(
3251
3252
  'td',
3252
3253
  null,
@@ -3490,6 +3491,15 @@ function ModelsSection({
3490
3491
  })()
3491
3492
  )
3492
3493
  )
3494
+ const groupCount = orderedModels.filter((entry) => groupFor(entry) === groupKey).length
3495
+ return React.createElement(React.Fragment, { key: `group-wrap-${m.id}` },
3496
+ showGroup
3497
+ ? React.createElement('tr', { className: 'cb-model-group' },
3498
+ React.createElement('td', { colSpan: 5 }, `${t(`models.group_${groupKey}`)} · ${groupCount}`)
3499
+ )
3500
+ : null,
3501
+ row
3502
+ )
3493
3503
  })
3494
3504
  )
3495
3505
  ),
@@ -1,5 +1,7 @@
1
1
  import { publicConfig, plainConfig, Config } from './config.js'
2
2
  import { resolveActiveAccountKey, formatProgressBar } from './provider-sync.js'
3
+ import { getBudgetSpend } from './stats-storage.js'
4
+ import { budgetStatus, budgetErrorMessage } from './budget.js'
3
5
  import { resolveAccountPool, rotateToNextAccount, getLastRotation } from './account-pool.js'
4
6
  import {
5
7
  getAllModels,
@@ -148,7 +150,27 @@ export function registerSlashCommand(ctx, { live, getSettingsApi, syncProviderSt
148
150
  return `Cline API Ping Failed: ${health.error || 'Host unreachable'}`
149
151
  }
150
152
 
151
- // 6. Subcommand /cline stats (real DSH stream metrics)
153
+ // 6. Subcommand /cline cost or /cline budget
154
+ if (subcmd === 'cost' || subcmd === 'budget') {
155
+ const spend = getBudgetSpend()
156
+ const daily = budgetStatus({ dailyBudgetUsd: pub.dailyBudgetUsd }, spend)
157
+ const monthly = budgetStatus({ monthlyBudgetUsd: pub.monthlyBudgetUsd }, spend)
158
+ const money = (value) => `$${Number(value || 0).toFixed(4)}`
159
+ const limit = (value) => Number(value) > 0 ? money(value) : 'off'
160
+ const lines = [
161
+ `ClineBot Estimated Spend · today ${money(spend.dayCost)} · month ${money(spend.monthCost)}`,
162
+ '',
163
+ `Today: ${money(spend.dayCost)} / ${limit(pub.dailyBudgetUsd)}`,
164
+ `Month: ${money(spend.monthCost)} / ${limit(pub.monthlyBudgetUsd)}`,
165
+ `Daily guard: ${daily.blocked ? `BLOCKED — ${budgetErrorMessage(daily)}` : 'ok'}`,
166
+ `Monthly guard: ${monthly.blocked ? `BLOCKED — ${budgetErrorMessage(monthly)}` : 'ok'}`,
167
+ '',
168
+ 'Set limits in Settings -> ClineBot -> Advanced.',
169
+ ]
170
+ return lines.join('\n')
171
+ }
172
+
173
+ // 7. Subcommand /cline stats (real DSH stream metrics)
152
174
  if (subcmd === 'stats' || subcmd === 'telemetry') {
153
175
  const summary = `ClineBot Telemetry · ${sessionStats.successfulRequests}/${sessionStats.totalRequests} reqs · ~${sessionStats.totalTokensEst} tokens`
154
176
  const lines = [
@@ -177,7 +199,7 @@ export function registerSlashCommand(ctx, { live, getSettingsApi, syncProviderSt
177
199
  return lines.join('\n')
178
200
  }
179
201
 
180
- // 7. Subcommand /cline test [model] / smoke
202
+ // 8. Subcommand /cline test [model] / smoke
181
203
  if (subcmd === 'test' || subcmd === 'smoke') {
182
204
  if (param && !isSupportedModel(param, pub.dynamicModels, pub.modelContextOverrides, pub.customModels)) {
183
205
  return `ClineBot: Model "${param}" is not recognized. Use /cline models to see available models.`
@@ -221,7 +243,7 @@ export function registerSlashCommand(ctx, { live, getSettingsApi, syncProviderSt
221
243
  return `Smoke Test Failed: ${outcome.error} (HTTP ${outcome.status || 'timeout'})${failoverNotice}`
222
244
  }
223
245
 
224
- // 8. Subcommand /cline quota or /cline balance
246
+ // 9. Subcommand /cline quota, /cline usage or /cline balance
225
247
  const activeKey = await resolveActiveAccountKey(ctx, live())
226
248
  if (!activeKey.value) {
227
249
  return 'ClineBot: API key is not configured. Open Settings -> Plugins -> ClineBot.'
@@ -234,9 +256,11 @@ export function registerSlashCommand(ctx, { live, getSettingsApi, syncProviderSt
234
256
 
235
257
  const fiveHour = usage?.windows?.fiveHour
236
258
  const weekly = usage?.windows?.weekly
259
+ const monthly = usage?.windows?.monthly
237
260
  const reset5h = fiveHour?.resetsAt ? new Date(fiveHour.resetsAt).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' }) : 'N/A'
238
261
  const resetWk = weekly?.resetsAt ? new Date(weekly.resetsAt).toLocaleDateString([], { day: '2-digit', month: '2-digit' }) : 'N/A'
239
262
  const resetWkFull = weekly?.resetsAt ? new Date(weekly.resetsAt).toLocaleDateString([], { day: '2-digit', month: '2-digit', year: 'numeric' }) : 'N/A'
263
+ const resetMo = monthly?.resetsAt ? new Date(monthly.resetsAt).toLocaleDateString([], { day: '2-digit', month: '2-digit', year: 'numeric' }) : 'N/A'
240
264
 
241
265
  const planRaw = usage?.plan || 'ClinePass'
242
266
  const planShort = planRaw.replace(/\s*\(\$\d+\.\d+\/mo\)/i, '').trim()
@@ -268,6 +292,7 @@ export function registerSlashCommand(ctx, { live, getSettingsApi, syncProviderSt
268
292
  '',
269
293
  `${pad('5-Hour Quota')}${formatProgressBar(fiveHour?.percentUsed)} (resets: ${reset5h})`,
270
294
  `${pad('Weekly Quota')}${formatProgressBar(weekly?.percentUsed)} (resets: ${resetWkFull})`,
295
+ `${pad('Monthly Quota')}${formatProgressBar(monthly?.percentUsed)} (resets: ${resetMo})`,
271
296
  )
272
297
 
273
298
  if (fiveHour?.percentUsed >= 95) {
@@ -315,8 +340,8 @@ export function registerSlashCommand(ctx, { live, getSettingsApi, syncProviderSt
315
340
  const unregister = commands.register({
316
341
  definitionId: '@goodandready/dsh-clinebot',
317
342
  name: 'cline',
318
- description: 'Check ClinePass subscription quota, models, accounts and test connectivity (/cline [quota|models|accounts|switch <name>|test [model]|ping|rotate])',
319
- input: { hint: '[quota|models|accounts|switch <name>|test [model]|ping|rotate]' },
343
+ description: 'Check ClinePass quota, cost, models, accounts and connectivity (/cline [quota|usage|cost|models|accounts|switch <name>|test [model]|ping|rotate])',
344
+ input: { hint: '[quota|usage|cost|models|accounts|switch <name>|test [model]|ping|rotate]' },
320
345
  handler: commandHandler,
321
346
  execute: commandExecute,
322
347
  })
@@ -9,7 +9,7 @@
9
9
  import { createRequire } from 'node:module'
10
10
 
11
11
  const require = createRequire(import.meta.url)
12
- let packageVersion = '0.6.4'
12
+ let packageVersion = '0.6.5'
13
13
  try {
14
14
  packageVersion = require('../../package.json').version || packageVersion
15
15
  } catch {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@goodandready/dsh-clinebot",
3
- "version": "0.6.4",
3
+ "version": "0.6.5",
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",