@danhachuel/thunderbolt 0.2.18 → 0.2.19
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 +12 -4
- package/README.md +12 -3
- package/app/main.py +138 -1
- package/hermes_ui/mcp.py +145 -0
- package/hermes_ui/storage.py +43 -1
- package/integrations/data/azure_voices.json +1326 -0
- package/package.json +3 -1
- package/scripts/cli.mjs +1 -0
- package/scripts/install.mjs +1 -0
- package/seed/skills/moneyprinterturbo-video.md +132 -0
package/MANUAL-INSTALACAO.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
Este manual descreve a instalação local da UI Thunderbolt, baseada no MoneyPrinterTurbo, utilizando o pacote npm `@danhachuel/thunderbolt`. O fluxo recomendado instala automaticamente o ambiente Python, as dependências da aplicação, as dependências do MoneyPrinterTurbo, o Streamlit e o suporte FFmpeg através de `imageio-ffmpeg`.
|
|
4
4
|
|
|
5
|
-
> **Versão deste manual:** 0.2.
|
|
5
|
+
> **Versão deste manual:** 0.2.19
|
|
6
6
|
> **Pacote npm:** `@danhachuel/thunderbolt`
|
|
7
7
|
> **Porta padrão da UI:** `localhost:3030`
|
|
8
8
|
> **Repositório:** [github.com/DanHachuel/thunderbolt](https://github.com/DanHachuel/thunderbolt)
|
|
@@ -100,13 +100,13 @@ Execute:
|
|
|
100
100
|
Windows PowerShell ou MobaXterm:
|
|
101
101
|
|
|
102
102
|
```powershell
|
|
103
|
-
npx.cmd --yes @danhachuel/thunderbolt@0.2.
|
|
103
|
+
npx.cmd --yes @danhachuel/thunderbolt@0.2.19 install
|
|
104
104
|
```
|
|
105
105
|
|
|
106
106
|
Linux/macOS:
|
|
107
107
|
|
|
108
108
|
```bash
|
|
109
|
-
npx --yes @danhachuel/thunderbolt@0.2.
|
|
109
|
+
npx --yes @danhachuel/thunderbolt@0.2.19 install
|
|
110
110
|
```
|
|
111
111
|
|
|
112
112
|
A instalação normal é **segura para actualizações**: preserva `storage`, Blueprints, Brandings, configurações e artefactos do utilizador. Remove apenas `.venv`, o clone técnico do MoneyPrinterTurbo e dependências que serão recriadas. Uma pasta antiga sem dados do utilizador, como `C:\Users\<utilizador>\AppData\Local\hermes` da tentativa incompleta, pode ser removida; uma pasta antiga que contenha Blueprints, Brandings ou storage é preservada e apenas avisada no terminal. Feche processos Python, Node, Streamlit e MobaXterm que estejam a usar as pastas antes de executar.
|
|
@@ -367,8 +367,16 @@ Após iniciar a aplicação, valide o seguinte percurso:
|
|
|
367
367
|
7. **Upload:** autorize primeiro o **youtube-automation-agent** na própria aba, confirme o estado **pronto para publicar**, preencha título/descrição/tags e publique um MP4 real. O botão **Autorizar fallback OAuth** existe apenas para redundância; a Data API Key é exclusivamente para consultas públicas.
|
|
368
368
|
8. **Limpador de metadado:** suba um vídeo de terceiro, preencha título, preview, links, timestamps e tags, aplique a limpeza e descarregue a cópia limpa e o manifesto JSON. O original fica preservado e vídeos da aba Novo vídeo não são aceites nesta área.
|
|
369
369
|
9. **Configurações:** confirme que os caminhos e credenciais estão locais e não aparecem no Git.
|
|
370
|
+
10. **MCP:** confirme que Short Video Maker, AutoVio, OpenMontage e OpenCut aparecem com as portas padrão editáveis. O estado **Activo** é apenas uma preferência local; a detecção deve indicar **Não detectado** quando os serviços externos não estiverem instalados ou iniciados.
|
|
371
|
+
11. **Skill MCP:** clique em **Guardar skill localmente** e confirme o ficheiro em `storage/skills/moneyprinterturbo-video.md`; opcionalmente use **Descarregar skill .md** para obter a cópia através do navegador.
|
|
370
372
|
|
|
371
|
-
##
|
|
373
|
+
## MCP e integrações externas
|
|
374
|
+
|
|
375
|
+
A aba **MCP** é um configurador local de quatro serviços opcionais. As portas iniciais são `3123` para Short Video Maker, `3001` para AutoVio, `8000` como referência editável para OpenMontage e `8787` para a API do OpenCut. A página consulta apenas `http://127.0.0.1:<porta>/` com timeout curto; não clona, instala ou inicia processos externos.
|
|
376
|
+
|
|
377
|
+
O botão **Guardar skill localmente** copia a skill anexada para `storage/skills/`. Esta pasta fica fora dos estados JSON e é criada pelo instalador e pelo launcher. Os quatro repositórios continuam fora do pacote npm.
|
|
378
|
+
|
|
379
|
+
## 11. Limpador de metadado
|
|
372
380
|
|
|
373
381
|
A aba **Limpador de metadado** recebe somente vídeos externos já prontos. O ficheiro enviado é copiado para `storage/metadata_cleaner/originals/`; a aplicação não altera o original. Ao aplicar a operação, o FFmpeg remove os metadados existentes do contentor e cria uma nova versão em `storage/metadata_cleaner/outputs/` com o título, descrição, tags, idioma e outros campos preenchidos.
|
|
374
382
|
|
package/README.md
CHANGED
|
@@ -17,6 +17,7 @@ A primeira versão implementa a camada UI independente com:
|
|
|
17
17
|
| Canais | Subabas de importação pública sem API Key, Data API opcional e cadastro manual independente |
|
|
18
18
|
| Novo vídeo | Subabas Criar vídeo e Vídeos; canal específico, lote no mesmo canal, lote geral, filtro de estados e controlos iniciar/parar |
|
|
19
19
|
| Upload | YouTube via `youtube-automation-agent` adaptado internamente, OAuth directo de redundância, TikTok e diagnóstico |
|
|
20
|
+
| MCP | Catálogo local opcional de Short Video Maker, AutoVio, OpenMontage e OpenCut, com portas editáveis e activação |
|
|
20
21
|
| Limpador de metadado | Upload isolado de vídeos terceiros, limpeza FFmpeg, edição de título/descrição/tags e manifesto JSON |
|
|
21
22
|
| Configurações | Provedores LLM, TTS/voz, materiais, Whisper, FFmpeg, YouTube, TikTok Client ID/Secret e Upload-Post |
|
|
22
23
|
| Launcher | Execução via `npx`, instalação assistida, diagnóstico e preparação para distribuição |
|
|
@@ -102,9 +103,9 @@ thunderbolt
|
|
|
102
103
|
No Windows PowerShell, se `npx` for bloqueado por `npx.ps1`, use directamente `npx.cmd`:
|
|
103
104
|
|
|
104
105
|
```powershell
|
|
105
|
-
npx.cmd --yes @danhachuel/thunderbolt@0.2.
|
|
106
|
-
npx.cmd --yes @danhachuel/thunderbolt@0.2.
|
|
107
|
-
npx.cmd --yes @danhachuel/thunderbolt@0.2.
|
|
106
|
+
npx.cmd --yes @danhachuel/thunderbolt@0.2.19 install
|
|
107
|
+
npx.cmd --yes @danhachuel/thunderbolt@0.2.19 doctor
|
|
108
|
+
npx.cmd --yes @danhachuel/thunderbolt@0.2.19
|
|
108
109
|
```
|
|
109
110
|
|
|
110
111
|
Como alternativa, pode permitir scripts para o seu utilizador:
|
|
@@ -173,6 +174,14 @@ Na aba **Upload**, configure primeiro o **YouTube OAuth Client ID** e o **YouTub
|
|
|
173
174
|
|
|
174
175
|
Use **Autorizar fallback OAuth** apenas se precisar de uma autorização separada para o caminho de redundância. Os resultados guardam no histórico local qual mecanismo foi utilizado e as tentativas realizadas, sem guardar segredos.
|
|
175
176
|
|
|
177
|
+
## MCP e integrações externas opcionais
|
|
178
|
+
|
|
179
|
+
A aba **MCP** funciona como um catálogo local de clientes externos. Ela lista **Short Video Maker**, **AutoVio**, **OpenMontage** e **OpenCut** com os respectivos links oficiais, protocolo/capacidade conhecida, porta local editável e toggle **Activo**. Os repositórios não são clonados, instalados ou incluídos no pacote npm do Thunderbolt.
|
|
180
|
+
|
|
181
|
+
As portas iniciais são `3123` para Short Video Maker, `3001` para a API backend do AutoVio, `8000` como referência editável para OpenMontage — que não documenta uma porta HTTP padrão — e `8787` para a API do OpenCut, cujo frontend de desenvolvimento usa `5173`. A detecção é passiva: apenas consulta `localhost` e nunca inicia processos externos. Activar uma integração guarda a preferência local; não substitui a instalação/configuração do serviço externo.
|
|
182
|
+
|
|
183
|
+
A mesma aba permite **Guardar skill localmente**, copiando `moneyprinterturbo-video.md` para `storage/skills/`, e **Descarregar skill .md** através do navegador. A skill é um recurso separado dos quatro repositórios externos.
|
|
184
|
+
|
|
176
185
|
## TikTok
|
|
177
186
|
|
|
178
187
|
A subaba **Vídeos**, dentro de **Novo vídeo**, mostra o backlog e permite iniciar/parar tarefas; deixou de existir como botão principal na barra lateral.
|
package/app/main.py
CHANGED
|
@@ -18,10 +18,66 @@ from hermes_ui.domain import STAGES, create_batch, create_channel, create_tasks_
|
|
|
18
18
|
from hermes_ui.storage import BLUEPRINTS, ensure_storage, list_blueprint_files, load_blueprint_file, now, read_json, write_json
|
|
19
19
|
from hermes_ui.blueprints import create_blueprint_from_link, list_branding_files, save_generated_blueprint
|
|
20
20
|
from hermes_ui.metadata_cleaner import build_description, clean_video_metadata, list_edit_records, metadata_manifest, normalize_tags, save_edit_record, store_external_video
|
|
21
|
+
from hermes_ui.mcp import detect_local_service, install_skill_locally, load_integrations, read_packaged_skill, update_integration
|
|
21
22
|
from integrations.platforms import IntegrationResult, TikTokAdapter, YouTubeAdapter
|
|
22
23
|
from integrations.local_runtime import MoneyPrinterRuntime
|
|
23
24
|
from integrations.moneyprinter_config import sync_moneyprinter_config
|
|
24
25
|
|
|
26
|
+
VIDEO_LANGUAGE_OPTIONS = [
|
|
27
|
+
"00 – Apenas Música de Fundo (Sem Falas)",
|
|
28
|
+
"01 – Inglês",
|
|
29
|
+
"02 – Norueguês",
|
|
30
|
+
"03 – Dinamarquês",
|
|
31
|
+
"04 – Sueco",
|
|
32
|
+
"05 – Holandês",
|
|
33
|
+
"06 – Alemão",
|
|
34
|
+
"07 – Luxemburguês",
|
|
35
|
+
"08 – Finlandês",
|
|
36
|
+
"09 – Hebraico",
|
|
37
|
+
"10 – Japonês",
|
|
38
|
+
"11 – Árabe (Golfo)",
|
|
39
|
+
"12 – Islandês",
|
|
40
|
+
"13 – Espanhol (Espanha)",
|
|
41
|
+
"14 – Francês",
|
|
42
|
+
"15 – Italiano",
|
|
43
|
+
"16 – Coreano",
|
|
44
|
+
"16 – Irlandês",
|
|
45
|
+
"17 – Estoniano",
|
|
46
|
+
"18 – Grego",
|
|
47
|
+
"19 – Esloveno",
|
|
48
|
+
"20 – Polonês",
|
|
49
|
+
"21 – Tcheco",
|
|
50
|
+
"22 – Lituano",
|
|
51
|
+
"23 – Português (Portugal)",
|
|
52
|
+
"24 – Eslovaco",
|
|
53
|
+
"25 – Letão",
|
|
54
|
+
"26 – Ucraniano",
|
|
55
|
+
"27 – Húngaro",
|
|
56
|
+
"28 – Afrikaans",
|
|
57
|
+
"29 – Turco",
|
|
58
|
+
"30 – Romeno",
|
|
59
|
+
"31 – Russo",
|
|
60
|
+
"32 – Croata",
|
|
61
|
+
"33 – Árabe (Magreb)",
|
|
62
|
+
"34 – Sérvio",
|
|
63
|
+
"35 – Búlgaro",
|
|
64
|
+
"36 – Português (Brasil)",
|
|
65
|
+
"37 – Cantonês",
|
|
66
|
+
"38 – Persa (Farsi)",
|
|
67
|
+
"39 – Mandarim",
|
|
68
|
+
"40 – Malaio",
|
|
69
|
+
"41 – Espanhol (LatAm)",
|
|
70
|
+
"42 – Vietnamita",
|
|
71
|
+
"43 – Filipino (Tagalog)",
|
|
72
|
+
"44 – Indonésio",
|
|
73
|
+
"45 – Malayalam",
|
|
74
|
+
"46 – Tailandês",
|
|
75
|
+
"47 – Télugo",
|
|
76
|
+
"48 – Tamil",
|
|
77
|
+
"49 – Bengali",
|
|
78
|
+
"50 – Hausa",
|
|
79
|
+
]
|
|
80
|
+
|
|
25
81
|
ensure_storage()
|
|
26
82
|
st.set_page_config(page_title="Thunderbolt", page_icon="T", layout="wide", initial_sidebar_state="expanded")
|
|
27
83
|
|
|
@@ -338,7 +394,15 @@ def render_new_video():
|
|
|
338
394
|
with st.form("new_video_form"):
|
|
339
395
|
topic = st.text_area("Tópico ou briefing", placeholder="Ex.: A história pouco conhecida por trás de...")
|
|
340
396
|
quantity = st.number_input("Quantidade", min_value=1, max_value=100, value=1, disabled=mode != "same_channel")
|
|
341
|
-
|
|
397
|
+
legacy_language = st.session_state.get("video_language")
|
|
398
|
+
legacy_language_map = {
|
|
399
|
+
"Português": "36 – Português (Brasil)",
|
|
400
|
+
"English": "01 – Inglês",
|
|
401
|
+
"Español": "41 – Espanhol (LatAm)",
|
|
402
|
+
}
|
|
403
|
+
if legacy_language not in VIDEO_LANGUAGE_OPTIONS:
|
|
404
|
+
st.session_state["video_language"] = legacy_language_map.get(legacy_language, VIDEO_LANGUAGE_OPTIONS[0])
|
|
405
|
+
language = st.selectbox("Idioma", VIDEO_LANGUAGE_OPTIONS, key="video_language")
|
|
342
406
|
fmt = st.selectbox("Formato", ["wide", "shorts", "music"])
|
|
343
407
|
style = st.selectbox("Estilo wide", ["pexels", "full_ia"])
|
|
344
408
|
submitted = st.form_submit_button("Criar tarefas", type="primary")
|
|
@@ -620,6 +684,77 @@ def render_settings():
|
|
|
620
684
|
st.warning(f"Configurações locais guardadas, mas não foi possível sincronizar config.toml: {exc}")
|
|
621
685
|
|
|
622
686
|
|
|
687
|
+
def render_mcp():
|
|
688
|
+
st.title("MCP")
|
|
689
|
+
st.caption("Clientes opcionais para serviços externos. Os repositórios não são instalados nem incluídos no pacote Thunderbolt.")
|
|
690
|
+
st.info("Activar uma integração guarda apenas a preferência local. O Thunderbolt não inicia processos externos automaticamente; a detecção verifica passivamente se existe um serviço já disponível na porta configurada.")
|
|
691
|
+
|
|
692
|
+
integrations = load_integrations()
|
|
693
|
+
for integration in integrations:
|
|
694
|
+
integration_id = integration["id"]
|
|
695
|
+
with st.container(border=True):
|
|
696
|
+
header_cols = st.columns([2.6, 1.15, 1.65, 1.2])
|
|
697
|
+
with header_cols[0]:
|
|
698
|
+
st.write(f"**{integration['name']}**")
|
|
699
|
+
st.caption(f"{integration['protocol']} · {integration['description']}")
|
|
700
|
+
st.markdown(f"[Abrir repositório oficial]({integration['repository']})")
|
|
701
|
+
with header_cols[1]:
|
|
702
|
+
port = st.number_input(
|
|
703
|
+
"Porta",
|
|
704
|
+
min_value=1,
|
|
705
|
+
max_value=65535,
|
|
706
|
+
value=int(integration.get("port", 8000)),
|
|
707
|
+
step=1,
|
|
708
|
+
key=f"mcp_port_{integration_id}",
|
|
709
|
+
)
|
|
710
|
+
with header_cols[2]:
|
|
711
|
+
status = detect_local_service({**integration, "port": port})
|
|
712
|
+
if status["available"]:
|
|
713
|
+
st.success("Disponível")
|
|
714
|
+
else:
|
|
715
|
+
st.caption("Não detectado")
|
|
716
|
+
st.caption(status["message"])
|
|
717
|
+
with header_cols[3]:
|
|
718
|
+
active = st.toggle("Activo", value=bool(integration.get("active", False)), key=f"mcp_active_{integration_id}")
|
|
719
|
+
if active != bool(integration.get("active", False)):
|
|
720
|
+
update_integration(integration_id, active=active)
|
|
721
|
+
st.rerun()
|
|
722
|
+
|
|
723
|
+
st.caption(integration.get("endpoint_note", "Porta editável para o serviço local."))
|
|
724
|
+
if st.button("Guardar porta", key=f"mcp_save_port_{integration_id}", use_container_width=True):
|
|
725
|
+
update_integration(integration_id, port=int(port))
|
|
726
|
+
st.success(f"Porta de {integration['name']} guardada: {int(port)}")
|
|
727
|
+
st.rerun()
|
|
728
|
+
|
|
729
|
+
st.divider()
|
|
730
|
+
st.subheader("Skill MoneyPrinterTurbo")
|
|
731
|
+
st.caption("A skill anexada pode ser guardada na pasta local do Thunderbolt ou descarregada como ficheiro Markdown. Nenhum dos quatro repositórios externos é copiado para o pacote.")
|
|
732
|
+
skill_cols = st.columns(2)
|
|
733
|
+
with skill_cols[0]:
|
|
734
|
+
if st.button("Guardar skill localmente", type="primary", use_container_width=True, key="mcp_install_mpt_skill"):
|
|
735
|
+
try:
|
|
736
|
+
destination = install_skill_locally()
|
|
737
|
+
st.success(f"Skill guardada em `{destination}`")
|
|
738
|
+
except (FileNotFoundError, OSError) as exc:
|
|
739
|
+
st.error(f"Não foi possível guardar a skill: {exc}")
|
|
740
|
+
with skill_cols[1]:
|
|
741
|
+
try:
|
|
742
|
+
skill_data = read_packaged_skill()
|
|
743
|
+
except FileNotFoundError:
|
|
744
|
+
skill_data = None
|
|
745
|
+
if skill_data is not None:
|
|
746
|
+
st.download_button(
|
|
747
|
+
"Descarregar skill .md",
|
|
748
|
+
data=skill_data,
|
|
749
|
+
file_name="moneyprinterturbo-video.md",
|
|
750
|
+
mime="text/markdown",
|
|
751
|
+
use_container_width=True,
|
|
752
|
+
key="mcp_download_mpt_skill",
|
|
753
|
+
)
|
|
754
|
+
else:
|
|
755
|
+
st.warning("A skill ainda não está disponível nesta instalação.")
|
|
756
|
+
|
|
757
|
+
|
|
623
758
|
def render_metadata_cleaner():
|
|
624
759
|
st.title("Limpador de metadado")
|
|
625
760
|
st.caption("Limpeza e edição de metadados para vídeos de terceiros que já estão prontos.")
|
|
@@ -756,6 +891,7 @@ def main():
|
|
|
756
891
|
("Canais", ":material/ondemand_video:", "Canais"),
|
|
757
892
|
("Novo vídeo", ":material/add_circle:", "Novo vídeo"),
|
|
758
893
|
("Upload", ":material/cloud_upload:", "Upload"),
|
|
894
|
+
("MCP", ":material/hub:", "MCP"),
|
|
759
895
|
("Limpador de metadado", ":material/edit_note:", "Limpador de metadado"),
|
|
760
896
|
("Configurações", ":material/settings:", "Configurações"),
|
|
761
897
|
]
|
|
@@ -777,6 +913,7 @@ def main():
|
|
|
777
913
|
"Canais": render_channels,
|
|
778
914
|
"Novo vídeo": render_new_video,
|
|
779
915
|
"Upload": render_upload,
|
|
916
|
+
"MCP": render_mcp,
|
|
780
917
|
"Limpador de metadado": render_metadata_cleaner,
|
|
781
918
|
"Configurações": render_settings,
|
|
782
919
|
}
|
package/hermes_ui/mcp.py
ADDED
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
from typing import Any
|
|
5
|
+
|
|
6
|
+
import requests
|
|
7
|
+
|
|
8
|
+
from . import storage
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
SKILL_FILENAME = "moneyprinterturbo-video.md"
|
|
12
|
+
|
|
13
|
+
MCP_DEFAULTS: list[dict[str, Any]] = [
|
|
14
|
+
{
|
|
15
|
+
"id": "short-video-maker",
|
|
16
|
+
"name": "Short Video Maker",
|
|
17
|
+
"repository": "https://github.com/gyoridavid/short-video-maker",
|
|
18
|
+
"protocol": "MCP + REST",
|
|
19
|
+
"description": "Servidor externo para criação de vídeos curtos, com MCP e API REST.",
|
|
20
|
+
"port": 3123,
|
|
21
|
+
"active": False,
|
|
22
|
+
"endpoint_note": "Porta documentada pelo projecto: 3123.",
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
"id": "autovio",
|
|
26
|
+
"name": "AutoVio",
|
|
27
|
+
"repository": "https://github.com/Auto-Vio/autovio",
|
|
28
|
+
"protocol": "MCP + REST",
|
|
29
|
+
"description": "Pipeline externo de vídeo com API REST e servidor MCP separado.",
|
|
30
|
+
"port": 3001,
|
|
31
|
+
"active": False,
|
|
32
|
+
"endpoint_note": "Porta padrão da API backend documentada pelo projecto: 3001.",
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
"id": "openmontage",
|
|
36
|
+
"name": "OpenMontage",
|
|
37
|
+
"repository": "https://github.com/calesthio/OpenMontage",
|
|
38
|
+
"protocol": "Agente local",
|
|
39
|
+
"description": "Sistema externo de produção agentic de vídeo; o README não documenta um servidor MCP/HTTP padrão.",
|
|
40
|
+
"port": 8000,
|
|
41
|
+
"active": False,
|
|
42
|
+
"endpoint_note": "Porta editável de referência; o projecto não documenta uma porta local padrão.",
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
"id": "opencut",
|
|
46
|
+
"name": "OpenCut",
|
|
47
|
+
"repository": "https://github.com/opencut-app/opencut",
|
|
48
|
+
"protocol": "API em desenvolvimento",
|
|
49
|
+
"description": "Editor externo; o README actual indica API/MCP em desenvolvimento e documenta um servidor API local.",
|
|
50
|
+
"port": 8787,
|
|
51
|
+
"active": False,
|
|
52
|
+
"endpoint_note": "Porta padrão da API documentada pelo projecto: 8787; frontend usa 5173.",
|
|
53
|
+
},
|
|
54
|
+
]
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def _merge_defaults(saved: Any) -> list[dict[str, Any]]:
|
|
58
|
+
saved_by_id = {
|
|
59
|
+
str(item.get("id")): item
|
|
60
|
+
for item in (saved if isinstance(saved, list) else [])
|
|
61
|
+
if isinstance(item, dict) and item.get("id")
|
|
62
|
+
}
|
|
63
|
+
merged: list[dict[str, Any]] = []
|
|
64
|
+
for default in MCP_DEFAULTS:
|
|
65
|
+
item = {**default, **saved_by_id.get(default["id"], {})}
|
|
66
|
+
try:
|
|
67
|
+
item["port"] = max(1, min(65535, int(item.get("port", default["port"]))))
|
|
68
|
+
except (TypeError, ValueError):
|
|
69
|
+
item["port"] = default["port"]
|
|
70
|
+
item["active"] = bool(item.get("active", False))
|
|
71
|
+
merged.append(item)
|
|
72
|
+
return merged
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def load_integrations() -> list[dict[str, Any]]:
|
|
76
|
+
return _merge_defaults(storage.read_json("mcp_integrations.json", MCP_DEFAULTS))
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def save_integrations(integrations: list[dict[str, Any]]) -> None:
|
|
80
|
+
cleaned: list[dict[str, Any]] = []
|
|
81
|
+
allowed = {item["id"] for item in MCP_DEFAULTS}
|
|
82
|
+
for item in integrations:
|
|
83
|
+
if not isinstance(item, dict) or item.get("id") not in allowed:
|
|
84
|
+
continue
|
|
85
|
+
copy = dict(item)
|
|
86
|
+
try:
|
|
87
|
+
copy["port"] = max(1, min(65535, int(copy.get("port", 1))))
|
|
88
|
+
except (TypeError, ValueError):
|
|
89
|
+
continue
|
|
90
|
+
copy["active"] = bool(copy.get("active", False))
|
|
91
|
+
cleaned.append(copy)
|
|
92
|
+
storage.write_json("mcp_integrations.json", _merge_defaults(cleaned))
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def update_integration(integration_id: str, **updates: Any) -> list[dict[str, Any]]:
|
|
96
|
+
integrations = load_integrations()
|
|
97
|
+
for item in integrations:
|
|
98
|
+
if item.get("id") == integration_id:
|
|
99
|
+
item.update(updates)
|
|
100
|
+
break
|
|
101
|
+
save_integrations(integrations)
|
|
102
|
+
return load_integrations()
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def detect_local_service(integration: dict[str, Any], timeout: float = 0.35) -> dict[str, Any]:
|
|
106
|
+
"""Detecta apenas um serviço local já iniciado; nunca inicia ou instala processos."""
|
|
107
|
+
port = integration.get("port")
|
|
108
|
+
try:
|
|
109
|
+
port = int(port)
|
|
110
|
+
except (TypeError, ValueError):
|
|
111
|
+
return {"available": False, "message": "Porta inválida."}
|
|
112
|
+
try:
|
|
113
|
+
response = requests.get(f"http://127.0.0.1:{port}/", timeout=timeout)
|
|
114
|
+
return {
|
|
115
|
+
"available": True,
|
|
116
|
+
"message": f"Serviço respondeu em localhost:{port} (HTTP {response.status_code}).",
|
|
117
|
+
}
|
|
118
|
+
except requests.RequestException:
|
|
119
|
+
return {"available": False, "message": f"Nenhum serviço detectado em localhost:{port}."}
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
def skill_source_path() -> Path:
|
|
123
|
+
return Path(__file__).resolve().parents[1] / "seed" / "skills" / SKILL_FILENAME
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
def skill_destination_path() -> Path:
|
|
127
|
+
return storage.STORAGE / "skills" / SKILL_FILENAME
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
def install_skill_locally(*, overwrite: bool = True) -> Path:
|
|
131
|
+
source = skill_source_path()
|
|
132
|
+
if not source.exists():
|
|
133
|
+
raise FileNotFoundError("A skill MoneyPrinterTurbo não está disponível no pacote local.")
|
|
134
|
+
destination = skill_destination_path()
|
|
135
|
+
destination.parent.mkdir(parents=True, exist_ok=True)
|
|
136
|
+
if overwrite or not destination.exists():
|
|
137
|
+
destination.write_bytes(source.read_bytes())
|
|
138
|
+
return destination
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
def read_packaged_skill() -> bytes:
|
|
142
|
+
source = skill_source_path()
|
|
143
|
+
if not source.exists():
|
|
144
|
+
raise FileNotFoundError("A skill MoneyPrinterTurbo não está disponível no pacote local.")
|
|
145
|
+
return source.read_bytes()
|
package/hermes_ui/storage.py
CHANGED
|
@@ -21,6 +21,48 @@ DEFAULTS: dict[str, Any] = {
|
|
|
21
21
|
"batches.json": [],
|
|
22
22
|
"uploads.json": [],
|
|
23
23
|
"metadata_edits.json": [],
|
|
24
|
+
"mcp_integrations.json": [
|
|
25
|
+
{
|
|
26
|
+
"id": "short-video-maker",
|
|
27
|
+
"name": "Short Video Maker",
|
|
28
|
+
"repository": "https://github.com/gyoridavid/short-video-maker",
|
|
29
|
+
"protocol": "MCP + REST",
|
|
30
|
+
"description": "Servidor externo para criação de vídeos curtos, com MCP e API REST.",
|
|
31
|
+
"port": 3123,
|
|
32
|
+
"active": False,
|
|
33
|
+
"endpoint_note": "Porta documentada pelo projecto: 3123.",
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
"id": "autovio",
|
|
37
|
+
"name": "AutoVio",
|
|
38
|
+
"repository": "https://github.com/Auto-Vio/autovio",
|
|
39
|
+
"protocol": "MCP + REST",
|
|
40
|
+
"description": "Pipeline externo de vídeo com API REST e servidor MCP separado.",
|
|
41
|
+
"port": 3001,
|
|
42
|
+
"active": False,
|
|
43
|
+
"endpoint_note": "Porta padrão da API backend documentada pelo projecto: 3001.",
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
"id": "openmontage",
|
|
47
|
+
"name": "OpenMontage",
|
|
48
|
+
"repository": "https://github.com/calesthio/OpenMontage",
|
|
49
|
+
"protocol": "Agente local",
|
|
50
|
+
"description": "Sistema externo de produção agentic de vídeo; não documenta um servidor MCP/HTTP padrão.",
|
|
51
|
+
"port": 8000,
|
|
52
|
+
"active": False,
|
|
53
|
+
"endpoint_note": "Porta editável de referência; o projecto não documenta uma porta local padrão.",
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
"id": "opencut",
|
|
57
|
+
"name": "OpenCut",
|
|
58
|
+
"repository": "https://github.com/opencut-app/opencut",
|
|
59
|
+
"protocol": "API em desenvolvimento",
|
|
60
|
+
"description": "Editor externo; a documentação actual indica API/MCP em desenvolvimento.",
|
|
61
|
+
"port": 8787,
|
|
62
|
+
"active": False,
|
|
63
|
+
"endpoint_note": "Porta padrão da API documentada pelo projecto: 8787; frontend usa 5173.",
|
|
64
|
+
},
|
|
65
|
+
],
|
|
24
66
|
"settings.json": {
|
|
25
67
|
"port": 3030,
|
|
26
68
|
"moneyprinter_path": "",
|
|
@@ -156,7 +198,7 @@ def seed_blueprints() -> None:
|
|
|
156
198
|
|
|
157
199
|
|
|
158
200
|
def ensure_storage() -> None:
|
|
159
|
-
for path in [STATE, BLUEPRINTS / "canais", BLUEPRINTS / "nichos", BLUEPRINTS / "importados", BLUEPRINTS / "brandings", STORAGE / "brand", STORAGE / "scripts", STORAGE / "thumbnails", STORAGE / "videos", STORAGE / "artifacts", STORAGE / "metadata_cleaner", STORAGE / "metadata_cleaner" / "outputs"]:
|
|
201
|
+
for path in [STATE, BLUEPRINTS / "canais", BLUEPRINTS / "nichos", BLUEPRINTS / "importados", BLUEPRINTS / "brandings", STORAGE / "brand", STORAGE / "scripts", STORAGE / "thumbnails", STORAGE / "videos", STORAGE / "artifacts", STORAGE / "skills", STORAGE / "metadata_cleaner", STORAGE / "metadata_cleaner" / "outputs"]:
|
|
160
202
|
path.mkdir(parents=True, exist_ok=True)
|
|
161
203
|
seed_blueprints()
|
|
162
204
|
for filename, default in DEFAULTS.items():
|