#!/usr/bin/env python3 """Apply this plugin's settings snapshot to a harness config file. python apply.py --config ~/.dsh/config.json # merge defaults in python apply.py --config ~/.dsh/config.json --dry-run python apply.py --config ~/.dsh/config.json --only model.* provider.* Existing keys are kept unless --overwrite is given, so applying this to a machine someone has already tuned will not stomp their choices. A backup of the original file is written next to it before any change. """ from __future__ import annotations import argparse import fnmatch import json import shutil import sys from pathlib import Path DEFAULTS = Path(__file__).resolve().parent / "defaults.json" def flatten(prefix: str, value, out: dict) -> None: """`{"model": {"temperature": 0.7}}` -> `{"model.temperature": 0.7}`.""" if isinstance(value, dict): for k, v in value.items(): flatten(f"{prefix}.{k}" if prefix else k, v, out) else: out[prefix] = value def load_defaults() -> dict: raw = json.loads(DEFAULTS.read_text(encoding="utf-8")) out: dict = {} flatten("", raw, out) return {k: v for k, v in out.items() if not k.startswith("_")} def set_path(cfg: dict, dotted: str, value) -> None: parts = dotted.split(".") node = cfg for p in parts[:-1]: nxt = node.get(p) if not isinstance(nxt, dict): nxt = {} node[p] = nxt node = nxt node[parts[-1]] = value def get_path(cfg: dict, dotted: str): node = cfg for p in dotted.split("."): if not isinstance(node, dict) or p not in node: return None, False node = node[p] return node, True def apply(config_path: Path, *, overwrite: bool, dry_run: bool, only: list[str] | None) -> dict: defaults = load_defaults() if only: defaults = {k: v for k, v in defaults.items() if any(fnmatch.fnmatch(k, pat) for pat in only)} if config_path.is_file(): cfg = json.loads(config_path.read_text(encoding="utf-8") or "{}") else: cfg = {} added, kept = [], [] for key, value in sorted(defaults.items()): _cur, present = get_path(cfg, key) if present and not overwrite: kept.append(key) continue set_path(cfg, key, value) added.append(key) if added and not dry_run: config_path.parent.mkdir(parents=True, exist_ok=True) if config_path.is_file(): shutil.copy2(config_path, config_path.with_suffix(config_path.suffix + ".bak")) config_path.write_text( json.dumps(cfg, indent=2, ensure_ascii=False) + "\n", encoding="utf-8") return {"added": added, "kept": kept, "config": str(config_path)} def main(argv=None) -> int: ap = argparse.ArgumentParser(description="apply team default settings") ap.add_argument("--config", type=Path, required=True) ap.add_argument("--overwrite", action="store_true", help="replace values that already exist") ap.add_argument("--dry-run", action="store_true") ap.add_argument("--only", nargs="*", metavar="GLOB", help="limit to keys matching these patterns, e.g. 'model.*'") args = ap.parse_args(argv) result = apply(args.config, overwrite=args.overwrite, dry_run=args.dry_run, only=args.only) if args.dry_run: print("dry run — nothing written") print(f"config : {result['config']}") print(f"added : {len(result['added'])}" + (f" {result['added']}" if result["added"] else "")) print(f"kept : {len(result['kept'])}" + (f" {result['kept']}" if result["kept"] else "")) return 0 if __name__ == "__main__": sys.exit(main())