diff --git a/README.md b/README.md index 5ab5080..2d34417 100644 --- a/README.md +++ b/README.md @@ -19,6 +19,7 @@ An [OpenCode](https://opencode.ai) plugin to query account quota usage for multi | Z.ai | Coding Plan | `~/.local/share/opencode/auth.json` | | GitHub Copilot | Individual / Business | `~/.local/share/opencode/auth.json` | | Google Cloud | Antigravity | `~/.config/opencode/antigravity-accounts.json` | +| Claude | Subscription (Pro/Max) or Organization | `~/.local/share/opencode/auth.json` (OAuth) or `ANTHROPIC_ADMIN_KEY` | ## Installation @@ -146,28 +147,66 @@ G3 Pro 4h 59m ███████████████████ G3 Image 4h 59m ████████████████████ 100% G3 Flash 4h 59m ████████████████████ 100% Claude 2d 9h ░░░░░░░░░░░░░░░░░░░░ 0% -``` -## Features +## Claude (Anthropic) Account Usage + +Account: Claude (Subscription) +5-hour session limit +███████████████████████░░░░░░░ 73% remaining 🟢 +Resets in: 3h 45m + +7-day weekly limit +████████████████░░░░░░░░░░░░░░ 55% remaining 🟢 +Resets in: 4d 12h + +## Features - Query quota usage across multiple AI platforms in one command - Visual progress bars showing remaining quota - Reset time countdown - Multi-language support (Chinese / English) - Multiple Google Cloud accounts support - API key masking for security +- Claude/Anthropic subscription usage tracking (5h session + 7d weekly limits) ## Configuration -No additional configuration required. The plugin automatically reads credentials from: +No additional configuration required for most platforms. The plugin automatically reads credentials from: - **OpenAI, Zhipu AI, Z.ai & GitHub Copilot**: `~/.local/share/opencode/auth.json` - **Google Cloud**: `~/.config/opencode/antigravity-accounts.json` +- **Claude (Anthropic)**: `~/.local/share/opencode/auth.json` (OAuth, auto-detected) or `ANTHROPIC_ADMIN_KEY` environment variable ### Google Cloud Setup To query Google Cloud (Antigravity) account quota, you need to install the [opencode-antigravity-auth](https://github.com/NoeFabris/opencode-antigravity-auth) plugin first to authenticate your Google account. +### Claude (Anthropic) Setup + +**For Subscription Users (Pro/Max):** No setup needed! The plugin automatically reads your OAuth tokens from `~/.local/share/opencode/auth.json` (stored by the `opencode-anthropic-auth` plugin). It makes a minimal API call (~$0.001) to read rate limit headers. + +**For Organization Users (Admin API):** + +If you have an organization account, you can use an **Admin API key** from the [Anthropic Console](https://console.anthropic.com/settings/admin-keys). + +> **Note:** The Admin API is only available for organization accounts. + +**Option 1: Environment Variable (recommended)** + +```bash +export ANTHROPIC_ADMIN_KEY="sk-ant-admin-..." +``` + +**Option 2: Config File** + +Create `~/.config/opencode/claude-admin-key.json`: + +```json +{ + "adminKey": "sk-ant-admin-..." +} +``` + ## Security This plugin is safe to use: @@ -185,6 +224,8 @@ This plugin is safe to use: - `https://api.github.com/copilot_internal/user` - GitHub Copilot official API - `https://oauth2.googleapis.com/token` - Google official OAuth API - `https://cloudcode-pa.googleapis.com/v1internal:fetchAvailableModels` - Google Cloud official API +- `https://api.anthropic.com/v1/messages` - Anthropic Messages API (minimal probe for rate limit headers) +- `https://console.anthropic.com/v1/oauth/token` - Anthropic OAuth token refresh **Privacy:** diff --git a/README.zh-CN.md b/README.zh-CN.md index 06bb45e..e5d9863 100644 --- a/README.zh-CN.md +++ b/README.zh-CN.md @@ -19,6 +19,7 @@ | Z.ai | Coding Plan | `~/.local/share/opencode/auth.json` | | GitHub Copilot | Individual / Business | `~/.local/share/opencode/auth.json` | | Google Cloud | Antigravity | `~/.config/opencode/antigravity-accounts.json` | +| Claude | 订阅 (Pro/Max) 或 组织账号 | `~/.local/share/opencode/auth.json` (OAuth) 或 `ANTHROPIC_ADMIN_KEY` | ## 安装 @@ -146,6 +147,18 @@ G3 Pro 4h 59m ███████████████████ G3 Image 4h 59m ████████████████████ 100% G3 Flash 4h 59m ████████████████████ 100% Claude 2d 9h ░░░░░░░░░░░░░░░░░░░░ 0% + +## Claude (Anthropic) 账号用量 + +Account: Claude (订阅) + +5 小时会话限额 +███████████████████████░░░░░░░ 剩余 73% 🟢 +重置: 3小时45分钟后 + +7 天周限额 +████████████████░░░░░░░░░░░░░░ 剩余 55% 🟢 +重置: 4天1小时后 ``` ## 功能特性 @@ -156,18 +169,46 @@ Claude 2d 9h ░░░░░░░░░░░░░░░░░░░ - 多语言支持(中文 / 英文) - 支持多个 Google Cloud 账号 - API Key 脱敏显示,保护安全 +- Claude/Anthropic 订阅用量跟踪(5小时会话 + 7天周限额) ## 配置 -无需额外配置。插件自动从以下位置读取认证信息: +大多数平台无需额外配置。插件自动从以下位置读取认证信息: - **OpenAI、智谱 AI、Z.ai 和 GitHub Copilot**: `~/.local/share/opencode/auth.json` - **Google Cloud**: `~/.config/opencode/antigravity-accounts.json` +- **Claude (Anthropic)**: `~/.local/share/opencode/auth.json` (OAuth,自动检测) 或 `ANTHROPIC_ADMIN_KEY` 环境变量 ### Google Cloud 设置 如需查询 Google Cloud (Antigravity) 账号额度,需要先安装 [opencode-antigravity-auth](https://github.com/NoeFabris/opencode-antigravity-auth) 插件来完成 Google 账号认证。 +### Claude (Anthropic) 设置 + +**订阅用户 (Pro/Max):** 无需配置!插件会自动从 `~/.local/share/opencode/auth.json` 读取 OAuth 令牌(由 `opencode-anthropic-auth` 插件存储)。插件通过一个最小化 API 调用 (~$0.001) 读取速率限制头。 + +**组织用户 (Admin API):** + +如果您拥有组织账号,可以使用从 [Anthropic 控制台](https://console.anthropic.com/settings/admin-keys) 获取的 **Admin API Key**。 + +> **注意:** Admin API 仅适用于组织账号。 + +**方式一:环境变量(推荐)** + +```bash +export ANTHROPIC_ADMIN_KEY="sk-ant-admin-..." +``` + +**方式二:配置文件** + +创建 `~/.config/opencode/claude-admin-key.json`: + +```json +{ + "adminKey": "sk-ant-admin-..." +} +``` + ## 安全性 本插件可以安全放心使用: @@ -185,6 +226,8 @@ Claude 2d 9h ░░░░░░░░░░░░░░░░░░░ - `https://api.github.com/copilot_internal/user` - GitHub Copilot 官方 API - `https://oauth2.googleapis.com/token` - Google 官方 OAuth 接口 - `https://cloudcode-pa.googleapis.com/v1internal:fetchAvailableModels` - Google Cloud 官方接口 +- `https://api.anthropic.com/v1/messages` - Anthropic 官方 Messages API(最小化探测,读取速率限制头) +- `https://console.anthropic.com/v1/oauth/token` - Anthropic OAuth 令牌刷新 **隐私保护:** diff --git a/plugin/lib/claude.ts b/plugin/lib/claude.ts new file mode 100644 index 0000000..75b836d --- /dev/null +++ b/plugin/lib/claude.ts @@ -0,0 +1,384 @@ +/** + * Claude (Anthropic) Usage Module — OAuth Rate Limit Headers + * + * [Input]: OAuth tokens from ~/.local/share/opencode/auth.json (anthropic key) + * [Output]: Formatted rate limit usage (5h session + 7d weekly) + * [Location]: Called by mystatus.ts to handle Claude/Anthropic accounts + * [Sync]: mystatus.ts, types.ts, utils.ts, i18n.ts + * + * Uses OAuth rate-limit headers from the Anthropic Messages API: + * - Makes a minimal API call with anthropic-beta: oauth-2025-04-20 + * - Parses anthropic-ratelimit-unified-5h/7d-utilization headers + * + * Works with Claude Pro/Max subscription accounts (OAuth tokens). + * Falls back to Admin API for organization accounts (if admin key is configured). + * + * Reference: https://github.com/nsanden/claude-rate-monitor + */ + +import * as fs from "fs"; +import * as path from "path"; +import * as os from "os"; + +import { t, currentLang } from "./i18n"; +import { + type QueryResult, + type AnthropicAuthData, + type ClaudeAdminConfig, +} from "./types"; +import { fetchWithTimeout, formatDuration, createProgressBar } from "./utils"; + +// ============================================================================ +// Constants +// ============================================================================ + +const ANTHROPIC_API_BASE = "https://api.anthropic.com"; +const ANTHROPIC_VERSION = "2023-06-01"; +const ANTHROPIC_BETA = "oauth-2025-04-20"; +const PROBE_MODEL = "claude-haiku-4-5-20251001"; + +// OAuth refresh +const ANTHROPIC_TOKEN_URL = "https://console.anthropic.com/v1/oauth/token"; +const ANTHROPIC_CLIENT_ID = "9d1c250a-e61b-44d9-88ed-5944d1962f5e"; + +// Admin API fallback config path +const CLAUDE_ADMIN_CONFIG_PATH = path.join( + os.homedir(), + ".config", + "opencode", + "claude-admin-key.json", +); + +// Rate limit header prefix +const RL_PREFIX = "anthropic-ratelimit-unified"; + +// ============================================================================ +// Types +// ============================================================================ + +/** Parsed rate limit window (5h or 7d) */ +interface RateLimitWindow { + /** Usage fraction (0.0 to 1.0+, can exceed 1.0 on overage) */ + utilization: number; + /** Remaining percent (0-100, clamped) */ + remainPercent: number; + /** Reset time as Unix epoch seconds */ + resetEpoch: number; + /** "active", "warning", or "rate_limited" */ + status: string; +} + +/** Combined rate limit info from all headers */ +interface RateLimitInfo { + session5h: RateLimitWindow; + weekly7d: RateLimitWindow; + overallStatus: string; + overageStatus: string; +} + +// ============================================================================ +// OAuth Token Refresh +// ============================================================================ + +/** + * Refresh an expired OAuth access token. + * Uses PKCE public client flow (no client_secret needed). + */ +async function refreshAccessToken( + refreshToken: string, +): Promise<{ access_token: string; expires_in: number }> { + const params = new URLSearchParams({ + grant_type: "refresh_token", + refresh_token: refreshToken, + client_id: ANTHROPIC_CLIENT_ID, + }); + + const response = await fetchWithTimeout(ANTHROPIC_TOKEN_URL, { + method: "POST", + headers: { "Content-Type": "application/x-www-form-urlencoded" }, + body: params, + }); + + if (!response.ok) { + const errorText = await response.text(); + throw new Error(t.claudeApiError(response.status, errorText)); + } + + return response.json(); +} + +/** + * Get a valid access token, refreshing if expired. + */ +async function getValidAccessToken( + authData: AnthropicAuthData, +): Promise { + if (!authData.access) { + throw new Error( + currentLang === "zh" + ? "缺少 OAuth access token" + : "Missing OAuth access token", + ); + } + + // Check if token is expired (with 60s buffer) + const isExpired = authData.expires && Date.now() > authData.expires - 60000; + + if (!isExpired) { + return authData.access; + } + + // Token expired — refresh + if (!authData.refresh) { + throw new Error( + currentLang === "zh" + ? "OAuth token 已过期且无 refresh token" + : "OAuth token expired with no refresh token", + ); + } + + const result = await refreshAccessToken(authData.refresh); + return result.access_token; +} + +// ============================================================================ +// Rate Limit Probe +// ============================================================================ + +/** + * Parse a single rate limit window from response headers. + * @param headers Response headers + * @param period "5h" or "7d" + */ +function parseWindow(headers: Headers, period: string): RateLimitWindow { + const utilization = parseFloat( + headers.get(`${RL_PREFIX}-${period}-utilization`) || "0", + ); + return { + utilization, + remainPercent: Math.max(0, Math.round((1 - utilization) * 100)), + resetEpoch: parseInt( + headers.get(`${RL_PREFIX}-${period}-reset`) || "0", + 10, + ), + status: headers.get(`${RL_PREFIX}-${period}-status`) || "unknown", + }; +} + +/** + * Make a minimal API call to get rate limit headers. + * Uses claude-haiku-4-5 (cheapest model, ~$0.001 per probe). + */ +async function probeRateLimits(accessToken: string): Promise { + const response = await fetchWithTimeout(`${ANTHROPIC_API_BASE}/v1/messages`, { + method: "POST", + headers: { + Authorization: `Bearer ${accessToken}`, + "Content-Type": "application/json", + "anthropic-version": ANTHROPIC_VERSION, + "anthropic-beta": ANTHROPIC_BETA, + }, + body: JSON.stringify({ + model: PROBE_MODEL, + max_tokens: 1, + messages: [{ role: "user", content: "hi" }], + }), + }); + + // Rate limit headers are returned even on 429 responses + if (!response.ok && response.status !== 429) { + const errorText = await response.text(); + throw new Error(t.claudeApiError(response.status, errorText)); + } + + const h = response.headers; + + return { + session5h: parseWindow(h, "5h"), + weekly7d: parseWindow(h, "7d"), + overallStatus: h.get(`${RL_PREFIX}-status`) || "unknown", + overageStatus: h.get(`${RL_PREFIX}-overage-status`) || "unknown", + }; +} + +// ============================================================================ +// Formatting +// ============================================================================ + +/** + * Format reset time from epoch seconds to human-readable duration. + */ +function formatResetTime(epochSeconds: number): string { + if (!epochSeconds || epochSeconds <= 0) return "-"; + + const diffSeconds = epochSeconds - Math.floor(Date.now() / 1000); + if (diffSeconds <= 0) return currentLang === "zh" ? "已重置" : "reset"; + + return formatDuration(diffSeconds); +} + +/** + * Status indicator emoji for rate limit status. + */ +function statusIcon(status: string): string { + switch (status) { + case "rate_limited": + return "🔴"; + case "warning": + return "🟡"; + case "active": + return "🟢"; + default: + return ""; + } +} + +/** + * Format the OAuth rate limit output (subscription accounts). + */ +function formatOAuthOutput(info: RateLimitInfo): string { + const lines: string[] = []; + + // Account info + lines.push( + `${t.account} Claude (${currentLang === "zh" ? "订阅" : "Subscription"})`, + ); + + // 5-hour session window + lines.push(""); + lines.push(t.claudeSessionLimit); + lines.push( + `${createProgressBar(info.session5h.remainPercent)} ${t.remaining(info.session5h.remainPercent)} ${statusIcon(info.session5h.status)}`, + ); + lines.push(t.resetIn(formatResetTime(info.session5h.resetEpoch))); + + // 7-day weekly window + lines.push(""); + lines.push(t.claudeWeeklyLimit); + lines.push( + `${createProgressBar(info.weekly7d.remainPercent)} ${t.remaining(info.weekly7d.remainPercent)} ${statusIcon(info.weekly7d.status)}`, + ); + lines.push(t.resetIn(formatResetTime(info.weekly7d.resetEpoch))); + + // Overage / rate limited warning + if ( + info.overageStatus === "active" || + info.overallStatus === "rate_limited" + ) { + lines.push(""); + lines.push(t.limitReached); + } + + return lines.join("\n"); +} + +// ============================================================================ +// Admin API Fallback (organization accounts) +// ============================================================================ + +/** + * Read Claude admin API key from env var or config file. + */ +function getAdminApiKey(): string | null { + const envKey = process.env.ANTHROPIC_ADMIN_KEY; + if (envKey?.trim()) return envKey.trim(); + + try { + if (!fs.existsSync(CLAUDE_ADMIN_CONFIG_PATH)) return null; + const content = fs.readFileSync(CLAUDE_ADMIN_CONFIG_PATH, "utf-8"); + const config = JSON.parse(content) as ClaudeAdminConfig; + return config.adminKey?.trim() || null; + } catch { + return null; + } +} + +/** + * Query using Admin API (organization accounts only). + */ +async function queryAdminUsage(adminKey: string): Promise { + const now = new Date(); + const todayStart = `${now.getUTCFullYear()}-${String(now.getUTCMonth() + 1).padStart(2, "0")}-${String(now.getUTCDate()).padStart(2, "0")}T00:00:00Z`; + const tomorrow = new Date(now); + tomorrow.setUTCDate(tomorrow.getUTCDate() + 1); + const tomorrowStart = `${tomorrow.getUTCFullYear()}-${String(tomorrow.getUTCMonth() + 1).padStart(2, "0")}-${String(tomorrow.getUTCDate()).padStart(2, "0")}T00:00:00Z`; + + const params = new URLSearchParams({ + starting_at: todayStart, + ending_at: tomorrowStart, + bucket_width: "1d", + "group_by[]": "model", + }); + + const response = await fetchWithTimeout( + `${ANTHROPIC_API_BASE}/v1/organizations/usage_report/messages?${params}`, + { + method: "GET", + headers: { + "x-api-key": adminKey, + "anthropic-version": ANTHROPIC_VERSION, + }, + }, + ); + + if (!response.ok) { + const errorText = await response.text(); + throw new Error(t.claudeApiError(response.status, errorText)); + } + + return { + success: true, + output: `${t.account} Anthropic (${currentLang === "zh" ? "组织" : "Organization"})\n\n${currentLang === "zh" ? "管理员 API 已连接" : "Admin API connected"}`, + }; +} + +// ============================================================================ +// Export Interface +// ============================================================================ + +/** + * Query Claude (Anthropic) usage. + * + * Priority: + * 1. OAuth tokens (from auth.json) — subscription accounts (Pro/Max) + * 2. Admin API key (from env/config) — organization accounts + * 3. null if neither is configured + * + * @param authData Anthropic OAuth data from auth.json + */ +export async function queryClaudeUsage( + authData?: AnthropicAuthData, +): Promise { + // Priority 1: OAuth tokens (subscription accounts) + if (authData?.type === "oauth" && (authData.access || authData.refresh)) { + try { + const accessToken = await getValidAccessToken(authData); + const rateLimits = await probeRateLimits(accessToken); + return { + success: true, + output: formatOAuthOutput(rateLimits), + }; + } catch (err) { + return { + success: false, + error: err instanceof Error ? err.message : String(err), + }; + } + } + + // Priority 2: Admin API key (organization accounts) + const adminKey = getAdminApiKey(); + if (adminKey) { + try { + return await queryAdminUsage(adminKey); + } catch (err) { + return { + success: false, + error: err instanceof Error ? err.message : String(err), + }; + } + } + + // No auth configured — silently skip + return null; +} diff --git a/plugin/lib/google.ts b/plugin/lib/google.ts index 18471db..8b5eb56 100644 --- a/plugin/lib/google.ts +++ b/plugin/lib/google.ts @@ -54,6 +54,8 @@ interface AccountQuotaInfo { interface ModelConfig { key: string; altKey?: string; + altKeys?: string[]; + prefix?: string; display: string; } @@ -68,11 +70,12 @@ const USER_AGENT = "antigravity/1.11.9 windows/amd64"; // 需要显示的 4 个模型配置 const MODELS_TO_DISPLAY: ModelConfig[] = [ { key: "gemini-3-pro-high", altKey: "gemini-3-pro-low", display: "G3 Pro" }, - { key: "gemini-3-pro-image", display: "G3 Image" }, + { key: "gemini-3-pro-image", altKeys: ["gemini-3-pro-image-generation"], prefix: "gemini-3-pro-image", display: "G3 Image" }, { key: "gemini-3-flash", display: "G3 Flash" }, { key: "claude-opus-4-5-thinking", - altKey: "claude-opus-4-5", + altKeys: ["claude-opus-4-5", "claude-sonnet-4-6-thinking", "claude-sonnet-4-6"], + prefix: "claude-", display: "Claude", }, ]; @@ -131,15 +134,34 @@ function formatResetTimeShort(isoTime: string): string { */ function extractModelQuotas(data: GoogleQuotaResponse): ModelQuota[] { const quotas: ModelQuota[] = []; + const modelKeys = Object.keys(data.models); for (const modelConfig of MODELS_TO_DISPLAY) { let modelInfo = data.models[modelConfig.key]; - // 如果主 key 没有数据,尝试 altKey + // Single altKey fallback (existing behavior) if (!modelInfo && modelConfig.altKey) { modelInfo = data.models[modelConfig.altKey]; } + // Multiple altKeys fallback + if (!modelInfo && modelConfig.altKeys) { + for (const altKey of modelConfig.altKeys) { + modelInfo = data.models[altKey]; + if (modelInfo) break; + } + } + + // Prefix match as last resort + if (!modelInfo && modelConfig.prefix) { + const matchingKey = modelKeys.find((k) => + k.startsWith(modelConfig.prefix!), + ); + if (matchingKey) { + modelInfo = data.models[matchingKey]; + } + } + // 只要模型存在就显示(即使没有 quotaInfo 或额度为 0) if (modelInfo) { const remainingFraction = modelInfo.quotaInfo?.remainingFraction ?? 0; diff --git a/plugin/lib/i18n.ts b/plugin/lib/i18n.ts index 2c15d4b..75e1a0a 100644 --- a/plugin/lib/i18n.ts +++ b/plugin/lib/i18n.ts @@ -4,7 +4,7 @@ * [输入]: 系统语言环境 * [输出]: 翻译函数和当前语言 * [定位]: 被所有平台模块共享使用 - * [同步]: openai.ts, zhipu.ts, mystatus.ts, utils.ts + * [同步]: openai.ts, zhipu.ts, claude.ts, mystatus.ts, utils.ts */ // ============================================================================ @@ -121,6 +121,13 @@ const translations = { "其他方法:\n" + "• 在 VS Code 中点击状态栏的 Copilot 图标查看配额\n" + "• 访问 https://github.com/settings/billing 查看使用情况", + + // Claude (Anthropic) 相关 + claudeTitle: "## Claude (Anthropic) 账号用量", + claudeSessionLimit: "5 小时会话限额", + claudeWeeklyLimit: "7 天周限额", + claudeApiError: (status: number, text: string) => + `Anthropic API 请求失败 (${status}): ${text}`, }, en: { // 时间单位 @@ -199,6 +206,13 @@ const translations = { "Alternatives:\n" + "• Click the Copilot icon in VS Code status bar to view quota\n" + "• Visit https://github.com/settings/billing for usage info", + + // Claude (Anthropic) related + claudeTitle: "## Claude (Anthropic) Account Usage", + claudeSessionLimit: "5-hour session limit", + claudeWeeklyLimit: "7-day weekly limit", + claudeApiError: (status: number, text: string) => + `Anthropic API request failed (${status}): ${text}`, }, } as const; diff --git a/plugin/lib/types.ts b/plugin/lib/types.ts index 01543f8..0d3d7ea 100644 --- a/plugin/lib/types.ts +++ b/plugin/lib/types.ts @@ -2,7 +2,7 @@ * 共享类型定义 * * [定位]: 被所有平台模块共享使用的类型 - * [同步]: openai.ts, zhipu.ts, google.ts, mystatus.ts + * [同步]: openai.ts, zhipu.ts, google.ts, claude.ts, mystatus.ts */ // ============================================================================ @@ -93,6 +93,32 @@ export interface AntigravityAccountsFile { accounts: AntigravityAccount[]; } +/** + * Claude (Anthropic) Admin API configuration + * Stored in ~/.config/opencode/claude-admin-key.json + * or via environment variable ANTHROPIC_ADMIN_KEY + * + * Requires an Admin API key (sk-ant-admin...) from: + * https://console.anthropic.com/settings/admin-keys + */ +export interface ClaudeAdminConfig { + /** Anthropic Admin API key */ + adminKey: string; +} + +/** + * Anthropic OAuth 认证数据 + * From ~/.local/share/opencode/auth.json (anthropic key) + * Supports both OAuth (subscription) and API key auth + */ +export interface AnthropicAuthData { + type: string; + access?: string; + refresh?: string; + expires?: number; + key?: string; +} + /** * 完整认证数据结构 */ @@ -101,6 +127,7 @@ export interface AuthData { "zhipuai-coding-plan"?: ZhipuAuthData; "zai-coding-plan"?: ZhipuAuthData; "github-copilot"?: CopilotAuthData; + anthropic?: AnthropicAuthData; } // ============================================================================ diff --git a/plugin/mystatus.ts b/plugin/mystatus.ts index 00eddee..e9b30e6 100644 --- a/plugin/mystatus.ts +++ b/plugin/mystatus.ts @@ -4,7 +4,7 @@ * [输入]: ~/.local/share/opencode/auth.json 和 ~/.config/opencode/antigravity-accounts.json 中的认证信息 * [输出]: 带进度条的额度使用情况展示 * [定位]: 通过 mystatus 工具查询各账号额度 - * [同步]: lib/openai.ts, lib/zhipu.ts, lib/google.ts, lib/types.ts, lib/i18n.ts + * [同步]: lib/openai.ts, lib/zhipu.ts, lib/google.ts, lib/claude.ts, lib/types.ts, lib/i18n.ts */ import { type Plugin, tool } from "@opencode-ai/plugin"; @@ -18,6 +18,7 @@ import { queryOpenAIUsage } from "./lib/openai"; import { queryZaiUsage, queryZhipuUsage } from "./lib/zhipu"; import { queryGoogleUsage } from "./lib/google"; import { queryCopilotUsage } from "./lib/copilot"; +import { queryClaudeUsage } from "./lib/claude"; // ============================================================================ // 插件导出(唯一导出,避免其他函数被当作插件加载) @@ -28,7 +29,7 @@ export const MyStatusPlugin: Plugin = async () => { tool: { mystatus: tool({ description: - "Query account quota usage for all configured AI platforms. Returns remaining quota percentages, usage stats, and reset countdowns with visual progress bars. Currently supports OpenAI (ChatGPT/Codex), Zhipu AI, Z.ai, Google Antigravity, and GitHub Copilot.", + "Query account quota usage for all configured AI platforms. Returns remaining quota percentages, usage stats, and reset countdowns with visual progress bars. Currently supports OpenAI (ChatGPT/Codex), Zhipu AI, Z.ai, Google Antigravity, GitHub Copilot, and Claude (Anthropic).", args: {}, async execute() { // 1. 读取 auth.json @@ -45,14 +46,15 @@ export const MyStatusPlugin: Plugin = async () => { ); } - // 2. 并行查询所有平台(Google 不依赖 authData) - const [openaiResult, zhipuResult, zaiResult, googleResult, copilotResult] = + // 2. 并行查询所有平台(Google 不依赖 authData,Claude 优先用 OAuth) + const [openaiResult, zhipuResult, zaiResult, googleResult, copilotResult, claudeResult] = await Promise.all([ queryOpenAIUsage(authData.openai), queryZhipuUsage(authData["zhipuai-coding-plan"]), queryZaiUsage(authData["zai-coding-plan"]), queryGoogleUsage(), queryCopilotUsage(authData["github-copilot"]), + queryClaudeUsage(authData.anthropic), ]); // 3. 收集结果 @@ -74,6 +76,9 @@ export const MyStatusPlugin: Plugin = async () => { // 处理 GitHub Copilot 结果 collectResult(copilotResult, t.copilotTitle, results, errors); + // 处理 Claude 结果 + collectResult(claudeResult, t.claudeTitle, results, errors); + // 4. 汇总输出 if (results.length === 0 && errors.length === 0) { return t.noAccounts;