emdash-cli 0.1.35__py3-none-any.whl → 0.1.67__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- emdash_cli/client.py +41 -22
- emdash_cli/clipboard.py +30 -61
- emdash_cli/commands/__init__.py +2 -2
- emdash_cli/commands/agent/__init__.py +14 -0
- emdash_cli/commands/agent/cli.py +100 -0
- emdash_cli/commands/agent/constants.py +63 -0
- emdash_cli/commands/agent/file_utils.py +178 -0
- emdash_cli/commands/agent/handlers/__init__.py +51 -0
- emdash_cli/commands/agent/handlers/agents.py +449 -0
- emdash_cli/commands/agent/handlers/auth.py +69 -0
- emdash_cli/commands/agent/handlers/doctor.py +319 -0
- emdash_cli/commands/agent/handlers/hooks.py +121 -0
- emdash_cli/commands/agent/handlers/index.py +183 -0
- emdash_cli/commands/agent/handlers/mcp.py +183 -0
- emdash_cli/commands/agent/handlers/misc.py +319 -0
- emdash_cli/commands/agent/handlers/registry.py +72 -0
- emdash_cli/commands/agent/handlers/rules.py +411 -0
- emdash_cli/commands/agent/handlers/sessions.py +168 -0
- emdash_cli/commands/agent/handlers/setup.py +715 -0
- emdash_cli/commands/agent/handlers/skills.py +478 -0
- emdash_cli/commands/agent/handlers/telegram.py +475 -0
- emdash_cli/commands/agent/handlers/todos.py +119 -0
- emdash_cli/commands/agent/handlers/verify.py +653 -0
- emdash_cli/commands/agent/help.py +236 -0
- emdash_cli/commands/agent/interactive.py +842 -0
- emdash_cli/commands/agent/menus.py +760 -0
- emdash_cli/commands/agent/onboarding.py +619 -0
- emdash_cli/commands/agent/session_restore.py +210 -0
- emdash_cli/commands/agent.py +7 -1321
- emdash_cli/commands/index.py +111 -13
- emdash_cli/commands/registry.py +635 -0
- emdash_cli/commands/server.py +99 -40
- emdash_cli/commands/skills.py +72 -6
- emdash_cli/design.py +328 -0
- emdash_cli/diff_renderer.py +438 -0
- emdash_cli/integrations/__init__.py +1 -0
- emdash_cli/integrations/telegram/__init__.py +15 -0
- emdash_cli/integrations/telegram/bot.py +402 -0
- emdash_cli/integrations/telegram/bridge.py +865 -0
- emdash_cli/integrations/telegram/config.py +155 -0
- emdash_cli/integrations/telegram/formatter.py +385 -0
- emdash_cli/main.py +52 -2
- emdash_cli/server_manager.py +70 -10
- emdash_cli/sse_renderer.py +659 -167
- {emdash_cli-0.1.35.dist-info → emdash_cli-0.1.67.dist-info}/METADATA +2 -4
- emdash_cli-0.1.67.dist-info/RECORD +63 -0
- emdash_cli/commands/swarm.py +0 -86
- emdash_cli-0.1.35.dist-info/RECORD +0 -30
- {emdash_cli-0.1.35.dist-info → emdash_cli-0.1.67.dist-info}/WHEEL +0 -0
- {emdash_cli-0.1.35.dist-info → emdash_cli-0.1.67.dist-info}/entry_points.txt +0 -0
|
@@ -0,0 +1,478 @@
|
|
|
1
|
+
"""Handler for /skills command."""
|
|
2
|
+
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
|
|
5
|
+
from rich.console import Console
|
|
6
|
+
from rich.panel import Panel
|
|
7
|
+
|
|
8
|
+
console = Console()
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def _get_skills_dir() -> Path:
|
|
12
|
+
"""Get the skills directory path."""
|
|
13
|
+
return Path.cwd() / ".emdash" / "skills"
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def _get_builtin_skills_dir() -> Path:
|
|
17
|
+
"""Get the built-in skills directory."""
|
|
18
|
+
try:
|
|
19
|
+
from emdash_core.agent.skills import _get_builtin_skills_dir as get_builtin
|
|
20
|
+
return get_builtin()
|
|
21
|
+
except ImportError:
|
|
22
|
+
return Path()
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def list_skills() -> list[dict]:
|
|
26
|
+
"""List all skills (both user and built-in).
|
|
27
|
+
|
|
28
|
+
Returns:
|
|
29
|
+
List of dicts with name, description, user_invocable, is_builtin, file_path, scripts
|
|
30
|
+
"""
|
|
31
|
+
from emdash_core.agent.skills import SkillRegistry
|
|
32
|
+
|
|
33
|
+
skills_dir = _get_skills_dir()
|
|
34
|
+
registry = SkillRegistry.get_instance()
|
|
35
|
+
registry.load_skills(skills_dir)
|
|
36
|
+
|
|
37
|
+
all_skills = registry.get_all_skills()
|
|
38
|
+
skills = []
|
|
39
|
+
|
|
40
|
+
for skill in all_skills.values():
|
|
41
|
+
skills.append({
|
|
42
|
+
"name": skill.name,
|
|
43
|
+
"description": skill.description,
|
|
44
|
+
"user_invocable": skill.user_invocable,
|
|
45
|
+
"is_builtin": getattr(skill, "_builtin", False),
|
|
46
|
+
"file_path": str(skill.file_path) if skill.file_path else None,
|
|
47
|
+
"scripts": [str(s) for s in skill.scripts] if skill.scripts else [],
|
|
48
|
+
})
|
|
49
|
+
|
|
50
|
+
return skills
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def show_skills_interactive_menu() -> tuple[str, str]:
|
|
54
|
+
"""Show interactive skills menu.
|
|
55
|
+
|
|
56
|
+
Returns:
|
|
57
|
+
Tuple of (action, skill_name) where action is one of:
|
|
58
|
+
- 'view': View skill details
|
|
59
|
+
- 'create': Create new skill
|
|
60
|
+
- 'delete': Delete skill
|
|
61
|
+
- 'cancel': User cancelled
|
|
62
|
+
"""
|
|
63
|
+
from prompt_toolkit import Application
|
|
64
|
+
from prompt_toolkit.key_binding import KeyBindings
|
|
65
|
+
from prompt_toolkit.layout import Layout, HSplit, Window, FormattedTextControl
|
|
66
|
+
from prompt_toolkit.styles import Style
|
|
67
|
+
|
|
68
|
+
skills = list_skills()
|
|
69
|
+
|
|
70
|
+
# Build menu items: (name, description, is_builtin, is_action)
|
|
71
|
+
menu_items = []
|
|
72
|
+
|
|
73
|
+
for skill in skills:
|
|
74
|
+
builtin_marker = " [built-in]" if skill["is_builtin"] else ""
|
|
75
|
+
scripts_marker = f" [{len(skill['scripts'])} scripts]" if skill.get("scripts") else ""
|
|
76
|
+
menu_items.append((skill["name"], skill["description"] + builtin_marker + scripts_marker, skill["is_builtin"], False))
|
|
77
|
+
|
|
78
|
+
# Add action items at the bottom
|
|
79
|
+
menu_items.append(("+ Create New Skill", "Create a new skill with AI assistance", False, True))
|
|
80
|
+
|
|
81
|
+
if not menu_items:
|
|
82
|
+
menu_items.append(("+ Create New Skill", "Create a new skill with AI assistance", False, True))
|
|
83
|
+
|
|
84
|
+
selected_index = [0]
|
|
85
|
+
result = [("cancel", "")]
|
|
86
|
+
|
|
87
|
+
kb = KeyBindings()
|
|
88
|
+
|
|
89
|
+
@kb.add("up")
|
|
90
|
+
@kb.add("k")
|
|
91
|
+
def move_up(event):
|
|
92
|
+
selected_index[0] = (selected_index[0] - 1) % len(menu_items)
|
|
93
|
+
|
|
94
|
+
@kb.add("down")
|
|
95
|
+
@kb.add("j")
|
|
96
|
+
def move_down(event):
|
|
97
|
+
selected_index[0] = (selected_index[0] + 1) % len(menu_items)
|
|
98
|
+
|
|
99
|
+
@kb.add("enter")
|
|
100
|
+
def select(event):
|
|
101
|
+
item = menu_items[selected_index[0]]
|
|
102
|
+
name, desc, is_builtin, is_action = item
|
|
103
|
+
if is_action:
|
|
104
|
+
if "Create" in name:
|
|
105
|
+
result[0] = ("create", "")
|
|
106
|
+
else:
|
|
107
|
+
result[0] = ("view", name)
|
|
108
|
+
event.app.exit()
|
|
109
|
+
|
|
110
|
+
@kb.add("d")
|
|
111
|
+
def delete_skill(event):
|
|
112
|
+
item = menu_items[selected_index[0]]
|
|
113
|
+
name, desc, is_builtin, is_action = item
|
|
114
|
+
if not is_action and not is_builtin:
|
|
115
|
+
result[0] = ("delete", name)
|
|
116
|
+
event.app.exit()
|
|
117
|
+
elif is_builtin:
|
|
118
|
+
# Can't delete built-in skills
|
|
119
|
+
pass
|
|
120
|
+
|
|
121
|
+
@kb.add("n")
|
|
122
|
+
def new_skill(event):
|
|
123
|
+
result[0] = ("create", "")
|
|
124
|
+
event.app.exit()
|
|
125
|
+
|
|
126
|
+
@kb.add("c-c")
|
|
127
|
+
@kb.add("escape")
|
|
128
|
+
@kb.add("q")
|
|
129
|
+
def cancel(event):
|
|
130
|
+
result[0] = ("cancel", "")
|
|
131
|
+
event.app.exit()
|
|
132
|
+
|
|
133
|
+
def get_formatted_menu():
|
|
134
|
+
lines = [("class:title", "Skills\n\n")]
|
|
135
|
+
|
|
136
|
+
if not skills:
|
|
137
|
+
lines.append(("class:dim", "No skills defined yet.\n\n"))
|
|
138
|
+
|
|
139
|
+
for i, (name, desc, is_builtin, is_action) in enumerate(menu_items):
|
|
140
|
+
is_selected = i == selected_index[0]
|
|
141
|
+
prefix = ">" if is_selected else " "
|
|
142
|
+
|
|
143
|
+
if is_action:
|
|
144
|
+
if is_selected:
|
|
145
|
+
lines.append(("class:action-selected", f"{prefix}{name}\n"))
|
|
146
|
+
else:
|
|
147
|
+
lines.append(("class:action", f"{prefix}{name}\n"))
|
|
148
|
+
else:
|
|
149
|
+
if is_selected:
|
|
150
|
+
lines.append(("class:skill-selected", f"{prefix}{name}"))
|
|
151
|
+
lines.append(("class:desc-selected", f" - {desc}\n"))
|
|
152
|
+
else:
|
|
153
|
+
lines.append(("class:skill", f"{prefix}{name}"))
|
|
154
|
+
lines.append(("class:desc", f" - {desc}\n"))
|
|
155
|
+
|
|
156
|
+
lines.append(("class:hint", "\n[up]/[down] navigate | Enter view | n new | d delete | q quit"))
|
|
157
|
+
return lines
|
|
158
|
+
|
|
159
|
+
style = Style.from_dict({
|
|
160
|
+
"title": "#00ccff bold",
|
|
161
|
+
"dim": "#666666",
|
|
162
|
+
"skill": "#00ccff",
|
|
163
|
+
"skill-selected": "#00cc66 bold",
|
|
164
|
+
"action": "#ffcc00",
|
|
165
|
+
"action-selected": "#ffcc00 bold",
|
|
166
|
+
"desc": "#666666",
|
|
167
|
+
"desc-selected": "#00cc66",
|
|
168
|
+
"hint": "#888888 italic",
|
|
169
|
+
})
|
|
170
|
+
|
|
171
|
+
height = len(menu_items) + 5 # items + title + hint + padding
|
|
172
|
+
|
|
173
|
+
layout = Layout(
|
|
174
|
+
HSplit([
|
|
175
|
+
Window(
|
|
176
|
+
FormattedTextControl(get_formatted_menu),
|
|
177
|
+
height=height,
|
|
178
|
+
),
|
|
179
|
+
])
|
|
180
|
+
)
|
|
181
|
+
|
|
182
|
+
app = Application(
|
|
183
|
+
layout=layout,
|
|
184
|
+
key_bindings=kb,
|
|
185
|
+
style=style,
|
|
186
|
+
full_screen=False,
|
|
187
|
+
)
|
|
188
|
+
|
|
189
|
+
console.print()
|
|
190
|
+
|
|
191
|
+
try:
|
|
192
|
+
app.run()
|
|
193
|
+
except (KeyboardInterrupt, EOFError):
|
|
194
|
+
result[0] = ("cancel", "")
|
|
195
|
+
|
|
196
|
+
# Clear menu visually with separator
|
|
197
|
+
console.print()
|
|
198
|
+
|
|
199
|
+
return result[0]
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
def show_skill_details(name: str) -> None:
|
|
203
|
+
"""Show detailed view of a skill."""
|
|
204
|
+
from emdash_core.agent.skills import SkillRegistry
|
|
205
|
+
|
|
206
|
+
skills_dir = _get_skills_dir()
|
|
207
|
+
registry = SkillRegistry.get_instance()
|
|
208
|
+
registry.load_skills(skills_dir)
|
|
209
|
+
|
|
210
|
+
skill = registry.get_skill(name)
|
|
211
|
+
|
|
212
|
+
console.print()
|
|
213
|
+
console.print("[dim]" + "-" * 50 + "[/dim]")
|
|
214
|
+
console.print()
|
|
215
|
+
|
|
216
|
+
if skill:
|
|
217
|
+
builtin_marker = " [built-in]" if getattr(skill, "_builtin", False) else ""
|
|
218
|
+
invocable = f"Yes (/{skill.name})" if skill.user_invocable else "No"
|
|
219
|
+
tools = ", ".join(skill.tools) if skill.tools else "None"
|
|
220
|
+
|
|
221
|
+
console.print(f"[bold cyan]{skill.name}[/bold cyan]{builtin_marker}\n")
|
|
222
|
+
console.print(f"[bold]Description:[/bold] {skill.description}")
|
|
223
|
+
console.print(f"[bold]User Invocable:[/bold] {invocable}")
|
|
224
|
+
console.print(f"[bold]Tools:[/bold] {tools}")
|
|
225
|
+
|
|
226
|
+
# Show scripts
|
|
227
|
+
if skill.scripts:
|
|
228
|
+
console.print(f"[bold]Scripts:[/bold] {len(skill.scripts)}")
|
|
229
|
+
for script in skill.scripts:
|
|
230
|
+
console.print(f" [yellow]{script.name}[/yellow]: {script}")
|
|
231
|
+
else:
|
|
232
|
+
console.print(f"[bold]Scripts:[/bold] None")
|
|
233
|
+
|
|
234
|
+
console.print(f"[bold]File:[/bold] {skill.file_path}\n")
|
|
235
|
+
console.print("[bold]Instructions:[/bold]")
|
|
236
|
+
console.print(Panel(skill.instructions, border_style="dim"))
|
|
237
|
+
else:
|
|
238
|
+
console.print(f"[yellow]Skill '{name}' not found[/yellow]")
|
|
239
|
+
|
|
240
|
+
console.print()
|
|
241
|
+
console.print("[dim]" + "-" * 50 + "[/dim]")
|
|
242
|
+
|
|
243
|
+
|
|
244
|
+
def confirm_delete(skill_name: str) -> bool:
|
|
245
|
+
"""Confirm skill deletion."""
|
|
246
|
+
from prompt_toolkit import PromptSession
|
|
247
|
+
|
|
248
|
+
console.print()
|
|
249
|
+
console.print(f"[yellow]Delete skill '{skill_name}'?[/yellow]")
|
|
250
|
+
console.print("[dim]This will remove the skill directory. Type 'yes' to confirm.[/dim]")
|
|
251
|
+
|
|
252
|
+
try:
|
|
253
|
+
session = PromptSession()
|
|
254
|
+
response = session.prompt("Confirm > ").strip().lower()
|
|
255
|
+
return response in ("yes", "y")
|
|
256
|
+
except (KeyboardInterrupt, EOFError):
|
|
257
|
+
return False
|
|
258
|
+
|
|
259
|
+
|
|
260
|
+
def delete_skill(name: str) -> bool:
|
|
261
|
+
"""Delete a skill directory."""
|
|
262
|
+
import shutil
|
|
263
|
+
|
|
264
|
+
skills_dir = _get_skills_dir()
|
|
265
|
+
skill_dir = skills_dir / name
|
|
266
|
+
|
|
267
|
+
if not skill_dir.exists():
|
|
268
|
+
console.print(f"[yellow]Skill directory not found: {skill_dir}[/yellow]")
|
|
269
|
+
return False
|
|
270
|
+
|
|
271
|
+
# Check if it's a built-in skill
|
|
272
|
+
from emdash_core.agent.skills import SkillRegistry
|
|
273
|
+
registry = SkillRegistry.get_instance()
|
|
274
|
+
registry.load_skills(skills_dir)
|
|
275
|
+
skill = registry.get_skill(name)
|
|
276
|
+
|
|
277
|
+
if skill and getattr(skill, "_builtin", False):
|
|
278
|
+
console.print(f"[red]Cannot delete built-in skill '{name}'[/red]")
|
|
279
|
+
return False
|
|
280
|
+
|
|
281
|
+
if confirm_delete(name):
|
|
282
|
+
shutil.rmtree(skill_dir)
|
|
283
|
+
console.print(f"[green]Deleted skill: {name}[/green]")
|
|
284
|
+
return True
|
|
285
|
+
else:
|
|
286
|
+
console.print("[dim]Cancelled[/dim]")
|
|
287
|
+
return False
|
|
288
|
+
|
|
289
|
+
|
|
290
|
+
def chat_create_skill(client, renderer, model, max_iterations, render_with_interrupt) -> None:
|
|
291
|
+
"""Start a chat session to create a new skill with AI assistance."""
|
|
292
|
+
from prompt_toolkit import PromptSession
|
|
293
|
+
from prompt_toolkit.styles import Style
|
|
294
|
+
|
|
295
|
+
skills_dir = _get_skills_dir()
|
|
296
|
+
|
|
297
|
+
console.print()
|
|
298
|
+
console.print("[bold cyan]Create New Skill[/bold cyan]")
|
|
299
|
+
console.print("[dim]Describe what skill you want to create. The AI will help you write it.[/dim]")
|
|
300
|
+
console.print("[dim]Type 'done' to finish, Ctrl+C to cancel[/dim]")
|
|
301
|
+
console.print()
|
|
302
|
+
|
|
303
|
+
chat_style = Style.from_dict({
|
|
304
|
+
"prompt": "#00cc66 bold",
|
|
305
|
+
})
|
|
306
|
+
|
|
307
|
+
ps = PromptSession(style=chat_style)
|
|
308
|
+
chat_session_id = None
|
|
309
|
+
first_message = True
|
|
310
|
+
|
|
311
|
+
# Ensure skills directory exists
|
|
312
|
+
skills_dir.mkdir(parents=True, exist_ok=True)
|
|
313
|
+
|
|
314
|
+
# Chat loop
|
|
315
|
+
while True:
|
|
316
|
+
try:
|
|
317
|
+
user_input = ps.prompt([("class:prompt", "> ")]).strip()
|
|
318
|
+
|
|
319
|
+
if not user_input:
|
|
320
|
+
continue
|
|
321
|
+
|
|
322
|
+
if user_input.lower() in ("done", "quit", "exit", "q"):
|
|
323
|
+
console.print("[dim]Finished[/dim]")
|
|
324
|
+
break
|
|
325
|
+
|
|
326
|
+
# First message includes context about skills
|
|
327
|
+
if first_message:
|
|
328
|
+
message_with_context = f"""I want to create a new skill for my AI agent.
|
|
329
|
+
|
|
330
|
+
**Skills directory:** `{skills_dir}`
|
|
331
|
+
|
|
332
|
+
Skills are markdown files (SKILL.md) with YAML frontmatter that define reusable instructions for the agent.
|
|
333
|
+
|
|
334
|
+
SKILL.md format:
|
|
335
|
+
```markdown
|
|
336
|
+
---
|
|
337
|
+
name: skill-name
|
|
338
|
+
description: When to use this skill
|
|
339
|
+
user_invocable: true
|
|
340
|
+
tools: [tool1, tool2]
|
|
341
|
+
---
|
|
342
|
+
|
|
343
|
+
# Skill Title
|
|
344
|
+
|
|
345
|
+
Instructions for the skill...
|
|
346
|
+
|
|
347
|
+
## Scripts (optional)
|
|
348
|
+
|
|
349
|
+
If scripts are included, document them here.
|
|
350
|
+
```
|
|
351
|
+
|
|
352
|
+
**Frontmatter fields:**
|
|
353
|
+
- `name`: Unique skill identifier (lowercase, hyphens allowed)
|
|
354
|
+
- `description`: Brief description of when to use this skill
|
|
355
|
+
- `user_invocable`: Whether skill can be invoked with /name (true/false)
|
|
356
|
+
- `tools`: List of tools this skill needs (optional)
|
|
357
|
+
|
|
358
|
+
**Skill Scripts (optional):**
|
|
359
|
+
Skills can include executable bash scripts in the same directory as SKILL.md. These scripts:
|
|
360
|
+
- Must be self-contained bash executables (with shebang like `#!/bin/bash`)
|
|
361
|
+
- Are automatically discovered and made available to the agent
|
|
362
|
+
- Can be named anything (e.g., `run.sh`, `deploy.sh`, `validate.sh`)
|
|
363
|
+
- Are executed by the agent using the Bash tool when needed
|
|
364
|
+
|
|
365
|
+
Example script (`run.sh`):
|
|
366
|
+
```bash
|
|
367
|
+
#!/bin/bash
|
|
368
|
+
set -e
|
|
369
|
+
echo "Running skill script..."
|
|
370
|
+
# Add script logic here
|
|
371
|
+
```
|
|
372
|
+
|
|
373
|
+
**My request:** {user_input}
|
|
374
|
+
|
|
375
|
+
Please help me create a skill. Ask me questions if needed to understand what I want:
|
|
376
|
+
1. What should the skill do?
|
|
377
|
+
2. Does it need any scripts to execute code?
|
|
378
|
+
|
|
379
|
+
Then use the Write tool to create:
|
|
380
|
+
1. The SKILL.md file at `{skills_dir}/<skill-name>/SKILL.md`
|
|
381
|
+
2. Any scripts the user wants (e.g., `{skills_dir}/<skill-name>/run.sh`)
|
|
382
|
+
|
|
383
|
+
Remember to make scripts executable by including the shebang."""
|
|
384
|
+
stream = client.agent_chat_stream(
|
|
385
|
+
message=message_with_context,
|
|
386
|
+
model=model,
|
|
387
|
+
max_iterations=max_iterations,
|
|
388
|
+
options={"mode": "code"},
|
|
389
|
+
)
|
|
390
|
+
first_message = False
|
|
391
|
+
elif chat_session_id:
|
|
392
|
+
stream = client.agent_continue_stream(
|
|
393
|
+
chat_session_id, user_input
|
|
394
|
+
)
|
|
395
|
+
else:
|
|
396
|
+
stream = client.agent_chat_stream(
|
|
397
|
+
message=user_input,
|
|
398
|
+
model=model,
|
|
399
|
+
max_iterations=max_iterations,
|
|
400
|
+
options={"mode": "code"},
|
|
401
|
+
)
|
|
402
|
+
|
|
403
|
+
result = render_with_interrupt(renderer, stream)
|
|
404
|
+
if result and result.get("session_id"):
|
|
405
|
+
chat_session_id = result["session_id"]
|
|
406
|
+
|
|
407
|
+
except (KeyboardInterrupt, EOFError):
|
|
408
|
+
console.print()
|
|
409
|
+
console.print("[dim]Cancelled[/dim]")
|
|
410
|
+
break
|
|
411
|
+
except Exception as e:
|
|
412
|
+
console.print(f"[red]Error: {e}[/red]")
|
|
413
|
+
|
|
414
|
+
|
|
415
|
+
def handle_skills(args: str, client, renderer, model, max_iterations, render_with_interrupt) -> None:
|
|
416
|
+
"""Handle /skills command."""
|
|
417
|
+
from prompt_toolkit import PromptSession
|
|
418
|
+
|
|
419
|
+
# Handle subcommands
|
|
420
|
+
if args:
|
|
421
|
+
subparts = args.split(maxsplit=1)
|
|
422
|
+
subcommand = subparts[0].lower()
|
|
423
|
+
subargs = subparts[1] if len(subparts) > 1 else ""
|
|
424
|
+
|
|
425
|
+
if subcommand == "list":
|
|
426
|
+
skills = list_skills()
|
|
427
|
+
if skills:
|
|
428
|
+
console.print("\n[bold cyan]Skills[/bold cyan]\n")
|
|
429
|
+
for skill in skills:
|
|
430
|
+
builtin = " [built-in]" if skill["is_builtin"] else ""
|
|
431
|
+
invocable = f" (/{skill['name']})" if skill["user_invocable"] else ""
|
|
432
|
+
console.print(f" [cyan]{skill['name']}[/cyan]{builtin}{invocable} - {skill['description']}")
|
|
433
|
+
console.print()
|
|
434
|
+
else:
|
|
435
|
+
console.print("\n[dim]No skills defined yet.[/dim]")
|
|
436
|
+
console.print(f"[dim]Skills directory: {_get_skills_dir()}[/dim]\n")
|
|
437
|
+
elif subcommand == "show" and subargs:
|
|
438
|
+
show_skill_details(subargs.strip())
|
|
439
|
+
elif subcommand == "delete" and subargs:
|
|
440
|
+
delete_skill(subargs.strip())
|
|
441
|
+
elif subcommand in ("add", "create", "new"):
|
|
442
|
+
chat_create_skill(client, renderer, model, max_iterations, render_with_interrupt)
|
|
443
|
+
else:
|
|
444
|
+
console.print("[yellow]Usage: /skills [list|show|add|delete] [name][/yellow]")
|
|
445
|
+
console.print("[dim]Or just /skills for interactive menu[/dim]")
|
|
446
|
+
else:
|
|
447
|
+
# Interactive menu
|
|
448
|
+
while True:
|
|
449
|
+
action, skill_name = show_skills_interactive_menu()
|
|
450
|
+
|
|
451
|
+
if action == "cancel":
|
|
452
|
+
break
|
|
453
|
+
elif action == "view":
|
|
454
|
+
show_skill_details(skill_name)
|
|
455
|
+
# After viewing, show options
|
|
456
|
+
try:
|
|
457
|
+
# Check if it's a built-in skill
|
|
458
|
+
from emdash_core.agent.skills import SkillRegistry
|
|
459
|
+
registry = SkillRegistry.get_instance()
|
|
460
|
+
skill = registry.get_skill(skill_name)
|
|
461
|
+
is_builtin = skill and getattr(skill, "_builtin", False)
|
|
462
|
+
|
|
463
|
+
if is_builtin:
|
|
464
|
+
console.print("[dim]Enter to go back[/dim]", end="")
|
|
465
|
+
else:
|
|
466
|
+
console.print("[red]'d'[/red] delete • [dim]Enter back[/dim]", end="")
|
|
467
|
+
ps = PromptSession()
|
|
468
|
+
resp = ps.prompt(" ").strip().lower()
|
|
469
|
+
if resp == 'd' and not is_builtin:
|
|
470
|
+
delete_skill(skill_name)
|
|
471
|
+
console.print()
|
|
472
|
+
except (KeyboardInterrupt, EOFError):
|
|
473
|
+
break
|
|
474
|
+
elif action == "create":
|
|
475
|
+
chat_create_skill(client, renderer, model, max_iterations, render_with_interrupt)
|
|
476
|
+
# Refresh menu after creating
|
|
477
|
+
elif action == "delete":
|
|
478
|
+
delete_skill(skill_name)
|