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,449 @@
|
|
|
1
|
+
"""Handler for /agents command."""
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
import subprocess
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
|
|
7
|
+
from rich.console import Console
|
|
8
|
+
from rich.panel import Panel
|
|
9
|
+
|
|
10
|
+
from ..menus import show_agents_interactive_menu, prompt_agent_name, confirm_delete
|
|
11
|
+
from ....design import (
|
|
12
|
+
Colors,
|
|
13
|
+
header,
|
|
14
|
+
footer,
|
|
15
|
+
SEPARATOR_WIDTH,
|
|
16
|
+
STATUS_ACTIVE,
|
|
17
|
+
ARROW_PROMPT,
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
console = Console()
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def create_agent(name: str) -> bool:
|
|
24
|
+
"""Create a new agent with the given name."""
|
|
25
|
+
agents_dir = Path.cwd() / ".emdash" / "agents"
|
|
26
|
+
agent_file = agents_dir / f"{name}.md"
|
|
27
|
+
|
|
28
|
+
if agent_file.exists():
|
|
29
|
+
console.print(f"[yellow]Agent '{name}' already exists[/yellow]")
|
|
30
|
+
return False
|
|
31
|
+
|
|
32
|
+
agents_dir.mkdir(parents=True, exist_ok=True)
|
|
33
|
+
|
|
34
|
+
template = f'''---
|
|
35
|
+
description: Custom agent for specific tasks
|
|
36
|
+
tools: [grep, glob, read_file, semantic_search]
|
|
37
|
+
# rules: [typescript, security] # Optional: reference rules from .emdash/rules/
|
|
38
|
+
# skills: [code-review] # Optional: reference skills from .emdash/skills/
|
|
39
|
+
# verifiers: [eslint] # Optional: reference verifiers from .emdash/verifiers.json
|
|
40
|
+
---
|
|
41
|
+
|
|
42
|
+
# System Prompt
|
|
43
|
+
|
|
44
|
+
You are a specialized assistant for {name.replace("-", " ")} tasks.
|
|
45
|
+
|
|
46
|
+
## Your Mission
|
|
47
|
+
|
|
48
|
+
Describe what this agent should accomplish:
|
|
49
|
+
- Task 1
|
|
50
|
+
- Task 2
|
|
51
|
+
- Task 3
|
|
52
|
+
|
|
53
|
+
## Approach
|
|
54
|
+
|
|
55
|
+
1. **Step One**
|
|
56
|
+
- Details about the first step
|
|
57
|
+
|
|
58
|
+
2. **Step Two**
|
|
59
|
+
- Details about the second step
|
|
60
|
+
|
|
61
|
+
## Output Format
|
|
62
|
+
|
|
63
|
+
Describe how the agent should format its responses.
|
|
64
|
+
'''
|
|
65
|
+
agent_file.write_text(template)
|
|
66
|
+
console.print()
|
|
67
|
+
console.print(f" [{Colors.SUCCESS}]{STATUS_ACTIVE}[/{Colors.SUCCESS}] [{Colors.TEXT}]created:[/{Colors.TEXT}] {name}")
|
|
68
|
+
console.print(f" [{Colors.DIM}]{agent_file}[/{Colors.DIM}]")
|
|
69
|
+
console.print()
|
|
70
|
+
return True
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def show_agent_details(name: str) -> None:
|
|
74
|
+
"""Show detailed view of an agent."""
|
|
75
|
+
from emdash_core.agent.toolkits import get_custom_agent
|
|
76
|
+
|
|
77
|
+
builtin_agents = ["Explore", "Plan"]
|
|
78
|
+
|
|
79
|
+
console.print()
|
|
80
|
+
console.print(f"[{Colors.MUTED}]{header(name, SEPARATOR_WIDTH)}[/{Colors.MUTED}]")
|
|
81
|
+
console.print()
|
|
82
|
+
|
|
83
|
+
if name in builtin_agents:
|
|
84
|
+
console.print(f" [{Colors.DIM}]type[/{Colors.DIM}] [{Colors.MUTED}]built-in[/{Colors.MUTED}]")
|
|
85
|
+
if name == "Explore":
|
|
86
|
+
console.print(f" [{Colors.DIM}]desc[/{Colors.DIM}] Fast codebase exploration (read-only)")
|
|
87
|
+
console.print(f" [{Colors.DIM}]tools[/{Colors.DIM}] glob, grep, read_file, list_files, semantic_search")
|
|
88
|
+
elif name == "Plan":
|
|
89
|
+
console.print(f" [{Colors.DIM}]desc[/{Colors.DIM}] Design implementation plans")
|
|
90
|
+
console.print(f" [{Colors.DIM}]tools[/{Colors.DIM}] glob, grep, read_file, list_files, semantic_search")
|
|
91
|
+
console.print()
|
|
92
|
+
console.print(f" [{Colors.DIM}]Built-in agents cannot be edited or deleted.[/{Colors.DIM}]")
|
|
93
|
+
else:
|
|
94
|
+
agent = get_custom_agent(name, Path.cwd())
|
|
95
|
+
if agent:
|
|
96
|
+
console.print(f" [{Colors.DIM}]type[/{Colors.DIM}] [{Colors.PRIMARY}]custom[/{Colors.PRIMARY}]")
|
|
97
|
+
|
|
98
|
+
if agent.description:
|
|
99
|
+
console.print(f" [{Colors.DIM}]desc[/{Colors.DIM}] {agent.description}")
|
|
100
|
+
|
|
101
|
+
if agent.model:
|
|
102
|
+
console.print(f" [{Colors.DIM}]model[/{Colors.DIM}] {agent.model}")
|
|
103
|
+
|
|
104
|
+
if agent.tools:
|
|
105
|
+
console.print(f" [{Colors.DIM}]tools[/{Colors.DIM}] {', '.join(agent.tools)}")
|
|
106
|
+
|
|
107
|
+
if agent.mcp_servers:
|
|
108
|
+
console.print()
|
|
109
|
+
console.print(f" [{Colors.DIM}]mcp servers:[/{Colors.DIM}]")
|
|
110
|
+
for server in agent.mcp_servers:
|
|
111
|
+
status = f"[{Colors.SUCCESS}]●[/{Colors.SUCCESS}]" if server.enabled else f"[{Colors.MUTED}]○[/{Colors.MUTED}]"
|
|
112
|
+
console.print(f" {status} [{Colors.PRIMARY}]{server.name}[/{Colors.PRIMARY}]")
|
|
113
|
+
console.print(f" [{Colors.DIM}]{server.command} {' '.join(server.args)}[/{Colors.DIM}]")
|
|
114
|
+
|
|
115
|
+
if agent.rules:
|
|
116
|
+
console.print(f" [{Colors.DIM}]rules[/{Colors.DIM}] {', '.join(agent.rules)}")
|
|
117
|
+
|
|
118
|
+
if agent.skills:
|
|
119
|
+
console.print(f" [{Colors.DIM}]skills[/{Colors.DIM}] {', '.join(agent.skills)}")
|
|
120
|
+
|
|
121
|
+
if agent.verifiers:
|
|
122
|
+
console.print(f" [{Colors.DIM}]verify[/{Colors.DIM}] {', '.join(agent.verifiers)}")
|
|
123
|
+
|
|
124
|
+
if agent.file_path:
|
|
125
|
+
console.print()
|
|
126
|
+
console.print(f" [{Colors.DIM}]file[/{Colors.DIM}] {agent.file_path}")
|
|
127
|
+
|
|
128
|
+
if agent.system_prompt:
|
|
129
|
+
console.print()
|
|
130
|
+
console.print(f" [{Colors.DIM}]prompt preview:[/{Colors.DIM}]")
|
|
131
|
+
preview = agent.system_prompt[:250]
|
|
132
|
+
if len(agent.system_prompt) > 250:
|
|
133
|
+
preview += "..."
|
|
134
|
+
for line in preview.split('\n')[:6]:
|
|
135
|
+
console.print(f" [{Colors.MUTED}]{line}[/{Colors.MUTED}]")
|
|
136
|
+
else:
|
|
137
|
+
console.print(f" [{Colors.WARNING}]Agent '{name}' not found[/{Colors.WARNING}]")
|
|
138
|
+
|
|
139
|
+
console.print()
|
|
140
|
+
console.print(f"[{Colors.MUTED}]{footer(SEPARATOR_WIDTH)}[/{Colors.MUTED}]")
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
def delete_agent(name: str) -> bool:
|
|
144
|
+
"""Delete a custom agent."""
|
|
145
|
+
agents_dir = Path.cwd() / ".emdash" / "agents"
|
|
146
|
+
agent_file = agents_dir / f"{name}.md"
|
|
147
|
+
|
|
148
|
+
if not agent_file.exists():
|
|
149
|
+
console.print(f"[yellow]Agent file not found: {agent_file}[/yellow]")
|
|
150
|
+
return False
|
|
151
|
+
|
|
152
|
+
if confirm_delete(name):
|
|
153
|
+
agent_file.unlink()
|
|
154
|
+
console.print(f"[green]Deleted agent: {name}[/green]")
|
|
155
|
+
return True
|
|
156
|
+
else:
|
|
157
|
+
console.print("[dim]Cancelled[/dim]")
|
|
158
|
+
return False
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
def edit_agent(name: str) -> None:
|
|
162
|
+
"""Open agent file in editor."""
|
|
163
|
+
agents_dir = Path.cwd() / ".emdash" / "agents"
|
|
164
|
+
agent_file = agents_dir / f"{name}.md"
|
|
165
|
+
|
|
166
|
+
if not agent_file.exists():
|
|
167
|
+
console.print(f"[yellow]Agent file not found: {agent_file}[/yellow]")
|
|
168
|
+
return
|
|
169
|
+
|
|
170
|
+
# Try to open in editor
|
|
171
|
+
editor = os.environ.get("EDITOR", "")
|
|
172
|
+
if not editor:
|
|
173
|
+
# Try common editors
|
|
174
|
+
for ed in ["code", "vim", "nano", "vi"]:
|
|
175
|
+
try:
|
|
176
|
+
subprocess.run(["which", ed], capture_output=True, check=True)
|
|
177
|
+
editor = ed
|
|
178
|
+
break
|
|
179
|
+
except (subprocess.CalledProcessError, FileNotFoundError):
|
|
180
|
+
continue
|
|
181
|
+
|
|
182
|
+
if editor:
|
|
183
|
+
console.print(f"[dim]Opening {agent_file} in {editor}...[/dim]")
|
|
184
|
+
try:
|
|
185
|
+
subprocess.run([editor, str(agent_file)])
|
|
186
|
+
except Exception as e:
|
|
187
|
+
console.print(f"[red]Failed to open editor: {e}[/red]")
|
|
188
|
+
console.print(f"[dim]Edit manually: {agent_file}[/dim]")
|
|
189
|
+
else:
|
|
190
|
+
console.print(f"[yellow]No editor found. Edit manually:[/yellow]")
|
|
191
|
+
console.print(f" {agent_file}")
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
def chat_edit_agent(name: str, client, renderer, model, max_iterations, render_with_interrupt) -> None:
|
|
195
|
+
"""Start a chat session to edit an agent with AI assistance."""
|
|
196
|
+
from prompt_toolkit import PromptSession
|
|
197
|
+
from prompt_toolkit.styles import Style
|
|
198
|
+
|
|
199
|
+
agents_dir = Path.cwd() / ".emdash" / "agents"
|
|
200
|
+
agent_file = agents_dir / f"{name}.md"
|
|
201
|
+
|
|
202
|
+
if not agent_file.exists():
|
|
203
|
+
console.print(f" [{Colors.WARNING}]Agent file not found: {agent_file}[/{Colors.WARNING}]")
|
|
204
|
+
return
|
|
205
|
+
|
|
206
|
+
# Read current content
|
|
207
|
+
content = agent_file.read_text()
|
|
208
|
+
|
|
209
|
+
console.print()
|
|
210
|
+
console.print(f"[{Colors.MUTED}]{header(f'Edit: {name}', SEPARATOR_WIDTH)}[/{Colors.MUTED}]")
|
|
211
|
+
console.print()
|
|
212
|
+
console.print(f" [{Colors.DIM}]Describe changes. Type 'done' to finish.[/{Colors.DIM}]")
|
|
213
|
+
console.print()
|
|
214
|
+
|
|
215
|
+
chat_style = Style.from_dict({
|
|
216
|
+
"prompt": f"{Colors.PRIMARY} bold",
|
|
217
|
+
})
|
|
218
|
+
|
|
219
|
+
ps = PromptSession(style=chat_style)
|
|
220
|
+
chat_session_id = None
|
|
221
|
+
first_message = True
|
|
222
|
+
|
|
223
|
+
# Chat loop
|
|
224
|
+
while True:
|
|
225
|
+
try:
|
|
226
|
+
user_input = ps.prompt([("class:prompt", "› ")]).strip()
|
|
227
|
+
|
|
228
|
+
if not user_input:
|
|
229
|
+
continue
|
|
230
|
+
|
|
231
|
+
if user_input.lower() in ("done", "quit", "exit", "q"):
|
|
232
|
+
console.print("[dim]Finished editing agent[/dim]")
|
|
233
|
+
break
|
|
234
|
+
|
|
235
|
+
# First message includes agent context
|
|
236
|
+
if first_message:
|
|
237
|
+
message_with_context = f"""I want to edit my custom agent "{name}".
|
|
238
|
+
|
|
239
|
+
**File:** `{agent_file}`
|
|
240
|
+
|
|
241
|
+
**Current content:**
|
|
242
|
+
```markdown
|
|
243
|
+
{content}
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
**My request:** {user_input}
|
|
247
|
+
|
|
248
|
+
Please make the requested changes using the Edit tool."""
|
|
249
|
+
stream = client.agent_chat_stream(
|
|
250
|
+
message=message_with_context,
|
|
251
|
+
model=model,
|
|
252
|
+
max_iterations=max_iterations,
|
|
253
|
+
options={"mode": "code"},
|
|
254
|
+
)
|
|
255
|
+
first_message = False
|
|
256
|
+
elif chat_session_id:
|
|
257
|
+
stream = client.agent_continue_stream(
|
|
258
|
+
chat_session_id, user_input
|
|
259
|
+
)
|
|
260
|
+
else:
|
|
261
|
+
stream = client.agent_chat_stream(
|
|
262
|
+
message=user_input,
|
|
263
|
+
model=model,
|
|
264
|
+
max_iterations=max_iterations,
|
|
265
|
+
options={"mode": "code"},
|
|
266
|
+
)
|
|
267
|
+
|
|
268
|
+
result = render_with_interrupt(renderer, stream)
|
|
269
|
+
if result and result.get("session_id"):
|
|
270
|
+
chat_session_id = result["session_id"]
|
|
271
|
+
|
|
272
|
+
except (KeyboardInterrupt, EOFError):
|
|
273
|
+
console.print()
|
|
274
|
+
console.print("[dim]Finished editing agent[/dim]")
|
|
275
|
+
break
|
|
276
|
+
except Exception as e:
|
|
277
|
+
console.print(f"[red]Error: {e}[/red]")
|
|
278
|
+
|
|
279
|
+
|
|
280
|
+
def chat_create_agent(client, renderer, model, max_iterations, render_with_interrupt) -> str | None:
|
|
281
|
+
"""Start a chat session to create a new agent with AI assistance.
|
|
282
|
+
|
|
283
|
+
Returns:
|
|
284
|
+
The name of the created agent, or None if cancelled.
|
|
285
|
+
"""
|
|
286
|
+
from prompt_toolkit import PromptSession
|
|
287
|
+
from prompt_toolkit.styles import Style
|
|
288
|
+
|
|
289
|
+
agents_dir = Path.cwd() / ".emdash" / "agents"
|
|
290
|
+
|
|
291
|
+
console.print()
|
|
292
|
+
console.print(f"[{Colors.MUTED}]{header('Create Agent', SEPARATOR_WIDTH)}[/{Colors.MUTED}]")
|
|
293
|
+
console.print()
|
|
294
|
+
console.print(f" [{Colors.DIM}]Describe your agent. AI will help design it.[/{Colors.DIM}]")
|
|
295
|
+
console.print(f" [{Colors.DIM}]Type 'done' to finish.[/{Colors.DIM}]")
|
|
296
|
+
console.print()
|
|
297
|
+
|
|
298
|
+
chat_style = Style.from_dict({
|
|
299
|
+
"prompt": f"{Colors.PRIMARY} bold",
|
|
300
|
+
})
|
|
301
|
+
|
|
302
|
+
ps = PromptSession(style=chat_style)
|
|
303
|
+
chat_session_id = None
|
|
304
|
+
first_message = True
|
|
305
|
+
|
|
306
|
+
# Ensure agents directory exists
|
|
307
|
+
agents_dir.mkdir(parents=True, exist_ok=True)
|
|
308
|
+
|
|
309
|
+
# Chat loop
|
|
310
|
+
while True:
|
|
311
|
+
try:
|
|
312
|
+
user_input = ps.prompt([("class:prompt", "› ")]).strip()
|
|
313
|
+
|
|
314
|
+
if not user_input:
|
|
315
|
+
continue
|
|
316
|
+
|
|
317
|
+
if user_input.lower() in ("done", "quit", "exit", "q"):
|
|
318
|
+
console.print("[dim]Finished[/dim]")
|
|
319
|
+
break
|
|
320
|
+
|
|
321
|
+
# First message includes context about agents
|
|
322
|
+
if first_message:
|
|
323
|
+
message_with_context = f"""I want to create a new custom agent for my project.
|
|
324
|
+
|
|
325
|
+
**Agents directory:** `{agents_dir}`
|
|
326
|
+
|
|
327
|
+
Agents are markdown files with YAML frontmatter that define specialized assistants with custom system prompts and tools.
|
|
328
|
+
|
|
329
|
+
**Agent file format:**
|
|
330
|
+
```markdown
|
|
331
|
+
---
|
|
332
|
+
description: Brief description of what this agent does
|
|
333
|
+
model: claude-sonnet # optional, defaults to main model
|
|
334
|
+
tools: [grep, glob, read_file, edit_file, bash] # tools this agent can use
|
|
335
|
+
mcp_servers: # optional, MCP servers for this agent
|
|
336
|
+
- name: server-name
|
|
337
|
+
command: npx
|
|
338
|
+
args: ["-y", "@modelcontextprotocol/server-name"]
|
|
339
|
+
---
|
|
340
|
+
|
|
341
|
+
# System Prompt
|
|
342
|
+
|
|
343
|
+
You are a specialized assistant for [purpose].
|
|
344
|
+
|
|
345
|
+
## Your Mission
|
|
346
|
+
[What this agent should accomplish]
|
|
347
|
+
|
|
348
|
+
## Approach
|
|
349
|
+
[How this agent should work]
|
|
350
|
+
|
|
351
|
+
## Output Format
|
|
352
|
+
[How the agent should format responses]
|
|
353
|
+
```
|
|
354
|
+
|
|
355
|
+
**Available tools:** grep, glob, read_file, edit_file, write_file, bash, semantic_search, list_files, etc.
|
|
356
|
+
|
|
357
|
+
**My request:** {user_input}
|
|
358
|
+
|
|
359
|
+
Please help me design and create an agent. Ask me questions about what I need, then use the Write tool to create the file at `{agents_dir}/<agent-name>.md`."""
|
|
360
|
+
stream = client.agent_chat_stream(
|
|
361
|
+
message=message_with_context,
|
|
362
|
+
model=model,
|
|
363
|
+
max_iterations=max_iterations,
|
|
364
|
+
options={"mode": "code"},
|
|
365
|
+
)
|
|
366
|
+
first_message = False
|
|
367
|
+
elif chat_session_id:
|
|
368
|
+
stream = client.agent_continue_stream(
|
|
369
|
+
chat_session_id, user_input
|
|
370
|
+
)
|
|
371
|
+
else:
|
|
372
|
+
stream = client.agent_chat_stream(
|
|
373
|
+
message=user_input,
|
|
374
|
+
model=model,
|
|
375
|
+
max_iterations=max_iterations,
|
|
376
|
+
options={"mode": "code"},
|
|
377
|
+
)
|
|
378
|
+
|
|
379
|
+
result = render_with_interrupt(renderer, stream)
|
|
380
|
+
if result and result.get("session_id"):
|
|
381
|
+
chat_session_id = result["session_id"]
|
|
382
|
+
|
|
383
|
+
except (KeyboardInterrupt, EOFError):
|
|
384
|
+
console.print()
|
|
385
|
+
console.print("[dim]Cancelled[/dim]")
|
|
386
|
+
break
|
|
387
|
+
except Exception as e:
|
|
388
|
+
console.print(f"[red]Error: {e}[/red]")
|
|
389
|
+
|
|
390
|
+
return None
|
|
391
|
+
|
|
392
|
+
|
|
393
|
+
def handle_agents(args: str, client, renderer, model, max_iterations, render_with_interrupt) -> None:
|
|
394
|
+
"""Handle /agents command."""
|
|
395
|
+
from prompt_toolkit import PromptSession
|
|
396
|
+
|
|
397
|
+
# Handle subcommands for backward compatibility
|
|
398
|
+
if args:
|
|
399
|
+
subparts = args.split(maxsplit=1)
|
|
400
|
+
subcommand = subparts[0].lower()
|
|
401
|
+
subargs = subparts[1] if len(subparts) > 1 else ""
|
|
402
|
+
|
|
403
|
+
if subcommand == "create" and subargs:
|
|
404
|
+
create_agent(subargs.strip().lower().replace(" ", "-"))
|
|
405
|
+
elif subcommand == "show" and subargs:
|
|
406
|
+
show_agent_details(subargs.strip())
|
|
407
|
+
elif subcommand == "delete" and subargs:
|
|
408
|
+
delete_agent(subargs.strip())
|
|
409
|
+
elif subcommand == "edit" and subargs:
|
|
410
|
+
edit_agent(subargs.strip())
|
|
411
|
+
else:
|
|
412
|
+
console.print("[yellow]Usage: /agents [create|show|delete|edit] <name>[/yellow]")
|
|
413
|
+
console.print("[dim]Or just /agents for interactive menu[/dim]")
|
|
414
|
+
else:
|
|
415
|
+
# Interactive menu
|
|
416
|
+
while True:
|
|
417
|
+
action, agent_name = show_agents_interactive_menu()
|
|
418
|
+
|
|
419
|
+
if action == "cancel":
|
|
420
|
+
break
|
|
421
|
+
elif action == "view":
|
|
422
|
+
show_agent_details(agent_name)
|
|
423
|
+
# After viewing, show options based on agent type
|
|
424
|
+
is_custom = agent_name not in ("Explore", "Plan")
|
|
425
|
+
try:
|
|
426
|
+
if is_custom:
|
|
427
|
+
console.print("[cyan]'c'[/cyan] chat • [cyan]'e'[/cyan] edit • [red]'d'[/red] delete • [dim]Enter back[/dim]", end="")
|
|
428
|
+
else:
|
|
429
|
+
console.print("[dim]Press Enter to go back...[/dim]", end="")
|
|
430
|
+
ps = PromptSession()
|
|
431
|
+
resp = ps.prompt(" ").strip().lower()
|
|
432
|
+
if is_custom and resp == 'c':
|
|
433
|
+
chat_edit_agent(agent_name, client, renderer, model, max_iterations, render_with_interrupt)
|
|
434
|
+
elif is_custom and resp == 'e':
|
|
435
|
+
edit_agent(agent_name)
|
|
436
|
+
elif is_custom and resp == 'd':
|
|
437
|
+
if delete_agent(agent_name):
|
|
438
|
+
continue # Refresh menu after deletion
|
|
439
|
+
console.print() # Add spacing before menu reappears
|
|
440
|
+
except (KeyboardInterrupt, EOFError):
|
|
441
|
+
break
|
|
442
|
+
elif action == "create":
|
|
443
|
+
# Use AI-assisted creation
|
|
444
|
+
chat_create_agent(client, renderer, model, max_iterations, render_with_interrupt)
|
|
445
|
+
elif action == "delete":
|
|
446
|
+
delete_agent(agent_name)
|
|
447
|
+
elif action == "edit":
|
|
448
|
+
edit_agent(agent_name)
|
|
449
|
+
break # Exit menu after editing
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
"""Handler for /auth command."""
|
|
2
|
+
|
|
3
|
+
from rich.console import Console
|
|
4
|
+
|
|
5
|
+
console = Console()
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def handle_auth(args: str) -> None:
|
|
9
|
+
"""Handle /auth command.
|
|
10
|
+
|
|
11
|
+
Args:
|
|
12
|
+
args: Command arguments
|
|
13
|
+
"""
|
|
14
|
+
from emdash_core.auth.github import GitHubAuth, get_auth_status
|
|
15
|
+
|
|
16
|
+
# Parse subcommand
|
|
17
|
+
subparts = args.split(maxsplit=1) if args else []
|
|
18
|
+
subcommand = subparts[0].lower() if subparts else "status"
|
|
19
|
+
|
|
20
|
+
if subcommand == "status" or subcommand == "":
|
|
21
|
+
# Show auth status
|
|
22
|
+
status = get_auth_status()
|
|
23
|
+
console.print()
|
|
24
|
+
console.print("[bold cyan]GitHub Authentication[/bold cyan]\n")
|
|
25
|
+
|
|
26
|
+
if status["authenticated"]:
|
|
27
|
+
console.print(f" Status: [green]Authenticated[/green]")
|
|
28
|
+
console.print(f" Source: {status['source']}")
|
|
29
|
+
if status["username"]:
|
|
30
|
+
console.print(f" Username: @{status['username']}")
|
|
31
|
+
if status["scopes"]:
|
|
32
|
+
console.print(f" Scopes: {', '.join(status['scopes'])}")
|
|
33
|
+
else:
|
|
34
|
+
console.print(f" Status: [yellow]Not authenticated[/yellow]")
|
|
35
|
+
console.print("\n[dim]Run /auth login to authenticate with GitHub[/dim]")
|
|
36
|
+
|
|
37
|
+
console.print()
|
|
38
|
+
|
|
39
|
+
elif subcommand == "login":
|
|
40
|
+
# Start GitHub OAuth device flow
|
|
41
|
+
console.print()
|
|
42
|
+
console.print("[bold cyan]GitHub Login[/bold cyan]")
|
|
43
|
+
console.print("[dim]Starting device authorization flow...[/dim]\n")
|
|
44
|
+
|
|
45
|
+
auth = GitHubAuth()
|
|
46
|
+
try:
|
|
47
|
+
config = auth.login(open_browser=True)
|
|
48
|
+
if config:
|
|
49
|
+
console.print()
|
|
50
|
+
console.print("[green]Authentication successful![/green]")
|
|
51
|
+
console.print("[dim]MCP servers can now use ${GITHUB_TOKEN}[/dim]")
|
|
52
|
+
else:
|
|
53
|
+
console.print("[red]Authentication failed or was cancelled.[/red]")
|
|
54
|
+
except Exception as e:
|
|
55
|
+
console.print(f"[red]Login failed: {e}[/red]")
|
|
56
|
+
|
|
57
|
+
console.print()
|
|
58
|
+
|
|
59
|
+
elif subcommand == "logout":
|
|
60
|
+
# Remove stored authentication
|
|
61
|
+
auth = GitHubAuth()
|
|
62
|
+
if auth.logout():
|
|
63
|
+
console.print("[green]Logged out successfully[/green]")
|
|
64
|
+
else:
|
|
65
|
+
console.print("[dim]No stored authentication to remove[/dim]")
|
|
66
|
+
|
|
67
|
+
else:
|
|
68
|
+
console.print(f"[yellow]Unknown subcommand: {subcommand}[/yellow]")
|
|
69
|
+
console.print("[dim]Usage: /auth [status|login|logout][/dim]")
|