dsh-cline-pass 0.2.9 → 0.2.10
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/README.en.md +1 -1
- package/README.md +1 -1
- package/lib/client.js +107 -74
- package/package.json +1 -1
package/README.en.md
CHANGED
|
@@ -22,7 +22,7 @@ Requires Node.js `>= 20.3.0`, dsh `>= 0.1.2-alpha.3 < 0.3.0`, and a valid Cline
|
|
|
22
22
|
dsh plugin --profile web add dsh-cline-pass
|
|
23
23
|
```
|
|
24
24
|
|
|
25
|
-
Restart dsh, open **Settings →
|
|
25
|
+
Restart dsh, open **Settings → Cline Pass** to save and test your API key, then select a Cline Pass model in the model picker. Keys are stored in the dsh credential store and displayed only in masked form.
|
|
26
26
|
|
|
27
27
|
Alternatively, configure the key through an environment variable:
|
|
28
28
|
|
package/README.md
CHANGED
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
dsh plugin --profile web add dsh-cline-pass
|
|
23
23
|
```
|
|
24
24
|
|
|
25
|
-
安装后重启 dsh,在 **设置 →
|
|
25
|
+
安装后重启 dsh,在 **设置 → Cline Pass** 中保存并测试 API Key,然后在模型选择器中选择 Cline Pass 模型。Key 存入 dsh 凭据库,界面仅显示掩码。
|
|
26
26
|
|
|
27
27
|
也可以通过环境变量配置:
|
|
28
28
|
|
package/lib/client.js
CHANGED
|
@@ -84,6 +84,17 @@ window.__ModuleLoader__.load({
|
|
|
84
84
|
'.cp-dot{width:8px;height:8px;border-radius:50%;display:inline-block;flex:none}',
|
|
85
85
|
'.cp-table{display:flex;flex-direction:column;gap:6px}',
|
|
86
86
|
'.cp-model{border:0.5px solid var(--dsw-alias-border-l2);border-radius:8px;padding:8px;display:flex;flex-direction:column;gap:8px}',
|
|
87
|
+
// The bulk toolbar sits directly above the first row; the card's own 10px gap
|
|
88
|
+
// reads as none against two stacked borders, so this card opens it up.
|
|
89
|
+
'.cp-models{gap:14px}',
|
|
90
|
+
'.cp-models-body{flex-direction:column;gap:16px;display:flex}',
|
|
91
|
+
// One line per model. `.cp-row` wraps by default, which a long id plus a long
|
|
92
|
+
// pin order turns into a two-line row that reads as broken rather than dense;
|
|
93
|
+
// both texts ellipsis instead, and their full value stays in `title`.
|
|
94
|
+
'.cp-model-head{flex-wrap:nowrap;min-width:0}',
|
|
95
|
+
'.cp-model-head .cp-grow{min-width:0}',
|
|
96
|
+
'.cp-model-mode{flex:none;max-width:55%}',
|
|
97
|
+
'.cp-ellipsis{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}',
|
|
87
98
|
// The account table is a real table for the same reason the history one
|
|
88
99
|
// is: per-row grids size their `auto` tracks from their own content, so a
|
|
89
100
|
// header cell never lines up with the data under it.
|
|
@@ -195,16 +206,10 @@ window.__ModuleLoader__.load({
|
|
|
195
206
|
// Plugins-page card chrome, mirrored token for token from the host's own
|
|
196
207
|
// `PluginCard`: that tab hands a plugin the whole card, so matching it —
|
|
197
208
|
// rather than inventing a second look — is this bundle's job.
|
|
198
|
-
'.cp-set-card{border:0.5px solid var(--dsw-alias-border-l4);background:var(--dsw-alias-bg-layer-3);border-radius:16px;list-style:none
|
|
199
|
-
'.cp-set-
|
|
200
|
-
'.cp-set-card-open{background:var(--dsw-alias-bg-layer-2);border-color:var(--dsw-alias-label-dimmed)}',
|
|
201
|
-
'.cp-set-header{appearance:none;width:100%;font:inherit;color:inherit;text-align:left;cursor:pointer;background:0 0;border:0;border-radius:12px;align-items:center;gap:12px;padding:14px 16px;display:flex}',
|
|
202
|
-
'.cp-set-header:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:-2px}',
|
|
203
|
-
'.cp-set-head-text{flex-direction:column;flex:1;gap:4px;min-width:0;display:flex}',
|
|
209
|
+
'.cp-set-card{border:0.5px solid var(--dsw-alias-border-l4);background:var(--dsw-alias-bg-layer-3);border-radius:16px;list-style:none}',
|
|
210
|
+
'.cp-set-head{flex-direction:column;gap:4px;display:flex;padding:14px 16px}',
|
|
204
211
|
'.cp-set-name{color:var(--dsw-alias-label-primary);font-size:15px;font-weight:600;line-height:1.4}',
|
|
205
212
|
'.cp-set-desc{color:var(--dsw-alias-label-tertiary);font-size:13px;line-height:1.5}',
|
|
206
|
-
'.cp-set-chevron{color:var(--dsw-alias-label-tertiary);flex:none;transition:transform .16s}',
|
|
207
|
-
'.cp-set-chevron-open{transform:rotate(180deg)}',
|
|
208
213
|
'.cp-chevron{color:var(--dsw-alias-label-tertiary);flex:none;transition:transform .16s;display:inline-flex}',
|
|
209
214
|
'details[open]>summary .cp-chevron{transform:rotate(180deg)}',
|
|
210
215
|
// A summary laid out as a flex row keeps no native affordance, so the
|
|
@@ -288,8 +293,6 @@ window.__ModuleLoader__.load({
|
|
|
288
293
|
title: 'Cline Pass',
|
|
289
294
|
description: '订阅模型的渠道钉住与账号池配置',
|
|
290
295
|
panelUnavailable: '浏览器通道不可用。',
|
|
291
|
-
expand: '展开设置',
|
|
292
|
-
collapse: '收起设置',
|
|
293
296
|
keyOk: 'API Key 已配置',
|
|
294
297
|
keyMissing: '尚未配置 API Key',
|
|
295
298
|
keyValue: 'API Key',
|
|
@@ -317,9 +320,10 @@ window.__ModuleLoader__.load({
|
|
|
317
320
|
active: '当前账号',
|
|
318
321
|
addAccount: '添加',
|
|
319
322
|
accountName: '账号名',
|
|
320
|
-
models: '
|
|
323
|
+
models: 'cline-pass',
|
|
321
324
|
modelsCount: '{n} 个模型',
|
|
322
|
-
hiddenModels: '{n}
|
|
325
|
+
hiddenModels: '{n} 隐藏',
|
|
326
|
+
hiddenModelsHint: '{n} 个已从选择列表隐藏',
|
|
323
327
|
visible: '在模型选择列表中显示',
|
|
324
328
|
hiddenTag: '已隐藏',
|
|
325
329
|
selectAll: '全选',
|
|
@@ -360,6 +364,10 @@ window.__ModuleLoader__.load({
|
|
|
360
364
|
noChannels: '未探测渠道',
|
|
361
365
|
pinned: '已钉住',
|
|
362
366
|
auto: '自动路由',
|
|
367
|
+
modeAuto: '智能路由',
|
|
368
|
+
modeStrict: '钉住',
|
|
369
|
+
modePreferred: '优先',
|
|
370
|
+
channelsCount: '{n} 个渠道',
|
|
363
371
|
probe: '探测',
|
|
364
372
|
validate: '校验',
|
|
365
373
|
testPin: '测试',
|
|
@@ -377,8 +385,6 @@ window.__ModuleLoader__.load({
|
|
|
377
385
|
title: 'Cline Pass',
|
|
378
386
|
description: 'Channel pins and the account pool for the subscription models',
|
|
379
387
|
panelUnavailable: 'The browser channel is unavailable.',
|
|
380
|
-
expand: 'Show settings',
|
|
381
|
-
collapse: 'Hide settings',
|
|
382
388
|
keyOk: 'API key configured',
|
|
383
389
|
keyMissing: 'No API key',
|
|
384
390
|
keyValue: 'API key',
|
|
@@ -406,9 +412,10 @@ window.__ModuleLoader__.load({
|
|
|
406
412
|
active: 'Active',
|
|
407
413
|
addAccount: 'Add',
|
|
408
414
|
accountName: 'Name',
|
|
409
|
-
models: '
|
|
415
|
+
models: 'cline-pass',
|
|
410
416
|
modelsCount: '{n} model(s)',
|
|
411
|
-
hiddenModels: '{n} hidden
|
|
417
|
+
hiddenModels: '{n} hidden',
|
|
418
|
+
hiddenModelsHint: '{n} hidden from the model pickers',
|
|
412
419
|
visible: 'Show in the model pickers',
|
|
413
420
|
hiddenTag: 'Hidden',
|
|
414
421
|
selectAll: 'Check all',
|
|
@@ -449,6 +456,10 @@ window.__ModuleLoader__.load({
|
|
|
449
456
|
noChannels: 'No channels probed',
|
|
450
457
|
pinned: 'Pinned',
|
|
451
458
|
auto: 'Auto',
|
|
459
|
+
modeAuto: 'Smart routing',
|
|
460
|
+
modeStrict: 'Pinned',
|
|
461
|
+
modePreferred: 'Preferred',
|
|
462
|
+
channelsCount: '{n} channels',
|
|
452
463
|
probe: 'Probe',
|
|
453
464
|
validate: 'Validate',
|
|
454
465
|
testPin: 'Test',
|
|
@@ -1170,12 +1181,29 @@ window.__ModuleLoader__.load({
|
|
|
1170
1181
|
const { t, model, disabled, actions } = props
|
|
1171
1182
|
const [open, setOpen] = React.useState(false)
|
|
1172
1183
|
const hidden = model.hidden === true
|
|
1173
|
-
|
|
1184
|
+
// What a request will actually do, readable without expanding the row. The
|
|
1185
|
+
// old line read `已钉住: 自动路由` — claiming a pin while saying nothing is
|
|
1186
|
+
// pinned, which is exactly how it was misread.
|
|
1187
|
+
// Every row is this route, and the card header already names it, so the prefix
|
|
1188
|
+
// is 11 characters of noise on each of nineteen rows. The full id stays in
|
|
1189
|
+
// `title` for anything that needs to copy it.
|
|
1190
|
+
const label = typeof model.displayName === 'string' && model.displayName !== ''
|
|
1191
|
+
? model.displayName
|
|
1192
|
+
: String(model.id).replace(/^cline-pass\//, '')
|
|
1193
|
+
const mode = model.pinned.length === 0
|
|
1194
|
+
? t('modeAuto')
|
|
1195
|
+
: `${t(model.pinMode === 'preferred' ? 'modePreferred' : 'modeStrict')} · ${model.pinned.join(' › ')}`
|
|
1196
|
+
// How many channels sit behind it and how many of those the order pins. After a
|
|
1197
|
+
// probe only: before that the list is empty and a count would be a guess.
|
|
1198
|
+
const channelCount = model.upstreams.length === 0 ? '' : fill(t('channelsCount'), { n: model.upstreams.length })
|
|
1174
1199
|
const action = (key, label, run) => h('button', {
|
|
1175
1200
|
key, type: 'button', className: 'cp-btn', disabled, onClick: () => run(model.id),
|
|
1176
1201
|
}, label)
|
|
1177
1202
|
return h('div', { className: `cp-model${hidden ? ' cp-model-hidden' : ''}` }, [
|
|
1178
|
-
|
|
1203
|
+
// One line, always. The id and the mode are long enough to collide on a
|
|
1204
|
+
// narrow panel, so both ellipsis and keep their full text in `title`: a
|
|
1205
|
+
// second line here reads as a broken row, not as more information.
|
|
1206
|
+
h('div', { key: 'head', className: 'cp-row cp-model-head' }, [
|
|
1179
1207
|
h('input', {
|
|
1180
1208
|
key: 'vis',
|
|
1181
1209
|
type: 'checkbox',
|
|
@@ -1186,11 +1214,12 @@ window.__ModuleLoader__.load({
|
|
|
1186
1214
|
'aria-label': `${t('visible')}: ${model.id}`,
|
|
1187
1215
|
onChange: (event) => actions.setModelVisible(model.id, event.target.checked),
|
|
1188
1216
|
}),
|
|
1189
|
-
h('span', { key: 'name', className: 'cp-mono cp-grow'
|
|
1217
|
+
h('span', { key: 'name', className: 'cp-mono cp-grow cp-ellipsis', title: model.id }, label),
|
|
1190
1218
|
hidden ? h('span', { key: 'off', className: 'cp-muted' }, t('hiddenTag')) : null,
|
|
1191
|
-
h('span', { key: '
|
|
1219
|
+
h('span', { key: 'mode', className: 'cp-muted cp-mono cp-model-mode cp-ellipsis', title: mode }, mode),
|
|
1192
1220
|
h('button', {
|
|
1193
|
-
key: 'more', type: 'button', className: 'cp-btn',
|
|
1221
|
+
key: 'more', type: 'button', className: 'cp-btn',
|
|
1222
|
+
title: channelCount === '' ? t('channels') : `${t('channels')} · ${channelCount}`,
|
|
1194
1223
|
onClick: () => setOpen(!open),
|
|
1195
1224
|
}, open ? '▾' : '▸'),
|
|
1196
1225
|
]),
|
|
@@ -1392,8 +1421,8 @@ window.__ModuleLoader__.load({
|
|
|
1392
1421
|
* which drops the browser's own disclosure marker — without it a folded
|
|
1393
1422
|
* section looks like a plain card title, not something to open.
|
|
1394
1423
|
*/
|
|
1395
|
-
function foldHeader(title) {
|
|
1396
|
-
return h('summary', { key: 'sum', className: 'cp-card-title cp-fold' }, [
|
|
1424
|
+
function foldHeader(title, hint) {
|
|
1425
|
+
return h('summary', { key: 'sum', className: 'cp-card-title cp-fold', ...(hint === undefined || hint === '' ? {} : { title: hint }) }, [
|
|
1397
1426
|
h('span', { key: 'l' }, title),
|
|
1398
1427
|
h(Chevron, { key: 'chevron', className: 'cp-chevron' }),
|
|
1399
1428
|
])
|
|
@@ -1409,27 +1438,40 @@ window.__ModuleLoader__.load({
|
|
|
1409
1438
|
function ModelsCard(props) {
|
|
1410
1439
|
const { t, data, disabled, busy, actions } = props
|
|
1411
1440
|
const hidden = Number(data.hiddenModels ?? 0)
|
|
1412
|
-
|
|
1413
|
-
|
|
1441
|
+
// Open by default: the pins are what this page is for, and a collapsed card
|
|
1442
|
+
// makes a user click before they can see whether anything is pinned at all.
|
|
1443
|
+
// The fold stays available for the days a pool has nineteen models in it.
|
|
1444
|
+
return h('details', { className: 'cp-card cp-models', open: true }, [
|
|
1445
|
+
// Short enough for a fold header; the long spelling is the tooltip.
|
|
1446
|
+
foldHeader(
|
|
1447
|
+
`${t('models')} · ${fill(t('modelsCount'), { n: data.models.length })}${hidden === 0 ? '' : ` · ${fill(t('hiddenModels'), { n: hidden })}`}`,
|
|
1448
|
+
hidden === 0 ? '' : fill(t('hiddenModelsHint'), { n: hidden }),
|
|
1449
|
+
),
|
|
1414
1450
|
// The refresh and the bulk checkbox actions sit in the body, not the
|
|
1415
1451
|
// header: a button inside a summary would also toggle the fold.
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
h('
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1452
|
+
//
|
|
1453
|
+
// `details` wraps everything after its summary, so the card's own flex gap
|
|
1454
|
+
// stops at that wrapper and the toolbar sat flush against the first row. This
|
|
1455
|
+
// grouping carries the spacing itself, whatever the engine's `details` layout.
|
|
1456
|
+
h('div', { key: 'models', className: 'cp-models-body' }, [
|
|
1457
|
+
h('div', { key: 'bar', className: 'cp-row' }, [
|
|
1458
|
+
h('button', {
|
|
1459
|
+
key: 'all', type: 'button', className: 'cp-btn', disabled,
|
|
1460
|
+
onClick: () => actions.setModelsVisibility('all'),
|
|
1461
|
+
}, t('selectAll')),
|
|
1462
|
+
h('button', {
|
|
1463
|
+
key: 'invert', type: 'button', className: 'cp-btn', disabled,
|
|
1464
|
+
onClick: () => actions.setModelsVisibility('invert'),
|
|
1465
|
+
}, t('invertSelection')),
|
|
1466
|
+
h('button', {
|
|
1467
|
+
key: 'r', type: 'button', className: 'cp-btn cp-grow-end', disabled,
|
|
1468
|
+
onClick: () => actions.refreshModels(),
|
|
1469
|
+
}, t('refresh')),
|
|
1470
|
+
]),
|
|
1471
|
+
h('div', { key: 'rows', className: 'cp-table' }, data.models.map((model) => h(ModelRow, {
|
|
1472
|
+
key: model.id, t, model, disabled, busy, actions,
|
|
1473
|
+
}))),
|
|
1429
1474
|
]),
|
|
1430
|
-
h('div', { key: 'rows', className: 'cp-table' }, data.models.map((model) => h(ModelRow, {
|
|
1431
|
-
key: model.id, t, model, disabled, busy, actions,
|
|
1432
|
-
}))),
|
|
1433
1475
|
])
|
|
1434
1476
|
}
|
|
1435
1477
|
|
|
@@ -1837,31 +1879,21 @@ window.__ModuleLoader__.load({
|
|
|
1837
1879
|
* host's own cards — and because the panel only mounts once opened, a folded
|
|
1838
1880
|
* card costs no round trip.
|
|
1839
1881
|
*/
|
|
1882
|
+
/**
|
|
1883
|
+
* The section's page: the panel itself, under a static heading.
|
|
1884
|
+
*
|
|
1885
|
+
* It used to fold, because it lived inside the Plugins tab where a long panel
|
|
1886
|
+
* had to stay out of the way. As a first-level page the fold only asks for a
|
|
1887
|
+
* click on the thing the user just navigated to.
|
|
1888
|
+
*/
|
|
1840
1889
|
function PluginSettingsCard(props) {
|
|
1841
1890
|
const t = props.t
|
|
1842
|
-
|
|
1843
|
-
|
|
1844
|
-
|
|
1845
|
-
|
|
1846
|
-
key: 'head',
|
|
1847
|
-
type: 'button',
|
|
1848
|
-
className: 'cp-set-header',
|
|
1849
|
-
'aria-expanded': open,
|
|
1850
|
-
'aria-label': `${t(open ? 'collapse' : 'expand')}: ${title}`,
|
|
1851
|
-
onClick: () => setOpen(!open),
|
|
1852
|
-
}, [
|
|
1853
|
-
h('span', { key: 'text', className: 'cp-set-head-text' }, [
|
|
1854
|
-
h('span', { key: 'name', className: 'cp-set-name' }, title),
|
|
1855
|
-
h('span', { key: 'desc', className: 'cp-set-desc' }, t('description')),
|
|
1856
|
-
]),
|
|
1857
|
-
h(Chevron, {
|
|
1858
|
-
key: 'chevron',
|
|
1859
|
-
className: `cp-set-chevron${open ? ' cp-set-chevron-open' : ''}`,
|
|
1860
|
-
}),
|
|
1891
|
+
return h('div', { className: 'cp-set-card' }, [
|
|
1892
|
+
h('div', { key: 'head', className: 'cp-set-head' }, [
|
|
1893
|
+
h('span', { key: 'name', className: 'cp-set-name' }, t('title')),
|
|
1894
|
+
h('span', { key: 'desc', className: 'cp-set-desc' }, t('description')),
|
|
1861
1895
|
]),
|
|
1862
|
-
|
|
1863
|
-
? h('div', { key: 'body', className: 'cp-set-body' }, h(Panel, { ...props, hideTitle: true }))
|
|
1864
|
-
: null,
|
|
1896
|
+
h('div', { key: 'body', className: 'cp-set-body' }, h(Panel, { ...props, hideTitle: true })),
|
|
1865
1897
|
])
|
|
1866
1898
|
}
|
|
1867
1899
|
|
|
@@ -2147,19 +2179,20 @@ window.__ModuleLoader__.load({
|
|
|
2147
2179
|
const renderPluginCard = (props) => h(PluginSettingsCard, { ...props, ...face, t: translate })
|
|
2148
2180
|
const renderQuick = (props) => h(QuickCard, { ...props, ...face, t: translate })
|
|
2149
2181
|
|
|
2150
|
-
// 1. The one configuration surface: a
|
|
2151
|
-
//
|
|
2152
|
-
//
|
|
2153
|
-
//
|
|
2154
|
-
//
|
|
2182
|
+
// 1. The one configuration surface: a first-level page in the Settings nav.
|
|
2183
|
+
//
|
|
2184
|
+
// It used to be a tab under Plugins, which put the channel pins five or six
|
|
2185
|
+
// clicks deep — Settings, Plugins, the tab, the collapsed card, the model
|
|
2186
|
+
// list, the row. The pins are what this plugin is for, so they get the same
|
|
2187
|
+
// standing as the shell's own pages: `order: 12` sits between Models (10) and
|
|
2188
|
+
// Plugins (15), which is also where a reader looks for a provider's routing.
|
|
2155
2189
|
//
|
|
2156
|
-
// `settings.
|
|
2157
|
-
//
|
|
2158
|
-
|
|
2159
|
-
|
|
2160
|
-
name: 'settings.plugins.tab',
|
|
2190
|
+
// `settings.section` is a list slot: its key is `id`, its position is
|
|
2191
|
+
// `order`, and its nav label is a thunk so it follows the active locale.
|
|
2192
|
+
ctx.slots.inject('settings.section', () => ctx.slots.register({
|
|
2193
|
+
name: 'settings.section',
|
|
2161
2194
|
id: NS,
|
|
2162
|
-
order:
|
|
2195
|
+
order: 12,
|
|
2163
2196
|
label: () => translate('title'),
|
|
2164
2197
|
locale: NS,
|
|
2165
2198
|
inject: face,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-cline-pass",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.10",
|
|
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",
|