b0aa0438af
Captures the known-good state after theme consolidation and consistency reconciliation: - NexusOS GTK/xfwm4/icon theme assets (assets/themes, management/Mint-Y-Nexus) - Tightened right-click menus, visible separators, no menu icons - assets/themes/install-theme.sh: idempotent restore of all wiring (symlinks, xfconf xsettings+xfwm4, GTK 3/4 settings.ini) - .gitignore excludes venv/ollama/models/runtime/db Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
108 lines
3.2 KiB
Python
108 lines
3.2 KiB
Python
from pathlib import Path
|
|
from typing import List, Optional
|
|
|
|
import yaml
|
|
from pydantic import BaseModel
|
|
|
|
|
|
class _BlockDumper(yaml.Dumper):
|
|
pass
|
|
|
|
_BlockDumper.add_representer(
|
|
str,
|
|
lambda dumper, data: dumper.represent_scalar(
|
|
"tag:yaml.org,2002:str", data, style="|" if "\n" in data else None
|
|
),
|
|
)
|
|
|
|
|
|
class PlaybookItem(BaseModel):
|
|
id: str
|
|
title: str
|
|
goal: str
|
|
instructions: str
|
|
tags: List[str] = []
|
|
order: int = 0
|
|
|
|
|
|
class PlaybookFileStore:
|
|
def __init__(self, directory: Path):
|
|
self.directory = directory
|
|
directory.mkdir(parents=True, exist_ok=True)
|
|
|
|
def _path(self, id: str) -> Path:
|
|
return self.directory / f"{id}.yaml"
|
|
|
|
def _read(self, path: Path) -> Optional[PlaybookItem]:
|
|
try:
|
|
with open(path, encoding="utf-8") as f:
|
|
data = yaml.safe_load(f)
|
|
return PlaybookItem(
|
|
id=data["id"],
|
|
title=data["title"],
|
|
goal=data.get("goal", ""),
|
|
instructions=data.get("instructions", ""),
|
|
tags=data.get("tags", []),
|
|
order=data.get("order", 0),
|
|
)
|
|
except Exception:
|
|
return None
|
|
|
|
@staticmethod
|
|
def _clean(s: str) -> str:
|
|
return "\n".join(line.rstrip() for line in s.split("\n")).strip()
|
|
|
|
def _write(self, item: PlaybookItem):
|
|
data = {
|
|
"id": item.id,
|
|
"title": item.title,
|
|
"goal": item.goal,
|
|
"tags": item.tags,
|
|
"order": item.order,
|
|
"instructions": self._clean(item.instructions),
|
|
}
|
|
with open(self._path(item.id), "w", encoding="utf-8") as f:
|
|
yaml.dump(data, f, Dumper=_BlockDumper, allow_unicode=True,
|
|
default_flow_style=False, sort_keys=False, width=4096)
|
|
|
|
def all_playbooks(self) -> List[PlaybookItem]:
|
|
items = [self._read(p) for p in self.directory.glob("*.yaml")]
|
|
return sorted((i for i in items if i), key=lambda x: x.order)
|
|
|
|
def get_playbook(self, id: str) -> Optional[PlaybookItem]:
|
|
return self._read(self._path(id))
|
|
|
|
def add_playbook(self, item: PlaybookItem):
|
|
self._write(item)
|
|
|
|
def delete_playbook(self, id: str):
|
|
p = self._path(id)
|
|
if p.exists():
|
|
p.unlink()
|
|
|
|
def reorder_playbooks(self, ordered_ids: List[str]):
|
|
all_ids = {p.stem for p in self.directory.glob("*.yaml")}
|
|
valid = [pid for pid in ordered_ids if pid in all_ids]
|
|
missing = sorted(all_ids - set(valid))
|
|
for index, pid in enumerate(valid + missing):
|
|
item = self.get_playbook(pid)
|
|
if item:
|
|
item.order = index
|
|
self._write(item)
|
|
|
|
def search_playbooks(self, query: str) -> List[PlaybookItem]:
|
|
if not query or not query.strip():
|
|
return []
|
|
q = query.strip().lower()
|
|
return [
|
|
p for p in self.all_playbooks()
|
|
if q in p.title.lower()
|
|
or q in p.goal.lower()
|
|
or q in p.instructions.lower()
|
|
or any(q in t.lower() for t in p.tags)
|
|
]
|
|
|
|
|
|
from ..nexus_config import PLAYBOOK_DIR
|
|
playbook_store = PlaybookFileStore(PLAYBOOK_DIR)
|