vibe-remote 2.1.6__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.
- config/__init__.py +37 -0
- config/paths.py +56 -0
- config/v2_compat.py +74 -0
- config/v2_config.py +206 -0
- config/v2_sessions.py +73 -0
- config/v2_settings.py +115 -0
- core/__init__.py +0 -0
- core/controller.py +736 -0
- core/handlers/__init__.py +13 -0
- core/handlers/command_handlers.py +342 -0
- core/handlers/message_handler.py +365 -0
- core/handlers/session_handler.py +233 -0
- core/handlers/settings_handler.py +362 -0
- modules/__init__.py +0 -0
- modules/agent_router.py +58 -0
- modules/agents/__init__.py +38 -0
- modules/agents/base.py +91 -0
- modules/agents/claude_agent.py +344 -0
- modules/agents/codex_agent.py +368 -0
- modules/agents/opencode_agent.py +2155 -0
- modules/agents/service.py +41 -0
- modules/agents/subagent_router.py +136 -0
- modules/claude_client.py +154 -0
- modules/im/__init__.py +63 -0
- modules/im/base.py +323 -0
- modules/im/factory.py +60 -0
- modules/im/formatters/__init__.py +4 -0
- modules/im/formatters/base_formatter.py +639 -0
- modules/im/formatters/slack_formatter.py +127 -0
- modules/im/slack.py +2091 -0
- modules/session_manager.py +138 -0
- modules/settings_manager.py +587 -0
- vibe/__init__.py +6 -0
- vibe/__main__.py +12 -0
- vibe/_version.py +34 -0
- vibe/api.py +412 -0
- vibe/cli.py +637 -0
- vibe/runtime.py +213 -0
- vibe/service_main.py +101 -0
- vibe/templates/slack_manifest.json +65 -0
- vibe/ui/dist/assets/index-8g3mNwMK.js +35 -0
- vibe/ui/dist/assets/index-M55aMB5R.css +1 -0
- vibe/ui/dist/assets/logo-BzryTZ7u.png +0 -0
- vibe/ui/dist/index.html +17 -0
- vibe/ui/dist/logo.png +0 -0
- vibe/ui/dist/vite.svg +1 -0
- vibe/ui_server.py +346 -0
- vibe_remote-2.1.6.dist-info/METADATA +295 -0
- vibe_remote-2.1.6.dist-info/RECORD +52 -0
- vibe_remote-2.1.6.dist-info/WHEEL +4 -0
- vibe_remote-2.1.6.dist-info/entry_points.txt +2 -0
- vibe_remote-2.1.6.dist-info/licenses/LICENSE +21 -0
modules/im/base.py
ADDED
|
@@ -0,0 +1,323 @@
|
|
|
1
|
+
"""Base classes and data structures for IM platform abstraction"""
|
|
2
|
+
|
|
3
|
+
import logging
|
|
4
|
+
from abc import ABC, abstractmethod
|
|
5
|
+
from typing import Optional, Callable, Dict, Any
|
|
6
|
+
from dataclasses import dataclass
|
|
7
|
+
|
|
8
|
+
logger = logging.getLogger(__name__)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
# Data structures for platform-agnostic messaging
|
|
12
|
+
@dataclass
|
|
13
|
+
class MessageContext:
|
|
14
|
+
"""Platform-agnostic message context"""
|
|
15
|
+
user_id: str
|
|
16
|
+
channel_id: str
|
|
17
|
+
thread_id: Optional[str] = None
|
|
18
|
+
message_id: Optional[str] = None
|
|
19
|
+
platform_specific: Optional[Dict[str, Any]] = None
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
@dataclass
|
|
23
|
+
class InlineButton:
|
|
24
|
+
"""Platform-agnostic inline button"""
|
|
25
|
+
text: str
|
|
26
|
+
callback_data: str
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
@dataclass
|
|
30
|
+
class InlineKeyboard:
|
|
31
|
+
"""Platform-agnostic inline keyboard"""
|
|
32
|
+
buttons: list[list[InlineButton]] # 2D array for row/column layout
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
# Configuration base class
|
|
36
|
+
@dataclass
|
|
37
|
+
class BaseIMConfig(ABC):
|
|
38
|
+
"""Abstract base class for IM platform configurations"""
|
|
39
|
+
|
|
40
|
+
@abstractmethod
|
|
41
|
+
def validate(self) -> None:
|
|
42
|
+
"""Validate the configuration
|
|
43
|
+
|
|
44
|
+
Raises:
|
|
45
|
+
ValueError: If configuration is invalid
|
|
46
|
+
"""
|
|
47
|
+
pass
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def validate_required_string(self, value: Optional[str], field_name: str) -> None:
|
|
51
|
+
"""Helper method to validate required string fields
|
|
52
|
+
|
|
53
|
+
Args:
|
|
54
|
+
value: The value to validate
|
|
55
|
+
field_name: Name of the field for error messages
|
|
56
|
+
|
|
57
|
+
Raises:
|
|
58
|
+
ValueError: If value is None or empty
|
|
59
|
+
"""
|
|
60
|
+
if not value or not value.strip():
|
|
61
|
+
raise ValueError(f"{field_name} is required and cannot be empty")
|
|
62
|
+
|
|
63
|
+
def validate_optional_int(self, value: Optional[str], field_name: str) -> Optional[int]:
|
|
64
|
+
"""Helper method to validate and convert optional integer fields
|
|
65
|
+
|
|
66
|
+
Args:
|
|
67
|
+
value: String value to convert
|
|
68
|
+
field_name: Name of the field for error messages
|
|
69
|
+
|
|
70
|
+
Returns:
|
|
71
|
+
Converted integer or None
|
|
72
|
+
|
|
73
|
+
Raises:
|
|
74
|
+
ValueError: If value is not a valid integer
|
|
75
|
+
"""
|
|
76
|
+
if value is None or value == "":
|
|
77
|
+
return None
|
|
78
|
+
|
|
79
|
+
try:
|
|
80
|
+
return int(value)
|
|
81
|
+
except ValueError:
|
|
82
|
+
raise ValueError(f"{field_name} must be a valid integer, got: {value}")
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
# IM Client base class
|
|
86
|
+
class BaseIMClient(ABC):
|
|
87
|
+
"""Abstract base class for IM platform clients"""
|
|
88
|
+
|
|
89
|
+
def __init__(self, config: BaseIMConfig):
|
|
90
|
+
self.config = config
|
|
91
|
+
# Initialize callback storage
|
|
92
|
+
self.on_message_callback: Optional[Callable] = None
|
|
93
|
+
self.on_command_callbacks: Dict[str, Callable] = {}
|
|
94
|
+
self.on_callback_query_callback: Optional[Callable] = None
|
|
95
|
+
# Platform-specific formatter will be set by subclasses
|
|
96
|
+
self.formatter: Optional[Any] = None
|
|
97
|
+
|
|
98
|
+
def get_default_parse_mode(self) -> Optional[str]:
|
|
99
|
+
"""Get the default parse mode for this platform
|
|
100
|
+
|
|
101
|
+
Returns:
|
|
102
|
+
Default parse mode string for the platform
|
|
103
|
+
"""
|
|
104
|
+
# Default implementation - subclasses should override
|
|
105
|
+
return None
|
|
106
|
+
|
|
107
|
+
def should_use_thread_for_reply(self) -> bool:
|
|
108
|
+
"""Check if this platform uses threads for replies
|
|
109
|
+
|
|
110
|
+
Returns:
|
|
111
|
+
True if platform uses threads (like Slack), False otherwise
|
|
112
|
+
"""
|
|
113
|
+
# Default implementation - subclasses should override
|
|
114
|
+
return False
|
|
115
|
+
|
|
116
|
+
@abstractmethod
|
|
117
|
+
async def send_message(self, context: MessageContext, text: str,
|
|
118
|
+
parse_mode: Optional[str] = None,
|
|
119
|
+
reply_to: Optional[str] = None) -> str:
|
|
120
|
+
"""Send a text message
|
|
121
|
+
|
|
122
|
+
Args:
|
|
123
|
+
context: Message context (channel, thread, etc)
|
|
124
|
+
text: Message text
|
|
125
|
+
parse_mode: Optional formatting mode (markdown, html, etc)
|
|
126
|
+
reply_to: Optional message ID to reply to
|
|
127
|
+
|
|
128
|
+
Returns:
|
|
129
|
+
Message ID of sent message
|
|
130
|
+
"""
|
|
131
|
+
pass
|
|
132
|
+
|
|
133
|
+
@abstractmethod
|
|
134
|
+
async def send_message_with_buttons(self, context: MessageContext, text: str,
|
|
135
|
+
keyboard: InlineKeyboard,
|
|
136
|
+
parse_mode: Optional[str] = None) -> str:
|
|
137
|
+
"""Send a message with inline buttons
|
|
138
|
+
|
|
139
|
+
Args:
|
|
140
|
+
context: Message context
|
|
141
|
+
text: Message text
|
|
142
|
+
keyboard: Inline keyboard configuration
|
|
143
|
+
parse_mode: Optional formatting mode
|
|
144
|
+
|
|
145
|
+
Returns:
|
|
146
|
+
Message ID of sent message
|
|
147
|
+
"""
|
|
148
|
+
pass
|
|
149
|
+
|
|
150
|
+
async def upload_markdown(
|
|
151
|
+
self,
|
|
152
|
+
context: MessageContext,
|
|
153
|
+
title: str,
|
|
154
|
+
content: str,
|
|
155
|
+
filetype: str = "markdown",
|
|
156
|
+
) -> str:
|
|
157
|
+
"""Upload markdown content as a file (optional per platform)."""
|
|
158
|
+
raise NotImplementedError
|
|
159
|
+
|
|
160
|
+
@abstractmethod
|
|
161
|
+
async def edit_message(
|
|
162
|
+
self,
|
|
163
|
+
context: MessageContext,
|
|
164
|
+
message_id: str,
|
|
165
|
+
text: Optional[str] = None,
|
|
166
|
+
keyboard: Optional[InlineKeyboard] = None,
|
|
167
|
+
parse_mode: Optional[str] = None,
|
|
168
|
+
) -> bool:
|
|
169
|
+
"""Edit an existing message
|
|
170
|
+
|
|
171
|
+
Args:
|
|
172
|
+
context: Message context
|
|
173
|
+
message_id: ID of message to edit
|
|
174
|
+
text: New text (if provided)
|
|
175
|
+
keyboard: New keyboard (if provided)
|
|
176
|
+
parse_mode: Optional formatting mode (markdown, html, etc)
|
|
177
|
+
|
|
178
|
+
Returns:
|
|
179
|
+
Success status
|
|
180
|
+
"""
|
|
181
|
+
pass
|
|
182
|
+
|
|
183
|
+
async def remove_inline_keyboard(
|
|
184
|
+
self,
|
|
185
|
+
context: MessageContext,
|
|
186
|
+
message_id: str,
|
|
187
|
+
text: Optional[str] = None,
|
|
188
|
+
parse_mode: Optional[str] = None,
|
|
189
|
+
) -> bool:
|
|
190
|
+
"""Remove inline keyboard / actions from a message."""
|
|
191
|
+
if text is None:
|
|
192
|
+
return await self.edit_message(context, message_id, keyboard=None)
|
|
193
|
+
return await self.edit_message(
|
|
194
|
+
context,
|
|
195
|
+
message_id,
|
|
196
|
+
text=text,
|
|
197
|
+
keyboard=None,
|
|
198
|
+
parse_mode=parse_mode,
|
|
199
|
+
)
|
|
200
|
+
|
|
201
|
+
@abstractmethod
|
|
202
|
+
async def answer_callback(self, callback_id: str, text: Optional[str] = None,
|
|
203
|
+
show_alert: bool = False) -> bool:
|
|
204
|
+
"""Answer a callback query from inline button
|
|
205
|
+
|
|
206
|
+
Args:
|
|
207
|
+
callback_id: Callback query ID
|
|
208
|
+
text: Optional notification text
|
|
209
|
+
show_alert: Show as alert popup
|
|
210
|
+
|
|
211
|
+
Returns:
|
|
212
|
+
Success status
|
|
213
|
+
"""
|
|
214
|
+
pass
|
|
215
|
+
|
|
216
|
+
@abstractmethod
|
|
217
|
+
def register_handlers(self):
|
|
218
|
+
"""Register platform-specific message and command handlers"""
|
|
219
|
+
pass
|
|
220
|
+
|
|
221
|
+
@abstractmethod
|
|
222
|
+
def run(self):
|
|
223
|
+
"""Start the bot/client"""
|
|
224
|
+
pass
|
|
225
|
+
|
|
226
|
+
async def shutdown(self) -> None:
|
|
227
|
+
"""Best-effort async shutdown for platform resources."""
|
|
228
|
+
return None
|
|
229
|
+
|
|
230
|
+
@abstractmethod
|
|
231
|
+
async def get_user_info(self, user_id: str) -> Dict[str, Any]:
|
|
232
|
+
"""Get information about a user
|
|
233
|
+
|
|
234
|
+
Args:
|
|
235
|
+
user_id: Platform-specific user ID
|
|
236
|
+
|
|
237
|
+
Returns:
|
|
238
|
+
User information dict
|
|
239
|
+
"""
|
|
240
|
+
pass
|
|
241
|
+
|
|
242
|
+
@abstractmethod
|
|
243
|
+
async def get_channel_info(self, channel_id: str) -> Dict[str, Any]:
|
|
244
|
+
"""Get information about a channel/chat
|
|
245
|
+
|
|
246
|
+
Args:
|
|
247
|
+
channel_id: Platform-specific channel ID
|
|
248
|
+
|
|
249
|
+
Returns:
|
|
250
|
+
Channel information dict
|
|
251
|
+
"""
|
|
252
|
+
pass
|
|
253
|
+
|
|
254
|
+
def register_callbacks(self,
|
|
255
|
+
on_message: Optional[Callable] = None,
|
|
256
|
+
on_command: Optional[Dict[str, Callable]] = None,
|
|
257
|
+
on_callback_query: Optional[Callable] = None,
|
|
258
|
+
**kwargs):
|
|
259
|
+
"""Register callback functions for different events
|
|
260
|
+
|
|
261
|
+
Args:
|
|
262
|
+
on_message: Callback for text messages
|
|
263
|
+
on_command: Dict of command callbacks
|
|
264
|
+
on_callback_query: Callback for button clicks
|
|
265
|
+
**kwargs: Additional platform-specific callbacks
|
|
266
|
+
"""
|
|
267
|
+
self.on_message_callback = on_message
|
|
268
|
+
self.on_command_callbacks = on_command or {}
|
|
269
|
+
self.on_callback_query_callback = on_callback_query
|
|
270
|
+
|
|
271
|
+
# Store any additional callbacks
|
|
272
|
+
for key, value in kwargs.items():
|
|
273
|
+
setattr(self, f"{key}_callback", value)
|
|
274
|
+
|
|
275
|
+
def log_error(self, message: str, exception: Optional[Exception] = None):
|
|
276
|
+
"""Standardized error logging
|
|
277
|
+
|
|
278
|
+
Args:
|
|
279
|
+
message: Error message
|
|
280
|
+
exception: Optional exception to log
|
|
281
|
+
"""
|
|
282
|
+
if exception:
|
|
283
|
+
logger.error(f"{message}: {exception}")
|
|
284
|
+
else:
|
|
285
|
+
logger.error(message)
|
|
286
|
+
|
|
287
|
+
def log_info(self, message: str):
|
|
288
|
+
"""Standardized info logging
|
|
289
|
+
|
|
290
|
+
Args:
|
|
291
|
+
message: Info message
|
|
292
|
+
"""
|
|
293
|
+
logger.info(message)
|
|
294
|
+
|
|
295
|
+
async def add_reaction(
|
|
296
|
+
self, context: MessageContext, message_id: str, emoji: str
|
|
297
|
+
) -> bool:
|
|
298
|
+
"""Add a reaction to an existing message.
|
|
299
|
+
|
|
300
|
+
Default implementation returns False (unsupported).
|
|
301
|
+
"""
|
|
302
|
+
return False
|
|
303
|
+
|
|
304
|
+
async def remove_reaction(
|
|
305
|
+
self, context: MessageContext, message_id: str, emoji: str
|
|
306
|
+
) -> bool:
|
|
307
|
+
"""Remove a reaction from an existing message.
|
|
308
|
+
|
|
309
|
+
Default implementation returns False (unsupported).
|
|
310
|
+
"""
|
|
311
|
+
return False
|
|
312
|
+
|
|
313
|
+
@abstractmethod
|
|
314
|
+
def format_markdown(self, text: str) -> str:
|
|
315
|
+
"""Format markdown text for the specific platform
|
|
316
|
+
|
|
317
|
+
Args:
|
|
318
|
+
text: Text with common markdown formatting
|
|
319
|
+
|
|
320
|
+
Returns:
|
|
321
|
+
Platform-specific formatted text
|
|
322
|
+
"""
|
|
323
|
+
pass
|
modules/im/factory.py
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
"""Factory for creating IM platform clients"""
|
|
2
|
+
|
|
3
|
+
import logging
|
|
4
|
+
from typing import Union, TYPE_CHECKING
|
|
5
|
+
|
|
6
|
+
from .base import BaseIMClient
|
|
7
|
+
|
|
8
|
+
# Use delayed imports to avoid circular import issues
|
|
9
|
+
if TYPE_CHECKING:
|
|
10
|
+
from config.v2_config import V2Config
|
|
11
|
+
|
|
12
|
+
logger = logging.getLogger(__name__)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class IMFactory:
|
|
16
|
+
"""Factory class to create the appropriate IM client based on platform"""
|
|
17
|
+
|
|
18
|
+
@staticmethod
|
|
19
|
+
def create_client(config) -> BaseIMClient:
|
|
20
|
+
"""Create and return the appropriate IM client based on configuration
|
|
21
|
+
|
|
22
|
+
Args:
|
|
23
|
+
config: Application configuration
|
|
24
|
+
|
|
25
|
+
Returns:
|
|
26
|
+
Instance of platform-specific IM client
|
|
27
|
+
|
|
28
|
+
Raises:
|
|
29
|
+
ValueError: If platform is not supported
|
|
30
|
+
"""
|
|
31
|
+
# Dynamic imports to avoid circular dependency
|
|
32
|
+
from .slack import SlackBot
|
|
33
|
+
|
|
34
|
+
if not config.slack:
|
|
35
|
+
raise ValueError("Slack configuration not found")
|
|
36
|
+
logger.info("Creating Slack client")
|
|
37
|
+
return SlackBot(config.slack)
|
|
38
|
+
|
|
39
|
+
@staticmethod
|
|
40
|
+
def get_supported_platforms() -> list[str]:
|
|
41
|
+
"""Get list of supported platforms
|
|
42
|
+
|
|
43
|
+
Returns:
|
|
44
|
+
List of supported platform names
|
|
45
|
+
"""
|
|
46
|
+
return ["slack"]
|
|
47
|
+
|
|
48
|
+
@staticmethod
|
|
49
|
+
def validate_platform_config(config) -> None:
|
|
50
|
+
"""Validate platform configuration before creating client
|
|
51
|
+
|
|
52
|
+
Args:
|
|
53
|
+
config: Application configuration
|
|
54
|
+
|
|
55
|
+
Raises:
|
|
56
|
+
ValueError: If configuration is invalid
|
|
57
|
+
"""
|
|
58
|
+
if config.slack is None:
|
|
59
|
+
raise ValueError("Missing configuration for platform: slack")
|
|
60
|
+
config.slack.validate()
|