文档

GetWebP CLI JSON 输出参考

GetWebP CLI 的 NDJSON 事件流架构。使用 --json 在 stdout 上发出结构化事件。

GetWebP CLI JSON 输出参考

在任何命令上使用 --json(别名 -j)以在 stdout 上发出结构化 NDJSON(换行分隔 JSON)事件。人类消息转到 stderr。每一行都是以 \n 终止的独立 JSON 对象。

另请参阅: 命令参考 | 退出代码 | CI 集成


概述#

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 会以退出码 2UsageError: 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 事件使用带有 eventts 字段的扁平格式,而非标准 NDJSON 信封。只有初始 version 前言和文件错误事件使用标准信封(@timestamptypedata 等)。

事件序列#

version 前言   (标准 NDJSON 信封,来自 runAction)
banner         (扁平格式,watcher 就绪后启动时发出)
converted      (扁平格式,每个成功转换的文件发出一次)
watch.failed   (标准信封,每个文件错误发出一次)
skipped        (扁平格式,每个跳过的文件发出一次)
heartbeat      (扁平格式,空闲时每 30 秒发出一次)
shutdown       (扁平格式,优雅退出时发出)

事件类型#

启动时 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_inputconvert未指定输入路径
license_invalidauth许可证密钥未被识别
license_expiredauth, convert许可证已过期
device_limit_exceededauth所有设备插槽均在使用中
activation_failedauth服务器拒绝激活
network_unreachableauth, logout, status无法访问 API 服务器
no_matchesconvert在路径中未找到受支持的图像
config_write_failedauth, logout无法将令牌写入配置文件
license_revoked_startupany会话开始前许可证已被撤销
license_revoked_mid_sessionwatch活跃 watch 会话期间许可证被撤销
not_activatedlogout此设备上无活跃许可证
device_not_foundlogout设备已解绑
invalid_tokenlogout令牌已过期或已撤销
confirmation_required_in_json_modelogout--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"
fi

Stderr 警告#

无论 --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。