@goodandready/dsh-subscriptions 0.5.25 → 0.5.30
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/config-schema.js +80 -0
- package/lib/index.js +28 -1042
- package/lib/routes.js +1016 -0
- package/lib/sse.js +39 -9
- package/lib/ui-i18n.js +256 -0
- package/lib/ui-styles.js +119 -0
- package/package.json +1 -1
- package/lib/coalesce-stream.js +0 -22
- package/lib/cookie-bridge.js +0 -8
- package/lib/credit-detect.js +0 -22
- package/lib/encrypted-bundle.js +0 -46
- package/lib/latency-heatmap.js +0 -26
- package/lib/latency-score.js +0 -36
- package/lib/multi-tenant.js +0 -16
- package/lib/network-benchmark.js +0 -23
- package/lib/pkce-store.js +0 -28
- package/lib/plan.js +0 -42
- package/lib/prompt-cache-warmer.js +0 -44
- package/lib/reconnect-stream.js +0 -21
- package/lib/reset-toast.js +0 -38
- package/lib/savings-calculator.js +0 -14
- package/lib/state-recovery.js +0 -33
- package/lib/telemetry.js +0 -28
- package/lib/token-speedometer.js +0 -26
- package/lib/zero-trace-logger.js +0 -27
package/lib/multi-tenant.js
DELETED
|
@@ -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
|
-
}
|
package/lib/network-benchmark.js
DELETED
|
@@ -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,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
|
-
}
|
package/lib/reconnect-stream.js
DELETED
|
@@ -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
|
-
}
|
package/lib/reset-toast.js
DELETED
|
@@ -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
|
-
}
|
package/lib/state-recovery.js
DELETED
|
@@ -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
|
-
}
|
package/lib/token-speedometer.js
DELETED
|
@@ -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
|
-
}
|
package/lib/zero-trace-logger.js
DELETED
|
@@ -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
|
-
}
|