emdash-cli 0.1.17__py3-none-any.whl → 0.1.30__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 +42 -20
- emdash_cli/clipboard.py +123 -0
- emdash_cli/commands/__init__.py +2 -0
- emdash_cli/commands/agent.py +193 -236
- emdash_cli/commands/skills.py +337 -0
- emdash_cli/keyboard.py +146 -0
- emdash_cli/main.py +5 -1
- emdash_cli/sse_renderer.py +124 -11
- {emdash_cli-0.1.17.dist-info → emdash_cli-0.1.30.dist-info}/METADATA +4 -2
- {emdash_cli-0.1.17.dist-info → emdash_cli-0.1.30.dist-info}/RECORD +12 -9
- {emdash_cli-0.1.17.dist-info → emdash_cli-0.1.30.dist-info}/WHEEL +0 -0
- {emdash_cli-0.1.17.dist-info → emdash_cli-0.1.30.dist-info}/entry_points.txt +0 -0
emdash_cli/sse_renderer.py
CHANGED
|
@@ -42,6 +42,7 @@ class SSERenderer:
|
|
|
42
42
|
self._session_id = None
|
|
43
43
|
self._spec = None
|
|
44
44
|
self._spec_submitted = False
|
|
45
|
+
self._plan_submitted = None # Plan data when submit_plan tool is called
|
|
45
46
|
self._pending_clarification = None
|
|
46
47
|
|
|
47
48
|
# Live display state
|
|
@@ -61,17 +62,27 @@ class SSERenderer:
|
|
|
61
62
|
self._spinner_message = "thinking"
|
|
62
63
|
self._spinner_lock = threading.Lock()
|
|
63
64
|
|
|
64
|
-
|
|
65
|
+
# Extended thinking storage
|
|
66
|
+
self._last_thinking: Optional[str] = None
|
|
67
|
+
|
|
68
|
+
def render_stream(
|
|
69
|
+
self,
|
|
70
|
+
lines: Iterator[str],
|
|
71
|
+
interrupt_event: Optional[threading.Event] = None,
|
|
72
|
+
) -> dict:
|
|
65
73
|
"""Render SSE stream to terminal.
|
|
66
74
|
|
|
67
75
|
Args:
|
|
68
76
|
lines: Iterator of SSE lines from HTTP response
|
|
77
|
+
interrupt_event: Optional event to signal interruption (e.g., ESC pressed)
|
|
69
78
|
|
|
70
79
|
Returns:
|
|
71
|
-
Dict with session_id, content, spec, and other metadata
|
|
80
|
+
Dict with session_id, content, spec, interrupted flag, and other metadata
|
|
72
81
|
"""
|
|
73
82
|
current_event = None
|
|
74
83
|
final_response = ""
|
|
84
|
+
interrupted = False
|
|
85
|
+
self._last_thinking = None # Reset thinking storage
|
|
75
86
|
|
|
76
87
|
# Start spinner while waiting for first event
|
|
77
88
|
if self.verbose:
|
|
@@ -79,6 +90,13 @@ class SSERenderer:
|
|
|
79
90
|
|
|
80
91
|
try:
|
|
81
92
|
for line in lines:
|
|
93
|
+
# Check for interrupt signal
|
|
94
|
+
if interrupt_event and interrupt_event.is_set():
|
|
95
|
+
self._stop_spinner()
|
|
96
|
+
self.console.print("\n[yellow]Interrupted[/yellow]")
|
|
97
|
+
interrupted = True
|
|
98
|
+
break
|
|
99
|
+
|
|
82
100
|
line = line.strip()
|
|
83
101
|
|
|
84
102
|
if line.startswith("event: "):
|
|
@@ -108,7 +126,10 @@ class SSERenderer:
|
|
|
108
126
|
"session_id": self._session_id,
|
|
109
127
|
"spec": self._spec,
|
|
110
128
|
"spec_submitted": self._spec_submitted,
|
|
129
|
+
"plan_submitted": self._plan_submitted,
|
|
111
130
|
"clarification": self._pending_clarification,
|
|
131
|
+
"interrupted": interrupted,
|
|
132
|
+
"thinking": self._last_thinking,
|
|
112
133
|
}
|
|
113
134
|
|
|
114
135
|
def _start_spinner(self, message: str = "thinking") -> None:
|
|
@@ -185,6 +206,8 @@ class SSERenderer:
|
|
|
185
206
|
return self._render_response(data)
|
|
186
207
|
elif event_type == "clarification":
|
|
187
208
|
self._render_clarification(data)
|
|
209
|
+
elif event_type == "plan_submitted":
|
|
210
|
+
self._render_plan_submitted(data)
|
|
188
211
|
elif event_type == "error":
|
|
189
212
|
self._render_error(data)
|
|
190
213
|
elif event_type == "warning":
|
|
@@ -408,12 +431,32 @@ class SSERenderer:
|
|
|
408
431
|
return " ".join(parts)
|
|
409
432
|
|
|
410
433
|
def _render_thinking(self, data: dict) -> None:
|
|
411
|
-
"""Render thinking event.
|
|
434
|
+
"""Render thinking event.
|
|
435
|
+
|
|
436
|
+
Handles both short progress messages and extended thinking content.
|
|
437
|
+
"""
|
|
412
438
|
if not self.verbose:
|
|
413
439
|
return
|
|
414
440
|
|
|
415
441
|
message = data.get("message", "")
|
|
416
|
-
|
|
442
|
+
|
|
443
|
+
# Check if this is extended thinking (long content) vs short progress message
|
|
444
|
+
if len(message) > 200:
|
|
445
|
+
# Extended thinking - show summary with collapsible indicator
|
|
446
|
+
self._stop_spinner()
|
|
447
|
+
lines = message.strip().split("\n")
|
|
448
|
+
preview = lines[0][:80] + "..." if len(lines[0]) > 80 else lines[0]
|
|
449
|
+
line_count = len(lines)
|
|
450
|
+
char_count = len(message)
|
|
451
|
+
|
|
452
|
+
self.console.print(f" [dim]┃[/dim] [dim italic]💭 Thinking ({char_count:,} chars, {line_count} lines)[/dim italic]")
|
|
453
|
+
self.console.print(f" [dim]┃[/dim] [dim] {preview}[/dim]")
|
|
454
|
+
|
|
455
|
+
# Store thinking for potential later display
|
|
456
|
+
self._last_thinking = message
|
|
457
|
+
else:
|
|
458
|
+
# Short progress message
|
|
459
|
+
self.console.print(f" [dim]┃[/dim] [dim italic]💭 {message}[/dim italic]")
|
|
417
460
|
|
|
418
461
|
def _render_progress(self, data: dict) -> None:
|
|
419
462
|
"""Render progress event."""
|
|
@@ -472,6 +515,67 @@ class SSERenderer:
|
|
|
472
515
|
else:
|
|
473
516
|
self._pending_clarification = None
|
|
474
517
|
|
|
518
|
+
def _render_plan_submitted(self, data: dict) -> None:
|
|
519
|
+
"""Render plan submission event and store for menu display."""
|
|
520
|
+
from rich.panel import Panel
|
|
521
|
+
from rich.table import Table
|
|
522
|
+
from rich.text import Text
|
|
523
|
+
|
|
524
|
+
title = data.get("title", "Plan")
|
|
525
|
+
summary = data.get("summary", "")
|
|
526
|
+
files_to_modify = data.get("files_to_modify", [])
|
|
527
|
+
implementation_steps = data.get("implementation_steps", [])
|
|
528
|
+
risks = data.get("risks", [])
|
|
529
|
+
testing_strategy = data.get("testing_strategy", "")
|
|
530
|
+
|
|
531
|
+
# Store the plan data for the CLI to show the menu
|
|
532
|
+
self._plan_submitted = data
|
|
533
|
+
|
|
534
|
+
# Build plan display
|
|
535
|
+
self.console.print()
|
|
536
|
+
self.console.print(Panel(
|
|
537
|
+
f"[bold]{title}[/bold]\n\n{summary}",
|
|
538
|
+
title="[cyan]📋 Plan[/cyan]",
|
|
539
|
+
border_style="cyan",
|
|
540
|
+
))
|
|
541
|
+
|
|
542
|
+
# Critical Files table (always shown)
|
|
543
|
+
if files_to_modify:
|
|
544
|
+
files_table = Table(title="Critical Files", show_header=True, header_style="bold cyan")
|
|
545
|
+
files_table.add_column("File", style="yellow")
|
|
546
|
+
files_table.add_column("Lines", style="dim")
|
|
547
|
+
files_table.add_column("Changes", style="white")
|
|
548
|
+
|
|
549
|
+
for f in files_to_modify:
|
|
550
|
+
if isinstance(f, dict):
|
|
551
|
+
files_table.add_row(
|
|
552
|
+
f.get("path", ""),
|
|
553
|
+
f.get("lines", ""),
|
|
554
|
+
f.get("changes", "")
|
|
555
|
+
)
|
|
556
|
+
else:
|
|
557
|
+
files_table.add_row(str(f), "", "")
|
|
558
|
+
|
|
559
|
+
self.console.print(files_table)
|
|
560
|
+
|
|
561
|
+
# Implementation Steps (only if provided)
|
|
562
|
+
if implementation_steps:
|
|
563
|
+
self.console.print("\n[bold cyan]Implementation Steps[/bold cyan]")
|
|
564
|
+
for i, step in enumerate(implementation_steps, 1):
|
|
565
|
+
self.console.print(f" [dim]{i}.[/dim] {step}")
|
|
566
|
+
|
|
567
|
+
# Risks (only if provided)
|
|
568
|
+
if risks:
|
|
569
|
+
self.console.print("\n[bold yellow]⚠ Risks[/bold yellow]")
|
|
570
|
+
for risk in risks:
|
|
571
|
+
self.console.print(f" [yellow]•[/yellow] {risk}")
|
|
572
|
+
|
|
573
|
+
# Testing (only if provided)
|
|
574
|
+
if testing_strategy:
|
|
575
|
+
self.console.print(f"\n[bold green]Testing:[/bold green] {testing_strategy}")
|
|
576
|
+
|
|
577
|
+
self.console.print()
|
|
578
|
+
|
|
475
579
|
def _render_error(self, data: dict) -> None:
|
|
476
580
|
"""Render error event."""
|
|
477
581
|
message = data.get("message", "Unknown error")
|
|
@@ -499,9 +603,6 @@ class SSERenderer:
|
|
|
499
603
|
|
|
500
604
|
def _render_context_frame(self, data: dict) -> None:
|
|
501
605
|
"""Render context frame update (post-agentic loop summary)."""
|
|
502
|
-
if not self.verbose:
|
|
503
|
-
return
|
|
504
|
-
|
|
505
606
|
adding = data.get("adding") or {}
|
|
506
607
|
reading = data.get("reading") or {}
|
|
507
608
|
|
|
@@ -509,18 +610,32 @@ class SSERenderer:
|
|
|
509
610
|
step_count = adding.get("step_count", 0)
|
|
510
611
|
entities_found = adding.get("entities_found", 0)
|
|
511
612
|
context_tokens = adding.get("context_tokens", 0)
|
|
613
|
+
context_breakdown = adding.get("context_breakdown", {})
|
|
512
614
|
|
|
513
615
|
# Get reading stats
|
|
514
616
|
item_count = reading.get("item_count", 0)
|
|
515
617
|
|
|
516
618
|
# Only show if there's something to report
|
|
517
|
-
if step_count == 0 and item_count == 0:
|
|
619
|
+
if step_count == 0 and item_count == 0 and context_tokens == 0:
|
|
518
620
|
return
|
|
519
621
|
|
|
520
622
|
self.console.print()
|
|
521
623
|
self.console.print("[dim]───── Context Frame ─────[/dim]")
|
|
522
624
|
|
|
523
|
-
# Show
|
|
625
|
+
# Show total context
|
|
626
|
+
if context_tokens > 0:
|
|
627
|
+
self.console.print(f" [bold]Total: {context_tokens:,} tokens[/bold]")
|
|
628
|
+
|
|
629
|
+
# Show breakdown
|
|
630
|
+
if context_breakdown:
|
|
631
|
+
breakdown_parts = []
|
|
632
|
+
for key, tokens in context_breakdown.items():
|
|
633
|
+
if tokens > 0:
|
|
634
|
+
breakdown_parts.append(f"{key}: {tokens:,}")
|
|
635
|
+
if breakdown_parts:
|
|
636
|
+
self.console.print(f" [dim]Breakdown: {' | '.join(breakdown_parts)}[/dim]")
|
|
637
|
+
|
|
638
|
+
# Show other stats
|
|
524
639
|
stats = []
|
|
525
640
|
if step_count > 0:
|
|
526
641
|
stats.append(f"{step_count} steps")
|
|
@@ -528,8 +643,6 @@ class SSERenderer:
|
|
|
528
643
|
stats.append(f"{entities_found} entities")
|
|
529
644
|
if item_count > 0:
|
|
530
645
|
stats.append(f"{item_count} context items")
|
|
531
|
-
if context_tokens > 0:
|
|
532
|
-
stats.append(f"{context_tokens:,} context tokens")
|
|
533
646
|
|
|
534
647
|
if stats:
|
|
535
648
|
self.console.print(f" [dim]{' · '.join(stats)}[/dim]")
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: emdash-cli
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.30
|
|
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,8 +10,10 @@ 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
|
|
13
14
|
Requires-Dist: click (>=8.1.7,<9.0.0)
|
|
14
|
-
Requires-Dist: emdash-core (>=0.1.
|
|
15
|
+
Requires-Dist: emdash-core (>=0.1.30)
|
|
15
16
|
Requires-Dist: httpx (>=0.25.0)
|
|
17
|
+
Requires-Dist: pillow (>=10.0.0) ; extra == "images"
|
|
16
18
|
Requires-Dist: prompt_toolkit (>=3.0.43,<4.0.0)
|
|
17
19
|
Requires-Dist: rich (>=13.7.0)
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
emdash_cli/__init__.py,sha256=Rnn2O7B8OCEKlVtNRbWOU2-GN75_KLmhEJgOZzY-KwE,232
|
|
2
|
-
emdash_cli/client.py,sha256=
|
|
3
|
-
emdash_cli/
|
|
4
|
-
emdash_cli/commands/
|
|
2
|
+
emdash_cli/client.py,sha256=aQO_wF4XQaqig6RWhTA3FIJslweo7LsZHCk_GBVGdvw,17117
|
|
3
|
+
emdash_cli/clipboard.py,sha256=hcg5sbIhbixqzpJdonoFLGBlSo2AKjplNrWy5PGnqaY,3564
|
|
4
|
+
emdash_cli/commands/__init__.py,sha256=D9edXBHm69tueUtE4DggTA1_Yjsl9YZaKjBVDY2D_gQ,712
|
|
5
|
+
emdash_cli/commands/agent.py,sha256=dLNNyJNL9uzZcBapzFghdjb-Xa27PglpbFNAMPGa3qc,28772
|
|
5
6
|
emdash_cli/commands/analyze.py,sha256=c9ztbv0Ra7g2AlDmMOy-9L51fDVuoqbuzxnRfomoFIQ,4403
|
|
6
7
|
emdash_cli/commands/auth.py,sha256=SpWdqO1bJCgt4x1B4Pr7hNOucwTuBFJ1oGPOzXtvwZM,3816
|
|
7
8
|
emdash_cli/commands/db.py,sha256=nZK7gLDVE2lAQVYrMx6Swscml5OAtkbg-EcSNSvRIlA,2922
|
|
@@ -13,14 +14,16 @@ emdash_cli/commands/research.py,sha256=xtI9_9emY7-rGQD5xJALTxtgTFmI4dplYW148dtTa
|
|
|
13
14
|
emdash_cli/commands/rules.py,sha256=n85CCG0WNIBEsUK9STJetPmZxoypQtest5BGPsXl0ac,2712
|
|
14
15
|
emdash_cli/commands/search.py,sha256=DrSv_oN2xF1NaKCBICdyII7eupVRsDQ2ysW-TPSU0X0,1661
|
|
15
16
|
emdash_cli/commands/server.py,sha256=UTmLAVolT0krN9xCtMcCSvmQZ9k1QwpFFmXGg9BulRY,3459
|
|
17
|
+
emdash_cli/commands/skills.py,sha256=8N4279Hr8u2L8AgVjSTRVBLJBcXhN5DN7dn5fME62bs,9989
|
|
16
18
|
emdash_cli/commands/spec.py,sha256=qafDmzKyRH035p3xTm_VTUsQLDZblIzIg-dxjEPv6tM,1494
|
|
17
19
|
emdash_cli/commands/swarm.py,sha256=s_cntuorNdtNNTD2Qs1p2IcHghMrBMOQuturPS3y9mM,2661
|
|
18
20
|
emdash_cli/commands/tasks.py,sha256=TdyunjSV5w7jpNFwv0fTL-_No5Fyvdm7Z2nXqxWSJec,1635
|
|
19
21
|
emdash_cli/commands/team.py,sha256=K1-IJg6iG-9HMF_3JmpNDlNs1PYbb-ThFHU9KU_jKRo,1430
|
|
20
|
-
emdash_cli/
|
|
22
|
+
emdash_cli/keyboard.py,sha256=haYYAuhYGtdjomzhIFy_3Z3eN3BXfMdb4uRQjwB0tbk,4593
|
|
23
|
+
emdash_cli/main.py,sha256=c-faWp-jzf9a0BbXhVoPvPQfGWSryXpYfswehqZCYPM,2593
|
|
21
24
|
emdash_cli/server_manager.py,sha256=RrLteSHUmcFV4cyHJAEmgM9qHru2mJS08QNLWno6Y3Y,7051
|
|
22
|
-
emdash_cli/sse_renderer.py,sha256=
|
|
23
|
-
emdash_cli-0.1.
|
|
24
|
-
emdash_cli-0.1.
|
|
25
|
-
emdash_cli-0.1.
|
|
26
|
-
emdash_cli-0.1.
|
|
25
|
+
emdash_cli/sse_renderer.py,sha256=aDOoHKglOkaYEXuKg937mH6yFPDxjU7Rqa_-APyM9Dc,23215
|
|
26
|
+
emdash_cli-0.1.30.dist-info/METADATA,sha256=czNXf-GzyfHKHe8g8Dfd7Bm7kI189H-VLRpFDVgtejc,738
|
|
27
|
+
emdash_cli-0.1.30.dist-info/WHEEL,sha256=zp0Cn7JsFoX2ATtOhtaFYIiE2rmFAD4OcMhtUki8W3U,88
|
|
28
|
+
emdash_cli-0.1.30.dist-info/entry_points.txt,sha256=31CuYD0k-tM8csFWDunc-JoZTxXaifj3oIXz4V0p6F0,122
|
|
29
|
+
emdash_cli-0.1.30.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|