emdash-cli 0.1.30__tar.gz → 0.1.72__tar.gz
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-0.1.30 → emdash_cli-0.1.72}/PKG-INFO +2 -4
- emdash_cli-0.1.72/emdash_cli/__init__.py +23 -0
- {emdash_cli-0.1.30 → emdash_cli-0.1.72}/emdash_cli/client.py +162 -22
- emdash_cli-0.1.72/emdash_cli/clipboard.py +92 -0
- {emdash_cli-0.1.30 → emdash_cli-0.1.72}/emdash_cli/commands/__init__.py +2 -2
- emdash_cli-0.1.72/emdash_cli/commands/agent/__init__.py +14 -0
- emdash_cli-0.1.72/emdash_cli/commands/agent/cli.py +100 -0
- emdash_cli-0.1.72/emdash_cli/commands/agent/constants.py +131 -0
- emdash_cli-0.1.72/emdash_cli/commands/agent/file_utils.py +178 -0
- emdash_cli-0.1.72/emdash_cli/commands/agent/handlers/__init__.py +51 -0
- emdash_cli-0.1.72/emdash_cli/commands/agent/handlers/agents.py +449 -0
- emdash_cli-0.1.72/emdash_cli/commands/agent/handlers/auth.py +69 -0
- emdash_cli-0.1.72/emdash_cli/commands/agent/handlers/doctor.py +319 -0
- emdash_cli-0.1.72/emdash_cli/commands/agent/handlers/hooks.py +121 -0
- emdash_cli-0.1.72/emdash_cli/commands/agent/handlers/index.py +183 -0
- emdash_cli-0.1.72/emdash_cli/commands/agent/handlers/mcp.py +183 -0
- emdash_cli-0.1.72/emdash_cli/commands/agent/handlers/misc.py +319 -0
- emdash_cli-0.1.72/emdash_cli/commands/agent/handlers/registry.py +72 -0
- emdash_cli-0.1.72/emdash_cli/commands/agent/handlers/rules.py +411 -0
- emdash_cli-0.1.72/emdash_cli/commands/agent/handlers/sessions.py +168 -0
- emdash_cli-0.1.72/emdash_cli/commands/agent/handlers/setup.py +715 -0
- emdash_cli-0.1.72/emdash_cli/commands/agent/handlers/skills.py +478 -0
- emdash_cli-0.1.72/emdash_cli/commands/agent/handlers/telegram.py +523 -0
- emdash_cli-0.1.72/emdash_cli/commands/agent/handlers/todos.py +119 -0
- emdash_cli-0.1.72/emdash_cli/commands/agent/handlers/verify.py +653 -0
- emdash_cli-0.1.72/emdash_cli/commands/agent/help.py +236 -0
- emdash_cli-0.1.72/emdash_cli/commands/agent/interactive.py +951 -0
- emdash_cli-0.1.72/emdash_cli/commands/agent/menus.py +760 -0
- emdash_cli-0.1.72/emdash_cli/commands/agent/onboarding.py +619 -0
- emdash_cli-0.1.72/emdash_cli/commands/agent/session_restore.py +210 -0
- emdash_cli-0.1.72/emdash_cli/commands/agent.py +10 -0
- {emdash_cli-0.1.30 → emdash_cli-0.1.72}/emdash_cli/commands/index.py +111 -13
- emdash_cli-0.1.72/emdash_cli/commands/registry.py +635 -0
- emdash_cli-0.1.72/emdash_cli/commands/server.py +176 -0
- {emdash_cli-0.1.30 → emdash_cli-0.1.72}/emdash_cli/commands/skills.py +72 -6
- emdash_cli-0.1.72/emdash_cli/design.py +328 -0
- emdash_cli-0.1.72/emdash_cli/diff_renderer.py +438 -0
- emdash_cli-0.1.72/emdash_cli/integrations/__init__.py +1 -0
- emdash_cli-0.1.72/emdash_cli/integrations/telegram/__init__.py +15 -0
- emdash_cli-0.1.72/emdash_cli/integrations/telegram/bot.py +402 -0
- emdash_cli-0.1.72/emdash_cli/integrations/telegram/bridge.py +980 -0
- emdash_cli-0.1.72/emdash_cli/integrations/telegram/config.py +155 -0
- emdash_cli-0.1.72/emdash_cli/integrations/telegram/formatter.py +392 -0
- {emdash_cli-0.1.30 → emdash_cli-0.1.72}/emdash_cli/main.py +52 -2
- {emdash_cli-0.1.30 → emdash_cli-0.1.72}/emdash_cli/server_manager.py +70 -10
- emdash_cli-0.1.72/emdash_cli/session_store.py +321 -0
- emdash_cli-0.1.72/emdash_cli/sse_renderer.py +1255 -0
- {emdash_cli-0.1.30 → emdash_cli-0.1.72}/pyproject.toml +3 -8
- emdash_cli-0.1.30/emdash_cli/__init__.py +0 -8
- emdash_cli-0.1.30/emdash_cli/clipboard.py +0 -123
- emdash_cli-0.1.30/emdash_cli/commands/agent.py +0 -859
- emdash_cli-0.1.30/emdash_cli/commands/server.py +0 -117
- emdash_cli-0.1.30/emdash_cli/commands/swarm.py +0 -86
- emdash_cli-0.1.30/emdash_cli/sse_renderer.py +0 -648
- {emdash_cli-0.1.30 → emdash_cli-0.1.72}/emdash_cli/commands/analyze.py +0 -0
- {emdash_cli-0.1.30 → emdash_cli-0.1.72}/emdash_cli/commands/auth.py +0 -0
- {emdash_cli-0.1.30 → emdash_cli-0.1.72}/emdash_cli/commands/db.py +0 -0
- {emdash_cli-0.1.30 → emdash_cli-0.1.72}/emdash_cli/commands/embed.py +0 -0
- {emdash_cli-0.1.30 → emdash_cli-0.1.72}/emdash_cli/commands/plan.py +0 -0
- {emdash_cli-0.1.30 → emdash_cli-0.1.72}/emdash_cli/commands/projectmd.py +0 -0
- {emdash_cli-0.1.30 → emdash_cli-0.1.72}/emdash_cli/commands/research.py +0 -0
- {emdash_cli-0.1.30 → emdash_cli-0.1.72}/emdash_cli/commands/rules.py +0 -0
- {emdash_cli-0.1.30 → emdash_cli-0.1.72}/emdash_cli/commands/search.py +0 -0
- {emdash_cli-0.1.30 → emdash_cli-0.1.72}/emdash_cli/commands/spec.py +0 -0
- {emdash_cli-0.1.30 → emdash_cli-0.1.72}/emdash_cli/commands/tasks.py +0 -0
- {emdash_cli-0.1.30 → emdash_cli-0.1.72}/emdash_cli/commands/team.py +0 -0
- {emdash_cli-0.1.30 → emdash_cli-0.1.72}/emdash_cli/keyboard.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: emdash-cli
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.72
|
|
4
4
|
Summary: EmDash CLI - Command-line interface for code intelligence
|
|
5
5
|
Author: Em Dash Team
|
|
6
6
|
Requires-Python: >=3.10,<4.0
|
|
@@ -10,10 +10,8 @@ Classifier: Programming Language :: Python :: 3.11
|
|
|
10
10
|
Classifier: Programming Language :: Python :: 3.12
|
|
11
11
|
Classifier: Programming Language :: Python :: 3.13
|
|
12
12
|
Classifier: Programming Language :: Python :: 3.14
|
|
13
|
-
Provides-Extra: images
|
|
14
13
|
Requires-Dist: click (>=8.1.7,<9.0.0)
|
|
15
|
-
Requires-Dist: emdash-core (>=0.1.
|
|
14
|
+
Requires-Dist: emdash-core (>=0.1.72)
|
|
16
15
|
Requires-Dist: httpx (>=0.25.0)
|
|
17
|
-
Requires-Dist: pillow (>=10.0.0) ; extra == "images"
|
|
18
16
|
Requires-Dist: prompt_toolkit (>=3.0.43,<4.0.0)
|
|
19
17
|
Requires-Dist: rich (>=13.7.0)
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
"""EmDash CLI - Command-line interface for code intelligence."""
|
|
2
|
+
|
|
3
|
+
from importlib.metadata import version, PackageNotFoundError
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
|
|
6
|
+
# Load .env files early so env vars are available for server subprocess
|
|
7
|
+
try:
|
|
8
|
+
from dotenv import load_dotenv
|
|
9
|
+
# Try to find .env in current dir or parent dirs
|
|
10
|
+
current = Path.cwd()
|
|
11
|
+
for _ in range(5):
|
|
12
|
+
env_path = current / ".env"
|
|
13
|
+
if env_path.exists():
|
|
14
|
+
load_dotenv(env_path, override=True)
|
|
15
|
+
break
|
|
16
|
+
current = current.parent
|
|
17
|
+
except ImportError:
|
|
18
|
+
pass # dotenv not installed
|
|
19
|
+
|
|
20
|
+
try:
|
|
21
|
+
__version__ = version("emdash-cli")
|
|
22
|
+
except PackageNotFoundError:
|
|
23
|
+
__version__ = "0.0.0-dev"
|
|
@@ -53,6 +53,7 @@ class EmdashClient:
|
|
|
53
53
|
max_iterations: int = _get_max_iterations(),
|
|
54
54
|
options: Optional[dict] = None,
|
|
55
55
|
images: Optional[list[dict]] = None,
|
|
56
|
+
history: Optional[list[dict]] = None,
|
|
56
57
|
) -> Iterator[str]:
|
|
57
58
|
"""Stream agent chat response via SSE.
|
|
58
59
|
|
|
@@ -63,6 +64,7 @@ class EmdashClient:
|
|
|
63
64
|
max_iterations: Max agent iterations
|
|
64
65
|
options: Additional options (mode, save, no_graph_tools, etc.)
|
|
65
66
|
images: List of images [{"data": base64_str, "format": "png"}]
|
|
67
|
+
history: Pre-loaded conversation history from saved session
|
|
66
68
|
|
|
67
69
|
Yields:
|
|
68
70
|
SSE lines from the response
|
|
@@ -91,6 +93,8 @@ class EmdashClient:
|
|
|
91
93
|
payload["session_id"] = session_id
|
|
92
94
|
if images:
|
|
93
95
|
payload["images"] = images
|
|
96
|
+
if history:
|
|
97
|
+
payload["history"] = history
|
|
94
98
|
|
|
95
99
|
try:
|
|
96
100
|
with self._client.stream(
|
|
@@ -138,6 +142,135 @@ class EmdashClient:
|
|
|
138
142
|
# Stream was closed early (interrupted)
|
|
139
143
|
pass
|
|
140
144
|
|
|
145
|
+
def plan_approve_stream(self, session_id: str) -> Iterator[str]:
|
|
146
|
+
"""Approve a pending plan and start implementation.
|
|
147
|
+
|
|
148
|
+
Args:
|
|
149
|
+
session_id: Session ID with pending plan
|
|
150
|
+
|
|
151
|
+
Yields:
|
|
152
|
+
SSE lines from the response
|
|
153
|
+
"""
|
|
154
|
+
try:
|
|
155
|
+
with self._client.stream(
|
|
156
|
+
"POST",
|
|
157
|
+
f"{self.base_url}/api/agent/chat/{session_id}/plan/approve",
|
|
158
|
+
) as response:
|
|
159
|
+
response.raise_for_status()
|
|
160
|
+
for line in response.iter_lines():
|
|
161
|
+
yield line
|
|
162
|
+
except GeneratorExit:
|
|
163
|
+
pass
|
|
164
|
+
|
|
165
|
+
def plan_reject_stream(self, session_id: str, feedback: str = "") -> Iterator[str]:
|
|
166
|
+
"""Reject a pending plan with feedback.
|
|
167
|
+
|
|
168
|
+
Args:
|
|
169
|
+
session_id: Session ID with pending plan
|
|
170
|
+
feedback: Feedback explaining rejection
|
|
171
|
+
|
|
172
|
+
Yields:
|
|
173
|
+
SSE lines from the response
|
|
174
|
+
"""
|
|
175
|
+
try:
|
|
176
|
+
with self._client.stream(
|
|
177
|
+
"POST",
|
|
178
|
+
f"{self.base_url}/api/agent/chat/{session_id}/plan/reject",
|
|
179
|
+
params={"feedback": feedback},
|
|
180
|
+
) as response:
|
|
181
|
+
response.raise_for_status()
|
|
182
|
+
for line in response.iter_lines():
|
|
183
|
+
yield line
|
|
184
|
+
except GeneratorExit:
|
|
185
|
+
pass
|
|
186
|
+
|
|
187
|
+
def planmode_approve_stream(self, session_id: str) -> Iterator[str]:
|
|
188
|
+
"""Approve entering plan mode.
|
|
189
|
+
|
|
190
|
+
Args:
|
|
191
|
+
session_id: Session ID requesting plan mode
|
|
192
|
+
|
|
193
|
+
Yields:
|
|
194
|
+
SSE lines from the response
|
|
195
|
+
"""
|
|
196
|
+
try:
|
|
197
|
+
with self._client.stream(
|
|
198
|
+
"POST",
|
|
199
|
+
f"{self.base_url}/api/agent/chat/{session_id}/planmode/approve",
|
|
200
|
+
) as response:
|
|
201
|
+
response.raise_for_status()
|
|
202
|
+
for line in response.iter_lines():
|
|
203
|
+
yield line
|
|
204
|
+
except GeneratorExit:
|
|
205
|
+
pass
|
|
206
|
+
|
|
207
|
+
def planmode_reject_stream(self, session_id: str, feedback: str = "") -> Iterator[str]:
|
|
208
|
+
"""Reject entering plan mode.
|
|
209
|
+
|
|
210
|
+
Args:
|
|
211
|
+
session_id: Session ID requesting plan mode
|
|
212
|
+
feedback: Feedback explaining rejection
|
|
213
|
+
|
|
214
|
+
Yields:
|
|
215
|
+
SSE lines from the response
|
|
216
|
+
"""
|
|
217
|
+
try:
|
|
218
|
+
with self._client.stream(
|
|
219
|
+
"POST",
|
|
220
|
+
f"{self.base_url}/api/agent/chat/{session_id}/planmode/reject",
|
|
221
|
+
params={"feedback": feedback},
|
|
222
|
+
) as response:
|
|
223
|
+
response.raise_for_status()
|
|
224
|
+
for line in response.iter_lines():
|
|
225
|
+
yield line
|
|
226
|
+
except GeneratorExit:
|
|
227
|
+
pass
|
|
228
|
+
|
|
229
|
+
def clarification_answer_stream(self, session_id: str, answer: str) -> Iterator[str]:
|
|
230
|
+
"""Answer a pending clarification question.
|
|
231
|
+
|
|
232
|
+
Args:
|
|
233
|
+
session_id: Session ID with pending clarification
|
|
234
|
+
answer: User's answer to the clarification question
|
|
235
|
+
|
|
236
|
+
Yields:
|
|
237
|
+
SSE lines from the response
|
|
238
|
+
"""
|
|
239
|
+
try:
|
|
240
|
+
with self._client.stream(
|
|
241
|
+
"POST",
|
|
242
|
+
f"{self.base_url}/api/agent/chat/{session_id}/clarification/answer",
|
|
243
|
+
params={"answer": answer},
|
|
244
|
+
) as response:
|
|
245
|
+
response.raise_for_status()
|
|
246
|
+
for line in response.iter_lines():
|
|
247
|
+
yield line
|
|
248
|
+
except GeneratorExit:
|
|
249
|
+
pass
|
|
250
|
+
|
|
251
|
+
def get(self, path: str) -> "httpx.Response":
|
|
252
|
+
"""Make a GET request to the API.
|
|
253
|
+
|
|
254
|
+
Args:
|
|
255
|
+
path: API path (e.g., "/api/agent/sessions")
|
|
256
|
+
|
|
257
|
+
Returns:
|
|
258
|
+
HTTP response
|
|
259
|
+
"""
|
|
260
|
+
return self._client.get(f"{self.base_url}{path}")
|
|
261
|
+
|
|
262
|
+
def post(self, path: str, json: dict | None = None) -> "httpx.Response":
|
|
263
|
+
"""Make a POST request to the API.
|
|
264
|
+
|
|
265
|
+
Args:
|
|
266
|
+
path: API path (e.g., "/api/agent/chat/123/compact")
|
|
267
|
+
json: Optional JSON body
|
|
268
|
+
|
|
269
|
+
Returns:
|
|
270
|
+
HTTP response
|
|
271
|
+
"""
|
|
272
|
+
return self._client.post(f"{self.base_url}{path}", json=json)
|
|
273
|
+
|
|
141
274
|
def list_sessions(self) -> list[dict]:
|
|
142
275
|
"""List active agent sessions.
|
|
143
276
|
|
|
@@ -539,31 +672,38 @@ class EmdashClient:
|
|
|
539
672
|
response.raise_for_status()
|
|
540
673
|
return response.json()
|
|
541
674
|
|
|
542
|
-
# ====================
|
|
675
|
+
# ==================== Todos ====================
|
|
543
676
|
|
|
544
|
-
def
|
|
545
|
-
|
|
546
|
-
tasks: list[str],
|
|
547
|
-
model: Optional[str] = None,
|
|
548
|
-
auto_merge: bool = False,
|
|
549
|
-
) -> Iterator[str]:
|
|
550
|
-
"""Run multi-agent swarm with SSE streaming."""
|
|
551
|
-
payload = {"tasks": tasks, "auto_merge": auto_merge}
|
|
552
|
-
if model:
|
|
553
|
-
payload["model"] = model
|
|
677
|
+
def get_todos(self, session_id: str) -> dict:
|
|
678
|
+
"""Get the current todo list for a session.
|
|
554
679
|
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
f"{self.base_url}/api/swarm/run",
|
|
558
|
-
json=payload,
|
|
559
|
-
) as response:
|
|
560
|
-
response.raise_for_status()
|
|
561
|
-
for line in response.iter_lines():
|
|
562
|
-
yield line
|
|
680
|
+
Args:
|
|
681
|
+
session_id: Session ID
|
|
563
682
|
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
683
|
+
Returns:
|
|
684
|
+
Dict with todos list and summary
|
|
685
|
+
"""
|
|
686
|
+
response = self._client.get(
|
|
687
|
+
f"{self.base_url}/api/agent/chat/{session_id}/todos"
|
|
688
|
+
)
|
|
689
|
+
response.raise_for_status()
|
|
690
|
+
return response.json()
|
|
691
|
+
|
|
692
|
+
def add_todo(self, session_id: str, title: str, description: str = "") -> dict:
|
|
693
|
+
"""Add a new todo item to the agent's task list.
|
|
694
|
+
|
|
695
|
+
Args:
|
|
696
|
+
session_id: Session ID
|
|
697
|
+
title: Todo title
|
|
698
|
+
description: Optional description
|
|
699
|
+
|
|
700
|
+
Returns:
|
|
701
|
+
Dict with created task info
|
|
702
|
+
"""
|
|
703
|
+
response = self._client.post(
|
|
704
|
+
f"{self.base_url}/api/agent/chat/{session_id}/todos",
|
|
705
|
+
params={"title": title, "description": description},
|
|
706
|
+
)
|
|
567
707
|
response.raise_for_status()
|
|
568
708
|
return response.json()
|
|
569
709
|
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
"""Clipboard utilities for image handling.
|
|
2
|
+
|
|
3
|
+
Uses platform-native clipboard access (no Pillow dependency).
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import base64
|
|
7
|
+
from typing import Optional, Tuple
|
|
8
|
+
|
|
9
|
+
from emdash_core.utils.image import (
|
|
10
|
+
read_clipboard_image,
|
|
11
|
+
is_clipboard_image_available,
|
|
12
|
+
get_image_info,
|
|
13
|
+
ClipboardImageError,
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def get_clipboard_image() -> Optional[Tuple[str, str]]:
|
|
18
|
+
"""Get image from clipboard if available.
|
|
19
|
+
|
|
20
|
+
Returns:
|
|
21
|
+
Tuple of (base64_data, format) if image found, None otherwise.
|
|
22
|
+
"""
|
|
23
|
+
try:
|
|
24
|
+
if not is_clipboard_image_available():
|
|
25
|
+
return None
|
|
26
|
+
|
|
27
|
+
image_data = read_clipboard_image()
|
|
28
|
+
if image_data is None:
|
|
29
|
+
return None
|
|
30
|
+
|
|
31
|
+
# Encode to base64
|
|
32
|
+
base64_data = base64.b64encode(image_data).decode('utf-8')
|
|
33
|
+
return base64_data, 'png'
|
|
34
|
+
|
|
35
|
+
except ClipboardImageError:
|
|
36
|
+
return None
|
|
37
|
+
except Exception:
|
|
38
|
+
return None
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def get_image_from_path(path: str) -> Optional[Tuple[str, str]]:
|
|
42
|
+
"""Load image from file path.
|
|
43
|
+
|
|
44
|
+
Only PNG files are fully supported. Other formats will be read as raw bytes.
|
|
45
|
+
|
|
46
|
+
Args:
|
|
47
|
+
path: Path to image file
|
|
48
|
+
|
|
49
|
+
Returns:
|
|
50
|
+
Tuple of (base64_data, format) if successful, None otherwise.
|
|
51
|
+
"""
|
|
52
|
+
try:
|
|
53
|
+
with open(path, 'rb') as f:
|
|
54
|
+
image_data = f.read()
|
|
55
|
+
|
|
56
|
+
# Determine format from file extension
|
|
57
|
+
ext = path.lower().split('.')[-1]
|
|
58
|
+
if ext in ('jpg', 'jpeg'):
|
|
59
|
+
img_format = 'jpeg'
|
|
60
|
+
elif ext == 'png':
|
|
61
|
+
img_format = 'png'
|
|
62
|
+
elif ext == 'gif':
|
|
63
|
+
img_format = 'gif'
|
|
64
|
+
elif ext == 'webp':
|
|
65
|
+
img_format = 'webp'
|
|
66
|
+
else:
|
|
67
|
+
img_format = 'png'
|
|
68
|
+
|
|
69
|
+
base64_data = base64.b64encode(image_data).decode('utf-8')
|
|
70
|
+
return base64_data, img_format
|
|
71
|
+
|
|
72
|
+
except Exception:
|
|
73
|
+
return None
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def get_image_dimensions(base64_data: str) -> Optional[Tuple[int, int]]:
|
|
77
|
+
"""Get dimensions of base64-encoded PNG image.
|
|
78
|
+
|
|
79
|
+
Args:
|
|
80
|
+
base64_data: Base64-encoded image data
|
|
81
|
+
|
|
82
|
+
Returns:
|
|
83
|
+
Tuple of (width, height) if successful, None otherwise.
|
|
84
|
+
"""
|
|
85
|
+
try:
|
|
86
|
+
image_bytes = base64.b64decode(base64_data)
|
|
87
|
+
info = get_image_info(image_bytes)
|
|
88
|
+
if info.get("width") and info.get("height"):
|
|
89
|
+
return info["width"], info["height"]
|
|
90
|
+
return None
|
|
91
|
+
except Exception:
|
|
92
|
+
return None
|
|
@@ -7,12 +7,12 @@ from .analyze import analyze
|
|
|
7
7
|
from .embed import embed
|
|
8
8
|
from .index import index
|
|
9
9
|
from .plan import plan
|
|
10
|
+
from .registry import registry
|
|
10
11
|
from .rules import rules
|
|
11
12
|
from .search import search
|
|
12
13
|
from .server import server
|
|
13
14
|
from .skills import skills
|
|
14
15
|
from .team import team
|
|
15
|
-
from .swarm import swarm
|
|
16
16
|
from .projectmd import projectmd
|
|
17
17
|
from .research import research
|
|
18
18
|
from .spec import spec
|
|
@@ -26,12 +26,12 @@ __all__ = [
|
|
|
26
26
|
"embed",
|
|
27
27
|
"index",
|
|
28
28
|
"plan",
|
|
29
|
+
"registry",
|
|
29
30
|
"rules",
|
|
30
31
|
"search",
|
|
31
32
|
"server",
|
|
32
33
|
"skills",
|
|
33
34
|
"team",
|
|
34
|
-
"swarm",
|
|
35
35
|
"projectmd",
|
|
36
36
|
"research",
|
|
37
37
|
"spec",
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
"""Agent CLI commands package.
|
|
2
|
+
|
|
3
|
+
This package contains the refactored agent CLI code, split into:
|
|
4
|
+
- cli.py: Click command definitions
|
|
5
|
+
- constants.py: Enums and constants
|
|
6
|
+
- file_utils.py: File reference expansion utilities
|
|
7
|
+
- menus.py: Interactive prompt_toolkit menus
|
|
8
|
+
- interactive.py: Main REPL loop
|
|
9
|
+
- handlers/: Slash command handlers
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from .cli import agent, agent_code
|
|
13
|
+
|
|
14
|
+
__all__ = ["agent", "agent_code"]
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
"""Click CLI commands for the agent."""
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
|
|
5
|
+
import click
|
|
6
|
+
from rich.console import Console
|
|
7
|
+
|
|
8
|
+
from ...client import EmdashClient
|
|
9
|
+
from ...server_manager import get_server_manager
|
|
10
|
+
from ...sse_renderer import SSERenderer
|
|
11
|
+
from .interactive import run_interactive, run_single_task
|
|
12
|
+
|
|
13
|
+
console = Console()
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@click.group()
|
|
17
|
+
def agent():
|
|
18
|
+
"""AI agent commands."""
|
|
19
|
+
pass
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
@agent.command("code")
|
|
23
|
+
@click.argument("task", required=False)
|
|
24
|
+
@click.option("--model", "-m", default=None, help="Model to use")
|
|
25
|
+
@click.option("--mode", type=click.Choice(["plan", "code"]), default="code",
|
|
26
|
+
help="Starting mode")
|
|
27
|
+
@click.option("--quiet", "-q", is_flag=True, help="Less verbose output")
|
|
28
|
+
@click.option("--max-iterations", default=int(os.getenv("EMDASH_MAX_ITERATIONS", "100")), help="Max agent iterations")
|
|
29
|
+
@click.option("--no-graph-tools", is_flag=True, help="Skip graph exploration tools")
|
|
30
|
+
@click.option("--save", is_flag=True, help="Save specs to specs/<feature>/")
|
|
31
|
+
def agent_code(
|
|
32
|
+
task: str | None,
|
|
33
|
+
model: str | None,
|
|
34
|
+
mode: str,
|
|
35
|
+
quiet: bool,
|
|
36
|
+
max_iterations: int,
|
|
37
|
+
no_graph_tools: bool,
|
|
38
|
+
save: bool,
|
|
39
|
+
):
|
|
40
|
+
"""Start the coding agent.
|
|
41
|
+
|
|
42
|
+
With TASK: Run single task and exit
|
|
43
|
+
Without TASK: Start interactive REPL mode
|
|
44
|
+
|
|
45
|
+
MODES:
|
|
46
|
+
plan - Explore codebase and create plans (read-only)
|
|
47
|
+
code - Execute code changes (default)
|
|
48
|
+
|
|
49
|
+
SLASH COMMANDS (in interactive mode):
|
|
50
|
+
/plan - Switch to plan mode
|
|
51
|
+
/code - Switch to code mode
|
|
52
|
+
/help - Show available commands
|
|
53
|
+
/reset - Reset session
|
|
54
|
+
|
|
55
|
+
Examples:
|
|
56
|
+
emdash # Interactive code mode
|
|
57
|
+
emdash agent code # Same as above
|
|
58
|
+
emdash agent code --mode plan # Start in plan mode
|
|
59
|
+
emdash agent code "Fix the login bug" # Single task
|
|
60
|
+
"""
|
|
61
|
+
# Get server URL (starts server if needed)
|
|
62
|
+
server = get_server_manager()
|
|
63
|
+
base_url = server.get_server_url()
|
|
64
|
+
|
|
65
|
+
client = EmdashClient(base_url)
|
|
66
|
+
renderer = SSERenderer(console=console, verbose=not quiet)
|
|
67
|
+
|
|
68
|
+
options = {
|
|
69
|
+
"mode": mode,
|
|
70
|
+
"no_graph_tools": no_graph_tools,
|
|
71
|
+
"save": save,
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
if task:
|
|
75
|
+
# Single task mode
|
|
76
|
+
run_single_task(client, renderer, task, model, max_iterations, options)
|
|
77
|
+
else:
|
|
78
|
+
# Interactive REPL mode
|
|
79
|
+
run_interactive(client, renderer, model, max_iterations, options)
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
@agent.command("sessions")
|
|
83
|
+
def list_sessions():
|
|
84
|
+
"""List active agent sessions."""
|
|
85
|
+
server = get_server_manager()
|
|
86
|
+
base_url = server.get_server_url()
|
|
87
|
+
|
|
88
|
+
client = EmdashClient(base_url)
|
|
89
|
+
sessions = client.list_sessions()
|
|
90
|
+
|
|
91
|
+
if not sessions:
|
|
92
|
+
console.print("[dim]No active sessions[/dim]")
|
|
93
|
+
return
|
|
94
|
+
|
|
95
|
+
for s in sessions:
|
|
96
|
+
console.print(
|
|
97
|
+
f" {s['session_id'][:8]}... "
|
|
98
|
+
f"[dim]({s.get('model', 'unknown')}, "
|
|
99
|
+
f"{s.get('message_count', 0)} messages)[/dim]"
|
|
100
|
+
)
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
"""Constants and enums for the agent CLI."""
|
|
2
|
+
|
|
3
|
+
from enum import Enum
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class AgentMode(Enum):
|
|
7
|
+
"""Agent operation modes."""
|
|
8
|
+
PLAN = "plan"
|
|
9
|
+
CODE = "code"
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
# Subcommands for slash commands that have them
|
|
13
|
+
SLASH_SUBCOMMANDS = {
|
|
14
|
+
"/telegram": {
|
|
15
|
+
"setup": "Configure bot token and authorize chats",
|
|
16
|
+
"connect": "Start the Telegram bridge",
|
|
17
|
+
"status": "Show current configuration",
|
|
18
|
+
"test": "Send a test message",
|
|
19
|
+
"disconnect": "Disable Telegram integration",
|
|
20
|
+
"settings": "View/modify settings",
|
|
21
|
+
},
|
|
22
|
+
"/session": {
|
|
23
|
+
"save": "Save current session (e.g., /session save my-task)",
|
|
24
|
+
"load": "Load a saved session",
|
|
25
|
+
"list": "List all saved sessions",
|
|
26
|
+
"delete": "Delete a saved session",
|
|
27
|
+
},
|
|
28
|
+
"/agents": {
|
|
29
|
+
"create": "Create a new agent",
|
|
30
|
+
"show": "Show agent details",
|
|
31
|
+
"edit": "Edit an existing agent",
|
|
32
|
+
"delete": "Delete an agent",
|
|
33
|
+
"list": "List all agents",
|
|
34
|
+
},
|
|
35
|
+
"/hooks": {
|
|
36
|
+
"list": "List all hooks",
|
|
37
|
+
"add": "Add a new hook",
|
|
38
|
+
"remove": "Remove a hook",
|
|
39
|
+
"toggle": "Enable/disable a hook",
|
|
40
|
+
},
|
|
41
|
+
"/rules": {
|
|
42
|
+
"list": "List all rules",
|
|
43
|
+
"add": "Add a new rule",
|
|
44
|
+
"delete": "Delete a rule",
|
|
45
|
+
},
|
|
46
|
+
"/skills": {
|
|
47
|
+
"list": "List all skills",
|
|
48
|
+
"show": "Show skill details",
|
|
49
|
+
"add": "Add a new skill",
|
|
50
|
+
"delete": "Delete a skill",
|
|
51
|
+
},
|
|
52
|
+
"/index": {
|
|
53
|
+
"status": "Show index status",
|
|
54
|
+
"start": "Start indexing",
|
|
55
|
+
"hook": "Manage index hooks (install/uninstall)",
|
|
56
|
+
},
|
|
57
|
+
"/mcp": {
|
|
58
|
+
"list": "List MCP servers",
|
|
59
|
+
"edit": "Edit MCP configuration",
|
|
60
|
+
},
|
|
61
|
+
"/auth": {
|
|
62
|
+
"login": "Login to GitHub",
|
|
63
|
+
"logout": "Logout from GitHub",
|
|
64
|
+
"status": "Show auth status",
|
|
65
|
+
},
|
|
66
|
+
"/registry": {
|
|
67
|
+
"skills": "Browse skills",
|
|
68
|
+
"rules": "Browse rules",
|
|
69
|
+
"agents": "Browse agents",
|
|
70
|
+
"verifiers": "Browse verifiers",
|
|
71
|
+
"install": "Install from registry",
|
|
72
|
+
},
|
|
73
|
+
"/verify": {
|
|
74
|
+
"run": "Run verification checks",
|
|
75
|
+
"list": "List available verifiers",
|
|
76
|
+
"add": "Add a verifier",
|
|
77
|
+
},
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
# Slash commands available in interactive mode
|
|
81
|
+
SLASH_COMMANDS = {
|
|
82
|
+
# Mode switching
|
|
83
|
+
"/plan": "Switch to plan mode (explore codebase, create plans)",
|
|
84
|
+
"/code": "Switch to code mode (execute file changes)",
|
|
85
|
+
"/mode": "Show current mode",
|
|
86
|
+
# Generation commands
|
|
87
|
+
"/pr [url]": "Review a pull request",
|
|
88
|
+
"/projectmd": "Generate PROJECT.md for the codebase",
|
|
89
|
+
"/research [goal]": "Deep research on a topic",
|
|
90
|
+
# Status commands
|
|
91
|
+
"/status": "Show index and PROJECT.md status",
|
|
92
|
+
"/diff": "Show uncommitted changes in GitHub-style diff view",
|
|
93
|
+
"/agents": "Manage agents (interactive menu, or /agents [create|show|edit|delete] <name>)",
|
|
94
|
+
# Todo management
|
|
95
|
+
"/todos": "Show current agent todo list",
|
|
96
|
+
"/todo-add [title]": "Add a todo item for the agent (e.g., /todo-add Fix tests)",
|
|
97
|
+
# Session management
|
|
98
|
+
"/session": "Save, load, or list sessions (e.g., /session save my-task)",
|
|
99
|
+
"/spec": "Show current specification",
|
|
100
|
+
"/reset": "Reset session state",
|
|
101
|
+
# Hooks
|
|
102
|
+
"/hooks": "Manage hooks (list, add, remove, toggle)",
|
|
103
|
+
# Rules
|
|
104
|
+
"/rules": "Manage rules (list, add, delete)",
|
|
105
|
+
# Skills
|
|
106
|
+
"/skills": "Manage skills (list, show, add, delete)",
|
|
107
|
+
# Index
|
|
108
|
+
"/index": "Manage codebase index (status, start, hook install/uninstall)",
|
|
109
|
+
# MCP
|
|
110
|
+
"/mcp": "Manage global MCP servers (list, edit)",
|
|
111
|
+
# Registry
|
|
112
|
+
"/registry": "Browse and install community skills, rules, agents, verifiers",
|
|
113
|
+
# Auth
|
|
114
|
+
"/auth": "GitHub authentication (login, logout, status)",
|
|
115
|
+
# Context
|
|
116
|
+
"/context": "Show current context frame (tokens, reranked items)",
|
|
117
|
+
"/compact": "Compact message history using LLM summarization",
|
|
118
|
+
# Image
|
|
119
|
+
"/paste": "Attach image from clipboard (or use Ctrl+V)",
|
|
120
|
+
# Diagnostics
|
|
121
|
+
"/doctor": "Check Python environment and diagnose issues",
|
|
122
|
+
# Verification
|
|
123
|
+
"/verify": "Run verification checks on current work",
|
|
124
|
+
"/verify-loop [task]": "Run task in loop until verifications pass",
|
|
125
|
+
# Setup wizard
|
|
126
|
+
"/setup": "Setup wizard for rules, agents, skills, and verifiers",
|
|
127
|
+
# Telegram integration
|
|
128
|
+
"/telegram": "Telegram integration (setup, connect, status, test)",
|
|
129
|
+
"/help": "Show available commands",
|
|
130
|
+
"/quit": "Exit the agent",
|
|
131
|
+
}
|