opencode-glm-quota 1.3.3 → 1.4.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/README.md CHANGED
@@ -3,6 +3,12 @@
3
3
  [![npm version](https://img.shields.io/npm/v/opencode-glm-quota.svg)](https://www.npmjs.com/package/opencode-glm-quota)
4
4
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
5
5
  [![Build Status](https://github.com/guyinwonder168/opencode-glm-quota/workflows/CI/badge.svg)](https://github.com/guyinwonder168/opencode-glm-quota/actions)
6
+ [![SonarQube Cloud](https://sonarcloud.io/images/project_badges/sonarcloud-light.svg)](https://sonarcloud.io/summary/new_code?id=guyinwonder168_opencode-glm-quota)
7
+ ---
8
+ [![Reliability Rating](https://sonarcloud.io/api/project_badges/measure?project=guyinwonder168_opencode-glm-quota&metric=reliability_rating)](https://sonarcloud.io/summary/new_code?id=guyinwonder168_opencode-glm-quota)
9
+ [![Security Rating](https://sonarcloud.io/api/project_badges/measure?project=guyinwonder168_opencode-glm-quota&metric=security_rating)](https://sonarcloud.io/summary/new_code?id=guyinwonder168_opencode-glm-quota)
10
+ [![Maintainability Rating](https://sonarcloud.io/api/project_badges/measure?project=guyinwonder168_opencode-glm-quota&metric=sqale_rating)](https://sonarcloud.io/summary/new_code?id=guyinwonder168_opencode-glm-quota)
11
+ [![Quality Gate Status](https://sonarcloud.io/api/project_badges/measure?project=guyinwonder168_opencode-glm-quota&metric=alert_status)](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
- - Merges agent configuration into `~/.config/opencode/opencode.json`
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 `glm-quota-exec` agent config
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 AGENT_CONFIG = path.join(SOURCE_DIR, 'opencode.jsonc')
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
- * Merge agent configuration and add plugin to plugins array
185
+ * Install agent file
185
186
  */
186
- function mergeConfig() {
187
- // Parse existing config if it exists (same file type will be written)
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
- // Handle both "plugin" array and "agent" section
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 merged config back to same file (opencode.json or opencode.jsonc)
240
+ // Write config back to same file (opencode.json or opencode.jsonc)
233
241
  writeConfig(TARGET_CONFIG, existingConfig)
234
- console.log(` ✓ Merged configuration into ${path.basename(TARGET_CONFIG)}`)
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
- mergeConfig()
347
+ installAgent(forceFlag)
348
+ updatePluginConfig()
339
349
 
340
350
  console.log()
341
351
  console.log('✓ Installation complete!')
package/dist/index.js CHANGED
@@ -249,10 +249,17 @@ function formatToolUsage(data, quotaData) {
249
249
  * @returns Formatted line with box characters
250
250
  */
251
251
  function formatBoxLine(content, lineIndent) {
252
- const trimmed = trimToDisplayWidth(content, lineIndent);
252
+ const trimmed = trimToDisplayWidth(content, lineIndent, 0);
253
253
  const padding = Math.max(lineIndent - getDisplayWidth(trimmed), 0);
254
254
  return '║ ' + trimmed + ' '.repeat(padding) + '║';
255
255
  }
256
+ function formatProgressBoxLine(content, lineIndent) {
257
+ const gap = 2;
258
+ const contentWidth = Math.max(lineIndent - gap, 0);
259
+ const trimmed = trimToDisplayWidth(content, contentWidth, 0);
260
+ const padding = Math.max(contentWidth - getDisplayWidth(trimmed), 0);
261
+ return '║ ' + trimmed + ' '.repeat(padding + gap) + '║';
262
+ }
256
263
  function getDisplayWidth(text) {
257
264
  let width = 0;
258
265
  for (let i = 0; i < text.length; i += 1) {
@@ -270,9 +277,10 @@ function getDisplayWidth(text) {
270
277
  }
271
278
  return width;
272
279
  }
273
- function trimToDisplayWidth(text, maxWidth) {
280
+ function trimToDisplayWidth(text, maxWidth, reserveRightPadding) {
274
281
  let width = 0;
275
282
  let result = '';
283
+ const allowedWidth = Math.max(maxWidth - reserveRightPadding, 0);
276
284
  for (let i = 0; i < text.length; i += 1) {
277
285
  const codePoint = text.codePointAt(i);
278
286
  if (codePoint === undefined) {
@@ -285,7 +293,7 @@ function trimToDisplayWidth(text, maxWidth) {
285
293
  continue;
286
294
  }
287
295
  const charWidth = isEmojiCodePoint(codePoint) || isFullWidthCodePoint(codePoint) ? 2 : 1;
288
- if (width + charWidth > maxWidth) {
296
+ if (width + charWidth > allowedWidth) {
289
297
  break;
290
298
  }
291
299
  result += String.fromCodePoint(codePoint);
@@ -368,7 +376,7 @@ function formatQuotaLimits(quotaData) {
368
376
  for (const limit of limits) {
369
377
  const pct = typeof limit.percentage === 'number' ? limit.percentage : 0;
370
378
  const line = formatProgressLine(limit.type || 'Unknown', pct);
371
- lines.push(formatBoxLine(line, LINE_INDENT));
379
+ lines.push(formatProgressBoxLine(line, LINE_INDENT));
372
380
  if (limit.nextResetTime !== undefined) {
373
381
  const resetMsg = formatTimeUntilReset(limit.nextResetTime);
374
382
  if (resetMsg) {
@@ -34,8 +34,8 @@ 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: 26 });
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
- return `${labelStr} [${bar}] ${pctStr}`;
40
+ return `${labelStr} [${bar}] ${pctStr}`;
41
41
  }
@@ -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,6 +1,6 @@
1
1
  {
2
2
  "name": "opencode-glm-quota",
3
- "version": "1.3.3",
3
+ "version": "1.4.0",
4
4
  "type": "module",
5
5
  "description": "OpenCode plugin to query Z.ai GLM Coding Plan usage statistics including quota limits, model usage, and MCP tool usage",
6
6
  "main": "dist/index.js",
@@ -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
- }