← Commit history

v2.0.0: Electronics Industry Sites Skillpack. Root lane ladder + symptom table + 15 per-site skills (ST, Mouser, DigiKey, SnapEDA, TI, Microchip, Analog, Arrow, Nordic, Ultra Librarian, CSE, datasheets.com, Octopart, JLCPCB/LCSC, generic manufacturer), verified 2026-09-12; studio hero

John Lauer ·a3641f7a03 ·24d ago ·parent 0e0fdd7
5 files changed +581
__pycache__/gen.cpython-312.pycadded
⋯ 1 unchanged line ⋯
docs/hero.pngadded
⋯ 1 unchanged line ⋯
gen.pyadded+581
@@ -0,0 +1,581 @@+#!/usr/bin/env python3+"""Generate the electronics-sites-skillpack v2 files from one data table.++Run from the pack root:  python3 gen.py+Every site skill has the same sections so an AI can pattern-match across+sites. ASCII punctuation only (wiki rule: no em/en dashes anywhere).+"""+import json, os, re, sys++ROOT = os.path.dirname(os.path.abspath(__file__))+VERIFIED = "2026-09-12"+PACK = "electronics-sites-skillpack"+TITLE = "Electronics Industry Sites Skillpack"+BRIEF = ("One skill per popular electronics-industry website (ST, TI, Mouser, DigiKey, SnapEDA, "+         "Ultra Librarian, Arrow, Analog, Microchip, Nordic, CSE, Octopart, JLCPCB, datasheets.com): "+         "which access lane works (fetch, curl, pup, nb, API), the bot defense each one runs, the "+         "exact failure signatures, and the task recipes. Verified live, dated, and built to grow.")++LANES = ["WebFetch", "curl", "pup", "nb"]+LANE_NOTE = {+    "WebFetch": "the Anthropic fetcher behind WebFetch / WebSearch",+    "curl": "curl or python from the Adom container",+    "pup": "pup, Chrome for Testing 148 in the durable adom-you profile, background window",+    "nb": "nb, the user's real signed-in Chrome 152 via the Adom Native Browser Extension",+}++# ---------------------------------------------------------------------------+# The data table. lane values: OK | BLOCKED | PARTIAL | untested, plus a note.+# ---------------------------------------------------------------------------+SITES = [+ dict(slug="st", name="STMicroelectronics", domains=["www.st.com", "community.st.com", "newsroom.st.com", "www.st.com/resource (CDN)"],+  what="Manufacturer. Product pages, datasheets, app notes, eval boards, myST CAD downloads, STM32Cube software.",+  defense="Akamai Bot Manager on www.st.com (sensor script issues _abck / bm_sz; the edge RSTs the HTTP/2 stream once the sensor verdict is negative). community.st.com is nginx, newsroom.st.com is CloudFront: both open.",+  lanes={+   "WebFetch": ("BLOCKED", "product page, datasheet PDF and eval-tool pages time out at 60 s; newsroom returns HTTP 473; community.st.com works"),+   "curl": ("BLOCKED", "curl: (92) HTTP/2 stream 1 was not closed cleanly: INTERNAL_ERROR in ~60 ms, even for /resource/*.pdf; community + newsroom return 200"),+   "pup": ("PARTIAL", "the FIRST page load renders, then every further www.st.com HTML navigation lands on chrome-error:// with ERR_HTTP2_PROTOCOL_ERROR (site root included). /resource/*.pdf still renders in the PDF viewer. A cold isolated profile and 15 s pacing do not help: it is the client verdict, not rate"),+   "nb": ("OK", "every page loads, hash-tab navigation works, nbrowser_fetch_url returns PDFs (200, base64) in under a second"),+  },+  symptoms=["ERR_HTTP2_PROTOCOL_ERROR on the second www.st.com page in pup", "curl (92) HTTP/2 INTERNAL_ERROR within 100 ms", "HTTP 473 from newsroom.st.com to a fetcher", "60 s WebFetch timeout on any www.st.com URL"],+  recipes=[+   ("Product page tabs (Documentation, Tools & Software, CAD)", "The tabs are already in the DOM. Read them by id instead of clicking: `#documentation-panel`, `#tools-software-panel`, `#cad-resources-panel`. Strip lines matching /Worldwide,Asia/ (a per-row country list that swamps innerText). Do NOT set location.hash: it reloads the SPA and, in pup, that reload is the one that dies."),+   ("Datasheet or data brief PDF", "`nbrowser_fetch_url {sessionId, url:\"https://www.st.com/resource/en/datasheet/<part>.pdf\"}` -> base64 body -> `base64 -d` -> `pdftotext -layout`. Data briefs live at /resource/en/data_brief/<part>.pdf. Document ids look like DS14879 (datasheet), DB5805 (data brief), UM/AN/TN."),+   ("Tools & Software list", "Rows are STSW-* (software), X-CUBE-* (STM32Cube packs), STEVAL-* / X-NUCLEO-* (boards). The list tells you which host the vendor actually supports (for the VL53L9CX it named STM32N6570-DK, Nucleo-H563ZI, Raspberry Pi, Rockchip)."),+   ("CAD (symbols, footprints, STEP)", "myST login gates exports by region/account; use the signed-in nb profile. See chip-fetcher-sites/st for the download choreography."),+   ("Spec tables when st.com is unreachable", "datasheets.com/stmicroelectronics/<part> mirrors the parametric table and works for WebFetch and pup; community.st.com posts often carry the numbers ST employees quote."),+  ],+  api="None public for product data. Stock and price: adom-mouser / adom-digikey backends via adom-parts-search (quota-limited, never loop).",+  gotchas=["A pup window that has been RST'd stays dead for that domain; do not reload it, switch lanes.", "st.com serves the HTML datasheet only from the product page modal; the /resource/*.pdf path is the reliable one.", "Country lists in innerText: filter /Worldwide,Asia/ before you read a tools table."],+  evidence="pup-bridge issue adom/pup-bridge#109 (verbatim sequence); DS14879 Rev 8 pulled via nb 2026-09-12."),++ dict(slug="mouser", name="Mouser Electronics", domains=["www.mouser.com"],+  what="Distributor. Product pages with stock, price breaks, datasheet and CAD links; Adom's preferred vendor.",+  defense="Akamai (AkamaiGHost) in front, PerimeterX / HUMAN bot defense behind it. The deny page is served with HTTP 200 and the title 'Access to this page has been denied.'",+  lanes={+   "WebFetch": ("untested", "assume blocked; use the API lane"),+   "curl": ("BLOCKED", "HTTP 200 with the PerimeterX denial page (server: AkamaiGHost)"),+   "pup": ("BLOCKED", "'Access to this page has been denied.' 899-char body, on the first navigation"),+   "nb": ("OK", "product page renders after ~9 s (the PX interstitial clears in a trusted profile); a 6 s wait reads as empty, wait 9 s"),+  },+  symptoms=["title 'Access to this page has been denied.' with HTTP 200", "'press and hold' human challenge", "x-px-* headers"],+  recipes=[+   ("Stock, price, lead time, datasheet URL", "Do not scrape. `adom-parts-search show \"<MPN>\"` (Mouser + DigiKey + JLCPCB in parallel, Mouser-preferred) or the adom-mouser CLI. Quota is shared: one search per user question."),+   ("Product page in a browser (photos, 3D tab, related parts)", "nb only: `nbrowser_open_window {profile, url:\"https://www.mouser.com/ProductDetail/<Mfr>/<MPN>\"}` then wait 9 s before eval. The URL redirects to /en/ProductDetail/..."),+   ("CAD downloads", "The 3D model sits behind a tab on the product page; see chip-fetcher-sites/mouser. Signed-in nb profile shows account pricing."),+  ],+  api="Mouser Search API via the adom-mouser backend (see the adom-parts-search skill for the current backend URL) and adom-parts-search. Free tier ~1000 calls/day shared by everyone: never poll.",+  gotchas=["The block page is HTTP 200: check the title, not the status code.", "Mouser is Adom's preferred vendor, so the API lane is the default for anything numeric."],+  evidence="curl sweep + pup/nb sweep 2026-09-12; pup denial also seen from a warm adom-you profile."),++ dict(slug="digikey", name="DigiKey", domains=["www.digikey.com"],+  what="Distributor. Product detail pages, parametric category filters, datasheet + ECAD links (SnapEDA / Ultra Librarian embeds).",+  defense="Cloudflare managed challenge ('Just a moment...') on every non-human client; HTTP 403 with a cf-ray header.",+  lanes={+   "WebFetch": ("BLOCKED", "HTTP 403"),+   "curl": ("BLOCKED", "HTTP 403, body 'Just a moment...', server cloudflare, cf-ray present"),+   "pup": ("BLOCKED", "stuck on the 'Just a moment...' challenge (263-char body, never resolves)"),+   "nb": ("OK", "product page title 'VL53L9CXV0VE/1 STMicroelectronics | Camera Modules | DigiKey' after ~9 s; the challenge clears itself in a trusted profile"),+  },+  symptoms=["title 'Just a moment...' and a 263-char body", "HTTP 403 with cf-ray", "endless spinner in pup"],+  recipes=[+   ("Stock, price, datasheet", "API lane: `adom-parts-search show \"<MPN>\"` or the adom-digikey CLI. DigiKey qty-1 pricing is often lower than Mouser on new parts (VL53L9CX: $49.07 vs $63.36); the recommendation banner still follows the Mouser-preferred policy unless you switch it."),+   ("Keyword -> category -> MPN list (parametric families)", "nb: `https://www.digikey.com/en/products/result?keywords=<QUERY>` lands on a category overview; collect the /products/filter/ links, navigate into one, then regex MPNs out of the DOM. Proven on the VL53L family (25 MPNs)."),+   ("Detail page", "`https://www.digikey.com/en/products/detail/<mfr-slug>/<mpn>/<pid>`: datasheet link, stock, price breaks, ECAD links. nb only."),+   ("CAD", "See chip-fetcher-sites/digikey: warm human profile, pace like a human, ask DigiKey's own assistant for the model path, or skip the rung (Arrow, CSE/UL)."),+  ],+  api="DigiKey Product Information API via the adom-digikey backend (URL in the adom-parts-search skill) and adom-parts-search.",+  gotchas=["Never try to solve the challenge programmatically; switch to nb.", "The 'ATTENTION: due to high order volume' banner is harmless noise in innerText."],+  evidence="curl sweep + pup/nb sweep 2026-09-12; chip-sourcing 1.0 notes (2026-06-22)."),++ dict(slug="snapeda", name="SnapEDA (SnapMagic)", domains=["www.snapeda.com"],+  what="CAD library. Symbols, footprints, 3D models per MPN; downloads gated behind a free login.",+  defense="Cloudflare managed challenge for non-human clients (403 + cf-ray); login wall on downloads; some downloads are email-gated.",+  lanes={+   "WebFetch": ("untested", ""),+   "curl": ("BLOCKED", "HTTP 403 'Just a moment...'"),+   "pup": ("BLOCKED", "stuck on 'Just a moment...' (263-char body)"),+   "nb": ("OK", "part page renders after ~9 s; the signed-in profile skips the email gate on downloads"),+  },+  symptoms=["'Just a moment...'", "Download bounces to a login modal (session lost)", "'Request this design' instead of Download (no model yet)"],+  recipes=[+   ("Part page", "`https://www.snapeda.com/parts/<MPN-urlencoded>/<Manufacturer>/view-part/` in nb; wait 9 s."),+   ("Download flow", "Download (top right) -> pick Symbol and Footprint (KiCad), 3D Model (STEP), Eagle/Fusion, Altium. Zip lands in the desktop Downloads; pull it with `adom-bridge pull_file`. Full choreography in chip-fetcher-sites/snapeda."),+  ],+  api="None public. The Adom wiki hardware pages and adom-lbr are the first stop for parts we already have.",+  gotchas=["Only ever type SnapEDA credentials into SnapEDA's own login form.", "A 6 s wait after navigate reads as null in nb; give the challenge 9 s."],+  evidence="curl + pup + nb sweep 2026-09-12; chip-sourcing 1.0 notes."),++ dict(slug="ti", name="Texas Instruments", domains=["www.ti.com"],+  what="Manufacturer. Product pages, datasheets (PDF and HTML), reference designs, Ultra Librarian CAD embed, native STEP.",+  defense="None observed. Plain Apache, HTTP 200 to curl. The friendliest major manufacturer site.",+  lanes={+   "WebFetch": ("untested", "expected OK"),+   "curl": ("OK", "HTTP 200, server Apache, 0.36 s"),+   "pup": ("OK", "product page renders"),+   "nb": ("OK", ""),+  },+  symptoms=["myTI login wall only on some downloads and reference-design files"],+  recipes=[+   ("Product page", "`https://www.ti.com/product/<PART>`; datasheet at `https://www.ti.com/lit/ds/symlink/<part-lowercase>.pdf` (follows a redirect to the current revision)."),+   ("CAD", "CAD/CAE Symbols section embeds Ultra Librarian; native STEP under the 3D model link. See chip-fetcher-sites/ti."),+  ],+  api="TI Store API for stock and price (rarely needed; use adom-parts-search).",+  gotchas=["Because curl works, prefer the cheapest lane: WebFetch or curl first, browsers only for the UL embed."],+  evidence="curl + pup + nb sweep 2026-09-12."),++ dict(slug="microchip", name="Microchip Technology", domains=["www.microchip.com"],+  what="Manufacturer (incl. Atmel, Microsemi). Product pages, datasheets, MPLAB tooling.",+  defense="Akamai on the edge: refuses curl with HTTP 403, but lets browsers through (pup included).",+  lanes={+   "WebFetch": ("untested", ""),+   "curl": ("BLOCKED", "HTTP 403, server AkamaiGHost"),+   "pup": ("OK", "product page renders (5607 chars)"),+   "nb": ("OK", ""),+  },+  symptoms=["HTTP 403 to non-browser clients"],+  recipes=[("Product page", "`https://www.microchip.com/en-us/product/<PART>` in pup (background) or nb; datasheets link from the Documents tab.")],+  api="None needed; stock via adom-parts-search.",+  gotchas=["An Akamai site that does NOT RST pup: the fingerprint threshold differs per customer, so test each site rather than assuming 'Akamai = nb only'."],+  evidence="curl + pup + nb sweep 2026-09-12."),++ dict(slug="analog", name="Analog Devices (incl. Maxim, Linear)", domains=["www.analog.com"],+  what="Manufacturer. Product pages, datasheets, LTspice models, eval boards.",+  defense="Akamai Bot Manager, strict: RSTs curl's HTTP/2 stream and serves an 'Access Denied' page to pup.",+  lanes={+   "WebFetch": ("untested", ""),+   "curl": ("BLOCKED", "curl (92) HTTP/2 INTERNAL_ERROR in ~100 ms"),+   "pup": ("BLOCKED", "'Access Denied' page (217 chars)"),+   "nb": ("OK", "product page renders (AD8232 Datasheet and Product Info)"),+  },+  symptoms=["'Access Denied' + 'Reference #18.xxxxxxxx'", "HTTP/2 stream reset to curl"],+  recipes=[("Anything on analog.com", "nb only. Datasheet PDFs are under /media/en/technical-documentation/data-sheets/<part>.pdf; fetch them with nbrowser_fetch_url once you have the link from the page.")],+  api="None public. Stock via adom-parts-search.",+  gotchas=["Same edge behavior as st.com and arrow.com: do not retry, switch to nb."],+  evidence="curl + pup + nb sweep 2026-09-12."),++ dict(slug="arrow", name="Arrow Electronics", domains=["www.arrow.com"],+  what="Distributor. Product search, stock, price, reference designs; free CAD via its own SnapEDA/UL embeds.",+  defense="Akamai Bot Manager, strict: RSTs curl, 'Access Denied' to pup.",+  lanes={+   "WebFetch": ("untested", ""),+   "curl": ("BLOCKED", "curl (92) HTTP/2 INTERNAL_ERROR"),+   "pup": ("BLOCKED", "'Access Denied' (195 chars)"),+   "nb": ("OK", "home page renders and shows the user's logged-in account (Logout visible): real logins ride along"),+  },+  symptoms=["'Access Denied' + Akamai reference number"],+  recipes=[("Search and product pages", "nb only: `https://www.arrow.com/en/products/search?q=<MPN>`."), ("CAD", "Arrow is the mid-ladder fallback when DigiKey blocks; see chip-fetcher-sites.")],+  api="Arrow has a partner API (key required); not wired into Adom yet.",+  gotchas=["Because the user is typically logged in, treat everything read here as account data: do not paste it into other sites."],+  evidence="curl + pup + nb sweep 2026-09-12."),++ dict(slug="nordic", name="Nordic Semiconductor", domains=["www.nordicsemi.com", "docs.nordicsemi.com", "devzone.nordicsemi.com"],+  what="Manufacturer. nRF product pages, docs site, DevZone forum.",+  defense="Cloudflare in front of www: curl gets the managed challenge, browsers pass (pup included).",+  lanes={+   "WebFetch": ("untested", ""),+   "curl": ("BLOCKED", "HTTP 403 'Just a moment...', cf-ray present"),+   "pup": ("OK", "home page renders (15 k chars)"),+   "nb": ("OK", ""),+  },+  symptoms=["'Just a moment...' to non-browser clients"],+  recipes=[("Docs", "docs.nordicsemi.com is a static docs site; try WebFetch first, pup second."), ("DevZone", "Login needed to post; reading is open in pup.")],+  api="None needed.",+  gotchas=["A Cloudflare site that pup passes: Cloudflare's bot score for CfT is not uniformly 'block'; DigiKey and SnapEDA set a stricter rule than Nordic."],+  evidence="curl + pup + nb sweep 2026-09-12."),++ dict(slug="ultralibrarian", name="Ultra Librarian", domains=["www.ultralibrarian.com", "app.ultralibrarian.com"],+  what="CAD library (also the engine behind many manufacturer CAD embeds). Symbols, footprints, 3D; free account needed to download.",+  defense="Open to curl (nginx, 200) but the download flow carries a captcha widget and a login wall.",+  lanes={+   "WebFetch": ("untested", ""),+   "curl": ("OK", "HTTP 200 (the HTML contains a captcha widget for the download/login flow)"),+   "pup": ("OK", "home page renders"),+   "nb": ("OK", "signed-in profile skips the login; captcha, if shown, needs the human: nbrowser_flash and wait"),+  },+  symptoms=["captcha on download or account creation", "login bounce on the format picker"],+  recipes=[("Download flow", "Search MPN -> part page -> Download -> pick KiCad / STEP (Altium if offered). Choreography and the reskinned-picker recovery in chip-fetcher-sites/ultralibrarian."), ("Manufacturer embeds", "TI, Microchip and others embed UL; the same account works inside the embed.")],+  api="None public.",+  gotchas=["Never solve the captcha programmatically; flash the window and let the user click."],+  evidence="curl + pup + nb sweep 2026-09-12."),++ dict(slug="componentsearchengine", name="Component Search Engine (SamacSys)", domains=["componentsearchengine.com", "www.samacsys.com"],+  what="CAD library (SamacSys / Supplyframe). Free symbols, footprints, 3D; the CSE desktop 'Library Loader' or web downloads.",+  defense="Cloudflare on componentsearchengine.com (curl 403), samacsys.com is Vercel and open. Browsers pass.",+  lanes={+   "WebFetch": ("untested", ""),+   "curl": ("BLOCKED", "HTTP 403 'Just a moment...' (samacsys.com: 200)"),+   "pup": ("OK", "home page renders (6157 chars)"),+   "nb": ("OK", ""),+  },+  symptoms=["'Just a moment...' to curl"],+  recipes=[("Search + download", "pup works for browsing; downloads need the free login (use nb so the saved login applies). Format picker: KiCad, Altium, Eagle/Fusion, STEP.")],+  api="None public.",+  gotchas=["CSE and Ultra Librarian are the last two rungs of the chip-fetcher ladder; both pass pup, so they are the cheap fallback when DigiKey/SnapEDA block."],+  evidence="curl + pup + nb sweep 2026-09-12."),++ dict(slug="datasheets-com", name="datasheets.com", domains=["www.datasheets.com"],+  what="Aggregator (Supplyframe). Parametric spec tables and datasheet links per MPN; a handy mirror when the manufacturer blocks you.",+  defense="Cloudflare for curl only; the Anthropic fetcher and browsers pass.",+  lanes={+   "WebFetch": ("OK", "returned the VL53L9CX parametric table (interfaces, FoV, temperature, supply)"),+   "curl": ("BLOCKED", "HTTP 403 'Just a moment...'"),+   "pup": ("OK", "'VL53L9CX Datasheet | Stmicroelectronics' renders"),+   "nb": ("OK", ""),+  },+  symptoms=["'Just a moment...' to curl"],+  recipes=[("Quick spec table", "`https://www.datasheets.com/<manufacturer-slug>/<part-lowercase>` via WebFetch. Good for interface / rate / package numbers when the vendor page is unreachable; confirm against the vendor PDF before designing.")],+  api="None.",+  gotchas=["It summarizes the datasheet; it does not replace it (lane counts, timing tables and ambient tables were only in the ST PDF)."],+  evidence="WebFetch + curl + pup + nb 2026-09-12."),++ dict(slug="octopart", name="Octopart (and findchips)", domains=["octopart.com", "www.findchips.com"],+  what="Aggregators (Nexar / Supplyframe). Cross-distributor stock and price, datasheet links, CAD links.",+  defense="Cloudflare in front, but both pass curl with HTTP 200 today.",+  lanes={+   "WebFetch": ("untested", ""),+   "curl": ("OK", "octopart search 200 (1.0 s); findchips 200"),+   "pup": ("untested", ""),+   "nb": ("untested", ""),+  },+  symptoms=["Octopart search results are JS-rendered; curl gets the shell, use a browser for the table"],+  recipes=[("Cross-distributor snapshot", "`https://octopart.com/search?q=<MPN>` in pup; for numbers you will act on, use adom-parts-search (real vendor APIs).")],+  api="Nexar GraphQL API (token required); not wired into Adom.",+  gotchas=["Prices here lag the distributors; treat as a map, not a quote."],+  evidence="curl sweep 2026-09-12 only; browser lanes not yet verified: contribute."),++ dict(slug="jlcpcb", name="JLCPCB parts and LCSC", domains=["jlcpcb.com", "www.lcsc.com"],+  what="Assembly-house parts library (JLCPCB SMT) and its distributor sibling LCSC. Basic/extended part flags, LCSC part numbers (C-numbers).",+  defense="Akamai on jlcpcb.com, but it passes curl (200); lcsc.com open (200).",+  lanes={+   "WebFetch": ("untested", ""),+   "curl": ("OK", "jlcpcb.com/parts 200 (1.9 s); lcsc.com 200"),+   "pup": ("untested", ""),+   "nb": ("untested", ""),+  },+  symptoms=["Product URLs need the C-number: /product-detail/<slug>_C<number>.html (a bare C-number path 404s)"],+  recipes=[("Parts library lookup", "API lane first: the adom-jlcpcb backend via adom-parts-search; it returns basic/preferred flags and the LCSC number. Browser only for photos."), ("Assembly quotes", "jlcpcb.com in pup; login needed for a real quote.")],+  api="JLCPCB parts via the adom-jlcpcb backend and adom-parts-search.",+  gotchas=["JLCPCB is never the delivery recommendation in adom-parts-search; it is the assembly library."],+  evidence="curl sweep 2026-09-12; browser lanes not yet verified: contribute."),++ dict(slug="manufacturer", name="Other manufacturers (generic playbook)", domains=["www.nxp.com", "www.infineon.com", "www.renesas.com", "www.espressif.com", "www.newark.com (Farnell / Avnet)"],+  what="The generic approach for a manufacturer or distributor site that does not have its own skill yet, plus lane-0 readings for a few we have only curl-tested.",+  defense="Varies. Lane-0 readings 2026-09-12: infineon.com 200 (open), renesas.com 200 (Cloudflare, passes curl), espressif.com 200 (Apache, open), newark.com 200 (Akamai, passes curl), nxp.com 404 on /products/ (URL shape differs; product pages are /products/<family>/...:<PART>).",+  lanes={+   "WebFetch": ("untested", ""),+   "curl": ("PARTIAL", "see the per-domain readings above"),+   "pup": ("untested", ""),+   "nb": ("untested", ""),+  },+  symptoms=["Unknown site: run the ladder and record what you see"],+  recipes=[+   ("The ladder for a new site", "1) ONE WebFetch or curl. 2) If it fails or returns a wall, pup in the background. 3) If pup shows a denial/challenge, nb with a 9 s settle. 4) Data you will act on: the official API or the Adom vendor backends. Record the result in a new sub-skill (copy skills/_template)."),+   ("Reading a manufacturer product page", "Datasheet link is usually the first PDF on the page; CAD lives under 'Design resources' / 'CAD/CAE' / 'Symbols and footprints'; many embed Ultra Librarian or SnapEDA (see those skills)."),+  ],+  api="Stock and price for any of them: adom-parts-search.",+  gotchas=["The bot-defense vendor alone does not predict the outcome (Akamai: st.com blocks pup, microchip.com does not; Cloudflare: DigiKey blocks pup, Nordic does not). Test, then write it down with the date."],+  evidence="curl sweep 2026-09-12."),+]++SKILL_PREFIX = "electronics-site-"++def lane_table(site):+    rows = ["| Lane | Result | What we saw (" + VERIFIED + ") |", "|---|---|---|"]+    for L in LANES:+        r, note = site["lanes"].get(L, ("untested", ""))+        rows.append(f"| {L} | **{r}** | {note or '-'} |")+    return "\n".join(rows)++def site_skill(site):+    name = SKILL_PREFIX + site["slug"]+    doms = ", ".join(site["domains"])+    best = [L for L in LANES if site["lanes"].get(L, ("",))[0] == "OK"]+    best_lane = best[0] if best else "nb"+    trig = ", ".join(sorted(set([d.replace("www.", "").split(" ")[0] for d in site["domains"]] + [site["name"].split(" (")[0].lower()])))+    desc = (f"{site['name']} ({doms}): how the site treats AI clients on each access lane (WebFetch, curl, pup, nb, API), "+            f"the bot defense it runs, the exact failure signatures, and the task recipes. Verified {VERIFIED}. "+            f"Read BEFORE fetching anything from this domain, or when a fetch to it times out, 403s, or shows a challenge page. "+            f"Trigger words: {trig}, {site['slug']} blocked, {site['slug']} datasheet, {site['slug']} product page, {site['slug']} timeout, {site['slug']} access denied.")+    out = [+        "---", f"name: {name}", "description: >-", "  " + desc, "---", "",+        f"Parent skill: **{PACK}** (the `electronics-sites` root skill has the lane ladder and the symptom table).", "",+        f"# {site['name']}", "",+        f"**Domains:** {doms}  ", f"**What it is:** {site['what']}  ", f"**Bot defense:** {site['defense']}", "",+        "## Lane matrix", "", lane_table(site), "",+        f"**Use first:** `{best_lane}`" + (" (cheapest lane that works)" if best else " (nothing cheaper works)") + ". For numbers you will act on (stock, price, lead time) use the API lane below, never a scrape.", "",+        "## Failure signatures", "",+    ]+    out += [f"- {s}" for s in site["symptoms"]]+    out += ["", "## Recipes", ""]+    for title, body in site["recipes"]:+        out += [f"### {title}", "", body, ""]+    out += ["## API / Adom backend lane", "", site["api"], "", "## Gotchas", ""]+    out += [f"- {g}" for g in site["gotchas"]]+    out += ["", "## Evidence and last verified", "", f"{site['evidence']} Re-verify when a lane changes and bump the date; the freshest reading wins.", ""]+    text = "\n".join(out)+    assert "\u2014" not in text and "\u2013" not in text, name+    return name, text++def matrix_md():+    rows = ["| Site | Bot defense | WebFetch | curl | pup | nb | Use first |", "|---|---|---|---|---|---|---|"]+    for s in SITES:+        vals = [s["lanes"].get(L, ("untested",))[0] for L in LANES]+        best = next((L for L, v in zip(LANES, vals) if v == "OK"), "nb")+        d = s["defense"].split(".")[0].split(",")[0].split(" on ")[0][:38]+        rows.append(f"| [{s['name']}](skills/{s['slug']}/SKILL.md) | {d} | " + " | ".join(vals) + f" | {best} |")+    return "\n".join(rows)++def skill_map_md():+    rows = ["| Skill | Site | One line |", "|---|---|---|"]+    rows.append(f"| **electronics-sites** | (root) | The lane ladder, the symptom table, what pup leaks that a real browser does not, and the matrix below. |")+    for s in SITES:+        rows.append(f"| **{SKILL_PREFIX}{s['slug']}** | {s['domains'][0]} | {s['what'].split('.')[0]}. |")+    rows.append("| **_template** | (copy me) | The fixed section layout for a new site skill. |")+    return "\n".join(rows)++ROOT_SKILL = f"""---+name: electronics-sites+description: >-+  THE map of how electronics-industry websites treat AI clients, one sub-skill per site (ST, TI, Mouser,+  DigiKey, SnapEDA, Ultra Librarian, Arrow, Analog, Microchip, Nordic, Component Search Engine, Octopart,+  JLCPCB/LCSC, datasheets.com, generic manufacturer). Which access lane works (WebFetch, curl, pup, nb,+  official API), which bot defense each runs (Akamai, PerimeterX, Cloudflare), the exact failure+  signatures, and what a Chrome-for-Testing window leaks that a real browser does not. Read BEFORE+  fetching anything from a vendor or manufacturer domain, and the MOMENT a fetch to one times out,+  403s, 473s, shows 'Just a moment', 'Access Denied', 'Access to this page has been denied', or+  ERR_HTTP2_PROTOCOL_ERROR. Trigger words: st.com, ti.com, mouser, digikey, snapeda, ultralibrarian,+  arrow, analog.com, microchip, nordicsemi, componentsearchengine, octopart, jlcpcb, lcsc, datasheets.com,+  vendor site blocked, manufacturer site timeout, datasheet download blocked, bot wall, cloudflare+  just a moment, akamai access denied, perimeterx, captcha on vendor site, which browser for this site,+  electronics website, electronics industry site skills.+---++# Electronics Industry Site Skills++Nobody built skills for the electronics industry's websites, so an AI meets each one cold: a fetch to+st.com times out, a pup window on DigiKey spins forever, Mouser answers HTTP 200 with a denial page, and+the AI reports "the site is down" when the site is fine and only that CLIENT was refused. These+vendors block anything that does not look like a person because they have not noticed the AI era+yet. This pack records, per site, exactly how it behaves toward each lane we have, dated, with the+evidence, so the next AI routes around the wall in one move instead of rediscovering it.++Every sub-skill has the same sections (domains, bot defense, lane matrix, failure signatures, recipes,+API lane, gotchas, evidence). Verified live on {VERIFIED} from an Adom cloud container against a+Windows desktop running Adom Bridge 2.1.104, pup 2.0.464 (Chrome for Testing 148) and the Native+Browser Extension 0.16.14 (Chrome 152).++## The rule: a fetch failure on a vendor domain is a ROUTING signal++1. **One plain attempt only.** WebFetch or curl once. It is the cheapest lane and it works on TI,+   community.st.com, datasheets.com (WebFetch), Octopart, JLCPCB, Infineon, Espressif.+2. **pup second** (a real rendered Chrome, background, the durable adom-you profile). Passes+   Microchip, Nordic, CSE, Ultra Librarian, datasheets.com; renders vendor CDN PDFs.+3. **nb third and last** (the user's real signed-in Chrome via nbrowser_*). Passes EVERYTHING we have+   tested, including the four that refuse pup (st.com after the first page, Mouser, DigiKey, SnapEDA,+   Analog, Arrow). Give a challenge page **9 seconds** to clear before you read the DOM: a 6 s wait+   reads as empty and looks like a block that is not there.+4. **Numbers you will act on come from an API, never a scrape:** `adom-parts-search show "<MPN>"`+   (Mouser + DigiKey + JLCPCB backends, quota-limited: one search per question, never a loop).++Never retry the failing lane, never report "the site is timing out", never try to solve a captcha or+spoof a fingerprint. Switch lanes, then write down what you saw in that site's skill.++## Symptom -> cause -> lane++| You see | It is | Do |+|---|---|---|+| `ERR_HTTP2_PROTOCOL_ERROR` / `chrome-error://` on the SECOND page of a domain in pup; `curl: (92) HTTP/2 stream ... INTERNAL_ERROR` in ~100 ms | Akamai Bot Manager reset the stream after its sensor verdict (st.com, analog.com, arrow.com) | nb. Do not reload the pup tab; it stays dead for that domain |+| `Access Denied` + `Reference #18.xxxx` | Akamai deny page (analog, arrow to pup; microchip to curl) | nb (pup passes microchip; test, do not assume) |+| `Access to this page has been denied.` with **HTTP 200**, or `press and hold` | PerimeterX / HUMAN (mouser.com) | nb for the page, API for the data |+| title `Just a moment...`, 263-char body, HTTP 403, `cf-ray` header | Cloudflare managed challenge (digikey, snapeda, CSE, nordic, datasheets.com, alldatasheet) | pup passes CSE / nordic / datasheets.com; digikey + snapeda need nb; wait 9 s |+| HTTP 473 | Akamai to a fetcher (newsroom.st.com) | pup or nb |+| WebFetch 60 s timeout on a vendor URL | the fetcher is being tarpitted, not an outage | pup, then nb |+| `captcha` in the HTML but the page renders | login / download gate (ultralibrarian) | nb, `nbrowser_flash`, let the human click |+| `renderCheck.ok:true` but the body says `This site can't be reached` | pup's render check does not flag chrome-error pages (adom/pup-bridge#109) | read the body yourself; switch lanes |++## What pup leaks that a real browser does not (measured on st.com, {VERIFIED})++| Probe | pup (Chrome for Testing 148) | nb (installed Chrome 152) |+|---|---|---|+| `navigator.webdriver` | **true** | false |+| Widevine (`requestMediaKeySystemAccess('com.widevine.alpha')`) | **NotSupportedError** (CfT ships without the CDM) | available |+| `navigator.userAgentData.brands` | Not/A)Brand 99, Chromium 148 (no "Google Chrome") | Chromium 152, Not?A_Brand 24, Google Chrome 152 |+| User agent major | 148 (cached build, four majors behind) | 152 |+| `Notification.permission` on first visit | granted (pup pre-grants 32 permissions) | default |+| Akamai `_abck` on first load | ~-1~ (unvalidated) | ~-1~ (unvalidated) |+| plugins / cores / memory / h2 | 5 / 16 / 32 GB / h2 | identical |++Both clients get the same unvalidated Akamai cookie; the verdict comes from the sensor telemetry, and+webdriver + no Widevine + no Google Chrome brand is enough. A cold isolated pup profile and 15 s+between requests changed nothing; the same navigation in nb returned 200. That is why the ladder ends+at nb and why pup is still worth trying first on the sites that do not look this closely.++## The matrix (lane 0 = curl; verified {VERIFIED})++{matrix_md()}++Lane 0 readings for sites without a skill yet: infineon.com 200, renesas.com 200 (Cloudflare, passes),+espressif.com 200, newark.com 200 (Akamai, passes), alldatasheet.com 403 (Cloudflare), samacsys.com+200 (Vercel). Add a skill when you touch one.++## Skill map++{skill_map_md()}++## Adding a site (this pack is built to grow)++Copy `skills/_template/SKILL.md` to `skills/<site>/SKILL.md`, run the ladder against the site, fill+every section with what you SAW (dated), list the new skill in `install.sh`, `uninstall.sh` and+`package.json` `files[]`, bump the version, publish. Rules: one site per skill, ASCII punctuation only,+no captcha solving, no fingerprint spoofing, credentials only ever typed into the site's own login+form. Related packs: `chip-fetcher-sites` (the CAD download choreography per site, inside+adom/adom-chip-fetcher), `driving-sites` (nb login recipes, inside adom/nb-bridge), `pup` and+`adom-native-browser` (the two browser lanes themselves), `adom-parts-search` (the API lane).+"""++TEMPLATE = f"""---+name: {SKILL_PREFIX}SITE+description: >-+  (Template) <Site name> (<domain>): how the site treats AI clients on each access lane (WebFetch,+  curl, pup, nb, API), the bot defense it runs, the exact failure signatures, and the task recipes.+  Copy this folder to skills/<site>/, fill every section with what you SAW, and date it.+  Trigger words: <domain>, <site> blocked, <site> datasheet, <site> product page, <site> timeout.+---++Parent skill: **{PACK}**++# <Site name>++**Domains:** <www.example.com, cdn.example.com>+**What it is:** <manufacturer / distributor / CAD library / aggregator; what people fetch from it>+**Bot defense:** <Akamai / Cloudflare / PerimeterX / none observed; how it presents>++## Lane matrix++| Lane | Result | What we saw (<date>) |+|---|---|---|+| WebFetch | OK / BLOCKED / PARTIAL / untested | <status code, title, timing> |+| curl | ... | <curl -sS -o /dev/null -w 'http=%{{http_code}} server=%header{{server}} cf=%header{{cf-ray}}'> |+| pup | ... | <title + first 100 chars of body after a 6 s settle> |+| nb | ... | <same, after a 9 s settle> |++**Use first:** `<cheapest lane that works>`.++## Failure signatures++- <exact title / body text / status / header that identifies the wall>++## Recipes++### <Task, e.g. datasheet PDF>++<URL pattern, DOM ids, the verb sequence, the wait you needed>++## API / Adom backend lane++<official API, or the adom-* backend that already wraps it; quota notes>++## Gotchas++- <the thing that cost you an hour>++## Evidence and last verified++<how you verified, date, issue links>+"""++def write(rel, text):+    p = os.path.join(ROOT, rel); os.makedirs(os.path.dirname(p), exist_ok=True)+    assert "\u2014" not in text and "\u2013" not in text, rel+    with open(p, "w") as f: f.write(text)++def main():+    names = []+    for s in SITES:+        name, text = site_skill(s)+        write(f"skills/{s['slug']}/SKILL.md", text); names.append((name, f"skills/{s['slug']}/SKILL.md"))+    write("SKILL.md", ROOT_SKILL)+    write("skills/_template/SKILL.md", TEMPLATE)+    # installer / uninstaller list every skill explicitly (publish linter rule)+    inst = ["#!/usr/bin/env bash", "# electronics-sites-skillpack installer: the root skill plus one skill per site.", "set -euo pipefail",+            'DEST="${CLAUDE_SKILLS_DIR:-$HOME/.claude/skills}"',+            'install_skill(){ mkdir -p "$DEST/$1"; cp "$2" "$DEST/$1/SKILL.md"; echo "  installed $1"; }',+            'echo "Installing electronics-sites-skillpack -> $DEST"',+            'install_skill electronics-sites SKILL.md']+    inst += [f'install_skill {n} {p}' for n, p in names]+    inst += ['# v1 of this pack was named chip-sourcing-*; retire those copies so the two sets do not drift',+             'for old in chip-sourcing chip-sourcing-digikey chip-sourcing-mouser chip-sourcing-snapeda chip-sourcing-manufacturer; do rm -rf "$DEST/$old"; done',+             f'echo "OK: installed electronics-sites plus {len(names)} site skills to $DEST/. Hint: read ~/.claude/skills/electronics-sites/SKILL.md before fetching from any vendor domain."']+    write("install.sh", "\n".join(inst) + "\n")+    un = ["#!/usr/bin/env bash", "set -euo pipefail", 'DEST="${CLAUDE_SKILLS_DIR:-$HOME/.claude/skills}"',+          'for s in electronics-sites ' + " ".join(n for n, _ in names) + '; do rm -rf "$DEST/$s"; done',+          'echo "OK: removed electronics-sites-skillpack skills from $DEST/."']+    write("uninstall.sh", "\n".join(un) + "\n")+    files = ["SKILL.md", "README.md", "page.json", "package.json", "install.sh", "uninstall.sh", "skills/_template/SKILL.md"] + [p for _, p in names]+    TAGS = ["electronics", "skillpack", "vendor-sites", "bot-defense", "akamai", "cloudflare", "perimeterx", "pup", "nb", "datasheets", "digikey", "mouser", "st"]+    TRIGGERS = ["st.com", "digikey", "mouser", "snapeda", "ultralibrarian", "arrow.com", "analog.com", "microchip", "nordicsemi", "componentsearchengine", "octopart", "jlcpcb", "lcsc", "datasheets.com", "vendor site blocked", "just a moment", "access denied", "ERR_HTTP2_PROTOCOL_ERROR", "bot wall", "electronics website skills"]+    PITCH = "One skill per electronics-industry website: the access lane that works, the bot wall you hit, the recipe. Dated and verified."+    PROMPTS = [{"label": "st.com timing out", "prompt": "st.com is timing out, get me the VL53L9CX datasheet"},+               {"label": "Which browser for DigiKey", "prompt": "which browser lane do I need to read a DigiKey product page"},+               {"label": "Mouser 200 but denied", "prompt": "why does Mouser return HTTP 200 with access denied and what do I do"},+               {"label": "Add a site", "prompt": "add an electronics-sites skill for nxp.com and verify its lanes"}]+    pkg = {"name": PACK, "slug": PACK, "version": "2.0.0", "type": "skill", "title": TITLE, "description": BRIEF,+           "license": "MIT", "files": files, "scripts": {"install": "./install.sh", "uninstall": "./uninstall.sh"},+           "dependencies": {}, "tags": TAGS, "keywords": TAGS, "discovery_triggers": TRIGGERS, "discovery_pitch": PITCH,+           "sample_prompts": PROMPTS, "hero": {"path": "docs/hero.png"}}+    write("package.json", json.dumps(pkg, indent=2) + "\n")+    readme = f"""# {TITLE}++{BRIEF}++Every vendor site is different and most of them refuse anything that does not look like a person. This+pack turns that into a table an AI can act on: per site, which lane works (WebFetch, curl, pup, nb, API),+which wall you hit when it does not, and the recipe for the common tasks (datasheet, product page,+CAD, stock/price). Dated, evidence-linked, and shaped so a new site is one copied template away.++## The matrix (verified {VERIFIED})++{matrix_md()}++## Skills++{skill_map_md()}++## Install++```bash+adom-wiki pkg install john/{PACK}+```++The root skill `electronics-sites` carries the lane ladder and the symptom table; each site skill+auto-surfaces on its domain name. Old `chip-sourcing-*` copies from v1 are retired by the installer.++## Why this exists++An AI that fetches st.com gets a 60 s timeout; the same URL opens instantly in the user's own Chrome.+The failure was the client, not the site, but without a record the AI reports an outage and stops.+Recording each site's behavior once, with the date, is cheaper than every AI rediscovering it.+The two browser lanes are Adom's: `pup` (a real Chrome for Testing window, background, durable profile)+and `nb` (the user's real signed-in browser via the Native Browser Extension). The API lane is+`adom-parts-search` (Mouser, DigiKey, JLCPCB backends).++## Contributing++Copy `skills/_template/SKILL.md`, run the ladder, write down what you saw, list the skill in+`install.sh`, `uninstall.sh` and `package.json`, bump the version, publish. ASCII punctuation only.+No captcha solving, no fingerprint spoofing: this pack routes around walls, it does not break them.+"""+    write("README.md", readme)+    page = json.load(open(os.path.join(ROOT, "page.json")))+    page.update({"slug": PACK, "version": "2.0.0", "title": TITLE, "brief": BRIEF, "readme": readme, "license": "MIT",+                 "tags": TAGS,+                 "hero": {"type": "image", "path": "docs/hero.png"},+                 "sample_prompts": PROMPTS, "discovery_triggers": TRIGGERS, "discovery_pitch": PITCH,+                 "source_path": "SKILL.md"})+    write("page.json", json.dumps(page, indent=2) + "\n")+    print(f"OK: wrote root + {len(names)} site skills + template + installer + manifest + README + page.json")+    print("Hint: run `adom-wiki pkg lint` next, then rename the page, publish, push, verify in pup.")++if __name__ == "__main__":+    main()
install.sh

  
uninstall.sh