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:
12
README.md
12
README.md
@@ -298,6 +298,12 @@ Mode behavior:
|
|||||||
- `Invalid tweet JSON file`
|
- `Invalid tweet JSON file`
|
||||||
- Regenerate input using `twitter feed --json > tweets.json`.
|
- 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`.
|
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 授权时点击 **"始终允许"**。
|
- 或在弹出 Keychain 授权时点击 **"始终允许"**。
|
||||||
- 报错 `Twitter API error 404`:通常是 queryId 轮换,重试即可。
|
- 报错 `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`。
|
- 结构化错误码通常会区分 `not_authenticated`、`not_found`、`invalid_input`、`rate_limited`、`api_error`。
|
||||||
|
|||||||
@@ -61,6 +61,7 @@ from .output import (
|
|||||||
default_structured_format,
|
default_structured_format,
|
||||||
emit_error,
|
emit_error,
|
||||||
emit_structured,
|
emit_structured,
|
||||||
|
ensure_utf8_streams,
|
||||||
error_payload,
|
error_payload,
|
||||||
structured_output_options,
|
structured_output_options,
|
||||||
success_payload,
|
success_payload,
|
||||||
@@ -303,6 +304,7 @@ def _run_write_command(
|
|||||||
def cli(ctx, verbose, compact):
|
def cli(ctx, verbose, compact):
|
||||||
# type: (Any, bool, bool) -> None
|
# type: (Any, bool, bool) -> None
|
||||||
"""twitter — Twitter/X CLI tool 🐦"""
|
"""twitter — Twitter/X CLI tool 🐦"""
|
||||||
|
ensure_utf8_streams()
|
||||||
_setup_logging(verbose)
|
_setup_logging(verbose)
|
||||||
ctx.ensure_object(dict)
|
ctx.ensure_object(dict)
|
||||||
ctx.obj["compact"] = compact
|
ctx.obj["compact"] = compact
|
||||||
|
|||||||
@@ -14,6 +14,27 @@ _OUTPUT_ENV = "OUTPUT"
|
|||||||
_SCHEMA_VERSION = "1"
|
_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:
|
def default_structured_format(*, as_json: bool, as_yaml: bool) -> str | None:
|
||||||
"""Resolve explicit flags first, then env override, then TTY default."""
|
"""Resolve explicit flags first, then env override, then TTY default."""
|
||||||
if as_json and as_yaml:
|
if as_json and as_yaml:
|
||||||
@@ -126,3 +147,4 @@ def emit_error(
|
|||||||
else:
|
else:
|
||||||
click.echo(yaml.safe_dump(payload, allow_unicode=True, sort_keys=False, default_flow_style=False))
|
click.echo(yaml.safe_dump(payload, allow_unicode=True, sort_keys=False, default_flow_style=False))
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user