Replace assistent-personas overlay seed with assistent-extensions SFTP and an assistent: section in extensions.yaml so personalities install like other extensions without private URLs in the public repo. Co-authored-by: Cursor <cursoragent@cursor.com>
97 lines
2.6 KiB
Python
97 lines
2.6 KiB
Python
"""Local app trees: Models / Wildcards / CustomWorkflows / Output."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import hashlib
|
|
from pathlib import Path
|
|
|
|
SKIP_NAMES = {".gitkeep", "README.md", "README.txt", ".gitignore"}
|
|
WEIGHT_SUFFIXES = {".safetensors", ".ckpt", ".pt", ".pth", ".bin", ".gguf", ".sft", ".onnx"}
|
|
META_SUFFIXES = {".json", ".civitai.json", ".swarm.json", ".preview.png", ".png", ".webp", ".jpg"}
|
|
|
|
|
|
def is_skipped(path: Path) -> bool:
|
|
name = path.name
|
|
if name in SKIP_NAMES or name.startswith("."):
|
|
return True
|
|
# Never sync git metadata (persona packs push shelves only).
|
|
if any(part == ".git" for part in path.parts):
|
|
return True
|
|
return False
|
|
|
|
|
|
def has_payload(root: Path) -> bool:
|
|
if not root.is_dir():
|
|
return False
|
|
for path in root.rglob("*"):
|
|
if path.is_file() and not is_skipped(path):
|
|
return True
|
|
return False
|
|
|
|
|
|
def folder_bytes(root: Path) -> int:
|
|
if not root.is_dir():
|
|
return 0
|
|
total = 0
|
|
for path in root.rglob("*"):
|
|
if path.is_file() and not is_skipped(path):
|
|
total += path.stat().st_size
|
|
return total
|
|
|
|
|
|
def iter_payload_files(root: Path) -> list[Path]:
|
|
if not root.is_dir():
|
|
return []
|
|
found = []
|
|
for path in sorted(root.rglob("*")):
|
|
if path.is_file() and not is_skipped(path):
|
|
found.append(path)
|
|
return found
|
|
|
|
|
|
def is_weight(path: Path) -> bool:
|
|
return path.suffix.lower() in WEIGHT_SUFFIXES
|
|
|
|
|
|
def sha256_file(path: Path, chunk: int = 1024 * 1024) -> str:
|
|
digest = hashlib.sha256()
|
|
with path.open("rb") as fh:
|
|
while True:
|
|
block = fh.read(chunk)
|
|
if not block:
|
|
break
|
|
digest.update(block)
|
|
return digest.hexdigest()
|
|
|
|
|
|
def sidecar_stem(name: str) -> str:
|
|
lower = name.lower()
|
|
for suffix in (
|
|
".civitai.json",
|
|
".swarm.json",
|
|
".preview.png",
|
|
".preview.webp",
|
|
".preview.jpg",
|
|
".json",
|
|
".png",
|
|
".webp",
|
|
".jpg",
|
|
):
|
|
if lower.endswith(suffix):
|
|
return name[: -len(suffix)]
|
|
return Path(name).stem
|
|
|
|
|
|
def model_push_set(root: Path) -> list[Path]:
|
|
"""Weights plus same-stem sidecars. Sidecar without weights is skipped."""
|
|
files = iter_payload_files(root)
|
|
weights = [p for p in files if is_weight(p)]
|
|
wanted: set[Path] = set(weights)
|
|
stems = {p.stem for p in weights}
|
|
for path in files:
|
|
if is_weight(path):
|
|
continue
|
|
if sidecar_stem(path.name) in stems:
|
|
wanted.add(path)
|
|
return sorted(wanted)
|