opencode-glm-quota 1.3.0 → 1.3.2

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
@@ -21,22 +21,37 @@ OpenCode plugin to query Z.ai GLM Coding Plan usage statistics with real-time qu
21
21
  ### Option 1: npm (Recommended)
22
22
 
23
23
  ```bash
24
- # Install the plugin
25
- npm install @opencode-glm-quota/plugin
24
+ # Install plugin
25
+ npm install opencode-glm-quota
26
26
 
27
- # Run the installer to configure OpenCode
28
- npx @opencode-glm-quota/plugin install
29
-
30
- # Add to your OpenCode config (~/.config/opencode/opencode.json)
31
- echo '"@opencode-glm-quota/plugin"' >> ~/.config/opencode/opencode.json
27
+ # Run installer to configure OpenCode
28
+ npx opencode-glm-quota install
32
29
  ```
33
30
 
34
31
  **What the installer does:**
35
32
  - Copies `/glm_quota` command to `~/.config/opencode/command/glm_quota.md`
36
- - Copies skill documentation to `~/.config/opencode/skill/glm-quota-skill.md`
33
+ - Copies skill documentation to `~/.config/opencode/skills/glm-quota/SKILL.md`
34
+ - Automatically adds plugin to your OpenCode config
37
35
  - Merges agent configuration into `~/.config/opencode/opencode.json`
38
36
  - Supports `--force` flag to overwrite existing files
39
37
 
38
+ ### Uninstall
39
+
40
+ ```bash
41
+ # Remove OpenCode integration files and config
42
+ npx opencode-glm-quota uninstall
43
+
44
+ # If installed globally
45
+ npx opencode-glm-quota uninstall --global
46
+ ```
47
+
48
+ **What the uninstaller does:**
49
+ - Removes `/glm_quota` command
50
+ - Deletes `skills/glm-quota/SKILL.md`
51
+ - Removes plugin entry from OpenCode config
52
+ - Removes `glm-quota-exec` agent config
53
+ - Runs `npm remove opencode-glm-quota` (or `--global`)
54
+
40
55
  ### Option 2: From GitHub
41
56
 
42
57
  ```bash
@@ -227,7 +242,7 @@ npm run prepublishOnly
227
242
 
228
243
  ```
229
244
  src/
230
- index.ts # Main plugin entry point (463 lines)
245
+ index.ts # Main plugin entry point
231
246
  api/
232
247
  client.ts # HTTPS client with timeout and error handling
233
248
  endpoints.ts # Platform-specific API endpoints
@@ -236,16 +251,16 @@ src/
236
251
  date-formatter.ts # Date/time formatting utilities
237
252
  progress-bar.ts # ASCII progress bar rendering
238
253
  time-window.ts # Rolling window calculation
239
- integration/
240
- command/glm_quota.md # /glm_quota slash command
241
- skill/glm-quota-skill.md # Skill documentation
242
- opencode.jsonc # Agent configuration (JSONC)
243
- bin/
244
- install.js # Installation script
245
- dist/ # Compiled JavaScript (generated)
246
- tests/ # Test suite
247
- package.json # Dependencies and scripts
248
- tsconfig.json # TypeScript configuration
254
+ integration/
255
+ command/glm_quota.md # /glm_quota slash command
256
+ skills/glm-quota/SKILL.md # Skill documentation
257
+ opencode.jsonc # Agent configuration (JSONC)
258
+ bin/
259
+ install.js # Installation script
260
+ dist/ # Compiled JavaScript (generated)
261
+ tests/ # Test suite
262
+ package.json # Dependencies and scripts
263
+ tsconfig.json # TypeScript configuration
249
264
  ```
250
265
 
251
266
  ### Code Style Guidelines
package/bin/install.js CHANGED
@@ -9,11 +9,13 @@
9
9
  * Usage:
10
10
  * node bin/install.js # Interactive install (ask before overwriting)
11
11
  * node bin/install.js --force # Force overwrite existing files
12
+ * node bin/install.js uninstall # Remove integration files and config
12
13
  */
13
14
 
14
15
  import * as fs from 'fs'
15
16
  import * as path from 'path'
16
17
  import * as os from 'os'
18
+ import { spawnSync } from 'child_process'
17
19
  import { parse as parseJsonc } from 'jsonc-parser'
18
20
 
19
21
  // ==========================================
@@ -24,12 +26,12 @@ const __filename = decodeURIComponent(new URL(import.meta.url).pathname)
24
26
  const __dirname = path.dirname(__filename)
25
27
  const SOURCE_DIR = path.join(__dirname, '..', 'integration')
26
28
  const COMMAND_FILE = path.join(SOURCE_DIR, 'command', 'glm_quota.md')
27
- const SKILL_FILE = path.join(SOURCE_DIR, 'skill', 'glm-quota-skill.md')
29
+ const SKILL_FILE = path.join(SOURCE_DIR, 'skills', 'glm-quota', 'SKILL.md')
28
30
  const AGENT_CONFIG = path.join(SOURCE_DIR, 'opencode.jsonc')
29
31
 
30
32
  const CONFIG_DIR = path.join(os.homedir(), '.config', 'opencode')
31
33
  const TARGET_COMMAND = path.join(CONFIG_DIR, 'command', 'glm_quota.md')
32
- const TARGET_SKILL = path.join(CONFIG_DIR, 'skill', 'glm-quota-skill.md')
34
+ const TARGET_SKILL = path.join(CONFIG_DIR, 'skills', 'glm-quota', 'SKILL.md')
33
35
 
34
36
  // Check which config file exists (opencode.json or opencode.jsonc)
35
37
  const TARGET_CONFIG_JSON = path.join(CONFIG_DIR, 'opencode.json')
@@ -72,6 +74,30 @@ function copyFile(source, destination) {
72
74
  fs.copyFileSync(source, destination)
73
75
  }
74
76
 
77
+ /**
78
+ * Remove file if it exists
79
+ */
80
+ function removeFile(filePath, label) {
81
+ if (fileExists(filePath)) {
82
+ fs.unlinkSync(filePath)
83
+ console.log(` ✓ Removed ${label}`)
84
+ } else {
85
+ console.log(` ⊙ Not found ${label}`)
86
+ }
87
+ }
88
+
89
+ /**
90
+ * Remove directory if it exists
91
+ */
92
+ function removeDirectory(dirPath, label) {
93
+ if (fileExists(dirPath)) {
94
+ fs.rmSync(dirPath, { recursive: true, force: true })
95
+ console.log(` ✓ Removed ${label}`)
96
+ } else {
97
+ console.log(` ⊙ Not found ${label}`)
98
+ }
99
+ }
100
+
75
101
  /**
76
102
  * Parse JSON or JSONC file
77
103
  */
@@ -144,14 +170,14 @@ function installCommand(force) {
144
170
  */
145
171
  function installSkill(force) {
146
172
  if (fileExists(TARGET_SKILL) && !force) {
147
- if (!promptConfirm(`Skill file exists: ${TARGET_SKILL}\nOverwrite?`)) {
173
+ if (!promptConfirm(`Skill directory exists: ${path.dirname(TARGET_SKILL)}\nOverwrite?`)) {
148
174
  console.log(` ⊘ Skipped ${TARGET_SKILL}`)
149
175
  return
150
176
  }
151
177
  }
152
178
 
153
179
  copyFile(SKILL_FILE, TARGET_SKILL)
154
- console.log(` ✓ Created ${TARGET_SKILL}`)
180
+ console.log(` ✓ Created ${path.join(path.basename(path.dirname(TARGET_SKILL)), path.basename(TARGET_SKILL))}`)
155
181
  }
156
182
 
157
183
  /**
@@ -162,36 +188,125 @@ function mergeConfig() {
162
188
  let existingConfig = {}
163
189
  if (fileExists(TARGET_CONFIG)) {
164
190
  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
+ }
165
195
  }
166
196
 
167
197
  // Parse new agent config from integration
168
198
  const newConfig = parseConfig(AGENT_CONFIG)
169
199
 
170
- // Merge agent definitions first
171
- const mergedConfig = deepMerge(existingConfig, newConfig)
200
+ const PLUGIN_NAME = 'opencode-glm-quota'
172
201
 
173
- // Ensure plugins array exists and add our plugin
174
- if (!mergedConfig.plugins) {
175
- mergedConfig.plugins = []
202
+ // Handle both "plugin" array and "agent" section
203
+ // Check for "plugin" array first (user's config uses this)
204
+ const pluginArrayName = existingConfig.plugin ? 'plugin' : 'plugins'
205
+
206
+ // Only ensure that array we're going to use exists, not both!
207
+ if (!existingConfig[pluginArrayName]) {
208
+ existingConfig[pluginArrayName] = []
176
209
  }
177
210
 
178
- const PLUGIN_NAME = '@opencode-glm-quota/plugin'
179
- const plugins = Array.isArray(mergedConfig.plugins) ? mergedConfig.plugins : []
211
+ const plugins = Array.isArray(existingConfig[pluginArrayName]) ? existingConfig[pluginArrayName] : []
212
+
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
+ }
180
222
 
181
223
  // Only add if not already present
182
224
  if (!plugins.includes(PLUGIN_NAME)) {
183
225
  plugins.push(PLUGIN_NAME)
184
- mergedConfig.plugins = plugins
185
- console.log(` ✓ Added ${PLUGIN_NAME} to plugins array`)
226
+ existingConfig[pluginArrayName] = plugins
227
+ console.log(` ✓ Added ${PLUGIN_NAME} to ${pluginArrayName} array`)
186
228
  } else {
187
- console.log(` ⊙ Plugin ${PLUGIN_NAME} already in plugins array`)
229
+ console.log(` ⊙ Plugin ${PLUGIN_NAME} already in ${pluginArrayName} array`)
188
230
  }
189
231
 
190
- // Write merged config back to the same file (opencode.json or opencode.jsonc)
191
- writeConfig(TARGET_CONFIG, mergedConfig)
232
+ // Write merged config back to same file (opencode.json or opencode.jsonc)
233
+ writeConfig(TARGET_CONFIG, existingConfig)
192
234
  console.log(` ✓ Merged configuration into ${path.basename(TARGET_CONFIG)}`)
193
235
  }
194
236
 
237
+ /**
238
+ * Remove plugin configuration and agent configuration
239
+ */
240
+ function removeConfig() {
241
+ if (!fileExists(TARGET_CONFIG)) {
242
+ console.log(` ⊙ Config not found: ${TARGET_CONFIG}`)
243
+ return
244
+ }
245
+
246
+ const PLUGIN_NAME = 'opencode-glm-quota'
247
+ const existingConfig = parseConfig(TARGET_CONFIG)
248
+ let changed = false
249
+
250
+ if (Array.isArray(existingConfig.plugin)) {
251
+ const next = existingConfig.plugin.filter((name) => name !== PLUGIN_NAME)
252
+ if (next.length !== existingConfig.plugin.length) {
253
+ existingConfig.plugin = next
254
+ changed = true
255
+ console.log(' ✓ Removed plugin from plugin array')
256
+ }
257
+ }
258
+
259
+ if (Array.isArray(existingConfig.plugins)) {
260
+ const next = existingConfig.plugins.filter((name) => name !== PLUGIN_NAME)
261
+ if (next.length !== existingConfig.plugins.length) {
262
+ existingConfig.plugins = next
263
+ changed = true
264
+ console.log(' ✓ Removed plugin from plugins array')
265
+ }
266
+ }
267
+
268
+ if (existingConfig.agent && existingConfig.agent['glm-quota-exec']) {
269
+ delete existingConfig.agent['glm-quota-exec']
270
+ if (Object.keys(existingConfig.agent).length === 0) {
271
+ delete existingConfig.agent
272
+ }
273
+ changed = true
274
+ console.log(' ✓ Removed glm-quota-exec agent config')
275
+ }
276
+
277
+ if (changed) {
278
+ writeConfig(TARGET_CONFIG, existingConfig)
279
+ console.log(` ✓ Updated ${path.basename(TARGET_CONFIG)}`)
280
+ } else {
281
+ console.log(' ⊙ No config changes needed')
282
+ }
283
+ }
284
+
285
+ /**
286
+ * Remove npm package
287
+ */
288
+ function removePackage(globalFlag) {
289
+ const args = ['remove', 'opencode-glm-quota']
290
+ if (globalFlag) {
291
+ args.push('--global')
292
+ }
293
+
294
+ const result = spawnSync('npm', args, { stdio: 'inherit' })
295
+ if (result.status !== 0) {
296
+ console.log(' ⊙ npm remove failed, remove manually if needed')
297
+ }
298
+ }
299
+
300
+ /**
301
+ * Uninstall integration files and configuration
302
+ */
303
+ function uninstall(globalFlag) {
304
+ removeFile(TARGET_COMMAND, TARGET_COMMAND)
305
+ removeDirectory(path.dirname(TARGET_SKILL), path.dirname(TARGET_SKILL))
306
+ removeConfig()
307
+ removePackage(globalFlag)
308
+ }
309
+
195
310
  // ==========================================
196
311
  // MAIN INSTALLATION FUNCTION
197
312
  // ==========================================
@@ -203,7 +318,17 @@ function main() {
203
318
  try {
204
319
  // Parse command line arguments
205
320
  const args = process.argv.slice(2)
321
+ const isUninstall = args.includes('uninstall')
206
322
  const forceFlag = args.includes('--force')
323
+ const globalFlag = args.includes('--global') || args.includes('-g')
324
+
325
+ if (isUninstall) {
326
+ console.log('✓ Uninstalling GLM Quota Plugin...\n')
327
+ uninstall(globalFlag)
328
+ console.log()
329
+ console.log('✓ Uninstall complete!')
330
+ return
331
+ }
207
332
 
208
333
  console.log('✓ Installing GLM Quota Plugin...\n')
209
334
 
@@ -224,6 +349,4 @@ function main() {
224
349
  }
225
350
 
226
351
  // Run installer
227
- if (import.meta.url === `file://${process.argv[1]}`) {
228
- main()
229
- }
352
+ main()
@@ -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);
37
+ const bar = createProgressBar(percentage, { width: 26 });
38
38
  const pctStr = formatPercentage(percentage).padStart(6);
39
- const labelStr = label.padEnd(20);
39
+ const labelStr = label.slice(0, 20).padEnd(20);
40
40
  return `${labelStr} [${bar}] ${pctStr}`;
41
41
  }
@@ -4,14 +4,7 @@
4
4
  "glm-quota-exec": {
5
5
  "mode": "subagent",
6
6
  "system": "You are a minimal tool executor. Your only purpose is to execute the glm_quota tool when requested. Do not explain, reason, or add any commentary. Simply call the tool and return its output directly.",
7
- "provider": "opencode",
8
- "options": {
9
- "system": "You are a minimal tool executor. Your only purpose is to execute the glm_quota tool when requested. Do not explain, reason, or add any commentary. Simply call the tool and return its output directly.",
10
- "provider": "opencode"
11
- },
12
7
  "permission": {}
13
8
  }
14
9
  }
15
- // Note: This agent definition is merged into user's existing OpenCode config
16
- // by the installer script to avoid overwriting their custom configurations
17
10
  }
package/package.json CHANGED
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "opencode-glm-quota",
3
- "version": "1.3.0",
3
+ "version": "1.3.2",
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",
7
7
  "types": "dist/index.d.ts",
8
8
  "bin": {
9
- "opencode-glm-quota-install": "./bin/install.js"
9
+ "opencode-glm-quota": "./bin/install.js"
10
10
  },
11
11
  "files": [
12
12
  "dist",
@@ -61,7 +61,7 @@
61
61
  "@types/node": "^20.0.0",
62
62
  "@typescript-eslint/eslint-plugin": "^6.0.0",
63
63
  "@typescript-eslint/parser": "^6.0.0",
64
- "baseline-browser-mapping": "^2.9.17",
64
+ "baseline-browser-mapping": "^2.9.18",
65
65
  "c8": "^10.1.3",
66
66
  "eslint": "^8.0.0",
67
67
  "tsx": "^4.0.0",