Skip to content

Commit 2834c52

Browse files
committed
fix addskill input handling
Fixes #12
1 parent 9ae4d91 commit 2834c52

7 files changed

Lines changed: 118 additions & 16 deletions

File tree

docs/commands/owner.md

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -199,14 +199,15 @@ List all dynamically created commands.
199199
Add an AI skill — custom instructions that the AI follows.
200200

201201
```
202-
/addskill <name>
203-
<instructions>
202+
/addskill <name> <instructions>
203+
/addskill <url>
204+
/addskill # with attached .md file
205+
/addskill <name> # when replying to text
204206
```
205207

206208
**Example:**
207209
```
208-
/addskill translator
209-
Always translate messages to English when asked.
210+
/addskill translator Always translate messages to English when asked.
210211
```
211212

212213
## /delskill

docs/features/ai.md

Lines changed: 20 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -80,17 +80,31 @@ Skills are custom instructions that the AI follows. They let you customize the A
8080
### Adding a Skill
8181

8282
```
83-
/addskill <name>
84-
<instructions>
83+
/addskill <name> <instructions>
8584
```
8685

8786
**Example:**
8887

8988
```
90-
/addskill translator
91-
When someone asks you to translate, translate the text
92-
to the requested language. If no language is specified,
93-
translate to English.
89+
/addskill translator When someone asks you to translate, translate to requested language.
90+
```
91+
92+
Alternative methods:
93+
94+
```
95+
/addskill <url-to-markdown-skill>
96+
```
97+
98+
or attach a `.md` skill file and send:
99+
100+
```
101+
/addskill
102+
```
103+
104+
You can also reply to a text message and send:
105+
106+
```
107+
/addskill <name>
94108
```
95109

96110
### Managing Skills

src/commands/owner/addskill.py

Lines changed: 67 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,53 @@
22
Add skill command - Add AI skills from URL or file.
33
"""
44

5+
from __future__ import annotations
6+
7+
from typing import Any
8+
59
from core import symbols as sym
610
from core.command import Command, CommandContext
711

812

13+
def build_inline_skill(raw_args: str, quoted_text: str = "") -> dict[str, Any] | None:
14+
"""Build inline skill payload from command args/quoted text.
15+
16+
Supports:
17+
- addskill <name> <instructions>
18+
- addskill <name> (with quoted message text as instructions)
19+
"""
20+
raw = (raw_args or "").strip()
21+
if not raw:
22+
return None
23+
24+
parts = raw.split(maxsplit=1)
25+
if not parts:
26+
return None
27+
28+
name = parts[0].strip().lower()
29+
if not name:
30+
return None
31+
32+
content = parts[1].strip() if len(parts) > 1 else ""
33+
if not content:
34+
content = (quoted_text or "").strip()
35+
if not content:
36+
return None
37+
38+
return {
39+
"name": name,
40+
"description": f"Inline skill: {name}",
41+
"trigger": "always",
42+
"priority": 10,
43+
"content": content,
44+
}
45+
46+
947
class AddSkillCommand(Command):
1048
name = "addskill"
1149
aliases = ["skill"]
12-
description = "Add an AI skill from URL or attached file"
13-
usage = "addskill <url> or attach .md file"
50+
description = "Add an AI skill from inline text, URL, or attached file"
51+
usage = "addskill <name> <instructions> | addskill <url> | attach .md file"
1452
category = "owner"
1553
owner_only = True
1654

@@ -24,9 +62,9 @@ async def execute(self, ctx: CommandContext) -> None:
2462
)
2563

2664
if ctx.args:
27-
url = ctx.args[0]
28-
if url.startswith("http://") or url.startswith("https://"):
29-
skill = await load_skill_from_url(url)
65+
first_arg = ctx.args[0]
66+
if first_arg.startswith("http://") or first_arg.startswith("https://"):
67+
skill = await load_skill_from_url(first_arg)
3068
if skill:
3169
save_skill_to_file(skill)
3270
agentic_ai.add_skill(
@@ -83,10 +121,34 @@ async def execute(self, ctx: CommandContext) -> None:
83121
)
84122
return
85123

124+
quoted_text = ""
125+
if ctx.message.quoted_message and ctx.message.quoted_message.get("text"):
126+
quoted_text = str(ctx.message.quoted_message.get("text") or "")
127+
128+
inline_skill = build_inline_skill(ctx.raw_args, quoted_text)
129+
if inline_skill:
130+
save_skill_to_file(inline_skill)
131+
agentic_ai.add_skill(
132+
inline_skill["name"],
133+
inline_skill["content"],
134+
inline_skill["description"],
135+
inline_skill["trigger"],
136+
)
137+
await ctx.client.reply(
138+
ctx.message,
139+
f"{sym.SUCCESS} *Skill Added*\n\n"
140+
f"*Name:* `{inline_skill['name']}`\n"
141+
f"*Description:* {inline_skill['description']}\n"
142+
f"*Trigger:* {inline_skill['trigger']}",
143+
)
144+
return
145+
86146
await ctx.client.reply(
87147
ctx.message,
88148
f"{sym.INFO} *Add AI Skill*\n\n"
89149
f"Usage:\n"
150+
f"• `/addskill <name> <instructions>` - Inline skill\n"
151+
f"• Reply to text with `/addskill <name>`\n"
90152
f"• `/addskill <url>` - Load from URL\n"
91153
f"• Attach a `.md` file and send `/addskill`\n\n"
92154
f"*Skill Format:*\n"

src/core/constants.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@
4343
("extendedTextMessage", "text"),
4444
("imageMessage", "caption"),
4545
("videoMessage", "caption"),
46+
("documentMessage", "caption"),
4647
)
4748

4849
PHOTO_IMAGE_EXTENSIONS = frozenset(

tests/test_addskill.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
from commands.owner.addskill import build_inline_skill
2+
3+
4+
def test_build_inline_skill_from_inline_text():
5+
skill = build_inline_skill("translator Always translate to English")
6+
assert skill is not None
7+
assert skill["name"] == "translator"
8+
assert "Always translate" in skill["content"]
9+
assert skill["trigger"] == "always"
10+
11+
12+
def test_build_inline_skill_from_quoted_text():
13+
skill = build_inline_skill("translator", quoted_text="Translate to Indonesian")
14+
assert skill is not None
15+
assert skill["name"] == "translator"
16+
assert skill["content"] == "Translate to Indonesian"
17+
18+
19+
def test_build_inline_skill_requires_content():
20+
assert build_inline_skill("translator") is None

tests/test_message_constants.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
from core.constants import TEXT_SOURCES
2+
3+
4+
def test_document_caption_is_in_text_sources():
5+
assert ("documentMessage", "caption") in TEXT_SOURCES

tests/test_privacy_controls.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,6 @@ def test_clear_memory_for_uncached_chat(tmp_path, monkeypatch):
6060
mem.add(role="user", content="hello")
6161
assert len(mem.get_history()) == 1
6262

63-
# Simulate uncached chat memory object
6463
memory_module._memory_cache.pop(chat, None)
6564

6665
clear_memory(chat)

0 commit comments

Comments
 (0)