DeepSeek Harness Plugin Hub

发布与管理完整 Harness Profiles,发现适合你的插件。

探索

插件目录环境预设文档中心动态

社区

发布插件联系我们报告问题

相关链接

Plugin Hub GitHubDeepSeek Harness 官方项目系统状态隐私说明
© 2026 DeepSeek Harness Plugin HubPowered byPaxTech

独立、非官方社区项目,与 DeepSeek 官方无隶属、授权或背书关系。

DeepSeek Harness Plugin Hub
ProfilesPlugins分类动态文档登录管理 Profiles
ProfilesPlugins分类动态文档登录
← Plugins

@jayyuen66/dsh-scrapling

Scrapling

Scrapling 网页抓取插件(dsh):静态/浏览器抓取 + CSS/XPath 结构化抽取 + 选择器自愈 + capture_xhr 与会话网络历史 + 登录态存取 + Spider 爬虫,由常驻 Python 进程驱动。

插件会安装到这里;不确定时保持 web。

npx -y @deepseek-ai/dsh plugin --profile web add github:JayYuen666/dsh-scrapling#52bbc8b89265839b7de50e501a31db2cdb926e66
README兼容性版本

兼容性与来源证明

Scrapling 以 @jayyuen66/dsh-scrapling 发布,当前版本为 0.1.7。Plugin Hub 会校验它的 manifest,并保存精确安装来源,便于复现安装结果。

DSH 兼容范围
*
运行环境
web
发布来源
github
Registry 更新时间
2026/11/4

版本

0.1.7stable
2026/11/4
0.1.6stable
2026/10/31
0.1.5stable
2026/10/29
查看其余 12 个版本收起版本
0.1.4stable
2026/10/28
0.1.3stable
2026/10/27
0.1.2stable
2026/10/24
0.1.1stable
2026/10/19
0.1.0stable
2026/10/19
0.0.3stable
2026/10/13
0.0.2stable
2026/10/12
0.0.1stable
2026/10/11
0.0.1-rc.3prerelease
2026/10/9
0.0.1-rc.2prerelease
2026/10/9
0.0.1-rc.1prerelease
2026/10/8
0.0.1-rcprerelease
2026/10/8

相关插件

正在加载相关插件…

最新版
0.1.7
DSH
*
HMR
重启进程
Tree shaking
按文件声明 side effects
解包体积
未提供
文件数
未提供
Surface
web
许可证
MIT
发布源
github
GitHub
★ 1
周下载
1,167
最近提交
2026/11/4
查看源码 ↗项目主页 ↗
README Badge

点击下方 Badge 复制 Markdown,粘贴到 README 即可。

这是你的 Plugin?认领权益 · 优先安全扫描

验证 package.json 声明的 GitHub 仓库,即可管理这个公开页面。认领后,Hub 会优先安排当前版本的安全扫描,并在通过后公开展示结果。

认领这个 Plugin →
报告问题

相关插件

继续浏览 search-research 分类下经过校验的插件。

Browser Skill Dsh Plugin@wxg-prc-cpg/browser-skill-dsh-plugin向模型提供 BrowserSkill 浏览器自动化功能(bsk_* 工具)的 DeepSeek Harness 工具插件Free Searchdsh-free-searchDeepSeek Harness 的免费网页搜索:27 个引擎(Bing/DuckDuckGo/AnySearch/SearXNG/Exa/Tavily/Keenable/Firecrawl/Parallel/You.com 无需密钥;通过 MCP 支持 Zhihu 全局/站内搜索,以及 Perplexity/SerpBase/DeepSeek(官方 + 原生工具/搜索)/Baidu/Kimi/Aliyun/Douba)Ab Wechat Scrapedsh-ab-wechat-scrape--- description: "树外 wechat_scrape 工具:获取微信公众号文章,过滤推广区块,并将每篇文章保存为 Markdown 文件,供此机器的 dsh 配置文件维护者使用。" kind: "package-reference" ---Arisdsh-arisARIS research-workflow 技能作为 DeepSeek Harness 捆绑包:包含 82 项技能,以及通过 Codex 进行的跨模型对抗性审查。

README

dsh-scrapling logo

dsh-scrapling

Scrapling web scraping for dsh: JavaScript rendering, structured extraction and a background crawler, behind one resident Python sidecar.

tech stack

A web-scraping extension for dsh: Scrapling as agent tools — JavaScript rendering, CSS/XPath extraction with self-healing selectors, sessions with login state, a background crawler and page Q&A. You supply the interpreter and the browser engine; everything runs in one resident Python process.

查看图片 Contents

  • What it does
  • Install
  • Runtime packs (macOS arm64)
  • Verify it is live
  • What it does not provide
  • Enabling it in dsh
  • The tools
  • Replacing the built-in fetch and search
  • MCP mode
  • Settings
  • Proxies
  • Security
  • Data and privacy
  • Known boundaries
  • FAQ
  • Development
  • License

查看图片 What it does

dsh ships web_fetch and web_search. The built-in web_fetch is deliberately honest and minimal: a plain HTTP(S) GET with no JavaScript execution and an honest User-Agent. That is a good default — and it means a single-page application yields only its loading shell, and there is no way to ask for "just the product table". Measured on a page whose content exists only after JavaScript runs:

PipelineText obtained
raw HTML to text (no JS)SPA Shell / LOADING_PLACEHOLDER
Fetcher (curl_cffi, still no JS)SPA Shell / LOADING_PLACEHOLDER
browser render (patchright)full rendered DOM, Markdown table, structured rows

This package adds what the built-ins structurally cannot do:

  • Browser rendering — JavaScript execution via patchright.
  • Structured extraction — CSS/XPath selection returning data, not a wall of Markdown.
  • Self-healing selectors — Scrapling's adaptive mode relocates an element after the site's markup changes; a selector that returns None after a class rename returns the relocated element with adaptive on.

Everything runs against one long-lived Python process. That is not an optimisation: Scrapling's session classes keep cookies, the browser engines keep a page pool, and adaptive keeps a SQLite fingerprint database — a process per call would throw all of that away. Tools are registered conditionally: the plugin probes what the environment can actually do at startup and registers only what will work, so the model never burns a turn on a tool that was guaranteed to fail. Every tool renders its result row through the official tool-call view slot. scrapling_answer is the only tool that spends another model call — see The tools.

查看图片 Install

dsh plugin --profile web add @jayyuen66/dsh-scrapling

Remove with dsh plugin --profile web remove @jayyuen66/dsh-scrapling. The package is on the public npm registry, so installation needs no credentials and no scoped-registry line.

  • Requires Node.js >= 22.18. This is an ESM-only package, and the build entry points (build-client.ts, build-host.ts, build-cli.ts) are executed by node directly; TypeScript type stripping is on by default from Node 22.18, so that is the floor that keeps pnpm build and prepack working on a declared-supported version.
  • Requires host dsh at the 0.2.1-rc.5 or 0.2.2-alpha.3 line. The requirement the host actually reads is engines.dsh (^0.2.1-rc.5 || ^0.2.2-alpha.3), and it decides alone; a host on a later line is refused and needs the explicit risk grant dsh plugin --profile <p> allow-version <pkg>@<ver> --dsh-version <exact> --accept-risk. The support window rolls: this package is verified against every released host version inside the window at publish time and re-verified once the window moves, so the version number in this bullet is not a standing promise.
  • Requires Python 3.14+ with Scrapling installed — named by pythonBin, or found by the fixed chain in lib/runtime.ts (managed runtime under dataDir/runtime, then the repository's _env/.venv, then PATH). In an installed package the _env/.venv leg does not exist, so after setup --feature core --apply the interpreter is that managed Python and otherwise the python3 on your PATH (python on Windows); whichever one is chosen is logged at load time, and a missing Scrapling reports an error carrying the install line to copy.
pip install "scrapling[rag]>=0.5,<0.6"
python -m patchright install chromium
  • Why rag and not fetchers: rag is fetchers plus markdownify, and the default extractionType is markdown — with plain fetchers the first fetch raises ModuleNotFoundError at the Markdown conversion.
  • Version range: developed and verified against Scrapling 0.5, through interfaces that are not stable API surface (Selector, Spider, SessionManager, and the converter's noise-stripping helpers). The upper bound is pinned below 0.6; outside the range the guards still run, but the anti-injection cleaning and the crawl path may not behave as documented. In 0.5 the browser backend is patchright only (the old standalone stealth fetch is gone; its anti-detection arguments live on scrapling_render), capture_xhr became session-level recording (hence scrapling_session_network), and extra_flags became additive — this package sends none, and WebRTC leak protection is carried by the blockWebrtc switch.
  • Without the installer, the browser engine is yours. The optional CLI can install the verified macOS arm64 set; the plugin itself downloads nothing at load time. Without a browser it simply does not register the browser-dependent tools, so the model never wastes a turn calling them.
  • No @deepseek-ai/* under dependencies, by contract (test/publish-manifest.test.ts enforces it). Consumer profiles install with nodeLinker: hoisted and autoInstallPeers: false, so a host package there becomes a real second copy whose module-level state splits from the running host's — every native tool call then throws while build and tests stay green. The host packages the artifact imports for value are required peers and stay externalized; the gate imports the built artifact and asserts that no host package body is inlined.
  • The artifact is ESM-only and never committed — prepack rebuilds it, so what ships is always what the current source produces.

查看图片 Shared version floor

If your profile pins @jayyuen66/dsh-plugin-shared with a pnpm overrides entry, that pin must be at least the version this package declares. A lower pin installs a copy without the names the sources use, and the host then reports N entries did not activate together with does not provide an export named ... at boot - the package installs fine and never loads, and no per-repository CI run can see it.

Runtime packs (macOS arm64)

The package ships bin/dsh-scrapling.mjs. It installs the ability packs into dataDir/runtime (default ~/.dsh-scrapling) — never into system Python and never into the user's Playwright cache. There is no postinstall; nothing is downloaded when the plugin loads.

dsh-scrapling setup --feature core --apply
dsh-scrapling setup --feature browser --apply
dsh-scrapling setup --status
  • core installs managed Python 3.14.9, Scrapling 0.5 and its pinned wheels; browser adds the full Chromium build (not the headless shell).
  • search, media, mcp and adaptive-redis all ride on core's venv: mcp brings the official scrapling-mcp server (the pinned mcp package; its tool set is the upstream's, not this plugin's) that official-mcp mode needs, and adaptive-redis brings the redis client behind adaptiveStorage: redis. Every pack is installable; the settings card reports each as ready, missing, corrupt or installing.
  • --feature is a dry run by default; --apply writes. --repair re-installs without touching user data, and --uninstall <pack> [--purge] removes runtime files; only --purge removes elements_storage.db and downloads/.
  • --offline <dir> installs with no network at all: <dir> must hold the Python archive named exactly as the manifest URL's last segment (browser and curl -O both keep cpython-3.14.9+…-install_only.tar.gz, and the percent-encoded spelling is accepted too) plus a wheels/ directory with one wheel per pinned requirement - pip download --only-binary=:all: --require-hashes -r py/runtime-requirements.txt -d <dir>/wheels produces it. The hash check on the Python archive is the same one the networked path runs, and pip is invoked with --no-index; a missing archive or wheelhouse fails as INSTALL_OFFLINE_ASSETS instead of silently going online.
  • setup --status prints one JSON document: dataDir, profile, runtime, packs and state. With --profile <dir> it reads that profile's cordis.patch.yml and resolves the interpreter from this package's pythonBin (the setting the host actually uses); without it, profile.path is null, a note says the profile was not read, and runtime falls back to the CLI's own chain (managed runtime → repo _env/.venv → PATH python3). Pass --profile whenever you compare the card's interpreter with what --status reports.

查看图片 Verify it is live

  1. The layer is mounted. dsh --profile web --dump-config shows this package's layer line.
    • That command is the only accepted "it is in the layer stack" signal, and it is not read-only — it rewrites the profile's root assembly file. Read layer lines from stdout and check stderr separately: an unmatched patch target appears only on stderr, so the exit code alone proves nothing. Accept both # == <layer> and # == <layer>, patched by <path>.
  2. The startup log names the interpreter and the plan. One line prints the probed capabilities and how many of the tools registered; the chosen interpreter path is logged at load. An interpreter that starts but lacks Scrapling is named in the log together with the pip line to copy.
  3. The settings card renders. The plugin detail page carries the scrapling card with every user-editable field grouped by purpose; toggling a value enables Save, and the write returns 200.
  4. A tool round-trip. Ask the model to fetch a JavaScript-heavy page: the result row renders in the chat through the official tool-call view. On a machine without a browser the render / capture_xhr tools are absent from the model's tool list and their prompt guidance is gone too — that absence is the conditional registration working, not a failure.

查看图片 What it does not provide

  • It is not a search engine. Discovery runs in the Python sidecar against one of six backends (searchBackend: auto / tavily / bing / brave / serper / duckduckgo / searxng); Scrapling's own google_search option only sets a referer header. Each backend needs its own setting (tavilyApiKey, braveApiKey, serperApiKey, searxngEndpoint) — a backend that is selected but not configured reports SEARCH_AUTH instead of silently switching to another one.
  • The npm tarball bundles no interpreter or browser. They come from your machine or from the optional CLI installer, which downloads the verified macOS arm64 set into dataDir/runtime; missing pieces remove the matching tools instead of being fetched at load time.
  • It adds no HTTP route on the host side. The settings card is a host config form and this package exposes no /api surface. The one socket that really listens is the MCP server under integrationMode: mcp, bound to 127.0.0.1 (http://127.0.0.1:<mcpPort>/mcp): any other path gets 404, a declared body over 1 MiB gets 413, a request without Content-Length gets 411, and the bearer token is read only from DSH_SCRAPLING_MCP_TOKEN — with no token set, startup prints a stderr notice that the endpoint is unauthenticated and reachable from any local process.
  • It adds no dsh subcommand. The package's own dsh-scrapling CLI manages runtime packs, plus the two halves of the MCP hookup: setup --mode native|mcp|official-mcp generates the client entry and, with --apply, writes it to <dataDir>/mcp/<mode>.patch.yml (the profile itself is never touched), while doctor --mode compares that entry against the profile's cordis.patch.yml item by item. Given --profile, setup --status reads pythonBin from that same file.

查看图片 Enabling it in dsh

  • dsh.bundle.patch points at cordis.patch.yml, whose single registration line is - id: scrapling with the bare package name — the host scans dsh.client on bare package-name entries only.
  • The client half registers two surfaces: the settings card (on the plugins.bundle.config slot, keyed by the bundle package name — the host dispatches entryKey: pkg.name, so the card renders on this package's detail page) and one result row per tool on tool.call.toolview, drawn with the official tool-call view props.
  • The settings namespace is the entry id scrapling; precedence is settings-card value > registration-line config: > schema default. Setting a field back to its default saves a clear-override, not a write of the same value, so a later default change still reaches you.
  • Gate policy and the sidecar environment are snapshotted once when the plugin is applied — settings changes take effect on the next start.
  • Disabling a tool also removes its system-prompt guidance: the guidance is re-read at each prompt assembly against the tools that scope can actually see, so a machine without a browser loses the render / capture_xhr lines and a sidecar that never came up loses all of them. The section is placed next to the built-in web_fetch guidance.
  • scrapling_crawl registers its own job kind, so job_list labels its jobs as crawls rather than shell jobs.

查看图片 The tools

Nineteen tools, behind fifteen settings switches (the seven session/state tools share switches), all exercised by the test suite:

ToolBackendNeeds a browserConcurrency
scrapling_fetchFetcher (curl_cffi)noconcurrent; write verbs are serialised once allowWriteMethods is on
scrapling_extractSelector + CSS/XPath + adaptivenoconcurrent
scrapling_locatetext / regex / similar-element lookup; returns generated selectorsnoconcurrent
scrapling_linksLinkExtractor plus next-page candidatesnoconcurrent
scrapling_renderStealthySession (patchright)yesserialised; the anti-detection switches (solveCloudflare / hideCanvas / blockWebrtc) live here
scrapling_capture_xhrone throwaway session, page-level recordingyesserialised
scrapling_session_open / scrapling_session_fetch / scrapling_session_network / scrapling_session_state_save / scrapling_session_state_load / scrapling_session_list / scrapling_session_closeScrapling session classesbrowser sessions onlyserialised
scrapling_session_readcurrent DOM of a browser session pageyesserialised
scrapling_session_actstructured actions on a session page (deployment gate)yesserialised
scrapling_downloadbyte download into dataDir/downloads (deployment gate)noconcurrent
scrapling_screenshotPNG/JPEG screenshot handed to the attachment serviceyesserialised
scrapling_crawlScrapling Spider framework (content / sitemap / feed / ) as a job
  • Quotas. At most 8 open sessions (3 of them browser sessions, each a real Chromium process), 2 crawls in flight, per-page bodies clipped by *MaxOutputChars, 8 MB per protocol frame, 16 queued frames. A ceiling answers with a specific code (SESSION_LIMIT / CRAWL_BUSY / BAD_FRAME) the model can back off from.
  • scrapling_crawl returns a job id immediately — a crawl takes minutes and a tool call has a timeout. Watch it with job_list, read it with job_output, stop it with job_kill; the tool needs a jobs registry that can attach a controller, and a profile without one simply does not register it (single-page fetching is unaffected). The crawl has a wall-clock ceiling (crawlMaxRuntimeSeconds) and never leaves the start domain. Politeness defaults follow Scrapling's spider defaults — robots.txt is not obeyed, per-domain delay is 0, up to 4 requests run in parallel — and is a deployment decision, not a per-call one (see Settings).
  • scrapling_answer fetches the page first, then hands the text plus your question to a model and returns the conclusion instead of the whole page. It follows the model the current session is already using by default (read from the session's live request header, so a mid-session model switch is picked up), which burns no separate quota; pin answerProvider / answerModel to route page-reading elsewhere. Page text is untrusted input — the prompt frames it explicitly as data, but that defence is prompt-level: it lowers the risk, it does not remove it.

Render or static

scrapling_render is much slower and heavier than scrapling_fetch, so start static. Two criteria:

  • If the rendered result is shorter than the static fetch, go back to static. Plenty of sites ship readable text from the server and then have their own script empty those containers and swap in canvas / Lottie animation — for those pages rendering can only lose content, and no wait setting brings it back. That is site behaviour, not a plugin defect.
  • waitSelector waits for an element to appear, not for content to settle. The element can be removed after it matched, so "the selector matched" does not mean the content is in the result.

One thing that is not a criterion but worth knowing: axis ticks inside SVG cannot be fetched at all — the Markdown conversion emits no <svg> / <text> nodes, so chart numbers are out of reach whichever tool you use. For actual values, go through scrapling_capture_xhr to locate the JSON endpoint.

查看图片 Replacing the built-in fetch and search

The plugin registers itself as a fetch provider and a search provider on ctx.web, so the built-in web_fetch / web_search tools actually run on Scrapling instead of dsh's plain-HTTP implementation. The model still calls web_fetch / web_search — tool names cannot be swapped (same-name registration throws), the provider seam is the supported channel. Both providers are registered by default (provideWebFetch / provideWebSearch), but that alone displaces nothing:

  1. leave provideWebFetch / provideWebSearch at their default (true);
  2. set fetchProvider / searchProvider to "scrapling" in the deployment config.

A configured id wins over auto-selection, which is why step 2 is required — and with two usable providers and no id named, ctx.web answers WEB_PROVIDER_AMBIGUOUS and the tool becomes unavailable outright. Two consequences of pointing that line at this plugin:

  • If the sidecar cannot start, the built-in web_fetch reports WEB_PROVIDER_CONFIGURED_UNAVAILABLE rather than quietly falling back to plain HTTP.
  • If you later remove the plugin but leave the line in, the built-in tools report WEB_PROVIDER_CONFIGURED_MISSING — go back and remove fetchProvider / searchProvider too.
  • The fetch provider really applies the output budget: a body over fetchMaxOutputChars is cut to a head/tail window and flagged truncated, not passed through with a flag. Failures keep the sidecar code prefix (URL_BLOCKED, SIDECAR_TIMEOUT, SIDECAR_UNAVAILABLE), so a policy refusal is distinguishable from a crash or a timeout.

Search is split in two, because Scrapling is not a search engine: discovery runs in the Python sidecar against the backend searchBackend names — Tavily, Bing HTML, Brave (API), Serper (API), DuckDuckGo HTML or SearXNG (JSON API); reading is this plugin's — the first searchRenderTopN results are actually fetched and, with a browser, rendered, so one web_search call returns both which pages matter and what they say. auto uses Tavily when tavilyApiKey is set, SearXNG when searxngEndpoint is set, and with neither configured it only goes to a public engine (Bing; DuckDuckGo when that backend is named explicitly) if searchPublicFallback is on — otherwise the provider is unavailable and web_search reports SEARCH_BACKEND_MISSING instead of querying anyone. Only Tavily and Bing have been exercised against the real endpoints here; Brave, Serper, DuckDuckGo and SearXNG are implemented to their documented request/response shapes and verified against local stubs, with real keys and real instances still unverified. Every returned URL goes through the same gate as a fetch; a page that fails to read is skipped alone, because search results routinely contain dead links.

One consequence to know: dsh-web's fetch body is a closed union of html | text with no markdown kind, and adding one is a cross-package change a plugin cannot make. The fetch provider therefore reports Scrapling's Markdown as text, which the built-in renderer displays normally. Use scrapling_fetch directly when you need html or a CSS selector.

查看图片 MCP mode

integrationMode selects one of three mutually exclusive tool surfaces. It is deployment configuration (the config: block of cordis.patch.yml), it is read once when the plugin is assembled, and changing it requires a restart.

ModeWhat it registers
native (default)the scrapling_* tools listed above
mcpno native tools; the plugin serves the same tool table over MCP
official-mcpno native tools and no server from this plugin; setup only generates the dsh-mcp-client entry for the official scrapling-mcp, which runs in its own process, with its own sessions, and without this plugin's URL guard

In mcp mode the server runs inside the plugin's own host process and calls the same sidecar, so the URL guard, sessions, jobs and adaptive storage are the same ones the native tools use — there is no second implementation to drift.

  • Endpoint http://127.0.0.1:<mcpPort>/mcp (mcpPort, default 39217). Only loopback is bound; a request whose declared body exceeds 1 MiB is refused with 413, and any other path gets 404. The server is stateless: each request gets its own protocol instance, so nothing survives across them.
  • Auth off by default. Set the environment variable DSH_SCRAPLING_MCP_TOKEN and every request must then carry Authorization: Bearer <token>. The token is only ever read from the environment, never from the profile, so it does not land in a config file.
  • Tools are the same ToolDefinition objects native registers: same JSON Schema, same argument validation, same renderers. scrapling_answer is not served — it routes through the model of the hosting session, which an external client does not have. search exists only on this surface, because an MCP client has no ctx.web seam. The gates are identical: with allowBrowserActions=false there is no session_act, with allowFileDownloads=false there is no download, and search is absent when no search backend is available.
  • Results are text content; a screenshot comes back as MCP image content instead of a host attachment. A failing tool returns isError with a readable message rather than dropping the connection.
  • Names: the MCP raw name is the tool name without the scrapling_ prefix, and dsh's MCP client shows it as mcp__scrapling__<raw>.
Native toolMCP raw name
scrapling_fetchfetch
scrapling_extractextract
scrapling_locatelocate
scrapling_linkslinks
scrapling_renderrender
scrapling_capture_xhrcapture_xhr
scrapling_session_opensession_open
scrapling_session_fetchsession_fetch
scrapling_session_networksession_network
scrapling_session_state_savesession_state_save
scrapling_session_state_loadsession_state_load
scrapling_session_listsession_list
scrapling_session_closesession_close
scrapling_crawlcrawl
scrapling_session_readsession_read
scrapling_session_actsession_act
scrapling_downloaddownload
scrapling_screenshotscreenshot
— (ctx.web only)search

Connecting dsh needs @deepseek-ai/dsh-mcp-client, which this plugin deliberately does not depend on. Pin the build that matches the current host — npm latest is older than the host's own line and the host rejects the mismatch:

dsh plugin --profile <name> add @deepseek-ai/dsh-mcp-client@0.2.2-alpha.3
# host on the 0.2.1-rc.5 line: @deepseek-ai/dsh-mcp-client@0.2.1-rc.5

Then add one entry to that profile's cordis.patch.yml — this is exactly what dsh-scrapling setup --mode mcp prints, so you can also let the CLI write it (drop headers when no token is configured):

- insert:
    - id: scrapling-mcp
      name: '@deepseek-ai/dsh-mcp-client'
      config:
        serverName: scrapling
        transport: streamable-http
        url: http://127.0.0.1:39217/mcp
        failOnStartupError: false
        # only with DSH_SCRAPLING_MCP_TOKEN set:
        # headers:
        #   Authorization: !!js '`Bearer ${process.env.DSH_SCRAPLING_MCP_TOKEN}`'

mcpPort and that URL have to say the same thing; after changing the port, re-apply the entry.

failOnStartupError is false here on purpose: the port only starts listening once the plugin has finished handshaking with the Python sidecar, and the client throws its fiber on the first failed connect (Version negotiation probe failed: connect ECONNREFUSED → the host reports 1 entry did not activate). Its own reconnect is on by default (500ms backoff, 10 attempts), so a first-attempt refusal is meant to be absorbed - setting this to true would let fiber start order decide whether the mode works. The official stdio entry keeps true: there the client spawns the server itself, so a failed connect is a real failure and should be loud.

查看图片 setup --mode and doctor

setup --mode writes that entry text; it never edits a profile. Without --apply it is a dry run, and with --apply it drops the text in <dataDir>/mcp/<mode>.patch.yml and prints the path - applying it to cordis.patch.yml stays your action, because the host offers no safe append API for that file (dsh --dump-config rewrites the profile's root config).

dsh-scrapling setup --mode mcp --port 45991 --with-auth --apply
dsh-scrapling setup --mode native --apply     # a file that deliberately contains no entry
dsh-scrapling doctor --mode mcp --profile <profileDir>

doctor reads what the profile actually says - cordis.patch.yml plus that profile's package.json - and prints one line per problem: two entries under serverName: scrapling, this plugin's entry next to an official stdio one, entries that do not match the mode, an entry whose port disagrees with --port, a profile without @deepseek-ai/dsh-mcp-client or with it pinned to something other than 0.2.2-alpha.3 / 0.2.1-rc.5 / next, and an official entry while officialMcpEnabled is off. Exit 0 is "consistent", 1 names at least one problem, 2 is a bad argument. integrationMode itself is a plugin setting the CLI cannot read, so --mode is the claim being checked and has to match the deployment.

official-mcp asks for two separate statements before it produces anything: officialMcpEnabled: true in the profile and --accept-risk on the command line; missing either one prints OFFICIAL_MCP_DISABLED / RISK_NOT_ACCEPTED and writes nothing. The entry it generates starts <dataDir>/runtime/venv/bin/scrapling-mcp, so run setup --feature mcp --apply first - see Runtime packs. When that binary is missing, setup prints a line saying so.

查看图片 Settings

Settings live on the scrapling card in the plugin detail page, grouped by purpose, each field with a one-line "what changes if you edit this". The card's language follows the host locale. Values marked below live on the card; the deployment table after them is only settable through the config: block of cordis.patch.yml.

FieldTypeDefaultPurpose
pythonBinstring""Interpreter name or absolute path. Empty walks the fixed chain in lib/runtime.ts: the managed runtime under dataDir (what setup --feature core --apply installs), then the repository _env/.venv when usable (a source checkout only), then python3 on PATH (python on Windows)
failFastOnMissingPythonbooleantrueRefuses to load when the sidecar process cannot start (wrong path, protocol mismatch, handshake timeout). An interpreter that starts but lacks Scrapling always degrades to "tools unavailable" with the path named in the log, whatever this switch says
fetchEnabled / extractEnabled / locateEnabled / linksEnabled / renderEnabled / captureXhrEnabled / networkEnabled / sessionEnabled / sessionStateEnabled / crawlEnabled / answerEnabled / sessionReadEnabled / sessionActionsEnabled / downloadEnabled / screenshotEnabledbooleantrueOne kill switch per tool or tool pair. sessionStateEnabled gates the state save / load pair, which also needs a deployment-side sessionStateDir
extractionTypeenummarkdownmarkdown / html / text; all three run the same anti-injection cleaning, and html additionally strips event-handler attributes, so it is not byte-for-byte raw HTML
mainContentOnlybooleantrueExtraction scope
*MaxOutputCharsnaturalfetch / extract / render 200000, xhr 120000Per-tool output caps. The sidecar clips by characters, the host then trims to a byte budget
crawlMaxItemsnatural

Deployment-only knobs (not on the card; set them in the config: block of cordis.patch.yml):

FieldDefaultPurpose
requestTimeoutSeconds / maxUrlLength120 / 2048Python-side per-request ceiling; URL length cap aligned with the built-in fetch
allowWriteMethodsfalseWhether the model may change state on a remote host at all — see Security
browserExecutablePath / browserCdpUrl / browserInitScript""Strong capabilities: they hand Chromium a file to execute and the browser to a debugger endpoint, or inject a JS file into every page. Deployment config only, unreachable from model input
browserRealChrome / browserDnsOverHttpsfalseUse the locally installed Google Chrome; route the browser's DNS over HTTPS (useful with a proxy to avoid DNS leaks)
recordRequests / maxRecordedRequestsfalse / 100Whether browser sessions record network history by default (the model can turn it on per session with recordNetwork), and how many entries a session keeps
sessionStateDir""Directory for Playwright-compatible login-state files. The model can only pass a plain file name, so the directory stays a deployment decision; empty disables the state save / load pair
adaptiveStoragesqliteWhere adaptive fingerprints live: sqlite (one file under dataDir) or redis (one keyspace, shareable across processes and hosts). Deployment-only - the model only ever says "save" or "relocate"
redisUrlredis://127.0.0.1:6379/0Connection string read when adaptiveStorage: redis. The password never reaches logs or error text; an unreachable server fails as ADAPTIVE_STORAGE_UNAVAILABLE
crawlMaxRuntimeSeconds1800Wall-clock ceiling of one crawl — the page cap says nothing about a site that takes a minute per page

查看图片 Proxies

Where a fetch goes out, resolved in this order — first hit wins:

  1. proxyBypass or the environment's NO_PROXY matches this host -> direct.
  2. proxyUrl on the settings card is non-empty -> use it.
  3. http_proxy / https_proxy / all_proxy in the environment (upper case works too) -> use the matching slot.
  4. The operating system's proxy settings (scutil --proxy on macOS, Internet Settings on Windows) -> use them.
  5. Neither -> direct.

What that looks like in practice — with or without a proxy, the tools just work, with no setting to fill in:

Your setupStatic fetchingRender / XHR
No proxydirectdirect
TUN mode (system-wide)direct, forwarded by the TUNsame
HTTPS_PROXY exported in the shellfollows it automaticallyfollows it automatically
macOS / Windows "system proxy" switchfollows it automaticallyfollows it automatically
Intranet targets / internal Gitexclude with proxyBypassexclude with proxyBypass
PAC (auto-config script)not followed; stderr says to set proxyUrlfollowed natively by the browser

Details worth knowing:

  • The resolved proxy is handed to the browser engine explicitly. Chromium does not read HTTP_PROXY / ALL_PROXY, so a proxy that is only exported in a shell would cover static fetches but not the browser — this package closes that gap by passing the resolved proxy on every browser fetch. A browser session still picks its proxy once, at scrapling_session_open: switching it means a new browser context, which throws away the session's cookies and login state — precisely the reason a session exists. Static sessions decide per request.
  • PAC is the one shape that needs a hand-filled proxyUrl. Evaluating a PAC script means downloading it and running JavaScript, and the dialects differ per platform, so this package does not guess; static fetching says so on stderr, and the browser follows PAC natively through its own system settings.
  • A proxy and synthetic DNS are two different problems. A fake-IP proxy resolves every domain into 198.18.0.0/15 (TUN clients often 100.64.0.0/10); neither is public unicast, so the gate would reject every URL — turning a proxy on would stop the plugin from working. The gate detects that class of resolver on its own (three control names unrelated to any target, all landing in one non-public pool while staying distinct) and then relaxes to the whole of the known pools. The control names only trigger the verdict, never size the pool — three hashed answers carry no size information. Private, loopback and link-local ranges are in neither pool, so intranet and metadata addresses stay blocked on a proxied machine. syntheticDnsRanges is the manual fallback for a self-built pool outside the two ranges; when detection does not fire, the gate stays strict.
  • Through a proxy, a host name this machine cannot resolve is no longer rejected. The proxy resolves it, and on a network with DNS filtered by region those are exactly the sites that need a proxy. IP literals, private answers the local resolver can produce, and single-label host names (intranet, router) stay rejected.
  • WebRTC leaks are closed whenever a proxy is in play. Chromium's WebRTC can send UDP around an HTTP/SOCKS proxy, and one STUN call in a page is enough to learn the visitor's real exit address; blockWebrtc is forced on with a proxy, no model input involved.
  • Credentials never reach a log or the capability frame. The report echoes scheme://host:port only, with a source field (settings / environment / system / ) — the first column to read when someone reports that fetching does not work. A malformed proxy address is logged to stderr and treated as "not configured" rather than failing the load.

查看图片 Security

  • SSRF. Scrapling itself has no SSRF protection. This package adds a URL policy on the host side — scheme allowlist, rejection of URLs with embedded credentials, length cap, rejection of an authority containing a backslash or control characters, DNS resolution with a whole-answer-set public-address check, IPv4-mapped and NAT64 addresses reclassified by their embedded IPv4 — and enforces the scheme, credential and address rules again on the Python side (the length cap stays with the host policy, because it is a deployment setting). Outbound paths accept only http/https and reject embedded credentials; ws/wss are judged by address alone in the WebSocket-handshake check. What is judged is what gets sent: the gate decides on the WHATWG-normalised form and the sidecar forwards that same string, otherwise "judged A, connected to B" would be a bypass in its own right. The host-side policy is enforced in one place (SidecarClient.call), and that one place screens every URL the model hands over as something to fetch — url, plus crawl's sitemapUrl and feedUrl — with one deliberate exclusion: the three offline parsers (scrapling_extract, scrapling_locate, scrapling_links) send their url to the sidecar as nothing but the base for resolving relative links, so lib/sidecar.ts (BASE_ONLY_METHODS) does not screen it and no request is made with it — so it covers every tool and the web_fetch provider alike, with a rejection throwing URL_BLOCKED before the request reaches the pipe; the web_search provider runs the same gate over the URLs it is about to return, so a hit the gate rejects never becomes a citeable source. The browser-side guard covers navigation, subresources, XHR and WebSocket handshakes.
  • Known gaps, stated in the open:
    • Connection pinning (an undici-level technique) is unavailable for a non-Node transport, leaving a TOCTOU window between validation and connect. Static fetches are covered by curl_cffi's follow_redirects="safe"; browser fetches by a route handler on the Python side.
    • NAT64 prefixes are not discovered dynamically. The two standardized prefixes are covered; operator-specific ones must be declared via nat64Prefixes, otherwise a private IPv4 hidden inside IPv6 is judged as an ordinary public IPv6.
    • No port allowlist (any port allowed by default), matching dsh's own built-in fetch provider.
    • The crawl re-checks every page twice before it enters the result set — once against the guard (the final URL may still have landed somewhere private) and once against the start domain, because Scrapling's is a string comparison over links found in the page, not redirect targets. The crawl session uses so curl_cffi refuses a jump into a private range.

查看图片 Data and privacy

AspectContent
On diskUnder the sidecar working directory (dataDir, default ~/.dsh-scrapling): Scrapling's adaptive fingerprint database when adaptiveStorage: sqlite (the default), scrapling_download's output in downloads/, the managed Python and Chromium installed by setup --feature … --apply (runtime/), and the MCP entry written by setup --mode … --apply (mcp/<mode>.patch.yml). With adaptiveStorage: redis the fingerprints are not written under dataDir at all — they go to the Redis at redisUrl, keyed <prefix>:<registrable-domain>:<sha256(identifier)>; that URL defaults to loopback, and pointing it at another machine hands this data to that machine. The crawl queue lands only when crawlCheckpointDir is set (the upstream Collector(crawldir=…) writes it); an empty value writes nothing. Login-state files go under sessionStateDir when it is configured. Screenshots are handed to the host attachment service, which decides where they live. Settings values live in the host settings document
NetworkFetches go to the URLs the model supplies, through the configured proxy if any. Search queries go from the Python sidecar to the single backend searchBackend selects — Tavily (api.tavily.com, key in the request body), Bing (www.bing.com, no key), Brave (api.search.brave.com, X-Subscription-Token), Serper (google.serper.dev, X-API-KEY), DuckDuckGo (html.duckduckgo.com, no key) or the SearXNG instance at searxngEndpoint. A backend receives only its own credential (tavilyApiKey / braveApiKey / serperApiKey) plus the query string; keys are never logged. Turning on searchPublicFallback lets auto send queries to a public engine with no credential at all. scrapling_answer sends the fetched page text and the question through the host llm service — that content leaves the machine when the routed model is remote
LogsThe chosen interpreter, the probed capabilities, and the proxy summary (scheme://host:port plus which rung answered — never credentials)
In memorySessions, cookies and recorded network history live in the sidecar process and disappear with it

查看图片 Known boundaries

Registered honestly, one entry per class.

  • Rendering can only lose content on pages that ship readable text and then swap it out. Sites that serve text for search engines and no-JS users, then empty those containers into canvas / Lottie animation, come back emptier after rendering — see Render or static.
  • Axis ticks inside SVG are unreachable in every tool: the Markdown conversion emits no <svg> / <text> nodes. For chart values, locate the JSON endpoint with scrapling_capture_xhr.
  • A browser session's proxy is fixed at session_open, and proxyBypass does not apply per URL inside it (a context switch would discard the cookies and login state that are the point of a session). Static sessions decide per request.
  • headers and retries on scrapling_session_fetch belong to static sessions. Upstream silently drops both for browser sessions, so this package refuses them with BAD_ARGS naming the knob to use instead — a silent no-op is the failure mode it exists to eliminate.
  • Browser identity knobs only live on the tools that can apply them. useragent / locale / timezoneId / headless are model-visible on the one-shot browser tools and on scrapling_session_open — they take effect at session construction, and passing them per request is silently dropped upstream.
  • Network history is session-level. Recording is a construction-time setting (recordRequests deployment default, or recordNetwork per scrapling_session_open); a session that is not recording answers an error, not an empty list.
  • scrapling_screenshot bypasses text cleaning. Screenshots capture whatever the page renders in the viewport — including visual secrets (tokens, private messages, data behind a login) that never appear in the DOM text the other tools clean. They are stored through the host attachment service and are only available when that service is mounted. What comes back is the service's normalized reference: a declared PNG can be persisted as image/jpeg at a smaller raster, with originalDimensions carrying what the page actually produced (host contract: saveImage in @deepseek-ai/dsh-attachment). scrapling_download writes bytes under dataDir/downloads, never overwrites an existing file, and refuses path components in filename.

查看图片 FAQ

QuestionAnswer
The model says the render / capture_xhr tools do not existNo browser engine on this machine — the plugin registers only what works. Run python -m patchright install chromium and restart. If every tool is missing, the sidecar did not start: the startup log names the chosen interpreter, and a missing Scrapling carries the pip line to copy
After enabling a proxy, every fetch is blockedA fake-IP proxy resolves every domain into a non-public range; the gate auto-detects that class and relaxes to the known pools. A self-built pool outside them goes into syntheticDnsRanges. Private and loopback addresses stay blocked on purpose
web_fetch reports WEB_PROVIDER_CONFIGURED_UNAVAILABLE / _MISSINGThe deployment line points at this plugin: either the sidecar is down (interpreter or Scrapling — see the startup log), or the plugin was removed and the fetchProvider / searchProvider line is still there. Remove the line too
The model cannot POST a formallowWriteMethods is off by default and deployment-only — it decides whether the model may change state on a remote host
web_search reports SEARCH_AUTHThe selected backend has no credential: Tavily/Brave/Serper need their key, SearXNG needs searxngEndpoint. Set it, or switch searchBackend. With searchBackend=auto and no key and no searxngEndpoint, the plugin reports SEARCH_BACKEND_MISSING — set a key / endpoint, or turn on searchPublicFallback to allow public engines
The numbers on a chart are missingSVG tick labels do not survive the Markdown conversion in any tool. Use scrapling_capture_xhr to find the JSON endpoint behind the chart

查看图片 Development

pnpm install
pnpm run check     # typecheck → lint → client build → host build → tests → format, then the Python chain
pnpm run test:py   # end-to-end check of the Python sidecar
  • The Python half is gated like the TypeScript half. check ends with check:py: an interpreter report, a face count (the shipped .py files and the test directory must both be there — a missing test directory makes "clean" and "untested" indistinguishable), a dependency consistency check, ruff check, ruff format, mypy, the sidecar end-to-end, bandit (all severities; # nosec only for justified false positives), and a coverage floor measured only over the modules that ship. Dev tools: pip install -r py/requirements-dev.txt into the interpreter test:py uses.
  • The Python 3.14 floor is one fact in four places: both READMEs, the ruff and mypy targets, and a runtime gate in py/bridge.py that exits before any Scrapling import with a clean stdout — stdout is protocol frames, so an old interpreter shows the exit code and a stderr line instead of garbage.
  • mypy checks untyped bodies and rejects unannotated defs and unused ignores. Playwright's page / route / websocket objects have no shape until runtime, so ignore_missing_imports and ANN401 stay off; those three checks are on, not the full strict profile.
  • Sources are TypeScript. host.js and client.js are build artifacts and deliberately .js: Node refuses to strip types for packages inside node_modules, which is exactly where dsh loads the plugin from. The artifact is ESM-only; require() will not get you the plugin, and that is deliberate.
  • Gates that pin the published shape: test/publish-manifest.test.ts (entry targets covered by files, no source file as an entry point, dsh-* pins exact and uniform, no host package body inlined, no dates or bad bytes in comments or documents) and test/readme-parity.test.ts (the two READMEs name every tool and every settings-card knob, and their section structures match).
  • Publishing runs through .github/workflows/publish.yml (manual, OIDC trusted publishing plus --provenance).

查看图片 License

MIT. Source at github.com/JayYuen666/dsh-scrapling. Issues go to that repository's tracker.

This package ships no third-party source code — Scrapling and the browser engines are runtime dependencies. The third-party content in the package is the icon (Tabler Icons) and the zod code inlined into the single-file CLI bundle; the host-side client bundle keeps zod external. THIRD_PARTY_NOTICES.md records that boundary.

  • The settings card can start the same install/repair/cancel actions through the scraplingRuntime remote when the profile has both a remote surface and a jobs backend; otherwise it prints the CLI command.
  • integrationMode is deployment-only; setup --mode prints or writes the client entry for the two MCP shapes and doctor checks a profile against it — see MCP mode. official-mcp starts scrapling-mcp from the managed venv, which the mcp pack provisions (setup --feature mcp --apply).
  • Only macOS arm64 is verified. Windows, Linux and macOS x64 are blocked (B1/B2): every pack reports blocked and the installer refuses rather than guessing an asset path.
  • shopify
    no
    serialised
    scrapling_answerany fetch path plus ctx.llmwith render: trueserialised
    1000
    Ceiling over the model-supplied maxPages. An out-of-range maxPages is passed through so the sidecar reports the parameter error rather than this plugin silently clamping it
    stripInlineImagesbooleantrueDrops inline data: URI images from the body, keeping only alt text — pure token noise for the model. Code fences are left untouched; pages without inline images come back byte-identical. Stripping happens before the cap. Turn off only when the raw base64 is the data (colours, fingerprints)
    headless / networkIdlebooleantrue / falseBrowser mode and whether a page must reach network idle before it is read
    blockAdsbooleantrueStacks on top of the SSRF guard rather than competing with it: the guard is registered last (so the engine runs it first) and yields with route.fallback()
    pierceShadowbooleanfalseMerges open shadow roots into the body — the switch for pages that hide their text in shadow DOM. The model can also override it per call on the browser tools
    waitSelectorStateenumattachedThe state a waitSelector element must reach (attached / detached / hidden / visible)
    sidecarHandshakeTimeoutMs / fetchTimeoutMs / sidecarGraceMsnatural30000 / 120000 / 5000Handshake budget, per-call cooperative timeout (declared on the tools' timeoutMs), and how long the client waits for the process to exit before forcing it down. scrapling_crawl passes no timeout — a crawl is measured in minutes
    dataDirstring""Sidecar working directory (the adaptive fingerprint database lands here); empty = ~/.dsh-scrapling. If it cannot be created, failFastOnMissingPython decides whether the plugin refuses to load or degrades to no tools
    provideWebFetch / provideWebSearchbooleantrueWhether the ctx.web providers are registered at all — see Replacing the built-in fetch and search
    searchBackend / tavilyApiKeyenum / secretauto / ""Discovery backend (auto / tavily / bing / brave / serper / duckduckgo / searxng) and the Tavily key. auto uses Tavily when a key is set, SearXNG when searxngEndpoint is set, and only falls back to public engines when searchPublicFallback is on (otherwise it reports SEARCH_BACKEND_MISSING); a backend selected without its own key (or instance) reports SEARCH_AUTH
    searchPublicFallbackbooleanfalseLets auto fall back to the public Bing engine when neither a key nor SearXNG is configured — py/search.py resolves auto in the order tavily → searxng → bing. DuckDuckGo is not reachable through auto at all; name searchBackend=duckduckgo to use it. Off by default on purpose: turning it on sends query strings to a third-party public engine
    braveApiKey / serperApiKeysecret""Keys for the Brave Search and Serper.dev backends
    searxngEndpointstring""Base URL of a SearXNG instance (…/search?format=json is appended); empty means the backend is unusable and reports SEARCH_AUTH
    answerProvider / answerModel / answerMaxTokensstring / natural"" / "" / 2000The model scrapling_answer routes to and the per-reply token cap. Provider and model left empty mean "follow the model the current session is already using"
    allowedHostsstring""Extra host names that skip the public-address check (comma-separated). Pointing it at an intranet address means giving up SSRF protection for those targets on purpose
    syntheticDnsRangesstring""Manual fallback for synthetic DNS: a comma-separated CIDR list of your proxy's fake-IP pool, for resolvers that mix synthetic and real answers. Invalid entries are dropped — a broken fallback degrades to "stay strict", not to a crash
    proxyUrlstring""Where fetches go out (http://… / socks5://…, optionally user:pass@). Empty = follow the environment; see Proxies. Masked on the card, never enters a tool schema
    proxyBypassstring""Host names that connect directly, mirroring NO_PROXY; * means the whole machine goes direct
    nat64Prefixesstring""Extra NAT64 prefixes (comma-separated <IPv6 prefix>/<32|40|48|56|64|96>) for operator-specific networks. A declared prefix is consulted for every IPv6 answer and applies to the addresses inside it. A malformed entry makes the plugin refuse to load (CONFIG_INVALID), unlike syntheticDnsRanges below: dropping a prefix would let a private IPv4 embedded in IPv6 through as ordinary public IPv6
    crawlRespectRobotsfalseObey robots.txt; with it on, a domain whose robots.txt fetch fails (network error or HTTP 5xx) is blocked rather than assumed permitted, while a missing robots.txt (HTTP 404, the common case) is treated as allowing everything
    crawlDownloadDelayMs / crawlConcurrentRequests / crawlAutothrottle / crawlStrategy0 / 4 / false / bfsGap between requests, parallelism, adaptive throttling, breadth- or depth-first order
    crawlCheckpointDir""Persist the crawl queue so a restarted crawl resumes instead of going back to the start URL
    tavilyApiEndpoint / bingEndpoint / searchRenderTopNhttps://api.tavily.com/search / https://www.bing.com/search / 3Discovery endpoints (overridable for tests or proxies), and how many results are actually fetched and rendered (0 = link list only)
    duckduckgoEndpointhttps://html.duckduckgo.com/html/DuckDuckGo HTML endpoint; override for a mirror or a test stub
    allowBrowserActions / allowFileDownloads / downloadMaxBytesfalse / false / 10485760Deployment-only gates for structured actions and downloads, plus the per-download byte cap. Both gates default off and also hide their tools from the model
    none
    allow_domains
    follow_redirects="safe"
  • WebSocket coverage depends on the Playwright version. The guard intercepts handshakes via route_web_socket; older builds lack that method, WebSocket is then outside the guard, and the plugin reports that as websocketGuard in the handshake capabilities (the startup log prints the probed capabilities) rather than degrading silently.
  • No code execution from model input. page_action, page_setup, selector_config, proxy, executablePath and cdpUrl never appear in any tool schema, and the browser-side network guard is installed by plugin code. The strong browser capabilities and allowWriteMethods come only from deployment config.
  • Write verbs are off by default. With allowWriteMethods off, method and body are absent from scrapling_fetch's parameter surface, and the sidecar independently refuses a non-GET with WRITE_DISABLED — two halves that do not rely on each other. The gate, the proxy decision and follow_redirects="safe" bound where a request goes, not what it does once there; prompt-injected page text can ask for a POST, so turn it on only where the task really is to submit data. headers and cookies stay available either way — anything handed to them lands in the model context by design.
  • Prompt-injection scrubbing runs unconditionally on all three extraction paths; there is no model-facing switch to turn it off.
  • TelemetryNone
  • scrapling_extract, scrapling_locate and scrapling_links are offline parsers. All three take HTML from the caller and never fetch; scrapling_links keeps relative links only when baseUrl is supplied — with an empty baseUrl it returns no links rather than guessing absolute URLs. All three share extractMaxOutputChars.
  • Adaptive relocation is best-effort. Scrapling saves only the first matching element, and a near-identical sibling can be chosen when the original attributes change. Treat a relocated match as a hint and verify it; scrapling_locate never saves fingerprints.
  • Runtime packs are verified on macOS arm64 only. Windows, Linux and macOS x64 report every pack as blocked; the installer refuses rather than guessing an asset path (B1/B2).
  • The adaptive-redis pack is a client, not a storage engine. Upstream ships only SQLiteStorageSystem; the Redis backend here is this plugin's py/redis_storage.py, a StorageSystemMixin subclass keyed by registered domain. Enabling it changes where fingerprints live, so treat it as a deployment decision, not a per-call switch. The plugin's own MCP server is separate — it runs in-process and needs no pack.
  • Shopify crawl mode walks the store's JSON API. mode=shopify uses the store's /collections.json and each collection's /products.json (upstream's ShopifySpider) and yields one row per variant — name (product + variant, minus Default Title), price, sku, stock (null when available, 0 when not), identifier, brand, category, image_url, url, barcode, old_price, description. It never scrapes product HTML, and it obeys the same URL gate, robots setting, delay and concurrency knobs as the other crawl modes. Verified against a local fixture store; a real Shopify store has not been exercised (B11).
  • official-mcp generates configuration only. integrationMode=official-mcp registers no native tool and starts no server from this plugin; setup --mode official-mcp --accept-risk produces the dsh-mcp-client entry for the official server, whose binary comes from the mcp pack (B10 is the risk decision that entry stands on).