@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
|
@@ -0,0 +1,723 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OpenCode GLM Quota Plugin
|
|
3
|
+
*
|
|
4
|
+
* Query Z.ai GLM Coding Plan usage statistics including quota limits,
|
|
5
|
+
* model usage, and MCP tool usage.
|
|
6
|
+
*/
|
|
7
|
+
import { tool } from '@opencode-ai/plugin/tool';
|
|
8
|
+
import * as fs from 'fs';
|
|
9
|
+
import * as os from 'os';
|
|
10
|
+
import * as path from 'path';
|
|
11
|
+
import { makeRequest, queryEndpoint } from './api/client.js';
|
|
12
|
+
import { getEndpoints } from './api/endpoints.js';
|
|
13
|
+
import { detectPlatform, getPlatformName } from './api/platforms.js';
|
|
14
|
+
import { BOX_WIDTH, HEADER } from './utils/box-constants.js';
|
|
15
|
+
import { createBoxedError } from './utils/error-formatter.js';
|
|
16
|
+
import { formatProgressLine } from './utils/progress-bar.js';
|
|
17
|
+
import { formatTimeUntilReset } from './utils/reset-timer.js';
|
|
18
|
+
import { getTimeWindow, getTimeWindowQueryParams } from './utils/time-window.js';
|
|
19
|
+
// ============================================================================
|
|
20
|
+
// CONSTANTS
|
|
21
|
+
// ============================================================================
|
|
22
|
+
const CANDIDATE_PROVIDER_IDS = [
|
|
23
|
+
'zai-coding-plan',
|
|
24
|
+
'zai',
|
|
25
|
+
'z-ai',
|
|
26
|
+
'z.ai',
|
|
27
|
+
'zhipu',
|
|
28
|
+
'zhipuai',
|
|
29
|
+
'github-copilot',
|
|
30
|
+
'github',
|
|
31
|
+
'copilot',
|
|
32
|
+
];
|
|
33
|
+
const DEFAULT_TOKEN_LIMIT = 40000000;
|
|
34
|
+
const TOKEN_LIMIT_LABEL = 'Token usage(5 Hour)';
|
|
35
|
+
const MCP_LIMIT_LABEL = 'MCP usage(1 Month)';
|
|
36
|
+
const TOKEN_LIMIT_TYPE = 'TOKENS_LIMIT';
|
|
37
|
+
const TIME_LIMIT_TYPE = 'TIME_LIMIT';
|
|
38
|
+
// ============================================================================
|
|
39
|
+
// CREDENTIAL DISCOVERY
|
|
40
|
+
// ============================================================================
|
|
41
|
+
/**
|
|
42
|
+
* Get auth file path based on platform
|
|
43
|
+
* @returns Path to auth.json file
|
|
44
|
+
*/
|
|
45
|
+
function getAuthFilePath() {
|
|
46
|
+
if (process.platform === 'win32') {
|
|
47
|
+
return path.join(process.env.LOCALAPPDATA || path.join(os.homedir(), 'AppData', 'Local'), 'opencode', 'auth.json');
|
|
48
|
+
}
|
|
49
|
+
return path.join(os.homedir(), '.local', 'share', 'opencode', 'auth.json');
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Format number with thousands separator
|
|
53
|
+
* @param num Number to format
|
|
54
|
+
* @returns Formatted number string
|
|
55
|
+
*/
|
|
56
|
+
function formatNumber(num) {
|
|
57
|
+
return num.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ',');
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Extract API key from auth entry
|
|
61
|
+
* @param entry - Auth entry (string or object)
|
|
62
|
+
* @returns API key or null
|
|
63
|
+
*/
|
|
64
|
+
function extractKeyFromEntry(entry) {
|
|
65
|
+
if (typeof entry === 'string')
|
|
66
|
+
return entry;
|
|
67
|
+
if (typeof entry === 'object' && entry !== null) {
|
|
68
|
+
const obj = entry;
|
|
69
|
+
for (const keyName of ['apiKey', 'api_key', 'token', 'key', 'accessToken', 'auth_token']) {
|
|
70
|
+
if (typeof obj[keyName] === 'string')
|
|
71
|
+
return obj[keyName];
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
return null;
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Get credentials from OpenCode auth.json or environment variables
|
|
78
|
+
* @returns Credentials or null if not found
|
|
79
|
+
*/
|
|
80
|
+
async function getCredentials() {
|
|
81
|
+
// Priority 1: OpenCode auth.json
|
|
82
|
+
const authPath = getAuthFilePath();
|
|
83
|
+
if (fs.existsSync(authPath)) {
|
|
84
|
+
try {
|
|
85
|
+
const content = fs.readFileSync(authPath, 'utf-8');
|
|
86
|
+
const authData = JSON.parse(content);
|
|
87
|
+
for (const providerId of CANDIDATE_PROVIDER_IDS) {
|
|
88
|
+
const entry = authData[providerId];
|
|
89
|
+
if (entry) {
|
|
90
|
+
const token = extractKeyFromEntry(entry);
|
|
91
|
+
if (token) {
|
|
92
|
+
const platform = detectPlatform(providerId);
|
|
93
|
+
if (platform) {
|
|
94
|
+
return { token, platform };
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
catch {
|
|
101
|
+
// Silent fail, try next method
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
// Priority 2: Environment variables (for development/testing)
|
|
105
|
+
if (process.env.ZAI_API_KEY) {
|
|
106
|
+
return { token: process.env.ZAI_API_KEY, platform: 'ZAI' };
|
|
107
|
+
}
|
|
108
|
+
if (process.env.ZHIPU_API_KEY || process.env.ZHIPUAI_API_KEY) {
|
|
109
|
+
return {
|
|
110
|
+
token: (process.env.ZHIPU_API_KEY || process.env.ZHIPUAI_API_KEY),
|
|
111
|
+
platform: 'ZHIPU',
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
return null;
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Create error message for missing credentials
|
|
118
|
+
* @returns Error message with setup instructions (box formatted)
|
|
119
|
+
*/
|
|
120
|
+
function createCredentialError() {
|
|
121
|
+
const header = '❌ Z.ai Credentials Not Found';
|
|
122
|
+
const instructions = [
|
|
123
|
+
'',
|
|
124
|
+
'Please authenticate first:',
|
|
125
|
+
'',
|
|
126
|
+
'1. Run /connect command in OpenCode TUI',
|
|
127
|
+
'2. Select "Z.AI Coding Plan" or "Z.AI"',
|
|
128
|
+
'3. Or "Zhipu" (for China region)',
|
|
129
|
+
'',
|
|
130
|
+
'For dev/testing, set environment:',
|
|
131
|
+
'- ZAI_API_KEY (global)',
|
|
132
|
+
'- ZHIPU_API_KEY (China)',
|
|
133
|
+
'',
|
|
134
|
+
];
|
|
135
|
+
const lines = [];
|
|
136
|
+
lines.push('╔' + '═'.repeat(BOX_WIDTH.BORDER_CHARS) + '╗');
|
|
137
|
+
lines.push('║' + ' '.repeat(BOX_WIDTH.BORDER_CHARS) + '║');
|
|
138
|
+
lines.push(formatBoxLine(header, BOX_WIDTH.CONTENT));
|
|
139
|
+
lines.push('║' + ' '.repeat(BOX_WIDTH.BORDER_CHARS) + '║');
|
|
140
|
+
for (const instruction of instructions) {
|
|
141
|
+
lines.push(formatBoxLine(instruction, BOX_WIDTH.CONTENT));
|
|
142
|
+
}
|
|
143
|
+
lines.push('╚' + '═'.repeat(BOX_WIDTH.BORDER_CHARS) + '╝');
|
|
144
|
+
return lines.join('\n');
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* Create error message for missing GitHub credentials
|
|
148
|
+
* @returns Error message with setup instructions (box formatted)
|
|
149
|
+
*/
|
|
150
|
+
function createGitHubCredentialError() {
|
|
151
|
+
const header = '❌ GitHub Copilot Credentials Not Found';
|
|
152
|
+
const instructions = [
|
|
153
|
+
'',
|
|
154
|
+
'Please authenticate first:',
|
|
155
|
+
'',
|
|
156
|
+
'1. Run /connect command in OpenCode TUI',
|
|
157
|
+
'2. Select "GitHub Copilot"',
|
|
158
|
+
'',
|
|
159
|
+
'For dev/testing, set environment:',
|
|
160
|
+
'- GITHUB_TOKEN (your GitHub personal access token)',
|
|
161
|
+
'- GITHUB_USERNAME (your GitHub username)',
|
|
162
|
+
'',
|
|
163
|
+
];
|
|
164
|
+
const lines = [];
|
|
165
|
+
lines.push('╔' + '═'.repeat(BOX_WIDTH.BORDER_CHARS) + '╗');
|
|
166
|
+
lines.push('║' + ' '.repeat(BOX_WIDTH.BORDER_CHARS) + '║');
|
|
167
|
+
lines.push(formatBoxLine(header, BOX_WIDTH.CONTENT));
|
|
168
|
+
lines.push('║' + ' '.repeat(BOX_WIDTH.BORDER_CHARS) + '║');
|
|
169
|
+
for (const instruction of instructions) {
|
|
170
|
+
lines.push(formatBoxLine(instruction, BOX_WIDTH.CONTENT));
|
|
171
|
+
}
|
|
172
|
+
lines.push('╚' + '═'.repeat(BOX_WIDTH.BORDER_CHARS) + '╝');
|
|
173
|
+
return lines.join('\n');
|
|
174
|
+
}
|
|
175
|
+
// ============================================================================
|
|
176
|
+
// RESPONSE PROCESSING
|
|
177
|
+
// ============================================================================
|
|
178
|
+
/**
|
|
179
|
+
* Process quota limit response
|
|
180
|
+
* @param data - Raw API response
|
|
181
|
+
* @returns Processed response with human-readable types
|
|
182
|
+
*/
|
|
183
|
+
function processQuotaLimit(data) {
|
|
184
|
+
const result = { ...data };
|
|
185
|
+
if (result.limits && Array.isArray(result.limits)) {
|
|
186
|
+
result.limits = result.limits.map((item) => {
|
|
187
|
+
if (typeof item === 'object' && item !== null) {
|
|
188
|
+
const limit = item;
|
|
189
|
+
if (limit.type === TOKEN_LIMIT_TYPE) {
|
|
190
|
+
return {
|
|
191
|
+
type: TOKEN_LIMIT_LABEL,
|
|
192
|
+
percentage: typeof limit.percentage === 'number' ? limit.percentage : 0,
|
|
193
|
+
nextResetTime: limit.nextResetTime,
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
if (limit.type === TIME_LIMIT_TYPE) {
|
|
197
|
+
return {
|
|
198
|
+
type: MCP_LIMIT_LABEL,
|
|
199
|
+
percentage: typeof limit.percentage === 'number' ? limit.percentage : 0,
|
|
200
|
+
currentValue: limit.currentValue,
|
|
201
|
+
total: limit.usage,
|
|
202
|
+
usageDetails: limit.usageDetails,
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
return item;
|
|
207
|
+
});
|
|
208
|
+
}
|
|
209
|
+
return result;
|
|
210
|
+
}
|
|
211
|
+
// ============================================================================
|
|
212
|
+
// OUTPUT FORMATTING
|
|
213
|
+
// ============================================================================
|
|
214
|
+
/**
|
|
215
|
+
* Query GitHub Copilot usage data
|
|
216
|
+
*/
|
|
217
|
+
async function queryGitHubUsage(token) {
|
|
218
|
+
const url = 'https://api.github.com/copilot_internal/user';
|
|
219
|
+
const headers = {
|
|
220
|
+
'Authorization': `token ${token}`,
|
|
221
|
+
'Accept': 'application/json',
|
|
222
|
+
'Editor-Version': 'vscode/1.96.2',
|
|
223
|
+
'Editor-Plugin-Version': 'copilot-chat/0.26.7',
|
|
224
|
+
'User-Agent': 'GitHubCopilotChat/0.26.7',
|
|
225
|
+
'X-GitHub-Api-Version': '2025-04-01',
|
|
226
|
+
};
|
|
227
|
+
const data = await makeRequest({ url, authToken: token, customHeaders: headers });
|
|
228
|
+
return data;
|
|
229
|
+
}
|
|
230
|
+
/**
|
|
231
|
+
* Get token limit information from quota data
|
|
232
|
+
*/
|
|
233
|
+
function getTokenLimitInfo(quotaData) {
|
|
234
|
+
let tokenLimit = DEFAULT_TOKEN_LIMIT;
|
|
235
|
+
let tokenPct = 0;
|
|
236
|
+
if (!quotaData?.limits)
|
|
237
|
+
return { tokenLimit, tokenPct };
|
|
238
|
+
for (const limit of quotaData.limits) {
|
|
239
|
+
if (limit.type === TOKEN_LIMIT_LABEL) {
|
|
240
|
+
tokenPct = typeof limit.percentage === 'number' ? limit.percentage : 0;
|
|
241
|
+
tokenLimit = limit.total || DEFAULT_TOKEN_LIMIT;
|
|
242
|
+
break;
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
return { tokenLimit, tokenPct };
|
|
246
|
+
}
|
|
247
|
+
/**
|
|
248
|
+
* Format MCP tool details as readable lines
|
|
249
|
+
*/
|
|
250
|
+
function formatMcpToolLines(details) {
|
|
251
|
+
const lines = [];
|
|
252
|
+
const mcpTotal = details.reduce((sum, d) => sum + (d.usage || 0), 0);
|
|
253
|
+
for (const d of details) {
|
|
254
|
+
const pct = mcpTotal > 0 ? Math.round((d.usage / mcpTotal) * 100) : 0;
|
|
255
|
+
lines.push(` - ${d.modelCode}: ${d.usage} (${pct}%)`);
|
|
256
|
+
}
|
|
257
|
+
return lines;
|
|
258
|
+
}
|
|
259
|
+
function formatTokenUsageLines(tokens, tokenLimit, tokenPct) {
|
|
260
|
+
const pct24h = Math.round((tokens / tokenLimit) * 100);
|
|
261
|
+
return [
|
|
262
|
+
` Total Tokens (24h): ${formatNumber(tokens)} (${pct24h}% of 5h limit)`,
|
|
263
|
+
` 5h Window Usage: ${tokenPct}% of ${formatNumber(tokenLimit)}`,
|
|
264
|
+
];
|
|
265
|
+
}
|
|
266
|
+
function formatModelUsageLines(totalUsage, quotaData) {
|
|
267
|
+
const lines = [];
|
|
268
|
+
const { tokenLimit, tokenPct } = getTokenLimitInfo(quotaData);
|
|
269
|
+
const calls = totalUsage.totalModelCallCount;
|
|
270
|
+
const tokens = totalUsage.totalTokensUsage;
|
|
271
|
+
if (tokens !== undefined) {
|
|
272
|
+
lines.push(...formatTokenUsageLines(tokens, tokenLimit, tokenPct));
|
|
273
|
+
}
|
|
274
|
+
if (calls !== undefined) {
|
|
275
|
+
lines.push(` Total Calls: ${formatNumber(calls)}`);
|
|
276
|
+
}
|
|
277
|
+
return lines;
|
|
278
|
+
}
|
|
279
|
+
/**
|
|
280
|
+
* Format model usage data as readable lines
|
|
281
|
+
*/
|
|
282
|
+
function formatModelUsage(data, quotaData) {
|
|
283
|
+
const lines = [];
|
|
284
|
+
const totalUsage = data.totalUsage;
|
|
285
|
+
if (!totalUsage) {
|
|
286
|
+
lines.push(' No usage data');
|
|
287
|
+
return lines;
|
|
288
|
+
}
|
|
289
|
+
return formatModelUsageLines(totalUsage, quotaData);
|
|
290
|
+
}
|
|
291
|
+
/**
|
|
292
|
+
* Format tool usage data as readable lines
|
|
293
|
+
*/
|
|
294
|
+
function formatToolUsageSummaryLines(totalUsage) {
|
|
295
|
+
if (!totalUsage)
|
|
296
|
+
return [];
|
|
297
|
+
const search = totalUsage.totalNetworkSearchCount;
|
|
298
|
+
const webRead = totalUsage.totalWebReadMcpCount;
|
|
299
|
+
const zread = totalUsage.totalZreadMcpCount;
|
|
300
|
+
return [
|
|
301
|
+
...(search !== undefined ? [` Network Searches: ${formatNumber(search)}`] : []),
|
|
302
|
+
...(webRead !== undefined ? [` Web Reads: ${formatNumber(webRead)}`] : []),
|
|
303
|
+
...(zread !== undefined ? [` ZRead Calls: ${formatNumber(zread)}`] : []),
|
|
304
|
+
];
|
|
305
|
+
}
|
|
306
|
+
function formatMcpUsageDetailLines(quotaData) {
|
|
307
|
+
if (!quotaData?.limits)
|
|
308
|
+
return [];
|
|
309
|
+
for (const limit of quotaData.limits) {
|
|
310
|
+
if (limit.type === MCP_LIMIT_LABEL && limit.usageDetails) {
|
|
311
|
+
const details = limit.usageDetails;
|
|
312
|
+
return [' MCP Tool Details:', ...formatMcpToolLines(details)];
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
return [];
|
|
316
|
+
}
|
|
317
|
+
function formatToolUsage(data, quotaData) {
|
|
318
|
+
const totalUsage = data.totalUsage;
|
|
319
|
+
const lines = [
|
|
320
|
+
...formatToolUsageSummaryLines(totalUsage),
|
|
321
|
+
...formatMcpUsageDetailLines(quotaData),
|
|
322
|
+
];
|
|
323
|
+
if (lines.length === 0) {
|
|
324
|
+
return [' No usage data'];
|
|
325
|
+
}
|
|
326
|
+
return lines;
|
|
327
|
+
}
|
|
328
|
+
// ============================================================================
|
|
329
|
+
// HELPER FUNCTIONS FOR OUTPUT FORMATTING
|
|
330
|
+
// ============================================================================
|
|
331
|
+
/**
|
|
332
|
+
* Format a single line with box characters
|
|
333
|
+
* @param content - Content to display (without padding)
|
|
334
|
+
* @param lineIndent - Total line width after padding
|
|
335
|
+
* @returns Formatted line with box characters
|
|
336
|
+
*/
|
|
337
|
+
function formatBoxLine(content, lineIndent) {
|
|
338
|
+
const trimmed = trimToDisplayWidth(content, lineIndent, 0);
|
|
339
|
+
const padding = Math.max(lineIndent - getDisplayWidth(trimmed), 0);
|
|
340
|
+
return '║ ' + trimmed + ' '.repeat(padding) + '║';
|
|
341
|
+
}
|
|
342
|
+
function formatProgressBoxLine(content, lineIndent) {
|
|
343
|
+
const gap = 2;
|
|
344
|
+
const contentWidth = Math.max(lineIndent - gap, 0);
|
|
345
|
+
const trimmed = trimToDisplayWidth(content, contentWidth, 0);
|
|
346
|
+
const padding = Math.max(contentWidth - getDisplayWidth(trimmed), 0);
|
|
347
|
+
return '║ ' + trimmed + ' '.repeat(padding + gap) + '║';
|
|
348
|
+
}
|
|
349
|
+
function getDisplayWidth(text) {
|
|
350
|
+
let width = 0;
|
|
351
|
+
for (let i = 0; i < text.length; i += 1) {
|
|
352
|
+
const codePoint = text.codePointAt(i);
|
|
353
|
+
if (codePoint === undefined) {
|
|
354
|
+
continue;
|
|
355
|
+
}
|
|
356
|
+
if (codePoint > 0xffff) {
|
|
357
|
+
i += 1;
|
|
358
|
+
}
|
|
359
|
+
if (isControlCodePoint(codePoint) || isZeroWidthCodePoint(codePoint)) {
|
|
360
|
+
continue;
|
|
361
|
+
}
|
|
362
|
+
width += isEmojiCodePoint(codePoint) || isFullWidthCodePoint(codePoint) ? 2 : 1;
|
|
363
|
+
}
|
|
364
|
+
return width;
|
|
365
|
+
}
|
|
366
|
+
function trimToDisplayWidth(text, maxWidth, reserveRightPadding) {
|
|
367
|
+
let width = 0;
|
|
368
|
+
let result = '';
|
|
369
|
+
const allowedWidth = Math.max(maxWidth - reserveRightPadding, 0);
|
|
370
|
+
for (let i = 0; i < text.length; i += 1) {
|
|
371
|
+
const codePoint = text.codePointAt(i);
|
|
372
|
+
if (codePoint === undefined) {
|
|
373
|
+
continue;
|
|
374
|
+
}
|
|
375
|
+
if (codePoint > 0xffff) {
|
|
376
|
+
i += 1;
|
|
377
|
+
}
|
|
378
|
+
if (isControlCodePoint(codePoint) || isZeroWidthCodePoint(codePoint)) {
|
|
379
|
+
continue;
|
|
380
|
+
}
|
|
381
|
+
const charWidth = isEmojiCodePoint(codePoint) || isFullWidthCodePoint(codePoint) ? 2 : 1;
|
|
382
|
+
if (width + charWidth > allowedWidth) {
|
|
383
|
+
break;
|
|
384
|
+
}
|
|
385
|
+
result += String.fromCodePoint(codePoint);
|
|
386
|
+
width += charWidth;
|
|
387
|
+
}
|
|
388
|
+
return result;
|
|
389
|
+
}
|
|
390
|
+
function isControlCodePoint(codePoint) {
|
|
391
|
+
return codePoint <= 0x1f || (codePoint >= 0x7f && codePoint <= 0x9f);
|
|
392
|
+
}
|
|
393
|
+
function isZeroWidthCodePoint(codePoint) {
|
|
394
|
+
return (codePoint === 0x200d
|
|
395
|
+
|| codePoint === 0xfe0f
|
|
396
|
+
|| (codePoint >= 0xfe00 && codePoint <= 0xfe0f));
|
|
397
|
+
}
|
|
398
|
+
function isEmojiCodePoint(codePoint) {
|
|
399
|
+
return ((codePoint >= 0x1f300 && codePoint <= 0x1f5ff)
|
|
400
|
+
|| (codePoint >= 0x1f600 && codePoint <= 0x1f64f)
|
|
401
|
+
|| (codePoint >= 0x1f680 && codePoint <= 0x1f6ff)
|
|
402
|
+
|| (codePoint >= 0x1f700 && codePoint <= 0x1f77f)
|
|
403
|
+
|| (codePoint >= 0x1f780 && codePoint <= 0x1f7ff)
|
|
404
|
+
|| (codePoint >= 0x1f800 && codePoint <= 0x1f8ff)
|
|
405
|
+
|| (codePoint >= 0x1f900 && codePoint <= 0x1f9ff)
|
|
406
|
+
|| (codePoint >= 0x1fa00 && codePoint <= 0x1faff)
|
|
407
|
+
|| (codePoint >= 0x2600 && codePoint <= 0x26ff)
|
|
408
|
+
|| (codePoint >= 0x2700 && codePoint <= 0x27bf));
|
|
409
|
+
}
|
|
410
|
+
function isFullWidthCodePoint(codePoint) {
|
|
411
|
+
return (codePoint >= 0x1100 && (codePoint <= 0x115f
|
|
412
|
+
|| codePoint === 0x2329
|
|
413
|
+
|| codePoint === 0x232a
|
|
414
|
+
|| (codePoint >= 0x2e80 && codePoint <= 0x3247 && codePoint !== 0x303f)
|
|
415
|
+
|| (codePoint >= 0x3250 && codePoint <= 0x4dbf)
|
|
416
|
+
|| (codePoint >= 0x4e00 && codePoint <= 0xa4c6)
|
|
417
|
+
|| (codePoint >= 0xa960 && codePoint <= 0xa97c)
|
|
418
|
+
|| (codePoint >= 0xac00 && codePoint <= 0xd7a3)
|
|
419
|
+
|| (codePoint >= 0xf900 && codePoint <= 0xfaff)
|
|
420
|
+
|| (codePoint >= 0xfe10 && codePoint <= 0xfe19)
|
|
421
|
+
|| (codePoint >= 0xfe30 && codePoint <= 0xfe6b)
|
|
422
|
+
|| (codePoint >= 0xff01 && codePoint <= 0xff60)
|
|
423
|
+
|| (codePoint >= 0xffe0 && codePoint <= 0xffe6)
|
|
424
|
+
|| (codePoint >= 0x1b000 && codePoint <= 0x1b001)
|
|
425
|
+
|| (codePoint >= 0x1f200 && codePoint <= 0x1f251)
|
|
426
|
+
|| (codePoint >= 0x20000 && codePoint <= 0x3fffd)));
|
|
427
|
+
}
|
|
428
|
+
/**
|
|
429
|
+
* Format header section
|
|
430
|
+
* @param platformName - Platform name
|
|
431
|
+
* @param startTime - Start time string
|
|
432
|
+
* @param endTime - End time string
|
|
433
|
+
* @returns Array of header lines
|
|
434
|
+
*/
|
|
435
|
+
function formatHeader(platformName, startTime, endTime) {
|
|
436
|
+
const lines = [];
|
|
437
|
+
lines.push('╔' + '═'.repeat(BOX_WIDTH.BORDER_CHARS) + '╗');
|
|
438
|
+
lines.push('║' + ' '.repeat(BOX_WIDTH.BORDER_CHARS) + '║');
|
|
439
|
+
lines.push('║' + ' Z.ai GLM Coding Plan Usage Statistics '.padStart(HEADER.TITLE_PAD_START).padEnd(BOX_WIDTH.BORDER_CHARS)
|
|
440
|
+
+ '║');
|
|
441
|
+
lines.push('║' + ' '.repeat(BOX_WIDTH.BORDER_CHARS) + '║');
|
|
442
|
+
lines.push('╠' + '═'.repeat(BOX_WIDTH.BORDER_CHARS) + '╣');
|
|
443
|
+
lines.push(formatBoxLine(`Platform: ${platformName}`, BOX_WIDTH.CONTENT));
|
|
444
|
+
lines.push(formatBoxLine(`Period: ${startTime} → ${endTime}`, BOX_WIDTH.CONTENT));
|
|
445
|
+
lines.push('╠' + '═'.repeat(BOX_WIDTH.BORDER_CHARS) + '╣');
|
|
446
|
+
return lines;
|
|
447
|
+
}
|
|
448
|
+
/**
|
|
449
|
+
* Format quota limits section
|
|
450
|
+
* @param quotaData - Quota limit data
|
|
451
|
+
* @returns Array of quota lines
|
|
452
|
+
*/
|
|
453
|
+
function formatQuotaLimits(quotaData) {
|
|
454
|
+
const lines = [];
|
|
455
|
+
lines.push(formatBoxLine('QUOTA LIMITS', BOX_WIDTH.CONTENT));
|
|
456
|
+
lines.push('╟' + '─'.repeat(BOX_WIDTH.BORDER_CHARS) + '╢');
|
|
457
|
+
const limits = quotaData?.limits;
|
|
458
|
+
if (limits && Array.isArray(limits)) {
|
|
459
|
+
for (const limit of limits) {
|
|
460
|
+
const pct = typeof limit.percentage === 'number' ? limit.percentage : 0;
|
|
461
|
+
const line = formatProgressLine(limit.type || 'Unknown', pct);
|
|
462
|
+
lines.push(formatProgressBoxLine(line, BOX_WIDTH.CONTENT));
|
|
463
|
+
if (limit.nextResetTime !== undefined) {
|
|
464
|
+
const resetMsg = formatTimeUntilReset(limit.nextResetTime);
|
|
465
|
+
if (resetMsg) {
|
|
466
|
+
lines.push(formatBoxLine(resetMsg, BOX_WIDTH.CONTENT));
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
if (limit.currentValue !== undefined && limit.total !== undefined) {
|
|
470
|
+
const usageStr = ' Used: ' + limit.currentValue + '/' + limit.total;
|
|
471
|
+
lines.push(formatBoxLine(usageStr, BOX_WIDTH.CONTENT));
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
else {
|
|
476
|
+
lines.push(formatBoxLine('No quota data available', BOX_WIDTH.CONTENT));
|
|
477
|
+
}
|
|
478
|
+
lines.push('╠' + '═'.repeat(BOX_WIDTH.BORDER_CHARS) + '╣');
|
|
479
|
+
return lines;
|
|
480
|
+
}
|
|
481
|
+
/**
|
|
482
|
+
* Format data section with optional data
|
|
483
|
+
* @param title - Section title
|
|
484
|
+
* @param data - Data object or null
|
|
485
|
+
* @param formatter - Function to format data if present
|
|
486
|
+
* @param noDataMessage - Message to show if no data
|
|
487
|
+
* @param LINE_INDENT - Line indent width
|
|
488
|
+
* @returns Array of section lines
|
|
489
|
+
*/
|
|
490
|
+
function formatDataSection(title, data, formatter, quotaData, noDataMessage, LINE_INDENT) {
|
|
491
|
+
const lines = [];
|
|
492
|
+
lines.push(formatBoxLine(title, LINE_INDENT));
|
|
493
|
+
lines.push('╟' + '─'.repeat(BOX_WIDTH.BORDER_CHARS) + '╢');
|
|
494
|
+
if (data) {
|
|
495
|
+
const formattedLines = formatter(data, quotaData);
|
|
496
|
+
for (const line of formattedLines) {
|
|
497
|
+
lines.push(formatBoxLine(line, LINE_INDENT));
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
else {
|
|
501
|
+
lines.push(formatBoxLine(noDataMessage, LINE_INDENT));
|
|
502
|
+
}
|
|
503
|
+
lines.push('╠' + '═'.repeat(BOX_WIDTH.BORDER_CHARS) + '╣');
|
|
504
|
+
return lines;
|
|
505
|
+
}
|
|
506
|
+
/**
|
|
507
|
+
* Format footer section
|
|
508
|
+
* @returns Footer lines
|
|
509
|
+
*/
|
|
510
|
+
function formatFooter() {
|
|
511
|
+
return ['╚' + '═'.repeat(BOX_WIDTH.BORDER_CHARS) + '╝'];
|
|
512
|
+
}
|
|
513
|
+
/**
|
|
514
|
+
* Format usage statistics as ASCII table
|
|
515
|
+
* @param platform - Platform name
|
|
516
|
+
* @param startTime - Start time string
|
|
517
|
+
* @param endTime - End time string
|
|
518
|
+
* @param quotaData - Quota limit data
|
|
519
|
+
* @param modelData - Model usage data
|
|
520
|
+
* @param toolData - Tool usage data
|
|
521
|
+
* @returns Formatted output string
|
|
522
|
+
*/
|
|
523
|
+
function formatOutput(platform, startTime, endTime, quotaData, modelData, toolData) {
|
|
524
|
+
const lines = [];
|
|
525
|
+
const platformName = getPlatformName(platform);
|
|
526
|
+
lines.push(...formatHeader(platformName, startTime, endTime));
|
|
527
|
+
lines.push(...formatQuotaLimits(quotaData));
|
|
528
|
+
lines.push(...formatDataSection('MODEL USAGE (24h)', modelData, formatModelUsage, quotaData, 'No model usage data available', BOX_WIDTH.CONTENT));
|
|
529
|
+
lines.push(...formatDataSection('TOOL/MCP USAGE (24h)', toolData, formatToolUsage, quotaData, 'No tool usage data available', BOX_WIDTH.CONTENT));
|
|
530
|
+
lines.push(...formatFooter());
|
|
531
|
+
return lines.join('\n');
|
|
532
|
+
}
|
|
533
|
+
// ============================================================================
|
|
534
|
+
// MAIN QUERY FUNCTION
|
|
535
|
+
// ============================================================================
|
|
536
|
+
/**
|
|
537
|
+
* Query all usage statistics
|
|
538
|
+
* @param credentials - API credentials
|
|
539
|
+
* @returns Formatted output string
|
|
540
|
+
*/
|
|
541
|
+
async function queryAllUsage(credentials) {
|
|
542
|
+
const { token, platform } = credentials;
|
|
543
|
+
const endpoints = getEndpoints(platform);
|
|
544
|
+
const { startTime, endTime } = getTimeWindow();
|
|
545
|
+
const queryParams = getTimeWindowQueryParams();
|
|
546
|
+
// Query all endpoints
|
|
547
|
+
const [quotaResponse, modelResponse, toolResponse] = await Promise.all([
|
|
548
|
+
queryEndpoint(endpoints, token, 'quotaLimit').catch(() => null),
|
|
549
|
+
queryEndpoint(endpoints, token, 'modelUsage', queryParams).catch(() => null),
|
|
550
|
+
queryEndpoint(endpoints, token, 'toolUsage', queryParams).catch(() => null),
|
|
551
|
+
]);
|
|
552
|
+
// Process responses
|
|
553
|
+
const quotaData = quotaResponse
|
|
554
|
+
? processQuotaLimit(quotaResponse.data)
|
|
555
|
+
: null;
|
|
556
|
+
const modelData = modelResponse
|
|
557
|
+
? (modelResponse.data || modelResponse)
|
|
558
|
+
: null;
|
|
559
|
+
const toolData = toolResponse
|
|
560
|
+
? (toolResponse.data || toolResponse)
|
|
561
|
+
: null;
|
|
562
|
+
return formatOutput(platform, startTime, endTime, quotaData, modelData, toolData);
|
|
563
|
+
}
|
|
564
|
+
// ============================================================================
|
|
565
|
+
// PLUGIN EXPPORT
|
|
566
|
+
// ============================================================================
|
|
567
|
+
// ============================================================================
|
|
568
|
+
// GitHub Copilot Pro Support
|
|
569
|
+
// ============================================================================
|
|
570
|
+
/**
|
|
571
|
+
* Get GitHub credentials from environment variables
|
|
572
|
+
*/
|
|
573
|
+
/**
|
|
574
|
+
* Get GitHub credentials from OpenCode auth or environment variables
|
|
575
|
+
* Automatically detects username from token if not provided
|
|
576
|
+
*/
|
|
577
|
+
async function getGithubCredentials() {
|
|
578
|
+
// Try OpenCode auth.json first
|
|
579
|
+
try {
|
|
580
|
+
const authPath = path.join(os.homedir(), '.local', 'share', 'opencode', 'auth.json');
|
|
581
|
+
const authContent = fs.readFileSync(authPath, 'utf8');
|
|
582
|
+
const auth = JSON.parse(authContent);
|
|
583
|
+
if (auth['github-copilot']?.access) {
|
|
584
|
+
const token = auth['github-copilot'].access;
|
|
585
|
+
// Query GitHub API to get username from token
|
|
586
|
+
const username = await getGitHubUsername(token);
|
|
587
|
+
if (username) {
|
|
588
|
+
return { token, username };
|
|
589
|
+
}
|
|
590
|
+
}
|
|
591
|
+
}
|
|
592
|
+
catch {
|
|
593
|
+
// Fall through to env vars
|
|
594
|
+
}
|
|
595
|
+
// Fallback to environment variables
|
|
596
|
+
const token = process.env.GITHUB_TOKEN;
|
|
597
|
+
const username = process.env.GITHUB_USERNAME;
|
|
598
|
+
if (!token || !username) {
|
|
599
|
+
return null;
|
|
600
|
+
}
|
|
601
|
+
return { token, username };
|
|
602
|
+
}
|
|
603
|
+
/**
|
|
604
|
+
* Query GitHub API to get authenticated user's username
|
|
605
|
+
*/
|
|
606
|
+
async function getGitHubUsername(token) {
|
|
607
|
+
try {
|
|
608
|
+
const response = await fetch('https://api.github.com/user', {
|
|
609
|
+
headers: {
|
|
610
|
+
'Accept': 'application/vnd.github+json',
|
|
611
|
+
'Authorization': `Bearer ${token}`,
|
|
612
|
+
'X-GitHub-Api-Version': '2022-11-28',
|
|
613
|
+
},
|
|
614
|
+
});
|
|
615
|
+
if (!response.ok) {
|
|
616
|
+
return null;
|
|
617
|
+
}
|
|
618
|
+
const data = await response.json();
|
|
619
|
+
return data.login;
|
|
620
|
+
}
|
|
621
|
+
catch {
|
|
622
|
+
return null;
|
|
623
|
+
}
|
|
624
|
+
}
|
|
625
|
+
/**
|
|
626
|
+
* Format GitHub Copilot usage as ASCII boxed table
|
|
627
|
+
*/
|
|
628
|
+
function formatGitHubOutput(data) {
|
|
629
|
+
const { copilot_plan, chat_enabled, quota_reset_date, quota_snapshots } = data;
|
|
630
|
+
const BOX_WIDTH = 58;
|
|
631
|
+
let output = '';
|
|
632
|
+
// Header
|
|
633
|
+
output += '╔' + '═'.repeat(BOX_WIDTH - 2) + '╗\n';
|
|
634
|
+
output += '║ ║\n';
|
|
635
|
+
output += '║ GitHub Copilot Usage Statistics ║\n';
|
|
636
|
+
output += '║ ║\n';
|
|
637
|
+
output += '╠' + '═'.repeat(BOX_WIDTH - 2) + '╣\n';
|
|
638
|
+
output += '║ Platform: GitHub ║\n';
|
|
639
|
+
output += `║ Plan: ${copilot_plan.padEnd(50)}║\n`;
|
|
640
|
+
output += `║ Chat: ${chat_enabled ? 'Enabled' : 'Disabled'.padEnd(49)}║\n`;
|
|
641
|
+
output += `║ Reset: ${quota_reset_date.substring(0, 10).padEnd(50)}║\n`;
|
|
642
|
+
output += '╠' + '═'.repeat(BOX_WIDTH - 2) + '╣\n';
|
|
643
|
+
output += '║ QUOTA USAGE ║\n';
|
|
644
|
+
output += '╟──────────────────────────────────────────────────────────╢\n';
|
|
645
|
+
// Format each quota type
|
|
646
|
+
const quotas = [
|
|
647
|
+
{ name: 'Completions', data: quota_snapshots.completions },
|
|
648
|
+
{ name: 'Chat', data: quota_snapshots.chat },
|
|
649
|
+
{ name: 'Premium', data: quota_snapshots.premium_interactions },
|
|
650
|
+
];
|
|
651
|
+
for (const quota of quotas) {
|
|
652
|
+
const { name, data } = quota;
|
|
653
|
+
const remaining = data.unlimited ? 'Unlimited' : data.quota_remaining.toLocaleString();
|
|
654
|
+
const percent = data.unlimited ? 100 : data.percent_remaining;
|
|
655
|
+
const entitlement = data.unlimited ? 'Unlimited' : data.entitlement.toLocaleString();
|
|
656
|
+
// Progress bar
|
|
657
|
+
const filled = Math.floor(percent / 5);
|
|
658
|
+
const empty = 20 - filled;
|
|
659
|
+
const progress = '#'.repeat(filled) + '-'.repeat(empty);
|
|
660
|
+
const percentStr = data.unlimited ? '∞' : `${percent}%`;
|
|
661
|
+
output += `║ ${name}:`.padEnd(12);
|
|
662
|
+
output += `${remaining.padEnd(15)}${progress} ${percentStr.padEnd(4)}║\n`;
|
|
663
|
+
output += `║ Entitlement: ${entitlement.padEnd(36)}║\n`;
|
|
664
|
+
if (data.overage_permitted && !data.unlimited) {
|
|
665
|
+
output += `║ Overage: ${data.overage_count.toLocaleString()} used${' '.repeat(41)}║\n`;
|
|
666
|
+
}
|
|
667
|
+
}
|
|
668
|
+
output += '╚' + '═'.repeat(BOX_WIDTH - 2) + '╝\n';
|
|
669
|
+
return output;
|
|
670
|
+
}
|
|
671
|
+
export const GlmQuotaPlugin = async () => {
|
|
672
|
+
return {
|
|
673
|
+
tool: {
|
|
674
|
+
glm_quota: tool({
|
|
675
|
+
description: 'Query Z.ai GLM Coding Plan and GitHub Copilot Pro usage statistics - automatically shows quota every 5 minutes during chat',
|
|
676
|
+
args: {},
|
|
677
|
+
async execute() {
|
|
678
|
+
try {
|
|
679
|
+
const credentials = await getCredentials();
|
|
680
|
+
if (!credentials) {
|
|
681
|
+
return createCredentialError();
|
|
682
|
+
}
|
|
683
|
+
return await queryAllUsage(credentials);
|
|
684
|
+
}
|
|
685
|
+
catch (error) {
|
|
686
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
687
|
+
// If error message is already boxed (starts with top border), return it as is
|
|
688
|
+
if (errorMessage.trim().startsWith('╔') && errorMessage.includes('╚')) {
|
|
689
|
+
return errorMessage;
|
|
690
|
+
}
|
|
691
|
+
// Otherwise, wrap raw error in a box
|
|
692
|
+
return createBoxedError(errorMessage);
|
|
693
|
+
}
|
|
694
|
+
},
|
|
695
|
+
}),
|
|
696
|
+
copilot_quota: tool({
|
|
697
|
+
description: 'Query GitHub Copilot Pro usage statistics including monthly requests, costs, and model usage',
|
|
698
|
+
args: {},
|
|
699
|
+
async execute() {
|
|
700
|
+
try {
|
|
701
|
+
const credentials = await getGithubCredentials();
|
|
702
|
+
if (!credentials) {
|
|
703
|
+
return createGitHubCredentialError();
|
|
704
|
+
}
|
|
705
|
+
const usage = await queryGitHubUsage(credentials.token);
|
|
706
|
+
return formatGitHubOutput(usage);
|
|
707
|
+
}
|
|
708
|
+
catch (error) {
|
|
709
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
710
|
+
// If error message is already boxed (starts with top border), return it as is
|
|
711
|
+
if (errorMessage.trim().startsWith('╔') && errorMessage.includes('╚')) {
|
|
712
|
+
return errorMessage;
|
|
713
|
+
}
|
|
714
|
+
// Otherwise, wrap raw error in a box
|
|
715
|
+
return createBoxedError(errorMessage);
|
|
716
|
+
}
|
|
717
|
+
},
|
|
718
|
+
}),
|
|
719
|
+
},
|
|
720
|
+
};
|
|
721
|
+
};
|
|
722
|
+
export default GlmQuotaPlugin;
|
|
723
|
+
//# sourceMappingURL=index.js.map
|