# light-ocr CLI 设计草案
Status: Draft(2026-07-21,落地前细化设计)
Authority: N1 CLI、结果契约、ROI、detect-only 出口与 Agent Skill 的实现与契约规范
Requirements: [requirements.md](requirements.md)
Roadmap: [roadmap.md §5 N1](roadmap.md)
Architecture: [architecture.md](architecture.md)
本草案是 [roadmap §5](roadmap.md) 引用的「落地前细化设计」。它不替代 [decisions.md](decisions.md) 中的 D106 决策记录,而是 D106 的前置设计材料。所有标记 **D-N1-x** 的条目为待维护者确认的开放决策。
## 1. 范围与现状
N1 的目标是让普通用户和 Agent 无需编写 Node.js 集成代码,即可从本地图片获得稳定文本、置信度和坐标,并建立 PDF、Layout 和多模型都能复用的版本化结果契约。
### 1.1 现有可复用基础
对照当前源码(`0.3.1`),N1 的底层能力已经具备:
| 能力 | 现状 | 来源 |
| --- | --- | --- |
| `createEngine` / `OcrEngine.recognize(RawImage)` / `recognizeEncoded(Uint8Array)` / `info` / `close` | 已发布 | [packages/runtime/src/index.d.ts](../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](../bindings/node/src/encoded_image.cpp) |
| 四平台 prebuild、离线 bundle 解析、provider Auto | 已发布 | [implementation-status.md](implementation-status.md) |
### 1.2 本节点需新增的能力
| 能力 | 现状 | 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` |
## 2. 分发形态与命令结构
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)。
### 2.1 子命令结构
N1 面向 Agent,采用子命令结构而非扁平 flag:Agent 先从顶层动词锁定意图,再看该动词下的 flags,互斥约束收窄到子命令内。代价是 `recognize`/`detect`/`info` 三个动词的 help 与退出码需分别维护,但换来 Agent 可逐步理解的入口结构。
```text
light-ocr recognize [flags] # 默认 OCR:detection + recognition
light-ocr detect [flags] # 只检测:检测框,不识别
light-ocr info [--model-info|--version] # 诊断,不读图
light-ocr [recognize] [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 ` 和 `light-ocr document
` 提供;旧 `light-ocr-document` bin 只作兼容转发(D110)。
> **与 [roadmap §5.2](roadmap.md) 的偏离说明**:roadmap 原文写「`--model-info` 和 `--version`」为顶导 flag,本设计改为 `info` 子命令以保持与 `recognize`/`detect` 的结构统一;语义等价。该偏离在 D106 决策中记录。
## 3. CLI 命令面
第一版保持命令面小而稳定([roadmap §5.2](roadmap.md)):
```bash
# 默认 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
```
### 3.1 各子命令的 flags
每个 flag 只挂在自己相关的子命令下。第一版只暴露 Agent/用户真实高频的入口层选项;provider 内部实现细节(session fallback、cpu partition、precision、detection strategy、max-side)不透传成 CLI flag,由 runtime 按 provider 默认处理。需要这些内部字段时用 Node API 或 `info --model-info`。
#### `recognize`(默认 OCR:detection + recognition)
默认 help 顶部(高频):
| Flag | 取值 | 语义 |
| --- | --- | --- |
| `` 位置参数 | 文件路径 | 必须与 `--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`。
#### `detect`(只检测:检测框,不识别)
| Flag | 取值 | 语义 |
| --- | --- | --- |
| `` / `--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 置信度内部用校准默认)。
#### `info`(诊断,不读图)
| Flag | 取值 | 语义 |
| --- | --- | --- |
| `--model-info` | flag | 输出 `EngineInfo` JSON(含 coreVersion、modelBundleId、execution、capabilities、limits) |
| `--version` | flag | 输出 npm/core/model 版本三元组 |
`info` 子命令约束:
- 不接受 ``、`--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](roadmap.md))。
## 4. 分层 help
子命令结构天然分层,Agent 可逐步理解:
1. `light-ocr --help`:列出三个子命令 + 各自一句话职责 + 一个完整示例;
```
Commands:
recognize Recognize text in an image (default action)
detect Detect text regions only, no recognition
info [--model-info|--version] Show engine/version info without reading images
```
2. `light-ocr --help`:该子命令的全部 flags 分组(输入/输出/能力/执行/资源/行为);
3. `light-ocr --=help`(如 `light-ocr recognize --region=help`):该 flag 的精确语义、坐标系、失败行为与示例。
Agent 与脚本应使用 `info --model-info`、`info --version` 和固定 `--schema-version`,不解析 help 文本。
## 5. stdout/stderr 严格分离
这是 N1 的硬约束([roadmap §5.2](roadmap.md)),不可回退:
- **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`。
## 6. 坐标与 EXIF
### 6.1 坐标词汇表(N1 冻结,后续只扩展)
沿用 [roadmap §3.3](roadmap.md):
| 术语 | 定义 |
| --- | --- |
| `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 |
### 6.2 EXIF orientation
[roadmap §5.5](roadmap.md) 要求对 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(可含 `eXIf` chunk,首版按无变换处理);
- `--no-exif` 关闭修正,`appliedTransforms` 仍记录「未应用」状态;
- raw-pixel API(`recognize(RawImage)`)继续由调用者负责方向,传入像素直接定义 `pageSpace`;
- 修正后的 `imageWidth/Height` 与 `line.box` 全部落在 `pageSpace`。
### 6.3 appliedTransforms 结构
```json
"appliedTransforms": {
"exifOrientation": 6,
"exifApplied": true,
"sourceWidth": 4000,
"sourceHeight": 3000,
"pageWidth": 3000,
"pageHeight": 4000
}
```
## 7. ROI 语义
[roadmap §5.3](roadmap.md):ROI 是输入侧的区域约束,不是 Layout 替代品。
- 第一版只接受位于方向修正后完整有效页面 `pageSpace` 内的轴对齐矩形 `--region x,y,width,height`(整数像素);
- 在进入完整 OCR pipeline 前限制检测/识别范围;
- 返回的 quad 坐标重新映射到完整有效页面的 `pageSpace`(不是 ROI 局部坐标);
- 非法、空或越界区域返回 `invalid_argument`,**不隐式 clamp**;部分相交也返回 `invalid_argument`([roadmap §3.3](roadmap.md));
- ROI 仍受像素、临时内存、candidate 和 timeout 上限约束;
- `--region` 与 `detect` 子命令可组合:限制检测范围 + 只输出检测框。
ROI 的实现位置在 D-N1-4 决策(adapter 层在 decode 后裁剪像素,还是 Core 在 detection 前接收 region)。倾向 adapter 层裁剪:保持 Core 边界不变,EXIF 修正后裁剪天然落在 `pageSpace`,与坐标契约一致。
## 8. detect 子命令
[roadmap §5.4](roadmap.md):detection 在 Core 中本就是独立 stage。此出口只把已有能力暴露为公共入口,不新增算法,不改 recognition 语义。
- `detect` 子命令仅运行 detector,输出检测框(与 OCR `line.box` 相同的 `pageSpace` quad 契约),不触发 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`,内部复用现有 detection stage,跳过 crop+recognition。这是 N1 里唯一的 Core 改动,必须在 D-N1-3 锁定方法签名、返回结构(box quad + score,可选 crop bytes)与 CoreErrorCode 映射后再实现。Node addon 增加 `detect` 绑定,JS `OcrEngine.detect()` 对称暴露。
## 9. 结果契约(schemaVersion=1)
[roadmap §3.2](roadmap.md):`OcrResult` 继续作为单张已解码图片的底层语义契约,CLI 在其外部增加文档级 envelope。
### 9.1 DocumentResult envelope(v1)
```json
{
"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"`:
```json
"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 锁定。
### 9.2 版本与稳定性
- `schemaVersion` 是整数主版本。兼容新增保持 v1;删除、重命名或语义变化必须增加版本;
- `--schema-version 1` 请求精确 schema,不支持返回 `invalid_argument`(不静默降级);
- 结果本身始终携带 `schemaVersion`;
- `OcrLine.id` 是 CLI envelope 新增的稳定 line ID(`L{index}`),用于未来 Layout region 关联与 Markdown 追溯;底层 `OcrResult.line` 不变。
### 9.3 JSONL 分页语义
[roadmap §3.3](roadmap.md):
- 单张图片表现为 `pages[0]`;JSONL 对图片输出一条 page record(即 envelope 内的单个 page);
- 每条 page record 带 document identity、page index 和 `status`(`ok` / `error`);
- 中途取消或失败时,已完成记录保持有效,stderr 给出终态,进程返回非零 exit code;
- `--format text` 不使用 JSONL,按行输出纯文本。
## 10. 退出码表
稳定 exit code,区分输入、能力、资源、模型和内部错误([roadmap §5.2](roadmap.md))。映射现有 `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.md))。
## 11. Agent Skill
[roadmap §5.6](roadmap.md):仓库内 `.agents/skills/local-ocr/SKILL.md`,是 CLI 的薄工作流层,不实现识别/坐标/schema 逻辑。内容至少覆盖:
- 何时使用 OCR,而不是让多模态模型猜测小字;
- 如何选择全文、ROI、text、JSON 与 diagnostics;
- 如何处理低置信度、空结果、超限和 unsupported capability;
- 如何只读取必要页面或区域,避免无界批处理;
- 如何引用文字及坐标,避免把推断写成 OCR 原文;
- 可执行 CLI 示例和小型验证脚本。
验证稳定后再打包为可安装 Plugin;本地文件 OCR 暂不需要 MCP server([roadmap §13](roadmap.md))。
## 12. Agent 友好性 checklist
- 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 均稳定失败,不产出伪结果。
## 13. 实施顺序
建议按依赖与风险递增分步交付,每步可独立验证:
1. **CLI bin 骨架 + 子命令 dispatch + 基本识别**:`recognize`(含隐式默认)、文件/stdin → `--format json|text`、`info --model-info`/`info --version`、stdout/stderr 分离、exit code 表(不含 ROI/detect/EXIF)。复用现有 `recognizeEncoded`; ✅ 完成
2. **schemaVersion=1 envelope**:`DocumentResult`/page/`line.id`、JSONL 单页 record、`--schema-version`; ✅ 完成
3. **EXIF orientation 修正 + appliedTransforms**(D-N1-5 实现方式); ✅ 完成(JS 解析器 + C++ 像素变换 + `applyExif` option)
4. **ROI `--region`**(D-N1-4 实现位置); ✅ 完成(adapter C++ 层裁剪 + box offset 重映射)
5. **`detect` 子命令**:Core `Engine::detect()` 公共方法 + Node 绑定 + `detect [--crop]`(D-N1-3 签名); ⬜ 进行中
6. **Agent Skill** `.agents/skills/local-ocr/SKILL.md`; ✅ 完成
7. **验收**:Tier 1 CLI smoke、schema snapshot、20 个 Agent task eval。 ⬜ 部分(52 单元测试,端到端待 native build)
第 5 步是唯一的 Core C++ 改动,风险最高,单独评审。
## 14. 验收与退出条件
沿用 [roadmap §5.7](roadmap.md):
- 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。
## 15. 本节点不做
- 目录递归、glob、watch mode、交互式 UI([roadmap §5.2](roadmap.md));
- 任意多边形 ROI、多个 ROI 合批、仅对已知 line crop 执行 recognition([roadmap §5.3](roadmap.md));
- Layout region label、阅读顺序、语义分类(`detect` 子命令不做,属 N4);
- MCP server([roadmap §13](roadmap.md));
- 字符级或词级坐标(recognition contract 以文字行为单位)。
## 16. 待决策项(D-N1)
以下在进入实现前需维护者确认,确认后提炼为 [decisions.md](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 报告格式。