@broberg/web-clipper

中文文档
0.27.1发布于 2026/10/25MITTypeScriptESM + CJS构建来源已验证近 30 天下载 4191

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

中文使用文档
文档目录6 节
  1. @broberg/web-clipper/core 指纹引擎(F099.2)
  2. @broberg/web-clipper/extract ——取出插头点(F099.3)
  3. @broberg/web-clipper/transport 目的地和发送者(F099.4)
  4. @broberg/web-clipper/auth —— BrobergID 从扩展名登录(F099.5)
  5. 绘制您的 OWN 弹出 : /engine (0.7.0, F099.12) (中文(简体) )
  6. 扩展名 : /extension , (中文(简体) ), /content , (中文(简体) ), /vite ,…

@broberg/网络剪辑器

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

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

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)

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)

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)

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中被测量. 所以说:
    // 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 和文本:

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 (单位:千美元)

// 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: "[email protected]" }),
}));

// 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/…和克劳德(Eclaude.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] ——锚 = 线粒体. 就像双子座一样 它现在读了SCREENcomplete: 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://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 共发布 45 个版本,首次发布于 2026/10/13;过去 12 个月发布了 45 个版本。最新版本附带 npm provenance 构建来源证明,可追溯到公开的 CI 构建记录。

版本发布日期距今
0.27.1latest2026/10/2514 天前
0.27.02026/10/2514 天前
0.26.02026/10/2019 天前
0.25.02026/10/2019 天前
0.24.02026/10/2019 天前
0.23.02026/10/2019 天前

关于 @broberg/web-clipper 的常见问题

@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 和提交记录判断。