import os import time import shutil from pathlib import Path from huggingface_hub import HfApi, snapshot_download # Configuration fetched from Space Secrets REPO_ID = os.getenv("DATASET_REPO_ID") TOKEN = os.getenv("HF_TOKEN") # Core directories WORKSPACE_DIR = Path("/home/node/workspace") OPENCODE_DIR = Path("/home/node/.local/share/opencode") # Default Linux storage GLOBAL_CONFIG_DIR = Path("/home/node/.config/opencode") # Global AGENTS.md + skills CACHE_DIR = Path("/home/node/.sync_cache") INTERVAL = 30 * 60 # 30 minutes in seconds api = HfApi(token=TOKEN) def initial_pull(): if not REPO_ID: print("No DATASET_REPO_ID provided. Skipping pull.") return print("Fetching dataset on startup...") try: # Download the latest snapshot of the private dataset local_dir = snapshot_download(repo_id=REPO_ID, repo_type="dataset", token=TOKEN) # Restore workspace files (includes env.example / env.json.local) ds_workspace = Path(local_dir) / "workspace" if ds_workspace.exists(): shutil.copytree(ds_workspace, WORKSPACE_DIR, dirs_exist_ok=True) # Restore OpenCode sessions and history ds_opencode = Path(local_dir) / "opencode" if ds_opencode.exists(): shutil.copytree(ds_opencode, OPENCODE_DIR, dirs_exist_ok=True) # Restore global config (AGENTS.md and skills) ds_global = Path(local_dir) / "global" if ds_global.exists(): shutil.copytree(ds_global, GLOBAL_CONFIG_DIR, dirs_exist_ok=True) # Create an initial cache to track future deletions shutil.copytree(OPENCODE_DIR, CACHE_DIR, dirs_exist_ok=True) print("Restoration complete.") except Exception as e: print(f"Initial pull failed (likely an empty/new dataset): {e}") def push_to_dataset(): if not REPO_ID: return print("Initiating 30-minute sync cycle...") session_dir = OPENCODE_DIR / "storage" / "session" cache_session_dir = CACHE_DIR / "storage" / "session" # 1. Check for trashed sessions if cache_session_dir.exists(): for cache_file in cache_session_dir.rglob("*.json"): relative_path = cache_file.relative_to(cache_session_dir) current_file = session_dir / relative_path if not current_file.exists(): print(f"Detected deleted session: {relative_path}. Moving to trash...") api.upload_file( path_or_fileobj=str(cache_file), path_in_repo=f"trashed/sessions/{relative_path}", repo_id=REPO_ID, repo_type="dataset", token=TOKEN ) # 2. Sync workspace and configuration # The API automatically ignores the push if files are unchanged/empty. # Files like env.json.local will upload securely since the dataset is private. api.upload_folder( folder_path=str(WORKSPACE_DIR), path_in_repo="workspace", repo_id=REPO_ID, repo_type="dataset", token=TOKEN, ignore_patterns=["**/__pycache__/*", "**/.git/*"] ) api.upload_folder( folder_path=str(OPENCODE_DIR), path_in_repo="opencode", repo_id=REPO_ID, repo_type="dataset", token=TOKEN, ignore_patterns=["**/*.log", "log", "log/**", "**/log/*"] ) # 3. Sync global config (AGENTS.md and skills) api.upload_folder( folder_path=str(GLOBAL_CONFIG_DIR), path_in_repo="global", repo_id=REPO_ID, repo_type="dataset", token=TOKEN, ignore_patterns=["**/node_modules/**", "node_modules/**", "node_modules", "**/*.log"] ) # 4. Rebuild local deletion cache if CACHE_DIR.exists(): shutil.rmtree(CACHE_DIR) shutil.copytree(OPENCODE_DIR, CACHE_DIR, dirs_exist_ok=True) print("Sync cycle finished successfully.") if __name__ == "__main__": initial_pull() while True: time.sleep(INTERVAL) push_to_dataset()