emdash-cli 0.1.30__py3-none-any.whl → 0.1.35__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/__init__.py +15 -0
- emdash_cli/client.py +121 -0
- emdash_cli/commands/agent.py +493 -28
- emdash_cli/session_store.py +321 -0
- emdash_cli/sse_renderer.py +224 -109
- {emdash_cli-0.1.30.dist-info → emdash_cli-0.1.35.dist-info}/METADATA +2 -2
- {emdash_cli-0.1.30.dist-info → emdash_cli-0.1.35.dist-info}/RECORD +9 -8
- {emdash_cli-0.1.30.dist-info → emdash_cli-0.1.35.dist-info}/WHEEL +0 -0
- {emdash_cli-0.1.30.dist-info → emdash_cli-0.1.35.dist-info}/entry_points.txt +0 -0
emdash_cli/__init__.py
CHANGED
|
@@ -1,6 +1,21 @@
|
|
|
1
1
|
"""EmDash CLI - Command-line interface for code intelligence."""
|
|
2
2
|
|
|
3
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
|
|
4
19
|
|
|
5
20
|
try:
|
|
6
21
|
__version__ = version("emdash-cli")
|
emdash_cli/client.py
CHANGED
|
@@ -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,123 @@ 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
|
+
|
|
141
262
|
def list_sessions(self) -> list[dict]:
|
|
142
263
|
"""List active agent sessions.
|
|
143
264
|
|