@mdrv/opencode-quota 262.0.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 (133) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +189 -0
  3. package/bin/copilot-quota.ts +374 -0
  4. package/bin/glm-quota.ts +467 -0
  5. package/bin/install.js +439 -0
  6. package/bin/kimi-quota.ts +314 -0
  7. package/dist/bin/copilot-quota.d.ts +8 -0
  8. package/dist/bin/copilot-quota.d.ts.map +1 -0
  9. package/dist/bin/copilot-quota.js +298 -0
  10. package/dist/bin/copilot-quota.js.map +1 -0
  11. package/dist/bin/glm-quota.d.ts +8 -0
  12. package/dist/bin/glm-quota.d.ts.map +1 -0
  13. package/dist/bin/glm-quota.js +367 -0
  14. package/dist/bin/glm-quota.js.map +1 -0
  15. package/dist/bin/kimi-quota.d.ts +3 -0
  16. package/dist/bin/kimi-quota.d.ts.map +1 -0
  17. package/dist/bin/kimi-quota.js +241 -0
  18. package/dist/bin/kimi-quota.js.map +1 -0
  19. package/dist/src/api/client.d.ts +76 -0
  20. package/dist/src/api/client.d.ts.map +1 -0
  21. package/dist/src/api/client.js +203 -0
  22. package/dist/src/api/client.js.map +1 -0
  23. package/dist/src/api/endpoints.d.ts +22 -0
  24. package/dist/src/api/endpoints.d.ts.map +1 -0
  25. package/dist/src/api/endpoints.js +41 -0
  26. package/dist/src/api/endpoints.js.map +1 -0
  27. package/dist/src/api/platforms.d.ts +20 -0
  28. package/dist/src/api/platforms.d.ts.map +1 -0
  29. package/dist/src/api/platforms.js +38 -0
  30. package/dist/src/api/platforms.js.map +1 -0
  31. package/dist/src/index.d.ts +10 -0
  32. package/dist/src/index.d.ts.map +1 -0
  33. package/dist/src/index.js +723 -0
  34. package/dist/src/index.js.map +1 -0
  35. package/dist/src/shared/logging.d.ts +7 -0
  36. package/dist/src/shared/logging.d.ts.map +1 -0
  37. package/dist/src/shared/logging.js +29 -0
  38. package/dist/src/shared/logging.js.map +1 -0
  39. package/dist/src/utils/box-constants.d.ts +43 -0
  40. package/dist/src/utils/box-constants.d.ts.map +1 -0
  41. package/dist/src/utils/box-constants.js +43 -0
  42. package/dist/src/utils/box-constants.js.map +1 -0
  43. package/dist/src/utils/date-formatter.d.ts +17 -0
  44. package/dist/src/utils/date-formatter.d.ts.map +1 -0
  45. package/dist/src/utils/date-formatter.js +33 -0
  46. package/dist/src/utils/date-formatter.js.map +1 -0
  47. package/dist/src/utils/error-formatter.d.ts +17 -0
  48. package/dist/src/utils/error-formatter.d.ts.map +1 -0
  49. package/dist/src/utils/error-formatter.js +60 -0
  50. package/dist/src/utils/error-formatter.js.map +1 -0
  51. package/dist/src/utils/progress-bar.d.ts +35 -0
  52. package/dist/src/utils/progress-bar.d.ts.map +1 -0
  53. package/dist/src/utils/progress-bar.js +43 -0
  54. package/dist/src/utils/progress-bar.js.map +1 -0
  55. package/dist/src/utils/reset-timer.d.ts +11 -0
  56. package/dist/src/utils/reset-timer.d.ts.map +1 -0
  57. package/dist/src/utils/reset-timer.js +32 -0
  58. package/dist/src/utils/reset-timer.js.map +1 -0
  59. package/dist/src/utils/time-window.d.ts +30 -0
  60. package/dist/src/utils/time-window.d.ts.map +1 -0
  61. package/dist/src/utils/time-window.js +34 -0
  62. package/dist/src/utils/time-window.js.map +1 -0
  63. package/dist/tests/error-handling/api-errors.test.d.ts +7 -0
  64. package/dist/tests/error-handling/api-errors.test.d.ts.map +1 -0
  65. package/dist/tests/error-handling/api-errors.test.js +110 -0
  66. package/dist/tests/error-handling/api-errors.test.js.map +1 -0
  67. package/dist/tests/error-handling/auth-errors.test.d.ts +7 -0
  68. package/dist/tests/error-handling/auth-errors.test.d.ts.map +1 -0
  69. package/dist/tests/error-handling/auth-errors.test.js +110 -0
  70. package/dist/tests/error-handling/auth-errors.test.js.map +1 -0
  71. package/dist/tests/error-handling/network-errors.test.d.ts +7 -0
  72. package/dist/tests/error-handling/network-errors.test.d.ts.map +1 -0
  73. package/dist/tests/error-handling/network-errors.test.js +94 -0
  74. package/dist/tests/error-handling/network-errors.test.js.map +1 -0
  75. package/dist/tests/error-handling/parse-errors.test.d.ts +7 -0
  76. package/dist/tests/error-handling/parse-errors.test.d.ts.map +1 -0
  77. package/dist/tests/error-handling/parse-errors.test.js +87 -0
  78. package/dist/tests/error-handling/parse-errors.test.js.map +1 -0
  79. package/dist/tests/error-handling/token-sanitization.test.d.ts +2 -0
  80. package/dist/tests/error-handling/token-sanitization.test.d.ts.map +1 -0
  81. package/dist/tests/error-handling/token-sanitization.test.js +59 -0
  82. package/dist/tests/error-handling/token-sanitization.test.js.map +1 -0
  83. package/dist/tests/functional/date-formatter.test.d.ts +5 -0
  84. package/dist/tests/functional/date-formatter.test.d.ts.map +1 -0
  85. package/dist/tests/functional/date-formatter.test.js +46 -0
  86. package/dist/tests/functional/date-formatter.test.js.map +1 -0
  87. package/dist/tests/functional/progress-bar.test.d.ts +5 -0
  88. package/dist/tests/functional/progress-bar.test.d.ts.map +1 -0
  89. package/dist/tests/functional/progress-bar.test.js +82 -0
  90. package/dist/tests/functional/progress-bar.test.js.map +1 -0
  91. package/dist/tests/functional/reset-timer.test.d.ts +6 -0
  92. package/dist/tests/functional/reset-timer.test.d.ts.map +1 -0
  93. package/dist/tests/functional/reset-timer.test.js +67 -0
  94. package/dist/tests/functional/reset-timer.test.js.map +1 -0
  95. package/dist/tests/functional/time-window.test.d.ts +5 -0
  96. package/dist/tests/functional/time-window.test.d.ts.map +1 -0
  97. package/dist/tests/functional/time-window.test.js +46 -0
  98. package/dist/tests/functional/time-window.test.js.map +1 -0
  99. package/dist/tests/integration/box-alignment.test.d.ts +8 -0
  100. package/dist/tests/integration/box-alignment.test.d.ts.map +1 -0
  101. package/dist/tests/integration/box-alignment.test.js +238 -0
  102. package/dist/tests/integration/box-alignment.test.js.map +1 -0
  103. package/dist/tests/integration/error-handling.test.d.ts +2 -0
  104. package/dist/tests/integration/error-handling.test.d.ts.map +1 -0
  105. package/dist/tests/integration/error-handling.test.js +36 -0
  106. package/dist/tests/integration/error-handling.test.js.map +1 -0
  107. package/dist/tests/integration/installer-config.test.d.ts +2 -0
  108. package/dist/tests/integration/installer-config.test.d.ts.map +1 -0
  109. package/dist/tests/integration/installer-config.test.js +65 -0
  110. package/dist/tests/integration/installer-config.test.js.map +1 -0
  111. package/dist/tests/integration/plugin-catch-block.test.d.ts +2 -0
  112. package/dist/tests/integration/plugin-catch-block.test.d.ts.map +1 -0
  113. package/dist/tests/integration/plugin-catch-block.test.js +134 -0
  114. package/dist/tests/integration/plugin-catch-block.test.js.map +1 -0
  115. package/dist/tests/integration/reset-time-display.test.d.ts +6 -0
  116. package/dist/tests/integration/reset-time-display.test.d.ts.map +1 -0
  117. package/dist/tests/integration/reset-time-display.test.js +138 -0
  118. package/dist/tests/integration/reset-time-display.test.js.map +1 -0
  119. package/dist/tests/module/http-client.test.d.ts +2 -0
  120. package/dist/tests/module/http-client.test.d.ts.map +1 -0
  121. package/dist/tests/module/http-client.test.js +49 -0
  122. package/dist/tests/module/http-client.test.js.map +1 -0
  123. package/dist/tests/module/platform-detection.test.d.ts +5 -0
  124. package/dist/tests/module/platform-detection.test.d.ts.map +1 -0
  125. package/dist/tests/module/platform-detection.test.js +48 -0
  126. package/dist/tests/module/platform-detection.test.js.map +1 -0
  127. package/integration/agents/copilot-quota-exec.md +20 -0
  128. package/integration/agents/glm-quota-exec.md +20 -0
  129. package/integration/command/copilot_quota.md +6 -0
  130. package/integration/command/glm_quota.md +6 -0
  131. package/integration/skills/copilot-quota/SKILL.md +11 -0
  132. package/integration/skills/glm-quota/SKILL.md +11 -0
  133. package/package.json +69 -0
@@ -0,0 +1,467 @@
1
+ #!/usr/bin/env -S bun
2
+
3
+ /**
4
+ * GLM quota CLI tool
5
+ * Fetches and displays Z.AI GLM Coding Plan usage statistics from command line
6
+ * Credentials are fetched from OpenCode's auth.json
7
+ */
8
+
9
+ import * as https from 'https'
10
+ import * as os from 'os'
11
+ import * as path from 'path'
12
+ import { configureLogging, getLogger } from '../src/shared/logging.js'
13
+
14
+ const COMMAND_NAME = 'opencode-glm-quota'
15
+ const AUTH_JSON_PATH = path.join(os.homedir(), '.local', 'share', 'opencode', 'auth.json')
16
+
17
+ /**
18
+ * Type definitions
19
+ */
20
+ type Platform = 'ZAI' | 'ZHIPU'
21
+ type Provider = 'zai' | 'zai-coding-plan' | 'zhipu'
22
+
23
+ interface Credentials {
24
+ token: string
25
+ platform: Platform
26
+ }
27
+
28
+ type AuthJson = Record<string, { key?: string; token?: string }>
29
+
30
+ // API Response wrapper
31
+ interface ApiResponse<T = unknown> {
32
+ code: number
33
+ msg: string
34
+ data: T
35
+ success: boolean
36
+ }
37
+
38
+ // Quota limit types
39
+ interface UsageDetail {
40
+ modelCode: string
41
+ usage: number
42
+ }
43
+
44
+ interface QuotaLimit {
45
+ type: 'TIME_LIMIT' | 'TOKENS_LIMIT'
46
+ unit: number
47
+ number: number
48
+ usage?: number
49
+ currentValue?: number
50
+ remaining?: number
51
+ percentage: number
52
+ nextResetTime: number
53
+ usageDetails?: UsageDetail[]
54
+ }
55
+
56
+ interface QuotaData {
57
+ limits: QuotaLimit[]
58
+ level: string
59
+ }
60
+
61
+ interface ZaiQuotaResponse extends ApiResponse<QuotaData> {}
62
+
63
+ // Global logger, assigned after configureLogging()
64
+ let logger: ReturnType<typeof getLogger>
65
+
66
+ /**
67
+ * Format Unix timestamp to readable date
68
+ */
69
+ function formatTimestamp(timestamp: number): string {
70
+ const date = new Date(timestamp)
71
+ return date.toLocaleString('en-US', {
72
+ month: 'short',
73
+ day: 'numeric',
74
+ hour: '2-digit',
75
+ minute: '2-digit',
76
+ hour12: true,
77
+ })
78
+ }
79
+
80
+ /**
81
+ * Calculate duration from now to a timestamp
82
+ * @param timestamp - Unix timestamp in milliseconds
83
+ * @returns Human-readable duration (e.g., "7d 4h 32m 15s", "2h 57m 31s", "5m")
84
+ */
85
+ function calculateDuration(timestamp: number): string {
86
+ const now = new Date().getTime()
87
+ const diffMs = timestamp - now
88
+
89
+ // If timestamp is in the past, show "0d 0h 0m 0s"
90
+ if (diffMs <= 0) {
91
+ return '0d 0h 0m 0s'
92
+ }
93
+
94
+ const diffDays = Math.floor(diffMs / (1000 * 60 * 60 * 24))
95
+ const diffHours = Math.floor((diffMs % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60))
96
+ const diffMinutes = Math.floor((diffMs % (1000 * 60 * 60)) / (1000 * 60))
97
+ const diffSeconds = Math.floor((diffMs % (1000 * 60)) / 1000)
98
+
99
+ // Build duration string, hiding zero values
100
+ const parts: string[] = []
101
+ if (diffDays > 0) parts.push(`${diffDays}d`)
102
+ if (diffHours > 0) parts.push(`${diffHours}h`)
103
+ if (diffMinutes > 0) parts.push(`${diffMinutes}m`)
104
+ if (diffSeconds > 0) parts.push(`${diffSeconds}s`)
105
+
106
+ return parts.length > 0 ? parts.join(' ') : '0s'
107
+ }
108
+
109
+ /**
110
+ * Parse and format quota limit data
111
+ */
112
+ function parseQuotaLimit(response: string): { parsed: ZaiQuotaResponse; formatted: string } {
113
+ logger.debug('Parsing quota limit response')
114
+ const parsed = JSON.parse(response) as ZaiQuotaResponse
115
+ logger.debug(`API response code: ${parsed.code}, msg: ${parsed.msg}`)
116
+
117
+ if (!parsed.success || parsed.code !== 200) {
118
+ throw new Error(`API error: ${parsed.msg}`)
119
+ }
120
+
121
+ const { limits, level } = parsed.data
122
+ logger.debug(`Plan level: ${level}, limits: ${limits.length}`)
123
+
124
+ let output = `Plan Level: ${level}\n`
125
+
126
+ for (const limit of limits) {
127
+ logger.debug(`Processing limit type: ${limit.type}`)
128
+ const timeWindow = limit.type === 'TIME_LIMIT' ? '1-month window' : '5-hour rolling'
129
+ output += `\n${limit.type} (${timeWindow}):\n`
130
+ output += ` Used: ${limit.percentage}%\n`
131
+
132
+ if (limit.type === 'TIME_LIMIT' && limit.usageDetails) {
133
+ output += ` Tool Usage:\n`
134
+ for (const detail of limit.usageDetails) {
135
+ logger.debug(`Tool ${detail.modelCode}: ${detail.usage}`)
136
+ output += ` ${detail.modelCode}: ${detail.usage}\n`
137
+ }
138
+ }
139
+
140
+ if (limit.type === 'TOKENS_LIMIT' && limit.usage !== undefined && limit.remaining !== undefined) {
141
+ output += ` Tokens: ${limit.usage} / ${limit.usage + limit.remaining}\n`
142
+ }
143
+
144
+ const resetTime = formatTimestamp(limit.nextResetTime)
145
+ const duration = calculateDuration(limit.nextResetTime)
146
+ logger.debug(`Next reset: ${resetTime} (${duration})`)
147
+ output += ` Next Reset: ${resetTime} (${duration})\n`
148
+ }
149
+
150
+ logger.info('Quota limit data parsed successfully')
151
+ return { parsed, formatted: output }
152
+ }
153
+
154
+ /**
155
+ * Parse generic API response
156
+ */
157
+ function parseResponse<T>(response: string, endpointName: string): T {
158
+ logger.debug(`Parsing ${endpointName} response`)
159
+ const parsed = JSON.parse(response) as ApiResponse<T>
160
+
161
+ if (!parsed.success || parsed.code !== 200) {
162
+ throw new Error(`API error from ${endpointName}: ${parsed.msg}`)
163
+ }
164
+
165
+ logger.debug(`${endpointName} parsed successfully`)
166
+ return parsed.data
167
+ }
168
+
169
+ /**
170
+ * Make HTTPS request with timeout
171
+ */
172
+ function makeRequest(
173
+ url: string,
174
+ token: string,
175
+ ): Promise<string> {
176
+ logger.debug(`Making request to ${url}`)
177
+ logger.debug(`Using token: ${token.slice(0, 8)}...${token.slice(-4)}`)
178
+
179
+ return new Promise((resolve, reject) => {
180
+ const timeout = 10000 // 10 seconds
181
+ const timer = setTimeout(() => {
182
+ req.destroy()
183
+ logger.error(`Request timeout for ${url}`)
184
+ reject(new Error('Request timeout'))
185
+ }, timeout)
186
+
187
+ const urlObj = new URL(url)
188
+ const options = {
189
+ hostname: urlObj.hostname,
190
+ port: urlObj.port || 443,
191
+ path: urlObj.pathname + urlObj.search,
192
+ method: 'GET',
193
+ headers: {
194
+ 'Authorization': token,
195
+ 'Accept-Language': 'en-US,en',
196
+ 'Content-Type': 'application/json',
197
+ },
198
+ }
199
+
200
+ logger.debug(`Request options: ${
201
+ JSON.stringify({
202
+ hostname: options.hostname,
203
+ path: options.path,
204
+ method: options.method,
205
+ })
206
+ }`)
207
+
208
+ const req = https.request(options, (res) => {
209
+ let data = ''
210
+
211
+ logger.debug(`Response status: ${res.statusCode} ${res.statusMessage}`)
212
+
213
+ res.on('data', chunk => {
214
+ data += chunk
215
+ })
216
+
217
+ res.on('end', () => {
218
+ clearTimeout(timer)
219
+ if (res.statusCode && res.statusCode >= 200 && res.statusCode < 300) {
220
+ logger.debug(`Response received: ${data.slice(0, 100)}... (${data.length} bytes)`)
221
+ resolve(data)
222
+ } else {
223
+ logger.error(`HTTP error: ${res.statusCode} ${res.statusMessage}`)
224
+ logger.debug(`Error response: ${data}`)
225
+ reject(new Error(`HTTP ${res.statusCode}: ${res.statusMessage}`))
226
+ }
227
+ })
228
+ })
229
+
230
+ req.on('error', (err) => {
231
+ clearTimeout(timer)
232
+ logger.error(`Request error: ${err.message}`)
233
+ reject(err)
234
+ })
235
+
236
+ req.end()
237
+ })
238
+ }
239
+
240
+ /**
241
+ * Get credentials from OpenCode auth.json
242
+ */
243
+ function getCredentials(): Credentials | null {
244
+ logger.debug(`Looking for credentials at ${AUTH_JSON_PATH}`)
245
+
246
+ try {
247
+ const fs = require('fs')
248
+ if (!fs.existsSync(AUTH_JSON_PATH)) {
249
+ logger.debug('auth.json does not exist')
250
+ return null
251
+ }
252
+
253
+ const content = fs.readFileSync(AUTH_JSON_PATH, 'utf-8')
254
+ const auth = JSON.parse(content) as AuthJson
255
+
256
+ logger.debug(`auth.json loaded with ${Object.keys(auth).length} providers`)
257
+
258
+ const providers: Record<string, Provider> = {
259
+ 'zai-coding-plan': 'zai',
260
+ 'zai': 'zai',
261
+ 'zhipu': 'zhipu',
262
+ }
263
+
264
+ for (const [providerId, platform] of Object.entries(providers)) {
265
+ const provider = auth[providerId]
266
+ const token = provider?.key ?? provider?.token
267
+ if (token) {
268
+ const credentials: Credentials = {
269
+ token,
270
+ platform: platform === 'zhipu' ? 'ZHIPU' : 'ZAI',
271
+ }
272
+ logger.info(`Found credentials for provider: ${providerId}`)
273
+ logger.debug(`Platform: ${credentials.platform}`)
274
+ return credentials
275
+ }
276
+ }
277
+
278
+ logger.debug('No credentials found for any provider')
279
+ return null
280
+ } catch (error) {
281
+ logger.error(`Error reading auth.json: ${error}`)
282
+ return null
283
+ }
284
+ }
285
+
286
+ /**
287
+ * Display help message
288
+ */
289
+ function showHelp() {
290
+ console.log(`
291
+ ${COMMAND_NAME} - Check Z.AI GLM Coding Plan quota from terminal
292
+
293
+ USAGE:
294
+ bun run bin/glm-quota.ts [--raw|--pretty] [--debug]
295
+
296
+ OPTIONS:
297
+ --raw Output raw JSON (default: pretty output)
298
+ --pretty Explicit pretty output (same as default)
299
+ --debug, -d Enable debug logging (logs hidden by default)
300
+ --help, -h Display this help message
301
+
302
+ AUTHENTICATION:
303
+ Reads credentials from ~/.local/share/opencode/auth.json
304
+ Provider: zai-coding-plan, zai, or zhipu
305
+
306
+ EXAMPLES:
307
+ bun run bin/glm-quota.ts
308
+ bun run bin/glm-quota.ts --debug
309
+ bun run bin/glm-quota.ts --raw
310
+ `)
311
+ }
312
+
313
+ /**
314
+ * Main CLI entry point
315
+ */
316
+ async function main() {
317
+ const args = process.argv.slice(2)
318
+
319
+ // Check for help flag first (before logging config)
320
+ if (args.includes('--help') || args.includes('-h')) {
321
+ showHelp()
322
+ process.exit(0)
323
+ }
324
+
325
+ // Check for debug flag before configuring logging
326
+ const debugMode = args.includes('--debug') || args.includes('-d')
327
+
328
+ // Only configure logging when --debug is present
329
+ if (debugMode) {
330
+ await configureLogging('debug')
331
+ }
332
+
333
+ // Create logger after configureLogging (or with no-op if not configured)
334
+ if (debugMode) {
335
+ logger = getLogger(['opencode-quota', 'cli', 'glm'])
336
+ } else {
337
+ // No-op logger when debug mode is off
338
+ logger = {
339
+ debug: () => {},
340
+ info: () => {},
341
+ warn: () => {},
342
+ error: () => {},
343
+ } as unknown as ReturnType<typeof getLogger>
344
+ }
345
+
346
+ logger.info(`${COMMAND_NAME} starting...`)
347
+
348
+ const rawOutput = args.includes('--raw')
349
+ // --pretty is accepted but same as default (explicitly allow it for completeness)
350
+
351
+ logger.debug(`Arguments: ${args.join(' ')}`)
352
+ logger.debug(`Raw output: ${rawOutput}`)
353
+
354
+ try {
355
+ // Get credentials from OpenCode auth.json
356
+ const credentials = getCredentials()
357
+
358
+ if (!credentials) {
359
+ logger.error('No credentials found')
360
+ console.error(`\n${COMMAND_NAME}: Not configured.\nPlease connect to Z.AI in OpenCode first.\n`)
361
+ process.exit(1)
362
+ }
363
+
364
+ // Query API
365
+ const endpoints = {
366
+ ZAI: {
367
+ modelUsage: 'https://api.z.ai/api/monitor/usage/model-usage',
368
+ toolUsage: 'https://api.z.ai/api/monitor/usage/tool-usage',
369
+ quotaLimit: 'https://api.z.ai/api/monitor/usage/quota/limit',
370
+ },
371
+ ZHIPU: {
372
+ modelUsage: 'https://open.bigmodel.cn/api/monitor/usage/model-usage',
373
+ toolUsage: 'https://open.bigmodel.cn/api/monitor/usage/tool-usage',
374
+ quotaLimit: 'https://open.bigmodel.cn/api/monitor/usage/quota/limit',
375
+ },
376
+ }
377
+
378
+ const urls = endpoints[credentials.platform]
379
+
380
+ logger.info(`Querying ${credentials.platform} endpoints...`)
381
+
382
+ // Make requests
383
+ const responses = await Promise.all([
384
+ makeRequest(urls.quotaLimit, credentials.token),
385
+ makeRequest(urls.modelUsage, credentials.token),
386
+ makeRequest(urls.toolUsage, credentials.token),
387
+ ])
388
+
389
+ logger.info(`Received ${responses.length} responses`)
390
+
391
+ if (!rawOutput) {
392
+ // Parse and display formatted output
393
+ const [quotaLimitRaw, modelUsageRaw, toolUsageRaw] = responses
394
+
395
+ if (quotaLimitRaw.length === 0) {
396
+ logger.error('Quota limit endpoint returned empty response')
397
+ console.error(`\n${COMMAND_NAME}: No quota data received.\n`)
398
+ process.exit(1)
399
+ }
400
+
401
+ // Parse and display quota limit
402
+ try {
403
+ const { formatted } = parseQuotaLimit(quotaLimitRaw)
404
+ console.log(formatted)
405
+ } catch (error) {
406
+ logger.error(`Failed to parse quota limit: ${error}`)
407
+ throw error
408
+ }
409
+
410
+ // Parse model usage if available
411
+ if (modelUsageRaw.length > 0) {
412
+ try {
413
+ const modelUsage = parseResponse<unknown>(modelUsageRaw, 'model-usage')
414
+ logger.info('Model usage data received')
415
+ console.log('\nModel Usage:')
416
+ console.log(JSON.stringify(modelUsage, null, 2))
417
+ } catch (error) {
418
+ logger.warn(`Failed to parse model usage: ${error}`)
419
+ }
420
+ }
421
+
422
+ // Parse tool usage if available
423
+ if (toolUsageRaw.length > 0) {
424
+ try {
425
+ const toolUsage = parseResponse<unknown>(toolUsageRaw, 'tool-usage')
426
+ logger.info('Tool usage data received')
427
+ console.log('\nTool Usage:')
428
+ console.log(JSON.stringify(toolUsage, null, 2))
429
+ } catch (error) {
430
+ logger.warn(`Failed to parse tool usage: ${error}`)
431
+ }
432
+ }
433
+ } else {
434
+ // Print raw responses (one per line), skip empty ones
435
+ const endpointNames = ['quota/limit', 'model-usage', 'tool-usage']
436
+ let hasOutput = false
437
+
438
+ responses.forEach((response, index) => {
439
+ if (response.length > 0) {
440
+ console.log(response)
441
+ hasOutput = true
442
+ } else {
443
+ logger.warn(`Endpoint ${endpointNames[index]} returned empty response (0 bytes)`)
444
+ }
445
+ })
446
+
447
+ if (!hasOutput) {
448
+ console.error(`\n${COMMAND_NAME}: No data received from any endpoint.\n`)
449
+ process.exit(1)
450
+ }
451
+ }
452
+
453
+ logger.info('Completed successfully')
454
+ } catch (error) {
455
+ if (error instanceof Error) {
456
+ logger.error(`${error.name}: ${error.message}`)
457
+ console.error(`\n${COMMAND_NAME}: ${error.message}\n`)
458
+ } else {
459
+ logger.error(`Unknown error: ${String(error)}`)
460
+ console.error(`\n${COMMAND_NAME}: Unknown error occurred\n`)
461
+ }
462
+ process.exit(1)
463
+ }
464
+ }
465
+
466
+ // Run CLI
467
+ main()