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.
- emdash_cli/commands/agent/constants.py +68 -0
- emdash_cli/commands/agent/handlers/telegram.py +48 -0
- emdash_cli/commands/agent/interactive.py +56 -10
- emdash_cli/integrations/telegram/bridge.py +150 -35
- emdash_cli/integrations/telegram/formatter.py +9 -2
- {emdash_cli-0.1.67.dist-info → emdash_cli-0.1.70.dist-info}/METADATA +2 -2
- {emdash_cli-0.1.67.dist-info → emdash_cli-0.1.70.dist-info}/RECORD +9 -9
- {emdash_cli-0.1.67.dist-info → emdash_cli-0.1.70.dist-info}/WHEEL +0 -0
- {emdash_cli-0.1.67.dist-info → emdash_cli-0.1.70.dist-info}/entry_points.txt +0 -0
|
@@ -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=
|
|
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
|
-
|
|
256
|
-
if
|
|
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
|
-
|
|
297
|
+
completion_text,
|
|
259
298
|
start_position=-len(text),
|
|
260
|
-
display=
|
|
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"
|
|
311
|
+
@kb.add("enter")
|
|
273
312
|
def submit_on_enter(event):
|
|
274
|
-
"""Submit on Enter."""
|
|
275
|
-
event.current_buffer
|
|
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
|
|
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
|
-
"*
|
|
194
|
-
"/
|
|
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
|
-
"/
|
|
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
|
-
"/
|
|
201
|
-
"/help - Show this help",
|
|
256
|
+
"/tghelp - Show this help",
|
|
202
257
|
)
|
|
203
258
|
|
|
204
|
-
elif command == "/
|
|
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 == "/
|
|
298
|
+
elif command == "/tgsettings":
|
|
246
299
|
await self._send_message(
|
|
247
300
|
chat_id,
|
|
248
|
-
"*
|
|
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 == "/
|
|
308
|
+
elif command == "/tghelp":
|
|
256
309
|
await self._send_message(
|
|
257
310
|
chat_id,
|
|
258
|
-
"*EmDash Bot
|
|
259
|
-
"Send any message to interact with the EmDash agent.\n\n"
|
|
260
|
-
"*
|
|
261
|
-
"/
|
|
262
|
-
"/
|
|
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
|
-
"/
|
|
266
|
-
"/
|
|
267
|
-
"/
|
|
268
|
-
"/
|
|
269
|
-
"/
|
|
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
|
-
"
|
|
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 "
|
|
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
|
-
|
|
280
|
-
|
|
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
|
-
|
|
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(
|
|
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.
|
|
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.
|
|
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=
|
|
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=
|
|
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=
|
|
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=
|
|
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=
|
|
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.
|
|
61
|
-
emdash_cli-0.1.
|
|
62
|
-
emdash_cli-0.1.
|
|
63
|
-
emdash_cli-0.1.
|
|
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,,
|
|
File without changes
|
|
File without changes
|