Claude Cursor Skill

api-provider-status

查询 API 供应商的余额、用量与可用状态,支持多供应商监控与定时汇报。

LLM Mart · 0 points · 0 views 0 listing impressions 0 install-command copies
Virus-scanned Reviewed automatically before listing.

Full trust report

Download aaaaqwq-agi-super-team-skills_api-provider-status-cdb04e8.zip · 10 KB
Part of aaaaqwq/agi-super-team — 46 skills

Install

skills CLI npx skills add https://github.com/aAAaqwq/AGI-Super-Team/tree/main/skills/api-provider-status
Claude Code claude plugin marketplace add https://llmmart.ai/marketplace.json && claude plugin install aaaaqwq-agi-super-team@llmmart
Git git clone https://github.com/aAAaqwq/AGI-Super-Team.git

The skills CLI installs just this skill, for any of its supported agents. Claude Code installs the whole aaaaqwq/agi-super-team collection as a plugin from our marketplace. Git is the plain clone.

Skill manifest

API Provider Status Skill

API 供应商状态查询与余额监控。

功能

  • 查询各 API 供应商的余额/用量
  • 生成统计报告
  • 支持定时自动查询

支持的供应商

供应商 查询方式 登录状态存储 状态
Anapi (Claude) API 直接查询 - ❌ 已废弃
Provider-B Playwright / Browser Tool ~/.playwright-data/provider-b ✅ 可用
GitHub Copilot Playwright 抓取 ~/.playwright-data/github ✅ 可用
Provider-A (your-provider) Playwright 抓取 ~/.playwright-data/your-provider ✅ 可用
OpenRouter VIP API 暂不支持 - ⏳
ZAI (智谱) 无公开 API,不查询 - ⏭️ 跳过

查询方法详解

1. Provider-B

方式: Browser Tool 或 Playwright 抓取

查询页面: https://ai.9w7.cn/console

登录状态: ~/.playwright-data/<provider>/model

抓取数据:

  • 当前余额 (¥)
  • 历史消耗 (¥)
  • 请求次数
  • 统计 Tokens
  • 统计额度 (¥)
  • 平均 RPM / TPM

使用 Browser Tool 查询:

  1. 打开 https://ai.9w7.cn/console(openclaw browser 已保存登录状态)
  2. 从页面 snapshot 中提取数据看板信息

2. GitHub Copilot Pro

方式: Playwright 抓取(需要先登录)

查询页面: https://github.com/settings/copilot

登录流程:

from playwright.sync_api import sync_playwright
import os

user_data_dir = os.path.expanduser('~/.playwright-data/github')
os.makedirs(user_data_dir, exist_ok=True)

with sync_playwright() as p:
    context = p.chromium.launch_persistent_context(
        user_data_dir,
        headless=False,  # 显示浏览器让用户登录
    )
    page = context.pages[0] if context.pages else context.new_page()
    page.goto('https://github.com/settings/copilot')
    # 用户手动登录后,登录状态会保存到 user_data_dir

抓取数据:

  • Premium requests 用量百分比
  • 订阅状态 (Active/Inactive)

正则匹配:

import re
usage_match = re.search(r'Premium requests\s*([\d.]+)%', text)
is_active = 'Copilot Pro is active' in text

3. Provider-A (your-provider)

方式: Playwright 抓取(需要先登录)

查询页面: https://your-provider.example.com/console

登录流程:

user_data_dir = os.path.expanduser('~/.playwright-data/your-provider')
# 同上,使用 launch_persistent_context

抓取数据:

  • 当前余额
  • 历史消耗
  • 请求次数
  • 总 Tokens

正则匹配:

balance_match = re.search(r'当前余额💰([\d.]+)', text)
consumed_match = re.search(r'历史消耗💰([\d.]+)', text)
requests_match = re.search(r'请求次数(\d+)', text)
tokens_match = re.search(r'总Tokens(\d+)', text)

使用方法

命令行

# 生成完整报告
python3 ~/clawd/skills/api-provider-status/balance_checker.py report

# 查询指定供应商
python3 ~/clawd/skills/api-provider-status/balance_checker.py query anapi
python3 ~/clawd/skills/api-provider-status/balance_checker.py query github-copilot
python3 ~/clawd/skills/api-provider-status/balance_checker.py query your-provider

# JSON 格式查询所有
python3 ~/clawd/skills/api-provider-status/balance_checker.py all

用量统计

# 最近 12 小时用量
python3 ~/clawd/skills/api-provider-status/usage_tracker.py report 12

# 最近 24 小时用量
python3 ~/clawd/skills/api-provider-status/usage_tracker.py report 24

# JSON 格式
python3 ~/clawd/skills/api-provider-status/usage_tracker.py stats 12

首次登录设置

如果 Playwright 登录状态过期,需要重新登录:

# GitHub 登录
cd ~/clawd/skills/playwright-automation
python3 << 'EOF'
from playwright.sync_api import sync_playwright
import os

user_data_dir = os.path.expanduser('~/.playwright-data/github')
os.makedirs(user_data_dir, exist_ok=True)

with sync_playwright() as p:
    context = p.chromium.launch_persistent_context(
        user_data_dir, headless=False
    )
    page = context.pages[0] if context.pages else context.new_page()
    page.goto('https://github.com/login')
    input('登录完成后按 Enter...')
    context.close()
EOF

# Provider-A登录
python3 << 'EOF'
from playwright.sync_api import sync_playwright
import os

user_data_dir = os.path.expanduser('~/.playwright-data/your-provider')
os.makedirs(user_data_dir, exist_ok=True)

with sync_playwright() as p:
    context = p.chromium.launch_persistent_context(
        user_data_dir, headless=False
    )
    page = context.pages[0] if context.pages else context.new_page()
    page.goto('https://your-provider.example.com/')
    input('登录完成后按 Enter...')
    context.close()
EOF

定时任务

OpenClaw 健康监控

任务名: OpenClaw 健康监控 模型:

  • 检查 OpenClaw 系统状态
  • 查询 Provider-B 和 your-provider 余额
  • 生成健康报告推送到 Telegram
  • 异常时发送 ⚠️ 告警

文件结构

skills/api-provider-status/
├── SKILL.md              # 本文档
├── balance_checker.py    # 余额查询脚本
└── usage_tracker.py      # 用量统计脚本(从 OpenClaw 日志读取)

登录状态存储

~/.playwright-data/
├── <provider>/model                 # Provider-B 登录状态
├── github/               # GitHub 登录状态
└── your-provider/         # Provider-A登录状态

OpenClaw 系统数据 (2026-02-07)

系统状态: ✅ Gateway 运行中 | 11 Agents | 7 活跃会话

通道: Telegram ✅ | WhatsApp ✅

API 供应商:

供应商 余额 状态
Provider-B ¥321.83 ✅ 正常
your-provider ¥41.79 ⚠️ 偏低
GitHub Copilot 免费额度 ✅ 可用
ZAI (智谱) - ✅ 可用
OpenRouter VIP - ✅ 可用
Files (agi-super-team)
  • references
    • credentials.md 660 B
      # 供应商登录凭据
      
      ⚠️ 此文件包含敏感信息,请勿泄露
      
      ## OpenRouter VIP
      - **Dashboard**: https://openrouter.vip/dashboard
      - **登录方式**: 待配置
      - **余额位置**: Dashboard 首页
      
      ## 智谱 ZAI
      - **Dashboard**: https://open.bigmodel.cn/console/account
      - **登录方式**: 待配置
      - **余额位置**: 账户中心
      
      ## Anapi
      - **Dashboard**: https://anapi.9w7.cn/dashboard
      - **登录方式**: 待配置
      - **余额位置**: Dashboard 首页
      
      ---
      
      ## 配置说明
      
      当用户提供登录凭据时,更新此文件:
      
      ```yaml
      provider_name:
        username: xxx
        password: xxx  # 或其他认证方式
        notes: 任何特殊说明
      ```
      
  • scripts
    • query_balance.py 2.7 KB
      #!/usr/bin/env python3
      """
      API Provider Balance Query Script
      查询各 API 供应商的余额(通过已知 API 端点)
      """
      
      import json
      import sys
      import os
      from urllib.request import Request, urlopen
      from urllib.error import URLError, HTTPError
      
      # 供应商配置
      PROVIDERS = {
          "openrouter-vip": {
              "name": "OpenRouter VIP",
              "balance_url": None,  # 需要浏览器登录
              "dashboard": "https://openrouter.vip/dashboard"
          },
          "zai": {
              "name": "智谱 ZAI",
              "balance_url": None,  # 需要浏览器登录
              "dashboard": "https://open.bigmodel.cn/console/account"
          },
          "anapi": {
              "name": "Anapi",
              "balance_url": None,  # 需要浏览器登录
              "dashboard": "https://anapi.9w7.cn/dashboard"
          },
          "openrouter": {
              "name": "OpenRouter",
              "balance_url": "https://openrouter.ai/api/v1/auth/key",
              "dashboard": "https://openrouter.ai/settings/keys"
          },
          "anthropic": {
              "name": "Anthropic",
              "balance_url": None,
              "dashboard": "https://console.anthropic.com/settings/billing"
          },
          "openai": {
              "name": "OpenAI",
              "balance_url": None,
              "dashboard": "https://platform.openai.com/usage"
          }
      }
      
      def query_openrouter_balance(api_key: str) -> dict:
          """Query OpenRouter balance via API"""
          url = "https://openrouter.ai/api/v1/auth/key"
          req = Request(url, headers={"Authorization": f"Bearer {api_key}"})
          try:
              with urlopen(req, timeout=10) as resp:
                  data = json.loads(resp.read().decode())
                  return {
                      "success": True,
                      "balance": data.get("data", {}).get("limit_remaining"),
                      "usage": data.get("data", {}).get("usage"),
                      "raw": data
                  }
          except (URLError, HTTPError) as e:
              return {"success": False, "error": str(e)}
      
      def get_provider_info(provider_id: str) -> dict:
          """Get provider info and dashboard URL"""
          return PROVIDERS.get(provider_id, {
              "name": provider_id,
              "balance_url": None,
              "dashboard": None
          })
      
      def main():
          if len(sys.argv) < 2:
              print(json.dumps({"providers": list(PROVIDERS.keys())}))
              return
          
          provider = sys.argv[1]
          api_key = sys.argv[2] if len(sys.argv) > 2 else None
          
          info = get_provider_info(provider)
          result = {
              "provider": provider,
              "name": info["name"],
              "dashboard": info["dashboard"],
              "balance_api_available": info["balance_url"] is not None
          }
          
          if provider == "openrouter" and api_key:
              balance_result = query_openrouter_balance(api_key)
              result.update(balance_result)
          
          print(json.dumps(result, indent=2))
      
      if __name__ == "__main__":
          main()
      
  • balance_checker.py 13.1 KB
    #!/usr/bin/env python3
    """API 供应商余额查询器
    
    支持的供应商及查询方式:
    - anapi: https://anapi.9w7.cn/api/apikeys/query?key=<key>
    - openrouter-vip: 待添加
    - your-provider: 待添加
    - zai: 待添加
    """
    
    import json
    import os
    import subprocess
    from pathlib import Path
    from datetime import datetime
    
    # 配置文件路径
    AUTH_PROFILES = Path.home() / ".openclaw/agents/telegram-agent/agent/auth-profiles.json"
    
    # 供应商查询配置
    PROVIDERS = {
        "provider-b": {
            "name": "AIXN (Claude Opus 4.6)",
            "query_url": "https://ai.xn--vuq861bvij35ps8cv0uohm.com/console",
            "method": "playwright",
            "user_data_dir": "~/.playwright-data/provider-b",
        },
        "github-copilot": {
            "name": "GitHub Copilot Pro",
            "query_url": "https://github.com/settings/copilot",
            "method": "playwright",
            "user_data_dir": "~/.playwright-data/github",
        },
        "openrouter-vip": {
            "name": "OpenRouter VIP",
            "query_url": None,
            "method": "skip",  # 暂不查询
        },
        "your-provider": {
            "name": "provider-a API",
            "query_url": "https://your-provider.example.com/console",
            "method": "playwright",
            "user_data_dir": "~/.playwright-data/your-provider",
        },
        "zai": {
            "name": "ZAI (智谱)",
            "query_url": None,
            "method": "skip",  # 暂不查询
        },
    }
    
    
    def load_auth_profiles():
        """加载认证配置"""
        if AUTH_PROFILES.exists():
            return json.loads(AUTH_PROFILES.read_text())
        return {"profiles": {}}
    
    
    def query_github_copilot() -> dict:
        """查询 GitHub Copilot Pro 用量"""
        import re
        import time
        
        user_data_dir = os.path.expanduser('~/.playwright-data/github')
        
        if not os.path.exists(user_data_dir):
            return {"success": False, "provider": "github-copilot", "error": "未登录,请先运行登录流程"}
        
        try:
            from playwright.sync_api import sync_playwright
            
            with sync_playwright() as p:
                context = p.chromium.launch_persistent_context(
                    user_data_dir,
                    headless=True,
                    timeout=60000
                )
                
                page = context.pages[0] if context.pages else context.new_page()
                page.set_default_timeout(30000)
                
                page.goto('https://github.com/settings/copilot', wait_until='domcontentloaded')
                time.sleep(3)
                
                text = page.inner_text('body')
                context.close()
                
                if not text:
                    return {"success": False, "provider": "github-copilot", "error": "页面内容为空"}
                
                # 解析数据
                # Premium requests 5.0%
                usage_match = re.search(r'Premium requests\s*([\d.]+)%', text)
                is_active = 'Copilot Pro is active' in text or 'active Copilot Pro' in text
                
                return {
                    "success": True,
                    "provider": "github-copilot",
                    "status": "Active" if is_active else "Inactive",
                    "plan": "Copilot Pro (学生认证)",
                    "premium_usage_pct": float(usage_match.group(1)) if usage_match else 0,
                }
        except ImportError:
            return {"success": False, "provider": "github-copilot", "error": "Playwright 未安装"}
        except Exception as e:
            return {"success": False, "provider": "github-copilot", "error": str(e)}
    
    
    def query_provider_b() -> dict:
        """查询 AIXN 余额(需要已登录的 Playwright session)"""
        import re
        import time
        
        user_data_dir = os.path.expanduser('~/.playwright-data/provider-b')
        
        if not os.path.exists(user_data_dir):
            return {"success": False, "provider": "provider-b", "error": "未登录,请先运行登录流程"}
        
        try:
            from playwright.sync_api import sync_playwright
            
            with sync_playwright() as p:
                context = p.chromium.launch_persistent_context(
                    user_data_dir,
                    headless=True,
                    timeout=30000
                )
                
                page = context.pages[0] if context.pages else context.new_page()
                page.set_default_timeout(20000)
                
                page.goto('https://ai.xn--vuq861bvij35ps8cv0uohm.com/console', wait_until='networkidle')
                time.sleep(2)  # 等待 JS 渲染
                
                # 获取页面文本
                text = page.locator('body').text_content()
                context.close()
                
                if not text:
                    return {"success": False, "provider": "provider-b", "error": "页面内容为空"}
                
                # 解析数据 - AIXN 页面格式
                balance_match = re.search(r'余额[::]\s*¥?([\d.]+)', text)
                consumed_match = re.search(r'已消耗[::]\s*¥?([\d.]+)', text)
                requests_match = re.search(r'请求[次数::]\s*(\d+)', text)
                
                return {
                    "success": True,
                    "provider": "provider-b",
                    "balance": float(balance_match.group(1)) if balance_match else 0,
                    "consumed": float(consumed_match.group(1)) if consumed_match else 0,
                    "requests": int(requests_match.group(1)) if requests_match else 0,
                }
        except ImportError:
            return {"success": False, "provider": "provider-b", "error": "Playwright 未安装"}
        except Exception as e:
            return {"success": False, "provider": "provider-b", "error": str(e)}
    
    
    def query_your-provider() -> dict:
        """查询星价比 API 余额(需要已登录的 Playwright session)"""
        import re
        import time
        
        user_data_dir = os.path.expanduser('~/.playwright-data/your-provider')
        
        if not os.path.exists(user_data_dir):
            return {"success": False, "provider": "your-provider", "error": "未登录,请先运行登录流程"}
        
        try:
            from playwright.sync_api import sync_playwright
            
            with sync_playwright() as p:
                context = p.chromium.launch_persistent_context(
                    user_data_dir,
                    headless=True,
                    timeout=30000
                )
                
                page = context.pages[0] if context.pages else context.new_page()
                page.set_default_timeout(20000)
                
                page.goto('https://your-provider.example.com/console', wait_until='networkidle')
                time.sleep(2)  # 等待 JS 渲染
                
                # 获取页面文本
                text = page.locator('body').text_content()
                context.close()
                
                if not text:
                    return {"success": False, "provider": "your-provider", "error": "页面内容为空"}
                
                # 解析数据
                # 当前余额💰43.08历史消耗💰62.56
                balance_match = re.search(r'当前余额💰([\d.]+)', text)
                consumed_match = re.search(r'历史消耗💰([\d.]+)', text)
                requests_match = re.search(r'请求次数(\d+)', text)
                tokens_match = re.search(r'总Tokens(\d+)', text)
                
                return {
                    "success": True,
                    "provider": "your-provider",
                    "balance": float(balance_match.group(1)) if balance_match else 0,
                    "consumed": float(consumed_match.group(1)) if consumed_match else 0,
                    "requests": int(requests_match.group(1)) if requests_match else 0,
                    "tokens": int(tokens_match.group(1)) if tokens_match else 0,
                }
        except ImportError:
            return {"success": False, "provider": "your-provider", "error": "Playwright 未安装"}
        except Exception as e:
            return {"success": False, "provider": "your-provider", "error": str(e)}
    
    
    def query_anapi(key: str) -> dict:
        """查询 anapi 余额"""
        import urllib.request
        import urllib.error
        
        url = f"https://anapi.9w7.cn/api/apikeys/query?key={key}"
        try:
            req = urllib.request.Request(url, headers={"User-Agent": "Mozilla/5.0"})
            with urllib.request.urlopen(req, timeout=10) as resp:
                data = json.loads(resp.read().decode())
                return {
                    "success": True,
                    "provider": "anapi",
                    "status": data.get("status_display", data.get("status")),
                    "plan": data.get("plan_display", data.get("plan_type")),
                    "expires_at": data.get("expires_at"),
                    "daily_limit": data.get("global_daily_limit", 0),
                    "daily_used": data.get("daily_success_count", 0),
                    "total_success": data.get("success_count", 0),
                    "total_fail": data.get("fail_count", 0),
                    "success_rate": data.get("success_rate", 0),
                    "total_hours": data.get("total_hours"),
                    "used_hours": data.get("used_hours"),
                    "raw": data,
                }
        except Exception as e:
            return {"success": False, "provider": "anapi", "error": str(e)}
    
    
    def query_provider(provider: str) -> dict:
        """查询指定供应商的余额"""
        config = PROVIDERS.get(provider)
        if not config:
            return {"success": False, "provider": provider, "error": "不支持的供应商"}
        
        # GitHub Copilot 不需要 key
        if config["method"] == "playwright" and provider == "github-copilot":
            return query_github_copilot()
        
        # provider-a(用 session)
        if config["method"] == "playwright" and provider == "your-provider":
            return query_your-provider()
        
        # AIXN(用 session)
        if config["method"] == "playwright" and provider == "provider-b":
            return query_provider_b()
        
        # 跳过的供应商
        if config["method"] == "skip":
            return {"success": False, "provider": provider, "error": "暂不查询"}
        
        return {
            "success": False,
            "provider": provider,
            "error": "需要手动查询",
            "method": config["method"],
        }
    
    
    def query_all() -> dict:
        """查询所有供应商"""
        results = {}
        for provider in PROVIDERS:
            results[provider] = query_provider(provider)
        return results
    
    
    def format_balance_report() -> str:
        """生成余额报告"""
        results = query_all()
        now = datetime.now().strftime('%Y-%m-%d %H:%M')
        
        lines = [
            f"💰 API 余额查询 | {now}",
            "━━━━━━━━━━━━━━━━━━━━━━━━",
            "",
        ]
        
        for provider, data in results.items():
            name = PROVIDERS[provider]["name"]
            
            if data.get("success"):
                status = data.get("status", "未知")
                plan = data.get("plan", "未知")
                
                lines.append(f"✅ **{name}**")
                
                # provider-b and provider-a特殊处理
                if provider in ["provider-b", "your-provider"]:
                    balance = data.get("balance", 0)
                    consumed = data.get("consumed", 0)
                    requests = data.get("requests", 0)
                    lines.append(f"   余额: 💰{balance:.2f}")
                    lines.append(f"   已消耗: 💰{consumed:.2f}")
                    lines.append(f"   请求次数: {requests}")
                # GitHub Copilot 特殊处理
                elif provider == "github-copilot":
                    usage_pct = data.get("premium_usage_pct", 0)
                    lines.append(f"   状态: {status} | 套餐: {plan}")
                    lines.append(f"   Premium 用量: {usage_pct}%")
                else:
                    lines.append(f"   状态: {status} | 套餐: {plan}")
                
                # 每日额度
                daily_limit = data.get("daily_limit", 0)
                daily_used = data.get("daily_used", 0)
                if daily_limit > 0:
                    pct = daily_used / daily_limit * 100
                    lines.append(f"   今日: {daily_used}/{daily_limit} ({pct:.1f}%)")
                
                # 到期时间
                expires = data.get("expires_at")
                if expires:
                    exp_date = expires.split("T")[0]
                    lines.append(f"   到期: {exp_date}")
                
                # 成功率
                rate = data.get("success_rate", 0)
                if rate > 0:
                    lines.append(f"   成功率: {rate*100:.1f}%")
            else:
                error = data.get("error", "未知错误")
                lines.append(f"⚠️ **{name}**: {error}")
            
            lines.append("")
        
        lines.append("━━━━━━━━━━━━━━━━━━━━━━━━")
        
        return "\n".join(lines)
    
    
    if __name__ == "__main__":
        import sys
        
        if len(sys.argv) < 2:
            print("""
    API 余额查询器
    
    用法:
      python balance_checker.py report          # 生成报告
      python balance_checker.py query <provider> # 查询指定供应商
      python balance_checker.py all             # JSON 格式查询所有
    """)
            sys.exit(1)
        
        cmd = sys.argv[1]
        
        if cmd == "report":
            print(format_balance_report())
        elif cmd == "query" and len(sys.argv) > 2:
            result = query_provider(sys.argv[2])
            print(json.dumps(result, indent=2, ensure_ascii=False, default=str))
        elif cmd == "all":
            print(json.dumps(query_all(), indent=2, ensure_ascii=False, default=str))
        else:
            print(f"未知命令: {cmd}")
            sys.exit(1)
    
  • SKILL.md 6 KB
    ---
    name: api-provider-status
    description: 查询 API 供应商的余额、用量与可用状态,支持多供应商监控与定时汇报。
    ---
    
    # API Provider Status Skill
    
    API 供应商状态查询与余额监控。
    
    ## 功能
    
    - 查询各 API 供应商的余额/用量
    - 生成统计报告
    - 支持定时自动查询
    
    ## 支持的供应商
    
    | 供应商 | 查询方式 | 登录状态存储 | 状态 |
    |--------|----------|--------------|------|
    | ~~Anapi (Claude)~~ | ~~API 直接查询~~ | - | ❌ 已废弃 |
    | Provider-B | Playwright / Browser Tool | `~/.playwright-data/provider-b` | ✅ 可用 |
    | GitHub Copilot | Playwright 抓取 | `~/.playwright-data/github` | ✅ 可用 |
    | Provider-A (your-provider) | Playwright 抓取 | `~/.playwright-data/your-provider` | ✅ 可用 |
    | OpenRouter VIP | API 暂不支持 | - | ⏳ |
    | ZAI (智谱) | 无公开 API,不查询 | - | ⏭️ 跳过 |
    
    ## 查询方法详解
    
    ### 1. Provider-B
    
    **方式**: Browser Tool 或 Playwright 抓取
    
    **查询页面**: `https://ai.9w7.cn/console`
    
    **登录状态**: `~/.playwright-data/<provider>/model`
    
    **抓取数据**:
    - 当前余额 (¥)
    - 历史消耗 (¥)
    - 请求次数
    - 统计 Tokens
    - 统计额度 (¥)
    - 平均 RPM / TPM
    
    **使用 Browser Tool 查询**:
    1. 打开 `https://ai.9w7.cn/console`(openclaw browser 已保存登录状态)
    2. 从页面 snapshot 中提取数据看板信息
    
    ### 2. GitHub Copilot Pro
    
    **方式**: Playwright 抓取(需要先登录)
    
    **查询页面**: `https://github.com/settings/copilot`
    
    **登录流程**:
    ```python
    from playwright.sync_api import sync_playwright
    import os
    
    user_data_dir = os.path.expanduser('~/.playwright-data/github')
    os.makedirs(user_data_dir, exist_ok=True)
    
    with sync_playwright() as p:
        context = p.chromium.launch_persistent_context(
            user_data_dir,
            headless=False,  # 显示浏览器让用户登录
        )
        page = context.pages[0] if context.pages else context.new_page()
        page.goto('https://github.com/settings/copilot')
        # 用户手动登录后,登录状态会保存到 user_data_dir
    ```
    
    **抓取数据**:
    - Premium requests 用量百分比
    - 订阅状态 (Active/Inactive)
    
    **正则匹配**:
    ```python
    import re
    usage_match = re.search(r'Premium requests\s*([\d.]+)%', text)
    is_active = 'Copilot Pro is active' in text
    ```
    
    ### 3. Provider-A (your-provider)
    
    **方式**: Playwright 抓取(需要先登录)
    
    **查询页面**: `https://your-provider.example.com/console`
    
    **登录流程**:
    ```python
    user_data_dir = os.path.expanduser('~/.playwright-data/your-provider')
    # 同上,使用 launch_persistent_context
    ```
    
    **抓取数据**:
    - 当前余额
    - 历史消耗
    - 请求次数
    - 总 Tokens
    
    **正则匹配**:
    ```python
    balance_match = re.search(r'当前余额💰([\d.]+)', text)
    consumed_match = re.search(r'历史消耗💰([\d.]+)', text)
    requests_match = re.search(r'请求次数(\d+)', text)
    tokens_match = re.search(r'总Tokens(\d+)', text)
    ```
    
    ## 使用方法
    
    ### 命令行
    
    ```bash
    # 生成完整报告
    python3 ~/clawd/skills/api-provider-status/balance_checker.py report
    
    # 查询指定供应商
    python3 ~/clawd/skills/api-provider-status/balance_checker.py query anapi
    python3 ~/clawd/skills/api-provider-status/balance_checker.py query github-copilot
    python3 ~/clawd/skills/api-provider-status/balance_checker.py query your-provider
    
    # JSON 格式查询所有
    python3 ~/clawd/skills/api-provider-status/balance_checker.py all
    ```
    
    ### 用量统计
    
    ```bash
    # 最近 12 小时用量
    python3 ~/clawd/skills/api-provider-status/usage_tracker.py report 12
    
    # 最近 24 小时用量
    python3 ~/clawd/skills/api-provider-status/usage_tracker.py report 24
    
    # JSON 格式
    python3 ~/clawd/skills/api-provider-status/usage_tracker.py stats 12
    ```
    
    ## 首次登录设置
    
    如果 Playwright 登录状态过期,需要重新登录:
    
    ```python
    # GitHub 登录
    cd ~/clawd/skills/playwright-automation
    python3 << 'EOF'
    from playwright.sync_api import sync_playwright
    import os
    
    user_data_dir = os.path.expanduser('~/.playwright-data/github')
    os.makedirs(user_data_dir, exist_ok=True)
    
    with sync_playwright() as p:
        context = p.chromium.launch_persistent_context(
            user_data_dir, headless=False
        )
        page = context.pages[0] if context.pages else context.new_page()
        page.goto('https://github.com/login')
        input('登录完成后按 Enter...')
        context.close()
    EOF
    
    # Provider-A登录
    python3 << 'EOF'
    from playwright.sync_api import sync_playwright
    import os
    
    user_data_dir = os.path.expanduser('~/.playwright-data/your-provider')
    os.makedirs(user_data_dir, exist_ok=True)
    
    with sync_playwright() as p:
        context = p.chromium.launch_persistent_context(
            user_data_dir, headless=False
        )
        page = context.pages[0] if context.pages else context.new_page()
        page.goto('https://your-provider.example.com/')
        input('登录完成后按 Enter...')
        context.close()
    EOF
    ```
    
    ## 定时任务
    
    ### OpenClaw 健康监控
    
    **任务名**: OpenClaw 健康监控
    **模型**: <provider>/glm-4.7
    **频率**: 每天 2:00, 8:00, 14:00, 20:00 (每6小时)
    **功能**:
    - 检查 OpenClaw 系统状态
    - 查询 Provider-B 和 your-provider 余额
    - 生成健康报告推送到 Telegram
    - 异常时发送 ⚠️ 告警
    
    ## 文件结构
    
    ```
    skills/api-provider-status/
    ├── SKILL.md              # 本文档
    ├── balance_checker.py    # 余额查询脚本
    └── usage_tracker.py      # 用量统计脚本(从 OpenClaw 日志读取)
    ```
    
    ## 登录状态存储
    
    ```
    ~/.playwright-data/
    ├── <provider>/model                 # Provider-B 登录状态
    ├── github/               # GitHub 登录状态
    └── your-provider/         # Provider-A登录状态
    ```
    
    ## OpenClaw 系统数据 (2026-02-07)
    
    **系统状态**: ✅ Gateway 运行中 | 11 Agents | 7 活跃会话
    
    **通道**: Telegram ✅ | WhatsApp ✅
    
    **API 供应商**:
    | 供应商 | 余额 | 状态 |
    |--------|------|------|
    | Provider-B | ¥321.83 | ✅ 正常 |
    | your-provider | ¥41.79 | ⚠️ 偏低 |
    | GitHub Copilot | 免费额度 | ✅ 可用 |
    | ZAI (智谱) | - | ✅ 可用 |
    | OpenRouter VIP | - | ✅ 可用 |
    
  • usage_tracker.py 9.1 KB
    #!/usr/bin/env python3
    """API 用量和费用追踪器 - 从 OpenClaw 会话日志中统计实际用量"""
    
    import json
    import os
    import glob
    from datetime import datetime, timedelta
    from pathlib import Path
    
    # OpenClaw 会话日志目录
    OPENCLAW_DIR = Path.home() / ".openclaw"
    AGENTS_DIR = OPENCLAW_DIR / "agents"
    
    # 价格配置 (每 1M tokens)
    PRICING = {
        # Anthropic
        "claude-opus-4-5-20250514": {"input": 15.0, "output": 75.0, "currency": "USD"},
        "claude-sonnet-4-5-20250514": {"input": 3.0, "output": 15.0, "currency": "USD"},
        # ZAI
        "glm-4.7": {"input": 0.5, "output": 0.5, "currency": "CNY"},
        # OpenRouter
        "gpt-5.2": {"input": 2.0, "output": 8.0, "currency": "USD"},
        "gpt-5.2-codex": {"input": 2.0, "output": 8.0, "currency": "USD"},
        "gpt-5.1-codex-mini": {"input": 1.0, "output": 4.0, "currency": "USD"},
        # Gemini
        "gemini-3-pro-preview": {"input": 1.25, "output": 5.0, "currency": "USD"},
        "gemini-3-flash-preview": {"input": 0.075, "output": 0.3, "currency": "USD"},
        "gemini-2.5-flash": {"input": 0.075, "output": 0.3, "currency": "USD"},
        # GitHub Copilot (免费)
        "github-copilot": {"input": 0, "output": 0, "currency": "USD"},
    }
    
    def get_all_session_files():
        """获取所有 agent 的会话日志文件"""
        files = []
        for agent_dir in AGENTS_DIR.iterdir():
            if agent_dir.is_dir():
                sessions_dir = agent_dir / "sessions"
                if sessions_dir.exists():
                    files.extend(sessions_dir.glob("*.jsonl"))
        return files
    
    def parse_session_file(file_path, since_timestamp=None):
        """解析单个会话文件,提取用量信息"""
        usage_records = []
        
        try:
            with open(file_path, 'r', encoding='utf-8') as f:
                for line in f:
                    try:
                        entry = json.loads(line.strip())
                        
                        # 只处理 message 类型
                        if entry.get("type") != "message":
                            continue
                        
                        msg = entry.get("message", {})
                        
                        # 只处理 assistant 消息(有 usage 信息)
                        if msg.get("role") != "assistant":
                            continue
                        
                        usage = msg.get("usage", {})
                        if not usage or usage.get("totalTokens", 0) == 0:
                            continue
                        
                        # 检查时间戳
                        timestamp_str = entry.get("timestamp")
                        if timestamp_str:
                            try:
                                timestamp = datetime.fromisoformat(timestamp_str.replace('Z', '+00:00'))
                                if since_timestamp and timestamp < since_timestamp:
                                    continue
                            except:
                                pass
                        
                        provider = msg.get("provider", "unknown")
                        model = msg.get("model", "unknown")
                        
                        # 跳过内部 mirror
                        if provider == "openclaw" or model == "delivery-mirror":
                            continue
                        
                        usage_records.append({
                            "provider": provider,
                            "model": model,
                            "tokens_in": usage.get("input", 0),
                            "tokens_out": usage.get("output", 0),
                            "cache_read": usage.get("cacheRead", 0),
                            "cache_write": usage.get("cacheWrite", 0),
                            "timestamp": timestamp_str
                        })
                        
                    except json.JSONDecodeError:
                        continue
        except Exception as e:
            print(f"Error reading {file_path}: {e}")
        
        return usage_records
    
    def get_usage_since(hours=12):
        """获取最近 N 小时的用量统计"""
        since = datetime.now().astimezone() - timedelta(hours=hours)
        
        all_records = []
        for file_path in get_all_session_files():
            # 只处理最近修改的文件
            mtime = datetime.fromtimestamp(file_path.stat().st_mtime).astimezone()
            if mtime < since:
                continue
            
            records = parse_session_file(file_path, since)
            all_records.extend(records)
        
        # 按 provider/model 聚合
        stats = {}
        for record in all_records:
            provider = record["provider"]
            model = record["model"]
            key = f"{provider}/{model}"
            
            if key not in stats:
                stats[key] = {
                    "provider": provider,
                    "model": model,
                    "requests": 0,
                    "tokens_in": 0,
                    "tokens_out": 0,
                    "cache_read": 0,
                    "cache_write": 0,
                }
            
            stats[key]["requests"] += 1
            stats[key]["tokens_in"] += record["tokens_in"]
            stats[key]["tokens_out"] += record["tokens_out"]
            stats[key]["cache_read"] += record["cache_read"]
            stats[key]["cache_write"] += record["cache_write"]
        
        # 计算费用
        for key, data in stats.items():
            model = data["model"]
            if model in PRICING:
                price = PRICING[model]
                cost = (data["tokens_in"] / 1_000_000 * price["input"] + 
                        data["tokens_out"] / 1_000_000 * price["output"])
                data["cost"] = cost
                data["currency"] = price["currency"]
            else:
                data["cost"] = 0
                data["currency"] = "USD"
        
        return {
            "period_hours": hours,
            "since": since.isoformat(),
            "stats": stats,
            "total": {
                "requests": sum(s["requests"] for s in stats.values()),
                "tokens_in": sum(s["tokens_in"] for s in stats.values()),
                "tokens_out": sum(s["tokens_out"] for s in stats.values()),
            }
        }
    
    def format_usage_report(hours=12) -> str:
        """生成用量报告"""
        data = get_usage_since(hours)
        
        now = datetime.now().strftime('%Y-%m-%d %H:%M')
        lines = [
            f"📊 API 用量统计 | 最近 {hours} 小时",
            f"⏰ 截至: {now}",
            "━━━━━━━━━━━━━━━━━━━━━━━━",
            "",
        ]
        
        if not data["stats"]:
            lines.append("📭 暂无 API 调用记录")
        else:
            total_cost_usd = 0
            total_cost_cny = 0
            
            # 按请求数排序
            sorted_stats = sorted(data["stats"].items(), key=lambda x: x[1]["requests"], reverse=True)
            
            for key, info in sorted_stats:
                requests = info["requests"]
                tokens_in = info["tokens_in"]
                tokens_out = info["tokens_out"]
                cost = info.get("cost", 0)
                currency = info.get("currency", "USD")
                
                # 格式化 tokens
                in_k = tokens_in / 1000
                out_k = tokens_out / 1000
                
                cost_str = ""
                if cost > 0:
                    if currency == "USD":
                        cost_str = f" ~${cost:.3f}"
                        total_cost_usd += cost
                    else:
                        cost_str = f" ~¥{cost:.3f}"
                        total_cost_cny += cost
                
                lines.append(f"• {key}")
                lines.append(f"  {requests}次 | {in_k:.1f}k↓ {out_k:.1f}k↑{cost_str}")
            
            lines.extend([
                "",
                "━━━━━━━━━━━━━━━━━━━━━━━━",
                f"📈 合计: {data['total']['requests']} 次请求",
                f"🔢 Tokens: {data['total']['tokens_in']//1000}k↓ / {data['total']['tokens_out']//1000}k↑",
            ])
            
            if total_cost_usd > 0:
                lines.append(f"💵 USD: ~${total_cost_usd:.3f}")
            if total_cost_cny > 0:
                lines.append(f"💴 CNY: ~¥{total_cost_cny:.3f}")
        
        lines.append("━━━━━━━━━━━━━━━━━━━━━━━━")
        
        return "\n".join(lines)
    
    
    def get_daily_summary(date_str=None):
        """获取某天的用量(兼容旧接口)"""
        if date_str is None:
            return get_usage_since(24)
        # 对于历史日期,暂时返回空
        return {"date": date_str, "stats": {}, "total": {"requests": 0, "tokens_in": 0, "tokens_out": 0}}
    
    
    if __name__ == "__main__":
        import sys
        
        if len(sys.argv) < 2:
            print("""
    API 用量追踪器 (v2 - 从 OpenClaw 日志读取)
    
    用法:
      python usage_tracker.py report [hours]     # 生成报告 (默认 12 小时)
      python usage_tracker.py stats [hours]      # JSON 格式统计
      python usage_tracker.py today              # 今日统计 (24h)
    """)
            sys.exit(1)
        
        cmd = sys.argv[1]
        
        if cmd == "report":
            hours = int(sys.argv[2]) if len(sys.argv) > 2 else 12
            print(format_usage_report(hours))
        elif cmd == "stats":
            hours = int(sys.argv[2]) if len(sys.argv) > 2 else 12
            print(json.dumps(get_usage_since(hours), indent=2, ensure_ascii=False, default=str))
        elif cmd == "today":
            print(json.dumps(get_usage_since(24), indent=2, ensure_ascii=False, default=str))
        else:
            print(f"未知命令: {cmd}")
            sys.exit(1)
    

Comments (0)

Sign in to join the conversation.

No comments yet.

Reviews (0)

No reviews yet.

Related