"""Deploy AuditEnv to HuggingFace Spaces — full sync with stale-file cleanup.""" from huggingface_hub import HfApi, CommitOperationAdd, CommitOperationDelete import os REPO_ID = "minato1718/Corp_AI_Auditor" ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) # --- Inline file contents for critical config files --- DOCKERFILE = """\ FROM python:3.11-slim WORKDIR /app COPY requirements.runtime.txt ./ RUN pip install --no-cache-dir -r requirements.runtime.txt COPY src ./src COPY configs ./configs COPY openenv.yaml ./openenv.yaml COPY inference.py ./inference.py COPY scripts ./scripts COPY server ./server COPY app.py ./app.py RUN mkdir -p data EXPOSE 7860 CMD ["uvicorn", "auditenv.server:app", "--host", "0.0.0.0", "--port", "7860", "--app-dir", "src"] """ REQUIREMENTS_RUNTIME = """\ fastapi>=0.115.0 uvicorn>=0.30.0 pydantic>=2.7.0 pyyaml>=6.0.1 gradio>=4.44.0 requests>=2.32.0 httpx>=0.27.0 openai>=1.51.0 """ README_HF = """\ --- title: AuditEnv - OpenEnv Agentic Environment emoji: 🏢 colorFrom: indigo colorTo: purple sdk: docker app_port: 7860 pinned: false license: mit --- # 🏢 AuditEnv — OpenEnv Agentic Environment Autonomous compliance auditing powered by reinforcement learning. """ SKIP = {"__pycache__", ".pyc", ".egg-info", ".venv", ".git", "artifacts", ".pytest_cache", ".env"} def should_skip(path): parts = path.replace("\\", "/").split("/") return any(s in parts or any(p.endswith(s) for p in parts) for s in SKIP) def collect_dir(local_dir, repo_prefix): ops = [] paths = set() for dp, dns, fns in os.walk(local_dir): dns[:] = [d for d in dns if d not in SKIP] for f in fns: fp = os.path.join(dp, f) if should_skip(fp): continue rel = os.path.relpath(fp, ROOT).replace("\\", "/") ops.append(CommitOperationAdd(path_in_repo=rel, path_or_fileobj=fp)) paths.add(rel) return ops, paths def main(): api = HfApi() add_ops = [] desired_paths = set() def add_inline(path_in_repo: str, payload: bytes): add_ops.append(CommitOperationAdd(path_in_repo=path_in_repo, path_or_fileobj=payload)) desired_paths.add(path_in_repo) def add_local(path_in_repo: str, abs_path: str): add_ops.append(CommitOperationAdd(path_in_repo=path_in_repo, path_or_fileobj=abs_path)) desired_paths.add(path_in_repo) # Inline overrides add_inline("Dockerfile", DOCKERFILE.encode()) add_inline("requirements.runtime.txt", REQUIREMENTS_RUNTIME.encode()) add_inline("README.md", README_HF.encode()) # Local files for f in ["openenv.yaml", "inference.py", "app.py", "pyproject.toml", "uv.lock"]: fp = os.path.join(ROOT, f) if os.path.isfile(fp): add_local(f, fp) # Directories for d in ["src", "configs", "scripts", "server"]: dp = os.path.join(ROOT, d) if os.path.isdir(dp): dir_ops, dir_paths = collect_dir(dp, d) add_ops.extend(dir_ops) desired_paths.update(dir_paths) # Remove files in Space repo that are no longer part of the deploy set. remote_files = set(api.list_repo_files(repo_id=REPO_ID, repo_type="space")) protected = {".gitattributes"} delete_paths = sorted( path for path in remote_files if path not in desired_paths and path not in protected ) delete_ops = [CommitOperationDelete(path_in_repo=path) for path in delete_paths] ops = delete_ops + add_ops print(f"[*] Syncing Space {REPO_ID}...") print(f" Add/Update: {len(add_ops)} files") print(f" Delete: {len(delete_ops)} stale files") api.create_commit( repo_id=REPO_ID, repo_type="space", operations=ops, commit_message="Sync Space with repo and remove stale files", ) print(f"[✓] Done! https://huggingface.co/spaces/{REPO_ID}") if __name__ == "__main__": main()