emdash-cli 0.1.46__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 +12 -28
- emdash_cli/commands/__init__.py +2 -2
- emdash_cli/commands/agent/constants.py +10 -0
- emdash_cli/commands/agent/handlers/__init__.py +10 -0
- emdash_cli/commands/agent/handlers/agents.py +67 -39
- emdash_cli/commands/agent/handlers/index.py +183 -0
- emdash_cli/commands/agent/handlers/misc.py +119 -0
- emdash_cli/commands/agent/handlers/registry.py +72 -0
- emdash_cli/commands/agent/handlers/rules.py +48 -31
- emdash_cli/commands/agent/handlers/sessions.py +1 -1
- emdash_cli/commands/agent/handlers/setup.py +187 -54
- emdash_cli/commands/agent/handlers/skills.py +42 -4
- emdash_cli/commands/agent/handlers/telegram.py +475 -0
- emdash_cli/commands/agent/handlers/todos.py +55 -34
- emdash_cli/commands/agent/handlers/verify.py +10 -5
- emdash_cli/commands/agent/help.py +236 -0
- emdash_cli/commands/agent/interactive.py +222 -37
- emdash_cli/commands/agent/menus.py +116 -84
- emdash_cli/commands/agent/onboarding.py +619 -0
- emdash_cli/commands/agent/session_restore.py +210 -0
- emdash_cli/commands/index.py +111 -13
- emdash_cli/commands/registry.py +635 -0
- 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/sse_renderer.py +632 -171
- {emdash_cli-0.1.46.dist-info → emdash_cli-0.1.67.dist-info}/METADATA +2 -2
- emdash_cli-0.1.67.dist-info/RECORD +63 -0
- emdash_cli/commands/swarm.py +0 -86
- emdash_cli-0.1.46.dist-info/RECORD +0 -49
- {emdash_cli-0.1.46.dist-info → emdash_cli-0.1.67.dist-info}/WHEEL +0 -0
- {emdash_cli-0.1.46.dist-info → emdash_cli-0.1.67.dist-info}/entry_points.txt +0 -0
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
"""Handler for /registry command."""
|
|
2
|
+
|
|
3
|
+
from rich.console import Console
|
|
4
|
+
|
|
5
|
+
console = Console()
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def handle_registry(args: str) -> None:
|
|
9
|
+
"""Handle /registry command.
|
|
10
|
+
|
|
11
|
+
Args:
|
|
12
|
+
args: Command arguments (list, show, install, search)
|
|
13
|
+
"""
|
|
14
|
+
from emdash_cli.commands.registry import (
|
|
15
|
+
_show_registry_wizard,
|
|
16
|
+
_fetch_registry,
|
|
17
|
+
registry_list,
|
|
18
|
+
registry_show,
|
|
19
|
+
registry_install,
|
|
20
|
+
registry_search,
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
# Parse subcommand
|
|
24
|
+
subparts = args.split(maxsplit=1) if args else []
|
|
25
|
+
subcommand = subparts[0].lower() if subparts else ""
|
|
26
|
+
subargs = subparts[1] if len(subparts) > 1 else ""
|
|
27
|
+
|
|
28
|
+
if subcommand == "" or subcommand == "wizard":
|
|
29
|
+
# Show interactive wizard (default)
|
|
30
|
+
_show_registry_wizard()
|
|
31
|
+
|
|
32
|
+
elif subcommand == "list":
|
|
33
|
+
# List components
|
|
34
|
+
component_type = subargs if subargs else None
|
|
35
|
+
if component_type and component_type not in ["skills", "rules", "agents", "verifiers"]:
|
|
36
|
+
console.print(f"[yellow]Unknown type: {component_type}[/yellow]")
|
|
37
|
+
console.print("[dim]Types: skills, rules, agents, verifiers[/dim]")
|
|
38
|
+
return
|
|
39
|
+
# Invoke click command
|
|
40
|
+
registry_list.callback(component_type)
|
|
41
|
+
|
|
42
|
+
elif subcommand == "show":
|
|
43
|
+
if not subargs:
|
|
44
|
+
console.print("[yellow]Usage: /registry show type:name[/yellow]")
|
|
45
|
+
console.print("[dim]Example: /registry show skill:frontend-design[/dim]")
|
|
46
|
+
return
|
|
47
|
+
registry_show.callback(subargs)
|
|
48
|
+
|
|
49
|
+
elif subcommand == "install":
|
|
50
|
+
if not subargs:
|
|
51
|
+
console.print("[yellow]Usage: /registry install type:name [type:name ...][/yellow]")
|
|
52
|
+
console.print("[dim]Example: /registry install skill:frontend-design rule:typescript[/dim]")
|
|
53
|
+
return
|
|
54
|
+
component_ids = tuple(subargs.split())
|
|
55
|
+
registry_install.callback(component_ids)
|
|
56
|
+
|
|
57
|
+
elif subcommand == "search":
|
|
58
|
+
if not subargs:
|
|
59
|
+
console.print("[yellow]Usage: /registry search query[/yellow]")
|
|
60
|
+
console.print("[dim]Example: /registry search frontend[/dim]")
|
|
61
|
+
return
|
|
62
|
+
# Simple search without tag filtering from slash command
|
|
63
|
+
registry_search.callback(subargs, ())
|
|
64
|
+
|
|
65
|
+
else:
|
|
66
|
+
console.print(f"[yellow]Unknown subcommand: {subcommand}[/yellow]")
|
|
67
|
+
console.print("[dim]Usage: /registry [list|show|install|search][/dim]")
|
|
68
|
+
console.print("[dim] /registry - Interactive wizard[/dim]")
|
|
69
|
+
console.print("[dim] /registry list - List all components[/dim]")
|
|
70
|
+
console.print("[dim] /registry show x:y - Show component details[/dim]")
|
|
71
|
+
console.print("[dim] /registry install x:y - Install components[/dim]")
|
|
72
|
+
console.print("[dim] /registry search q - Search registry[/dim]")
|
|
@@ -5,6 +5,15 @@ from pathlib import Path
|
|
|
5
5
|
from rich.console import Console
|
|
6
6
|
from rich.panel import Panel
|
|
7
7
|
|
|
8
|
+
from ....design import (
|
|
9
|
+
Colors,
|
|
10
|
+
header,
|
|
11
|
+
footer,
|
|
12
|
+
SEPARATOR_WIDTH,
|
|
13
|
+
STATUS_ACTIVE,
|
|
14
|
+
ARROW_PROMPT,
|
|
15
|
+
)
|
|
16
|
+
|
|
8
17
|
console = Console()
|
|
9
18
|
|
|
10
19
|
|
|
@@ -126,41 +135,41 @@ def show_rules_interactive_menu() -> tuple[str, str]:
|
|
|
126
135
|
event.app.exit()
|
|
127
136
|
|
|
128
137
|
def get_formatted_menu():
|
|
129
|
-
lines = [("class:title", "Rules\n\n")]
|
|
138
|
+
lines = [("class:title", f"─── Rules {'─' * 35}\n\n")]
|
|
130
139
|
|
|
131
140
|
if not rules:
|
|
132
|
-
lines.append(("class:dim", "No rules defined yet.\n\n"))
|
|
141
|
+
lines.append(("class:dim", " No rules defined yet.\n\n"))
|
|
133
142
|
|
|
134
143
|
for i, (name, preview, is_action) in enumerate(menu_items):
|
|
135
144
|
is_selected = i == selected_index[0]
|
|
136
|
-
prefix = "
|
|
145
|
+
prefix = "▸ " if is_selected else " "
|
|
137
146
|
|
|
138
147
|
if is_action:
|
|
139
148
|
if is_selected:
|
|
140
|
-
lines.append(("class:action-selected", f"{prefix}{name}\n"))
|
|
149
|
+
lines.append(("class:action-selected", f" {prefix}{name}\n"))
|
|
141
150
|
else:
|
|
142
|
-
lines.append(("class:action", f"{prefix}{name}\n"))
|
|
151
|
+
lines.append(("class:action", f" {prefix}{name}\n"))
|
|
143
152
|
else:
|
|
144
153
|
if is_selected:
|
|
145
|
-
lines.append(("class:rule-selected", f"{prefix}{name}"))
|
|
146
|
-
lines.append(("class:preview-selected", f"
|
|
154
|
+
lines.append(("class:rule-selected", f" {prefix}{name}"))
|
|
155
|
+
lines.append(("class:preview-selected", f" {preview}\n"))
|
|
147
156
|
else:
|
|
148
|
-
lines.append(("class:rule", f"{prefix}{name}"))
|
|
149
|
-
lines.append(("class:preview", f"
|
|
157
|
+
lines.append(("class:rule", f" {prefix}{name}"))
|
|
158
|
+
lines.append(("class:preview", f" {preview}\n"))
|
|
150
159
|
|
|
151
|
-
lines.append(("class:hint", "\n
|
|
160
|
+
lines.append(("class:hint", f"\n{'─' * 45}\n ↑↓ navigate Enter view n new d delete q quit"))
|
|
152
161
|
return lines
|
|
153
162
|
|
|
154
163
|
style = Style.from_dict({
|
|
155
|
-
"title": "
|
|
156
|
-
"dim": "
|
|
157
|
-
"rule": "
|
|
158
|
-
"rule-selected": "
|
|
159
|
-
"action": "
|
|
160
|
-
"action-selected": "
|
|
161
|
-
"preview": "
|
|
162
|
-
"preview-selected": "
|
|
163
|
-
"hint": "
|
|
164
|
+
"title": f"{Colors.MUTED}",
|
|
165
|
+
"dim": f"{Colors.DIM}",
|
|
166
|
+
"rule": f"{Colors.PRIMARY}",
|
|
167
|
+
"rule-selected": f"{Colors.SUCCESS} bold",
|
|
168
|
+
"action": f"{Colors.WARNING}",
|
|
169
|
+
"action-selected": f"{Colors.WARNING} bold",
|
|
170
|
+
"preview": f"{Colors.DIM}",
|
|
171
|
+
"preview-selected": f"{Colors.SUCCESS}",
|
|
172
|
+
"hint": f"{Colors.DIM}",
|
|
164
173
|
})
|
|
165
174
|
|
|
166
175
|
height = len(menu_items) + 5 # items + title + hint + padding
|
|
@@ -200,22 +209,29 @@ def show_rule_details(name: str) -> None:
|
|
|
200
209
|
rule_file = rules_dir / f"{name}.md"
|
|
201
210
|
|
|
202
211
|
console.print()
|
|
203
|
-
console.print("[
|
|
212
|
+
console.print(f"[{Colors.MUTED}]{header(name, SEPARATOR_WIDTH)}[/{Colors.MUTED}]")
|
|
204
213
|
console.print()
|
|
205
214
|
|
|
206
215
|
if rule_file.exists():
|
|
207
216
|
try:
|
|
208
217
|
content = rule_file.read_text()
|
|
209
|
-
console.print(f"[
|
|
210
|
-
console.print(
|
|
211
|
-
|
|
218
|
+
console.print(f" [{Colors.DIM}]file[/{Colors.DIM}] {rule_file}")
|
|
219
|
+
console.print()
|
|
220
|
+
|
|
221
|
+
# Show content with indentation
|
|
222
|
+
for line in content.split('\n'):
|
|
223
|
+
if line.startswith('#'):
|
|
224
|
+
console.print(f" [{Colors.PRIMARY}]{line}[/{Colors.PRIMARY}]")
|
|
225
|
+
else:
|
|
226
|
+
console.print(f" [{Colors.MUTED}]{line}[/{Colors.MUTED}]")
|
|
227
|
+
|
|
212
228
|
except Exception as e:
|
|
213
|
-
console.print(f"[
|
|
229
|
+
console.print(f" [{Colors.ERROR}]Error reading rule: {e}[/{Colors.ERROR}]")
|
|
214
230
|
else:
|
|
215
|
-
console.print(f"[
|
|
231
|
+
console.print(f" [{Colors.WARNING}]Rule '{name}' not found[/{Colors.WARNING}]")
|
|
216
232
|
|
|
217
233
|
console.print()
|
|
218
|
-
console.print("[
|
|
234
|
+
console.print(f"[{Colors.MUTED}]{footer(SEPARATOR_WIDTH)}[/{Colors.MUTED}]")
|
|
219
235
|
|
|
220
236
|
|
|
221
237
|
def confirm_delete(rule_name: str) -> bool:
|
|
@@ -260,13 +276,14 @@ def chat_create_rule(client, renderer, model, max_iterations, render_with_interr
|
|
|
260
276
|
rules_dir = get_rules_dir()
|
|
261
277
|
|
|
262
278
|
console.print()
|
|
263
|
-
console.print("[
|
|
264
|
-
console.print(
|
|
265
|
-
console.print("[
|
|
279
|
+
console.print(f"[{Colors.MUTED}]{header('Create Rule', SEPARATOR_WIDTH)}[/{Colors.MUTED}]")
|
|
280
|
+
console.print()
|
|
281
|
+
console.print(f" [{Colors.DIM}]Describe your rule. AI will help write it.[/{Colors.DIM}]")
|
|
282
|
+
console.print(f" [{Colors.DIM}]Type 'done' to finish.[/{Colors.DIM}]")
|
|
266
283
|
console.print()
|
|
267
284
|
|
|
268
285
|
chat_style = Style.from_dict({
|
|
269
|
-
"prompt": "
|
|
286
|
+
"prompt": f"{Colors.PRIMARY} bold",
|
|
270
287
|
})
|
|
271
288
|
|
|
272
289
|
ps = PromptSession(style=chat_style)
|
|
@@ -379,7 +396,7 @@ def handle_rules(args: str, client, renderer, model, max_iterations, render_with
|
|
|
379
396
|
show_rule_details(rule_name)
|
|
380
397
|
# After viewing, show options
|
|
381
398
|
try:
|
|
382
|
-
console.print("[
|
|
399
|
+
console.print("[red]'d'[/red] delete • [dim]Enter back[/dim]", end="")
|
|
383
400
|
ps = PromptSession()
|
|
384
401
|
resp = ps.prompt(" ").strip().lower()
|
|
385
402
|
if resp == 'd':
|
|
@@ -30,7 +30,7 @@ def handle_session(
|
|
|
30
30
|
current_mode_ref: Reference to current_mode (list wrapper for mutation)
|
|
31
31
|
loaded_messages_ref: Reference to loaded_messages (list wrapper for mutation)
|
|
32
32
|
"""
|
|
33
|
-
from
|
|
33
|
+
from ....session_store import SessionStore
|
|
34
34
|
|
|
35
35
|
store = SessionStore(Path.cwd())
|
|
36
36
|
|
|
@@ -100,81 +100,214 @@ When this skill is invoked, you should:
|
|
|
100
100
|
|
|
101
101
|
def show_setup_menu() -> SetupMode | None:
|
|
102
102
|
"""Show the main setup menu and return selected mode."""
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
border_style="cyan",
|
|
108
|
-
))
|
|
109
|
-
console.print()
|
|
103
|
+
from prompt_toolkit import Application
|
|
104
|
+
from prompt_toolkit.key_binding import KeyBindings
|
|
105
|
+
from prompt_toolkit.layout import Layout, HSplit, Window, FormattedTextControl
|
|
106
|
+
from prompt_toolkit.styles import Style
|
|
110
107
|
|
|
111
108
|
options = [
|
|
112
109
|
(SetupMode.RULES, "Rules", "Define coding standards and guidelines for the agent"),
|
|
113
110
|
(SetupMode.AGENTS, "Agents", "Create custom agents with specialized prompts"),
|
|
114
111
|
(SetupMode.SKILLS, "Skills", "Add reusable skills invokable via slash commands"),
|
|
115
112
|
(SetupMode.VERIFIERS, "Verifiers", "Set up verification checks for your work"),
|
|
113
|
+
(None, "Quit", "Exit setup wizard"),
|
|
116
114
|
]
|
|
117
115
|
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
116
|
+
selected_index = [0]
|
|
117
|
+
result = [None]
|
|
118
|
+
|
|
119
|
+
kb = KeyBindings()
|
|
120
|
+
|
|
121
|
+
@kb.add("up")
|
|
122
|
+
@kb.add("k")
|
|
123
|
+
def move_up(event):
|
|
124
|
+
selected_index[0] = (selected_index[0] - 1) % len(options)
|
|
125
|
+
|
|
126
|
+
@kb.add("down")
|
|
127
|
+
@kb.add("j")
|
|
128
|
+
def move_down(event):
|
|
129
|
+
selected_index[0] = (selected_index[0] + 1) % len(options)
|
|
130
|
+
|
|
131
|
+
@kb.add("enter")
|
|
132
|
+
def select(event):
|
|
133
|
+
result[0] = options[selected_index[0]][0]
|
|
134
|
+
event.app.exit()
|
|
135
|
+
|
|
136
|
+
@kb.add("c-c")
|
|
137
|
+
@kb.add("escape")
|
|
138
|
+
@kb.add("q")
|
|
139
|
+
def cancel(event):
|
|
140
|
+
result[0] = None
|
|
141
|
+
event.app.exit()
|
|
142
|
+
|
|
143
|
+
def get_formatted_menu():
|
|
144
|
+
lines = [
|
|
145
|
+
("class:title", "Emdash Setup Wizard\n"),
|
|
146
|
+
("class:subtitle", "Configure your project's rules, agents, skills, and verifiers.\n\n"),
|
|
147
|
+
]
|
|
148
|
+
|
|
149
|
+
for i, (mode, name, desc) in enumerate(options):
|
|
150
|
+
is_selected = i == selected_index[0]
|
|
151
|
+
prefix = "❯ " if is_selected else " "
|
|
152
|
+
|
|
153
|
+
if mode is None: # Quit option
|
|
154
|
+
if is_selected:
|
|
155
|
+
lines.append(("class:quit-selected", f"{prefix}{name}\n"))
|
|
156
|
+
else:
|
|
157
|
+
lines.append(("class:quit", f"{prefix}{name}\n"))
|
|
158
|
+
else:
|
|
159
|
+
if is_selected:
|
|
160
|
+
lines.append(("class:item-selected", f"{prefix}{name}"))
|
|
161
|
+
lines.append(("class:desc-selected", f" {desc}\n"))
|
|
162
|
+
else:
|
|
163
|
+
lines.append(("class:item", f"{prefix}{name}"))
|
|
164
|
+
lines.append(("class:desc", f" {desc}\n"))
|
|
165
|
+
|
|
166
|
+
lines.append(("class:hint", "\n↑/↓ navigate • Enter select • q quit"))
|
|
167
|
+
return lines
|
|
168
|
+
|
|
169
|
+
style = Style.from_dict({
|
|
170
|
+
"title": "#00ccff bold",
|
|
171
|
+
"subtitle": "#888888",
|
|
172
|
+
"item": "#00ccff",
|
|
173
|
+
"item-selected": "#00cc66 bold",
|
|
174
|
+
"desc": "#666666",
|
|
175
|
+
"desc-selected": "#00cc66",
|
|
176
|
+
"quit": "#888888",
|
|
177
|
+
"quit-selected": "#ff6666 bold",
|
|
178
|
+
"hint": "#888888 italic",
|
|
179
|
+
})
|
|
180
|
+
|
|
181
|
+
layout = Layout(
|
|
182
|
+
HSplit([
|
|
183
|
+
Window(
|
|
184
|
+
FormattedTextControl(get_formatted_menu),
|
|
185
|
+
height=len(options) + 5,
|
|
186
|
+
),
|
|
187
|
+
])
|
|
188
|
+
)
|
|
189
|
+
|
|
190
|
+
app = Application(
|
|
191
|
+
layout=layout,
|
|
192
|
+
key_bindings=kb,
|
|
193
|
+
style=style,
|
|
194
|
+
full_screen=False,
|
|
195
|
+
)
|
|
122
196
|
|
|
123
|
-
console.print(" [dim]q[/dim]. Quit setup wizard")
|
|
124
197
|
console.print()
|
|
125
198
|
|
|
126
199
|
try:
|
|
127
|
-
|
|
128
|
-
choice = ps.prompt("Select [1-4, q]: ").strip().lower()
|
|
129
|
-
|
|
130
|
-
if choice == 'q' or choice == 'quit':
|
|
131
|
-
return None
|
|
132
|
-
elif choice == '1':
|
|
133
|
-
return SetupMode.RULES
|
|
134
|
-
elif choice == '2':
|
|
135
|
-
return SetupMode.AGENTS
|
|
136
|
-
elif choice == '3':
|
|
137
|
-
return SetupMode.SKILLS
|
|
138
|
-
elif choice == '4':
|
|
139
|
-
return SetupMode.VERIFIERS
|
|
140
|
-
else:
|
|
141
|
-
console.print("[yellow]Invalid choice[/yellow]")
|
|
142
|
-
return show_setup_menu()
|
|
200
|
+
app.run()
|
|
143
201
|
except (KeyboardInterrupt, EOFError):
|
|
144
|
-
|
|
202
|
+
result[0] = None
|
|
203
|
+
|
|
204
|
+
console.print()
|
|
205
|
+
return result[0]
|
|
145
206
|
|
|
146
207
|
|
|
147
208
|
def show_action_menu(mode: SetupMode) -> str | None:
|
|
148
209
|
"""Show action menu for a mode (add/edit/list/delete)."""
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
210
|
+
from prompt_toolkit import Application
|
|
211
|
+
from prompt_toolkit.key_binding import KeyBindings
|
|
212
|
+
from prompt_toolkit.layout import Layout, HSplit, Window, FormattedTextControl
|
|
213
|
+
from prompt_toolkit.styles import Style
|
|
214
|
+
|
|
215
|
+
options = [
|
|
216
|
+
("add", "Add new", f"Create a new {mode.value[:-1]}"),
|
|
217
|
+
("edit", "Edit existing", f"Modify an existing {mode.value[:-1]}"),
|
|
218
|
+
("list", "List all", f"Show all configured {mode.value}"),
|
|
219
|
+
("delete", "Delete", f"Remove a {mode.value[:-1]}"),
|
|
220
|
+
("back", "Back", "Return to main menu"),
|
|
221
|
+
]
|
|
222
|
+
|
|
223
|
+
selected_index = [0]
|
|
224
|
+
result = [None]
|
|
225
|
+
|
|
226
|
+
kb = KeyBindings()
|
|
227
|
+
|
|
228
|
+
@kb.add("up")
|
|
229
|
+
@kb.add("k")
|
|
230
|
+
def move_up(event):
|
|
231
|
+
selected_index[0] = (selected_index[0] - 1) % len(options)
|
|
232
|
+
|
|
233
|
+
@kb.add("down")
|
|
234
|
+
@kb.add("j")
|
|
235
|
+
def move_down(event):
|
|
236
|
+
selected_index[0] = (selected_index[0] + 1) % len(options)
|
|
237
|
+
|
|
238
|
+
@kb.add("enter")
|
|
239
|
+
def select(event):
|
|
240
|
+
result[0] = options[selected_index[0]][0]
|
|
241
|
+
event.app.exit()
|
|
242
|
+
|
|
243
|
+
@kb.add("c-c")
|
|
244
|
+
@kb.add("escape")
|
|
245
|
+
@kb.add("b")
|
|
246
|
+
def go_back(event):
|
|
247
|
+
result[0] = "back"
|
|
248
|
+
event.app.exit()
|
|
249
|
+
|
|
250
|
+
def get_formatted_menu():
|
|
251
|
+
lines = [
|
|
252
|
+
("class:title", f"{mode.value.title()} Configuration\n\n"),
|
|
253
|
+
]
|
|
254
|
+
|
|
255
|
+
for i, (action, name, desc) in enumerate(options):
|
|
256
|
+
is_selected = i == selected_index[0]
|
|
257
|
+
prefix = "❯ " if is_selected else " "
|
|
258
|
+
|
|
259
|
+
if action == "back":
|
|
260
|
+
if is_selected:
|
|
261
|
+
lines.append(("class:back-selected", f"{prefix}{name}\n"))
|
|
262
|
+
else:
|
|
263
|
+
lines.append(("class:back", f"{prefix}{name}\n"))
|
|
264
|
+
else:
|
|
265
|
+
if is_selected:
|
|
266
|
+
lines.append(("class:item-selected", f"{prefix}{name}"))
|
|
267
|
+
lines.append(("class:desc-selected", f" {desc}\n"))
|
|
268
|
+
else:
|
|
269
|
+
lines.append(("class:item", f"{prefix}{name}"))
|
|
270
|
+
lines.append(("class:desc", f" {desc}\n"))
|
|
271
|
+
|
|
272
|
+
lines.append(("class:hint", "\n↑/↓ navigate • Enter select • b back"))
|
|
273
|
+
return lines
|
|
274
|
+
|
|
275
|
+
style = Style.from_dict({
|
|
276
|
+
"title": "#00ccff bold",
|
|
277
|
+
"item": "#00ccff",
|
|
278
|
+
"item-selected": "#00cc66 bold",
|
|
279
|
+
"desc": "#666666",
|
|
280
|
+
"desc-selected": "#00cc66",
|
|
281
|
+
"back": "#888888",
|
|
282
|
+
"back-selected": "#ffcc00 bold",
|
|
283
|
+
"hint": "#888888 italic",
|
|
284
|
+
})
|
|
285
|
+
|
|
286
|
+
layout = Layout(
|
|
287
|
+
HSplit([
|
|
288
|
+
Window(
|
|
289
|
+
FormattedTextControl(get_formatted_menu),
|
|
290
|
+
height=len(options) + 4,
|
|
291
|
+
),
|
|
292
|
+
])
|
|
293
|
+
)
|
|
294
|
+
|
|
295
|
+
app = Application(
|
|
296
|
+
layout=layout,
|
|
297
|
+
key_bindings=kb,
|
|
298
|
+
style=style,
|
|
299
|
+
full_screen=False,
|
|
300
|
+
)
|
|
301
|
+
|
|
157
302
|
console.print()
|
|
158
303
|
|
|
159
304
|
try:
|
|
160
|
-
|
|
161
|
-
choice = ps.prompt("Select [1-4, b]: ").strip().lower()
|
|
162
|
-
|
|
163
|
-
if choice == 'b' or choice == 'back':
|
|
164
|
-
return 'back'
|
|
165
|
-
elif choice == '1':
|
|
166
|
-
return 'add'
|
|
167
|
-
elif choice == '2':
|
|
168
|
-
return 'edit'
|
|
169
|
-
elif choice == '3':
|
|
170
|
-
return 'list'
|
|
171
|
-
elif choice == '4':
|
|
172
|
-
return 'delete'
|
|
173
|
-
else:
|
|
174
|
-
console.print("[yellow]Invalid choice[/yellow]")
|
|
175
|
-
return show_action_menu(mode)
|
|
305
|
+
app.run()
|
|
176
306
|
except (KeyboardInterrupt, EOFError):
|
|
177
|
-
|
|
307
|
+
result[0] = "back"
|
|
308
|
+
|
|
309
|
+
console.print()
|
|
310
|
+
return result[0]
|
|
178
311
|
|
|
179
312
|
|
|
180
313
|
def get_existing_items(mode: SetupMode) -> list[str]:
|
|
@@ -26,7 +26,7 @@ def list_skills() -> list[dict]:
|
|
|
26
26
|
"""List all skills (both user and built-in).
|
|
27
27
|
|
|
28
28
|
Returns:
|
|
29
|
-
List of dicts with name, description, user_invocable, is_builtin, file_path
|
|
29
|
+
List of dicts with name, description, user_invocable, is_builtin, file_path, scripts
|
|
30
30
|
"""
|
|
31
31
|
from emdash_core.agent.skills import SkillRegistry
|
|
32
32
|
|
|
@@ -44,6 +44,7 @@ def list_skills() -> list[dict]:
|
|
|
44
44
|
"user_invocable": skill.user_invocable,
|
|
45
45
|
"is_builtin": getattr(skill, "_builtin", False),
|
|
46
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 [],
|
|
47
48
|
})
|
|
48
49
|
|
|
49
50
|
return skills
|
|
@@ -71,7 +72,8 @@ def show_skills_interactive_menu() -> tuple[str, str]:
|
|
|
71
72
|
|
|
72
73
|
for skill in skills:
|
|
73
74
|
builtin_marker = " [built-in]" if skill["is_builtin"] else ""
|
|
74
|
-
|
|
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))
|
|
75
77
|
|
|
76
78
|
# Add action items at the bottom
|
|
77
79
|
menu_items.append(("+ Create New Skill", "Create a new skill with AI assistance", False, True))
|
|
@@ -220,6 +222,15 @@ def show_skill_details(name: str) -> None:
|
|
|
220
222
|
console.print(f"[bold]Description:[/bold] {skill.description}")
|
|
221
223
|
console.print(f"[bold]User Invocable:[/bold] {invocable}")
|
|
222
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
|
+
|
|
223
234
|
console.print(f"[bold]File:[/bold] {skill.file_path}\n")
|
|
224
235
|
console.print("[bold]Instructions:[/bold]")
|
|
225
236
|
console.print(Panel(skill.instructions, border_style="dim"))
|
|
@@ -332,6 +343,10 @@ tools: [tool1, tool2]
|
|
|
332
343
|
# Skill Title
|
|
333
344
|
|
|
334
345
|
Instructions for the skill...
|
|
346
|
+
|
|
347
|
+
## Scripts (optional)
|
|
348
|
+
|
|
349
|
+
If scripts are included, document them here.
|
|
335
350
|
```
|
|
336
351
|
|
|
337
352
|
**Frontmatter fields:**
|
|
@@ -340,9 +355,32 @@ Instructions for the skill...
|
|
|
340
355
|
- `user_invocable`: Whether skill can be invoked with /name (true/false)
|
|
341
356
|
- `tools`: List of tools this skill needs (optional)
|
|
342
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
|
+
|
|
343
373
|
**My request:** {user_input}
|
|
344
374
|
|
|
345
|
-
Please help me create a skill. Ask me questions if needed to understand what I want
|
|
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."""
|
|
346
384
|
stream = client.agent_chat_stream(
|
|
347
385
|
message=message_with_context,
|
|
348
386
|
model=model,
|
|
@@ -425,7 +463,7 @@ def handle_skills(args: str, client, renderer, model, max_iterations, render_wit
|
|
|
425
463
|
if is_builtin:
|
|
426
464
|
console.print("[dim]Enter to go back[/dim]", end="")
|
|
427
465
|
else:
|
|
428
|
-
console.print("[
|
|
466
|
+
console.print("[red]'d'[/red] delete • [dim]Enter back[/dim]", end="")
|
|
429
467
|
ps = PromptSession()
|
|
430
468
|
resp = ps.prompt(" ").strip().lower()
|
|
431
469
|
if resp == 'd' and not is_builtin:
|