#!/usr/bin/env python3 """ repo_to_space_auto.py --------------------- 공개 Git 레포를 Hugging Face Gradio Space로 자동 배포합니다. 필요 환경변수 ------------- BAPI_TOKEN : Brave Search API Key FRIENDLI_TOKEN : Friendli Dedicated Endpoint Token CLI 인자 -------- --repo_url : 원본 Git 레포 URL (필수*¹) --hf_token : 쓰기 권한 Hugging Face Access Token (필수*¹) --private : 비공개 Space로 생성 --hardware : 예) 't4-medium' GPU 인스턴스 지정 *¹ 인자를 생략하면 동명 환경변수(REPO_URL, HF_TOKEN)가 대신 사용됩니다. """ import os, sys, json, argparse, subprocess, tempfile, textwrap, requests, shutil from pathlib import Path import git # GitPython from huggingface_hub import HfApi, login # HF Hub SDK # ---------- Brave Search 헬퍼 ---------- # def brave_search_repo(repo_url: str, count: int = 5) -> list[dict]: api_key = os.getenv("BAPI_TOKEN") if not api_key: raise RuntimeError("환경변수 BAPI_TOKEN이 설정돼 있지 않습니다.") headers = {"X-Subscription-Token": api_key, "Accept": "application/json"} params = {"q": f'site:github.com "{repo_url}"', "count": count, "search_lang": "en"} resp = requests.get("https://api.search.brave.com/res/v1/web/search", headers=headers, params=params, timeout=10) resp.raise_for_status() return resp.json().get("web", {}).get("results", []) # ---------- Friendli LLM 헬퍼 ---------- # def friendli_generate_scaffold(context: str) -> dict: token = os.getenv("FRIENDLI_TOKEN") if not token: raise RuntimeError("환경변수 FRIENDLI_TOKEN이 설정돼 있지 않습니다.") payload = { "model": "dep89a2fld32mcm", "messages": [ {"role": "system", "content": ("You are an expert Hugging Face Space architect. " "Given repository context, output JSON with keys " "`app_py`, `requirements_txt`, `need_docker` (bool), " "`dockerfile` (if needed) and `summary`.")}, {"role": "user", "content": context} ], "max_tokens": 16384, "top_p": 0.8, "stream": False } headers = {"Authorization": f"Bearer {token}", "Content-Type": "application/json"} r = requests.post("https://api.friendli.ai/dedicated/v1/chat/completions", json=payload, headers=headers, timeout=120) r.raise_for_status() return json.loads(r.json()["choices"][0]["message"]["content"]) # ---------- 메인 배포 로직 ---------- # def deploy(repo_url: str, hf_token: str, private=False, hardware=None) -> str: """레포를 Gradio Space로 배포하고 Space URL 반환.""" login(hf_token) api = HfApi(token=hf_token) user = api.whoami()["name"] space = f"{user}/{Path(repo_url).stem.lower().replace('.', '-')}" api.create_repo(space, repo_type="space", space_sdk="gradio", private=private, exist_ok=True, hardware=hardware) with tempfile.TemporaryDirectory() as work: # 1) Brave 메타데이터 brave_meta = brave_search_repo(repo_url) # 2) 원본 레포 클론 src = Path(work) / "src" git.Repo.clone_from(repo_url, src) readme = "" if (src / "README.md").exists(): readme = (src / "README.md").read_text(encoding="utf-8", errors="ignore")[:4000] tree_out = subprocess.run(["bash", "-lc", f"tree -L 2 {src} | head -n 40"], text=True, capture_output=True).stdout context = textwrap.dedent(f""" ## Brave meta {json.dumps(brave_meta, ensure_ascii=False, indent=2)} ## Repository tree (depth 2) {tree_out} ## README (first 4 kB) {readme} """) # 3) Friendli 스캐폴드 생성 scaffold = friendli_generate_scaffold(context) # 4) Space 레포 클론 & 파일 쓰기 dst = Path(work) / "space" api.clone_repo(space, local_dir=dst) (dst / "app.py").write_text(scaffold["app_py"], encoding="utf-8") (dst / "requirements.txt").write_text(scaffold["requirements_txt"], encoding="utf-8") if scaffold.get("need_docker"): (dst / "Dockerfile").write_text(scaffold["dockerfile"], encoding="utf-8") (dst / "README.md").write_text(scaffold["summary"], encoding="utf-8") subprocess.run(["git", "-C", dst, "add", "."], check=True) subprocess.run(["git", "-C", dst, "commit", "-m", "Initial auto-deploy"], check=True) subprocess.run(["git", "-C", dst, "push"], check=True) return f"https://huggingface.co/spaces/{space}" # ---------- CLI 엔트리 ---------- # def main(): parser = argparse.ArgumentParser(description="Git 레포를 Hugging Face Gradio Space로 자동 배포") parser.add_argument("--repo_url", default=os.getenv("REPO_URL"), help="원본 Git 레포지토리 URL (or env REPO_URL)") parser.add_argument("--hf_token", default=os.getenv("HF_TOKEN"), help="쓰기 권한 HF 토큰 (or env HF_TOKEN)") parser.add_argument("--private", action="store_true", help="비공개 Space 생성") parser.add_argument("--hardware", default=None, help="예: 't4-medium'") args = parser.parse_args() if not args.repo_url or not args.hf_token: parser.error("--repo_url 및 --hf_token (또는 동명 환경변수) 둘 다 필요합니다.") try: url = deploy(args.repo_url, args.hf_token, args.private, args.hardware) print(f"✅ 배포 성공: {url}") except Exception as e: print(f"❌ 배포 실패: {e}", file=sys.stderr) sys.exit(1) if __name__ == "__main__": main()