izteamslots 1.3.2 → 1.4.0

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.
package/CHANGELOG.md CHANGED
@@ -1,3 +1,18 @@
1
+ # [1.4.0](https://github.com/izzzzzi/izTeamSlots/compare/v1.3.3...v1.4.0) (2026-03-06)
2
+
3
+
4
+ ### Features
5
+
6
+ * auto-discovery for mail providers — no manual registration needed ([ca54650](https://github.com/izzzzzi/izTeamSlots/commit/ca54650a42696b714c79cf328da5d58b5e2d1f60))
7
+
8
+ ## [1.3.3](https://github.com/izzzzzi/izTeamSlots/compare/v1.3.2...v1.3.3) (2026-03-06)
9
+
10
+
11
+ ### Bug Fixes
12
+
13
+ * quick start recommends configuring mail API key first ([c852b9d](https://github.com/izzzzzi/izTeamSlots/commit/c852b9d63d9e5867b897791e8fd11e712e1a515f))
14
+ * remove extra leading spaces from hero logo ([ddb43bb](https://github.com/izzzzzi/izTeamSlots/commit/ddb43bbc6ba0075736ac7acba433032b45dbfc37))
15
+
1
16
  ## [1.3.2](https://github.com/izzzzzi/izTeamSlots/compare/v1.3.1...v1.3.2) (2026-03-06)
2
17
 
3
18
 
package/CONTRIBUTING.md CHANGED
@@ -4,7 +4,7 @@
4
4
 
5
5
  ## Подготовка окружения
6
6
 
7
- 1. Форкните репозиторий
7
+ 1. Форкните [izzzzzi/izTeamSlots](https://github.com/izzzzzi/izTeamSlots)
8
8
  2. Клонируйте форк:
9
9
  ```bash
10
10
  git clone https://github.com/<your-username>/izTeamSlots.git
package/README.md CHANGED
@@ -45,6 +45,12 @@
45
45
  ```text
46
46
  izTeamSlots/
47
47
  ├── app.py # Entrypoint: запускает UI
48
+ ├── bin/ # CLI-бинарники
49
+ │ └── izteamslots.mjs # Кроссплатформенный entrypoint (Node.js)
50
+ ├── scripts/ # Установочные скрипты
51
+ │ ├── setup.mjs # Диспетчер: выбирает .sh или .cmd
52
+ │ ├── setup.sh # Unix: uv + venv + Bun
53
+ │ └── setup.cmd # Windows: uv + venv + Bun
48
54
  ├── backend/ # Весь Python-бэкенд
49
55
  │ ├── __init__.py # PROJECT_ROOT
50
56
  │ ├── __main__.py # python -m backend
@@ -71,8 +77,9 @@ izTeamSlots/
71
77
  │ ├── screens/MainScreen.ts # Главный экран
72
78
  │ ├── transport/stdioClient.ts # JSON-RPC клиент
73
79
  │ └── menus/ # Меню, таблицы, форматирование
74
- ├── accounts/ # Данные аккаунтов (runtime)
75
- ├── codex/ # Codex-файлы (runtime)
80
+ ├── requirements.txt # Python-зависимости
81
+ ├── ruff.toml # Конфиг линтера Python
82
+ ├── LICENSE # MIT
76
83
  └── README.md
77
84
  ```
78
85
 
@@ -100,7 +107,9 @@ npm install
100
107
 
101
108
  ### Настройка
102
109
 
103
- Создайте файл конфигурации `~/.izteamslots/.env`:
110
+ API-ключи и провайдеры почты настраиваются прямо в приложении через меню **«Настройки»**. Значения сохраняются в `~/.izteamslots/.env`.
111
+
112
+ Альтернативно — создайте файл вручную:
104
113
 
105
114
  ```bash
106
115
  # Linux / macOS
@@ -114,8 +123,6 @@ echo "BOOMLIFY_API_KEY=your_api_key" > "$env:USERPROFILE\.izteamslots\.env"
114
123
 
115
124
  Конфиг загружается в порядке приоритета: `~/.izteamslots/.env` > `./.env` > встроенный.
116
125
 
117
- Опциональные переменные:
118
-
119
126
  | Переменная | По умолчанию | Описание |
120
127
  |-----------|:------------:|----------|
121
128
  | `BOOMLIFY_API_KEY` | — | API-ключ Boomlify (обязательно для слотов) |
@@ -234,8 +241,3 @@ flowchart TD
234
241
  - Не публикуйте папки `accounts/` и `codex/` в публичные репозитории.
235
242
  - Для стабильной работы перелогина у worker должен быть `openai_password`.
236
243
 
237
- ---
238
-
239
- ## Участие в разработке
240
-
241
- См. [CONTRIBUTING.md](CONTRIBUTING.md).
@@ -1,4 +1,4 @@
1
- """Pluggable mail provider system.
1
+ """Pluggable mail provider system with auto-discovery.
2
2
 
3
3
  Usage::
4
4
 
@@ -17,18 +17,17 @@ Select provider via env var or name::
17
17
 
18
18
  provider = create_provider("imap", host="imap.example.com")
19
19
 
20
- Available providers:
21
- trickads - trickadsagencyltd.com temp mail (generic/default)
22
- boomlify - Boomlify temp mail API (slots default)
23
- imap - any IMAP server (env: IMAP_HOST, IMAP_PORT, IMAP_SSL)
24
-
25
20
  Custom providers:
26
- Subclass ``MailProvider`` from ``backend.mail.base`` and implement
27
- ``generate()`` and ``inbox()``.
21
+ Create a .py file in backend/mail/, subclass MailProvider, set ``name``
22
+ and optionally ``password_prefix``. The provider will be discovered
23
+ automatically — no edits to __init__.py needed.
28
24
  """
29
25
  from __future__ import annotations
30
26
 
27
+ import importlib
31
28
  import os
29
+ import pkgutil
30
+ from pathlib import Path
32
31
  from typing import Any
33
32
 
34
33
  from .base import Inbox, Mail, MailAuthError, Mailbox, MailError, MailProvider, MailServiceUnavailable
@@ -46,7 +45,38 @@ __all__ = [
46
45
  "create_slot_provider",
47
46
  ]
48
47
 
49
- _BUILTIN_PROVIDERS = ("boomlify", "trickads", "imap")
48
+ _REGISTRY: dict[str, type[MailProvider]] | None = None
49
+
50
+
51
+ def _discover_providers() -> dict[str, type[MailProvider]]:
52
+ """Scan this package for MailProvider subclasses and build a registry."""
53
+ registry: dict[str, type[MailProvider]] = {}
54
+ package_dir = Path(__file__).parent
55
+
56
+ for module_info in pkgutil.iter_modules([str(package_dir)]):
57
+ if module_info.name.startswith("_") or module_info.name == "base":
58
+ continue
59
+ try:
60
+ module = importlib.import_module(f".{module_info.name}", __package__)
61
+ except Exception:
62
+ continue
63
+ for attr in vars(module).values():
64
+ if (
65
+ isinstance(attr, type)
66
+ and issubclass(attr, MailProvider)
67
+ and attr is not MailProvider
68
+ and attr.name != "base"
69
+ ):
70
+ registry[attr.name] = attr
71
+
72
+ return registry
73
+
74
+
75
+ def _get_registry() -> dict[str, type[MailProvider]]:
76
+ global _REGISTRY # noqa: PLW0603
77
+ if _REGISTRY is None:
78
+ _REGISTRY = _discover_providers()
79
+ return _REGISTRY
50
80
 
51
81
 
52
82
  def create_provider(name: str | None = None, **kwargs: Any) -> MailProvider:
@@ -58,23 +88,12 @@ def create_provider(name: str | None = None, **kwargs: Any) -> MailProvider:
58
88
  **kwargs: Passed to provider constructor.
59
89
  """
60
90
  provider_name = name or os.environ.get("MAIL_PROVIDER", "trickads")
61
-
62
- if provider_name == "boomlify":
63
- from .boomlify import BoomlifyProvider
64
- return BoomlifyProvider(**kwargs)
65
-
66
- if provider_name == "trickads":
67
- from .trickads import TrickAdsProvider
68
- return TrickAdsProvider(**kwargs)
69
-
70
- if provider_name == "imap":
71
- from .imap import IMAPProvider
72
- return IMAPProvider(**kwargs)
73
-
74
- raise ValueError(
75
- f"Unknown mail provider: {provider_name!r}. "
76
- f"Available: {', '.join(_BUILTIN_PROVIDERS)}"
77
- )
91
+ registry = _get_registry()
92
+ cls = registry.get(provider_name)
93
+ if cls is None:
94
+ available = ", ".join(sorted(registry)) or "(none)"
95
+ raise ValueError(f"Unknown mail provider: {provider_name!r}. Available: {available}")
96
+ return cls(**kwargs)
78
97
 
79
98
 
80
99
  def create_slot_provider(name: str | None = None, **kwargs: Any) -> MailProvider:
@@ -89,10 +108,14 @@ def create_slot_provider(name: str | None = None, **kwargs: Any) -> MailProvider
89
108
  def create_provider_for_mailbox(mailbox: Mailbox, **kwargs: Any) -> MailProvider:
90
109
  """Pick provider based on stored mailbox credentials.
91
110
 
92
- Boomlify mailboxes store their email id inside ``Mailbox.password`` as
93
- ``boomlify:<uuid>``. Everything else falls back to the generic provider.
111
+ Checks ``password_prefix`` of each registered provider first,
112
+ then falls back to the default provider.
94
113
  """
95
114
  password = mailbox.password.strip()
96
- if password.startswith("boomlify:"):
97
- return create_provider("boomlify", **kwargs)
115
+ registry = _get_registry()
116
+
117
+ for cls in registry.values():
118
+ if cls.password_prefix and password.startswith(cls.password_prefix):
119
+ return cls(**kwargs)
120
+
98
121
  return create_provider(**kwargs)
@@ -63,6 +63,7 @@ class MailProvider(ABC):
63
63
  """
64
64
 
65
65
  name: str = "base"
66
+ password_prefix: str = ""
66
67
 
67
68
  @abstractmethod
68
69
  def generate(self) -> Mailbox:
@@ -50,6 +50,7 @@ class BoomlifyProvider(MailProvider):
50
50
  """Temporary email via Boomlify API."""
51
51
 
52
52
  name = "boomlify"
53
+ password_prefix = "boomlify:"
53
54
 
54
55
  def __init__(
55
56
  self,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "izteamslots",
3
- "version": "1.3.2",
3
+ "version": "1.4.0",
4
4
  "description": "ChatGPT Team slot management — automated invite, register, OAuth login & codex token pipeline",
5
5
  "bin": {
6
6
  "izteamslots": "bin/izteamslots.mjs"
@@ -14,8 +14,8 @@ const EMPTY_STATE: AppState = {
14
14
  }
15
15
 
16
16
  const HERO_LOGO = [
17
- " izTeamSlots",
18
- " Локальный центр управления слотами",
17
+ "izTeamSlots",
18
+ "Локальный центр управления слотами",
19
19
  ].join("\n")
20
20
 
21
21
  type RpcJobResult = { job_id: string }
@@ -578,6 +578,7 @@ export class MainScreen {
578
578
  private getRecommendedSteps(): string[] {
579
579
  if (this.state.admins.length === 0) {
580
580
  return [
581
+ "Откройте «Настройки» и укажите API-ключ временной почты.",
581
582
  "Добавьте первого админа через раздел «Админы».",
582
583
  "После добавления выполните логин, чтобы сохранить токен и профиль браузера.",
583
584
  "Затем создайте слоты и проверьте входящие письма.",