@goodandready/dsh-subscriptions 0.5.26 → 0.5.31

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.
@@ -1,36 +0,0 @@
1
- export class LatencyTracker {
2
- constructor(sampleLimit = 10) {
3
- this.samples = new Map()
4
- this.limit = sampleLimit
5
- }
6
-
7
- record(slotRef, latencyMs) {
8
- if (!slotRef || typeof latencyMs !== 'number' || latencyMs < 0) return
9
- if (!this.samples.has(slotRef)) {
10
- this.samples.set(slotRef, [])
11
- }
12
- const arr = this.samples.get(slotRef)
13
- arr.push(latencyMs)
14
- if (arr.length > this.limit) arr.shift()
15
- }
16
-
17
- getAverage(slotRef) {
18
- const arr = this.samples.get(slotRef)
19
- if (!arr || !arr.length) return null
20
- const sum = arr.reduce((acc, v) => acc + v, 0)
21
- return Math.round(sum / arr.length)
22
- }
23
-
24
- getHealthScore(slotRef) {
25
- const avg = this.getAverage(slotRef)
26
- if (avg == null) return 100
27
- if (avg < 300) return 100
28
- if (avg < 800) return 85
29
- if (avg < 2000) return 60
30
- return 30
31
- }
32
-
33
- clear() {
34
- this.samples.clear()
35
- }
36
- }
@@ -1,16 +0,0 @@
1
- export function filterAccountsForUser(accounts, userContext) {
2
- const list = Array.isArray(accounts) ? accounts : []
3
- if (!userContext || userContext.isAdmin) {
4
- return list // Admin sees all
5
- }
6
-
7
- const username = userContext.username || userContext.userId || userContext.sub
8
- if (!username) return []
9
-
10
- return list.filter((acc) => {
11
- if (!acc.allowedUsers || !Array.isArray(acc.allowedUsers)) {
12
- return true // Public in pool
13
- }
14
- return acc.allowedUsers.includes(username)
15
- })
16
- }
@@ -1,23 +0,0 @@
1
- export async function benchmarkEndpoint(url, fetchImpl) {
2
- const impl = fetchImpl || fetch
3
- const start = Date.now()
4
- try {
5
- const res = await impl(url, { method: 'HEAD', signal: AbortSignal.timeout(4000) })
6
- const ttfb = Date.now() - start
7
- return {
8
- url,
9
- ok: res.ok,
10
- status: res.status,
11
- latencyMs: ttfb,
12
- grade: ttfb < 300 ? 'A' : (ttfb < 800 ? 'B' : 'C')
13
- }
14
- } catch (err) {
15
- return {
16
- url,
17
- ok: false,
18
- error: err.message,
19
- latencyMs: Date.now() - start,
20
- grade: 'F'
21
- }
22
- }
23
- }
package/lib/pkce-store.js DELETED
@@ -1,28 +0,0 @@
1
- const pkceStates = new Map()
2
- const STATE_TTL_MS = 10 * 60 * 1000 // 10 minutes
3
-
4
- export function storePkceState(state, verifier) {
5
- if (!state || !verifier) return
6
- pkceStates.set(state, {
7
- verifier,
8
- createdAt: Date.now()
9
- })
10
- }
11
-
12
- export function consumePkceState(state) {
13
- if (!state) return null
14
- const entry = pkceStates.get(state)
15
- if (!entry) return null
16
-
17
- // Single-use consumption (prevent INVALID_REPLAY_STATE)
18
- pkceStates.delete(state)
19
-
20
- if (Date.now() - entry.createdAt > STATE_TTL_MS) {
21
- return null // expired
22
- }
23
- return entry.verifier
24
- }
25
-
26
- export function clearPkceStates() {
27
- pkceStates.clear()
28
- }
package/lib/plan.js DELETED
@@ -1,42 +0,0 @@
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
- }
@@ -1,37 +0,0 @@
1
- export class ProactiveTokenRefreshDaemon {
2
- constructor({ refreshLeadMs = 15 * 60 * 1000, checkIntervalMs = 60 * 1000 } = {}) {
3
- this.refreshLeadMs = refreshLeadMs // refresh 15 min before token expires
4
- this.checkIntervalMs = checkIntervalMs
5
- this.timer = null
6
- }
7
-
8
- start(getAccountsFn, refreshFn) {
9
- if (this.timer) return
10
- this.timer = setInterval(async () => {
11
- try {
12
- const accounts = (typeof getAccountsFn === 'function' && await getAccountsFn()) || []
13
- const now = Date.now()
14
- for (const acc of accounts) {
15
- if (!acc || !acc.expiresAt || !acc.refreshToken) continue
16
- const expiresAt = Number(acc.expiresAt)
17
- if (expiresAt - now <= this.refreshLeadMs && expiresAt > now) {
18
- if (typeof refreshFn === 'function') {
19
- await refreshFn(acc)
20
- }
21
- }
22
- }
23
- } catch {
24
- // silent catch on daemon tick
25
- }
26
- }, this.checkIntervalMs)
27
-
28
- if (this.timer.unref) this.timer.unref()
29
- }
30
-
31
- stop() {
32
- if (this.timer) {
33
- clearInterval(this.timer)
34
- this.timer = null
35
- }
36
- }
37
- }
@@ -1,44 +0,0 @@
1
- const warmedSlots = new Map()
2
-
3
- export class PromptCacheWarmer {
4
- constructor(ttlMs = 4.5 * 60 * 1000) {
5
- this.ttlMs = ttlMs // default warm-up every 4.5 min before 5 min Anthropic cache expires
6
- this.activeTimers = new Map()
7
- }
8
-
9
- touchSession(sessionId, slotRef, warmFn) {
10
- if (!sessionId || !slotRef || typeof warmFn !== 'function') return
11
- if (this.activeTimers.has(sessionId)) {
12
- clearTimeout(this.activeTimers.get(sessionId))
13
- }
14
-
15
- const timer = setTimeout(async () => {
16
- try {
17
- await warmFn(slotRef, sessionId)
18
- warmedSlots.set(sessionId, Date.now())
19
- } catch {
20
- // quiet error on background warm-up
21
- }
22
- }, this.ttlMs)
23
-
24
- if (timer.unref) timer.unref()
25
- this.activeTimers.set(sessionId, timer)
26
- }
27
-
28
- cancelSession(sessionId) {
29
- if (this.activeTimers.has(sessionId)) {
30
- clearTimeout(this.activeTimers.get(sessionId))
31
- this.activeTimers.delete(sessionId)
32
- }
33
- }
34
-
35
- isWarmed(sessionId) {
36
- return warmedSlots.has(sessionId)
37
- }
38
-
39
- clear() {
40
- for (const t of this.activeTimers.values()) clearTimeout(t)
41
- this.activeTimers.clear()
42
- warmedSlots.clear()
43
- }
44
- }
@@ -1,21 +0,0 @@
1
- export async function* streamWithReconnect(streamProducer, { maxReconnects = 2 } = {}) {
2
- let reconnects = 0
3
- let emittedCount = 0
4
-
5
- while (true) {
6
- try {
7
- for await (const chunk of streamProducer(emittedCount)) {
8
- emittedCount++
9
- yield chunk
10
- }
11
- return
12
- } catch (err) {
13
- reconnects++
14
- if (reconnects > maxReconnects) {
15
- throw err
16
- }
17
- // Brief pause before reconnect
18
- await new Promise((r) => setTimeout(r, 250))
19
- }
20
- }
21
- }
@@ -1,38 +0,0 @@
1
- export class ResetNotificationManager {
2
- constructor() {
3
- this.scheduledAlerts = new Map()
4
- }
5
-
6
- scheduleResetNotice(accountRef, resetAt, onResetAlert) {
7
- if (!accountRef || !resetAt || resetAt <= Date.now()) return
8
- if (this.scheduledAlerts.has(accountRef)) {
9
- clearTimeout(this.scheduledAlerts.get(accountRef))
10
- }
11
-
12
- const delay = Math.max(0, resetAt - Date.now())
13
- const timer = setTimeout(() => {
14
- this.scheduledAlerts.delete(accountRef)
15
- if (typeof onResetAlert === 'function') {
16
- onResetAlert({
17
- accountRef,
18
- message: `Subscription quota reset! Slot ${accountRef} is ready to use.`
19
- })
20
- }
21
- }, delay)
22
-
23
- if (timer.unref) timer.unref()
24
- this.scheduledAlerts.set(accountRef, timer)
25
- }
26
-
27
- cancel(accountRef) {
28
- if (this.scheduledAlerts.has(accountRef)) {
29
- clearTimeout(this.scheduledAlerts.get(accountRef))
30
- this.scheduledAlerts.delete(accountRef)
31
- }
32
- }
33
-
34
- clear() {
35
- for (const t of this.scheduledAlerts.values()) clearTimeout(t)
36
- this.scheduledAlerts.clear()
37
- }
38
- }
@@ -1,14 +0,0 @@
1
- export function calculateCacheSavings({ cachedTokens = 0, totalInputTokens = 0, ratePerMillion = 3.0 } = {}) {
2
- const hitRate = totalInputTokens > 0 ? Math.round((cachedTokens / totalInputTokens) * 100) : 0
3
- // Anthropic / OpenAI prompt cache discount is typically 90% (cached tokens cost 10% of standard input)
4
- const fullCost = (cachedTokens / 1_000_000) * ratePerMillion
5
- const discountedCost = fullCost * 0.1
6
- const savedDollars = Math.round((fullCost - discountedCost) * 100) / 100
7
-
8
- return {
9
- cachedTokens,
10
- totalInputTokens,
11
- hitRatePercent: hitRate,
12
- estimatedSavedDollars: savedDollars
13
- }
14
- }
@@ -1,33 +0,0 @@
1
- import { readFile, writeFile } from 'node:fs/promises'
2
- import { join } from 'node:path'
3
- import { homedir } from 'node:os'
4
-
5
- const SNAPSHOT_FILE = join(homedir(), '.dsh', 'subscriptions-snapshot.json')
6
-
7
- export async function persistPoolSnapshot(accounts) {
8
- try {
9
- const data = JSON.stringify({
10
- savedAt: Date.now(),
11
- accounts: (accounts || []).map((a) => ({
12
- ref: a.ref || a.id,
13
- cooldownUntil: a.cooldownUntil || 0,
14
- quota: a.quota || null,
15
- status: a.status || 'active'
16
- }))
17
- }, null, 2)
18
- await writeFile(SNAPSHOT_FILE, data, 'utf8')
19
- return true
20
- } catch {
21
- return false
22
- }
23
- }
24
-
25
- export async function recoverPoolSnapshot() {
26
- try {
27
- const text = await readFile(SNAPSHOT_FILE, 'utf8')
28
- const json = JSON.parse(text)
29
- return json && json.accounts ? json.accounts : []
30
- } catch {
31
- return []
32
- }
33
- }
package/lib/telemetry.js DELETED
@@ -1,28 +0,0 @@
1
- import { createHash } from 'node:crypto'
2
-
3
- const USER_AGENTS = [
4
- 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.0.0 Safari/537.36',
5
- 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.0.0 Safari/537.36',
6
- 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.0.0 Safari/537.36',
7
- 'VSCode/1.98.0 (Windows_NT x64)',
8
- 'Cursor/0.45.6 (Darwin x64)'
9
- ]
10
-
11
- export function getRandomUserAgent(seed) {
12
- if (!seed) return USER_AGENTS[0]
13
- const hash = createHash('md5').update(String(seed)).digest('hex')
14
- const idx = parseInt(hash.slice(0, 4), 16) % USER_AGENTS.length
15
- return USER_AGENTS[idx]
16
- }
17
-
18
- export function generateIdeTelemetryHeaders(sessionId, clientType = 'vscode') {
19
- const sid = sessionId || 'session-' + Math.random().toString(36).slice(2, 10)
20
- const machineHash = createHash('sha256').update(sid).digest('hex').slice(0, 32)
21
- return {
22
- 'vscode-sessionid': sid,
23
- 'vscode-machineid': machineHash,
24
- 'editor-version': clientType === 'cursor' ? 'cursor/0.45.6' : 'vscode/1.98.0',
25
- 'editor-plugin-version': 'dsh-sub-client/0.5.8',
26
- 'User-Agent': getRandomUserAgent(sid)
27
- }
28
- }
@@ -1,26 +0,0 @@
1
- export class TokenSpeedometer {
2
- constructor() {
3
- this.startTime = null
4
- this.tokenCount = 0
5
- }
6
-
7
- recordChunk(chunkText) {
8
- if (!this.startTime) this.startTime = Date.now()
9
- // Approximation: 1 token ~ 4 chars
10
- const tokens = Math.max(1, Math.ceil((chunkText || '').length / 4))
11
- this.tokenCount += tokens
12
- return this.getCurrentSpeed()
13
- }
14
-
15
- getCurrentSpeed() {
16
- if (!this.startTime) return 0
17
- const elapsedSec = (Date.now() - this.startTime) / 1000
18
- if (elapsedSec <= 0) return 0
19
- return Math.round((this.tokenCount / elapsedSec) * 10) / 10
20
- }
21
-
22
- reset() {
23
- this.startTime = null
24
- this.tokenCount = 0
25
- }
26
- }
@@ -1,27 +0,0 @@
1
- const SENSITIVE_PATTERNS = [
2
- /Bearer\s+([^\s,"'}]+)/gi,
3
- /(access_token|refresh_token|token|api_key|secret|password|authorization)=([^&\s]+)/gi,
4
- /("?(?:accessToken|refreshToken|apiKey|secretKey|token)"?\s*:\s*")([^"]+)(")/gi
5
- ]
6
-
7
- export function sanitizeLogText(text) {
8
- if (typeof text !== 'string') {
9
- try {
10
- text = JSON.stringify(text)
11
- } catch {
12
- return '[Unserializable]'
13
- }
14
- }
15
-
16
- let sanitized = text
17
- // 1. Bearer
18
- sanitized = sanitized.replace(/Bearer\s+([^\s,"'}]+)/gi, 'Bearer ***MASKED***')
19
-
20
- // 2. Query param style
21
- sanitized = sanitized.replace(/(access_token|refresh_token|token|api_key|secret|password|authorization)=([^&\s]+)/gi, '$1=***MASKED***')
22
-
23
- // 3. JSON key style
24
- sanitized = sanitized.replace(/("?(?:accessToken|refreshToken|apiKey|secretKey|token)"?\s*:\s*")([^"]+)(")/gi, '$1***MASKED***$3')
25
-
26
- return sanitized
27
- }