emdash-cli 0.1.67__py3-none-any.whl → 0.1.70__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.
@@ -9,6 +9,74 @@ class AgentMode(Enum):
9
9
  CODE = "code"
10
10
 
11
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
+
12
80
  # Slash commands available in interactive mode
13
81
  SLASH_COMMANDS = {
14
82
  # Mode switching
@@ -47,6 +47,9 @@ def handle_telegram(args: str) -> None:
47
47
  elif subcommand == "settings":
48
48
  _handle_settings(subargs)
49
49
 
50
+ elif subcommand == "commands":
51
+ _handle_commands()
52
+
50
53
  else:
51
54
  console.print(f"[{Colors.WARNING}]Unknown subcommand: {subcommand}[/{Colors.WARNING}]")
52
55
  console.print(f"[{Colors.DIM}]Run /telegram help for usage[/{Colors.DIM}]")
@@ -81,6 +84,7 @@ def _show_telegram_help() -> None:
81
84
  ("/telegram connect", "Start the Telegram bridge (foreground)"),
82
85
  ("/telegram disconnect", "Disable Telegram integration"),
83
86
  ("/telegram settings", "View/modify settings"),
87
+ ("/telegram commands", "Show BotFather command list to copy"),
84
88
  ]
85
89
 
86
90
  console.print(f" [{Colors.DIM}]Commands:[/{Colors.DIM}]")
@@ -473,3 +477,47 @@ def _handle_settings(args: str) -> None:
473
477
 
474
478
  save_config(config)
475
479
  console.print(f" [{Colors.SUCCESS}]Setting updated: {key} = {value}[/{Colors.SUCCESS}]")
480
+
481
+
482
+ def _handle_commands() -> None:
483
+ """Show BotFather command list for easy copy-paste."""
484
+ from ..constants import SLASH_COMMANDS
485
+
486
+ console.print()
487
+ console.print(f"[{Colors.MUTED}]{header('BotFather Commands', SEPARATOR_WIDTH)}[/{Colors.MUTED}]")
488
+ console.print()
489
+ console.print(f" [{Colors.DIM}]Copy the following and paste to @BotFather after /setcommands:[/{Colors.DIM}]")
490
+ console.print()
491
+ console.print(f" [{Colors.MUTED}]{'─' * 60}[/{Colors.MUTED}]")
492
+
493
+ # Generate command list in BotFather format
494
+ # Format: command - description (no leading slash, underscores instead of hyphens)
495
+ for cmd, desc in SLASH_COMMANDS.items():
496
+ # Skip telegram and quit commands (not useful via Telegram)
497
+ if cmd in ("/telegram", "/quit", "/paste"):
498
+ continue
499
+
500
+ # Convert /cmd-name to cmd_name (BotFather format)
501
+ botfather_cmd = cmd.lstrip("/").replace("-", "_")
502
+
503
+ # Remove any argument placeholders from command
504
+ if " " in botfather_cmd:
505
+ botfather_cmd = botfather_cmd.split()[0]
506
+
507
+ # Truncate description if too long (BotFather has limits)
508
+ short_desc = desc.split("(")[0].strip() # Remove parenthetical notes
509
+ if len(short_desc) > 50:
510
+ short_desc = short_desc[:47] + "..."
511
+
512
+ console.print(f" {botfather_cmd} - {short_desc}")
513
+
514
+ console.print(f" [{Colors.MUTED}]{'─' * 60}[/{Colors.MUTED}]")
515
+ console.print()
516
+ console.print(f" [{Colors.DIM}]Steps:[/{Colors.DIM}]")
517
+ console.print(f" [{Colors.SUBTLE}]1. Open @BotFather in Telegram[/{Colors.SUBTLE}]")
518
+ console.print(f" [{Colors.SUBTLE}]2. Send /setcommands[/{Colors.SUBTLE}]")
519
+ console.print(f" [{Colors.SUBTLE}]3. Select your bot[/{Colors.SUBTLE}]")
520
+ console.print(f" [{Colors.SUBTLE}]4. Paste the command list above[/{Colors.SUBTLE}]")
521
+ console.print()
522
+ console.print(f"[{Colors.MUTED}]{footer(SEPARATOR_WIDTH)}[/{Colors.MUTED}]")
523
+ console.print()
@@ -10,7 +10,7 @@ from rich.console import Console
10
10
  from rich.panel import Panel
11
11
  from rich.markdown import Markdown
12
12
 
13
- from .constants import AgentMode, SLASH_COMMANDS
13
+ from .constants import AgentMode, SLASH_COMMANDS, SLASH_SUBCOMMANDS
14
14
  from .onboarding import is_first_run, run_onboarding
15
15
  from .help import show_command_help
16
16
  from .session_restore import get_recent_session, show_session_restore_prompt
@@ -177,6 +177,7 @@ def run_interactive(
177
177
  from prompt_toolkit.completion import Completer, Completion
178
178
  from prompt_toolkit.styles import Style
179
179
  from prompt_toolkit.key_binding import KeyBindings
180
+ from prompt_toolkit.formatted_text import FormattedText
180
181
 
181
182
  # Current mode
182
183
  current_mode = AgentMode(options.get("mode", "code"))
@@ -206,6 +207,10 @@ def run_interactive(
206
207
  "completion-menu.meta.completion": f"bg:#1a1a2e {Colors.MUTED}",
207
208
  "completion-menu.meta.completion.current": f"bg:#2a2a3e {Colors.SUBTLE}",
208
209
  "command": f"{Colors.PRIMARY} bold",
210
+ # Styled completion text
211
+ "slash-cmd": f"{Colors.ACCENT} bold",
212
+ "sub-cmd": f"{Colors.SUCCESS}",
213
+ "file-ref": f"{Colors.WARNING}",
209
214
  # Zen bottom toolbar styles
210
215
  "bottom-toolbar": f"bg:#1a1a1a {Colors.DIM}",
211
216
  "bottom-toolbar.brand": f"bg:#1a1a1a {Colors.PRIMARY}",
@@ -238,11 +243,14 @@ def run_interactive(
238
243
  rel_path = match.relative_to(cwd)
239
244
  except ValueError:
240
245
  rel_path = match
241
- # Replace from @ onwards
246
+ # Replace from @ onwards - styled display
247
+ styled_display = FormattedText([
248
+ ("class:file-ref", f"@{rel_path}")
249
+ ])
242
250
  yield Completion(
243
251
  f"@{rel_path}",
244
252
  start_position=-(len(query) + 1), # +1 for @
245
- display=str(rel_path),
253
+ display=styled_display,
246
254
  display_meta="file",
247
255
  )
248
256
  return
@@ -250,14 +258,45 @@ def run_interactive(
250
258
  # Handle slash commands
251
259
  if not text.startswith("/"):
252
260
  return
261
+
262
+ # Check if we're completing a subcommand (e.g., "/telegram " or "/telegram c")
263
+ parts = text.split(maxsplit=1)
264
+ base_cmd = parts[0]
265
+
266
+ # If we have a space after base command, show subcommands
267
+ if len(parts) > 1 or text.endswith(" "):
268
+ subcommand_prefix = parts[1] if len(parts) > 1 else ""
269
+ if base_cmd in SLASH_SUBCOMMANDS:
270
+ subcommands = SLASH_SUBCOMMANDS[base_cmd]
271
+ for subcmd, description in subcommands.items():
272
+ if subcmd.startswith(subcommand_prefix):
273
+ # Styled display for subcommand
274
+ styled_display = FormattedText([
275
+ ("class:sub-cmd", subcmd)
276
+ ])
277
+ yield Completion(
278
+ subcmd,
279
+ start_position=-len(subcommand_prefix),
280
+ display=styled_display,
281
+ display_meta=description,
282
+ )
283
+ return
284
+
285
+ # Otherwise show main slash commands
253
286
  for cmd, description in SLASH_COMMANDS.items():
254
287
  # Extract base command (e.g., "/pr" from "/pr [url]")
255
- base_cmd = cmd.split()[0]
256
- if base_cmd.startswith(text):
288
+ cmd_base = cmd.split()[0]
289
+ if cmd_base.startswith(text):
290
+ # Add space suffix for commands with subcommands to trigger subcommand completion
291
+ completion_text = cmd_base + " " if cmd_base in SLASH_SUBCOMMANDS else cmd_base
292
+ # Styled display for slash command
293
+ styled_display = FormattedText([
294
+ ("class:slash-cmd", cmd)
295
+ ])
257
296
  yield Completion(
258
- base_cmd,
297
+ completion_text,
259
298
  start_position=-len(text),
260
- display=cmd,
299
+ display=styled_display,
261
300
  display_meta=description,
262
301
  )
263
302
 
@@ -269,10 +308,17 @@ def run_interactive(
269
308
  # Key bindings: Enter submits, Alt+Enter inserts newline
270
309
  kb = KeyBindings()
271
310
 
272
- @kb.add("enter", eager=True)
311
+ @kb.add("enter")
273
312
  def submit_on_enter(event):
274
- """Submit on Enter."""
275
- event.current_buffer.validate_and_handle()
313
+ """Submit on Enter, or select completion if menu is open."""
314
+ buffer = event.current_buffer
315
+ # If completion menu is open, accept the selected completion
316
+ if buffer.complete_state and buffer.complete_state.current_completion:
317
+ completion = buffer.complete_state.current_completion
318
+ buffer.apply_completion(completion)
319
+ return
320
+ # Otherwise submit the input
321
+ buffer.validate_and_handle()
276
322
 
277
323
  @kb.add("escape", "enter") # Alt+Enter (Escape then Enter)
278
324
  @kb.add("c-j") # Ctrl+J as alternative for newline
@@ -21,6 +21,30 @@ from .formatter import SSEEventFormatter, TelegramMessage as FormattedMessage
21
21
  # Default EmDash server URL
22
22
  DEFAULT_SERVER_URL = "http://localhost:8765"
23
23
 
24
+ # Telegram-only commands (not forwarded to agent)
25
+ # All other /commands are forwarded to the EmDash agent
26
+ TELEGRAM_COMMANDS = {
27
+ "/start", # Telegram welcome message
28
+ "/stop", # Cancel current operation
29
+ "/cancel", # Cancel pending interaction
30
+ "/tgstatus", # Telegram bot status
31
+ "/tgsettings", # Telegram display settings
32
+ "/tghelp", # Telegram help
33
+ "/thinking", # Toggle thinking display
34
+ "/tools", # Toggle tool calls display
35
+ "/plan", # Switch to plan mode
36
+ "/code", # Switch to code mode
37
+ "/mode", # Show current mode
38
+ "/reset", # Reset session
39
+ }
40
+
41
+ # Map BotFather command format (underscores) to EmDash format (hyphens)
42
+ # BotFather doesn't allow hyphens in command names
43
+ COMMAND_ALIASES = {
44
+ "/todo_add": "/todo-add",
45
+ "/verify_loop": "/verify-loop",
46
+ }
47
+
24
48
 
25
49
  @dataclass
26
50
  class PendingInteraction:
@@ -50,6 +74,9 @@ class BridgeState:
50
74
  # Pending interactions requiring user response (per chat)
51
75
  pending: dict[int, PendingInteraction] = field(default_factory=dict)
52
76
 
77
+ # Current mode per chat (code or plan)
78
+ modes: dict[int, str] = field(default_factory=dict)
79
+
53
80
 
54
81
  class TelegramBridge:
55
82
  """Bridge between Telegram and EmDash agent.
@@ -175,40 +202,71 @@ class TelegramBridge:
175
202
  await self._process_agent_message(chat_id, text)
176
203
 
177
204
  async def _handle_command(self, chat_id: int, text: str) -> None:
178
- """Handle Telegram bot commands.
205
+ """Handle slash commands.
206
+
207
+ Telegram-specific commands (in TELEGRAM_COMMANDS) are handled locally.
208
+ All other slash commands are forwarded to the EmDash agent.
179
209
 
180
210
  Args:
181
211
  chat_id: Chat ID
182
- text: Command text
212
+ text: Command text (e.g., "/plan" or "/todo_add Fix tests")
183
213
  """
184
214
  parts = text.split(maxsplit=1)
185
215
  command = parts[0].lower()
186
216
  args = parts[1] if len(parts) > 1 else ""
187
217
 
218
+ # Handle Telegram-specific commands locally
219
+ if command in TELEGRAM_COMMANDS:
220
+ await self._handle_telegram_command(chat_id, command, args)
221
+ return
222
+
223
+ # Apply command aliases (BotFather format -> EmDash format)
224
+ if command in COMMAND_ALIASES:
225
+ command = COMMAND_ALIASES[command]
226
+
227
+ # Forward to agent as a slash command
228
+ message = f"{command} {args}".strip() if args else command
229
+ await self._process_agent_message(chat_id, message)
230
+
231
+ async def _handle_telegram_command(self, chat_id: int, command: str, args: str) -> None:
232
+ """Handle Telegram-specific commands.
233
+
234
+ Args:
235
+ chat_id: Chat ID
236
+ command: Command name (e.g., "/start")
237
+ args: Command arguments
238
+ """
188
239
  if command == "/start":
189
240
  await self._send_message(
190
241
  chat_id,
191
242
  "*EmDash Bot*\n\n"
192
243
  "Send me a message and I'll process it with the EmDash agent.\n\n"
193
- "*Commands:*\n"
194
- "/status - Show connection status\n"
244
+ "*Mode commands:*\n"
245
+ "/plan - Switch to plan mode\n"
246
+ "/code - Switch to code mode\n"
247
+ "/mode - Show current mode\n"
248
+ "/reset - Reset session\n\n"
249
+ "*Telegram-only commands:*\n"
195
250
  "/stop - Cancel current operation\n"
196
251
  "/cancel - Cancel pending interaction\n"
197
- "/compact - Toggle compact mode\n"
252
+ "/tgstatus - Show bot connection status\n"
253
+ "/tgsettings - Show display settings\n"
198
254
  "/thinking - Toggle showing agent thinking\n"
199
255
  "/tools - Toggle showing tool calls\n"
200
- "/settings - Show current settings\n"
201
- "/help - Show this help",
256
+ "/tghelp - Show this help",
202
257
  )
203
258
 
204
- elif command == "/status":
259
+ elif command == "/tgstatus":
205
260
  session_id = self.state.sessions.get(chat_id)
206
261
  pending = self.state.pending.get(chat_id)
262
+ current_mode = self.state.modes.get(chat_id, "code")
207
263
  status = "Connected" if session_id else "No active session"
208
264
  pending_status = f"\n*Pending:* {pending.type}" if pending else ""
265
+ mode_emoji = "📋" if current_mode == "plan" else "💻"
209
266
  await self._send_message(
210
267
  chat_id,
211
268
  f"*Status:* {status}\n"
269
+ f"*Mode:* {mode_emoji} {current_mode}\n"
212
270
  f"*Server:* `{self.server_url}`"
213
271
  f"{pending_status}",
214
272
  )
@@ -227,11 +285,6 @@ class TelegramBridge:
227
285
  else:
228
286
  await self._send_message(chat_id, "No pending interaction.")
229
287
 
230
- elif command == "/compact":
231
- self.config.settings.compact_mode = not self.config.settings.compact_mode
232
- status = "enabled" if self.config.settings.compact_mode else "disabled"
233
- await self._send_message(chat_id, f"Compact mode {status}.")
234
-
235
288
  elif command == "/thinking":
236
289
  self.config.settings.show_thinking = not self.config.settings.show_thinking
237
290
  status = "enabled" if self.config.settings.show_thinking else "disabled"
@@ -242,42 +295,98 @@ class TelegramBridge:
242
295
  status = "enabled" if self.config.settings.show_tool_calls else "disabled"
243
296
  await self._send_message(chat_id, f"Show tool calls {status}.")
244
297
 
245
- elif command == "/settings":
298
+ elif command == "/tgsettings":
246
299
  await self._send_message(
247
300
  chat_id,
248
- "*Current Settings:*\n\n"
249
- f"Compact mode: `{self.config.settings.compact_mode}`\n"
301
+ "*Telegram Display Settings:*\n\n"
250
302
  f"Show thinking: `{self.config.settings.show_thinking}`\n"
251
303
  f"Show tools: `{self.config.settings.show_tool_calls}`\n"
304
+ f"Compact mode: `{self.config.settings.compact_mode}`\n"
252
305
  f"Update interval: `{self.config.settings.update_interval_ms}ms`",
253
306
  )
254
307
 
255
- elif command == "/help":
308
+ elif command == "/tghelp":
256
309
  await self._send_message(
257
310
  chat_id,
258
- "*EmDash Bot Help*\n\n"
259
- "Send any message to interact with the EmDash agent.\n\n"
260
- "*Commands:*\n"
261
- "/start - Welcome message\n"
262
- "/status - Show connection status\n"
311
+ "*EmDash Telegram Bot*\n\n"
312
+ "Send any message or slash command to interact with the EmDash agent.\n\n"
313
+ "*Mode commands:*\n"
314
+ "/plan - Switch to plan mode (read-only exploration)\n"
315
+ "/code - Switch to code mode (execute changes)\n"
316
+ "/mode - Show current mode\n"
317
+ "/reset - Reset session\n\n"
318
+ "*Agent commands (forwarded):*\n"
319
+ "/todos - Show todo list\n"
320
+ "/status - Show project status\n"
321
+ "/help - Show all agent commands\n\n"
322
+ "*Telegram-only commands:*\n"
263
323
  "/stop - Cancel current operation\n"
264
324
  "/cancel - Cancel pending interaction\n"
265
- "/compact - Toggle compact mode\n"
266
- "/thinking - Toggle showing agent thinking\n"
267
- "/tools - Toggle showing tool calls\n"
268
- "/settings - Show current settings\n"
269
- "/help - This help message\n\n"
325
+ "/tgstatus - Bot connection status\n"
326
+ "/tgsettings - Display settings\n"
327
+ "/thinking - Toggle thinking display\n"
328
+ "/tools - Toggle tool calls display\n"
329
+ "/tghelp - This help message\n\n"
270
330
  "*Responding to questions:*\n"
271
- "When the agent asks a question with options, reply with the option number (1, 2, 3...) "
272
- "or type your answer directly.\n\n"
331
+ "Reply with option number (1, 2, 3...) or type your answer.\n\n"
273
332
  "*Plan approval:*\n"
274
- 'Reply "approve", "yes", or "y" to approve a plan.\n'
275
- 'Reply "reject", "no", or "n" to reject.\n'
276
- "Or type feedback to request changes.",
333
+ 'Reply "yes" to approve, "no" to reject, or type feedback.',
277
334
  )
278
335
 
279
- else:
280
- await self._send_message(chat_id, f"Unknown command: {command}")
336
+ elif command == "/plan":
337
+ # Switch to plan mode and reset session
338
+ self.state.modes[chat_id] = "plan"
339
+ if chat_id in self.state.sessions:
340
+ del self.state.sessions[chat_id]
341
+ await self._send_message(
342
+ chat_id,
343
+ "✅ Switched to *plan mode* (session reset)\n\n"
344
+ "_Plan mode explores the codebase and creates plans without making changes._",
345
+ )
346
+ else:
347
+ await self._send_message(
348
+ chat_id,
349
+ "✅ Switched to *plan mode*\n\n"
350
+ "_Plan mode explores the codebase and creates plans without making changes._",
351
+ )
352
+
353
+ elif command == "/code":
354
+ # Switch to code mode and reset session
355
+ self.state.modes[chat_id] = "code"
356
+ if chat_id in self.state.sessions:
357
+ del self.state.sessions[chat_id]
358
+ await self._send_message(
359
+ chat_id,
360
+ "✅ Switched to *code mode* (session reset)\n\n"
361
+ "_Code mode can execute changes and modify files._",
362
+ )
363
+ else:
364
+ await self._send_message(
365
+ chat_id,
366
+ "✅ Switched to *code mode*\n\n"
367
+ "_Code mode can execute changes and modify files._",
368
+ )
369
+
370
+ elif command == "/mode":
371
+ current_mode = self.state.modes.get(chat_id, "code")
372
+ if current_mode == "plan":
373
+ await self._send_message(
374
+ chat_id,
375
+ "📋 Current mode: *plan*\n\n"
376
+ "_Use /code to switch to code mode._",
377
+ )
378
+ else:
379
+ await self._send_message(
380
+ chat_id,
381
+ "💻 Current mode: *code*\n\n"
382
+ "_Use /plan to switch to plan mode._",
383
+ )
384
+
385
+ elif command == "/reset":
386
+ # Reset session for this chat
387
+ if chat_id in self.state.sessions:
388
+ del self.state.sessions[chat_id]
389
+ await self._send_message(chat_id, "🔄 Session reset.")
281
390
 
282
391
  async def _process_agent_message(self, chat_id: int, message: str) -> None:
283
392
  """Process a message through the EmDash agent.
@@ -311,7 +420,10 @@ class TelegramBridge:
311
420
  update_interval = self.config.settings.update_interval_ms / 1000.0
312
421
  has_sent_response = False
313
422
 
314
- async for event_type, data in self._stream_agent_chat(message, session_id):
423
+ # Get current mode for this chat (default to code)
424
+ current_mode = self.state.modes.get(chat_id, "code")
425
+
426
+ async for event_type, data in self._stream_agent_chat(message, session_id, current_mode):
315
427
  # Check if cancelled
316
428
  if not self.state.processing.get(chat_id):
317
429
  break
@@ -388,12 +500,14 @@ class TelegramBridge:
388
500
  self,
389
501
  message: str,
390
502
  session_id: str | None = None,
503
+ mode: str = "code",
391
504
  ) -> AsyncIterator[tuple[str, dict]]:
392
505
  """Stream agent chat response via SSE.
393
506
 
394
507
  Args:
395
508
  message: User message
396
509
  session_id: Optional session ID for continuity
510
+ mode: Agent mode (code or plan)
397
511
 
398
512
  Yields:
399
513
  Tuples of (event_type, data)
@@ -406,6 +520,7 @@ class TelegramBridge:
406
520
  "options": {
407
521
  "max_iterations": 50,
408
522
  "verbose": True,
523
+ "mode": mode,
409
524
  },
410
525
  }
411
526
 
@@ -279,14 +279,21 @@ class SSEEventFormatter:
279
279
  """Format final response event."""
280
280
  content = data.get("content", "")
281
281
 
282
+ # Check if we were streaming partial content (should update existing message)
283
+ was_streaming = bool(self.aggregator.content)
284
+
282
285
  # Reset aggregator
283
286
  self.aggregator.reset()
284
287
 
285
288
  # Format response with icon
286
289
  if self.compact:
287
- return TelegramMessage(text=content, parse_mode="Markdown")
290
+ return TelegramMessage(text=content, parse_mode="Markdown", is_update=was_streaming)
288
291
 
289
- return TelegramMessage(text=f"{ICON_RESPONSE}\n\n{content}", parse_mode="Markdown")
292
+ return TelegramMessage(
293
+ text=f"{ICON_RESPONSE}\n\n{content}",
294
+ parse_mode="Markdown",
295
+ is_update=was_streaming,
296
+ )
290
297
 
291
298
  def _format_error(self, data: dict) -> TelegramMessage:
292
299
  """Format error event."""
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: emdash-cli
3
- Version: 0.1.67
3
+ Version: 0.1.70
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.67)
14
+ Requires-Dist: emdash-core (>=0.1.70)
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)
@@ -4,7 +4,7 @@ emdash_cli/clipboard.py,sha256=3iwkfj4Od1gPSsMbIBc6sx9XH2XCNgvc5Uu2pPRGcpw,2371
4
4
  emdash_cli/commands/__init__.py,sha256=kApebp8AegR0ULBvRVSu36RpMrVluvKq1oN2W0f1onQ,721
5
5
  emdash_cli/commands/agent/__init__.py,sha256=Q02HtlODcid-YS_HDqBjbW6V8Xg9pYy0gWvrxn1EB9E,410
6
6
  emdash_cli/commands/agent/cli.py,sha256=bfRSRAo6exy3llBoHS81AgGKP36xJ3MyL9eoLBtnJW4,2975
7
- emdash_cli/commands/agent/constants.py,sha256=9v5WCV66wK0WYKfrFX4T-9LAT3be1QYtMdYRniH7qIU,2409
7
+ emdash_cli/commands/agent/constants.py,sha256=R67iPmrXXXG0qJgwKdgDxEWPd86lqDrxhfm_Zj_I91s,4446
8
8
  emdash_cli/commands/agent/file_utils.py,sha256=2YOKybPVGyjmImbqLHQOIL7zDKeVjQMWssAh9ZX1_Vc,5257
9
9
  emdash_cli/commands/agent/handlers/__init__.py,sha256=GxVjekZX-H0QaUs0t0-7mO9AVHOJhNzv4MdkuqvyHqc,1210
10
10
  emdash_cli/commands/agent/handlers/agents.py,sha256=T0f0qqYedHX80JCgCPTvHAuyYiLldZlXu7ICjsMnOOM,16002
@@ -19,11 +19,11 @@ emdash_cli/commands/agent/handlers/rules.py,sha256=N84ngO9yGIEMFIGlyd8Ga2pHpTKAY
19
19
  emdash_cli/commands/agent/handlers/sessions.py,sha256=hx4Mri4pGu0sb6xkotBYG8SkuhDbg40JntjbW8qQp9Y,6610
20
20
  emdash_cli/commands/agent/handlers/setup.py,sha256=i9k3WMSIcFlB2JdxWto5bW5C3Q93iK_qS5QKPV2f5qA,20840
21
21
  emdash_cli/commands/agent/handlers/skills.py,sha256=yL2GkChl9WG4fvs1-Ep3JnZfqBH2ZsCcPKkrrPyTPPM,15864
22
- emdash_cli/commands/agent/handlers/telegram.py,sha256=aDpgHEa2Zm6d8ngNGY28_gnuIihfCq5qcXJ3tBaydp4,18239
22
+ emdash_cli/commands/agent/handlers/telegram.py,sha256=vQq__-PkTd67PlZBkm3XB9gwmcpAij_H1Lo_cWOn7E0,20340
23
23
  emdash_cli/commands/agent/handlers/todos.py,sha256=otvtDKetsqcXKWRL82Ja8-FrpwTd-3iNRhAWCXboYqQ,4931
24
24
  emdash_cli/commands/agent/handlers/verify.py,sha256=5i4qbRbisRcFau8_Btpi-SbZDEyFb-pY9w8_WbE-qyQ,20919
25
25
  emdash_cli/commands/agent/help.py,sha256=-3L--ja02ikS163k4jUa7yvSAH6N7T1lZgY3ad8olrc,8730
26
- emdash_cli/commands/agent/interactive.py,sha256=aSkqCXozIICRLl-r3gtHAbtJiSo3mM-vWEXn6AUzP_M,32076
26
+ emdash_cli/commands/agent/interactive.py,sha256=VlXprjFVqYW0ym_jPiX3mS9CuUK_kofzMQfADnoInps,34440
27
27
  emdash_cli/commands/agent/menus.py,sha256=S6AylgFuAWmZHr-ix43sp_Y1U627Ltz2eyX6we5amhE,22865
28
28
  emdash_cli/commands/agent/onboarding.py,sha256=fAqY9zR1gGCnwT0o0h3QmWLRg-PQpiQ_SyulUKBsX78,21617
29
29
  emdash_cli/commands/agent/session_restore.py,sha256=cbGVlkNtcZFahFuCBWpDY0FIYBGnUaIxX2i_CxmP-xo,6231
@@ -49,15 +49,15 @@ emdash_cli/diff_renderer.py,sha256=OrYsKcYZZob6nQ-z7xB_jseGYLoK8czTDJrOiriWlOo,1
49
49
  emdash_cli/integrations/__init__.py,sha256=9SFXQiGeuVpLw21_kKBZgToAHC_9gQ31xDMffUaGo70,31
50
50
  emdash_cli/integrations/telegram/__init__.py,sha256=mULT7y1k1CTvBLeuckBh_nBS_gCzU_t9r0U0AIT1lrY,369
51
51
  emdash_cli/integrations/telegram/bot.py,sha256=3qTYPue9XJeZi3sqlu4B9WQ1lxwXaWIOaE2O9hDAKTI,11032
52
- emdash_cli/integrations/telegram/bridge.py,sha256=dTT3wQxXhLMUCxLbAvSjaDDsFb3ovPUu2jUolHo8IXU,31420
52
+ emdash_cli/integrations/telegram/bridge.py,sha256=i74f5Gb2PiqpDuybBRmEoFVvDQTYsCTQLQ7W93P7VNM,36080
53
53
  emdash_cli/integrations/telegram/config.py,sha256=RQxm3Qw9L7mepRm6YgbndVy35rc_RBaE7th_XjFtKoI,4546
54
- emdash_cli/integrations/telegram/formatter.py,sha256=RkW24K6pef57Ogx8M_9NdUVI8sk1Xa1rzZjqPtwr3g0,12197
54
+ emdash_cli/integrations/telegram/formatter.py,sha256=emh6ggnL2fa2GOm8xjkIL7LnUFv9pdNWtABbda2gIpc,12435
55
55
  emdash_cli/keyboard.py,sha256=haYYAuhYGtdjomzhIFy_3Z3eN3BXfMdb4uRQjwB0tbk,4593
56
56
  emdash_cli/main.py,sha256=ZtESOZsr9ocljiIjijaRnz-9IiepNbwhJZOj-7MhLzA,4111
57
57
  emdash_cli/server_manager.py,sha256=saSxTaCu-b2n2-cIA3VzUe-Tj8ABpeZ39TPOdqjBzVI,9397
58
58
  emdash_cli/session_store.py,sha256=GjS73GLSZ3oTNtrFHMcyiP6GnH0Dvfvs6r4s3-bfEaM,9424
59
59
  emdash_cli/sse_renderer.py,sha256=6kDiL2otmZYy--WRI8X0FuDYtW-hY4UAqvqVCoexCOg,48864
60
- emdash_cli-0.1.67.dist-info/METADATA,sha256=xTKZmM1ushcb-PDdI0AfBvzvRwUqW4RdNRutTHeR0cI,662
61
- emdash_cli-0.1.67.dist-info/WHEEL,sha256=zp0Cn7JsFoX2ATtOhtaFYIiE2rmFAD4OcMhtUki8W3U,88
62
- emdash_cli-0.1.67.dist-info/entry_points.txt,sha256=31CuYD0k-tM8csFWDunc-JoZTxXaifj3oIXz4V0p6F0,122
63
- emdash_cli-0.1.67.dist-info/RECORD,,
60
+ emdash_cli-0.1.70.dist-info/METADATA,sha256=d0gsrSNwIWRSKepGCzD8oH5zhV5gQ_ZWBDS8A1bnRPQ,662
61
+ emdash_cli-0.1.70.dist-info/WHEEL,sha256=zp0Cn7JsFoX2ATtOhtaFYIiE2rmFAD4OcMhtUki8W3U,88
62
+ emdash_cli-0.1.70.dist-info/entry_points.txt,sha256=31CuYD0k-tM8csFWDunc-JoZTxXaifj3oIXz4V0p6F0,122
63
+ emdash_cli-0.1.70.dist-info/RECORD,,