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,715 @@
|
|
|
1
|
+
"""Setup wizard for configuring rules, agents, skills, and verifiers.
|
|
2
|
+
|
|
3
|
+
This is a dedicated flow separate from the main agent interaction,
|
|
4
|
+
specialized for configuration management with its own permissions.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import json
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
from enum import Enum
|
|
10
|
+
|
|
11
|
+
from rich.console import Console
|
|
12
|
+
from rich.panel import Panel
|
|
13
|
+
from rich.table import Table
|
|
14
|
+
from prompt_toolkit import PromptSession
|
|
15
|
+
from prompt_toolkit.history import InMemoryHistory
|
|
16
|
+
|
|
17
|
+
console = Console()
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class SetupMode(Enum):
|
|
21
|
+
"""Available setup modes."""
|
|
22
|
+
RULES = "rules"
|
|
23
|
+
AGENTS = "agents"
|
|
24
|
+
SKILLS = "skills"
|
|
25
|
+
VERIFIERS = "verifiers"
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
# Templates for each config type
|
|
29
|
+
TEMPLATES = {
|
|
30
|
+
SetupMode.RULES: {
|
|
31
|
+
"file": ".emdash/rules.md",
|
|
32
|
+
"example": """# Project Rules
|
|
33
|
+
|
|
34
|
+
## Code Style
|
|
35
|
+
- Use TypeScript for all new code
|
|
36
|
+
- Follow the existing patterns in the codebase
|
|
37
|
+
|
|
38
|
+
## Testing
|
|
39
|
+
- Write tests for all new features
|
|
40
|
+
- Maintain >80% code coverage
|
|
41
|
+
""",
|
|
42
|
+
"description": "Rules guide the agent's behavior and coding standards",
|
|
43
|
+
},
|
|
44
|
+
SetupMode.AGENTS: {
|
|
45
|
+
"dir": ".emdash/agents",
|
|
46
|
+
"example": """---
|
|
47
|
+
name: {name}
|
|
48
|
+
description: {description}
|
|
49
|
+
tools:
|
|
50
|
+
- read_file
|
|
51
|
+
- edit_file
|
|
52
|
+
- bash
|
|
53
|
+
---
|
|
54
|
+
|
|
55
|
+
You are a specialized agent for {purpose}.
|
|
56
|
+
|
|
57
|
+
## Your Role
|
|
58
|
+
{role_description}
|
|
59
|
+
|
|
60
|
+
## Guidelines
|
|
61
|
+
- Follow project conventions
|
|
62
|
+
- Be concise and accurate
|
|
63
|
+
""",
|
|
64
|
+
"description": "Custom agents with specialized system prompts and tools",
|
|
65
|
+
},
|
|
66
|
+
SetupMode.SKILLS: {
|
|
67
|
+
"dir": ".emdash/skills",
|
|
68
|
+
"example": """---
|
|
69
|
+
name: {name}
|
|
70
|
+
description: {description}
|
|
71
|
+
---
|
|
72
|
+
|
|
73
|
+
## Skill: {name}
|
|
74
|
+
|
|
75
|
+
When this skill is invoked, you should:
|
|
76
|
+
|
|
77
|
+
1. {step1}
|
|
78
|
+
2. {step2}
|
|
79
|
+
3. {step3}
|
|
80
|
+
|
|
81
|
+
## Output Format
|
|
82
|
+
{output_format}
|
|
83
|
+
""",
|
|
84
|
+
"description": "Reusable skills that can be invoked with /skill-name",
|
|
85
|
+
},
|
|
86
|
+
SetupMode.VERIFIERS: {
|
|
87
|
+
"file": ".emdash/verifiers.json",
|
|
88
|
+
"example": {
|
|
89
|
+
"verifiers": [
|
|
90
|
+
{"type": "command", "name": "tests", "command": "npm test", "timeout": 120},
|
|
91
|
+
{"type": "command", "name": "lint", "command": "npm run lint"},
|
|
92
|
+
{"type": "llm", "name": "review", "prompt": "Review for bugs and issues", "model": "haiku"}
|
|
93
|
+
],
|
|
94
|
+
"max_retries": 3
|
|
95
|
+
},
|
|
96
|
+
"description": "Verification checks (commands or LLM reviews) to validate work",
|
|
97
|
+
},
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def show_setup_menu() -> SetupMode | None:
|
|
102
|
+
"""Show the main setup menu and return selected mode."""
|
|
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
|
|
107
|
+
|
|
108
|
+
options = [
|
|
109
|
+
(SetupMode.RULES, "Rules", "Define coding standards and guidelines for the agent"),
|
|
110
|
+
(SetupMode.AGENTS, "Agents", "Create custom agents with specialized prompts"),
|
|
111
|
+
(SetupMode.SKILLS, "Skills", "Add reusable skills invokable via slash commands"),
|
|
112
|
+
(SetupMode.VERIFIERS, "Verifiers", "Set up verification checks for your work"),
|
|
113
|
+
(None, "Quit", "Exit setup wizard"),
|
|
114
|
+
]
|
|
115
|
+
|
|
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
|
+
)
|
|
196
|
+
|
|
197
|
+
console.print()
|
|
198
|
+
|
|
199
|
+
try:
|
|
200
|
+
app.run()
|
|
201
|
+
except (KeyboardInterrupt, EOFError):
|
|
202
|
+
result[0] = None
|
|
203
|
+
|
|
204
|
+
console.print()
|
|
205
|
+
return result[0]
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
def show_action_menu(mode: SetupMode) -> str | None:
|
|
209
|
+
"""Show action menu for a mode (add/edit/list/delete)."""
|
|
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
|
+
|
|
302
|
+
console.print()
|
|
303
|
+
|
|
304
|
+
try:
|
|
305
|
+
app.run()
|
|
306
|
+
except (KeyboardInterrupt, EOFError):
|
|
307
|
+
result[0] = "back"
|
|
308
|
+
|
|
309
|
+
console.print()
|
|
310
|
+
return result[0]
|
|
311
|
+
|
|
312
|
+
|
|
313
|
+
def get_existing_items(mode: SetupMode) -> list[str]:
|
|
314
|
+
"""Get list of existing items for a mode."""
|
|
315
|
+
cwd = Path.cwd()
|
|
316
|
+
|
|
317
|
+
if mode == SetupMode.RULES:
|
|
318
|
+
rules_file = cwd / ".emdash" / "rules.md"
|
|
319
|
+
return ["rules.md"] if rules_file.exists() else []
|
|
320
|
+
|
|
321
|
+
elif mode == SetupMode.AGENTS:
|
|
322
|
+
agents_dir = cwd / ".emdash" / "agents"
|
|
323
|
+
if agents_dir.exists():
|
|
324
|
+
return [f.stem for f in agents_dir.glob("*.md")]
|
|
325
|
+
return []
|
|
326
|
+
|
|
327
|
+
elif mode == SetupMode.SKILLS:
|
|
328
|
+
skills_dir = cwd / ".emdash" / "skills"
|
|
329
|
+
if skills_dir.exists():
|
|
330
|
+
return [f.stem for f in skills_dir.glob("*.md")]
|
|
331
|
+
return []
|
|
332
|
+
|
|
333
|
+
elif mode == SetupMode.VERIFIERS:
|
|
334
|
+
verifiers_file = cwd / ".emdash" / "verifiers.json"
|
|
335
|
+
if verifiers_file.exists():
|
|
336
|
+
try:
|
|
337
|
+
data = json.loads(verifiers_file.read_text())
|
|
338
|
+
return [v.get("name", "unnamed") for v in data.get("verifiers", [])]
|
|
339
|
+
except Exception:
|
|
340
|
+
pass
|
|
341
|
+
return []
|
|
342
|
+
|
|
343
|
+
return []
|
|
344
|
+
|
|
345
|
+
|
|
346
|
+
def list_items(mode: SetupMode) -> None:
|
|
347
|
+
"""List existing items for a mode."""
|
|
348
|
+
items = get_existing_items(mode)
|
|
349
|
+
|
|
350
|
+
console.print()
|
|
351
|
+
if not items:
|
|
352
|
+
console.print(f"[yellow]No {mode.value} configured yet.[/yellow]")
|
|
353
|
+
else:
|
|
354
|
+
console.print(f"[bold]Existing {mode.value}:[/bold]")
|
|
355
|
+
for item in items:
|
|
356
|
+
console.print(f" • {item}")
|
|
357
|
+
console.print()
|
|
358
|
+
|
|
359
|
+
|
|
360
|
+
def run_ai_assisted_setup(
|
|
361
|
+
mode: SetupMode,
|
|
362
|
+
action: str,
|
|
363
|
+
client,
|
|
364
|
+
renderer,
|
|
365
|
+
model: str,
|
|
366
|
+
item_name: str | None = None,
|
|
367
|
+
) -> bool:
|
|
368
|
+
"""Run AI-assisted setup flow for creating/editing config.
|
|
369
|
+
|
|
370
|
+
Args:
|
|
371
|
+
mode: The setup mode (rules, agents, skills, verifiers)
|
|
372
|
+
action: The action (add, edit)
|
|
373
|
+
client: EmDash client
|
|
374
|
+
renderer: SSE renderer
|
|
375
|
+
model: Model to use
|
|
376
|
+
item_name: Name of item to edit (for edit action)
|
|
377
|
+
|
|
378
|
+
Returns:
|
|
379
|
+
True if successful, False otherwise
|
|
380
|
+
"""
|
|
381
|
+
cwd = Path.cwd()
|
|
382
|
+
template = TEMPLATES[mode]
|
|
383
|
+
|
|
384
|
+
# Build the system context for the AI
|
|
385
|
+
if mode == SetupMode.RULES:
|
|
386
|
+
target_file = cwd / ".emdash" / "rules.md"
|
|
387
|
+
current_content = target_file.read_text() if target_file.exists() else None
|
|
388
|
+
file_info = f"File: `{target_file}`"
|
|
389
|
+
|
|
390
|
+
elif mode == SetupMode.AGENTS:
|
|
391
|
+
if action == 'add':
|
|
392
|
+
# Prompt for agent name
|
|
393
|
+
ps = PromptSession()
|
|
394
|
+
console.print()
|
|
395
|
+
item_name = ps.prompt("Agent name: ").strip()
|
|
396
|
+
if not item_name:
|
|
397
|
+
console.print("[yellow]Agent name is required[/yellow]")
|
|
398
|
+
return False
|
|
399
|
+
|
|
400
|
+
target_file = cwd / ".emdash" / "agents" / f"{item_name}.md"
|
|
401
|
+
current_content = target_file.read_text() if target_file.exists() else None
|
|
402
|
+
file_info = f"File: `{target_file}`"
|
|
403
|
+
|
|
404
|
+
elif mode == SetupMode.SKILLS:
|
|
405
|
+
if action == 'add':
|
|
406
|
+
ps = PromptSession()
|
|
407
|
+
console.print()
|
|
408
|
+
item_name = ps.prompt("Skill name: ").strip()
|
|
409
|
+
if not item_name:
|
|
410
|
+
console.print("[yellow]Skill name is required[/yellow]")
|
|
411
|
+
return False
|
|
412
|
+
|
|
413
|
+
target_file = cwd / ".emdash" / "skills" / f"{item_name}.md"
|
|
414
|
+
current_content = target_file.read_text() if target_file.exists() else None
|
|
415
|
+
file_info = f"File: `{target_file}`"
|
|
416
|
+
|
|
417
|
+
elif mode == SetupMode.VERIFIERS:
|
|
418
|
+
target_file = cwd / ".emdash" / "verifiers.json"
|
|
419
|
+
current_content = target_file.read_text() if target_file.exists() else None
|
|
420
|
+
file_info = f"File: `{target_file}`"
|
|
421
|
+
|
|
422
|
+
# Build initial message for AI
|
|
423
|
+
example = template["example"]
|
|
424
|
+
if isinstance(example, dict):
|
|
425
|
+
example = json.dumps(example, indent=2)
|
|
426
|
+
|
|
427
|
+
if action == 'add' and current_content:
|
|
428
|
+
action_desc = "add to or modify"
|
|
429
|
+
elif action == 'add':
|
|
430
|
+
action_desc = "create"
|
|
431
|
+
else:
|
|
432
|
+
action_desc = "modify"
|
|
433
|
+
|
|
434
|
+
initial_message = f"""I want to {action_desc} my {mode.value} configuration.
|
|
435
|
+
|
|
436
|
+
{file_info}
|
|
437
|
+
|
|
438
|
+
**What {mode.value} do:** {template['description']}
|
|
439
|
+
|
|
440
|
+
**Example format:**
|
|
441
|
+
```
|
|
442
|
+
{example}
|
|
443
|
+
```
|
|
444
|
+
"""
|
|
445
|
+
|
|
446
|
+
if current_content:
|
|
447
|
+
initial_message += f"""
|
|
448
|
+
**Current content:**
|
|
449
|
+
```
|
|
450
|
+
{current_content}
|
|
451
|
+
```
|
|
452
|
+
"""
|
|
453
|
+
|
|
454
|
+
initial_message += """
|
|
455
|
+
Help me configure this. Ask me what I want to achieve, then create/update the file using the Edit or Write tool.
|
|
456
|
+
|
|
457
|
+
IMPORTANT: You have permission to write to the .emdash/ directory. Use the Write tool to create the file."""
|
|
458
|
+
|
|
459
|
+
# Run interactive AI session
|
|
460
|
+
console.print()
|
|
461
|
+
console.print(Panel(
|
|
462
|
+
f"[bold cyan]AI-Assisted {mode.value.title()} Setup[/bold cyan]\n\n"
|
|
463
|
+
f"Chat with the AI to configure your {mode.value}.\n"
|
|
464
|
+
"Type [bold]done[/bold] when finished, [bold]cancel[/bold] to abort.",
|
|
465
|
+
border_style="cyan",
|
|
466
|
+
))
|
|
467
|
+
console.print()
|
|
468
|
+
|
|
469
|
+
# Start the AI conversation
|
|
470
|
+
session_id = None
|
|
471
|
+
ps = PromptSession(history=InMemoryHistory())
|
|
472
|
+
|
|
473
|
+
# Send initial message
|
|
474
|
+
try:
|
|
475
|
+
stream = client.agent_chat_stream(
|
|
476
|
+
message=initial_message,
|
|
477
|
+
model=model,
|
|
478
|
+
max_iterations=10,
|
|
479
|
+
options={"mode": "code"},
|
|
480
|
+
)
|
|
481
|
+
|
|
482
|
+
# Import render function
|
|
483
|
+
from . import render_with_interrupt
|
|
484
|
+
result = render_with_interrupt(renderer, stream)
|
|
485
|
+
|
|
486
|
+
if result and result.get("session_id"):
|
|
487
|
+
session_id = result["session_id"]
|
|
488
|
+
|
|
489
|
+
except Exception as e:
|
|
490
|
+
console.print(f"[red]Error: {e}[/red]")
|
|
491
|
+
return False
|
|
492
|
+
|
|
493
|
+
# Interactive loop
|
|
494
|
+
while True:
|
|
495
|
+
try:
|
|
496
|
+
console.print()
|
|
497
|
+
user_input = ps.prompt("[setup] > ").strip()
|
|
498
|
+
|
|
499
|
+
if not user_input:
|
|
500
|
+
continue
|
|
501
|
+
|
|
502
|
+
if user_input.lower() in ('done', 'finish', 'exit'):
|
|
503
|
+
console.print()
|
|
504
|
+
console.print("[green]Setup complete![/green]")
|
|
505
|
+
return True
|
|
506
|
+
|
|
507
|
+
if user_input.lower() in ('cancel', 'abort', 'quit'):
|
|
508
|
+
console.print()
|
|
509
|
+
console.print("[yellow]Setup cancelled.[/yellow]")
|
|
510
|
+
return False
|
|
511
|
+
|
|
512
|
+
# Continue the conversation
|
|
513
|
+
if session_id:
|
|
514
|
+
stream = client.agent_continue_stream(session_id, user_input)
|
|
515
|
+
else:
|
|
516
|
+
stream = client.agent_chat_stream(
|
|
517
|
+
message=user_input,
|
|
518
|
+
model=model,
|
|
519
|
+
max_iterations=10,
|
|
520
|
+
options={"mode": "code"},
|
|
521
|
+
)
|
|
522
|
+
|
|
523
|
+
result = render_with_interrupt(renderer, stream)
|
|
524
|
+
|
|
525
|
+
if result and result.get("session_id"):
|
|
526
|
+
session_id = result["session_id"]
|
|
527
|
+
|
|
528
|
+
except KeyboardInterrupt:
|
|
529
|
+
console.print()
|
|
530
|
+
console.print("[yellow]Setup interrupted.[/yellow]")
|
|
531
|
+
return False
|
|
532
|
+
except EOFError:
|
|
533
|
+
break
|
|
534
|
+
|
|
535
|
+
return True
|
|
536
|
+
|
|
537
|
+
|
|
538
|
+
def select_item_to_edit(mode: SetupMode) -> str | None:
|
|
539
|
+
"""Let user select an item to edit."""
|
|
540
|
+
items = get_existing_items(mode)
|
|
541
|
+
|
|
542
|
+
if not items:
|
|
543
|
+
console.print(f"[yellow]No {mode.value} to edit. Create one first.[/yellow]")
|
|
544
|
+
return None
|
|
545
|
+
|
|
546
|
+
console.print()
|
|
547
|
+
console.print(f"[bold]Select {mode.value[:-1]} to edit:[/bold]")
|
|
548
|
+
for i, item in enumerate(items, 1):
|
|
549
|
+
console.print(f" [cyan]{i}[/cyan]. {item}")
|
|
550
|
+
console.print()
|
|
551
|
+
|
|
552
|
+
try:
|
|
553
|
+
ps = PromptSession()
|
|
554
|
+
choice = ps.prompt(f"Select [1-{len(items)}]: ").strip()
|
|
555
|
+
|
|
556
|
+
idx = int(choice) - 1
|
|
557
|
+
if 0 <= idx < len(items):
|
|
558
|
+
return items[idx]
|
|
559
|
+
else:
|
|
560
|
+
console.print("[yellow]Invalid choice[/yellow]")
|
|
561
|
+
return None
|
|
562
|
+
except (ValueError, KeyboardInterrupt, EOFError):
|
|
563
|
+
return None
|
|
564
|
+
|
|
565
|
+
|
|
566
|
+
def select_item_to_delete(mode: SetupMode) -> str | None:
|
|
567
|
+
"""Let user select an item to delete."""
|
|
568
|
+
items = get_existing_items(mode)
|
|
569
|
+
|
|
570
|
+
if not items:
|
|
571
|
+
console.print(f"[yellow]No {mode.value} to delete.[/yellow]")
|
|
572
|
+
return None
|
|
573
|
+
|
|
574
|
+
console.print()
|
|
575
|
+
console.print(f"[bold]Select {mode.value[:-1]} to delete:[/bold]")
|
|
576
|
+
for i, item in enumerate(items, 1):
|
|
577
|
+
console.print(f" [cyan]{i}[/cyan]. {item}")
|
|
578
|
+
console.print()
|
|
579
|
+
|
|
580
|
+
try:
|
|
581
|
+
ps = PromptSession()
|
|
582
|
+
choice = ps.prompt(f"Select [1-{len(items)}]: ").strip()
|
|
583
|
+
|
|
584
|
+
idx = int(choice) - 1
|
|
585
|
+
if 0 <= idx < len(items):
|
|
586
|
+
return items[idx]
|
|
587
|
+
else:
|
|
588
|
+
console.print("[yellow]Invalid choice[/yellow]")
|
|
589
|
+
return None
|
|
590
|
+
except (ValueError, KeyboardInterrupt, EOFError):
|
|
591
|
+
return None
|
|
592
|
+
|
|
593
|
+
|
|
594
|
+
def delete_item(mode: SetupMode, item_name: str) -> bool:
|
|
595
|
+
"""Delete an item."""
|
|
596
|
+
cwd = Path.cwd()
|
|
597
|
+
|
|
598
|
+
try:
|
|
599
|
+
if mode == SetupMode.RULES:
|
|
600
|
+
target = cwd / ".emdash" / "rules.md"
|
|
601
|
+
if target.exists():
|
|
602
|
+
target.unlink()
|
|
603
|
+
console.print(f"[green]Deleted rules.md[/green]")
|
|
604
|
+
return True
|
|
605
|
+
|
|
606
|
+
elif mode == SetupMode.AGENTS:
|
|
607
|
+
target = cwd / ".emdash" / "agents" / f"{item_name}.md"
|
|
608
|
+
if target.exists():
|
|
609
|
+
target.unlink()
|
|
610
|
+
console.print(f"[green]Deleted agent: {item_name}[/green]")
|
|
611
|
+
return True
|
|
612
|
+
|
|
613
|
+
elif mode == SetupMode.SKILLS:
|
|
614
|
+
target = cwd / ".emdash" / "skills" / f"{item_name}.md"
|
|
615
|
+
if target.exists():
|
|
616
|
+
target.unlink()
|
|
617
|
+
console.print(f"[green]Deleted skill: {item_name}[/green]")
|
|
618
|
+
return True
|
|
619
|
+
|
|
620
|
+
elif mode == SetupMode.VERIFIERS:
|
|
621
|
+
target = cwd / ".emdash" / "verifiers.json"
|
|
622
|
+
if target.exists():
|
|
623
|
+
data = json.loads(target.read_text())
|
|
624
|
+
data["verifiers"] = [
|
|
625
|
+
v for v in data.get("verifiers", [])
|
|
626
|
+
if v.get("name") != item_name
|
|
627
|
+
]
|
|
628
|
+
target.write_text(json.dumps(data, indent=2))
|
|
629
|
+
console.print(f"[green]Deleted verifier: {item_name}[/green]")
|
|
630
|
+
return True
|
|
631
|
+
|
|
632
|
+
console.print(f"[yellow]Item not found: {item_name}[/yellow]")
|
|
633
|
+
return False
|
|
634
|
+
|
|
635
|
+
except Exception as e:
|
|
636
|
+
console.print(f"[red]Error deleting: {e}[/red]")
|
|
637
|
+
return False
|
|
638
|
+
|
|
639
|
+
|
|
640
|
+
def render_with_interrupt(renderer, stream) -> dict:
|
|
641
|
+
"""Render stream with interrupt support.
|
|
642
|
+
|
|
643
|
+
This is a simplified version for the setup wizard.
|
|
644
|
+
"""
|
|
645
|
+
import threading
|
|
646
|
+
from ....keyboard import KeyListener
|
|
647
|
+
|
|
648
|
+
interrupt_event = threading.Event()
|
|
649
|
+
|
|
650
|
+
def on_escape():
|
|
651
|
+
interrupt_event.set()
|
|
652
|
+
|
|
653
|
+
listener = KeyListener(on_escape)
|
|
654
|
+
|
|
655
|
+
try:
|
|
656
|
+
listener.start()
|
|
657
|
+
result = renderer.render_stream(stream, interrupt_event=interrupt_event)
|
|
658
|
+
return result
|
|
659
|
+
finally:
|
|
660
|
+
listener.stop()
|
|
661
|
+
|
|
662
|
+
|
|
663
|
+
def handle_setup(
|
|
664
|
+
args: str,
|
|
665
|
+
client,
|
|
666
|
+
renderer,
|
|
667
|
+
model: str,
|
|
668
|
+
) -> None:
|
|
669
|
+
"""Handle /setup command - open the setup wizard.
|
|
670
|
+
|
|
671
|
+
Args:
|
|
672
|
+
args: Command arguments (unused currently)
|
|
673
|
+
client: EmDash client for AI interactions
|
|
674
|
+
renderer: SSE renderer
|
|
675
|
+
model: Model to use for AI assistance
|
|
676
|
+
"""
|
|
677
|
+
console.print()
|
|
678
|
+
console.print("[bold cyan]━━━ Setup Wizard ━━━[/bold cyan]")
|
|
679
|
+
|
|
680
|
+
while True:
|
|
681
|
+
# Show main menu
|
|
682
|
+
mode = show_setup_menu()
|
|
683
|
+
if mode is None:
|
|
684
|
+
console.print()
|
|
685
|
+
console.print("[dim]Exiting setup wizard.[/dim]")
|
|
686
|
+
break
|
|
687
|
+
|
|
688
|
+
# Show action menu
|
|
689
|
+
while True:
|
|
690
|
+
action = show_action_menu(mode)
|
|
691
|
+
|
|
692
|
+
if action is None or action == 'back':
|
|
693
|
+
break
|
|
694
|
+
|
|
695
|
+
if action == 'list':
|
|
696
|
+
list_items(mode)
|
|
697
|
+
|
|
698
|
+
elif action == 'add':
|
|
699
|
+
run_ai_assisted_setup(mode, 'add', client, renderer, model)
|
|
700
|
+
|
|
701
|
+
elif action == 'edit':
|
|
702
|
+
item = select_item_to_edit(mode)
|
|
703
|
+
if item:
|
|
704
|
+
run_ai_assisted_setup(mode, 'edit', client, renderer, model, item)
|
|
705
|
+
|
|
706
|
+
elif action == 'delete':
|
|
707
|
+
item = select_item_to_delete(mode)
|
|
708
|
+
if item:
|
|
709
|
+
# Confirm deletion
|
|
710
|
+
ps = PromptSession()
|
|
711
|
+
confirm = ps.prompt(f"Delete '{item}'? [y/N]: ").strip().lower()
|
|
712
|
+
if confirm in ('y', 'yes'):
|
|
713
|
+
delete_item(mode, item)
|
|
714
|
+
|
|
715
|
+
console.print()
|