7913116a8e
- .claude/agents/fn-analizador/SKILL.md - .claude/agents/fn-constructor/SKILL.md - .claude/agents/fn-executor/SKILL.md - .claude/agents/fn-mejorador/SKILL.md - .claude/agents/fn-orquestador/SKILL.md - .claude/agents/fn-recopilador/SKILL.md - .claude/commands/app.md - .claude/commands/compile.md - .claude/commands/cpp-app.md - .claude/commands/create_functions.md - ... Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
144 lines
4.6 KiB
Python
144 lines
4.6 KiB
Python
"""export_hub_manifest — genera el TSV sidecar para app_hub_launcher."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import os
|
|
import sqlite3
|
|
import sys
|
|
from pathlib import Path
|
|
from typing import Any
|
|
|
|
|
|
def _read_frontmatter(md_path: Path) -> dict[str, Any]:
|
|
"""Parse YAML frontmatter from a .md file. Returns {} on any error."""
|
|
try:
|
|
import yaml # PyYAML — available in python/.venv
|
|
|
|
text = md_path.read_text(encoding="utf-8")
|
|
if not text.startswith("---"):
|
|
return {}
|
|
# Find the closing ---
|
|
end = text.find("\n---", 3)
|
|
if end == -1:
|
|
return {}
|
|
yaml_block = text[3:end].strip()
|
|
data = yaml.safe_load(yaml_block)
|
|
return data if isinstance(data, dict) else {}
|
|
except Exception as exc:
|
|
print(f"[export_hub_manifest] WARN: could not parse {md_path}: {exc}", file=sys.stderr)
|
|
return {}
|
|
|
|
|
|
def _snake_to_display(name: str) -> str:
|
|
"""Convert snake_case name to Title Case With Spaces.
|
|
|
|
Examples:
|
|
graph_explorer -> Graph Explorer
|
|
dag_engine_ui -> Dag Engine Ui
|
|
app_hub_launcher -> App Hub Launcher
|
|
"""
|
|
return " ".join(part.capitalize() for part in name.split("_"))
|
|
|
|
|
|
def export_hub_manifest(out_path: str, *, registry_root: str | None = None) -> dict:
|
|
"""Generate TSV sidecar manifest for app_hub_launcher.
|
|
|
|
Queries registry.db for all cpp/imgui apps, reads their app.md
|
|
frontmatter to extract name, description and accent color, then
|
|
writes a UTF-8 TSV to out_path.
|
|
|
|
Args:
|
|
out_path: Destination path for the TSV manifest file.
|
|
registry_root: Path to the fn_registry root directory.
|
|
Defaults to FN_REGISTRY_ROOT env var or repo root derived from file location.
|
|
|
|
Returns:
|
|
{"ok": True, "count": N, "out_path": "<abs_path>"}
|
|
"""
|
|
root = Path(
|
|
registry_root
|
|
or os.environ.get("FN_REGISTRY_ROOT")
|
|
or Path(__file__).resolve().parents[3]
|
|
).resolve()
|
|
|
|
db_path = root / "registry.db"
|
|
if not db_path.exists():
|
|
raise FileNotFoundError(f"registry.db not found at {db_path}")
|
|
|
|
con = sqlite3.connect(str(db_path))
|
|
con.row_factory = sqlite3.Row
|
|
try:
|
|
rows = con.execute(
|
|
"SELECT id, name, dir_path FROM apps WHERE lang='cpp' AND framework='imgui' ORDER BY name"
|
|
).fetchall()
|
|
finally:
|
|
con.close()
|
|
|
|
DEFAULT_ACCENT = "#64748b"
|
|
TSV_HEADER = "name\tdisplay_name\tdescription\taccent_hex\n"
|
|
|
|
lines: list[str] = [TSV_HEADER]
|
|
count = 0
|
|
|
|
for row in rows:
|
|
app_name: str = row["name"]
|
|
dir_path: str = row["dir_path"]
|
|
|
|
# Derive defaults in case app.md is missing / malformed
|
|
display_name = _snake_to_display(app_name)
|
|
description = ""
|
|
accent_hex = DEFAULT_ACCENT
|
|
|
|
md_path = root / dir_path / "app.md"
|
|
if md_path.exists():
|
|
fm = _read_frontmatter(md_path)
|
|
if fm:
|
|
description = fm.get("description", "") or ""
|
|
icon_block = fm.get("icon")
|
|
if isinstance(icon_block, dict):
|
|
accent_hex = icon_block.get("accent", DEFAULT_ACCENT) or DEFAULT_ACCENT
|
|
else:
|
|
print(
|
|
f"[export_hub_manifest] WARN: empty/malformed frontmatter in {md_path}",
|
|
file=sys.stderr,
|
|
)
|
|
else:
|
|
print(
|
|
f"[export_hub_manifest] WARN: app.md missing for {app_name} at {md_path}",
|
|
file=sys.stderr,
|
|
)
|
|
|
|
# Sanitize: TSV values must not contain tabs or newlines
|
|
def clean(s: str) -> str:
|
|
return s.replace("\t", " ").replace("\n", " ").replace("\r", "")
|
|
|
|
lines.append(
|
|
f"{clean(app_name)}\t{clean(display_name)}\t{clean(description)}\t{clean(accent_hex)}\n"
|
|
)
|
|
count += 1
|
|
|
|
out = Path(out_path).resolve()
|
|
out.parent.mkdir(parents=True, exist_ok=True)
|
|
out.write_text("".join(lines), encoding="utf-8")
|
|
|
|
return {"ok": True, "count": count, "out_path": str(out)}
|
|
|
|
|
|
if __name__ == "__main__":
|
|
import argparse
|
|
import json
|
|
|
|
parser = argparse.ArgumentParser(
|
|
description="Export hub manifest TSV for app_hub_launcher."
|
|
)
|
|
parser.add_argument("out_path", help="Destination .tsv file path")
|
|
parser.add_argument(
|
|
"--registry-root",
|
|
default=None,
|
|
help="Path to fn_registry root (default: FN_REGISTRY_ROOT env or repo root derived from file location)",
|
|
)
|
|
args = parser.parse_args()
|
|
|
|
result = export_hub_manifest(args.out_path, registry_root=args.registry_root)
|
|
print(json.dumps(result, indent=2))
|