emdash-cli 0.1.35__py3-none-any.whl → 0.1.46__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 +35 -0
- emdash_cli/clipboard.py +30 -61
- emdash_cli/commands/agent/__init__.py +14 -0
- emdash_cli/commands/agent/cli.py +100 -0
- emdash_cli/commands/agent/constants.py +53 -0
- emdash_cli/commands/agent/file_utils.py +178 -0
- emdash_cli/commands/agent/handlers/__init__.py +41 -0
- emdash_cli/commands/agent/handlers/agents.py +421 -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/mcp.py +183 -0
- emdash_cli/commands/agent/handlers/misc.py +200 -0
- emdash_cli/commands/agent/handlers/rules.py +394 -0
- emdash_cli/commands/agent/handlers/sessions.py +168 -0
- emdash_cli/commands/agent/handlers/setup.py +582 -0
- emdash_cli/commands/agent/handlers/skills.py +440 -0
- emdash_cli/commands/agent/handlers/todos.py +98 -0
- emdash_cli/commands/agent/handlers/verify.py +648 -0
- emdash_cli/commands/agent/interactive.py +657 -0
- emdash_cli/commands/agent/menus.py +728 -0
- emdash_cli/commands/agent.py +7 -1321
- emdash_cli/commands/server.py +99 -40
- emdash_cli/server_manager.py +70 -10
- emdash_cli/sse_renderer.py +36 -5
- {emdash_cli-0.1.35.dist-info → emdash_cli-0.1.46.dist-info}/METADATA +2 -4
- emdash_cli-0.1.46.dist-info/RECORD +49 -0
- emdash_cli-0.1.35.dist-info/RECORD +0 -30
- {emdash_cli-0.1.35.dist-info → emdash_cli-0.1.46.dist-info}/WHEEL +0 -0
- {emdash_cli-0.1.35.dist-info → emdash_cli-0.1.46.dist-info}/entry_points.txt +0 -0
|
@@ -0,0 +1,440 @@
|
|
|
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
|
|
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
|
+
})
|
|
48
|
+
|
|
49
|
+
return skills
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def show_skills_interactive_menu() -> tuple[str, str]:
|
|
53
|
+
"""Show interactive skills menu.
|
|
54
|
+
|
|
55
|
+
Returns:
|
|
56
|
+
Tuple of (action, skill_name) where action is one of:
|
|
57
|
+
- 'view': View skill details
|
|
58
|
+
- 'create': Create new skill
|
|
59
|
+
- 'delete': Delete skill
|
|
60
|
+
- 'cancel': User cancelled
|
|
61
|
+
"""
|
|
62
|
+
from prompt_toolkit import Application
|
|
63
|
+
from prompt_toolkit.key_binding import KeyBindings
|
|
64
|
+
from prompt_toolkit.layout import Layout, HSplit, Window, FormattedTextControl
|
|
65
|
+
from prompt_toolkit.styles import Style
|
|
66
|
+
|
|
67
|
+
skills = list_skills()
|
|
68
|
+
|
|
69
|
+
# Build menu items: (name, description, is_builtin, is_action)
|
|
70
|
+
menu_items = []
|
|
71
|
+
|
|
72
|
+
for skill in skills:
|
|
73
|
+
builtin_marker = " [built-in]" if skill["is_builtin"] else ""
|
|
74
|
+
menu_items.append((skill["name"], skill["description"] + builtin_marker, skill["is_builtin"], False))
|
|
75
|
+
|
|
76
|
+
# Add action items at the bottom
|
|
77
|
+
menu_items.append(("+ Create New Skill", "Create a new skill with AI assistance", False, True))
|
|
78
|
+
|
|
79
|
+
if not menu_items:
|
|
80
|
+
menu_items.append(("+ Create New Skill", "Create a new skill with AI assistance", False, True))
|
|
81
|
+
|
|
82
|
+
selected_index = [0]
|
|
83
|
+
result = [("cancel", "")]
|
|
84
|
+
|
|
85
|
+
kb = KeyBindings()
|
|
86
|
+
|
|
87
|
+
@kb.add("up")
|
|
88
|
+
@kb.add("k")
|
|
89
|
+
def move_up(event):
|
|
90
|
+
selected_index[0] = (selected_index[0] - 1) % len(menu_items)
|
|
91
|
+
|
|
92
|
+
@kb.add("down")
|
|
93
|
+
@kb.add("j")
|
|
94
|
+
def move_down(event):
|
|
95
|
+
selected_index[0] = (selected_index[0] + 1) % len(menu_items)
|
|
96
|
+
|
|
97
|
+
@kb.add("enter")
|
|
98
|
+
def select(event):
|
|
99
|
+
item = menu_items[selected_index[0]]
|
|
100
|
+
name, desc, is_builtin, is_action = item
|
|
101
|
+
if is_action:
|
|
102
|
+
if "Create" in name:
|
|
103
|
+
result[0] = ("create", "")
|
|
104
|
+
else:
|
|
105
|
+
result[0] = ("view", name)
|
|
106
|
+
event.app.exit()
|
|
107
|
+
|
|
108
|
+
@kb.add("d")
|
|
109
|
+
def delete_skill(event):
|
|
110
|
+
item = menu_items[selected_index[0]]
|
|
111
|
+
name, desc, is_builtin, is_action = item
|
|
112
|
+
if not is_action and not is_builtin:
|
|
113
|
+
result[0] = ("delete", name)
|
|
114
|
+
event.app.exit()
|
|
115
|
+
elif is_builtin:
|
|
116
|
+
# Can't delete built-in skills
|
|
117
|
+
pass
|
|
118
|
+
|
|
119
|
+
@kb.add("n")
|
|
120
|
+
def new_skill(event):
|
|
121
|
+
result[0] = ("create", "")
|
|
122
|
+
event.app.exit()
|
|
123
|
+
|
|
124
|
+
@kb.add("c-c")
|
|
125
|
+
@kb.add("escape")
|
|
126
|
+
@kb.add("q")
|
|
127
|
+
def cancel(event):
|
|
128
|
+
result[0] = ("cancel", "")
|
|
129
|
+
event.app.exit()
|
|
130
|
+
|
|
131
|
+
def get_formatted_menu():
|
|
132
|
+
lines = [("class:title", "Skills\n\n")]
|
|
133
|
+
|
|
134
|
+
if not skills:
|
|
135
|
+
lines.append(("class:dim", "No skills defined yet.\n\n"))
|
|
136
|
+
|
|
137
|
+
for i, (name, desc, is_builtin, is_action) in enumerate(menu_items):
|
|
138
|
+
is_selected = i == selected_index[0]
|
|
139
|
+
prefix = ">" if is_selected else " "
|
|
140
|
+
|
|
141
|
+
if is_action:
|
|
142
|
+
if is_selected:
|
|
143
|
+
lines.append(("class:action-selected", f"{prefix}{name}\n"))
|
|
144
|
+
else:
|
|
145
|
+
lines.append(("class:action", f"{prefix}{name}\n"))
|
|
146
|
+
else:
|
|
147
|
+
if is_selected:
|
|
148
|
+
lines.append(("class:skill-selected", f"{prefix}{name}"))
|
|
149
|
+
lines.append(("class:desc-selected", f" - {desc}\n"))
|
|
150
|
+
else:
|
|
151
|
+
lines.append(("class:skill", f"{prefix}{name}"))
|
|
152
|
+
lines.append(("class:desc", f" - {desc}\n"))
|
|
153
|
+
|
|
154
|
+
lines.append(("class:hint", "\n[up]/[down] navigate | Enter view | n new | d delete | q quit"))
|
|
155
|
+
return lines
|
|
156
|
+
|
|
157
|
+
style = Style.from_dict({
|
|
158
|
+
"title": "#00ccff bold",
|
|
159
|
+
"dim": "#666666",
|
|
160
|
+
"skill": "#00ccff",
|
|
161
|
+
"skill-selected": "#00cc66 bold",
|
|
162
|
+
"action": "#ffcc00",
|
|
163
|
+
"action-selected": "#ffcc00 bold",
|
|
164
|
+
"desc": "#666666",
|
|
165
|
+
"desc-selected": "#00cc66",
|
|
166
|
+
"hint": "#888888 italic",
|
|
167
|
+
})
|
|
168
|
+
|
|
169
|
+
height = len(menu_items) + 5 # items + title + hint + padding
|
|
170
|
+
|
|
171
|
+
layout = Layout(
|
|
172
|
+
HSplit([
|
|
173
|
+
Window(
|
|
174
|
+
FormattedTextControl(get_formatted_menu),
|
|
175
|
+
height=height,
|
|
176
|
+
),
|
|
177
|
+
])
|
|
178
|
+
)
|
|
179
|
+
|
|
180
|
+
app = Application(
|
|
181
|
+
layout=layout,
|
|
182
|
+
key_bindings=kb,
|
|
183
|
+
style=style,
|
|
184
|
+
full_screen=False,
|
|
185
|
+
)
|
|
186
|
+
|
|
187
|
+
console.print()
|
|
188
|
+
|
|
189
|
+
try:
|
|
190
|
+
app.run()
|
|
191
|
+
except (KeyboardInterrupt, EOFError):
|
|
192
|
+
result[0] = ("cancel", "")
|
|
193
|
+
|
|
194
|
+
# Clear menu visually with separator
|
|
195
|
+
console.print()
|
|
196
|
+
|
|
197
|
+
return result[0]
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
def show_skill_details(name: str) -> None:
|
|
201
|
+
"""Show detailed view of a skill."""
|
|
202
|
+
from emdash_core.agent.skills import SkillRegistry
|
|
203
|
+
|
|
204
|
+
skills_dir = _get_skills_dir()
|
|
205
|
+
registry = SkillRegistry.get_instance()
|
|
206
|
+
registry.load_skills(skills_dir)
|
|
207
|
+
|
|
208
|
+
skill = registry.get_skill(name)
|
|
209
|
+
|
|
210
|
+
console.print()
|
|
211
|
+
console.print("[dim]" + "-" * 50 + "[/dim]")
|
|
212
|
+
console.print()
|
|
213
|
+
|
|
214
|
+
if skill:
|
|
215
|
+
builtin_marker = " [built-in]" if getattr(skill, "_builtin", False) else ""
|
|
216
|
+
invocable = f"Yes (/{skill.name})" if skill.user_invocable else "No"
|
|
217
|
+
tools = ", ".join(skill.tools) if skill.tools else "None"
|
|
218
|
+
|
|
219
|
+
console.print(f"[bold cyan]{skill.name}[/bold cyan]{builtin_marker}\n")
|
|
220
|
+
console.print(f"[bold]Description:[/bold] {skill.description}")
|
|
221
|
+
console.print(f"[bold]User Invocable:[/bold] {invocable}")
|
|
222
|
+
console.print(f"[bold]Tools:[/bold] {tools}")
|
|
223
|
+
console.print(f"[bold]File:[/bold] {skill.file_path}\n")
|
|
224
|
+
console.print("[bold]Instructions:[/bold]")
|
|
225
|
+
console.print(Panel(skill.instructions, border_style="dim"))
|
|
226
|
+
else:
|
|
227
|
+
console.print(f"[yellow]Skill '{name}' not found[/yellow]")
|
|
228
|
+
|
|
229
|
+
console.print()
|
|
230
|
+
console.print("[dim]" + "-" * 50 + "[/dim]")
|
|
231
|
+
|
|
232
|
+
|
|
233
|
+
def confirm_delete(skill_name: str) -> bool:
|
|
234
|
+
"""Confirm skill deletion."""
|
|
235
|
+
from prompt_toolkit import PromptSession
|
|
236
|
+
|
|
237
|
+
console.print()
|
|
238
|
+
console.print(f"[yellow]Delete skill '{skill_name}'?[/yellow]")
|
|
239
|
+
console.print("[dim]This will remove the skill directory. Type 'yes' to confirm.[/dim]")
|
|
240
|
+
|
|
241
|
+
try:
|
|
242
|
+
session = PromptSession()
|
|
243
|
+
response = session.prompt("Confirm > ").strip().lower()
|
|
244
|
+
return response in ("yes", "y")
|
|
245
|
+
except (KeyboardInterrupt, EOFError):
|
|
246
|
+
return False
|
|
247
|
+
|
|
248
|
+
|
|
249
|
+
def delete_skill(name: str) -> bool:
|
|
250
|
+
"""Delete a skill directory."""
|
|
251
|
+
import shutil
|
|
252
|
+
|
|
253
|
+
skills_dir = _get_skills_dir()
|
|
254
|
+
skill_dir = skills_dir / name
|
|
255
|
+
|
|
256
|
+
if not skill_dir.exists():
|
|
257
|
+
console.print(f"[yellow]Skill directory not found: {skill_dir}[/yellow]")
|
|
258
|
+
return False
|
|
259
|
+
|
|
260
|
+
# Check if it's a built-in skill
|
|
261
|
+
from emdash_core.agent.skills import SkillRegistry
|
|
262
|
+
registry = SkillRegistry.get_instance()
|
|
263
|
+
registry.load_skills(skills_dir)
|
|
264
|
+
skill = registry.get_skill(name)
|
|
265
|
+
|
|
266
|
+
if skill and getattr(skill, "_builtin", False):
|
|
267
|
+
console.print(f"[red]Cannot delete built-in skill '{name}'[/red]")
|
|
268
|
+
return False
|
|
269
|
+
|
|
270
|
+
if confirm_delete(name):
|
|
271
|
+
shutil.rmtree(skill_dir)
|
|
272
|
+
console.print(f"[green]Deleted skill: {name}[/green]")
|
|
273
|
+
return True
|
|
274
|
+
else:
|
|
275
|
+
console.print("[dim]Cancelled[/dim]")
|
|
276
|
+
return False
|
|
277
|
+
|
|
278
|
+
|
|
279
|
+
def chat_create_skill(client, renderer, model, max_iterations, render_with_interrupt) -> None:
|
|
280
|
+
"""Start a chat session to create a new skill with AI assistance."""
|
|
281
|
+
from prompt_toolkit import PromptSession
|
|
282
|
+
from prompt_toolkit.styles import Style
|
|
283
|
+
|
|
284
|
+
skills_dir = _get_skills_dir()
|
|
285
|
+
|
|
286
|
+
console.print()
|
|
287
|
+
console.print("[bold cyan]Create New Skill[/bold cyan]")
|
|
288
|
+
console.print("[dim]Describe what skill you want to create. The AI will help you write it.[/dim]")
|
|
289
|
+
console.print("[dim]Type 'done' to finish, Ctrl+C to cancel[/dim]")
|
|
290
|
+
console.print()
|
|
291
|
+
|
|
292
|
+
chat_style = Style.from_dict({
|
|
293
|
+
"prompt": "#00cc66 bold",
|
|
294
|
+
})
|
|
295
|
+
|
|
296
|
+
ps = PromptSession(style=chat_style)
|
|
297
|
+
chat_session_id = None
|
|
298
|
+
first_message = True
|
|
299
|
+
|
|
300
|
+
# Ensure skills directory exists
|
|
301
|
+
skills_dir.mkdir(parents=True, exist_ok=True)
|
|
302
|
+
|
|
303
|
+
# Chat loop
|
|
304
|
+
while True:
|
|
305
|
+
try:
|
|
306
|
+
user_input = ps.prompt([("class:prompt", "> ")]).strip()
|
|
307
|
+
|
|
308
|
+
if not user_input:
|
|
309
|
+
continue
|
|
310
|
+
|
|
311
|
+
if user_input.lower() in ("done", "quit", "exit", "q"):
|
|
312
|
+
console.print("[dim]Finished[/dim]")
|
|
313
|
+
break
|
|
314
|
+
|
|
315
|
+
# First message includes context about skills
|
|
316
|
+
if first_message:
|
|
317
|
+
message_with_context = f"""I want to create a new skill for my AI agent.
|
|
318
|
+
|
|
319
|
+
**Skills directory:** `{skills_dir}`
|
|
320
|
+
|
|
321
|
+
Skills are markdown files (SKILL.md) with YAML frontmatter that define reusable instructions for the agent.
|
|
322
|
+
|
|
323
|
+
SKILL.md format:
|
|
324
|
+
```markdown
|
|
325
|
+
---
|
|
326
|
+
name: skill-name
|
|
327
|
+
description: When to use this skill
|
|
328
|
+
user_invocable: true
|
|
329
|
+
tools: [tool1, tool2]
|
|
330
|
+
---
|
|
331
|
+
|
|
332
|
+
# Skill Title
|
|
333
|
+
|
|
334
|
+
Instructions for the skill...
|
|
335
|
+
```
|
|
336
|
+
|
|
337
|
+
**Frontmatter fields:**
|
|
338
|
+
- `name`: Unique skill identifier (lowercase, hyphens allowed)
|
|
339
|
+
- `description`: Brief description of when to use this skill
|
|
340
|
+
- `user_invocable`: Whether skill can be invoked with /name (true/false)
|
|
341
|
+
- `tools`: List of tools this skill needs (optional)
|
|
342
|
+
|
|
343
|
+
**My request:** {user_input}
|
|
344
|
+
|
|
345
|
+
Please help me create a skill. Ask me questions if needed to understand what I want, then use the Write tool to create the file at `{skills_dir}/<skill-name>/SKILL.md`."""
|
|
346
|
+
stream = client.agent_chat_stream(
|
|
347
|
+
message=message_with_context,
|
|
348
|
+
model=model,
|
|
349
|
+
max_iterations=max_iterations,
|
|
350
|
+
options={"mode": "code"},
|
|
351
|
+
)
|
|
352
|
+
first_message = False
|
|
353
|
+
elif chat_session_id:
|
|
354
|
+
stream = client.agent_continue_stream(
|
|
355
|
+
chat_session_id, user_input
|
|
356
|
+
)
|
|
357
|
+
else:
|
|
358
|
+
stream = client.agent_chat_stream(
|
|
359
|
+
message=user_input,
|
|
360
|
+
model=model,
|
|
361
|
+
max_iterations=max_iterations,
|
|
362
|
+
options={"mode": "code"},
|
|
363
|
+
)
|
|
364
|
+
|
|
365
|
+
result = render_with_interrupt(renderer, stream)
|
|
366
|
+
if result and result.get("session_id"):
|
|
367
|
+
chat_session_id = result["session_id"]
|
|
368
|
+
|
|
369
|
+
except (KeyboardInterrupt, EOFError):
|
|
370
|
+
console.print()
|
|
371
|
+
console.print("[dim]Cancelled[/dim]")
|
|
372
|
+
break
|
|
373
|
+
except Exception as e:
|
|
374
|
+
console.print(f"[red]Error: {e}[/red]")
|
|
375
|
+
|
|
376
|
+
|
|
377
|
+
def handle_skills(args: str, client, renderer, model, max_iterations, render_with_interrupt) -> None:
|
|
378
|
+
"""Handle /skills command."""
|
|
379
|
+
from prompt_toolkit import PromptSession
|
|
380
|
+
|
|
381
|
+
# Handle subcommands
|
|
382
|
+
if args:
|
|
383
|
+
subparts = args.split(maxsplit=1)
|
|
384
|
+
subcommand = subparts[0].lower()
|
|
385
|
+
subargs = subparts[1] if len(subparts) > 1 else ""
|
|
386
|
+
|
|
387
|
+
if subcommand == "list":
|
|
388
|
+
skills = list_skills()
|
|
389
|
+
if skills:
|
|
390
|
+
console.print("\n[bold cyan]Skills[/bold cyan]\n")
|
|
391
|
+
for skill in skills:
|
|
392
|
+
builtin = " [built-in]" if skill["is_builtin"] else ""
|
|
393
|
+
invocable = f" (/{skill['name']})" if skill["user_invocable"] else ""
|
|
394
|
+
console.print(f" [cyan]{skill['name']}[/cyan]{builtin}{invocable} - {skill['description']}")
|
|
395
|
+
console.print()
|
|
396
|
+
else:
|
|
397
|
+
console.print("\n[dim]No skills defined yet.[/dim]")
|
|
398
|
+
console.print(f"[dim]Skills directory: {_get_skills_dir()}[/dim]\n")
|
|
399
|
+
elif subcommand == "show" and subargs:
|
|
400
|
+
show_skill_details(subargs.strip())
|
|
401
|
+
elif subcommand == "delete" and subargs:
|
|
402
|
+
delete_skill(subargs.strip())
|
|
403
|
+
elif subcommand in ("add", "create", "new"):
|
|
404
|
+
chat_create_skill(client, renderer, model, max_iterations, render_with_interrupt)
|
|
405
|
+
else:
|
|
406
|
+
console.print("[yellow]Usage: /skills [list|show|add|delete] [name][/yellow]")
|
|
407
|
+
console.print("[dim]Or just /skills for interactive menu[/dim]")
|
|
408
|
+
else:
|
|
409
|
+
# Interactive menu
|
|
410
|
+
while True:
|
|
411
|
+
action, skill_name = show_skills_interactive_menu()
|
|
412
|
+
|
|
413
|
+
if action == "cancel":
|
|
414
|
+
break
|
|
415
|
+
elif action == "view":
|
|
416
|
+
show_skill_details(skill_name)
|
|
417
|
+
# After viewing, show options
|
|
418
|
+
try:
|
|
419
|
+
# Check if it's a built-in skill
|
|
420
|
+
from emdash_core.agent.skills import SkillRegistry
|
|
421
|
+
registry = SkillRegistry.get_instance()
|
|
422
|
+
skill = registry.get_skill(skill_name)
|
|
423
|
+
is_builtin = skill and getattr(skill, "_builtin", False)
|
|
424
|
+
|
|
425
|
+
if is_builtin:
|
|
426
|
+
console.print("[dim]Enter to go back[/dim]", end="")
|
|
427
|
+
else:
|
|
428
|
+
console.print("[dim]'d' delete | Enter back[/dim]", end="")
|
|
429
|
+
ps = PromptSession()
|
|
430
|
+
resp = ps.prompt(" ").strip().lower()
|
|
431
|
+
if resp == 'd' and not is_builtin:
|
|
432
|
+
delete_skill(skill_name)
|
|
433
|
+
console.print()
|
|
434
|
+
except (KeyboardInterrupt, EOFError):
|
|
435
|
+
break
|
|
436
|
+
elif action == "create":
|
|
437
|
+
chat_create_skill(client, renderer, model, max_iterations, render_with_interrupt)
|
|
438
|
+
# Refresh menu after creating
|
|
439
|
+
elif action == "delete":
|
|
440
|
+
delete_skill(skill_name)
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
"""Handler for /todos and /todo-add commands."""
|
|
2
|
+
|
|
3
|
+
from rich.console import Console
|
|
4
|
+
|
|
5
|
+
console = Console()
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def handle_todos(args: str, client, session_id: str | None, pending_todos: list[str]) -> None:
|
|
9
|
+
"""Handle /todos command.
|
|
10
|
+
|
|
11
|
+
Args:
|
|
12
|
+
args: Command arguments (unused)
|
|
13
|
+
client: EmdashClient instance
|
|
14
|
+
session_id: Current session ID
|
|
15
|
+
pending_todos: List of pending todos (before session starts)
|
|
16
|
+
"""
|
|
17
|
+
if not session_id:
|
|
18
|
+
if pending_todos:
|
|
19
|
+
console.print("\n[bold cyan]Pending Todos[/bold cyan] [dim](will be added when session starts)[/dim]\n")
|
|
20
|
+
for i, todo in enumerate(pending_todos, 1):
|
|
21
|
+
console.print(f" [dim]⬚[/dim] {todo}")
|
|
22
|
+
console.print()
|
|
23
|
+
else:
|
|
24
|
+
console.print("\n[dim]No todos yet. Start a conversation and the agent will track its tasks here.[/dim]\n")
|
|
25
|
+
else:
|
|
26
|
+
try:
|
|
27
|
+
result = client.get_todos(session_id)
|
|
28
|
+
todos = result.get("todos", [])
|
|
29
|
+
summary = result.get("summary", {})
|
|
30
|
+
|
|
31
|
+
if not todos:
|
|
32
|
+
console.print("\n[dim]No todos in current session.[/dim]\n")
|
|
33
|
+
else:
|
|
34
|
+
console.print("\n[bold cyan]Agent Todo List[/bold cyan]\n")
|
|
35
|
+
|
|
36
|
+
# Status icons
|
|
37
|
+
icons = {
|
|
38
|
+
"pending": "[dim]⬚[/dim]",
|
|
39
|
+
"in_progress": "[yellow]🔄[/yellow]",
|
|
40
|
+
"completed": "[green]✓[/green]",
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
for todo in todos:
|
|
44
|
+
icon = icons.get(todo["status"], "?")
|
|
45
|
+
title = todo["title"]
|
|
46
|
+
status = todo["status"]
|
|
47
|
+
|
|
48
|
+
if status == "completed":
|
|
49
|
+
console.print(f" {icon} [dim strikethrough]{title}[/dim strikethrough]")
|
|
50
|
+
elif status == "in_progress":
|
|
51
|
+
console.print(f" {icon} [bold]{title}[/bold]")
|
|
52
|
+
else:
|
|
53
|
+
console.print(f" {icon} {title}")
|
|
54
|
+
|
|
55
|
+
if todo.get("description"):
|
|
56
|
+
desc = todo["description"]
|
|
57
|
+
if len(desc) > 60:
|
|
58
|
+
console.print(f" [dim]{desc[:60]}...[/dim]")
|
|
59
|
+
else:
|
|
60
|
+
console.print(f" [dim]{desc}[/dim]")
|
|
61
|
+
|
|
62
|
+
console.print()
|
|
63
|
+
console.print(
|
|
64
|
+
f"[dim]Total: {summary.get('total', 0)} | "
|
|
65
|
+
f"Pending: {summary.get('pending', 0)} | "
|
|
66
|
+
f"In Progress: {summary.get('in_progress', 0)} | "
|
|
67
|
+
f"Completed: {summary.get('completed', 0)}[/dim]"
|
|
68
|
+
)
|
|
69
|
+
console.print()
|
|
70
|
+
|
|
71
|
+
except Exception as e:
|
|
72
|
+
console.print(f"[red]Error fetching todos: {e}[/red]")
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def handle_todo_add(args: str, client, session_id: str | None, pending_todos: list[str]) -> None:
|
|
76
|
+
"""Handle /todo-add command.
|
|
77
|
+
|
|
78
|
+
Args:
|
|
79
|
+
args: Todo title to add
|
|
80
|
+
client: EmdashClient instance
|
|
81
|
+
session_id: Current session ID
|
|
82
|
+
pending_todos: List of pending todos (before session starts)
|
|
83
|
+
"""
|
|
84
|
+
if not args:
|
|
85
|
+
console.print("[yellow]Usage: /todo-add <title>[/yellow]")
|
|
86
|
+
console.print("[dim]Example: /todo-add Fix the failing tests[/dim]")
|
|
87
|
+
elif not session_id:
|
|
88
|
+
pending_todos.append(args)
|
|
89
|
+
console.print(f"[green]Todo noted:[/green] {args}")
|
|
90
|
+
console.print("[dim]This will be added to the agent's context when you start a conversation.[/dim]")
|
|
91
|
+
else:
|
|
92
|
+
try:
|
|
93
|
+
result = client.add_todo(session_id, args)
|
|
94
|
+
task = result.get("task", {})
|
|
95
|
+
console.print(f"[green]Added todo #{task.get('id')}: {task.get('title')}[/green]")
|
|
96
|
+
console.print(f"[dim]Total tasks: {result.get('total_tasks', 0)}[/dim]")
|
|
97
|
+
except Exception as e:
|
|
98
|
+
console.print(f"[red]Error adding todo: {e}[/red]")
|