@goodandready/dsh-subscriptions 0.4.19 → 0.4.20

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 +238 -85
  2. package/package.json +1 -1
package/lib/client.js CHANGED
@@ -13,7 +13,6 @@ window.__ModuleLoader__.load({
13
13
  const setT = (fn) => { t = fn }
14
14
 
15
15
  const CSS =
16
- // card — same structure as core PluginCard (bash, agent-loop, web-search)
17
16
  '.dsub-card{list-style:none;border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-3);border-radius:12px;transition:border-color .16s,background .16s}' +
18
17
  '.dsub-card:hover{border-color:var(--dsw-alias-label-dimmed)}' +
19
18
  '.dsub-cardOpen{background:var(--dsw-alias-bg-layer-2);border-color:var(--dsw-alias-label-dimmed)}' +
@@ -24,38 +23,22 @@ window.__ModuleLoader__.load({
24
23
  '.dsub-description{color:var(--dsw-alias-label-secondary);font-size:13px}' +
25
24
  '.dsub-chev{color:var(--dsw-alias-label-tertiary);flex:none;transition:transform .16s}' +
26
25
  '.dsub-chevOpen{transform:rotate(180deg)}' +
27
- '.dsub-body{border-top:1px solid var(--dsw-alias-border-l2);margin:0 16px;padding-bottom:8px}' +
28
- // existing dsub classes kept for SubsSection internals
29
- '.dsub-wrap{display:flex;flex-direction:column;gap:22px;padding:4px 0;max-width:760px}' +
30
- '.dsub-block{display:flex;flex-direction:column;gap:10px}' +
31
- '.dsub-h{font-size:14px;font-weight:600;color:var(--dsw-alias-label-primary)}' +
26
+ '.dsub-body{border-top:1px solid var(--dsw-alias-border-l2);margin:0 16px;padding-bottom:12px}' +
27
+ '.dsub-block{margin-top:10px}' +
28
+ '.dsub-row{display:flex;align-items:center;gap:8px;margin-top:8px}' +
29
+ '.dsub-h{font-size:13.5px;font-weight:600;color:var(--dsw-alias-label-primary)}' +
32
30
  '.dsub-sub{font-size:12px;color:var(--dsw-alias-label-secondary)}' +
33
- '.dsub-row{display:flex;gap:8px;align-items:center;flex-wrap:wrap}' +
34
- '.dsub-row input,.dsub-field input,.dsub-row select{background:var(--dsw-alias-bg-layer-1);border:1px solid var(--dsw-alias-border-l1);color:var(--dsw-alias-label-primary);border-radius:6px;padding:6px 8px;font-size:13px}' +
35
- '.dsub-grow{flex:1;min-width:180px}' +
36
- '.dsub-field{display:flex;flex-direction:column;gap:6px;padding:12px 0}' +
37
- '.dsub-input{height:34px;border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-3);color:var(--dsw-alias-label-primary);border-radius:8px;padding:0 12px;font-size:13px}' +
38
- '.dsub-mini{border:1px solid var(--dsw-alias-border-l2);background:transparent;color:var(--dsw-alias-label-primary);border-radius:6px;height:28px;padding:0 8px;cursor:pointer}' +
39
- '.dsub-foot{border-top:1px solid var(--dsw-alias-border-l2);display:flex;justify-content:flex-end;align-items:center;gap:8px;padding:12px 0 4px}' +
40
- '.dsub-save{appearance:none;font:inherit;cursor:pointer;border:1px solid transparent;border-radius:8px;padding:5px 14px;font-size:13px;background:var(--dsw-alias-label-primary);color:var(--dsw-alias-bg-layer-3)}' +
31
+ '.dsub-dim{font-size:11.5px;color:var(--dsw-alias-label-tertiary)}' +
32
+ '.dsub-mini{appearance:none;font:inherit;font-size:12px;cursor:pointer;border:1px solid var(--dsw-alias-border-l2);border-radius:6px;padding:3px 8px;background:var(--dsw-alias-bg-layer-2);color:var(--dsw-alias-label-primary);transition:all .15s}' +
33
+ '.dsub-mini:hover{background:var(--dsw-alias-bg-layer-1);border-color:var(--dsw-alias-label-dimmed)}' +
41
34
  '.dsub-ok{font-size:12px;color:var(--dsw-alias-state-success-primary)}' +
42
35
  '.dsub-bad{font-size:12px;color:var(--dsw-alias-state-error-primary)}' +
43
- '.dsub-badge{font-size:11px;padding:2px 8px;border-radius:999px;border:1px solid var(--dsw-alias-border-l2);color:var(--dsw-alias-label-secondary);white-space:nowrap}' +
44
- '.dsub-badge-on{color:var(--dsw-alias-state-success-primary);border-color:currentColor}' +
45
- '.dsub-badge-warn{color:var(--dsw-alias-state-warning-primary);border-color:currentColor}' +
46
- '.dsub-chip{display:inline-flex;align-items:center;gap:5px;height:26px;padding:0 10px;border-radius:999px;font-size:12px;font-weight:600;line-height:1;white-space:nowrap;color:var(--dsw-alias-label-secondary);border:1px solid var(--dsw-alias-border-l2)}' +
47
- '.dsub-chipOn{color:var(--dsw-alias-state-success-primary);border-color:currentColor}' +
48
- '.dsub-chipWarn{color:var(--dsw-alias-state-warning-primary);border-color:currentColor}' +
49
- '.dsub-chipDanger{color:var(--dsw-alias-state-error-primary);border-color:currentColor}' +
50
- '.dsub-chipDot{width:6px;height:6px;border-radius:999px;background:currentColor;flex:none}' +
51
- '.dsub-chipActive{display:inline-flex;align-items:center;gap:6px;height:26px;padding:0 10px;border-radius:999px;border:1px solid var(--dsw-alias-border-l2);font-size:12px;font-weight:600;line-height:1;white-space:nowrap;color:var(--dsw-alias-label-secondary);background:var(--dsw-alias-bg-layer-3)}' +
52
- '.dsub-chipActiveWarn{color:var(--dsw-alias-state-warning-primary);border-color:currentColor}' +
53
- '.dsub-chipActiveDanger{color:var(--dsw-alias-state-error-primary);border-color:currentColor}' +
54
- '.dsub-chipActiveOk{color:var(--dsw-alias-state-success-primary);border-color:currentColor}' +
55
- '.dsub-chipActiveBar{height:3px;width:24px;background:var(--dsw-alias-bg-layer-1);border-radius:2px;flex:none;overflow:hidden}' +
56
- '.dsub-chipActiveFill{height:100%;border-radius:2px;background:currentColor}' +
57
-
58
- '.dsub-link{background:none;border:none;color:var(--dsw-alias-label-secondary);cursor:pointer;font-size:12px;padding:0}' +
36
+ '.dsub-btn{appearance:none;font:inherit;cursor:pointer;border:1px solid var(--dsw-alias-border-l2);border-radius:8px;padding:5px 12px;font-size:12.5px;font-weight:500;background:var(--dsw-alias-bg-layer-2);color:var(--dsw-alias-label-primary);transition:all .15s}' +
37
+ '.dsub-btn:hover{background:var(--dsw-alias-bg-layer-1);border-color:var(--dsw-alias-label-dimmed)}' +
38
+ '.dsub-inp{height:30px;border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-3);color:var(--dsw-alias-label-primary);border-radius:6px;padding:0 8px;font-size:12px;min-width:180px}' +
39
+ '.dsub-inp:focus{outline:none;border-color:var(--dsw-alias-brand-primary)}' +
40
+ '.dsub-box{border:1px solid var(--dsw-alias-border-l2);border-radius:8px;padding:10px;margin-top:8px;background:var(--dsw-alias-bg-layer-2)}' +
41
+ '.dsub-cooldown{display:inline-block;padding:2px 6px;border-radius:4px;background:var(--dsw-alias-state-warning-primary);color:#000;font-size:11px;font-weight:600}' +
59
42
  '.dsub-bar{position:relative;height:6px;border-radius:3px;background:var(--dsw-alias-bg-layer-1);overflow:hidden;flex:1;min-width:80px}' +
60
43
  '.dsub-barFill{height:100%;border-radius:3px;background:var(--dsw-alias-state-success-primary);transition:width .2s}' +
61
44
  '.dsub-barWarn .dsub-barFill{background:var(--dsw-alias-state-warning-primary)}' +
@@ -64,20 +47,53 @@ window.__ModuleLoader__.load({
64
47
  '.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
48
  '.dsub-verify{font-size:12px;color:var(--dsw-alias-state-warning-primary)}' +
66
49
  '.dsub-verify a{color:var(--dsw-alias-brand-primary)}' +
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)}' +
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}' +
69
- '.dsub-pill:hover{border-color:var(--dsw-alias-label-dimmed)}' +
70
- '.dsub-led{width:7px;height:7px;border-radius:50%;flex:none}' +
71
- '.dsub-ledOk{background:var(--dsw-alias-state-success-primary);box-shadow:0 0 6px var(--dsw-alias-state-success-primary)}' +
72
- '.dsub-ledWarn{background:var(--dsw-alias-state-warning-primary);box-shadow:0 0 6px var(--dsw-alias-state-warning-primary)}' +
73
- '.dsub-ledBad{background:var(--dsw-alias-state-error-primary);box-shadow:0 0 6px var(--dsw-alias-state-error-primary)}' +
74
- '.dsub-ledOff{background:var(--dsw-alias-label-tertiary)}' +
75
- '.dsub-modalWrap{position:fixed;inset:0;z-index:10020;display:grid;place-items:center;background:color-mix(in srgb,#000 45%,transparent)}' +
76
- '.dsub-modal{width:min(560px,92vw);max-height:min(70vh,640px);overflow:auto;border:1px solid var(--dsw-alias-border-l1);border-radius:14px;background:var(--dsw-alias-bg-layer-3);color:var(--dsw-alias-label-primary);box-shadow:0 24px 64px rgba(0,0,0,.4);padding:16px}' +
77
- '.dsub-modalHead{display:flex;align-items:center;justify-content:space-between;gap:10px;margin-bottom:12px}' +
78
- '.dsub-modalRow{display:flex;align-items:center;gap:10px;padding:9px 10px;border:1px solid var(--dsw-alias-border-l2);border-radius:10px;margin-top:8px;background:var(--dsw-alias-bg-layer-2)}' +
79
- '.dsub-modalName{font-weight:600;font-size:13px}' +
80
- '.dsub-modalSub{font-size:11.5px;color:var(--dsw-alias-label-secondary)}' +
50
+ '.dsub-pill{display:inline-flex;align-items:center;gap:7px;height:28px;padding:0 11px;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:12px;font-weight:600;cursor:pointer;box-shadow:0 1px 3px rgba(0,0,0,.15),inset 0 1px 0 rgba(255,255,255,.06);transition:all .16s ease;user-select:none}' +
51
+ '.dsub-pill:hover{background:var(--dsw-alias-bg-layer-3);border-color:var(--dsw-alias-label-tertiary);transform:translateY(-1px);box-shadow:0 3px 8px rgba(0,0,0,.25),inset 0 1px 0 rgba(255,255,255,.1)}' +
52
+ '.dsub-pill:active{transform:translateY(0)}' +
53
+ '.dsub-pillTag{padding:1px 5px;border-radius:4px;font-size:10.5px;font-weight:700;letter-spacing:.02em;background:var(--dsw-alias-bg-layer-1);color:var(--dsw-alias-label-secondary)}' +
54
+ '.dsub-led{width:7px;height:7px;border-radius:50%;flex:none;transition:all .2s ease}' +
55
+ '.dsub-ledOk{background:var(--dsw-alias-state-success-primary,#10b981);box-shadow:0 0 6px rgba(16,185,129,.7)}' +
56
+ '.dsub-ledWarn{background:var(--dsw-alias-state-warning-primary,#f59e0b);box-shadow:0 0 6px rgba(245,158,11,.7)}' +
57
+ '.dsub-ledBad{background:var(--dsw-alias-state-error-primary,#ef4444);box-shadow:0 0 6px rgba(239,68,68,.7)}' +
58
+ '.dsub-ledOff{background:var(--dsw-alias-label-tertiary,#6b7280)}' +
59
+ '.dsub-modalWrap{position:fixed;inset:0;z-index:10020;display:grid;place-items:center;background:rgba(0,0,0,.65);backdrop-filter:blur(10px);animation:dsubFadeIn .16s ease-out}' +
60
+ '.dsub-modal{width:min(520px,94vw);max-height:min(85vh,720px);overflow-y:auto;border:1px solid var(--dsw-alias-border-l1);border-radius:16px;background:var(--dsw-alias-bg-layer-3);color:var(--dsw-alias-label-primary);box-shadow:0 24px 64px rgba(0,0,0,.6),0 0 0 1px rgba(255,255,255,.07);padding:20px;animation:dsubScaleIn .18s cubic-bezier(.16,1,.3,1)}' +
61
+ '@keyframes dsubFadeIn{from{opacity:0}to{opacity:1}}' +
62
+ '@keyframes dsubScaleIn{from{opacity:0;transform:scale(.96)}to{opacity:1;transform:scale(1)}}' +
63
+ '.dsub-modalHead{display:flex;align-items:center;justify-content:space-between;gap:12px;padding-bottom:14px;border-bottom:1px solid var(--dsw-alias-border-l2)}' +
64
+ '.dsub-modalTitleWrap{display:flex;align-items:center;gap:10px}' +
65
+ '.dsub-modalIcon{width:30px;height:30px;border-radius:8px;background:var(--dsw-alias-bg-layer-1);display:grid;place-items:center;color:var(--dsw-alias-brand-primary);font-size:14px}' +
66
+ '.dsub-modalBadge{font-size:11px;font-weight:600;padding:2px 7px;border-radius:10px;background:var(--dsw-alias-bg-layer-1);color:var(--dsw-alias-label-secondary)}' +
67
+ '.dsub-heroCard{margin-top:14px;padding:14px 16px;border-radius:12px;background:linear-gradient(135deg,var(--dsw-alias-bg-layer-2) 0%,var(--dsw-alias-bg-layer-3) 100%);border:1px solid var(--dsw-alias-border-l1);box-shadow:inset 0 1px 0 rgba(255,255,255,.05)}' +
68
+ '.dsub-heroHead{display:flex;align-items:center;justify-content:space-between;margin-bottom:10px}' +
69
+ '.dsub-heroTitle{font-size:14px;font-weight:700;display:flex;align-items:center;gap:6px}' +
70
+ '.dsub-heroModel{font-size:11.5px;color:var(--dsw-alias-label-secondary);font-family:ui-monospace,monospace}' +
71
+ '.dsub-heroBars{display:flex;flex-direction:column;gap:8px;margin-top:10px}' +
72
+ '.dsub-barLabelRow{display:flex;justify-content:space-between;font-size:11.5px;color:var(--dsw-alias-label-secondary);margin-bottom:3px}' +
73
+ '.dsub-barTrack{height:6px;border-radius:3px;background:var(--dsw-alias-bg-layer-1);overflow:hidden}' +
74
+ '.dsub-barFillGrad{height:100%;border-radius:3px;transition:width .3s ease}' +
75
+ '.dsub-poolTitle{margin-top:16px;margin-bottom:8px;font-size:11.5px;font-weight:700;color:var(--dsw-alias-label-secondary);text-transform:uppercase;letter-spacing:.05em}' +
76
+ '.dsub-accountCard{display:flex;align-items:center;gap:12px;padding:10px 14px;border:1px solid var(--dsw-alias-border-l2);border-radius:10px;background:var(--dsw-alias-bg-layer-2);margin-top:6px;transition:border-color .15s,background .15s}' +
77
+ '.dsub-accountCard:hover{background:var(--dsw-alias-bg-layer-3);border-color:var(--dsw-alias-label-tertiary)}' +
78
+ '.dsub-brandBadge{width:32px;height:32px;border-radius:8px;display:grid;place-items:center;font-size:12px;font-weight:700;flex-shrink:0}' +
79
+ '.dsub-brandCodex{background:rgba(16,185,129,.15);color:#10b981;border:1px solid rgba(16,185,129,.3)}' +
80
+ '.dsub-brandClaude{background:rgba(245,158,11,.15);color:#f59e0b;border:1px solid rgba(245,158,11,.3)}' +
81
+ '.dsub-brandGrok{background:rgba(168,85,247,.15);color:#c084fc;border:1px solid rgba(168,85,247,.3)}' +
82
+ '.dsub-brandAgy{background:rgba(59,130,246,.15);color:#60a5fa;border:1px solid rgba(59,130,246,.3)}' +
83
+ '.dsub-brandOllama{background:rgba(107,114,128,.15);color:#9ca3af;border:1px solid rgba(107,114,128,.3)}' +
84
+ '.dsub-accInfo{flex:1;min-width:0;display:flex;flex-direction:column;gap:3px}' +
85
+ '.dsub-accNameRow{display:flex;align-items:center;justify-content:space-between;gap:8px}' +
86
+ '.dsub-accName{font-size:13px;font-weight:600}' +
87
+ '.dsub-accStatusTag{font-size:10.5px;font-weight:600;padding:1px 6px;border-radius:4px}' +
88
+ '.dsub-statusOk{background:rgba(16,185,129,.15);color:#34d399}' +
89
+ '.dsub-statusWarn{background:rgba(245,158,11,.15);color:#fbbf24}' +
90
+ '.dsub-statusBad{background:rgba(239,68,68,.15);color:#f87171}' +
91
+ '.dsub-statusOff{background:var(--dsw-alias-bg-layer-1);color:var(--dsw-alias-label-tertiary)}' +
92
+ '.dsub-modalFoot{display:flex;align-items:center;justify-content:space-between;margin-top:16px;padding-top:12px;border-top:1px solid var(--dsw-alias-border-l2)}' +
93
+ '.dsub-btnPrimary{display:inline-flex;align-items:center;gap:6px;padding:6px 12px;border-radius:8px;font-size:12px;font-weight:600;background:var(--dsw-alias-brand-primary,#3b82f6);color:#fff;border:0;cursor:pointer;transition:opacity .15s}' +
94
+ '.dsub-btnPrimary:hover{opacity:.9}' +
95
+ '.dsub-btnSec{display:inline-flex;align-items:center;gap:5px;padding:5px 10px;border-radius:7px;font-size:11.5px;font-weight:500;background:var(--dsw-alias-bg-layer-1);color:var(--dsw-alias-label-secondary);border:1px solid var(--dsw-alias-border-l2);cursor:pointer;text-decoration:none;transition:all .15s}' +
96
+ '.dsub-btnSec:hover{background:var(--dsw-alias-bg-layer-2);color:var(--dsw-alias-label-primary);border-color:var(--dsw-alias-label-tertiary)}' +
81
97
  '.dsub-cq{display:inline-flex;align-items:center;gap:6px;font-size:12px;color:var(--dsw-alias-label-secondary);padding:0 6px}' +
82
98
  '.dsub-cqBar{position:relative;width:40px;height:5px;border-radius:3px;background:var(--dsw-alias-bg-layer-1);overflow:hidden}' +
83
99
  '.dsub-cqBarFill{height:100%;border-radius:3px;background:var(--dsw-alias-state-success-primary)}' +
@@ -132,7 +148,15 @@ const cssId = 'dsh-subscriptions/settings.module.css'
132
148
  'diagIssues': 'Open issue tracker',
133
149
  'diagHint': 'The report has no tokens, emails, proxy addresses or personal data.',
134
150
  'subsPill': 'SUBS',
135
- 'subsModalTitle': 'Subscription console',
151
+ 'subsModalTitle': 'Subscription Hub',
152
+ 'subsActiveHero': 'Active Subscription',
153
+ 'subsPoolTitle': 'Provider Accounts',
154
+ 'subsRefresh': 'Refresh',
155
+ 'subsOpenSettings': 'All Settings →',
156
+ 'subsFastMode': 'Fast Mode 1.5x',
157
+ 'subsHealthy': 'Healthy',
158
+ 'subsFree': 'free',
159
+ 'subsUsed': 'used',
136
160
  'subsModalClose': 'Close',
137
161
  'subsLogged': 'connected',
138
162
  'subsNotLogged': 'not connected',
@@ -233,6 +257,14 @@ const cssId = 'dsh-subscriptions/settings.module.css'
233
257
  'diagHint': 'В отчёте нет токенов, email, адресов прокси и персональных данных.',
234
258
  'subsPill': 'SUBS',
235
259
  'subsModalTitle': 'Консоль подписок',
260
+ 'subsActiveHero': 'Активная подписка',
261
+ 'subsPoolTitle': 'Пул аккаунтов',
262
+ 'subsRefresh': 'Обновить',
263
+ 'subsOpenSettings': 'Все настройки →',
264
+ 'subsFastMode': 'Режим Fast 1.5x',
265
+ 'subsHealthy': 'Исправен',
266
+ 'subsFree': 'свободно',
267
+ 'subsUsed': 'израсходовано',
236
268
  'subsModalClose': 'Закрыть',
237
269
  'subsLogged': 'подключен',
238
270
  'subsNotLogged': 'не подключен',
@@ -1082,37 +1114,56 @@ const cssId = 'dsh-subscriptions/settings.module.css'
1082
1114
 
1083
1115
  // #83: пилл SUBS(N) в шапке сессии со светодиодом здоровья пула и
1084
1116
  // модальной консолью аккаунтов (закрытие: крестик, Escape, клик мимо).
1117
+ function brandBadge(prov) {
1118
+ const p = String(prov || '').toLowerCase()
1119
+ if (p.includes('codex') || p.includes('chatgpt') || p.includes('openai')) return { label: 'OpenAI', cls: 'dsub-brandCodex', icon: '⚡' }
1120
+ if (p.includes('claude') || p.includes('anthropic')) return { label: 'Claude', cls: 'dsub-brandClaude', icon: '✳' }
1121
+ if (p.includes('grok') || p.includes('xai')) return { label: 'Grok', cls: 'dsub-brandGrok', icon: '✦' }
1122
+ if (p.includes('antigravity') || p.includes('google') || p.includes('gemini')) return { label: 'AGY', cls: 'dsub-brandAgy', icon: '◆' }
1123
+ if (p.includes('ollama')) return { label: 'Ollama', cls: 'dsub-brandOllama', icon: '🦙' }
1124
+ return { label: (p.charAt(0).toUpperCase() || 'P'), cls: 'dsub-brandCodex', icon: '●' }
1125
+ }
1126
+
1085
1127
  function SubsPill(props) {
1086
1128
  const t = (props && props.t) || ((k) => k)
1087
- const [state, setState] = React.useState({ logged: [], usage: 0, accounts: [], open: false, active: null })
1129
+ const [state, setState] = React.useState({ logged: [], usage: 0, accounts: [], open: false, active: null, loading: false })
1130
+
1131
+ const pull = () => {
1132
+ setState((s) => Object.assign({}, s, { loading: true }))
1133
+ refreshLoggedIn().then((r) => {
1134
+ const logged = r.logged || []
1135
+ fetch('/dsh-subscriptions/config', { cache: 'no-store' })
1136
+ .then((res) => res.json())
1137
+ .then((cfg) => {
1138
+ setState((s) => Object.assign({}, s, { logged, usage: r.usage, accounts: (cfg && cfg.accounts) || [], active: r.active, loading: false }))
1139
+ })
1140
+ .catch(() => {
1141
+ setState((s) => Object.assign({}, s, { logged, usage: r.usage, accounts: [], active: r.active, loading: false }))
1142
+ })
1143
+ }).catch(() => {
1144
+ setState((s) => Object.assign({}, s, { loading: false }))
1145
+ })
1146
+ }
1147
+
1088
1148
  React.useEffect(() => {
1089
- let alive = true
1090
- const pull = () => {
1091
- refreshLoggedIn().then((r) => {
1092
- if (!alive) return
1093
- const logged = r.logged || []
1094
- fetch('/dsh-subscriptions/config', { cache: 'no-store' })
1095
- .then((res) => res.json())
1096
- .then((cfg) => {
1097
- if (!alive) return
1098
- setState({ logged, usage: r.usage, accounts: (cfg && cfg.accounts) || [], open: false, active: r.active })
1099
- })
1100
- .catch(() => { if (alive) setState({ logged, usage: r.usage, accounts: [], open: false, active: r.active }) })
1101
- }).catch(() => {})
1102
- }
1103
1149
  pull()
1104
- const id = setInterval(pull, 20 * 1000)
1105
- return () => { alive = false; clearInterval(id) }
1150
+ const id = setInterval(pull, 25 * 1000)
1151
+ return () => clearInterval(id)
1106
1152
  }, [])
1153
+
1107
1154
  React.useEffect(() => {
1108
1155
  if (!state.open) return undefined
1109
1156
  const onKey = (e) => { if (e.key === 'Escape') setState((s) => Object.assign({}, s, { open: false })) }
1110
1157
  document.addEventListener('keydown', onKey)
1111
1158
  return () => document.removeEventListener('keydown', onKey)
1112
1159
  }, [state.open])
1160
+
1113
1161
  const n = state.logged.length
1162
+ const total = state.accounts.length || n
1114
1163
  const led = n === 0 ? 'dsub-ledOff' : (state.usage >= 90 ? 'dsub-ledBad' : (state.usage >= 50 ? 'dsub-ledWarn' : 'dsub-ledOk'))
1115
1164
  const now = Date.now()
1165
+
1166
+ // Header Pill Label
1116
1167
  let pillText = t('subsPill') + ' (' + n + ')'
1117
1168
  if (state.active) {
1118
1169
  const a = state.active
@@ -1121,29 +1172,116 @@ const cssId = 'dsh-subscriptions/settings.module.css'
1121
1172
  const provName = a.provider ? (a.provider.charAt(0).toUpperCase() + a.provider.slice(1)) : ''
1122
1173
  pillText = (a.provider === 'codex' && a.fastMode ? '⚡ ' : '') + provName + (winStr ? ' · ' + winStr : (a.usagePercent != null ? ' · ' + Math.round(a.usagePercent) + '%' : ''))
1123
1174
  }
1175
+
1176
+ // Active Hero Card Renderer
1177
+ const renderActiveHero = () => {
1178
+ if (!state.active) return null
1179
+ const a = state.active
1180
+ const b = brandBadge(a.provider)
1181
+ const wins = Array.isArray(a.windows) ? a.windows : []
1182
+ const primaryWin = wins.find((w) => (w.label || w.id) === '5h') || wins[0]
1183
+ const secWin = wins.find((w) => (w.label || w.id) === '7d') || wins[1]
1184
+ const primPct = primaryWin && primaryWin.usedPercent != null ? Math.round(primaryWin.usedPercent) : (a.usagePercent != null ? Math.round(a.usagePercent) : 0)
1185
+ const secPct = secWin && secWin.usedPercent != null ? Math.round(secWin.usedPercent) : null
1186
+
1187
+ return React.createElement('div', { className: 'dsub-heroCard' },
1188
+ React.createElement('div', { className: 'dsub-heroHead' },
1189
+ React.createElement('div', { className: 'dsub-heroTitle' },
1190
+ React.createElement('span', { className: 'dsub-brandBadge ' + b.cls, style: { width: 24, height: 24, fontSize: 11 } }, b.icon),
1191
+ React.createElement('span', null, (a.provider ? a.provider.toUpperCase() : 'LLM') + (a.index ? ' #' + a.index : '')),
1192
+ a.fastMode ? React.createElement('span', { className: 'dsub-pillTag', style: { background: 'rgba(245,158,11,.15)', color: '#fbbf24' } }, '⚡ FAST 1.5x') : null,
1193
+ ),
1194
+ React.createElement('span', { className: 'dsub-heroModel' }, a.model || 'active model'),
1195
+ ),
1196
+ React.createElement('div', { className: 'dsub-heroBars' },
1197
+ React.createElement('div', null,
1198
+ React.createElement('div', { className: 'dsub-barLabelRow' },
1199
+ React.createElement('span', { style: { fontWeight: 600 } }, (primaryWin && primaryWin.label) ? primaryWin.label + ' window' : t('subs5hWindow')),
1200
+ React.createElement('span', { style: { fontVariantNumeric: 'tabular-nums', fontWeight: 600 } }, primPct + '% ' + t('subsUsed') + ' (' + (100 - primPct) + '% ' + t('subsFree') + ')'),
1201
+ ),
1202
+ React.createElement('div', { className: 'dsub-barTrack' },
1203
+ React.createElement('div', {
1204
+ className: 'dsub-barFillGrad',
1205
+ style: {
1206
+ width: Math.min(100, Math.max(0, primPct)) + '%',
1207
+ background: primPct >= 90 ? 'var(--dsw-alias-state-error-primary,#ef4444)' : (primPct >= 50 ? 'var(--dsw-alias-state-warning-primary,#f59e0b)' : 'var(--dsw-alias-state-success-primary,#10b981)'),
1208
+ },
1209
+ }),
1210
+ ),
1211
+ ),
1212
+ secWin ? React.createElement('div', null,
1213
+ React.createElement('div', { className: 'dsub-barLabelRow' },
1214
+ React.createElement('span', null, (secWin.label || '7d') + ' window'),
1215
+ React.createElement('span', { style: { fontVariantNumeric: 'tabular-nums' } }, secPct + '%'),
1216
+ ),
1217
+ React.createElement('div', { className: 'dsub-barTrack' },
1218
+ React.createElement('div', {
1219
+ className: 'dsub-barFillGrad',
1220
+ style: {
1221
+ width: Math.min(100, Math.max(0, secPct)) + '%',
1222
+ background: secPct >= 90 ? '#ef4444' : (secPct >= 50 ? '#f59e0b' : '#3b82f6'),
1223
+ },
1224
+ }),
1225
+ ),
1226
+ ) : null,
1227
+ ),
1228
+ )
1229
+ }
1230
+
1231
+ // Accounts list
1124
1232
  const rows = state.accounts.map((a, i) => {
1125
1233
  const isLogged = state.logged.includes(a.provider)
1126
1234
  const cooled = a.cooldownUntil && a.cooldownUntil > now
1127
1235
  const pct = a.usagePercent != null ? Math.round(a.usagePercent) : null
1128
- return React.createElement('div', { className: 'dsub-modalRow', key: i },
1129
- React.createElement('span', { className: 'dsub-led ' + (pct == null ? 'dsub-ledOff' : (pct >= 90 ? 'dsub-ledBad' : (pct >= 50 ? 'dsub-ledWarn' : 'dsub-ledOk'))) }),
1130
- React.createElement('span', { className: 'dsub-modalName' }, a.provider + (a.index ? ' #' + a.index : '')),
1131
- React.createElement('span', { style: { flex: 1 } }),
1132
- React.createElement('span', { className: 'dsub-modalSub' },
1133
- (isLogged ? t('subsLogged') : t('subsNotLogged')) +
1134
- (cooled ? ' · ' + t('subsCooldown') : '') +
1135
- (pct != null ? ' · ' + pct + '%' : '')
1236
+ const b = brandBadge(a.provider)
1237
+ const isActiveThis = state.active && state.active.provider === a.provider && state.active.index === a.index
1238
+
1239
+ let statusClass = 'dsub-statusOff'
1240
+ let statusText = t('subsNotLogged')
1241
+ if (cooled) {
1242
+ statusClass = 'dsub-statusWarn'
1243
+ statusText = t('subsCooldown')
1244
+ } else if (isLogged) {
1245
+ if (pct >= 90) { statusClass = 'dsub-statusBad'; statusText = '90%+ ' + t('subsUsed') }
1246
+ else if (pct >= 50) { statusClass = 'dsub-statusWarn'; statusText = pct + '% ' + t('subsUsed') }
1247
+ else { statusClass = 'dsub-statusOk'; statusText = (pct != null ? pct + '% · ' : '') + t('subsLogged') }
1248
+ }
1249
+
1250
+ return React.createElement('div', { className: 'dsub-accountCard', key: i },
1251
+ React.createElement('div', { className: 'dsub-brandBadge ' + b.cls }, b.icon),
1252
+ React.createElement('div', { className: 'dsub-accInfo' },
1253
+ React.createElement('div', { className: 'dsub-accNameRow' },
1254
+ React.createElement('div', { style: { display: 'flex', alignItems: 'center', gap: 6 } },
1255
+ React.createElement('span', { className: 'dsub-accName' }, a.provider.toUpperCase() + (a.index ? ' #' + a.index : '')),
1256
+ isActiveThis ? React.createElement('span', { className: 'dsub-pillTag', style: { background: 'rgba(16,185,129,.15)', color: '#34d399' } }, 'ACTIVE') : null,
1257
+ a.proxy ? React.createElement('span', { className: 'dsub-dim', title: a.proxy }, '🌐 proxy') : null,
1258
+ ),
1259
+ React.createElement('span', { className: 'dsub-accStatusTag ' + statusClass }, statusText),
1260
+ ),
1261
+ (isLogged && pct != null) ? React.createElement('div', { className: 'dsub-barTrack', style: { marginTop: 4, height: 4 } },
1262
+ React.createElement('div', {
1263
+ className: 'dsub-barFillGrad',
1264
+ style: {
1265
+ width: Math.min(100, Math.max(0, pct)) + '%',
1266
+ background: pct >= 90 ? '#ef4444' : (pct >= 50 ? '#f59e0b' : '#10b981'),
1267
+ },
1268
+ }),
1269
+ ) : null,
1136
1270
  ),
1137
1271
  )
1138
1272
  })
1273
+
1139
1274
  return React.createElement(React.Fragment, null,
1140
1275
  React.createElement('button', {
1141
- type: 'button', className: 'dsub-pill', 'aria-expanded': state.open ? 'true' : 'false',
1276
+ type: 'button',
1277
+ className: 'dsub-pill',
1278
+ 'aria-expanded': state.open ? 'true' : 'false',
1142
1279
  title: t('subsPill') + ': ' + (n > 0 ? (n + ' ' + t('connected')) : t('notConnected')),
1143
1280
  onClick: () => setState((s) => Object.assign({}, s, { open: !s.open })),
1144
1281
  },
1145
1282
  React.createElement('span', { className: 'dsub-led ' + led }),
1146
- pillText,
1283
+ React.createElement('span', null, pillText),
1284
+ React.createElement('span', { style: { fontSize: 9, opacity: 0.6, marginLeft: 2 } }, '▼'),
1147
1285
  ),
1148
1286
  state.open ? React.createElement('div', {
1149
1287
  className: 'dsub-modalWrap',
@@ -1151,16 +1289,36 @@ const cssId = 'dsh-subscriptions/settings.module.css'
1151
1289
  },
1152
1290
  React.createElement('div', { className: 'dsub-modal', role: 'dialog', 'aria-label': t('subsModalTitle') },
1153
1291
  React.createElement('div', { className: 'dsub-modalHead' },
1154
- React.createElement('span', { className: 'dsub-h' }, t('subsModalTitle')),
1155
- React.createElement('button', {
1156
- type: 'button', className: 'dsub-mini',
1157
- onClick: () => setState((s) => Object.assign({}, s, { open: false })),
1158
- }, t('subsModalClose') + ' ×'),
1292
+ React.createElement('div', { className: 'dsub-modalTitleWrap' },
1293
+ React.createElement('div', { className: 'dsub-modalIcon' }, '⚡'),
1294
+ React.createElement('div', null,
1295
+ React.createElement('div', { style: { fontWeight: 700, fontSize: 15 } }, t('subsModalTitle')),
1296
+ React.createElement('div', { className: 'dsub-dim' }, n + ' of ' + total + ' ' + t('connected')),
1297
+ ),
1298
+ ),
1299
+ React.createElement('div', { style: { display: 'flex', alignItems: 'center', gap: 6 } },
1300
+ React.createElement('button', {
1301
+ type: 'button',
1302
+ className: 'dsub-mini',
1303
+ title: t('subsRefresh'),
1304
+ onClick: () => pull(),
1305
+ }, state.loading ? '…' : '🔄'),
1306
+ React.createElement('button', {
1307
+ type: 'button',
1308
+ className: 'dsub-mini',
1309
+ onClick: () => setState((s) => Object.assign({}, s, { open: false })),
1310
+ }, '✕'),
1311
+ ),
1159
1312
  ),
1160
- rows.length ? rows : React.createElement('div', { className: 'dsub-sub' }, t('subsNotLogged')),
1161
- React.createElement('div', { className: 'dsub-row', style: { marginTop: 12 } },
1313
+ renderActiveHero(),
1314
+ React.createElement('div', { className: 'dsub-poolTitle' }, t('subsPoolTitle')),
1315
+ rows.length ? rows : React.createElement('div', { className: 'dsub-sub', style: { padding: '12px 0' } }, t('subsNotLogged')),
1316
+ React.createElement('div', { className: 'dsub-modalFoot' },
1317
+ React.createElement('span', { className: 'dsub-dim' }, 'DSH Subscriptions'),
1162
1318
  React.createElement('a', {
1163
- className: 'dsub-mini', href: '#', onClick: (e) => {
1319
+ className: 'dsub-btnSec',
1320
+ href: '#',
1321
+ onClick: (e) => {
1164
1322
  e.preventDefault()
1165
1323
  setState((s) => Object.assign({}, s, { open: false }))
1166
1324
  try {
@@ -1174,11 +1332,6 @@ const cssId = 'dsh-subscriptions/settings.module.css'
1174
1332
  ) : null,
1175
1333
  )
1176
1334
  }
1177
- // #82: перетаскиваемый HUD-бабл (shell.overlay) с кольцевой шкалой
1178
- // остатка активной подписки, прилипанием к краям и frosted-панелью.
1179
- const HUD_KEY = 'dsh-subscriptions.hud'
1180
- const HUD_SIZE = 64
1181
-
1182
1335
 
1183
1336
  function registerSubsPill(ctx) {
1184
1337
  ctx.effect(() => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@goodandready/dsh-subscriptions",
3
- "version": "0.4.19",
3
+ "version": "0.4.20",
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",