discord-agent-bridge 0.1.0

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 (201) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +195 -0
  3. package/dist/app.js +210 -0
  4. package/dist/app.js.map +1 -0
  5. package/dist/app.test.js +117 -0
  6. package/dist/app.test.js.map +1 -0
  7. package/dist/cli.js +63 -0
  8. package/dist/cli.js.map +1 -0
  9. package/dist/cli.test.js +91 -0
  10. package/dist/cli.test.js.map +1 -0
  11. package/dist/core/auditLog.js +65 -0
  12. package/dist/core/auditLog.js.map +1 -0
  13. package/dist/core/auditLog.test.js +124 -0
  14. package/dist/core/auditLog.test.js.map +1 -0
  15. package/dist/core/auth.js +106 -0
  16. package/dist/core/auth.js.map +1 -0
  17. package/dist/core/auth.test.js +216 -0
  18. package/dist/core/auth.test.js.map +1 -0
  19. package/dist/core/channelRegistry.js +119 -0
  20. package/dist/core/channelRegistry.js.map +1 -0
  21. package/dist/core/channelRegistry.test.js +114 -0
  22. package/dist/core/channelRegistry.test.js.map +1 -0
  23. package/dist/core/commandPolicy.js +6 -0
  24. package/dist/core/commandPolicy.js.map +1 -0
  25. package/dist/core/commandRouter.js +9 -0
  26. package/dist/core/commandRouter.js.map +1 -0
  27. package/dist/core/config.js +141 -0
  28. package/dist/core/config.js.map +1 -0
  29. package/dist/core/config.test.js +149 -0
  30. package/dist/core/config.test.js.map +1 -0
  31. package/dist/core/configResolver.js +90 -0
  32. package/dist/core/configResolver.js.map +1 -0
  33. package/dist/core/configResolver.test.js +138 -0
  34. package/dist/core/configResolver.test.js.map +1 -0
  35. package/dist/core/configSchema.js +164 -0
  36. package/dist/core/configSchema.js.map +1 -0
  37. package/dist/core/contracts.js +5 -0
  38. package/dist/core/contracts.js.map +1 -0
  39. package/dist/core/eventBus.js +42 -0
  40. package/dist/core/eventBus.js.map +1 -0
  41. package/dist/core/eventBus.test.js +72 -0
  42. package/dist/core/eventBus.test.js.map +1 -0
  43. package/dist/core/hookBridge.js +11 -0
  44. package/dist/core/hookBridge.js.map +1 -0
  45. package/dist/core/logger.js +116 -0
  46. package/dist/core/logger.js.map +1 -0
  47. package/dist/core/logger.test.js +88 -0
  48. package/dist/core/logger.test.js.map +1 -0
  49. package/dist/core/modeRegistry.js +33 -0
  50. package/dist/core/modeRegistry.js.map +1 -0
  51. package/dist/core/permissionResolver.js +43 -0
  52. package/dist/core/permissionResolver.js.map +1 -0
  53. package/dist/core/permissionResolver.test.js +136 -0
  54. package/dist/core/permissionResolver.test.js.map +1 -0
  55. package/dist/core/providerCatalog.js +240 -0
  56. package/dist/core/providerCatalog.js.map +1 -0
  57. package/dist/core/providerCatalog.test.js +230 -0
  58. package/dist/core/providerCatalog.test.js.map +1 -0
  59. package/dist/core/sessionOrchestrator.js +463 -0
  60. package/dist/core/sessionOrchestrator.js.map +1 -0
  61. package/dist/core/sessionOrchestrator.test.js +344 -0
  62. package/dist/core/sessionOrchestrator.test.js.map +1 -0
  63. package/dist/core/state/schema.js +49 -0
  64. package/dist/core/state/schema.js.map +1 -0
  65. package/dist/core/state/store.js +86 -0
  66. package/dist/core/state/store.js.map +1 -0
  67. package/dist/core/state/store.test.js +99 -0
  68. package/dist/core/state/store.test.js.map +1 -0
  69. package/dist/core/usageService.js +310 -0
  70. package/dist/core/usageService.js.map +1 -0
  71. package/dist/core/usageService.test.js +234 -0
  72. package/dist/core/usageService.test.js.map +1 -0
  73. package/dist/discord/alwaysAllow.test.js +181 -0
  74. package/dist/discord/alwaysAllow.test.js.map +1 -0
  75. package/dist/discord/client.js +666 -0
  76. package/dist/discord/client.js.map +1 -0
  77. package/dist/discord/client.test.js +256 -0
  78. package/dist/discord/client.test.js.map +1 -0
  79. package/dist/discord/configPanel.js +270 -0
  80. package/dist/discord/configPanel.js.map +1 -0
  81. package/dist/discord/configPanel.test.js +332 -0
  82. package/dist/discord/configPanel.test.js.map +1 -0
  83. package/dist/discord/directoryBrowser.js +151 -0
  84. package/dist/discord/directoryBrowser.js.map +1 -0
  85. package/dist/discord/directoryBrowser.test.js +163 -0
  86. package/dist/discord/directoryBrowser.test.js.map +1 -0
  87. package/dist/discord/favorites.js +10 -0
  88. package/dist/discord/favorites.js.map +1 -0
  89. package/dist/discord/fileDownload.js +94 -0
  90. package/dist/discord/fileDownload.js.map +1 -0
  91. package/dist/discord/fileDownload.test.js +59 -0
  92. package/dist/discord/fileDownload.test.js.map +1 -0
  93. package/dist/discord/format.js +104 -0
  94. package/dist/discord/format.js.map +1 -0
  95. package/dist/discord/format.test.js +40 -0
  96. package/dist/discord/format.test.js.map +1 -0
  97. package/dist/discord/guildChannels.js +97 -0
  98. package/dist/discord/guildChannels.js.map +1 -0
  99. package/dist/discord/guildChannels.test.js +231 -0
  100. package/dist/discord/guildChannels.test.js.map +1 -0
  101. package/dist/discord/i18n.js +207 -0
  102. package/dist/discord/i18n.js.map +1 -0
  103. package/dist/discord/i18n.test.js +31 -0
  104. package/dist/discord/i18n.test.js.map +1 -0
  105. package/dist/discord/interactionRouter.js +954 -0
  106. package/dist/discord/interactionRouter.js.map +1 -0
  107. package/dist/discord/interactionRouter.test.js +1245 -0
  108. package/dist/discord/interactionRouter.test.js.map +1 -0
  109. package/dist/discord/messageRouter.js +221 -0
  110. package/dist/discord/messageRouter.js.map +1 -0
  111. package/dist/discord/messageRouter.test.js +360 -0
  112. package/dist/discord/messageRouter.test.js.map +1 -0
  113. package/dist/discord/ports.js +12 -0
  114. package/dist/discord/ports.js.map +1 -0
  115. package/dist/discord/renderers/defaultSet.test.js +97 -0
  116. package/dist/discord/renderers/defaultSet.test.js.map +1 -0
  117. package/dist/discord/renderers/diffView.js +73 -0
  118. package/dist/discord/renderers/diffView.js.map +1 -0
  119. package/dist/discord/renderers/diffView.test.js +54 -0
  120. package/dist/discord/renderers/diffView.test.js.map +1 -0
  121. package/dist/discord/renderers/index.js +143 -0
  122. package/dist/discord/renderers/index.js.map +1 -0
  123. package/dist/discord/renderers/index.test.js +124 -0
  124. package/dist/discord/renderers/index.test.js.map +1 -0
  125. package/dist/discord/renderers/mentionOnComplete.js +19 -0
  126. package/dist/discord/renderers/mentionOnComplete.js.map +1 -0
  127. package/dist/discord/renderers/mentionOnComplete.test.js +35 -0
  128. package/dist/discord/renderers/mentionOnComplete.test.js.map +1 -0
  129. package/dist/discord/renderers/permissionButtons.js +150 -0
  130. package/dist/discord/renderers/permissionButtons.js.map +1 -0
  131. package/dist/discord/renderers/permissionButtons.test.js +108 -0
  132. package/dist/discord/renderers/permissionButtons.test.js.map +1 -0
  133. package/dist/discord/renderers/resultLine.js +25 -0
  134. package/dist/discord/renderers/resultLine.js.map +1 -0
  135. package/dist/discord/renderers/statusEmbed.js +20 -0
  136. package/dist/discord/renderers/statusEmbed.js.map +1 -0
  137. package/dist/discord/renderers/statusEmbed.test.js +32 -0
  138. package/dist/discord/renderers/statusEmbed.test.js.map +1 -0
  139. package/dist/discord/renderers/streamEmbed.js +134 -0
  140. package/dist/discord/renderers/streamEmbed.js.map +1 -0
  141. package/dist/discord/renderers/streamEmbed.test.js +97 -0
  142. package/dist/discord/renderers/streamEmbed.test.js.map +1 -0
  143. package/dist/discord/renderers/toolThread.js +65 -0
  144. package/dist/discord/renderers/toolThread.js.map +1 -0
  145. package/dist/discord/renderers/toolThread.test.js +54 -0
  146. package/dist/discord/renderers/toolThread.test.js.map +1 -0
  147. package/dist/discord/renderers/transcriptFeed.js +33 -0
  148. package/dist/discord/renderers/transcriptFeed.js.map +1 -0
  149. package/dist/discord/renderers/usageEmbed.js +78 -0
  150. package/dist/discord/renderers/usageEmbed.js.map +1 -0
  151. package/dist/discord/renderers/usageEmbed.test.js +42 -0
  152. package/dist/discord/renderers/usageEmbed.test.js.map +1 -0
  153. package/dist/discord/wiring.js +214 -0
  154. package/dist/discord/wiring.js.map +1 -0
  155. package/dist/discord/wiring.test.js +252 -0
  156. package/dist/discord/wiring.test.js.map +1 -0
  157. package/dist/discord/wizard/channelWizard.js +305 -0
  158. package/dist/discord/wizard/channelWizard.js.map +1 -0
  159. package/dist/discord/wizard/channelWizard.test.js +298 -0
  160. package/dist/discord/wizard/channelWizard.test.js.map +1 -0
  161. package/dist/discord/wizard/resumeWizard.js +123 -0
  162. package/dist/discord/wizard/resumeWizard.js.map +1 -0
  163. package/dist/modes/claude/index.js +80 -0
  164. package/dist/modes/claude/index.js.map +1 -0
  165. package/dist/modes/claude/listResumable.test.js +72 -0
  166. package/dist/modes/claude/listResumable.test.js.map +1 -0
  167. package/dist/modes/claude/mcpFileTool.js +82 -0
  168. package/dist/modes/claude/mcpFileTool.js.map +1 -0
  169. package/dist/modes/claude/permissions.js +35 -0
  170. package/dist/modes/claude/permissions.js.map +1 -0
  171. package/dist/modes/claude/plugins.js +56 -0
  172. package/dist/modes/claude/plugins.js.map +1 -0
  173. package/dist/modes/claude/session.js +324 -0
  174. package/dist/modes/claude/session.js.map +1 -0
  175. package/dist/modes/claude/session.test.js +415 -0
  176. package/dist/modes/claude/session.test.js.map +1 -0
  177. package/dist/modes/codex/discovery.js +221 -0
  178. package/dist/modes/codex/discovery.js.map +1 -0
  179. package/dist/modes/codex/discovery.test.js +213 -0
  180. package/dist/modes/codex/discovery.test.js.map +1 -0
  181. package/dist/modes/codex/eventMapper.js +262 -0
  182. package/dist/modes/codex/eventMapper.js.map +1 -0
  183. package/dist/modes/codex/index.js +132 -0
  184. package/dist/modes/codex/index.js.map +1 -0
  185. package/dist/modes/codex/index.test.js +170 -0
  186. package/dist/modes/codex/index.test.js.map +1 -0
  187. package/dist/modes/codex/runner.js +207 -0
  188. package/dist/modes/codex/runner.js.map +1 -0
  189. package/dist/modes/codex/runner.test.js +587 -0
  190. package/dist/modes/codex/runner.test.js.map +1 -0
  191. package/dist/modes/codex/sqliteReader.js +171 -0
  192. package/dist/modes/codex/sqliteReader.js.map +1 -0
  193. package/dist/modes/codex/sqliteReader.test.js +137 -0
  194. package/dist/modes/codex/sqliteReader.test.js.map +1 -0
  195. package/dist/setup/wizard.js +115 -0
  196. package/dist/setup/wizard.js.map +1 -0
  197. package/dist/setup/wizard.test.js +216 -0
  198. package/dist/setup/wizard.test.js.map +1 -0
  199. package/dist/startBot.test.js +78 -0
  200. package/dist/startBot.test.js.map +1 -0
  201. package/package.json +58 -0
@@ -0,0 +1,310 @@
1
+ import * as fs from 'node:fs';
2
+ import * as path from 'node:path';
3
+ import * as os from 'node:os';
4
+ import { z } from 'zod';
5
+ // Claude usage/limits poller (§7.4). Polls the undocumented Anthropic OAuth usage
6
+ // endpoint for 5-hour + weekly limits. Grounded in A4D src/sessions/usageTracker.ts
7
+ // (endpoint, fields, auth, 429 backoff) and adapted to our injectable style +
8
+ // redacting logger. Framework-agnostic (no discord.js). Context usage is emitted
9
+ // separately by Claude mode as a `context_usage` AgentEvent — NOT handled here.
10
+ //
11
+ // Requires OAuth subscription login (~/.claude/.credentials.json), not an API key.
12
+ // API-key-only users degrade gracefully: the service reports unavailable and the
13
+ // Discord usage embed is simply hidden. This service NEVER throws into callers.
14
+ //
15
+ // Codex usage is UNSUPPORTED (Codex CLI exposes no limits) — see codexUsageUnavailable().
16
+ // ---------------------------------------------------------------------------
17
+ // Constants (copied verbatim from A4D usageTracker.ts, plus the UA hardening)
18
+ // ---------------------------------------------------------------------------
19
+ const OAUTH_TOKEN_URL = 'https://platform.claude.com/v1/oauth/token';
20
+ const USAGE_API_URL = 'https://api.anthropic.com/api/oauth/usage';
21
+ const CLIENT_ID = '9d1c250a-e61b-44d9-88ed-5944d1962f5e';
22
+ const OAUTH_BETA_HEADER = 'oauth-2025-04-20';
23
+ // Refresh a token that is expired or expiring within this window (A4D uses 5 min).
24
+ const REFRESH_SKEW_MS = 300_000;
25
+ // 429 backoff: double the effective cache TTL on each throttle, up to this cap.
26
+ const BACKOFF_MULTIPLIER = 2;
27
+ const MAX_BACKOFF_MS = 600_000; // 10 min
28
+ // ---------------------------------------------------------------------------
29
+ // Wire schemas (zod-validate the endpoint response; tolerate missing/null fields)
30
+ // ---------------------------------------------------------------------------
31
+ const rateLimitSchema = z
32
+ .object({
33
+ utilization: z.number().nullable().optional(),
34
+ resets_at: z.string().nullable().optional(),
35
+ })
36
+ .nullable()
37
+ .optional();
38
+ const usageResponseSchema = z.object({
39
+ five_hour: rateLimitSchema,
40
+ seven_day: rateLimitSchema,
41
+ seven_day_opus: rateLimitSchema,
42
+ seven_day_sonnet: rateLimitSchema,
43
+ // extra_usage is tolerated but not surfaced in the snapshot (renderer concern).
44
+ extra_usage: z.unknown().optional(),
45
+ });
46
+ const credentialsSchema = z.object({
47
+ claudeAiOauth: z
48
+ .object({
49
+ accessToken: z.string().optional(),
50
+ refreshToken: z.string().optional(),
51
+ expiresAt: z.number().optional(),
52
+ })
53
+ .optional(),
54
+ });
55
+ const tokenResponseSchema = z.object({
56
+ access_token: z.string(),
57
+ refresh_token: z.string().optional(),
58
+ expires_in: z.number().optional(),
59
+ });
60
+ function defaultCredentialsPath() {
61
+ return path.join(os.homedir(), '.claude', '.credentials.json');
62
+ }
63
+ // ---------------------------------------------------------------------------
64
+ // Service
65
+ // ---------------------------------------------------------------------------
66
+ export class UsageService {
67
+ logger;
68
+ userAgent;
69
+ cacheMs;
70
+ fetchFn;
71
+ credentialsPath;
72
+ now;
73
+ cached = null;
74
+ lastFetchAttemptAt = 0;
75
+ // Effective min interval between fetches; grows on 429 backoff, resets on success.
76
+ currentIntervalMs;
77
+ constructor(options) {
78
+ this.logger = options.logger;
79
+ this.userAgent = `claude-code/${options.userAgentVersion ?? 'unknown'}`;
80
+ this.cacheMs = Math.max(0, (options.cacheSec ?? 180)) * 1000;
81
+ this.fetchFn = options.fetchFn ?? fetch;
82
+ this.credentialsPath = options.credentialsPath ?? defaultCredentialsPath();
83
+ this.now = options.now ?? Date.now;
84
+ this.currentIntervalMs = this.cacheMs;
85
+ }
86
+ // True when subscription OAuth credentials are present and readable. API-key-only
87
+ // users (no credentials file) report false so the usage embed is hidden.
88
+ isAvailable() {
89
+ return this.readCredentials() !== null;
90
+ }
91
+ // Serve the cache within TTL; otherwise fetch. NEVER throws — on any failure the
92
+ // last-good snapshot (if any) is returned, else an unavailable result.
93
+ async getUsage() {
94
+ const creds = this.readCredentials();
95
+ if (!creds) {
96
+ return { available: false, reason: 'no-credentials' };
97
+ }
98
+ // Serve cache while within the effective interval (base TTL, or a backed-off one).
99
+ const nowMs = this.now();
100
+ if (this.cached && nowMs - this.cached.fetchedAt < this.currentIntervalMs) {
101
+ return this.cached;
102
+ }
103
+ this.lastFetchAttemptAt = nowMs;
104
+ const snapshot = await this.fetchUsage(creds);
105
+ if (snapshot)
106
+ return snapshot;
107
+ // Fetch failed/backed-off: serve last-good if we have one.
108
+ if (this.cached)
109
+ return this.cached;
110
+ return { available: false, reason: 'no-credentials' };
111
+ }
112
+ // ---- OAuth credentials -------------------------------------------------
113
+ readCredentials() {
114
+ let raw;
115
+ try {
116
+ raw = fs.readFileSync(this.credentialsPath, 'utf-8');
117
+ }
118
+ catch {
119
+ // Absent/unreadable — API-key-only user. Not an error condition.
120
+ return null;
121
+ }
122
+ let parsed;
123
+ try {
124
+ parsed = JSON.parse(raw);
125
+ }
126
+ catch {
127
+ this.logger.warn('usage credentials file is not valid JSON; treating as unavailable');
128
+ return null;
129
+ }
130
+ const result = credentialsSchema.safeParse(parsed);
131
+ if (!result.success) {
132
+ this.logger.warn('usage credentials file has an unexpected shape; treating as unavailable');
133
+ return null;
134
+ }
135
+ const oauth = result.data.claudeAiOauth;
136
+ if (!oauth?.accessToken || !oauth.refreshToken)
137
+ return null;
138
+ return {
139
+ accessToken: oauth.accessToken,
140
+ refreshToken: oauth.refreshToken,
141
+ expiresAt: oauth.expiresAt ?? 0,
142
+ };
143
+ }
144
+ writeCredentials(creds) {
145
+ try {
146
+ let data = {};
147
+ try {
148
+ const existing = JSON.parse(fs.readFileSync(this.credentialsPath, 'utf-8'));
149
+ if (existing && typeof existing === 'object' && !Array.isArray(existing)) {
150
+ data = existing;
151
+ }
152
+ }
153
+ catch {
154
+ // Fresh/unreadable file — start from an empty object.
155
+ }
156
+ const prevOauth = data.claudeAiOauth && typeof data.claudeAiOauth === 'object'
157
+ ? data.claudeAiOauth
158
+ : {};
159
+ data.claudeAiOauth = {
160
+ ...prevOauth,
161
+ accessToken: creds.accessToken,
162
+ refreshToken: creds.refreshToken,
163
+ expiresAt: creds.expiresAt,
164
+ };
165
+ fs.writeFileSync(this.credentialsPath, JSON.stringify(data), { encoding: 'utf-8', mode: 0o600 });
166
+ }
167
+ catch (err) {
168
+ // Persisting the refreshed token is best-effort; a failure just means we
169
+ // refresh again next cycle. Do not surface the token in the log.
170
+ this.logger.warn('failed to persist refreshed usage credentials', { error: String(err) });
171
+ }
172
+ }
173
+ async refreshAccessToken(refreshToken) {
174
+ let res;
175
+ try {
176
+ res = await this.fetchFn(OAUTH_TOKEN_URL, {
177
+ method: 'POST',
178
+ headers: {
179
+ 'Content-Type': 'application/json',
180
+ 'User-Agent': this.userAgent,
181
+ },
182
+ body: JSON.stringify({
183
+ grant_type: 'refresh_token',
184
+ refresh_token: refreshToken,
185
+ client_id: CLIENT_ID,
186
+ }),
187
+ });
188
+ }
189
+ catch (err) {
190
+ this.logger.warn('usage token refresh request failed', { error: String(err) });
191
+ return null;
192
+ }
193
+ if (!res.ok) {
194
+ this.logger.warn('usage token refresh returned a non-OK status', { status: res.status });
195
+ return null;
196
+ }
197
+ let body;
198
+ try {
199
+ body = await res.json();
200
+ }
201
+ catch (err) {
202
+ this.logger.warn('usage token refresh returned invalid JSON', { error: String(err) });
203
+ return null;
204
+ }
205
+ const result = tokenResponseSchema.safeParse(body);
206
+ if (!result.success) {
207
+ this.logger.warn('usage token refresh response has an unexpected shape');
208
+ return null;
209
+ }
210
+ const creds = {
211
+ accessToken: result.data.access_token,
212
+ refreshToken: result.data.refresh_token ?? refreshToken,
213
+ expiresAt: this.now() + (result.data.expires_in ?? 3600) * 1000,
214
+ };
215
+ this.writeCredentials(creds);
216
+ return creds;
217
+ }
218
+ async getValidToken(creds) {
219
+ if (creds.expiresAt < this.now() + REFRESH_SKEW_MS) {
220
+ const refreshed = await this.refreshAccessToken(creds.refreshToken);
221
+ if (!refreshed)
222
+ return null;
223
+ return refreshed.accessToken;
224
+ }
225
+ return creds.accessToken;
226
+ }
227
+ // ---- Usage endpoint ----------------------------------------------------
228
+ async fetchUsage(creds) {
229
+ const token = await this.getValidToken(creds);
230
+ if (!token)
231
+ return null;
232
+ let res;
233
+ try {
234
+ res = await this.fetchFn(USAGE_API_URL, {
235
+ headers: {
236
+ Authorization: `Bearer ${token}`,
237
+ 'anthropic-beta': OAUTH_BETA_HEADER,
238
+ 'Content-Type': 'application/json',
239
+ 'User-Agent': this.userAgent,
240
+ },
241
+ });
242
+ }
243
+ catch (err) {
244
+ this.logger.warn('usage fetch request failed', { error: String(err) });
245
+ return null;
246
+ }
247
+ if (res.status === 429) {
248
+ this.currentIntervalMs = Math.min(this.currentIntervalMs * BACKOFF_MULTIPLIER, MAX_BACKOFF_MS);
249
+ this.logger.warn('usage endpoint rate-limited; backing off', {
250
+ nextIntervalSec: Math.round(this.currentIntervalMs / 1000),
251
+ });
252
+ return null;
253
+ }
254
+ if (!res.ok) {
255
+ this.logger.warn('usage endpoint returned a non-OK status', { status: res.status });
256
+ return null;
257
+ }
258
+ let body;
259
+ try {
260
+ body = await res.json();
261
+ }
262
+ catch (err) {
263
+ this.logger.warn('usage endpoint returned invalid JSON', { error: String(err) });
264
+ return null;
265
+ }
266
+ const parsed = usageResponseSchema.safeParse(body);
267
+ if (!parsed.success) {
268
+ this.logger.warn('usage endpoint response has an unexpected shape');
269
+ return null;
270
+ }
271
+ // Success — reset backoff.
272
+ this.currentIntervalMs = this.cacheMs;
273
+ const snapshot = this.toSnapshot(parsed.data);
274
+ this.cached = snapshot;
275
+ return snapshot;
276
+ }
277
+ toSnapshot(data) {
278
+ const snapshot = { fetchedAt: this.now() };
279
+ const fiveHour = toLimit(data.five_hour);
280
+ if (fiveHour)
281
+ snapshot.fiveHour = fiveHour;
282
+ const sevenDay = toLimit(data.seven_day);
283
+ if (sevenDay)
284
+ snapshot.sevenDay = sevenDay;
285
+ const opus = toLimit(data.seven_day_opus);
286
+ if (opus)
287
+ snapshot.sevenDayOpus = opus;
288
+ const sonnet = toLimit(data.seven_day_sonnet);
289
+ if (sonnet)
290
+ snapshot.sevenDaySonnet = sonnet;
291
+ return snapshot;
292
+ }
293
+ }
294
+ // A rate-limit block maps to a UsageLimit only when utilization is present; a null
295
+ // utilization means "no data for this window" and is omitted from the snapshot.
296
+ function toLimit(raw) {
297
+ if (!raw || raw.utilization == null)
298
+ return undefined;
299
+ const limit = { utilization: raw.utilization };
300
+ if (raw.resets_at != null)
301
+ limit.resetsAt = raw.resets_at;
302
+ return limit;
303
+ }
304
+ // Codex usage is structurally unsupported (Codex CLI exposes no limits and no
305
+ // context maximum — §7.4). The Discord layer shows the "usage/limits unavailable
306
+ // (Codex CLI limitation)" status line instead of the usage embed.
307
+ export function codexUsageUnavailable() {
308
+ return { available: false, reason: 'codex-unsupported' };
309
+ }
310
+ //# sourceMappingURL=usageService.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"usageService.js","sourceRoot":"","sources":["../../src/core/usageService.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,234 @@
1
+ import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
2
+ import * as fs from 'node:fs';
3
+ import * as path from 'node:path';
4
+ import * as os from 'node:os';
5
+ import { UsageService, codexUsageUnavailable } from './usageService.js';
6
+ import { createLogger } from './logger.js';
7
+ // ---------------------------------------------------------------------------
8
+ // Test helpers — all HTTP and FS are mocked; nothing touches real ~/.claude or
9
+ // the network. Secret-shaped fixtures are zero-entropy sentinels assembled here.
10
+ // ---------------------------------------------------------------------------
11
+ // Zero-entropy sentinel tokens (no realistic secret literals in source).
12
+ const ACCESS_TOKEN = 'A'.repeat(24);
13
+ const REFRESH_TOKEN = 'R'.repeat(24);
14
+ const NEW_ACCESS_TOKEN = 'B'.repeat(24);
15
+ function captureSink() {
16
+ const lines = [];
17
+ return {
18
+ lines,
19
+ sink: {
20
+ write(_level, line) {
21
+ lines.push(line);
22
+ },
23
+ },
24
+ };
25
+ }
26
+ // A fetch double that dispatches by URL and returns queued Response-likes.
27
+ function jsonResponse(status, body) {
28
+ return {
29
+ ok: status >= 200 && status < 300,
30
+ status,
31
+ json: async () => body,
32
+ };
33
+ }
34
+ const USAGE_URL = 'https://api.anthropic.com/api/oauth/usage';
35
+ const TOKEN_URL = 'https://platform.claude.com/v1/oauth/token';
36
+ // The mocks are typed via `typeof fetch` at the injection site, so their inferred
37
+ // call tuples vary; read them positionally through this untyped view.
38
+ function calls(fetchFn) {
39
+ return fetchFn.mock.calls;
40
+ }
41
+ function usageBody(overrides = {}) {
42
+ return {
43
+ five_hour: { utilization: 42, resets_at: '2026-07-01T12:00:00Z' },
44
+ seven_day: { utilization: 10, resets_at: '2026-07-07T00:00:00Z' },
45
+ seven_day_opus: { utilization: 55, resets_at: null },
46
+ seven_day_sonnet: { utilization: null, resets_at: null },
47
+ ...overrides,
48
+ };
49
+ }
50
+ describe('UsageService', () => {
51
+ let dir;
52
+ let credsPath;
53
+ let clock;
54
+ const now = () => clock;
55
+ function writeCreds(expiresAt) {
56
+ fs.writeFileSync(credsPath, JSON.stringify({ claudeAiOauth: { accessToken: ACCESS_TOKEN, refreshToken: REFRESH_TOKEN, expiresAt } }), 'utf-8');
57
+ }
58
+ beforeEach(() => {
59
+ dir = fs.mkdtempSync(path.join(os.tmpdir(), 'dab-usage-'));
60
+ credsPath = path.join(dir, '.credentials.json');
61
+ clock = 1_000_000; // fixed base epoch (ms)
62
+ });
63
+ afterEach(() => {
64
+ fs.rmSync(dir, { recursive: true, force: true });
65
+ });
66
+ it('parses a mocked usage response into a snapshot (5h / weekly / per-model)', async () => {
67
+ writeCreds(clock + 3_600_000); // token valid for an hour
68
+ const fetchFn = vi.fn(async () => jsonResponse(200, usageBody()));
69
+ const svc = new UsageService({
70
+ logger: createLogger('usage', { sink: captureSink().sink }),
71
+ fetchFn: fetchFn,
72
+ credentialsPath: credsPath,
73
+ now,
74
+ });
75
+ const result = await svc.getUsage();
76
+ expect('available' in result).toBe(false);
77
+ const snap = result;
78
+ expect(snap.fiveHour).toEqual({ utilization: 42, resetsAt: '2026-07-01T12:00:00Z' });
79
+ expect(snap.sevenDay).toEqual({ utilization: 10, resetsAt: '2026-07-07T00:00:00Z' });
80
+ // resets_at:null → resetsAt omitted, utilization still present.
81
+ expect(snap.sevenDayOpus).toEqual({ utilization: 55 });
82
+ // utilization:null → window omitted entirely.
83
+ expect(snap.sevenDaySonnet).toBeUndefined();
84
+ expect(snap.fetchedAt).toBe(clock);
85
+ // Only the usage endpoint was hit (token still valid — no refresh).
86
+ expect(fetchFn).toHaveBeenCalledTimes(1);
87
+ expect(calls(fetchFn)[0][0]).toBe(USAGE_URL);
88
+ });
89
+ it('sends the User-Agent, Authorization and anthropic-beta headers on the usage call', async () => {
90
+ writeCreds(clock + 3_600_000);
91
+ const fetchFn = vi.fn(async () => jsonResponse(200, usageBody()));
92
+ const svc = new UsageService({
93
+ logger: createLogger('usage', { sink: captureSink().sink }),
94
+ fetchFn: fetchFn,
95
+ credentialsPath: credsPath,
96
+ userAgentVersion: '1.2.3',
97
+ now,
98
+ });
99
+ await svc.getUsage();
100
+ const init = calls(fetchFn)[0][1];
101
+ const headers = init.headers;
102
+ expect(headers['User-Agent']).toBe('claude-code/1.2.3');
103
+ expect(headers['anthropic-beta']).toBe('oauth-2025-04-20');
104
+ expect(headers.Authorization).toBe(`Bearer ${ACCESS_TOKEN}`);
105
+ });
106
+ it('serves cache within TTL (a second call within cacheSec does not refetch)', async () => {
107
+ writeCreds(clock + 3_600_000);
108
+ const fetchFn = vi.fn(async () => jsonResponse(200, usageBody()));
109
+ const svc = new UsageService({
110
+ logger: createLogger('usage', { sink: captureSink().sink }),
111
+ fetchFn: fetchFn,
112
+ credentialsPath: credsPath,
113
+ cacheSec: 180,
114
+ now,
115
+ });
116
+ await svc.getUsage();
117
+ clock += 179_000; // still within 180s
118
+ const second = await svc.getUsage();
119
+ expect(fetchFn).toHaveBeenCalledTimes(1); // no refetch
120
+ expect(second.fiveHour?.utilization).toBe(42);
121
+ // After the TTL elapses, it refetches.
122
+ clock += 2_000; // now 181s past the first fetch
123
+ await svc.getUsage();
124
+ expect(fetchFn).toHaveBeenCalledTimes(2);
125
+ });
126
+ it('on HTTP 429 backs off, serves last-good cache, and does not throw', async () => {
127
+ writeCreds(clock + 3_600_000);
128
+ const fetchFn = vi
129
+ .fn()
130
+ .mockResolvedValueOnce(jsonResponse(200, usageBody())) // seed cache
131
+ .mockResolvedValueOnce(jsonResponse(429, {})); // throttle
132
+ const { lines, sink } = captureSink();
133
+ const svc = new UsageService({
134
+ logger: createLogger('usage', { sink }),
135
+ fetchFn: fetchFn,
136
+ credentialsPath: credsPath,
137
+ cacheSec: 180,
138
+ now,
139
+ });
140
+ const first = await svc.getUsage();
141
+ expect(first.fiveHour?.utilization).toBe(42);
142
+ clock += 181_000; // TTL elapsed → refetch attempt → 429
143
+ const second = await svc.getUsage();
144
+ // Last-good cache is served, no throw.
145
+ expect(second.fiveHour?.utilization).toBe(42);
146
+ expect(fetchFn).toHaveBeenCalledTimes(2);
147
+ expect(lines.some((l) => l.includes('rate-limited'))).toBe(true);
148
+ // Backoff doubled the effective interval to 360s (measured from the last good
149
+ // fetch at t=0). At t=+300s we are past the normal 180s TTL but still inside the
150
+ // backed-off 360s window, so the service must serve cache and NOT refetch.
151
+ clock += 119_000; // now t = +300s (181s + 119s)
152
+ const third = await svc.getUsage();
153
+ expect(third.fiveHour?.utilization).toBe(42);
154
+ expect(fetchFn).toHaveBeenCalledTimes(2);
155
+ });
156
+ it('no credentials file → isAvailable() false, getUsage() unavailable, no fetch attempted', async () => {
157
+ const fetchFn = vi.fn();
158
+ const svc = new UsageService({
159
+ logger: createLogger('usage', { sink: captureSink().sink }),
160
+ fetchFn: fetchFn,
161
+ credentialsPath: credsPath, // never created
162
+ now,
163
+ });
164
+ expect(svc.isAvailable()).toBe(false);
165
+ const result = await svc.getUsage();
166
+ expect(result).toEqual({ available: false, reason: 'no-credentials' });
167
+ expect(fetchFn).not.toHaveBeenCalled();
168
+ });
169
+ it('refreshes an expired token, then calls the usage endpoint', async () => {
170
+ writeCreds(clock - 1_000); // already expired
171
+ const fetchFn = vi.fn(async (url) => {
172
+ if (url === TOKEN_URL) {
173
+ return jsonResponse(200, { access_token: NEW_ACCESS_TOKEN, refresh_token: REFRESH_TOKEN, expires_in: 3600 });
174
+ }
175
+ return jsonResponse(200, usageBody());
176
+ });
177
+ const svc = new UsageService({
178
+ logger: createLogger('usage', { sink: captureSink().sink }),
179
+ fetchFn: fetchFn,
180
+ credentialsPath: credsPath,
181
+ now,
182
+ });
183
+ const result = await svc.getUsage();
184
+ expect('available' in result).toBe(false);
185
+ // Refresh endpoint hit first, then usage — with the NEW token.
186
+ expect(fetchFn).toHaveBeenCalledTimes(2);
187
+ expect(calls(fetchFn)[0][0]).toBe(TOKEN_URL);
188
+ expect(calls(fetchFn)[1][0]).toBe(USAGE_URL);
189
+ const usageInit = calls(fetchFn)[1][1];
190
+ expect(usageInit.headers.Authorization).toBe(`Bearer ${NEW_ACCESS_TOKEN}`);
191
+ // The refreshed token was persisted back to the credentials file.
192
+ const persisted = JSON.parse(fs.readFileSync(credsPath, 'utf-8'));
193
+ expect(persisted.claudeAiOauth.accessToken).toBe(NEW_ACCESS_TOKEN);
194
+ });
195
+ it('never leaks the access or refresh token into logged output', async () => {
196
+ writeCreds(clock - 1_000); // force a refresh so both tokens are in play
197
+ const fetchFn = vi.fn(async (url) => {
198
+ if (url === TOKEN_URL) {
199
+ return jsonResponse(200, { access_token: NEW_ACCESS_TOKEN, expires_in: 3600 });
200
+ }
201
+ // Return a 500 so the fetch path also logs a status warning.
202
+ return jsonResponse(500, {});
203
+ });
204
+ const { lines, sink } = captureSink();
205
+ const svc = new UsageService({
206
+ logger: createLogger('usage', { level: 'debug', sink }),
207
+ fetchFn: fetchFn,
208
+ credentialsPath: credsPath,
209
+ now,
210
+ });
211
+ await svc.getUsage();
212
+ const joined = lines.join('\n');
213
+ expect(joined).not.toContain(ACCESS_TOKEN);
214
+ expect(joined).not.toContain(REFRESH_TOKEN);
215
+ expect(joined).not.toContain(NEW_ACCESS_TOKEN);
216
+ });
217
+ it('degrades gracefully on a malformed credentials file (no throw, unavailable)', async () => {
218
+ fs.writeFileSync(credsPath, '{ not valid json', 'utf-8');
219
+ const fetchFn = vi.fn();
220
+ const svc = new UsageService({
221
+ logger: createLogger('usage', { sink: captureSink().sink }),
222
+ fetchFn: fetchFn,
223
+ credentialsPath: credsPath,
224
+ now,
225
+ });
226
+ expect(svc.isAvailable()).toBe(false);
227
+ await expect(svc.getUsage()).resolves.toEqual({ available: false, reason: 'no-credentials' });
228
+ expect(fetchFn).not.toHaveBeenCalled();
229
+ });
230
+ it('codexUsageUnavailable() returns the typed codex-unsupported result', () => {
231
+ expect(codexUsageUnavailable()).toEqual({ available: false, reason: 'codex-unsupported' });
232
+ });
233
+ });
234
+ //# sourceMappingURL=usageService.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"usageService.test.js","sourceRoot":"","sources":["../../src/core/usageService.test.ts"],"names":[],"mappings":"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"}