#!/usr/bin/env python """Show or set a public VARIABLE on one of the DemoPrep HF Spaces. Variables are the per-Space switches that make ONE commit behave differently on test and prod — e.g. MUZE_ENGINE_MAIN_TABS=false as an emergency off on one Space (unset = ON everywhere since 2026-09-22). They are NOT secrets: on a public Space anyone can read them. Secrets go through scripts/setup_test_mcp_secrets.py. ./.venv/bin/python scripts/set_space_variable.py test-mcp --show ./.venv/bin/python scripts/set_space_variable.py test-mcp MUZE_ENGINE_MAIN_TABS true ./.venv/bin/python scripts/set_space_variable.py mcp MUZE_ENGINE_MAIN_TABS false --dry-run ./.venv/bin/python scripts/set_space_variable.py test-mcp MUZE_ENGINE_MAIN_TABS --delete Space may be a short name (test-mcp, mcp, demoprep, test-demoprep) or a full `owner/name` repo id. Setting or deleting a variable RESTARTS the Space and kills any build in flight. Check the MCP `status` tool for active_builds first. Set the variable BEFORE pushing code that reads it — old code ignores the variable, so the extra restart is harmless, and the new code comes up already in the intended mode. Token: HF_TOKEN from the environment, else from the .env named by --env (default: /.env). Reading variables on a public Space needs no token. """ import argparse import os import sys from pathlib import Path SHORT_NAMES = { "test-mcp": "thoughtspot-demoprep/test-mcp", "mcp": "thoughtspot-demoprep/mcp", "demoprep": "thoughtspot-dp/demoprep", "test-demoprep": "thoughtspot-dp/test-demoprep", } DEFAULT_ENV = Path(__file__).resolve().parent.parent / ".env" def repo_id_of(space: str) -> str: if "/" in space: return space if space in SHORT_NAMES: return SHORT_NAMES[space] sys.exit(f"ERROR: unknown Space {space!r}. Use one of {sorted(SHORT_NAMES)} or a full owner/name.") def token_from(env_path: Path) -> str: tok = (os.getenv("HF_TOKEN") or "").strip() if tok: return tok if env_path.exists(): for raw in env_path.read_text().splitlines(): line = raw.strip() if line.startswith("HF_TOKEN="): return line.split("=", 1)[1].strip().strip('"').strip("'") sys.exit(f"ERROR: no HF_TOKEN in the environment or in {env_path}. " "A write needs one; pass --env to point at the .env that has it.") def show(api, repo_id: str) -> None: variables = api.get_space_variables(repo_id=repo_id) if not variables: print(f"{repo_id}: no variables set") return print(f"{repo_id}:") for key, var in sorted(variables.items()): updated = getattr(var, "updated_at", None) or getattr(var, "updatedAt", "") print(f" {key} = {var.value!r} (updated {updated})") def main() -> None: ap = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter) ap.add_argument("space", help="test-mcp | mcp | demoprep | test-demoprep | owner/name") ap.add_argument("key", nargs="?", help="variable name") ap.add_argument("value", nargs="?", help="value to set") ap.add_argument("--show", action="store_true", help="list the Space's variables and exit") ap.add_argument("--delete", action="store_true", help="delete KEY instead of setting it") ap.add_argument("--dry-run", action="store_true", help="say what would change, touch nothing") ap.add_argument("--env", type=Path, default=DEFAULT_ENV, help="the .env holding HF_TOKEN") args = ap.parse_args() from huggingface_hub import HfApi repo_id = repo_id_of(args.space) if args.show or not args.key: # Reads work without a token on a public Space; send one if we have it. tok = (os.getenv("HF_TOKEN") or "").strip() or None show(HfApi(token=tok), repo_id) return if args.delete and args.value is not None: sys.exit("ERROR: --delete takes KEY only, no value.") if not args.delete and args.value is None: sys.exit("ERROR: give a VALUE to set, or --delete, or --show.") action = f"DELETE {args.key}" if args.delete else f"SET {args.key}={args.value!r}" print(f"{action} on {repo_id} — this RESTARTS the Space and kills in-flight builds.") if args.dry_run: print("--dry-run: nothing was changed.") return if input("Proceed? [y/N] ").strip().lower() != "y": sys.exit("Aborted.") api = HfApi(token=token_from(args.env)) if args.delete: api.delete_space_variable(repo_id=repo_id, key=args.key) else: api.add_space_variable(repo_id=repo_id, key=args.key, value=args.value) print("done. Now:") show(api, repo_id) print("\nNEXT: after the restart, call the `config_check` tool and confirm `muze.main_tabs` " "(or whatever the variable drives) reads as intended. A booted container is not a verified one.") if __name__ == "__main__": main()