@danhachuel/thunderbolt 0.4.88 → 0.4.90

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
@@ -1,12 +1,20 @@
1
1
  import os
2
2
  import sys
3
+ import io
3
4
 
4
5
  os.environ["PYTHONIOENCODING"] = "utf-8"
5
6
  os.environ["PYTHONUTF8"] = "1"
6
- if hasattr(sys.stdout, "reconfigure"):
7
- sys.stdout.reconfigure(encoding="utf-8", errors="replace")
8
- if hasattr(sys.stderr, "reconfigure"):
9
- sys.stderr.reconfigure(encoding="utf-8", errors="replace")
7
+
8
+ def _force_utf8_stream(stream: object) -> object:
9
+ buffer = getattr(stream, "buffer", None)
10
+ encoding = str(getattr(stream, "encoding", "") or "").lower().replace("-", "_")
11
+ if buffer is not None and encoding != "utf_8":
12
+ return io.TextIOWrapper(buffer, encoding="utf-8", errors="replace", line_buffering=True)
13
+ return stream
14
+
15
+ _original_stdout, _original_stderr = sys.stdout, sys.stderr
16
+ sys.stdout = _force_utf8_stream(sys.stdout)
17
+ sys.stderr = _force_utf8_stream(sys.stderr)
10
18
 
11
19
  import hashlib
12
20
  import json
@@ -1303,7 +1311,6 @@ def render_channel_edit_form(channel: dict, youtube_account_ids: list[str], yout
1303
1311
  edited_language = st.selectbox("Idioma do roteiro", language_options, index=language_options.index(normalize_video_language(channel.get("language") or "pt")) if normalize_video_language(channel.get("language") or "pt") in language_options else 0, format_func=video_language_label)
1304
1312
  edited_style = st.selectbox("Fonte do vídeo", WIDE_STYLE_OPTIONS, index=WIDE_STYLE_OPTIONS.index(channel_video_source_value(channel.get("style_wide"))) if channel_video_source_value(channel.get("style_wide")) in WIDE_STYLE_OPTIONS else 0)
1305
1313
  edited_aspect = st.selectbox("Proporção do vídeo", CHANNEL_ASPECT_RATIO_OPTIONS, index=CHANNEL_ASPECT_RATIO_OPTIONS.index(str(channel.get("video_aspect_ratio") or "Landscape 16:9")) if str(channel.get("video_aspect_ratio") or "Landscape 16:9") in CHANNEL_ASPECT_RATIO_OPTIONS else 0)
1306
- edited_format = st.selectbox("Formato", CHANNEL_FORMAT_OPTIONS, index=CHANNEL_FORMAT_OPTIONS.index(str(channel.get("format") or "wide")) if str(channel.get("format") or "wide") in CHANNEL_FORMAT_OPTIONS else 0)
1307
1314
  edited_niche = st.text_input("Nicho", value=str(channel.get("niche") or channel_niche_label(channel) if channel_niche_label(channel) != "SEM NICHO CONFIGURADO" else "") )
1308
1315
  with edit_cols[1]:
1309
1316
  edited_blueprint = st.selectbox("Blueprint Padrão", blueprint_ids, index=blueprint_ids.index(current_blueprint) if current_blueprint in blueprint_ids else 0, format_func=lambda item: blueprint_labels.get(item, item or "Sem Blueprint padrão"))
@@ -1325,7 +1332,7 @@ def render_channel_edit_form(channel: dict, youtube_account_ids: list[str], yout
1325
1332
  else:
1326
1333
  update_channel(channel_id, {
1327
1334
  "name": edited_name.strip(), "url": edited_url.strip(), "handle": edited_handle.strip(), "language": edited_language,
1328
- "style_wide": channel_video_source_storage(edited_style), "video_aspect_ratio": edited_aspect, "format": edited_format,
1335
+ "style_wide": channel_video_source_storage(edited_style), "video_aspect_ratio": edited_aspect,
1329
1336
  "niche": edited_niche.strip(), "reference_channels": [item.strip() for item in re.split(r"[,|]", edited_niche) if item.strip()],
1330
1337
  "default_blueprint_id": edited_blueprint.strip(), "blueprint_id": edited_blueprint.strip(),
1331
1338
  "default_voice": edited_voice.strip(), "voice": edited_voice.strip(),
@@ -1996,7 +2003,6 @@ def render_tiktok_channels():
1996
2003
  language = st.selectbox("Idioma do roteiro", VIDEO_LANGUAGE_SELECTION_OPTIONS, format_func=video_language_label, key="tiktok_import_language")
1997
2004
  source_value = st.selectbox("Fonte do vídeo", WIDE_STYLE_OPTIONS, key="tiktok_import_source")
1998
2005
  aspect_value = st.selectbox("Proporção do vídeo", CHANNEL_ASPECT_RATIO_OPTIONS, index=1, key="tiktok_import_aspect")
1999
- format_value = st.selectbox("Formato", CHANNEL_FORMAT_OPTIONS, index=1, key="tiktok_import_format")
2000
2006
  niche = st.text_input("Nicho", key="tiktok_import_niche")
2001
2007
  prompt = st.selectbox("Prompt Master padrão", prompt_ids, format_func=lambda item: prompt_labels.get(item, item), key="tiktok_import_prompt")
2002
2008
  voice = st.selectbox("Narrador/Voz Padrão", voice_options, format_func=lambda item: item or "Sem voz padrão", key="tiktok_import_voice")
@@ -2007,7 +2013,7 @@ def render_tiktok_channels():
2007
2013
  if not valid_hhmm(automation_time):
2008
2014
  st.error("O horário diário deve estar no formato HH:MM.")
2009
2015
  st.stop()
2010
- channel = create_channel(name.strip(), url.strip(), {"platform": "tiktok", "handle": handle.strip(), "language": language, "niche": niche.strip(), "description": description.strip(), "default_prompt_master": prompt, "prompt_master": prompt, "default_voice": voice, "voice": voice, "style_wide": channel_video_source_storage(source_value), "video_aspect_ratio": aspect_value, "format": format_value, "automation_on": automation_on, "automation_time": automation_time.strip(), "metrics_source": "tiktok_public_page", "subscriber_count": imported.get("subscriber_count"), "video_count": imported.get("video_count")})
2016
+ channel = create_channel(name.strip(), url.strip(), {"platform": "tiktok", "handle": handle.strip(), "language": language, "niche": niche.strip(), "description": description.strip(), "default_prompt_master": prompt, "prompt_master": prompt, "default_voice": voice, "voice": voice, "style_wide": channel_video_source_storage(source_value), "video_aspect_ratio": aspect_value, "format": "Shorts", "automation_on": automation_on, "automation_time": automation_time.strip(), "metrics_source": "tiktok_public_page", "subscriber_count": imported.get("subscriber_count"), "video_count": imported.get("video_count")})
2011
2017
  avatar_url = _tiktok_avatar_url(imported)
2012
2018
  update_channel(channel["id"], {"platform": "tiktok", "default_prompt_master": prompt, "prompt_master": prompt, "avatar_url": avatar_url, "thumbnail_url": avatar_url})
2013
2019
  st.session_state.pop("tiktok_channel_import", None)
@@ -2049,27 +2055,20 @@ def render_tiktok_channels():
2049
2055
  if st.button("Apagar card", key=f"tiktok_import_card_delete_{channel_id}"):
2050
2056
  st.session_state[f"tiktok_delete_{channel_id}"] = True
2051
2057
  st.rerun()
2052
- _, _, _, card_voice_options, card_current_voice = channel_default_options(channel)
2053
- selected_voice = st.selectbox("Narrador/Voz Padrão", card_voice_options, index=card_voice_options.index(card_current_voice) if card_current_voice in card_voice_options else 0, format_func=lambda item: item or "Sem voz padrão", key=f"tiktok_import_card_voice_{channel_id}")
2054
- selected_source = st.selectbox("Fonte do vídeo", WIDE_STYLE_OPTIONS, index=WIDE_STYLE_OPTIONS.index(channel_video_source_value(channel.get("style_wide"))) if channel_video_source_value(channel.get("style_wide")) in WIDE_STYLE_OPTIONS else 0, key=f"tiktok_import_card_source_{channel_id}")
2055
- selected_aspect = st.selectbox("Proporção do vídeo", CHANNEL_ASPECT_RATIO_OPTIONS, index=CHANNEL_ASPECT_RATIO_OPTIONS.index(str(channel.get("video_aspect_ratio") or "Portrait 9:16")) if str(channel.get("video_aspect_ratio") or "Portrait 9:16") in CHANNEL_ASPECT_RATIO_OPTIONS else 1, key=f"tiktok_import_card_aspect_{channel_id}")
2056
- selected_format = st.selectbox("Formato", CHANNEL_FORMAT_OPTIONS, index=CHANNEL_FORMAT_OPTIONS.index(str(channel.get("format") or "Shorts")) if str(channel.get("format") or "Shorts") in CHANNEL_FORMAT_OPTIONS else 1, key=f"tiktok_import_card_format_{channel_id}")
2057
- selected_language = st.selectbox("Idioma do roteiro", VIDEO_LANGUAGE_SELECTION_OPTIONS, index=VIDEO_LANGUAGE_SELECTION_OPTIONS.index(normalize_video_language(channel.get("language") or "pt")) if normalize_video_language(channel.get("language") or "pt") in VIDEO_LANGUAGE_SELECTION_OPTIONS else 0, format_func=video_language_label, key=f"tiktok_import_card_language_{channel_id}")
2058
- automation_on = st.toggle("Automação ON", value=bool(channel.get("automation_on", False)), key=f"tiktok_import_card_automation_{channel_id}")
2059
- automation_time = st.text_input("Horário diário (HH:MM)", value=str(channel.get("automation_time") or "00:00"), key=f"tiktok_import_card_time_{channel_id}")
2060
- if st.button("Guardar", key=f"tiktok_import_card_save_{channel_id}", use_container_width=True, type="primary"):
2061
- if not valid_hhmm(automation_time):
2062
- st.error("O horário diário deve estar no formato HH:MM.")
2063
- else:
2064
- update_channel(channel_id, {"platform": "tiktok", "default_prompt_master": selected_prompt, "prompt_master": selected_prompt, "default_voice": selected_voice, "voice": selected_voice, "language": selected_language, "video_aspect_ratio": selected_aspect, "format": selected_format, "style_wide": channel_video_source_storage(selected_source), "automation_on": automation_on, "automation_time": automation_time.strip()})
2065
- st.success("Dados do canal TikTok guardados.")
2066
- st.rerun()
2067
- st.caption(f"Fonte do vídeo: {channel_video_source_value(channel.get('style_wide'))}")
2068
- niche_action = st.columns([1.0, 4.0])
2069
- with niche_action[0]:
2070
- if st.button("Editar nicho", key=f"tiktok_import_card_niche_{channel_id}"):
2071
- st.session_state[f"tiktok_niche_{channel_id}"] = not st.session_state.get(f"tiktok_niche_{channel_id}", False)
2072
- st.rerun()
2058
+ summary_cols = st.columns(4, gap="small")
2059
+ with summary_cols[0]:
2060
+ st.markdown("**Blueprint Padrão**")
2061
+ st.caption(prompt_labels.get(str(channel.get("default_prompt_master") or channel.get("prompt_master") or ""), "Sem Blueprint padrão"))
2062
+ with summary_cols[1]:
2063
+ st.markdown("**Nicho**")
2064
+ st.caption(channel_niche_label(channel))
2065
+ with summary_cols[2]:
2066
+ st.markdown("**Narrador/Voz Padrão**")
2067
+ st.caption(str(channel.get("default_voice") or channel.get("voice") or "Sem voz padrão"))
2068
+ with summary_cols[3]:
2069
+ st.markdown("**Idioma**")
2070
+ st.caption(video_language_label(normalize_video_language(channel.get("language") or "pt")))
2071
+ st.caption(f"Fonte do vídeo: {channel_video_source_value(channel.get('style_wide'))} · Proporção: {channel.get('video_aspect_ratio') or 'Portrait 9:16'}")
2073
2072
  if st.session_state.get(f"tiktok_delete_{channel_id}"):
2074
2073
  st.warning("Apagar este canal TikTok e os dados locais associados?")
2075
2074
  confirm_cols = st.columns(2)
@@ -2097,7 +2096,6 @@ def render_tiktok_channels():
2097
2096
  edit_language = st.selectbox("Idioma do roteiro", VIDEO_LANGUAGE_SELECTION_OPTIONS, index=VIDEO_LANGUAGE_SELECTION_OPTIONS.index(normalize_video_language(channel.get("language") or "pt")) if normalize_video_language(channel.get("language") or "pt") in VIDEO_LANGUAGE_SELECTION_OPTIONS else 0, format_func=video_language_label)
2098
2097
  edit_source = st.selectbox("Fonte do vídeo", WIDE_STYLE_OPTIONS, index=WIDE_STYLE_OPTIONS.index(channel_video_source_value(channel.get("style_wide"))) if channel_video_source_value(channel.get("style_wide")) in WIDE_STYLE_OPTIONS else 0)
2099
2098
  edit_aspect = st.selectbox("Proporção do vídeo", CHANNEL_ASPECT_RATIO_OPTIONS, index=CHANNEL_ASPECT_RATIO_OPTIONS.index(str(channel.get("video_aspect_ratio") or "Portrait 9:16")) if str(channel.get("video_aspect_ratio") or "Portrait 9:16") in CHANNEL_ASPECT_RATIO_OPTIONS else 1)
2100
- edit_format = st.selectbox("Formato", CHANNEL_FORMAT_OPTIONS, index=CHANNEL_FORMAT_OPTIONS.index(str(channel.get("format") or "Shorts")) if str(channel.get("format") or "Shorts") in CHANNEL_FORMAT_OPTIONS else 1)
2101
2099
  edit_voice = st.selectbox("Narrador/Voz Padrão", voice_options, index=voice_options.index(str(channel.get("default_voice") or channel.get("voice") or "")) if str(channel.get("default_voice") or channel.get("voice") or "") in voice_options else 0, format_func=lambda item: item or "Sem voz padrão")
2102
2100
  edit_automation = st.toggle("Automação ON", value=bool(channel.get("automation_on", False)), key=f"tiktok_edit_automation_{channel_id}")
2103
2101
  edit_time = st.text_input("Horário diário (HH:MM)", value=str(channel.get("automation_time") or "00:00"))
@@ -2107,7 +2105,7 @@ def render_tiktok_channels():
2107
2105
  if not valid_hhmm(edit_time):
2108
2106
  st.error("O horário diário deve estar no formato HH:MM.")
2109
2107
  else:
2110
- update_channel(channel_id, {"name": edit_name.strip(), "url": edit_url.strip(), "handle": edit_handle.strip(), "language": edit_language, "default_voice": edit_voice, "voice": edit_voice, "style_wide": channel_video_source_storage(edit_source), "video_aspect_ratio": edit_aspect, "format": edit_format, "automation_on": edit_automation, "automation_time": edit_time.strip(), "description": edit_description.strip(), "platform": "tiktok"})
2108
+ update_channel(channel_id, {"name": edit_name.strip(), "url": edit_url.strip(), "handle": edit_handle.strip(), "language": edit_language, "default_voice": edit_voice, "voice": edit_voice, "style_wide": channel_video_source_storage(edit_source), "video_aspect_ratio": edit_aspect, "automation_on": edit_automation, "automation_time": edit_time.strip(), "description": edit_description.strip(), "platform": "tiktok"})
2111
2109
  st.session_state.pop(f"tiktok_edit_{channel_id}", None)
2112
2110
  st.rerun()
2113
2111
  with st.expander("Últimos 10 vídeos publicados", expanded=False):
@@ -2164,7 +2162,6 @@ def render_tiktok_channels():
2164
2162
  language = st.selectbox("Idioma do roteiro", VIDEO_LANGUAGE_SELECTION_OPTIONS, format_func=video_language_label, key="tiktok_manual_channel_language")
2165
2163
  source_value = st.selectbox("Fonte do vídeo", WIDE_STYLE_OPTIONS, key="tiktok_manual_channel_source")
2166
2164
  aspect_value = st.selectbox("Proporção do vídeo", CHANNEL_ASPECT_RATIO_OPTIONS, index=1, key="tiktok_manual_channel_aspect")
2167
- format_value = st.selectbox("Formato", CHANNEL_FORMAT_OPTIONS, index=1, key="tiktok_manual_channel_format")
2168
2165
  niche = st.text_input("Nicho", key="tiktok_manual_channel_niche")
2169
2166
  prompt = st.selectbox("Prompt Master padrão", prompt_ids, format_func=lambda item: prompt_labels.get(item, item), key="tiktok_manual_channel_prompt")
2170
2167
  voice = st.selectbox("Narrador/Voz Padrão", voice_options, format_func=lambda item: item or "Sem voz padrão", key="tiktok_manual_channel_voice")
@@ -2256,7 +2253,6 @@ def render_channels():
2256
2253
  language = st.selectbox("Idioma do roteiro", VIDEO_LANGUAGE_SELECTION_OPTIONS, index=VIDEO_LANGUAGE_SELECTION_OPTIONS.index(normalize_video_language(imported.get("language") or "pt")) if normalize_video_language(imported.get("language") or "pt") in VIDEO_LANGUAGE_SELECTION_OPTIONS else 0, format_func=video_language_label, key="yt_import_language")
2257
2254
  style = st.selectbox("Fonte do vídeo", WIDE_STYLE_OPTIONS, index=0, key="yt_import_style")
2258
2255
  video_aspect_ratio = st.selectbox("Proporção do vídeo", CHANNEL_ASPECT_RATIO_OPTIONS, key="yt_import_aspect_ratio")
2259
- video_format = st.selectbox("Formato", CHANNEL_FORMAT_OPTIONS, key="yt_import_format")
2260
2256
  blueprint = st.selectbox("Blueprint Padrão", blueprint_ids, index=blueprint_ids.index(imported_blueprint) if imported_blueprint in blueprint_ids else 0, format_func=lambda item: blueprint_labels.get(item, item or "Sem Blueprint padrão"), key="yt_import_blueprint")
2261
2257
  voice_options = voice_catalog(imported.get("default_voice") or imported.get("voice", ""))
2262
2258
  current_voice = imported.get("default_voice") or imported.get("voice", "")
@@ -2286,7 +2282,7 @@ def render_channels():
2286
2282
  "niche": niche.strip(),
2287
2283
  "reference_channels": [item.strip() for item in re.split(r"[,|]", niche) if item.strip()],
2288
2284
  "language": language,
2289
- "style_wide": channel_video_source_storage(style), "video_aspect_ratio": video_aspect_ratio, "format": video_format,
2285
+ "style_wide": channel_video_source_storage(style), "video_aspect_ratio": video_aspect_ratio, "format": "wide",
2290
2286
  "blueprint_id": blueprint.strip(),
2291
2287
  "default_blueprint_id": blueprint.strip(),
2292
2288
  "default_voice": voice.strip(),
@@ -5007,15 +5003,14 @@ def render_tiktok_automation():
5007
5003
  with default_cols[3]:
5008
5004
  prompt = st.selectbox("Prompt Master Padrão", prompt_ids, index=prompt_ids.index(channel.get("default_prompt_master", "")) if channel.get("default_prompt_master", "") in prompt_ids else 0, format_func=lambda item: prompt_labels.get(item, item), key=f"tiktok_automation_prompt_{channel_id}")
5009
5005
  with default_cols[4]:
5010
- st.markdown("**Pool de vídeos**")
5011
- st.caption("TikTok / Shorts")
5006
+ automation_format = st.selectbox("Formato", CHANNEL_FORMAT_OPTIONS, index=CHANNEL_FORMAT_OPTIONS.index(str(channel.get("format") or "Shorts")) if str(channel.get("format") or "Shorts") in CHANNEL_FORMAT_OPTIONS else 1, key=f"tiktok_automation_format_{channel_id}")
5012
5007
  with default_cols[5]:
5013
5008
  if st.button("Guardar", key=f"tiktok_automation_save_{channel_id}", use_container_width=True, type="primary"):
5014
5009
  if not valid_hhmm(schedule_time):
5015
5010
  st.error("Use o formato HH:MM, por exemplo 08:30.")
5016
5011
  else:
5017
5012
  avatar_url = _tiktok_avatar_url(channel)
5018
- update_channel(channel_id, {"automation_on": bool(enabled), "automation_time": schedule_time.strip(), "default_prompt_master": prompt, "prompt_master": prompt, "platform": "tiktok", "video_aspect_ratio": "Portrait 9:16", "style_wide": "portrait", "avatar_url": avatar_url, "thumbnail_url": avatar_url})
5013
+ update_channel(channel_id, {"automation_on": bool(enabled), "automation_time": schedule_time.strip(), "default_prompt_master": prompt, "prompt_master": prompt, "platform": "tiktok", "format": automation_format, "video_aspect_ratio": "Portrait 9:16", "style_wide": "portrait", "avatar_url": avatar_url, "thumbnail_url": avatar_url})
5019
5014
  st.success("Automação TikTok guardada.")
5020
5015
  st.rerun()
5021
5016
  st.divider()
@@ -5110,6 +5105,8 @@ def render_automation():
5110
5105
  st.markdown("**Thumbnail Blueprint**")
5111
5106
  st.caption(str(paired_thumbnail.get("name") or "Generic_Thumbnail_Blueprint"))
5112
5107
  with default_cols[4]:
5108
+ automation_format = st.selectbox("Formato", CHANNEL_FORMAT_OPTIONS, index=CHANNEL_FORMAT_OPTIONS.index(str(channel.get("format") or "wide")) if str(channel.get("format") or "wide") in CHANNEL_FORMAT_OPTIONS else 0, key=f"automation_format_{channel_id}")
5109
+ with default_cols[5]:
5113
5110
  automation_voice = st.selectbox(
5114
5111
  "Narrador/Voz Padrão",
5115
5112
  voice_options,
@@ -5125,6 +5122,7 @@ def render_automation():
5125
5122
  update_channel(channel_id, {
5126
5123
  "automation_on": bool(enabled),
5127
5124
  "automation_time": schedule_time.strip(),
5125
+ "format": automation_format,
5128
5126
  })
5129
5127
  set_channel_defaults(channel_id, automation_blueprint, automation_voice)
5130
5128
  paired_id = str(paired_thumbnail.get("id") or "Generic_Thumbnail_Blueprint")
@@ -1,12 +1,14 @@
1
1
  import os
2
2
  import sys
3
+ import io
3
4
 
4
5
  os.environ["PYTHONIOENCODING"] = "utf-8"
5
6
  os.environ["PYTHONUTF8"] = "1"
6
- if hasattr(sys.stdout, "reconfigure"):
7
- sys.stdout.reconfigure(encoding="utf-8", errors="replace")
8
- if hasattr(sys.stderr, "reconfigure"):
9
- sys.stderr.reconfigure(encoding="utf-8", errors="replace")
7
+ _original_stdout, _original_stderr = sys.stdout, sys.stderr
8
+ if getattr(sys.stdout, "buffer", None) is not None and str(getattr(sys.stdout, "encoding", "") or "").lower().replace("-", "_") != "utf_8":
9
+ sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding="utf-8", errors="replace", line_buffering=True)
10
+ if getattr(sys.stderr, "buffer", None) is not None and str(getattr(sys.stderr, "encoding", "") or "").lower().replace("-", "_") != "utf_8":
11
+ sys.stderr = io.TextIOWrapper(sys.stderr.buffer, encoding="utf-8", errors="replace", line_buffering=True)
10
12
 
11
13
  import argparse
12
14
  import time
@@ -1,12 +1,14 @@
1
1
  import os
2
2
  import sys
3
+ import io
3
4
 
4
5
  os.environ["PYTHONIOENCODING"] = "utf-8"
5
6
  os.environ["PYTHONUTF8"] = "1"
6
- if hasattr(sys.stdout, "reconfigure"):
7
- sys.stdout.reconfigure(encoding="utf-8", errors="replace")
8
- if hasattr(sys.stderr, "reconfigure"):
9
- sys.stderr.reconfigure(encoding="utf-8", errors="replace")
7
+ _original_stdout, _original_stderr = sys.stdout, sys.stderr
8
+ if getattr(sys.stdout, "buffer", None) is not None and str(getattr(sys.stdout, "encoding", "") or "").lower().replace("-", "_") != "utf_8":
9
+ sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding="utf-8", errors="replace", line_buffering=True)
10
+ if getattr(sys.stderr, "buffer", None) is not None and str(getattr(sys.stderr, "encoding", "") or "").lower().replace("-", "_") != "utf_8":
11
+ sys.stderr = io.TextIOWrapper(sys.stderr.buffer, encoding="utf-8", errors="replace", line_buffering=True)
10
12
 
11
13
  import json
12
14
  import queue
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@danhachuel/thunderbolt",
3
- "version": "0.4.88",
3
+ "version": "0.4.90",
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",
package/sitecustomize.py CHANGED
@@ -3,13 +3,22 @@ from __future__ import annotations
3
3
 
4
4
  import os
5
5
  import sys
6
+ import io
6
7
 
7
8
  os.environ["PYTHONIOENCODING"] = "utf-8"
8
9
  os.environ["PYTHONUTF8"] = "1"
9
10
 
10
11
  for stream_name in ("stdin", "stdout", "stderr"):
11
12
  stream = getattr(sys, stream_name, None)
12
- if stream is not None and hasattr(stream, "reconfigure"):
13
+ buffer = getattr(stream, "buffer", None) if stream is not None else None
14
+ encoding = str(getattr(stream, "encoding", "") or "").lower().replace("-", "_") if stream is not None else ""
15
+ if buffer is not None and encoding != "utf_8":
16
+ try:
17
+ replacement = io.TextIOWrapper(buffer, encoding="utf-8", errors="replace", line_buffering=True)
18
+ setattr(sys, stream_name, replacement)
19
+ except (OSError, ValueError):
20
+ pass
21
+ elif stream is not None and hasattr(stream, "reconfigure"):
13
22
  try:
14
23
  stream.reconfigure(encoding="utf-8", errors="replace")
15
24
  except (OSError, ValueError):