输出规范¶
twitter-cli 所有命令的 stdout 输出均为严格 JSONL 格式(每行一个 JSON 对象)。本文档是 AI agent 解析 CLI 输出的权威参考。
目录¶
- stdout 与 stderr 分离
- 单条命令:Envelope 格式
- 批量命令:BatchLine 三段式格式
- 错误对象结构
- ErrorCode 说明表
- 退出码
- schema_version 版本化规则
- AI agent 解析建议
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":"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")