Initial commit: plan-usage - GLM & MiniMax statusline for Claude Code
This commit is contained in:
110
README.md
Normal file
110
README.md
Normal file
@@ -0,0 +1,110 @@
|
|||||||
|
# plan-usage
|
||||||
|
|
||||||
|
简体中文 | [English](README_en.md)
|
||||||
|
|
||||||
|
一个用于 Claude Code 的插件,在状态栏显示 GLM(智谱/ZAI)和 MiniMax 算力套餐的使用量统计。
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
## 功能特性
|
||||||
|
|
||||||
|
- 📊 **实时使用量追踪**: 同时显示 GLM 和 MiniMax 使用百分比
|
||||||
|
- 🎨 **颜色警告提示**: 绿色 (0-70%)、黄色 (70-90%)、红色 (90-100%)
|
||||||
|
- ⚡ **快速查询**: 异步并行获取两个 API
|
||||||
|
- 🔧 **灵活配置**: 支持单独查看 GLM 或 MiniMax
|
||||||
|
|
||||||
|
## 安装
|
||||||
|
|
||||||
|
### 方法 1: npm 安装(推荐)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm install -g @renato97/plan-usage
|
||||||
|
```
|
||||||
|
|
||||||
|
### 方法 2: 手动安装
|
||||||
|
|
||||||
|
```bash
|
||||||
|
mkdir -p ~/.claude/plan-usage
|
||||||
|
cp -r dist/index.js ~/.claude/plan-usage/
|
||||||
|
chmod +x ~/.claude/plan-usage/index.js
|
||||||
|
```
|
||||||
|
|
||||||
|
## 配置 Claude Code
|
||||||
|
|
||||||
|
编辑 `~/.claude/settings.json`:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"statusLine": {
|
||||||
|
"type": "command",
|
||||||
|
"command": "plan-usage"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
或者使用完整路径:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"statusLine": {
|
||||||
|
"type": "command",
|
||||||
|
"command": "node ~/.claude/plan-usage/index.js"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 环境变量
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# GLM API Key (也会使用 ANTHROPIC_AUTH_TOKEN)
|
||||||
|
export GLM_API_KEY="your-glm-api-key"
|
||||||
|
|
||||||
|
# MiniMax API Key (可选)
|
||||||
|
export MINIMAX_API_KEY="your-minimax-api-key"
|
||||||
|
```
|
||||||
|
|
||||||
|
## 使用方法
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 显示状态栏 (默认)
|
||||||
|
plan-usage
|
||||||
|
|
||||||
|
# 只显示 GLM
|
||||||
|
plan-usage --glm
|
||||||
|
|
||||||
|
# 只显示 MiniMax
|
||||||
|
plan-usage --minimax
|
||||||
|
|
||||||
|
# JSON 格式输出
|
||||||
|
plan-usage --json
|
||||||
|
```
|
||||||
|
|
||||||
|
## 输出示例
|
||||||
|
|
||||||
|
```
|
||||||
|
GLM 6% | MiniMax 93% 1.6h
|
||||||
|
```
|
||||||
|
|
||||||
|
颜色说明:
|
||||||
|
- 🟢 绿色: 0-70%
|
||||||
|
- 🟡 黄色: 70-90%
|
||||||
|
- 🔴 红色: 90-100%
|
||||||
|
|
||||||
|
## API 端点
|
||||||
|
|
||||||
|
- **GLM**: `https://api.z.ai/api/monitor/usage/quota/limit`
|
||||||
|
- **MiniMax**: `https://api.minimax.io/v1/api/openplatform/coding_plan/remains`
|
||||||
|
|
||||||
|
## 构建
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 本地运行
|
||||||
|
node dist/index.js
|
||||||
|
|
||||||
|
# 测试
|
||||||
|
node dist/index.js --json
|
||||||
|
```
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
MIT
|
||||||
212
dist/index.js
vendored
Normal file
212
dist/index.js
vendored
Normal file
@@ -0,0 +1,212 @@
|
|||||||
|
#!/usr/bin/env node
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Plan Usage - GLM & MiniMax Coding Plan Statusline
|
||||||
|
* Shows real-time usage for Claude Code
|
||||||
|
*
|
||||||
|
* Based on glm-plan-usage by jukanntenn
|
||||||
|
* Modified to support both GLM and MiniMax
|
||||||
|
*
|
||||||
|
* Usage:
|
||||||
|
* plan-usage # Show statusline
|
||||||
|
* plan-usage --json # Show JSON output
|
||||||
|
* plan-usage --glm # Show only GLM
|
||||||
|
* plan-usage --minimax # Show only MiniMax
|
||||||
|
*/
|
||||||
|
|
||||||
|
const https = require('https');
|
||||||
|
|
||||||
|
// Colors
|
||||||
|
const colors = {
|
||||||
|
reset: '\x1b[0m',
|
||||||
|
green: '\x1b[32m',
|
||||||
|
yellow: '\x1b[33m',
|
||||||
|
red: '\x1b[31m',
|
||||||
|
blue: '\x1b[36m',
|
||||||
|
orange: '\x1b[38;5;208m',
|
||||||
|
purple: '\x1b[35m'
|
||||||
|
};
|
||||||
|
|
||||||
|
// API Configuration
|
||||||
|
const CONFIG = {
|
||||||
|
glm: {
|
||||||
|
apiKey: process.env.GLM_API_KEY || process.env.ANTHROPIC_AUTH_TOKEN,
|
||||||
|
baseUrl: 'api.z.ai',
|
||||||
|
endpoint: '/api/monitor/usage/quota/limit'
|
||||||
|
},
|
||||||
|
minimax: {
|
||||||
|
apiKey: process.env.MINIMAX_API_KEY || 'sk-cp-XC8cbgbVBuv1g8mMcao0ABeZu_rGEN_S22EhBUqo4lJbY_UJVqUVO5XF8hVobp8gE_39JbgQggr00TQwNdV9vP458Y_MBC_8GstvzmwhuukEGY4a2I5_L6A',
|
||||||
|
baseUrl: 'api.minimax.io',
|
||||||
|
endpoint: '/v1/api/openplatform/coding_plan/remains'
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Make API request
|
||||||
|
function makeRequest(options) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const req = https.request(options, (res) => {
|
||||||
|
let data = '';
|
||||||
|
res.on('data', chunk => data += chunk);
|
||||||
|
res.on('end', () => {
|
||||||
|
try {
|
||||||
|
resolve(JSON.parse(data));
|
||||||
|
} catch (e) {
|
||||||
|
reject(e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
req.on('error', reject);
|
||||||
|
req.setTimeout(5000, () => { req.destroy(); reject(new Error('Timeout')); });
|
||||||
|
req.end();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fetch GLM usage
|
||||||
|
async function getGlmUsage() {
|
||||||
|
if (!CONFIG.glm.apiKey) {
|
||||||
|
return { error: 'No GLM API key configured' };
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await makeRequest({
|
||||||
|
hostname: CONFIG.glm.baseUrl,
|
||||||
|
path: CONFIG.glm.endpoint,
|
||||||
|
method: 'GET',
|
||||||
|
headers: {
|
||||||
|
'Authorization': `Bearer ${CONFIG.glm.apiKey}`,
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const limits = response?.data?.limits || [];
|
||||||
|
let tokenPct = 0;
|
||||||
|
let timeRemaining = 0;
|
||||||
|
|
||||||
|
for (const l of limits) {
|
||||||
|
if (l.quota_type === 'TOKENS_LIMIT') tokenPct = l.percentage || 0;
|
||||||
|
if (l.quota_type === 'TIME_LIMIT') timeRemaining = (l.remaining || 0) / 1000 / 60;
|
||||||
|
}
|
||||||
|
|
||||||
|
return { tokenPct, timeRemaining, error: null };
|
||||||
|
} catch (e) {
|
||||||
|
return { tokenPct: 0, timeRemaining: 0, error: e.message };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fetch MiniMax usage
|
||||||
|
async function getMinimaxUsage() {
|
||||||
|
if (!CONFIG.minimax.apiKey) {
|
||||||
|
return { error: 'No MiniMax API key configured' };
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await makeRequest({
|
||||||
|
hostname: CONFIG.minimax.baseUrl,
|
||||||
|
path: CONFIG.minimax.endpoint,
|
||||||
|
method: 'GET',
|
||||||
|
headers: {
|
||||||
|
'Authorization': `Bearer ${CONFIG.minimax.apiKey}`,
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const m = response?.model_remains?.[0];
|
||||||
|
if (!m) {
|
||||||
|
return { pct: 0, remainingMinutes: 0, model: '?', error: 'No data' };
|
||||||
|
}
|
||||||
|
|
||||||
|
const remainingMinutes = m.remains_time / 1000 / 60;
|
||||||
|
const used = m.current_interval_usage_count;
|
||||||
|
const total = m.current_interval_total_count;
|
||||||
|
const pct = (used / total) * 100;
|
||||||
|
|
||||||
|
return { pct, remainingMinutes, model: m.model_name, error: null };
|
||||||
|
} catch (e) {
|
||||||
|
return { pct: 0, remainingMinutes: 0, model: '?', error: e.message };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get color based on percentage
|
||||||
|
function getColor(pct) {
|
||||||
|
if (pct > 90) return colors.red;
|
||||||
|
if (pct > 70) return colors.yellow;
|
||||||
|
return colors.green;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Format time
|
||||||
|
function formatTime(minutes) {
|
||||||
|
if (minutes < 60) return `${minutes.toFixed(0)}m`;
|
||||||
|
return `${(minutes/60).toFixed(1)}h`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Format GLM status
|
||||||
|
function formatGlmStatus(glm) {
|
||||||
|
if (glm.error) {
|
||||||
|
return `${colors.blue}GLM${colors.reset} ${colors.red}offline${colors.reset}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
let status = `${colors.blue}GLM${colors.reset} ${getColor(glm.tokenPct)}${glm.tokenPct.toFixed(0)}%${colors.reset}`;
|
||||||
|
if (glm.timeRemaining > 0) {
|
||||||
|
status += ` ${formatTime(glm.timeRemaining)}`;
|
||||||
|
}
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Format MiniMax status
|
||||||
|
function formatMinimaxStatus(minimax) {
|
||||||
|
if (minimax.error) {
|
||||||
|
return `${colors.purple}MiniMax${colors.reset} ${colors.red}offline${colors.reset}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
let status = `${colors.purple}MiniMax${colors.reset} ${getColor(minimax.pct)}${minimax.pct.toFixed(0)}%${colors.reset}`;
|
||||||
|
if (minimax.remainingMinutes > 0) {
|
||||||
|
status += ` ${formatTime(minimax.remainingMinutes)}`;
|
||||||
|
}
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Main function
|
||||||
|
async function main() {
|
||||||
|
const args = process.argv.slice(2);
|
||||||
|
const showJson = args.includes('--json');
|
||||||
|
const showOnlyGlm = args.includes('--glm');
|
||||||
|
const showOnlyMinimax = args.includes('--minimax');
|
||||||
|
|
||||||
|
try {
|
||||||
|
const [glm, minimax] = await Promise.all([
|
||||||
|
showOnlyMinimax ? Promise.resolve({ error: 'disabled' }) : getGlmUsage(),
|
||||||
|
showOnlyGlm ? Promise.resolve({ error: 'disabled' }) : getMinimaxUsage()
|
||||||
|
]);
|
||||||
|
|
||||||
|
if (showJson) {
|
||||||
|
console.log(JSON.stringify({ glm, minimax }, null, 2));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Statusline output
|
||||||
|
let parts = [];
|
||||||
|
|
||||||
|
if (!showOnlyMinimax && !glm.error) {
|
||||||
|
parts.push(formatGlmStatus(glm));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!showOnlyGlm && !minimax.error) {
|
||||||
|
parts.push(formatMinimaxStatus(minimax));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (parts.length === 0) {
|
||||||
|
console.log(`${colors.red}No API configured${colors.reset}`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(parts.join(' | '));
|
||||||
|
} catch (e) {
|
||||||
|
if (showJson) {
|
||||||
|
console.log(JSON.stringify({ error: e.message }, null, 2));
|
||||||
|
} else {
|
||||||
|
console.log(`${colors.red}Error: ${e.message}${colors.reset}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
main();
|
||||||
11
package.json
Normal file
11
package.json
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
{
|
||||||
|
"name": "@renato97/plan-usage",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"description": "GLM and MiniMax Coding Plan usage statusline for Claude Code",
|
||||||
|
"bin": {
|
||||||
|
"plan-usage": "./dist/index.js"
|
||||||
|
},
|
||||||
|
"keywords": ["claude-code", "statusline", "glm", "minimax", "coding-plan"],
|
||||||
|
"author": "renato97",
|
||||||
|
"license": "MIT"
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user