150 lines
4.7 KiB
Bash
Executable File
150 lines
4.7 KiB
Bash
Executable File
#!/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
|