跳转至

输出规范

twitter-cli 所有命令的 stdout 输出均为严格 JSONL 格式(每行一个 JSON 对象)。本文档是 AI agent 解析 CLI 输出的权威参考。

目录


stdout 与 stderr 分离

内容 格式
stdout 命令结果(JSONL) 严格 JSON,每行一个对象,无多余空白
stderr tracing 日志 --log-format 控制(json / compact)

重要: AI agent 应只解析 stdout,忽略 stderr。--log-level off(默认)时 stderr 无输出。

# 只捕获 stdout,丢弃 stderr
result=$(twitter-cli --cookies-file cookies.txt user get --screen-name twitter 2>/dev/null)

# 同时保存两者
twitter-cli ... > result.jsonl 2> debug.log

单条命令:Envelope 格式

所有非批量命令输出单行 JSON,结构为 Envelope<T>

{
  "success": true,
  "data": { ... },
  "error": null,
  "meta": {
    "module": "user",
    "action": "get",
    "elapsed_ms": 342,
    "timestamp": "2026-04-17T10:45:00Z",
    "cli_version": "1.0.0",
    "schema_version": "1.0"
  }
}

Envelope 字段说明

字段 类型 说明
success bool true 表示操作成功,false 表示失败
data T \| null 成功时的结果对象;失败时为 null
error ErrorObject \| null 失败时的错误对象;成功时为 null
meta.module string 模块名(dm / posts / user / search / upload / settings)
meta.action string 操作名(send / create / get 等)
meta.elapsed_ms number 本次命令总耗时(毫秒)
meta.timestamp string ISO 8601 UTC 时间戳
meta.cli_version string CLI 版本号(如 "1.0.0"
meta.schema_version string 输出格式版本(如 "1.0"

成功示例

{"success":true,"data":{"user_id":"783214","event_id":"1811234567890123456","message":"Message sent successfully","media_id":null},"error":null,"meta":{"module":"dm","action":"send","elapsed_ms":519,"timestamp":"2026-04-17T10:45:00Z","cli_version":"1.0.0","schema_version":"1.0"}}

失败示例

{"success":false,"data":null,"error":{"code":"AUTH_FAILED","message":"Twitter 返回 401,cookies 可能已过期","retryable":false,"retry_after_secs":null,"docs_url":"https://x-api-rs.es007.com/cli/getting-started/#cookies","recovery_actions":["重新从浏览器导出 cookies","确认 cookies 包含 ct0 和 auth_token 字段"],"issue_url":null,"details":{"http_status":401}},"meta":{"module":"user","action":"get","elapsed_ms":128,"timestamp":"2026-04-17T10:46:00Z","cli_version":"1.0.0","schema_version":"1.0"}}

批量命令:BatchLine 三段式格式

批量命令(dm send-batch)输出多行 JSONL,每行通过 type 字段区分:

header 行(第 1 行)

{
  "type": "header",
  "command": "dm send-batch",
  "schema_version": "1.0",
  "expected_count": 3,
  "meta": {
    "module": "dm",
    "action": "send-batch",
    "timestamp": "2026-04-17T10:45:00Z",
    "cli_version": "1.0.0"
  }
}

item 行(每个目标一行)

成功:

{"type":"item","index":0,"success":true,"data":{"user_id":"783214","event_id":"1811234567890123456","message":"Message sent successfully","media_id":null}}

失败:

{"type":"item","index":1,"success":false,"error":{"code":"NOT_FOUND","message":"用户 999999 不存在或已停用","retryable":false,"retry_after_secs":null,"recovery_actions":["确认 user_id 是否正确"]}}

summary 行(最后 1 行)

{
  "type": "summary",
  "total": 3,
  "success_count": 2,
  "fail_count": 1,
  "elapsed_ms": 1820
}

完整四行示例

{"type":"header","command":"dm send-batch","schema_version":"1.0","expected_count":3,"meta":{"module":"dm","action":"send-batch","timestamp":"2026-04-17T10:45:00Z","cli_version":"1.0.0"}}
{"type":"item","index":0,"success":true,"data":{"user_id":"783214","event_id":"1811234567890123456","message":"Message sent successfully","media_id":null}}
{"type":"item","index":1,"success":false,"error":{"code":"NOT_FOUND","message":"用户 999999 不存在或已停用","retryable":false,"retry_after_secs":null,"recovery_actions":["确认 user_id 是否正确"]}}
{"type":"item","index":2,"success":true,"data":{"user_id":"6253282","event_id":"1811234567890123457","message":"Message sent successfully","media_id":null}}
{"type":"summary","total":3,"success_count":2,"fail_count":1,"elapsed_ms":1820}

错误对象结构

error 字段(失败时非 null)包含以下字段:

字段 类型 必填 说明
code string ErrorCode 枚举值,见下表
message string 人类可读的错误描述
retryable bool 是否值得重试
retry_after_secs number \| null 建议重试等待秒数(来自 Retry-After 头)
docs_url string \| null 相关文档链接
recovery_actions string[] 建议的修复步骤列表
issue_url string \| null GitHub issue 链接(已知 Bug 时提供)
details object \| null 额外调试信息(如 http_status)

ErrorCode 说明表

code 退出码 retryable 典型触发场景
INVALID_ARGS 2 false 参数缺失或格式错误(如 --text 过长、--user-id 为空)
AUTH_FAILED 3 false cookies 过期、ct0 缺失、401 响应
NETWORK 4 true 连接超时、DNS 解析失败、网络不通
RATE_LIMIT 5 true Twitter 返回 429,触发频率限制
NOT_FOUND 6 false 用户不存在、帖子已删除、404 响应
SERVER 7 true Twitter 服务端 5xx 错误
DUPLICATE 8 false 重复操作(如重复转发同一帖子)
MEDIA_UPLOAD_FAILED 9 false 图片上传失败(格式不支持、超过大小限制)
CONTENT_VIOLATION 10 false 内容违规(包含被禁词、垃圾内容检测)
DEPRECATED_COMMAND 11 false 调用了已废弃的子命令(通常为版本升级后)
CONFIG_PARSE_ERROR 12 false config.toml 格式错误或字段缺失
UNKNOWN 1 false 未分类的意外错误

退出码

0   成功
1   UNKNOWN(未分类错误)
2   INVALID_ARGS(参数错误)
3   AUTH_FAILED(认证失败)
4   NETWORK(网络错误)
5   RATE_LIMIT(速率限制)
6   NOT_FOUND(资源不存在)
7   SERVER(服务端 5xx)
8   DUPLICATE(重复操作)
9   MEDIA_UPLOAD_FAILED(媒体上传失败)
10  CONTENT_VIOLATION(内容违规)
11  DEPRECATED_COMMAND(命令已废弃)
12  CONFIG_PARSE_ERROR(配置解析错误)

Shell 脚本使用示例:

twitter-cli --cookies-file cookies.txt dm send --user-id 783214 --text "Hello"
exit_code=$?

case $exit_code in
  0) echo "成功" ;;
  3) echo "认证失败,请更新 cookies" ;;
  5) echo "速率限制,等待后重试" ;;
  4) echo "网络错误,检查代理设置" ;;
  *) echo "其他错误: $exit_code" ;;
esac

schema_version 版本化规则

schema_version 字段遵循 Major.Minor 规则:

变更类型 版本变化 向后兼容
新增可选字段 Minor 升级(1.0 → 1.1)
修改字段名或类型 Major 升级(1.0 → 2.0)
删除字段 Major 升级

AI agent 解析建议:检查 schema_version 的 Major 部分,Major 不同时应拒绝解析并报告不兼容。


AI agent 解析建议

单条命令解析

import json
import subprocess

result = subprocess.run(
    ["twitter-cli", "--cookies-file", "cookies.txt", "user", "get", "--screen-name", "twitter"],
    capture_output=True, text=True
)

envelope = json.loads(result.stdout.strip())

if envelope["success"]:
    data = envelope["data"]
    # 处理 data...
else:
    error = envelope["error"]
    code = error["code"]
    retryable = error["retryable"]
    # 根据 code 和 retryable 决定是否重试

批量命令流式解析

import json
import subprocess

proc = subprocess.Popen(
    ["twitter-cli", "--cookies-file", "cookies.txt", "dm", "send-batch",
     "--user-ids", "783214,6253282", "--text", "Hello"],
    stdout=subprocess.PIPE, text=True
)

items = []
summary = None

for line in proc.stdout:
    line = line.strip()
    if not line:
        continue
    obj = json.loads(line)
    t = obj.get("type")
    if t == "header":
        expected = obj["expected_count"]
    elif t == "item":
        items.append(obj)
    elif t == "summary":
        summary = obj

proc.wait()
print(f"成功: {summary['success_count']}, 失败: {summary['fail_count']}")

退出码检查

if result.returncode == 0:
    pass  # 成功
elif result.returncode == 5:
    # RATE_LIMIT,读取 retry_after_secs
    envelope = json.loads(result.stdout)
    wait = envelope["error"].get("retry_after_secs") or 60
    time.sleep(wait)
    # 重试...
elif result.returncode == 3:
    raise Exception("认证失败,需要更新 cookies")

版本兼容性检查

envelope = json.loads(result.stdout)
schema_ver = envelope["meta"]["schema_version"]
major = int(schema_ver.split(".")[0])
if major != 1:
    raise Exception(f"不兼容的 schema_version: {schema_ver},当前仅支持 1.x")