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

Files changed (45) hide show
  1. smart_bot_factory/admin/__init__.py +7 -7
  2. smart_bot_factory/admin/admin_events.py +483 -383
  3. smart_bot_factory/admin/admin_logic.py +234 -158
  4. smart_bot_factory/admin/admin_manager.py +68 -53
  5. smart_bot_factory/admin/admin_tester.py +46 -40
  6. smart_bot_factory/admin/timeout_checker.py +201 -153
  7. smart_bot_factory/aiogram_calendar/__init__.py +11 -3
  8. smart_bot_factory/aiogram_calendar/common.py +12 -18
  9. smart_bot_factory/aiogram_calendar/dialog_calendar.py +126 -64
  10. smart_bot_factory/aiogram_calendar/schemas.py +49 -28
  11. smart_bot_factory/aiogram_calendar/simple_calendar.py +94 -50
  12. smart_bot_factory/analytics/analytics_manager.py +414 -392
  13. smart_bot_factory/cli.py +204 -148
  14. smart_bot_factory/config.py +123 -102
  15. smart_bot_factory/core/bot_utils.py +480 -324
  16. smart_bot_factory/core/conversation_manager.py +287 -200
  17. smart_bot_factory/core/decorators.py +1145 -739
  18. smart_bot_factory/core/message_sender.py +287 -266
  19. smart_bot_factory/core/router.py +170 -100
  20. smart_bot_factory/core/router_manager.py +121 -83
  21. smart_bot_factory/core/states.py +4 -3
  22. smart_bot_factory/creation/__init__.py +1 -1
  23. smart_bot_factory/creation/bot_builder.py +320 -242
  24. smart_bot_factory/creation/bot_testing.py +440 -365
  25. smart_bot_factory/dashboard/__init__.py +1 -3
  26. smart_bot_factory/event/__init__.py +2 -7
  27. smart_bot_factory/handlers/handlers.py +682 -466
  28. smart_bot_factory/integrations/openai_client.py +218 -168
  29. smart_bot_factory/integrations/supabase_client.py +928 -637
  30. smart_bot_factory/message/__init__.py +18 -22
  31. smart_bot_factory/router/__init__.py +2 -2
  32. smart_bot_factory/setup_checker.py +162 -126
  33. smart_bot_factory/supabase/__init__.py +1 -1
  34. smart_bot_factory/supabase/client.py +631 -515
  35. smart_bot_factory/utils/__init__.py +2 -3
  36. smart_bot_factory/utils/debug_routing.py +38 -27
  37. smart_bot_factory/utils/prompt_loader.py +153 -120
  38. smart_bot_factory/utils/user_prompt_loader.py +55 -56
  39. smart_bot_factory/utm_link_generator.py +123 -116
  40. {smart_bot_factory-0.3.6.dist-info → smart_bot_factory-0.3.8.dist-info}/METADATA +3 -1
  41. smart_bot_factory-0.3.8.dist-info/RECORD +59 -0
  42. smart_bot_factory-0.3.6.dist-info/RECORD +0 -59
  43. {smart_bot_factory-0.3.6.dist-info → smart_bot_factory-0.3.8.dist-info}/WHEEL +0 -0
  44. {smart_bot_factory-0.3.6.dist-info → smart_bot_factory-0.3.8.dist-info}/entry_points.txt +0 -0
  45. {smart_bot_factory-0.3.6.dist-info → smart_bot_factory-0.3.8.dist-info}/licenses/LICENSE +0 -0
smart_bot_factory/cli.py CHANGED
@@ -3,21 +3,23 @@ CLI интерфейс для Smart Bot Factory
3
3
  """
4
4
 
5
5
  import os
6
- import sys
7
- import click
8
- import subprocess
9
6
  import shutil
7
+ import subprocess
8
+ import sys
10
9
  from pathlib import Path
11
10
 
11
+ import click
12
12
  from project_root_finder import root
13
13
 
14
14
  PROJECT_ROOT = root
15
15
 
16
+
16
17
  @click.group()
17
18
  def cli():
18
19
  """Smart Bot Factory - инструмент для создания умных чат-ботов"""
19
20
  pass
20
21
 
22
+
21
23
  @cli.command()
22
24
  @click.argument("bot_id")
23
25
  @click.argument("template", required=False, default="base")
@@ -27,6 +29,7 @@ def create(bot_id: str, template: str = "base"):
27
29
  if not success:
28
30
  sys.exit(1)
29
31
 
32
+
30
33
  @cli.command()
31
34
  def list():
32
35
  """Показать список доступных ботов"""
@@ -34,11 +37,12 @@ def list():
34
37
  if not bots:
35
38
  click.echo("🤖 Нет доступных ботов")
36
39
  return
37
-
40
+
38
41
  click.echo("🤖 Доступные боты:")
39
42
  for bot in sorted(bots):
40
43
  click.echo(f" 📱 {bot}")
41
44
 
45
+
42
46
  @cli.command()
43
47
  @click.argument("bot_id")
44
48
  def run(bot_id: str):
@@ -48,40 +52,44 @@ def run(bot_id: str):
48
52
  bot_path = PROJECT_ROOT / Path("bots") / bot_id
49
53
  if not bot_path.exists():
50
54
  raise click.ClickException(f"Бот {bot_id} не найден в папке bots/")
51
-
55
+
52
56
  # Проверяем наличие основного файла бота в корневой директории
53
57
  bot_file = PROJECT_ROOT / Path(f"{bot_id}.py")
54
58
  if not bot_file.exists():
55
- raise click.ClickException(f"Файл {bot_id}.py не найден в корневой директории")
56
-
59
+ raise click.ClickException(
60
+ f"Файл {bot_id}.py не найден в корневой директории"
61
+ )
62
+
57
63
  # Проверяем наличие .env файла
58
64
  env_file = bot_path / ".env"
59
65
  if not env_file.exists():
60
66
  raise click.ClickException(f"Файл .env не найден для бота {bot_id}")
61
-
67
+
62
68
  # Настраиваем окружение для бота
63
69
  from dotenv import load_dotenv
64
-
70
+
65
71
  # Добавляем корень проекта в sys.path
66
72
  sys.path.insert(0, str(PROJECT_ROOT))
67
-
73
+
68
74
  # Загружаем .env файл
69
75
  load_dotenv(env_file)
70
76
  click.echo(f"📄 Загружен .env файл: {env_file}")
71
-
77
+
72
78
  # Устанавливаем переменные окружения
73
79
  os.environ["BOT_ID"] = bot_id
74
-
80
+
75
81
  # Устанавливаем путь к промптам
76
82
  prompts_dir = bot_path / "prompts"
77
83
  if prompts_dir.exists():
78
84
  os.environ["PROMT_FILES_DIR"] = str(prompts_dir)
79
85
  click.echo(f"📝 Установлен путь к промптам: {prompts_dir}")
80
-
86
+
81
87
  # Запускаем бота из корневой директории
82
88
  click.echo(f"🚀 Запускаем бота {bot_id}...")
83
- subprocess.run([sys.executable, str(bot_file)], check=True, cwd=str(PROJECT_ROOT))
84
-
89
+ subprocess.run(
90
+ [sys.executable, str(bot_file)], check=True, cwd=str(PROJECT_ROOT)
91
+ )
92
+
85
93
  except subprocess.CalledProcessError as e:
86
94
  click.echo(f"❌ Ошибка при запуске бота: {e}", err=True)
87
95
  sys.exit(1)
@@ -89,6 +97,7 @@ def run(bot_id: str):
89
97
  click.echo(f"❌ Ошибка: {e}", err=True)
90
98
  sys.exit(1)
91
99
 
100
+
92
101
  @cli.command()
93
102
  @click.argument("bot_id")
94
103
  @click.option("--file", help="Запустить тесты только из указанного файла")
@@ -96,49 +105,51 @@ def run(bot_id: str):
96
105
  @click.option("--max-concurrent", default=5, help="Максимальное количество потоков")
97
106
  def test(bot_id: str, file: str = None, verbose: bool = False, max_concurrent: int = 5):
98
107
  """Запустить тесты бота"""
99
- try:
108
+ try:
100
109
  # Проверяем существование бота
101
110
  bot_path = PROJECT_ROOT / "bots" / bot_id
102
111
  if not bot_path.exists():
103
- raise click.ClickException(f"Бот {bot_id} не найден в папке {PROJECT_ROOT}/bots/")
104
-
112
+ raise click.ClickException(
113
+ f"Бот {bot_id} не найден в папке {PROJECT_ROOT}/bots/"
114
+ )
115
+
105
116
  # Проверяем наличие тестов
106
117
  tests_dir = bot_path / "tests"
107
118
  if not tests_dir.exists():
108
119
  click.echo(f"⚠️ Тесты не найдены для бота {bot_id}")
109
120
  return
110
-
121
+
111
122
  # Ищем YAML файлы с тестами
112
123
  yaml_files = [str(f.name) for f in tests_dir.glob("*.yaml")]
113
-
124
+
114
125
  if not yaml_files:
115
126
  click.echo(f"⚠️ YAML тесты не найдены для бота {bot_id}")
116
127
  return
117
-
128
+
118
129
  click.echo(f"🧪 Запускаем тесты для бота {bot_id}...")
119
-
130
+
120
131
  # Формируем команду для запуска
121
132
  bot_testing_path = Path(__file__).parent / "creation" / "bot_testing.py"
122
133
  cmd = [sys.executable, str(bot_testing_path), bot_id]
123
-
134
+
124
135
  if file:
125
136
  cmd.append(file)
126
-
137
+
127
138
  if verbose:
128
139
  cmd.append("-v")
129
-
140
+
130
141
  if max_concurrent != 5:
131
142
  cmd.extend(["--max-concurrent", str(max_concurrent)])
132
-
143
+
133
144
  # Запускаем тесты
134
145
  result = subprocess.run(cmd, check=False)
135
-
146
+
136
147
  if result.returncode == 0:
137
148
  click.echo("✅ Все тесты пройдены")
138
149
  else:
139
150
  click.echo("❌ Есть ошибки в тестах")
140
151
  sys.exit(1)
141
-
152
+
142
153
  except subprocess.CalledProcessError as e:
143
154
  click.echo(f"❌ Ошибка при запуске тестов: {e}", err=True)
144
155
  sys.exit(1)
@@ -146,6 +157,7 @@ def test(bot_id: str, file: str = None, verbose: bool = False, max_concurrent: i
146
157
  click.echo(f"❌ Ошибка: {e}", err=True)
147
158
  sys.exit(1)
148
159
 
160
+
149
161
  @cli.command()
150
162
  @click.argument("bot_id")
151
163
  def config(bot_id: str):
@@ -155,18 +167,18 @@ def config(bot_id: str):
155
167
  bot_path = PROJECT_ROOT / Path("bots") / bot_id
156
168
  if not bot_path.exists():
157
169
  raise click.ClickException(f"Бот {bot_id} не найден в папке bots/")
158
-
170
+
159
171
  # Открываем .env файл в редакторе
160
172
  env_file = bot_path / ".env"
161
173
  if not env_file.exists():
162
174
  raise click.ClickException(f"Файл .env не найден для бота {bot_id}")
163
-
175
+
164
176
  # Определяем редактор
165
- editor = os.environ.get('EDITOR', 'notepad' if os.name == 'nt' else 'nano')
166
-
177
+ editor = os.environ.get("EDITOR", "notepad" if os.name == "nt" else "nano")
178
+
167
179
  click.echo(f"⚙️ Открываем конфигурацию бота {bot_id}...")
168
180
  subprocess.run([editor, str(env_file)], check=True)
169
-
181
+
170
182
  except subprocess.CalledProcessError as e:
171
183
  click.echo(f"❌ Ошибка при открытии редактора: {e}", err=True)
172
184
  sys.exit(1)
@@ -174,83 +186,93 @@ def config(bot_id: str):
174
186
  click.echo(f"❌ Ошибка: {e}", err=True)
175
187
  sys.exit(1)
176
188
 
189
+
177
190
  @cli.command()
178
191
  @click.argument("bot_id")
179
192
  @click.option("--list", "list_prompts", is_flag=True, help="Показать список промптов")
180
193
  @click.option("--edit", "edit_prompt", help="Редактировать промпт")
181
194
  @click.option("--add", "add_prompt", help="Добавить новый промпт")
182
- def prompts(bot_id: str, list_prompts: bool = False, edit_prompt: str = None, add_prompt: str = None):
195
+ def prompts(
196
+ bot_id: str,
197
+ list_prompts: bool = False,
198
+ edit_prompt: str = None,
199
+ add_prompt: str = None,
200
+ ):
183
201
  """Управление промптами бота"""
184
202
  try:
185
203
  # Проверяем существование бота
186
204
  bot_path = PROJECT_ROOT / Path("bots") / bot_id
187
205
  if not bot_path.exists():
188
206
  raise click.ClickException(f"Бот {bot_id} не найден в папке bots/")
189
-
207
+
190
208
  prompts_dir = bot_path / "prompts"
191
209
  if not prompts_dir.exists():
192
210
  raise click.ClickException(f"Папка промптов не найдена для бота {bot_id}")
193
-
211
+
194
212
  if list_prompts or (not edit_prompt and not add_prompt):
195
213
  # Показываем список промптов (по умолчанию или с флагом --list)
196
214
  prompt_files = [f.name for f in prompts_dir.glob("*.txt")]
197
-
215
+
198
216
  if not prompt_files:
199
217
  click.echo("📝 Промпты не найдены")
200
218
  return
201
-
219
+
202
220
  click.echo(f"📝 Промпты бота {bot_id}:")
203
221
  for prompt_file in sorted(prompt_files):
204
222
  click.echo(f" 📄 {prompt_file[:-4]}")
205
-
223
+
206
224
  # Показываем справку только если не указан флаг --list
207
225
  if not list_prompts:
208
226
  click.echo()
209
227
  click.echo("Использование:")
210
- click.echo(" sbf prompts <bot_id> --edit <prompt_name> # Редактировать промпт")
211
- click.echo(" sbf prompts <bot_id> --add <prompt_name> # Добавить новый промпт")
212
-
228
+ click.echo(
229
+ " sbf prompts <bot_id> --edit <prompt_name> # Редактировать промпт"
230
+ )
231
+ click.echo(
232
+ " sbf prompts <bot_id> --add <prompt_name> # Добавить новый промпт"
233
+ )
234
+
213
235
  elif edit_prompt:
214
236
  # Редактируем промпт
215
237
  prompt_file = prompts_dir / f"{edit_prompt}.txt"
216
238
  if not prompt_file.exists():
217
239
  raise click.ClickException(f"Промпт {edit_prompt} не найден")
218
-
219
- editor = os.environ.get('EDITOR', 'notepad' if os.name == 'nt' else 'nano')
240
+
241
+ editor = os.environ.get("EDITOR", "notepad" if os.name == "nt" else "nano")
220
242
  click.echo(f"✏️ Редактируем промпт {edit_prompt}...")
221
243
  subprocess.run([editor, str(prompt_file)], check=True)
222
-
244
+
223
245
  elif add_prompt:
224
246
  # Добавляем новый промпт
225
247
  prompt_file = prompts_dir / f"{add_prompt}.txt"
226
248
  if prompt_file.exists():
227
249
  raise click.ClickException(f"Промпт {add_prompt} уже существует")
228
-
250
+
229
251
  # Создаем файл с базовым содержимым
230
252
  prompt_file.write_text(
231
- f"# Промпт: {add_prompt}\n\n"
232
- "Введите содержимое промпта здесь...",
233
- encoding='utf-8'
253
+ f"# Промпт: {add_prompt}\n\n" "Введите содержимое промпта здесь...",
254
+ encoding="utf-8",
234
255
  )
235
-
256
+
236
257
  # Открываем в редакторе
237
- editor = os.environ.get('EDITOR', 'notepad' if os.name == 'nt' else 'nano')
258
+ editor = os.environ.get("EDITOR", "notepad" if os.name == "nt" else "nano")
238
259
  click.echo(f"📝 Создаем новый промпт {add_prompt}...")
239
260
  subprocess.run([editor, str(prompt_file)], check=True)
240
-
241
-
261
+
242
262
  except subprocess.CalledProcessError as e:
243
263
  click.echo(f"❌ Ошибка при открытии редактора: {e}", err=True)
244
264
  sys.exit(1)
245
265
  except Exception as e:
246
266
  click.echo(f"❌ Ошибка: {e}", err=True)
247
267
  sys.exit(1)
248
-
268
+
269
+
249
270
  @cli.command()
250
271
  def path():
251
272
  """Показать путь к проекту"""
252
273
  click.echo(PROJECT_ROOT)
253
274
 
275
+
254
276
  @cli.command()
255
277
  @click.argument("bot_id")
256
278
  @click.option("--force", "-f", is_flag=True, help="Удалить без подтверждения")
@@ -261,62 +283,74 @@ def rm(bot_id: str, force: bool = False):
261
283
  bot_path = PROJECT_ROOT / Path("bots") / bot_id
262
284
  if not bot_path.exists():
263
285
  raise click.ClickException(f"🤖 Бот {bot_id} не найден в папке bots/")
264
-
286
+
265
287
  # Проверяем наличие основного файла бота в корневой директории
266
288
  bot_file = Path(f"{bot_id}.py")
267
289
  if not bot_file.exists():
268
- raise click.ClickException(f"📄 Файл {bot_id}.py не найден в корневой директории")
269
-
290
+ raise click.ClickException(
291
+ f"📄 Файл {bot_id}.py не найден в корневой директории"
292
+ )
293
+
270
294
  # Показываем что будет удалено
271
295
  click.echo("🗑️ Будет удалено:")
272
296
  click.echo(f" 📄 Файл запускалки: {bot_file}")
273
297
  click.echo(f" 📁 Папка бота: {bot_path}")
274
-
298
+
275
299
  # Запрашиваем подтверждение если не указан --force
276
300
  if not force:
277
301
  if not click.confirm(f"⚠️ Вы уверены, что хотите удалить бота {bot_id}?"):
278
302
  click.echo("❌ Удаление отменено")
279
303
  return
280
-
304
+
281
305
  # Удаляем файл запускалки
282
306
  if bot_file.exists():
283
307
  bot_file.unlink()
284
308
  click.echo(f"✅ Файл {bot_file} удален")
285
-
309
+
286
310
  # Удаляем папку бота
287
311
  if bot_path.exists():
288
312
  import shutil
313
+
289
314
  shutil.rmtree(bot_path)
290
315
  click.echo(f"✅ Папка {bot_path} удалена")
291
-
316
+
292
317
  click.echo(f"🎉 Бот {bot_id} полностью удален")
293
-
318
+
294
319
  except Exception as e:
295
320
  click.echo(f"❌ Ошибка при удалении бота: {e}", err=True)
296
321
  sys.exit(1)
297
-
322
+
298
323
 
299
324
  @cli.command()
300
325
  @click.argument("source_bot_id")
301
326
  @click.argument("new_bot_id")
302
- @click.option("--force", "-f", is_flag=True, help="Перезаписать существующего бота без подтверждения")
327
+ @click.option(
328
+ "--force",
329
+ "-f",
330
+ is_flag=True,
331
+ help="Перезаписать существующего бота без подтверждения",
332
+ )
303
333
  def copy(source_bot_id: str, new_bot_id: str, force: bool = False):
304
334
  """Скопировать существующего бота как шаблон"""
305
335
  try:
306
336
  # Проверяем существование исходного бота
307
337
  source_bot_path = PROJECT_ROOT / "bots" / source_bot_id
308
338
  if not source_bot_path.exists():
309
- raise click.ClickException(f"Исходный бот {source_bot_id} не найден в папке bots/")
310
-
339
+ raise click.ClickException(
340
+ f"Исходный бот {source_bot_id} не найден в папке bots/"
341
+ )
342
+
311
343
  # Проверяем наличие файла запускалки исходного бота
312
344
  source_bot_file = PROJECT_ROOT / f"{source_bot_id}.py"
313
345
  if not source_bot_file.exists():
314
- raise click.ClickException(f"Файл запускалки {source_bot_id}.py не найден в корневой директории")
315
-
346
+ raise click.ClickException(
347
+ f"Файл запускалки {source_bot_id}.py не найден в корневой директории"
348
+ )
349
+
316
350
  # Проверяем, не существует ли уже новый бот
317
351
  new_bot_path = PROJECT_ROOT / "bots" / new_bot_id
318
352
  new_bot_file = PROJECT_ROOT / f"{new_bot_id}.py"
319
-
353
+
320
354
  if new_bot_path.exists() or new_bot_file.exists():
321
355
  if not force:
322
356
  if not click.confirm(f"Бот {new_bot_id} уже существует. Перезаписать?"):
@@ -324,18 +358,19 @@ def copy(source_bot_id: str, new_bot_id: str, force: bool = False):
324
358
  return
325
359
  else:
326
360
  click.echo(f"⚠️ Перезаписываем существующего бота {new_bot_id}")
327
-
361
+
328
362
  # Копируем бота
329
363
  click.echo(f"📋 Копируем бота {source_bot_id} → {new_bot_id}...")
330
364
  copy_bot_template(source_bot_id, new_bot_id)
331
-
365
+
332
366
  click.echo(f"✅ Бот {new_bot_id} успешно скопирован из {source_bot_id}")
333
- click.echo(f"📝 Не забудьте настроить .env файл для нового бота")
334
-
367
+ click.echo("📝 Не забудьте настроить .env файл для нового бота")
368
+
335
369
  except Exception as e:
336
370
  click.echo(f"❌ Ошибка при копировании бота: {e}", err=True)
337
371
  sys.exit(1)
338
372
 
373
+
339
374
  @cli.command()
340
375
  def link():
341
376
  """Создать UTM-ссылку для бота"""
@@ -344,11 +379,11 @@ def link():
344
379
  link_script = Path(__file__).parent / "utm_link_generator.py"
345
380
  if not link_script.exists():
346
381
  raise click.ClickException("Скрипт utm_link_generator.py не найден")
347
-
382
+
348
383
  # Запускаем скрипт генерации ссылок
349
384
  click.echo("🔗 Запускаем генератор UTM-ссылок...")
350
385
  subprocess.run([sys.executable, str(link_script)], check=True)
351
-
386
+
352
387
  except subprocess.CalledProcessError as e:
353
388
  click.echo(f"❌ Ошибка при запуске генератора ссылок: {e}", err=True)
354
389
  sys.exit(1)
@@ -356,56 +391,59 @@ def link():
356
391
  click.echo(f"❌ Ошибка: {e}", err=True)
357
392
  sys.exit(1)
358
393
 
394
+
359
395
  def create_new_bot_structure(template: str, bot_id: str) -> bool:
360
396
  """Создает новую структуру бота в папке bots/"""
361
397
  try:
362
398
  # Создаем папку bots если её нет
363
399
  bots_dir = PROJECT_ROOT / Path("bots")
364
400
  bots_dir.mkdir(exist_ok=True)
365
-
401
+
366
402
  # Создаем папку для нового бота
367
403
  bot_dir = bots_dir / bot_id
368
404
  if bot_dir.exists():
369
405
  click.echo(f"⚠️ Бот {bot_id} уже существует")
370
406
  return False
371
-
407
+
372
408
  bot_dir.mkdir()
373
-
409
+
374
410
  # Создаем структуру папок
375
411
  (bot_dir / "prompts").mkdir()
376
412
  (bot_dir / "tests").mkdir()
377
413
  (bot_dir / "reports").mkdir()
378
414
  (bot_dir / "welcome_files").mkdir()
379
415
  (bot_dir / "files").mkdir()
380
-
416
+
381
417
  if template == "base":
382
418
  # Используем growthmed-october-24 как базовый шаблон
383
419
  copy_from_growthmed_template(bot_dir, bot_id)
384
420
  else:
385
421
  # Используем другой шаблон из папки bots
386
422
  copy_from_bot_template(template, bot_dir, bot_id)
387
-
423
+
388
424
  click.echo(f"✅ Бот {bot_id} создан в папке bots/{bot_id}/")
389
- click.echo(f"📝 Не забудьте настроить .env файл перед запуском")
425
+ click.echo("📝 Не забудьте настроить .env файл перед запуском")
390
426
  return True
391
-
427
+
392
428
  except Exception as e:
393
429
  click.echo(f"❌ Ошибка при создании бота: {e}")
394
430
  return False
395
431
 
432
+
396
433
  def list_bots_in_bots_folder() -> list:
397
434
  """Возвращает список ботов из папки bots/"""
398
435
  bots_dir = PROJECT_ROOT / Path("bots")
399
436
  if not bots_dir.exists():
400
437
  return []
401
-
438
+
402
439
  bots = []
403
440
  for item in bots_dir.iterdir():
404
441
  if item.is_dir() and Path(f"{item.name}.py").exists():
405
442
  bots.append(item.name)
406
-
443
+
407
444
  return bots
408
445
 
446
+
409
447
  def create_bot_template(bot_id: str) -> str:
410
448
  """Создает шаблон основного файла бота"""
411
449
  return f'''"""
@@ -466,9 +504,10 @@ if __name__ == "__main__":
466
504
  asyncio.run(main())
467
505
  '''
468
506
 
507
+
469
508
  def create_env_template(bot_id: str) -> str:
470
509
  """Создает шаблон .env файла"""
471
- return f'''# Telegram
510
+ return f"""# Telegram
472
511
  TELEGRAM_BOT_TOKEN=your_telegram_bot_token_here
473
512
 
474
513
  # Supabase
@@ -508,23 +547,26 @@ SESSION_TIMEOUT_HOURS=24
508
547
  # ⚠️ ВАЖНО: BOT_ID теперь НЕ нужен в .env!
509
548
  # Bot ID автоматически определяется из имени файла запускалки
510
549
  # Например: python {bot_id}.py → BOT_ID = {bot_id}
511
- '''
550
+ """
551
+
512
552
 
513
553
  def copy_from_growthmed_template(bot_dir: Path, bot_id: str):
514
554
  """Копирует шаблон из growthmed-october-24"""
515
555
  try:
516
556
  # Создаем основной файл бота в корневой директории проекта
517
557
  bot_file = PROJECT_ROOT / Path(f"{bot_id}.py")
518
- bot_file.write_text(create_bot_template(bot_id), encoding='utf-8')
519
-
558
+ bot_file.write_text(create_bot_template(bot_id), encoding="utf-8")
559
+
520
560
  # Создаем .env файл в папке бота (НЕ копируем из шаблона)
521
561
  env_file = bot_dir / ".env"
522
- env_file.write_text(create_env_template(bot_id), encoding='utf-8')
523
-
562
+ env_file.write_text(create_env_template(bot_id), encoding="utf-8")
563
+
524
564
  # Копируем промпты из growthmed-october-24
525
- source_prompts = Path(__file__).parent / "configs" / "growthmed-october-24" / "prompts"
565
+ source_prompts = (
566
+ Path(__file__).parent / "configs" / "growthmed-october-24" / "prompts"
567
+ )
526
568
  target_prompts = bot_dir / "prompts"
527
-
569
+
528
570
  if source_prompts.exists():
529
571
  for prompt_file in source_prompts.glob("*.txt"):
530
572
  shutil.copy2(prompt_file, target_prompts / prompt_file.name)
@@ -534,182 +576,195 @@ def copy_from_growthmed_template(bot_dir: Path, bot_id: str):
534
576
  # Fallback к базовым промптам
535
577
  create_basic_prompts(target_prompts)
536
578
  click.echo("📝 Созданы базовые промпты")
537
-
579
+
538
580
  # Копируем тесты из growthmed-october-24
539
- source_tests = Path(__file__).parent / "configs" / "growthmed-october-24" / "tests"
581
+ source_tests = (
582
+ Path(__file__).parent / "configs" / "growthmed-october-24" / "tests"
583
+ )
540
584
  target_tests = bot_dir / "tests"
541
-
585
+
542
586
  if source_tests.exists():
543
587
  for test_file in source_tests.glob("*"):
544
588
  if test_file.is_file():
545
589
  shutil.copy2(test_file, target_tests / test_file.name)
546
590
  click.echo("🧪 Тесты скопированы из growthmed-october-24")
547
-
591
+
548
592
  # Копируем welcome_files из growthmed-october-24
549
- source_welcome = Path(__file__).parent / "configs" / "growthmed-october-24" / "welcome_file"
593
+ source_welcome = (
594
+ Path(__file__).parent / "configs" / "growthmed-october-24" / "welcome_file"
595
+ )
550
596
  target_welcome = bot_dir / "welcome_files"
551
-
597
+
552
598
  if source_welcome.exists():
553
599
  for welcome_file in source_welcome.glob("*"):
554
600
  if welcome_file.is_file():
555
601
  shutil.copy2(welcome_file, target_welcome / welcome_file.name)
556
602
  click.echo("📁 Welcome файлы скопированы из growthmed-october-24")
557
-
603
+
558
604
  # Копируем files из growthmed-october-24
559
- source_files = Path(__file__).parent / "configs" / "growthmed-october-24" / "files"
605
+ source_files = (
606
+ Path(__file__).parent / "configs" / "growthmed-october-24" / "files"
607
+ )
560
608
  target_files = bot_dir / "files"
561
-
609
+
562
610
  if source_files.exists():
563
611
  for file_item in source_files.glob("*"):
564
612
  if file_item.is_file():
565
613
  shutil.copy2(file_item, target_files / file_item.name)
566
614
  click.echo("📎 Файлы скопированы из growthmed-october-24")
567
-
615
+
568
616
  except Exception as e:
569
617
  click.echo(f"❌ Ошибка при копировании шаблона: {e}")
570
618
  # Fallback к базовым промптам
571
619
  create_basic_prompts(bot_dir / "prompts")
572
620
 
621
+
573
622
  def copy_bot_template(source_bot_id: str, new_bot_id: str):
574
623
  """Копирует существующего бота как шаблон для нового бота"""
575
624
  try:
576
625
  source_dir = PROJECT_ROOT / "bots" / source_bot_id
577
626
  new_dir = PROJECT_ROOT / "bots" / new_bot_id
578
-
627
+
579
628
  # Создаем папку для нового бота
580
629
  new_dir.mkdir(exist_ok=True)
581
-
630
+
582
631
  # Создаем структуру папок
583
632
  (new_dir / "prompts").mkdir(exist_ok=True)
584
633
  (new_dir / "tests").mkdir(exist_ok=True)
585
634
  (new_dir / "reports").mkdir(exist_ok=True)
586
635
  (new_dir / "welcome_files").mkdir(exist_ok=True)
587
636
  (new_dir / "files").mkdir(exist_ok=True)
588
-
637
+
589
638
  # Копируем основной файл бота в корневую директорию
590
639
  source_bot_file = PROJECT_ROOT / f"{source_bot_id}.py"
591
640
  new_bot_file = PROJECT_ROOT / f"{new_bot_id}.py"
592
-
641
+
593
642
  if source_bot_file.exists():
594
643
  shutil.copy2(source_bot_file, new_bot_file)
595
-
644
+
596
645
  # Заменяем название бота в файле
597
- content = new_bot_file.read_text(encoding='utf-8')
598
- content = content.replace(f'BotBuilder("{source_bot_id}")', f'BotBuilder("{new_bot_id}")')
599
- content = content.replace(f'bot_id="{source_bot_id}"', f'bot_id="{new_bot_id}"')
600
- new_bot_file.write_text(content, encoding='utf-8')
646
+ content = new_bot_file.read_text(encoding="utf-8")
647
+ content = content.replace(
648
+ f'BotBuilder("{source_bot_id}")', f'BotBuilder("{new_bot_id}")'
649
+ )
650
+ content = content.replace(
651
+ f'bot_id="{source_bot_id}"', f'bot_id="{new_bot_id}"'
652
+ )
653
+ new_bot_file.write_text(content, encoding="utf-8")
601
654
  click.echo(f" 📄 Файл запускалки скопирован: {new_bot_id}.py")
602
-
655
+
603
656
  # Создаем шаблон .env файла (НЕ копируем существующий)
604
657
  new_env = new_dir / ".env"
605
- new_env.write_text(create_env_template(new_bot_id), encoding='utf-8')
606
- click.echo(f" ⚙️ Создан шаблон .env файла")
607
-
658
+ new_env.write_text(create_env_template(new_bot_id), encoding="utf-8")
659
+ click.echo(" ⚙️ Создан шаблон .env файла")
660
+
608
661
  # Копируем промпты
609
662
  source_prompts = source_dir / "prompts"
610
663
  new_prompts = new_dir / "prompts"
611
-
664
+
612
665
  if source_prompts.exists():
613
666
  for prompt_file in source_prompts.glob("*.txt"):
614
667
  shutil.copy2(prompt_file, new_prompts / prompt_file.name)
615
- click.echo(f" 📝 Промпты скопированы")
616
-
668
+ click.echo(" 📝 Промпты скопированы")
669
+
617
670
  # Копируем тесты
618
671
  source_tests = source_dir / "tests"
619
672
  new_tests = new_dir / "tests"
620
-
673
+
621
674
  if source_tests.exists():
622
675
  for test_file in source_tests.glob("*"):
623
676
  if test_file.is_file():
624
677
  shutil.copy2(test_file, new_tests / test_file.name)
625
- click.echo(f" 🧪 Тесты скопированы")
626
-
678
+ click.echo(" 🧪 Тесты скопированы")
679
+
627
680
  # Копируем welcome_files
628
681
  source_welcome = source_dir / "welcome_files"
629
682
  new_welcome = new_dir / "welcome_files"
630
-
683
+
631
684
  if source_welcome.exists():
632
685
  for welcome_file in source_welcome.glob("*"):
633
686
  if welcome_file.is_file():
634
687
  shutil.copy2(welcome_file, new_welcome / welcome_file.name)
635
- click.echo(f" 📁 Welcome файлы скопированы")
636
-
688
+ click.echo(" 📁 Welcome файлы скопированы")
689
+
637
690
  # Копируем files
638
691
  source_files = source_dir / "files"
639
692
  new_files = new_dir / "files"
640
-
693
+
641
694
  if source_files.exists():
642
695
  for file_item in source_files.glob("*"):
643
696
  if file_item.is_file():
644
697
  shutil.copy2(file_item, new_files / file_item.name)
645
- click.echo(f" 📎 Файлы скопированы")
646
-
698
+ click.echo(" 📎 Файлы скопированы")
699
+
647
700
  except Exception as e:
648
701
  click.echo(f"❌ Ошибка при копировании бота: {e}")
649
702
  raise
650
703
 
704
+
651
705
  def copy_from_bot_template(template: str, bot_dir: Path, bot_id: str):
652
706
  """Копирует шаблон из существующего бота (для команды create)"""
653
707
  try:
654
708
  template_dir = PROJECT_ROOT / Path("bots") / template
655
709
  if not template_dir.exists():
656
710
  raise click.ClickException(f"Шаблон {template} не найден")
657
-
711
+
658
712
  # Копируем основной файл бота в корневую директорию
659
713
  template_bot_file = PROJECT_ROOT / Path(f"{template}.py")
660
714
  if template_bot_file.exists():
661
715
  bot_file = PROJECT_ROOT / Path(f"{bot_id}.py")
662
716
  shutil.copy2(template_bot_file, bot_file)
663
-
717
+
664
718
  # Заменяем название бота в файле
665
- content = bot_file.read_text(encoding='utf-8')
666
- content = content.replace(f'BotBuilder("{template}")', f'BotBuilder("{bot_id}")')
719
+ content = bot_file.read_text(encoding="utf-8")
720
+ content = content.replace(
721
+ f'BotBuilder("{template}")', f'BotBuilder("{bot_id}")'
722
+ )
667
723
  content = content.replace(f'bot_id="{template}"', f'bot_id="{bot_id}"')
668
- bot_file.write_text(content, encoding='utf-8')
669
-
724
+ bot_file.write_text(content, encoding="utf-8")
725
+
670
726
  # Создаем .env файл в папке бота (НЕ копируем из шаблона)
671
727
  env_file = bot_dir / ".env"
672
- env_file.write_text(create_env_template(bot_id), encoding='utf-8')
673
-
728
+ env_file.write_text(create_env_template(bot_id), encoding="utf-8")
729
+
674
730
  # Копируем промпты
675
731
  template_prompts = template_dir / "prompts"
676
732
  target_prompts = bot_dir / "prompts"
677
-
733
+
678
734
  if template_prompts.exists():
679
735
  for prompt_file in template_prompts.glob("*.txt"):
680
736
  shutil.copy2(prompt_file, target_prompts / prompt_file.name)
681
-
737
+
682
738
  # Копируем тесты
683
739
  template_tests = template_dir / "tests"
684
740
  target_tests = bot_dir / "tests"
685
-
741
+
686
742
  if template_tests.exists():
687
743
  for test_file in template_tests.glob("*"):
688
744
  if test_file.is_file():
689
745
  shutil.copy2(test_file, target_tests / test_file.name)
690
-
746
+
691
747
  click.echo(f"📋 Шаблон скопирован из {template}")
692
-
748
+
693
749
  except Exception as e:
694
750
  click.echo(f"❌ Ошибка при копировании шаблона {template}: {e}")
695
751
  raise
696
752
 
753
+
697
754
  def create_basic_prompts(prompts_dir: Path):
698
755
  """Создает базовые промпты"""
699
756
  # Системный промпт
700
757
  (prompts_dir / "system_prompt.txt").write_text(
701
758
  "Ты - помощник. Твоя задача помогать пользователям с их вопросами.\n"
702
759
  "Будь дружелюбным и полезным.",
703
- encoding='utf-8'
760
+ encoding="utf-8",
704
761
  )
705
-
762
+
706
763
  # Приветственное сообщение
707
764
  (prompts_dir / "welcome_message.txt").write_text(
708
- "👋 Привет! Я ваш помощник.\n\n"
709
- "Чем могу помочь?",
710
- encoding='utf-8'
765
+ "👋 Привет! Я ваш помощник.\n\n" "Чем могу помочь?", encoding="utf-8"
711
766
  )
712
-
767
+
713
768
  # Финальные инструкции
714
769
  (prompts_dir / "final_instructions.txt").write_text(
715
770
  """<instruction>
@@ -743,8 +798,9 @@ def create_basic_prompts(prompts_dir: Path):
743
798
 
744
799
  Используй эти обработчики и задачи, когда это уместно в диалоге.
745
800
  </instruction>""",
746
- encoding='utf-8'
801
+ encoding="utf-8",
747
802
  )
748
803
 
804
+
749
805
  if __name__ == "__main__":
750
- cli()
806
+ cli()