emdash-cli 0.1.46__py3-none-any.whl → 0.1.67__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 +12 -28
- emdash_cli/commands/__init__.py +2 -2
- emdash_cli/commands/agent/constants.py +10 -0
- emdash_cli/commands/agent/handlers/__init__.py +10 -0
- emdash_cli/commands/agent/handlers/agents.py +67 -39
- emdash_cli/commands/agent/handlers/index.py +183 -0
- emdash_cli/commands/agent/handlers/misc.py +119 -0
- emdash_cli/commands/agent/handlers/registry.py +72 -0
- emdash_cli/commands/agent/handlers/rules.py +48 -31
- emdash_cli/commands/agent/handlers/sessions.py +1 -1
- emdash_cli/commands/agent/handlers/setup.py +187 -54
- emdash_cli/commands/agent/handlers/skills.py +42 -4
- emdash_cli/commands/agent/handlers/telegram.py +475 -0
- emdash_cli/commands/agent/handlers/todos.py +55 -34
- emdash_cli/commands/agent/handlers/verify.py +10 -5
- emdash_cli/commands/agent/help.py +236 -0
- emdash_cli/commands/agent/interactive.py +222 -37
- emdash_cli/commands/agent/menus.py +116 -84
- emdash_cli/commands/agent/onboarding.py +619 -0
- emdash_cli/commands/agent/session_restore.py +210 -0
- emdash_cli/commands/index.py +111 -13
- emdash_cli/commands/registry.py +635 -0
- emdash_cli/commands/skills.py +72 -6
- emdash_cli/design.py +328 -0
- emdash_cli/diff_renderer.py +438 -0
- emdash_cli/integrations/__init__.py +1 -0
- emdash_cli/integrations/telegram/__init__.py +15 -0
- emdash_cli/integrations/telegram/bot.py +402 -0
- emdash_cli/integrations/telegram/bridge.py +865 -0
- emdash_cli/integrations/telegram/config.py +155 -0
- emdash_cli/integrations/telegram/formatter.py +385 -0
- emdash_cli/main.py +52 -2
- emdash_cli/sse_renderer.py +632 -171
- {emdash_cli-0.1.46.dist-info → emdash_cli-0.1.67.dist-info}/METADATA +2 -2
- emdash_cli-0.1.67.dist-info/RECORD +63 -0
- emdash_cli/commands/swarm.py +0 -86
- emdash_cli-0.1.46.dist-info/RECORD +0 -49
- {emdash_cli-0.1.46.dist-info → emdash_cli-0.1.67.dist-info}/WHEEL +0 -0
- {emdash_cli-0.1.46.dist-info → emdash_cli-0.1.67.dist-info}/entry_points.txt +0 -0
|
@@ -0,0 +1,475 @@
|
|
|
1
|
+
"""Handler for /telegram command."""
|
|
2
|
+
|
|
3
|
+
import asyncio
|
|
4
|
+
from datetime import datetime
|
|
5
|
+
|
|
6
|
+
from rich.console import Console
|
|
7
|
+
from rich.panel import Panel
|
|
8
|
+
from rich.table import Table
|
|
9
|
+
|
|
10
|
+
from ....design import Colors, header, footer, SEPARATOR_WIDTH
|
|
11
|
+
|
|
12
|
+
console = Console()
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def handle_telegram(args: str) -> None:
|
|
16
|
+
"""Handle /telegram command.
|
|
17
|
+
|
|
18
|
+
Args:
|
|
19
|
+
args: Command arguments (setup, connect, status, test, disconnect)
|
|
20
|
+
"""
|
|
21
|
+
from ....integrations.telegram import TelegramConfig, get_config, save_config
|
|
22
|
+
from ....integrations.telegram.bot import verify_token, TelegramBot
|
|
23
|
+
|
|
24
|
+
# Parse subcommand
|
|
25
|
+
subparts = args.split(maxsplit=1) if args else []
|
|
26
|
+
subcommand = subparts[0].lower() if subparts else ""
|
|
27
|
+
subargs = subparts[1] if len(subparts) > 1 else ""
|
|
28
|
+
|
|
29
|
+
if subcommand == "" or subcommand == "help":
|
|
30
|
+
_show_telegram_help()
|
|
31
|
+
|
|
32
|
+
elif subcommand == "setup":
|
|
33
|
+
_handle_setup()
|
|
34
|
+
|
|
35
|
+
elif subcommand == "status":
|
|
36
|
+
_handle_status()
|
|
37
|
+
|
|
38
|
+
elif subcommand == "test":
|
|
39
|
+
_handle_test()
|
|
40
|
+
|
|
41
|
+
elif subcommand == "connect":
|
|
42
|
+
_handle_connect()
|
|
43
|
+
|
|
44
|
+
elif subcommand == "disconnect":
|
|
45
|
+
_handle_disconnect()
|
|
46
|
+
|
|
47
|
+
elif subcommand == "settings":
|
|
48
|
+
_handle_settings(subargs)
|
|
49
|
+
|
|
50
|
+
else:
|
|
51
|
+
console.print(f"[{Colors.WARNING}]Unknown subcommand: {subcommand}[/{Colors.WARNING}]")
|
|
52
|
+
console.print(f"[{Colors.DIM}]Run /telegram help for usage[/{Colors.DIM}]")
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def _show_telegram_help() -> None:
|
|
56
|
+
"""Show help for /telegram command."""
|
|
57
|
+
from ....integrations.telegram import get_config
|
|
58
|
+
|
|
59
|
+
config = get_config()
|
|
60
|
+
|
|
61
|
+
console.print()
|
|
62
|
+
console.print(f"[{Colors.MUTED}]{header('Telegram Integration', SEPARATOR_WIDTH)}[/{Colors.MUTED}]")
|
|
63
|
+
console.print()
|
|
64
|
+
|
|
65
|
+
# Status indicator
|
|
66
|
+
if config.is_configured():
|
|
67
|
+
status = f"[{Colors.SUCCESS}]configured[/{Colors.SUCCESS}]"
|
|
68
|
+
if config.state.enabled:
|
|
69
|
+
status += f" [{Colors.SUCCESS}](enabled)[/{Colors.SUCCESS}]"
|
|
70
|
+
else:
|
|
71
|
+
status = f"[{Colors.WARNING}]not configured[/{Colors.WARNING}]"
|
|
72
|
+
|
|
73
|
+
console.print(f" [{Colors.DIM}]Status:[/{Colors.DIM}] {status}")
|
|
74
|
+
console.print()
|
|
75
|
+
|
|
76
|
+
# Commands table
|
|
77
|
+
commands = [
|
|
78
|
+
("/telegram setup", "Configure bot token and authorize chats"),
|
|
79
|
+
("/telegram status", "Show current configuration and state"),
|
|
80
|
+
("/telegram test", "Send a test message to authorized chats"),
|
|
81
|
+
("/telegram connect", "Start the Telegram bridge (foreground)"),
|
|
82
|
+
("/telegram disconnect", "Disable Telegram integration"),
|
|
83
|
+
("/telegram settings", "View/modify settings"),
|
|
84
|
+
]
|
|
85
|
+
|
|
86
|
+
console.print(f" [{Colors.DIM}]Commands:[/{Colors.DIM}]")
|
|
87
|
+
console.print()
|
|
88
|
+
for cmd, desc in commands:
|
|
89
|
+
console.print(f" [{Colors.PRIMARY}]{cmd:24}[/{Colors.PRIMARY}] [{Colors.DIM}]{desc}[/{Colors.DIM}]")
|
|
90
|
+
|
|
91
|
+
console.print()
|
|
92
|
+
console.print(f"[{Colors.MUTED}]{footer(SEPARATOR_WIDTH)}[/{Colors.MUTED}]")
|
|
93
|
+
console.print()
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def _handle_setup() -> None:
|
|
97
|
+
"""Interactive setup wizard for Telegram bot."""
|
|
98
|
+
from prompt_toolkit import prompt
|
|
99
|
+
from prompt_toolkit.styles import Style
|
|
100
|
+
|
|
101
|
+
from ....integrations.telegram import TelegramConfig, get_config, save_config
|
|
102
|
+
from ....integrations.telegram.bot import verify_token, TelegramBot, TelegramAPIError
|
|
103
|
+
|
|
104
|
+
console.print()
|
|
105
|
+
console.print(f"[{Colors.MUTED}]{header('Telegram Setup', SEPARATOR_WIDTH)}[/{Colors.MUTED}]")
|
|
106
|
+
console.print()
|
|
107
|
+
console.print(f" [{Colors.DIM}]To create a Telegram bot:[/{Colors.DIM}]")
|
|
108
|
+
console.print()
|
|
109
|
+
console.print(f" [{Colors.SUBTLE}]1. Open Telegram and search for @BotFather[/{Colors.SUBTLE}]")
|
|
110
|
+
console.print(f" [{Colors.SUBTLE}]2. Send /newbot and follow the prompts[/{Colors.SUBTLE}]")
|
|
111
|
+
console.print(f" [{Colors.SUBTLE}]3. Copy the bot token and paste below[/{Colors.SUBTLE}]")
|
|
112
|
+
console.print()
|
|
113
|
+
|
|
114
|
+
# Get current config
|
|
115
|
+
config = get_config()
|
|
116
|
+
|
|
117
|
+
# Prompt for bot token
|
|
118
|
+
style = Style.from_dict({"": Colors.PRIMARY})
|
|
119
|
+
|
|
120
|
+
try:
|
|
121
|
+
token = prompt(
|
|
122
|
+
" Bot Token: ",
|
|
123
|
+
style=style,
|
|
124
|
+
default=config.bot_token or "",
|
|
125
|
+
).strip()
|
|
126
|
+
except (KeyboardInterrupt, EOFError):
|
|
127
|
+
console.print(f"\n [{Colors.DIM}]Setup cancelled[/{Colors.DIM}]")
|
|
128
|
+
return
|
|
129
|
+
|
|
130
|
+
if not token:
|
|
131
|
+
console.print(f" [{Colors.WARNING}]No token provided, setup cancelled[/{Colors.WARNING}]")
|
|
132
|
+
return
|
|
133
|
+
|
|
134
|
+
# Verify token
|
|
135
|
+
console.print()
|
|
136
|
+
console.print(f" [{Colors.DIM}]Verifying token...[/{Colors.DIM}]")
|
|
137
|
+
|
|
138
|
+
bot_info = asyncio.run(verify_token(token))
|
|
139
|
+
|
|
140
|
+
if not bot_info:
|
|
141
|
+
console.print(f" [{Colors.ERROR}]Invalid token. Please check and try again.[/{Colors.ERROR}]")
|
|
142
|
+
return
|
|
143
|
+
|
|
144
|
+
console.print(f" [{Colors.SUCCESS}]Bot verified: @{bot_info.username}[/{Colors.SUCCESS}]")
|
|
145
|
+
console.print()
|
|
146
|
+
|
|
147
|
+
# Save config with token
|
|
148
|
+
config.bot_token = token
|
|
149
|
+
save_config(config)
|
|
150
|
+
|
|
151
|
+
# Now wait for a user to message the bot to authorize
|
|
152
|
+
console.print(f" [{Colors.ACCENT}]Now send /start to your bot from Telegram to authorize.[/{Colors.ACCENT}]")
|
|
153
|
+
console.print(f" [{Colors.DIM}]Waiting for connection... (Ctrl+C to skip)[/{Colors.DIM}]")
|
|
154
|
+
console.print()
|
|
155
|
+
|
|
156
|
+
try:
|
|
157
|
+
authorized_chat = asyncio.run(_wait_for_authorization(token))
|
|
158
|
+
if authorized_chat:
|
|
159
|
+
config.add_authorized_chat(authorized_chat["id"])
|
|
160
|
+
config.state.enabled = True
|
|
161
|
+
config.state.last_connected = datetime.now().isoformat()
|
|
162
|
+
save_config(config)
|
|
163
|
+
|
|
164
|
+
console.print(f" [{Colors.SUCCESS}]Authorized: {authorized_chat['name']} (ID: {authorized_chat['id']})[/{Colors.SUCCESS}]")
|
|
165
|
+
console.print()
|
|
166
|
+
console.print(f" [{Colors.SUCCESS}]Setup complete![/{Colors.SUCCESS}]")
|
|
167
|
+
console.print(f" [{Colors.DIM}]Run /telegram connect to start the bridge[/{Colors.DIM}]")
|
|
168
|
+
except KeyboardInterrupt:
|
|
169
|
+
# Save config without authorization (user can authorize later)
|
|
170
|
+
save_config(config)
|
|
171
|
+
console.print()
|
|
172
|
+
console.print(f" [{Colors.DIM}]Token saved. Run /telegram setup again to authorize chats.[/{Colors.DIM}]")
|
|
173
|
+
|
|
174
|
+
console.print()
|
|
175
|
+
console.print(f"[{Colors.MUTED}]{footer(SEPARATOR_WIDTH)}[/{Colors.MUTED}]")
|
|
176
|
+
console.print()
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
async def _wait_for_authorization(token: str, timeout: int = 60) -> dict | None:
|
|
180
|
+
"""Wait for a user to send /start to the bot.
|
|
181
|
+
|
|
182
|
+
Args:
|
|
183
|
+
token: Bot token
|
|
184
|
+
timeout: Maximum seconds to wait
|
|
185
|
+
|
|
186
|
+
Returns:
|
|
187
|
+
Dict with chat id and name, or None if timeout
|
|
188
|
+
"""
|
|
189
|
+
from ....integrations.telegram.bot import TelegramBot
|
|
190
|
+
|
|
191
|
+
async with TelegramBot(token) as bot:
|
|
192
|
+
start_time = asyncio.get_event_loop().time()
|
|
193
|
+
|
|
194
|
+
while (asyncio.get_event_loop().time() - start_time) < timeout:
|
|
195
|
+
try:
|
|
196
|
+
updates = await bot.get_updates(timeout=5)
|
|
197
|
+
|
|
198
|
+
for update in updates:
|
|
199
|
+
if update.message and update.message.text:
|
|
200
|
+
text = update.message.text.strip()
|
|
201
|
+
# Accept /start or any message as authorization
|
|
202
|
+
if text.startswith("/start") or text:
|
|
203
|
+
chat = update.message.chat
|
|
204
|
+
user = update.message.from_user
|
|
205
|
+
|
|
206
|
+
name = chat.display_name
|
|
207
|
+
if user:
|
|
208
|
+
name = user.display_name
|
|
209
|
+
|
|
210
|
+
return {
|
|
211
|
+
"id": chat.id,
|
|
212
|
+
"name": name,
|
|
213
|
+
}
|
|
214
|
+
except Exception:
|
|
215
|
+
await asyncio.sleep(1)
|
|
216
|
+
continue
|
|
217
|
+
|
|
218
|
+
return None
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
def _handle_status() -> None:
|
|
222
|
+
"""Show current Telegram configuration and status."""
|
|
223
|
+
from ....integrations.telegram import get_config
|
|
224
|
+
from ....integrations.telegram.bot import verify_token
|
|
225
|
+
|
|
226
|
+
config = get_config()
|
|
227
|
+
|
|
228
|
+
console.print()
|
|
229
|
+
console.print(f"[{Colors.MUTED}]{header('Telegram Status', SEPARATOR_WIDTH)}[/{Colors.MUTED}]")
|
|
230
|
+
console.print()
|
|
231
|
+
|
|
232
|
+
# Configuration
|
|
233
|
+
if config.is_configured():
|
|
234
|
+
console.print(f" [{Colors.DIM}]Bot Token:[/{Colors.DIM}] [{Colors.SUCCESS}]configured[/{Colors.SUCCESS}]")
|
|
235
|
+
|
|
236
|
+
# Verify token is still valid
|
|
237
|
+
bot_info = asyncio.run(verify_token(config.bot_token))
|
|
238
|
+
if bot_info:
|
|
239
|
+
console.print(f" [{Colors.DIM}]Bot:[/{Colors.DIM}] @{bot_info.username}")
|
|
240
|
+
else:
|
|
241
|
+
console.print(f" [{Colors.DIM}]Bot:[/{Colors.DIM}] [{Colors.ERROR}]token invalid[/{Colors.ERROR}]")
|
|
242
|
+
else:
|
|
243
|
+
console.print(f" [{Colors.DIM}]Bot Token:[/{Colors.DIM}] [{Colors.WARNING}]not configured[/{Colors.WARNING}]")
|
|
244
|
+
console.print()
|
|
245
|
+
console.print(f" [{Colors.DIM}]Run /telegram setup to configure[/{Colors.DIM}]")
|
|
246
|
+
console.print()
|
|
247
|
+
console.print(f"[{Colors.MUTED}]{footer(SEPARATOR_WIDTH)}[/{Colors.MUTED}]")
|
|
248
|
+
console.print()
|
|
249
|
+
return
|
|
250
|
+
|
|
251
|
+
# Authorized chats
|
|
252
|
+
if config.authorized_chats:
|
|
253
|
+
console.print(f" [{Colors.DIM}]Authorized:[/{Colors.DIM}] {len(config.authorized_chats)} chat(s)")
|
|
254
|
+
for chat_id in config.authorized_chats:
|
|
255
|
+
console.print(f" [{Colors.SUBTLE}]{chat_id}[/{Colors.SUBTLE}]")
|
|
256
|
+
else:
|
|
257
|
+
console.print(f" [{Colors.DIM}]Authorized:[/{Colors.DIM}] [{Colors.WARNING}]no chats authorized[/{Colors.WARNING}]")
|
|
258
|
+
|
|
259
|
+
# State
|
|
260
|
+
console.print()
|
|
261
|
+
enabled_status = f"[{Colors.SUCCESS}]yes[/{Colors.SUCCESS}]" if config.state.enabled else f"[{Colors.DIM}]no[/{Colors.DIM}]"
|
|
262
|
+
console.print(f" [{Colors.DIM}]Enabled:[/{Colors.DIM}] {enabled_status}")
|
|
263
|
+
|
|
264
|
+
if config.state.last_connected:
|
|
265
|
+
console.print(f" [{Colors.DIM}]Last Active:[/{Colors.DIM}] {config.state.last_connected}")
|
|
266
|
+
|
|
267
|
+
# Settings
|
|
268
|
+
console.print()
|
|
269
|
+
console.print(f" [{Colors.DIM}]Settings:[/{Colors.DIM}]")
|
|
270
|
+
console.print(f" [{Colors.SUBTLE}]Streaming mode:[/{Colors.SUBTLE}] {config.settings.streaming_mode}")
|
|
271
|
+
console.print(f" [{Colors.SUBTLE}]Update interval:[/{Colors.SUBTLE}] {config.settings.update_interval_ms}ms")
|
|
272
|
+
console.print(f" [{Colors.SUBTLE}]Show thinking:[/{Colors.SUBTLE}] {config.settings.show_thinking}")
|
|
273
|
+
console.print(f" [{Colors.SUBTLE}]Show tools:[/{Colors.SUBTLE}] {config.settings.show_tool_calls}")
|
|
274
|
+
|
|
275
|
+
console.print()
|
|
276
|
+
console.print(f"[{Colors.MUTED}]{footer(SEPARATOR_WIDTH)}[/{Colors.MUTED}]")
|
|
277
|
+
console.print()
|
|
278
|
+
|
|
279
|
+
|
|
280
|
+
def _handle_test() -> None:
|
|
281
|
+
"""Send a test message to authorized chats."""
|
|
282
|
+
from ....integrations.telegram import get_config
|
|
283
|
+
from ....integrations.telegram.bot import TelegramBot
|
|
284
|
+
|
|
285
|
+
config = get_config()
|
|
286
|
+
|
|
287
|
+
if not config.is_configured():
|
|
288
|
+
console.print(f"[{Colors.WARNING}]Telegram not configured. Run /telegram setup first.[/{Colors.WARNING}]")
|
|
289
|
+
return
|
|
290
|
+
|
|
291
|
+
if not config.authorized_chats:
|
|
292
|
+
console.print(f"[{Colors.WARNING}]No authorized chats. Run /telegram setup to authorize.[/{Colors.WARNING}]")
|
|
293
|
+
return
|
|
294
|
+
|
|
295
|
+
console.print()
|
|
296
|
+
console.print(f" [{Colors.DIM}]Sending test message...[/{Colors.DIM}]")
|
|
297
|
+
|
|
298
|
+
async def send_test():
|
|
299
|
+
async with TelegramBot(config.bot_token) as bot:
|
|
300
|
+
bot_info = await bot.get_me()
|
|
301
|
+
for chat_id in config.authorized_chats:
|
|
302
|
+
try:
|
|
303
|
+
await bot.send_message(
|
|
304
|
+
chat_id,
|
|
305
|
+
f"*EmDash Test*\n\nBot `@{bot_info.username}` is connected and working.",
|
|
306
|
+
parse_mode="Markdown",
|
|
307
|
+
)
|
|
308
|
+
console.print(f" [{Colors.SUCCESS}]Sent to chat {chat_id}[/{Colors.SUCCESS}]")
|
|
309
|
+
except Exception as e:
|
|
310
|
+
console.print(f" [{Colors.ERROR}]Failed to send to {chat_id}: {e}[/{Colors.ERROR}]")
|
|
311
|
+
|
|
312
|
+
asyncio.run(send_test())
|
|
313
|
+
console.print()
|
|
314
|
+
|
|
315
|
+
|
|
316
|
+
def _handle_connect() -> None:
|
|
317
|
+
"""Start the Telegram bridge in foreground mode."""
|
|
318
|
+
from ....integrations.telegram import get_config, save_config
|
|
319
|
+
|
|
320
|
+
config = get_config()
|
|
321
|
+
|
|
322
|
+
if not config.is_configured():
|
|
323
|
+
console.print(f"[{Colors.WARNING}]Telegram not configured. Run /telegram setup first.[/{Colors.WARNING}]")
|
|
324
|
+
return
|
|
325
|
+
|
|
326
|
+
console.print()
|
|
327
|
+
console.print(f"[{Colors.MUTED}]{header('Telegram Bridge', SEPARATOR_WIDTH)}[/{Colors.MUTED}]")
|
|
328
|
+
console.print()
|
|
329
|
+
console.print(f" [{Colors.SUCCESS}]Bridge starting...[/{Colors.SUCCESS}]")
|
|
330
|
+
console.print(f" [{Colors.DIM}]Listening for messages. Press Ctrl+C to stop.[/{Colors.DIM}]")
|
|
331
|
+
console.print()
|
|
332
|
+
|
|
333
|
+
# Update state
|
|
334
|
+
config.state.enabled = True
|
|
335
|
+
config.state.last_connected = datetime.now().isoformat()
|
|
336
|
+
save_config(config)
|
|
337
|
+
|
|
338
|
+
try:
|
|
339
|
+
asyncio.run(_run_bridge(config))
|
|
340
|
+
except KeyboardInterrupt:
|
|
341
|
+
console.print()
|
|
342
|
+
console.print(f" [{Colors.DIM}]Bridge stopped[/{Colors.DIM}]")
|
|
343
|
+
|
|
344
|
+
console.print()
|
|
345
|
+
console.print(f"[{Colors.MUTED}]{footer(SEPARATOR_WIDTH)}[/{Colors.MUTED}]")
|
|
346
|
+
console.print()
|
|
347
|
+
|
|
348
|
+
|
|
349
|
+
async def _run_bridge(config) -> None:
|
|
350
|
+
"""Run the Telegram bridge (receives messages and forwards to agent).
|
|
351
|
+
|
|
352
|
+
Connects Telegram messages to the EmDash agent and streams
|
|
353
|
+
SSE responses back as Telegram messages.
|
|
354
|
+
"""
|
|
355
|
+
from ....integrations.telegram.bridge import TelegramBridge
|
|
356
|
+
from ....integrations.telegram import save_config
|
|
357
|
+
from ....server_manager import get_server_manager
|
|
358
|
+
|
|
359
|
+
# Get server URL from server manager (starts server if needed)
|
|
360
|
+
server = get_server_manager()
|
|
361
|
+
server_url = server.get_server_url()
|
|
362
|
+
console.print(f" [{Colors.SUBTLE}]Server: {server_url}[/{Colors.SUBTLE}]")
|
|
363
|
+
|
|
364
|
+
def on_message(event_type: str, data: dict) -> None:
|
|
365
|
+
"""Log bridge events to the console."""
|
|
366
|
+
if event_type == "bridge_started":
|
|
367
|
+
console.print(f" [{Colors.SUBTLE}]Bot: @{data.get('bot', 'unknown')}[/{Colors.SUBTLE}]")
|
|
368
|
+
console.print()
|
|
369
|
+
elif event_type == "message_received":
|
|
370
|
+
user = data.get("user", "Unknown")
|
|
371
|
+
text = data.get("text", "")[:50]
|
|
372
|
+
if len(data.get("text", "")) > 50:
|
|
373
|
+
text += "..."
|
|
374
|
+
console.print(f" [{Colors.ACCENT}]{user}:[/{Colors.ACCENT}] {text}")
|
|
375
|
+
elif event_type == "error":
|
|
376
|
+
console.print(f" [{Colors.ERROR}]Error: {data.get('error', 'unknown')}[/{Colors.ERROR}]")
|
|
377
|
+
elif event_type == "send_error":
|
|
378
|
+
console.print(f" [{Colors.WARNING}]Send failed: {data.get('error', 'unknown')}[/{Colors.WARNING}]")
|
|
379
|
+
|
|
380
|
+
bridge = TelegramBridge(config, server_url=server_url, on_message=on_message)
|
|
381
|
+
|
|
382
|
+
try:
|
|
383
|
+
await bridge.start()
|
|
384
|
+
finally:
|
|
385
|
+
# Save config with updated state (last_update_id, etc.)
|
|
386
|
+
save_config(config)
|
|
387
|
+
|
|
388
|
+
|
|
389
|
+
def _handle_disconnect() -> None:
|
|
390
|
+
"""Disable Telegram integration."""
|
|
391
|
+
from ....integrations.telegram import get_config, save_config, delete_config
|
|
392
|
+
from prompt_toolkit import prompt
|
|
393
|
+
|
|
394
|
+
config = get_config()
|
|
395
|
+
|
|
396
|
+
if not config.is_configured():
|
|
397
|
+
console.print(f"[{Colors.DIM}]Telegram is not configured.[/{Colors.DIM}]")
|
|
398
|
+
return
|
|
399
|
+
|
|
400
|
+
console.print()
|
|
401
|
+
console.print(f" [{Colors.WARNING}]This will remove your Telegram configuration.[/{Colors.WARNING}]")
|
|
402
|
+
|
|
403
|
+
try:
|
|
404
|
+
confirm = prompt(" Type 'yes' to confirm: ").strip().lower()
|
|
405
|
+
except (KeyboardInterrupt, EOFError):
|
|
406
|
+
console.print(f"\n [{Colors.DIM}]Cancelled[/{Colors.DIM}]")
|
|
407
|
+
return
|
|
408
|
+
|
|
409
|
+
if confirm == "yes":
|
|
410
|
+
delete_config()
|
|
411
|
+
console.print(f" [{Colors.SUCCESS}]Telegram configuration removed.[/{Colors.SUCCESS}]")
|
|
412
|
+
else:
|
|
413
|
+
console.print(f" [{Colors.DIM}]Cancelled[/{Colors.DIM}]")
|
|
414
|
+
|
|
415
|
+
console.print()
|
|
416
|
+
|
|
417
|
+
|
|
418
|
+
def _handle_settings(args: str) -> None:
|
|
419
|
+
"""View or modify Telegram settings."""
|
|
420
|
+
from ....integrations.telegram import get_config, save_config
|
|
421
|
+
|
|
422
|
+
config = get_config()
|
|
423
|
+
|
|
424
|
+
if not config.is_configured():
|
|
425
|
+
console.print(f"[{Colors.WARNING}]Telegram not configured. Run /telegram setup first.[/{Colors.WARNING}]")
|
|
426
|
+
return
|
|
427
|
+
|
|
428
|
+
if not args:
|
|
429
|
+
# Show current settings
|
|
430
|
+
console.print()
|
|
431
|
+
console.print(f" [{Colors.DIM}]Current Settings:[/{Colors.DIM}]")
|
|
432
|
+
console.print()
|
|
433
|
+
console.print(f" [{Colors.PRIMARY}]streaming_mode[/{Colors.PRIMARY}] = {config.settings.streaming_mode}")
|
|
434
|
+
console.print(f" [{Colors.PRIMARY}]update_interval_ms[/{Colors.PRIMARY}] = {config.settings.update_interval_ms}")
|
|
435
|
+
console.print(f" [{Colors.PRIMARY}]show_thinking[/{Colors.PRIMARY}] = {config.settings.show_thinking}")
|
|
436
|
+
console.print(f" [{Colors.PRIMARY}]show_tool_calls[/{Colors.PRIMARY}] = {config.settings.show_tool_calls}")
|
|
437
|
+
console.print(f" [{Colors.PRIMARY}]compact_mode[/{Colors.PRIMARY}] = {config.settings.compact_mode}")
|
|
438
|
+
console.print()
|
|
439
|
+
console.print(f" [{Colors.DIM}]Usage: /telegram settings <key> <value>[/{Colors.DIM}]")
|
|
440
|
+
console.print()
|
|
441
|
+
return
|
|
442
|
+
|
|
443
|
+
# Parse key=value or key value
|
|
444
|
+
parts = args.replace("=", " ").split()
|
|
445
|
+
if len(parts) < 2:
|
|
446
|
+
console.print(f"[{Colors.WARNING}]Usage: /telegram settings <key> <value>[/{Colors.WARNING}]")
|
|
447
|
+
return
|
|
448
|
+
|
|
449
|
+
key = parts[0]
|
|
450
|
+
value = parts[1]
|
|
451
|
+
|
|
452
|
+
# Update setting
|
|
453
|
+
if key == "streaming_mode":
|
|
454
|
+
if value not in ("edit", "append"):
|
|
455
|
+
console.print(f"[{Colors.WARNING}]streaming_mode must be 'edit' or 'append'[/{Colors.WARNING}]")
|
|
456
|
+
return
|
|
457
|
+
config.settings.streaming_mode = value
|
|
458
|
+
elif key == "update_interval_ms":
|
|
459
|
+
try:
|
|
460
|
+
config.settings.update_interval_ms = int(value)
|
|
461
|
+
except ValueError:
|
|
462
|
+
console.print(f"[{Colors.WARNING}]update_interval_ms must be a number[/{Colors.WARNING}]")
|
|
463
|
+
return
|
|
464
|
+
elif key == "show_thinking":
|
|
465
|
+
config.settings.show_thinking = value.lower() in ("true", "1", "yes")
|
|
466
|
+
elif key == "show_tool_calls":
|
|
467
|
+
config.settings.show_tool_calls = value.lower() in ("true", "1", "yes")
|
|
468
|
+
elif key == "compact_mode":
|
|
469
|
+
config.settings.compact_mode = value.lower() in ("true", "1", "yes")
|
|
470
|
+
else:
|
|
471
|
+
console.print(f"[{Colors.WARNING}]Unknown setting: {key}[/{Colors.WARNING}]")
|
|
472
|
+
return
|
|
473
|
+
|
|
474
|
+
save_config(config)
|
|
475
|
+
console.print(f" [{Colors.SUCCESS}]Setting updated: {key} = {value}[/{Colors.SUCCESS}]")
|
|
@@ -2,6 +2,16 @@
|
|
|
2
2
|
|
|
3
3
|
from rich.console import Console
|
|
4
4
|
|
|
5
|
+
from ....design import (
|
|
6
|
+
Colors,
|
|
7
|
+
header,
|
|
8
|
+
footer,
|
|
9
|
+
SEPARATOR_WIDTH,
|
|
10
|
+
STATUS_ACTIVE,
|
|
11
|
+
STATUS_INACTIVE,
|
|
12
|
+
DOT_BULLET,
|
|
13
|
+
)
|
|
14
|
+
|
|
5
15
|
console = Console()
|
|
6
16
|
|
|
7
17
|
|
|
@@ -16,12 +26,19 @@ def handle_todos(args: str, client, session_id: str | None, pending_todos: list[
|
|
|
16
26
|
"""
|
|
17
27
|
if not session_id:
|
|
18
28
|
if pending_todos:
|
|
19
|
-
console.print(
|
|
20
|
-
|
|
21
|
-
|
|
29
|
+
console.print()
|
|
30
|
+
console.print(f"[{Colors.MUTED}]{header('Pending Todos', SEPARATOR_WIDTH)}[/{Colors.MUTED}]")
|
|
31
|
+
console.print(f" [{Colors.DIM}]will be added when session starts[/{Colors.DIM}]")
|
|
32
|
+
console.print()
|
|
33
|
+
for todo in pending_todos:
|
|
34
|
+
console.print(f" [{Colors.MUTED}]○[/{Colors.MUTED}] {todo}")
|
|
35
|
+
console.print()
|
|
36
|
+
console.print(f"[{Colors.MUTED}]{footer(SEPARATOR_WIDTH)}[/{Colors.MUTED}]")
|
|
22
37
|
console.print()
|
|
23
38
|
else:
|
|
24
|
-
console.print(
|
|
39
|
+
console.print()
|
|
40
|
+
console.print(f" [{Colors.DIM}]No todos yet. Start a conversation to track tasks.[/{Colors.DIM}]")
|
|
41
|
+
console.print()
|
|
25
42
|
else:
|
|
26
43
|
try:
|
|
27
44
|
result = client.get_todos(session_id)
|
|
@@ -29,43 +46,41 @@ def handle_todos(args: str, client, session_id: str | None, pending_todos: list[
|
|
|
29
46
|
summary = result.get("summary", {})
|
|
30
47
|
|
|
31
48
|
if not todos:
|
|
32
|
-
console.print(
|
|
49
|
+
console.print()
|
|
50
|
+
console.print(f" [{Colors.DIM}]No todos in current session.[/{Colors.DIM}]")
|
|
51
|
+
console.print()
|
|
33
52
|
else:
|
|
34
|
-
console.print(
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
icons = {
|
|
38
|
-
"pending": "[dim]⬚[/dim]",
|
|
39
|
-
"in_progress": "[yellow]🔄[/yellow]",
|
|
40
|
-
"completed": "[green]✓[/green]",
|
|
41
|
-
}
|
|
53
|
+
console.print()
|
|
54
|
+
console.print(f"[{Colors.MUTED}]{header('Todo List', SEPARATOR_WIDTH)}[/{Colors.MUTED}]")
|
|
55
|
+
console.print()
|
|
42
56
|
|
|
43
57
|
for todo in todos:
|
|
44
|
-
icon = icons.get(todo["status"], "?")
|
|
45
58
|
title = todo["title"]
|
|
46
59
|
status = todo["status"]
|
|
47
60
|
|
|
48
61
|
if status == "completed":
|
|
49
|
-
console.print(f" {
|
|
62
|
+
console.print(f" [{Colors.SUCCESS}]●[/{Colors.SUCCESS}] [{Colors.DIM} strike]{title}[/{Colors.DIM} strike]")
|
|
50
63
|
elif status == "in_progress":
|
|
51
|
-
console.print(f" {
|
|
64
|
+
console.print(f" [{Colors.WARNING}]◐[/{Colors.WARNING}] [{Colors.TEXT} bold]{title}[/{Colors.TEXT} bold]")
|
|
52
65
|
else:
|
|
53
|
-
console.print(f" {
|
|
66
|
+
console.print(f" [{Colors.MUTED}]○[/{Colors.MUTED}] {title}")
|
|
54
67
|
|
|
55
68
|
if todo.get("description"):
|
|
56
69
|
desc = todo["description"]
|
|
57
|
-
if len(desc) >
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
console.print(f" [dim]{desc}[/dim]")
|
|
70
|
+
if len(desc) > 55:
|
|
71
|
+
desc = desc[:55] + "..."
|
|
72
|
+
console.print(f" [{Colors.DIM}]{desc}[/{Colors.DIM}]")
|
|
61
73
|
|
|
62
74
|
console.print()
|
|
63
|
-
console.print(
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
)
|
|
75
|
+
console.print(f"[{Colors.MUTED}]{footer(SEPARATOR_WIDTH)}[/{Colors.MUTED}]")
|
|
76
|
+
|
|
77
|
+
# Summary bar
|
|
78
|
+
total = summary.get('total', 0)
|
|
79
|
+
completed = summary.get('completed', 0)
|
|
80
|
+
in_progress = summary.get('in_progress', 0)
|
|
81
|
+
pending = summary.get('pending', 0)
|
|
82
|
+
|
|
83
|
+
console.print(f" [{Colors.DIM}]○ {pending}[/{Colors.DIM}] [{Colors.WARNING}]◐ {in_progress}[/{Colors.WARNING}] [{Colors.SUCCESS}]● {completed}[/{Colors.SUCCESS}] [{Colors.MUTED}]total {total}[/{Colors.MUTED}]")
|
|
69
84
|
console.print()
|
|
70
85
|
|
|
71
86
|
except Exception as e:
|
|
@@ -82,17 +97,23 @@ def handle_todo_add(args: str, client, session_id: str | None, pending_todos: li
|
|
|
82
97
|
pending_todos: List of pending todos (before session starts)
|
|
83
98
|
"""
|
|
84
99
|
if not args:
|
|
85
|
-
console.print(
|
|
86
|
-
console.print("[
|
|
100
|
+
console.print()
|
|
101
|
+
console.print(f" [{Colors.WARNING}]Usage:[/{Colors.WARNING}] /todo-add <title>")
|
|
102
|
+
console.print(f" [{Colors.DIM}]Example: /todo-add Fix the failing tests[/{Colors.DIM}]")
|
|
103
|
+
console.print()
|
|
87
104
|
elif not session_id:
|
|
88
105
|
pending_todos.append(args)
|
|
89
|
-
console.print(
|
|
90
|
-
console.print("[
|
|
106
|
+
console.print()
|
|
107
|
+
console.print(f" [{Colors.SUCCESS}]{STATUS_ACTIVE}[/{Colors.SUCCESS}] [{Colors.MUTED}]noted:[/{Colors.MUTED}] {args}")
|
|
108
|
+
console.print(f" [{Colors.DIM}]will be added when session starts[/{Colors.DIM}]")
|
|
109
|
+
console.print()
|
|
91
110
|
else:
|
|
92
111
|
try:
|
|
93
112
|
result = client.add_todo(session_id, args)
|
|
94
113
|
task = result.get("task", {})
|
|
95
|
-
console.print(
|
|
96
|
-
console.print(f"[
|
|
114
|
+
console.print()
|
|
115
|
+
console.print(f" [{Colors.SUCCESS}]{STATUS_ACTIVE}[/{Colors.SUCCESS}] [{Colors.MUTED}]added:[/{Colors.MUTED}] {task.get('title')}")
|
|
116
|
+
console.print(f" [{Colors.DIM}]total: {result.get('total_tasks', 0)}[/{Colors.DIM}]")
|
|
117
|
+
console.print()
|
|
97
118
|
except Exception as e:
|
|
98
|
-
console.print(f"[
|
|
119
|
+
console.print(f" [{Colors.ERROR}]error:[/{Colors.ERROR}] {e}")
|
|
@@ -480,14 +480,17 @@ def _run_verifiers(manager: VerifierManager) -> None:
|
|
|
480
480
|
return
|
|
481
481
|
|
|
482
482
|
console.print(f"[bold]Running {len(manager.verifiers)} verifier(s)...[/bold]")
|
|
483
|
-
console.print()
|
|
484
483
|
|
|
485
484
|
context = {
|
|
486
485
|
"git_diff": get_git_diff(),
|
|
487
486
|
"files_changed": get_changed_files(),
|
|
488
487
|
}
|
|
489
488
|
|
|
490
|
-
|
|
489
|
+
# Run with spinner for better UX
|
|
490
|
+
with console.status("[cyan]Running verifiers...", spinner="dots"):
|
|
491
|
+
report = manager.run_all(context)
|
|
492
|
+
|
|
493
|
+
console.print()
|
|
491
494
|
display_report(report)
|
|
492
495
|
console.print()
|
|
493
496
|
|
|
@@ -548,7 +551,6 @@ def run_verification(goal: str | None = None) -> tuple[VerificationReport, bool]
|
|
|
548
551
|
|
|
549
552
|
console.print()
|
|
550
553
|
console.print(f"[bold cyan]Running {len(manager.verifiers)} verifier(s)...[/bold cyan]")
|
|
551
|
-
console.print()
|
|
552
554
|
|
|
553
555
|
# Build context
|
|
554
556
|
context = {
|
|
@@ -557,8 +559,11 @@ def run_verification(goal: str | None = None) -> tuple[VerificationReport, bool]
|
|
|
557
559
|
"files_changed": get_changed_files(),
|
|
558
560
|
}
|
|
559
561
|
|
|
560
|
-
# Run verifiers
|
|
561
|
-
|
|
562
|
+
# Run verifiers with spinner
|
|
563
|
+
with console.status("[cyan]Running verifiers...", spinner="dots"):
|
|
564
|
+
report = manager.run_all(context)
|
|
565
|
+
|
|
566
|
+
console.print()
|
|
562
567
|
|
|
563
568
|
# Display results
|
|
564
569
|
display_report(report)
|