emdash-cli 0.1.17__py3-none-any.whl → 0.1.25__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.
@@ -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
- def render_stream(self, lines: Iterator[str]) -> dict:
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
- self.console.print(f" [dim]┃[/dim] [dim italic]💭 {message}[/dim italic]")
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,42 @@ 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", {})
614
+ largest_messages = adding.get("largest_messages", [])
512
615
 
513
616
  # Get reading stats
514
617
  item_count = reading.get("item_count", 0)
515
618
 
516
619
  # Only show if there's something to report
517
- if step_count == 0 and item_count == 0:
620
+ if step_count == 0 and item_count == 0 and context_tokens == 0:
518
621
  return
519
622
 
520
623
  self.console.print()
521
624
  self.console.print("[dim]───── Context Frame ─────[/dim]")
522
625
 
523
- # Show stats line
626
+ # Show total context
627
+ if context_tokens > 0:
628
+ self.console.print(f" [bold]Total: {context_tokens:,} tokens[/bold]")
629
+
630
+ # Show breakdown
631
+ if context_breakdown:
632
+ breakdown_parts = []
633
+ for key, tokens in context_breakdown.items():
634
+ if tokens > 0:
635
+ breakdown_parts.append(f"{key}: {tokens:,}")
636
+ if breakdown_parts:
637
+ self.console.print(f" [dim]Breakdown: {' | '.join(breakdown_parts)}[/dim]")
638
+
639
+ # Show largest messages (context hogs)
640
+ if largest_messages:
641
+ self.console.print(f" [yellow]Largest messages:[/yellow]")
642
+ for msg in largest_messages[:5]:
643
+ label = msg.get("label", "unknown")
644
+ tokens = msg.get("tokens", 0)
645
+ preview = msg.get("preview", "")[:50].replace("\n", " ")
646
+ self.console.print(f" [dim]{tokens:,} tokens[/dim] - {label}: {preview}...")
647
+
648
+ # Show other stats
524
649
  stats = []
525
650
  if step_count > 0:
526
651
  stats.append(f"{step_count} steps")
@@ -528,8 +653,6 @@ class SSERenderer:
528
653
  stats.append(f"{entities_found} entities")
529
654
  if item_count > 0:
530
655
  stats.append(f"{item_count} context items")
531
- if context_tokens > 0:
532
- stats.append(f"{context_tokens:,} context tokens")
533
656
 
534
657
  if stats:
535
658
  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.17
3
+ Version: 0.1.25
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
@@ -11,7 +11,7 @@ Classifier: Programming Language :: Python :: 3.12
11
11
  Classifier: Programming Language :: Python :: 3.13
12
12
  Classifier: Programming Language :: Python :: 3.14
13
13
  Requires-Dist: click (>=8.1.7,<9.0.0)
14
- Requires-Dist: emdash-core (>=0.1.17)
14
+ Requires-Dist: emdash-core (>=0.1.25)
15
15
  Requires-Dist: httpx (>=0.25.0)
16
16
  Requires-Dist: prompt_toolkit (>=3.0.43,<4.0.0)
17
17
  Requires-Dist: rich (>=13.7.0)
@@ -1,7 +1,7 @@
1
1
  emdash_cli/__init__.py,sha256=Rnn2O7B8OCEKlVtNRbWOU2-GN75_KLmhEJgOZzY-KwE,232
2
- emdash_cli/client.py,sha256=1Ri-BpZPfnZuXRhN8sCLna2SRhhr_AVDC_0CeTQPOhM,16275
3
- emdash_cli/commands/__init__.py,sha256=zf0lQ6S1UgoIg6NS7Oehxnb3iGD0Wxai0QU1nobgi9U,671
4
- emdash_cli/commands/agent.py,sha256=aYN9nLL30ETJKewo42d8U7QY9DGqCMTHCpIPiqJGlLA,30323
2
+ emdash_cli/client.py,sha256=sPgX2CEfiHc4TXN9TYqFhc8DVKgPyk20Efm66KQAOhE,16761
3
+ emdash_cli/commands/__init__.py,sha256=D9edXBHm69tueUtE4DggTA1_Yjsl9YZaKjBVDY2D_gQ,712
4
+ emdash_cli/commands/agent.py,sha256=RCgSYW6OBx3UFGVAM_byhyrtBKlsXN2xfJaT3w51uDY,27735
5
5
  emdash_cli/commands/analyze.py,sha256=c9ztbv0Ra7g2AlDmMOy-9L51fDVuoqbuzxnRfomoFIQ,4403
6
6
  emdash_cli/commands/auth.py,sha256=SpWdqO1bJCgt4x1B4Pr7hNOucwTuBFJ1oGPOzXtvwZM,3816
7
7
  emdash_cli/commands/db.py,sha256=nZK7gLDVE2lAQVYrMx6Swscml5OAtkbg-EcSNSvRIlA,2922
@@ -13,14 +13,16 @@ emdash_cli/commands/research.py,sha256=xtI9_9emY7-rGQD5xJALTxtgTFmI4dplYW148dtTa
13
13
  emdash_cli/commands/rules.py,sha256=n85CCG0WNIBEsUK9STJetPmZxoypQtest5BGPsXl0ac,2712
14
14
  emdash_cli/commands/search.py,sha256=DrSv_oN2xF1NaKCBICdyII7eupVRsDQ2ysW-TPSU0X0,1661
15
15
  emdash_cli/commands/server.py,sha256=UTmLAVolT0krN9xCtMcCSvmQZ9k1QwpFFmXGg9BulRY,3459
16
+ emdash_cli/commands/skills.py,sha256=8N4279Hr8u2L8AgVjSTRVBLJBcXhN5DN7dn5fME62bs,9989
16
17
  emdash_cli/commands/spec.py,sha256=qafDmzKyRH035p3xTm_VTUsQLDZblIzIg-dxjEPv6tM,1494
17
18
  emdash_cli/commands/swarm.py,sha256=s_cntuorNdtNNTD2Qs1p2IcHghMrBMOQuturPS3y9mM,2661
18
19
  emdash_cli/commands/tasks.py,sha256=TdyunjSV5w7jpNFwv0fTL-_No5Fyvdm7Z2nXqxWSJec,1635
19
20
  emdash_cli/commands/team.py,sha256=K1-IJg6iG-9HMF_3JmpNDlNs1PYbb-ThFHU9KU_jKRo,1430
20
- emdash_cli/main.py,sha256=pxUzyh01R-FDETMRf8nrBvP9QW3DVgeTJZ1Vsradw-E,2502
21
+ emdash_cli/keyboard.py,sha256=haYYAuhYGtdjomzhIFy_3Z3eN3BXfMdb4uRQjwB0tbk,4593
22
+ emdash_cli/main.py,sha256=c-faWp-jzf9a0BbXhVoPvPQfGWSryXpYfswehqZCYPM,2593
21
23
  emdash_cli/server_manager.py,sha256=RrLteSHUmcFV4cyHJAEmgM9qHru2mJS08QNLWno6Y3Y,7051
22
- emdash_cli/sse_renderer.py,sha256=55lDLzYngrWmXstb7PNpQcWi2G4u47qWaYqRJjqMUYo,18587
23
- emdash_cli-0.1.17.dist-info/METADATA,sha256=u7puOJ_vO_Nc_RU7RSCsQOoXV2rSTCd3zEfwpueg3o0,662
24
- emdash_cli-0.1.17.dist-info/WHEEL,sha256=zp0Cn7JsFoX2ATtOhtaFYIiE2rmFAD4OcMhtUki8W3U,88
25
- emdash_cli-0.1.17.dist-info/entry_points.txt,sha256=31CuYD0k-tM8csFWDunc-JoZTxXaifj3oIXz4V0p6F0,122
26
- emdash_cli-0.1.17.dist-info/RECORD,,
24
+ emdash_cli/sse_renderer.py,sha256=PEbD53ZohMp9yvii_1ELGwVKb8nnA_n17jICeaURkuY,23738
25
+ emdash_cli-0.1.25.dist-info/METADATA,sha256=RFuFUhHJlRcpfzwztnQXTWheB853OVlyWd1tAIzRKsE,662
26
+ emdash_cli-0.1.25.dist-info/WHEEL,sha256=zp0Cn7JsFoX2ATtOhtaFYIiE2rmFAD4OcMhtUki8W3U,88
27
+ emdash_cli-0.1.25.dist-info/entry_points.txt,sha256=31CuYD0k-tM8csFWDunc-JoZTxXaifj3oIXz4V0p6F0,122
28
+ emdash_cli-0.1.25.dist-info/RECORD,,