@danhachuel/thunderbolt 0.3.96 → 0.3.97

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/app/main.py CHANGED
@@ -4,6 +4,7 @@ import hashlib
4
4
  import json
5
5
  import mimetypes
6
6
  import re
7
+ import time
7
8
  from contextlib import nullcontext
8
9
  from datetime import date, datetime, timezone
9
10
  import sys
@@ -49,6 +50,7 @@ from hermes_ui.logs import list_logs, logs_to_rows
49
50
  from hermes_ui.languages import LANGUAGE_CODES, VIDEO_LANGUAGE_CODES, LANGUAGE_FLAG_DATA_URIS, language_code, language_label, ui_language_menu_label, ui_text, video_language_label, video_language_options
50
51
  from hermes_ui.api_key_tests import test_apify_credentials, test_influencer_database, test_innertube_api_key, test_kaggle_credentials, test_material_source_credentials, test_media_provider_card, test_nano_banana_credentials, test_postiz_credentials, test_telegram_credentials, test_tiktok_credentials, test_upload_post_credentials, test_voice_provider
51
52
  from hermes_ui.tutorials import tutorial_body, tutorial_caption, tutorial_title
53
+ from hermes_ui.update_manager import check_version, update_to_latest
52
54
 
53
55
  from hermes_ui.script_documents import list_script_documents, read_script_document, save_script_document, script_storage_path
54
56
  from hermes_ui.script_generation import generate_script_document
@@ -1269,6 +1271,48 @@ def render_channel_edit_form(channel: dict, youtube_account_ids: list[str], yout
1269
1271
 
1270
1272
  def render_dashboard():
1271
1273
  ui_language = current_ui_language()
1274
+ update_area, version_area = st.columns([1.45, 4.55])
1275
+ with update_area:
1276
+ st.markdown(
1277
+ """
1278
+ <style>
1279
+ div[data-testid="stButton"] button[kind="primary"] {
1280
+ background: linear-gradient(135deg, #2563eb 0%, #7c3aed 100%);
1281
+ color: #ffffff;
1282
+ border: 1px solid #8b5cf6;
1283
+ font-weight: 700;
1284
+ box-shadow: 0 8px 20px rgba(79, 70, 229, 0.28);
1285
+ }
1286
+ div[data-testid="stButton"] button[kind="primary"]:hover {
1287
+ border-color: #c4b5fd;
1288
+ filter: brightness(1.08);
1289
+ }
1290
+ </style>
1291
+ """,
1292
+ unsafe_allow_html=True,
1293
+ )
1294
+ if st.button("Atualizar Versão", key="home_update_version", use_container_width=True, type="primary", icon=":material/system_update:"):
1295
+ with st.spinner("A instalar a versão mais recente…"):
1296
+ st.session_state["home_update_result"] = update_to_latest(APP_VERSION)
1297
+ with version_area:
1298
+ cache_key = "home_update_version_check"
1299
+ checked_at_key = "home_update_version_checked_at"
1300
+ if not st.session_state.get(cache_key) or time.monotonic() - float(st.session_state.get(checked_at_key, 0)) > 300:
1301
+ st.session_state[cache_key] = check_version(APP_VERSION)
1302
+ st.session_state[checked_at_key] = time.monotonic()
1303
+ version_status = st.session_state[cache_key]
1304
+ if version_status.update_available:
1305
+ st.info(f"Nova versão disponível: {version_status.latest_version}. A versão actual é {APP_VERSION or 'desconhecida'}.")
1306
+ elif version_status.error:
1307
+ st.caption(f"Versão actual: {APP_VERSION or 'desconhecida'} · verificação de actualização indisponível.")
1308
+ else:
1309
+ st.caption(f"Versão actual: {APP_VERSION or 'desconhecida'} · já está actualizada ({version_status.latest_version}).")
1310
+ update_result = st.session_state.get("home_update_result")
1311
+ if update_result is not None:
1312
+ if update_result.ok:
1313
+ st.success(update_result.message)
1314
+ else:
1315
+ st.error(update_result.message)
1272
1316
  st.title("Thunderbolt")
1273
1317
  st.caption(ui_text("Interface local para operação e automação de conteúdo faceless", ui_language))
1274
1318
  summary = pipeline_summary()
@@ -0,0 +1,108 @@
1
+ """Verificação e actualização local da versão distribuída pelo NPM.
2
+
3
+ O módulo não recebe nem manipula credenciais. A instalação só é iniciada após o
4
+ clique explícito do utilizador na interface local.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ import os
10
+ import subprocess
11
+ from dataclasses import dataclass
12
+ from typing import Any, Callable
13
+
14
+ import requests
15
+
16
+
17
+ PACKAGE_NAME = "@danhachuel/thunderbolt"
18
+ REGISTRY_URL = "https://registry.npmjs.org/@danhachuel/thunderbolt/latest"
19
+ UPDATE_TIMEOUT_SECONDS = 20 * 60
20
+
21
+
22
+ @dataclass(frozen=True)
23
+ class VersionCheck:
24
+ """Version information displayed on the local home page."""
25
+
26
+ current_version: str
27
+ latest_version: str = ""
28
+ error: str = ""
29
+
30
+ @property
31
+ def update_available(self) -> bool:
32
+ return bool(self.latest_version and self.current_version and self.latest_version != self.current_version)
33
+
34
+
35
+ @dataclass(frozen=True)
36
+ class UpdateResult:
37
+ """Sanitised result of an explicit local package update request."""
38
+
39
+ ok: bool
40
+ latest_version: str = ""
41
+ message: str = ""
42
+
43
+
44
+ def latest_package_version(*, timeout: int = 8, get: Callable[..., Any] = requests.get) -> str:
45
+ """Return the latest public package version without sending local configuration."""
46
+ response = get(REGISTRY_URL, timeout=timeout)
47
+ response.raise_for_status()
48
+ payload = response.json()
49
+ version = str(payload.get("version") or "").strip() if isinstance(payload, dict) else ""
50
+ if not version:
51
+ raise ValueError("O registry NPM não devolveu uma versão válida.")
52
+ return version
53
+
54
+
55
+ def check_version(current_version: str, *, timeout: int = 8, get: Callable[..., Any] = requests.get) -> VersionCheck:
56
+ """Fetch only the package metadata needed for the version badge."""
57
+ current = str(current_version or "").strip()
58
+ try:
59
+ return VersionCheck(current_version=current, latest_version=latest_package_version(timeout=timeout, get=get))
60
+ except (requests.RequestException, ValueError) as exc:
61
+ return VersionCheck(current_version=current, error=f"Não foi possível verificar actualizações agora ({type(exc).__name__}).")
62
+
63
+
64
+ def update_command() -> list[str]:
65
+ """Build the same cross-platform install command documented for Thunderbolt."""
66
+ executable = "npx.cmd" if os.name == "nt" else "npx"
67
+ return [executable, "--yes", "--prefer-online", PACKAGE_NAME, "install"]
68
+
69
+
70
+ def update_to_latest(
71
+ current_version: str,
72
+ *,
73
+ timeout: int = UPDATE_TIMEOUT_SECONDS,
74
+ get: Callable[..., Any] = requests.get,
75
+ run: Callable[..., Any] = subprocess.run,
76
+ ) -> UpdateResult:
77
+ """Install the latest package only after an explicit UI action.
78
+
79
+ The running local process keeps its current code until it is restarted. No
80
+ subprocess output is returned to the UI, preventing accidental display of
81
+ environment values from third-party installers.
82
+ """
83
+ status = check_version(current_version, get=get)
84
+ if status.error:
85
+ return UpdateResult(False, message=status.error)
86
+ if not status.update_available:
87
+ return UpdateResult(True, latest_version=status.latest_version, message="O Thunderbolt já está na versão mais recente.")
88
+ try:
89
+ completed = run(
90
+ update_command(),
91
+ stdin=subprocess.DEVNULL,
92
+ stdout=subprocess.DEVNULL,
93
+ stderr=subprocess.DEVNULL,
94
+ timeout=timeout,
95
+ check=False,
96
+ )
97
+ except (OSError, subprocess.TimeoutExpired) as exc:
98
+ return UpdateResult(False, latest_version=status.latest_version, message=f"Não foi possível concluir a actualização ({type(exc).__name__}).")
99
+ if int(getattr(completed, "returncode", 1)) != 0:
100
+ return UpdateResult(False, latest_version=status.latest_version, message="A actualização não foi concluída. Feche processos Thunderbolt em execução e tente novamente.")
101
+ return UpdateResult(
102
+ True,
103
+ latest_version=status.latest_version,
104
+ message=(
105
+ f"A versão {status.latest_version} foi instalada. Reinicie o Thunderbolt para abrir a versão nova; "
106
+ "os dados e configurações locais foram preservados."
107
+ ),
108
+ )
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@danhachuel/thunderbolt",
3
- "version": "0.3.96",
3
+ "version": "0.3.97",
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",