@goodandready/dsh-subscriptions 0.5.0 → 0.5.2

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/client.js CHANGED
@@ -83,6 +83,8 @@ window.__ModuleLoader__.load({
83
83
  '.dsub-brandOllama{background:rgba(107,114,128,.15);color:#9ca3af;border:1px solid rgba(107,114,128,.3)}' +
84
84
  '.dsub-brandKimi{background:rgba(236,72,153,.15);color:#f472b6;border:1px solid rgba(236,72,153,.3)}' +
85
85
  '.dsub-brandGlm{background:rgba(20,184,166,.15);color:#2dd4bf;border:1px solid rgba(20,184,166,.3)}' +
86
+ '.dsub-planBadge{display:inline-flex;align-items:center;padding:1px 6px;border-radius:4px;font-size:10px;font-weight:600;text-transform:uppercase;letter-spacing:0.5px;background:rgba(99,102,241,.15);color:#818cf8;border:1px solid rgba(99,102,241,.3);margin-left:6px}' +
87
+
86
88
  '.dsub-accInfo{flex:1;min-width:0;display:flex;flex-direction:column;gap:3px}' +
87
89
  '.dsub-accNameRow{display:flex;align-items:center;justify-content:space-between;gap:8px}' +
88
90
  '.dsub-accName{font-size:13px;font-weight:600}' +
@@ -322,6 +324,47 @@ const cssId = 'dsh-subscriptions/settings.module.css'
322
324
  }
323
325
 
324
326
  // #51: live countdown to the quota window reset (account.quota.resetAt).
327
+ function normalizePlanBadge(provider, plan) {
328
+ if (!plan && provider !== 'kimi' && provider !== 'glm') return null
329
+ var p = String(plan || '').toLowerCase().replace(/[^a-z0-9]/g, '')
330
+ var label = plan
331
+ if (provider === 'codex') {
332
+ if (p.indexOf('pro20') >= 0 || p === 'pro') label = 'Pro 20x'
333
+ else if (p.indexOf('pro5') >= 0 || p.indexOf('prolite') >= 0) label = 'Pro 5x'
334
+ else if (p.indexOf('team') >= 0) label = 'Team'
335
+ else if (p.indexOf('plus') >= 0) label = 'Plus'
336
+ else if (p.indexOf('enterp') >= 0) label = 'Enterprise'
337
+ } else if (provider === 'grok') {
338
+ if (p.indexOf('super') >= 0) label = 'SuperGrok'
339
+ else if (p.indexOf('plus') >= 0) label = 'X Premium+'
340
+ else if (p.indexOf('premium') >= 0) label = 'X Premium'
341
+ } else if (provider === 'antigravity') {
342
+ if (p.indexOf('ultra') >= 0) label = 'Ultra'
343
+ else if (p.indexOf('pro') >= 0) label = 'Pro'
344
+ } else if (provider === 'kimi') {
345
+ label = 'Coding Plan'
346
+ } else if (provider === 'glm') {
347
+ label = '150% Boost'
348
+ }
349
+ return label ? React.createElement('span', { className: 'dsub-planBadge' }, label) : null
350
+ }
351
+
352
+ function formatRelativeReset(resetAt, lang, now) {
353
+ if (!resetAt || !Number.isFinite(resetAt) || resetAt <= 0) return ''
354
+ var delta = resetAt - (now || Date.now())
355
+ var isRu = lang !== 'en'
356
+ if (delta <= 0) return isRu ? 'только что' : 'just now'
357
+ var totalMinutes = Math.max(1, Math.round(delta / 60000))
358
+ var days = Math.floor(totalMinutes / 1440)
359
+ var hours = Math.floor((totalMinutes % 1440) / 60)
360
+ var minutes = totalMinutes % 60
361
+ var bits = []
362
+ if (days) bits.push(days + (isRu ? ' дн' : 'd'))
363
+ if (hours) bits.push(hours + (isRu ? ' ч' : 'h'))
364
+ if (minutes || !bits.length) bits.push(minutes + (isRu ? ' мин' : 'm'))
365
+ return (isRu ? 'через ' : 'in ') + bits.join(' ')
366
+ }
367
+
325
368
  function ResetCountdown(props) {
326
369
  const [now, setNow] = React.useState(Date.now())
327
370
  React.useEffect(() => {
@@ -331,12 +374,15 @@ const cssId = 'dsh-subscriptions/settings.module.css'
331
374
  const ms = (props.resetAt || 0) - now
332
375
  if (ms <= 0) return null
333
376
  const total = Math.floor(ms / 1000)
334
- const h = Math.floor(total / 3600)
335
- const m = Math.floor((total % 3600) / 60)
377
+ if (total > 3600) {
378
+ return React.createElement('span', { className: 'dsub-sub', style: { fontVariantNumeric: 'tabular-nums' } },
379
+ t('resetLabel') + ' ' + formatRelativeReset(props.resetAt, t('lang'), now))
380
+ }
381
+ const m = Math.floor(total / 60)
336
382
  const sec = total % 60
337
383
  const pad = (n) => String(n).padStart(2, '0')
338
384
  return React.createElement('span', { className: 'dsub-sub', style: { fontVariantNumeric: 'tabular-nums' } },
339
- t('resetLabel') + ' ' + pad(h) + ':' + pad(m) + ':' + pad(sec))
385
+ t('resetLabel') + ' ' + pad(m) + ':' + pad(sec))
340
386
  }
341
387
 
342
388
  function badgeFor(account, t) {
@@ -718,6 +764,7 @@ const cssId = 'dsh-subscriptions/settings.module.css'
718
764
  },
719
765
  }),
720
766
  React.createElement('span', { className: 'dsub-badge' + (account.validationUrl ? ' dsub-badge-warn' : (on ? ' dsub-badge-on' : '')) }, badgeFor(account, t)),
767
+ normalizePlanBadge(row.slot.provider, account.plan || (account.quota && account.quota.plan)),
721
768
  React.createElement('button', {
722
769
  type: 'button', className: 'dsub-mini',
723
770
  onClick: () => connect(row.slot.provider, row.slot.index).catch((e) => setErr(String(e.message || e))),
@@ -1222,7 +1269,7 @@ const cssId = 'dsh-subscriptions/settings.module.css'
1222
1269
  React.createElement('div', { className: 'dsub-heroBars' },
1223
1270
  React.createElement('div', null,
1224
1271
  React.createElement('div', { className: 'dsub-barLabelRow' },
1225
- React.createElement('span', { style: { fontWeight: 600 } }, (primaryWin && primaryWin.label) ? primaryWin.label + ' window' : t('subs5hWindow')),
1272
+ React.createElement('span', { style: { fontWeight: 600 } }, ((primaryWin && primaryWin.label) ? primaryWin.label + ' window' : t('subs5hWindow')) + (primaryWin && primaryWin.resetAt ? ' (' + formatRelativeReset(primaryWin.resetAt, t('lang'), now) + ')' : '')),
1226
1273
  React.createElement('span', { style: { fontVariantNumeric: 'tabular-nums', fontWeight: 600 } }, primPct + '% ' + t('subsUsed') + ' (' + (100 - primPct) + '% ' + t('subsFree') + ')'),
1227
1274
  ),
1228
1275
  React.createElement('div', { className: 'dsub-barTrack' },
package/lib/plan.js ADDED
@@ -0,0 +1,42 @@
1
+ /**
2
+ * Normalize and pretty-print subscription plans.
3
+ */
4
+
5
+ export function normalizePlanName(provider, rawPlan) {
6
+ if (!rawPlan || typeof rawPlan !== 'string') {
7
+ if (provider === 'kimi') return 'Coding Plan'
8
+ if (provider === 'glm') return 'Coding Plan 150%'
9
+ return ''
10
+ }
11
+ const key = rawPlan.trim().toLowerCase().replace(/[^a-z0-9]/g, '')
12
+ if (provider === 'codex') {
13
+ if (key.includes('pro20') || key === 'pro') return 'Pro 20x'
14
+ if (key.includes('pro5') || key.includes('prolite')) return 'Pro 5x'
15
+ if (key.includes('team')) return 'Team'
16
+ if (key.includes('plus')) return 'Plus'
17
+ if (key.includes('enterp')) return 'Enterprise'
18
+ if (key.includes('edu')) return 'Edu'
19
+ if (key.includes('free')) return 'Free'
20
+ }
21
+ if (provider === 'grok') {
22
+ if (key.includes('super')) return 'SuperGrok'
23
+ if (key.includes('plus') || key.includes('premiumplus')) return 'X Premium+'
24
+ if (key.includes('premium')) return 'X Premium'
25
+ if (key.includes('basic')) return 'X Basic'
26
+ if (key.includes('free')) return 'Free'
27
+ }
28
+ if (provider === 'claude') {
29
+ if (key.includes('team')) return 'Team'
30
+ if (key.includes('enterp')) return 'Enterprise'
31
+ if (key.includes('pro')) return 'Pro'
32
+ if (key.includes('free')) return 'Free'
33
+ }
34
+ if (provider === 'antigravity') {
35
+ if (key.includes('ultra')) return 'Ultra'
36
+ if (key.includes('pro')) return 'Pro'
37
+ if (key.includes('free')) return 'Free'
38
+ }
39
+ if (provider === 'kimi') return 'Coding Plan'
40
+ if (provider === 'glm') return 'Coding Plan 150%'
41
+ return rawPlan
42
+ }
@@ -0,0 +1,47 @@
1
+ /**
2
+ * Format a future timestamp as a relative remaining duration down to the minute.
3
+ * Supports en and ru.
4
+ */
5
+
6
+ export const RELATIVE_UNITS = {
7
+ en: {
8
+ soon: 'just now',
9
+ prefix: 'in ',
10
+ suffix: '',
11
+ minute: '{n}m',
12
+ hour: '{n}h',
13
+ day: '{n}d',
14
+ },
15
+ ru: {
16
+ soon: 'только что',
17
+ prefix: 'через ',
18
+ suffix: '',
19
+ minute: '{n} мин',
20
+ hour: '{n} ч',
21
+ day: '{n} дн',
22
+ },
23
+ }
24
+
25
+ function fill(template, n) {
26
+ return String(template).replace('{n}', String(n))
27
+ }
28
+
29
+ export function formatRelativeReset(resetAt, lang = 'ru', now = Date.now()) {
30
+ if (typeof resetAt !== 'number' || !Number.isFinite(resetAt) || resetAt <= 0) return ''
31
+ const delta = resetAt - now
32
+ const units = RELATIVE_UNITS[lang] || RELATIVE_UNITS.ru
33
+ if (delta <= 0) return units.soon
34
+
35
+ const totalMinutes = Math.max(1, Math.round(delta / 60_000))
36
+ const days = Math.floor(totalMinutes / 1440)
37
+ const hours = Math.floor((totalMinutes % 1440) / 60)
38
+ const minutes = totalMinutes % 60
39
+
40
+ const bits = []
41
+ if (days) bits.push(fill(units.day, days))
42
+ if (hours) bits.push(fill(units.hour, hours))
43
+ if (minutes || bits.length === 0) bits.push(fill(units.minute, minutes))
44
+
45
+ const durationStr = bits.join(' ')
46
+ return `${units.prefix}${durationStr}${units.suffix}`.trim()
47
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@goodandready/dsh-subscriptions",
3
- "version": "0.5.0",
3
+ "version": "0.5.2",
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",