dsh-cline-pass 0.2.10 → 0.2.11

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/lib/adapter.js CHANGED
@@ -992,7 +992,11 @@ export class ClinePassAdapter extends LlmAdapter {
992
992
  : 'ok',
993
993
  })
994
994
  this.config.record(options.model, {
995
- provider: observed.provider ?? (attempt.upstream ?? null),
995
+ // The upstream the gateway actually served from, never the one we asked
996
+ // for: a pin the gateway declined is exactly what this column exists to
997
+ // expose, and filling it with the requested name would report the pin as if
998
+ // it had been adopted. An unreported routing reads as unknown upstream.
999
+ provider: observed.provider,
996
1000
  canonical: observed.canonical,
997
1001
  attempts: trace.map((row) => row.upstream),
998
1002
  ms: Date.now() - startedAt,
package/lib/client.js CHANGED
@@ -118,6 +118,11 @@ window.__ModuleLoader__.load({
118
118
  '.cp-acct-newname{flex:0 0 150px}',
119
119
  '.cp-acct-add,.cp-acct-key{border-top:0.5px solid var(--dsw-alias-border-l2);padding-top:10px;margin-top:2px}',
120
120
  '.cp-chips{display:flex;flex-wrap:wrap;gap:6px}',
121
+ '.cp-chip-manual{border-style:dashed;color:var(--dsw-alias-label-tertiary)}',
122
+ '.cp-small{font-size:11.5px}',
123
+ '.cp-caps{flex-direction:column;gap:8px;display:flex}',
124
+ '.cp-caps-boxes{flex-wrap:wrap;gap:10px}',
125
+ '.cp-cap{display:inline-flex;align-items:center;gap:5px;font-size:12px;color:var(--dsw-alias-label-secondary);cursor:pointer}',
121
126
  '.cp-chip{border:0.5px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-base);color:var(--dsw-alias-label-secondary);border-radius:999px;padding:2px 8px;font-size:12px;cursor:pointer;display:inline-flex;align-items:center;gap:5px}',
122
127
  '.cp-chip-on{border-color:var(--dsw-alias-brand-primary);color:var(--dsw-alias-label-primary);font-weight:600}',
123
128
  '.cp-chip-excluded{text-decoration:line-through;color:var(--dsw-alias-label-tertiary)}',
@@ -361,7 +366,8 @@ window.__ModuleLoader__.load({
361
366
  historyTokensHint: '↓输入 ↑输出 ⚡缓存 🧠推理;— 表示网关本次未返回用量',
362
367
  historyEffort: '推理强度',
363
368
  historyAuto: '(自动)',
364
- noChannels: '未探测渠道',
369
+ historyUnknown: '(未上报)',
370
+ noChannels: '未探测到渠道,可手动输入',
365
371
  pinned: '已钉住',
366
372
  auto: '自动路由',
367
373
  modeAuto: '智能路由',
@@ -374,6 +380,19 @@ window.__ModuleLoader__.load({
374
380
  reset: '恢复自动',
375
381
  refresh: '刷新',
376
382
  channels: '渠道(点选=钉住顺序,⊘=排除)',
383
+ channelPlaceholder: '输入渠道名,回车添加',
384
+ channelAdd: '添加',
385
+ channelUnverified: '未验证',
386
+ channelsKnown: '已探测:{list}',
387
+ capabilities: '上游能力',
388
+ capabilitiesExtraPlaceholder: '如 quantization:fp8 或 !quantization:bf16',
389
+ capabilitiesDirect: '最近一次探测走 OpenRouter,该路由会忽略此筛选',
390
+ capFree: '免费',
391
+ capImplicitCaching: '隐式缓存',
392
+ capReasoning: '推理',
393
+ capStructuredOutput: '结构化输出',
394
+ capToolUse: '工具调用',
395
+ capVision: '视觉',
377
396
  history: '最近请求',
378
397
  noHistory: '暂无记录。',
379
398
  busy: '处理中…',
@@ -453,7 +472,8 @@ window.__ModuleLoader__.load({
453
472
  historyTokensHint: '↓in ↑out ⚡cache 🧠reasoning; a dash means the gateway reported no usage',
454
473
  historyEffort: 'effort',
455
474
  historyAuto: '(auto)',
456
- noChannels: 'No channels probed',
475
+ historyUnknown: '(unreported)',
476
+ noChannels: 'Nothing probed yet — type a name',
457
477
  pinned: 'Pinned',
458
478
  auto: 'Auto',
459
479
  modeAuto: 'Smart routing',
@@ -466,6 +486,19 @@ window.__ModuleLoader__.load({
466
486
  reset: 'Back to auto',
467
487
  refresh: 'Refresh',
468
488
  channels: 'Channels (click = pin order, ⊘ = exclude)',
489
+ channelPlaceholder: 'Channel name, Enter to add',
490
+ channelAdd: 'Add',
491
+ channelUnverified: 'unverified',
492
+ channelsKnown: 'Probed: {list}',
493
+ capabilities: 'Upstream capabilities',
494
+ capabilitiesExtraPlaceholder: 'e.g. quantization:fp8 or !quantization:bf16',
495
+ capabilitiesDirect: 'The last probe saw OpenRouter; that route ignores this filter',
496
+ capFree: 'Free',
497
+ capImplicitCaching: 'Implicit caching',
498
+ capReasoning: 'Reasoning',
499
+ capStructuredOutput: 'Structured output',
500
+ capToolUse: 'Tool use',
501
+ capVision: 'Vision',
469
502
  history: 'Recent requests',
470
503
  noHistory: 'Nothing recorded.',
471
504
  busy: 'Working…',
@@ -1123,47 +1156,188 @@ window.__ModuleLoader__.load({
1123
1156
  }
1124
1157
 
1125
1158
  /** The channel chooser for one model. */
1126
- function ChannelPicker(props) {
1159
+ /** The capability names the gateway accepts, in its own spelling. */
1160
+ const CAPABILITIES = [
1161
+ ['free', 'capFree'],
1162
+ ['implicit-caching', 'capImplicitCaching'],
1163
+ ['reasoning', 'capReasoning'],
1164
+ ['structured-output', 'capStructuredOutput'],
1165
+ ['tool-use', 'capToolUse'],
1166
+ ['vision', 'capVision'],
1167
+ ]
1168
+
1169
+ /**
1170
+ * The channel list, editable.
1171
+ *
1172
+ * The chips used to be exactly what a probe returned, which made an account whose
1173
+ * probe came back empty a dead end: the row said no channels were probed and
1174
+ * offered no way to name the upstream the user already knew served them. The list
1175
+ * is the user's data now — a probe fills it in, typing fills it in, and both are
1176
+ * the same list.
1177
+ */
1178
+ function ChannelEditor(props) {
1127
1179
  const { t, model, disabled, onPin } = props
1128
- if (model.upstreams.length === 0) return h('div', { className: 'cp-muted' }, t('noChannels'))
1180
+ const [draft, setDraft] = React.useState('')
1129
1181
  const verdicts = new Map((model.upstreamStatus ?? []).map((entry) => [entry.upstream, entry]))
1130
- return h('div', { className: 'cp-row' }, [
1131
- h('span', { key: 'label', className: 'cp-muted' }, t('channels')),
1132
- h('span', { key: 'chips', className: 'cp-chips' }, model.upstreams.map((upstream) => {
1133
- const pinned = model.pinned.includes(upstream)
1134
- const excluded = model.excluded.includes(upstream)
1135
- const verdict = verdicts.get(upstream)
1136
- const detail = verdict === undefined
1137
- ? ''
1138
- : ` · ${VERDICT[verdict.status] ?? verdict.status}${verdict.note ? `\n${verdict.note}` : ''}`
1139
- return h('span', { key: upstream, className: `cp-chip${pinned ? ' cp-chip-on' : ''}${excluded ? ' cp-chip-excluded' : ''}` }, [
1140
- h('span', {
1141
- key: 'pin',
1142
- role: 'button',
1143
- tabIndex: 0,
1144
- title: `${upstream}${detail}`,
1145
- onClick: () => {
1146
- if (disabled) return
1147
- const next = pinned ? model.pinned.filter((name) => name !== upstream) : [...model.pinned, upstream]
1148
- onPin({ model: model.id, upstreams: next })
1149
- },
1150
- }, [
1151
- h('span', { key: 'dot', style: { marginRight: 5 } }, h(Dot, { color: verdictColor(verdict?.status ?? 'unknown'), title: VERDICT[verdict?.status ?? 'unknown'] })),
1152
- upstream,
1153
- ]),
1154
- h('span', {
1155
- key: 'ex',
1156
- role: 'button',
1157
- tabIndex: 0,
1158
- title: excluded ? 'un-exclude' : 'exclude',
1159
- onClick: () => {
1160
- if (disabled) return
1161
- const next = excluded ? model.excluded.filter((name) => name !== upstream) : [...model.excluded, upstream]
1162
- onPin({ model: model.id, exclude: next })
1163
- },
1164
- }, '⊘'),
1165
- ])
1166
- })),
1182
+ const known = model.upstreams
1183
+ // A chosen name shows even when no probe reported it: a chip that disappears is
1184
+ // a chip that cannot be removed.
1185
+ const names = [...new Set([...known, ...model.pinned, ...model.excluded])]
1186
+ const pin = (next) => {
1187
+ if (disabled) return
1188
+ onPin({ model: model.id, upstreams: next })
1189
+ }
1190
+ const add = () => {
1191
+ const name = draft.trim()
1192
+ if (name === '') return
1193
+ setDraft('')
1194
+ const needle = name.toLowerCase()
1195
+ if (model.pinned.some((entry) => String(entry).toLowerCase() === needle)) return
1196
+ pin([...model.pinned, name])
1197
+ }
1198
+ return h('div', { className: 'cp-caps' }, [
1199
+ h('div', { key: 'row', className: 'cp-row' }, [
1200
+ h('span', { key: 'label', className: 'cp-muted' }, t('channels')),
1201
+ names.length === 0
1202
+ ? h('span', { key: 'none', className: 'cp-muted' }, t('noChannels'))
1203
+ : h('span', { key: 'chips', className: 'cp-chips' }, names.map((upstream) => {
1204
+ const pinned = model.pinned.includes(upstream)
1205
+ const excluded = model.excluded.includes(upstream)
1206
+ const probed = known.includes(upstream)
1207
+ const verdict = verdicts.get(upstream)
1208
+ const detail = verdict === undefined
1209
+ ? ''
1210
+ : ` · ${VERDICT[verdict.status] ?? verdict.status}${verdict.note ? `\n${verdict.note}` : ''}`
1211
+ return h('span', {
1212
+ key: upstream,
1213
+ className: `cp-chip${pinned ? ' cp-chip-on' : ''}${excluded ? ' cp-chip-excluded' : ''}${probed ? '' : ' cp-chip-manual'}`,
1214
+ }, [
1215
+ h('span', {
1216
+ key: 'pin',
1217
+ role: 'button',
1218
+ tabIndex: 0,
1219
+ title: `${upstream}${probed ? '' : ` · ${t('channelUnverified')}`}${detail}`,
1220
+ onClick: () => {
1221
+ if (disabled) return
1222
+ const next = pinned ? model.pinned.filter((name) => name !== upstream) : [...model.pinned, upstream]
1223
+ onPin({ model: model.id, upstreams: next })
1224
+ },
1225
+ }, [
1226
+ h('span', { key: 'dot', style: { marginRight: 5 } }, h(Dot, { color: verdictColor(verdict?.status ?? 'unknown'), title: VERDICT[verdict?.status ?? 'unknown'] })),
1227
+ upstream,
1228
+ ]),
1229
+ h('span', {
1230
+ key: 'ex',
1231
+ role: 'button',
1232
+ tabIndex: 0,
1233
+ title: excluded ? 'un-exclude' : 'exclude',
1234
+ onClick: () => {
1235
+ if (disabled) return
1236
+ const next = excluded ? model.excluded.filter((name) => name !== upstream) : [...model.excluded, upstream]
1237
+ onPin({ model: model.id, exclude: next })
1238
+ },
1239
+ }, '⊘'),
1240
+ // A name that only exists because someone typed it has no discovery
1241
+ // list to fall back on, so it needs an explicit way out: un-pinning
1242
+ // removes it, and this is what says so.
1243
+ probed ? null : h('span', {
1244
+ key: 'del',
1245
+ role: 'button',
1246
+ tabIndex: 0,
1247
+ title: 'remove',
1248
+ onClick: () => {
1249
+ if (disabled) return
1250
+ onPin({
1251
+ model: model.id,
1252
+ upstreams: model.pinned.filter((name) => name !== upstream),
1253
+ exclude: model.excluded.filter((name) => name !== upstream),
1254
+ })
1255
+ },
1256
+ }, '×'),
1257
+ ])
1258
+ })),
1259
+ ]),
1260
+ h('div', { key: 'add', className: 'cp-row' }, [
1261
+ h('input', {
1262
+ key: 'in',
1263
+ type: 'text',
1264
+ className: 'cp-input cp-grow',
1265
+ value: draft,
1266
+ disabled,
1267
+ placeholder: t('channelPlaceholder'),
1268
+ 'aria-label': `${t('channelAdd')}: ${model.id}`,
1269
+ onChange: (event) => setDraft(event.target.value),
1270
+ onKeyDown: (event) => {
1271
+ if (event.key !== 'Enter') return
1272
+ event.preventDefault()
1273
+ add()
1274
+ },
1275
+ }),
1276
+ h('button', { key: 'b', type: 'button', className: 'cp-btn', disabled, onClick: add }, t('channelAdd')),
1277
+ ]),
1278
+ known.length === 0 ? null : h('div', { key: 'known', className: 'cp-muted cp-small' }, fill(t('channelsKnown'), { list: known.join(' · ') })),
1279
+ ])
1280
+ }
1281
+
1282
+ /**
1283
+ * The capabilities an upstream must offer for this model.
1284
+ *
1285
+ * The gateway applies this itself and rejects a name it does not know, so the six
1286
+ * it documents are checkboxes and anything else — a weight format such as
1287
+ * `quantization:fp8`, or its negation — is typed. It only exists on the planner
1288
+ * pipeline: OpenRouter has no equivalent, and a model known to run there says so
1289
+ * rather than letting a filter look active while it is ignored.
1290
+ */
1291
+ function CapabilityFilter(props) {
1292
+ const { t, model, disabled, onPin } = props
1293
+ const [extra, setExtra] = React.useState('')
1294
+ const chosen = model.has ?? []
1295
+ const write = (next) => {
1296
+ if (disabled) return
1297
+ onPin({ model: model.id, has: next })
1298
+ }
1299
+ const toggle = (id) => write(chosen.includes(id) ? chosen.filter((name) => name !== id) : [...chosen, id])
1300
+ const addExtra = () => {
1301
+ const value = extra.trim()
1302
+ if (value === '') return
1303
+ setExtra('')
1304
+ if (chosen.includes(value)) return
1305
+ write([...chosen, value])
1306
+ }
1307
+ const extras = chosen.filter((name) => !CAPABILITIES.some(([id]) => id === name))
1308
+ return h('div', { className: 'cp-caps' }, [
1309
+ h('div', { key: 'head', className: 'cp-row' }, [
1310
+ h('span', { key: 'l', className: 'cp-muted' }, t('capabilities')),
1311
+ model.pipeline === 'direct' ? h('span', { key: 'w', className: 'cp-muted cp-small' }, t('capabilitiesDirect')) : null,
1312
+ ]),
1313
+ h('span', { key: 'boxes', className: 'cp-row cp-caps-boxes' }, CAPABILITIES.map(([id, key]) =>
1314
+ h('label', { key: id, className: 'cp-cap' }, [
1315
+ h('input', {
1316
+ key: 'c', type: 'checkbox', className: 'cp-check',
1317
+ checked: chosen.includes(id), disabled, onChange: () => toggle(id),
1318
+ }),
1319
+ h('span', { key: 't' }, t(key)),
1320
+ ]))),
1321
+ h('div', { key: 'extra', className: 'cp-row' }, [
1322
+ h('input', {
1323
+ key: 'in', type: 'text', className: 'cp-input cp-grow',
1324
+ value: extra, disabled,
1325
+ placeholder: t('capabilitiesExtraPlaceholder'),
1326
+ 'aria-label': `${t('capabilities')}: ${model.id}`,
1327
+ onChange: (event) => setExtra(event.target.value),
1328
+ onKeyDown: (event) => {
1329
+ if (event.key !== 'Enter') return
1330
+ event.preventDefault()
1331
+ addExtra()
1332
+ },
1333
+ }),
1334
+ h('button', { key: 'b', type: 'button', className: 'cp-btn', disabled, onClick: addExtra }, t('channelAdd')),
1335
+ ]),
1336
+ extras.length === 0 ? null : h('span', { key: 'list', className: 'cp-chips' }, extras.map((name) =>
1337
+ h('span', { key: name, className: 'cp-chip cp-chip-on' }, [
1338
+ h('span', { key: 'n' }, name),
1339
+ h('span', { key: 'x', role: 'button', tabIndex: 0, title: 'remove', onClick: () => toggle(name) }, '×'),
1340
+ ]))),
1167
1341
  ])
1168
1342
  }
1169
1343
 
@@ -1230,9 +1404,8 @@ window.__ModuleLoader__.load({
1230
1404
  action('test', t('testPin'), actions.testModel),
1231
1405
  action('reset', t('reset'), actions.resetModel),
1232
1406
  ]),
1233
- model.upstreams.length > 0
1234
- ? h(ChannelPicker, { key: 'channels', t, model, disabled, onPin: (payload) => actions.pinModel(payload) })
1235
- : h('div', { key: 'none', className: 'cp-muted' }, t('noChannels')),
1407
+ h(ChannelEditor, { key: 'channels', t, model, disabled, onPin: (payload) => actions.pinModel(payload) }),
1408
+ h(CapabilityFilter, { key: 'caps', t, model, disabled, onPin: (payload) => actions.pinModel(payload) }),
1236
1409
  ]) : null,
1237
1410
  ])
1238
1411
  }
@@ -1487,7 +1660,7 @@ window.__ModuleLoader__.load({
1487
1660
  */
1488
1661
  function historyRows(t, entry, showAccount) {
1489
1662
  const failed = String(entry.error ?? '') !== ''
1490
- const upstream = `${entry.provider || t('historyAuto')}${showAccount && entry.account !== '' ? ` · ${entry.account}` : ''}`
1663
+ const upstream = `${entry.provider || t('historyUnknown')}${showAccount && entry.account !== '' ? ` · ${entry.account}` : ''}`
1491
1664
  // The route prefix is constant across every row of this list, so it is
1492
1665
  // carried in the tooltip instead of eating the width the model name needs
1493
1666
  // — centred text that overflows loses BOTH ends, not just the tail.
@@ -1516,7 +1689,7 @@ window.__ModuleLoader__.load({
1516
1689
  h('td', { key: 'model', className: 'cp-history-model-cell' }, h('div', { className: 'cp-history-model-stack' }, [
1517
1690
  h('span', { key: 'name', className: 'cp-history-model', title: entry.model }, label),
1518
1691
  h('span', { key: 'upstream', className: 'cp-history-meta' }, [
1519
- h('span', { key: 'provider', className: 'cp-tag' }, entry.provider || t('historyAuto')),
1692
+ h('span', { key: 'provider', className: 'cp-tag' }, entry.provider || t('historyUnknown')),
1520
1693
  showAccount && entry.account !== '' ? h('span', { key: 'account', className: 'cp-tag cp-tag-quiet' }, entry.account) : null,
1521
1694
  ]),
1522
1695
  ])),
package/lib/index.js CHANGED
@@ -106,6 +106,17 @@ export const PinProfile = z.object({
106
106
  exclude: z.array(z.string()).default([]),
107
107
  pinMode: z.string().default('strict'),
108
108
  sort: z.string().default(''),
109
+ /**
110
+ * Capabilities an upstream must offer for this model, in the gateway's own
111
+ * vocabulary: `free`, `implicit-caching`, `reasoning`, `structured-output`,
112
+ * `tool-use`, `vision`, or a weight condition such as `quantization:fp8` /
113
+ * `!quantization:bf16`.
114
+ *
115
+ * The gateway applies this server-side and rejects a name it does not know, so
116
+ * the panel checks the list before it is written. It only exists on the planner
117
+ * pipeline — OpenRouter has no equivalent — and a model routed there ignores it.
118
+ */
119
+ has: z.array(z.string()).default([]),
109
120
  })
110
121
 
111
122
  /**
package/lib/panel.js CHANGED
@@ -71,6 +71,7 @@ export function projectModel(model, pin, meta, hidden = false) {
71
71
  sort: String(pin?.sort ?? ''),
72
72
  pinned: Array.isArray(pin?.upstreams) ? pin.upstreams.map(String) : [],
73
73
  excluded: Array.isArray(pin?.exclude) ? pin.exclude.map(String) : [],
74
+ has: Array.isArray(pin?.has) ? pin.has.map(String) : [],
74
75
  upstreams: Array.isArray(meta?.upstreams) ? meta.upstreams.map(String) : [],
75
76
  upstreamStatus,
76
77
  lastProvider: String(meta?.lastProvider ?? ''),
@@ -377,6 +378,9 @@ export function createPanel({ control, engine, store }) {
377
378
  sort: payload?.sort === undefined
378
379
  ? (current.sort ?? '')
379
380
  : (payload.sort === 'none' || payload.sort === '' ? '' : String(payload.sort)),
381
+ has: Array.isArray(payload?.has)
382
+ ? [...new Set(payload.has.map((name) => String(name).trim()).filter((name) => name.length > 0))]
383
+ : (current.has ?? []).map(String),
380
384
  }
381
385
  await control.updateConfig({ perModel: { ...(config.perModel ?? {}), [model]: next } })
382
386
  return { model, pin: next, ...(await state()) }
package/lib/protocol.js CHANGED
@@ -184,6 +184,9 @@ function knownChannels(meta, pipeline) {
184
184
  export function injectPrefs(body, meta, attempt) {
185
185
  const next = { ...body }
186
186
  const { upstream = null, orderRest = [], excludeList = [], strict = true } = attempt ?? {}
187
+ // `has` names the capabilities an upstream must offer. There is no equivalent on
188
+ // the OpenRouter spelling, so a direct-pipeline model simply does not carry it.
189
+ const required = (Array.isArray(attempt?.has) ? attempt.has : []).map((name) => String(name).trim()).filter((name) => name.length > 0)
187
190
  // Normalized here as well as in buildAttempts: this function is exported, so
188
191
  // a caller can hand it an attempt straight from configuration.
189
192
  const sort = normalizeSort(attempt?.sort)
@@ -197,7 +200,7 @@ export function injectPrefs(body, meta, attempt) {
197
200
  const both = attempt?.spelling !== 'detected'
198
201
  const useGateway = both || pipeline === 'planner' || pipeline === null
199
202
  const useOpenRouter = both || pipeline === 'direct' || pipeline === null
200
- const gatewayUsable = useGateway && (upstream !== null || sort !== null || (gatewayAllow !== null && gatewayAllow.length > 0))
203
+ const gatewayUsable = useGateway && (upstream !== null || sort !== null || required.length > 0 || (gatewayAllow !== null && gatewayAllow.length > 0))
201
204
  // `ignore` is OpenRouter's own exclusion field: written alongside the
202
205
  // allow-list so an exclusion still bites when the allow-list cannot be
203
206
  // computed (no channel list yet) or has gone stale.
@@ -215,6 +218,7 @@ export function injectPrefs(body, meta, attempt) {
215
218
  gateway.only = spell(gatewayAllow, 'planner')
216
219
  }
217
220
  if (sort !== null) gateway.sort = sort
221
+ if (required.length > 0) gateway.has = required
218
222
  next.providerOptions = { ...(next.providerOptions ?? {}), gateway: { ...(next.providerOptions?.gateway ?? {}), ...gateway } }
219
223
  }
220
224
  if (openRouterUsable) {
@@ -316,7 +320,12 @@ export function buildAttempts(config) {
316
320
  const wanted = listed.filter((name) => !excluded.has(normalizeSlug(name)))
317
321
  const strict = normalizePinMode(config?.pinMode) === 'strict'
318
322
  const sort = normalizeSort(config?.sort)
319
- const base = { strict, sort, excludeList: exclude }
323
+ // The capability filter is gateway-only and applies to every attempt, so it rides
324
+ // in the shared base rather than being threaded through each candidate.
325
+ const has = (Array.isArray(config?.has) ? config.has : [])
326
+ .map((name) => String(name).trim())
327
+ .filter((name) => name.length > 0)
328
+ const base = { strict, sort, excludeList: exclude, ...(has.length > 0 ? { has } : {}) }
320
329
  // Carried through only when the caller configured it, so an attempt built from
321
330
  // a bare pin profile keeps its historical shape.
322
331
  if (typeof config?.spelling === 'string' && config.spelling.length > 0) base.spelling = config.spelling
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-cline-pass",
3
- "version": "0.2.10",
3
+ "version": "0.2.11",
4
4
  "description": "Self-contained Cline Pass provider for dsh: register Cline Pass subscription models on the harness LLM seam, enumerate and validate the upstream channels behind each model, pin or exclude them with failover, and drive an account pool. No proxy process and no external cline-pass-switcher install required.",
5
5
  "type": "module",
6
6
  "main": "lib/index.js",