From 0b91e669987978a3a749315d368f49062f0a68a0 Mon Sep 17 00:00:00 2001 From: jackwener Date: Mon, 16 Mar 2026 17:59:33 +0800 Subject: [PATCH] fix(windows): add ensure_utf8_streams() for Windows GBK encoding + ConPTY docs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 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) --- README.md | 12 ++++++++++++ twitter_cli/cli.py | 2 ++ twitter_cli/output.py | 22 ++++++++++++++++++++++ 3 files changed, 36 insertions(+) diff --git a/README.md b/README.md index 6b39d35..a110a17 100644 --- a/README.md +++ b/README.md @@ -298,6 +298,12 @@ Mode behavior: - `Invalid tweet JSON file` - Regenerate input using `twitter feed --json > tweets.json`. +- **Windows: no output captured by pipe/subprocess** (AI agent integration) + - This is a **ConPTY** issue, not a twitter-cli bug. Windows Terminal's ConPTY pseudo-terminal can intercept pipe output from commands with network latency. + - **Fix**: Use **Git Bash** as your terminal shell and set `"windowsEnableConpty": false` in your terminal settings. + - If disabling ConPTY with PowerShell, emoji output may fail with `UnicodeEncodeError: 'gbk'`. Git Bash handles UTF-8 natively. + - Standard `subprocess.run(capture_output=True)` and file redirection (`> file 2>&1`) work correctly regardless of ConPTY. + Structured error codes commonly include `not_authenticated`, `not_found`, `invalid_input`, `rate_limited`, and `api_error`. @@ -553,6 +559,12 @@ score = likes_w * likes - 或在弹出 Keychain 授权时点击 **"始终允许"**。 - 报错 `Twitter API error 404`:通常是 queryId 轮换,重试即可。 +- **Windows 下 pipe/subprocess 无法捕获输出**(AI agent 集成场景) + - 这是 **ConPTY** 伪终端的问题,不是 twitter-cli 的 bug。Windows Terminal 的 ConPTY 可能拦截有网络延迟的命令的管道输出。 + - **解决方案**:使用 **Git Bash** 并在终端设置中设置 `"windowsEnableConpty": false`。 + - ConPTY 关闭后如用 PowerShell,emoji 可能会报 `UnicodeEncodeError: 'gbk'`。Git Bash 原生支持 UTF-8。 + - 标准 `subprocess.run(capture_output=True)` 和文件重定向 (`> file 2>&1`) 不受此问题影响。 + - 结构化错误码通常会区分 `not_authenticated`、`not_found`、`invalid_input`、`rate_limited`、`api_error`。 diff --git a/twitter_cli/cli.py b/twitter_cli/cli.py index ed33949..c0176c3 100644 --- a/twitter_cli/cli.py +++ b/twitter_cli/cli.py @@ -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 diff --git a/twitter_cli/output.py b/twitter_cli/output.py index 2a1d51e..7429e1b 100644 --- a/twitter_cli/output.py +++ b/twitter_cli/output.py @@ -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 +