@goodandready/dsh-subscriptions 0.6.72 → 0.6.83

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 (49) hide show
  1. package/README.md +8 -0
  2. package/README.ru.md +8 -0
  3. package/README.zh.md +8 -0
  4. package/lib/accounts-view.js +97 -5
  5. package/lib/accounts.js +137 -11
  6. package/lib/adapter.js +3 -0
  7. package/lib/alerts.js +35 -0
  8. package/lib/analytics.js +31 -2
  9. package/lib/billing.js +99 -0
  10. package/lib/blob.js +22 -0
  11. package/lib/capabilities.js +208 -0
  12. package/lib/client.js +344 -17
  13. package/lib/config-schema.js +50 -4
  14. package/lib/diagnostic-wizard.js +2 -5
  15. package/lib/diagnostics.js +2 -2
  16. package/lib/error-buffer.js +2 -2
  17. package/lib/forecast.js +56 -0
  18. package/lib/headless-auth.js +392 -0
  19. package/lib/history.js +36 -0
  20. package/lib/http.js +67 -4
  21. package/lib/import-auth.js +3 -6
  22. package/lib/index.js +99 -11
  23. package/lib/jwt.js +84 -17
  24. package/lib/loopback.js +3 -2
  25. package/lib/mask.js +16 -0
  26. package/lib/oauth-pending.js +12 -0
  27. package/lib/pricing.js +89 -0
  28. package/lib/refs.js +2 -0
  29. package/lib/reset-clock.js +55 -0
  30. package/lib/rotate.js +18 -4
  31. package/lib/routes/accounts.js +142 -7
  32. package/lib/routes/oauth.js +51 -2
  33. package/lib/routes/proxy.js +14 -0
  34. package/lib/scope-inspector.js +51 -0
  35. package/lib/stream-rotate.js +31 -10
  36. package/lib/subscriptions.js +24 -1
  37. package/lib/updater.js +21 -0
  38. package/lib/vault-sync.js +414 -0
  39. package/lib/vault.js +1 -1
  40. package/lib/vendor-factory.js +6 -2
  41. package/lib/vendors/antigravity.js +13 -4
  42. package/lib/vendors/claude.js +56 -8
  43. package/lib/vendors/codex.js +34 -6
  44. package/lib/vendors/copilot-cli.js +36 -5
  45. package/lib/vendors/ernie.js +9 -2
  46. package/lib/vendors/grok.js +66 -6
  47. package/lib/vendors/jetbrains.js +136 -13
  48. package/lib/vendors/perplexity.js +151 -17
  49. package/package.json +1 -1
package/lib/billing.js ADDED
@@ -0,0 +1,99 @@
1
+ // Subscription Billing Cycle Renewal Countdown & Payment Status Notifier (#558)
2
+ // Parses renewal dates, detects payment issues, formats countdowns and generates warnings.
3
+
4
+ const ONE_DAY_MS = 24 * 60 * 60 * 1000
5
+
6
+ /**
7
+ * Formats a renewal timestamp into a human-readable countdown string.
8
+ * @param {number|null} renewsAt
9
+ * @param {number} [nowMs]
10
+ * @returns {string}
11
+ */
12
+ export function formatRenewalCountdown(renewsAt, nowMs = Date.now()) {
13
+ const ts = Number(renewsAt)
14
+ if (!Number.isFinite(ts) || ts <= 0) return ''
15
+
16
+ const diffMs = ts - nowMs
17
+ if (diffMs <= 0) {
18
+ return 'Renewal overdue'
19
+ }
20
+
21
+ const days = Math.floor(diffMs / ONE_DAY_MS)
22
+ const d = new Date(ts)
23
+ const yyyy = d.getUTCFullYear()
24
+ const mm = String(d.getUTCMonth() + 1).padStart(2, '0')
25
+ const dd = String(d.getUTCDate()).padStart(2, '0')
26
+ const dateStr = `${yyyy}-${mm}-${dd}`
27
+
28
+ if (days === 0) {
29
+ return `Renews today (${dateStr})`
30
+ }
31
+ if (days === 1) {
32
+ return `Renews in 1 day (${dateStr})`
33
+ }
34
+ return `Renews in ${days} days (${dateStr})`
35
+ }
36
+
37
+ /**
38
+ * Extracts billing cycle metadata and evaluates payment status for a slot.
39
+ * @param {object} [blob]
40
+ * @param {object} [slot]
41
+ * @param {number} [nowMs]
42
+ * @returns {object}
43
+ */
44
+ export function extractBillingInfo(blob = {}, slot = {}, nowMs = Date.now()) {
45
+ const rawStatus = String(
46
+ blob?.subscriptionStatus ||
47
+ blob?.paymentStatus ||
48
+ blob?.planStatus ||
49
+ ''
50
+ ).toLowerCase().trim()
51
+
52
+ const rawRenew = (
53
+ blob?.billingCycleEnd ||
54
+ blob?.periodEnd ||
55
+ blob?.renewsAt ||
56
+ slot?.expiresAt ||
57
+ blob?.expiresAt ||
58
+ null
59
+ )
60
+
61
+ const renewsAt = Number.isFinite(Number(rawRenew)) && Number(rawRenew) > 0 ? Number(rawRenew) : null
62
+ const renewalCountdown = formatRenewalCountdown(renewsAt, nowMs)
63
+
64
+ let status = 'active'
65
+ let paymentFailed = false
66
+ let isOverdue = false
67
+ let badgeText = ''
68
+ let warning = ''
69
+
70
+ if (['past_due', 'unpaid', 'payment_failed', 'failed'].includes(rawStatus)) {
71
+ status = 'past_due'
72
+ paymentFailed = true
73
+ badgeText = 'Payment Failed'
74
+ warning = 'Payment failed or subscription past due - check provider billing'
75
+ } else if (['cancelled', 'canceled', 'revoked'].includes(rawStatus)) {
76
+ status = 'cancelled'
77
+ badgeText = 'Cancelled'
78
+ warning = 'Subscription has been cancelled with provider'
79
+ } else if (['trial_expired', 'expired'].includes(rawStatus)) {
80
+ status = 'trial_expired'
81
+ badgeText = 'Trial Expired'
82
+ warning = 'Subscription trial or plan has expired'
83
+ } else if (renewsAt && renewsAt < nowMs) {
84
+ status = 'past_due'
85
+ isOverdue = true
86
+ badgeText = 'Renewal Overdue'
87
+ warning = 'Subscription renewal date has passed'
88
+ }
89
+
90
+ return {
91
+ renewsAt,
92
+ renewalCountdown,
93
+ status,
94
+ isOverdue,
95
+ paymentFailed,
96
+ badgeText,
97
+ warning,
98
+ }
99
+ }
package/lib/blob.js CHANGED
@@ -45,6 +45,15 @@ export function serializeBlob(obj) {
45
45
  email: asString(obj.email),
46
46
  accountId: asString(obj.accountId),
47
47
  projectId: asString(obj.projectId),
48
+ selectedOrgId: asString(obj.selectedOrgId),
49
+ selectedOrgName: asString(obj.selectedOrgName),
50
+ ...(Array.isArray(obj.organizations) ? { organizations: obj.organizations } : {}),
51
+ ...(Array.isArray(obj.workspaces) ? { workspaces: obj.workspaces } : {}),
52
+ ...(obj.organizationId ? { organizationId: asString(obj.organizationId) } : {}),
53
+ ...(obj.plan ? { plan: asString(obj.plan) } : {}),
54
+ ...(obj.avatarUrl ? { avatarUrl: asString(obj.avatarUrl) } : {}),
55
+ ...(Array.isArray(obj.scopes) ? { scopes: obj.scopes.map(asString) } : {}),
56
+ ...(obj.scopeWarning ? { scopeWarning: asString(obj.scopeWarning) } : {}),
48
57
  ...(obj.clientId ? { clientId: asString(obj.clientId) } : {}),
49
58
  ...(obj.clientSecret ? { clientSecret: asString(obj.clientSecret) } : {}),
50
59
  ...(obj.idToken ? { idToken: asString(obj.idToken) } : {}),
@@ -65,6 +74,15 @@ export function parseBlob(text) {
65
74
  email: asString(obj.email),
66
75
  accountId: asString(obj.accountId),
67
76
  projectId: asString(obj.projectId),
77
+ selectedOrgId: asString(obj.selectedOrgId),
78
+ selectedOrgName: asString(obj.selectedOrgName),
79
+ ...(Array.isArray(obj.organizations) ? { organizations: obj.organizations } : {}),
80
+ ...(Array.isArray(obj.workspaces) ? { workspaces: obj.workspaces } : {}),
81
+ ...(obj.organizationId ? { organizationId: asString(obj.organizationId) } : {}),
82
+ ...(obj.plan ? { plan: asString(obj.plan) } : {}),
83
+ ...(obj.avatarUrl ? { avatarUrl: asString(obj.avatarUrl) } : {}),
84
+ ...(Array.isArray(obj.scopes) ? { scopes: obj.scopes.map(asString) } : {}),
85
+ ...(obj.scopeWarning ? { scopeWarning: asString(obj.scopeWarning) } : {}),
68
86
  ...(obj.clientId ? { clientId: asString(obj.clientId) } : {}),
69
87
  ...(obj.clientSecret ? { clientSecret: asString(obj.clientSecret) } : {}),
70
88
  ...(obj.idToken ? { idToken: asString(obj.idToken) } : {}),
@@ -72,3 +90,7 @@ export function parseBlob(text) {
72
90
  ...(obj.usageAt ? { usageAt: Number(obj.usageAt) } : {}),
73
91
  }
74
92
  }
93
+
94
+ export function validateCredentialBlob(blob) {
95
+ return parseBlob(serializeBlob(blob))
96
+ }
@@ -0,0 +1,208 @@
1
+ // Subscription Capability Matrix & Context Limit Inspector (#556)
2
+ // Determines per-account tier capabilities (context window, thinking mode, vision, tools)
3
+ // and dynamically adapts outgoing requests to prevent HTTP 400 Bad Request rejections.
4
+
5
+ export const PROVIDER_CAPABILITIES = {
6
+ codex: {
7
+ contextWindow: 128000,
8
+ contextLabel: '128K',
9
+ thinking: true,
10
+ thinkingType: 'low_med_high',
11
+ vision: true,
12
+ tools: true,
13
+ maxOutputTokens: 16384,
14
+ },
15
+ claude: {
16
+ contextWindow: 200000,
17
+ contextLabel: '200K',
18
+ thinking: true,
19
+ thinkingType: 'budget',
20
+ vision: true,
21
+ tools: true,
22
+ maxOutputTokens: 8192,
23
+ },
24
+ grok: {
25
+ contextWindow: 128000,
26
+ contextLabel: '128K',
27
+ thinking: true,
28
+ thinkingType: 'low_med_high',
29
+ vision: true,
30
+ tools: true,
31
+ maxOutputTokens: 8192,
32
+ },
33
+ antigravity: {
34
+ contextWindow: 1000000,
35
+ contextLabel: '1M',
36
+ thinking: true,
37
+ thinkingType: 'tiered',
38
+ vision: true,
39
+ tools: true,
40
+ maxOutputTokens: 8192,
41
+ },
42
+ copilot: {
43
+ contextWindow: 128000,
44
+ contextLabel: '128K',
45
+ thinking: false,
46
+ thinkingType: null,
47
+ vision: true,
48
+ tools: true,
49
+ maxOutputTokens: 4096,
50
+ },
51
+ kimi: {
52
+ contextWindow: 200000,
53
+ contextLabel: '200K',
54
+ thinking: true,
55
+ thinkingType: 'budget',
56
+ vision: false,
57
+ tools: true,
58
+ maxOutputTokens: 4096,
59
+ },
60
+ glm: {
61
+ contextWindow: 128000,
62
+ contextLabel: '128K',
63
+ thinking: false,
64
+ thinkingType: null,
65
+ vision: true,
66
+ tools: true,
67
+ maxOutputTokens: 4096,
68
+ },
69
+ cursor: {
70
+ contextWindow: 128000,
71
+ contextLabel: '128K',
72
+ thinking: true,
73
+ thinkingType: 'budget',
74
+ vision: true,
75
+ tools: true,
76
+ maxOutputTokens: 8192,
77
+ },
78
+ kiro: {
79
+ contextWindow: 128000,
80
+ contextLabel: '128K',
81
+ thinking: false,
82
+ thinkingType: null,
83
+ vision: false,
84
+ tools: true,
85
+ maxOutputTokens: 4096,
86
+ },
87
+ perplexity: {
88
+ contextWindow: 200000,
89
+ contextLabel: '200K',
90
+ thinking: true,
91
+ thinkingType: 'low_med_high',
92
+ vision: false,
93
+ tools: true,
94
+ maxOutputTokens: 8192,
95
+ },
96
+ jetbrains: {
97
+ contextWindow: 200000,
98
+ contextLabel: '200K',
99
+ thinking: true,
100
+ thinkingType: 'budget',
101
+ vision: true,
102
+ tools: true,
103
+ maxOutputTokens: 8192,
104
+ },
105
+ default: {
106
+ contextWindow: 128000,
107
+ contextLabel: '128K',
108
+ thinking: false,
109
+ thinkingType: null,
110
+ vision: true,
111
+ tools: true,
112
+ maxOutputTokens: 4096,
113
+ },
114
+ }
115
+
116
+ /**
117
+ * Inspects account capabilities based on provider, token blob metadata and plan.
118
+ * @param {string} provider
119
+ * @param {any} [blob]
120
+ * @param {any} [info]
121
+ * @returns {object}
122
+ */
123
+ export function inspectAccountCapabilities(provider, blob = {}, info = {}) {
124
+ const p = String(provider || '').toLowerCase()
125
+ const base = PROVIDER_CAPABILITIES[p] || PROVIDER_CAPABILITIES.default
126
+ const plan = String(blob?.plan || blob?.paidTierName || info?.plan || info?.paidTierName || '').toLowerCase()
127
+
128
+ let contextWindow = base.contextWindow
129
+ let contextLabel = base.contextLabel
130
+ let thinking = base.thinking
131
+ let thinkingType = base.thinkingType
132
+ let vision = base.vision
133
+ let tools = base.tools
134
+ let maxOutputTokens = base.maxOutputTokens
135
+
136
+ // Tier upgrades for Team / Enterprise / Ultra subscriptions
137
+ if (p === 'claude') {
138
+ if (plan.includes('team') || plan.includes('enterprise') || plan.includes('500k')) {
139
+ contextWindow = 500000
140
+ contextLabel = '500K'
141
+ }
142
+ } else if (p === 'antigravity') {
143
+ if (plan.includes('ultra') || plan.includes('enterprise') || plan.includes('2m')) {
144
+ contextWindow = 2000000
145
+ contextLabel = '2M'
146
+ }
147
+ } else if (p === 'grok') {
148
+ if (plan.includes('pro') || plan.includes('team') || plan.includes('1m')) {
149
+ contextWindow = 1000000
150
+ contextLabel = '1M'
151
+ }
152
+ }
153
+
154
+ const tags = []
155
+ tags.push(`${contextLabel} Context`)
156
+ if (thinking) {
157
+ tags.push(thinkingType === 'budget' ? 'Extended Thinking' : (thinkingType === 'tiered' ? 'Tiered Thinking' : 'Adaptive Thinking'))
158
+ }
159
+ if (vision) tags.push('Vision')
160
+ if (tools) tags.push('Tools')
161
+
162
+ return {
163
+ provider: p,
164
+ contextWindow,
165
+ contextLabel,
166
+ thinking,
167
+ thinkingType,
168
+ vision,
169
+ tools,
170
+ maxOutputTokens,
171
+ tags,
172
+ }
173
+ }
174
+
175
+ /**
176
+ * Normalizes an outgoing stream request according to account capabilities to avoid 400 errors.
177
+ * @param {any} options
178
+ * @param {any} capabilities
179
+ * @returns {object}
180
+ */
181
+ export function normalizeCapabilitiesRequest(options = {}, capabilities = {}) {
182
+ if (!options || typeof options !== 'object') return options
183
+ const out = { ...options }
184
+
185
+ // If upstream does not support tools, strip tool parameters
186
+ if (capabilities && capabilities.tools === false) {
187
+ if (out.tools) delete out.tools
188
+ if (out.tool_choice) delete out.tool_choice
189
+ }
190
+
191
+ // If upstream does not support reasoning/thinking, strip reasoning_effort
192
+ if (capabilities && capabilities.thinking === false) {
193
+ if (out.reasoning_effort) delete out.reasoning_effort
194
+ if (out.thinking) delete out.thinking
195
+ }
196
+
197
+ // Clamp maxTokens if exceeding maximum supported output tokens
198
+ if (capabilities && typeof capabilities.maxOutputTokens === 'number' && capabilities.maxOutputTokens > 0) {
199
+ if (typeof out.max_tokens === 'number' && out.max_tokens > capabilities.maxOutputTokens) {
200
+ out.max_tokens = capabilities.maxOutputTokens
201
+ }
202
+ if (typeof out.maxTokens === 'number' && out.maxTokens > capabilities.maxOutputTokens) {
203
+ out.maxTokens = capabilities.maxOutputTokens
204
+ }
205
+ }
206
+
207
+ return out
208
+ }