smart-bot-factory 0.1.5__py3-none-any.whl → 0.1.7__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.

Potentially problematic release.


This version of smart-bot-factory might be problematic. Click here for more details.

smart_bot_factory/cli.py CHANGED
@@ -35,9 +35,9 @@ def list():
35
35
  click.echo("🤖 Нет доступных ботов")
36
36
  return
37
37
 
38
- click.echo("🤖 Доступные боты:")
39
- for bot in sorted(bots):
40
- click.echo(f" 📱 {bot}")
38
+ click.echo("🤖 Доступные боты:")
39
+ for bot in sorted(bots):
40
+ click.echo(f" 📱 {bot}")
41
41
 
42
42
  @cli.command()
43
43
  @click.argument("bot_id")
@@ -191,8 +191,8 @@ def prompts(bot_id: str, list_prompts: bool = False, edit_prompt: str = None, ad
191
191
  if not prompts_dir.exists():
192
192
  raise click.ClickException(f"Папка промптов не найдена для бота {bot_id}")
193
193
 
194
- if list_prompts:
195
- # Показываем список промптов
194
+ if list_prompts or (not edit_prompt and not add_prompt):
195
+ # Показываем список промптов (по умолчанию или с флагом --list)
196
196
  prompt_files = [f.name for f in prompts_dir.glob("*.txt")]
197
197
 
198
198
  if not prompt_files:
@@ -202,6 +202,13 @@ def prompts(bot_id: str, list_prompts: bool = False, edit_prompt: str = None, ad
202
202
  click.echo(f"📝 Промпты бота {bot_id}:")
203
203
  for prompt_file in sorted(prompt_files):
204
204
  click.echo(f" 📄 {prompt_file[:-4]}")
205
+
206
+ # Показываем справку только если не указан флаг --list
207
+ if not list_prompts:
208
+ click.echo()
209
+ click.echo("Использование:")
210
+ click.echo(" sbf prompts <bot_id> --edit <prompt_name> # Редактировать промпт")
211
+ click.echo(" sbf prompts <bot_id> --add <prompt_name> # Добавить новый промпт")
205
212
 
206
213
  elif edit_prompt:
207
214
  # Редактируем промпт
@@ -231,12 +238,6 @@ def prompts(bot_id: str, list_prompts: bool = False, edit_prompt: str = None, ad
231
238
  click.echo(f"📝 Создаем новый промпт {add_prompt}...")
232
239
  subprocess.run([editor, str(prompt_file)], check=True)
233
240
 
234
- else:
235
- # Показываем справку
236
- click.echo("📖 Использование:")
237
- click.echo(" 📋 sbf prompts <bot_id> --list # Показать список промптов")
238
- click.echo(" ✏️ sbf prompts <bot_id> --edit <prompt_name> # Редактировать промпт")
239
- click.echo(" ➕ sbf prompts <bot_id> --add <prompt_name> # Добавить новый промпт")
240
241
 
241
242
  except subprocess.CalledProcessError as e:
242
243
  click.echo(f"❌ Ошибка при открытии редактора: {e}", err=True)
@@ -259,21 +260,21 @@ def rm(bot_id: str, force: bool = False):
259
260
  # Проверяем существование бота
260
261
  bot_path = PROJECT_ROOT / Path("bots") / bot_id
261
262
  if not bot_path.exists():
262
- raise click.ClickException(f"Бот {bot_id} не найден в папке bots/")
263
+ raise click.ClickException(f"🤖 Бот {bot_id} не найден в папке bots/")
263
264
 
264
265
  # Проверяем наличие основного файла бота в корневой директории
265
266
  bot_file = Path(f"{bot_id}.py")
266
267
  if not bot_file.exists():
267
- raise click.ClickException(f"Файл {bot_id}.py не найден в корневой директории")
268
+ raise click.ClickException(f"📄 Файл {bot_id}.py не найден в корневой директории")
268
269
 
269
270
  # Показываем что будет удалено
270
271
  click.echo("🗑️ Будет удалено:")
271
- click.echo(f" 📄 Файл запускалки: {bot_file}")
272
- click.echo(f" 📁 Папка бота: {bot_path}")
272
+ click.echo(f" 📄 Файл запускалки: {bot_file}")
273
+ click.echo(f" 📁 Папка бота: {bot_path}")
273
274
 
274
275
  # Запрашиваем подтверждение если не указан --force
275
276
  if not force:
276
- if not click.confirm(f" Вы уверены, что хотите удалить бота {bot_id}?"):
277
+ if not click.confirm(f"⚠️ Вы уверены, что хотите удалить бота {bot_id}?"):
277
278
  click.echo("❌ Удаление отменено")
278
279
  return
279
280
 
@@ -288,7 +289,7 @@ def rm(bot_id: str, force: bool = False):
288
289
  shutil.rmtree(bot_path)
289
290
  click.echo(f"✅ Папка {bot_path} удалена")
290
291
 
291
- click.echo(f"🗑️ Бот {bot_id} полностью удален")
292
+ click.echo(f"🎉 Бот {bot_id} полностью удален")
292
293
 
293
294
  except Exception as e:
294
295
  click.echo(f"❌ Ошибка при удалении бота: {e}", err=True)
@@ -318,8 +319,8 @@ def copy(source_bot_id: str, new_bot_id: str, force: bool = False):
318
319
 
319
320
  if new_bot_path.exists() or new_bot_file.exists():
320
321
  if not force:
321
- if not click.confirm(f"⚠️ Бот {new_bot_id} уже существует. Перезаписать?"):
322
- click.echo("Копирование отменено")
322
+ if not click.confirm(f"Бот {new_bot_id} уже существует. Перезаписать?"):
323
+ click.echo("Копирование отменено")
323
324
  return
324
325
  else:
325
326
  click.echo(f"⚠️ Перезаписываем существующего бота {new_bot_id}")
@@ -414,14 +415,16 @@ def create_bot_template(bot_id: str) -> str:
414
415
 
415
416
  import asyncio
416
417
 
417
- from smart_bot_factory.core import Router
418
- from smart_bot_factory.core import send_message_by_human
419
- from smart_bot_factory.clients import supabase_client
418
+ from smart_bot_factory.router import Router
419
+ from smart_bot_factory.message import send_message_by_human
420
+ from smart_bot_factory.supabase import SupabaseClient
420
421
  from smart_bot_factory.creation import BotBuilder
421
422
 
422
423
  # Создаем роутер для всех обработчиков
423
424
  router = Router("{bot_id}_handlers")
424
425
 
426
+ supabase_client = SupabaseClient("{bot_id}")
427
+
425
428
  # =============================================================================
426
429
  # ОБРАБОТЧИКИ СОБЫТИЙ
427
430
  # =============================================================================
@@ -677,18 +680,10 @@ def copy_bot_template(source_bot_id: str, new_bot_id: str):
677
680
  new_bot_file.write_text(content, encoding='utf-8')
678
681
  click.echo(f" 📄 Файл запускалки скопирован: {new_bot_id}.py")
679
682
 
680
- # Копируем .env файл
681
- source_env = source_dir / ".env"
683
+ # Создаем шаблон .env файла (НЕ копируем существующий)
682
684
  new_env = new_dir / ".env"
683
-
684
- if source_env.exists():
685
- shutil.copy2(source_env, new_env)
686
-
687
- # Заменяем BOT_ID в .env
688
- env_content = new_env.read_text(encoding='utf-8')
689
- env_content = env_content.replace(f'BOT_ID={source_bot_id}', f'BOT_ID={new_bot_id}')
690
- new_env.write_text(env_content, encoding='utf-8')
691
- click.echo(f" ⚙️ .env файл скопирован и обновлен")
685
+ new_env.write_text(create_env_template(new_bot_id), encoding='utf-8')
686
+ click.echo(f" ⚙️ Создан шаблон .env файла")
692
687
 
693
688
  # Копируем промпты
694
689
  source_prompts = source_dir / "prompts"
@@ -31,45 +31,6 @@ def get_global_var(var_name):
31
31
 
32
32
  logger = logging.getLogger(__name__)
33
33
 
34
- def parse_time_expression(time_text: str) -> int:
35
- """
36
- Парсит временные выражения и возвращает количество секунд
37
-
38
- Поддерживаемые форматы:
39
- - "30" -> 30 секунд (основной формат)
40
- - "Запуск через 30 секунд" -> 30 секунд
41
- - "через 5 минут" -> 300 секунд
42
- - "через 1 час" -> 3600 секунд
43
- - "через 2 часа" -> 7200 секунд
44
- """
45
- # Сначала пробуем простое число (основной формат)
46
- try:
47
- return int(time_text)
48
- except ValueError:
49
- pass
50
-
51
- # Убираем лишние символы и приводим к нижнему регистру
52
- text = re.sub(r'[^\w\s\d]', '', time_text.lower())
53
-
54
- # Ищем числа в тексте
55
- numbers = re.findall(r'\d+', text)
56
- if not numbers:
57
- raise ValueError(f"Не найдено число в выражении: {time_text}")
58
-
59
- number = int(numbers[0])
60
-
61
- # Определяем единицу времени
62
- if 'секунд' in text:
63
- return number
64
- elif 'минут' in text:
65
- return number * 60
66
- elif 'час' in text:
67
- return number * 3600
68
- elif 'день' in text or 'дней' in text:
69
- return number * 86400
70
- else:
71
- # По умолчанию считаем секундами
72
- return number
73
34
 
74
35
  # Создаем роутер для общих команд
75
36
  utils_router = Router()
@@ -244,10 +205,12 @@ async def process_events(session_id: str, events: list, user_id: int):
244
205
  event_handlers = router_manager.get_event_handlers()
245
206
  scheduled_tasks = router_manager.get_scheduled_tasks()
246
207
  global_handlers = router_manager.get_global_handlers()
208
+ logger.debug(f"🔍 RouterManager найден: {len(global_handlers)} глобальных обработчиков")
247
209
  else:
248
210
  event_handlers = _event_handlers
249
211
  scheduled_tasks = _scheduled_tasks
250
212
  global_handlers = _global_handlers
213
+ logger.warning("⚠️ RouterManager не найден, используем старые декораторы")
251
214
 
252
215
  # Сначала пробуем как обычное событие
253
216
  if event_type in event_handlers:
@@ -266,49 +229,59 @@ async def process_events(session_id: str, events: list, user_id: int):
266
229
  # Если не user_event, пробуем как запланированную задачу
267
230
  elif event_type in scheduled_tasks:
268
231
  try:
269
- delay_seconds = parse_time_expression(event_info)
232
+ # Извлекаем время из event_info (AI должен передавать число секунд)
233
+ try:
234
+ delay_seconds = int(event_info)
235
+ except ValueError:
236
+ # Если не число, используем значение по умолчанию
237
+ delay_seconds = 3600
238
+ logger.warning(f" ⚠️ Не удалось извлечь время из '{event_info}', используем 3600с")
239
+
270
240
  logger.info(f" ⏰ Сохраняем как scheduled_task: '{event_type}' через {delay_seconds}с")
271
241
  result = await schedule_task_for_later_with_db(event_type, user_id, event_info, delay_seconds, session_id)
272
242
  event_id = result['event_id']
273
243
  should_notify = result.get('notify', False)
274
244
  logger.info(f" 💾 Задача сохранена в БД: {event_id}")
275
245
 
276
- except ValueError as e:
246
+ except Exception as e:
277
247
  if "once_only=True" in str(e):
278
248
  logger.info(f" 🔄 Задача '{event_type}' уже запланирована, пропускаем")
279
249
  continue
280
250
  else:
281
- logger.error(f" ❌ Ошибка парсинга времени для scheduled_task '{event_type}': {e}")
282
- # Fallback - планируем через 1 час
283
- result = await schedule_task_for_later_with_db(event_type, user_id, event_info, 3600, session_id)
284
- event_id = result['event_id']
285
- should_notify = result.get('notify', False)
286
- logger.info(f" ⏰ Задача сохранена с fallback временем (1 час): {event_id}")
251
+ logger.error(f" ❌ Ошибка планирования scheduled_task '{event_type}': {e}")
252
+ continue
287
253
 
288
254
  # Если не scheduled_task, пробуем как глобальный обработчик
289
255
  elif event_type in global_handlers:
290
256
  try:
291
- delay_seconds = parse_time_expression(event_info)
257
+ # Извлекаем время из event_info (AI должен передавать число секунд)
258
+ try:
259
+ delay_seconds = int(event_info)
260
+ except ValueError:
261
+ # Если не число, используем значение по умолчанию
262
+ delay_seconds = 3600
263
+ logger.warning(f" ⚠️ Не удалось извлечь время из '{event_info}', используем 3600с")
264
+
292
265
  logger.info(f" 🌍 Сохраняем как global_handler: '{event_type}' через {delay_seconds}с")
293
266
  result = await schedule_global_handler_for_later_with_db(event_type, delay_seconds, event_info)
294
267
  event_id = result['event_id']
295
268
  should_notify = result.get('notify', False)
296
269
  logger.info(f" 💾 Глобальное событие сохранено в БД: {event_id}")
297
270
 
298
- except ValueError as e:
271
+ except Exception as e:
299
272
  if "once_only=True" in str(e):
300
273
  logger.info(f" 🔄 Глобальное событие '{event_type}' уже запланировано, пропускаем")
301
274
  continue
302
275
  else:
303
- logger.error(f" ❌ Ошибка парсинга времени для global_handler '{event_type}': {e}")
304
- # Fallback - планируем через 1 час
305
- result = await schedule_global_handler_for_later_with_db(event_type, 3600, event_info)
306
- event_id = result['event_id']
307
- should_notify = result.get('notify', False)
308
- logger.info(f" 🌍 Глобальное событие сохранено с fallback временем (1 час): {event_id}")
276
+ logger.error(f" ❌ Ошибка планирования global_handler '{event_type}': {e}")
277
+ continue
309
278
 
310
279
  else:
311
280
  logger.warning(f" ⚠️ Обработчик '{event_type}' не найден среди зарегистрированных")
281
+ logger.debug(f" 🔍 Доступные обработчики:")
282
+ logger.debug(f" - event_handlers: {list(event_handlers.keys())}")
283
+ logger.debug(f" - scheduled_tasks: {list(scheduled_tasks.keys())}")
284
+ logger.debug(f" - global_handlers: {list(global_handlers.keys())}")
312
285
 
313
286
  # Выполняем немедленные события
314
287
  if should_execute_immediately and event_id:
@@ -292,18 +292,16 @@ async def execute_scheduled_task(task_name: str, user_id: int, user_data: str) -
292
292
 
293
293
  async def execute_global_handler(handler_type: str, *args, **kwargs) -> Any:
294
294
  """Выполняет глобальный обработчик по типу"""
295
- # Сначала пробуем получить из роутеров
296
- if _router_manager:
297
- global_handlers = _router_manager.get_global_handlers()
298
- if handler_type in global_handlers:
299
- handler_info = global_handlers[handler_type]
300
- return await handler_info['handler'](*args, **kwargs)
295
+ router_manager = get_router_manager()
296
+ if router_manager:
297
+ global_handlers = router_manager.get_global_handlers()
298
+ else:
299
+ global_handlers = _global_handlers
301
300
 
302
- # Fallback к старым декораторам
303
- if handler_type not in _global_handlers:
301
+ if handler_type not in global_handlers:
304
302
  raise ValueError(f"Глобальный обработчик '{handler_type}' не найден")
305
303
 
306
- handler_info = _global_handlers[handler_type]
304
+ handler_info = global_handlers[handler_type]
307
305
  return await handler_info['handler'](*args, **kwargs)
308
306
 
309
307
  async def schedule_task_for_later(task_name: str, delay_seconds: int, user_id: int, user_data: str):
@@ -461,12 +459,7 @@ async def save_immediate_event(
461
459
  raise RuntimeError("Supabase клиент не инициализирован")
462
460
 
463
461
  # Проверяем, нужно ли предотвращать дублирование
464
- # Получаем информацию об обработчике через роутер-менеджер или fallback
465
- if _router_manager:
466
- event_handlers = _router_manager.get_event_handlers()
467
- event_handler_info = event_handlers.get(event_type, {})
468
- else:
469
- event_handler_info = _event_handlers.get(event_type, {})
462
+ event_handler_info = _event_handlers.get(event_type, {})
470
463
  once_only = event_handler_info.get('once_only', True)
471
464
 
472
465
  if once_only:
@@ -510,12 +503,7 @@ async def save_scheduled_task(
510
503
  raise RuntimeError("Supabase клиент не инициализирован")
511
504
 
512
505
  # Проверяем, нужно ли предотвращать дублирование
513
- # Получаем информацию о задаче через роутер-менеджер или fallback
514
- if _router_manager:
515
- scheduled_tasks = _router_manager.get_scheduled_tasks()
516
- task_info = scheduled_tasks.get(task_name, {})
517
- else:
518
- task_info = _scheduled_tasks.get(task_name, {})
506
+ task_info = _scheduled_tasks.get(task_name, {})
519
507
  once_only = task_info.get('once_only', True)
520
508
 
521
509
  if once_only:
@@ -559,12 +547,13 @@ async def save_global_event(
559
547
  raise RuntimeError("Supabase клиент не инициализирован")
560
548
 
561
549
  # Проверяем, нужно ли предотвращать дублирование
562
- # Получаем информацию о глобальном обработчике через роутер-менеджер или fallback
563
- if _router_manager:
564
- global_handlers = _router_manager.get_global_handlers()
565
- handler_info = global_handlers.get(handler_type, {})
550
+ router_manager = get_router_manager()
551
+ if router_manager:
552
+ global_handlers = router_manager.get_global_handlers()
566
553
  else:
567
- handler_info = _global_handlers.get(handler_type, {})
554
+ global_handlers = _global_handlers
555
+
556
+ handler_info = global_handlers.get(handler_type, {})
568
557
  once_only = handler_info.get('once_only', True)
569
558
 
570
559
  if once_only:
@@ -714,14 +703,8 @@ async def process_scheduled_event(event: Dict):
714
703
  async def schedule_task_for_later_with_db(task_name: str, user_id: int, user_data: str, delay_seconds: int, session_id: str = None):
715
704
  """Планирует выполнение задачи через указанное время с сохранением в БД"""
716
705
 
717
- # Проверяем через роутер-менеджер или fallback к старым декораторам
718
- if _router_manager:
719
- scheduled_tasks = _router_manager.get_scheduled_tasks()
720
- if task_name not in scheduled_tasks:
721
- raise ValueError(f"Задача '{task_name}' не найдена")
722
- else:
723
- if task_name not in _scheduled_tasks:
724
- raise ValueError(f"Задача '{task_name}' не найдена")
706
+ if task_name not in _scheduled_tasks:
707
+ raise ValueError(f"Задача '{task_name}' не найдена")
725
708
 
726
709
  logger.info(f"⏰ Планируем задачу '{task_name}' через {delay_seconds} секунд")
727
710
 
@@ -782,14 +765,15 @@ async def schedule_task_for_later_with_db(task_name: str, user_id: int, user_dat
782
765
  async def schedule_global_handler_for_later_with_db(handler_type: str, delay_seconds: int, handler_data: str):
783
766
  """Планирует выполнение глобального обработчика через указанное время с сохранением в БД"""
784
767
 
785
- # Проверяем через роутер-менеджер или fallback к старым декораторам
786
- if _router_manager:
787
- global_handlers = _router_manager.get_global_handlers()
788
- if handler_type not in global_handlers:
789
- raise ValueError(f"Глобальный обработчик '{handler_type}' не найден")
768
+ # Проверяем обработчик через RouterManager или fallback к старым декораторам
769
+ router_manager = get_router_manager()
770
+ if router_manager:
771
+ global_handlers = router_manager.get_global_handlers()
790
772
  else:
791
- if handler_type not in _global_handlers:
792
- raise ValueError(f"Глобальный обработчик '{handler_type}' не найден")
773
+ global_handlers = _global_handlers
774
+
775
+ if handler_type not in global_handlers:
776
+ raise ValueError(f"Глобальный обработчик '{handler_type}' не найден")
793
777
 
794
778
  logger.info(f"🌍 Планируем глобальный обработчик '{handler_type}' через {delay_seconds} секунд")
795
779
 
@@ -29,11 +29,6 @@ async def send_message_by_ai(
29
29
  try:
30
30
  # Импортируем необходимые компоненты
31
31
  from .bot_utils import parse_ai_response, process_events
32
- from ..config import Config
33
- from ..integrations.openai_client import OpenAIClient
34
- from ..integrations.supabase_client import SupabaseClient
35
- from ..utils.prompt_loader import PromptLoader
36
- from aiogram import Bot
37
32
 
38
33
  # Получаем компоненты из глобального контекста
39
34
  from ..handlers.handlers import get_global_var
@@ -2,7 +2,7 @@
2
2
  Роутер для Smart Bot Factory - аналог aiogram Router
3
3
  """
4
4
 
5
- from typing import Dict, List, Any, Callable, Optional
5
+ from typing import Dict, Any, Callable
6
6
  import logging
7
7
 
8
8
  logger = logging.getLogger(__name__)
@@ -170,3 +170,4 @@ class Router:
170
170
 
171
171
  def __repr__(self):
172
172
  return f"Router(name='{self.name}', events={len(self._event_handlers)}, tasks={len(self._scheduled_tasks)}, globals={len(self._global_handlers)})"
173
+
@@ -163,3 +163,4 @@ class RouterManager:
163
163
 
164
164
  def __repr__(self):
165
165
  return f"RouterManager(routers={len(self._routers)}, handlers={len(self.get_all_handlers())})"
166
+
@@ -3,11 +3,9 @@
3
3
  """
4
4
 
5
5
  import os
6
- import sys
7
- import asyncio
8
6
  import logging
9
7
  from pathlib import Path
10
- from typing import Optional, Dict, Any, List
8
+ from typing import Optional, Dict, Any
11
9
 
12
10
  from ..config import Config
13
11
  from ..integrations.openai_client import OpenAIClient
@@ -236,8 +234,13 @@ class BotBuilder:
236
234
  # Получаем модуль бота
237
235
  bot_module = sys.modules.get(module_name)
238
236
  if not bot_module:
239
- logger.warning(f"⚠️ Модуль '{module_name}' не найден для установки глобальных переменных")
240
- return
237
+ # Пытаемся импортировать модуль, если он не загружен
238
+ try:
239
+ bot_module = importlib.import_module(module_name)
240
+ logger.info(f"📦 Модуль '{module_name}' импортирован для установки глобальных переменных")
241
+ except ImportError as ie:
242
+ logger.warning(f"⚠️ Не удалось импортировать модуль '{module_name}': {ie}")
243
+ return
241
244
 
242
245
  # Устанавливаем глобальные переменные
243
246
  bot_module.supabase_client = self.supabase_client
@@ -302,8 +305,7 @@ class BotBuilder:
302
305
  prompts_status = await self.prompt_loader.validate_prompts()
303
306
  logger.info(f"Статус промптов: {prompts_status}")
304
307
 
305
- # Устанавливаем глобальные переменные ДО импорта обработчиков
306
- import sys
308
+
307
309
  import importlib
308
310
 
309
311
  # Устанавливаем глобальные переменные в модулях handlers и admin_logic
@@ -381,26 +383,6 @@ class BotBuilder:
381
383
  # Устанавливаем глобальные переменные в модуле бота для удобного доступа
382
384
  self.set_global_vars_in_module(self.bot_id)
383
385
 
384
- # Устанавливаем глобальные переменные в core.globals для внутреннего использования
385
- from ..core.globals import set_globals
386
- set_globals(
387
- supabase_client=self.supabase_client,
388
- openai_client=self.openai_client,
389
- config=self.config,
390
- admin_manager=self.admin_manager,
391
- analytics_manager=self.analytics_manager,
392
- conversation_manager=self.conversation_manager,
393
- prompt_loader=self.prompt_loader,
394
- bot=bot,
395
- dp=dp
396
- )
397
-
398
- # Устанавливаем клиенты в clients модуль для пользователей
399
- from ..clients import set_clients
400
- set_clients(
401
- supabase=self.supabase_client,
402
- openai=self.openai_client
403
- )
404
386
 
405
387
  # Устанавливаем роутер-менеджер в декораторы
406
388
  if self.router_manager:
@@ -0,0 +1,12 @@
1
+ """
2
+ Event модули smart_bot_factory
3
+ """
4
+
5
+
6
+ from ..core.decorators import event_handler, schedule_task, global_handler
7
+
8
+ __all__ = [
9
+ 'event_handler',
10
+ 'schedule_task',
11
+ 'global_handler'
12
+ ]
@@ -0,0 +1,12 @@
1
+ """
2
+ Message модули smart_bot_factory
3
+ """
4
+
5
+
6
+ from ..core.message_sender import send_message_by_human, send_message_by_ai
7
+
8
+ __all__ = [
9
+ 'send_message_by_human',
10
+ 'send_message_by_ai',
11
+ 'send_message_to_users_by_stage'
12
+ ]
@@ -0,0 +1,9 @@
1
+ """
2
+ Router модули smart_bot_factory
3
+ """
4
+
5
+ from ..core.router import Router
6
+
7
+ __all__ = [
8
+ 'Router'
9
+ ]
@@ -0,0 +1,7 @@
1
+ """
2
+ Supabase клиент с автоматической загрузкой настроек из .env
3
+ """
4
+
5
+ from .client import SupabaseClient
6
+
7
+ __all__ = ['SupabaseClient']