Status: Draft(2026-07-21,落地前细化设计)
Authority: N1 CLI、结果契约、ROI、detect-only 出口与 Agent Skill 的实现与契约规范
Requirements: requirements.md
Roadmap: roadmap.md §5 N1
Architecture: architecture.md
本草案是 roadmap §5 引用的「落地前细化设计」。它不替代 decisions.md 中的 D106 决策记录,而是 D106 的前置设计材料。所有标记 D-N1-x 的条目为待维护者确认的开放决策。
N1 的目标是让普通用户和 Agent 无需编写 Node.js 集成代码,即可从本地图片获得稳定文本、置信度和坐标,并建立 PDF、Layout 和多模型都能复用的版本化结果契约。
对照当前源码(0.3.1),N1 的底层能力已经具备:
| 能力 | 现状 | 来源 |
|---|---|---|
createEngine / OcrEngine.recognize(RawImage) / recognizeEncoded(Uint8Array) / info / close |
已发布 | packages/runtime/src/index.d.ts |
OcrResult.lines[].box(4 点 quad)、imageWidth/Height、modelBundleId、timingUs、diagnostics |
已发布 | 同上 |
EngineInfo.execution(requested/actual provider chain、device、precision、capabilities、selectionTrace) |
已发布 | 同上 |
OcrError / CoreErrorCode / AdapterErrorCode / CreationTrace |
已发布 | 同上 |
| JPEG/PNG 受限 decode(stb_image,带内存预算) | 已发布 | encoded_image.cpp |
| 四平台 prebuild、离线 bundle 解析、provider Auto | 已发布 | implementation-status.md |
| 能力 | 现状 | N1 交付 |
|---|---|---|
light-ocr CLI bin |
package.json 无 bin 字段 |
新增 bin + 子命令(recognize/detect/info) |
schemaVersion 与 DocumentResult envelope |
OcrResult 是单图底层契约,无 envelope |
CLI 层新增 envelope |
| EXIF orientation 修正 | 全仓 0 匹配,stb 不读 EXIF | 新增 EXIF 解析 + 像素变换 + appliedTransforms |
--region ROI |
recognize 无 region 参数 |
新增输入侧区域约束(recognize/detect 均可带) |
detect 子命令 |
Core Engine 公共接口仅 recognize/info/close,无 detect |
Core 新增 detect() 公共方法(不新增算法)+ Node 绑定 + detect 子命令 |
| Agent Skill | .agents/skills/ 不存在 |
新建 .agents/skills/local-ocr/SKILL.md |
N2 已把 CLI 公共实现迁到 packages/runtime/src/cli.cjs,三个 facade 只保留各自的命令名、版本和模型 profile 配置。@arcships/light-ocr 仍唯一拥有 light-ocr;Tiny/Medium 分别使用 light-ocr-tiny / light-ocr-medium。实现只用 Node 内置模块,零运行时依赖,符合禁网安装与 --ignore-scripts 要求;argv 解析继续使用手写最小 parser(D-N1-2)。
N1 面向 Agent,采用子命令结构而非扁平 flag:Agent 先从顶层动词锁定意图,再看该动词下的 flags,互斥约束收窄到子命令内。代价是 recognize/detect/info 三个动词的 help 与退出码需分别维护,但换来 Agent 可逐步理解的入口结构。
light-ocr recognize <path|--stdin> [flags] # 默认 OCR:detection + recognition
light-ocr detect <path|--stdin> [flags] # 只检测:检测框,不识别
light-ocr info [--model-info|--version] # 诊断,不读图
light-ocr [recognize] <path> [flags] # 隐式 recognize:不带子命令 = recognize
light-ocr --help [subcommand] # 分层 help
约束:
light-ocr image.png隐式等价light-ocr recognize image.png,保持首次使用直觉与向后兼容;- 每个 flag 只挂在自己相关的子命令下(见 §2.2),互斥约束在子命令内收敛;
info子命令不接受 path/--stdin,不接受--format;--model-info与--version作为info的 flags;- N3 文档入口另起
light-ocr-documentbin(roadmap §3.1),与 N1 子命令并列,不并入light-ocr。
与 roadmap §5.2 的偏离说明:roadmap 原文写「
--model-info和--version」为顶导 flag,本设计改为info子命令以保持与recognize/detect的结构统一;语义等价。该偏离在 D106 决策中记录。
第一版保持命令面小而稳定(roadmap §5.2):
# 默认 OCR(隐式 recognize)
light-ocr image.png --format json
light-ocr image.png --format text
light-ocr image.png --format jsonl
# 显式 recognize(等价于隐式)
light-ocr recognize image.png --format json
# 区域识别(ROI)
light-ocr recognize image.png --region 100,80,640,320 --format json
# stdin
cat image.png | light-ocr recognize --stdin --type image/png --format json
# detect 子命令:只检测,不识别(输出固定 JSON,不暴露 --format)
light-ocr detect image.png
light-ocr detect image.png --crop
light-ocr detect image.png --region 100,80,640,320 --crop
# info 子命令:诊断与版本,不读图(--model-info 与 --version 互斥)
light-ocr info --model-info
light-ocr info --version
# schema 版本(recognize/detect 均可)
light-ocr recognize image.png --schema-version 1 --format json
# 执行后端(与 Node API execution 契约等价)
light-ocr recognize image.png --provider auto --format json
light-ocr detect image.png --provider webgpu每个 flag 只挂在自己相关的子命令下。第一版只暴露 Agent/用户真实高频的入口层选项;provider 内部实现细节(session fallback、cpu partition、precision、detection strategy、max-side)不透传成 CLI flag,由 runtime 按 provider 默认处理。需要这些内部字段时用 Node API 或 info --model-info。
默认 help 顶部(高频):
| Flag | 取值 | 语义 |
|---|---|---|
<path> 位置参数 |
文件路径 | 必须与 --stdin 二选一 |
--stdin |
flag | 从 stdin 读取 bytes;需配合 --type |
--type |
image/png | image/jpeg | stdin 的 mediaType |
--format |
json | jsonl | text | 默认 json |
--region |
x,y,w,h |
pageSpace 轴对齐矩形,整数像素;详见 §7 |
--no-exif |
flag | 关闭默认 EXIF orientation 修正;详见 §6 |
--provider |
auto | cpu | apple | webgpu | 映射 execution.provider |
--schema-version |
1 | 请求精确输出 schema;不支持则稳定失败 |
--help 第二层(高级):
| Flag | 取值 | 语义 |
|---|---|---|
--score-threshold |
number | recognitionScoreThreshold;改它直接影响精度/召回,默认值已校准 |
--no-color |
flag | 显式关闭 stderr 彩色;非 TTY 或 NO_COLOR 已默认关闭 |
--quiet:stderr 只输出错误,不输出进度/警告。归入默认 help 顶部(行为开关,Agent 常用)。
不暴露为 CLI flag 的字段(内部固定默认或由 provider 决定):
sessionFallback:单值枚举(仅error),0.3.0 定为迁移期遗留,CLI 无意义;cpuPartition:provider qualification 内部细节,按 provider 默认allow;precision:fp16仅 Apple、WebGPU 只接受auto/fp32,选错直接失败,价值极低;detectionStrategy:tiled/upstream-exact是开发/parity 验证策略,普通用户用bounded,大图自动 tiled;maxSide:影响精度/速度 tradeoff,内部用校准默认;includeDiagnostics:diagnostics字段重且面向开发者调试 provider placement,Agent/用户消费不了,需要时用 Node API 或info --model-info。
| Flag | 取值 | 语义 |
|---|---|---|
<path> / --stdin / --type |
同 recognize |
输入一致 |
--region |
同 recognize |
输入侧区域约束,与 detect 正交可组合 |
--no-exif / --provider / --schema-version / --quiet |
同 recognize |
行为与后端一致 |
--crop |
flag | 每框附 PNG crop bytes;详见 §9 |
--no-color |
同 recognize |
高级,help 第二层 |
detect 子命令不暴露 --format:detect 输出永远是结构化 JSON(box quad + score + 可选 crop),text 格式无意义(无文字可输出)。从结构上消除 --format text + detect 的失败路径,不靠运行时报错。--score-threshold 不在 detect 下(detect 不做 recognition,无识别阈值概念;detection 置信度内部用校准默认)。
| Flag | 取值 | 语义 |
|---|---|---|
--model-info |
flag | 输出 EngineInfo JSON(含 coreVersion、modelBundleId、execution、capabilities、limits) |
--version |
flag | 输出 npm/core/model 版本三元组 |
info 子命令约束:
- 不接受
<path>、--stdin、--type、--format及任何 OCR/执行 flags;传入报invalid_argument; --model-info与--version互斥,不可同时传入:--version的三元组是--model-info的子集(coreVersion、modelBundleId已在EngineInfo内),合并会重复字段;需要完整信息用--model-info,只需快速版本探测用--version。
目录递归、glob、watch mode、交互式 UI、远程 URL 不进入第一版;批量调用先通过 shell + JSONL 组合完成(roadmap §5.2)。
子命令结构天然分层,Agent 可逐步理解:
light-ocr --help:列出三个子命令 + 各自一句话职责 + 一个完整示例;Commands: recognize <path|--stdin> Recognize text in an image (default action) detect <path|--stdin> Detect text regions only, no recognition info [--model-info|--version] Show engine/version info without reading imageslight-ocr <subcommand> --help:该子命令的全部 flags 分组(输入/输出/能力/执行/资源/行为);light-ocr <subcommand> --<flag>=help(如light-ocr recognize --region=help):该 flag 的精确语义、坐标系、失败行为与示例。
Agent 与脚本应使用 info --model-info、info --version 和固定 --schema-version,不解析 help 文本。
这是 N1 的硬约束(roadmap §5.2),不可回退:
- stdout:只承载机器结果(json / jsonl / text 机器输出、
--model-info的 JSON、--version的版本串)。成功时 stdout 必须可被JSON.parse或确定管道消费; - stderr:日志、warnings、diagnostics 的人类文本、进度、
--include-diagnostics的人类可读摘要、usage error 提示; - 任何混合 stdout 的人类提示一律视为缺陷;
--quiet只压制 stderr 的非错误输出,不改 stdout 契约;--no-color控制 stderr ANSI;CI 环境检测(NO_COLOR、非 TTY)默认关闭彩色,D-N1-8 确认检测策略。
文本格式(--format text)按行输出识别文本,stdout 仍只含文本行,不带坐标/置信度;需要坐标必须用 --format json。
沿用 roadmap §3.3:
| 术语 | 定义 |
|---|---|
sourceSpace |
encoded source 方向修正前的固有坐标,仅用于记录 identity 与 transform |
pageSpace |
所有 v1 line.box、Layout box、--region 的 canonical space;左上原点,x 向右 y 向下,单位为方向修正后 pixel |
appliedTransforms |
source→page 有序变换记录:EXIF orientation、crop、raster scale |
roadmap §5.5 要求对 encoded JPEG 默认应用可验证的 EXIF orientation 修正,修正后图片定义为 pageSpace,结果记录完整 appliedTransforms。
实现约束:
- stb_image 不解析 EXIF,需在 decode 后、送入 Core 前新增独立 EXIF 解析(JPEG APP1 segment)与像素变换。实现方式在 D-N1-5 决策(自带最小 EXIF 解析器 vs 换用支持 EXIF 的 decode 库);
- PNG 不含 EXIF orientation(可含
eXIfchunk,首版按无变换处理); --no-exif关闭修正,appliedTransforms仍记录「未应用」状态;- raw-pixel API(
recognize(RawImage))继续由调用者负责方向,传入像素直接定义pageSpace; - 修正后的
imageWidth/Height与line.box全部落在pageSpace。
"appliedTransforms": {
"exifOrientation": 6,
"exifApplied": true,
"sourceWidth": 4000,
"sourceHeight": 3000,
"pageWidth": 3000,
"pageHeight": 4000
}roadmap §5.3:ROI 是输入侧的区域约束,不是 Layout 替代品。
- 第一版只接受位于方向修正后完整有效页面
pageSpace内的轴对齐矩形--region x,y,width,height(整数像素); - 在进入完整 OCR pipeline 前限制检测/识别范围;
- 返回的 quad 坐标重新映射到完整有效页面的
pageSpace(不是 ROI 局部坐标); - 非法、空或越界区域返回
invalid_argument,不隐式 clamp;部分相交也返回invalid_argument(roadmap §3.3); - ROI 仍受像素、临时内存、candidate 和 timeout 上限约束;
--region与detect子命令可组合:限制检测范围 + 只输出检测框。
ROI 的实现位置在 D-N1-4 决策(adapter 层在 decode 后裁剪像素,还是 Core 在 detection 前接收 region)。倾向 adapter 层裁剪:保持 Core 边界不变,EXIF 修正后裁剪天然落在 pageSpace,与坐标契约一致。
roadmap §5.4:detection 在 Core 中本就是独立 stage。此出口只把已有能力暴露为公共入口,不新增算法,不改 recognition 语义。
detect子命令仅运行 detector,输出检测框(与 OCRline.box相同的pageSpacequad 契约),不触发 recognition;detect --crop可选返回每个区域的 PNG crop,与检测框 index 对齐,便于喂给下游模型、版面分析、计数或 redaction;- 与 ROI 互补不重叠:ROI 是输入侧区域约束(
--region,recognize/detect 均可带),detect 是输出侧能力裁剪;detect image.png --region 100,80,640,320 --crop= 只在该矩形内检测并附 crop; - 不是 Layout 替代:只给原始检测框,不附加 region label、阅读顺序或语义分类。
实现要求:Core Engine 当前公共接口仅 recognize/info/close,无 detection-only 公共方法。需在 Core 新增虚函数 Engine::detect(ImageView, DetectOptions) -> Result<DetectionResult>,内部复用现有 detection stage,跳过 crop+recognition。这是 N1 里唯一的 Core 改动,必须在 D-N1-3 锁定方法签名、返回结构(box quad + score,可选 crop bytes)与 CoreErrorCode 映射后再实现。Node addon 增加 detect 绑定,JS OcrEngine.detect() 对称暴露。
roadmap §3.2:OcrResult 继续作为单张已解码图片的底层语义契约,CLI 在其外部增加文档级 envelope。
{
"schemaVersion": 1,
"source": {
"kind": "image",
"mediaType": "image/jpeg",
"identity": { "path": "image.png" },
"appliedTransforms": { "exifOrientation": 6, "exifApplied": true, "..." : "..." }
},
"pages": [
{
"index": 0,
"width": 3000,
"height": 4000,
"coordinateSpace": "pageSpace",
"structure": "ocr-order",
"lines": [
{
"id": "L0",
"text": "...",
"confidence": 0.987,
"box": [ { "x": 0, "y": 0 }, { "x": 100, "y": 0 }, { "x": 100, "y": 30 }, { "x": 0, "y": 30 } ]
}
],
"modelBundleId": "ppocrv6-small-...",
"timingUs": { "..." : "..." }
}
]
}detect 子命令输出下 pages[0].lines 替换为 detections[],structure: "detect":
"detections": [
{
"id": "D0",
"score": 0.92,
"box": [ { "x": 0, "y": 0 }, { "x": 100, "y": 0 }, { "x": 100, "y": 30 }, { "x": 0, "y": 30 } ],
"crop": "base64...(仅 --crop 时出现)"
}
]--crop 的 PNG crop bytes 以 base64 编码出现在每个 detection 的 crop 字段,与检测框 id/index 对齐。crop 编码方式(base64 vs 二进制 sidecar)在 D-N1-3 锁定。
schemaVersion是整数主版本。兼容新增保持 v1;删除、重命名或语义变化必须增加版本;--schema-version 1请求精确 schema,不支持返回invalid_argument(不静默降级);- 结果本身始终携带
schemaVersion; OcrLine.id是 CLI envelope 新增的稳定 line ID(L{index}),用于未来 Layout region 关联与 Markdown 追溯;底层OcrResult.line不变。
- 单张图片表现为
pages[0];JSONL 对图片输出一条 page record(即 envelope 内的单个 page); - 每条 page record 带 document identity、page index 和
status(ok/error); - 中途取消或失败时,已完成记录保持有效,stderr 给出终态,进程返回非零 exit code;
--format text不使用 JSONL,按行输出纯文本。
稳定 exit code,区分输入、能力、资源、模型和内部错误(roadmap §5.2)。映射现有 OcrErrorCode,最终码在 D-N1-7 锁定:
| exit | 类别 | 触发 code | 含义 |
|---|---|---|---|
| 0 | 成功 | — | 正常输出 |
| 64 | usage | —(参数解析失败、缺文件、互斥 flag 冲突) | 命令行用法错误 |
| 65 | 输入 | invalid_argument |
ROI 越界、不支持 flag 组合、--schema-version 不支持 |
| 66 | 输入 | invalid_image / unsupported_pixel_format |
图片不可解码或不支持格式 |
| 67 | 能力 | unsupported_capability |
--detect-only 时 rec 不可用等 |
| 68 | 模型 | invalid_model_bundle / unsupported_model / model_integrity_failed |
bundle 损坏或 hash 不符 |
| 69 | 资源 | resource_limit_exceeded |
超像素/内存/timeout 上限 |
| 70 | 环境/包 | runtime_initialization_failed / package_load_failed / unsupported_platform / bundle_io_failed / adapter_unavailable / package_corrupt |
runtime/包加载失败 |
| 71 | 运行 | inference_failed / postprocess_failed |
推理或后处理失败 |
| 72 | 内部 | internal_error / environment_closing / queue_full |
内部错误或引擎已关闭 |
D112 Auto 创建期可跳过原因(adapter_unavailable 等)通过 creationTrace 在 stderr 报告,最终仍按对应 exit code 退出。exit code 表一旦发布即为 stable surface,后续只允许新增码,不重排现有码(roadmap §2.6)。
roadmap §5.6:仓库内 .agents/skills/local-ocr/SKILL.md,是 CLI 的薄工作流层,不实现识别/坐标/schema 逻辑。内容至少覆盖:
- 何时使用 OCR,而不是让多模态模型猜测小字;
- 如何选择全文、ROI、text、JSON 与 diagnostics;
- 如何处理低置信度、空结果、超限和 unsupported capability;
- 如何只读取必要页面或区域,避免无界批处理;
- 如何引用文字及坐标,避免把推断写成 OCR 原文;
- 可执行 CLI 示例和小型验证脚本。
验证稳定后再打包为可安装 Plugin;本地文件 OCR 暂不需要 MCP server(roadmap §13)。
- stdout 始终可被
JSON.parse(--format json)或逐行消费(--format jsonl/text); - 退出码与错误类别一一对应,Agent 可据此决定重试/放弃/换输入;
info --model-info与info --version不读图、不触发 model load 之外的计算,可安全探测;--schema-version固定输出,不随默认漂移;- 任何 failure 路径 stderr 给出
OcrError.code+ 人类消息 + 可选 detail,stdout 为空; - ROI 越界、空图、unsupported capability 均稳定失败,不产出伪结果。
建议按依赖与风险递增分步交付,每步可独立验证:
- CLI bin 骨架 + 子命令 dispatch + 基本识别:
recognize(含隐式默认)、文件/stdin →--format json|text、info --model-info/info --version、stdout/stderr 分离、exit code 表(不含 ROI/detect/EXIF)。复用现有recognizeEncoded; ✅ 完成 - schemaVersion=1 envelope:
DocumentResult/page/line.id、JSONL 单页 record、--schema-version; ✅ 完成 - EXIF orientation 修正 + appliedTransforms(D-N1-5 实现方式); ✅ 完成(JS 解析器 + C++ 像素变换 +
applyExifoption) - ROI
--region(D-N1-4 实现位置); ✅ 完成(adapter C++ 层裁剪 + box offset 重映射) detect子命令:CoreEngine::detect()公共方法 + Node 绑定 +detect [--crop](D-N1-3 签名); ⬜ 进行中- Agent Skill
.agents/skills/local-ocr/SKILL.md; ✅ 完成 - 验收:Tier 1 CLI smoke、schema snapshot、20 个 Agent task eval。 ⬜ 部分(52 单元测试,端到端待 native build)
第 5 步是唯一的 Core C++ 改动,风险最高,单独评审。
沿用 roadmap §5.7:
- Tier 1 平台 Node.js 22/24 均通过
npm install后 CLI smoke; - CJS、ESM、Node API 和 CLI 对同一输入返回语义一致的结果;
- JSON/JSONL 使用 committed schema 和 snapshot 测试;
- stdin、文件路径、ROI、EXIF、退出码和 stderr/stdout 分离有测试;
- 禁网、sterile cwd、
--ignore-scripts安装继续通过; - 至少 20 个 Agent task eval 覆盖全文、指定区域、低置信度和错误恢复;
- Agent eval 至少 18/20 通过,且任何失败不能把推断内容伪装成 OCR 原文;
- 一个不熟悉内部架构的读者能只凭 README/SKILL 完成首次 OCR。
- 目录递归、glob、watch mode、交互式 UI(roadmap §5.2);
- 任意多边形 ROI、多个 ROI 合批、仅对已知 line crop 执行 recognition(roadmap §5.3);
- Layout region label、阅读顺序、语义分类(
detect子命令不做,属 N4); - MCP server(roadmap §13);
- 字符级或词级坐标(recognition contract 以文字行为单位)。
以下在进入实现前需维护者确认,确认后提炼为 decisions.md 的 D106。已确认项不再列出(CLI 分发形态 = runtime 共享实现 + facade bin;--region = 整数像素;--no-color/--quiet 默认行为 = 非 TTY 或 NO_COLOR 自动关闭彩色)。
- D-N1-2 argv parser:手写最小 zero-dependency parser,还是引入轻量零依赖库。
- D-N1-3 Core
Engine::detect()公共方法签名与DetectionResult结构(box quad + score),以及--crop的 PNG crop 编码方式(base64 内联 vs 二进制 sidecar)与CoreErrorCode映射。 - D-N1-4 ROI 实现位置:建议 adapter 层(EXIF 修正后、送 Core 前裁剪像素),保持 Core 边界不变。
- D-N1-5 EXIF 解析实现方式:自带最小 JPEG APP1 EXIF 解析器(零依赖,与 stb 风格一致),还是换用支持 EXIF orientation 的 decode 路径。
- D-N1-7 exit code 表最终映射(§10),含 D112 Auto 创建期可跳过原因的 stderr 报告格式。