Add ekos-gazete-search FullSweep + telegram + browser-use skills

Skills:
- ekos-gazete-search: EKOS gazete arşivi (1928-1942) tarama skill'i.
  + 04_export.py (CSV+DOCX), run_capped.sh (systemd cap wrapper),
    02_search_pdfs.py interleaved-dispatch patch (crash-safe), kirim_core.yaml.
- telegram: TG inbox/search/send/read scripts.
- browser-use: paperclip browser automation skill.

build.py:
- Add ekos-gazete-search → scribe, scholar, oracle, frodo, chronos,
  centurion, wraith mapping.
- Add telegram, browser-use mappings (browser-use uses "*" wildcard).
- Add wildcard "*" support in DEFAULT_SKILL_PERSONA_MAP.
- Add paperclip_skills + community_skills buckets to skill injection.
- Wrap yaml.safe_load in try/except for malformed frontmatter.
- Index paperclip_skills with inferred persona mapping.

README.md:
- Add telegram skill to Sentinel/Frodo/Oracle/Echo skill lists.
This commit is contained in:
salvacybersec
2026-04-30 20:45:31 +03:00
parent 3126dadd19
commit 00dc88bf5f
28 changed files with 3316 additions and 13 deletions

View File

@@ -258,7 +258,12 @@ def build_persona(
# Inject mapped skills for this persona
if skills_index:
mapped_skills = []
for bucket in ("skills", "feynman_skills"):
for bucket in (
"skills",
"paperclip_skills",
"community_skills",
"feynman_skills",
):
for skill_name, skill_info in skills_index.get(bucket, {}).items():
if not isinstance(skill_info, dict):
continue
@@ -306,6 +311,8 @@ def build_persona(
DEFAULT_SKILL_PERSONA_MAP = {
# Browser automation for every persona
"browser-use": ["*"],
# Cybersecurity skills → personas
"pentest": ["neo"],
"nmap-recon": ["neo", "vortex"],
@@ -336,6 +343,7 @@ DEFAULT_SKILL_PERSONA_MAP = {
"news-crawler": ["frodo", "herald"],
"dellight-intelligence-ops": ["frodo", "echo"],
"dellight-strategic-intelligence": ["frodo"],
"telegram": ["frodo", "oracle", "sentinel", "echo"],
"agent-intelligence-network-scan": ["oracle"],
"social-trust-manipulation-detector": ["ghost"],
# Infrastructure skills → personas
@@ -349,6 +357,8 @@ DEFAULT_SKILL_PERSONA_MAP = {
# Web scraping → personas
"deep-scraper": ["oracle"],
"crawl-for-ai": ["oracle", "herald"],
# Historical / archival research → personas
"ekos-gazete-search": ["scribe", "scholar", "oracle", "frodo", "chronos", "centurion", "wraith"],
}
@@ -391,7 +401,10 @@ def parse_skill_frontmatter(skill_md: Path) -> dict:
fm_match = re.match(r"^---\n(.*?)\n---\n", content, re.DOTALL)
if not fm_match:
return {}
parsed = yaml.safe_load(fm_match.group(1))
try:
parsed = yaml.safe_load(fm_match.group(1))
except yaml.YAMLError:
return {}
return parsed if isinstance(parsed, dict) else {}
@@ -514,13 +527,18 @@ def infer_personas_from_skill_metadata(skill_name: str, metadata: dict) -> list:
def load_skill_persona_map(config: dict) -> dict:
"""Load skill→persona mapping from config.yaml or use defaults."""
custom = config.get("skill_persona_map", {})
merged = {
k: [p for p in v if p in VALID_PERSONAS]
for k, v in DEFAULT_SKILL_PERSONA_MAP.items()
}
merged = {}
for skill, personas in DEFAULT_SKILL_PERSONA_MAP.items():
if "*" in personas:
merged[skill] = sorted(VALID_PERSONAS)
else:
merged[skill] = [p for p in personas if p in VALID_PERSONAS]
for skill, personas in custom.items():
if isinstance(personas, list):
merged[skill] = [p for p in personas if p in VALID_PERSONAS]
if "*" in personas:
merged[skill] = sorted(VALID_PERSONAS)
else:
merged[skill] = [p for p in personas if p in VALID_PERSONAS]
return merged
@@ -718,7 +736,35 @@ def build_skills_index(shared_dir: Path, config: dict = None) -> dict:
continue
skill_md = skill_dir / "SKILL.md"
if skill_md.exists():
index["paperclip_skills"][skill_dir.name] = True
skill_meta = parse_skill_frontmatter(skill_md)
inferred_personas = infer_personas_from_skill_metadata(
skill_dir.name, skill_meta
)
configured_personas = skill_map.get(skill_dir.name, [])
merged_personas = sorted(
set(configured_personas).union(inferred_personas)
)
content = skill_md.read_text(encoding="utf-8")
first_line = ""
for line in content.split("\n"):
line = line.strip()
if line and not line.startswith(
("---", "#", "name:", "description:")
):
first_line = line[:120]
break
index["paperclip_skills"][skill_dir.name] = {
"personas": merged_personas,
"summary": first_line,
"domain": str(skill_meta.get("domain", "")),
"subdomain": str(skill_meta.get("subdomain", "")),
"tags": skill_meta.get("tags", []),
"mapped_by": {
"explicit": configured_personas,
"inferred": inferred_personas,
},
"has_references": (skill_dir / "references").is_dir(),
}
# Index community-skills
cskills_dir = shared_dir / "community-skills"
@@ -728,7 +774,35 @@ def build_skills_index(shared_dir: Path, config: dict = None) -> dict:
continue
skill_md = skill_dir / "SKILL.md"
if skill_md.exists():
index["community_skills"][skill_dir.name] = True
skill_meta = parse_skill_frontmatter(skill_md)
inferred_personas = infer_personas_from_skill_metadata(
skill_dir.name, skill_meta
)
configured_personas = skill_map.get(skill_dir.name, [])
merged_personas = sorted(
set(configured_personas).union(inferred_personas)
)
content = skill_md.read_text(encoding="utf-8")
first_line = ""
for line in content.split("\n"):
line = line.strip()
if line and not line.startswith(
("---", "#", "name:", "description:")
):
first_line = line[:120]
break
index["community_skills"][skill_dir.name] = {
"personas": merged_personas,
"summary": first_line,
"domain": str(skill_meta.get("domain", "")),
"subdomain": str(skill_meta.get("subdomain", "")),
"tags": skill_meta.get("tags", []),
"mapped_by": {
"explicit": configured_personas,
"inferred": inferred_personas,
},
"has_references": (skill_dir / "references").is_dir(),
}
# Index feynman-skills (research workflows adapted from Feynman).
# Use the same persona-aware indexing as shared skills so mapped skills