Files
openclaw/scripts/codex-limit-monitor.sh
2026-03-01 17:44:19 +03:00

150 lines
4.7 KiB
Bash
Executable File
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env bash
set -euo pipefail
SKILL_DIR="/home/openclaw/.openclaw/workspace/skills/codex-account-switcher-skill"
cd "$SKILL_DIR"
before_json="$(./codex-accounts.py list --json 2>/dev/null || echo '{}')"
auto_json="$(./codex-accounts.py auto --json 2>/dev/null || echo '{}')"
after_json="$(./codex-accounts.py list --json 2>/dev/null || echo '{}')"
python3 - "$before_json" "$auto_json" "$after_json" <<'PY'
import json
import subprocess
import sys
from datetime import datetime, timezone, timedelta
before_s, auto_s, after_s = sys.argv[1:4]
def j(s):
try:
return json.loads(s)
except Exception:
return {}
before = j(before_s)
auto = j(auto_s)
after = j(after_s)
MSK = timezone(timedelta(hours=3))
now = datetime.now(MSK).strftime('%Y-%m-%d %H:%M:%S MSK')
accounts = [a.get('name') for a in after.get('accounts', []) if a.get('name') and not str(a.get('name')).startswith('.')]
old_name = before.get('active')
new_name = auto.get('switched_to') or after.get('active')
all_acc = auto.get('all_accounts', {}) if isinstance(auto.get('all_accounts', {}), dict) else {}
def account_ttl(data, name):
for acc in data.get('accounts', []) if isinstance(data.get('accounts'), list) else []:
if acc.get('name') == name:
ttl = acc.get('token_ttl_seconds')
if isinstance(ttl, (int, float)):
return int(ttl)
return None
def fmt_ttl(seconds):
if seconds is None:
return 'ttl н/д'
if seconds <= 0:
return 'ttl expired'
m = seconds // 60
if m < 60:
return f'ttl {m}m'
h = m // 60
rm = m % 60
return f'ttl {h}h{rm:02d}m'
def auth_probe(name):
try:
subprocess.run(['./codex-accounts.py', 'use', name], check=False, capture_output=True, text=True, timeout=15)
p = subprocess.run(['codex', 'exec', 'PING', '--skip-git-repo-check', '--json'], check=False, capture_output=True, text=True, timeout=60)
return 'ok' if p.returncode == 0 else 'fail'
except Exception:
return 'fail'
probe = {}
for n in accounts:
probe[n] = auth_probe(n)
# Restore active account after probes
restore = new_name or old_name
if restore:
subprocess.run(['./codex-accounts.py', 'use', restore], check=False, capture_output=True, text=True, timeout=15)
def fmt_acc(name):
d = all_acc.get(name, {}) if name else {}
auth = probe.get(name, 'н/д') if name else 'н/д'
if d and 'error' not in d:
w = d.get('weekly_used')
a = d.get('available')
dd = d.get('daily_used')
if w is not None and a is not None:
return f"auth={auth}, week used={w:.0f}%, week left={a:.0f}%, day used={dd:.0f}%"
ttl_after = account_ttl(after, name)
ttl_before = account_ttl(before, name)
ttl = ttl_after if ttl_after is not None else ttl_before
if d and 'error' in d:
return f"auth={auth}, лимит: {d['error']}, {fmt_ttl(ttl)}"
return f"auth={auth}, {fmt_ttl(ttl)}"
if auto.get('error'):
status = f"quota-check failed: {auto.get('error')}"
else:
status = 'ok'
switch_note = 'без переключения'
if old_name and new_name and old_name != new_name:
switch_note = f"переключение: {old_name} -> {new_name}"
elif new_name:
switch_note = f"активный аккаунт: {new_name}"
accounts_summary = ', '.join([f"{n}:{probe.get(n, 'н/д')}" for n in accounts]) if accounts else 'н/д'
def reset_eta(epoch):
try:
if not epoch:
return 'н/д'
dt = datetime.fromtimestamp(int(epoch), tz=MSK)
return dt.strftime('%Y-%m-%d %H:%M MSK')
except Exception:
return 'н/д'
lines = []
lines.append(f"📊 Codex quota report — {now}")
lines.append(f"Статус: {status}")
lines.append(f"Маршрут: {switch_note}")
lines.append("")
lines.append("Аккаунты:")
for name in accounts:
d = all_acc.get(name, {})
auth = probe.get(name, 'н/д')
ttl = account_ttl(after, name)
ttl_text = fmt_ttl(ttl)
if d and 'error' not in d and d.get('weekly_used') is not None:
lines.append(
f"- {name}: auth={auth}, за неделю использовано {d['weekly_used']:.0f}% / осталось {d['available']:.0f}%, "
f"за день использовано {d.get('daily_used', 0):.0f}%, сброс лимита {reset_eta(d.get('weekly_resets_at'))}, {ttl_text}"
)
elif d and 'error' in d:
lines.append(f"- {name}: auth={auth}, лимит: {d['error']}, {ttl_text}")
else:
lines.append(f"- {name}: auth={auth}, {ttl_text}")
if new_name:
lines.append("")
lines.append(f"✅ Рекомендуемый/активный: {new_name}")
print("\n".join(lines))
PY