fix(windows): add ensure_utf8_streams() for Windows GBK encoding + ConPTY docs

- Add ensure_utf8_streams() in output.py: reconfigures stdout/stderr to UTF-8
  on Windows at CLI startup, fixing emoji UnicodeEncodeError on GBK locales
- Call ensure_utf8_streams() in cli() entry point — one-line fix for all
  output paths (click.echo, rich Console, print)
- Add Windows ConPTY troubleshooting to README (English + Chinese):
  document Git Bash workaround, ConPTY pipe capture issue, and encoding notes

Ref: #29 (issuecomment-4065690862)
This commit is contained in:
jackwener
2026-03-16 17:59:33 +08:00
parent 74386cebc8
commit 0b91e66998
3 changed files with 36 additions and 0 deletions

View File

@@ -61,6 +61,7 @@ from .output import (
default_structured_format,
emit_error,
emit_structured,
ensure_utf8_streams,
error_payload,
structured_output_options,
success_payload,
@@ -303,6 +304,7 @@ def _run_write_command(
def cli(ctx, verbose, compact):
# type: (Any, bool, bool) -> None
"""twitter — Twitter/X CLI tool 🐦"""
ensure_utf8_streams()
_setup_logging(verbose)
ctx.ensure_object(dict)
ctx.obj["compact"] = compact

View File

@@ -14,6 +14,27 @@ _OUTPUT_ENV = "OUTPUT"
_SCHEMA_VERSION = "1"
def ensure_utf8_streams() -> None:
"""Reconfigure stdout/stderr to use UTF-8 encoding on Windows.
On Windows with ConPTY disabled (e.g. winpty fallback + PowerShell),
the default encoding may be GBK/cp936 which cannot encode emoji.
Calling reconfigure(encoding='utf-8') once at startup fixes ALL
output paths — click.echo, rich Console, and plain print — without
needing per-call wrappers.
This is a no-op on Unix (already UTF-8) and safe to call multiple times.
"""
if sys.platform != "win32":
return
for stream in (sys.stdout, sys.stderr):
if hasattr(stream, "reconfigure"):
try:
stream.reconfigure(encoding="utf-8")
except Exception:
pass # frozen or non-standard stream, skip
def default_structured_format(*, as_json: bool, as_yaml: bool) -> str | None:
"""Resolve explicit flags first, then env override, then TTY default."""
if as_json and as_yaml:
@@ -126,3 +147,4 @@ def emit_error(
else:
click.echo(yaml.safe_dump(payload, allow_unicode=True, sort_keys=False, default_flow_style=False))
return True