#!/usr/bin/env python3 """Ф1 шаг 0 — ЖИВОЙ листинг моделей по каждому ключу. $0 (GET /models не биллится). Зачем отдельным скриптом и почему первым. Гардрейл CLAUDE.md: «слаги моделей не менять без live- фактчека `/models`», и quirks 00 несёт отдельный урок календаря — «слаг живой ≠ модель та же» (DeepSeek 31.07 сменил веса под неизменным слагом, D39.61). Пре-рег обязан назвать кандидатов слагами, снятыми В ДЕНЬ ЗАПУСКА, иначе смета и вердикты Ф1 повисают на памяти сессии. Ключи не печатаются никогда — скрипт выводит только имена моделей и HTTP-код. `.env` читается через python-dotenv; для Claude файл закрыт deny-правилом, и это правильно. """ from __future__ import annotations import json import os from pathlib import Path import requests from dotenv import load_dotenv REPO = Path("/home/ubuntu/projects/textmachine") load_dotenv(REPO / "eval" / ".env") OUT = Path.home() / "books" / "role-topology" OUT.mkdir(parents=True, exist_ok=True) # (провайдер, base_url, env-ключ) — источник: quirks 00 «Эндпоинты и модели»; семь ключей, # перечисленных промтом эксп-21 Ф1. PROVIDERS = [ ("deepseek", "https://api.deepseek.com/v1", "DEEPSEEK_API_KEY"), ("zai", "https://api.z.ai/api/paas/v4", "ZAI_API_KEY"), ("kimi", "https://api.moonshot.ai/v1", "KIMI_API_KEY"), ("openai", "https://api.openai.com/v1", "OPENAI_API_KEY"), ("gemini", "https://generativelanguage.googleapis.com/v1beta/openai", "GEMINI_API_KEY"), ("xai", "https://api.x.ai/v1", "XAI_API_KEY"), ("mistral", "https://api.mistral.ai/v1", "MISTRAL_API_KEY"), ] def main() -> None: snap = {} for name, base, env in PROVIDERS: key = os.getenv(env) if not key: print(f"{name:10s} КЛЮЧА НЕТ ({env}) — провайдер вне скрина, объявить в пре-реге") snap[name] = {"error": "no key"} continue try: r = requests.get(f"{base}/models", headers={"Authorization": f"Bearer {key}"}, timeout=30) except Exception as e: # noqa: BLE001 print(f"{name:10s} СЕТЬ: {type(e).__name__}") snap[name] = {"error": type(e).__name__} continue if r.status_code != 200: print(f"{name:10s} HTTP {r.status_code}") snap[name] = {"error": f"http {r.status_code}", "body": r.text[:200]} continue ids = sorted(m["id"] for m in r.json().get("data", [])) snap[name] = {"models": ids} print(f"{name:10s} {len(ids):3d}: {', '.join(ids[:40])}") if len(ids) > 40: print(f"{'':10s} …ещё {len(ids) - 40}") (OUT / "models-live.json").write_text(json.dumps(snap, ensure_ascii=False, indent=1), encoding="utf-8") print(f"\nснимок → {OUT / 'models-live.json'}") if __name__ == "__main__": main()