opencode-glm-quota 1.2.0 → 1.3.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 CHANGED
@@ -21,10 +21,19 @@ 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 plugin
24
25
  npm install opencode-glm-quota
26
+
27
+ # Run installer to configure OpenCode
28
+ npx opencode-glm-quota install
25
29
  ```
26
30
 
27
- OpenCode automatically discovers and loads plugins from npm. No additional configuration required.
31
+ **What the installer does:**
32
+ - Copies `/glm_quota` command to `~/.config/opencode/command/glm_quota.md`
33
+ - Copies skill documentation to `~/.config/opencode/skills/glm-quota/SKILL.md`
34
+ - Automatically adds plugin to your OpenCode config
35
+ - Merges agent configuration into `~/.config/opencode/opencode.json`
36
+ - Supports `--force` flag to overwrite existing files
28
37
 
29
38
  ### Option 2: From GitHub
30
39
 
@@ -43,6 +52,9 @@ npm run build
43
52
 
44
53
  # Link for local testing
45
54
  npm link
55
+
56
+ # Run the installer for local testing
57
+ node bin/install.js
46
58
  ```
47
59
 
48
60
  ## Quick Start
@@ -213,7 +225,7 @@ npm run prepublishOnly
213
225
 
214
226
  ```
215
227
  src/
216
- index.ts # Main plugin entry point (463 lines)
228
+ index.ts # Main plugin entry point
217
229
  api/
218
230
  client.ts # HTTPS client with timeout and error handling
219
231
  endpoints.ts # Platform-specific API endpoints
@@ -222,11 +234,16 @@ src/
222
234
  date-formatter.ts # Date/time formatting utilities
223
235
  progress-bar.ts # ASCII progress bar rendering
224
236
  time-window.ts # Rolling window calculation
225
- dist/ # Compiled JavaScript (generated)
226
- tests/ # Test suite
227
- .opencode/ # OpenCode integration files
228
- package.json # Dependencies and scripts
229
- tsconfig.json # TypeScript configuration
237
+ integration/
238
+ command/glm_quota.md # /glm_quota slash command
239
+ skills/glm-quota/SKILL.md # Skill documentation
240
+ opencode.jsonc # Agent configuration (JSONC)
241
+ bin/
242
+ install.js # Installation script
243
+ dist/ # Compiled JavaScript (generated)
244
+ tests/ # Test suite
245
+ package.json # Dependencies and scripts
246
+ tsconfig.json # TypeScript configuration
230
247
  ```
231
248
 
232
249
  ### Code Style Guidelines
package/bin/install.js ADDED
@@ -0,0 +1,243 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * GLM Quota Plugin Installer
5
+ *
6
+ * This script installs the GLM Quota Plugin integration files into the user's
7
+ * OpenCode configuration directory (~/.config/opencode/).
8
+ *
9
+ * Usage:
10
+ * node bin/install.js # Interactive install (ask before overwriting)
11
+ * node bin/install.js --force # Force overwrite existing files
12
+ */
13
+
14
+ import * as fs from 'fs'
15
+ import * as path from 'path'
16
+ import * as os from 'os'
17
+ import { parse as parseJsonc } from 'jsonc-parser'
18
+
19
+ // ==========================================
20
+ // CONSTANTS
21
+ // ==========================================
22
+
23
+ const __filename = decodeURIComponent(new URL(import.meta.url).pathname)
24
+ const __dirname = path.dirname(__filename)
25
+ const SOURCE_DIR = path.join(__dirname, '..', 'integration')
26
+ const COMMAND_FILE = path.join(SOURCE_DIR, 'command', 'glm_quota.md')
27
+ const SKILL_FILE = path.join(SOURCE_DIR, 'skills', 'glm-quota', 'SKILL.md')
28
+ const AGENT_CONFIG = path.join(SOURCE_DIR, 'opencode.jsonc')
29
+
30
+ const CONFIG_DIR = path.join(os.homedir(), '.config', 'opencode')
31
+ const TARGET_COMMAND = path.join(CONFIG_DIR, 'command', 'glm_quota.md')
32
+ const TARGET_SKILL = path.join(CONFIG_DIR, 'skills', 'glm-quota', 'SKILL.md')
33
+
34
+ // Check which config file exists (opencode.json or opencode.jsonc)
35
+ const TARGET_CONFIG_JSON = path.join(CONFIG_DIR, 'opencode.json')
36
+ const TARGET_CONFIG_JSONC = path.join(CONFIG_DIR, 'opencode.jsonc')
37
+ let TARGET_CONFIG = null
38
+ if (fileExists(TARGET_CONFIG_JSON)) {
39
+ TARGET_CONFIG = TARGET_CONFIG_JSON
40
+ } else if (fileExists(TARGET_CONFIG_JSONC)) {
41
+ TARGET_CONFIG = TARGET_CONFIG_JSONC
42
+ } else {
43
+ // Default to opencode.json if neither exists
44
+ TARGET_CONFIG = TARGET_CONFIG_JSON
45
+ }
46
+
47
+ // ==========================================
48
+ // UTILITY FUNCTIONS
49
+ // ==========================================
50
+
51
+ /**
52
+ * Ensure directory exists, create if missing
53
+ */
54
+ function ensureDirectory(dirPath) {
55
+ if (!fs.existsSync(dirPath)) {
56
+ fs.mkdirSync(dirPath, { recursive: true })
57
+ }
58
+ }
59
+
60
+ /**
61
+ * Check if file exists
62
+ */
63
+ function fileExists(filePath) {
64
+ return fs.existsSync(filePath)
65
+ }
66
+
67
+ /**
68
+ * Copy file from source to destination
69
+ */
70
+ function copyFile(source, destination) {
71
+ ensureDirectory(path.dirname(destination))
72
+ fs.copyFileSync(source, destination)
73
+ }
74
+
75
+ /**
76
+ * Parse JSON or JSONC file
77
+ */
78
+ function parseConfig(filePath) {
79
+ try {
80
+ const content = fs.readFileSync(filePath, 'utf-8')
81
+ return parseJsonc(content)
82
+ } catch (error) {
83
+ throw new Error(`Failed to parse ${filePath}: ${error instanceof Error ? error.message : String(error)}`)
84
+ }
85
+ }
86
+
87
+ /**
88
+ * Write JSON file
89
+ */
90
+ function writeConfig(filePath, data) {
91
+ ensureDirectory(path.dirname(filePath))
92
+ const json = JSON.stringify(data, null, 2) + '\n'
93
+ fs.writeFileSync(filePath, json)
94
+ console.log(` ✓ Wrote ${filePath} (${json.length} bytes)`)
95
+ }
96
+
97
+ /**
98
+ * Deep merge objects
99
+ */
100
+ function deepMerge(target, source) {
101
+ const result = { ...target }
102
+
103
+ for (const key of Object.keys(source)) {
104
+ if (source[key] instanceof Object && key in result && result[key] instanceof Object) {
105
+ result[key] = deepMerge(result[key], source[key])
106
+ } else {
107
+ result[key] = source[key]
108
+ }
109
+ }
110
+
111
+ return result
112
+ }
113
+
114
+ /**
115
+ * Prompt user for confirmation
116
+ */
117
+ function promptConfirm(message) {
118
+ process.stdout.write(`${message} (y/N) `)
119
+ const response = process.stdin.read()
120
+ return response?.trim().toLowerCase() === 'y'
121
+ }
122
+
123
+ // ==========================================
124
+ // INSTALLATION FUNCTIONS
125
+ // ==========================================
126
+
127
+ /**
128
+ * Install command file
129
+ */
130
+ function installCommand(force) {
131
+ if (fileExists(TARGET_COMMAND) && !force) {
132
+ if (!promptConfirm(`Command file exists: ${TARGET_COMMAND}\nOverwrite?`)) {
133
+ console.log(` ⊘ Skipped ${TARGET_COMMAND}`)
134
+ return
135
+ }
136
+ }
137
+
138
+ copyFile(COMMAND_FILE, TARGET_COMMAND)
139
+ console.log(` ✓ Created ${TARGET_COMMAND}`)
140
+ }
141
+
142
+ /**
143
+ * Install skill file
144
+ */
145
+ function installSkill(force) {
146
+ if (fileExists(TARGET_SKILL) && !force) {
147
+ if (!promptConfirm(`Skill directory exists: ${path.dirname(TARGET_SKILL)}\nOverwrite?`)) {
148
+ console.log(` ⊘ Skipped ${TARGET_SKILL}`)
149
+ return
150
+ }
151
+ }
152
+
153
+ copyFile(SKILL_FILE, TARGET_SKILL)
154
+ console.log(` ✓ Created ${path.join(path.basename(path.dirname(TARGET_SKILL)), path.basename(TARGET_SKILL))}`)
155
+ }
156
+
157
+ /**
158
+ * Merge agent configuration and add plugin to plugins array
159
+ */
160
+ function mergeConfig() {
161
+ // Parse existing config if it exists (same file type will be written)
162
+ let existingConfig = {}
163
+ if (fileExists(TARGET_CONFIG)) {
164
+ existingConfig = parseConfig(TARGET_CONFIG)
165
+ // REMOVE old 'options' field to prevent verbose agent output
166
+ if (existingConfig.agent?.['glm-quota-exec']?.options) {
167
+ delete existingConfig.agent['glm-quota-exec'].options
168
+ }
169
+ }
170
+
171
+ // Parse new agent config from integration
172
+ const newConfig = parseConfig(AGENT_CONFIG)
173
+
174
+ const PLUGIN_NAME = 'opencode-glm-quota'
175
+
176
+ // Handle both "plugin" array and "agent" section
177
+ // Check for "plugin" array first (user's config uses this)
178
+ const pluginArrayName = existingConfig.plugin ? 'plugin' : 'plugins'
179
+
180
+ // Only ensure that array we're going to use exists, not both!
181
+ if (!existingConfig[pluginArrayName]) {
182
+ existingConfig[pluginArrayName] = []
183
+ }
184
+
185
+ const plugins = Array.isArray(existingConfig[pluginArrayName]) ? existingConfig[pluginArrayName] : []
186
+
187
+ // REPLACE entire glm-quota-exec agent (not merge, to remove old redundant fields)
188
+ if (newConfig.agent && newConfig.agent['glm-quota-exec']) {
189
+ if (!existingConfig.agent) {
190
+ existingConfig.agent = {}
191
+ }
192
+ existingConfig.agent['glm-quota-exec'] = newConfig.agent['glm-quota-exec']
193
+ } else if (!existingConfig.agent && newConfig.agent) {
194
+ existingConfig.agent = newConfig.agent
195
+ }
196
+
197
+ // Only add if not already present
198
+ if (!plugins.includes(PLUGIN_NAME)) {
199
+ plugins.push(PLUGIN_NAME)
200
+ existingConfig[pluginArrayName] = plugins
201
+ console.log(` ✓ Added ${PLUGIN_NAME} to ${pluginArrayName} array`)
202
+ } else {
203
+ console.log(` ⊙ Plugin ${PLUGIN_NAME} already in ${pluginArrayName} array`)
204
+ }
205
+
206
+ // Write merged config back to same file (opencode.json or opencode.jsonc)
207
+ writeConfig(TARGET_CONFIG, existingConfig)
208
+ console.log(` ✓ Merged configuration into ${path.basename(TARGET_CONFIG)}`)
209
+ }
210
+
211
+ // ==========================================
212
+ // MAIN INSTALLATION FUNCTION
213
+ // ==========================================
214
+
215
+ /**
216
+ * Main installer function
217
+ */
218
+ function main() {
219
+ try {
220
+ // Parse command line arguments
221
+ const args = process.argv.slice(2)
222
+ const forceFlag = args.includes('--force')
223
+
224
+ console.log('✓ Installing GLM Quota Plugin...\n')
225
+
226
+ // Install integration files
227
+ installCommand(forceFlag)
228
+ installSkill(forceFlag)
229
+ mergeConfig()
230
+
231
+ console.log()
232
+ console.log('✓ Installation complete!')
233
+ console.log('✓ Restart OpenCode to use /glm_quota command')
234
+
235
+ } catch (error) {
236
+ console.error(`\n✗ Installation failed: ${error instanceof Error ? error.message : String(error)}`)
237
+ console.error('✗ Check file permissions and try again')
238
+ process.exit(1)
239
+ }
240
+ }
241
+
242
+ // Run installer
243
+ 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
  }
@@ -0,0 +1,5 @@
1
+ ---
2
+ description: Execute GLM quota check
3
+ agent: glm-quota-exec
4
+ ---
5
+ Execute glm_quota tool.
@@ -0,0 +1,10 @@
1
+ {
2
+ "$schema": "https://opencode.ai/config.json",
3
+ "agent": {
4
+ "glm-quota-exec": {
5
+ "mode": "subagent",
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
+ "permission": {}
8
+ }
9
+ }
10
+ }
@@ -0,0 +1,10 @@
1
+ ---
2
+ name: glm-quota
3
+ description: Query Z.ai GLM Coding Plan usage statistics including quota limits, model usage, and MCP tool usage
4
+ parameters:
5
+ - name: detailed
6
+ type: boolean
7
+ optional: true
8
+ description: Show detailed usage breakdown
9
+ ---
10
+ Query GLM quota usage including token limits and MCP tool usage.
package/package.json CHANGED
@@ -1,12 +1,17 @@
1
1
  {
2
2
  "name": "opencode-glm-quota",
3
- "version": "1.2.0",
3
+ "version": "1.3.1",
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
+ "bin": {
9
+ "opencode-glm-quota": "./bin/install.js"
10
+ },
8
11
  "files": [
9
12
  "dist",
13
+ "integration",
14
+ "bin",
10
15
  "README.md",
11
16
  "LICENSE"
12
17
  ],
@@ -14,6 +19,7 @@
14
19
  "build": "tsc",
15
20
  "clean": "rm -rf dist",
16
21
  "prepublishOnly": "npm run clean && npm run build",
22
+ "postinstall": "node bin/install.js",
17
23
  "test": "tsx --test $(find tests -name '*.test.ts' -type f)",
18
24
  "test:coverage": "c8 --reporter=lcov --reporter=text -- npm test",
19
25
  "lint": "eslint src/"
@@ -41,6 +47,9 @@
41
47
  "url": "https://github.com/guyinwonder168/opencode-glm-quota/issues"
42
48
  },
43
49
  "homepage": "https://github.com/guyinwonder168/opencode-glm-quota#readme",
50
+ "dependencies": {
51
+ "jsonc-parser": "^3.2.0"
52
+ },
44
53
  "peerDependencies": {
45
54
  "@opencode-ai/plugin": ">=0.1.0"
46
55
  },
@@ -48,11 +57,11 @@
48
57
  "access": "public"
49
58
  },
50
59
  "devDependencies": {
51
- "@opencode-ai/plugin": "latest",
60
+ "@opencode-ai/plugin": "^1.1.30",
52
61
  "@types/node": "^20.0.0",
53
62
  "@typescript-eslint/eslint-plugin": "^6.0.0",
54
63
  "@typescript-eslint/parser": "^6.0.0",
55
- "baseline-browser-mapping": "^2.9.17",
64
+ "baseline-browser-mapping": "^2.9.18",
56
65
  "c8": "^10.1.3",
57
66
  "eslint": "^8.0.0",
58
67
  "tsx": "^4.0.0",