Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Empty file added skill_maker/README.md
Empty file.
Empty file added skill_maker/__init__.py
Empty file.
Empty file added skill_maker/config.py
Empty file.
Empty file added skill_maker/requirements.txt
Empty file.
44 changes: 44 additions & 0 deletions skills/skill_maker/skill.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import os
import json
from pathlib import Path

# Projenin ana skills dizinini bulur
SKILLS_ROOT = Path(__file__).parent.parent

def get_tools():
return [
{
"type": "function",
"function": {
"name": "create_skill",
"description": "Hermes için yeni bir yetenek (skill) klasörü ve dosyaları oluşturur. Kullanıcı yeni bir özellik istediğinde bunu kullan.",
"parameters": {
"type": "object",
"properties": {
"skill_name": {"type": "string", "description": "Klasör adı (örn: 'hesap_makinesi')"},
"description": {"type": "string", "description": "Yetenek ne işe yarar?"},
"trigger_phrases": {"type": "array", "items": {"type": "string"}, "description": "Tetikleyici cümleler"},
"logic_code": {"type": "string", "description": "logic.py dosyasının Python kodu. run(**kwargs) içermeli."}
},
"required": ["skill_name", "description", "trigger_phrases", "logic_code"]
}
}
}
]

def create_skill(skill_name: str, description: str, trigger_phrases: list, logic_code: str) -> dict:
skill_dir = SKILLS_ROOT / skill_name
try:
skill_dir.mkdir(parents=True, exist_ok=False)
triggers = "\n".join(f"- {p}" for p in trigger_phrases)
skill_md = f"# {skill_name}\n\n## Description\n{description}\n\n## Triggers\n{triggers}"
(skill_dir / "SKILL.md").write_text(skill_md, encoding="utf-8")
(skill_dir / "logic.py").write_text(logic_code, encoding="utf-8")
return {"success": True, "path": str(skill_dir)}
except Exception as e:
return {"success": False, "error": str(e)}

def dispatch(tool_name: str, tool_input: dict) -> str:
if tool_name == "create_skill":
return json.dumps(create_skill(**tool_input))
return json.dumps({"error": "Unknown tool"})
73 changes: 73 additions & 0 deletions skills/skill_registry/logic.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import os
from pathlib import Path


SKILLS_ROOT = Path(os.path.expanduser("~/.hermes/skills"))


def list_skills(skills_root: str = None) -> list[str]:
"""
Scan the Hermes skills directory and return a list of all installed skills
with their name and description extracted from each SKILL.md frontmatter.

Args:
skills_root: Optional override path to the skills directory.
Defaults to ~/.hermes/skills.

Returns:
A list of formatted strings: "[skill-name] — Description"
"""
root = Path(skills_root) if skills_root else SKILLS_ROOT

if not root.exists():
return [f"Skills directory not found: {root}"]

skill_entries = []

# Walk all subdirectories looking for SKILL.md files
for skill_md in sorted(root.rglob("SKILL.md")):
skill_dir = skill_md.parent
name = skill_dir.name
description = _extract_description(skill_md)
skill_entries.append(f"[{name}] — {description}")

if not skill_entries:
return ["No skills found."]

return skill_entries


def _extract_description(skill_md: Path) -> str:
"""
Parse the YAML frontmatter of a SKILL.md file and return the description field.
Falls back to 'No description.' if not found.
"""
try:
content = skill_md.read_text(encoding="utf-8")
lines = content.splitlines()

# Frontmatter is between the first two '---' lines
if lines[0].strip() != "---":
return "No description."

in_front = False
for line in lines:
stripped = line.strip()
if stripped == "---":
in_front = not in_front
continue
if in_front and stripped.startswith("description:"):
return stripped.split("description:", 1)[1].strip().strip('"').strip("'")

except Exception:
pass

return "No description."


if __name__ == "__main__":
print(f"Scanning skills in: {SKILLS_ROOT}\n")
results = list_skills()
print(f"Found {len(results)} skill(s):\n")
for entry in results:
print(" ", entry)
46 changes: 46 additions & 0 deletions skills/skill_validator/logic.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import ast
from pathlib import Path


def validate_skill(skill_path: str) -> list[str]:
"""
Scan all .py files inside a skill folder and check each one
for Python syntax errors using ast.parse().

Args:
skill_path: Path to the skill directory (str or Path-like).

Returns:
A list of result strings, one per .py file found.
"""
folder = Path(skill_path)
py_files = list(folder.rglob("*.py"))

if not py_files:
return ["No Python files found."]

results = []
for py_file in py_files:
try:
source = py_file.read_text(encoding="utf-8")
ast.parse(source, filename=str(py_file))
results.append(f"OK: {py_file.name}")
except SyntaxError as e:
results.append(f"ERROR: {py_file.name} — {e}")
except Exception as e:
results.append(f"ERROR: {py_file.name} — unexpected error: {e}")

return results


if __name__ == "__main__":
import sys

if len(sys.argv) < 2:
print("Usage: python logic.py <path-to-skill-folder>")
sys.exit(1)

target = sys.argv[1]
print(f"Validating Python files in: {target}\n")
for line in validate_skill(target):
print(line)
1 change: 1 addition & 0 deletions skills_output