@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.
- package/LICENSE +21 -0
- package/README.md +189 -0
- package/bin/copilot-quota.ts +374 -0
- package/bin/glm-quota.ts +467 -0
- package/bin/install.js +439 -0
- package/bin/kimi-quota.ts +314 -0
- package/dist/bin/copilot-quota.d.ts +8 -0
- package/dist/bin/copilot-quota.d.ts.map +1 -0
- package/dist/bin/copilot-quota.js +298 -0
- package/dist/bin/copilot-quota.js.map +1 -0
- package/dist/bin/glm-quota.d.ts +8 -0
- package/dist/bin/glm-quota.d.ts.map +1 -0
- package/dist/bin/glm-quota.js +367 -0
- package/dist/bin/glm-quota.js.map +1 -0
- package/dist/bin/kimi-quota.d.ts +3 -0
- package/dist/bin/kimi-quota.d.ts.map +1 -0
- package/dist/bin/kimi-quota.js +241 -0
- package/dist/bin/kimi-quota.js.map +1 -0
- package/dist/src/api/client.d.ts +76 -0
- package/dist/src/api/client.d.ts.map +1 -0
- package/dist/src/api/client.js +203 -0
- package/dist/src/api/client.js.map +1 -0
- package/dist/src/api/endpoints.d.ts +22 -0
- package/dist/src/api/endpoints.d.ts.map +1 -0
- package/dist/src/api/endpoints.js +41 -0
- package/dist/src/api/endpoints.js.map +1 -0
- package/dist/src/api/platforms.d.ts +20 -0
- package/dist/src/api/platforms.d.ts.map +1 -0
- package/dist/src/api/platforms.js +38 -0
- package/dist/src/api/platforms.js.map +1 -0
- package/dist/src/index.d.ts +10 -0
- package/dist/src/index.d.ts.map +1 -0
- package/dist/src/index.js +723 -0
- package/dist/src/index.js.map +1 -0
- package/dist/src/shared/logging.d.ts +7 -0
- package/dist/src/shared/logging.d.ts.map +1 -0
- package/dist/src/shared/logging.js +29 -0
- package/dist/src/shared/logging.js.map +1 -0
- package/dist/src/utils/box-constants.d.ts +43 -0
- package/dist/src/utils/box-constants.d.ts.map +1 -0
- package/dist/src/utils/box-constants.js +43 -0
- package/dist/src/utils/box-constants.js.map +1 -0
- package/dist/src/utils/date-formatter.d.ts +17 -0
- package/dist/src/utils/date-formatter.d.ts.map +1 -0
- package/dist/src/utils/date-formatter.js +33 -0
- package/dist/src/utils/date-formatter.js.map +1 -0
- package/dist/src/utils/error-formatter.d.ts +17 -0
- package/dist/src/utils/error-formatter.d.ts.map +1 -0
- package/dist/src/utils/error-formatter.js +60 -0
- package/dist/src/utils/error-formatter.js.map +1 -0
- package/dist/src/utils/progress-bar.d.ts +35 -0
- package/dist/src/utils/progress-bar.d.ts.map +1 -0
- package/dist/src/utils/progress-bar.js +43 -0
- package/dist/src/utils/progress-bar.js.map +1 -0
- package/dist/src/utils/reset-timer.d.ts +11 -0
- package/dist/src/utils/reset-timer.d.ts.map +1 -0
- package/dist/src/utils/reset-timer.js +32 -0
- package/dist/src/utils/reset-timer.js.map +1 -0
- package/dist/src/utils/time-window.d.ts +30 -0
- package/dist/src/utils/time-window.d.ts.map +1 -0
- package/dist/src/utils/time-window.js +34 -0
- package/dist/src/utils/time-window.js.map +1 -0
- package/dist/tests/error-handling/api-errors.test.d.ts +7 -0
- package/dist/tests/error-handling/api-errors.test.d.ts.map +1 -0
- package/dist/tests/error-handling/api-errors.test.js +110 -0
- package/dist/tests/error-handling/api-errors.test.js.map +1 -0
- package/dist/tests/error-handling/auth-errors.test.d.ts +7 -0
- package/dist/tests/error-handling/auth-errors.test.d.ts.map +1 -0
- package/dist/tests/error-handling/auth-errors.test.js +110 -0
- package/dist/tests/error-handling/auth-errors.test.js.map +1 -0
- package/dist/tests/error-handling/network-errors.test.d.ts +7 -0
- package/dist/tests/error-handling/network-errors.test.d.ts.map +1 -0
- package/dist/tests/error-handling/network-errors.test.js +94 -0
- package/dist/tests/error-handling/network-errors.test.js.map +1 -0
- package/dist/tests/error-handling/parse-errors.test.d.ts +7 -0
- package/dist/tests/error-handling/parse-errors.test.d.ts.map +1 -0
- package/dist/tests/error-handling/parse-errors.test.js +87 -0
- package/dist/tests/error-handling/parse-errors.test.js.map +1 -0
- package/dist/tests/error-handling/token-sanitization.test.d.ts +2 -0
- package/dist/tests/error-handling/token-sanitization.test.d.ts.map +1 -0
- package/dist/tests/error-handling/token-sanitization.test.js +59 -0
- package/dist/tests/error-handling/token-sanitization.test.js.map +1 -0
- package/dist/tests/functional/date-formatter.test.d.ts +5 -0
- package/dist/tests/functional/date-formatter.test.d.ts.map +1 -0
- package/dist/tests/functional/date-formatter.test.js +46 -0
- package/dist/tests/functional/date-formatter.test.js.map +1 -0
- package/dist/tests/functional/progress-bar.test.d.ts +5 -0
- package/dist/tests/functional/progress-bar.test.d.ts.map +1 -0
- package/dist/tests/functional/progress-bar.test.js +82 -0
- package/dist/tests/functional/progress-bar.test.js.map +1 -0
- package/dist/tests/functional/reset-timer.test.d.ts +6 -0
- package/dist/tests/functional/reset-timer.test.d.ts.map +1 -0
- package/dist/tests/functional/reset-timer.test.js +67 -0
- package/dist/tests/functional/reset-timer.test.js.map +1 -0
- package/dist/tests/functional/time-window.test.d.ts +5 -0
- package/dist/tests/functional/time-window.test.d.ts.map +1 -0
- package/dist/tests/functional/time-window.test.js +46 -0
- package/dist/tests/functional/time-window.test.js.map +1 -0
- package/dist/tests/integration/box-alignment.test.d.ts +8 -0
- package/dist/tests/integration/box-alignment.test.d.ts.map +1 -0
- package/dist/tests/integration/box-alignment.test.js +238 -0
- package/dist/tests/integration/box-alignment.test.js.map +1 -0
- package/dist/tests/integration/error-handling.test.d.ts +2 -0
- package/dist/tests/integration/error-handling.test.d.ts.map +1 -0
- package/dist/tests/integration/error-handling.test.js +36 -0
- package/dist/tests/integration/error-handling.test.js.map +1 -0
- package/dist/tests/integration/installer-config.test.d.ts +2 -0
- package/dist/tests/integration/installer-config.test.d.ts.map +1 -0
- package/dist/tests/integration/installer-config.test.js +65 -0
- package/dist/tests/integration/installer-config.test.js.map +1 -0
- package/dist/tests/integration/plugin-catch-block.test.d.ts +2 -0
- package/dist/tests/integration/plugin-catch-block.test.d.ts.map +1 -0
- package/dist/tests/integration/plugin-catch-block.test.js +134 -0
- package/dist/tests/integration/plugin-catch-block.test.js.map +1 -0
- package/dist/tests/integration/reset-time-display.test.d.ts +6 -0
- package/dist/tests/integration/reset-time-display.test.d.ts.map +1 -0
- package/dist/tests/integration/reset-time-display.test.js +138 -0
- package/dist/tests/integration/reset-time-display.test.js.map +1 -0
- package/dist/tests/module/http-client.test.d.ts +2 -0
- package/dist/tests/module/http-client.test.d.ts.map +1 -0
- package/dist/tests/module/http-client.test.js +49 -0
- package/dist/tests/module/http-client.test.js.map +1 -0
- package/dist/tests/module/platform-detection.test.d.ts +5 -0
- package/dist/tests/module/platform-detection.test.d.ts.map +1 -0
- package/dist/tests/module/platform-detection.test.js +48 -0
- package/dist/tests/module/platform-detection.test.js.map +1 -0
- package/integration/agents/copilot-quota-exec.md +20 -0
- package/integration/agents/glm-quota-exec.md +20 -0
- package/integration/command/copilot_quota.md +6 -0
- package/integration/command/glm_quota.md +6 -0
- package/integration/skills/copilot-quota/SKILL.md +11 -0
- package/integration/skills/glm-quota/SKILL.md +11 -0
- package/package.json +69 -0
package/bin/glm-quota.ts
ADDED
|
@@ -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()
|