@danhachuel/thunderbolt 0.4.55 → 0.4.57
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/MANUAL-INSTALACAO.md +10 -0
- package/app/main.py +132 -6
- package/integrations/composio_upload.py +174 -0
- package/package.json +1 -1
- package/requirements.txt +1 -0
- package/scripts/cli.mjs +1 -1
package/MANUAL-INSTALACAO.md
CHANGED
|
@@ -825,3 +825,13 @@ Na interface, uma tarefa em execução mostra a última actividade do helper e o
|
|
|
825
825
|
A partir da versão 0.3.78, o card **Backlog Vídeos** mostra a causa terminal e disponibiliza **Ver diagnóstico completo**. As mensagens `Pexels key validation completed`, `installing or verifying project dependencies with uv` e `TASK_DIR` são apenas progresso ou metadados; não são, por si só, uma falha.
|
|
826
826
|
|
|
827
827
|
Se o `uv` falhar, o erro inclui o detalhe seguro do resolver. Se a CLI do MoneyPrinterTurbo falhar, o resumo inclui `MPT_ERROR` e mantém `Log completo` e `Manifesto`. A interface não exibe chaves, cookies ou tokens. O tempo limite da verificação de dependências é de 15 minutos; a etapa é encerrada em vez de ficar indefinidamente sem resposta.
|
|
828
|
+
|
|
829
|
+
## 18. Upload via Composio
|
|
830
|
+
|
|
831
|
+
Em **Pipeline Vídeos > Upload > Upload via Composio**, o Thunderbolt permite descobrir ferramentas de upload disponíveis no projecto Composio Platform, seleccionar um toolkit e enviar um vídeo pronto da pipeline. Como cada provider expõe ferramentas e campos diferentes, o slug da ferramenta e o campo que recebe o vídeo são escolhidos explicitamente na interface; nenhum destino é assumido automaticamente.
|
|
832
|
+
|
|
833
|
+
Configure primeiro **Configurações > Configuração API > API Keys Upload > Composio**. Active a integração, introduza a API key de projecto Composio Platform e defina um `user ID` estável para associar as contas conectadas. A chave permanece no storage local e não é incluída no histórico, nos logs nem no repositório. O botão de teste faz apenas descoberta read-only.
|
|
834
|
+
|
|
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
|
+
|
|
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.
|
package/app/main.py
CHANGED
|
@@ -98,6 +98,7 @@ from integrations.youtube_batch import account_key as youtube_batch_account_key,
|
|
|
98
98
|
from integrations.local_runtime import MoneyPrinterRuntime
|
|
99
99
|
from integrations.moneyprinter_config import sync_moneyprinter_config
|
|
100
100
|
from integrations.openai_model_discovery import DEFAULT_NVIDIA_NIM_BASE_URL
|
|
101
|
+
from integrations.composio_upload import ComposioUploadError, authorize_toolkit, discover_tools, execute_upload, parse_arguments, test_configuration
|
|
101
102
|
|
|
102
103
|
DEFAULT_UI_LANGUAGE = "en"
|
|
103
104
|
|
|
@@ -5302,7 +5303,7 @@ def render_music_upload() -> None:
|
|
|
5302
5303
|
|
|
5303
5304
|
def render_upload():
|
|
5304
5305
|
st.title("Upload")
|
|
5305
|
-
upload_tab, direct_tab, postiz_tab, upload_post_tab = render_localized_tabs(["Upload convencional", "Upload directo", "Postiz", "Upload-Post"])
|
|
5306
|
+
upload_tab, direct_tab, postiz_tab, upload_post_tab, composio_tab = render_localized_tabs(["Upload convencional", "Upload directo", "Postiz", "Upload-Post", "Upload via Composio"])
|
|
5306
5307
|
with direct_tab:
|
|
5307
5308
|
render_upload_direct()
|
|
5308
5309
|
with postiz_tab:
|
|
@@ -5311,6 +5312,81 @@ def render_upload():
|
|
|
5311
5312
|
render_upload_post()
|
|
5312
5313
|
with upload_tab:
|
|
5313
5314
|
render_upload_conventional()
|
|
5315
|
+
with composio_tab:
|
|
5316
|
+
render_upload_composio()
|
|
5317
|
+
|
|
5318
|
+
|
|
5319
|
+
def render_upload_composio():
|
|
5320
|
+
st.subheader("Upload via Composio")
|
|
5321
|
+
st.caption("Descubra uma ferramenta de upload Composio, ligue a conta do provider quando necessário e envie um vídeo seleccionado. O slug e o campo do ficheiro são sempre escolhidos explicitamente.")
|
|
5322
|
+
settings = read_json("settings.json", {})
|
|
5323
|
+
api_key = str(settings.get("composio_api_key") or "").strip()
|
|
5324
|
+
user_id = str(settings.get("composio_user_id") or "").strip()
|
|
5325
|
+
if not settings.get("composio_enabled", False) or not api_key:
|
|
5326
|
+
st.warning("Composio está desactivado ou sem API key. Configure-o em Configuração API > API Keys Upload > Composio.")
|
|
5327
|
+
return
|
|
5328
|
+
query = st.text_input("Pesquisar ferramenta Composio", value="upload a video file", key="composio_upload_query")
|
|
5329
|
+
toolkit_filter = st.text_input("Toolkit preferido (opcional)", value=str(settings.get("composio_toolkit") or ""), key="composio_upload_toolkit")
|
|
5330
|
+
if st.button("Descobrir ferramentas", key="composio_upload_discover", use_container_width=True):
|
|
5331
|
+
try:
|
|
5332
|
+
st.session_state["composio_upload_tools"] = discover_tools(api_key, user_id, query, toolkit_filter)
|
|
5333
|
+
if not st.session_state["composio_upload_tools"]:
|
|
5334
|
+
st.info("Nenhuma ferramenta encontrada. Tente uma pesquisa mais específica ou deixe o toolkit vazio.")
|
|
5335
|
+
else:
|
|
5336
|
+
st.success(f"{len(st.session_state['composio_upload_tools'])} ferramenta(s) encontrada(s).")
|
|
5337
|
+
except ComposioUploadError as exc:
|
|
5338
|
+
st.error(str(exc))
|
|
5339
|
+
tools = [item for item in st.session_state.get("composio_upload_tools", []) if isinstance(item, dict) and item.get("slug")]
|
|
5340
|
+
if not tools:
|
|
5341
|
+
st.info("Clique em Descobrir ferramentas para carregar os destinos disponíveis na sua conta Composio.")
|
|
5342
|
+
return
|
|
5343
|
+
tool_slugs = [str(item["slug"]) for item in tools]
|
|
5344
|
+
selected_slug = st.selectbox("Ferramenta de upload", tool_slugs, format_func=lambda slug: next((f"{item.get('name') or slug} — {item.get('toolkit') or 'toolkit desconhecido'}" for item in tools if item.get("slug") == slug), slug), key="composio_upload_slug")
|
|
5345
|
+
selected_tool = next((item for item in tools if item.get("slug") == selected_slug), tools[0])
|
|
5346
|
+
if selected_tool.get("description"):
|
|
5347
|
+
st.caption(str(selected_tool["description"]))
|
|
5348
|
+
schema = selected_tool.get("schema") or {}
|
|
5349
|
+
with st.expander("Schema da ferramenta", expanded=False):
|
|
5350
|
+
st.json(schema)
|
|
5351
|
+
file_field = st.text_input("Campo que recebe o vídeo", value=str(st.session_state.get("composio_upload_file_field") or "file"), key="composio_upload_file_field")
|
|
5352
|
+
arguments_json = st.text_area("Argumentos JSON adicionais", value=str(st.session_state.get("composio_upload_arguments") or "{}"), height=150, key="composio_upload_arguments")
|
|
5353
|
+
if st.button("Autorizar toolkit no Composio", key="composio_upload_authorize", use_container_width=True):
|
|
5354
|
+
try:
|
|
5355
|
+
auth = authorize_toolkit(api_key, user_id, str(selected_tool.get("toolkit") or ""))
|
|
5356
|
+
if auth.get("redirect_url"):
|
|
5357
|
+
st.success("Connect Link criado. Abra o link para autorizar a conta e depois repita o envio.")
|
|
5358
|
+
st.markdown(f"[Abrir Connect Link do Composio]({auth['redirect_url']})")
|
|
5359
|
+
else:
|
|
5360
|
+
st.info("O Composio não devolveu um Connect Link; confirme o estado da conta no dashboard.")
|
|
5361
|
+
except ComposioUploadError as exc:
|
|
5362
|
+
st.error(str(exc))
|
|
5363
|
+
tasks = [task for task in read_json("tasks.json", []) if task.get("state") == "done" or task.get("artifacts", {}).get("video")]
|
|
5364
|
+
if not tasks:
|
|
5365
|
+
st.info("Não há vídeos prontos para enviar via Composio.")
|
|
5366
|
+
return
|
|
5367
|
+
task_options = {str(task.get("id")): task for task in tasks if task.get("id")}
|
|
5368
|
+
selected_task_id = st.selectbox("Vídeo", list(task_options), format_func=lambda task_id: f"{task_options[task_id].get('topic') or 'Vídeo Thunderbolt'} — {task_options[task_id].get('artifacts', {}).get('video') or 'sem ficheiro'}", key="composio_upload_task")
|
|
5369
|
+
task = task_options[selected_task_id]
|
|
5370
|
+
video_path = str((task.get("artifacts", {}) or {}).get("video") or "")
|
|
5371
|
+
st.caption(video_path or "Sem caminho de vídeo registado")
|
|
5372
|
+
st.info(f"Resumo: `{selected_slug}` · toolkit `{selected_tool.get('toolkit') or 'não indicado'}` · campo `{file_field or 'não indicado'}`")
|
|
5373
|
+
if st.button("Enviar vídeo via Composio", type="primary", key=f"composio_upload_send_{selected_task_id}", use_container_width=True):
|
|
5374
|
+
try:
|
|
5375
|
+
parse_arguments(arguments_json)
|
|
5376
|
+
result = execute_upload(api_key, user_id, selected_slug, video_path, file_field, arguments_json)
|
|
5377
|
+
record = {"id": uuid.uuid4().hex, "task_id": task.get("id"), "destination": "Composio", "target": {"toolkit": selected_tool.get("toolkit"), "slug": selected_slug, "file_field": file_field}, "status": "published" if result.get("successful") else "failed", "message": result.get("error") or "Upload Composio concluído.", "data": result.get("data") or {}, "log_id": result.get("log_id") or "", "created_at": now()}
|
|
5378
|
+
uploads = read_json("uploads.json", [])
|
|
5379
|
+
uploads.append(record)
|
|
5380
|
+
write_json("uploads.json", uploads)
|
|
5381
|
+
reconcile_persisted_notifications()
|
|
5382
|
+
if result.get("successful"):
|
|
5383
|
+
st.success(record["message"])
|
|
5384
|
+
else:
|
|
5385
|
+
st.error(record["message"])
|
|
5386
|
+
if result.get("log_id"):
|
|
5387
|
+
st.caption(f"Composio log ID: {result['log_id']}")
|
|
5388
|
+
except ComposioUploadError as exc:
|
|
5389
|
+
st.error(str(exc))
|
|
5314
5390
|
|
|
5315
5391
|
|
|
5316
5392
|
def render_upload_postiz():
|
|
@@ -7048,20 +7124,70 @@ def render_settings():
|
|
|
7048
7124
|
with st.expander("Contas Google", expanded=False):
|
|
7049
7125
|
render_google_accounts(include_innertube=False)
|
|
7050
7126
|
with st.expander("API Innertube", expanded=False):
|
|
7051
|
-
st.
|
|
7052
|
-
|
|
7053
|
-
|
|
7127
|
+
st.markdown("### INNERTUBE_API_KEY")
|
|
7128
|
+
st.caption("Esta é uma chave API global do YouTube: aplica-se a todas as contas Google/YouTube e a todo o sistema. Não é associada a uma conta específica, não faz parte dos documentos de cookies/credenciais e não é editada no separador API Keys.")
|
|
7129
|
+
upload_batch_accounts = [item for item in settings.get("youtube_batch_accounts", []) if isinstance(item, dict) and item.get("id")]
|
|
7130
|
+
current_upload_innertube = str(settings.get("direct_innertube_api_key") or settings.get("INNERTUBE_API_KEY") or "").strip()
|
|
7131
|
+
if not current_upload_innertube:
|
|
7132
|
+
for legacy_account in upload_batch_accounts:
|
|
7133
|
+
current_upload_innertube = str(legacy_account.get("innertube_api_key") or legacy_account.get("INNERTUBE_API_KEY") or "").strip()
|
|
7134
|
+
if current_upload_innertube:
|
|
7135
|
+
break
|
|
7136
|
+
if current_upload_innertube:
|
|
7137
|
+
settings["direct_innertube_api_key"] = current_upload_innertube
|
|
7138
|
+
settings.pop("INNERTUBE_API_KEY", None)
|
|
7139
|
+
for legacy_account in upload_batch_accounts:
|
|
7140
|
+
legacy_account.pop("innertube_api_key", None)
|
|
7141
|
+
legacy_account.pop("INNERTUBE_API_KEY", None)
|
|
7142
|
+
settings["youtube_batch_accounts"] = upload_batch_accounts
|
|
7143
|
+
write_json("settings.json", settings)
|
|
7144
|
+
innertube_upload_status = st.columns([3.2, 1.2])
|
|
7145
|
+
with innertube_upload_status[0]:
|
|
7146
|
+
st.caption("Estado da chave global")
|
|
7147
|
+
with innertube_upload_status[1]:
|
|
7148
|
+
_render_credential_status(current_upload_innertube)
|
|
7149
|
+
with st.form("upload_innertube_api_key_form"):
|
|
7150
|
+
upload_innertube = st.text_input("INNERTUBE_API_KEY", value=current_upload_innertube, type="password", key="upload_api_innertube_key", help="Chave global usada pelo Upload directo para todas as contas Google/YouTube. Guarde-a na configuração global, separada dos documentos de cookies.")
|
|
7151
|
+
_render_api_test_control(settings, "innertube_upload", lambda: test_innertube_api_key(upload_innertube), widget_key="api_test_innertube_upload")
|
|
7152
|
+
save_upload_innertube = st.form_submit_button("Guardar INNERTUBE_API_KEY global", type="primary", use_container_width=True)
|
|
7153
|
+
if save_upload_innertube:
|
|
7054
7154
|
settings["direct_innertube_api_key"] = upload_innertube.strip()
|
|
7055
7155
|
settings.pop("INNERTUBE_API_KEY", None)
|
|
7156
|
+
for legacy_account in upload_batch_accounts:
|
|
7157
|
+
legacy_account.pop("innertube_api_key", None)
|
|
7158
|
+
legacy_account.pop("INNERTUBE_API_KEY", None)
|
|
7159
|
+
settings["youtube_batch_accounts"] = upload_batch_accounts
|
|
7056
7160
|
write_json("settings.json", settings)
|
|
7057
|
-
st.success("INNERTUBE_API_KEY guardada.")
|
|
7161
|
+
st.success("INNERTUBE_API_KEY global guardada para todas as contas Google/YouTube e para todo o sistema.")
|
|
7058
7162
|
st.rerun()
|
|
7059
7163
|
with st.expander("API Tiktok", expanded=False):
|
|
7060
7164
|
render_tiktok_api_cards(settings)
|
|
7061
7165
|
with st.expander("API Bilibili", expanded=False):
|
|
7062
7166
|
render_bilibili_api_cards(settings)
|
|
7063
7167
|
with st.expander("Composio", expanded=False):
|
|
7064
|
-
st.
|
|
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
|
+
composio_enabled = st.checkbox("Activar Composio", value=bool(settings.get("composio_enabled", False)), key="upload_composio_enabled")
|
|
7170
|
+
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
|
+
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
|
+
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.")
|
|
7173
|
+
_render_credential_status(composio_api_key)
|
|
7174
|
+
composio_action_cols = st.columns(2)
|
|
7175
|
+
with composio_action_cols[0]:
|
|
7176
|
+
save_composio = st.button("Guardar Composio", type="primary", use_container_width=True, key="upload_composio_save")
|
|
7177
|
+
with composio_action_cols[1]:
|
|
7178
|
+
test_composio = st.button("Testar configuração", use_container_width=True, key="upload_composio_test")
|
|
7179
|
+
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()})
|
|
7181
|
+
write_json("settings.json", settings)
|
|
7182
|
+
st.success("Configuração Composio guardada.")
|
|
7183
|
+
st.rerun()
|
|
7184
|
+
if test_composio:
|
|
7185
|
+
try:
|
|
7186
|
+
result = test_configuration(composio_api_key, composio_user_id)
|
|
7187
|
+
found = len((result.get("data") or {}).get("tools", []))
|
|
7188
|
+
st.success(f"Configuração Composio válida; {found} ferramenta(s) de upload encontradas.")
|
|
7189
|
+
except ComposioUploadError as exc:
|
|
7190
|
+
st.error(str(exc))
|
|
7065
7191
|
with st.expander("Upload-Post", expanded=False):
|
|
7066
7192
|
upload_post_enabled_upload = st.checkbox("Activar Upload-Post", bool(settings.get("upload_post_enabled", False)), key="upload_tab_upload_post_enabled")
|
|
7067
7193
|
upload_post_key_upload = st.text_input("Upload-Post API key", value=str(settings.get("upload_post_api_key") or ""), type="password", key="upload_tab_upload_post_key")
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
"""Composio Platform adapter for user-selected video upload tools."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
import json
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from typing import Any
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class ComposioUploadError(ValueError):
|
|
10
|
+
"""Safe, user-facing validation or integration error."""
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def _safe_value(value: Any) -> Any:
|
|
14
|
+
if value is None or isinstance(value, (str, int, float, bool)):
|
|
15
|
+
return value
|
|
16
|
+
if isinstance(value, dict):
|
|
17
|
+
return {str(key): _safe_value(item) for key, item in value.items()}
|
|
18
|
+
if isinstance(value, (list, tuple)):
|
|
19
|
+
return [_safe_value(item) for item in value]
|
|
20
|
+
for method in ("model_dump", "to_dict", "dict"):
|
|
21
|
+
converter = getattr(value, method, None)
|
|
22
|
+
if callable(converter):
|
|
23
|
+
try:
|
|
24
|
+
return _safe_value(converter())
|
|
25
|
+
except Exception:
|
|
26
|
+
pass
|
|
27
|
+
attributes = getattr(value, "__dict__", None)
|
|
28
|
+
if isinstance(attributes, dict):
|
|
29
|
+
return {str(key): _safe_value(item) for key, item in attributes.items()}
|
|
30
|
+
return str(value)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def _tool_item(tool: Any) -> dict[str, Any]:
|
|
34
|
+
raw = _safe_value(tool)
|
|
35
|
+
if isinstance(raw, dict) and isinstance(raw.get("function"), dict):
|
|
36
|
+
function = raw["function"]
|
|
37
|
+
return {
|
|
38
|
+
"slug": str(function.get("name") or raw.get("name") or ""),
|
|
39
|
+
"name": str(function.get("name") or raw.get("name") or ""),
|
|
40
|
+
"description": str(function.get("description") or raw.get("description") or ""),
|
|
41
|
+
"toolkit": str(raw.get("toolkit") or ""),
|
|
42
|
+
"schema": function.get("parameters") or {},
|
|
43
|
+
}
|
|
44
|
+
if isinstance(raw, dict):
|
|
45
|
+
toolkit = raw.get("toolkit")
|
|
46
|
+
if isinstance(toolkit, dict):
|
|
47
|
+
toolkit = toolkit.get("slug") or toolkit.get("name") or ""
|
|
48
|
+
return {
|
|
49
|
+
"slug": str(raw.get("slug") or raw.get("name") or ""),
|
|
50
|
+
"name": str(raw.get("name") or raw.get("slug") or ""),
|
|
51
|
+
"description": str(raw.get("description") or ""),
|
|
52
|
+
"toolkit": str(toolkit or ""),
|
|
53
|
+
"schema": raw.get("input_parameters") or raw.get("parameters") or {},
|
|
54
|
+
}
|
|
55
|
+
return {"slug": "", "name": "", "description": "", "toolkit": "", "schema": {}}
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def _response(result: Any) -> dict[str, Any]:
|
|
59
|
+
raw = _safe_value(result)
|
|
60
|
+
if not isinstance(raw, dict):
|
|
61
|
+
raw = {"data": raw}
|
|
62
|
+
error = raw.get("error")
|
|
63
|
+
return {
|
|
64
|
+
"successful": not bool(error),
|
|
65
|
+
"data": raw.get("data") or {},
|
|
66
|
+
"error": str(error) if error else "",
|
|
67
|
+
"log_id": str(raw.get("log_id") or raw.get("request_id") or ""),
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def _require_api_key(api_key: str) -> str:
|
|
72
|
+
value = str(api_key or "").strip()
|
|
73
|
+
if len(value) < 10 or value.lower() in {"placeholder", "your_composio_api_key"}:
|
|
74
|
+
raise ComposioUploadError("Configure uma API key válida do Composio em Configuração API > API Keys Upload > Composio.")
|
|
75
|
+
return value
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def _require_user_id(user_id: str) -> str:
|
|
79
|
+
value = str(user_id or "").strip()
|
|
80
|
+
if not value:
|
|
81
|
+
raise ComposioUploadError("Indique um Composio user ID estável antes de continuar.")
|
|
82
|
+
return value
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def _client(api_key: str, *, upload_dir: Path | None = None):
|
|
86
|
+
try:
|
|
87
|
+
from composio import Composio
|
|
88
|
+
except ImportError as exc:
|
|
89
|
+
raise ComposioUploadError("A SDK Python composio não está instalada. Execute a instalação do Thunderbolt novamente.") from exc
|
|
90
|
+
kwargs: dict[str, Any] = {"api_key": _require_api_key(api_key), "allow_tracking": False}
|
|
91
|
+
if upload_dir is not None:
|
|
92
|
+
kwargs.update({
|
|
93
|
+
"dangerously_allow_auto_upload_download_files": True,
|
|
94
|
+
"file_upload_dirs": [str(upload_dir.resolve())],
|
|
95
|
+
})
|
|
96
|
+
return Composio(**kwargs)
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def discover_tools(api_key: str, user_id: str, query: str, toolkit: str = "") -> list[dict[str, Any]]:
|
|
100
|
+
client = _client(api_key)
|
|
101
|
+
try:
|
|
102
|
+
tools = client.tools.get(
|
|
103
|
+
_require_user_id(user_id),
|
|
104
|
+
search=(query or "upload a video file").strip(),
|
|
105
|
+
toolkits=[toolkit.strip()] if toolkit.strip() else None,
|
|
106
|
+
limit=10,
|
|
107
|
+
)
|
|
108
|
+
return [item for item in (_tool_item(tool) for tool in tools) if item.get("slug")]
|
|
109
|
+
except ComposioUploadError:
|
|
110
|
+
raise
|
|
111
|
+
except Exception as exc:
|
|
112
|
+
raise ComposioUploadError(f"Não foi possível descobrir ferramentas Composio: {type(exc).__name__}: {exc}") from exc
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
def authorize_toolkit(api_key: str, user_id: str, toolkit: str) -> dict[str, Any]:
|
|
116
|
+
toolkit = str(toolkit or "").strip()
|
|
117
|
+
if not toolkit:
|
|
118
|
+
raise ComposioUploadError("Seleccione uma ferramenta descoberta para saber qual toolkit deve ser autorizado.")
|
|
119
|
+
client = _client(api_key)
|
|
120
|
+
try:
|
|
121
|
+
request = client.create(user_id=_require_user_id(user_id)).authorize(toolkit)
|
|
122
|
+
return {"connected_account_id": str(getattr(request, "id", "") or ""), "redirect_url": str(getattr(request, "redirect_url", "") or "")}
|
|
123
|
+
except ComposioUploadError:
|
|
124
|
+
raise
|
|
125
|
+
except Exception as exc:
|
|
126
|
+
raise ComposioUploadError(f"Não foi possível criar o Connect Link do Composio: {type(exc).__name__}: {exc}") from exc
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
def parse_arguments(arguments_json: str) -> dict[str, Any]:
|
|
130
|
+
try:
|
|
131
|
+
parsed = json.loads(arguments_json or "{}")
|
|
132
|
+
except json.JSONDecodeError as exc:
|
|
133
|
+
raise ComposioUploadError(f"Os argumentos da ferramenta não são JSON válido: {exc.msg}.") from exc
|
|
134
|
+
if not isinstance(parsed, dict):
|
|
135
|
+
raise ComposioUploadError("Os argumentos da ferramenta devem ser um objecto JSON.")
|
|
136
|
+
return parsed
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
def execute_upload(api_key: str, user_id: str, slug: str, video_path: str, file_field: str, arguments_json: str = "") -> dict[str, Any]:
|
|
140
|
+
path = Path(str(video_path or "").strip()).expanduser()
|
|
141
|
+
slug = str(slug or "").strip()
|
|
142
|
+
file_field = str(file_field or "").strip()
|
|
143
|
+
if not path.is_file():
|
|
144
|
+
raise ComposioUploadError("O ficheiro de vídeo seleccionado não existe ou não é um ficheiro.")
|
|
145
|
+
if path.stat().st_size <= 0:
|
|
146
|
+
raise ComposioUploadError("O ficheiro de vídeo seleccionado está vazio.")
|
|
147
|
+
if not slug:
|
|
148
|
+
raise ComposioUploadError("Seleccione ou indique o slug de uma ferramenta Composio.")
|
|
149
|
+
if not file_field:
|
|
150
|
+
raise ComposioUploadError("Indique o campo de argumentos que recebe o ficheiro.")
|
|
151
|
+
arguments = parse_arguments(arguments_json)
|
|
152
|
+
if file_field in arguments and arguments[file_field] not in (None, "", str(path)):
|
|
153
|
+
raise ComposioUploadError(f"O campo `{file_field}` já contém um valor. Remova-o antes de injectar o vídeo.")
|
|
154
|
+
arguments[file_field] = str(path.resolve())
|
|
155
|
+
client = _client(api_key, upload_dir=path.parent)
|
|
156
|
+
try:
|
|
157
|
+
result = client.tools.execute(
|
|
158
|
+
slug,
|
|
159
|
+
arguments=arguments,
|
|
160
|
+
user_id=_require_user_id(user_id),
|
|
161
|
+
version="latest",
|
|
162
|
+
dangerously_skip_version_check=True,
|
|
163
|
+
)
|
|
164
|
+
return _response(result)
|
|
165
|
+
except ComposioUploadError:
|
|
166
|
+
raise
|
|
167
|
+
except Exception as exc:
|
|
168
|
+
safe_message = str(exc).replace(str(api_key), "[REDACTED]")
|
|
169
|
+
raise ComposioUploadError(f"A ferramenta Composio falhou: {type(exc).__name__}: {safe_message}") from exc
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
def test_configuration(api_key: str, user_id: str) -> dict[str, Any]:
|
|
173
|
+
tools = discover_tools(api_key, user_id, "upload a video file")
|
|
174
|
+
return {"successful": True, "data": {"tools": tools}, "error": "", "log_id": ""}
|
package/package.json
CHANGED
package/requirements.txt
CHANGED
package/scripts/cli.mjs
CHANGED
|
@@ -116,7 +116,7 @@ function check() {
|
|
|
116
116
|
console.error(`Python não encontrado. Execute: npx.cmd --yes @danhachuel/thunderbolt install`);
|
|
117
117
|
process.exit(1);
|
|
118
118
|
}
|
|
119
|
-
const requiredModules = ["streamlit", "requests", "pandas", "toml", "imageio_ffmpeg", "edge_tts", "sklearn", "mlxtend", "plotly", "seaborn", "kagglehub"];
|
|
119
|
+
const requiredModules = ["streamlit", "requests", "pandas", "toml", "imageio_ffmpeg", "edge_tts", "sklearn", "mlxtend", "plotly", "seaborn", "kagglehub", "composio"];
|
|
120
120
|
const missing = requiredModules.filter((moduleName) => !moduleAvailable(moduleName));
|
|
121
121
|
const ffmpeg = moduleAvailable("imageio_ffmpeg");
|
|
122
122
|
const mptPath = configuredMoneyPrinterPath();
|