From 02bfc1f0143f8963fe57a7c0b8451deb3d993462 Mon Sep 17 00:00:00 2001 From: ren Date: Mon, 16 Feb 2026 20:34:11 -0300 Subject: [PATCH] Add source code --- dist/index.js | 321 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 321 insertions(+) create mode 100755 dist/index.js diff --git a/dist/index.js b/dist/index.js new file mode 100755 index 0000000..44b9fc3 --- /dev/null +++ b/dist/index.js @@ -0,0 +1,321 @@ +#!/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, + baseUrl: 'api.minimax.io', + endpoint: '/v1/api/openplatform/coding_plan/remains' + }, + kimi: { + apiKey: process.env.KIMI_API_KEY, + baseUrl: 'api.kimi.com', + endpoint: '/coding/v1/usages' + } +}; + +// 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; + let totalMinutes = 0; + + for (const l of limits) { + if (l.type === 'TOKENS_LIMIT') { + tokenPct = l.percentage || 0; + if (l.nextResetTime) { + const resetDate = new Date(l.nextResetTime); + const now = new Date(); + timeRemaining = (resetDate - now) / 1000 / 60; + } + } + if (l.type === 'TIME_LIMIT') { + if (l.nextResetTime && timeRemaining === 0) { + const resetDate = new Date(l.nextResetTime); + const now = new Date(); + timeRemaining = (resetDate - now) / 1000 / 60; + } + } + } + + return { tokenPct, timeRemaining, totalMinutes, error: null }; + } catch (e) { + return { tokenPct: 0, timeRemaining: 0, totalMinutes: 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, totalMinutes: 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 = 100 - ((used / total) * 100); + const totalMinutes = remainingMinutes / (used / total) || 0; + + return { pct, remainingMinutes, totalMinutes, model: m.model_name, error: null }; + } catch (e) { + return { pct: 0, remainingMinutes: 0, totalMinutes: 0, model: '?', error: e.message }; + } +} + +// Fetch Kimi usage +async function getKimiUsage() { + if (!CONFIG.kimi.apiKey) { + return { error: 'No Kimi API key configured' }; + } + + try { + const response = await makeRequest({ + hostname: CONFIG.kimi.baseUrl, + path: CONFIG.kimi.endpoint, + method: 'GET', + headers: { + 'Authorization': `Bearer ${CONFIG.kimi.apiKey}`, + 'Content-Type': 'application/json' + } + }); + + const usage = response?.usage; + const limits = response?.limits || []; + + if (!usage) { + return { pct: 0, remainingMinutes: 0, error: 'No data' }; + } + + const pct = parseInt(usage.used) / parseInt(usage.limit) * 100; + let remainingMinutes = 0; + let weeklyReset = null; + + if (usage.resetTime) { + const resetDate = new Date(usage.resetTime); + const now = new Date(); + remainingMinutes = (resetDate - now) / 1000 / 60; + weeklyReset = resetDate; + } + + const limitInfos = []; + for (const l of limits) { + if (l.detail?.resetTime) { + const resetDate = new Date(l.detail.resetTime); + const now = new Date(); + const limitMins = (resetDate - now) / 1000 / 60; + + if (limitMins > 0) { + const limitUsed = parseInt(l.detail.limit) - parseInt(l.detail.remaining); + const limitPct = limitUsed / parseInt(l.detail.limit) * 100; + const windowDur = l.window?.duration || 0; + const windowUnit = l.window?.timeUnit || ''; + + let label = '5h'; + if (windowUnit === 'TIME_UNIT_MINUTE' && windowDur >= 60) { + label = `${windowDur / 60}h`; + } + + limitInfos.push({ + pct: limitPct, + remainingMinutes: limitMins, + label: label + }); + } + } + } + + return { pct, remainingMinutes, weeklyReset, limitInfos, error: null }; + } catch (e) { + return { pct: 0, remainingMinutes: 0, 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}`; + } + + const timeStr = glm.timeRemaining > 0 ? formatTime(glm.timeRemaining) : ''; + return `${colors.blue}GLM${colors.reset} ${getColor(glm.tokenPct)}${glm.tokenPct.toFixed(0)}%${colors.reset}${timeStr ? ` ${timeStr}` : ''}`; +} + +// Format MiniMax status +function formatMinimaxStatus(minimax) { + if (minimax.error) { + return `${colors.purple}MiniMax${colors.reset} ${colors.red}offline${colors.reset}`; + } + + const timeStr = minimax.remainingMinutes > 0 ? formatTime(minimax.remainingMinutes) : ''; + return `${colors.purple}MiniMax${colors.reset} ${getColor(minimax.pct)}${minimax.pct.toFixed(0)}%${colors.reset}${timeStr ? ` ${timeStr}` : ''}`; +} + +// Format Kimi status +function formatKimiStatus(kimi) { + if (kimi.error) { + return `${colors.orange}Kimi${colors.reset} ${colors.red}offline${colors.reset}`; + } + + let status = `${colors.orange}Kimi${colors.reset} ${getColor(kimi.pct)}${kimi.pct.toFixed(0)}%${colors.reset}`; + + if (kimi.remainingMinutes > 0) { + status += ` ${formatTime(kimi.remainingMinutes)}`; + } + + if (kimi.limitInfos && kimi.limitInfos.length > 0) { + for (const lim of kimi.limitInfos) { + status += ` ${colors.orange}${lim.label}${colors.reset}${getColor(lim.pct)}${lim.pct.toFixed(0)}%${colors.reset}`; + } + } + + 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'); + const showOnlyKimi = args.includes('--kimi'); + + try { + const [glm, minimax, kimi] = await Promise.all([ + getGlmUsage(), + getMinimaxUsage(), + getKimiUsage() + ]); + + if (showJson) { + console.log(JSON.stringify({ glm, minimax, kimi }, null, 2)); + return; + } + + // Statusline output - always show all 3 + let parts = []; + + if (!showOnlyMinimax && !showOnlyKimi) { + parts.push(formatGlmStatus(glm)); + } + + if (!showOnlyGlm && !showOnlyKimi) { + parts.push(formatMinimaxStatus(minimax)); + } + + if (!showOnlyGlm && !showOnlyMinimax) { + parts.push(formatKimiStatus(kimi)); + } + + 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();