# @broberg/web-clipper 中文文档

规范地址：https://npmcn.com/package/%40broberg/web-clipper/

版本：0\.27\.1
npm 元数据更新时间：2026-10-24T21:52:27.185Z
中文译文更新时间：2026-11-08T16:01:30.470Z
软件包许可：MIT

本文由上游 README 翻译，可能存在偏差，请结合原文核对 API、命令与配置。版权与许可归原作者所有。

原始 npm 页面：https://www.npmjs.com/package/%40broberg/web-clipper
原文对照：https://npmcn.com/package/%40broberg/web-clipper/?lang=original
项目主页：https://github.com/broberg-ai/components#readme
源码仓库：https://github.com/broberg-ai/components

## 关键信息（来自 npm 元数据）

- 包体积（解压后）：1.2 MB，77 个文件
- 模块格式：ESM + CJS
- TypeScript 类型：包内自带
- 运行依赖：3 个
- peer 依赖：vite \>=5，preact \>=10\.0\.0，@broberg/theme \>=0\.13\.0，@crxjs/vite-plugin \>=2\.0\.0-beta\.28，@preact/preset-vite \>=2\.8\.0
- 首次发布：2026-10-12T19:47:21.413Z
- 最新版本发布：2026-10-24T21:52:26.774Z
- 近 30 天下载量：4191（2026-10-09 至 2026-11-07，npm 官方下载统计）
- 已发布版本数：45（近 12 个月 45 个）

## 中文简介

分舰队的一个网络剪接基站(Trail Web Clipper, Jam Clipper):/核心指纹+链条+分支引擎,/提取器插入点+ 特雷尔的文章提取器 / 运输目的地 + 发送者(仅新单元,同位素重传, 401/5xx 规则,

## 安装

```sh
npm install @broberg/web-clipper
```

也可以使用：`pnpm add @broberg/web-clipper`、`yarn add @broberg/web-clipper`、`bun add @broberg/web-clipper`

## README

# @broberg/网络剪辑器

舰队的一个网络剪接基地——Trail Web Clipper和Jam Clipper在此基础上更上一层楼(pic F099,克里斯蒂安的建造命令12/10 2026). 没有叉子:改进到上游.

## `@broberg/web-clipper/core` 指纹引擎(F099.2)

```ts
import { chain, diff, idempotencyKey, verify } from "@broberg/web-clipper/core";

const units = [{ kind: "turn", role: "user", content: "Hej", position: 0 }, /* … */];
const d = await diff(knownChainFps, units);   // what the destination already has, in order ([] for new)
// d.kind: "same" (nothing to send) | "append" (send d.units) | "branch" (an earlier unit changed:
//         a new branch from d.forkAt with parent d.parentChainFp — the old line is never overwritten)
for (const link of d.kind === "same" ? [] : d.links) send(unit, { idempotencyKey: idempotencyKey(link) });
```

-   A 类 **单位** 是一个页面,或者一个聊天回合。 其指纹为sha256 over(实物,角色,位置,内容).
-   这个 **链式** 一个单位的指纹包括上一个,所以任何更早的地方都会改变它之后的一切.
-   **只发送新**, (中文(简体) ), **不重复重发** (一能键是链纹指纹), **分支** 当一个提示被编辑或重新生成时 `verify()` 找到一个被篡改的单位.
-   每次替换整个页面的目的地( Trail: 每个 URL 一个来源) 只需通过 `[]` 并发送一切——这是不附带附件的目的地的默认.

## `@broberg/web-clipper/extract` ——取出插头点(F099.3)

```ts
import { extractClip, type Extractor } from "@broberg/web-clipper/extract";

const chatgpt: Extractor = { id: "chatgpt", matches: (u) => u.hostname === "chatgpt.com", extract: async (doc, url) => ({ /* Clip */ }) };
const clip = await extractClip(document, new URL(location.href), [chatgpt]);  // yours first, the article extractor otherwise
```

-   `articleExtractor` 有轨电车的取出器(可读性+倒数,相同的选项和规则)解除不变;有一次测试显示它与同页的有轨电车自己的文件(f5ce8ba)的字节相同输出.
-   您的提取器按您通过顺序进行测试; 第一场比赛获胜. 无任何匹配 = 文章提取器 .
-   **永远是决定性的:** 同样的 DOM 必须给出字节- 相同的标记下, 或者重新装入看起来像一个编辑, 指纹引擎打开一个假分支。 规则也约束了你的提取器.
-   一个没有要剪掉的页面 `NothingToClipError` - 从不空出.

## `@broberg/web-clipper/transport` 目的地和发送者(F099.4)

```ts
import { sendClip, DestinationError, type Destination } from "@broberg/web-clipper/transport";

const trail: Destination = {
  id: "trail", label: "Trail",
  targets: async (s) => /* tenant × brain */ [],
  async send(units, { target, session, clip }) {
    const res = await fetch(url, { method: "POST", headers: { authorization: `Bearer ${session.accessToken}` }, body });
    if (!res.ok) throw new DestinationError(res.status);   // report the HTTP answer — the base decides what it means
  },
};
const r = await sendClip({ destination: trail, target, clip, auth });
// r.state: "sent" | "login_required" («log ind igen») | "consent_required" | "refused" (4xx) | "unavailable" (5xx/network, retries spent)
```

-   **只有新单位** 用于 `appendOnly` 目标( 答案) `lastSeen(anchor)` 与链状指纹; 所有其他目的地每次都得到整个剪辑.
-   **同上,propoency key = 链式指纹.** 电池`batchSize`,默认为50)是逐一执行的,所以中断的运行恢复到停止的地方:没有两次,没有丢失.
-   **401 (中文(简体) )** + 一个强制会话刷新并重发;没有会话或再次401 `login_required`。 。 。 . **5xx/ 网络** ——退后(500毫秒,翻倍,4次尝试) —— `unavailable`永远不要迷惑. **其他 4xx (单位:千美元)** → `refused`,没有重考.
-   目的地为 `requiresConsent` 一无所获直到 `consented: true` 为那个锚.

## `@broberg/web-clipper/auth` —— BrobergID 从扩展名登录(F099.5)

```ts
import { createBidAuth } from "@broberg/web-clipper/auth";

const auth = createBidAuth({
  issuer: "https://id.broberg.ai",
  clientId: "trail-clipper",                 // this extension's PUBLIC BID client — no secret anywhere
  audience: "https://app.trailmem.com",      // optional: session() then returns a user TICKET for that receiver
  ticketScope: "trail:clip",
});
await auth.login();                          // chrome.identity.launchWebAuthFlow, PKCE (S256) + state
const r = await sendClip({ destination, target, clip, auth });   // auth IS the sender's session source
```

-   **运行背景中的登录( 0. 4. 0) .** Chrome在失去焦点时会关闭弹出器,而登录窗口则会关闭焦点——一个从弹出器中开始的登录在信使被存储之前就已死亡(BrobergID表示是,按钮仍然表示"Log ind";在Trail 12/10中被测量. 所以说:
    
    ```ts
    // background.ts
    import { createBidAuth, serveLogin } from "@broberg/web-clipper/auth";
    serveLogin(createBidAuth(bidOptions));
    // popup.tsx — same bidOptions
    import { createBidAuth, popupAuth } from "@broberg/web-clipper/auth";
    <ClipperPopup auth={popupAuth(createBidAuth(bidOptions))} … />
    ```
    
    弹出会问,背景日志输入并存储;关闭弹出会中日志一无所获,下一弹出会发现会话.
-   一个被拒绝的代码交换表示HTTP状态和BrombergID的答案的简短摘录(代码和令牌被擦除),例如. `BID refused the code (HTTP 400): http_400 redirect_uri mismatch…`。 。 。 .
-   需要一个 `identity` 财务报告和审定财务报表 `storage` 权限。 托肯斯住在这里 `chrome.storage.local`,由客户端 id 键入——两个扩展从不共享登录.
-   `session()` 到期前续约. **拒绝刷新** (用户的 BID 登录结束) `null` 并清除登录 ~ 显示“ log ind igen ” . **BID 未响应** → `BidUnavailableError`,登录保存,发送者报告 `unavailable`,不是登录.
-   与 `audience`: BID 令牌被交换为5分钟的用户票(BID F087.37),在有效时重新使用,当发送者在401后强制刷新时重新取回. `session_ended` → `null`; 设置拒绝(`no_rule_for_audience`, (中文(简体) ), `scope_not_allowed`页:1 `BidTicketRefusedError` (发件人报告) `refused`) (中文(简体) ).
-   重定向是 `chrome.identity.getRedirectURL()` =: `https://<extension-id>.chromiumapp.org/` —— BID 与字符匹配,所以扩展需要一个 FIXED ID(明显) `key`) (中文(简体) ).

## 绘制您的 OWN 弹出 : `/engine` (0.7.0, F099.12) (中文(简体) )

**设计属于使用此软件包的扩展名,而非软件包** (所有者,13/10). `/engine` 是弹出的所有 DOS —— 登录、加载和记住目标、剪辑、每次谈话的同意、发送、报告—— 没有元素、 CSS 和文本:

```ts
import { createClipper } from "@broberg/web-clipper/engine";   // framework-free
import { useClipper } from "@broberg/web-clipper/ui";           // the same, as a Preact hook

const [s, clipper] = useClipper({ auth: popupAuth(createBidAuth(opts)), destination, getClip: () => clipActiveTab(CONTENT_SCRIPT_PATH), storage: extensionStorage() });

s.phase.kind          // "checking" | "logged_out" (reason "login_again" | "login_failed", message) | "logging_in" | "ready"
s.targets, s.target   // where it can go; the chosen one (remembered per destination)
s.run                 // { kind: "idle" | "clipping" } | { kind: "done", result, clip } | { kind: "error", message }
                      //   result.sent = units sent · clip.completeness = { complete, reason } — show it when complete is false
s.consentFor          // a conversation that needs «record this conversation» → clipper.clip({ consent: true })
s.notAConversation    // consent needed but the page is no conversation — say so
clipper.login() · clipper.logout() · clipper.choose(id) · clipper.clip({ fields })
```

你的短信 你的布局 你的身高. `ClipperPopup` (下)是一个现成的绘制方法——可选的,并建在这个相同的引擎上,所以它的行为与你的完全一样.

## 扩展名 : `/extension`, (中文(简体) ), `/content`, (中文(简体) ), `/vite`, (中文(简体) ), `/ui` (单位:千美元)

```ts
// vite.config.ts — a whole clipper from the shared build
import { defineConfig } from "vite";
import { defineClipperManifest } from "@broberg/web-clipper/extension";
import { clipperViteConfig } from "@broberg/web-clipper/vite";
export default defineConfig(clipperViteConfig({
  manifest: defineClipperManifest({ name: "Trail Web Clipper", version: "0.3.0", apiHosts: ["https://app.trailmem.com/*"], bidIssuer: "https://id.broberg.ai", geckoId: "trail-clipper@broberg.ai" }),
}));

// src/content.ts — import ONLY /content and your extractors here
import { runContentScript } from "@broberg/web-clipper/content";
runContentScript([myChatReader]);

// src/popup.tsx
import "@broberg/theme/css/palettes.css";
import "@broberg/web-clipper/css/clipper.css";
import { createBidAuth, popupAuth, extensionStorage } from "@broberg/web-clipper/auth";
import { clipActiveTab, CONTENT_SCRIPT_PATH } from "@broberg/web-clipper/extension";
import { ClipperPopup } from "@broberg/web-clipper/ui";
render(<ClipperPopup title="Trail" auth={popupAuth(createBidAuth({ issuer, clientId: "trail-clipper", audience, ticketScope }))}
  destination={trail} getClip={() => clipActiveTab(CONTENT_SCRIPT_PATH)} storage={extensionStorage()} />, root);
```

-   **权限 :** `activeTab`, (中文(简体) ), `scripting`, (中文(简体) ), `identity`, (中文(简体) ), `storage`、 `apiHosts` (仅后端)和 BrobergID 从 `bidIssuer` (需要0. 3. 0) — 它的符号端点不会发出 CORS 页眉, 所以没有它登录失败 。 没有页面主机:内容脚本被点击后注入,从未声明.
-   **内容脚本必须独立.** 它作为经典剧本运行,所以不能 `import`; 如果与弹出共享模块, 构建失败( 命名共享块) .
-   **你自己的田园** (0.2.0,F099.8): (中文(简体) ) `ClipperPopup({ fields: [{ id: "tags", label: "Emneord", placeholder: "kommasepareret" }] })` - 每个字段的文本输入(`clipper-field-<id>`); 数值到达您 `send()` 作为 `ctx.fields` (单位:千美元)`{ tags: "…" }`) (中文(简体) ). 从未存储 : 新弹出开始为空 。 无 `fields`, (中文(简体) ), `ctx.fields` 缺席.
-   **聊天阅读器(0.5.0,F099.9):** `runContentScript(chatReaders)` (从) `/extract`剪辑 ChatGPT(`chatgpt.com/c/…`和克劳德(E`claude.ai/chat/…`)作为每转弯一个单位,主播=对话id. 他们从页面自己的数据(作为用户签名;屏幕上的线程是: `clip.units`,没有隐藏工具/搜索消息;其他分支进入 `clip.branches` 自0.1.0起),因为屏幕只持有~8个长线条的信息,被保存的页面没有. 如果数据无法读取, 他们会剪辑屏幕上的内容并设定 `clip.completeness = { complete: false, source: "dom", reason }`;只有全文 `complete: true`共享的 CHATGPT 链接`chatgpt.com/share/…`)在页面本身中携带整个对话(不需要登录),从那里读取完整(在真实共享链接上测量的0.5.1:30转,屏幕显示13;克洛德共享链接仍然从屏幕上读取并被标记为不完整. **克洛德的文物作为档案(0.6.0,F099.10):** 每一件艺术品,一转即产生或改变(claude. ai 的文物工具和文件工具 `create_file` 编号 : `str_replace`是一个 `unit.assets[]` 条目 `{ type: "file", id, name, mime, language?, content, version }` ——该回合的文件AS(对上一个版本适用了修改). 转动的文字得到 `[Artefakt: <title>]`所以只有一件文物的转弯 已经不再掉下去. `Asset.url` 现在可选。 所附人员的文件也保留(0.6.1):仅作为附件的电文保持自己的转弯(`[Bilag: name]`,将提取的文本作为文件)和上传的图像命名(`[Fil: name]`) (中文(简体) ). 读者不明白的信息部分或无法应用的更改使剪辑 `complete: false` 出于这个原因——它从未被默默地跳过。 每个剪辑 REPLACES 内容脚本的前一个收听器(0.5.1),所以重新加载的扩展在已经打开的分页中工作而不重新加载. **仅限附件的目的地应保留不完整的聊天片段 :** 它的单位是从 0 开始的可见的 TAIL 编号,所以它们的位置与整个对话不匹配,链会读作一个新的分支. 完整的剪辑总是对全线有0.n-1的位置,同线在每一个读取上都给出了字节-等同的单位. 将阅读器建在 `htmlToMarkdown(html)` (从 `/extract`)——与物品提取器相同的转换.
-   **无声跳跃(0.7.1,F099.13):** a Claude 工具呼叫读者不知道 – 一个编写文件或制作图像的代码工具 – 制作剪辑 `complete: false` 名称工具。 只有读取或搜索的工具(`web_search`, (中文(简体) ), `web_fetch`, (中文(简体) ), `view`被跳过。 代码工具本身的文件来自0.8.0. **缺少的(0.7.2):** 一个不完整的片段说 `completeness.missing`数字 : `"files"` = 每个转弯被全读,只有文件缺失——保持转弯,显示文件警告; `"turns"` =旋转本身不全(从屏幕上读取,或可能持有单词的部分不被理解).
-   **档案本身(0.8.0,F099.13):** 文件中包含对话的 SHOWS 但不携带, 由内容脚本与页面自身的会话获取( 您的服务器从未登录到 claude.ai / chitchgpt.com ) , 并且以 `unit.assets[]`数字 : `{ type: "file", id, name, mime, size, content }` 文本作为文本,其他任何内容(PNG、ZIP、MP4) `encoding: "base64"`. Claude: Artifacts 面板中的每个文件(什么 `present_files` 移交; 一次以上提交的文件一次取出, 最后一次取出) 并上传图像 。 ChatGPT: 生成图像并上传(`image_asset_pointer`)和代码工具文件(`sandbox:/mnt/data/…` 链接)。 转动每个文件的文本名( T)`[Fil: …]`, (中文(简体) ), `[Billede: …]`。限制:每个文件25 MB,每个剪辑60 MB。 无法获取或超过限制的文件在 `completeness.reason` 与 `missing: "files"` - 转弯还是完整的 尚未: ChatGPT 画布, 共享链接上的文件( 名称 :, `missing: "files"`) (中文(简体) ). **0.8.1: (中文(简体) )** ChatGPT文件现在下载(下载地址是ChatGPT自己的,需要登录——0.8.0得到403);生成的图像由自己的标题命名;克洛德从什么取出完整的文件列表 `present_files` 要求交出(0.8.0只读结果,名字较少); `completeness.problems[]` 列出每个问题和 `reason` 全部名字; 引用弹出语为 “ Alle ture er gemt —— N file mangler ” (中性, `clipper-incomplete-files`当只缺少文件时. **0.8.2 — DATA 缺少文件:** `completeness.missingFiles: { name, why, status?, tool? }[]` 每一个文件 片段没有携带, 从来没有第一批. `why`数字 : `no_url` • 妇女 `http` (+ 数)`status`· `network` • 妇女 `no_download_url` • 妇女 `too_large` • 妇女 `clip_limit` • 妇女 `unknown_tool` (+ 数)`tool`· `unapplied`。绘制您网站想要的信息; `reason` 保留为纯文本. **0.8.3 (中文(简体) )** 拒绝的批次报告 `sent` (在它之前到达了多少个单位);文件每段可达100 MB / 300 MB (配置 : `maxFileBytes`, (中文(简体) ), `maxClipFileBytes` 在阅读器上)——一个大于一个扩展消息的文件以部分方式行走,由 `clipActiveTab`克劳德:a `current_leaf` 如果剪辑线上有NEWER的谈话版本 剪辑线上写着 `missing: "turns"`。 。 。 .
-   **双子座(0.12.0,F099.22):** `chatReaders` 现在包括 `geminiReader` (单位:千美元)`gemini.google.com/app/<id>`,还有 `/u/<n>/app/<id>` (原始内容存档于2018-10-25). Gems. 每个转弯1个单元(用户行;答案为markdown),主播=对话id,屏幕阅读器标签被忽略. 它读取了SCREEN——双子座通过Google内部的RPC装入了对话,但尚未被测量——所以片段说: `complete: false, source: "dom", missing: "turns"`:长篇对话只保留了自己的一部分在屏幕上. 和屏幕读取的一样.
-   **复杂度(0.13.0,F099.22):** `chatReaders` 现在包括 `perplexityReader` (单位:千美元)`perplexity.ai/search/<slug>`) (中文(简体) ). 每个回合一个单位——问题来自 `[data-message-bubble]`,答案为: `[data-renderer]` ——锚 = 线粒体. 就像双子座一样 它现在读了SCREEN`complete: false, source: "dom", missing: "turns"`:线程数据调用已知(GET /rest/thread/),持有接应文本的块尚未被测量.
-   **微软365副驾驶(0.14.0,F099.22):** `chatReaders` 现在包括 `copilotReader` (单位:千美元)`copilot.cloud.microsoft/chat/conversation/<id>`) (中文(简体) ). 每转弯一个单位 `.fai-UserMessage` 编号 : `.fai-CopilotMessage__content` (回答为下划线);可访问标题、名称和工具栏被删除。 斯莱恩读到, `complete: false, source: "dom", missing: "turns"` ——副驾驶的列表是虚拟化的(只有靠近取景口的转弯存在),其数据调用没有被看到.
-   **嵌入式登录(0.15.0,F099.24):** `auth.embedded()` 在从 `createBidAuth` (和 `popupAuth` 通过)——对BID的F084.166合同v1:没有窗口,一个6位数的代码通过邮件登录INSIDE弹出后,如果账户有1个因素(如果有1个)`totp` 应用程序代码,或 `sms` 之后 `sendSms()`) (中文(简体) ). 无头状态机器—— `state()`, (中文(简体) ), `subscribe()`, (中文(简体) ), `resume()`, (中文(简体) ), `start(email)`, (中文(简体) ), `verify(code)`, (中文(简体) ), `sendSms()`, (中文(简体) ), `verifySecondFactor(method, code)`, (中文(简体) ), `resend()`, (中文(简体) ), `restart()` 与 `step: "email" | "code" | "second_factor" | "signed_in"`, (中文(简体) ), `busy`,以及 `error` 作为数据(`invalid_code {attemptsLeft}`, (中文(简体) ), `expired`, (中文(简体) ), `locked {retryAfter}`, (中文(简体) ), `too_many_requests {retryAfter}`, (中文(简体) ), `not_embedded`, (中文(简体) ), `invalid_request`, (中文(简体) ), `unavailable`) (中文(简体) ). 你画出屏幕。 结束于存储的 SME 会话 `login()` (授权代码在 /oauth2/token 上与 PKCE 验证器和您的第一个注册方向交换). 当Chrome关闭弹出时 当用户读取他们的邮件时 呼叫 `resume()` 然后他们又回到了代码阶下. `expired` 财务报告和审定财务报表 `locked` 结束流量( 回转至) `email`(三); `not_embedded` 表示不允许客户端——退到 `login()`。扩展名中从未处理过密码。 尚未活出:BID在所有者批准其模型后会建立终点.
-   **密码登录(0.17.0,F099.25):** `auth.embedded().passkeyStart()` ——面对BID的F084.166合同v2,没有邮件代码的外形标识/指纹. 国家去 `step: "passkey"` 与 `frameUrl`: 显示 `<iframe src={frameUrl} allow="publickey-credentials-get">` (~360x64) — BID的帧绘制出按钮,因为水龙头必须发生在它内部. 引擎会自己聆听帧的答案(只从发行商的出处,只从它的出处) `flow_id`,仅限 `type: "bid-embedded-passkey"`),完成流量并交换与邮件代码完全一样的代码 `signed_in`,同一届会议. `cancelled` 使步骤没有出错(框的按钮再次准备好); `not_found` 编号 : `failed` 设定 `error: {kind: "passkey", reason}` 在台阶上,和 `start(email)` 仍然在那里工作(“Andre muligheder”下的邮件编码)。 A 类 `frame_url` bID的原产地被拒绝。 流出于封闭的弹出: `resume()` 带回阶下再听. 过 `messages` 改为 `createBidAuth` 只有框架张贴 某处弹出自己的窗口. `EmbeddedLoginForm` 不绘制此步骤, 您的屏幕会显示 .
-   **从数据(0.25.0,F099.22):** 双子座读取器现在按照页面的方式加载对话—— `POST /_/BardChatUi/data/batchexecute?rpcids=hNvQHb` 含有页本身的会话值(`SNlM0e`编号 :`cfb2h`编号 :`FdrFJe` ——每转弯读取一个单位,最古老的第一. 答案是用主人的谈话(20/10)来衡量的;请求体没有被捕获,因此它跟随公开的 `f.req` 格式——如果Google拒绝,屏幕阅读器仍然会被剪辑,标记 `source:"dom"` 与缘由. 每通电话有100个转弯;填补它的对话名称可能更长. `createGeminiReader({ fetch? })`。 。 。 .
-   **从数据(0.24.0,F099.22):** 深渊阅读器现在读到整个谈话 `GET /api/v0/chat/history_messages?chat_session_id=<id>` 用DeepSeek自己的无记名符号(`localStorage.userToken`, (中文(简体) ), `{value}` JSON)——以业主谈话20/10测量出形状. 当前行从 `chat_session.current_message_id` 上楼 `parent_id` 树;其他的叶子(一个被编辑的问题,一个再生成的答案)作为分枝而来. 文本从 `REQUEST`编号 :`RESPONSE` 片段; 思维片段被忽略,任何其他片段类型都是完整的。 仅 `chat_session_id` 发送——页面的缓存参数使DeepSeek回答一个空列表。 没有签名或拒绝回答 = 屏幕阅读器,标记 `source:"dom"` 与缘由. `createDeepSeekReader({ fetch?, token? })`。 。 。 .
-   **数据中的复杂度(0.23.0,F099.22):** 困惑读取器现在读取整个线程 `GET /rest/thread/<slug>` (该调用页面制作,形状测量在主人线19/10上):每转弯一个单位——问题来自 `query_str`,从 `ask_text` 块的标记下——顺序,标题从线条; `has_next_page` 名称完整。 查询参数值是我们的(样本只保留名称);如果模糊性拒绝,屏幕阅读器仍会被剪辑,标记 `source:"dom"` 与缘由. `createPerplexityReader(o?)` 现在拿 `{ fetch }` 和其他数据阅读器一样.
-   **注解读取器(0.22.0,F099.22):** `notebooklmReader` 编号 : `createNotebookLMReader()` 内 `chatReaders` —— 说 `notebook.google.com` 财务报告和审定财务报表 `notebooklm.google.com` `/notebook/<id>`,读自《瑞典刑法》: `chat-message` 是一个转弯(问题 `.from-user-container`输入 `.to-user-container` 从结构块,倒塌的思维链和被忽略的动作按钮,引用标记为 `[n]`笔记封面的标题 诚实 `complete:false`:数据通过批量执行而来,还没有样本抓住页面要求的内容,笔记本的来源也不包括在内.
-   **深度阅读器(0.21.0,F099.22):** `deepseekReader` 编号 : `createDeepSeekReader()` 内 `chatReaders` —聊天. depseek.com (中文(简体) ) `/a/chat/s/<id>`,读自SCREEN(`.ds-message`; 答案 `.ds-markdown`诚实 `complete:false, source:"dom", missing:"turns"`: DeepSeek只保留取景口附近的转弯,其数据调用需要拥有者样本没有携带的无记名标志,因此数据阅读器等待一个被测量的形状.
-   **页面夹中的背景图像(0.2.0,F099.27):** 与 `createArticleExtractor({ images: true })`,将相片设定为 CSS 内置背景(`style="background-image:url(…)"` 页面主要区域中的滑行者、英雄、牌网(`main`编号 :`[role=main]`,则 `article`,否则,身体减去头/导航/脚)现在也作为图像资产,通过相同的盖子,并列为 `![](url)` 在页面单元的 END 中(可读性从文本中去掉)。 渐变、 svg 、 隐藏元素和页眉/ 导航/ 脚被忽略; 一个 URL 已被使用 `<img>` 来一次 一个占位符 `src="data:,"` 一个 `<img>` 不再隐藏它 `srcset`。只有内出行样式——样式表的背景不读取. **输出变化 :** 有这种背景的页面现在携带更多的资产和额外资产 `![](url)` 线条.
-   **Le Chat阅读器(0.19.0,F099.22):** `lechatReader` 编号 : `createLeChatReader()` 内 `chatReaders` - 聊天. mistral.ai (英语) `/chat/<id>` 财务报告和审定财务报表 `/work/<id>`,从DATA读取(该页发送的同为tRPC批次: `chat.byId,message.all,canva.all`页:1 `content` 或文本块(工具调用被忽略), Le Chat 的画布作为制作它们的答案上的文件. 未猜测, 名称在 `completeness` 取而代之的是:经编辑的信件的旧版本(最新版本在线),又一页(`nextCursor`页:1 没有屏幕回落——样本只显示答案'容器.
-   **Passkey (0.18.0, F099.26) 首发即时屏幕:** `EmbeddedLoginForm` 这样 `ClipperPopup` 与 `embeddedLogin` ——现在在BID批准模拟 v2:BID的帧(BID的帧)后打开密码`embedded-login-passkey-frame`)),"安德烈·穆利赫德"(英语:`embedded-login-other-options`\+ 带有 Log in med adgangsnøgle的邮件编码 (`embedded-login-back-passkey`) (中文(简体) ). 客户端 BID 不提供密码( 密码/ 启动拒绝: 403/ 404 或 年长的 BID 的 400 ) `error: {kind: "passkey", reason: "not_enabled"}` 从引擎和经典的邮件屏幕上——从不倒回窗口. **行为改变** 页:1 `embeddedLogin` 弹出:第一个屏幕是密码. 上面的 README 线约 `EmbeddedLoginForm` 不绘制密码步骤被取代 .
-   **嵌入式登录屏幕(0.16.0,F099.24):** `EmbeddedLoginForm` 财务报告和审定财务报表 `CodeInput` 内 `/ui` 绘图 `auth.embedded()` 在BID的模拟01a1409a——邮件——6-box代码(Paste或一次性代码自动填入第一个框填充全部6个)——第二个系数——签入后,加上“Vent lidt”(锁入/太多,有BID的分数)和“Ingen forbindelse”(加上“Prøv igen”)。 每个控制器都带有 `embedded-login-*` data-testid(模拟器自己的). `ClipperPopup` 通过时在 INSID 中记录弹出 `embeddedLogin` (0.16.1:选入——0.16.0在BID为终点服务之前自行打开;跳过0.16.0;如果BID回答 `not_embedded` 或404 它自己掉回了旧窗口按钮。 一个绘制引擎的方法——如果你的弹出看起来不同,就自己画. 实例/网络剪辑器上的镜头验证 `?state=embedded` (页面合同-v1 BID).
-   **YouTube视频(Jam F002.35):** `youtubeReader` (从) `/extract`——在取出文章前列出. 打开 `youtube.com/watch`, (中文(简体) ), `youtu.be/<id>`, (中文(简体) ), `/shorts/<id>` 财务报告和审定财务报表 `/live/<id>` (另见: `m.` 财务报告和审定财务报表 `music.`片段是一类 `"video"` 与 `unit.video = { provider: "youtube", videoId, url, title, channel?, start?, thumbnailUrl, format? }`。视频id来自URL,从不来自页面:在网页导航YouTube之后 `<meta>` 标记仍然可以描述之前的视频,所以它们只有在给这段视频取名id时才被使用. `start` 整个秒从 `t=`编号 :`start=` (单位:千美元)`83`, (中文(简体) ), `83s`, (中文(简体) ), `1m23s`, (中文(简体) ), `1h2m3s`),且不在单位内容中,因此在另一个时刻被剪辑的同名视频是"同",而不是分行. `thumbnailUrl` 实值 `i.ytimg.com/vi/<id>/hqdefault.jpg`,从身份证上建立起来的——什么也没有拿来. YouTube主页,频道和搜索仍然去到文章摘录.
-   **网页图像(0.11.0,F099.23):** `createArticleExtractor({ images: true, maxImages, maxImageBytes, maxImageFileBytes?, minImageSize? })` 从 `/extract` - 把它放在提取器列表中(它与每一页都匹配). 页面单位得到 `assets` 对于文章的内容图像(`{ type: "image", url, alt, name, mime, size, width?, height?, encoding: "base64", content }`; svg 以文本的形式出现,在页面中以自己的会话获取,所以登录后的图像也会来. `url` 确切的地址 在减记 `![alt](url)` ——在您存储的文件中标注该引用 。 离开 : `role=presentation`, (中文(简体) ), `aria-hidden`, svg 图标(`#fragment`),且其大小(取自标记或取自其PNG/GIF/JPEG/WebP标题)在下方的任何图像 `minImageSize` (64个px). 盖子是需要通过(所有者15/10:每Jam org;未设定为每段50个图像/100 MB). 盖上的图像或无法获取的图像在 `completeness.missingFiles` (单位:千美元)`clip_limit` 编号 : `too_large` 编号 : `http` 编号 : `network`),与 `complete: false, missing: "files"` ——文字完整. `articleExtractor` 本身没有变化(只有文本).
-   **每个分支(0.0.0,F099.21):** 对话是一棵树(经编辑的提示,“再次”). `clip.units` 仍然是屏幕上的线程; `clip.branches?: Unit[][]` 保持每条其它行 从根到叶的整条线 都有自己的0.n-1位置 与主线共用的拉伸是相同的单位,因此它印出相同的和对话的行叉. 当没有树枝的时候(弹出可以说"148ture" ^ 2 sidegrene",来自 `clip.branches.length`) (中文(简体) ). `sendClip` 将主线 First 和以前一样发送到 `appendOnly` 目的地——每条边线作为One发送其WALE路径 `ctx.side = { index, of, from }` (单位:千美元)`from` =该批次的首发位置;一长行仍分批进行. 将它与你持有的东西相对应:共享的长度,新的单位挂在您共享的最后一个链接上. 您已完整持有的一行边线被跳过; 执行可选 `heldKeys(anchor, target, session)` (你为锚所持的每条链子——整棵树) 所以一个回扣知道,或者每个侧线 被怨恨每个片段(无害,键入,但数入) `sent`) (中文(简体) ). 一个不是附件的目的地 只有主线发送一个, 分支打开 `ctx.clip.branches`。在多行显示的文件会被取出一次;一个克洛德文物的版本跟随自己的行.
-   **大文件流直接到您的存储( 0. 9. 0, F099. 20) :** 设定 `streamOverBytes` 在读取器上(Jam: 25 MB). 上面的文件不放入剪辑中;它以 `{ type: "file", name, mime, size, transfer: "stream", streamRef }`。然后,在扩展中:获得 `partsNeeded(asset.size, partSize)` 您服务器上预先签名的部分地址( E)`@broberg/media` 0.6.0 (中文(简体) ) `multipart.create` + 键 `presignPart`),电话 `streamAsset(asset, { partUrls, partSize })` 从 `/extension` 页面用自己的会话读取文件, PUT 将它部分地( 一次在内存中的一部分) —— 在您的服务器上完成 `multipart.complete(etags)` (或 `abort` 如果它扔了的话。 5个小米B,但最后一个除外。 水桶的CORS必须允许 `PUT` 从 [https://claude.ai (英语)](https://claude.ai/) 财务报告和审定财务报表 [https://chatgpt.com (中文(中国大陆) )](https://chatgpt.com/) 并揭露 `ETag`; 没有可见的 ETag 的部件失败了, 信件会这么说 .
-   **1高(0.6.2):** 弹出在每一个州都保持同样的高度(日志为 + 剪辑 → 剪辑 → 结果). 设置在您的扩展 CSS: `.bwc-popup { --bwc-popup-height: 480px }` (默认为 420px).
-   **从不沉默(0.5.0):** 一个不完整的剪辑显示 `clipper-incomplete` 原因; 在未显示对话的页面上获得同意的目的地 `clipper-not-a-conversation`说多少个单位去了. `ClipperPopup({ text: { noTargets: "…" } })` 替换弹出式文本.
-   **弹出** (序言, `@broberg/theme` 标记, 无本地控件 : 登录, 选择目标( 海关列表框, 键盘包含, 每个目的地记住) , 剪辑, 以及一个状态线, 分别告诉“ log ind igen” , “ kunne ikke nå frem” 和“ afvist” 。 每个互动要素都有: `clipper-*` 数据测试.
-   `examples/web-clipper` 是一个完整的扩展( e)`pnpm --filter web-clipper-example build` → `dist/`,装入已卸出)加上同样的弹出作为Lens的一页(`preview`, : 5200) (中文(简体) ).

接下来是第一次发布(F099.7),然后是Trail的剪辑机和上面的Jam原型.

## 常见问题

### @broberg/web-clipper 是什么？

分舰队的一个网络剪接基站\(Trail Web Clipper, Jam Clipper\):/核心指纹\+链条\+分支引擎,/提取器插入点\+ 特雷尔的文章提取器 / 运输目的地 \+ 发送者\(仅新单元,同位素重传, 401/5xx 规则,

### 如何安装 @broberg/web-clipper？

在项目目录运行 npm install @broberg/web-clipper，或使用 pnpm add / yarn add / bun add。

### @broberg/web-clipper 支持 TypeScript 吗？

支持。包内自带 TypeScript 类型声明（package\.json 声明了 types 入口），安装后即可获得类型提示，无需再安装 @types 包。

### @broberg/web-clipper 支持 ESM 还是 CommonJS？

同时提供 ES Module 与 CommonJS 入口，import 和 require\(\) 都可以使用，打包工具会自动选择合适的版本。

### @broberg/web-clipper 可以商用吗？

@broberg/web-clipper 使用 MIT 许可。宽松许可：允许商业使用、修改和再分发，通常只需在分发时保留原版权与许可声明。以上为概括，不构成法律意见，请以许可原文为准。

### @broberg/web-clipper 的下载量有多少？

根据 npm 官方下载统计，@broberg/web-clipper 在 2026/10/09 – 2026/11/07 这 30 天内被下载 4,191 次，日均约 140 次。下载量会把 CI 构建、镜像同步和作为其他包依赖的安装一并计入，适合横向比较热度，不能直接等同于使用人数。

### @broberg/web-clipper 还在维护吗？

最新版本 0\.27\.1 发布于 2026/10/25（14 天前）；至今共发布 45 个版本，其中过去 12 个月发布了 45 个；首次发布于 2026/10/13。发布频率只是参考信号：功能稳定的工具可能很少需要更新，建议结合源码仓库的 issue 和提交记录判断。

