{"id":50884106,"url":"https://github.com/crhan/contract-archive-cli","last_synced_at":"2026-06-15T15:02:20.747Z","repository":{"id":360115977,"uuid":"1248651479","full_name":"crhan/contract-archive-cli","owner":"crhan","description":"合同档案库 CLI：MinerU 解析 + qwen3.7-max 字段抽取 + SQLite 索引","archived":false,"fork":false,"pushed_at":"2026-06-12T01:36:02.000Z","size":1278,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-06-12T03:12:30.389Z","etag":null,"topics":["cli","contract","dashscope","mineru","ocr","pdf","sqlite"],"latest_commit_sha":null,"homepage":null,"language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/crhan.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-05-24T23:07:23.000Z","updated_at":"2026-06-12T01:36:06.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/crhan/contract-archive-cli","commit_stats":null,"previous_names":["crhan/contract-archive-cli"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/crhan/contract-archive-cli","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/crhan%2Fcontract-archive-cli","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/crhan%2Fcontract-archive-cli/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/crhan%2Fcontract-archive-cli/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/crhan%2Fcontract-archive-cli/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/crhan","download_url":"https://codeload.github.com/crhan/contract-archive-cli/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/crhan%2Fcontract-archive-cli/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34367696,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-15T02:00:07.085Z","response_time":63,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["cli","contract","dashscope","mineru","ocr","pdf","sqlite"],"created_at":"2026-06-15T15:02:20.069Z","updated_at":"2026-06-15T15:02:20.731Z","avatar_url":"https://github.com/crhan.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"# 本地文档档案库 CLI\n\n\u003e 把各类文档 PDF 批量入库、归档、可追溯——合同、协议、证明、发票、报告……\n\u003e OCR 提取版面文本，qwen3.7-max LLM 判类型 + 抽字段，索引到本地 SQLite，\n\u003e 支持按类型/字段过滤检索。\n\n**LLM-first**：文档类型与字段抽取都交给 LLM，统一归一化到一个「通用信封」\n（doc_type / title / summary / 主体 / 金额 / 日期 / 柔性字段）。加新文档类型\n**无需写代码**——LLM 自行决定抽什么。死代码 rule 仅保留为确定性数值归一化\n（中文大写金额→数值、日期→ISO）。合同另有一份调校过的专属 prompt（同样纯 LLM），\n仍保留全部合同字段与查询（甲乙方/到期日/自动续约/风险条款/义务清单）。\n\n历史：本项目最初是多路 OCR 对比 playground，后重构为档案库 CLI；\n再从「合同专用」扩展为「通用文档档案库」。\n\n## ✦ 数据流\n\n```\nPDF ─► sha256 去重 ─► OCR 提取（页级分流：文本页原生 / 扫描·表格页 VL OCR 混合提取）\n                          │\n                          ▼\n              LLM 判类型 ─► doc_type → handler 特化抽取\n                          ├─ 合同：专属 prompt + 看落款页签章核查\n                          └─ 保险：多源融合（A 文本 / C 看图两路评判 → field_verdicts sidecar）\n                                          │\n              ┌───────────────────────────┴──┐\n              ▼                              ▼\n   db.sqlite (通用信封 + 索引)     documents/\u003csha-12\u003e/\n                                    ├── source.pdf  (硬链接)\n                                    ├── ocr/markdown.md ...\n                                    ├── extraction_result.json  (通用信封 + 融合 sidecar)\n                                    └── ingest.log\n\n档案库默认在 XDG 数据目录：~/.local/share/contract-archive/\n```\n\n## ✦ 安装\n\n```bash\n# 1) 装 uv\ncurl -LsSf https://astral.sh/uv/install.sh | sh\n\n# 2) 装依赖\n./scripts/setup.sh\n```\n\n\u003e **uv hardlink 坑**：uv 默认 `UV_LINK_MODE=hardlink` 偶发只装包的一部分文件\n\u003e （实测 `cv2`/`pptx` 会丢，触发 `module 'cv2' has no attribute 'INTER_NEAREST'`\n\u003e 或 `cannot import name 'Presentation' from 'pptx'`）。`scripts/setup.sh` 已\n\u003e 显式 `export UV_LINK_MODE=copy` 规避。手动 `uv sync` 时建议也带上。\n\u003e 已损坏的包可以 `uv pip install --force-reinstall --no-deps \u003c包名\u003e` 修。\n\n## ✦ 全局安装（可选）\n\n如果想在任意目录用 `contract-archive`（不必 `cd` 项目目录或 `uv run`），用 `uv tool install`：\n\n```bash\n# 用 --reinstall 而非 --force：版本号没变时 --force 会命中 uv 缓存里的旧 wheel，\n# 把过时代码装进去（实测会停在旧版本）；--reinstall 强制重建，更新才可靠。\nUV_LINK_MODE=copy uv tool install --reinstall \"/path/to/contract-archive-cli\"\n```\n\n`uv tool install` 会在 `~/.local/bin/contract-archive` 装独立 venv（与项目 venv 隔离）。\n然后从任意目录：\n\n```bash\n# 用环境变量指定档案库\nCONTRACT_ARCHIVE_DIR=~/contracts contract-archive list\n\n# 或显式 --archive（per-command 选项，放在子命令之后）\ncontract-archive list --archive ~/contracts\ncontract-archive ingest ~/Documents/new_contract.pdf --archive ~/contracts\n```\n\n`DASHSCOPE_API_KEY` 建议用 `contract-archive config set dashscope.api_key ...` 配置；\n也可以通过 shell env / `.env` 提供。\n\n**开发者（改了源码要即时生效）**：加 `--editable`，全局命令指向本仓库源码而非快照——\n改完 `.py` 直接生效，不必每次重装：\n\n```bash\nUV_LINK_MODE=copy uv tool install --editable --reinstall \"/path/to/contract-archive-cli\"\n```\n\n\u003e 不加 `--editable` 装的是「当下代码的快照」：之后改了源码、或仓库升了版本，都得\n\u003e 重新 `--reinstall` 才更新。如果发现 `contract-archive --version` 跟仓库 `pyproject.toml`\n\u003e 对不上，多半就是装了旧快照——重装即可。\n\n卸载：\n\n```bash\nuv tool uninstall contract-archive-cli\n# 数据/配置不随之删除，需手动清理：\n#   ~/.local/share/contract-archive   档案库数据（db.sqlite + documents/）\n#   ~/.config/contract-archive        config.json\n```\n\n## ✦ 配置\n\n两种方式，优先级 **环境变量(含 .env) \u003e config 文件 \u003e 默认值**。\n日常使用推荐 `config` 命令；`.env.example` 仍保留，用于项目内开发、Docker、临时覆盖模型，\n以及 env-only 的运行时旋钮。\n\n```bash\n# 方式一：config 命令（落 ~/.config/contract-archive/config.json，权限 0600，比项目 .env 更安全）\ncontract-archive config set dashscope.api_key sk-xxx\ncontract-archive config show              # 看各项当前生效值与来源（secret 默认掩码）\ncontract-archive config show --format json   # 机读：含 key/env/secret/default/value/source\ncontract-archive config unset dashscope.api_key\n\n# 方式二：项目 .env（开发 / Docker / 临时覆盖用，仍支持）\ncp .env.example .env\n$EDITOR .env   # 填入 DASHSCOPE_API_KEY\n```\n\n| 环境变量 | config 键 | 说明 |\n| --- | --- | --- |\n| `DASHSCOPE_API_KEY` | `dashscope.api_key` | 必填。[百炼控制台](https://dashscope.console.aliyun.com/) 申请 |\n| `DASHSCOPE_LLM_MODEL` | `dashscope.model` | 默认 `qwen3.7-max`（用户百炼账户的特定别名；若 404 换 `qwen-max` / `qwen3-max`） |\n| `DASHSCOPE_BASE_URL` | `dashscope.base_url` | 默认 `https://dashscope.aliyuncs.com/api/v1`；海外换 `https://dashscope-intl.aliyuncs.com/api/v1` |\n| `DASHSCOPE_OCR_MODEL` | `dashscope.ocr_model` | 逐页 VL OCR 模型，默认 `qwen-vl-ocr-latest` |\n| `DASHSCOPE_VL_MODEL` | `dashscope.vl_model` | 签章核查视觉模型，默认 `qwen3.6-flash` |\n| `DASHSCOPE_VL_EXTRACT_MODEL` | `dashscope.vl_extract_model` | 多源融合\"看图抽字段\"视觉模型，默认 `qwen3.6-flash` |\n| `CONTRACT_ARCHIVE_DIR` | `archive.dir` | 档案库根目录，默认 XDG `~/.local/share/contract-archive`；CLI `--archive` 优先 |\n| `COMPUTE_DEVICE` | — | `auto` / `mps` / `cuda` / `cpu`（记录运行环境，默认自动选择） |\n| `LOG_LEVEL` | — | `DEBUG`/`INFO`/`WARNING`/...，默认 `INFO`；`--verbose`/`--quiet` 覆盖之 |\n| `DASHSCOPE_TIMEOUT_S` | — | LLM/VL 调用超时秒数，默认 `300` |\n| `CONTRACT_ARCHIVE_LLM_CONCURRENCY` | — | LLM 调用并发度（OCR/看图/评判共用线程池），默认 `4` |\n| `CONTRACT_ARCHIVE_VL_OCR_MAX_PAGES` | — | 单文档允许走 VL OCR 的最大页数，默认 `500` |\n| `CONTRACT_ARCHIVE_VL_OCR_DPI` | — | VL OCR 渲染页图 DPI，默认 `160` |\n| `CONTRACT_ARCHIVE_VL_OCR_RETRIES` | — | 逐页 VL OCR SDK 重试次数，默认 `4` |\n| `CONTRACT_ARCHIVE_VISION_FUSION_MAX_PAGES` | — | 融合看图单文档最多看几页（优先表格/扫描页），默认 `20` |\n| `CONTRACT_ARCHIVE_FUSION_THRESHOLD` | — | 融合低置信阈值 `[0,1]`，低于触发 agent 兜底，默认 `0.6` |\n| `CONTRACT_ARCHIVE_EVALSET_DIR` | — | 评测私有数据集根目录（仅开发/质量门禁用）；不设回退主仓库内合成 cases |\n\n\u003e 标 `—` 的是运行时旋钮，保持 env-only、不进 config 文件层。\n\n## ✦ 用法\n\n```bash\n# 入库单个 PDF\nuv run contract-archive ingest path/to/合同.pdf\n\n# 批量入库整个目录（递归扫 *.pdf，sha256 去重）\nuv run contract-archive ingest ~/Documents/contracts/\n\n# 跳过字段抽取：仅入库 OCR 产物，抽取字段留空，可后续 extract 补抽\nuv run contract-archive ingest path/to/合同.pdf --no-llm\n\n# 强制重跑（已 ingest 过的也再跑一遍，覆盖旧记录）\nuv run contract-archive ingest path/to/合同.pdf --reingest\n\n# 试跑前 3 个\nuv run contract-archive ingest ~/Documents/contracts/ --limit 3\n\n# 成本/进度（agent 友好）\nuv run contract-archive ingest ~/Documents/contracts/ --dry-run        # 只预览扫到几个、预计几次 API 调用，不建库不烧钱\nuv run contract-archive ingest ~/Documents/contracts/ --max-files 20   # 超 20 个直接报错退出，防误喂大目录\nuv run contract-archive ingest ~/Documents/contracts/ --progress ndjson  # 每文件一行 JSON 事件，供 agent 流式消费\n```\n\n### 查询\n\n```bash\n# 列出全部（按入库时间倒序，默认 50 条）\nuv run contract-archive list\n\n# 按签订日排序，只看 partial 的\nuv run contract-archive list --order-by sign_date --status partial\n\n# 输出 JSON 供脚本消费\nuv run contract-archive list --format json | jq '.[] | .contract_name'\n\n# 多字段过滤（全部 AND）\nuv run contract-archive search --party 张三 --amount-min 100000 --signed-after 2024-01-01\nuv run contract-archive search --expire-before 2026-12-31 --has-risk\nuv run contract-archive search --name 车位 --auto-renewal\n\n# 看单条详情（id 或 sha 前缀 ≥4 字符）\nuv run contract-archive show 5\nuv run contract-archive show a3f9c2b1\n\n# 看原文：show 看 LLM 抽出的字段，raw 看抽取依据的 OCR 原始文本（同一份喂给 LLM 的内容）\n# 交互终端下按抽取来源给命中关键字着色（当事人/金额/日期/风险/字段），一眼看出哪些被识别到\nuv run contract-archive raw 5\nuv run contract-archive raw a3f9c2b1 | grep 违约           # 管道时自动纯文本，不破坏 grep\nuv run contract-archive raw 5 --color always | less -R     # 强制上色配 less -R\n```\n\n### 待办看板（义务清单）\n\n每份合同抽取时会拆出双方\"动作\"（递交资料/付款/交付/签字等）作为\n独立的 `obligations` 表，每条带 `actor` (甲方/乙方/双方) + `deadline`：\n\n```bash\n# 跨合同列出所有待办（按 deadline 升序，NULL 排最后）\ncontract-archive todo --include-undated\n\n# 未来 30 天内要做的事\ncontract-archive todo --within-days 30\n\n# 只看甲方任务 / 只看乙方任务\ncontract-archive todo --actor party_a\ncontract-archive todo --actor party_b --before 2026-12-31\n\n# 找\"近 30 天内有截止动作的合同\"（不是单条 obligation，而是合同列）\ncontract-archive search --deadline-before 2026-06-30 --actor party_b\n```\n\n`contract-archive show \u003cid\u003e` 会按甲方/乙方/双方分组展示该合同所有义务，\n与原本的 `risk_clauses`（违约罚则）严格区分。\n\n### 身份核对（known_parties 基准库）\n\n抽取时把每个主体（自然人/机构）与其固有标识（身份证号/电话/银行账号/开户行/税号…）\n**精确绑定到人**（`person_identities`），不像扁平字段那样把多人号码混成一条。\n入库时与 `known_parties` 基准库比对，采用「首见入库、再见校对」：\n\n- **首见**：某主体的某标识第一次出现 → 录入为基准（记首见出处）。\n- **再见**：同主体同标识再出现 → 与基准比对，不一致即在 `show` 的「身份核对」块报\n  `identity` 缺陷（疑似 OCR 读错或信息被改），**不覆盖基准**。\n- 比较前归一化剥离分隔符噪声（空格/；/：不误报），但多/少/错位的真实数字差异会被抓出。\n- 不分自然人/机构——身份证、电话、银行账号、开户行一律核对。\n\n```bash\ncontract-archive party list                       # 列出所有已知主体及标识\ncontract-archive party show 张三                   # 查看某主体的标识基准\ncontract-archive party set 张三 身份证号 1101...   # 手动修正基准（纠正被 OCR 读错的首见值）\ncontract-archive party rm 张三 电话                # 删除某标识；省略标识则删整个主体\n```\n\n\u003e 基准库 `known_parties.json` 存档案库根目录，**含真实 PII**（身份证/电话/账号），\n\u003e 文件权限 0600、列入 `.gitignore`，绝不入库或分享。\n\n### 抽取层管理\n\nLLM 跑挂或想升级 prompt 后批量再抽取——不重跑 OCR：\n\n```bash\nuv run contract-archive extract 5            # 复跑 id=5 的抽取\nuv run contract-archive extract 5 --no-llm   # 跳过 LLM（抽取字段留空，rule 已退役）\n```\n\n### 统计与维护\n\n```bash\nuv run contract-archive stats                # 总数 / status 分布 / 按月签订 / 近 30 天到期\nuv run contract-archive delete 5             # 默认仅删 DB 行，交互确认\nuv run contract-archive delete 5 --purge-files -y    # 同时删 archive/documents/\u003csha\u003e/，无确认\nuv run contract-archive vacuum               # 大批量 ingest 后整理碎片\n```\n\n\u003e **注意**：`delete` 不会删用户原 PDF 文件——`source_path` 字段记录的是入库时\n\u003e 的源路径，源文件归用户所有。\n\n### 印章总览\n\n```bash\nuv run contract-archive seals                      # 跨文档列全部印章\nuv run contract-archive seals --seal-owner 示例公司   # 某主体的章（--owner 同义）\nuv run contract-archive seals --seal-type 合同专用章   # 按印章类型（--type 同义）\n```\n\n### 机器发现 / agent 接入\n\n把本 CLI 包成 MCP / OpenAI tool，或让 agent 自动调用时，用这几个命令免去硬编码——输出皆 JSON：\n\n```bash\nuv run contract-archive capabilities          # 全部命令 + 副作用/破坏性/幂等元数据\nuv run contract-archive describe ingest       # 单命令参数 schema（名称/类型/必填/默认/可选值）\nuv run contract-archive schema document        # 核心数据结构 JSON Schema（document/contract/confidence/error）\n```\n\n数据命令（list/search/show/stats/todo/seals/party/extract/ingest）都支持 `--format json`，\nstdout 纯净可 `| jq`；失败结果带结构化 `error`（`code`/`category`/`retryable`），供 agent 判是否重试。\n\n## ✦ 档案库目录结构\n\n```\narchive/\n├── db.sqlite                     # 索引表\n├── db.sqlite-wal / -shm          # WAL 模式产物（运行时）\n├── ingest.jsonl                  # 总日志（每次 ingest 一行 JSON）\n└── documents/\n    └── a3f9c2b1/                 # sha256 前 12 位\n        ├── source.pdf            # 硬链接源 PDF（跨盘 fallback copy）\n        ├── ocr/\n        │   ├── markdown.md\n        │   ├── layout.json       # bbox 已归一到 PDF point\n        │   ├── structured.json\n        │   ├── raw_text.txt\n        │   ├── pipeline_meta.json\n        │   └── preview_images/\n        ├── extraction_result.json    # 抽取字段（通用信封）\n        ├── extraction_confidence.json\n        └── ingest.log            # 单合同 stderr\n```\n\n## ✦ Docker\n\n```bash\ndocker build -t contract-archive -f docker/Dockerfile .\ndocker run --rm -it \\\n  -v $PWD/archive:/app/archive \\\n  -v $PWD/input:/app/input \\\n  --env-file .env \\\n  contract-archive uv run contract-archive ingest /app/input\n```\n\n## ✦ 项目结构\n\n```\ncontract-archive-cli/\n├── pyproject.toml          # uv 依赖管理\n├── docker/Dockerfile\n├── .env.example\n├── scripts/\n│   └── setup.sh\n├── contract_archive/\n│   ├── cli.py              # 入口 main_entry + 写命令 ingest/extract/delete/vacuum + 组装\n│   ├── cli_common.py       # app 实例 + 全局 callback + 参数 Enum + 双 console + 路径/ident 解析\n│   ├── cli_query.py        # 只读命令 list/search/show/raw/stats/todo/seals\n│   ├── cli_config.py       # config show/set/unset 子命令组\n│   ├── cli_party.py        # party list/show/set/rm（known_parties PII 基准库）\n│   ├── cli_introspect.py   # capabilities/describe/schema 机器发现命令\n│   ├── cli_render.py       # 纯渲染层（Table / JSON dict / raw 高亮）\n│   ├── schemas/            # pydantic schema（BBox/LayoutBlock/DocumentExtraction 等）\n│   ├── pipelines/\n│   │   ├── ocr_pipeline.py   # native text + VL OCR 混合提取 + markdown 清洗\n│   │   └── vl_ocr.py         # qwen-vl-ocr 逐页 OCR 调用\n│   ├── utils/\n│   │   ├── pdf.py            # PyMuPDF 文本层分析 / 渲染\n│   │   └── page_router.py    # 页级 text/ocr 分流\n│   ├── extraction/              # 纯 LLM 抽取（rule/hybrid 自 Phase 2 退役）\n│   │   ├── document_extractor.py  # 通用文档判类型 + 抽信封\n│   │   ├── contract_extractor.py  # 合同专属字段（专属 prompt）\n│   │   ├── llm_extractor.py        # DashScope OpenAI 兼容口调用\n│   │   ├── vision_seal.py          # 落款页 VL 签章核查\n│   │   ├── doc_type_handlers.py    # doc_type → 特化/后处理/融合配置\n│   │   ├── fusion.py               # 多源候选评判 → field_verdicts sidecar\n│   │   ├── text_fields.py / vl_extract.py / insurance_extractor.py\n│   │   ├── normalize.py / amount_check.py / evidence_page_fix.py / property_fee.py\n│   ├── archive/\n│   │   ├── db.py                # SQLite 连接 + migrations 引擎\n│   │   ├── repository.py        # DAO + 搜索查询构造\n│   │   ├── ingest.py            # 入库流水线（hash → OCR → extract → rename → DB）\n│   │   ├── party_registry.py    # known_parties 身份基准库\n│   │   ├── paths.py             # 档案库路径约定 + 硬链接工具\n│   │   └── migrations/          # 001_init … 005_completeness（5 个）\n│   ├── errors.py               # 结构化错误模型（code/category/retryable）\n│   ├── config.py               # XDG 配置 + env\u003efile\u003edefault\n├── archive/                # 档案库数据（gitignored）\n├── docs/\n├── evals/                  # 评测框架；真实数据在私有 evalset 仓库\n├── input/                  # 用户放待处理 PDF\n└── tests/\n```\n\n## ✦ 设计纪律\n\n- **统一 schema**：OCR 产物统一写入 raw_text/markdown/structured/layout/meta；markdown 反斜杠转义在喂给抽取层前清洗\n- **纯 LLM 抽取**：自 Phase 2 退役 rule/hybrid，字段全由 LLM 抽取；旧合同置信度列只保留 `llm`/`missing` 语义。死代码 rule 仅保留为确定性数值归一化（中文大写金额→数值、日期→ISO）\n- **类型路由集中化**：先抽通用信封的 `doc_type`，再经 `doc_type_handlers.py` 决定特化抽取、类型专属后处理和是否启用融合。\n- **融合只写 sidecar**：保险等高价值字段的多源融合结果只进入 `field_verdicts` / `fusion_overall_confidence`，不回写 `amounts` / `fields`。\n- **API key 不出包**：仅从 env 读，日志不打印响应体\n- **sha256 去重**：流式 hash 后查 UNIQUE 索引；命中即 skip 避免重复 OCR 后才发现重复\n- **事务边界**：tmp 目录跑全 → `os.rename` 到 documents/ → DB INSERT；任一阶段失败回滚干净，DB 不留半成品\n- **partial 状态可修复**：OCR OK 但 LLM 挂时 markdown 仍可用，`extract \u003cid\u003e` 命令只重跑抽取层\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcrhan%2Fcontract-archive-cli","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fcrhan%2Fcontract-archive-cli","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcrhan%2Fcontract-archive-cli/lists"}