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 +24 -7
- package/bin/install.js +243 -0
- package/dist/utils/progress-bar.js +2 -2
- package/integration/command/glm_quota.md +5 -0
- package/integration/opencode.jsonc +10 -0
- package/integration/skills/glm-quota/SKILL.md +10 -0
- package/package.json +12 -3
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
|
-
|
|
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
|
|
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
|
-
|
|
226
|
-
|
|
227
|
-
.
|
|
228
|
-
|
|
229
|
-
|
|
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,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.
|
|
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": "
|
|
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.
|
|
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",
|