GetWebP CLI JSON 输出参考
GetWebP CLI 的 NDJSON 事件流架构。使用 --json 在 stdout 上发出结构化事件。
GetWebP CLI JSON 输出参考
在任何命令上使用 --json(别名 -j)以在 stdout 上发出结构化 NDJSON(换行分隔 JSON)事件。人类消息转到 stderr。每一行都是以 \n 终止的独立 JSON 对象。
概述#
NDJSON 格式#
stdout 上发出的每一行都是一个具有此必需信封的 JSON 事件:
interface Envelope {
"@timestamp": string // RFC3339 UTC,毫秒精度,例如 "2026-04-11T10:00:00.123Z"
"@level": "info" | "warn" | "error"
"@message": string // 单行人类可读摘要
"@module": string // 例如 "getwebp.convert"、"getwebp.watch"
type: string // 事件鉴别器(见下方注册表)
data: object // 事件特定的负载;始终存在,永不为 null
}通道规范:
- stdout:仅 NDJSON 事件(当
--json激活时) - stderr:所有人类可读的输出(进度条、警告、旋转指示器)
- 例外:
--help和--version无论--json如何都写入 stdout
版本前言#
每个 --json 调用的第一行始终是一个 version 事件:
{"@timestamp":"2026-04-11T10:00:00.100Z","@level":"info","@message":"GetWebP CLI 1.2.1","@module":"getwebp.cli","type":"version","data":{"getwebp":"1.2.1","ui":"1"}}消费者在处理命令特定事件之前必须读取并丢弃此行。data.ui 字段是 NDJSON 接口版本——破坏性的信封更改会增加此数字。
getwebp convert --json#
事件序列#
成功运行发出:
version 前言
convert.completed
有错误的运行发出:
version 前言
convert.file.failed (每个失败文件一个)
convert.completed (带有 failedCount > 0)
被免费计划限制截断的运行发出:
version 前言
convert.truncated
完全失败的运行(例如缺少输入)发出:
version 前言
convert.failed
事件类型#
convert.completed#
在成功(或部分成功)运行结束时发出一次。
interface ConvertCompletedData {
processed: number // 尝试的总文件数
total: number // 与 processed 相同
successCount: number
failedCount: number
results: FileResult[] // 每个文件的详情
}
interface SuccessResult {
file: string
status: "success"
originalSize: number // 字节
newSize: number // 字节
savedRatio: number // 0 到 1(如果 WebP 更大则为负)
saved: string // 例如 "35.0%"
quality: number // 实际使用的 WebP 质量
qualityMode: "auto" | "fixed"
}
interface ErrorResult {
file: string
status: "error"
error: string // 人类可读的描述
}
interface SkipResult {
file: string
status: "skipped"
reason: "existing"
}示例——全部成功:
{"@timestamp":"2026-04-11T10:00:01.000Z","@level":"info","@message":"Converted 3/3 files","@module":"getwebp.convert","type":"convert.completed","data":{"processed":3,"total":3,"successCount":3,"failedCount":0,"results":[{"file":"hero.jpg","originalSize":204800,"newSize":133120,"savedRatio":0.35,"saved":"35.0%","quality":78,"qualityMode":"auto","status":"success"},{"file":"logo.png","originalSize":51200,"newSize":20480,"savedRatio":0.60,"saved":"60.0%","quality":82,"qualityMode":"auto","status":"success"},{"file":"banner.bmp","originalSize":921600,"newSize":184320,"savedRatio":0.80,"saved":"80.0%","quality":76,"qualityMode":"auto","status":"success"}]}}示例——部分失败:
{"@timestamp":"2026-04-11T10:00:01.200Z","@level":"info","@message":"Converted 1/2 files","@module":"getwebp.convert","type":"convert.completed","data":{"processed":2,"total":2,"successCount":1,"failedCount":1,"results":[{"file":"photo.jpg","originalSize":204800,"newSize":133120,"savedRatio":0.35,"saved":"35.0%","quality":80,"qualityMode":"fixed","status":"success"},{"file":"corrupt.png","status":"error","error":"Decode failed: Invalid PNG header"}]}}convert.truncated#
当免费计划 20 文件限制被达到时发出。之后不跟 convert.completed。
interface ConvertTruncatedData {
processed: number // 实际转换的文件
skipped: number // 因免费计划限制而跳过的文件
limit: number // 限制(当前为 20)
upgrade_url: string // "https://getwebp.com/pricing"
}示例:
{"@timestamp":"2026-04-11T10:00:02.000Z","@level":"warn","@message":"Processed 20/25 images — Free plan limit reached. 5 remaining.","@module":"getwebp.convert","type":"convert.truncated","data":{"processed":20,"skipped":5,"limit":20,"upgrade_url":"https://getwebp.com/pricing"}}convert.failed#
当整个命令失败时发出(不是每个文件的错误)。
interface ConvertFailedData {
code: string // 机器可读的错误代码
}示例——缺少输入:
{"@timestamp":"2026-04-11T10:00:00.200Z","@level":"error","@message":"Please specify an input path","@module":"getwebp.convert","type":"convert.failed","data":{"code":"missing_input"}}diagnostic#
可选的补充详情事件(例如警告)。可能在流中的任何时间点出现。
{"@timestamp":"...","@level":"warn","@message":"No --output specified: converted files will be written next to source files.","@module":"getwebp.convert","type":"diagnostic","data":{}}getwebp auth --json#
事件序列#
version 前言
auth.completed OR auth.failed
事件类型#
auth.completed#
{"@timestamp":"...","@level":"info","@message":"Activated — Pro plan unlocked","@module":"getwebp.auth","type":"auth.completed","data":{"plan":"pro"}}auth.failed#
{"@timestamp":"...","@level":"error","@message":"Activation failed: license_invalid","@module":"getwebp.auth","type":"auth.failed","data":{"code":"license_invalid","hint":"license_invalid"}}getwebp status --json#
事件序列#
version 前言
status.reported OR status.failed
事件类型#
status.reported#
interface StatusReportedData {
version: string
plan: "free" | "pro"
licenseKeySuffix?: string // 最后 4 个字符,例如 "A1B2"(免费计划时省略)
expiresAt?: string // ISO 8601(免费计划时省略)
devicesUsed?: number // 离线/缓存时省略
devicesLimit?: number // 离线/缓存时省略
cached?: true // 仅在数据来自本地缓存时存在
}免费计划:
{"@timestamp":"...","@level":"info","@message":"Free plan","@module":"getwebp.status","type":"status.reported","data":{"version":"1.2.1","plan":"free"}}Pro 计划(在线):
{"@timestamp":"...","@level":"info","@message":"Pro plan","@module":"getwebp.status","type":"status.reported","data":{"version":"1.2.1","plan":"pro","licenseKeySuffix":"A1B2","expiresAt":"2027-04-01T00:00:00.000Z","devicesUsed":1,"devicesLimit":5}}缓存(离线):
{"@timestamp":"...","@level":"info","@message":"Pro plan (cached)","@module":"getwebp.status","type":"status.reported","data":{"version":"1.2.1","plan":"pro","licenseKeySuffix":"A1B2","expiresAt":"2027-04-01T00:00:00.000Z","cached":true}}getwebp logout --json#
重要:
logout命令单独使用--json会以退出码 2(UsageError: confirmation_required_in_json_mode)退出,因为该命令需要确认操作。非交互式 NDJSON 输出必须同时使用--force --json。
# 正确用法——非交互式 NDJSON 输出需同时使用两个标志
getwebp logout --force --json事件序列#
version 前言
logout.completed OR logout.failed
事件类型#
logout.completed#
{"@timestamp":"...","@level":"info","@message":"Device unbound. Switched to Free plan.","@module":"getwebp.logout","type":"logout.completed","data":{}}logout.failed#
data.code | 含义 | 可重试 |
|---|---|---|
network_unreachable | 无法访问服务器 | 是 |
not_activated | 此设备上无活跃许可证 | 否 |
device_not_found | 设备已解绑 | 否 |
invalid_token | 令牌已过期或已撤销 | 否 |
confirmation_required_in_json_mode | 使用了 --json 但未使用 --force | 否 |
{"@timestamp":"...","@level":"error","@message":"Cannot reach server. Retry later or unbind via dashboard.","@module":"getwebp.logout","type":"logout.failed","data":{"code":"network_unreachable","hint":"Cannot reach server. Retry later or unbind via dashboard."}}getwebp watch --json#
注意: 大多数 watch 事件使用带有
event和ts字段的扁平格式,而非标准 NDJSON 信封。只有初始version前言和文件错误事件使用标准信封(@timestamp、type、data等)。
事件序列#
version 前言 (标准 NDJSON 信封,来自 runAction)
banner (扁平格式,watcher 就绪后启动时发出)
converted (扁平格式,每个成功转换的文件发出一次)
watch.failed (标准信封,每个文件错误发出一次)
skipped (扁平格式,每个跳过的文件发出一次)
heartbeat (扁平格式,空闲时每 30 秒发出一次)
shutdown (扁平格式,优雅退出时发出)
事件类型#
banner#
启动时 watcher 就绪后发出一次。所有字段均为顶层字段(无 data 包装)。
{"event":"banner","ts":"2026-04-11T10:00:00.200Z","root":"/abs/path/to/images","recursive":true,"concurrency":4,"stability_ms":1000,"existing_unconverted":5,"gitignore":true,"excluded_default":["node_modules",".git","dist"]}converted#
每个成功转换的文件发出一次。
{"event":"converted","ts":"...","input":"/abs/path/images/photo.jpg","output":"/abs/path/images/photo.webp","original_size":204800,"new_size":133120,"saved_ratio":0.35,"quality":78,"quality_mode":"auto","duration_ms":450}watch.failed(标准信封)#
文件转换错误时发出。使用标准 NDJSON 信封。
{"@timestamp":"...","@level":"error","@message":"watch error: decode_failed","@module":"getwebp.watch","type":"watch.failed","data":{"code":"decode_failed","hint":"Decode failed: Invalid JPEG header","input":"/abs/path/images/corrupt.jpg"}}skipped#
文件被跳过时发出(输出比输入新,或文件太大)。
{"event":"skipped","ts":"...","input":"/abs/path/images/photo.jpg","reason":"mtime"}heartbeat#
空闲期间每 30 秒发出一次。保持日志传输管道活跃。
{"event":"heartbeat","ts":"...","queue_depth":0,"in_flight":0,"processed_total":5,"errors_total":0}shutdown#
优雅退出时发出(SIGINT、SIGTERM、许可证撤销或致命错误)。
{"event":"shutdown","ts":"...","reason":"sigint","exit_code":130,"session_duration_ms":12345,"processed_total":5,"errors_total":0,"saved_bytes_total":125000}reason 值:"sigint" | "sigterm" | "license_expired" | "license_revoked" | "root_deleted" | "chokidar_error" | "fatal"
错误代码(data.code)#
所有命令中的机器可读错误代码:
data.code | 命令 | 含义 | 可重试 |
|---|---|---|---|
missing_input | convert | 未指定输入路径 | 否 |
license_invalid | auth | 许可证密钥未被识别 | 否 |
license_expired | auth, convert | 许可证已过期 | 否 |
device_limit_exceeded | auth | 所有设备插槽均在使用中 | 否 |
activation_failed | auth | 服务器拒绝激活 | 否 |
network_unreachable | auth, logout, status | 无法访问 API 服务器 | 是 |
no_matches | convert | 在路径中未找到受支持的图像 | 否 |
config_write_failed | auth, logout | 无法将令牌写入配置文件 | 否 |
license_revoked_startup | any | 会话开始前许可证已被撤销 | 否 |
license_revoked_mid_session | watch | 活跃 watch 会话期间许可证被撤销 | 否 |
not_activated | logout | 此设备上无活跃许可证 | 否 |
device_not_found | logout | 设备已解绑 | 否 |
invalid_token | logout | 令牌已过期或已撤销 | 否 |
confirmation_required_in_json_mode | logout | --json 需要 --force(JSON 模式下无交互式提示) | 否 |
jq 配方#
CI 脚本和自动化的常见管道。由于 --json 发出 NDJSON,通过 type 过滤来提取您需要的事件。
convert 命令#
# 检查所有转换是否成功
getwebp convert ./images --json | grep '"type":"convert.completed"' | jq '.data.failedCount == 0'
# 获取总字节节省
getwebp convert ./images --json | grep '"type":"convert.completed"' | jq '[.data.results[] | select(.status == "success") | (.originalSize - .newSize)] | add'
# 列出失败的文件
getwebp convert ./images --json | grep '"type":"convert.completed"' | jq -r '.data.results[] | select(.status == "error") | .file'
# 获取平均压缩比率
getwebp convert ./images --json | grep '"type":"convert.completed"' | jq '[.data.results[] | select(.status == "success") | .savedRatio] | add / length'
# 检查是否击中免费计划限制
getwebp convert ./images --json | grep '"type":"convert.truncated"' | jq '.data.skipped'
# 将文件名和大小提取为 CSV
getwebp convert ./images --json | grep '"type":"convert.completed"' | jq -r '.data.results[] | select(.status == "success") | [.file, (.originalSize|tostring), (.newSize|tostring), .saved] | @csv'status 命令#
# 获取当前计划
getwebp status --json | grep '"type":"status.reported"' | jq -r '.data.plan'
# 检查许可证是否活跃(非免费)
getwebp status --json | grep '"type":"status.reported"' | jq '.data.plan != "free"'
# 获取过期日期
getwebp status --json | grep '"type":"status.reported"' | jq -r '.data.expiresAt // "N/A"'
# 检查设备使用情况
getwebp status --json | grep '"type":"status.reported"' | jq '"\(.data.devicesUsed // "?") / \(.data.devicesLimit // "?")"'auth 和 logout#
# 激活并检查结果
getwebp auth XXXX-XXXX-XXXX-XXXX --json | grep '"type":"auth.completed"' | jq -e '.'
# 注销(必须同时使用 --force,--json 单独使用会退出码 2)
getwebp logout --force --json | grep '"type":"logout.completed"' | jq -e '.'CI 管道模式#
#!/usr/bin/env bash
set -euo pipefail
getwebp convert ./src/images -o ./dist/images --quality 85 --json > conversion.ndjson
exit_code=$?
if [ $exit_code -ne 0 ] && [ $exit_code -ne 6 ]; then
echo "Conversion failed with exit code $exit_code" >&2
grep '"type":"convert.failed"' conversion.ndjson | jq -r '"Error: " + .data.code' >&2
exit $exit_code
fi
completed=$(grep '"type":"convert.completed"' conversion.ndjson || true)
if [ -n "$completed" ]; then
total=$(echo "$completed" | jq '.data.total')
success=$(echo "$completed" | jq '.data.successCount')
echo "Converted $success/$total images"
fi
truncated=$(grep '"type":"convert.truncated"' conversion.ndjson || true)
if [ -n "$truncated" ]; then
skipped=$(echo "$truncated" | jq '.data.skipped')
echo "Free plan: $skipped files were skipped. Upgrade at getwebp.com/pricing"
fiStderr 警告#
无论 --json 如何,CLI 都可能向 stderr 发出警告。这些不是 NDJSON 流的一部分。使用 2>/dev/null 抑制。示例:
warn: Free plan: max 20 files, 3s delay between each.
warn: No --output specified: converted files will be written next to source files.
warn: Conflict: a.png, a.jpg all map to a.webp — only the last processed will survive
在
--json模式下,大多数警告也会作为diagnostic事件在 stdout 上发出,因此消费者不需要解析 stderr。