feat: add skill rename command and natural language rename detection

- Added 'skill rename <old> <new>' subcommand
- Intercept 'rename X to Y' natural language patterns locally
- Updates name field inside SKILL.md after rename
- Reloads skills immediately after rename
This commit is contained in:
2026-02-18 23:51:00 -05:00
parent e22306e9b8
commit b24c501860

57
main.py
View File

@@ -262,6 +262,18 @@ def ai_handler(msg: IncomingMessage) -> str:
if name:
return _create_skill_from_description(name, description)
# Detect natural language skill rename requests
_SKILL_RENAME_RE = re.compile(
r"rename\s+(?:the\s+)?(?:this\s+)?(?:skill\s+)?(.+?)\s+to\s+(.+)",
re.IGNORECASE,
)
rename_match = _SKILL_RENAME_RE.match(text_lower)
if rename_match:
old_name = rename_match.group(1).strip()
new_name = rename_match.group(2).strip().lower().replace(" ", "-")
if old_name and new_name:
return _handle_skill_command(f"skill rename {old_name} {new_name}")
# Build context from memory + skills
context = _build_context(msg)
@@ -1112,6 +1124,50 @@ def _handle_skill_command(text: str) -> str:
_skills.reload()
return f"✅ Skill `{name}` removed."
elif subcommand == "rename" and len(parts) >= 3:
# Parse: skill rename <old> <new> OR skill rename <old> to <new>
rename_args = parts[2].strip()
rename_parts = re.split(r"\s+to\s+|\s+", rename_args, maxsplit=1)
if len(rename_parts) < 2:
return "Usage: `skill rename <old_name> <new_name>`"
old_name = rename_parts[0].strip()
new_name = rename_parts[1].strip().lower().replace(" ", "-")
cfg = load_config()
workspace = os.path.expanduser(cfg.memory.workspace)
old_dir = os.path.join(workspace, "skills", old_name)
new_dir = os.path.join(workspace, "skills", new_name)
if not os.path.isdir(old_dir):
return f"⚠️ Skill `{old_name}` not found. Run `skill list` to see available skills."
if os.path.isdir(new_dir):
return f"⚠️ Skill `{new_name}` already exists."
import shutil
shutil.move(old_dir, new_dir)
# Update the name field inside SKILL.md
skill_path = os.path.join(new_dir, "SKILL.md")
if os.path.isfile(skill_path):
try:
with open(skill_path, "r", encoding="utf-8") as f:
content = f.read()
content = re.sub(
r"^(name:\s*).*$",
rf"\g<1>{new_name}",
content,
count=1,
flags=re.MULTILINE,
)
with open(skill_path, "w", encoding="utf-8") as f:
f.write(content)
except Exception as e:
logger.warning(f"Failed to update name in SKILL.md: {e}")
if _skills:
_skills.reload()
return f"✅ Skill renamed: `{old_name}` → `{new_name}`"
elif subcommand == "reload":
if _skills:
loaded = _skills.reload()
@@ -1124,6 +1180,7 @@ def _handle_skill_command(text: str) -> str:
"• `skill list` — List loaded skills\n"
"• `skill show <name>` — Show skill content\n"
"• `skill create <name>` — Create a new skill template\n"
"• `skill rename <old> <new>` — Rename a skill\n"
"• `skill remove <name>` — Remove a skill\n"
"• `skill reload` — Reload all skills\n"
"\nYou can also create skills naturally:\n"