opencli-adapter-author

opencli-adapter-author

熱門

當你要為新站點撰寫 OpenCLI adapter,或為既有站點新增指令時使用。引導你從初次偵察、欄位解碼、adapter 編寫到驗證的完整流程。取代 opencli-oneshot / opencli-explorer。若只是臨時操作瀏覽器(不寫 adapter),請改用 opencli-browser;若想了解 opencli 整體概覽,請參閱 opencli-usage。

2.6萬星標
0分支
更新於 2026/7/3
SKILL.md
readonlyread-only
name
opencli-adapter-author
description

Use when writing an OpenCLI adapter for a new site or adding a new command to an existing site. Guides end-to-end from first recon through field decoding, adapter coding, and verify. Replaces opencli-oneshot / opencli-explorer. For ad-hoc browser driving (no adapter), see opencli-browser instead; for a top-level orientation to opencli, see opencli-usage.

opencli-adapter-author

你是要為某個站點寫 adapter 的 agent。這份 skill 的目標:從零開始,在 30 分鐘內通過 opencli browser verify 閉環

全程使用現有工具:opencli browser * / opencli doctor / opencli browser init / opencli browser verify。沒有新指令。

除錯瀏覽器型 adapter 時,優先直接加上 --trace on --keep-tab true --window foreground--trace on 每輪都會產生 trace artifact,summary.md 是失敗/成功復盤的入口;--keep-tab true --window foreground 讓 tab lease 保留且瀏覽器視窗在前景,方便核對最終頁面狀態。


前置:看你落在哪

先拿 coverage-matrix.md 快速自測。三個問題:

  1. 資料在瀏覽器裡看得到嗎?(否 → 先解決驗證)
  2. 資料是 HTTP/JSON/HTML 嗎?(否 → 不在 skill 範圍)
  3. 需要即時推送嗎?(是 → 找同資料的 HTTP 介面;沒有就放棄)

三個都 yes 繼續。


頂層決策樹

先決定 strategy,再寫 adapter。 每次進入 Step 3/4 後、寫程式碼前,必須產出一段 strategy note。沒有這段 note,不要開始寫 clis/<site>/<name>.js

核心判斷不是「API 比 DOM 高級」,而是 資料來源有沒有外部契約。實測維護成本顯示:公開/官方介面最穩;UI/DOM 語意通常也有使用者可見契約;站內未文件化的 XHR/GraphQL/signature endpoint 最容易漂移。不要為了「API-first」就把穩定的 UI/DOM 實作盲目遷到無契約的內部介面。

Strategy: PUBLIC_API | COOKIE_API | PAGE_FETCH | INTERCEPT | DOM_STATE | UI_SELECTOR
Contract: stable | visible-ui | internal-unstable
Evidence:
- observed request/state: <endpoint / state global / UI-only signal>
- auth source: <none / browser cookie / csrf from meta / localStorage / page runtime>
- replay result: <status + content-type + non-empty sample shape>

If Strategy is PAGE_FETCH or INTERCEPT:
- why PUBLIC_API / COOKIE_API are unavailable:
- why UI_SELECTOR / DOM_STATE are not safer:
- why the maintenance cost is acceptable:

Strategy classes:

Strategy 契約級別 用在什麼時候 證據要求
PUBLIC_API stable 不需要登入,Node-side fetch 直接拿到目標資料 200 + JSON/HTML 含目標資料,不是埋點/廣告
COOKIE_API stable Node-side fetch + page.getCookies() / header helper 能拿資料 cookie/CSRF 來源清楚,replay 非空
UI_SELECTOR visible-ui publish/upload/click/表單,或頁面語意比內部介面更穩 selector 有語意錨點;錯誤路徑是 typed error
DOM_STATE visible-ui 資料在 hydration state / bootstrap JSON / SSR HTML 裡 state key / script JSON / HTML 結構明確
PAGE_FETCH internal-unstable 只能在頁面上下文 fetch 才能複用 same-origin/session/runtime opencli browser eval fetch(...) 非空;必須解釋為什麼避不開內部介面
INTERCEPT internal-unstable 請求簽章複雜,但頁面自己能自然發出請求 觸發 UI 後能攔到目標 response;必須解釋為什麼 UI/DOM 不夠

選擇規則:優先 PUBLIC_API / COOKIE_API。如果 UI/DOM 語意穩定,不要強行升級到 PAGE_FETCH / INTERCEPT。只有公開/官方介面不可用、UI/DOM 無法表達目標資料或操作時,才承擔無契約內部介面的維護成本。

實測:PAGE_FETCH / INTERCEPT 的修復頻率約為 PUBLIC_API 的 7-8 倍,UI_SELECTORCOOKIE_API 同級。詳細 ladder 推導、api_candidates 證據怎麼填、booking #1680 等反例請見 references/strategy-selection.md

邊界:只複用頁面自己已經合法獲得的資料/能力。不教破解簽章、不繞驗證碼/風控/存取控制;遇到不可複用簽章(如必須由頁面 runtime 產生且不能安全抽象)就降級到 UI_SELECTOR / DOM_STATE / INTERCEPT

START
  │
  ▼
┌──────────────────────────┐
│ opencli doctor 通?      │── no ──→ 修橋接(doctor 輸出裡的提示)
└──────────────────────────┘
  │ yes
  ▼
┌────────────────────────────────────────────────────┐
│ 讀站點記憶:                                        │
│   1. ~/.opencli/sites/<site>/endpoints.json         │
│   2. ~/.opencli/sites/<site>/notes.md               │
│   3. references/site-memory/<site>.md               │
└────────────────────────────────────────────────────┘
  │ 命中 endpoint + 欄位 → 直接跳到【endpoint 驗證】(不跳寫 adapter!memory 可能過期)
  │ 沒命中 → 繼續
  ▼
┌──────────────────────────┐
│ 站點偵察(site-recon)    │  → Pattern A/B/C/D/E
└──────────────────────────┘
  │
  ▼
┌──────────────────────────┐
│ API 發現(api-discovery)│  §1 network → §2 state → §3 bundle → §4 token → §5 intercept
└──────────────────────────┘
  │ 拿到候選 endpoint
  ▼
┌────────────────────────────────────────────┐
│ 直接 fetch 驗證 endpoint(memory 命中也要跑)│── 401/403 ──→ 回到 §4 排 token
│ 資料非空 + 200                              │── 空/HTML ──→ 回到 site-recon 換 Pattern
│ memory 裡的值還活著嗎?                     │── 站點改版 ──→ 標記舊 endpoint,回 api-discovery
└────────────────────────────────────────────┘
  │ OK
  ▼
┌───────────────────────────────────────┐
│ 欄位解碼(memory 裡的 field-map 也要抽查)│  自解釋 → 直接 / 已知代號 → field-conventions / 未知 → decode-playbook
│ 比對一條已知欄位和網頁肉眼值,確認沒對錯     │
└───────────────────────────────────────┘
  │
  ▼
┌──────────────────────────┐
│ 設計 columns (output)    │  對照 output-design.md 的命名 / 型別 / 順序
└──────────────────────────┘
  │
  ▼
┌──────────────────────────┐
│ opencli browser init      │  產生 ~/.opencli/clis/<site>/<name>.js 骨架
│ 複製最像的鄰居 adapter    │
│ 改 name / URL / 對映三處  │
└──────────────────────────┘
  │
  ▼
┌──────────────────────────┐
│ opencli browser verify    │── 失敗 ──→ autofix skill,用 --trace retain-on-failure 回對應步驟
└──────────────────────────┘
  │ 成功
  ▼
┌──────────────────────────┐
│ 欄位 vs 網頁肉眼對一遍   │── 數值不對 ──→ 回欄位解碼
└──────────────────────────┘
  │ 對得上
  ▼
┌──────────────────────────┐
│ 回寫 ~/.opencli/sites/   │  endpoints / field-map / notes / fixtures
└──────────────────────────┘
  │
  ▼
DONE

Runbook(一步一步勾選)

[ ] 1. opencli doctor 回傳 "Everything looks good"
[ ] 2. 讀站點記憶:
       [ ] ~/.opencli/sites/<site>/endpoints.json 存在?裡面有想要的 endpoint?
       [ ] references/site-memory/<site>.md 存在?看「已知 endpoint」節
       [ ] 命中後:**跳到第 5(endpoint 驗證) + 第 7(欄位核對)**,不能直接跳第 9 寫 adapter
       [ ] memory 寫入超過 30 天(看 `verified_at`)→ 當作過期,按冷啟動走 Step 3 → 4
[ ] 3. 偵察(site-recon.md):
       [ ] **首選**:`opencli browser analyze <url>` 一步拿 pattern + 反爬 + 最近 adapter + next step
       [ ] `analyze` 結論模糊時再手跑:`open` → `wait time 2` (或 `wait xhr <regex>`) → `network`
       [ ] 定 Pattern(A / B / C / D / E)
[ ] 4. API 發現(api-discovery.md)按 Pattern 選 §:
       [ ] Pattern A → §1 network 精讀
       [ ] Pattern B → §2 state 抽取 + §1 深層資料
       [ ] Pattern C → §3 bundle / script src 搜尋
       [ ] Pattern D → §4 token 來源 + 降級 §5
       [ ] Pattern E → 找 HTTP 輪詢介面;找不到才 §5
[ ] 5. 直接 fetch 候選 endpoint 驗證:
       [ ] 回傳 200
       [ ] 回應含目標資料(不是 HTML / 廣告)
[ ] 6. 寫 strategy note(寫程式碼前的強制產物):
       [ ] 從 `PUBLIC_API / COOKIE_API / PAGE_FETCH / INTERCEPT / DOM_STATE / UI_SELECTOR` 選一個
       [ ] 填 Contract:`stable / visible-ui / internal-unstable`
       [ ] 填 Evidence:observed request/state、auth source、replay result
       [ ] 如果選 `PAGE_FETCH` / `INTERCEPT`,必須解釋為什麼 `PUBLIC_API` / `COOKIE_API` / `UI_SELECTOR` / `DOM_STATE` 都不適合
       [ ] 如果選 `UI_SELECTOR` / `DOM_STATE`,不需要為「為什麼不是 API」過度辯護;只要說明語意錨點和 typed error 路徑
[ ] 7. 欄位解碼:
       [ ] 自解釋 → 直接用 key
       [ ] 已知代號 → field-conventions.md 查表
       [ ] 未知代號 → field-decode-playbook.md(排序鍵比對 / 結構差分 / 常數排查)
[ ] 8. 設計 columns(output-design.md):
       [ ] 命名 camelCase 且對齊鄰居 adapter
       [ ] 型別 / 單位 / 百分比格式清楚
       [ ] 順序:識別列 → 業務數字 → metadata
[ ] 9. 寫 adapter(adapter-template.md):
       [ ] opencli browser init <site>/<name>
       [ ] 找同站點或同型別最像的 adapter,cp 過來
       [ ] 改 name / URL / 欄位對映
[ ] 10. opencli browser verify <site>/<name>
        [ ] 首輪通過後立刻 `--write-fixture` 產生 `~/.opencli/sites/<site>/verify/<cmd>.json` 種子
        [ ] 手改種子:加 `patterns`(URL / 日期 / ID 格式)+ `notEmpty`(核心欄位)+ 收緊 `rowCount`
        [ ] 再跑一次 `opencli browser verify <site>/<name>`,確認 ✓ matches fixture
[ ] 11. 欄位值 vs 網頁肉眼比對(別只看「Adapter works!」)
[ ] 12. 回寫站點記憶(**verify 通過 + 肉眼比對對得上之後**,schema 見 `references/site-memory.md`):
        [ ] `endpoints.json`:以 endpoint 的短名為 key,value = `{url, method, params.{required,optional}, response, verified_at: YYYY-MM-DD, notes}`
        [ ] `field-map.json`:只追加新代號。key = 欄位代號,value = `{meaning, verified_at: YYYY-MM-DD, source}`;**已存在的 key 不要覆蓋**,有衝突先和網頁肉眼值對齊再寫
        [ ] `notes.md`:頂部追加一段 `## YYYY-MM-DD by <agent/user>`,寫本次寫 adapter 時遇到的新坑 / 新結論
        [ ] `verify/<cmd>.json`:**必填。** `opencli browser verify` 的期望值(args / rowCount / columns / types / patterns / notEmpty),Step 10 已經讓你產生了,這裡只是 checklist
        [ ] `fixtures/<cmd>-<YYYYMMDDHHMM>.json`:存一份該 endpoint 的完整回應樣本(去掉 cookie / token / 使用者私有欄位再存),給後續欄位比對 / 離線 replay 用
        [ ] 除錯過程中如果在 repo / adapter 目錄 dump 過暫存檔(`.dbg-*.html` / `raw-*.json` / 等),**在 commit 前清乾淨**——這些本來就該落在 `~/.opencli/sites/<site>/fixtures/` 或 `/tmp/`

降級路徑(某步卡住跳到哪)

卡在 現象 跳去
Step 4 API 發現 network 空,__INITIAL_STATE__ 也空 §3 bundle 搜 baseURL
bundle 搜不到 baseURL §5 intercept
Step 5 endpoint 驗證 401 / 403 §4 token 排查
200 但回應是 HTML 回 Step 3 換 Pattern 判斷
200 但 data: [] 參數傳錯 / 介面改版,回 §1 看 network 裡真實請求頭
Step 7 欄位解碼 排序鍵比對推不出 field-decode-playbook.md §3 結構差分
還推不出 先輸出 raw,adapter 跑起來再迭代
Step 10 verify 失敗 fltt 漏了 / 欄位對映錯 autofix skill;重現指令加 --trace retain-on-failure
某列永遠是 null 欄位路徑錯了,回 Step 7
Step 10 verify fixture mismatch [pattern] row[i] 報錯 先肉眼比對網頁值;值對 → 是 fixture pattern 太嚴,放寬;值不對 → 欄位對映錯
[column] missing column "X" 實際 response 沒這列(站點改版 or args 影響);重新 --update-fixture 或修 adapter
[type] actual null / undefined 欄位提取失敗,回 Step 7 重抽;暫時 fallback 用 union type string|null 只有在語意真的可空時用
Step 11 數值不對 差 10000 倍 單位不統一(「萬」 vs 「元」)
百分比小 100 倍 回應已是 0.025,不要 × 100

參考文件

文件 什麼時候翻
references/coverage-matrix.md 動手前做「是否在範圍內」自測
references/site-recon.md Step 3 定站點類型
references/api-discovery.md Step 4 找 endpoint
references/strategy-selection.md Step 6 填 strategy note 之前:契約模型 + 實測修復頻率 + api_candidates 證據用法 + 反例
references/field-conventions.md Step 7 查已知欄位代號
references/field-decode-playbook.md Step 7 欄位不在辭典時
references/output-design.md Step 8 命名 / 型別 / 順序
references/adapter-template.md Step 9 檔案結構 + 活例子 convertible.js
references/site-memory.md 總覽:in-repo 種子 + 本地 ~/.opencli/sites/ 的兩層結構
references/site-memory/<site>.md Step 2 讀站點公共知識(eastmoney / xueqiu / bilibili / tonghuashun 已鋪)
references/success-rate-pitfalls.md Step 7 / 11 踩坑前翻:11 種「verify 能過但資料是錯的」靜默失敗(含 aria-label locale-dependence)
references/jsdom-fixture-pattern.md 當 adapter 走 page.evaluate 內 DOM 抽取、且 mocked-evaluate 單測漏 silent bug 時——把 HTML 凍進 clis/<site>/__fixtures__/ 用 JSDOM 跑(含 fixture 建立 mandatory awk 'NF>0' 收緊 + reverse-validate 紀律)
references/typed-errors.md func 主體之前必讀:5 類 typed error 落點表(ArgumentError / EmptyResultError / CommandExecutionError / AuthRequiredError / TimeoutError)+ 三大 silent anti-pattern(silent-clamp / sentinel-row / generic CliError)的反例修法

關鍵約定

  • adapter 只引用 @jackwener/opencli/registry + @jackwener/opencli/errors,不用第三方
  • columns 陣列和 func 回傳物件 keys 完全對齊(含順序)
  • 中間解析物件 key 不能跟 columns 任一項重疊(否則 silent-column-drop audit 誤判,PR #1329 R1 真的踩過;改成專屬命名 + push row 時 destructure aliasing)
  • browser: field 決定 func 簽章browser:false → (args)browser:true → (page, args)。搞反時 args 實際是 debug flag,所有外部參數 silent fallback 到 default(PR #1329 upstream 之前 8 個 non-browser adapter 全踩過這個)
  • 已知失敗按 references/typed-errors.md 5-classification 拋對應 typed error;不要 silent return []不要 silent return [{sentinel}]不要 Math.max/min silent clamp 外部參數
  • 寫私人 adapter 用 ~/.opencli/clis/<site>/<name>.js(免 build);要提 PR 才 copy 到 clis/<site>/<name>.js
  • 站點記憶每輪回寫:沒記憶 → 用 skill → 產生記憶 → 下次變 5 分鐘
  • 除錯過程中的原始 dump / 抓包 / HTML 樣本只能落在 ~/.opencli/sites/<site>/fixtures//tmp/。嚴禁在 repo 根目錄、clis/<site>/ 或當前工作目錄留 .dbg-*.html / raw-*.json / sample.* 這類暫存檔(PR diff 會帶上去,別人 review 時很煩)。
  • JSDOM unit-test fixture(clis/<site>/__fixtures__/<command>.html)是上面那條的例外——它是有意 commit 進 repo 的 review artifact,不是暫存 dump。但因此 quality bar 要更高:必須按 references/jsdom-fixture-pattern.md 的 5 步做完(含 mandatory awk 'NF>0' 空白行收緊),並 reverse-validate 一道證明 regression guard 真能掛。

卡住了

  • 診斷類:opencli doctor → 看 notes.md → 搜 autofix skill
  • 欄位解碼類:field-decode-playbook.md 全三節走完 → 先輸出 raw 迭代
  • endpoint 找不到:api-discovery §5 intercept 兜底

不要猜。猜錯了 verify 能通過但資料是錯的,使用者看到亂碼才發現。