"""Generate one of pi's JSON files from the repo and its fragments. pi-merge.py Run by link/pi.sh, for ~/.pi/agent/models.json (key "providers") and ~/.pi/agent/mcp.json (key "mcpServers"). It lives in a file rather than a heredoc inside $( ): macOS ships bash 3.2, which does not skip heredoc content when it scans for the end of a command substitution, so an odd number of apostrophes in here would stop the whole script parsing -- which it did, on 2026-09-25. """ import glob, json, os, sys merge_key, base_path, frag_dir, target = sys.argv[1:5] def load(path): with open(path) as f: return json.load(f) # The repo's file, if there is one, then each fragment's. A fragment may add an # entry under merge_key or replace one of the same name; any other top-level key # takes the last file's value. Which entry came from where is reported: a # fragment quietly shadowing one the repo ships would otherwise be invisible. # With no repo file every entry comes from a fragment, so only a replacement -- # one fragment shadowing another's -- is worth a note. paths = sorted(glob.glob(os.path.join(frag_dir, "*.json"))) if base_path != "-": paths.insert(0, base_path) # A symlink is the pre-generation state: replace it, even with no fragments, so # the live file is never a tracked one. was_link = os.path.islink(target) # Nothing to generate from, and nothing there: leave it absent rather than # create an empty file. if not paths and not was_link and not os.path.exists(target): print("unchanged:") sys.exit(0) desired = {} notes = [] for path in paths: try: layer = load(path) except (OSError, ValueError) as e: print("error:cannot read %s: %s" % (path, e)) sys.exit(0) for key, value in layer.items(): if key == merge_key: entries = desired.setdefault(merge_key, {}) if path != base_path: for name in value: if base_path == "-" and name not in entries: continue notes.append("%s %s by %s" % ( name, "replaced" if name in entries else "added", os.path.basename(path), )) entries.update(value) else: desired[key] = value try: current = None if was_link else load(target) except (OSError, ValueError): current = None for note in notes: print("note:" + note) if current == desired: print("unchanged:") elif os.environ.get("DRY_RUN"): print("would-write:" + ("replacing the link" if was_link else "merged changes")) else: tmp = target + ".dotfiles-tmp" with open(tmp, "w") as f: json.dump(desired, f, indent=2) f.write("\n") os.replace(tmp, target) print("wrote:" + ("replaced the link" if was_link else "merged changes"))