@goodandready/dsh-subscriptions 0.4.18 → 0.4.19

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.
Files changed (2) hide show
  1. package/lib/client.js +16 -337
  2. package/package.json +1 -1
package/lib/client.js CHANGED
@@ -64,27 +64,7 @@ window.__ModuleLoader__.load({
64
64
  '.dsub-manual{display:flex;flex-direction:column;gap:6px;margin-top:4px;padding-top:10px;border-top:1px solid var(--dsw-alias-border-l2)}' +
65
65
  '.dsub-verify{font-size:12px;color:var(--dsw-alias-state-warning-primary)}' +
66
66
  '.dsub-verify a{color:var(--dsw-alias-brand-primary)}' +
67
- '.dsub-hud{pointer-events:auto;position:fixed;z-index:10010;width:64px;height:64px;transition:left .2s,top .2s}' +
68
- '.dsub-hud[data-dragging=true]{transition:none}' +
69
- '.dsub-hudBtn{position:relative;width:64px;height:64px;padding:0;border:0;border-radius:50%;cursor:grab;display:grid;place-items:center;user-select:none;touch-action:none;' +
70
67
  'background:var(--dsw-alias-bg-layer-3);box-shadow:0 0 0 1px var(--dsw-alias-border-l2),0 8px 24px rgba(0,0,0,.25);backdrop-filter:blur(18px)}' +
71
- '.dsub-hudBtn:hover{transform:scale(1.05)}' +
72
- '.dsub-hudBtn:active{cursor:grabbing}' +
73
- '.dsub-hudRing{position:absolute;inset:4px;width:56px;height:56px;transform:rotate(-90deg)}' +
74
- '.dsub-hudTrack{fill:none;stroke:var(--dsw-alias-border-l2);stroke-width:4}' +
75
- '.dsub-hudVal{fill:none;stroke:var(--dsub-tone,var(--dsw-alias-state-success-primary));stroke-width:4;stroke-linecap:round;transition:stroke-dashoffset .4s}' +
76
- '.dsub-hudCore{position:relative;z-index:2;font-size:14px;font-weight:700;font-variant-numeric:tabular-nums;color:var(--dsw-alias-label-primary)}' +
77
- '.dsub-hud[data-collapsed=true] .dsub-hudBtn{opacity:.45}' +
78
- '.dsub-hud[data-collapsed=true]:hover .dsub-hudBtn{opacity:1}' +
79
- '.dsub-hudPanel{pointer-events:none;position:absolute;width:280px;padding:12px;border-radius:14px;background:var(--dsw-alias-bg-layer-3);border:1px solid var(--dsw-alias-border-l2);box-shadow:0 18px 48px rgba(0,0,0,.35);backdrop-filter:blur(28px);opacity:0;transform:translateY(6px);transition:opacity .16s,transform .16s}' +
80
- '.dsub-hud:hover .dsub-hudPanel{opacity:1;transform:none}' +
81
- '.dsub-hud[data-dragging=true] .dsub-hudPanel{opacity:0}' +
82
- '.dsub-hudRow{display:flex;align-items:center;gap:8px;margin-top:8px;font-size:11.5px}' +
83
- '.dsub-hudRow .dsub-bar{min-width:70px}' +
84
- '.dsub-hudName{font-weight:600;min-width:52px}' +
85
- '.dsub-hudPct{margin-left:auto;font-variant-numeric:tabular-nums}' +
86
- '.dsub-hudHead{font-size:12px;font-weight:600;color:var(--dsw-alias-label-primary)}' +
87
- '.dsub-hudHint{margin-top:10px;font-size:10.5px;color:var(--dsw-alias-label-tertiary)}' +
88
68
  '.dsub-pill{display:inline-flex;align-items:center;gap:6px;height:26px;padding:0 10px;border:1px solid var(--dsw-alias-border-l2);border-radius:999px;background:var(--dsw-alias-bg-layer-2);color:var(--dsw-alias-label-primary);font-size:11.5px;font-weight:600;cursor:pointer;letter-spacing:.06em}' +
89
69
  '.dsub-pill:hover{border-color:var(--dsw-alias-label-dimmed)}' +
90
70
  '.dsub-led{width:7px;height:7px;border-radius:50%;flex:none}' +
@@ -346,23 +326,7 @@ const cssId = 'dsh-subscriptions/settings.module.css'
346
326
  }
347
327
  const Chevron = ChevronIcon || FallbackChevron
348
328
 
349
- function PluginCard(props) {
350
- const t = (props && props.t) || ((key) => key)
351
- const [open, setOpen] = React.useState(false)
352
- return React.createElement('div', { className: 'dsub-block' },
353
- React.createElement('div', { className: 'dsub-row' },
354
- React.createElement('span', { className: 'dsub-h' }, t('title')),
355
- React.createElement('span', { className: 'dsub-sub' }, t('cardIntro')),
356
- React.createElement('span', { style: { flex: 1 } }),
357
- React.createElement('button', {
358
- type: 'button',
359
- className: 'dsub-mini',
360
- onClick: () => setOpen(!open),
361
- }, open ? t('hide') : t('show')),
362
- ),
363
- open ? React.createElement(SubsSection, props) : null,
364
- )
365
- }
329
+
366
330
 
367
331
  function SubsSection(props) {
368
332
  // Переводчик приходит от слота, потому что в его записи указан locale.
@@ -1120,7 +1084,7 @@ function PluginCard(props) {
1120
1084
  // модальной консолью аккаунтов (закрытие: крестик, Escape, клик мимо).
1121
1085
  function SubsPill(props) {
1122
1086
  const t = (props && props.t) || ((k) => k)
1123
- const [state, setState] = React.useState({ logged: [], usage: 0, accounts: [], open: false })
1087
+ const [state, setState] = React.useState({ logged: [], usage: 0, accounts: [], open: false, active: null })
1124
1088
  React.useEffect(() => {
1125
1089
  let alive = true
1126
1090
  const pull = () => {
@@ -1131,13 +1095,13 @@ function PluginCard(props) {
1131
1095
  .then((res) => res.json())
1132
1096
  .then((cfg) => {
1133
1097
  if (!alive) return
1134
- setState({ logged, usage: r.usage, accounts: (cfg && cfg.accounts) || [], open: false })
1098
+ setState({ logged, usage: r.usage, accounts: (cfg && cfg.accounts) || [], open: false, active: r.active })
1135
1099
  })
1136
- .catch(() => { if (alive) setState({ logged, usage: r.usage, accounts: [], open: false }) })
1100
+ .catch(() => { if (alive) setState({ logged, usage: r.usage, accounts: [], open: false, active: r.active }) })
1137
1101
  }).catch(() => {})
1138
1102
  }
1139
1103
  pull()
1140
- const id = setInterval(pull, 60 * 1000)
1104
+ const id = setInterval(pull, 20 * 1000)
1141
1105
  return () => { alive = false; clearInterval(id) }
1142
1106
  }, [])
1143
1107
  React.useEffect(() => {
@@ -1149,6 +1113,14 @@ function PluginCard(props) {
1149
1113
  const n = state.logged.length
1150
1114
  const led = n === 0 ? 'dsub-ledOff' : (state.usage >= 90 ? 'dsub-ledBad' : (state.usage >= 50 ? 'dsub-ledWarn' : 'dsub-ledOk'))
1151
1115
  const now = Date.now()
1116
+ let pillText = t('subsPill') + ' (' + n + ')'
1117
+ if (state.active) {
1118
+ const a = state.active
1119
+ const wins = Array.isArray(a.windows) ? a.windows : []
1120
+ const winStr = wins.map((w) => (w.label || w.id) + ' ' + Math.round(w.usedPercent) + '%').join(' ')
1121
+ const provName = a.provider ? (a.provider.charAt(0).toUpperCase() + a.provider.slice(1)) : ''
1122
+ pillText = (a.provider === 'codex' && a.fastMode ? '⚡ ' : '') + provName + (winStr ? ' · ' + winStr : (a.usagePercent != null ? ' · ' + Math.round(a.usagePercent) + '%' : ''))
1123
+ }
1152
1124
  const rows = state.accounts.map((a, i) => {
1153
1125
  const isLogged = state.logged.includes(a.provider)
1154
1126
  const cooled = a.cooldownUntil && a.cooldownUntil > now
@@ -1167,10 +1139,11 @@ function PluginCard(props) {
1167
1139
  return React.createElement(React.Fragment, null,
1168
1140
  React.createElement('button', {
1169
1141
  type: 'button', className: 'dsub-pill', 'aria-expanded': state.open ? 'true' : 'false',
1142
+ title: t('subsPill') + ': ' + (n > 0 ? (n + ' ' + t('connected')) : t('notConnected')),
1170
1143
  onClick: () => setState((s) => Object.assign({}, s, { open: !s.open })),
1171
1144
  },
1172
1145
  React.createElement('span', { className: 'dsub-led ' + led }),
1173
- t('subsPill') + ' (' + n + ')',
1146
+ pillText,
1174
1147
  ),
1175
1148
  state.open ? React.createElement('div', {
1176
1149
  className: 'dsub-modalWrap',
@@ -1182,7 +1155,7 @@ function PluginCard(props) {
1182
1155
  React.createElement('button', {
1183
1156
  type: 'button', className: 'dsub-mini',
1184
1157
  onClick: () => setState((s) => Object.assign({}, s, { open: false })),
1185
- }, t('subsModalClose') + ' \u00d7'),
1158
+ }, t('subsModalClose') + ' ×'),
1186
1159
  ),
1187
1160
  rows.length ? rows : React.createElement('div', { className: 'dsub-sub' }, t('subsNotLogged')),
1188
1161
  React.createElement('div', { className: 'dsub-row', style: { marginTop: 12 } },
@@ -1190,7 +1163,6 @@ function PluginCard(props) {
1190
1163
  className: 'dsub-mini', href: '#', onClick: (e) => {
1191
1164
  e.preventDefault()
1192
1165
  setState((s) => Object.assign({}, s, { open: false }))
1193
- try { document.querySelector('[data-slot-key=\"settings.plugin.item\"],[data-dsub-open]') } catch {}
1194
1166
  try {
1195
1167
  const btn = Array.from(document.querySelectorAll('button')).find((b) => (b.textContent || '').includes(t('title')))
1196
1168
  if (btn) btn.click()
@@ -1202,162 +1174,11 @@ function PluginCard(props) {
1202
1174
  ) : null,
1203
1175
  )
1204
1176
  }
1205
-
1206
1177
  // #82: перетаскиваемый HUD-бабл (shell.overlay) с кольцевой шкалой
1207
1178
  // остатка активной подписки, прилипанием к краям и frosted-панелью.
1208
1179
  const HUD_KEY = 'dsh-subscriptions.hud'
1209
1180
  const HUD_SIZE = 64
1210
1181
 
1211
- function hudClamp(pos) {
1212
- const w = window.innerWidth, h = window.innerHeight
1213
- return {
1214
- left: Math.min(Math.max(8, pos.left), Math.max(8, w - HUD_SIZE - 8)),
1215
- top: Math.min(Math.max(8, pos.top), Math.max(8, h - HUD_SIZE - 8)),
1216
- }
1217
- }
1218
-
1219
- function hudLoad() {
1220
- try {
1221
- const raw = JSON.parse(localStorage.getItem(HUD_KEY) || 'null')
1222
- if (raw && typeof raw.left === 'number' && typeof raw.top === 'number') return hudClamp(raw)
1223
- } catch {}
1224
- return hudClamp({ left: window.innerWidth - HUD_SIZE - 24, top: window.innerHeight - HUD_SIZE - 120 })
1225
- }
1226
-
1227
- function hudDock(pos) {
1228
- const w = window.innerWidth, h = window.innerHeight
1229
- const dist = { left: pos.left, right: w - (pos.left + HUD_SIZE), top: pos.top, bottom: h - (pos.top + HUD_SIZE) }
1230
- const TH = 24
1231
- let side = null, best = Infinity
1232
- for (const k of ['left', 'right', 'top', 'bottom']) {
1233
- if (dist[k] <= TH && dist[k] < best) { best = dist[k]; side = k }
1234
- }
1235
- if (!side) return pos
1236
- const next = { left: pos.left, top: pos.top }
1237
- if (side === 'left') next.left = 0
1238
- if (side === 'right') next.left = Math.max(0, w - HUD_SIZE)
1239
- if (side === 'top') next.top = 0
1240
- if (side === 'bottom') next.top = Math.max(0, h - HUD_SIZE)
1241
- return next
1242
- }
1243
-
1244
- function UsageBubble(props) {
1245
- const t = (props && props.t) || ((k) => k)
1246
- const [pos, setPos] = React.useState(() => (typeof window === 'undefined' ? { left: 0, top: 0 } : hudLoad()))
1247
- const [accounts, setAccounts] = React.useState([])
1248
- const [active, setActive] = React.useState(null)
1249
- const [dragging, setDragging] = React.useState(false)
1250
- const [busy, setBusy] = React.useState(false)
1251
- const drag = React.useRef(null)
1252
- React.useEffect(() => {
1253
- let alive = true
1254
- const pull = () => {
1255
- refreshLoggedIn().then((r) => {
1256
- if (!alive) return
1257
- setActive(r.active)
1258
- fetch('/dsh-subscriptions/config', { cache: 'no-store' })
1259
- .then((res) => res.json())
1260
- .then((cfg) => { if (alive) setAccounts((cfg && cfg.accounts) || []) })
1261
- .catch(() => {})
1262
- }).catch(() => {})
1263
- }
1264
- pull()
1265
- const id = setInterval(pull, 60 * 1000)
1266
- return () => { alive = false; clearInterval(id) }
1267
- }, [])
1268
- const onPointerDown = (e) => {
1269
- if (e.button !== 0) return
1270
- e.preventDefault()
1271
- e.currentTarget.setPointerCapture(e.pointerId)
1272
- drag.current = { id: e.pointerId, sx: e.clientX, sy: e.clientY, left: pos.left, top: pos.top, moved: false }
1273
- }
1274
- const onPointerMove = (e) => {
1275
- const d = drag.current
1276
- if (!d || e.pointerId !== d.id) return
1277
- if (Math.abs(e.clientX - d.sx) > 3 || Math.abs(e.clientY - d.sy) > 3) { d.moved = true; setDragging(true) }
1278
- if (!d.moved) return
1279
- setPos(hudClamp({ left: d.left + (e.clientX - d.sx), top: d.top + (e.clientY - d.sy) }))
1280
- }
1281
- const onPointerUp = (e) => {
1282
- const d = drag.current
1283
- if (!d || e.pointerId !== d.id) return
1284
- drag.current = null
1285
- setDragging(false)
1286
- setPos((cur) => {
1287
- const next = d.moved ? hudDock(hudClamp(cur)) : cur
1288
- try { localStorage.setItem(HUD_KEY, JSON.stringify(next)) } catch {}
1289
- return next
1290
- })
1291
- if (!d.moved) {
1292
- setBusy(true)
1293
- refreshLoggedIn().then((r) => {
1294
- setActive(r.active)
1295
- return fetch('/dsh-subscriptions/config', { cache: 'no-store' }).then((res) => res.json())
1296
- }).then((cfg) => setAccounts((cfg && cfg.accounts) || [])).catch(() => {}).finally(() => setBusy(false))
1297
- }
1298
- }
1299
- // Остаток активной подписки: 100 - usagePercent.
1300
- const remaining = active && active.usagePercent != null ? Math.max(0, 100 - active.usagePercent) : null
1301
- const tone = remaining == null ? 'var(--dsw-alias-label-tertiary)'
1302
- : (remaining < 10 ? 'var(--dsw-alias-state-error-primary)'
1303
- : (remaining < 30 ? 'var(--dsw-alias-state-warning-primary)' : 'var(--dsw-alias-state-success-primary)'))
1304
- const R = 26
1305
- const CIRC = 2 * Math.PI * R
1306
- const offset = CIRC * (1 - (remaining == null ? 0 : Math.min(100, remaining)) / 100)
1307
- const rows = accounts.map((a, i) => {
1308
- const pct = a.usagePercent != null ? Math.round(a.usagePercent) : null
1309
- return React.createElement('div', { className: 'dsub-hudRow', key: i },
1310
- React.createElement('span', { className: 'dsub-hudName' }, a.provider + (a.index ? ' #' + a.index : '')),
1311
- React.createElement('span', { className: 'dsub-bar' },
1312
- React.createElement('span', { className: 'dsub-barFill', style: { width: (pct == null ? 0 : Math.min(100, pct)) + '%' } })),
1313
- React.createElement('span', { className: 'dsub-hudPct' }, pct == null ? '—' : pct + '%'),
1314
- )
1315
- })
1316
- if (!createPortal) return null
1317
- return createPortal(React.createElement('div', {
1318
- className: 'dsub-hud',
1319
- 'data-dragging': dragging ? 'true' : undefined,
1320
- style: { left: pos.left + 'px', top: pos.top + 'px' },
1321
- },
1322
- React.createElement('button', {
1323
- type: 'button', className: 'dsub-hudBtn', 'aria-label': t('hudTitle'),
1324
- style: { '--dsub-tone': tone },
1325
- onPointerDown, onPointerMove, onPointerUp, onPointerCancel: onPointerUp,
1326
- },
1327
- React.createElement('svg', { className: 'dsub-hudRing', viewBox: '0 0 56 56', 'aria-hidden': 'true' },
1328
- React.createElement('circle', { className: 'dsub-hudTrack', cx: 28, cy: 28, r: R }),
1329
- React.createElement('circle', {
1330
- className: 'dsub-hudVal', cx: 28, cy: 28, r: R,
1331
- strokeDasharray: String(CIRC), strokeDashoffset: String(offset),
1332
- }),
1333
- ),
1334
- React.createElement('span', { className: 'dsub-hudCore' },
1335
- remaining == null ? '—' : (busy ? '\u2026' : Math.round(remaining) + '%'),
1336
- ),
1337
- ),
1338
- React.createElement('div', { className: 'dsub-hudPanel' },
1339
- React.createElement('div', { className: 'dsub-hudHead' }, t('hudTitle')),
1340
- rows.length ? rows : React.createElement('div', { className: 'dsub-sub' }, '\u2014'),
1341
- React.createElement('div', { className: 'dsub-hudHint' }, t('hudHint')),
1342
- ),
1343
- ), document.body)
1344
- }
1345
-
1346
- function registerUsageBubble(ctx) {
1347
- ctx.effect(() => {
1348
- ctx.slots.inject('shell.overlay', () =>
1349
- ctx.slots.register(
1350
- {
1351
- name: 'shell.overlay',
1352
- id: 'dsh-subscriptions-usage-bubble',
1353
- order: 40,
1354
- locale: NS,
1355
- },
1356
- (props) => React.createElement(UsageBubble, { t }),
1357
- ),
1358
- )
1359
- }, 'dsh-subscriptions: usage bubble')
1360
- }
1361
1182
 
1362
1183
  function registerSubsPill(ctx) {
1363
1184
  ctx.effect(() => {
@@ -1391,145 +1212,6 @@ function PluginCard(props) {
1391
1212
  }, 'dsh-subscriptions: composer quota')
1392
1213
  }
1393
1214
 
1394
- // #52: компактный чип в шапке сессии со статусом подключённых подписок.
1395
- function HeaderChip(props) {
1396
- const [state, setState] = React.useState({ loading: true, count: 0, usage: 0, expiry: null, active: null })
1397
- const t = (props && props.t) || ((k) => k)
1398
- React.useEffect(() => {
1399
- const pull = () => {
1400
- refreshLoggedIn().then((r) => {
1401
- // #67: ближайшее окончание подписки в пределах expiryNotifyDays.
1402
- let expiry = null
1403
- const now = Date.now()
1404
- const days = r.expiryNotifyDays || 7
1405
- for (const p of r.logged) {
1406
- const at = r.expiresAt[p]
1407
- if (!at) continue
1408
- const msLeft = at - now
1409
- if (msLeft > 0 && msLeft <= days * 24 * 60 * 60 * 1000) {
1410
- if (!expiry || at < expiry.at) expiry = { at, label: r.labels[p] || p }
1411
- }
1412
- }
1413
- setState({ loading: false, count: r.logged.length, usage: r.usage, expiry, active: r.active })
1414
- }).catch(() => setState({ loading: false, count: 0, usage: 0, expiry: null, active: null }))
1415
- }
1416
- pull()
1417
- const id = setInterval(pull, 60 * 1000)
1418
- return () => clearInterval(id)
1419
- }, [])
1420
- // #69/#72: если есть активная подписка — показываем её (провайдер + индекс + windows с процентами).
1421
- if (state.active) {
1422
- const a = state.active
1423
- const wins = Array.isArray(a.windows) ? a.windows : []
1424
- // макс-процент среди окон для цвета полоски
1425
- let maxPct = -1
1426
- for (const w of wins) {
1427
- if (w && typeof w.usedPercent === 'number' && w.usedPercent > maxPct) maxPct = w.usedPercent
1428
- }
1429
- if (maxPct < 0 && typeof a.usagePercent === 'number') maxPct = a.usagePercent
1430
- const u = maxPct >= 0 ? maxPct : null
1431
- let acls = 'dsub-chipActive'
1432
- if (u != null) acls += u >= 100 ? ' dsub-chipActiveDanger' : (u >= 80 ? ' dsub-chipActiveDanger' : (u >= 50 ? ' dsub-chipActiveWarn' : ' dsub-chipActiveOk'))
1433
- const idx = a.index != null ? ('#' + a.index) : ''
1434
- const plan = a.plan ? (' ' + a.plan) : ''
1435
- // #77: без provider-префикса
1436
- const label = (a.provider === 'codex' && state.fastMode ? '\u26a1' : '') + idx + plan
1437
- // windows: "5h 12% 7d 8%"
1438
- const winLabels = wins.map((w) => (w.label || w.id) + ' ' + Math.round(w.usedPercent) + '%').join(' ')
1439
- return React.createElement('span', {
1440
- className: acls,
1441
- title: a.model ? (prov + idx + ' · ' + a.model) : (prov + idx),
1442
- },
1443
- React.createElement('span', { className: 'dsub-chipDot' }),
1444
- label,
1445
- u != null
1446
- ? React.createElement('span', { className: 'dsub-chipActiveBar' },
1447
- React.createElement('span', { className: 'dsub-chipActiveFill', style: { width: Math.min(100, Math.max(0, u)) + '%' } }))
1448
- : null,
1449
- winLabels || null,
1450
- )
1451
- }
1452
- const on = state.count > 0
1453
- // #66: цвет чипа по максимальному usagePercent (50/80/100).
1454
- const u = state.usage
1455
- let cls = 'dsub-chip'
1456
- if (on) cls += u >= 100 ? ' dsub-chipDanger' : (u >= 80 ? ' dsub-chipDanger' : (u >= 50 ? ' dsub-chipWarn' : ' dsub-chipOn'))
1457
- // #67: надпись об окончании подписки.
1458
- let text = on ? String(state.count) : '—'
1459
- if (state.expiry) {
1460
- const d = new Date(state.expiry.at).toLocaleDateString()
1461
- text = t('expiryLabel') + ': ' + state.expiry.label + ' ' + d
1462
- }
1463
- return React.createElement('span', {
1464
- className: cls,
1465
- title: on ? t('chipActive') + ': ' + state.count : t('none'),
1466
- },
1467
- React.createElement('span', { className: 'dsub-chipDot' }),
1468
- text,
1469
- )
1470
- }
1471
-
1472
- function registerHeaderChip(ctx) {
1473
- ctx.effect(() => {
1474
- ctx.slots.inject('conversation.session.header.utilities', () =>
1475
- ctx.slots.register(
1476
- {
1477
- name: 'conversation.session.header.utilities',
1478
- id: 'dsh-subscriptions-header-chip',
1479
- order: 5,
1480
- locale: NS,
1481
- },
1482
- (props) => React.createElement(HeaderChip, { t }),
1483
- ),
1484
- )
1485
- }, 'dsh-subscriptions: header chip')
1486
- }
1487
-
1488
- function registerComposerSwitch(ctx) {
1489
- ctx.effect(() => {
1490
- const render = () => {
1491
- if (!labelEl) return
1492
- refreshLoggedIn().then((logged) => {
1493
- if (!labelEl) return
1494
- const current = logged.length ? logged[activeIdx % logged.length] : null
1495
- labelEl.textContent = t('switchLabel') + ': ' + (current || t('none'))
1496
- }).catch(() => {})
1497
- }
1498
- ctx.slots.inject('composer.action', () =>
1499
- ctx.slots.register(
1500
- {
1501
- name: 'composer.action',
1502
- id: 'dsh-subscriptions-provider-switch',
1503
- order: 31,
1504
- label: () => {
1505
- // label используется ядром до монтирования; держим актуальным
1506
- setTimeout(render, 0)
1507
- return t('switchLabel')
1508
- },
1509
- },
1510
- (props) => {
1511
- const el = React.createElement('button', {
1512
- onClick: async () => {
1513
- const logged = await refreshLoggedIn()
1514
- if (!logged.length) return
1515
- activeIdx = (activeIdx + 1) % logged.length
1516
- render()
1517
- },
1518
- }, '')
1519
- // Обновить текст в смонтированном элементе
1520
- setTimeout(() => {
1521
- labelEl = el
1522
- render()
1523
- }, 0)
1524
- return el
1525
- },
1526
- ),
1527
- )
1528
- render()
1529
- }, 'dsh-subscriptions: composer provider switch')
1530
- }
1531
-
1532
- // #43: slash-команды входа/выхода/статуса прямо из чата.
1533
1215
  function registerSlashCommands(ctx) {
1534
1216
  ctx.effect(() => {
1535
1217
  const triggers = ctx.get('inputTriggers')
@@ -1595,11 +1277,8 @@ function PluginCard(props) {
1595
1277
  exports.apply = function apply(ctx) {
1596
1278
  setT(ctx.locale.bind(NS))
1597
1279
  registerSettings(ctx)
1598
- registerUsageBubble(ctx)
1599
1280
  registerSubsPill(ctx)
1600
1281
  registerComposerQuota(ctx)
1601
- registerHeaderChip(ctx)
1602
- registerComposerSwitch(ctx)
1603
1282
  registerSlashCommands(ctx)
1604
1283
  }
1605
1284
  return module.exports
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@goodandready/dsh-subscriptions",
3
- "version": "0.4.18",
3
+ "version": "0.4.19",
4
4
  "description": "Use ChatGPT Codex, Claude, Grok, and Antigravity subscriptions as DeepSeek Harness LLM providers via OAuth.",
5
5
  "license": "MIT",
6
6
  "type": "module",