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.
Files changed (52) hide show
  1. config/__init__.py +37 -0
  2. config/paths.py +56 -0
  3. config/v2_compat.py +74 -0
  4. config/v2_config.py +206 -0
  5. config/v2_sessions.py +73 -0
  6. config/v2_settings.py +115 -0
  7. core/__init__.py +0 -0
  8. core/controller.py +736 -0
  9. core/handlers/__init__.py +13 -0
  10. core/handlers/command_handlers.py +342 -0
  11. core/handlers/message_handler.py +365 -0
  12. core/handlers/session_handler.py +233 -0
  13. core/handlers/settings_handler.py +362 -0
  14. modules/__init__.py +0 -0
  15. modules/agent_router.py +58 -0
  16. modules/agents/__init__.py +38 -0
  17. modules/agents/base.py +91 -0
  18. modules/agents/claude_agent.py +344 -0
  19. modules/agents/codex_agent.py +368 -0
  20. modules/agents/opencode_agent.py +2155 -0
  21. modules/agents/service.py +41 -0
  22. modules/agents/subagent_router.py +136 -0
  23. modules/claude_client.py +154 -0
  24. modules/im/__init__.py +63 -0
  25. modules/im/base.py +323 -0
  26. modules/im/factory.py +60 -0
  27. modules/im/formatters/__init__.py +4 -0
  28. modules/im/formatters/base_formatter.py +639 -0
  29. modules/im/formatters/slack_formatter.py +127 -0
  30. modules/im/slack.py +2091 -0
  31. modules/session_manager.py +138 -0
  32. modules/settings_manager.py +587 -0
  33. vibe/__init__.py +6 -0
  34. vibe/__main__.py +12 -0
  35. vibe/_version.py +34 -0
  36. vibe/api.py +412 -0
  37. vibe/cli.py +637 -0
  38. vibe/runtime.py +213 -0
  39. vibe/service_main.py +101 -0
  40. vibe/templates/slack_manifest.json +65 -0
  41. vibe/ui/dist/assets/index-8g3mNwMK.js +35 -0
  42. vibe/ui/dist/assets/index-M55aMB5R.css +1 -0
  43. vibe/ui/dist/assets/logo-BzryTZ7u.png +0 -0
  44. vibe/ui/dist/index.html +17 -0
  45. vibe/ui/dist/logo.png +0 -0
  46. vibe/ui/dist/vite.svg +1 -0
  47. vibe/ui_server.py +346 -0
  48. vibe_remote-2.1.6.dist-info/METADATA +295 -0
  49. vibe_remote-2.1.6.dist-info/RECORD +52 -0
  50. vibe_remote-2.1.6.dist-info/WHEEL +4 -0
  51. vibe_remote-2.1.6.dist-info/entry_points.txt +2 -0
  52. 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()
@@ -0,0 +1,4 @@
1
+ from .base_formatter import BaseMarkdownFormatter
2
+ from .slack_formatter import SlackFormatter
3
+
4
+ __all__ = ['BaseMarkdownFormatter', 'SlackFormatter']