opencode-glm-quota 1.2.0 → 1.3.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
@@ -21,10 +21,21 @@ 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
- npm install opencode-glm-quota
24
+ # Install the plugin
25
+ npm install @opencode-glm-quota/plugin
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
25
32
  ```
26
33
 
27
- OpenCode automatically discovers and loads plugins from npm. No additional configuration required.
34
+ **What the installer does:**
35
+ - Copies `/glm_quota` command to `~/.config/opencode/command/glm_quota.md`
36
+ - Copies skill documentation to `~/.config/opencode/skill/glm-quota-skill.md`
37
+ - Merges agent configuration into `~/.config/opencode/opencode.json`
38
+ - Supports `--force` flag to overwrite existing files
28
39
 
29
40
  ### Option 2: From GitHub
30
41
 
@@ -43,6 +54,9 @@ npm run build
43
54
 
44
55
  # Link for local testing
45
56
  npm link
57
+
58
+ # Run the installer for local testing
59
+ node bin/install.js
46
60
  ```
47
61
 
48
62
  ## Quick Start
@@ -222,9 +236,14 @@ src/
222
236
  date-formatter.ts # Date/time formatting utilities
223
237
  progress-bar.ts # ASCII progress bar rendering
224
238
  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
225
245
  dist/ # Compiled JavaScript (generated)
226
246
  tests/ # Test suite
227
- .opencode/ # OpenCode integration files
228
247
  package.json # Dependencies and scripts
229
248
  tsconfig.json # TypeScript configuration
230
249
  ```
package/bin/install.js ADDED
@@ -0,0 +1,229 @@
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, 'skill', '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, 'skill', '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 file exists: ${TARGET_SKILL}\nOverwrite?`)) {
148
+ console.log(` ⊘ Skipped ${TARGET_SKILL}`)
149
+ return
150
+ }
151
+ }
152
+
153
+ copyFile(SKILL_FILE, TARGET_SKILL)
154
+ console.log(` ✓ Created ${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
+ }
166
+
167
+ // Parse new agent config from integration
168
+ const newConfig = parseConfig(AGENT_CONFIG)
169
+
170
+ // Merge agent definitions first
171
+ const mergedConfig = deepMerge(existingConfig, newConfig)
172
+
173
+ // Ensure plugins array exists and add our plugin
174
+ if (!mergedConfig.plugins) {
175
+ mergedConfig.plugins = []
176
+ }
177
+
178
+ const PLUGIN_NAME = '@opencode-glm-quota/plugin'
179
+ const plugins = Array.isArray(mergedConfig.plugins) ? mergedConfig.plugins : []
180
+
181
+ // Only add if not already present
182
+ if (!plugins.includes(PLUGIN_NAME)) {
183
+ plugins.push(PLUGIN_NAME)
184
+ mergedConfig.plugins = plugins
185
+ console.log(` ✓ Added ${PLUGIN_NAME} to plugins array`)
186
+ } else {
187
+ console.log(` ⊙ Plugin ${PLUGIN_NAME} already in plugins array`)
188
+ }
189
+
190
+ // Write merged config back to the same file (opencode.json or opencode.jsonc)
191
+ writeConfig(TARGET_CONFIG, mergedConfig)
192
+ console.log(` ✓ Merged configuration into ${path.basename(TARGET_CONFIG)}`)
193
+ }
194
+
195
+ // ==========================================
196
+ // MAIN INSTALLATION FUNCTION
197
+ // ==========================================
198
+
199
+ /**
200
+ * Main installer function
201
+ */
202
+ function main() {
203
+ try {
204
+ // Parse command line arguments
205
+ const args = process.argv.slice(2)
206
+ const forceFlag = args.includes('--force')
207
+
208
+ console.log('✓ Installing GLM Quota Plugin...\n')
209
+
210
+ // Install integration files
211
+ installCommand(forceFlag)
212
+ installSkill(forceFlag)
213
+ mergeConfig()
214
+
215
+ console.log()
216
+ console.log('✓ Installation complete!')
217
+ console.log('✓ Restart OpenCode to use /glm_quota command')
218
+
219
+ } catch (error) {
220
+ console.error(`\n✗ Installation failed: ${error instanceof Error ? error.message : String(error)}`)
221
+ console.error('✗ Check file permissions and try again')
222
+ process.exit(1)
223
+ }
224
+ }
225
+
226
+ // Run installer
227
+ if (import.meta.url === `file://${process.argv[1]}`) {
228
+ main()
229
+ }
@@ -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,17 @@
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
+ "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
+ "permission": {}
13
+ }
14
+ }
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
+ }
@@ -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.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",
7
7
  "types": "dist/index.d.ts",
8
+ "bin": {
9
+ "opencode-glm-quota-install": "./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,7 +57,7 @@
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",