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