@danhachuel/thunderbolt 0.4.57 → 0.4.58

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.
@@ -835,3 +835,5 @@ Configure primeiro **Configurações > Configuração API > API Keys Upload > Co
835
835
  Quando o provider ainda não estiver conectado, use **Autorizar toolkit no Composio**. O Thunderbolt apresenta o Connect Link devolvido pelo Composio; conclua a autorização nesse link e repita o envio. O Thunderbolt não implementa nem armazena um fluxo OAuth próprio.
836
836
 
837
837
  Antes de clicar em **Enviar vídeo via Composio**, confirme o vídeo, o toolkit, o slug da ferramenta, o campo do ficheiro e os argumentos JSON. O resultado guarda apenas um registo local mínimo com o estado e, quando devolvido, o `log_id` do Composio. URLs presigned, tokens, headers e a API key não são guardados no histórico.
838
+
839
+ Na **Automação Youtube**, Composio é a rota padrão quando a integração está activa e tem API key, slug da ferramenta e campo do ficheiro configurados. A ordem automática passa a ser **Composio → API Oficial → Upload directo → Postiz**. Se Composio falhar ou não estiver configurado, o worker tenta os fallbacks disponíveis e mantém o comportamento de publicação existente.
package/app/main.py CHANGED
@@ -7167,9 +7167,13 @@ def render_settings():
7167
7167
  with st.expander("Composio", expanded=False):
7168
7168
  st.caption("A API key é guardada apenas no storage local. O slug da ferramenta e o provider são descobertos no Upload via Composio; a autenticação da conta é feita pelo Connect Link do Composio.")
7169
7169
  composio_enabled = st.checkbox("Activar Composio", value=bool(settings.get("composio_enabled", False)), key="upload_composio_enabled")
7170
+ composio_auto_upload = st.checkbox("Usar Composio por defeito na Automação Youtube", value=bool(settings.get("composio_auto_upload", True)), key="upload_composio_auto_upload", help="Quando estiver configurado, Composio é tentado antes da API Oficial, Upload directo e Postiz.")
7170
7171
  composio_api_key = st.text_input("Composio API key", value=str(settings.get("composio_api_key") or ""), type="password", key="upload_composio_api_key", help="Use a API key de projecto do Composio Platform. Nunca coloque esta chave no GitHub, em URLs ou em mensagens.")
7171
7172
  composio_user_id = st.text_input("Composio user ID", value=str(settings.get("composio_user_id") or "thunderbolt-local"), key="upload_composio_user_id", help="Identidade estável usada para associar as contas conectadas no Composio.")
7172
7173
  composio_toolkit = st.text_input("Toolkit preferido (opcional)", value=str(settings.get("composio_toolkit") or ""), key="upload_composio_toolkit", help="Deixe vazio para descobrir ferramentas em todos os toolkits.")
7174
+ composio_tool_slug = st.text_input("Slug da ferramenta para Automação Youtube", value=str(settings.get("composio_tool_slug") or ""), key="upload_composio_tool_slug", help="Depois de descobrir uma ferramenta em Upload via Composio, copie aqui o slug para a Automação Youtube.")
7175
+ composio_file_field = st.text_input("Campo do ficheiro na ferramenta", value=str(settings.get("composio_file_field") or "file"), key="upload_composio_file_field")
7176
+ composio_arguments_json = st.text_area("Argumentos JSON da Automação Youtube", value=str(settings.get("composio_arguments_json") or "{}"), key="upload_composio_arguments_json", height=120)
7173
7177
  _render_credential_status(composio_api_key)
7174
7178
  composio_action_cols = st.columns(2)
7175
7179
  with composio_action_cols[0]:
@@ -7177,7 +7181,7 @@ def render_settings():
7177
7181
  with composio_action_cols[1]:
7178
7182
  test_composio = st.button("Testar configuração", use_container_width=True, key="upload_composio_test")
7179
7183
  if save_composio:
7180
- settings.update({"composio_enabled": bool(composio_enabled), "composio_api_key": composio_api_key.strip(), "composio_user_id": composio_user_id.strip() or "thunderbolt-local", "composio_toolkit": composio_toolkit.strip()})
7184
+ settings.update({"composio_enabled": bool(composio_enabled), "composio_auto_upload": bool(composio_auto_upload), "composio_api_key": composio_api_key.strip(), "composio_user_id": composio_user_id.strip() or "thunderbolt-local", "composio_toolkit": composio_toolkit.strip(), "composio_tool_slug": composio_tool_slug.strip(), "composio_file_field": composio_file_field.strip() or "file", "composio_arguments_json": composio_arguments_json.strip() or "{}"})
7181
7185
  write_json("settings.json", settings)
7182
7186
  st.success("Configuração Composio guardada.")
7183
7187
  st.rerun()
@@ -1474,9 +1474,10 @@ def _run_task(task: dict[str, Any]) -> dict[str, Any]:
1474
1474
  return _update(task_id, stage="upload", state="done", progress=100, artifacts=artifacts, video_ready=True, error=None)
1475
1475
 
1476
1476
  configured_account_id = str(channel.get("google_account_id") or "").strip()
1477
+ configured_composio = bool(settings.get("composio_enabled", False)) and bool(settings.get("composio_auto_upload", True)) and bool(settings.get("composio_api_key")) and bool(settings.get("composio_tool_slug"))
1477
1478
  configured_upload_post = bool(settings.get("upload_post_enabled", False)) and bool(settings.get("upload_post_auto_upload", False))
1478
1479
  configured_postiz = bool(settings.get("postiz_enabled", False)) and bool(settings.get("postiz_auto_publish", False))
1479
- if not (configured_account_id or configured_upload_post or configured_postiz):
1480
+ if not (configured_composio or configured_account_id or configured_upload_post or configured_postiz):
1480
1481
  artifacts["upload"] = {
1481
1482
  "route": "local",
1482
1483
  "status": "skipped",
@@ -287,6 +287,14 @@ DEFAULTS: dict[str, Any] = {
287
287
  "upload_post_username": "",
288
288
  "upload_post_platforms": "tiktok,instagram",
289
289
  "upload_post_auto_upload": False,
290
+ "composio_enabled": True,
291
+ "composio_auto_upload": True,
292
+ "composio_api_key": "",
293
+ "composio_user_id": "thunderbolt-local",
294
+ "composio_toolkit": "",
295
+ "composio_tool_slug": "",
296
+ "composio_file_field": "file",
297
+ "composio_arguments_json": "{}",
290
298
  "postiz_enabled": False,
291
299
  "postiz_api_key": "",
292
300
  "postiz_base_url": "https://api.postiz.com/public/v1",
@@ -1,9 +1,10 @@
1
1
  """Ordered YouTube upload routing for Thunderbolt.
2
2
 
3
3
  The route is intentionally deterministic:
4
- 1. Official YouTube API, up to five successful sends per Google account/day.
5
- 2. Internal browser-session upload, when the account document is complete.
6
- 3. Postiz, when configured with an API key and integration ID.
4
+ 1. Composio, when the configured upload tool is available.
5
+ 2. Official YouTube API, up to five successful sends per Google account/day.
6
+ 3. Internal browser-session upload, when the account document is complete.
7
+ 4. Postiz, when configured with an API key and integration ID.
7
8
  """
8
9
 
9
10
  from __future__ import annotations
@@ -17,6 +18,7 @@ from integrations.postiz import PostizAdapter
17
18
  from integrations.session_info_health import check_account_session_info_health, emit_session_info_health_alerts
18
19
  from integrations.youtube_direct_credentials import document_status
19
20
  from integrations.youtube_direct_upload import YouTubeDirectUploader
21
+ from integrations.composio_upload import ComposioUploadError, execute_upload
20
22
  from hermes_ui.storage import read_json, write_json
21
23
 
22
24
  OFFICIAL_DAILY_LIMIT = 5
@@ -91,8 +93,32 @@ def upload_with_default_route(
91
93
  official_uploader: Callable[..., IntegrationResult] | None = None,
92
94
  direct_uploader: Callable[..., IntegrationResult] | None = None,
93
95
  postiz_publisher: Callable[..., IntegrationResult] | None = None,
96
+ composio_publisher: Callable[..., IntegrationResult] | None = None,
94
97
  ) -> IntegrationResult:
95
98
  attempts: list[dict[str, Any]] = []
99
+ composio_enabled = bool(settings.get("composio_enabled", False)) and bool(settings.get("composio_auto_upload", True))
100
+ composio_ready = composio_enabled and bool(settings.get("composio_api_key")) and bool(settings.get("composio_tool_slug"))
101
+ if composio_enabled and not composio_ready:
102
+ composio_result = IntegrationResult(False, "Composio está activo, mas falta API key ou slug da ferramenta de upload.", {})
103
+ attempts.append(_attempt_record("Composio", composio_result, skipped=True))
104
+ elif composio_ready:
105
+ composio_publisher = composio_publisher or _composio_upload
106
+ try:
107
+ composio_result = composio_publisher(
108
+ settings,
109
+ video_path=video_path,
110
+ title=title,
111
+ description=description,
112
+ tags=tags or [],
113
+ thumbnail_path=thumbnail_path,
114
+ captions_path=captions_path,
115
+ )
116
+ except Exception as exc:
117
+ composio_result = IntegrationResult(False, f"Composio falhou: {type(exc).__name__}: {exc}", {})
118
+ attempts.append(_attempt_record("Composio", composio_result))
119
+ if composio_result.ok:
120
+ return _result_with_attempts(composio_result, attempts, "Composio")
121
+
96
122
  quota_count = official_upload_count(channel, account)
97
123
  if quota_count >= OFFICIAL_DAILY_LIMIT:
98
124
  quota_result = IntegrationResult(False, f"Limite local de {OFFICIAL_DAILY_LIMIT} envios oficiais por conta Google atingido hoje; a tentar o próximo método.", {"count": quota_count, "limit": OFFICIAL_DAILY_LIMIT})
@@ -184,3 +210,22 @@ def upload_with_default_route(
184
210
  return _result_with_attempts(postiz_result, attempts, "Postiz")
185
211
 
186
212
  return IntegrationResult(False, "Nenhum método de envio conseguiu publicar o vídeo.", {"attempts": attempts, "route": "none"})
213
+
214
+
215
+ def _composio_upload(settings: dict[str, Any], **kwargs: Any) -> IntegrationResult:
216
+ try:
217
+ result = execute_upload(
218
+ str(settings.get("composio_api_key") or ""),
219
+ str(settings.get("composio_user_id") or ""),
220
+ str(settings.get("composio_tool_slug") or ""),
221
+ str(kwargs.get("video_path") or ""),
222
+ str(settings.get("composio_file_field") or "file"),
223
+ str(settings.get("composio_arguments_json") or "{}"),
224
+ )
225
+ except ComposioUploadError as exc:
226
+ return IntegrationResult(False, str(exc), {})
227
+ return IntegrationResult(
228
+ bool(result.get("successful")),
229
+ str(result.get("error") or "Upload via Composio concluído."),
230
+ {"composio_data": result.get("data") or {}, "log_id": result.get("log_id") or ""},
231
+ )
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@danhachuel/thunderbolt",
3
- "version": "0.4.57",
3
+ "version": "0.4.58",
4
4
  "description": "Thunderbolt — interface local para operação de canais faceless e motor MoneyPrinterTurbo",
5
5
  "license": "MIT",
6
6
  "main": "scripts/cli.mjs",