opencode-glm-quota 1.3.4 → 1.4.1
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/README.md +9 -3
- package/bin/install.js +35 -25
- package/dist/index.js +55 -37
- package/dist/utils/progress-bar.js +1 -1
- package/integration/agents/glm-quota-exec.md +20 -0
- package/package.json +1 -1
- package/integration/opencode.jsonc +0 -10
package/README.md
CHANGED
|
@@ -3,6 +3,12 @@
|
|
|
3
3
|
[](https://www.npmjs.com/package/opencode-glm-quota)
|
|
4
4
|
[](https://opensource.org/licenses/MIT)
|
|
5
5
|
[](https://github.com/guyinwonder168/opencode-glm-quota/actions)
|
|
6
|
+
[](https://sonarcloud.io/summary/new_code?id=guyinwonder168_opencode-glm-quota)
|
|
7
|
+
---
|
|
8
|
+
[](https://sonarcloud.io/summary/new_code?id=guyinwonder168_opencode-glm-quota)
|
|
9
|
+
[](https://sonarcloud.io/summary/new_code?id=guyinwonder168_opencode-glm-quota)
|
|
10
|
+
[](https://sonarcloud.io/summary/new_code?id=guyinwonder168_opencode-glm-quota)
|
|
11
|
+
[](https://sonarcloud.io/summary/new_code?id=guyinwonder168_opencode-glm-quota)
|
|
6
12
|
|
|
7
13
|
OpenCode plugin to query Z.ai GLM Coding Plan usage statistics with real-time quota monitoring, model usage tracking, and MCP tool usage.
|
|
8
14
|
|
|
@@ -32,7 +38,7 @@ npx opencode-glm-quota install
|
|
|
32
38
|
- Copies `/glm_quota` command to `~/.config/opencode/command/glm_quota.md`
|
|
33
39
|
- Copies skill documentation to `~/.config/opencode/skills/glm-quota/SKILL.md`
|
|
34
40
|
- Automatically adds plugin to your OpenCode config
|
|
35
|
-
-
|
|
41
|
+
- Copies agent to `~/.config/opencode/agents/`
|
|
36
42
|
- Supports `--force` flag to overwrite existing files
|
|
37
43
|
|
|
38
44
|
### Uninstall
|
|
@@ -49,7 +55,7 @@ npx opencode-glm-quota uninstall --global
|
|
|
49
55
|
- Removes `/glm_quota` command
|
|
50
56
|
- Deletes `skills/glm-quota/SKILL.md`
|
|
51
57
|
- Removes plugin entry from OpenCode config
|
|
52
|
-
- Removes
|
|
58
|
+
- Removes agent file and legacy config
|
|
53
59
|
- Runs `npm remove opencode-glm-quota` (or `--global`)
|
|
54
60
|
|
|
55
61
|
### Option 2: From GitHub
|
|
@@ -252,9 +258,9 @@ src/
|
|
|
252
258
|
progress-bar.ts # ASCII progress bar rendering
|
|
253
259
|
time-window.ts # Rolling window calculation
|
|
254
260
|
integration/
|
|
261
|
+
agents/glm-quota-exec.md # Minimal executor agent (Markdown)
|
|
255
262
|
command/glm_quota.md # /glm_quota slash command
|
|
256
263
|
skills/glm-quota/SKILL.md # Skill documentation
|
|
257
|
-
opencode.jsonc # Agent configuration (JSONC)
|
|
258
264
|
bin/
|
|
259
265
|
install.js # Installation script
|
|
260
266
|
dist/ # Compiled JavaScript (generated)
|
package/bin/install.js
CHANGED
|
@@ -27,11 +27,12 @@ const __dirname = path.dirname(__filename)
|
|
|
27
27
|
const SOURCE_DIR = path.join(__dirname, '..', 'integration')
|
|
28
28
|
const COMMAND_FILE = path.join(SOURCE_DIR, 'command', 'glm_quota.md')
|
|
29
29
|
const SKILL_FILE = path.join(SOURCE_DIR, 'skills', 'glm-quota', 'SKILL.md')
|
|
30
|
-
const
|
|
30
|
+
const AGENT_FILE = path.join(SOURCE_DIR, 'agents', 'glm-quota-exec.md')
|
|
31
31
|
|
|
32
32
|
const CONFIG_DIR = path.join(os.homedir(), '.config', 'opencode')
|
|
33
33
|
const TARGET_COMMAND = path.join(CONFIG_DIR, 'command', 'glm_quota.md')
|
|
34
34
|
const TARGET_SKILL = path.join(CONFIG_DIR, 'skills', 'glm-quota', 'SKILL.md')
|
|
35
|
+
const TARGET_AGENT = path.join(CONFIG_DIR, 'agents', 'glm-quota-exec.md')
|
|
35
36
|
|
|
36
37
|
// Check which config file exists (opencode.json or opencode.jsonc)
|
|
37
38
|
const TARGET_CONFIG_JSON = path.join(CONFIG_DIR, 'opencode.json')
|
|
@@ -181,25 +182,42 @@ function installSkill(force) {
|
|
|
181
182
|
}
|
|
182
183
|
|
|
183
184
|
/**
|
|
184
|
-
*
|
|
185
|
+
* Install agent file
|
|
185
186
|
*/
|
|
186
|
-
function
|
|
187
|
-
|
|
187
|
+
function installAgent(force) {
|
|
188
|
+
if (fileExists(TARGET_AGENT) && !force) {
|
|
189
|
+
if (!promptConfirm(`Agent file exists: ${TARGET_AGENT}\nOverwrite?`)) {
|
|
190
|
+
console.log(` ⊘ Skipped ${TARGET_AGENT}`)
|
|
191
|
+
return
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
copyFile(AGENT_FILE, TARGET_AGENT)
|
|
196
|
+
console.log(` ✓ Created ${TARGET_AGENT}`)
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* Update plugin configuration and cleanup old JSON agent
|
|
201
|
+
*/
|
|
202
|
+
function updatePluginConfig() {
|
|
203
|
+
// Parse existing config if it exists
|
|
188
204
|
let existingConfig = {}
|
|
189
205
|
if (fileExists(TARGET_CONFIG)) {
|
|
190
206
|
existingConfig = parseConfig(TARGET_CONFIG)
|
|
191
|
-
// REMOVE old 'options' field to prevent verbose agent output
|
|
192
|
-
if (existingConfig.agent?.['glm-quota-exec']?.options) {
|
|
193
|
-
delete existingConfig.agent['glm-quota-exec'].options
|
|
194
|
-
}
|
|
195
207
|
}
|
|
196
208
|
|
|
197
|
-
// Parse new agent config from integration
|
|
198
|
-
const newConfig = parseConfig(AGENT_CONFIG)
|
|
199
|
-
|
|
200
209
|
const PLUGIN_NAME = 'opencode-glm-quota'
|
|
201
210
|
|
|
202
|
-
//
|
|
211
|
+
// CLEANUP: Remove old JSON agent config if it exists (migration from v1.3.x)
|
|
212
|
+
if (existingConfig.agent && existingConfig.agent['glm-quota-exec']) {
|
|
213
|
+
delete existingConfig.agent['glm-quota-exec']
|
|
214
|
+
if (Object.keys(existingConfig.agent).length === 0) {
|
|
215
|
+
delete existingConfig.agent
|
|
216
|
+
}
|
|
217
|
+
console.log(' ✓ Removed old JSON agent config (migrated to Markdown)')
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// Handle both "plugin" array and "plugins" array
|
|
203
221
|
// Check for "plugin" array first (user's config uses this)
|
|
204
222
|
const pluginArrayName = existingConfig.plugin ? 'plugin' : 'plugins'
|
|
205
223
|
|
|
@@ -210,16 +228,6 @@ function mergeConfig() {
|
|
|
210
228
|
|
|
211
229
|
const plugins = Array.isArray(existingConfig[pluginArrayName]) ? existingConfig[pluginArrayName] : []
|
|
212
230
|
|
|
213
|
-
// REPLACE entire glm-quota-exec agent (not merge, to remove old redundant fields)
|
|
214
|
-
if (newConfig.agent && newConfig.agent['glm-quota-exec']) {
|
|
215
|
-
if (!existingConfig.agent) {
|
|
216
|
-
existingConfig.agent = {}
|
|
217
|
-
}
|
|
218
|
-
existingConfig.agent['glm-quota-exec'] = newConfig.agent['glm-quota-exec']
|
|
219
|
-
} else if (!existingConfig.agent && newConfig.agent) {
|
|
220
|
-
existingConfig.agent = newConfig.agent
|
|
221
|
-
}
|
|
222
|
-
|
|
223
231
|
// Only add if not already present
|
|
224
232
|
if (!plugins.includes(PLUGIN_NAME)) {
|
|
225
233
|
plugins.push(PLUGIN_NAME)
|
|
@@ -229,9 +237,9 @@ function mergeConfig() {
|
|
|
229
237
|
console.log(` ⊙ Plugin ${PLUGIN_NAME} already in ${pluginArrayName} array`)
|
|
230
238
|
}
|
|
231
239
|
|
|
232
|
-
// Write
|
|
240
|
+
// Write config back to same file (opencode.json or opencode.jsonc)
|
|
233
241
|
writeConfig(TARGET_CONFIG, existingConfig)
|
|
234
|
-
console.log(` ✓
|
|
242
|
+
console.log(` ✓ Updated ${path.basename(TARGET_CONFIG)}`)
|
|
235
243
|
}
|
|
236
244
|
|
|
237
245
|
/**
|
|
@@ -303,6 +311,7 @@ function removePackage(globalFlag) {
|
|
|
303
311
|
function uninstall(globalFlag) {
|
|
304
312
|
removeFile(TARGET_COMMAND, TARGET_COMMAND)
|
|
305
313
|
removeDirectory(path.dirname(TARGET_SKILL), path.dirname(TARGET_SKILL))
|
|
314
|
+
removeFile(TARGET_AGENT, TARGET_AGENT)
|
|
306
315
|
removeConfig()
|
|
307
316
|
removePackage(globalFlag)
|
|
308
317
|
}
|
|
@@ -335,7 +344,8 @@ function main() {
|
|
|
335
344
|
// Install integration files
|
|
336
345
|
installCommand(forceFlag)
|
|
337
346
|
installSkill(forceFlag)
|
|
338
|
-
|
|
347
|
+
installAgent(forceFlag)
|
|
348
|
+
updatePluginConfig()
|
|
339
349
|
|
|
340
350
|
console.log()
|
|
341
351
|
console.log('✓ Installation complete!')
|
package/dist/index.js
CHANGED
|
@@ -25,6 +25,17 @@ const CANDIDATE_PROVIDER_IDS = [
|
|
|
25
25
|
'zhipu',
|
|
26
26
|
'zhipuai'
|
|
27
27
|
];
|
|
28
|
+
/**
|
|
29
|
+
* Box drawing layout constants
|
|
30
|
+
* Total line width: LEFT_BORDER (1) + LEFT_PAD (2) + CONTENT (56) + RIGHT_PAD (0) + RIGHT_BORDER (1) = 60
|
|
31
|
+
* Border lines: ╔ + 58 chars + ╗ = 60 total
|
|
32
|
+
* Content lines: ║ + 2 spaces + 56 content + 2 spaces + ║ = 60 total (formatted by formatBoxLine)
|
|
33
|
+
*/
|
|
34
|
+
const BOX_WIDTH = {
|
|
35
|
+
CONTENT: 56, // Available content width
|
|
36
|
+
BORDER_CHARS: 58, // Character count between borders (╔══...══╗)
|
|
37
|
+
TOTAL: 60 // Total line width including borders
|
|
38
|
+
};
|
|
28
39
|
// ============================================================================
|
|
29
40
|
// CREDENTIAL DISCOVERY
|
|
30
41
|
// ============================================================================
|
|
@@ -97,20 +108,33 @@ async function getCredentials() {
|
|
|
97
108
|
}
|
|
98
109
|
/**
|
|
99
110
|
* Create error message for missing credentials
|
|
100
|
-
* @returns Error message with setup instructions
|
|
111
|
+
* @returns Error message with setup instructions (box formatted)
|
|
101
112
|
*/
|
|
102
113
|
function createCredentialError() {
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
+
const header = '❌ Z.ai Credentials Not Found';
|
|
115
|
+
const instructions = [
|
|
116
|
+
'',
|
|
117
|
+
'Please authenticate first:',
|
|
118
|
+
'',
|
|
119
|
+
'1. Run /connect command in OpenCode TUI',
|
|
120
|
+
'2. Select "Z.AI Coding Plan" or "Z.AI"',
|
|
121
|
+
'3. Or "Zhipu" (for China region)',
|
|
122
|
+
'',
|
|
123
|
+
'For dev/testing, set environment:',
|
|
124
|
+
'- ZAI_API_KEY (global)',
|
|
125
|
+
'- ZHIPU_API_KEY (China)',
|
|
126
|
+
''
|
|
127
|
+
];
|
|
128
|
+
const lines = [];
|
|
129
|
+
lines.push('╔' + '═'.repeat(BOX_WIDTH.BORDER_CHARS) + '╗');
|
|
130
|
+
lines.push('║' + ' '.repeat(BOX_WIDTH.BORDER_CHARS) + '║');
|
|
131
|
+
lines.push(formatBoxLine(header, BOX_WIDTH.CONTENT));
|
|
132
|
+
lines.push('║' + ' '.repeat(BOX_WIDTH.BORDER_CHARS) + '║');
|
|
133
|
+
for (const instruction of instructions) {
|
|
134
|
+
lines.push(formatBoxLine(instruction, BOX_WIDTH.CONTENT));
|
|
135
|
+
}
|
|
136
|
+
lines.push('╚' + '═'.repeat(BOX_WIDTH.BORDER_CHARS) + '╝');
|
|
137
|
+
return lines.join('\n');
|
|
114
138
|
}
|
|
115
139
|
// ============================================================================
|
|
116
140
|
// RESPONSE PROCESSING
|
|
@@ -348,16 +372,14 @@ function isFullWidthCodePoint(codePoint) {
|
|
|
348
372
|
*/
|
|
349
373
|
function formatHeader(platformName, startTime, endTime) {
|
|
350
374
|
const lines = [];
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
lines.push('
|
|
354
|
-
lines.push('║' + ' '.repeat(
|
|
355
|
-
lines.push('
|
|
356
|
-
lines.push(
|
|
357
|
-
lines.push(
|
|
358
|
-
lines.push(
|
|
359
|
-
lines.push(formatBoxLine(`Period: ${startTime} → ${endTime}`, LINE_INDENT));
|
|
360
|
-
lines.push('╠' + '═'.repeat(LINE_CONTENT) + '╣');
|
|
375
|
+
lines.push('╔' + '═'.repeat(BOX_WIDTH.BORDER_CHARS) + '╗');
|
|
376
|
+
lines.push('║' + ' '.repeat(BOX_WIDTH.BORDER_CHARS) + '║');
|
|
377
|
+
lines.push('║' + ' Z.ai GLM Coding Plan Usage Statistics '.padStart(35).padEnd(BOX_WIDTH.BORDER_CHARS) + '║');
|
|
378
|
+
lines.push('║' + ' '.repeat(BOX_WIDTH.BORDER_CHARS) + '║');
|
|
379
|
+
lines.push('╠' + '═'.repeat(BOX_WIDTH.BORDER_CHARS) + '╣');
|
|
380
|
+
lines.push(formatBoxLine(`Platform: ${platformName}`, BOX_WIDTH.CONTENT));
|
|
381
|
+
lines.push(formatBoxLine(`Period: ${startTime} → ${endTime}`, BOX_WIDTH.CONTENT));
|
|
382
|
+
lines.push('╠' + '═'.repeat(BOX_WIDTH.BORDER_CHARS) + '╣');
|
|
361
383
|
return lines;
|
|
362
384
|
}
|
|
363
385
|
/**
|
|
@@ -367,32 +389,30 @@ function formatHeader(platformName, startTime, endTime) {
|
|
|
367
389
|
*/
|
|
368
390
|
function formatQuotaLimits(quotaData) {
|
|
369
391
|
const lines = [];
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
lines.push(formatBoxLine('QUOTA LIMITS', LINE_INDENT));
|
|
373
|
-
lines.push('╟' + '─'.repeat(LINE_CONTENT) + '╢');
|
|
392
|
+
lines.push(formatBoxLine('QUOTA LIMITS', BOX_WIDTH.CONTENT));
|
|
393
|
+
lines.push('╟' + '─'.repeat(BOX_WIDTH.BORDER_CHARS) + '╢');
|
|
374
394
|
const limits = quotaData?.limits;
|
|
375
395
|
if (limits && Array.isArray(limits)) {
|
|
376
396
|
for (const limit of limits) {
|
|
377
397
|
const pct = typeof limit.percentage === 'number' ? limit.percentage : 0;
|
|
378
398
|
const line = formatProgressLine(limit.type || 'Unknown', pct);
|
|
379
|
-
lines.push(formatProgressBoxLine(line,
|
|
399
|
+
lines.push(formatProgressBoxLine(line, BOX_WIDTH.CONTENT));
|
|
380
400
|
if (limit.nextResetTime !== undefined) {
|
|
381
401
|
const resetMsg = formatTimeUntilReset(limit.nextResetTime);
|
|
382
402
|
if (resetMsg) {
|
|
383
|
-
lines.push(formatBoxLine(resetMsg,
|
|
403
|
+
lines.push(formatBoxLine(resetMsg, BOX_WIDTH.CONTENT));
|
|
384
404
|
}
|
|
385
405
|
}
|
|
386
406
|
if (limit.currentValue !== undefined && limit.total !== undefined) {
|
|
387
407
|
const usageStr = ' Used: ' + limit.currentValue + '/' + limit.total;
|
|
388
|
-
lines.push(formatBoxLine(usageStr,
|
|
408
|
+
lines.push(formatBoxLine(usageStr, BOX_WIDTH.CONTENT));
|
|
389
409
|
}
|
|
390
410
|
}
|
|
391
411
|
}
|
|
392
412
|
else {
|
|
393
|
-
lines.push(formatBoxLine('No quota data available',
|
|
413
|
+
lines.push(formatBoxLine('No quota data available', BOX_WIDTH.CONTENT));
|
|
394
414
|
}
|
|
395
|
-
lines.push('╠' + '═'.repeat(
|
|
415
|
+
lines.push('╠' + '═'.repeat(BOX_WIDTH.BORDER_CHARS) + '╣');
|
|
396
416
|
return lines;
|
|
397
417
|
}
|
|
398
418
|
/**
|
|
@@ -406,9 +426,8 @@ function formatQuotaLimits(quotaData) {
|
|
|
406
426
|
*/
|
|
407
427
|
function formatDataSection(title, data, formatter, quotaData, noDataMessage, LINE_INDENT) {
|
|
408
428
|
const lines = [];
|
|
409
|
-
const LINE_CONTENT = 58;
|
|
410
429
|
lines.push(formatBoxLine(title, LINE_INDENT));
|
|
411
|
-
lines.push('╟' + '─'.repeat(
|
|
430
|
+
lines.push('╟' + '─'.repeat(BOX_WIDTH.BORDER_CHARS) + '╢');
|
|
412
431
|
if (data) {
|
|
413
432
|
const formattedLines = formatter(data, quotaData);
|
|
414
433
|
for (const line of formattedLines) {
|
|
@@ -418,7 +437,7 @@ function formatDataSection(title, data, formatter, quotaData, noDataMessage, LIN
|
|
|
418
437
|
else {
|
|
419
438
|
lines.push(formatBoxLine(noDataMessage, LINE_INDENT));
|
|
420
439
|
}
|
|
421
|
-
lines.push('╠' + '═'.repeat(
|
|
440
|
+
lines.push('╠' + '═'.repeat(BOX_WIDTH.BORDER_CHARS) + '╣');
|
|
422
441
|
return lines;
|
|
423
442
|
}
|
|
424
443
|
/**
|
|
@@ -441,11 +460,10 @@ function formatFooter() {
|
|
|
441
460
|
function formatOutput(platform, startTime, endTime, quotaData, modelData, toolData) {
|
|
442
461
|
const lines = [];
|
|
443
462
|
const platformName = getPlatformName(platform);
|
|
444
|
-
const LINE_INDENT = 56;
|
|
445
463
|
lines.push(...formatHeader(platformName, startTime, endTime));
|
|
446
464
|
lines.push(...formatQuotaLimits(quotaData));
|
|
447
|
-
lines.push(...formatDataSection('MODEL USAGE (24h)', modelData, formatModelUsage, quotaData, 'No model usage data available',
|
|
448
|
-
lines.push(...formatDataSection('TOOL/MCP USAGE (24h)', toolData, formatToolUsage, quotaData, 'No tool usage data available',
|
|
465
|
+
lines.push(...formatDataSection('MODEL USAGE (24h)', modelData, formatModelUsage, quotaData, 'No model usage data available', BOX_WIDTH.CONTENT));
|
|
466
|
+
lines.push(...formatDataSection('TOOL/MCP USAGE (24h)', toolData, formatToolUsage, quotaData, 'No tool usage data available', BOX_WIDTH.CONTENT));
|
|
449
467
|
lines.push(...formatFooter());
|
|
450
468
|
return lines.join('\n');
|
|
451
469
|
}
|
|
@@ -34,7 +34,7 @@ export function formatPercentage(percentage, decimals = 1) {
|
|
|
34
34
|
* @returns Formatted progress bar line
|
|
35
35
|
*/
|
|
36
36
|
export function formatProgressLine(label, percentage) {
|
|
37
|
-
const bar = createProgressBar(percentage, { width:
|
|
37
|
+
const bar = createProgressBar(percentage, { width: 12 });
|
|
38
38
|
const pctStr = formatPercentage(percentage).padStart(6);
|
|
39
39
|
const labelStr = label.slice(0, 20).padEnd(20);
|
|
40
40
|
return `${labelStr} [${bar}] ${pctStr}`;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Minimal executor for GLM quota tool
|
|
3
|
+
mode: subagent
|
|
4
|
+
hidden: true
|
|
5
|
+
permission:
|
|
6
|
+
edit: deny
|
|
7
|
+
bash: deny
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
CRITICAL INSTRUCTION: When the glm_quota tool returns output, you MUST return it EXACTLY as received, character-for-character, with NO modifications.
|
|
11
|
+
|
|
12
|
+
- Do NOT add introductory text
|
|
13
|
+
- Do NOT summarize
|
|
14
|
+
- Do NOT reformat
|
|
15
|
+
- Do NOT explain anything
|
|
16
|
+
- Do NOT add any commentary
|
|
17
|
+
- Return ONLY the raw string from the tool
|
|
18
|
+
|
|
19
|
+
Example of WRONG behavior: 'Here is your usage: [summary]'
|
|
20
|
+
Example of CORRECT behavior: [paste entire tool output verbatim]
|
package/package.json
CHANGED
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"$schema": "https://opencode.ai/config.json",
|
|
3
|
-
"agent": {
|
|
4
|
-
"glm-quota-exec": {
|
|
5
|
-
"mode": "subagent",
|
|
6
|
-
"system": "CRITICAL INSTRUCTION: When the glm_quota tool returns output, you MUST return it EXACTLY as received, character-for-character, with NO modifications.\n\n- Do NOT add introductory text\n- Do NOT summarize\n- Do NOT reformat\n- Do NOT explain anything\n- Do NOT add any commentary\n- Return ONLY the raw string from the tool\n\nExample of WRONG behavior: 'Here is your usage: [summary]'\nExample of CORRECT behavior: [paste entire tool output verbatim]",
|
|
7
|
-
"permission": {}
|
|
8
|
-
}
|
|
9
|
-
}
|
|
10
|
-
}
|