#!/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 : 원본 GitHub/GitLab 등 공개 레포 URL --hf_token : 쓰기 권한 Hugging Face Access Token 옵션: --private : 비공개 Space 생성 --hardware : 예) 't4-medium' GPU 인스턴스 지정 """ import os, sys, json, argparse, subprocess, tempfile, textwrap, requests, shutil from pathlib import Path import git # GitPython from huggingface_hub import HfApi, login # ---------- 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( repo_id=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 = "" readme_path = src / "README.md" if readme_path.exists(): readme = readme_path.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") # 5) 커밋 & 푸시 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(): p = argparse.ArgumentParser(description="Git 레포를 Hugging Face Gradio Space로 자동 배포") p.add_argument("--repo_url", required=True, help="원본 Git 레포지토리 URL") p.add_argument("--hf_token", required=True, help="쓰기 권한 Hugging Face 토큰") p.add_argument("--private", action="store_true", help="비공개 Space 생성") p.add_argument("--hardware", default=None, help="예: 't4-medium'") args = p.parse_args() 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()