emdash-cli 0.1.60__tar.gz → 0.1.72__tar.gz
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-0.1.60 → emdash_cli-0.1.72}/PKG-INFO +2 -2
- {emdash_cli-0.1.60 → emdash_cli-0.1.72}/emdash_cli/commands/agent/constants.py +70 -0
- {emdash_cli-0.1.60 → emdash_cli-0.1.72}/emdash_cli/commands/agent/handlers/__init__.py +2 -0
- {emdash_cli-0.1.60 → emdash_cli-0.1.72}/emdash_cli/commands/agent/handlers/sessions.py +1 -1
- emdash_cli-0.1.72/emdash_cli/commands/agent/handlers/telegram.py +523 -0
- {emdash_cli-0.1.60 → emdash_cli-0.1.72}/emdash_cli/commands/agent/interactive.py +124 -10
- emdash_cli-0.1.72/emdash_cli/integrations/__init__.py +1 -0
- emdash_cli-0.1.72/emdash_cli/integrations/telegram/__init__.py +15 -0
- emdash_cli-0.1.72/emdash_cli/integrations/telegram/bot.py +402 -0
- emdash_cli-0.1.72/emdash_cli/integrations/telegram/bridge.py +980 -0
- emdash_cli-0.1.72/emdash_cli/integrations/telegram/config.py +155 -0
- emdash_cli-0.1.72/emdash_cli/integrations/telegram/formatter.py +392 -0
- {emdash_cli-0.1.60 → emdash_cli-0.1.72}/emdash_cli/main.py +50 -0
- {emdash_cli-0.1.60 → emdash_cli-0.1.72}/emdash_cli/sse_renderer.py +26 -11
- {emdash_cli-0.1.60 → emdash_cli-0.1.72}/pyproject.toml +3 -3
- {emdash_cli-0.1.60 → emdash_cli-0.1.72}/emdash_cli/__init__.py +0 -0
- {emdash_cli-0.1.60 → emdash_cli-0.1.72}/emdash_cli/client.py +0 -0
- {emdash_cli-0.1.60 → emdash_cli-0.1.72}/emdash_cli/clipboard.py +0 -0
- {emdash_cli-0.1.60 → emdash_cli-0.1.72}/emdash_cli/commands/__init__.py +0 -0
- {emdash_cli-0.1.60 → emdash_cli-0.1.72}/emdash_cli/commands/agent/__init__.py +0 -0
- {emdash_cli-0.1.60 → emdash_cli-0.1.72}/emdash_cli/commands/agent/cli.py +0 -0
- {emdash_cli-0.1.60 → emdash_cli-0.1.72}/emdash_cli/commands/agent/file_utils.py +0 -0
- {emdash_cli-0.1.60 → emdash_cli-0.1.72}/emdash_cli/commands/agent/handlers/agents.py +0 -0
- {emdash_cli-0.1.60 → emdash_cli-0.1.72}/emdash_cli/commands/agent/handlers/auth.py +0 -0
- {emdash_cli-0.1.60 → emdash_cli-0.1.72}/emdash_cli/commands/agent/handlers/doctor.py +0 -0
- {emdash_cli-0.1.60 → emdash_cli-0.1.72}/emdash_cli/commands/agent/handlers/hooks.py +0 -0
- {emdash_cli-0.1.60 → emdash_cli-0.1.72}/emdash_cli/commands/agent/handlers/index.py +0 -0
- {emdash_cli-0.1.60 → emdash_cli-0.1.72}/emdash_cli/commands/agent/handlers/mcp.py +0 -0
- {emdash_cli-0.1.60 → emdash_cli-0.1.72}/emdash_cli/commands/agent/handlers/misc.py +0 -0
- {emdash_cli-0.1.60 → emdash_cli-0.1.72}/emdash_cli/commands/agent/handlers/registry.py +0 -0
- {emdash_cli-0.1.60 → emdash_cli-0.1.72}/emdash_cli/commands/agent/handlers/rules.py +0 -0
- {emdash_cli-0.1.60 → emdash_cli-0.1.72}/emdash_cli/commands/agent/handlers/setup.py +0 -0
- {emdash_cli-0.1.60 → emdash_cli-0.1.72}/emdash_cli/commands/agent/handlers/skills.py +0 -0
- {emdash_cli-0.1.60 → emdash_cli-0.1.72}/emdash_cli/commands/agent/handlers/todos.py +0 -0
- {emdash_cli-0.1.60 → emdash_cli-0.1.72}/emdash_cli/commands/agent/handlers/verify.py +0 -0
- {emdash_cli-0.1.60 → emdash_cli-0.1.72}/emdash_cli/commands/agent/help.py +0 -0
- {emdash_cli-0.1.60 → emdash_cli-0.1.72}/emdash_cli/commands/agent/menus.py +0 -0
- {emdash_cli-0.1.60 → emdash_cli-0.1.72}/emdash_cli/commands/agent/onboarding.py +0 -0
- {emdash_cli-0.1.60 → emdash_cli-0.1.72}/emdash_cli/commands/agent/session_restore.py +0 -0
- {emdash_cli-0.1.60 → emdash_cli-0.1.72}/emdash_cli/commands/agent.py +0 -0
- {emdash_cli-0.1.60 → emdash_cli-0.1.72}/emdash_cli/commands/analyze.py +0 -0
- {emdash_cli-0.1.60 → emdash_cli-0.1.72}/emdash_cli/commands/auth.py +0 -0
- {emdash_cli-0.1.60 → emdash_cli-0.1.72}/emdash_cli/commands/db.py +0 -0
- {emdash_cli-0.1.60 → emdash_cli-0.1.72}/emdash_cli/commands/embed.py +0 -0
- {emdash_cli-0.1.60 → emdash_cli-0.1.72}/emdash_cli/commands/index.py +0 -0
- {emdash_cli-0.1.60 → emdash_cli-0.1.72}/emdash_cli/commands/plan.py +0 -0
- {emdash_cli-0.1.60 → emdash_cli-0.1.72}/emdash_cli/commands/projectmd.py +0 -0
- {emdash_cli-0.1.60 → emdash_cli-0.1.72}/emdash_cli/commands/registry.py +0 -0
- {emdash_cli-0.1.60 → emdash_cli-0.1.72}/emdash_cli/commands/research.py +0 -0
- {emdash_cli-0.1.60 → emdash_cli-0.1.72}/emdash_cli/commands/rules.py +0 -0
- {emdash_cli-0.1.60 → emdash_cli-0.1.72}/emdash_cli/commands/search.py +0 -0
- {emdash_cli-0.1.60 → emdash_cli-0.1.72}/emdash_cli/commands/server.py +0 -0
- {emdash_cli-0.1.60 → emdash_cli-0.1.72}/emdash_cli/commands/skills.py +0 -0
- {emdash_cli-0.1.60 → emdash_cli-0.1.72}/emdash_cli/commands/spec.py +0 -0
- {emdash_cli-0.1.60 → emdash_cli-0.1.72}/emdash_cli/commands/tasks.py +0 -0
- {emdash_cli-0.1.60 → emdash_cli-0.1.72}/emdash_cli/commands/team.py +0 -0
- {emdash_cli-0.1.60 → emdash_cli-0.1.72}/emdash_cli/design.py +0 -0
- {emdash_cli-0.1.60 → emdash_cli-0.1.72}/emdash_cli/diff_renderer.py +0 -0
- {emdash_cli-0.1.60 → emdash_cli-0.1.72}/emdash_cli/keyboard.py +0 -0
- {emdash_cli-0.1.60 → emdash_cli-0.1.72}/emdash_cli/server_manager.py +0 -0
- {emdash_cli-0.1.60 → emdash_cli-0.1.72}/emdash_cli/session_store.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: emdash-cli
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.72
|
|
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.72)
|
|
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)
|
|
@@ -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
|
|
@@ -56,6 +124,8 @@ SLASH_COMMANDS = {
|
|
|
56
124
|
"/verify-loop [task]": "Run task in loop until verifications pass",
|
|
57
125
|
# Setup wizard
|
|
58
126
|
"/setup": "Setup wizard for rules, agents, skills, and verifiers",
|
|
127
|
+
# Telegram integration
|
|
128
|
+
"/telegram": "Telegram integration (setup, connect, status, test)",
|
|
59
129
|
"/help": "Show available commands",
|
|
60
130
|
"/quit": "Exit the agent",
|
|
61
131
|
}
|
|
@@ -22,6 +22,7 @@ from .misc import (
|
|
|
22
22
|
handle_compact,
|
|
23
23
|
handle_diff,
|
|
24
24
|
)
|
|
25
|
+
from .telegram import handle_telegram
|
|
25
26
|
|
|
26
27
|
__all__ = [
|
|
27
28
|
"handle_agents",
|
|
@@ -46,4 +47,5 @@ __all__ = [
|
|
|
46
47
|
"handle_context",
|
|
47
48
|
"handle_compact",
|
|
48
49
|
"handle_diff",
|
|
50
|
+
"handle_telegram",
|
|
49
51
|
]
|
|
@@ -30,7 +30,7 @@ def handle_session(
|
|
|
30
30
|
current_mode_ref: Reference to current_mode (list wrapper for mutation)
|
|
31
31
|
loaded_messages_ref: Reference to loaded_messages (list wrapper for mutation)
|
|
32
32
|
"""
|
|
33
|
-
from
|
|
33
|
+
from ....session_store import SessionStore
|
|
34
34
|
|
|
35
35
|
store = SessionStore(Path.cwd())
|
|
36
36
|
|
|
@@ -0,0 +1,523 @@
|
|
|
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
|
+
elif subcommand == "commands":
|
|
51
|
+
_handle_commands()
|
|
52
|
+
|
|
53
|
+
else:
|
|
54
|
+
console.print(f"[{Colors.WARNING}]Unknown subcommand: {subcommand}[/{Colors.WARNING}]")
|
|
55
|
+
console.print(f"[{Colors.DIM}]Run /telegram help for usage[/{Colors.DIM}]")
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def _show_telegram_help() -> None:
|
|
59
|
+
"""Show help for /telegram command."""
|
|
60
|
+
from ....integrations.telegram import get_config
|
|
61
|
+
|
|
62
|
+
config = get_config()
|
|
63
|
+
|
|
64
|
+
console.print()
|
|
65
|
+
console.print(f"[{Colors.MUTED}]{header('Telegram Integration', SEPARATOR_WIDTH)}[/{Colors.MUTED}]")
|
|
66
|
+
console.print()
|
|
67
|
+
|
|
68
|
+
# Status indicator
|
|
69
|
+
if config.is_configured():
|
|
70
|
+
status = f"[{Colors.SUCCESS}]configured[/{Colors.SUCCESS}]"
|
|
71
|
+
if config.state.enabled:
|
|
72
|
+
status += f" [{Colors.SUCCESS}](enabled)[/{Colors.SUCCESS}]"
|
|
73
|
+
else:
|
|
74
|
+
status = f"[{Colors.WARNING}]not configured[/{Colors.WARNING}]"
|
|
75
|
+
|
|
76
|
+
console.print(f" [{Colors.DIM}]Status:[/{Colors.DIM}] {status}")
|
|
77
|
+
console.print()
|
|
78
|
+
|
|
79
|
+
# Commands table
|
|
80
|
+
commands = [
|
|
81
|
+
("/telegram setup", "Configure bot token and authorize chats"),
|
|
82
|
+
("/telegram status", "Show current configuration and state"),
|
|
83
|
+
("/telegram test", "Send a test message to authorized chats"),
|
|
84
|
+
("/telegram connect", "Start the Telegram bridge (foreground)"),
|
|
85
|
+
("/telegram disconnect", "Disable Telegram integration"),
|
|
86
|
+
("/telegram settings", "View/modify settings"),
|
|
87
|
+
("/telegram commands", "Show BotFather command list to copy"),
|
|
88
|
+
]
|
|
89
|
+
|
|
90
|
+
console.print(f" [{Colors.DIM}]Commands:[/{Colors.DIM}]")
|
|
91
|
+
console.print()
|
|
92
|
+
for cmd, desc in commands:
|
|
93
|
+
console.print(f" [{Colors.PRIMARY}]{cmd:24}[/{Colors.PRIMARY}] [{Colors.DIM}]{desc}[/{Colors.DIM}]")
|
|
94
|
+
|
|
95
|
+
console.print()
|
|
96
|
+
console.print(f"[{Colors.MUTED}]{footer(SEPARATOR_WIDTH)}[/{Colors.MUTED}]")
|
|
97
|
+
console.print()
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def _handle_setup() -> None:
|
|
101
|
+
"""Interactive setup wizard for Telegram bot."""
|
|
102
|
+
from prompt_toolkit import prompt
|
|
103
|
+
from prompt_toolkit.styles import Style
|
|
104
|
+
|
|
105
|
+
from ....integrations.telegram import TelegramConfig, get_config, save_config
|
|
106
|
+
from ....integrations.telegram.bot import verify_token, TelegramBot, TelegramAPIError
|
|
107
|
+
|
|
108
|
+
console.print()
|
|
109
|
+
console.print(f"[{Colors.MUTED}]{header('Telegram Setup', SEPARATOR_WIDTH)}[/{Colors.MUTED}]")
|
|
110
|
+
console.print()
|
|
111
|
+
console.print(f" [{Colors.DIM}]To create a Telegram bot:[/{Colors.DIM}]")
|
|
112
|
+
console.print()
|
|
113
|
+
console.print(f" [{Colors.SUBTLE}]1. Open Telegram and search for @BotFather[/{Colors.SUBTLE}]")
|
|
114
|
+
console.print(f" [{Colors.SUBTLE}]2. Send /newbot and follow the prompts[/{Colors.SUBTLE}]")
|
|
115
|
+
console.print(f" [{Colors.SUBTLE}]3. Copy the bot token and paste below[/{Colors.SUBTLE}]")
|
|
116
|
+
console.print()
|
|
117
|
+
|
|
118
|
+
# Get current config
|
|
119
|
+
config = get_config()
|
|
120
|
+
|
|
121
|
+
# Prompt for bot token
|
|
122
|
+
style = Style.from_dict({"": Colors.PRIMARY})
|
|
123
|
+
|
|
124
|
+
try:
|
|
125
|
+
token = prompt(
|
|
126
|
+
" Bot Token: ",
|
|
127
|
+
style=style,
|
|
128
|
+
default=config.bot_token or "",
|
|
129
|
+
).strip()
|
|
130
|
+
except (KeyboardInterrupt, EOFError):
|
|
131
|
+
console.print(f"\n [{Colors.DIM}]Setup cancelled[/{Colors.DIM}]")
|
|
132
|
+
return
|
|
133
|
+
|
|
134
|
+
if not token:
|
|
135
|
+
console.print(f" [{Colors.WARNING}]No token provided, setup cancelled[/{Colors.WARNING}]")
|
|
136
|
+
return
|
|
137
|
+
|
|
138
|
+
# Verify token
|
|
139
|
+
console.print()
|
|
140
|
+
console.print(f" [{Colors.DIM}]Verifying token...[/{Colors.DIM}]")
|
|
141
|
+
|
|
142
|
+
bot_info = asyncio.run(verify_token(token))
|
|
143
|
+
|
|
144
|
+
if not bot_info:
|
|
145
|
+
console.print(f" [{Colors.ERROR}]Invalid token. Please check and try again.[/{Colors.ERROR}]")
|
|
146
|
+
return
|
|
147
|
+
|
|
148
|
+
console.print(f" [{Colors.SUCCESS}]Bot verified: @{bot_info.username}[/{Colors.SUCCESS}]")
|
|
149
|
+
console.print()
|
|
150
|
+
|
|
151
|
+
# Save config with token
|
|
152
|
+
config.bot_token = token
|
|
153
|
+
save_config(config)
|
|
154
|
+
|
|
155
|
+
# Now wait for a user to message the bot to authorize
|
|
156
|
+
console.print(f" [{Colors.ACCENT}]Now send /start to your bot from Telegram to authorize.[/{Colors.ACCENT}]")
|
|
157
|
+
console.print(f" [{Colors.DIM}]Waiting for connection... (Ctrl+C to skip)[/{Colors.DIM}]")
|
|
158
|
+
console.print()
|
|
159
|
+
|
|
160
|
+
try:
|
|
161
|
+
authorized_chat = asyncio.run(_wait_for_authorization(token))
|
|
162
|
+
if authorized_chat:
|
|
163
|
+
config.add_authorized_chat(authorized_chat["id"])
|
|
164
|
+
config.state.enabled = True
|
|
165
|
+
config.state.last_connected = datetime.now().isoformat()
|
|
166
|
+
save_config(config)
|
|
167
|
+
|
|
168
|
+
console.print(f" [{Colors.SUCCESS}]Authorized: {authorized_chat['name']} (ID: {authorized_chat['id']})[/{Colors.SUCCESS}]")
|
|
169
|
+
console.print()
|
|
170
|
+
console.print(f" [{Colors.SUCCESS}]Setup complete![/{Colors.SUCCESS}]")
|
|
171
|
+
console.print(f" [{Colors.DIM}]Run /telegram connect to start the bridge[/{Colors.DIM}]")
|
|
172
|
+
except KeyboardInterrupt:
|
|
173
|
+
# Save config without authorization (user can authorize later)
|
|
174
|
+
save_config(config)
|
|
175
|
+
console.print()
|
|
176
|
+
console.print(f" [{Colors.DIM}]Token saved. Run /telegram setup again to authorize chats.[/{Colors.DIM}]")
|
|
177
|
+
|
|
178
|
+
console.print()
|
|
179
|
+
console.print(f"[{Colors.MUTED}]{footer(SEPARATOR_WIDTH)}[/{Colors.MUTED}]")
|
|
180
|
+
console.print()
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
async def _wait_for_authorization(token: str, timeout: int = 60) -> dict | None:
|
|
184
|
+
"""Wait for a user to send /start to the bot.
|
|
185
|
+
|
|
186
|
+
Args:
|
|
187
|
+
token: Bot token
|
|
188
|
+
timeout: Maximum seconds to wait
|
|
189
|
+
|
|
190
|
+
Returns:
|
|
191
|
+
Dict with chat id and name, or None if timeout
|
|
192
|
+
"""
|
|
193
|
+
from ....integrations.telegram.bot import TelegramBot
|
|
194
|
+
|
|
195
|
+
async with TelegramBot(token) as bot:
|
|
196
|
+
start_time = asyncio.get_event_loop().time()
|
|
197
|
+
|
|
198
|
+
while (asyncio.get_event_loop().time() - start_time) < timeout:
|
|
199
|
+
try:
|
|
200
|
+
updates = await bot.get_updates(timeout=5)
|
|
201
|
+
|
|
202
|
+
for update in updates:
|
|
203
|
+
if update.message and update.message.text:
|
|
204
|
+
text = update.message.text.strip()
|
|
205
|
+
# Accept /start or any message as authorization
|
|
206
|
+
if text.startswith("/start") or text:
|
|
207
|
+
chat = update.message.chat
|
|
208
|
+
user = update.message.from_user
|
|
209
|
+
|
|
210
|
+
name = chat.display_name
|
|
211
|
+
if user:
|
|
212
|
+
name = user.display_name
|
|
213
|
+
|
|
214
|
+
return {
|
|
215
|
+
"id": chat.id,
|
|
216
|
+
"name": name,
|
|
217
|
+
}
|
|
218
|
+
except Exception:
|
|
219
|
+
await asyncio.sleep(1)
|
|
220
|
+
continue
|
|
221
|
+
|
|
222
|
+
return None
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
def _handle_status() -> None:
|
|
226
|
+
"""Show current Telegram configuration and status."""
|
|
227
|
+
from ....integrations.telegram import get_config
|
|
228
|
+
from ....integrations.telegram.bot import verify_token
|
|
229
|
+
|
|
230
|
+
config = get_config()
|
|
231
|
+
|
|
232
|
+
console.print()
|
|
233
|
+
console.print(f"[{Colors.MUTED}]{header('Telegram Status', SEPARATOR_WIDTH)}[/{Colors.MUTED}]")
|
|
234
|
+
console.print()
|
|
235
|
+
|
|
236
|
+
# Configuration
|
|
237
|
+
if config.is_configured():
|
|
238
|
+
console.print(f" [{Colors.DIM}]Bot Token:[/{Colors.DIM}] [{Colors.SUCCESS}]configured[/{Colors.SUCCESS}]")
|
|
239
|
+
|
|
240
|
+
# Verify token is still valid
|
|
241
|
+
bot_info = asyncio.run(verify_token(config.bot_token))
|
|
242
|
+
if bot_info:
|
|
243
|
+
console.print(f" [{Colors.DIM}]Bot:[/{Colors.DIM}] @{bot_info.username}")
|
|
244
|
+
else:
|
|
245
|
+
console.print(f" [{Colors.DIM}]Bot:[/{Colors.DIM}] [{Colors.ERROR}]token invalid[/{Colors.ERROR}]")
|
|
246
|
+
else:
|
|
247
|
+
console.print(f" [{Colors.DIM}]Bot Token:[/{Colors.DIM}] [{Colors.WARNING}]not configured[/{Colors.WARNING}]")
|
|
248
|
+
console.print()
|
|
249
|
+
console.print(f" [{Colors.DIM}]Run /telegram setup to configure[/{Colors.DIM}]")
|
|
250
|
+
console.print()
|
|
251
|
+
console.print(f"[{Colors.MUTED}]{footer(SEPARATOR_WIDTH)}[/{Colors.MUTED}]")
|
|
252
|
+
console.print()
|
|
253
|
+
return
|
|
254
|
+
|
|
255
|
+
# Authorized chats
|
|
256
|
+
if config.authorized_chats:
|
|
257
|
+
console.print(f" [{Colors.DIM}]Authorized:[/{Colors.DIM}] {len(config.authorized_chats)} chat(s)")
|
|
258
|
+
for chat_id in config.authorized_chats:
|
|
259
|
+
console.print(f" [{Colors.SUBTLE}]{chat_id}[/{Colors.SUBTLE}]")
|
|
260
|
+
else:
|
|
261
|
+
console.print(f" [{Colors.DIM}]Authorized:[/{Colors.DIM}] [{Colors.WARNING}]no chats authorized[/{Colors.WARNING}]")
|
|
262
|
+
|
|
263
|
+
# State
|
|
264
|
+
console.print()
|
|
265
|
+
enabled_status = f"[{Colors.SUCCESS}]yes[/{Colors.SUCCESS}]" if config.state.enabled else f"[{Colors.DIM}]no[/{Colors.DIM}]"
|
|
266
|
+
console.print(f" [{Colors.DIM}]Enabled:[/{Colors.DIM}] {enabled_status}")
|
|
267
|
+
|
|
268
|
+
if config.state.last_connected:
|
|
269
|
+
console.print(f" [{Colors.DIM}]Last Active:[/{Colors.DIM}] {config.state.last_connected}")
|
|
270
|
+
|
|
271
|
+
# Settings
|
|
272
|
+
console.print()
|
|
273
|
+
console.print(f" [{Colors.DIM}]Settings:[/{Colors.DIM}]")
|
|
274
|
+
console.print(f" [{Colors.SUBTLE}]Streaming mode:[/{Colors.SUBTLE}] {config.settings.streaming_mode}")
|
|
275
|
+
console.print(f" [{Colors.SUBTLE}]Update interval:[/{Colors.SUBTLE}] {config.settings.update_interval_ms}ms")
|
|
276
|
+
console.print(f" [{Colors.SUBTLE}]Show thinking:[/{Colors.SUBTLE}] {config.settings.show_thinking}")
|
|
277
|
+
console.print(f" [{Colors.SUBTLE}]Show tools:[/{Colors.SUBTLE}] {config.settings.show_tool_calls}")
|
|
278
|
+
|
|
279
|
+
console.print()
|
|
280
|
+
console.print(f"[{Colors.MUTED}]{footer(SEPARATOR_WIDTH)}[/{Colors.MUTED}]")
|
|
281
|
+
console.print()
|
|
282
|
+
|
|
283
|
+
|
|
284
|
+
def _handle_test() -> None:
|
|
285
|
+
"""Send a test message to authorized chats."""
|
|
286
|
+
from ....integrations.telegram import get_config
|
|
287
|
+
from ....integrations.telegram.bot import TelegramBot
|
|
288
|
+
|
|
289
|
+
config = get_config()
|
|
290
|
+
|
|
291
|
+
if not config.is_configured():
|
|
292
|
+
console.print(f"[{Colors.WARNING}]Telegram not configured. Run /telegram setup first.[/{Colors.WARNING}]")
|
|
293
|
+
return
|
|
294
|
+
|
|
295
|
+
if not config.authorized_chats:
|
|
296
|
+
console.print(f"[{Colors.WARNING}]No authorized chats. Run /telegram setup to authorize.[/{Colors.WARNING}]")
|
|
297
|
+
return
|
|
298
|
+
|
|
299
|
+
console.print()
|
|
300
|
+
console.print(f" [{Colors.DIM}]Sending test message...[/{Colors.DIM}]")
|
|
301
|
+
|
|
302
|
+
async def send_test():
|
|
303
|
+
async with TelegramBot(config.bot_token) as bot:
|
|
304
|
+
bot_info = await bot.get_me()
|
|
305
|
+
for chat_id in config.authorized_chats:
|
|
306
|
+
try:
|
|
307
|
+
await bot.send_message(
|
|
308
|
+
chat_id,
|
|
309
|
+
f"*EmDash Test*\n\nBot `@{bot_info.username}` is connected and working.",
|
|
310
|
+
parse_mode="Markdown",
|
|
311
|
+
)
|
|
312
|
+
console.print(f" [{Colors.SUCCESS}]Sent to chat {chat_id}[/{Colors.SUCCESS}]")
|
|
313
|
+
except Exception as e:
|
|
314
|
+
console.print(f" [{Colors.ERROR}]Failed to send to {chat_id}: {e}[/{Colors.ERROR}]")
|
|
315
|
+
|
|
316
|
+
asyncio.run(send_test())
|
|
317
|
+
console.print()
|
|
318
|
+
|
|
319
|
+
|
|
320
|
+
def _handle_connect() -> None:
|
|
321
|
+
"""Start the Telegram bridge in foreground mode."""
|
|
322
|
+
from ....integrations.telegram import get_config, save_config
|
|
323
|
+
|
|
324
|
+
config = get_config()
|
|
325
|
+
|
|
326
|
+
if not config.is_configured():
|
|
327
|
+
console.print(f"[{Colors.WARNING}]Telegram not configured. Run /telegram setup first.[/{Colors.WARNING}]")
|
|
328
|
+
return
|
|
329
|
+
|
|
330
|
+
console.print()
|
|
331
|
+
console.print(f"[{Colors.MUTED}]{header('Telegram Bridge', SEPARATOR_WIDTH)}[/{Colors.MUTED}]")
|
|
332
|
+
console.print()
|
|
333
|
+
console.print(f" [{Colors.SUCCESS}]Bridge starting...[/{Colors.SUCCESS}]")
|
|
334
|
+
console.print(f" [{Colors.DIM}]Listening for messages. Press Ctrl+C to stop.[/{Colors.DIM}]")
|
|
335
|
+
console.print()
|
|
336
|
+
|
|
337
|
+
# Update state
|
|
338
|
+
config.state.enabled = True
|
|
339
|
+
config.state.last_connected = datetime.now().isoformat()
|
|
340
|
+
save_config(config)
|
|
341
|
+
|
|
342
|
+
try:
|
|
343
|
+
asyncio.run(_run_bridge(config))
|
|
344
|
+
except KeyboardInterrupt:
|
|
345
|
+
console.print()
|
|
346
|
+
console.print(f" [{Colors.DIM}]Bridge stopped[/{Colors.DIM}]")
|
|
347
|
+
|
|
348
|
+
console.print()
|
|
349
|
+
console.print(f"[{Colors.MUTED}]{footer(SEPARATOR_WIDTH)}[/{Colors.MUTED}]")
|
|
350
|
+
console.print()
|
|
351
|
+
|
|
352
|
+
|
|
353
|
+
async def _run_bridge(config) -> None:
|
|
354
|
+
"""Run the Telegram bridge (receives messages and forwards to agent).
|
|
355
|
+
|
|
356
|
+
Connects Telegram messages to the EmDash agent and streams
|
|
357
|
+
SSE responses back as Telegram messages.
|
|
358
|
+
"""
|
|
359
|
+
from ....integrations.telegram.bridge import TelegramBridge
|
|
360
|
+
from ....integrations.telegram import save_config
|
|
361
|
+
from ....server_manager import get_server_manager
|
|
362
|
+
|
|
363
|
+
# Get server URL from server manager (starts server if needed)
|
|
364
|
+
server = get_server_manager()
|
|
365
|
+
server_url = server.get_server_url()
|
|
366
|
+
console.print(f" [{Colors.SUBTLE}]Server: {server_url}[/{Colors.SUBTLE}]")
|
|
367
|
+
|
|
368
|
+
def on_message(event_type: str, data: dict) -> None:
|
|
369
|
+
"""Log bridge events to the console."""
|
|
370
|
+
if event_type == "bridge_started":
|
|
371
|
+
console.print(f" [{Colors.SUBTLE}]Bot: @{data.get('bot', 'unknown')}[/{Colors.SUBTLE}]")
|
|
372
|
+
console.print()
|
|
373
|
+
elif event_type == "message_received":
|
|
374
|
+
user = data.get("user", "Unknown")
|
|
375
|
+
text = data.get("text", "")[:50]
|
|
376
|
+
if len(data.get("text", "")) > 50:
|
|
377
|
+
text += "..."
|
|
378
|
+
console.print(f" [{Colors.ACCENT}]{user}:[/{Colors.ACCENT}] {text}")
|
|
379
|
+
elif event_type == "error":
|
|
380
|
+
console.print(f" [{Colors.ERROR}]Error: {data.get('error', 'unknown')}[/{Colors.ERROR}]")
|
|
381
|
+
elif event_type == "send_error":
|
|
382
|
+
console.print(f" [{Colors.WARNING}]Send failed: {data.get('error', 'unknown')}[/{Colors.WARNING}]")
|
|
383
|
+
|
|
384
|
+
bridge = TelegramBridge(config, server_url=server_url, on_message=on_message)
|
|
385
|
+
|
|
386
|
+
try:
|
|
387
|
+
await bridge.start()
|
|
388
|
+
finally:
|
|
389
|
+
# Save config with updated state (last_update_id, etc.)
|
|
390
|
+
save_config(config)
|
|
391
|
+
|
|
392
|
+
|
|
393
|
+
def _handle_disconnect() -> None:
|
|
394
|
+
"""Disable Telegram integration."""
|
|
395
|
+
from ....integrations.telegram import get_config, save_config, delete_config
|
|
396
|
+
from prompt_toolkit import prompt
|
|
397
|
+
|
|
398
|
+
config = get_config()
|
|
399
|
+
|
|
400
|
+
if not config.is_configured():
|
|
401
|
+
console.print(f"[{Colors.DIM}]Telegram is not configured.[/{Colors.DIM}]")
|
|
402
|
+
return
|
|
403
|
+
|
|
404
|
+
console.print()
|
|
405
|
+
console.print(f" [{Colors.WARNING}]This will remove your Telegram configuration.[/{Colors.WARNING}]")
|
|
406
|
+
|
|
407
|
+
try:
|
|
408
|
+
confirm = prompt(" Type 'yes' to confirm: ").strip().lower()
|
|
409
|
+
except (KeyboardInterrupt, EOFError):
|
|
410
|
+
console.print(f"\n [{Colors.DIM}]Cancelled[/{Colors.DIM}]")
|
|
411
|
+
return
|
|
412
|
+
|
|
413
|
+
if confirm == "yes":
|
|
414
|
+
delete_config()
|
|
415
|
+
console.print(f" [{Colors.SUCCESS}]Telegram configuration removed.[/{Colors.SUCCESS}]")
|
|
416
|
+
else:
|
|
417
|
+
console.print(f" [{Colors.DIM}]Cancelled[/{Colors.DIM}]")
|
|
418
|
+
|
|
419
|
+
console.print()
|
|
420
|
+
|
|
421
|
+
|
|
422
|
+
def _handle_settings(args: str) -> None:
|
|
423
|
+
"""View or modify Telegram settings."""
|
|
424
|
+
from ....integrations.telegram import get_config, save_config
|
|
425
|
+
|
|
426
|
+
config = get_config()
|
|
427
|
+
|
|
428
|
+
if not config.is_configured():
|
|
429
|
+
console.print(f"[{Colors.WARNING}]Telegram not configured. Run /telegram setup first.[/{Colors.WARNING}]")
|
|
430
|
+
return
|
|
431
|
+
|
|
432
|
+
if not args:
|
|
433
|
+
# Show current settings
|
|
434
|
+
console.print()
|
|
435
|
+
console.print(f" [{Colors.DIM}]Current Settings:[/{Colors.DIM}]")
|
|
436
|
+
console.print()
|
|
437
|
+
console.print(f" [{Colors.PRIMARY}]streaming_mode[/{Colors.PRIMARY}] = {config.settings.streaming_mode}")
|
|
438
|
+
console.print(f" [{Colors.PRIMARY}]update_interval_ms[/{Colors.PRIMARY}] = {config.settings.update_interval_ms}")
|
|
439
|
+
console.print(f" [{Colors.PRIMARY}]show_thinking[/{Colors.PRIMARY}] = {config.settings.show_thinking}")
|
|
440
|
+
console.print(f" [{Colors.PRIMARY}]show_tool_calls[/{Colors.PRIMARY}] = {config.settings.show_tool_calls}")
|
|
441
|
+
console.print(f" [{Colors.PRIMARY}]compact_mode[/{Colors.PRIMARY}] = {config.settings.compact_mode}")
|
|
442
|
+
console.print()
|
|
443
|
+
console.print(f" [{Colors.DIM}]Usage: /telegram settings <key> <value>[/{Colors.DIM}]")
|
|
444
|
+
console.print()
|
|
445
|
+
return
|
|
446
|
+
|
|
447
|
+
# Parse key=value or key value
|
|
448
|
+
parts = args.replace("=", " ").split()
|
|
449
|
+
if len(parts) < 2:
|
|
450
|
+
console.print(f"[{Colors.WARNING}]Usage: /telegram settings <key> <value>[/{Colors.WARNING}]")
|
|
451
|
+
return
|
|
452
|
+
|
|
453
|
+
key = parts[0]
|
|
454
|
+
value = parts[1]
|
|
455
|
+
|
|
456
|
+
# Update setting
|
|
457
|
+
if key == "streaming_mode":
|
|
458
|
+
if value not in ("edit", "append"):
|
|
459
|
+
console.print(f"[{Colors.WARNING}]streaming_mode must be 'edit' or 'append'[/{Colors.WARNING}]")
|
|
460
|
+
return
|
|
461
|
+
config.settings.streaming_mode = value
|
|
462
|
+
elif key == "update_interval_ms":
|
|
463
|
+
try:
|
|
464
|
+
config.settings.update_interval_ms = int(value)
|
|
465
|
+
except ValueError:
|
|
466
|
+
console.print(f"[{Colors.WARNING}]update_interval_ms must be a number[/{Colors.WARNING}]")
|
|
467
|
+
return
|
|
468
|
+
elif key == "show_thinking":
|
|
469
|
+
config.settings.show_thinking = value.lower() in ("true", "1", "yes")
|
|
470
|
+
elif key == "show_tool_calls":
|
|
471
|
+
config.settings.show_tool_calls = value.lower() in ("true", "1", "yes")
|
|
472
|
+
elif key == "compact_mode":
|
|
473
|
+
config.settings.compact_mode = value.lower() in ("true", "1", "yes")
|
|
474
|
+
else:
|
|
475
|
+
console.print(f"[{Colors.WARNING}]Unknown setting: {key}[/{Colors.WARNING}]")
|
|
476
|
+
return
|
|
477
|
+
|
|
478
|
+
save_config(config)
|
|
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()
|