SKILL.md
1 --- 2 name: opencli-autofix 3 description: Automatically fix broken OpenCLI adapters when commands fail. Load this skill when an opencli command fails — it guides you through diagnosing the failure via OPENCLI_DIAGNOSTIC, patching the adapter, retrying, and filing an upstream GitHub issue after a verified fix. Works with any AI agent. 4 allowed-tools: Bash(opencli:*), Bash(gh:*), Read, Edit, Write 5 --- 6 7 # OpenCLI AutoFix — Automatic Adapter Self-Repair 8 9 When an `opencli` command fails because a website changed its DOM, API, or response schema, **automatically diagnose, fix the adapter, and retry** — don't just report the error. 10 11 ## Safety Boundaries 12 13 **Before starting any repair, check these hard stops:** 14 15 - **`AUTH_REQUIRED`** (exit code 77) — **STOP.** Do not modify code. Tell the user to log into the site in Chrome. 16 - **`BROWSER_CONNECT`** (exit code 69) — **STOP.** Do not modify code. Tell the user to run `opencli doctor`. 17 - **CAPTCHA / rate limiting** — **STOP.** Not an adapter issue. 18 19 **Scope constraint:** 20 - **Only modify the file at `RepairContext.adapter.sourcePath`** — this is the authoritative adapter location (may be `clis/<site>/` in repo or `~/.opencli/clis/<site>/` for npm installs) 21 - **Never modify** `src/`, `extension/`, `tests/`, `package.json`, or `tsconfig.json` 22 23 **Retry budget:** Max **3 repair rounds** per failure. If 3 rounds of diagnose → fix → retry don't resolve it, stop and report what was tried. 24 25 ## Prerequisites 26 27 ```bash 28 opencli doctor # Verify extension + daemon connectivity 29 ``` 30 31 ## When to Use This Skill 32 33 Use when `opencli <site> <command>` fails with repairable errors: 34 - **SELECTOR** — element not found (DOM changed) 35 - **EMPTY_RESULT** — no data returned (API response changed) 36 - **API_ERROR** / **NETWORK** — endpoint moved or broke 37 - **PAGE_CHANGED** — page structure no longer matches 38 - **COMMAND_EXEC** — runtime error in adapter logic 39 - **TIMEOUT** — page loads differently, adapter waits for wrong thing 40 41 ## Before Entering Repair: "Empty" ≠ "Broken" 42 43 `EMPTY_RESULT` — and sometimes a structurally-valid `SELECTOR` that returns nothing — is often **not an adapter bug**. Platforms actively degrade results under anti-scrape heuristics, and a "not found" response from the site doesn't mean the content is actually missing. Rule this out **before** committing to a repair round: 44 45 - **Retry with an alternative query or entry point.** If `opencli xiaohongshu search "X"` returns 0 but `opencli xiaohongshu search "X 攻略"` returns 20, the adapter is fine — the platform was shaping results for the first query. 46 - **Spot-check in a normal Chrome tab.** If the data is visible in the user's own browser but the adapter comes back empty, the issue is usually authentication state, rate limiting, or a soft block — not a code bug. The fix is `opencli doctor` / re-login, not editing source. 47 - **Look for soft 404s.** Sites like xiaohongshu / weibo / douyin return HTTP 200 with an empty payload instead of a real 404 when an item is hidden or deleted. The snapshot will look structurally correct. A retry 2-3 seconds later often distinguishes "temporarily hidden" from "actually gone". 48 - **"0 results" from a search is an answer.** If the adapter successfully reached the search endpoint, got an HTTP 200, and the platform returned `results: []`, that is a valid answer — report it to the user as "no matches for this query" rather than patching the adapter. 49 50 Only proceed to Step 1 if the empty/selector-missing result is **reproducible across retries and alternative entry points**. Otherwise you're patching a working adapter to chase noise, and the patched version will break the next working path. 51 52 ## Step 1: Collect Diagnostic Context 53 54 Run the failing command with diagnostic mode enabled: 55 56 ```bash 57 OPENCLI_DIAGNOSTIC=1 opencli <site> <command> [args...] 2>diagnostic.json 58 ``` 59 60 This outputs a `RepairContext` JSON between `___OPENCLI_DIAGNOSTIC___` markers in stderr: 61 62 ```json 63 { 64 "error": { 65 "code": "SELECTOR", 66 "message": "Could not find element: .old-selector", 67 "hint": "The page UI may have changed." 68 }, 69 "adapter": { 70 "site": "example", 71 "command": "example/search", 72 "sourcePath": "/path/to/clis/example/search.js", 73 "source": "// full adapter source code" 74 }, 75 "page": { 76 "url": "https://example.com/search", 77 "snapshot": "// DOM snapshot with [N] indices", 78 "networkRequests": [], 79 "consoleErrors": [] 80 }, 81 "timestamp": "2025-01-01T00:00:00.000Z" 82 } 83 ``` 84 85 **Parse it:** 86 ```bash 87 # Extract JSON between markers from stderr output 88 cat diagnostic.json | sed -n '/___OPENCLI_DIAGNOSTIC___/{n;p;}' 89 ``` 90 91 ## Step 2: Analyze the Failure 92 93 Read the diagnostic context and the adapter source. Classify the root cause: 94 95 | Error Code | Likely Cause | Repair Strategy | 96 |-----------|-------------|-----------------| 97 | SELECTOR | DOM restructured, class/id renamed | Explore current DOM → find new selector | 98 | EMPTY_RESULT | API response schema changed, or data moved | Check network → find new response path | 99 | API_ERROR | Endpoint URL changed, new params required | Discover new API via network intercept | 100 | AUTH_REQUIRED | Login flow changed, cookies expired | **STOP** — tell user to log in, do not modify code | 101 | TIMEOUT | Page loads differently, spinner/lazy-load | Add/update wait conditions | 102 | PAGE_CHANGED | Major redesign | May need full adapter rewrite | 103 104 **Key questions to answer:** 105 1. What is the adapter trying to do? (Read the `source` field) 106 2. What did the page look like when it failed? (Read the `snapshot` field) 107 3. What network requests happened? (Read `networkRequests`) 108 4. What's the gap between what the adapter expects and what the page provides? 109 110 ## Step 3: Explore the Current Website 111 112 Use `opencli browser` to inspect the live website. **Never use the broken adapter** — it will just fail again. 113 114 ### DOM changed (SELECTOR errors) 115 116 ```bash 117 # Open the page and inspect current DOM 118 opencli browser open https://example.com/target-page && opencli browser state 119 120 # Look for elements that match the adapter's intent 121 # Compare the snapshot with what the adapter expects 122 ``` 123 124 ### API changed (API_ERROR, EMPTY_RESULT) 125 126 ```bash 127 # Open page with network interceptor, then trigger the action manually 128 opencli browser open https://example.com/target-page && opencli browser state 129 130 # Interact to trigger API calls 131 opencli browser click <N> && opencli browser network 132 133 # Narrow to the request you care about by the fields its body should have 134 opencli browser network --filter author,text,likes 135 136 # Inspect specific API response (key is the `key` field from the default JSON output) 137 opencli browser network --detail <key> 138 ``` 139 140 ## Step 4: Patch the Adapter 141 142 Read the adapter source file at the path from `RepairContext.adapter.sourcePath` and make targeted fixes. This path is authoritative — it may be in the repo (`clis/`) or user-local (`~/.opencli/clis/`). 143 144 ```bash 145 # Read the adapter (use the exact path from diagnostic) 146 cat <RepairContext.adapter.sourcePath> 147 ``` 148 149 ### Common Fixes 150 151 **Selector update:** 152 ```typescript 153 // Before: page.evaluate('document.querySelector(".old-class")...') 154 // After: page.evaluate('document.querySelector(".new-class")...') 155 ``` 156 157 **API endpoint change:** 158 ```typescript 159 // Before: const resp = await page.evaluate(`fetch('/api/v1/old-endpoint')...`) 160 // After: const resp = await page.evaluate(`fetch('/api/v2/new-endpoint')...`) 161 ``` 162 163 **Response schema change:** 164 ```typescript 165 // Before: const items = data.results 166 // After: const items = data.data.items // API now nests under "data" 167 ``` 168 169 **Wait condition update:** 170 ```typescript 171 // Before: await page.wait({ selector: '.loading-spinner', hidden: true }) 172 // After: await page.wait({ selector: '[data-loaded="true"]' }) 173 ``` 174 175 ### Rules for Patching 176 177 1. **Make minimal changes** — fix only what's broken, don't refactor 178 2. **Keep the same output structure** — `columns` and return format must stay compatible 179 3. **Prefer API over DOM scraping** — if you discover a JSON API during exploration, switch to it 180 4. **Use `@jackwener/opencli/*` imports only** — never add third-party package imports 181 5. **Test after patching** — run the command again to verify 182 6. **Never relax `verify/<cmd>.json` fixtures to silence a failure.** A failing `patterns` / `notEmpty` / `mustNotContain` / `mustBeTruthy` rule means the adapter's output is broken. Tighten the adapter so it produces correct values; do not loosen the fixture to accept the broken values. The one legitimate reason to edit a fixture during repair is when the **site itself** changed shape (e.g. URL format migration) — in that case update the fixture and note the change in `~/.opencli/sites/<site>/notes.md`. Otherwise editing the fixture is covering up a silent correctness regression. 183 184 ## Step 5: Verify the Fix 185 186 ```bash 187 # Run the command normally (without diagnostic mode) 188 opencli <site> <command> [args...] 189 ``` 190 191 If it still fails, go back to Step 1 and collect fresh diagnostics. You have a budget of **3 repair rounds** (diagnose → fix → retry). If the same error persists after a fix, try a different approach. After 3 rounds, stop and report what was tried. 192 193 ## Step 6: File an Upstream Issue 194 195 If the retry **passes**, the local adapter has drifted from upstream. File a GitHub issue so the fix flows back to `jackwener/OpenCLI`. 196 197 **Do NOT file for:** 198 - `AUTH_REQUIRED`, `BROWSER_CONNECT`, `ARGUMENT`, `CONFIG` — environment/usage issues, not adapter bugs 199 - CAPTCHA or rate limiting — not fixable upstream 200 - Failures you couldn't actually fix (3 rounds exhausted) 201 202 **Only file after a verified local fix** — the retry must pass first. 203 204 **Procedure:** 205 206 1. Prepare the issue content from the RepairContext you already have: 207 - **Title:** `[autofix] <site>/<command>: <error_code>` (e.g. `[autofix] zhihu/hot: SELECTOR`) 208 - **Body** (use this template): 209 210 ```markdown 211 ## Summary 212 OpenCLI autofix repaired this adapter locally, and the retry passed. 213 214 ## Adapter 215 - Site: `<site>` 216 - Command: `<command>` 217 - OpenCLI version: `<version from opencli --version>` 218 219 ## Original failure 220 - Error code: `<error_code>` 221 222 ~~~ 223 <error_message> 224 ~~~ 225 226 ## Local fix summary 227 228 ~~~ 229 <1-2 sentence description of what you changed and why> 230 ~~~ 231 232 _Issue filed by OpenCLI autofix after a verified local repair._ 233 ``` 234 235 2. **Ask the user before filing.** Show them the draft title and body. Only proceed if they confirm. 236 237 3. If the user approves and `gh auth status` succeeds: 238 239 ```bash 240 gh issue create --repo jackwener/OpenCLI \ 241 --title "[autofix] <site>/<command>: <error_code>" \ 242 --body "<the body above>" 243 ``` 244 245 If `gh` is not installed or not authenticated, tell the user and skip — do not error out. 246 247 ## When to Stop 248 249 **Hard stops (do not modify code):** 250 - **AUTH_REQUIRED / BROWSER_CONNECT** — environment issue, not adapter bug 251 - **Site requires CAPTCHA** — can't automate this 252 - **Rate limited / IP blocked** — not an adapter issue 253 254 **Soft stops (report after attempting):** 255 - **3 repair rounds exhausted** — stop, report what was tried and what failed 256 - **Feature completely removed** — the data no longer exists 257 - **Major redesign** — needs full adapter rewrite via `opencli-adapter-author` skill 258 259 In all stop cases, clearly communicate the situation to the user rather than making futile patches. 260 261 ## Example Repair Session 262 263 ``` 264 1. User runs: opencli zhihu hot 265 → Fails: SELECTOR "Could not find element: .HotList-item" 266 267 2. AI runs: OPENCLI_DIAGNOSTIC=1 opencli zhihu hot 2>diag.json 268 → Gets RepairContext with DOM snapshot showing page loaded 269 270 3. AI reads diagnostic: snapshot shows the page loaded but uses ".HotItem" instead of ".HotList-item" 271 272 4. AI explores: opencli browser open https://www.zhihu.com/hot && opencli browser state 273 → Confirms new class name ".HotItem" with child ".HotItem-content" 274 275 5. AI patches: Edit adapter at RepairContext.adapter.sourcePath — replace ".HotList-item" with ".HotItem" 276 277 6. AI verifies: opencli zhihu hot 278 → Success: returns hot topics 279 280 7. AI prepares upstream issue draft, shows it to the user 281 282 8. User approves → AI runs: gh issue create --repo jackwener/OpenCLI --title "[autofix] zhihu/hot: SELECTOR" --body "..." 283 ```