@danhachuel/thunderbolt 0.2.17 → 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.
@@ -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()
@@ -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():